pax_global_header00006660000000000000000000000064146037504420014517gustar00rootroot0000000000000052 comment=7e82013994fe3e90f236667656bb47d2dc386d84 fwupd-1.9.16/000077500000000000000000000000001460375044200127425ustar00rootroot00000000000000fwupd-1.9.16/.circleci/000077500000000000000000000000001460375044200145755ustar00rootroot00000000000000fwupd-1.9.16/.circleci/config.yml000066400000000000000000000055341460375044200165740ustar00rootroot00000000000000version: 2 jobs: build-ubuntu-x86_64: machine: image: ubuntu-2204:edge steps: - checkout - run: name: "Build container" command: OS=ubuntu-x86_64 ./contrib/ci/generate_docker.py build - run: name: "Run build script" command: docker run --privileged -e CI=true -t -v `pwd`:/github/workspace fwupd-ubuntu-x86_64 - persist_to_workspace: root: . paths: - "dist/share/doc" build-windows: docker: - image: fedora:38 steps: - checkout - run: name: "Build Win32" command: ./contrib/ci/build_windows.sh - persist_to_workspace: root: . paths: - "dist/setup/*.msi" - "dist/VERSION" - "dist/news.txt" - store_artifacts: path: dist/setup publish-docs: machine: image: ubuntu-2204:edge steps: - attach_workspace: at: . - add_ssh_keys: fingerprints: - "d8:73:05:1b:7c:93:8c:12:41:78:15:3d:5d:af:b4:c2" - run: name: Clone docs working_directory: dist/share/doc/fwupd command: | git clone --depth 1 git@github.com:fwupd/fwupd.github.io.git - deploy: name: Trigger docs deployment working_directory: dist/share/doc/fwupd/fwupd.github.io command: | git config credential.helper 'cache --timeout=120' git config user.email "info@fwupd.org" git config user.name "Documentation deployment Bot" rm -rf * cp ../../libfwupd* ../*html . -R git add . git commit -a --allow-empty -m "Trigger deployment" git push git@github.com:fwupd/fwupd.github.io.git publish-github-exe-release: docker: - image: circleci/golang:1.17.5 steps: - attach_workspace: at: . - run: name: "Publish Release on GitHub" command: | go get github.com/tcnksm/ghr VERSION=$(cat dist/VERSION) BODY=$(cat dist/news.txt) ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -b "${BODY}" ${VERSION} ./dist/setup/ workflows: version: 2 main: jobs: - build-windows - build-ubuntu-x86_64 deploy: jobs: - build-ubuntu-x86_64: filters: branches: ignore: /.*/ tags: only: /^\d+\.\d+\.\d+$/ - build-windows: filters: branches: ignore: /.*/ tags: only: /^\d+\.\d+\.\d+$/ - publish-github-exe-release: requires: - build-windows filters: branches: ignore: /.*/ tags: only: /^\d+\.\d+\.\d+$/ fwupd-1.9.16/.clang-format000066400000000000000000000026141460375044200153200ustar00rootroot00000000000000--- AlignAfterOpenBracket: 'Align' AlignConsecutiveAssignments: 'false' AlignConsecutiveDeclarations: 'false' AlignConsecutiveMacros: 'true' AlignOperands: 'true' AlignTrailingComments: 'true' AllowAllArgumentsOnNextLine: 'false' AllowAllParametersOfDeclarationOnNextLine: 'false' AllowShortBlocksOnASingleLine: 'false' AllowShortCaseLabelsOnASingleLine: 'false' AllowShortFunctionsOnASingleLine: 'Inline' AllowShortIfStatementsOnASingleLine: 'false' AlwaysBreakAfterReturnType: 'All' BinPackParameters: 'false' BinPackArguments: 'false' BreakBeforeBraces: 'Linux' ColumnLimit: '100' DerivePointerAlignment: 'false' IndentCaseLabels: 'false' IndentWidth: '8' IncludeBlocks: 'Regroup' KeepEmptyLinesAtTheStartOfBlocks: 'false' MaxEmptyLinesToKeep: '1' PointerAlignment: 'Right' SortIncludes: 'true' SpaceAfterCStyleCast: 'false' SpaceBeforeAssignmentOperators : 'true' SpaceBeforeParens: 'ControlStatements' SpaceInEmptyParentheses: 'false' SpacesInSquareBrackets: 'false' TabWidth: '8' UseTab: 'Always' PenaltyBreakAssignment: '3' PenaltyBreakBeforeFirstCallParameter: '15' --- Language: 'Proto' --- Language: 'Cpp' IncludeCategories: - Regex: '^"config.h"$' Priority: '0' - Regex: '' Priority: '1' - Regex: '^<' Priority: '2' - Regex: 'fwupd' Priority: '3' - Regex: '.*' Priority: '4' ... fwupd-1.9.16/.clang-tidy000066400000000000000000000023561460375044200150040ustar00rootroot00000000000000--- Checks: "-*,\ bugprone-*,\ -bugprone-assignment-in-if-condition,\ -bugprone-easily-swappable-parameters,\ -bugprone-implicit-widening-of-multiplication-result,\ -bugprone-macro-parentheses,\ -bugprone-misplaced-widening-cast,\ -bugprone-narrowing-conversions,\ -bugprone-reserved-identifier,\ -bugprone-too-small-loop-variable,\ -bugprone-unchecked-optional-access,\ misc-*,\ -misc-confusable-identifiers,\ -misc-const-correctness,\ -misc-non-private-member-variables-in-classes,\ -misc-no-recursion,\ -misc-static-assert,\ -misc-unused-parameters,\ modernize-*,\ -modernize-macro-to-enum,\ -modernize-use-trailing-return-type,\ -modernize-use-transparent-functors,\ performance-*,\ -performance-inefficient-vector-operation,\ -performance-no-int-to-ptr,\ readability-*,\ -readability-avoid-const-params-in-decls,\ -readability-braces-around-statements,\ -readability-function-cognitive-complexity,\ -readability-identifier-length,\ -readability-identifier-naming,\ -readability-implicit-bool-conversion,\ -readability-isolate-declaration,\ -readability-magic-numbers,\ -readability-non-const-parameter,\ -readability-qualified-auto,\ -readability-redundant-declaration,\ -readability-suspicious-call-argument,\ -readability-uppercase-literal-suffix,\ " ... fwupd-1.9.16/.editorconfig000066400000000000000000000002161460375044200154160ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf [*.py] indent_style = space indent_size = 4 [*.{c|h}] indent_style = tab indent_size = 8 fwupd-1.9.16/.git-blame-ignore-revs000066400000000000000000000000511460375044200170360ustar00rootroot0000000000000072819f91c19e076ca383e6e9fd7c7a510c62792e fwupd-1.9.16/.gitconfig000066400000000000000000000000611460375044200147110ustar00rootroot00000000000000[blame] ignoreRevsFile = .git-blame-ignore-revs fwupd-1.9.16/.github/000077500000000000000000000000001460375044200143025ustar00rootroot00000000000000fwupd-1.9.16/.github/ISSUE_TEMPLATE/000077500000000000000000000000001460375044200164655ustar00rootroot00000000000000fwupd-1.9.16/.github/ISSUE_TEMPLATE/bug-report-general.md000066400000000000000000000015021460375044200225060ustar00rootroot00000000000000--- name: Bug report (General) about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **Steps to Reproduce** Steps to reproduce the behavior. **Expected behavior** A clear and concise description of what you expected to happen. **fwupd version information** Please provide the version of the daemon and client. ```shell fwupdmgr --version ``` Please note how you installed it (`apt`, `dnf`, `pacman`, source, etc):
**fwupd device information** Please provide the output of the fwupd devices recognized in your system. ```shell fwupdmgr get-devices --show-all-devices ```
**Additional questions** - Operating system and version: - Have you tried rebooting? - Is this a regression? fwupd-1.9.16/.github/ISSUE_TEMPLATE/bug-report-uefi.md000066400000000000000000000021641460375044200220260ustar00rootroot00000000000000--- name: Bug report (UEFI Updates) about: Issues involving UEFI device updates title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **Steps to Reproduce** Steps to reproduce the behavior. **Expected behavior** A clear and concise description of what you expected to happen. **fwupd version information** Please provide the version of the daemon and client. ```shell fwupdmgr --version ``` Please note how you installed it (`apt`, `dnf`, `pacman`, source, etc):
**fwupd device information** Please provide the output of the fwupd devices recognized in your system. ```shell fwupdmgr get-devices --show-all-devices ```
**System UEFI configuration** Please provide the output of the following commands: ```shell efibootmgr -v ``` ```shell efivar -l | grep fw ``` ```shell tree /boot ``` **Additional questions** - Operating system and version: - Have you tried rebooting? - Is this a regression? - Are you using an NVMe disk? - Is secure boot enabled? - Is this a Lenovo system with 'Boot Order Lock' turned on in the BIOS? fwupd-1.9.16/.github/ISSUE_TEMPLATE/bug-report-wd19.md000066400000000000000000000031271460375044200216620ustar00rootroot00000000000000--- name: Bug report (Dell WD19) about: Create a report to help us improve title: 'Dell WD19 upgrade issue' labels: bug assignees: 'cragw' --- **Describe the bug** A clear and concise description of what the bug is. **Steps to Reproduce** Steps to reproduce the behavior. **Expected behavior** A clear and concise description of what you expected to happen. **fwupd version information** Please provide the version of the daemon and client. ```shell fwupdmgr --version ``` Please note how you installed it (`apt`, `dnf`, `pacman`, source, etc):
**fwupd device information** Please provide the output of the external fwupd devices recognized in your system. ```shell fwupdmgr get-devices --filter=~internal ```
**Dock SKU** Please mention which module is installed in your WD19. - [ ] WD19 (Single-C) - [ ] WD19TB (Thunderbolt) - [ ] WD19DC (Dual-C) **Peripherals connected to the dock** Please describe all devices connected to the dock. Be as specific as possible, including USB devices, hubs, monitors, and downstream type-C devices. **Verbose daemon logs** First enable daemon verbose logs collection. ```shell fwupdmgr modify-config "VerboseDomains" "*" ``` Then try to reproduce the issue. Even if it doesn't reproduce, please attach the daemon verbose logs collected from the system journal. ```shell journalctl -b -u fwupd.service ``` **Additional questions** - Operating system and version: - Have you tried unplugging the dock or any peripherals from your machine? - Have you tried to power cycle the dock from the AC adapter? - Is this a regression? fwupd-1.9.16/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011341460375044200222110ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. fwupd-1.9.16/.github/ISSUE_TEMPLATE/question.md000066400000000000000000000006071460375044200206610ustar00rootroot00000000000000--- name: Question about: Ask a question about the project or how to do something title: '' labels: question assignees: '' --- **Describe the question** A clear and concise description of what you are wondering about. **fwupd version information** Please provide the version of the daemon and client if applicable to your current installation of `fwupd`. ```shell fwupdmgr --version ``` fwupd-1.9.16/.github/dependabot.yml000066400000000000000000000001661460375044200171350ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" fwupd-1.9.16/.github/pull_request_template.md000066400000000000000000000002731460375044200212450ustar00rootroot00000000000000Type of pull request: - [ ] New plugin (Please include [new plugin checklist](https://github.com/fwupd/fwupd/wiki/New-plugin-checklist)) - [ ] Code fix - [ ] Feature - [ ] Documentation fwupd-1.9.16/.github/stale.yml000066400000000000000000000023251460375044200161370ustar00rootroot00000000000000# Number of days of inactivity before an issue becomes stale daysUntilStale: 30 # Number of days of inactivity before a stale issue is closed daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - enhancement - regression # Label to use when marking an issue as stale staleLabel: wontfix # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Please note: We are just a few people who contribute to a shared project, and it's impossible for us to fix every bug with such limited resources. If you want to investigate and try to help solve this yourself, we will review all pull requests from new contributors. If this is issue is important to you for your business please talk with your technical account manager about arranging resources to solve this issue. You might even consider hiring someone to write the code if you're unable to do so yourself, e.g. see: https://fwupd.org/lvfs/docs/consulting # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false fwupd-1.9.16/.github/workflows/000077500000000000000000000000001460375044200163375ustar00rootroot00000000000000fwupd-1.9.16/.github/workflows/ci.yml000066400000000000000000000020521460375044200174540ustar00rootroot00000000000000name: Continuous Integration on: push: branches: [ 1_9_X ] jobs: snap: uses: ./.github/workflows/snap.yml with: deploy: true secrets: inherit matrix: uses: ./.github/workflows/matrix.yml fuzzing: permissions: actions: read # to fetch the artifacts (google/oss-fuzz/infra/cifuzz/actions/run_fuzzers) contents: read # to clone the repo (google/oss-fuzz/infra/cifuzz/actions/run_fuzzers) runs-on: ubuntu-latest timeout-minutes: 20 steps: - name: Build Fuzzers id: build uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master with: oss-fuzz-project-name: 'fwupd' dry-run: false - name: Run Fuzzers uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master with: oss-fuzz-project-name: 'fwupd' fuzz-seconds: 150 dry-run: false - name: Upload Crash uses: actions/upload-artifact@v3 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts fwupd-1.9.16/.github/workflows/codeql-analysis.yml000066400000000000000000000020001460375044200221420ustar00rootroot00000000000000name: "CodeQL" on: push: branches: [ main ] pull_request: branches: [ main ] jobs: analyze: name: Analyze runs-on: ubuntu-22.04 permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'cpp', 'python' ] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} - name: Install dependencies run: > sudo apt-get update && sudo ./contrib/ci/fwupd_setup_helpers.py install-dependencies --yes -o ubuntu && python3 -m pip install --user "meson >= 0.62.0" - name: Build run: | mkdir -p $GITHUB_WORKSPACE/build cd $GITHUB_WORKSPACE/build meson setup .. -Dman=false --prefix=$GITHUB_WORKSPACE/dist ninja - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 fwupd-1.9.16/.github/workflows/create_containers.yml000066400000000000000000000020441460375044200225520ustar00rootroot00000000000000name: Create containers on: workflow_dispatch: schedule: - cron: '0 0 * * *' permissions: contents: read jobs: push_to_registry: permissions: packages: write # for docker/build-push-action runs-on: ubuntu-latest strategy: fail-fast: false matrix: os: [fedora, debian-x86_64, arch, debian-i386, void] steps: - name: Check out the repo uses: actions/checkout@v4 - name: "Generate Dockerfile" env: OS: ${{ matrix.os }} run: ./contrib/ci/generate_docker.py - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Push to GitHub Packages uses: docker/build-push-action@v5 with: context: . push: true tags: ghcr.io/fwupd/fwupd/fwupd-${{matrix.os}}:latest fwupd-1.9.16/.github/workflows/matrix.yml000066400000000000000000000027341460375044200203740ustar00rootroot00000000000000on: workflow_call: jobs: build: runs-on: ubuntu-latest strategy: matrix: os: [fedora, debian-x86_64, arch, debian-i386] steps: - uses: actions/checkout@v4 - name: Docker login run: docker login docker.pkg.github.com -u $GITHUB_ACTOR -p $GITHUB_TOKEN env: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - name: Build in container env: CI_NETWORK: true CI: true run: | echo $GITHUB_WORKSPACE docker run --privileged -e CI=true -t -v $GITHUB_WORKSPACE:/github/workspace docker.pkg.github.com/fwupd/fwupd/fwupd-${{matrix.os}}:latest openbmc: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Refresh dependencies run: sudo apt update - name: Install dependencies run: | sudo ./contrib/ci/fwupd_setup_helpers.py install-dependencies -o ubuntu --yes sudo ./contrib/ci/fwupd_setup_helpers.py test-meson - name: Build run: | ./contrib/build-openbmc.sh --prefix=/home/runner/.root macos: runs-on: macos-12 steps: - name: install dependencies run: | brew install meson libgusb gobject-introspection sqlite libarchive json-glib curl gnutls protobuf-c vala gi-docgen python3 -m pip install --user jinja2 - uses: actions/checkout@v4 - name: configure run: ./contrib/ci/build_macos.sh - name: build run: ninja -C build-macos fwupd-1.9.16/.github/workflows/pull-request-reviews.yml000066400000000000000000000024351460375044200232120ustar00rootroot00000000000000name: Pull Request reviews on: pull_request: branches: [ 1_9_X ] permissions: contents: read # to fetch code (actions/checkout) jobs: pre-commit: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Refresh dependencies run: sudo apt update - name: Install dependencies run: sudo apt install shellcheck clang-format -y - name: Run pre-commit hooks run: | ./contrib/setup source venv/bin/activate sed -i "/no-commit-to-branch/,+1d" .pre-commit-config.yaml pre-commit run --hook-stage commit --all-files abi: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Refresh dependencies run: sudo apt update - name: Install dependencies run: | sudo ./contrib/ci/fwupd_setup_helpers.py install-dependencies -o ubuntu --yes sudo ./contrib/ci/fwupd_setup_helpers.py test-meson - name: Check ABI run: ./contrib/ci/check-abi $(git describe --abbrev=0 --tags) $(git rev-parse HEAD) snap: uses: ./.github/workflows/snap.yml with: deploy: false secrets: inherit matrix: uses: ./.github/workflows/matrix.yml fwupd-1.9.16/.github/workflows/snap.yml000066400000000000000000000040531460375044200200250ustar00rootroot00000000000000name: Snap workflow on: workflow_call: inputs: deploy: required: true type: boolean jobs: build-snap: runs-on: ubuntu-latest outputs: snap_name: ${{ steps.snapcraft.outputs.snap }} channel: ${{ steps.channel.outputs.channel }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - id: channel run: | if git describe --exact-match; then echo "::set-output name=channel::1.9.x/candidate" else echo "::set-output name=channel::1.9.x/edge" fi - id: prep run: | mkdir -p snap ln -s ../contrib/snap/snapcraft.yaml snap/snapcraft.yaml - uses: snapcore/action-build@v1 id: snapcraft - uses: actions/upload-artifact@v4 with: name: snap path: ${{ steps.snapcraft.outputs.snap }} test-snap: needs: build-snap runs-on: ubuntu-latest steps: - id: checkout uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/download-artifact@v4 id: download with: name: snap - id: install-snap run: | sudo snap install --dangerous ${{ needs.build-snap.outputs.snap_name }} sudo snap connect fwupd:polkit :polkit sudo fwupd.fwupdtool enable-test-devices - name: Run fwupdmgr tests run: sudo /snap/fwupd/current/share/installed-tests/fwupd/fwupdmgr.sh - name: Run fwupd tests run: sudo /snap/fwupd/current/share/installed-tests/fwupd/fwupd.sh - name: Run fwupdtool tests run: sudo /snap/fwupd/current/share/installed-tests/fwupd/fwupdtool.sh deploy-store: needs: [build-snap, test-snap] runs-on: ubuntu-latest if: ${{ inputs.deploy }} steps: - uses: actions/download-artifact@v4 id: download with: name: snap - uses: snapcore/action-publish@v1 env: SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }} with: snap: ${{ needs.build-snap.outputs.snap_name }} release: ${{ needs.build-snap.outputs.channel }} fwupd-1.9.16/.gitignore000066400000000000000000000007521460375044200147360ustar00rootroot00000000000000/build /build-win32 /dist /.vscode .vscode-ctags /build-dir /.flatpak-builder /repo *.flatpak *.snap /fwupd_source.tar.bz2 /parts /prime /stage /snap/.snapcraft /libxmlb /*.deb /*.ddeb /*.changes /*.buildinfo /fwupd*.build /*.dsc /*.xz /*.gz /venv __pycache__ plugins/acpi-dmar/tests/ plugins/acpi-facp/tests/ plugins/ata/tests/ plugins/dfu/tests/ plugins/nvme/tests/ plugins/synaptics-prometheus/tests/ plugins/tpm-eventlog/tests/ plugins/uefi-dbx/tests/ .buildconfig .ossfuzz *.rej /snap fwupd-1.9.16/.gitmodules000066400000000000000000000001561460375044200151210ustar00rootroot00000000000000[submodule "contrib/flatpak"] path = contrib/flatpak url = https://github.com/flathub/org.freedesktop.fwupd fwupd-1.9.16/.markdownlint.json000066400000000000000000000002131460375044200164200ustar00rootroot00000000000000{ "default": true, "MD033": false, "MD041": false, "MD036": false, "MD013": { "tables": false, "line_length": 1000 } } fwupd-1.9.16/.pre-commit-config.yaml000066400000000000000000000054161460375044200172310ustar00rootroot00000000000000default_stages: [commit] repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: no-commit-to-branch args: [--branch, main, --pattern, 1_.*_X] - id: check-added-large-files - id: check-byte-order-marker - id: check-executables-have-shebangs - id: forbid-new-submodules - id: check-yaml exclude: '.clang-format' - id: check-json - id: pretty-format-json args: ['--no-sort-keys'] - id: check-symlinks - id: check-xml - id: end-of-file-fixer types_or: [c, shell, python, proto] - id: trailing-whitespace types_or: [c, shell, python, xml] - id: check-docstring-first - id: check-merge-conflict - id: mixed-line-ending args: [--fix=lf] - repo: https://github.com/codespell-project/codespell rev: v2.2.2 hooks: - id: codespell args: ['--config', './contrib/codespell.cfg', --write-changes] - repo: https://github.com/ambv/black rev: 22.12.0 hooks: - id: black - repo: local hooks: - id: check-deprecated name: check for use of any deprecated items language: script entry: ./contrib/ci/check-deprecated.sh - id: check-null-false-returns name: check for null / false return mismatch language: script entry: ./contrib/ci/check-null-false-returns.py - id: check-potfiles name: check for missing translated files from potfiles language: script entry: ./contrib/ci/check-potfiles.py - id: check-finalizers name: check for missing GObject parent finalize language: script entry: ./contrib/ci/check-finalizers.py - id: check-headers name: check for superfluous includes language: script entry: ./contrib/ci/check-headers.py - id: check-source name: check source code for common issues language: script entry: ./contrib/ci/check-source.py - id: check-quirks name: check quirk style language: script entry: ./contrib/ci/check-quirks.py - id: shellcheck name: check shellscript style language: system entry: shellcheck --severity=warning -e SC2068 types: [shell] - id: run-tests name: run tests before pushing language: script entry: ./contrib/run-tests.sh stages: [push] - id: clang-format name: clang-format language: script entry: ./contrib/reformat-code.py types: [c] - id: check-license name: Check license header types_or: [shell, c, python] language: script entry: ./contrib/ci/check-license.py - repo: https://github.com/igorshubovych/markdownlint-cli rev: v0.33.0 hooks: - id: markdownlint args: ['--fix', '--ignore', '.github'] fwupd-1.9.16/.tx/000077500000000000000000000000001460375044200134535ustar00rootroot00000000000000fwupd-1.9.16/.tx/config000066400000000000000000000002411460375044200146400ustar00rootroot00000000000000[main] host = https://www.transifex.com [o:freedesktop:p:fwupd:r:main] file_filter = po/.po source_file = po/fwupd.pot source_lang = en type = PO fwupd-1.9.16/CODE_OF_CONDUCT.md000066400000000000000000000062231460375044200155440ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fwupd@googlegroups.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ fwupd-1.9.16/COMMITMENT000066400000000000000000000037761460375044200143560ustar00rootroot00000000000000Common Cure Rights Commitment, version 1.0 Before filing or continuing to prosecute any legal proceeding or claim (other than a Defensive Action) arising from termination of a Covered License, we commit to extend to the person or entity ('you') accused of violating the Covered License the following provisions regarding cure and reinstatement, taken from GPL version 3. As used here, the term 'this License' refers to the specific Covered License being enforced. However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. We intend this Commitment to be irrevocable, and binding and enforceable against us and assignees of or successors to our copyrights. Definitions 'Covered License' means the GNU General Public License, version 2 (GPLv2), the GNU Lesser General Public License, version 2.1 (LGPLv2.1), or the GNU Library General Public License, version 2 (LGPLv2), all as published by the Free Software Foundation. 'Defensive Action' means a legal proceeding or claim that We bring against you in response to a prior proceeding or claim initiated by you or your affiliate. 'We' means each contributor to this repository as of the date of inclusion of this file, including subsidiaries of a corporate contributor. This work is available under a Creative Commons Attribution-ShareAlike 4.0 International license. fwupd-1.9.16/CONTRIBUTING.md000066400000000000000000000056421460375044200152020ustar00rootroot00000000000000# Contributor Guidelines ## Getting started To set up your local fwupd development environment, from the top level of the checkout run: ```shell ./contrib/setup ``` This will create pre-commit hooks to fixup many code style issues before your code is submitted. On some Linux distributions this will install all build dependencies needed to compile fwupd as well. A [virtualenv](https://virtualenv.pypa.io/en/latest/user_guide.html) will be created in `venv/` in the checkout that is used for building and running fwupd without affecting the local system installation. To enter this virtualenv run: ```shell source venv/bin/activate ``` To build fwupd in the venv run: ```shell build-fwupd ``` Wrappers are configured while in the venv to run `fwupdtool`, `fwupd`, and `fwupdmgr` using the virtualenv directory structure. To leave the virtualenv run: ```shell deactivate ``` ## Coding Style The coding style to respect in this project is very similar to most GLib projects. In particular, the following rules are largely adapted from the PackageKit Coding Style. * 8-space tabs for indentation * Prefer lines of less than <= 100 columns * No spaces between function name and braces (both calls and macro declarations) * If function signature/call fits in a single line, do not break it into multiple lines * Prefer descriptive names over abbreviations (unless well-known) and shortening of names. e.g `device` not `dev` * Single statements inside if/else should not be enclosed by '{}' * Use comments to explain why something is being done, but also avoid over-documenting the obvious. Here is an example of useless comment: // Fetch the document fetch_the_document(); * Comments should not start with a capital letter or end with a full stop and should be C-style, not C++-style, e.g. `/* this */` not `// this` * Each object should go in a separate .c file and be named according to the class * Use g_autoptr() and g_autofree whenever possible, and avoid `goto out` error handling * Failing methods should return FALSE with a suitable `GError` set * Trailing whitespace is forbidden * Pointers should be checked for NULL explicitly, e.g. `foo != NULL` not `!foo` * Use the correct debug level: * `g_debug()` -- low level plugin and daemon development, typically only useful to programmers * `g_info()` -- generally useful messages, typically shown when using `--verbose` * `g_message()` -- important messages, typically shown in service output * `g_warning()` -- warning messages, typically shown in service output * `g_critical()` -- critical messages, typically shown before the daemon aborts **NOTE:** Do not use `g_error()` -- it's not appropriate to abort the daemon on error. `./contrib/reformat-code.py` can be used in order to get automated formatting. Calling the script without arguments formats the current patch while passing commits will do formatting on everything changed since that commit. fwupd-1.9.16/COPYING000066400000000000000000000636421460375044200140100ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! fwupd-1.9.16/MAINTAINERS000066400000000000000000000001131460375044200144320ustar00rootroot00000000000000Richard Hughes Mario Limonciello fwupd-1.9.16/README.md000066400000000000000000000120751460375044200142260ustar00rootroot00000000000000# fwupd [![Build Status](https://github.com/fwupd/fwupd/actions/workflows/ci.yml/badge.svg)](https://github.com/fwupd/fwupd/actions/workflows/ci.yml) [![CodeQL](https://github.com/fwupd/fwupd/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/fwupd/fwupd/actions/workflows/codeql-analysis.yml) [![Coverity Scan Build Status](https://scan.coverity.com/projects/10744/badge.svg)](https://scan.coverity.com/projects/10744) [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fwupd.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:fwupd) [![CircleCI](https://circleci.com/gh/fwupd/fwupd/tree/main.svg?style=svg)](https://circleci.com/gh/fwupd/fwupd/tree/main) This project aims to make updating firmware on Linux automatic, safe, and reliable. Additional information is available [at the website](https://fwupd.org/). ## Where to get help? - GitHub issues & discussions in [this repository](https://github.com/fwupd/fwupd) - Libera IRC channel `#fwupd`. You can join through Libera's [IRC](https://libera.chat/) or via the IRC bridge on [Matrix](https://matrix.to/#/#fwupd:libera.chat). ## Compiling See [Building and Debugging](docs/building.md) for how to build the fwupd development environment. **NOTE:** In most cases, end users should not compile fwupd from scratch; it's a complicated project with dozens of dependencies (and as many configuration options) and there's just too many things that can go wrong. Users should just have fwupd installed and updated by their distro, managed and tested by the package maintainer. The distribution will have also done some testing with how fwupd interacts with other software on your system, for instance using GNOME Software. Installing fwupd using [Snap](https://github.com/fwupd/fwupd/wiki/fwupd-snap) or using [Flatpak](https://github.com/fwupd/fwupd/wiki/fwupd-flatpak) might be useful to update a specific device on the command line that needs a bleeding edge fwupd version, but it should not be considered as a replacement to the distro-provided system version. ### Using Tartan [Tartan](https://gitlab.freedesktop.org/tartan/tartan/-/wikis/home) is a LLVM static analysis plugin built to analyze GLib code. It can be installed and then run using: mkdir build-tartan CC=clang meson ../ SCANBUILD=../contrib/tartan.sh ninja scan-build ## LVFS This project is configured by default to download firmware from the [Linux Vendor Firmware Service (LVFS)](https://fwupd.org/). This service is available to all OEMs and firmware creators who would like to make their firmware available to Linux users. You can find more information about the technical details of creating a firmware capsule in the hardware vendors section of the [fwupd website](https://fwupd.org). ## Basic usage flow (command line) If you have a device with firmware supported by fwupd, this is how you can check for updates and apply them using fwupd's command line tools. `# fwupdmgr get-devices` This will display all devices detected by fwupd. `# fwupdmgr refresh` This will download the latest metadata from LVFS. `# fwupdmgr get-updates` If updates are available for any devices on the system, they'll be displayed. `# fwupdmgr update` This will download and apply all updates for your system. - Updates that can be applied live will be done immediately. - Updates that run at bootup will be staged for the next reboot. You can find more information about the update workflow in the end users section of the [fwupd website](https://fwupd.org). ## Reporting status fwupd will encourage users to report both successful and failed updates back to LVFS. This is an optional feature, but encouraged as it provides valuable feedback to LVFS administrators and OEM developers regarding firmware update process efficacy. The privacy policy regarding this data can be viewed on the [lvfs readthedocs site](https://lvfs.readthedocs.io/en/latest/privacy.html). To report the status of an update, run: `# fwupdmgr report-history` Only updates that were distributed from the LVFS will be reported to the LVFS. ## Enterprise use The flow of updates can be controlled in the enterprise using the "approved updates" feature. This allows the domain administrator to filter the possible updates from a central server (e.g. the LVFS, or a mirror) to only firmware that have been tested specifically in your organization. The list of approved updates can be enabled by adding `ApprovalRequired=true` to the remote configuration file, e.g. `lvfs.conf`. Once enabled, the list of approved updates can be set in `fwupd.conf` using a comma-delimited list. For example: ApprovedFirmware=foo,bar Where `foo,bar` refers to the container checksums that would correspond to two updates in the metadata file. Additionally, the list of approved firmware can be supplemented using `fwupdmgr set-approved-firmware baz` or using the D-Bus interface. ## Other frontends fwupdmgr is a command line client, but various additional graphical frontends are enumerated in the [fwupdmgr man page](https://fwupd.github.io/libfwupdplugin/fwupdmgr.html#description). fwupd-1.9.16/RELEASE000066400000000000000000000023311460375044200137440ustar00rootroot00000000000000fwupd Release Notes Forking stable branch: When forking main into a stable 1_9_X, be sure to disable the following CI jobs: * publish-docs * publish-stable Also update `SECURITY.md`, removing the oldest branch and add the new branch at the top. To make sure it's done right, you can reference commit 433e809318c68c9ab6d4ae50ee9c4312503185d8 Write release entries: * ../contrib/generate-release.py * copy into ../data/org.freedesktop.fwupd.metainfo.xml * appstream-util appdata-to-news ../data/org.freedesktop.fwupd.metainfo.xml > NEWS 2. Commit changes to git: # MAKE SURE THIS IS CORRECT export release_ver="1.9.16" git commit -a -m "Release fwupd ${release_ver}" --no-verify git tag -s -f -m "Release fwupd ${release_ver}" "${release_ver}" git push --tags git push 3. Generate the tarball: ninja dist 3a. Generate the additional verification metadata gpg -b -a meson-dist/fwupd-${release_ver}.tar.xz 4. Create release and upload tarball to https://github.com/fwupd/fwupd/tags 5. Do post release version bump in meson.build 6. Commit changes: git commit -a -m "trivial: post release version bump" --no-verify git push 7. Update flatpak package for new release: https://github.com/flathub/org.freedesktop.fwupd fwupd-1.9.16/SECURITY.md000066400000000000000000000162301460375044200145350ustar00rootroot00000000000000# Security Policy Due to the nature of what we are doing, fwupd takes security very seriously. If you have any concerns please let us know. ## Supported Versions The `main`, and `1.8.x`, branches are fully supported by the upstream authors with all unstable code belonging in `wip` branches. Additionally, the `1.6.x` and `1.7.x` branches are supported for security fixes. | Version | Supported | | ------- | ------------------ | | 1.9.x | :heavy_check_mark: | | 1.8.x | :heavy_check_mark: | | 1.7.x | :white_check_mark: | | 1.6.x | :white_check_mark: | | 1.5.x | :x: EOL 2022-01-01 | | 1.4.x | :x: EOL 2021-05-01 | | 1.3.x | :x: EOL 2020-07-01 | | 1.2.x | :x: EOL 2019-12-01 | | 1.1.x | :x: EOL 2018-11-01 | | 1.0.x | :x: EOL 2018-10-01 | | 0.9.x | :x: EOL 2018-02-01 | Older releases than this are unsupported by upstream but may be supported by your distributor or distribution. If you open an issue with one of these older releases the very first question from us is going to be asking if it's fixed on a supported branch. You can use the flatpak or snap packages if your distributor is unwilling to update to a supported version. ## Reporting a Vulnerability If you find a vulnerability in fwupd your first thing you should do is email all the maintainers, which are currently listed in the `MAINTAINERS` file in this repository. Failing that, please report the issue against the `fwupd` component in Red Hat bugzilla, with the security checkbox set. You should get a response within 3 days. We have no bug bounty program, but we're happy to credit you in updates if this is what you would like us to do. ## Threat Modeling ### Who We Trust In this diagram, the arrow shows the flow of *information* from one entity to another. Important things to note: * OEMs and ODMs have to apply for a LVFS account and the website and email domain is verified * OEMs and ODMs can only upload for devices that match their device-supplied vendor-id * The relationship between the OEM/ODM and affiliate ISV/IBV is implemented using per-task ACLs * The LVFS is assumed to be managed by a vendor-neutral trusted team * Signing of the metadata and firmware is implemented using PKCS#7 and GPG * End users only trust the LVFS signing signatures by default * Metadata contains SHA-1 and SHA-256 hashes of the firmware archive * Access to the embargo remotes requires knowing the secret vendor hash, but not a token * The firmware archive internal metadata and firmware payload are both signed * Reports have to be signed by the user machine key to be attributable to an OEM or QA team * Signed reports are uploaded using a username and access token * SBoM metadata is extracted from the payload by the LVFS and formatted into HTML/SWID formats * Security researchers can only run FwHunt/Yara scans on public firmware ```mermaid flowchart TD LVFS((LVFS Webservice)) -- "SBoM.html" --> User(End User) LVFS -- "md.[xml|jcat] 🔒" --> CDN(Fastly CDN) CDN -- "md.[xml|jcat] 🔒" --> User LVFS -- "embargo.[xml|jcat] 🔒" --> User LVFS -- "fw.cab 🔒" --> User User -. report.json .-> LVFS User -. hsi.json .-> LVFS QA(QA Teams) -- "report.json 🔒" --> LVFS OEM(Device Vendor) -- "fw.cab" --> LVFS ODM(Device Manufacturer) -- "fw.[bin|cab]" --> OEM OEM -. "report.json 🔒" .-> LVFS ODM -. "fw.cab" .-> LVFS IBV(BIOS Vendor) -- "fw.bin" --> ODM ISV(Silicon Vendor) -- "fw.bin" --> ODM User -. "md.xml 🔒" .-> User2(Other LAN Users) User -. "fw.cab 🔒" .-> User2 LVFS -- "FwHunt|Yara" --> SecAlert(Security Researchers) ``` ### What We Trust In this diagram, the arrow shows the flow of *data* between different processes or machines. Important things to note: * User-facing clients like `fwupdmgr` and `gnome-software` should not be run as the root user * The fwupd daemon should be run as a privileged user and have no access to the network * Privilege escalation is performed using PolicyKit based on fine-grained ACLs, if available * Passwords may be in plaintext in `remotes.d` or config files, and should be readable only by root * The fwupd daemon will only install firmware archives signed by the LVFS unless modified * The fwupd daemon scans and verifies the mtime of various files at startup to build caches * If SecureBoot is turned on then `fwupd-efi` has to be signed by a trusted key * Files are passed between the user client and fwupd using an open file-descriptor, not a filename * There is no public interface to either the PostgreSQL or EFS data stores * The fwupd daemon may need to mount the EFI system partition to copy in capsule payloads * The `fwupdtool` debug tool requires root access to perform updates on devices ```mermaid flowchart TD subgraph Vendor OEM(OEM/ODM/ISV Firmware) end subgraph User fwupdmgr((fwupdmgr\ngnome-software)) end subgraph Local Network User fwupdmgr2((fwupdmgr\ngnome-software)) end subgraph Privileged fwupd((fwupd\ndaemon)) passim((passimd)) fwupdengine(FuEngine) fwupdtool(fwupdtool\ndebug\ntool) fwupd-efi(fwupd capsule loader) Pending[(history.db)] Kernel((OS Kernel)) ESP[(EFI\nSystem\nPartition)] SPI[(System SPI)] UsbDevice(USB Device) UsbDeviceEEPROM[(USB Device\nEEPROM)] State[(/var/lib/fwupd)] NVRAM[(Non-volatile\nRAM)] end subgraph Internet LVFS((LVFS\nInstance)) CDN(Fastly\nCDN) EFS[(Amazon\nEFS)] Postgres[(Amazon\nRDS)] Worker(Async Workers) end LVFS -- "fw.cab" --> Worker Worker -- "md.xml 🔒" --> EFS EFS <-- "fw.cab 🔒" --> Worker OEM -- "firmware.cab" --> LVFS LVFS -. "report.html" .-> OEM EFS <--> LVFS Postgres <--> Worker Postgres <--> LVFS fwupd <--> fwupdengine fwupdengine <-- "sqlite" --> Pending UsbDevice <-- "i²c" --> UsbDeviceEEPROM fwupdengine <-- "libusb" --> UsbDevice fwupdtool <---> fwupdengine fwupdengine <-- "ioctl()\nread()\nwrite()" --> Kernel fwupdengine -. "fwupdx64.efi" .-> ESP fwupdengine -- "fw.bin" --> ESP fwupdengine -- "fw.bin" --> Kernel fwupdengine -- "efivar" ---> Kernel Kernel -. "HSI attrs" .-> fwupdengine Kernel <-- "efivars" --> NVRAM fwupd-efi -- "fw.cap 🔏" ---> SPI fwupd-efi <-- "efivars" --> NVRAM ESP --> fwupd-efi fwupdmgr -- "md.[xml|jcat] 🔒🚏" --> fwupd fwupd -- "Devices\nHSI attrs\nReleases 🚏" --> fwupdmgr fwupdmgr -- "fw.cab 🔒🚏" --> fwupd CDN -- "md.[xml|jcat] 🔒" --> fwupdmgr LVFS -- "md.[xml|jcat] 🔒" --> CDN LVFS -- "fw.cab 🔒" --> fwupdmgr LVFS -- "embargo.[xml|jcat] 🔒" --> fwupdmgr fwupdmgr -. "report.json" .-> LVFS fwupdmgr -. "report.json 🔒" .-> LVFS State <-- "fw.cab 🔒" --> fwupd passim -. "md.md|fw.cab 🔒\nmDNS with TLS" .-> fwupdmgr2 fwupd -. "md.md|fw.cab 🔒🚏" .-> passim User ~~~~ Privileged Internet ~~~~~ User Vendor ~~~~~ Internet ``` ### Key * 🚏: D-Bus * 🔒: Signed using JCat file * 🔏: Signed using Platform Key fwupd-1.9.16/contrib/000077500000000000000000000000001460375044200144025ustar00rootroot00000000000000fwupd-1.9.16/contrib/PKGBUILD000066400000000000000000000023161460375044200155300ustar00rootroot00000000000000# Maintainer: Bruno Pagani (a.k.a. ArchangeGabriel) # Contributor: Mirco Tischler pkgname=fwupd pkgver=dummy pkgrel=1 pkgdesc='A system daemon to allow session software to update firmware' arch=('i686' 'x86_64') url='https://github.com/fwupd/fwupd' license=('GPL2') depends=('libgusb' 'modemmanager' 'tpm2-tss') makedepends=('meson' 'valgrind' 'gobject-introspection' 'gi-docgen' 'git' 'python-cairo' 'noto-fonts' 'noto-fonts-cjk' 'python-gobject' 'vala' 'libdrm' 'curl' 'polkit' 'xz') pkgver() { cd ${pkgname} VERSION=$(git describe | sed 's/-/.r/;s/-/./') [ -z $VERSION ] && VERSION=$(head meson.build | grep ' version:' | cut -d \' -f2) echo $VERSION } build() { cd ${pkgname} if [ -n "$CI" ]; then export CI="--wrap-mode=default" fi arch-meson -D b_lto=false $CI ../build \ -Dplugin_intel_spi=true \ -Dplugin_powerd=disabled \ -Dlaunchd=disabled \ -Ddocs=enabled \ -Defi_binary=false \ -Dsupported_build=enabled ninja -v -C ../build } check() { CACHE_DIRECTORY=/tmp ninja -C build test } package() { DESTDIR="${pkgdir}" ninja -C build install } fwupd-1.9.16/contrib/README.md000066400000000000000000000052071460375044200156650ustar00rootroot00000000000000# Distribution packages The relevant packaging necessary to generate *RPM*, *DEB* and *PKG* distribution packages is contained here. It is used regularly for continuous integration using [Travis CI](http://travis-ci.org). The generated packages can be used on a distribution such as Fedora, Debian, Ubuntu or Arch Linux. The build can be performed using Linux containers with [Docker](https://www.docker.com). ## RPM packages A Dockerfile for Fedora can be generated in `contrib`. To prepare the Docker container run this command: ```shell OS=fedora ./generate_docker.py build ``` To build the RPMs run this command (from the root of your git checkout): ```shell docker run --privileged -t -v `pwd`:/github/workspace fwupd-fedora ``` RPMs will be made available in your working directory when complete. To build additional RPM packages for Qubes OS (fwupd-qubes-dom0 and fwupd-qubes-vm) add `QUBES=true` environment variable: ```shell docker run --privileged -e QUBES=true -t -v `pwd`:/github/workspace fwupd-fedora ``` ## DEB packages A Dockerfile for Debian or Ubuntu can be generated in `contrib`. To prepare the Docker container run one of these commands: ```shell OS=debian-x86_64 ./generate_docker.py build OS=debian-i386 ./generate_docker.py build OS=ubuntu-x86_64 ./generate_docker.py build ``` To build the DEBs run one of these commands (from the root of your git checkout): ```shell docker run --privileged -t -v `pwd`:/github/workspace fwupd-debian-x86_64 docker run --privileged -t -v `pwd`:/github/workspace fwupd-debian-i386 docker run --privileged -t -v `pwd`:/github/workspace fwupd-ubuntu-x86_64 ``` DEBs will be made available in your working directory when complete. To build additional DEB package for Qubes OS (fwupd-qubes-vm-whonix) add `QUBES=true` environment variable: ```shell docker run --privileged -t -v `pwd`:/github/workspace fwupd-debian-x86_64-qubes ``` ## PKG packages A Dockerfile for Arch can be generated in `contrib`. To prepare the Docker container run this command: ```shell OS=arch ./generate_docker.py ``` To build the PKGs run this command (from the root of your git checkout): ```shell docker run -t -v `pwd`:/build fwupd-arch ``` PKGs will be made available in your working directory when complete. ## Additional packages Submissions for generating additional packages for other distribution mechanisms are also welcome. All builds should occur in Docker containers. Please feel free to submit the following: * Dockerfile for the container for your distro * Relevant technical packaging scripts (such as ebuilds, spec file etc) * A shell script that can be launched in the container to generate distribution packages fwupd-1.9.16/contrib/build-openbmc.sh000077500000000000000000000007671460375044200174730ustar00rootroot00000000000000#!/bin/sh rm -rf build-openbmc meson setup build-openbmc \ -Dauto_features=disabled \ -Ddocs=disabled \ -Dpolkit=disabled \ -Dbash_completion=false \ -Dcompat_cli=false \ -Dfish_completion=false \ -Dfirmware-packager=false \ -Dhsi=disabled \ -Dman=false \ -Dmetainfo=false \ -Dtests=true \ -Dudevdir=/tmp \ -Dsystemd_root_prefix=/tmp \ $@ ninja install -C build-openbmc build-openbmc/src/fwupdtool get-devices --verbose test $? -eq 2 || exit 1 fwupd-1.9.16/contrib/build-venv.sh000077500000000000000000000017441460375044200170220ustar00rootroot00000000000000#!/bin/sh -e VENV=$(dirname $0)/.. BUILD=${VENV}/build DIST=${VENV}/dist EXTRA_ARGS="-Dsystemd=disabled -Dlaunchd=disabled" #build and install if [ -d /opt/homebrew/opt/libarchive/lib/pkgconfig ]; then EXTRA_ARGS="${EXTRA_ARGS} -Dpkg_config_path=/opt/homebrew/opt/libarchive/lib/pkgconfig" fi if [ ! -d ${BUILD} ]; then meson setup ${BUILD} --prefix=${DIST} -Dudevdir=${DIST} ${EXTRA_ARGS} $@ fi ninja -C ${BUILD} install # check whether we have an existing fwupd EFI binary in the host system to use EFI_PREFIX=$(pkg-config fwupd-efi --variable=prefix 2>/dev/null || echo "/usr") EFI_DIR=libexec/fwupd/efi BINARIES=$(find "${EFI_PREFIX}/${EFI_DIR}" -name "*.efi*" -type f -print) if [ -n "${BINARIES}" ]; then mkdir -p ${DIST}/${EFI_DIR} for i in ${BINARIES}; do if [ -f "${DIST}/${EFI_DIR}/$(basename $i)" ]; then continue fi ln -s $i "${DIST}/${EFI_DIR}/$(basename $i)" done fi fwupd-1.9.16/contrib/build-windows.sh000077500000000000000000000053501460375044200175330ustar00rootroot00000000000000#!/bin/sh set -e root=$(pwd) export DESTDIR=${root}/dist build=$root/build-win32 mkdir -p "$build" && cd "$build" # install deps if [ ! -f /usr/share/mingw/toolchain-mingw64.meson ]; then ./contrib/ci/fwupd_setup_helpers.py -v mingw64 install-dependencies fi # try to keep this and ../contrib/ci/build_windows.sh in sync as much as makes sense meson setup .. \ --cross-file=/usr/share/mingw/toolchain-mingw64.meson \ --cross-file=../contrib/mingw64.cross \ --prefix=/ \ --sysconfdir="etc" \ --libexecdir="bin" \ --bindir="bin" \ -Dbuild=all \ -Ddbus_socket_address="tcp:host=localhost,port=1341" \ -Dman=false \ -Dfish_completion=false \ -Dbash_completion=false \ -Dfirmware-packager=false \ -Dmetainfo=false \ -Dcompat_cli=false \ -Dlibxmlb:introspection=false \ -Dlibxmlb:gtkdoc=false \ -Dlibjcat:man=false \ -Dlibjcat:gpg=false \ -Dlibjcat:tests=false \ -Dlibjcat:introspection=false \ -Dgusb:tests=false \ -Dgusb:docs=false \ -Dgusb:introspection=false \ -Dgusb:vapi=false # run tests export WINEPATH="/usr/x86_64-w64-mingw32/sys-root/mingw/bin/;$build/libfwupd/;$build/libfwupdplugin/;$build/subprojects/libxmlb/src/;$build/subprojects/libjcat/libjcat/;$build/subprojects/gusb/gusb/" ninja -C "$build" install ninja -C "$build" test MINGW32BINDIR=/usr/x86_64-w64-mingw32/sys-root/mingw/bin #disable motd for Windows sed -i 's,UpdateMotd=.*,UpdateMotd=false,' "$DESTDIR/etc/fwupd/fwupd.conf" # copy deps cp -f -v \ $MINGW32BINDIR/gspawn-win64-helper-console.exe \ $MINGW32BINDIR/gspawn-win64-helper.exe \ $MINGW32BINDIR/iconv.dll \ $MINGW32BINDIR/libarchive-13.dll \ $MINGW32BINDIR/libbrotlicommon.dll \ $MINGW32BINDIR/libbrotlidec.dll \ $MINGW32BINDIR/libbz2-1.dll \ $MINGW32BINDIR/libcrypto-3-x64.dll \ $MINGW32BINDIR/libcurl-4.dll \ $MINGW32BINDIR/libffi-*.dll \ $MINGW32BINDIR/libgcc_s_seh-1.dll \ $MINGW32BINDIR/libgio-2.0-0.dll \ $MINGW32BINDIR/libglib-2.0-0.dll \ $MINGW32BINDIR/libgmodule-2.0-0.dll \ $MINGW32BINDIR/libgmp-10.dll \ $MINGW32BINDIR/libgnutls-30.dll \ $MINGW32BINDIR/libgobject-2.0-0.dll \ $MINGW32BINDIR/libgusb-2.dll \ $MINGW32BINDIR/libhogweed-*.dll \ $MINGW32BINDIR/libidn2-0.dll \ $MINGW32BINDIR/libintl-8.dll \ $MINGW32BINDIR/libjson-glib-1.0-0.dll \ $MINGW32BINDIR/liblzma-5.dll \ $MINGW32BINDIR/libnettle-*.dll \ $MINGW32BINDIR/libp11-kit-0.dll \ $MINGW32BINDIR/libpcre2-8-0.dll \ $MINGW32BINDIR/libsqlite3-0.dll \ $MINGW32BINDIR/libssh2-1.dll \ $MINGW32BINDIR/libssl-3-x64.dll \ $MINGW32BINDIR/libssp-0.dll \ $MINGW32BINDIR/libtasn1-6.dll \ $MINGW32BINDIR/libusb-1.0.dll \ $MINGW32BINDIR/libwinpthread-1.dll \ $MINGW32BINDIR/libxml2-2.dll \ $MINGW32BINDIR/libzstd.dll \ $MINGW32BINDIR/zlib1.dll \ "$DESTDIR/bin/" fwupd-1.9.16/contrib/cfu-inf-to-quirk.py000077500000000000000000000036461460375044200200700ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ # # pylint: disable=invalid-name,missing-docstring import sys def _convert_inf_to_quirk(fn: str) -> None: with open(fn, "rb") as f: lines = f.read().decode().split("\n") instance_id = None comment = None dict_convert = { "VersionsFeatureValueCapabilityUsageRangeMinimum": "CfuVersionGetReport", "OfferOutputValueCapabilityUsageRangeMinimum": "CfuOfferSetReport", "OfferInputValueCapabilityUsageRangeMinimum": "CfuOfferGetReport", "PayloadOutputValueCapabilityUsageRangeMinimum": "CfuContentSetReport", "PayloadInputValueCapabilityUsageRangeMinimum": "CfuContentGetReport", } is_cfu = False data = {"Plugin": "cfu"} for line in lines: line = line.split(";")[0] if line.find("HidCfu.NT.Services") != -1: is_cfu = True if line.find("FwUpdateFriendlyName") != -1: try: comment = line.split("=")[1] for token in ['"', "Firmware Update", "(TM)"]: comment = comment.replace(token, "") comment = comment.strip() except IndexError: pass if line.find("HID\\VID_") != -1: instance_id = line.split(",")[1].strip().upper() instance_id = instance_id.replace("HID\\", "USB\\") instance_id = "&".join(instance_id.split("&")[:2]) for inf_key, quirk_key in dict_convert.items(): if line.find(inf_key) != -1: data[quirk_key] = line.split(",")[4].strip() if is_cfu: if comment: print(f"# {comment}") print(f"[{instance_id}]") for key, value in data.items(): print(f"{key} = {value}") else: sys.exit("this is not a CFU firmware.inf") for fn in sys.argv[1:]: _convert_inf_to_quirk(fn) fwupd-1.9.16/contrib/ci/000077500000000000000000000000001460375044200147755ustar00rootroot00000000000000fwupd-1.9.16/contrib/ci/Dockerfile-arch.in000066400000000000000000000005771460375044200203200ustar00rootroot00000000000000FROM archlinux:latest %%%OS%%% ENV LANG en_US.UTF-8 ENV LC_ALL en_US.UTF-8 ENV CI_NETWORK true RUN echo fubar > /etc/machine-id RUN sed "s,#en_US.UTF-8,en_US.UTF-8," /etc/locale.gen -i RUN echo "LANG=en_US.UTF-8" > /etc/locale.conf RUN locale-gen RUN pacman -Syu --noconfirm archlinux-keyring %%%INSTALL_DEPENDENCIES_COMMAND%%% WORKDIR /github/workspace CMD ["./contrib/ci/arch.sh"] fwupd-1.9.16/contrib/ci/Dockerfile-debian.in000066400000000000000000000004231460375044200206130ustar00rootroot00000000000000FROM %%%ARCH_PREFIX%%%debian:testing %%%OS%%% ENV CI_NETWORK true RUN echo fubar > /etc/machine-id %%%ARCH_SPECIFIC_COMMAND%%% %%%INSTALL_DEPENDENCIES_COMMAND%%% RUN apt install -yq --no-install-recommends python3-apt WORKDIR /github/workspace CMD ["./contrib/ci/debian.sh"] fwupd-1.9.16/contrib/ci/Dockerfile-fedora.in000066400000000000000000000004121460375044200206270ustar00rootroot00000000000000FROM fedora:39 %%%OS%%% ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 RUN echo fubar > /etc/machine-id RUN dnf -y update RUN echo fubar > /etc/machine-id %%%INSTALL_DEPENDENCIES_COMMAND%%% WORKDIR /github/workspace CMD ["./contrib/ci/fedora.sh"] fwupd-1.9.16/contrib/ci/Dockerfile-snap.in000066400000000000000000000017651460375044200203440ustar00rootroot00000000000000FROM ubuntu:xenial RUN apt-get update && \ apt-get dist-upgrade --yes && \ apt-get install --yes \ curl sudo jq squashfs-tools && \ curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/core' | jq '.download_url' -r) --output core.snap && \ mkdir -p /snap/core && unsquashfs -d /snap/core/current core.snap && rm core.snap && \ curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/snapcraft?channel=edge' | jq '.download_url' -r) --output snapcraft.snap && \ mkdir -p /snap/snapcraft && unsquashfs -d /snap/snapcraft/current snapcraft.snap && rm snapcraft.snap && \ apt remove --yes --purge curl jq squashfs-tools && \ apt-get autoclean --yes && \ apt-get clean --yes COPY contrib/ci/snapcraft-wrapper /snap/bin/snapcraft ENV PATH=/snap/bin:$PATH LABEL maintainer="Mario Limonciello " RUN apt-get update && apt-get install -y \ curl \ git \ jq \ openssh-client \ wget WORKDIR /root/project fwupd-1.9.16/contrib/ci/Dockerfile-ubuntu.in000066400000000000000000000004411460375044200207130ustar00rootroot00000000000000FROM ubuntu:rolling %%%OS%%% ENV CI_NETWORK true ENV CC clang ENV DEBIAN_FRONTEND noninteractive RUN echo fubar > /etc/machine-id %%%ARCH_SPECIFIC_COMMAND%%% RUN apt update -qq && apt install -yq --no-install-recommends python3-apt WORKDIR /github/workspace CMD ["./contrib/ci/ubuntu.sh"] fwupd-1.9.16/contrib/ci/Dockerfile-void.in000066400000000000000000000003771460375044200203420ustar00rootroot00000000000000FROM ghcr.io/void-linux/void-linux:latest-full-x86_64-musl %%%OS%%% ENV LANG en_US.UTF-8 ENV LC_ALL en_US.UTF-8 ENV CI_NETWORK true RUN echo fubar > /etc/machine-id %%%INSTALL_DEPENDENCIES_COMMAND%%% WORKDIR /github/workspace CMD ["./contrib/ci/void.sh"] fwupd-1.9.16/contrib/ci/README.md000066400000000000000000000102561460375044200162600ustar00rootroot00000000000000# Continuous Integration By using CI, builds are exercised across a variety of environments attempting to maximize code coverage. For every commit or pull request 6 builds are performed: ## Fedora (x86_64) * A fully packaged RPM build with all plugins enabled * Compiled under gcc with AddressSanitizer * Tests with -Werror enabled * Tests with the built in local test suite for all plugins. * All packages are installed * An installed testing run with the "test" plugin and pulling from LVFS. * With modem manager disabled ## Debian testing (x86_64) * A fully packaged DEB build with all plugins enabled * Compiled under gcc * Tests with -Werror enabled * Tests with the built in local test suite for all plugins. * All packages are installed * An installed testing run with the "test" plugin and pulling from LVFS. * All packages are removed ## Debian testing (i386) * A fully packaged DEB build with all plugins enabled * Compiled under gcc * Tests with -Werror enabled * Tests with the built in local test suite for all plugins. * All packages are installed * An installed testing run with the "test" plugin and pulling from LVFS. * All packages are removed ## Ubuntu devel release (x86_64) * A fully packaged DEB build with all plugins enabled * Compiled under clang * Tests without -Werror enabled * Tests with the built in local test suite for all plugins. * All packages are installed * An installed testing run with the "test" plugin and pulling from LVFS. * All packages are removed ## Debian testing (cross compile s390x) * Not packaged * Tests for missing translation files * No redfish support * Compiled under gcc * Tests with -Werror enabled * Runs local test suite using qemu-user * Modem manager disabled ## Arch Linux (x86_64) * A fully packaged pkg build with all plugins enabled * Compiled under gcc * Tests with -Werror enabled * Compile with the deprecated USB plugin enabled * Tests with the built in local test suite for all plugins. * All packages are installed ## Flatpak * A flatpak bundle with all plugins enabled * Compiled under gcc with the org.gnome.Sdk/x86_64/3.28 runtime * Builds without the daemon, so only fwupdtool is available * No GPG, PKCS-7, GObjectIntrospection, systemd or ConsoleKit support * No tests ## Adding a new target Dockerfiles are generated dynamically by the python script ```generate_dockerfile.py```. The python script will recognize the environment variable `OS` to determine what target to generate a Dockerfile for. ### dependencies.xml Initially the python script will read in `dependencies.xml` to generate a dependency list for that target. The XML is organized by a top level element representing the dependencies needed for building fwupd. The child elements represent individual dependencies for all distributions. * This element has an attribute `id` that represents the most common package name used by distributions * This element has an attribute `type` that represents if the package is needed at build time or runtime. Each dependency then has a mapping to individual distributions (`distro`). * This element has an attribute `id` that represents the distribution. Each distribution will have `package` elements and `control` elements. `Package` elements represent the name of the package needed for the distribution. * An optional attribute `variant` represents one deviation of that distribution. For example building a specific architecture or with a different compiler. * If the `package` element is empty the `id` of the `dependency` element will be used. * `Control` elements represent specific requirements associated to a dependency. They will contain elements with individual details. * `version` elements represent a minimum version to be installed * `inclusive` elements represent an inclusive list of architectures to be installed on * `exclusive` elements represent an exclusive list of architectures to not be installed on For convenience there is also a helper script `./contrib/ci/fwupd_setup_helpers.p install-dependencies` that parses `dependencies.xml`. ### Dockerfile.in The `Dockerfile.in` file will be used as a template to build the container. No hardcoded dependencies should be put in this file. They should be stored in `dependencies.xml`. fwupd-1.9.16/contrib/ci/abidiff.suppr000066400000000000000000000005241460375044200174550ustar00rootroot00000000000000[suppress_type] type_kind = enum changed_enumerators = FWUPD_ERROR_LAST,FWUPD_GUID_FLAG_LAST,FWUPD_INSTALL_FLAG_LAST,FWUPD_KEYRING_KIND_LAST,FWUPD_REMOTE_KIND_LAST,FWUPD_SELF_SIGN_FLAG_LAST,FWUPD_STATUS_LAST,FWUPD_TRUST_FLAG_LAST,FWUPD_UPDATE_STATE_LAST,FWUPD_VERSION_FORMAT_LAST,FWUPD_CLIENT_DOWNLOAD_FLAG_LAST,FWUPD_FEATURE_FLAG_LAST fwupd-1.9.16/contrib/ci/arch.sh000077500000000000000000000026021460375044200162510ustar00rootroot00000000000000#!/bin/bash set -e set -x shopt -s extglob #clone test firmware if necessary . ./contrib/ci/get_test_firmware.sh #refresh package cache and update image pacman -Syu --noconfirm #install anything missing from the container ./contrib/ci/fwupd_setup_helpers.py install-dependencies -o arch # prepare the build tree rm -rf build mkdir build && pushd build cp ../contrib/PKGBUILD . mkdir -p src/fwupd && pushd src/fwupd cp -R ../../../!(build|dist) . popd chown nobody . -R # install and run the custom redfish simulator pacman -S --noconfirm python-flask ../plugins/redfish/tests/redfish.py & # install and run TPM simulator necessary for plugins/uefi-capsule/uefi-self-test pacman -S --noconfirm swtpm tpm2-tools swtpm socket --tpm2 --server port=2321 --ctrl type=tcp,port=2322 --flags not-need-init --tpmstate "dir=$PWD" & trap 'kill $!' EXIT # extend a PCR0 value for test suite sleep 2 tpm2_startup -c tpm2_pcrextend 0:sha1=f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 export TPM_SERVER_RUNNING=1 # build the package and install it sudo -E -u nobody PKGEXT='.pkg.tar' makepkg -e --noconfirm pacman -U --noconfirm *.pkg.* #run the CI tests for Qt5 pacman -S --noconfirm qt5-base meson qt5-thread-test ../contrib/ci/qt5-thread-test ninja -C qt5-thread-test test # move the package to working dir mkdir -p ../dist mv *.pkg.* ../dist # no testing here because gnome-desktop-testing isn’t available in Arch fwupd-1.9.16/contrib/ci/build_freebsd_package.sh000077500000000000000000000041031460375044200215760ustar00rootroot00000000000000#!/bin/sh GITHUB_SHA= GITHUB_REPOSITORY= GITHUB_REPOSITORY_OWNER= GITHUB_TAG= while [ -n "$1" ]; do case $1 in --GITHUB_SHA=*) GITHUB_SHA=${1#--GITHUB_SHA=} ;; --GITHUB_REPOSITORY=*) GITHUB_REPOSITORY=${1#--GITHUB_REPOSITORY=} ;; --GITHUB_REPOSITORY_OWNER=*) GITHUB_REPOSITORY_OWNER=${1#--GITHUB_REPOSITORY_OWNER=} ;; --GITHUB_TAG=*) GITHUB_TAG=${1#--GITHUB_TAG=} ;; *) echo "Command $1 unknown. exiting..." exit 1 ;; esac shift done if [ -z "$GITHUB_SHA" ] || [ -z "$GITHUB_REPOSITORY" ] || \ [ -z "$GITHUB_REPOSITORY_OWNER" ] || [ -z "$GITHUB_TAG" ]; then exit 1 fi # Include-file of libefivar port uses GCC-specific builtin function export CC=gcc set -xe mkdir -p /usr/local/etc/pkg/repos/ # Fix meson flag problem https://www.mail-archive.com/freebsd-ports@freebsd.org/msg86617.html cp /etc/pkg/FreeBSD.conf /usr/local/etc/pkg/repos/FreeBSD.conf # Use latest pkg repo instead of quarterly https://wiki.freebsd.org/Ports/QuarterlyBranch sed -i .old 's|url: "pkg+http://pkg.FreeBSD.org/${ABI}/quarterly"|url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest"|' \ /usr/local/etc/pkg/repos/FreeBSD.conf pkg install -y meson efivar pkg upgrade -y meson cd /usr git clone https://github.com/3mdeb/freebsd-ports.git --depth 1 -b fwupd ports cd /usr/ports/sysutils/fwupd rm -rf ./* ls . cp -r ~/work/fwupd/fwupd/contrib/freebsd/* . ls . sed -i .old "s/GH_TAGNAME=.*$/GH_TAGNAME=\t${GITHUB_SHA}/" Makefile sed -i .old "s/GH_ACCOUNT=.*$/GH_ACCOUNT=\t${GITHUB_REPOSITORY_OWNER}/" Makefile sed -i .old "s/DISTVERSION=.*$/DISTVERSION=\t${GITHUB_TAG}/" Makefile make makesum make clean make # Generate current list of files in the pkg-plist make makeplist > pkg-plist sed -i "" "1d" pkg-plist sed -i "" "s/%%PORTDOCS%%%%DOCSDIR%%/%%DOCSDIR%%/g" pkg-plist # Build artifact make clean make package make install cp /usr/ports/sysutils/fwupd/work/pkg/fwupd*.pkg \ ~/work/fwupd/fwupd/fwupd-freebsd-${GITHUB_TAG}-${GITHUB_SHA}.pkg || exit 1 fwupd-1.9.16/contrib/ci/build_macos.sh000077500000000000000000000004131460375044200176130ustar00rootroot00000000000000#!/bin/bash set -e set -x mkdir -p build-macos && cd build-macos meson setup .. \ -Dbuild=all \ -Ddbus_socket_address="unix:path=/var/run/fwupd.socket" \ -Dman=false \ -Dgusb:docs=false \ -Dlibjcat:gpg=false \ -Dlibxmlb:gtkdoc=false \ $@ fwupd-1.9.16/contrib/ci/build_windows.sh000077500000000000000000000117661460375044200202200ustar00rootroot00000000000000#!/bin/sh set -e # if invoked outside of CI if [ "$CI" != "true" ]; then echo "Not running in CI, please manually configure Windows build" exit 1 fi # install deps ./contrib/ci/fwupd_setup_helpers.py --yes -o fedora -v mingw64 install-dependencies # update to latest version of meson if [ "$(id -u)" -eq 0 ]; then dnf install -y python-pip pip install meson --force-reinstall fi #prep export LC_ALL=C.UTF-8 root=$(pwd) export DESTDIR=${root}/dist build=$root/build-win32 rm -rf $DESTDIR $build # For logitech bulk controller being disabled (-Dplugin_logitech_bulkcontroller=disabled): # See https://bugzilla.redhat.com/show_bug.cgi?id=1991749 # When fixed need to do the following to enable: # 1. need to add mingw64-protobuf mingw64-protobuf-tools to CI build deps # 2. add protoc = /path/to/protoc-c.exe in mingw64.cross # 3. Only enable when not a tagged release (Unsupported by Logitech) # try to keep this and ../contrib/build-windows.sh in sync as much as makes sense mkdir -p $build $DESTDIR && cd $build meson setup .. \ --cross-file=/usr/share/mingw/toolchain-mingw64.meson \ --cross-file=../contrib/mingw64.cross \ --prefix=/ \ --sysconfdir="etc" \ --libexecdir="bin" \ --bindir="bin" \ -Dbuild=all \ -Dman=false \ -Ddbus_socket_address="tcp:host=localhost,port=1341" \ -Dfish_completion=false \ -Dbash_completion=false \ -Dfirmware-packager=false \ -Dmetainfo=false \ -Dcompat_cli=false \ -Dlibxmlb:introspection=false \ -Dlibxmlb:gtkdoc=false \ -Dlibjcat:man=false \ -Dlibjcat:gpg=false \ -Dlibjcat:tests=false \ -Dlibjcat:introspection=false \ -Dgusb:tests=false \ -Dgusb:docs=false \ -Dgusb:introspection=false \ -Dgusb:vapi=false $@ VERSION=$(meson introspect . --projectinfo | jq -r .version) # run tests export WINEPATH="/usr/x86_64-w64-mingw32/sys-root/mingw/bin/;$build/libfwupd/;$build/libfwupdplugin/;$build/subprojects/libxmlb/src/;$build/subprojects/libjcat/libjcat/;$build/subprojects/gusb/gusb/" ninja --verbose -C "$build" -v ninja -C "$build" test # switch to release optimizations meson configure -Dtests=false -Dbuildtype=release ninja -C "$build" -v install #disable motd for Windows cd $root sed -i 's,UpdateMotd=.*,UpdateMotd=false,' "$DESTDIR/etc/fwupd/fwupd.conf" # create a setup binary CERTDIR=/etc/pki/tls/certs MINGW32BINDIR=/usr/x86_64-w64-mingw32/sys-root/mingw/bin # deps find $MINGW32BINDIR \ -name gspawn-win64-helper-console.exe \ -o -name gspawn-win64-helper.exe \ -o -name iconv.dll \ -o -name libarchive-13.dll \ -o -name libbrotlicommon.dll \ -o -name libbrotlidec.dll \ -o -name libbz2-1.dll \ -o -name libcrypto-3-x64.dll \ -o -name libcurl-4.dll \ -o -name "libffi-*.dll" \ -o -name libgcc_s_seh-1.dll \ -o -name libgio-2.0-0.dll \ -o -name libglib-2.0-0.dll \ -o -name libgmodule-2.0-0.dll \ -o -name libgmp-10.dll \ -o -name libgnutls-30.dll \ -o -name libgobject-2.0-0.dll \ -o -name libgusb-2.dll \ -o -name "libhogweed-*.dll" \ -o -name libidn2-0.dll \ -o -name libintl-8.dll \ -o -name libjson-glib-1.0-0.dll \ -o -name liblzma-5.dll \ -o -name "libnettle-*.dll" \ -o -name libp11-kit-0.dll \ -o -name libpcre2-8-0.dll \ -o -name libsqlite3-0.dll \ -o -name libssh2-1.dll \ -o -name libssl-3-x64.dll \ -o -name libssp-0.dll \ -o -name libtasn1-6.dll \ -o -name libusb-1.0.dll \ -o -name libwinpthread-1.dll \ -o -name libxml2-2.dll \ -o -name libzstd.dll \ -o -name zlib1.dll \ | wixl-heat \ -p $MINGW32BINDIR/ \ --win64 \ --directory-ref BINDIR \ --var "var.MINGW32BINDIR" \ --component-group "CG.fwupd-deps" | \ tee $build/contrib/fwupd-deps.wxs # no static libraries find "$DESTDIR/" -type f -name "*.a" -print0 | xargs rm -f # our files find "$DESTDIR" | \ wixl-heat \ -p "$DESTDIR/" \ -x include/ \ -x share/fwupd/device-tests/ \ -x share/tests/ \ -x share/man/ \ -x share/doc/ \ -x lib/pkgconfig/ \ --win64 \ --directory-ref INSTALLDIR \ --var "var.DESTDIR" \ --component-group "CG.fwupd-files" | \ tee "$build/contrib/fwupd-files.wxs" #add service install key sed -i "$build/contrib/fwupd-files.wxs" -f - << EOF s,fwupd.exe"/>,fwupd.exe"/>\\ , EOF MSI_FILENAME="$DESTDIR/setup/fwupd-$VERSION-setup-x86_64.msi" mkdir -p "$DESTDIR/setup" wixl -v \ "$build/contrib/fwupd.wxs" \ "$build/contrib/fwupd-deps.wxs" \ "$build/contrib/fwupd-files.wxs" \ -D CRTDIR=$CERTDIR \ -D MINGW32BINDIR=$MINGW32BINDIR \ -D Win64="yes" \ -D DESTDIR="$DESTDIR" \ -o "${MSI_FILENAME}" # check the msi archive can be installed and removed (use "wine uninstaller" to do manually) # wine msiexec /i "${MSI_FILENAME}" # ls -R ~/.wine/drive_c/Program\ Files/fwupd/ # wine ~/.wine/drive_c/Program\ Files/fwupd/bin/fwupdtool get-plugins --json # wine msiexec /x "${MSI_FILENAME}" #generate news release contrib/ci/generate_news.py $VERSION > $DESTDIR/news.txt echo $VERSION > $DESTDIR/VERSION fwupd-1.9.16/contrib/ci/centos.sh000077500000000000000000000006131460375044200166270ustar00rootroot00000000000000#!/bin/sh set -e set -x #clone test firmware if necessary . ./contrib/ci/get_test_firmware.sh #build rm -rf build mkdir -p build cd build meson setup .. \ --werror \ -Dplugin_uefi_capsule=disabled \ -Dplugin_modem_manager=disabled \ -Dplugin_synaptics_mst=enabled \ -Dplugin_flashrom=enabled \ -Dintrospection=true \ -Dpkcs7=false \ -Dman=true ninja-build -v ninja-build test -v cd .. fwupd-1.9.16/contrib/ci/check-abi000077500000000000000000000071111460375044200165310ustar00rootroot00000000000000#!/usr/bin/python3 import argparse import contextlib import os import shutil import subprocess import sys def format_title(title): box = { "tl": "╔", "tr": "╗", "bl": "╚", "br": "╝", "h": "═", "v": "║", } hline = box["h"] * (len(title) + 2) return "\n".join( [ f"{box['tl']}{hline}{box['tr']}", f"{box['v']} {title} {box['v']}", f"{box['bl']}{hline}{box['br']}", ] ) def rm_rf(path): try: shutil.rmtree(path) except FileNotFoundError: pass def sanitize_path(name): return name.replace("/", "-") def get_current_revision(): revision = subprocess.check_output( ["git", "rev-parse", "--abbrev-ref", "HEAD"], encoding="utf-8" ).strip() if revision == "HEAD": # This is a detached HEAD, get the commit hash revision = ( subprocess.check_output(["git", "rev-parse", "HEAD"]) .strip() .decode("utf-8") ) return revision @contextlib.contextmanager def checkout_git_revision(revision): current_revision = get_current_revision() subprocess.check_call(["git", "checkout", "-q", revision]) try: yield finally: subprocess.check_call(["git", "checkout", "-q", current_revision]) def build_install(revision): build_dir = "_build" dest_dir = os.path.abspath(sanitize_path(revision)) print( format_title(f"# Building and installing {revision} in {dest_dir}"), end="\n\n", flush=True, ) with checkout_git_revision(revision): rm_rf(build_dir) rm_rf(revision) subprocess.check_call( [ "meson", build_dir, "--prefix=/usr", "--libdir=lib", "-Dauto_features=disabled", "-Db_coverage=false", "-Dgusb:docs=false", "-Dtests=false", ] ) subprocess.check_call(["ninja", "-v", "-C", build_dir]) subprocess.check_call( ["ninja", "-v", "-C", build_dir, "install"], env={"DESTDIR": dest_dir} ) return dest_dir def compare(old_tree, new_tree): print(format_title(f"# Comparing the two ABIs"), end="\n\n", flush=True) old_headers = os.path.join(old_tree, "usr", "include") old_lib = os.path.join(old_tree, "usr", "lib", "libfwupd.so") new_headers = os.path.join(new_tree, "usr", "include") new_lib = os.path.join(new_tree, "usr", "lib", "libfwupd.so") subprocess.check_call( [ "abidiff", "--headers-dir1", old_headers, "--headers-dir2", new_headers, "--drop-private-types", "--suppressions", "contrib/ci/abidiff.suppr", "--fail-no-debug-info", "--no-added-syms", old_lib, new_lib, ] ) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("old", help="the previous revision, considered the reference") parser.add_argument("new", help="the new revision, to compare to the reference") args = parser.parse_args() if args.old == args.new: print("Let's not waste time comparing something to itself") sys.exit(0) old_tree = build_install(args.old) new_tree = build_install(args.new) try: compare(old_tree, new_tree) except subprocess.CalledProcessError: sys.exit(1) print(f"Hurray! {args.old} and {args.new} are ABI-compatible!") fwupd-1.9.16/contrib/ci/check-deprecated.sh000077500000000000000000000006661460375044200205170ustar00rootroot00000000000000#!/bin/sh -e set -e # these are deprecated in favor of INTERNAL flags deprecated="FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS FWUPD_DEVICE_FLAG_ONLY_SUPPORTED FWUPD_DEVICE_FLAG_MD_SET_NAME FWUPD_DEVICE_FLAG_MD_SET_VERFMT FWUPD_DEVICE_FLAG_NO_GUID_MATCHING FWUPD_DEVICE_FLAG_MD_SET_ICON" for val in $deprecated; do if grep -- $val plugins/*/*.c ; then exit 1 fi done fwupd-1.9.16/contrib/ci/check-finalizers.py000077500000000000000000000031611460375044200205740ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring,consider-using-f-string # pylint: disable=too-few-public-methods # # Copyright (C) 2022 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import glob import sys from typing import List class ReturnValidator: def __init__(self): self.warnings: List[str] = [] def parse(self, fn: str) -> None: with open(fn, "rb") as f: infunc = False has_parent_finalize = False for line in f.read().decode().split("\n"): # found the function, but ignore the prototype if line.find("_finalize(") != -1: if line.endswith(";"): continue infunc = True continue # got it if line.find("->finalize(") != -1: has_parent_finalize = True continue # finalize is done if infunc and line.startswith("}"): if not has_parent_finalize: self.warnings.append(f"{fn} did not have parent ->finalize()") break def test_files(): # test all C source files validator = ReturnValidator() for fn in glob.glob("**/*.c", recursive=True): if fn.startswith("dist/") or fn.startswith("subprojects/"): continue validator.parse(fn) for warning in validator.warnings: print(warning) return 1 if validator.warnings else 0 if __name__ == "__main__": # all done! sys.exit(test_files()) fwupd-1.9.16/contrib/ci/check-headers.py000077500000000000000000000111641460375044200200430ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-module-docstring,missing-function-docstring # # Copyright (C) 2021 Richard Hughes # Copyright (C) 2021 Mario Limonciello # # SPDX-License-Identifier: LGPL-2.1+ import glob import sys import os from typing import List def __get_includes(fn: str) -> List[str]: includes: List[str] = [] with open(fn, "r") as f: for line in f.read().split("\n"): if line.find("#include") == -1: continue if line.find("waive-pre-commit") > 0: continue for char in ["<", ">", '"']: line = line.replace(char, "") for char in ["\t"]: line = line.replace(char, " ") includes.append(line.split(" ")[-1]) return includes def test_files() -> int: rc: int = 0 lib_headers1 = glob.glob("libfwupd/*.h") lib_headers1.remove("libfwupd/fwupd.h") lib_headers2 = glob.glob("libfwupdplugin/*.h") lib_headers2.remove("libfwupdplugin/fwupdplugin.h") toplevel_headers = ["libfwupd/fwupd.h", "libfwupdplugin/fwupdplugin.h"] toplevel_headers_nopath = [os.path.basename(fn) for fn in toplevel_headers] lib_headers = lib_headers1 + lib_headers2 lib_headers_nopath = [os.path.basename(fn) for fn in lib_headers] # test all C and H files for fn in glob.glob("**/*.[c|h]", recursive=True): # we do not care if fn.startswith("subprojects"): continue if fn.startswith("build"): continue if fn.startswith("venv"): continue if fn.startswith("dist"): continue if fn.startswith("contrib/ci"): continue if fn in [ "libfwupd/fwupd-context-test.c", "libfwupd/fwupd-thread-test.c", "libfwupdplugin/fu-fuzzer-main.c", ]: continue includes = __get_includes(fn) if ( fn.startswith("plugins") and not fn.endswith("self-test.c") and not fn.endswith("tool.c") ): for include in includes: # check for using private header use in plugins if include.endswith("private.h"): print(f"{fn} uses private header {include}") rc = 1 continue # check for referring to anything but top level header if include in lib_headers or include in lib_headers_nopath: print( f"{fn} contains {include}, should only use top level includes" ) rc = 1 # check for double top level headers for toplevel_header in toplevel_headers: toplevel_fn = os.path.basename(toplevel_header) toplevel_includes = __get_includes(toplevel_header) toplevel_includes_nopath = [ os.path.basename(fn) for fn in toplevel_includes ] # we do not need both toplevel headers if set(toplevel_headers_nopath).issubset(set(includes)): print(f"{fn} contains both {', '.join(toplevel_headers_nopath)}") # toplevel not listed if toplevel_fn not in includes: continue # includes toplevel and *also* something listed in the toplevel for include in includes: if include in toplevel_includes or include in toplevel_includes_nopath: print(f"{fn} contains {toplevel_fn} but also includes {include}") rc = 1 # check for missing config.h if fn.endswith(".c") and "config.h" not in includes: print(f"{fn} does not include config.h") rc = 1 # check for one header implying the other implied_headers = { "fu-common.h": ["xmlb.h"], "fwupdplugin.h": [ "gio/gio.h", "glib.h", "glib-object.h", "xmlb.h", "fwupd.h", ] + lib_headers1, "gio/gio.h": ["glib.h", "glib-object.h"], "glib-object.h": ["glib.h"], "json-glib/json-glib.h": ["glib.h", "glib-object.h"], "xmlb.h": ["gio/gio.h"], } for key, values in implied_headers.items(): for value in values: if key in includes and value in includes: print(f"{fn} contains {value} which is implied by {key}") rc = 1 return rc if __name__ == "__main__": # all done! sys.exit(test_files()) fwupd-1.9.16/contrib/ci/check-license.py000077500000000000000000000024561460375044200200560ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2021 Richard Hughes # Copyright (C) 2021 Mario Limonciello # # SPDX-License-Identifier: LGPL-2.1+ import glob import os import sys def __get_license(fn: str) -> str: with open(fn, "r") as f: for line in f.read().split("\n"): if line.find("SPDX-License-Identifier:") > 0: return line.split(":")[1] return "" def test_files() -> int: rc: int = 0 build_dirs = [os.path.dirname(cf) for cf in glob.glob("**/config.h")] for fn in glob.glob("**/*.[c|h|py|sh|rs]", recursive=True): if "meson-private" in fn: continue if os.path.isdir(fn): continue if fn.startswith(tuple(build_dirs)): continue if fn.startswith("subprojects"): continue if fn.startswith("venv"): continue if fn.startswith("dist"): continue lic = __get_license(fn) if not lic: print(f"{fn} does not specify a license") rc = 1 continue if "GPL" not in lic: print(f"{fn} does not contain LGPL or GPL ({lic})") rc = 1 continue return rc if __name__ == "__main__": # all done! sys.exit(test_files()) fwupd-1.9.16/contrib/ci/check-null-false-returns.py000077500000000000000000000172041460375044200221730ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring,too-many-branches # pylint: disable=too-many-statements,too-many-return-statements,too-few-public-methods # # Copyright (C) 2021 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import glob import sys from typing import List def _tokenize(line: str) -> List[str]: # remove whitespace line = line.strip() line = line.replace("\t", "") line = line.replace(";", "") # find value line = line.replace(" ", "|") line = line.replace(",", "|") line = line.replace(")", "|") line = line.replace("(", "|") # return empty tokens tokens = [] for token in line.rsplit("|"): if token: tokens.append(token) return tokens class ReturnValidator: def __init__(self): self.warnings: List[str] = [] # internal state self._fn = None self._line_num = None self._value = None self._nret = None self._rvif = None self._line = None @property def _tokens(self) -> List[str]: return _tokenize(self._line) @property def _value_relaxed(self) -> str: if self._value in ["0x0", "0x00", "0x0000"]: return "0" if self._value in ["0xffffffff"]: return "G_MAXUINT32" if self._value in ["0xffff"]: return "G_MAXUINT16" if self._value in ["0xff"]: return "G_MAXUINT8" if self._value in ["G_SOURCE_REMOVE"]: return "FALSE" if self._value in ["G_SOURCE_CONTINUE"]: return "TRUE" return self._value def _test_rvif(self) -> None: # parse "g_return_val_if_fail (SOMETHING (foo), NULL);" self._value = self._tokens[-1] # enumerated enum, so ignore if self._value.find("_") != -1: return # is invalid if self._rvif and self._value_relaxed not in self._rvif: self.warnings.append( "{} line {} got {}, expected {}".format( self._fn, self._line_num, self._value, ", ".join(self._rvif) ) ) def _test_return(self) -> None: # parse "return 0x0;" self._value = self._tokens[-1] # is invalid if self._nret and self._value_relaxed in self._nret: self.warnings.append( "{} line {} got {}, which is not valid".format( self._fn, self._line_num, self._value ) ) def parse(self, fn: str) -> None: self._fn = fn with open(fn) as f: self._rvif = None self._nret = None self._line_num = 0 for line in f.readlines(): self._line_num += 1 line = line.rstrip() if not line: continue if line.endswith("\\"): continue if line.endswith("&&"): continue self._line = line idx = line.find("g_return_val_if_fail") if idx != -1: self._test_rvif() continue idx = line.find("return") if idx != -1: # continue if len(self._tokens) == 2: self._test_return() continue # not a function header if line[0] in ["#", " ", "\t", "{", "}", "/"]: continue # label if line.endswith(":"): continue # remove prefixes if line.startswith("static"): line = line[7:] if line.startswith("inline"): line = line[7:] # a pointer if line.endswith("*"): self._rvif = ["NULL"] self._nret = ["FALSE"] continue # not a leading line if line.find(" ") != -1: continue # a type we know if line in ["void"]: self._rvif = [] self._nret = [] continue if line in ["gpointer"]: self._rvif = ["NULL"] self._nret = ["FALSE"] continue if line in ["gboolean"]: self._rvif = ["TRUE", "FALSE"] self._nret = ["NULL", "0"] continue if line in ["guint32"]: self._rvif = ["0", "G_MAXUINT32"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["GQuark", "GType"]: self._rvif = ["0"] self._nret = ["NULL", "0", "TRUE", "FALSE"] continue if line in ["guint64"]: self._rvif = ["0", "G_MAXUINT64"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["guint16"]: self._rvif = ["0", "G_MAXUINT16"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["guint8"]: self._rvif = ["0", "G_MAXUINT8"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gint64"]: self._rvif = ["0", "-1", "G_MAXINT64"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gint32"]: self._rvif = ["0", "-1", "G_MAXINT32"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gint16"]: self._rvif = ["0", "-1", "G_MAXINT16"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gint8"]: self._rvif = ["0", "-1", "G_MAXINT8"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gint", "int"]: self._rvif = ["0", "-1", "G_MAXINT"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["guint"]: self._rvif = ["0", "G_MAXUINT"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gulong"]: self._rvif = ["0", "G_MAXLONG"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gsize", "size_t"]: self._rvif = ["0", "G_MAXSIZE"] self._nret = ["NULL", "TRUE", "FALSE"] continue if line in ["gssize", "ssize_t"]: self._rvif = ["0", "-1", "G_MAXSSIZE"] self._nret = ["NULL", "TRUE", "FALSE"] continue # print('unknown return type {}'.format(line)) self._rvif = None self._nret = None def test_files(): # test all C source files validator = ReturnValidator() for fn in glob.glob("**/*.c", recursive=True): if fn.startswith("dist/") or fn.startswith("subprojects/"): continue validator.parse(fn) for warning in validator.warnings: print(warning) return 1 if validator.warnings else 0 if __name__ == "__main__": # all done! sys.exit(test_files()) fwupd-1.9.16/contrib/ci/check-potfiles.py000077500000000000000000000022701460375044200202530ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring,consider-using-f-string # pylint: disable=too-few-public-methods # # Copyright (C) 2022 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import glob import sys from typing import List def test_files(): # compare with POTFILES.in with open("po/POTFILES.in", "rb") as f: potfiles_fns: List[str] = f.read().decode().split("\n") for fn in sorted( glob.glob("src/*.c") + glob.glob("plugins/*/*.c") + glob.glob("policy/*.policy.in") + glob.glob("data/*/*.xml") + glob.glob("libfwupdplugin/tests/bios-attrs/*/*.txt") ): if ( fn.startswith("dist/") or fn.startswith("subprojects/") or fn.startswith("build/") ): continue with open(fn, "rb") as f: blob = f.read().decode() if blob.find('_("') != -1 or blob.find("TRANSLATORS") != -1: if fn not in potfiles_fns: print(f"{fn} is missing from po/POTFILES.in") return 1 # success return 0 if __name__ == "__main__": # all done! sys.exit(test_files()) fwupd-1.9.16/contrib/ci/check-quirks.py000077500000000000000000000032061460375044200177440ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2021 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import glob import sys def test_files() -> int: rc: int = 0 for fn in glob.glob("**/*.quirk", recursive=True): with open(fn, "r") as f: for line in f.read().split("\n"): if line.startswith(" ") or line.endswith(" "): print(f"{fn} has leading or trailing whitespace: {line}") rc = 1 continue if not line or line.startswith("#"): continue if line.startswith("["): if not line.endswith("]"): print(f"{fn} has invalid section header: {line}") rc = 1 continue for deprecated in ["DeviceInstanceId", "Guid"]: if line.find(deprecated) != -1: print(f"{fn} has deprecated prefix: {deprecated}") rc = 1 continue else: sections = line.split(" = ") if len(sections) != 2: print(f"{fn} has invalid line: {line}") rc = 1 continue for section in sections: if section.strip() != section: print(f"{fn} has invalid spacing: {line}") rc = 1 break return rc if __name__ == "__main__": # all done! sys.exit(test_files()) fwupd-1.9.16/contrib/ci/check-source.py000077500000000000000000000024001460375044200177210ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-module-docstring,missing-function-docstring # # Copyright (C) 2023 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import glob import sys def test_files() -> int: # test all C and H files rc: int = 0 for fn in glob.glob("**/*.[c|h]", recursive=True): if fn.startswith("subprojects"): continue if fn.startswith("build"): continue if fn.startswith("dist"): continue if fn.startswith("contrib/ci"): continue with open(fn, "rb") as f: for line in f.read().decode().split("\n"): if line.find("nocheck") != -1: continue for token, msg in { "g_error(": "Use GError instead", "g_byte_array_free_to_bytes(": "Use g_bytes_new() instead", }.items(): if line.find(token) != -1: print( f"{fn} contains blocked token {token}: {msg} -- use a nocheck comment to ignore" ) rc = 1 return rc if __name__ == "__main__": # all done! sys.exit(test_files()) fwupd-1.9.16/contrib/ci/check-unused.py000077500000000000000000000037631460375044200177410ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-module-docstring,missing-function-docstring # # Copyright (C) 2023 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import glob import fnmatch import sys import subprocess def test_files() -> int: fns = sys.argv[1:] if not fns: fns.append("./plugins") fns.append("./src") data = [] # find all .o files for fn in fns: for fn in glob.glob(f"{fn}/**/*.o", recursive=True): print(f"Analyzing {fn}...") p = subprocess.run(["nm", fn], check=True, capture_output=True) # parse data for line in p.stdout.decode().split("\n"): line = line.rstrip() if len(line) == 0: continue if line.endswith(".o:"): continue t = line[17:18] if t == "b" or t == "t" or t == "r" or t == "d" or t == "a": continue symb = line[19:] data.append((t, symb)) # collect all the symbols defined symbs = [] for t, symb in data: if t != "T": continue if symb.endswith("_get_type"): continue if fnmatch.fnmatch(symb, "fu_struct_*_set_*"): continue if fnmatch.fnmatch(symb, "fu_struct_*_get_*"): continue if fnmatch.fnmatch(symb, "fu_struct_*_to_string"): continue if symb.find("__proto__") != -1: continue if symb in ["main", "fu_plugin_init_vfuncs"]: continue if symb not in symbs: symbs.append(symb) # remove the ones used for t, symb in data: if t != "U": continue if symb in symbs: symbs.remove(symb) # display symbs.sort() for symb in symbs: print("Unused: ", symb) return 1 if symbs else 0 if __name__ == "__main__": # all done! sys.exit(test_files()) fwupd-1.9.16/contrib/ci/check_missing_translations.sh000077500000000000000000000001251460375044200227410ustar00rootroot00000000000000#!/bin/sh set -e cd po intltool-update -m if [ -f missing ]; then exit 1 fi fwupd-1.9.16/contrib/ci/debian.sh000077500000000000000000000052511460375044200165610ustar00rootroot00000000000000#!/bin/bash set -e set -x export QUBES_OPTION= #although it's debian, we don't build packages if [ "$OS" = "debian-s390x" ]; then ./contrib/ci/debian_s390x.sh exit 0 fi # Set Qubes Os vars if -Dqubes=true is parameter if [ "$QUBES" = "true" ]; then export QUBES_OPTION='-Dqubes=true' fi #prepare export DEBFULLNAME="CI Builder" export DEBEMAIL="ci@travis-ci.org" VERSION=`git describe | sed 's/-/+r/;s/-/+/'` [ -z $VERSION ] && VERSION=`head meson.build | grep ' version:' | cut -d \' -f2` rm -rf build/ mkdir -p build shopt -s extglob cp -R !(build|dist|venv) build/ pushd build mv contrib/debian . sed s/quilt/native/ debian/source/format -i #generate control file ./contrib/ci/generate_debian.py #check if we have all deps available apt update -qq && apt install python3-apt -y ./contrib/ci/fwupd_setup_helpers.py install-dependencies -o debian --yes || true dpkg-checkbuilddeps #clone test firmware if necessary . ./contrib/ci/get_test_firmware.sh #disable unit tests if fwupd is already installed (may cause problems) if [ -x /usr/lib/fwupd/fwupd ]; then export DEB_BUILD_OPTIONS=nocheck fi #build the package EDITOR=/bin/true dch --create --package fwupd -v $VERSION "CI Build" debuild --no-lintian --preserve-envvar CI --preserve-envvar CC \ --preserve-envvar QUBES_OPTION #check lintian output #suppress tags that are side effects of building in docker this way lintian ../*changes \ -IE \ --pedantic \ --no-tag-display-limit \ --suppress-tags missing-build-dependency-for-dh-addon \ --suppress-tags library-not-linked-against-libc \ --suppress-tags bad-distribution-in-changes-file \ --suppress-tags debian-watch-file-in-native-package \ --suppress-tags source-nmu-has-incorrect-version-number \ --suppress-tags no-symbols-control-file \ --suppress-tags gzip-file-is-not-multi-arch-same-safe \ --suppress-tags missing-dependency-on-libc \ --suppress-tags arch-dependent-file-not-in-arch-specific-directory \ --allow-root #if invoked outside of CI if [ ! -f /.dockerenv ]; then echo "Not running in a container, please manually install packages" exit 0 fi #test the packages install PACKAGES=$(find .. -type f -name "*.deb" | grep -v 'fwupd-tests\|dbgsym') dpkg -i $PACKAGES # copy in more non-generated data mkdir -p /usr/share/installed-tests/fwupd cp fwupd-test-firmware/installed-tests/* /usr/share/installed-tests/fwupd/ -LRv # run the installed tests if [ "$CI" = "true" ]; then dpkg -i ../fwupd-tests*.deb service dbus restart gnome-desktop-testing-runner fwupd apt purge -y fwupd-tests fi #test the packages remove apt purge -y fwupd \ fwupd-doc \ libfwupd2 \ libfwupd-dev #place built packages in dist outside docker mkdir -p ../dist cp $PACKAGES ../dist fwupd-1.9.16/contrib/ci/debian_s390x.sh000077500000000000000000000014001460375044200175170ustar00rootroot00000000000000#!/bin/sh set -e set -x export LC_ALL=C.UTF-8 #evaluate using Debian's build flags eval "$(dpkg-buildflags --export=sh)" #filter out -Bsymbolic-functions LDFLAGS=$(dpkg-buildflags --get LDFLAGS | sed "s/-Wl,-Bsymbolic-functions\s//") export LDFLAGS rm -rf build mkdir -p build cp contrib/ci/s390x_cross.txt build/ cd build meson setup .. \ --cross-file s390x_cross.txt \ --werror \ -Dplugin_flashrom=disabled \ -Dplugin_uefi_capsule=disabled \ -Dplugin_modem_manager=disabled \ -Dplugin_msr=disabled \ -Dplugin_mtd=false \ -Dplugin_powerd=disabled \ -Dintrospection=false \ -Dlibxmlb:introspection=false \ -Dlibxmlb:gtkdoc=false \ -Dman=false ninja -v ninja test -v cd .. #test for missing translation files ./contrib/ci/check_missing_translations.sh fwupd-1.9.16/contrib/ci/dependencies.xml000066400000000000000000001335251460375044200201560ustar00rootroot00000000000000 amd64 arm64 armhf i386 clang-tools-extra mingw64-gcc cairo-devel cairo-devel cairo-devel cairo-devel libcairo-dev:s390x cairo-gobject-devel cairo-gobject-devel cairo-gobject-devel libcairo-gobject2:s390x libdrm libdrm-devel libdrm-devel json-glib json-glib-devel json-glib-devel json-glib-devel mingw64-json-glib json-glib-devel (>= 1.1.1) libjson-glib-dev:s390x (>= 1.1.1) libftdi libftdi-devel libftdi-devel libftdi-devel pciutils pciutils-devel pciutils-devel pciutils-devel noto-fonts google-noto-sans-cjk-ttc-fonts google-noto-sans-cjk-ttc-fonts google-noto-sans-cjk-ttc-fonts noto-fonts-cjk (>= 12) (>= 12) freetype freetype freetype libfreetype-dev:s390x ia64 ia64 flashrom-devel flashrom-devel ia64 ia64 gettext mingw64-gettext (>= 0.19.8.1) (>= 0.19.8.1) gnu-efi-libs gnu-efi-libs amd64 arm64 armhf i386 gnu-efi gnu-efi amd64 arm64 armhf i386 gnu-efi gnu-efi glib2-devel glib2-devel glib2-devel glib2-devel mingw64-glib2 glib-devel (>= 2.45.8) libglib2.0-dev:s390x (>= 2.45.8) glibc-langpack-en glibc-langpack-en gobject-introspection-devel gobject-introspection-devel gobject-introspection-devel gnutls-devel gnutls-devel gnutls-devel mingw64-gnutls gnutls-devel libgnutls28-dev:s390x libgnutls28-dev gnutls-utils gnutls-utils gnutls-utils gnutls-tools libxmlb libxmlb-devel libxmlb-devel libxmlb-devel (>= 0.1.13) libxmlb-dev:s390x (>= 0.1.13) libjcat-devel libjcat-devel libjcat-devel xz-devel xz-devel mingw64-zstd libzstd-dev:s390x libarchive-devel libarchive-devel libarchive-devel mingw64-libarchive libarchive-dev:s390x libarchive-devel libcbor libcbor-devel libcbor-devel libcbor-devel libcbor-dev:s390x libcbor-devel libgudev1-devel libgudev1-devel libgudev1-devel libgudev-1.0-dev:s390x libgusb libgusb-devel libgusb-devel libgusb-devel mingw64-libgusb libgusb-devel (>= 0.3.5) libgusb-dev:s390x (>= 0.3.3) libicu-dev:s390x libidn2-0-dev:s390x curl libcurl-devel libcurl-devel libcurl-devel mingw64-curl libcurl-devel libcurl4-gnutls-dev:s390x passim-devel passim-devel passim kernel-headers amd64 arm64 armhf i386 amd64 arm64 armhf i386 pango-devel pango-devel pango-devel pango-devel mingw64-pkg-config polkit polkit polkit polkit polkit ModemManager-glib-devel ModemManager-glib-devel ModemManager-glib-devel modemmanager libmm-glib-dev:s390x libqmi-devel libqmi-devel libqmi-devel libqmi libqmi-glib-dev:s390x libmbim-devel libmbim-devel libmbim-devel libmbim libmbim-glib-dev:s390x libqrtr-glib-dev:s390x polkit-devel polkit-devel polkit-devel polkit-devel libpolkit-gobject-1-dev:s390x python-pip python34-devel python-cairo python3-cairo python3-cairo python-pefile python-gobject python-packaging qemu-user sqlite-devel sqlite-devel sqlite-devel mingw64-sqlite libsqlite3-dev:s390x sqlite-devel elogind-devel (>= 231) systemd-dev systemd-dev systemd-dev (>= 231) systemd-dev systemd-devel systemd-devel systemd-devel libsystemd-dev:s390x umockdev-devel umockdev-devel umockdev-devel vala vala vala vala vala valgrind-devel valgrind-devel valgrind-devel valgrind-if-available valgrind-if-available valgrind-if-available valgrind-if-available tpm2-tss tpm2-tss-devel tpm2-tss-devel tpm2-tss-devel libtss2-dev:s390x amd64 arm64 armhf i386 tpm2-tss-devel ShellCheck protobuf-c protobuf-c-devel protobuf-c-devel protobuf-c-devel protobuf python-virtualenv wine msitools fwupd-1.9.16/contrib/ci/fedora.sh000077500000000000000000000043421460375044200165770ustar00rootroot00000000000000#!/bin/bash set -e set -x #get any missing deps from the container ./contrib/ci/fwupd_setup_helpers.py install-dependencies --yes -o fedora # disable the safe directory feature git config --global safe.directory "*" #generate a tarball mkdir -p build && pushd build rm -rf * if [ "$QUBES" = "true" ]; then QUBES_MACRO=(--define "qubes_packages 1") fi # smoke test something small with no auto-deps meson setup .. \ -Dbuild=library \ -Dauto_features=disabled \ -Dtests=false ninja-build rm -rf * # do the full-fat build meson setup \ -Ddocs=disabled \ -Dman=true \ -Dtests=true \ -Dcompat_cli=true \ -Db_sanitize=undefined \ -Dgusb:tests=false \ -Dplugin_flashrom=enabled \ -Dplugin_modem_manager=disabled \ -Dplugin_uefi_capsule=enabled \ -Dplugin_synaptics_mst=enabled $@ ninja-build ../contrib/ci/check-unused.py ninja-build dist popd VERSION=`meson introspect build --projectinfo | jq -r .version` RPMVERSION=${VERSION//-/.} mkdir -p $HOME/rpmbuild/SOURCES/ mv build/meson-dist/fwupd-$VERSION.tar.xz $HOME/rpmbuild/SOURCES/ #generate a spec file sed "s,#VERSION#,$RPMVERSION,; s,#BUILD#,1,; s,#LONGDATE#,`date '+%a %b %d %Y'`,; s,#ALPHATAG#,alpha,; s,enable_dummy 0,enable_dummy 1,; s,Source0.*,Source0:\tfwupd-$VERSION.tar.xz," \ build/contrib/fwupd.spec.in > build/fwupd.spec if [ -n "$CI" ]; then sed -i "s,enable_ci 0,enable_ci 1,;" build/fwupd.spec fi #build RPM packages rpmbuild -ba "${QUBES_MACRO[@]}" build/fwupd.spec #if invoked outside of CI if [ ! -f /.dockerenv ]; then echo "Not running in a container, please manually install packages" exit 0 fi #install RPM packages dnf install -y $HOME/rpmbuild/RPMS/*/*.rpm mkdir -p dist cp $HOME/rpmbuild/RPMS/*/*.rpm dist if [ "$CI" = "true" ]; then fwupdtool enable-test-devices # set up enough PolicyKit and D-Bus to run the daemon mkdir -p /run/dbus mkdir -p /var ln -s /var/run /run dbus-daemon --system --fork /usr/lib/polkit-1/polkitd & sleep 5 # run the daemon startup to check it can start /usr/libexec/fwupd/fwupd --immediate-exit --verbose # run the installed tests whilst the daemon debugging /usr/libexec/fwupd/fwupd --verbose & sleep 10 gnome-desktop-testing-runner fwupd fi fwupd-1.9.16/contrib/ci/flatpak.py000077500000000000000000000057411460375044200170030ustar00rootroot00000000000000#!/usr/bin/python3 import subprocess import os import json import shutil def prepare(target): # clone the flatpak json cmd = ["git", "submodule", "update", "--remote", "contrib/flatpak"] subprocess.run(cmd, check=True) # clone the submodules for that cmd = ["git", "submodule", "update", "--init", "--remote", "shared-modules/"] subprocess.run(cmd, cwd="contrib/flatpak", check=True) # parse json if os.path.isdir("build"): shutil.rmtree("build") data = {} with open("contrib/flatpak/org.freedesktop.fwupd.json", "r") as rfd: data = json.load(rfd, strict=False) platform = f"runtime/{data['runtime']}/x86_64/{data['runtime-version']}" sdk = f"runtime/{data['sdk']}/x86_64/{data['runtime-version']}" num_modules = len(data["modules"]) # update to build from main data["branch"] = "main" for index in range(0, num_modules): module = data["modules"][index] if type(module) != dict or "name" not in module: continue name = module["name"] if "fwupd" not in name: continue data["modules"][index]["sources"][0].pop("url") data["modules"][index]["sources"][0].pop("sha256") data["modules"][index]["sources"][0]["type"] = "dir" data["modules"][index]["sources"][0]["skip"] = [".git"] data["modules"][index]["sources"][0]["path"] = ".." # write json os.mkdir("build") with open(target, "w") as wfd: json.dump(data, wfd, indent=4) os.symlink("../contrib/flatpak/shared-modules", "build/shared-modules") # install the runtimes (parsed from json!) repo = "flathub" repo_url = "https://dl.flathub.org/repo/flathub.flatpakrepo" print("Installing dependencies") cmd = ["flatpak", "remote-add", "--if-not-exists", repo, repo_url] subprocess.run(cmd, check=True) cmd = ["flatpak", "install", "--assumeyes", repo, sdk] subprocess.run(cmd, check=True) cmd = ["flatpak", "install", "--assumeyes", repo, platform] subprocess.run(cmd, check=True) def build(target): cmd = [ "flatpak-builder", "--repo=repo", "--force-clean", "--disable-rofiles-fuse", "build-dir", target, ] subprocess.run(cmd, check=True) cmd = ["flatpak", "build-bundle", "repo", "fwupd.flatpak", "org.freedesktop.fwupd"] subprocess.run(cmd, check=True) if __name__ == "__main__": t = os.path.join("build", "org.freedesktop.fwupd.json") prepare(t) build(t) # to run from the builddir: # sudo flatpak-builder --run build-dir org.freedesktop.fwupd.json /app/libexec/fwupd/fwupdtool get-devices # install the single file bundle # flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo # flatpak install fwupd.flatpak # to run a shell in the same environment that flatpak sees: # flatpak run --command=sh --devel org.freedesktop.fwupd # to run fwupdtool as root: # sudo -i flatpak run org.freedesktop.fwupd --verbose get-devices fwupd-1.9.16/contrib/ci/fwupd_setup_helpers.py000077500000000000000000000172111460375044200214430ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Dell, Inc. # Copyright (C) 2020 Intel, Inc. # Copyright (C) 2021 Mario Limonciello # # SPDX-License-Identifier: LGPL-2.1+ # import os import sys import argparse WARNING = "\033[93m" ENDC = "\033[0m" # Minimum version of markdown required MINIMUM_MARKDOWN = (3, 2, 0) def get_possible_profiles(): return ["fedora", "centos", "debian", "ubuntu", "arch", "void"] def detect_profile(): try: import distro target = distro.id() if target not in get_possible_profiles(): target = distro.like() except ModuleNotFoundError: target = "" return target def pip_install_package(debug, name): import subprocess cmd = ["python3", "-m", "pip", "install", "--upgrade", name] if debug: print(cmd) subprocess.call(cmd) def test_jinja2(debug): try: import jinja2 except ModuleNotFoundError: print("python3-jinja2 must be installed/upgraded") pip_install_package(debug, "jinja2") def test_markdown(debug): try: import markdown new_enough = markdown.__version_info__ >= MINIMUM_MARKDOWN except ModuleNotFoundError: new_enough = False if not new_enough: print("python3-markdown must be installed/upgraded") pip_install_package(debug, "markdown") def get_minimum_meson_version(): import re directory = os.path.join(os.path.dirname(sys.argv[0]), "..", "..") with open(os.path.join(directory, "meson.build"), "r") as f: for line in f: if "meson_version" in line: return re.search(r"(\d+\.\d+\.\d+)", line).group(1) def test_meson(debug): from importlib.metadata import version, PackageNotFoundError minimum = get_minimum_meson_version() try: new_enough = version("meson") >= minimum except PackageNotFoundError: import subprocess try: ver = ( subprocess.check_output(["meson", "--version"]).strip().decode("utf-8") ) new_enough = ver >= minimum except FileNotFoundError: new_enough = False if not new_enough: print("meson must be installed/upgraded") pip_install_package(debug, "meson") def parse_dependencies(OS, variant, add_control): import xml.etree.ElementTree as etree deps = [] dep = "" directory = os.path.dirname(sys.argv[0]) tree = etree.parse(os.path.join(directory, "dependencies.xml")) root = tree.getroot() for child in root: if "id" not in child.attrib: continue for distro in child: if "id" not in distro.attrib: continue if distro.attrib["id"] != OS: continue control = "" if add_control: inclusive = [] exclusive = [] if not distro.findall("control"): continue for control_parent in distro.findall("control"): for obj in control_parent.findall("inclusive"): inclusive.append(obj.text) for obj in control_parent.findall("exclusive"): exclusive.append(obj.text) if inclusive or exclusive: inclusive = " ".join(inclusive).strip() exclusive = " !".join(exclusive).strip() if exclusive: exclusive = f"!{exclusive}" control = f" [{inclusive}{exclusive}]" for package in distro.findall("package"): if variant: if "variant" not in package.attrib: continue if package.attrib["variant"] != variant: continue if package.text: dep = package.text else: dep = child.attrib["id"] dep += control if dep: deps.append(dep) return deps def _validate_deps(os, deps): validated = deps if os == "debian" or os == "ubuntu": try: from apt import cache cache = cache.Cache() for pkg in deps: if not cache.has_key(pkg) and not cache.is_virtual_package(pkg): print( f"{WARNING}WARNING:{ENDC} ignoring unavailable package %s" % pkg ) validated.remove(pkg) except ModuleNotFoundError: print( f"{WARNING}WARNING:{ENDC} Unable to validate package dependency list without python3-apt" ) return validated def get_build_dependencies(os, variant): parsed = parse_dependencies(os, variant, False) return _validate_deps(os, parsed) def _get_installer_cmd(os, yes): if os == "debian" or os == "ubuntu": installer = ["apt", "install"] elif os == "fedora": installer = ["dnf", "install"] elif os == "arch": installer = ["pacman", "-Syu", "--noconfirm", "--needed"] elif os == "void": installer = ["xbps-install", "-Syu"] else: print("unable to detect OS profile, use --os= to specify") print(f"\tsupported profiles: {get_possible_profiles()}") sys.exit(1) if yes: installer += ["-y"] return installer def install_packages(os, variant, yes, debugging, packages): import subprocess if packages == "build-dependencies": packages = get_build_dependencies(os, variant) installer = _get_installer_cmd(os, yes) installer += packages if debugging: print(installer) subprocess.check_call(installer) if __name__ == "__main__": command = None # compat mode for old training documentation if "generate_dependencies.py" in sys.argv[0]: command = "get-dependencies" parser = argparse.ArgumentParser() if not command: parser.add_argument( "command", choices=[ "get-dependencies", "test-markdown", "test-jinja2", "test-meson", "detect-profile", "install-dependencies", "install-pip", ], help="command to run", ) parser.add_argument( "-o", "--os", default=detect_profile(), choices=get_possible_profiles(), help="calculate dependencies for OS profile", ) parser.add_argument( "-v", "--variant", help="optional machine variant for the OS profile" ) parser.add_argument( "-y", "--yes", action="store_true", help="Don't prompt to install" ) parser.add_argument( "-d", "--debug", action="store_true", help="Display all launched commands" ) args = parser.parse_args() # fall back in all cases if not args.variant: args.variant = os.uname().machine if not command: command = args.command # command to run if command == "test-markdown": test_markdown(args.debug) elif command == "test-jinja2": test_jinja2(args.debug) elif command == "test-meson": test_meson(args.debug) elif command == "detect-profile": print(detect_profile()) elif command == "get-dependencies": dependencies = get_build_dependencies(args.os, args.variant) print(*dependencies, sep="\n") elif command == "install-dependencies": install_packages( args.os, args.variant, args.yes, args.debug, "build-dependencies" ) elif command == "install-pip": install_packages(args.os, args.variant, args.yes, args.debug, ["python3-pip"]) fwupd-1.9.16/contrib/ci/generate_debian.py000077500000000000000000000060671460375044200204570ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Dell, Inc. # # SPDX-License-Identifier: LGPL-2.1+ # import os import sys from fwupd_setup_helpers import parse_dependencies def parse_control_dependencies(): QUBES = os.getenv("QUBES") return parse_dependencies("debian", "x86_64", True), QUBES def update_debian_control(target): control_in = os.path.join(target, "control.in") control_out = os.path.join(target, "control") if not os.path.exists(control_in): print(f"Missing file {control_in}") sys.exit(1) with open(control_in, "r") as rfd: lines = rfd.readlines() deps, QUBES = parse_control_dependencies() deps.sort() if QUBES: lines += "\n" control_qubes_in = os.path.join(target, "control.qubes.in") with open(control_qubes_in, "r") as rfd: lines += rfd.readlines() with open(control_out, "w") as wfd: for line in lines: if "Build-Depends:" in line and "%%%DYNAMIC%%%" in line: wfd.write("Build-Depends:\n") for i in range(0, len(deps)): wfd.write(f"\t{deps[i]},\n") elif "fwupd-qubes-vm-whonix" in line and not QUBES: break else: wfd.write(line) def update_debian_copyright(directory): copyright_in = os.path.join(directory, "copyright.in") copyright_out = os.path.join(directory, "copyright") if not os.path.exists(copyright_in): print(f"Missing file {copyright_in}") sys.exit(1) # Assume all files are remaining LGPL-2.1+ copyrights = [] for root, dirs, files in os.walk("."): for file in files: target = os.path.join(root, file) # skip translations and license file if target.startswith("./po/") or file == "COPYING": continue try: with open(target, "r") as rfd: # read about the first few lines of the file only lines = rfd.readlines(220) except UnicodeDecodeError: continue except FileNotFoundError: continue for line in lines: if "Copyright (C) " in line: parts = line.split("Copyright (C)")[ 1 ].strip() # split out the copyright header partition = parts.partition(" ")[2] # remove the year string copyrights += [f"{partition}"] copyrights = "\n\t ".join(sorted(set(copyrights))) with open(copyright_in, "r") as rfd: lines = rfd.readlines() with open(copyright_out, "w") as wfd: for line in lines: if line.startswith("%%%DYNAMIC%%%"): wfd.write("Files: *\n") wfd.write(f"Copyright: {copyrights}\n") wfd.write("License: LGPL-2.1+\n") wfd.write("\n") else: wfd.write(line) directory = os.path.join(os.getcwd(), "debian") update_debian_control(directory) update_debian_copyright(directory) fwupd-1.9.16/contrib/ci/generate_dependencies.py000077700000000000000000000000001460375044200263032fwupd_setup_helpers.pyustar00rootroot00000000000000fwupd-1.9.16/contrib/ci/generate_docker.py000077500000000000000000000061311460375044200204740ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Dell, Inc. # # SPDX-License-Identifier: LGPL-2.1+ # import os import subprocess import sys import shutil from fwupd_setup_helpers import parse_dependencies def get_container_cmd(): """return docker or podman as container manager""" if shutil.which("docker"): return "docker" if shutil.which("podman"): return "podman" directory = os.path.dirname(sys.argv[0]) TARGET = os.getenv("OS") if TARGET is None: print("Missing OS environment variable") sys.exit(1) OS = TARGET SUBOS = "" split = TARGET.split("-") if len(split) >= 2: OS = split[0] SUBOS = split[1] deps = parse_dependencies(OS, SUBOS, False) f = os.path.join(directory, f"Dockerfile-{OS}.in") if not os.path.exists(f): print(f"Missing input file {f} for {OS}") sys.exit(1) with open(f, "r") as rfd: lines = rfd.readlines() with open("Dockerfile", "w") as wfd: for line in lines: if line.startswith("FROM %%%ARCH_PREFIX%%%"): if (OS == "debian" or OS == "ubuntu") and SUBOS == "i386": replace = SUBOS + "/" else: replace = "" wfd.write(line.replace("%%%ARCH_PREFIX%%%", replace)) elif line == "%%%INSTALL_DEPENDENCIES_COMMAND%%%\n": if OS == "fedora": wfd.write("RUN dnf --enablerepo=updates-testing -y install \\\n") elif OS == "centos": wfd.write("RUN yum -y install \\\n") elif OS == "debian" or OS == "ubuntu": wfd.write("RUN apt update -qq && \\\n") wfd.write( "\tDEBIAN_FRONTEND=noninteractive apt install -yq --no-install-recommends\\\n" ) elif OS == "arch": wfd.write("RUN pacman -Syu --noconfirm --needed\\\n") elif OS == "void": wfd.write( "RUN xbps-install -Suy xbps && xbps-install -uy && xbps-install -y \\\n" ) for i in range(0, len(deps)): if i < len(deps) - 1: wfd.write(f"\t{deps[i]} \\\n") else: wfd.write(f"\t{deps[i]} || true\n") elif line == "%%%ARCH_SPECIFIC_COMMAND%%%\n": if OS == "debian" and SUBOS == "s390x": # add sources wfd.write( 'RUN cat /etc/apt/sources.list | sed "s/deb/deb-src/" >> /etc/apt/sources.list\n' ) # add new architecture wfd.write(f"RUN dpkg --add-architecture {SUBOS}\n") elif line == "%%%OS%%%\n": wfd.write(f"ENV OS {TARGET}\n") else: wfd.write(line) wfd.flush() if len(sys.argv) == 2 and sys.argv[1] == "build": cmd = get_container_cmd() args = [cmd, "build", "-t", f"fwupd-{TARGET}"] if "http_proxy" in os.environ: args += [f"--build-arg=http_proxy={os.environ['http_proxy']}"] if "https_proxy" in os.environ: args += [f"--build-arg=https_proxy={os.environ['https_proxy']}"] args += ["-f", "./Dockerfile", "."] subprocess.check_call(args) fwupd-1.9.16/contrib/ci/generate_news.py000077500000000000000000000014071460375044200202020ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2020 Dell Inc. # # SPDX-License-Identifier: LGPL-2.1+ # import os import argparse import xml.etree.ElementTree as etree if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("version", help="Generate news for release") args = parser.parse_args() tree = etree.parse(os.path.join("data", "org.freedesktop.fwupd.metainfo.xml")) root = tree.getroot() for release in root.iter("release"): if "version" not in release.attrib: continue if release.attrib["version"] != args.version: continue description = release.find("description") result = etree.tostring(description, encoding="unicode", method="text") print(result.strip()) fwupd-1.9.16/contrib/ci/get_test_firmware.sh000077500000000000000000000004351460375044200210500ustar00rootroot00000000000000#!/bin/sh if [ "$CI_NETWORK" = "true" ]; then #clone fwupd-test-firmware rm -rf fwupd-test-firmware git clone https://github.com/fwupd/fwupd-test-firmware #copy data for self-tests into the source tree cp fwupd-test-firmware/ci-tests/* . -R fi fwupd-1.9.16/contrib/ci/oss-fuzz.py000077500000000000000000000414601460375044200171570ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2021 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ # # pylint: disable=too-many-instance-attributes,no-self-use import os import sys import subprocess import glob from typing import Dict, Optional, List, Union DEFAULT_BUILDDIR = ".ossfuzz" class Builder: def __init__(self) -> None: self.cc = self._ensure_environ("CC", "gcc") self.cxx = self._ensure_environ("CXX", "g++") self.builddir = self._ensure_environ("WORK", os.path.realpath(DEFAULT_BUILDDIR)) self.installdir = self._ensure_environ( "OUT", os.path.realpath(os.path.join(DEFAULT_BUILDDIR, "out")) ) self.srcdir = self._ensure_environ("SRC", os.path.realpath("..")) self.ldflags = [ "-lpthread", "-lresolv", "-ldl", "-lffi", "-lz", "-llzma", "-lzstd", ] # defined in env self.cflags = ["-Wno-deprecated-declarations", "-g"] if "CFLAGS" in os.environ: self.cflags += os.environ["CFLAGS"].split(" ") self.cxxflags = [] if "CXXFLAGS" in os.environ: self.cxxflags += os.environ["CXXFLAGS"].split(" ") # set up shared / static os.environ["PKG_CONFIG"] = "pkg-config --static" if "PATH" in os.environ: os.environ["PATH"] = "{}:{}".format( os.environ["PATH"], os.path.join(self.builddir, "bin") ) else: os.environ["PATH"] = os.path.join(self.builddir, "bin") os.environ["PKG_CONFIG_PATH"] = os.path.join(self.builddir, "lib", "pkgconfig") # writable os.makedirs(self.builddir, exist_ok=True) os.makedirs(self.installdir, exist_ok=True) def _ensure_environ(self, key: str, value: str) -> str: """set the environment unless already set""" if key not in os.environ: os.environ[key] = value return os.environ[key] def checkout_source(self, name: str, url: str, commit: Optional[str] = None) -> str: """checkout source tree, optionally to a specific commit""" srcdir_name = os.path.join(self.srcdir, name) if os.path.exists(srcdir_name): return srcdir_name subprocess.run(["git", "clone", url], cwd=self.srcdir, check=True) if commit: subprocess.run(["git", "checkout", commit], cwd=srcdir_name, check=True) return srcdir_name def build_meson_project(self, srcdir: str, argv) -> None: """configure and build the meson project""" srcdir_build = os.path.join(srcdir, DEFAULT_BUILDDIR) if not os.path.exists(srcdir_build): subprocess.run( [ "meson", "--prefix", self.builddir, "--libdir", "lib", "--default-library", "static", ] + argv + [DEFAULT_BUILDDIR], cwd=srcdir, check=True, ) subprocess.run(["ninja", "install"], cwd=srcdir_build, check=True) def build_cmake_project(self, srcdir: str, argv=None) -> None: """configure and build the meson project""" if not argv: argv = [] srcdir_build = os.path.join(srcdir, DEFAULT_BUILDDIR) if not os.path.exists(srcdir_build): os.makedirs(srcdir_build, exist_ok=True) subprocess.run( [ "cmake", f"-DCMAKE_INSTALL_PREFIX:PATH={self.builddir}", "-DCMAKE_INSTALL_LIBDIR=lib", ] + argv + [".."], cwd=srcdir_build, check=True, ) subprocess.run(["make", "all", "install"], cwd=srcdir_build, check=True) def add_work_includedir(self, value: str) -> None: """add a CFLAG""" self.cflags.append(f"-I{self.builddir}/{value}") def add_src_includedir(self, value: str) -> None: """add a CFLAG""" self.cflags.append(f"-I{self.srcdir}/{value}") def add_build_ldflag(self, value: str) -> None: """add a LDFLAG""" self.ldflags.append(os.path.join(self.builddir, value)) def substitute(self, src: str, replacements: Dict[str, str]) -> str: """map changes""" dst = os.path.basename(src).replace(".in", "") with open(os.path.join(self.srcdir, src), "r") as f: blob = f.read() for key in replacements: blob = blob.replace(key, replacements[key]) with open(os.path.join(self.builddir, dst), "w") as out: out.write(blob) return dst def compile(self, src: str) -> str: """compile a specific source file""" argv = [self.cc] argv.extend(self.cflags) fullsrc = os.path.join(self.srcdir, src) if not os.path.exists(fullsrc): fullsrc = os.path.join(self.builddir, src) dst = os.path.basename(src).replace(".c", ".o") argv.extend(["-c", fullsrc, "-o", os.path.join(self.builddir, dst)]) print(f"building {src} into {dst}") try: subprocess.run(argv, cwd=self.srcdir, check=True) except subprocess.CalledProcessError as e: print(e) sys.exit(1) return os.path.join(self.builddir, f"{dst}") def rustgen(self, src: str) -> str: fn_root = os.path.basename(src).replace(".rs", "") fulldst_c = os.path.join(self.builddir, f"{fn_root}-struct.c") fulldst_h = os.path.join(self.builddir, f"{fn_root}-struct.h") try: subprocess.run( [ "python", "fwupd/libfwupdplugin/rustgen.py", src, fulldst_c, fulldst_h, ], cwd=self.srcdir, check=True, ) except subprocess.CalledProcessError as e: print(e) sys.exit(1) return fulldst_c def link(self, objs: List[str], dst: str) -> None: """link multiple objects into a binary""" argv = [self.cxx] + self.cxxflags for obj in objs: if obj.startswith("-"): argv.append(obj) else: argv.append(os.path.join(self.builddir, obj)) argv += ["-o", os.path.join(self.installdir, dst)] argv += self.ldflags print(f"building {','.join(objs)} into {dst}") subprocess.run(argv, cwd=self.srcdir, check=True) def mkfuzztargets(self, globstr: str) -> None: """make binary fuzzing targets from builder.xml files""" builder_xmls = glob.glob(globstr) if not builder_xmls: print(f"failed to find {globstr}") sys.exit(1) for fn_src in builder_xmls: fn_dst = fn_src.replace(".builder.xml", ".bin") if os.path.exists(fn_dst): continue print(f"building {fn_src} into {fn_dst}") try: argv = [ "build/src/fwupdtool", "firmware-build", fn_src, fn_dst, ] subprocess.run(argv, check=True) except subprocess.CalledProcessError as e: print(f"tried to run: `{' '.join(argv)}` and got {str(e)}") sys.exit(1) def write_header( self, dst: str, defines: Dict[str, Optional[Union[str, int]]] ) -> None: """write a header file""" dstdir = os.path.join(self.builddir, os.path.dirname(dst)) os.makedirs(dstdir, exist_ok=True) print(f"writing {dst}") with open(os.path.join(dstdir, os.path.basename(dst)), "w") as f: for key in defines: value = defines[key] if value is not None: if isinstance(value, int): f.write(f"#define {key} {value}\n") else: f.write(f'#define {key} "{value}"\n') else: f.write(f"#define {key}\n") self.add_work_includedir(os.path.dirname(dst)) def makezip(self, dst: str, globstr: str) -> None: """create a zip file archive from a glob""" argv = ["zip", "--junk-paths", os.path.join(self.installdir, dst)] + glob.glob( os.path.join(self.srcdir, globstr) ) print(f"assembling {dst}") subprocess.run(argv, cwd=self.srcdir, check=True) def grep_meson(self, src: str, token: str = "fuzzing") -> List[str]: """find source files tagged with a specific comment""" srcs = [] with open(os.path.join(self.srcdir, src, "meson.build"), "r") as f: for line in f.read().split("\n"): if line.find(token) == -1: continue # get rid of token line = line.split("#")[0] # get rid of variable try: line = line.split("=")[1] except IndexError: pass # get rid of whitespace for char in ["'", ",", " "]: line = line.replace(char, "") # all done srcs.append(os.path.join(src, line)) return srcs class Fuzzer: def __init__(self, name, srcdir=None, globstr=None, pattern=None) -> None: self.name = name self.srcdir = srcdir or name self.globstr = globstr or f"{name}*.bin" self.pattern = pattern or f"{name}-firmware" @property def new_gtype(self) -> str: return f"g_object_new(FU_TYPE_{self.pattern.replace('-', '_').upper()}, NULL)" @property def header(self) -> str: return f"fu-{self.pattern}.h" def _build(bld: Builder) -> None: # CBOR src = bld.checkout_source( "libcbor", url="https://github.com/PJK/libcbor.git", commit="v0.9.0" ) bld.build_cmake_project(src) bld.add_build_ldflag("lib/libcbor.a") # GLib src = bld.checkout_source( "glib", url="https://gitlab.gnome.org/GNOME/glib.git", commit="glib-2-68" ) bld.build_meson_project( src, [ "-Dlibmount=disabled", "-Dselinux=disabled", "-Dnls=disabled", "-Dlibelf=disabled", "-Dbsymbolic_functions=false", "-Dtests=false", "-Dinternal_pcre=true", "--force-fallback-for=libpcre", ], ) bld.add_work_includedir("include/glib-2.0") bld.add_work_includedir("lib/glib-2.0/include") bld.add_build_ldflag("lib/libgio-2.0.a") bld.add_build_ldflag("lib/libgmodule-2.0.a") bld.add_build_ldflag("lib/libgobject-2.0.a") bld.add_build_ldflag("lib/libglib-2.0.a") bld.add_build_ldflag("lib/libgthread-2.0.a") # JSON-GLib src = bld.checkout_source( "json-glib", url="https://gitlab.gnome.org/GNOME/json-glib.git" ) bld.build_meson_project( src, ["-Dgtk_doc=disabled", "-Dtests=false", "-Dintrospection=disabled"] ) bld.add_work_includedir("include/json-glib-1.0/json-glib") bld.add_work_includedir("include/json-glib-1.0") bld.add_build_ldflag("lib/libjson-glib-1.0.a") # libxmlb src = bld.checkout_source("libxmlb", url="https://github.com/hughsie/libxmlb.git") bld.build_meson_project( src, ["-Dgtkdoc=false", "-Dintrospection=false", "-Dtests=false"] ) bld.add_work_includedir("include/libxmlb-2") bld.add_work_includedir("include/libxmlb-2/libxmlb") bld.add_build_ldflag("lib/libxmlb.a") # write required headers bld.write_header("libfwupd/fwupd-version.h", {}) bld.write_header( "config.h", { "FWUPD_DATADIR": "/tmp", "FWUPD_LOCALSTATEDIR": "/tmp", "FWUPD_LIBDIR_PKG": "/tmp", "FWUPD_SYSCONFDIR": "/tmp", "FWUPD_LIBEXECDIR": "/tmp", "HAVE_REALPATH": None, "PACKAGE_NAME": "fwupd", "PACKAGE_VERSION": "0.0.0", }, ) bld.write_header( "libfwupdplugin/fu-version.h", { "FU_MAJOR_VERSION": 0, "FU_MINOR_VERSION": 0, "FU_MICRO_VERSION": 0, }, ) # libfwupd + libfwupdplugin built_objs: List[str] = [] bld.add_src_includedir("fwupd") for path in ["fwupd/libfwupd", "fwupd/libfwupdplugin"]: bld.add_src_includedir(path) for src in bld.grep_meson(path): if src.endswith(".c"): built_objs.append(bld.compile(src)) elif src.endswith(".rs"): built_objs.append(bld.compile(bld.rustgen(src))) # dummy binary entrypoint if "LIB_FUZZING_ENGINE" in os.environ: built_objs.append(os.environ["LIB_FUZZING_ENGINE"]) else: built_objs.append(bld.compile("fwupd/libfwupdplugin/fu-fuzzer-main.c")) # built in formats for fzr in [ Fuzzer("csv"), Fuzzer("cab"), Fuzzer("dfuse"), Fuzzer("edid", pattern="edid"), Fuzzer("fdt"), Fuzzer("fit"), Fuzzer("fmap"), Fuzzer("hid-descriptor", pattern="hid-descriptor"), Fuzzer("ihex"), Fuzzer("srec"), Fuzzer("intel-thunderbolt"), Fuzzer("ifwi-cpd"), Fuzzer("ifwi-fpt"), Fuzzer("oprom"), Fuzzer("uswid"), Fuzzer("efi-firmware-filesystem", pattern="efi-firmware-filesystem"), Fuzzer("efi-firmware-volume", pattern="efi-firmware-volume"), Fuzzer("efi-load-option", pattern="efi-load-option"), Fuzzer("ifd"), ]: src = bld.substitute( "fwupd/libfwupdplugin/fu-fuzzer-firmware.c.in", { "@FIRMWARENEW@": fzr.new_gtype, "@INCLUDE@": os.path.join("libfwupdplugin", fzr.header), }, ) bld.link([bld.compile(src)] + built_objs, f"{fzr.name}_fuzzer") bld.mkfuzztargets( os.path.join( bld.srcdir, "fwupd", "libfwupdplugin", "tests", f"{fzr.name}*.builder.xml", ) ) bld.makezip( f"{fzr.name}_fuzzer_seed_corpus.zip", f"fwupd/libfwupdplugin/tests/{fzr.globstr}", ) # plugins for fzr in [ Fuzzer("acpi-phat", pattern="acpi-phat"), Fuzzer("bcm57xx"), Fuzzer("ccgx"), Fuzzer("ccgx-dmc"), Fuzzer("cros-ec"), Fuzzer("ebitdo"), Fuzzer("elanfp"), Fuzzer("elantp"), Fuzzer("genesys-scaler", srcdir="genesys", pattern="genesys-scaler-firmware"), Fuzzer("genesys-usbhub", srcdir="genesys", pattern="genesys-usbhub-firmware"), Fuzzer("pixart", srcdir="pixart-rf", pattern="pxi-firmware"), Fuzzer("redfish-smbios", srcdir="redfish", pattern="redfish-smbios"), Fuzzer("synaptics-prometheus", pattern="synaprom-firmware"), Fuzzer("synaptics-cape"), Fuzzer("synaptics-mst"), Fuzzer("synaptics-rmi"), Fuzzer("uf2"), Fuzzer("wacom-usb", pattern="wac-firmware"), ]: fuzz_objs = [] for obj in bld.grep_meson(f"fwupd/plugins/{fzr.srcdir}"): if obj.endswith(".c"): fuzz_objs.append(bld.compile(obj)) elif obj.endswith(".rs"): fuzz_objs.append(bld.compile(bld.rustgen(obj))) src = bld.substitute( "fwupd/libfwupdplugin/fu-fuzzer-firmware.c.in", { "@FIRMWARENEW@": fzr.new_gtype, "@INCLUDE@": os.path.join("plugins", fzr.srcdir, fzr.header), }, ) fuzz_objs.append(bld.compile(src)) bld.link(fuzz_objs + built_objs, f"{fzr.name}_fuzzer") bld.mkfuzztargets( os.path.join( bld.srcdir, "fwupd", "plugins", fzr.srcdir, "tests", f"{fzr.name}*.builder.xml", ) ) bld.makezip( f"{fzr.name}_fuzzer_seed_corpus.zip", f"fwupd/plugins/{fzr.srcdir}/tests/{fzr.globstr}", ) if __name__ == "__main__": # install missing deps here rather than patching the Dockerfile in oss-fuzz try: subprocess.check_call( [ "apt-get", "install", "-y", "liblzma-dev", "libzstd-dev", "libcbor-dev", "python3", "python3-jinja2", ], stdout=open(os.devnull, "wb"), ) except FileNotFoundError: pass except subprocess.CalledProcessError as e: print(e.output) sys.exit(1) _builder = Builder() _build(_builder) sys.exit(0) fwupd-1.9.16/contrib/ci/populate-bios-setting-translations.py000077500000000000000000000022041460375044200243250ustar00rootroot00000000000000#!/usr/bin/python3 # # Helper script to generate a list of translations # Sample call: # ./contrib/ci/populate-bios-attr-translations.py ./libfwupdplugin/tests/bios-attrs/dell-xps13-9310/ # will lead to ./libfwupdplugin/tests/bios-attrs/dell-xps13-9310/strings.txt # which can be added to po/POTFILES.in # # Copyright (C) 2022 Mario Limonciello # # SPDX-License-Identifier: LGPL-2.1+ import os import sys def populate_translations(path): output = open(os.path.join(path, "strings.txt"), "w") for root, _, files in os.walk(path): for file in files: val: str = "" if not file.endswith("display_name"): continue with open(os.path.join(root, file), "r") as f: val = f.read().replace('"', "").strip() if not val: continue output.write("#TRANSLATORS: Description of BIOS setting\n") output.write(f"{val}\n\n") output.close() if __name__ == "__main__": if len(sys.argv) != 2: print("path to bios settings directory required") sys.exit(1) populate_translations(sys.argv[1]) fwupd-1.9.16/contrib/ci/qt5-thread-test/000077500000000000000000000000001460375044200177305ustar00rootroot00000000000000fwupd-1.9.16/contrib/ci/qt5-thread-test/meson.build000066400000000000000000000010431460375044200220700ustar00rootroot00000000000000project('qt5-thread-test', 'cpp', license: 'LGPL-2.1+', ) add_project_arguments('-fPIC', language: 'cpp') qt5core = dependency('Qt5Core') qt5concurrent = dependency('Qt5Concurrent') glib2 = dependency('glib-2.0') gio2 = dependency('gio-2.0') fwupd = dependency('fwupd') env = environment() env.set('G_DEBUG', 'fatal-criticals') e = executable( 'qt-thread-test', sources: [ 'qt-thread-test.cpp' ], dependencies: [ qt5core, qt5concurrent, glib2, gio2, fwupd, ], ) test('qt-thread-test', e, timeout: 60, env: env) fwupd-1.9.16/contrib/ci/qt5-thread-test/qt-thread-test.cpp000066400000000000000000000013101460375044200232750ustar00rootroot00000000000000/* * Copyright (C) 2020 Aleix Pol * * SPDX-License-Identifier: LGPL-2.1+ */ #include #include #include #include int main(int argc, char** argv) { QCoreApplication app(argc, argv); auto client = fwupd_client_new(); auto cancellable = g_cancellable_new(); g_autoptr(GError) error = nullptr; auto fw = new QFutureWatcher(&app); QObject::connect(fw, &QFutureWatcher::finished, [fw]() { QCoreApplication::exit(0); }); fw->setFuture(QtConcurrent::run([&] { return fwupd_client_get_devices(client, cancellable, &error); })); return app.exec(); } fwupd-1.9.16/contrib/ci/s390x_cross.txt000066400000000000000000000004221460375044200176330ustar00rootroot00000000000000[binaries] c = 's390x-linux-gnu-gcc' cpp = 's390x-linux-gnu-cpp' ar = 's390x-linux-gnu-ar' strip = 's390x-linux-gnu-strip' pkgconfig = 's390x-linux-gnu-pkg-config' exe_wrapper = 'qemu-s390x' [host_machine] system = 'linux' cpu_family = 's390x' cpu = 's390x' endian = 'big' fwupd-1.9.16/contrib/ci/snap.sh000077500000000000000000000000661460375044200162770ustar00rootroot00000000000000#!/bin/sh set -e mkdir -p /build cd /build snapcraft fwupd-1.9.16/contrib/ci/snapcraft-wrapper000077500000000000000000000004671460375044200203710ustar00rootroot00000000000000#!/bin/sh SNAP="/snap/snapcraft/current" SNAP_NAME="$(awk '/^name:/{print $2}' $SNAP/meta/snap.yaml)" SNAP_VERSION="$(awk '/^version:/{print $2}' $SNAP/meta/snap.yaml)" SNAP_ARCH="amd64" export SNAP export SNAP_NAME export SNAP_VERSION export SNAP_ARCH exec "$SNAP/usr/bin/python3" "$SNAP/bin/snapcraft" "$@" fwupd-1.9.16/contrib/ci/ubuntu.sh000077500000000000000000000022501460375044200166550ustar00rootroot00000000000000#!/bin/sh set -e set -x #check for and install missing dependencies ./contrib/ci/fwupd_setup_helpers.py install-dependencies --yes -o ubuntu #check we have pip ./contrib/ci/fwupd_setup_helpers.py install-pip --yes -o ubuntu #check meson is new enough ./contrib/ci/fwupd_setup_helpers.py test-meson #check markdown is new enough ./contrib/ci/fwupd_setup_helpers.py test-markdown #check jinja2 is installed ./contrib/ci/fwupd_setup_helpers.py test-jinja2 #clone test firmware if necessary . ./contrib/ci/get_test_firmware.sh #evaluate using Ubuntu's buildflags #evaluate using Debian/Ubuntu's buildflags #disable link time optimization, Ubuntu currently only sets it for GCC export DEB_BUILD_MAINT_OPTIONS="optimize=-lto" eval "$(dpkg-buildflags --export=sh)" #filter out -Bsymbolic-functions LDFLAGS=$(dpkg-buildflags --get LDFLAGS | sed "s/-Wl,-Bsymbolic-functions\s//") export LDFLAGS root=$(pwd) rm -rf ${root}/build chown -R nobody ${root} sudo -u nobody meson ${root}/build -Dman=false -Ddocs=enabled -Dgusb:tests=false --prefix=${root}/dist #build with clang sudo -u nobody ninja -C ${root}/build test -v #make docs available outside of docker ninja -C ${root}/build install -v fwupd-1.9.16/contrib/ci/void.sh000077500000000000000000000011421460375044200162730ustar00rootroot00000000000000#!/bin/sh set -e set -x #install dependencies xbps-install -Suy python3 ./contrib/ci/fwupd_setup_helpers.py install-dependencies --yes -o void #clone test firmware if necessary . ./contrib/ci/get_test_firmware.sh # regenerate the example plugin ./contrib/create-plugin.py \ --vendor Vendor \ --example Example \ --parent Usb \ --author "Author" \ --email "autogenerated@localhost" #build rm -rf build meson build \ -Dgusb:tests=false \ -Dconsolekit=disabled \ -Dsystemd=disabled \ -Doffline=disabled \ -Dcompat_cli=true \ -Dplugin_vendor_example=true \ -Delogind=enabled ninja -C build test -v fwupd-1.9.16/contrib/codespell.cfg000066400000000000000000000003441460375044200170360ustar00rootroot00000000000000[codespell] builtin = clear,informal,en-GB_to_en-US skip = *.po,*.csv,*.svg,*.p7c,subprojects,.git,pcrs,build*,.ossfuzz,*/tests/* ignore-words-list = conexant,Conexant,gir,GIR,hsi,HSI,cancelled,Cancelled,te,mitre,distroname,wel fwupd-1.9.16/contrib/create-plugin.py000077500000000000000000000050021460375044200175130ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2023 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ # # pylint: disable=invalid-name,missing-docstring,consider-using-f-string import os import datetime import argparse import glob import sys from jinja2 import Environment, FileSystemLoader, select_autoescape subst = {} def _fix_case(value: str) -> str: return value[0].upper() + value[1:].lower() def _subst_add_string(key: str, value: str) -> None: # sanity check if not value.isascii(): raise NotImplementedError(f"{key} can only be ASCII, got {value}") if len(value) < 3: raise NotImplementedError(f"{key} has to be at least length 3, got {value}") subst[key] = value subst[key.lower()] = value.lower() subst[key.upper()] = value.upper() def _subst_replace(data: str) -> str: for key, value in subst.items(): data = data.replace(key, value) return data if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( "--vendor", type=str, help="Vendor name", required=True, ) parser.add_argument( "--example", type=str, help="Plugin basename", required=True, ) parser.add_argument("--parent", type=str, default="Usb", help="Device parent GType") parser.add_argument( "--year", type=int, default=datetime.date.today().year, help="Copyright year" ) parser.add_argument("--author", type=str, help="Copyright author", required=True) parser.add_argument("--email", type=str, help="Copyright email", required=True) args = parser.parse_args() try: _subst_add_string("Vendor", _fix_case(args.vendor)) _subst_add_string("Example", _fix_case(args.example)) _subst_add_string("Parent", args.parent) _subst_add_string("Year", str(args.year)) _subst_add_string("Author", args.author) _subst_add_string("Email", args.email) except NotImplementedError as e: print(e) sys.exit(1) template_src = "vendor-example" os.makedirs(os.path.join("plugins", _subst_replace(template_src)), exist_ok=True) env = Environment( loader=FileSystemLoader("."), autoescape=select_autoescape(), keep_trailing_newline=True, ) for fn in glob.iglob(f"./plugins/{template_src}/*"): template = env.get_template(fn) with open(_subst_replace(fn.replace(".in", "")), "wb") as f_dst: f_dst.write(template.render(subst).encode()) fwupd-1.9.16/contrib/debian/000077500000000000000000000000001460375044200156245ustar00rootroot00000000000000fwupd-1.9.16/contrib/debian/README.Debian000066400000000000000000000013421460375044200176650ustar00rootroot00000000000000signed vs unsigned fwupd programs ------------------------------------ fwupd 1.1.0 is configured to understand when to use a signed version of the EFI binary. If the signed version isn't installed but secure boot is turned on, it will avoid copying to the EFI system partition. This allows supporting secure boot even if not turned on at install, or changed later after install. In Ubuntu, both fwupd-signed and fwupd are seeded in the default installation. Nothing is installed to the ESP until it's needed. In Debian, the package name for the signed version is slightly different due to different infrastructure. fwupd-signed-$ARCH and fwupd should both be installed and then things will work similarly to what's described above. fwupd-1.9.16/contrib/debian/README.source000066400000000000000000000004201460375044200177770ustar00rootroot00000000000000fwupd for Debian ---------------- To build from the git tree, run: git-buildpackage -us -uc -S Then, if using sbuild, you can use something like: sbuild -s -c sid-amd64 -d unstable -- Daniel Jared Dominguez Thu, 21 May 2015 13:44:16 -0500 fwupd-1.9.16/contrib/debian/clean000066400000000000000000000000011460375044200166200ustar00rootroot00000000000000 fwupd-1.9.16/contrib/debian/compat000066400000000000000000000000031460375044200170230ustar00rootroot0000000000000012 fwupd-1.9.16/contrib/debian/control.in000066400000000000000000000113271460375044200176400ustar00rootroot00000000000000Source: fwupd Priority: optional Maintainer: Debian EFI Uploaders: Steve McIntyre <93sam@debian.org>, Matthias Klumpp , Mario Limonciello Build-Depends: %%%DYNAMIC%%% Build-Depends-Indep: gi-docgen , Rules-Requires-Root: no Standards-Version: 4.6.0.1 Section: admin Homepage: https://github.com/fwupd/fwupd Vcs-Git: https://salsa.debian.org/efi-team/fwupd.git Vcs-Browser: https://salsa.debian.org/efi-team/fwupd Package: libfwupd2 Section: libs Architecture: linux-any Depends: ${misc:Depends}, ${shlibs:Depends} Multi-Arch: same Description: Firmware update daemon library fwupd is a daemon to allow session software to update device firmware. You can either use a GUI software manager like GNOME Software to view and apply updates, the command-line tool or the system D-Bus interface directly. Firmware updates are supported for a variety of technologies. See for details . This package provides the library used by the daemon. Package: fwupd Architecture: linux-any Depends: ${misc:Depends}, ${shlibs:Depends}, libfwupd2 (= ${binary:Version}), shared-mime-info, systemd-sysusers Recommends: python3, bolt, dbus, secureboot-db, udisks2, fwupd-signed, jq Suggests: gir1.2-fwupd-2.0 Provides: fwupdate Conflicts: fwupdate-amd64-signed, fwupdate-i386-signed, fwupdate-arm64-signed, fwupdate-armhf-signed Breaks: gir1.2-dfu-1.0 (<< 0.9.7-1), libdfu1 (<< 0.9.7-1), fwupdate (<< 12-7), libdfu-dev (<< 0.9.7-1) Replaces: gir1.2-dfu-1.0 (<< 0.9.7-1), libdfu1 (<< 0.9.7-1), libdfu-dev (<< 0.9.7-1), fwupdate (<< 12-7) Multi-Arch: foreign Description: Firmware update daemon fwupd is a daemon to allow session software to update device firmware. You can either use a GUI software manager like GNOME Software to view and apply updates, the command-line tool or the system D-Bus interface directly. Firmware updates are supported for a variety of technologies. See for details Package: fwupd-tests Architecture: linux-any Depends: ${misc:Depends}, ${shlibs:Depends}, ca-certificates, dbus-x11, fwupd, gnome-desktop-testing, polkitd | policykit-1, python3, python3-gi, python3-requests, Breaks: fwupd (<< 0.9.4-1) Replaces: fwupd (<< 0.9.4-1) Multi-Arch: foreign Description: Test suite for firmware update daemon fwupd is a daemon to allow session software to update device firmware. You can either use a GUI software manager like GNOME Software to view and apply updates, the command-line tool or the system D-Bus interface directly. Firmware updates are supported for a variety of technologies. See for details . This package provides a set of installed tests that can be run to validate the daemon in a continuous integration system. Package: fwupd-doc Section: doc Architecture: all Multi-Arch: foreign Depends: ${misc:Depends}, Build-Profiles: Description: Firmware update daemon documentation (HTML format) fwupd is a daemon to allow session software to update device firmware. You can either use a GUI software manager like GNOME Software to view and apply updates, the command-line tool or the system D-Bus interface directly. Firmware updates are supported for a variety of technologies. See for details . This package provides development documentation for creating a package that uses fwupd. Package: libfwupd-dev Architecture: linux-any Multi-Arch: same Depends: libfwupd2 (= ${binary:Version}), gir1.2-fwupd-2.0 (= ${binary:Version}), libcurl4-gnutls-dev, libglib2.0-dev (>= 2.45.8), libjcat-dev, libjson-glib-dev (>= 1.1.1), ${misc:Depends} Breaks: fwupd-dev (<< 0.5.4-2~) Replaces: fwupd-dev (<< 0.5.4-2~) Section: libdevel Description: development files for libfwupd fwupd is a daemon to allow session software to update device firmware. You can either use a GUI software manager like GNOME Software to view and apply updates, the command-line tool or the system D-Bus interface directly. Firmware updates are supported for a variety of technologies. See for details . This package provides the development files for libfwupd Package: gir1.2-fwupd-2.0 Architecture: linux-any Multi-Arch: same Depends: ${misc:Depends}, ${gir:Depends} Section: introspection Description: GObject introspection data for libfwupd This package provides the introspection data for libfwupd. . It can be used by packages using the GIRepository format to generate dynamic bindings. fwupd-1.9.16/contrib/debian/control.qubes.in000066400000000000000000000002451460375044200207530ustar00rootroot00000000000000Package: fwupd-qubes-vm-whonix Architecture: amd64 Description: Whonix support for Qubes OS This package is used to download firmware updates and metadata via TOR. fwupd-1.9.16/contrib/debian/copyright.in000066400000000000000000000201061460375044200201630ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: fwupd Source: https://github.com/fwupd/fwupd %%%DYNAMIC%%% Files: *.metainfo.xml Copyright: Richard Hughes License: CC0-1.0 Files: debian/* Copyright: 2015 Daniel Jared Dominguez 2015 Mario Limonciello License: LGPL-2.1+ License: LGPL-2.1+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU Lesser General Public License version 2.1 can be found in "/usr/share/common-licenses/LGPL-2.1". License: CC0-1.0 Creative Commons CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. . Statement of Purpose . The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. . 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. . 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. . 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. . 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. fwupd-1.9.16/contrib/debian/docs000066400000000000000000000000011460375044200164660ustar00rootroot00000000000000 fwupd-1.9.16/contrib/debian/fwupd-doc.links000066400000000000000000000001741460375044200205600ustar00rootroot00000000000000usr/share/doc/libfwupd /usr/share/gtk-doc/html/libfwupd usr/share/doc/libfwupdplugin /usr/share/gtk-doc/html/libfwupdplugin fwupd-1.9.16/contrib/debian/fwupd-qubes-vm-whonix.install000066400000000000000000000001351460375044200234070ustar00rootroot00000000000000usr/libexec/qubes-fwupd/fwupd_download_updates.py usr/libexec/qubes-fwupd/fwupd_common_vm.py fwupd-1.9.16/contrib/debian/fwupd-qubes-vm-whonix.postinst000066400000000000000000000005221460375044200236240ustar00rootroot00000000000000#!/bin/bash HOME_DIR=`getent passwd user | awk '{ split($$0,a,":"); print a[6]}'` if [ -z "$HOME_DIR" ]; then echo "Default user does not exist!!" >&2 echo "Package does not create fwupd directories" >&2 else mkdir -p $HOME_DIR/.cache/fwupd_download_updates chown -R user:user $HOME_DIR/.cache/fwupd_download_updates fi fwupd-1.9.16/contrib/debian/fwupd-qubes-vm-whonix.postrm000066400000000000000000000002531460375044200232660ustar00rootroot00000000000000#!/bin/bash HOME_DIR=`getent passwd user | awk '{ split($$0,a,":"); print a[6]}'` if [ -z "$HOME_DIR" ] && [ "$1" = "purge" ]; then rm -rf $HOME_DIR/.cache/fwupd fi fwupd-1.9.16/contrib/debian/fwupd-tests.install000066400000000000000000000005561460375044200215070ustar00rootroot00000000000000#These are in a generic looking directory because #that is where gnome-desktop-testing expects to #find them. for more information see: #https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=872458 usr/share/installed-tests/* usr/libexec/installed-tests/fwupd/*-self-test debian/lintian/fwupd-tests usr/share/lintian/overrides usr/share/fwupd/remotes.d/fwupd-tests.conf fwupd-1.9.16/contrib/debian/fwupd-tests.postinst000066400000000000000000000004021460375044200217120ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# #only enable on installation not upgrade if [ "$1" = configure ] && [ -z "$2" ]; then if [ "$CI" = "true" ]; then fwupdtool enable-test-devices else echo "To enable test suite, run `fwupdtool enable-test-devices`" fi fi fwupd-1.9.16/contrib/debian/fwupd-tests.postrm000066400000000000000000000003761460375044200213650ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# # don't run on purge; the commands might be missing if [ "$1" = remove ]; then if [ "$CI" = "true" ]; then fwupdtool disable-test-devices else echo "To disable test suite, run `fwupdtool disable-test-devices`" fi fi fwupd-1.9.16/contrib/debian/fwupd.dirs000066400000000000000000000000301460375044200176250ustar00rootroot00000000000000var/cache/app-info/xmls fwupd-1.9.16/contrib/debian/fwupd.install000066400000000000000000000010051460375044200203350ustar00rootroot00000000000000usr/bin/* etc/* usr/share/bash-completion usr/share/fish/vendor_completions.d usr/share/fwupd/*.* usr/share/fwupd/metainfo/* usr/share/fwupd/remotes.d/vendor/* usr/share/dbus-1/* usr/share/icons/* usr/share/polkit-1/* usr/share/locale usr/share/metainfo/* usr/libexec/fwupd/* usr/lib/sysusers.d/* usr/share/man/* data/fwupd.conf etc/fwupd debian/fwupd.pkla /var/lib/polkit-1/localauthority/10-vendor.d usr/lib/*/fwupd-*/*.so debian/lintian/fwupd usr/share/lintian/overrides obj*/data/motd/85-fwupd /etc/update-motd.d fwupd-1.9.16/contrib/debian/fwupd.maintscript000066400000000000000000000010371460375044200212310ustar00rootroot00000000000000 rm_conffile /etc/fwupd.conf 1.0.0~ rm_conffile /etc/fwupd/remotes.d/fwupd.conf 1.2.7~ rm_conffile /etc/dbus-1/system.d/org.freedesktop.fwupd.conf 1.3.2~ rm_conffile /etc/modules-load.d/fwupd-msr.conf 1.5.3~ rm_conffile /etc/modules-load.d/fwupd-platform-integrity.conf 1.5.3~ rm_conffile /etc/fwupd/ata.conf 1.5.5~ mv_conffile /etc/fwupd/uefi.conf /etc/fwupd/uefi_capsule.conf 1.5.5~ rm_conffile /etc/pki/fwupd/GPG-KEY-Hughski-Limited 1.9.10~ rm_conffile /etc/fwupd/upower.conf 1.9.10~ rm_conffile /etc/fwupd/remotes.d/dell-esrt.conf 1.9.11~ fwupd-1.9.16/contrib/debian/fwupd.pkla000066400000000000000000000002071460375044200176210ustar00rootroot00000000000000[Call internal fwupd actions] Identity=unix-group:admin;unix-group:sudo Action=org.freedesktop.fwupd.update-internal ResultActive=yes fwupd-1.9.16/contrib/debian/fwupd.postinst000066400000000000000000000001611460375044200205540ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# if [ -d /run/systemd/system ]; then deb-systemd-invoke reload dbus || true fi fwupd-1.9.16/contrib/debian/fwupd.postrm000066400000000000000000000002421460375044200202150ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# if [ "$1" = purge ]; then rm -rf /var/lib/fwupd /var/cache/fwupd /var/cache/fwupdmgr rm -f /var/cache/app-info/xmls/fwupd.xml fi fwupd-1.9.16/contrib/debian/fwupd.preinst000066400000000000000000000004151460375044200203570ustar00rootroot00000000000000#!/bin/sh set -e #DEBHELPER# # 1.3.2 had fwupd-refresh.service and fwupd.service both claiming # this directory, but fwupd-refresh.service used DynamicUser directive # meaning no other unit could access it. if [ -L /var/cache/fwupd ]; then rm -f /var/cache/fwupd fi fwupd-1.9.16/contrib/debian/gbp.conf000066400000000000000000000001611460375044200172410ustar00rootroot00000000000000[DEFAULT] debian-branch = debian upstream-tag = %(version)s [buildpackage] sign-tags = True dist = experimental fwupd-1.9.16/contrib/debian/gir1.2-fwupd-2.0.install000066400000000000000000000000531460375044200216340ustar00rootroot00000000000000usr/lib/*/girepository-1.0/Fwupd-*.typelib fwupd-1.9.16/contrib/debian/libfwupd-dev.install000066400000000000000000000003101460375044200215760ustar00rootroot00000000000000usr/include/fwupd-1/fwupd.h usr/include/fwupd-1/libfwupd usr/lib/*/libfwupd.so usr/lib/*/pkgconfig/fwupd.pc usr/share/gir-1.0/Fwupd-*.gir usr/share/vala/vapi/fwupd.deps usr/share/vala/vapi/fwupd.vapi fwupd-1.9.16/contrib/debian/libfwupd2.install000066400000000000000000000000301460375044200211030ustar00rootroot00000000000000usr/lib/*/libfwupd.so.* fwupd-1.9.16/contrib/debian/lintian/000077500000000000000000000000001460375044200172625ustar00rootroot00000000000000fwupd-1.9.16/contrib/debian/lintian/fwupd000066400000000000000000000001321460375044200203260ustar00rootroot00000000000000#see Debian bug 896012 fwupd: library-not-linked-against-libc usr/lib/*/fwupd-plugins-*/* fwupd-1.9.16/contrib/debian/lintian/fwupd-tests000066400000000000000000000001401460375044200214650ustar00rootroot00000000000000#see Debian bug 896012 fwupd-tests: library-not-linked-against-libc usr/lib/*/fwupd-plugins-*/* fwupd-1.9.16/contrib/debian/not-installed000066400000000000000000000002011460375044200203150ustar00rootroot00000000000000usr/libexec/qubes-fwupd/fwupd_usbvm_validate.py usr/sbin/qubes-fwupdmgr usr/share/qubes-fwupd/src/* usr/share/qubes-fwupd/test/* fwupd-1.9.16/contrib/debian/rules000077500000000000000000000053571460375044200167160ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- export LC_ALL := C.UTF-8 export DEB_BUILD_MAINT_OPTIONS = hardening=+all export DEB_LDFLAGS_MAINT_STRIP=-Wl,-Bsymbolic-functions #GPGME needs this for proper building on 32 bit archs ifeq ($(DEB_HOST_ARCH_BITS),32) export DEB_CFLAGS_MAINT_APPEND = -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE endif CONFARGS = ifneq ($(CI),) CONFARGS += --werror endif ifneq ($(DEB_HOST_ARCH_CPU),ia64) CONFARGS += -Dplugin_flashrom=enabled else CONFARGS += -Dplugin_flashrom=disabled endif ifneq ($(filter $(DEB_HOST_ARCH_CPU),i386 amd64),) CONFARGS += -Dplugin_msr=enabled else CONFARGS += -Dplugin_msr=disabled endif ifneq ($(QUBES_OPTION),) CONFARGS += -Dqubes=true endif ifneq ($(filter nodoc,$(DEB_BUILD_PROFILES)),) CONFARGS += -Ddocs=disabled endif CONFARGS += -Dplugin_powerd=disabled -Dsupported_build=enabled -Dplugin_modem_manager=enabled -Defi_binary=false %: dh $@ --with gir override_dh_auto_clean: rm -fr obj-* rm -fr debian/build override_dh_auto_configure: dh_auto_configure -- $(CONFARGS) override_dh_install: find debian/tmp/usr -type f -name "*a" -print | xargs rm -f sed -i 's,wheel,sudo,' debian/tmp/usr/share/polkit-1/rules.d/org.freedesktop.fwupd.rules dh_install dh_install -pfwupd $$(pkg-config --variable=systemdsystemunitdir systemd | sed s,^/,,) dh_install -pfwupd $$(pkg-config --variable=udevdir udev | sed s,^/,,)/rules.d #install MSR conf if needed (depending on distro) [ ! -d debian/tmp/usr/lib/modules-load.d ] || dh_install -pfwupd usr/lib/modules-load.d [ ! -d debian/tmp/lib/modules-load.d ] || dh_install -pfwupd lib/modules-load.d [ ! -d debian/tmp/usr/share/fwupd/quirks.d ] || dh_install -pfwupd usr/share/fwupd/quirks.d #install docs (maybe) [ ! -d debian/tmp/usr/share/doc ] || dh_install -pfwupd-doc usr/share/doc #install devices-tests (maybe) [ ! -d debian/tmp/usr/share/fwupd/device-tests/ ] || dh_install -pfwupd-tests usr/share/fwupd/device-tests #/usr merge or not (make backporting easier) [ ! -d debian/tmp/lib/systemd ] || dh_install -pfwupd lib/systemd [ ! -d debian/tmp/usr/lib/systemd ] || dh_install -pfwupd usr/lib/systemd dh_missing -a --fail-missing #this is placed in fwupd-tests rm -f debian/fwupd/usr/share/fwupd/remotes.d/fwupd-tests.conf # avoid shipping an empty directory [ ! -d debian/fwupd/lib/systemd ] || find debian/fwupd/lib/systemd -type d -empty -delete [ ! -d debian/fwupd/usr/lib/systemd ] || find debian/fwupd/usr/lib/systemd -type d -empty -delete # the below step is automatic with debhelper >= 14 dh_installsysusers override_dh_strip_nondeterminism: dh_strip_nondeterminism -Xfirmware-example.xml.gz ifneq (yes,$(shell command -v valgrind >/dev/null 2>&1 && echo yes)) override_dh_auto_test: : endif override_dh_builddeb: dh_builddeb fwupd-1.9.16/contrib/debian/source/000077500000000000000000000000001460375044200171245ustar00rootroot00000000000000fwupd-1.9.16/contrib/debian/source/format000066400000000000000000000000141460375044200203320ustar00rootroot000000000000003.0 (quilt) fwupd-1.9.16/contrib/debian/source/lintian-overrides000066400000000000000000000001231460375044200225010ustar00rootroot00000000000000#github doesn't have these fwupd source: debian-watch-does-not-check-gpg-signature fwupd-1.9.16/contrib/debian/source/options000066400000000000000000000000641460375044200205420ustar00rootroot00000000000000extend-diff-ignore=".vscode|venv|subprojects|build" fwupd-1.9.16/contrib/debian/tests/000077500000000000000000000000001460375044200167665ustar00rootroot00000000000000fwupd-1.9.16/contrib/debian/tests/ci000077500000000000000000000005601460375044200173100ustar00rootroot00000000000000#!/bin/sh -e exec 2>&1 # try loading the mtdram module to run our mtd tests modprobe mtdram 2>&1 || true fwupdtool enable-test-devices fwupdtool modify-config VerboseDomains "*" sed "s,ConditionVirtualization=.*,," \ /lib/systemd/system/fwupd.service > \ /etc/systemd/system/fwupd.service systemctl daemon-reload # run the tests gnome-desktop-testing-runner fwupd fwupd-1.9.16/contrib/debian/tests/control000066400000000000000000000002231460375044200203660ustar00rootroot00000000000000Tests: ci Restrictions: needs-root Tests: libfwupd-dev Depends: build-essential, libfwupd-dev, pkg-config Restrictions: allow-stderr, superficial fwupd-1.9.16/contrib/debian/tests/libfwupd-dev000077500000000000000000000013551460375044200213100ustar00rootroot00000000000000#!/bin/sh # Copyright 2020 Collabora Ltd. # Copyright 2021 Simon McVittie # SPDX-License-Identifier: LGPL-2.1-or-later set -eux WORKDIR="$(mktemp -d)" trap 'cd /; rm -fr "$WORKDIR"' 0 INT QUIT ABRT PIPE TERM if [ -n "${DEB_HOST_GNU_TYPE:-}" ]; then CROSS_COMPILE="$DEB_HOST_GNU_TYPE-" else CROSS_COMPILE= fi CC="${CROSS_COMPILE}gcc" PKG_CONFIG="${CROSS_COMPILE}pkg-config" cd "$WORKDIR" cat > trivial.c <<'EOF' #undef NDEBUG #include #include int main (void) { assert (fwupd_error_to_string (FWUPD_ERROR_NOTHING_TO_DO) != NULL); return 0; } EOF # Deliberately word-splitting pkg-config's output: # shellcheck disable=SC2046 "${CC}" -otrivial trivial.c $("${PKG_CONFIG}" --cflags --libs fwupd) ./trivial fwupd-1.9.16/contrib/debian/watch000066400000000000000000000003531460375044200166560ustar00rootroot00000000000000# You can run the "uscan" command to check for upstream updates and more. # See uscan(1) for format version=3 opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/fwupd-$1\.tar\.gz/ \ https://github.com/fwupd/fwupd/tags .*/v?(\d\S*)\.tar\.gz fwupd-1.9.16/contrib/firmware_packager/000077500000000000000000000000001460375044200200535ustar00rootroot00000000000000fwupd-1.9.16/contrib/firmware_packager/README.md000066400000000000000000000113701460375044200213340ustar00rootroot00000000000000# Firmware Packager This script is intended to make firmware updating easier until OEMs upload their firmware packages to the LVFS. It works by extracting the firmware binary contained in a Microsoft .exe file (intended for performing the firmware update from a Windows system) and repackaging it in a cab file usable by fwupd. The cab file can then be install using `fwupdmgr install` ## Prerequisites To run this script you will need 1. Python3.5, a standard install should include all packages you need 2. 7z (for extracting .exe files) 3. gcab (for creating the cab file) ## Usage To create a firmware package, you must supply, at a minimum: 1. A string ID to name the firmware (`--firmware-id`). You are free to choose this, but [fwupd.org](http://fwupd.org/vendors.html) recommends using "a reverse-DNS prefix similar to java" and to "always use a .firmware suffix" (e.g. net.queuecumber.DellTBT.firmware) 2. A short name for the firmware package, again you are free to choose this (`--firmware-name`). 3. The unique ID of the device that the firmware is intended for (`--device-unique-id`). This *must* match the unique ID from `fwupdmgr get-devices` 4. The firmware version (`--release-version`), try to match the manufacturers versioning scheme 5. The path to the executable file to repackage (`--exe`) 6. The path *relative to the root of the exe archive* of the .bin file to package (`--bin`). Use 7z or archive-manager to inspect the .exe file and find this path. For example, if I want to package `dell-thunderbolt-firmware.exe` and I open the .exe with archive-manager and find that `Intel/tbt.bin` is the path to the bin file inside the archive, I would pass `--exe dell-thunderbolt-firmware.exe --bin Intel/tbt.bin` 7. The path to the cab file to output (`--out`). ## Documentation `--firmware-name` Short name of the firmware package can be customized (e.g. DellTBT) **REQUIRED** `--firmware-summary` One line description of the firmware package (e.g. Dell thunderbolt firmware) `--firmware-description` Longer description of the firmware package. Theoretically this can include HTML but I haven't tried it `--device-guid` GUID ID of the device this firmware will run on, this *must* match the output from `fwupdmgr get-devices` (e.g. 72533768-6a6c-5c06-994a-367374336810) **REQUIRED** `--firmware-homepage` Website for the firmware provider (e.g. ) `-contact-info` Email address of the firmware developer (e.g. someone@something.net) `--developer-name` Name of the firmware developer (e.g. Dell) **REQUIRED** `--release-version` Version number of the firmware package (e.g. 4.21.01.002) **REQUIRED** `--release-description` Description of the firmware release, again this can theoretically include HTML but I didn't try it. `--exe` Executable file to extract firmware from (e.g. `dell-thunderbolt-firmware.exe`) **REQUIRED** `--bin` Path to the .bin file inside the executable to use as the firmware image', relative to the root of the archive (e.g. `Intel/tbt.bin`) **REQUIRED** `--out` Output cab file path (e.g. `updates/firmware.cab`) **REQUIRED** ## Example Let's say we downloaded `Intel_TBT3_FW_UPDATE_NVM21_318RY_A01_4.21.01.002.exe` (available [here](https://downloads.dell.com/FOLDER04421073M/1/Intel_TBT3_FW_UPDATE_NVM21_318RY_A01_4.21.01.002.exe)) containing updated firmware for Dell laptops thunderbolt controllers. Since Dell hasn't made this available on the LVFS yet, we want to package and install it ourselves. Opening the .exe with archive manager, we see it has a single folder: `Intel` and inside that, a set of firmware binaries (along with some microsoft junk). We pick the file `0x07BE_secure.bin` since we have a Dell XPS 9560 and that is its device string. Next we use `fwupdmgr` to get the device ID for the thunderbolt controller: ```shell $ fwupdmgr get-devices Thunderbolt Controller Guid: 72533768-6a6c-5c06-994a-367374336810 DeviceID: 08001575 Plugin: thunderbolt Flags: internal|allow-online DeviceVendor: Intel Version: 21.00 Created: 2017-08-16 ``` The GUID field contains what we are looking for We can then run the firmware-packager with the following arguments: ```shell $ firmware-packager --firmware-id net.queuecumber.DellTBT.firmware --firmware-name DellTBT --device-unique-id 72533768-6a6c-5c06-994a-367374336810 --release-version 4.21.01.002 --exe ~/Downloads/Intel_TBT3_FW_UPDATE_NVM21_318RY_A01_4.21.01.002.exe --bin Intel/0x07BE_secure.bin --out firmware.cab Using temp directory /tmp/tmpoey6_zx_ Extracting firmware exe Locating firmware bin Creating metainfo Cabbing firmware files Done ``` And we should have a firmware.cab that contains the packaged firmware. We can then install this firmware with `fwupdmgr install firmware.cab`. fwupd-1.9.16/contrib/firmware_packager/__init__.py000066400000000000000000000000001460375044200221520ustar00rootroot00000000000000fwupd-1.9.16/contrib/firmware_packager/add_capsule_header.py000077500000000000000000000047161460375044200242140ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2019 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import sys import uuid import argparse import ctypes CAPSULE_FLAGS_PERSIST_ACROSS_RESET = 0x00010000 CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE = 0x00020000 CAPSULE_FLAGS_INITIATE_RESET = 0x00040000 def add_header(infile, outfile, gd, fl=None): # parse GUID from command line try: guid = uuid.UUID(gd) except ValueError as e: print(e) return 1 import struct try: with open(infile, "rb") as f: bin_data = f.read() except FileNotFoundError as e: print(e) return 1 # check if already has header hdrsz = struct.calcsize("<16sIII") if len(bin_data) >= hdrsz: hdr = struct.unpack("<16sIII", bin_data[:hdrsz]) imgsz = hdr[3] if imgsz == len(bin_data): print("Replacing existing CAPSULE_HEADER of:") guid_mixed = uuid.UUID(bytes_le=hdr[0]) hdrsz_old = hdr[1] flags = hdr[2] print(f"GUID: {guid_mixed}") print(f"HdrSz: 0x{hdrsz_old:04x}") print(f"Flags: 0x{flags:04x}") print(f"PayloadSz: 0x{imgsz:04x}") bin_data = bin_data[hdrsz_old:] # set header flags flags = ( CAPSULE_FLAGS_PERSIST_ACROSS_RESET | CAPSULE_FLAGS_INITIATE_RESET | CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE ) if fl: flags = int(fl, 16) # build update capsule header hdrsz = 4096 imgsz = hdrsz + len(bin_data) hdr = ctypes.create_string_buffer(hdrsz) struct.pack_into("<16sIII", hdr, 0, guid.bytes_le, hdrsz, flags, imgsz) with open(outfile, "wb") as f: f.write(hdr) f.write(bin_data) print(f"Wrote capsule {outfile}") print(f"GUID: {guid}") print(f"HdrSz: 0x{hdrsz:04x}") print(f"Flags: 0x{flags:04x}") print(f"PayloadSz: 0x{imgsz:04x}") return 0 if __name__ == "__main__": parser = argparse.ArgumentParser(description="Add capsule header on firmware") parser.add_argument("--guid", help="GUID of the device", required=True) parser.add_argument("--bin", help="Path to the .bin file", required=True) parser.add_argument("--cap", help="Output capsule file path", required=True) parser.add_argument("--flags", help="Flags, e.g. 0x40000", default=None) args = parser.parse_args() sys.exit(add_header(args.bin, args.cap, args.guid, args.flags)) fwupd-1.9.16/contrib/firmware_packager/add_dfu_header.py000077500000000000000000000030501460375044200233240ustar00rootroot00000000000000#!/usr/bin/python3 # -*- coding: utf-8 -*- # # Copyright (C) 2020 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import struct import zlib import argparse def main(bin_fn, dfu_fn, pad, vid, pid, rev): # read binary file with open(bin_fn, "rb") as f: blob = f.read() # pad blob to a specific size if pad: while len(blob) < int(pad, 16): blob += b"\0" # create DFU footer with checksum blob += struct.pack( " org.{developer_name}.guid{firmware_id} {firmware_name} {firmware_summary} {firmware_description} {device_guid} {firmware_homepage} CC0-1.0 proprietary {contact_info} {developer_name} {release_description} {version_format} {update_protocol} """ def make_firmware_metainfo(firmware_info, dst): local_info = vars(firmware_info) local_info["firmware_id"] = local_info["device_guid"][0:8] firmware_metainfo = firmware_metainfo_template.format( **local_info, timestamp=time.time() ) with open(os.path.join(dst, "firmware.metainfo.xml"), "w") as f: f.write(firmware_metainfo) def extract_exe(exe, dst): command = ["7z", "x", f"-o{dst}", exe] subprocess.check_call(command, stdout=subprocess.DEVNULL) def get_firmware_bin(root, bin_path, dst): with cd(root): shutil.copy(bin_path, os.path.join(dst, "firmware.bin")) def create_firmware_cab(exe, folder): with cd(folder): if os.name == "nt": directive = os.path.join(folder, "directive") with open(directive, "w") as wfd: wfd.write(".OPTION EXPLICIT\r\n") wfd.write(".Set CabinetNameTemplate=firmware.cab\r\n") wfd.write(".Set DiskDirectory1=.\r\n") wfd.write("firmware.bin\r\n") wfd.write("firmware.metainfo.xml\r\n") command = ["makecab.exe", "/f", directive] else: command = [ "gcab", "--create", "firmware.cab", "firmware.bin", "firmware.metainfo.xml", ] subprocess.check_call(command) def main(args): with tempfile.TemporaryDirectory() as d: print(f"Using temp directory {d}") if args.exe: print("Extracting firmware exe") extract_exe(args.exe, d) print("Locating firmware bin") get_firmware_bin(d, args.bin, d) print("Creating metainfo") make_firmware_metainfo(args, d) print("Creating cabinet file") create_firmware_cab(args, d) print("Done") shutil.copy(os.path.join(d, "firmware.cab"), args.out) if __name__ == "__main__": parser = argparse.ArgumentParser( description="Create fwupd packaged from windows executables" ) parser.add_argument( "--firmware-name", help="Name of the firmware package can be customized (e.g. DellTBT)", required=True, ) parser.add_argument( "--firmware-summary", help="One line description of the firmware package" ) parser.add_argument( "--firmware-description", help="Longer description of the firmware package" ) parser.add_argument( "--device-guid", help="GUID of the device this firmware will run on, this *must* match the output of one of the GUIDs in `fwupdmgr get-devices`", required=True, ) parser.add_argument("--firmware-homepage", help="Website for the firmware provider") parser.add_argument( "--contact-info", help="Email address of the firmware developer" ) parser.add_argument( "--developer-name", help="Name of the firmware developer", required=True ) parser.add_argument( "--release-version", help="Version number of the firmware package", required=True, ) parser.add_argument( "--version-format", help="Version format, e.g. quad or triplet", required=True, ) parser.add_argument( "--update-protocol", help="Update protocol, e.g. org.uefi.capsule", required=True, ) parser.add_argument( "--release-description", help="Description of the firmware release" ) parser.add_argument( "--exe", help="(optional) Executable file to extract firmware from" ) parser.add_argument( "--bin", help="Path to the .bin file (Relative if inside the executable; Absolute if outside) to use as the firmware image", required=True, ) parser.add_argument("--out", help="Output cab file path", required=True) args = parser.parse_args() main(args) fwupd-1.9.16/contrib/firmware_packager/install_dell_bios_exe.py000077500000000000000000000077321460375044200247640ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2019 Mario Limonciello # # SPDX-License-Identifier: LGPL-2.1+ import dbus import os.path import sys import tempfile import gi try: gi.require_version("Fwupd", "2.0") except ValueError: print("Missing gobject-introspection packages. Try to install gir1.2-fwupd-2.0.") sys.exit(1) from gi.repository import Fwupd # pylint: disable=wrong-import-position from simple_client import get_daemon_property, install, check_exists, modify_config from add_capsule_header import add_header from firmware_packager import make_firmware_metainfo, create_firmware_cab class Variables: def __init__(self, device_guid, version): self.device_guid = device_guid self.developer_name = "Dell Inc" self.firmware_name = "New firmware" self.firmware_summary = "Unknown" self.firmware_description = "Unknown" self.firmware_homepage = "https://support.dell.com" self.contact_info = "Unknown" self.release_version = version self.release_description = "Unknown" self.update_protocol = "org.uefi.capsule" self.version_format = "dell-bios" def parse_args(): """Parse arguments for this client""" import argparse parser = argparse.ArgumentParser(description="Interact with fwupd daemon") parser.add_argument("exe", nargs="?", help="exe file") parser.add_argument("deviceid", nargs="?", help="DeviceID to operate on(optional)") args = parser.parse_args() return args def generate_cab(infile, directory, guid, version): output = os.path.join(directory, "firmware.bin") ret = add_header(infile, output, guid) if ret: sys.exit(ret) variables = Variables(guid, version) make_firmware_metainfo(variables, directory) create_firmware_cab(variables, directory) cab = os.path.join(directory, "firmware.cab") print(f"Generated CAB file {cab}") return cab def find_uefi_device(client, deviceid): devices = client.get_devices() for item in devices: # match the device we were given if deviceid: if item.get_id() != deviceid: continue # internal if not item.has_flag(1 << 0): continue # needs reboot if not item.has_flag(1 << 8): continue # return the first hit for UEFI plugin if item.get_plugin() == "uefi" or item.get_plugin() == "uefi_capsule": print(f"Installing to {item.get_name()}") return item.get_guid_default(), item.get_id(), item.get_version() print("Couldn't find any UEFI devices") sys.exit(1) def set_conf_only_trusted(client, setval): prop = "OnlyTrusted" current_val = get_daemon_property(prop) if current_val: pass elif setval: pass else: return False modify_config(client, prop, str(setval).lower()) return get_daemon_property(prop) == setval def prompt_reboot(): print("An update requires a reboot to complete") while True: res = input("Restart now? (Y/N) ") if res.lower() == "n": print("Reboot your machine manually to finish the update.") break if res.lower() != "y": continue # reboot using logind obj = dbus.SystemBus().get_object( "org.freedesktop.login1", "/org/freedesktop/login1" ) obj.Reboot(True, dbus_interface="org.freedesktop.login1.Manager") if __name__ == "__main__": ARGS = parse_args() CLIENT = Fwupd.Client() check_exists(ARGS.exe) try: is_restore_required = set_conf_only_trusted(CLIENT, False) directory = tempfile.mkdtemp() guid, deviceid, version = find_uefi_device(CLIENT, ARGS.deviceid) cab = generate_cab(ARGS.exe, directory, guid, version) install(CLIENT, cab, deviceid, True, True) except Exception as e: print(e) if is_restore_required: set_conf_only_trusted(CLIENT, True) prompt_reboot() fwupd-1.9.16/contrib/firmware_packager/meson.build000066400000000000000000000010161460375044200222130ustar00rootroot00000000000000if get_option('firmware-packager') install_data('firmware_packager.py', install_dir: 'share/fwupd') install_data('add_capsule_header.py', install_dir: 'share/fwupd') install_data('install_dell_bios_exe.py', install_dir: 'share/fwupd') con2 = configuration_data() con2.set('FWUPD_VERSION', fwupd_version) configure_file( input: 'simple_client.py', output: 'simple_client.py', configuration: con2, install: true, install_dir: 'share/fwupd', ) endif fwupd-1.9.16/contrib/firmware_packager/simple_client.py000077500000000000000000000144701460375044200232650ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ """A simple fwupd frontend""" import sys import os import dbus import gi from gi.repository import GLib gi.require_version("Fwupd", "2.0") from gi.repository import Fwupd # pylint: disable=wrong-import-position class Progress: """Class to track the signal changes of progress events""" def __init__(self): self.device = None self.status = None self.percent = 0 self.erase = 0 def device_changed(self, new_device): """Indicate new device string to track""" if self.device != new_device: self.device = new_device print(f"\nUpdating {self.device}") def status_changed(self, percent, status): """Indicate new status string or % complete to track""" if self.status != status or self.percent != percent: for i in range(0, self.erase): sys.stdout.write("\b \b") self.status = status self.percent = percent status_str = "[" for i in range(0, 50): if i < percent / 2: status_str += "*" else: status_str += " " status_str += "] %d%% %s" % (percent, status) self.erase = len(status_str) sys.stdout.write(status_str) sys.stdout.flush() if "idle" in status: sys.stdout.write("\n") def parse_args(): """Parse arguments for this client""" import argparse parser = argparse.ArgumentParser(description="Interact with fwupd daemon") parser.add_argument( "--allow-older", action="store_true", help="Install older payloads(default False)", ) parser.add_argument( "--allow-reinstall", action="store_true", help="Reinstall payloads(default False)", ) parser.add_argument( "command", choices=[ "get-devices", "get-details", "install", "refresh", "get-bios-setting", ], help="What to do", ) parser.add_argument("cab", nargs="?", help="CAB file") parser.add_argument("deviceid", nargs="?", help="DeviceID to operate on(optional)") parser.add_argument("--setting", help="BIOS setting to operate on(optional)") args = parser.parse_args() return args def refresh(client): """Uses fwupd client to refresh metadata""" remotes = client.get_remotes() client.set_user_agent_for_package("simple_client", "@FWUPD_VERSION@") for remote in remotes: if not remote.get_enabled(): continue if remote.get_kind() != Fwupd.RemoteKind.DOWNLOAD: continue client.refresh_remote(remote) def get_devices(client): """Use fwupd client to fetch devices""" devices = client.get_devices() for item in devices: print(item.to_string()) def get_details(client, cab): """Use fwupd client to fetch details for a CAB file""" devices = client.get_details(cab, None) for device in devices: print(device.to_string()) def get_bios_settings(client, setting): """Use fwupd client to get BIOS settings""" settings = client.get_bios_settings() for i in settings: if not setting or setting == i.get_name() or setting == i.get_id(): print(i.to_string()) def status_changed(client, spec, progress): # pylint: disable=unused-argument """Signal emitted by fwupd daemon indicating status changed""" progress.status_changed( client.get_percentage(), Fwupd.status_to_string(client.get_status()) ) def device_changed(client, device, progress): # pylint: disable=unused-argument """Signal emitted by fwupd daemon indicating active device changed""" progress.device_changed(device.get_name()) def modify_config(client, key, value): """Use fwupd client to modify daemon configuration value""" try: print(f"setting configuration key {key} to {value}") client.modify_config(key, value, None) except Exception as e: print(f"{str(e)}") sys.exit(1) def install(client, cab, target, older, reinstall): """Use fwupd client to install CAB file to applicable devices""" # FWUPD_DEVICE_ID_ANY if not target: target = "*" flags = Fwupd.InstallFlags.NONE if older: flags |= Fwupd.InstallFlags.ALLOW_OLDER if reinstall: flags |= Fwupd.InstallFlags.ALLOW_REINSTALL progress = Progress() parent = super(client.__class__, client) parent.connect("device-changed", device_changed, progress) parent.connect("notify::percentage", status_changed, progress) parent.connect("notify::status", status_changed, progress) try: client.install(target, cab, flags, None) except GLib.Error as glib_err: # pylint: disable=catching-non-exception progress.status_changed(0, "idle") print(f"{glib_err}") sys.exit(1) print("\n") def get_daemon_property(key: str): try: bus = dbus.SystemBus() proxy = bus.get_object(bus_name="org.freedesktop.fwupd", object_path="/") iface = dbus.Interface(proxy, "org.freedesktop.DBus.Properties") val = iface.Get("org.freedesktop.fwupd", key) if isinstance(val, dbus.Boolean): print(f"org.freedesktop.fwupd property {key}, current value is {bool(val)}") else: print(f"org.freedesktop.fwupd property {key}, current value is {val}") return val except dbus.DBusException as e: print(e) return None def check_exists(cab): """Check that CAB file exists""" if not cab: print("Need to specify payload") sys.exit(1) if not os.path.isfile(cab): print(f"{cab} doesn't exist or isn't a file") sys.exit(1) if __name__ == "__main__": ARGS = parse_args() CLIENT = Fwupd.Client() if ARGS.command == "get-devices": get_devices(CLIENT) elif ARGS.command == "get-details": check_exists(ARGS.cab) get_details(CLIENT, ARGS.cab) elif ARGS.command == "refresh": refresh(CLIENT) elif ARGS.command == "install": check_exists(ARGS.cab) install(CLIENT, ARGS.cab, ARGS.deviceid, ARGS.allow_older, ARGS.allow_reinstall) elif ARGS.command == "get-bios-setting": get_bios_settings(CLIENT, ARGS.setting) fwupd-1.9.16/contrib/flatpak/000077500000000000000000000000001460375044200160245ustar00rootroot00000000000000fwupd-1.9.16/contrib/freebsd/000077500000000000000000000000001460375044200160145ustar00rootroot00000000000000fwupd-1.9.16/contrib/freebsd/Makefile000066400000000000000000000025261460375044200174610ustar00rootroot00000000000000# Created by: Norbert Kamiński # $FreeBSD$ PORTNAME= fwupd DISTVERSION= GH_TAGNAME= CATEGORIES= sysutils MAINTAINER= norbert.kaminski@3mdeb.com COMMENT= Update firmware automatically, safely, and reliably LICENSE= LGPL21 BUILD_DEPENDS= gi-docgen:textproc/gi-docgen \ help2man:misc/help2man \ vala:lang/vala \ ${LOCALBASE}/libexec/fwupd/efi/fwupdx64.efi:sysutils/fwupd-efi \ ${PYTHON_PKGNAMEPREFIX}gobject3>0:devel/py-gobject3@${PY_FLAVOR} LIB_DEPENDS= libcurl.so:ftp/curl \ libefiboot.so:devel/libefiboot \ libgnutls.so:security/gnutls \ libgpg-error.so:security/libgpg-error \ libgpgme.so:security/gpgme \ libgusb.so:devel/libgusb \ libjcat.so:textproc/libjcat \ libjson-glib-1.0.so:devel/json-glib \ libprotobuf-c.so:devel/protobuf-c \ libcbor.so:devel/libcbor \ libxmlb.so:textproc/libxmlb \ libefiboot.so:devel/gnu-efi RUN_DEPENDS= ${LOCALBASE}/libexec/fwupd/efi/fwupdx64.efi:sysutils/fwupd-efi USES= gnome libarchive meson pkgconfig python:3.8+ shebangfix sqlite USE_GITHUB= yes USE_GNOME= glib20 introspection:build GH_ACCOUNT= INSTALLS_ICONS= yes USE_LDCONFIG= yes SHEBANG_GLOB= *.py MESON_ARGS= -Dgudev=disabled \ -Dplugin_intel_me=false \ -Dpolkit=disabled \ -Dsystemd=disabled \ -Doffline=false \ -Dtests=false \ -Ddocs=enabled \ -Defi_binary=false .include fwupd-1.9.16/contrib/freebsd/pkg-descr000066400000000000000000000006321460375044200176170ustar00rootroot00000000000000Make firmware updates automatic, safe, and reliable. fwupd is a system daemon to allow session software to update device firmware on your local machine. It is designed for desktops, but also usable on phones and headless servers. You can either use a GUI software manager like GNOME Software to view and apply updates, the command-line tool, or the system D-Bus interface directly. WWW: https://fwupd.org/ fwupd-1.9.16/contrib/fwupd.spec.in000066400000000000000000000311441460375044200170130ustar00rootroot00000000000000%global glib2_version 2.45.8 %global libxmlb_version 0.1.3 %global libgusb_version 0.3.5 %global libcurl_version 7.61.0 %global libjcat_version 0.1.0 %global systemd_version 231 %global json_glib_version 1.1.1 # although we ship a few tiny python files these are utilities that 99.99% # of users do not need -- use this to avoid dragging python onto CoreOS %global __requires_exclude ^%{python3}$ %define alphatag #ALPHATAG# %global enable_ci 0 %global enable_tests 1 %global __meson_wrap_mode nodownload # fwupd.efi is only available on these arches %ifarch x86_64 aarch64 %global have_uefi 1 %endif # gpio.h is only available on these arches %ifarch x86_64 aarch64 %global have_gpio 1 %endif # flashrom is only available on these arches %ifarch i686 x86_64 armv7hl aarch64 ppc64le %global have_flashrom 1 %endif %ifarch i686 x86_64 %global have_msr 1 %endif # Until we actually have seen it outside x86 %ifarch i686 x86_64 %global have_thunderbolt 1 %endif # only available recently %if 0%{?fedora} >= 30 %global have_modem_manager 1 %endif Summary: Firmware update daemon Name: fwupd Version: #VERSION# Release: 0.#BUILD#%{?alphatag}%{?dist} License: LGPL-2.1-or-later URL: https://github.com/fwupd/fwupd Source0: http://people.freedesktop.org/~hughsient/releases/%{name}-%{version}.tar.xz BuildRequires: gettext BuildRequires: glib2-devel >= %{glib2_version} BuildRequires: libxmlb-devel >= %{libxmlb_version} BuildRequires: libgudev1-devel BuildRequires: libgusb-devel >= %{libgusb_version} BuildRequires: libcurl-devel >= %{libcurl_version} BuildRequires: libjcat-devel >= %{libjcat_version} BuildRequires: polkit-devel >= 0.103 BuildRequires: protobuf-c-devel BuildRequires: python3-packaging BuildRequires: python3-jinja2 BuildRequires: sqlite-devel BuildRequires: systemd >= %{systemd_version} BuildRequires: systemd-devel BuildRequires: libarchive-devel BuildRequires: libcbor-devel %if 0%{?rhel} >= 10 || 0%{?fedora} >= 28 BuildRequires: passim-devel %endif BuildRequires: gobject-introspection-devel %ifarch %{valgrind_arches} BuildRequires: valgrind BuildRequires: valgrind-devel %endif BuildRequires: gi-docgen BuildRequires: gnutls-devel BuildRequires: gnutls-utils BuildRequires: meson BuildRequires: json-glib-devel >= %{json_glib_version} BuildRequires: vala %if 0%{?rhel} >= 10 || 0%{?fedora} >= 41 BuildRequires: bash-completion-devel %else BuildRequires: bash-completion %endif BuildRequires: git-core %if 0%{?have_flashrom} BuildRequires: flashrom-devel >= 1.2-2 %endif BuildRequires: libdrm-devel %if 0%{?have_modem_manager} BuildRequires: ModemManager-glib-devel >= 1.10.0 BuildRequires: libqmi-devel >= 1.22.0 BuildRequires: libmbim-devel %endif %if 0%{?have_uefi} BuildRequires: python3 python3-cairo python3-gobject BuildRequires: pango-devel BuildRequires: cairo-devel cairo-gobject-devel BuildRequires: freetype BuildRequires: fontconfig BuildRequires: google-noto-sans-cjk-ttc-fonts BuildRequires: tpm2-tss-devel >= 2.2.3 %endif Requires(post): systemd Requires(preun): systemd Requires(postun): systemd Requires: glib2%{?_isa} >= %{glib2_version} Requires: libxmlb%{?_isa} >= %{libxmlb_version} Requires: libgusb%{?_isa} >= %{libgusb_version} Requires: shared-mime-info %if 0%{?rhel} > 7 || 0%{?fedora} > 28 Recommends: python3 %endif Obsoletes: dbxtool < 9 Provides: dbxtool %if 0%{?rhel} > 7 Obsoletes: fwupdate < 11-4 Obsoletes: fwupdate-efi < 11-4 Provides: fwupdate Provides: fwupdate-efi %endif # optional, but a really good idea Recommends: udisks2 Recommends: bluez Recommends: jq %if 0%{?rhel} >= 10 || 0%{?fedora} >= 28 Recommends: passim %endif %if 0%{?have_modem_manager} Recommends: %{name}-plugin-modem-manager %endif %if 0%{?have_flashrom} Recommends: %{name}-plugin-flashrom %endif %if 0%{?have_uefi} Recommends: %{name}-efi Recommends: %{name}-plugin-uefi-capsule-data %endif %description fwupd is a daemon to allow session software to update device firmware. %package devel Summary: Development package for %{name} Requires: %{name}%{?_isa} = %{version}-%{release} Obsoletes: libebitdo-devel < 0.7.5-3 Obsoletes: libdfu-devel < 1.0.0 %description devel Files for development with %{name}. %package tests Summary: Data files for installed tests Requires: %{name}%{?_isa} = %{version}-%{release} %description tests Data files for installed tests. %if 0%{?have_modem_manager} %package plugin-modem-manager Summary: fwupd plugin using ModemManger Requires: %{name}%{?_isa} = %{version}-%{release} %description plugin-modem-manager This provides the optional package which is only required on hardware that might have mobile broadband hardware. It is probably not required on servers. %endif %if 0%{?have_flashrom} %package plugin-flashrom Summary: fwupd plugin using flashrom Requires: %{name}%{?_isa} = %{version}-%{release} %description plugin-flashrom This provides the optional package which is only required on hardware that can be flashed using flashrom. It is probably not required on servers. %endif %if 0%{?have_uefi} %package plugin-uefi-capsule-data Summary: Localized data for the UEFI UX capsule Requires: %{name}%{?_isa} = %{version}-%{release} %description plugin-uefi-capsule-data This provides the pregenerated BMP artwork for the UX capsule, which allows the "Installing firmware update…" localized text to be shown during a UEFI firmware update operation. This subpackage is probably not required on embedded hardware or server machines. %endif %if 0%{?qubes_packages} %package qubes-dom0 Summary: fwupd wrapper for Qubes OS - dom0 scripts Requires: gcab Requires: fwupd >= 1.5.7 Requires: libjcat >= 0.1.6 Requires: python3-packaging Requires: sequoia-sqv %description qubes-dom0 fwupd wrapper for Qubes OS %package qubes-vm Summary: fwupd wrapper for Qubes OS - VM scripts Requires: gcab Requires: fwupd >= 1.5.7 Requires: libjcat >= 0.1.6 Requires: python3-packaging %description qubes-vm fwupd wrapper for Qubes OS %endif %prep %autosetup -p1 %build %meson \ %if 0%{?enable_ci} --werror \ %endif -Ddocs=enabled \ %if 0%{?enable_tests} -Dtests=true \ %else -Dtests=false \ %endif %if 0%{?have_flashrom} -Dplugin_flashrom=enabled \ %else -Dplugin_flashrom=disabled \ %endif %if 0%{?have_msr} -Dplugin_msr=enabled \ %else -Dplugin_msr=disabled \ %endif %if 0%{?have_gpio} -Dplugin_gpio=enabled \ %else -Dplugin_gpio=disabled \ %endif %if 0%{?have_uefi} -Dplugin_uefi_capsule=enabled \ -Dplugin_uefi_pk=enabled \ -Dplugin_tpm=enabled \ -Defi_binary=false \ %else -Dplugin_uefi_capsule=disabled \ -Dplugin_uefi_pk=disabled \ -Dplugin_tpm=disabled \ %endif %if 0%{?have_modem_manager} -Dplugin_modem_manager=enabled \ %else -Dplugin_modem_manager=disabled \ %endif %if 0%{?qubes_packages} -Dqubes=true \ %endif -Dman=true \ -Dsystemd_unit_user="" \ -Dbluez=enabled \ -Dplugin_powerd=disabled \ -Dlaunchd=disabled \ -Dsupported_build=enabled %meson_build %if 0%{?enable_tests} %if 0%{?enable_ci} ./contrib/ci/get_test_firmware.sh %endif %check %meson_test %endif %install %meson_install mkdir -p --mode=0700 $RPM_BUILD_ROOT%{_localstatedir}/lib/fwupd/gnupg # workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1757948 mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/cache/fwupd %find_lang %{name} %post %systemd_post fwupd.service fwupd-refresh.timer # change vendor-installed remotes to use the default keyring type for fn in /etc/fwupd/remotes.d/*.conf; do if grep -q "Keyring=gpg" "$fn"; then sed -i 's/Keyring=gpg/#Keyring=pkcs/g' "$fn"; fi done %preun %systemd_preun fwupd.service fwupd-refresh.timer %postun %systemd_postun_with_restart fwupd.service fwupd-refresh.timer %triggerun -- fedora-release-common < 39-0.28 # For upgrades from versions before fwupd-refresh.timer was enabled by default systemctl --no-reload preset fwupd-refresh.timer &>/dev/null || : %files -f %{name}.lang %doc README.md %license COPYING %config(noreplace)%{_sysconfdir}/fwupd/fwupd.conf %dir %{_libexecdir}/fwupd %{_libexecdir}/fwupd/fwupd %ifarch i686 x86_64 %{_libexecdir}/fwupd/fwupd-detect-cet %endif %{_libexecdir}/fwupd/fwupdoffline %{_bindir}/dbxtool %{_bindir}/fwupdmgr %{_bindir}/fwupdtool %dir %{_sysconfdir}/fwupd %dir %{_sysconfdir}/fwupd/bios-settings.d %{_sysconfdir}/fwupd/bios-settings.d/README.md %dir %{_sysconfdir}/fwupd/remotes.d %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/lvfs.conf %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/lvfs-testing.conf %config(noreplace)%{_sysconfdir}/fwupd/remotes.d/vendor-directory.conf %config(noreplace)%{_sysconfdir}/pki/fwupd %{_sysconfdir}/pki/fwupd-metadata %if 0%{?have_msr} /usr/lib/modules-load.d/fwupd-msr.conf %endif %{_datadir}/dbus-1/system.d/org.freedesktop.fwupd.conf %{_datadir}/bash-completion/completions/fwupdmgr %{_datadir}/bash-completion/completions/fwupdtool %{_datadir}/fish/vendor_completions.d/fwupdmgr.fish %{_datadir}/fwupd/metainfo/org.freedesktop.fwupd*.metainfo.xml %{_datadir}/fwupd/remotes.d/vendor/firmware/README.md %{_datadir}/dbus-1/interfaces/org.freedesktop.fwupd.xml %{_datadir}/polkit-1/actions/org.freedesktop.fwupd.policy %{_datadir}/polkit-1/rules.d/org.freedesktop.fwupd.rules %{_datadir}/dbus-1/system-services/org.freedesktop.fwupd.service %{_mandir}/man1/fwupdtool.1* %{_mandir}/man1/dbxtool.* %{_mandir}/man1/fwupdmgr.1* %{_mandir}/man5/* %{_mandir}/man8/* %{_datadir}/metainfo/org.freedesktop.fwupd.metainfo.xml %{_datadir}/icons/hicolor/scalable/apps/org.freedesktop.fwupd.svg %{_datadir}/fwupd/firmware_packager.py %{_datadir}/fwupd/simple_client.py %{_datadir}/fwupd/add_capsule_header.py %{_datadir}/fwupd/install_dell_bios_exe.py %{_unitdir}/fwupd-offline-update.service %{_unitdir}/fwupd.service %{_unitdir}/fwupd-refresh.service %{_unitdir}/fwupd-refresh.timer %{_unitdir}/system-update.target.wants/ %dir %{_localstatedir}/lib/fwupd %dir %{_localstatedir}/cache/fwupd %dir %{_datadir}/fwupd/quirks.d %{_datadir}/fwupd/quirks.d/builtin.quirk.gz %{_datadir}/doc/fwupd/*.html %if 0%{?have_uefi} %config(noreplace)%{_sysconfdir}/grub.d/35_fwupd %endif %{_libdir}/libfwupd.so.2* %{_libdir}/girepository-1.0/Fwupd-2.0.typelib /usr/lib/udev/rules.d/*.rules /usr/lib/systemd/system-shutdown/fwupd.shutdown %dir %{_libdir}/fwupd-%{version} %{_libdir}/fwupd-%{version}/libfwupd*.so %ghost %{_localstatedir}/lib/fwupd/gnupg %if 0%{?have_modem_manager} %files plugin-modem-manager %{_libdir}/fwupd-%{version}/libfu_plugin_modem_manager.so %endif %if 0%{?have_flashrom} %files plugin-flashrom %{_libdir}/fwupd-%{version}/libfu_plugin_flashrom.so %endif %if 0%{?have_uefi} %files plugin-uefi-capsule-data %{_datadir}/fwupd/uefi-capsule-ux.tar.xz %endif %files devel %{_datadir}/gir-1.0/Fwupd-2.0.gir %{_datadir}/doc/fwupd/libfwupdplugin %{_datadir}/doc/fwupd/libfwupd %{_datadir}/doc/libfwupdplugin %{_datadir}/doc/libfwupd %{_datadir}/vala/vapi %{_includedir}/fwupd-1 %{_libdir}/libfwupd*.so %{_libdir}/pkgconfig/fwupd.pc %files tests %if 0%{?enable_tests} %{_datadir}/fwupd/host-emulate.d/*.json.gz %dir %{_datadir}/installed-tests/fwupd %{_datadir}/installed-tests/fwupd/tests/* %{_datadir}/installed-tests/fwupd/fwupd-tests.xml %{_datadir}/installed-tests/fwupd/*.test %{_datadir}/installed-tests/fwupd/*.cab %{_datadir}/installed-tests/fwupd/fakedevice124.jcat %{_datadir}/installed-tests/fwupd/fakedevice124.bin %{_datadir}/installed-tests/fwupd/fakedevice124.metainfo.xml %{_datadir}/installed-tests/fwupd/*.sh %{_datadir}/installed-tests/fwupd/*.zip %if 0%{?have_uefi} %{_datadir}/installed-tests/fwupd/efi %endif %{_datadir}/installed-tests/fwupd/chassis_type %{_datadir}/installed-tests/fwupd/sys_vendor %if 0%{?fedora} >= 37 %{_datadir}/fwupd/device-tests/*.json %endif %{_libexecdir}/installed-tests/fwupd/* %{_datadir}/fwupd/remotes.d/fwupd-tests.conf %endif %if 0%{?qubes_packages} %files qubes-vm %{_libexecdir}/qubes-fwupd/fwupd_common_vm.py %{_libexecdir}/qubes-fwupd/fwupd_download_updates.py %files qubes-dom0 %{_datadir}/qubes-fwupd/src/fwupd_receive_updates.py %{_sbindir}/qubes-fwupdmgr %{_datadir}/qubes-fwupd/src/qubes_fwupdmgr.py %{_datadir}/qubes-fwupd/src/qubes_fwupd_common.py %{_datadir}/qubes-fwupd/src/qubes_fwupd_heads.py %{_datadir}/qubes-fwupd/src/qubes_fwupd_update.py %{_datadir}/qubes-fwupd/src/__init__.py %{_datadir}/qubes-fwupd/test/fwupd_logs.py %{_datadir}/qubes-fwupd/test/test_qubes_fwupdmgr.py %{_datadir}/qubes-fwupd/test/test_qubes_fwupd_heads.py %{_datadir}/qubes-fwupd/test/__init__.py %{_datadir}/qubes-fwupd/test/logs/get_devices.log %{_datadir}/qubes-fwupd/test/logs/get_updates.log %{_datadir}/qubes-fwupd/test/logs/help.log %{_datadir}/qubes-fwupd/test/logs/firmware.metainfo.xml %{_datadir}/qubes-fwupd/test/logs/metainfo_name/firmware.metainfo.xml %{_datadir}/qubes-fwupd/test/logs/metainfo_version/firmware.metainfo.xml %endif %changelog %autochangelog fwupd-1.9.16/contrib/fwupd.wxs.in000066400000000000000000000034761460375044200167110ustar00rootroot00000000000000 NOT NEWERVERSIONDETECTED fwupd-1.9.16/contrib/generate-ds20.py000077500000000000000000000042601460375044200173210ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2022 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ # # pylint: disable=consider-using-f-string import sys import argparse import configparser import base64 from typing import List if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( "-b", "--bufsz", type=int, help="Buffer size in bytes", ) parser.add_argument("-i", "--instance-id", type=str, help="Device instance ID") parser.add_argument("filename", action="store", type=str, help="Quirk filename") args = parser.parse_args() if not args.bufsz: parser.print_help() sys.exit(1) config = configparser.ConfigParser() config.optionxform = str try: config.read(args.filename) except configparser.MissingSectionHeaderError: print("Not a quirk file") sys.exit(1) # fall back to the default if there is only one device in the quirk file if not args.instance_id: sections = config.sections() if len(sections) != 1: print("Multiple devices found, use --instance-id to choose between:") for section in sections: print(" • {}".format(section)) sys.exit(1) args.instance_id = sections[0] # create the smallest kv store possible lines: List[str] = [] try: for key in config[args.instance_id]: if key in ["Inhibit", "Issue"]: print("WARNING: skipping key {}".format(key)) continue value = config[args.instance_id][key] lines.append("{}={}".format(key, value)) except KeyError: print("No {} section".format(args.instance_id)) sys.exit(1) # pad to the buffer size buf: bytes = "\n".join(lines).encode() if len(buf) > args.bufsz: print("Quirk data is larger than bufsz") sys.exit(1) buf = buf.ljust(args.bufsz, b"\0") # success print("DS20 descriptor control transfer data:") print(" ".join(["{:02x}".format(val) for val in list(buf)])) print(base64.b64encode(buf).decode()) fwupd-1.9.16/contrib/generate-emulation.py000077500000000000000000000074321460375044200205520ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ # # pylint: disable=invalid-name,missing-docstring,consider-using-f-string import json import sys from typing import Dict, List, Any import gi from gi.repository import GLib gi.require_version("Fwupd", "2.0") gi.require_version("Json", "1.0") from gi.repository import Fwupd # pylint: disable=wrong-import-position from gi.repository import Json # pylint: disable=wrong-import-position def _minimize_json(json_str: str) -> str: nodes = json.loads(json_str) new_attrs: List[Dict[str, Any]] = [] new_devices: List[Dict[str, Any]] = [] new_bios_settings: List[Dict[str, Any]] = [] try: for attr in nodes["SecurityAttributes"]: new_attr: Dict[str, Any] = {} for key in attr: if key in ["AppstreamId", "HsiResult", "Flags", "Plugin"]: new_attr[key] = attr[key] new_attrs.append(new_attr) except KeyError: pass try: for device in nodes["Devices"]: new_device: Dict[str, Any] = {} for key in device: if key not in ["Created", "Modified", "Releases", "Plugin"]: new_device[key] = device[key] new_devices.append(new_device) except KeyError: pass try: for device in nodes["BiosSettings"]: new_attr: Dict[str, Any] = {} for key in device: if key not in ["Filename"]: new_attr[key] = device[key] new_bios_settings.append(new_attr) except KeyError: pass return json.dumps( { "SecurityAttributes": new_attrs, "Devices": new_devices, "BiosSettings": new_bios_settings, }, indent=2, separators=(",", " : "), ) def _get_host_devices_and_attrs() -> str: # connect to the running daemon client = Fwupd.Client() builder = Json.Builder() builder.begin_object() # add devices try: devices = client.get_devices() except GLib.GError as e: print(f"ignoring {e}") else: builder.set_member_name("Devices") builder.begin_array() for device in devices: builder.begin_object() device.to_json_full(builder, Fwupd.DEVICE_FLAG_TRUSTED) builder.end_object() builder.end_array() # add security attributes try: attrs = client.get_host_security_attrs() except GLib.GError as e: print(f"ignoring {e}") else: builder.set_member_name("SecurityAttributes") builder.begin_array() for attr in attrs: builder.begin_object() attr.to_json(builder) builder.end_object() builder.end_array() # add BIOS settings try: attrs = client.get_bios_settings() except GLib.GError as e: print(f"ignoring {e}") else: builder.set_member_name("BiosSettings") builder.begin_array() for attr in attrs: builder.begin_object() attr.to_json(builder) builder.end_object() builder.end_array() # export to JSON builder.end_object() generator = Json.Generator() generator.set_pretty(True) generator.set_root(builder.get_root()) return generator.to_data()[0] if len(sys.argv) < 2: sys.stdout.write(_minimize_json(sys.stdin.read())) else: for fn in sys.argv[1:]: try: with open(fn, "rb") as f_in: json_in = f_in.read().decode() except FileNotFoundError: json_in = _get_host_devices_and_attrs() json_out = _minimize_json(json_in).encode() with open(fn, "wb") as f_out: f_out.write(json_out) fwupd-1.9.16/contrib/generate-release.py000077500000000000000000000042101460375044200201640ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2023 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ # # pylint: disable=invalid-name,missing-docstring import os import subprocess import datetime from jinja2 import Environment, FileSystemLoader def _get_last_release() -> str: return ( subprocess.check_output(["git", "describe", "--tags", "--abbrev=0"]) .decode() .replace("\n", "") ) def _get_next_release(last_tag: str) -> str: triplet: list[str] = last_tag.split(".") return f"{triplet[0]}.{triplet[1]}.{int(triplet[2])+1}" def _get_appstream_date() -> str: return datetime.datetime.now().strftime("%Y-%m-%d") def _generate_release_notes(last_tag: str, next_tag: str) -> str: lines_feat: list[str] = [] lines_bugs: list[str] = [] lines_devs: list[str] = [] for line in ( subprocess.check_output( [ "git", "log", "--format=%s", f"{last_tag}...", ] ) .decode() .split("\n") ): if not line: continue if line.find("trivial") != -1: continue if line.find("Typo") != -1: continue if line.find("Merge") != -1: continue if line in lines_feat or line in lines_bugs or line in lines_devs: continue if line.startswith("Add "): lines_feat.append(line) continue lines_bugs.append(line) env = Environment( loader=FileSystemLoader(os.path.dirname(os.path.realpath(__file__))), autoescape=False, keep_trailing_newline=False, ) template = env.get_template("generate-release.xml") return template.render( { "version": next_tag, "date": _get_appstream_date(), "features": lines_feat, "bugs": lines_bugs, "devices": lines_devs, } ) if __name__ == "__main__": _last_tag: str = _get_last_release() _next_tag: str = _get_next_release(_last_tag) xml: str = _generate_release_notes(_last_tag, _next_tag) print(xml) fwupd-1.9.16/contrib/generate-release.xml000066400000000000000000000011221460375044200203300ustar00rootroot00000000000000

This release adds the following features:

    {%- for item in features %}
  • {{item}}
  • {%- endfor %}

This release fixes the following bugs:

    {%- for item in bugs %}
  • {{item}}
  • {%- endfor %}

This release adds support for the following hardware:

    {%- for item in devices %}
  • {{item}}
  • {%- endfor %}
fwupd-1.9.16/contrib/launch-venv.sh000077500000000000000000000016741460375044200171770ustar00rootroot00000000000000#!/bin/sh -e gcc=$(gcc -dumpmachine) DIST="$(dirname $0)/../dist" BIN="$(basename $0)" export FWUPD_LOCALSTATEDIR=${DIST} export FWUPD_SYSCONFDIR=${DIST}/etc export LD_LIBRARY_PATH=${DIST}/lib/${gcc}:${DIST}/lib64:${DIST}/lib if [ -n "${DEBUG}" ]; then if ! which gdbserver 1>/dev/null 2>&1; then echo "install gdbserver to enable debugging" exit 1 fi DEBUG="gdbserver localhost:9091" fi if [ -f ${DIST}/libexec/fwupd/${BIN} ]; then EXE=${DIST}/libexec/fwupd/${BIN} else EXE=${DIST}/bin/${BIN} fi if [ ! -f ${EXE} ]; then echo "Not yet built! Please run:" echo "" echo "# build-fwupd" exit 1 fi SUDO="$(which sudo) \ LD_LIBRARY_PATH=${LD_LIBRARY_PATH} \ FWUPD_LOCALSTATEDIR=${FWUPD_LOCALSTATEDIR} \ FWUPD_SYSCONFDIR=${FWUPD_SYSCONFDIR} \ G_DEBUG=${G_DEBUG} \ FWUPD_POLKIT_NOCHECK=1" ${SUDO} ${DEBUG} ${EXE} "$@" fwupd-1.9.16/contrib/meson.build000066400000000000000000000005771460375044200165550ustar00rootroot00000000000000subdir('firmware_packager') if get_option('qubes') subdir('qubes') endif con2 = configuration_data() con2.set('FWUPD_VERSION', fwupd_version) configure_file( input: 'fwupd.spec.in', output: 'fwupd.spec.in', configuration: con2, ) if host_machine.system() == 'windows' configure_file( input: 'fwupd.wxs.in', output: 'fwupd.wxs', configuration: conf ) endif fwupd-1.9.16/contrib/migrate.py000077500000000000000000000160561460375044200164170ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2022 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ # # import os import sys import glob if __name__ == "__main__": fns = [] if len(sys.argv) > 1: fns.extend(sys.argv[1:]) else: exts = ["c", "h", "map"] for ext in exts: for fn in glob.glob(f"**/*.{ext}", recursive=True): if fn.startswith("build"): continue if fn.startswith("subprojects"): continue if fn.startswith(".git"): continue fns.append(fn) for fn in fns: modified: bool = False with open(fn, "r") as f: buf = f.read() for old, new in { "fu_common_sum8": "fu_sum8", "fu_common_sum8_bytes": "fu_sum8_bytes", "fu_common_sum16": "fu_sum16", "fu_common_sum16_bytes": "fu_sum16_bytes", "fu_common_sum16w": "fu_sum16w", "fu_common_sum16w_bytes": "fu_sum16w_bytes", "fu_common_sum32": "fu_sum32", "fu_common_sum32_bytes": "fu_sum32_bytes", "fu_common_sum32w": "fu_sum32w", "fu_common_sum32w_bytes": "fu_sum32w_bytes", "fu_common_crc8": "fu_crc8", "fu_common_crc8_full": "fu_crc8_full", "fu_common_crc16": "fu_crc16", "fu_common_crc16_full": "fu_crc16_full", "fu_common_crc32": "fu_crc32", "fu_common_crc32_full": "fu_crc32_full", "fu_byte_array_set_size_full": "fu_byte_array_set_size", "fu_common_string_replace": "g_string_replace", "fu_common_string_append_kv": "fu_string_append", "fu_common_string_append_ku": "fu_string_append_ku", "fu_common_string_append_kx": "fu_string_append_kx", "fu_common_string_append_kb": "fu_string_append_kb", "fu_common_strnsplit": "fu_strsplit", "fu_common_strnsplit_full": "fu_strsplit_full", "fu_common_strjoin_array": "fu_strjoin", "fu_common_strsafe": "fu_strsafe", "fu_common_strwidth": "fu_strwidth", "fu_common_strstrip": "fu_strstrip", "fu_common_strtoull": "fu_strtoull", "fu_common_strtoull_full": "fu_strtoull", "FuCommonStrsplitFunc": "FuStrsplitFunc", "fu_common_bytes_pad": "fu_bytes_pad", "fu_common_bytes_new_offset": "fu_bytes_new_offset", "fu_common_bytes_align": "fu_bytes_align", "fu_common_bytes_is_empty": "fu_bytes_is_empty", "fu_common_bytes_compare(": "fu_bytes_compare(", "fu_common_set_contents_bytes": "fu_bytes_set_contents", "fu_common_get_contents_bytes": "fu_bytes_get_contents", "fu_common_get_contents_stream": "fu_bytes_get_contents_stream", "fu_common_get_contents_fd": "fu_bytes_get_contents_fd", "fu_common_read_uint8_safe": "fu_memread_uint8_safe", "fu_common_read_uint16_safe": "fu_memread_uint16_safe", "fu_common_read_uint32_safe": "fu_memread_uint32_safe", "fu_common_read_uint64_safe": "fu_memread_uint64_safe", "fu_common_write_uint8_safe": "fu_memwrite_uint8_safe", "fu_common_write_uint16_safe": "fu_memwrite_uint16_safe", "fu_common_write_uint32_safe": "fu_memwrite_uint32_safe", "fu_common_write_uint64_safe": "fu_memwrite_uint64_safe", "fu_common_write_uint16": "fu_memwrite_uint16", "fu_common_write_uint24": "fu_memwrite_uint24", "fu_common_write_uint32": "fu_memwrite_uint32", "fu_common_write_uint64": "fu_memwrite_uint64", "fu_common_read_uint16": "fu_memread_uint16", "fu_common_read_uint24": "fu_memread_uint24", "fu_common_read_uint32": "fu_memread_uint32", "fu_common_read_uint64": "fu_memread_uint64", "fu_common_bytes_compare_raw": "fu_memcmp_safe", "FuOutputHandler": "FuSpawnOutputHandler", "fu_common_spawn_sync": "fu_spawn_sync", "fu_common_kernel_locked_down": "fu_kernel_locked_down", "fu_common_check_kernel_version": "fu_kernel_check_version", "fu_common_get_firmware_search_path": "fu_kernel_get_firmware_search_path", "fu_common_set_firmware_search_path": "fu_kernel_set_firmware_search_path", "fu_common_reset_firmware_search_path": "fu_kernel_reset_firmware_search_path", "fu_common_firmware_builder": "fu_firmware_builder_process", "fu_common_uri_get_scheme": "fu_release_uri_get_scheme", "fu_common_dump_raw": "fu_dump_raw", "fu_common_dump_full": "fu_dump_full", "fu_common_dump_bytes": "fu_dump_bytes", "fu_common_error_array_get_best": "fu_engine_error_array_get_best", "fu_common_get_path": "fu_path_from_kind", "fu_common_filename_glob": "fu_path_glob", "fu_common_fnmatch": "g_pattern_match_simple", "fu_common_rmtree": "fu_path_rmtree", "fu_common_get_files_recursive": "fu_path_get_files", "fu_common_mkdir": "fu_path_mkdir", "fu_common_mkdir_parent": "fu_path_mkdir_parent", "fu_common_find_program_in_path": "fu_path_find_program", "fu_common_cpuid": "fu_cpuid", "fu_common_get_cpu_vendor": "fu_cpu_get_vendor", "fu_common_vercmp_full": "fu_version_compare", "fu_common_version_ensure_semver_full": "fu_version_ensure_semver", "fu_common_version_from_uint16": "fu_version_from_uint16", "fu_common_version_from_uint32": "fu_version_from_uint32", "fu_common_version_from_uint64": "fu_version_from_uint64", "fu_common_version_guess_format": "fu_version_guess_format", "fu_common_version_parse_from_format": "fu_version_parse_from_format", "fu_common_version_verify_format": "fu_version_verify_format", "fu_common_get_volumes_by_kind": "fu_volume_new_by_kind", "fu_common_get_volume_by_device": "fu_volume_new_by_device", "fu_common_get_volume_by_devnum": "fu_volume_new_by_devnum", "fu_common_get_esp_for_path": "fu_volume_new_esp_for_path", "fu_common_get_esp_default": "fu_context_get_esp_volumes", "fu_smbios_to_string": "fu_firmware_to_string", "fu_i2c_device_read_full": "fu_i2c_device_read", "fu_i2c_device_write_full": "fu_i2c_device_write", "fu_path_fnmatch": "g_pattern_match_simple", "fu_string_replace": "g_string_replace", "fu_efi_firmware_decompress_lzma": "fu_lzma_decompress_bytes", "fu_device_build_instance_id_quirk": "fu_device_build_instance_id_full", }.items(): if buf.find(old) == -1: continue buf = buf.replace(old, new) modified = True if modified: print(f"MODIFIED: {fn}") with open(fn, "w") as f: f.write(buf) sys.exit(0) fwupd-1.9.16/contrib/mingw64.cross000066400000000000000000000001161460375044200167460ustar00rootroot00000000000000[binaries] windmc = '/usr/bin/x86_64-w64-mingw32-windmc' exe_wrapper = 'wine' fwupd-1.9.16/contrib/pcap2emulation.py000077500000000000000000001053541460375044200177120ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2023 Collabora Ltd # Author: Frédéric Danis # # SPDX-License-Identifier: LGPL-2.1+ import argparse import base64 import json import os import subprocess import sys from typing import Any, Dict, List, Optional, Tuple from zipfile import ZipFile, ZIP_DEFLATED URB_INTERRUPT = 1 URB_CONTROL = 2 URB_BULK = 3 DESCRIPTOR_DEVICE = "1" DESCRIPTOR_CONFIGURATION = "2" DESCRIPTOR_STRING = "3" DESCRIPTOR_INTERFACE = "4" DESCRIPTOR_ENDPOINT = "5" DESCRIPTOR_EXTRA = "33" INTERFACE_CLASS_HID = 3 INTERFACE_CLASS_SMARTCARD = 11 CCID_PC_TO_RDR_SET_PARAMETERS = 0x61 CCID_PC_TO_RDR_ICC_POWER_ON = 0x62 CCID_PC_TO_RDR_ICC_POWER_OFF = 0x63 CCID_PC_TO_RDR_GET_SLOT_STATUS = 0x65 CCID_PC_TO_RDR_ESCAPE = 0x6B CCID_PC_TO_RDR_TRANSFER_BLOCK = 0x6F CCID_RDR_TO_PC_DATA_BLOCK = 0x80 CCID_RDR_TO_PC_SLOT_STATUS = 0x81 CCID_RDR_TO_PC_PARAMETERS = 0x82 CCID_RDR_TO_PC_ESCAPE = 0x83 def get_int(data: str) -> int: if data[:2] == "0x": return int(data, 16) return int(data) def add_bytes(array: bytearray, string: str, size: int) -> None: array += get_int(string).to_bytes(length=size, byteorder="little") class Pcap2Emulation: def __init__(self, device_ids: str): self.device: Dict[str, Any] = {} self.platform_id = "" self.phases: List[Any] = [] self.device_ids: List[List[str]] = [] self.interface_index = 0 self.endpoint_index = 0 self.previous_data: Optional[str] self.bulk_incoming_lens: Dict[str, int] = {} self.usb_port = None self.enumerate = False for i in range(len(device_ids)): device_id = device_ids[i].split(":") if len(device_id) > 2: sys.stderr.write(f"Malformed device ID: {device_ids[i]}\n\n") exit(1) if len(device_id) == 2 and len(device_id[1]) == 0: del device_id[1] if device_id not in self.device_ids: self.device_ids.append(device_id) def _save_phase(self) -> None: self.phases.append({"UsbDevices": [self.device]}) self.interface_index = 0 self.endpoint_index = 0 def save_archive(self, path: str) -> None: if not self.phases: return print(f"Found {len(self.phases)} phases:") phase = 0 if path.endswith(".zip"): emulation_file = path else: emulation_file = path + ".zip" with ZipFile(emulation_file, "w", compression=ZIP_DEFLATED) as write_file: print(f"- phase {phase} as setup.json") json_string = json.dumps( self.phases[phase], indent=2, separators=(",", " : ") ) write_file.writestr("setup.json", json_string) phase += 1 if len(self.phases) > 2: print(f"- phase {phase} as install.json") json_string = json.dumps( self.phases[phase], indent=2, separators=(",", " : ") ) write_file.writestr("install.json", json_string) phase += 1 print(f"- phase {phase} as reload.json") json_string = json.dumps( self.phases[phase], indent=2, separators=(",", " : ") ) write_file.writestr("reload.json", json_string) phase += 1 print("Emulation file saved to " + emulation_file) while phase < len(self.phases): phase_path = f"{path}-{phase}.json" with open(phase_path, "w") as dump_file: json.dump( self.phases[phase], dump_file, indent=2, separators=(",", " : "), ) print(f"- unused phase {phase} saved to {phase_path}") phase += 1 def _run_tshark(self, file: str, tshark_filter: str) -> Any: cmd = ["tshark", "-n", "-T", "ek", "-l", "-2", "-r", file, "-R"] print("running: " + " ".join(cmd) + ' "' + tshark_filter + '"') cmd.append(tshark_filter) return subprocess.Popen(cmd, stdout=subprocess.PIPE) def _get_usb_addrs(self, file: str) -> Tuple[str, List[str]]: tshark_filter = "" for i in range(len(self.device_ids)): if len(tshark_filter) == 0: tshark_filter += "(" else: tshark_filter += " or (" tshark_filter += "usb.idVendor == 0x" + self.device_ids[i][0] if len(self.device_ids[i]) == 2 and len(self.device_ids[i][1]) > 0: tshark_filter += " and usb.idProduct == 0x" + self.device_ids[i][1] tshark_filter += ")" usb_bus = "" usb_addrs: List[str] = [] p = self._run_tshark(file, tshark_filter) for line in p.stdout: pcap_data = json.loads(line) if "layers" in pcap_data: if not usb_bus: usb_bus = pcap_data["layers"]["usb"]["usb_usb_bus_id"] elif usb_bus != pcap_data["layers"]["usb"]["usb_usb_bus_id"]: print( "* Warning: Found different USB Bus ID: expected {}, found {}".format( usb_bus, pcap_data["layers"]["usb"]["usb_usb_bus_id"] ) ) addr = pcap_data["layers"]["usb"]["usb_usb_device_address"] if addr not in usb_addrs: usb_addrs.append(addr) return usb_bus, usb_addrs def _get_interrupt_event(self, layers: Dict[str, Any]) -> Dict[str, str]: if "usb_usb_capdata" in layers: captured_data = str( base64.b64encode( bytes.fromhex(layers["usb_usb_capdata"].replace(":", "")) ), "utf-8", ) s = "InterruptTransfer:Endpoint=0x{:02x}".format( get_int(layers["usb"]["usb_usb_endpoint_address"]) ) if layers["usb"]["usb_usb_endpoint_address_direction"] == "1": if hasattr(self, "previous_data") and self.previous_data: s += f",Data={self.previous_data}" self.previous_data = None else: s += ",Data=" else: self.previous_data = captured_data s += f",Data={captured_data}" s += f",Length=0x{int(layers['usb']['usb_usb_data_len']):x}" return {"Id": s, "Data": captured_data} return {} def _get_bulk_event(self, layers: Dict[str, Any]) -> Dict[str, str]: captured_data = None if "usbccid" in layers: message_type = get_int(layers["usbccid"]["usbccid_usbccid_bMessageType"]) ccid = bytearray([message_type]) add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_dwLength"], 4) add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bSlot"], 1) add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bSeq"], 1) if message_type == CCID_PC_TO_RDR_SET_PARAMETERS: add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bProtocolNum"], 1) add_bytes( ccid, layers["usbccid"]["usbccid_usbccid_hf_ccid_Reserved"], 2 ) ccid += bytearray.fromhex( layers["data"]["data_data_data"].replace(":", "") ) elif message_type == CCID_PC_TO_RDR_ICC_POWER_ON: add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bPowerSelect"], 1) add_bytes( ccid, layers["usbccid"]["usbccid_usbccid_hf_ccid_Reserved"], 2 ) elif ( message_type == CCID_PC_TO_RDR_ICC_POWER_OFF or message_type == CCID_PC_TO_RDR_GET_SLOT_STATUS ): add_bytes( ccid, layers["usbccid"]["usbccid_usbccid_hf_ccid_Reserved"], 3 ) elif message_type == CCID_PC_TO_RDR_ESCAPE: ccid += bytearray.fromhex( layers["usbccid"]["usbccid_usbccid_abRFU"].replace(":", "") ) ccid += bytearray.fromhex( layers["data"]["data_data_data"].replace(":", "") ) elif message_type == CCID_PC_TO_RDR_TRANSFER_BLOCK: add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bBWI"], 1) add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_wLevelParameter"], 2) ccid += bytearray.fromhex( layers["data"]["data_data_data"].replace(":", "") ) elif message_type == CCID_RDR_TO_PC_DATA_BLOCK: add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bStatus"], 1) add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bError"], 1) add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bChainParameter"], 1) if "data" in layers: ccid += bytearray.fromhex( layers["data"]["data_data_data"].replace(":", "") ) elif message_type == CCID_RDR_TO_PC_SLOT_STATUS: add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bStatus"], 1) add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bError"], 1) add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bClockStatus"], 1) elif message_type == CCID_RDR_TO_PC_PARAMETERS: add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bStatus"], 1) add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bError"], 1) add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bProtocolNum"], 1) elif message_type == CCID_RDR_TO_PC_ESCAPE: add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bStatus"], 1) add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bError"], 1) add_bytes(ccid, layers["usbccid"]["usbccid_usbccid_bRFU"], 1) ccid += bytearray.fromhex( layers["data"]["data_data_data"].replace(":", "") ) else: print(f"Unknown USB CCID Bulk message type: 0x{message_type:02X}") captured_data = str(base64.b64encode(ccid), "utf-8") elif "usb_usb_capdata" in layers: captured_data = str( base64.b64encode( bytes.fromhex(layers["usb_usb_capdata"].replace(":", "")) ), "utf-8", ) if captured_data: s = "BulkTransfer:Endpoint=0x{:02x}".format( get_int(layers["usb"]["usb_usb_endpoint_address"]) ) if layers["usb"]["usb_usb_endpoint_address_direction"] == "1": if layers["usb"]["usb_usb_request_in"] in self.bulk_incoming_lens: length = self.bulk_incoming_lens[ layers["usb"]["usb_usb_request_in"] ] else: length = int(layers["usb"]["usb_usb_data_len"]) data = str( base64.b64encode(bytes.fromhex("00" * length)), "utf-8", ) s += f",Data={data}" else: length = int(layers["usb"]["usb_usb_data_len"]) s += f",Data={captured_data}" s += f",Length=0x{length:x}" return {"Id": s, "Data": captured_data} elif layers["usb"]["usb_usb_endpoint_address_direction"] == "1": if "usb_usb_urb_len" in layers["usb"]: self.bulk_incoming_lens[layers["frame"]["frame_frame_number"]] = int( layers["usb"]["usb_usb_urb_len"] ) return {} def _get_interface_descriptor( self, layers: Dict[str, Any], descriptor_index: int ) -> Any: table = { "usb_usb_bInterfaceNumber": "InterfaceNumber", "usb_usb_bAlternateSetting": "AlternateSetting", "usb_usb_bNumEndpoints": "NumEndpoints", "usb_usb_bInterfaceClass": "InterfaceClass", "usb_usb_bInterfaceSubClass": "InterfaceSubClass", "usb_usb_bInterfaceProtocol": "InterfaceProtocol", "usb_usb_iInterface": "Interface", } # Interface descriptor frame may occur multiple times, # it should not change unless device has been re-enumerated if len(layers["usb_usb_bInterfaceNumber"]) <= self.interface_index: return None interface: Dict[str, Any] = { "Length": get_int(layers["usb_usb_bLength"][descriptor_index]), "DescriptorType": 4, } for key in table: # data can be a string or a list of strings if type(layers[key]) is str: val = get_int(layers[key]) else: val = get_int(layers[key][self.interface_index]) if key not in layers: continue interface[table[key]] = val if key == "usb_usb_bNumEndpoints": interface["UsbEndpoints"] = [] return interface def _get_endpoint_descriptor( self, layers: Dict[str, Any], index: int ) -> Dict[str, int]: table = { "usb_usb_bEndpointAddress": "EndpointAddress", "usb_usb_bInterval": "Interval", "usb_usb_wMaxPacketSize": "MaxPacketSize", } endpoint = { "DescriptorType": 5, } for key in table: val = get_int(layers[key][index]) if val != 0: endpoint[table[key]] = val return endpoint def _get_hid_descriptor(self, layers: Dict[str, Any], index: int) -> str: desc = bytearray() add_bytes(desc, layers["usb_usb_bLength"][index], 1) add_bytes(desc, layers["usb_usb_bDescriptorType"][index], 1) add_bytes(desc, layers["usbhid_usbhid_descriptor_hid_bcdHID"], 2) add_bytes(desc, layers["usbhid_usbhid_descriptor_hid_bCountryCode"], 1) add_bytes(desc, layers["usbhid_usbhid_descriptor_hid_bNumDescriptors"], 1) add_bytes(desc, layers["usbhid_usbhid_descriptor_hid_bDescriptorType"], 1) add_bytes(desc, layers["usbhid_usbhid_descriptor_hid_wDescriptorLength"], 2) return str(base64.b64encode(desc), "utf-8") def _get_smartcard_descriptor(self, layers: Dict[str, Any], index: int) -> str: desc = bytearray() add_bytes(desc, layers["usb_usb_bLength"][index], 1) add_bytes(desc, layers["usb_usb_bDescriptorType"][index], 1) add_bytes(desc, layers["usbccid_usbccid_bcdCCID"], 2) add_bytes(desc, layers["usbccid_usbccid_bMaxSlotIndex"], 1) add_bytes(desc, layers["usbccid_usbccid_bVoltageSupport"], 1) add_bytes(desc, layers["usbccid_usbccid_dwProtocols"], 4) add_bytes(desc, layers["usbccid_usbccid_dwDefaultClock"], 4) add_bytes(desc, layers["usbccid_usbccid_dwMaximumClock"], 4) add_bytes(desc, layers["usbccid_usbccid_bNumClockSupported"], 1) add_bytes(desc, layers["usbccid_usbccid_dwDataRate"], 4) add_bytes(desc, layers["usbccid_usbccid_dwMaxDataRate"], 4) add_bytes(desc, layers["usbccid_usbccid_bNumDataRatesSupported"], 1) add_bytes(desc, layers["usbccid_usbccid_dwMaxIFSD"], 4) add_bytes(desc, layers["usbccid_usbccid_dwSynchProtocols"], 4) add_bytes(desc, layers["usbccid_usbccid_dwMechanical"], 4) add_bytes(desc, layers["usbccid_usbccid_dwFeatures"], 4) add_bytes(desc, layers["usbccid_usbccid_dwMaxCCIDMessageLength"], 4) add_bytes(desc, layers["usbccid_usbccid_hf_ccid_bClassGetResponse"], 1) add_bytes(desc, layers["usbccid_usbccid_hf_ccid_bClassEnvelope"], 1) add_bytes(desc, layers["usbccid_usbccid_hf_ccid_wLcdLayout"], 2) add_bytes(desc, layers["usbccid_usbccid_hf_ccid_bPINSupport"], 1) add_bytes(desc, layers["usbccid_usbccid_hf_ccid_bMaxCCIDBusySlots"], 1) return str(base64.b64encode(desc), "utf-8") def _save_event(self, event: Dict[str, str]) -> None: if not self.device: return self.device["UsbEvents"].append(event) def parse_file(self, file: str) -> None: bus_id, addrs = self._get_usb_addrs(file) if len(addrs) == 0: print("Device(s) not found in pcap file") return # Filter the device related packets and the C_PORT_CONNECTION clear # feature packets to allow detection of the re-plug/re-enumerate events tshark_filter = f"usb.bus_id == {bus_id}" tshark_filter += " and (usbhub.setup.PortFeatureSelector == 16" for addr in addrs: tshark_filter += f" or usb.device_address == {addr}" tshark_filter += ")" p = self._run_tshark(file, tshark_filter) for line in p.stdout: pcap_data = json.loads(line) if "layers" in pcap_data: layers = pcap_data["layers"] usb_port = None if ( layers["frame"]["frame_frame_cap_len"] != layers["frame"]["frame_frame_len"] ): print( "* Incomplete frame {}: {} bytes captured < {}".format( layers["frame"]["frame_frame_number"], layers["frame"]["frame_frame_cap_len"], layers["frame"]["frame_frame_len"], ) ) # Store the USB port of C_PORT_CONNECTION clear feature packet # Trigger an enumeration requirement if it's the same port that # was used for the previous device description if "usbhub_usbhub_setup_Port" in layers: usb_port = layers["usbhub_usbhub_setup_Port"] if usb_port == self.usb_port: self.enumerate = True if get_int(layers["usb"]["usb_usb_transfer_type"]) == URB_INTERRUPT: event = self._get_interrupt_event(layers) if len(event) > 0: self._save_event(event) elif get_int(layers["usb"]["usb_usb_transfer_type"]) == URB_CONTROL: if "usb_usb_bDescriptorType" in layers: descriptor_index = -1 # usb_usb_bDescriptorType can be a string or a list of strings if type(layers["usb_usb_bDescriptorType"]) is str: layer = [layers["usb_usb_bDescriptorType"]] else: layer = layers["usb_usb_bDescriptorType"] for descriptor_type in layer: descriptor_index += 1 if descriptor_type == DESCRIPTOR_DEVICE: # Check this is a reply for the Vid[:Pid] expected if layers["usb"]["usb_usb_src"] == "host": continue found = False for i in range(len(self.device_ids)): if get_int(layers["usb_usb_idVendor"]) == int( self.device_ids[i][0], 16 ): if len(self.device_ids[i]) == 1 or get_int( layers["usb_usb_idProduct"] ) == int(self.device_ids[i][1], 16): found = True break if not found: continue # Save the previous USB device if ( self.device and len(self.device["UsbInterfaces"]) > 0 ): self._save_phase() # Create a new USB device # using a fake PlatformId based on USB bus id and device address, # this PlatformId should be stable for all recorded devices if not self.platform_id: self.platform_id = "usb:{:02x}:{:02x}".format( int(layers["usb"]["usb_usb_bus_id"]), int(layers["usb"]["usb_usb_device_address"]), ) # Device re-enumeration is triggered when 'Created' time differs # from previous phase, this keeps the 'Created' time from previous # phase unless a re-enumeration requirement has been detected if not self.device or self.enumerate: frame_time = layers["frame"]["frame_frame_time"] self.usb_port = usb_port self.enumerate = False else: frame_time = self.device["Created"] self.device = { "PlatformId": self.platform_id, "Created": frame_time, "Tags": ["org.freedesktop.fwupd.emulation.v1"], "IdVendor": int(layers["usb_usb_idVendor"]), "IdProduct": int(layers["usb_usb_idProduct"], 16), "Device": int(layers["usb_usb_bcdDevice"], 16), "USB": int(layers["usb_usb_bcdUSB"], 16), "Manufacturer": int( layers["usb_usb_iManufacturer"] ), "DeviceClass": int(layers["usb_usb_bDeviceClass"]), "DeviceSubClass": int( layers["usb_usb_bDeviceSubClass"] ), "DeviceProtocol": int( layers["usb_usb_bDeviceProtocol"] ), "Product": int(layers["usb_usb_iProduct"]), "SerialNumber": int( layers["usb_usb_iSerialNumber"] ), "UsbInterfaces": [], "UsbEvents": [], } if descriptor_type == DESCRIPTOR_CONFIGURATION: if "usb_usb_iConfiguration" in layers: # The GetConfigurationIndex GUsb event is not directly # related to a specific USB event, but data can be # retrieved from the DESCRIPTOR CONFIGURATION request index = ( layers["usb_usb_iConfiguration"] .encode("utf-8") .hex() ) event = { "Id": "GetConfigurationIndex", "Data": str( base64.b64encode(bytes.fromhex(index)), "utf-8", ), } self._save_event(event) if descriptor_type == DESCRIPTOR_STRING: if "usb_usb_DescriptorIndex" in layers: desc_index = get_int( layers["usb_usb_DescriptorIndex"] ) if desc_index == 0: # The list of supported languages are not recorded for the emulation continue event_str = { "Id": "GetStringDescriptor:DescIndex=0x{:02x}".format( desc_index ) } # duplicate the event so it can also be used for GetStringDescriptorBytes language_id = get_int(layers["usb_usb_LanguageId"]) length = get_int(layers["usb_usb_setup_wLength"]) event_bytes = { "Id": "GetStringDescriptorBytes:DescIndex=0x{:02x}".format( desc_index ) } event_bytes["Id"] += f",Langid=0x{language_id:04x}" event_bytes["Id"] += f",Length=0x{length:x}" elif "usb_usb_bString" in layers: if int(layers["usb_usb_bLength"]) != len( layers["usb_usb_bString"].encode("utf-16") ): # Discard frame used to retrieve STRING DESCRIPTOR length continue # Found a new STRING DESCRIPTOR response data = ( layers["usb_usb_bString"].encode("utf-8").hex() ) if "usb_usb_capdata" in layers: # Add leftover capture data data += "00" data += layers["usb_usb_capdata"].replace( ":", "" ) event_str["Data"] = str( base64.b64encode(bytes.fromhex(data)), "utf-8" ) # now that the event is completed it can be added to the device events self._save_event(event_str) # duplicate the event so it can also be used for GetStringDescriptorBytes event_bytes["Data"] = event_str["Data"] self._save_event(event_bytes) if descriptor_type == DESCRIPTOR_INTERFACE: interface = self._get_interface_descriptor( layers, descriptor_index ) if not interface: continue # Add the interface to the device self.device["UsbInterfaces"].append(interface) if ( "InterfaceClass" in interface and "InterfaceSubClass" in interface and "InterfaceProtocol" in interface ): # The GetCustomIndex GUsb event is not directly # related to a specific USB event, but data can be # retrieved from the DESCRIPTOR INTERFACE request index = interface["Interface"].to_bytes(1, "big") event = { "Id": "GetCustomIndex:ClassId=0x{:02x},SubclassId=0x{:02x},ProtocolId=0x{:02x}".format( interface["InterfaceClass"], interface["InterfaceSubClass"], interface["InterfaceProtocol"], ), "Data": str( base64.b64encode(index), "utf-8", ), } self._save_event(event) self.interface_index += 1 if descriptor_type == DESCRIPTOR_ENDPOINT: if ( len(layers["usb_usb_bEndpointAddress"]) > self.endpoint_index ): endpoint = self._get_endpoint_descriptor( layers, self.endpoint_index ) # Add the endpoint to the first interface with missing endpoint for interface in self.device["UsbInterfaces"]: if ( "UsbEndpoints" in interface and len(interface["UsbEndpoints"]) < interface["NumEndpoints"] ): interface["UsbEndpoints"].append(endpoint) break self.endpoint_index += 1 if descriptor_type == DESCRIPTOR_EXTRA: for interface in self.device["UsbInterfaces"]: if ( interface["InterfaceClass"] == INTERFACE_CLASS_HID ): d = self._get_hid_descriptor( layers, descriptor_index ) interface["ExtraData"] = d break elif ( interface["InterfaceClass"] == INTERFACE_CLASS_SMARTCARD ): d = self._get_smartcard_descriptor( layers, descriptor_index ) interface["ExtraData"] = d break elif ( "usb_usb_bmRequestType_type" in layers and int(layers["usb_usb_bmRequestType_type"], 16) == URB_CONTROL ): # Found vendor CONTROL URB request direction = not (layers["usb_usb_bmRequestType_direction"]) s = f"ControlTransfer:Direction=0x{direction:02x}" s += ",RequestType=0x{:02x}".format( int(layers["usb_usb_bmRequestType_type"], 16) ) s += ",Recipient=0x{:02x}".format( int(layers["usb_usb_bmRequestType_recipient"], 16) ) s += f",Request=0x{int(layers['usb_usb_setup_bRequest']):02x}" s += f",Value=0x{int(layers['usb_usb_setup_wValue'], 16):04x}" s += f",Idx=0x{int(layers['usb_usb_setup_wIndex']):04x}" if "usb_usb_data_fragment" in layers: data = layers["usb_usb_data_fragment"].replace(":", "") else: data = "00" * int(layers["usb_usb_setup_wLength"]) s += ",Data={}".format( str(base64.b64encode(bytes.fromhex(data)), "utf-8") ) s += f",Length=0x{int(layers['usb_usb_setup_wLength']):x}" event = {"Id": s} if direction: # Duplicate the outgoing data as response and add the event to the device events event["Data"] = str( base64.b64encode(bytes.fromhex(data)), "utf-8" ) self._save_event(event) elif "usb_usb_control_Response" in layers: # Found CONTROL URB response data = layers["usb_usb_control_Response"].replace(":", "") event["Data"] = str( base64.b64encode(bytes.fromhex(data)), "utf-8" ) # now that the event is complete it can be added to the device events self._save_event(event) elif get_int(layers["usb"]["usb_usb_transfer_type"]) == URB_BULK: # TODO: check it event = self._get_bulk_event(layers) if event: self._save_event(event) else: if not self.device: continue print( "Unknown frame type: " + layers["usb"]["usb_usb_transfer_type"] ) # Save the last USB device if "UsbInterfaces" in self.device: self._save_phase() if __name__ == "__main__": options = argparse.ArgumentParser(description="Convert pcap file to emulation file") options.add_argument("input_pcap", type=str, help="pcap file to convert") options.add_argument("output_archive", type=str, help="Output archive path") options.add_argument( "device_id", metavar=("VendorID[:ProductID]"), type=str, nargs="+", help="Device ID in hexadecimal", ) args = options.parse_args() path = os.path.abspath(os.path.expanduser(os.path.expandvars(args.input_pcap))) parser = Pcap2Emulation(args.device_id) parser.parse_file(path) parser.save_archive(args.output_archive) fwupd-1.9.16/contrib/prepare-system000077500000000000000000000023211460375044200173060ustar00rootroot00000000000000#!/bin/bash -e # Setup local system for running development version PREFIX=$1 ACTION=$2 cleanup () { sudo rm -f /etc/dbus-1/system-local.conf \ /usr/share/polkit-1/actions/org.freedesktop.fwupd.policy \ /usr/share/polkit-1/rules.d/org.freedesktop.fwupd.rules \ /etc/grub.d/35_fwupd } install () { cat > system-local.conf << EOF PREFIX/share/dbus-1/system.d EOF sed -i s,PREFIX,$1, system-local.conf sudo mv system-local.conf /etc/dbus-1/system-local.conf sudo ln -s $1/share/polkit-1/actions/org.freedesktop.fwupd.policy \ /usr/share/polkit-1/actions/org.freedesktop.fwupd.policy sudo ln -s $1/polkit-1/rules.d/org.freedesktop.fwupd.rules \ /usr/share/polkit-1/rules.d/org.freedesktop.fwupd.rules sudo ln -s /usr/local/etc/grub.d/35_fwupd /etc/grub.d/35_fwupd } if [ "$PREFIX" = "/" ]; then echo "Invalid prefix: $PREFIX" exit 1 fi case $ACTION in remove) cleanup ;; install) cleanup install $PREFIX ;; *) echo "Unknown action $ACTION" exit 1 ;; esac sudo systemctl reload dbus.service fwupd-1.9.16/contrib/qubes/000077500000000000000000000000001460375044200155215ustar00rootroot00000000000000fwupd-1.9.16/contrib/qubes/README.md000066400000000000000000000162671460375044200170140ustar00rootroot00000000000000# qubes-fwupd fwupd wrapper for QubesOS ## Table of Contents * [Requirements](#requirements) * [Usage](#usage) * [Installation](#installation) * [Testing](#testing) * [Whonix support](doc/whonix.md) * [UEFI capsule update](doc/uefi_capsule_update.md) * [Heads update](doc/heads_update.md) ## Requirements **Operating System:** Qubes OS R4.1 **Admin VM (dom0):** Fedora 32 **Template VM:** Fedora 32 **Whonix VM:** whonix-gw-15 ## Usage ```text ========================================================================================== Usage: ========================================================================================== Command: qubes-fwupdmgr [OPTION…][FLAG..] Example: qubes-fwupdmgr refresh --whonix --url= Options: ========================================================================================== get-devices: Get all devices that support firmware updates get-updates: Get the list of updates for connected hardware refresh: Refresh metadata from remote server update: Update chosen device to latest firmware version update-heads: Updates heads firmware to the latest version downgrade: Downgrade chosen device to chosen firmware version clean: Delete all cached update files Flags: ========================================================================================== --whonix: Download firmware updates via Tor --device: Specify device for heads update (default - x230) --url: Address of the custom metadata remote server Help: ========================================================================================== -h --help: Show help options ``` ## Installation For development purpose: * Build the package for fedora and debian as it is shown in the contrib [README](../README.md). * The build artifacts are placed in `dist` directory: -- dom0 package - `dist/fwupd-qubes-dom0--0.1alpha.fc32.x86_64.rpm` -- vm package - `dist/fwupd-qubes-vm--0.1alpha.fc32.x86_64.rpm` -- whonix package - `dist/fwupd-qubes-vm-whonix-_amd64.deb` * Copy packages to the Qubes OS. * Move the `fwupd-qubes-vm--0.1alpha.fc32.x86_64.rpm` to the Fedora 32 template VM (replace `` with the current version) ```shell qvm-copy fwupd-qubes-vm--0.1alpha.fc32.x86_64.rpm ``` * Install package dependencies ```shell # dnf install gcab fwupd ``` * Run terminal in the template VM and go to `~/QubesIncoming/`. Compare SHA sums of the package in TemplateVM and qubes-builder VM. If they match, install the package: ```shell # rpm -U fwupd-qubes-vm--0.1alpha.fc32.x86_64.rpm ``` * Shutdown TemplateVM * Run whonix-gw-15 and copy whonix a package from qubes builder VM ```shell qvm-copy fwupd-qubes-vm-whonix-_amd64.deb ``` * Install dependencies ```shell # apt install gcab fwupd ``` * Run terminal in the whonix-gw-15 and go to `~/QubesIncoming/qubes-builder`. Compare SHA sums of the package in TemplateVM and qubes-builder VM. If they match, install the package: ```shell # dpkg -i fwupd-qubes-vm-whonix-_amd64.deb ``` * Shutdown whonix-gw-15 * Run dom0 terminal in the dom0 and copy package: ```shell $ qvm-run --pass-io \ 'cat /qubes-src/fwupd/pkgs/dom0-fc32/x86_64/fwupd-qubes-dom0--0.1alpha.fc32.x86_64.rpm' > \ fwupd-qubes-dom0--0.1alpha.fc32.x86_64.rpm ``` * Install package dependencies: ```shell # qubes-dom0-update gcab fwupd python36 ``` * Make sure that sys-firewall and sys-whonix are running. * Compare the SHA sums of the package in dom0 and qubes-builder VM. If they match, install the package: ```shell # rpm -U qubes-fwupd-dom0-0.2.0-1.fc32.x86_64.rpm ``` * Reboot system (or reboot sys-firewall and sys-whonix) * Run the tests to verify the installation process ## Testing ### Outside the Qubes OS A test case covers the whole qubes_fwupdmgr script. It could be run outside the Qubes OS. If the requirements of a single test are not met, it will be omitted. To run the tests, move to the repo directory and type the following: ```shell $ python3 -m unittest -v test.test_qubes_fwupdmgr test_clean_cache (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok test_downgrade_firmware (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... skipped 'Required device not connected' test_download_firmware_updates (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... skipped 'requires Qubes OS' test_download_metadata (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... skipped 'requires Qubes OS' test_get_devices (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... skipped 'requires Qubes OS' test_get_devices_qubes (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... skipped 'requires Qubes OS' test_get_updates (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... skipped 'requires Qubes OS' test_get_updates_qubes (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... skipped 'requires Qubes OS' test_help (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok test_output_crawler (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok test_parse_downgrades (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok test_parse_parameters (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok test_parse_updates_info (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok test_refresh_metadata (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... skipped 'requires Qubes OS' test_user_input_choice (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok test_user_input_downgrade (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok test_user_input_empty_list (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok test_user_input_n (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok test_verify_dmi (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok test_verify_dmi_argument_version (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok test_verify_dmi_version (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok test_verify_dmi_wrong_vendor (test.test_qubes_fwupdmgr.TestQubesFwupdmgr) ... ok ---------------------------------------------------------------------- Ran 22 tests in 0.003s OK (skipped=8) ``` ### In the Qubes OS In the dom0, move to: ```shell cd /usr/share/qubes-fwupd/ ``` #### Qubes OS 4.1 Run the tests with sudo privileges: ```shell # python3 -m unittest -v test.test_qubes_fwupdmgr ``` Note: If the whonix tests failed, make sure that you are connected to the Tor ## Whonix support ```shell # qubes-fwupdmgr [refresh/update/downgrade] --whonix [FLAG] ``` More specified information you will find in the [whonix documentation](doc/whonix.md). ## UEFI capsule update ```shell # qubes-fwupdmgr [update/downgrade] ``` Requirements and more specified information you will find in the [UEFI capsule update documentation](doc/uefi_capsule_update.md). ## Heads update ```shell # qubes-fwupdmgr update-heads --device=x230 --url= ``` Requirements and more specified information you will find in the [heads update documentation](doc/heads_update.md). fwupd-1.9.16/contrib/qubes/doc/000077500000000000000000000000001460375044200162665ustar00rootroot00000000000000fwupd-1.9.16/contrib/qubes/doc/heads_update.md000066400000000000000000000026721460375044200212450ustar00rootroot00000000000000# Heads update The Heads update was tested on the `Lenovo ThinkPad x230`. ## Requirements You need to build and flash Heads ROM from the [3mdeb fork](https://github.com/3mdeb/heads/tree/qubes-fwupd). You will find there Heads ROMs for ThinkPad x230. ## Update process ThinkPad x230 is now the only laptop that has Heads ROM in the custom LVFS storage. Nevertheless, qubes-fwupd has already implemented a `device` flag, that will allow updates for other hardware. At first run the qubes-fwupd Heads update. ```shell sudo qubes-fwupdmgr update-heads --device=x230 ``` Press Y to reboot the device. In the main menu, choose `options` and then go to `Flash/Update the BIOS` ![img](img/heads_options.jpg) Decide to retain or erase the settings. ![img](img/heads_firmware_management_menu.jpg) The tool will inform you that heads update has been detected in `/boot` directory. If you will decide not to update, you will be asked to attach the USB drive. ![img](img/heads_detected.jpg) Select a ROM file. ![img](img/heads_selecting_rom.jpg) Press yes to confirm the choice. The Heads update will begin. ![img](img/heads_flash_rom.jpg) Wait until the end of the update process. ![img](img/heads_update_process.jpg) Press OK to reboot the system. ![img](img/heads_success.jpg) ## Test Change directory to `/usr/share/qubes-fwupd` and run test case with sudo privileges. ### Qubes OS R4.1 ```shell # python3 -m unittest -v test.test_qubes_fwupd_heads fwupd-1.9.16/contrib/qubes/doc/img/000077500000000000000000000000001460375044200170425ustar00rootroot00000000000000fwupd-1.9.16/contrib/qubes/doc/img/heads_detected.jpg000066400000000000000000003060111460375044200224720ustar00rootroot00000000000000 ICC_PROFILE mntrRGB XYZ $acsp-)=ޯUxBʃ9 descDybXYZbTRC dmdd gXYZ hgTRC lumi |meas $bkpt rXYZ rTRC tech vued wtpt pcprt 7chad ,descsRGB IEC61966-2-1 black scaledXYZ $curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmdesc.IEC 61966-2-1 Default RGB Colour Space - sRGBXYZ bXYZ PmeasXYZ 3XYZ o8sig CRT desc-Reference Viewing Condition in IEC 61966-2-1XYZ -textCopyright International Color Consortium, 2009sf32 D&uJFIFC    ++&.%#%.&D5//5DNB>BN_UU_wqwC    ++&.%#%.&D5//5DNB>BN_UU_wqw" 7e@%P(B,R,BP,"\,(*Xe @@ PYe@HM A=Gc鏘Ppzs:!ɖdiGy=o=#=C; ם@)BT(!`,AA`P X`U@AQ[-RJR,,@PP,(*(TJ,fny=q93,|8*RXZ `)  ,* K Y@"RQb(E* *P `  *PHPP%P!J%" @ (   ,,[,@, B%HAUeE XaT @ `(T(P@H(Z@‚*,K ,Bl :D* l,-( X, )VQe@5(Y@*Šk4%€ EYITI@ %"* H"Q, A$A(P IH,A, @KX* BYEXT`YAIAAH Q,% I@R- (@@ !K)JCPB %@ X, HB¥e !eE@Y@5%P RYBX(@TRYVP J @dԊ@D+3Q`M+IA* @,",KI e%()I@(JI(%ZPPe %RPbRI@  ,),!H, VKʒb, ,R")` "(-2((PPX P@"EQVU ( ((Q BPJ ,JKD*D(@, JI@ P*"% `@ TfMB,(%(I@AIATAI@R(,PXe !e R (YF՚%@RP PD(KDDJ !`V, Q,*" (Z"*T( %P R(e(Ae(( dK),!j*-ZX(-"@I@("@"J@ʄD*@*"˚$* * , H)aH*(ʉ*5͢)%*U3h %RX*Qe(*P@l `(@A(%Qe.UT"@(B( (Ȳ@D*@ 3%MB([r @PJ" `KR,P$"6("(I@("P( ,(((fXP(j3h%"*I)$,$ @K*$"-3A,Y2E"` BD%*P"(JJ(,Z +LB-3jU"T"-"S6*%"T%%UB*#P IAVXP-Z""A($̰*B(BJL%u @ ,EI EUXJTXJDI*ejBU*QQIEQ*DQDhEheeDZERŦmFZ,Q*4E*ڰ*DEX&&TEheFhERXIeIe,YE&TIDQ楊"4 2!JHT @ @ K@ -o TQEEETRJY-&f(EDfeeUFmlfћDnVZFmћKDj jj*h DFDQEQ IIDeeIa%IDP DXEDQ,QEAc@ Q@RR  @ "( UHUhLHAj)"MS-$"ЋL22TL\"- "- L[W- ږ[HU-ͨI-2C*\ 2P,C+ ʋ*$ʈ, 434$*\t'3ssl ID)A**%PZ"-I@ZH%Hҳt3h %BM 66 %5pC7CC- 2r362V00qtjYLؗEͨ #P$2$I(K 5*MdPʈPP"J5$8lQ)(bX @T-HXDRJmijeEiVZfڙjhEiZFцmmVhEZjhefhejjFnіQZjhfj j:TIYjhfhfjER EjTeZƱ95ԕ(Q`RI@RR 1~;?Wsl'Wgagr~"~~'~R'|ދs'ſc)_V1od9C)39\*@%E(}K;K,J ,JK)Y$2KCMzC=dy2bnjHi({<2tdv88!/G|}[؟/|q_jJj]Gٿ/ǧ|}i|'MGҟ8}8Gls3Q37+3C+J$22$Y42J2ԉ,&upwXIhXE@5y͝ydQEQEQIIFTEhfQ&fuYє:9 a&eu3~$(w5dI2)s3f$\ʌ,gp܌Ms4\"*Ym\Is5 $K,,=;V~޸:g^>VT@EK%$.VTeEXFUg}׸!e(@(P)%OoҺb*Ȣ("2I4$""M 37wC8"K+Ӧq},cCό,_f_g|oCOGHcߓӼ?,3y34 \͕b*YB,u%KdD*Kfîks} N&E:ZdiheFT[QE.eF3x|Uf RP@@ ~YӜPQDQE DU%DQ%DQ FT:Tk@HI{y</W|C~;~Ht'_=}9a/?8'~6<>@?0i.&lKjKDfYkT\"YR>g~Kãxʫ-C--C*$PR22R"BJ\MH}2|^z^eh @(ZUU%=O,( (("42P3J$I5 ʫ+ʫ3C3P2 M 4M9|jQfCW3p󛑌d\MHw r\*\"-*Y4)d 35fgY$ʓY$nKO;heFTjnQ&D.f31 2ށbRQ Y@%Z'%?CNiu'{y]8޺QtOcą4eXI- 43:e@3taDf%TIL+3g:F&g;o1fjϦW3R153Le\ةdgHԉ,Y,3.I, ,2*Owx癡EQjjiQe.f䏋N{"= DQU4EEPY( RO~sgNqG+QyXG>O/o-tCXO>xrznuI_Cx[|_.]Y>Oa/7Lo;̽/L5t|}g'oM9z4^>Otθr'?o.gFW|˓G>RcrQg>}2a3.3bo&fᬙKjK,TI,:&:seflXIX}O5Wk;=Q3IiejDRijk&f.~wo/Nz^b,( RԪ@UP~9ӜQ( EDUDZeDPZheFZEQabzHV:ыfcaؖHgR=,ƍ(\R{zq%ʲ3PPw3PLr\MCPY35%35%ʥ,*15 dY y3J̰J3({#;~WueIDQTIfDXIf3K>nwLkl BR)T R()@@Gﳼt)KPE DX%FV DXETIDIDQe;bzd33Pw%wy35 Ms5#3Is5"M%R\2 f&w93C9IdI,$N._\W3>(( 5$ГPP\dLdw|5jL6(%KK(RزeER6kXvX?O\ytX從ĎܷC6zr}=jy^|YO.ף9sk>Kx\גPJ2(*2y, $ʙ35 yLdLC3Qq7173R353Ip\5"MId ydw9Ly3,C22şk>zOoeEQ%a`eDE.Tg;bY/W^sH:l( ,VE,X()*سD@A#>mWGY,VirK霢R|hI}HswX`oSu>8w){5v8nQNQž<'Κ`ndiYΆZ=a}p1\לϬ9=sϬ_'9%K17͹bo+c;Lgy3IBgY'سGI~'gNvjTIjjQ%hQ%Qbok&<9Wj,PZ (l(e(t+U~|_&ies{~:~=~5%~3__~~-QߵGH~1(~9)a7߬ߪ埩G埩[~~j~~elw1we'ȇٟa_f|h}k?f|a_}t}],V/|Q|/|_9A|wN"N(v'\9a9aӟj{g`|ŖQQ&TeDIQ+1CK=|y!H:iR 5@URKRl(4UQf/~oܿ-ԧfhfEDZEDQEZQEDj DXQ@"j EǣS>SuuÑ8d8fSvCGPuWT^gJ9#tXt#<^<^z<y1o+lBh,)J*ШJ*)WK~~5FZ&ZheFZheZhem:^+-#3pBM 1t2C3c3pc X_:nϛvvÎwCv+wCwt8pvj8vf83_}iTuI֎IuYԎYYYՓ=RgNNitÚ{?GA'ȧo?oSYf,"(,+%PuGqҊ]2( AKK4J%U MJZu~3~eLeeZlbeZlaaz y뜥yVOQyjgZ̔e`IDIKdIXA,Ρpo{):fQAe DYiAQfEQZIU5~X+qĹ~=xO7z;.laeZ.ZhfћFZheZhhhhlla~'zn&OqqNq^z;aSpG/ww>}Ї~>s澌>tPp>>:γ}}Wҫ, ! ,R((%̰1(+3dXuYzf(-)Aie*TZRBХFR~ ا}ϛy_GG蟞~v~~}| }|3G|~| ~~x~~x~7 ~t~~~t~,x~~cyi_8_U|_*Y|qG|h}/|8}q }K}/ݟv|8}_G|}ߟ/|}~~{'>">wY\'7>,,",%ґR/\,R@%ԵSVMgBHTBl?Ue r4* ܒԒt#LR B (K)h@H"((Vh̹IdY,$2E3dI%LPu u 3d1OЯ>r`)` `.fN>ϙ/=<9,T\l)R*(J*eԣR>=% D,*C3q37 *37 4BgP@PPBVlu"KK ,ed3,$$sC9fl Y1.Ld̰̰}_wrXID,@%T) d%3j}K=1{D ;) (V,,MKMKK*[4JR 4*t-Je "H(C- 2Tc S OAoAs^SyJyRy=GOQ/Q=/js5Ӥra4s==a=aq:!:!ϟzӥrΤqɞ{dp3|gI~d9>>>>~>A'o _Ou~z\% @%Ka%_fuKPvRAQJ)lPIUh V$/'򏥯/~w9~t~~) ۟iig]i.M)6FpSqZrSɣ=^T`ndjM&Zlbz<+OE9gXwS>{Ϧ>[>/O>>>3Ϸ>p߳OI~3>>;>Dϰ>;O>D돑>>>F~>;Ϯ>Nz,z\Մ)%Q%`KS\kt ^jDԢX-в4KK4,гTvMM JEMn}r^,+N+N-vlvpkp붜Np|w {{i GWnzzӪsr;q޸r:˞; ./.3GqgpGNqN; .;\33p;wË]c~,$K@E>7~: /^tU-((QibJMM jj - )5i-ћZFj˛tbKDҤZfhEZADQhE`)E%ZEd(D @@@a B DX" a%bRX3`QI` @IBTea)>.}5URךJ,[B,(54, M 4KгECSB t5)l(PeP(,( @@Q )ED(@- `I` " `XI`"YRX% EHD!eDRjDfj,ǧ!w˪XۙEV)e(iT5Z)jҋ45)l5)jJZSEM@ **,$ J,PXE aX@Bse 3D@I` a%JA( FUQJ_[LoD(諷5[jQTYVPjjhYKQKThYjRR.١KBRKeB XVPYTPP RP(Q(PPJX@KKK,$B , Hb, MH+ ~Su%}:wPeUJh*h4%jJ+BR[4[,[4Z5)lв(Ԣ Tl !a  r%YL%HUB XEd @ DTBbBQ%,g߲VuiXGӫ K4-M4J4R*,54,Bt7 SDh]KRZR(MJJ(((@)(,(,(@P  e`! !RX%YR Q%,QEDYTIIDK%O[Y1~SZUTPIQTUQB)ERRKe5e&-QEj AB((eY@% @!,",$B*JK" 3AX(H R(,"?)񻸹E=c]=|QfZUQeATVTU&jh%hYKfQF- V-UE Rm %@ AeBPPB@HKB  @KB "T",T,*%W+K^}, fV/j{}z,.Kܬ4 \7r7qM\Sw67|:nF,kXSlW4444#lGO{͓a~W?A9~l~C'r~'_ΰH5̿2PqM4sD??X^?.z{c?%Sÿc<}=&x!/{Qsu9[v8evstLq7~OxrVPî;>Iȑ_q+~3o!Yw$ퟌi~~23RYw}|͗>~4y@`*R)` ,B"-SKOjx:)#kj>mcO>K:O~h%wDC{y"vq~c=?M#ޟo'~c~C?gk~ӿ@( ("(`H(LLA})}tx=ǃ+pS>>c_~>u6>{ 8eKm%ߔ=<:u;\CR}wLJ|U}'ia_g|#>8*口>uN뜣YOImUx:s<<7ߞ7ouͱJ,XO^O'c@(-2TtzS<{E9#9]Tuv#K9#w@{+ ] `0rCOt>3珡8|uQg|zL H,B`-~E?_oϿ}=wo-BE ,Hzz2tgCyOi.؆؇5;5F)t|]13Dy4^sxF54||[|)SjYZPyW=0x^*r ꜵ:\á_w=#yGLA  X*$I?KsǞf큖ZhezB,0yK=o_g=&i4Fy=s:;lpO>[_9>M#]P{HzP6ŭ_9'5}ף{mx>>P9>l>C>e>|.<,G:Nj#R/^3q9>{x%P(5( Y 24=)bz'wNN))J1QX|Z/̿N:p'5釆G%{>7_D>-K'ڟ_^|gY1/{cxU7Ku4}kCƷ@J% CL240* R-2<ޕ|tuxݴwN 8]ÍڎGXE9_+ 0y==8s#::(rӢx+==1@IDZePUITETQEQRP:#ڥ[,@5+6crzfYәO]+>:=鑦Fن6T()&y=saΡ3s }Dž'/Fji=gyK=^C=c=^CсjAJDQDZEDTERH(K)a PTEJ @ @EDY7v~^G-񳦒EDPj>އSo'_tr7^BR 1_InbmAEA`XheaiVQg'H % @!H("@P,Y@(%",,,,R((("%(Ip_dt^͢(Ռ62_9賅~_KLvspD͢-2dv=C8yOxy_JxT򞣟C- =O;>hJ#ry%ś1:)(E ,(QUED E`(J,Q( E@ Zehe[lbћaY|tOtNaxٮwk׈u<) 2<Ҽ>C5RMB/BrލG.ZĒJ. z^=>.򳷆znz_>|ߗI z<ǡGn4Lt' E@EQ)@Q.mQEVD),ɶ!)q9axGkv8\.j{zLHTLkz7<4֥55M:/y<~dY4(M~>3Ujś_Tf+R?1O/]%1X{7zyGXv } BP>g؀HOe"LMqnlϭJ @DQR!R`m=Cw<:o(鼃:s==4DnOZx_jsǃޞQoAwL63u6oW21=ĥ fgq_{~oϯ?'}+tq}/C~w.|-puY|&/=?~|#~zN>]OMx{RoV>G|}n.j<=A:EЉ@Bh#RCRP^{Gzz0=&)4=&fѭCΎN0k^шi,%[m{<{z;Kz#TЅoSo}]GVheΙ;:9MQB+;NZq^Vw~C{~uVlW}_KꯟgsLߧ:%7~VytuVJié|}*YeVt՗3BMB,- Ϸz{~:isIǧoWn}1#^01wSӟ\"7t> W%:o(sS>#>z'N7yNGT9]8ӣN N}>}K{#x_Q"4zGJ^ |}+;?] ?7X\GW q{zeͅC`zEޅo3gG7IIfID/%^.uK!DįN?~t^":rg;g;^KsgC98/hx94vӓ}tὃNu8uefYYhwZ1 kpzayKS.z<dz1! FZy R(M,ELjtlEO@A>d^@9]; 9iY E yãZ"~ f @BD)-вPpydD #>AKB+K)PJj(%@6 !"01P#23@pA`$CB%4D{!BBU (PE*H <$I$I$BU B (QJU!H#̒I$I$I$I!U D(PBRBG$I$I$I$O! VBiCL45(RA~ʄ! FBQ 3L(PRH#Z{$h㫴3?_e{*br9MͣͣͮCjllnA/4i}>݌Vz%jEF6) r S1[i=%qN'M2F\rcr3K ъ̙sU#\qd䈪i c\!kōF[!_H/OaO\O3zlb"oXWw<*>f>?WkNX|#UF*U9!]g bf4^#<7='F!7 &D˗_vq\jhgZTOb~qRUDrU%aSrUejUG+]W*eHbs~YLۣDi3V ~7pQefDn}sZv_CI&iˋp5fb;"&'{C}rzhś8f (}؃ '?OF$2$;5Z&7) GvYqʖoȐLdu+~Y_bj|pge4rEǧS&2hT;Db~:bJCA C5ٍ^+)ӃlZAs3\cp܎k߾}} TG*'':¹UȎrLuEU9Һ#(Uu1݈ZkW9VUXEWcQ^*TWZɐ_ș|R/̶}/LfrxS&%s;Q][WSܷcN ~:m#1:_~;0h=:8ǣ˿)w)w4UA^IXUMl*v\F_#Q^*s;ysF s}i''{ \,{ F90hDL?sEG'nʾ41ݙzczqs(+1Z+a*Zn(;+\WB.wQ͌u=4`at1QZmr cS'fEd+W/זy|Ylov',r#jBkjؽl01e"8Us~8/eAoU ;͈ʊ+9+ܼ찪A^%eUT"+G=U99Er\ST5ʋ1'f٩[ۃ0Oaa2xA.Of743W}Sٸ?Ҫ/7Hn!Ct MMM 3rrqc73qn1|F#_k5؍lF#[j5q\F3S_|eafififPϷDsGD=щ/A{g_{z4DI^$GG?|s[)l[)|G5\jqjkƱkƲk!jUMF5j4a./ -2c-Dk#q2,,,,.к\r˗.\bYK)e%IRYĩ*Kq.&         #~#y'>#!?8ϰ߷  *U RH*AAA1x:3x4131n~c9q\ᾞQ 9AAAAwhAAAAAAf_lhc'qKy0AA:8_/DpAr#ATRJ*TRJ*TRJ*TRJ*TB (PI{7x{B Iiѝ\      *TRJ*TRJ_דռT#1Oi:2c&Fa❄79o8syÛp79y`7 {`7 {`7 {7 }`7 {7 }`7 {`7M{7 }`7 `7 }`7 }Oz&* {gp& op& op& o& o0 0ooooo|Co 0|C 0?G^|s7r/=*"+b+")"c)2+"+2c)2+2*¬*¬*Ҭ*¬*¬*Ҍ(Ҭ(ҍ(ҍ(ҍ(QBQ !D(HC|Q !D(BU !B !BBU T*PBB!v;cؔ% BP$,bŋ.j9 9c٫}0G(!Cv;{} 3 ~_Il'qK ĂSw;^ K'9 Z[V&V˹p!vErȹш7xθz~fG ?A1ebKÔ A) B) B) UJU*T888<<+COf>)cyω>'sYψĸ\A |C>! &7IMo}ě$&7IMo8wě$q7\A nxqg7|s_9kf5s\SS)|YY众w;AAA*TRJ*TRJAAAT   *A*#kDOaʰ V*AJ*TRJ*TRJ*TB (PB (PB (PB fififiFihifififififififP43L43L4fifififPP'q+*TJ RJ(PBJ*TRJ*TR *TRJ*TRJ*TR *TBH *J*TBJ*PR *TBJ*PBJ(TRJ(PB (PB *Gk` H           pAAAG\tAAAAAAAAA PAA{7/zBkY\vbαb/e]K1~-|Kg7rb,9'OZ?%Ww?%Vry(+fYV]'7גV1~C?gY;RrCt򑪢aS/16nTS_#I>|K+XľGtןW'ùT|P㗿*%; IB51؍|&dG"9f7ٍsy9kf53[!9UJB !D(ZCHa <'ZY !d.к5 u.ԺR]K)e,I$I$F5!S IB] \f#q`7 }>!Xω>&|K!s7A n3\K܅!H Bcv;I$$bŋ$ĒI$I=E] \f3_nqFo1fߛ777 c}7ysqj52y/L/GC7p9ȩbRRQJ)B 2BU B|Y !viQMFjkƲjUƣ$%H%%I xպ溚kqUƣV| \#k"eg%NrI"uUc>Y%]MD5P8܊n]$I$I$r*(Mƛ%44#I $4iJHivI=rI$XBŋ,YK,yٴzrTzxrT/WF ʎ(p܆W*T4{&G6I$I$}r*TjifiHiM6iVBBT$I%XbI%I%|,YK!8<Ҝս8% !d.MD5PCU cX/<dK+G"&8Uʽ"r#Z֣FR+X׺2I$I$!HRRQJ)CL43M !D(P'(I,Xbŋ,X$Jӱc¢ y'6ZY#:IRT)zĹd܊WpT5*RWdI$I!HRUJR (PBU v甒I$IbK,X$I$U<$EQSs鿔u' B*TRJBBA$"sUˇ2.!W(cG'upw;R#| DyA!K0KCP5 E5]Y众#܎m8#uG(שD谙 BY 'Gc(KK4KCQ CQME.,%s A*TH  }ġ)yGBL,'~c3^G9  }v;d,.ju.YĸAJAA"LsԞ,jEc{̸(֫|mz%p`n%3juqc r5,Xbŋ]BP,B桨j]KRTw;  #uHRU$*ڎ{={Uز̭n n^zߛ#v>1in~Dk |-z#zEsvG7[T*sՌfLWLܱZ6^j'5=zE ÒPXbŋ,YIRTw;AAAG(OhڸLN\#oCN ~cոSʌř/ +Euoʪe/$1V1S#_ĹwB* {꩟**}#O~oA^h=*"I$Os܏+J,Xbŋ)*JsߔAAH  :媉&,ÑԣyWp1FŏƌwC[*cp4mv ңF_ǒcxG\ەf$.G*jyON'zyGTrw;R*TR'9$$ŋ,Xe%ISw;PAAA'( Qf4L vG`bީɶw nlm˯Kq>7#x";Ç"UzwN^yKQ|v$Xbʼn$T%Nsߢ  Auvd[\/aּ".MLD֣yf\qxNǓ^KwGcE]ɾzZ5s. >Ӳ Ҟb6z eoʁ{+yGGcؖ.J)$%$I   uI>bFo䨨J'&aŢWcᱫm2WjnXֹK'W;84MW*zo,ny(n䞜v澃:5TVzYC\<_Aw*7z #b,?t5 CPSQMW,rJO\) "   N3䪉(#$nٍ3c\OQ9bLs?F-u(َ{s&n˧z}U9"ǐu@wޖ܃`XδQ*oOU=y;:#˱(;Kt.r9$ w *ARJ*T A'%O9yqDUUEj[gdc{Y+zNY4[Է&uN\C՘2a+gu(YrDc^)|ԩv^K=:jBAgA&K /Δ$U9-uv% !d.y]Ku.~H) U\UJU*TD*ҭ*J(QJ(TB!?P,NTWbpkqu> 5xDgbxpe~jשFҞD$"ކ;}ts:IT t.кrYI$I=0TRTJBJBŹw RUB:ZYY t.˗RKq.TUJ)Q 44QPRJ*!d(J BYIq; Hd^HM-D䊩娞b :谐/!=\}k7I$I$I_5r<}|yB!B!Bck9yc'cY6R/4OcIq|(Btؿ1c1c1c1c=[N[c^[^[-?l-zcaQ2y4iԤyTk.UR',\:pA8O1iԊE%dRJII)%$B )`( ŃLiE"IY+%dPB AH+`( F1Ѕ"IY+%dRJIB (PV X+` i^[^R!8jD!R(c(BFxcQB!Bv;p1c1c?Gc1c1c$B/ çV-<+B!Bb%",X=,cHBgba>h??2H\5tB- !01@`APQa2Bpq"?:R)K)JR)KڊL0F#bLLLLLLLLHB!1BE}E1oҝ5t#?G.c?z/8 =L硫eBnQk|Gc6 =Qπ~ƞz-}lo3OcQv:wW ٓ2bl}^"coɱd&"|rqv/o64dD_D$1F(S~=uػK_JR^┥)JR)JS")JRb_N!M];dd9u\B!B! }R.R)JRiKO9bХ)JR/k-R/mc׵{`0F~#`0F#`0F#`! ݬ}^ ~ ӷh법EEEEEEEEEE_eE_eE_eEEEFH)JRWsBDDDDDDDDDD!N!B!Byk~Zyn-寯yM_'{O&{#OO*٣zjlw}ITUd2Fh4fљ2fLɕ[zEE_e_e_fKi2Fh4g3ٛ2flə3&dj#ǵ_cNGj= ?!M!B'uJQ=F\KJ]b܄B|*lu>l]'6B.|uz PNe)JRH[ޟ Bl!m)JR9|c}uB12!3 APq"04BQ`ap@rb#RS?2D_i"${.D P P"Y ίo["D^ɓ&O!]ě hꮴl6 H jJo&&h@H,--CiA]nvhwIK4T!(X*f"PB:+vF؊(m*䦃 _] ̾S\&d,-L.d8hY ̢q̉Eə8_l|MXXdg њHf}M; E ?2_LB(1J(eԆɉy8o9@]E"D,hD4ӢslnZ(j*M^*SKz e ₤v  b1ь!qV eX䯦wiij5K-p4UsTE:P!d~]zԒB+Mʻ3\lsE܋s3Q/a.]̚AEB9HG2!UXZ@4ZLȻDGLHys*DYKGs@ MVq2d" E9H?[>#$^O.erg yx氵u`[=4394,l3AH:%`s4ܫkr.Zs(P P R*RRRN$N$N; p6mہ5O>DUȨ Э Э Э Э1+n%mĭ1*LJĩ1*LJi䶧t;KOR((qC(v`PJC)\ W)J~ҎEwUjog|/eb*z ,HO]:LyPg_OB 1U񉫓Ojj6M} qv|qEaXEʅMV7r&o F/? ~s~TjApȯ7|d;+~eLM\ʻ5}f݇E\r*UȫW"E\r*UȫW"Dk5mہ5ہn p6mہn p6mہ`w;{n{n   p;D:2K:䂊%'Iv'I'II ?O$ ?O$ ?O$ ?O$ ?O$ ?O)~/)yK^R򗔼/)yK^t+5S]Ȼ5m"dJC.݉vKbPKb]ؗnȻv%۱.݉tKb]ةvKb۱RqRqRةvvvvvvvvvvvvvvvvvvtttttttttqttqt_\\\..W.W+CF F+J+*ȧ_~~~~~~~'vd̟qٓ;2}vd>;2}fOvf;3ygfg33ٙvfs;39ó0>ggɝ&v|ggɝ&v|q\d.2eO'q\:,bpB.]\ƾv8wƻɾ&L2dɓ)r$H-[uhDɷr&E|-ވjErCo&L2d%z-ĊrWrp_"D$H"D$H"D$H"D$H"D6 M16bMiSJTJS1+^d/re /_3ӓ;NL930Y30L;Sӵm;[Nӵihvr;W#r;W#.j\?/)/_e?нRiq F!_*^jM.]0aC P†,$wwp& IR`Tr*Wȯ_"yȼ^r/ RK/TRĽqzKb^ļ~%/KRbnJ݊;*v*TJk5:D$Kݙy?%e}_vWݔeݕe}_vWDl݅e͒'d쯻֧v[c߯Ԣj;ܷp}5k9޺Z'TorӪo {4*B+B+ Ե=uiB*B+BdͦJRRRRRl&TTVJT)̚!RZ'i"E%%%%$jL5&Ԛ&L<'̓*B+B+B+**&m6R)BYLJJI&TL2dɓ'ȑ"[>+"̩ Э ЬmJ P &i*+R+R*R&5=7,ɕ!R!R'i%$RRRRRI3̨JԩJI5]L2dɓԑ"D$Hɕ)RR&g+KU}jz{t]T%"E%$# H%$H,O_jdH"DL2}T^OG="^dN_oaEGB-Շ4f(dɓ'-" ~*dɓՑ"D%ȑ"Di/"D {7ɓ'"D|b*g{-tԆjDkTH{aI|TDC*:d}4"Q-B,#~[%,UE  4'M-"9=g>: %-[Nh?b |fʊ'"D%2dɓpD-ȑ/#tSfy$tULO4~"aEKH6,CF1X|Ӱ"*gDQWh52_sU&SIuQF0zkY+M"D|DH-Y'M|v .: d3'֯V{25! z)h\SF1q-!1Qa 0Aq@P`p?!|D(i[@9K"d!/$DݿR&OtSL<!dB il&J "dd2I$vB{_h%[={.;dhFb4'C@4 &L2 ĒMIQ:$IY~bD24HӬG3%4:-qL A _fI'RIIb.B4@ѧ!s&h%& xى7*m&$Ok?8s-~!ypZ5 y;*rɤOxp e8yN-~6'MDż|[;X^`| -[e<_h<89G'6?_~6cV-CfTVo+pm/>~Cqn8n} Z|$mF[b6j6QxCnb17FlGݴ~2)"<+lE ) #n6#v7$IѴF^Epҝ>Y9f4gI A;ۍO4/olgcj"AG?5+oPFGd;i 1gYsEfsr(tTgTnzkwcg: PK@4Z\i3A!AzF+R)AE )R)G҇%*Ы_#z 6a- "HF%#H4!{f4"9! "ŪHԡkNbs̶eΰAAAADAxK6yI$776xocf6n\co3Q5 CXޔ/H- e'1R7tyay "u{iR6qI _"]MdukKWDb(I\&CbKHb;vo$S$yi5^ĪDZc"0JDR6AF !ЍXi",qLXRNỲ Qv$G>pO/IY44/m-6 $u#]K} xA9R>FYTc~HyF]"Rep5F `ULZtj%,l!$/Nbc,g% ) K%Vg) zZ@DNܔbRj`?t!+Is%nDWtcqZBdb67CD0:`-hHy~/ufܱ+13IVYYq Q QqWCsf9̂N$E2D9m> ̠8]Lo B!*?6T@}$,n 46'M$dow o[<\K-vL !-zKRf >gP0lcym/"X<F+# d6pD4qka#~b⤺!\Sp8 >'?>åI$Q<7ZP6`#f7M[ $ 2y #c&x"JRybzfBb,˦ f7?B qsa IP-#mKێ2Cbc<ܖY6c,6!6c @fF#̈,E8|GKndZ$i#xv |k_=i==kǽӣQ[Hm bo=EѴyVG ' f:|W.g )<+~H1'>[ E4?T=:l%fe@pBix9 \bX6\ƛK;.(a,F-!gU$lqI jBdcX愰|Dx¯o³Z3qGEFx)Z nţS-A]6@c3 P'H6ebQ8LYqL&u=)WE^l[MF;lu{1G&]-|]i`ڱS7"Á1`K E h\I>6JQoStMD $a)ERkyN<) ys[ӑyG^ tӣ##//^6jjƿwj}Ƨj}AgE3A4>=};Thc㱎;##~fDh,F'#!y?٠y?,LTC!C% bb  AAAAAjuWx`g$ Z U;̕U xs崣CcَvӴ.R~N'Yc!?;M k:;gσQ֣GLƎҾNLk{OOEd/n$2"D,hr?'D:>NN;h;8H93{NӯO,9ӑ>ޟ~w;B~' ~GQ܎or#? #)k[ F/ ehPlϮ)YJ ^̾e/r=v  "HGf;;B;B;J;*3=gevcnvc\έs:u#GKNu{j 9(ZsdZS圳r_""&LHg׹׹ 9!g׹ !^:G> "r8 KK-0!x1.ӱuʞiYR'^}wEN":O!׹?Cd>dgD#y)L𑽟CUE̓I2ŀ8qqqqqqqq߇vw5[5}wa#:n$}G%}+x밻 ДIDIA: uI@ID dI@Z-X^# [Q>Nt||kis:>Nis4z9}fG3K>M.gP:G>Nu|C'\:>Nu|{'k|[o|[oN; ;J;J;J;j; ; ; ;J;Z;J;J5p+Q;5H!'$f?(iRJr5sI;;;~Yz:tçM}:uw_æ^}:{uhuO?âH3:Gt?ê~S;~X>^ 9Dý~:uW=wts=65=y_#G}̏Ww//g|GWu#3>g[t}؟'䟢I#??????C;C;OcH?k!H}b?ǬO! inoq$I$I$Ԛ,Y,%gcz'Sۂ4#O?ς_ /؇g !Ѕ^2~F}}-GEGEGEB}莣莣?_DuE=CŽXBa^>Ekk_Nz^:u$OGONddgKzor{{ԎE]#ŴG!!}}}}['סl-^gKnfw o!| uƝbu+[Jd:J['+]^ǡFhF]{Hz%^ij}GEGUFO?_D&?|O ,{߲LNNK/_'BN~Ν|2:Uv߳_'BN(vut|3\ٳH cuK>a(Gƛ|XZA b8bu:FuҳgRN- 1)ڦh}uNd:it/S o;:uVwZ;N;Me7d]ws?vs?r3?r"Tr9NRyȞD%,dK"YȖD$H"D2d<ɓ'Vdɓd̙:fJ$*"!Aa:G'-9(yrO7::USSDs]z5NC9NS9w=.9+!ɼ@%!rEiz"ء.M B)_nS"@!S:Y~:D+DO2:C^Hlocᧆ} k OA/ q#!R$H#_$iXDA C"o7#`A`F4`-ED@AA AA)AAAAAA>=g\!dAQFŋmym=izZo8~@t/ ^Y븶VK$3G|GuCG4cӖWَJ`ҴٚNIJ5czn5Ԛ4SVC;LtC#[HnO|G>ti,"Msr _dI$I%FpF 8{\<٪ji<ѫH_b6,j sPUԨ9 L#r%7zgPyG'|$IIs?9V  QthI'%S/MԜgfkbi*Q"4yIhFD$JkĜMCX$L2d$I$I># 7y U)O$=w֓Տ9kG#o N3Fvj9,YC^9IhȎG)˱dH,fK2d$LĕI$DI$YhvـΕs&1,J%Ƙ|]4OUs=[A !i{Uc"`5fdg#1,s'!9dC"9P՞J$I$I$I$Ï Ƒ2$E qCsFiM-tni9PƖGBhQ5?U_]Dr;ލwRs8#12d!hBĢVDdC-I$I$I$bgؑ*)bBD@BD9zcR"%dJȁrmTI$$I$I;sPEdPI$I$I?BK `1ؒKn&mU#7j7p @??Cs`;Cy"$Hr6db|!C[^hK#\";o^ rD`K DDBK#@Ң4M#B5JsطH4MHGQf B̗X[P|@i ='Pw<dhFhF]5 'ZB5!fh(ih$dj!\hqj"1b޹hʃg4Ѥrr,(H+RKPC!4D4OciO2y3٭4{3$r AX" A 8GS)a~C8'>"ə=9NSZNBa8TWRI%Q "@6RD$L e' C$cFjB̅HiȎH9 m̏YYhtU+XL#@53BhRVDHظ'4u1bTK%.\Ar؍!^1Lp&8ܷ$&AddE}K,XJ%[A"D]HhfO2Ys56|4hFDQ$'I&c9H FJ*d}T&3&(6F+h"N 4+hBNkZC^@"qlXA&4/E"+lJ`&M#Sn!D΄Ar bŋ%mDd4Gc)! 43H!d@I$؉ b9\H BH2u d Jb#NN (hшk;/FHK'iRᰔ%!b2L bIPۋ$%ڱbĢQ(NM"DYrt4`H4&YؚM$DH"D^J$L2D2wH؊ĉdI0.$N'".[&?qbŋDI$I$I'b2 S$HÁ[DQ H"DYr"7 Yo'HFwBb7*'7`NҶK$K.\ H2d B!RǦi(M$K%˒e'8$8k%3Rǃ8 7C Sj #GbFjq'9B{nͼHYhG=RDLV-BO6Io03#B0]<4 5GyoR  5=NdNz-sV H%޵,g_rJ%/2#G9b[p#-XQt{> ԇeq m0g`In/T%r$K%)q,&LX `![GK X O F}9s)`Me$sN9i7\DZT.D'N1R>E"bupяY'טN!A߲1e[#Ʒ:Tc&C2ƣklُMV"TqK!B$!V%CPڂG!OfTFb 2{T!,X[&9J P| CR&1břk/q9 4qa59 j56"|kK*)E/tbqD{ Dˀ7#\EuoAObaCXɃw&tJVȱ *,1^I 4Jh_CF@I ]8kcĩ%C:Y!J$HD"j.HH1n?"!Nrie胐2%-#m2k#!1yn TG˸w J+G&+ڑP(5ZƠߏJ^16!ʎL7 TT0x1-7XN"v`jjb' KI`C!e)΄ Fi]mᰔ+- 46MsVT5Ms\&NbY fD4v`YBy %Gb}3@ !%KlSP5jҞVzn/Pmx*B|f%V8B@'xM 7DKDDQ mxl wI{ ښc]7#;>.wbcsa Gs瀯%d $vjl3Ph; 'Rl,HtKN=il$$peKQ|0RCϾ03a@!ڛ g1'x  )G<]$g=+[7O #4E{oI!% ,ByCc0 ioC'>대";<_ۍx$aύ| e - C9sΤq:/]B 1O2 !}A|*U}8I4i4X}1HO002z j !p6c츃€kXeZUqT~ ao1WA4PO*{v2(_e'H[ 0No0 ҉,z~9w0qIEnώ,:%-6G0ҤI>u}7 E夲žc ms揻* ~_`8m{S"WoHbFxs>qMFvT$, n!W]0r1ò57[}I,0Kcc4Ďc0$?SJQ g2 NtB%1u7/D $[[;=, D"ؤ9:7:|}0w<0< 5<tF:~ es=Z+qAiėu UrEq0ҵAL &ws,# ?$bC[-֖ӟ)SeCs$e_M~ޓ0ˉ3oA^q{g HK}P r|n<}ڀ_Ho$78,J<ڔ{nmI;Ͻ J@hxrYgRjejL a^pGXl)E8er&#bwo08;=4V#H[Ev[ADPJjF4ey&fXS _"@pŲh9, ?È0( w,f0 rk,>:[oTL'窾@ &ߒmrᤑÜ/wq_u^asz=~ PþY73pK$h/ cRN0C#F8J.}"}Vti[(Qɒ07 <hok@/ nlje@b}q<0+|3R 8U~ /倜hYGĒ (}EtA74C ?#aÕMwID< ?G,:Cہ--,*DeH#ݷQAI 0if"(n`gVqAEQQ՜}jQְ3C_ym?qFeF[媄WiwRARC}0ӄ0뺙(iEYWS<%I-/'p]OSsX]_Eh➆1aqԐAAA 3񔡣jg[M7閊,p M0piCZJ=4{/|Dq'yR궙zIyM3<MM8,0E$0A=? l-__nk40@^4'"i 4߿{4a%0 Fi8 0 0ֈ"8cNOj)H;Ta'-E( $lX`?4I08t4׆D( g3'߭:' [Q"gv=bFԈ'p z#,Meq It00032TfsH kA+JI,uwM3|85 =y ,8 rβc""!*`J`]?Ǘ˲, $,]c9k s0$AsY";(` gI{}Z hp=/,>u 92(@,L1uİS'I8r<?sF}m9ˣ)% QwG^^-ln4 01Mw/$ (CRLZJ$O\tJt> H/Mm^0-hQ -)iJ:H9V$z 2mEdvhGxp4G>1,<28*3yC -op4bS *萐;&D#J甲U$"A7M ,`S 8?0ϦI#[4|[C\rzQgsY( `#HcJƀz:Ad@t,θ}!l/U:PWC J>AI9]zleTB, MyI6Ȁ=ã <&sYKw@ }pqż,/ؼ~[,z%VQ]qR<{6oh<"^`Wn s!HN:t]>t>4?mtiՊ+b++(ɾ{Y.4~>i[;i.U L9RF" $4 ؑ쟖%HM !jџ uEpY exmblLT.Bo~Unw'\NBTTTTT\o# !DB"3ڜ>NP˰=]Yez+^((((Ez+%PֲLDDAA@ǓR踽|^2c_qw;h!GFFFFGx+^ WQEx(#(B=QD^=(ЈtEEGB#@.)p6 $I AN _R)JVREyEDAv/OON$?KnE} ;io^M##+@DB""#.iJR謯QsK BD"89qb}k;&x7SbV)5E)aQeQEdd!Mǽ 6mbI- [1hv 51r"=fmNixW,zXژM-M=^r6>,!{=lO*dX4Au7𛯁6  B&1f kny- bhcMv[Nr*6$T=Z% kЄ,4RᴓlM4*)M4j1CۙG5w,LkkBu9B%m-!F&(*ruteVAT\'U;$ұJj܃CcQ:%Яs;+mqУ:t_w4b)US΃*1L&jp'DyipHQ,":H)H IvGDw=<7j2 xz&G),ųNư>h۫\muC3dOi#I*&]nbWBC|7k_xc䖙,n'K z{1ԱSBA#6:R, Q%Cl7R_ vx=rhY]G:##'#,Vl~ C)K)K)J\RM)x)t8W[(,P.Fx վ)KR_r4 !44B!1B &.iJR)J6fđJR)QJRl!B 0pacY ĭ<)JRK)J7r20!LB !v=g.{B/5VVVVReeee+)YJR\z.AAKR]Zj)Qv3.&ܳTogs}OrS}Ϲ>S}>c}ϱՎQG|3 8dG$dxVVRJxxq-蟉К:bi)uݷ/[e* '2rK={#9ȎDr#9ds#cs#s9C9c9mI Rě/c~hZDz Dz8#Dz8Ďq#8zDzDzDzDzD^8"E/D!B!B`~ݗ ]W)KRJR)|GE|5ݹne&?Knd!<=-}އ؟'hN$[_(?5z[Us@ӫ(ُDs6f^./G9NSO+5r{+_7G!hݥ*Dq6r9O"DG)s+uuBAB"x4A2d{G1rs)s8FABG>ySsqlB~[zCu!x:/q$EE^B=6(|'Ej3/3df.rͶI)ҕA8(EM#~Wgi#fC"MkoqQQQQQJR)JRu##(I:*8DDB=3S/?BBZ)F#́E++R2222!B"""""Q{QJ|ޫWg!ɒgR(gAI3 )`Yplx&ZKmۅqL8]-؇8uuq8,XXXHtv⟡zr3#_ٜ$x0pmwc X !Ǯ:ŷAw;Fj[dr^X6 9c' ǽs38 x_[ 魶[]3ulmjpD /9miiu ]/8v-zT%&ߢ@៹?Z ~P"v@Z`n& $Ã&lͶ`I6- Q]+(oGõmÆxÇmש_p)=o |:Ǎ86;_Hx> z"vz/þNN:^]x3KxӍ6xٰ ,YJ^OԟHSu/DsԫCv =H2kj;E#ڎV1!d{e Ӧ l ;߆pl{>g^qXm%-xw?Wde)z[6mB>a>'Ǽx;,~7nN7fZFl'"#'%]=o1)Х)U3:ԟE{]Y=E&x'+&W cw+k "nnSgx˫dxAm6%lo Zf]YG:pZo rǴ^Lg~G;396HcYu))$"< ő\88:myξX[݅ /l/=mg Zo# Zd)?^SfNۦ?~CM=TZ Npvpl2q˹weI7%[{c^V7f߂c\xsC7\/ ,䳁עGc3ND>8|y99 prnجYֹw'e8-6Ӎ>_/!rW!լE%If"RrC3[\ߎaxV%-m8Yfrm]縶vׅܙR<|5Na,yG`x7̱*p6/䷁<,]Y2g pucȁ6Zm-plonvNRι{S{**]/M8ׇ 8-xn~ u0g: k2Β0rp? .EՑY\no:pNGGN^]ȮY-,N'nVٴۣsuh΋".dg p.dd7Kx-%/R,%pwYxskm{{޲t;^vώqpCix"^ =opr" -dbÞEu.㇆,Ç;:Iյ z%xrD?8l3LIKouS83s3-׍mx߃gKp?n}ͼ;@! \/),p]<L縳96v KX+:9g%%qpp0LAdgp'q8GŶdbδ8fl# 2+x'8X:xY,u6Kf;`SIe>J[u BmςY&1r'*Ìp3o|g!q豎G8,mx#g 2ÝxDEomθ-.2!9%gwǻa78ˤ3syLO|ixMmw'%erW6Y.C-್`#'tme7Y;jqÒ KLk+o9a_ #9^87#!>%vrb80~؝?-} l8 g #?DH,l9=#呅ŁYqvJ첼m{ܺ:xcK\߃{xIdl@YؽG ]s2޾CyiY9GsἋweeg{S66 pr Ֆ6wa"8 ^3rFz1:xxsݒ2$mZ,%.fBVql~-9ŝ8Fm'φ<3 2-8h]8M$3dq>=pnppLfYw!ap uc>ln9^z(Ѻ8Ȑ!GGxy dQ|g?Eǀ9wxHdE$`pCc-;gvgdll)+,eg:qˤsn.AdKݖ['b!YtՓ,߂Y1c njw9 K$3L:4E x9H:)Yeb|[ReAz8H,w/o9.SHLnơA )C*fφݱ˼g;1osŽCl^0ÖvG\2;ydZ83w'97yd7g &2G.Dٜh31dA Y gdbma10py8HCaFg˲K/smRsbF|hAp|bN myM88Hga-2~qe@>R/Q Ry9U=[%/RYAbfdYYX6s:m' rp^f7wV|ZÎl6fnɒL c!ɛpouw,׎|dh:,du<{Π ,0,;,9 R |z$y[lopgAYb.Fޞ=`,'[#^{ۮ1x$wp</g8;჌?φpFI: ،K8 7\;.q-cs9.AcGlAYqYYY2yN3> Zʻ2KƳdpyx1;jq70o\˩x|po$ cmf>a<Ɍ kYqIՖYd88Sa 99x 3B6|H8, }zK,-2r0;Ł\%0%K'$xW,Fc5 ,v[ 5н[os7sX>j9uO" e2'VAI'vrl#;YyN88 , cs|gkevp EpYg  `xe ,Y¼dB@òNa{呞`'Y8 xrx^I[N7yyX"ψe/ [weI^3wl'`"]$ 5eRH l1& , >g # ,`18`8" :lYe3X<3M<3<96 džݱÍ[/g!gZEx7ӍmnG&szcFxlxX)\9NC` O$.V, $, ld$YpIr 8 H:'c, Hck{ l3À#omp xΥD1; KY&YkeVw$#X Kx%'xc,Nq ng|`d 0,F `-nF, 8ųK,aYdLmaXffq^Alق;, l08ɰ,NO X<6%Ix,Qf7u᳒o^tȒ]!9gTebzWYg+p=-$2c=,qv-psܜd$9C8 O A݌or呰@f6Ywm,,:$$daa6A ;`@ RNlCl#s,:^=ZXIg 03YdntX`p>WEANd, 8Ȳ0 }ww. Fy#XeeqY ܺ*ǣ`8 "!Nr,,ᅐXAgg0V9 , !AԖeXYcRI:l2H@p'$޲B^V/ L'ōD[l{39 RUuᏆpYd |2Ȏ3 -dcead 6, B  ̲ `,y`l9#$Iae;edk@ ,,fw$L"0@$0,NlELy5v'[$ s2 F^!HHl2H2loHfkǤcXs%08~zgspqUj  YpdLx$ ,{!dAȉ2caa 2NdwLJ'e)eO 2nj[l4&l7Id儝A$b*됽Ha83)"w/]}tFW3acg,N2uoO)Lg6:9KH @@/h 0j8 "6A,`:$ 0yB5dH,`Yd - ,]IY$;d#N쌒IՀwvK9H:2u$k$Y,!$Ycee$Ĺ[&-r۫8yÀrx͂v~m[_VMk"WwJ?ZIO0#l~*vi$'#>O-%??Wh韢B^>l}T߹v/ή W_zi*O»RP!+ vG ;DF`Dd7221S0NAHg LBŲ!p,l1 FBG3 H29'\ k#gFɲ6la$$ gIL3`㵟հ1Xdy.[maxߊkk.[,&K.w ݲ xHe4VUr ;hίI}Ǣ4?JTΎ ٓ9'ɧ7v:Q}1yz8}J[:=z.hhzG7/ѻzo4h_A,Ϣv~E#ΥdBY:&$A8p `6K1 ^ / N+?WkHcn:vu'WtmЀ8,"Ύ2'O)g9ynq{du;'M,VO)1O d)zUM8N:I_dɪCW!/Y~ɩ%$Iܝi1BD$ԒX2L[I_L`_̋8?oC>}/_֏r ; ,`p].]lI 8l K ,`T;  ls? BN9 $G 8;Hh$jL ,M&Y&-M!OlAtu`,/zǹ8!Nɫ9d< !IdY$$3p!k ,KK($댒d8f|Hd)!cu^;$AedzRK=Ie%HI0dۇDВL;ư|XI0-l9~2 &; sx%IDHO8t6SHzƳswY qH,>$eԘn۵IVbWz螑!`5Q1֊C$چc1Y Qq (Cg"NuKfwm6 6Yc$I؄Au!:M9G725L;Gd$Ccr^/>юAH1t8up#/.,?an`>I,;x+<`h|f-g<آ #4 1FQ-|Ћ h-YU"ƍctr&ڙk4#:쐻ď0-ϩyŗ>aV:NrtnK}^$$`;^,><#l,JKrCrL# w :d,dld7,FH\l`ܱlעL;^N3]A1Yuecb S?'>,ϗ8`K$$ &H;dK;H$M?iOQDd?KR sH)LYH0N_/x?ڒUu:}Hg2<0܊?_0p(ʤSAIцo1 y?d?H^3 ?S?2?F}Ľ~n-_$Oٶ xW`뾟lS$͜dO0mld+du$LR:Yג䱻, skd`gdN زḾn0\'9÷vpp)AvrJ̇Y6YgVNYz N$H$$ &6C8NBR!wوW?$H:#єZ++:'zFBFXo/L?Ȣ7Wd@E?lΚig3"]hdg@]c1W+m!ѕ0A;~Gb?ӥ(~+f:3^zj\Vwa6C֬??q=bWnyoK^?^b jc~ := $rK526X%s ݸ`^XWg H'dL$Na=$,Y'oN;c^ -^lNS#6 ,wǵo|,pY9wpppXObAepudY& $g{s1;l$Nw?#ׇd-k ?&0?a7'sZL!:' N=KGLl$GB UFf.hPl"dwBĶ, ;,, ;ណdxW-&|A%ռg8w,K$=$A﬜l$1fd ⛃&N膳{Gc8.k:8-cǓ3@wwa1g1dsg9dHe#VpK;3ᰓHb ]I:ld=zFC]: :Td2qؒvT!}Ca#!*O՘ ޒ@e &8N2NI<5'gfyd~-o̸ L`^z$$ Ab9O6k3OG!#FdHWEAܜ{#2'V%#~&&p|K?|Yña<' %YeDeg${N!b :d;g'OE$4^{vƯ’`:02vMbv! XΥ)#ГNt?.wrl';yK5$e3v,3I>eVxfxRxfɷ~,nDzA2A9"@:8 $!%lXYe萉,y[x^.4cqB# aޞp;H2*gqsze>' (R*]"t`'=Gdt_`1Nb\klt0ìȌ[|L 3<D>.@Kom;P4rΎ51;"نDw%u =ɰNe\B;ԋ?0/:I{mߨsvU]X@ x4L}tmI۟LI4P 6L x&I!%% 8ppl+<33$%lA6-;6HXFL6M$-8|kcu=%d]frI&j5 mfp: 82 !zH2 0xrBN,K8,H 2 bA'BBBY3H!.L7.ׇd{F8]m*eVWuĆz=K8U;UNas^  EKt@'d: (tCFI]pw>krmC~E`-u;75OYgb68K fLdG$FNmIL:$$\%t6HφNf{ɑHvF YǸ$F@vI$$8G2#`u&ɄoG e%x<Ypn&,y,^Mɋ .x:XXeAY'|d 6Xz:ٓK 6'{293[  $i[IhocZv`"0dՀΗ>)]_oI&z_Gs铿BY3fI CL86;$Y]23$˄33$>go՟P;ZYig/An `g ]a'WGLNA=/gz Yǘ"89<wp8>';,#᜾K;w8NΧ>쳯'42ẗ;?5 ,yY^'y,NKgy0Hj9٣yv{G;%ncf ݱ}qa"}vLL`@_ uc{`uH %O I#0)%qY' I䄓;32wg:Xq &d$ % ;,GaH`ʹuǩ^6DȈ>~xlOvx &ygnHF\l)km"ē):S|y&|~L0gn: zfcEH<$%I$Z{' =]HI#>833 &!ԝZ d. 4[;:I"=ub89,c93e58$Yq#wM"^@.>iC$1QdX0*3|7ng'.Ⱦ_y5 $|BgvNg/2_~z"`(c ƕzD;Knx ;} 8Tm =jk?{ŎN@s;g'Aw^jBlW oezgĂ;#:Hz d{DOLwݽߧdA=w$K8=l2fYLNsd5$rrICgIoK] H&l0tkm;^BvgxHo$738#8_;/'ŽR#rƓɌ3N4̒u xOKs x#Ag!<Ŀc|8ŒY%qAIdpE  lN3:, wYC}f|XHj!oSd5(yHNcY N-"@Ф/lu, ,ndK8I:&CdN $'RL,>;y 8a'` $=@q%e2(:<2φui`x}tcp{Vg7ϯ{?Lm'p _r.EV{y_W?:wc:g>D}>ǃxNw>H~Unćsu oh;Bp}C鞵s4 UkM>||>CM$t%쒧ru=Q!B 1bLlj;^+d 8򓴒K:dK$$$ vAH2w%He;t V4%;R)#]3 9-86ش  4OR }zFؿ2RO|Ot~wGվߵR~wXQ>R}>qgg)ۧ?^a_<kc`J}ؾެ<>~??}_FGij}^"bJD1g~a>':=GLMORov6OYBl8/fa0Hld]X@HA&vdင~Վm9(A,o v^A^WiYϸ"Yc@9o/,?ek+qڿooٷldpg躼/{&Xf\.3Ͳ'v3Y^?կ}.dm9O:lל=^ 5c&v4v8za){wOO_}a~>E~.%Zg|2k?ddJ{%8 5Ҍ -g߿#vAuدfY=p%p]Ia`m~! ŐYccecep 6P3{FiV.XLxJ1*.^dY N 5:N:=N l? Q^ؽ.dS+~.mg.  `;<`_a{qXIg9Y%YdYeY-#!vr#Ŏuewcwίl$cLG ?,ײ,oC~=]7 K]i2YeYeIeYeYeaeY$LBCVz,VT,l ,VO7YxXLx@[ ?䃡hҥS 'A>ߢ?ِ#I镬RâR ϩRCXlIbWH=du{{B|?pQ&1+X1r10G2?klN4Ǽ,"I2d,ޝ9$b^xَ,. ^u :g 8yG}ru'xIݜ,l,K, ,]]^˷38=*ֶXkllee،lc"uvN~mdgM޿]oGu;/~< tz?_k?pNK( ͹nvIX'񎫊OiG7s~KuP<,$XH$Y$HdOA#~6jT  מ,2/p+-a$ ِ[TP'Β &pYXlE 4 VPxI3s%҉܍6Yc*8e<8+X|j] >ͻv7x}uxxRxYq~oO< #sRyZ폻?wqXaXs#>7>!?Na#<?E~>XW嵞'Г-+!~G{;bt}xِ7PY*TRJjTA+mJRUr^]@8J@G#x"O)%#&6<$yfpaYg^L㵉 s3b65/viі}'c8g͞LX,Xg3<ٳg<^[GG^k_7wZL7O<FGOzGC,S%d_GF-/h0wtOc;ϲ bGԛM2~Oՙ٩rD}O?c} :%f`FwxMԜ&E k9$33$l} 7[;ufX@p ,㬃"=)ukp] {$w>beYeǖYg9eYeqïYVYuueYeYebsK߶8=ߧmOhG}:LPG/NO`?vGc<󵩗nەֵs?\KĹvjOzjYN&bwxB;9C^'*-0#;'"Yx2&]θ2,; ]E%7MQc? ˶y;ozϐ,^]da<ۯ yLc݆wƈg!b<~O9"~访f;3c#V3W~3޳w1i|يoWd}_~k ]$|?R'>ҞG0?9֟I~'>oV^~>ӿ99ws ?33f0v}_gQhϪ'ś}ɛ6*tޕ'*3zeчpļ!7$x^rΦxg$X̟xܗFXݲx78 , B?vĐ  c_u=OGbU\{]^xGtyGHh}p[ !}џ}g TFo?Do[=co1d'OoIK1B=' }˒>#we,Ku}_d>?v|8`~Hu)+oOFXBo?8iL#o3ル=}ҙ˟\f8^ӻ^ &xm3Hpcu|s&fщQ N {W9#" ;b?#-/:V_mUk=n-#om춹ܿ}-5_:ZKzkk?}zؠ}'%= _8}olAt}g~0?gMbΏaF"пGҿI6$|K="253?5_u~3=j~G?hL?iRgu,3»̒plԓIt&fć :M,ǯZG ),>K.V~6웩e9dgquFb2ŏW7mm]!w lBɦ&޿vw Wcu?Xrqc4?Hx8^?hw|1;aoC%Z"~C{IpٍH!ҏcO?xp~_Oҫ"7ҙ el?o$1Co_&{D)ˬIfY%YxI8N 'p27c^Rn,R^[--Ifl[8Vw&G>crlbg 3tG:820u" le]\2dq+@X$ls6{'ߟZ?{}/hvDV}fjgog_O⓿?_?0ća~A{pY$&G?Ȼi L1!ǯ_>'Wa >|#  A?ԟQ)$̈́嬍+$ffyfg$-+Q}uO,C;Yc;6;,OLe;;f. `؋dAw Ǘg1~B'O/9m:g5ɝq'32a6H>߹M{m~_:^KQ{{:_c//D~M/]Op,{\wO(= $_ .7yqRf0m$8w9v^镘{m̙)33,L.D3c*I]8_RVdy8'wmЖxK+rsg xpACȂ7/ DDcx9>Y8Oq x8dp7VOJxcs7c$wu;9nQ_3γ_ֿsb3~_c^a:$ p(>_Zu#ɏ c{?9_z$dʌ 2̦̰VeYeYeQYep&y{dpUN RSx'ŶY,fsdwvr.no/sdO"܍ ຈ87;< Qi ;e g$ o7~!gƿc͡eda>N!u=7G1Q }?#OOO\l~ -?}'/_t~'Mm?g]~cp];IܰGly'S,=d!g]ut/}ӿ?\?GL?$?hwg}߽?uoz}>ݟg}M~22)ufXDg=`gsvv7xJ+!SXVȈ{M/qACH8x eg1vo=~_W&s7:?rqd>8!JٷK$~m?'dŏ?K g~o_?3o͙O!gm[~7`0QglWc V~Ec/Ӹx4{W#,=_규]ZK+V&XuJ/fisNG:C'D?W~ܣ3_PVZ~X?d" #OLeah57Q~t"DI<@=޳˛Bd Yv,@=J\&~򟺼 bb{$gO՝zdH,H3< 5_+>%{?hoDEe ͓p3ϱ +gwUU~'H8 $ 86:b7##@pl@@AW"%&Bkw&R!We0}mi 03~'p`ق2 g+Nx/ij38d=`\O/?`}~!>5tGoj_gvw+ 铿_=˭ϮBgA#N2v͏gidY#1!=OVc Xd I ᝞2sr8xpxdOsp1d1A@Ype@Ae:,IHb:x0=NHE@쳖{Yzóĸibe81V!|?k#O> #QcQ/p?ݏO_{zGvvӈ`Ȉz[w Y,|81'Eёb2fgSbE; .xbEY 6vs'%'[nm'lg{Ꮖm Y!ȜgǀsV ׀` , 8i <cduc$l!8bxxDCpbE,Ybu b&&fXXw2v0?Sكg ?_B;eݎZ?6hcym_\q,x,sI$ $8B˨ & ,l'$$'6C䳗VIwg-fXfwmAxy'I+-y|^z!F-2,Zx# 6p0݁ }Dw+u 3`XdpolZFZiՂt,;`)/ڞu٭c$cx`>m|ۼ/IgrVsRl;_xx\͌&`'gdru0FD$ Z#Ǎ$0A0tFF1poYnK'.c{˫8 #lάH;< pAdAW!qppDAD8#frgr?G878 P#o!*& d܅e\d<'~//\wI/ <33/./9Xw81z`Nm+ cnFpAkkn8 FAwp\g| 9re6AݷSmx2p"\-,$ 92x~ qp<~ te;by 8 #^B `82A ,/llY>!3v;cg| 9.oVsugý'yxg]gfmͳe/xK-o'~cIgkuǢ߈qCrp/9;wCsl8Fəf&brtdw 8ÌN33Df8˻ "0,,aAXoA@Dt!ad!9o|6(],wxlxg8!<<켼3`'Kl,t}OR]QY,##` UXo.`F,nFo0GpG 83MD6$3#Bx В8v`2|z ]GY7"iD0@6A<{8|Y<N l= &7Smwwy^:NpyFN:CYv$ M-8[ܻlԺCpcu"8v7839.숂.؂""v#/|02# %89e6|ϊe^7p7F 83x28$ӄ#"{H%8m'G dKuA0p ]Y`. b@r9>w"='q^YKV^7[g뗌mYm~IƬk \gu$VDs$rwYc6 2ӂ %#`,%H~?;388> ]|=œl22~l.qdgY<;kǶKf 9,c'p oY8gGH#2 8n a1A@0 FDG9 x8xcx>gg |W8߂rfxveeYyWxe˜/ ] a3ů%0Y2], H ؂tl ڱlp `9>G:wg;]y?Iqǫu[+w;&880I dӌe7&zb{g|1 v$  muȂ2";#67c#x9qpǟw_-9ǿߓ<ptp0q\l</=şl\g/{- pwp' '%.Ǎ Gxm2!'wQ0;uĐ@#n`I=pDѓ#:# ##xx8Ȃ6#-plw9;mϾ}|=ExYgyxmKx߃,ŞvleeM|,s ʻؿF6pAkd ;^ッ2#y ξF['9/-+6<2lxY~3g>` ݐH{!n"wc!w?_'KY[xxeYx6l .p6[Ɯ mqeh>^}%,2c B"l[91'dvGn[eplvrpDCq-x#G4{w3o^6mxa[xWxYymeye%xmxmazxmF;m;1uX/Hi $dpFlpbHKx :Xsbnr< pr6 82N &?7 ~[l-lmlllnq-,2lm,b6a܏{,X[ܴ.7!χolo dDCiǟ{mm tLm?1mlbnp۲m^m%YeʖT)Xvm!emYm[[mm#89S a2>IvRLavgg꿸ʗ&v3v>$Flu]pDp0ppl^8!xb-!Kmmc[mopo+l,--V 3eS-[^[yYummK$?ҼIk}ԟ2>р}TQ_E}?~}3'EZl?>WOMeY,"FԂplAN'v}>?)=/),Z%`j;K{[Sz$}BݿcIwNb~n~c鏷~]~` HF|ڜ<̑\ V v{DGijk l0<v!!Xx!.0!ammz-26mm׍-phX$;?ZKyן2I3?9qߧOϭ~H?\>}/揝̣~_?1h9Isu>~d;Ogw{3MA-N}7n.Rr_>iJ@z$W.+Sԛm&f_ڴ*q,tT#"`^/t(Vw!0c%{m2r:abck w ؆!aa^58m-HK p}K~В2~`%zĿx~?H%O~0+d~3~kWtnpkV|8a/ōHo7HN 1OV?,}菶(?;rmm,V;Vmms‹蛥OE>}.}c]}p]O+%žd>GGo?;aZ [v= R6O8^鏤O_|&EjhGckoҦnԾ;Mڅi mSnRa^^]xsb88罞].2stplC-C GL+'/Zo?.} Ըv/ILwgԿws?g?__۽0Y?W662Amqy}A,ڻVӑ_v&=ϴK', (/z81v$_|UjջVmm+#s%dgx'3.üEaǷ N>.a%+[U\m:^0;6S 0F2i-e4cg؞ХZom[O  _km mm8|^{O7>g> q߃Χ ܂wxӎ/);(x,42'ND%lsc9e:gɍ]xZ А1.{{~"|t~-zax78Ss/> ,׀ c/xϏII~ !!p/go;!WN: 6lf6}1R` <Go6$&(0-렴zED<,+l:/'î'Ox$>{//"^7xIEgywqYދg\RYcc0vͫw8S/F(:1FyB[>IWzɸ0nVӜPOJPҿIϠd vJggWbz9kl6bƄw"1匜ຽ|xyi -篇S7f|XZΆ -+?v˫!ѳcwl2cqQdW,y :HxHv 3rB#GBtfI2;!l؁B0zL`g$ОkП@쬻+m)u<)u&v+Ȭ`Z*8wo^[8O.3C x%bj=^xɴ0F%I Ua=d|5̝i&u|;/\nx>߃ǜck"y{_P E_JF|'< b}B4V$tŎ,S$%!crMn>:HyfxcG ڴ6S<];zv]ݿQ>^lW`ȅUsն 1amz!a׋AN`@oSzifkX8coĂBM؛`Fqb^_?x9Ǎ1up_#~qpQɥy~Spc8B EdCl6.ypnI]M0>G.]e+u3n}<8KaD\3H;-mmm]w"mx{wl' & 1FZ#r;b>ZqAܶ-z Z&[3ϫv,:!/xσn;8`eRG,~{/'Llyx"}?seB&߂aJ#hBCƌ&F|i&{%{"owm//t)˶>/ʟ~E mw<]x,<]$e(x܆^$ovRώΛ|9A m+/*aE :a5$` V1v'.-:6Z2D1}1Bm(gqb`Fc('E8HGb&ӝ㽁Dm8}8\$&]mpl {SG%7< @/7uydcߑ#?ckwA^68ߟo;% GV6Bx5%\vzu{7ky.0h5el}/Ft~<7om~7#ZjLq=tƝ߾2&C.ltvZD`e@G}Olė'xB֜vIX* La0u7j,ÁY&旤g25V2Sjvy%oo\g>ID\.m8ǀhfϊ< {a,^^C> DZ UgwX9:JcOR>a{===T  0]WK렽H'Pk`j;x6Vp"ǎYD q/9HMz_TSt ɺ1$_g;#/5Y 7B#_r+<^7Z/ߘBC&#|V 1C |XL2;]=ݷtؐ8$t]SR*"e/2WOȸa Z-bBlL:blOgGKbD WovK6t ԥҢl0o7"!l4X@a`{݊I97. 9~Gx{1a-6ebϽ; N7WEc|P/~uSվ: ˳%$33!Plza66n-FzIxDAYa;v ֒b3#] DCBޢEݟJlIõlx6.V~HPѰ X:gycɜ}Xm1=c^x!Dgw>ԝ˱e9$=1n)YۻoH;"$uz&=KVC LC<{`TjL5nF9qm?EPMlb';u_/-^^LQO@ߑ~Na>;# ;{QM 37C#_@@urE5rtnq #Ў|xLe.]>!{-F Yv}+)~KcceqYg!'{dA\97V>[ FU^xt Fj2C$B 5\n$ ldZ; qf4{B%(–gݜ;V4֒V^Hll4]K d;ohm#]a )+cb%XdO63a:LgX/8 Աqo߁* /.?j,g# cC4/!".Y;zĦ"6IO[eGYVh;'µ$lb 1K}6?fy  ,&m XpJ',[z2ePu٤w{lǜ x8x s-DY?%ɡpye8g#6LgFNJ^^)`a&1>x #6/7Z,Ea Il;ƒp^!|6 ۚ) q\$C iа@]pJDR[8Ed`24$-Kr.neY〲Π .3l', 8ώZbǽO@mI 8$8L. : # 0Ya -'4|c(, d|NFw+/ó߰#Íd8As2=!a h"ǤA6*[27x]f}F ܳ&SqueksYx^3 .| ;zx3l'xRs8|XZgb8 _ruKeeI{ ,B-d,tXC}DD +qwBT 0"Pu6BmiF놁 @X&8tYp+X[#`y1'xaÖiw[ tmp)U݇9 "V.۵{d &8 >޸s^C#9g`< ] $YrAܖ7,'nIl, 8l,9,8^Q~9|L!~ 4!yE܄o\;WŁ]b|;D=rNXG0-8 &YIv ,"tlY{:-2D ef1 ?j4%iy ^R2RȌgk#QL^h/\w/Jv7<܋<ϻm|jm0V!iB$6BF{^o]=1n]oJe & "! 'd-L'`[3%.{B;u˄X++k턦8G^V9,7~#ge86<' o26ZgvEE:wAf0[ղxc臛@vIُ''ʟEpõWoߟj9^MBew/LBȾqDYj;:z2cq+݁$*(f<9/ `$ vX٫d RMG` ȬDE a0=-3~Oao,ukư&<"lUm)828˫DY%gW|6{:p 1$>adNdo άl`d$X,`-nxD,M joŸI+~Tף;i7z&:닱yWǧlAy3K?E 06 > !uz4-˘Bq{ kUPz)Mv j;A`՘H1D[){hX<w=8ʥqQ,c{ۥNmz7%=Kq.:k=^٪mxFXZ} a 0זsCa-7WÁ^]^BHF2+קD[#ِHDHnzgbƅ5Sv?x>gA&ٽMo71!u[:N@[ x^zj{I Y7kr:=J9rZ!7tG  ?|p=Še7 #bŒg+mY@ W$2zl,H:N&w (L\l6u %X6pZ})?a?l GMͶ#iYoan'ɩOr o$6N>,Aiԏ+&L^猱,BAdpi5ybW:~ H IaQnaL Nl l޽q~ <TmAy}vg#r:oLG.v=$^H|{< g߁8SOR.Po)xfNsa!Z#C'n%(}1N 4ę% w$IG%cWNNKX5 gtF0SЇBWB/tuRt;͒kO9uci9i}?mGmbY3[~͜Xxۋ; 02y&$Zz/rc <(׊Ĩ>a2 (?2SmYtg5ٚ;㸓nW߿.al5(H"027fN+;juyߤ5D3ޞ}`n&bB'-8#XGRoic+a৿d&HH{ǁF/JV]L`fu[Nѳcl\xa%ah܈n$A<#[+#ghNsuw!7m-/Y=F]ݔp o2M8ǖ23ؿ$&{$$.7W>ԿE_,ݫS]pޏ Y#4gė #8L@ = , 8d3,62B5A4@0'i$wn㶃#c< Q,fo n`,) Տ׮E3[w#I d&' P@^,[ذSoEOmHxHwRxZwG_GRmjXaF0v XɮW3~uT>-}!=}f>tvX@nYfԔnCeE&')XZG;Efw!.HoM伄u9&3eOWv%! V,H;ǀC0Zg&lL\!H$ R.fl}HJ4 {~ZqoWYoк7lQeAf<*[yѻb{Y`&-&[L 6_:F@X=t_qE0RC)qQx `Oϣ&IGO@hbt?ǸP`1u|ez-w:r. %b}2'X1i(gq[ݜ]~@8%@o̼]vJ_6!At/95r{[]u`4ut $vFs8YYafLwr%-v :lg`AܞwhYIɩBZɏyaސb!ʅFN{H@f1~F#)ע$rqQn+7mGnp8 vey8{,}2)jgc,pÆah -}nCt:}QP{b@'y0 ,Ihxk#LyUw$'q}w_3Y? vʄ}ZXARq{!6WqW72oEg/|+ 8[8z̛t2F`d@R،:8KA$~-ëU,5.y71)K0g- :R)w/[1;S%U&nnUdWeS| -'CK#O$lx h+ %٩uY), -ag ׈ j~壱~̻;v5 BPP}Iyytqk%v ÏV]9(CKqަ=t u6ӁvGtu62w -$gnݙv3evޕj08K˽j/_ےo\G_g#ɁYKFi W>ۈ~Q*pRԌLd@a2=%d ej?1qYV^=oa]jz_3ћ{}Ys8t`ve69|!ǣ+ͻ"g]1Ek$4_`JX=MFtgRT _jx7v࿂co|L6I=0 ?%i[Ydfq]"z=^ub#.s;@8(o\;zF}HxnM2{7 陸#{^^8%mVoSu{x;&ǁy;ޡyfwupd-1.9.16/contrib/qubes/doc/img/heads_firmware_management_menu.jpg000066400000000000000000003376341460375044200257640ustar00rootroot00000000000000 ICC_PROFILE mntrRGB XYZ $acsp-)=ޯUxBʃ9 descDybXYZbTRC dmdd gXYZ hgTRC lumi |meas $bkpt rXYZ rTRC tech vued wtpt pcprt 7chad ,descsRGB IEC61966-2-1 black scaledXYZ $curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmdesc.IEC 61966-2-1 Default RGB Colour Space - sRGBXYZ bXYZ PmeasXYZ 3XYZ o8sig CRT desc-Reference Viewing Condition in IEC 61966-2-1XYZ -textCopyright International Color Consortium, 2009sf32 D&uJFIFC    ++&.%#%.&D5//5DNB>BN_UU_wqwC    ++&.%#%.&D5//5DNB>BN_UU_wqw" z,%AR(!(&#),&B X,B`,BYH B@A@E P* !` X*X(X*PX( @赺k̫LYȓ111F,,,`a`@ERE,TA@!RXEA$[** @ ƀT`,(TPAR!QT,(!A`eXIEIe,@Μϐv򇱟=ܼYՏi|6g }_Xy>#3a>G3'}+>^^q&gCNFfqc3!!1bb(E mXY J@P@* `* q, RP!APT(TJJ1T%% * q1LFLFW65D7]{@sߟ_W?{Yxv=zEStWɏv>?sC ~?Ge3}~_c3mÏ '쏫/#sO}gnksmcb,,, YB)"jP A*q %@(", H"("J"P!HTB) * b3ѵoh7ww^|^P3~?e%GGg} 7v/>33f}S3=?>|H|Ao>G>|߇ROr#ۑ⽑_^KGboLyHyF}G q;!+ v[d0f0f1d1d1dU"(@ "@( X%EEDQEa99s@@d*nxgQeAP%XJ`!}口I",ƄB*1Q@%,EXX`-$X@EXJ%DXI1XT@@EDXdYT%T%H16665d `XPQDE<1B@Z1@R6j٩EX@u}_W~zS8ITe 2*$DX%DEŔ,_/qMr= Gs3|yCn>dziv/5|Vzvkv vCz4Nk@Q`X @ !b%Te*+"ʒ |f,eJ [5fH KߐלQ&P&9fCPŔ1e,H\YHŐъTE)QW0ǯ<9)=<ؘVRZ9wcżݏ/smshՠsc.iqk>xt^;.\ԨX!(!,Q @BĢc[sײō( "R, $aVXK% jePX(HB ae =IŔ1e,&C2̆*1Td1B(d$J&9 Ud1QL*1TJ&9 T̆+"L2 d,@JJ ",",TB*)9#l۫mYUEF,-DQEъTsٯ"J# \fl@D `QE>Ϯ6L~X/B(ZsqIDtG,ݦK(T"J1  eX!aq#D,c2P(,KDB@ gvcXwjۥQEDUEDQDZ`eL+A‚D*QV2P@XT_N;1osvv9>Mqwr^zzq<vzysy7^<~kO^'g$vX6{γѻ>eî~=^n:5z7v6Cۋ7|?~;ӛa]?GwuWMjgC Pdb,(I1"KAQJIQdJ,! ,B{,ڷiUf,H("Bb2/: (-(&[S$`EEDP/0ُ~XMrl fc\5V 8솶l5Lƻs4 1pcc,b.pیt~_O˃N sȻtJŔPŔfC\VF,F*!)bȋ 2%XIbIa% %$ `ftoտHɩIэ$DQ"qÖ9s`T @I^U`Gg֙ߎ+LŨRL%DQI$K,*2,ɔ$ *$L"K",$X! `d D`BX`b%!% `(#nYaߧq2ch$bĔ,ʗ[GxgX($ <33K`%APPTm<{r=^;bZ+򧥨䞜?kέ<9=_݋7j6zv>s_uǍs{#ܧۏWol|}=z> wG:~9פ?(ǓK[Z{Y)>&><:=_|~_CK|o{vy}>;2|ُ6oIކ]y7ó<'GvC%XsƚujEEDQEQ%Q(I]z:9 ؔ@)2 YRT @(Ps5<{Ő.z%޿ s̷+|4N Wyk6Ͳٜ;qrpμgl\=׿Smڎl9㠘l_0n3KOMvC;.zs{s.76vsIn:tꑻw7.wHt9aDXE `dջOAٿ^ޓR2K(E Ŕ%F,I"Ó.L@ (S;*e)=sǿ wZY_C{yxYKhv'mu韢r׻|+Z珻O3R{Kھymgx/k/^w) ` F3,E&9EȐ 1Ea&C 1(a2F8a3D%F+%,Ix2c\e1YQ%YD 60 Y TEDQ`Q%Tc3?EJQ@PPE JPQ*RO_1w11|^S>o7;k'gk^&^Z:O~.F_ s{Xt[|kv^7Fz͜tgǩ+h/O?)y;= |m| <]s;^^Cfz>\ui7חyv~/7'[uoI8Sos9/˻fyvr&9Pe fR\̱2ĒŘ"c1EF3(cQqHQȒJA%w曽_38!2QTIF8%)f4X#)eP,,3(@ RTYE)@ >w7W^rUxޮyëO6Zl:}/ _,s̗N176eoݎZl2k4jVӳ^әz+V[n/O6zRûIWOIN,}kɏֆk5ܡ;.v Lƹ݉m,28%d08a2F3),\fPe#1c*\&PeXęHe2oO{N(Elc3,*$b)̆$~7sK3K*,(@ @( (-J}wt爳Z7jsZz3^o_GN6'Nwu#s%ςGou{tff'*uy/_;λݚ=> \gTpsُ==4|M]36Xa I\ٙA$,"L928 fQqHe fXębĘ#\fR1C +D$c)d"O2}'o !UF,%+(Y̤f F7 ]("^€ EieIJ"KTR}o;+,N_֝Z=L9kž.j.vvx{\S3ȞnKճ7f}ZywwfX|gYeP(E.,cl"ь\fRKzwlMLTb, $K9Cugc:(mK@PX-J -(S*UQU l-"WGUDED m,QEbE,Q(P1QE9EĒ,J1 haIp1D"b%5FK\PLPIgw " KF(c2\VD,8=1=Kz96Ie"(( DI$e%;i`dl׿R[b6Ҕ@ [AjեTUl-B#ZJt: FH("d@Fqk^Ɖ(:OW9]=G7aZކwl kˍо[RusyX3/f_3~]x/{ n'Ջi ٿ3g|zq7N[c:;}EjHKx=/;ntbo}77:Ow/7P-ь%e&R1|>L~~>GOO>9i4f8ʚŇWڧd|W}gϽ/o/_]Gߦ5CG}Fu_o7CvoGfX E aBQ F81/?7V,Q+,r6B* xר}Y2UE[JQY ZKTU'CxPc姩|Gb0||=_pxG<̽9221EDTEDE1a3!!]Sh6Sl5M6Sj4T W`>s}ܳ<3dR",$LZBVxg(F[+tYlK2EZYRKB-Rեd)"u> X+<5=KONn*E%(,"("ųLCYYsMlteu47 -Ych1d0g fcpŐg+Cfc m6 MSd5̓[f'|]s} pbevbJ!T*T$@o弆h( X1<^`P@3}^ZFRҨ)jFS!jQrI շVA^ps3drݲs;9Q7s:r^G]GVGC7L݉_NuiTٲ4Ʃ]o"mJ%:9fW6Uc;m-r6sVf5 l魲.klaѩjm[᥸iƉhoho0?5kzOoˬJ$K,$~g-ћ(ls ϖ>|zqYn!>!k|_UO汯!p^χo:v.P, dc&S#<>;Q []XXlkAIp;dfH(TU2le)jZ)Z(' =O3m56Sj57 Mvv w` Sƺ51=Dž+޾>FJ|k%}~(>=~fG~|;V߆MX˾hqöpz3qq_K3ˇz8y} x$wNۏz(4)`bclV{_?_8z>Ǒk=" KK#ͻO]Ұ٭nwA}?}_FP{y{;cj2Y*ҨZ̊ZKfBj.XȥE,qYy^9jt9tGTX+~:Id6輸y؞ɇMlo0AEY2_wVਫV̅ ,rKLFAjdTȵFRQoqpta/<iSÆS{FFɊlNgM9]tu8`t㽔⽃7h⽃WU9/U9]cWPuS9]=1 Ǔ}=ёuGXuIG\9'\9g^5:::::K43Ӥs:!:a4MO7ȼ5qs羃=_Ww(PUGG"8rݧlϴn:}g'y w0(V[-*)lU-LQP_v-rpz>uOļצ25zi{8/FU2G-ʹoVv[e[ga|K8ExϤ__>g?+O=:l{rÿz&o.Xz|G.4թNUϾV/U9tc9/Z9/U9c9oM^WPuWNXktN]gsV)5O>[Y~1O-~yǯc}L @dٙ)U|쳮jQ,@o{=3hU-ّ*҆VRKe.ȒTd,Pok7U_6N>/=NW]N0)߇ ru\ʼnۏ^݆eێMxtp=xÖ1:jn;#NGe8e8ݔw'hv+8a8݃9tV^;9puÕ9CY9'PPPPti9]#yy9H#:q47CS8`o&n>׫)^one@X)BDR _ƴ n-׼ah>,2篜< ,ۧvG,JJQBжRKfBʖ̩TX- Yuzea{M&]tyyuv/]9/VG%-+rުr3s:iz)s@tXtS9999LTۉlq,Ydׁiwxo17~:a r3X,XXc22,J,Y=^^g)` DQ(J@DQ@?  `1b626>f|n K\u YJ!UYJ"e)lYRb{oZK}[7pOܷehhƋ.᩺.inn᥼iQjnhinѭ3*$"AK6X1\IXceLlLeKȄ1.6Dl$$D$DK9ѣgG˗oFQ,TJ(N?ƃx#=dK ~,o=px_E2fWk)eiU- 2d, Pd*VihfMT佗SS9@5]vC ",K!A Y,DA,! D$Bcq$ԌF(%,cSZ-5m.2zhΘlǺמr<科_NǛ},3/JmǞi {,qeN;NK9E90wr4ݣSh0f0g fCPa&P,"BİBI#)!`XDAPYU %j|oϢݯz,96l55yZϐ7G>WcF;0JQfB2YJR-RKe--R0 ^'G]˺YvS.L˗E9M}4]w#U5 .TL.T1K`QJ@!P `!I!qB *K D,EDYdEAdLE Ɇ&֍q־5Ǹr#<>3~_ŏI}|qu猡.^k416zR{9/;0Q`Po Q)l̉)E \l)K`(ZFTЪKBQe  `* *B*" !l*Jc)!dc,c dW%8=^2_W_|wZR횆֚miZkZdƀ  R!||k|>8e1=p,SgջW Pd (PTLr-Ɩ+L4W\FH2Ad+ɍ-dƙ111Ƹms;v'鏭|fgi)Ψ#0]?tb>G̣5e;uIw&ZƱ&3c )B ARA@!PQ(XbB_>#ƺ]|e#8@}W_5#5x}m>z;5hlX65#&"%(K PYL\n%k7r|W鐰A(WiRiƖdsc ҾN<,#ok_|a/r>[.:{_\7a\#&#&#&#&ɀ!PT APR(PA@A`PP` Je ӑ/&}(͛>}N&{ P{a$Y`T(~_qھz*=||(Ý.R" V#)APTAPTX, AHPPSTEgVg vprވi4,=|W{u^/?0˚6LLiCIjW?Lb+QW,)yԨ*2TiPi*`ez-s:7nG|ϾὩxXpՖX[6'Swv19tME0liux{y8]1E*( DeLّw-vm an(ۦV<:\sCsnɀbJ@ @ICYNrcR,ekyՔnpre9D5]Xn\LD7 l TaDRI&XȻ뺾_鼽iγ`y }]:b@T,  ,*PŕLwe56Sm56We5]]EAlk֨ni摵s!*"[qR<-9Y/7q^z* P1:'8lkE@JEQ&Plv&QP ̾wu{Ծ:>x˟~) bb`6M:,s:^+ߙL#GgFr֛=>_}|Y'9 ˆj nR9D?SJiHyiR)دd((1(((((WJ(Ҋ([,,,œQEQEQEQF%QEQEQEWʾYeYeYeYe_K((1111111111111111111(((?kQEW'R'Rl#DӹN)CqN)8qT)qqqyyyyǐؐyeͧo6`1S1S((' o4Ŧ-1iL`4 0imMhmhmٶmfٶmQEQ_S>_%+A~(9UUhi5Ʒ`mF"tr\p!R$q(Wm2<"F'$,Q햌%C`H,=̍^,44{qVz1#sF tjVj; }4^3C}Rz_kS-^%нTlr=pD#GG:Cg^G脃ԾlԉEETީEO/;Z޽-LorC%U [ސm䙡"9Z5G**=Dkգ\W1urZd|O|{﷏Ԟ_{o >sf9}yWID'ّYѭW+.R35ǹΛZآ*q*=$ҵ=C[Ѓ8nq2=螓*i#|pLѨ6%b2H#fHP9)2=AK4njED.G&+bI4R"/7zBo2F<.i5ˠ1QLiUU:jZ Ъm׶A15n ^^N]Id$?Gˮ(h=U$3}$aYrYdkt8{Z{ެXj&s=̞V'/QWtޏ}ע Wt?D4~Cw/nti[q>diNmX+y&|YEE8 wCO-؛rFWQʈ_ 4N$)/ #ȐHvf "ަrl˚'D,DUs xs֚YuP`1/z$Y4F"|,Hʜyw5oEDOz/8%K4mK4i~$KM jڭK.Ii2Jȴ̜Y8ZVQ>WPånC#|jj=FT R7I;"EbM4],7ᠽO/uzQ~xKW5=z(}q{UܴͭzI7~//Tek5]cQ^uQl3h7jyvm^w+$t冈K߷+[sIh}ܶShR98[ܗD/sn,/j5zOGpOui/'S#2csl8IZɽ{_kM'p[]*i5jZ4wD'h6Z{A\-iPET-ŭ!D9GUQ$ dsqPES59:h#s,5i$;pW+^{ܻ kѲHv[ܓ${WRy9rKLF UhIOMɦfȊ*zdFI, LQ_47vL,FW'E/Q]1HŒyA-ډM+ŚUsS$5lұch+u|H2ic^=ɨ]LrsKJs!KTi'* :m~Jz/Q=⑃a΍cR*)M4¢HXe*&غx|(j5Ƣ+7xRFjF70928d2=FA#>?^-}|ףDnF6HQ܇/uIވ\..#vDy&Qy&C_,ɤV$sYlsA%A${YT$j6 )4+芨nJ* ,**$ǔfPWQs!V"jdkӻV=h1GΓ`kQp]FUcXcjR5á{F)ٔȻǪ7HeQZRtb#2|b.ёH&8fB"# .M]3Q^ԋ;#Uͦ~Q !149 6QU`ҽ];3V FG+'Fh{v%~V/F__wV& Lj"E~{Kɤ~VDX\\Ak/yy[sr6t\H=NHCrVҫj=bE_?乬_k2k[H6dyM#X*$kBb'$~dtǔr(D?4c:G4G ׎Yd%sWq!%>{ *+ܨޒI:G={YI,U5hdr$r{GVoUrh92V67-ml0rvZʉH誮Ts*˹%}|A|{ϛ`1Gy^>O@>j _b _?O6uf*ثNN^:VbFG&.ZTV=Ep{M%1ubڑJ[XA{1UΞ*4|clJފ$*:7xb{/HٛJd(ܝŐfI!Đ],pG*:#F<EM6-ڔloԣ#t#$N? _Lf܄,ef.DZfQsDr{F#[$Ud-o7tOiǐHcsp9|aQV;6o/{=aj)#P.d)bD69D-uo~]ƪdcy&MWynKNDyi"1;'kSnF%οz4_"Erj=SzF x(W=ec+^7o&Sy׼/z3Yn;&~Nֽgxszn*;sQߖ0z:UAUWD5d~kuTT+HRׅs1GF MX|J<^JX̖FFѵF)!k͹ D8V"mh[܊7X F^-ȕ%G"kvո#IUx8A"ӱDdž0&zb楹tzf˦b5OMɑm+m"#bO77P!6?>C5Q|kjM{\`RH**n/33ݙ*ʈ?<w1JK::}Jc\mRG"5fOw?R$zֽaёMY$clByHQRv5-K*2Y] %jCdQ&L OoSB1>l~ ;?_ETGWGImݴCw%jG9p-rVZo-$"*d5i}rE{ ǛsAdy" ;nW*z99#\s}hF'͛"wby}~lj+Mõ֗x֪FcՉnTG8r9܊j叮̂#]'96^q$oW,i\XѧH%?j=hF'͗y&]1Rm?ډ5A;vH?koZg*u)#[җ}Se<+zTBMrI*V9 r?{#?eA>Cv7]7I'⊭7P[AdNGr39\$&)hn r 9-F>Fzw`'F'+4^7_[F ~ "IDUW֫$ooҹ|qEީ=m'wVoy~uWz-e^*v)jE-#\#tIŔٓ941A͙7Z|)$[n}ot<]] oScvcQV3gM up$Ob[t&8]K"YP.AzEy|WV}#P{StTru_$EQTF=L\jm8p9ԆŏZerڌүgHk1]dtJ5Ԥd"k$bNEHҭs4j9#ԹQg"HƹYZX#ZbM*+Jz!1"~4x9ӿO;H߶k#TϺ.dPI|qij ͮdڧSdԾFxo'z0O/B;^~%rNj#\%%~B $zZ*tۇɈu}+Ws&ƫQQzsFP$QѱYnwG&1+179p9ZoG.*dY_$2u/H$HrZZvOt`ϛ7hXe}zyaDO4ǤH+R^oxUG d"E*' r{rֵUAWg/_Kx/b(d)4cs\#zo6?|G"O >DsUF,Lu9O;d#gxtWK}#fx =zm=_y:O=gb|ItVi1|4BɦM3iMƛ7n!ᙚ)))ezm'|iS4~- f67nay麦7\nqqM7 p f6!gTS:cRq'Rqus18-8,81(G6Rb5 I_> ~I/ZM3iq 7Cx7Tqu7TSuMtCu ֛7Xn4O*S5&֨89Ô<8,81  8sƀ؄ڌ(Q_s>|ɽvoi}Z͆ 7Xnioo)o)ꛦnM֛nrToRmjMAǜLqd88jpNg# ŀ@q6!6b6bҾos'̟'i~~MaUtKR1Pm A9ǘJq^q\q"FFX,G DŽ؄ڈaJO{t`>f'jOWߤ^ĮH"J\RG|O}^~/q{w/oq^]ȗDҗ*"YCǺ?ZRz}s'x)ڛ7^üRP#DNW Q~&#|qWr% DKO1<8O(QE;ORuz0OQ{3I潇zm?SPDEZ1SAr 1çkQ"%SJC),FX Ջ;p#Q hQ{:77&/zb U{w%TW$G#d$vpU7Zf(1(J(((((((ɆF'"̀sXsP8rg9Roj sUDu^jO>Һj<4ZZOG9m+g3asqb$j&7nSkF9X$9{pz?(((((((((((8؍T3Ns`9֜9rQ75Fz)ϥ0"2a7f'׳:'ɣA:WK?/wGI<㼢((((((((iffG& ˈ䜗sTe/R~Fa @~q7Ty!R׸?-M2C4333S52S%,7toV4`3WoҺM(yw'݉EQEQEQEQ]+Ķ܌ތ߈r9-9 oݘjKԟ~!&0 Efpq!R!;Ķ&0ͦhn!nꛪn)eEQE҄y`3WEBWT%GUg(-l7#7791KNAvcsPe/P~Ra&0e[[Kil2iLn!onfjdJZw(((((((((0`3S+=+CM;Jd8ޔܜR^T/06mmcQ~&Fjf'w;iLf47Cu x7t7 3S52qR;QEbQEQEQEQF%QEQEQEQEQEQEQEQEX}WsX//xP}y˪dw;Bd6oo鸦♸Jdyw((LJ1111(((((((((((((߮'(tE('ƒ_OU%onqn2qyҊ(LLLJ1(Ģ(((((((((((((((((w!]73/t_WyQEQF%QEQF&%QEQEQEQEQEQEQEQEQEQEQEQEQEQEQ]㸴- LL/ҧ2z]AKP=~B(((((((((Ҋ(((+u ,,/cMm$Z}='LUN<ã{J6>0((((((ExTWJ!u21u=_G;B?[~nƉZO4QEQEQ^%QEQEWER-14b*Pj 5>ׂ/U}g_Eʥo|ލz#"bQ7=oJ(1111(Ģ(4nCfc9Ŝpq+<֐z$7trBML;FhoAPEY> >ix ܆̧c)Ő8njӘ#9p9d*s~crS'Rbbbb`#FgNWB/GA66d6ql,0Z3wFrts 9?)BC1͜None!)/Q/SPJPz>|ʔL\`mڛfكJ 19:c a99Ҝَ\"svc)ȥ1110000006ͳlĢ(z?Q+Dk At]JD󨢊1(,]{,-K_Ik$C$3Cq p3"LZZrd((;,/qhZdYjZ&)0TueGy)eڗ((K,{,-~Hͥ!J{چdjv:((šeYkiLTF*c\;E2Bh5SœQEQE}- ,ԵcUFozn[DJĺ={q_ זz,*sb`"Bv/U u*qTɲ%%G)+?& !@AP`01Qp?1cX1c1}ٌc1c c1cj|tItItIttIt8O| G.|v?m&T*l~I7*mHλ@~5:?)P/H; 1 B"D"> :nmMͧĐm~sg3sfff}^k2Ȕ1tc&hIlL<'Y B!#v& F&!`#c>$BD#z!@E?EP[rHƺ~:<. O >MTތp::I:::ь6<t!saB#zmeI <tÙUSGx٤,Dь1&c<,@6t(BZ!B7226#I cʬtcɣ#ȡU Dd}*:O:1crfEgn>Sr}7'B!QUQG ܟ||O&.r}##!p8qqqqqqp1Y:y:V1c1c<3S#W8BLFFDU#qNdoF1JBqףt[rcǾ~tGzuq/>nq<}S~cc1zh18'޻1D]CjE%[%(h|*IlIaaadP- {"Ilaaall U8--@BlX0GB!  c<1#q3 UiBcǪk^-GB(:!q(B|zE2OW?2?cYK[1ѱo#r\~ՙ-l^M<-1o쳝gKRu/DYmb_~8Ԣ!{ 77ĴKhy=5Idb3mA|?_ԗVY˹Yv]2ɨ.Af7MEȳ1rLY6i?g%HL57Fz#ՓsW9oF6* IF~~ )غo뎄~X?X!>D%AA]d8wṎeeW0Q21` )eecauϼ֭oc=4w<N e܋7&_,(Y4cfN j.ǔe?2~.vCس,Oŷծ~[u Uآ ..J\!2b]BBGH]] ǟ_?/`M븲q0_较#B\/ے]NKfN.˹ĸZbȾZ6"r|'6`ɲJP/ǟU$%*CDžQFH6?S6,, a4ĜQ*QlD#Y?-q}FVXȖ Wʑ1Mnd(A(D\#Y!p35{{ pX p6',˳qBߞ*KQ $/qB5|Aۚ!ǟcϚQ+I$,"zwC~[UؒȒŐ,]-Dزe9A.2#R ZsdI$I'.~[[#B ##CCCCBI$I,Xbʼn$I$d2VTR ?~F>eJޤIbI$MMHdeخ]S"( DQETU\tI$I$O-s|ҼS("Uv*DuAAAAGtۦIJWl:\4%w%w%2ȸȿҙ,-u29C'#)lUf*Lܕ2̳0}JT&8E%D* iI$ODo=#$-o=.0AGAAAG^~[sߢ|>>Ko*ߢ|³-wp(-Yk럗+n/#RTDI$IAy1c^dQ(~%%AAA忙d6e$^ƽMHd>}UF}㿛A2d*b>G9EdYEe/ߙoYw/txvYd$O/E.'3e/rϹ/&pQeܶ=]'}evY$j ⼸Kܶ=x}'3e-r_r}|\VU&Qeܺ_L~$мu*ʕ#$[>$`2OŸqfؿ&>η"ifyW_'CQΒIELg^clח?Ƽ*T^ϑmG%|>1!23A "Q`aq@Pp0B4r#RbC?^o{_W.7{7/cblGlf7$}ܿ՛cođf{شM6#8c6ȴ~߱ގ"87ܺwn^ofwr-j6/sg͌#{~z7|}ȿջgȲ6aͬ97ѽrVnfogȲ6amfsz7r7"Vnfqf"ڍk,xn7#z7"[anogSo.Y͌9n^UlXbgYVqN"/bp7lG.HHu6,Xs.888z/cblGp&le/b8k4Xo\n7܋rرcick6̱bOu%,,Xo./՜2>_庚1- e,EDfEYS*le$)k(i2z7jPUq*6iDJNd<M#TM&S:Aӣ:B~$r#Ti@VMhTz*E pheQlyQ%Ewua +u߅ z4E$ӫDUAƿ[dde3E*FQF6i&r׺;Zא|׸ѳتu7#N+$f2*WIUC|}V}%k$hy`*.eZhΑ*zRO̎"p~imQ#.d#QA]gQ2 y?Dh.]S GД;6_!b:H+U%G'ʇF.yg*ئTzvFtS+5_58*nua즇I5\%*\J+m%6# ^/ [gfMff;3esRudz6׼d5ȬdfŚUTC:f̱+DbՇK:Ӑ֨SE/oDUuj?32+ZM5e7DCtJ<7E 4xyJsu<_yZ?4ԮмJIa*ފG Q&[s%<2eԮh]Q*F2sevۦ9%dRH3JR(Eb?MQK9)ǐҺDCV hƐoN&JUISr*]o4G]0W/X?"o} 1T`׼UH*\d/J]O*OՑq'MuX6ʓ$ҔtEbd3}i,٩]*C'KtOes T$+MIX;SUɣ4eF9JʺGA8M)=Y؛B_>1Х /F,EU<ZjRZȫZ K<3\Qv*'OTdǖ ,uF{IX̖Bz+kO"{*\k}*QU>'[WA3Gx&f'O?x"*MYX\Mz:\ E܍qkձz_:?/4Mƫڕ4e3H$qЦb ^rF7>ѥXenGQ:|niً)%CHKSX3kvN#vQe˩S.j&Ғ04*dE$+HhRQ6òu+hI}eCZjK(Q Qg)]:?Zw(L<W3J&5wG͊*zUR^CQk,kz F+A="gHu5: 5ӼOBCK,ЗKa(,j"(U{л/Է,އGG[%](Gȝ$z'5ps#܊ JzN]N󨞹ij72"ZރCC7 ~td)QCG(i#7.6*6E(V,M$kLiZHLkLHó*H]J*Ȧs6mFٓE^OUKo+]{JgfmLt3fu;SlMaV̚+'Ve*|j>?~߆}]Z% "#JuCFּ#X <3SBe죴FhQiE<Il4kZy(tGTt7D/a<E:z(kj4f#"yuNsdT|(ʫb*߆RH^ѮjFOGMwyWq))/Acp/TFacOڭWiȶF5tm~?܈}Γ/O"}Q|IȌ)"2gGFjt)*s,fѿRJMTZԌ: өGR4Fry_\y2923CTr4cϔH3W35xL}E- %B] UIc=qc*[ toplUi\,,ͬ"k+M |63Um ]†I-Υre.5]둙Y)f Tz~Աl)Ae+B5_rRȔ cMGKs4C v59҃"e唗xb 9UDBuTTJt#/Rq2&^שځف\6pMJL6chv1._iʔnЭu+'7$ 5x=J6:\tQEp4")!rq5ZuXcH6MMbhV5KBaԨ(C[!v܊c/\s'ڛmNC]LZw$HRvԊRyY$i1/3k%WTf9٥R|SJT]IfU쓊YE9Fn&T_-B1Dr(- Т1h3BUB2Z2DJ %ĝk,=HSjG1 WDAa(uc}#5;r5*xJjtNj{L*L+y2zv_pғgNFmTtojM%*J}hԋ|FjgUځ+T46lQQT6n.Tњw6jjawvdut}|9?ع[׳ѣKbM ,Rb%,܇rcU7+5U5gfZ&>Vyl)QUK[ - ,[&NQg#+kC^dJF#Rky(Y:UB#YI?()L6XRk㡣$jnx&jXż7~W)%LUٖK5RKƉUy֩n_fY\JC54+m+\Ζ<ЫZyR'1ӤU9BEڮg#I2I7U)Oo&*kE)ȩ®ZTِ_EIkOȯ39btuȎ%O&z!ծoXcбFi|j#r;JPU\5)]  Ñ se˅MQ(k#v#8!,t.=ctym̐ä'y6:GJe1WeZft:Udi.?,24jNKC+E8GZ%ϸS#<,E2PzU>FHt2ṫv̿AFIҠ*~LhPZFc2^aƈʃC(,0H:!>Ddžï2bOROfV~dtԵ!PCo i#6gSY*dR] R?OËLkm+)SBY~M+VOëoԳu([\-CDjr=:x- STz XTB>lQA=:ڣBźJ+MJ71P?#> =~<ev~;^~=~ r˗رbŋu.\~}E ˣr7#qrs,3k63aY&ѽCofne 3c8oٜ' 8/ n'OGoffӳGԇ=|˗._fYMidr.ލ{7s.˳Æ' 3 8_ ]{qG_Gq$oyܳ6~Y8HGcju![.\VōŎG"܍qeRc8oc͟ ~N^oEqoyܳ6M 88#m_9R>]m0͏c͌[]{Ony{?sa88#m^ż+>z7?OשǯG׮|bz/_>U_*a> R^:b뿆CcL~]F1u4]qaqq7 ش ?O8 Duf?_GӪϿ.nFHXTq شIp&M#o:816@HDLgeZFp8h؍ݗ.\Zc7#|}$}$N"7 28r82?3?8Q6,"TkVqom~ pQ>ŖÙv].\Rŋ-rǿǭ?_7sws,ͬ38hM8?G'Oqq&^%p' {({#r.\˲܍qvs9fiG#.\_sU֨9&pg#lKDqőG-/sc8cl}E˛wԺ.\X9s.=ףG^\.˾˗._bŋ#Ⱥ.\vskK,5Ñt].]xXn˗Gz]ث_vcNi^2Rԣ_b+5/2RȔe'[BIX4|ܚkC&s^n4=O4eʌUFy,*?%M;ǥW˗V>.d5 _ -fŋģZZ*\IP]eC+;CeD$8'jxJIƙLsS}b3Ȯ]{5\n,Xe}TގԒ&єfITtf͊Pg/![o44:7FyB^WpD9QTr/7#z7yYg2Ⱥ7kҚucƎZhHcYji K/7z7ymÑg7D⣎P<$m6Qln\r_ 07z7͒8S8,4m$MztRqqdn$ple˗.\r,[ר>_ 7#r7Ù)p-t,N9gFgl4ppm]Krbő˩^]uFnFfpgQxq8Hm~ ᣇcl}˗/Աb˗}~@f6Tq+8#_dK..\ܹ~@/Թr /?/c ?~]W6>_YGk⺻Wǿ_+Ge/H:xDVfX!]r(W͌ac''c˲ +oY]F)7ͧ Ӣ8F[ ̻B-Y܍l4:3a\ofnetkbŰ\o7m6 ˿ݧ[sqn/iYasqw>,[sqXl6Q neߏ4]boFnk63aL9̿|zcsq|lXȯhk˗/cr5UV˗űr# ܹ6,[reX˛bܹrkK.nFXm-s/pԬ|UtnFn/w̹iލ݅\nf_ލ[ 7#q\nf_ˣq6q)JR.YEy#:GsvBOa#DgXu~E~~Tbg|vэ!M!4&&ysIR/YEviً/ΏgސCQ L~::/! !BXO)EQeW#"~o+__ 0T~k!/ɋ䟑o:_|xt0`Ɛ!B&uIJR,,vjOE"ڋޘ+O4On\oKOu_? gC_OٔC:vHBY J_4eQ|α:Wr;9#t5ޱ6~/u>7 s4 ܧ1yk 'kJR+:Tor; -@fr>A~sSB~$G/Ĝ~e땿ቜrŸB:Ixf8+(gXsuiM%mQm E=D?=dwNo@92>\X@Н13oSo!4!O*1:T4N;gM'\}zr~_?p?d/׋GZ?b4OZ?(U(J'O7˥eees:XCuөJRmY!B T`ipiM!4-,! cQ|2gL3> ߪx؄!B!B&'?hEh!B!B! &͆ϣM!M!BB!M!4G3ЂP;ð+;6*z6c8~\I!5OTSjtN9dMLJ])KҔ. HI>w7,+Eh!4E)JR)JR)JRKDDDDDDBh !BDB222])JR)|])JRt*DDAI$,,+Ddd!&L2VRE`DEB I$I@(####2dɓ:])JR)JR2O!DA#'R>d|2d$g^F93 hס5琿O!v/<;xlt?GhNO:߃:âtΆe"ȢȮD|OO:;ڗ((,B;8HR)KR r')~NU% 'ރ]1PnJٰuo6鋚i 4 Qy,Ұll$tbu#GeMalIcMDZB j-5WQ6UD2ą rlt&o Bʇ- [g6o; SR2U0HD_HYONLMxcۢXWґleyiڃ%/-73iܱFin-(6.9.ovgWvx;s@r"b|+Iʌ3+ח[} &yel|%o6̻taq :&ǾzщqJ,{-\3:$ ǥUfDbS\ȫ[ fuIH8BH''ѐBo6Hz!zkx}Dg?ZO_FY=R }hϽȂbvyf+ҍ[ CmsuCWsJɶ!㝼wʫ/D/ZRa{1$OK_cC7(鰟 O{z^L1\ph^\d>}u Hp Z3./czCMo]b _o'*5)($a qKu! WwfWq9`qCv48BB7Bl됆'QdpZ› Xm KlQ:xxW{ю cԼsob]℩)p+Cil9Nd2|3W;c^΅ٌ#sN,#lCYьi*\K$Uq\%ςo1P]/dSQm܊5likk}b6e;MhA'с9!t_ȪM'ĺ48 #gN.(|F}(Ck[̘ͣcm^_cx]"hB_wc?N1h^/%}`4MfHB!Bhnfr|Iy=eSgw3cMSE'm$M\7-?q4M(= M"Z.Lǘۭd±q:w$g#v0ŋ~8x܃04a=i7DGDљ:ȜB#:UG7VJ;+yIogi1z/B O&&BI!B!B[{Qz)lcWџ8z=F!}c:SX|ۜ| ^UO~mW=FdΨBs)4q!S@@B0 * ӻvbcc{=#(LJn:51bŻW+s)w췒'@SM(cMXNc c+铉Ͱ)uJ:,f +kauR'nq;zaAK֛9\!۪!5EpgqxvqeP[-d4zfmxzn#HB!WJ;_Aw,pXuc K VhӟݧERomV1Fn9"7wi8>0\-W{j?&S9pN-VXu"DbL{զ3Vql|evj">whU[igj s>[Ӂh[ޙ.,|?6ƍ2 WC<pF),M 5 b;u&udвh U"} I./gP5[Ue* ewPvv>h&ͮ olN@utN<6G &tڷ.6fN;pF; +9D|Wn)} P{}Aud\_5trf7/y/Rڷmr|1у=Μ~msw6 Yn^i*z&E6' EWH\NbxNnr5Y 6JKN{ү!n8ɡ 1Ŕ9A4h#fy w}H]{4n[fmӰ͠i8MQcpe!],!Mv+b97gyz傠R68b ,m~͢W51}Kr,A $!aɏ 8h_3R f4oQoDs_Bm`9GcZ'57'AK98^ R!Rs^4{q F7'Ș(rk)ƈ.5bӨt)դ!4'B=M!B&B!4O 1MzW_J6.eV8n"H5J+7\Йc|;b;1\G#nwP.aN\Octt+5^G{AspApFl2D;H/@qG]ވo3^'!nc>T򧥟IhC+yUo\gs{KodIXew hȤ(&+򻬛.H3n}>̖- \ n\FB灶<]vm0NBٴjd7p5"Ϊ7:X>:(kOsolemq7T82Q/r|3.!e#D wEo0R6sA̹}x_Z}at[yIhO*iO!<BB!B-z>a/Fdlgϥgfo ѯ.#ioRmod4lkt3v=//o eާ?ƻ]c޸8t?Qu8O*b)FOwAI77qLUZ==TӺ_'%+I^=bUEsiIwLS2n* {7\F-T:Me3xmIi4t:!Uocv`M7QA!Ywf(O$M>YHc$ڭc4 |`ieq&mą"H~'ݏdy 4z- +FQ0/Y="*ϵ[CtaW.= Mr"f:? yBiIT%q!T|`.djVH4'6(m y|w|tbHxU}Yv~])U?:Û WS#=7џFm;'8?boOs+N?)ԃXj?zɜ>$2WFkv=mO< _ك}u-E2#p Ec#"&M8O(NoqhnBs}G `^sү}HϠc["ʩhy(qS|LUq+ UInಛOJd:7}Mbɥ=GUg1<̜{Qm+k?rYnTM_Crɇ'Em0"wBpoq&vJs%ދ!5uInkgssqlI-kpy5ѸQ*FX]35DJ\i^w;6Uvpt>#pm=ص(r5g-Nݫoq;Nd\=q f)r$%FAA5cqeY+,|Zȷ/gGnQ48\ƙ <#42;N$Q\>N87L&e *E*2;M'"7h_QWўqoL;l_H&&Wʆ$U(\aD~ LK~#nB$L*vڎHkHNSn' &(NCc3[_}t-C}SsC(')uqg% 2ٖV>8+T!p{ m"l Cs'p< w4<]G寣?lYG7'a1.ɉ /#/t4q<.ⅱ>Q:"IqGB$F0L.JCO~ XbZ2NN Mh XbuW{bS5GtϹPJ5W8\*K3JC\ː5|MJ(8i~ /A EGe M>${9(\J͈!r do(_FoJeURlA:=L,n˺JcQza8%Ÿ3"r=r?!yIVOU}Lin..4nѐKG=:>H\¨k 76eѓ)r۸ٖIN irT%1.*x$n DS{S"!",w/2 &Y8*%h3wq.GQ M`6 >ΣI oP)0Q\Z'kо\":?!߭ogzly1[_"FQHM1Xd v:ATpI6 CqS}G&qF {3Fܹj]2(MFD#Ch#`[6CZq,qrO2>NQY|$}ʼn)%H?zJy⿦qyIkIhIu1GfnbbZ`vf=v*CNm,.Cn;]>|3FRM䴜:kHƫbSIѝDNzp/"Y}I\!D8`U_gLZSf}idGwR ɺ) VavY%jHӇO)>CY}kn?(:< JchԮ.V2QC8MTFrӑDE> ` wK mW{RJI< ^~[˱ӆ wn5L' ݟ!j;7Q2 IE!̓v>y&ql F֔3FWn%BJժu jOqB 9L,{3ދH-5Z4BљoijiYut?08<e4lBtT9T5aKK# >L<5mc(ĸ-!a:aˤظ^ˁZXqw(sΛ!Z/tetB %n>Sy e6"UM1M9ZZ ,UjUU<نbaHm<Ks0ޏ&q1*N yI>zPqF!$$/8>ze>޽pчMMH_Dǰ7"6ȸq*[vZ!'l!\В!^ksc4נih…t)6]uM]mɌ7B[6m֌Et'UQ^-4Woީ`,ABؙL !rVwfnQźyT4d_P5dƳϮ?b El0lOUd6v;}jM!5sy9Џ;Ma/t:莀B[s{v#wM^/2:oZtZ̎ ҝ^s701>6z#NQg}_c{*~6ta/3Vt*Ah~|ȗCǪ^Eo7yON\ sG@tBtgC3t^]&޳ΗKc1cx3/+]Y 'G;/iYc:Os~~ΠO%?Z: RD!O"HBhm3bn=筯Dvf^tgKCw΃:9(t'EdϠ0곹~tLWoHſtP_~{?t?w?5Zgb~GK J9-2g'!B!B&!B>|'ec7̳2?+?/#_g?#E C7J yI!B!B!B _c`q󩗗;nEYVEZiB!B!5!-_޽txLDò72sp_v:$v Kbš\7:Zt.c^^ ҍRI!4!B o8}qvx_ax<i>NGȅr2*;QM X^dry+o"ȵYi X6Bmb*lw*C`Lt7}$!B!B /6>N|J^m<{U=ۈiM1 dIK4/ B.<7!VhacCc6N\CdFq0=#UFId@,os!J7N6ZYEz: B!By''h 37kwZCG$/q} nW-%\ 5;s rDT7t*{#Cqtn֔Wi]i^!B'! !B!Cƍa4hQZ8~mna>t|| U߮ZhlVBk U>6h B!<@!By И:> -G=`傛|t^|s|_}8>uur~T'!~#:ЏZ""`5 ӓk]YBh!O,!Bys@k?+ \Op|/rW泓{ѳt9_9F6|q}q=#9C- r?)b;'4t?'zkW7U$|iu:Rbѿ_cv75G$V/'/ o6!B'W24??ي?;?=~/ Ŀsfs쎦:M8g1ƥG%|NO`l?Ϋ:s3d'lhz'Gc>tX>c(E2dΐ!B!Bc~}uOKny{}rf 1LUqOG-Ó:Og7Nc쎧@Pb!^¾Mԟ)s'9\?Rh>4·<~5dۘmEr:IjcuΫ/>eQ_EHp7kLHS#upF<{deMd'_C^^u: NޏwHv:GE.:QEY~Lx)gi7â45bPs1o/^scᑄ+d$'Ijds;^huXuYy2(`?ov7L{ KPL =SX8EQe̿:v Q$4!c;!?ȜJd>y"j/ƨoq!BD7x8v >Ch<,SG(NzoBg|פMtqMsxI=0]j >DldAUdgE:*~*,[$ eR|rq6OGIxyɡ)H|j>#x&U+;m4˴ }5ohX~&wxr%5L^&CC|}  D1x giƴXJ^.""m̳ipdSv0{UvB<˘&=ѸMyrfwf{QLNcb89y2MMM)l"xO1uM8B]ǒ3!lMԤJ21%{KEqoM#w̕#u6 ߊv7cx&VTCŃĴYOmm;Q}0~ 5[\ zy, ֔&|g<]jFӪLs}Q~Ͻo.|& +0n!>7Mpdb$WIkd$Şֆdd:$eiY["$z$(JIⲰ8\e%D6256,eRUWM}/ ӿ4m\_ \~'Ӑ|)|'!%LEljy; LE&^lUo(ƥBd9n/&3εt>g'/twΊF9\tۦ}^>KfǧbKso¾M`9;_C!B! 8[W,cb;|2|]x8184x?g #G{: }Ϗ#\/Q:nCiu,+/ ٯpzJ]!oFnGz7h!BHc?t~ZM_4qgEl <+^H:N$w11]%i_ :k!n/@䜟tUgp>GOG!+0uв,&L!O$hLŻֈlLSkRQ;Ǻnx%~!C _~JXq!.tSD:hs?ƝLR09l.r:2N=&9K;E΁#|+lm͔_u E} [DԄPH6?c8uǻx_oNN_>_s3σ:70:+vuQ3^ItPtΈ~t5:T#(+@-|B1BR^?m뎸3!t7[Ct΁D'h{y;Jtn:v,;[!_%^/94PtX7ō[oQ[Gy/::XSFQEBuNrZΌ^Q`P7>",C|ݢۅ? vous++;[S^ٿo09JB/+O.BQ?D!4Bh!<0AYb,(rn)>^7<DP'Lm ?d cJIVѺ=|W!BRh'-#\^i7?!(&HAP^!?;Zكz#-g&&L6ɖ9!B! ~G""'Vo #R+EL%+B҅[hз )$ ݂ϹUJH1ȾEW W/R6D3\9}b}βw݄9g{Gɟz1{ Pհ.(~Ve^nhĦiy>^G\ʹ)P999)߹7.r+#r>>U7! a,,^IOXuS^=S3sh.T'<%/@iw\oݍ|)}E_ GQCox6on##]Jz>%N!ta7 AmN:tQG#zwo|"߀|m6!or1~,(3ɥ_aCO4)ļ5/-G(Aquև5H!5.b5QcoWТOo&B1"%pyCG7&7 Hec: ]3uԄ7R ׺ oP|7UbgQekG!BO2`>AF!J[v(Y^'vj/h?B&'d7b щV;ۼ"ygt?q8!An-,;VB!B5ypČMd< H#4vx?B!?RG5 >CП! "le QR P?-t5!B'* |_4-Fܣ!Cy7q ^="0`Ǒ92i 3~1!yflB|j*#:\#__<|4 !#g6eG;jTT]JRVV[$pL'{{q|7H:i/w[Exg:jTGYz)KeDfe&5BvJA<Πi8g44/_,`82fb>gqȉ|~RJ}-wȶ!1"%ss {u)KtO4e ' o]BdE̚#(JRW4DS\ sCaMJRujܚ}SqH>R-`7uFy *@)JR~!o[CT.4CF P4C8ہ|5y ow n=)J_oGeSqkX[)yJR醽Ai/q|m-w+{E^)K& UF@Md#H5pa |&ʏ6v~3gÍ)JR)r3DƼUDDGj&a>(>Z 忈oA벾c)JR/+ BPj8P[ᡏȥE !.\H|ˏۂ'XuΡy6Ҕ//3p7dE<', qэ_ tC՝vu+2FBdž/?V"h fEvk4I ei5nftCcJRe- (Z#:ƕs#&1m.B) D՞^uJ_&2-JւH#:! GPF:+|8CJuE ƵG ThFW#^`)G-$wSJG@r:;R($8T)"U:-ouΦ q61JJ3|+eI$I42bz R2AE< /!hPkqZC?呔Ye^A$h s/1eQKxB$~LJA~++)uz+NcUgI|K!ExN5'JR>+)~)$gKEJ |?E{.O8=ݴyK+ƄDDiJR0TRW ԍ$"kt YEk F'}(V%/M""WYJ)JS&H/^H H⨂ {,EJ$C٣&OU8Q ĹBMVa_!7#.Q)J`Q_0R$eiY^AfDO`;^si MrǖpaP44$j{>咪 {PAvA{C_M 4 w}<}>{./ AW?| -z(^wYqs*{ykBcMPAϰE$ c s-9gjm\q5XE%C0C" u '@aD3aK! ]Cf5)_{xpƻk ({$s Uu&*0 |4L80ARAPAQ5ʐ^ '2@ʀ8؁MsO=axL)wI<<$H)4A}} 0 8<= 0L9<!681|BONo>E)$P Ϭs0=E~ *@ %g4/WA ls?`0ׯUO~ wϛ<kӌpvwd s O;EEGi8 ADViw۞(wh^󲔷43 HA=0ǰ{A?MAW]]$)Opqm/z; όE^zDa{> _GۘA(?@C0Q84s ?aOuPq4 (.O![ ,󦜀س)_Z3wRڷ$OUs5*9 ,@rRc7Ä́HkT.4~wEt-`oEІAL.\GJ"kZo+\u$} K+>z03dGztB6@@Ll!M-cUKi0Ï&?\033!O$(d\006a(# $hnO=R A%w?<XWϿq53}5Iۓdᕞtlڈە/5{%:TI,˞ AA~&}zl&{H5Ͻ.Vk b=0;&e3ADS4 ǃA1}q9|9jĭԱ?A+4&.ȼ="js!@4W<,2@X ih8q8~ p'A'JYHqHv14UNKHGDKIL=`eNk(?n0?[]w"/PwG玀#X# p3>;#IQ~f]ZE\S0lEHDOFS PSMC?;}N_.z8J>$4ɩ 9Ȝ6MZIG <4< K89~H+a*&%mtO>H!āhl M=9,} )l.7tE]EUM'[WF PQ@ۣM -<0O0<dEx7$sc/MXT.[Y&2ϝ< K(ΰ]S botW3A + <,#*^z|\ zO~dScQ4Agz @k!A,}ϸi_$RCKOܥC/3A'R}}A/Y[i0E8bƆ"O8\ATǰcxA9HM ï,@h']ú]X?J(4,rGc4) :r㦞Y>oYg:;A]SwI9% I)+xa B38o:` IXr"Jg$ w *>k0}4-&ʭAKa=\ݡJ$_:%qr&tG&%  # UPiN<|]U{:)mJ6텬KH!O5EF$4]B jW)ۭ0:\{ASZ) Ij/!IJ$A} (sL 0|%4Glܥ39 G(JpO?Gs E0ï&Ig4B(7ڢK<Y+oZ!ᇺ_ϼp4eM v?8ꏥRߟۨŀ W~5UfY4,?ʽؕuʩ%E>yC=B 7lsZYFpWP̴Qu ?g F;s[{Jo-?Rޅ(0ϾKE cy"1!yS{t"ls0_QWl0 0 0 0 0 p 0 pm0 0WأB|i(0M}^D4M;뵸o8 0 0 0 0 0 0 0 2cwsֹyM0󏰢s$96!y 9-8k9z,+آl~4o A)QQJ_^sZoN┥L  J]WL,BfdEIuo|XHiu'}DWJ&+b/Msdi1$D8p(*bQϏC4{6D$4'?N"DJ1rmR!͈"ݿXtn?+l*FiFG(on)S HVY_6R)^(Ebx{<i7JD& 4 E ɤD%**g _3]KUI4Mؗ> V%QHcQO0!S!bDiK1rEJ~ΆF 9WAý6Ar&Db ND.iQUb؍|;DMkxwE S'jLCbhR*TEZ#"IGBTDIHDP{.r| e !nCb Ba.BMQO[mq\woeU {>X2l$" $ǐc-_>t 0;O  Z\]Hؓ~$IFQenGcM PQlRdYHcrODEIiT$bIkRUTJ˞$'T@i*>Di^ 0lev⢕pBZS}7*BiBO RBbKrlGهt.+(nⲲRWC|WUO|]FB2_j}%sZj*)J\QEd(jBX;Xtե}%FM0#Za11B .|>pB22<## GCHiʄXmT&J\=0!Bi!4p\>w)qKqBb@صLsO)lB!DDlQt=d,(! rsXo"(+VR!B])JRRO+.4-us?HBiiJR#77.R)JR̹ZVU<\v47#FFV 4G)XjJ {ED Ժn+%?2iZ8v/DCd) HV6ۅecl!:a$!pK )JR)K.iK]2K㤾tQ\OKjo>_QBiA&$B!B/x؁ lL7Gv!QK Q" FBbclҔ.R9 =&8^2b)J^!5hCHpX᫚ޔ!N-I3?,/"Y|jMb}.B?LpW%5rz?#W/{k~D^]_"cQU h}2|?G~G嘿bd?MU=?,{+z"&/<?Dg~G嚯g1"/!$akO-S1 EaD D*eQ aiG IvOg|f!BA#|!?EvWyJ>Ga2Ba IľG2V@>G>cRI$I$e ,_WL2y%.ؿτ!#喪J_/j\”)K/ e/zR?w((ddd##ل&!BBm͈Bf؄1!Lj B!B^]LИ|(O c6gL4<[Թ[%.1|''qUWev_gD}e`&K/^3D&(岶K gD& #XQE6 ֥WEt4[mxP7JRR(м2]B`!0pSL&'[V.ol0$J4Uv4qm&j ffȰ[/ Q6>.ѽz)6Ũ%MuMe!yjO؜pJE N>Bkhn Zn3cFፒQ29MDZXBc1cCCa !L+6SDD"'D] |ȶ"EDV$KƆMM!2Bgc(|?=JhƕВr5ߣQOBW/MCMÀĶk5Q>;JGW(z|nT UzdQ آ-XmX'j;|ՅXށ!peVWtZi4DG ʘOtOԒD#T*ʨk S،v=șؙ&].Ddzj4JPmIְ|b"DGHj$7:!;B!0HCI:DU !t4DB",&HL[Sq~o $iv*Tp9)T"*U{?{t—ZSD|vLn= p S@eERL 1Bm(KMpxn&1&kX9Pn&y*9JE&%%KZ9{I(&~cI'1PњDDiɅ6;0Ba6}!k,&  uq_ .&k $#I&4#NAi^ ($O.uI""!$[S m>B޾t[X.6l4L!IW@z5.틲IDjp҃U5؟ą:Qk,Ժ M+kk5' j8 .F7m<7^0.t0ф-BR!DE .H)X:[J4Tm1NZ6*;[D 6D&mMk_S Jȧ(6Cvӆ}?t^+ZcѦ'}{P Xɷy)=o{Qƭ$.$4Q 6Y≄!_"(7KFL"5C=!2V߲$YD*pKzV6m_FuQNPЯ Fjws :i2U905Vζ62(u=K}C[*NO"ɚLj5`4`M )d^R t*%qj'(| KF5ThГ 5ڋ4Ȱi4BI(ElI9^!3% aXֺK| Lk kH2 .YHj =ZiMfȚ4֬HQu5z9;,Ȗ.Yu r{ l$%m.X.ZEU*/ []ԣJ%!4f Is.u~g_j5<$ 8-ǥ葯҅r&VR$pt#Iro>2-ͧdKr) c+3#֬M4DBk bQ$4m7"&I$ڟ$$& &&~j&Wf 't:7C{ ^|5RI6KdO+[ : c>ID)xg3&f[o^C9o{5m,_/Dz7yℷ|ZTTTTTRp[^s-.R5#5A|Kw x \)Y^5JR)JR)Nl#%Yy! xkJR”)JR(cE=)HHVGdv3%|aJR)JRҔ>Gy^BD؛+Cr%йNBl'| KM#p/F=^ eo27}8)TJTAJR)JRtqqf\xKGKa)ER;N XpMEB$xYeYE'FNQ]ǂ""!4Z8dy8feQDϋ>>G]?Dv}|QXF*** X(W+8ou/ 3Y|BDDXTTR)JjWG!< {_{x.($d!B!1Bm$٨$pzFx!B!LSL_qy^F؛O ]  \ ~= Nr1m 4pƥ BÖ';DTO^)QV5d)QK\-L'o|m7t~&(MFP nh-HMeR{Htխ \B#aJRJ\.Ɣ(l.p|q͕̈́73Ji45Ԍ&!EE*#)E++3\!h!DBdFJ˒22! %.WsY]BЄ!B-h>^V7OaBg4ޯesoY dZE)K9ll]k2M2l bv2.)qͨ}0}QYY1B9U>a~YE);2I$"",4/3KkQT]}~_.$ ^""ث )K76TByƧVL'бdvI :FTR”)pKNL&W{rTRJ]O>?"(K3 Wh}b}t>3}1M<. VϘC>>G2`}23VW菋ƃ|g}C\<@܇/"}ϋ$~#xmBEv>> ~,0}Gد41Sړ2`}>Z)JR$-j2)J\SR!Ll 4L%,*)Q)JSR22 "VklLGR.:B9R!L-۱J\И^/Ҕ.I!DBds$ب)KfIt,ZRb'QV.0! "C<\)qDڄJRf!<1("VT,d}KER )YK+-8FmG2-e0z`"cg%$Ɩ+i'lhi #xnѥzyMxv {Ao;16z R &N:ۼ39̆ԵX;zx" Ѽ w1Pma#h[ޗN/8tBc#ƌB<i9ǿ==ae;ܷn]I,pB"882 ![c쳩0Kx$}! 0L2i<9o& klqrہmfuam mF8,rx{y cvNULd2v 6@XVx]-EZMyFݤOq ȱleݜldɄ+G[xxny7#w8s/Vݤkeհ]r[o=Mg yx6Ӈ-H )lqFA58rKm_›AM}. @$F2 ]e81aYi]dIc6Y!&IckGtAmK̖gH{ax 85dіq;Ygmo)l o%s e,|[K ڒ6[j[1l§ 1%Ԧ۶q8Yw #INӼ!}@~@*I{8o_~yG/JgA[ ?"xm|6H6a!,8˼͐66ᝇFH $6cDzmvvyme3:.,|-]o ^G::~qw~=~ۅt7c'lt^1W&,RbDR˵%W qwcg|xY1Lsw q g"M;ܼw z^xrme3чVE yX{96eˮxv8a ^x= ݶYt>nmmR5%ۅoVEjo|-[lxVjBo=#tݰ?RQwAl=|=Q} >| (6 d@G`Q⿷.~ܧOǽp{~W|=CTW<|IoC,H@psKVXeYcg.g>dO9|huaᅵl׌fna8鳄XcwcKnmevy F9}Sȼmd 3pl$;up5qؗv|??}^*PLG?B{{Gnp#8eMIg lYfXYa"dbpqJgM`g993s~{9 6YנཿVo;o_K7KmpЯɏ&Q:cvW`ƉAȝDίtgü'Gǰנ>xv?_sK+S?W}G?i"mr%rIf7ee?Ye YaqgeY%e,ds=Yd9^{mzӟ}wvk~e(^Ӏ0qEF]׃cXfl-Qgn >C7)?ǵY~3AA>Wcu[P~{ׅO|"x{ ӟXAe'eeXLYgggYe,,Ìe ?rE$qyZ(LgzC{ۮHzo(̾vtn18&;=i!~,ُM@#cYdYeeYeYYeYdIIeYg9d C4@d'Ȱÿ"qgw=;}FoNpr[c=;zu6(L=^8,K, ,Y=8$z7Ԛ$,,,,=FzrOO~??..c@q]Yӌǹgϋgq _=!Dg9aHqYdO!@OEk0#3Nk>,,ឌ,,FN18Bz2[C}G?eJm7ż5KTY%Yag #imYe9W֐Yfx6X͍, 8,8OVpY<ɺƜ3;y8r-wOIzsўv-o&qİ𾎭K6p'8\k3grZ;q~n=hmedKaxyy-mm[KmMnӍm=ywzmקOVq<=ףnӞq=1=AvzBW8˻axyH%wF;s<y7ydmop*;NHnF8ZIg,BBBŏzٱf nݸvڵnջVZv[ZXc67w|jV[ov۵jբ!lIf͛$6ش )[0 rK=:_GRbg}w{<(f;C{S#lkGXqaueg?P~Fş7>;-KoW~kݿ> &NƸ-VZ*._;0rL ,1q7L)uugݱœnx8"e 6so^wק=[g9,,ae X~"E߇K;,M}mKmKm|6*neb߅2lج+YV۵i\x5 ve"KÿΞ}':zC'\Ş3eVYad I $_~~ <k2mRn˟'c#ٶ{̏[7͖O R׫gm9qßðI%933<Ye&YeH 0ǙzHGK \guo/x[{z^.zsg͗{'}X$emާKD_o -؟K>7vV?=YM-pXzsќ͞n>9O|u!K8/ Ϡw y_}[ӟ?uuV )g9$es_K>xf8p_Fo`~_Scs>3 ~H>aY:>.}q/~F W_X9=9G8Fqg9Vp|7c0t6_'WҞ֟X!$:;IᶤnϠ;lD oKr[i$SOҟ)oI 8{d|Ϳ78GwgK?;~f}>(  ?3}|% lkc#V۷j,}#=-קx} קf}ag&rxOs9/w|1w1.y۹} :# X^%axs-K%%V%wF%bx2m^5-[zV'9xl&xvP;=.u CeTn/ / u/ <(\GRvQ*-y$nmo0e4}w׌[ߩ;KxwIg;.&J9XmB_RKH판MIqY#eIdq8IeI `h!@c^EӰBa=GA+!`H A $x3,!TQ1dhdy#wT_CnA t$ 0thr&ɢ;;4/pnI7,2HfsӖ o+/2mlAH0^Џ|g<,7y{ˀ߭};ϰMe6XX!'*Yek;"pV@ɬ67rY۷tbx lұ҉JV3@0f=/aaM)7@KF? %a۽%ҽ; /CN0aʻ5D8D&ik3~#9A}ku -09 =qqg9$e& z;|$9էt?;d%IIgk'`$6 ,,ဓA$dd;Tb.Y*HB;GS~,(j7cډIC:rf KyKQ?a@ 0gn $$PK OD!Fp4N2G8['ImKex^[{&H&:''K.7cg,^xf8yu)?V㬷uI, A%Yb%'Vu#HA$qeIAw%I`Ia>Y$lI,ɓN$%[RK$6I$Ў_K6*9[ed fux o Yy$ kgSXfYd,K:v,$IdeIa%d %Xx^r` .I$N3Ռmeyx[x[|i`,42,θy2Ɉk.%xTqvg.VHe3!ÙM2@tu$ O{$l {w8w8Ilr,f8 $ӳ2-jOQɲ,K $s = =|ߥzVӍF/ z}o> Qwe&cPu'yZdPv'~< gaYô ^R&/GIul < KR`yDvZp7ׇ-[fl!)i)-ܒ$%]qeI$z, 3p9N2K8G} u @I˳xKq' xF[@'N}^'"c9$}R?KOmtb|7n Vu϶v3nn{u fw / 8:7tDTt=!LsS=;bW9kWqwJUnM+K={~*o`Iv&l,Y:OBDYg|c'=+,̲̲ڳ3^" 8l})AddsH]'|﫿K //1%JX1v`6ݞa;;z$̀?kw]R&Vr媴/X6FB61fc}/&=u <~⍫2{X[{䐠H+ٴ:C y7[n#'|{^x?TƐH">±$#<>d;ŏ9{񞌺fvvfff^xyqLl5,nF ,,f8=wfAu<o_NsOv*FG8cnG-ʇgw|# Y:#`dDH]CIVX swݟ|X(eB@V"@!1 _dq1P kw;FShz,zPk CpADG۴Ֆ>73! 7$A>U1[o2<620`3A?׼sY:gۋޟ=ځi>Q6ݎQؾi"bAuo5 43.k[|O+jcwg H̒z2N8@:$ϡx]2a , $F6AgS9YA ;t,}OMǗѿŞICdd {AN) 8XDK $Y`ڻw $4vXqf&%ᅉ02BI+<$c$CpK,:$6K aH!d' Veew%0pcYdX᳦Kg]xpyM ObzvΝB UGOw>qK {Bfg$,[ȒZ (rN(p[jZxaxrezn+l a^eyxx xq;YHAIevpȐ ̜FNpQ9{xѾdMh vG/~ތLC5JԲ!=R'5Tnvf}Kh&u$;VC=<3Bۣ"okwDQ"8 js¯݁(>#ȇM`DB ûSNM5r\pa IC-Hn5{%<}Wၑ4ab}߾ (:QpK>H{~h<,ͷ~ם~x hv>[ҽVUZ\ Dގ?>u.~z얖9lC |;)orp8;xtPh"A]W=gͣa}p*$fҿFAǀz+66-ةfxsM/ ȩ9s?6cl dXdYeqY6papgb3Ɯ7^#G 㡱 :Z]=:j$1!{_l1E/HKO'G}|ۯa󚓘r׺A{[BG^twD4/+_[6~xA$Ƈ/ԊsZw6k==vguǡ" mPe|ɓ:Xka>B] ߦgG?C ]} l(GtvT?+v^w0kqG@>nߋ2["|y_s4h>/qyZj>'NPК͓=dnν_/$Hy\tO۹Փ3j_QAF:LnK/ދj }nr3wveeX em]_33͎|:`ԥLKoI111r@a'&Dsn~@&Avtc:V/>lQY&Aܛ sdY$XI$;C̾W8:J_sI!!%y]}$%23@M9fM>Py,߷Y5_Y$^!^k/! P_.Q+U>MlƑ{_]oV? )Dc lwȲC"O?bC̓J\?!6~Rϭ~ u ?$>~2xh^\ԳKcL@>O躎mzf,rp.ø;=W2v?uJQ`T4<|OAXx3)oA`l;0{/&-hVjxῒ-O`|GÄ:zg 8aih!X!G@|[ &˦3D-6oo1Wo=DWUfA`!U{_`b$=B aS63:g@G/ h1>al_㡁$ ֈ߅]=%mt!/H"܆pKGd8t:: 'z$,, `ok.xHSypM=~>ܒ !P8V Q\O\6%*|柨o ]$&CѠ+ܺEk'Xv;;^WܱI>z t u4z `L;ـ/uյcrvBştVn8OZFfп; B!tYE؟yd _)mRObbaسJ ckQ1sX F12j81,N dK<-l;Va%Xq26YIdNXDl$^Sϡ+xm7rnj}g ;Hx-Qh6]+N0@27ACbnvE2L@p*5h=쎃 =Iz_u~aԀg؈Ԍc]K)N#`ߙjH-{'|췐G) B6ˡ?ēj+2FCvGdw,ld#$ {dXY, ;8K$d$$eY%Yԓ2p' $shTUlٺM?2Aa.&kξX>SCݷ_E:}Ғ:~68O1%>c_:x)\&@$i_#5 6.ָfbby;|@ѳ!Gpњ>${>"3b~X =,mgUOl iτ]΋L+?f:B@;7w l:`'$]L9xp\g egeYg\dg L⫕ca9F601Qٙ[{D3#joH}aF/&ZñY׀uu{K:wUw Bt% rz"*cGA6c#4 n2Qxf@A>P ̤?{'aLf^*olh|~&7rYĐYr{E_d)EՑӋ>.@}`Ag }H=l)0Zwup2BZ{>묑p/τƆ*N!Ly&O2?5㐂M$'>*H$vL$dgdl3fx$piv솒I{$8evg I% ;g!N著VsdYY$kŒM`v(uX%px+\~`!%ߛῸOqOA&Tձ# _c7b1`'v' [n{PR_|OAB(Sgtw`cP™>f\qۙ ߘ|j~tk1dx@η1l>2eOzf>G:4*M$> Qg2S']Ռ&Y6AI I&$I$dlɒC8l} əX'epHYdpd,6pO =9I#2A' BK $Ŝ41r,ΤGLѐ:vܒuޛ "wgc<*Lj,a%IdLIdrY ͌LIO-OYĻFY6 # q<6NeqtT?u<拾W;9,S88AdXz2l:[$WY#cYg}V]li/\$x! $rlK$l& p'9턋<#?'S$+pSz'##g#$;$sad2XLp;a'sl.>rz;ߣ8923G,L&ˮ7pdģ:>Y"SѣpK[me[{Yvbp]Ol#dvzl' - IgR$I$8I0H,̒wdd0q !dIgVugAu!óuijz=9zӜh O\ݶ9 |Yxԏoԏg?W( cj{a>lݼ!:|B <1CXG?ZCtxYBKGq=( 1}G ,2,_l; T(xs{N{63ΐVHGx G}΃cd5r|=yى> c2c=FKfQ$pY $@2ɳd 8,yyL% Ye6_VKPuL(V1(w| GG3့+u@Ns&$,$,,d $8FNSK8K=OYgYdYYLJ9I=1'30 9I'Oy]0zR]`C1dɚM(qw1sp{ftqwػ?Rti)n-֦`[#/âJ #ؙRxAk:~6,n=khBon!7C:,-xOO1!|e/ԑjw('@j/ F;T|1PO,l,Z)*)!xF/]a%SIdԧ`ZctFpq#I IDDH~'M! ,dFI $7$FNГIg"c 9NrK,I6N1g=f<'s>oNtxQXrpzȒ8= o>GgE(!AHML1w_?B~`Dq$%=HgQ~f&;Q3]1\}$eװ~'>%(x,k|2oo\!P|ϱEwC˫Nb͌tX~kэ`XHA_tPǝC֠Xs Nb6Ba~6fKV}ìO'A8$IJ͓,$E$$oͲK$$lxI &NɒB$K'&}輣:ؒL,8 d2K$FdždٜOKz]ye'`# 8 ,m H=ܢ`&;^D sdǠ 'Vӯ)ހ=P"`}:suOޓr*I=NY6I' YSg d2 #&2N;$I<2O$'d2I,$; ">#4G$ $&vdG8IA\kmٓLb#μgq8ȳbOIpPqAuI;r~ֶ67_Alʈ< E|>DG}lihjGRf) H>iCZ;1!{0&D8(Ŕ EBIg-~/G'`nCWUf\(??Dւ]J~ "O>O1ၰc{iu"/,zQ8~`vkiV $Y$<I$HIIe2yNSdX'26Nq#'!%L2-ܷ1 pe_Ydy&Fz3 #` .>QEoo6(Mgãi/aC h$[dKWt?kH/LfɒBHkgS2p%$,nI8I$$I-Y8|g)ꉄXlH!$Id IY$Yw=$Ņ߄速?/Я9AAddD'8=łn O%}añ>lX|HdOY1J"مAcY:9A\%ǿ10`Ɖ#q-A3MP: :$-hwK>_e:|LB:` 047h܀PvjzwAd#d$A%H6X2 u"K,ddlfId$BrɒNdNxxooâ$ $ ,H-D2A>"!c\ wD=9i888 3~C$~L{,~M̑] K<D1кpI>#AgPpA;s$ > .1=lp.y iqBmĶhGX? r|6Z`7|6h`cTq϶=QK {Րy'^Ȝ F=`å{]6HY2k2w=9dNpI$I]'D̒Y3I1xIld$Ô8:8E8Âl 8Fd=i:3"֒-7|,νYqAAeAO>G,6WRj e30ϞJ&.ߒ16x!#ÍdA L0X@ \qN+/Jd&}kJXA!|%O" p .>745:a7wCH @Ⱦ1|5$ȀK$w&l %pՓLp$ g,$ْI$МkS$9we{Yq f1a%3ox%/%9b7B GArpX6YI$МeYeeYՒ%2O=NYI'GҞo=<9–8c) , L,[/ L_P"6aY\<%̓$[z;;.lH'8듌r62" 8"u&l2lÜHO9%{/6 ppʒ2/v#pfBt&&p Ydp#l'I1\]lۏ(~ * cݙ@h|(K9ev{(k: ZDM$sU-4bHv̛]/a2Bق=A0C/1D W }agN:A w(OQ0;=`0{f K߫Ffu=fbb: <'Mg:!OM0!y$7̃Y>>.a1I¼e8vAm^$ wx;0 ٟnXLg,gWL}qg w' #;I|@9L0I>XL"TO?'z& l@W~@{dd~x:fԹm{䈴kaApød(S&ws9Npk#~sx,~/k(w yoV13Ў@(IA=H'`?R2Ns|1?6`u>x)ψv1>c3&>m. !*J"u|fP(N[J|RȮ ,:X'J$1 VLI&3&dNٙҞx6SĒGyN8ÍC;cu<;7-},llr98 ,,  ۮihBΦ0겎Cكp~l3#'Zw˚-Q:К]R1.V,:yI`u^RrvG~k' 0toɱ-G=XHlY$Y<I!dd0I$ɱ!$$:!$$2H#v_(B qFIu,$9P, $g$񍽧$xFI$Gr%ᙙ&v ug&cwLgO3 86,1a@@Ap@Y̐WmqKU{w2>QX65g 8@&n/Vc2Dm+"{$iȨe:'<Ϟ>9=r 9e2Y=dL3$' Ü;' <<l'v'd~8>y1M"O~s,t,y 8 " xIFz[,g= ?œ8<aM6r $ωxfxg&]OpI,̄ 2p͛'/SG:sdb͈DL, ɱPUfVz2} NC9," "8I},euA^ޓ8#^ r<3'33dI&nj&xN屓> !p:Y'$lfqd|HYmzFplXGۖ},<zrN2lN$K,MI !$'R+d|HyO ~>~_:y =CxsԜBOǝz2=#T6}۴=/3@{A` `E|wl==Yc2l82$g%AI$哖?O {ҐCJv_a? ^^/uPZRL_%#Ok?s#y7oTPa=G%hKo XLo]<N_gQ=;papA)gq88Y+9,8,FI<1r_|c ??Oiߩb_fgV4?#;?؟|{oԿtG}?t1vqJ8{w~`<~x|afGɚ̝H!%I&ʔ˷=XuWIҚ}qu¼wofl&sy ;ێ9xzeף"b #8cGAAua;;?r H$??O\:ؗ٧w=?3zNڟr~ ~))!_e0/BMwkd#?VeWڇ?y_?F(@:CO IO?s >h=ꇽ(W{s!?/>;=?໣}QG~n[,qy;N3Y/ {D32ԣLx&#i0F= M21`^zOID1gbvY E@}tH1TYep]Xqz2,,2,;,K,,,VzxXǐO b 3 Yu, !8\7Vdܚ'لy=ɖPEAADD@plƒMWKv]H,A2,Τ ,B,EYA6P%ijW#x"όAc$n`4"+pp8eIgYYgeYgcL'v-# k$xxM'}!u`(o|r@plEcp$ヶIPmwIggXptn<e c!B|treCl $(CI*$a5 g^` ӱ|mCݟ-ЁOY'Ƀ0Edz d΢ YeY%YepYg pfZ๛X?Iὧa{MuO qY9 $ySKLG-lןu,6d@Dpu@AaAD9{<tkA @KhćoIdjgpCS:ĺ&KBX0y| `?rx!#dLDy jcc4l}:̺Yxw%1!{$[Q卍dmG۷WZLkXǣ>6 C/K.lזӅ6ׇyʂǫۋc#t< 3,  3~[@K?o#K6]SWynOeI5%0 >/tL"b>Y ,EVŵq;vPV_@sٳg8kj }yZ|['񾂾5B?ޯS37'n,r_dz9O>'p<9OGYQD"YRQjX+AQw n~a%o[_|J$Z26Jkg)nɋ6lٳbŋYeYg/>Ǒ?E_ե)k}'_Wϸr?^+ҥw<>}b'Og:V 6b>GRӕ."r$tqLD}, !"랞"KF8շqYD`XD6@++9NY Yfň2,G/*?fDԾCPӏ/u9)ak/׿'beq?'#υu՗,wGYL?识g wv|*w辢!3>'2I协lg:{\a +yR)VZc%c|tE ٻ$x[ae~-7׸$E_N,". ; x8@27l`~ gc 8x,ذO_bOK1>LҠK_OԖs)5_?\kEO#QnSoO/%~~`<~^`>PƾAK k*q_?|Ⱦ~F|i) } IO8+}R^?bj_KEIndkL?$geR|Zol8/5C <gg)!1t]d).€ "i;џco$ Gl 2 b;5x82,76!u ㎖n^_G0/5 I|E{Od_o|-_及k}HK ~la:A`3>藟+kwڕv|'v!lO}O}M9!s?dK>տO}GO?ݎx~ !?~YO)W'!djթv8K/g}omI3eW}~wr?揵~>'ٿ{ZXᏴfKZpsj8ݨ@8gG}f![pndȑ$|px;ʗ/e˵1SMl|XH snF8&M,q&Yd6=g լɍ]=#

%jԩmrFɓbE68$9J?[ƩsKeUM'ҼɍAwd@dfY@ppGCF8ch< ؏QD;plu܇}Cn>'xg&~/+hephI08A{,A,SAeL F_>7;q z< [)w HlbjG9*Ui)FjtEϏFL9lu{/O|Mci |1YI`1Gdawu #,_EpGSpK ?v)7~-nB!͵ pXSxf1>ϛ3S%o| ?H!~֏i '&5_],Cǧ_1"s|GCI&b^5J=dBS?ݹɟ#A6WB>.6{ͧ.,yA9`7 M/Bx] "|8&E6$"k^bJ.թ\ZvKO~?3 ⁄: vg,7Y!cK}L8RD(8,: ͟’H ,ǀ ` NB`:o0oî raڛ5e&#r_%F6 gaxC?ٽݭJJ`:!p섏͑;ő7=??ɘk]AӻE=tB~ "3PM}ʹK>FG$;ôd,ڟ1 $˝=׏~NH|EjnÀb~I..6HdnBv{#nS|-s}^'J*ջ|u7~";<|OOBq3= y7u>s8 c7D{8x3p,-E,`=`Iv^ 'blXdYYF<  P<,NaZPPle 0`Y=6x3ľBćӟ;0_^BSD U1>ߏxH-SHO~e;C*"W>JGOGo#toiӟdO x{C )O0[[uP#~e:ΜqeH'r2 B!(&A(`{ĝHIi  v 2 l#701g$qw0y |G󌇨q !͜U>ܻCt_Kv/ E޾WIc:wO!  F݃dA0Pmmㅼ??`= x֟rPɆ'2l;+"MZnoE&G|Fwuz09,7eRFs/LIvH&^ vz_A-fu1pe0 c6 C-FV3#Lz)>Ty//%I/?ғ>v`?DiOm}_]+"HGewv|AyeWKlxC_T?J5e={i $~KcG'~*~V ~)~f~DZ= jԾ XZ8bŋ&biDz!'9C8vkGzCepcx}}dy;  ȳ 8-{܍*0kҟ&~}JqCD~`<*?BraO-yv䯒'ݿ#:r?aq aB[\&VS9V|Drٱ>,O}mK.fb?*]vKGYc @FLܐÆ6aїC}ẲNr=/'kg}i# Z?b(u0Kz16H28" `c`x q]YSpKo?yͯm?ݧcH/̳*GrG1!h>kE{GۯƔg[yVԋB$۵k`Ag܃/k+P|ld?RpWx<Ą,G#M*Ṍ),A׆'htz쳐,g [N+{ ).Kǿ{2weYgPpx` p[ - u#?-B}iNbEb  9|ao|MCfŐbŎpջPM,H"͟*VK;ǥf̟! %zI& 6(8Y#tD89JAwqVRGA. ŋC2"rDq@EȎG>ϾUH p6`$~18vD 0`؁<@ވ?0+{|fŝɌ3< 5fD"OzK~ QydM>Me|o;k+;ò'rld.LLWž!V;If@$l`Rr:.]6 <@DYA@pX@Ey%Vr2Qi(?G PC0b ?~ p9pZyaɋxo[3p(f Iʓ2/ 2L<9ǖxdI[2ٓ(9fs հpfKE-}XB 8zaHNu$n G`xAA@Ad -6r0\9t`'Jg/ܪjYy@!5 aB drرg`SS"B&ԵYvveeeBB_Ko K-<),I胨=c'V;O2EǴFz;(u tϬ%ss7F 6,"ȏ<dOyAqN ~'Q&hc !p6~8r7 a\X]s-yxYxYx^eeeeYve,)-+)k]"uVe/u{oQ6_1+s"lC f8V<Ȟ0g/ >/٠2"sr.g*]o>%=썍#p č,"D=?~e mmyy_^^YyYeYe3)).}&:H@(J%g|_ ~=_N{پ3xה1ԷHw_>9nH>"<<' "N!2lGvLatm'Y'?T0X=lO`$d0uՐpq  AcІ.tw 2Y/V?$ $/i%<+CX3WKǙy%YOe`OI;=a [z肧>OD98#x"cbnAy^#t{O>!_>~{xϗ_SbDSpQ1B$8{O~w~6~ {X~9͟ Y Yl1} /C628g3Hh6!LV<`μ@尳?m$ ,#7!8u=߫ޅߦE|DgQ 7+H{wRe,Pp|B!G>?`~,|_RW~<gşYfϱf͋6x,b 8,xN7ӗ`7xv9ۗ&=Dx[u &}]A/?_̄$l2ǀ5. !689v",'іp|xڃQq"zK/G}GXSٷ))yL}O!Ogrc(~ā > X?౧V> ?=~,Y,X,X36xe Xx^-mxce[meAƓϞ6-Z}~O$H$#= 6aC}`[; ,XG" F"6$pO^}y5^Tྕ҃w쾁'~'|:jg8_0!$? A X Y,X"LIK~WرX` ?w60 @,BYlE&Yer=z[fxveYyx^VmrmemeaF-x:(l12CzNn i|uƁ qCtc ,9"#c8 ",^!IS0`> H~ A\@c<1 , Ym嗅mYy^erY{e8meem6e#xvǃg bõ v?i^ϳ3R`=x0@nPd? V2 ; /vb ܲ8!Y#$w ` b ,E ql ?x=;^[xx^6YeexeYe[exmf#mt{#'3~o%׌Q<i)/GxHNyO)e, Odf'6ӽ;2^!,t3x Hgl'k=pg60AA"[a , DL, zY=;8Fe[xYe@e[e^6^ m+/,ݧ+-!"ty sC&>/e+ێՏ׼r? ~nx2\o==N`p Tc#IX$1x؞wYKGȌeGmEgQ: 9#89=G}X6mmm^meY3m "Gɇ$w^s={kJ ~Y>yc 2풙r_G^=FY}omC<~$X/R3 ?w!1qzMpl<>cmyvyFlMf-"ndIj-kϋ':yyo~uݼ\6~+,q]\O*Kjխݭ>= ϣxIN]wzncBLNZEuaL0 ;۬vЉyD,caCpC,B[ w-mlL^iscއ9 HDQljm;pk9nթvʴko\/c4ey}|o';87r GpEm؟#;o>@~ad#N7޲88VpCk 0ava-aq&/ _h]O'/?_=iٳN/?ʼnTk|76&*ջRvZm'ߨ>r׼ w}=erz" 8p,8@Ssa_ %m< l=C (a!mޭ{0d>K_*vqy]o# yvO%ڣm^Uv*m=[ż=K"r6[FB3𳝞6:HAdU2Ȉ^F8amCmm k [bGȟ8K7˷x G+뉼S~՟q][4վMnOIcǴ %VFf1eI?n-C9l*#Aii%][G| #o3_%~VAlZ^,}+ Wѿ׾xA*A3oJ[ڕ2R-!bVcKD[3GfGnQƿSYC߰1Y?yrWٻ)KIs=KlOnq?Þў0a1/(aN,`Y - ,!C!#퉟Asg|&LEMC?SۓV?{*}}zFi_*kݽ=j-4- m?6A,Xm= O$`mGkcb ab(*[1BgR9>+M a·2nT܋M;H o .J1X[mm7 |7__g9ŵ,F5mQ _ad'R~y"iq*wU+A'gh2 ]#;$cFl) uOKg"؊m,k3I%ӟʼo oΆY<{a 6pO8M7iȕLlm!'}I4Xd\Do䳌DN900{Y & Ӿ}֜܃S3Ns#!D'{#⁇ā@yDQ3ޘG2=%%}|KڷYk+LzuАX=h3_*_(JSWݏ@3d`jxɟÌEλz\w7m}О(P( 0$4!&bt_ HFMaIGV}^PUmm~mxycJRbdv|sXp&Beaͧf۰lPwDOst-qc!;zv}y3=% ID A5qņ@@Z|/d~[tb=&}Y_.-fʵm^uoN X<˾k:HMG ,c=ԁ獡2Hjq@`nj VVÁ:z[><\G ,q@ .͈ 8O_ݞ%_ LW?"tj ڞ>ʚPٸEcm,Hn=p2_'ڙ\ (|pN!B - x%,[1$BD\6jvC=03G]#/yy^_P$A2I~I&\ ?+)rmx-Lv?윎!!>WpC߇Ȳ; dOJ-l^'Z|0X1q"Hi)t* 2=?Ԝ'gg Yma,fBBR4LĆ 0'ewwpqߡ}9V!Cbpfn!1#4\/᭯h,cSLmʫI@C:VI`7 2g( P}Y|JZ|&(xyvlQI ww/;l37UOJԶp|f:8<1o Oyծ`ڈI, 9mG0f)mycc T{| ۵|Z|b#c$s2,p>`ɲJe[ZpksvvqEdpeZd.Dxn+@MՈpmMo[RFվ88ae: Bcd+2+8ضYce͇< ܻV̧{(|ŀ^"PbIBx,ݩFa+¶=ǒdCٝ,828|w$0dӅIz8F&i};,zm\l(6-`]CՠXIKCuB$---9 Ͽ/o<yg,dql[ ŧ%|jO)~Kd bg kXlHwG /`#K!cH; ;5xoY jvX5>tNNAHR,E'ݒ,֩jJܧş0:-yyB,ѳ5jKwv9V>,=Iwrz}s~ #Ñd xxά2=_AhoYͺd""&xϹ ,a|l>=]sA͗s+-:w.3q8 ifwupd-1.9.16/contrib/qubes/doc/img/heads_flash_rom.jpg000066400000000000000000002361331460375044200226720ustar00rootroot00000000000000 ICC_PROFILE mntrRGB XYZ $acsp-)=ޯUxBʃ9 descDybXYZbTRC dmdd gXYZ hgTRC lumi |meas $bkpt rXYZ rTRC tech vued wtpt pcprt 7chad ,descsRGB IEC61966-2-1 black scaledXYZ $curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmdesc.IEC 61966-2-1 Default RGB Colour Space - sRGBXYZ bXYZ PmeasXYZ 3XYZ o8sig CRT desc-Reference Viewing Condition in IEC 61966-2-1XYZ -textCopyright International Color Consortium, 2009sf32 D&uJFIFC    ++&.%#%.&D5//5DNB>BN_UU_wqwC    ++&.%#%.&D5//5DNB>BN_UU_wqw" YQ% *QK(E`)BaEEDPEDQKE$QE,QEBheEKmhfіEmZhjsGc ,U"o)heenjn&[c 60\0DTpTC- 2EC- ] 25M- ک4\2C*"("()(1eAPT.G$X("@@ PT#ϛ-8U,1ZTEimMAFZheZhIflanAm9! r6mjiZq^A8,8܊nr7 rÍ0 ",@P7!8܃Ȯ7 ƪP@@ D]4r^!͞111caHXR(aPPn@`, ETNlNwpgϝBPET @bYD@ ((% XX([  PA J "e% (% BR5s D* A`,*H( DT-  bJK(PYaPP,!B@@T( eJ @D@EXB Y@ @( DPeJA*X* "(* @ P* ,e @ (K (* ,T$ [F.diFdibijIKF&ddiFbbdi`m;``nm9dqcwr8#r8#wr8#r8$TcwnF1x$lSLG`mئdjediTdTAPXT`REXYv7]'vExt]g|twE} vz'tt]wE'ttptӝtwM7rGmGluvvQGnYۇU[vGZvUgduv]88ssW8;#Ds8s:q;sw`p9u`u݊uvGYgduvGZvYgduսugdugfWdgduugduu݂pNs8Å8\s0s0s48r'(rTr',99xyeYHH,[%@ ,  %P*,P* ] * "*  *P* `@* ZT ` P*PBRPR( "(uŲ,X` `X%d`*R!`T AP"AQ( `PTA@T JQ(  P Je$PX) bGuήn@P@ `"XTE!P!`@ `AQ PiA@@TJPT (J[T(JDJP R(!a@P BY@yosw7yX [)Bl ,) Z*P %@ XPPe %UH (PP(,P,U ", H*KA_}t>E֓[}\>UJG_T>VB]Q}8F>r>y#@< {/zO1z0wt̝g`u M.MIcw^VGmӇuч}_IMyPD؞D=%gimm%/cx)}if*=j׉ڳМwSQj5 B(ʓ*5+MDEʢgP<ˤsYnBPP% Y泯8PJ%R(L(RʌH,MBJM +t22&y2eiIfjQjFf&fnjF'&LNHəMpܗp܌Mw%0R$$X%t!eJ, ,N'{WȪPʌ 2*2\Csʂ(Ye, OO>kc2T3hH(MB)$Ҳ$$Ԩ- ̴34$+c3y$ܬ#3P 40PCݼ!Czc}̼=G8z^J1=Xj.fbnjhqHag;nnFfRiQ&@,TQ,$W;>D;L̨4$*Lr-C*2\ǤlXJ*Y@>_z5 "52JʌڬC- [ 7 M ʌ3pP+- MC9=3;| zpWzDg$wzAľsg/W\M3y373p17y35#917%L%ԉEPeUZKA,,'kܳsN[>ԓK2MHC BMC*Vu ,zͲX*U*X Cϐ·-"R("(2ԨJ2C*$P2-d -035 5 JC9ɖᆡ&ቱfo&ZfᙸbnFffhjsbhbn.&adaL.Vk1&J,PdE"y:zcMo8RIE.ZfQQVuzK`V*R(T=_+x R MB(P2ʌ-B,"(Q2+ 5 5 ,$&w MdJ342ʌH35 Qfw M Cqq5 #Qq7#ɜKfo1əbDQ*daXr%=;ҳ~w<]c3BJ2E$2$Ԍr2%bTT  7sqss|N=sXw{2utzRY]N՗3tsخuv+v'o_=G= ]:1=lfd^wy^sw{η痳 q\.#n뾇κ>N>y~mu<&,J33CpL—9ɉbnKdwnF&ቼ˙jKg;fQJDYS:EKO#=OknEjj.-:TfiYjFZ$[/-,( K- AρRscfZ'CkSosuw'N=/#rzΕjofocˇ/[LJxZ"!/SV7Ws׳~42+u 5C3PLC3JwMC3PʌMC3P܌pW3PeLH\Ib\35 53dIC3Qfuc+8;=nSQaeea%DE8hرeQeU% UB ,ȹ"͢4ȊMeH42΃- LZ.42(ʌ- 423421t3BMBM*34A5 $23y35 C3P$00y34MC9LsR\M MHY43p\HgR35 gy3C935~{~cY/vYʉ435h"3R$ C-GK(e*QAe*QEQP H)I)55@J*PZ @I-2R,(dJ!L(1C5LBM ԩBMC3PI5 ,$ILH35PLIs5 PdԈr Is5 dLdLPII~_O:XERa&&I`XE=R%9Rز)AA@4RJ- >sΰC|;>~Φ]<^ S7ꯛ]{Vug|S8ӏ<zWz}& eaeFTXIXIIdG×UNJYAAAJ , eJPRШ- ,Jw*Qr7p7IɀyTU\kN^SPgpgyEPΡ Жf&EQ%XIDYRjf3fj:fIk1&.fg;IfTj.VD,esΡ1K9;;ϓWͬ&EQ%Rd@eDK 9ub,”R*-(*$<@N>^6B )%j(KjP($(H*($(4ʉ57 *M 5 $2Ԭ 5 5 , C MP370\Mw#px&ce£9\ʌEY3Îo&qΡa;~|垏6wxu5,"YMB,$gQdʏ as((*ZP-RZYJQVERw:8xYEu}ERUQOߕ'>T}S7G>X}KG>X}KԾX}K33/RQ/Ra//RaNN}fO>d}36}} QSϝE>zB} V} }  }~A|({!O /Gxps sgL>wk|=?g,rqD`XEBk$,QcPN^N.[EYEYlYKe-QZq|ߧpW/fǍ}xاU^=KK'}AAEyN]ǘǘǚ}==tнtwE'vtt=R|uaj n\s'SNXqq9aNQ/NQ.hNTq9 .bNQG/q9SNHqHq;ᳵ Ƿ}yww^zv1h!$YX.3oJYEJPPRP[AERYTP,5'Nx۱b꘺hfћDZeZYEDQEZheZjZhet\|:/pGU7ptu'v9))ݕwI9GM9/IGMw/MvGnGnWjWjKvvQڑv֝ufiڇUه[=]lvㇱ韤8ukV!JY(ŒK 1˨ rk-JXJ%(lMJ(ZYh}yeO7|mKZFZZhfehfEZG$f\ki塖ZheZhelb'::::Ԯ:::PH:g~us=gZvVvu]vGUً֝u]uugk'^vbb|bu<8syXϓ>;zo=BXE@`%%QD]@=fX,YJ eRQTTQZ$>dzDԩ=SkkYn[&Zjheaaf 69nEnANQnAnAGr;w|_ z~wIܒuvRwӝwM܇M7ptttzGI݇M/O=ӝIܑѽtzއJwѝttg'Fwѝt'xt'~ އG>:_Ǽ|+?ae'w>d, X@!,@>\Y5PeTE-YFj٤U(C羧l|o^nwC-XC6(")L24NGpgpw;:Ã<Χ/~E/x}}g 9pC8"vtìˬ;.;SӫӨ;sۦ;n;øüC::=gAl.j}}l|W{*ċYdKYA.~k9j,T*QTT(*-[4,,вճ@Cx~\vT.{[/}Zy7է}a_VMi=Q=aQ=ZyOTC;;اU=x`x`x3ۇI=xlxlxlxhxn+WG^7Ob;؇=`x^DǓ=hy/Ty/ZSG=HyN/dy@yч;;^C;:C 9!92&2k͓&fo{ޏn=9zN엩{c{:N:7Nӡ{: yuiJeyӧ}=Sէmy7ձVK֧ǒx׵㽁_\y`y^;;Y=hxhx3/⽡===Oq$ Opxopxoq/džOr$!Or܋Orۑ==xjGhxۇ=xf4G=^oZ҃J@Z - *Ъ*гIi M ԥJԩj *(, (T(P  BP!`I`@X X EYQaJX%|9u*PBOZ b**T Y(ZR5)jj54[,hС)j©ll J((,al TT @` %* `#6RX %D Ρ~}c['"jEZQDZTUQTU)fjhU)F-5e-QKV-ZRRYE%PP P,JHA,QDK@D.h,T J$,?>'. )Oly鈪UEQVIU&FBжRRRKTUb٢KfjQTYTYQBPP ((%@P,"@@K $D@XRχ.`gw+&:sRKTUQAT[hQfjR١BQTU- TKe- HiVhUEPUQR@R(b,K@@$,@*"! ,%",R\Ł`uo9*F-EQKEQT(URжhUKe)fQf:RZ-PhUU@*P(T X*P K@@, KBJ,H, (%r@ 98}&z¬EZUiZPAJQTUEhPhjQKe-SEB١UjhYbJE4RZ A@%(X* @KA,"$,D(" ($,,?:/.} 㕬 gB(4XԢZ RM AjJ5)lhZ(,M攪[*TAR(TX%X" a% XQ%Q4J @%j*Q D$ _}<>;dG͏5.>/(> >#8X|6O/__?@~~?A~|?A~|?A~/ >|£_ >'>CO>>|>|C&,>'CΏ|Cߞ}ٞD^y4sNyy::5{S83)%i rӆsS8gc' p9 8\8\Å'%',8c xy `m$l 6s$#L2]$M1M26v-6l ̍3Kr4Ԑ44#LL244#L224#LҤ4 *"* * * * * * j * * P@*P* @=oƠ@@A bX, B   ( @((P,Z>~y)z"PTJ B@% (* T) @  P,*P) POZ !h"P(P (Q,P԰ P:"PHB D(@`DxkQKXP@,@,JAA J(@-@ %PY`P*,X(@}O7 PHQABP@%(H(JKL2ГC-S*XDCC- ;nA/q)NQ' p;SN죬:::n:N;;==yGGJcAJyOPyw[c[[Շ}8yPyOGqY٧U7yW狨EhE,}>:!>cvUEDPQEDQfQQDQ(` BQfAPjHmmnGC9\C#NWpSy9;8=X^;.{7;WCzTވ#|wzG|z4zW͑-^lzȧ|z!^̞GǴaO{ZǷy }\}@!@B  * BDUP@@(DZB,-ϯOޏnZo* *  MkӍ'!snaSW{>3m[ a, %ss}7Atߝwޝ!qj5/cَ7;^N<&KuyZ^~nNM@H,)PXPPPJBhOyϏ5߮ķ#ETÐqqQ8 r'(r'(r'(r'(rӅ814nM8r2q*s*% q'&u1PXKMkt4*CLR+&M3J4#L24ȷ$#LH4#L24#R 24*4 "* * ,e"-\2DC-] 37W ӍN' rS80sbw=::Ӳ{Uz;cӦӦ{;===yNǖ2yJccԇ=:yoRcևGGK=jCՇGa_A/{:W:n=S8"(@(J#%@P( (% Q( @@FP( @aP (@(@ ((P ```)@$B(J3!123P`p "0@#$4ABCDjp_ʿ#'WcAAzqo$I$IQCq!8נ  [νgC!Ld/Q^':NSu:N+WS -R-R-R-R"?'I$I'I$I$I$I$I$I$I$I$I$I<$I'`                =Yo-e>YOSi>YOSe>Yo)|,)i|*U>YOSe>YOS4e>YOSE<ڼڼڼڼڼڼڼڼڼڼڼڼڼڼ<<<<<<<<<<<<<ۼۼۼۼۼۼۼ۸080 .0N18N1LjcSƦ51bԱKKKTKT ?,(J\\\\\\^^"ȆD24#L24ȆD24#L24ȆFdiJzzM:*tB> G(Ҹc{\I?ĕOx"6\ymFs/U41[bQJ4+(:tQ[כDQzN4N҇vª&Um4*5uLhTkl)nGRHuv#)sƥ]`j-_ġ݃)eZQ. (R*֦)S6.V]h{GXPUW5A\rU΂[sʕ{sZ +יѾo ѿ$PFz6K! m/𓼡 \R-Aռ,[~RWpF*kUʩ gSsjWi519Nr}V=O,A}CNi/Waڍj5JmkMGxxx1%m&j9X6g7z>6էMX־DثNT991"Re61SFI\tшM:1~Jte*QmJJ,Iƽ\Wͺ6#s&uц~i-.Fcؕ#F *^bvOWR1[;•5J:#T<)x*#Ze%<ćӋ)-e1eJʜꎵuHkJ)U1a)^STo5u5 oڦ'Vڈ;Zk,a҅UkѕTM5-2=DG{ ;84`§oo؂Ѣ^1>XG8G0Oa7Wh=~|c $)yi|O:_I QV4bJ^C)Eiׄ/ I#c1IGu$/UcQ B/^ ax"*y0OaNfj$Tok]-W+IҚO_r7ޞ"n^ìRӋyUXJEak9fwxzUmIzwZrQj#_ ԩ`䩒*#Q%t}x`=x%k)M#PeHaQ![U_j,uZjjhN|u+,259*TcQLJDj­Jg*ŵ[Mzގjw̒%_-9J'fUFHcƢz֪֧- vAD⊨/_ª⊩U?*]O '^_~h>:iLo4_H**{:iF &xJ#DbbX"JQX%1Jh=!x[G\J**(-%NQ̴m9GZ%!V]Fi{Z4 &Ž"W5rx(ubO{V Mk_WH4SʍrU?+-IhUwS~q&_#US#UsG9 nqoyss *\4J*ʗ8?Pt|Z `=WxZ '4rGA&_oA&w>Ct:Ct: BP% BP% BZKIi-%ZKIi-%.isIi-%%% BP% BP% BP% BP%>/ ~z-/1~k=L38̦e3)LfS2c1e2LS) eC*PȆD2! dC#L24#L/i{K^/a{ ^/a{ ^/a{ ^>ƗR.]H}2E.]H}"e˩/_H}2E._L}2e/_L}2S/as \0.as \4.isK\\E;}:$]wуVxUW+fSeLS6T͕#eLR6T#eHR6TMeD6tMgD6tMgD6tMg@P6t i@P6 i@P6 i@6 k@6 k@P6 m@P6 m@P6 o@6 o@~%k8.qs\8%IRT*J*J*u:NSu~Z0hR)_ ݕ:AAAAAAAAAAAAG#{}~kF T+<_Z[AAAAAAAAAAAAAppQݾmGу}=S<_Z_AAAAAAA 9\5`    #p u  <:/D zk߅~ސAAAZAZZZZAH(ʵ4YŤAZAAAAAT;yEz z._8oAW $NAiAiZAiiiZZZAiiiiiiiiiiii_5^_z z.ƪKxYVAAAAAAAAAAAAiAZZAWwt+t [~stQI% BP.B.i{ 20LLLLLLLLHHHHHHĥ(\ܔjY⁸n(⁸ӛ⁸ntNntNntNntNntNnNnNnNnLnLo4Lo4Lo4Lo4Lo4LotLotLotLoLoNoNoNoNT"jw^)UN -]uGj uAnmr啋+U,YTeBʆ7cq7b0P†Zaiiiiiihhhh::=9NcӘ=9NY,Ӗi4r( 9ŮVY-tźb1nL[-#LF)R4iHґ#JTS?Xc?N9:rtɠNu Q.]H}"E/_L, syH0hҴ f f f f f f fffffffff*f&bba&ca6ca6ca60,icKZ֖!B!B!B!BU$M $]iuo0o'nM[уDTXZNO4U%YVc.OhV_SYj A= ;z- RK*"Sxu:HR!HRCq-qk^Zז<,YPPPTTTXXXXX\\\6ͽsm6ڃm6k6i6i6zg6ze6Zc6:? E]}6%JPj A= ;z3j=? Mތhhi i MMMa@ Q9Q9#R9#S930,9NbӘP'19Nb1ST.9b㘸.9b㘼/9b/9cP5cP5cP5NcT5NcT5NcT5NcX5cX5cX5cX5c\5c\c\9sja9ja9jNa9ja9ja9jjjj]VEsA4kFDFhB#AAAAZZZZZZZZZZZZZZZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXc1f3c1f3c1f3c,1e0FDgAiiiiiiiiiiaiiiiiiiiiiiaaaaaaaaaaaaaiaaaaaiiaaaaaaaaaaaaaaaaaaiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`#љPAAZZZZZZAAAAAAAAAAAAAAiAAAAAAAAiAAiZZZZAiiiiG  >#0GY;,'N+Xg}X?,f)xXa;oo~)x>XcOΟGĿ,S/ WAs  !H!HR!HR-R-R-R-R-R-R-R-R,R,R,SƦ51bƦ51LjcSb1F#b1F# F#Ć$1! HcCĆ41Ɔ41Ć41 m1! Hci6cCu%/1< /0?oAp ) B ZBjAAAAAAAAAAAAAAAAA}c18t:p% BP%(J(KIBP% BP% Aeclm6+x?mʔ 0 `LO_ܞI$I$I$I$I$I$I$I$\I%B!yd2 C!ʦE2)ȦE2)LdS"ȦE2)LdS#28/q{\8u%ĩ*J*JS"ewٝwf CE2)LU2TeS*TY|*aWԓ VR_?WE MnIRQp_NNuSwqmEDʦU2L¯d2 S)e2>S4DE.-EFAWjZ$/s5֮Uյ?muY}5UUZ**\/Db RPc:W"BT.ljkW5E=r8 -R-RKTKTKKKKKKKKKKKKKKKKKKKKKKH- H  )rB$I$I$I$I$I$I$I$I$*J*u:>S)jjb{aЂ R!HR!H !HR!HR!KTKTKTKTKTKKKTK c1f3c1f3Ɔ41 hcB1cKZ-ijBBC}Ct:Ct:C>ӡCt$I$W/  #_*`1P A!0@aQ"Bp?_lc1c1c1cs[9l/%ZKIi-%ZKIi-%1c?!B!B.eeyoV\]X\]X\]]Z-gD!hTZ!HP <WcF=yь% zXm,?QOՄx.^\'|G|t LA;"x9|crxu-QnX D!%+녿0Iv5((϶jJ"*"&jjjjC5!?oa=I$I$I$I$I$I$I$$I$I$g7123! 4A`"Bpq0@PQaR?#7C6SFJHjNb)ߍr< * MHE"a"}6#\EO 8HJd" HX#RcQ$K <&b' ; fIL"hM~=NFM3J;C)fO#*II /X"rD<"hO LYఒg3&YxHFM 't1%b⇄U*x 9)"^*MO ÐdgHl#"XIv/yf*~ ~0;4GUrUς i,Ŋ"&R!qCYHUHR'(*,1QT(2P~I/({$3<#":vso*P_%U3(Y.\r2|ڊ ~9~GA=y"oD٘[9-^M1TL$1,2Q\3Eŗ WB<;ka9&LUHBO E='đ!HYﰉB(dGؘqGHpr^:l(PD$ a1Eff2$E3<(xLBrY/ LɜE92rB_bdI2zD,$C,bd,\Dܟ)D/*ysTB:#+l23OJBh> >2$>JL|s\1{1{ޕ 2ّ>E(,TI`̉SxOFH~dBn&~>3.8123QQp%3Q bq9!=NHD}]qqqAtAtAttqqqqq2rL>Yd\.uK]R.)qK\R.)ZZZZZR)R)R)RJ::q8N'q8N'q8f           SJJJJJBRJR*R%%%%%%%%%%%%%%%%#r 2jI ĥJUTC-}_|{EOnJnJgrB*MJRu+MJRu+Ru+Ru.CrK\R:!Թf}\[c'BQT r%K\B.!q \BP.!q \B.!p\. +VWЯ_B} +UЫRT.C͡hy<m1(!QbeۚE%/b7o?cx:Syo=M穼7zSx7oxннлллллкB\] \Zš\Zš\ZUTeQsy9I)="zg>IOyo bzz^uyI<=Fy~>F`8O.J wžzlOAy1 OyIsba6&3mL'0O704؛S ؘO7>%ƞn 0OU|0F'L&؛3fm &0[3ᗦ& L!B!B!B!B!LaBnc=8p!?4YQhE,~Ƌ4_hcu7icMcuwCwCd;a=Un?\"e&s22*TTF% ^U>b5>$&donaK)J_>~#ĥ3I9F^?;Mqry&až("w^>Vx!+~m"/)<×6? 85! p*[c-F!{pӸLjyszC/Rc=m/ lOɧ< xs8p3/CFhFq3.7hH}&VD)YN F5 (6gfg͓Û3žoBmlxołО"L:݂Hs"pG[x՘g Їj(AO{Y .f~LICs!2fg )/:܎,! -ŽIY,?a w! '8xl#Bʕқ$q?`Qhʲ{`M Ћz2Mt^,M߸ä3=s2k̉~pjTQٿ1 ϫCA윅s%V5?sćtovJQǘk.TEL'ƉҊ 7n8ure{t7Ê}_~TI˨DǍ2:0dU3,_/7z ʕ9T]u2CfM7U*iiߋnÛS 6 wof5WiނUkacMoMuorkzhIM;Rȼ3>LuoOgϊ7)Yn\UBJt ҷ3N&嗶{m\Gq9ѽTWw:\wrs!1T#(e.s :k&r!igFFo$3&vCMdOǗONL y8W-lXn:AցotD~4PwQyQ4dIFu(iL?{ hx-\dYbKs~w>%~S' ݖ䔜Ъ6D>*ZI̸%Od#mRJG(Fz/;؞#S8=\)WL gJΘ0d !$"C8f! 陠Q7An3voI"$w#$oIiS=Lo)- 5gy^?l¹y6C6ulwhLhu]{í mX8T)F{*Q)/:W1Ş;؋1z3 y$ݱ/"{mx v'{|f໕$fe cX9[Lr3q4g)ОWj!݊Jq'E O pQo*%p-CMYptfO E‹ќwowp{.h揚>h揚>hZq&ҚS@iM!4ƘcMF k湯-ˇʗO8vTϑ{0|γ|/+;4w[;;vYڳgz;wIğ|I5=Ӷ;(j4SC4SG4SG2*HOs!+#t/b4CGp;v#Gh;dwH#Gd>I$G#Qt|>u&>L3>k|s[k}_5{vgavgiNS bОoa/cAo5;v $|G(|5=Ƨَ-Jȏ$>h|Q5W5>W;Mg}4~Z}Q_>㏎>8"](kqx585 CP5 CP5 CP5s\5sT5MST5 CP/1yQqោ3|0aď>|8 =/lYUy<|x#ݳgtٝ;f|3>9 74{3Agu;);lwqyqS09:&"&i{Ey6aH8~Oo&4 4M4v?9oooooov[v[|Hg}3qlW~U|paG#|;4v(QwDo#B4euF|,ϓ>vڿN;W_ӿN;ӺN;ӲN'_;O'm'oosa\k|O->Rk=k07mq<>h|lV|>U*ϗg<BEHzϟovZOzO~{sQj=ggqvYgecFi4F`Hi04fҚBhM14ƘC@hh#AF4r""Eȋ".D\".H"䈹r2dDEȋ".DDDD\r2'A+w' L;m4x} W%pj?K_$$Ǽ%x[Η[ 7n<HԨTU̺s**TTTTR~'GggO?d&|qE#͏ >`||Q|;ċGv;tv5>k5~j};(iih4X\%zyj'BOLw5~{3]_4>iUWh⏎G#d|>!;4vhأGxtj=ѪMS% Ӎ>,>PC>L3ϗ>|?gdٝ;Fv 3gp;vv۳gjύgYgß|)'ßixvfDkkƘ? 4üm;hv$w$w;d|q`K=GEPxB+'l (EYEr((aA|^=ᢹV%X+ /NӇ:Nt FzF~#c tF :N@:hG=AtAt FCDE/~/#~[@.?(u={W0! !$! `! ! B! BB! !Bb!L!BBL!B!B"! ޕ6لaz2e&^ox sg_ }n//їSz=>! +ϳr=+痤ϲ2/;3)o3v7+d݆cވ/6Suǽ[ߴrNZtoWb—RJϤ<嗩]_4pw XRds5 dk#Lh) |ɟ$|!4ҚCJvQ;);I<6h|osLititi14FњHi !4Қ_sJiM/4ҚsJiM 4~Қ_s 4F CDh!4CLh14#Di,m&5 z#@h2_^ǿ4]. Li t$te>Ʒ5MI5&&毛¾HCPk AVu nNR''YmUQIxVм((h(uMST5MST5MsT5S\5s\5 s\5Gjj/9yuGQu`:3g̏?$ԏ5&Ԅ! Ï""""" eldQE7@4D4#H'O"9'ȑt'It(:Q:Qt(EEErErEErEE_$V^rErErGB+|||(?G&EvBqJR)JR)JR)JR)JR)JR)JR)JR)JR.)JR)JR)J\.JR.R)JR)pR \/.M8٥kf{ݕߨop3<kl7_\ SneXFZgNEȬ W"HF5iXhX֙ܫ[es/\+uGYux!#rit3G C^ =f4M,L I$N44 , @Mc$ $2HjI$AAIAAAIAAAAA """"""""ȈD! !  FFD`A!CXB`KC-E˙2.d\ eUffTPp)ԉqCQjDk|QsF3*PԊ\QeQcE)JR)QQQJRX***#Al s  I$ $  I$I$AA# (?kjƠ#X5_ y3g̯_2e||3~L3Wϙ3gϙ3>c#1aּW2e)K)H+`J CMH1I+{~ . RJW4llIBYZ[QZbdTR7q!BFX S7-qsE &if7ިN Y4kx׉,|(̯b*8Qn2 R+/6M<31rn#jN=8ݓlKu2 1 v/Mc~ ;&Dgn^T4+p4=x52!(4 Z\|||:X:@2QtH#RByI#sg:gD-:@5p)ṗ5k'<'eFFXdddeFFFFFFFXdddeFFFFEFFEEEFEEEEEE*)|l +QEQEYklE /ӄrea33+33333333?;ᙙEQE`Q[EU{hy*  (eY|(:γ:΢9O3XDWt3@4@4#Gh!t')9r I:GH):QǑ:H*'*:GJ77 r*Ni,`\,$h "DE"2""lL!L&fzxWq.)J\]V.(lck-.<(y k9K-,,K " 0(.>`.ڋ4\s=sClz(h8u> !+Juo}͖-bX0I Q3}F;o-8nJ 0| Ï|"Lil꾬1 B= @aDE|!*঺mh0RA<:'n߼/K< yQ yq]}yM$`UӍ|zx q2$}J 6B M0 t\1* o;*Mǜy-||Y[hdt$*(leH+  (J!"AD=0 ufZ4m}h9bm-l4Fw﾿yAr3G~2 dm  _Q8"'8flL0[ 48 2A{;ܳH oHPK8C',L0ϰ!%f[P5UM90^}׼*Y2}1({-,? (k%8۽wJpM7rC@=Npg}1bFEo<, (0R 8pc=rC)ۜ08(^}Y)5pE0z0=֕ `ې}^㴦O춍qx%O@WxBE/4ormE7Q+ sκ# g]MY)C^<~h>v,y p+ JQ$@B4,a9rMM7B_)S CD|QiDo| AGD*+4Ks D^g5-<Vu+z gOrj۰q4Q>v[iWqL<}2+5 1ﲀe  AV}Cϖ3O?{5TC;r+-]wߏIFSy]؝ 3)0}!)P/<,6lNQ$I$04[Rq 'U2Lop}F0îTs58 ORCO<,q 2AhߘA0Hp8,$q3PK ! 49=zӑ8XӼȬKKʝ,K.׾09˜k,pO{aI w&W(- \=ihAO L5:S<9-&991TQʜs;;}/yqC q0T8b{g^L4Bpj 4AS3IZO(qsH}0k(`]M1(\vlg5C}0eG T@ a,BI` 65zQl_iqO~-M7tq\of<^B,I{.K? 0 4 aQ xѕ? Mw]Eby%7'\ܤe 0gkI> `AW|.9@ Ʋ-߼N<ÿTovd?;,,ȵ[‘+r nĝ3"w:3O~<BW3׾QxM<$A0I|]gs9oi7=P]x{ Y>4>띯S(k-&<=;IevUg$ٳ?4 !w̘ʷG:V7% ^=OLRȲOr[ |{[~ ~4 cosnjuVEOҠ@Y@SQAuzR@qTc[SI]espݟspϼ <QU;)cR}|QuXq?,1k/' @Xg5Ju?.w|ɗ磩л2>+jkx1f|,F,36B,wv<[ w]D0뾜{B7ݦiu+v q$ K#}$yM ;(< `JwVjzCnmv57*Y?vZM=H z{OrӍtm]ymq%.A<0.I?O%~8b]m^_9♈-\05n_FBǨJ5 8#fWMuumfTe֐22T8q<2DV0TSr]Hdoh4Ͽ?=sHPIVs]}׾yy9$-Q OŶ(WtwXy,JϞm\D$cnw_O2>i4itS<5M}qj tGj~ @>z|L}VJ\W"}68G.C8;!M7L80 3wb:" vճк؉ HH r 0I&M5qdPJJ0@wm?_X1j UÏ?( s<%~R(C(߱_ZP)4=lQQehcFz1_rm97 f 4Mqǜ681 -pR(QO(1IxuX= l.|<#o-a(s`{C sCy5HR}TqIdXXz9ž,U$QuTBhcYƜA'9~qEQ CitnLh =㊿PU"ڼpl9 nlhܪaXU_eI4Ts#}G6ꍮ߼'> |ti?n! 9>eFjEj$l(9Iy\+4 c_/~uUXBD|lbO|F]Kz`zKFk=,Ͽ\4wDuE^}7}cF0 8E䀞 %abrݼmXunc୰:8{GyڥW ci}]AG<0Y/íav&@dP1>=}#]=^Ɓ WT,WMqw'qaQEgDr,0K,p5uB-&8  Cs]F0Mx$/~8*H=]oe* "<>8 [k9 L0kQJdk,l<ڂL2uPp_q|}~_clﻭ;N2ָ  0M4qO(邨.{ 152("׾-qC]TIq/eP}~0s0 1Ï7EhH? rtX]q u-%иǝdK⮨2TqwMQqֺ眷o'{{,:xm/X5lx?  ~`0!J4؄Ͼsex? 9,p s 0=<3_< `0 0 0Ӝ 8Cw}A4aYC $CLs 4_0 00 ?Uy_\࢈0} 4@00 s Lu.9Dn88=<|g\OLޝdO){])x}v/ckBqmcN=yWwǿ7ۼ;^^%sJR旗tR)JRҗMvitRu7v]7U)JR.^-|x4JR)JR.n}Ҕ)JR┺iKtڻE/1R\]/9~pnإإk UU磑;2}ubT <4CM 64ӌT$! v>RӚTSbj ! ㊈Qۤy,|y_P |-`뻵؟Wf5箟4FI42ц6@t Iva 3[(cmV:VVWYJ2|7ʄٌdddeQXV&..\NoਨEڻO:|#etD'; )JR)J&R)JR._Ft8GXBڈ4K}p| )JRc|! 2 7VѮR|>wslM&>8x"M2cྛiб1YJϓ>JVVR')JR)J]c%G0}1'+!B22=! E~$y#HDDEVH!MI!&&URR)JR)YYJVVJ(/ v!4KMNGq^OOz>w◵_>N{15u>R쳷u9 jt4ޟץ+}Zb222?exex+^ W࡯,=c=c؟'{{=c!{{'{{$ $'AU~'~'~'~g~'~g~i"">SùМJRҗbfk7<˕'+4nM3\!222221&FFGx(VbUYeYe,+3:14JQEQZ"씫****TTTTAQAAI$I$c8z\ZE(j[޽R$?;W2I'Zq4ݬ)GਢnTII|I{ZXbLd#GFFFFFFQEQEQXYE굁L\_( 0!1@PQaq`Ap?a<Ҕ{4J]R)J6R/OK| ~nB! B!BbD!1 bb3T $:EB!1BkiJR)JR*****EEEEEEEEEEEEEE9?BuЄ9wUMO?O{t!:9~>vnkBi!B!B!31Bb33A3Bh&! LB!4BT!Bf&sfjy܄!335S333e Z&&f!6 T&&.ao\BL7G/,I$FAAb&RƷ^> (/gȍsitm.Ԟa6H|˕&!4s Wa#uhں45MƆ_qkjmrwlj(]+ťQ/"!4੉ mcl HM%CC1ȗ fXp9V1qKxc ]'Q}'DQM myd"a"GMWBqLTQBQne.4P #6.KY5ϱ!Eҽ,x{AعZ(D66PbBDOƁ4sHUJxΫecE.φlu(3090 MT)B3\¼Ix@G.u=e٥\q銬$El)zv<=S݂,թoUe,ZgĎ1G8M6;p\Ocg+ zɵ/OD%$x"$Rx"EFYg:zG'cyZB!Btt d2=_0F/L$ )YYJReeez+exeee.!Gq8N9qLy&!sfk U)JR)JR)J<5B!BhB&i{8bm,.G>itRl,ce)JR)JRůe+,L!B gBT7)JRVDo_0JR)JR<<قp<˟DB E)J^?d!aW)JR*P\ \ ""/^3DDD!Bc)KL&!B3atusښ&Qͩ+w= ѮKc侙|σ>^ xexe b| _*TRC_RzQ=@W` G&hh#(aB$! BDD!DDB !9tSrjWEiK/?7?P_܏Ns_\W!;#'yޙoKKtt݄&TUyE^JQQQJ8|'|^ _>O|>IO|'###QX^ xGzGzG#z{Oi={q= (5OB0'{g߂~~ԸEE)J(*{a={q=-XI=__`q########(EtEUT_%+}G}G}G6|8! 3YY?VhMKGԄ_Y="CQcP1wXj1'YBI$إ)sJ]/%)JTTTR#AI$|I+=*! A1Qa0q@ѡP?f'9';rNs=z-<^wg>G;e>;oWO^#mo~/eu',E9cQ㷽gy>َ͝{}q߁<:rw/ovzolvO߮'[Rsby$Y=K/~Xg dG//8gr `,Y2Lx,,9 ,K$ŖARpe {Ľd| Y0"'8YWF#/BB5Y_CIbb38O6#½,ee[#8Y{6LH6tμ;qՊ`˜~O '8=x$ 5盗u:qs%8^YA /2l x[=2FLNxuCK,Ydac`[p0,^X<,sչ62+9bJw<"Dk&bP_DvmL;$,G9[-eq4/>s9\[x)["Ovm=t,F؂syrx6Nud8H7>dIy+`{}ALL2m^A)^Cd,< 9,$XAieYai>1ac Lţ//7/9̆Y!zH9N,7}?8Nm22Ӻ_n13ķ ղw2ya>'C πF &yuO-%7lzlľ[eɜ 4܄ ,=Np&N#I-L:pl̞1w"o$%w9s>g8t5! RGyx1-Kg͋&/}| |x>C?,PdXxKss^cf @KU̳& ,o9y1i a'8Jףv/&v3[;1:d-V$Y԰ y3X;ɾoyFT>D|! lHbL-fu3k KCYdO>мT-{l8#s{lepyg4g u -}a,̃9q8sxM,`2'N2|zYO=?m{;g71Ǻq:~e/di/IXRu|I}0;'-X>Nl> - VCo2|r=I$KiY-I<93N)MY׉#9/u^L|uoAgݍp~!=8M G{>!܆#d p> ď; %ȗ>Fvrx/3ō8E痜X>̵IB&&3P7nB,w8Cfߏ޶m?_\=xzsfmw,{8ek aI~~/v~cY< d}lp-xN̼S#/m7Xd/V!a6Gd#6s6/x}pH-`goLKemi зe%6 6mLapȴ!----5MKyqmy%mymצsNm;~eCi6|s|?mmiy8_ v#wVesm}3,k!}|-8Wĝ$pXvi\meCJKoBixͱ3amfؖ8f-mm58oMZj\kx)Kjտܢվd;Vtµ.4y¡[OHcԅ͋E$@b0bE6dnٳ`! eHOQ"4m )la2ݴKIx_ʱ3Soʼ o_ȽwWM q^ &&鿁gJ'?;'~OVpcce?>66mZZjǦ9bXccc{ce6X{#{c#{c{dIJdɍ ضbɊɍcccd6Ӄy쭭lכkkjVyV˷\ ݻvZ݄ďk{K ̀ ,\AK aa!cXe~a K ~,2XX~/,$,/"OXXXe兄谰$>Q XXXabE,|ΰ^IcƶñzGŜt8:͘1:FR͙;fV[_nƟ?EW-O8{'W/{~,OTga{s>_V}[o5~) >>qw;y#nmo-o56y[lg^NfL>c}^9,'9e $a8Ø%alsag%y͞2ł͜rznyg}ٷ [ߋey /zzږn^ N=>'C~g> "'sz^$aIݓ} -yw/` գ)lm@6b p&~Qؘ̈́͟fry,[q> psr6)x|x=ޮm[ {፼4Xs_cy{B)G``g3'xm ol2c%$儐Hp6yє[ /t~ fyz5߂o,zAqρ=[l86π|v܎ zIV_|2"OpN-Ac -f6ϏdYϫ|bׁ9!amf|]3Hz?smG6՛m6 ߈~;߁m1ݷNbsb{>(o;op|ya6"M#ge{Ƚg] P /sXommo-HTMecC;#6ˤ' e7#yGp^g%m? FYyfI ~xOBO"egqOyo9oɗ8l쾧}/~;.Ͷy?f?f7~||#ll L pπNY<}#6r[ 9[ q/1l -7x6Is z/^7Zl7+3<6̶comç~'88p @N%p:9 `92C>ѓϨ[c}l83? `#[|ټΧ~oV~lrezo_1p9sG㲋s~g ·?4ijIcopE@33om[͏>͗Xwq=L}fr_~{_] xl"Klm߶#Yy!7??-mpٙGy~I3>|yA8C~ /wC~cߒ' Xga߉>c{ eߒaӈBo}p|`WɅa $;YVy1x+`d$_Գ72x,=37^_Dov8O[m߆O$g0_Ps,q8 \$ǡgY$X9e`6Cavbe8cgd2!$>--6wYBu$'g͗?ŝ>9dgs^}a~/m/=~?,;ʡ=," 7}pdF26 $,xdߛ$r3/l9{g#]LdljNs,9{ݜi}^{S< s:ϖu}<06s8p⭐p> s<|CgNa9g2ܲ &Cl$Ext/x}3xI']a~w/Ω;;z̳xg|}xvq/~'>)g1Y8Aгf΄: {w,#,[ 1/2?8Hj;dUlg&\ Ljco768٘Nocǚٟ/x׌3d~@~s8Y 87@2 {]XY9%`t<&,>IbdpwFF͓/o'T%&axx&'6m8d3g9-O2zalφd<',<&0pAds/67 ;9yb'7ddE@@Y^H6O$M,v3xm7̇qlͽgrwD07O[Ǚ}qos}{>|1e`C,syD,,Áos;:ޗ dA%sd@pf_Y$N6Ğcq졭pͱZm%Wiiy3Ŷюly/'8_9Y0YdG8G,s8pîdp,6 s 8/<3'or3dɬEoϫeZw<{Jdq9tΤ|Llpϫ9@..Y 1V"Hlώ|2dB6v08b,;Y+$ zY F9#/I/kfsD6oV^/s|=y,$,_sořL , 8,;>dY-dɀp$K8gI'Gsɂ,珼!e{s#н,>G{kyf6voo>qS,,D̲, , s; /2;,~`8giyy;yaaN9A5` '%| d IJqY^3ĀIcg,=}8/D᷄ݶ>K63!g>GI8l̒B,v,sAq#$ $fS 8Y$c,A̋$8,{eAp,8L`A ݑ,"#,c2K'g5mML1c[<ܕm\g:C? ~,,de 9YedAB,e$a'1ld`/2 |K,t3fŲ7xE2H,$[,6,"HY82OXou#z0ϑ,0|!yγ$p,dĩek 7= IddXXHkdYXY9 d,`YA$d@'}JymAāuYfHf6NX?W;fgx$=}CǛ5~,Omg8zds>9eG<, \LYeAYYg 6IYg2&@-Yg0,3dea  9 ,3"IXeI;qmgMIeI%x<3Qdl.}?%<{/#xYq<8o $N$,p5 YeHc1e@D , =`A$X3,,,2H,ň K" $$YFEi!þ0$dkaldoFx &#c>q,ez φYù=MyeA'2,΂ xYe 6 ,,b` K,eAdYadDIeYɐ6B 2_< ga; 9@'$Xrrc9%lg̟1/o4 -߾Į\KeplIijdY o2 "xg@mdD,N3ș, ճ l`eYeIeXY1"eXXIRx& #p\:ep!X/, '59g,xX2̎q9طm痝e?/re|{8 AYd@ekeAY28%xw -gH,  &AĐ!e,` 2 $BG$k`|ĘI`I?$L <}mO, =f+L"$k!dIdK$'܈= /YV7wדDII勥EdI0AӁ#emxgr :Fo=7D#"s#fAd %Yyxq$, $vdK$,y's=,.0F@N#_$$b៟$'ܟ?wQ?rgfdŷ $G$lenyYY-2{e$d̳lǎ  M!?1% OJE'bk$YdHeI2Y6Y#I%c$l-U1lmfMIgY%$lG1$>.t^20+y!|RadYLx53Y0 =K,leOG,= ;~o2Ch,I0lId1$$xI1,$g<[ovmzM$ ͒t$0I ߆ /;39Ŝ8 8 A1Äyx $OX Ͱ/$dYe X=$6`,l|>MCTʾW1ȣ7rÀSq2k7uٞeu-cpL!w/}$a%G$MI!$22p2YdY<N6tbπK/YOa,,!NlLcb#g>}=s?Ri,NC,,a_mY̲K$!xX2Y%Ag'6xO!'AETآ!}c>'nHdhpE7 >B`K ers5-cIt$Y6Y!a̓Y lIdq/LK$O#ጼzeiw$KM269#Z$z dlv8lsr9ؽpVMXd p: dl}@e;"߆b$2L$$|=m<clM6O`͟ $̃YHe#/O08H c>ɎrFH$}d$MdAa!a 26Y$H$Nb{$'kqdf)~[{a0ׁ%s"cN}9f$20eӫ>xtbǞYgV| , 8kd@8Ig+aeY# 6I&lnIs$S8I=,dgg?Hl#d=g2'y%%d>IL({ I$'PI !2Y=K$g|X:o3-tEI!9 <aēI Ŝ -0/xp;>8^,·r?xYeA#܂K$lY$7%_VIa{"Y?Iȃ'2K,c>lIc0~gOds$̐#Ieel$BK$6BI=K $I$6AYeO&oL}# 4Y,$<5K63X[a#2Nke>'$Vw/ludtI7?ReldN|YYX|rq=$zH.oL?lL6O#(qx 3O*q?x.y2<e.~oz:?I.m  Lʸxm~"^hP>cv5ɲB$dO,F=#qYd0Yix dǤW/xs:@8/yל Jia+? D Sh0,vE<5đA0Aqs~b)1g7̪Bk03.k??bG25/ƈf3!>,n jfɈh|G7`V>KbY{!CA4$M5o>xO bjg)hkc?0K#A &N~??5'3>oEtg'?ii8x$m~mnp}#L4,W_ѱ ?ʹ`T=s#=|3&l, f $ID8YaYO2I6BI$vY$F$IYdAYYı<<;<8b$"ixH&Y嚖HRu_#:s 3{yd0Acq0p!R_YdG1ĂCQ L zc8I|l $[҉Sx/? {JĒy8~3PHz>P`a*$` :UYJWY"8^׮R70RNkV?3plqd vL]Z/}UArJp-P_g3l9!Y0YHH$쓙%I!'>LK,?RYܝq<ّM?S"!8,q9fK/:ؽӛc9eݘ$sl̰7 AYHiy9a^c&{dY$'&2I?$IG,,FI',%Ỉ5$rI?Id6|rI =/(YI!Y2 g ĵc~tρ86t8D~,D Ύ"1m:+"yocT m?G[}Owŝ7ll6q59٥IeIHq<׉NLA,IN$H$2Iq͐ݙ9Y;g93+$m CR.2#6<$9-3~>&Nb"=lxN[]"_-L½2O?IY CkȎ"32jpPgg ,P xk''D-L`D "ii!߄'ݰ>lGF'|4sB[o~}E?7t?$|U5bэ ~#";p|f^Y-?ݒl.L AeLP`|wwU8w| =5g2;N$Y=O K$l /a'0$HXl 8d 2N͓6OS$8Y3!&Idgd&xDDAH0;eMeIacl lDgA#" l8sx^e# GBgD 4)XG3Q~6YaρbkR^`129}+ȀSF5GzD̓c*O];jdj$f dq7'tg޿_q ō]EMDGR_RO<6;g#I9̒xIĐg$'I$ $& 8y{I$LHIY$#'HOd$iGșx.9;FweY$Ys=2#Ya~$ ǥ'9>F^ id$y6I?K/>̊?"~Eg:@d>P3_>9Iw0IcI]م%̑KY02y#OII̽=$E$)2 y;3$0L}C /'>6Ƕg ܱw,sdp8^D63@8g,`Y܃gOY6I~>Y'HmA}s>~OHxoOY<֡?`0=lr,c#d{;Xc'yY&I쁶2qY1&1<S%1$H$LBI$30m&|_.}3!;̶ټKgWkx6A::Ӆ"#@Ixt, mAeYe_ڇeB+VVM_/J0M6V񕐭{/\կhqݴ}28kua_Ia1I"΄oIY៻ElKa?Dq?O2oX?K~PЯIX;5)Iƿ'?gD/ '?'/_I'!zM_3~RVtgoN߄%ɜ٫XIdre՘ۖg] !:1mB8g8FM@-O8?A1ۨ ,5ii8mC3sg?MC~\z)0i~<;Cwe~Qxyo?џ%4_&J FFY3V ߫cG?oYqu-~6?s?Oo~g¿G(ҧṠխUZWWc,` #7m rCmK,-z΅0w0W`xݲȲ##",ɒxLa2Ga]P3[WĿuJNU5[eS=Q`G+*_:XF;!8Ilal #~/߾??>~}7/_ʿ*k/_ȿ}͟mg{c#li3݅yÂF$6XJIsg8aN,{#{lYɎ8<;p l/L~ G2!3 8ds eYY7ms8Y6,l,oo,$,+YkjTկԹD_ oնkVVZ+P[ZZ _nտլhk-qPsUd5pB0/062>k6>N22XY6^HdFu㑒,g`疼8OD@Ç>_KyIy??Ƀ| v,a,I2,v,%YcYeYeYIYgL,u~xpf?8__5oտ$Zo;L??/g̍|¿ έZ*ԢT\+ܩRT{>#pc}dx !%!9aeq=Dt:g3~y?:_5MrLo,,,,<69ddXXc9eXce ,&p~?!?/D##HS1^ѓ8zsW8QfGgA!\x73򑽞Z fppK(NBʲ3~ #63$2/Y_3FKoco?r8^_{?LY"2@Ո 4Ie,,, es,H/zL%z̃ϖ{eygSׁ̲K"cN_uG?ߜZ7Ϸٱ&zCf@߭5~.N "1s&L331c"|̉`!{fmuIJBBK$8|2%o ,8lEGsmhL, ȄccYeHZ~tٖYaaY g3=/ gLYe_afng3nc?^RR^92G_dԿ3g-2WR-yt2@12ۘkw Rʝ2|Hk3 bNN;lv&x}fm93yǞ"=)r d 6_d kdGy}ؓ2,lX̲,r^#)+eL8 p|  ?k$AcS>ye6?Lw<#G8Ol՟ԏl`H2*ϥB?b/݊dKK'-e+/?k+kk/o++V~!/0 fcB ~:Nld;;e}Dz܏6g38Ȳ8YI3pMß/>~lٳf͛6lٳg6,X^`d?I=y͎sgc?WĿ쿶~՟՟ՏԏbOGCdgHrYI?3O bRԉY )S{}NFL,'6NXzpAg,˾pA cDY3п%Ā#О ,e|,,e|reYeYe?kc;o"$X8z0<~=md~f,p=ۙO\ƿ $Kdy` 'Ίqfv^#dv$YfXdn|N>3`DeIyWϸ#`g7Oc_7;N~'jHAe%QS?p-Tι~T{wjn"'_?q,n_q7}{_g8t* 190ŹA 5Iq$ '6S9O ,`= = 6ȈA̳4鴋iOW?klgR?i/w>#v>ԏz?+o~s;*2߻?"#ߏ~E?7 ?gc-LX+ v~# pO>?˟cq?fCa4%CA!7%B!q6~wݚfo3PP?] ?x1C1I𙵰~SB:!⋅D2̴0/[ aɷmgoFgpÄAFÄ/!&px(\_E?/~_~_+BQ[_DI}|Y;,?߫7SE/?D^gH~?Oȿ?y~ ŸֿH^ /BOgNx_¿HSпQgғ,!!dY!$7Oa!HH/BBBrrC$$$fm,?x@xso2S/:fLYvy~o$}DdX`<6Pl11[x0mko7}}'>Nq^t e9ׯ6l3ffW'f=eo2We.K;/9se6z %929|vv/^<{Ͷx6:ל[4d7'I8e$ 18YaWI)B zAcda0  8YAA"`XÃp͝?|yx˖J/e^qOQ6l&L3̴are&?A!#p,e/1=o8 A{dtU0ys ;?5z%6^?c8oN7f[XglG%l,ga9"{}  d^p,l8AAp[ d|=A/|Yx^/6x^xϹ|C#g4㲼K'B9`7 `3",8 =Ꮬcv,""8?3Yx ?'w߆qK̟]D_1%sk`FYd lG fADfpcg#&3|ߞ:~ޖS&> ݟm#8ty dnD[Aaȍ /AaȈoM,}3ϖAi Jd5μpqxl# 76#132a!bP8sa ÞG'??Yŕgm^<߂k3 ys#{Go>I6 :IA < #Xf6G X^{AÇ DA`F8ض:w_s7ffYIe}q8_| ǃ ÙC}al'b5pyA~qϲ'~g xOޟd6~YxWe-sfw[>; /9Fr|e` aEH,2#xAd|F7OuǚQ=mlqC?l=<^,}uzzT/;ͽ߇ygscvv {޿"lfK/AM}Fs8[ t[{8p le x`[~oɖS=ٖ[om:m:3m-t[\<3N͜IσGf2ac08sdo ƹ2~%Ao}_}">#N,~+/_a&_y3g2cXfg>G<.̈ /{a8X1xt!>O ?>[ř^hg/^=ѷ^KFs/%[xg6ɓ60x$̀0*~t5#lFGG9 o/`X "#Fp>[͏_~kYe,zm[[emsae;%/~-Ն}olc"dg9a-2c1"!,l2=w bz1ӄ|{]^llŖx/7LKm%/mwN16Xl|Njןs{0r$ 1{Fl7"7 !6D0l~IHaowkV}x67߆ᥲoŔYmem^yۖ[fm[[e[} 6%9Ļ]ى[ex=̇؟9Kak  [ZCl0aa!$n[oXxZma_! 2oyͶm9oWm,,ll)omoKm o4ɶfrgEoٍ ɴ忛< *}6_0ZfyuFF$zsk'/_ٿ?m)y^F7C[&~ߪ 0?oL~ٻ޾?D[  l~oF- xOg}46p(Jշc7խze7o/o^?g,7T~M__̏ܿ?RG_̿?z]Uۿ2n]'W?>]ɿm'cr=-k}~_^n[?L~\o[u_O͏ f̝`)a&L2Yz7js!{=\/'/~H``c(Qgm׸@}oO@;'|6?[h'Kf|~+H>_ 50ȕ85crl~n`ӎ${B&_A^2{ sdFAURcp;v-VöVevmn8m57k$ݮ5P|)[ s e[jޚվBY5*7kƶݫ\jq[ƭeVnzBkkoȘmWumorex[oeyN92VooXm}c3m^pD,o1x2yݶRg_iӟq*~[{-|>|wOxgqe;س{Hxo0ρazӞ1ߒ{}ߛz|ωy'~G6{߆,aw; 7"^bxkӆ3<ѹHp߆sgsc,Gw>^g=y9}ÞGw l ձo ߘN,t|G<0xk{Ͼoto=L3'o{g?%~0Kx@8?u1ޟ{sӇ_\q+ s,>fW}0g8_|g_\ο1w;q~]x #|,\Gomc[{ZސmxN}o7]Eo%`vŲ"?Ǯg>y3gV7{<ql6r9,|7xuHc͞)ya ѸO%-{G&fi79 ǎe\}Kdg2l-O5 3~Fy,y|9,Yyo~|ȳ{ȴ Aksm[! ?4;-7ā/y bkM'~I{şφg~e/~ m`oyq"ǘ#'=,llBzgQ_o_oW_|U7/_ȿb?O6uC;?Hq^Z nǧ!'} Hg'l؀r?Y?1CUxLae 9E 3 l<{x>3;?THE~ )O6mi8 56I]INپyhIG٢0~f/0C0?.FQ(6@Bv φ(*Q큝"yH 1౻X3`?n_ڷO@_r 9x(|ϴ(?YʼnfX'`-ݘ-VLFrfb͏݈쿘/X/OHXI4? h xJ^qؽ;MynƐL4м 8g"Dl ԧ?8H0o<xmٕcOc]Hu#}׋㊃9F̖?wwGЦΜ߽{ۮr}qWcGj}{vW%ƷgGe`h-KB~ٱw DܿHWVmOk^S}?!ώ϶D`/#Y"dl?==cy=BL%,? կԭ?"1"(0HxDɊZX] Ș' ǩ?ټsm}m|bOKc g7e^}Os?P}? |7ϗu#ymvg4$3($"b~=1ć{Y -pqO1kl/vﷻ+,$(cdx+GQX°iepJs8/{ͼ dz|̃痗ϖ2njAbВ^IؖԵO676z{kd>0줚*Xkq+S emi}F/  9}1^s2 }$~`YG <3:g>(f/ll!ge2,I`}̜,ϻ dYg3Ya,lll,/, ?pydɳ͇V̈́IKfV43f͛6z[7 x? ŏԇ聖?G W[ݵo5Zpoxug/f?mOJ߉;˭i U s>l1# 6\1Ql^Cqu͑D?LO zlX9 ]FkB#dʜ5W {{69{d gb6~g2dѳ6@2#<⟭-oo?_z~'3"u̐&W|JI( FpBo!>NH("B~)'Sq?^ 'qw$g+33<˟~!}7Y4hr 27fju|Ȳ{^12)r,x@Z,R3ن[p# mԞ+hl`',GB4Y~#?QFl$8BOD,,XLC?PӥG agXb 3?RoՅ/# 6rgyyco Ç79m*\K[oXM-񵤨Eq6Ve+3WłLq./-7չ7ɓm2V_ n0ogtm3*=a'{,?2J?W, fwupd-1.9.16/contrib/qubes/doc/img/heads_options.jpg000066400000000000000000003764151460375044200224230ustar00rootroot00000000000000 ICC_PROFILE mntrRGB XYZ $acsp-)=ޯUxBʃ9 descDybXYZbTRC dmdd gXYZ hgTRC lumi |meas $bkpt rXYZ rTRC tech vued wtpt pcprt 7chad ,descsRGB IEC61966-2-1 black scaledXYZ $curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmdesc.IEC 61966-2-1 Default RGB Colour Space - sRGBXYZ bXYZ PmeasXYZ 3XYZ o8sig CRT desc-Reference Viewing Condition in IEC 61966-2-1XYZ -textCopyright International Color Consortium, 2009sf32 D&uJFIFC    ++&.%#%.&D5//5DNB>BN_UU_wqwC    ++&.%#%.&D5//5DNB>BN_UU_wqw" _(寪+O1#3>=O7#pg3#)r0  Yp!ch%DQDQ EDX Q`@ J-@(HT,JT, )JAPT@ E(~^epFLFWfl h7]{@ .aן3Gkg>>>/Uɣ9|hL&1~~?A~#wY?p/>7#!gI>w#G=wzO>#Ϝmae,B2d"B( EEDRJX(XTAPT UAPTPT(-P~\;DX[ *B( BX -dę1RflkZ摺bu^R9GV\c>z׾@sǿ#2F_-O}nO}_>/mÏ}_O~,|6Q7f}s>d}O#y9rdtQEQETPE @DQ(P ( J!P P~^@([$QEI QT @TPT!ep˶u^AGmGGogOg~C(?#ϱ|U>/Gett>7aϓ4Wf} 粏}{o]OMdz gKFf mLT+qQEDZb(ы!!DQEeŐŐŐŐŔ"!CCCP")bY 2QB(H @"(1ZbDXұ%"¥i %`* kPyF\;o ;o;|zSΫ/Ȉ|( @*RH("Y( crS!1!!1!(bbbbar !!!c32g9:L 5 MTN,EDQEDQ&E"H,HRPJJ @*Q(%*PBB  ,(J%` k MP6 ( "(őqd1dLY Y fx,og/\Nn1cJl5MX.tMMSh56M[ѩjmmjm5Cccccp0f0d1d1d1d1d1d1QEDQcl") E QZ) PQD* P6u%2BTR{5@!A,X,("(2j`` Ʉ561qi ijmѩimmFr26CpŐ @QHQaPYH%,RP @@*  ( b2%"1T-BT*(!@aH,(AR(d@ulg%J"XJ `QKQ,T`"XT )@(bDT %JB) B% , X@* %,RH, - eVb29Rl) e$2cLR)P[͌3aL1111 * -e*Q`!) i`T *J`)J,Je, ( ( J,d,JBVPZC)b3ǫSCN7b8ݐv988%v7XuÖuGPuWL9L947Kt56[b5Ccech"2DQ,EɈɈ̀ͬl֡ni=7=7#}E74skHMP5 X6600ZllFLFH-X()I1Y@!PTJB J\[IuX* BP BD*(`*$ETAQ AB,Q!!ark᥸fWtӕ9]DuSv8ݣ8pwSzzzzzzwu z|+DiBL,Iid1e 26LFQqQ(FPm,*\UŔ1&/3hՈ_nYMΚll߿ëwV{#qT#Td,)IQ(@ % db3o>65X*LQQ@, JNCًY@,rΌR% D, @ 'gU}2T2bL(bȘqd1e YCCPEQ&C1Q&E((ŔJ$cbAR3C6n{`,YTqŕ" R(",A" f5l9i$DQ cB(LfE1}?t,((FxeמK`  KB`%:4nY Y cCc]c3L1kfL&c p! b.,38a3K!̋c2F3!JY*$Ra%K&PY ,QE*T@ AXX II`Xmն*%&+k`QchŐDQ&@STx{1wc8@d@n[l @ BK ¢ lיcϊM?/{|޾{˥ˏ|_,>>_,>_*>*>rAOTQO>^Ra>]PqԾX}CӾ`}<+ϜD3<:{sőO/+l?n<{q9l X *(X* j -dl،eq#+QR- cU!bYdS T~,\UUbcT%) D)B3(phߣcflYABK @ B( ,NW mk?89t 2ˤvsULە{1```i6K0`Κ!:jl5 m[4``,,PŐ2QJQ*,[*RiPRH* THIU2bę1J mѻEe[ռ(J1EDdY(1d1e ma%G)V`boѺ @ %TRE/z>oK^]<˞T:2Ϳ]W{0^32gy|O{}OgۡhoVhohoѡho47 㝼ho'D] 47vinF᥸[jmF˅133L (``,c3(c9 WF.M9V,6L,Qi!(@F6es%d( a[oJ,X, R(__otz_=/V_<<|}_B7G>tDOG>t}Ѿp}UoDW_GѾp}ߛFw>l}$o/Ѿp}ϝDOGоt}/B3>x} >|}O{{/i<1<//O_o>_Nc92#r:$nr;qqnnqؼn;aG/NKrα:::,/+piߧ7rzg՛su1e 2Qb aI1c3(c{=Ps؀!U mQ(PR*QD=x|o|f|{%K֧=qcǾ"z<%-%y\ey/TyOZ]AXyoV[U)=EyoTyOTyOUSTaa\y^G=qO\;Cև=qO\yD!㽈y؇=.OcGOYʏ.>|k2|W7O|,~~C>~Gxj][,zs͇<8vd=<|z3ڹZ:e~gz::ze YBL4"VZ1D dPm `@*,VhT~u;o澛k)xS:=x%=Dyޞ_yzxD^'=dž=ljmrx"=mnxxge؞D]Zy#ycyj=痽;#qe뜣s|6Cdg1EAPT(A@"[Fjsƺutr~ti̥DIqd1Q&P","$ \qh ,QEXl^ @jUEZ+*RZ>>Ze7}^^MyoK}9wHt9E9$tә9C9JuWPu[9]CcgPugU9c9'f'+r:#NGXuY9c8c^GPu#9]C9]#ԎWL9ttvtH4M4-Kt56U;=?*C/z~̏} nlߙ^Z|ʗ7Og_>Wz8ݣ8o`v'`v'l8v99^98u7\9euI9ppuHPtPuCuCW4%e OggsŔ")qe 2Q&CXI1EeҲP fdbnӸRRP*@PU -S%{_I2}. nR_Wz};c6gGǧFOw7ݾu~5~jKq C fV{Qztc!rw2Ӻ.mL==yHyFOPysՇ=RSևhy/Xy^Cׇǎ1<{{0eLϘ>VT,O|~+/Y8s>!~q\~0}?>|>/M[ Oo>#=NܦZ3(I"J1Q%(EI&9bIb2-à,9cMFXnմZ2āT * *̉QVRwqtixk 5˖;ti:t \5]]$dYI ,&9[`ܮyЎygL9nFk!X9Z!SWm?yy<=||//5ϙ/ϛOA<O {sŧǯ=9' ;g윴s#sM6XE!H᡼sNsc9'ZpuI8݃8݈v7`u7b8݅䝃v98d9puÑ9]HtÙ9o@tgL^wD47vho6eM}}0 Oo~<_oSQVbɔ(V Q%0bw(A({^?2 Lޭ@l(UU*زlUlZ-ZUEd@}%*|gk;IyU9]juӒK֮K9ӖSXuSdr:W-z,p:iq S\.NpiGq;GpRNN)8]❣vÉ'l8d8ݐuI^GT9g\9gXuCԎ]> =rY|x1{>GU"(,VL%Q%e (Lr\|_ėǤ`X}/Oغ,GRe)J )eZJK2EZe2&KJER2LmIVϞegɮ=7}nwf3O|W/}irm8=$ӫ/߫SGɋէf;n^]^|?LF|]NWNG-$Y/#ȼG$nsSW 33pOB#Ξ<z0:zC͞'p ŀYc)E"-bdQU-*) TFS*(}n^%*-yMf񩿓hv.rNWXvq֜n ×`v8`v9vG];֎;9.r^u+N;9cN7`vGZ9c9]Uyv8݃GV'3vY&֌N˅v1=`z'<b?o{!..}IeL%a1yϢX,/7Fǹ rzvjݼʶJ U,TER+*Yd2d+!xM -yOf\5m+#;|F==Ommmmޞ>>gC'}??kݞjx'Vyyf P6'V<$m M *1.36 DolYlc6m:G.=F^w#d|_˕Xz>wV Gy$X%  aIF2X㾿q9(ƂQRb@(KnqU,-Z-RKfUj)X{IJכgG>zqxCt\dte:o8MtJE 3Nyӧ}\{YG>Y֏}`Wԏ}@oJ>j}1~f*>r "ʾsG]˦nѮ YCk! &a6C M6 s`klkSW\=o#< r~I(,T, $,$a㾷ylQ,:iGc@J% 2ݹ[&QV-R*QU- BjhefAGce8|Oͳ_},Yu{j1(,EDQFPDbE"mDQ Y Y Y Y Y Y#C1d0g fcp1 fm\]m56 mŽ\OvϘ<,K,@XK .1%/}?dƀNg_czv.:Ű/1[$iUAhZYjE)jfLEz>߅FS11ޗEمFT\cvfjlit`joX:r)͖{i3yʴkjnhoz3FvlY0gMmc[`m[`5 m[`X3,!!`bd1d1 Y fpŐ`kf0gMM5SSh?4nk?c3o|"ńKcQ X|x:N}/V>2W\T}-LtC)lR,Ze*2 fC)[2LT(9pJ|gƩ3ٻ;,2ͷ jT,lp(ɈrJARc]̚uqWbm[h5#[`s33. [15 m[`ڳT MSh6[`MM l"`lhu8=MggӾW/|6a׍st ` aX%X%x˥Le|¶k툡(C56e-R- Jd%2iY 4e)rLMOf>'ᏦƲmѭ\b"(X+^&>)ƻ =<|+x8@}IOѾkwb}[>>xk>.W~P}N?3Gaozy''^~bf?/O=|;^>E=L|vc͒nձmija XYpd li鼘n^|JyǪ0fx>,=Cxpq!1c%"=jx'_)>h1Kۇ$:118a3Ѧn&ΔsN:3s: <ΘbƦ{sߧv%X%e%XL~/F|z7l_U7~'\%sǷ8[&AlTEQKV*e2FX*)KR~k%$|z'^/ߙLOC=Sɧ;l-1)hǨr:7.g吏pG WN;Nsq;;snq/$43~3eW#=ۯnQ` P Q\h~)/DXE4L˝>~kCPmו{g~p,ʖd(LիTUKY "ِQ>s|W̳=.;04ؾ^Μ_+~՞{nz|Ѭz\ǯO;n9ʎ}mMKu4 -Kpw -M4SCxMp47ӝ }9ЎwHHuS9CuI8l8l8h❣wC+8]wpwqNNGVIϬƾ_/O>5׎X/] $J,$R[ė.=%mä5%o}`7J {4TZ)lRT-Rّle2!}Iz^*ϻ7fr7f5MK}9P4ענho񡾚[h᩸iF`ˣɉ=7]]]1=ׁS1>|>|>juyCÄvN,N`zXn'ɉӏ<7csIz\؝z0ʎ2S|xfXf+",$L~{c:l.)~)y}O=gLaqo3 jeC"d2dL.RlYl {fX峋<6Wٛéǂw^꼑__2:s43Ǧ"LdTRe)lPKcշT];p3ׅ#;N'u8nU{{G];NK9oUKNWU9]D4ttwHtә9$twB@tSwE99tg@tÙy9@#tÞtCtWL919LigL^gL9T9T9T9PuU̱e{77y>e  "($D>+)qE)M^/YyXl>ٔ@}7/L} I jJRٔ,ș㐪2)KfB杺lvCCο$zx8;0Fa p 7iǢ M\&pe ,qd!gXDT@(`DQ*GWFt* eϞS"`l3{z, _=o=rQV2J*-XRRQHlךNzѫ9sEMpvC `c#;ECyўxTFFxc6C vSt5M4MtÙ90M7`eXIa1qiDV8ؒ\I$l!@匩qH>V帽ENg %DEQT%Dd|}U5)#qv6].yxd|xr )=5oㄴi[)ll,PRbe2@=2b˫h94uYNGX#Zsch ,@@@@X )1Y%]8eӃId)׋O[ɞ#f/08sߨayxGj_Wju3%@bA >;}hHmմ~%}HY||Lj,]=}\5jYRUE e-XcK4,*P*R*  B2<6Kf816lk=geI>g1|Oa|=|aӻ_0ٮN6)SǞ<[SžͯT^ghe*UdPUYE ,*J_c/KKed1ge7Tw -x>5pbz9yX>'>c-i|=|J{89;0xarO>zCz<<ʾ>,wnϙ:N_7Ye2oOJ DZʲ:NinbD1gffg|"?.^ogYz =oF9,YRrRVKqlʖl-㐲)An4 qdiRҠT(XPTEAR1I 2a VDw3\5GO}>?+돼|2޿AWXx/O8r#wcFơFos{@704v8D11JJ iCZeϾ;` Bѯf2ę\il\irƙ [.Xɍ$+-ƖL3cW$\FWeq)dFH* q1ƜN&yzx:Ϣ|ƥk|.ͰҰ#_( WI~W|]>_o_KW^z$n\65%6Cu7 5CkP0101$2T,PRAPTEH-٨{4 #ߣ?O0۫O^~p_|ԏ> >I/ϕQX|xǣ :6.R qEB ɀ̀́3`\ 2cJ @PJAŨ* ,PU  e%RT*PI@QHRP mǞy qykg,* `݆#&0̀̀́3`3`3&#&"* `X, lK@P@,U`@UH -1eƇNG##ߒGgc|g|?QVk;%L sï|Wxq[Lb(%(% X `*J!*PPAHT,57XvӅ^k~[ypYH%@@P@ HPJ" @@ Xʘ6SSxwfsrCه(PbJ s.6Cf9 y9y=aǁgdD/)[Hǻ/_-盿a=5 S/Y+YaPP(Jؤ (E,魶r9{){ wreҗc0 hu^!z/2̇<=]qǔtM6Ϙ& BTUPPAPTeLkm1)YGFbMna7%:a=S^^G{yOG=2,6F,G)e3=St@LaAJ $FT5SSu4:;Ρ-o.eZۢxtHv8cvN189I9F:Fٮ01APT1S;lӐV e\Sc]3cJrpcSG텂zGz3[qwbzjOO9=w^'G ׫i4ӵ(YPx5bK 3(Oj|MCY@ T22;xaq4d T5"ZPA@P((T @ BJ  9Cwb9w '5$cIq9胔(cJ@%DՁf(P(O3!01 23@P"#A`p$4CBDEY7M{t77 ٽo,n,=%Kmk6Zͭfᶸmk᷸m{ᆳf*u1B AH"Te"(2(2/E6yAd#IHF4$)!I HRB1cIƆ41 hcC11 @$H"DaaT'Q>͹*"'Z騟 RU:֑z)CBLjܓ ZxSijJ ]TUUBU5WUB*]UTTֺ>M{ OAx'Y>ם;۬ oΞ_x dZ<| !n훻FѻnFɺnl&ɹnl&ɸn,&ɞɞɞɞɞɞɚɚɚњіііђْْْْ;d퓶Nt%I*GzGt9G#?x''Y>yxܸ鮺*II)%$RJH"D$H"D$H"D$H㎄tAtAtAtCrC:!tqqqqquGQuGQuqQ׏?vUS'MosSdk^N7=OA=OwէA=O|owLH_Wj6mzVn"*}.^rEy.]U᫣Mĝ{Y>M]ni!n Qi,K5nޗ[JѢn~]{K.ӥ}Mmf>m=oZݰM5)Eto[VZo"iuƣOUn 7aaa n 0 0 a7ǯ;BWt'i=.:Zߝ|SwIsKKb׬_ZhoթԥNt׊Ui:榊j)ѥj鮗5+uu6zhi}et]#Q TXW.k-U 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7ܯw)ߨ)J<'H#(~wDe"De"D$H"D$H"D$H"H"D$H"D"D$H"D"D$H"D 0 0 0 0 0 0 0 0(Wr=]Ν?@@@c11f3c c @ @ @32d111f3c1f32 @ @ @ \FEQޮ"{+wBR @H"D @"D D$ @ @ @ @ @ @ @ @ @ _TؽD<+YH\*ijMjMjMjMjMjMjMjjjjެjjjjjj ް ް ް ް ް ް ް ްM޴M޴M޴kָθμkƼ? P3}D7LQ2D/ԌR2}H#'ԌR2}HIDIDID @ kkkIII k jIIIIj j j j[ʟ<xR' uN ]toooonoooooooooo.///.dddN;N;N;N;N;N;N;N;J=p{~g~g~g~g5CT5CT5C( (2QeFQaaan 0mTB=>C-2dQ-FZj2dQ-FZj2eQ-FZj2eQ-FZj2eQ-FZj2dQ%FJ*2TdQ%FJ*2TdȤԚRjMI%$RDqqN4y(^mU;/tO!@>Gq%/a 7 QU8'B wO` 􋾟Q,0 7aaaaaaaaaaaaaaSq_DB=2{]7\K4[ oIgZ=[UWzl]enm&CU bwEE]=uM+M-۴Kz.㵬m%)^kXL~ƂjƑ4tlLt+ cUDYzZ&j]M=ꅵZUV 0ª-+J Daaaaaaa0 0 0 0 0 0 7o 0kJ|lj_};TQwM64~IM6,TUZo?N娿M[W=7tVKvtL,^ҭuR_wCsUfvy\йٴ.?VQ]Uk[5ۗWkIRT֔'Z8%z tg 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0xne%{d (NLYr Xj]uV-^jwUzʚ{LhW53]ulmVםu z2U#k.SJݮFwe^KfYzU{ 0 0 0 0 0 0 0 0 1"0Fa0Fa1$H"D$H"GPR'ʧ=0 @)U+U@@叜r @ @ @ @ @ @@ c1 @ c1 @@Z9xu<()~];qOG$H D$H"DH"@$H"D<$H"DQ"D$H"D$H"D$H"D$H"D$H"D$H"D$H"D$H @$/MW;{/owյt؉$H"D$H0 #%A* 'hddd````ϧ7sq7cs7:cu7Zcu7Zcw7cy7Sy7c{7c}7c}7c>>`6>dV>lvϹ}ғ}t>>Y+/AԛYqy&oeFO>O$K֏V>}IA~?!cURԼh|[)G0 !r???Ƕ=lV[%hV'lv'A;fKfKfJ (2PdA%JL)2RdC"ȆD2e2LS)̦e3)L3TfQ5fjՙ3VfY%FJMI)%$8WQ!l_P\EdC!d2 C!LdS"ȦE2TdQuD'Q:NuD%Q*TJUzQGSN:WQ!lj;|)/y0Xaaaaaaaaaaaaaaaaaol5)M^)-OO\u^jKqaaaaaaaaaaaaaaa#"0 0 0 0 0 0 0 0 0 0 7Jx|}JKb{Oy8/aa0 - Jҵ*Ҵqa 0+YaM-KEXX- /v4b(Z[[tJL!S0UI"D"D$H"D$H"D$F"0 0 0 0 0 0EED5dIfe5\T6.&Z*YQKZQ%4*h)[vU]ua)U8,롒v]\WUܵ*+54ܵR\ +M5%4ZRW.VI0ZmM=5%KwVu\m[)_=/EĦ̢LVJ쵻l[ .\nܮ^6Wnaaaab#"D$H"D$H @ @ ^F+G7(^IDS随3WrtJzTe"1$H"D"2$H"d"DI"S"-"RD @$H"D$H"D$H"D$H"D @ @ @'_z8? <>EqzF("D$H"D$H"D$H"Da"D$y$F"Da D$HH1"Db$FF"D$H"D'#V2Y23isq7:cs7ZSw*iRm- W]|&/) 0 0 0 0 0 70 0 0#%%55=q`iΜiޜ}`Y7OY>hVϹP}ʃtr>j>t7ړy7zs3@/ԌR'"_R%GkVEH!Ǧ1iFҍ???N>VI2[+U|) e{ڼ٧iuw`Z7֍}Ao7}ߛ7鼼nRn5f}aZd׏\6mA.QaI Y!d6Hֆ~? &Z̵o/~cY@(2 R5'~~cǶ==%I*IRI !"CpB@z79\ᒦ8㒨d..N;zj 51 D #Ozz PJ+dJ;d퓶d2RdȆD2!e2Lc1LgS=FqQTn+3gQLd&HA"DQuGQiO {p eĉO/*LeS*Φu3M7ܛrn s:I-&ZLPʆS)fS*j2dQ%fJNuD%XPQņaaaaaaaaaaaaao<)SN}$J|AySr 0 0 0 0 0 0 0 0 0 Da$H"D$H"D$H"D 0 0 0 0 0 0 0 0 0 0 1OY%}u:IU֜Db$H"D$Haaaaaaaa# 1$H"D$H"D0 0 0 0 0 0 0 0 0 0 ']ڒOzieaaaa 0 D0paWA%a0 SjH 0a"1$H"D$H"DaaaaaaaabSͯ)-]:'I>O~(0;TwGi+ZU*.MyQ-ԫV)NIiV鷩(M=KJ֔[sKۧV[: zW[{inݩֺKE55QvWŷ+mmMOr6SM-%IT)MKEUQMaaaaaaaa"Db$F"0 0 0 0 0 0ܓ~IlO}tu~M>ߥIl?-j~IMݼ\9-ʭ&dRUn$nYfT^|tEFtEhy)bFKNjMWj)TZdtjyi.ckDiS54ۦ*SbP*Zaaaaaaab#"D$H"@ @ @*Yz b{OYx- S}crWqkoW=bRз+Tj.Z\t]ʓsQ]Nj%ta"1ea"0*Q$HDDa$H"D$H"D$H"D$H"D$H"D$H"D$H"D}Rȝi}oAlO}Фo x-'$H"D$H"DHaaaaaaaaaa 0a!'ld`iΘiޘiMiVO>A:Oq7w7Zq3@/ &T]rTKZ b{~'ӧ4ޏz<Yaaaa]n'Aٚɚɞɸnlɻo,FٽRo7oۆQXe֙5ĵO55SǦ!#N=쒴N%&T3)qp77ƠϨ3j W~"T$xS߆zb{盡O ѫSOO5={WRL̶LLMŃs`76MՓuh7v{IޛyQnoARdՓV~B 4h1[11!l#lkc[?䉩%f[Kp\#Y )# #??+d----)&^!1LZ6m6i6Zc|]>Yn>I ad/i S߆zb{ߡO Jyx%\ݓY#h65?"JdYuze"T9C=#JT'A-Z ԙPdRu~jHjzë0jͶ6wcQS`llh66͕ehY6V iͮi͵ AR4}z5^J/dA-ZLԙ3!Q:]?yԑԐ&IPmkᴬ)6 &҃kAmkFɶm,l퐠?u~wsԶQz}Oҧ4\U([DdhhlPdoQG'_Gz}=o~=I(lj=j~=$M/oy= Q/kwͲ}~I=7] 9HEDASx"rD"DTa;p~ȤH" >aaykG|OSo/~<<*;%#s?( DMaaan 0 7a 0 0 0 7Wz8-}A8GӧZ*¿)sʝ8WUЕ&Kf[F)қ!!-!M1]9k G.WRvW-%}{=:<0 0[ Rdegn,9ӛop}+gܩ>o7k θϯ3.ɫ'%xz5$mGN6{dIΦ{{{ܼsOnii-OФ~5roS櫏\m~*Iefgn,&ɻo-Fٿߡ7Pgf]aXOTKR>#Q&;f;F;$,5g=$5--%kk!Q D I=풶J* N"d2)L*2TNj< OIl_:o+׷FEЕ$23Z7M̓u`Y7vM{l77wt76˫2j\ 3&: vv[#lkc[rJNu=*Dr?T'A:IdC"Pf3Φz3fS!2dRJ:s9*)[[]S?wJAqabbcB Dm lkcP~'"D'Y:ǬjQ~#=%A*IJhLe3s9f3LS)LdQud90 0 0 0 0 0"Db$H0 0 0 0< j b{_ gҷn$'I4&Ld2s9CpgC:̆d3fQ%fJ̕daaaa"D$H"D$H"D$HaaaaaaaaapͯAlO{_{JߛB%dzaa0 1"D$H"D$Haaaa0 0 D 0 0 0 0 0 0 0 0 0 0 kb{ޚ3/gGq΅"D$H"D$H0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 aaaax,z=؞Q]~:OOqQaaaaa 0 0 0 0 0 0 0 0 7n,0 0 0 0 0 0 0 0 0 D$H_7- u^EQiRV=>7|XLEMb)8լ%j\rٷEMEMsکF EJbb])jUn 0&a0 0 0 0 n,0 0 0 0 DaDa"D$H"D$H"D;zR@W7tZ'j{B= mdEi4Z\E{Yufkj7D'XQJRpaa0 0 0 0 0 0 0 0 0 0 0 0 0 0 D9I;f[&kNnNot^nl'~?[S[(z)U:ty~ Mۍ7Y+%u-cDVNFJJT#0 0 0 0 0 0 0 0 0 0 0uJv̶LM͓u`X7 lߡSwxMƬͬ2LzZi#hk#X??@ Z'A)nV նQﵫϢ ૕]+~_x/aaaaaaaa=$'AٖٚўٸnmfyACtRd?1!h6~hD2;?`ՍP05'~~=%I4&C!LdJB4~kޭ})+] Mߍ 0 0 =$'AٖٚٚٞsAnܩ5%zZ?pTc1c#hkC[?$MLJjQr?+d'A:Lʆc1L3TdS"RjII(:3^.z}RKézV~O^.*E)BkF{fٹn7tMѸt|}AAƺF c1c#lkcP~#"ug~C(ǑGz PJTe3u3S!dRjMI)*zg3aaaaaaaaaO'RO EA*jtw+uETDm lrJNuY~G!%I*I!4&D2Lc9f3S*QuC~G3 0 0"D$H"D$H"DaaaaaaaaEOyW]'>^!%I: 3Y;x]#pF3 T1B(7cU r} EDjN_DT=xF#1PbA4՝N .o}Aԛ=A&{VԦJSޡ_N<:-'~ ߧYtb`mk鵬ڛZM hL#q7Zc{lߟqx7w=-WR4RWE/VKK/ZBt̍D+11\1Va¦&;dl4fm!қ+7My|_7׌I\?3H @f23c1) Gnoi/Y^u'?e5D h?X ̺s6iգyA7ok7 sxxxt*AH c1f3c1f3 @ D$Fpo]+^u$V)TݲR5R$PjO{Dl웛Fxon+sH @@ @ @$H"D$H"D 0 0 7_io|SҥiMB2FR*FR$Fdegnl&xTo+7wM=5%wQ"D @"D$H"D$H"Db# 0 0 0 =5d^ʿݩ7o7 wx32'pzc(H"D$"D$H"D$H"Daaaaa0u񠣰㯕}+^# H @"D$H"Db$Faaan-VJ2+KO|+ 0Haaa]q|zWН&[f{Fɺo- }Y]q:YUZ?*AٸnFٽ7˦鹺fNU3ar9] {zn%tWm{OW_BTlh7VyACzon T# 2:8?֔WE7bU:Z?Q9$'I=shZ7}AC|o7 ux^2't{2HB4Itq$H8j\9)wF]-/ٝ dlZ7VM}h>I o7Mu|xt?!Pb(2 I"D$Hqq;wVQ+^kajSrHN-kfѻol'-qqS {x]]qxx?#G#:8$88RF[/'BT(3[7Mݓ{`>hܐq}x_77׌IV~G> 1r9pqqq~w RdlZ7VM}d>A*}ῼo*SqSv޺Aٸnl&ѾRoFὺo/{fN*g> 0×^q(Nұ/IНKfkf{FѺnMzyp77LL Js'-=-OҵKvZo*7uᒲu30 1ˢPT Wɿ\1Ђ paWR7U^2F51LF#[H`ۡ `C 嵢kHM d25Ui|2R cSb1Ɔ:HayBt)Qf1#`-3֬ӡOtv_Xe"TƦ%1HcC&4 Pan$2ReC2dQ:(ӯ~-#U1J7 ) 51b1! hA!" 0 ÐJtI g3=Fj̕uMɑSUۅ9fS2j2TNE ^) f3ЂB(EA0x9'I"Pʆc*j2TdDu)1}R]=5-KiGਊ-]0EH f2B(2 :BTBhdC!ʦU2TdQr\:$8QsH")\,K3ty)" DAn!t$Кd2RjMI(:i۪YT]UeHA?%G^R*% cSf3B4IƊ֊֕v^l[ c1c1c1c1c1c1c1c>F!B!B!B!BTEJ!B!Bx2BRJ*TB*TB!B!B!\xߎfGɊY ?QcLzc1cŔ||s1mz!B!Z|f-TRJ*TRRJ*TRJQB! s'l1c͏Slc1c1m̝9rNS&1&>ɏGxc~aLVZIrc |8ܞ|z!B䟏B!B!Bn<" *TB@P( t:;cLccA=O>;}SȝsG?"zeѓæOoy8[t!BnO1ǣL9a:? =-hY!B&÷rxB!B!B.g<۹=1c1s?ktNPRVE< {B Y+%$(R AX+GR)+%d AH)` JR)+%d(PH+`P(6ۂ"HPBV X >3dPB`(|?4BJY+ DR!BtɌct߷>B!et:1c܁zļOBHZX1cxu#!eϩy1|\Xc)?׾!ppbB!s!B1zt!B1X>n1eK<01 !0@QAP`a"2qBRp?VJȤR) `V X(PB VJY+%dR!HBX+  (PRJ)E"Z RJ(TB *TQBR)lBG#RJB!B/cT2W/rW/gYV|gY)D!{x`Ѕ@P( @P(ħ}GF˺H7o$Ixo 7xo 7˗.\r˗.\r˗,Xbņ1}Ƒuvׅ b-bضgu6N!ڹO9B"$B!B!B!B!B#6N@!B!B!B!B!B*TRJ*TRJ*T<^lH1c1c2ņXb,1,Xbŋ,Xbŋ,Xbŋ,X{#]ם;4ِh-Z Ah-Z Ah-Šbŋ,Xb1}k:y$_}N eFv!B!B!B!B#%%eyȜY0)n RLqe~ܕB!B!B!mR; 0I}>1c1{}$>8-\r˖,Xbŋ,Xbŋ,Xr11cgy!B!B!B!B!B!B!BIamy"btC--AodN>ef[s"BB!BL'k'Ԥe%jWqE~}O7eSAy$RȬzN { 1c>6> 1e,XvNc(B/B}[yc,Xbŋ16eGkwsbŋ-%r9G#s9g3!B!B!B6eG^t;!B!{ !B!l:iŎ4/:"_ʔNr[!D2G/SG5#Ys箧s@ȧ#dG_-8+X+(9Hlc}w1c1d'viŇwg>t!B! R)#g ׊;r9mc1c,eŎQ#dkgcz=a,Z ȹi#"ŋ c1c1ӳzG==v1c1c1c1c;=L{ ce;s1| |rS"RJII)%$B (PT@Q|iÎSݛ7pn#HR X+B峖ױc1! ; ׏8c^}8_~! ;|t߼o<H֒9EȌ~D~=o:C(ӱ8bɌ|yӡ:v33!B؄!pr9p=B|cv3[|F_N4N>q~?^8/[趤d^ s徉-1KF:/bmpVJ1B䊈f1P! rףp(쐄! ٣^:pp81c vNײ\<1H>>~ȧȾ!B!BOۏo1p8sss~,||RVeqF` < >ŎZ:Xh_^ /\92cxy-K^ o>7o$E-ϑy0Ʉ>Wh_^ ϣyo$#'?ptǒ>K_y Ѽ7o$E->KON%I|M& o~ѽy&"|#FH|81h/$|)c1s9BpO1c'3B!BX+GYB{,81sB!B۟ND~Lfb<XD:<#(S?&X&1{SL/%G&C=L8OkŖIY+% albWH'_G 123 !Aq0BP"4@Q`apr#CRbs?AJD)Ș%qңq'n$S$ĨJĪ⪕2C{"04)fS\Jn$y+WlʈTBq*7vNHȩ2NI݉QE''&ș08`wH4H)$J;\ ''Bq'n$2e'\JTRC%BD$̓2E$RU IН Н Q&'RT\JUR92`E08BT$BdJ q9::bL~ĉ @$NJĪU* Њ $B)MI\Ap&ȜFdĊ}J$H؁RRR$TJ+;Tȝ0;i+ SBe,KMĎȕ)Pҫq*3vL~De&&"D R4EI݉Qؕ\UR>DɁ$H"G!n$H"G80 @RU%RU  @(  @hetɍ 7-oع4'LY}{xz/k5KاܣQ8+{B}Є)e/Cxߑh]#|ǫ|YC.V! ;Aw;{8p"\r&ț"l%':::::;q'n$ĝ1'LIН1'Bd&Bd&Bd&Bd"PB$~ ۠@ @ @{$HR$H"D$H"D$Hn{Q6݇knyףk/X hSU顬N*-#uޝWUV;&~n[%\K7ZYōuslo[]OOrW%q7Z%묈%_*OOuMD grUm][ٷSҹF~Y=D{\ۉZ6LÅbNc7k\=.W*UEg~!qfON(UkH } H6m[G lq]z>Wևkgn"EH2lj~56?/fk8]a/:,Џ~6Ma Qr.v(H*H*H*H*H*H*H*H*H*H*H*H*SN)JӥJiҥ4RtM:T*SOʥ?*ӥJ~E)TRJiХ?"SN)BӡJiХ?"S)Oȥ/"R)Oȥ/"R)Kȥ/"R)Kȥ/"R)Kȥ/"R)Kȥ/"R?!KS^נ5)AMz kI$^O)'K%Hd! HC$?C!GGM&~ ?*fLЩ3BhT̫s*U 37o]a Zw9PJĨJĨJĨJĨJĨJĨJĨJĨJĨJĨJĨJĨJĨTTTTTTTTTؓ'~*NTTةQQQQ;RwIߊ*?'~*NT;RwIߊ?&~*LTة3R.H2.̋2.fw;q8|-~w-NNNNNNNNNNNNNLNLNLLLEH"T"Gė尿m M9l?C>a7|v 36KG\^N&-U[yˬZ.cr]ư]lnjc,OnvCuTʬK[;EŊ-1bOH{P&.p[TX:Զ_IF:dj]惚9,OI]fVYi-,܎rs| |YsM6w(+=kjMڨ~У+*~r-/~J?f5ut#w#Q/?\̿.ZO~^25Mϻdu5Ws\!nr1.-[j,58+o]EizxQx۲r?V|mnJ직/7էъu뢢f Y&&, j[TwXLjվѬh5pmSuճj/[YޒݺӪG], ZMOܿҾ/kw)nTRp=456r'?.|0+̶"ދpح˴_ݲ -%-I'#sO_gMTf&BdęvTf%Vb/ ĪJĬJ*ҫJ*T̪S2JTJ)S%*d)2Lv]`w;~+%y#$~DȦnȤNĤ(>buSq(eQneJ JU(yRJ>B)J~RL$!G$*fҢbҪbҲu!]:C[և=i:ZN֓OZN=i1SS=g^/o?_6.wpWoY Sԧ3OXf*WfevfWfevvfY*VN+'BV|_!Qz *;'JZ`֘!5F"6{(B!iԄ-:ө?uBKNHHS_)e(ԣRu)A:ާy,(Y`PeQ(t v]}IgЄЇwJN"(O UqUWU~%WTJ=*NTw;xq  @p8E"D$v>[lp"Sdę1&n$ęvNv Н Н $M6D\W+8AAJU%RU%RUđq$\IEē2L3)I"bSLI2FHZJVi`Aw"!MdLLNNN'q3H"߆rhR @4pô$H"D*D"GS> t??a|٥ɬW~]jb\չ5`+7qr-=s?RԔ",5jnu,WVFضVĽ?XZ+nUr"dws[znB.ܑa6mo= )ώ+ÑUnĺ%qaZ_薿3]ҫ&zyH8m_U }|gsYzw[x'KtKF~Yk$?S&݀߿#w}ܐW kiaQϿѿF>Ur)lu5\zHwG#ԷK¹kn[1b..,D[e/Zk%Dm..Vk]p^0Cy_xz܉.KٴWc,й^+QW[UyoU^dn=/KmcD(L71B1B1*1+3J*U [Gno 7a,W^l q*31*q*Ң2RJO.]A)q)S/1K2bQLJPCȥ!GR^O*$NM=ahzzZoY=qjzuq9) VwAUGZtL#܈Z№RwQEz+P)A:`~>2͇жk?nѤ$y#$qMq)%<~bu&$ĦFHi)TR/I"ApC#|OB ruӬޥ+Jf+& UN*yT*/A;I҄dFy(B%Q#n)/YK̥RqqEb3(QgS$BVtCO UJ*'~*E8IT[7fNv?"|&\TAq%R\IIIIPyx OKZn[>e+< V}%;>I#:IY҇w(G$'Ru'q;'v$ĝة3R*q8R!En$ĝ1'Bt'Bt'L$)6D\{w8\J$&d)$JVaùpBd ''*)QG;&v$\EN: @< W?g=tIPwN7"vNT*G\ExAH))))))))*BA1"D$TR*EHӞҩwkm> ]SSyl;^֛&F*wc*#ONɼA](Ϻ^-#ʗsM s"MM]UzLTz0ջZ^\Zo.k5R"]zo[Ui} z_pJX|--[E{p\k;!Ub7wzntpfoTۯgysRDi TMquZպ H'a] 1x\1{6]T~]pHU-WԀ{x,){p,$kWHYܩ?t=-ڪQTA~d)K/K-5QSr;rp/ʮ-(,o ]W7XDN]t bV^5=^9R]&/y"_M7;܅X^q+~ [YK=>K ws!;q*1*Ҫ2RJLv{"GdSv%%ģC2SռzJD&oPֶ&~5Zs_&$ĨJ*!Q ɲ&Ȋw ^HE$̧)iM _JQKʥ/*UNXNS)IYO19)_ʥe*A;Idy-R?;(L!9QJ*<&~d^q8p8M{ ĨJ*!Q &Ȋw;q+q$̓2Dđ1$B䧒S/I*qȎm'!SVNuөJɊ%TJU'I3PC _HJ~e)&*Rn*Rfe;< ,I҄҇w &ċ;8H2;ĝ:fO6Dˁ;{#(Aq%̓2jR% %(yJ9!OI?d'LJ2fUBE\WE{Yew/h~R7k!LdwBđDS)O5)72s)30$gIp:8؜N$ IHBd&Bt'BblEp8q8R̗2Tĕ3$Ly)Oʥ%)yJy!&HJS|btRtIۙ;s*7`UNw<;ؐv$&jSғ0)3p%L'ͧ /$7LN' Aq! s%BO&JSOʥ5$^E W" q?fNt0'ȩ:N&qbw8K&jH4)p$nvmy ~w-OI/RہM7T|akgזC>7/? g?9x vMg>ȻIySatYBX= v^Z I1*71+7JU'Ȋw Gz״]at7 H:bTn%FUB "p8 J5(=Ys=YpS(#{<6B|b>~*a^YBY"oE-Kr#U+SyzvKD9>b72tV7ފnBE.K8(𺋰߿l]x&s]~⢪ KsQK*DKr*ofcx#}&R*EOHr|&LITiQ NLswc)/a4;/.o7Bd'Bt'Brb'%RTuOW\ %4ĪOXi)?W*#bHRfRiM7 m; ۪|9 !&Bd&&&"q8d3$)). SȐbUn%t̯+V\ yđؔRZQf6`G."㎈G2d&Bd&&"EN' IP!LM6_?]2!11L3%$)5 bULʩS"NH;Lԧ) +pCH&Bd&&&"%%%BT pe"}6-y׳_ GDD3!)!MpRHK?LIbD̪r*.ȋ$\Ԧ6ہ+p8h8D!11@Cp"LLEN:`Cb/?wn=v Jm"EHH"D "LLEHālD?sg'a8B(LTqlĉ?x ٧d?uf >0n}< 6BlbG`oK;J:vr$SAR"*]7Jڷ7ݖr_w?iq/-%B﹭z7\oSr/MKF F۸$: h!fq87TҚ.=o$LN*k.͟6BFFKA /] ~n7xH2!:8"dJ$E'Ru&R>¼46쮘菉Ċ!111*)MJdANT*):)8˼~.(B$H%RE$$%Bp&Br:)81o_MrEbb: SRPE It@!n=ƫ|)˳wD d'*I㎈*O H o*bw1=+D=_agf;W)fA1"IUi]0+V\"̧(Es Hibb>ao?*pċITBERNfRB`RL d4ĘIH;6#%RR&Brrrr9ԁ)&D4ĉ12)2yhBbbrbb: JJJ@H)2R?//br|Gv!(LNNNMRBBRRbLLLTƯ%%!$LWS6;_c>>LNN$JHp.wbطfBd&&"q JJJ )2g2ٻȑ&Bd&&#*EHao]iϳ|u$LLGL IH$H"Dtݥp/{m®h/D:`@"GDG؏.DK;ُ&nT앨&5߯z  ш۠=qZM~'hۗD Ȫ'vh;uQQ󈺌䨣?d=+-UT6=9 }}_ãuoU*ܗ/nj sT,ĎO}r.?G7knMycu "ʩbf/FjTM=ʗ+ݵr ⋽u٫vƵ7\YFzGsO׷:oNejG]r.7Ru,!1Qaq 0A@P`p?!"AR n 2w_S}Nٞ}N*uҮya? 0w Mw8 aDlAHɤDxĖ$Hd$\prWSD;Tw:Bwa{DŽ}Da}yar,y/x֛f^3;Kף%<`N1Fl#1$I$I$w@9',IbKXę:=RQRyaNc$d3Kz<|ۣ]E˭#ϰg &ޡ'ś ϭI$]Ē$H2d$NT"k 7sWsEx$E5~Wȏ3&$Kأ]Oer7ɋt>OBTbo 3 Jİo$I4H"fq;`D%6`e NTnfG5ƃH|ԩD9x"w-- ^bb} c;ɉ'%6H(s'( nI%%#83LБfN<,Ou(L(10?0I;&trbi)F?K$H&q3O{ $3- 2.; bK Ӯ.9] 1r. {BEk #:܇nc7C7 [گtw}G]NEف铋Osُ_g%gg(fo u.,ʑ'h|FeDa.p\!sU"D!{.\z9$F6ٍz٧l !BIBEbE R"H   w[.V"A F98AAAAAAAG6TZZZZK-%7N"fDø۾-")AA jH "  ( "    E 6H+^+7R7pE  ""DDAAFAAAR d"DāQ(YҿȣK̭ eFR "AATAGK9f 0,a#L8&ps>}%X&D8'h{ȽL?aKZԍK|\I4ْk$I44I$II$I$I$I$I$M$v&W!V§L$&NQdFAO( &OhDAA w2M&I4i$k4i$I$I$DD @ %DI$Iٝ?Ya;k؄Y  C„(Gb"D$}d2tL"d$H" C$I$DTNUB% J%%RvNn/oB!XAeFVHG^&L"dɓ&L"D&L2d$H"D2 ZZZZZK-%dY"Dk$I$!X`@`e[D&V:M&x 8fC23Db#@ChćI'ow B `C"G$H hqEW?\w ^ۮ%6z'6Ӌ<~N:G ӿ{wNO2gsvisFW; :י3[02g4g $˧dFAeFQdACC!=%t;RM'bI&I$I99I4`D`FKx(eL2Pl$2(̳(* D;ߐAV6gbI&I(I(J%%JnwǺ,C t;;S'rW{ǰG\l'+^ \]GdnC*KF1IxБ:pf&Poe`>*wW~9~8f7 q_*$6 K۝ WI7wQˑ/JBJ,Zpt=4w9o1R.y!+Wryi T+蘩gB+IR.#.i2&ݬN*فL+C9C"ķ"4V ZVӇF#g.>iZLI sz 6&by;ˣ8n=mZQ@Z6@̒#*5%Dm-.*8:XhbtmjQJQ xb{KS ˗{vX!۝Ĺ_ ӺǠ¸coG嬌s`[#tz+Ï`tƸQ~"·-%[pqg7$PAAAAAAAAEbAF]HR)HHH*(qo}>.9BYW9usK03ky;I2ܙ͞\<㇎xQG-_rytyytyyΞUxǒ<8\N$jFXXYaaae"E#n)b=[qqCϕ=,n @D%,,,,,<,,  @@DF;y$D2Lp$fC'j9f2S5 jMsX'2x3:g ܐ,IbK0%KĜDIx%K%ZZZ[[Kii;\Jۂ7˭JǶUdMq..E#b!B   "q Uǭ W/r%;۝&I$I$I#Wg[ >Ƿ؍V Ecf)~Rw;a^uUP?O{C;TQ Fx'mCܮEx> QD'kI AAl   "E"E   FV6#j? . qv~l'+>is)EZb/ KO ,XiCi>&=xWIra.3 {\a(jeD$It& 7Fb6!.7 'm$vK.*TR6B7@ DAAAAAMnټ? #֒΀K.顋_ )""ewM1MFIF?dDqK#ԇi s$#=J7dmVYѦ)8ne<*-CzK|m`)@"AAARnv7xW:Idrd&H*JzibJH)$L"D$H"D&H*$H"D$H"T$H"D$H$H"D$H"Dɓ&L2dɓ&ND&H"DHW!+D~^%d(gI`Ii4Th4TF4 &e8iqO&vSNNi8l4.A4(_FFE h4mv<4J64Q_o/*K+`87EV.j,4MWQZM4ӵ?TMkŰaY#ڬ6^ڋ @@(T @& _ 7 q^4:o Hd*χG<<>Sai@HAN@2[1dL2! e `ʆPr e,`n$kcmA|3wC1gaVwIܓ5)lf[2(2x'< &t#É #l7Mz|+cHI6b;w $w ك2v`̙2f ə3&`0f ϑ ̻-~d@J%=OoWO7];'M+n*dbs3;S;R:3O_Ϻvf3oٟú?G;C̺qφDv}gax:whxvu CcGo΁y:S/jIB1 vwtbe %úþHk5PCBBBBBgKI 1xqK;9#kGGD}QXv!J~;OC)w1b0G#'kvսj\aSR.]N?QQQwuwކwq̏9vXc'vg?'ԂS;aIeň'CHr<<<<<<< bĒI$I$ݫ~/Pk|Я·=D07l;dIO 2XFXFXFg#<3 0 M3|͙d   o     ( E#eъ~iVmK| Fg^{ ."薅΂,_k< kt4Qj^`-]vl#* FFKC]:R;[QyYmゴ[DSg:x-!rR]1b;S'|Ooz&J%X:%_G?Ƈvgv^li辅}-٦>%ѽvmfG]NJK5B׎$CE,|]'$x@[T6p\C CȌOIsr$qԈN5gb/(CbAUT/BCD"ѭzdbѩqhC/~[ ,-cvYp՗xȴ!"iB2׏.㷍kqa(׸MØ DȏVMۖ{&ܱap IZ"xɗd6M>-{INIKX$H"D&L2D$H"[v_^;GE.BbEcO@%lXɍR)Y6wV&eXa"S6#6rXDM  (̋ a%a(%ed$H!0fu+>vD{^2ӱ/U;BNO4!#2K `w8Cmz{T؎>WȹEw?w86@t+o'6MCAh4@uCk^xcH<3'J&. e,0㲎_4{ .}W(ȰdO"x'Gs'QHEӞxxŒ;PVx΁}Z/u<;wh?wRC"|” vgdfey{`H2gg=L˶guѱ.Owᩖg9N_>QO;wS̏;}?Fyy#ķ ^gj@O8yv,_B>Aˠ#THwGa#W5 쳲̿6da ԭ^Ǎe_UJ? cN^uI^Щ$|2'uI FsCT?4yf}w['-K?"^؂8sEB>%+gbn: .\rvK;D?GGt<zismS=Iw%;fris 'CE ,<%GXbcҡ,, aT%dKKKKw|hwpнkAwqǾm8ӏt%eŠxv;ϳ'#ď%(X}/9lG}Ntf 3O2x9A׫|#Gؐg ٛyHDs5>e~/N@}K/'kÜv3c>8a,CeLԏ鯦S?MQn4 IBJg]ձGN?]j~Zg."Fxx'^d)h!?;dL"^8K콈*U=Wޟaydu1#$>ҼڿBbqcD"鑐 hWQpGzy|TYF_ w= t/N{.x5'#̤]nwdɔj8. @41Tcd|S._.{qs+ȶ ^bvYx^Mȼuw0H EFDb\>+dHNA. l-/5b”8#۬&`ۼ;w^lX0dm!dEǕI:w?(Af-8V8,^M}44g \$o%\m -EXB\P-(,.U(جZ`@vYlq@K~&:ڬلe]F/ دCcÂg 3ʲ@$;eƎD1!KZy w;N)Ϩ-5{4ЌtK>]$xǞ 챙<ݩS]N vC39 y nF:rGݺ1KX>rNC#v= oG?>Qg0!ہ<-E1+Kj"'xvϡ=v͞Gi!(= }`8/;N++s9){ '^^bg gQ)cŵH^Y/EK(rwc)(?بLvz;B̏~g;>˩ۙ?LI\m5'<<ǝ?"ٜHi|҈c;m-;e3tc;Դ]LP<2]0$P~̔c |wԧO;C(Ry ss!'G( kbՃFAAAAAAAAGAAEAuuJ.{^ΒuEʯ/+$IJIp@WELZ[8ȽK .$y6! V!AAAAAQAm`GfFB%_-"vk6>8h#za*lrK--e AAAAA"A G `3r} .h:v%ㆅrRYboҭ]ސE1'IQAǁ x1YL~X0e|:Ş]T':'~>c'{f<$^>7{Aۡ=ȹf>+}Gw˩f g3a: @dE%0j%mu(/%\D$TXX:nVmX84Kq#lh|H39TlGdyƧmI #G3ðٖ+fTY2xݡvއ1f?BYSBn<#c'䟒;e$G;lIںeEt;gC82ǟg`=t Q?O.6{x9'   м\ e/zuw2Ҩ^8l;59Bڿ3zנS^fK/ٙ3Ý'p_3iq|adK GaNwQY_F2h(C̎lQv hr;v8NXL3)<FwE͙1C%GdovRwg\"~ڋp"!!{|KIܕQ2>GukFI3B9̏?##B\#;$gH51ߟ$H2{8v>Nɲw$`_8=vƉ}jK|F\/G>ymf`dM: $4$Os$H"D:k4k̝2OEr޹CFj>=P[٭$-ؐ!D"G&t GE;G }sQW B^nˁ hBL]ϦT^м$= u@O-l#р0ߋ2dr*eyeCX*7p"AdädZY"vȫ݅-P-60$^6vEBilZp) .MG [?~\|C]D֖ bQ%= N6B6Alz (?kh2NG?פ={w1}ڼOtz~!p%+HlVQÁŔ`"4xq&BEe:mԷ ) ŗM9[IZ%6xy۸  #  ``i!, / AځCg~uq蒗[!]%{YD$,S[Ǖ~?C`S/(RH$҆OV!G ~$)@   ( 1<T?-? xCk z 확xw!y{A}W}: v΄b}PGqw(Y%ؙyIBp 2,?e3   ,WED]ty*e3we)7'3 ,"=4ee'/[-*4z=܅Il@;UnPtEE%oAAE'<;Ar.Dfġj /|ov c 3͐\6H&8] /a8fQ%?ם3HUXan.W lx/WK/ѐN7'a=xddYiwS3ey8 CR! \J2Πq lDF"F4lp\ENWŵ6J6)"p< AګpJ'j5%y!5@Bt  6~ ,`JBpz p w^E$=Fݟ.EjEcYR o+N2^eϝܸ̀m)a6,;m2EMR|8?7ǿK=RdM<Br26 \%$N.2 }|E$LEAAAA 该̆i\j8W"><¦c- %^>E%uBK,j[\OĆܤJ J$Da31bAX\,>"l> KA (pdOQ-bu0ECb/&ZY*aDlzAF.gzI3/,|?6 䉋im 疯'ku(0 A e ^k R!pmݪ(ݭW'pj|$ ;8WD;2!替$6AAIČ3P"2T+8rhdyz|A/r!y3ٛeQe eB#չ' Cnl: ě9wo>)fQl@JY}8AARq#-΅ g,'Bff ZG5$ș~FH B P\r!E5! UḢ񻶜Ao Zç  p9j2O9?K094G2Q_f#1JG2_:\ K?M,&^IeȂ"ܸɮ$;.i% ?O(y33W886vۦxR.onwsax(yoq/Xqu &H"[ h4쾓MiCA#yn`A;[):_C &2tKK<#8f; y ,#?>u}\n3$% BjR$K}&87tӧxĊA'[rb)6W6}¼{1fC$FxRd|<z;̇ a|0ċ3 CyOI \w̎ aXmvAA+! Fh9C1_ۇ0`r<ܱD)& vJsT:RAu4 Xa.C3i7W1kP3A:Nx$I i -Vi;XNcYdF8|3<Q7 !K']O0k#3d^ddpʒFl! o=i;M$H<4G5<6nX!LM0\;I_:I$I%B4<63%ǂ76>G}8 (02DڄO-PF&dHbdHiiKW l @Fd1A 5KgLI$I?aX{K+ݴ;:I_PkG1<8Ŵ6![xB35č 6^dI$Q߱;($2Քb)Ni>i&r" M<O^p C <6d75=DEV-*I$IEe~aWC- ~1s"AjqrfG> 6#n2N2qxX"XJvBI$I'!#ry+ǖdЃ-h_!w#(ʼ+~IOqm$%K2yVл5I RO>Q"}'T2xeJKibs=,,ę+7'D ᰲa&ei,MD6 XF@ KNIda;%_ B$\PFt$I$I5fnҖ$r8)TVSs po$/C%dcP2EAp!a`, cfQge!I&Ij$HI I>̷p  MX$X8J᥊MW-GcB$Qym]D`C%dnKOŒM\C3@Hɒ%OAQ l)hY5ƴ 2YI$14hD8%,Yn\Q&6TUMrܠ<)vMEe@߷sFq_ UO=MqGqm},4w;,9t}6wl0,<;,-< }nf{+ !8Jo9$:G$},1ˌ4ۼ$ "9n$~ <oZQe'16 EM4{mAuS]G x! ,/\ͻn= AOxE$@u6 paq n+H[]2,AvI<+?-8 3Km bGM4I_m&A6ya<0M 4S |ʙo⊢L 2-l<ԳM,0ŸO)ӏtE 0PD@WQSy_A8 12kna q̲PF~3- <_n<-˯;-7=4ARUx4SmtSy&8l8AMBq?.c}_vt0q,[1seJIuk;O9@"MgTO_ZeS ߷{8nZS Q$" :!0҃7Aw8v7g:۳9q`$/<ѡ\H*_ar墄_7d? 308!)&6?iv ( ,O[ÎD4ƌs$@ V m{R٭ИAįf81Z\-}<Ͻs7@1=tvEEF1|, $+j{<͉_|zkϷ7/PA٪N۾y]|o3'zStS8oL9s$" ssa~o{qWM7U{ ?<.?RZU)<$3? fFU]TdKn/{802|~3hD|9/=xI/ ྺq kI!2$+PpT~2 2 o=Fwg 20NS<ݼ Mq8_Q"0p0?5?3$IC.a='?2ém&95x׽U`c;<}_:{43$M5}0M?j;8! W}=A^BTbE<v?]N=˟gwYa%"jw P8]YouIO;st9l5{ۓWI6 8Q ,kE"c?Ho Q`!; (T4Vo|2Y@ \Q8ƗL$)j.)m(Ci ϼ4X \vCr u78,~MG:X}l+ G! 0IPB߯<ֲW(_U\R"[fSᱚAqQoc=3j;@@SMk}'M_h+0s #}o9WyCr{ۑnRe.s0wq_A,> Ru<|>ÏpI3kg#)$<9SPk 2X/t0MTS_{,è9]<&.24$ YĦ= wm3e,E4CSQ@<1S8R]77aJ #}Hv6_w<l0ϋ%$Vv',_Zy p:ʝ  ̒ Ӏ,,CS5x\qQY>?+mCI+$~(C&5X}g]?E* z.BQxk n â<ʣp1m"Q@rLY@ c'2Ce-{_aY ]A'hCͪa0 AM!<#l\NIK٦DZ Qv笾Bb u~} SBˏ'.CO<?~{)Ͽ0 AsaQWqdO{Uјe6nWLF XK?n>7:M֍QlsXe>;MxIϓٚqoWZ o$}6 c_aײ\ d)=n77+Mc> gNaN5YW9S_8+T:1AY`F,,: J ZLꯇY.ޓs'3G!ё*ӧJ^<|S;Ǵ('JAϽ(qA` 5&g5Qt?)ZLpoɫ -v $츲u0#Ȓ[i&1qeA>, ¨u,0.}~o\`_x-t,G:dDorr,a4f =h$1"$~/+=]afCt~tz}XM˾>W5UuVЩgq\V-x͏;W?-=m5_çiDPDsӊKw.x$3N͗QQI{꛶sc=0,0"zY# J?g^t}~P}֖ypUc ]ܻ.rqU @1)m$}]t֔/,>y}@{b]۬ /Yekaj"`?T0 /69'vB0a0, yjWL7a| cI, Zh8`vOgcض0v.0\kEw|JWAE2d󈄂ڊ0_ϸL2ϝ)\P< Χ 8i^MIe&\+?1!F&juq,E"?4_jRy^ K,#C zC8z<0>,28岴Q4I:( E $"o ?˚87?ry r< ->䂨PS(;OiAei.'LԇRD7 c q6!i+CI0jN~ɫڀj8̞%] V˄H7AQPp벛,pUpÜsQ˔A "ء,Ȥ$O!qY\=a}5ÎQEMs49A:UA8$2;=3ťOZt˳}8_s9gJ1=s뼐x =O}<zy (h 8c3 <Aﺯ 7$O?(WDD!@Hqo<5 jcg w{8+0<㬰ko?23~Ԙ}7M4Es:ǢP {%W U5~M/8I\b -)n(6g%FAJq^}]4^!q9;_>/._}pLo).~}NQꈰhGú!8 MwQ%QYA:5ns}}Yx{뻬cM|ӌ1#1<8 miדEG[mF]%`|Ϸ'8᳐‹9˛oO/(3N0xN00a-qM!\p0I&n)(|+Hß<f(gt[ι={5`K# $ _00Q>Kmɣłt T.,b< ar 7{ u硊/0A?A"< w\烎({/?( !10@QaAPq`p?)J\_BJR+'@)JRogKRQe)JU++++++++()K R)JRt\)KRO! !4L%"xE:?[z6Qe@)J^VVR.nnj)JRX.R.^o^ڗ'Rk|m#{Oy={aQW%^Jv/Yk}r6It++VW++W%yeyey=%(R]/ItN _z.*u^7\Un}㲸oNb-OciqKҥ6M@I$08F?g~?GW缔VBKnxwt޻\.04\4R)JR)JR{\hҙ&Y_WO\. OenyDFףt)JR/BWvM_I'HI$I$ %н5YeYe?h?EG~eYxߔ~/^OE__/eY~KYe%0QhJYeQEQeYeYeB! RZot8.RoS3;_OO N1l |6LMmFR`R)JR>P=%z?{4~L␔MNpʚ/`?|ޞM4ˬ")JR%)r^/V9JRey++/m1RTX|>{HBi0"""6"""ًf""H ADüof.EEYEV^ 4$I$ "!"">>'")J^ \)rRAJR)JR+hBtC-\o WX~pIGHӉ&4dz#'"":-⟰nz?D.)JR)JR :ױwr1My0lMwTaJy!u$2='JA6U쭍Wٕ䯰)YYJRl)-KCzTo+!B!B!5#!1B!:!s/ R)qt<7Rչ#!(A"!>0ß]r;xA=* 66666****** )AJ\.JR(^E R)JR)qJR┥)KR)JR)JR. _`!rݡJSM&ݥ9AQllAI OUm7_6ɲj^ Ҭ.)JRqhBw[E#qTWHu蘙Bf! /eҹ}F4.)KRv6***#&Jco91!B&b""!M!\j^a}'m}>" Dd~ ( Daddd!N!1BgᏎX|>0 hxQ/SLdev! BL>;T1pb!4llUG{tz O|a^ (Ɗ)YYB}B۲\a ={YYqc##7#!ZB"iEEEE)J\3bKeg. ZDDER R)JR.iKR)J,x\- 㖖>%)KR( J=Oج!WT"WolW"@tB!1B!15Bde +pbet|Bm 6B bTRf!B2+#J]>aXQQsJRR댌d!BFب))JQs,=P#&!1N.)JR)JR)JR.W=Y|j+)qKE\=Gš&Mp^;'#[ɸ*b*յ< <qHSBtc!3L!By>s:V9[14i_E(N |B'R”)J\^8t_:yEEEEYY_!A-/+['222BHkqh]#U222"DDlllllTTR weaEe}l-dSLLmب²VVWFFFGBbT8hHSߣ$nΔ.)quLjԷBЅpEOWzP[Jzu3>WOH9/ȗȼ?I=]4^}~g==ٟY=gۢsJ\^bi/A=Z'$yOQ"'Z)´=)3Gע~z>^lz¼|7='"/ׂ[iG.:, GPF(QQ . н/22,H" 粊(VX2(n"${]s1aDFeVR1 L3@W5!BF٨䢊WO -1$B"#clTR)sZ(FBD"QsKpѠ"#cbM"/z^%Z$$]/^2 DFXKQ#WG]eQb##!BJRKz&R})""-T.NkeԚ!5Τ.aN!3J]sR4-<5=+W=) !1Qa0q@AP`p?:>2B""uAu#+UAWAG!DANAՐYYB#I$ANAXl(+*:>9D!DDAIu_Kh؞[ R)JR)JR*Y$̹kT6!2cBD' BLaB! xa1əmPL!B!B##!B! !0!1}غ~, CFM)I88SH4$iV"I70B~6/ij h{оv1A3סl ~v"ڕ#QPojh MEqv)^uR4h'vpDB!B<6zdDDXELaQ:aQpШ RJ钢444xÞnc&B>Mq2G#׳^{!Ɇkɯ8kx3S6Rf{3(g+/ ur [IA$eV`+++++ Xe,4!A,'$Cv)P 6ғm&2C]WQ4m?B 04.̧fNBF3;N[8M8]!ͷ?=iY_F^ ЌAǿm~yK2Mٶm:ӈ28e^F%\;Z7%ۭyA=7Opz5i0lӄ  ݚ2|hEX-4JR)J\O$)YYDz1 W+ᗦ^+++O VWEdfgz&I D=?OG=D] O_zvNgבe콚r9fM f^%+3v=EyUa1K:XA|Q^0(22222(+bW4QX!iKͷ 2B ,ltČB!Hӕ0[!B!B0#&-ѱy6f*DȄz&=~$ХJ't4H&]wOb[a\i&1a5u1UNZeV=CSWDY.ؿ͞v-H/ Ȳ*Xt2#lKjЈDCZ9. R)JRJ_I_Ɍf(^eŔk?L3^'!8_yN'!=qOOz/`0?B:"'f p 1`cսϵy-/ ʷx-Ng"'C'C('G B|"5*U)pԑmr/o<)K.4)J\ԥ)JR6n| 6H^w (%cE&Z"K`iQXNdrIWR)J\ԥ)JSj-6cjn|ZƙB厑TRYbQEQeYeYyn+xD- Yv0ׇr &+ \Άwta׃w,j//IШ>O)2ax3X"y坌%:J: p`B(ˎ!V%~ gԙvY )J_"JR^ x5cّ""l e-z; |QYYoyx g-;;aK\ {=Y\SI:>|aΓ)JRK)J_y3gIu. )qҗgeXc*q6exkhi'Hcv}Lw|HWZQ܎:+|kB ~gay:B=x}'k0S㴚tȶY_PW5\Xa;^өX|HvaGa_?FTU X;\-?TTTR ;%jƜe H' I&}eX\** ˲.Qy""kpGPK" , J+\QY4`""6n^7 RM6ϩJR— q!B|(jMPJRe++222!m **”)rB!BTAb\VVFFFB"&I}.m@ɿhsQP21+)YB!c2:J*H8J/-)JR\aBőn-)JR"Y)~Ut!BR Jh=Bjg!B"/EEXDcͿ~Ǎ%,^ [`)!1AQa q0@?,rTbNc$XPYaapB$dWFB@xH,fwv߁\l2]~ !$G{aqx7N49~';G FOӷ_N;YDѷ\ݶ}$iɒYȐX mJ!G׀/G=HSa8zy#\z{о;V(~(ذQ`CZ|e_1N=WSz<'Ո%_g!.Ig.'|W7?f, K#,,rxepf<MqI},pL,a7G-W{mXe>my,43mmoZZ ZԸnm۵jոRttmQ^l7x|L3GKu住#.;}*V |>%:Lo'xcu!7YzclN~x&`aAbyxm2ӆvS巌s;Ѽk\7n#{ץE]? 1ͺ%,{K1.U܃3']FpC_hzӐO7xv?&{x{~D?4?XsDoK}>'Yd;%YeYe%bgэ)e]8YoιsFK;8.Vȷwv8۸~~{ xmg'ߣm6~mmmoK]q`L# Pآ8?@rGvfz <3wX1ޣHu _"CqG13_Yݔ}x%~?a8uxoǼ%AhϥeW{ɰ'o;o R(;;μkkkj=%F@z̤$l~䪱.<f?0@':fCS>}XCO4c:TGF!h|XԿ}zG _1rz#{Ǩx СnBx_c^ZtR"K$λlVH ,Agpw'vHli&qr,&Dl,,GvM`7rK,Ì28Ń,[z[[mmzcig#omնۖwmN M {mG:>`]t鮷o}^d5]х s,/?kh:?䞪c 2X럠Q>&:s_7TG)w[EXARk%IY#udYuY%AeAcaIAg Ydg9AeYgԖXFYbp?Bݓ;c7|7~x~8ԻTYߣn>v]K-MOxqߨ8> Kuًx8v2I~`yx2H]hfȺx0 W gdp% L*DQ&gp"zo#̲I }mWuo [iua. ,dYqYYgIwg,pnEwVLzqmpac i>Rm-w{Y;g ë-qk#-n]haw8r87`Ü$ՆrI@dp0i ucep5sjE/7hxg\3Yew%@edYwdYe8 quOі7qb} z!ΜjNogDp< <l s[M~wIUo כ6m취'a-R[ ɺ;HP$;LCacY ag ΤÕyN|yÛ<ׅ9RU#(!6 K; ceN %YgPppXHYX3XٽMB=,%V=&[=f+x@暴H8λ[83O<;[@H/ 0Ew]eo @.mw+,ol&<6mg,O.:2\fp0h쳷ZZeឆQɓ;7/)Ǭl; >k>YϙS3~ͯfT+Ve˻v5w62NmeBەyM هW폞rWmZ\D7TQDu V|iYMBz0}B;cX-%/[edcddڶFRD=څ(Y1d$; ]"%pd-d^ٳ=[{ۢoh#y['>oo7gͬc(pº6v ë[8 vR{6Y$f2fw.ٱ7n6z!1Q'KI&Y IyΤ {Gяo[ɶ) zvm,z6> m'C_3uWm㿣f׮zrx C3z."6lN/'\&p :}=]eA 6]eaAc9$ ^yaqp [ua} OVu+;33+?@F̼63e["[e=yH^Kdn};og;И qovp}Ydyñ 8,,, l 9vq0 'N5nYrrxGxxI#'x1p.ZX<6g> Gc!?Oy=[wwo) o 0Y}CCC_}>_>Os )>[OWOFOg;_U_"=#_"_|wccccwpڶ[mѱmGC=eqavwegc!%NYv7|<;wV X̌Dg-f;x$96xI͘GR>/6D{1gΫ9ug9ݿBZ^xYyXe{2Pcn-HlX F@]AvK}VjWD#|{ WF{}NO^ S3/~7bw{6^D|,6SMyx;>D"X$sr5hmZ.UK K  c\y.Ɲwh2^ݝv}-}:'738@c͜ M1,qlld94@@.wVˍÌrV-׸[XQ1 j]60ۭGykyF5mVx^Rmmxl3mxxOUxN'E7-8vԗ dNJRengqea`9gs=#KN;ǵx:^Dg#חux뀃/Pxxb Xl=,I0(gܽbuc`XɌ1wlLigi<-N2 : l^-y mCl$$~;e@9gZeĢrÜ6.rx2xNB8!l$v`W1F;,$ W"{+:gvDx9-\wߧ'Y ,I5$BK$C"aea2;'r:ML2!gExO FC6 mwM!ojqXJ'fYl7Z{h=h`Sl6I)wZ}adx#ٺ埡7I,w*39<2LwaLgј9=Ԣqn|pdK8 BOзSu, >rN:$3]&;lcdGcvJmgwPYdw VtN'c Ox|a #c0Eb?i@j Z' 1Э=>ReYc}(Ea+>G|)4FʒLcǬĘkdXp:8άLmY',f˳ii*6nOaI6GCx-6~7,3~l$O ee#'N&δ,2 I$ş"l-`&vV6u%IIݮYVX>eh!٘ 'Y'ޓ`  xRмL lngJB^4&~7G| `ؚ5avxmF $ ᵖ3`$pl .lAxYdIvYedxvlBڐ yc`8޵G:ˮ=ᔻ-xv;WyOdugvM\a&K$^^2BYzI5H;>ׂEtۼ:$NgRFEeE:dP} dҌ0a,Dar\S$1xF]pfEc ]sY8霜煑e8-ǎw^ V 2+1#<$8S: ,uHgLx&;gNeISYGmg0]Xɐq6Ym@AN NY2ޓ9,lޓm[9-SXfK W-$2q0 R@:N#eI.,n'O~x|˓Ùu\idgKϽa,I$, :vI$d$lI,C`o 7=|={n#T>x}l`$᱘ -xNˬOg >8O ޲7s' .6Yl]lK/-Jkvųٵ 9U. w$XIwd7 w$aMF{93 <gFƲ;-Yv8x lkϿ < 8E1|}\=Ks_6{Yc3IP}CObG};y;w1`,|-xO:ww\y2,܉c;c'{_juZ-Cԗ“NG^'L2G2ņI ,2Ć@g,.$2D= ,"wog8/mc+-orIxЕ]ȇ,e,l1Vaib@gHkLoYHe}rm卖2WIնo :pFO < rʇ/]ęem+3S&$ #?_k]</>9ՑӐE^ln[nV}wl?-ܿoYm~oxõO_7_*_"7Wn0æW1pC1i=dY/u#ܷfGD|F{={ȏwwɺ3wϾW%?B6\y+*mTX86dO 6Aa ' [8Yo< yۼjÜ>~\ba;Ճ1x-oǎ98ϩ-6zmy5x׍FVWaxV[6m2vcpmUh\8 ,a `bX,@bōbNnbBŖ 2&yN2G$ g`c&6KSx&oOo8,˹qfߠOIg=RYC?XXY<;'eeFsYgOӲ&ommm!XazαxxMa#8 lx 2a [|䆍 Yvgv!8;2:|^so-9@223fYIyߥ>x@?i=ZH,,i6IY%uqd8eXe+,"#-YVYedg9e{eIe[xo;g<`r :0,$YmY#!0I8;I- %uX8]L'579ٛeyq708C2{WS =Ӊ~~#8#7wX.= ;=ؑ)Rw.h?,KRݚ]Ϯ7޾KZwݼ󽳌{z#wѻzϋD#> ~ɟi #$XθSU~6s YdYcv&9HCvvz$넂:$,W[$MI; {şI: Ķ<9oOpA&8ad߳XB|{F=>Y]T!yYMyk R9^6nmx^1&2o#X2,񽄟76A=1{x`Kxn/۷.re<_6|kva\#xV(x7){=۲t<}o;3>S8J8 ;xOtB]QjQyx#ՖHuecZdž5$(>.෨b]o.}PpI6C[BXAbu{k/=1 =bC-A{Jaj,ާڗ܏ .-|J%Ư|J+W9SkF`@1wdXX@=8zQ0g } gG\e[9xnj9d-=HCy03wbě33f!b,m fČ, Z1:]#;Ϲ4xyާ_H'ceag6:=7@=<de z8A<slo)}Ys=d>rNIGg6EYOR=,q|SS0M"DM4ŏB1Iw9"c3b3$08bq%kƐ%-s6k,F'>>78w-x-4ן/ضk?{w?[[?S>Go7qw߆=|u{Vry:ќq8b3:o~<~<4?2OOi z"[Ԕ#*2|s=3 QfV7Wطo_ %̾in4{Kx bUvYmkb$8A##3xYa 2%Xp|ںgY9I ]qOq՘6cg 6~Idf&GұKǛ 10-o3?acyBq[޿{_rWxk>/}߿yw~KԿyx}_ڞo_gJg=/?su53/<*+X}gS?(&{հ6_7Xe(y!ZJJ!BW/ɯ~ģG%Z(o߹nQӓ _vNx @d#82S$K,8 6K `Iۡ16Y,~.b[.8Kgi T8GO?z+:s_?/gw]IFO>>c?ܻ7NGi#wizs/_?%"//#?=06L_Jy RUo57e/Kxgv{~paaW-vV00XK, XȽG׬}>"H0Xe 76ူ$&ά,DXx?zcx98zt-ITe6qqY#,VmY)dIe2,, $,,,$Nrϣ,^X4mp XKmD5#2.$,wd1`LK1IڃxK$q[;wn6szg3-c,7x->C~ҍ;bWәf4撸, 273~PNtO PVlY~(b5h{OU}|ӼvtA;kVA"Wd_uL7.}l~9?1ۇ"\eaOЍv63pOJ۴2/k~amw}~s^IX;h!C5L0 𾆗\{^>#Pީ իWdD& Pt*K # V\Vrvܾ;)'!{˟cx9 s1<F0Vj:s=XIݒ@gĐYfA6Hw[%t׎8fg_<A}x,[܏'Ywo~;}T7E\WjO_=?LGȫ[7PH@]=,>.~_*`wgëaf]Id`6;,i⏷sŧß@ X)GΜswux󆽥Q1,[Jd{&s~i|GnRcT236T-\uz}2? [jWkWi^rVxZkW A|nYsY.ܻs5/-epJ͂|6W8gV' 2N8BH,gu+d03 ;'9pA: '~'೒$ gOI"/GsηϜ;M^^ hl}q֐ܳQ.8 P@4KP @ {#0:Pu0t1^ܬ^aIvfyh*ł%F7cΉjFp-Eܒ~ Goؾ]iu0gFRCp495;+; vZ_*a TݓgN6`䜓g6=j61*ע _\y[K H<[ȹk~&^nk-Mb|L'۾;/Nr>\ぅf{'}  ^ H`!Ԝ1c%B6Ig l 8E lll9ux$rǧИqMx7m>6AwaXA${ ^-oT|VlPU=;^Z?o{|=|GkV}?$k>@Xc1z7q_eoӯ/f[G~NvtL쑌C'ѯǺ@߀$4;F cx`D`G1H@{Xݗ#|]=,kՏk>}>3},fG.}OI_\\D}S~3s~ͬm%orZ{C< Rǵb pĉfŏk2 =X`Ek} >}LLzOg#[C|;{粿ͨT\$bޟMvlHA%Xg%wc$926a$x$I+xKKt~acHmϤ98 ;Oz,+7>r.:p߳o;L{7ͻwn}gFa43t0{}xOo˴{O?[PSnW˿0{9|_ߴ[Woѿ{YsCRsO}(Ο)ҏgGkW/!{B_NJfػPS ?>"/w{OQu^c$}^J{_'և}uoRz=|B ?qD9I?ޟ\XdMd$H 3:I8G;۩ <2QQKwbql[89r,82l302wϙ"jI}~gֹi}hOvcX|_X^ǯ ʙO'3o;YЦzO%7ugurTNOk?Om][$3O}_Nu߳CڹTC{ x.g7ohS=gwBG}o͘^wO)3Pȩc~Xd\]RtSY~hҏo ?H$#ӑg|+>o i^"tp!' r' ! &W1Y''5*[/-Zs:6qd uq;9lAuu $hct5{ wwv/mտͥ4܇U/ڇ9Ox~G?0tT1ha3_y:?m8#40/?:zAN]~j;uG?,s}_R>7?Y? G_ֿ5'OO3?ђjs&}kWe)M-pxsg~fo aӏI+Hu->5[~mI%6Y'pa9!H͎3#!$N,E 8BN3%ׄɲbِӌ2,`8 ;x I}8,A=q{M[ ״-n.oݨZjµ T-Z۷n|屏 lYEFn̾3ؙkCD:!eI݄p,ѓı2}_ǵk/1}w}\5<;@ sK'i#=Cѐ9eI՘Y!%a! %}9 #tp\1;&-99p$dwZ{)|frp?kGM>"FǴ @@9*j*6DZc8Uh09VMfלa8ci>^KL3s7o{Yk]3e|nm4L\87L2'qr06.WΣn=J bcHG#/\"@w;S8d Z6 r÷qwʼn3&L6,Y< |뚏i6f}IO,d:K$,p;I63 zc7df%q8Mp.83n NxNn4>Ӡ$-_KqBuߍAa|߱ 6 ŠMPtgv ~*])C~?]Uv`3߽c{dI nSFTE=Uzp/>1vn;Bw>8jaP$$Yfoxmͤߘ i#_w'υ^1xGxUnܿk{r.+wFC-ox62gV;>8rH$s{<%ɳ&0l '2@ۥ'S A@EA6@d ;@qz{죦,ocimؕ*;L˾, >qkZV@r v#B#:PC#' тH i]Awnu6h-?cT]uUYzV?kOp͛v@#g`}`#vsyLi  =|Aٛ6gf ǵ_lbg|I_m[oϴ>c? es,>S'Ol{]1&ϴ x6}>X1 Ʌ;;a1!&u!' ]e!6buxAm@ xgde6r]dy,95g_ߙCDgw/k Ǵ!z}1cكP7r P|u4(%OU֛2C]s܌\Og|&u:M?xCՐ D,!e/4vϴb}fŎs prE,_c}^~~~ϸ~"/?%2>xs~jѾO>K~? Sa}kT^M:<^QX>[w)沝{_9$/hG{<@X8MH!a%weDNffB\OUtdsBrD1kzwp<23xYK XAAbuuY lK#3g|?>=};Sm>~z Հz3Sn; $n_y>/We<{ߝ?#ɷ_iCԪz`?/wJF7A9R/w?~K3KD>׀[ۧ'ciOf/ .\P|ήWg#>"׉ÚO/q=CKF6Cϐg14O~>q߫L~l>_Fgą{x-lݛxl$N$Ց?)-:]Zbw0 ۳Awl Gя9^0]&pu#6#  IEz OGa߯f`|YIM6<_zzl?1cvGMwKm1~t7Gs<:Aib@OY[wa=WD{n;)޶kGI>$~xۨWtq'G~tzkp=_yO._y׸>~sB.=c։W?̷oĽ~xGanO8} 腾{Y^owev2"(rx;@8lr<"ޜ8jnȜHB;!;0m~5KսNNN #x,`8 ӨpY29_&QZ{ཿ?C|?3:f0?iZ?qM3j=K.+ZɫOHmdkk߷ʵoԵݻanUvZ q4Oy!⥯bp!pͲId)K48ox].$ ^챂cF`,oFX߸^K' e 􅡩Rv/Vuijծ!Ny6,^8/Y}DzcՑɞ8'+л87ѐN V0[.P]f͏Bϵk>2ǵ3$Ѓ&1S*b%-կk^ҭg^֓k-B\+- tP=.кp8bv|XdŌdgq-OXLjxNr^axtoK9K,l4,́[:l"Xa$2ٱ"6j8Aᜐ+cyS4 n<YDB &+ 02zt'BNJ/C˿j>a܃$ҲXaocag$7B(H.D>z9:x$KSdrEI6,ٓ%də#GiX1^Zx1P;f&#$tNs9e%Գjfʼn\T;@vn/=.#tGlAD83!ezga# =p O[KĴHK#=^v PLNЂh74cs3G)Ϥ{Nq 7|ێnGαw}:~7S#o;iu)7[;ensVgx b1܇wz8;oq1(GFh54?DeOKTzn}zw1O5{o w2}&.=:I3>b~Ԍu׸N ,g7&hO_wmR{(8)=??߳gzd$kA+\^״k{nܥl1wvT'FM^J}aog6đi;~YtiGcqGR~RXm1zjž^0cѰ*O/> vAtEVҁk\0K8 UDN1>]Z7;f_Oze<Ɏztk'3zCG>t0 !~e=eݡx3П~6}D^̖F, _}]Z:! ;[<;$N dWxkO 6`xOeEG$y 82x  ,oF}~$tٹcgnӐ1iG \| 0(4wkbg?1= 4}h9zr!C4"F9.I)1#*UXC'u؁4[8 +!=!!O#OWA[C7rF^1V GsL JIxvx6lـ &ɆuO 1耞 "w:yxH,Yl`,m'$C$_vS[ c̎#,3`, 8d x-|[{T+͟n+ w}Yb~30lo8΁']>ŅyCB|4Fѡf5oSWc+yʋ.G>d~hD-6_;Io ӇK$eM ř3-Oȑ1y/N7wqg&FHDc!DxOWN`)K>$l@bϴ#xb^yׇs'lFK017'F ~k{}κ?=`wdЧ_{O>/@s?K_o{" DZQG[_9!oz ]'{6YzR?DOi>'_҃ ) StxOzO̼HxI}/zgĿ̪ 3:' zM;\wʿNL~P6Sc9.|޲c 7a@AA `, ]W:}=gq>_|8VohѨ?O}/C?k&-#ązv_o~U>}>V}\CnSp~*K\ ќO=͟?)ϯq9u({u t_iHXq޷TүW$<3H7/g_ǹ' _|ή̇ߘ?z(o}>!cԻ՚=VNd;P@pN̓L)n0@9&_a cpKח{Ԏp@dqGaxBSl%P,\ g{qw~?(C.>&k@ϏY/ vym_}R[.[[|م>>ĠG}Icps?_~uN?!&y{c_?4џ?lC?D?8MSJy_?O=߉cդ=OG?zCzz֤ݏF/H/V}c`[Y,{HMc?H1"D1^X;jLC=Lg96XY9;Lb(ğhzyNVV8##X'r`m .1`y >Oc"ػ^??kkГd=(G?K-l/w_Sqs=OʇFo6~TzGq /W/2 3̔ڏC}k/9;: zwy,lN <,T_36lY8\3??#=<9%pλ˛l1ǰx ~^qAö>rK 6"9,`rB, 4ujH>q #ѐPowxNs9I;l,# !MDe |KR" Ce>1$ҝ M>ƗtY//'XdYe9e6XmZCvߙp}`{j!;+h^M<=NNpclÄx!>@>l(y?:N"^}2sY<jFWHu-jMԽml{տC썫ug:3 1CXnVu!üf0YzEf]Yyc،8 ,#gqp,1%p!Cc`&p,,gygӡ>A}̇4>o'`yʯGMߦRg_D>?ϭI>f/n۟_? ho_Y(|ϥ}57ޟO(/ݏo>@6}z/G>΄u,lD]q,=|, o Ϭه 21oI9auVHlvN_I9f8 -cr ^.4 DA#܈ayxC42pg^{I/?'gȁ?%.vXGƾGw!:zepw?կKx=Q韼?_sBI5{_l???zk8~?]xk.qC'gG~5׏ϩs?/P~DM>#G̏i e~|ϣ?}?nlF41H>v@'A 9o ȖDIu6lZŷ9"8K#6/Ym %v-YppA#A<^A9( f}f9gcLIy9H??'f'?>ܾPOmɆߊOS=8~W!SpJ?h~ISn] rK=gڞ5b]]RWz KA(7L}<> e_/W{_XPyvs;):? }U~z?Td~wQ=O7׷aIT_J>>7T/|?|W3Ӎ3'JS[*]˷nܹ>P\)j;B:!Ǭ)&jpπmx:6 !+.ѷ!`.c80 .$8wc^ځFp'>=u!Lz)x6Oq?8@]>zt_kcw_&>+Ńk={?՜+8e:fߴ{?#z'l?y?>'q_$7~1/+}I1Ss2!O>JyRaO?WogS^۷oO~._ژU?D̛6f1bʼn2FtNOzn8yg8z#'x-BRF[1.03>Y<%F038'aۆ61H 00DA1KWzGGD'i%c~ OaOvs2?}_o;1׿O-n:G_}?mL=}:SZWm޸4Åk8,)/ߴ)Cڋ8a÷nɃ&O5f>p:X}o״B[ךDNxfś{<7΃v4?j Υ%yrW.Gdܺ.ٍ}#7:P [C nc KFp 5 7! Aei mPM_uߠwWKgYϽFd;Pf>Z c3!OFJ)heQgر1ۀM{Z>|6dXgZ2=cd;,r,&jTiCW%J󒽦qܿ3$xI )"5;Y=Sx< /cgdϡ-$Y k'upx5l'w Ny<.@0 m` lbl E …j @G",uAHuk08l-‡n)sf -xDL +Z}5 y9<,͂ 6&1"I'ϴi$Xg2grV=cDn-}A`N0p'6pQ!oS6&=d\p_Hbŋ.gjv[lnx'-^\}S&lϼ )^vC~&H`^fgl<# -d^t1ip['x|;Ǥg,EΏ"_ʡ-GOD:'a0W\u]sqϴc@ \q# ie6rÃxx;ΰx2=$F.,qkox?ɵtyD9C Xf S}^:'הl)Rztr3o?RW~LWύa+'7~?-1פOZFiPLWϠD?^vdZ?v쟑>_w3 Hy*v/_79ud?3o-c'M .'L'Mpp팙eN{Id-ǰ@^;jqٟ{q#R8Ӓ͚B#rȂ8 8AYA8}v6.01q'qlOOD'ć~HyhG}B~C/co'޶??E|>_}eժH_[~}Ͽ_U߻}?Fdt?oo?Ce<^GWvvIY:c#M?ł|$OO[_>ɽXsCݙaCKIn'm)2Dk-iӷ61O Osd9u˶A2d&lf}]3i>g,28u0'eFD >D7ዱ-F5g6r9Sދۭ~W?|Cp;?/}L>Ϭܞf}_`?gؾzy{'[4(6GN8~rxOE9uG/'!?42_O3ۂ}>_Л}OFy[yRK~-yQ^|Vۘ2l؁1 btm3ϛHNu8/V')|SHMIdKQgD!%?</$pXlFp&:H ":]&X>?' r oEt?@x ځp?#=Oݗjz~i Q?W_2GN:ֽ;H3}3>}iu6^*qg^A)c#+q>e^vf>7}/o8lX;x$HaC׷cjZ8l?@0#x0;<Ys-A,2x72> rwu8x(2AiŐan#!hAdXD>3};#?(Cϡ .+jU6V#Pܱgx/7-CKpft]3ŏnlkW+2U,"$XXޒd&fjbk%qLxqbJ%b]}:U sb`rGŌƼHAkأ+GҖ thH1;_LHĘőGD Qdi3b8Hy  ࣢ apq)JG״'|PÇġg8F2 z;Bǵh_d/k^5Ak6, >0\|˶Z1dXbG03#D&bkž rxw5Mթv1ؙdHMAnqA\,2 8L=?,B1Jkes;{jծcx,ng+^Ҷ;s3Lֵxl{qp#͟cs<)A ǷP5V8p=E>O>}'2ϡ/Qlz _C3,c~Ǵվ$/A%Hǩ$$'&gvI9xlxK8y yo!h"y$qy~cCGYa/p#&sK oI,1r,8x/\{#DGBlyu~_U}?s|9$5,G`]I:iƟ_OHy=23o %OmI?'>}E%<؝oKo?/{ƛ~4@w aXId2Lޒ)f[zY&VxreϡReل{.ɚǨ7w"!>xn-=C 'О}?e^>-MB[_q)YkNM2{owMQG# |aa!a9uOeeӍvxY]8z̳Թ,[Yu[e\Ieee[{$3>o ,gsY9uueOm 8ׁ޼CGMb/r9sm ; BF!|Yg#`,W  赵Ke^Mbx!KyV8o&F]I>X  ,{WPvi-/$} /?_D}?%c#I Ryw[OwRd蔯|G_%f]K3._ 88$7ϣ-x0y0,f#QJ p`^Bd "#2 8 䃇;!-fmx;~| 9<Ԡ]}btDz 0MP?iϛ//ɇ,ǿ&{ߦh?Ox}oq>u~3Om+~i>GK=mpBm~sb} =/S2~v'X!Vp*z8~8~v+(!XDN2 "x2@Xq]k dl1-_SA$Y22ņ1ɎO?Ͻ}r_σ,ONvϑ_-O`<-OROWg?#z %Γ"|.|7| ߩ}񳽗ľ |,{A#G|I13C؃X6}}HӧO}>NF[V+lfpdㅰ[tc=wwlGF2;%|wzyx81@Fȯyb8K6NAeD@O$@wd^~=='#~?y#Ww'>OIy|r>%࿪d/@O_J)◊GpzJ}_>/x8u=d fxcǩcx3'ԃc|Ye; I {X'lb /X,Al,,[ysmvxH6>Ǎdݝ!``J#s'prĜ%z Ua[(oAEAdyX#1"y=D|/u~Ϫ#ϱx>G'l|fYb>5 >B4J^X~'%XSO?/"PoAq=P{A={Agn- k6,@gŀ&bE$ٳ&nxbyس60yy yxǍ97C{,3'ctASgLHñ,:lXAdy "89,Vԍl:5Lo~Gl_ϝo/QlD=O뫳(!twCS6}>όN X68 fMm9c@87}DМ&xI#dd@E{e@A#_ p K3d8l ;x"}F}9㜁@%!13̓1auaeYgӗ\ּ-o;Kl-)<}mo+o œmm}!-kcu?N]TFRr U2R,`,[x"˨JF$11& 66 8<,0y6mx~exx4WeemxmamXxey lo:Ŝ{wN[{Gn2ǭ<][|g$"I}X !84̂b #7! _:f[+f:,6 838wmxYe6Y//rod6[m,mƥ=FYa$/C%z.Yj/ƽBiKlG6pgG$;/6 ,0,3889!8.aonv77W9mxmf[mmYeeYIq ,s>E>wO.<}*^B}G^^<伢jd%T)bv~fo\we O9A;v8x"||ޠaseFCo!odGrï>G~6m[eYeIeYd[k2[e[e[mL;CB?q!IzjeסxIn+OE-n);[U܃zg֏xǁYx7 :2> F weZljIvnñ0Gjo\2wsx3l׺8!}"~:, F9]>ax6-[l66>;-mm-7,,I&--!"xX#GK}9-Ef|o!OZ(5L+Iލ>폫;^KZLjc2IS^0PIEKk{,KoqyYpgq oQo<p/ F[m9 B8mc$Gǵ mmmʼn%'~!>@;9^p^+ҸOozO(- k`|u; m}!{0{M_4/ܫVڕa%yLN= >zxޖSZλV[mw/wrg;XskruƼe#Qڬ=rpDg)`$mH3׍P/ OX[-ՌK-QXa[B Ű80kj0i!1m-mxmx{ӍL!O'R|>#\gVxXn \Z"{EعY2bNtnXڵm\m6~|Kռ-Agvż9 i+Ư-fM1vKdHFDᵁz ovLh\6[XaчYmxemKa&$;KvJ>?gEhDxe^fgUo%}GiG^㧐ȿ!=$=vGdEf* }8>FK_:k|>wOUKP/+,^ Sжg: 17帚y5WӞ#~~w`A[ӜÄIo'aȱ#|K·slpx3m>7aNH0^u6žwqԜ)uF !6 N7 쿙_> |<>򭺱g݄; /u .,{x+|V,m;<&qD!Kx ;yx>&-2nNpuBip&hp͡%7If[IY{kdM^qg["Ͷ\]A?F['zx4n̙ 7\\"==[ηr(PI}-:՜w+w"aKV uemqP Ҏ"=0:m5w]96 .!}ZPl;{w)wNw6poտFĶ }//׎տQGVFmmav퇍ឡ2a{g8-\xnGx?ntNM޾;GGwcb vt(o\a3z >7XIt.7$nJD.=)2I8".u`Zwt2a$F! YfYN)ԎВk 3l:SX޳_ }"_B}߷>t9QnpBl7-T_A#2NQ'iow?GGg q!eEJrrprxm8|};g;u吣7z~0#݇T'1beL,6,wI8;%ϴDn#=פ "t3JyrI]eZmmnm76CKW1WN J¦OK,F;dBS H 􈧳Ҽx`eF=[9 Fdެzra)˼iY=؝ pMgӼ'ymSd x5`w&tY !bԍ>QPכؗ Kޭ{wT?p,=oGbwQl Zw! BD+pmio8F ؋DSݝ[憭`\`rFzeRtLD"C ! +Q(0^Nu!G#uo\g_Hāʓa]c7/YBZwD@܀Hee],v|@n3/X8r}աuy6lO:B?QӐ>͜ZЏAa3,\Bg>VxmRw7ܸ۱ndX1h@C46mw q%܅pǬ.g"J|f]˾7ز#ڜ[\5{Ìc4ÐٌCKH>Ol<.bF-yr ćwd$2c>tg$x'`lym{'̜dO'Yĸ/ H,9YaID|D{mx^̍xp0ȾYq˳+k <pQ Ϭ3yx2ChA ,{z=!r]YݷOF{ _&қ-MY&!CpXiYdaDHaF@&(Э|k%^3,2g%pw<C߉J'WFH}&퐄's`Id4@E3忼Y݌b1!J=% \u4X{zofI*[[U =:3n^s><{6o@$יm˰bO/_ Cײ,K~ aFI#'. f= I;Dгd3ıMi>+Egw*ٯ'ds9ufg9 v$c9kM!rQpo#hc!>4 Qxb%c&`G4&0ٌ$X+Du}6~.hպWF]Kk62lQ:-Mg{W#!e9~rpuu062alw`NZmj= iEFF /EРFpz͗Y LA zS-m禬h|D,CEEZwfi2ܖ;C<)Yx[[q,yyxBK - -o=dA =7雾8nLZ%:U5-vs][\,IOKh݂]Rf $=gLML"NB*J6Mx@$,dm)/5;[黬o|NsȰLqgջHj; u(l,텾l8f3q` p-H򓏒I3 mI9tKw8_f<nwYYdKf8AHq Y`s;9czDxx`/g%E>jjX)bptYk&{;u6.,f8mŚGNcw}g 2lԟ*]HxwϠ'%rOF<^mN U/B@׹vqm0EܟR8.oVhd>;slm *ى8aL {qxnsd&0.6RǼϼ,Hl QbH3r.zj}#A:`< d[$ι 0Gal:&u|ZVdmm[[[!@yqam| 13ٱBe:&ǩ9q& 1a1ݪeXf;f1Ǥd,ϗ:`Ǿnx>~Fxx:9nR2Ky~9 HAxC) ͌<W:W.\^;k<$܂ yL)PGdX:|0ؖ՞(;Pi&Zyn2|FF=Is풺 Q<0 HС@<:8kBElH%ծֽNʲϣI r^DR)vvpw $ͳÒ99_r8ϣ#쓇,푱&v6 ;1lll$ 4CAء'ˍa"^ zBkpzX.~$5eNp-3#MBx(*" $0R@d9byW=S3^#kt{޿O#_,2ѯ0%{Zz xEze)@Y ǹDD[ 2ݵ`ls<ŘYdd% Nz´u$;%!Í[ ^\8vpo{G^{WO^Ygr$;$ A呍yG`![aKD]|V{vt*ѝ3S YV"bCl A!+mll9K=F6WDQ.FѪ~e4A >,c~ahO#C![/Inb3놀+GXќ>{桜1?f]'~ ţIX錘'[NDb>W KHgXYdO*ۄ B5A)Kdl | W`- 7I`=߮HA- 20ᡱBS8sGy85 md.ȷ͵`>H m>(!o*;<ǗE❳ HX/E:\Hp݁Od@?e(w11 N]x)/Z,h9!,lOƨ },HE..w&4&+b;'}ѰS-aӍ$>a?7.;6W"l׫lgyCy tNQzetCFBZn>?]GM80Kܢ^#'=g /dl 7v=jA7]wTtꋜj|BgxSG a0CϩQzLJ9;1i0$H6i$oM`ف&ś.Ƿ ƞvsY3Ok8 xQwљæx~׬7 ~jl,KM -7:J|>̚z.'f6B뮑vvJyp.`0cXXr4#ݥ/kgWvco?C "% RKfwupd-1.9.16/contrib/qubes/doc/img/heads_selecting_rom.jpg000066400000000000000000002402101460375044200235410ustar00rootroot00000000000000 ICC_PROFILE mntrRGB XYZ $acsp-)=ޯUxBʃ9 descDybXYZbTRC dmdd gXYZ hgTRC lumi |meas $bkpt rXYZ rTRC tech vued wtpt pcprt 7chad ,descsRGB IEC61966-2-1 black scaledXYZ $curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmdesc.IEC 61966-2-1 Default RGB Colour Space - sRGBXYZ bXYZ PmeasXYZ 3XYZ o8sig CRT desc-Reference Viewing Condition in IEC 61966-2-1XYZ -textCopyright International Color Consortium, 2009sf32 D&uJFIFC    ++&.%#%.&D5//5DNB>BN_UU_wqwC    ++&.%#%.&D5//5DNB>BN_UU_wqw" (((PH XQ reH QR`QR eTQ;9:bgY4P( ~X3M[d0FFk`̘3M[d0f0f0g KqdLYDŔ"("p@YH*T Y@%`RX2 ,EQ`XJ kZ]HE$AAul())XFCCCpŘjlim[jѩZѩklpŕ5Cpf0f0d0g*L"I"("( ]@JBRJ"B,P )`R, K,[Z  e( @J QH(f1YSc ` \KqȖPP`@,2,@Q, "mg !ہWEa6CpF3zz 8]vCÝ.Z%ɀɂa P u7DvA ARJJT@(J(XJ@%XJ-D( `@$jPA !eY@6khH%P e* `Y`X) *Q(Őح-D:a:( U B,B[PE*-ƕJ P% X*X` E"@P @@*R(RP, 2PQ"B DTXTEA` !PH *  @, B`(( RTP ("(AAeT BhX JJq*R,)PTv K `!DT(E@YA` !PT("E,J`DA eP)e,XRPe%X( E("("JK(, RT @ BE BKR P) @(PX!eP`P!*(PK@($  BZJ(PQ(XARYP%G\E@XJKBXT X(E%-DPP,@@P*@@ @X EDP%H1#l "aE@  K EK @ ((@(XRYP I@QeRR(%%H*`P("E,bXV%"y@€[ , @TXBPDQ! VE @,$U @" )@UPe(%HR(%(@ (*%*@ `XX"XKBU$((YTPRP(PT %HRP(R@R((%@RPP HP,A K  -P% , @!( @, !(I,  IUe RPPR( A@RYEP)E @ZEi ED,JD)DR @%U%!P`QII `  BEDX E%%`TQ@JADP(D( B@P)(JJTE(DQEEƁ`Qe"2Ĩ BK,*X&P@A @`XQaeEE$QJJ -,J%)E(Q*@R@JK*P)J"l "("((ȱbRXIU%@ EP@DT@J@ b DEV2*T$-TET*EJQ*@X(% QE1P@APPA@i ADE B@QE,$*TXEXEUA`)b`"P,J( cF*("("2XEQLjDQ(V6( "HR(%PU2BBQEiEDQEQT eI`%%`TRE Y(PȔ% YeV,R((E&RTQ(Zcm1"VD@`U1!FB-1d"EŐQq""@eR[bQiaUJ%X""-1QEDX%DI$K*,$"JJ"YB.!aa(XX, ReTUIiQjImFCPID(%TEm"[LY+Ő62Q#!cm1d"Ci!27!cm0 YȸڂImU%X-1ZDPQEF, TbJ$a&R9D+e,"2Qc2XG<0BX `P@RQK/B6'VȾ'OƾIGMO^byoNmr^vMcTIDR"(LmiDZE6bʘV,\bbbʘ\,bcm\mDe%PZ@2.,,3"E. J1QV$XI`TQ&P(%XFCeed1!9B%* U_}l@TYbX%V3836 3t49]R'd)^ O>z0^1*zʞ_&zɞ<-Eޜ<ע<8]uÙ9᥶36(* "ՌŚXʘK*ard.Tr33 d0&c&PfCXK2 DQc2bQsTE TvY Y(iP1REV*1e 2,ŐK̉.3!c3c3(E fXQD,XIU%F*\fxQc2̡&r0 vC\a3\&c[$a2. 1Y(R.sۥk9rX꼃;===;yCʽujnK(dfCF+S$bD),Q(IdIe^ /: P!`K(!@( JO~X,R*"1e*((E)Q$g YBL*LTbc2.38c2Lc3e#ڶZ:xi/ߘs=*ǓVJQE"UEe(I&Jd$bF,Ĕ32̡!Y`~[5/fg,<<}wzfOGO}ϓۯїLN5.I^o<1/N#w#;<u zt/GsfPe fC \&Pd\&x̑\fr0C 1.+ 2ɔ"6PK a"Yf$Gw<v׷x)RRE&CabQKȓ(c2pS>!y-tT籿g-2MjGC@ݯ e56kHfzzxR~ƥ==fίW^vu͓Yn{~~wrթ_nxyu|lwRާf,88a3g 01HeR18a3F3.}}>1q>5#oJK#O}>{/{ľC_4>|'Ώ|Onx_byǩ_FypúqI{gǚ/LGDct]V3`\#6)kTq @I5:y :O7fpaIDXEeLaTc2dGN;YE%B %ZJ(>~\V~Φѭjm)Z)5 11118bdL&i48 g<%{c~|aGO%>||t(>C'I~ϻc9> 9>PWy>!p~_"0K*wzUW.zso8̡&P!YBL2(De YBc1c.+ "^]* QB( @ ,EY^|\`eGO7d6D׻=p=JwZ2$Zko^|lcl/;٧_W.ثT2TbQ2&C0̡֨,,P, bP()bQb{-WwRO~#U|>(>_||S3O;O|G< uxxQy}'=Gk$rsnj4DQEDQDQ!!!5`̚Cc klkf5 mShKp7 47Z&honptَx~o:}e4X"2%፰(E`)\qx)qҥ(-X-ZXEZAVTPotYyx㶦\:eoORt/OQSӾ^I_.צizWͧN|8cuNQ9DM05CkP0FLFLdffMT]Ύ®Bd=W^xx%muy}C_=#6>P<}y|TaY|?Ck?I?Q~[tYsY5yσy׫۾y,((E D (b,1H\qF|Al%AAJU(-)J")UQWWG~8驸jn[ѩZ[)m&1 % $2b2bL65cZ6 H4{@aTrC#pp=^;x'=;pc;/cc: t L7c&٪V1ZvYqY=O3՗錥%`DB,2XIK%F3(cX,cRn)ETX-*BTJЧG|?F"ޏ^9t3S\[}wFj9JCWPu9]C9]C9]UycWPuWR9]C9g\9XtgQy]#9(uWHpuCIֳuY8c#9'XepuuK^iҎgHt##EޗCp܍- ShՎ躦ؚfZfZv[ukx}o/חRbbE,H 2R$1X1\pٮ0RsXEQfI()U*PRPU)A>" /xg~u{8i{ב</9nkNgHugHtә9HtyNgHtgPu[9#9]C9#9#NK9C9gXuI9'd9'd9'd9'd8݃8`㝰㝰㝰㝣؎)^7d8b8`uuG\9])yЎi9L9yD96=RD9ߤѻN}x:}'=-w2eLRńXEe T@EńLr9b 5V*%[-((**K2Bqq V9;9SkۦoM5뱫r;7n{q^q#qqqޱn'#r:rθrα;!;%rN#G#rNr:$yg\9r9]Pu%ptÙiҎi9L^i9gT9gL9L9,47w>>~y>wtj۬$,X@`%F+ fX13ͬH*,,T(BِUYK,wӢCekmjin47ӝг99999999#9#9(uJ#9]CuII8cv98)܎8'x Cw>z_>z>z磉FWaOFtaJem|?iߧt8f>v>z+oO e/zDog|>|>?3+vwq۫>>}c+fk2X%Q%` K@B Dcer )r j[QeQeQeJ ePZR-cSaJPd-P l@RR@ @@@K,KAK,$K ,V6YXBK#3,LV.8 &R\&xL1IpaMx̱Xzw|zڝYL`(@EX@@",YPicrƥT,AlhR*-2REZen_Ok-(@P!@E%DQIDID$IaX1c(F+#1C2(c,Y."Y1<9bIbr&9EXc2Ę匳X$,IX_|=_;w%XEX% D %3,H,{TKC [)A R)*S"?߿]fFB2, jm&᥹Z[hoƆ9#9#gHtgI9]C9r]C9'`u7`㝃8vÊwCwC8gpwÁ88'G <#z0҇gS*24zUsvkK,JXK!,Kc#Vc嘹/LRس"UTRPe(S%EUU&Jƍ=G՛^gAt:G=;צרtӖ+9C[N[9]CgTwHuWPuTuG]9TuG`uGXuWPuWU9CkƆjmdkcPȹ1FWX]( L7Tn4v8cz#Ξ<碏556zC͞_:zPg6zp^<2zc̞'2zc˞<ʞ<ʞ+/Lpoշ+$e@@%XIQ%bI`=kz#;fTj R2ZQTERеKeKJZ Z[R[V[Lr[Sd#!KDd"-1eLYSTDd"ƐQETQ@"("HŔ$c332L1!!%`2,(c3 8c39X̆3(E\AB @ b/oV:z+{sIJ *+%UQTUFS!fBdJUJ[2eZ)RUZRPQe `Y@( ,J$KK,$B@J ((K!(Xe ]֥c~{sE[)E[)Je(ZU KT--QTU!TU- ),\JRU(PQB@Qe eP(J,,,! $@KX%%%Q%QA@%TLha&Qu|w5K{sKfTdQTU%E(e(2 2d*)FR̆Rd)L[e-TAITP  % P @e%K@ ` ƈA@@caqK*,!* K"8>OgYץW)JJ*JPJR¨(Z)JRKC+2-TEYjd([)JR,)@)@T @ %X% V+ ,K , ,"JV,#w7UEJ*2iEQTUeR&RdLVRlPKe-TAlUBJQAADUY@DPI@ ( @ @BKK ,K RIae%Ii%DX QřO>_]o~Bس Y*Ҩ*ТV)lL TZ!lU )l((*, (   K(%BXB ` V%XI`, $(b P"%@J*$c(_۫\{J-UQeE B%ZU)FR,PE VVR!-lX-U*Q@Te` BX* " @%  `!`e%T!q. *Q,`EBQ K%X{9!ϭTY-- 2 AE҅*2Ue2Ee!l(,E  RQ(PYDPD@@cQ!PXB  BRX`EXI@PJb(g>D|xϬQL22eKe)JQJe(,([**ZZ)jRJ,BT`B,(, `,*X% lAQ "X `X -EQ @ -b{3x9:E/=Rjd,-@(*2e)lREUe,([)JTAAAB)T  J@@@%YH" R!P % `XE@EDXBTe__3^vx厀z8J2QTPde(-Qe-)JQfBR-UREB[)lP[(2HJ,Y@`RKA@A,! ,BK@%JJ%(J"#/e% Z(QfBK2AFR[(U--)Kb2R(Q ((P "$Kb@X@ ` XYd(*XK,k/\Ys_OG AJPdl-R)ER-PR-2*RKe-˒RJQ9 RKKPPP@`1AA,!B ,(!Hʲ,fw}y _()eQe*RҙJRZ,l2-+(,նXRJR(-E ( !e @" RX D\P  K,  KD, xl_律q!4Lp -e)jA!eAJQe)FXն a2J\Wq-"ilVl\W[)l[(*R2E[("*P e* * @$,EB,B  ,B+PTb*R* - PT1PTAb PXɍjܿ#z~g>%hY,Teq[4[2J[ 4)l%[qH22+͍2Af)rcn4LeqVW14 [H* * q1bqD* +XFRB IVHLaEC&0b+RH,be$2XAd`APTBEHdFƵfFֈa;X侞"3ǷV:e,(_fr.X$J[ILdƥ+.XdP[*Ɨ,jeqWn#+2+3Ɉ͈ͅ3guP7Su7a7nAk χa鼡꼑<{+/>0wgC_)}c_||>؟e>;9pGb}?O$}3!KO/OKΣqܞ מH'OsV͗ЙGN$U"J"TV (")"(,,12``\&ѭjmFho;Αr:p #z0z<Ͼހ8Ͼ<Ͼ⽘צrބhm74arZq0ZK3Xve;\CҞf)꼘{^6덒5-&#wx>'=vGuGxYzfg11lH*B* * P@P, T B@PXJ]<8/^>~Zㅀ @%DQ nCTl6[`ّ[in[1߯(ٞ喑.z];1Lz4m60KfgV6#9tߣ|o|zwˇ W$U\C=`o`ئ6)Mh#~7kSZqGR"G#̓!BU D(B (R ?,!T*P(PB *TRJA)m!J*TRJ*AAΒI$I$AATRJ  #I$I$I$  H*TAG'I$I$I${Ox  RJ*TR  ݒI$I$I$I'ATRJ*TARI$I$I$I?`*TRJ*TA$I$XI$I$BP(PB *TR !T*B (P'K~VDFqqqqqqqZqZqZq!8E8qTqqqqqƇ^iyƧkqGqU*) GLXDDOx!BiT*QjiZii4! h4MIi5)MjkSZRQJU*) BALv&?oυs3nEα=L}cDNJ9Z+#zʮVdVu m֎[.<: ȭ~hmM1*5ݑeM˥gto؝/I{:y(D$haD'}/7 ~$bF+4FAQP(QkkQCP\M4\Mm22DF1F\D(<ƪ=mρX**)UDDĈDpsLEoli/v=W&3OysTT#bϰ] >=Y䭒 ^!9SLݙ۷ߘx=bz=x׋}ؕQ}}GÑr&5C'vMxXL^\XT8=f,m11|/OEEE0L3q7*e:?3I~6K=>>j~oWܳq*KwC\E\SkܪU]ܯQ#1j]wcS{fhͮGF.j9 mqnNks.=í[d+1?!Q.<͙|.FV}u0OO}s1))+rqG#r1q11]Y(xA{5a;+vkr99+fDnG*(r9cf\ʌ,w'f>U.eRIr~^YŜYK)r˗CbB] ؆aڦ78s7ߐߐߐd9FC<|Σֆ ~ҵSlKu0ATV+U (T(VUw UJ4֊0{5ÁJ !D!PTDj0#A/1|h0o؝/=HaIe|/0;"E7ؾ~H7yTrx_B !T*|aPTB9 jH4VB*G     :K|m7Nu>ha^UKY, G,YRK!bPO7('|'y|b/vN"@~h/쎧?q>SM7NwD0 #  r?a?ǷpAU?q}h>_~C   ;AAAAAAAAAAAAAA;xoW=̞Kт}zHV^;Av=   #AAYwo1`btk{My7uu'9]i_Vs:Ԝ9~cOh=Gi6{OJ{K=ҞOht?9)NoJs:ct'r0q1]NSϰQ;0`b`SMqn7jMmSjTmSim6 b؆6! 64KK 0 0 0 a,%||'|'B!BTRJRUJU*Cq>3>2rB /ٔٔٔ۔ݔݔݔߐߐd9FC㐦76ƛla|e[lD'8?H#?L2X+'~A=J'x'*J*JYŜYŜ]]]]lq6)MlSbئ6 al6 al6 am6Tڦ6MmSjTڦ6MmSjTڦil6 lB] t,<O<<Oy }ԞG J0AAAAAAAAAAAAAAAAAAAADF (dAAAAAAAAAAAAAAAAAAJTTR AAAAAA)'~˨0oظLwC'M /ck=cs̘`                  H      #׹|ͣG|ޔD;*ΧǗlcqÿǰDbkcVAT T  *T                   ;AvGh^M:6Vz;CP{Qɑፎni]'Y     Gx Aw                    `>g_J1<PYrfʃV\W+$vLH                            #@}x``>CoOJ Ğ*TRJ*TRJ*TRJ*TRJ*TRJ*TRJ*TRJ*TRJTRJ*TRJ*AJ*T          wO[ '} x}>*TR *T*AAAAAAAAAAAT R ATRJ*ARJ*TRJ*TRJ*TRJ*TRJ*TRJ*T̃2z_`>Ifg ~&\jl_vafafafZKIi(Jx}V       *TRJ*TRJ*TRJ*TwOi*H*5c5b5c5c5c50SMM54Rj5JjSRkq淚S)L3W1\sW9AAIIII=Y=Y=anX[- gXl͝a6fެRr:Ԝ'9r/)ss^s\sTszg?sG?sG; s'7 sp'3 s0'/ r'+ rp'' r0'# ;ƃaDg?cDQ4+BiFiFiFikakakajajajijiԆ5! HjCRԆQj5FQ֦5MjkqQW!\rFR2꟪~Nbsų[)l_!|yqwR\rŋ!d,K4K0qqd'g韦~,A\Gӧu_.>ҿz_y'?}*;oDԯF o_wgK 7_OWaz|O}>FGY7K?5"#NagQ A'вz_9z() |%[lE[8N2q3>>>>>!0CHi !ZUZUZUZUZUZQQQQֆ5 hkCZԆ5! FIi4MAM qSN;;;;3333+.C8.Se8N&S8yc8ycg8Ysg8YPpz8=ANRpzԉ?0Ώ Cfb A>оFP}& T J*TRJTRJ*TRJ*TRJ*TRJ*TRJ*TRJ(PR *PB (PB (PB (PB (PB (PB (PB (#DB>}2=                               J*T}~X_Ww|-,u>R~XWc_Qe~WwyWTcX,uk:Oo_u~W_?_?_Zt:\t:+/X? JfB.60k 7c779F3c98N3c9x"ʧY$I% BY !d.KM64cM6kM70{ 7444C)RrQ9G)NR9N9N9.9.9/997sͯ6c˼8q.>#>#'oǷǴv #$I$I'C9FSZk5 *TRQJ)E5MjkSYMJj5FQԆ5 hkCZBkBQ T*PBhq18qCt6пxAA) BRUJU(T( (k5fYk5ֆ5BQ BTBJ*TAH #~ǁJd,]]li6چ:5cxxxc)"1#􎹬Ke,r    *TRJ*TRJ*TR JAAAGh(J(JB] t6! lCam6ئiM6MlSbRYK)e,$I':WCYK)e%IRTAAR*AAAAAA{$PI,Xbŋ,\r˗.\r˗.\r˗,Xbʼn,X$I$I$I$I=$"<"EO,>Y !d.؆ƛmC6DV#$k5fYk5$/$I$I$I$I$I$I$%$bĖ,XI$I$I$I$I${OӦsU¨G1$UwfRJRTI'Ɩrbi m5[Mm(Œ*C Y$I$I$I$cɍ.Ϻ-TKEq-?iW0رe,*I$_ָW") BTRTRJB!COOO(J"I$I$/a܋{ڕj.5\j ƪkhD\HDkZ#+^ŏ<(Jbŋ,Xbŋ,I$x' B\U\QƷqƷkqƧL&Qk5 hQ !FiFi !1lSbئ6)u.ԺR+;0w_O#"*{(_|9 LRO$ą!HRUJE(֦5j5ֆ5 hkCZBU T!<zI'}$QlQ\]ĊRT'ٰB) BRUJU*B (PօBQ T!B#2Oy$I$I(Ibŋ,Xbŋ,XbĒJ絻'0AAA*TRJ*TRJ*TRAAAI$I$I$I$bI,XbI$I$IL%hG]r{I=I$I$轤#h(!P1` 0@ApBQ?ZR)Kԥ)JR>4fњ2FH#$du7JR)K\!BẻoW~5|wޮ>/_kBUn>nMěGz4kxv!##&ԛ^ }]V=.L}եޏL b?RB!B!B!B|$>2222222223222Z~'i ֌CwZ-v6}"|{0!B!BqGfccccccov!B! 𞏃~N)YYYYYYYYKB!BjzB!B'}ഥ)JR)JRc^g/?)ZVcB!=~XoG~Ҕ)JR)JR)J].6666666666666666]wޟ-ޟ-嫷wޞ[>[=vFFFFFFbY1f,` 0F#bQ1F(&&&&(bQ1F(bQ1F(bQ1F(bQ1DDDDDDDDDDD"!CČŘ'!5!B!B!=R)JR)D]^)JR)JRֺ{5S##!B_G˿JR؜K܏HB!BB!B-!P1Q` a0@AbB"2?ov2dFB|!B!OJ|”ؓf 03 2̳,OO!B! vL43L4'|/O/ |)x>> ^FUy/ӧ.egCgmS'G+jqFٶqu9NU% $>N8\H摤kP* ^苤DFWHt%ǽ'bIߦ;CrOfDŽf^qOb|}>O׹Ҕ)JSE4hѣF)MEEEEE^6t2dɓ& 0`2Q2)JTRE14_ G/B;!bA{ܬME׏EW֔)JR)JR)JRR)K׏E#######;)JR)JR/K}z/5e{^We{^aFaFa}/ssss3c?rb}s;sw*R+)JRe{~!B!BTeMN>N! TD^yxG^] ?)JRcg>)JR)JR)JRFQiFiFiFiFTTTTTTTTTTTR)JR^"!B!BL2dɓ$!&L2dɓ&L2dɓ&Hd!B2'I_-_-~-kkkkkk]/v;{{{{{{FiFѴmFѳfͳl6ͳl63L++++m6ͳl6ͳl6ͳl6ͳl66ͳ|7#l6ͳ|L#L#O4?sOܯܬܬ*****4#H44hѣHhhҔR)JRJRaFFFe2dɓ&Lr]'QQQi^!B!B!BtˏVk_f ~Ynj_GZw;A/?W/g?ˋl03 Vd:R)JR)JRR)JR)JR_E7123! Ap4P`q"0@BQarR? Ɔaa諑bKWaקjdp`ԸhdHB)BaO547.eB(f㐔 aWgY̤=sSC\4pUp ;Fxf RS*y3SC?l5L3冦xfh1ƍXh{¯ O%ŰƈjvSSTQ1cE^|iGiˡ>.!ckE~F*~ xW'$Ч)˞33TJS<je5S?NvO c?8T R*jƵjvyg3<" g W|3̖9| #e?3? TO)5\>ө1%ҥuv M+QVSآQU+ 'ir̤^GJJ~P2˖b9|8$BH8kǢԸd3U1jUTEC-S|yINž^IN*Jy)8UQ9 -!dX, `,eKU-B5j>{Hp\BPKiܹNw.S:w'NВA&/ >%>+888rB(BSntZb;iسIf%,dZ-k܂FƨjǨD.T\QyKw;ʝN{;w޿gzM7/z%D=4If%;v;NQݪ;GwYbeub55{^հl:Iv'&\.P.]Nw.ӹr˔\rtM7$CNEB Ǡ$Hqq 0 0 0 7qq$H88s9::8rAB(A! "D 7 |q)qqq0 0 0 7/Vkr/y%8WCRjv4ʤ%8Wg>-52%S:*SU]ʻfTeR7ߒSp\Sә).Fš域r棎fhjfJ~)¸/Bh'~xj9$$BHI :8tt zD'Q:*.T]QuK^]˱yv/~ߢ/~ߢ/~^B.]Ki.R\Nw'Nܝ;rTJ&-[9n2n2n2B$H [-EʖT,)aK*Y]*Y]*YRʖQiKj[RڐR EHo88q3dadB(Daaaac::%$RJII)"D$H"D$H"D8b$PB(A! b[B[Mim!m hA hA! $H"iOՅ_x:?f #:> S;(e8888? 8888888H!$H"D$H2dɓ&M 4&КBHI RJT$%I*IRJTHHHHHIINӹ};Nӹ};NS2n2nE7"MMV3{F">88::RJ:RJII)%$RJII)%$RJII)%$RJII)5$ԚRjMI5&ԚRjMI5&ԚRjMI5&ɓ&L"DqG#G#r9C 2 0 0 0kc+Օez-?ZezY^:%ezaz$ue?K՚8888)Cqq~8888.O8?_Ӕ eO{_ Ϩc#y!SJ嚼/8 Zp>/Xkoa[<2ˇ\W.a !1Q0APq@`pс?!P(A0h'R0f!B!B!BU~!'4<B!B JB!B!Bĸ(_uNf|m~B!B!P!B!BPUuh'ZG1cTB!` !PB!BVUU@a"HiGDQB!B!B*TBdOOrd*pBąQP**Buё(]/խ-kQPF1Agw`Dmx4,! D#31c1c c1c2ƉkjP (`=Ex1|Pt1>*QuE@ -+8 )SL1t>0<G1L9'~B@(P8 0Sюx⾠ki":3/] AE-aaHFxqQ!#DN=1rJ.Š&.0Î8 D)333t0? J ~~lXD`.6l6Ţ /QHR)33<3FE;==Qpr ?EB?ȓXGs>^*gƏnS3$u(G_gy{~vO.D,tU'WU^p^ԏT#{ \qR K؈^e5EW_תTנ, ثpe` k]'1"I./@(hK.;gg"zp2wZ,`]V ehaHaHj/L`?əq. t_.=ZUl>ba+.ϫBpWb&~ } 5#K<=T$'9Ûq"SQ@9Dj'E80"}B鑯^D!&LA'f%脒yn̎%9IH'C(ldؔ'T$OCŻQI9;!hGO(pK1)"}О"`X9Ԃ&sN"*`5=dMX]ɔkDgX>~q2DIHw2v.IHd܍2{T׬t8==qt1">b9ND"ЉADA"%2{>KXN0=FM4S()m0e 3(#hA"eк:$GQT1fr!XP/P$}D+^GL\z=p5C, ^G8%N !7$=59fUD5%;{G$$Ď \xA@S<܌ԧ=71A$g1$Ikj=3.ܿ8B Fp/*iLC"7dJ tCԁ4w3,͏ikX֡3JfX&#F) [1'ZaǮDs0rPdnTȔ“2FLI'VԚ<1.l>ňvEL&gJGM4AiWӭG3R璈.dgRԄB3$T[V2 OLh<@}2t︌c= *ywQ!dm&TDrAg0Erendz|kuHcG6kxM<Ƅ*3>Dd.Lb!9?0D(HLseE"25tB"k62g( B7%Cd/O֙Sۗ:sBn&l! Qh<*sD5dYD\hX;@,KbpneLP!B ! ! O,ȉLh&!BJĸ#TЉ `dS? :FHgfC\ $%1.$N3Pa*5,B,UYLj3%ّ)_-h[]􉸹OOK9ዯt}0Xǔ;_'o\TuD烯b\σRIc-$䗦~^o;pmnM.Fİ.Arڣn e3havn@J(81Dt==΄iB!B*!B!B!pP!Bl&Ţв<xĞ,&:Kt_&r&/ض,Kj5z+Sܗe~\x_t[{22lqUB!B!B! ***!B!B!B!B!B!B#M]EȲ- 2ܱ-2Գ-KԑAAI9ƺ!'s^Fj"K!PrD2JTTB!B!B!B!B!B!B!Fp $ke&t s$)Dlr1"SL") "PZj4h*/LF$sH: !B! (B!B!BX @+*zh@=#D$"ndd\e@噤6H;RS3.IG9Оr]ԎryiD 9㭁kK!De 9jΥ1bԄ,B V!B!B/J1pbc QD32Qt#̓JE`'1H4ɋqA,)L&b |PEyM$Q"put(I72LM"`&4 ""UTB!QB*QbUXШ!B!B!F4`Ģ4 1T"&`T!B"!TF{3;C LsWUTB!Bj-5]\ 'B#(!BYT!PB!B!B.!Bj L=ڝ̇OP!B!T,TB*B. CIr9'4rD-On.jƘ@QVx]"FğlI $ğJpNpGZKlUrZ>,-BXIs ğT8cÏ<`Mv]RE#_R} zpAg"#6Ӳ6;Gh  m_Rp[ܖdüw](b-lefZa;}mkcN.e誯KAw.;oGRB bȲ-8 3f?;"ܚp$$UBOC4I 233333333=;㼺]/4ׅ.*%p_/PhXaa;Nð;M\Kg|w;tmo.K|_/r;Emm`v<#kYMhY, oo'I%0$etN}BİX5菥oߓ@B!B!BS>.|X7C84@z35κ"?7B!TBIwbyG񚢣3M1B%L_/!B!B"`2+ h$F4A23TN33j'2n܃A GJO'ߓI$yi<ԞqbY8c(|5 _,>``N_?<1xؑ?O:/ R[z*p^83z8d#͚GB$U\~gzktDs 27Yi4(K7DCDr rjJIPBtVH\M5F-~K/h>aw}.Kp./.ˢ/. 乡/K...>>_#vgx~_>Zv>Xv9ӀRO᫗ A%13Ht).H&cq7IwHHHHG==|%s\HG<>;_V`ZČBґjYefZ%k}K sS20Hjh^&zX3i;}ov`]aWфb)Dt=2u葁zWN NƟ}'v`vp'avivij{hKH(B\!{ 0\??.=b5EBPT!B!B!B 4! !B!B!B!B! 娓t8ƳG|s#YmB0/rAS HVdp 'O f ~WDxēSXfq#\k# ~e:,r?bF'H~Zh3G~n|{Dp{}5-Ӓ?+A55vUeʱH(׍B~G?O)&ts xќ7., "г-Գ,4&ks tC:c1MMMwr\.P_.Kx\.'L|_/ .IzK\䗤%.~KT*]8Huzqp^/Em \/ p^.V.a퓈)o˥| | 7p]t]/h~Kp].xqg{Ȯw. ==Y,Ke`MJKUqqrgNܵ\.RaXFlY, vavc죶8W_~|^. t\.WKp].˥x6n9m;1юc:A ~q1ьcLR #n5N!B!F 25Tg1c1c1c1c1c1c1c1c7j1c1ttc1c1'W2?b/¯Ү- !' 2L%! E"# #*eW4h PR)lZ- edKPza qJlY3,%v FF=wP`\Mf$FQ(2„)6l0+\( !8 D.ŊZ, `Dig .ˆv%ysm=G!2&FF[p pw'Ή.엠\;j %It_.Ѕ")0 0 080pAQADQEQDP((PB2jeF Ҿ_.\)EJ, 8y]/x}}:HF_.r&L=.>>q~"-uJTAA* P!P!Pr!^1p PAEAAEA(N݇aa}qqqq Dc1я139%@25\dz$B+t].U)& 68i;348AAEQEkaq1c1cGƞl/Sr) N whk$ De3:9#WezjȌI *Pw88 9r9G#G#;EA2ϙ]`Js2T d fhCN7E@â6n\\zğL'.^'6W$Arh≞21Ha,Em㺂h ah[-Blf$Ń1Hre###!.5dT D8fmISM0q$̘Yjf4!k*!QB2s!A X-RL^qqqǠè[K%`%-`X"lɽpk*V-$0Np\&#$!n<%E@"T"vg#4ȌIfOa!qr1cF1 c1G&fffblhY>q&b+%lY- i'avlecAj9HH:DyՆ&|8ɕf$E̾9\tl1c1c1c "p 8 &+Eբ @QcGQp D&$DQi( v ?{iaǬ4Pr9aIR\Y222FCc1c1R)E# 0Î8? (!AZ (B"E*1LaJn%u/]c31Oh`ER!.ftpUP!BPP (  @P(P :Cjaa$̒2fFc>>p(d49dFW$.4EXuh {7:˾}Q`aoUn08|SM  @H >3渣8/f ϖmE0M0N5sSu[}%^M^B,kE=U,p)4-0)Kƶ1 ,0;}޺,sn51-8L"l)쵥^ %# k!kA^AF|\a8@AeqTM}䢖(}$y0}ZA:Ѝ6^*dI<yh奄 x D j˄u~ܼw# vlY08 p}7>ZH,ѤDa/8@qAFۍ;}l<4myMGh(!D׼ {pʃ`x-9= 1M4?AXQM047i2:-K=5i4}73߻O;lCh!_삌LvPmpicR(`ŚAE}^O2t#" (~dZF8'a~Ǽwa=<#2K{ZJ  w <0KOM?;5sHD;)SmcO cM(TP )" K8OmKyDS'Xt׌=4Ss=Z׌,>ޠ<n 1h0q<38mQ-~8SmKO,0˫ۭ9% >8>[ﶈ2@qn C^QD^]de6A=yN<<˭Sd@  * _p<)EĜ .#[}aƎ{CkNS w2I 4TG,IEai 0 0LO^<ǯ5d1H _yRI(4idN()g]4 ׁV!a 9<l|7(GD O[ =#\`>t*=%]hXE>ӵF2|C,7vC{BwY}|B8dNkjՠ8ꁆXAlt;Pr/  7 m"-Kᒠ av5R==l7r ԗ4pL<q -88o')YGmD*_ΰV<(7j<<A 8,E4sZ◬~O0箲Ru}pCK oL[|t8cnIM0  0-<,ǢA\(e5p Mu0G:qb߷,$xɨD363 m48T Պ8P̶y ?&]QN<y:ϭUv R:ɿ1BkQ L{ ]?4i4G9ʵK48qoVa@4A?%Tӑ؀oR(z SHB0ú;]`]S |]xF`,m|8xy ,BW>h߲(AGxS?qA0 "-g40zb 9#1ֶ4yQm3V7C,X8K< 002ÊL BK{MlY6 u,o0𧡆pBn1XQ}9X+[Aq}A4C,1 9`RUK 8.6hi{f47NfX ]QjWKUE^ew ! A<Y:0_MY>WZ%w uwE՗y 3G8-@!4f(#z yhMr q?&|B\䃈,sᰵ]dq]a]4Pz/(! QK%akʨvv낻j$^7p$Ex+q?4#G Ey${ <0*D:>` Cqhаe,uz׿),S (C]Eˌ0KOfu2l7_?Y-4O{D]EGAݑN$i}}ˆm"bcBSqq[In_c L0ADC '+cN:(?XwC\F7ϑ%dL)$sQ{cMa=yyX ?<0y|!xj5$ b 1_6;2_DRY'WݙECκK,7Y֓]A 0}粽0 *ĝbl`3%%4iQa rQu6eDz>".PU]Au{<0Gx) 8&nwVU8Ӕ1@MnQmě!6Q˥E6{ ZQ@tA_0A,xK;$۩TAE0B񜟣G@!f{OK,1E5P{/YZ:#.8lEQGAm Av3"x`|a]qUDC pF0[QU75ZmU}O3}M yKI|8e77{D3:٠CM|KAUTq}RaIW|T8 $llG=۫p}auBcGq% u=9/ݷl;W1U,V#Nӿ:| pK.k( +i +Q4IcijaEZu}g}}}K̶l}-5ϽA?#Z-<9, 4AAG]aIQG}=#ްݼb>H,0l _k[R4IEAAAg;d^WTUKåSu+VVQ^Ϧj,)JR]ۋ-AI$پdTk~b{D7iuSв z\^/,ۄ= qn&4Au| Rqq.#,DXP}ػΒJ+A!S$$:9|d%BH/R^PȫAoHi3#`CKjh[kR.`.R箊Y\K5&iLWE A9`(^XWN2{ѓ] f6MT7DVKpMd~ )!JEEGBt:AoEb(22=\VVVVVVW (Ez?'>ODwmnBDhjl4IQQsg aqq2Jq^hA4(H+cw:A50/D6 $}mM3Ke.iNŞ!B(,*QEFFFFFFFFB aņ&TTt**`BؾEEEEGCЈDDDDDA$I:!B!.#. xƙu:JΥegRe}>b(} 8yC=Z.鎘"""""    B!BcVJR)JR)JR)v@+HVR)JR)JRЈcHO.)JRJR)JR.n)JR)J\)JR)J]3ޛc++:uu:uuڥ)YJR(|4~ bj&! uw5KfݺR=3EI$HsbQ.wdm]lCpmGB22222!FFB222222d~M)JQ* JR# TRAF02AQEDAEQt]@T4w| R)KɄ!Bfnu.VVVVV^j^ O;<G#|"e/>϶#|DDD[Sd'EXx>#|>G#||"/D^6.)JR)u]R\әJ]͛D!5O­JRvl_ V'dddddeh*,+@I^ &#! &lAADDDDDDt:bQШ I(]= 0:REEE)JR@,(e++!tإetRҜdf"gKå}Lp2pRRDI΄!CIm۪k)ʖLB22`FQEVxAD$BB!5  """"": R)JRe.ivWٷ% 0a!1@QqP`Ap?:LfgffffffffxffffffffgMHl 9)}DlrG|;|FFBxt)r8A G_WdG"#b#|WMހ&frrsD|gi _xi? g~QwH9y|:NpGh#DGu3? ;DGIpDDDrrDppGI0_2"#888#"""#~q#""""8"""#DDpp :H""9""8;OΣGA""""""#:"##DDt:hHH"""#""9"?e5^ xԾccyDDDrw_31L!B8B'I<Ӽk;;3^ky:,Շ"8#>Wg;[okFٶrD-3#xDEk|c3=/Qz#pȈ>!q?V%ް99K|Cb׳/}"8ƹޏޏ ڣo-yw96^Es~[ݽ2Zl2^g<}~)3g𽎏KӠf^ xX5- ÀޥB׺s[nIw/FBLjmg9R؈e)KB1cǪ9n3/ɜ33333?22gh*1/wK_r>ΤDt{yٵL>Ÿy2Őv1՘mYf&f~Clxw󤈋ۦY٦fz[^ c}=%j'Rсk$T}me䵃fzii^ffffgfouǫ{pvɞAB18yoQcB a#Lߟ9k{^U^ ^G6666xfggffvvge"fm{s~Y{>)3ܜӣ􌌌Ϭex/o 7.}y>ߥs X/8Sfg>^>!~3ߍz=o^=gggoYَc)p)JR[39;Op=~ CaXltl""""">:}4j=kzNDDDDDDDDDDDDDDDDpD[Eg=>=fffffffffffffffffgӰDGI`sLfffffxffgfffg[odvzwH"""#cb6666666667zļKħvdzx>υ_fgeQrO-g߆@coc1cǽy/%众Ky5ך^ky/$>B!;aoɑ?0>}CP!9sPfli>ffffffxffvfffx<ᙙqw5O?{s0'^I#w]?#cO`">a׷L{49ϹϹϸ}}~I87؈>Ox])m弼//;߭G8+Wy-y/&MWo-弗KyIyך/5漛/}"?Gϸ}Ky/%!}_?y/-W333993399GNsUQ众Lvv)KJRW_"1[9{de^K¸c1c}FrёGd6x_~zWF_FX3#OIL/,DqL: ѻ!x|3Et!#<)vvggg~~[ރ#ddd|Gə")NvvwGDDDDdddp"2####8"#&h߻*! 1AQa0q@Pѱ?gט"_ K/ss:LYnBKs]"~0o3}89$g8{p[ax aB7e,gC=X@N18$% < <#duYH:^6mFmlU!26"w- 1ܱx/x<`k[=&easb8syΣy<mLJ X巤œ^"-T˝=A'DLtoхlu0YgӬG{,$l,I,,I$d ϰINe űs,2@6DhY;e =Ýϑx㭰z H/:O2ԋ|oSYmlPo;!NfpeYaxDZcĒH]`,n2.{5;½ d.Pӌ$1$K qXYcds[=}o3dLgܴ|xl1j6f [نa2!#3&^9)c+۞LucXy 8wy ։d1}%f$0#D`,K^0ad6*5eLXOIUv6kmmz|o3o<3o<81ys9mA gbݍxL8[̽-|ϟ oFH^ -ec5&6^a%yb8u%K<''O'AMteG D_v0l'Ĉ_im8X'MRj#b5[Kl0o m@[l&`RG$He v:3BI#Ig_Еw%ly|xY:;+<$Iعy܅V;ǛQ\[."Űpk[{g i".d6 :/o9bL5,H<{L>/ a'F9Xzp<ޮXM1M|a'HۅoOMV%1#nkH|&@Jd[ E@|}eBXd7& ]i9jmgh$ݜ~ 0Jy' 9{gRӿkɗCiVO1ky-Zm@ γ6ݻȇ0v$3 |"c <^y?\%-KKy滽m8 klme 'φJ{)_I<&kfa2xVLo <˗&1o6kVۇjcffE6d@,"l i'1ždn29x6,B`NUmv,b$%Xr<x$mpy=wm׋8o7s|6Kļ^aŷֆx"~RaؑB.c!)SҵHymm Ѯ;vZ\r -61;m[$rdƷ#;"Kss'|i/^j|#) 99r؄Z* `$}pMHl'sp 0`Z0|O`X2UW<I<$ G/6>npx>wH~{/%w&3NJ%DoʹCkxmHMxW6x63 9oo^m'fax{a /La!c5#4"͉rd{7OHԊv|y6[)(=f`[Me!_f-B*pYp}Rc"׌L}<}Ye[Nz@Jsⲁg[fe{m|gsxsC|&e8孼{samFy/o7͵ym-0E8H[/%'I.k͙~,ׂx5% )3!~E,A;Xl&)Ĩ7|cqdҒ0̣oM6玲-Wy}dtxշomEyeb[\2lsX@wyy ^8<my-m5= K >b?7842~V}q/ OʭΖdx5/QL`c쌼]Lyc-^)cmΞW], =Jx-9{_'͆0Ng:anN/@Ǥxg=>9zud/@+i[mǛͬÛ7C?8yfQ_.LVx$_L#1_8yL2^ox[|Dm3z6|$oKx[ o6[k3ewmy'GN z//K:}s$kd<Ʃ<}967'aCmӼޝ(Ng|Co;m5KXJgVמ,y`81 e玛?e8ǣ'e:_pܱ'y''] ň|/-' '6 ߂ÿӞ }۞=:ogϞhmӾmᷤ9omͲsͶ w lfSÄZGZXY;ibxNAn8mky7 #'gñ_6 Ӈ> 0i:r[03y.l6O6rн8N,k gtN|6Ikĝl2;';ǡSl>m~+x>d2zu{&c}xw =^%p:_%XL[086p/+Y %0bH0o:LN_9xymy[xĵlc{>K64`CSMF6}%1|u9yo="xC2 8^#㙑l 0l/5$xqg䕓9k,IwK7 ޓ<2yx/6Ae'vS^{|9O>j3bsd V>8'ž,i'69Dq<8;Bs|e`r؃͓g|;l 1<%x̦߶ͱ2H)Ŗ;͎ym൛9}=[mKy6}[yyd^-c pd9p9YЌ3xp8wNOV=",cxK Eoy޼,,!g%fm^2-:z wX2s=6}㏥} ݒgsҼ`؟Fឃ8sy}z9x^-6^v=8YܳeyN^|yY捏,oZǂ8yg6Ø- ^;;xYg|F/^Ia[ͦB'm[ܝ͒'~2(F,RfǣY!68Ϡo Ǜyx^/{ q{ϣlslpd^l!XDo2ds 9Lk3ŜH=#,r89<OH<7pOFY)LoմYXwy ɗy<ޓw׮^vHI3㭼{0[^oC3=Yڞ: Nd/,AYT[aa񒒲y8gOWS<ޛyzcz@pw9p#ьm@>;YA%#" u7o1o6̵ :KZl7FIeP0dvVGo!6WxRlolos^>f>2m8Im̼b>lNz1w7稍pp;3-_9́6w"G7l qxKkwg/M:Poh-d2^᷎|m淋%Gw'o>l|^vyA>=<=/6܃mIYQw=9Ų8zן$]L7u|=$XfX%o2YߝNMg%9H&$$8XlaxYÄ'sd=1|e'΄d#=eYVFzM>p=6i&!Ylwk!3#%a̼ ؘ}ߛ|9ff h6I;%Y%8XeYys<9d 8 =9ug6Qǒ>_KOfޖƼ݂Bހ#66Xi%ẍ́Yy;?q gc;>63x̑[6Kc"̜+,^w8ә<=$No0=eK8YY֤іAe,d,0 ,ผՖYYdK,2ef=69g,06@yK, $XY'1$=|_6N E$zxO@|21Dp q$<[tcİy'83Ỷ׆x,t]aY|pg3̱Hm;&@IeXe1Րa%d w,#`` l<,,cF YN`Y1%_ r-Vg2t&aOlY,gH ̥y6XЁ^|ڰ> Hg^,yI 'x'1Fy&HeA3dcd Dl,9LlA$ &AeYdXI #ad l $5ȲO7ȵ'3{dpg{y]p3նlIe7-ӘE^,p6I% 1IK|[{xI% `6Xl O9̔N+a'䜎|0pmx<ÙѼ29&NgAe`8s2' rl/0YeÀYACČIYIl HYcdA%,AY,  8 , ,l!l66OdO{,BIHYagɟ A9U>̞%F݌"rx񏔔|2}_M76=m?sxxxj4Y,9Yg3edApAdArH,qlcNeIӇ,I $x##Y8 13d!dr!2s͇MȼpNexݶ'B` mmm6 -mmagY_q ;3}&~#fMOЛ?A0!b6m:v}~ }i}? VTLLH:LɂEA&^My"0YԋY!x0K,V'K/ٞ0p><%Yf9e',l}g>W}=$%8sVz,Rl!`ŞfN;d5y/nxmeerXmveylNHH}D'8p{SpC4C>,> şI[61~n}F>~Wɋ,FU2x$xl9'ld̲H gə;l͞;aqİק9:^=!>p H -,dI > BK$eH$ 0H,HY%#cg̖I% fY$A2q &g'$I$$,I$I9Ib,,'l, ,lw8F󳳹j|007=@2ÉdX$0l8lClIxXZp|7_a8Yg3O:$z,,,N%Yf$$Xgr,xPoL|쉳ǿY\'eIdIVMyYc%YXs;YfaωaF`W9|Y'N2w,8|2s$e焼zÞyq,8Ew NgR$q $Fső̎_bK$1jfJuĆ-x6a 9<1Űa+!{^C>l=Tk7МD&x2IIS׋9d3IY,K,I,&I$M%If$,g8u`싕b\,ԇ?Y`)]-C6/0ԯۅ. iX3?GlQm L{ކcNEr<]m)ldT j |y'Q̲`l̖IԲ$5$BKIg2Nĵ3Ք=C c H g$d:',Ic#$&F3'̟'#cOoO ő,̓Xp!R1bqhj.DL /[2J!|M}ncm Ir|&VK`򟉟$h7k>$G7mf72^OwD!V/;x^0Kxkk5K7J}=_fy?3|lm_MuGl1L|݄l;f ϹgTor||vDNR݇lf/9$XXj+݌b03D>u|_ na&~ cG <-5>O1 0" qC>d1'˷7ʝ/gh-e^?VxE` = ^Z/"Ȫ*,i1gἃN|%#ϰ $68è1ۥhϑyS}sb6Ky}HpR'_MW`xdRY9% 5DI,)8FK$$Y $I1,Y!e6FēqI&mٟylkl 8s,6i6=%I g `B|@FsuI1)eRS~7wǶ}7o0r|c?\&#>T@q')Kk1qUEd??FĻD~R{H}d{>RyfLw5c^LclbqId䳈uCd xt/d2YYO] &HdH >==/1cAyA6`"Ic < }D_g'8'斾e~Hz~O5GF5ԅxK #JPL0<_͟{/+ &__G}siŲnj_|.y/~+O?Y~Cc{/pI'6ls=^j,,2>Xc՗h)7'%䷼5ѬhW߫/c}SR־ܬ79)N? q/zٙzkn[x|c!;)m%-G^x&%I#cd 8@q&Iudp<3,Kez|G =!†YyϣdYcdYeY̲,,,8,z=`'M}-;oK辊\.ǂO՟_~os?A7?Oꟛ涧' Dt'Q6 ИCMf o*/&220ˉ 6YeYeY,,^nje,leYe3~I9٭c1&LLDL#6?0\l9ă8z6I8ױ %o-, 3FGJ}o',~_8{_lо+<R~=0,oe}<@'\O o,7ߗ~ ?e}iixgsמ ,,,,X=@вyY!sŖ'6k:ldd@ 8fY!y {f =xᣅŇ IQAe_qu\|ggl??/?tb|>yv2ͯcD?aX}?xg|p1>) [aiFJ~H} b>3zo}ھܾ}7~Pm=ExG}ЏqUD|3k0{U̍Aʡ8C\s"tg^%Fo CQU$y^l6p63^ \0`>QMQ}#_E C{Bj,9O|k!_G|,k޿6v_}oy#G3s\5=&DWT@Q,rXϺWyww$絟r̟~Xb͋bŋ,سfL6lؑ&Dl1vwդvZ7ۅ,VBj|n]d*G!93exC1 Ǐ26l,$=v3ї ɬAbA;d Adp atO뛨Ól<^]||qߟG HGzGȟ0̱c͋Vb,cdX`&y)vUrU\RJjիv۷kxչ\ڷʦ33^L?r#!!㯩O6ɼ̖l>&KD샚X #8<}Yq YA%O񜌂'g i4I^3tX|kdqėǎ'MaxgFdCؼς,yv͂cxp"Ȏ|^o?$`">qB|9;s-b/(!D[xH=/D=CŇg1菠L{GdI~31ŏ,}X,}HdX#,|,ɓ,س,X:c3&Lגd)< =M6I^ɂl$y^sb 6g,f6,Xbŋ,m6E,68LO x>\3^zCRo**T[N3viYnku.*b*W |0VqL5-%$$d;xX;ɑd,<oCfюaDu,2GGH;y lzy?_ywwovrrd)vmnVʵVڕk_%v*\ڵkUrڵ*\ڕ?U+%OnGĿs8 RKC6_3})>Yg,du'$vyNy=Zlp,sAxn^ c,2$>8|62({@1?P8 ${̛6~zk,X`#ŋV8ŎbǠc,سg,Yg9?7O goo{gOj~dr&5&F{O B&OdOF>#u 'ғ<,'e0 gMYb/;e#zAy"_[CB_hS +QED>?쾯>/ }?C- /gɓ1ŋ ,X3llXbś,ٳb͝x͛=&͛6$Xbď@͛2}?\L>.j3dy5Vfen>Ì!> ,,"cx Yc<8OG=o:+2JO3ym3rx/>Ny2" <-s0/e?as|_8>?__Xf#?>>j}/o'w^)OS__G~_NYow,on/~P_k#a}$~gwgwg777wwv,`NOK?N߿qO~~7~ܔ&TyO~$.O[ 7䟡?R~~+w? 䯳I?3:yps$jA}џ\7GLٟͿL +y&[5Bo8unqxy^%&6-#83s9@Az'>{og2O,;gYd2N$lݙ6I$HI)9$BIYqI`,tAm KflySݞ+&ZGlxzd3 b2B #b:n&1cx&Fx3z2lj!i)ŖRs&x̤Kd6ehX?C&3lKl6m8c.{z$wŲoc!Fk^c6,yэX"8@ dȊ /-?o?<>8_~(}h~ ?6?6Nwu~_ӈ,TQWc=BN`x/E__~cR>ꫫ)_X' ? $S&}<4?Oͅf^l_0hUf_kd<,93<^ŵk+Y`b1b#pGdnp oG/fz9_\ע7>=\81M`e}Te޷lzN\~~~ O$?H+?#ĿDJW߼ݿ8KO>Il=vӋR[oRYmg72ݐTAǙ1"*,/Ѓ, 0Li.& ~aÎe 0w2~<}C[kջv}S6l}X?Xk03?WO'\?7쟡?_@~/>'?\<0×蕏W{s9}_?)󒾙_RZ[?D{Z*W}K|K2˕)K/ox=O3b3̞c;aNJ2K !ȶ p" 9{,`jb !'9e!Fs?R~}_\G'0gNj0~ lAeX0lq@|g Ig LyXYg՟6>g?V"wcfb D}X'O?R՗$OFY$G11cHk"?D}?B>:a[ey'[z09$ Ղ6%>◎)oV[6wx~3'&2Η НV,[A#cy8s <@@ `lmA8^=#8dt=YÇ?z[C6z} {R<}Ѽs>Y,Gy|o+`07d ǒms,2xo x fl}99d̞/;'O&q̴ZgAa8@E%ydAYApAA pz88p'ӑ?goClm>[N&z3!|>,yK|kk5f  0A#"1B67@@/0Y,[e.wӸ3ŗme׌ϥ$9=I9wf ŜA%#260e)b#Cd,Aldp88YDg@2"SCz{l}+=f2[VmK6r|'glltCNCX0^V8p:GLa AdCz~&=yoIIǽٖ}/xz,3SҼo2/,,9?bOFp`9{x'%1Y!"` ;AlAcþR8{p=} śm<ݗ7,Ghy!Ӊ9`&Ŗ^oB3r$NYed+Զ:=9v2Y8g@C`6,A' 8<8Dz}tNoоeݾzI#=gV JY)o=)d|E9lp]o1fsWaxp8sHcv#gмSͼ_K/}myշ7cĵ{s,-V7qK@48Lg[o8&#^9FDA͈h"8DC?=oeͲ-^-[Lx^K}9i s,Ķ5xyd&cq#v8t#Ds 6xݶV[x&s,Dx)/ /vZ~Nn%v6vX瘋öA <"cDt"! "l1!t?oemYe^-^me|l<^Wŧ4m_a 6 [oaa-&ame[fm̲em͖^|["eVl^m>ާ%-N'H8{YZ?7͏ S~9V-|*};<$䄸8$-"xmlJ{- aK[^lCOí - mammlY ?5-|_G;fw!~ܿy?[o勵V/~~Kil03o~Û\߶߮~;῾|\}W?ީl}E+h_kgK^Ӎ9!<1,ɱfM0&xX xxyW c|v;&nnծdRK V,<|PVjky.mx3d>6|CKe{>Ǹ{Ck7e}{=vO;>g qw D&'#Yxe-7ZWa$Ǡ0N6iӻwϣeWn]s4ZkZRU.YYVjԵ5K[;m^6WcagyolӲN{Lީ[з-i/yk)s׮$smm3>0mgfl33ys_3xz63go<[ӥC7FwѶwK9.sp,le%rao6N99^|~ Ll lB/;oH_ׇǚ-3NyC/ogw㏡m}[đwd>sx67k0y睝!Kuf-ks7-_$ay-Np猋FXǣ[xxNFwyǹ28fycׯ4No yY}āl}>m}}xeن ϣ6 8m7azNmߛweyȴ#[eB` p:xվKտEg3YezN1=oy^='>K<^y{Eo^ 2X}[׹=Ymg l)ў77aͼJ>2xͳ n[[P;t/I5@y6xC)7 @&g/3kǛk%&#' 9ܼ[ώ瞏|Cn8$p1'NzizBVzFI=~/;Ӈ82|0qB<%ocbK,K̂޲LfyxF%z$yUɼ[ -Iv%mxG w^o{gBDF,ך9ݜoxysxm{mNr66wovxe%DŽs ݼy!kx;/vЇ|[{mV'^zycӤ{Oo+|wyr=9;ѼV9yo7&xzFU1qgl&}',r,,l[͎{Y cf>?/Kټ0Ȑ̶ HBFA0oz<,q;r7z|7 1|gyzll[~/ɿ6<ſU+ZxiE0!8&vOr7)!ODi-T# `bo'@ߛ_1>@9 5˦iY5w9cxydy|X|g9/3mIfvY8zl8Y#  }WU=xln1LGGBƢz HF9 L_m4<'aCfGY!# y> bI`ZE&BCRvU>D yt׼ù"5!u~/ܓ'Kf{)nc4wRϾ[ߏl,ƑkgNw6;cbQka}6Dx- Ǚ3e vA ɣ~Q^eh:Yx^Hdy i&ȒN@7p, iv{ 9%/,~Hc1OYdcI'IM2LޘZ!Z=|?#sS+hY畕dZZXO O-ۿTVv,0o0`6!9/2aijElY,,lp E!;Ly ,D<ٱaHc@baXlXXHF22eacdA_,Kx($2>"snsGS1y6P,#V5t };mxH;yOHlFH$d,<,xIKw0N5fMv۷m[3[R_{y0A&E|O(gYx /N /|p^O2]21[b,ɟdU 0N!i*QG K2yo!Io(Gμ" #ò9ep /FgrGg\ I` r,.{5}ج$ 7o+c6Ig0 -!hD l,y۞{rիPZ1YlΏ?'o>^-y<<!lap4/#O""7MQ:xԍ^s<@!}q ![(cQ-}C}?%7sx7}Q/#Զ,̷8 g{<<=>, ܧy b6~;#!WDeHd@H $x7{4fwupd-1.9.16/contrib/qubes/doc/img/heads_success.jpg000066400000000000000000002172211460375044200223650ustar00rootroot00000000000000 ICC_PROFILE mntrRGB XYZ $acsp-)=ޯUxBʃ9 descDybXYZbTRC dmdd gXYZ hgTRC lumi |meas $bkpt rXYZ rTRC tech vued wtpt pcprt 7chad ,descsRGB IEC61966-2-1 black scaledXYZ $curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmdesc.IEC 61966-2-1 Default RGB Colour Space - sRGBXYZ bXYZ PmeasXYZ 3XYZ o8sig CRT desc-Reference Viewing Condition in IEC 61966-2-1XYZ -textCopyright International Color Consortium, 2009sf32 D&uJFIFC    ++&.%#%.&D5//5DNB>BN_UU_wqwC    ++&.%#%.&D5//5DNB>BN_UU_wqw" ((%h(( XX E ( EPT  `,Y PPQ(P) P @Ae`eAPTAPTAPTTd[FdiidinF[APYiAPTAPP(() K!PP,(!PT `,*TDP`P@YDXP%W+xÅ8죬쎫:Cê:P;#îp9]w`u݉q p q9KnBq"᱆ᖆZQAPTAdiF`mئ`mqGcNG9`ndidiAP[@ARP@(eXP@@%B!(Q@P,, B K(J( $B4362ÐqnBp p .atp9p9p p qheDP!PTdijmr^!/+"rx+!!*16v#L^RP@( , @B ,   (JX(P*  R( $C- 0cp p9u8;#;C#;DC쎴^6* ,, `B@*YH X*TXJB@*PPX( P!H"XJe% ,,J #lB,* @ Q BB,@T* X*P@@VP$ajPh͔5!@H(`PJ% (B ,`"PH,B!@ 5" %X @P @((@%(HR( ,,!(@"% ,,B, b  ` @(eP P(`RPePbP( K RB@BP@ "PYH@* T( %UR(*"%,HHbR(@* *  K@R( `P"%PIeP XX, B (%P,* #l) `  BV ((%B(J%@eY@PX""I@,` % %", ,,@A,7(` `@P H@D,K J",PPPHPRPUbHEEP*%HR%!H,A(A, ԰H %,,(K(("(( %VP((K @([HP((,JKK*("[ XP""RRJ  ,((R(((%(E!HY@ (TJ@R(H(,l  P,@P M@(KR(""(("(% PR("@P%"T)EЊ"(UXJ@"J"(BMePذBPR,JJ"")"*"((͢("X) X(H(%"@(T`""(ʀ,D*(!R@( ED) QBԔ QjEDQQ@JmeEDQDjADQ-TQ( EDZfEDQKDQE,*PTJPQ@E D@%%F@ΥIDQ%pʼnP%bJI`@"BJ(TH"(H%R("--2B-2ЋI4#C- 4#C- ""B(X("U%RjjYT`*,%4$"ʓP$,"(ͰP:P@PB*% @PH((R-"(#Rh%6͢4"-2#Tʈҳh#- +6-#- k-#- 2ГUrM 43hBM"REXeHr,- Kk $-JC C* "ͨ@R,P @PHR*Ȣ(j-"46B43m2 #C- L3t3h 3m$B-2Њ#C-6#C-IcC-C7PC6LZX65L"%j!H(,"+K 4$-dMB("BP% Je@""j-2H*-HM(*PL3hS6-B-\HB41t2Ѝ 3202sm2ŴC3eГTC-B(4"%-h(#-B(ʌ2R"MB((*3421@HUQ("¥I@PZ%@P**(H4%Z43m3hͶȴ͢-I5k-S-B43h L3u͵2ГUs637L͌2360C--ppCPQsu 1t2ͨC66ZX3h2J$MC*2C-EC C+LJ29.,(*D@P%D(()(QA@XQ(MEffIIeiDF噺feVZmhFZZVZnla塛FZiej塆ffˆZheZPhejFfIDeARheIhfn;&hy#@!e%,(TRP(( PT%Z%ZQ4#BZ%661t"6-M4#B(-3t3t3t3m2TTTTTVՌ͌͌͌Y420\ 7 2܌2%hC-BMB,2M 41h26 02$$"*\5HT*(TR Eu^_| oo_|C|Sa񏲕_$|aW!󯡉=xor+ڇ!QHyіy:펣:8Å88V[helmncqݗnHar7$^7 mӎrSlam 43Hg;jjDZaeDeeDRG1%(h}T:b *  X,2P340PI5 C+ ,2,+2]LpYp#]\Nty^d=GVx==_qܾ^^Ϗ~gϏ|>wN=Vo$QjjjnxV^z*P  (% @I@|jzSxHU"@("ʪ($"42*2H$Ԭu\뙜Df]rXII@fjr:8'&L2gy&t173fuK"J\HW3r8o&fbjFf%DQEjQ%X@@%zggsf+-C*02\< @Q(- JV~C6oDEAD-EDQEDQYEjDQFZZ?,:ݣ}~gw:ޙӏ|`_'G^_/6O#/Kq:n;~xsstDwNG'w]z.]zz~hv|Ggz {gPsz>4=/ y=f9g֬s79ܗpw#y\NLjFfiTYjDQ`&%XE[]N[ʫ$Y*"($-@DE (P~vYJ!JIJUDQE%RE,EIIjQ&f&ffTaRj373C&bnqLj.s415%y3fjFfbhafjKfjXea%XEa{\=Co<܌#Pʗ*2 40=D ,PP(( B[5kUBTu)X8P X@% DXEQ&%jQ3IVVfΡ3,͆fIKΡ&Ly\H35%w #- H35 3Is5 $$PKK}kݎzOM,Y3ȓPʗ*3(ʏo=Ԡ()BKJ"~{ޯE_Xb݅u^/^hvyry9;\^ vu{y:OKw=7B/7\vGZvϩ;|g+;׆k:O2v{z_^ceN+z]򺾗4LwyKqsl.ϤG>>w)w;5<9kaYfla.fffXeaIrIDK$ %γ=y}WnjTX%.TfZaal)@AeAI@RU%)@PPZERUxޱRˀ;89tָލG%F­j\{Wk'ovWog\\$gkGogiF1^n^~9pkٗcǒ.LΙ^|{~NW;}g>|gm}'K=j_y]NldԌes7#qs7 pI53w23Y$3Y$$+X|gų]kYYYFZV,XIeeGg\nh%, R `(PRPP@ RZ("( (J"J","*2MC-C*2-C3PPPw,168 M3qg'pYYCyr—3Cr\C9ԌdLPİ35MdY",$Ԗ= Pfʉ MB98t(J((,(UU(*{ Mk7ϽVN|XU,޻Vp7rӉnYG/eki3SX+("J "(J"" BMC-C3PIJ&u qs5 dY35#9353Y3Qs5#3EȒQs5#9159LMCPL$3(,7q{>7|Z\3:LfQKܜ{eZYAAAA@TYhUȟ}fqݿ7^}9ccVfN,}9erqk6jjvo={=f|e2oWѾRY~NX:}c)oVQWi7i/Y>`}=qN>^NLϙL>h}#oоzE>|}澜^~z~sU3P,K ,XY >,c|Z)%P((PeJRCP OW̗N8v v)vGUڧQWM9xtg|tОހ^<1-)!{CŞ<<<< B>uCsң+N>^}HiC}b>E؏}~6}>1c'#k}~)}g}~g܏t>}> }> ?|>?J~ϧP~_}/C\<=w=Z$ȔeaEf%.V,[U(JP[(**)lV"B&FlO_VjheheemmeZheZheZheeZhemIDZeEKee塖ZFZQ&Zhaᙱ'$8,^9r✣rÊr' 3~:^n.l֯3zGuIa&%DVXa g" R*(,)B(ZCRJRG_ :|3s7-42B-2C-+62Њ"-- 2P"C- 2C- 2C- 3\ٽ|zIefheZheefVZjhhm6cqA9nA#_(Τ+|oӋE^޳X!`XEQ2{7=-,ZR 5(Ҫ*hGfofߏ]^>U٧;iQDUEZhee[VheZhEhEZheZiYi]bҲ2C- 2\2C*2C*$ʌ2C3p37 ͌MM c~yg/"a_-"랇gR , $d n_` E "*ԡJ,MJ[*5(ԣRlгAG{7xzssax{w r;8܃nAwN7 r7 Yqqq\nAnAnAwq9G N98܃N7 r#^97 r97 r97"8܃r7 rJr7$8܃r7$8܃ 68c NA9㯂}Mo:Yg9(w=o/N?.DBJv:E*IERYE KT-|GeyO{W76ױcSӪT;ET;c:v]gdu݉\.aq!RJdnWpw^VLw/JDw|==WYx0o=Dž#||>#._|c&>>C>pW|6O|$>y>?B~{ߝ?H~mҟߙCe^a;V;7xwo3֮&7b@XI`,@|E9sfHh(PRQIl4,(]J>RX*RQ` X*P[@X,* *! @Bl@D$Y&u 3Y35 M#3w118bo&3ɕy2bn= c{j}_7Գ$K,K (/=^.JY((JTTRҩJղc/cYie"e(I`(%@`@` ,K KMBJ35MBK#+ ,Yd.I,Y,35fgY.I.Fif,2BD$&l3,&lYd2K!Oh={ X%YU%DXk5PZUEU)ji&-ZR#T>ٿO1ڎ JPQEDQEUEDREA@A`T!UEdiaEfVdjfHk9-̆afa̮a̚H\ƤYsLH2rȈ$#"9οZ:vn波6aa a%Dʁ;: Qe(EBjQe)RдAjqHs^} qK< _7/оz@_'_B} ߝо|})<{1h}+I/>`}<Ph}Ti/O>VTX}[!oϔV1oW>P}\H}c>FWOZ_c95\<>{K D,!, X"8wKQB BҕE BFQVhU&y:s%Nө{::k{k:N:7{::.S{;;h;Az ާAߧAz#z#z#z#z4^<ץ#zC͞'<<<).z|.z沦* ,RTMRԥu)jR J]gE]f(,UT , R, XX% !b `B `X%  K d%R 9{RQTQT eKe-UTUR٢UKe--hSI٢MQBR)@T@P e(PD@@, ,HBKK",(P",R;jj)J54(Z KJJ)V5-[(ғR  SCR lCR[(lP[(()(())()J@ !I`%X%"RDa(ETY`,Ze`(9S=v yUJVITUAJZjZYTV"TQUmVhU*e)EX(()A@B PIe@ H"KJ @@@@A,"(DR,JYJXy&:f)% TUiITQiBj[-U4Th-n- K-XjQKe-jQe (P@ !@(H,QJ  ,K@KK)d ,T,"(@Jbucy(%hZBQVQZFTEQUZRhYKfTYKehYAA@TeBX* R(KBB, K@DK" ,b, ʈ,A,*>s sʬFEZQhFQV &4E TZFZRKeTYKBKee PT$!BP %R @)J$A, @KK,! (" J\ʏ %Y*4 R҉J**Tԥ(*YJ[4,[45[)Al,RT(JH P@J@ B ` % ` `bX @%KERƑLtJ=Cw% )lh-M T55BJ*lAt**[)u-t,K*,@KJ ,P X@ @B `@" `PDX%%FTBDQXC:Xx>ǝ5QieKAfffQJQTjQfTUJ EQ- F@R.Ke-X*PP!bRP(TH@Q,.@!@@@, `(J"( R(Qb-\bߡY7h-UEZQ-E BKBжRQҔTR4 YaV5sJQe4X*Qe *P T,,A.A%$,( ,P "*"(::}Nw(YYR袭EZYjQKe-RQTk4RKeKe-: %[e-QAA@ X(*PP*PJ ,BfB !! (K J"eKIePJ"B"󡎀JO}1ղRSPi*[ihZJ]Al)l[)h[*[)llSBRB5 ((HP,B,)@(@ @  `!`QDXXHQ(JDRXE.Te@{=.n.TYeRl6QF΅RKs.VQ[ee--J `RY@*PP T@@@KK,A.K%,BPU(%, A,T󁎁@9xMǬuKeiTTE[)nt[J[i))ti(MHu[,[RRR-BX( @@@X %TBP% J(a@(D$=`=qrPP[)ntX]f[(int]f[jkYiii[( R ,TBA`@!b%"!dYAb%@@$T!H[,!PT,XJ,BPT)(K* to:cزVBj[AVi)mj暹)jF)ѫe&.4[kX)jjfdmf()REARPTAHTDTAPTAR!dY"Y!YHjIUYR!RdjAY[FARdi[ nFAP[i!PjBB!P~t'@o:52M%--4ʵsM\$+w5 SwjXiffj)j[f##WMM\7p6-#LLL2]3 6lC^G9CŽg0uufubu֕ڝXvRNJ@w@wBACҞha}7#yp/Zy0FOeڞ,_rxp^ ='C3/#_+|~9>YQ~0wC/Hc_|C:hrz~?1gǣ|~BOFoFSоzD@{{{Gs_r~=[%5ϣ8!_**4*CWrD%$#L25p4l 6l 9c9c9cWqWqy#N[wpw:î;^ި:ۨ;n^:c;ET^;ӤNÿ|z/:}A]G}'~{Έހ΍;Μ;ΘN;n^;^˫ztN;S;9C9gqGqJS+GڼHw!#BPzxiWqK9pqɇg-e -B60  A @B|߱×OJuTTARr.Zc-)88&1{:%T/epy9#9g;ᙼ,[۫cδ^zۮQƁ`X  QQDPEDPJQE%XQEDQ@Q5 okjDP)FhTF`my11qG725p428ܕxnp * --ZB$H( "R("("(#C-B42Tc 60cAq9Gq9i.a.xpw`u݊uvivUgduvUGlu]u]vUgfWjwdubsÅ8N7!xDQ(,jAHiimc!*r8,`m9cSqY448zMd -#L24+#I !#LKr4t-%"* 25 #Lr]24#L3J#LM24#L24l 06¶#Ls#WlIx#r06lG1l 4#WH* *P @QR)b"UH(J%D.CFVZ3@438 R( @TQ@    (@(,$(Z (A @nN7nB1!12P "#3@p0`ABC%AAAxG           ?O>     # =|$I$IcAAAAAAAA?          ?jAGSҿ!K\Z-R!HR٬)D L1S1S1S0000000LL^] f5jaU V5j!j!jjkk)`S¦0LN17cqcX,R-R-R!HR!HR#c \SoI$!d2LS)e3fC2̆d3! fC+LeC*P+L24#L24#L24#K^76\i' RT%KJ_#'P'll19dž%1(UXQ8+U?/'G32TxM;1!HDW.!ͅģ"kIQL. m78kmaGEZֵT5poo J8ZUUԵwU7b&GEOqADQ'{]/*)L]3ܿA{f<#SNUw; =O'?S |//U|'&QZB%?ŏ⨩Ѣ{ A~VĵGSTNGST)gwf!,XJC捦*CMTZnAU)G51EEh֧)Q<W5V/**^ėUUskZ>' 9'Pe$s\> l~ tJ纣oj}1DbWKTO62Q)yM;^MtOƓӏ/J z}4Uǘ{V?r!XZΚOKPl_Ji5J H{njx#RVR7j&/S.uoȱnjQhG; 5)Sj%N븈Z^K44O{D/uG9sETQPWD+ܣ{4G %Rr{drrsTT8JA^%Nzʃe음VAᎵ[Li:cMmX!CSI'^L-F,8ƟzpSBQrQXVQZcEA!Qj1dk-ڨ''S'Ƙ+U_O~Uw9R5Q=u}T'U|i_UR|OZU%mEEmGJQqV;/ADzEVDX&|28%g#QʆWJGGXZ+|\A*B9(竌q\i╽H0htn7۩ADDKRAh0oP_fffffeeeeddddda/a{ ^% BP;s㯛F 7`Y\*e2LS)ʆT2 eC*ZeiVdiFdaF/_L}2e/_L{ 0/a{ ^z B!ȦC"ȦE28#2<%C%C%C%C%S%S%S%S%S%c%c%c%b/_\}r̕̕̕L 2+2ʦU2LdS" C!d2I$uS0O~}̏m5,icKcac tttTTTT44LLLMz&^@נk5 jZ@ՠj5(MJ&RDԢjQ4t:F#NLҤiS4L4i0iEM4PCE D4MD4TSAM4h8yA C:}SB sB4y cܥF߸~O_x94R᠓W ''p?hq~O~"}nZAdO~W' ~2{G ~ (h,I3~!~t4OOhzƷ WуDdR N"E_D)v?у=yw#/֢| ڂ`rC"y~_.WDO: }h7#\P4jr0OOg!&jF ޮoW7ՍSzPߨo7 7o7oqu:u:u@P:u@P:uAPC!u:N}PiuAPa~)F#~Hߢo7M&~@ߠo7soq17xcwn178csnq16\ckmq͞96x z&z&j&j&j&jFjFjFjFZFZFZfZfJfJg#JksAvzu&,Y\mrȮEr+\W>Ϯ}s\>}cX>}cT>O}CP>y!<Cq B)ijj4!0dS"E"(DP#G9r8q#G1b8q#G)R8qH-KxR1o[-xŜb1o[--[@mr׺ ''߷!B!B!B!B!BBApS/̃ tޟ>E>B0o8MJ{E䇷h?%=^ӳV)1W* A~J{U.C3Nij) B)rJ(J(J(J(J(J(J(J(J(JI$OI?uR"L*5Va+!NNNNNNNu:Su7IRyԞu'IRyԞu'IRyԞu'IQy^uERy^uEQy^uEQyjFjFjFjFjJԪJԪIRyԞu'IRyԞu'IRqԜu%:INu#HGRC!ԐM:Nè4ΣL4ΣH4ND@ 97c{oqM!7xCwn 78f|#omM 6&<gl 6x͏???cyyA䐩˨" hڠZZAZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZXZZZZZZZZZZZZZZZZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZZZZZZZZZZZZZZZZZZZZZZAAAAA@Z"~c|  XXXXXXXXXXXXXXXXXXXXXXXXXXXXX#Dh              -   H H- ?9xG  <  # #[eS~O-/lO?? ?]O^_(٣b^ 4=?)z?lPءf+/~?'=(?lq+c/?lqkI$P% BP.B.B.B.B旴/i{L20# 20+ 23 ͊f3bLئl6i4͖4͖L6m0ai m6mnnn)7n<ܨnT7*uMSjTڪl6jMzzzzjjjjjWj^eyG4k;LfC2f3s9g3̓`6 `6Md6Md6Mh6h6ͳl7 p7 qM7SuM7TSuM7TSuM7TSy7\nqyכ7^nyuכ7n<ܨn<ܨm6u CjPڨmT6j5 CbPبgggygfyW^dyG/q{\8%IRT%Nsw;)AAAZZZZZ!j!B!B!B;))))Ncv;cc#   #?OI$I$I>O$I$I$I$I$I$OI$I$O4 !HR!KTKTKTKTKKKKKK f3ac1f3c1f3cCc1O1<|        ------------------------------------  --      3$I$I>I$I$Oէoǂ?                   >#O>0[I)I$II$|$Ib?dZBȂQ>$)` 1AP!@pQ"0B?W;U\UUUUW~r"""""""|nwnn~_G']FMޛ}DDN==QQ1^ÿaW{E^֯J\f՟Fz~σ"v7*ZVw|{[UUUUUUU~zbgb_ξ UUUUUUUUUUUUUUUUUUUUUUUWJe۽۽ۿnnnnn7wwjjjjjj&뎸뎸뎸뎸뎸'"""b"bbbbbbbbbbbbc8bc888㋎&8cc8bbbbbbbbbbbbbbb\\\\\\\\\\\\\Q1:DLp p4.....?UUUWUUUUUUUU]]UUUUObWׇUUU<>??`,!1P` @A0aQp"2q?B!BrW&L2dɓ&HB!Bԥ)JR4hѣF4h{om^ڹJx7Nf>晦VV.v/%ߒSK٣_zvpz>#FH|\.mCQ]8p⊕!;B!p Bt!eNT'T>=̙厦{cCM' HV">1x'iF-u*5Ԩ ʢNg^ u'Qe|}HNu#/k]ϫ_\!B!Bs^_5CL/^ xG۝)JRc~)JR,FQeFQeFQeFQeFQeFQe2dɓ(2dɓ& 2dɃ&L2`2̳,2̳,#######2Ĺ'OJF)xXB!B!BHB!&L!B!B!B ϰ^ڽ{j]{oj{oom#H4#FhJ_L++43L43L43l43L63L6ͳL6ͳl6ͳl6ͳl43L43L42JR)JR)M4hѣF4hѣF4ϔOn#>S>S>QvP!B!B!BDDD'BB',QEDDDDENP_x,_إ)KA 123!A 4`pq"0BPQa@C#Rb?H0 70 79aG#s ϭ2AW2ps!D^JFC!a. &s2<9"E=ꦽHLL"S!" hja z1*jeNȌDO|&) Efg*"2\UP_z#1QLt3,AxU!u~bw;Ţ8ĺ?2_pg5O WffcMp pHDL1DL(aœ34 TЕFŗտSb 2=ULuW9XpJe / j.Fkt545!z z ff@Éz/Xϙ\\! ˜6 dC޴ ˩. EObuWL# ȅHQ"! D+޿Ӹ(' ]Vef6HK!9Ùw5> ĢX)%)bX()bX)%Pآ)%)bXӢK#'Zi٧)/+888888888aaaaC911112:::22)2)2)2)2alaaaaaaaaa8888888>/)AhSŔq1'jÞbf/,~Hpq|q|DO/!a 01Qq@AP`pѱ?!+4,,(B!BMzR)JR)JR.4}Ar  H  pϊB&)JR)K)JR)~!B@!B)JR)p])JR)J_Є!0AF _FFFFFM)JR)K)JRja  I;c +s!Br#AdIKAO-j{s/YO{q =|'cϴ,lO6&6gfxIW >Snz4O>&x9>MٛLfc06f'fmLfja6!6&؛SO>lۛSfllAL&})7w7w6&a6 L&3fa6瀞%67ja6fa6fc1s Os McLf 0jmBc 7SMlOxؘL&c0Mڛl}&xBa0fa0 a7bxiF{Tk6i3@+ %nfBBmLf͉1$sQ+>F| { f4Y`|8t>t=/lr):'ƝsHό̭JԱcu=:_5Ѩ:Xh&i&ifhr+\ę !La0!B`!FBĄ!B`!B!LbѮ]\r"Eˑ".B9D\ex(tȾE:ӱI$L [ 5MSWk&s7SҜ!z{ĽB &kb++W̯_3P5 A r*ZT\foS?[{aPB('\Dxq4% N +XWd˴qieM8`G61ŵ=ZOQ{l F N07m`_(pij2hIx-FE!,1<"1s =yFK3.4k24<85#gB DxdOQh0O`p+qM'(rVRQ:kQZ-qz/DqW$OR`Oмxeմa1Ltᘢ6YKES/womxb /^geyNDJs%ckZS( ڤUЮ%~K:R5|'5//fGjq\F~e^vFa:ɹ%q{S_pSWJ&3L!E G"`!sѝcy,ʝEUq //"mKɲy_!BfcORGfzJ=Ţn!=%/X٩a=a)ai;M9fX̬z9Kqm/=̋~/zRqhqŁvz%Rd$ bJTKlÏ1YKifd/͎]RLcI;vmܐ#qB$%._KX!~|8Dp#(Ȅ"pNB,AYMy#2UM7z FoCq .7 _ٳ2d!d^C?!N>qD$^`# I߉J_"g;%y RwWH)YkÝ2!itM9c.&ny_|D^a~GT t#yud8ƏVPX8`ayp%79Iޥ7R8rQ"^h gD3}ON>ȍHs$8U"q\,3Ĝ?I\ÍxxIzK.Fd~mP6.ض)j C@)<Á/ȟQ$-2I2Će'(3?"q7(~{Ӥ6M =LdY)qWxyg6~zW;TzFYRAr zjI#o"?'聥)_=ac%Xd;'+ ŏ[/%t׍3=&K]brdX3=21%/b-Fj+o53Uxssodkj+1qNg?5_|d5EB'7]ٞ3ܯI|n& 6Ykq7MCAOcD%j4D">S!: ?-aޢ4|LmϬW34ךto͖h6ÌcO\&&Ao6-.N"nM‚ 0BBODǞ#aMȨKcݓ8*knz4&?$XzGl O 3+fy-nixB:B 2$Urb[XV6mo#'0QRmrtu8bZcǓN^F|՞= lPoWd럫?,8+xhM8h|-bWq,8f^8埙 ӖRaY1 C=8.'fUG3:F?Љg H0ԔYdAFmgOCLlQ|"?WafMLfgK\s3:|p&o!GR{PަHIfL^ H$a 5VQ"aHdJmDqrf‚RmB'?E~&|*\#?|&W73X?!qF}̙&|fh9;wG~;ݍy\W4ej">D|">D|Bp!BaB!BBa6!7;`\ /H\GlP>A:gDO/<{OsI|3fsQwz; ^|*>4WvD;C5]59s{ŁXB_xX˙{WumHgwq9ö#{cK|&| "gƙ&|ɟ2gę]:IO"9ȞDȂ $6.: uȃ9b#ovAjvهdv8b;qێ߰!!vi}||j}Ʒk=vwYf|ήSw;࿇|_棵###;<?Qg[ |4EVw߇~=@||vwww݇rwf3'Lhb{ gӶ;㳎ji;R)5{qOa"愠/H|-a1!B!L!0͘AdBz|! !L!B!B!BLU: ,X <\c6&lM!6!Bc҅ ų̻HMژBMڞT?DhL!B_B<7Qb &!Bla)Ǹcűq a6!HG%W-L&a $k M|G &&&0B!BB!B!B !1B!BaLgͷlBO:^6gc8;!B! !B!B!B!B!B!B!B!B!B!B!1!B!B! !B!BL*.lx(2A[MG6{Ba0B!B!Bjx!B!B!B!B!B!B!B! 7=5!7|G/dg1Y' P{3G4>O?4L333糺zPlpDODOL!={$vOM_; ó菀hGjGlGlGh;F櫻5˻;Q>f>6>v>f>f>f>Tύ3G;#>#gwwy;Nw3wwt;wlۇyku+<'Gx[Gg̻l{l{I74C9{NO9>/st=ε~>|):?C:?CG|H>O&Bn)5Q5٧`v)٧mv_KDzj%;l |4cK>w:m 3wa߇uw!ܛ$:?;gv۟÷?5> _k|s|>R)Ў؎ڎҎڎڎҎ؎ߺa-t}22"!4"DEȜr#h4 H4 H4 #@4#@4 G#@9A]W]>,0-2 V2 `^#2 7G JN,Q0-jq\~~VN,5%/G47UTi3A f4\92>L#2>L#2>L#3>F|2!4]E)QQQW29jcLi14os~;ߎw~4{OsAh=4{{r9r9r99999h#G2)K”ȲRk.LӇ.d˫qwӾy;Nw;q;'w;cKƐii6Ӱgω<>x|ᆣkFdk}k}>>u_ 2*:: : 2"22aVg[2.61l9QxpLxGB,,(,ȢQeȮEQEYeY{_6Ye_,=:6e8(X+EȲ,/ X߁W/Ӹ9ѹZqp'Iӆ{At'Gx уAt:At6Iӵya:N'It'N{^t`:6lzxF[0$Ӽ;I$ymG=>)(XB! !B!LHB& !B&!L!B!B!B!B!66&$Ą! 7a<z"sy=׀_h)} BK-v<ܯ Rv /{k\pz_hξxq8D࿻ tį^}h u)/s3.fz2d=Kj6w݇δׯ鼯ݓΡgqH{kڞ4g}.#ވ5zѮZK__įw>n nuK]\.\)J]_~l~?NݛҔ)W2e\G299QF4#Dh14КBi94&ҚSJiMLkW@4 7 az%rLi$tΑ:&56"j^<x;Nwgs;wC[9͍y @7 ( W"ȾGA\Hƴ<<7ꚦk桪jMM"Xj A5ԚXjTjy5\kqVk fjk^r:Q2>d|B̬GQՉu̎d̎ds#j#P5Ms\5Ms\5MST ssELblMLHL!MOu#/JR)JR)JR..Ā@])JR)JR—җ RJRbp]Ka/ B22>Er#W">EȢFiFhx@*,Y[dsddA$jA(xTdM-xɱ0L!B!Bdx!FQD#!EV ĢQX+FFA$AAAAAA$IAd/,0AR I$,eYF| h%FFG2de̼E2.eE eY6!6 K+QEEHҕaQQJ #bAI$FYxWS X5McP5cP5MS\5MsX5sP/1yjy^b+,q-5.)KSj_l5пD"]{,QEQX(J+d+t)JR)JR )J\iJRJRJR ѣy@мs­rp|ST5sT^g׮Y .L'q$pC> lE%SGc##,22!8j \$ߑCȌ4 !&Ư#CR ny<&Y45<Ə4TjFxCaEk㏙%Teֲ 3 F=GXq=Amt E01=]pa5$L3 <0S|. /4Ì{Λq @m0(' P};D1A'{eg B CC=?{8&8o,)-QϿQ@ w$F2 A7pKxKjOOr!gD s /J!8E\B<B 7qU|}}]8~ bB1O>#rl B 43mMI 00 <p}u i]}vws8.ukCN(S$GwG묦7 L <5 C "7<0@e7]%ڛ C4R1Ai,vg/= >0 8C8ەܜpN}qs;H(eEu|K(C0 }$yt-!aC4QWQuwV<]r<1'2“<@I%4u򉀽[qdL!`▋l`hXmK4YQ ۸ G sz o>wH,cE bB`bj-5dUd;YzyfKλf q* o}< 0y?^|eᆰ L;Lj0@ ";%TAX,PR:ϵCCBQ]<4'8$k?2,iJ/UD<&15u;[N|&(PRפ_c1Vu<>3Ïpkiqwi4V!G8Xf&[:)5v8n7[j[q8ߝzHJN>}A,x/{[]0;_o(fQIDANlˌ&eJ,_Sv9YN}_?)a=} ogi\Q1R+Z'~ď֜y䕳,Ԗʯ=~ ߞVgwy,/ځ+!&4YӦ z!J HY\^mrKq֕>":}y&(o? *,vd%{ ){Fy<Г kOSv];?s堥4 R"OOPKUTyWezxiO㘃ׯP[؆ AgI3+1-$QN0H!i, wPśque.j-ݯ,hqo84rkӠ{I]N;,g(=_A /lؼ"Hek[%E<4\z'i_'0-U zwq3 4# QbF* k6npV>.Y*Anr6Gj Zǀd)w3q\D51N4%qU5& h0qwo uM|Gyz Wt†lQp=D&#Kf_I,0 m4 /`A리 8I[ѩDózȌj8cNi@<e~So]18þ M61 Ozל+feďTA(~ye"$5Ž0K JM~_,1LB (Aqoξ- !}o+81f02G|܎Tq|Q;:eynyIiF_m{3PjSژی<xϾ2TV8yㄯ\? $ *7< 1݄Ͼ4|w 21Iŝj-rRFa:CxB"_uI~"(H"PZ]>[wo,?Gn2׼}ٵP?4BN]tK.FB>YF Fif|"EھL1۬|\'oqqN㒘cLvA]}؈kj1a8cBac #`%]v8|.8Ԕa?Ko8Q_ۻ!-dw,Zq%FT!]1^AG1/^tYmu,C hlvS_Hj$XkM*8Q܁HʫAW0t^_ü0v}[uy(g,O-Ϭ8{< pEu.3׀n 쑂yf2 Ya ~ >_2HuRA4IM0,4o<H4ΦY@K d/ަPC'Kr(x<1$4Ï|`3(Ō6=MO94t*¨BI#nuQ,Z{("8S= |02MeCL< 1ǞtƈI5̦ K~6Na I3PO<+L_0C\A,F̶6:5}<AxGȂ(gn]mfHm=u㖸@kb ?oc (AMy`_w4n0)LzG5p8 e_[!0ywK P펹"Pxl- D0hS;Ow,3$Glmr{ٵ]<7ÓɒkSHEJt[]4OH4,8}u'sH`9\⤡v c [4t,h#Ӛh87a8’OQ.ܺ<<9C=׻ B )  0=,Af^<MsT-ZMPCÈ|f8[&6Aӈ0$_*  ԏ:2qy0SQwݤͪ!cν4CK~"kodJ.CE MwM A'9+c '8gw O, 7uDy-Ls}Yr'q`*f0I0 M1ilsB %Jaͷ]g-n:_]YaPK(67 Ol,-m-~}q]E}a4q|0Hd(A|϶0ֈ#eYyIQ+a\x |!|1ǜaEpqM%_|8 5M48<^l4(4s 0q|Ng0h,}v A@Ac}}}}<x`:@ 8I= 5l L 8Q7qU 0<y램e ovs߼0ߍ;}|U_-s@$DAAEq0A/o; g8ro},0 38$߭o l]i׏ vQA@ ; ; x֕n{랚(8 >ʔi=qK$ ,K<<}$ 4 41H'7{ # 0ǎnA4x:;,*,"0 $@A$3[{pCqF5ב\(s 6_O:4a%}8 ,30ҁ<<1 b 8 s<1¸ 0,04 3BS , TqY#ˌÊɭ<=^O>0 4ƐqS-=E6 s<<,5I r1ARM$I^pq Bz| #0 < s@ }q"'{ v}}p<@Gs%5Q  *l_SPT#)ĐEX. ;FETblYF̬W V_ۥv"n>A1~^D,|]/~P?  7J_o0Aj).sc/. QB 4#LDdcMvErv7A!m4P6-Hj %C|9v7Mp&B!Bhygg`8e+((izAvMTꦩt<3n*BHitTˠ_H̆&&ǵ4LMM#| |>XQEy+ΦIAF('2QF/>G#|><x""""""E<4REEEE)KR)JRů]\ogdLٙ!B((/GAAI$ " 0CS_mRҔ)v)KuxeYY,B%aJRME.l\ҏ1uX>"(8hW٨EEEECjwXV؄!B&fx]<ۙ&B&!B!LDEp8tޚ'dK׿gZ?& !01@QaP`qAp?ܑ";#!ԍ""#a"#<>iљaXufffffffffg͟~D彻? hsNIۿM5#~ۿDrMyÐAԈ45"4#B4""""6FB"7"7Ԉ|7rؑ"#iԍԈk'dDhsĈsShsNa~jsMDDGfFCa6MH|jhDFsB5#an"#q䑼r#q6DD|yyP~zRf"">(IcK1gs=3hR9JRьs8ygՙc:gw83"9hp:hDnŋFf{;f:fŎ׫\9'{w:33ߞً5Dgqqqp?݊n>6?lk Y ,O=s.r%8L煜K&s3pXx8q͜Kb1F#3:Lq3Bq38B4řՙ]\ttgFfg3ΏfhDlοG ؝fffffyfg͍:>thά,:yrYtfffffffffffffffflŌ3333333:37V9=ugΆffffffy`333333363t͞N~zz{uU@1c1~ߩyyUDf?,tcc v~v,v Lq3c:;s{gV^v tdhYŝ!0|Gu#cBcy(DFc1#61ьs;r"""#CgVffffffffug#R1zy3߿AY^q5#S؞f28<F<>ێ^?9(]x}1I(ή#yc<q88ǙǜN<{u׺^ݼ 5wPAPU}{om{o}^{'|>s99|n<3#?gt>}uffffffgp!B/)JRT@ 3/6aBpjhhjn3xLc"1+zD|^z!##b118Gvxxx8t3qݛM e`9PvpޫzEe\;>pCig2;z?gWo2ya>k1{Ԝ=3\+m=µs?)~~rUvnݩVZqEdXcmmo6m^ o7m}[mڕj|8O b,8x6smX{M=xm9l|wo%~WG,q=lsնYg ,v ߡ2,,$,>_9?A~'Oz>OryAcbnj*׀ծujUV[nվ1-X{wxmmmmmqz[ny5 \lXKa#-)Y ׏6o {ޙ8q,LXdfd޻<^|N|}Zu{~y6y;?rՅެ,$,X&͛09 ,X+ZjջVkn#2Fc"=P5jջd%o[DG3g,r͏͋bZ[i9moo}sm|Yw_/_6{|7͵~<=x?V,XglyY@jſjݯ0QV5m+VmMVZ[cI{;9qgcyLzocmz}Oqyy 9O/~_v8/YeI,^,#s$<2,e3|%9qoY8+^:>ͽ[o>:۝ ^G: z&^><>ϛs A}Gu^-+Y={/3VǟNxo7[}l 3{ŏ[>݋:7y6ܰ:8v 9Ϟ';~z%>';K׋6޽$ǻ_Ĝ9aͽtq|H@'7ww|x<{',Lݙl؟E#x[0M<-؜9Yp̉0{ecߘ=pM/w׆ZC m~ߎcA#Ax{+grPxdw,xg1 "N6[e,q"Czφ>w8[z9>>|΅G<~gY;yq 6 ;XY/=g{m|^{[yv|_>Ygo^yԇ9g3t< < p<3{سlÀ; ߞvqzζz}[3~ߎm1}wqeÇxs}d<3g,xK㞹e ~pyrp$Z=&_NN xvxG,Ϥ}<Hyg ^1pȷp=o}(1Ď˶Kd):N >}>|;} 8gGsegN<yΣ{; yc#,lbsm=K;'>zmy@Ϥ<'8p-݇c;& ݃o6gL:6x Yfgy|y>>_Ys#9Çw:s,I&o̱̽g3?A86{e2{-kɔ^I|  @>yAygd랺"q%g gf2c&E> <ńKgxZ}o6}d~ _Gсd;Q^s(|gIȰ ,s9 ,G;ys8/~3fs#fsieי#̞ YͲ}n d//s/<מ|Wgg?:ϡ̋,I>~D < ag3$ ,Hp 8pAl,,l,wAge#x'oxs8^(>E/^ρ'ϧ#gpN[͜<}:Ϡ>C;s9YijYg ,dIw,8^ 8Ydi3dpYe0H̽=,#" ,Ȉ $ {, xŗM6sFC+#'o{>p[;珆y<2gԲ, %Ye쳌, $b%ÁeXea1 !sؓ,,ɂ9 =Aa+6XAİ$Y82ɛ ~у>xfiyո[>׿'<>gYgs9,8K yNYgC%Ad , ,YezK$ȈeY9XYAd)K,,% =@p/YbY)azp8[!%{В9z3r[cE &2gygo~^ Cw

a6Y /gr,deYŶ ,8dAd^d+eXYeGr, /v{#X팖;dlI%gX c3Xl2YFDlLe+ '4HBtOw&I͋g;ܟϡs,w Ygw, ${Yep8Bl,,l|!в,;g2H$^Y&H1`F$,ᬐY ,,z`gfdldwܓOlr]&<,e`3g<78sm<et<6F96/z8c̛,+'3ް{=O׿> wwlO;g,l H,؜$0Ce,R+ df{l,xe"@ $$bXY{ [=zl_ <l,<27-eg3Ğ-f>׸#g3hd,lv#6 ,[, HY I 2L $wd,[=s=౓=Ix$L,w ə3 eLXdAб9/m;=9 !Y'Ֆs8{go|/O/PxY,g&6A&8YeCFYee@ ,vF4e }K `,' G(A$Ad"g$+$d"$LMK= Zf l 2s 9IӍk~916-'l'W,z[>:E<<to2<2 fYs :>YI1 l,!G0Y6Yj$DRDB#Kdq $>XOHY8Adg,ȉXݒXYodbȁ}9}:$ld},>F{x0Yet XdLd{> O pXlXAah&@ 6 Ȝ,؉@Yc:`$ "aA%,,,` vl2ew=[9!g,$ Y%A1a|3z}9,Šōc2ض%%>dhfALId; I lxHAP?= #ReK=ڍ7 ,vd0Y#iH,/e u,Gda&s#l 432,a ge?rzz4adȏ+xWWNGB?z/~ǯ3?1z)_?~ߧ7gտLk'CH?4?6?'[o4~y#G_73g߉ٿv~ef}_a?UNߪ@RxCMN\@3#~fQ Lg1[^X$2R>t 5ⶶ{m}; 󥾭mo%k$n[ŲjKkk/ ˥?O'~~, 'b[-9}xDm?y$lk+ /\L|d=Ii,NM}b&)[_*FOg2 32x2~ ~?[,)k; nKgc!g^Z& QXmMY!a?~/7^{<Y ȽY|A!e9y~Ilci ,ׁV{dda}Ȳ'{@s,cII$H ,dGllcdՑ јEC%$cdI0dNe 7:/؟Ϳb T?|@̰ 6`x͆ w$/Af6/v^Nc{<3A3 & 8Yxs8;4=%Bù6{?e0=Y>}7/Ԟ|M ܜy'I$ Nd^ dbdxSfFɉI$$ $lNAlGތʧvL{I$LK&ElldNgC,(Vi6 0!g2K$I;;DԏQ l8A{,egO xY':z$y}1ah-<=ǩXkX@oV HLG$TG6B`41%;>cNl'DPT31CdP?$(kP8 7 /a/IObpՍ$S3 O'N?IB lQ}Aa7O{&̌IdI$$,;eYsg>\E3 G#HHAgNFXGdm:$8އ ԃ=(009zm=&G;A~f/v1&"$ɝcQ{嘡il܆p$/~d'dIgG,x#3~3WiM/\B'mMN# ܎ϓ#0w۞QmY,A&<|>~z3͛ dYM!Qbc!ѓtN gz4vXkX_iLO{ iwNScK(t~  IţhO\X6|)!qs؈bQ!T}5 _J"8Ys^1ǬUII$$H̒C%Hs $F ZwIH d sdv6>Yk' mߥ_ ,7{)p,ɂzZ=O\ls,,Y6N$yHcgHXY!ag$1Z}̅Y gaYHt73n$WMEC򄪯a,/b2uLއЉ2I$̒;%I#$ &՜fx3Or3X̆@s=Hݓ<> ̳bz?Q8r`g2gaYg1̲q 3#$`$ 6K,#f1]IĖHEEK$BOԖIN'dY$$K$x'^!922 Fwspo~^3gslQ/{9r%czHeYd19 % xKOMɳԐd$2Ld)c"YIId1, q$OVIg$DYԞ2O^<2> (H`@xa9\?W/8s!7zgCAqăG6 2~\lrs/S6M${ldI!ɃIr^Ʌ'?ܐI0ᑒI32dvÙ% ؎^gܳyes TDcxl 01-Hߪ֙wE4I/M4Fad|AHCy?ke8wzL8fٽ$ ܰ $'9.8d+3&l,$r,d܍2XN;;&j[ydCwqC~l߉<\z[/vs 289Y&8A'뀙>侀LGMX> )iYJ KʹB0W>mOSN> ,,}řg`tC~> \ŁCAnGBasGvvCWDF{S;Z>b@d }}}>wB#1H,{?(-p}$ R j&Xgۚl,\lgDZfAѱ=#8.D쟺^yYɖUx:'\/_`Kj7/GVYRܒIg$,HIg ,SφqLsuXC$`\g2E>YsŞϫj?8 mm%Ra[=|,N9e: >>?L_s07h?Rc`3-?JLg52Wj%z}M^Hp/ML{ '{b|)K~XϒKoS/w !G g0.zc$P`1 b97s1z9oyD&)f_ ϧKu%L]6AMl[z͜`&DY0ˑ >!zV('؆A^#³$CCl \~=2&!YTs7VT#~_!B[ ? ._q}i}X_5 |vLnQyjZ}޵hI{O9>D'%+,6FN%C[ٗ$?k`OZ{ܳAj6g ρCը}r > Xܦ74F_è.'66 ?Ag2:n.4z`4_c="Dz6}}3 O𞇺Fnރmc~LL`t?Xzl#cm'O,d'[CC-4I". Ez:'lznz0Gk%cп&|G]GuqVg wi['~-jƯo^=1T '??F쏮=d|~V})W=[1/ŪcL"g dpIcgFI1dܞI6I$OC5'qOܑg2=Y-c, xXeXq,xds$LV0,Ɓ>$X,d`dCV|g$,,X, ,K1l dc!C[ߵ/fHsC) !:!:i,W-nX ` OI' 33"#9C mآ(4}Ed5r1U*UU*a-UUɬIvgaI${܀͒L̜dLEXD0d,l,c'&LvceQ|pm9oCeG}"{D[ :d>49 :`޻7f݂8G܋: ,?| xdx=c,Xώex;w52q $rRYNI 1=pX$XI;OI&I$&Bg6ɟ)=d</ Ml$BC&D{MU',?1G{[p,Eɏi)I"#[0{~9 n([ze>dùgIY2' qɳI Y%'s $#$l8gIđHHI3߰ .z BC5X$$etp?$;~ܒbb2360`^I6 88KG,ϗId&~}IB3dL*͒2Y$L{BOrIeH9ǩ'2glK=M/c8 $B"Y{9I!Âxgz8A̛G$zJ&fʓl>g??qGe?`;>"&Bf*}#cZI56d.I&Itؔ4lH*!ϼ@X!#s? ա|kz-_„Twߋ 8Mud}3k!Id1lYK=9$̚Hd#$VH͌Old$I!FR{Hd;π :ϳ#g7os8I%_s"j6aj`W ~rj`m,|Βt W`3`;}8zN9-/NP:O,- zGs̲u99|ϛ$bz~I,zK)'8I,3e"psI.FW[g%7<cd3ppA=N<~q|\my:x/h_w ?LG  "$OX*3g_?~I6 ܕk*e/[z2K$Id!aa$pY߳&01S%JI+HdIpdI%g(}fjX3d% #g :Gp,#/X&5sH.A? OV"1:,=<Ro?2O?7??K?/|9!ih ~_?7?>???zF+e},~_Y_Q_g$!qhkn[֘+gSs?gߣo)JOx÷ʣ??7>ƏO_oR)ZHs3??6~e-3Z+KԐ+$O0D% 9dwÌ[gO@/Ec5-87Y ~}ߜqҮl7ѧCIO՟Ϣ,Ⱦ~~/o t3/o&O?7m~oOߣgֿ_߯iHpm~y/~|/;6:c<lIO`H?yߟ4H-:*["}0 N:28g}ŜD1}"8.Do">OC ?e,{X!:7,_<<0X[̽wxØXXXY!̲<2,,HŎ1bʼn9Gd-##l,L~ 07%|KAI=d93x&N9dx[]k8DFA̒/!{K ,,aew egYXYeAw90Ya,,YeYgYeYe,g3<JjTsU*\s5kz7ͤ6_i $xd9Ŷo"< FYla /P aX{eYeY̳deYeYeeY ,,Y=eIeX6Y!a̲e8&eqYe,,ll,$YdǃS|c.Ҹ}ix5$},:͜Nl0KeĀWv~SCt'ޅ8Fd{` $,8G(z^̒Ye282, Ad2s9dqY38sv6@cceLˑU#rOB6s9YeYe;%YeYeYexg2eYeYeLt9 .DNd_,x,Yl8tmX2X=Q$Sa,$ 9p,,r ,g3,{eXYade2/@~f$8XYeAYeYa'Y!!eYaaeY%2'LΉ1`#/|S< e&v|M}zpH޶9 pXلa</_8s,,,.Y' ,,K , ݭeYg eW݀VetՖYces,eYes,,YeYcdq$,I$3Z//T C՚INcd0w?~Ϟo#d؎ťs? s؀7捦get:WΚ+Vq:GƭYc VZ閺ccYeY̲,Yccc{cg2;K$,$,߸{?̟z>2G>Klɑ CȽbp,q y}'O!՟t_SZ1 jtkb)9¢,@<ǐ^; kgۓ;_ɽz ofI,{^|d6Ξd^7x"p#9`:FC ,,YYeYcq1bŋ,X1,8,,,,,,,X2ssM7 @}a `_ VqgBxtq|A,8p=@e@n@XDFA85_ I}kwDQy}J8wF{CoMAG߭o~%7UEpawwo__o5Go?\woq_I>_?XkZGWiPDM_o{rsO_/amY,(k1$v@v`3ϕu!a#8gG,,v=ᷤݹy80~=9ܢ2g+#?/7*ofC>۟)8x~J<כdz#?/.?oOo}26_'?࿟?r'-v=R*?RW?\|PsLeszS? Sml>٦&eocdw ճmcǍs,xg8d90d,KՁaZ8amm;xANo^>-|͛=g,NYz㗩Ճ X~ Ԅa$t$_:>)I)ٟ_Yggg''g3s3sɳY [~iO IelD۟r2M^lcg1"H"8ń8G {"s'!-V-ov8ͷ[y93x]ɶ;{{ep,gŗqI~ I3,ʓexx,̻ȸ^'2w̌߁x >pd錉b6 yp>K]:g=ȱ6<|}} sIxgN3dfQxqY[^BYUp4*Й捳i28gLX/ #dzb2 # o`.Jc̷C<_ /_Ez^6"NdD Ds ~ Act?p9T3=}\7e%YSKlڲu+,3,)+,vԖeV<=#B`I}=!6MGmel^n< #8^ುfp 91 x1Ad!vE2|p/M?&?ooo?oF??'0 k lOd ?O_ޯVo1iX سObG#' D7ߐp'Z?$^r?B '?O)SOJSP?'LbWODD#$KI?OK9&IxYGek,oɏ ',zgFmXrm⇢7KY'ϞgXY l"Hq/YMnLPUF}}do[r_W+tobwioG|77/O?W߿ogMk{חH_OO \_/=Fχ֧ BWbzI͔;j)K{^|pC0XidAdmXc 8p TPo%Îj+[ 7o[j~)QUVۇlwȟ[~c~"?U/LjիVkǢ#-~_7~-~%'_-~%-JϴO?S=]K.]Pf[? d8O^ɖlx_\"<1g=-pY AaY#bC"{ /2_65LsA랳in{10M>9ޮљ c7z 9/VvzYcgvt`p2 cdK1/c=1 "/@80yo؅TF5alǀd 08e@>><̞)""A_^"td8dٓ:f"DĞ#$M&soxNlzepI̙6w$sfzE=a̳AÇ=l ^ls E3A@YdXx2,, $̂szXI;Iazgs,,q8Y%ds$K $HYe $qK 682Xdkcώc`%,󓅑F,gGplA!̈dX}dDXX y5zmk-xR/}qٷŷr|U2vF%8g6I39Ep MY@DEr A 2#ۼk0Az<gws,S#/ :xV}Mvxi/[q=o'{6{Y Ő a !gl$3>{ c2"dFsa|c8ww > 9dgr15Yx3Ͷe6mgY_{ճ/Gn60JI97)=/x<@cH8e,dp8czx=Ff ",geD}R9GdqNgZOveNoywx=fosc.D儜ga?y, aí 7, cA á89 @w#s<>9#t^>Mz^3汥de -gS lc1X8nAͲ  z,}Az o{Dk ~F,o or7y -w-z|6z/J[mO7ٳ%ۂ0qDVYd^-FډR61wA,{y pD@D|e @ 6 bȃddt"8s;ϡx{y YY^w͛[gxdd|rqgYܽNfcg2e/sScp72sF/ @o m ^ -c1`,xsYzq,c:C=6:}@FlAzc5l<1 X Gg^EoO'xvNd/=ydǙg x#`HYfqlrI,< [\"8jw~@ t AcdNlZx"<G { |Vg|m~׿|g;/6Yd[a㭹o7!o,L` s=ާ^-*^AØF^ s  XΜ^@p~^>A8G8t G3<3=u~}wv_x2$ǛǏpgYf3oL zfc3e:C/Ϸޞ1ёB6ނ/VFp H# A6#Cx7#T7>vx/]^2z{vf 8;bx=:sEF$,-68&pdx=AQ7Clbt:YiQey68{_q[͗ަxfwٷzINjz, &#c'ߙxHl o`,px!,AGNAJA$DL|-/'ՇOmsgg=x{9=z^&yIF?c!ad&3ll`pbxp؁/  , pϗ x39[_=[xef|t6Bl+̃'y,A 6w/#zSu/ D1x6 ,66 8}DLE8Dyu|7Kնm-lzOۍ${z[:ϟ %{K$Aoٶ,z&~ -,dazQNd0CO:Ǧx^<6/|#p1Ç>w~K|SŞǶD^Fp9o/6xG-'|,ö$oS[͗b[ laՆvX`D<88g8 K=[xdo Xe:}s|-l>=Ylyǫo=o_'ueX7gP%s99D3zc|do7޶A Xl}op ,l"/x@DG2 y'"3͆,ml ms|48{y!z}p' 걌̶^|΍2|QoP {b1=G=dsDbcbpNEp<ǧsm|7x}ׯ^2[޼Ryg{ 8alxg18x,}1z޷v[/cA 62-DlG܍"s`CE8<؈ >NjYfst-σm z/Y܉y路p9Xg 1~.62Z|pBp0#ඌ>;cx0}dY[c#-Ǟcz^z5x,ezqә̟ |7Ėǃdx1as+ܶ#)hGݹ+/yXHD$p/Q f8o=a8>fŰl6-8?T|xHovYy,^o[mYRodŸ-#ms2Ǜx{meξȜ私̜ о!g63m6=pz- G} b݆!#͇C >>'|No7l+/Ryol6%mާ-abY|2ql5xGpGe9dcaHK!H<6mHa#baN^x09m-?m7m{mzme[Lx6e쥲y ^/ ݲ%mx\Kc yqHx+`at[P[}CxH؈M-/[nBo m!-2}abax6"{m;N/7o7Ŷ6YeݶYmm-^-Ͷ-xd6-[ͷ-o; y {m7Coٟc|%.2v07$>Km -!pw 6ޭ-C=C 6aaC 0myo7mmvmo6mYe[m%[mY͖m[m-km<6x2m5kaRG԰xoPj̇~`WgL$&l*z2ua-"Xx2}Cq[Ra6\ol&[ [ oammeymmmml73exlmmmm[k3kͰqVX`ܶ^zcx229c?)k>@xz<,ll+kc=Ð <-Kmxk#ac:mAc] 07[w 6xmm-,X?%~$~5W__ExO`O9M_O3?Ia;7?=751?3:y4`QϟO ⟃gY%??~+?OU0u$"hߗߟp_٧ulizĭ~S*<f 0g~`? 6 6 Qcoa[zO|}#D?W/WGUm~O? :޿z'K96y ,[36?9f?~w߹~ٿlϚ_g{~_c~ >HC/ֿ^?~s?/ֿ #/NS!ďIQ> b`ݑsSϽ& ,|ݜ#1VO=GXlH0&fNرg Xd{>!eYaXXXXXX@Y& zzZZZZZJZBZJ[i),XdAFomsƸnթp~mڗ ݶJθզջP[vV˷hZjRm[\Wg6&6tMBeZڶڵ-mmZo^7mjmmmm[mmkm[k+klykmkko7͵oݷ,6ݶyovſC|wy?<ͷמz~7ż y/ 7N/t|ϥ|'6?9ǩ`wO  |^m׀x>^w,ǀFy=B9#:{o=ys|7;?߭w{|w?3; -rs:gT|Sx =pwпOY/]~؟ųpyN{>^gr=͸/XHo}xy%rsy׋o|xdz[6,^~yߏ~'O$WGcg͞=} |WHNZ,X'e9|M/}g}sћpUy?AWŇI} Nx#mX9!&< z<^cb~yfY'6L74'OG2D) 8A|!#by|GA8b{ovt3a[dU}I lOdZ &~Ġh$n/4ݾA{}#Óm߃'_% ,@Ye!?XZzNײVF3X/9XH@m, ,7s , ,@XHs l,,,,=^<4mi2Di6c?xIO`>M9Kٳ<-94ez  gHϴp'y|`= iݞ9cWӍ}OܿsUہ3NZ4XHZ "2[_bF!"X_z_hg b/XHI!"ŋ,d ,$ؑ͛bM,@bś6lXC5ߛK#jZ*բիVZծD/n~[_P6V~[VV[avݻ~Rul6&UީI*V ͭ[l,wkmj׺@%f5"JtI3/ؾC%'oml,n _}&FjHݿ4a?S5w7;#; <^}ߢZo6|a(ϒ?c d{!`f, O9@)#;ٲo hJzWO",gbBkNZV>y?HX|g M'{1c!uC~=9 ɓMlEK}\@J, xom,2[82M uEdվ q@ ׾ؚKHiA 7{NuLDx^<,{jc [amc{SmQ_O_p~䟫~ia~x^{nȟzV?_?~1~S~AGտ,~"'XX=XZpJ0lpdAc8{#e`l ,lr,,ewl>[XXXeL&d4iihZZ[_ff>O?}ڳf~  /w?~QWWa32>e#(7s3  :B^VHș=^'-% ճm7KLsKKf imRKK7D[o<ZVZյ涶3l7o7con_f^Ϸ#M6~_{m|o2`k|(\~#؏^/П°?p,>o2˃R2,`H|QcM-0F_Db5e!1hq 7-AYelVZRmr9 Ԩ\jݮ5nռj.uX40,Yv س" ,e@8e~8O8c)Fřꀱaa6= $,6Baz^VeZlo?Kk^mK셐%l^ 22< ark""R_xIJ_#KpoG$:#%7 oնe|tl&Y.!lo73l%/4-նiosym mLJm6 ,Adٳd0x,XGtxZj.sێ\C5\We ,9AXXdX,:3$/,~2`{-mmx,- Զj[cv69c73dn zM o{3|}{ͭ}g<2ϡup7"9@!s^Ԃ{ΰD>zq,|xow7<7-|qSߘqO\9>,2z:,?is_N̋vOdW^Áfwupd-1.9.16/contrib/qubes/doc/img/heads_update_process.jpg000066400000000000000000003504261460375044200237420ustar00rootroot00000000000000 ICC_PROFILE mntrRGB XYZ $acsp-)=ޯUxBʃ9 descDybXYZbTRC dmdd gXYZ hgTRC lumi |meas $bkpt rXYZ rTRC tech vued wtpt pcprt 7chad ,descsRGB IEC61966-2-1 black scaledXYZ $curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmdesc.IEC 61966-2-1 Default RGB Colour Space - sRGBXYZ bXYZ PmeasXYZ 3XYZ o8sig CRT desc-Reference Viewing Condition in IEC 61966-2-1XYZ -textCopyright International Color Consortium, 2009sf32 D&uJFIFC    ++&.%#%.&D5//5DNB>BN_UU_wqwC    ++&.%#%.&D5//5DNB>BN_UU_wqw" |ѣ,LFL5z~Qߍs F-E79eӤ˚F\zvqN."ۅeC;GOϳ;zˈ;KvK3s/Js7fٵ~Lх2AlY@@P*BPX)  `P@P?)W(TTPTAPP{yb2Hdep1{ߜ}[]~!宦=R79Ӟ&\:Kn|{ X;*wO2='3rX@z3hf}ϓydfƕDQAA(`T(A@PTE@PX!Q6I2@%F,EX XXP@ZWΕAP[Ɉ͂_G#0~Fɮ6<#w>zESϗS'gӲϋ;?N.ǧ g;gvNC#r19~5~|F4@Ph*B" ?p:DchaE,*"L(Œ1e"("( BRP* *bT* j (A{ߜ}YC\6jQAD7 |:<>g#:kd/1u9r=8.+#ˋ윇unb;*o|W|G=y% nQMȰJ 21d1E,I"F6Y V!(Ed1eL Y V"L,Q" !HdhEDQETQ@ PJX* @* T8RɈ\Gח6\6F7hF:,Sr:N:\r㳎>;#ˍj^ߑ#F("2QEDQ%,I!Y Y YB2Zbd1d1ZbbDQbbar3!!!!!!ŐjbKIŐŔ"("}{<^z7BR*S aHeJA@T A+r˦hP x߫S}D_T>g_}0D 96cCCPH FS0z4zʞz_*z<1ff"11 q%JqFRP%-ƭ"dƀ J,I,K,P `qVT I2Xg=p??Z=|gÝ˯6:W|ٵuj5we6uΓ_>SKz #CNa4}L5>6NB|3ili_>9#}~)b>7>K#<ރEŐŔIBP q1_1zʞK_}/KK>z}G{GΙ1We%*RZT(B (%e(c%B( P@~z~-h@F[/չL.GK}}u'կ:}|orXOTH** qAn4-ƔDd0z<{}c}>>mFm˨nFFnnFk OC}~^iW8cPz_"G}SKK'~QbWMG{!'™-T2ƊqQ~Z5{7\@};co=f]; ~tާھg|GѮؚ[v~ 6??Rᖫu?ɨ|}ڋ6]gץݯ7{k>?fZ_yyK{=}{W/w?Lh>-ߺ:\i2ZSrsKn囍:?Ξ9`h$\)~6~Q\aL2X⬮#6y[呝lFWfdffgpW c=,>!Q.oott&ommqce u|臃,+HOItTD\2~9Ή,zCϢSNn7Zik/66$|p_Zx~*ioW_k l~t=:,ݢ$}8mv\眽Mv37_G/uΣ[份ω[O۵o9۬?~&|}:]}75fm9? 懏LcSa[΃x_r}n7}.sug;k ;ZV٧o  @ @ ) d 9y=^c=cW=c3&n9s3IUJKa&{ 3ɼy>5seˍ/U_u#gKI{mo8G_h7n}7-*zvLv?!5w:_MӞ羫zuڅtxj˩5v ñ{;_(|=Gv\|Fi|^tM{í]gYO\cg>|޿-xS|(z% NOߍ[M:M7Om\ӱi*_\3 XP "AB*  ^.Wzd\rƳ9k>iz1{ΣiI}>?~uti|=o/ոlx^guΗδw9WWo jpmo;e~.y'Mן{ ZW/snSoϣm.& P|55=&v/A}{[|q,Yuo ,봚Lyuw\o=ۍ-ƕ) (IE`%P X(* (* WP& \l2nyߴu֏=xg}#+۬O_ϏY+_S g+w=zVS˙j}6?g?/[t;ze>Ysg}꽴0n8F3ӫvN>M7u5n-.SE?.G=h;Yy;fSUv=\u}NY?q}ܱ1Lieq\FljۊL*ɍ2cKqAepdd)BP X%**J Wec;pMJ |æ|NgwZGpkgS7Z_N{>/?y{)|6ƃe]%s7a۳E[?yaN}ߙt19 u?:}{=gvv9]\jm΃O oSl{v_Q{^'7rcb쵋6ZG>γ)i[V%XS&#+2dV4Ɋb2cKqLFlFl)™%2-Ɣ,,@)J% "%Ƭb%v.ܐJD[n9OMwgw8zj~.t:Zsyf?f9ɽ´vðК]V_Vg>s?;/cf;}F_O[o9sL9uBj=~iϟߔ|ab6ߏǣK,=o#g;.J>^%wku^]6| {Ztޞ|{?om:o#t_5c]zqGś epVl) dfg|w뗕i3ΙȩER@ %e,H*6XQn<]#S&@P~OKmƕ PD`"(Y  !%!PT!PT1폚Ϫ|=uovځt2[.r7.n>lm-?".GX Kˬ\.R楟sfS㸸3adŸ6#&4Ɉͅ3aLOK=.FyaR`(* jRX@ "@DJ"Ad Ɉx׍q,F;|^Ӯ4_Mǧ.L]KiEle]kMP#v^': Q> |篌'sa(Mw~:B.6TEX@(TA@An#)H* $2cJ&4ɍL+ɍ2Az1yyg,m,@((T XAPXQ!dXQ %r]o)(@~Neθmy1wx7#c,ߩpg%^ϳ7sjm}t{?_x}#]7۟j}ukPށ8n͆۞IpCs)KP%APX HT(APe`* C+-U™1JT eqWz0<)KXX͎FH-(Y@`(, ʒBJN|n%`Mm{sgϫ>WykyMzIq/.8徇Cq/糹K8{oƾ>-󿷢e}:m?;â/?5xs ,A @HK cL[2AƙX%p(,iƖJK-%E!bR%X1,(@4Vs:@@ ~}ˬ}8a|S͚ [178jQ', >|տkë5:No:>MWϼz,p>{Nˁf?eߝ˟~̽0i3bB A`X TTJV"RTUn*H2L%/<žyz\2#&(P *R  ",(@ Gnu5&Gٽ_nr}zWa|O⶜rt~w놖tfdk<6:[1Kk>:ϒ ޺.~ܹۉGێM|˝lܱ[je@X%  DX@ BT DPAPRlJ[E5s#ϣ<ޛӞzl̒.4*ȿA=|Q%DP)H,Q(J%)ReqeqVS,矞fyagqVXK@B %,*@;Ϛi=2A`JJ >ϏӬYw~|yٸk7y>y?K:~C¶>3Cf .C _$kGݮn>w/Kp1G}|ѝto9^ܗ.GYlf/z_M] 꾟Y6ڝ:o>/Yg=]o٬O]rO8:_Sv&w6[@X%PT J)RRL9XdgQpƙ%*TX** J⅒UEY QVE!PucIyyu Dߎ wzli?y~?%Y箿?W??ͷ\ϵ9|zO#Mˇ7} ^?.yHἻ#?>pAf~iv^7t-~6~i^?:~dO~;ζ>=5γwYWwݞ7bg~]Dn>ӝ.Wz'(fnqg/IШYHKHZieR9)rƙeFyyagqȶ qAPEXĩ*DJ ((H3|~b, ᱯӿ.G F4 j|DB("ɐ,.5\1wsϮ|+c56xk˶:um :\9՝:KQStMX~)fS6nSq*4hg[.O5W:s>~Ï?w^ _ϼmyO3yO3DX,, ((K (eV `R2 э3 S64AQU"Tc VP%ETGv{P~?鐤}_/u^^}NSe6ǴIʩ"* %(,$(*, !H("TQA(EX(,ҥ `YJ DcEe*R [)n4,22 3 4ARUIeF+,B* ,Z) %* eni鐨R'~ i¿:6nu;i_8/;[ξ.^?i/?RqG_̟CgеZɶ\ 6ۣG'ysg'1|vO;On~QsϨ>3IFc}#x=F*"K4ڜs"#F "~/_*QQ,meq)}^cy2UdbʂⲡlA+V*Z@ @xr}ޓx  >=zci۝n)N[o>OLz9u6[λú#Υ9gO/y_rd?_ӬW4|ӡGFn[5+^g }1lS~;>R΋Ksx!t?@ÂO%UX I|+;=kp_]glwu:|e^|~9~?At6v|͡]ugSҧUisk^뭎|5/=xz֩Xcs{ 7ϿI\GKlwYyuUW>H5c|/Wx?4r9o>yYc\YM{ N+m=9aCtϗF93-[7vX(vNoC%EA?<$GQ}Nm/_N[tkD{~'L8lsvƳy=Vuas8o~C#>ntߣts>L|>9_7 Y]ީƐD+F*HRQ%DPEKEDQQ)ru%2FR$REęo)n5jd2c2fLY0󇧧">X}0>>Az!ln#kl+sCT(ce4?o_y#{8/~JƃUs}qnx4ݷp8+_˯٬12+D(H"@,",2i*"(($b(ɔ"Ȕ$*1e` VLX clBTTUKfPQ` A,)`hAae)(Pwv_qSQHfwm<۫|Λ,__3:MfOo?e4Mƣ7kʷY}\D;#]F(Ji@_@ dQ/i&CR("L,*")c(E,,EK!Y fpdY BJ("* ITVD&RADXr bYPT)a@,UJPQ@v<]x5 e:ˮ;S:.o#=h?Bs}Wzo;w\׬z;_tO>3X}'Muj ]Fcf35})e+gkX($f,Q&BL&EŐő1d1d1d1d\Y#CCIqdR$Q%dTb)DTD@ (-)V-K,!,K),*, @P ;q+R S{h:N^䳔+\q{9.|~_c_z?u=7~To~U8߷oR|̯u¯v }F?'/]GpOuU8V(-1dz .TŐKHLIbbbbbb"ŐŒ$bȸOHl(c3fHDT̆,IDH$(U%Xd2KK F++LW$T%԰PTP KKJ@wwkˇxOk6m:MMֿnӫq_E>C&K3}gK.OKr>:6;Nn5>Ӡ/Ir+;ϏY{p/h|.G:fP$;cJP JB(*(P-JQBXEQ@E,eF*$.+"(%b!KTK( bqLVTe 2ċ"J*(H yzAq7PBj*-J@ RXQe-BUBP*I@H(%""(L,Tb,IY*"ȋ%,K @B@HcbIa% iXEXb%,n<<ώ|@T\O~PԠP(R ElB*RU"* "(H(1eJ"Q%,BJ2*\Ub %,T,TTX%3,D "RLFP ;q>,@U uO/o/O,҅--P** (TX-PP(YH@" $$,KJ$dH\UXK ,, "RP1YE D Jaa "((A( DE[>Al/lupP,?oV T%EZU&RZ"(K(P[(((E Q E*X d%\U,$ ,$ Pd\I,Idi $ "j "PJ "!B`1Xu|}XӝpYE$̌~yzza,@JdJ̪Q-%QeYJTPPQAAeEAI@R "(R(1Q%d1K ,&9BcF2,$&41XIK% D)%d`Y@% EDX1ʀd%H,IuȖYBY,=}NUEPR(R҂[QBEVQeJ (P%JP,@"T`Q%K%a\lX$a,I*1Xl!K 2Ĕ QH!( "zyXd%@E@J<}#U|\EX2#<=^[wF-`)Ɣ(`PR,P,Rl[Q@(TU(J``YaH `@@BX"̡%%b"K% %@ @@ Xe, ,% bd,r]w%rX C}XRʕ)KJ)l, q lTP[TPQ@`X1c`%(HDe" `Y*X%X%K a`DXY`,J, "2dE) DXD F<^HX]>7, }~~xټ*,R,RԢPTɍ2J,[)R%J[* `YJE B"H aI`%XYX1KK!,)bT,Z(K @R(* d*XKQV\S|Cy[.&~ bRKe ,iR*, +$ )R,in4@ `THa* B `D!6 BBT,T( l",@K" P:x}2 .\ټy6?M( hAJPPQ@hP((@HT@H, @" BR% Tb%Pc@PH\A2!012p "#34P`$@ABC%5s\5s\7f7rNrNrNrg.rrsu Υ[[snuε֞uӝvuѝhgZbsNu)u:ystf湮k湮k7fݛvo97fћ~rNrsu Υ7:Xi[9g[ֆuέY+:gP^ro3Xs\X#^s\5s\5ٻ7fݛ~o7L,,>s\7No,,,,< :Φsxx`U*zxf ,fs;9ݜΥ3:ί:άs^u+uw[\5g7Nn9YYYYYY9g?}Ӻ'IO3䗗~pwiݧ~igg$d|{OOLӿN<۟ vnٺ3tfkkO?g~sAͣG6l6mYB 41&pB:96sWߠ=jc];‰ #2=@ʼn.:2hѻl ҕF8J%6[=YSw%*\s")@y! \ ІPjoPc\X:/$^ g'7Ѣ tLĨɶ$RS>n"ǻ\\ C5پk21jc0Fs\5 3HN8{(H=p%k P7h01/" 13,&Y1P*5HTp` <4@d /`|(T^2}X yt ~YC33'6\1c,63ؕ mXҽkI+H8kenœ"!Y&'[pJUd[,"kQDwYBZdyIf:>NODxk3KlL_b-|Z+m$T8^8(6I5++"="f} L-XNބ<2ܭTuj~tT\Vq@uxA^]}Q46`Aܗp..'mn-2+{2n; R^Vl:Hu UCyRيujWl}4v[ q4&ŁHOwyf3Z7Qk grd|l8fFXˍbeϨ?e{&Ջ%Bt FԅuH ,76)Mյ]*a+>S)պYN֒bg: Un!upR,.l$ƵOӐ{K?,~"f2fg]g5]t, "CF3%-YĐ0~tjyL= -_jj/r47GMXp׍YlNK<ۻfOTA)@8?>kc[TlmeV]')a>Q᧨NЧlY\ƓxieO6&c$r 7DX&cfYtd0K0hAЉ")Žɴ.2m`(Bď'zof@4!i.^T۬Ji`JWK(mk)vD+P+Iڭ-N-ȴvH%e\ ]v.2TL)ke2\ V+\{{P?UFBib8ebgLl;#3;Pɤ!Yr&c&ffȈ65gSmW׏EOQ@V?m^uA(Gb3z6`EW(+ UJ(Ew;OߝTJ&BJo%u ߳[H0gaaŝ[vȸXc#luxh%zHjmӃ`vV^=ϱku%nʶ86-Fh7,,`a@\M.k0;2o͚p?gMjp'f *tf¡Fm]"0 R1?pwS??bƛ4q( 7RzLg/h6LB7ѱU䩓aP`ʹtЯA[=-z0>Έ _-fSe@t& mjVV-aw䩑Lf@iPcq\}LƷzPo'}2Lw0 ,#g=qkOXȎssNňti a~0&J;Gݕ^e-Z\HY[E> YG7m$V(68wKuSW4UV? `XPWD죁RKg*F;eO;0]jkdIENe1EYDc.HܳhkҘ; ‹1&+T[іeX~+u_W5XrC4l֛ M<ًP(v}rTجvo8H_DK=~?7>jt#O)\JqJqUQ,"fJp*DH391,`䱓$ yDmi{rnX>˟ 7lXx.EIZvaK}r2uY͓)<ў}#9cnU޶R%nW.< TDNVc&]z2Y Ѫ F `uu땖8Nٙ_1^W\S%%)'t"m* ,Q RT7a]@!cFb{4Of0%f&_Mtvs"##8Od NNęҲCցY삮HoeiR[+dwIM]8}F[5UX㩱*m]rLE[S&&'þF{{!4xQ69ֆ:ʘ;j"oCbР^7QY eF%bѧPDw-4Ӝ&hJ(ҵFcfMd bv],LfgpG8'vGhG]Lu3ԘeWmhIC" Ro7kJX$5:NѬCMnOPgdr_YJf{:Q|,@gg9VX¡٠KHHQ=b] 6[x֪v%YeJIhԲ1M0_J1E6tnףv2Ƅ5l}#0H̲*aul80ә(b 4UmXf c-#aD3ڬ=]'hq}M;ɨ;W&TS[nV;~zؖ٧ŊΩg4q͊Imv|j"*[eNv;L ;5Gn;"cz1gf[Hk$A% KEaDpjTs_s?I~5ugvKC$`̀""+ @zr,e7ekWq(Zd>OH?#" -x٦DL|g<*fB~:MIp{tWS gGӏOwә͜e}= b>k&)96Q0XE1 CA  LC&&'8ꭥiNq<],`+o>avtqy a:ުtf6HFT 1f?i>}8Mco\xB_ 4*2<]3o`=,î(Rlz:Ν<)Y=N5<.ifDُ}:Xݷg9Tc0Qu:fJ^銳vvУ\\۔PaU+H{JC:J~:U!֠KW=$4Pj4IV\÷\{f:z"tf2g8;EKS9hk S_Ig8[u t2ҁ,Q4df3ISRX**CY&d{&p5|~23ifFۋY}wdxg3\kF )۲Q L*bCX8`bo{EnqĤggMlM{K˶J +?}Iuoo#ꦋ (5);K_Fd"YUSbX6v/S-U T҈ˍk\bO[,ѩ]}٪!33x( sB!-`|fpFzjZI/,/g񕱜i9:~8BtK-?lHl ?&Npn9nlFi3Sr*Zvi5{q.Nfwl?Qgγj7cL.<sqofC9[=dyr{"àE74m:`,qe[ ;L)[({Nu~2:泛"f3qfH%8d\s3nfr,#v[ڬl޲17,IJ -5F 8'µt Kq_c{z2C>GR_U=1ek@^];kZ.aȗt2^F+vERH@2ү-{׾hWﮐK+ﲚ_.RCZgbg̏+YL3df,F>_? ϣ=d|8#GɖSIcJ/&>L#~ 6>LFGvdzGvdd|k.]g%my[=g/_2>K/CyYz3s#>2>L#|<~dd|o͌OUO4}>JG>4=>JC<_=W[}{KbK/F{^T;/B}0=~8;>0}~KG*כU62=>JFV<*ϷO{||oOUoI)?%z4''gNOSY+;'~KO~K{{Jϻ_^`zk_r*ק9>J##Ug>ߒ#?%;FЛ5^ϒ_1>ߒ}|g/woSwO[b2~KFO_,!1 `A"PQ2@B0aqRp?̒I$RI$y$}>$s",Ibŋo,Y "ŋO>5΍D"Q *$WEY 3TȂ6C72.t#&l,bŋ],[e.ȕ̯$I$I?I$dl48!EQ!ER +FWE6UXcOnz!6>g=c`1m%X[|XH*bR 䍲!r9,r&:e؉ = %YIgY&Y bJ4B!*U*4Ff=𿑀ދ-ɖ,(hnEx%A!}.I,X'#e!9$H%hĪ*":+=K  @Ԕ(QbRfU-lv,L,}POf]z$r7VtXYE0YBX1*CĠԕo~oI$Q$D9o T]TljJ?&[!gf=z{}K%Y',Nq.Yb嶸.2ȶ8D˯SFI\hhJ4UEP!~E i%E_BYVU!!Ǯ#lٽ2]fYbr>dXEd[Ebbr7P[''B! JADEPTX+)c)e\}#u!eѻq MُC{%f|02l,bٓ,2̳.vbe.L l}' Ie1ĺ("QƸx'vbsz@T$ :#r5F #䍟8ADpc[dXB%efYbɷe\Yln[krŸS Ld2Q((Ƙ!C4T@T*G0Wds/: hgc|QB>0(,61oKw&$NFI$$I$T]*ʳ*bCQ%\ļ!n$( 7dYDŴXn˟ߛhѣB4A (Li*!ǫ? \|2 AQ*ˑ.#JE(QAJ2x@FSRQ   hٳ}6lM,K,hlN $).(b     .(IDPhԚؘ(b?ևb%Tjx!4xp<DQFLzl(2xVC}!AAAAĸHV5RC!fff,YȳM&],Sz=D_g<&vj9,lB!4?LERk$f|ʕ# *ǪeXȜ.K(,^J%;*"Ke4F] \ȦDeFRFDdFR}Ȓr-؞I&M 3/,yKpQf_ǿ`f͒6,1Igv,bc?E<u}Eľ&>U3 \W,qś,TK=4g~_4YEb,AN&qc H˓ q%і50R,d,e6SLkSF礲Y$/~Պ(PJ5)2XBMȮ!d>O9Cm,vZy- EX%fYeK Ȝ.<(.J*4U HF|_xQ'd%dK2zOI'I$O1Iܓ?#E|I#Q\JUEBEvQ hJH t;}'qzQ Wd2͍2Y6C6}ľO.H"d O|E={џp8>&1SLXbO\dJ1RŊL2$bD"B!A+-OB.rYE$+. {OIdY,K%O~Zד.W-^Nq嫎޸l{ɱDžǓc߇M~y6=y2&\˞9e~2ß-ynn<-Ǖrqܼq嫎ǓczɱDžl{ɱÏ&ǿ<A!1"2AQaq #03pBR`@Pbr4CS?Ug};9|B"ZV\?uȹ +BOs.e8\s֥sW+su_h(\r]vj0\j6+r2jE\rR;u\˘.` _&sι2tZʹ)Zg2 .` sW9\t](\r]Vs2kSOWUξ!W+s.@B\R+U̹ --OSUX!W:]r/ +s.p _Khu\/"Z\r.EVuZe̹ P ϺjZ}MEns |U:]rȹ)Zjh>l#?速e͓'EjZ-?gSd?6O͒͂FERL)Z!B e €҉vfiP~V;߸Ô v r Gd)B2UЋ2ZP!m¸ /pS(g  l.U~L bT('dѫ NWaCk<0ar-)!z s.ʩ)ɩ <_4Jc0&O2AXZspN"JVehBV*+#ԓ>IQ8Q*rn.ġ0$g%+-襭XFFt)NABWrq5* e8L@9D RNW+wBѰޜ 8N!rKƉdZN!ה مqVT+Ek( !MX #fF D`B֮Eu+8JjvsP%ɦ$t&Ɓ!J+ =ӁJ7 IN<鄅c;MWbu9Tkwb~ vITUQt&~j!QvF߈xAg*z! BuJ*D?*q{1vW462iDH'npP%=2NNT.61Eh?@PGp*⠼QzW%A8VC1 "NT5K$TAlIW{Qa1xsANC4Ȉ 蘔)D'8no2v@S'RtMtju{Q?Pө[t ~S#T]Ԫ`>,ꡃ1*:~qhW5Swua5>(1H7 B&au:-BtPf݊X5E.[*AԝG]Ay VdUŪvIZu62TBq Sjj v]&vL{[UW RJ0ʀLi8FʲƢd+nmfS*ph ANEOv J5WpiĬԓSs_{ wTʇSnJLkLSg1,N #s qU9菳 Nf8 \  Æ^ƥEѨ%o:!x-?uK,Lu4OƅK0UjU^]۴U[ثJ{-{#U-(N(28@6x'TBj ȏSRrʴ84QyP&pmPAh1.v8! !j{kyPhW$h4(}KK0 hHO!T.w2.6id+XݘS+T\aMs!/RJVgEm_aڔxm6JYS&UkN%[tLtUP迤h*L.:ypSÎ2D 1䫟=:¬9Jkq['lToj(4BZc+)K뢞^1:Sm2 +Ek؍›VZ +Zl.%}h $( &1IV]J1⛒#qd9˄.CE{UAS%NJnkb4@5U=nwUDWcX]O|j ĭ3t[׿m^Uz8!_Ux!3>Q{[u^1XV {ΉţDПM͒_* a3c(2+&BrqA:"(SvuncDE;M5 L@(:Ns8l._WSv|R6 ҹ Y U/q!AnSU=w%\ፂīOesA0a`Tr .2Sd+j.5㳜9s̉WNVFB~Kd Q;(*.8 q x¸Hz^4NeK2er׊"L#0J!4\ѕ-m+yˮ_tSsZ-Shԣx`s|)YR`m'UH}yO}:?]*n?G˔]`,(.ʃ=~bHR Wy(J~^vnSl\.Fx!U~)R3; + Np:ɏp2eДy*we8iZ! wxQ? >; LJ4TBv^Ɍ #4(#`@ʽڮkbQ F]>Uєj84¿]j!Qi\ wbuM7Lǩ2 \eKpe^-2 7NeB.%Fese4־S#a(PNc!j=-TU'0\4jȍaVq!O_e[̧,l']Jux.I@*z4*2 .ʏ7-se86^p=>;`W#eBUeӺ z*v]r"-:rqkI]k@\ jpp ?T;V`o08W:@JBcsRHT-J!g8Pd@ x#R!SQӹlwx!MRcMuOvq|"cUiUv5SZUp~"hGoAy iיW#,e0_=TR8W3OdʅCbJ~ZS`;ԲtMG=phsOӮuS&T('T.q;8 ]Q;7x M8Uӛ$fU+((46֣DD g&ʓvWԜhd\'*Ѯ[ ;jۍ5fwa6K{*$uUw@.dB*n05b]k[sitY?V ʦ z_dm,(-ʗ4R Z ]S!s(6NkB֘.^Ն>FAAM3¦( C4rMP) '愯ON )Ҝ!=`Cbap06}(U99-+%E`ˉC:BSz Eup\z ݅kܛ sT{Sm82QtDy0`l4ane1zecRj|0\򮖕[ ֋@8޸Ri˔e' Hphö\_T6atk IDoPXDWeC]0%q46XY&(慩5OTfքtFpV56(Ӫ"O4rcsalx ԉrB h,d_ly$B/ ;.FZ6BGtC:M:3/SiūwEL'uFZnz/j!ʝ*tԫNȏ_hY+q+.'+\O*.2vp.e[Pt$m{(fsd(Jf|ӝaCBt,~& (7u &=7prP ָ-S n`=T+T% g YĪnoTfWpœèCmQb"6mi(^`22nZzL ɕu3Fj*wh8CٜIdnW]%`&U;) ĩcIEoEN:'JlMT \0))6@r .ET`9N'@'kJ꬙B]‹(i:jxʥ-Ut@quS(UA*@pe Y |$of<Q GOv`USñ&WW#Q~Fxԓq7MfFpa1wgw5z|>* - C0Z #ֵ(WFj}hhU֘lk䟔D9:P_DZQjj'F cZ6豤,(%zeݓCB )O)bL1\C:wD" H'W6O:-ɤu),U)ymG۴ot4IO4I%cwhۇf}qNؐJP B-=6@zd얀NԆ+o B^L(uBB $((kн&8DjH.B`jsR1{ĝ!OL%0,h[4]!V6)dJa@5o^]ř~w 4kFq]6%Q Ԯȣr¹ ҸM0T+߀JvQtNʜP'N P,qüj ּ'6*ۄ"q;t ={ƪvT\-lU$'UBUĄGr0Mx `g5'P,la!EA='_*N kTLDL&:kgc#MJ<_ZON1ę}gI­HTJYA!đ5)ikT0Jm2GP6Ӽd DHsS{Z#D4 q4"6cn:I@9sz 1oR 0-:ku^ҡtMu"U;ҸjsɄy*? :$_Sxǚ-Eֶ;"ֱ ]ʴAtmfGR_9*ӮЕ{a:@c`e1T)FGD3V)TdG yԨ\ :eRT`G9U1OE\CϏ"j򵹒f&&/PkuLW;eB{aWiaQR*o, 3*Q{ZmgWΧ^u^ Ϭ2YUOщ"|7xXm=RzJm0QVAZ{ּ5A_xpNp w>%Zᝰ66GRÎw}Խ%|B/FZ@@;xT@zvpBr DcAT&J.-O6r{`S`Ɵ\$,s$- Vd.0s8):&fl |;%U)ХWӟD:VI 'Gxrmk;Qph'/Iy~ G#$BU9 tE}Z *L{~47- *źe ]S!o*CΊex[A3Nݻp^ nB#*/OL49P~g^$3Xp!^$lziGхmd[MPyQqO{oRd@Z-oi84Vt@qcq)jLm 7uJuIТx3=J#PXs⎶ eļzO"^<4F.`?dט*oӢoJ. b{ˀlAR*+!CA%e"ꔄt r[B.y^ͪ,*<dk ^+/"^BuWZ{%ϕ$Gk>JIHV&AV<ԇ\ Gbk4RZ-ZN,Z`x~HJsᝀ'r&Q I 4 kBmultXi,e~޾ _ñ$(uGX(^݊3:`j* ?\Gy>ga yJ0;y\quL#ckbz&"/t-FAJ'O8LA46r%XX6 AWnwu^OdƵ+ot l"d<6[Eu)j$ɬH1>'S9\sz+uU.wPuI twW6ꏙʿys )wDU7aJ%=xUSjmVj:W> 3s34Ԭʝ/t!jMO(V:!{:l{@V ҤHG+PR"H E7]pTq9PjhqW':iQzW>(1Z!)Qt<Ǩa@ L"DvE=:ص)0ʣ︙(&uTpQsJpe39Ki la+IE(c` QCI3hv8n5˧z{Vh &"s钋H$} |v5E(\M:REYeYePBg?7kLH>;'G?[a; Xe/JR,Q}xw 剹ü!_ۣA?_$:N 'xýЂӓMzJR,w:]΂(O;ŷ %} w-ށ!"'O+v**V =JRQE;QCV`QeyX2_Qfp--_42NYY~h)Ѣz!:Bt"'D!!N!:!:!B! >nO^+#gt ֱ{,N:c=Н'Y:A"cȾ_n8` t;3n^hm^ ֢ &n+M/>Cuhj{/)JR)K&mUz/zuL/])J7ѱ}8աCzBO+cKҗܥ/JR)JRRKo]zgo>}W/Hd]G= I2+跳RP B22?j)JR)z^)z+E)JRuoOK}8,TG0FGe=;\x,)F󡨚hW4Mi14\P1x74X}^m}y] E1TY||W^BT)JR)JRKEz{A izE[>7> ̳czk Hzfi)bVkF&PcS+\rc[;a0ȉ.n6J.fؼW-<h.]_T+XVƂձ_ԩCLnCƑH֨kHrjMܐr\:dq8+MJ)ڪ0#ձ [pEvA'^,(eYEQ>)JR)K{QEYeI$: R}5`j?c><{ u#4cmbmh }hʠ }8Gp.%;4bk1?9b!$h(o׀S5`w1\dף!t+aاe?"Me8޻zKZʞ񌑬Bnjd t.c}Jf,DD]=( $=/A=%Fdɓ&zR)JR4ߢ,+ HDSM>F,mM蝢SƦ, ;Kbycѭp6h Ym$)9IϫG6"So`8n(;]b=dv}rt2dɞ)JR袋OEAz*)l"0 7"m, &^MV| iMLHIM=KMh]b{qc_,Vv|l= d-#>%E1ZVx)!6⺡ŽVHTJ*cGɪ "n!.IdicCɨ-dug$p-w8[U@{ӝrk-hw7j)KҔ)JR/K"pv:]Ci`N݃v3 ɓ=)JRJ^u,ǸD I; n2uC]o;ѝRFd8Z~DPKW.ؠDfD _wթkشjoVIP]Kx72NDdX9 eQe[Z=dpn}ybV(qC=O16x$uk{٨Xʟ#oO{4SWӹjfB& V2ϒuRڈl\)}*%C2.ΆΤlؓeCP]4BF5qjRU%PʜS7DBB9=*ʛK}TRJRR)JR)J^1DvN;G`7O,g\üwG읾wpwS/NEdBaz&Cɍ'T!`;#xfM>Ʀ_ vcikJŴ͒<Ӷ$c[T˸hIW*7&3bnA!-`Cav)?Rb6Vl9vOK&MУ-ٙyoo`:gVg.ǿl9 Ч`2zG-ɺy+ C$"q,b;4cl?>P2 GLnET%k7HvC+=Ƶ!x4Iz!A 8m]%z3C7-`bn;>MFiިjҾ:0H4MXHN>/(Q}t)JR)z)JS px;u薴m> ?_N]4BexOɔZgb S2% 1EA4In9p)UTO%ĚdïaT2u.B/d5?N5=JRFZ33WfEMh*DTx⦢'D5ey&%ІErXI !SI3wKVb"?Cj d1Adu=^. ~z-BOl8poJ׈ghIy4t\_)#9$5R9h{x7Xͽd'#6u|+՟cM4fyѶMKD!"pG&MrqGma sj3 װ/hga>wS"B 'k2QOͱV)LN;!sRRƘ!`h7<)+aӣ*zg Tl$z葉 qYVul[ ;~sn*x J=Oml}(o,_oStnl+ &OXǬ *Kq۱RXKo}|lr}upXh\R7eͫ_ȏ`G-1 -m:{L ϢuGk8PFuspk KQ1j]yaѿc6|ܐc} T'ʣ2 L$99+Fޥ;6\:=ж,)n{Z>,*mAuLoț7uB Vlt[JK/"h"6"O2'kaH@_FQ?4wС,a k2Q+gl K 6ifՉ|>5Efj_iګ=EDYtbzZ <̐žGtO L2˂W@$`H27e]7Jk!%1Zp1ZWNv)>3mw.PkKsR={[QjN7Xq$&/ lM?avoY{`#m%32]֤Zowe۶ jT/$KDڪch˟s"ty|VxĤ[S]k= q%"HnDQC O Hgmc&;D^|E6hn̶]aZ|YT^Z,JF[^VGbs %t1 XAEOG !w1VVh Xqp"MM G&% NeeZjoؘJ6j㦔G2'] 8$LXz3=*pN#0`="$d={WԾ.#Z}㖂*%Oc}WYk.*GY[74M#E0jK5jLЙl8^$Gb.D38Ž;M;)zF E] }VRoݚ1&!EԻ< #l'}NGm9-:N Wz-j!^}K~K{TGpNA"liA8LBAIݪkZ'.]ndvWhNfWRcSbEI%J3Sb1#ri4gKwR0i:]Fi jKؚk;iBǫ?\OE4g۽//J^kO{t^'/1%`q_ ekOIh,Ed ̓|t)fecY0N3^š2>]DJ̕ojPkb z5l6床M^5J&+YVYSdɍsNĭFC%ȫhJgt\ڐF c2!۬"=SmlxZh!}!_!┭$trN_\+ђCRѲ] 8"ג\k+o qR-~f2$ v(ZIm3Uv564~ѫ/(h%DN֒ˣiC"JifAΗ}7RK)J:'2z{h:! =t'Y:f68jjrI\VAaGͲ; k,{ ⴹUXvޯ}rc"Ƥk8M}4~K<ԛxcbl@*> $=Ro:Nj,dBL+pϻ77/D‡_EI$Kccjil`( p4ymISiitmk7 M6H;dh}vG,GsG_~8F/qK\mDw!/8R >n1GY| A,i}/Z^/ agfGuoЛN b!]ܛ <^/K? j[` =P)@v;;= W$}9׾Lv0~Y1*<rQeDZ]]2(mM!&IVgG+7?ԡ!=z/Kb{ˮQp˱wqǣ𮢺:f,}?2?0oUo/{|zFB,op%b3 rJR+0*ZM>nutE]~MKesRv+6%63+_2:%qI[A[*6A5R[G!G$ie^[fy}t'P#.; sBw$K+pW[OcB;"5αWŋՏ~RB$i9c Y;k")V)FRywb#I'IKRxKCKRt*Zy B"r߅^LYh?8J&.2|%}蕁T՗LK wnm5A I̗uk=K63J9d 3nnį: FĴȝ&0еLصՌ#<=|1؟_%Po(],؏',)x,A7 [>Z[0.})LPe2/HRfuq5!2nz{Ǯ'>eL'3.GvtG'1]mJ!~L\ T%lוhnxgU|#{wʋfQt_>Hj8~!ԼU>ƚn&NUF!J[B .#W8!/*6I+2ȼI΍5ڛWvzzo&uj@\u5/7X0Z8(֌w{5dnyi#*C~H~ZXf3ÏK]2G(+6TCx!ݛ˴4)uWAK toJ\ $rZ#ЩNx' ]h".8BYD:5>fnnͣ/]w5^ Z)vd\ra9Dԯ#s|$~xf_Q%u| v{!P<6KcЙ&= }&iByLcȒUL98Cō)3]FTԏpSI;zX#\t(s7+? /4]G!ˊ74c z'= k_i} wq`KCIkF5WOA``i#.Gft #ޡ$kvO(k<q`hˇѬ3[Ueʊ1e9TR!]3zg43O(Bv,OpOOP]ä!mc4ǣoVީˊ5O|%n]8m&!d.̳!SI1j5I5)jBlj?w%6Qw5@acSc̹|ӏK m7放_ѐ5˿_? 6gv1)‚QD˰[JPDc-qMFF-v5/ t-̾=>&%9iB CƨodFr~ 1\|5K=p}_&hv8ۤ]+Fz&X9dF7t3"t; W 3+㷑 }s)8TnF'PGmE4q†wю\!{[EmĊ 2WT؁%ge(_~)A2l/[d=EM*kb߮BUrKmML*6[dyJxb [Uw]A' 1fT}Ypݏ, mlRןZJ'ȭQ#P7S첮&-U屢WۤHWvlw-bI7(͇b f GH^Cà3ۋ- J8]j=9bgg7,jeٯ0z6WULXoֻs _. MFcQ;MW<49]4 "49ۢ\^|1MEߪ5Oo! _7)b8& j >/0z^F>\ Kbmi`(є5^^N58/'Eq)I BGF9C]1Z-D"EbݔExt&uG^DҴJQ/8%dYJ(kqh-52['^eoA3GZ-O"Kװj26;LTVq]cW^oE>q& c=\eb-%;-ʈis&ߞ4?'yQe+pZ.dI;&Θ3V%)oEV"E TѤw@Zu2TmIjzg(t Io%ngo,ܵ/>5 MHpWXYmhP:zߊ/}_~GHDypїcYa}[ hBv""!U<86(`N.D0V'_FӻG{%|tPO4Q{xwr:݆/ݏrz?\'Q\ЋQ:noA-3C0<"a~ʚ?9b\Nvdkq,_l:w0%;)mB yrwv?;0/e}:es6ޝ]?";Tz#KtI~pH7%255m3y2=诱73d' GI}T6OJ۽*/_ӏcjk _%&X&܎t &"t)'ߓfdFh:ȒTd,C]'7z ?#Xa%Bv[m;?#4Gy0/3˂ s{ZF{W~9'1ni}wi ]L;L3] v v&-bhcgӟ J4:Y}olcN jjۚ:: 77sH.K,y> DկY5_j@0hQQ;:s=%ZPŌ;g6dȑz~ɐ$ű)=o:Ohma8&:ɖ`|-~YCZȗ 7ߚ4,5"EjRSͣe!=h1/VJU! bVx>cLMye2O9.gcCe\p-0åLp'X7$c`j\. EjyӑoJRvJg(4';5<d$I*!H AcΑVQBzkh#zޗLg߿Nw*a e2;I(@?¼Ѕ;Wr} 'u:/<\nKAY[( k9ΦEhuȂ2-x63[ J!j!4&V[Z!s49R"U wif:1MĴ|d57+ࡎjjhƛEU~'ҢcA%(j'yYo_`s %JvU5ђ0Ğ7BkJdgy汙#,ip~amؽZFcfJ=_aDz(k4s,^m%诬hۗhDR:'M)0Ƭ!4ʚlh,y,#S?uRU Nk\7'VӃsM-< P؏'XFܜy4M'U]̦Y5YCFs=+]n\l$F_&_P/}L^a<դ#cAkf)y@*u*XI^ >T4$b2qҏCܨ-W9~M?չ)l;q5?4&O}.H5|pTнCCxč5Kj&329Rs0n\1Y3OY|㦃Ej%*4l {ܲhb$X5MT2oZGdVkSԸ!MD3h좩FpUqql`MDsvx >P͒^G˫x-'†úM69<ƢIU5)z Ů+1nQ]@#JXבRyȦR!-/G& mc\V;Zqu/k#Ԗ'_ hZWOXLdY|hC%ąf rBKW5K܂CұIr/gDxtqys?ꄍ>Yd܍71TYa*=QLQݺ^'~mQ)ޥ)( xG|*$W? .Je2!h`kkF~Wxl]uJelD͑6i2+}pm,M^IQlM+=qF5b^h-lK䝆v&I/_ԊMRdYÌK<~{5\!6güq_FF0WqAYuXL>]XMѥ3褔"=Yy@OVǏ;۲qa.ULE>MƗT V7Ta+f)ɯRO[4Ʊ+)&Gk8< "oE4cN[&m0%?R']Jdq;i~_ g"xl_3>qD3IOwOe}ִ2n(vM䂲sMFF&):IEmXt՟7F^Eg &tw?ŏZ4B׬4a~$6^؃Mjg'o?1[3̉C2sN2%,&$j#C˲"C&22 U8V/$Ӱ"ABcoBTUgQ7zgԡ]q3]ߤgj:_&[8 a>It/\B!ax9M܇Dts쨾FG|v)Eb][G_fcsQb;>lA>[7pMh%E^thoJQ[TЯrRYt,jő WZR0oL|IlbiFsZSZ"蓂ܔ&j> Y!$g=戒1-6h  _15&=#+bK+3ҫ +ȷ1si0^ip4SM^[#&EWR cxCf_x!uhH^! -+cR!zm$I;R̚ObӍ;oxM׺F>4EFmH{22ւsͭoSF*%M2_*Z/4}}e0!k&87`9fKϱn5*ipvjH7=L]a42ɻIQL2#ģ_VrO#z8^PFvbMT7߃甭xȲ z1w My\KHJ?\kZduL虣? Dkk;.pQQ@$ܱF"c&H*T <,$Ew3a&"ta_j8-9q 6>'HNzBz%ϗ{^h3mDithCGƞq /#l~h!wt_a62V)\v\F0[&&10aA5wωGh^prQowpT$5P^ Ypo< :溑F\>:&1 fN篁&.c%PDW/ Y~-F^`)b$eQ'ovU<PYw-#8h>1Yy5Sl Pٿ b~z}~ 23=grTl)o#R! Nc=(K;}D$N(~O:e e簊ʥw9861?߱7!NG.h'Iֱ;~ϙ_k@_1>o}LNw:.z'>{Bַ6>Gъ|=3 ,~^M?^Q쿈L/lEeІe̴ 4e#o|~ӯRצ47cwUʣQ ~[zeb-*㋤2|τoc\'f8mA~XILRa,E)AȞkeύǿ=O٬ \}ju=\kGfG~ɿ}֯hS/ݏb{s}az)}o?/ q>>~?E3W~O޿ٖM}Cī5-wPrI>.{S'Wɵ'Te?ܷ3Df#eq~ q-Lq_G|G~~^5? 4Gaso)}t_W{k[g/{i9=}Wnj~'>ڸK+VNDr9׮~O/TO}d׷?3D=_^Ml&^j| vf.`'gz |p\f/'f?ܤZFV~%#`htB4}B_!=5+[o@?ۛQtz#G,S?[S]?9{ru5Gq=я_ =: TM?o=}t$b8>S08<4n0 ; -( * _}x6}r x 1 y+kA6٩)|j,-ら:#A aa>5uvAq 10}}sI_mt%J.M=*8v (# ~ ?<0@"K0=m4Aߺ}M}o</O?B P}8l; )>4_{5qI,<0i2;w??;zNsi/8X!YDE I&]Ծȟ,= a\mQ7̶8( <"8=?;9 FCƓ0#6itQ&P,)g b8T G_چ8AIv3b fok "M)%0I&ҳ]0L1lBd r Maǿq@q%2eTuq0]@p<2b8=|45C h@AM'o5Aw{c<0 lўrAm\xA0H JdKa H(/I맼DֶYxӋ82<,BK0 C"{Qqڰ76_W/G14* Idzll\aJ* e@x4{p8 s4؊߭DD1d}<8NLp<֏eIܮ j5rF;0٦2w%j,@< T[$ʱ?X-[5=Y30lL]Ӱ Мޛ鷕QfAM_Wӭ $#4aO{ oW9(*AAy'Grr\4b=2kTzUѕM^'iwcT]8;IqXK|OVW hYu&}|k{`F4Ciלugzz4VqDSe[]01/ <}FRP#"0ɭMcx0O5ũr'lNzת홶 @Ox`O,(`BC}qsuI@[DCq62 iE8QVMG crqFܵ:Z#۪@ uP^gGWVԹ6V5MD'0a4Sq]0҄L,ɍQvy H M'_̦[,cbiQG 4iΤq07a@C wU]5AeG߆+ cUmr 0N{7w<]_$OhWUd"o 8A uOuR}Puo*Z]BλѼ`Sn.0$H ɧ]eH#6VM Mu}aד}Z}uL1Jk 4[OMB;ѣT@:8!ѕXf h^^-C }Qe4Vehw Bd.G`%j9_xvjL>߮ᴊƯTK?wYE OAvYsݳ{5ױѹ 7r;~x9L-aw[n9؎6 D]# 2$WY.q qVu$[U3)Ņ,}O1_^)I QN4fM#odK9دAKnCyH4"8̲I%\vaVe@}M s Ş0L/{p@15Q'/:R qӹ3i a (THc~8kC|y<b00e,1@U}q6l1FP@,~ӈOz5cD2~17 -YEۘZ{-h6n jc{NMߊo#[`Os߰L~uYUݒ\kdwA{,Rw sjjt |5yOe 99||:6=@ϣm"K,篿w~^a;%$EX$QZJO l4f7[[Ó0qCANT0À# M th?eX}9{G:cag;hP<9\C gL6ށ?yu9ZG! OGAOm~l:~^rMEgނEZAǝy%L[p#Bsf\q[:8L,@b&4 8wQBz4-TqDЖx#91Q#5=,^'c0q-{721;RQO2¹!00hW<4q)ހrxM%18_:}mvH.Es}/|v7Wިg0}5!̖l.0PϦEёkC=r רZo M?o0wisW ^0.S>nr d{߳O2I,G vO-' ^yvxҿ7<%:,_ong]s`4(eIg=?3MDUoKj] yg^tr=-7=|O6*sݦ0u.J c$9- -,@u =DDzZ[tđU536%AL?l4=gg}!_sNv70l30c8BGs#.cߕlu~AVA%^y˳qji~D&h{(jiAS[ =>YE>$:=,H;qvQe˚17y RBr (}o A`۱pS0{˅{yIȖ \WLP}4k|" :Tb8s pmV ёeQVL1}e -DqyCNz4=dL0e)| \8Jsi"@Lss쾻($ (tP@.˯u4۸f|uq5P{ Q`8 EL1K垰se^ȲZ 923 %Tqz3 yo]$&Tq&%E8"w$߼~ۮ!4>=~L ??6 /  6c̱{7ǿi 3-ǓLy90`j̓꫄:=s6,?qA3I$t4ro >y$ {r Y߼>f-Pz I€vl}7PC_PoUƒ-5q,nf`(%lk,HOrqc h\ |K(Wu[2F}C'?p8L#)<4p7}ܽsqÌgPC-46_ vT7N9RbE=풁ϓ[=(7(rqX>>2@f+<@2 klHE;.@eI*?J[E~.lqެo|-BmN }7øъ>Ω`m/MpUb0AڠI{|8Fj翲dw?1:p간߅F'B Ol|tg8ku}^"z@+޷iߪL!߹nX~DIv!n6o kFQ L <Ô7."[A"=58覊˯ʬ)oԘڐ0cCipKD-'3se&p5\$bAorp'Y[7iK % k<'NMyj UQۃwn\Qd-s4sBf]WY5$R󾸧 ilzp^oJRnC]q7s-ψ%Q3O8F=BR o.]~hw(mpȧ oZ)E_˯-uG0Tw0Y7XAtmG~E<O''H8i#i-aI%Q:vJ!iy4PM H0N"fHw)JR ߡѤƤrnC VrW% H2& )0IQEYd>R5(s!=(>!bSq4dJCh4Bo>G)ěI pHJ #穠(=HmPRL{ $+| bIlI9 Jx 1D_= liIa$C7)4Hr60c)p{yCЙPMb4SX|4LtZ@ш S>Fґ^ A' (A ^=`ͥi=5m˹%:VvJP|t%DU/ zTڨ:XS")zl;;2֐}%+hH׉!"CI#"+Oؼn/$ţ릑s,d~F&E"Z|3dlx Y[< ?>wWʕ $#jj耇y4W$Xۑ Qx ' Y AYZ1SWc5fX!CJ ؛mU!Au)Q&k+>s } Ջ<&H[]) KDD%a Dӣ_4K ZRUBcԄ5.A# W%LQPnQ4} ONMQ_z!ࣹbj7GYC]++» VEBv4D&F6L#@[!͍ s >uEF6apksRN,>Bu_U:SyF^H RH|LNM- i! E}XܢKؘ'IǶ ҸE;B6M SD@ĝ[Mhص; JM"CpiZ7bXX5.5BkU䎲A0|n"n!iB8GX0f;],k#(աfꍛ!]LtwFrW%eeYE`X2+TJWqĂ7nz0"׬b}2W\ Xz4Ɣ.K։]瑣BEa""CHABK%I E6/=v!/ԶiQvӽdRa<T`kbN6/Vte{ߚXBx 8 Ls$&-m Lz Kɳ+BܚuԂ9J"^yz!: ITJ-|#87 hdO%DDLNr_p|"iIIʍ|,4d%0BLiK/oK;:J\/N佴}3%)z4S:i}=gWg^7RٚgK]=Q)Kн/{>izirRBRyyJ/P7-K.4)K[{9izw{ԥ^/a;JR};J~.j\Խ{S.i^/ߚ_OsڷwL/SޕPwwKѾ~zۉվuZ^ /I>_[pw&JR[ޝ/oO^{V[Rl}zνͱ;ܯ|۝}w3Op{?U=z 'ݖ` .wo]s𷽙&e{y!ϻr?^5;f]) !1Q0@AaPq`p?! I$ # ,(iB I 2 ,(v0D!bOJEBvW ^/DB rfQDdd#'Owi{;D"""" ǞeK)qKJR)J\oНOx6uҥ6xJ\l~Z]en,ĖМ=G+> !B'JcB~:\䀉IZ Mj4zPQZ Nԁ=F S= lYE`>BB1d'-v3DM nL.%$FhRЉ!-%=HXB#F=QE!LFNŞKqR9]$ȭm"D@?  lj!y5AB=-$w^ E4>d=D ei -#Z3B>bA$N_,#! Ғ J7. σVH=CdF(?D&DZ˕᧴ A nj4{v=,Zj/k4rkQKYzI-5F [ְK2IXqhzKI`0/yBa+"4Q0%FWnxE9 Q}ظX|"nC`dL~ia݆4B`W]X+-D#ąs65xWOpиK Ĥ;MLge] tI!#HePaZL6*B8vaw)v6JЊ/nDDщT>!c:E-鵣PJڤbcDY1JMq6CRhZ\t&CEiVMma-הEN䶷52] b Jhx"TA~A!b47uQ $pZj)؆"M-C^]Gg U3Ss%:!&4V-H P*b45bl.yTJ ͏ʟbtq~0Q&-D 4ver&#zf5 v{D6>% q 0!; ̾Y٠rcImAQ!&h5 WQȺ SoTDѻ[wE u4j mP,]-/" JWEn tj*1=CgBwpHK1v.jRl{ĥcbLAYE_ѱHbenԂZo^va>KpUٍzIlJ*@|M 22LRmG)~6gE` ձ =iF"&hE )!5d^jEZ_}yҕ5DIx  !*d/VC88"KV6 BR5atߨm|Ok>KxSfjQ%n{ nN& Гi A:Е]7ޜi/Y.}(Ę4LBQ%j$+c4ZQ57GЪHu4m1#I0 _&9[r BdC Єflh=X,M KE5J5JH0v jfS xNu[ƋU ǒ) !%B& ׁT4SbZ7B&LH\!F!F߲!'M%5 89zEq^x|HE{?HZuCAj/@*,hnIࢦ>V1 &6< [}F5ZByHpDMǢc"l*7 V%i#&ƤTM/ Z-pB!B##yk|2 4a2vRCV/E=cJA15`ҥ/!4MkdPQ[^hK}ѶHSrWQ%v9RLH4\l{ҷ~|([{wi7Li6+57hLҿF{#p:n `d1\Zy<&1@Vb{~ѬAPz1 5RE  G+.,#T5r"" Q^XLg}F˅)4!u|bnX؛Gț{f (!%EG$DU"8OfieBa0B3&5o[A QH=7V2 ȐNa2Bu&ܫ{B፶ + !e<ᴌߨp [rnCֵv4\Fubx-?mnBtT,E>̩]%A5nhHUPkLp0I5a3h{a= ўycB D6JɌwMIU;fcuY,Ba:vS$&kItW~u %!!"LfY fc.—.u"a3!.:I` !vs.t%.;cdLWeKӐ1L&a ՘<_n]Ta>aL! }Uc;m:2N=W@0 3>w+t,ۅNB!Lͻ%Nt|6`ϑxس-аB\o[YDf9Bw#tv;ϑc's>R3 })6vjw0Ne xLa5> |c;7םʄ>^c2! uMF_&xL4!: !>*C>'a>o>"e'a:]=|&YΆB|'Ioc" e[,?73N&UOYw5/Nu,w0;hLY. '೩2g[/m:3ٝ3M}gFvCom})!1A Qaq0@P?\ lcv#䭄2V661la9=lXu/GCB3?Q7gM==>OoRRlz}!?BXM3,,>[|V?twmoe)mڌaZRZ!C=yz;T`GMm Ϋ`t]? fހT}5{ʟeOG9?[@ ?}Ged',l7OHo #?.[ovƼ$<տ !a2V.uh#O7VaQG:Yݘ!16Х<~:]G?i$_kOk$X{?=uPJO#'P0 /7> iiC*ޟV^,l,cm?Rqn[:1wmxx7yXۻm6[VaCp'c_(2Xn",T`NCs Ǎ8>/u{i{K}=p%?`)mum86q%φ3>G p.wvφ/[e9Ἴow;g&YaMCx;~V7tO(pND$\ ."5#b0#.,P_ݳ'^/ }7'Gh/?7+Q =WWOt!#-}848ߞ?-m;,N $No',cv6<e~+m[101 3 HS.8 &VQ26~j?#]@_v h{)wX~ԏH6.f0 !->YߞSC DDAwuVXH!eYeH#dI? ,8,d|7 sixmmmJ],+\}-r#tWWP&(xRie'g+ByӒvѭpJ0,_0dÛ!ݭj)0|ͅO!K~yϻ9ߋ34gIq%퓌x8'PI6Yi'Hcw^XY'8]| .wp~[<6mZ~lz`}Z1uݨ-) P&c>%~ }t>&Dž?@yJGH~`?q#x*x_/B{KOKDé8H,/VY#e``e;g\M'HE%HYIadYIYHeAwgO.,˽>9.|77xߙ2°7_vCBzC0 Pz7U& k\ߤGSE+^-GXã=&?#z?xؗ#!Q{WXT竬1 ᐌ $ ddfYٿ $m%!YXl,8K,,23, ,njl;,N3,φqb mms~=^!Ƨ[nemk 6H=-qqH2 F9QG[%jGq]͇Hz1l 6㼒Y!d69erXae; $H,,IJI2, 8CH, $8,B՝Y%YdAYg>)gz%~|/ o C3o< R[eNvǯ[l@SaD2qq' xvN3CnqIY`%2|sx!a#pY=gV ,= HvYd 6YAY" gL , ,! .Uσ'ՇV7>sy'7vLs~9`uܰAXp7 ,xdpupqwlo 9.ܝH`$Bnjb1!,,~1Gp"GO瑳4DVLPYdNt-qYb3_MB$U8l˴a`H=BX6wb̔Xv|XgNr wm.|t<Y;l|6u!4R$a<Y7sR!x|ݖKoV;^7w^zKcY8 X~K5p9poؐl&86XIa$!dvIDL8f&ng^A#`EȬ !m\bnevpc6HE$,Y,,gĒcd<$<-|wƶ<w/xwuVL!&N :JS!+pZr'Wݦd tͼ Ô&;mZ|u[ls-{mw={IY{'&Nd o!0$G.npYieMB@"ۍ.#661' ;xe"'bi#"%ZH$M@aeq s<j~v3$'."ccx;eqci[KYќdnmP+l+g49,[&^ ,{ZtZmKAc+yZw6ovBrH{l 핰9 XDo)6$Ba c&B@~)lHTq{o倉:D[i%f]"qf2p`șdNl6I-1l`vqg3g.ZlY %a@ycexbf8kf<+o`Cv-qYa2/Xqg8ӗBN٢lK{xۤ2oQLwnB°nd0X3hI/0 "[ G&R<Լ66{Hi'gvxaM'fM^3Yl'5Vܵcw-Գ.;vvsHdD<1\CuǶy02#)$n{|[Vُc $x :4c!acuSLJldmY@ƭF"p!`Y3Ď:Y$qe(m{d00ea NÀN;5,\gfT냛[ggYu[Irp6!˓ǹMƐ]Z ]&v]n&̝Gg8{'58& #'.՞[ln[6ߎ܏؜1NY/R;##ml{lmK V$zFYKw3V T%d7zOD}IGtMNdvq)!'VX< ;)]u:{4l2`/yˡ>:fz$8  б`OㆪXa~i)[ʓl&DdXceg.[qؚvymqaz8ѰG ɀds[3 HrH7 Hqd5xvո ^1X^t :0;$F7/ 5$p#gYB`aM1>iӎXw0 Gv3JI=2& \,d;CH-v6P5 mv똶LH 6,w]׻̱-Ŧ,:[-uzij r'@:[  h$Y#Z k-ն+:ۻR ,6x#x9wvnt^>%'g/828  d8Z]Jg!v8qjjq|jCv?HmdaTdsv9Bld[B^sm+c iԨUeB\|lY2a]`ir-";(K |u5Ha۵YKISI63*9oNN 6S0l" G;cmqvqx[=|ǫf7V;,mۻ#-t;38-#YA/Y#*^۴-4؛{ǮfQn6I 5ؔ:%N\d.'R.F7c'wrNb2 *XEfp2dVD#[c elhIǶIQSA`164ϩOھѵ+` 9dϋgs jT|2[j{pŋlX[l^9 jޝ5d)BPl,nqcK""XeOl@B, ;ӬKcXrh౳#z4<ɖ;c+ZlafvVX A.7yƻ-weoMKVV1؉1=d N7aѝ,&P HOPk֏N`JF , >?z>'Db$-Bd@'۸B߷}od~9t抽 ]0īH(4oVZ pT8@o=./Gwit',ǧ[zi&P04>(>ů@K\qP}K^owBG~}KmOWa~O~><u 1#9D;|zml9P>cW +o0m}ķzd Jnl<;FCF-1$ue)m[ WXnX!usaU hg$r՝Kbpr=p t>5ٷKC@RxBgH-Kv}r!a0H' ߘ}н!=3-?!v@IlW'B޸{yOV7֧? LHc×8l&Řyf|~[ݛ#[n=ff1zdFSi+ozdQ!#3Hm%7IeOb$\I*`"-e[,&CM;`@"S ܇$zzO-?Ol|pT T=>_Tb"6h%Gd$ cYbRĈn@Z) GHMy "o҃rO"GiP}NEoDN`ֺ^Ǝ'ӨnWW@U72_vM|%-۫Hm^7aat[ 0 mΙ!g~q]^/t>wkV8z?Y?I>58Q%=ʘa.~sxi.M/M~-µώ&]gﻢmpz.хã!T;f?PM. ~^ִź9{'cTt)ksU>Y#P7k ?~r7E[2?{*8mHm!Ŧ Ű[mld0m-qqx^EraVm2_~'OR`Jy~S~^9-=菢"a?IzϪ{N7kkkVߑ ,#ʱe| X6ng`x }QR+zg}xoahK5Sߥ~^}e'C`C&2멻ӃDL X2t|R?M=oݕ~g#;DWE a$}[~6e:L̼Dwx39tŭI&CFr ;؈bw#Yd@7͡1u񕎝'ō9YC0qzu ~zO b13/pɿ+v@tG]~_?Wxyٲ缚|(^ݶGզ ??|@=HSGۿwth0þoпV!suܒx{]KV~"6Sps`v9ζ Ol!CgQKMx},C+zm7#RptDCM|W7zilo1lp0Ƴ<y-ly6\xe{m"-0k7q׉^!^7D+ѬR$~mL^؀|r, ϣo="ƀ3r.P?j?)c Q1)>Y2O>&[}KK0lY~׋? ISQ}g8k *y]:&`z^uw 6Yi6L5h) jN e$Zqa [GP:>ա:XqChQ3j,g2t3X=#ߏZe c|!} OxK_lh/7emde ,D:0Za}`" `7Ǿ鿞؇pLqiz_Z_ :رrWcf\O2͎+蔻k][ X'x=~Z<~"߃8} F$[: ?JA5Ξx7 xF'Ygk#_P'#|5bP}d [df[ev8v~$t<< XRlO\yJk2Ao8~ f<+l^[t`쁟A$Ed0l4-#!]~f|k=v^0q "QhtEx<ԺtE2h[km.H*9Fݎޭ~}' P! ?Gp/Yi~i4ߢGO{l]㿩K INE?@YɝQRx ɫT lŝ3 n.aI(?~wS=BKGԽSlH=p[:"wh{0P@^ zd )/sp`nBhߌ?5|Nꛞ ?IY*! tpp%[ .ۭ6g]㹎zd8"۽;m9x`rBfް;φol7m-80vȵ<-m!'$;ؓmFBРz]6 c6r$tiȳi ,8"A'Bq/RrnM <O;5셧|Vunވo.%l$hOϨ(nъ!_>q-^]V]DHHw*1h>kL Bϑ6G9WrQ"$}` 1n܎FǥjT"Bs4aQ^kx/}c 3SLIKy1%Ջ`w~ޡ)3ӽeC{Ī_» &RnU|0 {'VzI?=h2ޟ< zʽy;8| 3! 4-;_P JS1nAꪯl #ĝ e,x ż2e;d4p=񱶼7|+Dž Sa`EvͲDzt}[zd<:ށ`Ay"I6 MaDHВHPȟ3acCEDNMs]sۋn ltx&, Eo{tQ莼^CE1׋ 4|z^fQ[KvB&Z$Wkt?5;9 x~htXxݦ LHXDDl*@䓷\w`m"m'fY{o|u܍[myW9ɺKiZDTo|!M}GaOvFƒ5;'=/nxCv< C3ބQ&[V5B/Ppx, V0b.'c~pX=쫠OQ${lzqoϐt[p!<_QQg6lm8u'QF(a#peto6,u8uν}c#V|^!ӸWkAaOBAR{R[w)lEg] 1qLH C[ !]< ŜƳ ]'lZ ;ce*Pub sVgo￲;i~d>GF}{tc~`vdJayد1X2S4RN.G,fvu)P֢1i)d=^,DS g~GSrP&n=K(3e05{wZ;30~6>R$1 gYۃs|ΰN|c;?g]qM'l8 @H 1AzF8R ^6c-m]^N Ya~F/a7,xemPk0 L6.Y8 2P=~Fݜ//Ǥ@S#@I!?3%Ju&7ޮ4 yU"Ec3_6ba>/z|z+\WO=_~]s=x~GT?DS1[w&^H`rH|пﳱ%g }4R|PLG~)'mnJO׫k!d i}]%8m!aae{xCfc)D=qvwoCn6oWyjppΓp+<3k+uco:8DxnPH,?LTOvu1<$:$7Yɤ`ѽA?sD)ub7c~t0;:@[R'~zV@F3QYϨ{C ^1tyYLG%0]]c'ϻ7cSN67c:K ~|OؐGl^Z& @zBU ~0zL aiSI!`p"[v2I{[o$')*WW\ f +d$X}:ZEQ9}?^?3}VaW#SYA[T>p[k )[o l6 6 0 & -ݙƻ ƒ&ۯe^sƸC<>%6Yx9wucwos.p 6̳6p۩gӁ4c`fudlGY|LSaX}M@cK,B+*Yl]xcuc zCsr;x=ޞن#ߡoG<xawe>!=3ߩVwLotc$ڲ{DCM=5#}D6c8[DWYBS_1ݿ_%8W<nz;ҾwIjI'U2EP/D]Iiy?Ϭ砲\y;`ϔpHkPbX~5@o= m%ٓƙ!2  -cuala`-x]mw[2y %.8V8Mcr <їrQi[l2|h<,lu&1e[@ggѤя6G/,lؑ9 n0xR#a'., ̐Y "xQ'|iÁF`ϳ,%QD_mfdawb<8 ߔμ$鵉4.agv?XI_!Y ջ,97~Rf )miQY`t#@g]\Aq&6_0M2i:[7fw1<4.M;b@nu->ݷx`G?f,Z?+Eh_qg3^@pOUg `!ρǻ^/O~CPa\l; & bx݆6S7V.[mdD snx^=oNl=p v"Q!f ގDFo߶zz d“^lϹlFؠ;/j- uiau`'n}#Z)tЃ6yiVx@_dM AKK0غRl:= ˦ݘj!?C?'f|=?^i1Ol&;<:hd,Ă(g^Ѕo۴/t;N2GٰҖ!mm8;<6:0@t v nOl6^1-6[,2#9ހ'm/,Blm;%9b<۽ͬ/-k%4`H$m0sY0l#Bfb'' b_O?6 Y(2d 2vh~!cxEli9aB[|_Nfyv8MK?|gK@ qY| e==Ogݙ84 y0v4u)>ZT XϤq5|>~}WE}Zw(݊~ẹiON'>G)Z:}F.׿DWwvIks=e{t{?Ş5?~7$w;vxic? {~%t|WoW5߃hyu g~:kb4AmKbjclF!, &)0ikx8 b8H9ٶ4-MIeD/V]XC}6K7rH,sīg,s.Ա>$3ۮCNDHn蜕4n^5ෑIGD.?)#LIj wAv2ÛpwkޟltCïm:g~P>B9?KRLYv}-ik.RPz_~ 8=[ܘ >oΧ/ }G?{1)N`HnsnpDŽ^:fޖ{ g8u '8-m'%ݶ(ȞҤ#/.]q =EQ{M!{ւfo$]/ObI>S흺x-.eP;~'KF?qJzVx ˫Szu$ Ƿx#cugDM\3ao΋b'lc8$g⃐C~ĵe6 ]35,;ö>8~lqro;mo km0m[m,0C,aa!#nVx\#l̵6xNrܶi(*-ZdK9t@qm\0Dm=DG|m2{ *1 2>*m :,:L@V,Nznէ`# H=> c]1۵ ȷ@!dh_d| ~%b{fRMY#KKp{0NDd{`f%u'"+G4S&ўC o:Y d}=؆{Z:4XmlH 4 D~,Tl[ȳd.r9< +wᶬ 8qKu6|i{ׁ!!ې؄$1#m1P?|j$p544eн>۰& !ϲ]~3Gko5 6Obde'd3Zuhk~- "3o;ҿ:{?,8р6=A㫻'7X*ƙ.DžmlHx4888pP[x-6vmq ڇ .z\8ˡmxΕv%YBى3tNۼؔ6޶,8ko\6Í ^~DvaN)ʘ] ?NT0]gL/`euU;YdtlO8u\m-,S4q>{\(GMN{D#v 9S/##bE'3|=30gxB+Oyu#$?]=]Gя~-Nu#w{aP7ui?e}$|wo@k7_?QѾ]~/q| ޹Wr )uι9lIn f4]uNh("0 K½ 4WBn__m-74e/ AoI >Rl4cM;q\!x1ͦdDݨIdL|-},(njĒ/ H@Tu'iU|ne~>[Q=e> rKoYm)N7Xl@)p*~cY{Rc=W_g9%L#Dcz778߉߆c zƙ (z˾ 0i(69³Z2i)6ll°ppmhqmԎI $p]G$0?-Ky%|ީyEɡPE4Nć%~Oe=Fc^#Q#r? )[U';[>x"f+Nܰxn>Ba}D'd@.,2B5+8N2i.0cdJ`{[.Q J.҃>`|PFcۜeqo#ʰ &C)B.qC/O/+jJ2/\,;fgoL3#Ò/džamSc[{N7^ ; =.A{'@*v I|'tgB`_-9,gitMj9O#-α7YvPcn"JkVכM:TL2A~>е xY̦=}¢3nD0I0"G-3Q1#df~CSVT"w0oNQ0}sx",}2 oR辰FGr2:b/W׻\~eN0d{)J9|g^q<H2iuԷE9^[y6O۴f' ]q1FwBZ*:{hTLhw])@F׽6r𞏻@"\60I'_V|1]"Kt]x2~:1iiBc C7oaoyڼ9Vliknrݼo <7kS3L,[1D? N"/S?'ph H9 ~x}#KJ:EA mxѮMJKDij*T.fd NH˨ULZPl]m}s]*$90Ĩ:.8]Os+!PsFD~yznx( ?}eq;Lt^l%$;j Pu>ђL?+ۃ{w V>]8 %w4z29=؍Ak@cv`ҺΊ MA3o|uL>5ֽ׌^//Č% jPIY{d~a\=țwq`x7CXX w8[KxNeHul;ioR8WĻe^22q}:N^0?Mbѱ9m !F 0} !jtiC$ 8[qut9-4׹^t]vt Vu'ҧ/xڋaE^_=³yp?5f#'6[Ng2|~sٛ:٤I& .m KM#|Bϟ_Rmlܠ4):t 1L9,hFeB' {VUk\%Yb=v{Ykb0IAB`lù&nbv 0 xb }m$,6Yy_8f8p8a0Hzxq $0b=sOv(k޳)l[ yx^5uo{a˸~"s:[Kx ys{Y̅f;*Vv~hלYoe o'Pt|֠@&w=~g(jh4oH ]ktzmo޳s^j-=3ڤ}A5Ć-V0<@~7_a G!3eWĥ ޺@z{x+}wH?fFhŠy;~cfTœy0=@7^R{gVR~\ ۳1bwxm=.j%[pZ0R7]fҌ Q0<pOn]ggQ9Jdo]dOJb !Lr5DQx "B~eB40b'<{qpBn[bWZ ;O_uxMUc8Drd%㬆cヂX!{ma!"/\iI}lmeV-VvrS|=#tsS܇l6B kLY:ƜHXddbKXѲ <Ӡń~"! R2ђ&{ˆ#>/ǔQ݋E@A[vf#q\F^8?lt~8oie+h{d'ry]Ғzz3傇5}8X6w|鈻75DW61啓0ܽ':]fG_U_ǫ杝ݿMmYd!<תxz#~M]h Duq~уv|Yo 9֒iyߴv1ߺ[s[f`(&v zW~l|k&~;%BrtH6/mn*pf=qo#ŤB,aEmVYe^FDP-mR^Im zᇍgn5ph8s=q{of$yɰ!ɖ|ϖ@-Y2D,2FpvGr>az#pB (%J@ǽ&;ُJ [lh&c"7I'Igwf)UwuR>Bl_}>`|L ,oycZ @:;qA[_ՔV;'a-2rb ^Imj2̄2OdgxI> !eY`]asJ$༼c߷~"E 8C9WyK[c987aHa!-2RG̥)UmV i)9.ZLJ5KIۻeaIl&wT#OqJ7uXd bbC2jO^5`3eRxYCE,vYggae@lp9eY'3N3ymgx=&Yg"Ymyx 0,3hRkK7=}dday)ag s^ 7y"+m>1=S| ˅2o'xZ{rLgw?_B[ ͋)Gk:Z?xq7ը؀\dѿ=w봵>kgA$>34e#(wfZw.}@}z`[rYd *?̟P'KxtPy%:{YX}vdyW=TfJF&Lt~ae~kj\HAR>ǝfl(A DM;1V#ӞN!(bApYYd,ed"]l^88 nQn 㤶2l8epp_pmy@$-٧0|φ7}f?\_lv\B|zHhlG\o Hıԁ2C gd.CI8`%-E^fx )vC^]e,^*l ]36vNwݨY  #  .ήtE3D ڙ߻bÉP"=E0@~btH]L,Zl{":pښaB^۰ܞ$98OBJe:mϊNYtޢAmQs/?[uXpo[@3!,>;Z; 3R߾fA ŤN{\Y>kIgb^A(s߸˔=ӝ- ז0fO@4v·cgcI Slg|vrp{ih}0 } D?Td!?Wc#cXB6H:% #t%7v%Jl=_j #cc٤Tc/{ރ8[t{AyTLOl_űqBwd \0UKC͏ELo~zx=4nrh+|xa(wG2>UQ 6ph2~@ z&89B6w|*_Vm0Cp jz=,,4|9wns#; ӣęq;2F 'gl6l|uhѓ!^7YA4e`r3 g_pu?=y@_ 3=TEYo>J!3sG{`QKP2: 1S OE~}e^ߔt;Կ#hBR>cg!-{՟Ov8x be׃fM]=H A&XޏOžfH 8BNfrb^byw"[0:A@ߵtLzX}q>`sޏ)[VN?c+{\ǤHS1''%=pgs}t\#>c}G?-&#/hP/^5gGtsl, C42E"/FY9fuy@xƄS>l Esl?9ʳ }I[,Zm0?P =;`ٗ.Ҟ~ٸFzS*07;y>o8]kByXſc}HQbhB@o溔$_:8y B V{q~"`)CgG ( 1 BX=$$ii+f ɽBUDv[`Y*Mj@Åփ +LSTGg gy`a-6Ӽ,[I~eغhFhqBǴqQ[0O㠔a C=:hMAW7D'OJΡ@xi"4Pw7:0+ްv%G.ǡNFC&ǒ;Č '.=АDsDE1$&@XC1_n :H}=?qޟY7L;NCx88$ፍvH c[,sde8M߹8^&5VȎ[oy A[rOkUw5Or#/@DT0Oz@X}6XD :!޸&Ͼd$ 3AN*2:<~{:ujt7*$q7S̃ dDd]D]ÅllDc^ÿp06v9<>;.x>vIdKX2gq]dTxZF ,D?W= ^(e ^9,"8h% OP/nC8t;X?izk7kΎqaz{']|wzgM0ByItw/SVlT  YzH=}"-A!5}+3N2S0Ae,l8>6kYdug e[V0,gxޮD_9'LG3n+˯wq-vƼ 98.z%" #3C{: ^cI |kS聽O &?!1~-Bf铟E00!\J{Oyu.wO@DMML{hY*ZZar\{:> b"޾9q,gǫ,8C,ѶY$콙sz9s&l9W88 x-g>r >vvIK~Va&xVX xg7!3*sJsio6'">H岔ZtB1>x-!"=38"t?DvFc4R ygaî+{YRY?kko_Au#L)q/Bv.9,`{Idu ,sYeY@p6q%qesGvsd&~,.  `ς>ywmeÑDvBl$+9χmx:0x ݶxic'9XAYgaYg9c%g$d=?"FzpF-p@dEAAudf^5Զƾ>m;Y6ڒݼAj'(A#= opAF?HY)xXaQnaiֹW!ӷF"u2>%<F=k,nDZc2InWZ}2>7= !6>_voQǾã"6j#\1hFAk. uS=ayaKe|ddIeYeYdYYgA,8,,8K9$/Xq8vφXٜ6B zgoY e8npp7a~[kpwRJg\6y?%owGxW<^"w%p$ x]\"G: qa?ɶC@vg@:AMw~Ѝ&9?C\*,>sr[_9N*ɢ5' 7]t2ۧF:!`׷:J磢<:G/#MLX96yO/cz%lg,U<$ŵYe,9, l 8NI,9K8I>r^3 NedIuaglnqm.o~e1Hn#" p3:lL1)4wY|z` B[db&IYFYs՜dY=gYAeYg 3I$6Yd$wgaY%,` 6v8=r8φ[D+%6Y͵ ظ~#n[k+,׿A j'zc!(YcX^hń gq7rqY#\|$ ͻ7B3ЄN@{YMÖ-q(:y"BגX !ܐI'8lXgcdKYYeu@Yvv 6N2ɞ2I!dYIaF'', @q;#3y,cBCœz9ٱp!v[3,5 #x;ͱs;Ï\g|oc;Ŝ9eYge3%}pYd%I$tʮ2A6Yap@A\^1I͙yw^g[~.|z v8o=d<xFeġqJviidŖ? 8r3697' X]Y'6ig9d#Xݭ{H}EZP;I {^<6\zygav6YqoFzaD,peK82|w 9a2IS9cYdY%I%YgY7SmȤ/!;{ zgRvq0c:$wcgEq,,ݳ22Y;| $8y~Lq$pedpAs˶wg9gVI^3{96גfߓ XNOdgqM^tO 3x >&-"ͱ>%d7rgdag8|3-I8߂1dpY/ IeAeAݐ'n1 uSq!^#6=ƭ64gͳN@<|o"NC>!cwa<'lY'Apdx8 Yqsܜul'gNn߇Y|peesn,xgdzzx or]N[f7[Ƽ"ymqn|}LowClsDqpdF:,߁֜eIAg, ,8N2.I:N33wacdd2YdYdDՌwww$̗'w-.Ἓu9F *,de:~mOcΙ$2ǖy='z=Fqpl]AIg9guYg8eeIdqYYL'>ag8lɰq;N: pMYgY,?-m'rX.Ã]d Z.v&pig(F1d9˻D AgpqG!gXee6,,9N1 >)Ic;7|Y]uz/|clGexu$x`x,Yvqvkd[ñ  RVg0Mko x9øl $9,'slN4<Hq , ,,x",>w'eYYc6Yetw'û8.㜰ຽq93)6wchY;L%'d8sޫ?!"5άY4nX~] f9ùfs+gǎ3,7w? uep'cqA,Odψdgpǩ;28:egH$I7dׂ H 64߆l$p_|qd7|$qYagN1<wdraguae/ ͗VIa''ov<>=íxeg$3<9$2epS|sݭ{C8p8Ixe # 88C[,IdpYqq .윲L,,,,$2Xݼo|u9$K㌺dQe\NBx؟I- ۼٶeLx<lNv N5xxx 10c 3r ,8: B188w88K89Kq wgN޸3z$㫽z'useCY^7l6s8 '&Y,e0wv]|OKϞ|q;dgAYaDADX0|K8Ì>YYggX6ɲI1"q\b߂[N= #i$?gnӟOY_̦ɳᓜ9pF'8[d[cǮ788#猳3|39y'vyܝ#tg9O7 xx۴ۮr[[a3xfg/s=6pu%ԟ'6'apiMO/sx)u #e1u Awpp8>p3g򗿊Y?6I$|:97OǻӍ9w-`$'ᇒd$,NR뇌s>9,f<1Fz>-ח"_F؃%[,  V<7vo՜wbK",9 ,9d σe6p|2,8FI$&k,IIcc6~]՜sYϒIl8NrI$Nlw|67^ΐe:xY8R9bs{e0 pO 8LxFYO ܇:E90,8獍EcFgA> |3xnN>O)dq#%';3agywvdfN6o.o v:`KllN3웾;D FBIWIvԖ8Gl2scd;7\lAueum u ȳ8^Hωo's9yÜOLLI<R=Ͷlmllb<3u<62!/V1,󱅽e%3YYagovs,I,Ns '<sseg,xy3eўzԟsadl g $sKɽ7asֻp;2 ,D/l ]8Vs9geϯY88]2xI$rl9fyLdm9x~ AiƷ\d2<62pY˾I571]'82[KN^f݂ϙ$Arn  8,1EYddCY~G> 7r󎧲;̌Ls8YMn᷃$Ł(_027Yr le, %!l/\j<玸V]ɗVz8F69cͅzC,cLb3ೌ8d/|g9s3;I'$YWήr][Ò rslH͜9I)'d"&:ώpns2@䭥mG6~ Gosu2w]|#V^xl # 882,,>lɳɳ9,Opp3 xYFn=K՜!2YR}|g 3 g.Ms8;,lnx5gvf$#6NI 2f$^]ѲVxeσtDZ[~lmIEYqg1 #A YuA{dA .:pYI2RK=Ӿ2g\,z匐[xSVwNHHr]nYg˯V6ckuN0@q "nsDlπO$xbx7tx3,YAG8,9Ύ3FȞS&d? $eyld~/KpMw%w<+Sg8% ,,xG8vp䝌ZKP`x'2fvwyq9.}p|Â#r 80,: #,,qpe7+m'f{$l'%LrذφNZN]oPwsFYpsYg ;8>YeGld^x38,gY2,pHs-嗔Ρʁ?,wu`ǮdlYpo݅N= 7BGgx'̱ۡ8f%d MԾxϾ397qFpI88&$Y{8^7 x=`A6reA38l,3>g8I[|33IÒg(LLO^3CF^2HPl ;V8链ιIY~l)'+=3²=xE)'|8^> <0qOJophYg80wwMnm"dqxV8`F ;qqwdgm'!|xy,$drb/ś9YIe^ LZ[)<d Ä`mnI%ʼn  K,0]dF\#s"N{8Ե{:lԺpzxHbl?tz9>>nY'= u?'0LBfI6wdPK Rv]sg-͑[{Pe^SdfYݡݦb;JǛXR'g'+)݈Ζq՟ K"8G'rNs:Լ<,Kg~ m36IOx~9e6fG2Nnˬ nN1 lYǙ 㹜,IF^v{2ݰJpjpd9GHHMD D-Gemycl<~'άA`p^vHbǏIx<>Onxf_3<38,/ܗxw,gy83%6mu4xgv7{c퓂c8-gJ<;ۭürr^7b3_>^w෾Kxyϋ<2̍hN|=&{8K>pų6 d;I}e!M\lNq֣ <>C'9ӂ^&Bs98!a:&2 x H1ɿ9Ǧf5feefםxoE?ݓ\:[sdfMk˶91kv#lv<G pp`Y \_P-Ӗ 3K)ɶCw]‘oQp< /;cq76/S)l{z~/,1z% Y9ļ;zg<;cc mr`0L!<YD[m[c<4μwl ˻B,# 7 &  #`9#-Da_%N~ Y6,ݓ$2w{a#VՄrIf<&[xxfYsXMxx\pᲄ/q\8'ܤmіr:" xѵ<|ulDpxmk\-~,<"xǒxSp~ y&f;q32sdKgM;n_}jxn焰dre$8/-ȒKսrZi(B-13oR;/;<3p3u<3.-cn~co - Z݇xߓNOx^YfN7x8d~69b2G p cfX6wa {7LOee '.L, =+lex[ݗg> fo8\yu:Yǖ1ٷ2l$nq[3}pysg^R9)*0s*l9,^V0G[ 7!xXX!K~]66?"3y8=[<˯-VYYyfm̼uoi6>y2Grv m| g%ǁx~NXvpD +y{-/,r#a'7ǎOϾ7<|=~ g/~<<8|Ocfwupd-1.9.16/contrib/qubes/doc/img/qubes_manager.png000066400000000000000000006330331460375044200223710ustar00rootroot00000000000000PNG  IHDR_JsBITO IDATxw|\ձ[JUYnɖdYM61զcLL _^ $@ޏ%!K^K# {\꽭Vm?u-c #={e̙Cz}{-m-iQ @ %J@0Kvm /;Qx`0F1%%tjibbȒdM͒,%%%͞;[Ǐ9{fIiɀN?u_8pɄe ݴ&<Uv:::6nu7\71AO};RJiZO`/#2}0$HZ?9)YQ(!gn{4,ΝH oL ? [mȄ|f !QjFhlUPJyy(PBhL}BHupn]CWU+_s\흷yDe9F^<0}8PBR@[~=;@'L:P(w8g^`5J)(A΂h "3})GُR\\*;wڝG( {%9)9i欙i?̀g2DQzd@SC %@ /?YV1 8F.\ ,~W꧖O?py~ǫWϞ3x BAa1}{uwȲ2X8|Y[[Z-VK(o) dr:͟gZ<q<0..kSJ,X`mc-[:q7 ȥ "8YkpZ? O7EQ؟8c΍Zi97:ݟ8.@8-[[[[UE%deg1mvvwtt$&'2_iYF`o-X`էM&ӜsE{kV?{߻[PV^gM$ew|PUqㆍ$a.xrAkK9mveyㆍ`7D؁ -wٲDWA^`W^x=3_yuw(Al `C 0?]vqqz逵 D4JB-=mͩS1ñ3;zHXd!,`a[[q -ÂII2~X9<*pY,OA6R0 v߾$ˬEg]ǎ$Ia~k-'OOz#ŽAf}߹8QJE?!c99O9=08:%;|4iұǜ/b}NoKtL)h ^`̖JbHψH$ފFRͦዼȶ|R8 B<噭N)MMMe<>rDQe'++ C)3&xYl>;.9* GevhOfdfܼO mԩ{v~rFMSN9t$%%o 21xQ6wq0l6J)O,;ih+e"oAK^^~o{}^n49`#( Hv ڟR طY)8>"˹z箾7;zfM/^HOp1SUfJiOv4aգlnޑs޹:ABH](ݰkUoL 韖9x}߽= }bk P'闔ʊиk.㮸 A  Wkkk=㸊,D (OOq<`OR2䉓'Onhh0lw`{麙yE mٶ}ׯMeegbC---vqc;F))+x.0?11>7/wҲsO qAƽ`,p9M!<9rxn8 n|mooX,@IFX)} r q:8B~~`0hBH8>W:K:8?!DZ a%rfife }w@\6's76/?"|xO*ūnZ5b 駟fK.;oP=7x#// 3.l[AK_/d|dg])!z=: EUz{{S\》I@~|BZ/~2:!\*mp4='L0C۷o_tɅVdYYY]]է^(\Y c'п3g\Ov p@ Q)у6 2V}~asS3TGJVXAc>U^`ǎ?j@; gCdE:   L_9XAAA$߀tv sxYAAA.yآ<:gW%@-:   L xH4Y    AdEZ ȥv/   n ƀFAAA;@AAA;@AAA;w3m 8pʕ+m۶iiӦ۷3GYfMVVVVV-rĉOXڵk{ؖ 6L4I[neٲe&/v={&v؋rqAAA Đށh4ve˖?~СC<f+V5k7mڴkn{WTU[^~5kx⊊OL^zn⋟DAAAcH@KK7pW,Xn?z.쭷 _W -[xo}ڴi=>|k[pMΝC_k׮c={ky衇RSSy.}͜9Slٲ7mIر5vww_-***//~7VM@ }v_~ݺupvfk6UV544@;w;3mO6cUcϞ=---?akͽ[[[Yc}}իΝ믳5kּ+l߿+܋   \ ̼6nvuɓ'gy& UUU׿?yFF?ȑ;r^_zmm~}klׯߵk//~w:~駟ӑhe˖n)o=CGUVV=~+55ѣs=~1)c0n馗^z|7 lٲ~ꩧꮾ/}KUVV޽[`0gMZZZniƤW^yUDQ|w^x??jjjLr=~38#\ܱj   |; ￟YjxWZe0Av:ӧ ӧO_z5q_dɆ srr*++-ZT^^n6o#GH`8NMMmLKKsݱRTTtmqz?ɓ~njFcff=oIB!x饗X@,瞊 ogee%&&>|?^tinnɓ'w1|Ƥ@8~7֬Y7\zꊊ Ay䑝;wvvv;v?(ͻ[t   gR?z-QW^/?ï';:::@ pm=Lϝ; L`&)v;Hf`mW|133Snooey…1I={vFFƿ ٳ/ˀͻw5ڻ *++oVYYp8vqȑʱ*~zUUjXv+j4AX{NNΡC*AAADUԻg/׭[/yɒ%<ϟ? Ï555k׮XjUMxg}Bdɒ7xcʕz믿jD^}[OVjڵkxˆ| _x饗,Y6]V^RYYovtt{;rűJᰞΠ(ʫzB!۝N)$9{    *JWWWᆆfbo͜9h4>Ck׮%mjjj4MKLLY)))uuulUVڵ_$)ܹ)L,?Otvvvtt? ~?[wijjr:/o\|yIIIQQ#(ʄLyyowޙ3g vi=ݎGII  A> Ywn\ pO]wu;*|wB]OR   r 1w`(3r˖-֭[xz*+s@AAA.!FfA,K,ikk    rQuAAA~u@AAAbO4b    ńWUb            ^Ӵ    @AAA;VAAAdenzEA.:;Pұu%җ\8Yp8 # ٜj4Q'&b4Mz,q6 E9'Bq kP:}%$-9G`.!Zr {H4КMنlJ0 Azcs [H0AB]mgĹ< t}aW)e]"liiQEQUUҏRJ)qeee qAEHoo`Zv}h\ C}7ĶeNQ:dZ*"۫\> )65s̆ ' Y ! `}MjQM IDATU55pN4E{"ȡc7OKO'GhÁ]Ɍ_#ptod->UTw! ȅl6kHqwAFؼvuu1 j(ܲ{u7My$,*Լ'gNtI*s0...>>~#tuu:t(GQYm6[\\(p8<(ӧ;qD JJJb۝ wmD]{m`o;ĉ3f̈-:Urjx t6s@ĺid2^zG@i_ٹf#ϫJ4*U4*kD$r^s[yE".2 cQ-ޖp zK-wl?"CAA.9xvUYY(qnoEQ"h4Z-nGNZ߻~fh@( x)I7 cB^o0y^Uax7`R:`cܞcǎB!JY# N4%##СCPرc&)-- EL+((yrڙDPUreffhooq丸w}L&nj2<jkVRR2cD"UUU;uTp:p hoow@`" "?MzzzL&0}dYy?Z,sasO( ]+R$3ϛj4QBh*M(5~mja/1y_$]psԕާn*@)a|+|mx!<xxlc'O7>?AA1iqno# [,VA#B_},d'Olc/Nm/>v}<'P 1+B4QY\P=AP n8޷oxgϞ*"zsΙ? ۷|Ø?" vk._OOfc֠(o=2Ѣ(Ύ5vww'''/ CNN X,qqq^eZcK+2mFczzb~ /--zJi(Z θQpz|@?bǰ̄~G2/΅Ri(Gs ׀,zDs!k%سœ8R_DOk#MQ磦X`mT>hF;%tR^ldq%gܦj7n9p%Jq퍁DkbrBh/%U"@VZ;G6 pBAA>j×PRR 4+"'=?SV-U]5p:x\.d,ƈ ǃ`47oόcǎM2П3{۷gΜ9>"/9`0xĉǏJKKKJJf8D ErrrCCd]p904挌 QF^oaᨲ b}w@Uؘ_R} t:9c ͛SJ#Hwwj5k\PN ^?Q 14Cˡ&e \k˲Ns+eň@^ )(k`گq4²h~UUDe pvܽ<O 6eY޲9*ūzӥ5M#~R%%5n$m<7I;]-& a5Q#a%,)RT D0M>{bZʴCAA.]xv]YYƠ;( h&(+Οn˖_[GzhTꀀ7o!Mp8=ϳ{7" 644D"촴X,'''V4hLb6ytznXzGGGWW#11qBDx<}"]ɜOJJJHH8Oƭ[|>`q`pUUUK,q8]]]# ʲ\SScX n%ÇLh4ݝ=񓓓SSSvRʮQJJ ;Пn\.(-ֺ:^vwwt`0dee9ф'XB hD)'Z=lk6G wMM[ֹs:uFQ)>`Υs=5uKqf*D4*"$Q-hM(.e@aPɗjQ5B8OɄ\4-۽ϹUSM9 /*qqhٟh4gϞkf۽q%Kdgg_g E#l ` B<H8R pD 倬*QO̢ tCv+?ڄǪ{5cE׭ZT(.D gI~Gd @uK/H样E8D5D5 &nUU"HS|~^漱4AARL=ܠMYYm9|枙%&BKٓ,wjN$kq{{,vbĺXz8D"Njhhˊwy7 }DDH$GIhuwBD(FٰxfoMjXzubb"`_UUU͙3g"@y`0-Z ;VFh/5ERyJQh@@ H58Wz+QFAMD nx!IUGUC'a",ΤQ1.8Bhzhhm#LOԗrhHʱFt漅p@4dkKڴ;23N#,&|ck^ȵg&  |8;gϞ ʲocG~%@o }mgT a#$>:턐qtu׫( Mc"H0T% uuu/fA̴zE*IR8-DBLfT6O)՗?ORۭgmf9CBH0dcWbBD` _D_\[RRR^EAD^J@SUEY #HDlu#D^*K/tM_w@>V|n㌉m۷fggƔZjʪ qA-DB*4@2@T%Qx{Y#/,vBk2ZDjJ }J|Onf!O9JzbTTNs!@U6hKv.[Wd8@AA.ux.\8`c<fo0=q"D<@"EN[98z' /˲dbr`0 cO:յp|l6 䱊ݒ$UUczP(vEaG꺱q>"O^ZZ=)eeeGusFĠaVhy쌋coܹ-X4MD"2I@\\\^^^ggg4RUn0GV #..5\.ݕ0DX$I Rɤ+((W1HNNx<(GLO|̰:#_ƁYQd%(rn/l0]~+ꅐ Q4Q TiM&ۇ~ 7t͙E͡η=D`'tdui h;QHm U@x'ޖ`)>%h |{rO׬Y_lXFp?!  IƼfXՋW-԰i?㔸ٮKsN:l6p:(---/?=Cjjj͛'CssRݸms --->O/\̆pWWW , l6L&2!G pfQ`dUUmhh`EEEz(ؑRJY);}zD).RvѨG h(YJqV`={|>|FbL&vcW^c60vNgIIf^wOw>hKl2LQB h"R4~|Hw(ԯ}&er#?G}}5JcN0l 0KT5@T"j(YFݽ^1a><5؈ȅ7ҘN KGJom%7˪hFܑDuΕ F$FaRȒ: Xߡ$qFt9 +=$  |j9Sw`4I4%X,9s攕E"aiӦ۷UU=p@UUɓ7lpUW :&) 8"X1vAQQW\LDN>d[=%`y<3A`&=__˹cǎXhD`H}Y{vs\.ӧyARRRtC劢[c6G[[󥤤$''(,/  1=33b<|їϛlx5jqݱ1DQdRv 7Am,roo/d"X.dbLfYwf<=n`_g(,,\d }}}UUU;v%IKHHeKQ8d2)RXXȔdLRlMC`0UE 0sLd={vuuuoo,V`0.bx<hBKK 6m뮬hAѐ*-&If[*J<J{AYGXn`z%Ev:of>v@9@b^l`_TȱxQA*8SUVMeESj*N;irU Re墜OPA@ 'ӞI7$IRh+g^15m{7-Q_=rykړfG!ghZMLgMy CAA.Q&><;w޽{] %%uwuup BnzzzSSSwww$`Zi|AJJJVX?]h4PÈyl6<2!"h4 ,`BDB!9cƌp8|ر*gZ3e"8r YrAqqS|>_ww͛,jeqܤIKBQJdX, i6n3 T].Wkk()S nHIIѳ XBSS411q&#s×d-6[&h01u.UW3$n9aLuBz\iimm]9EQED"lkZ0પ C0g _q@?@`@)6}}}l=°lIhiiaβYf5f㾟i&dd %I(@ڽ?F|eL2DQխt%gjLvdYNKK}{GBȃ=iJ$]ty^Ee-4l>V۩d4Y1=hxnmLco]$صh/Qhi9m\ٵ(%婔M]Yb$= 2|5LT;7.U^%C j)k N> ?̰  g3uXBiƹ'$wd5kּ v͟B@ p]w50|c\XXxlG`@ GUUMEMpSή1L@4^ʀ>ނ{bض`2̛UJMMHMrrd°(?.;#'4˳ F>erVKrh2n6o4, 2BhH$ž?(/l ׬r EQ]Jш  1􍋅l]n۴===(pQQQvܹ|r=zz ^>TQQ1n)ܗɴp\e&t1===b1w^B_W] `(tmAv!!;u(e t nAhoo=ztΜ9F1 Qsrr ϧwfO(4L@QPiG=1 !,QeL|GXY& ,EQX. KdavN #b__XuJ˯1}fknn޲myyW& 'g؍RTŠ5@Uӧ_ frӦM~ƍlY{fκ3vo޼Y;?"Ѩ.]1555e0c:$B!=үd4Ѩv;4Mc*0kh4 6Nkؿdd_g{{{JJhzáǨz{{!  :j͂qt:N'EQ -[DvłdY^bR\.Pr^wݛoDXBzzzbb",`0,Zh/\ EQRSSn4$I$ vtt~z<׭XbyT,744H(Jgãld!BnNn%P@)RkRE P$h=Yo#6dy=+.Zز}*777;;["0@X,$6r>8L&KLL,..[v .UnP ~Hvmo޼;s4YPP6k.Q\KShllBӴT* ?&Z{NNNOc{p&QC!TB(4mldlbb|4W Z) /77ƍB0773ᬬA}i:*魃/^0yχ̯{{:Թ<&PrrrX,v 1l<g}8%졝E W|õ!_qJMӹLgggg6W^EE:v uyݪY?j:׭]!P3G4;wX2--۷ݻwnHJKKҊKJJ@KKK[[ҲAb'SSS:MY؂e2C b͛`mmv!jJX;P5B!@j;u6B!B!ZϠB!BkB!B!u!B!qB!B!uTB!B!Tݷo_UǀB!B!UbK$UP[EEE!B!Ԍ B{{{U'j+::buQՁ B!UG'쓩(**8p@!B5GoVu}> CB!BdjB!B!Hv!B!Rw*ntB!B!Tϟ?9r͐!CUB!B!,H$s (ӧlv'5i(U B!B!Ԉv %%%77wܹ::: ٳ'{amm限2l666III̖g^l٘1c:vXaÆ:XZZ<8//gּ}v֭MmB!B)v|oV޽{W駟:m4i1bDXXXDD_ի k߾M^uV. %%%_ =ƍ ::n#B!Bp7n-_;99~׀:Xs禧s8MMM>|G)cffA?~|ƍ$IvQKK h^nF^~ cƌl#B!B¸`nn4̜?/]@fYYY?իWKJJH,--<LLLmmm+-eMMFB!B!>AMLLNh"077 PԩSw޽zQFFF6mhf^"YӋ766n, B!B^*J?DEED_~s0}={<K.QUTTdbbbdd?Su L2oMII(*,,-qTBB!BH%S;ccc3tP##{@7nܸpB++ݻ_v I&Qշo_ooo XreϞ= fmmtRTZݖ8*!B!B!٭[UB!B:M9F&&&Lr!E~xB!BhX;B!B;@!B!'S;B!Bd@>>P3UGB!j>}* UB0-{{۷o*, B!P(Wu }؏?Vu G[[[! B!L@IFFF:995q*LWݴrny9',CVEEuYOX*w޽P4M4,P,3/@HH DS,@!B!ErMM$ PB!B!L}[!BQɧ骛W-/GrxԪ(0ΰ< !>Bz1˭yDhmmm.dQ!Fr?^]Λ7B-Ç;v(\nFFǾ6wOú|˗UB-  \.x<]]]*==yU[[[nnnn 9s&˝9sA-_,Z͛7Ǐ߶mN}bDL a3_~ʕ+{@jiyrTOX *ר@N:۷ٳkݺu6m7SIMJ3m R%~ճkhXr%ccP>\e7K.)Z \rѣG6$Z l;B _~EVVֲe<&Olnnmkk;u {vqqx? 2P<{ԩmڴsH$۷o/ mll>\={xk׮x={2/CWW˫ۓ'OvU__]vvRLƆZ*!Cjf޼yEEEJKK]vfff0i$mmm 1cDFF2o$dBfĄ=xίmfbb2{rǏwE__yŊLOff +D\5r @!ryݼyS&)w޳g>gϞIIIaaaʯx{{GGGoذaҥM5Buqʕcccc`…ǎ yyy̳3##[n4M+W+8qb֬YZZhѵkז/_Nŋ/kYp_2dEQsIJJ:zhjjŋϟA\>ev ""bȑׯ_OJJ UI1w܃ٳ\y#T~;c&O|ԩ~MWWw֭ǎ ޹sgrr^~JJJ&Lw#GXݻw+'1fBi XņPrny9',kT@,+?áiy۾}{KKK&Lnݺe˖5js6bf< EB̲k```IIaJJJNNݻwTb°4M߹s'##͛6 69r ؽ{E5s4YP}ѣ"hڴi˗/(*88޶mۑ#Grh xR4 ۷YYYFFF+Mځ 6Vؠv8p`^ t-[0n޼y֬Y ϟ߹sg #IR$u#((O],@!Tx رc``رc*ߥ3a„j׮hii$ %%%4MѵkWfSrb9-- (^bf={ ==s-*s>;v277ޏ8L&\B[[T"@vvv͔O†Py}w~+++bɝ3|HLLr?999>>>Uf!ԨvOP(8{j$o߾ݼy3 2Og4KKG%$$\z޽{W:qĢ#G4uП~,(Zh[[[3c>}ʬ0W߼yX4OMMeLJJRdmm ֭ޅ x<^#9=z4?qD=^>8.^إK :B4H; )Nڴ׊ x<;fDHUgÆ W߾}_w^h6 ">H5Ǐ|>?3k||||Ν;UXqwww######77f7|,Y=~Fk~UZnx2WrۨdVBBSSNeݻ;7Ӈ_|RCCC>m{]viёI%,,yѣGWWWT>>>m۶ҥKJJ-[_i~ܿ?Ìf```͚5&M(..Ve~hV:mjƍ|>(js6 5k,ŭаw-PeZhiiM2F={ǯZcyۼye˘8311IHH9s5k.^xל9sѣ߿wwf*c8::g'5_G u7˧5WM]vG>ٳgǏw^ΝUTٵkӧϜ9](,]Ops猌,Xf͚7޿?));$$DҕFUB1H=U j ''IT‰'fϞ4Pܱcܹs}}}\iӦEݾ}{ŊIII̧Œ3щ>|8p@++Yfm߾]WWw7n$bժULlyy-[Ο?vڡC$u./S1TDDŋڵɓ'?x 77WGGK.7ovrrr˴o޽{eeeݻw߶m3OcΝ;#GdXHoŋ#F|ZZZr|۷o/,,\bEΝ>nܸ}Xm۶YfԨQ\.Ν;Zھ};%UWWW,GDDX[[ܹѣ,qÆ kؒQLF ƌo߾K>?jso߾Udڵ;w III\.766Ņ$h333''$]]]D+544 _x7CL|TQ(Nba.͛78q"99yĈ?_rHn@ 8p`ttw}7gF>##C(Xb .ܼys XjwyY^SG0aԩӭ[,Y2j(33 8qK.~~~G7n\PPPkM:3YɓYfuɉ}ϟ?wuuӧ1""b<~zbbg*o)F1n833C 2$,,:5QQQC(* ))NMMe~2֯_ߥK7oZJ(#99رc fsՙ3g>|۷L+AEnnnm۶MII_|%K J?`Z+ۗ9|֭[w҅iyƍQF")SٳĉbxΜ9J*/|H¨}QTh,ڵĉO۷ IDAToΝG}dzX,2;pႣ>3ؕ+W<=={yڵ(h^eA11ynH$oXuRWbll |||׭[_+DT8?I 暬?sBĮ|pܸq X2*@xW\y…hd… /]9yQF1{yy8p̙3Æ ;{,)=/\paXloo߷oݻwJ?^s8GQ~ˎ;FEQ޽{&M4}+W1K~~~u7X477fFޱco㥥)))U޵}j&; ɓ'O<ɬ744lH$oXUgeذabX ifٲe؊ b3gܲeKaaChhWڴiSٳp`mmmJ}N8166v͚5_}L&SL0ߵ>I 氠5X7TM)*];wܹ9ٳgVz53? dgg+611166f777B!oُrcd><Oݻ֭[Lw5@&^?P=;;а–LDEE*ؒ癣x#Ǐ?x5::u23Н?̓N [ZZ&SNڵ+))I(''G9/%P*U{{>X8pڵk!!!GVI3gd:O28W5CL|TQTX6mT֔AlnxIETwתRd>,(33kii9::N>ĤQ#aJQ^_狆ƺu6oIk(5ur777''ȑ#*$1yM6$9a„'O4rPRI˴OpT#ܹsݺu&L}ƍ}&NXbWee… tҶmQ:3AQQ̬+GDD2L&S\h7n044xL\aap3?IQCiB 2x{{رC򔔔7o~޴;v 6}YݘbbbLLLJJJ``aa/_~)Z]~]Ѥ ppp>v옋Kzz۷w)S9rƍ&&&qqq7n܈5bjhhxxx(%B"<{իWsԓɓŚcǎN6zヒ*R544~:ILԲ1'ITT… ϟ_l w-HLMMt< }́2˶Lʟ/2m̯}ELrG D:tpvv c^p9""O>}Ylll@@pժUA:ul۶ׯϟollT!ݚ?I[o23(U`ff]]v׮]sUnR-FM)nfn@q h޽{?o >\^y֞;,Y?>}Z(ׯsX?...^XXXttÇq뗟O?=xkYYYk֬133S WyKwE__g֭ׯ_֭]H4xnݺ={v׮]~~~$IG'O466V~?  ׭[ŋ޽{+۬JA(ҥKozѢE/_|xLLڵkgϞpBU|%:pѥK˗:466JKK,Y"~D"oXX+I}=̙`--իW߿)\vM,w=>>BJ;v033~)Ss^,{zz:u+SXX8vؘ5k,^X3mMVVD" HKK[hnhh`ŁDGG=&Lڵcf͚\XXߎ?V&8qҥKm*RСâEƍ:eʔ>}0Co~Μ95u=zL0!((hԨQMP!(U7-[TIs+5H @GG‚iKK$IHԹsgoo{4nll|U3 MӇիW6_~Ç߿?Z \\\~R ԜϘ1cӦMNNNaaaUwލ000wb=ErӦM]zW_}.,t֍3"""88ҥK&L`& 477wvvf& [S[-3 ǎD'Of~!3Rb۷{zzv-::z6mꫯ~ח/_;٦٫b`N~0=zc><44T123öm >>ԩSRw8*!BE-ZHQO1̜93((ܹsΝ>};w`ѢEgivvv̌NNNV@: ##C18BMq h_ZZZe퀓Bf===*yfK'''j; 32 ̮J ov֭ ,--z?I+c'N8qbellv-bFm ٳGy:L V&`WB kBH (,,"(((33͛7K.  rׇ <C13)*??՝;wfH$.۔yDLUL077ߴiS<3^ddd?ƍ+OWyxf^|y{԰===R)S 閕^ŋs8}|70h*O?*nllEՖ zdQirny9',kCm۶xرcTn3.34y}555MOO:tݻwΚ5kH$i͛7eeel~wUaeʯ4{bC՝NNN{Glb }}}/_foiA$3ѣRiΝ dnn,= i -y>W^+A5.\`;sLzzv:i$D}v'OTHw̘1'Il۶k׶l2tP58GGK.uܹs{afXgP(ܰaË/zx=??>d)Ǐo۶ƍ]vUq@5 "//)#Iq)UasQ=a\=EEE=<[!B!*3TBjg4D!B! B!B] 8+-Ļ?AT5%TQjl B!BΪ 7rb1,b"I$H`EӴD"!8MQԐsr1(<7],0⵲#HlB!B5jkH-*1ج)-KJJKD ,E4 ibɥ䄴^vV_[p uY2#ۢkG;c5B5~a-:]ۚh}ԞTjfӺݞZ=-e{jG4m` X@ƧpKSZNRIVک9dHDOGGAث.f:,>|ѩS'CCھQ.c NoI?c-cJ$y2JGvaijp)N( 4Sj. Ig٬QGWAk˴Z-.֓;~?_f2/-o3{'V&jP똜aD}!PO-S*Vg' ]N^,$jǽ;yuÆayZN:Vhj1hrYszY8 Ɏڵcl56 omZQ,.{orgSZ\֓o,u& N˒$mw2ū62sDd&Fd. ILV$)ϓp cSK5ǏO"z'~;#AXj1S"IbOpFtvlf_*hs 9Uעgv7wia-W>r̒G8Itxr^x!"HSjH}rgB\p2<,yia_?^f~22jnt>1A_~^FXFm5 n`$1y[&HduU@=q-nn{Xd/6Ie9!Cދz7\JkbggAp/6UZCoc8mfw7R ELsi[ "c;@=(ߤBJf,8_vjp|:btqqqFfU k׮׶ɉgi18LlaGIaze1u7bVt7~_x7fz ˘K$yeR;ޡ"J IDATIћ—>y:/M^Th'9abH'Orf,_@an?k_Bǵ7/e`$p4|{=f/a:|6'?ߤB_Vckqcs˘]Zimu,,&l1攷~:@_l{Pisnڶo2J2%岦,f_{ s~3[| =LpmL4'tlՔ <I}շ G:zw0p3Y\{ҩW#<pYz[}{2s-ojVJ꿣?u#u,X$}o/ !_4ԫ>{ze#SpƵ[Q6s0iI")r)W3a?V摎FZ\Xn!lOcf?tx;8xok9ƵxXKi= }? dߍ@{S蜲2#rw6PӨD4\IOM\#!l)$iyb(H\.WSYLj%EDD|diT9E?N,|sDӡy")^Bz<6x$Qb}Ef^4H,XVR\e}D@L*d>qZdL.RiEb$݋͗Qb.R N ~ըIy/ߤ mI'Ugg \Sm[})&X\a~uJeM(9.dP~nVfwu0u#EWQ$4d(V ݞ8vJ%rX+Sby]`"OHF1RRuUVU_t5W aZTRr(QP` XYrB-ZZ EO$MnLNkjr;w*[hjP0.x\*}VY$.ɬLDe'IE__T"pE.( O1"*O.uXN~nfy/ʺԈzD΂>V K!ԀjY`afŸN=:n XHy$xb9:::IA,m||ȬblX};"ϭC.v/תv&ZVE8$aeAؚ1ucd~,3 YH~K~_|~&e.'\ʹ-uk[s!i;hP3&sH ]^JYhW ylT\1anX,s0܍ɻ85Tz+*W*|G#S 9;AFX,# 70Ji*nfl]<(0  5K%rLlt$3|~irao[=Ն'u;&n~EO*\x Ί<6[&]w#hE꠺$B~D$vEe^y}zkZ8:LёyJӓ;dJb*xuюw +Xo_d̪ ʹe/·/v^9Жmfɦ[^!v&dAy HE֛F:-3\e+S0نacs_L.f\E7//TJ@Fѷr\MTu0B pVMi*+%/%P(dEEŅB!+ i "^SvN-/Ye޽+(뷬qqu[>9mL4bt@*@=qGH n{QrhjM5n-[-ŇQn߾l!!!5; . V^)3|PJP>-q'o6M]w߮MXv2[)xK*X  H2ajYR ϝ;WPPP} [?~GVӦMSS &覿xsZJNNV0œ'OW`ŊZvh42ʪ I>\E k׮Maf֭?~O8z˫V?bq˖-kVرcWZԵkʪ3hѢڵkFFƽ{j&gaƌnnnk֬A/6}QlgeetRe@T;99YZZ9sFoCҥ/W\1+ǎ+Wj̙vRlJtZS/wk׮~F0u|2lȨAe]~3Y@24]*4єJZuPZ*J%Ѳ 0,KӬVPZYeؚp]pcD~d%s9~n"0C֭7pssl x{{{zz;w233 AxmH*ZXX2WZV ¦M޸qJ*ta._laaaggW,wwOT/2|fT(益aÆm߾UV*'$44t/_2dgϞ=f̘2^ҥK ,YŋaÆ9::1Ϙ1C$]xqذaiiiHYfٳGP@@@۷oQ۷ov֭1b۸qct˖-aaar|…F4OĆ Zh1hР۫T &,]s˅T355)DoSBb0ZAGji3a)XC抋$EjQaX@,eYeٚ(W͛7'O|EQ5IMMСm~l>((HwDŽ7:;;lܸpE 2޾O>vԨQ}ppp˖-+ѷo޽{[XX&޿?==}޼yu7n2/3BR#@vv}֮]oqZ111oߎݴiٳg 9m۶8H+9))i۶mk׮v*;pBLLիw)Hŋa}}5,Mϟ??uTiiiT烂rss}K.EEEo999AAAÇGcҔe˖]rE6eCBB?^^)SpN:cǎ-Z={q觏ϳgj1G~~\:;;ѣڵ2e_||<jL&ˇ:bĈ&%%{رeee_FPEĨQzƍ"JvnSNurrB@H4t &m97n8::"_DDhffVTTVoݺծ]B[[*ݿHvXW<$ rryll/މ sss1b7oތ,YrƍW.XΝ| 'QȜGDDY EZZڑ#G8!C̛7/;;{С}E6r˗W5+plȐ!x-`0~IPJ_ʖ9 @,Ͱ1hj46il7]0Y=ɨ"B1q㼼3f]_~eĉ͛7x'OES8aE"WDDuk&&&g`ذahճgχVS<\YY7q hڢ"ݹŋ R(JR*uԉ ### ڷo/000@f2RCiKRMD"$z\.h4\T dM=juII-:{ƍ&Mh4PX}t4hY033KMMuy_6m{hyGRݼySNId˖-EuB.6mڍ7v={ׯ\",ZeѣGs){MNN^t)zhٷo߶m^zemm{6>>̬cǎI~JPN˸q mllv޽dɒ^8ƍ/\ȶ IDAT}E͙3ѣ ^~- [nYE;'w|jٲAx<^۶m;wl:4w\cc p1_?,"##.^NM8[/|]I~GWo#tTw2%<Ӊa(,%X#eטv횃X*t>\'''·dff޼ys޽\NDbggg``ׯyh^pa|||ii)Ir\VD"JQD 1115())W_5J|>V(yyy* ??Au(,,422 ѳ!Ju !HׯK$ZmÆ  ??u<+Wx{{󍍍mmm޼y3f̘ϟp>Urqq-qFXbE!Bi'qoeeeIII&߽{7::ŋ,˖pˌ/+҇_zٳgt9[ϟ700------Nںuf͚qnCC^z!9s渻{ b0::߿/.۶mjs]'''u֭S1DP3,j4aV\9|[[ۙ3g6JO8233ܹӬY3Er\&qs6hЀs.sppB4:ݓʺrΎ;&Nz366>tЏ?17 Ta|йq <he(XZǏ2 yaa66666hn\Ԣc~~>3??}ի7lذrn]iii*eee~+`.\8wM^^^Fݱcǎ;I&iiiJa*͛)S?6m###Z 'rT|WYZZ[sn6waÆxI&qmjjݧ7n:u[ߴiӖ-[.\PURN;]#ݱK.#"aaa?CuDE"QHHirr2abdddaa۴iStGG_~eK./00oOEt|رcm:u$ RrW&̺ucWxdݮb3j(@ ` 40̧F熦~Mn\hqیaܠvc}?.k䘾##2 ۩SyZjڵ'M266 񱰰8r:u ;v͚5cYV&;v i+Hh4Ϟ=۽{uk+))qpp@sr.a(JE4M* Ekٲ%K Ν;bqHHH\8VНAOUd(4ٳg_zқ5kvuaÆ-ؠ{ {W^Z3gpL񲲲` p=zT|~6nܘ={ D%_~eժUǏRTh3·;vL,K$e˖@NNNTT.EDD8pɓ'Jrҥm۶+Wn"#FHHH^(JP y<< \eُ 1]9VGJmh`EH0aƍBCCQz.]/_>qtSSӎ;I~1bĈ/(-[ٳgʔ)˖-C?ѱQFxx x>>mڴiР7Zv۷M6zb#F@a{sNS|͂ ƌSyg F`ggؿ?w…~IT6h`\?Kk׮H+z:z(Ik֬;v,LO<?~<((--ϟ={!"J?[ `0 J |~NAN?>@5iڴ܎/E0`8OV1 `0?{\z8Gxx7oMf``byeٸiӦa`0 `0OUq!x~i޼yba+++33-`0 `0`0 `0 vyg`0 `07X;`0 `0`0 `0 sˀ`0 `0 sm0 `0  `0  `0 `?EPfff6hРiӦ<sK`0=,E`0̿ ܱĊy0h(:sL\\MPZZ*Jg̘1hР-;ҪbGf Xea;@} wDy0QR_ H  I I  Y*e.-,,>R[nm߾].s)111;vXrG6`0(7YũR0 ;}t20Ah*%: Riai-)i`TqNx *ɉIKK{Ύeٱc6nxժU`ʡWP?`0 Sinfzj6_`DwU"D; EoU"ԔBŨ5Z[&SJ5ry=ALwޫWV  (={1/<|V^iWTNR!SjV0 J%o3_M SfKYJ @<> SRT(ׯ_OIIiٲu5 jhƍopk)S?~\"888L8q̙uʕ2__uy{{. 8_h-E[ m=k 'j!FSRRbccS$Ϟ=>|xerrrť0 q6m444JII9p۰aj**2{5aȑqղ4ir&MT<ս{Ν;5o_~cill¸>|Pwr3fwN6ݻϏT*}y\.www_xq^ʩJKKLrVۡCu֡/\;99=z5TVVlff&գiz[n>߰aè}} _qjǛ7o%'';88|^0xg^2E.\ &4MO}'*zC2w33c0UCv$ye**iTh"-)EɕRM52MDU*'JZe* `X Y,C,˰STTtggg##J/:uj(> 4uTss/^zyy-ZDGGEGGyԪ;1R deeرc111(ڵk_k׮9!! ..nÆ wFtYYْ%K7lذ|$TP"p;q9sfڵ?֭[b1={ݻ# +h N*WX8tPjO?ԪU+//iӦ4w}n1NRSS{ոqÇsÇwu_iC ԩM+WlݺK``+WPuM6 d2{{{BA4K:t(!!aÆ [>}E%&&4r>|اO77N:]t )..;v,J|1[tR``kppGPb׭[׭[W&.$ImƦN: 6j9994MϜ9}wޭXV`aaa[lA^*;Vo1Euv+y<@^YuAQj2ZhV2kaZA|XhTJ+A[#ς4󌌌Ν;sɯZRRҵkHСCV222 zڷo߸]vݺu&,, d_~}IHHsN^\]]K؅ Ο?k.e<<<߿߭[7?????b̛7o˖-EEE 62dH7nܰiG]hPj-r_Ϭ4lɓ'G2eZ.)))--W 111/_H$EYPP믿z{{[YYI$d}Νs.\ޣG'O1mlll5jW^urrjذ!jܼ4999""b5x >|xrr˗CBB&M3fHHHh֬ن  tݧOZ[['&&zxx͛7mllv=dȐZ ?^gAVVVqq+xʕm۞>>YYYڵۼy3 ²ln}~7duP:#VKi)x2)'>?z/:ťWY@Bu+uLU(gi0O, PچvƁmJdr90,jZFm1ASP| [ߧO޿ŋ|>o=tP߾}ccc'O Gn=\`@ h׮݋Z  nzz3g;voݺUNSYYY&MZn0U^v#3G3%y^Rw Xbp  'j ugΜԩ$$$(JTjllܭ[7  DKh4uEOuaaU~~ ѼysԊs@.j.* # IDAT mmmL&sرc@VI4mT [>ˋ]v޽;J߲e˷~۲eK:ue7n[vС`ԨQK,yyV>Toxmz왑Q\\lnnRnܸq!=E)ځ8q"((֭[͛7͝1cƘ1c<ϟ??s挻~;lذk׮z޾}{1䆠`YL@˖-7oތF̙3YYY{F5# #G9rx<ެYLMM8j-ZԥK=-۶m|fxYYY^^^:O>Gkmڴ @f{쉈@V 4@FQի O 6z{{n~go&wPNB+ ^^^(L-w^PhooSuI___o;|;w&%%u ]gWgݺu2ܹs|>ڵk4hPk+X```TTׯׯOx;]vD,;>rwej]ʴP/ǜ_/e)N CQ,e-_$ڵsƍp]GGGH1*t k V}4 :E ](ёuvvw: ts禦,[\\oee~*\Ju-"X,vvvFCAA7۔Jhn/Htr9#DrXLӴFAlXlccZ[[ۻw@bbbQbŋiii&M?k;:99qܛ\*bv`߾}֭xyyyں.Sp 'rK+ܡ=TO t% 4b#G @~u۶m_}ZDH`gg璒3337o޴is/e### x⅁… Ž=>@=o߾kj4V}oٲe4Mz i7uyn ĤQFqRTo߾>}:zʼn5kjܸq}܂cms_z꡹}NNXuօw>8qݻR)IR}u?N;;`bbRj5'++wUTzzzd2\8::yyy\"JE?---¿~[x)dȑ!!!ގ;(:tܹse˖۷o+@  B;P@T=F/e`+D) ¡s x$,P,l j\]]YfMXx}9Sv)LMM={VN(H1wZ;޼yVVV/^ɱjf} m,do߾{ڵ Uد_?®B0ٳuЭu(};$Yʴ؈ES;D)4tk $GphV[&cS*$IK++DBaox{{{xx̘1#::855U& 2d͚5#G444>>>>NNN-3gν{bccϟ?_NLִiSyѣGPVV}ss󤤤;wn޼ t<}td+!꽈_,Vkcˤl='|ͥ xafͭSX,FfD"AAAAJJJӦMe2ى'ID! @HLII?\ZRqeZ۷o9EpvvѐB,̪yA=ztƌnnnJ=::"888p,wر'mڴ髯XgLLLXXi;{…  ŋ#""mpTu0{f\,x>wӧy<^ݺuDzl~~>DH$hb\zQQqݺugH9&M_\lY``W_}UuϷPպOX,FB!eY]3$#G|7֭ݺuǎ:utܹ Yf#FhѢڵ9۷ mѢcbу B:Ǐ3gN tIIICe޽{cbbjp7>ɓ5}<##cܸqVVV=zXbܽ{K.wa~QFx&M>|q ÈϚ5K(.]4** -xyym޼i]¦ `0_hNWA@T=(JT>zO>$IΚ5k̘1FFFUl|EeeeO999k׮b:0)))'N@1AZ-EAˋlxf=x$)rB| ݻw7o^@@޳W^zu5?~Æ F(m!24E?g0 `0Uĵ : BWAUiry/_<{Gi-Z533DƍyH$SSӵkꚶb0_/_h4\ḐÞ;Cj@pN7luY>W`0 y?!TÊ J(JvvT&U*%F?zhRׯy@iiT*]`!rƍ#o 3R@]\\XMJ[ `0 |1 _vΝ;gΜ:E2dԩ`#*z@8s WInWԞ>^`0̿V}P]{(;۵mWNA?*?#G~QQQ7nD1322;`0l* \t.,vS?@UV?Ua0%zvvб^xt<~CJ1 w(m 7Pk |!}`0ɇt C?{\P֫arE1 cmmvk`0L ]g9A9@zV:Hn^;7r|Q-`>/GQxP -@`aa{UoZ}J TJ&{xx$%'mٺ~$+W#:2~_?)m$ $j2,x49Tc|& pA  ES Rǰ,:U;x`.'b0}`^vn޼r+IW~ZS=@}(W>w\ԬVZ}3S>zlv@;7tvcVlXիWsΩN<>?#gϜ]pi~lۺƍDX*7g˗/7n&͡jGb Y1$' /\>}pj;`XO?~>>?kT(mY }!Cׯ)$_144yqq#Fd}I׮]g8GH$ 7ܿw_R5i:} /..vi2p!u#j^a[n`an\~ B@XٍBzeͦ}?\TPؤIccc :0##~LvRB|B#G1 sI[;[tDZlll )))K-yaA?{o#DZq8||U;a`0 /)_@?IIzP6" AH!G;-_ E 99c6:w1zb}J:f}ٷ?yi3.]0hȠi |/^.Iuj99;;~֭[΍Q6GI5UW?~컩iԚI&eeemܼ~D؈HjJ/~ݧ92dٵW֫U\vD -FU _:E]xǙ?o߱]ZF Ih4Æ ܥ_J(@ex4hϞݰ}0R6׻O3Ϙ6#99ѣ#FPTSL-,(ܰyèQ߻{ۈ`>ܰJW5"""*hqlϘi~tv`0퀈/ D:e:t,W[Zz׍d?_7@Koء#c} 4Ρnݺ2,OM&Mݽ'd2݆ ee````jb%ZXXX4"T^HXxMXxɄ'O9sfrRrFFF;tN:}9񲥕%Aޙ4wf.?R pځ*֮`0_ 0ݿi(P)DB:ƶxie~XY[S66+S + O $`XֶjP~-*l,,@RQ ,-InU@bmk5]PPln^.<|л7PfVf}|-([o~s;^YXZV&&&?}P$Vv* RTѠ 87-=*}ꕕ] '.3 wځ4mlmH> ПRZ K+KTUEɀUF )a2\:;ԫ d8%Y`0OJP*ٶO` 0ܽZ5o%gdevo)`>Fb2_޾+m m[t׫Ͻ"@`@$WßHۇ$i'$I z7E$<[οJz?H>{^84iҋ("Eb!+P1[(** "vEEEwG̛]h"jP3g۝]Nssrjp76VˢsUmEU"Rײ&Mu<(^|?~ssͥ_ DɩC*OTKTcZ6~q{\]]Ϟ;ƙ s׶V==-Z?:[CgU7nqp :[y+@Ueܧd2l/# __^g~V]]]]M]]]ШޯU= ! AoZ7EAP!p792㥫+P y_V/c`b捛[laXbq9]{tHjcksE5&l;G bXZ[2wtu]]002pk񻦖ҥKJe~}uh004(/+Oj捛uss 1btͶG\%KA#pyf`.W^y!P5+]}UW͟7mZ]}ݎ:|1^"acLוj'O3>:tn۵oGO/R8 cy<ׯݿglb| w6~UN uV)U,(ܸ pk@A@Y A}ɩg͂cp5 Qk͂sԌgF?q|̓J.αbkeGX,0 ͛7 HVYmdhәڋ̜9fqa׬mٲeӧM,䉓'O2:jQ׭ieMuSظ0___rrr~w\&N4fV&dL;F:::..۷W-5~jԨQ?t񒏧ͦs/H40_pi=ACD^^ޚUkVXyȡnݺM[VUr؜Ο7?tLa09lNN>x'.L\\%yփY:J_>DUPP`ee [4A՚qN4@oT*qܹ3EQRN 062ϭbR c"ȡ IDATD J-{ke t8h`ܾ/^ VZ9s̆xvp4u7g,]9' E8wnd\0`FN d'MMM=e }}HJJJLLT*@9| w 1;`LLuҲN~zA3*l+˛!'L 1i1c|;Hq9uuAZ~*Nff4}4U$氘 a0a0Ơ( VY~lp֭[E P5Pt߁.]PER'E_TAfΉJIIةcKW A>kG9JeJ)du/j!SI+ o)9jT32ݻ7''WMLL(xR RWC doTmSCAw?qDKA_53hfJʫD25\$rB"# WK˄ :ϕ&!!Ν;a\.2dرcgΜ O Ai6Y 릫F_  ȷItzԅS)&q"H +deUj)!I\)Q8b7Yee陮>f;99;wŅAXT}!zЯ 973  444z%L&(`` 4urUW6|I Q B)("`S͊ڵ^^^VVVM?,33sE޽{_RI̘1nJΝ; }+-[FDLLLC;\v-""ŋ>>> V}|}}mРA/ԩSs%))iǎkv99-] AA8?y_yρVCJrIITRB~Ewc;H PR1Y0z@)qI9d2D"sNffQ|=F7s[n{....$$&n߾}>u?y\z")P$ >ؑ~Z͙3P( ׯڡرc[$={tQSSĉ... /eee|sݗztǏm۶ٕ7p¿n=|TTTQQݻaw!GuVү_?lwݧOZeeddlݺ533A[o*!  4)^<G XF$rb%!#T)+ XQ&Ո0T) ` )aRI8IYJ˓lll<<<455?%o9j([A(88x׮]p[M;!|:(MIb ʛ5G-ÇG0d~Џ# \n#@dFFFrFGG)ԫ egϞ1c|p|DRoh ٻwq$ɃZYY?>,,Çƍ۽{wϞ=,X0a„+WwyECnذaҥ.[1cm***~ &,_|ȑ #  媳}Qo*ɨfzZy%IVD()*)/%RR.#qI`aL`RR$NQ$\"uuTnn ={FD3rkÇ }5vrrz ޷o寿 SD"ѬYVXo߾AM:}^^^p3fl޼0w`O?ݻ011:thDDĢE...>?~\CC#==pÇ=@S+`Q-%r 0bX,PhbbahСC۶m+**>xȑ#˗N8{H$[lINN޽{mRSSၪ7III[nݿ?\~=---===66Ip`&rΝ;ݻw0;;;WVViii&&&L&yсׯ_wxСtѣG}||LMM)--ׯ_NNN<C7oބGm޼yƌp[$ihhd2:τSNm޼i֬Yՠ(dž0.]۷/ٳ۷xNNN;C``_ՌK"444mֶm[7q֭[?z۷o'Onaapƒ 55555'O0@[8ԩӨQޥ AAwdJJEv6٥58)UbR"J@)W(bL!63 `b" %ERIȤl1լu)ʌnݺOA?Q@@@dd$AEVTTxzzԤ|͛7ݻw={ܻwGP$ѣG7.22ݻC :u*[ڷom[[۔ww۷o?Mq;;;SSӰ;@ !qȔEark`VC 888?|֡C???@޽U444۷dɒo޼lϟ?/--d2;wݻ7LHII.Xn4=:pʕyEGG޽Wk[}4=>eBDA2&Ub29uڟ7K`4ׯ_ܹ aD* BMMͮ]baX@@a P(433ҥ 655500B!pttE"Bg%t+--TWWi;ryUU1|ݻ... xyqqY,V{ 3sL///&QTT]ؑ#G> ƍ{ǖBSSSuG2޽{]vtٙ` 6^ESP(ƏDT===j:E,ӻ]pAWW߿n|>ŋK,AS6" |w#$š}q#_, &A$IS$QGll, 55 4BGP`.)))Ϟ=ouuuඖH$UUU6}d2lX,uPXXHa8qbBB **̌iժ]ùs Xf͚BP/\н{znٽرcwaྈ*uteU>ĕ Z,>xGڄqX\|"Pb X&=~l6-CYY&, mB8|DW\ ;w|R㸭-@  022oݺKZZZZZZ޽͈͝ ttt~g]SSCPUU WUUpQFH]] /w"  HK?:@$;v;x͞pdϞ=;֥o,,,X޽{kjj7nܸxb}_MKK+..j aokkk}}۷wYKKr;vwb޽eJViɹ2c&+W) a~K<~Cd2555ޒ&[nm'BRejjZ]]MB8rH߾} `0Ο?3QPݻw|>/񒓓{ꥦ&7逹@ P=e333@VBBBjM *77wW^~CrS^^^Z{c#|2t0v˗0Yf^~mbb +P*ǏW(G#5... |>ÉWӭa[\\|֭&! =[$up 7uᝦ 6˴v2О'0nIR]v[b͛m6s,---5%ܾ}ںcǎ7n())+88xՕEEE6mlܻw-[믿g<<< mۦT*SSS^Zɝ;w ]tR(2(e2ݻ͛7}Xk)yc9J^R'O9Kz0,==(RjϙUG CCCAQTYYYVVR|r.] }_UUEʢu  ŋ;v|>rԨQ+ <<<\'999rSN?~LQTuuSj /_~-Lso-0wpN#/::P]]d2 Ξ=KRw1I'O#IR&8`PPǏϞ=+V\Îcǎ?233|͛ڷo@%%%6l<  =L&IR &rssH244S jjjX sa^&hFl䱕R mmm:::/Ynݺ xxxXƟVlҤI0=iҤw磌o &yȑ 6tqĈtϋÇwno޼n5p@x_~ĉΛ7bu;knjZþٔD/8;fj_,@vCL&sذaׯ_OJJb2fff (VD>w*off}v555???8ڵvڇ:;;>}z۶m]tck bXr\uq>@кukÁyª68rȌ3onbbB/ѣG 6̜93''GGG'00pСzxxY[[!C?~|hhi߾}߯Ҕ)SV\9yjk6Mo&L033311Q >|שѣk~H++?s…EEEtg#Gfeeਨ(:#GDDP(`2~~~SL?>AA`#B*1 ?^SSO[???YhΕEdfffbb"C6jԨ=zQiJ0RT9vsn`|wzUG>,Y~u믅7nl$k׮&&&~ ~$I*J ]AGǻuFQTNNN֭<  7RuN&&&<5D"Q~A\;?x𠕥joo͛Yf;w)|ѣGݻw쌍]y斮79Z6M& jiiIА=zV AAA F(RWWҶwwuuɤ 8\S9r$===<<\u~`DMM@ Xt74B!YYY!!!2<..t M' a'1lذ  |`b,1 S-Ν;/A999#Fzzqƍ7k*   _,PEI-44477wΜ9p\̙}AAAe56Wd[nŊ%%%$ItAAA_ uuu֭[ d2.[B9_~r>vXPPЧAKyQK۶mkZ w囉  ߨ'O 0UV666{NIIi_nTSSfg Grsrr蔁r\Y[ʅ \.500pvvn5>TZ#G֭ۗ+Ǡ )nݺzӧ .dAm]]]A`oo1]PP %)YZZ ’#G۷̙3_bMAQ"C! 3UAPtA f̘߳gώ;8noosOyy@ ۷/G6k,@hh\.O spqqٻwŋ daa,jUGGG777[>@(N<~zjYYYqtt3gL&˗ZJ ;#>>9" ۍGk{8fwwwQ"80zVZxbϞ=NNNNNNOH'''իWXF1c __={f֭p8^^^۷m&Aaaa666AAA9V<<ȷ|X-]A (J?d2J%`2L&`QkۦhժEDDDhh#F;vpΝ;ϝ;o߾.]q<==K'NNN^lѣGv=a„7n*={ŋk׮=ںu]PP#""Tt#G$%%M:N?~WzzzYYɡC yf~~~!!!v={ƍ미vBCCk# ;;;=zСCw/mذ׮]љ5kڵk׭[~Ν;7n=f̘<{͛3g߿?YbE^^%IPPln޼yԩXhW6$cؾQտ;w2̨y?~\umm8gϞ 6m۶u^~}jj-,,={p`iӦ!C<|pر瓒_"R IDATU}b\zǏG~iep8UJ<~n߿Ԫz?iޛnmbH$o6mڬY&::~ʔ)U |Ao###!!!ǎ4hlǎE%''wڕGN%)055 p^haa}޽Z%ZrVjjFbb"=@WSS]^^^juϱ| ڵkwџ~Nrpuu455utt `Soff_5n33C6l8z7Ο?JJJU&Nx`&I瘓jYXXץ{BBBBBBᇍ7n޼"; HΜ9SՉOAYaau޼y#rssFtRBB̙3B0`0X,!'N_>hС6lJ߿߻w/L| fM A Aϟ6mƍ-[֩SZ  _'fȑ666}xp9Ivܹ3LsNΝ'L}v svիWݺu[vٳ:tpŏ%Գgnݺ 6 ~{`vmΜ9ցK,9~x߾}ǍG}=z,[wuK\zu\\\s#ZjShѢN: 0UVhbnW633k߾}{9}zwfRPPx<^``-[$Iv%((UVuڲeԩS jժ={6^ŋwرo߾ ,qĉptttuuݲeKPhyWm?k}"4^UڵkLȑ#XpB[[[T` XEM c9,&a`00``1(¥miH{WWWߴivBAT5fAJq$ n8㸿?EQ999[###\j\A!l]vݿ?..+ -RuN&&&0oNFRLb,.r\R-#d I qW5ۗ~J߿ݻz_511(jԩ7oF6A9߿?}4쓏 4+;;[*{nΝ˗/o!׮96UR^X&ʖr%!)W(常ZZ&TDɏUWWǧ4ao߾ǏѿAi6l_֭[t]AvlH4qR]]СCtkWt$IuczwTI YYZJpR&WdlLQH5f;99:uͭG(AEEEEEEt-A lǏt-[R/>嵧_H{}3Y)ަ>{[If5ΜU#,DeJ9A)DHBA5+:cǎCQfffM޾}L2%&&o_>+WbѢEodWnݚ֝ qn}}G\˕+WгAA?,~q/x_)TK9$ %"v c0a JNY J%ɝ;w233}||x WbرzzzBA7nXr'OLLL޾}K:̙3p[KK̎ߟT&SI"N( ئNΟOWaŋ?8 ?PM^z5f̘vx-ZD$}--#G>yQGGǐxu-]t˖-Æ kv&LhӦyRRRbqC39rqqILLtqqR޽>|ؾ}fWDĜ9s.^X\\looj*ر_~R*ڵKOO'xbx۶m~zGGz#Ir„ _AA[)/smXIB$UJJB.V d5"L*U*pJ"H `TR8N8E*)"'%%xxxhjj~JV-[nNAZZZӦM+-- kc)I_AW٣nnnw{i ØL-4q())111i$'kjjttt`Ç9WJJ@ $I&%%~SW~~gv$IhرvV8$`mm}ѣG?ŋ׮]ܼyСC`󽼼-[VoqǎkF,HJKKLgKA-FFFu5CxDGLF t1(W !V%Sr+H0 c2(")%EE%QZGTY̡X>|ҥK6mڸn-P8|Z5|jhh` V"-((L& Z3ANlSR*.I)PJB)c ^Hv0PI() JB&5df,KTfddu֭֍qΚ5 rDr] ˽srrjjj={Ν;6v…999b_~\v֭[:upɓUڸq޽{]]]w+}vTZXXp8NOsժUVrppXtaFy @( %%%%NNN #FMOO?qٳryUUUMM͸qD"֭[E"Ѱa8NlllVV3'ðӧnnn|>v (Դ[nl6ʕ+III}IOO?~<zPo')))m뗕ܾ}{ܸq;vhFt$Ç37o4hЬY׮]?===#G>z˗FFF׮]kӦ ޽{<F`sرW^wdAAAAeeŋg̘IHHhӦ͓'OzաC:7T@ vww ><66^^^{̙l2]]z3yq͛'McXw aD"%Ԛ!5kjF0Dv[ZV&E$1&P0JI5g[]U]#$5RB R (H233?W__+++CC'O333˗iii;w](i۶glmm>|.r.];vԩo߾yyyjhhKOOo۶muc9Wf LOO϶m6ÍΝ;׿{yy}k)w76:K LN=ynjyg,2{k׮>RT(jii 0--- úwﮦЀ^>6733322*++344vvv _+W"H.7{all, >:u*@.WVV´4wwwB|xiqqEX,VϞ=g;w|||111޽W=vpcĉBSSSg2ݻǏٮ]#Fܾ}F`>Vt@P3f„ 0:`llb߻D")**d߇)l6f?RMMMnS({{>:_$n*))Q* `x<اڵk^^^Ēmmm---7o޼{n֬Y/^055mF'ɢ"KKKzs^^ޝ;wOzϒZсC/L&cKWr%Rz0oܸ뫡x_~MQTee̙30O_Dѣuuuak ضm۱c^|iggسgό ]]ٳg'$$ԛIII#""FT*U_q<&&SaA]pV4^ A֧7PGHb8'H0&0ÀHTv4$籡ffffi34q``NjΝkjjM2tuu=[nnEڵkWuuͺuTFMMm͚5k֬yM}||LH=pj}kp7DŽE1+J>3 %oH80~ϧ555  tȩ`nn^UUN ş9hР#F0SN!zJ†J%%%YYYtM]֯_?555}ssszvCX1sssehhh#srrfΜy-OOO#7E?RVVVo߾UQŋ WZd2MFw\055:(1cSNёAӘ1cΝ#{{7o.A(JSS3gδkN o̘1u3fP7&  eGJ[Gg`11 @`Y@㸸$]uQ&1 CC 8oU``?lllw;6X"!!A,_)޾}hѢ$.ۮ]-ܸqАFBqND`08T*=q~ٳg޸qc.7F3l#K(oF j]̾3Dq1 {'IEEEzzz%%%p=|}uu\6ҥKYYYt[nZZZ۶m[ʝ4 훜9s:3ӧOO4 ''']o~$I79s$ILfLfn:k֭֬['&&VTT8;;jժٳ<}O>}e2I/..޵k-,,] 8Q+  =?:a`=$IRYNN6IRFFz \ADMM pjdF(ʆfjH֭\.\][[AGGif6mbBCCoS*aaa̓^6l {ILnnԩSNXXؐ!Cjq-z2v m{쉈P(NNNc\-ְ-f(%za3*))Qd2\r%&iaa1tPTW@kiiYXXlٲEMMsp>=㹸믦><رc111ݻw[:,bq\\'+p80OT9|qĉ)S/#GzڲeԩSuuuu6b===Ǐ W-/N@@ý&M rsh˖-6mZCdرcǚcccq5ZDQQÇ\.lڴiyyyݻw///T_4 R_|˗tD&))M ٳ'22d8=!  Y! Rg`0,Y2y/:YkVVX,t$333ϝ;geej|p'J *Ev0_0l!]tL}222XDII "|9ATTT4G _F/U\.;Zۗ IDATww711n"]vv֛W.Çw137151888M6̙3MuttN>Be<|j&Gq+t-+pMXYYӧzիWZ)AA4(JMMMWGU&444(4>,044~x ,A999FrcK n* 'dggDzWBA; uuut H]_ &-Zhpw۷G)++suu-]Z͈ bd b("I$IR?AJq qF``˗/1 vpp>|)SXOy={#FZwΝ/^<{,Һub={ڵkGvss[lrՈ/^\r׮]رcK7nܸqUf͚Y|+VlܸqȐ!˗I+VXhQ V!>>C>}ܹsܹ-X%APA/FDDٳgɟS㏓'O޹sÇ?ggrq$cǎ1F1;R֭[߅/:?? nx׋3sy>9g|g~˖-SVV622WX驠`ddTXXxMMM--;w2k֬4igDDqwwߵkҥK+**:::vuu'544v100jll\z͛7JJJK.,XBdddvdrJJ#hbbRSSS__̔RVVTz} vfffnn.Dhjbbɓ၎@PTT fkA--,A\\\<==---555 h4 {{{ٸqSiii?w 555 ‚@ mٲJZ-<)))&&&8~ljjrvv#ׯG$00^%e6y TUU`uXe…gϮCӳ^MM-,, O]ȏhkkZxLg"w 4iRff&\jAIIə3gvؑ <~x̙V ڵkFjkkuuu e2ܿҥKO<3gΦML&s͚5۷o7m K"UZZ˗/?.++{+W\x_~Yv- 666h;wQTTՅ.] tpp@epBGGgЬ$YYY]]]"X "" A77˗/755䘙-2_~-&&v޽/_"]߹s'$$XGGŋiiid2СC=}A'cccc...&yڬϣ-RPPLhAAAuuЊ# xǏqrr"eee=vZRRr޽W~zGA$)22tհj~~>ǥ@BBA6z??vI&_di(*b8_"UTTT__g^^ӧ2@WW֖޾ۛ~ """ 3`ҥ77/_l߾{֬Ygφ%xxxƍ_x10WWW]]]<T]] pttLJJRHp500p}d[[!R$ Agg爈ASSSi/_.++ O^rqqqx`<{lYYYL&3==}ƌϜ9@ lٲ%##c-Agg0<.***.. 111Ze$7oH$wwXXzR̟??''g$vj|釁0b6p O: 8p8hx..!~ sFC()&.*""tzq8%({j7},..L$hyyy."""𣀀 dxa`=CPǏƍknnnii!HhEEEx㓝Mս|虖'ܽ{(;;;$$@@@GqBSSرc.++[`'dr\\ԩSI$ **رc'OVUUmllܽ{U H$ƒvWWW.ch4ɔy왇GDDDiig֭[x}^|IFPLL 222T`t:Iӵౄ_>_,tv(bxJח.i*+[`````|o{ȈH$677jkkeddFوFqq1<.,,cL0K|=Jah@OOOgg'H$hÃÇ BD_3^rpp9s&" | .`|l+VxIQQQ___@@ Ҧn݂nL6->>fٮ##XPqq7oKuu5jffHP"##[ZZ233KKKO82+D"5660hPDH$aaׯ_C ʰdVTT -ڹsHV Ve˗zxx9ssZŒGYXX =ɓ)S(++nww566cCCW^}}݃?),,F>K#2v}KsEyYʦڮΖr G38Z2=HQ:E%dDy'H0GjyU֬u~1L%y^E7mmo{a````90^zSSݻwW^ iiiHӧOh츸8;;Q6pڵߟ={ɉ) ~OĹsN^^N?zi`;11@MMMjj*,ӣ, u}))*xr9hM&T˗/YXX[9&&fܹ' ⰴŋt:]HHHXXwrr?rrr,Y BP(h1u&ۚt_~޽{aܾTxҥKŋFFFx<An8BCCG0{9DFܳgL :::ϟ?P(?k`~2<88XZZ=҃#"%%UYY^͕PQQu_UUUd5ktX[[KII}JfٲelmFɓ??͗ϯR__e744$H˖-kjjkq/""">Yd``jx<@_@@_O_OWOOW_OGOW[SSCAIe$I4'iihijhjk(Kk 0;;[|Y3sp(@WEZ+}v"hll￯_?ׯgggkhhlݺɓ'Oe...VVVӦM300d+ ,,n``0s+WW>WXXإK.\ bbb-ZbjjDGURR 5778qIJJ H$ L^pЧ5k[~BڵKNNNMMoϞ=… -Z$$$\rEMMMNN."" ȶ}LLL,--DEJJJySS^022x{{GFFΛ7 YƃUTT}l^l֬Y/9YO>OO<MR,X ((B ddd\yiw1P(>7777wxxxbbׯ]\\bbb`≎4D <<<99y222&&&'OFDDttt(**=ztƌ訨(~L1eʔSNttt6lؐehhx9x`II ??+Tn߾ L tz`` xxx<}TVV.))ѣG  RRRRϟ:u*kIYYW^A+0nnn R...KK?YY#GdǏGԴBZZZBB"--ɓ''O.&&VVVV[[OB***nZVVfdd$+++,,pŸI&%%%,,,<݋=J"vҲzCԻwɩk8:(.YLL0H$''8qFBBbӦMnnnFCCC088 VW_z[XX߿]ZZ͛7zzzΝ۷/--M@@u˖-C ܹnTWW/ EZZZDD$--MGGgΝ!!! ,3v*))Ƿk׮+W~RRR"##~w8 'uvvjkky@ oMMMAAA Ŋ8 _<751%p?dJ?/7 a2xyxy4և|Q]E^/r~~ammL*++?m 6m:u!44 Lrr9sZO;Q _~]/_DEE zAnnnooo@@<}Ejjjjj*̜ xcffƍx333Vz#]vZݻ7nذmA y󦨨D"ٳQUUhmmsrr*))y9z괴4??۷Ó>>> ۨŰ޸qӧ޽A}8shڵe˖_<00ͭKP n޼y͡Meddǧׯ^z˖-sppŒϟ?k4.9-nBBBpppDDD^^//mw={v/^LJJz͛7Jii)z RRReC4<044NU(W:QFbh: ja0:|3FDUUw1k֬os#!!׷]xٳga8V?i?:߬c=K.ݺu+4tppԴ:{ݻwmJ|RPPuΝᬭIKK;vLVVv``ҥKwfM?&c\`߰?+l|mll Jmoo߼y3 C)ZzzzЌ*rrrp@ ʆ eºFۉtRKK oiiax:88x^... Bxyy[x fuEAxnnn ۋZp^khh>|8 b޼yGѣRRRuuuo޼AsR(2Zs~@ss3 G kaⲶ#--66M fO'?&%I} ;v@ ,ga(ʪUH$ x,z:qss X'D &9))iʔ)@WWH$N8ҥKl>}{L@ p>ғ&MB+upee%ݻ+V{.|TWW/ZHVVVLL5"/)))--=a45>>~͚500>qqq&`p8&.*QQQK.EO aaa0iVVVYY(ʠ~C'lz|GGGoee5l(Dh'555w ə0a_QQQ?NLLϿ>d!H @u6 _RC[nNFFܹsI$e6??_A3ײ"͹;HiqYimmhݫQTTޮZ;XY~ݫWrssmll8{_Iё,XB^0x~NYQQann#2/H{4uYglmmΝ+** 8f8g0{(}t:cp9QAunnny9R{w__?m}Z dSutw~_jjjiii[lqvvF݄ݻ͛ӧ9rPWWS__d`0+3fxhmmBSOOJNN.**Ɂ~VgϞ>pŋ; kcc3sL7?~1%EEEIII]]]v=<~xǎ߿<444+++$$ʕ+l)„WVV644?tUTT,--?~̹d~~~hh˗/޽K"dddfI<F;;;wDGG9d8ѳJ $/Pd2capOT"2_Q犌c@V^}ĉ2\W[ZZ¸ fff-_x^xYͭ3gNYYYqqq^^ܹj,Yr۷oo޼Z?~\QQ aI[PPfoM:uܹ̓a|q>89rDPPP__Sddݻw{)dPY/vuu ]pq:::pn۷OPP޽{666qssS3ff-NMMz}nn|uuul̘1sppٳgǎC$==RtuuaڵP &&&:Gsݺu/_. .}:77={@2eJii)kvUVfϞw^k׮ZZZlш+W|䉹9@`ZGGGU8III5kHKK谆oY9Cpk׮L6_EEe޽zzzp8UUUh:q2޾}}{Aɒ3#Xmm/ϠkjjDDD̾ۈ e[e]Ck8592uV܎S&qKA aG 6z >%e>%Dq|G}s皛x|sssLL̼y֮];eʔ[^|vڢ}Q(tY[[/\}k׮n${=t萫fCCAyyl^^ |)++åשS*++/^hjjl2x;11ϟ񙚚>fNNNNNN𼷷opʡdmm-%%USSI/_\ti}}}hhhJJ WLL Jݴi]3gWTT@'ǣC_yf1w7)) }X{{{mmm___rƍK.[ZZJKKhkkWVVFEEA}VVV@@ ?>ydeeehf˗/|ʕ+c-WѣGly"1_1!e$E˫KjhrymsG7EG ‘ݔxfL)9LP]]} .}L^^ی,Kmm-p)T*J4^ٳg3f̘1cFzz:voQ&k),,?v9/ OcTHHHTTӚQ\rL&+((@}6 [TTø;w]堒ggpYKKJJT*D nݺ7ӧOsN}} W[YY f̘qΝ^yy3fDFFpzwޒYfA{5kHII shBttt477믋/j'АC @TUUw]t Zb``|{FZTx$} #Unsp'&~&{zzaԷor(dɒqa3| ˌ3={ݭ`llff˗/.//۷o߂ aF[mmm.\˧_WWWccc``hnTYYyW^zٳ999%ӧO+**d2t: 00 Jhjj~zrrQJKKa6+WBmll}}}}}}|||Cr%h 9s̋/sޟzAPΝ;WUUٙ_P]]]^޼yݻaxSwuuQ+W***/^ꪪڿ?E"RRRnܸv}d a9y򤱱ƍ磤EIIiҥڴc``|F 4e::d$kfOR:1h2ob0eD!y8P84NCuvv622RPPV vqqHHH,d$&N(&&fbbVQQvilٲŋYf >,Ǐߴizܹs–b|>aLvޭ44P׭[֭{\ȍF͛7 XʊϞ=Cݫ ?z'FSUU Fݻwfff0)j֭VVVA˗/>l3fdؘJ ^<_PTC\VYY)%% o;v4iӻq<ӧ+++ ٳFXXxʔ)/_p\\\t:(wdgg cDF;PTQm0I A#8o+۔Itƻ&Ei)jLSS3 @qtVgKg ǻx0r&d "?]1FWWԩShAF; `tNϞ=;LUU(h.!::Z>www}}}|F$fh  &sΏࠛ[ZZYhh[8q" QE&ưc? MU#8"Cp A4L\U]cOoIZU YUK<9㿍 k;PK/:yS ``|?$%% >-Oɓnӳ+Wx%%%522"ڟM BBB Γ'ND?#N&@gg?^pL8@' -/݃&$Px?ԟ>IIٹ kG*58/(:Ys0  _rk&ߒw c([B 5(++?+&&&RR__ݎ2"""T @'ֈIHX}G_d2AL& #\8Bp\8 ~dRg]? @ute 7wX{k֬Yc-͛OGG#$$a[nuww111r ]o555)(('O|}}kkk544N<+nذYvmFFSt:ĉlB^t̙3x<~֭7nTTTxzz,\رc|||UN<ѡxQ )<<<11@ @3 cϟg,'''wuu9sAǏڵs~[[۾}\]]a!www11f11˗/F hll*..;w[#:~h*`AAAA^LJFfll\PPlٲB*%%ٳgUdǏeee/))=r䈹yxx]M6t^z+؈Y\z5Fn߾,99ĉ5556mrss٨غukyyideeEDDΜ9SUU(BCC]ÎЇuСV;w[n$$^^^ ..3gh o߾e2&&&^^^WF}/_<((hد-eeeaaa"I.봜W?+?(4Ha``````/ۋ0 'O~׾KRRRrr2Bxӧ/_~͛7DDDL4)$$dڵ=+ׯ^:$$dΜ9ׯ_wppxꕠ ##<<<qqqpEJoooYYYnnnee) ^^^ӧOXr˗mZKUU555URR222rÆ 0NZZZRRҴi>|}ׯ_#f--l>>7o du\\CLLLllG$%%n⢯noo`RSSǏ{ĉgr111>>>6mJLLdE```VVփDDDmp۷o?x@JJ *SR\\ziN&7 |rF|}})JAAA[[Dhoo_lى'W^jժ@yy9ׯ IXX8$$DUU5??nʔ)SL\fݻsGGs2t,}X"m۶Svwwsvv:thaaa7_uׯO:iݹsիWNNN^:~xjjP--KŽMNNNMMRяhḹ? ^^/eT}5bd80000000FC{{;/d۶m&&&ڂ0WWW]]]<T]]=III&&&Y~'O`E777^^^'%%5cƌ;w]BB† &L7V)--?t???Druu ?~<`޼yyyyliia˵k<(--- gx 6 5ٰ yeGlmmx̙3>|V̚5K]]K__YYYRXXںm6<?cƌ:#=,/**7nH 4I>qF99976n8}t...yy'rc:::0G:hlpʂ!^c/ :R```````x O@1Bս|hbss ZMMMlw8::]vi?i:';;N sҥ<҂*YP(222l lHHH~~~ͨ?iX vL544d}A`qaaaqqfYP͛7C'pAh4lnnePz{{яv2'w144f'B!!{ dyرco߾E%?Jss3HD 3CbqƩS|}}m$SVVnhh@]6,Y2777 ~?0 OS`cz IDATD"ۏґ"QWW'##YrΝ)))iiiChookWq 8|`zz@ll˗Y޽;))I[[0m4_(YY٦&;# =`kVXX1O 08BߠI9@@@@\\<,,lҤIVxUYY.,diiicc㤤+V $$$"" H$ٵk8<'((H2C3sݻ{zzI$j:`*Cb:ujdd$F;wܖ-[222$vT$jkjjj+++Ye4_,8Ǚ3g>JxxU<?(ch>^xaii9RC{{I(X \\\Ν; LNLLd2heeC5l@ ^^%K u1VZZz-[[[@OO@٪d~~~_;rjjjjjj߿Fefflllz*o ۬ooo/,//Hq8!!NL6mp_lmmoܸc]/-^xNqT*B̌?*;|'N-))ܻw$֍L&kiiQF֖:`FF:NVVV__ҥKUŰKRT*//8m.%%UQQHŋ/^ 'V|rvv6 uuuUUUػwn޼ cyTWWϙ3 %%Ŧ_(v޽{&LPWWwtt(!H==='Ν-%%e``PN /BMM Tg>>>w򰡪~$$$ϟ?1778qIJJ(Ž9ym(^VUU[l h"SSS*ڎ˗/:8ڵk'O4i@3˖-022&&&<<\SSSGG'((h:i \fUzz:4`E@@sJJJŬ7nhkk{ȑDpqqݽ{WYYٳ𼑑5бa VQQQ' a'M6oެiӦ Cm߾]QQ޾3gNffʩS]&&&ؾ}ѣGBCCGmp߃c@d2A?`5 `:Nat몪*%%CinnF#q ooC͟?_HH(==ѣGAAAGD<` mjjTTT̘1fYHil6 ǏOMMnwwKo߾_޾}ᜑjjj7n܀_^jj%&&Ξ=C^{yʔ)ƍkY>";;ұKaC*2}?~~~ +d27oge'^_#sSSvch~~~k׮7nP .L2EMMmݺuh|WWW eppp펎H )))ɺv޽/6rEEEK"h48ydxC y-@8uꔑ3gKo600  ,x jjjjjjŬp8YNNH$_A@wwwxKSTA\\\<==---555 y TUU`uXe…gϮCӳ^MM-,, AO3%G[[c=:L&… K,T ÇL&ѢEc-GA@JJ F㬟 F5@2!l 111rrr۶mu2rVmyxxkjjaXdŋ;;;߾}KO>w ǂuYXPP5~<?a" """qss>}zwwwCCAnA? ~)**>~+..NGGŋ+VȈGBY*((PSSgqpp.((%;w>}J"ǧO>~A'''vkkkeeeF޽{O>޿#GO>|uh;yUUtQ$%%\x1,//vRSSdr\\ԩSa謨w睊YZZBUUU!!!!!ǏЉ&Z @KK yHf222P50؜Kяt: ̄`3fJooeXx19cBQVVvy5 ѣO5?WjÁ)))ɟm7ØFݶpÁ~ %$dhȎ1'\͛7W\y1{{rزeˣG_NR.\aÆ׋8mȐ!7n<;Ǐ/_ ={l``NihhG9v옣cVVjÆ ](++3gN||˯ڱ[nnn||ݻwtziiW]޿n:6]\\ %NQ8{{{ݻH$ϑN,\0!!޾QPB*L4iRrr ܹs˖-aA_~5555klڴ_8"3](ϝ;wԩׯ9stf544nݺ0/g0Q(r BP(rruwͭ->'ڹͭȊ"#+####wǎl>wcՀY s"am[ }4@ $._6bĈo… =߮Dh4ړ'O{[o~䰰F3g.] %%%222?~0СCoܸ!\JNNNzz~AAAzzc?q_z5B߿/˜;wR__?%%e߾}:Gsܹ(yyy==3fLi*k׮đ3g@344=ztժU %22~ćH|{{{sĈeeefϞM&aŜiϏR222YHŋI$ҢE:@"Z[[sssv9]hhL>}T e AAA rrr˗/ȀD"ŋO-AAAgΜ$~aA^^^Ќ uHzgΜ###;9AAAFSSӍ7xX:vN3UD$8/+C!Zح> cߞ^j@\]q>JKK BSڊa&z/VooctQQQ˖-7h4ZffGlbb"lR.QFD6oެ]uޑx׈aruuȑ#_M+}}}.\m6 sOԤ uuuׯp8...pQVVS믿c8;;?x ##%=====**PK}݂ E ,Y&uʕ+W\9PQQWl6L2cH{A]"H"$CbE`݇AI;g knذ|K5<aħUܿ1cưXv@|||jjj}}}O7_0`FKKK+...,,3f 4ΝKt)t< .IZ0i%8'OqFss  -zݻܻtJ;Phz]U/#Cfs_z,}DDVk[sKgb7'*ԍmݻ#"">Rŋ l3555<< 4s>=?{J%%%uuuܹsE t} >>>=z˗/())INNV%&&ٳ͛yyyedee'/*Lwgѻh4> 0 nN?ř4iƍv)))tp„ ><{,Áe IDATaYXܥwppHKK0`@ ӧO aX_;87af itЩɓ':t޽{&-2eʁc2ؗӧ\zy&4G($vJbTUUU.+B؅c޹s?,CJJ/_>z*** ,~l6رc!N:p;lICCN83lvJJ ͆!]DYUW&&& \iv>jI. Ҙ8qb|||kk+`$8 00p߾}Ϟ=7nKځֶ9\UQvvv'԰Z۹8V]/0蒏@>_]@~k{Ϻԙ9sŋ ޽{uֿ[ ===a?o޼9r$FHHH5jԴi,,,wܹST_U 4XQQ''( MM_~Ta$Xff`rE+++@XXO?4|pyyyOOϚ5J}ngN7QPP訤4f]'OVVVQ߿]J޺u ޵qL&a 9qӧ7md``0{la1pOMMb \]]i4*4~۷ 0u򋖖)߿ ٸqAM|&QT;w*ӧ/2x'NYFMM%$$ȑ#"e=:{XB$88ɓ0/, _5JJJyyyPAyyy[q:/^~ÇYv׈#_C͘1c׮]***jjjO>~8t}v؞@AA^b(>ŋ􂃃MMMh4Z׮]`0F1eh7S'N|𡳳33#&&fbdd$衯?a„Bz$>Bb0IQu(ʒ%Kndd' zzz۷o2e?~,pPQQ ebbbccff͚d2BCC'Ԓ;fffIP>?lذ@3Ça$Үjhh 4 Da}h\bgnn.^@ƌSYY!p)[2G 0Yȸ߻FyKp>.O i~@.ߩ?~ϧ hDKH!ɓ'{zzC4hÆ F?~|BBŠ+n߾z…fyyY޷n>qQQQ"p8iĉ?2dٳgccc'/^VWW7.33S]]H$ ,!Pm_^^nbb:::pႌ agQBB5;pႍQbb_zU-@dd$ɔtȈ`a@#۷o4i4UtzvvWnn.xO< =X[[oݺuʕjjj!!!555"_Z<8i3Ν;iiinnnÆ {.tWfeee"hllRe *O:% @ Ql޼iΜ9"1# &Me˖ٜV.5Us L6ᠨ] ]OSEWCHm.O_S|և.:&եRyyy"SKK # --Jy%%%(# XvmXXH={6Ty3Òqh xԩS.] sLLL&^|ɓ!!!] DgN% q w,8<<<-- zN233UPPxe07J{*++~ikk9rk3ӳo߾ӦM  HӦM6m9s/gϞth"cccwww3fzF}i;V%===*3l0gϬEX[[@˂gϞKxyy Za @|"-Ewq!U!KWq@u4HKƾ eiMf >'G}n]q_XwM^%e˖577Gw6 1߿u322$ÛlllDbii X;w՝;wȑ#F`xxxkmmmmm_3H {[nu*cfNw`kkKђEEEOsݲe tiGJMMM +A߇!g%TWWE0lтO~a*e)JDD<˥RT*U܍Sg6 =Һ/QFIJ$CBB֭[WWW㄄ӧ˗-[?L:՛6m$B&ϟ? 3]_^tjddd}"?~ؠ!ZPǁC7g|!˂,eh \Qðg֚L>z- nD"ܹsӧO߳gP}BCC=<<~g&P"x5{{{mm+V_~ԩܭ[RA|N`{ :TM;fNw@ Μ9c˗ {yym۶m֬Y}p aӦM=z۷&Mdgg7l0===g̘IBCCE.͙30 啕A4S><,, ~dX .TQQniiYjUALswwxلK$55޾[nUWWmAAAsuss3339SS }̛7o׮]zǐin)a8 ` uɫZc=V@:,SUUd?V놺29}Mee;>}Ũ@ 6#G\zj@'Ogqss۱cǧl|>x_xr\p8,@ *=C`(LppI"DgƘ<>]`8cqFz>V\VlVW'xP1-8gJ9N+R ׯwpfٳ q{{WOQQɔj>--Lg$iկ^((( G޵k,oܹH$.X`Μ90H;@ zځxXH0a|8ۇHRӔill$dZuM&9:=EOO͚XZB]Fyzu!>F+@ EEEz@|<}罦[nȑ.]p80dfggyf„ sqqIF@ ݊TUO6q>Cq0 #0 #0TC}EO:vͻ6Hx6\tpb! @|o0L@@(_ۓ䒒 L>-##cii|PTTg13]|N">Qրg>F.[]'lܢO@8@ i0 *zxxh5 +++p4ӧO @ R}'H#o ee:E @| PPPhooz555ݻqOKK Y T |ںG 5R#~i(TS3Uc@ D(w-۷oSSSafJJJmm-at:B\\\{{{AAɓ''N ?xO8q@ W@ B]vYXXXZZ~]З8⻢7o433SWW̴xa75xyy9J9sl޼YLFFѣO`X[[XDl֭;~ի1tP##3fڵKEEhjj:`ɓ'GDD FVVፗ.]JOOn}tȑ#r;(ܼiӦwQ;;.IHHݻw{{{ _mhh͍'N\ڼy3N߳g' EV^y暚AZ=˳|^ѣG02jԨy<\.Fr\.$!$IKKxĈ?(ʬYϟ`0 e˖3g>|4h֭zzz--LhGzj$UYY9uTYYe˖ .?~<%%ECC#''lٲ .IIIɚ5kX,JMKK6lXbbbTT*11100o߾]^^C:NݻwȐ!Ynذl6[MMСCrrr>>>d2pŘ'O\x1::ݻw %**jΜ9u8vѪ?>|!C~_RRr)++'$$cnݺUAAaܹ޽{u痖WVV~z222[nwJXTggg `~uu޽p|||:u7o۷d2D4iҼyΜ9d2?K޹sBGDD-UUUǎYtiEE+MfggW[[[XXhff֍O! (@ >:;@ /kkk##Oٳ˗/8q"55*99YUU5++k8{{{?}ŋڑibƍZZZǎV @ҦN/,^fKb``vZ4@```RR\deeM<N &M 5?00PII%%%ӧOQw޾}ٳg8۷?{,㮮\./~ϿwC%Hx@LAP__/^۲e J%H.##Ύ~ܹgΜAKQFeeeIJQQA|>;cǎ;vC Ro @ i4Zyy9˅B$۸q˗L&@`XpmegD"DbX ++;~D''UV _577P(6lppp6+@$qqqk׮-,,޴iz```lllssٳgIIIqqqvvv6l8piee%ҥKÇք p}KK 5k~L{{;SQQʺ~DDDBBBAAg̘x?_u١rss3Lkjj <}"KKK9XYY4cXjf2}ȸq/^7nL#jfVn*YsKM !ν+qn߾Bӫaz555;S---W8qzUV;vM#>M㐐 6l߾]]]ܹs'O2e ,|(//o>V$Я_ǏLmmVYYYAyy6X`W"XwڊO< IDAT2t@  Wlllzl~رfff¢gZD o ?JkKojKW/xݛe5J_*|[me5ok^T0+T71?8pHkm,lw8f]Y*G ^UUuҥS=zk֬a2|>˗O<ICC/8p _~n}655UE:._cIBʄ52DMM-""b„ k3HXXӧ===E>X^PPpd2޵k 58:wŒhb"gEΫbgΜO?8^YYy5o3stt$8766S(ÇKkND//t>㸥Udddsss[[MIOOgX8PhiiiffbŊfWPP P@wq:l>LMM;.@ j8Q(Y @om㷶9|.zfCQS[_d>]>ţGf??ġm4ӛ@ ėŋcǎ3g Î=`0 bbb`CCC|autt֭;~ի$~z##ÇP|'_fS__y=yyޖ_իW;r" m@ d:x} CpOHwqv,A~D]~`csd S fVW״wU?˯c+yC/L[FZ07Kf΂]B X._6bĈ8::~!FB ėT0 ?n+\>.K"rq &'CʋZTBD"vE=}_?oB_ÜHdn]h@ ķO?>77w߾}"Dt]3fV H p>_V,C&[9+_xS\q' d5/ m)BYYk߾}w­ <:bر$UAsϿ~˫ _ =˥Sځ*Fz2mgy$"F Zۚ[>Afrd]S ?o*++wS刯Vz"e̔-,,o3KJJ ս_x+x~a2ӦMSUU544ܿ2qqq .䈽Z/b1͛w^FP{0>Oz{J"J66\_oR5u.@kDXpIͩM,f'!=FYYywv+3iҤׯ?~|׮]W^(׮]1118q"l\vmѽ-ŷϊ+*++ O|i&fdd+W444|||TTT`Z8*PRR˃  SRR)@ _(#vWU4 0ijjpqe K>u 8m)2頱y.\0dȐR6m8p={ 󖖖"̘1L&a05[dɉ'(**ZhQffFtt4>8|ٳg훝pv ~ҥwܡP(1w+W[n=L 7 FSRR222mA EvS.jqG&c0hliimolj2֦c]cXwqqZ>YYFaNѣ[nBCCo߾u9}'%% 0P_޼y@ nٲq7o|sg>yd&&&ܾ}͛0 88˗ cܸqƍGt7661b֬YOOOg=:?;xnݺ|Cp}cͳnj3d{{{q1~嗃\}!N ^:8U ޽{.]c}󔕕5770~߿#GD bKKKLH27o,_\@ v{[@ t.aX`y9,hdjӕ2pdy8hhSP/U<vup2L&=<< % ,,**`=s%D"qIII0!!!d2~xmCCV\)## ;888::FYZZАGPttt͛wُ [͛ w 0lĈa'NLOO%K22292f**|<""b۶mTuW d2o߾ ,xA =}^ A>}Ľ`2L%^˵kN>=~xiSs @ r~`Dd5UJLv]hj5udY7kY|>r ti„|ii};ؘBP(Y p544`zdUYY ]+**D$!d2br8AAIoyD=JXF 7nٳgkkk?r111Jљ6`cﲲ2[;Ҫp8p[mm$QTT`G!iii J޽dJ{ߔUVã(PYvÇKb~whhE>g?ĭ[ ;cyy={tT*ϭDXYY=|񰀞xb KK˧Ov@ /+OYӠFZ6/Kk2_y<9NCu;[----ƌZk~*E5k۟_PPO"M<ȑ#l6{nXO_~D",%%Eڽ{wqqq}}}JJax:ooѣG/^wc l6?EIDb```\\\}}'OL0BCC[!H(%@ wԳ22200ߟ1`aYWߨmaV8H$oryt߷`]<; +eG tn\V*zgشiSxxxqq1Juww0a|dd˽y #F֎?>22RZD"111qzzz;v'$aXbbbTTMkkU>ψgXXXڷo߱c|v>}ƨQ ě7onذf\xŋ;v%} :ڵk"7L\\\xx JjjjvC"_zp~jt|]&~all|ܹ|ėIvvܹs>|ۂ :|>jr<p\.pG3|phbddԙq4Dp ]@?\n}}}߾}/(**"=C|iܺuŞO>-..644mAQL&4E|t"xiii-T@{[[}}V0}PI*rr22M,V|˪'}&|nixy%mGxt!E@Hd"2pwN _R Բwu--l>|8qF0 H` 0@(rr$vNG8] C$rpQVaSDG]=䷶;@@| @ XDH]pcQ0R}'H7mu[@AW()P@ &Ĭe{CCŋwINNNNNNJJr EEE]5bbbۂ|JII g^_h@(#@ v*++zY }Rggguu_l6[FF%{7nذ7nGwO6www:^RR"ɓ'wyw8p L7oެ0iҤ:AuՕΝpDj;L&=~^a{{{iӦUTT(ݭ [EQ++~͇;ܨy7o\={ٳg\ $%%M:h֬Y^^^%%%G o?~D?rȇddd>dee>|˗PӧO.]w{ggg?~|ӦMOONN|2LKK{aVVV{{={y[Μ9sl𧵵F=̙3b֬Y+W}? l6Ohgoll {_u%͛7%$$`K~>!!g@QflDE D"H$L&:"T_7nqvvvCCٳ_윙 `0g>uHܶm͛]*~ܜ3]UU5<<͛a0-+&&gϞ]vN:={xyygff]vħOɝ8qbժUp ~kii丸SNyyy5j0---ӧO666&&&>|hhh;wclllttgddd.] K`Xnnn7nȘ?)ӧwޅKIIIQQQcƌrpp7nܸqzs~IUUյ,222rrr=sssW^ kkk߿pBgs`8%%%))I@@3==N9윖`tyyySRRo߾s3gbkkkSZYYyZߎ4ś#"{ll,@4dܸqǏL>T vɐ!!!;;;E|feesϙ3'--̌k]VVVSLp.sf.\w!V IDAT \paŊرcSEm۶ @#I&GEEEGG6oGԯ^b ؘzzz---R)@6((Ǐڱs΅C1f/q֭K.=z4l:5nooǼ~`Gjoo'H=.Y|yUUŋxyEEEEEEDÇ5CHTmpppppG(JsssaaaNN?,$$b leCTTTHHhӦM^\Iv!''',,yfL. VYYqvv+W|MMM gVVVcƌ!ؗ}?k5jT}}鿘ޫ𠠠 ++kݰ߸qEXѣGl߾ɩؾ};??ĉ,XpUX,yڴi:::̟??55rw``˗/{{{y"00022AW555a̼MLLw Xz5)&&&bڴi-,vvv=ʄ Ħ&9l8:::::t߻w/::'|||78="""bbb"""𧰰0;777cc۷oaODR 2JTTTcLc2hիWt:\ Bx JJJXTWWP@@(޸qqYxի444tuuaHVMzD"СC$=Xd L&ݻwg@7 888888RRRRL&&2 9Y,־}ݻN :::t:soY"@Q;[MR޽ۻw/*oiiYlY?k'pP^^`00VQQhuu& Ӝl8twwc PAPYY ;CXXXJJ… TP(𸮮N766h:u*߄ ,w !!!!!!W^\II޽{=tuuDr2XII W_c8,L& ۲|8::˜߃0{RCj֦G$+++߿+}^|9qē'O677tŋh/b ޽{S\\|ʕnݺq'%%9::~租~:q™"J˛9sf.tqq ={bXXFbbb"$$)N:5~~d?L&dBw.>>>>>"mm>Xb֭pG oo www))*ӧP h8b>}՟nƍ#-Ȑpƍ_#-ȷ:Rqqq1 :ظq#?8 W'M&P׿JH2Yl&b,6bqDQH$ "# &@`||TRJ _vtF# PqppppwWoll?҂IJJ5kVm~\:fÑ%))ٳ%;@ $&&ځvӳ?\;%..m{%ዓ>AAADF}FOۙr=;Z_3;߷_Lf666n8"?ӦMSg8y왧%נ88-=aD\S;@$M@@ &FgԚjl> &@~tbMV .8888888?p݂wguONZQQ1R ! `ա@ llvgJUC (z9HdH F]m<{vSc?'bJ#NP888888ϙ3gFZ8|gdd 8#Wlv~~PK/)E̶. 12‚( :i]|A铷>O%?Ci޽mO铹9LTVVjjjvuu/_ btuu ;`41P! !XF宮EEEaaa}g?a֭L&2ydϞ=G.\8p@iiEDDnݺaѣ;wԘW5ΏxAAtlaG-@MMmE/dQA|F<9M*MHSMK<n{`&a2ctI?qp`L&sd2ڗbul R;^ Zz(Jrss 񣳳=J511 wpp(--uuu3gxyy nUUUpO`0V˗//++SQQ[__Ν;T*VKKkժUeee>>>ϟ\l/^033P( 崷 _| ؿݻo~!Y@]]޽8bGZvu;Y6Eg0GOC4yy2EM"=/C̄ba(ﻺMD& T&MRVV?x}>}h4 99y 'N{nyyyJJJDDD9sSNN.**¢wǎ۷oHܴifZxŋwݵkׯ9YP4<<ӂXzmmu>}*,, ߯_NP֬Y0mvd !}b $T7=ڷoeddz9UV䰰 ^HII),,dԨQAAA֭-6A">}zZDDٳgA߯RAA2܂pʕݻw_UWWׯ1cƨQrrrϟ?Рqӧx/_?s挭hBBŽ;0/_O Ν{yY[[cK&&&!!!}fͪSUU G%%k^~Ǐ߾ ==&>>>ׯ#c``oaa5CIIɾ}DEE544lmm˖-2OH$2 d׮]ϟ?Rn߾mddd2׭[7sj;ECCcE&Of&`0Y,_!VVlhW t*& ;[X.f͛ظiӦK.}k׮|(??޽z[[5kן>}z…UUUٿ{VVVssÇzVPPPZZk׮oڵkQQQiii߿{.*//_VVŋ?0K,),,\v7U4ueֽ}W1cf}^z}VIK%ܹsg޽{wii6lǍWSSs9??oڭ]v%...,,,--rrr f8+YYٯt:(Wѱ1((NA64ꏖR.2PYbIo.++fD"q?{,+++--O?]zcffA$---g̘q}>>>MUUU{fw-((h``tRXZzڵkUUUEEE`b~~w"##_Oe_ꢱ^Ԥ>>_p*I5CV\Pvvvyy?, 0yEa3U4sL===`nn Uoڴ)##s?YQQf͚cǎ OfeeUWWoݺo̙0̙3D"100˗/?qB!K.x?IDDHˣǏ8::Z.]&((؟֭DWOJIIHIIe:k֬4ikkyÇ/^ܳg2mڴxzzBoE8^:;;9jH7 (¹:A@D~XemxK;B+k?׶շtl PE20ǂvPUUU D7/'شAEE PVVIMMmhh?xO?$++hѢO>!d2BBBi.V7 ,| &22!WIw $BQE( L&L5!n\rKUU˗/yNUUUEEڢ/^ f.&&`08]^]]=s̠ 0 TUU)++cĉR=|㕕UUUH"%%5ȑ# L__*b<<QPP~`oŋM^ٳgcǎhkkÛ&BYYYRRӧOpxE_LLL[$퀀UƦyeShΦ.`NhJ$+뚙L_ \qA1*$ 7n,]tݓ'O;Llbb0o N:FLLLGGgD"q۶mК7 АL&[[[?y-*,,7n<&$ gpp6F3fLxxxdx@DHH_RRb>>>"X7|ڜ"7o\zL>y$tӧ===( pD"0< !!!/////0gzzPWiǎP(nnn0СC+V(..6m cǎ]dЂ>AȲe,X %%eooeS~~~?ʕ+Ϟ= hd2?5jm۶8?/W4*''>^ϕqƩr=K$o߾o>AAA[[}Ξ=؈5((--ݻwomm-L7oG !!!&&bl$TRR:~xDDDMMݻ>88}5j8gG*^ Z`|XO Ap( SiQeh.ZUN@橮|* r*PS-\}HpQ% uMz!muV&Mppp~Pྃ W\9~ׯSȔ)S=jf6T9Pel6jXd2Y,`2L&0 1ydEKJJ|9o<iPۢEI =naA͛7HjR| >>KKK##%Kw---#-(i)x斜\]]}ڵGDPN8ӧBŋfffqqq/^(-- ]0ŋ˹ŋdx`0ZZZvYUU5N=RA\;]@ Ν{t@}}+ƌcll ׷ LLL6ld2,Y2f  ЈnZUUmbb¹'**w!;;{,WWW?~<?88˗/0giiĉO0auuu}}?PTT訡ݍ sY===CCóg5''^8eʔdLĸI&UVV^~-##\>o<%%%gg熆 6L2Ç0f͚eiia:Y#wΝǏg͵?.-qc޽{/VRR:rfwܩyϟ?;88d##8::={222'N:APQQTSS#H>}BQtd2944Dž\PTTĦw566FQgRRRgϮ[jYAAA---+WAw Jxv˖-!!!(~L&|,HNNRSS={vnn.L444dee>|˗444~122rʕT* ?&JKK333]]]%rj qqqw#N:ujQQь38SRRjkk=Y2:wug OӧOچ,\B|ɓ'QQQ(FGG?{,33NfPeXnnnfffeeeO^vmFFlOzzzfeeFEEQ+Wܹ͛7\'***l6յYU0ii'O<111 ,]vt:}ʔ)ӦM{N***ZZZ444l޼_~}6LFTa[ZZ:;;SlOnݺܹsg^h4hiiAQ455ɓ'w.))QSSۻweaa!hcƌڀHKK(C5}ĸ.~iժU殢СCRRRppŋcƌ+++oGWWWwwNx*==޽{/_,))@^rQ]||<.66g݅/^jhh̆{kܻwɓNNNd2ӧ0Qex P(l6[[[)mۆeNLLP(֭ZץK7o޴v[CοEl6fX2Ls d2 X \XXs->>>EE+Vܾ}G=??<޴ii.]]]k|||]]]yyy&M=z4,''Xr]jjjjkk+++7o?aׯ|mmm> ֆFd2͛+f.\ƍn;v`ĉSN}著}ppׯ-,,bbb`wwwիW'&&@hWu7770w7n>>_~-D200022Abs EQ77tCCCccc]\\NNN=Jnjj!0k֬8q"Vǩpsjժc֬Ysϟ?DڵkQ999***أmnnϔExsΜ90:o߾-))qrrҊ[vm^^^MMN?e+++uN>Z'Mdggߒ+WouNN;7}ɫQF5jҤI :::İĠMA=x`XXرc#""BCC Fww*DPP2SVV~ l޽8@gg'tK7o^LLEll,2`̘1{0aTWuL ,,pGNN[9?| 3 ===@uu5m tDyyyQdffc ۈ"EW\ Xl\ݼysXXD"H$WWW^9rdr*0?pKK W ,hwNa^0V<=CdXD"PYY 'ZALLLTT_Rjjjd(pzGPɜ9sś#µkצN ˵k֮]KRݾJbY JURR~=KKK!!!klZ,/9yUٯ8LEEEڰ ڸn1aJr~9߿  4}#&&&&&ŋA BPo߮^UUE"˖-KJJ] Htww?tPnn/%K.\bƐ\;Ó'H>vɍMJJs玌 J500eEEE&l^w޽{)ãqMMM @ PTPƍ'NXf˗/EDD?.,,| **++9^EEEKKK[Bwww'' رZ>eeeǍzIAA+=lEoEEEY 򵵵𒚚NPt:|eeeE~//"3;wܹsX쒒XWWgoopBx!@]]s&U0E5555559 8?҂?a<"""0:>8oGk(**zxx@/ {ԧ6NuN2朜 RWWeJCBk %%%YQQ[KWljNqqqWW //o; "''gaahѢoex@Q8=hkkځfxyyB +%FFFǵC ^z{ H^qppppp l6ɬ{YuuulǏ322z\J&_Е z@FF3LfgglْuVγ?~LKKc2p[}vvv6صkdCtzzzBܹh]]]vw^:^XXxʕ+`0tׯ+**Ο?_PP .^8..n̙Z+}TN:U__t79r>&^ᛢrcuuuob QuvvDQĉ0Ҿ}bccy8g᭭zzzF1AGGɓ'KJJܹ f͚@h4Z= qss;sLJJ ! лMlll?>}:--f}EQ##G(ZZZ95]ѣG߾}f[[[o޼bx]"##C$`?nccӻ @KK  !!f|2%%%%%%55ڵkn=xwttLNNZ[n05dG1777C[zӇccchޚʯ2a5ӧ1Jqرƛ7o^paƌ?ٳgEEE~~~ԩSiiiaaa03W>}7(2w=};0`Wjj]RSS9Ǵa^,3K~>;zg`_BXXX=z."0~xMM͵krІxzzlkkkwwwNם;w^t&44ƆkvRWW?|S ECCݽÇps֭4mƍ0}…!!!?~ e#G|}}`1;ESNCCC.\Ç ;&7=*%55uҤIﴏcK^zϜ9sΝW?>JEQtϞ=QQQVVV۶m.\K9mڴݻw(((߽{oKNh}}wϟ[:0]ᡬ,7˗/qźpٳg͛=ȑ#^^^KOO! oذaڴiX vܩs5N[zC M.իWOυ x:rioo_UU5{ll~ģ7NQQqvtuu)((L^v I `{\a2-hd„ {c}IJJvOC6n@ $$$ x* R_N4L&CF(qQ&dY,6fX,bX(FQH$ " Mچ"d1DvÚ;AHQ%= ϱ ΀& #-: g4 3}xxjjkT@QhBc AX ,DNADDAeşw!ΎW.)trHk7i?pe|7'Op88E <#qD"?`ht&IH@ ` 0JT~vSSу>@WPճ;So⻂3(0 "77wƵ8?iӆW&fJ B)U -L6*Gd:DHػ(W9pG/JQĆ(׈5jb!5klQcQc%bGF4 w;A~۝}TP 6|E/?Jx'oJ߹홋!MA8аzzz?j@@Jn[:1RB_b0I|Ls9<.zxՔ>:NZq%C;ARKI`l@j5a„/^\?JR(j\]]߭hNLLk@]ZZڝ;w:n޼Y###===fՠҁi+Wy/]mU,TQUbkjAL!kd~BegZ抡dddtԩ\\2C'r9E2dPj/6~:z߿gϞwm,YrBXg1S\ݻ2ko̴ ,pss:\o=zl޼y…ǏwCݱcv oܸf͚nݺmڴi(ɶlr= LczzzmuoAd$iTv梘s^ڛAQR6bݥKXBCClU>|o߾V Q[ͭK.˖-H$ĿYHHȼyjd,D"##/^ ƎYf\_!K&zl(_¸i-p(2y=8\.S*VVVI&15塡ǏgRŕّf!3]O?4wΝ;Ԇ2Ȼo^{fޡqbbb6o,̙̈s̙9sXIɓ+Wxsr\.w111ׯ3...}rb888޽{؇[B$?~˻w{ٳՉ+W?ƦFVRJ(d2ѣ[{IAc^67fW*9J gpBIQij:a1FD:7٣&"rss۲eڵkNZXX?~|hhhEEoֽ{wKKKйs^8x`]v@ ܾ}!lAee%B(33s^C4W._0Fc}0ӘPz=:TT^^^̫sNN577W=z!77\֖i277WѣÇ;;;;99oEEEӧ3ok2ɄBرcIԻ:tƍk3E=C#PQQ^z! ?:8::.[9)мj%D"۷oo߾˗/WU1} >/}}}X{H$ 3lڵkY,իWCBBZ@8X/iR,[K)Rs erȈ"$*I _}MaeMk 0z&M4iRIIٳ7mڴk.Hsœ'ON2I֣GK.UUUڵk̙Liu&2&LPnVVEaX=,z~.]*XЍsH;ٍJWW7::F-0)xbVT* ##)~BZj„ l6{޼yLaّ#Gaj3Z ^|4}]bNddӧ5E˥iLu&vqqQO#^xh"}O@XU%^̳̂G14Eiqoܼ-)e2򍋵ٲLJJ IRWWWWWW]gϞQF!d2م d27446xzYfܹ㊊ .4mllR5 ehhϥoڶݷ][;_[:t|rDBtRR3Lرc=*B{e[YYm޼YT>x̙3󬨨pwwgYYY/_@*ݻ7--Ǐ߿?BȐ! ,P( mrPw+@;::jkkÇaT%Mo;wĔ%$$,ZO  ~ǧOEFFnذIZ~BFb̐J;vڵ+S~5Dn'x>>۷og0n13gAh`ܺuܹs҄Ba߾}Gt=z$y-_ \>EQ,6xq8};vP(WZ LNNLb6} 0ڵk׮Mfnn.> l[6m$mmm׮] WOp޽.] c|u1W^=k֬/'44Mz_izKdffΛ7/''hРA6mbgddl۶˗fffÇWLѣGv߿f͚nݺm۶I_nݺ5004,-yg' 3@vc@ /ҟgT<;3&//_YV"7 yVڛuߣn۷oe7_. ZiQQL&cm ԇi8kjjZV6j .VLƍ|p87\iM7Nb?HTTaEҴmll: h*64\wMDp8n]Ad :(7 wl( |tAhiiu Ƙi)b2l6EQ,flE$L 𾂶JxOtѣGm;)%{aϞz򄦨@Ceg})?3.߿ll4V5$6YR% ufFzY:r(]Z}>AxsPIn\m1իW;wnҤI'Oddee=~{LT,D1  //}@4hBH<ba<.G&W(U<}@~|PQ;7?ŋm[AR䕤WJ%"oݻڵ?+ *++[!0f:::Ν+))6m3v.\$φ` Ay/Jy<\I>ȋM}Y [Ȫ1 R*Pʓ%9GjЎUf J$eD.;L𢘗\]Ft[]ZZVw# ,Ҳ߶m2---00\OOoO>mj;w}:޽{trgΜY}-[llltttzq}f;w+ ۷o_gnȑ#,XP^^ά?~g{ݸ8Bo=k,FuϫTfՔ}@T:@VIĈ/~ag0<9%C.(!pU*Q} ^R i;uAdt|^F ]YPUV*/e>UD,/l@ffkKL |g^^ޯk׮P~~;w 64ʸ~СC:ڵk'NX}eHHƍ#F5yK||ܲ&N_|9--mݺuMC {nS&000pBrr9sLLLΟ?/899uaǎhffffjjߥ*b4VHcP<==ǎQPP U(ILJ؍@Qcծ0BeaRou֯_oaaa``mjj+ҥK̂EZWڵ7BIII>>>ӧOW'ؽ{ݻt XyTVeajEΫ$g% oílC̘1ʕ++VKHH(,,?~ۙ4k׊D"'NMII3:Ս3fĈW1U|>̙ݻjmɁVVV۷5kVTTzx.,,L]xallqAqҥw , IDAT/^;w!бcG.\Psߨ]ixR㨄W$IUHecoktp86֖2BY^QҤF7Qދ(RHR(?:t(&&,==f[[[߸qc'NL<!4}k׮y{{egg70* ,,LR0+%IBBBzzߩS~5+$Uȗ1lmK)iksL{cNL9?͞=!ԧO//>qȑc}a 0Tȩo^{fjY)iMQQQ۶m֟ٳgBN Yf 866633sڵ<w'NT7+0`B ???777յkcnk/?~ozzz7eee͛7oݭΟ?rA]t+puu߿?577_`AXXX!333>|xϞ=|7?<{(kǎPyws˰yӧO?^Ac^67fW*9J gpBIQi"ױ\|ta+W4558qbAAB(((($$ٳ={B]tҥK{Bʊ  nݺ);wT*//:ouwu: m-JLa11bfb֡5FFFJrwwwqqqqqٶm[EEB(77ޞI,ZXX0s#rrresssu>裏lmmϞ=[TT4i[r%L(Z[[ToV{^^ŋ'Lв'od@LPQQPAAɓ۷oocc_0'Zڮ]Μ9_UUw31o޼#Gnnn^^^Lax1m\:XOR\,='21C$_H2Kʥ:Z\kEHURu enpϞ4>X_֡5P(LHHHJJJJJJMM|2B‚)oB,,,rss}---꟔qM81%%%###00),;q=855Y.,,8p)S/^R`7oׯSl2̝;w+cLoddN֮];Dwtfs 033cX̎,kժUJ2>>ȑ#X,eN96bj`ɒ% g\ڵkzÆ J2""ԩS5FgTTTtؑfgff^xvT{Ԓ .=zt6/P( mo BNNNr~jèW$I* (R(=zv):}tii)3M BRa Rj.իW?{ !5~;vt޽JƍYYY) x<\}8ؙۚ=K.*REمfg,#I.D#uu5m';a-5I?ŋ|>O>[#GΞ={ȑB!1޿Ypqqk䣭o߾1cƈD"OOΝ;3?\.DÇ B6W_}s rEX,f߉k#~[d\.wvvf~8qbRRRnG$f.\;wH$ꬕݿPPX,f_^&}7ׯ_ӧOS'DFFЍ72ׯ_?mڴ~M___~ׯ_oc|zG~۷o222 ^y fLb@ス۷oiӦ1iBQQQ[l05cd"6f0E&&&ڍ΄T4#AEZ}1DϳX*Tdjf݉I9|[ 7 Y҆>>:&~wQv@c|c5z{!C4=+>LQQQ3g΄wƘi)b2H(JR$I$RT*U޽1iiiy P(FX 4`+z&fb#{mnnP>MQ$b0B#b#]lT4[Q)43R 7@eӤ3 K;:(3N],E.]}GmfzB84x gwl"RXf7kh No>tP:|tq E[`iiIdf!I$IJ,.j}<$I֓^"lݺݻoT4PG{;v,5###M(޽v=Y]po޼ {寿366vuuՔݻwe2Y /߾}\.oxƶl6!BR\IjX,ze52?@J+zI\/ZwƬin%޽;00ѣG֭8v옦BĤ5#INKS?ydA۷OWWiPYYf͚N:?:::22)V@ Oۧ)~iĉMiPlӫ… &LhtHMc@ -VPhfIDRQ_x}:;]QQ!8bĈf3???88MXdΝ;&M333oؚb͚5[nҥʕ+:wFeeԩS6olmm͌ʦZVVJnnn9s0aaaoSX\VVvZ---KKYf]tFJ.;~x--/2""G&M ?E=zt֬Yl6{yyyLbf;w!$:t|;ƌ'.]ZnO?O3f>}ի=ztlllG~~ {x,Xߪ3 wڵ~zM;ָLs}ii6b9rd޼y={$}B .T'۷/СCsԩŚ7o^nnnJJ Ƙf3e"#..āSVVV, @~yԅ$ظx\0\uzK{:CRT$yTTZ)x;eeeӧ%rni̘1ϟ? tuu2dHAAAΝWZqѣG'''[#qdee9v`1MӍ~sss?>rH77os8p $$hԨQ[neZ#""z D̦aÆT*B"O fgg$ìTT...5RRe˖k׮UVVX,TT*y<^SEPVVÇWuH$ >@ JrxܹcT*iIc|8V[QQ,Ji# %Һ~ ugBStU%*Ie.,100a/_\+9rdf֭[Ν/ŋsrrƍ|رXcǎ]Μ9Ç:tPg۫W!tXloo?._<,,ٙiR֬Ydmm=gjc78:::uyO4ٳCBB֬Y윘ɓ'[[[;99ڵIELMM ŝ:u:s y͜9SN|rnLMMյAWW7%%!ԣG%K8;;իWgeeZڦMbqǎ22z;wܺuΝ;;v4i?ܸq#^&7n޽SNyfCA,--}!3F@uUUUrIIU1A/~-55ۻ^f^Y%%%uر!, /jFWD^$ L")e~n9eqLPfՎhleH"c%TSgIl;w̘1#22rL̙3SNE1"=ztM|?S~~~ttÖ-[|}}q6n,H֮]:{'bbb_ ȁnݺdWW &gϞݾ}رcW\A^DǍ啙yਨ(f۷'M;g]]_~ԩS6l`*_֖?D{wȑ_zҲSNLBԩS'ND>5򉊊200~zStҡC=<<0{{0fW^l6cj*7::Z=J{#++K"tԉyᑐP#MBBBu1rPBۢF4IyyyW^2eʐ!Cvf͚J={;VTTD"oFeddjbb)O>d֬YFCM6m޽cDrO6mʕLAC~~~7P&:u*77!!!C ggg*&999<<<<<fLfGw}Ťիק~g{߿Ϭ6mZrJrM~˗A1M4xxx0e W^o3fڵѣG4MWTT\p)sLR6hРz6X^u49 IDAT#eGRg"(צ0..`cZGDc4ꛦQуwU'ڍ>\]]e]]x>fΝ;5v>СCZ'O55UCu".t)Td֭uy5կ BBBjlEuܙy}vܹD Om+BL}||޽{kii3'LP}<;J{{,unSL2eJG]c0c.{Yu{?V]`ͨu~4.n͍$Ņs"3dJ2eyD,3\ŵYD^U\.Um̧iS$֛Fo|1=ec=jԡU?TϪG.]8p̙3kdmhhxԩظ,eaa.,,|3JWW7::yuyyy=3)(((000!!ٳgcǎe9"r9S4PX.˗AAAoSӤIr93OH$xɓ'nODJJ s^ jϏO BҥK666O.((DXBGG'22۶m{n B'OOohh2̎fZtivv6B(//iS:vأG2Uv{m-C˗/H$4M'%%1}|ݭ6oެT*}LQ^^設-˙3l6;00pͥ?>s S靓|r$BAAA?sRRRAAյq8g6jwE+\ohY!B@.v%F2e T}zva/H271| ȶ| 5ZaP:lݺO?=~ŋ/ZhI[[{Ǐ733ܹ33s!ٙ_~666Bٳg+Wo TTTdaa1}>(00099G,VFӧ/_ޱcGBj*P/">} lllvյky~ӦM377DLGl[6m$mmm׮] WOp޽YZL&[hQjj*پ};> 888 ^' |}}% 3ء\.W|zYf]xQ__O>ٵ]yso^(]ΰ`k׮p8RƍlٲzrСMWLgjhZ2Գ4yg' Z}1DϳX*Tdjf݉I,+Tm@-n{ǶxaJ?{6|IE"bb=5QDc4h1j$D4AcPh%bE* +DjIpooveͳ3/[;²,˲ p?9E1 ((41ƺW^w^w^wزܬ,GxZr0!??Wa7GZx<=1Ͱ !Ĩ!xZ-B(aw]ۨэf4c&1fXaiYBQ<ǣ(̧xy-x<|~f[ uC L(*@]?yG#wj iD7cYaycX-%|1a1TFFVS:N vIo /7JY4rwkEQ>^0Ba6 yEBC#BD_TE HJٴ!gx뉆F[P #,+ Rh (j׎=ɏ՚z('4U &&&d̙_~e2.]x֎m{=wѣ; )x7Z; Ќ.(M. * 45v죬b}L&ӽos˕WƋ ,his!>>>ǼPkhE65h@7@WWXP|U9C:tPPP1vXnΝOIػw_ͭ)//we2~J4WlȐ!oKºnu^|y~;B1qvڙu֭ʱcҤIbX,<=DBBB^ VTTj111\B==l]RÇ/_ibbdɒW\9խ]-**޽{;vMLLwY3flܸ!1oB 7oQu窙Nl0O?zj T)sw}ۛ;Q pBBB9-;vl„ `d+쀪{fJJf{.Ϸ)(Q*՚|R[V1M >00i:77WTFEEB }||BeeeΝ[bųgBK,yY||7N>}v/qqq7o1cF}}W?޻woTTݻ<޽{U*UFFF~~={Ba>0`ŋ˲رcGQXXGC qƅqesqܹ[n|rLL̾}t W\9Uݻ7&&񱲲:wWSLAM>ŏ=۷o|˗>}P:|Ϟ= -Z;Wtbۖ.]p/t*e>|X@jjjyyyƎ{YJ%+4TJjvFPD!0 3z[P/nh~>e {Wcڝytt+W MBȗ_~ߵkW__XБ#G֯_ojjjkk|Cq!4t;|%KBCCmVVm܄o}r{_? 6l;NLLLvvҥK|AtCCCC=e|9ʭ'[N,9Ϟ=100z*r**66V8::!MvaPIIIddI&&&\oʸL0W\Yx1|r`teʔ)`̙)))ܹj۶p|###q~" 趪$ 22رccƌ.ryS;j` !\['Է2d31*)W2 J5 )DIe% e ":7l` AGѤ{W\ ׯ˗+7E erBQRRkH8::fffr˺\n___777FkiOu`PP;JLL\tC:uֻwT0.]z !\( *۲,VZ-!DVk4J:|pVVVAAO:5bĈ*vF%''>}ZVhhhxxÇ_;M㹹$D"eԩ_ݻw'JJR$U;;v ԨNlBQThhu o߾o#222/^8u>>>^.oܸ+P#@믿۷~ chUXT`e$5o?_JY&-ݷWW2ijg|ڟ^Gu4"bKԔbmhhZmSM6-\ݝO| uWNe b"o߾۶m֏5j٣FzBv5k,G2n&<JYĶ-7o5k[l|ĉ#0+ 6?.\1ƈER}BFa(1iFZK'e;ZJ|;qe/r|ـ $ۧ\KWU$i\C%otewh&۷o:thk^²,˲ p?94M3 jii[jZߟ3gggau妦uAtQQ 255%\_*r7,`t_Y,!%,lhm,=qb.5^~37 @F{ !5GDDDEEAZh**c0,! Xϐo&[’R  q{۵Gg͸b s[ ܰàAbcc<՚Ԛg)*eY8BY&!<1Oycy<':@Xv{{չJB>"QKH M>hƜT-… Ԛč8mb73~?,anxuEI_n2PZj7z/Tkx<' K={<z$n/.s;;;ZnB˲,: ~ IDAT2 S'MܓkhiZr/Zmk ^w^w^w]J))SMd^GvD+@<!L! Fc153 [Bd&FM#{5xXcPi*lgmSMP9ګ5;dnd@3,Ͱ K3 ð 2 BQ<ǣ(ħxE[PBx4C |^l)Uk$kWcvt򍩕g#wj "1B,a0<1 F,Ɩ>0T*U##cUOTOR5!d dbCFxm՚(ʧcGB!0ҌJC<O̭s4Aְa #ў;ug\5;W^UPBDħhShhX(jܜ(D7$=@9Գ/`oYO5`k^O4$52Zȧx<aYP *F4m@IQD: Tem+MhHZ؜9s6oQKOOӧL&^XXV镗@`mŋ===e2Ytt׭[o_&&&2|@d&f ~};QV^OaPW)+0 Q)u:/%2,ݔaP]vK.ϛ7UXx֭[)))'Ovtt zaW޸q\.ڵ+[;6#11q?3ҥKAAA2v̙ź6l?VRۦMd2GQQBHT~gÇ9rdh1* 1q21?NJ 2r 1B H7~~Q*PMsx7GƎ￧:to=wB(''K.ϟk߾1ci .!aҫa &x{{۷o޼y)Sܾ}ŋ-?~|ݑݻqƍTG$%%=~x…ݺu355Eݻӧ8rH #h/+4JDzD ÂfNNB )j Ȳ=KaOeim7u\\\֯_;w܊];ֵkWL6lذgϞ!d2ٖ-[u6sLB1e++޽{!G!:ww7}>h]v0`@FF~={믿@={vddիLbggm6 !?wuu m[E#GΜ9sSwnaacǎcݻaRRBgϞ/vwwdVJOO|2/d2YNn޼kkk#}i___n[&999ٳJ%]v ޽;BH7uZvnnnvvvsQ(ٳgoٲ!ǝ4T0Ǐ $͛V[洴+V:СCiӦٹϟ?_7R3fԩ͒%K•Q%ǩSƌí H$-yeԚPUd]2sڵӧ?~͍[chh_RRqLV9sfܸq5k֬xgΜl_o(A.)W9ٴ+(Q0%g*U{+3;e -͘IoP-8XJJJnBϞ=Z !!!~bIIIE$^zO>=zKvvV~W oooPNqckkkhhx޽*w魭Bk-s+kH*_]&MZreHHץ6,5@f@M4>h|ɓ'O}~#GL:+ֳgψm۶͜93::1tG__4,,SNUDDD;v,''!|r۷o;v4]}ȑ\WЍ7BBB[WC5jB\gΝ;ghh秛,311?411 Ho@O,lmPw̓;1YI;cɟ2FT`E6m۶7?!4k֬[޽{RZZz_/_SBP(;vݰaFy?\NKKKb+B󉉉/8Orww_tiYY˲ ܘ8p@R!;p=/Ҏ;R~ԩ w}ٳSN:t C.X@V:%$$DEE4mhhhhh1iҤo޽{GF)ʓ'O*JHdjj+f͚dns۷]|>BU___R㏍?۷oggg|Wqqq.BݻcǎjnI&۷/!!!''gSL֯]ӧ:qUN]u~+++U^%6ƅBk]`hf.ϷZׯ{F^qZ^9˲F4$?V1 jݰ6'N0`@:td7n8w\TsG:zhTڭ[Ç1cǎݼy~֬Y۶m${ɽzz7/_ mرc:u5kX!Cg\z^$~or!CT/@Qԅ WZf͚I&!Ο?m۶޽{y)?YT.[[?bĈ#FpC !Ց#GvݰݭXO>:th||<> OR&>\jUXX؀BCCO߻vqҥgr|޽EEE'N]_ &̚5k:u֭'|­ߵkWjj*}5.g +sw8{4 c~{0Qbӌ|{NNvv|>5;[!<[,[a|a(ˢB Mm#'OBfy7x/@aYeYa*iaVK4MZ{jύjqvvbu妦\oh.**du355%\_*rù`X9%,Ka1,!,z8;Z[IZ{qV…1"[ Lj}j <ƸZy|7vجU24ϥSϸ^'_OdQ 3 xAv@k6u 0_bBBXeYa'(ax<EQEx<1M*Ұ@!JEtci3h,,,J%EQ +caaјJ ;y۷v.0k3}2<'AjA^hf3aBA'C9Ba7ɔZx<=1Ͱ !Ĩ!,{OL#4O5g^A7rԚҌĀfXafKBE(0 ThFʽR3˵H|{F?qui.\فyA6`?K#0c`11"1"J2?xUbk)))4@\`رtA___*Θ1CPTٳݺuspp [n͘1_~oV ŋߒ{x=+;@Wh&=,Ht4ܺ8)UO,)Ĥ F۠LSI+[6-OR[FF;S|8T`n޸qfϞ݀]{k׮#F4Pk2wtu{qv@]Q2<%-mn 4g7srrj M[O#Ԡ9mdJK?˻jڵkMLLܢݻgaa>DDDDtؑ[pww711޹sgJҍ,j+M3f>}z厎=͝8q͛25kHR{{ÇMJJz7LMM=<<¸{2dȴi<<J&&&7nuqquo9rdРA1cTTT8::r9/SNM2SNFFFWGuԩCfffR+\}||ƌclllٲ[neddp6ʪZ_i)U(ZaX'|>ΦDTk2r7p=?IAlAmIu"ݻwoLLLqqnkkceeu9ÇL>}=zԷo*<|ŋqqqU:ytg7oLII)++[bB(00B]|r>}("|g111ѵ5ۖy1DPa{n<+Ii+ف}_>%%CǏJMMvڏ?:zo޼nCeGݽ{}{w޺p´i͛'H=ZRRrիW߼y0VZeii)X nTTL&k(!JJJBw޽vڵk={xŋ:TPP0bĈ &BN<)RRRB _Kʀe>|-;wN&>}>RU|||NeTjnnf ;99aƍ ٳݻeee󎣣c޽+uݸqm@@+WB۶m͘1յSN~-addd<<Rbcc5BhڴiܝޒI&q%cccMLL[jkkٳgÆ 2L__͚5a+W,^8**r@gՂ ^2egV1r_u/ޔߵxz rOyattMϟf͛h``zjplllZx IDATZښ5kBhh[BAuЁumܸq;cW߽{~9rQ۷)mEQ~!Bʪsαݻwϟ?k׮E-\0333))ͩ~I1⧟~W(9BHTr޽O4JU B"^J$&h* .DFFܹWZP(=ztp\]]/\:{wyGRM0aԨQ .L6M*޿ܹsY;wLMMyf޽pڮZcB"![K2rۙUd Dܲ˲6!RZUy+VXXX &MYZZޫW/maggׯ_?f-Ƙ:[.toJe~~ԩS=<<<<裐=4GP(rqzHMM<M*~ILSY3gB<88ĉ'N,[L__~ƌcǎ(jҤIFFF?RUBBBllڵkb{믿"|JJJiƽQYAJ WRX~&/,QvR3T% ]v#԰4B=̧MvdRvZL&۷':F={6??ӦMCeddp8hР:ى'?~ 100KKKۺuk[x_7og=mvnsK[;H$=~ɧNBY[[sdn:++K7?%%Ɔ[Iy뭷BCCRSSÇK\!8pԩS?:4GO>I\.ӧO[ibtxrb}S7!77յQ ŏjLH$uUJ LשQaxx333 v(KOOiw=zѣ7|e:9oyyy1޽{<WR_EnYO,lmPw̓;1YI;cɟ2FT@"}3*iH$D=uԩ_ݻwǍR*ǏW*"LWe͞=?LOOGeee={[cǎP~[>*))qssWT?C㙙 8xMN۾:;m k`eee,s۷OR!tS{{{٭[NDEEVSNEqx`ݜyEc|JJʰaäR  }vn~۷oߪU<==322twsٲeYYY/رcwJuҥ1cpoxWTTx~!//oʕ.]Rծ˖- ~W]ti djjJuݿT*n?Y[vvܣ. !0"!0&XB|rY@WYو/n@v!T,ZuhE($NU +vvK?iX`Arrr |ڐԆTtU~mh**U`YBA#!L, %%=O6(Jcq7 #W_YjP"x']* {AhQͩ5; 12+R*U,˲,a gx0<a1eg_~y|cX-[_>IMS˪ zmNMNnN\҆|il񣺏rjӉS'FҧeS?_? #jmcHZ2z`7_z D j3mRϧ(֎=s]v W^^D"Wyh$B!DV9884h^|>____ v ,RlL |ZThs!,˲,0L4MsOiiV˽j/ PTqͪ_ ; ;̬ 6!"3BjI*4j„OB^a1F ih!2sS#ƇH3L2`bMt9īKͰ42 K3,0 2 0fXBE( TnA -%zGabccqޛ/f< xl.\فyAp}0F%F1f0Rc}cJjdd7 H#ت;w6nрvfڪ5;@QOǎ7{ZQihCVVry40R㕒jT#0殮͈E$4@򚬂%">EBC#BD_TE!!B3p~ԝwGBE<˹{^sz!!Dі)B>ˊT5ZisWK$&4m$/;.==O>2{aaZW^^u/^d^^^nj}effpsټys0bĈv뉆 @s rr<]euTnnL&{>CPJJʊ+]T*}}}7oz7+V8::vDmFyy##O>dlذaΝ_@ nqqݽ{5 LJ­3g5Bի˖-kCHLLOа}Mw&+T4 6P[h%u].%3ʬ& mH@ yy"ڵooO`ߚti>^z5;vyLLL>}9tМ.]\xcƌyqۚ#==ݝ[ .!eYZ<6O?zӧOGաC*wiff6z7Xz="(**gϞHT*˹jAIҜ=nݺ؝;wvP7 xuFXh`XcQ54"E aTω`JIB>=v$ V}Ѧ/1ȑ#F;w-GGG#.^سgOTگ_X]͛7T-::Qgώ\z{|||^^ޔ)Sܶmƕ!|aaamG!:w?s+?rș3gv?uT-,,wQc {vtt411qvv644LJJBs2lժUv]s/d2YNn޼kkk{i___n[&999ٳJ%]v ޽;BH7`{5.eA4Q(RTX{GAbA U(T *`.,ҤH]`˼ynKdNN23'$~Ͱa޼yCԼ^^^G֊z+//' ݻwSNURRRWW_reGGGTK`0\\\^ʖڵk***7oLqvvs`e4ʘL& '^^^gϞf}JKK55͉'tuu9ݻwSH{;vD?;ۨb``ǫ.}|ռ?zdȐ!yyy[YY ;t:F3p6D"7wtUa{ܬ%NzC"ӧOtϟ۳1 P( eǎgφ/!Gn7beeS!~XΞ=;}]v{NOOo޼yJJJ߿OMM MLLDEE]v-===//D&lbbR^^~ٕ+Wq(@jjW%$$\RSSsƍ={<wVSS`2NNNlGdrU yYuuuRR2޸qce˖]p:77w„ lr^|)##sm"_xpĈ{izzwZZZv 077OKK<8==ǍG$1 ۾};ByYnnӧ{J bhh1-MAAkr.gNII訪*..677Uͻpuu6lXgg'd֪:::}[KK322 rΝQF8::VUUeh{lb۷{b##orcǎVL& .&"+ :T䥈Ba`0Hb <\]\o0guV7늼QSS0`@^^^FFɓ TPPNM0a̙^^^ҩDaaaho^xqTT◧ //o7oDFFz{{khhį_P(BBBpw+0668q"@@@`zzzaȑO}z3fHJJXv#Gn9H$*ե pwwkjjJIIquu)|"##DpqqQQQ\xqϞ=d2YLLV8c0k׮}VWWRPPPIIiʕQ|oiii7V|ēǧDGGϞ=- UXX&,swk׮y{{<{ٳgDR]]-)))//ߝt:WXd_AAܑWC dIEEvk@Ĕ$+jH5RR?7w1Aaږ6& |St*$r7fdd[XXxJ O>vI:::))IKKkobȐ!\Wiood2eeesss322544 ǹ?>|֖-[`JHHHhjj1liXyl+vK ݻEEE7m+qWWݻ/^}Z^^Ζ+++axǎw\zaٲe'O8q">"++A~FE 2f[YYpBccׯ۷ojjj>ҥK.((ӧCcǎ]&hkkǝ&222ƌnݺ.>eynSLyC4|pMM1cp֬Դ̙3x'&$$:VNgddvv-,,wesUNHH8~8~/rR-^񹩡}yuCSy BojG6߁;oxx|#欣dWQfUUU W^z0::0p@|r-Ȫ*|5~YYכť BRT*&aXii) mսTdR@L0aB\\\XX\V0552eʒ%KŰ\'O#111YY7n ϧP(p~<>>U]]<""'..>q.QSSĻz_6l[aÆ&PWWAL:5//OPP֩rRSScoowLohhgpb55===߿l2C322KJJ<($$K;}ٳg544aÆEEEWTT,\p۶muqq=?@ff;w)ˏ9RTT$++/7;{,ɅVZ[[[KJJ^zuM69s&11ѣGׯ_JJJ޽fyxx>|xѢEMMML&sѢEӧO񣷷wDDLa˗/߾} 8p`W\ussUëV:ydII noe߽{7 IDAT'//Ň۷o?{ī,ݡқ7o]㒒h߽{ puuՋ(5[q5AAAwkmmLش4*vvvWՋ@耰N631(G{_^"-!aҒKmijoo@O777hjj033{auu5|={vzz۷t'MU۷#""0|p__ߖ&YTT/_LR'OUUUՕJss>HP('O666&$$\zޜ 666k֬ࢢ"v9scccۅeee>}.]y把 @UUsƏ8s挙DSSJ%g,Dsrr"##,X铯/tuss믿jjj> pD"EEEqn7HHHdggٳݙ466 r=ddd$$$4|ػ344'cǎwڵkvRSS#FFFɁbbb㕔8n8+++AAAOOO)))|njٲeBBBlWbb%DZz^tݻ޽ }=0 ۺuD𐗗WWW7nikkc]d$%%՝A Qttte}`H$RGGǛ7o9VG!((痕USS3}􊊊"##XGjo}vEEEQQ͛7dz՝ }*\ݗJB&6%%QƊ޿PJ>JT.k/8wxOyW xOn:Pz͛p&22F 寿 ~y6nAAAHBNN։'p .\p!Ο?x5DJNNBF1bIJJb0o\\v_544444۷^FHHiVX+uuu1 gZ[[a+W+WJ𐾾>2''gxe< a0~PRRu8NNNhjjFGGo:>zӧO dvvvakqA:^PP //sN8㈋.^8 ¢pÆ 2vuu566r|e111Byرca|{{#TjccN8#Gk^]] 7݀rտ#HxUPUUerGͼC8QTTd WWWKHHXWH1ܺu@ uuu0ڵk044ܽ{7ᅤ@#Hd[W<*e C%]]]v~aڴiSP`I /luu5Lڶm[LL `:WNR}||1 K#YNA>ߧH0d2L!t:`h4:NaFѾ}og…'N8s̙3g:)1bl\\ŋYKLEEe„ l񲲲QHTT,:JJJ?R(3U%%%%|; %%%sWW2cr:::lix?u ?g<DFF>|0!!A^^Z__(vpyѣ#""NWTT@,8bbbQ466ᾱK k֬ :t[|3FD255:N.***++{e>2*:.Ԉ@ $.X_  صkו+W֯_+9sn޼IѺ( }׮]L&իW[Ihh322akky=:KKK=zqqqZ\ǏT*5 `ڴi9srssoݺ8vXֽu!% * ͛ɓeddRYYL&-С3D^^k SԄ* ]?Q?d0'Oķ400Ά{M>}˗/p^eqDwj*p…;vhiiQ(grٳgիWMcǎiӦ'O:tÇCCCedd˗ϝ;7cƌٳgÔnnnL&sĉ...^k֬ٻw/0,$$D__СǏfF:uj:::0~ر'Nc[?{lѣG:nϫ,8AAA-MOO]joo:tJݰag=jkk}ծ VWyf333;;; 9spn˫ K.mmm577wss+q;d-[D #deeku+}me ,0 ~igLU9<@{W޽{իC(++ 6L>oRLLqǁN` ++aؓOƏO& %%$$-4Y#q;B DU>#j@ Qk…+ot !![=K7U=.@ z d477 &8;;.D"tht@ ~I}hя,X`c.YXX@bot`֬Y>F7Cx~@ +k[[[ 1@ ~x2o@ =@&tq-UF @<H#D _5@ _.N!@|utx@ $ ͤO@ H@ yHIdL&d`t<*#@ zu@ @ ?־\AG ;puh0L/Fq5JJJZ[[{\2@t999))Aq=lGSo@ 8:aۂ:d2:Ng4:NqJJJeKF #@ ^g!j@𧵵uD3rǏs#@ ^g!)++%Uj0 mhh٬Qs@ ~@6 @&wGXr@n`L{I2@|?F!gz?d 7@/@B HLۓ8C<zaXHtRrrrtt4[א!C6mY|֭3f\DS6L Y#?}mj~G[# ||||lbb=ݻw>}{Y0 <|2<8ŋSN#ɖp+WjNYYTk@ ~wF-,,,""0a„'N)7oԩSWޣGJcǎC@w +--P(=54IaQ/&H-R"""HHH899;VXXÃ3uMg###CD"͜9ð_v xŋO2Fo%C ֦Raaa 0ٱ1cl޼y߰yU?UO99!Cݑp7nSW`oV\d2={F$vF4{{.l[<"_wܹsnnn555۶mKOO)ZRNsS֒M·rnDL&H$~Ov( ͿhC amm@I$WZT+=?,k{f JP(ϕCMMMV,_zֆN<z|sՓ@ ,[,<<|ܹW>|8>~ԨQ'OOhK.%iii=766&666%%%ؿ6l,%%0UVY[[3ƆB Æ SRR>0hʔ)d2YMMmŊT*0''Nmmmmm킂^۷oРAd2Y__?++ C G]]]78wFD### 0hҤId2YYYyҥh4gggUUU2۷oQPP077ǿqҥA;v OlxA:uU:|ٳg#L>|رcp_|7oL6119t萙uuuְ*..9s&L1bDDDLn:kkk==ׯ_.--]QQ[l0ҥK3fXpرcnj7[MMMnnnP<*[ӧONNNڗ/_0,77ws `-={Ukǎn%;vLWWw̓O ^i&&&smXkYkk-a<;vhiizyyb_^A< FFF'NNJJ255 p̙3O>}޽ .ñիW'$$߹sΝ;$I@@@VV6((()))..ѣzzz~gla˗/Lׯsrr !D $$$=zقoS㇭詠PSSd2nzwuGݻwq%%%%|}}oݺ#*** SeeeǏ vyh}},,#?OUUUUUuwmhh޿ ={axhhhuuuXX؞={`$޽{&Lغuӗ/_0>Jjj_UQQacc3|&05u?ð2Ǐ0ad2˳sssao5""BXXHWW&!!!UUU[1` ؆x6c~1X۷TVV+&ikk[PPPRRzj͛*j޼yJJJ޽{Ahhhbb"w~qJJJUUUpp0D0%??͛7 ;w,--x𚚚3f暚wvv.X%22RDD:655aɓ۷űaaa=''GFF&99ٳg Ĥ̙3+WɁY_pt^Ç===}VTTҲsNГ/L2uT&) 0nܸ˗/Z 0aHtvv>x3>|gx'fϞ촷;w7LV8}|}}###kkk!XҥKߟ6mujjwG]] T[G^otGiӦ2L o!3k֬dYY|a…k׮D k۷'%%9::~zݺu=P<`mOPm&((8vXgg7n4plM=ZOO6[QQQbbb2|.-**ё@ M4 $H_|Z4 }6>ξvZ΂t,;^@55˗C ŋvRTTy&eɎ͂|]]]!! iiis5 uwwijj$ ---Gз+fcbbt4kzmmm'Owbbbw܁Sk֬\lcccx9$ IDAT;|7UO]]uyyy0٦Mlll6opM= ƥKLMMBBB˱OLLd$>;@tľDDkPL&hl~~~D"&0 BX"TTT\]]rbbbx Wx?'3L/..%&&yNyi2744_"n Sp}6q7۷ RYYܹs=zhܹ'NwރܹPUUe***yy.\ kMʖp&c_zu-aٔ)SfΜyŒl%$$(,,[6eAYY2϶\Odm_3ax(--|¸^MUU`y̘1ǎ[t/ \^^õŵS`چ0Zlsu}}}V}8^2̩6+zח~JUUH$VVVOɌ^xԩS?~x/+iӄRSSSSSY?;zׯ_sly :uꔟ_II I šG~'`„ ~#F/DTT޽{%CL< ,`"Byy={TjiiŋO]]]0YssaByy90LN:wxd6772  [ xzLL˗/ƎK&RSS՘8iaXSSӐ!CDEECBBh4|%=}F~ @ z--- c~vL&F})>>uƌGƏb[[[ jjj"=رc?nll|x̘1’uuu+++*' ɷnhArss]]]߽{x_%&&mZ'F{와J뵨T9ܹsiiiׯ_DGG'&&^|jcO7$]]]йё`^W;vKRR2''SѣGJJJ>|0..nҥK,ܸqpuuxcc*,◇dpv0 0TSS«eee|Ç_ݞYtO+@ z pΝI&6 ѳ={6++Д߳gǏ[^:W>GL<}2~x2O.n6q%ց@ zUUP @htૠ攔PÇϝ;wǎ)ht{\k!66=_Υx$C=OS(@ >bCKKG]]9s梣ϲNϙVDqV~gR+@ z 4:c``Ŏ} ,)SL27899m{"]_& Qczn {HII!?5_0B)))CF!Y@ MM/_vgC dee4559!@ >gA * OB V@ @ D?C055<0k֬π >="@ @ +V477^-WBNNٳO>MIIa}E5kVBB۷oMփݻ >|<^JJjڵ we;G!O>=z(qƌ5k#""pz^@ D@|I/IWVVUGy򥗗ªsssĻlڴ)''GGGԴill6lw۶mmmm@ذaùs粳\RXX0D"M>hڴi:thٲeǎxxx1bڴiŋ? [paDDD||Gbbb$$$9e˖d@ _n޼a#..;w3˗fdd?L&444^z%$$ӐHUUUV={n޶m[PP9_~=77722RCC7nӧ'O\lx!!͛7_>33իWϟ%݌G}~ԇd.Yɓ' ŋ?622ZZZ>}ʚ{Y{{;JMIIQRRnnnRmmmGqssS(&$$xׯ_w'#ERR2==]LLl߾}ُ=MNN F}o+@ @@HݻwKKK[XXK.=z[YYYaaԩS'N,**`h"Ν;SRRZ[[mmm|`ccn: @ H$iiz۷okjj feen6m:vAhhhIЃ$$$WUU;99?~ %KtuuݼysW^͜9Jh4 w>~=/SNݻwѣT*5??_OO/---((HJJCOO͛7_L)))imm@ 999))Aq=lGT^^C"""s7n};QWW%ÇB ~(JJJe+@ ~k^|YZZ YA6 @; T/uttTUUۻwǏ?~ѣiii;w䤥G}ؤaaT<^G!++ñj*yyׯ;::矕|+**I$`˖-F,SLa{_}e˖A}.^laaARjmmYnL&8}tbbb]]ݩS455 y***պzzz\@wgȑ+F!~YHx^LLLJJm榦K{D;;;*zNĉwvʒ%K^z9gΜ8>rxܡmEOJuu `xѢE|+++lllr1f̘7hkk_xqڵ?~䟲055]x1?;;{۶m QF!//d%''Ϝ9?煓"55533/^@ >|O~q@ >F!>XсgϞmڴi׮] -[bEMMa'Tqlll?~̜9SNNƍϟ?/9|333bbbh4ZOL?0~zcccx&yС]v566fgg>Q㨫yp>ϟ_~}vvW>qqqL&s镕Vz5kDU9033[nT @4hPmm-\>/\?~;}WYYN!?@ ~dB $?k׮=xg>|C;v5<<\HH@aa,۾hGWN$Rppp```}}}}}ɓ'OU/}7nPRRzawf\ TUUVZO󊏏h_]]]k={U. p aaa@{{;H9vXaaa))) ITjOxbb@RR/_N+VǏd0>>>|^?oȝUA{`Yq'bAAo( ;=}gm>tСC~rn |aXmmVpp0Lm۶ӧ|[^^Ĥܻwȑ#;::X0l޽cƌyasge֬Yaaa0|-sss >~H&;a##'NMMMnnnd2$//͚M4L&+++/]W"?/uuu ܨLM }a֭1cI( Lia 'O&xbvءʩFXXȑ#FO666VTT?`pccc2lccSRR#Ԕ<==Y˗/={v{{{XXجYLÇǎw1x ܹsСC/]$""ªq]]Q0 +..9s&L1bDDDLs%[[[OO#F;wο~ںp1c(**ZZZkjj;Ȉt쬪J&g͚dٳGMMmС^\k J8q℆&^K\Vww*))577c%@ l׆/ז7n͛gllݻz;;!Co޼9dȐ9s昛޽;88x׮]k8PZVVֽ{KPPnZZZ?~wﺟ;¿MMMXXXlݺǏuuuRRRߩmll߿O@% 0 dzf͂g]k?~m۶={={p0bbbW&%% >?HIIao߾OIJJ._|Νbݻ7o055P(t:$((heeC__ߛ7o9sf}}ׯ_<]s- ð'OTWW͚5kРA7n܈̔[|9g䨪&''0 '''[[ۘ9s 2dȑԤ .|QAg̘QTT(//wqq 6mڥKfϞqqqǏ>|CLŋD V^kmdddXXXzz'oKKKaaaQQQqq%wWHHJ񣐐ЫW#7 3`??Bl۶MPPpر7n܀i---1nm֏lq Ǐ!H˖-zL#$$V.&...""m۶'Op2###x%гCHHHOOo]$޽_o_W@ ~ei4ڨQ_6l0& IDAT0 &&EEE[[[UUUl#""N:U]]M$뻟Ws?;mM@EWPEA)u#`U,XEqWD!겖EAEuUpBĮAz'P|;' Igs$CyP__?tPpذa=СCަ۶mstt0=.[nڵk<>!''O766Ӽ04J-))!Kojjrvv&ddkk+&D.XcbTii)B 55DzUUUqqv|ӧ"HII{m,))!BLRRR*""s|ӱlikkiڻw͛7drCCCKK q>8{[牋O--zwXtҥNNNwL^ǃ%%:hU]]]ZZٳg_FFL:77WEEaϟ7m7fرcl6a/'))h¿eeeቼ޽{^("RQQ),,$ձZ%%%|UUU%}2󜝣O4o {{KJJ~|ΎN>|8aY:'sd2yϞ=o޼|… 1 [pӧϟy&׮]ß#@&pV+ۊa60W|uϞ=&&&ʜ3Zjv9\iiil6;??ujժǏgddl:~5׷$%%^zڵs)**jk 466:t' 544<ҒqWEy"s[[{={ʊOꔕ:uv8n…Do߿|||ZZZ޽{w%?~'6MREEEh=%^8:yfdee5{7n<7Bl޼͛qqq'O<ٴiD"^y۷o_>s~뙙xsCSNMII!r5GII)"""**/0 t:񧪪kz1gv:Gl'>5ҥK.[+B(44rܸq...555crF-Zh̙[n|uK,155?'O9/`llxbsss"ނ ~w |}}+++i4Z3ILLOH~O?f{܎^ gSN%.p2|=++vv۷o͛ >qƦ&sssebb fH$mm?^5&&TGGGDD$55S ѣG+**𗄄N8? y򥵵5~t\\ǏnŊtMMݻ];)_jNp|9_E#K9'npn@oapvvڽ{w?n#\YTT'""R^^D!~YYZ^AʦMfbb.`Lniioii 'ɲxC7̞=Mzʶme˖9??_]]}񙙙} |oL4IQQ1$$ˉ[)f;4@_݆ ?on sD|@&'' X,ֺuΝ?'[XSNVVVFDDlٲeҥvvvQQQgtpp> T*fH$0Pd2>yǗ`X|6PΠ*,,Sf/^Ν~999OF!T\\\RR~QF,xܹsaaas鋥:uŋl6ʺsرc"!!aiiyΝv---rrrA)By~Bm`᳁]!EEj<==^Ƨǝ;w^zؘY',,?0L.]iӦT!!!WWW>|={jjj'ɠ __ߔu%&& #L&oǎV?'Ovss[d %Cş1F{qSSSl@1VC*oVSSßz…/qF[[ێ;ttt>YYYyJHHyݽ㼷o^f˃{kk 6,ӏѣG;WUUܹ?DAe(i蠠{CB|o444._lii89=A `nﲲ2 ttt9VZZJR; dԨFR1 KNI6bL"BoA pw|^+s 8;~@ tt޾|N'wx UUU;%//OP:ЉXnn.L:u@'eff 6lw` {7~L/\;@R:4q_x1>ݽ{+?ؖ UUU t&ؼy3D={7z{fll|Xbzyy+%%%66Fᅒ6ltssdtuuCBB={dL&'&&>ydӦM$ /?wS())b>hBټy͛7B%%(;;;DWWWWWW6l齘d?d\ ~_u|0z[?|mm-B[F ظqczzڈ#r̵kת>!TSScmm}IO?Ի‘[^xrW\i``0sL2TQQ9syyy/4!aXNNY˗GDDܸqѣGW^߾} UUUutt{7~/rj9zr9#####CHH($$$888%%<..222h;;ϟGEEzxx$%%8>d2%>>>11̙3\xZR>>PTTDdaaa0 B۷#Għ%%%oݺemm=wܿ[RRrFH$mm?^zYYYC 8q!::sCx򥵵ѣڤ?~,vvvMVVɓo߾3gΊ+ 4>W>֭333KOO?zy \^﫮oF}}S֯_YYY:tP 2f̘}566ldee9O^O)J5`K8qu"f̘QPP19_;PTTӧ 8 )JLLLXXXUUU~~~ARSStzdd$q\Zϯ!d`` %%i.坶 ZNEFFZXXP( Oӧ袢說́8ȝsԻK'V#̙ӽy,ٳg1# |8( fggX,*'ٳg]]]={vWsfUVVzxxdff!\]]BCC?^\\LRQQQSSw߾}-[l6BAVVmǎrrrK!nݺ?˫3:uԭ[*++O<CPȇCTTTXN篨;qR(yy}=~Ǐ=&Sz_u\wgϞmhh?~??e rrr """+WܹsYT*FXn„ ޽{9}{QQѬYϟO1 KKK#JiȑAAAnnnmO0622ܹsaaasdap5uŋl6uΝcEBBΝ;ommUPP| p:gg8]W]-_v__~?<k b-Zh„ O޸qg\rႂVsss++Yfyzz_~yYhhÇ?\__LiÀOܹsܹsϞ=366޻w/gŋ/X`֭YYY'NXtiXX^ݻw4bbbYYYYYY?c||; i4ڒ%Kl6a>| ܴiS@@@\\\jj &?|pժU/l޵kWAAAZZO<)_zu[nqbi4ZQQkhhhUuv))Ұ{>}Offf-j\2 @NN.33?r{#''aXCDy;RTTt6kkk "!!yն6>=<<ƎK"ᄞ6mڄNnݺĖG:Տ?H" ϟ5rH'''2lgg7b D\ӻvԩSlقߊ".g9^r %%%땗GDD()){{{󯜘6tPaaᒒbX)_jjj@@@hhě7or ݻwk4~r D?kԛBoVSS}6B… \o}5k/_[) T5l IDATA?~DLLB"JKK7l؀DFFXN?wÇ[[[?x`Νk׮ty?\"**J/\,k1 ^﫮oJmhhJJJ:)))QUU%BjkkJ!!))ILKHH19HHHptC999|B\\eQQE޽;22+ RSS/_L)++2W!W𦕖rF.//xikkkKK Q<.}ʔ)سgχh4ڡC8TRR!,OGVWW>>>>}0xЌ1V&pMx0~y#b& |WlmmBnݲŧ#bJz?7nܸѮ0"""""]aSS9LL>WϜ9s̙v+1aX_<ڼys¼mW^__5紴4#v|===ye2k\ }Սr5jwn?~477GYYY_M0VA~~>~QoKlW_RRP]]YYwyd2g%K,_RXXxLW' ۍ۱ 3Q7w\$w%%%*))yi Y:aK,YdIuuO<!RQQAD(++# "w +чD/_M pM/###== O3++KYYYAA >++ACzիWm+Yf%&&ӫVtppxyCCǏݓ544<ҒnqۇS:th}}۷o1 uV~~>*((`6~xqq})''ֆ-=~xFFͦ׮]cXdɹx"Ɍ͝1cgÇKHH455?^geeIIIIKKp?fYY٫Wb2¹\t===2\PPp-ǏX,VLLǏ&5savU<`BB̙31Є`$< ;~W-|8߿N2DRR޽{{]hQMM=~yXXؖ-[ tFıcǖ,Yd`````g?-[b YfVޗ.]!!!njjkeeꚗGP9/쨎 ̘1#))ijjj3W===׮]{M bffv}.=''GLLH˗/oܸԩS , 0;vlժUJJJ3g$*O>[nijj矎rrrG644$8v.aIKKks089'.++0,//OGG॥T*;O톰gcjQQQ׃n!!!aaa}Ç\2Љ Lw@'d2kjjTTTWR%$N1UVV֭[:^`0Ξ=rʁNbee;x@Wǎ/}@?;G or¢-[lٲe^CJln7d2fSԁN ȑ#˗/4&::F g͚)$$ vlmm#""koĭZȑ#G*Fky澈|@]#sO'iΜ9dȐIII__ߴGIHH owJIIዓ>rHff{\\\+y }ydd6|x /zə6mZN4(j?ܹsi4ڬYƏK($$$888%%<..Na>yYyy\Wᓧ={RRRRRRWttfff||qBT*ÇÇ& *mܸ"((/_nYzzѣGYdeeO<9sX`˫"H$҈#jjj455B퓕ppp;v )h]vM8ݻwǎ%K{N}||T*^].'ПG]O2bLfLL̋/cffvɸ8___'''6miiIJJ666FܞbŊFµ]sϞ=JJJӦM1cW=tPTTeFF'B5''_,P!сqƙ|/_j~hPP1B Giii!4y䨨e˖ IJJΟ?˫N߽{WLfXX؄ dee+ .@v+~_p!v?y$^Dݸq####!!eff~)11]^;@͜9xIGGgѢEǎhMMM?8jjj\+((TVV"I$Rqq1jiiI~TB{{{)))gggcر$1??f!!! g222'O泤!1[lh///MMMݶm+:!D(իW+>[YY WWW?}k{ԟ@5{?Bv_UTTLLLrssGSSSXX!aΗ._ѣ]vn]`0]OLLL\\|׮]QQQX_yyy&L@)**¨ Z9:w^ ;;;w剉nnn>LLLttt\~=^ܿʕ+IIIW\ⳤ2!!!--TÇ} !榣Ǐ'$$tBիWIII?\ɇ@UUմӧOg\7o?:`kkK6$%%Սi4>ML-^MޯPUUvZ7fܰaN|@zon̥ѽW';;jC222:::\_=۬Һ~Ϊ?MF]7M'A:: ''w1c0̏??~ "+BRRRӧO///OOO'HJJn߾}֬Y;w577Z+߽H$}]]8򉎎>x?leeU[[e˖/^PԘ |,! >rȴiӈ˧]d2yŢ޽*+;Kx/hkk߼ysĉx͛WXX+!Cܹ3{lOO3f0 `0Kvv6D:u@'233srrޮ6P$| A; 6ndnnnaaCR-,,={())M6mƌ^^^㘙߿1c^2++ϡɓ'߾};gΜ+V0 >K}}@gƏ%uv&l6챟'}H{npp0Jeeeƍ311MII .jhhw% FccPEEE>}JLL,//˓FP@@@jj*N1bpɓkBV󫩩A9… w9뱫k}'<<cggiQ(|zSUU}7=[6y=!,`gϞ鮮)))YYY*kjj677+hnn&. BH]]ϼBa&&!D\PQQ!&&Ʒppp4>qCt/b2k;Fb4: LNHMI161!H}R_y;få\\\Ν;7ePkk~(^JRRBH\\\AA!N"18\ᯥENNNKHHXZZz{{w;Otz]]3K3\^y/>ܺukܹ ݻ---ȓW|55;_8@PTT9L='`D=߇,Dex;:󥊊aÆB}4wcر$1??f999RRR&&&xauuӧOm&,,b0׮]'&&&..k׮N0O𗗗'++;a4MMM=ɓ___eeeɓiBk}ʕ+ͳhܹs111xR{v8aP]94y捵֎z6щr{w;pX꧇%сD77&&&:::_/߻wEBBΝ;7o6lӧO?ySW]]ݣG>|XZZ*xxUUU;w?/|Ot5O^_r%))ʕ+ZZZ.嵾{DR߼y.Ȅ ~Nj{p;ۛC.l ZZZ8Zzwq]§ٹ;~§&B˕5؁zo8yl_n#~ и|2>~>#)))//ownﲲ2 tttE08?s~#y/,ء Lܞ}Y̕ttt_VV֐!Clllߏ^[[*,,={9szDy&l5k?{{"E]}54TSْuljKHu1ׯ_KIJ0aMLs \7qΧD2 O۽(*aXrJSeee @իW:nٲeph777;eIfʪk?mL7lt ͦ)pqVחy]YvI1qqŐ kVryɳ/|l˗{\!U8:Ǐ?:AmSN]v@'5Y1fJ\IU׵Nv 1 Cxzz.^XVVvԨQᕕ!!!D3f,[ǧgc-hTJ] !k#._zӺj F׮>{WZEUm\l6v SI*Jz:9~Ἱ\֘L4D=wTUaeYYYfc/?͚1TCm Y233W^=Y_*.[t)5!e 9e .0 {Euu\111[[Yð3gnܸq˖-=?){01 z!C0 XHb=3«/,k0)i~9v玟`4CGN26ۆu^Aݛt;^? ӟ._$9UEEa:yD;7G֬u Â>u޹u abbb s***o޼K^~-,,`~84bcHl6{cCd,=PwvWUWv k>0gh3w^JJ Md4i,OKMm!#54+[hdlb>2.6i ! uvugfHؗ"~Ê|} 99ZNII<̼y( Fvn0jCd[ѵP4))i-"""5e֖jY*]anl Fs "" ""555ZCrVPg'Nljjc0[Aq҃0 H$ɤ)^53Zds[j:1Jz*Q`0n߾=uT8ÇOnmm]\\ <{@g;^X:rLES݂ϡ9Z&=#0 W=z,z=*g61_V,["ś_ .&.K~ 6<15|n&j=|AIDATl^m8닉ٳg׮]QQQt:ӧONNN˗/L<ƦO@aaa?&)(+ ~!:!.xfuu5ݵ{Ͼ=//))}-V__?JWD&ޝG#RZ_|aX/^<̛455jtkkksKsJJrAAzQ#G[I_tt4FlUTT>}:k,O!!l{{{^sFDDt?[jՑ#G9i4mݺu݋̟͛{iןWUO+=?-vٖ͆s -YkZ*PsOvW\ycǎ 6l̙wx" ~ɓcǎa|N52za$EWSbt1GDD8/q:vԕS&N62 |n#߸aXMaX}{"wL46opmZ[[/GF]h7rcL _{CtҥK?ާtǏ ?Y,B@pߟDRRR-t?[;v:yjmm]ة6YmL*))?ή]&$)((n3;:Fj6m f[·4ODTØLfiֳir7fvK:""^=vsV=&Դm۶1xSHHhӦMƍ.))褺l'Out``ܸb0d2YXXblg$up^},JQd2Brɤ=?3Ƚ;*aNN:>mooO|;sLJJJBBBpp1cf2%>>>11̙3x!C$%%}}}=z$!!ھ}σ޽+%%/NZZȑ#sqq! BHKK+44瑑?\',, 9''gڴi;i$ 'ڟsΥhf?~|ll,+##ss󸸸ヵ6񙙙gϞ8ʕ+_.%%'՟FFFƍyG;wz/zgoODDdȐ!***۷z& 6LYYYUUUMMMMMMYYJt 2*I& < EN˽/3B ÎhT7655[XX֭333KOO?z(1ɓ'߾};gΜ+V0 yyyuuTD1bDMM }ZXX888;Vh4ڮ]&NݻcǎχW]˒%K޽{'H>>>T*o/.bO|O.'LnmmeXL&3&&ŋ133;yd\\ͶD$%%Y[[#nnFGG+V466ϿKC,--322<==9sIlll+`zgo III+)))**RTiii111!!ZP'{J* JJd!觇o.(++6mIzzzJJ Qjժ͛7 ~7)))|Bܺu !TUUz=Bh[n}'OЀ{S)!~YYZ^… WZ' })qq N2c{ԟ@n"nܸa``sNeff鉈%/w-''!!!1 iBLLO["##;]]]4й!6{\;}'!!¾N al6ؽWr=  EEE!QQQfKII;wnʔ)t:Ç,RVV!$))njjzΝ III B4N¶6^p_9 OGWપ 'PPPk׮>]^;@͜9xIGGgѢEǎhMMMο]tz/_̟?띆viq,7fĈ>}įVQT#Ft|iL?xF=ϯ] ^G|с{{1c8;;?sΫWRBBB|}}7lPVV&##yy;:::!WW-[߿655u۶m{QTT\bBF%%%zzz^zO>k|>QPP`eeuUOrmoOЮ]X,Innn7 455A\;ij[, UJp"!^_@ "1<!F bQDhPBp(H1F@C\TDeŖއֶ{S `6_Z?>ҭ[Z[[[[[_k|n^8sN D"˩ Y}ݽ" TVV>y򤲲rϞ=QQQTŋ e2Yaa CO&&&666Ցeeeo޼!fffVTTL!A"dxg}xXYYdggSR[\\9ddbT*---mjjV!%%߿B(޸qC%uE(zxxPO0.~.fE"QjjjOOOsssVVVff&x^ۙ8XT}inM6RD˛.={1FtCb3+{r!c  6P(1.a#s'0yéZFdښJBH``߾}!V}|||}}Y,VDDDPP/NqFBܜ@ prr277?qDOOB7n'%%r|v3̋D" pttliisZh\z!x{{GGG]'// H׾yTGUTT4>>N]?g|||>cS ˗322V\966P.vw^ٳgO:{>OOO77dCokkklls!ٹ!5@ ޽n<#Gn޼YT qddBqʕǏ'$$Ly"M(r\www6e;t%,,,663))I$ڵq3_xxxGEEZYYGn;6~zBb&''1I׿@`F24;`ffVRRB}*b\oyxxxLLL__!իgddrҥjBHqqqnnq JƵCOww\.GB.]mٲŸ`Rwuu&4̙3^.K}T˗K޻O&''wttxyy Bڡ'77wppѣ6lp«WHNNMMM&&&˗/7"qp8]]]cmm>󼨵(J7999y4_okkKϗ}y].Y@PZY9 `Ӻch֘Xz5qA  DDD~J211* 2lhhȸPLNNJ$Dp"##_H$w?''[2ǯn|+ Go}ښ߿OkgدNzC*30>?99o`TwsǷ;0w@Dxw:@IENDB`fwupd-1.9.16/contrib/qubes/doc/img/uefi_ME.jpg000066400000000000000000002464221460375044200210670ustar00rootroot00000000000000 ICC_PROFILE mntrRGB XYZ $acsp-)=ޯUxBʃ9 descDybXYZbTRC dmdd gXYZ hgTRC lumi |meas $bkpt rXYZ rTRC tech vued wtpt pcprt 7chad ,descsRGB IEC61966-2-1 black scaledXYZ $curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmdesc.IEC 61966-2-1 Default RGB Colour Space - sRGBXYZ bXYZ PmeasXYZ 3XYZ o8sig CRT desc-Reference Viewing Condition in IEC 61966-2-1XYZ -textCopyright International Color Consortium, 2009sf32 D&uJFIFC    ++&.%#%.&D5//5DNB>BN_UU_wqwC    ++&.%#%.&D5//5DNB>BN_UU_wqw" 4,RRT X!H"@J.mH,X* P(* B"* ABXDP,!LJ) l(%b 3fBQe%*, "(%3 "R4 XJE$3sX% c .6@EKDDeP,&Q`!ڲAa4DViPTR !`&&XK4fj\)*2iKjX.tB(UK K`-DAbԲaA5To";ȡI,4qFinlMfej\J"ԱBPPPB,lY,"( , R H(CRe%\(%5(%`X,RgAC4"®u#Q,Ҥ `QV%IPPisPQUrXK*[L-Γ;5r5. ZCY LRʑDX @JPM@XPX J i%*RPEW4ĠA@APX@.MMdR(!,PUeDJ`P Z% UdQ*̖ܔ\+ro6%si*X7!fufЍ:71b֙h,EQ*-ARQ*-2J@Q*,%P%A@`HѕXЊ+!!@Ae DQJK(\`e5QYZB(!e aXXX)UtT&)&( H!VZd)I A`!nEA)`X $T RjAlF)ddi@f┅!PX(aP*jAl.hJRPEATgR, ,UK,E܅gBU"%JE ٩D( cr -PX I`*Kb$X!@!PTJ APdil@T(RJM5HP.FŀBPX !eJPTPTKUP(k0i( 5JgVT P9/ECGvvt۽1unuvQڝaٝquGaZGjuiׇfUuGiGiU]TvQuQuGjiWiձuGjuiٽaٝr]js޵Nw:w9 9T#NW9Epg#9o9\C9\hqG6#r8ѻg#|iN\|g C9)9Ns;vqwpgPpk9\PCM!85ӚqCÓ:::ò才LJ% 6[!k ) ER@(!@-EDZET)E@QEZheZhEiZnheeZhe[laffZj#CTcAnAnAwN'(rӅ8\sʎ'1x\ÅN8ss:88yw`nYO3tqM0^>N=560'qtr7$0wcanAՉ8܃8܃8܃N',8܃0nc ] L6L61t$͢("(%P 5 24#L6l 6 11F#r8#rƎGC9\cWqW9/qW9\Eq[Nk9^׀s)s8G+s8uŋHM5qo:ĠX@QDX(P (T(dEEA ` @,@(TeA(RTJJ ,fz=͉X8jUucy,7e@X  RJ RR" @ ( @@ %X(-RPPP@98_aYf&mIuY|{,PX@ @)T(RBPԠA`PT,BXY` `X*@J@P, a( J ,(X5:;?WMmJg@dDicu|{Q,%XP , U(eT`)XP( ,%DX (!ARQP,,(T 4JPN?sv:=Ys9uXHƮǼbJ @ , RT,YDREP @IK*PH YET  `#ԮWOg-g75D+ź8x9 `,Q(@,@ @Q@QbT@% D@` J -PXJJ@J)v>5Ť E2 un⸹x7I( B bX%(,(@R(( @   *",A,@XP`*" e ( R(sK898{]C_;Ʀw%J"F4|_/FI`PP,PK (@`P,()&P `RT(AlEMXK(,RJ (,-!@a{&=sųY`8jX|\;ŖP),PT@QU,YPTUJ( EBX @% aR*%%  ,5(EJT BBT&}lk2"T*ƚ6|g/A,, J,TPHP,UAlU*PDQ(EDQEQ(,@ R,JTABKDPT JPT,J5t]N:3x4PB%*PB** *P,Q`EPIhSy=NɕP)3,X%D X*P P (J[,,-J@P,J`(,(>S^gmq SZ޸xyw(R ( *P, (X) BX*ReT`%3}=߾e( ~7~cY&Ia,Y `  P[AAR ,R A;c8k%0hhj4|w7JJD(TQ@J,JPi-, 5_8c_5gGS_8ޞ?2eǕ|۫?>|٩Rk)b`BXE%D,EJ(ADP(,EA@X( (A;fny9MBƌ7*N֮yx΢$  B  X* E( Qe(}I|3l* 5qNOsꞏ]|{ 㚒bD@ K,E,DTAAR (RP% tJ,nWc/g|xfĪ1rWX\[ XRT(,B,J[4^2=}Ow5,%@ 4+Z=Mύ|~XID%dBPPniR‚l((e iXu{=~yޜI1.uj>7|7, J DAeIAKɏy}9zHY( U @,T@lUuz~=K?__35+K  ةUa)a`J M3wpz|b=V5buYjkgqro)@X(,P lK+<bB@h }A5o9?(/&a@YA` ,KD,f( ( ,XRl1p{~'/5ǍMstFjU*#ӚʚP@Q,%PXPT@PZ*^^?F5kx9J-Ja@EA fo?bueX%X%KREJ,(ŰTDj)EQ,),{^ߤW7Ֆ(GBźt|7NbTTXԠ RPT,YH>>FYi.VDUJTEU%DE5_eS_ϑΦX B `D  ((i,,&P(@-TK(D~zΗ~gcgM5:+KOe5 (  %Y@ BQ`J{ߤg(7+oS;9gYl|=Hίdp~}5i}?pj}2[_.~w! ,P",-"@*Q` (*XX(* uMgK)QlXdu`>N> Y@(*Q(P  `X*PMrq}c^uÓֻ<>_[p~={4}?>1Gf۞>k/\)n_=~;̱`$KD*  (( % @ /[9)#\˜Ո[ ZM-9e5,JJYD,X *Q@)` Qe5~c_ _S0_yx9~W>[/sz^3Sk58??G=/̥3GK̼Θ̱d@ ,P* "*X%PP *P X(!IPnPtgk-8abDa.81Ӝ`T԰P@R(xng~[~K<篂;='> ez~sM1Nig㟱~;eU?72__< 8ea{J_Շ=~mלM"D)K)R[)PT, (JPPAos#'LB0۫):sX4RB(o ePP\[?Ww豯_ҿ5~ſz'|ig[>f~5wo|-ϮOl`?"~?&+=O)"|7_\rNw/˙, P"-5sY@Q@ `@IB3 }ufؠy)m>gNu(XQeUP P BPT(dz>_ұ䟳Ky8\K~||9"Ń~W_Q_u8u0{ؿ?.{*}?ߝ__ߋ>w?G?L: (,E!@,,[EJ,J(T-J@(^osNOγ,.K`,P=ⵟΤΡ%(DX@(kzffnB$TYT*R X*R-M%e*RE *!e @'_y8o=k1c:B,i;gx, D `* bA`Y@@[3w>rS ,,PU&v7<wg陔, ,@*PQ`XQe *Q`E4`Lqrq/7'ǓcXY(gKx4{:, lE,f@@5oX3n̿<{|Y~w:C3C- 23_>w}|QGg~/Yf[,l$P,Yae--IcRR#H* `-* Qb(Sqw}/z8ԄXV4͛@XTD(UJ,T%A`TAU`s}w_ٵ?e eOAgY{cJ}Gp/K,S{>8x.Y ! B @%P`Q`" J-SW6-$>N3껽^5İ U*4YeJAb(JR`Q(PlnG&o纏uﵛC=8ɧ| o"X  @EJT *PTl(JTJTJ[)]qq_ƱB*bU5*Y@P`Y@%X*  , P( ( EK`X5qW 9u;w;|C8@TQ%`T@@,E lP,TAR gEJuxa~ϓ:ƙ htȊ3ZBTAPX@@ A@"TJTX\@%(@%E`%,EJTA@DT(!,j~niXYIJJ +9$K5@ J(PI@  J P(* K@B ,a`P (`*PP sKӃJ75 ,)3skQ< UTPR`(PP - -(@X ` *XX% D TJ,EJRG7498xi("+~^˝bee" (P@I@(E` ,KR!,(*PX*Xe,Qe899pk fR57NlT(PTPZR% % *X- J),(KJ " ahJ* !`Y@R,5sF899)~;Ƅ( "':k': DPR(Y@ gߡ7]rMus\ K%X `@T)E `Q(, $QK )A̹|b>4)DK\X'nt%@X (PJR|nLwMNGKxY\Nm~g}n=?OBgG)>W\;ۺKMֳ4۳7R@P K)P,EA@JP,7!sγPE*Qε ے(,)( @P(E( 5@ Gg}9C,k#Xƚ%bT3ǡt (, @ b(Y@eLqF~羈IBPxDMXOYۓYР,@@*K)@,PQ(/7q}7=.^8s]ƾm}ut]tݛvgMsS$,@K  @@ *RB (PePFuɍD_qg@P/+;.Svt[,TUEDQEE!`@DQQ BDY@ E/c?O{㦻\mxvG{jo|LJ?[v9}Wfuw}uY ۳/~oK/ D ,(HDPTTPT(T,5j'|rYGKe< gnMM ,((XPI)@ PKE`QEK%.,r{%TJ!`,E,@T(ɬ1#RS6Fw>N3vvYP)( E((*Z@EDID a%(@BY@DP,,Axrc?gm.fjXRQ d Mq`aۋxDT)`T(@-JDUE@KDQPYEDP@EQIaI`$D!R%X,E,(.N3>s,@B_Y<)gn-*%  H `]Η'>Κ]= V޷Zkb^VQDQW N\A@@AUea&fQI`"QT%JTEDXPPP[ RNk(KlXJW 돓v(PRPKH, ,*P e/]>4)|~Wnwtz_<VoN42k96 +;|G,Q (K@ E([iDԶfɪbbnhek|Jk9jK9׺vuYכl5o},&=jVK=kMg׈xY5V}'n} кǡ6\*Y,zSNSz^ϛ<Y ;w7E>G{G缟k/?=gB*"*Q(@,P`) ($(("6C+N>I,>Ǜ, $X ]\>s=\AR)5X*E~GϮ]/K>徔(PT @Tk X3lөxwpԹ"*PX* `* * @ ,BL>%ϒ@z2X% %* `( γD JPo E DX%a&T`A`P, APTAAY[FT* @T((=<\XXK`Bp@JJ%R()"(C- ͌NAm68nheE APTARAP[F`mM6diTheZhabla'$0pC- ((E BCAPiFzS\eE!7PT,K (C- ͌60che( APTfdjm#r07x#l M\ M24 ( 360c 68ܐÒnAnA' nAc g5F.f_5e<.k4\ P|p@(%Ie$(""C- Ӎ 619la@APTdm#r^!x-sN!1&G+`mlanI.N@X5ty| `8j,'[<{e^JD!Ydi(!eDQ%DQEa@Q)lasa8lbіTJTPTjm1xG;v'ɡ,-h%hK5%Kgy@((!s`~?{NDj% @REr[(fᖆQ2ЋE M26qFMw3N^jK8ݾYvO}Ϝ{!1 &<>CO'U}ΝuwzBQ70iSz{<2 L26* I43hTpC-- 0C- 5%j P&_t0\j,h@T~d,)#PvÇ@L 6P5 3z B4=Na{2Bca9 VDqoKdB{B^/4  : QAP(, R5Ƃ 0T_%%221` 023A!@Pp"4#B$5CD'.I>bx堼{|3ex\'?%g''/N5g}H/8LOY_bWT/Ng^x$d8V`+ ^xخfX~щx!y_bό=xן cy_bo>3ρ}XاׂsGG%`Z2 GZOGC^oJ+$` qZH&Z[qjZ͉hCwI$d!%YZ/f1RB*ayLג2 gFgݖD{ Y$^pp%}XV]+י܇K9g Ӂpf\K[9!c- D7m"WT/l}\k(IHM(UrۅCj'4 |gz*%w"0L3- nϋme" 4Í􋀲-] `B'm8X$3L2JBm.?B$D>_Cmp"!\Lv;12QR12b: vH3"(PJ F38;ewIM+ 1xVz( 0 hmCɆ^lB.4܅7$:LLR6X[CEl89ׁ ߫r0oy+< Da?}jpimhQ)p-"b8T4 [LTb<u`CeÜh=Aڽ#m^ Olc"͠sQ K[`} PAĆq?WOtNhxVz(}{OHڽB">/0&p&DDX+,HlGxYakH+朼+=BSXmOrxTq%&$bNtCf_ S{ i\Dg@/g\sB0Ѩ#ID{g*!at9Xm/ח j,ՃyO#ћ1S`m4Nuq>LDB^k`y#CiSl Zh;1I6C nhC4)3xY"z3dD7qr2ڐJ! 7Q)vRρxHm~!o+ X/DkH.f]1Dmsn;jZxjAq|HߢF}ԵiJ %?|(U! ݶrMi(~iz1TT[ a"]@c>Qz0vkWa f5" U)qfP|nvf vts/syI᝼Џb?zI9e }%G҉i38(Bg""!L6$"`|u{&[o]KIS8pdzM) qRRفmL͍LpmDr S9|&Im!iweÐQ rdy 0ENJRL/"7kyifbdMρl a[$#Ȑ[2 0CCc,^KZ^aZ Dm\Q;X@z=a43 Ј@ok$&>A/2ben4dmV/iao-g=<|ZvbbV/8+11=D|>#efi2,V_cx#>q1`W8`_8`s?s,Ř1 +/0yBw9tRO|eK$JXvA$i`b#}wto)|ӽF6Ѽ$[XtYK(&I f_q@I*i-PJ˒&iTNHHKN /GFLC$quoEX}R3[@<1d%<2?ԋ?3g"%~ )d1DKҩ=}:iT^zF&xjRRuJ^` 8 JDX˜O"O9}:! F$n4h&if rtD?v'Xe7JI%9K7Sˡ ɰIM!,C RʖhaJ mH3pC.,m)i-DI33Ga{\膕B:ǍBK']7&Iq.'y'YL̔fw ť(qÎ$ő"B4<aH58A$HiMŤ5* 2UPeJI7waE36@L:Tm4n,¡"* j5^" 0rt]5Fe33 qH֥KI-,&%DUԷJqպU\J*.6P-ݘL%1UT9赙9%F(wF,):>xnNy9}1 E9F٨ D2Ioż:e7;˪MEJMGl[1mBŵ)P8#OכmJT3L$շY)_* cW^}q-Yqo:YEZ3Pȉ?i֫h6p P(ݒ7Qn ѹ7Gݜ,bP)Hضmbŷ V]|:>* *I 4)tmmCN] A>QN%m,u r54\2ڎCJ$I  ,RD./7,oF7}"q(Z! 7fFꁺcttno <7WF챻YPBZYXb~pcsf|3!qB V*"VBpՋ T.[Y*`P$(b*p\p^p_po. czPތo$7PPb7xACաaS 4~Lٽ8/,a{:4a3 ff ؇sPBsRFr-8_wfíU0yLLLLLLOA)_ڔc f[IäBe M<dPh4=?ݺfwPT*UJRa.!N)ځDZT;&&&&&&&&'>(KyÖ( l& % Q?qr[е*үplшAaI" ")Dm66*+4.{131PT*VBLKKx`j5 %Fr% [q4sz" =A)M-m[4TͷYYZ6QK>{1<&bf*1Zf+1p \pd+!ZED&Bd&ZcwGTb VYV. d+!Q TBd&BeςwRLTbF* zz>/>n=\GT1ti4c8)0N< [\3((Vn: )JQ YM:jDghjFRpYF? b}Ha9$zf 6R"a aԓ53 gD_6L--($"jKUK8EfQKQrP 8ZmH zM&i8rr4IdnR]\FҢC ںy|(hv鬋npJIVq %=RG%6E|iB /p6/J4 QȖQM..@\M|9M+ .i[DAl (amͦrЪU6PR !1Q`"A0@Sa#Cbp2Bcq?0閪b2ytuplOx&YRn%zՅah+qWMc` OJ]wcvt-A3}ƠyꅱuXT+Sc_{,`$3҅66Umm{];+5f0GbiIG U2򐃆*GBt[IΏEULV*t\Qw8,Ԫ:\IVdFvZd"$RPN޾"񅿫3|MKWW󇒼U*O4[\Wl+GroN X$Vo+Ek[F6ұv!c+h SsL{tج}P5U ab( }OX[j { FT2P[i uOH[zBҰԻ,-. OV j+ X7w QHF  aߥuPkWjzaWRß"8%J<P( { :PA;/)WSݗUu]A栨* (0nryRO-= 2!1 "03AQ`pq@aBPbr#R4s?4y9GTLS)u:)œ)œ)œ)œ)œ)œ)œ)œ)œ)œ)œ)œ))œ))œ))œ)œ)˜)))œ))˜))))))˜)))))))))))))))))))))))))))))))))))))))œ)œ))œ)œ)œ)œ)œ)œ)œ)œ)œ)œ)u8S8jou;T㺝ouououou;Vvœ)8S84]+.! )͓V5bXV+bXV+bXV+Ȟle2ySzO3Jy)crcA4S̰M)XN< rO^e+,'euat.}+0i]יe ySt̲ҡw^eޚ ]יDt.VTVT`2׆J!\DܼjIId@o+;<=;ب=!tق'9޹9+w_e8쵟0kUgnW= J[{7+l10fli1_QƳGDz3i654I֪_*ǃ(orUnxoh ދ\" &{!uT2KpziQUG!Tmmpؚ ]ZqՖ 8+"!z;望ʢ?P*ޚcEL2Ҧ} ȰɷjwEzoPh?qS>YG cEL2JdQwwQ7,$j@ Oe3Qӆ*>Ch/VhV&ݺ;HjaE.Q*H Y쬴@T]b*˯gb DrNwbmOrY*t=U!V]#=t0*g\br"VֵZVk?YsVZ#V RH v}Mg<*޼6n)*e&67dkG ,mOFOaSE9iVuno!bSX6U`ÇZDLC❘V\8[ۇ|3ySZ01u/àmE^< ~*l_)ƻl~|0o ^zVih?q2¨['o ;gJ!vkD";o^Y};Bs n  Bz<ؔI{V\r݃=xe;Q>iOI?* l+]C~'āH"/h!~ۡ^<4 1ȳEy߱ãb(?42s}ևEK)ؾ}(s͢Ʉb7prEKUB6GLxn⦏UGOX9hZXYjO{c #xVC[bQe&;Ygrh`tP*e: p {VܼAjjAjԿwS;ֽIZkYcGIFLZ{C֪KIbsk? X;wR Js:lNx(RV)rZjŋ)AjV[[TuZ `` @MZjEXkrxe28+=o eEͣq»bXձJH|]Z-qZօ;bZNRmS:-`Z `F;SKhGJ'R6k3)M ? a 7]H|@++R5H.Ќ}rBKItprqu%901nV'QT0a!*h{<.$!Cfӹx!<;*.O xFΎ 7u?Vc&4bM(PwE]Fm^#e&2m͜͵jBnU]-&(5]@`p-vDl^)B w_zr /՗`Jnۡru ьU=X[w8*6Ǻ[+R-)a-`/jP>4Gs`v̋ sBu)`"_Hȁq Ia[C~,uދt曕5,on 8e@Ø0v9UyVcuQ ZhvqZk?pQih[ Ff(=҃k`QECqT"޿K˺t+6=y͖͑>ΜWAtԫ&A'5zu+h^>(&Xژ98<{Tvz6q^%1^Nc|JUUK%oAcLVDWհ+..?4ڄ=hE]ă5QqV@R7ٕH8[8rNJWNe7'l8Jog~_EcjV…J@aʫ;*;t!@ P>s9F(._8 8߻|7C.Dg9]9azރ@.8PvB"B0W(sVw(h BQ@4zfEQһu @ Un yx4bpE#H[n;v#t pܬqпy ᚩ] 8X"; [ ( &<]01VH 0eX,ؔsk nF}6 ^~St|nXE\QDt`ƸPEL|W ( ^F-TN-8)z5aL4,!1 AQaq0`@Pp?!R/qj=;K^'EITpdTڈډ.Ū)q5"ceH@L摚XQږ!K2:S8cs؋(c[曍); RaiPЕbFV7tGc-F/\j#TԼ>ӊ#JޫN iN{ qJ3}9 &ȓeB FGGfUN ЭM6D>N"Thf62$ c(Ǝ$^Y0Y߁`-ˤDŽ(ġ5tō;X2Сxb+cm1*#Nh`"#tsފQnfu&:lnGx780dڻh7,E7ѽl-7I rlDKVl걑Z7"Daʔ)Y"5Hm*CƔDw3Ę`ۃv8f & e @s0NuǁNuΉדdZ&ґp^Eڛ4"9EZ6666$1lb($n< dD `.lN(wL6DCܱ07ToԹsr;O( oHv$`P5k a>7(FT2W"й*C1bao÷mLO4 xfڛQ37͍4=1e[Sѓ"v9"LjMɑ &DNRrrM}9-&+/#p8eX\cٌre99h3 NdDa^lI t}ŋx҈3LGhLj_ "8YmEHF^n"܌cmL Cc v:tNA FoW$bO oFtfipGC|м k^'+{bqⓂB,.ÍvoXzcF+4mlؚ4v3XX63NizD;4\r6Cv'7alUDnVtB Y1If%& oNmXUOLhڙ&f^j362-2'΍/M7j,UG}14ZX;U#:X.If1DQYMI,.E,Nh1*EIS{P^~*ȭ=K^(J[" 9'> c…Iθ#cڻ G:6*l^Lo]Ocqb)H&*:hF'$Q$WMy32\[lWc\ ܉Qxh3d# qE>Q72ŹxcK; WtZ^Ӕ/,rE1Y:}""\nx$}Q]A)92E"BKhMpZfzn7'̍u)hf>; < Rz>OB֔@s-?CwL6{/5cύ>)4[JWzUQnaVELRh798ȣVã3[9jeI顎*[ CWdF&HM&rdڳ7b]H6RoYUΧC6ŧ"~zhSLVt-MVӠ7'DM:qVmY7 7FIi9697h%StdH$}3 z5ccoKRdƋmzv-r̋Xh@M.ť"r$H֟P :){d>lw#̮LĬ ?R;.BQi(謓,w2]?X6ʏ YZE'- kyS8Osn2xʣo_~i|-2v=d!\E)lzIQ'"Nh~&j 5rn9{)5Ά:ij!Me?.g>y"3bn0I0z&^l# '_K}erEWZe=fZۓeȍ%TmX݅+JfȑfO$kvc5@e1!<C=+I"fn[a$*z)/~>KO;&M6Caq~-Q=1x9 ،V"Ű'rW 5qyHf TMLm?ȿ(NhrE~:16>n.OFǯb'BYE|&7piLgҏʙїHsd_~t:|T~$*$ʞPZr:bd9&D I ѱ^N#$K ~ Z]DvS bmZ0ɤ%"[pSi.Xץ52{bw~TSe`q)>I&ǯi]d-vtF9jpו;R8n\H0-cy))ۄ}Β@>8G##RI6Dkbb>L"1!k'!cig?+m_~˹c#Pz&x.GQP-g _5j싄,Uc3_r ¾L #d/mHiޝ<1S7$;]1"EoeT׆Qy4FΤnHQY?Mad؋G*I"J!HPjqTgD.þYMC:K*_{c I)ꋼ4M}(Ή~Q`1na7(W뼖Eǚ\ET#MY9$CТݲē- !d 8=&D9}V<_gfM}7і+ȣ}y 4OKCS\M[sE)9DDQ WGXVh{O:::XJSȡF)"hhz% @}z~թ=k ދ)cjwe@fuMړZWOrLl6Zqʹ8UUuG8u~]5[mv $D遱巟3fXzkϕ3TH3x.+hcUyQE?cs,j"Y]nGw݃3-'K WzY lxLF17.U&y;|gi(\= DPRjۼM$j6'rb{ͼ$Avlmo־Az`_O݁OF-f`E3}\&Fh[\FPpw[2gGǡzrZ^z͔Po̍wX# Y,V #_Q5C}ih,2#кƌoWвJZk=-ٰev6{nğֳěqvo *vi6FC7ޫɟ![I,uHh]f[Չ':ﲥ. OT_{C?I16SΟ1AeL@S2l@^I[2a /-SRN$k?we[sW'ݤВb721y(r LQfzgޖaܝ{ψƪ吗S )Ho,Lt,в;-#f>>T!(?="F_)yISp,L7c')V޳Bлb1a ߠ7LQ34B2:%!Ӕ؝|C$G&ЕoYc>2^KDGbiNS |ZD_rz.< O=x#gUƂoG[HЄP9QKil~.~gocMqSz!|bXgx/^cv|dZ Q}n39YLj~.jX:s2~JNAVZD_N{|$EKøMd*GX$ *0c ڱVF~NȌ2Wـ]">,G`GƸh:zcF^ l|UQW*Q3/b#Ă6#{@$ublD 8(.bX4:3ʰFeb+"܌7aAEоe=[R'*>Qƌc}]gɕ&XȳDӕ0/D cGL&Z}O:%嗦Q 0?t-t~T@K't}W$[+r-0UgK:ȩ&rDf3~]fTC-)bj&9m7]Om+Y$)XmvqCf~odI$.d,Om!1DzatONS( 9>di6xkҤ^k:M=00 YkNJM$Lk'{~EZN6R+}ysl,cVqOgVQ7ីvLΌhuF$y҆ tηY$?8 տ(rHb:&t/gLL4f։RL?',!T&7?%ˊ={HX2UMkyWr,zA?LWQ*Rȉ$dc6fRatn̜&.㓷Ir\"P.DCF%Jn^2,R>#v&ԅݮI1m;84~s^.$BR^G \r)čd/Eޙҏ@?'0qzu:4toaqlHI0Wݾ;ZBv$TbeHN›\ǪS `V6ƞVX|5r8FN,*: UЏ)ҟmm,:uE4Fh,6I\ = &H/0dJ۸0ƛ4Ժ+cM~HۙeпkNGLLi#^Ea,T|Ź1 r";!Õ>*a%2%݂Ds,.`a6;$,F./"r]K%L\K*g :ܿa $܁d>N9W)ͱ"@eљ%8ck68s|&MG>Xw#l:$Lh=PftޏWfΐGjC9dHɗ6k 9ج(H6̂±H lïLUރbcwdض2Sq'7|s'!,_*8ֲƞ',;$*bxaY յgWeL1ىKg0[J Aۭ"5xلy%lo'= @F?j^MlAӎ,ǩdzq!f*,DZ#ӎ !QeUq|XC>Pɦj#Ír_®=-z2V$2q6†EȿuQPfݰ80(]Iw8>ruON{J];5p?=vHOt~b LjI>a'hM6z#|!B.$y:(ؘͅ60 ˚WqbV-Ni,##c7:N_I20K?s;\\V.SM(Oi \r:!-GYCH4YGdӽ/ՏggJFl?hն|O% joY"?6oPiSh ~@:36t> ex=co~rWp':;xHwgWF=3q&~.~&FMk#_IK{w!]Ql~xq{15P&di۪HѓDz"ƸX )r_'P뎼h:C?J9Cv}EYT{'GUVENO8~TMϹ0`1LB-rp09]F[nc3H)+ 4,3#s5G?_jKv!]\Zݝ|W6cru6_{_WLo*<3ӽ]):co$%򆍳o>Jp/{ m~D<r y8<#$YmW抲zq244&4y :_O~P|.bKBY6ڲI#s%6/:[@qkDžd;(QoA V)^TZn牗-`Adf2=,ju*(+gFlRn0)G$sΰ~/)?*Tſ=ɁbY"Dq@(J-1>81YqٸT]UķS!֟^#4m,_>4&Y+b'!o[U^ \FbF 78gr0kPz hsq%3tt2ՏVH;HhuN'nimq<2AA A-pEH㑱%a  ({:K' Fy.Tɐ)Mq=Ǎn.%?BS* =J$M"Y:V~vACM:b"E /a?o^/ZHa&T9:mnb=FԔI49dN:_G⼒Io*7?4zg[Gpoi>5 |nzAArI$~TX~GjѸƮnAyp\hš0TR?WOµ1I)y:~,r.+|1$I? $~f*i<8s/E y_L|'D)Xhύ:[j~wu6#66&cI$I(mPA _Sv5tQX|~UAR>C> .I$J,Xm[L+#tf7(AAA|i4V   ).]$&Jbŋ]ٛg0AAA|k$I$I$I:gDAAAAA| Y$3Ǧ@  I$I$I$I$I8R     >A uԴ#UAAGI$I$I$I$I$I8     0KCd}/Sј;O-AAAA I$I$I$I$$I$I'‚    I|7_(ѧ):_d,AA)$I$I'I:Ddžb[A=2I$O " I$I$>Uh&SuU'(yƤ᨜9W:X\jl'[c}* L .K'ڷ *AG#=8*ýd o+ҜH&1Ui) wtAm,C, / ? 9i_XZ#bhCX5VI$I'AEH >XhI-.Њ|-QRK[p6'*F-Qr{D4= iN䍲1vKI[q#bEhx,1Uj0-1Nqqšה˗.I$'pAEH"!xsLk{d(YSVYI&[im$2Ɏ%htiZ%؀[MI ?̨%P\FlmG&\} tI$I+#&Sw6$LI$Ikc&];%2{e%V)CCb$*GPWuQ[VK';܍qBf\^;G H8Ы(i\4rY$uIՏ[-&dK $IdY%3$˖-vj?3_:^F;_]:""Ν/1ɍnHcNn MׁB3MJRqD5j,I$~"  ) FErՙLJW@ #dN@45%Br1iC [py 9%D0 A$ݝjLb$B#ob&w7@lnغq5 \RIl`i˒MdQ(E   >ՋU+ Ls?t?Djd#[$NJMp)ir9kEhGS}+DD&F:HC`Q(CϪ ,"A?4WI!* ʆV0LVJ*Ql'M[I$|Vhr _U˽E<0@Hhzl᣾q^HZODXchm9-<(#pDBQHӃ}o~71?Ov4 e4}}}BK  ,5Iu5o==ʖ:IuEsKwL8<ȶY3 s  ( 0s.~d4$m&[ 9kU~俿0sϾ @zIo< 24 At0# H[X4@.*Ua {̰u޷[((B@U9 1 30A坴u,5 E~Y !_wLk?u6 u~qvH t-4Lq:SAUVr?<0oeCO90 0 :sZ$B%ĐYlK[s:%)՜mHGwX:~d7p0vAF0`Ҁ,?}`Bhd2)BQ_OHl!(`b>|,m, CJ}4(/K< %M>:k Y^yV륏ɀz]( η<0[A0oЎ8 ( $2Kut3FvK}HLkm6 r i b~c& ->5ɾ֢x 9΂x :`CΗnB d+9 ~:#ho0M{G_?NG=sYc@9O8 "koP( -{J u?t9p<9ܷpe}G>1 1>xOA⡆R<ˬKZcIIxۼ>I,1M,O?  /E;#=oOo*G_z 0Ozh~ΰt#?'<ʾ㊌y0~:kmfah}:,or/4n=8k6C 4AWߪ jz9&Kww8׾%Crۼ3j $ K M|IEM 9]CKJk5.}Y oz_%-,=['> nj8@#O>mj /R'$0Hq 2cúkazA y;W+ƕ}0:{ǎ}NZ0J@<8 <  FqB4@/>otC ( 7tn}"?gNjr0u%\!{IWU{a+:_y y u@W۪/+%ͅyW2 4} <Ѫӆ ϮK=uݽ =q) ,``Q0@1(EC˟m_%+'7D}po~J*}HE7u5>;#Ao쟔(>,5} ,Eg[EzdM `m*mm4gMaۤ}辭<ڮ a5}{2 71:D}?mTv+{ˣ: 'WA j6;" |AGQ>j{M0G}q<VoV4 .q0=cL۪ ,}}qr <$E} O}[;.g|=t||òKoh((Qa˽ Z O,dZ}f]=ξupm>_m+g<}}!J,O}}= 2'T W w'5W ׌}q ͧ=^ \y}KK  }%Fly ibk~"GR}>|}~+hK+I <Ӿ+j qtz|ϋ }vgS_3wD2X|]ǀ>2SI$[zx% d_˫i}%A\K80AWz3%Zٮ[ =M5}߬}/c[YAVU\sW([Lwp/-Sa! a("[ c̖_5}$KN8׸}\po"߄F 8?ODAL  <}ECMpν%tIC#`?ӌr>CC}= _z_AwiWumA3?Ͽ9q SsϾ>1>Ü4|w?%UPߕ`"<eGmM뫂g;/(夷RQ/N~߈< |_ =qK tAm}u-;oO8+o[QA㳿 ˼EC~On<_m3;P7 u S.0a4}c2>GMNjݳK,88Pt8S\O<Î0_K=RIG8 AE0]gB 1:ԓw9}}ScEw=6˿n!GӅ_}ߢ ,$ 0l0pse1R kFPݤ!WOj( }#6}NG ; fmDEl !JaquF "!I; qn8C0NpÈ,iʛC,Jh y~O$%AgOnM`W]$3.]k; wC!DXMaԠ^< 48|5Y&]" *)idT0 J KzA$rzPqUIW]_ֳX 4 1,1 2+<0O?sL8~gB  Z"d$,IDq_y\I6s0<=?0~ 0}GaOQmG0N8] fQE%]ti A[sq81<0 8 9Lό0q]A,0C8C9߭< p ]]ׅۈ@q-:<0,0 1 0 0 0L4u7ʈQM-4?-[ C 1QE4{_}^ 00 0í < 2|078 1,=<,0A08 ό0_ϰ0s<0M{Hh 0 |4ӌ0~׼38>QU?z05>{NMq< 1>ϼsL2}= l <2߼O9N>{l1ɿϬy׌0lwO~ 8̒՜qq~%y7,w=3QT8_''( ~0}}70 7~?B 0A߃C|)1P !0@AQ`aqp? /g3i;EEEEEEEEҢJRREEEE)QQQQQQQQQJRK+])JRKե)KInww%V]/Dۮq=n׆[N+ֻnhZS 푱!D 5žuHIha}bo؁(эMq)iĉPHmˠui,<Ki HزgJ!鰵 k]hwYFg4ZBuL=ˠdzf5$Ƥ&49[B.aibC 65Z2ju/mxHUYZybGm՛U c_ZyiHbi6O//Aqu|YbcI1`tFM TFCHnŘEzoEY=5Z^0{-]Iŀ]SUQ :7_}{{2u6^9Cp˽)zWOFM" юeHKFڡ5X[bzFA!% se/.: 0/_|9.;{߬u' ! Nt&X-I9r᧶1d!B!4VWA 4A 19\2>QZiٺɶ E7< pGf$yo߱T49%g)ȣMet5MQ[)J@\;2Ֆ1m&14hN/T3?>iʗD,"S䂫ϗ.!#UZdfi"\mqhi["iZg19͎fW4$QFFBkDا`x+y'rC3 H _8|Ф?'(2J&;.|/*p/>`ng<$wUFBU}t#9fU=6 .)J*64:4Q^e:ߑ&k$|Il{BH\%#I!;Ǔ(r+\%8Ж(y^ƍ^o&!§;}QVܺ<>"i54xCg6x ???G8~4yƏ">-[+r!HBhDDz>׀obʟwc5w7N7ASk|NE5 2* RmϘޑAA%Em= KZUǷ.)r{.&bz.(Q)Kµʕ*R%{oVXB8ǹ.(12om Bm)zzt/'FoZ8.`zh.wI$SY.C+1@PQ !0A`aqpѱ?f57 J_]<'Mm\o&7lsMw%up! |ddddd|2>d|2>|2> G#G|G|Gǹ1|G|G|FB>FFB2 vBa<BcBa B'4aJR)ziJ_/ha 1BΩ3l KM?fa'3udG3j4Û !ئx}K7\}70_lWa "D گJz$OD&_m7|YU.a5*RTY"V4 b2>N6} Ʉ!La0fz&﮲m1 M:ʗE5-Wj}Kg!Er8C=3cO>c} Y6^&.f%9'>WʅR:oas  C@L[ؿƇPJϷK3/,=U/%{ K k_~nU+55WQKfda:a OaҢbcMivL !B!B<#j+ԁ(#B_bI/zi"xiQK^{vlL27:\)z)K.7U+{+yFKk ބBijfb[ucl/M\^)|wqi1+QJ^OX.`3K/D5D^7IKޞԞMT^+]Kn{,k&jNG*! 01AQaq@P?K ܜl2`cww^{q'HuodZHKbq`PeSg@GB2dll=9tœNX^(KHCIA4[lo$8d!5,ihHB8:F .$ c3 `"dL$YXL o$D,Y"f ¡Y21nIw43/C8qrK4-. ->DdY%({~mYv& Hq  C4{ xx& \E.APu߰hDb /a;+"X`[hb69GLsE0!F # "|L4=D $HLR.,`YpHq1`> vGcFE j+֓5 ^:Ď'K9&tp@`0|,qplM-g0H KHѓ1`.AlAݐ[ݸd{Z&"&1xGdD[I; %ĸ Kέ@q h˄,0KFİ! ,/bRuH\}h)i4Fw Qr) &:1 R`,tdnZ`% $.A$"$bhc-.9H`Gdc=c2\ !#Ah&#I A.-@& bJ!=jH$KKn44hJ$B5F-s취'Su˦ka ǰtF}bfHU1-2t pU6,tR`LuQg@`քz4ndqg,u!9FĝCHY nhdh@ 1ҡ6N0^s1!DV!GHLC2"{%٣1t`N˴j0Ƨ,Ԃ I1 (,h!/!p`$D"NnbH-ٛ tC@qRWpʌ$C -F~ed#:F0ۡ,!4oOAF~/dÌǰ$H:`@l HBDd!-;"t0":=- h;B"9{( 0a@}z0n9n?fɍ't΀ .pcI(Bʈ1Y Fu &TYLBkl"冱Ų:I7ii :d;:.T ;k/4ӸrC lfH8hD]^@ڒߐ.'645*oN- H%ƌcj9?#`dfɌTV# $ d4Fd("Fc;jCN{!p'H,OIaTe- O̮aчD`uf $ؓIH"Jdc8!U@ %H{~$HVt I#K.դvIi^=]=4)#p6OlbIv]Zi=КG}PRQrA(2ۣ'#`L!3X: HVpE &{MXb,cD} QAEmhB D%c!'p`ͧK,a 5dY1B!в4p@$(Ob1"NhujbBNu2]ao6LIœ*Hؑ:lzV{OY=û7 !dё$_ftčR%4m#G3M q ƽF/c  $i8v$dئ2׵u gv A)"H"CiDTIĤe H V;Ig{㢆BOsM@0rzCc0GN HLFdS "BlEhCB%ގ +]T@8&3H5j+*gV8 ?)%YSZ@ ik܉b0H5SH]qrEH6sV AbKB ``gܠnGl(V1n]qLdv1F6FzchtR۝ Ak a (ƋHIMB=(5=me~Q FOxl6Y`,55#&@ښ62($!4b 4Q"BMm]-P1HzAr5 lr KZ%i'bD6d:1XFȥl_rcJ@:LlFDqLz섔v (1{>P!q sqI=0DbV%F36X`"6lь&Vc:i]'fڱ`J 8:hLK Q鄡( [ $-GFQ05#kT'iHj6g@Rѳ V@gmc~AP9ӄLnL;BuV01ȱ(gI5ѐ$[MVXlʣB#<(0g[$!=4 2c5"Gmnd&vXX3$gjX ) 8)쑣h(tt¤3E-D,I v71p Z:C=" qR4mđM ܋U $"$}0e[dkZf# ZԘP;ؠ# Xh2l``&2Igưv1-$;_m*FQ7 :n{转Ggej`hIq@4K| ^@$=6C#H$HTcѕZ}B"tl xApЖ0-P0['G"dfXt u0. -e;#O]Π3#8h85O}̖p!z;h n3Ic'XΘH5dsuBS@@?ƉHvqbx# - pIvof,D8ؤ C3+q,9hɋVGX(f$-GRM$BDq9-tDKY!^eֆFqA,F@P~cm 2A02tXՄB@$!COB$TI Cteea \lf| FYr%c av%k3Ctr}c t" *ɐL[%;%"/cIFtdqGcZ:7N'̨ H1f m3cd-2swv EÊv wG(LTv у, u``zKt sRL.8 4F2PE0t齑($ 7Cwr[%;Ϧ#nڍt:60,KG7XQ/q3-dѣ)mF]\lR^̖ àJ8lBc Yє=,b$8F$L8H{Ff]+.HTvNդZ Hb$Ėʰ,]l#rɎ/I0%'I\,@41#lh˚0J$pR 8ݖ$zmO*JF6Ta ޥq", QiC| I.CܵN]I Q.lS 0`hm$lc;`h8"BNH |Q3BFz ڰD04?(˃ilP`ܻ Cىw[ 23!d:[{0ET ]$ը۰ J@a@0,1I20n#a@ms\m~IM%&"ȘB@l4Qx66t-Bc܀n! #K~@$c&tB^BuCXLPDrI^^&i$YԬp2BЬDA*#i`9Jv,'d鵠BD ,{ځH($ vq% a8-%.F1%02tLOeF#trɤhHv;L䤆8ۄ3p#kز,BUe Eﱚ'q0w">ĻFpQv27Qƣ`,0APP'`e vMяР3DDpI ,.U,Ѐh(Dz*h). VdЛ+Ypbf܋RO\,/C)#ƗDH]hP]ݶ|aYsmTĜCp-w$GKXwF IHG$HdL˶3KwݚF.qH,8hC2ήŠt\agD[2JZh[4I4!Dbf0ā-c~#(b#=gZ[[t4Tw=Q~BuAcAzTcP!FCtˤ2C04I"ЖapKSjlbc1!H0 qtdlk#I!զ1 P!$1p{AΡ,f2jH :ܳ I6A7bQ  *)Y qKGRp"3UN Q.\@3K T{G#umvpx],tFĀæxUȌ[cj8C9+ё( Xqm-7(݄>UaE%֓9 '5nmCK9g]Fv3,'0I!^.P.$;@6c̱=+>$.s o $}'`KIK@FH 7EX :$+3F{MB$۱5tB$,^=, ` 2 u t&#Ҷ=MU!qc6>,7cI@@9s:K<{ Rpi-$]J'XQ`Ht:Ӱp%\"5v0w ^ khЬ-Ě ,Rtr^1c%gcica`Z3a'AL[d'-G$ZBqe 80/d.ؽ'iT-)C&) .R@D:$4ԴJZHdtn-pHDF]a]CIV:J%@d:2a;TQ G Dm-$iؤxM("` "J?btdmGPa !ʹ&nJKmf>a"FofؒIؖ:6Hے.D%:@! zBC6ADruB]c `n,0a9ʄ;:$CP>4@t6X b]kX"B0f$ A$:Z -V1'71ښNth al+B tv: &A~V.r$& >TI5& dYB[e2;0B!( i*2$e`̣XYtKW>E!Wfg bK/pHOѕN! J) 'XDZSVDDp)|1HݚB Sx 1aE*jLbpKBPݸ<] `CT@@ lk%Ej`qY glˀjȆ@lblfT|gl(#+.ܞ ȰA9`na %;G Zm1L!te'2 P֋*v# 0pq`vQkI ffۉ]8@ 4BpdGRX}C% TQ,$8, !$f11PXD &%"$ B ac-1cd'@};!Y,E,Dl.٘h&R )H"p;Rk :bH S$k, ѕCE:@Hc9b@aH:Aoyl u0h6:}$#lBzm"A`H"FDб[J1dGobG$LK/a`GbHjd2Z,.։llHiՂ`2^(u‚ݻ`n -0;0 x Ԋ>IĽ˛d*3 mam[٤zRv6 $s'2EM88X8F ԒEy#CHed4;Uh{.ڠAЫ1f` K{d V~Oa~e}G2|6|U, 4p?г4?i(nYZmۖk,;YiG2NQd}/dd?쏃'qet`Bӓ%4 ltBէ/h1.RORĻ I7OGmY{ZU7SABJXRЗ?HR2?j%?$CB~oK[ YRIHz?ԷP7Іt-I $W?(?IF?(NcOK:Bw?#9Yԟ{ig4?Y82o_ֿՁsIֱF._`ҠB~?MЏV`'{4˙97?Ӿ 7ެ+> $a >h7utkД(;]CH_Yߋ Ϙ4I,mp@N rA@YwD =a `>H"9$@0d#mRpxgu_RxQ ZnTs]@6hkj!plٮ eZjVիfsm}Z ڵ~/k/jTԻv}ի_P}6[oK|[oE't7Z{#_j.6(Я̾ [[v־iBBJe|j}nñ;oٿ2^2ή~otf X_zd-u#fAƌ`uz$v$8Rddi3*8Wx8NK|>᷾ 3c` 3<,&<s$8,$ ,8,<2: 7۫48H#DAÒAuX@jH,3lr 0Ep,| 8t  Hc,h7v 邂O]~⊔#7 }lCdN_0+Em,سf,f͘,س&řك'xc>)"`ɱf͛6,,lكfś0,MĈ6Y;%5 q~h 4%Ӄ2!?q~k_-wWI%_亽ܶ8Kߞ/ #N,ŕ%%7_݇n$3O}%6RӣKHHM'-8Qz`ZZe%İtnFnluu7[ ~6#hKI !a ͳw j Q ;p ,zE%K :,X~Hl}@ ,É8pc~"~X m 72ŋ i`tעn!c0| 2=q.{+6~ĞC~.͌"Ի&?bśbͅ6,Hb `ر͋,cl؁Ĉ+};[gnݳ~PsYߺr~s_h_{_hLF֯q+p/{w׺ .1Nw7Fɩ`k kk$HBrԃ Q7oقr $xÁ- ,[8N4m, RMi^ /tz`eT0q럖BO<㡟|w|N3Hx38 H^v-{O,Kf3 ,,ku-guo?>3Eߖ$0F=FÇceؓsymb_28y |3:,cƋ)a|[H Ӭc#~`8[qC5d":oDAh`mBk /׌8xF=.x 8&< bx׆|:H xLy<:<7vC,r }<}NsY%T' '8n x1" 0Վr=ȱ-mlqa] [L߇KH0MBA C i 6# xp rDw<9v %g=g|Cƾ]$sK6yp<+Èb%UW9Ýg!y8Yxx'#|48x V,-!89aۻ3Džob#4n3qfh8pbCF Q5,t/oLÁ--ud6'ÎO7NX<;|yxL,G8<_n9=uFWx_ǯ l9W:w y} ռK;|cDDZx|mklm *-)$7 {ӀDD">D8°:p6,bΣc'_ mymx؞6 83=-&>񾂮q9gAAgPYeNI%8?y ['!'9vrot9].:Ņ .ZH+荐 ``0{TN$<Ƽj9 xg;i#QFFY[7/Ä/ 3i(C-HI#Yѱ$.rgd)lAw{l1vŦd 1w9޵0=HQT ɨAQsHU/E Z~#5Q 7>¶&zƻ[|>y=q>gXoCT}8/Ŕ:5tQb6Y][$3ṗopp[nąuc6udkm$l&]z&`b[Ʊq$:t\H%vOȲ|O9sK}=Vfo/oC_',DpY # AuT Gֈdl<&m_T<_L+ywɑwIM B p p7/ _e3`"1X`&ӌN i`k`E=Nѽro]I׎>'nG}Z;gw>&sH~`W-i> k9w:$fl~|B_[y3Ã=w>#x\`mMy^6{Ŏa# +V,t\Iو!&BVN0 ;B ~x g998-O6C|<W`CD E28,R-@#mm[maHe>Z+]՘|{i$I˼>xcppf]Aˇ 1ʱ0䂗L}0 {(tF.]ȀaծK $/[ՌgOc>X@)Ob,>?1Y :rx[meZlAH+8!y9vl$Ӈ]y9 |~|m8<>-/>^[fKp[ 1cZ&l ӭ0IQp#Q;Nջ,r@.{à19,_-}a7,8##a-v9wj;Nov%p"$3S&""&'zG,Ó<<3<&g;C{9< ut8Tmj3b@:LmowI'hȋぱ:9|]x-G>phA3>V7a,ՖF'LXe Q =/}'7dxggxGpuY"goP[<,ˢje)a $3 B//;32X8'9zCw8O|" [NA~`?vݯح]W ٷ2K28>xF3_@mw" u3It"55,fA JNbHa0A(2^A^19`OLd| |_E5\-C-~},op;kp_ש^0q!۷dg-,8l#If8<7bL㬈[2 '2FxX ۲\9>)owo$rp'~0=Ռ^wl`ϑ}_K231,37b߮5$2I&xvypaXmKcu|eӃ":SmӀ!.]HxqOmHN#pb-( 6S4rB8Jzx>c> {M\t>-{dx6r00|%y%b#32<>0O񼏇r<uoqmu:wȇ0oQ/6Ydg)p8'` D G&rs GLS_| !x-,|Ժ8#$ {&X5KIY?m I$aFS!K .t x۳og>' 寡Gxh YѿC8jqX%H&@Yg\g|$g OOH ;IsO"8"<_cرX\3tG]}4KH1İ-a@:BLcv&+p%xWXYGx_K灎W kGp@z$}opfCik^YBg{2ڗgqdžl?1CwpaxX#by68EvOO谌+q,{ޤ#( t^Λ7`7Y8(ۇh=z߆ p+vs ,ô_*1rY{3F>?9K3K z}m C"nZ{ 06G$e`9w ;/kk缭p>?>9[|K|qD6d2Ưхd Hc+='tI ۜ #6&dzYo9op)<Hzfٽk PzAB8Ј~ی>VОP CJB~n%u??mن]v0z/zpLsXl2G[{Dc>jp?"_`%~ G;`װc猏A.8ps_gI_oGY[wy&>߆0k3q?_qX"揽R\ɿ 8Ў=ʿbh pA_A?.ɧC:cw$OOW߆MeyB|~'8?-8}%?333^)ol< ƜoDuۑ!| _ "5QS1XtjN F%&ukgG/çFzgo߂_ u u>goTXm=4'5 \{ډdMޟg~ U$?]mff Cv((zEnpxYv|Wwl~~o{>E_Rf]6ɴp`b#o0rgdp1k>Dw/J 'H7LUM4Gِhݗ|m?> S~*f((=‰S g}i~qwm;CH88$?s7Y*}~xa\?O >gk';qp|Y,exj6:CK'C`S::}0N"E|8~\8Ym506.8uu/P%l~O(M\??O+i|KIѩ.OUXDZI3XOtg@:Ӳ_u+N75k 8Oa3S3caI Cj$81N$ q/`¶ڈr^$qü1|o"du bcSm?ܟEW+m'v5EMJh[S/]$!MӏLoϢk <pf)Bsdp:F" > #0A u18AHV Չybyػ8՜o'y}2^K,ODFc]9{l(v*VF"-=bZfhՖajXi?)bD>o р_|vfU)_'W}'5䈆7x9G89ˢv67qw~s`\V&!BkduUdppl#ox00{?}aKxaV!]+Հ8-]eeߧ輜;D|pg)vX$pH@VL ʨt Oamє0{^I |esk>3G'mV8eXaa 6ŔYfm\wb8se9bW>_1I[m-mKaNQ^$ȟ,/;8>V' 凈Ya^ŸJ3cGnKY  BKo_<>z-}$g枰xm/?>C)@! xڇ>#dplec<np(']vHc2 錆ث7KNހV oi xzq.ᧉyc26ٿ+ߚ'/ lS|?npH|hDfx|mӍ9HO 9ӂ9/N/$S7Ju?N{1%ښݤtq 1=M&?̗g>oC)q>x/92hgnND%nxӌs ΂pGEpn;osoc"O[<Hx 2tq_3P9<20YG }hCO繖8J[t,$*`'5HgY{xmaHqaԇNH WIi&I(nc؞:s3'mr%"dB,:!yH 5?/*%KS}ה#O>AԲ/螃g oma"%}H\\X& rC!>83o[<4xxxZip-賍Pa4mni_IS^CDtO Y?Iu,uCV?a>0^_3*n  2<my4KNv0Fӑu]x>߂x qIA`ʡ{Ƥ i.' Lx88'_.}T)>ek[Y#/ᘲ--/’G0/6Ƒ*>u~--L{ߡd6 o/<dNy<w 11/dD??콀?"ӗm U7 7bZao'o#<|zs9DIaaR66f< ͳU|NwS<93"Sc0BKF73uŗij;эFCt(A7La|gӃl=^syxx[x}L<z;rv; v1ppBm`*wܑ "37!YgddC>/yoo{l݉l H&o°St@$ z|wo&:L4L#3DCy<<|7^wxQsDegp:7CE }gy'Qw0*)<[ygyY6|w[iw(Gk gH,gE'IC63eK"N}'ά2t]x ;,#(u@cc$?c3#'<^DxH|3,sƚF>_?-ā2-`'!`  Wg  Ku]4\ϊJ!~YDi (~5 ìp#~lX = %b̘U" iY,,BG0b(Y17 F0cN!lEc 8#aI}?q7~+`hP =$.tX=S[Շg\gqdFDH:Ҫ" WZ X>B2!>6@Dk>^763=ET_=- >Ij¸jcOl~ bn]HCi!5}މQ|z?;#Y,}i0k=Ё8>NolLyirC¶Śz(#O|ף}fx'f|a!'qKX̒Fn-m޽Hs(Am>?<{羓pvzpl>BOl2< )&BIFI͒n7 ?D>3Yណ>9%A5z1x+/?>^Yv9lj[*j$iجF Y(#<3x⃘#(b}[%i/Qtέ^K͛f=,t"}  l}G0RF`&%tibx~얱SaC`xX8RdX$xsVaPYjǔ,bDŽ76eXŚȚc-4+D".S~eUk9EXTb'k2^uZKH葟I}P|w39x} `Cn >BqU3BN4`+UXRX_3| CϮVxex|2%la%yQ!>FEO.%;0S!Ѓ0{&qeǠgYW[k`9yq]s<7xԶ"#cH7gH{ +$ktII+ Gvat \Zg96AAY%qq' yg(]xcux%d!I^<;Nr,(piۥ{v.EjH ć`g2 % clps{'qsxIgs9&{g y6qyYIYg OXOpxpmǀ,9 GQ><=235 :VAg˰K`<9q7ρ *prO|R 9!Oyx,\2389RN3$NI|E-wx}1Hx],xy.if,@ ]`|1( eǎB,3 by[^7Wb6.F\~۳rմ>&(4? /6-Z0ߌŇ{ТAJ#VFˠv2^rP-fn{2V@zom L~"?h{`,=i m%3lrIHl$zGCE:灙'\g93 :Ldux#EKeNBn kd}3>{yR}%=ch3Om.ͣPGlE1Cfgm[^U 2׷“n7{Wn D)/W!}Q# 7OePVz~{?kѿs:~ 2$Z)!;*K,FXf3Ƿtp񱏆Ƈ+g Ƽ*h%Q!v \1ыn]Vr0,TҕLpgyZi*qgmӅh[e\ SH_B{@>d @(unO|\DG ؔb}m.YO ڇ4to WFlObi}-B@pC&?OmAj7q?}#K$~{v}}9ǔ2s?'_9%V gd-GN*$`,IAxN%X<Σgb.3N~2,;-`ce9%$eJI?+a}o}G*G'8C@?=7/؀F[[|#> I!^?M?}N|_6/>TX>amRK.>/ MAI B&G mya2N c2 cu08$Ydd@pܑ<n,ߚ7߱~Ӕs%#(`8_P}`: b>#(OYndB{C_+?p=X _q1э./{|rdSK1>!w%>-xi\0!f3Nw8s'l `%s V %2BpGߎ8K8&N^S'y"8 ,1$ 9Y, ,΃ى ,"`d 3Y'3$%|--s^ko1}GaZN>T:Ѱ _3bdZwll34=kka]F (KdnS|_6żӼxxa 00pA6Yde0;>D`sq'Zo5 "p̞.xoϢcXs<$ah;% {V{ ">>Ow'';); oNg k,GRAJ]2-YÜ:`g-94i x$ yEHN aQӥBtq[+(r=gq>?;'_¥أ`;LD$` 9$;{,[~͇ߓC11gCf ~1/ؐ,#$$oucg4 , '#8聲TtUI9 M IWeϸ|R̲{p9:,`OGDu~#8vv|pMS?Ɉ3 H`p,O k[+8˯'c㍞[ ux[g9g煈0 2tFF1Ԍ:>I9[>Bi24xoΰф@o.l8 XgAHAdIg$$,|ϡIufϋIg–ou[N Kvb v]$nn Y2v ^7ayOa1'b ۹wm= !8I&SjLyx|8=sОO9o@o:C?'3l H;bB,X[}3g/y}SO7/k>q87/%S-I?ځa%< [xClKh  evQ'l>Vp0q&mxgm#\7^ YaaaeAٲ]SضP/p1.*z*qC᜹o&g>: %d^KuY4L[Uo>5wL}|M ,,,Xc|6FtgG!-|Fz;91<6cxȷJwJ/ViHO~ӣ!,82ud3 0}9y} G;?>E͛o˂bZdQKlpф ZϬ"6>?s䆰!}ZHacpq㱻;f`q.@oIq3<^6AvhGi2eFb#*LPF^I8ܟ='_ s y'=cp #. ؚY68IMY2NQFD{ r Rx8l)>u˱e@Ifi=fp5϶0 <6| `D9a#7LL[R1al[qm mw= _omKKpXymmoǞqBiz}QfwR5d,/x. nFL2Odg x8,wy}8laƶkɋ N4û6mmMy}89N7ӆwXy=9kIF{KgK\11<1Gl<!eXIeYe6YepzB>yeZ6svmxm 8s ˮ8vD Â?E]pc+Y/j[]&un H񳶶/L6m9m qeY1#,$,l,Ե4۫sDmmxo mmxޢ[a_x,8$YeYe2,l28<_TPO";s/*a@|2`NCi$m8zN0,vH:@j Q89ZY<02͜3'}Cl/_p =Yd{ #6#7bxS'9S8sS˫ l۵j,-m_0Po'XXXXA%r3^"?ԮOg *qd&Pwd# ' g1p6Rd[@ aQ{:-8ۮ&$"Υx׍xIE,l7l`,@3x-Z2v-mxm x9r͏iagDy,cWc>P=e16'd&GԨ; $qF9V 83$n6};2,=MFWyrX9:*`YWNQaRdF Z =Ga-=- $P66>"㾩oYXXHbիvڕcg{mƶ # !qYc 0I;M`ԱY]v0;jZ [_KOppHKϖ )GJ03Sl"јV&yԘA#~ݕUJ @vc; 4sA#Km, $yPXY*KN \,[dpMMdgɚz_90o&nKtpqaa"̋6lHἵjݫVll}SGH`f? =U pn!D$qHc+#Ic8unJXH';Q b` X)!j (ٯ=(pK 9Vt:rpєd0!!ݲW6@](1\A!YtA2u# %x_!8 Ƕ$ž81bxv[a| $$,me2XؐK{Ԝ<7NmFR\ whQ"218Ck߼aaYxyq8v6HY0,ݻyjՌ"6v\BZB<d* >C:_4C| q@Dq]=Y<DŽCaN~%R9<#J9!?* ;cN˶K><PƦdЈP)ƴ GbM D11nKwf͛ve\pjU:NxNs㓗&keav9[1HDBCC db3!%ol"`Wq:._pmoXXXHx$|vKKK8BŖ̉*ǍmHXT  ش_eն+;)ހEW2BHC m-@Ft& z0ma^:T~vB9pT`Q1Eeo@dOp^"$2fF;[T10QIb($*$V^# @d[6n[ <7m᷎S$,6'-]VllxGx$H[-Io{8&Y<C a*}f{!=,lrOƏGZ[]JXrpXpwL6MKm$Yuax6Ǽ|>&+[mxn-nz| Ggzx88ֈ4ƝdC?-˫vw/YT˝}8{83e?,\ aNfSϵUG7L@l-`I8tdžݙ,(އE 8aY6[?,x@Nl< {M#KKuKcݝLͽKW8cD fwupd-1.9.16/contrib/qubes/doc/img/uefi_capsule_found.jpg000066400000000000000000003465741460375044200234260ustar00rootroot00000000000000 ICC_PROFILE mntrRGB XYZ $acsp-)=ޯUxBʃ9 descDybXYZbTRC dmdd gXYZ hgTRC lumi |meas $bkpt rXYZ rTRC tech vued wtpt pcprt 7chad ,descsRGB IEC61966-2-1 black scaledXYZ $curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmdesc.IEC 61966-2-1 Default RGB Colour Space - sRGBXYZ bXYZ PmeasXYZ 3XYZ o8sig CRT desc-Reference Viewing Condition in IEC 61966-2-1XYZ -textCopyright International Color Consortium, 2009sf32 D&uJFIFC    ++&.%#%.&D5//5DNB>BN_UU_wqwC    ++&.%#%.&D5//5DNB>BN_UU_wqw" $3j)dd* d* 25 *CL߄]Jh̀ "*Qe"4%"TRhTʉTЍ 2C-SC- LC6+L%S*%R(D(Y@(X5q+ySO @, % <[ * * Ԑ kv|ov?O#{?Xx+Bh ((  @R* K5qJ P46 X\ҁ@(EQe4(R5xK~?M @,"(<  !EC\zT7[!H(e%(B @`X*LB* H "@ RPUae (IVPP% y;9񿽏pP %B  @E@f@-?#YՖŔ%K*P eH BX! j *"7Z(4ܐ4 \--ՔQeP(-eHER(s#x @@T* R,UB,A,Y!ͯ~cY6 XE @ `X)(X (@KHD5) * ֱ H* `ֳBIKe-- %"jPS߇cWrBZ( "݀ ʨT* P ,%@*1,@h((--.VRb4Ք-YJ@) -U_?|133@BRRH, (<R!"KK ,|w `T(  BBXTBLOO|S"5qSo61_1=q7% PH-k:Mշ:Ƌh͓VSIae*Qe@.7.bjRy;s8dʥ,X)*(B,Z<@.A# EBKK !O~cy,%P%*P @DB_Ӗk!(%-n~]/zcI5"P(*j--.-e.BE (CLضULF%%@ JR H*TJX>xB*QYY&u k4/Y € X*P &l"AH*Qfߟ+ߝI`("@׻?7W/cw#**"13lRR:-..:)EKRMAo?~X R ( (P@>pP.A%@f`]O{4,@e @BKJKR:gϿ~NM6M+L34223%hgҳs77s7#3Pˆ:(]\\:d-QeP LMBtƏ} ؔAS4,,eK >x * $lA(@PM|/(T`X-@Jb*İY,*R;ٿ[V_WmO7t=w98O7/z3Yu53\'5+3P HΠAKeZQe躔R- \-F5 fc5 hH, @>p D l"KE/x鍥4P A @( ?ԿN__WMg~zou8~o__5?UfG?? 4Y$Ԍ2j YRE.MjPKe(*hX(&*P xח5`) P, %PJ% #憉`K ,`"٣~=ޱ%,UDIeQe,ʋ$ֱ>/sw~߃oEƏ5'篵OzzcӦ^/_o1bK"J%X]\:4-F:)RP4-)IAB@׃|ӧ?_2Is@) ԰ TJ>`X.JK%A@EJ! ƏwoV X- -H7;qMf$,ToQO?~Cy3Z?.Osp9=}g9kxk{?3=3Ώk_s~z(ȊCV%݃w4͍Y5e5sU feP |ϣC*B ( ) B"jTBĄA@ ߛgj %jhЋ T@$Q_}J2}r#L2\S"46_K//? ,Xw4ֱ΋sW:-4QBePP heB(((*eh(*KEK~7x-*R \RJYJ (@DTK X-^;g?/H#H* -Z?WGW7"% B E*YmΣZƋCZ΋&R k:;S|m̬AFCRE Q(YKfe:Z΋-(*PQAB?W4  ( I`P쳽3M 5,B[, .lQ ?0)IEH-.Ke( H,5 ,$"RS[l?}i9/ߍ䴊#1_l4K%@Y@"@bK-.RJ!Be"浚: `@,@b4P,B*@P, qSsYKsJAK(HC+*(­z>o]??Ɋ4,+_}5,BP%"P(*ؖThsR.J T R+\z=1$ QQ,P>& e") H,BP*"a-*RMRT#YJE gP5*J r3Mk:vZ_˿_kg? +ʨY@% RU%RiEhؕI% `J%T/wχߤ_B " @*44H ,D,EJ>'ʕ)J-܍k4 K$.@!(LW#L  @()IB)afiaTZae((- RY&h3_hB@ %@, 5 %`ȩ * hcdRJX(R:5e* @RBgPʌ" @ hKb-UbQTX% e%PЋh33IR``* * ?;bkP* sJ`j ,B5 ,K7IE e(Q, )k2f, D* RR  X-b4-j"QhXՔYD@EegP<:=wVJ `B@)9sf4H5$44"R  Ԃ2:|>1w R)eQ. ),& 5 *uA,H X) ((PEYaKe---R,jR(k4Pk:>~?I_ ((Ͳ 5w̍]M23 ̊Ԑ"-ȩ 24͗!@F%R٢P "`(P"%@ X(* \ҥ(((ȶR͍RKe `X*QeS{X, p6ȹur,4(#LL5 * rlxYEYJ-΋*P"b @J 37$,X P YJ-4 eIKsSV "JeP~s )*%R 9:^Iw9Dۜ^h:c掎cl 66l>omKeQ@RJP (L($K++B,  `R5sIJ eQM%e((* O_~OW (* X* V1wNn^.N.M<8N838C38.>7:r:hRM%( , A,),$,@`*"  \W45sM\B5e-5sKe(* E7W?# (* ** Fv%ͥBX[&vspYJ$gPe<sγw` @5r5rMKsKsVEP %fK"KL* X(*R-.M\ZƋֳK4ΡH* %PBh-_?FȨ* ( @,B yv6u`-JgCId,KRiukx킁AsJHsR](KI@*"*(5 K* "Y` *Qe e-:5sM5sM\VX5sK`U)39}>e *  @>=ιwU"@   e2"H(ʍ|l* ** 4hk4ՔPRآU$I5d(̹$ ԈH-ȷ4*P* sJ(*Q` \\W5sM5h5j*QA`hPHOO~}@ *** , >X` % L&*R(p p:`` (ʌ5T7+;q(** \IMYM\Ί(5 KgYL˔  * Ȩ-ʴ͍24lKsKsJ `6΍kޱcz7j5`Ue `JX*Vs?oޘ * "sx() Ae "1uK 5SMmPCc:S5(,JCH-*R5qZ]YKe- @)3Y3&l$ "B*"-44(5d5tƍ\Sw7Sw랎:5qJJP s>Ij X* %,,>e5L -5r,5L41l$PY&zCzJA"€J sKsMky7uz^tqWRWNrJ52&YFl,Hy!HDP CLW#W5qMp:V;=᣾n:^^c:^TMcw:yNK * `,* ,*<7{TM M#-@j$ MP92K .I5jXGs* %J"T"(J%(YJ `(( &"eP "RP P1#P B(,* x-٢V* .hD.hMC2Y49G9` K X BZ @X(:գ}] <`@@*Q`J%P4*Q,-"*P (@ A   .JPT҂S4"*#PCqdԌÜy>=SpuOsxμȲP(J%*P!`*jR(=eO:s*P `X( E-2P@(,`*PBBí^KLS: )%R-3hJCFcLڨ59(w9d89N:լ͌͌:C n9Σ9nN.Ë+{.ー9:NË8;Nヺ8;ヸ;<@8=;<@={=#<Ä;z8QE~[ ***P  ~z;>=#ϮÍ9^N8|L8Ô.9¹Θ "q;"W=o [OUCcO[=o$=<]c==珡<_ߞ>珢hO>e>+>X>cT>#>H>F:,[AB@PA` B yu6:1M4444稩 3K%D4l *r<5z, "˰dOfO+ԯ+Z]U?E#(*P*8RP**"3K 4<6?#?u>?>~t=,i5,,l瞹LN0+.c09H9nc 60NN9S :S ӛ9:ÛzS02ގ.009tN.9h78hOVÖE `7=+W1~B* ,9i@- S4$.P-34HUξwn~=~}_'Oƾdԋ MBKHS5ͽ^oO>U?.;{<>ϖ+͓ξΚ.G毬Z14w>L4ʣ=_lx}?`?9=ׯׯ?A?=G}o_􏝿o[u{#~t=~>'3o|\o?S~?bu:Q@gcРU1~pH B( @,x$B* *X)IPW;sϦyj>g3,yα9ϗl9B, "k8V>ϻ˛5ê^sNKt;f;N[1jCꌨPä%9:v-38k>= =- 9t `96% nDs13FHβ=,UEA`?G* B ¸TCT̢gB,Q,33y߮3>sy:ÞzDjgx4Rz;oOלQy?I>#hƳŝH=dnP('Nz1}wt>xhۗAgB냠>p3Cn=~ ÷*Ӛ>j5#gׯ>σ~SӏiE(#P~=b* X磏|Ε1t0ά5 ۣY"/g3,J3ß.NwP_?Xά `#^";TI>W>5>|hh|COhƦ=3X7zGC/<:w__8T9?k[c9*`羿 C^]̈:F9ߞZ#B99:1z9tN7Nw9vr8lHNzßJ.Lm g8XsS9w 8lwMCNn1֪r9NË}Lj<8`9;#8#C΋B @'>?c}ȁ* * @P, 45. LSl|>>|>ZΥ3FdTs9S-7K `Ƶ~ݳʥ 0P=O7 ;{ΌtΪ_Ќ=7Ϧ חR53N (Ք_kcRY6l|:ۏal3<;=O CC_cŔt<D?8z>?FNeY@"Qd6H* ,*  @* 1u+#Wls0?378}6΍2MINz.f rJ'谂E S8T:k~PZ05Cgt́>XL e;̄Ҏ'"ΣLjcy.:r: ^]*IϤ3c9Fҹ;a9t}<'N;"Yl PPC?X*",*J`I@ @E[ T dֹ߲tƷe4M |zr/"ύzJ!@,I:9kc99N/-lzÛcz9Σҋc31NS㮀d˦NnㆺXC֫"֫ގNk c: bZ#}=o#|'SӖ}c֩g. B%BH*( `-* * *Bnx4:Z$k\'?I}|o܀ԅ qM~kWXJ2_GKXCpΐ~>%[3`+1wg|OO>.t~ uOo>3 HX铡L7cn]n]K>2 s,_xҰ3?97}^jo6<ޯ74=<ӟRw^()5O̲ a*  UB@^ZcHW dc\}s_^{愴NX<^_5B ϧCSR[/Xӏr¾]#~xA. 7²4*Z* @v UK?/o>t繭SX]Js ç"z %L}|Mì9Σ8;#h9:8ÕN7 q8޴dHn8;8S.#5jNd5ô9ӌS`>Ôr<δ{.Ϯ5Ύ79w .Ë0>=Yb Lr?k(*P*P%@ yv!-*".Un~g3-Mђ6f5^, R 8i-\r.sM<)ϧ84z+/oRLOLuOW333v瞘:^sϦ~܎ǼGCr:4z87>kY2w>eppS:YT4>ȎޙlDgy> Md(,J"¥P B:98ӣI F#s0`~S:/^=1d9 QwlOY`D 5!u?A@2sʺ(?u }=_+h}>Sy5>qƳ4&?ABYc2Ju._Jwy_gX̥=w'eCޢJBh\,N'[%-T,9~pY`A B(XZ,4ԂJwK^:܂$ +λq  (@{KLSY=^xv%p;׀͜Ng:1ח38C|㮳P㣶49Nx42ƎO8]3K1@氲SW=|< (N[<,4#w霍3 c>[BiaIP >.s!hR "RX7@%(%,R(K`˧*xڿ?7˷3>ΫKdCL C=o ͓8673bcX;wV%%"OTh ( f(3H6N5 7aWoC6`L|Jr> A`X,Q Ll^n*=K3~<3Rs,3|"eJ&wǮ+dc^O71k>/;O_ޣ>zSyϙcV8`tԀ%E1n|b( `LY5"3L#m^T#\gy1q^ZLFfk<|۟55AB,D7ÿ;^;q=:=88#F9Z'3ў4\||S=o,=o(<_=o <|[=o <<cǣzd]C|t>¯D#iL~WMUJgy9D9yN9:8ރ8Ӌ88^79N㋰8ޔS9;3룅8^Ó9:W8ްzN9:ӓ8ޣn:ӓ9^9S Sz[, EBjR,.m&uwMdP@*X71 f52,( ;ۯ?("B\>~t=8^=}=doG#>;q)PE%@ ` BP%*BR e, `Y $4LҳJ d5%53JL4, %`PQ(RP(%  (D }Y0P2,d-S*2(*3xz45Tzd8y~{?+GL]M 68su<:N % PBET*PTD@X%@E @E@g>}@={D8Õ9m:nc 60ڰv9GPP% EBPQ`Y P5%% lBcTߕ_><>(O>⏵~ #>~ > H-?Lh^CH-Lgpp/?i]c9*37TE[dK,UATe ,"Q˒ ,( P(bP *Qe8uKU2RU",(,P A`(J%*R@$F%S+%InJ Q%IEEAP,,%P.KJeX( X,P  ,HS߇U* APP  %E%AP,Ys\G57H9瓼F|O=;LC1'C<=S<:* (VX2Ж&5w?.:_ۭG85|[z@ I@,RT PJ.mR*dMs:TI@ ) z|¥ @ (KDhIPX& :HVCX9ѩ=[ grK)yM[9Uwgkь#C yt-K+2qySNuh~[oxw,&XDD , ABj lKUAPBgpP(I@͎yt6e aKıK&&PY PPADRYRjQ*ŲHiRʹHȬ3n{D<; oH5LR9y~[>|375ïU>/ǯ;RAFm.D,APTeh,PX)*,, A`%(e9[j$3fB,)DUIe%@Ƶ: \R'Js,bn qL5(MoE"¢,J1'|^?5L붹I͘;>>+`YBhE")EA`P%H%X4)PHP@X,PLyLiRlE`TTTjUR(A(DjEɩ#:1 s1nMgZ9ky3ˤ4169 ; nMfE~?O-g:A]qE.hJ@ AP[PajRYIeDP&( @PT  `Y@ %=K" FfњXBP,QPHUgEynkVn_>]9 03 cX/ID7%1b ?UeKYx+~wA^ܷΨ_΋) )X P&(%d(&-AP,&%B ,X %%@U(/=ZT,R@*PP U$*(ETY Jb%Au-٧>˛q IÞ9 苢X1or *Mqs~]Ƶ_6.zi@ @HQDɤ,E nFdi@}yu  X(,  X/=ņ@(, Q,PPRPQT, !BU3zsα ;SR|μt LIPX6ֱJ Kr>O_>κ\\_&R% @&QP@)Bdi&A(LjR(K@U@D)eG}9T (,   Dh%RQe%*T* Rh}fsf 71X&1t3CͰs2]3cVZD&v37 +>G>[,)  X APP!BRKk: @**P( 铗鉨%)`T( *Rʢ E 5HP BZYbPXZ/^=f;rI1J35N}5 Qy3hճyM05%3zrOyxT)T@PXe"hI"hao@(B*UPS{H%PB@ BX A`h(&U"QD3۝^R4#7pŴSx6h K2[Ƹo$92^?wɯgW?[Y͚"hP&RYDRQ@@HP% K&[$.-.TcYYH`T% @@Z%S{FfP,P PRPE JQmT Q[‚R.n^ɯ~O];q7=3 mE+01 gNt]Lj5&_9x5d5,  @ D@P¨%P'^x 5 T%$Qe*PQ%)E% D TDPP@P+C3К s s"Q u YJ ?0 !01P"2@A`3#B4p$C+vRTܗ\K1*J8\\8K\/q{^/q{^㊧N*U8888888888888?!8~Cy?!-?W&%MϏNS08cׯ1i5>I;HrKTOe2G59)jDI$U7U$Iŧ[ܓu>L?5ztW ,5z<%|3 0d84 !peU.*kaI40Up)i R.O Ms݆3:R*x-gJA)X3)*ѩI|9OuR|Y|5: *֧I++y2PUPRj7~JES{/E+%Jc\a[M.J%5nzͣNGTwC<=}F 'ʩ4׼u1J҅WRǵI+RsUNxr'KU614}[[^_Hw{'VڍEQ]"uíO!Ox0xGUqĭev#ZZiVe[xTAsO1xn9rp(eV)2gVʎcLTe6ⱎP\&å,/j9? ~&l[Û880>Oykr7!}j'P?xq(Z*$X0!%|E: }w*W#?a!u,>>iŸ䰧,)ռ)b~#ޖtqI"I}ªncU!V/%xʴ=2WS=I1ؤ?bTuGا'x`R(}O_Gt?S'WdoEԆ7bjz4p08H^\J'D Oxry~MC긟O8$PmᢌHg]W'F%~~08O?߫t0 5$W}!Z֖kKZZƖ4,acNKXƖ4,icKXƖ4-B,BƖjj^?WħSCث$ SHg~jKQ!|08$7|0)5݇oUÓ~s~\{Zmʞ<=?<?;)H4KЉrtds$Tӗʝ($Q̂=%?U} &iP$% BP isK\J\(J$ύ(I$!r\QK^/i{ ^/aUȹxzyO/w{Oژ$~Q%UAWA"6{w_x:)RGj_,wx#TFY"`kUU}k&[QRIJ'ް5s|qLuߗG2 #Lh]ʍݿos9llJġ&mġ(lmI(I(I$I)I$I%&_QFU*Em----- 3|3n充XXXXX1_MhumI? z5AiIBItQɒ"$z^ٷsU!rvr*^eI\vlunw0t ܷ;0N]/C(%pR=׼D*YW4ɋM oB2LU귇ȝjuW:|s:|丅5?(oD܁*j-ʰ?wHR!HR!DIU{jEj:m0&rOc$y1777׾$rž6p٦ԪNXo>]RO!7֋:}(+m73rmUnvvTO"?J u;$ԝ>ttT^钢nnQtp+想eaܭk[_'tgM;}1A^tL;,r#%γmNg5c)PEJ2L%U"3asWBeGpkaQֶ'9 DUENVԨ>TN'֔dz .I~3 ]xC8N9\nHՙ\L> 8XcJs۶oQJ}NDI9.x{*=5]9eLB>5AԫԥQU\h{L,ӮNH.iQO5NjiR(77v%ܐvns 2ۈ?IAf_+ѝN&^{.XGv؍;YxĆԻ"Mkѽ>rO.%2Ij p_)dsn'pu)RuUT{kХGaH.u免m!CՔ>z\ʍQ/ ʍfQG9\:G&HT-f?SѪ'>WI$N{9UYė<" SIrJ6%NMʭGgܓF2CHe*ϯR\*UJaۗȝ "9h=\\1ËoS;姎`rՕ:woΜWV'w13nnA!llmaj.[KtkLhezGb+2M 8._96FKkQ:iAu.I7:Vf6gjۃzS=^:w77I *oy[>ujUj'zT% BP% BP%2t(Jp r E.A/Dz4.i-\4H4.isIisIi)4U.Lr:ií{Vܬ_ M$BYmUy17\Hq CTrZ)jjܕ$D$DXXXXXX,B,!k PVlebb!bfB-B-B,B,B*"!b!C  8m8m8m8m8m""w( ~6Թ]uO"2=s|7sV|HRR ΘrEU#8 NRg(BJ{ +h8tIPyz`;$ԽDԽF7dvMi3Rz.VvUZ7PJ4ի)=bjV'횓W[OدZѺeԝWtm+zܗ':^sO`s|g9ֹ :- If㥎krh1})L輦k]0Ȕ+]roc:I9I$9;f%ĸ\]QI%I\7Oat챢srM77$9T[{RAq-[ٚh?͢IZWz%JgU.iͣ2Nؕ^YU^zsJx5d_+Ҙ֫ks\ʖjtoAQSKDGov66$OR2Y!ҽ/]+Wz/]? /O7¾+bW" :1jbRS+5}JHW以2nG8jYX7O`[SAllBe.Ko.xmWuּ|^ _;E:MZeG-EEEնK5:2_*ª 颓w'Fj(/٧~EeUU%PKKM%.q.%.%ĸ̹*^K9St8#(gPAI*.b5eO Bpg;rgtS|aQT;3s顢840L'\h(zS՚uk;̯o^zrȌg9&BI֦'FRSS)9O!4Ȃ(\DF1۪Spo^FAP;\ T܁S<+nJ:vI:I3؄()[%/ڿyr5'%F T V4uN1WaWZ!t g)I:TS.nXN{IPTr5]霔r\)FjWhRoz^;u&`P\H'ةhQ+_=§D]H4)L9'D9Jb} 'U_=§F-IyAFjFp1KxC;pFt(FJd<U: 1+^Jg]e;Fʝ9ȌAP9VY⏥2UUiTg{B%DФusQw{iXw,͞ڛdF):v%lPA_Ppbw$wFPAiH3LA1 슲zw/'%al.PB &wd)r)](L3/N便bH*99HzL*[ uB^%`1 0@!AQpP"?kZ*ZkZjZR#R_ 2dɂ`0L7c-2-?I2ag)A} L@d!{A2%2\BcoW0CpΞ{ALOѭ.t2-?xLY-Zq"j̶mX2ڌaӧO Pӧ)يP%i2bPqĔ@o/PFP2JI<IRHssѡaTU$SVdeSeYCĔ炬%/{ě;̘gq=&MvL2dɓ&M7U-9JPʥ(ca@vU8ՕMԩHuT*ʧTr,sQaت,gX')1` 0@P!A2QBp"a?h',*dYEeY|[B*JJROSvYhEGH 1RW%f(nbJ' E SJk}ߤ sN)qM=\ӷSCb7hXbzMq f >p4M܇'M onNt )koO ARO^]3kb[(9[dAS}؍6(Ro0Wno2x%bH@®P$Je!HRe,π*,K]%J*Siven] [x7k[7h^:+*P\ B 0Y6|[hP^4ȵ](Q4'K( ܍F ٍQ:xQllΩTr5: مSdSfVbLچcjފ91?aڞDOzf:Ohu(6Ns#DvBStOCBƨ=km{YSkmi\ƚ[h+t #] KN"Khnb)t:mn:6PFXŶqQ @ 1 !"02@APQ`aqp3Br#Rb4s?{Ou[Vյm[Vյm[VյL2TʙS*eL2VZEiVZEiVZei i + |B!_W+NuzO$%S}sԫR5BߑjaBcm#ciay#DB 0Y\'A lV[X]$g-"0A6NѤòck*;nN S2ؤ2zGR6i;L&`6e8MT0N< TzEl.9v䖞#$2V0LpnWEA|4mYő[ڦVG,7{ED "۱+H -q=VC]UN#weԎ! 8wVW<-Qi95#` Ȣ6{H_ñ Ǫ)Ǒ:!su8QO$hsGZ9&ʇ~I|G=ےHsZy)@QwLSNJE]|r[_#:;_!0Yt+|6/e[v+"eQ@ojߒېc7Rx b28oֈj)jC=֜;,\k+-cY{azy1햨6{MQyPfH ,tv915}O_QqZT>S#MCgnLom MVrc:H&4~+eU韶(pVb[4uw&j/(Yvtv &dx؃sAޕ8SӀ*S'?}'V?(q=Q^{ouSv`qn0úgnNiR{{ 6{|*cvӰgZkɭnjkkDT\Z :ܤgHGvA54tzz!L]UtA8J0*q ;-p; ?jwzGzUB+aU F)A\8U PhYX#º.ʛ7(UhS{{oz*Uz +GV@"e<8n҅OTM}B?z6{ィES>U]ڎOP .P '}*ϨUGzzTG Zަ}UFGY+ F1Pl7D ]ҩ{\TCr(Mq 5 |&{Uik.qP3?MQuI_ʐM*D8D/eZdT87z֦rN1SgL^u 8QwQwӥ/|oxiؘF )e3}s%f RA0t]?Z':t.AYݛjm;w ۂ ȧ0ռC7~*sHoFNNmoUU;TN#W:bUQqE;)zoB_Vݦ~xL8 g7w͡{J-p*#q U^|t2"[BU HÎņ.Qe{ ¬ћCz2Y4(N `<&PEPsAlY4ii1cJՕHⰣpN貎6f!Z::r؃젱^ @mvmO=9Q܃JuO|__)JyPv A֪>ܨ|5ʎ`<@|yfjyXw6|50ueozj;JJJJJJJJJJJJJJJJJJJJJJJJJJJY֞^uLs,~54uwS;ZӖϭ疛ϣN=|53۟]SO6wS;ۗN sMMMMMMMMMMMMMMi- Ii .ח>~h@V-+(~$k>[L}QQ`rN?N{\1t0|o/s٤>% =RuMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNhP0M@<fC-J|f=w976n['<<۝T:VbAEM[xcGI`űǓr S;MMO1͞hX+-": CxV$G hER圆!uJ m؂k.Bf?;[M{$Eդks~h `VK +c@'1ut@5B$#`dF"ZEPDU4a&VG x4b# BpF`-L0}F27,6GGb'hF^ZU8D0>6ʫeA8$Ӊ+cYb!\h2q@C'¹,{-װ)IhDtʚtQ0uJAY 6Jɕxl&jӋGr1A)lpk n`J1 3My -!(9<&M%]lA?ѹ-.avF"04p*<B0 D*0ևziE0C%'ک: U7La^U> m0,cQRRXTzU%%%$0E;Qh\mphXRQcTRXUIK"j)}֊7b´V*uFXAj^،0aw|Ye:SA8}aGl/RlE`Z>' 7MDɧ HD I.K1*᮵ 5m#u+JL/pͲ'68_ v[`f/q{u޲F@g(C ǽݚS\ЅD5555;SSSSD5555sSSSSZJkIi(֐ZAj#uG|kX榦rjjj{U`!rUK{EYPJn,ԷdpSSjjι<7 8oVqF`To ;o Fj`| ڃDuqYڦ8sGwj1E7S{g]HKAZp0̱yEŸWHAT;o9G pxpaul#R1`h&*ma8j{{`2[l45 RU\))),u&sIIK8yژsVa%j>oeFrc\fhNGL(Fa˱Bab7QqUfчnŤMF$lШ2UeFY@aVnb՜gwơ)@D(L-2+L"+H"7)MnSr̾e/|[}ՒrRMMMMMMMMO<79 gwj'{"様57lc6fi q^4|5rV㹅?,!1AQa q0@P`p?!zD[!D"AA +HO$rG$rG$rG%!_rԇ}_s;;N'z$ɓ5$ptx#8*\Gp' 6q úw*:n9>r?g,ܳbv=gغ~bY?g˦*%W|zh p9;Sj&}p_\$I$I$3I$I$ʼn$IDM$IDQ4I,y%|4|X T b!:q#G8Ď vC^Ŧ7t؂+\R:"INQ]9VjḲл/D$oaѱm[[!lB؆6!  @4㸎H=[_r;;;NL'y;NL2g$ɑ&D3ן;Ӵ;ðvP;ĽķKKq-vcn4L4iX'q&7Wne&;'> a #6DQ- tLGٗ&D>5it]6ӿb<%&+R$m+Dw<%o rEkHhlbānɈ|޿e? up"Gg}8 g׿<~5B}9!yf6t7r$`l@hxKr7gx=|]Ȅb 2w3&]l&vfr.i%.2͍CpJ,RrIגmؘ/vK ;|Po^rX;$)jb>ͿܶI O{Z`Jk[P42,(ɇJ :s:(`+/ ocSĤzWv#N}w6zG*z} .MQѭuTۦ͋-#A étI \Į'~*OiYL%jn#4|WɿEDy DDr&cӟM@d5I6]}ZmYkKtiњ89bL_)3KВ~Zw]}ըfc>BY$B՚ODz8$D=V=ssD\g>_i&FC#ЖDܒIY8.qǿ^ކRYk_&Ď#s g4H5Lnk4_ f)4:](tG;~z4vz_o~\/7x<=( N=9jhnؐӯ=zm25CUbHEfh-kuI$I$$dBbQ-Fy?BF7뙿¡4iz/E3j[xMM:d/u](#Ic6߇/C "~Ա&¦}IEHn6Xv4UuQ y$3Wl#81o+|Xgq﷫C(9تk]:EёJ'TF_w/XwɾmMfH*ÿ^eikS~kI>ok4AȦ 5I5IH6iP?&MJ41kBkjm )uHoF]dD j_&z[i%9B͸˂zˡ| h-BX%]=4(#A>5f05 t}T=iW^{Cc\:$֗|&i3G풜{;K"IXF[c Hh/,uVHwNs_~Ktc.XT"2<\C߳&pW {4ϠzpY'%X$ sbHcsQЎ\3F/F_!}3DthEm#W ~zQU;dăRygǩد0u.xSF*ySUG߿"LVE a/uGJ~4 Xfkk8=]~ mVK[I"C 2“)<_h`YiPU!p3#A?2=x/GМi= r;s~=eq'Z~m!uq=$~cShMQbr ĬRu;" čH|ԭ~SZ,п;KzݯI&/hGZǣwbQkhx[QfM\(X&mL_DC?~S'&<;;|/nveC ,C4Txg Z$?Qn4=N6<6%݌XeLI,9&֟dMN#/+ gt z/T'd+TPxE[!Q_w?+I'&iW72g 3gocZ/x\MiZߚ4k$$7IM06[;P/_"'f{lRF'*HǓ{"r/C _"#f LvAr~NǺh_vIfDO_IMX+$_M(gO_n}1*d v>XD?xѲs헼C.IZ b2QD4L-deڟe~{ 3/ݢ7pbt]BۭP= :2JH3>ENXͷ-庶i-ғ|Cx,$, eOKzS[сzBWLY F S=Xq$:qZ!$llv)'# ZK-бO>G[CSEFGH6I0 ߥ'}VsYq_A2MaORe1a ?S"l=2ce,V"hS)2N>=hCd֚I4`Pۢ~Yу<\h957/w[?E⇏m?jzhgݢn(cɧ[.=y|3q J&=)(X\ hUE/ wefоi)'֭Fk'9>Otv7N+(<vI5f QMSsҿɟ_?$z$QgC;3$i$i=I>.窣=|&qI$$ԒI$I$I$I&$I$I$DuCgWq5x#_t|/ ]Ipq'qgjOi~Bq|uطSDFn/(i&'wb#swݚN_ꞈ"ZB˞ $BJEbpXy9?f!rxE#XWJeo}|yO5<މd9%JؔJI<O-7O>5owM4qAtjp@XB"@y{yPgz׸X7(TC.\O=0U!p\`#9 {9&I=Rۢk4B_X܋OJi=KR+=1XÊ?FOI%B9srQss)'1s'+ f{![ACِ!C؇b[%\Sg9qtĚo 1'pN 8gR85a.^A<Ϧޢ};=]It-wR/DsHi'Vu$Ŷ"$G'J%nZ'cY[cUzAr~rO]vL=_LA(-h/z=g^ A~" b:/Y&G\VN%&;!ޱt<AؒgK ()u,[C^J!xE'K_zAPG$rG$tAtTN3845pd^qlY(,$=;֖;6ޗ 'E'ܹrϲ-"s=,} (ƗqR'O%O.zc '#K1r`d2+|F//b^I&!tKdrArRH!/mID;}rO5yR IdH{˗.K%XbĪ&K\rD6bDI&I0%}\(@Hl' lǓ<JܕHԾԞ*6]/ Gr Ȼ6يO[_b^r"v;v% sW; $qhGe2mBe{Ry# qڏ8e$zy!2IDQ I$H!D D:tt I$J$J%D DQ(D DI @IDQ(J%DQ(J,J%,XbŨH"Qh,(,w; v-rC[b> x<!$Dr_O._c<gF.eLz&ףNd&L2dɓ&L2~>>:fL"Dɓ&L2DN$H"Dɒ$H"Dc9s9c1r)r9r$sL"0#IRMn^Xbǚ[bI-Gy$%v< #_؝kqQUPrtQKz%-C9C%-C%r9C9B[ܖ9 ns9c9ds1s LX!I2Ox'BpE!,y+/;$8ݭh6|wcEAghEω !HsWwbz *P+?KXhnx[n54dy eGr)2c֐bAz TB5lqLڞjVI(;Q"i䞉O$Yn][,n"+'c*Ing??iq{ %*Y"gq,X$dpXX&Dj{ W&]HݽG&KF=Ӹ. #2)'a1X[,i.juyK6<bݜ}DzaG^ty(i$@A &B&&w.cҵ|ƌ8L&[j #R CS?$'R e$Y<7Xb! wNk j$H*#kt4%&^G.dŶ!ll$aීٖw;S,fdR>"D"!DtH[FjϿY a4ّ(:3výn_#/eA2K܉G6f4y7afؙfE\ }C\j'99q*" ø;ht6ED  ^0wQ ^,ԫf ޴ .&p3IK$_73؛w'0nЁ8FK"% ƁCÕ}7퇁[sIkJ7$6!EƲcSѴD;1F?dg>M廨}#RO'#wH!npRZ ،BWG.u+AfX1;l_j_WF9M=}` SXdW1Sv!_xS.KZCrSצI_s-Y?0Fsl-Đ4d1;ۓeGRn7CwRkfQ-/alhXC\XȠlsDe.i)喣K7zh=9$9hI0, Zz' YmAOmC'I0Jg;DIY:9-FKT4x43}/#.m%G,^deSZM*I Z%U*RI>UX0oLfSF(C^B=1xbG qL͕`Y4-̱] D)FKY Rvr藢]$yⲄ#!gqwy#;Pi1 㸅D78#;KF=Lw1,.&-E̓"LX&4tNjTI<ܶDD%2OІ\Re%x%؅ >g|mSR>.\rhNWE˗r3H"&irܺ5$)r.4ȄKܾtw/hZ2w;;Ǝ˰jiriKD rk/i~t&۸2݊wd<*vrɧ&ug-˫6hw8 0M%K+z*CQ)iÌ!`CMe5ur4f#ƅ/T]6-U!Ji@儖O1I<DI/ѓñ,yG(<A$X4N  `9E$}vF9x m=MKF<`]~Q֋̘ͭW Mz NEY<bU4*cMdjmw^, :LDQ(IۣpmMULhBCBU}19o `<7dWi#V2tLU]jaљvP_BT,`;VLZ拏 Y7w3L b=,a([Z><c'<Z'qIԎH{K< x; 6~oO5<%N1Fl!CoZRt^ J|@Q9D 卷˦x!xOM|S $ލ o3}:trU^"fGI54w ]X)J&MeQeMZVmîR-]X{Qz.к2KG(ɍ&N)29DI/Lr7.Aؗ䗹, Gbe:ŋbv(xO,Xp\}2Qs:ItZş%~F.蚗Ld.,f<>šbۖ<޳)Jd+t[tJ ܋n[VP_bT4N;; cz)vW$`a<J!؈%}βrۯ/D2b,:mpٖf2kb.N̵;֐Ѐ[?{/\mlwC:Dp!E'p|^%< źEܽ^I ʣ'ZRF᠀jQrt4dto8پ E súnL6х+V!dwdw2=]wfd7#(qgY;!B;7'Bj<,Q[2p fJ9q9_؋YMY0Q˸Bf|L'a[M#`hM1~h]쓺GSzJSa"WvyTVi[WV$C.go1E`6i{:fbF hKS&Ms?į!$\%I$,A}̱' .I$#2nG1D;#SӽWOCEtQeԖ{QbؾuEcKʏԹ= Y7GWꈝ]J6ʑKm,Ƅf6RE1{HȥE|] 'hdQω d\XbKVG+R_Qg$[3 x/,KؖyQ%>MrF5pTd|!ȤͲn(!%SؓѶk$P"MDp+' p)P+ JbMC$ cx; U&; "h)٭In8 uwcD@o'e!Ey#\$rܜsD̓A99'<cR7g9g7մwqbM2V81FŌd w6]!U\y\!9ܗ7>Ń葸J7OQGN(9`ab#(<2xdX%bxN/by%NؾĽ{N愽bvi'a!;] MDGb;;6Dv"vpq A<IdMr ؙlY޾w_GxGܾ;wЍKPdd"LBR_ђ9.m!Rb; wܑДnMxGRenegUeg>t]o omˊ,!eՄfVx]Y ^Y2`#N[C?@lTL$6Rƫnt٪^3Wyܮ?6Gu] =jI,|GشQ]hj~?R}bhFölBڷ$K?E[M%P܍|dRImu>*+~:+TGpЌYWbT kZјU*SNSU*^ͅ~c6^{< ȵTB3UT՚pCa6zgJ'Ru(iSHb4ʹEwT; X|=BJN\Ymd_9sVu7O 1t#RdN`&,@O0ڕ]$NDϫBd[GamE!R}L zm,V+RD]:k.CBVm6XN7&TBsg <3H"YPZV&biQYXbٗa\ a^çX$ "\.E'RSKWQ%ǷԆ_ѻgh-4db%bV)q؁)^PnFNNOl2eĶ"ذG]+u+ Ck'j>oI1eB0tʘ  t΍u^1U TM25ĜXy#ǰ0DC-LQ AjZ T\]VX{'N9XŨPv-GQrAho(GqbS=)T6p,g8qtp pT&L;3tAr"ENPjG\*bwY 8 /amwӃ5TC4ZI'x'x'3bR^ºɸ$ntdcJMF"IQ.8bOr[4Mn^<ݑ܂aũj!&D}c@= B U_d#OZb_ލaK÷'z:.E M'gK5&K\ W.5ؘi5?PX)◣Lh4AGIIrر֌F fg$Q۳~ݍRP:gЅ/kHB4!;G`5mPX_ jR5^q n4G~Dw B:8k}dO e;GgY0G} "Plw"RIؾrAB @U]8]l1DO\HCV<'VAa|= cʐF TۓLp,I[-phZdurBx_SD,u.%`@ 1 7eGkfpfԩ!siNus$N$]Nkjh={ѓ0gC&Y0%#vBcI=Ś l@*1(<,,HIA}wEx-kj\tM3LR!uZdצKSL:;ᆮl4!Kf'*RQ٨xi?Gl!(I`aeE<ܶuw!mRۢۡnp"}}ȝv 8NF93عt%%E\r5c{">?c$I4'IHKZGp b؟Z1'~O. \^$dv;Ӱ;e)1۪LDqDǡRt|}Ma]g BOywqFnmj;E%s"482[2 M;'6d1+٭1J!Fy1ȩw tF'K5Gj $I4$H8! &wq~` }Kn4 #[xHbp!HlBAApE 8 p<C!%j"($@"DP*ބR)&MT̙2kRdʙwԖy5q=r[w9;:x1STB[t:j@3ZMiMpfK3I2`'$tdЉ$rLFIg$td,˲I2`?L~=ˋ\Y$y밶#`kbVHؤټɂ+k# dAfMLO,tEjJ%DQDQ(Q$QQ+I(M@TMIDIfI&),kZ]1IK%$.Av; .,fL IsBg#W.$IqF-H +tǡ7tcho29%/2I#e[s6ܽ/tW F fiuCsr[P0 O8G >I?̩Dґ~w |?G?Û7G?Ց˰CZRuђꗤD\ԖD`W0d1fj*5aQh)h.tvav #Y838zXAtN/,gR7$714~@)wOq#'bEKiҺgM0nl}=$dЈ41X2`ɨІu\QA5"⤨3))36Ah )tT2 _+{[QsTw5֩ULTeSQaY$ ,ҒcAf j7& 0GM v l;HF6l;H4ava-lK%-lKj(IQDHE"]1s,I Ct;u544Ƀ&)5 ȭEL2bj5,faM&`M&L$f f)'$tdKؾF2KTMMI$CI *ڈA b[ l\ęS:WEJ:1"d].3]hSJ3 R`p+p`yD8p`q64&ɢdd-QpbFī 7{*V 4HAD#ɢ_1}Q HjF"sLTW(4Ҍס>QTW`0D50XD& DY!1Q5IP`m3 p؜TGf'I)ǁ)عQDL4YQCD -5r% 2b nB8^JP9dO$/"mxե.Gz*M&EI3L+XfHdvaY)jY4NԔ`0Y8"IdMܳКMlI%5cA5ɁHL2F`ȡbfD2I$Qbč#hEĤNn':mCEEtb":YTNjL2`Ƀ$'ѓLQ'3|zU`!x L2$D :0bLSI$ڒ`0dN)4 e%\v&XC3$!&E5$-D XLG .\R‹v&JbX!Qbz34L0dH$j Ɇd&JmKia C&,a&jZEc"p6eёY&,hOqYI&K& Ќa%ob"YfMj]dMCr4 Y kZXjXQ%cdGDA>Xp:;ȩ4☮ED&2`Ƀ5 0Li Ɋ~x5.F!52m\2,btM$ I$IkJI29FY\ t KCLSQ"ނ7hR ՕݑBr Q*BLx1Ko Rtw$b}M 0sj&&)4ɂ$ 2;0dH QD5ܘ2LIL4ɆDuH.D0dv2`FTmrɟXdV43Y&I$`We \W.fGGf"HҌ̑%r&5DXɑX&'RI$IĢ! ?(+>h6T 2`Ƀ"d IL0f0DA,A4Fdu]ZQј.#mPH&e2^)5td2%%F#UrJ[hy \iȥsS#\%N춆744C,emCPZ(w鎈D AXH`Ƀ$:'D$v&Gc& 2`1I L5j2dhdb_):;S.ƤZ+0;dH5&`Ƀ$J9C2aђ.E5Q)ZR.<҇2e 2'hj&ZHnjH&>5DI%˓%Cn"j&E& 2a&HԒ: dv2jd)ThCa\f~DēRMIRII4"1\ѭK^H"] M"Qr-FH2/M q.݄Q,Y#q(2] ͆X@OMT\ ij$0L2`Ƀ&] 3#Ɇdvfh3zE"jv21 W>G( M&Q$HEЮ`te"ƋˊTT5F**e L| \KFӰKnH#6ABrY=A ,姚4wI4$I Lv%2;0dfI\ԉHQbW;yŗ&BFHM$V&L2LRY\.t].DS4"!Q^Tjj .$a™ V3oArL+:& 5.\$i$ⅅ.|;iD{! GbL0dv3L3LkQFG#a\vb%&Us#W$ԙ0dɃ$Z&$t+U"Q4"!ziM+2񑧸!A_A%{P@$o"a]#XȦ7ǚAH&`1L0L2`Ƀ$ &ȤQQ#2;1dvEz# JEԊb G:> &i%2+ ̘2E $R&z+3H"Q@E0+#/ 1҂DK/"&"c0^]dKZxdA{!e%7Fd.%O43Xy2H.E{ VSL& 0@4 " #PLQdv2;1)5&OɌ!LfM#&Eɹ`ZY5"`TE%R-FQ41]҄iXXi%,p$cR`L fĽՆ%ސ6epHRtN@$2 Tj L0dv2`ɆD*%K"R-KA> w#2i$jMIɖbte ~@1^ʤZHE[3ihA\(m "U4Uw%\CT"U`Ə#$]5$IH$& L0dGc& 3L&4`Ji I$U\H,psE4]4"AT4a6=CC9 AOqc E<,H5 4S  Uq}g<B&S@  AI4830gM4zi-Q]9=,;㝨(D]XA<ۯy ~04$ 0$ˈ#q֖qrPRY<'r1Ͼ{`qq]xuPӌ -x̀[,gd(  q B,ܳ3}uA{8~al8 㰀VLswIy'[]0^0ԎK⊂B^2QIߙqlO8vAt SR,SJ|?@?E(Wz1)(ARL2DpO󠎍ۙ?^˔<>۩ly et$CO[ /A=* Y.HP2 174Y.\6ƀ |ۄ$/j" Ҽ{>LI.횉g,( ؍q2ϯ_*"=l/Eq>]2|'G13y0I$ #O/,  CASM[UP41mFSm.s8 jaG}Κj9 4 d((3MC=n2v]QdĉTq]D?n&v+Gp'8˄:@Yc/f,2_+-∓<Y!Y8 Ly_Üx «v+/_D\.,BEg Hau'a5d@+ '`CU{f<\8ہj/Q2Aw-`TM"#)` r}ܱ箆SiO8Q\pӻs2*A$>RUV#`8@q(3O 2 (ÑUj8 ):$((kfo,JMSab 3\Q ڊa)$+<0g^ߪ!yz#/ꓨh{羊^D*@:Y-} jpL1;kF<֜ aüB'y,r?K kOqrjMt.p[ݑs >ß(^8hÌ4M7|+sgKt󡲨acSijΙ1*!I$b00O<#ALpD7ӌiA7(MW0,X8,S)c M9Íx˯0<1O4{=Ģ! sM5"xRPk$튚n<jrH6ɧ0}{N&_0K 1C8ü {2n~Q<Ć)n#]qǬm5_s8B, @1+kǜG(Zyeqˠ 2*0%[Ӯm 4<hO8o:ʹ]M'_I5qFn3]AZp6,?qm4MBlؓ@W(g΍=vq}m/9&QɿcumaO?G6Uuo 1g<@36_{hy)M7As߼ްѝg9aϋ>%Dh0pdGOMzm]ƔQTeF7x7œa42޽$@[?<}u<?_wQm#!(ĶY|;ˍ{p,>}ߟv=ZiIymQ`.zdSAT_=j]DžkB7Skˌm 4~}=˼gCYREҰ`@Ie,4 Ӌ(׎=g 8͇7We?P_qmAܺQ;[10##K1M Y7^Ooӟ^6UL:A~ᰎJA40o=FiSDsh䱣tRIB.鄙{mDžQ՗R*D5q^3wWn 79d c 9d0CKX7 BCY?}O?~waHy5=<nj<5qin8=dYs F <~ͦB5mdI~|5g=28L42qUnnC_Jퟷq3=i`sL 0O{p,<=;ߟG\3~0(eM  0!ags 0sO\oz<8TH0#KE$@ `baKn% /O* 'K)z)A@ p c< )JF8Ά2%N$sNRN6TL&ƑY>QF^0o iA׎4_~;OMo &*X[0"w&+V) `Ŧ  =z/0ʐo1\w ov󝠾EM~[<&(GI4=ews,\ 닌!($OU=2Dm/+Wi'juߘ 0 ? ?0 7B0='#  |  `" 0@P!1AQ`aq?{tS0&!B+W\cO,>G"R>Bc5.o>.R◛ 0!ЗG }Gl8Bfa8+KGJ]4F(U3no«<EX~]RF>R9 Qp(^φ\{o(2"! R5=1r!01 ]!9hjV7D բ1Tؗv sa^ON-Ѕɥ)w߾ya ^B"㯸T! 3 >D DDD_D_D_D_D_D_D_DD_DD_DD_D_D'Wq|r Z4u & ~n5S6|&gio3Nʗ]zRK̈́&fj$cQ.&#BvЈ" R)JRJQiJRsMiڷĺBffe⯱΄&!MCmwEٺfa1 M3)G<ͩ„&!32J\[Rųxf" 5|+ϛ΄P YKۚ!5Mٳ Bjp wBx71Bn!+45{1LouKO-hل&!BrW"lBCy 6j*F߶&bJdƬE!1BۍB }dzM/K>;wt <T[T7j-DDkdy?x>ޒ!tW?@#dm#aΘۻ caxd1/؝봅1!D/k6^|&%/%_Cs\/nU4<.iqKxߩHLX> N,^tƒ m$佷 ;hLHM> E^Ph~D#)u_g9[nz^;7 IDzObMJR)xn/E8ߒ^~t^W7޼#Nٜ7fJ++W_ҿJ}+_G^xMwӿZ58&zZGkoaNϭmItR'RSQҗ)JR)K7xuJRj_EkLw]tQ*侵gYi?zl/Ziڄ!B!B!$ZB' AWxR.)JR]7)sJh}ۛȜ/|!$NI2yG^ H[I\Dxp3a335LBfEYHUSJQV[ ޕ$*** R7 \&R)K&6m)kk=[SH9~ iN"D&D&S[\+CeecD&qJR)JR)J\R)YJR|*5҄z&%f XgJ,N KNGa-- ̥ D.qR)JR)JR)J\\җåe/YX׀Bia1,LMd5jᙘh2 @mptP2e.Bxz_u*Ѹp;i.*)ssVR)JR)JR랉(}5&f2LS2C fS_|f%˜Me _ZBQ$&"ff"",$1 JlBz ֶ \旆i iNz-~1yW COL! D륄XM+'Nt ޤ ڋ(جU4Ja!Q.ZE8!9nJRMmtnƖ&V+fE)t攥)J\^Q=Б/K eyGRTJ]3Ⲕe)YJRЖ~4kJR)JR)x?ZV& ѝxBi&R nɕDx)qJ]Pо_ώ-2}{1k SN c#yew1iyef1^XJ ՞н.' kz74bCns^}(B)Z _,' G5&+hϵ 2Gc1ݽ~ uYN=}}֗_y>ٸ>ݞ,5>lp>X1,zK?D=O:/?J򘃟Z{WC.Oܽڏ~u;3doA}~_j=Pߗ_d??U߹_鯿kX>'0Z /+?p:w{.`-ŃAcE7aFQpGװDMDZ =Ja=z*ѽpIG߹2CZdfZ$Gt"CPfĒ-:2:fh8Po".ki+mmmVeC˶wd ՛2vs``􈖣V8P]l@ jLDqbWYQmDIgn a6RRѺ&tD =Y!}.}i_}t_$p0FPE ,YҢWmzvߑkj՛]ɱYѪubX N^Ѹ~C _-)/Zx0b jKИX AϢF|~>cFUEI,^>Z&~7) mti,/_0eh"]H} rRDBE4Pmdcwc3P8ʻ*Um!9J"rh 9{v2 mAU]İd/"Ɩd׍xL-3-8PqL%OfN$+( b@ 0jbTt{ [p]ųSDT}5F?LKX+6??a$MQw}|n*pM5V$N. bߡd`5#*}?SjؕtD}%IoFW^h){M%W^AEf Dh#ElB@4od^[\Ixx,vG.av27exg,vFlgh yf#a x87 eq>pv%p^ nj67^:K\LP.eX;*v".($fЁhjρen1NӍmՐdM-Neі ' @0I:(o= %QWu "e@4qCYxv|OMPHqYHAtY2p`H)!Y~͕DŽ0џ ./X,)[8eڵ$d@͎o,&&O}eBC!Tb1ofy 42Mp:9 do[IN_<@_tF~ge3#i80⤠<8Lw<g&<<*O29p@ulɉ᷻e^7a-[aq d,0oLE 9 ,,[6N1?!/ &?vvP5$MľezQ|ߦ~Px&a` Đ4, vZfbXD6A@h  2 #0Œ4q;ėSqH;qʲ++(YY\mؿapùTavӭ tpMno 8t@ 8[wI;φzGX[Lu- g1rabx\bmOMx!CCŸCYà^x8,YEIck߈-lz[N)gX1i !T :6 B,t9 2ѓCX>@CpIa ww387c qOIFI-O]G B{ 鱱 yxY9,ˌ!A:Ʋ3+'..,-̼eƒ )1t 2]y?ܫ~(v/."[csubNbpq-l&{hoF1ޜbČl) 90gbhθHt`DH\o{,n7MM IѤ=l.exX~,™mvh N4bN1sPvz^3| {N7-3-qw@/%c!>g]jJ2zU?V<̎YP=/d, Bͱ6$$el:Gx-;Jv~M nƤ+Μeo\ SυׁJͶ`I62`"A P̈́ "5WWe&²E "kX\)<1GI&C,9s!]t! 1 1HbNI8ZKU@C>ۼan6^aN7{-$0V>N Դ*C՚Ӥ _d'qa2C{COr&k+$$ퟙFMݘ:vC4wN7웸$2pʉ/ l<<0*iDu ᇌ'UU^YdiY~ 0qɳ;0Vk#!!'^Ѐ maRԄq 賀Frxc 8ݎ xc!F8 ] wd^,@xN}qq,au88L`@6R"+-zHtHB7Nhvxf^Kpe1L*ȴt0c7c,1Žörl2 Ύ6ᖛ0ͭ-Ȑʌn2m c}ς[+,<6˭9%E-t"pcdY! X:a 0r609?:' { 5]z(cnHF.RYu4,Mь)s "2Oz4pmNgd6?ŝ? }vzl[`IHb93&5m2(J/*oC/ 7 M_Sϻn+Ype<#٩[: `fxI8N28`]Z0Vacy" _Id}O?j gN^ =7aHݓ1[r`%b~'tm:6]|omgOd"O {etGi-ex0 xǂ-q &txHe3Im a?`(}2U-Y;$ #dAt`N7ǾԤIu8eId1{(`q`M858G,"68YgL6[/[<Zm^vDL}` dH.`ёmӸoG9/)332I>~m#xwk%rper&D.٠<7YKlK?~kLֆ|I$؀tX; =1-QL9Ccq$ 5Vx|=>'tL q 80togIJldI"a)v,Ft8XK'Y^-$uM<o `[$gp :p3a>ͦ=?P,BtA%wn/I84F{r>id%Lfnud8k$G æ 2/DDΞwD,45y26'o;>_a?a `I%|㸜l[ +8y8 > &,  ',0cm .o9z}BPKWaChͥ -Jl0n[/HL[eqٌeatgyxfYM Eb̍-̖6@gip "wl9e[:<%kRB߮t #ɠU/)WT}[ |}\WB=/?pn<$=IcV70"Bx, :2`ǀkžQ<4̈́CmŵēT8' _Yl.r++2j`Ȝ8K[ ^PS7bC׀.#ғ0X 0 vDE|q>= Y?}PxqǸ![ !ّ!23dd,Xe1inr9l2vJxF^8 9v)d- Ɯ͖ٗftNu˫g'-[a]uw g|5rpdmNHOe#}ݑ/q[Ǣ0'+ƕ8g޼}AʀX=!T?07oѨͳ(1)Iq8.w(k*)xQǩ5cɅ2Yvtˤ4LNlHm<£ʒKYu#1[h8cXS ># ,W?{"Ą*k""KqOˬ. s^7r`dts 8.)`f: 1YhÄG_덲-edju)jZo$-k.B<JXNK` )ΎVo$E<4!HsDJ{~$ă^ PJ=>>o'_**Ȋ<D,U#wHqodD#؍&WCзvϻ->e -~ lL o6}Lͼk6#i d ~+/ ,žYXvlp$>'i'vGlY#:Gwf0͉1 ds,,aBwhzK+tIpb({W EZA\PX, (`\ bt0ݿq?!P.L,|4dGXp$H# X[$7Î/!۟, 凂C~Zf.IAi9ZGoޒXSmCw$Dsm[a%7NձٞvRfSm [^/v#1-0x:plD@ &e-xlJ#2xuU3\AaaF;l c&@W[/V0}oQZq~WU۬xߏROo3֑[D=g< c}:xfq;IZv0^ 9La8`IݻK]amYXavKIeF[\់ !hR0 rqlAukoz1 &X ?l-pI6{K3Pk@HFz;2Z'okm3%~a= _3[X%[ag2n8` nb@ŚA^8<şIm9 0[jd%k;o;Qmqԕ\ !e'966wvwyφZd0.,m0ݲm"vw03:UW=K7Y,8qڀ6hԃH:gyAt_ă%ڭ1bU8 ,8IJ 2 2jX plp4ృX'DǢ2|k6Hq 1ujqnZ7em3ñ$XIRBh3l kli<. [NyeЃ`yyd"I2DxxX Dq.I_yOI*mxal3L,  ,7cspac} ?Ͽw&2đY \<<-[x[nxR՛aO@'aVA!mtxb Hxxٺ2tN2 '3Od$t|=`;lC ,p0>'/DK -xx'exxB%m&6$z'!"]Z 2WBF3!' $|_'$$>[o&]|Kc|#/AK dPEo:pp/z_Է|2]8^r7BCÜ E ԗխxvV-2 Kyn +˜qoEO/XQG;ɲ'Od+&xvE&0B87vԄcb2ξ9y pYAbO6_{J[{xXDq:'# /(-im&ȶm6Ĕ_ l| 286` lxSbLfl7Hm>m3p\j圹˜A8ӃÁ#R38]ශb!8x#6"88X8#mk s_ "H5 S2saeVl? i)mm%YoXbzX[lS~d/-b^đw孬6U?g㇧< 0;9'p!a"7a|* 8Gwi1tr;y̬S]-8n O7b|dJ=A%}Ig,I6+a]| .fvp [pٽ >؆6>#<l$$$0w -a2n-φĶs 8q#dy/;/: vdfzmw93m#p-Ƌ`/~I 63-[.[9rqrΠ!}n0 mhCo턅m6 ј[jM#0'>3p8p#k-aHx83A[ьb]epY@oQdݲC|6 dY XkiaBXH}pzx1H2I-32-''d2{P>Gbp`0 mFZdXm[c8Haݷ 3o 3p87G :?" 1 0C 16|8ؗc7M}{o;ΟN6r7弿 xDs ݴ-8$, iM#W6C-Ƽr6ޙ HY-p|1Y{>8GNY=gDvbՈbCGjDpsA1LXo( O7m.qa{{B#Ի HlKg,b d6CgЗ$}H p8izYf86l.g(y ؄AdfYԐYp<` eevօoo%Xey-C0y>%A1@ֺ$ߎ|wx 95ii <'$c"됻wYg~~ HB:-gNڱdldpfZ|R8ɳI]fmt x6Զy%= .h)ewT˜vgYNEbIX)˜XgRDo D^߼88Sb.i]#x܅.N HOdŎ Lc, 72ܼp=' 6 <b=Ʈ0[ 0[ pq:MUXcX,;?j@(6z)zI0Z#Ғ}z2pHlD&YbOƶKg|6#$cGɳpęze%Y~>" mmsL0;! 0C,a"F߆ y#HoO=7uEY Lz RXJ}Z2 `ggN0ȱmjQ@ܙN5}D mgoHl>@__PD[]IeN.u*ܐj,ZBHb`idt " za^|)yzƩ>7մ06cƉ* YGrI6?LDqH0/@}CLYʳ3/H=#A|+/ȿ""ẼDlpzXB } 69\M7<¢f;Ƒ @Fp@/0XGpFh%`ɽ֝b$N&&<xX9 DBt `~?oxko;umDYd ` 17leRCȖ#UY,29fȜ&2 +l{8vߎ1Ye 3#m,߆ qyv60YcdL &a&lLl mzVoC@K%Oyx7 r6o9ߘ`l $-<3#wwkf#c*TIA x$6ۥ~jͶ"b͏/U党]pq<3.qG9Ty|Vlml!6m7wJ®rq.#Ft{TP|#FpB"G1mmammx ---ḶBBq8Zi %m{%St=8gZ2eOQ/AŏDs,~`^fPWjU2⟶(1D2؆z,5v8mw2; 8@aPr8$Y"9 .[ E<,cti ymxxm>%oh `Yb au}}({sY2KY<9)0DĎV,O\HZH `O ,<Ɵߑ̐h\O{ӍD `D[A3̠}QH#a!B* pom8؅ww!-mme^ H1<gmbq@BZ(SڡKzgM Ru, Sa#bU!"J:%qXn,d|i|K~g_8S ~9A]="km|2{B9.0m!o԰ݧµ)~D ſ9Y=tXNj@Mp'_kv[~Opf-Z{HNd'.t䐓 NT"lp[O K'=r N3smm>.qq,,'v ,,@[ $d`!Ȅ:f0#ͦ$7YİG|7>s {jZ, #:|hNmd>~Dtr? tmlFqi`eIf8,I0\O};t 9l>_H7Moo/=L;v<:o.O,u$ELF1v'f}A$-]sDl5!<(U/j>-f=v є<a_S">Ywkk c/mJ#UuXq2JL1N Y@nkj]v۷jݹv۵ik۷n8vڗng〼•j{p\[vP\9pkn"˻ Ж.HzNMLv݆vIhrsmo 'BڊGVi L/?`j_Of-<-dc$&%]G+Jz^` ) [rL$y!gP`Ag!`lr,l Ae5~ sF,VxU/9-S`]p@eH?|Zj+VZի\o.%~[ߖԟWO cc9/| >Ë/_!B_~B ƟH /ĿK#ߏ~ }Iտ>ߋVKǿ{#7_Ft߃~ zߋ toHCD ĸc#4c2!C \p2$vfY8տ-~{VRWtGO< ]^txr~Ml^R 9`/.Li$_p֏QgbGڽt%3#3d`$Ôl'wKnor>;-C Lq!z@:p%_Z4!! HnC7[(pXKd p4=CK:P3OޞRAQmQ`Ja.wqǥd,ں@}J~"H /do㦪OJ(?™ҽ: xrc^B!$ę1{acc#ccc@q?- )CccmZjFCR gBemzRL7u6_Pym}dOWVw^лPŷݗ }MMF _LL{[>V^Q v-G4$C6=j!|cs'xf0IqNDğGJoǿ7༶Cնխeaa bi&l>s5|+L.hծ'-=Oh-HDΖ{t̔9ϳl @V5wëWץdbc"UP Aw[?b d gr:`F=(9\mcE::xDA; BS DUB&'`K|e;!z+.5?&BN,[c%[hNR`JiHP(BrOJXHB0CtWX?im> )tPZ-y}{gF#IkDGt̓m%~H1'm/JT#xDt"%5`OC e} 3a[yTmpS !y~,&, l/AɌdB0!YB{~%\5D Гႄ0nYbY w肍r'n5"P,6f.6=P>4EBIԹCFfiGn곚 lz-k"+DBy!@80 M.\j1 ۂq@K0ЃC>A -oV?ao/mꍠY$POXΉO$hڰ{K'U,,NQ4-G?Id'lwN#b3FDaΨ,7\#8 ɚd G2r"B$o@w';6 vf6ce&͝0tUveY)5ACz[SG-$6 L }/L"8[`6*CHk쯠hDq«- ; Hq [\tA6q@ϼYDl sdbcFrRה _(>At!j훩/Ѵw1a*@Pd_9݆" T8-u0XA!2۴dHHPɈDc[6n[k #e Pthk Ufd$ 0KHj4B ,%@hQUƈ;4?ڻ_&3ߞ6~;G6yrb#j=x7bϬ{ID}pʐ50;f&+v#06!>[SM;y_EfB.F^v(?i$H~Y}(:0iDCI/]-BK}S~{ovm~;c0>!0i?Ea,Mf(|l8)o8Oah .{c5>pd }σY [W Y ) $NtmF@LQL m,Rnny6ΐ%f1  LePj4տO|<dm,6 ЁtxT\IKl`8LK^0~ʆ3+33>rbAHHmx~>Kw" * iX& P-6KlUnׂ m[mLQ6>Xe 0AE"n1)IFmFx`t ٴ>QIſ >~ /=D6˘/Y]$ !Oj&~ =|aG#j^}Q˿g!t obox hf,a-bRDNmpaz P{L`lf ;M2 @:=2B*ձA1N V& `P1맇2 RcM~\:zOi=ǹfi#C> Cy2 Z>D` z(ˡ^;!T?iІQМov8)7aںJŽrOXʑ @=0$=FJ vvM1 "8miu* 1\qiJ dq妡S@CRA,bA`V39b5 u `DJ^^u_Nߟ';6 Jk[HvPn3+iof;I$j=dB>0}`_~"r wܳ?H./M0ړ;8r1p3zُ)d:YÍ-3/;i|qU19"8T?FH( PxG 6HƤyz؝/OhdWP>]:ggn4 N` ]VqlCɢKMd_ZQD'c҉. K)QTЃ Sqaƈ#(*RNZZ%(e;)˨Z  3"#V/vP11ILN v_ H1gѯА1h{oruLH|ȼC=tJOWvА`&yfx}0bhAEDzlaL`!k7[\N[ HXlb$@X 2{30YZ8@ $py%gBq[.& O2.$ 'J"NAr̈`h~5V3eVȵKjoG? p?Ř,#66!`@]OI(WzAzmI'ħm7mGQ~'A3]i| 'Il?+<Go"g N)7S"UTWdaί_!g=^߼K@k"uTC3=Ď+ @'e}$6$vPK #[3#n{u `fSsu @It$fH$랆&63j1LVK{a jڠmԂeհg$}m*.P@ %3K7F +Y&$FzŸv=}T)6on`8$@o{rǥ vP[֗O=#OXV LQ#,*)Bi"v1* bqc*4 {hZ3bekˣ<6x~ ~~ݰ}/O w%OՏ s0}=Rz}7 FPyrl~>b~$ICUlq O ,X饍'"i!䱳Y(,9#flo鼰:j}?kga,"+cά~wHQekkc[*),~ֿI PvMąCX[Gwp0jkw_s]6lc|{CjgFoUck3F"RJ*;my*Isc7A1pA\I+u,0u)':`u)}M0o|wȕ}Nb: ZT0̀}*/ }..0{ZBAԇ0>LeIӦ#hHRHn%:6cSDo$h#uzKg-o#lj~bP`:ڲcGxQk~BkmbhҖL$ȴ:qx?XUpՐ b#{^f7@\`͏_dma ;Up j /ن`}|0Of}d?/nH(}E,OhN2i reY02LF ,Xbck'NnN *[mR-ƖCH<0_t:vobKTf]BLI#/Lcc~ nXHZ&I1! z^#qEtDe:MVЍ ͯ]ıv H0G~h)PǫOx'J ]es?F$Z6 28-9.:XtC-a//X@7BDd6X[&<EWʮ7h GD8K0)=$2sbo7v7)d\h`D8n.bg(OMx"m80B&m5>,V2^xL CqE* # {z5"60@R.2"FuTX8uIuu'A1":⤀O2>[5[y߂r7=| Bzry?OZZQ͞SɘuO&T_DZaa$an>>I/REa pW  u 8ۦ]dc"uuD a(Yq8H\gΏ$ AÐNն0UX(>Kej糀o. ʼnW55載׃*bwF~f@A p: %_M)U}'pv/ ht U_SFG<#(W1\},N3V xFaIh>_ hk X.8ƽmO6%I ePTūԻ J@iHѦZ``YPXL^:vo;oo o+oeX%>NwK=~=l~G@ŀA$>2b rb2$xiFCKeh\nAu"&N/hFYemM m>[~ z+<Vz7]).m  ~,# -]'tMo'KWOdB}?%(E@?Ӝ~Kyk"¢X8ذ&28HR&idc5 &3 f1  ƚ  c!\[Z^uljf7ۢRVa61=zXz'v}PȈ=pu'?t{#Ҁ0% n<0eİ5jtQja/cꥼr؆ ][#cOi:yB1@װb쓷KD_ϰl(ٲH-+ `j6-"T:G8e*H`(vYda7`)^aiӰ0-lq ]$1@SGB x4mx׎-~>g1m^wȅe}o}ZH  <i{x(bSD1wkcvPaHj=_the$_~߶3 a8RtxdR Qb C7gC?b>ѽD@YD=h>%m(2à:l0(SJ O#a=~ ehI1 )huTCC lw &tIgT-j6z-8mBşJ:LI_h?/?dfR'$avh8pz(9e2רGq)hVvHkG$dagPKS 8 Ж8mrՐ N6.Xjs2ZQU_aXm#`gaAݿw Ǚm7-M#;(G?/[pekw@p9k&$t[7FnuC!a)l69{P$ Q+%B@G{aJUlV0$s]G@'Qդ5 ]nCH @H!C`Sڲ1"->XzuOtɗxm4Nx1w-I~qXvWB؝ D}2$E:KU?1bt?0]?-T@ &:esAVUX4P!LĞ!1` p=۸;vvȂH' -5 II{'Ʊ8  ^H5KDjsM%R 8hh@)hmoHٺ*3z\*Aa BgQvkpu~ *ʎl? c3b*> od}!Upx1`s'GBYu c $D бϤ< A@G:Xk'z\g0MvIw$?4 Ͳ e1Pe]5:9gzj1ndM&yMIwGJ?%%[ _z%e9cJ'п2(3`.H1𱇢`# 10 &x%Hf]ǰGv:6 Pf " >ϕT~<+xG@5C/~rw1 qeFzMaM}Y -›џܪiU}ׅ&(:O`aM樑chk^ 6b 18t/I;ߒF`m3`^Տ8K`k,(tˬHc5C` p?I 4-"HN@|?؉@#@^]p,8N*&[$z*4 (#kÖ@$ uіñq{c3⫼>j,i7y(}d, c!)Fq] % J>˽j(@&%۳JSx `-omm5̡>os}KSk"I~6(R23~ƚz:D,'脥Ҡ)!IN$xNB3_dd:EطMyHH"a)$x,,HA,͜AG s+dK$.5( eA9wt;$dH tȗ/ȝwHxK$}C&^7[,x&ӈ$6HôA"کUZA@+61 3mU-@ɠ/.zPHz@rj#~fYXi)@[V(N(2jAwhg 4aѰR0- ആbT8$x׍㰖ͼ+ˬRf,{>C gЧ`?-Τ`b]YKY4H @Nф;/K'J{Y`(8OU @j+9]rA#2cP:E뤿ju:OڱV~5~DQ RRM͝{w%Ѩ(DVG# 4`Hw#BD [! Y !&$胸8RKFKq!N *ez>B7_L-͆[v(xSbdGơ\]ԹCB04]U*(pMյN$j+CxBf"6&A#=?l]n*8lL=jm] %ªH=;ԣs9c&wpCq5~ҩoFa`(F((H##z Q"h]J  &kR`߂lڅݲ8㚅m T> , WcLrLZd#r% */qRqS71?EeIcHĕAl`'&4n`O@./hH!2[c=?Ԛ a8kOաuH ~I'X_~dŸ /C4o˽ѭ~b&t(Ϳ1f϶@jj <? (mEqaUHY&`'1bp2  : `  b6L>?gbTDDp w bF;I11DO_Cv?^L~CBItBk?%h2+N#2FJpAD1tlW o̰mF4[ɴL$q-J[u mʿtcױ%i cӝ6xo~/Y l--V-f,"U' l4>*t`qR9 tT -H~ %$Ga o$ ?D8 Bv"0X3GpƒkI b3 #8m6ۅo:#kzbb5BSE9xK oSƘ߾%KX>!l]‰Mŭ>\[2*&PF3vOk: uMx0zϽ-x)<d)x(4h)_Kbw ,BhqDIl">kv 4cu}I Yt06 HI1ŏ'c`؄d/Y7ѓ -/f)X":il],:. 46;ĎĒV9RAB 2֘+3Ƽpcw8l2#aNOvI Sm˳Ѹ-Bp=nj?%S''m=ڠE,p}GD5HѱA5n͆DvO)'@eEJOIw')-R/Rւ=mRƏΖLÍ1_肬Պ#ab;.]A&)4hd,:yPat+,!Id#) ly仿R05#$LsA8~l]#Y:#I׏&2Z5g3R@;3-ڥ7H6&|r-Fue`-gW1sz4*#$gd@ 2BF2 aj AH:7qęG_%wx[mxߎNK+/g)013KK|Az?S>NdWR^(b k/)埔CRn^Z? 66Ѽ]d˦ DEƮ1b>kRl{  ٟŲŰVx8Ȓ\4$le JQz)O'G3LЊ$ m+N`UBFÉgBT԰cjc;A$İ ك?ַHDFx66)&JxLiWrsUQPgR"XȳpY?$K#g h$5]Q0`= 1"{ ,a{CqMpmw4^KaXxmi\U럙Qg3_QgP'0'g%Jj!Oʫ7*ADFH!HzН1I 负*BGV,,H ԰@أ_ ;$^ za܌&cIZ&&B 1)޶@!}B6dr3})WBcd+d7Q1 IQMV]i$^#`#GVIo5 Wj.2m{/Fp USĮ1KP}.;LɶUiaCs?HߏcAbb m8$Ǧ'E,+a $Dc LF20)hXKf+1$Å^Xggr6mFit`"H\ART$4Yf''EԣwRr+( U5rF'1H2ńR"q#i"æAgu߅ܿ3.N5e @/={wO3ݳBrN*SՉR+Q &>~f]VӇ Ӫr_+ r} 0`D_7X+:y` $DgЇR>賷ZWI+յS}R+>[NzNDpi%'2<$t&` d$Uޗ]K@N3Jr><"f}j~X_[ZDc,XaQ"^D5X &J]m%:_:LL N2͂<osιMy6cbpR@d)=H=3>7S;=X:`l+a!LF>Hi~йO $8K@ѓ [twԐe&@O ="@tP%y{hcv5aXVa!  0]d;'Nhw!PT' Cd4I;WbN_mJ1>ߣd#}/K)_S?ϛ[ܬ+/_@G?W' my 'l#@TlZO$߿}&YA1b[X^.ZƲT ,6 6bF:MtB*cc݅ W=? NsmVW`:H{<$Ά ~'v :EsBDh,%އhH:'T܀F7\tLtDѤ?wdn+0I4\'*lp@,]$tADWG+ 4s<<1?˨@s vO(.4mdv, rGt%1%0qXp/&F u9NȴgBJ0"EGN0+YFh"jnMGO'Üg_,qp7 @tbm0=(U%dqd}H 8p>db4;E'~6C cޤ!f4tN؏KYj:$Pp # ,"y{ I,ev O:!es0 1a&L'慨0׃%5O zGn[7&)cMذ:Gz^VF{CACI OƤ,%@HS܆,zKQ.4AS _AɃ] 'I0BwQX(@8pc`B `jZ `9c>ź"}#Au^N3weOI%WB= 0qNh% aظb0 ov dE#v1¤ .𼭿y8$m I{B<#?~-2Q>H^m ߆{PGf%T 薝%{}TJ5#f!IFF`b09!? QF1.!0Ӓx ldd-"= ϙu'1~Ĝ6)Džh/, [=: Y e 2xσg/wlt&K0 S ` P|P!+F~SV籍O)~ĔBZ 9?l`&"t(4"(Ɩ =j'LMpS X{ *h3q$Lv-vcĎUe!Qqо$"Bl$DGxУ"GdcV"rwӃ=oYN [s(MFRatڝɧ`g.IeyA"9;9-G}.MHRv>u'kh;ԀG&sE@i N͞ æI.CrMx$!qpdOV$=.ɖcPIz%!KɅ~!g).' T~M$ V`Z,eWfdg #B4~KId|L;[ ٢94%dTQ:M4̃(H DǢsÚ4hc "6\ A)8[R'"!,he,dtDCb#:sVΌKo+muBL7:3!l-Zg )mʃ5KloCV3r zV ߡ?$x ԂLm|P)!=>b 0C_=&'R$Ma]s"&&N/vXXճ9Z -#n@JICߢa+і ,?)v!~MjjMzOH}Rߥ]:jDi bQu/T)U`6kJؚ#; "v=l)jռnI--IBcUm7Gqg_BG쳫(s DEnD Txb5MYq^MzáRF&B2EYXz͓2g[MwYO;ww+'x90 H8/ھUZ@v شXD衇BXLk_x~}e+ҳ&7ݿ"oȟc6{W>k_rh9*|:?C6,=ezpA( CPhX%eUB=`Hnؑ@k09qP]-96_v+,/ q I [$l,`̂BlՂ@DIAa5 qq`GvzAvߖ "Zil!5$pjʶͱ= 6(b<"lH=ӀGD@pIWDHM7Vl>E 'q "Ɗjk&wo1! gS]+fgs@z] n0BQ0U=O03אB ->sKo{")n/RZE ! ^="jZ PZ4m]I#\eDy˖E1eF\rŖ6, lBNkp/imC#- a,[ 59قv.(.3K0 edR3XL9 UK6n0pq7Twi BK3^c=%#@`БÌJ!q SI$cYg(MՂ,LU/,CbyVwHߙڛn*bN&VдѰ0$4&D0KYmB!DePMɁմxa8Q$!&'bY`ea&K-Lx6 7iyL,ɾK£-5H d5O 3 #cTA8*, B)# 7WnĄLd='Pqvp] qH70"&N0Y7RC8Fܝv$ 8@LlFQLd#4b{OV`Xȍ! %:SKQH6 Ԅw $ 1$ڃ4D`Fi!_eD0 ֨aF:(HA `:BM wIlJKg$ʎq"ޭ_'L f b2W^#A\3SRP{2"pS{m#2I2<c!Id("@"H |6IVbHX٨QI\!T Y"FHI6:)&tHE]pnLl)h½2dj Q 9>c`#7B $LcW1bd,ЄlKG$HHxK 1a&3GpmWD')N=Dt}"IЖS5](WbH,K403C@-$0d+6!c'Mt݃,RX,z-O@İ0F:0-Kn{8> +jd۲Xw-8e%[YI,H_zX/J6;D@)p9! b- Kp$¼#g #06q$rFmL 8^:80Uv;. >X0nbXHO^i240z!pf}k3OB.mm;DPE,` XTM:6b[8L ` 81gF$4s22HF`7Cj#I@66Ňٰe|jC_¿4ǁQ^?wԈG_o1|{ORy_?]8/oW+Z?{gؿ?w;Vfɴpb WH  "* ȝy c%-Hj&RX& S6GE(hp"6&nEhLM[* `R!6)'],ZBȀFH]]߾>j.=T Oc~L{W _P yCh0tGm!lS'҄_IYD#Ҝ 0QP:GhP6YIԗ0ij6 Hl2}X$i ɉd L@(X ȍ%Ä́Y6x&q đ Fa$ N7[l-kͶnam׍Kma WC *5۳EF1;YYnЇӶ@ 8 Dѐ ""nU1Ⱦz#`ѐXTLUҸ(#Ʉ1Gr{OB6DBt6ܾ,$իVed+,dxf-#d5Uk1N1D_ J|bE6'5:% RN1k4*e! !WXļ-D!6HID&F(4X.;"gP`]d萃(b(^Yft̳%j6UC&^\k!Ium #7>GPXi0@Y*-B + @ 8RE%2(ŀeN ] Z!8$2} ! 7C% i%{ :ĀN bB6;Xv b hV3 4sEM,gF-in6LlFΏs}jwQUr:eQ}=Q "iIPgQ*H<)#Oc,AD*;-1,KqCx$ bu: N2tuLNAć X̺q[ZbqYRj`K4XT64ZKN,@eFX*ez.\v3!{y-^ua>[dL)H@D-FLQaAhА'Dq* Ќ)g1'K1dꍘRc"#VZH4R#AwD $ P J0)`('@aI eZW=̄6ض H3 DaC|@7d=Ilv;Hu@Bi#*;[} Ebn>W)!@hz00H مQFAa `U?XQά" "7kRTayXI & 9'Y5Y1$CmFImD[,(#H ڱZ; dDCI1\o8Id Ȝc!u3S$%2 !'B@g©p؎R+# /D`"2Fn֖^ZL[qD!0쀎6ILbB ĄbBëjh eM @b̧cޙ01 MeՃ3]v½ԃ4ea(+h`cx0GV-SR~H^P&N%st6ev}@d +bI˲M@ULnc;7Y#l1R2fHbXFp*a&1!ldlLø,dL`X/ 87pD)k?C~Fpq, hBI40N腰*3DjI!;A XH* tA@ڸ M.ƌ(.Pi4#h8dFփ"2ul H$ `~B4v6[Dv&n &ǀ 5gᗼ&k'^Nv0 50UTaGFX~@ww8c܏8F Hc7? S|zbiХ,GM+?ۊd 8$!D E гfJXT 1Uwلxla *cj0k:H\F(-2DzAlmC/WKohE m퐽D|xK5 {sp@}=0!BO!e/r*~ h P萮Ä"у#ZR10;A!GYFZȤ"#"6hTat:EȮ&2:jb0V9|Qd ׀ b{%a8P-zY;a' HS u7+BR[|0NVV# G!tM6j~L`t$~I ǽ!ID4e=G[t]F`ٓ W%Ta3mB HȢİ7$G@kXW 6- ۋ]BN,Ƹ bGfĝhXH\3hX?í3ip"rE>@HL QEąeL/F`ًXQ]e7M<f Q1l11FD2r> XH 8gc${H3 YX.`EP#4N !`_iaA H8tK)΀v@􄫬]c@zP,4e&πXAܺdK6k-,YWӣ F ]$ Bw:yTn.+A$\!,ƹ.Xf;!Ta"H [`0c0AEN q20q a bB$ amg>nx{o0ρz8->NUEj,8@ N‚ȇK1 9*6 1Ld,Ά:cvdXlk,->a,FiEXKZdCq#`!13V^fb #HQgIЩbnMnJFo4dtGgr:@DEN޺~z& _p4Z _H+7$kFG?k` OP+R`Ë'Np Fh,l lQ@0̑"3:e1(ɬcbDM,$8ݚ5c"@h! 1(Q%׌,8>6gp]ǥ=Y1>HT,Qd,KB2h)nXŜ] M AY RgFi #&"T"YY0dv M#dtp`tFYc" H|24K{Y&%+`<]b4JO^ہ*Vgb i‘+ x+ZYEI٤5%e5 OL} t# L1=CcBqn)5$Bcdra!xS,0ul,dWI"T!wBl:A" bTe،TBҷ' 0? rCmc8+Y9#$&0lTeC9#’1I LQXH$ atJbD4f J zXQ,BJ,D`Z3tΉGV`:),YHKcBu"iEX*@N4& [g@XHdrhUQ|ɳa 0aKC[Hbk0@Ēhӄk 8l2"wYpS:cb} 2k!i('nc6X"td1|HR]΍;2ķ f0&{ c3)sq؜xɃ&|!n;tضEH!k霕K!]jA!C1MJZK#R$DnbH'@3wInHTb"YZI*$A-Q*,ʸX2T=ۢ( vz,XۈdH'Yñ wY4tl tX@c$@HlcvDhCB3/qO`]F:fO k61Wwđ #&.QKlM`Q{$m.UdVU1%D6UDTZURM$t[Hv"# 3õ=Lp[C<φ<Ay{$f,8 a& AXBFCDp$0b4Аf$ Ѱo- (KI4z.N tv @JU!0I( )3 Ű'J?"fZ@i DaDzG<"ހ$%XXf`Ǟ2ՆаXhhtl%nbY$# o?/ےű 'I֨? Zǀ(Q%` C$Ua1 `$[,p#cPE&$ +6e7P"\# Q,f, !S 0c*0V6IU3v<7Dgg955;u03RI8Yc8$ RZq,$)cXol$M%$-AVVf0:00vAzKX Ą$].ݶ dhUN;z[A!$1"S 8 )]hu@[ DE @$QgX"i:~V9H0RDA8N H3X?}#&I}u_+B`É k!:ت1VL%H$;N1!œ]Y(4a&HLfvV«]E#aApmvɢ˨ -@o 8/Q~Y0ȟ >2wy.0I:n6 3C HI1]d &`9H@Q,$H; f,pRAK*I0:Z6 qatn0\V2 ʓgBT@0;v=EǦX*u$1tM`ȄOTuc( h?+8mC''bϩXHv픰&jH:4% }'1G.^k;X8&3ň b%BOr4ZuA:,UP-Z̓ %EvNu+[ʰh 0х"Fka , yHXF?6ǾH9pyp8pXeMXYf K N! DXե,3LHeYHgK\d;a ,*A u$tY(k F͆` B %g/'#vIlK g/#3p!xW;AX` ##D$tH:"¨H(K`4^nDq% iKY(8Fҋ#B -BZctVIB KB1;aЉ=A厰R<$emٖCa΄Y²1a3$i 3!ˠ|c R `;D` ?F tYgKc;x O6*$!Qp *bY#a2N%2ՌHѶ 6Z:$F+;`T*cPх  1 !aA! [H-qHd@zk`$,Ϟ20a]fˡc>;%B3H:!-DNaGbth1b6v  "HP4mHZaL0 )gIGc $v( #",0pk` #$`&3&v`H` ݁2`DHF f4 lb"i (!Xɂ "Ć"Ӫ̆rПxa |  0x30:i'"Ojv}]Q[A' ,d(ȮHJCbBzhV*!Rk >YVl,kaFuCɅ XR -W@0 a㧝r5p߀d`A1x,rsGIAb-!-+ΎbApBF.dA,Mwq1 E(/9H0tJ2($H!7 m,ۣ%VCH%c8SX2!:x`QB`Wщ`b#0qYh2Qb{`\1 .CA4D:H%{K}48MF6AưVY'J] 2k LI>a X.Ln2Mkf] 1!cZarL!Y!Pv$؝DMR1Օم 0A/LB!c3Ymwdq8p%դqn v<dz< IAY۱07bHլ!r4:OL! IЄZ& HTKB B,T$@VYGEa퀂;mA6"wl4"04 @'lZNT,p8D!3c "jZ& 8+՝:YRz*[N4)k0IQ ǥ02iiAblaet0g+/  :ȍuO谁aN@630zr"h#.$alF,"@6 &0RH^2FP (dVi72:6ѐcDV@8PGDb&t8)55  1ug18H!@]`wTCYMMKS4d@CdrAp\L2fWDCKv`pڅ^ }4%!L#2'NI,{0B<!@0*qދF(Hk50YQ`M(@$.ac!f /|żst>u$퐤$ 0ɩu- #رx XrD f-Bu5:FqvZ[)hF K Ay,d ltb1"1d:IOi D`` l d#bmͅ('+.;-)"!bBgV&!`D#ߌm&ǐJ+~Ќj26{a'UdURPK7[$]i[X@A'HlY0p l-F)$=vq#I5 EaŌ4PmCl2YHY4 s?b-/Sppx8SCp:O LY$ `GL?"QHtHؐ$hH@F.Nv$E DF-VPSY,[Ab4 F,:2j΍,`G\T5!P"!V9kMCgyRGswpa 83U:Mg6UyCa1 -th[¤:B'I.ΐ6edpd &͂L 6 $ ARX!ؑc 1.'M%%@3ؕ=VjPqmFKj0kj(1a,.ڊAM 13,)ƈR;BMQ WI-DŽ9Ӓ='5qmf;g%aglHR(L:`@M%@&3d` &l ""ZY7diV `4hYAbA- #-pnhm*2F؏Nf()K1'kfXte y)v&Hn 6Yd2CFhDAHv55u/Q]O01=Be5OWBcŎ,B l%! mB ÆtCE e5',Y A6;pua(XņO p;Ǽ88x.]ÔFXu:xG`1c%Ўe]2^vXuV‰8"; [)H$pvߴ0;kмG2> b)+c1ԊyNF0!'+a4*SAEHw.%ݑDI5 ϗmc ap!=Y /fwupd-1.9.16/contrib/qubes/doc/img/uefi_success.jpg000066400000000000000000003215211460375044200222300ustar00rootroot00000000000000 ICC_PROFILE mntrRGB XYZ $acsp-)=ޯUxBʃ9 descDybXYZbTRC dmdd gXYZ hgTRC lumi |meas $bkpt rXYZ rTRC tech vued wtpt pcprt 7chad ,descsRGB IEC61966-2-1 black scaledXYZ $curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmdesc.IEC 61966-2-1 Default RGB Colour Space - sRGBXYZ bXYZ PmeasXYZ 3XYZ o8sig CRT desc-Reference Viewing Condition in IEC 61966-2-1XYZ -textCopyright International Color Consortium, 2009sf32 D&uJFIFC    ++&.%#%.&D5//5DNB>BN_UU_wqwC    ++&.%#%.&D5//5DNB>BN_UU_wqw" Q@Q XXX,XTQ,XR @%@PE@,\(XTb)J 4feE%X*e&R Q,QJ\%EU5ԤK@XPXQ E%R ))(JBPPQ % BPJJeDXY)`*)(J@(!H,"XRXX!eJ!BXRb`XX[%XKe,VRLj[H-X&UsTXDK%XTE(`)K(AbPJX*T"BJ`,Q aeJ`@XPJ"he@ i&*Zhfb&Z&.hfыo!,b)J@JPXQ* (@UP UAHTHRCYUJ(D[%%JhE,,e&flj,hfle&f-Vo\99͓|Y; fGQt;39=CD#`O,}+x>${\sIqGϝϟ{]zvyt\3fmZ2Q* ,PRDXJE`XFEfіleEDX&Y9˚N=~SuY;wON2zgzx{g'x({%{!j{0{ !맒ya懣ϝwg~O}L9ʮ'*8sC8'(sC8Cc 6™\ K9o 9>|||>>O9\<2泡4B%"%-YXPp<=C?C~w?@xQs{\{,맒>XzlgovxcL˜hgQ"20ڰÒlenDPm%r07x+mm9c9\pqSn109t0#PM60nJq9Gq9i9 S.hq)yjmujJ,,Ri>3^;KfiDP&JJɩ`4\&K)|vf֤,,fTAR)Rіa p'=/q!p^aypG ' q9G8iQD!e!DPPTX(E(QQ R ([Y4_ϻ9fmEP"Ձa(EaYG}߄XWv3neZhE%UEX(AR`YT@-P(FPXED))%, Q@%@) % C RlU ώϷ>[=^n .dTYD-"HE nt|x=K 9( DlRPT(-J(Y@(EDQ@PJ`ΡTEHQX,,@QRD`P[l(RJK(![fd\-eXBQ&:Jf> xK>gP A@ Q(%*`ReTRJET Rbń@ `A@ T M @P)AR)D >7c61lJ$PDPAI}}:QlQ)%`B(@HP@PBP-* eRXȀK `("ٵȊ* j e!Jk:'GΝJP "J23cU~1eF` T*YD(% EB„(B(*JH "TYD *XA@bPk4#W4J-#YQ%oz/?3fk5 " P%"%][sG}sѬ6 B,`PBAB, * X A (K,gR$YegBKb%@H%RE e AFV e-}?2w^pآX\\j,**\Xշ55?j}ʈBe"YH* RQ,*Bj HET(@J"e (+ ,@EB* eY@BUb*R*PP sb-|u|Wʝ}wx{], 4 dPKxm[q.~EJ[P,X(HRP @ BTT*D( J)EBQ`IF&E P( (@J QaQ(F((,`_>KABPMA)BEKFuRkߗOIu*P UjAU`,K[lzny7  (@P APT!eY`X TEB % ){c׷>=<7K p~Y=ǛYdQ)YIaD!`%A`T@ŹninQlJ([,WS~ݷUP@QaDEAPTK&P߄~Y\-@ (XJB(J((X-(*Qb(Wмǣz:_?_oG}cz2^t cIbBB@ŁaX@PTi*Ԣj暀MB[,,>_=Ogvx JЖ є 5-7φe6X%*P(H*P-5 `ԲHRT(M%RR[ ]px^K3aUPjAPjr] Ҿ}V5>Y?=fu515#+K YH P @ H," X-ՂYM%* \蠠|O}/G5%e PC|vHP *Mi>RoYرK@J PT(eQR,*JVik_gws|L@Pjչs,qH65_1R,$eJ%J J@PXe-j Ye6΂Ĝ'?2Ǯ1P @[ D(kYO:MgXT (TX%BQKBT( P,T @SEJ-]M///]op2$TPPY`VuvOm_q\fXQ "K(YX X@(()AlF8x~__?nl PE DQ,usMxk>8kBR DT  *PZJ% TX5YK^_7玼m *(* QI?0gYY*Q`"@a%XX,EAARPlJ[uPJT88?gd(Xab蒂)):Mi5k<ల @**B (@X*Z J*  *@ k*i>|ߜ{?͵"P ( +*`''{n$YR @B,V [` fؒжPP x9_eV.lKHP%jBT`"Հ1nS{/X7R P !@!A`XUAe-,TP@]gF 9~"5K5 5;kW}W=y?w[e `X3D*Q(KX,PPPPi)DYM (13~i}Y1tJR ,@3 =C+.EA(XAP@RTJJJ@wo:f[/Lw^{Z>.>?<鞷Nzw{>{Nxs/aw5|W^gy WƎPƳ7|R@K@RJ,X@(X Y`ԔYaAHR*hYRҁe1d_m˝! J%JfX %jXXEA_ߒOYgBa@P P!`QUJX(J to3{_<1o>g}_?=?N>s,]csz^{N':νχ9נ~+)忦c_W?x,PDB,5,X*l*R,-Z4-Ke*R/?9x3 B*PHY.MgPV朞Oy]g@TPRTP(iO濤f C7y^.{?<cCym(wYN?/^s9g|7/λ5|^%]s m^ux}td!l%K  -mK`XYFKeTX-rG/u@X (DFu4 L4\x'肀@,( Z2 * J*Q` \{}7Z<]ηQX (6"㦤ر'-+|}yoD)"T-ARX DX ,[PjP]dhpqKnfjh\Ҡ) [/jY,XTU@Q[T"@JAniG__埪dD隀B@ * sٳ~~^:l3:ab)(%@ @ !`ETE R[(,,,prqr.3YƉIP-B@P%ҵRoy˞/LoxвMTkPTg9ͺ@ * `6=t`Se 7+bKPAnEJT%)i,m[,R,JP|eG8Q,`X,X%(FujTQCzĽ1q "PU(@U@J,@[ T%A@ )ntkX~kҳ"ĔfQ[)M,w{_u1ff"Q * ,@([b-jRb-e( S]\rGγEDD )ͤ!A*M_?!,%AQA UER"@T , PikY8w^I}' 3?뎽eEPKD+kNoa/U6||*DXATiARTEAPjA`TJ,lliTйAH[)p=wj%R RQ :pw Uc7sCQ*€B sAA Q`55Z}w]/\|^oq~L=HqW{8uw/C>ߗc7}'Q州_XEe"Œ*B*  ,% `"-͍YbJ `Wk@J єQ];6kIM=CgV=k:!l * ( (RT(( dYhQ A* %-Mk*޸z/~7غ'[َЫwê>3_z!rA+!!** BE`KݔXYERYE_WG5),,5jӓz-Nc@P !@TJ EPP),`T(,%JjJ]`r^=W/G~dz<~9ZɼY!dAb A`3DXTѐ*lE!niS@xjF)eȲnips,~/=nnq, @P*4:+VG'ItT7 % P(, *RԠ]\"6J0ԈB, ,!  %*  B B %ƍ\5XR (*Ryvwe4!@AAX KFh\ՃMNuT%(J*`JPTPPiAPT!Ql!PX `` *@(lXѤl)|<;>Qg HJ, P 2nrQgU]J,,* A @` eZRPXK` ,, % B*U"(X, b-:-΢%Ƌsd5%4xyov禗8Ԡ "(B6jY?mxͫsMIJ, X( hYKY@* 5( B*UKA,,Z,,Ae X*PEj \u`-4"I/αJeHI \t>ΪIJU)R怔AHTU J ((JAP,-X(X a Q` d]BAEAu } X(,,*".ƎNu:o@RJJ H T J` E@%R @B ` eK%Q%( B[[(j,TJTI -A uZ A`" ,XJ5\Zκ˼*, (,,@JDR PT(!`X a ` `Q)DQJEDX) J l[l[ŕ,T[q}?:yIX Q` ԰K*U %q-t>ϊξA@JP( )(@XT (A` `(E`( PED-IEQ D)E,l,KTPBT,q}?м~|lPJXT`KIDY@=8N.,^k-,*P*`)( R,P ,XPETX@%XX`QJ@Q DQDQ I@,JTnj[@AQFosc5`*PBRX%"6*7Ѯ굞vt) % ATQ@, ETPR@XE(XRDP`JT)%@ lDcz)3{Y, @@6GH5vU(ɩJ%)BP"@PTJ3cu^<[p9u/Ծf_:>n~o]x]/uT|עw9OAڧfך>~'r @Z (TBD BQ)UTR[J[198 @J@PJ PP*R}|gX7V_Ir듡ϥ8crDϹi/&}K;/OWx=?!Ok^o8#qysW߅WϷ{,ϻv>-O9o`k!R@`Y-JTBT>8uԢ,* Ed(W>׫~MB( D@%),ϸoT`*(5HUfAaB !`X%X ( ɘ B@ @ ",  U`` (&!>7fs@X P sLs)v}eH;rgPJUBDU}Uϝ{O:7Gl:yotA/Xx3jUw=?kחm1~MSr~yU܎vEluWϠgK3y'//zBԾF7YꇔϬK筲+/ޯ{xGMxMz=~Y:wz?:kB  €XP rps=v7% ,DQ #ϭA5v(ZX * , X*R$׵btJYO?[Y_gݮv7^]I:zezK>Gb駚z9mГtvwS@;zI՝Î/@}{_I^bT,YDj! !hK܊ sK/,cqMc64 P::)5:AۍR , )HX(*Z%("(f,"JT* `  + @^KD4Y (RX#[ˮTk:gnN^=%A@P (JR@ @eP,ETXQ%YPD(AAPXT!RQHJ r{ %γ) EJPeJ"Jg]9zˬnWx))DT,PTAJR @YJ@T  *JDUIDXIFf%  Y`REA`€! !*I(JIaɼm3α@KDK) r,c[}gg6;sM3K3 61W%1%!xG3'5׀sj8ixG-C``dj⥱UJ@J( BQ+FejTX%%eb@%Q)EJQIbU(U3&ebX(%EWž.]t¹:ǮیXT KB%DP) E@JEKHiar-W6;ӈr1!Fdiiinܐ#L5 *" 91(J-!PTAPT.hǴ~k:iADAcTweю`J 5* ,"@* "H(C- 0Ð^=-q3Yla8ܨr7 la+&/B̳5`h@ T `>n>]~yV%%A@ @ kPTTJ ?@_ߠ랿<_tyJTA@HP `TPP, ,,JPk@͘ԔB B  r98M}[1: * " @K A@L A XX* _7=_Ե~D/ѣg;sDePXe%%`X @ @H}_-_C|:Q~PsQJAPTPTPY@!lHPRW772e A (T( ,R^cwzTx:-gy[Y9iڰ̹5e bX" B -#RCW#L24#LҠҠ* * (KP) . @(TO6gY @P4*RxS), LqWGq!ՆFZVmDPQUAEEHTiFPXHQeZDT(XREɦt,TAEVq_}:~1_}τ}<z>fġb 0i '}뱼YK`` :DP%4UqNPe23^(靇bjI`}gʹ*CIA * sAHP 2C- 2C- 3hC6("("`(, ("- ͌NAnJpa.a>|.M*AV)`{Ozip*P**RPX*qBbwAβZg"wdXrD>'| 9xyh| 'Bc6+/:m`ɥɦiiLTjheZlaflaZIDjTR6UL''O~7WQ^'m YTJ" ̠!@\|r^Q)18}TqM07㋜.quCθͳM)O]u83;G[|ԃP^^ނoy9xs|P>p7żvYCI5̿O]Dv]a98yL?v3N~ģ[>81%SYI{=T:itk_o]rj# K,"(EE9xO՞}:[J-nsvrJejI4}||Ae'¨Ξ:/:" ok q!)1ˑqw \ 5Ƨo;ޛfyxqqh#^sWtqof}_]گ}?:gY98Hwvu7kՄ>v=q~q{H3,:8އ]T_GvE[cItAsu?AN,O\[^=pYD|]9qz.uno7sMY@3Khfx/bTePHRQ`< eΠ,PqNOni%@>H" ,zX=hz?]|\ȟIBuR,lcQ,5.RLAd5X*} PcpƮ6%ANQ|8Ƭ,BEJ* rR]L95fӼG2*RYB @@'6L)%B܍1UoW>N{SJMqey#N9t5pm^î<̀[) wςXvwS]P}?d}$ŦE r{:01nkf> \3>霼C:ɸ>vA@ΰou\5¨d@RUk=wd.JA@gPQYhʗ¥@- 42BM P$Hl㜰=)8\Å+$lg|f#9o '.9 kl)pAsHMg@Y0&Mq;S=BKny Cꎧ\8>w} 98qqX.vqrJΡb\qKŢrcg69NǮx88UzQ`g7!e}u?UJgXs8Gӯg/^;zuqrr3t42HA YbiJK"s}H L4?уAd1qT;ì7GM]=%IQVwi}Q|F킀u-ΰlTd`XMqrgADQqP23h/q9p^a8pA`8ܜfCg 9ϣ{8xONF)iPTcV&UE%@EUIk9=qD dLf[R( ^I rCy''l,v=p:rʼn85(|}Gq(?}pGo{~1BQ_Z>N>C:nG!/i}!N= rq@( k("\l3 R9xr5%M%J3619qjpO|yFѭI ()5)"$(lz,  k<.b 'j1EPpÝ>S\K/w 94qs1',{*P9Y@q47`!5(f0@13Z;·';ϓ:`'}]H.uk:8x@>ϛΛh|'!AnyF7!e/[u0,9i1`7.PP7eBdilfQPY@ou DR«YC- H*QQ(Wf3c.5Ilyay=fPpk󏣅\pgqF X6 \g&u`|g$<`uN>^=yJc{=Pcy33}/:r\lO% &<?}_!c:ԧ&4ky%)sDPR$OWye% I@RTLeR|ccxE.9΂RX*R^>^>D2rIxBR(B7 NHa| .a|qW8koW9cM98y N>N>Cγsycx982g||@)c9>ώ$z°VB B&T2١I챬fY`gp|E]@B@P9BЊ8ŜJ Ѐ ЀJ(`X @@@6ABAÏ!h(2h^ $pz P q9"24-0 3Y p}/bK+!A@ 2 !0123@PA`p"#4B$C5#| GdF+C̰3, 623 cT}*Dix "m;oob @t]Et]Et@ @ ]a]4},pOQ; @@ @YGut<3 z6#,ݯƟu>4isCv?4]\~ZxNv?Ni>/Κ.~q 4\_/ZZ EAn⫧/%rKZO/%E|X_SE.JǤ9QXcϞpwpd*.@w8(pK\#~1OPpQW1 :Я0_Q})nԾ`JׇCևZ+fKPpQz:/FBB8Ki6kfy78):= @}-7] R2֕:ASFTඍ8*s Z&\:rab:] -OB;Vaͤ yz"sEEV ݋I/wE=26C)֚UCU3Rּ'[*,_:Nߓ0_G7)$[m5s#M qe0bIp'p/RRDI!QRu^5R6C627b{^/Ӱo-J*V-^-=B[.Kq6&_U7oQӰL*j-^=5BZ$VɻM/l/l Ki}<& VWϓ|^^OA;8A _@yu|OrƧ}gqX^c.kIm&nGɾ@^ Иoecدy9Hkxg;^6GznpvJ#>;~> T6B ހJ7[=CSް/cs -x1{j/S}%\{~a}fjRS@Ze `û50'iPVg%8wR6RZ6aQ*E$e ւ9H(wzG!a}gXm>3+cK4-*%6?PIVꩨ*k.N*&ѳCi썣W>j#mYTl/Tm/Y;¿qq) enY4l})hH >a}ib͙ձfmvŋZP<)顴vKO ]i5= ;8P}7+Qlum+6+[tΚ""!SVM%^lm-?6DhHIA Ӛ/y^~/f%*NYEeN*/0o'EMpHJ#.U;AII @da7~I\ Ov?k6gwC,*k85Z] UXmݍg_r2tI_$.^a}WjJITe|f_뇬;FJUuhaxf }Zaxfٳ W<:ː*{R{Y-^ʏ8kIsy{жk-^kΣj<#Isy]Z*n>R^ls!DƅsSkߪb~GVbSR啯b;9 _:{{DeRLb&nyO#o4*24B8*\[;a}eĩ*HSb?MpESܰCUp쭢51iI%"&si)i) 2 SQRI!QPPZESa}Q͵x<ÌGOBI T;w=Or9FFAIJ|BIfm-?G]ESa}&lmұHJݚPi28)^XofmT+Wm'sT_JJ& q96?Nd13"WCԗ|Rw=O]8[Id_ u 98'sT_N@N|vI,d^0'>(+>(`|`|OU]@Qu*JʋTX_Wd}@Quz*PE?+?BBru||seoeH?ʇ ?_*^1CȺ.D2(<B .d\…Gȹ]ESy]BBy}ESi{)5P6l"HIp0m;VJԞ`kTFL]H:&ZqiCiir&Mש}Y]I3ueukg2fWhaUm%ڵѶЅq4ZTN5| 8E)钆BHzIK4WU"ADt*2H%mI*n:nR>BSɴRKn&$x]223J&`o2ټh_M(^ x5VIŸxraK5SSKR'ZE=^. ViUl٨F)E+ʦV.*dZ%7r uH}e)-T9KPm[ &Z%EGg\mN׸SJ|֬Zu_'S2t'|*pp:TO! igY,TqI|m/BT %0Aپa*KкUMF}4RRT{-&J*Hz~ vR%[9SO4nlϓlNS(ʸ2 &ɸ2n &ɸ2 (Pʨeee!C*  .x`0^/ 'FRe曳HT^lSSظuN5mrӒ0NT"!R6;ϛ, #Zu_t V.!/[U<O!Gzjyp$H"D$H"D?y0\:|jzbD/ "D$HBD/&7}dr)y}+xbLI// / / D'y }i rL_@ @I$@7o \SpBA!Λڞ_!xFzFiҶm1E2].M6ԴiK QZ:E]>DF;4Ocn)thiMۢyVt@=XiOmCaSQLURbV(]@skɼ:@qV1PTCntFܻ6{av+_j3gi:~"`;TAKRq癒Iz)Z7{3"'7~ y^Ƙ4ǹ$HwYnGFm?~"D$H"Jڷx(Mi$a_dY,q,.dMi @ @zd&ɲD$HdO @ @yQ-nCx @ @zy$H"D$Ob @Y@3I: @ @I"D$H"D$H"D$H>$bKs Ͼ @ G"D$H"D$H$H"Ex^ V\x @ @<S9qVMDm1ߢDX @ @8E0FIDA- mŦ-3aԦ&jB гY*ҷDʔH͒$O GF_ep&XVp֡iX(oAXY +KHVRɃY\Ŵ7Tw4MF4,ųDxPt\"QW!D+m-ԛziiV2peҵJ5q7 0i%mn:ifRGd۾0d’vlH'ƈefY}izc+7obN$H"D["m%QtFUcmI%xӕSJJAJR]u:ḢYk%k%AbkV!+[w ^G,qv g%+Ndg=D(ɣHwV% 7)w[l=a%J';Ca)5(lY8 NoN9iYm:oRS B)<9@УBH/ xsD'.Z Klmk4e+CVS0N̛JRf) zM:4OIN-MMOi 0ZE`M2DDpLH"D&l[Z,aiKS 0WѠR4NkBSxk ZOAΙ-ap~A7k.хݽaZRESI,㞢xP @GD$O %Pn,ֻ qN+I-'V+ ANy-V|J; dd|ܾߦ0\#~, @-$H 1"Dv-+IYk%(Kh; !U&ߐz)KrZK\B%t(Ҥ%i҂u-IhC0\#)WuN\#0^L#D @i"D$$H'aOAJ5 4mdUKخZZ +>mi)ṘHUgͤ1& ܜI]MA$iΆ};sx*V8pR[Z;s0^#L F-R$H"+ H: \%ҰtѮCHJK&{ZXkNT 5JVXziKyW)ɶdR` N/G#D FD$HxĖ qŝI;IO4M"MO,+U & h[`!f)FZ`- :ۍd=}sun2 JZӠ=4i2S֝σҮ. F Gĉ$H"DV XZdq1va+>mpcLٰ86y>Rb^qh.!xƁ @OT$$H"u.X|#G )7͢V+p^3NF/Pݤ@; | E1g0\3>)q';$H"D9X|#?G0ZOP.`pmW[ˍ;ܓ HKiQEII[gp. @4D $Nn4v-,'M x_3/A[& -Urq89!"O"Jjm&$G2SRg"AĎ$q#I$NN9TƖ; !YR(W2NN3(%C9?7;'9]KԦ*j:aQR8*:A+Ng?CP)]'RU˲wKൕEQ#>F+=QoDc|QF:c1?1= u1f6Y/SebJ-)M:]|q'(U96~(eEFK c`} 06_e0~Qw>Lz= .U<3ʬmʬ[mvUedkyi)I ӗdi%u,.v۫11@P`!R0Qa 2Aqbr?"H$2L&HI"D$H"DYd&IRYK)e%%%RQJ$$Kaz)$H"D$RH"H$"H$K BЮ8+= += BЮ8+i2l[kЭńAJ)E%/kz71)%8pN{SЭM+dlOՉ 6"o+cvޖȴ'a_|&bOC|" o nbcr苑XaoD;;D7܆CJcja 7"lN7O§) LjHNL}P29obb))Cm[BCBxW]՞ Io0dhNEFWC}9 Cs;ھ "kV }6M–I-_j)E( 6KoDމ_z%ފ׍7oE NQYYX&TTT&TR*EH"eD2eDE ֮E("(R))()2((~'PRpRY&I 2Ls:NQkDD_'FZLX{2eWSL'~e"H&R>ɔecfV>eE//E_W<"[C}LɓjǛw3;^y/qc3f3Qf3qfr/9ɜfs3pU47qm衺oE CC~6Pߍz5׍= )N4 OjcB !12A"03PQ`q @aprB#4RbCSр?eg!/|+_ W¾}ǢW\_*_-"ҕ}e 7j+_ W¾+UaWJU®+Up+X hW}i UiXbXV+UjZVwzO&᨝|;Jr$0A>ٵ@UgwIFۈs7;JrMnދ^JGrKqfwcnhNOH&s Ri*O@MEOKq4ڰQi3hoE8OEhFHT6SAZ6Z&G;Ոz-z-:'SPq6gW;ܧױQ7܈0*tqT[S>wt{3Ɲ;'@4ZHZWZWuZWuUs 0̗i]i]i_i_@:VKZWuZWuZWuZWuZGu10 uV;hߺ@lQ-3S\vӹQ1œÇ+tb i7j0ƍv uj&LAEa!o3PQz f"AܦD:uAf217DsDnE]?I̤[Ih!d?Uv<b[<\`:Gsb U#X F9+\i9\h3(SLN5-ψ0Yb*8Cznj>[YR}¾n讹U'U@*w`z'zf'갟-GnZzlxj1Ǧcgj1鳵ڌzlF=6v;QMǦǦǎoǎo}6ۭPwT#J46"34sm֌;PTpOES ?u1I 6ZT[FRmQ0h®*E'Z۱*ՊscS=KT"JlA6NW)tg_#I9|3+7:ЋM0I06 aJ[l4 %%h:m8*Rn1](f)m!ԫ2. ֖lWaHGj@6$ars0R2eBG*X=!UIͫ쨱*lȦ9ܮ dbME!vN P%` a50 7naTEF!# c"(j7pR"`)VMIx@a2T'D\m+ pb\TI7!5ℬNmM} 5,;H¸:/f N|!N Þ\6!T8(N7/)f N%=TfTS(>oZ ?B!WE))#OmTR*AeX TD1O7aloiT{|t2QAѣW)I6 "6)&dlڙDB2dROF5"S#]pKHHayYfjƲ1@v\[_)iGCoI aIK>H8J`BkѨ۸mi56Ye#R5lT);F)0T`U'%BR9QS154T{eE:TTFƯBa+'Qhd*i^1A6HɌ&Rr-isI 6)yIGŮ0W*_D:b9\^aOj~"7f%vlxt/;MB1nT~T;@QhiAQ8^aB.}_I_tVI+Jh ?/޿.KA)- hG-]謕+O?+KK+L:oiYZV}VOIWZ^a+P}ţ+FwEwEqXw&٨۩$%)^0Av,iWIIFP_y"\pRM|3c]_e)П JR8OS "d]r__IA=މ0ÖnIJ~ğ^UUo[VV+,V*ҭVUWB-Z  DjcZ+ծ>QQ{l K`҄g# 58j6(LJi h(Md> yQsQ-s}JVu_7UV(JEwE=VtZfZVtZI>EĬIZ6%oZ_˻ւQheQ\EVz-"ҭ(ZPZV#UW^Uoۺ\#Vܙ_ȁ/ =]vdn֞.Lڭ*WʾH*WտEh]gZ6tZ9>DšohGR?R{eP9*FN2ynecF9I)FIWN1箙QMQfpmٜ5}6o FMQfcaQv#QSB5}Y7zMGH7IP#kS?Pfб8&bqLda)[8F)/`MatA0B8 2xPkE{1)9).??wJO&jҟ,N%8  1'#t^7ݩ_rh%y1,|muԱF{%F{ VBl:%yk gW,}:/@dz*Y*8ُ^cՑի#Xuwl(b~P|tAG+kS8p =-۫UD7ԪiRYU4C jvTL*0UHPq`)W|A}ZDz.~l!mՠ*- Bɴ3+(p?iܤmQ0Ʋoġ ~RP9&N$@0ƌL|=ﺸ"P tI( [0Vntse5O~O| *A@UquD:?=˾QbѭC\m*xRy*K5ӽFZa>}S uR4 nt"@Ga T(lDFcfkw&1PiTLoN(h*dmU`;0kmDú׌&O> \ {ТG%`Rօ88y'&T ksh8UkcS 1nZsv+7*s&RT-֝@!0D6'7NL* ̺5qMlfs"U@ #e*hK(E9NaTv)r4 QJ. Ÿ\ `4F*W.zMsmZD"aj)V>,l5j[SX9 -4Qؠs0 1q H qZUg0#bqux6? M3!ѭ9=E4؉N'[hTb UC>5cjihv1Lb)949)`a")Y4HBs #ϒDnaVcTs-ljkk;uû]j0ƆЩdVHٛBkHfqf=-FHkօYNF ##UPHlOd4'JC+4L+6fK,5+͆b/Vf@f{# b8 "N sMkqD3RmP*e|9G[TR Yn5lR*D(PB=j9ѳ5 D5 ]XsGñ7avn S[FsEҤQ8; Ф+M"4jD3Ghٚc`!Re=`Ug4caTkfT{Z^Is@JDJIٺ4Fsnx(MYIZ'l0>vQH5B$k,>JH ALlZq%Ag4it7@4ֺ#K ^Q9b] DX*spsR@9FhEN1[URY C11Nj" x OT ~rMQ XmڬGi5gucd>y1T,J4l*ES(ӊ9I#?E dNN~ FsMu(E>D]U% 4(X(eY Pt`4[^j*wmH 7%% KE᛬j\ s bvVh4ƦuR4h괢*d)jSk٫GDgh+Gm{srcܐoj9Cz99N#uU!z[ѿ!\m9S6MI(]np4(dum scDS-yWbQh 4҈9EF,Z%q:kɪ1OΏ\1C{'?6~v_,!1 AQ0aq@P`p?!Q7O~F}'ɵ{r^8FFȌI6 s'i77٢;-x{1'Fuo&kG]kOUn>\SmoEqpoVb'jw7fpr1QcjE'KuSs h;S2Epaw~=$vֺ[UCzdYb#sc}6i:gcr z:nN[Sp*;I'=ћ2 dpLɴ\|lf 1ܵǁpv=ɖ<[GSn'f7t9Zչ{nɱ4F0XiS$fc%+bnEyiMZ]{n80qq;Itm'r.<2EE͇rnOǧѶYhB>VIw9onڷ7U $<Ś^ɲK %cȸbjZ-bUeRIFYxtGAugDhj=&ޟ&"gZ1E-\ik6ƈ6{&ŕ3KS; DR*A[T] $I:\f)Ԫ;5lsK7Gz&mShƧI&hDdB4A=) "#GDGH  )m2;|_}U!vRݝ߷D~vA'gF2~MI RhrpA{ږcM1taF7"&z$cDXڑ1)ADm1KRձby~F;!;}ȴ5ɗ4 ;o; ~6xjS) /ā;˝r<읂cܜ$HCPA.FE"pd24ܟAՋAQ+]W?t?O;Zʎw~QlI}to 7d~R OƼ0{p$%y5;D8;8D#C\jaؗ¹l8kETN('&WKڱH"^AH )AR+V4R7;#Oڟd~$>oH}:6KhgGohDޟgYfwB'@TH!D""Ax5ĮI\.;CBBZ'1RC{TH؃Y6#(,lMٰ]@'ඨbʼnGpGޡ>i!Qtz}OOo~|_;ϵpz~e˗rd>I܂5HAYjXQ( C@'x%;gl&;\GH}qCBc7>w2Hqp=͌057ffaD{eN q`) r$CY~>x{#ؗr˗.\!Gr "H$(Q<'OI%v vx|GH @( C =TH}cOrqcr.&H䱃*=iCp!AV)i=,I$DRhI||B iL2tk$x"F!B"I?'zw62L^]2yf"0;?b;"ЉxbQ4J MI$؞ijˆ"$rKP @<!A 4ڋDt7%R6W%\h]1>LILca1YJ6}/V"D;Dx"@ E)b)TeUڱB'&P,^Ź8G!hKctbEu&6/j0UZ/G?-GCi^uFY: j++^iؒmdAf4f!( 8NL$+ Ǣ~zIZG2P 󡐬L>-b "HW`7l$NkЈ{t_}8[G!ݛ(I89I$ $Ix!%"#9FFM°^u#vQDi:ޜ=ƤĜزCyn5, z+Q輞,K7H܈9jmigq]͔7ra3}ƣ֞zl12SϵjdN x6"jؼ&'2&幻2My.7nt#UFt36+!1hF;Ůn;I#4)| ^HDhGڳm0^>|'6?' TSȝs+#*`rJaJ6%ܽn<<|ʨT?>(0y1IfL@ڬܠqMqcJFƜ ztTba}Na1'1 XqYc'2mW-Lm:vf4 +%8R: >Ѓf68ěqd2sRǛ28eű7tcxBp%<Eu~ZOƭJtًNVt9%MjccژzSa|Y近#||.dSСe ܹ{3e.ݙ|3rVn}~fla,?#x9qjjEt`#| N vk-_u}E7i$L%&w#& -#00оKJ\EO? Y?ٟ${ 4|?H z!;C#7g%a! \{l&2Jgm_tjaTR oغ5G1tGDމv7>j-z&\7Chp*_4]1d11S00kq!=T gQ. F͏72E_#pN")tjl_bbEdsh,f/$oKk'qr iCس:~_ 6ԴK'ĞsKM$&DWwBK1rα$?\]FC<9HxgV1]!Ħlҹa34W ^ F&'5jEm _L >HٛɔdG6k/C{S kcfjLhH!ʼnN/b_n]:T00 Si Tz9еN!q~n%OI)1ixzԄ\va9lpf lȝb} l,L%I%>e>?)e|ex3od.> ؓ"|I 'KMUUn Z*Y6ێp S& jim_z+QX's $5Z,؈ݫxfm֏(m ˤw823cidx]?d1u"D^sK `(2rOaZ&译^Var}C$o Zt"Yz?+CtNPXpcbi&v!n\pqzA`a\ B,~o_!8?V;1eCNRi&Fs=>›"tj޹v%RΨ|-H(k4e*"v%/ٌʤ)adc.Zu^~t5Yz"Q %˨^L2u *pSg0F{BWJ~BSX]I*IFEͪfi$H  W稝nwH8LLcЏԅwD+1 =ׁP)*` 5 ˜4_wouN>KǨ]K%fhb ފw,af¢ ~%5禋t~ytFzAoI=L ,-w>OGXowDzKzI4[A&$6X\' fa,uq査܇kܹ1vF,^]Ct$@|q `ǰҪb:y?\了KM+q7MdΉrVObv1'̈́)z GPQ~/e%M,mJ_nJ"HJ'pnֆ\:oi̾5B4!te>3Q|MUT$؊c&cOb}Uo?BOOsАd6N+,ԡiC3(!to{>۪º,~sGKe$dO= X$x ѴJGϒX(7А(?\t}RӢVOBZ7.DMXI.*Q,8jy+}+!.,I퉐Ǒ). Gv1 .9 \(ŽKC鹹sڐ+Tn-= n?G^s1> :xZe${QY6wꧾZO:I\RrqX[F\_EUG *G|H0&ww~fcZ2E5D2KTsBIm*ϙGB&]D=(h¢2+'${c!'bޛiTsIW^ 6WhB>Qr>PI9N4Bί.NiT M_d)QZ>)0H2#Kh1Kao& Kѽ.mWLV`uE,(.ObQh-1#YX]IڹJ%*yz:FڳIU]|~t{Gوx1h눽mg=Pc  ::ã/a2ct@2h3oj~ %PGIB*H78ѹ)F<|ޚt2aR 7աǍPKY/hk`0Q %\MQșcҏ>> E)g"̸?`Er"T{VY<HFzGg Om!$¯[ )cE;W G)hP1N [߰I%Mɘ .+ScGriUy.LfҴLjAE~z2 EJqv=n/aK3IPr(f Y=KFXHh>?lLfۡ.#n*M{?N=J3 n=rOjm͉,釁_Jѱ#.#ҸL=c`AB+ A wa7Q~ WN~+=a3Ǯޖ#Fitp2Z&FvJ{Vlʲ#HxRDhBQ2\۱3 '9bWli5m?&T/DW`h/Glx!*䩬ϲ33~(fǸ[R?9Y|9i}40]8..gO^'h҄6':&I$DM&Dg=(S>%E&Q*>)= 'M).2ǹz_Gb8%)К U@aRd:*krtkˆ_ tZƋVteGf{diU3* ?*4g 藧][ `cEj}䥡2_,mGDn!oE"ZMeLioб$pg5D}W);E[>Md7cbG[W;jL*.oM?*shhb¶dFs4`Q4U_/MK$UCDuVދ[ 3ѹ|1>}rm[D=*rS1= LU̼)Z&Oj`p=%We9,{"f --|<@z4/~Oz@дE0MP:7:AA_ 8z j˭g#LQ^fKAøJ$ fHF< aZW4匔4zW,Z] UY >$s7to&.D7!NWݶ0)]]@}u/1\|a}E#v|<ۃ+rsb7B6kln.;۽危a2 ~Ex]yx^C;9,*.BP1 ]0i^}o&ryL/ 'cHYFqL ٸ~yi١MYDI.LH{%$=~7ҲHb}8'L ":{.M.,%[H"|TY QZɗwX:m;BL,)ӶL"ah|D78 Y/əc>-|nd:%_=DJaEvnv}!)MF~Rki!RRl{4'jxSѱy O6JSrn;N'HBZ0BIno+o 7v_zNmj`%-Ih Q5%dp-Ǒ)0kȸj*!cZ\#g5 wmY1~$L;; vn1`$f~\m2&\u-3txyfsRKZ#/x #˸l/pI[ ]-0CmۖliɑhPl$);[ne2rQrL43{>NSD%Z%Ws @fݘ3G޾uL"ӊ B1h}o ;`{hƋQi 7I׭ rFSPnsOGn'?2o -+5KzgFLρPՀ)큧#E )nRlD4~7l"nvHE- e.AqzܡdY'%za?֟(XG-!ݧu!I(o)Df1Q*o؆a%rJWYi˚<U/[EbVWܹq}^=c#@PvN!(EK*Ǹޟ ͣG8u5ÆolaŜKdTd}c-vȰ3c7li\?ϨQ{チhw4} =My [ D-:D;o vU9g(;ȏ?tFSw~OP?;C?N?Z;oǚ?3"_?GC=K0)De|2mZ.Aܼbev:<+֣3?`~ҟ?cJ>oQ.?D'F*]G҉d-l>QI$aZ~ OZ]+`*XF?>sl hG:qjYYd6N_i3Ym(|? x07oT@?JO:Q&՝\HcfVƛMvãB19 *n>CZ41!GK%di "@Q(J%TH d}CIbg|Y/TL2G $H#XEJ+Wb~ |W#ǦIRY>//_$;phPH> xڏ<5%Oz[ţZϲ! dϾ>1Bd$K'$I$DI$OJ}\ W]7"uas4  BTK\'9G$# .Ob{ԒI$Q%'ZqcTFkj*Fp4߀>K_E. pB`yILq[Fr#^O7n>rbI=?W~j+ NbHOGbl0$m.B65x,k?,NJ|n^_:ޙZWba |8r0$@y6қZxmZl8;#_挞·:Ao*PJI!K_.ಥ,}9f%m~ZGΘ}I,bnК-,/$(bی%mGCLݶ :QԹsù$‚EAJ 2JޗԏE4T4t3_، t?oATܹ䋗 Tl!+]o,/H"A"()$X=RŋRԱ˗ΉI4~hJundtڰ>8}VA(Wl,I&&Dŋ,{"螻FKDWc.5K'a%      K%dY/I$MJ,Xb=Q)!*/߬wѿYy0;j?Mcȵ$E` " (   )um)qoz\&K%pOaCj J%QbhX,A z+ + ]E'D&dI$MI$I$I4I$IO       #DzOm[d_'=$xC(nKr{0ȥ(b.KI$KZ`:D%DlG]"II$I$I$I$I$I$I$I$ADEDT   )qѢʼnVAb踂2˒%Q-DڑԷ\G2d$C!4I$I$I$I:Bh &I$Вi:'AA@   Vbj C}\["CTZvB!D"GG"D!~I$I$I'I$I$I$I$I4I'AQQC0ՈpbEMHN>> B @$k&H"D2 $I$I$I@^ $I$JZA u83X'BhWG{D DCĉ$C!_I$I$zC$i08fa MXq/\hMqт5GbA"$C!I$I5K` W L Cop=8zFyĢc,ZG|'a=D˒:݄eA$tϬۅ"= QB @$5"Dd2 :A4L՜$=6rzDXЬ7H'IK_݊e{3."9Y[]uftIJD8|.쟢PO@F8q#ʷql I"sYjCAAAu;B 5HUfH 0LO?VR tߥ--r*;"ӣ4~ S/ &'leO?CG=\y$OFQ0nةA ܿ$[IA,thAjAAAAw /F׸2ĸK `r#ojo=JЯlfiM"M[f-WXC/#o;*/TDd^"Ա\@J X77XXs[hmsx_WմoGx `{fz< n β>0Eȣ;̎/%$m4~Ia.N6&)Q ޥ+IfJ$"  =-Wu.ILV:K&Hj %)vcq®tSvC7bC4H8(L#}V/$gl%s% %8dݬp\$"p#N ig aC2,)?[&Yڟ 4ᦌ @/7{d^$ \%xE(݈vDHq˄;K3vM,nķ m* IIDbD]M&+zR_ީ*"?BagF:zc"! F$Cc!rY,M%21(G,܉^\Yq9| J$y!ab6$'.[C"SsS wńuiorhRش%ZSEݖŐdy :n%.nLl 9j\ FNlp0, cE^]'$IJnF$1a *.t$xQx J'5BotilgKZ"㫑5hw%iNdhYz&kb"hy:[عrY,/ˁ$)HEW$Krgabm!5=%^y6gDx|8d[?⇍,~PV%& „RRV,S+b†R9y !̧rfn/d%`2R_˸V 6tLǣ'/+*[p]mn9j̴HCt %$I,Xm#XKFd Fh"Dɓ!/4vtāX lKUm^bɴ$/4%R#M!e˗dș2zEM'Y.K.B-FB004YC8 ˝xT/ (-J'LA0 *a@E",xzG$J(i"Ǧu^Ae˗/IOE,X DX\rY"dH J& uY7G*`a38S-ȃ*d-A#W4lō n F5bޓ!C΄54CTfLIRY=/2$ܹr{Hz!2Dd2˱(Jة+ٕ2]FI,DbƳxƜ );{JUF5D5I1Hdj(b)_1vHF:nç eˍֆ̖.\K$H'a"WQ(Qk*b̴ȗ-1DrY11CifN1L (^\L `ڒ:!g=Xؔ`6#OMMt .u"i$HC1"L'gr%EμŎ&Y`Ӻ1^5h%H0X8-Q!D3c%҆@\2aKXت؃o1i;!Hn./V])ƘD%MRdFt-'B،)I.yђzX]8\8Z&ll:  =ϣ}5a(IGA5UiALCw 88BM??DI"/U&c4m1 WQm՘aY4sF)sMN11ϼ:_" s8Y^]09Y N{9tͪLJgG'm:p@*QY1 Z +ǟ( >\9GN#5n +40%*E҃>L>K[:-Lf>pce,p ҳ)A ұtvA%$.Gpc nW\i[5k|㏲ R<Φ^M&pA?kgd,= %Yq L)bCSm]8d1- y=u޸ o2neV~Gj2ۼ8 o4L20w0ï}$t,A`-V<%% 2q -b X*7>uH$|nrA4jOq`7@(hߌ!%(?[=>-ilבnh@g(:w01m4AO;<, QꟌpF}<. E(1'pŮ>$xꮻ>Kgz k 2Y#IF+{yw}S_ϙ<1 ?AG޾η+bXﲨ/0ݜ_ /U#{ ;ώ 2Mi]kM7_wL*%*}zG [ꃽ{R,*x2 4M HoF}!۴Y׬ݩܫ*`rb3c< | wf&@%(.󄥠uk X1:7;8e~g-<C's-3 3ΪE 1!$ċ"M5XTТ?s4 t8U%!()_i (6a0> Tu%_l2<8Cag<,6没>{"OiN0S[23><` A6F<, 6q @Fx ?=ύ:ʓUb43$[AN挑iC ]Xu iҋjͳKc3,pSA<#e<2Ϭ0_y'o0p_QTǂﴩ8QAf : 8L!TaL2Ho pכ 9SH E\w>ç,o8,4}Q`4a:%AAv OPS0)1KA0Ɇq~={o "KcR=m#zyb9EOt ,a1"n םOtYxH" 5 @qL~ {<C ,ߗvZ^Kp=p|,!a ׽k~׏x2Qz ,r,]&{>C{qK,:*z駋i f#<0d*KB i, "}H,vt2K}ͷ %5td2@,,0,(quKZf,!φׯ<{y_.~eydHeoY*A Ƞ)rAn#o|@y@P ( <@ ko$Hk 7_D< :pۍ%Yo3Ψ]W.4m ! +) @O)ʭǾ;uA4)<*xO H/$>APx <eX{ kREH0Gc0 ?qm$ 0e {[i_eLe=~ _r4N 08 O0~@HSyӌ4'XsSn?4ʆ S|i. <'WAK 7 m#3}]G{a =8:8Œ!8Ei٬, fK(W8 {l1:,VL=n C윣C/ofLw|sL]  ,9!CA2%p@ qJQ= `!C! A0Pk!`\y۞rt}c?LB 1%o{.(0p j"Ŵڼp U'02_vm/sی$YR=?L.9(A :(!3,42JzCӑA$ Q\<%1@5=9ܳӏ 2yӷۤ]M$aԒA̸ױ L2}uuuR 8r;=gkOe5R;,8A4o}35U|G<&wUM1S'LxȃĝfȧM4idBE^2O_N0ue786s߼[}.v7|?v_5߽O2yѴ qEt`Gz0Ns48 ;7Ymo l )GQ  = qtǍ<<8;N2<_|~;,0M;?ێ0As]ea>۾F!wjٿ_|>˼<3<<0VAq1ϗ!-3#<]sR yAA_~~o*5Y4\:o:lE$q]o<{MdPsv%ɧ{w_=D꾝9ό^ʹl8C\/zFĜǧט GY}0}/U/ FX Uydqx"h(>?p|}l{8Ǥ9ܳ|V.}3VѣSI'790טAwM&(ՓJ?k9wy>3~c{2ԔE!/f骳BYUi0G=bܲ2/y^4=/{z.L޳wezn/E&AWq8kNN>OCݸ,p4 _ow$YAT_}(ck;e8g]i%Z5w[}4ᄌ<$~+X[{wsEuIzH-lO51>0 =:]rvs<0MR}S|ht.]}0ϮN,uϴ0-}l Ϭ0>;׼=n9,Mc+꣛~-P7y?ǮQl4{f1=SnÌt뽰:4_5^n? -Y87~.\pOp=]d]k<}]Ym:^MSg4z`j|hhx1,仜7\z?700|/}}7ߌ7C08ߎ?~7at8~0?ߍ) 01@P!QA`aqp?_ϼ]+¨EEEDAB)pȟYJ{*** `eQEQYYY_W\()H (++(++Rqҗ R22a8sItnJRx-P/>^ yd}J^h5/ ^Ftx-x-kKs藫؝~H% ƚ՝ Rq. ]Zҝe*%!ΛX11kHJ#Dҁҝ!-WJ;qx F5BN[hL.K)mF5V LIӒkۂF! $ "C{Bsȼ BѪ3m,!eO)s#f*cD$ڼE{ bzu҅1A5bU(ƪx>=:?mugğG} H[ŖۃR觃Qk DBF#`4y\->6BAĺ-yOYEWSr!h. &LNp`3Φuw2ԙ}mp}:+ BsBi=Y\K.`[E)q.J\/Jf)t6I~DxeXբz >D1G|G W!{r&%p_ j<5 }̂ R&'[M6LF "<-ɥ'7D`cVʧ|Kx׆CcȡxmYD^)?HHID"GZ[n"7y%켋v Z5Ww>D!L! '9oi<PfM ;JB6 a{ /k?q 슸_w {sBc!24HFwĦf~l`W9#Oo_{py!;ݩ49Yf?B+bwм4#CPgqtMj6.Vi=M}5Z#i b,J-+2o|;8UVڢԚ.G+9PBU$.I %=IZ+G$҉HyQz'j we~X'҇ڲtR6p'ЛLJM*mbl7N;-O PxA7 lx8FGmS؍1ጚƃ'ǖqfĩƩ^LI$X$'$!~K&N~K| c{=g9ǩ7l݉w4${<)KE=G2>i4:&(#w͊]>زm%Xϵ7B#C@-Ԅ>X}CVwV~>ӧCM =缧$؝iMX6"-mМiǶiFt{å^ okގz'XN#fTldR3g^RkJRf&]]Es^ᰥl6_6~QY4X?=  (JTR ےiL&mpF-6Mgpy|n~o"e/Iو# ƛm!Zx Hޭ3oKH!BB!B!5%Ǚt&˟E E+'_ŮٟB<Wc ?OcQB?gF1SUy$VVRh `/м{Ãck=ly >?F?ᎴĚ|+fEq)pK)J^%re^;RKlBaB&a0+y3 )JRJR)JR)JR xഥ)J][,6xjּ'c$]wi̗m͉r^=*]uIi :Ihs)piJ^B-JR9rJ\n)J\ZeNeƔXBxO㐄! X\!2[tKKKzP&.Eޤ"8725˜'̳NU ^ա)-7hӝEAҗ5&0`3ӄ3YX ,gt!ME7Rsf& yԥ.= fYX\iLfy>uuk^GO)!1 AQaq0@?txb58s,ad4Kqm0 f/F1Ib{%3eX܀Ckoh(݋Ɍx R #Ğ%ve;B#œ “5GxRmoo9Lc$?deЌlj-R'gcu@ .Ľ $t)hon뭇RfL"'0'H8%,f7s!GBc( XOePADlDgaw6?ee$D.h],.K4Mv|dmICYFFݢv=!f7Gjt`j$a4 (lTQvi#,n (KK=CxBIN;,E$mrYC[d! S=I}_n$}vB0$A%)wT`X2C c,e!KEKYN[nx̀ñK$'@4Su>(h(44b&Fh ΈYb:$ ؁4@`IR2"[;h=' q"#(ͩGdэbZ%'VIz`20-P1P=!Nf6̹B2#AVQ dE PfFoFtOmcSfBA}c & D(΄3`MFH+?W#=M_gH,b@;&R+('I M-a#GEoA6݌% EaN# bwU% !Tlez} 3zA0lb3nJk+aw:F #}'Kt,,&0fdFerB2% f /"1a /I܄e hd̴DQ2z22Τs;hˏyHFR.0Y h" *\x~%r]=ACn;%b]Yj^/dYtN4aۦ ay;$8pf鑇`ISL‰ ˠd,Y p%%!,8&zmA$H0G-?}D~S}zi7=F ߾[}/qunu*Sj_lvw4]+ fKG X16ፁl<L-7u%ruw.{a#ЬKU@S< R'K@M(݇]'D03 \DmMLcˣH"_'$}N>7x'"kX|ZA🡟_?YQV!?J:GbJ?uo7R2S_[i2??9Yqۍ;z[}v}lj^w,fnR4\G/V:pXǀ @61(16JjK9Lj[>$ ɣ""flF43 6@H@v 7X4()iШqI ώh>ń_C N #"B#KIeK`߅ĝ 7,fB0e%$0 Y2. pB#K `i=z!D"ɓ=_̈4#E>rOe?U#ubOJwZSv/WU奻W/3[28M_Ӷ${ $`q&`$ $,byd1Z~ @{K ~s8鬥~k:v9Ou0QA m/E ]'I?} #u`huI K`0BRJGD{Piފ&LK52 !;g@zBqȽ nX#N16Ow KfƺH$M?~a?ߓ,qȿ'sgJ?$ϹrO~'~?e|~辬-/YUu>[M\^O5YGU[vi"ys~g]X H bDXYXXAc8%HYH q8c}L}bX-IԻ}fxvJqvx|S_TЀtlm͋ɽD{d†LJD4/Y:,:wxGnٛ>ac_6lH e ?6P(oH$x`6J# |6`˨BI atء, I`in^`h2R1t m W4: ?qyϖ,X]HXS)|_[2Qmx6Nnыs>3i{[_j ,XIdatbcnHXD#`] "|D$:)(0`L%fsZHu:(+'h!܊`nZf헳q"Ř -`L?}&0-nD2тc( Od"w:l5B5 T:$ /zLawN#^Րi`e\@dǣeAɫY#e2-=[%X2lXXw?kmNdJ`#! +o1X0f͋0-xY0W. // ;lt  ICR2m$]^vTpFYee F!V5aN#sa%,H̵` 61`ԺXٵc!Zç8N6 pfoh߹(AD&vAhp2NcAa8MZ)0XwF_d Ӱm (*Rጊb@V8$ Cv;lkcd Yf: $bņL%6m+A:\aQבyYBŁ~NBno$vy'\1 X۲Ӭ YİXp{q$l88][mxq[K1 ͝8-ݾmXzq,zW Yt赜xx' -͂:x}lBD0aKQ =!,w9dٕ L7Rc !Ia12 " :&f6qv$NcQ\X6!cb?tأرHAhK@nex@.5 R2u!t0t~DD55#dvh/;B% @, |<$˨٬Yg3:JvN1IJ wgی{0 xwz8 0 ;xG˽xxz&22E^bNs>pV7 X,$laìtd6;'ֻll>FX߼2 !CCbg7B]zbϦ1wB- OpV ( W,C1p^cw$J"[o[C :RD=4vlHd0IU捼 wI4|$ Zc ,-iN.,b Y&1x,VR8[:\Xr,^= f', -MK'W߃dX>Y֜:'ŷ"H#$Xx-#Vj FhF"Kd7;)nLRF#"έs,rlWVd0@l0 Nxqa7pT"ɀ#IU"Jrh*+gà{nH pĘS )EX4 +;]V  ·1 v8k&pi:Af$DlA(xjnbpr`cx>cca%:'[@\ 㭷[tuE=]bq xz F,-$cbqCd MKú9𑮐;;i s&FSzH蔇mKTNNL|&ѲAd6lLA,-B4&h@sԿCa3I01LGKa&ze4; NH[I! 7ɘX͇GXw6 ;}Ki&1jY"H~> 311u,N,[#,鴕щ!RT:j1w`)iݦGA^н d`K$ m}R|! ݗ˯qN ap݁㧇׀-Ўa38njKaY4-2 φ;8vZzI JǡkntE3Ymk@iѐ/#Gœ15& ы)c_>,.11u 0 Dchѳ:LzaXu 5v0Cwx] ]1C~̶3}m6LD?ΰ<:-6m;c# E ]wFp3p-,Y(iyxH z 9d,}&Xb1qYɻNz[eQ̒F0,6k&Fٌͳv  goKP8ɴn 78-ǂ5moF72`ă=f$HXa@g c"KHudF*JSG@ B; @"#jB)BÊ*t lOT\!c"1%" L[<b3a 4I^my/6'Kf"Y) < B塲NCYF\x=5 #bԍ.zx'@r N3Rη9mϦ@$p O ց¼ ;:p 0ip dF~DN a-Fñ`hG KR볃:l!umD3A. ud:   $ %κwڂYP@@ .}DHkdtJ) UA fQ7Vu^'N0Y}6mOfqs[o]A'[pi&28r=dgdXo.:Ï 9i '1ƱI,<lrYn3wg=NI pX*g˧dO/de_26%5V,es((`6EL"u#w#ImbhO(U`QweIZ@qzl;6h8j621~a׬{d)DTadrJDe (tnY$KȌ~B]8qs :# Hl-2<)hIǬt1`Z$f%p0vwY  ";& ].8X>zr؞FpN6Ya\.xBqxlDx%7,g[ +cg'`GqYVLЌ]pr: 9 `,`(GuL*FӠm* =KE]ZZzH2I5@9wMHM+1!l(:%ѤoDl,Pg܈j}ZFі/`gr181{4vٜTu^0ES82*3ry8X'we2sPaMmtȎ5mIgc!^7Wj,87n5фqe 1T,G]9hؘ.9c`9#DauzB}:0GtlTcIhhJ,Xi#>〺k< 5-c7H R1v:m$wa2RU6rs#F3ed`Q1TÀFNmmly$XN63wՂ8, rɒĝuI Ėp0ug61)6ufNef/oZie)k ?Kk}dfH85ݍ|m 9&juaE+h)fKwAdѢ"X^:P}d86 *TG,`b!a\⒈/`" A:{:jz XF"Ncy 6˦VXuĶBfb18Gp],ɀ7f$ebYk)_ q7F/watӟ?z1n~AIgGeθ'4lM86Howci;zk+c&:AjHGy8RxLynD 6\-т2ܷ~Tcv/Y>{)jtqHc @(RDH)`2:s:XKĚ&_c<:^69qbeF)?kc)O`߱E0LT7NZ>?}Hg *0FHm'O2;93mY0Kmwd6ÇWJڧ pD:l/lG2hӹ3L5HQ"O@#HFR}7](8iHt V ѝ'V !" >KI#nΡl^([!eԄ-em8u4HXbXĈH m%gE$:bl ZM AcD~V37lw{vܚ~?:w mlaR8+&9f'Pq'p'鑾855Lྸap9 ~: zͳG '+Ä<l̚pi.]u l]* lDC r (,-Xp٥v!k;ϼ "4@1 3$F#FLDBZatIa1E+HKhAtXL vE4rA?Iłh2LK}AXaPzN7s`X  SmF&W:cam ƒtYi 7Wd /:@d0(t8zf{)Kn2&m a0~}&R,Rdw[IӍr9F5aV txF'>6fwx^62<5 K o\C9k!# " "(#Pg@f'І[bX+x}H_,4 0Dąt5!7I mPz=8ֱi\D8}ٜm#vI[4cvXFY,pg``lLAbú 6D?oVΛnt䇦8؀WI~[}$nI9!JM'~{h,Fv<+}}[m_> aavUqa1atHVMgH`0h՜lfQ/U! [`/M,4 0 5nhl\1,8,@qs,nmΒ9;^v-<c7?o:Cσz]opG-Me] ' > cUTbqp=̙Npn 60 .#N>k}&D%,8CލX`^%Fڲ(62QQ88=ٝ[\ :!NOcY 0cPgU[Tr1Ek;%Bht&@HNCIH;>P &:?$B,G,3 C"n:Ev 6sۼ;:DqKfݖ%g^uƬtpa/Ր(^>yY<(; :Zf9h[| Qߜh7Q^I˖9:q ) O>LO}D;0Pm냂#s30!*z .b~"eփ~Ƀ0 UƝ$a\Ђ4hJhlia]ME=Xz0Yp=],< V7L7e2s-Toq90' ).ou gn4- Džym,`_ mgagnjG>〗l)t͞e DM<,u1'$wᰡoc>dbb-oiCvo]"P.@f mI"$H`'MæL$ޒװT!"4 6]\j84hDn:F8?0#InllaMgG ;{8lY-8vq6Gy8j'B?P5]mÏvF7W8&b6St>>Ɵp$c3%nȶg$i3IK? O>ڶ:vDz,yԂެSa$BG?8>6a a>ҙ%_$RZhً$gSSDT3KT:0L8t `LtUb:8؅~jd-6u7m-a^|m-l<Yh'G"{# 7~ M?>@4x.>O!p=C]GC]6sved#;l sDL7-`/8%μ7ʳoXg yRd>HXeF&#h:1Hp gdݝ&, ,[aBjja۹xt3#*,##J (TBb@ N$83tIhßl0NTzc4t.lc9N7RC!ZJl<k "?v-kFGIQr`A{~ @^(~wt$28pya6#7쳙}s9àxp' ;d<``6ô[uddq&GC7۶3 CqkIXƛH 8'wtBrĜzH#L`Nf,ѣtnk-Q hnnl0//A5g g#@fIVx i> 6! sp%`N1u[d E-/[_ aqaH_Onfjx4>wIyCL گڭNs3Qfȋ>k^XbNZIk}G;}E>s6A~-y~=fHB@c!q,Ixgx{ikfo N3o^VvHğkOK/$vQyݶbxH??b{?2TV 0π$Lsx 7?/?8b%m3e1>X/,"x^ [`P ɀl"SGdTMHYGDHC1IE>ч`O$FZpFafZ"۹iR?ŷ--8d#%aY{>}r!i=sW緍䴎Fwv.)(Օޟ Luϩ3<<<_+[_W?\"G?xQ(HjKǫpo eukgVxdLӍx|LHe>ɺebp[qI-a.2+/Èj Gt56C *F ãkKxN'ejz]](l/L2Bĺ,)m;Fmerut[NmH۱`Gsf6 j< ]G Brgn9h2O\AgǷb]x,U< Fh)}ݣpp@jupQ?X0;oXb84#1~4lEw>Pɉe$vvXmf-o cȼgJXpnFq -3Ls >go .# ѓ[EXmgj b($$} D`NXgqF#nʀ!KV$~F7Qeؐ'j/h^dǽy'}qMոɛe,u`n0p5#{Hzf f!UdK= O`/X2@Х|Y\OSuW7'-F]Kt2u7p]' ӟỺlX"nؼ׻ 'bx,R+Ёd4A!$ j nv15у&hȺu -z CtgXZPivqKR㧌HK 缀θ-q =x8e4r3cծk8ْPDx>V }G_[P'#9tBͰC<6D1 ɊaMLQ}O2 x&|[Z-YB^5x쟉ƙ9m]ew>BrhpcKbCSKj3t$DN 2Ho] md)k1sHe({"6BoA_>=cYމ{HKZ26at G W76Hqgv,d+"Z fd(IO8ro aIeXmJCY9}.($]u㬾8y>KK+9+&iolX#1`08w\1aa;o Whqmg3' i[-y88nӍ<ĹKD3mv[XQT$Esj ,Wvz6߆olü+iS}N7[{Jv|f1omgwȒtYhB1v0pk֜k}#a@~`pWA(I$A e0BQMNΉ7dSd茨E2 űO S`wƆ[,;zFthv^:ߋg$2 ܶ:،ón&>:ph5 b3Mlm)Q d;?v;쥰lG!f5d@ 0{K$,Af^uxyg1Y巍X clƅ!-E,6bAw^KFԌi7UzS A$t-u4~эX4Δ5"Y0DB(6Fiڞ8rLx0fS u8!#vmtK@(7ylA7XIa6ӑQ>K{&}ැ% Nv:؈8T7V"Gp'xoF\Ք,3NO'xKHӍ C4xÅ[> 1""J]lpBtajhF1d] 3 x;HvA"0ѝ'I]K3| (N4J,v&> 1.}J# w b 'd5p^%m6ޑsx%xa7ItC\lo 83x_ ax!bEߏ2Yapy4*?Ai: ͮd"[<pK[6e Xxނ8!{x `hF,u l?KuԅFN& n0cA蒚20J"c <;gd#%YPE[FL82pje@(Yk8dg9&6MpZkouu/;!cv:!ԉ 2Lt¼wBRD2 D.'@E[pw{3'-Tm&lpA"e]Bưdg][!aONcP1 f"{3HxB ha$u'1ij$"z$ݓmHꇰd1eGU5\ȆXwNoYPvȎ| x-l$41° >.',MrR8䁁F8oˑE>S0QUWexZ;dk- nϳ9o"Hq&6Ep$le tdaa 8&1 b" d 3 IB 0mAPLA ж|2 l*#0JœSO.ɍ*KXImYxy۠LKĺtN:b.""W65Y\Qm|/ZĜb;WI}'"&ˣ㍳0P f/'-,,'q7Og`bQ%xl8-o aK-P>,| l<qۺ!Hg7<5ϿddH,X )Y7H`t j%Y82̬ݯAH"[<=ƶ!6eYy[m Imm~bD0"wo8b?wdTݢΐ' `Ie{=`]r4@c@ 1Fl(a"Hw\cp@ q"Β 5Lmfͼ[6:c㤁1e8Xz!Ib-DFsOߍ4L2 8&J.*iA}{ʌg翵02ʖ(&آگjl𳐹 [/p +l<+ [1wq fḦ́ODf(b:Hl@B|ԻNL9@lz6)d nLF![4gK=iY& r)dғufcg gQ1IN{8y&ׇwN3p7c"1]/ ~$Ki߷j3<> (lQ?fv U]Vb2꒯ ?#TuWm7<28NJ""[uvH3FK`!Z!A!5EJQ`#H93.(0Oif M84[mm#F| Kxۭ--8- na@mmC 83Eٖn̻#N8gǧ/DzeY-9˰6iʓ uᷮ e߀?x$$81I,X[F`^l. ]fäaCkAptC;HԴ?鈟Ҝ rTdju;.{%d~dFƷF2FB;$ qX!81MD\j # K>#d 8c-d71!p">&D Z'mG+jç; ŶY[mmyyYy~;i˧rfp BCC˦6Agdd҅~UA!,9 欠 ܮ]jhH(m( b籘 e :,V C$rZqd[e*DƬ'ۥޛ;;oYoz$fUpH&|L~Ǭ#:Ӂ87ӌ^E{8',跿$4.rCm4X?grL]8(d&BCÚ! ݅Dr^PcRXW AD`,}oYx8}rfDNe ywbPjaq㬌>[s¤!o/;yz8 g2uާMBl Ϩ`@cmmWbfqܐ2󸤪$XQd Af!À``#0p[DHd ;+בpQon6 lȴR'l{#_˼MO^c8<0mYnH[(hXQ-B4=!;̇tŷOݣ%Ld' \2#vv,c xI,}6,Yhj/g Å3cl ԛ'XKl5xC,:圁p3$-uHkl:'ewWq{HWL]75Ld>ˡ ;D{1)/1|Xus,Î(@9,:0N~Xx'/Nl3Iow<Dzc[,/##2ؑ,&m gxgel8$_sYepˠ Ɩ]86ChZFT[?͎I41&38N[ qoзH셚4Lu%xߤ"ZiH/lc8 -84[- I~}[6|Ew }c>Xg2SC~>dpFq*g\H}E'@Ht|n{'="NnO]Rp$ R-4](4Yq!O 0xX0/`g߂orOu<K-xƥB[{z58Ιyɳg/g 8xC%:pl)NFĈ˫[ͽqwl 1~$ A7 LQ=C#u2ױ2 4NLm$@oa,K#H7rȰRfI2+auk AgĖӜecmxF]W\ N=b"Ymgȓ|>'!'tɃ~ ʖ݆qr+@S FgWItmVEϫ2&Dx oo|Npae8% #K'$lbXql<)rqK< ##R>|;zmX;?gvlK:֑ V.ND,}`vBiLnBjf! E5!v?}ygcp |s.'&r| ~ s9'[lKt䵕fa3x)03Ú,I2X&0 Yg09^~6beo;΢c 8/e}ٖ(Pdp&gF,xķ$$H4mE-h#DE[?2:~q|u;ftO8j͵JRLAٰg%4xCp2vj4:''&_Cգo]pg6Ѷ!9yI[ AxmkyODbq,፜^;^Rr;ɚ"ou;.8/]mcH Aa:l$H BvF[ART,nJ{{\,xN*GYk|rG8fx \,H?}3 [Mߒ!H+ýhJU8?J 2[:=K : weH0+9J^VlOBMˠM-p+@ zȝ VoW\n Hn}8,F? }aܹ엲 ;'ʠsaWO40>"f5{}JL / uaK%$ޞ^'/~OىK8ݓe&|H .ua'!vr$S'XEA( p PC% |Fs|097,gpvu' 3x1ĴtcJ]EN%#m K^c$($(taj݃7j"%c'I쬀cl>GɅui,Z伇9Ɯ K828BdyD8./Up? } .n_qbjk u.X?aMw~eU3G %N%-LS D ҄ S},\&HBtR;;8 qRf\~nz2"j+SyTfͭ1~u0AQ- &̙̦) ?I $[O@H/BNU#0[gP{ EnJ-<+qlf 3L8^DDChq#g9YD| uYfXX@fY&h vPGGx ."($FFPꫪ9kFnKoԠۚ^8D}?EKg꿻8`  WfaۛN=&S sF6Dm(8Z"Bp:;Ay}6'~$o~CS~tmmaa8D]E\ιlkmx~*&pgnK}<hˏdu,y2ff"@]Y 6$ж+G%t}\Am`Ff1LXoOwv$6$% l2m<6tllۗ|~9F{tz8 6c;S'q'?ܐHBi Ia.xADZD\!t̉v SqBqxQN>qfI gr7`i?¸?*[v-{3qRh\EM+?<؛B'Zم}g1fZY$s0p VAuX}LNY_cqaI]DDQ:F-xg,&vqk1?}uO~g!ȗOLOˉy~`A#g_ѵu?K~7Z1$=2~ڰ0q'}/g|DqƄA8F$ hqpۖ1'~ ?~Yaƚӯ&AKF@ uck㥢H`ƓD$UQnHdG1u O!"эnY|6|98x?ʒK1L1OK,(Hp`,{H=Cʢq!+rJ? Uf6bXfژh"'খpr` _  |#d_}ѱ#?>#qWj{{"[}uo`e]ោc?}'OpCO_V7_.ʸ_oeGDdMC=KrɦAƿ_-pHMz YZzP@yL$?4x4㼽x%|!BF̲I Vb:<.q2Èh'1-6Q jq[ OLpi$i¼i},? Pc>oHiT Y\ja 捐Ia<Йj v/m( +%ۿ:ZI@hAoA *DS-GS h+ }G!T>J>ϫ_?>~1Q$>'7)???a5Oi5ן|O_'H';IaLs߿s{ Mg?]M/f}0m?JD9"'GV]mFy _W Lٽ+0T'FNapHY:}}1B_16h ۙi0dSW혁ڼ=?x.価;ߋgI$29$ѯ,9! w[em2q;i3'dg7yƜ;cHYvOorMl@ ݐbGÈs$!]h$4M Sp`_YY3l*oEuQldv%UKlݜa-X~3ZMItvY;C'F#vS|Nu!C4,eĔuL&FbHHgwI.ؐ> PrM%+oUg 2|12L1_6'tȎ66 [ׄrubu"y$Y8I~ lBtٍ,/z 8&R~Ǎ13I絎nep C}.őHGE@t[r w?IbIfAؖژ9t F%}Eې0 LnB ~k|ȍG,o@oOSRlw6IgϺ]$$b]bc_|YA#d٬-ݝp`1[Yt nA:ьIKsYnw'L<3[ku>+' 3nuemxF㽞008V[Txs' -c7;\f]aAec  so_@l5I1Kޝ 'ql0~&kk氏k j]SXŽ^$mxC-h2n y-?$vqd@EЋ8qw$ܒpq8kDeD1yxo `T caݗme3LMl#3uwG: Y f,@Nn],UH"q|-V Sqs!f({?).`A^>㇄M[9^@xN5Ixb6maO9Pbj?: ߂}c xb>.T_F~#֟sܒ?@ul,Ť [o%ˤbeo/AO+{˿ 6ٷ৆8T0]x݇dHpn[u 4IOc+|'`$vCA@td%cKsNؒϊ| -댲 ×DЬr|8ge"$bYckm 6e_7eo6d*6 i#a-@2g8؟Hq:$e\f_]=4D! FBNö(ctZt[y=:bcA3Mihu$b1; ;摐>RS-#fP`~}wYxo[1y3yl= Ì8~Oxy>yx>Yg9xI9^5x ?FP Xk#ٔ&Mg/LpА[˻!$e~VSkZ0YWY9'~d~m% eN{ibD-: A|88 \/Q Fdm)tLԌ`F~`~~π,N7q X[nmCmO_}ǰp7k}q|.@ '.fK$8öeqAc:]c,T|Nij.B6S$`6OB :ߐv]0[g;u?z=#-b:u6%|7",x;l瞩5>R9::b%(;59 d㼏>NMM~#96 ;ìml9x.$cb:aᎏR'HUsQ-Q@vC5 lrHk"k$!0Gxv~>>ȼ6ܜaYÜg'xXy܌xs>g#kAB 4Y}?r￑go;gt~9'y2xClZB[|7Ïѳeu-qtrޠaOY:ВCh7C`6䄂iǦvДō-I"IaLxZ_j[݋ll|IclYYxv"LJ_<0ZxDaZ1ѵ9wj2FQ:HC&HbZdnL6KȢ, qH$5)w`EgcwIx- eʹπ8m6`y->꼧wˏ7{acD~ND.[6|-Aa?3ra`MwCDY{:d&c0ldy݌w (IZqecM,[K6uc?@ءezi DQI B˘Ad L}Ǎd wv|5!-Bm[m m-QHm%Imӆxׄ? _R-w7q_V_'m^Nsy#%;,: qoY& 1!vun ݜ%GLm%40mB2E}!#v2˦EUp Hj5$ AKBe@ɐJ D +hx[K4mm8XuYt=MmBՕ%_{I`'V~V1퇑$F [̰I DE8-[min Xa74-!----?6iiap&|B2̙o ZXwƜmxvȞ1fY+05x[{ ȏDc c]%"rDv Eh¥ʩc@ѰviHbbNTdpƄZ-o_KU-vG$t9*F,l^E Yy8Η{ ʋݮ, Ld:YO$g! M\v@&?-lfl``C6)k o-n6'"Z[)9mO"Ygsd r 7'Cc,f)7B v-K%BD-Ic , e!v“"H[vtcsqGcA*lYdt\ O\Lc-6C, Yx1@1`s"͈6v“Fű,`#3gn8 m:e Nt g Ƞ4VM yy "yo[g>Y$:M: Y~,-f:--# cxIa0ά8,K] 2-,#8ќeZ` 6]$Nʘ1t18|| b͞6[m%8gNhNz,,,<ذlEg8?'fŮ f3E{EVZ-Z Yىk)H&u؄2d= hsH`B"r>  q'0ͱ!TERmv&B"# j߂7(0&x[4z}H, ~] s\  ZS(call~:so ȶkm BVۅ$pc^R u`K"aN4 0 ,'3 #卞:<0g8 a Ia jSi` I6‰kBTM$}-QK]l7X#a|mܻBäWc6t$F,vⱆ!{B]8aH&g#83[&$3k pktcZڼkϤfl`,H.&;&$z6?0c6iM6?7>{6R rjۖ[Qo7b `tab8@F04 ,Ƚcd$53یPjBՈ:5n+k\AU;Uو0OAp>.ab@ѝ"7ecAʨ E :h5%|I6,N–(<69!'YF0W[+)EV6$#s6{qp 'ligo'H='N y7. g'$f XF -`rYhIMT!DI{4A4H}2 mC?u+˳$(.!&'&}l,I=.P_8(IIUM?tPq`rޞ<'M=0E&54dcmP14XH 1Մ4LXP`~ Wd~i2$8L9 ZE0IyZ1)2$fZ#b^E3qL!ُFIє!Y[4o a[P$sߪ(I[f'l*og\#nᾳ=5T[ -m U/%xM_Kz'XµS VPKʟ_HqV dRM!>~4,dsGͷ`[&KIH^9D" ɨ i-pA4&X{ϗ[`AXzSzXN4 %kO䵑{ 7D̏h$&6*36p|d,6H2rg,EveԬEHɢ]?*AF: f0CgJCdڄ+B2*U|,mPK32 @l,%Kd}Ou0^*В^\Hn0[.ȺH7kLM#a7DiA)vdѢJZ ptvzafE+ N31$vpbp5zlpR 1|vP'P9"bcP V 9Iq7CXqWV I`t$5#"q,]?1f?C ڬoDo"H6qlDҋ3YkX tJ[&h}=1q\dǻ=(v>AFRӃv0 ;:M,KXMfCSa7YBA.fƀ8tcYCCIiia'شbњE `8&N6  ֓8ׄb]^>4dktc))jhE'2l4d`i'it, th]/LHd# lXZ<X卯ğ>d㤀p0ii3Ⱥd .1ڵ٦X%–E[U1L0U<'ٵ\ Vs%O~0 ` X1!_*V5I$UUYo +`PL8JآZq9'aA3KB: TraXUUWc Ua9Xu2:$Z(RGV/*FM#mrW`pIq!8䤲iiV!: : D+N$OVP(4ޟǬt ] !c a}ћ Lz6v6 2@ BqWahHd_4 .2, uajO"QdmFջ2gcK[h2_Y2I K01$p!9d@Nc lc"l$=e`|l Fɬ˻Q##a5NV Le.挳R6 3q`DFAZJE ^$ > 4Kv[8O=8~h tZD"P$ $I#<C@<%ΰ%t}Jx eaUmP 5a k7DT\[[gFk=V9~猲 @jcxl$.tc;;*8mxCTT7'UB.xD3|utIʾ%WQݖNTdݴU,_%(p"v3 }U0C6[_CA!o'U=)0䒬A|#SH]X;6UK $Q#td]ǐX ,'F$N,v-B,|I/Bb$ㄍ[QΛЗY^k;n3ްf7f r\hο L=d (qZQC@c@?x 2ѩyB.5XEld\ve-B4;ImQ%:Vdgl ec' r)mXDslK }w}!,z xJZlp`1d!. wb%oV5B@HB5*NhIWGw mvk*1qhK ߡ褅_Ղet҇Y RhVT*La6pâoJT` Kes)S/HQ}-%De!BF#C,i", .} 8䴵cd-K{r,!KO-7@MF6E& z@ y .!Go"S򁖃zCuU~ HGIc“AnҨ&2Ӓr ̉^23ID4v\Ћ%L 7?P2!,cTNG^u2fmp}xo1uuL_DD}"0:3 cHNNEB%2'pcv%-i_!zK <,du5g0uy1Ȱl ILcF]%Q!@Dm  z$ }!v4-nåD6 v-$RQ퀭Dna$82ZNM [1㏣Ip2Yp=d  5>9#eѡ$w#WQX2ΈXlK ksB7mpv^_Pj-#ӳ-1?N^N jA}Ȏ='Ԏ֜1xGx32ph qhmB %c,!\[l;CI ΗdB"NH*BY۬;l$pF(+$#͈hT.i?a4ؘsOyXf3ygH"%KQ5ӷ3xzvqE/|Ĉ[K 8PZ"0da9)c21 d?<."URÀw"Da)b >ŝۚSYXY ,BIA[e% KVgfqоz{hud4k طa…mfadjmW6"ȈބAĘ<.AA`/ iFMaInC;]pcUW"AEIv(c{[𪟊 /@G#Q/ ?x1[Eǧy>,G or Dw "_8M$(_v~ %Cl"VB퐧 _н zA>,+q[&I#F%A],H,! ^e~ \lii &u_Jn?[Zn t4.9p H0=pcr6xrVHYYiOA< >`/m,7Yq]:,\ŎV 8;O ثvl.Rmm@غÀbA'3dJf_Y>¦_UWi/m o$`Ao?NJ>{=&xM~ BGr@mG#AUڴ!7i)0.頑/ԅSG"mY~Cdҁl⊪pv(.XxӧݻSm`Aӗ8 YcIW?rİt^~yyAweD8d.яJMN{TfT40e{'%Z'Ix+Eb@L hD{# DDb.`#]dIIv04&=!Ղ^Dϭ{vIOI5F> ]>]&ӔdL lal",Ʋ (nZEhxm(Z 1ԉ H<z5Y^01G3U9?.SH{x3RmbMR>Rx㡝"θIG" nlP;Tٲ=Uu]G(WELF8{/I}OJ-ayoIl$ĪUUy6CU8x9~lC@E|^~Nc谳 ,- 2 бH$,%SvgfAk ]џdC {6,줇Du$}iviEw @%g#"<]|,bi`l~!%XLͫeۜppnN0b |BP@LĎX`qGr8 [&]9J5Pdu>Q }?lx \z.Ñ +oi]KgvtZKD2ρ FV1D1uwBQHBF|2CD"o^XM$8ΗKN #t~*h!&D0 w ؾZa!̲Ab:a""'A|<d=[m!,r0B4e[O" 0'GtS [5٣8؞i`/[^^TxeOT  W@ qPk&(6u+3;2`᷎A~8l3yrwe@YC)KLAOBׅ-,Rn aD Hq_Z{hH1g852q x Hdp lG%{<`$,b[Xf!lu^ аl,R9$LKt!鍴H[Q +YxBOrcGX9txWlgh }@ao[.iG]I "\VNNϭ*6FFu(=U ?\#[nV `]osRl8a.,˽7wrڧ!a&p dÉN' ^#FbN;vг ]lbD7]_i:&`~-aatv1=Ȑl kiUK&$`krr `tN(Į=bHѱ]4N=0BQml7Y~~exE.dlX`F*l¸wv/Wu6xGP#Sav=k&-dw @aHPD u5mF7/~-3"t"1K K*E,1e!_dHB BMC FЌA^oF:C7^XZ#t;SA+#^^tˋlX[ `8ybGaö8Y*lJR%rnPYܢ,.xzwVxeLv9[l0 `PX@V;2h|՗ luRG iЎ6ߊpG ZY;6ύxjO? GFe^,,,c;Xu-il]FY{;aok~$[T igU,:H!HP%ke4Pi1 TaSxHIQS5q5 /ڐäv3b1ΰP H6؁# 3'@C$1 UIB,db11 IL DY*YvgGR0wdsą1vB#i mNܰm >L=<xgRi6H ( "J9td-[],Y9߂H97e Xh(UU_ d9xVfC"]7Ü1.:'6 PCIe8 FiJoj<{D=d52G-`>$,F:kϯ8}1`vI f0U)c!$ ;;!6KH3 * PQ0.eA6r4=A67^:/xgR- l$, I'м:H2ۇխ;[%0dIr_蟤`6A<1l(B@"r#`!1!;qosNepT ~*Ij 7 X[cn]_jW1yjRa㳻x= xj6FH4d1`H ,U Ņ .m2v!EB܄XCblInx{:HF1 at,w؇ ?LxIJw o d7Eb 02se6ݳA II.R@9p[7V7cNJt4~DD]$3\8bЅygp:>;px!`_jccҗ#ϤNzՋOb'&C.bC&N 1auVİnKaWT #'#T&FD:]?tjOي/S:]/mqDFR}(XY!ql wB ul!F`j0s BE YWZy KO6-N0cNٖ-fZ X60XPF(լ~cQ"?#oa= -Y¤7ct+8S $z{#F8i!4!VZ Ʋ!yՊm,Hb H"Hs*0c`1r'@ VgeR$pnrs7F"OǗuN VKpudq`p8f}\BI,r̳3,-zŶl{8f&xp $kݭR9t<$Kp4dxLsökk~ƾY2՟~p\G\ #Rǧx~^{t xmm䞡a0Ad ,$!qܘBH5+IWu!.\A 2j.]fwupd-1.9.16/contrib/qubes/doc/uefi_capsule_update.md000066400000000000000000000017371460375044200226260ustar00rootroot00000000000000# UEFI capsule update The qubes-fwupd handle the UEFI capsule update under several conditions. The fwupd uses ESRT tables to read GUID, and that causes trouble when the OS is running under a hypervisor. The Xen does not pass the ESRT tables to paravirtualized dom0, so the Qubes is not able to provide sysfs information. More information you can find it this thread: ## Requirements ### Qubes OS You need Qubes R4.1 to use the UEFI capsule update. ### Hardware Make sure that your hardware has available firmware updates in the [LVFS](https://fwupd.org/) ## UEFI capsule update - downgrade UEFI capsule updates and downgrades were tested on DELL XPS 15 9560. ```shell sudo qubes-fwupdmgr downgrade ``` ## UEFI capsule update - update ```shell sudo qubes-fwupdmgr update ``` ## Update process ### Capsule found ![img](img/uefi_capsule_found.jpg) ### ME updated ![img](img/uefi_ME.jpg) ### Success ![img](img/uefi_success.jpg) fwupd-1.9.16/contrib/qubes/doc/whonix.md000066400000000000000000000007441460375044200201310ustar00rootroot00000000000000# Whonix support The qubes-fwupd uses the sys-whonix VM as the update VM to handle downloading updates and metadata via Tor. The tests detect if sys-whonix is running, but do not check if you are connected with Tor. So before running the test make sure that sys-whonix has access to the network. ## Refresh ```shell sudo qubes-fwupdmgr refresh --whonix ``` ## Update ```shell sudo qubes-fwupdmgr update --whonix ``` ## Downgrade ```shell sudo qubes-fwupdmgr downgrade --whonix fwupd-1.9.16/contrib/qubes/meson.build000066400000000000000000000022171460375044200176650ustar00rootroot00000000000000install_data([ 'src/__init__.py', 'src/fwupd_receive_updates.py', 'src/qubes_fwupd_common.py', 'src/qubes_fwupd_heads.py', 'src/qubes_fwupd_update.py', 'src/qubes_fwupdmgr.py', ], install_dir: 'share/qubes-fwupd/src', ) install_data([ 'test/__init__.py', 'test/fwupd_logs.py', 'test/test_qubes_fwupd_heads.py', 'test/test_qubes_fwupdmgr.py', ], install_dir: 'share/qubes-fwupd/test', ) install_data([ 'test/logs/firmware.metainfo.xml', 'test/logs/get_devices.log', 'test/logs/get_updates.log', 'test/logs/help.log', ], install_dir: 'share/qubes-fwupd/test/logs', ) install_data([ 'src/vms/fwupd_common_vm.py', 'src/vms/fwupd_download_updates.py', ], install_dir: 'libexec/qubes-fwupd', install_mode: 'rwxrwxr-x', ) install_data([ 'test/logs/metainfo_name/firmware.metainfo.xml', ], install_dir: 'share/qubes-fwupd/test/logs/metainfo_name', ) install_data( 'test/logs/metainfo_version/firmware.metainfo.xml', install_dir: 'share/qubes-fwupd/test/logs/metainfo_version', ) install_symlink( 'qubes-fwupdmgr', pointing_to: '/usr/share/qubes-fwupd/src/qubes_fwupdmgr.py', install_dir: '/usr/sbin', ) fwupd-1.9.16/contrib/qubes/src/000077500000000000000000000000001460375044200163105ustar00rootroot00000000000000fwupd-1.9.16/contrib/qubes/src/__init__.py000066400000000000000000000000001460375044200204070ustar00rootroot00000000000000fwupd-1.9.16/contrib/qubes/src/fwupd_receive_updates.py000066400000000000000000000271721460375044200232470ustar00rootroot00000000000000#!/usr/bin/python3 # # The Qubes OS Project, http://www.qubes-os.org # # Copyright (C) 2010 Rafal Wojtczuk # 2020 Norbert Kamiński # # SPDX-License-Identifier: LGPL-2.1+ # import base64 import glob import hashlib import itertools import os import shutil import struct import subprocess import tempfile from qubes_fwupd_common import EXIT_CODES, create_dirs FWUPD_DOM0_DIR = "/var/cache/fwupd/qubes" FWUPD_DOM0_UPDATES_DIR = os.path.join(FWUPD_DOM0_DIR, "updates") FWUPD_DOM0_UNTRUSTED_DIR = os.path.join(FWUPD_DOM0_UPDATES_DIR, "untrusted") FWUPD_DOM0_METADATA_DIR = os.path.join(FWUPD_DOM0_DIR, "metadata") FWUPD_VM_DIR = "/home/user/.cache/fwupd" FWUPD_VM_UPDATES_DIR = os.path.join(FWUPD_VM_DIR, "updates") FWUPD_VM_METADATA_DIR = os.path.join(FWUPD_VM_DIR, "metadata") FWUPD_PKI = "/etc/pki/fwupd" FWUPD_PKI_PGP = "/etc/pki/fwupd/GPG-KEY-Linux-Vendor-Firmware-Service" FWUPD_DOWNLOAD_PREFIX = "https://fwupd.org/downloads/" HEADS_UPDATES_DIR = "/boot/updates" class FwupdReceiveUpdates: def _check_shasum(self, file_path, sha): """Compares computed SHA256 checksum with `sha` parameter. Keyword arguments: file_path -- absolute path to the file sha -- SHA256 checksum of the file """ with open(file_path, "rb") as f: c_sha = hashlib.sha256(f.read()).hexdigest() if c_sha != sha: self.clean_cache() raise ValueError(f"Computed checksum {c_sha} did NOT match {sha}.") def _jcat_verification(self, file_path, file_directory): """Verifies sha1 and sha256 checksum, GPG signature, and PKCS#7 signature. Keyword argument: file_path -- absolute path to jcat file file_directory -- absolute path to the directory to jcat file location """ assert file_path.startswith("/"), "bad file path {file_path!r}" cmd_jcat = ["jcat-tool", "verify", file_path, "--public-keys", FWUPD_PKI] p = subprocess.Popen( cmd_jcat, cwd=file_directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout, __ = p.communicate() verification = stdout.decode("utf-8") print(verification) if p.returncode != 0: self.clean_cache() raise Exception("jcat-tool: Verification failed") def _crc24(self, data): """Calculate CRC-24 Checksum calculation for PGP armored signature. This algorithm isn't available in Python standard library, but it's simple enough to implement here. It doesn't need to be fast, nor side-channel resistant. """ crc = 0xB704CE # crc24_init for b in data: crc ^= b << 16 for i in range(8): crc <<= 1 if crc & 0x1000000: crc ^= 0x1864CFB # crc24_poly return crc def _pgp_parse(self, signature_path): """Verifies if GPG signature is correctly formatted Verify if signature is well formed - sqv will verify if the signature itself correct, but may accept extra data in the signature packets that could later confuse GnuPG used by fwupd. Verify also armor checksum, as sqv doesn't do that. """ # sigparse expects binary format, so decode base64 first with open(signature_path, "rb") as sig: data = sig.read(4096) # arbitrary size limit if sig.read(1) != b"": raise Exception("pgp: signature too big") lines = data.splitlines() # format described in RFC-4880 ch 6 if lines[0:2] != [b"-----BEGIN PGP SIGNATURE-----", b""]: raise Exception("pgp: invalid header") if lines[-1] != b"-----END PGP SIGNATURE-----": raise Exception("pgp: invalid footer") checksum = lines[-2] if checksum[0] != ord("=") or len(checksum) != 5: raise Exception("pgp: invalid checksum format") base64_data = b"".join(lines[2:-2]) data = base64.b64decode(base64_data, validate=True) crc = base64.b64decode(checksum[1:], validate=True) crc = struct.unpack(">I", b"\0" + crc)[0] if crc != self._crc24(data): raise Exception("pgp: invalid checksum") with tempfile.NamedTemporaryFile() as tmp: tmp.write(data) tmp.flush() p = subprocess.Popen(["sigparse", tmp.name], stderr=subprocess.PIPE) _, stderr = p.communicate() if p.returncode != 0: raise Exception("pgp: invalid signature format: " + stderr.decode()) # reconstruct armored data to ensure its canonical form with open(signature_path, "wb") as sig: sig.write(b"-----BEGIN PGP SIGNATURE-----\n") sig.write(b"\n") encoded = iter(base64.b64encode(data)) while line := bytes(itertools.islice(encoded, 76)): sig.write(line + b"\n") sig.write(checksum + b"\n") sig.write(b"-----END PGP SIGNATURE-----\n") def _pgp_verification(self, signature_path, file_path): """Verifies GPG signature. Keyword argument: signature_path -- absolute path to signature file file_path -- absolute path to the signed file location """ assert file_path.startswith("/"), "bad file path {file_path!r}" try: self._pgp_parse(signature_path) except Exception: self.clean_cache() raise cmd_verify = [ "sqv", "--keyring", FWUPD_PKI_PGP, "--", signature_path, file_path, ] p = subprocess.Popen(cmd_verify, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, __ = p.communicate() signature_key = stdout.decode("utf-8") if p.returncode != 0 or not signature_key: self.clean_cache() raise Exception("pgp: Verification failed") def _reconstruct_jcat(self, jcat_path, file_path): """Reconstruct jcat file from verified parts Currently included parts: .asc, .sha256 Hashes are generated locally if missing Arguments: jcat_path - absolute path to the output jcat file file_path - absolute path to the signed file """ # generate missing hashes hashes = ("sha256",) for hash_ext in hashes: hash_fname = f"{file_path}.{hash_ext}" if not os.path.exists(hash_fname): # TODO: switch to hashlib.file_digest (py3.11) with open(file_path, "rb") as f_data: hash_val = hashlib.new(hash_ext, f_data.read()).hexdigest() with open(hash_fname, "w") as f_hash: f_hash.write(hash_val) signatures = ("asc",) file_id = os.path.basename(file_path) for sign_type in signatures + hashes: sign_path = f"{file_path}.{sign_type}" if not os.path.exists(sign_path): raise Exception(f"Missing signature: {sign_path}") jcat_cmd = ["jcat-tool", "import", jcat_path, file_id, sign_path] subprocess.check_call(jcat_cmd) def handle_fw_update(self, updatevm, sha, filename): """Copies firmware update archives from the updateVM. Keyword arguments: updatevm -- update VM name sha -- SHA256 checksum of the firmware update archive filename -- name of the firmware update archive """ create_dirs(FWUPD_DOM0_UPDATES_DIR, FWUPD_DOM0_UNTRUSTED_DIR) with tempfile.TemporaryDirectory(dir=FWUPD_DOM0_UNTRUSTED_DIR) as tmpdir: dom0_firmware_untrusted_path = os.path.join(tmpdir, filename) updatevm_firmware_file_path = os.path.join(FWUPD_VM_UPDATES_DIR, filename) cmd_copy = [ "qvm-run", "--pass-io", "--no-gui", "-q", "-a", "--no-shell", "--", updatevm, "cat", "--", updatevm_firmware_file_path, ] with open(dom0_firmware_untrusted_path, "bx") as untrusted_file: p = subprocess.Popen(cmd_copy, stdout=untrusted_file, shell=False) p.wait() if p.returncode != 0: raise Exception("qvm-run: Copying firmware file failed!!") self._check_shasum(dom0_firmware_untrusted_path, sha) # jcat verification will be done by fwupd itself self.arch_name = filename self.arch_path = os.path.join(FWUPD_DOM0_UPDATES_DIR, filename) shutil.move(dom0_firmware_untrusted_path, self.arch_path) def handle_metadata_update(self, updatevm, metadata_url): """Copies metadata files from the updateVM. Keyword argument: updatevm -- update VM name """ metadata_name = os.path.basename(metadata_url) self.metadata_file = os.path.join(FWUPD_DOM0_METADATA_DIR, metadata_name) self.metadata_file_jcat = self.metadata_file + ".jcat" self.metadata_file_updatevm = os.path.join(FWUPD_VM_METADATA_DIR, metadata_name) create_dirs(FWUPD_DOM0_METADATA_DIR, FWUPD_DOM0_UNTRUSTED_DIR) with tempfile.TemporaryDirectory(dir=FWUPD_DOM0_UNTRUSTED_DIR) as tmpdir: cmd_copy_metadata_file = [ "qvm-run", "--pass-io", "--no-gui", "--no-shell", "--", updatevm, "cat", "--", self.metadata_file_updatevm, ] # TODO: switch to ed25519 once firmware.xml.gz.jcat will have it cmd_copy_metadata_file_signature = [ "qvm-run", "--pass-io", "--no-gui", "--no-shell", "--", updatevm, "cat", "--", self.metadata_file_updatevm + ".asc", ] untrusted_metadata_file = os.path.join(tmpdir, metadata_name) with open(untrusted_metadata_file, "bx") as untrusted_file_1, open( untrusted_metadata_file + ".asc", "bx" ) as untrusted_file_2, subprocess.Popen( cmd_copy_metadata_file, stdout=untrusted_file_1 ) as p, subprocess.Popen( cmd_copy_metadata_file_signature, stdout=untrusted_file_2 ) as q: p.wait() q.wait() if p.returncode != 0: raise Exception("qvm-run: Copying metadata file failed!!") if q.returncode != 0: raise Exception("qvm-run: Copying metadata signature failed!!") self._pgp_verification( untrusted_metadata_file + ".asc", untrusted_metadata_file ) self._reconstruct_jcat( untrusted_metadata_file + ".jcat", untrusted_metadata_file ) # verified, move into trusted dir shutil.move(untrusted_metadata_file, self.metadata_file) shutil.move(untrusted_metadata_file + ".jcat", self.metadata_file_jcat) def clean_cache(self): """Removes updates data""" print("Cleaning dom0 cache directories") if os.path.exists(FWUPD_DOM0_METADATA_DIR): shutil.rmtree(FWUPD_DOM0_METADATA_DIR) if os.path.exists(FWUPD_DOM0_UPDATES_DIR): shutil.rmtree(FWUPD_DOM0_UPDATES_DIR) if os.path.exists(HEADS_UPDATES_DIR): shutil.rmtree(HEADS_UPDATES_DIR) fwupd-1.9.16/contrib/qubes/src/qubes_fwupd_common.py000066400000000000000000000071361460375044200225650ustar00rootroot00000000000000import grp import re import os EXIT_CODES = {"ERROR": 1, "SUCCESS": 0, "NOTHING_TO_DO": 2} WARNING_COLOR = "\033[93m" def create_dirs(*args): """Method creates directories. Keyword arguments: *args -- paths to be created """ qubes_gid = grp.getgrnam("qubes").gr_gid old_umask = os.umask(0o002) if args is None: raise Exception("Creating directories failed, no paths given.") for file_path in args: if not os.path.exists(file_path): os.makedirs(file_path) os.chown(file_path, -1, qubes_gid) elif os.stat(file_path).st_gid != qubes_gid: print( f"{WARNING_COLOR}Warning: You should move a personal files" f" from {file_path}. Cleaning cache will cause lose of " f"the personal data!!{WARNING_COLOR}" ) os.umask(old_umask) class LooseVersion: """Version numbering for anarchists and software realists. Implements the standard interface for version number classes as described above. A version number consists of a series of numbers, separated by either periods or strings of letters. When comparing version numbers, the numeric components will be compared numerically, and the alphabetic components lexically. The following are all valid version numbers, in no particular order: 1.5.1 1.5.2b2 161 3.10a 8.02 3.4j 1996.07.12 3.2.pl0 3.1.1.6 2g6 11g 0.960923 2.2beta29 1.13++ 5.5.kw 2.0b1pl0 In fact, there is no such thing as an invalid version number under this scheme; the rules for comparison are simple and predictable, but may not always give the results you want (for some definition of "want"). """ component_re = re.compile(r"(\d+ | [a-z]+ | \.)", re.VERBOSE) def __init__(self, vstring=None): if vstring: self.parse(vstring) def parse(self, vstring): # I've given up on thinking I can reconstruct the version string # from the parsed tuple -- so I just store the string here for # use by __str__ self.vstring = vstring components = [x for x in self.component_re.split(vstring) if x and x != "."] for i, obj in enumerate(components): try: components[i] = int(obj) except ValueError: pass self.version = components def __str__(self): return self.vstring def __repr__(self): return f"LooseVersion ('{str(self)}')" def _cmp(self, other): if isinstance(other, str): other = LooseVersion(other) elif not isinstance(other, LooseVersion): return NotImplemented if self.version == other.version: return 0 if self.version < other.version: return -1 if self.version > other.version: return 1 def __eq__(self, other): c = self._cmp(other) if c is NotImplemented: return c return c == 0 def __lt__(self, other): c = self._cmp(other) if c is NotImplemented: return c return c < 0 def __le__(self, other): c = self._cmp(other) if c is NotImplemented: return c return c <= 0 def __gt__(self, other): c = self._cmp(other) if c is NotImplemented: return c return c > 0 def __ge__(self, other): c = self._cmp(other) if c is NotImplemented: return c return c >= 0 fwupd-1.9.16/contrib/qubes/src/qubes_fwupd_heads.py000066400000000000000000000122761460375044200223620ustar00rootroot00000000000000#!/usr/bin/python3 # # The Qubes OS Project, http://www.qubes-os.org # # Copyright (C) 2021 Norbert Kamiński # # SPDX-License-Identifier: LGPL-2.1+ # import subprocess import tempfile import os import shutil import xml.etree.ElementTree as ET from packaging.version import Version from qubes_fwupd_common import EXIT_CODES, create_dirs, LooseVersion FWUPDTOOL = "/bin/fwupdtool" BOOT = "/boot" HEADS_UPDATES_DIR = os.path.join(BOOT, "updates") class FwupdHeads: def _get_hwids(self): cmd_hwids = [FWUPDTOOL, "hwids"] p = subprocess.Popen(cmd_hwids, stdout=subprocess.PIPE) self.dom0_hwids_info = p.communicate()[0].decode() if p.returncode != 0: raise Exception("fwupd-qubes: Getting hwids info failed") def _gather_firmware_version(self): """ Checks if Qubes works under heads """ if "Heads" in self.dom0_hwids_info: self.heads_version = None hwids = self.dom0_hwids_info.split("\n") for line in hwids: if "Heads" in line: self.heads_version = line.split("Heads-v")[1] else: print("Device is not running under the heads firmware!!") print("Exiting...") return EXIT_CODES["NOTHING_TO_DO"] def _get_hwid_device(self): """ Device model for Heads update, currently supports ThinkPad only. """ for line in self.dom0_hwids_info.splitlines(): if line.startswith("Family: ThinkPad"): return line.split(":", 1)[1].split(" ", 1)[1].lower() return None def _parse_metadata(self, metadata_file): """ Parse metadata info. """ metadata_ext = os.path.splitext(metadata_file)[-1] if metadata_ext == ".xz": cmd_metadata = ["xzcat", metadata_file] elif metadata_ext == ".gz": cmd_metadata = ["zcat", metadata_file] else: raise NotImplementedError( "Unsupported metadata compression " + metadata_ext ) p = subprocess.Popen(cmd_metadata, stdout=subprocess.PIPE) self.metadata_info = p.communicate()[0].decode() if p.returncode != 0: raise Exception("fwupd-qubes: Parsing metadata failed") def _parse_heads_updates(self, device): """ Parses heads updates info. Keyword arguments: device -- Model of the updated device """ self.heads_update_url = None self.heads_update_sha = None self.heads_update_version = None heads_metadata_info = None root = ET.fromstring(self.metadata_info) for component in root.findall("component"): if f"heads.{device}" in component.find("id").text: heads_metadata_info = component if not heads_metadata_info: print("No metadata info for chosen board") return EXIT_CODES["NOTHING_TO_DO"] for release in heads_metadata_info.find("releases").findall("release"): release_ver = release.get("version") if self.heads_version == "heads" or LooseVersion( release_ver ) > LooseVersion(self.heads_version): if not self.heads_update_version or LooseVersion( release_ver ) > LooseVersion(self.heads_update_version): self.heads_update_url = release.find("location").text for sha in release.findall("checksum"): if ( ".cab" in sha.attrib["filename"] and sha.attrib["type"] == "sha256" ): self.heads_update_sha = sha.text self.heads_update_version = release_ver if self.heads_update_url: return EXIT_CODES["SUCCESS"] else: print("Heads firmware is up to date.") return EXIT_CODES["NOTHING_TO_DO"] def _copy_heads_firmware(self, arch_path): """ Copies heads update to the boot path """ heads_boot_path = os.path.join(HEADS_UPDATES_DIR, self.heads_update_version) heads_update_path = os.path.join(heads_boot_path, "firmware.rom") create_dirs(HEADS_UPDATES_DIR) if os.path.exists(heads_update_path): print(f"Heads Update == {self.heads_update_version} " "already downloaded.") return EXIT_CODES["NOTHING_TO_DO"] else: os.mkdir(heads_boot_path) with tempfile.TemporaryDirectory() as tmpdir: cmd_extract = ["gcab", "-x", f"--directory={tmpdir}", "--", arch_path] p = subprocess.Popen(cmd_extract, stdout=subprocess.PIPE) p.communicate() if p.returncode != 0: raise Exception(f"gcab: Error while extracting {arch_path}.") update_path = os.path.join(tmpdir, "firmware.rom") shutil.copyfile(update_path, heads_update_path) print( f"Heads Update == {self.heads_update_version} " f"available at {heads_boot_path}" ) return EXIT_CODES["SUCCESS"] fwupd-1.9.16/contrib/qubes/src/qubes_fwupd_update.py000066400000000000000000000101731460375044200225520ustar00rootroot00000000000000#!/usr/bin/python3 # # The Qubes OS Project, http://www.qubes-os.org # # Copyright (C) 2021 Norbert Kaminski # # SPDX-License-Identifier: LGPL-2.1+ # import os import re import shlex import subprocess from qubes_fwupd_common import create_dirs FWUPD_DOM0_DIR = "/var/cache/fwupd/qubes" FWUPD_VM_DOWNLOAD = "/usr/libexec/qubes-fwupd/fwupd_download_updates.py" FWUPD_DOM0_UPDATES_DIR = os.path.join(FWUPD_DOM0_DIR, "updates") FWUPD_DOWNLOAD_PREFIX = "https://fwupd.org/downloads/" SPECIAL_CHAR_REGEX = re.compile(r"%20|&|\||#") UPDATEVM_REGEX = re.compile(r"^sys-") run_cmd = ( "qvm-run", "--pass-io", "--no-gui", "--no-shell", "-q", "-a", "--filter-escape-chars", "--color-output=31", "--color-stderr=31", "--", ) def run_in_tty(updatevm, args, **kwargs): return subprocess.check_call( ( *run_cmd, updatevm, *args, ), stdin=subprocess.DEVNULL, **kwargs, ) class FwupdUpdate: def _specify_updatevm(self): cmd_updatevm = ["qubes-prefs", "--force-root", "updatevm"] p = subprocess.Popen(cmd_updatevm, stdout=subprocess.PIPE) self.updatevm = p.communicate()[0].decode().split("\n")[0] if p.returncode != 0 and not UPDATEVM_REGEX.match(self.updatevm): self.updatevm = None raise Exception("Specifying updatevm failed") def _check_updatevm(self): """Checks if updatevm is running""" cmd_xl_list = ["xl", "list", "--", self.updatevm] p = subprocess.Popen( cmd_xl_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) output = p.communicate()[0].decode() return p.returncode == 0 def download_metadata(self, whonix=False, metadata_url=None): """Initialize downloading metadata files. Keywords arguments: whonix -- Flag enforces downloading the metadata updates via Tor metadata_url -- Download metadata from the custom url """ if not whonix: self._specify_updatevm() else: self.updatevm = "sys-whonix" if not self._check_updatevm(): raise Exception(f"{self.updatevm} is not running!!") if not os.path.exists(FWUPD_DOM0_DIR): create_dirs(FWUPD_DOM0_DIR) cmd_metadata = [FWUPD_VM_DOWNLOAD, "--metadata"] if metadata_url: cmd_metadata.append("--url=" + metadata_url) try: run_in_tty(self.updatevm, cmd_metadata) except subprocess.CalledProcessError: raise Exception("Metadata download failed.") def download_firmware_updates(self, url, sha, whonix=False): """Initializes downloading firmware update archive. Keywords arguments: url -- url path to the firmware update archive sha -- SHA256 checksum of the firmware update archive whonix -- Flag enforces downloading the updates via Tor """ if not whonix: self._specify_updatevm() else: self.updatevm = "sys-whonix" if not self._check_updatevm(): raise Exception(f"{self.updatevm} is not running!!") if not os.path.exists(FWUPD_DOM0_DIR): create_dirs(FWUPD_DOM0_DIR) self.arch_name = os.path.basename(url) self.arch_path = os.path.join(FWUPD_DOM0_UPDATES_DIR, self.arch_name) if not os.path.exists(self.arch_path): cmd_firmware_download = [ "qvm-run", "--pass-io", "--quiet", "--autostart", "--no-shell", "--color-output=31", "--color-stderr=31", "--", self.updatevm, FWUPD_VM_DOWNLOAD, f"--url={url}", f"--sha={sha}", ] p = subprocess.Popen(cmd_firmware_download, stdin=subprocess.DEVNULL) p.wait() if p.returncode != 0: raise Exception("Firmware download failed.") else: self.cached = True print("Firmware already downloaded. Using cached files.") fwupd-1.9.16/contrib/qubes/src/qubes_fwupdmgr.py000077500000000000000000000665331460375044200217340ustar00rootroot00000000000000#!/usr/bin/python3 # # The Qubes OS Project, http://www.qubes-os.org # # Copyright (C) 2021 Norbert Kaminski # # SPDX-License-Identifier: LGPL-2.1+ # import json import os import re import shutil import subprocess import tempfile import sys import xml.etree.ElementTree as ET from pathlib import Path from packaging import version as pversion FWUPD_QUBES_DIR = "/usr/share/qubes-fwupd" # Check if script is run by tests and append sys path properly if __name__ == "__main__": sys.path.append(os.path.join(FWUPD_QUBES_DIR, "src")) else: sys.path.append("./src") try: from qubes_fwupd_heads import FwupdHeads from qubes_fwupd_update import FwupdUpdate, run_in_tty from fwupd_receive_updates import FwupdReceiveUpdates from qubes_fwupd_common import EXIT_CODES, create_dirs except ModuleNotFoundError: raise ModuleNotFoundError( "qubes-fwupd modules not found. You may need to reinstall package." ) FWUPD_DOM0_DIR = "/var/cache/fwupd/qubes" FWUPD_DOM0_METADATA_DIR = os.path.join(FWUPD_DOM0_DIR, "metadata") FWUPD_DOM0_UPDATES_DIR = os.path.join(FWUPD_DOM0_DIR, "updates") FWUPD_DOWNLOAD_PREFIX = "https://fwupd.org/downloads/" METADATA_URL = "https://fwupd.org/downloads/firmware.xml.xz" METADATA_URL_JCAT = "https://fwupd.org/downloads/firmware.xml.xz.jcat" FWUPDMGR = "/bin/fwupdmgr" BIOS_UPDATE_FLAG = os.path.join(FWUPD_DOM0_DIR, "bios_update") LVFS_TESTING_DOM0_FLAG = os.path.join(FWUPD_DOM0_DIR, "lvfs_testing") HELP = { "Usage": [ { "Command": "qubes-fwupdmgr [OPTION…][FLAG..]", "Example": "qubes-fwupdmgr refresh --whonix --url=\n", } ], "Options": [ { "get-devices": "Get all devices that support firmware updates", "get-updates": "Get the list of updates for connected hardware", "refresh": "Refresh metadata from remote server", "update": "Update chosen device to latest firmware version", "update-heads": "Updates heads firmware to the latest version (EXPERIMENTAL, not fully supported yet)", "downgrade": "Downgrade chosen device to chosen firmware version", "clean": "Delete all cached update files\n", } ], "Flags": [ { "--whonix": "Download firmware updates via Tor", "--device": "Specify device for heads update (default - x230)", "--url": "Address of the custom metadata remote server\n", } ], "Help": [{"-h --help": "Show help options\n"}], } class QubesFwupdmgr(FwupdHeads, FwupdUpdate, FwupdReceiveUpdates): def _download_metadata(self, whonix=False, metadata_url=None): """Initialize downloading metadata files. Keywords arguments: whonix -- Flag enforces downloading the metadata updates via Tor metadata_url -- Download metadata from the custom url """ if not metadata_url: raise Exception("missing metadata URL") self.download_metadata(whonix=whonix, metadata_url=metadata_url) self.handle_metadata_update(self.updatevm, metadata_url=metadata_url) if not os.path.exists(self.metadata_file): raise FileNotFoundError("Metadata file does not exist") def get_remotes(self): """Get metadata URLs for all enabled remotes""" if hasattr(self, "_remotes_cache"): return self._remotes_cache remotes_json = subprocess.check_output( [ FWUPDMGR, "get-remotes", "--json", ] ).decode() remotes_list = json.loads(remotes_json)["Remotes"] remotes = {} for remote in remotes_list: name = remote["Id"] # skip disabled if remote.get("Enabled", "true") != "true": continue # skip local - for metadata refresh, we only care about those # actually needing refreshing if remote.get("Kind") != "download": continue # skip unsupported keyring kind if remote.get("KeyringKind") not in ("jcat",): print( "Skipping remote '{}' due to unsupported keyring type '{}'".format( name, remote.get("KeyringKind") ) ) assert "MetadataUri" in remote remotes[name] = remote["MetadataUri"] self._remotes_cache = remotes return self._remotes_cache def refresh_metadata(self, whonix=False, metadata_url=None, remote_name=None): """Updates metadata with downloaded files. Keyword arguments: whonix -- Flag enforces downloading the metadata updates via Tor metadata_url -- Use custom metadata from the url remote_name -- Set refreshed metadata to this remote """ if not metadata_url: if remote_name: metadata_url = self.get_remotes()[remote_name] else: raise Exception("missing metadata URL") metadata_name = os.path.basename(metadata_url) self.metadata_file = os.path.join(FWUPD_DOM0_METADATA_DIR, metadata_name) self.metadata_file_jcat = self.metadata_file + ".jcat" if not remote_name: if "testing" in metadata_url: remote_name = "lvfs-testing" else: remote_name = "lvfs" self._download_metadata(whonix=whonix, metadata_url=metadata_url) cmd_refresh = [ FWUPDMGR, "refresh", self.metadata_file, self.metadata_file_jcat, remote_name, ] p = subprocess.Popen(cmd_refresh, stdout=subprocess.PIPE) output = p.communicate()[0].decode() print(output) if p.returncode != 0: raise Exception("fwupd-qubes: Refresh failed") if not output != "Successfully refreshed metadata manually": raise Exception("Manual metadata refresh failed!!!") def refresh_metadata_all(self, whonix=False): """Refresh metadata for all 'download' remotes Keyword arguments: whonix -- Flag enforces downloading the metadata updates via Tor """ for name, url in self.get_remotes().items(): try: self.refresh_metadata(whonix=whonix, remote_name=name, metadata_url=url) except Exception as e: print(f"Failed to refresh remote '{name}': {e}") def _get_dom0_updates(self): """Gathers information about available updates.""" cmd_get_dom0_updates = [FWUPDMGR, "--json", "get-updates"] p = subprocess.Popen(cmd_get_dom0_updates, stdout=subprocess.PIPE) self.dom0_updates_info = p.communicate()[0].decode() if p.returncode != 0 and p.returncode != 2: raise Exception("fwupd-qubes: Getting available updates failed") def _parse_dom0_updates_info(self, updates_info): """Creates dictionary and list with information about updates. Keywords argument: updates_info - gathered update information """ self.dom0_updates_info_dict = json.loads(updates_info) self.dom0_updates_list = [ { "Name": device["Name"], "Version": device["Version"], "Releases": [ { "Version": update["Version"], "Url": update["Uri"], "Checksum": update["Checksum"][-1], "Description": update["Description"], } for update in device["Releases"] ], } for device in self.dom0_updates_info_dict["Devices"] ] def _download_firmware_updates(self, url, sha, whonix=False): """Initializes downloading firmware update archive. Keywords arguments: url -- url path to the firmware update archive sha -- SHA256 checksum of the firmware update archive whonix -- Flag enforces downloading the updates via Tor """ self.cached = False self.download_firmware_updates(url, sha, whonix=whonix) if not self.cached: self.handle_fw_update(self.updatevm, sha, self.arch_name) if not os.path.exists(self.arch_path): raise FileNotFoundError("Firmware update files do not exist") def _user_input(self, updates_list, downgrade=False): """UI for update process. Keywords arguments: updates_dict - list of updates for specified device downgrade -- downgrade flag """ decorator = "======================================================" if len(updates_list) == 0: print("No updates available.") return -EXIT_CODES["NOTHING_TO_DO"] if downgrade: print("Available downgrades:") else: print("Available updates:") self._updates_crawler(updates_list) while True: try: print("If you want to abandon process press 'N'.") choice = input("Otherwise choose a device number: ") if choice == "N" or choice == "n": return -EXIT_CODES["NOTHING_TO_DO"] device_num = int(choice) - 1 if 0 <= device_num < len(updates_list): if not downgrade: return device_num break else: raise ValueError() except ValueError: print("Invalid choice.") if downgrade: while True: try: releases = updates_list[device_num]["Releases"] for i, fw_dngd in enumerate(releases): print(decorator) print( f" {i+1}. Firmware downgrade version:" f"\t {fw_dngd['Version']}" ) description = fw_dngd["Description"].replace("

", "") description = description.replace("

  • ", "") description = description.replace("
      ", "") description = description.replace("
    ", "") description = description.replace("

    ", "\n ") description = description.replace("
  • ", "\n ") print(f" Description:{description}") print("If you want to abandon downgrade process press N.") choice = input("Otherwise choose downgrade number: ") if choice == "N" or choice == "n": return -EXIT_CODES["NOTHING_TO_DO"] downgrade_num = int(choice) - 1 if 0 <= downgrade_num < len(releases): return device_num, downgrade_num else: raise ValueError() except ValueError: print("Invalid choice.") def _parse_parameters(self, updates_list, choice): """Parses device name, url, version and SHA256 checksum of the file list. Keywords arguments: updates_list - list of updates for dom0 choice -- number of device to be updated """ self.name = updates_list[choice]["Name"] self.version = updates_list[choice]["Releases"][0]["Version"] for ver_check in updates_list[choice]["Releases"]: if pversion.parse(ver_check["Version"]) >= pversion.parse(self.version): self.version = ver_check["Version"] self.url = ver_check["Url"] self.sha = ver_check["Checksum"] def _install_dom0_firmware_update(self, arch_path): """Installs firmware update for specified device in dom0. Keywords arguments: arch_path - absolute path to firmware update archive """ cmd_install = [FWUPDMGR, "install", arch_path] p = subprocess.Popen(cmd_install) p.wait() if p.returncode != 0: raise Exception("fwupd-qubes: Firmware update failed") def _read_dmi(self): """Reads BIOS information from DMI.""" cmd_dmidecode_version = ["dmidecode", "-s", "bios-version"] p = subprocess.Popen(cmd_dmidecode_version, stdout=subprocess.PIPE) p.wait() self.dmi_version = p.communicate()[0].decode() cmd_dmidecode = ["dmidecode", "-t", "bios"] p = subprocess.Popen(cmd_dmidecode, stdout=subprocess.PIPE) p.wait() if p.returncode != 0: raise Exception("dmidecode: Reading DMI failed") return p.communicate()[0].decode() def _verify_dmi(self, arch_path, version, downgrade=False): """Verifies DMI tables for BIOS updates. Keywords arguments: arch_path -- absolute path of the update archive version -- version of the update downgrade -- downgrade flag """ dmi_info = self._read_dmi() with tempfile.TemporaryDirectory() as tmpdir: cmd_extract = ["gcab", "-x", f"--directory={tmpdir}", "--", arch_path] p = subprocess.Popen(cmd_extract, stdout=subprocess.PIPE) p.communicate() if p.returncode != 0: raise Exception(f"gcab: Error while extracting {arch_path}.") path_metainfo = os.path.join(tmpdir, "firmware.metainfo.xml") tree = ET.parse(path_metainfo) root = tree.getroot() vendor = root.find("developer_name").text if vendor is None: raise ValueError("No vendor information in firmware metainfo.") if vendor not in dmi_info: raise ValueError("Wrong firmware provider.") if not downgrade and pversion.parse(version) <= pversion.parse( self.dmi_version ): raise ValueError(f"{version} < {self.dmi_version} Downgrade not allowed") def _get_dom0_devices(self): """Gathers information about devices connected in dom0.""" cmd_get_dom0_devices = [FWUPDMGR, "--json", "get-devices"] p = subprocess.Popen(cmd_get_dom0_devices, stdout=subprocess.PIPE) self.dom0_devices_info = p.communicate()[0].decode() if p.returncode != 0: raise Exception("fwupd-qubes: Getting devices info failed") def update_firmware(self, whonix=False): """Updates firmware of the specified device. Keyword arguments: whonix -- Flag enforces downloading the metadata updates via Tor """ self._get_dom0_updates() self._parse_dom0_updates_info(self.dom0_updates_info) updates_list = self.dom0_updates_list ret_input = self._user_input(updates_list) if ret_input == -EXIT_CODES["NOTHING_TO_DO"]: exit(EXIT_CODES["NOTHING_TO_DO"]) choice = ret_input self._parse_parameters(updates_list, choice) self._download_firmware_updates(self.url, self.sha, whonix=whonix) if self.name == "System Firmware": Path(BIOS_UPDATE_FLAG).touch(mode=0o644, exist_ok=True) self._verify_dmi(self.arch_path, self.version) self._install_dom0_firmware_update(self.arch_path) def _parse_downgrades(self, device_list): """Parses information about possible downgrades. Keywords argument: device_list -- list of connected devices """ downgrades = [] if "No detected devices" in device_list: return downgrades dom0_devices_info_dict = json.loads(device_list) for device in dom0_devices_info_dict["Devices"]: if "Releases" in device: try: version = device["Version"] except KeyError: continue downgrades.append( { "Name": device["Name"], "Version": device["Version"], "Releases": [ { "Version": downgrade["Version"], "Description": downgrade["Description"], "Url": downgrade["Uri"], "Checksum": downgrade["Checksum"][-1], } for downgrade in device["Releases"] if pversion.parse(downgrade["Version"]) < pversion.parse(version) ], } ) return downgrades def _install_dom0_firmware_downgrade(self, arch_path): """Installs firmware downgrade for specified device. Keywords arguments: arch_path - absolute path to firmware downgrade archive """ cmd_install = [FWUPDMGR, "--allow-older", "install", arch_path] p = subprocess.Popen(cmd_install) p.wait() if p.returncode != 0: raise Exception("fwupd-qubes: Firmware downgrade failed") def downgrade_firmware(self, whonix=False): """Downgrades firmware of the specified device. Keyword arguments: whonix -- Flag enforces downloading the metadata updates via Tor """ self._get_dom0_devices() dom0_downgrades = self._parse_downgrades(self.dom0_devices_info) ret_input = self._user_input(dom0_downgrades, downgrade=True) if ret_input == -EXIT_CODES["NOTHING_TO_DO"]: exit(EXIT_CODES["NOTHING_TO_DO"]) device_choice, downgrade_choice = ret_input downgrade = dom0_downgrades[device_choice] releases = downgrade["Releases"] downgrade_url = releases[downgrade_choice]["Url"] downgrade_sha = releases[downgrade_choice]["Checksum"] self._download_firmware_updates(downgrade_url, downgrade_sha, whonix=whonix) if downgrade["Name"] == "System Firmware": Path(BIOS_UPDATE_FLAG).touch(mode=0o644, exist_ok=True) self._verify_dmi( self.arch_path, downgrade["Version"], downgrade=True, ) self._install_dom0_firmware_downgrade(self.arch_path) def _output_crawler(self, updev_dict, level, help_f=False): """Prints device and updates information as a tree. Keywords arguments: updev_dict -- update/device information dictionary level -- level of the tree """ def _tabs(key_word): return key_word + "\t" * (4 - int(len(key_word) / 8)) decorator = "===================================" print(2 * decorator) for updev_key in updev_dict: style = "\t" * level output = style + _tabs(updev_key + ":") if len(updev_key) > 12: continue if updev_key == "Icons": continue if updev_key == "Releases": continue if updev_key == "Name": print(style + updev_dict["Name"]) print(2 * decorator) continue if isinstance(updev_dict[updev_key], str): print(output + updev_dict[updev_key]) elif isinstance(updev_dict[updev_key], int): print(output + str(updev_dict[updev_key])) elif isinstance(updev_dict[updev_key][0], str): for i, data in enumerate(updev_dict[updev_key]): if i == 0: print(output + "\u00B7" + data) continue print(style + _tabs(" ") + "\u00B7" + data) elif isinstance(updev_dict[updev_key][0], dict): if level == 0 and help_f is True: print(output) else: if level == 0: print(f"Dom0 {output}") for nested_dict in updev_dict[updev_key]: self._output_crawler(nested_dict, level + 1) def _updates_crawler(self, updates_list, prefix=0): """Prints updates information for dom0 Keywords arguments: updates_list -- list of devices updates prefix -- device number prefix """ available_updates = False decorator = "======================================================" print(decorator) print("Dom0 updates:") print(decorator) if len(updates_list) == 0: print("No updates available.") return EXIT_CODES["NOTHING_TO_DO"] else: for i, device in enumerate(updates_list): if len(device["Releases"]) == 0: continue if not available_updates: print("Available updates:") print(decorator) print("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^") print(f"{i+1+prefix}. Device: {device['Name']}") print(f" Current firmware version:\t {device['Version']}") for update in device["Releases"]: print(decorator) print(" Firmware update " f"version:\t {update['Version']}") print(f" URL:\t {update['Url']}") print(f" SHA256 checksum:\t {update['Checksum']}") description = update["Description"].replace("

    ", "") description = description.replace("

  • ", "") description = description.replace("
      ", "") description = description.replace("
    ", "") description = description.replace("

    ", "\n\t") description = description.replace("
  • ", "\n\t") print(f" Description: {description}") print(decorator) available_updates = True if not available_updates: print("No updates available.") return EXIT_CODES["NOTHING_TO_DO"] def get_devices_qubes(self): """Gathers and prints devices information.""" self._get_dom0_devices() dom0_devices_info_dict = json.loads(self.dom0_devices_info) self._output_crawler(dom0_devices_info_dict, 0) def get_updates_qubes(self): """Gathers and prints updates information.""" self._get_dom0_updates() self._parse_dom0_updates_info(self.dom0_updates_info) self._updates_crawler(self.dom0_updates_list) def help(self): """Prints help information""" self._output_crawler(HELP, 0, help_f=True) def trusted_cleanup(self): """Deletes trusted directory.""" trusted_path = os.path.join(FWUPD_DOM0_UPDATES_DIR, "trusted.cab") if os.path.exists(trusted_path): os.remove(trusted_path) shutil.rmtree(trusted_path.replace(".cab", "")) def refresh_metadata_after_bios_update(self): """Refreshes metadata after bios update""" if os.path.exists(BIOS_UPDATE_FLAG): print("BIOS was updated. Refreshing metadata...") if "--whonix" in sys.argv: self.refresh_metadata_all(whonix=True) else: self.refresh_metadata_all() os.remove(BIOS_UPDATE_FLAG) def heads_update(self, device=None, whonix=False, metadata_url=None): """ Updates heads firmware Keyword arguments: device -- Model of the updated device whonix -- Flag enforces downloading the metadata updates via Tor metadata_url -- Use custom metadata from the url """ if not metadata_url: metadata_url = METADATA_URL metadata_name = os.path.basename(metadata_url) self.metadata_file = os.path.join(FWUPD_DOM0_METADATA_DIR, metadata_name) self._get_hwids() if device is None: device = self._get_hwid_device() self._download_metadata(whonix=whonix, metadata_url=metadata_url) self._parse_metadata(self.metadata_file) if self._gather_firmware_version() == EXIT_CODES["NOTHING_TO_DO"]: return EXIT_CODES["NOTHING_TO_DO"] if self._parse_heads_updates(device) == EXIT_CODES["NOTHING_TO_DO"]: return EXIT_CODES["NOTHING_TO_DO"] self._download_firmware_updates(self.heads_update_url, self.heads_update_sha) return_code = self._copy_heads_firmware(self.arch_path) if return_code == EXIT_CODES["NOTHING_TO_DO"]: exit(EXIT_CODES["NOTHING_TO_DO"]) elif return_code == EXIT_CODES["SUCCESS"]: print() while True: try: print("An update requires a reboot to complete.") choice = input("Do you want to restart now? (Y|N)\n") if choice == "N" or choice == "n": return EXIT_CODES["SUCCESS"] elif choice == "Y" or choice == "y": print("Rebooting...") os.system("reboot") else: raise ValueError() except ValueError: print("Invalid choice.") else: raise Exception("Copying heads update failed!!") def validate_dom0_dirs(self): """Validates and creates directories""" if not os.path.exists(FWUPD_DOM0_DIR): create_dirs(FWUPD_DOM0_DIR) if os.path.exists(FWUPD_DOM0_METADATA_DIR): shutil.rmtree(FWUPD_DOM0_METADATA_DIR) create_dirs(FWUPD_DOM0_METADATA_DIR) else: create_dirs(FWUPD_DOM0_METADATA_DIR) if not os.path.exists(FWUPD_DOM0_UPDATES_DIR): create_dirs(FWUPD_DOM0_UPDATES_DIR) def main(): if os.geteuid() != 0: print("You need to have root privileges to run this script.\n") exit(EXIT_CODES["ERROR"]) q = QubesFwupdmgr() if len(sys.argv) < 2: q.help() exit(1) metadata_url = None device_override = None whonix = False for arg in sys.argv: if "--url=" in arg: metadata_url = arg.replace("--url=", "") if FWUPD_DOWNLOAD_PREFIX not in metadata_url: print( "Metadata must be stored in the Linux" " Vendor Firmware Service (https://fwupd.org/)" ) print("Exiting...") exit(1) if "--device=" in arg: device_override = arg.replace("--device=", "") if "--whonix" == arg: whonix = True q.validate_dom0_dirs() q.trusted_cleanup() q.refresh_metadata_after_bios_update() if not os.path.exists(FWUPD_DOM0_DIR): if metadata_url: q.refresh_metadata(whonix=whonix, metadata_url=metadata_url) else: q.refresh_metadata_all(whonix=whonix) if sys.argv[1] == "get-updates": q.get_updates_qubes() elif sys.argv[1] == "get-devices": q.get_devices_qubes() elif sys.argv[1] == "update": q.update_firmware(whonix=whonix) elif sys.argv[1] == "downgrade": q.downgrade_firmware(whonix=whonix) elif sys.argv[1] == "clean": q.clean_cache() elif sys.argv[1] == "refresh": if metadata_url: q.refresh_metadata(whonix=whonix, metadata_url=metadata_url) else: q.refresh_metadata_all(whonix=whonix) elif sys.argv[1] == "update-heads": q.heads_update(device=device_override, metadata_url=metadata_url, whonix=whonix) else: q.help() exit(1) if __name__ == "__main__": main() fwupd-1.9.16/contrib/qubes/src/vms/000077500000000000000000000000001460375044200171155ustar00rootroot00000000000000fwupd-1.9.16/contrib/qubes/src/vms/fwupd_common_vm.py000066400000000000000000000065631460375044200227000ustar00rootroot00000000000000#!/usr/bin/python3 # # The Qubes OS Project, http://www.qubes-os.org # # Copyright (C) 2021 Norbert Kamiński # # SPDX-License-Identifier: LGPL-2.1+ # import grp import hashlib import os import shutil import subprocess FWUPD_VM_DIR = "/home/user/.cache/fwupd" FWUPD_VM_UPDATES_DIR = os.path.join(FWUPD_VM_DIR, "updates") FWUPD_VM_METADATA_DIR = os.path.join(FWUPD_VM_DIR, "metadata") WARNING_COLOR = "\033[93m" FWUPD_PKI = "/etc/pki/fwupd" class FwupdVmCommon: def _create_dirs(self, *args): """Method creates directories. Keyword arguments: *args -- paths to be created """ qubes_gid = grp.getgrnam("qubes").gr_gid self.old_umask = os.umask(0o002) if args is None: raise Exception("Creating directories failed, no paths given.") for file_path in args: if not os.path.exists(file_path): os.makedirs(file_path) os.chown(file_path, -1, qubes_gid) elif os.stat(file_path).st_gid != qubes_gid: print( f"{WARNING_COLOR}Warning: You should move a personal files" f" from {file_path}. Cleaning cache will cause lose of " f"the personal data!!{WARNING_COLOR}" ) def check_shasum(self, file_path, sha): """Compares computed SHA256 checksum with `sha` parameter. Keyword arguments: file_path -- absolute path to the file sha -- SHA256 checksum of the file """ with open(file_path, "rb") as f: c_sha = hashlib.sha256(f.read()).hexdigest() if c_sha != sha: self.clean_vm_cache() raise ValueError(f"Computed checksum {c_sha} did NOT match {sha}. ") def validate_vm_dirs(self): """Validates and creates directories""" print("Validating directories") if not os.path.exists(FWUPD_VM_DIR): self._create_dirs(FWUPD_VM_DIR) if os.path.exists(FWUPD_VM_METADATA_DIR): shutil.rmtree(FWUPD_VM_METADATA_DIR) self._create_dirs(FWUPD_VM_METADATA_DIR) else: self._create_dirs(FWUPD_VM_METADATA_DIR) if not os.path.exists(FWUPD_VM_UPDATES_DIR): self._create_dirs(FWUPD_VM_UPDATES_DIR) os.umask(self.old_umask) def _jcat_verification(self, file_path, file_directory): """Verifies sha1 and sha256 checksum, GPG signature, and PKCS#7 signature. Keyword argument: file_path -- absolute path to jcat file file_directory -- absolute path to the directory to jcat file location """ cmd_jcat = ["jcat-tool", "verify", f"{file_path}", "--public-keys", FWUPD_PKI] p = subprocess.Popen( cmd_jcat, cwd=file_directory, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout, __ = p.communicate() verification = stdout.decode("utf-8") print(verification) if p.returncode != 0: self.clean_vm_cache() raise Exception("jcat-tool: Verification failed") def clean_vm_cache(self): """Removes updates data""" print("Cleaning cache directories") if os.path.exists(FWUPD_VM_METADATA_DIR): shutil.rmtree(FWUPD_VM_METADATA_DIR) if os.path.exists(FWUPD_VM_UPDATES_DIR): shutil.rmtree(FWUPD_VM_UPDATES_DIR) fwupd-1.9.16/contrib/qubes/src/vms/fwupd_download_updates.py000066400000000000000000000125551460375044200242400ustar00rootroot00000000000000#!/usr/bin/python3 # # The Qubes OS Project, http://www.qubes-os.org # # Copyright (C) 2021 Norbert Kamiński # # SPDX-License-Identifier: LGPL-2.1+ # import sys import subprocess import os from fwupd_common_vm import FwupdVmCommon FWUPD_VM_DIR = "/home/user/.cache/fwupd" FWUPD_VM_UPDATES_DIR = os.path.join(FWUPD_VM_DIR, "updates") FWUPD_VM_METADATA_DIR = os.path.join(FWUPD_VM_DIR, "metadata") FWUPD_DOWNLOAD_PREFIX = "https://fwupd.org/downloads/" METADATA_URL = "https://fwupd.org/downloads/firmware.xml.gz" METADATA_URL_JCAT = "https://fwupd.org/downloads/firmware.xml.gz.jcat" class DownloadData(FwupdVmCommon): def _download_metadata_file(self): """Download metadata file""" if self.custom_url is None: metadata_url = METADATA_URL else: metadata_url = self.custom_url cmd_metadata = ["curl", "-fL", "-o", self.metadata_file, "--", metadata_url] p = subprocess.Popen(cmd_metadata) p.wait() if p.returncode != 0: raise Exception("fwupd-qubes: Downloading metadata file failed") if not os.path.exists(self.metadata_file): raise FileNotFoundError( "fwupd-qubes: Downloaded metadata file does not exist" ) def _download_metadata_jcat(self): """Download metadata jcat signature""" if self.custom_url is None: metadata_url = METADATA_URL else: metadata_url = self.custom_url cmd_metadata = [ "curl", "-fL", "-o", f"{self.metadata_file}.jcat", "--", f"{metadata_url}.jcat", ] p = subprocess.Popen(cmd_metadata) p.wait() if p.returncode != 0: raise Exception("fwupd-qubes: Downloading metadata file failed") if not os.path.exists(f"{self.metadata_file}.jcat"): raise FileNotFoundError( "fwupd-qubes: Downloaded metadata file does not exist" ) environ = os.environ.copy() environ["LC_ALL"] = "C" cmd_info = ["jcat-tool", "info", f"{self.metadata_file}.jcat"] info_stdout = subprocess.check_output(cmd_info, env=environ).decode() info_id_line = [line for line in info_stdout.splitlines() if "ID:" in line] if info_id_line: info_id = info_id_line[0].split(":", 1)[1].strip() else: info_id = None if info_id and info_id != os.path.basename(metadata_url): # fetch the file referenced in jcat, to workaround CDN being few # hours out of sync self.custom_url = os.path.dirname(metadata_url) + "/" + info_id cmd_export = [ "jcat-tool", f"--prefix={FWUPD_VM_METADATA_DIR}/", "--", "export", f"{self.metadata_file}.jcat", ] p = subprocess.Popen(cmd_export, stdout=subprocess.PIPE, env=environ) stdout, _ = p.communicate() if p.returncode != 0: raise Exception("fwupd-qubes: Extracting jcat file failed") # rename extracted files to match jcat base name, instead of "ID" # inside jcat for line in stdout.decode("ascii").splitlines(): if not line.startswith("Wrote "): continue path = line.split(" ", 1)[1] base_path, ext = os.path.splitext(path) if base_path == self.metadata_file: continue new_path = f"{self.metadata_file}{ext}" os.rename(path, new_path) def download_metadata(self, url=None): """Downloads default metadata and its signatures""" if url is not None: self.custom_url = url custom_metadata_name = os.path.basename(url) self.metadata_file = os.path.join( FWUPD_VM_METADATA_DIR, custom_metadata_name ) else: self.custom_url = None self.metadata_file = os.path.join(FWUPD_VM_METADATA_DIR, "firmware.xml.gz") self.validate_vm_dirs() self._download_metadata_jcat() self._download_metadata_file() def download_updates(self, url, sha): """ Downloads update form given url Keyword argument: url - url address of the update """ self.validate_vm_dirs() self.arch_name = os.path.basename(url) update_path = os.path.join(FWUPD_VM_UPDATES_DIR, self.arch_name) cmd_update = ["curl", "-fL", "-o", update_path, "--", url] p = subprocess.Popen(cmd_update) p.wait() if p.returncode != 0: raise Exception("fwupd-qubes: Downloading update file failed") if not os.path.exists(update_path): raise FileNotFoundError( "fwupd-qubes: Downloaded update file does not exist" ) self.check_shasum(update_path, sha) print("Update file downloaded successfully") def main(): url = None sha = None dn = DownloadData() for arg in sys.argv: if "--url=" in arg: url = arg.replace("--url=", "") if "--sha=" in arg: sha = arg.replace("--sha=", "") if "--metadata" in sys.argv: dn.download_metadata(url=url) elif url and sha: dn.download_updates(url, sha) else: raise Exception("Invalid command!!!") if __name__ == "__main__": main() fwupd-1.9.16/contrib/qubes/test/000077500000000000000000000000001460375044200165005ustar00rootroot00000000000000fwupd-1.9.16/contrib/qubes/test/__init__.py000066400000000000000000000000001460375044200205770ustar00rootroot00000000000000fwupd-1.9.16/contrib/qubes/test/fwupd_logs.py000066400000000000000000001126261460375044200212330ustar00rootroot00000000000000#!/usr/bin/python3 # # The Qubes OS Project, http://www.qubes-os.org # # Copyright (C) 2021 Norbert Kamiński # # SPDX-License-Identifier: LGPL-2.1+ # UPDATE_INFO = """{ "Devices" : [ { "Name" : "ColorHug2", "DeviceId" : "b0a78eb71f4eeea7df8fb114522556ba8ce22074", "Guid" : [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad", "aa4b4156-9732-55db-9500-bf6388508ee3", "101ee86a-7bea-59fb-9f89-6b6297ceed3b", "2fa8891f-3ece-53a4-adc4-0dd875685f30" ], "Summary" : "An open source display colorimeter", "Plugin" : "colorhug", "Protocol" : "com.hughski.colorhug", "Flags" : [ "updatable", "supported", "registered", "self-recovery", "add-counterpart-guids" ], "Vendor" : "Hughski Ltd.", "VendorId" : "USB:0x273F", "Version" : "2.0.6", "VersionFormat" : "triplet", "Icons" : [ "colorimeter-colorhug" ], "InstallDuration" : 8, "Created" : 1614224175, "Releases" : [ { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This release fixes prevents the firmware returning an error when the remote SHA1 hash was never sent.

    ", "Version" : "2.0.7", "Filename" : "hughski-colorhug2-2.0.7.cab", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "80bddeb898cda5b87d9837e13a9ace19846053bf", "32c4a2c9be787cdf1d757c489d6455bd7bb14053425180b6d331c37e1ccc1cda" ], "License" : "GPL-2.0+", "Size" : 16384, "Created" : 1482901200, "Locations" : [ "https://fwupd.org/downloads/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab", "ipfs://QmUByRuHG9Gb2s8gKKVqDcjhUrn8vy62B4WqjbpWDD42cf" ], "Uri" : "https://fwupd.org/downloads/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "Flags" : [ "is-upgrade" ], "InstallDuration" : 8 } ] } ] } """ DMI_DECODE = """# dmidecode 3.1 Getting SMBIOS data from sysfs. SMBIOS 3.1.1 present. Handle 0x0000, DMI type 0, 26 bytes BIOS Information Vendor: Dell Inc. Version: P1.00 Release Date: 02/09/2018 Address: 0xF0000 Runtime Size: 64 kB ROM Size: 16 MB Characteristics: PCI is supported BIOS is upgradeable BIOS shadowing is allowed Boot from CD is supported Selectable boot is supported BIOS ROM is socketed EDD is supported 5.25"/1.2 MB floppy services are supported (int 13h) 3.5"/720 kB floppy services are supported (int 13h) 3.5"/2.88 MB floppy services are supported (int 13h) Print screen service is supported (int 5h) 8042 keyboard services are supported (int 9h) Serial services are supported (int 14h) Printer services are supported (int 17h) ACPI is supportedUSB legacy is supported BIOS boot specification is supported Targeted content distribution is supported UEFI is supported BIOS Revision: 5.13 """ GET_DEVICES = """{ "Devices" : [ { "Name" : "ColorHug2", "DeviceId" : "cf294bf55b333004beb7c41f952c1838c23e1f4a", "Guid" : [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad", "aa4b4156-9732-55db-9500-bf6388508ee3", "101ee86a-7bea-59fb-9f89-6b6297ceed3b", "2fa8891f-3ece-53a4-adc4-0dd875685f30" ], "Summary" : "An open source display colorimeter", "Plugin" : "colorhug", "Protocol" : "com.hughski.colorhug", "Flags" : [ "updatable", "supported", "registered", "self-recovery", "add-counterpart-guids" ], "Vendor" : "Hughski Ltd.", "VendorId" : "USB:0x273F", "Version" : "2.0.6", "VersionFormat" : "triplet", "Icons" : [ "colorimeter-colorhug" ], "InstallDuration" : 8, "Created" : 1614246373, "Releases" : [ { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This release fixes prevents the firmware returning an error when the remote SHA1 hash was never sent.

    ", "Version" : "2.0.7", "Filename" : "hughski-colorhug2-2.0.7.cab", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "80bddeb898cda5b87d9837e13a9ace19846053bf", "32c4a2c9be787cdf1d757c489d6455bd7bb14053425180b6d331c37e1ccc1cda" ], "License" : "GPL-2.0+", "Size" : 16384, "Created" : 1482901200, "Locations" : [ "https://fwupd.org/downloads/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab", "ipfs://QmUByRuHG9Gb2s8gKKVqDcjhUrn8vy62B4WqjbpWDD42cf" ], "Uri" : "https://fwupd.org/downloads/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "Flags" : [ "is-upgrade" ], "InstallDuration" : 8 }, { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This stable release fixes the following problems:

    • Fix the swapped LEDs on the second half of batch 16
    • Fix the firmware upgrade process using new versions of fwupd
    ", "Version" : "2.0.6", "Filename" : "hughski-colorhug2-2.0.6.cab", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "60e28bb402b427dbce19e150d63987f5e18c1880", "a646b1798ce7f5ac26229aa85c35cc4f44a5bd8bfc9e5332a8ec815aef075566" ], "License" : "GPL-2.0+", "Size" : 16384, "Created" : 1450792062, "Locations" : [ "https://fwupd.org/downloads/170f2c19f17b7819644d3fcc7617621cc3350a04-hughski-colorhug2-2.0.6.cab", "ipfs://QmdWFrYo1YJxgGU37Qy7LkwPQM26vPMVxLRANUga6TzSjW" ], "Uri" : "https://fwupd.org/downloads/170f2c19f17b7819644d3fcc7617621cc3350a04-hughski-colorhug2-2.0.6.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "InstallDuration" : 8 }, { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This stable release fixes the following problems:

    • Fix the swapped LEDs on batch 16
    • Make the self test more sensitive to detect floating pins
    ", "Version" : "2.0.5", "Filename" : "hughski-colorhug2-2.0.5.cab", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "e37b9d360d61157657335d80585a005ff2593108", "8cd379eb2e1467e4fda92c20650306dc7e598b1d421841bbe19d9ed6ea01e3ee" ], "License" : "GPL-2.0+", "Size" : 16384, "Created" : 1444059405, "Locations" : [ "https://fwupd.org/downloads/f7dd4ab29fa610438571b8b62b26b0b0e57bb35b-hughski-colorhug2-2.0.5.cab", "ipfs://QmQ648kwvv52wuqPoKjm5zLGXngQnmuJzp1xtJmTEbzgz5" ], "Uri" : "https://fwupd.org/downloads/f7dd4ab29fa610438571b8b62b26b0b0e57bb35b-hughski-colorhug2-2.0.5.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "Flags" : [ "is-downgrade" ], "InstallDuration" : 8 }, { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This unstable release adds the following features:

    • Add TakeReadingArray to enable panel latency measurements
    • Speed up the auto-scaled measurements considerably, using 256ms as the smallest sample duration
    ", "Version" : "2.0.2", "Filename" : "hughski-colorhug2-2.0.2.cab", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "1b43bd71bbed2cf0e9c9efcca79799f07b3d0dd2", "c09674fb818d4a1033dbde2fab5885716aed1d8b751b428f16687a78f2a4d61f" ], "License" : "GPL-2.0+", "Size" : 15680, "Created" : 1416675439, "Locations" : [ "https://fwupd.org/downloads/30a121f26c039745aeb5585252d4a9b5386d71cb-hughski-colorhug2-2.0.2.cab", "ipfs://QmZ1DKKsWZQuvnff2DJTDJESMaXTpsc5zfNGX7Sb2HibAn" ], "Uri" : "https://fwupd.org/downloads/30a121f26c039745aeb5585252d4a9b5386d71cb-hughski-colorhug2-2.0.2.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "Flags" : [ "is-downgrade" ], "InstallDuration" : 8 } ] }, { "Name" : "Display controller", "DeviceId" : "ecf0d22adf39a244a723466378a8884aa22b7e78", "Guid" : [ "e358a53d-98bc-5565-b55e-7df8e0d06c5e", "7365091f-756a-5c83-878c-edd1120ca718", "06208e9f-1dd0-5857-b700-3d77525793aa", "af9ff5a0-c613-5da3-bab8-5d411adebbca" ], "Plugin" : "optionrom", "Flags" : [ "internal", "registered", "can-verify", "can-verify-image" ], "VendorId" : "PCI:0x1234", "Version" : "02", "VersionFormat" : "plain", "Created" : 1614209932 }, { "Name" : "Intel(R) Core™ i7-7700HQ CPU @ 2.80GHz", "DeviceId" : "4bde70ba4e39b28f9eab1628f9dd6e6244c03027", "Guid" : [ "30249f37-d140-5d3e-9319-186b1bd5cac3", "809a0b93-8a12-5338-a571-ad5583acf896", "d0f754d5-1395-5573-bc83-85ba955da70a" ], "Plugin" : "cpu", "Flags" : [ "internal", "registered" ], "Vendor" : "Intel", "Version" : "0x000000de", "VersionFormat" : "hex", "VersionRaw" : 222, "Icons" : [ "computer" ], "Created" : 1614209932 } ] } """ GET_DEVICES_NO_UPDATES = """{ "Devices" : [ { "Name" : "ColorHug2", "DeviceId" : "203f56e4e186d078ce76725e708400aafc253aac", "Guid" : [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad", "aa4b4156-9732-55db-9500-bf6388508ee3", "101ee86a-7bea-59fb-9f89-6b6297ceed3b", "2fa8891f-3ece-53a4-adc4-0dd875685f30" ], "Summary" : "An open source display colorimeter", "Plugin" : "colorhug", "Protocol" : "com.hughski.colorhug", "Flags" : [ "updatable", "supported", "registered", "self-recovery", "add-counterpart-guids" ], "Vendor" : "Hughski Ltd.", "VendorId" : "USB:0x273F", "Version" : "2.0.7", "VersionFormat" : "triplet", "Icons" : [ "colorimeter-colorhug" ], "InstallDuration" : 8, "Created" : 1592916092, "Releases" : [ { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This release fixes prevents the firmware returning an error when the remote SHA1 hash was never sent.

    ", "Version" : "2.0.7", "Filename" : "658851e6f27c4d87de19cd66b97b610d100efe09", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "490be5c0b13ca4a3f169bf8bc682ba127b8f7b96" ], "License" : "GPL-2.0+", "Size" : 16384, "Created" : 1482901200, "Uri" : "https://fwupd.org/downloads/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "InstallDuration" : 8 }, { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This stable release fixes the following problems:

    • Fix the swapped LEDs on the second half of batch 16
    • Fix the firmware upgrade process using new versions of fwupd
    ", "Version" : "2.0.6", "Filename" : "f038b5ca40e6d7c1c0299a9e1dcc129d5f6371b6", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "03c9c14db1894a00035ececcfae192865a710e52" ], "License" : "GPL-2.0+", "Size" : 16384, "Created" : 1450792062, "Uri" : "https://fwupd.org/downloads/170f2c19f17b7819644d3fcc7617621cc3350a04-hughski-colorhug2-2.0.6.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "Flags" : [ "is-downgrade" ], "InstallDuration" : 8 }, { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This stable release fixes the following problems:

    • Fix the swapped LEDs on batch 16
    • Make the self test more sensitive to detect floating pins
    ", "Version" : "2.0.5", "Filename" : "ae76c6b704b60f9d1d88dc2c8ec8a62d7b2331dc", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "4ee9dfa38df3b810f739d8a19d13da1b3175fb87" ], "License" : "GPL-2.0+", "Size" : 16384, "Created" : 1444059405, "Uri" : "https://fwupd.org/downloads/f7dd4ab29fa610438571b8b62b26b0b0e57bb35b-hughski-colorhug2-2.0.5.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "Flags" : [ "is-downgrade" ], "InstallDuration" : 8 }, { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This unstable release adds the following features:

    • Add TakeReadingArray to enable panel latency measurements
    • Speed up the auto-scaled measurements considerably, using 256ms as the smallest sample duration
    ", "Version" : "2.0.2", "Filename" : "d4b3144daeb2418634f9d464d88d55590bcd9ac7", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "448527af3ce019d03dbb77aaebaa7eb893f1ea20" ], "License" : "GPL-2.0+", "Size" : 15680, "Created" : 1416675439, "Uri" : "https://fwupd.org/downloads/30a121f26c039745aeb5585252d4a9b5386d71cb-hughski-colorhug2-2.0.2.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "Flags" : [ "is-downgrade" ], "InstallDuration" : 8 } ] }, { "Name" : "GP106 [GeForce GTX 1060 6GB]", "DeviceId" : "71b677ca0f1bc2c5b804fa1d59e52064ce589293", "Guid" : [ "b080a9ba-fff8-5de0-b641-26f782949f94", "f95bfce3-18e4-58b0-bd81-136457521383" ], "Plugin" : "optionrom", "Flags" : [ "internal", "registered", "can-verify", "can-verify-image" ], "Vendor" : "NVIDIA Corporation", "VendorId" : "PCI:0x10DE", "Version" : "a1", "VersionFormat" : "plain", "Created" : 1592899254 }, { "Name" : "Intel(R) Core™ i5-8400 CPU @ 2.80GHz", "DeviceId" : "4bde70ba4e39b28f9eab1628f9dd6e6244c03027", "Plugin" : "cpu", "Flags" : [ "internal", "registered" ], "Vendor" : "GenuineIntel", "Version" : "0xd6", "VersionFormat" : "hex", "Icons" : [ "computer" ], "Created" : 1592899249 }, { "Name" : "SSDPR-CX400-256", "DeviceId" : "948241a24320627284597ec95079cc1341c90518", "Guid" : [ "09fa3842-45bc-5226-a8ec-1668fc61f88f", "57d6b2ff-710d-5cd2-98be-4f6b8b7c5287", "36bebd37-b680-5d56-83a1-6693033d4098" ], "Summary" : "ATA Drive", "Plugin" : "ata", "Protocol" : "org.t13.ata", "Flags" : [ "internal", "updatable", "require-ac", "registered", "needs-reboot", "usable-during-update" ], "Vendor" : "Phison", "VendorId" : "ATA:0x1987", "Version" : "SBFM61.3", "VersionFormat" : "plain", "Icons" : [ "drive-harddisk" ], "Created" : 1592899254 } ] } """ GET_DEVICES_NO_VERSION = """{ "Devices" : [ { "Name" : "ColorHug2", "DeviceId" : "203f56e4e186d078ce76725e708400aafc253aac", "Guid" : [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad", "aa4b4156-9732-55db-9500-bf6388508ee3", "101ee86a-7bea-59fb-9f89-6b6297ceed3b", "2fa8891f-3ece-53a4-adc4-0dd875685f30" ], "Summary" : "An open source display colorimeter", "Plugin" : "colorhug", "Protocol" : "com.hughski.colorhug", "Flags" : [ "updatable", "supported", "registered", "self-recovery", "add-counterpart-guids" ], "Vendor" : "Hughski Ltd.", "VendorId" : "USB:0x273F", "VersionFormat" : "triplet", "Icons" : [ "colorimeter-colorhug" ], "InstallDuration" : 8, "Created" : 1592916092, "Releases" : [ { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This release fixes prevents the firmware returning an error when the remote SHA1 hash was never sent.

    ", "Version" : "2.0.7", "Filename" : "658851e6f27c4d87de19cd66b97b610d100efe09", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "490be5c0b13ca4a3f169bf8bc682ba127b8f7b96" ], "License" : "GPL-2.0+", "Size" : 16384, "Created" : 1482901200, "Uri" : "https://fwupd.org/downloads/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "InstallDuration" : 8 }, { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This stable release fixes the following problems:

    • Fix the swapped LEDs on the second half of batch 16
    • Fix the firmware upgrade process using new versions of fwupd
    ", "Filename" : "f038b5ca40e6d7c1c0299a9e1dcc129d5f6371b6", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "03c9c14db1894a00035ececcfae192865a710e52" ], "License" : "GPL-2.0+", "Size" : 16384, "Created" : 1450792062, "Uri" : "https://fwupd.org/downloads/170f2c19f17b7819644d3fcc7617621cc3350a04-hughski-colorhug2-2.0.6.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "Flags" : [ "is-downgrade" ], "InstallDuration" : 8 }, { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This stable release fixes the following problems:

    • Fix the swapped LEDs on batch 16
    • Make the self test more sensitive to detect floating pins
    ", "Version" : "2.0.5", "Filename" : "ae76c6b704b60f9d1d88dc2c8ec8a62d7b2331dc", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "4ee9dfa38df3b810f739d8a19d13da1b3175fb87" ], "License" : "GPL-2.0+", "Size" : 16384, "Created" : 1444059405, "Uri" : "https://fwupd.org/downloads/f7dd4ab29fa610438571b8b62b26b0b0e57bb35b-hughski-colorhug2-2.0.5.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "Flags" : [ "is-downgrade" ], "InstallDuration" : 8 }, { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This unstable release adds the following features:

    • Add TakeReadingArray to enable panel latency measurements
    • Speed up the auto-scaled measurements considerably, using 256ms as the smallest sample duration
    ", "Version" : "2.0.2", "Filename" : "d4b3144daeb2418634f9d464d88d55590bcd9ac7", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "448527af3ce019d03dbb77aaebaa7eb893f1ea20" ], "License" : "GPL-2.0+", "Size" : 15680, "Created" : 1416675439, "Uri" : "https://fwupd.org/downloads/30a121f26c039745aeb5585252d4a9b5386d71cb-hughski-colorhug2-2.0.2.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "Flags" : [ "is-downgrade" ], "InstallDuration" : 8 } ] }, { "Name" : "ColorHug2", "DeviceId" : "203f56e4e186d078ce76725e708400aafc253aac", "Guid" : [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad", "aa4b4156-9732-55db-9500-bf6388508ee3", "101ee86a-7bea-59fb-9f89-6b6297ceed3b", "2fa8891f-3ece-53a4-adc4-0dd875685f30" ], "Summary" : "An open source display colorimeter", "Plugin" : "colorhug", "Protocol" : "com.hughski.colorhug", "Flags" : [ "updatable", "supported", "registered", "self-recovery", "add-counterpart-guids" ], "Vendor" : "Hughski Ltd.", "Version" : "2.0.6", "VendorId" : "USB:0x273F", "VersionFormat" : "triplet", "Icons" : [ "colorimeter-colorhug" ], "InstallDuration" : 8, "Created" : 1592916092, "Releases" : [ { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This release fixes prevents the firmware returning an error when the remote SHA1 hash was never sent.

    ", "Version" : "2.0.7", "Filename" : "658851e6f27c4d87de19cd66b97b610d100efe09", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "490be5c0b13ca4a3f169bf8bc682ba127b8f7b96" ], "License" : "GPL-2.0+", "Size" : 16384, "Created" : 1482901200, "Uri" : "https://fwupd.org/downloads/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "InstallDuration" : 8 }, { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This stable release fixes the following problems:

    • Fix the swapped LEDs on the second half of batch 16
    • Fix the firmware upgrade process using new versions of fwupd
    ", "Version" : "2.0.6", "Filename" : "f038b5ca40e6d7c1c0299a9e1dcc129d5f6371b6", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "03c9c14db1894a00035ececcfae192865a710e52" ], "License" : "GPL-2.0+", "Size" : 16384, "Created" : 1450792062, "Uri" : "https://fwupd.org/downloads/170f2c19f17b7819644d3fcc7617621cc3350a04-hughski-colorhug2-2.0.6.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "Flags" : [ "is-downgrade" ], "InstallDuration" : 8 }, { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This stable release fixes the following problems:

    • Fix the swapped LEDs on batch 16
    • Make the self test more sensitive to detect floating pins
    ", "Version" : "2.0.5", "Filename" : "ae76c6b704b60f9d1d88dc2c8ec8a62d7b2331dc", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "4ee9dfa38df3b810f739d8a19d13da1b3175fb87" ], "License" : "GPL-2.0+", "Size" : 16384, "Created" : 1444059405, "Uri" : "https://fwupd.org/downloads/f7dd4ab29fa610438571b8b62b26b0b0e57bb35b-hughski-colorhug2-2.0.5.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "Flags" : [ "is-downgrade" ], "InstallDuration" : 8 }, { "AppstreamId" : "com.hughski.ColorHug2.firmware", "RemoteId" : "lvfs", "Summary" : "Firmware for the Hughski ColorHug2 Colorimeter", "Description" : "

    This unstable release adds the following features:

    • Add TakeReadingArray to enable panel latency measurements
    • Speed up the auto-scaled measurements considerably, using 256ms as the smallest sample duration
    ", "Version" : "2.0.2", "Filename" : "d4b3144daeb2418634f9d464d88d55590bcd9ac7", "Protocol" : "com.hughski.colorhug", "Checksum" : [ "448527af3ce019d03dbb77aaebaa7eb893f1ea20" ], "License" : "GPL-2.0+", "Size" : 15680, "Created" : 1416675439, "Uri" : "https://fwupd.org/downloads/30a121f26c039745aeb5585252d4a9b5386d71cb-hughski-colorhug2-2.0.2.cab", "Homepage" : "http://www.hughski.com/", "SourceUrl" : "https://github.com/hughski/colorhug2-firmware", "Vendor" : "Hughski Limited", "Flags" : [ "is-downgrade" ], "InstallDuration" : 8 } ] }, { "Name" : "GP106 [GeForce GTX 1060 6GB]", "DeviceId" : "71b677ca0f1bc2c5b804fa1d59e52064ce589293", "Guid" : [ "b080a9ba-fff8-5de0-b641-26f782949f94", "f95bfce3-18e4-58b0-bd81-136457521383" ], "Plugin" : "optionrom", "Flags" : [ "internal", "registered", "can-verify", "can-verify-image" ], "Vendor" : "NVIDIA Corporation", "VendorId" : "PCI:0x10DE", "VersionFormat" : "plain", "Created" : 1592899254 }, { "Name" : "Intel(R) Core™ i5-8400 CPU @ 2.80GHz", "DeviceId" : "4bde70ba4e39b28f9eab1628f9dd6e6244c03027", "Plugin" : "cpu", "Flags" : [ "internal", "registered" ], "Vendor" : "GenuineIntel", "Version" : "0xd6", "VersionFormat" : "hex", "Icons" : [ "computer" ], "Created" : 1592899249 }, { "Name" : "SSDPR-CX400-256", "DeviceId" : "948241a24320627284597ec95079cc1341c90518", "Guid" : [ "09fa3842-45bc-5226-a8ec-1668fc61f88f", "57d6b2ff-710d-5cd2-98be-4f6b8b7c5287", "36bebd37-b680-5d56-83a1-6693033d4098" ], "Summary" : "ATA Drive", "Plugin" : "ata", "Protocol" : "org.t13.ata", "Flags" : [ "internal", "updatable", "require-ac", "registered", "needs-reboot", "usable-during-update" ], "Vendor" : "Phison", "VendorId" : "ATA:0x1987", "Version" : "SBFM61.3", "VersionFormat" : "plain", "Icons" : [ "drive-harddisk" ], "Created" : 1592899254 } ] } """ HEADS_XML = """ com.3mdeb.heads.x230.firmware Heads x230 System Update x230 heads system firmware

    x230 heads system firmware

    596c3466-0506-5ca5-a68f-dc34532a93d3 http://osresearch.net/ CC0-1.0 GPLv2 coreboot X-System https://fwupd.org/downloads/e747a435bf24fd6081b77b6704b39cec5fa2dcf62e0ca6b86d8a6460121a1d07-heads_coreboot_x230-v0_2_3.cab 1a54e69ca2b58d1218035115d481480eaf4c66e4 ba519a7a5d8136c8ade0cf0c775c58f3165f42798ff631c3f57f075897ef1586 76373f1b5a157b6563d3605271472901b03f57f3 9a9c5dbd3faf90ff7a1f4c9be8d71c4db93dd69fa690f8722fec19c5a51aed9e

    Fixes flash-gui issue.

    12582912 12591670
    https://fwupd.org/downloads/1a0f0ad487a40bb27a49db55e256a207a33dac92c5c53761501c9fb89e4fd115-heads_coreboot_x230-v0_2_2.cab 58e85d012ad1d5c6f98e8fe65202b4d6c8a6ec03 94430160d35cf74adf29c7fc1490b44497e1a3f0fff72733efe2982c61c9a772 8e97ce38396e281fcf9a5a248819925a2fa04265 a6774661407622f345bf0ac2f113540507f0288bb97bf5dba586059c0653f659

    Lenovo x230 heads system firmware

    12582912 12591680
    """ fwupd-1.9.16/contrib/qubes/test/logs/000077500000000000000000000000001460375044200174445ustar00rootroot00000000000000fwupd-1.9.16/contrib/qubes/test/logs/firmware.metainfo.xml000066400000000000000000000036041460375044200236060ustar00rootroot00000000000000 com.dell.uefi6180aaaa.firmware Latitude 7390 2-in-1 Firmware for the Dell Latitude 7390 2-in-1

    Updating the system firmware improves performance.

    6180aaaa-5529-4bbf-b4fd-65b4f788de5b http://support.dell.com/ CC0-1.0 proprietary Dell Inc. X-System quad dell-bios org.uefi.capsule b03252481573f600c7f530d7a4c24bd62c810412 bd866bcd2b5964da4b20d3f57128746efb7afefe648cf7284f2fae399225f1ac

    This stable release fixes the following issues:

    • Firmware updates to address the Intel Security Advisories.
    • Enhanced the system firmware auto recovery function when system firmware does not work.
    • Updated the BIOS warning message that is displayed when an AC adapter with low wattage is connected to the system.
    • Updated the Intel CPU Microcode.
    14699068 CVE-2019-14607 CVE-2019-11157
    fwupd-1.9.16/contrib/qubes/test/logs/get_devices.log000066400000000000000000000014171460375044200224330ustar00rootroot00000000000000====================================================================== Dom0 Devices: ====================================================================== ColorHug2 ====================================================================== DeviceId: b0a78eb71f4eeea7df8fb114522556ba8ce22074 Guid: ·2082b5e0-7a64-478a-b1b2-e3404fab6dad ·aa4b4156-9732-55db-9500-bf6388508ee3 ·101ee86a-7bea-59fb-9f89-6b6297ceed3b ·2fa8891f-3ece-53a4-adc4-0dd875685f30 Summary: An open source display colorimeter Plugin: colorhug Protocol: com.hughski.colorhug Flags: ·updatable ·supported ·registered ·self-recovery ·add-counterpart-guids Vendor: Hughski Ltd. VendorId: USB:0x273F Version: 2.0.6 Created: 1614224175 fwupd-1.9.16/contrib/qubes/test/logs/get_updates.log000066400000000000000000000014021460375044200224500ustar00rootroot00000000000000====================================================== sys-usb updates: ====================================================== Available updates: ====================================================== ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. Device: ColorHug2 Current firmware version: 2.0.6 ====================================================== Firmware update version: 2.0.7 URL: https://fwupd.org/downloads/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab SHA256 checksum: 32c4a2c9be787cdf1d757c489d6455bd7bb14053425180b6d331c37e1ccc1cda Description: This release fixes prevents the firmware returning an error when the remote SHA1 hash was never sent. ====================================================== fwupd-1.9.16/contrib/qubes/test/logs/help.log000066400000000000000000000021701460375044200210770ustar00rootroot00000000000000====================================================================== Usage: ====================================================================== Command: qubes-fwupdmgr [OPTION…][FLAG..] Example: qubes-fwupdmgr refresh --whonix --url= Options: ====================================================================== get-devices: Get all devices that support firmware updates get-updates: Get the list of updates for connected hardware refresh: Refresh metadata from remote server update: Update chosen device to latest firmware version update-heads: Updates heads firmware to the latest version (EXPERIMENTAL, not fully supported yet) downgrade: Downgrade chosen device to chosen firmware version clean: Delete all cached update files Flags: ====================================================================== --whonix: Download firmware updates via Tor --device: Specify device for heads update (default - x230) --url: Address of the custom metadata remote server Help: ====================================================================== -h --help: Show help options fwupd-1.9.16/contrib/qubes/test/logs/metainfo_name/000077500000000000000000000000001460375044200222465ustar00rootroot00000000000000fwupd-1.9.16/contrib/qubes/test/logs/metainfo_name/firmware.metainfo.xml000066400000000000000000000036051460375044200264110ustar00rootroot00000000000000 com.dell.uefi6180aaaa.firmware Latitude 7390 2-in-1 Firmware for the Dell Latitude 7390 2-in-1

    Updating the system firmware improves performance.

    6180aaaa-5529-4bbf-b4fd-65b4f788de5b http://support.dell.com/ CC0-1.0 proprietary Wrong name X-System quad dell-bios org.uefi.capsule b03252481573f600c7f530d7a4c24bd62c810412 bd866bcd2b5964da4b20d3f57128746efb7afefe648cf7284f2fae399225f1ac

    This stable release fixes the following issues:

    • Firmware updates to address the Intel Security Advisories.
    • Enhanced the system firmware auto recovery function when system firmware does not work.
    • Updated the BIOS warning message that is displayed when an AC adapter with low wattage is connected to the system.
    • Updated the Intel CPU Microcode.
    14699068 CVE-2019-14607 CVE-2019-11157
    fwupd-1.9.16/contrib/qubes/test/logs/metainfo_version/000077500000000000000000000000001460375044200230135ustar00rootroot00000000000000fwupd-1.9.16/contrib/qubes/test/logs/metainfo_version/firmware.metainfo.xml000066400000000000000000000036041460375044200271550ustar00rootroot00000000000000 com.dell.uefi6180aaaa.firmware Latitude 7390 2-in-1 Firmware for the Dell Latitude 7390 2-in-1

    Updating the system firmware improves performance.

    6180aaaa-5529-4bbf-b4fd-65b4f788de5b http://support.dell.com/ CC0-1.0 proprietary Dell Inc. X-System quad dell-bios org.uefi.capsule b03252481573f600c7f530d7a4c24bd62c810412 bd866bcd2b5964da4b20d3f57128746efb7afefe648cf7284f2fae399225f1ac

    This stable release fixes the following issues:

    • Firmware updates to address the Intel Security Advisories.
    • Enhanced the system firmware auto recovery function when system firmware does not work.
    • Updated the BIOS warning message that is displayed when an AC adapter with low wattage is connected to the system.
    • Updated the Intel CPU Microcode.
    14699068 CVE-2019-14607 CVE-2019-11157
    fwupd-1.9.16/contrib/qubes/test/test_qubes_fwupd_heads.py000066400000000000000000000114731460375044200236070ustar00rootroot00000000000000#!/usr/bin/python3 # # The Qubes OS Project, http://www.qubes-os.org # # Copyright (C) 2021 Norbert Kamiński # # SPDX-License-Identifier: LGPL-2.1+ # import io import os import platform import shutil import imp import src.qubes_fwupd_heads as qf_heads import sys import unittest from test.fwupd_logs import HEADS_XML CUSTOM_METADATA = "https://fwupd.org/downloads/firmware-3c81bfdc9db5c8a42c09d38091944bc1a05b27b0.xml.gz" QUBES_FWUPDMGR_REPO = "./src/qubes_fwupdmgr.py" QUBES_FWUPDMGR_BINDIR = "/usr/sbin/qubes-fwupdmgr" class TestQubesFwupdHeads(unittest.TestCase): def setUp(self): if os.path.exists(QUBES_FWUPDMGR_REPO): self.qfwupd = imp.load_source("qubes_fwupdmgr", QUBES_FWUPDMGR_REPO) elif os.path.exists(QUBES_FWUPDMGR_BINDIR): self.qfwupd = imp.load_source("qubes_fwupdmgr", QUBES_FWUPDMGR_BINDIR) self.q = qf_heads.FwupdHeads() self.maxDiff = 2000 self.captured_output = io.StringIO() sys.stdout = self.captured_output @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_get_hwids(self): self.q._get_hwids() self.assertNotEqual(self.q.dom0_hwids_info, "") def test_gather_firmware_version_empty(self): self.q.dom0_hwids_info = "" return_code = self.q._gather_firmware_version() self.assertEqual(return_code, self.qfwupd.EXIT_CODES["NOTHING_TO_DO"]) def test_gather_firmware_version(self): self.q.dom0_hwids_info = "CBET4000 Heads-v0.2.2-917-g19f0e65" self.q._gather_firmware_version() self.assertEqual(self.q.heads_version, "0.2.2-917-g19f0e65") @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_parse_metadata(self): qmgr = self.qfwupd.QubesFwupdmgr() qmgr.metadata_file = CUSTOM_METADATA.replace( "https://fwupd.org/downloads", self.qfwupd.FWUPD_DOM0_METADATA_DIR ) qmgr._download_metadata(metadata_url=CUSTOM_METADATA) self.q._parse_metadata(qmgr.metadata_file) self.assertTrue(self.q.metadata_info) def test_check_heads_updates_default_heads(self): self.q.metadata_info = HEADS_XML self.q.heads_version = "heads" return_code = self.q._parse_heads_updates("x230") self.assertEqual(return_code, self.qfwupd.EXIT_CODES["SUCCESS"]) self.assertEqual( self.q.heads_update_url, "https://fwupd.org/downloads/e747a435bf24fd6081b77b6704b39cec5fa2dcf62e0ca6b86d8a6460121a1d07-heads_coreboot_x230-v0_2_3.cab", ) self.assertEqual( self.q.heads_update_sha, "ba519a7a5d8136c8ade0cf0c775c58f3165f42798ff631c3f57f075897ef1586", ) self.assertEqual(self.q.heads_update_version, "0.2.3") def test_check_heads_updates_no_updates(self): self.q.metadata_info = HEADS_XML self.q.heads_version = "0.2.3" return_code = self.q._parse_heads_updates("x230") self.assertEqual(return_code, self.qfwupd.EXIT_CODES["NOTHING_TO_DO"]) def test_check_heads_updates_lower_version(self): self.q.metadata_info = HEADS_XML self.q.heads_version = "0.2.2" return_code = self.q._parse_heads_updates("x230") self.assertEqual(return_code, self.qfwupd.EXIT_CODES["SUCCESS"]) self.assertEqual( self.q.heads_update_url, "https://fwupd.org/downloads/e747a435bf24fd6081b77b6704b39cec5fa2dcf62e0ca6b86d8a6460121a1d07-heads_coreboot_x230-v0_2_3.cab", ) self.assertEqual( self.q.heads_update_sha, "ba519a7a5d8136c8ade0cf0c775c58f3165f42798ff631c3f57f075897ef1586", ) self.assertEqual(self.q.heads_update_version, "0.2.3") @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_copy_heads_firmware(self): qmgr = self.qfwupd.QubesFwupdmgr() self.q.heads_update_url = "https://fwupd.org/downloads/e747a435bf24fd6081b77b6704b39cec5fa2dcf62e0ca6b86d8a6460121a1d07-heads_coreboot_x230-v0_2_3.cab" self.q.heads_update_sha = ( "ba519a7a5d8136c8ade0cf0c775c58f3165f42798ff631c3f57f075897ef1586" ) self.q.heads_update_version = "0.2.3" qmgr._download_firmware_updates( self.q.heads_update_url, self.q.heads_update_sha ) heads_boot_path = os.path.join( qf_heads.HEADS_UPDATES_DIR, self.q.heads_update_version ) if os.path.exists(heads_boot_path): shutil.rmtree(heads_boot_path) ret_code = self.q._copy_heads_firmware(qmgr.arch_path) self.assertNotEqual(ret_code, self.qfwupd.EXIT_CODES["NOTHING_TO_DO"]) firmware_path = os.path.join(heads_boot_path, "firmware.rom") self.assertTrue(os.path.exists(firmware_path)) if __name__ == "__main__": unittest.main() fwupd-1.9.16/contrib/qubes/test/test_qubes_fwupdmgr.py000077500000000000000000000416721460375044200231600ustar00rootroot00000000000000#!/usr/bin/python3 # # The Qubes OS Project, http://www.qubes-os.org # # Copyright (C) 2021 Norbert Kamiński # # SPDX-License-Identifier: LGPL-2.1+ # import json import unittest import os import subprocess import sys import imp import io import platform import tempfile from packaging.version import Version from pathlib import Path from .fwupd_logs import UPDATE_INFO, GET_DEVICES, DMI_DECODE from .fwupd_logs import GET_DEVICES_NO_UPDATES, GET_DEVICES_NO_VERSION from unittest.mock import patch QUBES_FWUPDMGR_REPO = "./src/qubes_fwupdmgr.py" QUBES_FWUPDMGR_BINDIR = "/usr/sbin/qubes-fwupdmgr" if os.path.exists(QUBES_FWUPDMGR_REPO): qfwupd = imp.load_source("qubes_fwupdmgr", QUBES_FWUPDMGR_REPO) elif os.path.exists(QUBES_FWUPDMGR_BINDIR): qfwupd = imp.load_source("qubes_fwupdmgr", QUBES_FWUPDMGR_BINDIR) FWUPD_DOM0_DIR = "/var/cache/fwupd/qubes" FWUPD_DOM0_UPDATES_DIR = os.path.join(FWUPD_DOM0_DIR, "updates") FWUPD_DOM0_UNTRUSTED_DIR = os.path.join(FWUPD_DOM0_UPDATES_DIR, "untrusted") FWUPD_DOM0_METADATA_DIR = os.path.join(FWUPD_DOM0_DIR, "metadata") FWUPD_DOM0_METADATA_FILE = os.path.join(FWUPD_DOM0_METADATA_DIR, "firmware.xml.xz") FWUPD_DOM0_METADATA_FILE_JCAT = os.path.join(FWUPD_DOM0_METADATA_DIR, "firmware.xml.xz") REQUIRED_DEV = "Requires device not connected" XL_LIST_LOG = "Name ID Mem VCPUs State Time(s)" FWUPDMGR = "/bin/fwupdmgr" BIOS_UPDATE_FLAG = os.path.join(FWUPD_DOM0_DIR, "bios_update") LVFS_TESTING_DOM0_FLAG = os.path.join(FWUPD_DOM0_DIR, "lvfs_testing") CUSTOM_METADATA = "https://fwupd.org/downloads/firmware-3c81bfdc9db5c8a42c09d38091944bc1a05b27b0.xml.gz" def device_connected_dom0(): """Checks if the testing device is connected in dom0""" if "qubes" not in platform.release(): return False q = qfwupd.QubesFwupdmgr() q._get_dom0_devices() return "ColorHug2" in q.dom0_devices_info def check_whonix_updatevm(): """Checks if the sys-whonix is running""" if "qubes" not in platform.release(): return False q = qfwupd.QubesFwupdmgr() return "sys-whonix" in q.output class TestQubesFwupdmgr(unittest.TestCase): def setUp(self): self.q = qfwupd.QubesFwupdmgr() self.maxDiff = 2000 self.captured_output = io.StringIO() self.orig_stdout = sys.stdout sys.stdout = self.captured_output def tearDown(self): sys.stdout = self.orig_stdout @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_download_metadata(self): self.q.metadata_file = FWUPD_DOM0_METADATA_FILE self.q._download_metadata(metadata_url=qfwupd.METADATA_URL) self.assertTrue( os.path.exists(FWUPD_DOM0_METADATA_FILE), msg="Metadata update file does not exist", ) self.assertTrue( os.path.exists(FWUPD_DOM0_METADATA_FILE_JCAT), msg="Metadata signature does not exist", ) @unittest.skipUnless(check_whonix_updatevm(), "Requires sys-whonix") def test_download_metadata_whonix(self): self.q.metadata_file = FWUPD_DOM0_METADATA_FILE self.q._download_metadata(whonix=True) self.assertTrue( os.path.exists(FWUPD_DOM0_METADATA_FILE), msg="Metadata update file does not exist", ) self.assertTrue( os.path.exists(FWUPD_DOM0_METADATA_FILE_JCAT), msg="Metadata signature does not exist", ) @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_download_custom_metadata(self): self.q.metadata_file = CUSTOM_METADATA.replace( "https://fwupd.org/downloads", FWUPD_DOM0_METADATA_DIR ) self.q.metadata_file_jcat = self.q.metadata_file + ".jcat" self.q._download_metadata(metadata_url=CUSTOM_METADATA) self.assertTrue( os.path.exists(self.q.metadata_file), msg="Metadata update file does not exist", ) self.assertTrue( os.path.exists(self.q.metadata_file_jcat), msg="Metadata signature does not exist", ) @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_refresh_metadata_dom0(self): self.q.refresh_metadata(metadata_url=qfwupd.METADATA_URL) self.assertEqual( self.captured_output.getvalue().strip(), "Successfully refreshed metadata manually", msg="Metadata refresh failed.", ) @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") @unittest.expectedFailure # fwupd refuses metadata downgrade def test_refresh_metadata_dom0_custom(self): self.q.refresh_metadata(metadata_url=CUSTOM_METADATA) self.assertEqual( self.captured_output.getvalue().strip(), "Successfully refreshed metadata manually", msg="Metadata refresh failed.", ) @unittest.skipUnless(check_whonix_updatevm(), "Requires sys-whonix") def test_refresh_metadata_whonix(self): self.q.refresh_metadata(whonix=True, metadata_url=qfwupd.METADATA_URL) self.assertEqual( self.captured_output.getvalue().strip(), "Successfully refreshed metadata manually", msg="Metadata refresh failed.", ) @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_get_dom0_updates(self): self.q._get_dom0_updates() self.assertIn( "Devices", self.q.dom0_updates_info, msg="Getting available updates failed" ) def test_parse_updates_info(self): self.q._parse_dom0_updates_info(UPDATE_INFO) self.assertEqual( self.q.dom0_updates_list[0]["Name"], "ColorHug2", msg="Wrong device name" ) self.assertEqual( self.q.dom0_updates_list[0]["Version"], "2.0.6", msg="Wrong update version" ) self.assertEqual( self.q.dom0_updates_list[0]["Releases"][0]["Url"], "https://fwupd.org/downloads/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab", msg="Wrong update URL", ) self.assertEqual( self.q.dom0_updates_list[0]["Releases"][0]["Checksum"], "32c4a2c9be787cdf1d757c489d6455bd7bb14053425180b6d331c37e1ccc1cda", msg="Wrong checksum", ) @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_download_firmware_updates(self): self.q._download_firmware_updates( "https://fwupd.org/downloads/e5ad222bdbd3d3d48d8613e67c7e0a0e194f" "8cd828e33c554d9f05d933e482c7-hughski-colorhug2-2.0.7.cab", "e5ad222bdbd3d3d48d8613e67c7e0a0e194f8cd828e33c554d9f05d933e482c7", ) update_path = os.path.join( FWUPD_DOM0_UPDATES_DIR, "e5ad222bdbd3d3d48d8613e67c7e0a0e194f8cd828e33c554d9f05d933e482c7" "-hughski-colorhug2-2.0.7.cab", ) self.assertTrue(os.path.exists(update_path)) @unittest.skipUnless(check_whonix_updatevm(), "Requires sys-whonix") def test_download_firmware_updates_whonix(self): self.q._download_firmware_updates( "https://fwupd.org/downloads/e5ad222bdbd3d3d48d8613e67c7e0a0e194f" "8cd828e33c554d9f05d933e482c7-hughski-colorhug2-2.0.7.cab", "e5ad222bdbd3d3d48d8613e67c7e0a0e194f8cd828e33c554d9f05d933e482c7", whonix=True, ) update_path = os.path.join( FWUPD_DOM0_UPDATES_DIR, "e5ad222bdbd3d3d48d8613e67c7e0a0e194f8cd828e33c554d9f05d933e482c7" "-hughski-colorhug2-2.0.7.cab", ) self.assertTrue(os.path.exists(update_path)) def test_user_input_empty_dict(self): self.assertEqual(self.q._user_input([]), -2) def test_user_input_n(self): user_input = ["sth", "n"] with patch("builtins.input", side_effect=user_input): self.q._parse_dom0_updates_info(UPDATE_INFO) choice = self.q._user_input(self.q.dom0_updates_list) self.assertEqual(choice, -2) user_input = ["sth", "N"] with patch("builtins.input", side_effect=user_input): self.q._parse_dom0_updates_info(UPDATE_INFO) choice = self.q._user_input(self.q.dom0_updates_list) self.assertEqual(choice, -2) def test_user_input_choice(self): user_input = ["6", "1"] with patch("builtins.input", side_effect=user_input): self.q._parse_dom0_updates_info(UPDATE_INFO) choice = self.q._user_input(self.q.dom0_updates_list) self.assertEqual(choice, 0) def test_parse_parameters(self): self.q._parse_dom0_updates_info(UPDATE_INFO) self.q._parse_parameters(self.q.dom0_updates_list, 0) self.assertEqual( self.q.url, "https://fwupd.org/downloads/0a29848de74d26348bc5a6e24fc9f03778eddf0e-hughski-colorhug2-2.0.7.cab", ) self.assertEqual( self.q.sha, "32c4a2c9be787cdf1d757c489d6455bd7bb14053425180b6d331c37e1ccc1cda", ) self.assertEqual(self.q.version, "2.0.7") @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_clean_cache_dom0(self): self.q.clean_cache() self.assertFalse(os.path.exists(FWUPD_DOM0_METADATA_DIR)) self.assertFalse(os.path.exists(FWUPD_DOM0_UNTRUSTED_DIR)) def test_output_crawler(self): crawler_output = io.StringIO() sys.stdout = crawler_output self.q._output_crawler(json.loads(UPDATE_INFO), 0) with open("test/logs/get_devices.log", "r") as get_devices: self.assertEqual( get_devices.read(), crawler_output.getvalue().strip() + "\n" ) sys.stdout = self.captured_output @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_get_dom0_devices(self): self.q._get_dom0_devices() self.assertIsNotNone(self.q.dom0_devices_info) @unittest.skipUnless("qubes" in platform.release(), "Requires Qubes OS") def test_get_devices_qubes_dom0(self): get_devices_output = io.StringIO() sys.stdout = get_devices_output self.q.get_devices_qubes() self.assertNotEqual(get_devices_output.getvalue().strip(), "") sys.stdout = self.captured_output @unittest.skipUnless(device_connected_dom0(), REQUIRED_DEV) def test_get_updates_qubes_dom0(self): get_updates_output = io.StringIO() sys.stdout = get_updates_output self.q.get_updates_qubes() self.assertNotEqual(get_updates_output.getvalue().strip(), "") sys.stdout = self.captured_output def test_help(self): help_output = io.StringIO() sys.stdout = help_output self.q.help() with open("test/logs/help.log", "r") as help_log: self.assertEqual(help_log.read(), help_output.getvalue().strip() + "\n") sys.stdout = self.captured_output @patch( "test.test_qubes_fwupdmgr.qfwupd.QubesFwupdmgr._read_dmi", return_value=DMI_DECODE, ) def test_verify_dmi(self, output): self.q.dmi_version = "P.1.0" with tempfile.TemporaryDirectory() as tmpdir: arch_name = tmpdir + "/firmware.cab" subprocess.check_call( ["gcab", "-c", arch_name, "firmware.metainfo.xml"], cwd="test/logs" ) self.q._verify_dmi(arch_name, "P1.1") @patch( "test.test_qubes_fwupdmgr.qfwupd.QubesFwupdmgr._read_dmi", return_value=DMI_DECODE, ) def test_verify_dmi_wrong_vendor(self, output): with self.assertRaises(ValueError) as wrong_vendor: self.q.dmi_version = "P.1.0" with tempfile.TemporaryDirectory() as tmpdir: arch_name = tmpdir + "/firmware.cab" subprocess.check_call( ["gcab", "-c", arch_name, "firmware.metainfo.xml"], cwd="test/logs/metainfo_name", ) self.q._verify_dmi(arch_name, "P1.1") self.assertIn("Wrong firmware provider.", str(wrong_vendor.exception)) @patch( "test.test_qubes_fwupdmgr.qfwupd.QubesFwupdmgr._read_dmi", return_value=DMI_DECODE, ) def test_verify_dmi_version(self, output): self.q.dmi_version = "P1.0" with self.assertRaises(ValueError) as downgrade: with tempfile.TemporaryDirectory() as tmpdir: arch_name = tmpdir + "/firmware.cab" subprocess.check_call( ["gcab", "-c", arch_name, "firmware.metainfo.xml"], cwd="test/logs/metainfo_version", ) self.q._verify_dmi(arch_name, "P0.1") self.assertIn("P0.1 < P1.0 Downgrade not allowed", str(downgrade.exception)) @unittest.skipUnless(device_connected_dom0(), REQUIRED_DEV) def test_downgrade_firmware_dom0(self): old_version = None self.q._get_dom0_devices() downgrades = self.q._parse_downgrades(self.q.dom0_devices_info) for number, device in enumerate(downgrades): if "Name" not in device: continue if device["Name"] == "ColorHug2": old_version = device["Version"] break if old_version is None: self.fail("Test device not found") user_input = [str(number + 1), "1"] with patch("builtins.input", side_effect=user_input): self.q.downgrade_firmware() self.q._get_dom0_devices() downgrades = self.q._parse_downgrades(self.q.dom0_devices_info) new_version = downgrades[number]["Version"] self.assertGreater(Version(old_version), Version(new_version)) def test_parse_downgrades(self): downgrades = self.q._parse_downgrades(GET_DEVICES) self.assertEqual(downgrades[0]["Name"], "ColorHug2") self.assertEqual(downgrades[0]["Version"], "2.0.6") self.assertEqual(downgrades[0]["Releases"][0]["Version"], "2.0.5") self.assertEqual( downgrades[0]["Releases"][0]["Url"], "https://fwupd.org/downloads/f7dd4ab29fa610438571b8b62b26b0b0e57bb35b-hughski-colorhug2-2.0.5.cab", ) self.assertEqual( downgrades[0]["Releases"][0]["Checksum"], "8cd379eb2e1467e4fda92c20650306dc7e598b1d421841bbe19d9ed6ea01e3ee", ) def test_parse_downgrades_no_version(self): downgrades = self.q._parse_downgrades(GET_DEVICES_NO_VERSION) self.assertEqual(downgrades[0]["Name"], "ColorHug2") self.assertEqual(downgrades[0]["Version"], "2.0.6") self.assertEqual(downgrades[0]["Releases"][0]["Version"], "2.0.5") self.assertEqual( downgrades[0]["Releases"][0]["Url"], "https://fwupd.org/downloads/f7dd4ab29fa610438571b8b62b26b0b0e57bb35b-hughski-colorhug2-2.0.5.cab", ) self.assertEqual( downgrades[0]["Releases"][0]["Checksum"], "4ee9dfa38df3b810f739d8a19d13da1b3175fb87", ) def test_user_input_downgrade_dom0(self): user_input = ["1", "6", "sth", "2.2.1", "", " ", "\0", "2"] with patch("builtins.input", side_effect=user_input): downgrade_list = self.q._parse_downgrades(GET_DEVICES) downgrade_dict = downgrade_list device_choice, downgrade_choice = self.q._user_input( downgrade_dict, downgrade=True ) self.assertEqual(device_choice, 0) self.assertEqual(downgrade_choice, 1) def test_user_input_downgrade_N(self): user_input = ["N"] with patch("builtins.input", side_effect=user_input): downgrade_list = self.q._parse_downgrades(GET_DEVICES) N_choice = self.q._user_input(downgrade_list, downgrade=True) self.assertEqual(N_choice, -2) @unittest.skipUnless(device_connected_dom0(), REQUIRED_DEV) def test_update_firmware_dom0(self): old_version = None new_version = None self.q._get_dom0_updates() self.q._parse_dom0_updates_info(self.q.dom0_updates_info) for number, device in enumerate(self.q.dom0_updates_list): if "Name" not in device: continue if device["Name"] == "ColorHug2": old_version = device["Version"] break if old_version is None: self.fail("Test device not found") user_input = [str(number + 1)] with patch("builtins.input", side_effect=user_input): self.q.update_firmware() self.q._get_dom0_devices() dom0_devices_info_dict = json.loads(self.q.dom0_devices_info) for device in dom0_devices_info_dict["Devices"]: if "Name" not in device: continue if device["Name"] == "ColorHug2": new_version = device["Version"] break if new_version is None: self.fail("Test device not found") self.assertLess(Version(old_version), Version(new_version)) if __name__ == "__main__": unittest.main() fwupd-1.9.16/contrib/reformat-code.py000077500000000000000000000043121460375044200175060ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Dell Inc. # # SPDX-License-Identifier: LGPL-2.1+ # import os import sys import subprocess import argparse CLANG_DIFF_FORMATTERS = [ "clang-format-diff-11", "clang-format-diff-13", "clang-format-diff", ] def parse_args(): parser = argparse.ArgumentParser( description="Reformat C code to match project style", epilog="Call with no argument to reformat uncommitted code.", ) parser.add_argument( "commit", nargs="*", default="", help="Reformat all changes since this commit" ) parser.add_argument( "--debug", action="store_true", help="Display all launched commands" ) return parser.parse_args() def select_clang_version(formatters): for formatter in formatters: try: ret = subprocess.check_call( [formatter, "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if ret == 0: return formatter except FileNotFoundError: continue print("No clang formatter installed") sys.exit(1) ## Entry Point ## if __name__ == "__main__": args = parse_args() base = os.getenv("GITHUB_BASE_REF") if base: base = f"origin/{base}" else: if args.commit: base = args.commit[0] else: base = "HEAD" cmd = ["git", "describe", base] if args.debug: print(cmd) ret = subprocess.run(cmd, capture_output=True) if ret.returncode: if args.debug: print(ret.stderr) base = "HEAD" print(f"Reformatting code against {base}") formatter = select_clang_version(CLANG_DIFF_FORMATTERS) cmd = ["git", "diff", "-U0", base] if args.debug: print(cmd) ret = subprocess.run(cmd, capture_output=True, text=True) if ret.returncode: print(f"Failed to run {cmd}\n{ret.stderr.strip()}") sys.exit(1) cmd = [formatter, "-i", "-regex", "^.*\\.(c|h|proto)$", "-p1"] if args.debug: print(cmd) ret = subprocess.run(cmd, input=ret.stdout, capture_output=True, text=True) if ret.returncode: print(f"Failed to run {cmd}\n{ret.stderr.strip()}") sys.exit(1) sys.exit(0) fwupd-1.9.16/contrib/run-tests.sh000077500000000000000000000000551460375044200167050ustar00rootroot00000000000000#!/bin/sh meson build && ninja -C build test fwupd-1.9.16/contrib/setup000077500000000000000000000105251460375044200154730ustar00rootroot00000000000000#!/bin/bash -e # Setup the repository and local system for development cd "$(dirname "$0")/.." HELPER=./contrib/ci/fwupd_setup_helpers.py HELPER_ARGS="-y" rename_branch() { OLD=master NEW=main if git log $OLD >/dev/null 2>&1 && git remote get-url origin 2>&1 | grep fwupd/fwupd.git >/dev/null 2>&1; then read -p "Rename existing $OLD branch to $NEW? (y/N) " question if [ "$question" = "y" ]; then git branch -m $OLD $NEW git fetch origin git branch -u origin/$NEW $NEW git remote set-head origin -a fi fi } setup_deps() { read -p "Install build dependencies? (y/N) " question if [ "$question" = "y" ]; then $(which sudo) python3 $HELPER install-dependencies $HELPER_ARGS -y fi } setup_run_wrappers() { BASE=../../contrib/ BIN=venv/bin/ TEMPLATE=${BASE}/launch-venv.sh # launch wrappers for F in fwupdtool fwupdmgr fwupd; do rm -f ${BIN}/${F} ln -s $TEMPLATE ${BIN}/${F} done # build wrapper rm -f ${BIN}/build-fwupd ln -s ${BASE}/build-venv.sh ${BIN}/build-fwupd } setup_vscode() { # Add default vscode settings and debug launcher SOURCED=./contrib/vscode TARGETD=./.vscode SETTINGS_F=settings.json LAUNCH_F=launch.json for f in $SETTINGS_F $LAUNCH_F; do TEMPLATE=${SOURCED}/${f} TARGETF=${TARGETD}/${f} mkdir -p ${TARGETD} echo "Copy ${TEMPLATE} to ${TARGETF}." cp "${TEMPLATE}" "${TARGETF}" done } setup_git() { echo "Configuring git environment" git config include.path ../.gitconfig } install_pip() { package=$1 args=$2 if ! python3 -m pip install $package $args; then $(which sudo) python3 $HELPER install-pip $HELPER_ARGS -y fi #try once more python3 -m pip install $package } setup_virtualenv() { echo "Setting up virtualenv" if ! which virtualenv >/dev/null 2>&1; then install_pip virtualenv fi virtualenv --system-site-packages venv --prompt fwupd source venv/bin/activate cat >> venv/bin/activate << EOF echo "To build or rebuild fwupd within development environment run:" echo "" echo "# build-fwupd" echo "" echo "To run any tool under gdbserver add DEBUG=1 to env, for example:" echo "" echo "# DEBUG=1 fwupdtool get-devices" echo "" echo "To leave fwupd development environment run:" echo "" echo "# deactivate" . data/bash-completion/fwupdtool . data/bash-completion/fwupdmgr export MANPATH=./venv/dist/share/man: EOF } setup_precommit() { echo "Configuring pre-commit hooks" install_pip pre-commit pre-commit install } setup_prepush() { read -p "Run tests locally before pushing to remote branches? THIS WILL SLOW DOWN EVERY PUSH but reduce the risk of failing CI. (y/N) " question if [ "$question" = "y" ]; then pre-commit install -t pre-push else pre-commit uninstall -t pre-push fi } check_markdown() { python3 $HELPER test-markdown } check_jinja2() { python3 $HELPER test-jinja2 } check_meson() { python3 $HELPER test-meson } detect_os() { for i in "$@"; do case $i in --os=*) OS="${i#*=}" shift ;; --debug) DEBUG=1 shift ;; *) ;; esac done if [ -z $OS ]; then OS=$(python3 $HELPER detect-profile) if [ -z "$OS" ]; then install_pip distro OS=$(python3 $HELPER detect-profile) fi echo "Using OS profile $OS to setup" fi if [ -n "$OS" ];then HELPER_ARGS="$HELPER_ARGS --os $OS" fi if [ -n "$DEBUG" ]; then set -x HELPER_ARGS="$HELPER_ARGS --debug" fi } #needed for arguments for some commands detect_os "$@" #if interactive install build deps and prepare environment if [ -t 2 ]; then case $OS in debian|ubuntu|arch|fedora) setup_deps ;; void) setup_deps ;; esac rename_branch fi setup_virtualenv setup_run_wrappers check_markdown check_jinja2 setup_vscode setup_git check_meson setup_precommit #needs to be after pre-commit is sourced if [ -t 2 ]; then setup_prepush fi echo "" echo "To enter fwupd development environment run:" echo "" echo "# source venv/bin/activate" echo "" fwupd-1.9.16/contrib/snap/000077500000000000000000000000001460375044200153435ustar00rootroot00000000000000fwupd-1.9.16/contrib/snap/README.md000066400000000000000000000015631460375044200166270ustar00rootroot00000000000000# Snap support Snaps are containerised software packages that are simple to create and install. They auto-update and are safe to run. And because they bundle their dependencies, they work on all major Linux systems without modification. ## stable vs unstable Two yaml files are distributed: * snapcraft.yaml This uses tarball releases for all dependencies and what is currently in tree for fwupd. * snapcraft-master.yaml This uses git for most dependencies and may be considered unstable. ## Building Builds can be performed using snapcraft: ```shell # snapcraft cleanbuild ``` ## Installing A "classic" snap is produced, and locally built snaps can be installed like this: ```shell # snap install fwupd_daily_amd64.snap --dangerous --classic ``` The `--dangerous` flag is because snaps built locally are not signed. Snaps distributed by a store will not need this flag. fwupd-1.9.16/contrib/snap/fwupd-command000077500000000000000000000042051460375044200200330ustar00rootroot00000000000000#!/bin/sh export XDG_CACHE_HOME=$SNAP_USER_COMMON/.cache mkdir -p $XDG_CACHE_HOME export GIO_MODULE_DIR=$XDG_CACHE_HOME/gio-modules export XDG_DATA_DIRS="$SNAP/usr/share" export FWUPD_LOCKDIR=/run/lock/snap.fwupd export FWUPD_POLKIT_NOCHECK=1 export FWUPD_HOSTFS_ROOT=/var/lib/snapd/hostfs #determine architecture if [ "$SNAP_ARCH" = "amd64" ]; then ARCH="x86_64-linux-gnu" elif [ "$SNAP_ARCH" = "armhf" ]; then ARCH="arm-linux-gnueabihf" elif [ "$SNAP_ARCH" = "arm64" ]; then ARCH="aarch64-linux-gnu" else ARCH="$SNAP_ARCH-linux-gnu" fi # re-generate gio modules in local cache needs_update=true if [ -f $SNAP_USER_DATA/.last_revision ]; then # shellcheck source=/dev/null . $SNAP_USER_DATA/.last_revision 2>/dev/null fi if [ "$SNAP_DESKTOP_LAST_REVISION" = "$SNAP_REVISION" ]; then needs_update=false fi if [ $needs_update = true ]; then if [ -f $SNAP/usr/lib/$ARCH/glib-2.0/gio-querymodules ]; then rm -rf $GIO_MODULE_DIR mkdir -p $GIO_MODULE_DIR ln -s $SNAP/usr/lib/$ARCH/gio/modules/*.so $GIO_MODULE_DIR $SNAP/usr/lib/$ARCH/glib-2.0/gio-querymodules $GIO_MODULE_DIR fi echo "SNAP_DESKTOP_LAST_REVISION=$SNAP_REVISION" > $SNAP_USER_DATA/.last_revision fi # Setup directory structures if command was run as root if [ "$(id -u)" = "0" ]; then CONF_DIR="${SNAP_COMMON}/var/etc/fwupd" REMOTE_DIR="${SNAP_COMMON}/var/lib/fwupd/remotes.d" SHARE_DIR="${SNAP_COMMON}/share/fwupd/remotes.d/vendor/firmware" mkdir -p ${CONF_DIR} ${REMOTE_DIR} ${SHARE_DIR} # copy a writable fwupd.conf for users to use if [ ! -f "${CONF_DIR}/fwupd.conf" ]; then cp "$SNAP/etc/fwupd/fwupd.conf" "${CONF_DIR}/fwupd.conf" fi # Migrate remotes from "old" snap guidance and from immutable directory for BASE in "${SNAP}/etc/fwupd/remotes.d" "${SNAP_USER_DATA}/etc/fwupd/remotes.d/"; do if [ ! -d "${BASE}" ]; then continue fi for P in ${BASE}/*.conf; do REMOTE=$(basename $P) # vendor.conf doesn't make sense in snap if [ "${REMOTE}" = "vendor.conf" ]; then continue fi if [ ! -f "${REMOTE_DIR}/${REMOTE}" ]; then cp ${P} "${REMOTE_DIR}" fi done done fi exec "$@" fwupd-1.9.16/contrib/snap/fwupd.wrapper000077500000000000000000000001041460375044200200700ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/libexec/fwupd/fwupd "$@" fwupd-1.9.16/contrib/snap/fwupdmgr.wrapper000077500000000000000000000000751460375044200206050ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/bin/fwupdmgr "$@" fwupd-1.9.16/contrib/snap/fwupdtool.wrapper000077500000000000000000000000761460375044200207760ustar00rootroot00000000000000#!/bin/sh exec "$SNAP/fwupd-command" $SNAP/bin/fwupdtool "$@" fwupd-1.9.16/contrib/snap/snapcraft.yaml000066400000000000000000000217271460375044200202210ustar00rootroot00000000000000name: fwupd adopt-info: fwupd summary: A standalone version of fwupd to install newer firmware updates description: | This is a tool that can be used to install firmware updates on devices not yet supported by the version of fwupd distributed with the OS. grade: stable confinement: strict base: core22 license: LGPL-2.1+ architectures: - amd64 slots: fwupd: interface: fwupd fwupd-dbus: interface: dbus bus: system name: org.freedesktop.fwupd plugs: fwupdmgr: interface: fwupd polkit: interface: polkit action-prefix: org.freedesktop.fwupd apps: fwupdtool: command: fwupdtool.wrapper plugs: [bluez, udisks2, modem-manager, upower-observe, network, hardware-observe, home, opengl, raw-usb] slots: [fwupd] completer: share/bash-completion/completions/fwupdtool environment: FWUPD_HOSTDIR: /var/lib/snapd/hostfs fwupd: command: fwupd.wrapper daemon: dbus slots: [fwupd] plugs: [bluez, udisks2, modem-manager, upower-observe, polkit, network, hardware-observe, home, opengl, raw-usb] daemon-scope: system activates-on: - fwupd-dbus environment: FWUPD_HOSTDIR: /var/lib/snapd/hostfs fwupdmgr: command: fwupdmgr.wrapper plugs: [fwupdmgr, home, network, polkit, shutdown] completer: share/bash-completion/completions/fwupdmgr parts: #needed for UEFI plugin to build UX labels build-introspection: plugin: nil stage-packages: - python3-gi-cairo override-stage: | craftctl default prime: - -etc - -usr - -var pkttyagent: plugin: nil stage-packages: - polkitd - libpolkit-agent-1-0 prime: - usr/bin/pkttyagent - usr/lib/*/libpolkit-agent-1.so* fwupd: plugin: meson meson-parameters: [--prefix=/, -Defi_binary=false, -Ddocs=disabled, -Dbuild=all, -Dintrospection=disabled, -Dman=false, -Dpython=/usr/bin/python3, -Dplugin_flashrom=disabled, -Dplugin_powerd=disabled, -Dudevdir=$CRAFT_STAGE/lib/udev, -Defi_os_dir=fwupd, "-Dgusb:tests=false", "-Dgusb:docs=false", "-Dgusb:introspection=false", "-Dgusb:vapi=false", "-Dlibxmlb:gtkdoc=false", "-Dlibxmlb:introspection=false", "-Dlibjcat:man=false", "-Dlibjcat:gtkdoc=false", "-Dlibjcat:introspection=false", "-Dlibjcat:tests=false"] source: . source-type: git override-build: | ${CRAFT_PART_SRC}/contrib/ci/fwupd_setup_helpers.py test-meson craftctl default for name in ${CRAFT_PART_INSTALL}/lib/*/fwupd-*/libfwupd*; do mv ${name} `dirname ${name}`/.. ;\ done # fixes up commands like fwupdtool -> fwupd.fwupdtool sed -i "s,\(complete -F _fwupd[a-z]*\) \(fwupd.*\),\1 fwupd.\2,; \ s,\(command.*\)\(fwupdtool\),\1fwupd.\2,; \ s,\(command.*\)\(fwupdmgr\),\1fwupd.\2,; \ s,\(command.*\)\(fwupdagent\),\1fwupd.\2," \ ${CRAFT_PART_INSTALL}/share/bash-completion/completions/* # make installed tests runnable sed -i "s,\(fwupdmgr\),fwupd.\1,; \ s,\(fwupdtool\),fwupd.\1," \ ${CRAFT_PART_INSTALL}/share/installed-tests/fwupd/*.sh sed -i "s,\(/share\),/snap/fwupd/current/\1," \ ${CRAFT_PART_INSTALL}/share/installed-tests/fwupd/fwupdtool.sh sed -i "s,\(/libexec\),/snap/fwupd/current/\1," \ ${CRAFT_PART_INSTALL}/share/installed-tests/fwupd/fwupd.sh sed -i 's,^MetadataURI=.*$,MetadataURI=file:///snap/fwupd/current/share/installed-tests/fwupd/fwupd-tests.xml,' "${CRAFT_PART_INSTALL}/share/fwupd/remotes.d/fwupd-tests.conf" # fixes up dbus service for classic snap sed -i 's!SystemdService=\(.*\)!SystemdService=snap.fwupd.fwupd.service!' \ ${CRAFT_PART_INSTALL}/share/dbus-1/system-services/org.freedesktop.fwupd.service cp -R /usr/libexec/fwupd/efi ${CRAFT_PART_INSTALL}/libexec/fwupd cp /usr/lib/shim/shimx64.efi.signed ${CRAFT_PART_INSTALL}/libexec/fwupd/efi/ sed -i 's,^MetadataURI=.*$,MetadataURI=file:///var/snap/fwupd/common/share/fwupd/remotes.d/vendor/firmware,' "${CRAFT_PART_INSTALL}/etc/fwupd/remotes.d/vendor-directory.conf" override-pull: | craftctl default craftctl set version=$(git describe HEAD --always) build-packages: - bash-completion - curl - fwupd-signed - fwupd-unsigned - git - gettext - gnu-efi - fwupd-unsigned-dev - libcurl4-openssl-dev - libarchive-dev - libcbor-dev - libcairo-dev - libgudev-1.0-dev - libglib2.0-dev - libgnutls28-dev - libgpgme11-dev - libgusb-dev - libjson-glib-dev - libjcat-dev - liblzma-dev - libpango1.0-dev - libpolkit-gobject-1-dev - libprotobuf-c-dev - libsqlite3-dev - libsystemd-dev - libtss2-dev - libxmlb-dev - libmm-glib-dev - libqmi-glib-dev - libmbim-glib-dev - locales - meson - modemmanager - pkg-config - python3-pip - python3-cairo - python3-gi - python3-jinja2 - protobuf-c-compiler - shim-signed - systemd - uuid-dev stage-packages: - libnghttp2-14 - libpsl5 - librtmp1 - libarchive13 - libcurl4 - libcbor0.8 - libassuan0 - libffi8 - libjcat1 - liblzma5 - libgusb2 - libusb-1.0-0 - libgudev-1.0-0 - libgpgme11 - libprotobuf-c1 - libpolkit-gobject-1-0 - libtss2-esys-3.0.2-0 - libtss2-fapi1 - libtss2-mu0 - libtss2-rc0 - libtss2-sys1 - libtss2-tcti-cmd0 - libtss2-tcti-device0 - libtss2-tcti-mssim0 - libtss2-tcti-swtpm0 - libtss2-tctildr0 - libxmlb2 - glib-networking - libglib2.0-bin - libglib2.0-0 - libmm-glib0 - libqmi-glib5 - libmbim-glib4 prime: # we explicitly don't want /usr/bin/gpgconf # this will cause gpgme to error finding it # but that also avoids trying to use nonexistent # /usr/bin/gpg2 - -etc/dconf/db - -etc/grub.d - -etc/systemd/system - -libexec/installed-tests - -share/fwupd/*.py - -share/fish - -root - -usr/bin - -usr/sbin - -usr/libexec - -usr/share/man - -usr/share/GConf - -etc/X11 - -etc/ldap - -etc/logcheck - -usr/lib/dconf - -usr/lib/gcc - -usr/lib/glib-networking - -usr/lib/gnupg - -usr/lib/gnupg2 - -usr/lib/sasl2 - -usr/lib/systemd - -usr/lib/*/audit - -usr/share/bash-completion - -usr/share/dbus-1 - -usr/share/gnupg - -usr/share/glib-2.0/schemas - -usr/share/session-migration - -usr/share/upstart - -usr/share/X11 - -include - -lib/systemd - -lib/udev - -lib/*/pkgconfig - -usr/share/lintian - -usr/share/pkgconfig - -usr/share/polkit-1 - -usr/share/vala - -usr/share/doc - -usr/share/gnupg2 - -usr/share/info - -usr/share/gir-1.0 - -usr/share/upstart - -usr/lib/*/libicu* - -usr/lib/*/pkgconfig - -usr/lib/*/libnpth* - -usr/lib/*/libgthread* - -usr/lib/*/libksba* - -usr/lib/*/gio/modules/libdconfsettings.so - -usr/lib/systemd/user/dconf.service - -usr/share/dbus-1/services/ca.desrt.dconf.service - -usr/lib/*/libdconf.so.1.0.0 after: [build-introspection] build-environment: - PYTHONPATH: "${CRAFT_STAGE}/usr/lib/python3/dist-packages" update-mime: plugin: make source: contrib/snap/update-mime stage-packages: - shared-mime-info - gsettings-desktop-schemas - libxml2 prime: - -usr/bin - -usr/share/doc - -usr/share/doc-base - -usr/share/man - -usr/share/lintian - -usr/share/pkgconfig - -usr/share/GConf - -usr/lib/dconf - -usr/lib/*/gio/modules/libdconfsettings.so - -usr/lib/systemd/user/dconf.service - -usr/share/dbus-1/services/ca.desrt.dconf.service - -usr/lib/*/libdconf.so.1.0.0 after: [fwupd] fwupd-wrappers: plugin: dump source: contrib/snap stage: - fwupd-command - fwupdtool.wrapper - fwupd.wrapper - fwupdmgr.wrapper policy: plugin: nil after: - fwupd override-build: | mkdir -p "${CRAFT_PART_INSTALL}/meta/polkit/polkit.fwupd/" cp "${CRAFT_STAGE}/share/polkit-1/actions/org.freedesktop.fwupd.policy" \ "${CRAFT_PART_INSTALL}/meta/polkit/polkit.org.freedesktop.fwupd.policy" fwupd-1.9.16/contrib/snap/update-mime/000077500000000000000000000000001460375044200175525ustar00rootroot00000000000000fwupd-1.9.16/contrib/snap/update-mime/Makefile000066400000000000000000000002021460375044200212040ustar00rootroot00000000000000build: true install: update-mime-database ../install/usr/share/mime glib-compile-schemas ../install/usr/share/glib-2.0/schemas fwupd-1.9.16/contrib/standalone-installer/000077500000000000000000000000001460375044200205255ustar00rootroot00000000000000fwupd-1.9.16/contrib/standalone-installer/README.md000066400000000000000000000004361460375044200220070ustar00rootroot00000000000000# Standalone installer This is a script that will build a standalone installer around the fwupd snap or flatpak. This can be used for distributing updates that use fwupd on machines without networking and the needed tools. For usage instructions, view: ```shell ./make.py --help ``` fwupd-1.9.16/contrib/standalone-installer/assets/000077500000000000000000000000001460375044200220275ustar00rootroot00000000000000fwupd-1.9.16/contrib/standalone-installer/assets/header.py000066400000000000000000000246571460375044200236470ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Dell, Inc. # # SPDX-License-Identifier: LGPL-2.1+ # from base64 import b64decode import io import os import subprocess import sys import shutil import tempfile import zipfile TAG = b"#\x00" def parse_args(): import argparse parser = argparse.ArgumentParser(description="Self extracting firmware updater") parser.add_argument("--directory", help="Directory to extract to") parser.add_argument( "--cleanup", action="store_true", help="Remove tools when done with installation", ) parser.add_argument( "--verbose", action="store_true", help="Run the tool in verbose mode" ) parser.add_argument( "--allow-reinstall", action="store_true", help="Allow re-installing existing firmware versions", ) parser.add_argument( "--allow-older", action="store_true", help="Allow downgrading firmware versions" ) parser.add_argument( "command", choices=["install", "extract"], help="Command to run" ) args = parser.parse_args() return args def error(msg): print(msg) sys.exit(1) def bytes_slicer(length, source): start = 0 stop = length while start < len(source): yield source[start:stop] start = stop stop += length def get_zip(): script = os.path.realpath(__file__) bytes_out = io.BytesIO() with open(script, "rb") as source: for line in source: if not line.startswith(TAG): continue bytes_out.write(b64decode(line[len(TAG) : -1])) return bytes_out def unzip(destination): zipf = get_zip() source = zipfile.ZipFile(zipf, "r") for item in source.namelist(): # extract handles the sanitization source.extract(item, destination) def copy_cabs(source, target): if not os.path.exists(target): os.makedirs(target) cabs = [] for root, dirs, files in os.walk(source): for f in files: if f.endswith(".cab"): origf = os.path.join(root, f) shutil.copy(origf, target) cabs.append(os.path.join(target, f)) return cabs def install_snap(directory, verbose, allow_reinstall, allow_older, uninstall): app = "fwupd" common = f"/root/snap/{app}/common" # check if snap is installed with open(os.devnull, "w") as devnull: subprocess.run(["snap"], check=True, stdout=devnull, stderr=devnull) # check existing installed cmd = ["snap", "list", app] with open(os.devnull, "w") as devnull: if verbose: print(cmd) ret = subprocess.run(cmd, stdout=devnull, stderr=devnull) if ret.returncode == 0: cmd = ["snap", "remove", app] if verbose: print(cmd) subprocess.run(cmd, check=True) # install the snap cmd = ["snap", "ack", os.path.join(directory, "fwupd.assert")] if verbose: print(cmd) subprocess.run(cmd, check=True) cmd = ["snap", "install", "--classic", os.path.join(directory, "fwupd.snap")] if verbose: print(cmd) subprocess.run(cmd, check=True) # copy the CAB files cabs = copy_cabs(directory, common) # run the snap for cab in cabs: cmd = [f"{app}.fwupdmgr", "install", cab] if allow_reinstall: cmd += ["--allow-reinstall"] if allow_older: cmd += ["--allow-older"] if verbose: cmd += ["--verbose"] print(cmd) subprocess.run(cmd) # remove copied cabs for f in cabs: os.remove(f) # cleanup if uninstall: cmd = ["snap", "remove", app] if verbose: print(cmd) subprocess.run(cmd) def install_flatpak(directory, verbose, allow_reinstall, allow_older, uninstall): app = "org.freedesktop.fwupd" common = f"{os.getenv('HOME')}/.var/app/{app}" with open(os.devnull, "w") as devnull: if not verbose: output = devnull else: output = None # look for dependencies dep = "org.gnome.Platform/x86_64/3.30" repo = "flathub" repo_url = "https://flathub.org/repo/flathub.flatpakrepo" cmd = ["flatpak", "info", dep] if verbose: print(cmd) ret = subprocess.run(cmd, stdout=output, stderr=output) # not installed if ret.returncode != 0: # look for remotes cmd = ["flatpak", "remote-info", repo, dep] if verbose: print(cmd) ret = subprocess.run(cmd, stdout=output, stderr=output) # not enabled, enable it if ret.returncode != 0: cmd = ["flatpak", "remote-add", repo, repo_url] if verbose: print(cmd) ret = subprocess.run(cmd, stderr=output) # install dep cmd = ["flatpak", "install", repo, dep] if verbose: print(cmd) ret = subprocess.run(cmd) # check existing installed cmd = ["flatpak", "info", app] if verbose: print(cmd) ret = subprocess.run(cmd, stdout=output, stderr=output) if ret.returncode == 0: cmd = ["flatpak", "remove", app] if verbose: print(cmd) subprocess.run(cmd, check=True) # install the flatpak cmd = ["flatpak", "install", os.path.join(directory, "fwupd.flatpak")] if verbose: print(cmd) subprocess.run(cmd, check=True) # copy the CAB files cabs = copy_cabs(directory, common) # run command for cab in cabs: cmd = ["flatpak", "run", app, "install", cab] if allow_reinstall: cmd += ["--allow-reinstall"] if allow_older: cmd += ["--allow-older"] if verbose: cmd += ["--verbose"] print(cmd) subprocess.run(cmd) # remove copied cabs for f in cabs: os.remove(f) # cleanup if uninstall: cmd = ["flatpak", "remove", app] if verbose: print(cmd) subprocess.run(cmd) # Check which package to use # - return False to use packaged version # - return True for snap/flatpak def use_included_version(minimum_version): try: import apt except ModuleNotFoundError: return True cache = apt.Cache() pkg = cache.get("fwupd") version = pkg.installed if not version: return True if minimum_version: if minimum_version > version: print( "fwupd %s is already installed but this package requires %s" % (version.version, minimum_version) ) else: print( "Using existing fwupd version %s already installed on system." % version.version ) return False else: print(f"fwupd {version.version} is installed and must be removed") return remove_packaged_version(pkg, cache) def remove_packaged_version(pkg, cache): res = False while True: res = input("Remove now (Y/N)? ") if res.lower() == "n": res = False break if res.lower() == "y": res = True break if res: pkg.mark_delete() res = cache.commit() if not res: raise Exception("Need to remove packaged version") return True def install_builtin(directory, verbose, allow_reinstall, allow_older): cabs = [] for root, dirs, files in os.walk(directory): for f in files: if f.endswith(".cab"): cabs.append(os.path.join(root, f)) # run command for cab in cabs: cmd = ["fwupdmgr", "install", cab] if allow_reinstall: cmd += ["--allow-reinstall"] if allow_older: cmd += ["--allow-older"] if verbose: cmd += ["--verbose"] print(cmd) subprocess.run(cmd) def run_installation(directory, verbose, allow_reinstall, allow_older, uninstall): try_snap = False try_flatpak = False # determine if a minimum version was specified minimum_path = os.path.join(directory, "minimum") minimum = None if os.path.exists(minimum_path): with open(minimum_path, "r") as rfd: minimum = rfd.read() if not use_included_version(minimum): install_builtin(directory, verbose, allow_reinstall, allow_older) return # determine what self extracting binary has if os.path.exists(os.path.join(directory, "fwupd.snap")) and os.path.exists( os.path.join(directory, "fwupd.assert") ): try_snap = True if os.path.exists(os.path.join(directory, "fwupd.flatpak")): try_flatpak = True if try_snap: try: install_snap(directory, verbose, allow_reinstall, allow_older, uninstall) return True except Exception: if verbose: print("Snap installation failed") if not try_flatpak: error("Snap installation failed") if try_flatpak: install_flatpak(directory, verbose, allow_reinstall, allow_older, uninstall) if __name__ == "__main__": args = parse_args() if "extract" in args.command: if args.allow_reinstall: error( "allow-reinstall argument doesn't make sense with command %s" % args.command ) if args.allow_older: error( f"allow-older argument doesn't make sense with command {args.command}" ) if args.cleanup: error(f"Cleanup argument doesn't make sense with command {args.command}") if args.directory is None: error("No directory specified") if not os.path.exists(args.directory): print(f"Creating {args.directory}") os.makedirs(args.directory) unzip(args.directory) else: if args.directory: error( "Directory argument %s doesn't make sense with command %s" % (args.directory, args.command) ) if os.getuid() != 0: error("This tool must be run as root") with tempfile.TemporaryDirectory(prefix="fwupd") as target: unzip(target) run_installation( target, args.verbose, args.allow_reinstall, args.allow_older, args.cleanup, ) fwupd-1.9.16/contrib/standalone-installer/make.py000077500000000000000000000115471460375044200220270ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright (C) 2017 Dell, Inc. # # SPDX-License-Identifier: LGPL-2.1+ # from base64 import b64encode import io import os import subprocess import shutil import sys import tempfile import zipfile from assets.header import TAG def error(msg): print(msg) sys.exit(1) def parse_args(): import argparse parser = argparse.ArgumentParser( description="Generate a standalone firmware updater" ) parser.add_argument( "--disable-snap-download", action="store_true", help="Don't download support for snap", ) parser.add_argument( "--disable-flatpak-download", action="store_true", help="Don't download support for flatpak", ) parser.add_argument( "--snap-channel", help="Channel to download snap from (optional)" ) parser.add_argument( "--minimum", help="Use already installed fwupd version if at least this version" ) parser.add_argument( "cab", help="CAB file or directory containing CAB files to automatically install", ) parser.add_argument("target", help="target file to create") args = parser.parse_args() return args def bytes_slicer(length, source): start = 0 stop = length while start < len(source): yield source[start:stop] start = stop stop += length def generate_installer(directory, target): asset_base = os.path.join(os.path.dirname(os.path.realpath(__file__)), "assets") # header shutil.copy(os.path.join(asset_base, "header.py"), target) # zip file buffer = io.BytesIO() archive = zipfile.ZipFile(buffer, "a") for root, dirs, files in os.walk(directory): for f in files: source = os.path.join(root, f) archive_fname = source.split(directory)[1] archive.write(source, archive_fname) if "DEBUG" in os.environ: print(archive.namelist()) archive.close() with open(target, "ab") as bytes_out: encoded = b64encode(buffer.getvalue()) for section in bytes_slicer(64, encoded): bytes_out.write(TAG) bytes_out.write(section) bytes_out.write(b"\n") def download_snap(directory, channel): cmd = ["snap", "download", "fwupd"] if channel is not None: cmd += ["--channel", channel] if "DEBUG" in os.environ: print(cmd) subprocess.run(cmd, cwd=directory, check=True) for f in os.listdir(directory): # the signatures associated with the snap if f.endswith(".assert"): shutil.move( os.path.join(directory, f), os.path.join(directory, "fwupd.assert") ) # the snap binary itself elif f.endswith(".snap"): shutil.move( os.path.join(directory, f), os.path.join(directory, "fwupd.snap") ) def download_cab_file(directory, uri): cmd = ["wget", uri] if "DEBUG" in os.environ: print(cmd) subprocess.run(cmd, cwd=directory, check=True) def download_flatpak(directory): dep = "org.freedesktop.fwupd" flatpak_dir = os.path.join(os.getenv("HOME"), ".local", "share", "flatpak") verbose = "DEBUG" in os.environ # check if we have installed locally already or not if not os.path.exists(os.path.join(flatpak_dir, "app", dep)): # install into local user's repo cmd = [ "flatpak", "install", "--user", "https://www.flathub.org/repo/appstream/org.freedesktop.fwupd.flatpakref", "--no-deps", "-y", ] if verbose: print(cmd) subprocess.run(cmd, cwd=directory, check=True) # generate a bundle repo = os.path.join(flatpak_dir, "repo") cmd = ["flatpak", "build-bundle", repo, "fwupd.flatpak", dep, "stable"] if verbose: print(cmd) subprocess.run(cmd, cwd=directory, check=True) if __name__ == "__main__": args = parse_args() if not args.cab.startswith("http"): local = args.cab with tempfile.TemporaryDirectory(prefix="fwupd") as directory: if local: if not os.path.exists(local): error(f"{local} doesn't exist") if not os.path.isdir(local): shutil.copy(local, directory) else: for root, dirs, files in os.walk(local): for f in files: shutil.copy(os.path.join(root, f), directory) else: download_cab_file(directory, args.cab) if not args.disable_snap_download: download_snap(directory, args.snap_channel) if not args.disable_flatpak_download: download_flatpak(directory) if args.minimum: with open(os.path.join(directory, "minimum"), "w") as wfd: wfd.write(args.minimum) generate_installer(directory, args.target) fwupd-1.9.16/contrib/tartan.sh000077500000000000000000000004571460375044200162400ustar00rootroot00000000000000#!/bin/sh /usr/bin/scan-build -load-plugin /usr/local/lib64/tartan/16.0/libtartan.so \ -disable-checker core.CallAndMessage \ -disable-checker core.NullDereference \ -disable-checker deadcode.DeadStores \ -disable-checker unix.Malloc \ -enable-checker tartan.GErrorChecker \ --status-bugs -v "$@" fwupd-1.9.16/contrib/upload-smc-license.py000066400000000000000000000032031460375044200204360ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ import sys import argparse from urllib import request, parse import base64 import ssl if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( "-u", "--user", type=str, help="Redfish user name", default="ADMIN", ) parser.add_argument( "-p", "--password", type=str, help="Redfish user password", default="ADMIN", ) parser.add_argument( "-f", "--file", type=str, help="License file", ) parser.add_argument("hostname", type=str, help="BMC IP of hostname") args = parser.parse_args() license = "" if len(sys.argv) == 1: print("hostname required") sys.exit(1) if not args.file: print("License:") license = sys.stdin.read() else: with open(args.file, "r") as fd: license = fd.read() sslctx = ssl.create_default_context() sslctx.check_hostname = False sslctx.verify_mode = ssl.CERT_NONE url = ( f"https://{args.hostname}/redfish/v1/Managers/1/LicenseManager/ActivateLicense" ) auth = str( base64.b64encode(bytes(f"{args.user}:{args.password}", "latin1")), encoding="latin1", ) headers = { "Content-Type": "application/json", "Authorization": f"Basic {auth}", } req = request.Request(url, headers=headers, data=bytes(license, "latin1")) resp = request.urlopen(req, context=sslctx) if resp.status < 300: print("Success") else: print("Failed") sys.exit(-1) fwupd-1.9.16/contrib/vscode/000077500000000000000000000000001460375044200156655ustar00rootroot00000000000000fwupd-1.9.16/contrib/vscode/README.md000066400000000000000000000031061460375044200171440ustar00rootroot00000000000000# Using Visual Studio Code to debug This directory contains a collection of scripts and assets to make debugging using Visual Studio Code easier. ## Preparing First install the following applications locally: * GDB Server * GDB * Visual Studio Code In Visual Studio code, visit the extension store and install *C/C++* which is an extension provided by Microsoft. Configure Visual Studio code to open the folder representing the root of the fwupd checkout. ## Building Run `build-fwupd` within the virtualenv setup by the `contrib/setup` script to build fwupd with all default options and create helper scripts pre-configured for debugger use. The application will be placed into `./venv/dist` and helper scripts will be created for `fwupdtool`, `fwupdmgr`, and `fwupd` that are placed in `venv/bin`. ## Running To run any of the applications, execute the application within the venv. ## Debugging To debug any of the applications, launch the helper script with the environment variable `DEBUG` set. For example to debug `fwupdtool get-devices` the command to launch would be: ```shell DEBUG=1 fwupdtool get-devices ``` This will configure `gdbserver` to listen on a local port waiting for a debugger to connect. ## Using Visual Studio code During build time a set of launch targets will have been created for use with Visual Studio Code. Press the debugging button on the left and 3 targets will be listed at the top. * gdbserver (fwupdtool) * gdbserver (fwupd) * gdbserver (fwupdmgr) Select the appropriate target and press the green arrow to connect to `gdbserver` and start debugging. fwupd-1.9.16/contrib/vscode/launch.json000066400000000000000000000033331460375044200200340ustar00rootroot00000000000000{ "version": "0.2.0", "configurations": [ { "name": "gdbserver (fwupdtool)", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/venv/dist/bin/fwupdtool", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "miDebuggerServerAddress": "localhost:9091", "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] }, { "name": "gdbserver (fwupd)", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/venv/dist/libexec/fwupd/fwupd", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "miDebuggerServerAddress": "localhost:9091", "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] }, { "name": "gdbserver (fwupdmgr)", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/venv/dist/bin/fwupdmgr", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "miDebuggerServerAddress": "localhost:9091", "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] } fwupd-1.9.16/contrib/vscode/settings.json000066400000000000000000000001041460375044200204130ustar00rootroot00000000000000{ "editor.tabSize": 8, "mesonbuild.buildFolder": "venv/build" } fwupd-1.9.16/data/000077500000000000000000000000001460375044200136535ustar00rootroot00000000000000fwupd-1.9.16/data/90-fwupd-devices.rules000066400000000000000000000004311460375044200177200ustar00rootroot00000000000000######################################################################## # Copyright (C) 2015 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ # # NVMe hardware SUBSYSTEM=="nvme", ENV{ID_VENDOR_FROM_DATABASE}=="", IMPORT{builtin}="hwdb --subsystem=pci" fwupd-1.9.16/data/bash-completion/000077500000000000000000000000001460375044200167375ustar00rootroot00000000000000fwupd-1.9.16/data/bash-completion/fwupdmgr000066400000000000000000000161431460375044200205220ustar00rootroot00000000000000_fwupdmgr_cmd_list=( 'activate' 'block-firmware' 'clear-results' 'disable-remote' 'device-test' 'device-emulate' 'device-wait' 'downgrade' 'download' 'enable-remote' 'emulation-tag' 'emulation-untag' 'emulation-load' 'emulation-save' 'get-approved-firmware' 'get-bios-setting' 'get-blocked-firmware' 'get-details' 'get-devices' 'get-history' 'get-plugins' 'get-releases' 'get-remotes' 'get-results' 'get-topology' 'get-updates' 'get-upgrades' 'get-plugins' 'inhibit' 'uninhibit' 'install' 'local-install' 'modify-config' 'modify-remote' 'quit' 'reinstall' 'refresh' 'report-history' 'report-export' 'security' 'security-fix' 'security-undo' 'set-approved-firmware' 'set-bios-setting' 'switch-branch' 'sync' 'unlock' 'unblock-firmware' 'update' 'upgrade' 'verify' 'verify-update' '--version' ) _fwupdmgr_opts=( '--verbose' '--offline' '--allow-reinstall' '--allow-older' '--allow-branch-switch' '--force' '--assume-yes' '--no-history' '--no-unreported-check' '--no-metadata-check' '--no-reboot-check' '--no-safety-check' '--no-remote-check' '--no-security-fix' '--show-all' '--sign' '--filter' '--filter-release' '--disable-ssl-strict' '--p2p' '--json' ) bios_get_opts=( '--no-authenticate' '--json' '--verbose' ) bios_set_opts=( '--no-reboot-check' '--json' '--verbose' ) modify_config_opts=( 'ArchiveSizeMax' 'AllowEmulation' 'ApprovedFirmware' 'BlockedFirmware' 'DisabledDevices' 'DisabledPlugins' 'EspLocation' 'EnumerateAllDevices' 'HostBkc' 'IdleTimeout' 'IgnorePower' 'OnlyTrusted' 'P2pPolicy' 'ReleaseDedupe' 'ReleasePriority' 'ShowDevicePrivate' 'TestDevices' 'TrustedReports' 'TrustedUids' 'UpdateMotd' 'UriSchemes' 'VerboseDomains' ) _show_file_in_dir() { local files files="$(ls 2>/dev/null)" COMPREPLY+=( $(compgen -W "${files}" -- "$cur") ) } _show_bios_get_modifiers() { COMPREPLY+=( $(compgen -W '${bios_get_opts[@]}' -- "$cur") ) } _show_bios_set_modifiers() { COMPREPLY+=( $(compgen -W '${bios_set_opts[@]}' -- "$cur") ) } _show_filters() { local flags flags="$(command fwupdtool get-device-flags 2>/dev/null)" COMPREPLY+=( $(compgen -W "${flags}" -- "$cur") ) } _show_modifiers() { COMPREPLY+=( $(compgen -W '${_fwupdmgr_opts[@]}' -- "$cur") ) } _show_bios_settings() { if ! command -v jq &> /dev/null; then return 0 fi local attr attr="$(command fwupdmgr get-bios-setting --json --no-authenticate 2>/dev/null | jq '.BiosSettings | .[] | .Name')" COMPREPLY+=( $(compgen -W "${attr}" -- "$cur") ) } _show_bios_settings_possible() { if ! command -v jq &> /dev/null; then return 0 fi local attr attr="$(command fwupdmgr get-bios-setting "$1" --json --no-authenticate 2>/dev/null | jq '.BiosSettings | .[] | .BiosSettingPossibleValues | .[]')" COMPREPLY+=( $(compgen -W "${attr}" -- "$cur") ) } _show_device_ids() { if ! command -v jq &> /dev/null; then return 0 fi local description description="$(command fwupdmgr get-devices --json 2>/dev/null | jq '.Devices | .[] | .DeviceId')" COMPREPLY+=( $(compgen -W "${description}" -- "$cur") ) } _show_plugins() { if ! command -v jq &> /dev/null; then return 0 fi local plugins plugins="$(command fwupdmgr get-plugins --json 2>/dev/null | jq '.Plugins | .[] | .Name')" COMPREPLY+=( $(compgen -W "${plugins}" -- "$cur") ) } _show_release_versions() { if ! command -v jq &> /dev/null; then return 0 fi local description description="$(command fwupdmgr get-releases "$1" --json 2>/dev/null | jq '.Releases[].Version')" COMPREPLY+=( $(compgen -W "${description}" -- "$cur") ) } _show_modify_config() { COMPREPLY+=( $(compgen -W '${modify_config_opts[@]}' -- "$cur") ) } _show_remotes() { local remotes remotes="$(command fwupdmgr get-remotes | command awk '/Remote ID/ { print $4 }')" COMPREPLY+=( $(compgen -W "${remotes}" -- "$cur") ) } _fwupdmgr() { local cur prev command arg args COMPREPLY=() _get_comp_words_by_ref cur prev _get_first_arg _count_args case $prev in --filter) _show_filters return 0 ;; esac case $arg in activate|clear-results|downgrade|get-releases|get-results|unlock|verify|verify-update|get-updates|switch-branch|update|upgrade|report-export) #device ID if [[ "$args" = "2" ]]; then _show_device_ids fi ;; get-bios-settings|get-bios-setting) #bios settings (no limit) _show_bios_settings _show_bios_get_modifiers return 0 ;; set-bios-setting) if [[ "$prev" = "--json" ]]; then _show_file_in_dir "$prev" return 0 fi count=$(($((args)) % 2)) #allow setting a single bios setting at a time if [[ $count == 0 ]]; then _show_bios_settings fi #possible values (only works for enumeration though) if [[ $count == 1 ]]; then _show_bios_settings_possible "$prev" return 0 fi _show_bios_set_modifiers return 0 ;; get-plugins) return 0 ;; get-details) #find files if [[ "$args" = "2" ]]; then _filedir fi ;; device-test) #find files if [[ "$args" = "2" ]]; then _filedir fi ;; install) #device ID if [[ "$args" = "2" ]]; then _show_device_ids #version elif [[ "$args" = "3" ]]; then _show_release_versions "$prev" fi ;; local-install) #find files if [[ "$args" = "2" ]]; then _filedir #device ID or modifiers elif [[ "$args" = "3" ]]; then _show_device_ids _show_modifiers fi ;; modify-remote) #find remotes if [[ "$args" = "2" ]]; then _show_remotes #add key elif [[ "$args" = "3" ]]; then local keys keys="$(command fwupdmgr get-remotes | command awk -v pattern="Remote ID:.*${prev}$" '$0~pattern{show=1; next}/Remote/{show=0}{gsub(/:.*/,"")}show')" COMPREPLY+=( $(compgen -W "${keys}" -- "$cur") ) fi ;; enable-remote) #find remotes if [[ "$args" = "2" ]]; then _show_remotes fi ;; disable-remote) #find remotes if [[ "$args" = "2" ]]; then _show_remotes fi ;; modify-config) if [[ "$args" = "2" ]]; then _show_modify_config return 0 elif [[ "$args" = "3" ]]; then case $prev in AllowEmulation|EnumerateAllDevices|OnlyTrusted|IgnorePower|UpdateMotd|ShowDevicePrivate|ReleaseDedupe|TestDevices) COMPREPLY=( $(compgen -W "True False" -- "$cur") ) ;; ReleasePriority) COMPREPLY=( $(compgen -W "local remote" -- "$cur") ) ;; UriSchemes) COMPREPLY=( $(compgen -W "file https http ipfs file;https;http;ipfs file;https;http https;http" -- "$cur") ) ;; P2pPolicy) COMPREPLY=( $(compgen -W "none metadata firmware metadata,firmware" -- "$cur") ) ;; IdleTimeout|ArchiveSizeMax|HostBkc|TrustedUids) ;; ApprovedFirmware|BlockedFirmware) ;; DisabledDevices) _show_device_ids ;; DisabledPlugins) _show_plugins ;; EspLocation) ;; TrustedReports) ;; VerboseDomains) ;; esac return 0; fi ;; refresh) #find first file if [[ "$args" = "2" ]]; then _filedir #find second file elif [[ "$args" = "3" ]]; then _filedir #find remote ID elif [[ "$args" = "4" ]]; then _show_remotes fi ;; *) #find first command if [[ "$args" = "1" ]]; then COMPREPLY=( $(compgen -W '${_fwupdmgr_cmd_list[@]}' -- "$cur") ) fi ;; esac #modifiers _show_modifiers return 0 } complete -F _fwupdmgr fwupdmgr fwupd-1.9.16/data/bash-completion/fwupdtool000066400000000000000000000127221460375044200207110ustar00rootroot00000000000000_fwupdtool_cmd_list=( 'activate' 'build-cabinet' 'clear-history' 'disable-remote' 'disable-test-devices' 'efivar-list' 'enable-remote' 'enable-test-devices' 'esp-list' 'esp-mount' 'esp-unmount' 'firmware-build' 'firmware-convert' 'firmware-export' 'firmware-extract' 'firmware-parse' 'firmware-sign' 'firmware-patch' 'get-bios-setting' 'get-updates' 'get-upgrades' 'get-details' 'get-firmware-types' 'get-firmware-gtypes' 'get-device-flags' 'get-devices' 'get-history' 'get-plugins' 'get-remotes' 'get-report-metadata' 'get-topology' 'hwids' 'update' 'upgrade' 'install' 'install-blob' 'modify-config' 'modify-remote' 'monitor' 'reinstall' 'security' 'security-fix' 'security-undo' 'set-bios-setting' 'switch-branch' 'self-sign' 'smbios-dump' 'attach' 'detach' 'firmware-dump' 'firmware-read' 'refresh' 'verify-update' 'watch' 'unbind-driver' 'bind-driver' 'export-hwids' 'reboot-cleanup' ) _fwupdtool_opts=( '--verbose' '--allow-reinstall' '--allow-older' '--filter' '--filter-release' '--force' '--json' '--show-all' '--plugins' '--prepare' '--cleanup' '--filter' '--method' '--disable-ssl-strict' '--no-safety-check' '--no-search' '--ignore-checksum' '--ignore-vid-pid' '--save-backends' ) modify_config_opts=( 'ArchiveSizeMax' 'AllowEmulation' 'ApprovedFirmware' 'BlockedFirmware' 'DisabledDevices' 'DisabledPlugins' 'EspLocation' 'EnumerateAllDevices' 'HostBkc' 'IdleTimeout' 'IgnorePower' 'OnlyTrusted' 'P2pPolicy' 'ReleaseDedupe' 'ReleasePriority' 'ShowDevicePrivate' 'TestDevices' 'TrustedReports' 'TrustedUids' 'UpdateMotd' 'UriSchemes' 'VerboseDomains' ) _show_modify_config() { COMPREPLY+=( $(compgen -W '${modify_config_opts[@]}' -- "$cur") ) } _show_remotes() { local remotes remotes="$(command fwupdtool get-remotes | command awk '/Remote ID/ { print $4 }')" COMPREPLY+=( $(compgen -W "${remotes}" -- "$cur") ) } _show_filters() { local flags flags="$(command fwupdtool get-device-flags 2>/dev/null)" COMPREPLY+=( $(compgen -W "${flags}" -- "$cur") ) } _show_firmware_types() { local firmware_types firmware_types="$(command fwupdtool get-firmware-types 2>/dev/null)" COMPREPLY+=( $(compgen -W "${firmware_types}" -- "$cur") ) } _show_device_ids() { if ! command -v jq &> /dev/null; then return 0 fi local description description="$(command jq '.Devices | .[] | .DeviceId' @localstatedir@/cache/fwupd/devices.json 2>/dev/null)" COMPREPLY+=( $(compgen -W "${description}" -- "$cur") ) } _show_plugins() { if ! command -v jq &> /dev/null; then return 0 fi local plugins plugins="$(command fwupdtool get-plugins --json 2>/dev/null | jq '.Plugins | .[] | .Name')" COMPREPLY+=( $(compgen -W "${plugins}" -- "$cur") ) } _show_modifiers() { COMPREPLY+=( $(compgen -W '${_fwupdtool_opts[@]}' -- "$cur") ) } _fwupdtool() { local cur prev command arg args COMPREPLY=() _get_comp_words_by_ref cur prev _get_first_arg _count_args case $prev in --plugins) _show_plugins return 0 ;; --filter) _show_filters return 0 ;; esac case $arg in get-details|install|install-blob|firmware-dump|firmware-read) #find files if [[ "$args" = "2" ]]; then _filedir #device ID elif [[ "$args" = "3" ]]; then _show_device_ids fi ;; attach|detach|activate|verify-update|reinstall|get-updates) #device ID if [[ "$args" = "2" ]]; then _show_device_ids fi ;; firmware-parse|firmware-patch) #find files if [[ "$args" = "2" ]]; then _filedir #firmware_type elif [[ "$args" = "3" ]]; then _show_firmware_types fi ;; firmware-convert) #file in if [[ "$args" = "2" ]]; then _filedir #file out elif [[ "$args" = "3" ]]; then _filedir #firmware_type in elif [[ "$args" = "4" ]]; then _show_firmware_types #firmware_type out elif [[ "$args" = "5" ]]; then _show_firmware_types fi ;; modify-remote) #find remotes if [[ "$args" = "2" ]]; then _show_remotes #add key elif [[ "$args" = "3" ]]; then local keys keys="$(command fwupdtool get-remotes | command awk -v pattern="Remote ID:.*${prev}$" '$0~pattern{show=1; next}/Remote/{show=0}{gsub(/:.*/,"")}show')" COMPREPLY+=( $(compgen -W "${keys}" -- "$cur") ) fi ;; enable-remote) #find remotes if [[ "$args" = "2" ]]; then _show_remotes fi ;; disable-remote) #find remotes if [[ "$args" = "2" ]]; then _show_remotes fi ;; modify-config) if [[ "$args" = "2" ]]; then _show_modify_config return 0 elif [[ "$args" = "3" ]]; then case $prev in AllowEmulation|EnumerateAllDevices|OnlyTrusted|IgnorePower|UpdateMotd|ShowDevicePrivate|ReleaseDedupe|TestDevices) COMPREPLY=( $(compgen -W "True False" -- "$cur") ) ;; ReleasePriority) COMPREPLY=( $(compgen -W "local remote" -- "$cur") ) ;; UriSchemes) COMPREPLY=( $(compgen -W "file https http ipfs file;https;http;ipfs file;https;http https;http" -- "$cur") ) ;; P2pPolicy) COMPREPLY=( $(compgen -W "none metadata firmware metadata,firmware" -- "$cur") ) ;; IdleTimeout|ArchiveSizeMax|HostBkc|TrustedUids) ;; ApprovedFirmware|BlockedFirmware) ;; DisabledDevices) _show_device_ids ;; DisabledPlugins) _show_plugins ;; EspLocation) ;; TrustedReports) ;; VerboseDomains) ;; esac return 0; fi ;; *) #find first command if [[ "$args" = "1" ]]; then COMPREPLY=( $(compgen -W '${_fwupdtool_cmd_list[@]}' -- "$cur") ) fi ;; esac #modifiers _show_modifiers return 0 } complete -F _fwupdtool fwupdtool fwupd-1.9.16/data/bash-completion/meson.build000066400000000000000000000010721460375044200211010ustar00rootroot00000000000000if bashcomp.found() completions_dir = bashcomp.get_variable(pkgconfig: 'completionsdir', pkgconfig_define: bashcomp.version().version_compare('>= 2.10') ? ['datadir', datadir] : ['prefix', prefix], ) con = configuration_data() con.set('localstatedir', localstatedir) configure_file( input: 'fwupdtool', output: 'fwupdtool', configuration: con, install: true, install_dir: completions_dir, ) if build_daemon install_data(['fwupdmgr'], install_dir: completions_dir, ) endif # build_daemon endif # bashcomp.found() fwupd-1.9.16/data/bios-settings.d/000077500000000000000000000000001460375044200166675ustar00rootroot00000000000000fwupd-1.9.16/data/bios-settings.d/README.md000066400000000000000000000020151460375044200201440ustar00rootroot00000000000000# BIOS Settings On supported machines fwupd can enforce BIOS settings policy so that a user's desired settings are configured at bootup and prevent fwupd clients from changing them. ## JSON policies A policy file can be created using `fwupdmgr`. First determine what settings you want to enforce by running: ```shell # fwupdmgr get-bios-settings ``` After you have identified settings, create a JSON payload by listing them on the command line. Any number of attributes can be listed. For example for the BIOS setting `WindowsUEFIFirmwareUpdate` you would create a policy file like this: ```shell # fwupdmgr get-bios-settings --json WindowsUEFIFirmwareUpdate > ~/foo.json ``` Now examine `~/foo.json` and modify the `BiosSettingCurrentValue` key to your desired value. Lastly place this policy file into `/etc/fwupd/bios-settings.d`. Any number of policies is supported, and they will be examined in alphabetical order. The next time that fwupd is started it will load this policy and ensure that no fwupd clients change it. fwupd-1.9.16/data/bios-settings.d/meson.build000066400000000000000000000002351460375044200210310ustar00rootroot00000000000000if build_standalone and host_machine.system() == 'linux' install_data('README.md', install_dir: join_paths(sysconfdir, 'fwupd', 'bios-settings.d') ) endif fwupd-1.9.16/data/cfi.quirk000066400000000000000000000113061460375044200154720ustar00rootroot00000000000000# No Manufacturer [CFI\FLASHID_0020] Name = M25PxxA/xx CfiDeviceCmdChipErase = 0xC7 CfiDeviceCmdSectorErase = 0x00 [CFI\FLASHID_00BF] Name = PCT/SST25VFxxx/xxxA CfiDeviceCmdReadId = 0x90 CfiDeviceCmdChipErase = 0x60 CfiDeviceCmdSectorErase = 0x20 [CFI\FLASHID_009D] Name = PM25LDxxx CfiDeviceCmdReadId = 0x90 CfiDeviceCmdChipErase = 0xC7 CfiDeviceCmdSectorErase = 0xD7 [CFI\FLASHID_009D] Name = PM25LVxxx CfiDeviceCmdReadId = 0xAB CfiDeviceCmdChipErase = 0xC7 CfiDeviceCmdSectorErase = 0xD7 [CFI\FLASHID_00EF] Name = W25XxxBV/W25XxxCL CfiDeviceCmdChipErase = 0xC7 CfiDeviceCmdSectorErase = 0x20 # Fujitsu [CFI\FLASHID_04] Vendor = Fujitsu # Atmel [CFI\FLASHID_1F] Vendor = Atmel [CFI\FLASHID_1F65] Name = AT25F512A/B CfiDeviceCmdReadId = 0x15 CfiDeviceCmdChipErase = 0x62 CfiDeviceCmdSectorErase = 0x00 FirmwareSizeMax = 0x10000 # EON [CFI\FLASHID_1C] Vendor = EON [CFI\FLASHID_1C31] Name = EN25Fxx CfiDeviceCmdChipErase = 0x60 CfiDeviceCmdSectorErase = 0x20 # ST [CFI\FLASHID_20] Vendor = ST # Catalyst [CFI\FLASHID_31] Vendor = Catalyst # AMIC [CFI\FLASHID_37] Vendor = AMIC [CFI\FLASHID_3730] Name = A25Lxxx CfiDeviceCmdChipErase = 0xc7 CfiDeviceCmdSectorErase = 0x20 # SyncMOS [CFI\FLASHID_40] Vendor = SyncMOS # ESI [CFI\FLASHID_4A] Vendor = ESI # Alliance [CFI\FLASHID_52] Vendor = Alliance # Tenx [CFI\FLASHID_5E] Vendor = Tenx # Sanyo [CFI\FLASHID_62] Vendor = Sanyo # AMIC [CFI\FLASHID_7F] Vendor = AMIC # Puya Semiconductor [CFI\FLASHID_85] Vendor = Puya [CFI\FLASHID_854012] Name = P25Q21H FirmwareSizeMax = 0x40000 [CFI\FLASHID_854011] Name = P25Q11H FirmwareSizeMax = 0x20000 [CFI\FLASHID_854010] Name = P25Q06H FirmwareSizeMax = 0x10000 [CFI\FLASHID_852014] Name = PY25Q80HB FirmwareSizeMax = 0x100000 [CFI\FLASHID_89] Vendor = Intel # Elite [CFI\FLASHID_8C] Vendor = Elite # Texas Instruments [CFI\FLASHID_97] Vendor = Texas Instruments # PMC [CFI\FLASHID_9D] Vendor = PMC [CFI\FLASHID_7F9D20] Name = PM25Lx512x FirmwareSizeMax = 0x10000 [CFI\FLASHID_7F9D21] Name = PM25LD010 FirmwareSizeMax = 0x20000 # Fudan [CFI\FLASHID_A1] Vendor = Fudan [CFI\FLASHID_A131] Name = FM25xxx CfiDeviceBlockSize = 0x10000 CfiDeviceSectorSize = 0x1000 CfiDevicePageSize = 0x100 CfiDeviceCmdBlockErase = 0xd8 [CFI\FLASHID_A13110] Name = FM25W04 FirmwareSizeMax = 0x80000 [CFI\FLASHID_A13111] Name = FM25F01 FirmwareSizeMax = 0x20000 # Hyundai [CFI\FLASHID_AD] Vendor = Hyundai # Sharp [CFI\FLASHID_B0] Vendor = Sharp # SST [CFI\FLASHID_BF] Vendor = SST # Macronix [CFI\FLASHID_C2] Vendor = Macronix [CFI\FLASHID_C220] Name = MX25Lxxx/xxxC/xxxE CfiDeviceCmdChipErase = 0x60 CfiDeviceCmdSectorErase = 0x20 CfiDeviceCmdBlockErase = 0xd8 CfiDeviceBlockSize = 0x10000 CfiDeviceSectorSize = 0x1000 CfiDevicePageSize = 0x100 [CFI\FLASHID_C22011] Name = MX25L1006E FirmwareSizeMax = 0x20000 [CFI\FLASHID_C22012] Name = MX25V2033F FirmwareSizeMax = 0x40000 [CFI\FLASHID_C22016] Name = MX25L3236F FirmwareSizeMax = 0x400000 [CFI\FLASHID_C222] Name = MX25Lxxx1E CfiDeviceCmdChipErase = 0x60 CfiDeviceCmdSectorErase = 0x20 [CFI\FLASHID_C22312] Name = MX25V2035F FirmwareSizeMax = 0x40000 # GigaDevice [CFI\FLASHID_C8] Vendor = GigaDevice [CFI\FLASHID_C840] Name = GD25Qxxx CfiDeviceCmdChipErase = 0xC7 CfiDeviceCmdSectorErase = 0x20 CfiDeviceCmdBlockErase = 0xd8 CfiDeviceBlockSize = 0x10000 CfiDeviceSectorSize = 0x1000 CfiDevicePageSize = 0x100 [CFI\FLASHID_C84011] Name = GD25D10B FirmwareSizeMax = 0x20000 [CFI\FLASHID_C84012] Name = GD25Q20C FirmwareSizeMax = 0x40000 [CFI\FLASHID_C84016] Name = GD25Q32C FirmwareSizeMax = 0x400000 [CFI\FLASHID_C84018] Name = GD25Q127C FirmwareSizeMax = 0x1000000 # Nantronics [CFI\FLASHID_D5] Vendor = Nantronics # Winbond [CFI\FLASHID_DA] Vendor = Winbond # Winbond (ex Nexcom) [CFI\FLASHID_EF] Vendor = Winbond [CFI\FLASHID_EF3010] Name = W25X05CL FirmwareSizeMax = 0x10000 [CFI\FLASHID_EF3011] Name = W25X10CL FirmwareSizeMax = 0x20000 [CFI\FLASHID_EF3012] Name = W25X20X FirmwareSizeMax = 0x40000 [CFI\FLASHID_EF3013] Name = W25X40 FirmwareSizeMax = 0x80000 [CFI\FLASHID_EF3014] Name = W25X80 FirmwareSizeMax = 0x100000 [CFI\FLASHID_EF3015] Name = W25X16 FirmwareSizeMax = 0x200000 [CFI\FLASHID_EF3016] Name = W25X32 FirmwareSizeMax = 0x400000 [CFI\FLASHID_EF3017] Name = W25X64 FirmwareSizeMax = 0x800000 [CFI\FLASHID_EF4013] Name = W25Q40 FirmwareSizeMax = 0x80000 [CFI\FLASHID_EF4014] Name = W25Q80 FirmwareSizeMax = 0x100000 [CFI\FLASHID_EF4015] Name = W25Q16 FirmwareSizeMax = 0x200000 [CFI\FLASHID_EF4016] Name = W25Q32 FirmwareSizeMax = 0x400000 [CFI\FLASHID_EF4017] Name = W25Q64 FirmwareSizeMax = 0x800000 [CFI\FLASHID_EF4018] Name = W25Q128 FirmwareSizeMax = 0x1000000 [CFI\FLASHID_EF4019] Name = W25Q256 FirmwareSizeMax = 0x2000000 [CFI\FLASHID_EF4020] Name = W25Q512 FirmwareSizeMax = 0x4000000 # Fidelix [CFI\FLASHID_F8] Vendor = Fidelix fwupd-1.9.16/data/device-tests/000077500000000000000000000000001460375044200162525ustar00rootroot00000000000000fwupd-1.9.16/data/device-tests/8bitdo-nes30pro.json000066400000000000000000000006101460375044200220020ustar00rootroot00000000000000{ "name": "8BitDo NES30Pro", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/5b7c4df860695bf000967e140682082b44cea2c033da27fabeffc91be5d2d30c-8Bitdo-SFC30PRO_NES30PRO-4.01.cab", "components": [ { "version": "4.01", "guids": [ "c6566b1b-0c6e-5d2e-9376-78c23ab57bf2" ] } ] } ] } fwupd-1.9.16/data/device-tests/8bitdo-sf30pro.json000066400000000000000000000005561460375044200216360ustar00rootroot00000000000000{ "name": "8BitDo SF30Pro", "interactive": true, "steps": [ { "url": "https://fwupd.org/downloads/3d3a65ee2e8581647fb09d752fa7e21ee1566481-8Bitdo-SF30_Pro-SN30_Pro-1.26.cab", "components": [ { "version": "1.26", "guids": [ "269b3121-097b-50d8-b9ba-d1f64f9cd241" ] } ] } ] } fwupd-1.9.16/data/device-tests/8bitdo-sfc30.json000066400000000000000000000005631460375044200212560ustar00rootroot00000000000000{ "name": "8BitDo SFC30", "interactive": true, "steps": [ { "url": "https://fwupd.org/downloads/fe066b57c69265f4cce8a999a5f8ab90d1c13b24-8Bitdo-SFC30_NES30_SFC30_SNES30-4.01.cab", "components": [ { "version": "4.01", "guids": [ "a7fcfbaf-e9e8-59f4-920d-7691dc6c8699" ] } ] } ] } fwupd-1.9.16/data/device-tests/aiaiai-h05.json000066400000000000000000000005561460375044200207620ustar00rootroot00000000000000{ "name": "AIAIAI H05", "interactive": true, "runtime": false, "steps": [ { "url": "https://fwupd.org/downloads/84279d6bab52262080531acac701523604f3e649-AIAIAI-H05-1.6.cab", "components": [ { "version": "1.6", "guids": [ "7e8318e1-27ae-55e4-a7a7-a35eff60e9bf" ] } ] } ] } fwupd-1.9.16/data/device-tests/analogix-anx7518.json000066400000000000000000000020071460375044200220570ustar00rootroot00000000000000{ "name": "Analogix ANX7518", "interactive": true, "steps": [ { "url": "https://fwupd.org/downloads/5d860747c1378ef8921f95e41bbb7055dc9b470043655a65b00157ab74dd12e8-Analogix-ANX7518-fw-1.5.01-rx-1206.cab", "emulation-url": "https://fwupd.org/downloads/63698f1d36491d795f36335a28e301e7484b1735c63aa763c007c149cc74569c-Analogix-ANX7518-fw-1.5.01-rx-1206.zip", "components": [ { "version": "0001.1501", "guids": [ "cfc5f783-2f3c-5db0-9d09-d5a3044eabd9" ] } ] }, { "url": "https://fwupd.org/downloads/2e4b5747ea2659ddae893a7b8a238d6d9c3166b426c0d0e5feb308a443662c38-Analogix-ANX7518-fw-1.5.08.cab", "emulation-url": "https://fwupd.org/downloads/ff75670536d3de6def5cb4e6e1377dc1ef132882288dd6ceee37c0aecc3fbda3-Analogix-ANX7518-fw-1.5.08.zip", "components": [ { "version": "0001.1508", "guids": [ "cfc5f783-2f3c-5db0-9d09-d5a3044eabd9" ] } ] } ] } fwupd-1.9.16/data/device-tests/bizlink-no-sku-vli.json000066400000000000000000000021141460375044200226070ustar00rootroot00000000000000{ "name": "BizLink Cayenne Hub [VL822+VL103]", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/e0d6f62140663744f114d59b1ec48dd3e4743006bb06d0643efe178b8bdb2a04-BizLink-Cayenne_New.cab", "components": [ { "name": "tier1", "version": "106.83", "guids": [ "a0eff862-6b0b-5571-91ec-a0c4bb25b539" ] }, { "name": "tier3", "version": "138.2.5.18", "guids": [ "a7927038-e2df-5209-88db-2e28bcd3cf8e" ] } ] }, { "url": "https://fwupd.org/downloads/baf2f1a78334b7722d913ffa468240f728a09d91d0d2f755ff5515d4fed111c1-BizLink-Cayenne_Old.cab", "components": [ { "name": "tier1", "version": "06.83", "guids": [ "a0eff862-6b0b-5571-91ec-a0c4bb25b539" ] }, { "name": "tier3", "version": "122.2.5.18", "guids": [ "a7927038-e2df-5209-88db-2e28bcd3cf8e" ] } ] } ] } fwupd-1.9.16/data/device-tests/caldigit-element.json000066400000000000000000000017371460375044200223640ustar00rootroot00000000000000{ "name": "CalDigit Element Hub", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/cc7e51c0f8acb853adf6ca25e99cd6ea41e47f4b2da75a1922bdcc814f05c5a7-CalDigit-Element_DMC15-TBT36.cab", "components": [ { "version": "36.0", "guids": [ "3e4c9daf-ad32-5470-b2f7-a14557c96c71" ] }, { "version": "0.0.0.15", "guids": [ "c15b1b1a-502a-535a-99a5-f929a3cf9593" ] } ] }, { "url": "https://fwupd.org/downloads/b8fa86d745d9c8f4f207c7782a4a09c7381ad19e3733cc8574acde36d250c88a-CalDigit-Element_DMC16-TBT40.cab", "components": [ { "version": "40.84", "guids": [ "3e4c9daf-ad32-5470-b2f7-a14557c96c71" ] }, { "version": "0.0.0.16", "guids": [ "c15b1b1a-502a-535a-99a5-f929a3cf9593" ] } ] } ] } fwupd-1.9.16/data/device-tests/caldigit-ts4-tbt.json000066400000000000000000000006031460375044200222230ustar00rootroot00000000000000{ "name": "CalDigit Element Hub", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/78f0eb25da781a88a2a9a1191a32b6c7f8c8a61664a42dcb6b156a1b87d525e4-CalDigit-TS4_TBT39.cab", "components": [ { "version": "39.82", "guids": [ "6c67279d-c348-5610-a7ee-0f97836a898d" ] } ] } ] } fwupd-1.9.16/data/device-tests/caldigit-ts4.json000066400000000000000000000020671460375044200214420ustar00rootroot00000000000000{ "name": "CalDigit TS4 Dock", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/e07ae12eefb6dc1b9985b5759a017a74160a587361db2e4e63c4032d1af5d7d2-CalDigit-TS4-combind-050322-secure-Cus-22.cab", "emulation-url": "https://fwupd.org/downloads/e755191ff3c7d26777273262eff0be266758ebe5b79685cd44d14d491ab6107a-CalDigit-TS4-combind-050322-secure-Cus-22.zip", "components": [ { "version": "F907.14.10", "guids": [ "707d7941-3129-53d9-a205-47a659d94fe3" ] } ] }, { "url": "https://fwupd.org/downloads/d685ab1fa73a63a797172d498370d8717a6d80c70fff527be051baeaf8ac2bb8-CalDigit-TS4-combind-121022-secure-Cus-37.cab", "emulation-url": "https://fwupd.org/downloads/21c81548a6126f806966497613bc6e33ddd5b3dc045e7cc83b27abd64ba6afd8-CalDigit-TS4-combind-121022-secure-Cus-37.zip", "components": [ { "version": "F907.14.13", "guids": [ "707d7941-3129-53d9-a205-47a659d94fe3" ] } ] } ] } fwupd-1.9.16/data/device-tests/corsair-katar-pro-xt.json000066400000000000000000000006721460375044200231430ustar00rootroot00000000000000{ "name": "Corsair KATAR PRO XT Gaming Mouse", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/7598356dbb3c40f16bc4cd314497ad0b983303e52311f95afc1d9aa8661a154d-corsair-bora-1.6.26.cab", "components": [ { "version": "1.6.26", "protocol": "com.corsair.bp", "guids": [ "b7b0728e-94e9-5ad5-999f-c6a359a9ddd4" ] } ] } ] } fwupd-1.9.16/data/device-tests/corsair-sabre-pro.json000066400000000000000000000006721460375044200225040ustar00rootroot00000000000000{ "name": "Corsair SABRE PRO Gaming Mouse", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/5adc54e39596b5b941339d247ac01f6a40377bc58e6b1b87c49d60c8a7509f4e-corsair-tongs-1.15.25.cab", "components": [ { "version": "1.15.25", "protocol": "com.corsair.bp", "guids": [ "9896f0c3-6682-5392-80f6-0ba462918645" ] } ] } ] } fwupd-1.9.16/data/device-tests/corsair-sabre-rgb-pro.json000066400000000000000000000006751460375044200232570ustar00rootroot00000000000000{ "name": "Corsair SABRE RGB PRO Gaming Mouse", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/a7540311d6b8da599cc24cd6225c34d4c27a4afb9bbbe73397fa23ccf71723f8-corsair-vise-1.15.30.cab", "components": [ { "version": "1.15.30", "protocol": "com.corsair.bp", "guids": [ "7eb05392-a5e0-529a-991b-1910a4bce8cf" ] } ] } ] } fwupd-1.9.16/data/device-tests/dell-kh08p.json000066400000000000000000000012761460375044200210230ustar00rootroot00000000000000{ "name": "Dell KH08P [BCM5719]", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/6cf165037a381eb29c183319e031def6b87e3ce955781ecf73f28751a1365db2-kh08p-bcm5719-0.4.62.cab", "components": [ { "version": "0.4.62", "guids": [ "ec5b8a9e-973b-58cc-935b-8322fabaebe9" ] } ] }, { "url": "https://fwupd.org/downloads/c786be1c525ad062c5af8983474a9412f83f5251efb767fe9cb414a3a124b8ce-kh08p-bcm5719-0.4.64.cab", "components": [ { "version": "0.4.64", "guids": [ "ec5b8a9e-973b-58cc-935b-8322fabaebe9" ] } ] } ] } fwupd-1.9.16/data/device-tests/dell-wd19tb.json000066400000000000000000000023261460375044200212000ustar00rootroot00000000000000{ "name": "Dell WD19TB Dock", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/c05bacfd8f73f30812559f14245b92a069c680caf300e961c78e00c985efe3e0-WD19FirmwareUpdateLinux_01.00.14.cab", "components": [ { "name": "ec", "version": "01.00.00.04", "guids": [ "cd357cf1-40b2-5d87-b8df-bb2dd82774aa" ] }, { "name": "mst", "version": "05.04.03", "guids": [ "89fec0b6-6b76-5008-b82c-5e5c6c164007" ] }, { "name": "pkg", "version": "01.00.14.01", "guids": [ "8ceeeffd-51b6-580c-9b75-69143227aff8" ] }, { "name": "tbt", "version": "43.00", "guids": [ "c94770ca-1773-592c-b20a-e87243bc7cd0" ] }, { "name": "usb1", "version": "01.21", "guids": [ "ac5b774c-b49d-566b-9255-85f0f7f8a4ed" ] }, { "name": "usb2", "version": "01.47", "guids": [ "568ffa1e-a0db-5287-9ea3-872b60f7730b" ] } ] } ] } fwupd-1.9.16/data/device-tests/fpc-lenfy-moh.json000066400000000000000000000010401460375044200216040ustar00rootroot00000000000000{ "name": "FPC Lenfy MoH", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/bbc350a290baed298fca24be8aadb9b3b78ecc806845a42ba9e796b9fc45e5d8-fpc-lenfy-moh-v27_26_23_19.cab", "emulation-url": "https://fwupd.org/downloads/8ef3c5f6bf4d294bec4738eb6077652171939f8c5f7eee1e13fd9ca1e5c4ba9e-fpc-lenfy-moh-v27_26_23_19.zip", "components": [ { "version": "27.26.23.19", "guids": [ "bcb666d2-d13b-5295-b6ef-32a3f4ba8737" ] } ] } ] } fwupd-1.9.16/data/device-tests/fwupd-a3bu-xplained.json000066400000000000000000000016301460375044200227240ustar00rootroot00000000000000{ "name": "LVFS A3BU XPLAINED", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/f5bbeaba1037dce31dd12f349e8148ae35f98b61-a3bu-xplained123.cab", "emulation-url": "https://fwupd.org/downloads/01b95b0206f1a42a2bf95a432d162ef1f9f1f71edb5696127c923ceffadfdf68-a3bu-xplained123.zip", "components": [ { "version": "1.23", "guids": [ "80478b9a-3643-5e47-ab0f-ed28abe1019d" ] } ] }, { "url": "https://fwupd.org/downloads/24d838541efe0340bf67e1cc5a9b95526e4d3702-a3bu-xplained124.cab", "emulation-url": "https://fwupd.org/downloads/357483755bbc4f3a33c0b5bc05a0ef49654be0a15c14cbde2bdf0cd9c203d632-a3bu-xplained124.zip", "components": [ { "version": "1.24", "guids": [ "80478b9a-3643-5e47-ab0f-ed28abe1019d" ] } ] } ] } fwupd-1.9.16/data/device-tests/fwupd-at90usbkey.json000066400000000000000000000016111460375044200222670ustar00rootroot00000000000000{ "name": "LVFS AT90USBKEY", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/b6bef375597e848971f230cf992c9740f7bf5b92-at90usbkey123.cab", "emulation-url": "https://fwupd.org/downloads/57c8acd0de45ff01d91da5ecec1f826fbb438cc3f7b11ca732a8fbfdc2ce24e1-at90usbkey123.zip", "components": [ { "version": "1.23", "guids": [ "c1874c52-5f6a-5864-926d-ea84bcdc82ea" ] } ] }, { "url": "https://fwupd.org/downloads/47807fd4a94a4d5514ac6bf7a73038e00ed63225-at90usbkey124.cab", "emulation-url": "https://fwupd.org/downloads/e65432a2dd63c3ce666cc13a0f5ae13eb6d15cebbc1d022cafcff46b892cdcc4-at90usbkey124.zip", "components": [ { "version": "1.24", "guids": [ "c1874c52-5f6a-5864-926d-ea84bcdc82ea" ] } ] } ] } fwupd-1.9.16/data/device-tests/google-servo-micro.json000066400000000000000000000017251460375044200226710ustar00rootroot00000000000000{ "name": "Google Servo Micro", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/3c3123b6eaa89d5469553b301210a9e4cb06efa98a1cb7c6847a1d10bb0a0c4b-servo_micro_v2.4.0.cab", "emulation-url": "https://fwupd.org/downloads/72f84e212c5c5976bd46ea0fa4f1dda26b4c41f35ac01aaa212867e672690726-servo_micro_v2.4.0.zip", "components": [ { "version": "2.4.0", "guids": [ "13564257-c649-586d-b4e4-4f048d480f36" ] } ] }, { "url": "https://fwupd.org/downloads/1dc362734f138e71fa838ac5503491d297715d7e9bccc0424a62c4a5c68526cc-servo_micro_v2.4.17.cab", "emulation-url": "https://fwupd.org/downloads/b5f152b8e1f814375ae586bdac9536b70b3a9cd1a2a12bf685e8da50eef78f2a-servo_micro_v2.4.17.zip", "components": [ { "version": "2.4.17", "guids": [ "13564257-c649-586d-b4e4-4f048d480f36" ] } ] } ] } fwupd-1.9.16/data/device-tests/hp-910-bt-keyboard.json000066400000000000000000000006331460375044200222660ustar00rootroot00000000000000{ "name": "HP 910 White BT Keyboard (US)", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/57a202956b7dedef59e5a396d3593237f0789730426571d2f53df2747ccef860-HP-910-white-bt-keyboard-us-2.2.3.cab", "components": [ { "version": "2.2.3", "guids": [ "7300b3c6-d58f-5527-815d-be4c72753e67" ] } ] } ] } fwupd-1.9.16/data/device-tests/hp-910-bt-mouse.json000066400000000000000000000006151460375044200216160ustar00rootroot00000000000000{ "name": "HP 910 White BT Mouse", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/85bf9dca11caefcdc58112e2f0487a8596971e3a078d01ead7547ab792d51378-hp-910-white-bt-mouse-2.2.0.cab", "components": [ { "version": "2.2.0", "guids": [ "3d9c8d7e-6b08-5e16-92bc-bd34ec06174b" ] } ] } ] } fwupd-1.9.16/data/device-tests/hp-dock-g5.json000066400000000000000000000017621460375044200210110ustar00rootroot00000000000000{ "name": "HP USB-C Dock G5", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/eb866447bb755c00e748cce14918dcbfaec0ec123237daefce5876c769c2bf92-HP-USBC_DOCK_G5-V1.0.11.0.cab", "emulation-url": "https://fwupd.org/downloads/1cd36afb2da2ec2c43c346076d494180ee8f8f236671985cccd3b82601349bc7-HP-USBC_DOCK_G5-V1.0.11.0.zip", "components": [ { "version": "1.0.11.0", "guids": [ "9434f89a-3351-536d-a281-f70203326833" ] } ] }, { "url": "https://fwupd.org/downloads/c15a0df7386812781d1f376fe54729e64f69b2a8a6c4b580914d4f6740e4fcc3-HP-USBC_DOCK_G5-V1.0.13.0.cab", "emulation-url": "https://fwupd.org/downloads/5d35b4edc89fd01b7e6f7e7101cbf076fe4f712bdd817540351573c9fcafe01a-HP-USBC_DOCK_G5-V1.0.13.0.zip", "components": [ { "version": "1.0.13.0", "guids": [ "9434f89a-3351-536d-a281-f70203326833" ] } ] } ] } fwupd-1.9.16/data/device-tests/hughski-colorhug-plus.json000066400000000000000000000020311460375044200234040ustar00rootroot00000000000000{ "name": "Hughski ColorHug Plus", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/5cbff92158331aeb10008ca36fa918a9637dde7bfe31de3e0523d14090be8977-fakedevice01_dfu.cab", "emulation-url": "https://fwupd.org/downloads/8be5c7f3fe5c399a8699768da3f4ddd2582df19c70e197b852efaa027f42688b-fakedevice01_dfu.zip", "components": [ { "version": "0.1", "guids": [ "dfbaaded-754b-5214-a5f2-46aa3331e8ce", "f5b42624-ff57-5073-ba09-b7c9c04241be" ] } ] }, { "url": "https://fwupd.org/downloads/8bc3afd07a0af3baaab8b19893791dd3972e8305-fakedevice02_dfu.cab", "emulation-url": "https://fwupd.org/downloads/72c580400020f22929510e9fec43d6781d56af697e72e8d560b45daa57e1817c-fakedevice02_dfu.zip", "components": [ { "version": "0.2", "guids": [ "dfbaaded-754b-5214-a5f2-46aa3331e8ce", "f5b42624-ff57-5073-ba09-b7c9c04241be" ] } ] } ] } fwupd-1.9.16/data/device-tests/hughski-colorhug.json000066400000000000000000000017111460375044200224270ustar00rootroot00000000000000{ "name": "Hughski ColorHug", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/9a4e77009da7d3b5f15a1388afeb9e5d41a5a8ae-hughski-colorhug2-1.2.5.cab", "emulation-url": "https://fwupd.org/downloads/08ddc01a50fb866398d01cc4829531640e651f0233306e10a54117f88474b153-hughski-colorhug-1.2.5.zip", "components": [ { "version": "1.2.5", "guids": [ "40338ceb-b966-4eae-adae-9c32edfcc484" ] } ] }, { "url": "https://fwupd.org/downloads/2a066c8a1bfbd99f161c867b4dbe7e51ac36fc2b16ef37b11d18419874fbcb6c-hughski-colorhug-1.2.6.cab", "emulation-url": "https://fwupd.org/downloads/b25122f17912467c1488aaadb59483efc3fe773cb7d002f64289177ca2f3285c-hughski-colorhug-1.2.6.zip", "components": [ { "version": "1.2.6", "guids": [ "40338ceb-b966-4eae-adae-9c32edfcc484" ] } ] } ] } fwupd-1.9.16/data/device-tests/hughski-colorhug2.json000066400000000000000000000017151460375044200225150ustar00rootroot00000000000000{ "name": "Hughski ColorHug2", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/170f2c19f17b7819644d3fcc7617621cc3350a04-hughski-colorhug2-2.0.6.cab", "emulation-url": "https://fwupd.org/downloads/5ddb5d8e9ac85ea05b8dafcad638befdf3e70e5e1db0ca47489fa43d93f33a57-hughski-colorhug2-2.0.6.zip", "components": [ { "version": "2.0.6", "guids": [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad" ] } ] }, { "url": "https://fwupd.org/downloads/e5ad222bdbd3d3d48d8613e67c7e0a0e194f8cd828e33c554d9f05d933e482c7-hughski-colorhug2-2.0.7.cab", "emulation-url": "https://fwupd.org/downloads/29ee025e65b6a469556bbdd819cba665b043244c90c65b9f659933a347f8cf2e-hughski-colorhug2-2.0.7.zip", "components": [ { "version": "2.0.7", "guids": [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad" ] } ] } ] } fwupd-1.9.16/data/device-tests/hyper-no-sku-vli.json000066400000000000000000000021051460375044200222740ustar00rootroot00000000000000{ "name": "Hyper USB-C Hub [VL817+VL103]", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/ecfebc47d63a5319e35da39bd6124b80e90138a208bc9134c9d1b256b232a704-Hyper-USB-C_Hub.cab", "components": [ { "name": "tier1", "version": "90.83", "guids": [ "a476b1bb-9f8e-5ad4-a0ed-afadb52334fc" ] }, { "name": "tier3", "version": "154.36.9.55", "guids": [ "0a120f99-b0f4-52af-9af9-e13155634370" ] } ] }, { "url": "https://fwupd.org/downloads/1694cceda16068b24d7627caace4bd373ecae73aca891f610dec0fe5a4d1207c-Hyper-USB-C_Hub_Old.cab", "components": [ { "name": "tier1", "version": "80.83", "guids": [ "a476b1bb-9f8e-5ad4-a0ed-afadb52334fc" ] }, { "name": "tier3", "version": "138.36.9.55", "guids": [ "0a120f99-b0f4-52af-9af9-e13155634370" ] } ] } ] } fwupd-1.9.16/data/device-tests/jabra-evolve2-75.json000066400000000000000000000017341460375044200220420ustar00rootroot00000000000000{ "name": "Jabra Evolve2 75", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/335f10696cd57ae74b2115a9e44a39a2c5208e7e090d1f4e72bb91bc65a7480d-jabra-evolve2-75-1.8.13.cab", "components": [ { "version": "1.8.13", "protocol": "com.jabra.gnp", "guids": [ "354b39e6-e12e-557e-8beb-fa4128b0a98c", "dc636868-13f1-5c3b-bac5-c4432d447707", "b0870be4-7c69-50af-9b7d-fdfa8d849607" ] } ] }, { "url": "https://fwupd.org/downloads/8fa6ff5d43c0cb5807f136c6edc90c146f3796b9228706ebed6f6348d9c358cd-jabra-evolve2-75-1.7.4.cab", "components": [ { "version": "1.7.4", "protocol": "com.jabra.gnp", "guids": [ "354b39e6-e12e-557e-8beb-fa4128b0a98c", "dc636868-13f1-5c3b-bac5-c4432d447707", "b0870be4-7c69-50af-9b7d-fdfa8d849607" ] } ] } ] } fwupd-1.9.16/data/device-tests/jabra-speak-410.json000066400000000000000000000012031460375044200216230ustar00rootroot00000000000000{ "name": "Jabra Speak 410", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/eab97d7e745e372e435dbd76404c3929730ac082-Jabra-SPEAK_410-1.8.cab", "components": [ { "version": "1.8", "guids": [ "1764c519-4723-5514-baf9-3b42970de487" ] } ] }, { "url": "https://fwupd.org/downloads/50a03efc5df333a948e159854ea40e1a3786c34c-Jabra-SPEAK_410-1.11.cab", "components": [ { "version": "1.11", "guids": [ "1764c519-4723-5514-baf9-3b42970de487" ] } ] } ] } fwupd-1.9.16/data/device-tests/jabra-speak-510.json000066400000000000000000000012051460375044200216260ustar00rootroot00000000000000{ "name": "Jabra Speak 510", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/45f88c50e79cfd30b6599df463463578d52f2fe9-Jabra-SPEAK_510-2.10.cab", "components": [ { "version": "2.10", "guids": [ "443b9b32-7603-5c3a-bb30-291a7d8d6dbd" ] } ] }, { "url": "https://fwupd.org/downloads/c0523a98ef72508b5c7ddd687418b915ad5f4eb9-Jabra-SPEAK_510-2.14.cab", "components": [ { "version": "2.14", "guids": [ "443b9b32-7603-5c3a-bb30-291a7d8d6dbd" ] } ] } ] } fwupd-1.9.16/data/device-tests/jabra-speak-710.json000066400000000000000000000012051460375044200216300ustar00rootroot00000000000000{ "name": "Jabra Speak 710", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/d2910cdbc45cf172767d05e60d9e39a07a10d242-Jabra-SPEAK_710-1.10.cab", "components": [ { "version": "1.10", "guids": [ "0c503ad9-4969-5668-81e5-a3748682fc16" ] } ] }, { "url": "https://fwupd.org/downloads/a5c627ae42de4e5c3ae3df28977f480624f96f66-Jabra-SPEAK_710-1.28.cab", "components": [ { "version": "1.28", "guids": [ "0c503ad9-4969-5668-81e5-a3748682fc16" ] } ] } ] } fwupd-1.9.16/data/device-tests/lenovo-03x7168.json000066400000000000000000000013331460375044200214050ustar00rootroot00000000000000{ "name": "Lenovo USB-C to HDMI (with power) [VL100]", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/9ef0bb237baf8043f3ff16db5a8dd23bfd63fc24ea0eca2a6af3285792aa283b-Lenovo-USB-C_to_HDMI.cab", "components": [ { "version": "130.4.22.1", "guids": [ "eca16353-163f-570b-9e0a-8329045fdcff" ] } ] }, { "url": "https://fwupd.org/downloads/5b06a36aa0f2b99fc33f43a26a553d95c2d8ac46a7f5a2e45a840570456fe29b-Lenovo-USB-C_to_HDMI.cab", "components": [ { "version": "130.4.23.1", "guids": [ "eca16353-163f-570b-9e0a-8329045fdcff" ] } ] } ] } fwupd-1.9.16/data/device-tests/lenovo-03x7605.json000066400000000000000000000013311460375044200213770ustar00rootroot00000000000000{ "name": "Lenovo USB-C to HDMI (no power) [VL103]", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/61b393d8e27503746bae9a16dccf54929b9f1a0d1b5106334933a7313596c00c-Lenovo-USB-C_to_HDMI.cab", "components": [ { "version": "153.84.6.1", "guids": [ "d0909dd3-d140-5da8-85fd-4aa5b2dcee5d" ] } ] }, { "url": "https://fwupd.org/downloads/8be7eea7db239766cf92adf2145138c9929fd953d6d36e39d60c9dd6425638de-Lenovo-USB-C_to_HDMI.cab", "components": [ { "version": "153.84.7.1", "guids": [ "d0909dd3-d140-5da8-85fd-4aa5b2dcee5d" ] } ] } ] } fwupd-1.9.16/data/device-tests/lenovo-03x7608-vli.json000066400000000000000000000011401460375044200221700ustar00rootroot00000000000000{ "name": "Lenovo Travel Hub Gen2 [VL817]", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/ebfda3c96543d6d7ad97a972344f4d34a14549c535095bfbb1860115a88e6ff6-Lenovo-TravalHub-2020-12-11-153442.cab", "components": [ { "name": "tier1", "version": "4.74", "guids": [ "3fc55e47-f57a-55bd-9f41-ac60280bd689" ] }, { "name": "tier3", "version": "138.04.72.18", "guids": [ "3ae6610b-5c33-5714-96e3-05735eb9b2a5" ] } ] } ] } fwupd-1.9.16/data/device-tests/lenovo-03x7609-cxaudio.json000066400000000000000000000012041460375044200230340ustar00rootroot00000000000000{ "name": "Lenovo USB-C Dock Gen2 (CXAUDIO)", "interactive": false, "repeat": 2, "steps": [ { "url": "https://fwupd.org/downloads/2e0bf8aaf9c63ca11cfe3444d032277c21ec0d678e5963123a8b33e5dcd37d99-Lenovo-ThinkPad-USBCGen2Dock-Firmware-49-0E-14.cab", "emulation-url": "https://fwupd.org/downloads/9b6a1401bbd5ab3304a50a00dfc6d17d853bfbfd63f80c0360774d0e9e46e772-Lenovo-ThinkPad-USBCGen2Dock-Firmware-49-0E-14.zip", "components": [ { "name": "cxaudio", "version": "49-0E-14", "guids": [ "dbb8d54c-42e6-5215-b7ac-1df16872bb06" ] } ] } ] } fwupd-1.9.16/data/device-tests/lenovo-40au0065-vli.json000066400000000000000000000026051460375044200223240ustar00rootroot00000000000000{ "name": "Lenovo USB-C Mini Dock", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/3b183e21869e4fce8bc81b3357de2fd1ee47b35e272e26169ebaee88faa39e03-Lenovo-Mini_Dock_New.cab", "components": [ { "name": "tier1", "version": "4.154", "guids": [ "f281c1df-c3d5-5f8a-984d-e9548ffc95fe" ] }, { "name": "tier2", "version": "4.43", "guids": [ "d636c717-44c4-5fcf-9d7f-b96f9c5f6608" ] }, { "name": "tier3", "version": "138.4.24.38", "guids": [ "3ae6610b-5c33-5714-96e3-05735eb9b2a5" ] } ] }, { "url": "https://fwupd.org/downloads/f55a307af1dc66d46bc12460ced828b31b2ee2a78c1d58d4f474958c2c6d134e-Lenovo-Mini_Dock_Old.cab", "components": [ { "name": "tier1", "version": "4.94", "guids": [ "f281c1df-c3d5-5f8a-984d-e9548ffc95fe" ] }, { "name": "tier2", "version": "4.33", "guids": [ "d636c717-44c4-5fcf-9d7f-b96f9c5f6608" ] }, { "name": "tier3", "version": "138.4.23.38", "guids": [ "3ae6610b-5c33-5714-96e3-05735eb9b2a5" ] } ] } ] } fwupd-1.9.16/data/device-tests/lenovo-GX90T33021-vli.json000066400000000000000000000020631460375044200224420ustar00rootroot00000000000000{ "name": "Lenovo Travel Hub 1in3 [VL211]", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/2004603b40bd529d85c2fcfcf9d50d77b47229e6564ef0ea9c03633bdccff94d-Lenovo-Travel_Hub_1in3_New.cab", "emulation-url": "https://fwupd.org/downloads/de48f2281354c4479b8b2302cf85b9643b0ca248fcf4beb19a71f5aba7927278-Lenovo-Travel_Hub_1in3_New.zip", "components": [ { "name": "tier1", "version": "44.33", "guids": [ "7636b85e-d79f-5d30-a329-458957958b88" ] } ] }, { "url": "https://fwupd.org/downloads/5fb4f4dce233626558806c2b7474d3b5ed4f500f6013aa3b376a54322642d449-Lenovo-Travel_Hub_1in3_Old.cab", "emulation-url": "https://fwupd.org/downloads/58221c63de8cdec9b4db3cc72b1e0303b6cfee80b7e7965edad9816c124b0cd7-Lenovo-Travel_Hub_1in3_Old.zip", "components": [ { "name": "tier1", "version": "4.33", "guids": [ "7636b85e-d79f-5d30-a329-458957958b88" ] } ] } ] } fwupd-1.9.16/data/device-tests/logitech-bolt-receiver.json000066400000000000000000000023121460375044200235010ustar00rootroot00000000000000{ "name": "Logitech Bolt receiver", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/003dcc6c2c5437ab093e0c5c5fc1c4e5ab6274d3060505fe0ef843fd13e7200c-Logitech-MPR05-MPR05.00_B0008.cab", "components": [ { "version": "MPR05.00_B0008", "protocol": "com.logitech.unifyingsigned", "guids": [ "af1404c4-f038-5b3e-92b0-09bf4aa84f1c" ] } ] }, { "url": "https://fwupd.org/downloads/c35ade1237da4e1ff90d031cc2b2218c32ee53c01b92b014aae2d2226aed2e35-Logitech-MPR05-MPR05.00_B0009.cab", "components": [ { "version": "MPR05.00_B0009", "protocol": "com.logitech.unifyingsigned", "guids": [ "af1404c4-f038-5b3e-92b0-09bf4aa84f1c" ] } ] }, { "url": "https://fwupd.org/downloads/f1e3ba268ae4e1d3c029392d4f203a976970b162521296a2e9a9a31f314c0c46-Logitech-MPR05-MPR05.01_B0010.cab", "components": [ { "version": "MPR05.01_B0010", "protocol": "com.logitech.unifyingsigned", "guids": [ "af1404c4-f038-5b3e-92b0-09bf4aa84f1c" ] } ] } ] } fwupd-1.9.16/data/device-tests/logitech-k780.json000066400000000000000000000012471460375044200214360ustar00rootroot00000000000000{ "name": "Logitech K780", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/454f1034f5efeb531163d90852ef33afd3fafeeb-Logitech-K780-MPK01.02_B0021.cab", "components": [ { "version": "MPK01.02_B0021", "guids": [ "3932ba15-2bbe-5bbb-817e-6c74e7088509" ] } ] }, { "url": "https://fwupd.org/downloads/b0dffe84c6d3681e7ae5f27509781bc1cf924dd7-Logitech-K780-MPK01.03_B0024.cab", "components": [ { "version": "MPK01.03_B0024", "guids": [ "3932ba15-2bbe-5bbb-817e-6c74e7088509" ] } ] } ] } fwupd-1.9.16/data/device-tests/logitech-m650.json000066400000000000000000000015031460375044200214270ustar00rootroot00000000000000{ "name": "Logitech M650", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/6531c7a26d54f9dacbc24f81e8b26e37dd630fe22a417cd2ce6e13ea3b6fd105-Logitech-RBM16-RBM16.00_B0009.cab", "components": [ { "version": "RBM16.00_B0009", "protocol": "com.logitech.unifyingsigned", "guids": [ "bb3fe644-ed3c-55a4-a506-191e65974b04" ] } ] }, { "url": "https://fwupd.org/downloads/422d8c719d859b4ef321d95aa9e21f8d0d899ac924b9ca3ed76b1d745224e8e4-Logitech-RBM16-RBM16.00_B0010.cab", "components": [ { "version": "RBM16.00_B0010", "protocol": "com.logitech.unifyingsigned", "guids": [ "bb3fe644-ed3c-55a4-a506-191e65974b04" ] } ] } ] } fwupd-1.9.16/data/device-tests/logitech-m750.json000066400000000000000000000006201460375044200214270ustar00rootroot00000000000000{ "name": "Logitech M750", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/597a12cca95b9dcf19e82e5add970a45bf658de8c82dc329cc271a6c50d68752-Logitech-RBM18-RBM18.00_B0010.cab", "components": [ { "version": "RBM18.00_B0010", "guids": [ "b0904956-9081-5ce4-9490-bee057a2d577" ] } ] } ] } fwupd-1.9.16/data/device-tests/logitech-mr0077.json000066400000000000000000000015041460375044200216750ustar00rootroot00000000000000{ "name": "Logitech MR0077", "interactive": true, "steps": [ { "url": "https://fwupd.org/downloads/7b86a6cb5747607f38e78259734dcf0d1afc02d2116b7386ac00c15e70eece72-Logitech-RBM14-RBM14_00_B0007.cab", "components": [ { "version": "RBM14.00_B0007", "protocol": "com.logitech.unifyingsigned", "guids": [ "b2b50d12-c3df-5980-b5e9-6cc6c34b3037" ] } ] }, { "url": "https://fwupd.org/downloads/1cec33151e0f206df9b353a56e318adebb9a9ba9f330b452336eef169f5b1f3c-Logitech-RBM14-RBM14_00_B0008.cab", "components": [ { "version": "RBM14.00_B0008", "protocol": "com.logitech.unifyingsigned", "guids": [ "b2b50d12-c3df-5980-b5e9-6cc6c34b3037" ] } ] } ] } fwupd-1.9.16/data/device-tests/logitech-rqr12-signed.json000066400000000000000000000010001460375044200231460ustar00rootroot00000000000000{ "name": "Logitech Unifying Receiver (RQR12 SIGNED)", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/2443cef8b1dae48751d3c60dab22210733e57036-Logitech-Unifying-RQR12.11_B0032.cab", "components": [ { "version": "RQR12.11_B0032", "protocol": "com.logitech.unifyingsigned", "guids": [ "9d131a0c-a606-580f-8eda-80587250b8d6", "d637baf7-3ab5-502a-8169-2545302e44e2" ] } ] } ] } fwupd-1.9.16/data/device-tests/logitech-rqr12.json000066400000000000000000000014421460375044200217110ustar00rootroot00000000000000{ "name": "Logitech Unifying Receiver (RQR12)", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/6e5ab5961ec4c577bff198ebb465106e979cf686-Logitech-Unifying-RQR12.05_B0028.cab", "components": [ { "version": "RQR12.05_B0028", "protocol": "com.logitech.unifying", "guids": [ "9d131a0c-a606-580f-8eda-80587250b8d6" ] } ] }, { "url": "https://fwupd.org/downloads/938fec082652c603a1cdafde7cd25d76baadc70d-Logitech-Unifying-RQR12.07_B0029.cab", "components": [ { "version": "RQR12.07_B0029", "protocol": "com.logitech.unifying", "guids": [ "9d131a0c-a606-580f-8eda-80587250b8d6" ] } ] } ] } fwupd-1.9.16/data/device-tests/logitech-rqr24-signed.json000066400000000000000000000012001460375044200231530ustar00rootroot00000000000000{ "name": "Logitech Unifying Receiver (RQR24 SIGNED)", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/6a0134766c26c73cc08f3d30f0bb84d94d035b50f702e39506abdcab9a8f658f-Logitech-Unifying-RQR24.11_B0036.cab", "components": [ { "version": "RQR24.11_B0036", "protocol": "com.logitech.unifyingsigned", "guids": [ "cc4cbfa9-bf9d-540b-b92b-172ce31013c1", "87fd7145-3913-50c8-bfcb-86f85006d7d1", "111c9951-f819-5c48-93ef-205a8f8b96c1", "40410bd7-57eb-5c82-9eac-abf893861221" ] } ] } ] } fwupd-1.9.16/data/device-tests/logitech-rqr24.json000066400000000000000000000014421460375044200217140ustar00rootroot00000000000000{ "name": "Logitech Unifying Receiver (RQR24)", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/82b90b2614a9a4d0aced1ab8a4a99e228c95585c-Logitech-Unifying-RQ024.03_B0027.cab", "components": [ { "version": "RQR24.03_B0027", "protocol": "com.logitech.unifying", "guids": [ "cc4cbfa9-bf9d-540b-b92b-172ce31013c1" ] } ] }, { "url": "https://fwupd.org/downloads/4511b9b0d123bdbe8a2007233318ab215a59dfe6-Logitech-Unifying-RQR24.05_B0029.cab", "components": [ { "version": "RQR24.05_B0029", "protocol": "com.logitech.unifying", "guids": [ "cc4cbfa9-bf9d-540b-b92b-172ce31013c1" ] } ] } ] } fwupd-1.9.16/data/device-tests/meson.build000066400000000000000000000025301460375044200204140ustar00rootroot00000000000000if gusb.version().version_compare ('>= 0.4.5') install_data([ '8bitdo-nes30pro.json', '8bitdo-sf30pro.json', '8bitdo-sfc30.json', 'aiaiai-h05.json', 'bizlink-no-sku-vli.json', 'corsair-katar-pro-xt.json', 'corsair-sabre-pro.json', 'corsair-sabre-rgb-pro.json', 'dell-kh08p.json', 'dell-wd19tb.json', 'fwupd-a3bu-xplained.json', 'fwupd-at90usbkey.json', 'hp-dock-g5.json', 'hughski-colorhug2.json', 'hughski-colorhug.json', 'hughski-colorhug-plus.json', 'hyper-no-sku-vli.json', 'jabra-speak-410.json', 'jabra-speak-510.json', 'jabra-speak-710.json', 'lenovo-03x7168.json', 'lenovo-03x7605.json', 'lenovo-03x7608-vli.json', 'lenovo-03x7609-cxaudio.json', 'lenovo-40au0065-vli.json', 'lenovo-GX90T33021-vli.json', 'logitech-bolt-receiver.json', 'logitech-k780.json', 'logitech-m650.json', 'logitech-m750.json', 'logitech-mr0077.json', 'logitech-rqr12.json', 'logitech-rqr12-signed.json', 'logitech-rqr24.json', 'logitech-rqr24-signed.json', 'nordic-hid-nrf52840-mcuboot.json', 'realtek-rts5423.json', 'realtek-rts5855.json', 'synaptics-prometheus.json', 'ugreen-cm260.json', 'wacom-intuos-bt-m.json', 'wacom-intuos-bt-s.json', ], install_dir: join_paths(datadir, 'fwupd', 'device-tests'), ) endif fwupd-1.9.16/data/device-tests/nordic-hid-nrf52840-mcuboot.json000066400000000000000000000013611460375044200240220ustar00rootroot00000000000000{ "name": "nRF52840 DK (nRF52 Desktop) MCUBoot variant", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/904ff137256218bc617661bb4fc2bfddad19522c7e54773d1fd0290b54adfcee-nordic-nrf52840dk-mcuboot-0.0.0_2.cab", "components": [ { "version": "0.0.0.2", "guids": [ "43b38427-fdf5-5400-a23c-f3eb7ea00e7c" ] } ] }, { "url": "https://fwupd.org/downloads/2eb21f9439f0c4928c14548ff5c5c73ad6590d23fa704c58c4afa88168bcea90-nordic-nrf52840dk-mcuboot-0.0.0_3.cab", "components": [ { "version": "0.0.0.3", "guids": [ "43b38427-fdf5-5400-a23c-f3eb7ea00e7c" ] } ] } ] } fwupd-1.9.16/data/device-tests/qualcomm-qcc5171.json000066400000000000000000000010211460375044200220370ustar00rootroot00000000000000{ "name": "Qualcomm QCC5171", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/084ebf794bfcb56b7c749238b4c6ed211342738cece664d02b0cbe285eb646f8-Qualcomm_qcc517X_1.1.2.cab", "emulation-url": "https://fwupd.org/downloads/e87827fa8ee36d0dc8cfc217fd2a8964183caeff963ef248ed11d43b0e582959-qc-reinstall-1.1.2.zip", "components": [ { "version": "1.1.2", "guids": [ "f3eb444d-46de-59da-97b1-7a281cb6a778" ] } ] } ] } fwupd-1.9.16/data/device-tests/realtek-rts5423.json000066400000000000000000000017341460375044200217250ustar00rootroot00000000000000{ "name": "Realtek 4-Port USB Hub", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/460a18d93f5d6118908d8437009ebbd6eceb3c6a0cdfbaf31f8a96df008564da-Realtek-RTS5423-1.56.cab", "emulation-url": "https://fwupd.org/downloads/eaa82f84d31f524f48369c9b46698ff00bf6e398dbb66fb7fed3e6808c69789c-Realtek-RTS5423-1.56.zip", "components": [ { "version": "1.56", "guids": [ "b2d2fae3-1546-5d16-a9af-ac117a255a91" ] } ] }, { "url": "https://fwupd.org/downloads/659e721b0efbe2dfc003d3d30ea5b771c00113cc9a162333ee5a8918c4515f69-Realtek-RTS5423-1.57.cab", "emulation-url": "https://fwupd.org/downloads/962e175bf7809183620ecfad90583e912fb0e16a98c44b0ab539468fd340fc8a-Realtek-RTS5423-1.57.zip", "components": [ { "version": "1.57", "guids": [ "b2d2fae3-1546-5d16-a9af-ac117a255a91" ] } ] } ] } fwupd-1.9.16/data/device-tests/realtek-rts5855.json000066400000000000000000000013721460375044200217340ustar00rootroot00000000000000{ "name": "Realtek RTS5855 Webcam", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/ff989c4b71c92a4a217dfb2f82c1c87691b8eb31-rts5855_v0.4.cab", "emulation-url": "https://fwupd.org/downloads/75d6c26e3842bbd00991a585078e872cae66c7a81e15c5736fc478ba5ec0f77d-rts5855_v0.4.zip", "components": [ { "version": "0.4", "guids": [ "9829f051-47f5-55e7-87dc-a49cf55602e2" ] } ] }, { "url": "https://fwupd.org/downloads/ed5c411d6b74c363209f408f87618fa5c31b50ab-v0.3.cab", "components": [ { "version": "0.3", "guids": [ "9829f051-47f5-55e7-87dc-a49cf55602e2" ] } ] } ] } fwupd-1.9.16/data/device-tests/steelseries-aerox-3-wireless.json000066400000000000000000000031671460375044200246120ustar00rootroot00000000000000{ "name": "Steelseries Aerox 3 Wireless", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/41e25df1ee09f3c05a4799e4fc325115bd5ba9490309c76c545f3e3a88c1c9b9-aerox-3-wireless-dongle.cab", "components": [ { "version": "1.4.2", "guids": [ "291b0582-8d08-5714-88db-986911c744d9", "df0cb7f0-be88-532f-87f9-65662da376b9" ] } ] }, { "url": "https://fwupd.org/downloads/de803999ea8f8c71268bb8ed771c230907b78e9ca8435212f977bfa859cc5f2b-aerox-3-wireless-mouse.cab", "components": [ { "version": "1.10.9", "guids": [ "541b2713-d367-5240-a443-bed07f09ffbf", "4f3fac7c-981b-58cc-bb58-8a3b90f31c9c", "ac7d7cbb-7ef5-5466-ab3d-e70ced97ee61" ] } ] }, { "url": "https://fwupd.org/downloads/596d1032e7c916822ffae84a48d5f84d641808be761723536c0d82bb83ac077f-aerox-3-wireless-dongle.cab", "components": [ { "version": "1.11.4", "guids": [ "291b0582-8d08-5714-88db-986911c744d9", "df0cb7f0-be88-532f-87f9-65662da376b9" ] } ] }, { "url": "https://fwupd.org/downloads/f1453976fe7ea27522524872327ad5aab6fba0c962df719ac05253439d7d72a9-aerox-3-wireless-mouse.cab", "components": [ { "version": "1.11.4", "guids": [ "541b2713-d367-5240-a443-bed07f09ffbf", "4f3fac7c-981b-58cc-bb58-8a3b90f31c9c", "ac7d7cbb-7ef5-5466-ab3d-e70ced97ee61" ] } ] } ] } fwupd-1.9.16/data/device-tests/steelseries-rival-3-wireless.json000066400000000000000000000006261460375044200246060ustar00rootroot00000000000000{ "name": "Steelseries Rival 3 Wireless", "interactive": true, "steps": [ { "url": "https://fwupd.org/downloads/0e227c83714c7c37ac8053164d420d1ca2b85d68b65ba60d51aa0a6b03ee4087-SteelSeries_Rival_3_Wireless_1.4.cab", "components": [ { "version": "1.4", "guids": [ "73e6daa7-d5a1-567d-a0ff-01514f8498b1" ] } ] } ] } fwupd-1.9.16/data/device-tests/synaptics-prometheus.json000066400000000000000000000011021460375044200233450ustar00rootroot00000000000000{ "name": "Prometheus Fingerprint Reader", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/7c260a13ea6df444f7a1fa8fa2bf431876a8c7203b6aeac371564aad30756ff1-Synaptics-Prometheus-10.01.3121519.cab", "emulation-url": "https://fwupd.org/downloads/73877ed7c8c15dac6adf04db32bd5e9f1e702229cfdb266fe98855ee53929cd1-Synaptics-Prometheus-10.01.3121519.zip", "components": [ { "version": "10.01.3121519", "guids": [ "8088f861-6318-5b1e-9ce4-fbddbedb09ac" ] } ] } ] } fwupd-1.9.16/data/device-tests/ugreen-cm260.json000066400000000000000000000012701460375044200212570ustar00rootroot00000000000000{ "name": "Ugreen CM260", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/de152591660080ed5de8550b1b2dfcd738d1f5a68bd30e5f4c684b39b9ebeeb2-Ugreen-CM260-7.2.1.0.cab", "components": [ { "version": "7.2.1.0", "guids": [ "7afc5bff-be55-5e95-81ca-584f13207b1d" ] } ] }, { "url": "https://fwupd.org/downloads/9480d06d1a3e524609660653fadc337d14d72f362bd2e80b15c5c3c1733f629b-Ugreen-CM260-7.2.2.0.cab", "components": [ { "version": "7.2.2.0", "guids": [ "7afc5bff-be55-5e95-81ca-584f13207b1d" ] } ] } ] } fwupd-1.9.16/data/device-tests/wacom-intuos-bt-m.json000066400000000000000000000016401460375044200224300ustar00rootroot00000000000000{ "name": "Wacom Intuos BT-M", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/d16b682de56c42b134f1e5af7f9926c62dc246df851648e49aa1d1d0e5b38532-Wacom-Intuos_BT-M_MainFW-1.66.cab", "emulation-url": "https://fwupd.org/downloads/48a669a65a69b999afdb172882959e30c25b61d3095f47b04d9207b019be74cf-Wacom-Intuos_BT-M_MainFW-1.66.zip", "components": [ { "name": "main", "version": "1.66", "guids": [ "edf56833-dbe5-56ca-a651-734b01bb02ba" ] } ] }, { "url": "https://fwupd.org/downloads/1ee4f3dc9fd08acd4c6bc833b25e7061f85ddd40122148d66940cd3ddd748920-Wacom-Intuos_BT-M_BluetoothFW-1.12.cab", "components": [ { "name": "bluetooth", "version": "1.12", "guids": [ "230fb992-35c7-56b1-8236-7f5674a04153" ] } ] } ] } fwupd-1.9.16/data/device-tests/wacom-intuos-bt-s.json000066400000000000000000000020671460375044200224420ustar00rootroot00000000000000{ "name": "Wacom Intuos BT-S", "interactive": false, "steps": [ { "url": "https://fwupd.org/downloads/0d9314e86d28f78f5dc37d71c5cc5205b681334fe64b28f1ec95b5eedc6e1ea6-Wacom-CTL-4100WL-1.10.cab", "components": [ { "name": "bluetooth", "version": "1.12", "guids": [ "e317b626-4c1d-5892-8b4c-aafec894a4c9" ] }, { "name": "main", "version": "1.10", "guids": [ "dc80ba55-c5f7-5195-b6f4-23c2940bcaec" ] } ] }, { "url": "https://fwupd.org/downloads/cfec6515263c0d358d40d7b8fb6472214015225210dfa2db8dd307e896f03dcf-Wacom-CTL-4100WL-1.11.cab", "components": [ { "name": "bluetooth", "version": "1.12", "guids": [ "e317b626-4c1d-5892-8b4c-aafec894a4c9" ] }, { "name": "main", "version": "1.11", "guids": [ "dc80ba55-c5f7-5195-b6f4-23c2940bcaec" ] } ] } ] } fwupd-1.9.16/data/device-tests/wistron-dock-40b7.json000066400000000000000000000017451460375044200222510ustar00rootroot00000000000000{ "name": "Wistron Dock 40B7", "interactive": true, "steps": [ { "url": "https://fwupd.org/downloads/f00838f10d6faf58ff57cbfcfca390ed63bd804e4c654f19ff89423b286dc5d2-lda_viking_r_composite.cab", "emulation-url": "https://fwupd.org/downloads/08c477c340e51990fc5df84907149f513aef65ae7f828b469e0a14ac01579a34-lda_viking_r_composite.zip", "components": [ { "version": "1.0.1.4", "guids": [ "78d3f11b-6ad9-5739-a650-2f0d2955a710" ] } ] }, { "url": "https://fwupd.org/downloads/43bcfb3278ad2baf918af6abc972e0de9a0de66a25b012177b00d96664819010-lda_viking_r_composite.cab", "emulation-url": "https://fwupd.org/downloads/330411c3eaaba8b7e8b51108fddc614963af5b5fcc0fd328683197491584f387-lda_viking_r_composite2.zip", "components": [ { "version": "1.0.1.6", "guids": [ "78d3f11b-6ad9-5739-a650-2f0d2955a710" ] } ] } ] } fwupd-1.9.16/data/ds20.quirk000066400000000000000000000001001460375044200154670ustar00rootroot00000000000000# Nostromo n52 gamepad [USB\VID_050D&PID_0815] Flags = no-probe fwupd-1.9.16/data/fish-completion/000077500000000000000000000000001460375044200167535ustar00rootroot00000000000000fwupd-1.9.16/data/fish-completion/fwupdmgr.fish000066400000000000000000000163651460375044200214740ustar00rootroot00000000000000function __fish_fwupdmgr_devices --description 'Get device IDs used by fwupdmgr' set -l ids (fwupdmgr get-devices | string replace -f -r '.*Device ID:\s*(.*)' '$1') set -l names (fwupdmgr get-devices | string replace -f -r '.*─(.*):$' '$1') for i in (seq (count $ids)) echo -e "$ids[$i]\t$names[$i]" end end function __fish_fwupdmgr_remotes --description 'Get remote IDs used by fwupdmgr' fwupdmgr get-remotes | string replace -f -r '.*Remote ID:\s*(.*)' '$1' end # complete options complete -c fwupdmgr -s h -l help -d 'Show help options' complete -c fwupdmgr -s v -l verbose -d 'Show extra debugging information' complete -c fwupdmgr -l version -d 'Show client and daemon versions' complete -c fwupdmgr -l offline -d 'Schedule installation for next reboot when possible' complete -c fwupdmgr -l allow-reinstall -d 'Allow reinstalling existing firmware versions' complete -c fwupdmgr -l allow-older -d 'Allow downgrading firmware versions' complete -c fwupdmgr -l allow-branch-switch -d 'Allow switching firmware branch' complete -c fwupdmgr -l force -d 'Force the action by relaxing some runtime checks' complete -c fwupdmgr -s y -l assume-yes -d 'Answer yes to all questions' complete -c fwupdmgr -l sign -d 'Sign the uploaded data with the client certificate' complete -c fwupdmgr -l no-unreported-check -d 'Do not check for unreported history' complete -c fwupdmgr -l no-metadata-check -d 'Do not check for old metadata' complete -c fwupdmgr -l no-reboot-check -d 'Do not check or prompt for reboot after update' complete -c fwupdmgr -l no-safety-check -d 'Do not perform device safety checks' complete -c fwupdmgr -l no-history -d 'Do not write to the history database' complete -c fwupdmgr -l no-security-fix -d 'Do not prompt to fix security issues' complete -c fwupdmgr -l show-all -d 'Show all results' complete -c fwupdmgr -l disable-ssl-strict -d 'Ignore SSL strict checks when downloading' complete -c fwupdmgr -l p2p -d 'Only use peer-to-peer networking when downloading files' complete -c fwupdmgr -l filter -d 'Filter with a set of device flags' # complete subcommands complete -c fwupdmgr -n '__fish_use_subcommand' -x -a activate -d 'Activate devices' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a block-firmware -d 'Blocks a specific firmware from being installed' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a clear-results -d 'Clears the results from the last update' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a disable-remote -d 'Disables a given remote' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a downgrade -d 'Downgrades the firmware on a device' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a device-wait -d 'Wait for a device to appear' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a enable-remote -d 'Enables a given remote' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-approved-firmware -d 'Gets the list of approved firmware' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-blocked-firmware -d 'Gets the list of blocked firmware' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-bios-setting -d 'Retrieve BIOS setting' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-details -d 'Gets details about a firmware file' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-devices -d 'Get all devices that support firmware updates' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-history -d 'Show history of firmware updates' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-plugins -d 'Get all enabled plugins registered with the system' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-releases -d 'Gets the releases for a device' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-remotes -d 'Gets the configured remotes' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-results -d 'Gets the results from the last update' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a get-updates -d 'Gets the list of updates for connected hardware' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a install -d 'Install a firmware file in cabinet format on this hardware' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a modify-config -d 'Modifies a daemon configuration value' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a modify-remote -d 'Modifies a given remote' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a refresh -d 'Refresh metadata from remote server' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a reinstall -d 'Reinstall current firmware on the device' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a report-history -d 'Share firmware history with the developers' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a report-export -d 'Export firmware history for manual upload' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a set-bios-setting -d 'Set a BIOS setting' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a security -d 'Gets the host security attributes' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a set-approved-firmware -d 'Sets the list of approved firmware' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a switch-branch -d 'Switch the firmware branch on the device' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a unblock-firmware -d 'Unblocks a specific firmware from being installed' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a unlock -d 'Unlocks the device for firmware access' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a update -d 'Updates all firmware to latest versions available' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a verify -d 'Checks cryptographic hash matches firmware' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a verify-update -d 'Update the stored cryptographic hash with current ROM contents' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a inhibit -d 'Inhibit the system to prevent upgrades' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a uninhibit -d 'Uninhibit the system to allow upgrades' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a quit -d 'Asks the daemon to quit' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a emulation-load -d 'Load device emulation data' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a emulation-save -d 'Save device emulation data' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a emulation-tag -d 'Adds devices to watch for future emulation' complete -c fwupdmgr -n '__fish_use_subcommand' -x -a emulation-untag -d 'Removes devices to watch for future emulation' # commands exclusively consuming device IDs set -l deviceid_consumers activate clear-results downgrade get-releases get-results get-updates reinstall switch-branch unlock update verify verify-update # complete device IDs complete -c fwupdmgr -n "__fish_seen_subcommand_from $deviceid_consumers" -x -a "(__fish_fwupdmgr_devices)" # complete files and device IDs complete -c fwupdmgr -n "__fish_seen_subcommand_from install" -r -a "(__fish_fwupdmgr_devices)" # commands exclusively consuming remote IDs set -l remoteid_consumers disable-remote enable-remote modify-remote # complete remote IDs complete -c fwupdmgr -n "__fish_seen_subcommand_from $remoteid_consumers" -x -a "(__fish_fwupdmgr_remotes)" # complete files and remote IDs complete -c fwupdmgr -n "__fish_seen_subcommand_from refresh" -r -a "(__fish_fwupdmgr_remotes)" fwupd-1.9.16/data/fish-completion/meson.build000066400000000000000000000001461460375044200211160ustar00rootroot00000000000000install_data(['fwupdmgr.fish'], install_dir: join_paths(datadir, 'fish', 'vendor_completions.d'), ) fwupd-1.9.16/data/fwupd-offline-update.service.in000066400000000000000000000006411460375044200216700ustar00rootroot00000000000000[Unit] Description=Updates device firmware whilst offline Documentation=man:fwupdmgr ConditionPathExists=@localstatedir@/lib/fwupd/pending.db DefaultDependencies=false Requires=sysinit.target dbus.socket After=sysinit.target system-update-pre.target dbus.socket systemd-journald.socket Before=shutdown.target system-update.target [Service] Type=oneshot ExecStart=@libexecdir@/fwupd/fwupdoffline FailureAction=reboot fwupd-1.9.16/data/fwupd.conf000066400000000000000000000000631460375044200156460ustar00rootroot00000000000000[fwupd] # use `man 5 fwupd.conf` for documentation fwupd-1.9.16/data/fwupd.ico000066400000000000000000002040761460375044200155050ustar00rootroot00000000000000 (( ̜̜̜̙n̜̜̜̗[̜̜̜˓I̛̜̜ʓ;ye"""""""" """""""! """"""""""(y"""p󜢡󜢡󟣣󜢡󜢡󗛚M󜢡󜢡󠥤󜢡󜢡󜢡󕚙8󜢡󜢡󢧦󜢡󜢡󜢡󕚙#󜢡󜢡󦪪󜢡󜢡󜢡󕚙w񓚗S>񕛕)w񓚗S>񕛕)u33333333򕚗e33333333T33333333*7딙픘<ؔޔ& ҔږZQTG;?G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>H;?YPS ywxI<@G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>H;?usu)wI<@G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>H;?e_bG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>aZ\ޔQFJG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>MAE򔙘J>BG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>jegG:>f_`G:>G:>G:>aY[G:>ibdG:>G:>QFJG:>icfG:>G:>c^_G:>G:>rlmG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>e^aUMOUMOiceI>BI>BG;?d\_UMOUMO}{{lhhI>BI>BQGJG:>oikG:>G:>vssVPQVPQUMPZTVI>BI>BwstG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>hadG:>G:>[TWd\_G:>G:>|QGJG:>oikG:>G:>[TU[RVPGJG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>hadG:>G:>qmnXPTYQTf_bXPSXPS}||pmnXPTXPTSJMXPStrsXPSRHJwtvXPTXPTXPS\UWXPTXPTxuuG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>c\^G:>G:>{wx~{{RHK_XZG:>G:>ULO\TVb]^G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>SJMhbdG:>G:>G:>G:>hcdupruprupruqqTJMjegG:>G:>]TVaZ\hadVLOG:>G:>qknTJLlggG:>G:>[SUd\_e_aVMQG:>G:>oilUJNmhiG:>G:>YPSf^`f^`upruprupruprNCGG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>hadG:>G:>G:>G:>{}e_aG:>G:>|mgiG:>G:>e`apjk[SUrloG:>G:>fabG:>G:>xtuSKNG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>hadjegG:>G:>G:>[TVG:>d\_oilmgiytvSJMe`afabtpqTKMG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>hadlfhG:>G:>G:>[TVG:>d\_ohje`a]SWtpq|xzfabrnpTKMG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>hadoik{xyd_aibdULOxtue`aG:>G:>|xzohj~~_Y\lfhSILG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>UJNzuxzuxzuxzuxc[^lfhzuxzuxzuxzvwUMNzuxzuxzuxf`aG:>jdgVMQG:>G:>uprUKOoklG:>G:>^TWf_`G:>lggzuxzuxPEHOEHzuxzuxzuxjcfG:>hadzuxzuxzuxzuxOEHG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>I=ANEHNEHLBEG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G<@NEHNEHMDGG:>G:>G:>G:>G:>G:>G:>KAENEHNEHI=AG:>G:>G:>I=ANEHNEHNEHNEHNEHNEHNEHMDGG:>G:>G:>G:>LBENEHNEHI=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>K?CNEHNEHNEHNEHNEHNEHNEHNEHNEHNEHI=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>ZTV{||G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>K@CG:>G:>G:>G:>G:>G:>G:>y{z[VYG:>G:>G:>[UVG:>G:>G:>G:>{||[VWG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>nmm[UVG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>[UVG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>K@CG:>G:>G:>G:>G:>G:>G:>}~~[WYG:>G:>G:>[UVG:>G:>G:>G:>\VXG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>ono[UVG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>[UVG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>K@Cwwxwwx^Y[G:>RJMvvwwwx[WYG:>mllwwxstttywwwxwwx^XZG:>\VXG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>onosttwwxlllG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>[UVG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>K@ComnG:>[UX[WYG:>LADG:>G:>G:>G:>G:>G:>nlmnlmG:>\VXG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>ononlmG:>G:>G:>G:>G:>G:>LADG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>[UVI=AI=AI=AI=AI=AI=AI=AH;?G:>G:>G:>K@ComnI=A[VY[WYG:>LADG:>G:>G:>G:>G:>G:>nlmnlmG:>]XYI=AI=AI=AI=AI=AI=AI=AG:>G:>G:>G:>ononlmG:>G:>G:>G:>G:>G:>LADG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>[UVjiiG:>G:>G:>K@Cc``c``{~}dabc``~[WYG:>LADG:>G:>G:>G:>G:>G:>nlmnlmG:>KACG:>G:>G:>ononlmG:>G:>G:>G:>G:>G:>LADG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>[UVoooG:>G:>G:>K@CG:>G:>K@DG:>}~~[WYG:>LADG:>G:>G:>G:>G:>G:>nlmnlmG:>KACG:>G:>G:>ononlmG:>G:>G:>G:>G:>G:>LADG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>[UVihiG:>G:>G:>K@CG:>G:>xzxK@DG:>}~~[WYG:>LADG:>G:>G:>G:>G:>G:>nlmnlmG:>gffgef\WXG:>ononlmG:>G:>G:>G:>G:>G:>LADG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>[UVG;?G;?G;?G;?G;?G;?G;?G:>G:>G:>G:>K@CG:>G:>G:>G;?G;?G:>G:>}~~[WYG:>LADG:>G:>G:>G:>G:>G:>nlmnlmG:>[VYG;?G;?G;?G;?G;?G;?[VY}~~G:>ononlmG:>G:>G:>G:>G:>G:>LADG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>[UVG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>K@CG:>G:>G:>G:>G:>G:>G:>}~~[WYG:>LADG:>G:>G:>G:>G:>G:>nlmnlmG:>\VXG:>G:>G:>G:>G:>G:>\VX}~~G:>ononlmG:>G:>G:>G:>G:>G:>LADG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>[UV}~K?CK@CG:>G:>G:>G:>G:>G:>G:>}~~[WYG:>LADG:>G:>G:>G:>G:>G:>nlmnlmG:>|~rrststd`bG:>onoqrrtstjiiG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>[UVKADK@CG:>G:>G:>G:>G:>G:>G:>}~~[WYG:>LADG:>G:>G:>G:>G:>G:>nlmnlmG:>KACG:>G:>G:>ono[UVG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>ZTVKADK@CG:>G:>G:>G:>G:>G:>G:>y{z[VYG:>LADG:>G:>G:>G:>G:>G:>lkklkkG:>{||KACG:>G:>G:>nmm[UVG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>I=ANEHNEHNEHNEHNEHNEHNEHNEHNEHNEHNEHNEHNEHH;?G<@NEHNEHMDGG:>G:>G:>G:>G:>G:>G:>KAENEHNEHI=AG:>MDGNEHNEHH;?G:>G:>G:>G:>G:>G:>J@CNEHNEHJ@CG:>LBENEHNEHNEHNEHNEHNEHNEHNEHNEHNEHG<@G:>G:>G:>K?CNEHNEHNEHNEHNEHNEHNEHNEHNEHNEHI=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>I=AG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>󔙘J>BG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>ߔPFHG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>LAEd]`G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>_YZ{H;?G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G;?$usuG;?G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>H;?rop.VMPG:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>G:>TKN}}}ٔ%擙-DKDDDDDDDD󓙙qDDDDDDDDbDDDDDDDD8w񓚗S>񕛕)w񓚗S>񕛕)v򱴳򱴳򳷷򱴳򱴳򜠟R򱴳򱴳򴷷󱴳򱴳򱴳򚞝=򱴳򱴳򶺺򱴳򱴳򱴳򗝜(򱴳򱴳򻿾򱴳򱴳򱴳򖚙UUUU˓UUUU!UUUUUUUT(UUUUUUUQ.UUUZUUUMyeɖzۖbꖛP??fwupd-1.9.16/data/fwupd.service.in000066400000000000000000000006331460375044200167710ustar00rootroot00000000000000[Unit] Description=Firmware update daemon Documentation=https://fwupd.org/ After=dbus.service Before=display-manager.service ConditionVirtualization=!container [Service] Type=dbus TimeoutSec=180 RuntimeDirectory=@motd_dir@ RuntimeDirectoryPreserve=yes BusName=org.freedesktop.fwupd ExecStart=@libexecdir@/fwupd/fwupd PrivateTmp=yes ProtectHome=yes ProtectSystem=full SystemCallFilter=~@mount @dynamic_options@ fwupd-1.9.16/data/fwupd.shutdown.in000077500000000000000000000004071460375044200172060ustar00rootroot00000000000000#!/bin/sh # no history database exists [ -f @localstatedir@/lib/fwupd/pending.db ] || exit 0 # activate firmware when we have a read-only filesystem if ! @bindir@/fwupdtool activate; then ret=$? [ "$ret" -eq "2" ] && exit 0 exit $ret fi fwupd-1.9.16/data/installed-tests/000077500000000000000000000000001460375044200167725ustar00rootroot00000000000000fwupd-1.9.16/data/installed-tests/README.md000066400000000000000000000056011460375044200202530ustar00rootroot00000000000000# Installed tests A test suite that can be used to interact with a fake device is installed when configured with `-Dbuild=all` and `-Dtests=true`. The test files have been signed by the production LVFS instance, and are available here: * * By default this test suite is disabled. ## Enabling To enable the test suite: ```shell fwupdtool enable-test-devices ``` ## Using test suite When the daemon is started with the test suite enabled a fake webcam device will be created with a pending update. ```text Integrated Webcam™ DeviceId: 08d460be0f1f9f128413f816022a6439e0078018 Guid: b585990a-003e-5270-89d5-3705a17f9a43 Summary: A fake webcam Plugin: test Flags: updatable|supported|registered Vendor: ACME Corp. VendorId: USB:0x046D Version: 1.2.2 VersionLowest: 1.2.0 VersionBootloader: 0.1.2 Icon: preferences-desktop-keyboard Created: 2018-11-29 ``` ## Upgrading This can be upgraded to a firmware version `1.2.4` by using `fwupdmgr update` or any fwupd frontend. ```shell $ fwupdmgr get-updates Integrated Webcam™ has firmware updates: GUID: b585990a-003e-5270-89d5-3705a17f9a43 ID: fakedevice.firmware Update Version: 1.2.4 Update Name: FakeDevice Firmware Update Summary: Firmware for the ACME Corp Integrated Webcam Update Remote ID: fwupd-tests Update Checksum: SHA1(fc0aabcf98bf3546c91270f2941f0acd0395dd79) Update Location: ./fakedevice124.cab Update Description: Fixes another bug with the flux capacitor to prevent time going backwards. $ fwupdmgr update Decompressing… [***************************************] Authenticating… [***************************************] Updating Integrated Webcam™… ] Verifying… [***************************************] Less than one minute remaining… ``` ## Downgrading It can also be downgraded to firmware version `1.2.3`. ```shell $ fwupdmgr downgrade Choose a device: 0. Cancel 1. 08d460be0f1f9f128413f816022a6439e0078018 (Integrated Webcam™) 2. 8a21cacfb0a8d2b30c5ee9290eb71db021619f8b (XPS 13 9370 System Firmware) 3. d10c5f0ed12c6dc773f596b8ac51f8ace4355380 (XPS 13 9370 Thunderbolt Controller) 1 Decompressing… [***************************************] Authenticating… [***************************************] Downgrading Integrated Webcam™… \ ] Verifying… [***************************************] Less than one minute remaining… fwupd-1.9.16/data/installed-tests/fakedevice123.bin000066400000000000000000000000121460375044200217710ustar00rootroot000000000000000x1020003fwupd-1.9.16/data/installed-tests/fakedevice123.jcat000066400000000000000000000055701460375044200221600ustar00rootroot00000000000000aeWWʑ+ym12p9)&+?0 Qxqw ,AbUuwuU÷Ʋﴰ__×e)FN*',$8EPOQ}=keR l &hLQch cI)R48')x#_×\gWI3g)($$S R9 c:Ҕ$ˆ&p2)L VGbxQֿŒE΍?GW$B _aԫ [jsO, l^?iT yʴ.2Ab̄s'*y3+Q4TkJaB"^'9bάH+F2mzR ])A򣳂-\hO Ȩ S7چJ9ۅ/}e6RKm?:BHM8AMyi,r#~]eZD &/ց-:YOʯzxDarnmE['w$e[J8 ^9jh8>Lxȶˎ80*erefs2hqx4sKrPt#VQэA8b/8%@.q{kFRK#moa{h;Z]*||l4Zaһ`Z$S7G%K;Kqp-Nאj<}2#/rJ'ztIda8X.WtDemMġidDۣhDזXcZz Mɡ& m`]o s9zWI#A;FAO;}1Nl$zƀ!B n0RRE Ti1Y E_7tcI#/۹M0w Xbx7oY?Z#>QE(߼5 X6eut3҉wÒLrWy5iOElq@"}P1L=pcOvY^)+* eQOaEA3h.~yB/X܈ >\n_pwc>rѾ2yfAB=n@ytyEJ_Zq uk\ I>uWU>."*T> 柘B?/1X} S\}|LIiˑ( NYIT7eHR6@e|̜<{HBW!7'-V.9_T蕷(ָxDnjcYz .K+ɱ*{4D>Owd_*|rYv -$q#{^Ȋ8DYG_҄麋fAÛ>afa@kk5ܴ̮F0&?<%(bR f=vC|};?WA ʉ˴"^{;s,`n /~3|/3?m.Ϳg8ؙNc Q䜞aMFH8FC I_g B Ni" ;G)'`qS1Fhv0,"$%B$q8JN?j9:qWT.:1'mE,fo~uh'FVWNeusAQsG#Fp159U!'0H7 &qV5pKÈ1CjJ+(bR)lKXT.k奏-T0›:զ[sm WDk]Z#- :(0;ͬE3v խwC:s߅I5cO7'3~<'3~B\oKxm祣+ yytr[T0l2YުP ӁX+D\O/cMqC h\R~I/kQׁ zG".)Yzj +wG4͏XZ9 Oð)^;4É͠#³Bl:]ߜcbgV<'RE[qiIסٔװ;qeMQɁKA$K@+[Еl.&)K( ج?7ͯIHATECDNfwupd-1.9.16/data/installed-tests/fakedevice123.metainfo.xml000066400000000000000000000034351460375044200236360ustar00rootroot00000000000000 org.fwupd.fakedevice.firmware FakeDevice Firmware for the ACME Corp Integrated Webcam

    Updating the firmware on your webcam device improves performance and adds new features.

    b585990a-003e-5270-89d5-3705a17f9a43 http://www.acme.com/ CC0-1.0 GPL-2.0+ LVFS X-Device unsigned triplet com.acme.test bfc32ae0b003b7f0deb8e6e780b8434de7c04e41 d8904fe3bc21e867097df4e32be648399fac8e7ee5cfd65ab8546c0776def4b6

    Fixes a bug with the flux capacitor to avoid year 2038 overflow.

    https://github.com/fwupd/fwupd/tree/main/data/installed-tests fakedevice123.bin bfc32ae0b003b7f0deb8e6e780b8434de7c04e41 d8904fe3bc21e867097df4e32be648399fac8e7ee5cfd65ab8546c0776def4b6
    fwupd-1.9.16/data/installed-tests/fakedevice124.bin000066400000000000000000000000121460375044200217720ustar00rootroot000000000000000x1020004fwupd-1.9.16/data/installed-tests/fakedevice124.jcat000066400000000000000000000056061460375044200221610ustar00rootroot00000000000000ieWYӣȱ+*b_1ž/bG9濛}{l߈Z!!)ʢ2OJM5}GUO>`./f0S֎g9=?Q\&`1i(>aUhb-lvOB8$J7h>&HdG1%ipB 3B(!8w &(SHQ<30 Bp2 (!3Q*'cN$JVG~x1(,Ȣ\߻{WBa ϛlv9jpGY?~آnwQgУ%^?^-n޻hVVřhm6Z KOF~udz1g|@{BpR)jn)9cHZF5;v!ƬӧA`epk"duJ uo t j%oUma9ۓ# (+0zni' B.+u Ж@'C/ܻ_xݗOs`0*ߜ,zE}(?vY(ʹ@cx: ~asݷY17c,Ef=Џ2 QelQTmAdP).C̑"{ms:te`t'XŮ+G>g,>^Ǩ\ܻKl-}$Wx2GJ9 Xu7Xt&:lomGzvpe e.Eu?bȿy#>& |` #ju1S8z hd ܐq۽;Y%TnlJ-&avTƇHd7vDI&U M5]MY,!%NX}Rg |2U.-k`$+ZIx*wf }Dʺ@9C'=\neD֖Y]Dj}@/<#6 G5p7%~ǘ6NakxV'vb.!BncRRE  جb,j.Ѕ9-.P"鄶m^MX d;1<5.i/ Ȼd W1-Wo -Y]=}g[:3<,8P~ J1^(lOO^(â>c%s髇aEw}EkNb+* eQ/sy r)zdsg"N1t᧥y8#u h3ݡLt-=R*F2on]a^7τ cH<{tߍ/) 3DIU0Œmg1KnL#=wZk@;?Bgkwэ-POgg" ,+KMUN(Sz\擎nK1M kC&7<ÔYZXDK'pLVΰoSw %R NV+OݲIí69E0-˦ y8O=μtF0Cd_f$H/EY+X8cFA|)}|/_z&/l.HgiJFYGF9L'T P~iFai&oa q* %(bM)44N`:%"Ji%h8E, :>+S:sЍ%YWtxI>=-c~oFRi[{+`%pzV=^I"ɰi~c610)u}hqG 첿: |ͯeI'oq2cx /]0g5ouxǔBxP[ySGsO&Fâ`3bM* {8xODA5feTHODiGٟ[\<,3[1ULԼ}mg}qzuSg?uOSg?uw:W:c^C͑@x 3ݳBFwNZZgt3~$6 O"Hyq=IppTVUn$ Eet-eߣ,:D)TOED[ fh| org.fwupd.fakedevice.firmware FakeDevice Firmware for the ACME Corp Integrated Webcam

    Updating the firmware on your webcam device improves performance and adds new features.

    b585990a-003e-5270-89d5-3705a17f9a43 http://www.acme.com/ CC0-1.0 GPL-2.0+ LVFS X-Device unsigned triplet com.acme.test c2ef77a5ab4ecde5f1c75f1e3fe983c0b901cbdc 77f3e17bad9d510786133804fc1e050488f628257ce00c7e23938f7b31c4cac2

    Fixes another bug with the flux capacitor to prevent time going backwards.

    https://github.com/fwupd/fwupd/tree/main/data/installed-tests fakedevice124.bin c2ef77a5ab4ecde5f1c75f1e3fe983c0b901cbdc 77f3e17bad9d510786133804fc1e050488f628257ce00c7e23938f7b31c4cac2
    fwupd-1.9.16/data/installed-tests/fwupd-tests.xml000066400000000000000000000105421460375044200220030ustar00rootroot00000000000000 fakedevice.firmware FakeDevice Firmware Firmware for the ACME Corp Integrated Webcam ACME Corp GPL-2.0+

    Updating the firmware on your webcam device improves performance and adds new features.

    http://www.acme.com/ 17 1163 ./fakedevice124.cab fc0aabcf98bf3546c91270f2941f0acd0395dd79 2b8546ba805ad10bf8a2e5ad539d53f303812ba5

    Fixes another bug with the flux capacitor to prevent time going backwards.

    17 1153 ./fakedevice123.cab bc3c32f42cf33fe5aade64f999417251fd8208d3 7998cd212721e068b2411135e1f90d0ad436d730

    Fixes a bug with the flux capacitor to avoid year 2038 overflow.

    b585990a-003e-5270-89d5-3705a17f9a43
    com.hughski.ColorHug2.firmware ColorHug2 Firmware for the Hughski ColorHug2 Colorimeter Hughski Limited GPL-2.0+

    Updating the firmware on your ColorHug2 device improves performance and adds new features.

    http://www.hughski.com/ 16384 19592 hughski-colorhug2-2.0.7.cab 490be5c0b13ca4a3f169bf8bc682ba127b8f7b96 658851e6f27c4d87de19cd66b97b610d100efe09

    This release fixes prevents the firmware returning an error when the remote SHA1 hash was never sent.

    2082b5e0-7a64-478a-b1b2-e3404fab6dad
    com.hughski.ColorHug.firmware ColorHug Firmware for the Hughski ColorHug Colorimeter Hughski Limited GPL-2.0+

    Updating the firmware on your ColorHug device improves performance and adds new features.

    http://www.hughski.com/ 16384 18054 hughski-colorhug-1.2.6.cab 570a4259af0c7670f3883e84d2f4e6ff7de572c2 111784ffadfd5dd43f05655b266b5142230195b6

    This release fixes prevents the firmware returning an error when the remote SHA1 hash was never sent.

    40338ceb-b966-4eae-adae-9c32edfcc484
    fwupd-1.9.16/data/installed-tests/fwupd.sh000077500000000000000000000021011460375044200204500ustar00rootroot00000000000000#!/bin/sh exec 2>&1 run_test() { if [ -f @installedtestsbindir@/$1 ]; then @installedtestsbindir@/$1 rc=$?; if [ $rc != 0 ]; then exit $rc; fi fi } run_device_tests() { if [ -n "$CI_NETWORK" ] && [ -d @devicetestdir@ ]; then fwupdmgr modify-config AllowEmulation true -y for f in `grep --files-with-matches -r emulation-url @devicetestdir@`; do echo "Emulating for $f" fwupdmgr device-emulate --no-unreported-check --no-remote-check --no-metadata-check "$f" rc=$?; if [ $rc != 0 ]; then exit $rc; fi done fwupdmgr quit fi } run_test acpi-dmar-self-test run_test acpi-facp-self-test run_test acpi-phat-self-test run_test ata-self-test run_test nitrokey-self-test run_test linux-swap-self-test run_test nvme-self-test run_test wacom-usb-self-test run_test redfish-self-test run_test optionrom-self-test run_test vli-self-test run_test uefi-dbx-self-test run_test synaptics-prometheus-self-test run_test dfu-self-test run_test mtd-self-test run_test vli-self-test run_device_tests # success! exit 0 fwupd-1.9.16/data/installed-tests/fwupd.test.in000066400000000000000000000002141460375044200214220ustar00rootroot00000000000000[Test] Type=session Exec=sh -c "G_TEST_SRCDIR=@installedtestsdatadir@ G_TEST_BUILDDIR=@installedtestsdatadir@ @installedtestsdir@/fwupd.sh" fwupd-1.9.16/data/installed-tests/fwupdmgr-p2p.sh000077500000000000000000000010071460375044200216610ustar00rootroot00000000000000#!/bin/sh set -e exec 2>&1 # only run as root, possibly only in CI if [ "$(id -u)" -ne 0 ]; then exit 0; fi # --- echo "Starting P2P daemon..." export FWUPD_DBUS_SOCKET="/run/fwupd.sock" rm -rf ${FWUPD_DBUS_SOCKET} @libexecdir@/fwupd/fwupd --verbose --timed-exit --no-timestamp & while [ ! -e ${FWUPD_DBUS_SOCKET} ]; do sleep 1; done # --- echo "Starting P2P client..." fwupdmgr get-devices --json rc=$?; if [ $rc != 0 ]; then exit $rc; fi # --- echo "Shutting down P2P daemon..." fwupdmgr quit # success! exit 0 fwupd-1.9.16/data/installed-tests/fwupdmgr-p2p.test.in000066400000000000000000000001051460375044200226260ustar00rootroot00000000000000[Test] Type=session Exec=sh -c "@installedtestsdir@/fwupdmgr-p2p.sh" fwupd-1.9.16/data/installed-tests/fwupdmgr.sh000077500000000000000000000040511460375044200211640ustar00rootroot00000000000000#!/bin/sh exec 2>&1 device=08d460be0f1f9f128413f816022a6439e0078018 error() { rc=$1 journalctl -u fwupd -b || true exit $rc } # --- echo "Getting the list of remotes..." fwupdmgr get-remotes rc=$?; if [ $rc != 0 ]; then error $rc; fi # --- echo "Update the device hash database..." fwupdmgr verify-update $device rc=$?; if [ $rc != 0 ]; then error $rc; fi # --- echo "Getting devices (should be one)..." fwupdmgr get-devices --no-unreported-check rc=$?; if [ $rc != 0 ]; then error $rc; fi # --- echo "Testing the verification of firmware..." fwupdmgr verify $device rc=$?; if [ $rc != 0 ]; then error $rc; fi # --- echo "Getting updates (should be one)..." fwupdmgr --no-unreported-check --no-metadata-check get-updates rc=$?; if [ $rc != 0 ]; then error $rc; fi # --- echo "Installing test firmware..." fwupdmgr update $device -y rc=$?; if [ $rc != 0 ]; then error $rc; fi # --- echo "Getting updates (should be none)..." fwupdmgr --no-unreported-check --no-metadata-check get-updates rc=$?; if [ $rc != 2 ]; then error $rc; fi # --- echo "Testing the verification of firmware (again)..." fwupdmgr verify $device rc=$?; if [ $rc != 0 ]; then error $rc; fi if [ -z "$CI_NETWORK" ]; then echo "Skipping remaining tests due to CI_NETWORK not being set" exit 0 fi # --- echo "Downgrading to older release (requires network access)" fwupdmgr downgrade $device -y rc=$?; if [ $rc != 0 ]; then error $rc; fi # --- echo "Downgrading to older release (should be none)" fwupdmgr downgrade $device rc=$?; if [ $rc != 2 ]; then error $rc; fi # --- echo "Updating all devices to latest release (requires network access)" fwupdmgr --no-unreported-check --no-metadata-check --no-reboot-check update -y rc=$?; if [ $rc != 0 ]; then error $rc; fi # --- echo "Getting updates (should be none)..." fwupdmgr --no-unreported-check --no-metadata-check get-updates rc=$?; if [ $rc != 2 ]; then error $rc; fi # --- echo "Refreshing from the LVFS (requires network access)..." fwupdmgr refresh rc=$?; if [ $rc != 0 ]; then error $rc; fi # success! exit 0 fwupd-1.9.16/data/installed-tests/fwupdmgr.test.in000066400000000000000000000001011460375044200221230ustar00rootroot00000000000000[Test] Type=session Exec=sh -c "@installedtestsdir@/fwupdmgr.sh" fwupd-1.9.16/data/installed-tests/fwupdtool.sh000077500000000000000000000007541460375044200213620ustar00rootroot00000000000000#!/bin/sh exec 2>&1 CAB=fakedevice124.cab INPUT="@installedtestsdir@/fakedevice124.bin \ @installedtestsdir@/fakedevice124.jcat \ @installedtestsdir@/fakedevice124.metainfo.xml" # --- echo "Building ${CAB}..." fwupdtool build-cabinet ${CAB} ${INPUT} --force rc=$?; if [ $rc != 0 ]; then exit $rc; fi # --- echo "Installing ${CAB} cabinet..." fwupdtool install ${CAB} rc=$?; if [ $rc != 0 ]; then exit $rc; fi # --- echo "Cleaning ${CAB} generated cabinet ..." rm -f ${CAB} fwupd-1.9.16/data/installed-tests/fwupdtool.test.in000066400000000000000000000001021460375044200223140ustar00rootroot00000000000000[Test] Type=session Exec=sh -c "@installedtestsdir@/fwupdtool.sh" fwupd-1.9.16/data/installed-tests/meson.build000066400000000000000000000045231460375044200211400ustar00rootroot00000000000000con2 = configuration_data() con2.set('installedtestsdir', installed_test_datadir) con2.set('installedtestsbindir', installed_test_bindir) con2.set('installedtestsdatadir', installed_test_datadir) con2.set('devicetestdir', join_paths(datadir, 'fwupd', 'device-tests')) con2.set('bindir', bindir) con2.set('libexecdir', libexecdir) configure_file( input: 'fwupdmgr.test.in', output: 'fwupdmgr.test', configuration: con2, install: true, install_dir: installed_test_datadir, ) configure_file( input: 'fwupdmgr-p2p.test.in', output: 'fwupdmgr-p2p.test', configuration: con2, install: true, install_dir: installed_test_datadir, ) configure_file( input: 'fwupdtool.test.in', output: 'fwupdtool.test', configuration: con2, install: true, install_dir: installed_test_datadir, ) configure_file( input: 'fwupd.test.in', output: 'fwupd.test', configuration: con2, install: true, install_dir: installed_test_datadir, ) configure_file( input: 'fwupdmgr-p2p.sh', output: 'fwupdmgr-p2p.sh', configuration: con2, install: true, install_dir: installed_test_datadir, ) install_data([ 'fakedevice124.bin', 'fakedevice124.jcat', 'fakedevice124.metainfo.xml', 'fwupdmgr.sh', 'fwupd-tests.xml', ], install_dir: installed_test_datadir, ) configure_file( input: 'fwupdtool.sh', output: 'fwupdtool.sh', configuration: con2, install: true, install_dir: installed_test_datadir, ) configure_file( input: 'fwupd.sh', output: 'fwupd.sh', configuration: con2, install: true, install_dir: installed_test_datadir, ) custom_target('installed-cab123', input: [ 'fakedevice123.bin', 'fakedevice123.jcat', 'fakedevice123.metainfo.xml', ], output: 'fakedevice123.cab', command: [ gcab, '--create', '--nopath', '@OUTPUT@', '@INPUT@', ], install: true, install_dir: installed_test_datadir, ) custom_target('installed-cab124', input: [ 'fakedevice124.bin', 'fakedevice124.jcat', 'fakedevice124.metainfo.xml', ], output: 'fakedevice124.cab', command: [ gcab, '--create', '--nopath', '@OUTPUT@', '@INPUT@', ], install: true, install_dir: installed_test_datadir, ) # replace @installedtestsdir@ configure_file( input: 'remote.conf.in', output: 'fwupd-tests.conf', configuration: con2, install: true, install_dir: join_paths(datadir, 'fwupd', 'remotes.d'), ) fwupd-1.9.16/data/installed-tests/remote.conf.in000066400000000000000000000005331460375044200215420ustar00rootroot00000000000000[fwupd Remote] # This is a local fwupd remote that is used only for installed tests # either from continuous integration or for fake devices from fwupd # frontends # It will only be loaded when the daemon configuration has `TestDevices=true` Enabled=true Title=fwupd test suite Keyring=none MetadataURI=file://@installedtestsdir@/fwupd-tests.xml fwupd-1.9.16/data/meson.build000066400000000000000000000106241460375044200160200ustar00rootroot00000000000000subdir('bios-settings.d') subdir('pki') subdir('remotes.d') if get_option('bash_completion') subdir('bash-completion') endif if get_option('fish_completion') subdir('fish-completion') endif if get_option('tests') subdir('device-tests') endif if build_daemon subdir('motd') endif if get_option('tests') if build_daemon subdir('installed-tests') endif endif if build_standalone install_data(['fwupd.conf'], install_dir: join_paths(sysconfdir, 'fwupd'), install_mode: 'rw-r-----', ) plugin_quirks += files([ 'ds20.quirk', 'power.quirk', 'cfi.quirk', ]) endif if get_option('metainfo') custom_target('metainfo', input: 'org.freedesktop.fwupd.metainfo.xml', output: 'org.freedesktop.fwupd.metainfo.xml', command: [ generate_metainfo, '@INPUT@', '@OUTPUT@', ], install: true, install_dir: join_paths(datadir, 'metainfo'), ) install_data(['org.freedesktop.fwupd.svg'], install_dir: join_paths(datadir, 'icons', 'hicolor', 'scalable', 'apps') ) endif if build_daemon install_data(['org.freedesktop.fwupd.conf'], install_dir: join_paths(datadir, 'dbus-1', 'system.d') ) if gudev.found() install_data(['90-fwupd-devices.rules'], install_dir: join_paths(udevdir, 'rules.d') ) endif if libsystemd.found() con2 = configuration_data() con2.set('libexecdir', libexecdir) con2.set('bindir', bindir) con2.set('datadir', datadir) con2.set('localstatedir', localstatedir) rw_directories = [] if not get_option('plugin_uefi_capsule').disabled() rw_directories += ['-/boot/efi', '-/efi/EFI', '-/boot/EFI', '-/boot/grub'] endif # not using ConfigurationDirectory further down if get_option('prefix') != '/usr' rw_directories += join_paths(sysconfdir, 'fwupd') endif dynamic_options = [] if systemd.version().version_compare('>= 232') dynamic_options += 'ProtectControlGroups=yes' dynamic_options += 'ProtectKernelModules=yes' endif if systemd.version().version_compare('>= 231') dynamic_options += 'RestrictRealtime=yes' # dynamic_options += 'MemoryDenyWriteExecute=yes' dynamic_options += ['ReadWritePaths=' + ' '.join(rw_directories)] else dynamic_options += ['ReadWriteDirectories=' + ' '.join(rw_directories)] endif #pull configuration/cache/state from /etc and /var only if prefix is /usr if get_option('prefix') == '/usr' dynamic_options += 'ConfigurationDirectory=fwupd' dynamic_options += 'StateDirectory=fwupd' dynamic_options += 'CacheDirectory=fwupd' endif if not get_option('plugin_redfish').disabled() dynamic_options += 'RestrictAddressFamilies=AF_NETLINK AF_UNIX AF_INET AF_INET6' else dynamic_options += 'RestrictAddressFamilies=AF_NETLINK AF_UNIX' endif con2.set('dynamic_options', '\n'.join(dynamic_options)) con2.set('motd_dir', motd_dir) # replace @bindir@ if offline.allowed() configure_file( input: 'fwupd-offline-update.service.in', output: 'fwupd-offline-update.service', configuration: con2, install: true, install_dir: systemdunitdir, ) endif # replace @dynamic_options@ configure_file( input: 'fwupd.service.in', output: 'fwupd.service', configuration: con2, install: true, install_dir: systemdunitdir, ) # for activation configure_file( input: 'fwupd.shutdown.in', output: 'fwupd.shutdown', configuration: con2, install: true, install_dir: systemd_shutdown_dir, ) endif if libsystemd.found() or elogind.found() con2 = configuration_data() con2.set('libexecdir', libexecdir) # replace @libexecdir@ configure_file( input: 'org.freedesktop.fwupd.service.in', output: 'org.freedesktop.fwupd.service', configuration: con2, install: true, install_dir: join_paths(datadir, 'dbus-1', 'system-services'), ) endif if get_option('launchd').allowed() and launchctl.found() con2 = configuration_data() con2.set('libexecdir', libexecdir) con2.set('dbus_socket_address', get_option('dbus_socket_address')) configure_file( input: 'org.freedesktop.fwupd.plist', output: 'org.freedesktop.fwupd.plist', configuration: con2, install_dir: get_option('launchd_agent_dir'), ) endif endif fwupd-1.9.16/data/motd/000077500000000000000000000000001460375044200146165ustar00rootroot00000000000000fwupd-1.9.16/data/motd/85-fwupd.motd.in000077500000000000000000000001121460375044200174640ustar00rootroot00000000000000#!/bin/sh if [ -f @motd_fullpath@ ]; then cat @motd_fullpath@ fi fwupd-1.9.16/data/motd/README.md000066400000000000000000000010331460375044200160720ustar00rootroot00000000000000# Message of the day integration Message on the day integration is used to display the availability of updates when connecting to a remote console. It has two elements: * Automatic firmware metadata refresh * Message of the day display ## Automatic firmware metadata refresh This uses a systemd timer to run on a regular cadence. To enable this, run ```shell # systemctl enable fwupd-refresh.timer ``` ## Motd display Motd display is dependent upon the availability of the update-motd snippet consumption service such as pam_motd. fwupd-1.9.16/data/motd/fwupd-refresh.service.in000066400000000000000000000006561460375044200213750ustar00rootroot00000000000000[Unit] Description=Refresh fwupd metadata and update motd Documentation=man:fwupdmgr(1) Wants=network-online.target After=network-online.target [Service] Type=oneshot CacheDirectory=fwupdmgr StandardError=null @user@ RestrictAddressFamilies=AF_NETLINK AF_UNIX AF_INET AF_INET6 SystemCallFilter=~@mount ProtectKernelModules=yes ProtectControlGroups=yes RestrictRealtime=yes SuccessExitStatus=2 ExecStart=@bindir@/fwupdmgr refresh fwupd-1.9.16/data/motd/fwupd-refresh.service.md000066400000000000000000000017461460375044200213700ustar00rootroot00000000000000--- title: Message Of The Day Integration --- % fwupd-refresh.service(5) {{PACKAGE_VERSION}} | Message Of The Day Integration ## NAME **fwupd-refresh.service** — message of the day integration. ## DESCRIPTION Message on the day integration is used to display the availability of updates when connecting to a remote console. It has two elements: * Automatic firmware metadata refresh * Message of the day display This uses a systemd timer to run on a regular cadence. To enable this, run: ```shell systemctl enable fwupd-refresh.timer ``` ## NOTES Motd display is dependent upon the availability of the update-motd snippet consumption service such as `pam_motd`. To enable a proxy, set the systemd global environment in `/etc/systemd/system.conf` so all the systemd child processes have the proxy settings applied: ```text [Manager] DefaultEnvironment=http_proxy="http://internal.corp:3128/ "https_proxy="http://internal.corp:3128/" ``` ## SEE ALSO fwupd-1.9.16/data/motd/fwupd-refresh.timer000066400000000000000000000003011460375044200204330ustar00rootroot00000000000000[Unit] Description=Refresh fwupd metadata regularly ConditionVirtualization=!container [Timer] OnCalendar=*-*-* *:00:00 RandomizedDelaySec=1h Persistent=true [Install] WantedBy=timers.target fwupd-1.9.16/data/motd/fwupd.sysusers000066400000000000000000000001131460375044200175600ustar00rootroot00000000000000u @systemd_unit_user@ - "Firmware update daemon" @localstatedir@/lib/fwupd fwupd-1.9.16/data/motd/meson.build000066400000000000000000000047631460375044200167720ustar00rootroot00000000000000 if libsystemd.found() install_data(['fwupd-refresh.timer'], install_dir: systemdunitdir) motd_fullpath = join_paths ('/run', motd_dir, motd_file) else motd_fullpath = join_paths (localstatedir, motd_dir, motd_file) endif con2 = configuration_data() con2.set('bindir', bindir) con2.set('libdir', libdir) con2.set('motd_fullpath', motd_fullpath) con2.set('systemd_unit_user', get_option('systemd_unit_user')) if libsystemd.found() if get_option('systemd_unit_user') == '' con2.set('user', 'DynamicUser=yes') warning('Using systemd DynamicUser for fwupd-refresh.service. See https://github.com/systemd/systemd/issues/22737 for possible implications') else dynamic_options = [ 'ProtectSystem=strict', 'ProtectHome=read-only', 'User=' + get_option('systemd_unit_user') ] con2.set('user','\n'.join(dynamic_options)) endif configure_file( input: 'fwupd-refresh.service.in', output: 'fwupd-refresh.service', configuration: con2, install: true, install_dir: systemdunitdir, ) endif # This file is only used in Ubuntu, which chooses to use update-motd instead # of sourcing /run/motd.d/* # See https://bugs.launchpad.net/ubuntu/+source/pam/+bug/399071 configure_file( input: '85-fwupd.motd.in', output: motd_file, configuration: con2, install: false, ) if libsystemd.found() if get_option('man') custom_target('fwupd-refresh.service.8', input: 'fwupd-refresh.service.md', output: 'fwupd-refresh.service.8', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, ], install: true, install_dir: join_paths(mandir, 'man8'), ) endif if build_docs md_targets += custom_target('fwupd-refresh.service.md', input: 'fwupd-refresh.service.md', output: 'fwupd-refresh.service.md', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, '--md', ], ) man_md += ['"fwupd-refresh.service.md"'] endif endif if libsystemd.found() and get_option('systemd_unit_user') != '' con3 = configuration_data() con3.set('localstatedir', localstatedir) con3.set('systemd_unit_user', get_option('systemd_unit_user')) configure_file( input: 'fwupd.sysusers', output: 'fwupd.conf', configuration: con3, install: true, install_dir: systemd_sysusers_dir, ) endif fwupd-1.9.16/data/org.freedesktop.fwupd.conf000066400000000000000000000020231460375044200207440ustar00rootroot00000000000000 fwupd-1.9.16/data/org.freedesktop.fwupd.metainfo.xml000066400000000000000000005172311460375044200224340ustar00rootroot00000000000000 org.freedesktop.fwupd CC0-1.0 LGPL-2.0+ fwupd Update device firmware on Linux

    This project aims to make updating firmware on Linux automatic, safe and reliable. You can either use a GUI software manager like GNOME Software to view and apply updates, the command-line tool or the D-Bus interface directly.

    The fwupd process is a system daemon to allow session software to update device firmware on your local machine. It is designed for desktops, but this project is also usable on phones, tablets and on headless servers.

    https://github.com/fwupd/fwupd/issues https://fwupd.org/ https://www.transifex.com/freedesktop/fwupd/ https://github.com/fwupd/fwupd richard_at_hughsie.com The fwupd authors The fwupd authors fwupd moderate fwupdmgr fwupdtool

    This release adds the following features:

    • Prefer zstd over xz for metadata
    • Relicense a few remaining plugin files to LGPL-2.1+

    This release fixes the following bugs:

    • Correctly record UEFI success if adding ESRT nodes
    • Defer the DP Aux MST scanning on hotplug to workaround a kernel bug
    • Do not do the post-update version check if the device needs-reboot
    • Fix a fastboot warning when loading device
    • Fix a possible warning in fwupdmgr when excluding releases
    • Fix a qsi-dock warning when writing chunks
    • Ignore attribute-changed to work around a regression in macOS
    • Ignore ZFS zvols when finding the default ESP
    • Mark Pluton TPMs as part of the main CPU
    • Put the fwupd-efi verbose debugging in the journal
    • Recognize zfsbootmenu in ESP detection heuristic

    This release adds support for the following hardware:

    • Acer T34 and U33 docks
    • Qualcomm Series 5 Gen 1 and Gen 2 and Series 3 Gen 1 and Gen 2 devices
    • Several Puya SPI chips
    • VIA VL822 C0

    This release adds the following features:

    • Allow loading in parameters for the test device from fwupd.conf
    • Ensure LVFS remotes are changed from .gz to .xz
    • Store the install duration in the history database

    This release fixes the following bugs:

    • Drop OverrideESPMountPoint references in uefi-capsule
    • Fix a potential double-free when writing AVer firmware
    • Only request the BOS descriptor when bcdUSB > 0x0200
    • Use the root device order when sorting device children

    This release adds the following features:

    • Allow plugins to opt-into a default device GType

    This release fixes the following bugs:

    • Correctly detect ARM32 and RISC-V UEFI binaries
    • Correctly migrate the database schema from very old fwupd versions
    • Fix critical warnings when using FWUPD_DBUS_SOCKET= on macOS
    • Fix DS-20 descriptors by opening the GUsbDevice earlier
    • Fix updating the fingerprint reader on the Framework 13 and 16 laptop
    • Fix warning when probing devices using the metadata allowlist
    • Only recover the version format for specific devices

    This release adds support for the following hardware:

    • Poly Studio V52

    This release adds the following features:

    • Add a timer inhibit if the daemon took a long time to startup
    • Add a concept of 'Test Mode' rather than enabling specific plugins
    • Do not idle-quit the daemon if there is a connected D-Bus client

    This release fixes the following bugs:

    • Allow plugins to opt-out of the child-device first depsolve
    • Allow setting multiple flags in LVFS::DeviceFlags
    • Do not migrate config comments for removed keys
    • Do not request the Advantech BMC to reboot
    • Do not warn the user about ESP when using MBR
    • Fix a critical warning when adding a PixArt wireless device
    • Fix migration of legacy config files
    • Only save config values to the mutable config file
    • Parse DS-20 descriptors earlier in device setup
    • Store the version format in the history database to fix offline reports
    • Use the correct GUID for matching realtek-mst and parade-lspcon

    This release adds support for the following hardware:

    • GoodWay Acer Dock

    This release adds the following features:

    • Add remote modification support to fwupdtool
    • Add support for more modify-config options
    • Generate HTML pages for all man pages

    This release fixes the following bugs:

    • Assume the legacy LVFS::UpdateRequestId tag is non-generic
    • Avoid crashing the daemon if not using udisks
    • Correctly mark the CPU as supported
    • Correctly match invalid EFI partitions
    • Do not change the device status until the action has completed
    • Do not require systemd for fwupdtool modify-config
    • Enable access to the home interface for snap
    • Fix an assertion when enabling lvfs-testing for the first time
    • Fix a possible crash in fwupdtool build-cabinet
    • Handle systems with more than one ccp device
    • Only check AMD CPUs for SHSTK, not IBT
    • Only write the mutable fwupd.conf with the current values
    • Re-evaluate supported every time pci-psp attributes are refreshed
    • Show "CET OS support" on AMD systems too

    This release adds support for the following hardware:

    • AVer CAM340plus
    • AVer VB342 Pro
    • More Algoltek devices

    This release adds the following features:

    • Allow exporting 'offline' reports for manual upload

    This release fixes the following bugs:

    • Add some recovery partition names to ignore for ESP selection
    • Check for CET and SMAP on non-Intel x86 processors too
    • Correctly mark the CPU as supported in the HSI tests
    • Do not fail on probing downstream Synaptics MST ports
    • Do not offer to change BIOS settings that are already set
    • Do not prefer msftdata when choosing the default ESP
    • Do not show spurious device request flags
    • Fix a missing build dependency to fwupdplugin-self-test
    • Fix a segfault when using zlib-ng instead of zlib
    • Fix updating Jabra 410, 510, 710 and 810 devices
    • Match more community-supported branches
    • Remove the Intel SPIBAR proxy support as the mtd module works
    • Show a better error when the ESP is missing
    • Show an error if the post-update version does not match exactly
    • Speed up Synaptics MST device enumeration

    This release adds support for the following hardware:

    • Algoltek USB devices
    • Luxshare Quad USB4 Dock

    This release adds the following features:

    • Add support for not_hardware requirements
    • Add support for loongarch64
    • Add support for per-release priority attributes
    • Make USB claim retry count configurable across devices

    This release fixes the following bugs:

    • Compare the HID report value when checking for duplicates
    • Consider the component priority when installing composite updates
    • Deploy the CCGX firmware correctly the first time
    • Do not export the 'main-system-firmware' and 'cpu' GUIDs
    • Enforce fwupd version requirements client side
    • Fix Genesys 'failed to get static tool info from device' error
    • Fix potential 'dereference before null check' in ccmx-dmc
    • Fix the 'already registered private FuMmDevice flag with value' warning
    • Fix the 'assertion backend_id != NULL failed' runtime warning
    • Fix Wacom USB device emulation by recording the composite phases
    • Generate generic request message text where possible
    • Hide HTTP passwords in fwupd debugging logs
    • Let the client know what interaction is expected
    • Make all critical warnings into backtraces for non-release builds
    • Never obsolete the wrong HSI attribute
    • Never show a HSI index that is impossible
    • Only apply fastboot plugin to modem devices supporting fastboot
    • Only send interactive requests when the sender is alive
    • Remove the now-obsolete Synaptics MST cascade device scanning
    • Replace the Redfish KCS user if required
    • Restrict mediatek-scaler devices on specific hardware only
    • Skip any recovery partitions when detecting ESP

    This release adds support for the following hardware:

    • AVer CAM520 Pro3

    This release adds the following features:

    • Add a new generic request for the device power cable

    This release fixes the following bugs:

    • Disable scanning for synaptics-mst cascade devices until it is more stable
    • Disable the mediatek-scaler plugin by default until we do VCP probing
    • Do not enforce additional requires on emulated devices
    • Fix a potential critical warning when listing the contents of the ESP
    • Fix 'fwupdmgr get-devices' with deeply nested devices
    • Make the Kinetic DP GUID specific to the customer
    • Only use the USB open-retry behavior when required
    • Remove obsoleted HSI attributes earlier
    • Remove the timestamp from gzip to achieve a reproducible build
    • Support cabinet archives produced using 'makecab.exe'
    • Use metadata authentication when using 'fwupdtool refresh'
    • Use the generic ti-tps6598x GUID and instead enforce requirements

    This release adds support for the following hardware:

    • Lenovo X1 Yoga Gen7 530E
    • Advantech BMC devices

    This release adds the following features:

    • Add a DP AUX device subclass and port the Synaptics MST plugin to it
    • Add a feature flag for non-generic requests where translations are required
    • Hide generic VID/PIDs to avoid accidental firmware matches
    • Optionally set the modem carrier configuration as the branch name
    • Rename 'fwupdmgr sync-bkc' to 'fwupdmgr sync' and also consider the branch
    • Require additional requirements for devices using non-OEM USB VIDs
    • Set the waiting-for-user status when sending a request
    • Support uSWID SBoM data with LZMA compressed payloads

    This release fixes the following bugs:

    • Accept any registry versions when parsing Redfish message IDs
    • Do not read the AMDGPU DM DPCD on broken firmware versions
    • Do not save obsoleted HSI attributes into the history database
    • Fix a crash when some DRM devices are hotplugged
    • Fix a possible crash in fu_io_channel_read_raw()
    • Fix parsing LZMA-compressed EFI sections
    • Only use the architecture-specific GUID for UEFI dbx quirk matching
    • Parse all the type 42 SMBIOS tables
    • Prompt the user to reboot after applying internal Wacom firmware
    • Respect no-serial-number flags when using GUID matches
    • Use the etag when patching Redfish resources
    • Use the username and password to get metadata when set

    This release adds support for the following hardware:

    • Kinetic SST/MST DisplayPort converters
    • Wacom Cintiq Pros (DTH172, DTH227)

    This release adds the following features:

    • Add support for child device requirements in metadata
    • Allow to have more than one host BKC
    • Delete BootNext as a post-reboot action to work around broken firmware
    • Parse cabinet archives internally without libgcab
    • Use close-ended mode for eMMC FFU to speed up firmware updates

    This release fixes the following bugs:

    • Do not abort 'fwupdmgr update' when one updatable device has a problem
    • Do not call unimplemented functions when running under Wine
    • Do not modify the BootOrder by default to work around broken firmware
    • Do not treat an immutable buffer as mutable in the vli plugin
    • Drop the concept of HSI:INVALID:missing-data and just show HSI-0
    • Fix a small memory leak in bulkcontroller when getting the device info
    • Inhibit the Nordic HID dongle when there are peripheral updates pending
    • Retry claiming the interface if the USB device returns BUSY
    • Send the Olson location to bulkcontroller rather than the timezone
    • Use a longer timeout for the post-erase STM32 GetStatus
    • Wait 60 seconds for the bulkcontroller device after writing firmware

    This release adds support for the following hardware:

    • Logitech Rally System devices
    • More PixartRF HPAC devices
    • More Synaptics Prometheus fingerprint readers
    • Some Western Digital eMMC devices
    • VIA VL830 and VL832

    This release adds the following features:

    • Add a launchd agent for macOS
    • Add a new security attribute for BIOS capsule updates to be enabled
    • Add functionality to fix specific host security attributes
    • Add global information from the context into the report data
    • Add support for coSWID payload sections
    • Add support for parsing the EDID
    • Allow adding only-quirk instance IDs from quirk files
    • Install a sysusers.d systemd file when using -Dsystemd_unit_user

    This release fixes the following bugs:

    • Allow devices to require a connected display
    • Allow Wacom modules to specify a status polling interval
    • Do not show Intel CET unsupported as success
    • Do not show multiple Genesys GL32xx devices for the same physical device
    • Fix a fuzzing timeout in the HID descriptor parser
    • Recalculate the SUPPORTED flag after adopting a child device
    • Reduce the amount of memory used when chunking large firmware
    • Speed up logitech-bulkcontroller firmware updates
    • Stop reading ownership and TPM flashes left in Dell plugin
    • Try to use the LVFS when using report-history --force
    • Write the coSWID TAG_ID as a bytestring when possible

    This release adds support for the following hardware:

    • AMD dGPUs, Navi3x and above
    • Foxconn SDX12, SDX55 and SDX6X devices
    • Google Rex Intel USB-4 retimers
    • MediaTek DP AUX Scalers
    • Quectel EM160 module
    • Star Labs StarBook Mk VIr2
    • VLI VL105-VL109
    • Wacom DTH134 and DTC121 Tablets

    This release adds the following features:

    • Add optional support for Passim, a local caching server
    • Allow using fwupdtool get-devices --json

    This release fixes the following bugs:

    • Allow adding UF2 devices without a filesystem UUID
    • Correctly read the size of Synaptics Panamera MST firmware
    • Do not return historical results with no AppStream ID
    • Fix parallel build when using a machine with a lot of cores
    • Fix uninhibiting ModemManager after the fastboot flash has completed
    • Make firmware USI dock flashing more reliable
    • Record the update state of success when the device is returned to runtime
    • Remove the default-installed fwupd-refresh systemd preset
    • Sort composite updates by the device order when required

    This release adds support for the following hardware:

    • EPOS ADAPT 1x5
    • Fibocom FM101
    • Foxconn T99W373
    • Genesys GL3525S USB Hub
    • HP Rata/Remi BLE Mice
    • Luxshare Quad USB4 Dock
    • System76 Launch 3, Launch Heavy 3 and Thelio IO 2

    This release adds the following features:

    • Add a new HSI attribute that detects any missing Intel GDS mitigation
    • Allow configuring the refresh interval per-remote rather than per-system
    • Remove the libsoup-2.4 compatibility code
    • Show the firmware release checksum in CLI tools

    This release fixes the following bugs:

    • Correctly query the Steelseries Fizz version on reconnect
    • Correctly wait for USB replug on macOS
    • Do not add gl32xx disk partitions as extra devices
    • Do not assume the logical block size is always 0x200
    • Ensure the AppStream ID is set on historical releases
    • Enumerate Synaptics MST devices correctly
    • Fix a possible CFU crash when adding modules
    • Fix 'fwupdmgr get-history --json' output to only show one release
    • Fix 'fwupdmgr modify-remote' on ChromeOS
    • Fix incorrect OEM trusted reports flags being set when not matching
    • Fix regression in returning device history with libxmlb 0.3.12
    • Fix some Wacom hardware by only attempting retries for the busy error state
    • Fix transaction timed out issue for T99W373 QDU device
    • Handle cros-ec boards with '_v' in their name
    • Ignore the authentication when username and password are both empty
    • Load the effective size of a PE section instead of raw size
    • Set some feature flags when non-interactive
    • Set the HWIDs correctly when running macOS
    • Use a much larger USB timeout for STM32 erase operations
    • Use the correct offsets when checking Synaptics MST Spyder devices
    • Use the correct URI when downloading from authenticated remotes
    • Use /var/run when /run/lock does not exist

    This release adds support for the following hardware:

    • Genesys GL352350 and GL3590
    • Logitech Huddle
    • Microsoft USB-C Travel Hub
    • PixArt BLE HPAC OTA
    • Quectel RM520
    • Synaptics Triton devices
    • VIA VL122, VL817S and VL822T
    • Wacom One 13 and One 12 Tablets

    This release adds the following features:

    • Add the expected result to each HSI test attribute
    • Allow autodetection when using fwupdtool firmware-parse
    • Allow devices to only accept explicitly specified release versions
    • Allow filtering by release flags from fwupdtool and fwupdmgr
    • Allow filtering by remote when looking for trusted reports
    • Drop the libefiboot dependency and generate UEFI DPs directly
    • Ensure that BootService-only variables cannot be read in runtime mode
    • Parse the various SBAT sections from PE firmware
    • Record the NVRAM space used as report metadata
    • Show the user a warning when the ESP may not be valid
    • Speed up the daemon startup by 35% and reduce RSS by 12%
    • Support reading and writing EFI variables on Windows

    This release fixes the following bugs:

    • Check only the EFI executables from the boot menu when using --force
    • Correctly obtain the Thunderbolt is_native controller attribute
    • Deduplicate the remotes as required
    • Do not accidentally depend on python 3.9
    • Do not misuse the offset as an address in the SREC parser
    • Do not truncate feature reports to fix Wacom ID6 update
    • Fix parsing of IGSC code firmware
    • Get the Jabra GNP device name from the device
    • Ignore small ESP block devices if there are multiple choices
    • Never install a shim too new for the system
    • Only add the little-used _REV instance IDs by request
    • Use a CapsuleOnDisk filename supported by InsydeH2O

    This release adds support for the following hardware:

    • Belkin Thunderbolt 4 Core Hub dock
    • CE-LINK TB4 Docks
    • Genesys GL32XX SD readers
    • Genesys GL352350 USB 3.1 hub
    • Nordic HID devices without DFU support
    • TUXEDO InfinityBook Pro 13 v3
    • Wacom tablets with ID9 Bluetooth chipsets

    This release adds the following features:

    • Beep the console when CLI programs are waiting for user input
    • Bump requirements of various dependencies to remove a lot of fallback code
    • Show devices with problems in fwupdmgr get-upgrades

    This release fixes the following bugs:

    • Auto-detect the BCM57xx OEM PCI cards with double the expected EEPROM
    • Disable ThunderBolt retimer offline mode for some hardware
    • Do not assume a file descriptor of 0 is invalid when updating NVMe hardware
    • Fix discovery of Nordic peripherals connected via the dongle
    • Fix high memory usage when writing some EFI filesystem images
    • Fix USI dock devices with an incorrect factory-set firmware version
    • Ignore a client refresh on a non-download remote to fix old versions of KDE
    • Ignore the immutable flatpak config file file permission being incorrect
    • Limit the number of possible file objects in the EFI filesystem
    • Make the installed size smaller by deduping and filtering assets
    • Only expose --force for security attributes for unsupported builds
    • Require the user to manually replug the USI dock after update has completed

    This release adds support for the following hardware:

    • AVer FONE540
    • Genesys GL3525 USB hubs
    • Goodix Touch controllers
    • Jabra Evolve 65e/t and SE, Evolve2, Speak2 and Link devices

    This release adds the following features:

    • Add a new device-wait command to fwupdmgr for use in boot-time scripts
    • Add a report flag if generated by the OEM which can be used for policy
    • Add CCID decoding support to pcap2emulation
    • Add fwuptool build-archive cmd to allow building firmware without gcab CLI
    • Add support for NVMe CA3 activation
    • Allow setting device flags and version numbers from the metadata
    • Allow specifying the MTD metadata offset and size
    • Allow using basic auth when uploading a report
    • Autogenerate enums and structures (with default values) from Rust format
    • Delete the obsolete .gz files if the remote is now using .xz
    • Read the AGESA Bootloader and TEE versions from the kernel
    • Tag releases with an extra flag if they have a report we trust

    This release fixes the following bugs:

    • Add the latest dbx fixups for BlackLotus
    • Allow setting the remote username and password on the CLI
    • Be more careful parsing the kernel cmdline
    • Convert the man pages to MarkDown format and use a built-in converter
    • Enable the vendor-directory remote by default
    • De-duplicate releases by the container checksum
    • Enable the retimer offline mode depending on HWIDs
    • Escape the username and password when using basic authentication
    • Fix a pci-mei crash by more carefully converting enums to strings
    • Fix Dell dock by triggering passive flow for USB4 subcomponents
    • Fix the version detection for SteelSeries Bluetooth mode
    • Have fwupd-refresh.timer trigger once per hour on average
    • Implement enough of the CFI specification to be able to update a device
    • Allow the firmware time to process commands to avoid a Wacom device crash
    • Invalidate the XMLb cache when installing new fwupd versions
    • Make the config file be called fwupd.conf and add a useful manpage
    • Move the expected default plugin config value to the code
    • Prefer local remotes when deduplicating releases with the same version

    This release adds support for the following hardware:

    • CH347 SPI programmer
    • Logitech Tap devices
    • More Logitech Unifying receivers
    • Nordic HID MCUboot direct-xip
    • nRF52 Desktop Keyboard
    • Wacom Cintiq Pro 27

    This release adds the following features:

    • Add pcap converter which allows emulating devices from a Wireshark dump
    • Add the ability to dump TPM firmware for future use
    • Optionally retain firmware in a backup remote
    • Record the ESP type in the firmware report sent to the LVFS

    This release fixes the following bugs:

    • Accept application/octet-stream for archives when the mime database is missing
    • Add the latest dbx version version fixups as Microsoft removed another entry
    • Assume DFU appIDLE if GetStatus is not implemented
    • Do not require signatures for local or directory remotes
    • Do not use pandoc to build the man pages
    • Enhance Qubes functionality to use JCat
    • Fix a CCGX 'usbfs: process did not claim interface 1 before use' warning
    • Fix a compile warning when using a new libqmi version
    • Fix a critical warning when parsing an empty kernel cmdline
    • Fix a synaptics-cape regression where the firmware pauses for INTR
    • Fix the defines for HFSTS6 enforcement policy
    • Fix the i2c name properly for ElanTP hardware
    • Fix the name of the MTD Intel SPI controller
    • Set the release remote when installing archives
    • Use the powerd power type information to better set AC levels

    This release adds support for the following hardware:

    • Framework Audio Card
    • Lenovo ThinkPad TBT3-TR Gen 2
    • Wacom Intuos BT S Gen 3

    This release fixes the following bugs:

    • Allow setting the package user agent before the client has connected
    • Fix a small memory leak when refreshing metadata

    This release adds the following features:

    • Add support for replaying USB devices so they can be emulated in CI
    • Allow desktop software to inhibit the system to prevent updates
    • Allow using requirements with depth=0 and no parent
    • Auto-set the CCGX remove-delay now we parse DMC subcomponents
    • Detect and warn users with the broken NVMe firmware 3B2QGXA7
    • Print errors as JSON objects when using fwupdmgr --json

    This release fixes the following bugs:

    • Allow installing battery firmware updates even when the power is too low
    • Correctly fall back to the compatible vendor when FDT vendor is missing
    • Detect CCGX factory mode and set a non-zero version
    • Detect fixed Insyde firmware that can actually use Capsule-on-Disk
    • Do not make any of the HWIDs setup failures fatal
    • Fix a critical warning when parsing an empty kernel cmdline
    • Fix a small memory leak when installing TPS6598x firmware
    • Fix compiling with -Dbuild=library for Flathub
    • Fix fwupdtool firmware-convert to work with image-less formats
    • Fix regression in downloading files in fwupdtool
    • Fix SMBIOS struct parsing when the tag section ends with NUL
    • Indicate HSI attributes that will only be returned for specific CPU vendors
    • Only accept application/x-xz compression for the metadata payload
    • Only offset the IPMI user ID when using Lenovo XCC
    • Prefer the Intel USB4 plugin over the Thunderbolt plugin when required
    • Require at least twice the capsule size in the ESP when updating
    • Save all the device flags in the pending database correctly
    • Set the device percentage and status for the duration of the update
    • Show the 4XX download failure in the CLI error output
    • Speed up regenerating the MOTD when installing composite devices
    • Use an updated shim if provided during for capsule update
    • Use strict snap confinement

    This release adds support for the following hardware:

    • CalDigit Element Hub
    • CalDigit TS4 Dock

    This release adds the following features:

    • Add a PE/COFF firmware parser to allow reading coSWID SBoM data
    • Allow dumping CFI SPI chips using devices like CH341a
    • Refactor the HWIDs functionality to include FDT data

    This release fixes the following bugs:

    • Add back a legacy eMMC GUID to fix a regression
    • Always search for uSWID SBoM data in the image
    • Do not allow LZX compressed cabinet archives
    • Fallback to the checksum if the metadata artifact is invalid
    • Improve FDT parsing compatibility with new OpenBMC images
    • Never call grub2-probe without arguments
    • Respect user requested paths for the ESP even if they are not volumes
    • Speed up ChromeOS startup by a huge amount when using directory remotes
    • Verify the Synaptics RMI signature in more cases

    This release adds support for the following hardware:

    • Quectel RM520
    • StarBook Mk VI
    • System76 launch_heavy_1

    This release adds the following features:

    • Add an interactive request for re-inserting the USB cable
    • Add SHA384 support for TPM hashes
    • Add X-FingerprintReader, X-GraphicsTablet, X-Dock and X-UsbDock categories
    • Allow specifying OR parent requirements in metadata

    This release fixes the following bugs:

    • Add the fwupd version to the HSI result if the chassis is invalid
    • Allow getting the ESP when there is a block device with no filesystem
    • Allow reinstalling on devices with only-version-upgrade set
    • Do not require the TPM event log to have all reconstructions
    • Fix a tiny memory leak when parsing signed reports
    • Ignore failure to mount the ESP if unsupported
    • Never allow using SHA-1 for checksum validation
    • Return a more useful error if USB recovery failed
    • Skip the fwupdx64.efi BootXXXX entry when measuring system integrity
    • Speed up daemon startup using prepared XPath queries
    • Suggest to turn on ThunderboltAccess for Lenovo systems
    • Use better defaults if the config file is missing

    This release adds support for the following hardware:

    • More Solidigm NVMe devices
    • More Synaptics Cape devices
    • More Synaptics Prometheus devices
    • Most Texas Instruments USB-4 docks
    • Scaler support for Wacom USB devices
    • Several new Wistron USB-C docks

    This release adds the following features:

    • Add BIOS rollback protection support for Dell and Lenovo systems
    • Generate OVAL rules for openSCAP evaluation
    • Show the signed reports from QA teams in client tools

    This release fixes the following bugs:

    • Add a X-Gpu category for new hardware support
    • Add more ChromeOS metadata to the report attributes
    • Ensure the device name is set for Intel USB4 devices
    • Fix a critical DFU CSR warning when deploying firmware
    • Fix a Synaptics RMI issue when updating non-secure devices
    • Match more device properties when using GetDetails
    • Move AMD platform rollback protection to level 4
    • Use the correct AppStream ID for the Key Manifest failure
    • Wait for the Intel GPU to come back after updating

    This release adds support for the following hardware:

    • Logitech Whiteboard cameras
    • More Goodix MoC devices
    • Several QSI Docks

    This release adds the following features:

    • Add a new HSI check for the leaked Lenovo 'Key Manifest' hashes
    • Measure system integrity when installing UEFI updates
    • Record more host DMI data when submitting a report for dbx failures
    • Use xz-compressed metadata to reduce bandwidth used by ~25%

    This release fixes the following bugs:

    • Add documentation for three existing HSI attributes
    • Add re-insert requirement for Analogix devices
    • Allow parsing metadata more than 1MB in size
    • Do not follow symlinks when searching for ESP devices
    • Ensure the config file permission is correct for built-in plugins
    • Fix a compile failure when compiling without efiboot
    • Fix a regression when using fwuptool install-blob with FMAP firmware
    • Only count the Microsoft hashes when getting the dbx version
    • Only use the IFD when the system is Intel-based
    • Support loading CoSWID when only one role has been set

    This release adds support for the following hardware:

    • Anker Thunderbolt 4 Mini Hub
    • ELAN haptic hardware
    • Fingerprint lenfy devices
    • Goodix GF3258WNC
    • Intel discrete GPUs (experimental)
    • More Star Labs laptops
    • QSI Godzilla Creek Reference Hub

    This release adds the following features:

    • Reduce the installed package size by more than 30%
    • Translate more interactive messages

    This release fixes the following bugs:

    • Allow disabling a DFU device when required
    • Fix a regression when getting the i2c bus number
    • Fix a small memory leak when reloading the parade-lspcon device
    • Fix installing the dbx update when using fwupdtool
    • Improve writing CoSWID and uSWID metadata
    • Only include the last 5 releases in the installed metainfo file
    • Only request the BOS descriptor for newer libgusb versions
    • Prevent high memory usage when loading corrupt SREC files
    • Try harder when trying to find the default ESP volume
    • Use a higher compression preset for the UEFI splash images

    This release adds support for the following hardware:

    • Focaltech touchpads
    • FPC fingerprint readers
    • Supermicro machines using Redfish

    This release adds the following features:

    • Add a new android-boot plugin to update specific block devices
    • Add new plugin to display SMU firmware version on AMD APU/CPU
    • Add support for platform capability descriptors so devices can set quirks
    • Move the generic Intel Goshen Ridge code out to a new plugin

    This release fixes the following bugs:

    • Allow specifying the ESP when applying the dbx update
    • Always check the BDP partitions when getting all the possible ESPs
    • Correctly update Wacom AES devices
    • Disable changing sleep mode on Ryzen 6000 systems
    • Do not show the 'may not be usable while updating' message for DBX updates
    • Expose Pine64 PinePhone Pro MTD as Tow-Boot
    • Fix a critical warning when issuing Secure Boot modem AT commands
    • Fix a fuzzing crash when parsing malicious FDT data
    • Fix aligning up addresses greater than 4GB
    • Fix a possible crash when dumping VBE firmware
    • Fix a possible critical warning when parsing cabinet archives
    • Fix a regression when parsing pixart-rf firmware
    • Fix a small memory leak when parsing UF2 files
    • Fix checking for invalid depth requirements
    • Fix parsing the coSWID firmware ID when encoded as a UUID
    • Fix parsing uSWID uncompressed metadata
    • Fix uploading to DFU-CSR devices
    • Limit the archive size to 25% of the RAM, or 4G
    • Load coSWID metadata from a uSWID MTD block device
    • Never save the Redfish auto-generated password to a user-readable file
    • Only create users using IPMI when we know it's going to work
    • Write all the CCGX metadata block as intended

    This release adds support for the following hardware:

    • Corsair SABRE RGB PRO Gaming mouse
    • More Sonix CAM devices
    • More Intel Goshen Ridge USB-4 docks

    This release adds the following features:

    • Add a translated title and long description for HSI security attributes
    • Add support for loading a machine-default BIOS settings policy
    • Add support for reading and writing BIOS settings
    • Allow loading BIOS settings for host emulation
    • Prompt users to fix some BIOS configuration issues

    This release fixes the following bugs:

    • Actually show provided AppStream security issues
    • Add Quectel secure boot status AT commands
    • Correctly detect CET IBT
    • Do not assert when running with no plugins
    • Do not require UEFI capsule updates for checking TPM PCR0
    • Do not show HSI events where we changed the spec result value
    • Fix applying the latest DBX update
    • Include vfat in the list of possible BDP partition types
    • Install all devices with the same composite id in fwupdtool
    • Only fail the kernel HSI test for specific taint reasons
    • Only show changed events in fwupdmgr security
    • Update vulnerable CMSE versions from CSMEVDT data

    This release adds support for the following hardware:

    • Elan non-HID touchpads
    • Google Prism
    • LabTop Mk III
    • ThinkPad Thunderbolt 4 Dock
    • ThinkPad Universal Smart Dock

    This release adds the following features:

    • Add resolution flags to each security attribute failures for the user
    • Allow loading in emulated host profiles for debugging
    • Check if Intel TME has been disabled by the firmware or platform
    • Wait for the system to acquiesce after doing each update

    This release fixes the following bugs:

    • Do not use CoD even when advertised on non-aarch64 platforms
    • Fix a crash when updating the Logitech Bolt radio device
    • Fix a critical warning when parsing an invalid PHAT record
    • Fix a critical warning when parsing invalid FDT firmware
    • Fix fwupdmgr security when plugins are added to the blocklist
    • Fix parsing SMBIOS data to correct the device hardware IDs
    • Fix uploading signed reports by sending the correct checksum
    • Use the correct protocol attribute name when exporting to JSON

    This release adds support for the following hardware:

    • Additional Startech devices
    • Additional Elan fingerprint readers

    This release adds the following features:

    • Add startup profiling which allowed us to speed up daemon startup considerably
    • Add support for OptionROM, CPD and FPT firmware formats for future hardware
    • Add the HostVendor to the D-Bus interface
    • Break some internal ABI and add a conversion helper for out-of-tree plugins
    • Optionally build the quirk files into the daemon binary to reduce installed size

    This release fixes the following bugs:

    • Allow front-end clients to read the percentage property
    • Allow more quirk entries to add multiple items
    • Allow to force install Genesys firmware even if the public-key does not match
    • Allow UFS disks to define the signed status in metadata
    • Autoconnect the Redfish network device when rebooting the BMC
    • Copy the instance ID strings when incorporating devices
    • Do not generate a capsule header for the FMP GUID
    • Ensure more firmware formats can round-trip to and from XML
    • Fix a regression for devices using the Atmel FLIP Bootloader
    • Fix running fwupdtool security with a user-specified plugin allowlist
    • Handle ENOTTY with the correct error code for ioctl calls
    • Increase the self tests coverage substantially
    • Modernize the AMT plugin and split out common MEI functionality
    • Only move the logitech-bulkcontroller progressbar forwards when writing
    • Set the device ID on the FwupdRequest to allow better UX
    • Show the get-details output when the device requirements fail
    • Simply quirk matching for i2c devices to speed up daemon startup
    • Support SHA256 fastboot hashes if specified
    • Use force-detach to bypass the DFU streaming check for camera devices
    • Use the SCSI target to correctly set the physical ID
    • Wait for the System76 launch device to re-enumerate if already unlocked

    This release adds support for the following hardware:

    • Corsair HARPOON RGB Wireless mouse
    • U-Boot devices writing simple FIT images
    • Genesys M27fd AIM101
    • More PixArt wireless devices
    • More Steelseries HID, Sonic and Fizz devices
    • System76 launch_2

    This release adds the following features:

    • Add archive writing support for devices with composite firmware
    • Add a way to read device composite firmware in fwupdtool
    • Allow clients to opt-in to showing updates with user-solvable problems
    • Allow the device to pause polling when writing firmware
    • Export the system and device battery levels on the D-Bus interface
    • Log errors and warnings to the win32 eventlog when required
    • Add X-UsbReceiver as an update category with icon usb-receiver

    This release fixes the following bugs:

    • Accurately return the last-set status to client tools
    • Allow dumping flashrom firmware using fwupdtool
    • Allow specifying a non-file D-Bus transport
    • Allow to request post actions from fwupdtool
    • Always be arch-explicit when installing OS deps
    • Be more resilient when restarting the Redfish BMC
    • Do not mark all Redfish updates as UPDATABLE
    • Do not use 'dongle' to describe USB receiver hardware
    • Download in-process when using fwupdtool
    • Fix a critical warning on failed modem update
    • Fix regression when probing PS175 devices
    • Hardcode the Redfish filedata name to firmware.bin
    • Set the Bluetooth version if REV has been set
    • Switch the Windows installer from NSIS to MSI
    • Use StartServiceCtrlDispatcherA for the daemon on Windows
    • Use the native certificate store on Windows

    This release adds support for the following hardware:

    • Corsair KATAR PRO XT, SABRE PRO and KATAR PRO Wireless
    • HP Thunderbolt Dock G4
    • Lenovo ThinkPad Universal USB-C Dock
    • More PixArt wireless devices
    • More SunplusIT USB cameras
    • Some UFS devices
    • Steelseries Aerox 3 Wireless and Rival 3 Wireless

    This release adds the following features:

    • Add a new attribute for CPUs supported by HSI
    • Add coSWID and uSWID parsers to libfwupdplugin for initial SBoM support
    • Add new HSI attributes for the AMD PSP and various other system protections
    • Add the runtime fwupd-efi version as a firmware requirement
    • Allow 'fwupdmgr install' to install a specified firmware version
    • Allow overriding the detected machine type for debugging and development
    • Restart the BMC after installing BCM updates
    • Show the device serial number and instance IDs by default
    • Support dumping the MTD image to a firmware blob
    • Take a device inhibit when updating a device
    • Use the CFI manufacturer ID to set the vendor
    • Use the correct icon automatically for more hardware

    This release fixes the following bugs:

    • Add signed-payload metadata for more devices
    • Allow Capsule-on-Disk to work in more cases
    • Allow quirking the detected flashrom flash size
    • Check for os-release on FWUPD_SYSCONFDIR
    • Check the alignment when parsing raw firmware
    • Check the update protocol exists when checking requirements
    • Convert the build system to use meson tristate features
    • Correctly probe USB-2 hubs with more than 7 ports
    • Do not add the Windows compatibility ID to capsule devices
    • Do not allow the DBX update for specific motherboards
    • Do not expect KernelCmdline on Windows
    • Do not export USB4 host controllers as updatable if they don't have unique GUIDs
    • Do not fallback to audio-card and use a more suitable icon for USB hubs
    • Do not hardcode the libexecdir to /usr/libexec
    • Do not leak child processes when canceling
    • Do not show unconnected or unreachable devices in the client tools
    • Do not throw away the TPM eventlog when uploading to the LVFS
    • Do not use /var/run for the socket
    • Export the version_lowest_raw value correctly
    • Fix build for MacOS and add to the CI matrix
    • Fix eventlog replay for Intel TXT machines
    • Fix several small memory leaks
    • Fix writing large mtd images than 10kb
    • Ignore MTD devices that report EPERM on open
    • Mark the ME region device locked if it is read only
    • Never send the DeviceChanged signal with old data
    • Only show the CLI time remaining for predictable status phases
    • Respect the NO_COLOR env variable
    • Return the correct error when there is no GPIO device to open
    • Support the new UPower PENDING device states

    This release adds support for the following hardware:

    • CH341A SPI programmer
    • Corsair Sabre RGB PRO and Slipstream USB receiver
    • Genesys GL3521 and GL3590 hubs
    • Google Servo Dock
    • Logitech M550, M650 and K650
    • More ELAN fingerprint readers
    • More integrated Wacom panels
    • More NovaCustom machines
    • More StaLabs StarLite machines
    • More Tuxedo laptops
    • Quectel EM05
    • FlatFrog devices
    • System76 launch_lite_1

    This release adds the following features:

    • Add a flag for UEFI devices that never want a capsule header auto-added
    • Add a flag to indicate the device has a signed or unsigned payload
    • Add a plugin to set a GPIO pin for the duration of an update
    • Add a simple plugin to enumerate (but not update) SCSI hardware
    • Add two more instance IDs to the MTD devices
    • Add X-BaseboardManagementController as an update category
    • Allow assigning issues to devices for known high priority problems
    • Parse the MTD firmware version using the defined GType

    This release fixes the following bugs:

    • Check the IFD sections have non-zero data length to fix a critical warning
    • Modify the AT retry behavior to fix getting the firmware branch
    • Do not run fwupd-refresh automatically in containers
    • Do not show a warning if the TPM eventlog does not exist
    • Do not show TSS2 warning messages by default
    • Fix a critical warning when loading an empty TPM eventlog item
    • Fix a logic error when adding the community warning in fwupdmgr
    • Fix loading flashrom devices in coreboot mode
    • Fix the error handling when updating USB4 retimers
    • Show the user when devices are not updatable due to inhibits
    • Skip probing the Dell DA300 device to avoid a warning
    • Try harder to convert to a version into a correct semver
    • Use multiple checksums when there are no provided artifacts

    This release adds support for the following hardware:

    • HP M2xfd monitors
    • Star Lite Mk III

    This release adds the following features:

    • Add a flag to indicate the firmware is not provided by the vendor
    • Add support for showing dependency versions in JSON format
    • Allow fwupd to operate in socket mode without a D-Bus daemon
    • Allow marking a device as End-of-Life by the OEM vendor
    • Allow specifying the machine Best Known Configuration locally
    • Fall back to the ARM Device Tree 'compatible' data when required

    This release fixes the following bugs:

    • Be more robust by retrying IPMI transactions on servers
    • Change the expired Redfish password when required
    • Fix a ModemManager segfault on startup for some MBIM-QDU devices
    • Fix a possible dell-dock segfault at startup
    • Fix compiling with new versions of efivar
    • Fix the Nordic bootloader type detection
    • Fix USB4 retimer enumeration
    • Get the SMBIOS table and host machine ID when running on Windows
    • Show results when calling get-details if failing requirements
    • Uninhibit the modem using ModemManager after upgrade

    This release adds support for the following hardware:

    • Future Analogix devices
    • NovaCustom NV4x

    This release adds the following features:

    • Add firmware branch support for ModemManager devices
    • Allow firmware engineers to patch files at known offsets
    • Show why more devices are not marked as updatable

    This release fixes the following bugs:

    • Allow fwupdtool to be run as the non-root user in more cases
    • Assign the Logitech bulkcontroller update interface correctly
    • Do not allow UEFI updates when the laptop lid is closed
    • Do not autoload ipmi-si to avoid warning on non-server hardware
    • Do not show a critical warning for a weird TPM event log
    • Fix waiting for USB devices when using Windows
    • Ignore non-PCI NVMe devices

    This release adds support for the following hardware:

    • HP USB-C G2 Dock
    • Many UF2 devices, experimentally
    • More PixArt devices
    • Nordic HID devices using MCUBoot
    • Quectel EG25-G LTE Modem
    • ThinkPad Thunderbolt 4 Dock

    This release adds the following features:

    • Add a sync-bkc subcommand to ensure a known set of firmware versions
    • Add FuArchiveFirmware for plugins that use archives as firmware files
    • Add quirkable page and sector size properties to FuCfiDevice
    • Make Upower and powerd support optional

    This release fixes the following bugs:

    • Add some sanity checks to the elanfp firmware parser
    • Add the CFI JEDEC instance ID if using the vendor-extended version
    • Check the value range when parsing the quirk keys
    • Do not wait for a USB runtime if will-disappear is set
    • Enable the MOTD integration when using pam_motd
    • Fix DFU regression when merging the FuProgress work
    • Fix running the tests when fwupd is not installed
    • Fix the GLib error message when inotify max_user_instances is too low
    • Fix VLI VL820Q7 detection to fix flashing of the Lenovo TBT3 dock
    • Ignore a USB error for STM32 attach when the device goes away
    • Make the HSI tests optional for embedded targets
    • Make the plugin startup order deterministic
    • Set Thunderbolt ports offline on host controller
    • Use endian-safe version functions when enumerating Logitech hardware
    • Use lowercase flag names in intel-spi to prevent a runtime warning
    • Wait for the System76 Launch device to come back from DFU mode

    This release adds support for the following hardware:

    • Most Nordic Semiconductor nRF Secure devices

    This release adds the following features:

    • Add a new HSI check that PCR registers 0-7 are not empty
    • Add several compile flags to reduce the install size by over 300Kb
    • Allow overriding HwId data from the daemon.conf config file
    • Allow overriding the firmware GType from a quirk file
    • Export the component release ID over DBus
    • Remove support for the SoloKey and ChaosKey devices
    • Show a daemon warning if quirk flags are malformed
    • Speed up the daemon startup by ~40% by doing less at startup

    This release fixes the following bugs:

    • Be case insensitive when fixing the device model
    • Fix a critical warning in ccgx found by the fuzzer
    • Fix a DFU crash if the attach failed due to a hardware fault
    • Fix a Redfish crash when specifying a URL without a port
    • Fix CLI downloads when using fwupdmgr --ipfs
    • Fix critical warning when /etc/machine-id does not exist
    • Inhibit thunderbolt devices to correctly use UPDATABLE_HIDDEN
    • Set SSL_VERIFYHOST=0 when using Redfish to fix OpenBMC auth
    • Skip UEFI devices that fail coldplug

    This release adds support for the following hardware:

    • All exported MTD block devices

    This release adds the following features:

    • Allow specifying 'fwupdmgr device-test foo --json' for unattended testing
    • Allow using a filename when using set-approved-firmware
    • Inhibit ModemManager device in mbim-qdu
    • Share the Common Flash Memory Interface quirks between plugins
    • Show changes in HSI attributes when using 'fwupdmgr security'
    • Show the user a warning if updating may affect full-disk-encryption
    • Show translated firmware release notes when provided
    • Support loading remotes from /var/lib/fwupd/remotes.d

    This release fixes the following bugs:

    • Fix a CCGX regression when loading firmware
    • Fix a potential crash when dumping Parade devices
    • Fix build error when sys/io.h is not available
    • Fix building the Synaptics RMI self tests on s390x
    • Fix the CSME CVE detection for new generations
    • Handle EPERM when running the self tests on systems with IPMI
    • Mark as SUPPORTED even if on battery power
    • Only save the HSI attributes to the database if different
    • Raise the client timeout value from 25 seconds to fix Redfish startup
    • Redirect the old HSI links to the correct place
    • Relax the ITE SuperIO signature checks for new hardware support
    • Set device time and timezone for logitech bulkcontroller devices
    • Set the verfmt of the returned device when the daemon device is unset

    This release adds support for the following hardware:

    • Dell Atomic Dock
    • HP Thunderbolt Dock G4
    • More PixArt devices
    • Steelseries Stratus
    • Wacom 3rd-gen Intuos BT

    This release adds the following features:

    • Add FuCfuPayload and FuCfuOffer for future usage
    • Add support for an 'unreachable' device flag
    • Add support for Logitech devices supporting the Unified Battery feature
    • Allow adding GUIDs to each HSI security attribute
    • Allow installing the LVFS remote, but with it disabled by default
    • Convert security attributes to JSON and write then to the database
    • Convert the device test script to a fwupdmgr subcommand
    • Create Redfish user accounts automatically using IPMI
    • Use an interactive request to restart some Logitech DFU devices

    This release fixes the following bugs:

    • Abort on invalid SREC files early to avoid a fuzzing timeout
    • Allow using interrupt transfers for HID devices
    • Allow waiting for multiple devices to replug
    • Fix a critical warning on a Unifying flash failure
    • Fix a regression in flashing the Dell dock
    • Fix Thunderbolt host controller probing
    • Forcefully set checksums found in cabinet files to lowercase
    • Force UX-capsule over full size BGRT
    • Make the SuperIO ports and timeouts specific to the DMI model
    • Only probe SynapticsMST devices that have opted-in
    • Remove support for --ignore-power as it did not work for UEFI firmware
    • Reset the CMOS as required when changing system firmware branch
    • Restart the daemon if any of the plugin config files are modified
    • Show HSiLevel=0 attributes in JSON security output
    • Update the child composite ID if the parent changes
    • Use a per-device global percentage completion
    • Write the BMP image upside down to avoid using a negative bitmap height

    This release adds support for the following hardware:

    • A huge number of Synaptics CAPE devices
    • Elan fingerprint readers
    • Logitech Bolt peripherals, receivers and radio hardware
    • Logitech devices supporting the bulk controller protocol
    • More supported PixArt devices
    • More supported StarBook coreboot devices
    • Union Point SPI hardware

    This release adds the following features:

    • Add a plugin to check Lenovo firmware settings
    • Add initial support for the powerd daemon
    • Add support for CapsuleOnDisk
    • Add support for installing UEFI updates from GRUB
    • Add support for soft-requirements that can be ignored with --force
    • Allow devices to only accept version upgrades
    • Allow discovery of Redfish BMCs specified by VID-PID or MAC
    • Allow the daemon to request interactive action from the end user
    • Automatically connect the BMC network interface at startup
    • Show the build timestamp if set on the device
    • Show the user how to switch out of Wacom tablet Android-mode

    This release fixes the following bugs:

    • Add the alternate vendor name into the 8BitDo allowlist
    • Allow multiple devices to set WAIT_FOR_REPLUG
    • Allow the client to watch for more property changes
    • Always ensure the SuperIO version string is NUL terminated
    • Automatically clear the update error as required
    • Disable all UX capsules for Lenovo hardware
    • Do not assume the metainfo file is NUL-terminated
    • Do not save invalid files on LVFS server error
    • Fix a VLI regression in enumerating the PD device
    • Fix a VLI regression when installing VL820Q7 firmware
    • Fix enumeration of the Synaptics Prometheus config child
    • Fix parsing Redfish USB/PCI network VID/PIDs
    • Fix the fwupdmgr progressbar spinner to actually work
    • Fix version number for legacy Wacom Bluetooth modules
    • Ignore virtual M.2 ATA devices
    • Preserve NEEDS_REBOOT on successful update
    • Prevent a corrupt PHAT table from allocating lots of memory
    • Read the Redfish SMBIOS table when required
    • Remove the vendor string from the device name where required
    • Save the update state to the database correctly all of the time
    • Switch from sysctl to ioctl for ESRT on FreeBSD
    • Try reading from /sys/class/dmi if SMBIOS direct access fails
    • Watch for children added or removed after setup has been completed
    • Work around a XCC-ism on Lenovo hardware

    This release adds support for the following hardware:

    • ModemManager devices supporting Firehose or MBIM QDU
    • More models of RTS54HUB
    • More Poly DFU devices
    • Parade LSPCON
    • PixArt receiver and wireless hardware
    • Realtek MST with RTD2142
    • SuperIO IT5570
    • USB4 Dell dock

    This release adds the following features:

    • Add FreeBSD UEFI Capsule support
    • Add generic ModemManager support for PCI based modems
    • Add initial support for USB4 module in the Dell dock
    • Add support for sibling requirements
    • Add support for the ACPI PHAT table
    • Allow building the documentation with gi-docgen and gtk-doc
    • Support binary artifact resources in cabinet archives
    • Use GProxyResolver to get the system proxy setting for a given URL

    This release fixes the following bugs:

    • Ask the user to confirm all CLI actions
    • Check the versions of libfwupd and libfwupdplugin at startup
    • Do not prevent firmware updates on desktop hardware
    • Do not show an invalid DFU warning on attach
    • Fail parsing if wacom firmware sections are not in sorted order
    • Fall back to binary files when flashing STM32 hardware
    • Fix a critical warning when downloading files
    • Fix a possible critical warning due to a bug in type casting
    • Fix a regression in updating the WD19TB dock
    • Fix GUID generation on pixart hardware
    • Fix the VLI i2c device enumeration, e.g. MSP430
    • Follow HTTP 3XX redirects when downloading files
    • Force the device locker to close() an aborted open()
    • Handle bsdisks' UDisks2 implementation on FreeBSD
    • Only lock fwupdtool when loading the engine
    • Read current Wacom firmware index before finding image to write
    • Support all hash types when loading cabinet archives
    • Support mirroring the detach and update images
    • Switch lock directory from /var/run to /run/lock

    This release adds support for the following hardware:

    • Minibons devices
    • More 8BitDo hardware
    • More Synaptics Prometheus hardware
    • RTD21xx devices in background mode
    • Some Kingston SSD and NVMe hardware

    This is the first release of the 1.6.x series, and since 1.5.x some internal plugin API has been changed and removed. Although we've tested this release on all the hardware we have regression tests for, bugs may have crept in; please report failures to the issue tracker as required.

    There are several new plugins adding support for new hardware and a lot of code has been migrated to the new plugin API. The public libfwupd API also has some trivial additions, although no action is required.

    This release adds the following features:

    • Add a composite ID that is used to identify dock device components
    • Add an Intel Flash Descriptor parser
    • Add API to allow the device to report its own battery level
    • Add API to recount why the device is non-updatable
    • Add lspcon-i2c-spi programmer support
    • Add more hardware support to the pixart-rf plugin
    • Add some more new category types for firmware to use
    • Add support for downloading the SPI image from the Intel eSPI device
    • Add support for some Analogix hardware
    • Add support for writing SREC firmware
    • Add the firmware-sign command to fwupdtool to allow resigning archives
    • Split UEFI EFI binary into a subproject
    • Use an OFD or Unix lock to prevent more than one fwupdtool process

    This release fixes the following bugs:

    • Actually write the bcm57xx stage1 version into the file
    • Add option to disable the UEFI capsule splash screen generation
    • Avoid use-after-free when specifying the VID/PID in dfu-tool
    • Cancel the GDBusObjectManager operation to fix a potential crash
    • Check PixArt firmware compatibility with hardware before flashing
    • Do not check for native dependencies as target dependencies
    • Do not use help2man to build manual pages
    • Fix a crash when shutting down the daemon
    • Fix build on musl
    • Fix build when using BSD
    • Fix /etc/os-release ID_LIKE field parsing
    • Force the synaptics-rmi hardware into IEP mode as required
    • Never allow D-Bus replacement when a firmware update is in operation
    • Offer the user to refresh the remote after enabling
    • Remove unused, unsafe and deprecated functions from libfwupdplugin
    • Simplify asking the user about reviews
    • Write BMP data directly without using PIL
    • Write synaptics-rmi files with valid checksum data

    This release adds the following features:

    • Add initial support for Bluez bluetooth devices
    • Add more supported pixart devices
    • Add support for the RTD21xx HDMI converter

    This release fixes the following bugs:

    • Convert MBR types to GPT GUIDs to help find the ESP
    • Do not allow updating a synaptics-mst device with no customer ID
    • Drop unused heap pages after startup has completed
    • Ensure SBAT metadata is added correctly
    • Move the plugin build logic to the plugins themselves
    • Only allow verify-update for plugins that support CAN_VERIFY

    This release adds the following features:

    • Add SBAT metadata to the fwupd EFI binary
    • Add support for GD32VF103 as found in the Longan Nano
    • Add support for RMI PS2 devices
    • Add support for the System76 Keyboard
    • Allow downloading firmware from IPFS
    • Install the UX data into a single .tar.xz file

    This release fixes the following bugs:

    • Add support for the Starlabs LabTop L4
    • Allow using an external ESP again
    • Ask the user to reboot when required if downgrading
    • Be more paranoid when parsing ASCII buffers and devices
    • Check if the fwupd BootXXXX entry exists on failure
    • Clear the pending flag if restarting the system
    • Do not allow flashing using flashrom if BLE is enabled
    • Do not allow Lenovo hardware to install multiple capsules
    • Do not parse the OptionROM image
    • Do not show Unknown [***] for every client connection
    • Fix dnload wBlockNum wraparound for ST devices
    • Fix OOM when using large ArchiveSizeMax values
    • Fix several crashes spotted by AddressSanitizer
    • Fix several places where the Goodix MOC plugin could crash
    • Include the PCR0 to the report metadata
    • Report the lockdown status from UEFI and SuperIO plugins
    • Show a console warning if the system clock is not set

    This release adds the following features:

    • Add a plugin to update PixArt RF devices
    • Add new hardware to use the elantp and rts54hid plugins
    • Allow specifying more than one VendorID for a device
    • Detect the AMD TSME encryption state for HSI-4
    • Detect the AMI PK test key is not installed for HSI-1

    This release fixes the following bugs:

    • Fix flashing a fingerprint reader that is in use
    • Fix several critical warnings when parsing invalid firmware
    • Fix updating DFU devices that use DNLOAD_BUSY
    • Ignore the legacy UEFI OVMF dummy GUID
    • Make libfwupd more thread safe to fix a crash in gnome-software
    • Never show unprintable chars from invalid firmware in the logs

    This release adds the following features:

    • Add Maple Ridge Thunderbolt firmware parsing support
    • Add --no-remote-check to ignore checking for download remotes
    • Allow creating FMAP and Synaptics firmware using builder.xml
    • Build a test harness that uses honggfuzz to fuzz firmware

    This release fixes the following bugs:

    • Allow using fwupdtool as non-root for firmware commands
    • Do not trust the Block.HintSystem boolean for ESP filtering
    • Fix a memory leak when parsing Synaptics firmware
    • Fix a possible crash when reading the Goodix MOC USB request
    • Fix crashes when parsing invalid FMAP, DMC, Solokey and Synaptics images

    This release adds the following features:

    • Allow setting the GMainContext when used for sync methods
    • Export the driver name from FuUdevDevice

    This release fixes the following bugs:

    • Add a UEFI quirk for Star Labs Lite Mk III
    • Add the device firmware ID for serio class hardware
    • Allow the client to send legacy PKCS7 and GPG signatures
    • Do not use accidentally depend on new meson versions
    • Fix a possible critical warning due to missing retval
    • Fix the endianness for the CRC check in bcm57xx
    • Lower the CURL version required to fix RHEL
    • Make sure the correct interface number is used for QMI
    • Mark more user-visible strings as translatable
    • Restrict loading component types of firmware
    • Validate ModemManager firmware update method combinations

    This release adds the following features:

    • Add a flag to indicate if packages are supported
    • Add a plugin for the Pinebook Pro laptop
    • Allow components to set the icon from the metadata
    • Switch from libsoup to libcurl for downloading data

    This release fixes the following bugs:

    • Fall back to FAT32 internal partitions for detecting ESP
    • Fix detection of ColorHug version on older firmware versions
    • Fix reading BCM57XX vendor and device ids from firmware
    • Fix replugging the MSP430 device
    • Fix sync method when called from threads without a context
    • Ignore an invalid vendor-id when adding releases for display
    • Improve synaptics-mst reliability when writing data
    • Install modules-load configs in the correct directory
    • Notify the service manager when idle-quitting
    • Only download the remote metadata as required
    • Remove HSI update and attestation suffixes
    • Restore recognizing GPG and PKCS7 signature types in libfwupd
    • Set the SMBIOS chassis type to portable if a DT battery exists

    This release adds the following features:

    • Include the amount of NVRAM size in use in the LVFS failure report

    This release fixes the following bugs:

    • Delete unused EFI variables when deploying firmware
    • Fix probe warning for the Logitech Unifying device
    • Make bcm57xx hotplug more reliable
    • Recognize authorized thunderbolt value of 2
    • Remove the duplicate parent-child data in FwupdDevice and FuDevice
    • Show a less scary fwupdate output for devices without info
    • Show a link to discover more information about a specific plugin failure
    • Use a different Device ID for the OptionROM devices
    • Use UDisks to find out if swap devices are encrypted

    This release adds the following features:

    • Add a compatible re-implementation of the rhboot dbxtool
    • Add async versions of the library for GUI tools
    • Add commands for interacting with the ESP to fwupdtool
    • Add firmware-extract subcommand to fwupdtool
    • Add FwupdPlugin so we can convey enumerated system errors to the end user
    • Add plugin for Goodix fingerprint sensors
    • Add plugin that can update the BCM5719 network adapter
    • Add plugin to update Elan Touchpads using HID
    • Add support for a delayed activation flow for Thunderbolt
    • Add support for ChromeOS Quiche and Gingerbread
    • Add support for Hyper hardware
    • Add support for the Host Security ID
    • Add support for ThunderBolt retimers
    • Add switch-branch command to fwupdtool and fwupdmgr
    • Allow blocking specific firmware releases by checksum
    • Allow constructing a firmware with multiple images
    • Allow firmware to require specific features from front-end clients
    • Allow updating the dbx using the LVFS, validating it is safe to apply
    • Include the HSI results and attributes in the uploaded report
    • Support loading DMI data from DT systems
    • Support LVFS::UpdateImage for GUI clients

    This release fixes the following bugs:

    • Allow compiling the daemon without polkit support
    • Always look at all TPM eventlog supported algorithms
    • Change all instances of master/slave to initiator/target
    • Correctly order devices when using logical parents
    • Do not dedupe NVMe or VLI PD devices
    • Do not expose the VLI shared-SPI devices on the USB2 recovery device
    • Do not fix up the version on post-update mismatch
    • Download the metadata first when using 'fwupdtool refresh'
    • Drop efivar dependency
    • Drop support for ThunderBolt force power due to hardware issues
    • Fix setting BootNext correctly when multiple updates are scheduled
    • Fix the topology of the audio device on the Lenovo TR dock
    • Make return code different for get-updates with no updates
    • Make specific authorizations also imply others
    • Make TPM support more optional
    • Parse the HEX version before comparing for equality
    • Prevent dell-dock updates to occur via synaptics-mst plugin
    • Record the UEFI failure in more cases
    • Retry the HID SetReport to fix flashing the TB3 dock
    • Show an error when a plugin is missing dependencies
    • Use libxmlb bound parameters to speed up the device verification
    • Use pkttyagent to request user passwords if running without GUI
    • Use the JCat file to select the metadata file

    This release adds the following features:

    • Allow adding a device 'proxy' device that can do actions on it
    • Allow specifying the device on the command line by GUID

    This release fixes the following bugs:

    • Add a device quirk that forces an explicit device-id match
    • Allow a device to set the logical or physical ID during ->setup()
    • Correctly format firmware version of Dynabook X30 and X40
    • Do not show safe mode errors for USB4 host controllers
    • Do not show the USB 2 VLI recovery devices for USB 3 hubs
    • Fix the correct DeviceID set by GetDetails
    • Make the EP963X plugin actually work on real hardware
    • Make the tss2-esys dep conditional for RHEL 8
    • Only update the FW2 partition of the ThinkPad USB-C Dock Gen2
    • Prefer to update the child device first if the order is unspecified
    • Refresh device name and format before setting supported flag
    • Reset the progressbar time estimate if the percentage is invalid
    • Set the CCGX device name and summary from quirk files
    • Wait for the cxaudio device to reboot after writing firmware

    This release adds the following features:

    • Add 'firmware-convert' subcommand to fwupdtool
    • Add fu_device_retry() API
    • Add FuHidDevice abstraction
    • Add plugin for CPU microcode
    • Add plugin for Cypress CCGX hardware
    • Add plugin for EP963x hardware
    • Add 'reinstall' command to fu-tool
    • Allow server metadata to set the device name and version format
    • Export the device state as part of the D-Bus interface
    • Export the release creation time and urgency
    • Introduce a new VersionFormat of 'hex'
    • Use Jcat files in firmware archives and for metadata

    This release fixes the following bugs:

    • Actually reload the DFU device after upgrade has completed
    • Add a lot of missing metadata about wacom-usb devices
    • Add a way to set the device timeout from a quirk
    • Add STM32F745 DfuSe version quirk
    • Allow waiting for the parent device when replugging
    • Always check for 'PLAIN' when doing vercmp() operations
    • Apply version format to releases and devices at same time
    • Check the firmware requirements before adding 'SUPPORTED'
    • Correctly attach VL103 after a firmware update
    • Do not allow devices that have no vendor ID to be 'UPDATABLE'
    • Do not conditionalize attach() and detach() on 'IS_BOOTLOADER'
    • Do not use shim for non-secure boot configurations
    • Fix a crash when removing device parents
    • Fix a difficult-to-trigger daemon hang when replugging devices
    • Fix a runtime error when detaching MSP430
    • Fix CounterpartGuid when there is more than one supported device
    • Fix reporting Synaptics cxaudio version number
    • Load the signature to get the aliased CDN-safe version of the metadata
    • Never add USB hub devices that are not upgradable
    • Only auto-add counterpart GUIDs when required
    • Parse the CSR firmware as a DFU file
    • Set the protocol when updating logitech HID++ devices
    • When TPM PCR0 measurements fail, query if secure boot is available and enabled

    This release adds the following features:

    • Added completion script for fish shell
    • Inihbit all power management actions using logind when updating

    This release fixes the following bugs:

    • Always check for PLAIN when doing vercmp() operations
    • Always return AppStream markup for remote agreements
    • Apply UEFI capsule update even with single valid capsule
    • Check the device protocol before de-duping devices
    • Copy the version and format from donor device in get-details
    • Correctly append the release to devices in `fwupdtool get-details`
    • Decrease minimum battery requirement to 10%
    • Discard the reason upgrades aren't available
    • Do not fail loading in /etc/machine-id is not available
    • Fix a critical warning when installing some firmware
    • For the `get-details` command make sure to always show devices
    • Set the MSP430 version format to pair
    • Switch off the ATA verbose logging by default
    • Use unknown for version format by default on get-details

    This release adds the following features:

    • Add an extra instance ID to disambiguate USB hubs
    • Add a plugin to update PD controllers by Fresco Logic
    • Replay the TPM event log to get the PCRx values

    This release fixes the following bugs:

    • Fix updating Synaptics MST devics with no PCI parent
    • Correctly reset VL100 PD devices
    • Do not rewrite BootOrder in the EFI helper
    • Do not use vercmp when the device version format is plain
    • Fix firmware regression in the EFI capsule helper
    • Ignore Unifying detach failures
    • Make the cxaudio version match that of the existing Windows tools
    • Set up more parent devices for various Lenovo USB hubs
    • Support the new gnuefi file locations
    • Use the correct command to get the VLI device firmware version

    This release adds the following features:

    • Add 'get-remotes' and 'refresh' to fwupdtool
    • Add support for standalone VIA PD devices
    • Allow applying all releases to get to a target version
    • Discourage command line metadata refreshes more than once per day
    • Generate a win32 setup binary
    • Get the list of updates in JSON format from fwupdagent
    • Move MOTD population into the daemon
    • Shut down automatically when there is system memory pressure

    This release fixes the following bugs:

    • Correctly delete UEFI variables
    • Correctly import PKCS-7 remote metadata
    • Disable the battery percentage checks if UPower is unavailable
    • Do not always get the vendor ID for udev devices using the parent
    • Fix display of UTF-8 characters on Windows
    • Show the device parent if there is an interesting child
    • Use a different protocol ID for VIA i2c devices
    • Use the correct timeout for Logitech IO channel writes

    This release adds the following features:

    • Add a new plugin that can parse the TPM event log
    • Add a new plugin that exposes the TPM device firmware version
    • Allow building on Windows with MinGW
    • Enforce that device protocol matches the metadata value
    • Export the device protocol and raw device version to the client --verbose output

    This release fixes the following bugs:

    • Add a dell-bios version format to match what is shown on the vendor website
    • Allow incremental version major and minor number for Synaptics Prometheus devices
    • Clarify error messages when no upgrades are available
    • Correct the default prompt for reboot/shutdown
    • Do not expose bootloader version errors to users
    • Fix the quirk for the legacy VIA 813 usbhub chip
    • Hardcode the vendor ID for Dell dock hardware
    • Only check the vendor ID if the device has one set
    • Return exit status success if there is no firmware to be updated
    • Set the correct vendor eMMC ID prefix
    • Use the baseboard vendor as the superio vendor ID
    • Use the BIOS vendor as the coreboot and flashrom vendor ID

    This release adds the following features:

    • Convert libfwupdprivate to a shared library libfwupdplugin
    • Create a REV_00 instance ID as this may be what the vendor needs to target

    This release fixes the following bugs:

    • Improve coreboot version detection
    • Invert default behavior to be safer for reboot and shutdown prompts
    • Reload the Synaptics prometheus device version after update
    • Use the correct unlocker when using GRWLock
    • Whitelist VIA USB hub PD and I²C devices

    This release adds the following features:

    • Add a new property Interactive to the daemon
    • Add a new script for installing a Dell BIOS from an EXE file
    • Add support for Foxconn T77W968 and DW5821e eSIM
    • Add support for matching firmware requirements on device parents
    • Add support for writing VIA PD and I2C devices
    • Add versions formats for the Microsoft Surface devices

    This release fixes the following bugs:

    • Allows confined snaps to activate fwupd via D-Bus
    • Correct Wacom panel HWID support
    • Don't assume all udev devices have device_file
    • Dynamically determine release version
    • Fall back to `ID_LIKE` when the path for `ID` doesn't exist
    • Fix a fastboot regression when updating modem firmware
    • Fix regression when coldplugging superio devices
    • Fix the linking of the UEFI update binary
    • Fix the vendor id of hidraw devices
    • Make loading USB device strings non-fatal
    • Reject invalid Synaptics MST chip IDs
    • Skip cleanup after device is done updating if required

    This release adds the following features:

    • Add a plugin for systems running coreboot
    • Add a plugin to update eMMC devices
    • Add a plugin to update Synaptics RMI4 devices
    • Add a plugin to update VIA USB hub hardware
    • Add some success messages when CLI tasks have completed
    • Add support for automatically uploading reports
    • Add support for `fwupdmgr reinstall`
    • Allow fwupdtool to dump details of common firmware formats
    • Use XMLb to query quirks to reduce the RSS when running

    This release fixes the following bugs:

    • Add several quirks for Realtek webcams
    • Add support for the 8bitdo SN30Pro+
    • Add support for the ThinkPad USB-C Dock Gen2 audio device
    • Always report the update-error correctly for multiple updates
    • Create a unique GUID for the Thunderbolt controller path
    • Fix a regression for Wacom EMR devices
    • Move the Jabra-specific detach out into its own plugin
    • Recognize new 'generation' Thunderbolt sysfs attribute for USB4
    • Reduce more boilerplate in plugins, modernizing where required
    • Remove unused DFU functionality
    • Rework ESP path detection and lifecycle to auto-unmount when required
    • Show a useful error for Logitech devices that cannot self-reset
    • Use correct method for stopping systemd units
    • Use device safety flags to show prompts before installing updates
    • Use `genpeimg` to mark ASLR and DP/NX on EFI binary
    • Use will-disappear flag for 8bitdo SF30/SN30 controllers

    This release adds the following features:

    • Add a plugin to detach the Thelio IO board
    • Add a plugin to update Conexant audio devices
    • Support issues in AppStream metadata

    This release fixes the following bugs:

    • Align the key values to the text width not the number of bytes
    • Display more helpful historical device information
    • Do not ask the user to upload a report if ReportURI is not set
    • Do not crash when starting tpm2-abrmd
    • Ensure HID++ v2.0 peripheral devices get added
    • Fall back to /var/lib/dbus/machine-id when required
    • Include all GUIDs when uploading a report
    • Move D-Bus conf file to datadir/dbus-1/system.d
    • Update device_modified in sql database during updates

    This release adds the following features:

    • Add support for the Minnowboard Turbot
    • Add support for the SoloKey Secure
    • Add support for thunderbolt kernel safety checks
    • Add support to integrate into the motd
    • Allow filtering devices when using the command line tools
    • Allow setting custom flags when using fwupdate
    • Allow specifying a firmware GUID to check any version exists
    • Include the kernel release as a runtime version
    • Print devices, remotes, releases using a tree
    • Publish docs to fwupd.github.io using CircleCI

    This release fixes the following bugs:

    • Add aliases for get-upgrades and upgrade
    • Allow disabling SSL strict mode for broken corporate proxies
    • Be more accepting when trying to recover a failed database migration
    • Do not segfault when trying to quit the downgrade selection
    • Fix a possible crash when stopping the fwupd service
    • Fix incomplete hex file parsing in unifying plugin
    • Fix thunderbolt logic to work properly with ICL thunderbolt controller
    • Never show AppStream markup on the console
    • Never use memcpy() in a possibly unsafe way
    • Only write the new UEFI device path if different than before
    • Partially rewrite the Synapticsmst plugin to support more hardware
    • Reload metadata store when configuration changes
    • Use environment variables for systemd managed directories
    • Use tpm2-tss library to read PCR values

    This release adds the following features:

    • Add a new experimental plugin that supports libflashrom
    • Add a specific error code for the low battery case
    • Add support for 8bitdo USB Retro Receiver
    • Export new API to build objects from GVariant blobs
    • Show a warning when running in UEFI legacy mode
    • Support a UEFI quirk to disable the use of the UX capsule

    This release fixes the following bugs:

    • Fix installing synaptics-prometheus config updates
    • Fix the supported list of Wacom tablets
    • Never set an empty device name
    • Prompt for reboot when unlocking on the command line if applicable
    • Show devices with an UpdateError in get-devices output
    • Support empty proxy server strings
    • Try harder to find duplicate UEFI boot entries

    This release adds the following features:

    • Add support for Synaptics Prometheus fingerprint readers
    • Check if VersionFormat is ambiguous when adding devices
    • Check the daemon version is at least the client version
    • Export the version-format used by devices to clients
    • Set the version format for more device types

    This release fixes the following bugs:

    • Allow using --force to trigger a duplicate offline update
    • Be smarter about existing installed fwupd when using standalone-installer
    • Correctly identify DFU firmware that starts at offset zero
    • Display the remote warning on the console in an easy-to-read way
    • Fix a libasan failure when reading a UEFI variable
    • Never guess the version format from the version string
    • Only use class-based instance IDs for quirk matching
    • Prompt the user to shutdown if required when installing by ID
    • Reset the forced version during DFU attach and detach

    This release adds the following features:

    • Allow the fwupdmgr tool to modify the daemon config

    This release fixes the following bugs:

    • Correctly parse DFU interfaces with extra vendor-specific data
    • Do not report transient or invalid system failures
    • Fix problems with the version format checking for some updates

    This release adds the following features:

    • Add a component categories to express the firmware type
    • Add support for 8BitDo M30
    • Add support for the not-child extension from Logitech
    • Shut down the daemon if the on-disk binary is replaced

    This release fixes the following bugs:

    • Blocklist the synapticsmst plugin when using amdgpu
    • Correct ATA activation functionality to work for all vendors
    • Implement QMI PDC active config selection for modems
    • Make an error message clearer when there are no updates available
    • Match the old or new version number when setting NEEDS_REBOOT
    • More carefully check the output from tpm2_pcrlist
    • Recreate the history database if migration failed
    • Require AC power when updating Thunderbolt devices
    • Require --force to install a release with a different version format
    • Save history from firmware installed with fwupdtool

    This release adds the following features:

    • Add a plugin to support modem hardware
    • Add support for delayed activation of docks and ATA devices
    • Add support for reading the SuperIO device checksum and writing to e-flash
    • Add the fwupdagent binary for use in shell scripts
    • Allow restricting firmware updates for enterprise use
    • Allow signing the fwupd report with a client certificate
    • Use Plymouth when updating offline firmware

    This release fixes the following bugs:

    • Allow forcing an offline-only update on a live system using --force
    • Allow running offline updates when in system-update.target
    • Ask to reboot after scheduling an offline firmware update
    • Correctly check the new version for devices that replug
    • Do not fail to start the daemon if tpm2_pcrlist hangs
    • Do not fail when scheduling more than one update to be run offline
    • Do not let failing to find DBus prevent fwuptool from starting
    • Do not schedule an update on battery power if it requires an external power source
    • Include all device checksums in the LVFS report
    • Rename the shimx64.efi binary for known broken firmware
    • Upload the UPDATE_INFO entry for the UEFI UX capsule

    This release adds the following features:

    • Allow a device to be updated using more than one plugin
    • Report the DeviceInstanceIDs from fwupdmgr when run as root

    This release fixes the following bugs:

    • Add an extra check for Dell NVMe drives to avoid false positives
    • Call composite prepare and cleanup using fwupdtool
    • Correct handling of CAB files with nested directories
    • Detect and special case Dell ATA hardware
    • Do not fail fwupdtool if dbus is unavailable
    • Do not unconditionally enable Werror for the EFI binary
    • Fill holes when reading SREC files
    • Filter the last supported payloads of certain Dell docks
    • Fix flashing failure with latest Intuos Pro tablet
    • Fix potential segfault when applying UEFI updates
    • Fix unifying regression when recovering from failed flash

    This release adds the following features:

    • Add a directory remote that generates metadata
    • Add a new remote type "directory"
    • Add a plugin to update Wacom embedded EMR and AES panels
    • Add a plugin to upgrade firmware on ATA-ATAPI hardware
    • Add a quirk to use the legacy bootmgr description
    • Add flag to support manually aligning the NVMe firmware to the FWUG value
    • Add SuperIO IT89xx device support
    • Add support for Dell dock passive flow
    • Add 'update' and 'get-updates' commands to fwupdtool
    • Allow Dell dock flashing Thunderbolt over I2C
    • Check the battery percentage before flashing
    • Show a per-release source and details URL
    • Show a `UpdateMessage` and display it in tools

    This release fixes the following bugs:

    • Add the needs-shutdown quirk to Phison NVMe drives
    • Correct Nitrokey Storage invalid firmware version read
    • Do not check the BGRT status before uploading a UX capsule
    • Do the UEFI UX checksum calculation in fwupd
    • Fix flashing various Jabra devices
    • Fix the parser to support extended segment addresses
    • Flash the fastboot partition after downloading the file
    • Show a console warning if loading an out-of-tree plugin
    • Support FGUID to get the SKU GUID for NVMe hardware

    This release fixes the following bug:

    • Correctly migrate the history database

    This release adds the following features:

    • Add support for devices that support fastboot
    • Add more standard USB identifier GUIDs
    • Add new API to get the release protocol from the metadata
    • Add the PCR0 value as the device checksum for system firmware
    • Include the device firmware checksum and update protocol in the report

    This release fixes the following bugs:

    • Add Dell TB18DC to the supported devices list
    • Allow replacing the last byte in the image when using 'dfu-tool replace-data'
    • Append the UEFI capsule header in userspace rather than in the loader
    • Check the device checksum as well as the content checksum during verify
    • Correctly parse format the version numbers correctly using old metadata
    • Fix a crash if AMT returns an empty response
    • Fix a regression when doing GetReleases on unsupported hardware
    • Fix the 8bitdo version number if the daemon locale is not C.UTF-8
    • Remove the Wacom DTH generation hardware from the whitelist
    • Sanitize the version if the version format has been specified

    This release adds the following features:

    • Add per-release install duration values
    • Shut down the daemon after 2h of inactivity when possible

    This release fixes the following bugs:

    • Fix a use-after-free when using --immediate-exit
    • Fix flashing the 8bitdo SF30
    • Fix showing the custom remote agreements
    • Include the os-release information in the release metadata
    • Speed up startup by loading less thunderbolt firmware
    • Speed up startup by using a silo index for GUID queries
    • Use less memory and fragment the heap less when starting

    This release adds the following features:

    • Add a plugin for an upcoming Dell USB-C dock
    • Add a standalone installer creation script
    • Add support for devices to show an estimated flash time
    • Add support for some new Realtek USB devices
    • Allow firmware files to depend on versions from other devices
    • Allow setting the version format from a quirk entry
    • Port from libappstream-glib to libxmlb for a large reduction in RSS
    • Stop any running daemon over dbus when using fu-tool
    • Support the Intel ME version format

    This release fixes the following bugs:

    • Add version format quirks for several Lenovo machines
    • Adjust panamera ESM update routine for some reported issues
    • Adjust synapticsmst EVB board handling
    • Check the amount of free space on the ESP
    • Don't show devices pending a reboot in GetUpgrades
    • Ensure that parent ID is created before creating quirked children
    • Optionally wait for replug before updating a device
    • Set the full AMT device version including the BuildNum
    • Sort the firmware sack by component priority
    • Stop showing errors when no Dell dock plugged in
    • Stop showing the current release during updates in fwupdmgr
    • Update all sub-devices for a composite update
    • Use HTTPS_PROXY if set

    This release adds the following features:

    • Add a new device flag 'ignore-validation' that will override checks
    • Add a new plugin to enumerate EC firmware
    • Add a new plugin to update NVMe hardware
    • Add a plugin for updating using the flashrom command line tool
    • Allow the device list to take care of waiting for the device replug
    • Allow updating just one specific device from the command line
    • Allow upgrades using a self-signed fwupd.efi binary
    • Download firmware if the user specifies a URI
    • Include serial number in daemon device output when trusted
    • Notify all plugins of device removals through a new vfunc
    • Use boltd force power API if available

    This release fixes the following bugs:

    • Add an install hook for classic snap
    • Allow forcing installation even if no AC power is applied
    • Allow using --force to ignore version_lowest
    • Always use the same HardwareIDs as Windows
    • Check the device state before assuming a fake DFU runtime
    • Copy over parent GUIDs from other plugin donors
    • Detect location of python3 interpreter
    • Do not add udev devices after a small delay
    • Don't fail to run if compiled without GPG/PKCS7
    • Fix a segfault in fwupdtool caused by cleanup of USB plugins
    • Implement the systemd recommendations for offline updates
    • Improve performance when reading keys from the quirk database
    • Remove children of devices when the parent is removed
    • Rewrite synapticsmst to use modern error handling
    • Rewrite the unifying plugin to use the new daemon-provided functionality
    • Show a time estimate on the progressbar after an update has started

    This release adds the following features:

    • Add support for the Synaptics Panamera hardware
    • Add validation for Alpine and Titan Ridge
    • Improve the Redfish plugin to actually work with real hardware

    This release fixes the following bugs:

    • Allow different plugins to add the same device
    • Allow flashing unifying devices in recovery mode
    • Allow running synapticsmst on non-Dell hardware
    • Check the ESP for sanity at startup
    • Do not hold hidraw devices open forever
    • Don't override _FORTIFY_SOURCE when building the EFI binary
    • Don't show passwords in fwupdmgr
    • Fix a potential segfault in smbios data parsing
    • Fix encoding the GUID into the capsule EFI variable
    • Fix various bugs when reading the thunderbolt version number
    • Reboot synapticsmst devices at the end of flash cycle
    • Show status messages when the daemon is initializing
    • Show the correct title when updating devices
    • Show the reasons that plugins are not run on the CLI
    • Use localedir in po/make-images

    This release adds the following features:

    • Add a initial Redfish support
    • Add a tool to mimic the original fwupdate CLI interface
    • Allow devices to assign a plugin from the quirk subsystem
    • Change the quirk file structure to be more efficient
    • Merge fwupdate functionality into fwupd
    • Run a plugin vfunc before and after all the composite devices are updated
    • Support more Wacom tablets

    This release fixes the following bugs:

    • Add release information for locked devices
    • Allow building with older meson
    • Detect the EFI system partition location at runtime
    • Do not use 8bitdo bootloader commands after a successful flash
    • Enable accessing downloaded files in flatpak and snap
    • Fix a potential buffer overflow when applying a DFU patch
    • Fix downgrading older releases to devices
    • Fix flashing devices that require a manual replug
    • Fix several small memory leaks in various places
    • Fix the retrieval of Redfish version
    • Fix unifying failure to detach when using a slow host controller
    • Set the Wacom device status when erasing and writing firmware
    • Show errors in the CLI if unable to access directory
    • Use the parent device name for Wacom sub-modules

    This release adds the following features:

    • Add a plugin to update some future Wacom tablets
    • Add 'fwupdmgr get-topology' to show logical device tree
    • Add support for creating a flatpak
    • Add support for creating a snap
    • Add support for Motorola S-record files
    • Add the Linux Foundation public GPG keys for firmware and metadata
    • Show a translated warning when the server is limiting downloads

    This release fixes the following bugs:

    • Add a firmware diagnostic tool called fwupdtool
    • Adjust all licensing to LGPL 2.1+
    • Allow installing more than one firmware using 'fwupdmgr install'
    • Allow specifying hwids with OR relationships
    • Do not call fu_plugin_init() on blacklisted plugins
    • Do not require libcolorhug to build
    • Fix a crash in libfwupd where no device ID is set
    • Fix a potential DoS in libdfu by limiting holes to 1MiB
    • Fix a segfault that sometimes occurs during cleanup of USB plugins
    • Fix Hardware-ID{0,1,2,12} compatibility with Microsoft
    • Hide devices that aren't updatable by default in fwupdmgr
    • Search all UEFI GUIDs when matching hardware
    • Stop matching Nintendo Switch Pro in the 8bitdo plugin

    This release adds the following features:

    • Add enable-remote and disable-remote commands to fwupdmgr
    • Add fu_plugin_add_compile_version() for libraries to use
    • Allow requiring specific versions of libraries for firmware updates
    • If no remotes are enabled try to enable the LVFS
    • Show a warning with interactive prompt when enabling a remote

    This release fixes the following bugs:

    • Check that EFI system partition is mounted before update
    • Disable synapticsmst remote control on failure
    • Don't recoldplug thunderbolt to fix a flashing failure
    • Fix SQL error when running 'fwupdmgr clear-offline'
    • Improve the update report message
    • Only enumerate Dell Docks if the type is known
    • Only run certtool if a new enough gnutls is present
    • Prevent a client crash if the daemon somehow sends invalid data
    • Reboot after scheduling using logind not systemd
    • Use the right encoding for the label in make-images

    This release adds the following features:

    • Add bash completion for fwupdmgr
    • Add support for newest Thunderbolt chips
    • Allow all functions that take device arguments to be prompted
    • Allow devices to use the runtime version when in bootloader mode
    • Allow overriding ESP mount point via conf file
    • Delete any old fwupdate capsules and efivars when launching fwupd
    • Generate Vala bindings

    This release fixes the following bugs:

    • Allow ctrl-d out of the prompt for devices
    • Allow to create package out of provided binary
    • Correct handling of unknown Thunderbolt devices
    • Correctly detect new remotes that are manually copied
    • Fix a crash related to when passing device to downgrade in CLI
    • Fix running the self tests when no fwupd is installed
    • Fix Unifying signature writing and parsing for Texas bootloader
    • Only send success and failure reports to the server
    • Use a CNAME to redirect to the correct CDN for metadata
    • Use a longer timeout when powering back the Thunderbolt device

    This release adds the following features:

    • Offer to reboot when processing an offline update
    • Report the efivar, libsmbios and fwupdate library versions
    • Report Thunderbolt safe mode and SecureBoot status
    • Show the user a URL when they report a known problem
    • Support split cabinet archives as produced by Windows Update

    This release fixes the following bugs:

    • Be more careful deleting and modifying device history
    • Clarify which devices don't have upgrades
    • Ensure the Thunderbolt version is xx.yy
    • Fix a daemon warning when using fwupdmgr get-results
    • Fix crash with MST flashing
    • Fix DFU detach with newer releases of libusb
    • Include the device VID and PID when generating the device-id
    • Set the RemoteId when using GetDetails
    • Stop matching 8bitdo DS4 controller VID/PID
    • Use help2man for dfu-tool and drop docbook dependencies
    • Use ngettext for any strings with plurals
    • Use the default value if ArchiveSizeMax is unspecified

    This release adds the following features:

    • Add D-Bus methods to get and modify the history information
    • Allow the user to share firmware update success or failure
    • Ask the user to refresh metadata when it is very old
    • Store firmware update success and failure to a local database

    This release fixes the following bugs:

    • Add a device name for locked UEFI devices
    • Allow each plugin to opt-in to the recoldplug action
    • Fix firmware downloading using gnome-software
    • Fix UX capsule reference to the one specified in efivar
    • Never add two devices to the daemon with the same ID
    • Rescan supported flags when refreshing metadata

    This release adds the following features:

    • Add a new plugin to add support for CSR 'Driverless DFU'
    • Add initial SF30/SN30 Pro support
    • Support AppStream metadata with relative <location> URLs

    This release fixes the following bugs:

    • Add more metadata to the user-agent string
    • Block owned Dell TPM updates
    • Choose the correct component from provides matches using requirements
    • Do not try to parse huge compressed archive files
    • Fix a double-free bug in the Udev code
    • Handle Thunderbolt 'native' mode
    • Use the new functionality in libgcab >= 1.0 to avoid writing temp files

    This release adds the following features:

    • Add a plugin for the Nitrokey Storage device
    • Add support for the original AVR DFU protocol
    • Allow different plugins to claim the same device
    • Allow quirks to set common USB properties
    • Move a common plugin functionality out to a new shared object
    • Optionally delay the device removal for better replugging
    • Set environment variables to allow easy per-plugin debugging
    • Use a SHA1 hash for the internal DeviceID

    This release fixes the following bugs:

    • Add quirk for AT32UC3B1256 as used in the RubberDucky
    • Disable the dell plugin if libsmbios fails
    • Don't register for USB UDev events to later ignore them
    • Fix a possible buffer overflow when debugging ebitdo devices
    • Fix critical warning when more than one remote fails to load
    • Fix DFU attaching AVR32 devices like the XMEGA
    • Ignore useless Thunderbolt device types
    • Refactor ColorHug into a much more modern plugin
    • Release the Steelseries interface if getting the version failed
    • Remove autoconf-isms from the meson configure options
    • Show a nicer error message if the requirement fails
    • Sort the output of GetUpgrades correctly

    This release adds the following features:

    • Add support for HWID requirements
    • Add support for programming various AVR32 and XMEGA parts using DFU
    • Add the various DFU quirks for the Jabra Speak devices
    • Allow specifying the output file type for 'dfu-tool read'
    • Move the database of supported devices out into runtime loaded files
    • Support the IHEX record type 0x05
    • Use help2man to generate the man page at build time
    • Use the new quirk infrastructure for version numbers

    This release fixes the following bugs:

    • Catch invalid Dell dock component requests
    • Correctly output Intel HEX files with > 16bit offset addresses
    • Do not try to verify the element write if upload is unsupported
    • Fix a double-unref when updating any 8BitDo device
    • Fix crash when enumerating with Dell dock connected but with no UEFI
    • Fix uploading large firmware files over DFU
    • Format the BCD USB revision numbers correctly
    • Guess the DFU transfer size if it is not specified
    • Include the reset timeout as wValue to fix some DFU bootloaders
    • Make the error message clearer when sans fonts are missing
    • Support devices with truncated DFU interface data
    • Use the correct remote-specified username and passord when using fwupdmgr
    • Use the correct wDetachTimeOut when writing DFU firmware
    • Verify devices with legacy VIDs are actually 8BitDo controllers

    This release breaks API and ABI to remove deprecated symbols!

    This release adds the following features:

    • Add a human-readable title for each remote
    • Add a method to return a list of upgrades for a specific device
    • Add an 'Summary' and 'Icons' properties to each device
    • Add FuDeviceLocker to simplify device open/close lifecycles
    • Add functionality to blocklist Dell HW with problems
    • Add fu_plugin_check_supported()
    • Add fwupd_remote_get_checksum() to use in client programs
    • Add ModifyRemote as an easy way to enable and disable remotes
    • Add the plugin documentation to the main gtk-doc
    • Allow plugins to depend on each other
    • Disable the fallback USB plugin
    • Parse the SMBIOS v2 and v3 DMI tables directly
    • Support uploading the UEFI firmware splash image
    • Use the intel-wmi-thunderbolt kernel module to force power

    This release fixes the following bugs:

    • Only run SMI to toggle host MST GPIO on Dell systems with host MST
    • Disable unifying support if no CONFIG_HIDRAW support
    • Do not auto-open all USB devices at startup
    • Do not fail to load the daemon if cached metadata is invalid
    • Do not use system-specific information for UEFI PCI devices
    • Fix a crash when using fu_plugin_device_add_delay()
    • Fix the libdfu self test failure on s390 and ppc64
    • Fix various printing issues with the progressbar
    • Generate the LD script from the GObject introspection data
    • Never fallback to an offline update from client code
    • Only set the Dell coldplug delay when we know we need it
    • Prefer to use HWIDs to get DMI keys and DE table

    This release adds the following features:

    • Add a configure switch for the LVFS remotes
    • Add a FirmwareBaseURI parameter to the remote config
    • Add a firmware builder that uses bubblewrap
    • Add a python script to create fwupd compatible cab files from Microsoft .exe files
    • Add a thunderbolt plugin for new kernel interface
    • Allow plugins to get DMI data from the hardware in a safe way
    • Allow plugins to set metadata on devices created by other plugins
    • Optionally install the LVFS PKCS7 root certificate
    • Optionally use GnuTLS to verify PKCS7 certificates

    This release fixes the following bugs:

    • Add back options for HAVE_SYNAPTICS and HAVE_THUNDERBOLT
    • Allow configuring systemd and udev directories
    • Enable C99 support in meson.build
    • Fix an incomplete cipher when using XTEA on data not in 4 byte chunks
    • Fix minor const-correctness issues
    • Implement thunderbolt image validation
    • Remove the confusing ALLOW_OFFLINE and ALLOW_ONLINE flags
    • Show a bouncing progress bar if the percentage remains at zero
    • Use a hwid to match supported systems for synapticsmst
    • Use the new bootloader PIDs for Unifying pico receivers
    • When thunderbolt is in safe mode on a Dell recover using SMBIOS

    This release adds the following features:

    • Add DfuPatch to support forward-only firmware patching
    • Add --version option to fwupdmgr
    • Display all errors recorded by efi_error tracing
    • Make building introspection optional
    • Support embedded devices with local firmware metadata

    This release fixes the following bugs:

    • Check all the device GUIDs against the blocklist when added
    • Correct a memory leak in Dell plugin
    • Default to 'en' for UEFI capsule graphics
    • Don't log a warning when an unknown unifying report is parsed
    • Enable test suite via /etc/fwupd.conf
    • Fix a hang on 32 bit computers
    • Fix compilation of the policy on a variety of configurations
    • Fix UEFI crash when the product name is NULL
    • Make flashing ebitdo devices work with fu-ebitdo-tool
    • Make messages from installing capsules useful
    • Make sure the unifying percentage completion goes from 0% to 100%
    • Run the plugin coldplug methods in a predictable order
    • Test UEFI for kernel support during coldplug
    • Use new GUsb functionality to fix flashing Unifying devices

    This release adds the following features:

    • Add a get-remotes command to fwupdmgr
    • Add a plugin to get the version of the AMT ME interface
    • Add Arch Linux to CI
    • Add some installed tests flashing actual hardware
    • Allow flashing Unifying devices in bootloader modes
    • Allow ordering the metadata remotes

    This release fixes the following bugs:

    • Do not check the runtime if the DFU device is in bootloader mode
    • Do not unlock devices when doing VerifyUpdate
    • Filter by Unifying SwId when making HID++2.0 requests
    • Fix downgrades when version_lowest is set
    • Fix the self tests when running on PPC64 big endian
    • Move the remotes parsing from the client to the server
    • Split up the Unifying HID++2.0 and HID++1.0 functionality
    • Store the metadata files rather than merging to one store
    • Use a longer timeout for some Unifying operations
    • Use the UFY DeviceID prefix for Unifying devices

    This release adds the following features:

    • Add installed tests that use the daemon
    • Add the ability to restrict firmware to specific vendors
    • Enable Travis CI for Fedora and Debian
    • Export some more API for dealing with checksums
    • Generate a images for status messages during system firmware update
    • Show progress download when refreshing metadata

    This release fixes the following bugs:

    • Compile with newer versions of meson
    • Ensure that firmware provides are legal GUIDs
    • Fix a common crash when refreshing metadata
    • Use the correct type signature in the D-Bus introspection file

    This release adds the following features:

    • Add a 'downgrade' command to fwupdmgr
    • Add a 'get-releases' command to fwupdmgr
    • Add support for ConsoleKit2
    • Add support for Microsoft HardwareIDs
    • Allow downloading metadata from more than just the LVFS
    • Allow multiple checksums on devices and releases

    This release fixes the following bugs:

    • Allow to specify bindir
    • Correctly open Unifying devices with original factory firmware
    • Deprecate some of the old FwupdResult API
    • Do not copy the origin from the new metadata file
    • Do not expect a Unifying reply when issuing a REBOOT command
    • Do not re-download firmware that exists in the cache
    • Fix a problem when testing for a Dell system
    • Fix flashing new firmware to 8bitdo controllers
    • Increase minimum required AppStream-Glib version to 0.6.13
    • Make documentation and man pages optional
    • Make systemd dependency at least version 231
    • Only decompress the firmware after the signature check
    • Remove 'lib' prefix when looking for libraries
    • Return the remote ID when getting updates about hardware
    • Send the daemon the remote ID when sending firmware metadata

    This release adds the following feature:

    • Add support for Unifying DFU features

    This release fixes the following bugs:

    • Do not spew a critical warning when parsing an invalid URI
    • Ensure device is closed if did not complete setup
    • Ensure steelseries device is closed if it returns an invalid packet
    • Fix man page installation location
    • Ignore spaces in the Unifying version prefix
    • Set HAVE_POLKIT_0_114 when polkit is newer than 0.114

    This release adds the following features:

    • Add a config option to allow runtime disabling plugins by name
    • Add the Meson build system and remove autotools
    • Support signed Intel HEX files

    This release fixes the following bugs:

    • Add DFU quirk for OpenPICC and SIMtrace
    • Create directories in /var/cache as required
    • Refactor the unifying plugin now we know more about the hardware
    • Set the source origin when saving metadata
    • Support proxy servers in fwupdmgr
    • Use a 60 second timeout on all client downloads

    This release fixes the following bugs:

    • Adjust systemd confinement restrictions
    • Do not hardcode docbook2man path
    • Don't initialize libsmbios on unsupported systems
    • Fix a crash when enumerating devices on a Dell WLD15
    • Fix compiler warnings
    • Fix fwupdmgr timeout with missing pending database

    This release adds the following features:

    • Add a set of vfuncs that are run before and after a device update
    • Add Dell-specific functionality to allow other plugins turn on TBT/GPIO
    • Add support for Intel Thunderbolt devices
    • Add support for Logitech Unifying devices
    • Add support for Synaptics MST cascades hubs
    • Add support for the Altus-Metrum ChaosKey device
    • Add VerifyUpdate to update the device checksums server-side
    • Allow the metadata to match a version of fwupd and the existing fw version

    This release fixes the following bugs:

    • Add a new method for forcing a controller to flash mode
    • Always make sure we're getting a C99 compiler
    • Close USB devices before error returns
    • Don't read data from some DfuSe targets
    • Include all debug messages when run with --verbose
    • Return the pending UEFI update when not on AC power
    • Use a heuristic for the start address if the firmware has no DfuSe footer
    • Use more restrictive settings when running under systemd

    This release adds the following features:

    • Add a 'replace-data' command to dfu-tool
    • Use an animated progress bar when performing DFU operations

    This release fixes the following bugs:

    • Add quirks for HydraBus as it does not have a DFU runtime
    • Don't create the UEFI dummy device if the unlock will happen on next boot
    • Enable hardening flags on more binaries
    • Fix an assert when unlocking the dummy ESRT device
    • Fix writing firmware to devices using the ST reference bootloader
    • Match the Dell TB16 device
    • Re-get the quirks when the DfuDevice gets a new GUsbDevice
    • Show the nicely formatted target name for DfuSe devices
    • Verify devices support updating in mode they are called

    This release adds the following features:

    • Add dfu_firmware_add_symbol()
    • Allow the argument to 'dfu-tool set-release' be major.minor
    • Load the Altos USB descriptor from ELF files
    • Support writing the IHEX symbol table

    This release fixes the following bugs:

    • Add a fallback for older appstream-glib releases
    • Fix a possible crash when uploading firmware files using libdfu
    • Fix libfwupd self tests when a host-provided fwupd is not available
    • Show the human-readable version in the 'dfu-tool dump' output
    • Write the ELF files with the correct section type

    This release adds the following features:

    • Add a set-address and set-target-size commands to dfu-util
    • Add a small library for talking with 0bitdo hardware
    • Add Dell TPM and TB15/WD15 support via new Dell provider
    • Add FU_DEVICE_FLAG_NEEDS_BOOTLOADER
    • Add fwupd_client_get_status()
    • Add fwupd_result_get_unique_id()
    • Add initial ELF reading and writing support to libdfu
    • Add support for installing multiple devices from a CAB file
    • Allow providers to export percentage completion
    • Show a progress notification when installing firmware
    • Show the vendor flashing instructions when installing

    This release fixes the following bugs:

    • Add XPS 9250 to Dell TPM modeswitch blocklist
    • Allow blacklisting devices by their GUID
    • Conditionally enable all providers based upon installed
    • Display flashes left in results output when it gets low
    • Do not attempt to add DFU devices not in runtime mode
    • Do not use the deprecated GNOME_COMPILE_WARNINGS
    • Don't fail while checking versions or locked state
    • Embed fwupd version in generated documentation
    • Ensure the ID is set when getting local firmware details
    • Fix gtk-doc build when srcdir != builddir
    • Fix libdfu hang when parsing corrupt IHEX files
    • Ignore devices that do not add at least one GUID
    • In get-details output, display the blob filename
    • Save the unique ID in the pending database
    • Support the 'DEVO' cipher kind in libdfu
    • Switch to the Amazon S3 CDN for firmware metadata
    • Update fwupdmgr manpage for new commands and arguments
    • Use a private gnupg key store
    • Use the correct firmware when installing a composite device
    • Use the SHA1 hash of the local file data as the origin

    This release adds the following features:

    • Add a GetDetailsLocal() method to eventually replace GetDetails()
    • Add fu_device_get_alternate()
    • Allow devices to have multiple assigned GUIDs
    • Allow metainfo files to match only specific revisions of devices
    • Show the DFU protocol version in 'dfu-tool list'

    This release fixes the following bugs:

    • Enforce allowing providers to take away flash abilities
    • Only claim the DFU interface when required
    • Only return updatable devices from GetDevices()

    This release adds the following features:

    • Add a --force flag to override provider warnings
    • Add device-added, device-removed and device-changed signals
    • Add dfu_image_get_element_default()
    • Add for a new device field 'Flashes Left'
    • Add fwupd_client_connect()
    • Add the 'monitor' debugging command for fwupdmgr
    • Add the 'supported' flag to the FuDevice

    This release fixes the following bugs:

    • Add summary and name field for Rival SteelSeries
    • Fix a critical warning when restarting the daemon
    • Fix BE issues when reading and writing DFU files
    • Make the device display name nicer
    • Match the AppStream metadata after a device has been added
    • Remove non-interactive pinentry setting from fu-keyring
    • Return all update descriptions newer than the installed version
    • Set the device description when parsing local firmware files

    This release adds the following features:

    • Add a version plugin for SteelSeries hardware
    • Add FwupdClient and FwupdResult to libfwupd
    • Generate gtk-doc documentation for libfwupd
    • Return the device flags when getting firmware details
    • Support other checksum kinds

    This release fixes the following bugs:

    • Add Alienware to the version quirk table
    • Allow the test suite to run in %check
    • Do not return updates that require AC when on battery
    • Do not use /tmp for downloaded files
    • Test that GPG key import actually was successful

    This release adds the following features:

    • Add an unlock method for devices
    • Add a simple plugin infrastructure
    • Add ESRT enable method into UEFI provider
    • Install the hardcoded firmware AppStream file

    This release fixes the following bugs:

    • Correct the BCD version number for DFU 1.1
    • Do not use deprecated API from libappstream-glib
    • Ignore the DFU runtime on the DW1820A
    • Only read PCI OptionROM firmware when devices are manually unlocked
    • Require AC power before scheduling some types of firmware update
    • Show ignored DFU devices in dfu-util, but not in fwupd

    This release adds the following feature:

    • Add 'Created' and 'Modified' properties on managed devices

    This release fixes the following bugs:

    • Fix get-results for UEFI provider
    • Support vendor-specific UEFI version encodings

    This release fixes the following bugs:

    • Always persist ColorHug devices after replug
    • Do not misdetect different ColorHug devices
    • Only dump the profiling data when run with --verbose

    This release adds a new GObject library called libdfu and a command line client called dfu-tool. This is a low-level tool used to upgrade USB device firmware and can either be shipped in the same package as fwupd or split off as separate subpackages.

    This release adds the following feature:

    • Add support for automatically updating USB DFU-capable devices

    This release fixes the following bugs:

    • Emit the changed signal after doing an update
    • Export the AppStream ID when returning device results
    • Fix compile with --disable-shared
    • Use new API available in fwup 0.5
    • Use the same device identification string format as Microsoft

    This release fixes the following bugs:

    • Avoid seeking when reading the file magic during refresh
    • Do not assume that the compressed XML data will be NUL terminated
    • Use the correct user agent string for fwupdmgr

    This release adds the following features:

    • Add profiling data to debug slow startup times
    • Support cabinet archives files with more than one firmware

    This release fixes the following bugs:

    • Add the update description to the GetDetails results
    • Clear the in-memory firmware store only after parsing a valid XML file
    • Ensure D-Bus remote errors are registered at fwupdmgr startup
    • Fix verify-update to produce components with the correct provide values
    • Require appstream-glib 0.5.1
    • Show the dotted-decimal representation of the UEFI version number
    • When the version is from the 'FW' extension do not cache the device

    This release fixes the following bugs:

    • Fix the error message when no devices can be updated
    • Fix reading symlink to prevent crash with some compilers

    This release adds the following feature:

    • Raise the dep on GLib to support and use g_autoptr()

    This release fixes the following bugs:

    • Do not merge existing firmware metadata
    • Do not reboot if racing with the PackageKit offline update mechanism

    This release adds the following feature:

    • Remove fwsignd, we have the LVFS now

    This release fixes the following bugs:

    • Add application metadata when getting the updates list
    • Depend on appstream-glib >= 0.5.0
    • Don't apply firmware if something else is processing the update
    • Install fwupd into /usr/lib/$(triplet)/fwupd instead
    • Simplify the version properties on devices to avoid complexity
    • Update the offline update service to invoke right command
    • Use the new secure metadata URI

    For the device verification code to work correctly you need at least libappstream-glib 0.5.0 installed.

    This release adds the following features:

    • Add a Raspberry Pi firmware provider
    • Add a simple config file to store the correct LVFS download URI
    • Make parsing the option ROM runtime optional

    This release fixes the following bugs:

    • Allow fwupd to be autostarted by systemd
    • Allow no arguments to 'fwupdmgr verify-update' and use sane defaults
    • Devices with option ROM are always internal
    • Do not pre-convert the update description from AppStream XML
    • Fix validation of written firmware
    • Move the verification and metadata matching phase to the daemon
    • Sign the test binary with the correct key
    • Use the AppStream 0.9 firmware specification by default

    In this release we've moved the LVFS website to the fwupd project and made them work really well together. To update all the firmware on your system is now just a case of 'fwupdmgr refresh && fwupdmgr update'. We've also added verification of BIOS and PCI ROM firmware, which may be useful for forensics or to verify that system updates have been applied.

    This release adds the following features:

    • Actually parse the complete PCI option ROM
    • Add a 'fwupdmgr update' command to update all devices to latest versions
    • Add a simple signing server that operates on .cab files
    • Add a 'verify' command that verifies the cryptographic hash of device firmware
    • Allow clients to add new firmware metadata to the system cache
    • Move GetUpdates to the daemon
    • Move the LVFS website to the fwupd project

    This release fixes the following bugs:

    • Accept multiple files at one time when using fwupdmgr dump-rom
    • Automatically download metadata using fwupdmgr if required
    • Do not return NULL as a gboolean
    • Don't call efibootmgr after fwupdate
    • Fallback to offline install when calling the update argument
    • Fix Intel VBIOS detection on Dell hardware
    • Reload appstream data after refreshing
    • Use the new LVFS GPG key
    • Fix build: libgusb is required even without colorhug support

    This release adds the following features:

    • Get the firmware version from the device descriptors
    • Run the offline actions using systemd when required
    • Support OpenHardware devices using the fwupd vendor extensions

    This release fixes the following bugs:

    • Add an UNKNOWN status so we can return meaningful enum values
    • Coldplug the devices before acquiring the well known name

    This release adds the following features:

    • Add a 'get-updates' command to fwupdmgr
    • Add and document the offline-update lifecycle
    • Create a libfwupd shared library

    This release fixes the following bugs:

    • Create runtime directories if they do not exist
    • Do not crash when there are no devices to return

    fwupd is a simple daemon to allow session software to update firmware.

    fwupd-1.9.16/data/org.freedesktop.fwupd.plist000066400000000000000000000012051460375044200211530ustar00rootroot00000000000000 Label org.freedesktop.fwupd ProgramArguments @libexecdir@/fwupd/fwupd Sockets Listener SockPathName @dbus_socket_address@ SockPathMode 438 RunAtLoad KeepAlive fwupd-1.9.16/data/org.freedesktop.fwupd.png000066400000000000000000000432061460375044200206130ustar00rootroot00000000000000PNG  IHDR6 pHYs.#.#x?vtEXtSoftwarewww.inkscape.org<tEXtTitleAdwaita Icon Template?tEXtAuthorGNOME Design Team`v~RtEXtCopyrightCC Attribution-ShareAlike http://creativecommons.org/licenses/by-sa/4.0/Tb IDATxwtׁ.V4r$"sdV<ӌ<hkfg}{̆yoyDzhq%e+XHI95@Xu&h хnA~stlVhnRJeJY !""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@H!""] DD t1.t'~BʯI(RrPKGUų)L !cBʳ`~EO~?ȴ]Dw~7kRke0.ڵ\j'?y|ӀPdf%(F+9^^xJ !A), qii'U;+d3T/7v,W-hR%3;_@mvkuuaNR+F]J|CH=,L(WY/F۷mӹnmm9Q*..+tn[{;(556jN:|X,5_7lnypG 5:0@@vSg?9V>F]<"6a. ""҅BDD\} {2B4rB(N!ʧ>TeR=~5glW2w/󿺼OdVDzh~f| [!efs ǧ>JZB<{y_>?bQVt7L m|U F|J_ȴ@TؕY!ɟfV1>fMY lEă{ڽ{w 75'F(31/z~I^,Ta,$לBA,\Tj1a87(((܂^ T4ehlYgs:~E8|V|!g uVr :.:kF9k`4{I@sP˖黲7t [>qeQBx&ו-|'%! $p.ץ32=^"]j$ !!N >)MZTpb??i \/@(V ]'"A]!*> ]\lR>&DtBeO(š}ۿԢ[g?{`ADW)/<㋮)~Q??cq?\eK([Ʉq+pc6SUU|ADBQl5] F6NLYI~^oa<CUdaĸݝpcb ( M(+/]P[K`|g'?m{ēHuK\yߠhk|,U-gEV$_8fR|~|Q=݅k߈݋bRW_br"@[&So?X,݊BQl޾+W韦)ʶG|Pu`V3>OcU4_\'_;Ba[LvuKy=E}:uʖ~ CGxG< " OGOQa,W':@ d+ a千̸g}֐*eE~x9[3-39>ᓏ?͸j<rP&[aB0ZrS!"yh夐:Y" 2~8sʦ+"ONe\F@|&UɊ H#5MCWGjCD4oCn=M$_DXIQ8"hCD4R=Ù5:w3)324eϰ{qU+y eZfb—ex*)ՌC^ pfZ$]"`A9ʼeMÊŹ?UPU52$d4k%Z.*BDu WB*Ue^2@m(2@(1@H!""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@HBWj( .+v l63f#FH?Oj]# SQZTKZRqih9M T31@. PY]BfCuu3&sP$ǏCِH1*0ؾm{(:::Vt5`jB*XmlټׯGQQѬ珎㗱tعsg(1@rXa}Q?wIy*a0 !``ڠI `x}G(AQ ׭KՅx<1qZa`}$P5Њ OpNU\Zںڴ#zgZ & [lMFOO7ńr4759s>Xfl޼9ulpx=0s8hXZ IUzj,]ɔ>x8~86TTmxO$E)fǖ-[l2L_;1>|.;Dx$K"֭[/ᅬ~|a`4&E. 94a8 lNmm &F[bp$ ZNQ i>rȼ*+shxӟfb2:t=p;P\y]nd2/ـ= 7WUlǵ^H}g**–[yf;v o6lv k$|a ĥ.pԘw<OEG/>N+nI}XOO kXb.(@r1~?FN'*+*`0R*++o~ mgID`IC9J{R<8 , (//6mBUU=,,VO^H)ڊ>1!^'7mM D$ԀޞTpQx-K;r0Z@}}= ňXÊ.`YSz{zk]CS10Џ>===6L&V^v=uK 탦gyu J;GKY8p#vlX\s ó='Hx~95ň'i"f;U %مo1ZSS> s8 0 |8'ضm;EAݒ%BYh{hjlJ; wn'LQ ""M@QL ]Ģ{=hhhMf|/~3TוOuf UKuV^:u0A>x~D1hDBE88~108t:q-?Sk, ߐ:i^z%ÍALxE4x\E0AZNgaΝ"ˉKK>90ܾ6oN0N>5̩Q61@o"ήδc͍M&-N,d2C8p暚dsf}$McRJtuu7Q`NLLg<"K9|ɁW`Dɀӯ nڴ]?x\DF{8] <G011`pIAb5XȎ RJ CJ }&1шjF6BL‘0OF" &ў:fXP[[, fB%:u 9 Vq/2(W y, CJw%uGPMI 6RzzHh{ÐZzXcc#NfXB#& _Khttv3o^,G<@fA 9,>KU2***S~?Dlfwvv*O4 <HTtN&3*+fH˷a2@:;;;wsN1$VP!$laT貣.m-nCQhW&~x 0 "L _UUEL#mv3 aN~nֹpDs^o]]iǚglzrZaQR\c?@11I6C@eE%L&E9̩@riOo/swcc~<ֱOtXX\t4CGd˕>J|=3)3`dIDs`QBuu9GP' $#EA}] ];? #H8ug4H+2yX0Wh2v&cH:} ȕѬH $==L`Lk2 0 hhF4H-%HiݔvsZaXPVZ:)ż|Ih4ǩnS'{Z-P7jLX.d,iq̋Tx14OiO+`glʄbP. EcɦMUD/諰-P yrAa1[(l1A1 O݅p0 MKoP*bW}Cù p:mp:(2ZRyXR9S`4bBsu~42̹0[Mvs>1C `B1s\YƋnE+ E@$XZ? p(B%KΝމ'y'BL56*U@csL.9D"v/dE88h4>cv(E"3KYYF#XZ6 A'ed"H ; iĬk<@ tO6c557Ж|a__q~ph^eKɄ\|TXreޞYߏ1˖ :8m/ˁHC1H8UT#9ꪾnՉoS&EӚ-**}JSQOO4M":C̬u$JF,QPPg ʫ\شqS ٰz*!PZQqيbTWU=̺ZJ18~"u`4RJ1(vaiK]. EBJ?M0 Mbm{!7suuͪ.\)31RJ;vf4)9X`֭7xYm[ :K ap-?1$ڴSx14<)#P__ja4pקT@t0@/Q[S;&?D"mEE4_r:/yC8{lڱm[l6aIc% !P\BNl4_m;rtU5Ls)tQS_ 6LLY^bc*Ҟ IDAT7I|| kg> ͊UshPVYҊ|(p:o\ Q1@/ M?П:V__6g*ĐP0 n|%s'Q?>oZ]vNXzZ^`4*(.ub:{AsS3^ybY\FEoF򕯢PTqrh@4,ҥKqۭMxW&0] 3J/)._V\Bی -F4,D\s͵ؼiZZ[Қ#.N_]D"8LK4nw`us:;:^|0tuuaǵ;[6oNۃDLE9 o۞zݿAoozzI ݆456 ٱֆw}GWCee%/|ccc8s Fn^MFP_+V͖>ɓx06hDžz:ݰxf>|k_O .))7ٖjȅ&,]& Rx --"E$Iܾۘv7@`Iys[F3 "p{~(B֭[S) iknM%c[o;Ś:0+@ii)nKRȑ#x1>@ofǢqbj55|4/$¢M$+HEѴ1ySЅίeښH5$bsNiKaֿ-rMh~؈cXx,:QQa7W^+(+x'?ߏG\D1I?ǵ^M7M`*UUqi߿P]ðX(,r}6,X# $G4)qa>|8ƑHHڟg|=! =GN{-I{ G8E"׽=p`is3\.l6;E CgW'|>?q }pd)%Z[Z:L%Dw0KT-gQ\\bBJ ߇ߏή.~$b*GV8Ù)?KꖠNvX#n7P{C<RJ?ҧB47 na[|O:,s)2$<_\!QPhȏ 6(S" 3:wM;?0p$ )%lv;n?~zyUR"ci,-BtbdY}cvxwjrӟ^FgNm ,s8l0y;}@T( YRZ^k0OIi1v޺msޚl7 ز} GAӲZ,@?>4l_ٌBcs-k`ދ 5h^VbIbĦ 8t$Eo^"~?~r6uhT嚵09tP  Mը)GAvD~_]=ՖkVnVN BJ QMBUփΡl}(K Ybٰ~6lX` v&? EQu&#/A Xh$~~"&j*~翅&5|{j=Xr9~_wBJxLEUu%>08#tHMs8tNoG\U/߇O>94y܅> XmVb3Yqpd !м ;o}h #QeN+v޼ 01>vf~ggOgw/"H ?ه5뛱r [8|T9[]f,o2p/~>b 8S lݾzoklܲ-?v|[_w|-@J \,KHYE1֯_>[6oFUm9z:UVCcMU/kcn/ F+4_"n6tw b|̗5 \wQBA6s-Gii);ł-]ʊH$0{GwHH8Oի4}?9݊Cw"h$1TՔ};o~^7bNkǛKѴNO ykp>{GҎ%*<0LrO!>vΜiU"}}}G[{;K `4ԀNCJ !TVjl1$p8f*$mT][Zb0[LiAND '|^/&&|D<MSL1OcMp(v{ݨDWq Xrv|bYoS6u֛XX4H$ UU!=֘_??4ә ߁͛6L`2p8($ODы-gOdF[cYm(//A$źukܳi=܁%U(*.DQQ#iej|篿/*D@JWo3^7#}$~_`p蓣g~*4<4 lu'[FƠ*+K*,D8{d/F1(X @ozӇ.]٘q}\l"D SXf5JJ ށޞd@N:b,_- Y׋ fM-y~)cz}hjjat" P,@WW7uf>s@%lU+qL+Xl"j|u\ɀ׮EcSC+믻߲ES/ !pXa Z8/MXWQF#Xz/c=ƆF(QXڬr)֖o wA8AEE9X?^:h׿UlڸmmD"m[k'/ePy~k>y& Oh63KϞAWw 'wHlkPyA< wk#ط9!0802ٓxafA==@Bb3a›޼ 51(Bqi "(Μh݉(0@`TpݷOo+V5~Csm;rJ<ض}3nz F!p'x\Fvܸw}kFKVЎq;|2X,V!qQ#8ښZduqם|#ӿ#ɋJؾc#n-$P8rNmKM0T~-ksmp;aB0@bni7"+d3 Nl۶ O<xUe/݇Ғʿ/b|'0\Eyh9{vkvo|:Zݝx?]|!mIёqig?gKBOw?(\E37 ?MwN~f3lۄx<ۯDD $O\K"NkuT74UÃ5V] ҲB|3aƋ/ޮoxCO?v yY}*@NĖE#<36l^>珐HL>xfKfo<|4vj3;n=ލgZڐǡI~ 7 EcY;s Cn(k%ŅM$`PĴr0folp8m (t"!MOX,ӎ\ӌ{/2^}]C!*㮻>w3iMo/\YWfmw"Z8ɢDBwb΂8 AAsRIEӎ}}kMWiAL_¤ шrM+dǷ"!EŅ_IHI) QRRjʱqYF"a\ 6S+V5ǎGПt ;wހY۶oXr==#V]U>6"jDLE8NYV PU[7,lʲD"}0LBp:vV<}7m냢(7 DUe_|--8s=uۋ7^ {o?AЗrMǗ(H$f~ 5N#Ctvv3H8)%5c_߇]0pm &fܯ5H9щ_ᄏ#FGGpiB>9k݆=pr$Ӕ瀜wÓ0 ;h~/'A 1fn6BJ P(-ࡴp8qxV>k"ZX ,SU { ]}*{}#; #3w8 M`o^nC"ka%*([PQ\j@6' )9itw fP=ULV> !$T~oov>2Xf$*B0'[4M;o~c"0@r@JQ .~㌄8{{sO[@ XfŘ{ٟ  @d^Sy|.Z_M3Q!ŇDD ta4GODBg+RKHDg؄EDD0@H!""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@H!""] DD ta. ""҅BDD0@H钗"4)3.#rQ"RWy RA82!U!":RhUe^2@3-c2g !"Z!G/ UQ})(t*DDYUrf\Fx9eȘ+2E%Ԅ(3W Mve&{!*Qm:{39ʼe+.qd3/EQP*bݻw>"x?h^^[]},S2p9m(Pʴ08-͸Au"oh Iń5FDD56ՠ(r 9NVm|L˭Z EDyj`zݵ{]lM1ny+(/!p-,A& @ \ބX2=ZBeMy v+leUn>тB`XArݽ٭Uvu$S q u,@Dj-R٬S.ݓ{<, [>Wqӳ8{ejDD)B46`󶵺<ΓbrbQ .k)o4y,_ހS'UeQv!P[_떣,`<*T,<!sz14{d`c /t]cQ\I3+.DD[,<.eQ?LC?0!"BqR@ѿƘh%޵g2vEyOoeWeLw$n6-_*_`R>O>Y!550w7_15UY裏,8eRIDATv\k?ԏ!qםw_Kglhh~zx.}Iuyy=v~z,!~C\-+f]^ ""҅BDD0@H!""] DDK_~c \M~}X,q\x<0 X,p:2[, "dT&$lɨTMͨ\ CV@QQe?bbb"iP']e S|]/[뮻aN Ӽ 7ްKtnoo7~m,ɮx2.g6p*32N>ikoC[{[劋qBQ.ݠi^uo’Y %73FgחR^*ߛX,9;u6" 8 Y !5>O Q, [,=O^~hl0ڛ4IAr*JDsM+dv<%Eam~<3(ZN"4?%"*?v|ug󽺎J͕XhT19Vh{.(qw0rzW+q)"4_8?׺.ﶜ鮮QzFċY@{}VCU>i9Lk">~)·v^bkN8G[k#v۹O?僕]q^Eě٭ͯ#./Q^_Yuy>"^nmo=a~),r e2 DF?[U&U㵚ZVj< 'Fkurl=iF@`<>Ͽc&<|j`?āA)כGsiu絪Km̆kb2d9/Hk7t_L.cvy!iEUYDtqƍߺ}|FÈhqee8GsLvv>>].mGKKK{vww{GGG㍍ͬ+h13¸6R.d04|% 8@" )@" )@" )@" )@" )@" )@" )@" )@" )@" )@" )@" )@" )L@/aIENDB`fwupd-1.9.16/data/org.freedesktop.fwupd.service.in000066400000000000000000000002611460375044200220660ustar00rootroot00000000000000[D-BUS Service] Name=org.freedesktop.fwupd Documentation=https://fwupd.org/ Exec=@libexecdir@/fwupd/fwupd User=root SystemdService=fwupd.service AssumedAppArmorLabel=unconfined fwupd-1.9.16/data/org.freedesktop.fwupd.svg000066400000000000000000000243251460375044200206270ustar00rootroot00000000000000 Adwaita Icon Template image/svg+xml GNOME Design Team Adwaita Icon Template fwupd firmwareupdater fwupd-1.9.16/data/pki/000077500000000000000000000000001460375044200144365ustar00rootroot00000000000000fwupd-1.9.16/data/pki/GPG-KEY-Linux-Foundation-Firmware000066400000000000000000000041711460375044200224020ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 mQENBFsDBO0BCACjkrMuRgaWxP88Ubc1Xar5mxLMNXAJUzeYQVh/LnkEwytO3Ekh GDH8Ch78269MiezkJmUGUUGyjKhqZECtZaKGp4LSl6gTPFDFHS/xKaq8L+8G/v4K LEtZE03PKSnY2XYnf+3Kc6tmIZBB67yRg/79p3OpFd95wqyu+2c1cVkjCA1Q8XpO bgCDfNacU3Yag6GXYlKpLmlVkYaAptjV0FrbLLBjaHvFeGAXgRUlv0PRyDjKD2XT PEBtbg2+qTxPJIOlFgGNsJjkFL7R3mWwn00yF4jt9JMYGkpNAuFg1c/TZ1v64wlP N6i2DsDwIMQ9S/ahJWdX/zP4JMTdpYNP91o9ABEBAAG0WkxWRlMgYSBTZXJpZXMg b2YgTEYgUHJvamVjdHMsIExMQyAoTFZGUyBhIFNlcmllcyBvZiBMRiBQcm9qZWN0 cywgTExDKSA8ZmlybXdhcmVAZnd1cGQub3JnPokBOAQTAQIAIgUCWwME7QIbAwYL CQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQxjYXh6CoSeE3pQf+MlPyQzkVhdRU fqjzu3Ba9dZ+hmsol2ooFmytd058AIO1eKie2LxnkQw4P5prFOnVWbbFi79vUEzQ KK5xpW46nEglU14xYgv+4cMlVNWBYIVsXIIKKs2z4gM8oCA20JpVunnVbaViWTna YwiUbTniIvLQH4RLo66Qzd0b3Z8ycK0bVbCl4RazSbWAGHMAnqm0xSQqsWRQwaHk vxXEbjb0hfO4P/PZui5vFGr82tZUdGVKift1JlOzDMjVcmFIuYITHQyGaZ5Xsh2h Pu9gI9Vp7OQoA7dJ+Qc5LBk3rVxN5Zx3jUSWOd7Dtvrm+ArBy/y0MCVyv9fcSvAH vEv9T4zhE7kBDQRbAwTtAQgAwjiRDT3qinYz7b1s9SM2Y7aZG9JhWi/Zp2qGzGVX QpVV2EK3PnAfZpyt99I63N7d/QDPqLFmTyjlv5cMb3QxVyXGBCGGz3OiWYY0NaDd s1sp3J+TT6bHNG/Lo+vgcTRvzHvq7HbbeNssIMLr6MDAj0fZSh5UlAfQdC3qz90A qIPGcx5AwgCwXLDqzCusz17Erc3IK/TG4r0AbRFtGx0hl2w5EOn7funw8BhnJ59w OMsq7sXDmFff4hQjgQoDezMkA1EgzFokRY7pToLG3X1KdDXKR0edQ3+1mlJTf9XN Zaz+ortKsugmTmzsF4DlRhq0Ok0VWuq0rzWndpFyFvIewwARAQABiQI+BBgBAgAJ BQJbAwTtAhsuASkJEMY2F4egqEnhwF0gBBkBAgAGBQJbAwTtAAoJENTAcNuxNA6+ 1T4H/jsWVrANLKElBwZJpPOVy4Haw7UG+zk7lfwck0H8J9ShDtwhNTg03BiOONP/ JuR8XvOxjqdUexEmAJdQCtxJgLTlI40xSlcmSEIneCamOhA/I/T+nkXFAfV65FKF +OR3Ee122sUMXvLCcNvcbM23GIWiN/YmYFlK1PGNe3oOn4MWJ/28dbCLuEOPP4Tu VJM/RpZ65qCnojc1meMcPJxI5iNWZtG9SmmGWDI3f7mDK+dtD06VLmPd3uc8P23t YN0o/Jkgz2oV5GKD9t2+Ne2C5H5xZ0aE7dDVM8ErEPd3wTS+bC8GhPxHjj4A6HyA MNZAEZSAJ4TVpzbfyOMcpSRK4yVLTAgAmTVV79EH/14s60Ya0LCtpifpvpZimbbo xBGFaymvX7doxZyITC66JNTzT4Eixp8FNRloKWkEo6gPwA6qshlhc0HpmiqNmC+k QNYIVeanrflV2bVzYSdsIzrZLUTd6P835YYxD1nsQGwnCqeeD0gJlV+alo0LYTRt lFwNYxHU7BM09wu7sUEvYW5wt4TXPUrZ9jV+BM9UQLatW+S5vO41wqfTmPKGEqct doW2ZYUgCc4aFGTOj3fA5hoK6EjAQpVdkcA7fiRYLn5AIvM4FGxIqI5khjsZUEPa 9Gpui69Y4a+x4QEIDj/WAHOOMSIg/n96e+uRdGXN4c8nr7JSASxlow== =RFA4 -----END PGP PUBLIC KEY BLOCK----- fwupd-1.9.16/data/pki/GPG-KEY-Linux-Foundation-Metadata000066400000000000000000000041711460375044200223460ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 mQENBFsDA8IBCACgSd0NAJFEUjqcyv38If9f/FCQi2C2MQbzNt05DblHAg6eBk/V eYM/GI+Cr9sPwxs8ZWtN0IRoQp/d7MRxe43zFT4IH2N4RVaBTgWCoRerPn09k4K/ 2fk6GWIY8lgxlKV/LinM5XkFDXv6Zf/o8Nv/i9bVO9Dv1bVh1ThgA3xy8WIzUQge cVviEjEYG10TX+NENGgdA+aD/fMk4Wzwz6L48D+ryTiXGFnwoizifr9DIn4yIp6i b4vTQY96VoXHSgU6JRvYjzPPME+NmmcLgW0hGJlVvi8RL+7wJPVeS0ioqPzMtonS evrwVv5E5k87i+LS/vdVu3SIUzR9JLIXtvNNABEBAAG0WkxWRlMgYSBTZXJpZXMg b2YgTEYgUHJvamVjdHMsIExMQyAoTFZGUyBhIFNlcmllcyBvZiBMRiBQcm9qZWN0 cywgTExDKSA8bWV0YWRhdGFAZnd1cGQub3JnPokBOAQTAQIAIgUCWwMDwgIbAwYL CQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQCm3O9rRvPb/wsQf7BGEkgT08Bx1v 657l//B/yniB7VGH+4plrX2OkWVzDxn+z6APcgqDMnzwatddxM7J6mm4yxA0nFKs D5BueTItYv5zQCzX8e4TM1oaUWBr8nACK7/WSxNIC+GRUKl68v+dIbp1bhdmAYzj wTn/uDW47Z7gtQYDJJit2MsKfu5Lwar7w+divH+6KWdaMctm2injYYIlpKCjffl8 RZ4PgX7lN5C0s9kWDkH9iI2i5aqJaI8gZHK6EKwitmsHMJBczekymlXh4MheYCKm IWJLh8tvK6LaVoddwfle4orhq4b7doA57H8BgJDDz+MxyjZn+GAireCMaJjtCSWv q5bnsdnMH7kBDQRbAwPCAQgAq6hTejZZcIXnfA4Z/1c03USKnK8SeG/yqlggvpyZ 9C3hAvJITtQ7iZmz0VKOjwQtds52qnZYbOn/Fr+4Ef+cbFZRNxamZ4kf0XSAVXSv EODMFj+BpdoDwAWhJcvyijoMV6N+gbCG+UedMNnpe25tlCRjouBSEF5KWJabejdh iZ8ikN+HNJa5uQ68F76aDP1BOE0XiLrOZC0MZ7mvbyOi9LPXFyV2EuVj+gt7r+2x OlSMmor7RTEAJcBK9tiew5LhNHeaqbe3xnOcpWrAaoVdIed7h5YbbetTFMWHCPGJ raGGRSv3OrZDfQXZvOi+k6I6wWEQrsUCIiVKPefU+5xSpwARAQABiQI+BBgBAgAJ BQJbAwPCAhsuASkJEAptzva0bz2/wF0gBBkBAgAGBQJbAwPCAAoJEL4e3StH4Ita hvAIAIlZlrAJrbj7aQ3VkFcTJJC+68BaGNqte2S3Zv7ONLjT1kc0xSflf4c4MHef q7WmLLsjUHocD0a8SUsR1V/Fp36qG1Yr7mfhf7dY3TwwUw9VXQoEdpEWZES/IJ4d oPXSowk8eSbb72g4dvt9p+wlDKlsT7YHjmfn9Zct5FqcQ2kV9+900DtWlvPK8hRR N0FibLR9GMorSFfHotFQ8AjdiXQUo+6GTb2HDJ1+aI4fpYo8NnqUs0wvCVtxrqn9 JBzSgKkFAjHOiz/C6a0JOBtmH6tL41U7ZtUI2idQWsHiufyCTVOHRbyHoOxxFEpX Xu3l2vckZaENNyNOGCqrIo8npFQczAf/REhK0Z8UumttRm2CQkJnkqRgLnIoJq3i l7ljKjFi06XLJxOjLLpIFBH/yAJ5YbsYZt+XuT3WgORfHPDU3xepWGfQj4QFcsny GWStnu6Ej/PogJyyvZ1hFpKu8g2cIp04vEebWeYHAMorvwty/p+MJnH6NeSQq5Pe n22AuKKENtgYwulgJH0VQ0lJ5k1CcFuEuZqnXk5CUIC4tStdUS6hgn+vEkaJ5dX8 nj/X9etEj2nnQGIL+7Dh2Z+UGaZqxSa1tjF5+7uC85K0NTq6Cpc+Bnd7Gqb+afnn /iFHG21+hpjQmdSvpbGTabrM9gxd0iuDFXSFSttMtSC+gR5VcNQpUA== =7Jrd -----END PGP PUBLIC KEY BLOCK----- fwupd-1.9.16/data/pki/GPG-KEY-Linux-Vendor-Firmware-Service000066400000000000000000000016771460375044200231370ustar00rootroot00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2 mQENBFWt/98BCADZ4+lUHSp4OMlzVf4HlJNLJ7Ks5QxGwL/hy2wChoNLuA/j4GNM 9mBZutKynYmphD0Mi4XjXn7JNXyuJa8Qutz98/Iyhsjq4LeiL9ayaKMXT+3pKlTm Gd/Fzo3QEOqTJ5s2RamrfwFIVuvwoj+rNmzj5fUCgoDOZeqVl6gxb7ZPzL8sWTOU iLeGMSzZBGE0ioJ82PZzsHelrrObDP1mMre1jQ6zxLlnYUlLvtJpydAfeBxU+6yL fgPeoFeuCE6JIszyWuyAgpBpYSGgj1bpt9Sxc2+MoZ0BjDzoijZqt4O48gYuEaLf iqYzQybe1JF0McO4C0dmjdKQz2qm0XrQyNhVABEBAAG0LkxpbnV4IFZlbmRvciBG aXJtd2FyZSBTZXJ2aWNlIDxzaWduQGZ3dXBkLm9yZz6JATcEEwEIACEFAlWt/98C GwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQSKbYDkU4usJjjQgAzmTcA8qH s+1kieEZvsUzH4wun2Hlz7R5FRc/7BijgIQAA9TTrJnwbJmEBzEvHv7FKQLiBN3a 0lQIZgahmcUt1qm6VW94VAio+SDCdqTx73wUsgM3t9sAwKxkEdJQQoO8PqYHV3uK rq0t2YjXglIBHRDiJlOTAR3if37OCDKCcHOOODqYrsN7wNleez+ulkDyP7C7ZTbm /A7Xec73t2OQUnejU0uvRvc7VSnQDRFBHA9TPiBhbruMw+ZX+z/wfPd7x2RCqoOE vHh+QofE41Ya2QOkT96fAKfcJ+gvIbmwp3w7h+Hus1h3xDrykCG9cCxuH0HxooVI XL3IlFx/6OUpBA== =6Dz2 -----END PGP PUBLIC KEY BLOCK----- fwupd-1.9.16/data/pki/LVFS-CA.pem000066400000000000000000000032171460375044200161770ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEqjCCAxKgAwIBAgIBATANBgkqhkiG9w0BAQsFADA6MRAwDgYDVQQDEwdMVkZT IENBMSYwJAYDVQQKEx1MaW51eCBWZW5kb3IgRmlybXdhcmUgUHJvamVjdDAeFw0x NzA4MDEwMDAwMDBaFw00NzA4MDEwMDAwMDBaMDoxEDAOBgNVBAMTB0xWRlMgQ0Ex JjAkBgNVBAoTHUxpbnV4IFZlbmRvciBGaXJtd2FyZSBQcm9qZWN0MIIBojANBgkq hkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAtfUXH3NwDJzWyhkPyPcFI899+tPZ/SMp OkDtRr9dJjgQkSO9jKCue4DVq8Bd9RcL76F7XnEKG0LiuKnr+D7+x86TtDAPCbkP WAS7fAaetLtiNFU96cokhjeALB3hyamkMQnCw+5Ov+sHJfGI9Bor9UaIIbIB4r8v oU1WpE7N6Ix2qsS5b88+Z6EIV6CX8RbciOC/TfyYVnpF1cd4l7LH7TtL+ERpsPwv rk0JgVoRzG3BT5yYfuxHIe4H4Axh95tW9i6urzyQkXRz14twwwcEDvl5ALrBLNJJ 8EDz9oR8HBPbxbd4i2dBfziY7TW4o/VgZKTGWA39JfwWNc5RxaYzBhBmg5nRcVFs E7PlovhyFH/0RNm/3E6vZQCeM+FNps0ovVq8Yqg8whL/yZ0iNlavCGTWhaxisVHG 7mQopV4jZlafxvrcBFzK8RPe8Gi04FFn4ugZtJnOuMel+AiADhgtWZCENiyWV+V7 WF1SFF4HaHuS8qqna/p9lrpVq6TBr0WRAgMBAAGjgbowgbcwEgYDVR0TAQH/BAgw BgEB/wIBATAwBgNVHREEKTAnhhVodHRwOi8vd3d3LmZ3dXBkLm9yZy+BDnNpZ25A Znd1cGQub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA8GA1UdDwEB/wQFAwMHBgAw HQYDVR0OBBYEFLGN6uQjp34JjrXuMeBq3Z40N2WsMCoGA1UdHwQjMCEwH6AdoBuG GWh0dHA6Ly93d3cuZnd1cGQub3JnL3BraS8wDQYJKoZIhvcNAQELBQADggGBABNK mC4AcqsBCVRGpwJeUymh5G6uUpzkoEDw+y9TEoWzfldV0epU7ruqI2p8B8YshDK6 +D4CFmCnW8cc+Jb6jrJ2ZcjUqWE/c+uwZhwsUHNdk6ummPPKfMhRSbduk1ngdQe5 meIgWGkoCfJ48GUAVVD6MlrMTNFsot1GN9x3ALMqhSU49+X43yikcc9WY2F8JOY8 xYpGpgUQV1hBSPOGK4XhgztpFLqw0GxJiLrOfKjtJwSTkxGCpPi2dLS0huk/mreT NAQ5FnMLkoqfR1RGga3tiP5w13gqDBV7a6MYMdmMfAAZhfRtlDu6SiAmjEmlSkOK PNhdoCNVDQLQpGaKZUI5hjMfR90U8Cm/6e0ondwjV4J6f4CS4wkQ5zzITGWptagE 01tpgTXf7TLaFGtzR8cl8XgV+UO3T4DQjEQkXUaS7n72ZCGv/s4LraLunhBrVHSq glEXpU/V/JNptgArIiRFZOrto52cUnnlNEfgqIzAHv/LMFRIkMo8ZMGTgScFrA== -----END CERTIFICATE----- fwupd-1.9.16/data/pki/meson.build000066400000000000000000000015271460375044200166050ustar00rootroot00000000000000# only install files that are going to be used supported_gpg = libjcat.get_variable(pkgconfig: 'supported_gpg', default_value: '1') == '1' supported_pkcs7 = libjcat.get_variable(pkgconfig: 'supported_pkcs7', default_value: '1') == '1' or host_machine.system() == 'windows' if supported_gpg install_data([ 'GPG-KEY-Linux-Foundation-Firmware', 'GPG-KEY-Linux-Vendor-Firmware-Service', ], install_dir: join_paths(sysconfdir, 'pki', 'fwupd') ) install_data([ 'GPG-KEY-Linux-Foundation-Metadata', 'GPG-KEY-Linux-Vendor-Firmware-Service', ], install_dir: join_paths(sysconfdir, 'pki', 'fwupd-metadata') ) endif if supported_pkcs7 install_data([ 'LVFS-CA.pem', ], install_dir: join_paths(sysconfdir, 'pki', 'fwupd') ) install_data([ 'LVFS-CA.pem', ], install_dir: join_paths(sysconfdir, 'pki', 'fwupd-metadata') ) endif fwupd-1.9.16/data/power.quirk000066400000000000000000000002451460375044200160650ustar00rootroot00000000000000#This file provides manufacturer specified minimum battery thresholds [LENOVO] BatteryThreshold = 25 [Star Labs] BatteryThreshold = 30 [HP] BatteryThreshold = 50 fwupd-1.9.16/data/remotes.d/000077500000000000000000000000001460375044200155535ustar00rootroot00000000000000fwupd-1.9.16/data/remotes.d/README.md000066400000000000000000000057321460375044200170410ustar00rootroot00000000000000# Remotes ## Vendor Firmware These are the steps to add vendor firmware that is installed as part of an embedded image such as an OSTree or ChromeOS image: * Compile with `-Dvendor_metadata=true` to install `/etc/fwupd/remotes.d/vendor.conf` * Change `/etc/fwupd/remotes.d/vendor.conf` to have `Enabled=true` * Change `/etc/fwupd/remotes.d/vendor.conf` to have the correct `Title` * Deploy the firmware to `/usr/share/fwupd/remotes.d/vendor/firmware` * Deploy the metadata to `/usr/share/fwupd/remotes.d/vendor/vendor.xml.gz` The metadata should be of the form: FIXME.firmware FIXME FIXME FIXME FIXME

    FIXME

    http://FIXME 86406 firmware/FIXME.cab 96a92915c9ebaf3dd232cfc7dcc41c1c6f942877

    FIXME.

    FIXME
    Ideally, the metadata and firmware should be signed by either GPG or a PKCS7 certificate. If this is the case also change `Keyring=gpg` or `Keyring=pkcs7` in `/etc/fwupd/remotes.d/vendor.conf` and ensure the correct public key or signing certificate is installed in the `/etc/pki/fwupd` location. ## Automatic metadata generation `fwupd` and `fwupdtool` support automatically generating metadata for a remote by configuring it to be a *directory* type. This is very convenient if you want to dynamically add firmware from multiple packages while generating the image but there are a few deficiencies: * There will be a performance impact of starting the daemon or tool measured by O(# CAB files) * It's not possible to verify metadata signature and any file validation should be part of the image validation. To enable this: * Change `/etc/fwupd/remotes.d/vendor-directory.conf` to have `Enabled=true` * Change `/etc/fwupd/remotes.d/vendor-directory.conf` to have the correct `Title` * Deploy the firmware to `/usr/share/fwupd/remotes.d/vendor/firmware` * Change `MetadataURI` to that of the directory (Eg `/usr/share/fwupd/remotes.d/vendor/`) ## Mirroring a Repository The upstream LVFS instance will output a relative URL for firmware files, e.g. `bar.cab` instead of an absolute URI location, e.g. `http://foo/bar.cab`. When setting up a mirror of the LVFS onto another CDN you just need to change the `MetadataURI` to your local mirror and firmware downloads will use the relative URI. fwupd-1.9.16/data/remotes.d/lvfs-testing.conf000066400000000000000000000005321460375044200210470ustar00rootroot00000000000000[fwupd Remote] # this remote provides metadata and firmware marked as 'testing' from the LVFS Enabled=false Title=Linux Vendor Firmware Service (testing) MetadataURI=https://cdn.fwupd.org/downloads/firmware-testing.xml.@compression@ ReportURI=https://fwupd.org/lvfs/firmware/report OrderBefore=lvfs AutomaticReports=false ApprovalRequired=false fwupd-1.9.16/data/remotes.d/lvfs-testing.metainfo.xml000066400000000000000000000027461460375044200225340ustar00rootroot00000000000000 org.freedesktop.fwupd.remotes.lvfs-testing Linux Vendor Firmware Service (testing firmware) CC0-1.0

    The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer.

    This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails.

    Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$.

    fwupd-1.9.16/data/remotes.d/lvfs.conf000066400000000000000000000006241460375044200173760ustar00rootroot00000000000000[fwupd Remote] # this remote provides metadata and firmware marked as 'stable' from the LVFS Enabled=@enabled@ Title=Linux Vendor Firmware Service MetadataURI=https://cdn.fwupd.org/downloads/firmware.xml.@compression@ ReportURI=https://fwupd.org/lvfs/firmware/report SecurityReportURI=https://fwupd.org/lvfs/hsireports/upload AutomaticReports=false AutomaticSecurityReports=false ApprovalRequired=false fwupd-1.9.16/data/remotes.d/lvfs.metainfo.xml000066400000000000000000000023221460375044200210470ustar00rootroot00000000000000 org.freedesktop.fwupd.remotes.lvfs Linux Vendor Firmware Service (stable firmware) CC0-1.0

    The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer.

    Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$.

    fwupd-1.9.16/data/remotes.d/meson.build000066400000000000000000000035611460375044200177220ustar00rootroot00000000000000if build_standalone and get_option('lvfs') != 'false' con3 = configuration_data() con3.set('compression', lvfs_metadata_format) if get_option('lvfs') == 'disabled' con3.set('enabled', 'false') else con3.set('enabled', 'true') endif configure_file( input: 'lvfs.conf', output: 'lvfs.conf', configuration: con3, install: true, install_dir: join_paths(sysconfdir, 'fwupd', 'remotes.d'), ) configure_file( input: 'lvfs-testing.conf', output: 'lvfs-testing.conf', configuration: con3, install: true, install_dir: join_paths(sysconfdir, 'fwupd', 'remotes.d'), ) i18n.merge_file( input: 'lvfs.metainfo.xml', output: 'org.freedesktop.fwupd.remotes.lvfs.metainfo.xml', type: 'xml', po_dir: join_paths(meson.project_source_root(), 'po'), data_dirs: join_paths(meson.project_source_root(), 'po'), install: true, install_dir: join_paths(get_option('datadir'), 'fwupd', 'metainfo') ) i18n.merge_file( input: 'lvfs-testing.metainfo.xml', output: 'org.freedesktop.fwupd.remotes.lvfs-testing.metainfo.xml', type: 'xml', po_dir: join_paths(meson.project_source_root(), 'po'), data_dirs: join_paths(meson.project_source_root(), 'po'), install: true, install_dir: join_paths(get_option('datadir'), 'fwupd', 'metainfo') ) endif install_data('README.md', install_dir: join_paths(datadir, 'fwupd', 'remotes.d', 'vendor', 'firmware') ) # replace @datadir@ con2 = configuration_data() con2.set('datadir', datadir) configure_file( input: 'vendor.conf', output: 'vendor.conf', configuration: con2, install: get_option('vendor_metadata'), install_dir: join_paths(sysconfdir, 'fwupd', 'remotes.d'), ) configure_file( input: 'vendor-directory.conf', output: 'vendor-directory.conf', configuration: con2, install: true, install_dir: join_paths(sysconfdir, 'fwupd', 'remotes.d'), ) fwupd-1.9.16/data/remotes.d/vendor-directory.conf000066400000000000000000000004451460375044200217240ustar00rootroot00000000000000[fwupd Remote] # this remote provides dynamically generated metadata shipped by the OS vendor and can # be found in @datadir@/fwupd/remotes.d/vendor/firmware Enabled=true Title=Vendor (Automatic) Keyring=none MetadataURI=file://@datadir@/fwupd/remotes.d/vendor/firmware ApprovalRequired=false fwupd-1.9.16/data/remotes.d/vendor.conf000066400000000000000000000007701460375044200177230ustar00rootroot00000000000000[fwupd Remote] # this remote provides metadata shipped by the OS vendor and can be found in # @datadir@/fwupd/remotes.d/vendor and firmware in @datadir@/fwupd/remotes.d/vendor/firmware Enabled=false Title=Vendor Keyring=none # NOTE: never point MetadataURI to a NFS or SMB *directory* otherwise all the archives are copied # locally where metadata is auto-generated by the daemon -- point to the `.xml.gz` instead. MetadataURI=file://@datadir@/fwupd/remotes.d/vendor/vendor.xml.gz ApprovalRequired=false fwupd-1.9.16/docs/000077500000000000000000000000001460375044200136725ustar00rootroot00000000000000fwupd-1.9.16/docs/architecture-plan.svg000066400000000000000000002241361460375044200200350ustar00rootroot00000000000000 image/svg+xml fwupd Bluetooth (bluez) Keyboard customplugins udev, sysfs, ESRT systemd pending.db internet system IPFS CDN sqlite gnome-softwarefwupdmgr Mouse SAS / RAID BMC UpdateMetadata() GetDevices() IPMI & Redfish only metadata manual user opt-in firmware AppStream XML LVFS session embargoedmetadata fwupd-1.9.16/docs/best-known-configuration.md000066400000000000000000000120701460375044200211500ustar00rootroot00000000000000--- title: Best Known Configuration --- ## Introduction Component ``s are used both by OEMs and by customers to identify a *known-working* (or commercially supported) set of firmware on the machine. This allows two things: * Factory recovery where a system in the field has been upgraded * Ensuring a consistent set of vendor-tested firmware for a specific workload The tags are either assigned in the firmware cabinet archive (the `.metainfo.xml` file) or added post-upload on the LVFS and are then included in the public AppStream metadata. A single firmware can be marked with multiple tags, and tags can be duplicated for different firmwares. This would allow an OEM to say *this set of firmware has been tested as a set for workload A, and this other set of firmware has been tested for workload B* which is fairly typical for enterprise deployments. ## LVFS The LVFS added support for “vendor-defined” component ``s in 2021, which are also supported in fwupd since version 1.7.3. These tags are typically used by OEMs to identify a manifest of firmware for a specific machine SKU. This is opt-in for each vendor, and so if you are a vendor reading this and want to use this feature, let us know by [opening an issue](https://gitlab.com/fwupd/lvfs-website/-/issues). ## Client When provisioning the client machine, we can set the BKC by setting `HostBkc=vendor-2021q1` in `/etc/fwupd/fwupd.conf`. Any invocation of `fwupdmgr sync` will install or downgrade firmware on all compatible devices (e.g. UEFI, RAID, network adapter, & SAS HBA) to make the system match a compatible set. The `fwupdmgr sync` command will also ensure that firmware is installed that matches the device branch, if the device has one assigned. Updating or downgrading firmware away from the *Best Known Configuration* or to different branches is allowed, but the UI shows a warning. Using `fwupdmgr sync` will undo any manual changes and bring the machine back to the BKC. ## Local metadata To define a locally defined BKC, extra metadata is read from the `/usr/share/fwupd/local.d` and `/var/lib/fwupd/local.d` directories. For instance: For example: 3ef35d3b-ceeb-5e27-8c0a-ac25f90367ab 2ef35d3b-ceeb-5e27-8c0a-ac25f90367ac 1ef35d3b-ceeb-5e27-8c0a-ac25f90367ad mycompanyname-2022q1 This then appears when getting the releases for that specific GUID: fwupdmgr get-releases --json 3ef35d3b-ceeb-5e27-8c0a-ac25f90367ab { "Releases" : [ { ... "Version" : "225.53.1649", "Tags" : [ "mycompanyname-2022q1" ], ... }, { ... "Version" : "224.48.1605", "Tags" : [ "mycompanyname-2022q1" ], ... }, { ... "Version" : "224.45.1389", ... } ] } ..and can be synced on the command line: $ fwupdmgr sync ╔══════════════════════════════════════════════════════════════════════════════╗ ║ Downgrade System Firmware from 225.52.1521 to 225.53.1649? ║ ╠══════════════════════════════════════════════════════════════════════════════╣ ║ This release sets the number of HBAs supported by the system to 1024. ║ ╚══════════════════════════════════════════════════════════════════════════════╝ Perform operation? [Y|n]: ## Vendor Firmware Remotes Tags can also be included in the `metainfo.xml` files included in `.cab` archives installed into `/usr/share/fwupd/remotes.d/vendor/firmware/` -- although in most cases it makes sense to actually *decouple* the tag assignment from the firmware binary by specifying local metadata. If the tag is specific to the firmware build, then it can be included directly in the metadata: org.fwupd.myproduct.firmware ... product-mycompanyname-2022q1 **NOTE:** the `namespace="lvfs"` is required for fwupd as the `` section is also used by other software for different purposes. Forgetting the namespace will cause fwupd to ignore the tag! fwupd-1.9.16/docs/bios-settings.md000066400000000000000000000251311460375044200170100ustar00rootroot00000000000000--- title: BIOS Settings API --- fwupd 1.8.4 and later include the ability to modify BIOS settings on systems that support the Linux kernel's [firmware-attributes API](https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-firmware-attributes). Drivers included with the Linux kernel on supported machines will advertise all BIOS settings that the OS can change. The fwupd daemon uses this API create an abstraction that fwupd clients can use to offer BIOS settings to change to end users. ## Interactive command line usage Both `fwupdmgr` and `fwupdtool` have support for the fwupd BIOS settings API both for interactive use. Using `fwupdgmr` will require PolicyKit authentication as only authorized users can view the current BIOS settings. Using `fwupdtool` will require running the tool as root, as `fwupdtool` only works as root. ### Getting available BIOS settings To fetch available BIOS settings for a machine: ```shell # fwupdmgr get-bios-setting ``` This will provide a listing of all available settings. If you would like to just see a subset of attributes, you can list them space delimited on the command line. For example: ```shell $ fwupdmgr get-bios-setting WindowsUEFIFirmwareUpdate MmioAbove4GLimit Authenticating… [ - ] WindowsUEFIFirmwareUpdate: Setting type: Enumeration Current Value: Enable Description: BIOS updates delivered via LVFS or Windows Update Read Only: False Possible Values: 0: Disable 1: Enable MmioAbove4GLimit: Setting type: Enumeration Current Value: Auto Description: MmioAbove4GLimit Read Only: False Possible Values: 0: Auto 1: 40 2: 42 3: 44 4: 46 5: 48 ``` When using BASH as your shell, bash-completion can be used to discover BIOS settings. In the above example, those settings could be found from this series of tab actions: ```shell $ sudo fwupdmgr get-bios-setting W WakeonLAN WakeUponAlarm WindowsUEFIFirmwareUpdate 130 $ sudo fwupdmgr get-bios-setting WindowsUEFIFirmwareUpdate Display all 131 possibilities? (y or n) AbsolutePersistenceModule CStateSupport M2Slot2Port PCIeSlot3Bifurcation PXEIPV6NetworkStack SetStrongPassword AccessSecuritySettings DASHSupport MaxPasswordAttempts PCIeSlot3DLFSupport QuadM2PCIeCardFanControl SmartUSBProtection AfterPowerLoss DataScrambling MCRUSBHeader PCIeSlot3LinkSpeed RealtimeDIAG SMT AlarmDate(MM\DD\YYYY) DeviceGuard MediaCardReader PCIeSlot3Port RearAudioController SRIOVSupport AlarmDayofWeek DevicePowerupDelay MmioAbove4GLimit PCIeSlot4Bifurcation RearUSBPorts StartupSequence AlarmTime(HH:MM:SS) DeviceResetTimeout --no-authenticate PCIeSlot4DLFSupport RemoteSetSMP USBChargingPortInS4S5 AllowJumperClearSVP DIAG7SegMode NUMA PCIeSlot4LinkSpeed RequireHDPonSystemBoot USBPortAccess AMDMemoryGuard EnhancedPowerSavingMode NVMeRAIDMode PCIeSlot4Port RequireSVPwhenFlashing USBTransferTimeout AMDSecureVirtualMachine ErrorBootSequence OnboardEthernetController PCIeSlot5Bifurcation SATAController UserDefinedAlarmFriday ASPMSupport FanControlStepping OptionKeysDisplay PCIeSlot5DLFSupport SATADrive1 UserDefinedAlarmMonday AutomaticBootSequence FrontUSBPorts PasswordChangeTime PCIeSlot5LinkSpeed SATADrive2 UserDefinedAlarmSaturday BIOSPasswordAtBootDeviceList HardDiskPre-delay PasswordCountExceededError PCIeSlot5Port SATADrive3 UserDefinedAlarmSunday BIOSPasswordAtReboot InternalUSB2Port PatroScrub PCIeSlot6Bifurcation SATADrive4 UserDefinedAlarmThursday BIOSPasswordAtSystemBoot InternalUSB3Port PatroScrubInterval PCIeSlot6DLFSupport SATADrive5 UserDefinedAlarmTime BootUpNumLockStatus IOMMU PCIeSlot1Bifurcation PCIeSlot6LinkSpeed SATADrive6 UserDefinedAlarmTuesday CardLocation --json PCIeSlot1DLFSupport PCIeSlot6Port SATADrive6HotPlugSupport UserDefinedAlarmWednesday ClearDIAGLog KeyboardLayout PCIeSlot1LinkSpeed pending_reboot SecureBoot --verbose ConfigurationChangeDetection M2Slot1DLFSupport PCIeSlot1Port PhysicalPresenceforClear SecureRollBackPrevention WakeonLAN ConfigureSATAas M2Slot1LinkSpeed PCIeSlot2Bifurcation POPChangeablebyUser SecurityChip WakeUponAlarm CoverTamperDetected M2Slot1Port PCIeSlot2DLFSupport PostPackageRepair SelectActiveVideo WindowsUEFIFirmwareUpdate CPBMode M2Slot2DLFSupport PCIeSlot2LinkSpeed PrimaryBootSequence SerialPort1Address XHCIHandoff CPUC6Report M2Slot2LinkSpeed PCIeSlot2Port PXEIPV4NetworkStack SetMinimumLength 130 $ fwupdmgr get-bios-setting WindowsUEFIFirmwareUpdate MmioAbove4GLimit ``` ### Setting BIOS settings To set one or more BIOS settings for a machine: ```shell # fwupdmgr set-bios-setting SETTING VALUE ``` For supported attributes, `fwupdmgr` will also use tab completion in BASH for filling out a value. For example to enable an enumeration attribute: ```shell $ sudo fwupdmgr set-bios-setting W WakeonLAN WakeUponAlarm WindowsUEFIFirmwareUpdate 130 $ sudo fwupdmgr set-bios-setting WindowsUEFIFirmwareUpdate Disable Enable 130 $ sudo fwupdmgr set-bios-setting WindowsUEFIFirmwareUpdate Enable ``` After setting an attribute you may be prompted to reboot as most settings will require a reboot to take effect. ```shell $ fwupdmgr set-bios-setting WakeonLAN Primary Authenticating… [ - ] Set BIOS setting 'WakeonLAN' using 'Primary'. An update requires a reboot to complete. Restart now? [y|N]: ``` If you would like to program multiple attributes, list them in pairs of the name of the attribute followed by the desired value. ## Programmatic command line usage `fwupdmgr` offers support for the fwupd BIOS settings API programmatically as well for use in scripts. To use the programmatic API you will add the `--json` argument to your calls. The output or input will then be expected to be JSON payloads. ### Fetching BIOS settings programmatically Below is an example of fetching the `WakeonLAN` setting. ```shell # fwupdmgr get-bios-setting WakeonLAN --json { "BiosSettings" : [ { "Name" : "WakeonLAN", "Description" : "WakeonLAN", "Filename" : "/sys/class/firmware-attributes/thinklmi/attributes/WakeonLAN", "BiosSettingId" : "com.thinklmi.WakeonLAN", "BiosSettingCurrentValue" : "Primary", "BiosSettingReadOnly" : "false", "BiosSettingType" : 1, "BiosSettingPossibleValues" : [ "Primary", "Automatic", "Disable" ] } ] } ``` Similar to the interactive usage, if no parameters are provided all settings are fetched, and if desired multiple settings can be listed on the command line. Error messages won't be emitted, you'll have to rely on the return code to tell if this was successful. *NOTE:* To debug errors, use the `--verbose` argument to see messages related to the error. ### Setting BIOS settings programmatically To set BIOS settings, a JSON payload will need to be crafted in advance. The path to this payload is used as an argument. ```shell # fwupdmgr set-bios-setting ~/foo.json --json ``` An important return code to know for programmatic usage is that *2* means nothing was done because all settings are already programmed. This message will also be emitted. *NOTE:* To debug errors, use the `--verbose` argument to see messages related to the error. ## Firmware setting policies `fwupd` has the ability to enforce the BIOS settings policy of a system administrator. To use this feature, create a json payload using `fwupdmgr get-bios-setting --json` that reflects the settings you would like to see enforced. Then copy this payload into `/etc/fwupd/bios-settings.d` with a filename ending in `.json`. The next time that the fwupd daemon is started (such as a system bootup) it will ensure that all BIOS settings are programed to your desired values. It will also mark those settings as read-only so no fwupd clients will be able to modify them. This *does not* stop the kernel firmware-attributes API from working. So a determined user with appropriate permissions would be able to modify settings from the kernel API directly, but they would be changed again on fwupd daemon startup. ## Settings types The Linux kernel will offer the following types of BIOS settings: * Enumeration: The setting will only accept a limited list of possible values * Integer: The setting will accept a limited range of integer values * String: The setting will accept a limited length UTF-8 string All of those setting types are accepted by fwupd. It is expected that drivers or firmware will validate the input, but where possible fwupd will do validation of the input to give better error messages and avoid failures. fwupd will also do a mapping where it can accept multiple cases or synonyms for words that are obviously positive or negative. So for example if the setting expects `Enabled` but the user passes `tRUE` fwupd will map this into `Enabled` before sending it to the driver and the driver sending it to the firmware. ## libfwupd `fwupdmgr` internally uses `FwupdClient` to manage BIOS settings. Any other clients that are interested in managing BIOS settings can use this library as well. A sample python application is included in the `contrib/` directory in the fwupd source tree. fwupd-1.9.16/docs/building.md000066400000000000000000000153701460375044200160170ustar00rootroot00000000000000--- title: Building & Debugging fwupd --- These instructons below can either be used by the silicon vendor, or the consulting company to debug existing and new plugins. Sometimes new hardware is only supported in the development version of fwupd which may not even be available as a Snap or Flatpak yet. ## Prerequisites * A PC with Linux (preferably the latest version of Fedora) installed bare metal (i.e. not in VirtualBox or VMWare) * Working access to the internet * A user account (we’ll use `u` as the example here) with administrator permissions ## Setup fwupd development environment A fwupd development environment is setup in a [virtualenv](https://virtualenv.pypa.io/en/latest/user_guide.html) to avoid development work for fwupd from conflicting with any system fwupd installation. All builds will occur in `venv/build` and all installs in `venv/dist`. To set it up follow the below steps: ```shell cd ~ git clone https://github.com/fwupd/fwupd.git cd fwupd ./contrib/setup ``` ## Building After the development environment has been setup you can enter it by running: ```shell source venv/bin/activate ``` You can tell you are in the development environment by looking at the start of your prompt for this prefix: ```text (fwupd) ``` To build the project a script is included that will configure and build the project with default settings. ```shell build-fwupd ``` If you want to leave the development environment at any time you can run: ```shell deactivate ``` ## Running binaries The fwupd project is split into three main components: 1. **fwupd**: The binary that’s running in the background, as root 2. **fwupdmgr**: The client tool that end-users use to interact with the running `fwupd` binary, as a normal user 3. **fwupdtool**: The debugging tool developers use to find problems and to run new code, as root The `fwupdtool` binary does most of the things that `fwupdmgr` does, but without talking to the system fwupd instance. It is a lot easier to run `fwupdtool` with just one plugin (e.g. `--plugins vli`) than running the daemon and all the plugins. You might have to wait 5 seconds and then read thousands of lines of debugging to see the `printf()` you added in a new plugin with the daemon, but with `fwupdtool --plugins vli --verbose get-devices` it’ll be in a few lines, and instant. Within the development environment wrappers have been setup to allow launching `fwupd`, `fwupdtool` or `fwupdmgr` very similar to a host system. There are 3 main differences to note: 1. The `systemd` service will not run. That means that the daemon needs to be manually launched in the environment. 2. dbus *activation* doesn't work. This mean that if you are testing the daemon (`fwupd`) and client (`fwupdmgr`) interaction you need to have two terminal tabs opened each in the development environment activated. One tab would run the daemon, and one would run the client. 3. `fwupd` and `fwupdtool` will be automatically started as root (IE with `sudo`). With those differences in mind all 3 binaries can just be launched like normal: ```shell fwupdtool get-devices ``` ```shell fwupd ``` ```shell fwupdgmr get-devices ``` ## Using fwupdtool To get the list of devices from one specific plugin I would do: ```shell fwupdtool --plugins vli get-devices --verbose ``` This outputs lots of text onto the console like: ```text 10:51:49:0584 FuMain Lenovo ThinkPad WS Dock DeviceId: 73ef80b60058b4f18549921520bfd94eaf18710a Guid: dd1f77bd-88ef-5293-9e34-1fe5ce187658 <- USB\VID_17EF&PID_305A&REV_5011 Guid: 1c09a12d-e58a-5b4d-84af-ee3eb4c3c68b <- USB\VID_17EF&PID_305A Guid: 6201fecc-1641-51f6-a6d2-38a06d5476bf <- VLI_USBHUB\SPI_C220 Guid: c9caa540-6e27-5d40-a322-47eaeef84df0 <- USB\VID_17EF&PID_305A&SPI_C220&REV_5011 Guid: cfa1e12c-4eb9-5338-8b23-02acc5423ccb <- USB\VID_17EF&PID_305A&SPI_C220 Summary: USB 3.x Hub Plugin: vli Protocol: com.vli.usbhub Flags: updatable|registered|can-verify|can-verify-image Vendor: LENOVO VendorId: USB:0x17EF Version: 50.11 VersionFormat: bcd Icon: audio-card InstallDuration: 10 Created: 2019-12-20 ``` Using fwupdtool raw firmware blob (i.e. not the cabinet archive with metadata) can be installed on the device using: ```shell fwupdtool --verbose --plugins vli \ install-blob /home/u/the-firmware.bin 73ef80b60058b4f18549921520bfd94eaf18710a ``` ## Firmware Parsing You can also parse the raw .bin files using `fwupdtool` which has access to all the available firmware parsers built into all plugins. For example: ```shell fwupdtool firmware-parse /home/user/VL105_APP6_8C_09_08_06_20190815.bin Choose a firmware type: 0. Cancel 1. conexant 2. 8bitdo 3. synaprom 4. rmi 5. wacom 6. vli-pd 7. raw 8. altos 9. srec 10. ihex 11. vli-usbhub 12. vli-usbhub-pd 12 FuVliUsbhubPdFirmware: Version: 140.9.8.6 ChipId: VL105 VID: 0x2109 PID: 0x105 FuFirmwareImage: Data: 0xc000 ``` ## Using fwupdmgr You can perform the end-to-end tests with two terminals open to the fwupd development environment. In the first do: ```shell fwupd --verbose ``` and in the second you can do: ```shell fwupdmgr install ~/foo.cab ``` This will send the firmware archive from the locally built `fwupdmgr` to the locally built daemon using a file descriptor, which will call the new plugin code with the firmware blob in the archive. The daemon terminal will also show lots of useful debugging during this process. ## Debugging fwupd When setup using the virtualenv all 3 binaries have the ability be launched with a debugger attached as a **user** by using `DEBUG=1` in the environment. For example to debug `fwupdtool` you would launch it like this: ```shell (fwupd) u@fedora:~/fwupd$ DEBUG=1 fwupdtool get-devices Process /home/u/fwupd/venv/bin/../dist/bin/fwupdtool created; pid = 595311 Listening on port 9091 ``` Then the process will wait for a debugger to be attached to `localhost:9091`. One example is using the [debugger](https://code.visualstudio.com/Docs/editor/debugging) that is part of [Visual Studio Code](https://code.visualstudio.com/). Launch vscode in the same directory as the Git checkout. After it's launched, set a source breakpoint. Then use the run and debug button (or *ctrl-shift-d*) to open up the debugger. From the debugger choose the tool to use. Press the green start button (or use *F5*) to start debugging. The debugger will attach to the process you launched and stop where you left off. ![debugger attached](debug_attached.png) fwupd-1.9.16/docs/debug_attached.png000066400000000000000000012215441460375044200173340ustar00rootroot00000000000000PNG  IHDR@isBIT|dtEXtSoftwaregnome-screenshot>-tEXtCreation TimeFri 05 Jan 2024 06:16:31 AM CSTg.E IDATxy\T?יfaaXrPJn[vKe٦{efi%+* "Ⱦ8;s~sd``@Çs`̙~X̂B!B!),~MG{|ByX1 ӣw@ B!B! i0!tMGώg5mDz,yQB!B!jЍm;^o`!. B!B!] ip!tO{A2(FÇoB!B!}@gnF!G,7M8 p!B!B"FsBnin;u4 !B!BH6H֙!{4]G.״l< lllB!B!64wLYG!dשemy(@GHEoB!B!Hu(F!]hksO:uM7B'2}B!)%?toE2D"󩳖Bz˲hiiA}}=jjjn&6Yn-'F!B!҇t&keYX[[ԴE!a```SSSw8kFm<(F!BC=~@!]m3`bbA]]$!0E"())Q*GI5B>x#B!BуoS011AKK R)zB,&455VVV011R[6Yo#B!B!D{9%)F!T 055@ cBbx#B!B4eH$ //IA7Bѿ&VhzNyPB!B!Ц3V1GD!D{kݙ:!A7B!B!^ݎV>hnnEs!⚬Fw(F!B!tTa!⚬F[gBȃoB!B!uBHCnB>x#B!B!B!D(F!B!B!B@!nܸ&B!K[Ne!cYVe7u[N(B!B!B!7B B: B!B!eB:": !B! $<\(F!B!B!BPB!B!u@B\x#B!B!B!D(F!B!B!BPB!B!c<qB70nNz;Yo9B}7B!<l'''eEEEXtZD!@ \_=H8HAqy=H7Pܟ oMy{<%In_CM:=BCC___899P]]|dffHJJsKa=rbΝ=r]#B!@ [+++HR}6B!}ج`x|8G XWlo6M-2QF"sȚQ_UtFA_hijTOBpe}24?)/KVu ppo8a.6TFTV#Z>/ƭ #°pD6)occ8q"ծ7779$ &O<_{:711k5kP (F3 M \ܧn!tȑ#1m4lذwww0 .`͚5z H$8s fBNNO}!BJ[ZZ: ̬4y_s+/6tB !Ӑ 2?j)ɇ7[ptS"0ت-; 審S 3,+K}͌alf [O[N AEa%}w)ǮjY1t΃nLP!EA"q$''Ν;XXX3,Y#GbyᩧBcc#f̘?`hjjp}aԩx BHd烊J" ,B!]0j(+xX~=v܉m̙3 Le_TH7B`e)u5/GKv#: !tߌ32 w0a1W)t &g>{vt#tƫƀ[[S+L{ޣbq#ұn%AqEФe:#-+7 0qbs+WC~~>݋JRHR\zvˆ#0sLH$|ذaN>g@: B{l}l0\;_3FO[hB!8|nx HRi'...6mكB2e !W\Ck s˲ؼy)3f̀X,ѣG-Gll,:=o=z4VZ̟?C Ayy9^ 0 W^y(&ll PZmaPz^ 9Xh[-b!-,\K>hCD6N a.LD.8z:G۠[c('  zҮw^ML̍;aa-GʱTdF246B 4-z"0F y-ckߣVZ-;{M7:CusXZZ FFFxx` Bhhh@zz:J|>|}}ZpwwGaa!233w>|8VZ߰m6d2nŋ1j( 8}4,YqqǢio>oY!pp~(`aa_~EiBBB|H:u*wʕ+add0 dddepttDeeeoooD"\zgr*^vMc.ڢWYY.]mj(++p///Mcc#_3R)JJJ4N؝@[ nnn@uu5ʐgF2 OZcpÛF??? 0RNRZ0o<a8~8ܒĉ@"l[7ԩSX|9Õ+W0p@;v _~%WɓaddӧS!`σѲO]6n[98W{ -zpD- }[+4 <q0Ȓ1X$vBc7P\[*f.\o&,XXh#v WWW,O`&kcs=\.Ra+ݮ\ւe;Ʋ}'$'`/c;zds; ޣf}3 b_Ǎ?QU CkgS5~dͪ"ɚE*$;/g$+cq_q2=ƺFv EEx_857pqqXE^^lSSjjjWWWxzz ---zT+P]]*g;677}6`jj\tIOv^l-Z~M%tkmMa׮]077Ǹqh"yI[oգm+++ ;vŋϞ=ÇҥKثVH$B\\lȑ?>|v_`1uNӢE0bÝFI&9R2A,RǏ I{bbbPYY~AњL&?yaҤIxS#>>Æ '"##q9{UV!((H_\t)܌9XgUn- u(r U9hDVp9x^ _\G5" J wa"c\t5g{ s@`aM-'@ <߯  5kV b1ts:k߾}Zߛ"q~s߁<#y Ncp+/k-]s[N| }x躉]fq9S+Z7E7zFw[^ H{ƦȻƠ"6/j_9ы-m W&-L<(>- ɿ<.kkk888eYdeec,"'' 7[o @ ݻwMLQEbb"acc`$&&*gll ի|?~<$ m6h߶mD_nS@g&'pѣGcxq]=yZUCLMM1tP GLXwggg777 8ӦMèQ~z$%%i}))r pqq;"##ؼy34lذAcƢ `ii)l@^3oI%w!Kz~0ո^,bY?^=y-Ξ3gb@|| BC =w-y%UxGy}x<=?RUxs.uv>v-~`hhX̜9׿tRo222+VرcQ\\N/^ԅ^o|̝;xǹ.,YQQQرc8v?SgGzv9uAS$$߈!pLow 6 jK@ӌh .]1|p&&&kݛ{РAzj:99Gc= bӦMjSTQ$a28p@5 K;X)-kiG*A*2"s;c,FRrzAlg$HQw- SZ׀oSjqCZ EȭEY},Fx!v+c\MEȪͽ{AAbccOOO.VZhkkmٳg\ʂ8$NA`fѯ|;s5z,nC&q;1opJ # عWR^o\nѭ3?ս\d\@IF3[7CcC {f(g> ??MÒkkkKA999 7nИIss3233df3.{3 ggg#!!Av[EE,,,ow/3gJ!&^O?ӧ XZZ}rrrO]gǍT*ERRN:H ;wÇaffTޱ233zjcƌpssAK5?GJJxx8q ٳOwBpT9?oz|nmtQ;v<P(~<fff:1Н <ǟgРAR6Ac``;+LL m,[ /""##111x3t= QQQXp!>ʽ#|M|zjyi===am--//Ǹq뫱5̽6eX, P``TGڃv>˩xl~õR)kZVwFN*,,Dll,n޼ oooƆ[occxx{{#++ t>I҄{o75e ~cb⻿s@o0{;i(~uJ;2O1;Ԗpj~1 r`=NmyePgv6 "?@dGԳWq*..R˲ϗ_tڶBB466 lݻwQ__cccfgff"77|>!!!:&觮Q[s{q 7(x.pY[{>]SS֭[kbժUXjUKL&éSObԨQo͓D[Z۵kzӤ ǜ9sO?QKU):uv磩 _~ΎI.iN99IM 3o;v,! Q]]L߿G9::'lll܌;w` IDATZ|}}O}6~G"&&aaa˲{.RSSvӀ455EEEo>7`K,+֬Y+++<Gbb" 1j(k̆ GGGܹS0 bbbWWWIII8tڌڴs/+ <:xVo߾LĠ7o\\\PZZ>L6/ߏ$xxx`ѢEJXn}yTH600իqe?~< X aD9r/;D"ꐞFa͚5\VwuA\|YeH >nnnD;wpYξܼy,B,Dehu~',_ .n3mEGG#66}r^O?...JX͛7F-⣏>RB5x`H$Z+k׮!88Æ ðaøc^腳"I쥲'3:2.{[ n2Pmw$;_5c!wTkI$l'=Y\,Bab`Sn^3Ƹ12iw+uʍC33m,oe:\.}a=:ڡ]X u{r&EW\(ǎޜDBv#榸[߈4i~Z&@K UQr睮m0֖pEeTw2SYw^Ki ȫr{5)..Fll,v___ڵ gnYff&fϞNC"7c nL&ݻ;/(~?s "}ec;z5?wzD=Y8r?wƟ(H\֯wtm"z<:|̬ǷDApao"J`>w-O-iA'++ <NNN=+w=xwo[n1_m۶)%''cڴis[ 憊 >ƍسg⸛;v,% QZZ!C@$qsF[Z1dH$Nwکs̙3:hz:u*jjj̷ʅD)#/1]~Өٳ1fR999ppp@XX e5œ9s`dd,--˗wx,```[[[XZZ"(({UyW_hjj9 HTn `7nXEaa! A!(([lQ `@,#** SNUTWWdB,Q< CuuRK$aɒ% Dcc#򸛴q!,, 0 2eCCCsK `nnތ ̙3;-`jjaÆT)! 7n-*beeŕhK,#:: 둗[lQדa̚5 նܜ{_|UL;ccc̟?_6***@޽{U;vpppVtlܸKѣGc٨ڵk/=ׯ#-- *gy8}46o x饗0l0,Yk֬ H$H$i|G"##rJd2lܸϟWΥM6i5tPKxزe N:cmo D*7+A1M*F|i}#%(m ^c۾+F$GsծcŠ@57ce둏ފe)q##χ6Bj&qxw'<n.3!NU@ ?7t9gam y<, (#p9Vq:g<!24h8&cT-sr6:8OV< >xajdBq/SUJJJ0k,ڵ 0  Scm%6-Eޕ]g`Iz-V~' (PoI_}/ |cYx { `l~?+n+U[s~ߨN144˲N_ž#GTVQFq?w(2Ny%hC|S K,sNJű{{Aޣ7@%K _~euݻ9s&Ν;-hqfYEBBz)DEElSdu"UY^~e }nݺ֖4Spqq>.]QU000P̲lIP[coG~"|+g} Å sNnĐ,X̚5KW;w.LLLb޽JA6@+++:t(}Q>:u*ƍgyWV{xgp۷ 544Ő!Cp!}%Μ9TJv ˗_sonng};MYcX٪<ױb DFFgEVV>Crssmaaaxhll"""/(m3`x<;|>֭[4, a# ..&LPlذa=КL&Ê++W5'Dxx8 駟rƈ#i8|+ciXOLg&·6K5Q) 2WG3| 9c#~ɖ__oU?;߬ 74s]?qMKC_b[\*.öHV"LpƮl}>I^.8SIȐV\`%^x>υ!>&k{87y4lL8yAVy<[[`Xm XYBdh3!mal` Ҳq:KVYYfϞ]vqJw[U-L=z{ZpjKڢ[:W }Sg_=HGኊ .6Eqlu}D;p]D"\]]!/ShnnƺuaXre 5;NSO=ѣGwxU[QdwuvN&MРxӕeO|{aK y_܊VY.HԖrJ;wr?-,Rj!b=|#yGy]x).]d2\v 7oƿfh˰|8qB庘>~ae`۶mJˤR)֯__9s&W~А{ѣ*m *A%###!uVTUU+'|JTWWc)JsM2cG=Hqfd ^]&|-͵Ȫl{}8M>1U)H cY g[g|02-[t>mO*=]J^,aWWW]6֓h"+ms8%y,MtQ ヒ:tH%sJ񸽹i4u2uADEEa8{,22a,ί{Ov#F?أmDQVQSWEړ͛7㭷㹬,\]]UR`Ϟ=J> '8Q?eN}qǟ_JT{{{0 TJNF]̄L&ǃ ,N8ɓ'cرkp5 D)J,1`ڪ]ߞ&={шDvJCˊ6YZZjl"ҕ6iFuùĀP|\p---x"F9r\~WoZZZKKKTwR}R)$_m͛7~z*oÆ üyP^^?=[KP.;eYܺu +W>ÿ 8#F#+>R;!uo>Μ9Ո҄$$$x7USCXj+Ae$5{8LnT44bo-"x^Vu͸V&[a*mTs\p qnjK-/\bܩ9,y > <wf5YDXY No} wh,vm`J_'[@sIƦ̌QQsAzPB***`ee{{.9ۛ߅P]] SSSwmll PWW28n{9\]]!J;A9RLק=6իWqDEEO?^QQe0a`ccteY[6mªU9r$v^6H3f`Z޺u ]hǏ@s'$%%A&)eqt5ǏѣG{tŃav "4YK߸+^] )U ͸{.lmmaeee(//DŽ `ggGy<C)GлMs)t\BBB1x`ݻ < pA9hSDD"""=vGYnj[˻Ο?x W<{JmzdG%([{@[@_Gϡם}v' 2;;֭S 1  .Ε6l<==$yFYY^~e̛7o:DMMM@!xU\~ׯ_$ҷULgf#(:*{o!ap.jT3|,aݍL4Gl#ٹʁZgյtÐÁhg{-jU;_Ve5Rx&Vx5Vr]Dx}^ݪb8:mQXSwLyxG^|;kܶ ɥWBy BCyj^Ϻ²@eC.EfyFχwt)mooxx{{#33SiN8޽سgfϞo>W;z+[P-řVNbGv ߄4704}y,+C}ŃinEsCm2;(Q=w@ 5~Ŏ?2ʨC{R)! !OOOB \H$@*vHNA7@>P]" 88JZsEHyj1gu-m1 %Ka˖-]>Nkh=a |7oΞ=ob 'I4%CCC!33koVVJKKݔe)3E֯x+2:zu-E;7@9vZ+\M*?Rѣ eeeTWWرc?~<}j6#%(ϸK0n_`/"kCS/>`=eu&3j3 |-Su?Y{g۬(*'nIXc!r_[k/nnn*N;HԔ?R׬`bbfC^^~ccc#G~p?-뮓'OC AJJ WW]x~~>ث&Jwj`"[000P,Ν;'"<<IIIAZZJ↷(JZ}+^]yhи ^.Н}x-t5ZNN֮]˗֭&MY,[n|[1118{,Ξ=f>(yot?H(vCwPz%9hX A0wAE;|:m`& Pbr&Z r;eikcj5giߛQdusԙ 7OZbQժ F|e[ߠ?O%]YV)vZVِst3ý6imX"C $^PݨHGGG޽XJfž={ɕ/s%3f扖d*!MfKF<-57z~$ tkXkMCsބd(XY ;נ(v>C§9Z:e5[D F>wUa"JQPP'''x{{#??"a///0 ,ntobY dk066Fpp0Ѐk׮z Wc˃3FLŋ1a玂o#F/֣7سgƎ &ѣ\ਡ7nĚ5kǏGZZ䄑#GB(bӦM\'XƍyfXYYuX-&&"6zho$m.]NNNæMp1SGEPP<4pwwe˖v/x1T`ii4" ~~~prr¹sp!n}w_9OFFFq ܼr`x G7˭lCTપ*'O_v;U-u 4<%dSRRTPPsW֭[hll@ @LLLtpbcc1h ߿aaaWM!##>(Wm&MOQEprrvCHHlllL*(o斩+3($ J_&H쌪*ܾ}[ gã{M'ݝod! d'*)*8puOF "" B$dOgG2MW'I'y9D[F/!Y (nV k ƻ9wbOyDŽ>SHVWWcҥرcF|3w8~}R\ϓ#kH\/ ՌN~7̈́᱙Z_8 H/:C ޿/ Pu֭[d ߏ5kӧ9%''sN- G6Vlݺ<?μW'Ĺs%K`՘>}:3;`R]]mT7P)Sχ f-T*ܹsٟ(tvvbϞ=x'qAFGG:u*ܐ7|/r9MS?zd2̟?5kPQQ^{ ^͛s!==~~~1cVZ|8ux Ci/"06Ukuzłn@w4&ҥK3_.6o0qD,X@'lٲ>{V) Y"Hg)SZ]ts3-n'ؼ%% 455=M,X$΀ s1_8<$ V\Ɏ%##W^ի1zxlzDs)++J8q%h4DGG#,, 7nkuML655/_Ȋa+W{xx`ʕy\[[;ciPe.\"<ЙEk `ccFR>L֮] +++tvv⣏>bKR<;@(mqFN?{,agg7rИmoysewZ[9XR_z|7 x< 4†RQڞ2+t6h3U6ߛ}K:8Vmux|mO|$c6/GI/7VwoFB}z@o0=^go%g\e ڻs6!2~;Yi Kf9!_.üox{0AcE8"|>cHhJxL:jn\R,YĨ,Xd JKK{@>gD݌2z4n:=V2D=W h`Fj}T*>Ԗt/<-G>+&5BqwDfpL^ǶA7So7F5هF:VR䠳666@ll,&NDL0dhmm˗az,/^Dyy9|>1n8$''cHNNFLL <==QQQ .p6dX"Dvv6<<2Z+\ek2/ԡ?nܸppp*++-v'A~b^n ///<䓨DII 1j(=ܹsÜ9s"x>>P(F/<O>$JJJP^^B???Bĉzٷoy j\t ƍúu됙 FjKb֭XnK/`{۷:kcbڴiP(N 5ep={X ?G\\?c=^^^ \.gӂ:::_ɓ'Ga…lC ȝMY7@3a=0mZo.j3NqW ~.@c)r1pqD3řڥH&Ty:o'G%\`YlDB|6#WnգI ;9`\uܒDKw~*~.fJ?; ZЦRc=>pfH>s| U٘1q8^VFHEB;a Do=U 6_ÐN*6J [Ĺ;#z>tVo_[)^B!:տ-Qpι)8Q^XK>{1(lTb"LtFg^Z&)DZu 4`>tI)#o޼%K`ƌ}_LFZY5h>߸A9_^zM HOK] .4$d CCj`@G執{[ill˗GGGrD"x>S曘>}:gFǏ뤚dl2L>'':=դZ- t|gs@`emɱzT*/F\\V+WѣX~͛9slM6!$$ .4ݻwC*bڴi }TXXJEVӓhpYݻͺch4{żyڦ&9sVVV***BII |||p^[E^ìY0i$NL/c41vXh4α)e~|4,ˡP(g҂z7o^^uuu8t~T*jW\W_}GyDi˖-hllDbb""#[O^~]g+Xt)"""???joANNÇ ǏGll,w^66a=눮K.7 h"DEE! Z@PPϞq 0}tL4 qqqӣm߾۶m`PӨP}@b+Rڔh uXnj.R,A-lҥ43{OA:GO4Sv䑳x;9 k z@ zmz\m$0^uMxSx+9leqN(lT`-I1uPYI1d{yuMZ-r77p}h^C@m{w o'GcOwȂf'#*`ާ(mn )AyJl,1_VMlqk$FMj>l.V`gN1Lщ}WP 'k+HԵw:97pV^^>̥;ܹs@[T8_?\3 gTqT-% jzJD\7ӮG`b&|F3[!5.?x JOL4o쳴lIբ } =biâ@`ƍxgh"?f1YӘǏ̤fL>@w&M$w>N= a:T}cj9x<8;;C"͛B@@^xܼy/-PYYirJ "P@Ű-J%jkk# @z455=ŗ憮.כ=" |>_t`c988͵P>?MMM+jjj􂰽y 9Z;;;q~76ZD444roS0BseeeLH{l!CթBSU3rQxEUc?N'bǘuӲWͶ>w6Ŝ5C6pƍTX~=o18&Mxh4ظq#N>=%dXoL׮.b|Wz=I(F1j<Az?8bcc~ٳ۽ދy!==3`x[5 _5{0qDlذW^#22:;֮T*իQZZݻwlD!d`(F/V3NQwzu(q\UdӝV28(62cTTT̙3Dii) CBB;|vv6>St[d ;}wt' B̦7{`Qh(i;cزeZsww+N  x3 x H$(//tl0o\_r{衇P__?$ (F!C7B;QKp4j*N"}BYmZN1? ,3f9stWMyy9ߏrH6lx#ݴ- OYF4iJJJP\D"ߏѣGKH͛8vLS￷tJMM ;vG,pqnB!w,}ka6wZ} B!B!nݺGGGd2HR/!T*{ Bx#B!BFzƵl]]ZZZ J!H a/! 2V Zv^Ҙn;!B!Bɤ zNgm5 ZZZ! Y[[ښgw_!dd!B!B6\f!8`+f)'b^ָ7&$3PB!B!dm7Cn_AA8B1cSB?!B7B!B! 2zB1sNy(F!B!ba nK!d` zҔuBF B!B! cJ-Iu !L y!B!BC ͣn2뭯Q@;!B!Bo@[_#28Bτܙ(F!B!2|m>\F!elnܽ(F!B!2|c!𙈙!C| y?[X [ BF65}"B!.P*. !ޘkzsp2z 6< j-YDB Shp#@7B!BRx#c 5 cID6 g?\ڂ+ſ"w"mj8w.Z;-]lB@gt#A7B!BRx#b#6Rh5ZvZE#(F<`nÐItص8rt;D1?1!لi[q!ghAMBP!BK ;D"aklllP(ގ~o>>>prrBcc#rss.Ý@&1:<==%%%fYH6uT#--EdvpX98jh._Qoood2ܺu vH$;j5m;CDpssZFEEŐl t}(Ks44 ƯF8@IM.veuy. ;;N=z ĝp C]BA ^t!!B!CǒC'@jj*Р3hnnFWW;}̘1XjЈƪU0a 228}3T*իWͲNT/?lu`ZTVVٶ('#pz} r.a ZRQhx<̞=˗/Gdd$BBB0n8r\~l۱ggg_cǎʼn'm;<Lxꩧ0fkjZv̘1D2\6<<|>jz]]]o孿?rtRJp}W*"00Qu2Z-PWWg666=z4\сl鎎;B!jkkQ^^>I!BZ nnn(,,|`zzzDWW~'=sԗ|xB ##cȶߓGB%6Ef~xh4#nr^s?G}(**G}dѲ V-[`ժUVo\ƠK݁_Ƒ]&2KFE:ݏ)#1xϙ9ŋՅÇɖ>es|rssuGFFbҥFoրbkk✧hڊRܸqC'HhI5}݇|mH$$&&"""3ގL\|:[|4 ^xi {x4iয়~弽o.\x[v-ƍuVرs}+WYvEEEZN<8Yf˗/r.s\rrrw믿矱qF0f;bdwqqe0e|嫫s݄B!XP(m.)))ؿ?Ξ= ܙsdBl޼=.] V+W iBc9nPq %tuB$+cC(o:PSLlٲM{onf<|>bE"x<n܌?k֬ĉj?m w dпώJӅWF{\B?=dz!o>;w[,x|̘1paeT*U^j@ #d2BCC{}X1,yGGG}a~Iiooocǎ<kkk@kk+T [[[( o&zq54О?z(x{ǹD:Ӓ 2j(o5&7=((_!H (\.oWWWjyB!Ba˖-Ƶk̺^&AvwϑyfZ ˖-Fl:x!?7.,ZJgz (;Rh7[9R)d2ـe$C ߒl7;wu޸Gᛈ6}2ǯ(E0;ɖ>u+4AAApttDqqlmxw`}]m|>NNN̙3!ɰ|rr:ujǰ5////ġCpQ6Dŋoc݀~H$ꥉ쩿5ĉqyT*CHKKCss3r9f2mmmlj ŽW_E]]|>\\\0w\uYf N8aR>>/nz-8qBXb .4[m[rժUlɓرc***  d;>!BTWWCzLvb^92Eqq1>Z ˗/Ƕm-NOaqu,Fϡ50>VBy6 cǭ[p-K"۪UFlϯ΂F)nֿp ]nl{cAw@ф2WY@n>WIƏt钅K7F~233h"cܹ(--31X5ԄYfiiiPTC=Bj( ܼy5j,XF[zͤ lcÆ xg~J(xĉzxyyaԨQӧOLLɓ'Y"!''N‹/2݋OϲtvvbӦM&EVCѠEEEx7Z}ܼy7n <ctvvbŊ3Ma7}n0x<C,| n޼d̚5,xVz1]DLĆK9_'ذCDLD~ Yy[>kwj=z%H0f!O=Pؾ};tenYYYx7c`ѢEz J[lo==<Ǚ3gq!B0WFSS[VpQ̙3@wm:J3 ݁gBRA(bغuIQTvZ@d勋@٤VZc{+B!;رcボs.#JwwwX[[7\'Nb1vt6Ç9DFFBPe6mD"N< R 7 8s kfΜ _~EرcÇ Ǐ'lmmqM`7GGG rnիW .oee)S= !!!pwwG{{;JKK^{;@{pAv&L'''\|!<<pwwGKK ***p8;;#,,L/ϑ;p477#&&>>>pvvFSSJJJ{Us)++իW㡇—_~7ROj a3=Zt_~|^x< AAA()) +++DEE hhh@~~>7 7R)*++qu,7i$d2ܸqrttD\\֪g} UTUU>|8::b̘1򂽽=P^^˗/Qoy ZHOOwoǖB^^yOzj@⧟~CGW\ކS7cfRL I!bB4GEbRއa%=ӷ_:Z=$\p]kFDGGcԨQpqqAmm-q%TxkPRROOODDD@.'Nйv<<q̘rdddp} F>@ @iiy`qi.߿˗/G``:c􄵵5둗,s)!.]B]]/_P\|P*F___x{{9sGcc#6l`rlTVV ...y34uuuQjVř3gT*qGFnnIe`6ψ}wk ɓ{n;x<\PTؾ}Iu荕R)缶6h4hZ (( {ůj07.!B!Upp0y<1yddeeaΝ:7ﱱlc=Yӧ1ydƍ:|>.\tvvիzAs=hnnf:99!55%%%䥤ɓ:7>H$\.שIݻws銎}+++vZ@@YHT QQQ:#""JAAoRSSt6~DFFbѨ{!ɰd~kT*/ }\\\ OOO( ##** 'NG}%S71l'VqSn 2>9>95 8{lX,[ BgzLL_X=z4&M/b׮]1!ˑ///␚_~z入K'::ڨ,ZH>IIIt^}k}RRRto#..w1o<6pӳl:c|s=HIId߿,> c˖-f Th4:the4cq,k/[hI0CB8 vNw3lxq|'O0Ag#GVz;7ܹs999!%%s3tsp>kk+0 ';ǏG||}`ǑEEgZtt4N]vq6bajj>3f RRR?Cooo=7@=MqUj#--Mo;}۽P(ٳٸMO8qΐ[y Ɣ)Sׯ py?~3Pi%7F\\\ |'Ѐ 6GѣGtR@BBfzxxp1!%%@wuӧKӦMcxGaa`]]]8<ԩSXr%x{sM/..FUUڵkvZyه_|^{5|HR,[ ˖-CUUN8C &!B!Á\.Gii)~gC&!44ӦMرc!go߾bk֬D"֭[Q__onnFvv6ƎP[`` llltBBB`^zլ/PPPݻw Raaa:u*bbbֆ{eҥj8z(\&xzz"!!nNNN8prss///̜9xGӬA)Cx<}Qxyy `C@@f̘ww~;((P^^>Ț3g -Z92 |GXf ~a콱B:CCөߒy0uuuw߅/_V9}H`kk'xbYYY8y$₱c:TEHH/_fڵ͎  668vHIIA`` lmmzxEnRرbWD"mtGژo#SesE_Q__;v* 3grJlܸrҤIB^^Р L"`ڵdz*N:jaĉǼyfp\5'''}Ԅ '@HHBBBDs3^YYz>## ٳGg33gH7sOqEܹGjjj͛7Q]]˗#>>OFMM v… ueggƍx0n8j鴴F~~>֮] OOO̘1|YʅP]]M6f?bݺu }I_QQ}iSO=ؘ5*wWUto|U/0?V2Z6{k2sLb\t ;v`UUUXx|>Ν lT׮]C}}=֯_ӧBMM 5""B/M IDAT푗”XYYYmb_]^Yq1c hZd絵5vttDRR;fIϥh`̚5 }Q/99W_},N`zJjrł0%~\,8 -zoLbowFNfW9X_34S)犾TVV ^x wwwpD";3g̘L/\#??{/&Mٳg#3337|Z 999Xr%BBB0|{هJi3cggg<.ꫯ%}aܺu l}qwiҢ<Ν;q-$%%wFII t}d dn`y 8ytB5k͛7#''Ggoۀn@wz f>>>lpbqq1gzC}HIIAJJ bbb؝舱cǚTB<&8HLLd/VJ%[mo-׾K_ׯdz>zqgmmgy,G|sEff&y[wb XbB!sB!iN888ՕOP H$bJR4) 1jkk9&fdd@[@@QUU/NyOj9o滺``cL:tTUU#cUQRR¶0Y0777nw~L2es19_4~ {}:R \ezBej۷>>>:} lmmŋ{1ؼbb=p,LiLs xنLo@f .?7`/&M?Bs0;z(_?!!NbTn L:G.zG;;;[<&h N2E&Os39#''.]'|L///xzzU˗q^ô`1zh m, Vg&<QRR?#>qssDzg·`[[[!Ɍ:9::B,󡴹 1 w H? brrrs}SoB@j4"::...&elu6]v!1uuuCig]MMMҹ6wS .==]/6ÌmH}}=verYּɓXj؝jqqt>MRR>C.##iӦA&GƍZ3;.\_oM^ss3و9o+777nĦM3!BnV=[ˈBhh({_ô6X#,, la4}azdnk5J&~P>d2X[[CRY,u ޽{Ҕ XiIsєwZQEPPNjp>i 㩡* ihT,JBZZ]D"StZx<̞=ຏ=> ؠSiӦaڴiFdztRcSzY\1P\wb{saLQSSwwwkC&44}ѳg+++0Xcqѣ  kjjPLjNf 544 == b+Wp٣쥗^\f \.GtttK$&&SNšCЧ?mvildoBػh9( 4r{F !BΘZƍhkkèQ`mmvMUtu#44bAAAf/VVvf`d!``2BX[[cs4=n~퐤<hg1 >sWǭЪy!%CсڌSzK.zzX5//Og{.:;wNrV͛ P0c"Ќ)ӧu2q1f,G؆3薞;vPmǔ!#^>W @}Yߞ + >ώ9Ͱsakkk c/999HLLIY=z-hѣGsttdW())AZZN8u\]] >#ڮ/e6}9@ 0Kpv=n/_6[M[OW+y5ǶF1eHu}g,Fƚe}>d3w/^{J,Ώ;]K ---hkk5 c/7n@]]pB;C+ (sČq=ֳ٩S[2!!/={QF{uѣG@wTCQ|v [fxWꫯW^u|ᇜÒ%K8L8=S_777Xby}Tu̎9spe(ʸ=Y. ,^EQF,?A0{PX4ﶶt,\pT`_=[q{zg܊nJW{6c"]u<>7N\ gu\:sXIKKjl˳s|eRRR())FLgbRRR wpx|<ϞbXHHHn ^F| X֘ @7x:r-!sM\ on;a׮]] ߑ s455QQQcyx<}ۇM,g.Q555ܹGyoۣ 2^Z[[l2$i@'@ \ 5; P믿<ǼTQQAoo/%%%̚5!+++ꢤ3fp8"zct:QUׇ-ʲ̒%K5"B0'?ɰrz|3L&J2~9###B`! %%%Z*%wF,vQfE~ݺuC E]ٴiS(_&I\s ek^{C Mu[^?3,-_|ȵm#hشik֬a޼yaǍF#^{m>JAAArߏfΜ9̟?59V.l۶ Mbb"7|sX- C]]&;&EQ3sLz!p5א?Gn駟x.Mn~+gތ".8,ɬ*{]A{fF\ 9"}\ju]CB׮Y)S> ; k <~?Ϣ( 7oo17  6XMD>{N#33ŋ2c 8īBlwK,8{a0W-0`<L6-Pdxw<<Öbn|~w:|A(,`yx x≘dی'ND1HF477O;@ @0VeP__?ŋSSSi员e ,]zkHMطoWFUUv@ 믿έʲe˘:u* rssILLDӴ,YBqq1JAA6-6nH^^III|_477FNNNL1|k_ɓtttI^^\l9;㡮l/RVVC+x ILL ^BeV:*JOYU9u<9=Dc,YBZZ<eeeNnnXϽk.f͚ԩSg>W\ӧZ Mi&&OL^^_W(//EBBEEE3 zٷoK,!!!?0aa<\eƍ|dPQQAgg'IIIQ__SO=>]wE^^> ǎ mHMM7x#,e˰ZXVJKKRvl޼RLYY(BVVSN6D$I!>̟ ap{?e57-®_snψfSKʤ|UNj/]ζ+s17 戡\:Wsx)++vGzz:.~lw͌3())n+J~~>'NHKK_:uuug\Ea޽U o!X=In{8/5?ξ};w.6l`TWWvC6mzV=_XͺuHNNpH__6PTlַ5l@aM)RprB@ @p!yf2336m-ˌ3B1#b0XhwW_ y lذR&Oɓzرw}o|Q QE7HRff&"@ ) $>L&4M %W EzP /$df3#.nذ%K[oyft:x<b,ˤhiij&oovijjy1np8Զl$%%>b &2>($|GDp8蘐{z$%%aXhll68:Mgggt皋y1v ś#/۹\ 9"2&r-goHDJJ 1{ j%99>T7V.\[nǏOXϳg<9b!55>:::bJ5QB$nMƍywր纪8N:;;'@x @  ѾR__Gi&Mhkk KD?|>ߘF܍;ݣ |OOϘv8Gsbb""nEދc}!Fx8}ƴi&z|Sr\D0S'pYc͛ٳGSO=f$/|a8޽CC_( +֯_򈌕5MuTmXHtuu58p믿iӦFKK<{Ɠ}/ ~F@>c\ &@ !A=z%Ovoܲ^rR$$xnǯ8Qns?4O>z$]cE1bt>'??TuL&1yfZ֮]1ߙb!))>Ϸ8F@ @pQQXXHFFǎM0,[˗o}c{W 1ɇ[yXɱuVm6zSx7zyꩧeyTu>YXd gfƍ,y.E@ @pQ1|,XM7݄v3ό:@ i{*o1Ƅgqp!y|vvyw8~eLÛ@ @ \֢Ǖm|>{zWb餤( ԰}vM EAGG{|rQQQqEG:b)33z@ 焔@~@  ^.@ @ @ @ 0 @ @ @ q@~d IDAT@ @ @ 8 o@ @ @ A7@ @ @  η@ uE8/Hi罍K,B&)) ,ɅOJrS4}:;$hZ^EЏ5g:ƤL|.:+oq$ Ɏ@η8c&7@j#uƭmi<фI4MY73`CI4-oi۞u&T^= hT:׃|p;zrXbXѩ|^7h$`빐0 (j/%4: s4O0{l̤~LȲDwA[[7Gt|>@j1E0 @ <|NA{[ uc2ID8=4zFJJzf}VtZĄ'.l!d3p6WQgtqQ7Z@ࢼ&({v1\]=Ht#Ոt΀R q(X+UoeǍDBC=[!g6R!Ʒ }}^QT=Mj$f4MC$LBJWS=.fRet6zdD >}7o^UUz:tH  +'EB7lh<# sz~OO{򒬠3''ˁ۾j {\ )I2:=bߏ5|I $YvV{xdbB UɆ!!Cb:Hia0䢷^G'}-Æ#|TdC1ؒjZ{ܣI^k ^'jDC~~WodÔ`uj=u1d )lH:ׅw[݈!oe`x,u.TcLFuM~]ћU=~O_(t`N@k9UH(>ט$Y&-L>/ t4Ԡ &[p#Ώ@z4,I[h;]xbS$ OϲF[Nzd%BMpt +G43sHE59h>ƢH fS݌6y>!zCo'=ԗB뗧G? _ڽ;7#I2@}$JI6D94 ͑;w$za[[:Bi`h#zwY n()ɣ6,E@ K_hu1=ق磽yL f,Zcn~q`t u}_axĂ0Ŏj!(]Й(;n~džFuS'H YKn"hq(o Ҳހd#0¦Y's̅k:ʩ<&EodB끿ӲS>L)_w?5~,ޖBXvH$,%m(ơ*i}ͧN=6E& =ih~/oO;,G=֜d. n, xhǷEgJ%|8e.S}j=MѝX6Xu2}ׇ>F_KJ0cQoajcĶ6*uȱEgGڂ/9pYKoxȻ4zqMH)]w;i&'vDz-e[{9WT|9)i}oX4K RZ,krx䄖z37u߀kn䲫6aw6Pv8b5w+4U_>}YQۿ@o2sط/C\/aNHP;Cb!kLm3rnm_bö1VD"RNhҦ!6Ewb{,>EC^$Iv933 EQ ҂ץ(_^A p3^rI8.ۻZMx<^ʃ߯Z5t '' hFN>_ň\C LLOax@ .q.EzMѴ ySQz;銛jK -s5k#@Xh0pc_|<qJƂH:Y_XZ| +zh3{҆ȨYQ#YϔQt <yk4 zzꎃ$a.!{٭N7B{1偦[wWW: {L `Nϧ 74.&rZ=I-x8NF'--hŧ7ŬGI^Y/u'x]iX2`< kv 'y_P$͹E7[{Gz1$dRdÌ!T|9+?\8K)9䮺=_RW pׇ>RQ f^#km|:zpFRtX2 S$"uxzڐU:s>MHs z ' (cj.d̽sJh>{ǘ< IѡSI"oI2jUȲkt i䮾CBMQtPIcJ6e8ʑ)SɜO{]U7%h9sFJG՘)%\ F|vڄ=-ً0tTIWZSIS1tJΌq?_C^sp9wBIAϴ\=-SGF4ņ-5XGuTUdEPv$2f_{ஸd$dg; YP?46-=t7YЛ,l< _} 3<FQd~X~1~ dޛsst+opmWD<^[ۂѨr5#rJ<_KL禶I[Vxq<:t L</Iy_]w&@ \\ /}"%={Bp}Cݩ1]Ud:N8_XShA/&)Spȑq3g>!cbft=1a&.y*VU:(@wDԡ$ɡrPf oIKH. ZEC9א, QYy;iTmA^`51)+@k?Rr/Iŋ1OƜG_kTIŗ荴9az>G/WwkHrR5g>*Rg$o~_ ZVv~Ow+~W<^cJnIyhb)|ȊcDn')5$+J >B=} Gcx83Og#y/Xs[{So]xȹ'^xuUʽ$J?#d[G_K y gK 9 e*xF5Xp=,~l[0cr6W궧=Շ¾~ћȺ&t%%K8h;5tLRt^y/'kэ8NxrHN7RI;OyLPrU#)+>u 0ɓf=-CHHU_dmh<:nOd mDZ{`  p*ҷ7NXyȲWpp -| zt5([%+>? oSJXs7$üh93&UOuQsh$=vv r2&vsa&gMxZ&*NAKKN #cLc/Do0xhn F#ySd _W)**vEk׮[oeΝ<䓣kMUU9KdYkb^-:^qˡϳ;d͛MQjrÛ,HQs`ҒOFZkhCˁM3 +]dÕ9ԙKZnş#;kBb\|9m9Kf!l:OF^ЍSQT#r^;:I?3] K__ ۟IlP$;G5Ӝ୰2~w~#&_Io>F_O' eȟs9W\ávZT>7*:Tck[]y7Yp~3rd˫@k-:ՈdN ?`tm{_z-NM䖗a4~(4(2>_07@_⥈)~EWGlĉZ^{m~0:qKCa-ebÛ@ @ps1F`5qt*i}>.z F$ K):j ΄LAQt47?J/TU礹noAqq1_җe;wڵk(0F$Dvi0ǛlB=Ciba\(r(>h<qLd5x3J oD3΍cr<:Nl_ڏCQ(Q W)ӗz]-tWXf)9$u#৻r/!e vtG}Yv|nU`*dMARHαZ_j0r϶P~Hs/ǖ x"n{>`κ[)Zo ˷M+Ifv`R,I)>wlt!cxK%4F~3]tÚjKrbx+ΜdJZT^xGrr2)sr}x5,ܜ9sԩS@P/>$;[T$I&sdIoY|9ǎ-t餤y:X8ʫg6wD2ax@ .q.[Ȋи-Vm#cR.MӨ=geew|>B(bdEI`w?~<|+_Aev1rEF;3~qtPBáXL&SL=jƷKcZmte:;;Gx۹Pe]ޤ`hJ/vJgH >+/`JpQ 9ȊJ_S6\mu dLX6~Sr̩8H.YGYԏFHCv&EVT|c󄎗NƍGVTT-j jϝr6GmVȊ1)WGCrR}3QFo kh78ny-Oʆqg)Ed.ˤ"ZMɏzcI$dE7?[n9u\,9=GCyXW- u8c$#)*"`rgGѩTW@ވT-t4DEQ8=b%L__c+>S޿dnR0oy|ؽڈNFDGuhƷ $K"DMo|=JɌh6$qw7>mƌ|A*&@ B\ /cAQt!2<c #JQ),P7$T?X^s&Oh4&2~o~~ȲۇvZ/~'&>꺪bZqݸQ\v.9XG?HƬ(3Gbp^.ˑv$YƜ9s攑UQh{R[7t$t,]oo}/K6%80:d|LƂk1$MStXx= shr6Ͻ&~eYx蚒&&ȌZN ^w}N$&eاG5۱–7+@OӸQ;prt|rv!|^~iN\ +`UhwQB=Ŕ,+S#8]I!1#Φ`h) V`q;{9k}DsB22xdX )oxxmƈ8psUV7 q˰;i8P\LYYYX;%%%TWWPax@ .q.[a J^OELF,Go-MaeUE7H42'咐|S IDATcC( +>ժkjj؄$Kh9z{'Ct- Ebʁ4@0Sp11z7)Tfi~> ̹d沋PFyY,ty#ƻ{\l9%֝$RgFQ }oԺx.|0lxƑNCA'ABl$}-x]Rr1$s-5|#􅮓/gd֚af>T3k-:eNtWI:i%Hl#/T?s{o1o?j@P=)4VHMe: {Ӑ /ȊS1nF.ǛD5l~d~y> Wo f\y=f`W=2([ ^3T^o4uW8$*>-hif2K6>!M5#c`^|_hIll3m4&=&4;|H)7ʞ2//EZ[[q:MŊ}ΘG=-v|^5\>W;~0ő|8z(^w$ @ |N,ء`ɜAO@aLZs;㥬}{<!իCF~rbE"H28pmtIOO>UUzxco;;A5z[ 1nE$tL}8ʱd\޺rh?>|XxnI`Hsx9^ FoQ ɓ0$dsvx+ޤԁxȁ}-5B g<Mu'h3tJÎ.qCےE,OH;S7rx W Ȟ2 [J1|n-2v4--{(]}-Uϴpf =gB&Mʣ0GDCK-.亃Dž5w:$Q1gӛ{CÖ[H҂jCWWIII![ެEt6Ԓ4)Go{p>Л7 zo7o^W7o of":::~0ncٲe|%@ 珜Ʌ@p`CVFKW4֝yG[@FVNXAƻKVikiđʖLjʲZ)//Gn&I{/<46Fϕ3QXVRS^&Gv.9ĠN{1(%ӣ[j?"lgW,YE#tǶN:֪Aá9!WG,3 Ʃ}qtDV9Ѕ']tյz %@˩PK>u7&{RԲ[':?;hPʶ {EOO֜tIljtU쥳|w㷫b]UhjjBezspdi17Ծ6RVR?jvZkNPv(Tb]p7@ @ 'b[/G7is ktuŰm^69F`h`"V*++yGz&Ogg'O @g|Pk3޻j$o[ C4 'L*"g 62)҇mXI|?&$9<ؓ-#utUAoOFWվ ox{۩{$Χo?@j4%_Fu2&])7>Ȕ)7 e`,GI)57b0i٧qς#] f*"iڒ2c(xxdd]~3s}3J!+;31WYd=03Z)Կ3Z x]{l"Q=lYˣg"_'i1IVX/ 0}:dK +u>0&2xNLB4)3d ;nOd_a_-1!C;:/ǏNR]}ig<6vX&vs{i}x߉uװ\~zm?׋ɤn7c1lFR'i ̟? HBB,Vf{VaX~i- ӧO磏>WK.e'@ t { tuG,vp}A!Tz;q:FZP4YL@xRUU]wu!/Ƚ XK~KȪ[Lt&_!qBɓ&T]/4m1WFʌ8*P- fw9hh# MO`L^0Wh4桷Zh;Έm!pIχёP0OkwSÎ@~sSrHl %H.^}Bzg͙Ng#NJۿŚ]!1{er<'? j]e_8*3ٱd"N.]AԲ o˴OS{ZQ &w;8Ko4ZN'k¼\w?kG APԅȪws8+荘RsCp^G'U앟%cuaߩA$cuκ2OgHM{6b|ɥ+Y9~GIdkt GW}!~(^Dp0?67?K''7qj<}N"13]/<ɜub0G7 2%,[y ԟ8AB$R'!I>mSux\y7iq<}~/;?vݿ_ y ЇYw#sJGC L $g#I1ujNl4:@3&_Mï!4LFŧSttuOLLYe;Fr=~hQ ^׏EWe Å*t Ӧ˖D7޾o3sLqn:oFl?@ `∇(P"5U casmɤ/Cb \muzWyK 7nN<yWCek0&gcLUsF7O?DO: ӼMvv=}(z7 $Yr,Z84-]}şĔ9=?tV5jxdx[9$a< IYȺK)h`|B.Ϫ6O=HΪϑ8uaܙ|]{UY<oR>-_ZG(91Z Nt&+Ƥ534RK-؟EʌUd. k4,YEX5w;i;6b0GwdՀmGHK?$?ޞ20vSv\DWk _wH}1= WOv>; -#xv6? eXv?9Ygr{]Nlȶ'&SwK#v?r?d H%)5qKbWNoR'ZNsQ09IYy$e>׋ms;,a!)@VNTFJN<uYcngEQPFh4jix |> ,|+c:x,(}v~_cq}t;6{lnvYpa5 fܹ̝;M$Ǝyo~:ئ{4iRy"uOPƨd께eoeobTl פzg7|@iՓMף{z?yo8s;ݽ롺x{-h<{CJw&i>&pѾST;ͺ{II?D@K|ks/!!ZP㣰;]8n&MJ~~)F o8|Sbbٕɏaɒ%]%f5`Ν<׿vm֬Yq444pBJJNUQcǎfW_v?~<111$''|0Ьk op8deezW_0mFUUÇ3tPt:s ^^Mjj*)))lֽB!B޷q\s4հv{-- ޅBӉ&uM(Xwb)8 J l9IYIB%kWM%td/K';q{7jѽ{J񦓽•WNAVQWgRZZe\fZZ\:chT&9kʔ)(V%::;w[nnŸkoxG7M3gr]wnثnvh/?<0blBBB^z)g?f{,Zco6=G&11;]cz)?ڔB!B<B7Kz`[tsO00{Io?KE?pٟ=RMm0oo}Γe3P%4u#zzC=gt_鴄Pո\nijtz()$>>:E9`fv;C $\;˗pAG=h `<#f̘wM}}= .Oّ(M7n&gX;]-[0p@þ?à&-""h쓜e୤sox饗ظq#;wd45IY!B!B(-Zszf5S֣X7_W|cɹY|^7f+> ΈԒίOj3bG5'{(eqYıٷ?ḣ{ZuҥKB}?>}X7={]m? PVVgHZZZveeG>w%0uT tѩYhdȑ0m4Mw+x7X,]#B!Bq`?ѣ}=\E5~Nk^K}l$1Y0D&5G-+Pg[?{ !DWB{pRF㲸<ǘt&vh $5T*<ݻ@HHH@xn'_{}]Fyy7EQ{e]f|#WDFl6nWTTr:hwy'goaƍ0n8.nn#2dC1k,xꩧ=B!B!vXs!r6VQGGQT fPN^׎z5B1΀ڭFx<""j222PJdߟ[ >_̷uo>.\O<԰`@@d8:m̘1k-PXXn)Slٲsk4f̘@AAO$o L0iӦ۷o`ǎ466NNN> -L"##/h4t:h;v,۶m#99]xFbʕ8NΠAغu+8NQz5B!B!B!![Jo/..m.//­(l~|?vZ$333SWW+!C'33~8Ÿ@cC_W~_( &MbҤInuaٺu+#GDV裏vޝ;wnVOδiӸQ֝B!B!B!9з9u'( :jILLrQ[[پl&66jl6[st:BCCmxTT&ZB!oDGGBhE/̸ $DZݟC!T8?^vXX\i{`2x/@Rz}TUkQYSO|L(QQQqL\.JJJ:cٺpkt:Z? !B!B!B۷/g@V כNB!B!B!8}馠u NoB!B!B!B $&B!B!B1Htj"_ q:RB!B!B3.6 Cj"ZE'{9 bᶿ~LhL߭\$#xB!B!B/IԴi>pPoJ:И2Ϛ)<{cI^/HNB!B!BчJvlZ[@іut4&oB!B3(|>JFr!`Zgb^YhLYb񺚱Wc8Эx,$5)-ZZg$$e08ÂNӇǁ0**:jy=ZqQ'ơ AѨQ h#1%aLOEӯt&#Ahc"Y׀}o{~/RGтmo!ΪڮF1=mT80|^/:lEx]Fjhr\!5EgU\zh42k,֯_OYYYP4Vu|QF'$*KmñZac OH⠦d?l..x2GƠ-؎f $ytF*m3I<Tj  R]u#AYkp8hN9"zJT]>JMx\">ƪ#e\ 5/+xu[(Ǹj:]J%\8/)룠)Z D؈lm ur: eP>76D/nFf|/Pϩ7t:nJKK 'w E[pyq;[xϽIލ6hUwe/Ru`sD4)Nd Qy%ZRRkue &%w5ZlmX 9~c&s_?1fUj )oz]&ֺ*b33B"R?ʭt$~ߢ #FŢWdzaÑ5Ԋ8JŇr"Mqq _s+ !B!q).Tge&:=)8RNs{"J~jqD1E`=rJ#vy ?cl{ֲmZs$>)>K~nbp=\NsA/#%*7}4:Obt?|<5۾h7&hbG=o=VoRS4|/m<^DϺ\'*?oxw9/,DIqAo%/bcy\eԓqOM, gM}9){8=oBc';oG/_bojw :a_T2ig5S_fw8O#Q`Ep>g^mt8l/x򪠁0os ^[ R;u-j8Yz]*y}yx7X 2#FU.~-33I&g@)<=VkM_ۮh~ۃmn=̑1 yycLZ?ι6vŏ xkJ|rIyo\i5«LLwht(۳U9ηo;v^Z{nZ468ξ6ι6F]rɄ33m4&O'ǖB!` #>1Gei4ZӲHHjE!k|4#Kབྷ;n׸l.l@m߻ m?|9p\7VMF0.cpVs/Rhф8g~|`%evmgQtu҄v{?u PA7~qG;窫'7n7E\\W\q_=& ڃ=Vkmח5z0o?z3f;W/`>VZ|^Cs[k`JQ3[ &vRUt̎/>oLBcՑZ]~v펹co6MMM:t{[ّBHϛaUDħ>l,-v+MU{w*ty>U!B!N1n6#WCB}6+e_磨`Omg;V矼g +ȿ3Ι<sH(Ϧ!gvTt{ruc0 !+Oz 9ODwg n<]Mgԕqt9(J"GZ*A^CQBP P&;n M,G{kx'R EeԣZ U#/n}/?xE@QQQ̙39sPZZV:ɈxdM=q&^x{NLf?Upm Zl6߽zo%:?gV/gg)nmf}_,?xSz*JLL t۔24u-x/̃28VnOG4d%u+7mnV i?Cj'O5o1 Ŵc ?G;IMMtHll,]wOҐ8 ,bqjq\8}\>=cFiX/k_\.<6;j }|L.6^*hdM7믿ξ}x?~<_~9-rUKyЛC7\YA(پƪZ[e}-߸WpF0~~F\;ZU:qgÆ$&B!8+rid `02~ؗGšĔ jS3OLi3oΞ㡶%,<{;\ošJHJ 5ix;!!!{}I0EtDXx~zx'h hLaMADž$gTGk h*16% W3f9ދI^9;R=:uG>g $wVu3Zuqсͅf?̟?ZΝ;Yd >ŋss7w^v- ߰A#gEuVf浜}m"HUa~O;0~M'$mXp\ht}:>(*D2e SNeڴi\{~-pW_yrr̙ dgqS %&&#x3Gqk=&lx2G2=w*i@Rp9VOv K!B3ݎ-سe$k`f;W@s͘j;#,"Ɲ ՟hr-tto9f6{x; XV%:%(Z97<6$籕)_ !y\Lx=mm..kS\ IDATr&2:iط1F9`+O( /)>ҕSȹIr=HK-]NT#0wk)Y@RcNО.9J1$wtLqӚspcٺ, ۰lKA$ΝQSїxSTT* %ql*]F7[;J^F70K#Q!Q]N6?ϫJer.{/q߬am? ub<@q/3g|,.~듁oiL.>~>)~9dzZc:)7GdR:6]~ۧ3 O沇LmCęLn6}F<{SNNN@[bb"111&zUss3vCzZ;˗p6[|>t#H"z}3p` NbcJfsO{ˆAk ZEQ0/ڬL~G0upu;eoZ~DDrJ{|B!~hο rn\zil :AKK4ijzT*Sҩ:Z&95ڪN?I$"Ca)IHuƸ "-8muhH>GS}Y/aʠ^UDK]BQΦQ{>"S\&8y`(lAc plekrb}NHw|R~!ۅف1#Сq54QF"'%z8l{ hX"A/@ՠ;\:5zgmWޙ+W}% ۨ\ݫ1¹,]lcŇfU3+2nl#<.(Lݡvs\3h<6s_Rs@DB qYل%1g'ؿFLef3.ۻ5yù$ W VYks߾C :.|^g}NjFom}xjs=,^jMZ 5y &;Z Z梹3 mK*?2Mcּ஠v?o&f4Lq2fJ| gcevW;s q#/@@hjn1Φj*7/bbng3UE&\.,Q[BWm{}ǠhKQЄcf a#sz.-=>o MՋ͙AhC1Kks/R> ,_ @•FG}| lXh"gPt1;'l_G}nZmF^8{Co<9 =ң尳kR/Any֚*29t~חʭ3yUD&:mপr6;ngޛ#1Gfi[To^ڲ4;ue#df-k?7|uݻwn:ƏinyY!0NVl&11,OKK#**RFt4M***i7v(_xI{}JBBB^>9a,X@SS ,/wJj } ^Ǡ5;(YJ[ᅱsuN7qSq<|O.f֏wxKJO`s?h6bmqjINOd* ̃F_ (C 45j:*Uc2>Q( ,vLy6u t|uYx8n_po12`H@,Vvy !+::0C>矼ۣ9"b|%|ɻA3wι3lPW`4JkF RtPw1R23ٟ!DwiM#њq7[Wqvaր9! 1G]աH@`lʮwJk1b\^\q^<P?hܸZ>!mL$( ΪZZN\;E:,Asѡv= I8jvQILDB :k}5戄T1lS_VLCE%Q92h F˩--IU%>lܸ=N*I/RboDMp璗ǔ)SPVKtt4;wl{y瑔SR܀ sq6VDʚ(N@ڵkyWx'Xp!%%%}}s}r 9zyW\Ȩ øsyΧJ7eO`%ɽWc4n B-[| l_ɣV; ƓAglaOy>˶})erLA<~y_N&Ѩ9KR7*"c" +2k:{v~a4"IHt關˰19|Wu_D{hŅovM6umBt<=B!}fmk$&sys1n^m61~$oUD'8v99+Vt Dor\ $$D|bDDFԥkp~Ell8Wjҥx^.|IϟOYY[t&M /~w۹Vsxl~:~}k L@US5YkƜ;;s@^.ᓭ4Ȕd7S$;:9{ShwX!IJnxia}y7|.7s{il٤KP5Nr̳5a8+?Y;K ްohki/Y]QAQDiQyq\))(eѿwGYd>m=s`4xxS[Wd&P:E{BJ=B!B!){n yjK Z؞gᆪ&B I@J"bwa_}TlkY+vE]"6(kh!< 2)`B(ugԙrޛ8 W,p عİSOfqޥFMFL-KvЈ`+ՎRa=B%K hj4RYFcՒ$K øgQxD_/Z2Z {,~`v|vw~b9CF _"y>:T*3W(+h9Sۧ6/a( 6%_lQBANJ2G /j*kQ/EB!Bqw|B/xuiΧf*]f8!D{7juUhnG׹|k_ر֯oݱ87%K( w}3V\y bvGSO !!׹6,|ckG$ySWC|m봻weXfS:(O/G56ܑfc Hߴi_C$ο|<ŬER_ij=wjOliud8j#-X4{Ƌ䃗?;TMU-Oy+o1`Q47V ˿_bJrfͼjT* KxT(L?ul:c}9a(Wκ\W/x=m !B!B sy~JFmDR(UlǠ( >9[+:K-[(s=[III콷hu:|=}5ObJ6l!hKv;aՕvzcϐ躛bW,SNwi ju$O}/Hޗ`,[&'EANdz&JHOrDu Y{r,;T3&! MAQ:vC01l\?͂BH EQhj0<8gesgTnFT/f?\}eί ^xx1Rc^1ݿ(uw^ArD;p~i _=|~p7; ?o{UZoۢi+j5|я,fxIa?>6Q6ʉ|qEQ*n]ٟO\;o ޾>q˿_բoB!B!B!8[ocǎeΜ94440o޼NbW:\ !1 ¤`ࡋrP`(͘UvW,;* =45ATVFq\~X2wdn{/>&v@SCxoqFfOkXk0gB`Ha!&J0:/cIX6nwȂ2B.r" AɨaUrL۹yQ߫׭-5+55/2ҳܮ  82+JOB!B!Bq4@]h566裏>Y) r'smKŐA.r* J ʜTĵ:/"Y{s܎| 4Z _מx Rp]( &F__ι9;2ޗ,:7Fr \曌flV۳=ؼ w(PiRK3A}}\h5ѣ͚{5.8>sgnB!B!B!p[2ޚnMMM}ku.cp K\HO[||&[sQT])x@nE.{Vk5q8=uon1'>9؄(6>k֜iu^m ]ȃ%מO,vofYF_!Q,.jn,g2wpWTLr"WmA9b~vO/|`>y+gf%ίc,sQ @i/b]7 s[Gb6΀31t@>g#yx1nZyτB!B!Bqx;2o߾s70}5G`[z~zN2K;Ϲ O׌#wLξfu>f#**3xz@bXсQ_@QRp .c̺o:[A~vQ^6#QUQ/Ֆ'?JfשVȘ0JfS]z5jv9 )=1N=8LGV/" ؿŜ:֮2 X6<^ޞ^K{F IDAT^Xpvw?_N߁)bCwj/Y#/&3=-X-V%ͦ[j )slz%-]emeY%3A)<طs?Fן|js  Fryݞ;){be3a6; !B!B!xkL&͛Ǟ={쬒Rj7u=cӈ b|e2 xG~5Hs5X~IucuݦѨ4LHK!00T*j/78-_;UL1Șp"c0-Y/z5o/n}p&>[ʞ-v;o?6iI\z|Nw~v})3/$uP CGp/YāN;ȳ,]7c2rP2;+\6ظjZʊ+ 7~C8ǚشf;|#5Un߿;'yoB^ 3p<=[ߑwD_;!B!B!x4x\<<<ƇtymܹO?>N!B!B!BYnwJUUkQ vhT缬,zMNNqsRjR!Bq9KMT*=ucJ(k!&BqR'x,&#`?:VOeEIZjR!B!N4>?dvLA/JŸ/=165vxۇT"{eFRa2)-gV}B!B8ff#fc#y;67x"e9R+؉f㋯՝B!Be "1gE/^G#:q_FbJ*oV OObz1KwLwB!BZϛb7£6Sc=Eڵݻww]B!BZ1IwJH @aLƦ  aȳQ5哾u50`(C2r,|K}]1O!B!NW{i'(6~ฝ-oB!BJL|:L~c?Hw}ӆVkbΠ@]m5k_ZMnB!Bqffj*(O/=ĥj!'k/QtcB!BKjjjw_HM!BqVfR g"YGyBB!Bq23 dggw5ބB!$GZiQ> ǴG\B2DAnKU=<_[V׭9H(B!pDoB!BӂᑎYei:b*^ͥ*33 "*4Z۵fXf:B!B|ne˖Q[{lnxB!B(-w>Бc?xD{DDbn3bȱxxx][_[n AίU*UwB!BܣB!┧ꈍK {g @xVkʺgd2RVRPbi$B!BNmބB!$:6ܬ 1mo~EpRu$;m( o_?Κx!1q &4"G0z$V݄B!t2h7B!BSH|/[AnfeLk;SYBLJC}%<=jkXzCG?g2^\KNVgzоB!Bљ&O̶mo˖-"/B!?Yvu h{3hأ*KY×'Nlʊ >NmuEB!BܣXo3 !B!Ni=R k1n0řc k. rs"h|'<2e3ކ8FEY1{wnu{'nz&$PQZfsB!Bt:h$&B!8iJ&4Gs谠cCt m󼠐p,П-:6?@oY'B!Cxx8֭k$&B!8ز=[O&!/ƦF֮Z @SS˜/ ٯm:(+(? !B!Dg? sS=*HmEDh((( 4Q_0ip/ EV(+>.xxx F*+kl(v**j]k~OzC1,F#VqESR^鯝ބB!Ɔ6ǍFGMQj+1LVPk4ίjil?;%ߠh4ZMlZ;vB!B΢FY3Auq>XFof͚Ecc#ʱh4j^|k&M:MqRL痣R…qnqyCXz'W\qMM&~3|hj2WסW\qf̎ fBXX MM&N$&B!8uFPT#be45]N!B!:Sy>{`jjjЇ0}oDsZjhl4Fw$$Db6[ py^99%:zfMa= bu& !B!NkWf?]՛8<<<Z,d( 7B!Bcg6hv 6-9tRǠ( o6FfSPTh4jVsiV܊Fvk>oѢ?woonKthԝ!7!B!8K @MU9YegbZfB!BLxB|C%((} 2v`9nCzKR]]Mvv6PZm ř-ӡR8'|JPǧ=ݻwSQQ@>}fX6w=~TW@=k kO tF!0LMstz( em  ":.o_ SV\ѡT*b FVQQVEޜv_/!B!B![tmۈm\VVв\`=vSRp٧w'utyMq]w78kyn rs*Nprޔqxx] ru^1|3V-YbNI_ݫ{=v,>%g_F>-r2oH ٗ:kX_O!B!B!b˟=o8Yq+ 0JsΠtӏ`SkV~^|cR0WAn\HDG֢XM`g E gPՌZCy`37AyUF]x>|8ǏgС}OކN#'3+7R]YK@L|r,?u;/mvnbs{vnًbSHHI\Uxy{yB!B!B!zih,9@ؠ+6 pducya37a,T+<0=9b 6s=@Q uh^s#θtAKϽok֬o'&7: PmvlvO \?o{EӒ8/o/J ʘ;~i5>q8璳 ^CmuZWscov2):vl~X*o~<2)q2?_7 7eWt SMQ^RyB!B!BqzkG\]]9MJRXk1וa35b35#jAwQks:(6 ^ 6S#HW8.=-Zng-qKPo?a~GOU>qE|O-.f%# !61G{B!B!B!Noܽe`WlDq)( ?]JgkYNRwTjs]C~:OJ6.vY+b_[75{>bl14F9:zF=?68~-,޹pBkk3vnXKz齜y:X=>m[Ωg϶}қS7o?#/Kfq6Zs#61["0$WՕ5m΍Kt7LxI~vQM,҆e鷿k?qKIc픦֣ii6A; l%61S%&B!B!#9V;y7@~ieeGD1ip/ Ec @jl(v羡T*v;bOo'a(SO=Ń>Haaa% FR3*/7C1suۿ:c|l Zð!3  wcY 0z[+U ^7jv;wʴW1ԛZit2_wf0+EƄjl={1{_.&&!W3㮫ؿ'%3kM_ͻuVum:a=Bڜip!jQl_W`h5Dh53+n|~ǂToGpkCߖƆ&߃ٻ=Ŝʋ+\^Sk=蝽Q 6": NwB!B!B!Dg0<*bҤ3>x9\3;4&TW;⊳hj2ӎؼ9 d"/ //}]%W_%00>kh]od5xmXmV^5zJE}3\Gim)!XF^E>E.jVjeU:uJl FǘQnL7v;oWjR[(#b:ܪ,s~B#[jZ\Y&RRg@Nl'k5 <*oBAN1Cwo. IVjuZf?0΂g>jXzבf1[tDm.o-~x,|YM'g.>{;V\1F90b.<Eo}T9~,01VZ| Bƒ:B!B!BR^^M~~>>ijj((Hii5ၤ%`6[ p7ۿh99%vݏk[%K( w}7GĆM-ieoqQ}̲=j 3?ڦNߑx`3g$c\߱}i9Sy;(n5;P͌oNYf҆;J7v=l~I\R Ӓ8ZJ'fGV3PU,xC,epdtjTfa}hv+nH>x3K;t6x;ҳQTK*2xϙ1|׍-ޚ~)OfSv45k@R?]Ңes񾌜0+g]_cl<܅B!B!Bj|l6/(b)T*4ۄVʕ[huEQVulٲeK3|;g#V9:mR.aoQF߭5f%%3$e," =dDzvR3hiױ1ZVG2̼f.1;E`?kgum}uKwQzHEy%d;j^9B]1 Q\Ul;\}p6Kvla&'5AIyrji-Jt6 Fw0jht} GNw=YZJ{8B!B!Bq:h LF3 2͏S[W/b>~6OISdzm]:+_  btj눌 dSRXFr.}}q4*˪? Z {dïĂ s ]\>B&]9^l] ;-7^D\R of9 `[[B!B!BS޽{IMM%??r鶌fNzz/Ew[M`#L.uxv1)Ѩ, IDAT=4j ^:h57̬z}\n2][Y޾7_k754kwL/+a}/O=g9MRj"y_ǝj*]ˌx4|y4645UTW5kkt'e,:. ?`Πoc) 3{{}W9߳E|pP0M!B!'1 TcA!I."""I[3Ǝ˜9shhh`޼y``2s%j/ZUgL˗a CY 0`Sl8Nf@*J+ qoZB8.B,f g`27Ͻ}?rPChjhm1h_l6~i K^20gB`Ha!&J0:/cIX6nwh8e\pDrSݩpdkޗ T*5d:vmIAy%DE֋ޚVKHDYnׇUGuo!B!BCDp`j>J4(.d˖./v28_k 7be[9Ntp֭kp>tkllG%+}h@%KUCٽ0#mCF*?RRP̠:2+pڛvcgjE{W:( >EQ0ͨ5Z=Sz;2ޗ,:7Fr \曌flV۳^O;AR#>ٵߑC>"/k^s&is^߇6߳~,;#[B!B!@Y!D牍 {7v7N6ۣmٲeֶ?Q-oA&{1%DEDEk6NwD2%]sߵ4}9g$c@l|/;ux(>r+xuv?EQغ6Q33X{˞sɱ&Dn5gaAl]{Wۂgswsɵ瓹+YjV-GyywEJbҕXj fE>engԷX'<ʑVV7΀31t@>gQrzFV3!B!Brwl0IcM[qrm̜y.Vիӻ: }{ۑA}o9f#jV_:b 6+k3OTm9)-C5#BQX]Dz 1,İ:?EQHS'3lkE-֟{XGUE tW[֞lgT*_[Q"c;(Oui֨~$d4gK;](-{ѣg˸s#8, F6LF3*^2VK{YmlXe|o(/9]kuZnw>TW԰zgC!4W*n* B!Dgi* x$p2OUW?6oڟc#=ںqxkF͛GFFlδ&H?ӞaCFMÐA,ڼE6)SZ0+Od ^ݿ%-cg_6j&`e9qJۋ˘zE:g89xy{g`2z >zkSbzEvyck\|9}֋\ީexg@lBϼ;d`XIHPh6‚g>tZ,vwAPh~2w'fS/(+qY_[]Wd}2*Gرqg`213Cjr;2*~ػ=;vR/ʂg>l2wsB!8x2zdrkTw{FZ~.sO:E˭7M !%T*6Ң|FMUEkBce15Q{bVB\;gsIsoA7ļyسgOdnѯ |e.G\u}=^İǶ絞bo\uhV3+wީw NeySfL&2&1-Y/E9D>8V-eB\z%qŸ:E<3̼A) =ynF~xg2r;d]?#@Rjc,V sg@g_psnog][]w6OW2i:fs;B!ĩ&!/^"z'}K   Pż!g߫76ш8{İiew^B!8uryCټ9{:Bi0 ¦M]te:޶Jt.UDDDPt۵kW70*y$K_ Oo(VJ~eKp: $ DiQI1e"8<FMEi۾i]E&*ʪZ]kLYQ9M&$<B!8P_s"ju{UtznXCN,s7Rؾ/J [X_j]τd ޱ ՊNߠ3OfےEunI/{vMĉ,>>3S<}Lf GoRl~43m8XMFs3)س کPEL uC㒉3Rrlg>Fb^r/fDv?GDpl"4Vk e-[N]Q9?Srf=~fR'Pj5x{{G||$T*n ]g`bQQQvooV~b0x2hP"!!7cJK5\qſksOO=ii DDb2Y-eϞ6bRAC6 Eb2Yj.Ww&:`_1@]] mhԄ]h}NCuu=wQVV=/س'J||$hZZ5>>^baB/QEQKؔWة{vyСC3gf˃n.$juc Ս?ZOeUT|Myh /]GM濕V_ 6!BvbX,fsԖڣZw䒾PL־]'hI7MvLBMla\,o[juf2aX&^|h31Dop ZUdKr`Vϸ8,k|eo>=2uǺk2肫zx.g㢏X퇭>s''\vGfo>EJ;Ilc7O/=Juq۵(6+OL{c'n|>{Oc[.$,,ŘnlP_ok>>{d.`8vn̘qS&/3mu/ٓpy|ٲMlL0\)((祗fL͛AJJ4۷gqoV/>tKv5jXp|{`ܷ[dfEDG]\z(f8 ؾ=w?xc0xp]1q jxᅯ)*zE\F߾=1-l 6jdrkjzmY'.޽;w駟zYE!B!8z\ώ4][[t~MXp*h< q'~`[߱2LnU*.i^p%ř߹ ރagǴ?gn33wh %4.h\3}nW(O`d,ZF7*iO@lavl^Cil6+D!8:vukRWڵ=cl36vOEE-7 ~22\AoZ͜9Sϧ[h$**8xٛ>}lD__xChl{`ڴQ}9>l 7x!zG;k"3}dڻ҃z{T9'+Wnb ȠAiE.>m(>tsPmmS{ٳwt%SCA>mIX ̘W\1NKzEe{Nx23Ki}=e0+1lR}z{ko&`4xᅏyOx1vZQUPZfӌP8L,zhtV?8 *|Sŀ3FcU J@!t(v\p>9 ~j@QT'dETLvJ@E+ a}~O^*k'8ޑۏߙl`T;*ъՏGoQ'|R.@n   !]ik=>'kw"/,~6;Naӱ xmH93)>l:֯]o02kVJ훷^ >xnaM`ԜK{ALl?}wֺ 2ނYi#[n];Qc^Nlj& TʪiA28}o>|cX `큧Y|uY=3lذ>[yM@$Z[,^f>x#?ٜ>[GoYp&/c!ys!ے.0,Z}/L^'xbc3c46J$3"3e>]rc^q8U_ "3lX&Z+8vV{?n~}[ߖ_E#77YÛaA7kLGvvjo &NJBvC?/,+,Ye_u0O&f+@|f4I mk?^BT;IbHnC7ި =l#:/n@zqGnDBG'H s *r^Ť1Z8S8SeDz3~9h6lg7:228p?rrm'N?K&_MM.?>7T"]n/_|.̙gw;Z\b'e$&P[`„bQ2KJjJ3R\)##EEEǯzIDzz55]۬&0 r{l@g0w`#TE8 fHRyEVQJhUzE58WzixQ}.  wS0ۭUwQ4W[koG1(V[]oWcJ7kۇ!STf2wWbkx_RRbn.ڳ71mW]9 rr"g2]ˋffƌѼVxvk0.S/jdM̛wiifj$ӵLwZ!Sƍɮ]oJ;=''*X$VS[pA_s5i+Kxr=$>6j#[ctI:),VSVPI3$-j^#8 -~sCh40yv3V_T<Ɏ%TWדnjoÆe}' v"cdetyNrrBmǎg~1b e8p[zI&!*nn:$..z**‰N$IbԑSSSfÊj"I^'# +W"..CM@lւpZ-@LMM щ)$]HHR{4UU$ySPUrY8 IDATTE{0 qZwSq%/ c7[pyԾO5ٽo~   =ш,(^&p Ot>3;eP^>|]G?@OCӁ` ЇӃx|4VQs#g0|2eXF2XnhY#ϸ3 гc||_~uhȾdvNLL$%%GPRR'Xl]4 _ D$K/=q㴛^>YCoۿG:QQZ6|t.m+Wnb3:t))qTWk%&Lfry꫾v`P+g1}(~ih[0m}-N֮áeONDaa%VmeƌpTUձ}{{0.??.~~)kl0a5Y)+ =Ů]:|\u,6m*j5a'}{ ?5kvrP]݀|~NײַIqQ}&JޔsA0VDez=s (޴<9d҂E&7%y$9c)޴Վjg-vK/u{$qY#ddUrV}h#͠AصϽ  p2 gqwއ9CKH&c7:v Kڀ,.ᒔr Ġh|{WpVW׈@]vG|uuMaۊw?Sf`DKH|TUe3-Zࣱ۹"mmöO`25f#f_.ldž^ig0 ONyצƦ-͑Kmƌ\s5=w7u\^~+b{()II୷`=\?ØLEngγ$7m҂H̘1:1C 2^b9ɊY1lX&w"IӦL]`PX,^q} Q|N.Zx=>?O=u3WQYYG\x~[xOl ]IJgMY. ݎL U=$'" aĈ,bhkp8l|~j3|Yp*&_s9Rk0i$V^MXHlA77<&5w$ ]XѤ掤rVihh6lTTh_4yA/ $DHa3Ű@5  pZKNq8aDxJAׇ (N ,³I&LBGSDWp)'±صj)sny=AQ >mtc[2Y泓cy)\uظU~ &_q#o''vk?]f&77˖-E^իNp2-ٲ4[n%33}*XL\z(’%ߠ(j}?A*E:,p4@ff&{q+Rz~EQYtz=CQ^u37~EQPɓ7sss9x0 V}ȊA#pD`ZE+_P^   7@ l,+g}(=|F 9W[ ('f(g,i)ܳ7<8`i־-]p8HH.Ј`>-~)2|< auUf`Ƶu"l1q$ero.8X\@a$'sBU2GM$}NO&:Q ̕lOͱ\|9˗/u,Zt#Fdd<;c,_:Z[8Vrs3{7˦ ;; vgM\vt I'==dl6 6m[qSf0chCe&r M1ulewoςGQ.ڌ䒩8Pm5 O5~" GZ` `^g܉ [j{ %%.Err,mmq8|~E 21"6.$IA-pXi!v-{}d#IZiކi8ӎa%{P*A$Ib&..9s(.l6qYyOo9   Ah$iأ5:>p0Xe#e575v vXmjn:9Nu]""tEO#m_&KRv)yٽ.+mZiWl#oعCH::>c^$I(r+l-ѣshk,GXq{L)S;=^UUn2&NJLL$I̝;ul\=Z{W +C3F3}]rcCNsg[PD'YGtcĚ5;B.wϷ*Q\x>YgOvd;6;LiA1x™8vne>eΜw~mOH&>> g~U:43eMci9V,Ɯ9B=~B-=_VVȲB]]ߔ 7AAANk7˪]ֿ-\E+:F}CjΘx2()ڋ~|RJz.Qcc#""tIO#|)fL6;{~^?rϊ`ش'Fńɼ~/HK|1#ϙ^d1Mkl-[MS&>#;lЩs[X.Ky A/t#ss9s9~ W^NfXVb566CmذL}Vc7V`6+Z{:V|*:kXc2=iFr 퓚]w]]w-k[R (sθ#L өxNZظq7t!Nٴi1UWZp~„<~'yyq#QXXɲe5\z =G'ob5Æewp=Wp睗rrr35*縳IڡCpX;2`FEE.'i{rr,FHJ m n ܯ}vURR ޓǷ<9?_ݡCM|&bcXo+RJJw/+   J]!F1|x\mN D;cilr?`0`7qs/\^o@eV,} (%Ib?O4r_Ad7W"ʲ }[L8=㌝{E\mnɗU݆݆3)C$߯?_x 5Վ=6R8;P%bS3_!&E .܉ 3졘%ܻu%>W[ıݿI<вKJq}$'džq.kMư?eܸ!/q>통PIJZ}VR2aB>YVe@ QTTHII5PSY'[dg|,+%IBׅG2.h2kCE⋧h=TXD`wwעkd}E+yGOדIQQQu+Jum= ]\S)whsh   U=( c09klbٻs v_H;?i&GQA^= *XPM$F=gݳcs/8Am+ \_c?}wTlg›I<̑%;W-eE ,^NKo\CU)~.s/'65#6bWesƆG8/K[Ŀn7?D)ak6?t&~?0~|iiZ֎$I8vƌyPxb7LTojk"--`O6,%33) s}@CC _>gGy{˺RPP…?`4Flϲx|ZW_hx]rcSYPފ=;'&بz]m~`Geun-G?Fvv ٩.>k}1^d̟?h8#7>Zoѧ=dY^$??!C0k*VD2HM[c LK1)Kѫ*H2ʙme[1XqU_K9&Ig@ xAUv6mUh)I dvs):AL0m,Y$D<%چ~Iع ظTEE:s&mkaXD'** aipn@7Dƛ   |O7#=ˌ7Nqͪ(2yVbb$ 9`ןSSY|:?Kl\"RW[!6>]`o6vm/"2ބo©NLJ&ֆZjv:K1)p&cqDsh<@c?#&8pF*ʋ xE׾reӹy{- ±i8/e4Vw~SpjYfϟ ;﬉X/r stdd$TUe߾ ;맠w55 4XIUW7P]}GJK`ۼygeeڝ]νo_9%%դŇJ`*|-pW5p.nzV} ˏkz,3ſ:c{56m+z5a޼3JM1|@.IMM_}+4gȐtϟBlVTE ( J*Z5Mj_8SbAaCowd-kP|~1<ըrӉ`f{ImQx]-H4ZZp%"mZvIio< ޽wEMAAAA$zӟn$++'@@Սj5e {P=.W{vd$66f tqWI\j.-5v:a~K>{$&sWjZp<6_W?s+m|MzQ4oAo7V%Hz=Kmfϝ1>@}{)`…P*ł$28Esm%e7;7E0Z{`imr퇴Q^?=O"> AAAAAN{˹?r3?>xbb--. +Yv˖m[豄v.w-}_[8;|Ko~/J&:ڊnF_ƍ{Yb#uuͽN`/{5ž:ʽa=PAg4hnҊ`IOFUTYes{nYYxxZNL Ip*҄9~?>`0ʍ#G 'a~n["  P||<Vd+NeJANnIx<>Q Rc$Iru|~n*(JqnIzk=ڟSy&:C˿D2Qϕ:TY9zsջMo0jp)LI<;77v$I)x 2AAAAA({ 'UUimu}P@}Yٓ*ȁ}vE{\PPУq      K鑃ЪzN       N\Lz#wRNyi Lb5Jb<Sh;9yGq(-PI[AAAAA7yh4r뭷;PRRup7]'}A\}ed75|t-!>_4tq01t }n78AAAAA1~l:/T@LEBl"yPф"xZ"pX$~vI2$rTUE$<"mۭn/&YVPU6_Sks灷's}q2nL]=O܊j`=nTΘ:sO'1%jpc =+5[>DL 2jpé    gʠ{ӫ"rAPr-|+@>c!ʀ3P+e}U@Mn$Ir86Qo'oHWTTAZLbÆEEP$I(6npoqt:%%H:.8޽eݻW+]i|,x㍡[yyy̕Ψ#B-Fse[?{l5`~3pڜ$Ӈ 9\3h3Ğn E3j챜;b7`8TJ3\s$b4t^mr.>̹󧓕ItL͍Z=%47p3XtOx' 6~h.Xp~7_x?T,(¦;z|-& e×[ze   =a6sST-4 |_z͵ϥfx0x k)W'e/I1-o*vv6 ' (-, 5,Neƈ~M2e0pp_}o:6!o. 殩A e+<<i"&   B9NcQ>|ƋwđO=_G4Ey/ a\r/M5lj7Ī/_FyAүTI^oQiν$ 0|,RQ kl0a5Y)k`Ů]} I v\[I N>Mkt:+c^"1,xZ@eTUrkiH`K|0ȿKKK4hP(𖟟Ϛ5Dۺ{'t|̑zecl2?NjjG}m6V:QUUڼm]QEn(BY]9-6`ATT6l ٙ|c|{u(\߾Mqm ?>_HHID֐d @u2 Z-A^{%|^s.QC-x]Waw^1m91U`CAۓR:!   Й6$#D8[gijW=ɍ3gK:& nuSo.Sʠ_$H:+ndZjNgРA  b_ J~[>t$W=B:T$nĨDQVOuSuy˅nj`*JPdu)MjHb38j""{ړxhmkYAAAh{ ht5cag.Z<-tȊƒT6V鈳1"=`TGK>b{z]rYhpSSs2B9HvUq{-)_#{BN!K}(_ >L 4o|]k+F! jnkߤq߷< _0/}s\'<1鵿}np;-3q֭dff;zزMEA=$Z?J>߿O>$uΏsQPP6СC9p~ XEQCAܕÁ5 whGQUuU4h5LuwGr\Ƕc`4i_{-h2Fܾv:Ffʹ44 s '7rAAHtA:jr7qkw8A;5t%SP/la/{T[>ٿq O]1 NcuYtt|~k5`xqҸ)_ӛ$}]cT\Nӆ`M|Ǘ#1L#ږ=|KD1^6-اF%)** =VU9p7X>H 6ܹYkEE 3&'桾ÊGQTXMbI&..GSPPΎŬYĘ+aYp? E |o:puDxo5d#4~g1sx=o FAAcewD1p`^ vmSfEkTUdr`0(.CAzQXuPZ_!ZےCɞ; .p5}N3\p477x~]"$Ml>~'A63ЕmnCHb3 ֬eM߽UWbӦBfoΜH K"+2:DrP#嵌RAOSSM\<$'ǒ ))&=܌Ԑܞ'11&zuKnn--.rrR_u] xkmƌy睴Rzy j{:ݯU$3'ĔN9l[uN+OerGpяgbn/tn˹AAe0=}חgƯ=XvWkd fĘ̖P(U&pDa餜[9bgLE<׏X= kb0q%Rwyr'9b}ٲu(Jtj{zRJj~ n.|0Hta3YFᎨhfm}k}q}x&Nٝwt秼89d#Gs&r南{7/uk.Q )~^>>w*/ g/~M樉_GO=Hcuyc/Q%k$f9Ck.@RC2k,TUNz/#qlFcSk}lt(Y/5נ&FE?)|w8o=TU־{5r?O`vC5MJO'wgt(ܻ#3(u?`2h?֯{5P,"<,v޶"YȥCb.~=1~u5z7'!97Vt{YHJ82$@R EWqY׺+kq못՟eײ U:@{1dȐBPsܴBmu{2΅B!hi=zp0w/S QᰳkFrdF23\'ey:Ȥ6? ?wO:@]=׷K޾u9<'7cPRWRWrr6\]APx}h5gYԦp}{Q EDcxR??\EMIac߲NOtDߕ iR]OL7.1__[И Iރzk3 w5h-E}eّAx|זOf3ϼG@`0vs{/\UNd҇_Hg{-V7܇RbR{gtrۦNEQ3gtRvS@kp7ƥ7t:~wgO봤T+E<7{s88{I5)tۻ֨zWQņƐQoT]۩Yַk&5'5&՗"A}W͜7n(;?:eUǀ}x<,_X`p wλN-$Ѫw^'^3epSæK̿d 7=&+ۏ\ Ҹ􆩇-ms{!B!ZЕi;lkkظv9e%8 W^l޼9r$7n fpp0aכqęM/l㗾1!Ѽ||h#VT)ky"SW4}XXT ~`*?i:$2f$-E{2}BcNN{⣇nn a𴙄'qɃb c;goc ~Lwn7&:=$zc> Vë+K7/"e]DHOgsR'(OdS;!9GV3x;En6瓝ݡm; ӎQ3Fv^+AADDh2vy}5żXJ#EaסݬݻC>%)w婷®m{[INBbwy,`NӥؿoQښ͢ϖ5?'pS؛K/ohoOEή<ʊˉG4Ea;B!✒֣E뛦;? N4ӧ rbsϘ1[o+V+ tt:OBKPE4Ggr;~p} NȎ< |>arbAKb7IʠQnZդMj7su9LO.t-+g׾%}ĦtWtz5p8-_Guc;Mv9d >d<yZn7yiW_.{T_:~Z?nB!}M#hN'.1 EkDDT,FS n2j;~C{_w pӧs뭷R[[7|SiIPEHq:38NvfV57zBڽ\3~F?}슸gZc/ov=eAƷ)f 'wӪ#A7<ۻ꫙{Mش^wm~ZG9z2{wPKf](z_8ՠ٦6 ԔIQ7<6oތ4y,_~9/i}HuٻݲQ=oqz4XJ#5I!I}qćdʔ_L@Faf2NIۮ].ˎI)F|z&l~m+w-a(`sxu}itSok+%7RP_k|̇}AlBc5~i',E}?g[Oe&FD}CpB!aҢ#7ꂂC6js}o} #84B6_ 1c|I~M:ٳgS[[9xL&W[l6lFU! r3YHHatp5TU|OCImYk2}͸[ט)|#H-L2S&&%^klՇ{m*I[l}tӷl6 q IDATPQ kmD$$Ry/QUi[ ;Ջ$o^{q 'kt LH>Ʀ`)~W ^rEXx_mАެrg.L! þ_3v:QB)$[$&%iwqnж-+_Զ3ο` ⼫gsճ,#{R2~  A}նu%D$$u? F0gEaŊt?J@e},%ƥ;b =TCP,mVNRS~ա&uݨpzÛYQWnP4(EiQWnm`rbohtxT_G AMU%=r3b.z36]eVD׾C3~pɖVcǥjjb*?Uj~ĞU0*z_0^aȌY 1,;ph{C|||0ݻx_VΝS%2DG ~XkNxn>}EYr7%oQğw9*y^U@QsF;.w?n1o_3+w<7gM^Orr2'ߠb:FqKVW۴IKH'Iaw(FO;IdnwaҖsY &{R612W\\]OOM`X2xL IRaǼy4uji;j[lSD .ٳgXq6MmO맱̟H8m@ Q{Uz'Rƺ#EMk!yDyR)uEǸٶmI?lPòxQ?k<W6l]Mk~ߘ$''ƛB!Btx۴޴8oRZ|8BBjm*2'k|7nwuUUUdzY Mhmhl"umxdVWWt:, 'Vx7Ocn+<ءөrJRȪ/rcm@[ONM޺6^{b8>{o߾L%v ח~B!B׵[:୓w&0M?n*{(.w+}{˜`i5bʔ)dddp8x뭷:{:'tLKTqxTㅿ|De}8iKpW5y dBcjKqxj{U_ָ]'" ;BV֖yxB!B,֋l T\DAUeY&vfۜIٳ;{>Lm!׉xhtN4Zuׄ;p5dO|S:)܆vW\khs~{渝vӯSJJI$O zҋa34הQǢhk󶑿6ꇏfWC+3I=>?}=No1-):1xsQgnӕh<xB!B\kNg䅓aέf:rS$(8aƣq:d !hA10SX3k`2~t1n#%9M?}W pY+ٰM9,f>w _{.8҆(e[ {H>iaQc{LdwgEDx(dgoo[G(5:hA tfdm?\%eh#keӷr؏Nw7tTq-S6oѫep5?:Q[Nƅ Gmq'ݏ QEET'=FxSmHΣOi!B!/*{c?t՟c1׳dͶh42rv;&(boq .AxM='=Oe5'OKy? ejN8B.qeu<4Z-]R(ԔP~0AQ4Ĥ 82][] ea=vML&jˊ(؏~vԚLOO_FQx ֯_S.bhtk˰[3 m=ɚ4iׯj^ b jB'?Ȋ7!B!8&UUٶq5Ey7ȨX":Ȯ훨m=MFڎ(Bޔ.pѭxCu)?sʃmy<*PO͡1sU9ΞFV[2&& EQ3?Th`&SVEQ۝\~*KfI~y N 1$ [}-eEQRRBhLEj }Bpd |ڽWvX9o5:cEΞB!B!BE!)) !ULH zRiطbԤwlfOaao{}.4#p4#0_?G]9B ~c*NK-Ceߑ[;"]=ND.}WJsUgO紦7IBxT(NR N؊#(4*r@ud|!Bt>&:xm!D[u"ڃVatRS1sq:]=2y0'{RXX9.?; Ayy w2UUu=%!ZtxkINN&''ojMhxT4t\.?rPUJ6}Gux9FãQ4ZUeۢϼoz?|嗾\6$e02k霖:-3̔+`4+ģv̺1 &0l0͛ǁ:zH:{ [﹎Qxز6 04]ӿyvHmYs[UzK|LLAFB!B!87GEQ(,,_PSc&<0UUyܸ˷~p9Ϣ14<>Y—ʕ˸lZv[!tdgA7t;uB!ĹHp3Puv#6@Ӷq|5*vggOGvWɢEX|kgOG˫)((cTZ>NKM,fp8\Ɔ7)"#o%IY[kpz0jj8<6FRSE]Y}0ڵ|7x<~SO1o<;|ܴT LτBTWVXl(~?qf\s 7zٱ=xHHΥC.[t7 Z=ՇXc?l?KθcIFP@ uzXWawa>= վ{%dkxTevI zW]sUL?ɯ]k %݄1 IDAT34u1h *ηRV4#=i 1,mX6; gxSח8f~ ,f+o?G_8 ==LlZ W<SE|?x4޶B!i%(81.@hc۷{Eg0IKBn( R[Slrtz@P!8Ɔ…rVr`igOCeh4֬쩈ʕBn啯pUEA4}VӲ|VZ ֡ޚyk{?ޚnQ ,z1TEѠ9* ^NI ovswoy\̝x'PmFG.ΌAygSw\.I0;64I58[u3mn{+TKdn{+)f;weIM) &$~\\a2|Ť7D M?G޶~C{1pD_ۺ71qAMUy㯽2Hɡ2.o>lZś9~JrzW+7!Bzh $K7vbPcޜMnO c)Q<n VGxD4C钜ڕ?nB!Ohhs+-sm۶ôEC/~xZ|anr\8?ۯ6ǣvůSxPUzzC?Gr!ߍ-*+w@kpu\2b~?N}>~~s,uVZpb\w<+&碾㩵7OSj зKy-^}e#2`RPS#҆öM|RMozWa%!iB6Ly6^_^g["GT4ꌌ1{>x7h~?N^Z/]ˬr+2[GJHEFkygރڋ.~V;?.tf!sC7cp:{ج+&z7ۼT]curG!gNORa{wuoZKYiXlKdQ"vLey)S`=z {ob0d[~mB!Oih <ܛهDDPZZsJU'VKaa9Oh^nq@UU=۷ 2bZ> +*pud ,,C1mYd_bbx<հsgf}9Z=HJh4`68t={Zc40 v'{w~ƟBBf::ܴ؉Xjk-~URR hkس':Kk{سg}۹-Z̝;~zͣЃ]EŬǪ5~+Gt~`c0/]+|<a)CHʄw5w_ Z?=GW4m-32}CSMy3iNIpdΛOsvɶL;hv3/-u#7*n'^JIRTW Gk?r`oL3EѰfZ6#$.,Ms1%.zb : Fe7դ">|sng;w=Kmu=!z,>z s  ]WQMBd7BMe-U5Tzi4").B!8$@7t:(8saZ9vFKݛ^o`U~[-foZ@4R+s6\!m3fL?ރ1֦%M O 5c^g]7n@Lol޼.t6[n-L+xͶG{^k N3>[o}Op{r~RX,v,rVb-00S68/^}ݾ[qqq9*28Ux-Y<ҫ~ih5ZܪS)+[\nrJsIMcrS &Mkuy;/}w3"mxmwZemFŷh|>'hD+0›ɎMZl~폶dKgz[z,BCXV.^t#}٬G]4kg_ϖ Yhj>ݪB!ę,Go>u=[ַ&/g]P8 ٝF#6[۟L&ND[urn{;ݛaq瞻UW4lMAXX/pII1lڔ޽2zt_|Vcz³NXX PYYGrr CKFǃduՆ {'<< 7n kbiuXSc&;uNZZ/T[SѳgWص=.BRR,sl'sNӸz=ؾ}?V #ݻwW$r ]F3o5ۇ(5L̈́o!v`3|x]Dӷc~&ǗVmۑE"z唗ݱ~mkdI1ZPRR∋$ @7ҽ{"vr8-A!:F,Ys=[IIIh/u:B!}3,۹M]CV5<}1!1Mk6sHIgYl=[ 5p"8\wܯމঢ়˛ۙ-5Hf4ӺD&pur]ߨ[=^ѴTS=jW/%w<7W?'G2͡ ~؍q;[(S!LЕ0;4b>ki:sUpp0aכntqnD\'BѠjj4[II)ۉi$%v<Yjo߫~ß4hGCXXYYx> `tz Ƶ׎+*˗o+Fsy啯 DG -Y|=իwzNm}"mߒᅥ=v|~SRo˚=dI{ fq&mRRᆋϴiÙ6m8 xF߾==t^8nzIjٲ-,[v.%ϢhU||mמɞ0o,¹q[yETVjfsih%KZ&Dexl_ s}JDD0ePӎW{\kuNbHNaŊM7gN ¢Fc)NTv^^aDp% ʠ J]͋^fgal޼6,Po]lʕ\vw^:ZG@UU=_NUUTUm8;ujmܸqw}fϟ;N[}QPHNbXPBx`8.wG_ ö hjK&Ñ1-ֱ8s 3虐oi<0^}h_A.}مB=/kuuZp9pz~^͒/>xsDPQ؄ho ·nFӲם ї;|}ڣmVBt|4;rb7{'J!BjXfH퐠\[ kMW`?AɩTU<y]և0m66[FQnCQ:D"' 2꠵⯦LEE-QQ%4dZl321cK_KLGomRazm[/hsrJؽ;WǭgϮ,\kkwzx;k#ɂk|N 5, <ڋr Kv,߼@))Yk]h:ME;B9`"lp*.{|E}Eus],bgAk㉫߃Jk>[gc_"&;+gb_oEZ^֜u"4ڭQIaz Atb-:{Z>뉊`mԻo:z?x/>zH9ѽw k6]f;v^S !Bit:=)={ `4Rp0K}Jdt1qwaiXw+Y0[gәƥ;~j[myoIك,2 2Æ)(#U~ h %@iiFH BNqxo[Jy+I{ϹGG't>{B$:W&=y.9 j}1NbgP#} {n Z;=ݒ|;b4=:yM@II5ej`w@Ou d ODHEžzd 5H\ bTF}ƿq4Uahs$@eJ,#YN=DPV}wXkfgSYWD͍+e۱\GڭV?hiQLJ־[,ÔNLH4KvjćqϜ_?2`u˲ I$1= '\\|6#'fSW/_z! 5 |SnڝNf씑|؎[ެi8c  (1%Z頤{;U/o.32Q` %-3Y/JKZy߯?I3ǒ5*.;?>fhx07?p w$.dYϽ,6ygW:mX_\=pnwcp uMmAZAAMJ0K ۽o>RxVK[')5Ii~ %'D NݜI%WV?QJL/1N xYV|I}Ϸ|qL2XvҌF%6$qcѪ|{EoK])*Ij n~$UYXZ_FC[/^gD]k=qL:%;Xԅ׾yq2ՅX Z)Q)&rƚ7{ܦr鶯M!&$do6l1pVDMi5VxeǵWÌaI7p3vG; aOZf},eTWQZ//沛.``1l45u} ~$ⷿ Y\p\n{z߯@? 5c&0$I,q;xٷvW  ©OѽLၽ>ս0a&A!(J?KĘ n$nv$`'8#Ɖpjll ,,IZ IDATdm{JIkfzJjB en]g뿝; |v Fc}cd\.-a>h /|7&S(W^9?\:{.џsՕo 9pr!)$d{G~wh,hF]^;=v!SgKwq߸C[~G=B%Nxl6|Is>jw?ɍ~#|/ٶov.?A3'{I{%PkBsd{Es_9#fytNV]ͿWko,KIW22)I^+޺Y~VÍĆİFۺN VæIT;OSvRZ_JxsYW2O|uPH ihm w}>zX :ֵ; %$<FuE]+^Yy恗=??2A"LaVAA8]'$.S(}(GMlwrmĨ$et8X[jt3J*&Ʌq"Ca ؎{,Iļy۶x=w^}u9YY$%1lX"SdKA~kŊ-LŤIYĐ` δi9z,ƻ~yݭ> ꚻ<pt6m hll%$$믟Vb&$$Ȑ~}k'PCZM%uǓCQ%;)=b'/p7v r@ZWC%"w 3v I")True(5F$ i85!tDmG8Z"GKvPiqX[Iv"//ܻvB$J Qȝ{)~ nn1IKKnff3z{7h}?ǎߏn=yyy}I/vRU'#[Յ,bpU (>8X]ث3M>b{OuhwSV_Fq] -u=ݎjmu}U]~k(WDYQ ޱ(A7AA~vmew0j1{+/)ӵB75٬|5!q8Yfu5?ϻlJcc#eeb\'?8±cϟu,22g z;<̖-T>_< z7.g|_|* u8N-Zi#x}ꊉ g,\x_ڱYyܱ>[zL', ױ6}r*4F24&z*of Z,(uԁa^e%vs3ֆ8mf5hBLF ;Gz{\(T4tw2i:!&S?p986O{*5ʋ[i ORxvQKKK)//=Ah ~k׉Ax^5d axXm,_gR"(8Wiu̚*쭌;poXTTEx4$Iזr睿`?P_]u}K/IHH'Wuu#ǑWjg .] /|i=QO`2PQQGii ZX _?1oѠcW.뮛ͅNz,$$I^<:',SOĐ!T66"wH9p;xӕo'T*x[5*prUPpf$$D@cc+>O nsg\xb؈ %)) Ir_(/mII&-Nᤠ0--f"#CHJb._ױT4)/q:<迩s eY4 }[jD<P⒝DOd-_⒝ IcP()Ud'Bi 8ۭ(:6/*Qo/WR@q!I#']^=TTt"I J%NGO*5ӉB$=-_V6`S_/"l5#<2ؗX #.4ٳЩt`  Bwz"*'NChx$l0c4JM{;'wd2ݖ ӷYnW p<{<Ռ! bC s z.|#><Óp8|F-Z7SRR]w_rg&&&+O7ծ˷z!lڴ߯rPcǴZ Zspz[E\}Lϟ@h&.]'@Io92>OfɥN%33H`6[ n?$?Izz,#FxYvV~c _.%--CmkZT*%Ϻ:֠}/:: SpT}9+[ρx;Z΄P1{x8oNz%JRn@nyK =憫o‰3mT~3מbs.1AA-}ruMP0b$ ɲZ6ڀBB#$ u+:\SG1s]wXg}{RMX$Cd '11v'UvL&S(qq{ʦ4GNqqu9Nu*pB$zJK{'"18Njk(*q5OgLPC }EBph'űw9Vuo)@fm%8mt9ޒϻ'[[E ^E9ެdVMF!zi775F`/8=yyyL8N̺o8V 'ή] IVd7IAA~dYfT=("L^+wZZN .AA8S>\u'TU5PUzV;)(8ɧIII5%%}T蓦>S7Pc֯UrEG7cR6;TԑRfi3 1Mfo9&}L7{KM9ޜ63H-5HƂhMb2pX[ Цf!;2rl{]N'G\ȹ+ڠH\.s]'7\KM6s+VBLhSY޷bś   Ċ4Vk,4n;MX&Cd!Ɖ p*8Vu8s_?;-s   جlVn  nOu<E@uoTcܢOɩ#XՑC)Ljd >?110JkhY1G73ckj!(" wE먪"(2ވi5kk (*^179HQQW{/f,v;9eg AexgNu]9r$۷ojSxAAAAA8A ^ 6 BkPH}z}4tS!JC&9cu[[͔ڌFo@ sl${p:(Jf͚a] 63j>Ժ^xER(P5Jdg;*kk+ N7ߌlܹB#Bp:4v{gJqt^LL ՞     'a.gmOfG'ݩL)I@b**=f *݊tRqg]q;@밵R,L4իW6kW9en֪%f-MhSvV Ns6V+XVMX6 F=Nj>)23f̠3f;x߉o  p9ބIx!rw DA89z#wZ؈lYsctkMR֗B/%Ip;~Jpx9AAAAAA~Ϻd7KU@U5'9*] H f'}!I@PHM\PPr Rhv`p:(4#5HRkz~3"&      aH Z!3$.[yJE@j"Jb^ eggciBD`X$vlh:      0p\ )SJlQmv\h VQ9ݎ#@ݎVofnAԗ |7AAAAAAa#)U;O_66#)% pɾ^$I,.7]o      )Oveʹ0B+V55wƼyYdBP`2fvdE];G\dd0$:)3=2v>txȑ++0HDTV3Qnw s7AAAAAAa: +7oNoફfp]a(+ENܹc=,X0 Ɵ>\s[p9Xl\oܱHaبid׮":k8[PHtjDQjaCEMAAAAAA0Ր\o*6jj0Y1BIMnw9 <^\\9QOdd55M(7d eȐxZZ̤.۝XvnRAAAAAAf.^}u)N̥NEep:e$IBTpT*%+WnGTx-YYvru-^ǿz#B> NPr,"&     r$I">>FlAzr}@suxssM?xVU0]AAAAA8) rsHI& @KCC YH{d7_΁唗מs6_DCCn ~:sU>     gir 9f6XU/; -]6X.] /|r`Gg̈)Y$Jo     gwI(/e45b --`8|yFjjjc۶FJ]nU OxAAA8t8mP58'- pQ(~H{XRz&k﷿}DS}]JLL_+dرu::\NnqS{5N{  p12s>Ņ3PH=Wo7ŝh"&> cPȑLsS#e)<-OAA8***.]M0]M ',˔TfǙ7o< ~;MwL$o}pј_pW+^=?@>{K6d7GA,5c8=qI۽r&$[}K&0jT*56@pH!a$U`1.AAz/(HwS+p,^OQQniI 8 Xj[OvSSLf4J5H$30PH4P98Km~_WuY)cHB 3J$B#w55QGW :7F.@Ksc+jڂ pS$dp>ձs:jbnmyLP0dx.*NksXRP!̄Y>MAq/Mt: !!;])ժ lmː!q(JkٵPV%%̌GΝ& F{Ѩ 3PWBq)ZJKOAGvv2!\.jjس6uOR 33Ht: mmVcRV~4NII5vhbȱv @hh5"IIFTP[7gy*3Llذd747Zwͧ~*ݳtp; OvS5n("BÃomn.e7//9sc^ge_#,WJb}t:c6U_e_c{+0/ODt8?,[_z2i\R2w;XZ>Rv׹  #!%ZC{}j1{feui,:jaY3 OUEAN5Sd#Wp cf? @}M9%""Tkj_IHddgx=.2_~W^Yi#Rx꩛xWپW_Zͫ.hjn|Z= p8Yz555]>h^x_?@cfo{}8\s9ZkyOغr$ne70n؇zc Z C]q{{^+KNfc60J֬ٛ/Ѷ|nљcoǏg̙;GyΠ5ld7[SL{/f϶N4f{ c'+޲s d&t8#)C1جL$|r'dc)>%1-iNd%3HL篏]lYxUDD}8zntZٲv'm-m&0fJ./Ndt8x'  1 8[_6`_EYNREphC9f IDATagpfD'gWxm}[eO>&۶u=~*6?NBB$[s@9FɓoF݌8}V 66mO}} L"1ă泺jӦ46Ȍ# }v.?aSSޟRScP=>cx[̌{Kzz,3(NO o> Cyņ#==C)G#Ixw8 gOq3O |ફٱsn;[^^Kmm׹wݙk퓎2J MLahj|:c)/%0нl>Vm'Ϡ֮]믿έSO=ţ>JII`_VNT}LA!\}%|r>}Ks/\%<-?jS]f}s|&R2c9 wO1)=z/m| s'd;!Yˣ(;b튍_G|/r37mVHڰ7uIXVʋ*K}9-yvtU]ӯ91G{][A_Q1ݫmre%"00`Z[hlt&Ɖ1NBRQ(Hw୪ᄶ/n<"q:e㏻=^~y1DW4?~-y9ODnn*_>wq:eVOfoucw l/֮ڵ)\}QLN, 33S?᫯6zON6qM;uZ}2PBB⊳M<>$'sx뭮W'w8;o,?IJeGZ5?|%Ӧsn}w4/ϢP(|ȱ>׿`֬lNyGQH _!+}Džr鸋V7xYO 䂑2Hv䋭Ki:?>,>fW$'p4Rľ~gWOt~_ʼ\8|f ?HLau!.a]Ex`$Q=|u ]`Xm<)c3?DEſ9vG.-?.~2eDZ+j쭯|/t%g!1-O7xM?,M¿ӻ45x-Jw^c>υ{_\=G|WT;^APRPJJfr3_#v_bϹ M&53 ś+P+\싦3i"& =H@uEm=}Aς'5JJ"0НOGL D'½.;33E~…wz¹W,^W fkgcú 'b' 0q7o?K/Gb.x2FLg9`cc+6 |fgƏYge_x7c d $%EyUTTSOe*[oG 0{<܇Bhsg'.[1O"ILdLYn0m'D]/W^!88~AVRx"9 $'tyNNB69 ˈNaЩRRJ⊉=BR"yڿpK냙{./^rsʔ7l"'!rv[I&52Յݞq1$d=s~͵F. ~ˬ_~#fNEc%DÙ}OEvZCNB6ӇMsᆩבNCl"<3ow>Ĵ8NG܄fίyQ+>eU %9 ٌIErD]4ӇNl7c[03+k&O-=$aq$dަ@zױ:kɎiV8DӖ;ܒpU[a\rae/BL%,*um]Ab^Q?̿b1 &quׁaVc/&FyqqSGI)w_Y-]rtTW/ FEi-kV_Bme]l\<$u ™` Rxj7JM\b*ѱ/@o 6!!Y$f?T*5#NpY=8s466, 9MNAbD90a( {hٲa'mmOϘ15kvyDh8RZZsWuC~n8k(ZZ}^5}2<4ix;vAANϱXlY :'@NlWsɲ,˝L˗/Ի,",:1+:|Ȳ̝wO??ÇOdz$IwͺJKAAY,i2s.hx^Z70;87hhsYrX{t5jk_T3-s 筢9p;eMhlh߾' saL(nznzBRwQiY| @>~Q):&w5@aM^7mTʘH~o]Jqm1*`̾IeN,ڝۗzϱt෌MZ;@:$Iw=l*kbpN rdPM&$$>X'hn䣍 0կwp)Ӂ<1R|*ƻ}eW @eZ?7˝'>/dwy=ݞ=t㆑?'fwر;;: lA/!u5_(A!F"w57PD]B FeY Y17{7wO,͍ޫxO(-,] qL5go}s\v*|ίd@O _=K2GN1˶E޶|];>U ulB̭-\2a&"MDF1X| Ng=c&MGr 2w6ʞ hSvTqO܄jL_b98T]o뛻٨'AOmAuf1_ 1ƍGSLo./ZEp;^^޷O'#RJ&++Yٽ~[hi/"}Z{.8`>߱6}j~Nx `ٲeȲ= UVvDʎwoWTMq] )-&%2cO^AKfվ5\7jƧ 55V&c; ʚ]Tt/?v }h42 xjβ+C Z,GK,5[W%( :=g}Fv zM#sXW޶K}šC F~𷠸wxF[&N:g,6돾,z̖O=?~!iL5ﻚ׹ 1MYާLw.|$IbͲVlJf"p_q:U;6pT)J \.׹zD~!6;.م6@K[KΕzdDyf  @bJ*AIM*^޾]nZ|>7& a)CI"?/J$aQd '1emprT*R1Nq" \{l@a~Q>^YYϞ=Ede%3cFW0c^ֱ} jS'{]̞=3 $''Tn<|s9|F{ =^xVSNXv>X>o$g,{~[UyKlnu+2@rEAS\X,* 1w.߽ iHJ%)"+X~v-IRqmq d ] BҠT(<1_Dh?GUiI=Zt;PTEJd2I]:_bqa_ՂI>vg{9ԎxIH$gpxByq%o^)!5qq\,^l6B}ۂvfb#|={[V\ NZ",2S8Wqn=8k& g Bv-UjZ-]V+9ORja&z 9lYP:$q7v;KݶӍJv*00г-\ee(?չo@3'™#_Fc+|ZCqquǖ/JVV2'gR}$..8w[V|tjp|'elzI:4SG0on}c)ױ3RϵO:4:}N{Pg IGvsA̰0*ۨ/u/ 2qeBVIjpAh Wbmq5zQp'-wr{ȭz2@udcjڽdKXpdî($Z,^"9g XجHD}k= /Nru0ܿx1!\?Z$25 5|~oXK7~tHv=5nl]dyK0lv[$sذk?b"9!_\S qm_zm)Gr/aA]Fw=zC͹$kP?p{zwn,<˱x7  b 4)<縺TW&,„1(R3ǒe}4Ȱ?[?I.;3lIe1Nq"ݩN‚$OM6{5~gXjwu!q2Y.ϳΝTWn>EAK1>1}2\.{wo }_)+ٍ:{.џsXȘGA'RWLlV8v'uV꣫LdGne*X֜$Njw먿wqTk{-^0Q `I@ &ro:!7 ]B۸۲-˲z^fkJ^I+Ҝ3gGGjs}>k  vܱ80ao-OZGB C[e/_/Q*3i1?lx92ޣ.A9Ǯ)fW\6|~F.^cTs0Cqxl}mpc?_y%Xػsaodv'/u,f:o.Gwp6')`4.Շj}uG  Nr dL;`09kET,nx}7o-!2TOZF&K.ӕd"))xL$# 1OX;oCYY-7FE^^ b&Maƌ"^{Q9)s#SfΝYx3fVBG+d,uta^VX٬'1LKKxRYr V4E8G5⚜LThƜ' .݈D׷|UL2@RJN?lkuuC9tRnv;?yJ5ё#SR:@㑬y|~/jue@K?Eofn6ofN,+~{_o8_uqyߘ^5 :3v{{֞Kӆ(MpбO.O:iwكj&)-=)x<^ښ;"c0Pڲn;g=F⋭~=`'Qf 3ԔYl[sM{8뼹Ѩxb>_cm39$ d/7AA8Jh$ɩP*C #(}eR"Fp%7Ʌy"DC!*$%YK$|1Ν}-v>S䑛;}?ɤI9,\8 G5gOa)STOvh},^< ʕY:ԡc}MF#1B[[z}_nsmr:;mX&nB~Qǁj"9:Fcsm,ɉ:rt0fW BBlOgK$oJhVJ 9~RQ,l5 S,Ƀ>no3Xn X#wJI.)YҒ`֊m#Cbe YJ\ M߬%Ėmn0q,G7c ^FR5& ĩ'+˪}qב϶x⡿}%X7Fs묔.Tå\ k$$Tk P˖²G+5?>Gf^FPANTwnw^_EY! VW=O, ˉ?{@ iQNjkMraHbD1\2?-993Ϝ48tꪅ̟_>wn1=vKb`^}#UUM( {V-kW*{L{I96n܇`r=+쳽8nݺݡrό IDATqW(,^|=҄G5kY3Wr>7I Vpy5,^|{̀瞕s5Qk׮{e (陚r#kcqMNDlXR\ڞI~! GcIBcguM͉h̉MIh,ɘKEeKg{}t iĜ=%lKkNĘVONN3f97KrT,Xɞ:b q$d.\HbKxV:|EJ4.oA7sС˹$#`_8'[*Oè5bw` e+wRR+~O_Fy!ʛ #/=Fz Ho2g ^k4ylT8)/ Wo\KWG7q'[#S馫3RDzcn R3)O,x_l-t]>AA89Cnv?ȲLWg)*,qtDlgPWym|:[J&'oM58|{_,<!0ZuulVƜ9җ3mZM$&>Ix׹|,O<*X&yf$2)-0kքA<%%-3y p ¶Y_ϟ^^^5b??<WZ֜ڜ$c }],!!woRJ=oG<sTo֖.vx{hMV)JnY!}o86okk >3#g:*e_) Zu66f}=M_PVq]a3w788/}2dY4{pL_~ @˯[Ƥ郿XX'몆_< uZ_8 jJz](lRᚊcQbԂ}fqUK#c-Gi~U$$\xR͝L `cASR'T*5 ,cy0qP^F3]Jplπ> &LfYK+BPՇKe?F~JAcϱcGĉY\xlf͚޽Uqx cjk[?燐$ɓsYt:yyiTW{Yfw] Czz"0}zFÇ/_◿{}Ktvغ@TZazj5a"/w>>=h).h?wAN&{y_$ILł1D k7qmb{lR^^F37Tn/o[o1nUUMsϟ8tJIII6s2ҹ&22–@zzii(Vpds2%Ŵiu8F<=8PꌨM aJ ^G7zn4T̹g hC! &Z&эu$ @?2.JJ XSȝ6M\JFh4`cMM uuu{jP(4:T8RZZ87rя~+-̓7ֈeˡ`dK2 WY8,23bӡ-a߸+\1RvUR* gtrY4/BIM{-[+yNb^J5WWr݂k2 I?=͚obsE R ll؆dR$f孯z a- mn܉NeNlCnnҏX֞~J7_D_Y~@Iz1>7Q^YgfFt' 33x_𭧿Kc44w\Cek[}_1-<ߡSk~81j$ۿv:u6󵿿?6k*=} FMO_ @EXf|^(f0kϾ7\eC9](ǃ.~"?5>vSfd1kpٻBT2d8n}huz,YFܑr^6|^/z5I~vltЬSE&.'y"DrrRzTU55ncIM'33XСzFVZӐDRRq-JIFF"))V$I݋:I\df>.*+FMM'--^KGG5G4$P(tQY8S,IעdYF小-K_G4j5999JBI@6JeA@$"Td*I$uΥ .jMքoY<*DNN++~$IBx\R#(J (++{AR( M&&Y(J8 n~Y ck~} ΞLO~_ e&'1~ ea<<#\=J.</ e?o-P"3<9].v|y<W#~s%ӊ(X41C`:tFXXzw:Q>ݷkK){g7W0uvרgAy\=Op5xٙL;is'mv>`+mGՐh5e ^[oԣ7ga*Oٺ$sHID5΄m-sc)@ނݚ_dIJ >qeu:HNKoasXS) %X$v\G@0F 06DiuzL8j n[O7^OrLMfT*.[O>olDƛ $DCADpdEkwP3WJgc-<.OzPΫP*oB~9ZM8njk[$xQwuul6'sLd\?vjjZ(+ 뮫x\vdb~?zKJJ'rmu ѰuO`[-m4vښq{x  Ė>>Æ! pWq}\yy=1K}ѾO%⚜ZރDgKp<U4t^*.;--]8.GUAԞOAA:knj"5/Ѩx2[g܉x~ۏ/իٳ(w)J-dQ#>~[     choqqq1k 4/FsV:599_;3^y#;`:^,keJ~$I(y=+S|(q{"{@ kl.?%pgfFd9R8NRw  cLƓ(5)DC!pIM'1q%N77XR(dflUU8.T+5Y6AAr>I$^ 999$$$)9pC)Zr*5 ԭ[M[ o>ڎM'_IAAAAA!&fJeJK{'qMNNF+ZFOu[Mh{^^LYFodr 82qw7>uu5-6DMyw|Q.      tTU/fp80QւÔUhEyj^G1s\_kR  p&$JM %hy" NRQpСkkE")2CTŔX&AAAAAAqztV|m5rx.--RɁHN@T|<֬و,P(t9.!b{jjI$^ 999$$$)9nG]hQH,Sn5{{5 .d߾2@@ T{ua_v?ั o      BHъVASVڞGii)%SF`[ CBڎ+=xAAAAAN<.!Ow =ޣNP %W{=SF9}ʶ*CF15t:]Zn}JP>RMQH{9FAzzzk5xAAAA&)XrOEujj7;y7j K?OO{m)IҐ2{9&zsvkjc ad>* 4oon5B\L4D9Sh߷~GujTjr/&yk)ݸ |x^:4`{nڐkQ*8p8J%˗c͚rB@KK"chodxmmakj}@_AAAAA8-%N^LъApyh>GjC4do0 c覻rxmT! 'xmM tWAFoj ޵߮f)J_e//pwq%NV$ y}V:'b{MM :ef~^V\ǟ;ߎ]BAAA8-IDXD  n$IA$yp ^xh@3I BgDY&Dw7KV7v ^@FdҡT*Ptuiipa7X{jj<x<>RRK"&  ih2K8@Y.d9jgB:~PWU3-DճN:Z:FAĥ$мA7; d5= '{butz,?Q*2$T*%*{{W"R)Q*z7DMAAN2sٿgg%&"Iy=ۣOӑSBЁ! ^{{ upG{$@XPׅwt{}>Ƃ  pZQdO}#iia뉪 %NEAK֡6rw63j-jOWq)WiH-^Pjz29'T;=YJ9b`/J KGeDKjc<d]j8GĜ3]bGSa6R*Fk@yݧƒ)!^tKcH+BL non xAAᴒ?Z롶j7\N=P(+,p~Λ  $J΢{?+"> f1ux4DV܇9猰Vۨ%fQpw1gO ȴxq5s}}OwAϑ: rλaǯVQ)Kȿ;m25ףPk9Vg.C _) hۻ{R#&P) gP ,8iԭ{nȀ'9{ Wދ֚Vwʷjz<©AAAA8L@uAjFv>:YS]Q6n8Yt!1Ohyrzt`L+ ~")BK(VO "T nx]b];7B3__~(cz%=`qU }bq3I} ?9[סx흨V'/%N^@ǁMݎ^Ga )HV(^S*Zzj_kj8}R{zg>FsMbE{јP(qTИMC5ZÐAISf % Zq`#&iO2R0a2uՇq ࡘL&8l6;=% 1OIR )G> I %³]<]sh#}ׂee?_~ڪMCPi(Z*[>^ ^{%wūƒ{ ~Ëad?m6r&-"4@X@6q`cضw m\(}c7ya\Ϲ))hI,?̕= g6*qJFᵵSqՅڵT&~! :Ӑ\ڏgBk?ՇRk`Fmkş`o klR^Nݧ#2cY(v'iڶ5&)d+֢\F]FPs<MqPO9@NN h fPˉiCx.[1cTVV[(Tj$IA@[EQn5 .d߾}3"&  i7Pv85!d`'JJd2BDbD(׽z{n4dg\@ӶaЫ?ǔY<`s.Cşg9V_I XϫI{9*Kt*v 8OoIס#zѰ!~|A7gKe/d>bqMb?IջbIR`ɛF0e1l;8O :X͵IkXd^x[Ym;$@4o#hOØ1I!iڹ']-hEҠn mˣ) ,;t6B ~?@[ns tKO{+P .+q-=AA`4[HMƩRd3#LBnA1kBN JG[ߺ" )d`05S]gg']Vu$<!p*A:ײ݈d~}~,=Zt-@mǘ^8VW[mɑlofHz.>gM[_Y1&ǃ.t-Ն?'xC]vH\#|[۱3'6! R`ʚ4`]9b4v5;F#UKmvhÎ\.jjj jP(4:T#F ϓx"*NFfwqC% u\9< Z#.:jkhd1RPlawP}x[Aa$zMf,Xn:&O7`{Kc7{:=>6a42sعeՇO?6% ˅˥EӉLӈV#01Oӕ)+Fyp4QJ1v,|)CJ>KBzɰ 0]|:~_|8FZ8~ﰏ55IR6ţֽ m96# ab1N$4$ٙ0L }lo|fGK% $4-q"Xڵklskگw 6nyKrh+.^{ojo?PS8q}}|6ZaϹ9w6uϲإOU\E3Pؽm<"-cGx[AaT*59y(ǝZv TS3HN{k#+,AP񸩭z v4|L҆_7S%x\w*KLLrX1ONb+͑l}0e!5}{&>rf~Zz*a%zr`o88FCT2XpuԏX^XP($ϼĩK1-1 nb1N$A7Wtdxm"޹yz:4lW撚ϛon%YXf#cNMnwXKFF"Y\" 99Ir%KSVV˞=`pffFj5tCVllckؾMFjoseΜ9<TUU)Q2jťR~ҖDoZ\*]S,z ͇B܂dXӹ{ۿ峃SOz{y8.EKc+ք8.ɴ5=]S܂  ?Z"I0oy c"3-OaJw 8^PWX @UE?prST|cpD'J flʾguRF֢9Qʴ:RYI[dx{FL5-9A =7stJLf-IxBF3Ob1N$*}<#'kr@vIq,ɹjUw\馶It:5˖f\?ןǎwLN755-}?]}"lC8q$&Zhl .Έ)--`q:(k1cx۰a_׿#<ƒ>HuɓNy:*L)@cxyW-t::yܻ0#`xh)L)knd8y̸ KG[ښCm~~R2Xq%Ns  FIUWچer:m>\FnR-xG7,XH}e~oEb2Beč@[[zGK̓ӏ'/QF+m]ylyuĊ/> Jp ]BƴBhш (5ѕGh^q\d\$"^~R5yv"+#'m rY ɞ8vR[´i(sTRe Å"55t<))P{C81+P{wONN2nwv{ykAp.;:ii~\x{|Gfw݂kV |T W̺s&/%Œ_hkvx4.y ShJ@$m˫;8豗\άYlF!)d,)YD5 Yv}̝ScBj!fIek_Ǻ#23қ<<I$bΕLϙGϫw¦h(%3.fNlɚlIWp~ozCԭ+f]ƌl<ꍃq:3rgz aA7s_W<@)t#K,,>\L:-|6m*`w݂/3!-X 9\q#R)Y,^ڰ@wg/5-}<^k8_F[w=OȖOwF<Ǽ3Y|տuG}nAA񔚞\IHJlT*V01>FSC  a:IF7EIӃ y" EtsoHK ~(`@IFͣS{- ֈp6eCnջt5ё}|.* m|5)m\ =φͯF EBcN +8\k'OO[ϓƜgjsbc{D:Y}ipkez,HR8VRGT*BǭY YŽm?k) {#I rrrx!H2^u~?v[(V[[{G 3>iSil,*>zTILΜ>3YSxШ4n:$ .zG/dȀʴ6w]t'gM83ݠ5hJp7ν @SW3c5X}3rq_=*UP2-{*@\ZC*- hỦ?\eO>JdZ:6^ozg=9Yw<{Ogc[E5ܚ[Bko4Y(ACg#fNgFt/xݏzw0A,싈ں˃Vaʬ{7;L~q.eմ6BHNK\N'_ F{nAA[@ 3e NYK/`)uïqݼUke0BOPTrYl7d2i!n D'Bl :c({C9SA`H\9>n2LXӴmsY$L^BGf'w2N¤XrPY h&#? h(q?r4DiE:5b1N$Jxi%2Kh߿!~|.3cK.a׮]cVoׅmn@hp\H6] +-o 2~;>(?#[p(r xB!)dz.y3|h+=SK%ߺШ4|QkGR@+?> R97s *Z*Ifq"Κp&eymZZP)UXqt8|2.z>^'}zdҊx3'_>s%nXuK]֊lM9&)Οr.kvv?>}g7<Ӗj5ԶK?0F˟?;oξ wn;_ajf_![S&fkWo=@Z.)Y}9kgb*;?\Nw}dLMEE ~}F&N-dsƽ7O yP(7k>~3h- 0^TGJHԌAS(݀*^'|0BYx-V뀲+*xMra(bDƜ}T8I"e1G.Iϧǜ5}rΐSf1%gaH+8M?$xgbHÐVоoV۞ILڂ_|B߼R&p9}s'Ge`-1/eC%VsVc-9{ =5{#;<ڍ֎ڔ@̋"ޔ֢G0eʔqoO>${Xf߇^̢ya&[rt /tttҖZD\Ek:2V¯M|3 Դղv/^Ty+xur?`Nq>SXV{?.XZg̮<>v#GGs'ǦZyWѾOpo^'2 x >I(HyѴ Ѫ1H$SۚJf7! ١zS?44Q8)V]~u(GCMҞ8  xؽs3*JmGvsb qgm=]Cw^ =>~U6|(^:q\'B4<b/'eVxBcIdON?p4UU\J'ms('Hs/R$)(Y3s%ĩKv۟'es\el(5z/.6R 뒽F2_Bw?O$LZȴDE5 Ga|`iٗ X̘>A+%;yߎ_&$)H?k%9,k;ֲb;6_TG~:;PIqvD rەxq9\G8Gn.]u!{vGBk/G;BF[,- 0!]M Ylt?R%.Z\hC"t:4<& 1OrUYͿ sT-ըMXU 9c|)& q_0Z%fbL+VI\A}Q|ƥP|Oqw4lEbL+@ ~T͐c^'eE2h]ב:ҰmޝGU_ ٗ$}BBDEQZjնZV־VjZk]qBd YɾNf{$ Y(zg&HsS/&}9뾚<{F~gxrθ}:vКeC ]P3/1̸ Zm@ p*Im>}'|;Wͣx{X]L㴕nE pfǚfd|>F kmLҕubeߎK/Z >ѴS&\&G㮼1 6`bOCX} e+cMo4f:S"/>mP FYP>*^ 7#SD7b!+WiwuW,V[;9N xlfj.w;SsI'a1Y0 {QK/c3hfuȉNd䓟>[esU=$9^fZrs ܣ_%=om/ A'%}~I%^o]ϭ߹hgoʪum!BOA|1̝>)i %E;D~w&$2LfE;l+BB!ĉՇw=$3{<봕|jsm^.Xg%WIʟABdr'k,l! v.r鋰daMɊJu~XP #sfmJkq1Z)ָk&Gz;:Z2OYٙsԄX{oQU uHt]pt2O]иC}3W}ΚvşwS픯|^Up2>2T`r$:1}DO.vNr_OԳ1;\]5Ԭm}\u }UXHIHAS#jT~|\=ciʕ#{gu]?[o[":6;,=𲓳q85f*|Ďb7)D{bmc'z IDATdG2^mƻ`_mEg64o]?@Wjh+1h5+׳ dm!BO4MBZNa45 $( j$¦VB!> Xk[%maaKƖח4C~J̽X]RsE?D^Ο(}Ono[lFaI@ 4T`xwc[^ -SsR$e(-5Wj(#Uo~G8jЋ58*׼w-E뱥buQ>"=2T;l`K-%w55Kb:Z8p{C5}u/aLA 7Wjk5+8~<p 0ن>-])_IFb:]mx۹ډ[ރ'0`NV|!?֛D1wwIOL_+[̓YK==H h;;}BQ>^[!D4n\Kue)OIjZ&޶ O[ˑ/'B! 4Wh={бl#Zwh|5{PDSôWl^t]__3TO5?X55_?Ot#x[`w}7^x`線;QB5.u)DA/~>< qYw|J!{:"jZT;66#0`Oo ;~}TFo?Y|BfvUS&q{/9k !B|߳{o]Mu5UXmv]A?6¡O7K?9B!B u|>?%%CidLj?7ltK=;WA3%vN ތi{w,,OyfPϊJLftZn};Y.7O#@Ql;WjM=9gG-up/&QyO ]\z,aﶞi9jm!BA0'\-g!Br@)WSžd:#KYô2;y򘌜1dR MՉi-(JGk'Z㥩"'EK u-bP=#u蚎bP0Y7D/8LuƜI`ƈ:n~:9%wl43)+ZLsmf@8$'wfV8ꮃ{6OǛk f,fm1kتY,M;ލd᥏_uͥxy(ʤcN]ϺM׸ϷN'72y$)V]̲46Ney = d_<EQhkw{ϟ-GwFxќ/=r)_F~ikB!B!':ՁٙrLa؍rrD/b f>%1<~!PaEciU͆ظi( $aHP#`qDIs4MhoTmSbH ۷oXY7wEӵصNW0*9f`2:qϺņkGE_!?˷˥\OPRWkh{/4uZѓLIds4f)܉bgK't\JFsCKO2+f]ڢu) b?̻؛/;S=ۺ~&4]߻}ecnV:v}3uD2_ݣ][!B!DW+4X=pCDߴ7yO3yLFFjneH̰veppe; #B7HH%T%Y P E+F=S_'Tc랈S [g-PT4t'zML5__ >.و?grdN͟ k8 rǻ?,Cl*݂leV8-|»ӯrnG4xd_LϤw^{6Ѭ-B!B! ZGKU{Fz*򘌌hFC`[뉞SP!]1(k&u҇0g1 u݂ <{ ?~nٗ]}˛c>.SϽѱnvUOr _FsӼ/hoH3uO;TMeM)2R,Q)biZ}l1kd:zmsXdUt!ўpZ˹k9p6LYk<ڵmx)$HuS\p"νt>0nJm-l oY<t]_s6<ǝ?KϾ%ڲB!B!BqFK16t^ 9IMMLL궼@kr3Sh7%D z 7a%EB:b#d7D(Ē uYx %)cg:: -B1c骊b4b:ȰCjj*5̼6-GGm~\^MuΌٽ{7l]qޚԾ!}/vCnv^?9t>![G.23h*~0䥍ƠkkW݅'A>f9:M-oKxLMHeO" xg6Jv8hSZ;l2 dJ'!IKcɵB14ho![;e/3;f8p`D&!ihuRm[8_ǨQ47̟?]va;P[=wR\tfG2jO P4V *<=Ȟ{jOk Q &Wt\c%G9k)*D QC~"v+H]Gx*v2}]vq 44B~M%(4VhopYYWǖ!++ 6p-p8|>>z㮼-gSNb,Y B?[E}kN'O*ǜb)a_!O=jЇaIvcEX\]`Įʱ2bͱy2yQ#Z!t5i߈/yNɌO}5A_;Ao;\c2Gڣu@ f#:^ܮlCiowfΝu7!B!B!l'oz}֭Ѵ{ +DTN 4ҷCTP]C1hܹ`c0ۨݸ,n\g{͆n\b0k*b8-GF F#jg6CUo`0W8Xۊ+z}j6 `ay^^Fe[dٖB!B!B!/S\\^(Tո~s."E (p0@[}u,hظ%KfvVi4ud}}4Z T.p˖Ct 룙mmL;PMill7>uj&fs;>HS4Mf E0 \xC1 !B!B!B݉dCKM%:P_vZg4FII5^xľpo\Eg[talf/>5kvrk`4HNNRQQnųP7.?ݏ .*S-oB!B!B!XC>\Q"A?PWi -TV3}zF1v=!d2꥾/5v;c "df&;麎b]/+NwΓL}}+FB(rEJVV>,3 !B!>hooᝈQ;e/3;f'](6eGz[B!sdvi}4=l/M3 ƕWi:TU]W@$~wg2cs]zhfkuW^Y c64NO֑NitC2B!B!6l"sn̍(ڑޙB11k'ҫ //T,cgYCKK ,^51(4>|83ٽ{7=4DT^z>ہǵwg8mf!B!B!1C'at/݉oEϜH{Fz[B!gJ3G2vƊ|*++c5v/_U^}+_sk-ҥK&V}?),,;~xB!B!Ǘ@c\'ܙ#!!%#1Ii6QZp:=j,ָ^@ @EEUUU1Xl,6LY?tx}I5)B!B!KJ5ٍ!1>y[n%//=k灮pJJJXbE P Ft]x\'RTT4d1$&B!8!)+|.'PaT.p˖Ct hbF;7@MM%F 4622\4MgPTTɎ:ړ;VlBF!^$a2p8l躕Tj# !B!N8΄Dse%{)ڵM\a3^ oTWvHIMpj<} !蝞 B!a /< UxwXt!eA*+Pf3xY?'_͛hL zBQ`ܸ >_v?.kYo/fd׮]Cz5xB!BƎ +g {vl 72p-g[+tsBNO@i!'*+>f2imR_ߊ v0vl6PX{j&Lȍ]/+NwΓL}}+FB(rE „*afJt˽KM!BqB1̌.}hmQ_w`>p1g{? !&jRN !l TU+B4}TUCQFcddժO0 q˖}7bŦvрj t]U7$&B!8.l!QYֳ6`>Z؏p(xs!蟞=FބBX."nJeeCUi_zø뺮tb#Ǹ탙sC Un(eV!B!;~2%PF f-yCJ7;B!{2uԑ IoB!BFfv. .?4KHH ==ފ׉l@?mw"B[ґ IoB!BS6»e20L$$8%"$EE5 lI`0΄BvmƛB!LL @In&Ѩ5=$%-݁TO[KalHBc}- 0@rg5!:D׈:"ggq _gw'B +WNXxB!B:Oy=Uww&$2M>$W5UlZ` 0Y9cS812MWX?|V؁ Vl6UN V׉8 H#'!B$&B!8LfPZ]cmflTkn2ܣz2T56j<̈́a\Ǚ1xڎ߀AĐ@VvYrϮ4a׉_kIC$iO0xS;B!qLoB!B^^xLf3(t^ kL`y$R(8][{c1}9kELf<^>轝hL&HM!u"qg`Z(u!D 7!B!q`dKqm&y>1fxR)3 }򦭥]L26۠V~*jjKXSS+@cc#}K^'"!wވBq9%=MhN$P0;/ &5o 0єn`4FMV#ZDc 6ׁ( N` . m*ZDCh(32ػ:ZbCh<}ؓu@7ls}8Smh-ÄҢC=چ!%&B!8sIHc+ٷG0o9HjĤdF#5UL2WJz\=B<4"O `4P.qozb&5q"BS<~!PaoFS3idgh673['DS )L= jXꜷpn6hlH[kɀ{B @#><;=[Jș@ۉVqhYm f|!,vjXK;NdWKOI݁QfMg_*_B!.c u3fhw8h͵3,d.j{+j?'6b=ǣӣ$""S IY#!JMlsv7N}q+D jHaNB ]#/kB Q!h Daަɘ2gwYл D*$kl􈆦jHPE @ǩ'dMQFAO3۫xEO?~44 fJp+F觩=m^`Nc'擔H0ڃG~jF2?H{w8dZ[)[F$wӎ#>>~Ge0jL6vw 0%Bq$L6E1g?k>ؕ61 ӳ9~M GL׉蕧!Tc m fJ7v. z2N1(ZW]ˢh׼WQz5{:7}_qtOUw:s}=Ciof;_~^SZˊo|>k&vY,թKO;˗sxwqͭqM5-:S'apʙG6d6qѵqegcwڂ￵מ}`E<_y΀{ӽr7`☸p2Ǻ IDATUo  B!`m߲=;6ٞW0@Rr*U=Rsx=G4񦥥v%%}QoB!N09R}( =16@^^T4ݫ-E!CWveWr`KHbݿb% b40MDB@kAn>o]_܋bEGȺ@Qf%XS=_N6gڣ D[P&PJwRΓmԼ(݄mŖ3F c?쁷9sp90k,>ʆ{ʁRϓ#Oyw-2vnًj?3sӝbwXҪc鳧Ăn,|w765M,ⴳOaܔ}N~vo;0S\|OQnqIBaU:y_2W,$0|OhZAGNEIUp/fXĦzy38ﲳJ?KVB!8>o@ d4֖>&\)47jpQUy3iMQMvOkNDL!q(-(7!'g2VNycEz~~>vb w |Tl߈l3@Q0WDUUF#{.jK`H?cU$fd{1Ɓkclb7]nÚ9f}LtU%\!3À힝k ֓I(۸e|_'1Sz-B1l瓒ޝPRH딾3!Y/d2(.<'< S,+f׶M}]Mf3OKJjs{vn9BwxsS!8d$f`OQXFMkW t:1YDB4Ue9گ`]4O?իW8vM%$g1IATtL+6=a?x~q…,_ps }-ܕRߔbJL%X[q--@ue{mwNތFH_X{usn} `I<;{Yk:v> w< i2Yd&e8P_ʲ-oR0;ȹSr3Mf{S][[aouQ1_8: 3RVǟsxdPZdY|GcXEay̛x&cH% 8P_ʪݫY[\Mᒙ15g )"j6o䭭+Tr6.ʦJyjRW+GW1w3_ξ fu2>'~״/ 8v6wX~nu$x䇏ŻxSy}thmjG_?fM&#+~^r9L17.i)&Ϙ@E[{)_A1L1[ěS/؉cuB\ `zs-]v6]:_oB!8fs^Ton=hFBr҄׎d&%5cG;?R!2PT)i IJ7{uV8w+MU yע_TVPw FtKlˠ#e#yT5_M}}Sg?k{VL{6n\lg~ԳqxBd$믣*_Wcʎ'j)(|ah@)&qZl~~'X|{5])m(lM8yu/ǯč;\~.zߠ`';x F^5b0}I[>EaqpfjTԐcfp5qz뷄{G}W0xja5YYx8ΙS=@[m=gGoڭ}$O+eƜq/`hmc5_SϜAzֽq{YL=ώM{N%! wg4շ9Syo̸\~ㅔ-g▻wη~_6glj7_>}G0q[8ﲳɟGRr"m-RD!uZ+KxLR2IMwǵ,gMxZZ&O?Qq%VӭR{px?(5=)p>y_PoJ!tӖXȨyK{,2f.nӛ LԠP{&[Z$'1΍b0EN> o><;$uA|-’[OWUARSS)ݺ^æ,tMG1DrzhoÖD$DtfLݻ_~qW܇f?5Hoi~<{<{ph9X>vEk|)(\0c1,U&d0e ]M]ιNt -/7us,:)gc4 aV~?>䱛mmL͝Bc{#yQT޻pܾ6fg|>|&nlaX핍/(L} Y;{jnl_}-+7D$?ȟnιx?>g /G8O>Ew *gL6Wʋ+yo B4]ԌdrcY7G/n/K&olD^O5B^aE;Kzx6NQAc=69+7( "7!B {A NB B0F8k`2It`C>܊B p60cJcZBW{̙K5`ԙWk!?*@!XIl%}ƹ4YG+PC~_٧_b0av+1v;L1\@UDIB"0rk.μ,Ə}4UXQfg+ٳfs2!-=Baa!r >ÁԜayl wZo6HIIᡇ";‘:" 輵>9YwV8{<.U.W͹"- hJcMY},躎7 dQAF>g tݫY%zt󱚬q㯞s%F-GP5ǩc!! `57HMJ+Vm3 sWVNͥ$%'<(s&]y'Q{UjfJsԌ>|+Wɳ|qtή-E؝v[{Yi|ޛHHrRoVo_BmX$ cqectk(*9Og{LF5c &3fJ i&ٝ˘s2GڝN'O+**٩b7gc|4;3z{w`dRDoϚ̛pFms P|D_k5Ǥ$g+v˒Hfl<9n)d'gws l>toq&ڃDhx{^6xZSƜ̜YlSKc'Tmu;5֙2EKxߖ2f2Y 'rs菞%?_<RSY777܆+%SϘOz LJN`Ҙ}әx.u: .d.?~Bc[xx7Bd[cŻ>(x}l )%wN\3ΙoxesEQyn~h^ޒe\~_Wj6GL!B! Gz B!ħ^!Ok*Y]Fۏk*( N'hqj16[Slv㲸q5귽׮蚊:2Eu֢{vшYBh2sF}+Vz5^CHGmDo+WD4X𭶶uܱfMV*o^BAfM ;T UkL]YC9j3>kLY#vsbV c(?g6mF@(7tvO^4]O]/[5wP{Fkgo1}1 T߿̢͚ϝ5[pݡ>xg,] `m:HHr݌Ji.nZky7 ] :r}tMb`6HH&ݝFUYu8GEnUvRCq8at]gʔ)޽FԈFm7ͦeTMچ!LG,{:wu?O[z3'koP-p${bk+vq*8${R,:9GYͅzh~=tk p`#UP[঳P`?=7~o1 C$TӿaeƜrO{8X^+%sFT֮\%ǝڃ詳/zN;{֟M;.\I]mm-׋ikcU0I<|8+ժ7jl^f>|'i)]xjܩ94=t;PϙB!B!BY><[k:]hj-HŎԋjM\qãt;Zh-pA>ǂN31Zs??Ltľus(ӎ}ݘ&嗾y IN) {ү\܅8ɜtdt]gǻys:_'O१ޠdoa}-JMb疽^+>:I)h6Ko|4憖~߿eŕMᔂOZ&8Kyq%_st>o !B!B!8:ju&eF`nl-vjKDQ2331 4L&hll8餓q=333v----~hM7 穯&k'm'ٝulB\M8O}|ַ2 pwzyd@+ {0iuzNÕ?=#AV /f[g{>Pܻ۩jy 7@[Dҳ)QkQPƺޏ:|ޛ0lݰs{nG%m-=L3HDo^뜮+x1\s.U9erAڪz|3f1X,t FcwT֑76>:[M !B!B!Z5:k,m[GMNܳ/nӛqmU瞋X4 (hZjUǼ׻3L]Haaa\Mí 7 2 t|>?%%OWS{S̤^9G`LZ䧏E[ݣ/MNb #+3t`ESe,w~,85nr~}&T@iQyA[6ʣ?zMkJMgv` Qq$_8Æ+%~oׁX-!Iڳu_\P(L0"19׵MiMsZ O E=y UQSFtw*^SJz2-qcٔ>Nx¡0B!B!B1gn$;Vbw q'cNOƚNSRqN-o iɸ;syUU5y&P<.>wbfs{OEfA`I$Auػw/ƥHs2SXrN_4>%R?{wey.~;uLBBB¾/((k]УUԥz~nzj=RE I&20LBH^0<8cM;H餍,nX2޺n^xAwVW+;sƳ93 ~K\2"F呟>ʖr~ID*9Uhho>UjQÛt=T9{\sTne((s',o@Q[nހ=cY; A?;v^2מÝKnY <6Δ3&2sTd62g >_{,l!SL_$JX~Rxݘ5 =G<ү~ԴdK1|>tkY\yL NK}u#Ukc~x}ߙz,k_=3 h$95<>W ~:^pb.~~_^`e6G"(޻#s\u%8r2=/`.?k_>!B!B!CbPᢩ+!B{ʨk ??ݻwq9ht:Bm HI@obBӁHn ?D;¡njkk{\we<nIA4Z-u PWIx;:o߾c/:N#!#1=f$cvg. n?$bMzn>|kM}i20Z{2aK8kh4vwc49#|faOcK fbEq7-/̾#&B2 #p6|F'D+7}C^_Kbrֽ!n}Q|Q?EH'~4rK7xn:g"^A4m93YzE7}Vo`gR21ሼ,\k@  1|羯d8Fdp}@ ;c[lx 1K.[YQU7_ωB!8=!B1Zݭ4v4 +al{<l65,S0fڱ+"B6bL?;拭 IDATG.&ώ>.Vz=Zuo6qlVm;;hoy _|4YYY=A7OS3$%uf=ki3Ҟ˼3yyk\9r-Jc֏H}AR)tz;Y_)mv8pdZ]<ϩoIo/7 тF-P2J\iuuC]ӘJg$K6Wn&+̂:j6Y>-r5;]WA$9h4MZL(a5Q4.6F].nߏʘIIJI$ D+ƒ;?o<*ޗ^W,+::}~Bo&>Cr3Q_InFyC^|Eg}zs\D_=UUٱar3>w2:sx]_5>%v0qƸv{ټ~;, l<.Av;&Lvc0j^s=UVj4믧?0N#++6V+`UU96/x;:{2jTzx^Z+Ag`Dj6fvO;-]N‡~tRXV\>iR Ct9g}Y$$Hwh9ee4Hsر,turi'ٞF].~#h ].mCMF20M;;hm !o&dXU\A7:Ig_p-B1NՌʿV4Z=z E7bJLڔ0*$(!?!w$MfGXٽ{7w]]]X,Z-)))$''GѠ*#G\v3uT6n|Ӈzo3g&CԠ@UK5U-^ Ohp 8kIKsO4t\LH@ög}^5;P[0B!BӏB%tK3"YϮB! UE{DASR3htcrChFOK5 TE}&<)3lh4233jAz=8N^/'w=333閖 CfO~s=ǎ;r+!B!q71$M T,,z >>B!Ns+c3BUhH!C=thΑ{ɕPVK8FѠb|v"p8Nv{z!B!B\A7k?"ӒXc/B!'UAa e;.#}t6VASրż;HIBVefr(mB^Tn$$X(cPn0 yM!B!bu#}WB!haXphoU%n ?je Vx32KzֶOGXffK`ݺ]\y^?<=w !B!NKݍ Z-z`0ƧoiHVV !BRd.B~/dB_t㖖vjk< ZR !}p܁>q#l)$7!B!iǖȼs.b/e( vhZIC]Us4-ǒ_XBR*hom|U__FiH!BY -oF2f]zW>bYpX+(JN%Vn Et{W{?^ctAxB!Bv cXŞ_{]Z؁``sF3=-p8D0d`O$)%:>o&&P[eO"B!6&OCaFnX8-FW^E{{;pK]vh4-5)ԭ}R9:,JKKq:#_7niii[. ¼!{xB!BVz# 8t@ؾS{zh̞.L?7B}M%i3 V+A7!Ԥd !B|R0X5޽.j\/84`l>>j &nݺ$&B!8,(`0 *=|^΁((KZEQUQC&^xB!Hb7S鬢1z`َvzjJI Țu1zk2Y.GW/BVWWc2NΓ>DoB!BJa8+Nⱓ_t}31?p a[}!Ber[=^߶myyyWQQklV*pT_1c(++رc`B!ⴑKBb2䉰g`XHIIr>_IliF!B1| G>?H78egٲ٬\))pvhleĈ4 ºuHFѠ*rS(+Gm%&B!8m wJףIHHM=V\5%WhY[w=EUxB!Xf**X ag+pmAffK`ݺ]\y^?<]-[x4^?55X,!9ބB![b\ 6L71p(Dc}ugHMlhkm5iG}'&R0z, tz=Jj(8hKIIwp%(%WN*~a>B!8--63yr:.fLᦹLJH0@ DffJt[yy%%UUq8RxB!Bܮ.jm ̜3IHJX  y.2ihiĤٹcy=~~_5*\f_}>> ,7`%˚Eg zTB!b0YpX+( =*ᰂFA :VފN[3EEU^  !B!Nyzb*р`8"F <.TU 1Zxp8hIIo:[ dIɤ1E1g=9de9{mwH0 8h8+{.pwj9XB!b8* BE!=!`(XwmvTU% FvB0/6Ct !B!Ny e1c}޸X^A f#191(۽-n` -AU^m$>-t2{H0/+'&;O ⃛1+s}:̧B!fq m(cѸ>.';#Q6v$7!B!)xu`XU@ ;UcM AotPWb1"w)x+u461󰄄hƃx;鴑Ξ=|!B1:=Pfkvf̠݆#)UUp[LʠS@oB!BS#;H}qKSc=tI)t:&`LKSC%$;;ڰXmX v/ >\-ytMJLv6GB!O+It*j@II cHoB!BSZ"TUa”YqC-™ bn몏{p0`4>*C.bKHzr(`7s Ü>'vtKpE!B /d۶m'Teeeq׆ބB!4>ѦhpuV7 . ;[IMv(7f~n A78tspE!Be޽L0K{dB!┶ ٹ Kzl{x֗; ls6_4{z&ZEQh4ZlI:tSM{{;.KzN@2ބB!V+}a!7!B!)sEy:TZ{7-d@sj4}zBj*+ x#4ބB!l6lc !B!Noz)z9 ][(E(9wz`0@y (d6gSt<1)gPWEGV_$PbtB!8 {N*ALD.TE` ;|vPsSJEQW@FFt:˖fOQV @ssuTn$$X86]oB!Bڱn(a:Ip0ntLB{k BJj:p(>vz7jɂs/لl`Owjhcu$8͑ҮAE!B!l\y MbF6]-Ĵ !:-躛n ?UW-D߽ eg+_맶~VXxMM3fKgnݮ!yxbrY`K* ~_N\Ϯ-{z]k48ٌZBÎh up6_sd/CѸ|IZ|>?][k~qr31[̈́!|^].im={v1GsW+Z!BEaۦ4V2f4i1ՔnLWg{ l{L:#J  Rwl",c[H31kJ`K|*!Bq4D(#)3U c-NG8z?Ɏu555|cUU1 ft:]d7x<>zw8R),&24}H*qJHQ8fD:Z@S=Eu|sᎫh4=Ukb3sﯾGN~v~_Ҩѹ藷aYzW?u>5v{DVn&9Oב_GZF*r_ j?JTeƖhP_HscKH9Fd0bTfvy^/Bx`XGSc&d #~WW'@?9 ubɩiF>/]1%,^%m<_a<B!Iml^,aw?Zp/?{1l6rQpXA+h4t:-Pdy=jGutc;$w=`;W^r_1I) ̜?5Zc"QJyyppcGzlZ15\Fr /  ,& .o^r"rQUWzBb%Z<5ߺ BWUv0XN.>[UԊfl47 Fa %ƳzmzOM.5ߺ ƪŸ#aor+)3*z(b[y7 tB!yz 8DB .tVIl8_B!'OgMaF鼼UFnSACo4:pp$BUUB0/w~>k6h:sukcļԭ}yQZZy8 ``ٳ9s9s&{/UUUCXy6'3k4&Ϗ~{ss% 4?m l 0YL'i9"ssI3ɇ??sū2YLO(ſ6w$S/gFnnt<q{,1y{6P7y5 t>v`40)8r27LJΖO(.|zؓ}t J÷=c_^a?;0[L8Zټ~;.7#1o ]z6YiU~ B!Nix>B!T[ &B&z=??ݻw3Oj,MGY]]MQQQLm ym<r-z_]lڝt{W5ѱןyos#Ϝ]-{J?^1 TsLpW?⎟9Wp ]U-&vQ_bJ^Ys&21AP!B!B!Xf*U4vxl'l6sՔ;-%@35bdf]͵OJ_UUַ?̽KMMͱ\{˧]ȄR 4t4|oo{_I|et5?/3 f|23k|]l|Y>m󧳩bom{F˅S/NBQ:}]MVx3, ("dRZ'eՄS OGvg(osuL^~͘D2ӛ8=іh\h`ͻqMύg/Ƃ oٲr3 < qAozjx\Fp͗Dǒ8g|fΛv]|>ig.ř#!fcw^M([ػ}gV2dz;ufYXvL5d.{?@LyK"A{pژP(<ySY{&X+`[zHL3GN)i1 S8f#~k=]z6]@oB!B!Bar[=^߶myyy[`#h:TU~yڔdReiP !} GNR_Hsg3k4^_<J{ y^nsf1yۑ?:@en|yM~II)ton!B!B!S^^㸪*C +θv}h`ԛ,5{kGUUHN%ii޽Cvoop뭷s=P__?eFnnz6H(Ibq.50:Y3pk/%bMuJF`|,CY>^&U-UuzR,ɴyX]>RO0<ƊϣcŎɏ]Mg'z;`]s%PSQ, ySJ&1|GȏKj1[Llh :s,J&ݗ tZ- Ο˕7]LKu UW}h|Cљdo!spoNWl=ɒ-z-(67j-ljc2?'#)KxwX~RPsF=o( "QtEUC +1󺳧@Ayks‡wCI{8_7sf3Fn~_~c&zOBZFj{:x\pb::Y z|RE?6=+omn>yj l"ٞDFV:prRB!B!b(uza%֚,A GR&tA/xXjpwFo 2 Lϟ ;W8ɶL5E3o.22I&o&&pu,]&?~1ALd_8}9`:j Z=g?oy6@-};1KD0w(l1 LJE+s4h'c=r%SWƏ_2cFa4E[[>/m ټ~K.[jҳҘ:w=N\IFÔ3&`0(ßҭe?rrݭ_^˃-ъFaV>,s9*~h= tp]wEo䥍Dd>R?햟>vlboetXw3r S AQl!? 䥍dTZ^1{x^8sVYСGj?M?[~t="&&ޠgxK|u9r2H]ks{szyf/Fn~6˯]+:-W|)# r_h8:Ԟh"0FɈ-Ȃvm~B=&=޽B!B!B SVcR:h*ݰ]q5&ke|%[ @g3*J8ޜ@ٸn3i$L&5L[vW>he%;rp6aNH"**]Ρ [ ?DUU.~s-=,ѿw1?}Gq>{>Rp8.=P #oAp;`8 ;'~ΘI ֽl{{O>ds^¡qᗹ\bcmYi}sHߓ/b9X 0},^so>^Β3z\H۩; dfu "イ&Mm4:eU?m vgG\Эfk͘ɣcoMY&Xƺ?+B!B!Bq2dq)(  FC G`ǫ{PL#25r:lŹ{MtFk3G% Pa4-Zp(w&o8o(a|Axy<~PQQ1(iEUU4 yi#O8v2նh7V%PߺgI=`:PVD^$:Z;XgG>cFq/" %םϾ]~rgR}W[[K|kbr&ҥٹ~+ S&3&P-bRJ2)3[=׵c& B!B!o^x!۶mv`q歽't7?iŸG G+HUUz| 6<Vy^xDy^J#"g뼹`Hun;kvVh4()y-ʶCzo,a뚇]2_KVwFs,F;#+߹Ftz/}%Uk_OV?!˘ɣطK%(94:n\Dg=0}, |+z! !B!B!Dݻ zIt۷oߠ7]4ōOɛCQf?PN>޳k^EZ=f<`c&1S),(2|?K;8fdb!_wyDͻWigR^][ķ~|MͻF)?ߋY^?dd3; - Dr\K[K{ h0 1Zo ܭ"fƏ#%P3RcƗ\ɳƣ*o^ !B!B!8&Jeep+餖|>|Aʎh6W~/b uWrJ1d31g7͎xj3;ok4*K5F}vgL1gM懿J˴S<Fö ;ʵqk\is&_lL7qSywz=Gw;O^~Mg| B0c&1~^{.~Y_{Lq .;FRҒ7VKr^~͸]<2ώMimi'9%qSK)sE3㯐_GVn&?qqy+aD^K['B!B!p8ذap+޺n~|={6ڧlY1yD&+o-ola \~7?yA{ .)Kcހv~No0rp<_[̹,`h.roV0&}c9_?Kg+mqna%U/3 ';8ul۰9edtD3*]nj}\O?<Y|| J((ɋ|^}inh;qS93coWoB!HHq!B!)D{T(MVV֐ytmCe4Rm!ZtA+VLjllFJx5$Z-#IHrTcyI!N^#8[7kSHJMr%f47k}fvFvgm^+B--- +L:[B"ι_u[p{q򌷙3grwx衇Nz IKWհyzSڦ>8 Baz%ruI5u?P+BVX<JV( Ip0@0 { ~B!Bnңm y୴]vsϱsΡN!B!YP }͟TsڪrZYcc'NCQ:t>!B!Mz !nޡF!B!edA1`0@mUzpuv hs؉>B!B'=چv B!'Sw}áa>M;l*N~~!B!'FoB!BFfv. ?2CieO#B!xIM!Bq(,@SC-nW0&l!'rvB!B!&B!_$ٹTpI8XmebX B6:Gi}B!`LUB0D# *B8;nZkz.2_͡-K?LB}**z% `IՖx ބB!l7u[B"3.v;6~L77ֱy}:N#X RSUZ/B!GJ̴x^Aou>kNFLH# rG5#֚.fF5"ZFMY M@QTtz-A_G~޷ao]ay B!BzyTAU`<fj+(۽`=Y9tbFp}!B!8=7֚.\-^\!9FŒlJsyGt< `M6aK~ /v}Ӊd !B!Nyy e1c}ޘ]d͞Gbr*Ec&P{[[4fU~B!B8#K>ױe*,ƞ wTQoY#F)[YQgB!BG{(( =17 @nHv;udbJΤwt`J"cR6iwɛ)9MՊޜ rZ@գݰ]2L=t_ :- ~7! 5FgdXT6:*6|UQh#JvwwjmœD(GUT+y^( !B!NiÁ}qKSc=tL?=[zO !$ M@ bEŕ-u]su}wWײXcCiC {2s~ 3ɐIXuqe<3Lf&>}GFV|n5x8ՕeX{,AAA~b5::#j @AAW7EQ Rϼܿ_s;p֕Z=Φ~FR&^@ʤH* ZSԺn $Yj3H VOBًJlfμNfn# IAk02j|}/eOL-Zݎdnos/o   @Qdfd`01L1MAA1"*+v;fuDd GR$݊ˎeGQJ) *.^s ]TBp8MHC[E%aNٌQ ((>/^ h7T]ތLtR?-x.S[Ӊ`l7ě   ?iF$HHJmw=Z/?ZݝfknAAAfŎO.'==p>ϸd"@Tj>$=_!lQi TnY]`bӇT\2^c%2:37Z%==0χ$P6q5ZZMv@h_3}"&  Oڮ߳ov3s3w_st2S=ѱ|a@m`4>ϰ א\AA'y<>LsCzU|o-{"BzOǛZ&^CmR֛Pdu85jT*kX$AAAIۚ:w:I6Yilkw=ZCdT ua#)2(=Vi\Rz=+<   =E3(z]oʤPd/CSϸWC{xuuFu7\oP^AAA~,0L2il:+̖&N=FO0 +czږIAAA-=3ZKlXǛ ˎZ.:!=ޜe-/ m+Lá-* 1 ?nElsّN ((zAKoңXʉNG Jɢ   Ǒ 5/?~;:*c&1 k0죡˅h$:&Iyl޸ʲefaIlM֞}P?G?/`>DAAN]t|럁,#rȾ 7HRCzUnK1N6c IDAT=هR4n2>o?:q~%o'n#@{7\ѣ')>攟DIAAANȲL eeMAA@*Ћ-`%Q~`IRq9Vf.99Z FZBBGE_3wVBT{UWnJJ2nߗX,FZCv{eGq@ w>X &݉+c6GryYVT$AAAY;oҺUTU7DDp47Y]]ۿ`4ZɆ-  ktF3:iPn'+ Eay o+Z}s瓃 nEII  _bfj -gopp8\<ԛ!Njp(.hl˧v{p:u0cp8\ϝH   tt>|>>ߩCAA7$*) ˁ tWbπ456۝l-۵7)^'/:ۃ'i>Ə_~z&  ?SǛЗD7AA㭫_| [W3g<Z,|F|>Z'#Ij ׿SC;q5FEMFe7?6~V}Eᢋ&Z*J+jzǛH  ϔH }I$AAZmprv GHOO'66v{==Kc8JET(LeM˘2e {<ʚOR      BP9FQgXmqp((( wؔ{|>Ei  iU;vӖx @AAAAAAHH 7e0u:*+v;fM8NftR\\Liiipys^T:Zs4*)fN Ԥ   LRB_&AAZhZ9|pzHRQ9p@ym}RjR{M-NAAAAAAIlՐs'))O?Fc;w+VlDT*aƤl6'%v{eZ+ QHt SO …ضfp(.h7{8$ p8\TW7kQ&OʶmP$ -sNiinv{oa?No6l'7x#9vX`24hP>je׎ߑK.*>$AAAqq$  pU[Uz%^ G־w4PRRȑ1FMcFv'6L8|~EE$% '!!FjMCNN? (x<>N7nwbvW{]xtM444xb{%?qʕ+y/3<×_~yZ/  =Ix3&o  ,+n-?}>W>|xZY?oݥVdϟ,|F|>IPUxm_OFVqEe+;*!WQ#JpBJ ރX,teȔ)S83ټy3^H3{vj5999L0իWz{W8Hp{}X.Jp@n}ַlv'dZ:-5'|,zoMm&GqxXr%,so=[X;w ގn3n6!55 vСC9sq^u.\Htt4z9`_6syѯ_?bccq:ֲgz-E#===͂ 8sQ'|2$61%&&2tPLۺukAAh .z? A~G`@a7j,;zO;^P-xbX&-FBDMU* N8II;Ca 4Iy*|]ژD#||kEMu)\?v6Es#33ՇՑScShl+9C/O'xXz5pwOp7T\6FÀ馛_8:{xYlL<>~q /u]$I(멪bȑt9FN|ܵ|ǔO~(,l:t(Æ `ƍ6;sIUVUW]E\\-bTUUđEVVX,vխAA#I^zxodl-@4D'Iu,hTMad8XZG)"BGfp z!PNzaC㇬o|׏)<3ӧӿT*n}_tOл&NC2kиj;bJL FG} iuj\o,`0N9M:S[r%6m ?sIIIe %7mDjjjp6X vUW!I+pyVZgo'JMME?[latR.`/`qI())cp6\ee%O<:LJz ؽ{7O?4T*E!##  B{2hגnj;<?̾^MRKZ^f%lF^vI05t{}4?a )xޠ7-HQTA ~ӧ3hPXQ_ps*I9rC^#?GŎ|oؾC_qf:Ah).!sןmVsCszZl$Ӓlsxڶ$s8ی3뮻l,Y'|Qֳ$IbDFFj kllduEѣ]NUTTzh4̞= vٳgOWbb"3&8냷v{.k/p'AA~4jx R|lޫgNcDe?JI?VrdjN?ΪUxwBʿwCVwއ~rt:seڴi#I^f+Nxd޾_`$^=6fy"U?O ?uÆ '''B&NL}:>n7FK. `…Q]]ᾊG<"H\Ogv3rIb}KQjpnsL $v;> Gɸ> /$//6_~[~ݧ$It:>/N~orss+e˖q.+---x{ܸq7  ?O*BJ}~;Kە֨yzc}P=LC Os˄ x뭷ݐ!CBzDgٲe%&&+v466@bb"Æ cذa̚5'|9cΊ9:mtRRR93SEh4&w[e @qysxWݗ-v MDqnBdgeQT86! B벒oaOeI|_OMg!I()CbiQW/m u*d?*mU(4f2+KD:'ח3iHѶ/)8[ p8x衇8|pObR h4.2֮]Kiiip&˗/&>Pۍ^'&&\n~-ӦMc 6h233Yx1Kc(,,?^wOAAXt~^$t~鶎 fΜ g}vR׿Ν7GT8Z#%% y8/>}:7|3Cng=Ȩ@ i+wX߾Ek5|oj"&&ɄZd2C~!??cHTTDFF"2ah4 @?]i YWemNGNNx<***),Bh bPSSMQQQyZ\.6 Bnn.*}h4ԚlFcq:$$$o+R[[Caaa3 FT+W={vy;\^JT3I8T kiڢH@my8[z"NWVbW,WjbImoZn֙[5=j%GCVd ƘT\M54t$!*< (m8[Q>ƎkMQh !p$QXLxMjR,Ens׼>"J ]ul I*bŒ4I^WLy\aWk #q[H̝RSo][H<FG徵8+;ɊEքFXрZ*dvvDjqx9p<}f*G\Dm0mD<If|^EgofMLAdL]qS0V{ԊԚ_DžNt;x`oFYY+VK.Ap-CNj4in;lI\\Çg\z"ͪUXj$qs饗R5jT0zvlGqq1>ZM||<֭fͲ'AA̛lg`U-[͜9Ʉfc+T[[r!`W3 򗿄\T( ֭#66 2}t.]jA99g5 KvlPkGqt^{i;V9s>}Cۤ,x^B~qqq̟?!CSTTĚ5&wf!Cy&6pr+`4Kv8,^|_.'77cl _|Vrrrƛغu [l q\DDDp5ע˟{9C \p!'N?cÆ ,Xp%ÇY˗woA0t0Vk1|6l3ϜJ"77m.Y&U^(Cd">㣻o%tĪȔlyoߋ &1wF`5F2IrV_~1ޜq'0&o߶iv<.>{hT%ޚx%,Z?L,\/|]֙vdLEHr3^W̑}#c/3!{OE/y&2qBQdJώ$0e0b$9 ¼'mS‡s~C&&\ 9S9-LFF:'N7XѣG{8Py]g)((JETT4e/jr::t(x?-- шf/(/|v{#$ɷgP}e>/xbÒ8=Rqp XX@ȹ_'uI̝NdJ.G1 IDATqhNaڝ+[TVWyBIda䫿Σh{[?d9C֒2\mZ6F)lՒm]g9XR{x^W3C>i$dWپl/*Jk$*͟Lo(o3Z_i3Tj TIlx҆21D$Pɟn1ԑs+B͡o?n>,?4`5/ *uw/]aL?o JMyd/hr0i\7\G(aPkȌIaS]}ӻ}dWq~5_HJ鮚qI%KLJR]ו#p|K|*x $\.K,a߾}WX6^{7p lٲAee%/~;joo͆dB$}]^y>#9GTTwqIŔe]xee%[n ϧxrsscnJJJÈ# 77gyۍ(z^/ .L'  '+P7n O%*4SY鷢Ow=4Ei(No4̆8q"[lmLҩ2-X,847 >'pPnuO lDXwk=ORuf3:t(/4{o[_6 tYImj&NDlllPEE=RZ[CNNWξhOee%/<>񤧧sŗp8x뭷صk'yyy\s͵T4 WX,XVC$;Sm7dzzjV_|U IIIo7|rEEn 3s`k455 yoE2o<6&$Kׅ,OMMeĈ|80$&&eiyyΎs;G}]/ +X$~/MxG)7 >wKɶ#8U}Xp3Si ɀ W0檿v)~ER#N'Ö 4ť$śҾOUiAzIaRYp x)!2|?l{ΰ!g>r^D#yH+]g'\Nivڠn &en2*ۿM ,k^"S:j^Td[t֯8[l{6D&&_cGOo }-kylj"1?!nx]=k=_9g=;|9~WB\4)5v,_ |g,7q__:u]N`xj5x3Ի_h4rHEW4r9ͧ=ֹscǎv DRSSî(#V__ϱc]a]{//fԩ'yq=p뭷`dmjM0''HC>u w,CxLj/2f8?bނGٳ2t:g}Ғ>  &N.Z /<Ν!W;os.Ȳ̿63Ea^z8g9>xE~s3Oe}`F'n6ICiLA9mt`9BsrW!ZZ{5tUwe[%d勃=2&^٥}Yɞy [c˫)QSQ]tXMN`}_J>>+ߋJp\ EdaשܻKk2Ou>vPp`׆6xJwM'<nx5@ d&kih aI^Y+A^<֊ly74W\OvშZꋶBn־/S)IduK}x`HE}|`_r1ޏYL^j6*{}cuvAɤhvWtt4V J#++#21tL˘1J%;oO6t۸q뮻p<SϼyenÇ IFjkk&l6O=Tq8|u)?GII ӟ$(q\Դ{ujQQ{/:HjkkCy</_1L$%%a۩kp1j|  Ud/:+0>s򢣂[S!S^'yJ,&oQŗL%֔B.3;٭agrSUUEbb"<>l L,X㥗^˗/ovsrcZzJ`h Y&}Mo0Qk8/(Ȳ&ǎ }{9vȲrmy]|j5ܶl^eeeHw0i$222B@ϓTFɪUަ56*x[=ȥ'` e'vb2ti_ Y.^ه'L2'Asa,Y]H۬Ol 8hj >_3G=BNxͻ>z_7Ds)vlr:. f$xdHv[ -$v|ݶf[<\oS5F; - YyCg;I&}Enzl\:S5(> S]ǧȸn[} D BR .w65zXiDot7hB'TPP[fEf"dffr|֛UxXV5 *4=;w.$QYYR^199;3.'~_,S3ie]FgNsuhӢMo:zCgݻ(!YYSwt+H5M-y,T>]KZ+S_<-"(n/ ;CeMDNrZr9vjç&=WQ|z]Wݓ@99 TH{O^/n$I&yb&45Cf( ~YVPT}E%$**l9pxs$XM&l6/@œ9s8p 8?|RRRx   3dω=߾e=ӿS=zkhdƌ|}&SgEFFa*++|2rHFţ>ʽދ)++;G%;;;'F^OOv96{lnxG(.U~11l]@WNqW,hT6#|k"ZQe>c9r#F0x'z=I x袋̘13f`Zٽ{7[lȑ-֞ݻw~N],Cк“h kU¨QXfuH[l +{ݒ$j#џza97$ ҥR[tPşxәIșF5@ɦʃ4N:WS5߾x~Sl.$-Bn;UQ};? I.%W:s4TWa2-d͸7VQ J}@l8D3s ,E}݋ JAA6sLL&nիWx# mNruY7̛7w {rr2~;ܹoI&1rHT*w}7ӧO駟M{㡇4vgs7Ȓ%K=Bge.&Fɕ8ZsrssILL.&Zt: \dW_o7sN&LϨQIOO'22ɓ'3yd l2;YZlnIv炀(RjCDn|>j?q9x<֮]n೰MěUɞ]yBtz_Scn!6siyMonm_{w2jux<>ss`[htܹի_p-tF_@Zޅ:Dȹoʽk; : |OQsN!2ɀɿp3ۗᬮ+qɣ͢t48?2Nkv) 9SI?!g5 }kݧZ'2&]եm3ϲҖ'#^è3rbPPP_A $fFy1Xtō: m )k( m@g43}#v, 4T_|>h"v;ocZ'LNII*3xxxNN;Ż3 >${wg}6Ǥavf͍P']q3σxW[ t:_ 2X,V+ǎO?mS@AAvN|l7#mH46ZIyJ^DݙQߢ5k0k,rss:uj[zz:K,!::{_|/ILLK.fܸq<cwPkgIoES9W1wZגlujspљcI2W=LiVe]5T餎L%@37RDdTX$NEȹ )a>Ƴu{ K @S>U4X~T%D$`5pƊrݎl^RrFP5u:ngpy}Yn%'*){c=Mh͆餸*Es^,rњn$1E6c`kekY4i_}ek&2!r$ Qh~։uֱn:?7  OKhDФݍs6fyh5 . O%z~s6fT*;?p_r VJQkVKZWommZ'r$;3?lX! $|͛N >7qF.b/Ŵiji:/ HzԘχc֒AFF/ zqm\.Wl_wP#L#gui_@f ]S$2FͥnNƲO u}&pw#I:jkxᅇOF9~yʵO3gٿ鍌[x~zzwcALƗ2/c`qeQ0kUo~9"F r$H-{mo} -rp  z[J_/~Wu *Ԗ^1XNShEf+!sIY˷mFAA[zEQ$PփG)ʷʓDE=?%tz=r4_^[\rezڝmDQ hش׾/_'ؾ?q(89x;  B͒7íLQUz34%S)) |7il3PUb:]pn>==.<>[_Jv$I8rÇfug[3f0w\xV{z[l9Qk׮eb-^VVV$lގdȑg|F߻ &GAA~A\]8KK1Ap<HJ_KK.@ST~qɝtx9@Ƶ@KK/n]ff7۞W̘1Áf@*++|kjj8mꎧk_]EM8-~xJx ֱv{Iۑ:szʖ-[ 5j4EEE;'Ii1tb6>} ;x۷sUW2=_TnP(?aJ8R5"4ZrRǞ64SpfjAަ;Tm7=RG Q-LWcf+>_O? ^oӛH:`5 fs[w׶ՕIq}I_xl:3G6L{L|#ٙNۤhI` SJh=3~.C1Zzi۽x<$%%qͷo߾c/T ر]|?; 7x`+_j޽=_(>p`?'N$??cDzs:BJJJ?ԙoŤIp:v7xغukL&saΜӟm]v;xb1I?>#Gj֬Y,wz㏙7ov'fgt:jx뭮阏emۃ}ڲ.xկ#}<!GX2Gz}w6b}wZE$hMbuҖ|6dn'. >z$So1~Qj>`3j ij@ 6dWcl&]+2F1GDTm9hMeG#A.ۙi\xk)fugsW=xUUr/"kŨYfk: w~}~OOՒ$y]E%4Wn&v=G$e0oT!y\0] _\S2FÖZo>gvd蜯=n>KȑΈ #Cڛ)k:̰f#URYX2 I*HadСt6+J((vPUTE%Tۙ*Zuȑ0f]K)I9z4%JsMel{# Q%:g䶵ťO"&  €$42}U~󂻇WOVhnۙf ؿnӧ'tY_QQY0}tvڕĬYJ3y nAw""&\;ȴ=yBV;#VqUf Clj_y΋*o".خ*> r+&c&lwUWfVxm@O p:lٷiooH$BRR^ZJK{[ϿLۨAVV`o 9Y-7c ƍh5mVv?w۰tXKcc#F,G/DΙs>JOIp}SUU'''h3֬YoEEErwR^^N{{;{طoocS<sP?6? Zxͣ\][vvNg "KRST|rFaM弯>Ƅkz'+&{ I9#1YYUkHdasAŜIHĎWbUj\ c̿NRDo4l:?p"fg:,7<{#A,Ir oڧ8RDZ^E4؎$hzӉLJ:VdeԢ{njvl`%Iˆ6{{*~a&_[҆bލS k]5VewH.EOnzLd\yL,.9P~ 8^O]d+@ES~.4ʖ#B!2wxy#ZՃK--$O/sњs:bRҡƚ7zRsc\eeevL7AAA}7xvyF+?K \X,w}/]by׻իKXp!ދۿ6/0p] Y &Ie^nxa \qvѨJKKO~ȑ#q:^r1zn7]tv?/$6Kլ*UUU^͛7H$.cǒMv6+Be*++X,]oFeVUKAuzRQQ+,XIq88Yf 6uM-yWkjii17/ݾ|^y6mÇwx<;|ǽճ?jԌgg[x#R3tMd0MZĝ7I]ԓ౥`KhM"k< aO+ę9%SY'b8qhM"ZG[^?O$I3KR:}}-'5//N"&{ Όanh`[-u{LWIg3 ; U5Vm>s̎TiDm3(uνs׿o}mq6;PUB!EEQ@UQhT%UD"̞ wW$5| ဟKۆ͕@4F#D#avl5{AQ/[os=wszF;f秳o_5eǎJf־;57# .6GQf 0kP}ϠIJfD^tĞd"J@xAA"Qh ;-ʔA0Iv/.J32[,駟rR\\ӱ~<3TWw1Cqe`fgȲ̶mx绤Tu(X?jගBD0+}VVcǎgvNe>#ƏN#--m@oR[{~,wo)R(A@]]uu'(T6|-sDɇ`-8 x08M7:>|5DNy R<=IKqfG9}>kAUV'}D{r5SaPQU?74Q~8B~/!jzp\3!BDPd9tpga.f 3fԹs_eG~& nVw6NsBJ8p`3so`둣 ^"ZmNUE/EY_uXre\`Xߑ>;:K0~&c,+:dY_͊ƶۻ+>E^xa ^&;xAAG5 ?';B~fx;DY/˓o֟^rTg.pvk}J0fn7x#GՌtL< Z- .b֬Y}cMM ?>o'}tr}CcN݄ɞBޤ+(Y]̎TڪwR֯{XgP\AHK128E){ZlFg:sdZh!|lI,hTp6Ғ !鍤O!}|W? ))'mڠhAtzO'˼^L)yG8`N-@I4]jRRRkբSI̥׎͕őD}ɥ=xAAW,nv AVc>a`0寲t5?' XVL&S}EE9>L\>o#$Up{λ)_=HZ" mȑ`[ Ցf-<!ɆI Pl90d75DLڄi)W#-i.LCqtHp1 a D^$g~ UF#W"Р59Sd @!޽7ei.IʄK(8~^7AAAAϙO>ΝGQQIII`RYYɖ-پ}{l8>XÖ-P醅S hj:LAUmDbfp5T iϞzrT%::l z%kpEŇ$C9G13D#\IZjʣ۫J4X?I$=0EUZlmױߎv~?v`B{Cmf]G h8+#g@Ν%Ղ  pJMM5ńA> . ©2 fTU% !SSE|2'y"I:$]TA8IR,H (*%h2EYVFQhT%QDTrH:="5(ozU׉N*w JKzՁ˜8nnєk/iu3PQYբtq5ꎥ79\w̴[dS o   l6s/.P  qlpֽ7βA8FG؟A7IR$PPUu/2mFuu)*߉qA7      b޽3fqN7AAAAAAfrqN7AAAAAAHff&=sH-   vW\ o,gCPQQ1#AA81UUi:kiv<@薘&      @AAAAAA       QMAAAAANckBNΠEVd䨂N/?!m OHg#tHMz"(4+ U[$x;dUb-N"$ f=JT!B:7`m ĵ2m)jMVL$cyZamu>m!s&!v 'gh "&    Џә8q8uulrhT! 9 K %0lfv*(KT!7>;JO3fAGfq2`46#EQQdvsi!rD& \ uzxc)U#Z?AG;M:F2b8{[hoَ'RM     Ljy$%{8g93gF6e޼<A蝔6mu/pԩ%,\8 'א?OQPҜiIIIH(mN,N!_;AO̤􎔔*bXC&-M+AF mN`ĉRYYm*|('6pQN>CSS~N]nLJ.Hm9Č7AAA9v9]۽AI=;PښݮCv^!VIf{Z{}, BW9%}" gVa() {n|J1LXMV5UŖ2ݻw3  T؈fhR2$5e^_!)$c^6lf^s;! Scc,&2r뭷y3, IDAT񥹑Q&G$= nhÆ d AAArXmdRsKKMDzE›]7I`0jm#a C[PĦOpAEJyyA(f /ZysNKwcMPtrߏn`2 Qd0196y߽EQ9s&|9.&󒔞C{C-^ t#CȑH>`0HUU`g6vh4{llm"f  pN1@='Mp$q7BwJͼNO}m5; ;L:ԴLΛ9ۯmoK؏ Е+37vső$pguiiEe ݉HOZpǜG5DBZja䏙Bukδpe7hoGF=b" MUѯy!IFm`c[HMMbl F$^} %%ٰD"2MMmNvhTl&MFZ 79J&pJK;ꐙ$3~RRLB(--{l9'.z]m[O*eyQQ6Æ`zRYYN t%%ʼnll6줠 |L&u= 3))b1erNZcbx3- n]toˮbү3]l߼MyB\yHRצz&?z7'HM|;w߾ wuGz7 d e,\8}7͍7^Wϡ}u37.j5-Ml]7| l4߾n7RRuן`ރagc뫳&-͕pŋ#x&Oβe_0˺Fy=yg 8sҟ*F.NUU"H$ʑ#-TUj5'_fp8rs" z-ƛogѢio̘!|ApP$9sƱo_5--M&a6 ÑIO^`JF.z[HII)VNUT?-  pN)1 Ciۯj#=3 Ed$s bB,K\jAHD}hT#ryp!6lKss;L<;Vkt&9%yޫr[;%P{ />tަ#峯z9L|z-ҫpG}t@1thܲg})O?Akk ÷0aB\s!=5ýv~s|p(^;/t7R֑:QqpTV֟V?~(z>nGCC~/Hff2EEلQ22ܱ22(>1:44R\Gi!23cw8z=Ӧ`[oFtvդ00PxAAQ4BHm5>o;ܙ(M:pD 0  틲_[ [8cC,vrkR׺B@_̄K_ Xt2cƌ^٢ LE$׻]75yxM\ulnyxa0 <][WpK`smKvI3 E/^dܸ$';?<^y哸6^6mǴi#;wB g16lsws:6~ÿşp8k}qNôi#ci77nѨ̋/~C%LWQSإ+Mnn@x2.m[O>o_̒%Kp`+*`<5&2キ/~"}בe%KGQ{dYA$z]¿gvȲ^ٱtCQXZedt$IGǪ{@I'P/"&  9L"3;cf F2sQuV ,Vr4JKsa :/BK$L-i4mw..T%'BK+G'\?{uPUJ?~_&fh4D/Hc/@4}G^^:$Ouuii.\.;mm #e {Me}:SR&%x !>pW^9G% fMхihߛ:U! ￿c;Ytȑ6#[o99Uyyi6, װ$I 06oD@ 贴wY;$';1"={ztx**"++ ba?ECIsW((( %%6" b98]|ޠ9:,--z>L8Şb4[jͺv>}xFe&NHkk+/_k( nmE7,DQt:]>CSS׬!AhTZغv G3"&  9cΑrɔs?G6PY^OONI)qZ#wJjwIz5#bU`0H0hb9|txg#坵 Gxs:$bg iHϺ# RUUY?i#[l?v;gHDMAA^Fd9ʡ; }\`]C4mNW2Jưo.ںKL₋tLf Y9 VdLd0M87p&h>l6Æ U[EQصk׀㭷s啳M?X?IX|&Ony2bw9U_8<)ƕW걽vݪU92twS`Y];0[;)"UU%:󜜊f6Ν7qMY[?a0n.Ln ڴi#{_a`o_?S1cÇ͛7;*_=O[H|۶mpx66x ŵ/%a˗ IGU}L?:sw J uup8bE@БRۈya0uV :V[k[\vOw#j<`%K 8W~3fG~~:YY)҇^w#Lޮk}͛R͝;!;w<|b=$1W;I0'|sr~,\8[TU5PWLz!C$yWn9g;a&N~im kx$%٘4cd`4iii'`6 ÑIOf.^<;+=)/o!%%ʺt?{\AAA8efpj_؇[O&%-g^őjVCGhqO.$6: isD8%wY멾@{{;Q9h$<+: Q]]c;dQUUUٹt23zxc["𙠵# g쳮OBUU K^ 2f<=)9Iش99iTT<'*h/sNII>PzVVmw(իzVr&K`^lkYh:ӫl BTW7"IZ`ǻX<<O!= zw!-- ȬYcz$%ؼV Ĵi@7dH~Na0Pwf wi9Zns'o   ;M(0zbeM]M1C-&3r73e{w&\WXT @ӑs -M%)BwD8V,pS DCAzw:5+**Oj|9ncbŧ1VGj37p886mҥv;:47:U6t酌KnnCdbY8r+zscOZ|R>'b \};c=V1jT'g?O>p?:d(c,+H^#zgvȲ^ٻ;w$u%aDZEj[|hO[Vmm~mkJuŢbA5HHBB}!C<=3Iy۷֗P@(:l?m'Iʤsզ  ]{̚5 s=̝;*VZաMjj*/ؼys{L:5^㭷 kC=ԧc}ђ$q饗o}+P;a<vmys=].w7뮻N|AA$oV:KZfór:U𷳛Zn݉du}bcg*z{qhnnfZ]By"`=IXO&^S2I'P{1z%\cK9Eg4sO~!2x7_y &=ʕ N^v6m[EEC<aӦmcƤ7aFw;[om`Μqt~oc.x`3gv /od7JJ=>ŪUKYxJ(㧟vtw{g]Mll$t|HN5 IDATѝщY8<g;;҈#GDᶷ*_ OMM ~uIRaI wmlUT b%TW (3qh b1)ь(X,̻tR¾(|ƿfԀo۷oƏaJbɡIIIvNM0!lSEGG`Z`<Օd똵.r.b4:=qi#GXwW yXǴna%PU= 1j?BC:ŋ/%;{1L46wq9[׿;--!dž ˱X֬k_;<E񂂊|;&:7xs^aWkRK;sϽ/'33^UU 46e"%%.ml6mꘊ|I[9sr͈Ɍy&ZxbMXG_'j':t:-#G&c2p~t;,Y2+rE(/EӒ@D~! eg#:Lbb <6 +pDFIKKv{}<ؿmSk1h 5edd[qq9(ہhB70z% Ih*eŋ?#69Cob 5ZڄuD1*#65LZjx+3KV$82Ӂaekq wvo~@ w܁`͚5g1exo豬Vwu(fϞͶmz;BjxNJʙnoi&<Ղ PT>LVViiis}??a}Ν5\Wkd#555kP\\LUUFĉٸqc   ^j 3_Jtl<PRxIiLͺZ롸m'$ FHr-MlS[A뙇q4{7LH"2!)ءjM7PqOz㴳uu-~SL6X4`z/I21iFLaƌl|rW?7$Zv?'7˴iڪ^g'ӿHKK1@ ʕ 21o^pQBUU8K/ nq#G&3~|fh/曽 յwoI(g;{3ΧcR b{v7sH5ܲSGKRR&gü[yLWx衿QtPm5L:zn;*+~m]%L ;\ t:3W&!BB%Сbi˖|[OqMXp"L<_WzW=8#1)m8JuˉZ> hۗ^y7f 6jQYfa!)k<FjyuJt(9;(krF>QۈO I{<Ǎ ͯnrQ^^YÍ)ˆ =u~ ?(@JLL \}լZ?gyeȑ&99*YF)|/{|JM{-z3V|EW:ݮ` 6Lx$qW7a>poW^y%Vw}Om۶-x?~|X= m60acǎl6>gffju-===ڴi[lV?~j,[,ht:Ы   EQػcU7ba۫qpNZ+CƈlZ8ZRHyiQ :Aax]zrb(~i)w62t,_>w݈]w-ER? f֪Քsv~#~_4QSqйF57inX3qLb&6m:mJ)((gܸL gPTtc!/ܨ8r*q:SԂq]Q 9Ps.6C$ȋ~J0|H*Hj5` IQiuHHMɞD塽@AǮJgje˖qw?7oM7qB[,F.ݱc & 2SNeÆ @i&-ZO͛7cطoSL!))QFqp}1~xt:{Kr###X,nSTNOAA!Rt(Cy=7j+D7`Bv;潚c߳=֟As;.G$;]MIERPW, >KLc>ﲝFj P?:1H­:it&H*>$4dS 7ǃGsU9XZcX$|>&4WoSL-j{f~a^௨f, ٨j|>SL ܽ{7 6 Ɩ$ p!ʷ)So %'(Ux'4AAA nsNxxݻ Kg66- fjhheG+o~Uv2Qϫ_}55kַp3ܯ?Cw:رxE4a)255cǦsm([72XIy*h@q{)[[[x!#ʳϮc9x| :"" -8qQ_ѯ}:R~WVd:L~GEe޼[y9q5addX4iPZZˎ]APcr:ؾig{؞,TVKwG!Is玧FF=nKfteZn/)SS㏯L`4ꉈ0bxx<> _"&&TU0I`78A8 FͼI%P0FFeO.(8N=Рݻ|jƍ޽{FOmݺ5T.77 عsgؘzYfvm] | FJWׯ2xbxAAAA;/ڟf{= dңRI\SA9?z:yAQE\MM55/+Ú5_fHɤGx8e;\na!g/f׮" -E^^)s.]:_!>> #^_?Nj!/Q^al6'nAѨ )uPIrōD'y5kZKce).[ iw}( Zk |339R()) +...sƌn5kVh5\OΝJ]p=    paA)B[&@ 椹ن>gn h*R9z5_a۶C2f>@q{{5 jƫoÁdWKęt(ŇMD'8blۇ %ݾ};&MB׳tR -[pWJrrrNN3#P__ߡ}`ɝ +ի馛hkkwAAAAAA8_>\^eŊy(ڵ[Pk{=~YV+,_>k7vf`VT4@p4ET@ /? 6UV`2 vk⦧aҤIk׮NW_xXn'''V_̞={(++CQ7n|R[      .,5uj%&k݌Bq4v</5eAnR֭ҥyO7.3j55v{46et?]՚ tPZ7s5ztoǎ~?{t,R~l6fsjcccX:J "I7nr7o;)Z-O qΝ رc;Rú:xAAAAA^S67^}-tSQQ$Ayyz%Kq~>N';KL;^6NȰnưqӧ;l6'ZtUkNO ??9>}q:( Jv۶m }n,E8_u@nnnO0XM$dYGCuݮzvXN6uq{OW4 iii)-E$*YXu-ָA=f"&  (x$xzC*5(3= Aḳ-l2KEEŐ\WS4職AM5)    RF}G grrr4.H"&    tj-I$$I:S4 ,PrtAhLOイ>AAAAe+#'4^9bLx|͔ff3xyЬ1N۾A8:#Qçޠ5+v8w+TcS̘#{ \iyV+[n=Ӹ    pN"2.%}m>c֛}έ9훋\{C1ςB"vX|9yyyZ=jx<=$RLQ6ǻQ|g|7̥ٛښΌD#\w@ o y߆|BrrrXjpwRSsV?D BC wsuvƄ 2@Gm1sHy-^@kBGTlx+| %%U,]:_?/tG^Ò%Sٸ_?O^˕WoRkWQ(^al6'nAO]]3yy̜9gϞF=%%U:,F||NYVsg̹tXQDCy KVph؋   $IBGVQI*]$ Z BZ-<Ěcg:yYsCA[8ՇöKH~_0 cD|&])7~AGRzj>tИ,D z0r?kvj'r1'}OfJ>: Ok}Qit!'ԚZstLWbz)--EQӹꪫXp!>(adY?):455Dnn.Y_jTSPe}m$#1)m8Juˉd2Q_LEE&d"ޠV˴ةkpaZ>< GB%]kOZZ<Tj@{8,m!ƎM 9|y'%2{v %@ @Մ^JD[]n ݆ŚnMTB{$  d`e-)0/VcԘ eb0TQTw*5m8G`C$~U便w }5W,;&(P.@8ɣ'0vRfʵl2233immW_=sQ^TIRkl=&I?0㏇VEFFrw3gbbb:t_={ŋYz5 T>:Pjv6:?޽{IKK㫯._|#~+( =~$IȲ a Z/ ˪P-zLU jϜ' IDATBzNs~N]8キ #G>h ~ekCA$*YSVkzs(LAAA 5HbJ:SߨXfͿAQt:=)ilmcL:̑>.z عeKAcos=M`͹U.TG6y'X,nV^}UTcј,dϦ!3:ĉIMMw]XJVz)֬YCEEEW\Ln|*++"8q/y^bbS3]@1mN6L1VZXVjjjODgh /2>IRކւ-<'MDss3|>?oEQv\̱j9VVMMB3X6n'lm;f u,7DMAA ZaX([]ĬK67g&hueTSóBAۉC2n 2G1e|Zt; .Bw-V<}Yt:Hv'2r5%H$I%1Cֺ*slj* ӉKIӱL 6>Q4WQkLT2Q Icףђ1iQd͍ϯL1L' !p`.%aRx$)))8N*++;mYh4bX8|Jٮ6|[S"u6:#_5J̘6"N[<-}G>]Y_:0JIv !$>*3_rPфFo`K@T#2/>L^NkMVڄR(~/j/^~O ;pf,}>E+6@kce 1๕ mDMAA ʰQh4Z^G{pgl^WdMPr8QȲ&s} …ofΨYn{g}tOVJƉע*ysd69}Dm}Ego$eUs˨oXLJO~<#/ =ˏx'"6///bWq?u"E_ ه]}~Kvv6_|1@?gdҩd/>&~ҥDfLD v--z=f֫~χZ&>>~(xZ]m'/^unmRD*ª!u2-Yk{~zZJvzד<qdi=QGVG+?r|I%`jϚ܆Zqm+F7܌FM~>0k,6lSKF]iʹ$eւnz='>7v\.\'9ܘ"|>Rnƌ¸ $  pA>j e%{_""BRj:l Vb0#?ďΙ>eXv𞔖B> Bgy"Զr4X- 绋 =֩rRֶ&%}R[ R[+;st߶P(k T]A-D%B30[`)TFWP>f\w9 `/QUaI%2R{`E΄KAk0z:Hի$>C4Flph:u)Gҙ9oϞ=8N r /b|>vɌ3k/C<ۮ4z&·~NscJEİqh#b\6Ykvrdo_P8Vkvi>= gi4YAQJpZ ѣ|ŧ  pHHJLanmQR9Rt̑H*Jlbf|#y"cǻcǻq{_dI>xFIžh"` dp_᭡r o6^}}ßW_J9rB,$}RW>i#2,gZ77}y~#f^L%ϥ,0sL.~5L9$~ӟ__-U>&{vOv6W^yロ+OUUU+_{5FMbb"O?4_9tO'~!N~_Su!xPr };z)Kb*5tkXyǺgeow\t CxKNNf̙'''WN*LyԆbΣ~!nڵ|r-ZĢE<ưaxGzlWPP>؟ivK5IVN7sEd5COu?yC vtP|J>|4h&,famfZJvc9o,5|2K>cF=%%U,]:_?/tG^Ò%Sٸ_?Ok.Y2A㴋D0{v6H#va0h1LtzٜhN>_FA~~>sn} 8 Qw؉   SD$֤TJNZVk&Q}Ӿ$@KS0LBb iYDDFZ)?r.`i!)5{z=8vZW](K]p63 h4^/^=w<{0閻z^hu|k]#edRLռs\wկ˗/'--2-zֺJ^4@KITގâE8q"yh4磺:J"999P__(RSx]:فetZwvhvSP=qxp#iWb!tӈH62Ê Z^=M}}3uL,TeZZյp]X #!.!B}>?jqxUʡCeXѡ~Q3y<2GĐ6`=Ȳ:4~{v$|^/ŅBuݴ:}_Y/@^߻rptzTtZ<eDFEV˴VNIw)9D tPq---Ofذ`^xhĀΓΏ]눟t)c1&d-7Ʌd 5--Ν;y饗|0|~QWWw n=455ϳe˖)׮]J׿u<9,V c=ZTfֈ뀧&ט=Ndر99f%+V~kh4tGՅ=x+G8Z\ކ`"}hRG>Nt2H;#IJ͠0o(RV|}۞N0d^™r}8OE%*l@DdD/;!*>bfwv5:W[׫Vt\@$V^$IlذaHn0^VYu8 SQ_uwq$X,8c=eW_}5gSV]qt"I>&{utk&\LLμJ2| M=ׁXl{0`;7@ i =;kqO~}ݏ른y+}C{C)NAAḴQ5~e%wǪ{x;ML\~EyiQ9jA MX֛_ X]c{I:R9q.Ɯ<ZA9 րBѽ@3w"&  y/s*J_UNMvR]>/,v A%XQqĝ.T* -yX WV?9J^uuӧPP44kTW D84kkmu_2gsFwbMyqwyn$\.+V@ΓS}N;uF-UzJ?V}Νb=FN'_DDD(s>ζgys>)^snsvfEk;fh }o ~M&v7\yQMHgٻMMM&`'}:nj:eC֛1%%pkf@ct@+0%@>6Ykt [ea D#ZܟP7Iw}srݘ1ceƍCș)AAA8 HxkrHTzp<co wF%hDZ[0GFVMo0{-C=p>l o-e ћ=:1)>3mpgԑ$I^Iؼyi 6۱%čTwP ͚{U%NAgϞ=j5/]{v'rɒ%]=**tmDL5(GI 3-#sј,H t;LK7xZP.6{61tp-d!h5PPz>## |L\rzsnXue72yٍZ ĒeѪq]?f{B~wq7x㉉H`I {|ײ2FڜCQQ"&  y-oV]+) ^q9*J:LI""O|t^Wke%95ܮ]Bբ:@ss3""tK'J?`1IXfP2)c'lt]?f7!NMd̮oteɒ%deexӟtF2;n!S o\_sth52&f]@͎ڛzܷ:sJvTruщ=Ѿ oֆV|[bƌdggsmpH;^6< BDD?QT^;KSt~Y;sh~YtQ C> Je5z#1QN(%be6DE'23z5σ[H`Cg6HOvhTTw8L&j59sLs J C&ʚR6|'lhq\SYYzV~VdAգ6F{UdD²2tDIAAAk{E]k(BKs[K1dL춎f);WUqqg jC$Ib4~mswf q?^ǥN .ݻn|~o>ݝr/O"}PWZKcʻȹJˋĥ@a]4lmfd7GF)T2GO?q~P0ߦeA%woo=lӮ ~ҥOwK-ŏΒ ^Bn-K{Q6;Ni9$dɄ Cݗ(Xq 1!G}Zwew隸]O=aL!"d0 ~ l>/a1LbX~//>öEㄹ.СC,ZlF7".2Xf۶>{JkMܿ0F}V`bABBR,dbɅ]ݙ})"28SHK}L} gۓ(HZ>῝_'˿}ݬS據kkލ:OIW޾ IDAT\OPb]l{G+O_8ڳyԧHMM=6r\^6<3<-_"X[ckA~n>i\at``/|+Xg~.կc͔ru `3t =7ęOZrp=/¯H.@3?'aޚ`ML%sѥ{ڨn94<^Ok-.ļrRڌ}[&Tt礿i/)ELf^0vI-%u7^“OFGLwtDgpٴi?Mre'99@ aDNg^q nbi*i^}U/. BDמ3vjg?a#G=PqJ\ klˋ$"""""7YY=GU-Xe+y7)9WÉF '%%-V0kn<;o6ba!ц##+Ĥ75wgon,s&r6+++>ΙğQ$[Ni 6g"U$p{S*τԜaYYQ'>]O?ppYS%55;j)*Сf,)#'' 7nzܡ8-\ǧ=yw5&"""""2a$K&ە?l=67KOUV}h};ssn<к/@ȏO)V7HӜ4~39ꫯd2}9[tu$P__?AZ[[imm$Z6ޙ`xZk%ck쥹K˰X,ڬV }}::p2(/'kb[b (/ϧkT!Jee!M\cX;0qfFQvz e`10{ul6;iv|^/}]q҈7xɘEG=XxtJx;[-_❊cfۈ!pa& L(4rj!6Xp G_bNK-zq}1 #i:D;^x9x`0@W̭M"""V쿾Цoax3ر#):G"vj/ ~kx*n=a#ȨE7 = 7ZT99x:DO'DDDDDDDDD!wNCDdbL&lISQdڧ9›̨H`?ީfKS:2و<DDDDDDDDDd tNED 1pw"g o"""""""""2c"phu}DDDfs Tx*L&""""""""笲2㜉em/4MDDDDDDDDDDDDdZ&""""""""""""2 Tx*Yd2QTaw*""fw""""""""""dYl>eey$$8={CN\}  55G9z3L9/3DDNI7֯_w@VVꈶA?wy6!񑏬cy<ě; KKKb X-[9+SSMDDDDDDDDD/_׿qL&Gv[󐞞I%iI}}v ޽NIDdBTx9d2DN;jl2 !+9Sf3Wc2x|j08e(0`ӑ|K7SOm祗v; o"""""2$%h;ȡ'@nwPqR HHL  JwhplU`ZrM'77'xsrBNKN`MM❆dӦUf^}S9+&"""""sNy8+(ݝꛖWp& 0 I^A1g[qv/[IIyel[[ˑɝLYjjb>O3D7SVEeT߄$V_v nv=]~8E X|*gz431zN;I:RᰑXryTV`X8z{4b…8vzzS7na2)IJJ􅤱Jff ]]a Gc`;!)ťDc߾<߄cL…Etx|tq_NKt#~N(%%aۼ^ϟ D]l"kSf2(*!??V+8Hw>߃~IOObŊ!hq)uSfDDDDDdN)*f &= /pqO[-sq'%rʵd_ b'2"""rƭ]~xXo}V6oָOWvv_X|>88EE9yf/.0 m<>:kKo|vՇ>:7(mذ/y}~ az6mZ61x(Wv_>EGG1zM 7Sm>~m~Oy}GbcG;яaǎQ}M#WO_=lC>d }v宻~_­^EFFʰv|On_BZZ҈H$‹/?}tb.߾^  ~W&r7`2ࠟ~?j9B7S+p8tKIM'}Vt(Dfv.{ަIǘN'>LILY}s?9z!l~W655GIIIK C|#--A?۷{/_MqU۷Mzz26\0n˗9pPmXSÉӓwngBcy})*oǣ8k2>kǮ={z$%9YERXWQcL&\{Jjk[طʕdsݷo۶UX/wlY{h'}i9SJ+_(W^c
    HNN:a,]ZFff W^yL^9K&"""""sFn~!)iNnyEx=m}KtuD%''wen}"Df3>ͳld^xko9M_KQQ[^}X~'nb[iiI?ŦXl>m,[VM7]ҰK/Cu<:j!*//h!5^k Ap2F,_f.,$6я'^Z6¨OLdnr~z;C#)-u񏿏1\{Jv%a<3ϼks8l|+euM|SQ}ᅝu{f_^;ͳ\ro緿}.vRj9?ٟFF^Ozsĭ^'?y%W̆  ;Fa޼,|e~͗aJJ\/۸1 V>k(-uMU9 )p{X[)_ksndZZ$''t,D&B|ru_Ɨtolw<}OKKW3=t6n`ӟ^Vt}ik7 7\JAA6^o|a&ݻpF}BZZZ\` {{l>sWZĚ5UO(44o<\7d: 'Nη?91jjs2{{22R暋 fS=3Dغ5YHNNu*IEiiIdڇ|s7oh;d=+/77xQPMvvZ}99M5)"""""sh7{cnNJNaikifbpu 5-=rcV"{4DHk-Mst:INif?>>*l )HJrAۿ쩴vO_ڵяPxa޼Xqji&ˊ ؽ`pkrک>[mBxbo ŸĮQGx Cx|b$,L&,(ׂkյińrx~;1k):6"^}u7s=#oi=npؠ9mm˰CSq:mYS%55;jIH`gժpQ;gݺ5LPMDDDDDf=FqiPpz؛6mvGzd3@BB%RX2’x=8%d>|˛ZC#U5mlt_'s22Ru 8qTȥV[t98jsE o)),_^[oU.l)MM8tfM-xhbL5Ͽ$KQUUJUU)a lݺ{'6h#l6+sl`fL1}V>MVl|&\KssK CV }}::p2(/'kb[ $';X,!>i[{ttRYYHuu.WF$zܡ8':›zeXm6MuY'Rx;a\vl‘6{Ύ6~U̯hdXVBɿ.s'r*SO,ZhB}ZϬ}*eÆe oCLh)aɘk2e "VZHzz2Kti9~-<>SR &r kFDV_jOXAȑ^ziA : aaaN<ӌG ao\a' 4:jKX̱~5 Gnb1;j0"FSO5"hsrGT›zeѵ*6Ym^!leaE짼 łk^͍O#jmcµA]S}}t=Odz?S{t/;cc=JX$ SP͂D"~ǔsn'?$הps}DƣD^L)M7d2ϛoYi2^~y7wqIINV\o8ر]O110%%%yctD"8āM<~C 4#z͝NYw.r+x݆Qn"SwWܨi%D d_3!-xX{e?a[Y;N&&993=$>}"d[CkVJKcpGu.`i400ȶm)&7lN/yegf!;wp&?dL)%RF;둞LY }xOdhJ7լ6Lkވ?I)-ضa10~1[,+c/r/cF!GL~#.0Llڴjع\sѨTURR7΋/`*,wy=|zuee,X0@ +=⋻Zm7s}MNGV?{$$);kuO]=-9 MǙ>-ΤH$]mkpb#&Y\s o"""""2ݹyl?uƶm윱NI=7g2w|Te\Od"tto}}ukI?%7y>;CZ/־rBX㯧?Acc;fン+kX\qŅ278%1wn18xf>w[]nȧ>uf3/My&SuM#ĕ$k3L\uE\urodp?"F({`%uM#bgq睛Λ&џfň)=] kWt/^4·}@3-2YZMDDDDDfA>_]0s# . 'oI)x#-> }' ❂tD>uh'o}+*Gֳti9 del|L&gx.?c_ ==o6hر^ Y,_^1fP(̿oq2imȑ; #) D A? زe6]̢EEħ'VۖX͕W.mm+_Qso[o~r(,̉-\-{G7d*,3V-\í^Emm ]]$$8(*ʉu>2f^|lܸ+͠$D G;̯}- NII.o}h7y+*q8lwٺu/}ezyX,~6o_?;m9&"""""sIZB_OiYXm[PT}up ۋq)g IDATݿk_˗WPYYHee!H={ǩmo3>M]ssws\p|?/! Soqont;)z5W],doUzjzl fꗔ #c:eiiIc_} b22RX(O#lQt2da6\7c}vA/y::Ɵm 5s-W`<,)|^~y7?.46s?K_ nNV Pxg}f99if{No{qaþNM>93;{pegLKx0{W_|W'7 P[0[̱BL&3'o2iYnYKK--]N N P[Bm6; tcSq6\>ϔ'{SNd(P_J}}j$hTx9z/{'@/΄DR2 zǯLDDDDDDDDDDDΐ{❆0ؿ1iUtMd1;@#DDDDDDDDDDDDDNCKx*LM5)"""""""gx '"""r6ш7i›4T"""""""rNxz7yyaQMDDDDDDD  Yd'dҵʯ+}._WS4դ#nxF>G)M-;-DDDDDDDzxep!e.sF"""""ǩ&"""""""省H8Ι›I&iZXVl6sӒ\LDDDDD;3-)9W\GcAߍao;8o)e$$& lotuי* KHLJd2a}4A"i\-uz✉q*ȜS^q>΄D J~wedz8  pWPÙR.X66B.Xgd~Q6弱Y¡靤,ވN_g39N7SVEeT߄$V_v nv=]>jd"+5w rIF{}qDDDDD8x9#74k'7伢R ::htg rɤv{w:rqX,^Žc[QDDDDƷ0 9eddۏP\\Lff&yy6\ |r}ei* #re'99@ aD󟿎G} ݊dxXŋx|A-Сfۻ;D'P@ s͗C۸q%.WNz;{ک&"""""sFykmT߬h߶ikf}kՊj%99 @7gf>ZyfG_`ȋ1❞9-#)N=#߿keӦ ~ qz47wb2#86"^}u7s=_@NN~;jcTNkTv{$$ce.>өhK$/1T{Zl?1+]uݻ)..f˖w7޸0O%60LX,fBV^څbzL8l`Z0H_5sr!Cq}5F&*Ȭg(.WDbm'N84$ -%+#4>Ƞg$J/d>%zؿI846]{. sMдb䜫\@8}{[u;-Y- rZCbaӦU,^\#sk.СfzzwL&"""""f2qs?ko= FRJ1l~y-J/$ ƖgEVkWWD.*+vf;9IbV6mpw{[nw|Oss'&9ҁik.Wyz^?>a-N鴱fMnDv%!NBUVi?wDDDDDdVۻsyys˳x# 95әz{{qZMuhěY.-bjCGG><.W!rscCv+@d'`0Dyy>GX*wP~@V*+ n 77Èȹ&"""""ڠg`v/Zd3 1;PKQr摔=2nقE@tjʱւ[*'FMnrNoTMDDDle^ ao\a'L&,3PxD_K/b1}bD },<#bFÈ}꩷F3Tx-2;["-#k6mN_XBQiBwe+.tn1 sdZaDiqDDDDD2Tz䑭öG"Qn@l{(oBX99Db.DDDDDdN"_Wln3m~RRHˈinZy;sV"""""ǩ&"""""sZm^jNx^^{) e`10Cdi+}#e9d}O^~8f$""""2 o""""""S[MDf^wM}-xi*93DDDDDe>."""""""""""""r**LDDDDDDDDDDDDD o"""""""""""""@7i›4PMDDDDDDDDDDDDd&""""""""""""2 Txx' """"""""""""r.k~)o"""""""""""""B7i›4PMDDDDDDDDDDDDd&"""""sd8>lvǴ"""""""ew""""""gZRr kƺڿ0&nwPqR HHL  Jwhoye%c2D zinj-)9/gfٱX>y=\jeIKϤضHSE+pFQ gǶS=M#DDDDDdֳZmF e D"c;b_/XGx (’;aO՗aڀn59NdOQDDDDDD*ȬW\Vf#TwhXu"eǶ-imh#Q^Y*j! bp &$2s)8JFc)ē o"""""2UѦ:6va#aE짼 łk^͍ǎ o[f+Z V^J0H)s9 $W~!)in@-tȵ1bD&$&M*O0'_DDDDDDO#DDDDDdV+,@$bPld\\k;'D0LOXm4F8bqho!o^1ɩiN~ߔ∈ș›jVktDd&5oK@83OrjI)ưWS7 ]7s o"""""2ݹww^\VIyYtwFvN5+%5=oJ&Dy=S!"""""r::vMsssS9&"""""ڠg`v/Zd3 1;PKQr摔=2nقE@tjւ+(*s̜6ť0qs J)0;D&[G{ }=]LfVـ3!qX{~a E=hV^q>+l`STycIɩ FvIL]bb" N㜤o"""""2MYI6F '%%,Swh߈GS\^IZz&/^ϒE($!1LL&F8̎9Mg("""""29.m۶;s o""""""qOtj]ڼjRWԾ~-Q`)id à{awEDDDDDd,K/%M< reشi%= MVqOxq@ H "L&֮]¡C q8l!߸q%.WNSMDDDDDVS8IIy|\}ϙ3*Fh_,nc0؀@!hY&m{5mMwmڴM_閦K4  1xmYkk<}^~ytg9g{S OOZ`JM8s3'Xz=HD<B!BZtt r7Lj<3 7@o(f'x;GgnDH&5۷d;qb6سg@X,F,#v]Oip8F h|ߙrnuO˧$&B! C? X#!B!ȏ)FٺUU*AFG "ˋil KPVVY?hԟJ3*x zٳgS3|Ab$TCyy1eeEhZ*3\Z.eJ!B!5$='Y qu= @=uB!+734 MӲ^;BVwTUG2Ci?ɤ(D"9k^%PUo4Ma)ImyԴt]bw&)."7!B!( BB!\kjty-M!B!B!B5JoB!B!B!BބB!B!B! !B!B!BxB!B!B!"$&B!B!B!DHM!B!B!B<B!B!B!y 7!B!B\sT^=B!% !B!⚳nw%w|dUK ]!*LBW"m"&FTj(`+1$d"VNB!B!M*חc0z\V#IiBWI+Y[5ZRoaW*\>D)9pZQ :b8=>{}9˭XLDq@g IDAT jA8| V]ipDE\$&B!BkJt-wtIY`mD,YV1ѪSuxKIEol7Z%ymLWTղf&:I ]_NM? S`7U a]`m uyK!B! ՠa%IktǸL1tf2M*6MF s~ 3'N b:3Ԁ n!B 8E\$M!B&)B*yUGc"2V=`7F ]D˭UXL}d`uiz0mwv?HĒBBW犰6Q wւ#S.5Eѭ.W=Ii~Oo7o6HƵE\$&B!XslvV;Ns4mi\&7l@"glxZtY;v݂dLK@Щ 7Vl7-6 UI页x镂֥OTR>Bw_V&3=d#ne$IbuB+"pJKW 5)B!Xs7bXV-9*.ᎷAމeBKhLFvgYO2$3XSt f1:- Vy *XL*^?P :VɄF,?_V 1sm&0l5If{z(@n?˹va5fʼnXxxF483;w,tS(a+1T(S33F$%39V,N#)-Ep*dT!7!B!ĚRЌ`$}nInq&`g5[<.KQ6M4$zmvQPn(WjѺt;Er.xkuCAiܬ[yV=9ĻcoQ[E_U ZC~ۤVdvZ׻%9d'Cmk'yk;lUacKNUwb08fms6,9ŨYN-;HC1_f 9u/P@-U4EWHi)4MCKc}cc*K\;$&B!XS7qdrO:ETTpWnQYӀbEӒtYQYkl&y7DnnO:ۭPVJQd4. F 2ΖZ]Owҝd5P\Κpg ^YG㪰Jh4&")[W7( @)bAm]wg?F`F2 Y\%[}9TJ:=CKhU(S}9R yzެȶQ$}#;ZWssk+6齭y//x?R9$؈j`8pzM%M*k乾Y+v!_w| Wfq6429MPV0hn^۫iS ϛtƬޤZqUة+>X(1qZѩOT@L(வci[ިjrqmB!b(H?Iپa&+k 9rO'Ѩc)*rZNnquVl&Kjvێ0;b8Sm)В)&n*7PRdsW.[2G$|:[J|uhr6Wh0Ϋ95ab/1-9Գ}CNhT`,ePVD)S_x[PK{M`,̫>o$Y|h3AGF7̾ɇ|]+ `㝵(: ųkgs~귳8a f`MmzrY8J-49>3ɬew|tے5J3|L}*BShҦ"j2=5g6HM!BfFvI7!s$Rk^GcƗS݇G枱XLޖt){L;|UnՆ *=7=g.%}sּe7?-Di GE׺zsvYh3duCz}5t꜆tv';q80 Touhs{.EqZRBoTsV1;>sޥek Ax~6YC./t‹_9rCQ^>Nitm)Ɗ$׹Jϵjڕ䥯]]Uan寷1տst6kApTk)='_uR|b8c>J׹P3S-@:Cn8J̙@ߡ0hiC/@m M}x$kh{W EP#5d B!Bq 9W8(إm[Y;羊0=[عvʼU˺t㣙2js&MMM155 J RLhtZNDx_ȓ MTV7:X(A߱1?3nˉjAUՅw `HWʛw>Wsw8=d=ojZNSk#9grέipe뚣D,iqrڣ8T:e,z<燙cHʍPKl .`~` ?JJO.< Μm9Tɉ9O?0Z<׹]o[& }&'' &hݗ<5I1ƺ \ӐL͟ (\Ȳ .W@V4wH=H:(,Xr#1Igwd 3'r_&WE~99OR, cpYX8)Kʈ<5`.g *;[~m BS7tᥜ6i.Jqymu.}NdliWuUs<9Zk'/_3yԌ*ܳvؐVvLg=o0SA*mPF# ϑZf'ESLoB!Bk^o>(:Fua2/Ժr{;>wPЏbnzQ]p(ɣrUNG,#r杖Х%;xkYysr+ZRIi)JMaTk {yYé\YY cQ`Wuo_erʍnqюiO369e&{~a/9 d;\m+ّNPTev0nוGqJx\jM3 ʝ5s,9  jXy2|&pi{ըR D5QC`,ЙI\{u\" A|}rI-15_7\dzN>c_kɸIxPxөJ&;׼vg&5WIRL<=Aq dR̙  ]XgJ8fOZ!BqͫmhFo0L&Ȟ]E۸=e>]e!b-Xײgwt:׭ Z2d1uizD=,L3nGDQTD>ǟ1Ӵ ͚+g5MxߎQMF7%uNTbi=L YfŹlp tǸѢn(`C(o) ga&g8?Í.̯tDtSYC}K"VKh::Uk]D1v#ܙߙ ҆Y]Nc>\vn/`ֳEx]u v&1MoB!BkZyE5vGI厳 v%.\s`O2@UXY[  NƛBjCE^t!&ElYNl` 9N ݹXMlC;:~xەh4PUw}1ç');?0fwj =uQw,9EWRRV£6 $A71t  Xtv3wů/zVZ ՠCoU1>=qHf)Qx$}y2Vx/3R˭UsIi Tn( 39 49~3bϟa/\%4Ϥ3~*63٥j ND_ƹ']IJewܙa&{ KBz)&zDvݞ{X55G w %oB!BkڱvkZhlH$~@8(u8m~Sh ׺ިҲ kq:?Yo8DQjZBסaqk=2JDqL67yguVU Z{|TlpSQ\ RI2[`Lq=lV7[_fd-2t~ӣ?\z\8חSR`Q/[;ʰ{,zd=G`SJ^AV\m2prf̋*mҁջ ]_f`xG2红(:QxU_IWʵq_?>F6d\L6XA*_Hq( TnPdDluoT|w=\4Z_ekzʦЛD1zfNQxB!B\BDҝ1=51v]7Qfw .HMy\pdrfR oHMh%Йɼ x9Y\&eV,N#_ǖ40=$I`pY2Y8ӃA\b#%xB!Bm*`zrWq _=C̸:j#OAӖީB2܄JyS:fyBSQ^__NRE&5m¾=Gxe-1$KʷN /l7ϋ1J+B(к̀BURɄ_oc[)o.QjQMi)&L ?=iO%rkgk ܋'68-{Ӂ,#쭢jk kхJ#!: wd4:.9`V1Am!μ)m6g֧Ӝ?0oN~NNcnCÄ}1Z`/1Sx MFi?0@qW\ }OZ鵓fǘeC9̤>'/v:=SVJKqmTl,/E6\^.m$'9`޹&uκ{(sLiӈ^ih>_,^qeR^e'B!TR~3X6naӶ~chQ##Sl@0!%-9QZ$IbDw63(//n7߽}(}}c( b6ws/p|[F&JK]Dqn<'f={6FX,NqX7ݴ@ )7ejcմjm[B!B!B!UQJkz& ]4b(f166E_(vۑ^2=P(lH,(~$SVVD* RY?vÔgI&5^ymV9~={6eʙIz2ӟB!kPII ̕$.5B!bf{iZkY.`sҙd#x[4dRCQTUG"U^Lj}cU4-;qsK>3̘ߥ MR)^Q̪ބB!X$& IoB!ZVoךPB!B!B!BބB!B!B! !B!B!BBW@!B!B!B\;ˋO0:: {w/i)tt~Xyy1` vX,[u/?@n{ʽK_z ͛#bqbBQne g19'3<6cB!B!B!LG {#ɤƣ>߽}(}}c( b6ws/p|[~x/@dR/}{@V3<'f={6q:>܎bb1kOkޏ]oB!B!WUQIBդӃ(t-DW)FٺUU*AFG "ˋil KPVVY?hűͨJ<^ٔ)wLy>_X, --մP^^LYYZcW^,B! \u= @=uWeO_3zBU*&/POA=L% HoB!B!Vඥ\ ف?_r9vn)_~u1v]]IEG젛ƷYRZÇXiu e]ȿV$%n@p:q5(QS \K9+ۉng& w9˫>z˛Q F6 ^5IoP;(ݯZ@$&B!BJBSY8rYA7O)=u$$u:M8 Vl*oC:=w<=r%Ulx_7IDL:@7jb.ٰ\{X'__'L0R#U!:~):=)Wݮv<͎{A59܏ ]ӿ_ $>H`!td B!bMR%/Bz=:EG<C1IJ% -s8[[nřYEGu}I/eSvbQf#J+o0vYXxhyw=a;z~BWG`.":=rUh7B"{,~6yM ?4ZjocyNT\}$&B!XslvV;Ns4miO&7l@"glxs;nY=Fi;~xIb-&b L .{z&zgRm5?YA7@nW 0[nٰdUsH4cWy@ q'Dw+te MMbȟD E읟Gkb3BJ\&OvxB!BA1[xh;\%fLf x M0xj1-sk8]Ÿ= %X?a*_)= ɦW9mob g ]!V,qdx ]q:_D7tӄݨx[s$&B!XSz5 t]Mݷݍd7509> h {ΙSswG%Mkl0\7K,m1|J-P~=?zj-xEU @<0 ,Mp-YQ=,QJ,0(hpn0X]$"ALPN5`pT~"̪S1:sg7k8ҳ;{u+*t:=4|.cm:q.Հjb5`-oEG,ZPS{>ϻ3Pr9yHiIb"s-2@5[왟xx¼TFG -&$FdojTl-XJ$A"ΐҒhAou\ID<[eK C{O.8QMa?h(Sn Fg)Z,2Y=OQ8BnEIM#Xsj_wpyID#vvW( `jRFhkYEKL [7RnFhp)F,BԣI7 (s%xB!B)5  F}疴7a2 'YcQΝ^|G<%!{ͻsۧ>o\̶ҭׯV:n8gmmOJɖ}(.k :~D'gw &}(A_=@C̹op,ǿ>zr$=}+{Zn[ɦ`nT -# &9O0y|dV, ˯29zױC]^|Fp,sib\6(Qڻ>w/q;wj{0؊fKFCy&(t oDu/8qdQn}7>jgJ=U=9w~wzT>v`-0oR R:t˿lsyz]ΞG;c]rk/TJWkUv'y~͇1omC>n9uͳMwϾ7om:_LPthP>rIoB!B5y=gI&?YDEud3D"i#\#:G2?Uw͢Z߽#Mtܦ|_H]Yg q#K޿ug @RyY_ j{9(4=7@xAQp5\D=V  aQܼ gv6g9? :=o"`+dy!My -#8=w%w&\EG>A7M<é~*Άr{ǡ.Zε ?}DCٟgMSl^^A6VNe'e_O-a[yE{?v!kWo4wRѲ9wЍɢLvCzpU.}_qU-B!B\702G0[Ҿ%!qFi"VFףmJ8{~3^Z/6|!at0]ڽhF/«yGk}-kyG[WKխ 4A3_`|?Vьg>:ܬos?LwZV?ɳ3<%;/eZ ݡzكtgN0|艜eMlAQHF??;3z1R{oOL/}PD\[6i|W?Ep(;@X<5Ə?h7ídΡSndczEEw[\*OV ҙyUsoIޢD|rlxjPߙ UoC^z1|b[}xmNs3V[~TcC|O>tHĢjN|<ٮQ7bLL\qs?GDKGV$-'5ЛoK>L K)Z?r(=lZaqRB!BB4t6be-]Nڏ7%Vա[v܍O➞acphRZ0n1ȇ;sꗆ0F 2w/ IDATd 14`0`줘sV44xp`o ?ZT{j=p}OW#X w1TҹE$z`d%c]mɯ'@^vf3\8h04Ai(^9@84s1e+6 u5W%\^|n:-Wӿ[^= wgds@uofr E] <xB!B4-%"c5G=Վrt>z~Rr6xˁhb!EVq8q4tucMKhfkmX "yy)o|$[H̝,7kuҁ7A16y lN:ݎΚD摚wٝI׋ĥMwϰU1.+siu2tixڦFd-:Cv<}cId1ˣI2Te]U3=fsLz&('뿅EX C8{h{_ל-Z-"L~= \kq*I2 x"˅YjМ vM:`͎KWk\v?&]V+ j^|\xSku,)3-36\w.jL&G>oJӗބB!gJ4Z-`5 Zǖˮop4頻wX#ƴ;oVyOH>6nF˒ky{_zg FC 0R8U " V{ݜh@d/TJXSpUoGbKGJ 3Nf8Oԍt}쫯`+{9fv+䭽m 殸tRUfxRgfloU2~gd/JhMXK`-YB񅟣ec&!r=IV4jtrt`2Քh\NwVfᯀ;>rƴLV<j#^}_GA22ooL,xN')*pwz%C1\g$Ά6\;Ud#?ER[y䬼 BE(JLE?Z@!ٜmY,t.ΩDihA`Zd7<8^wk-W)3yp 3 NƐ3T8[*$)EE6/?q9{qB!f/2Tkݑ7;FeuuO  Vk0IOW{+ #ƧgfGw&Q$lnRbR `V|\UvBK!SwpY tN`7Zs&ဟ#;g'Ow3#+UoZhB!zkޞQgOTL܃a Ч 9`Þۏ. lufȞɹvʏI8gk-m>>=>I;p> gR* :-i.=gdeߍ.G={k켉WN1\Rg0sݙM8포b7VǂoL8GJfY?R9&Ʉ+@gֽqRuB!b6VaÖKU,8̹}/0>㡛;.兀uX,dgG_8x=D}ʹ5ս񍚟HQj"6WNXKAQM>6}Y՛[Fze̦hiKd&ގx1̡ǾƇߍy>иA7G>e-KIl9RRRXt2{`Vg7JWdPS޾~*(ӻ_L6rL`+s77Yom9C?rZrndC9K/JƔF s њ3]5~N8Bz߾Ɏ8Qo͎XPۈK"I(c2iKx>u}]ZoqH9O}Zkήk1&3LSFrJ;{I2Zo7jގ{a6h ӦZk8@nc- Yqŧm᣷qG6wS"GƲ̪\IH}>;c p8u7-dTI\vP2FoB!B3ھv_KA<{Muc더.I^ǚ{V2FnZ7餿V 3Γ#s[XpI`+Ö9"TZ%[?Oz?m;#mA&<>=wvut נgYWS}ۿ,qo4]\ti=N7 @z**?mt'H ̻+ HD4` ,(_U.?9b6^}FU7cvqrLy7|oJ2;-&{pfrzL+, 7ބh_(d/J pJpַ cj 7,T-4 !P36=_Ҷ_z `d,(-STj6%~F0畟}E.{5#nkM_Ѭ%}z#c}K▧q} PƘĵ_}9"T's !B!hnȓPޤ57x|r 0[ be#e|ޡseٖp(̛^D@` kfUz D }ʁmu~1ft6ut5c)%u;N3Ko*{bLrW^Nⲃʯ9[#ikPimͷn?ыZg]ˆ'I*i쫯{OkD쫮${Ʌu<1mt|iKUoh-䮸WQTEuFǞQ9RAC~Ϡ KF.^os?%=<2 q` yE#,O0ھמ# 7~~s.{y\]Z(|^vÎ>PO+ />Lρg={}>J>n`+`+`qwL oA# W_ΚEzrΏ^g&`yy>j}r9q3E,Y9p(ȱ?ƽ䯽c='ݢrL7EWGfX*kRyd_C?!f_ͷ1p|u8 o7Pǡ|&s:0o8RZo>)kmg6&Cf| DF;4Q p(ȡǾI䭽KG5}"WώJGVcJR;v7KˎMik=TRz‹-~Wעx'7_E_yT sW~ݯ([ye m!mqMw7%#xvFW}-oU瓙_Bf~P ⯏$m#c2|g&Jo#Ung)%//OB!B":g󫖰x.'q5[l*tz`v|>/Vk:陑li<ƇnO}iڀJ:ߋDђiA=iehy9&E?C8杹{=[ً\wiɵ*? B}ZA7Z/^,iB!⬤( Vk4~omT*4-~p84> !>EnFcb/"/54C9h}qn#wuwİs!l9CM4\wG$Pط0d$uw\SOG[gة{=>F i( ]O>R;:L& sݍӒބB!g^Ac]-!ZKSh)%eM?mOOWm(yW Pnx,%A!Ro ȍS \sD8<8eȵ>t,n@F`Qhk*7v;;wnTs!B!8+1MN9薞Ņ]%M~^/[3a:^ҕH˰ Q[v.KWG7Lg! @@iNkɤOb.X0]#SHpͭ?_S{sWPTT4_yx$M!BqVhE&?rhJMfo`/?A_OZtuۆ8wEزrm77QTRywB(F.=P A\EAY5@?4~N.-k9h&|^܎bZ) qKZH?aY;}9J.-;vw*2ܦKQBT* ~Z|]ncc2;qhM(j-j ņ) u8(Xa.E߁ix1d3* ١N3=LcLz3%u}fa-]9o.-5 $IQ1,UJk u1׆a¡}ј.  9>Q{1h>8afƘZo$qcmYKK!h>x3Ys?Κ^9wW# Ӛ3QiG2N<9X 9{q4}F.`E!c 5;#k~?ySnlLtl9D7ѣEk ܃\t)aS &s !B!*eh:~-Ǧ q}^*B!{GnB!7OOB1]Zw}#cI:G!=ZyIKa]m)9 IDATPG&ݎZgdeC֒ PbO{YYvPZ|IKBcj~mv-gY7p[WI}Um%a@ѴmhN3hcjY7QX07N8L(#u 8N0*\]z`Y?&ΔW毎QG_S>MV*ϵ }_#BE'o(*uly_;~IںMFk,uӵx/֐x3eWmemW~8( ?MkP,q-o)%[?OΊKhy7Eo!wF" i444J/Y7}?<4 6M}ǿJzŪ`fo||GFէV[ş1P' 7a^|_kW2o^*1,h} HRccޫ?^QPzzަmHM!BqV)\@SσaM ߋ MZ @CqA71>gq(r+y^Z-t3}2nݶwOjNau|< Bz ~$2]4F+U>Jۅ9sI+[Nm?ށθmCNGfo7b['>\mGgg$(*ʮ[r_ =`>7[֢j%Kg㲔R1&Tpލ_Y}H0dc_}(*^%]_Svߢ(*| "a-d/gޥ_AgɤGlll/Cmpj[wa:Tk01w0XEZ8+{Ml0-X|g)8Fܝ 8yMc)X@޹`+wrPS(T^MlՑs?ZKzJv]7hY$(cn)շ= 6CёU }f|ޚZ5g3a)! }zZњɨ<cv1U|O~'l~ EFkĘS>ݎJRa<3Pit )ahjWR|䭋x[z(9:?z).S2fRkqH7j#mRz3E?ZoJuw2^5z31?BnOgC6b)] x\9ބB!g",tNdA<@`A̩X,ddtx!X L\^%tk{w4H\B)SzW0TwQq7uiTŔ]q5gYCV&{y2gE=HQV~WYvф0ZkbA^+5gx`c y_pwIʪDpɑރooߨuF]4kT]K/"Ɯ^5}29^FϞi {ɅT\s/ ;oRi|?;{Yv/1d=O''Oe=6X=ΡǾEB7C7c)8C܃<:#7|U䭽/Md$[Sp0@*™ő=^ՊW^yepRMB!B+lkDY9ymۧ?GBVNt11&7nvW^y%{/wyg {6FA`[JQνֲ7qksx+x3:^}dLӽq0Wocf|{^ S1mȪN|}.6MƔFі[?:f.W~I'l#c /E!8uؠCWbǜHE48[kix@;S_ڃHTk5&WfqǗ$co$lgw~S9{qA7p0@ ?̳(q >qA7d92.jsJ:'rhh\ #'93쟈1ӽu>1sW&ˌlz嗱y _{$ z]y4F6zZ Rf_dڼ: `|ܝ%7!B!YlMÞ_gi4Z K+(I(ز#7z+d kr5Zܼod iÚUXbJRjO?4~{gZ/}s ?az8 8F4&#jlΩҏә!CVqxNg QY7xIK3\R0c1@m٪7 c2l 7hITI*D&BAu5+\vj96'SLJJ:wS4Qj|vTk9&Ls8D8"=^Ƃl.Bkkϫ || rwhڟpw8lO #s!oK.U>Jk qCdl` 4*8Nz*vRiԚfwFck;z#&B!gh頳mIk[pO6CCC 1 X,qg/JZzMs980H__2{Ωҏӝ:zx-HXJo4wG=ƜRL˻m#we21W%nJwM|;U1&SmR5&3LIQͱ`p%(gdqwFT*}f>C0sm6dq /><&jyKB8J:wHI0Z8Ֆzj; RclMn23e۞L1'84d(&{9漊1% ǔ/ϬS(*LT:\}WfacP!gB!⌧h) 5! w@~˳Oyh[$74[zFz\V5:@L9UqfxnGg"e]n4NP to ;Y!0=ޘv_'C mmhLf|ۢ1d>fhIKE  dΩ4#?5FL>fzLP5O# k9XUp6`h-ȋp3u78rюǩRIg3zJG7O{ճI*ZrV\J9[0ϟC3X0;ϙ*]K[M/`7P!v͎Q@W3j{!{!G IO\w_l!7!B!VK0.gdoֱek47r:j|b*,ȡshR9Yvjq7m{b˶W7:w] o]t_ev6FC xE!)/.9̦"P0&Jg)l՛hzؒőMx{ uc23]+1 ^*䭽m 殸tRUN k9&qV}|^Vt+W5c-Yd ~d IQgؘ&QUZ=L"6^psYT}9h90`dі[cs*BAP>'5ׄC=v^= ,"BOy!%M39N1@Q,Vu9+/CcPr(4:Sy9:<0BlL4ΫhGd cN)!O`1nx Qz '`7E" :{GqtI7`L']1 b_I8=~fxB!BEX'P뎌D;[`TZWG  Vk0c~tzݝm'h2c4Y.ۖ-[;吝meϢ"7'[*Ġ<9Zߏ;,stpY Lfo9pO3ꓧWت7lZ]Z!5oϨ@3F*dHݲpgAvCzAG/1ذ'K;tM}~SLεS~La8[ki{YcI}pg70I(謶قm:׼]sJѥ医~xy`TOύ]ٶAMո`|B+c4E'tQT `xRˏ@Q)CcǒmLq$&B!8VXl͘`dÖH#ioyQ%6O[2`Zp8׃bT0Rȑ#<<(u]r>v^fXΎ̿"A7q{02F3mTǴz@QQ\#YJ-cݘ+RJMWm+ɪD+VC;b$i~7U+J(z;t~锷w4# 5[ S8 5?L\1-~W}ow5h V[\0UGJ[ ّp=KaU_"3r&\ɨX!3)-O :&m+PLݨZU67 ۫P:jRԯ+㶹))#ۚ?xblTU`g?G /嗟ϿC(F3qb1 DέO_w!>NGQ"gtvF92\!|>?>_pp Q0 A|@oSOmK`gbxtZ^? ZZmfÊ?ɇp(2|B.|%&B!8i({ATjuly[k`01%-5m6A5w ^2r1[ǟ`4E:s#;vlL𭧧￟ V~OnBV?RNkJNg:A44r^DƔF s ߀֜IxgvJ>tiXGJ6v'fEۨy>CQQH+]@Ap=뮣{kVMO~L6sm6d&~goj):8䚕>8wYqiZo7jN9NO!C=]jK!hnӥL9yN`MhM F1 յqk C<ثtu5x<^ZZQhn`r%_Ol/Z@NN:^]oi Z6lXc4bg߾֭[鉵{w|hM_\t<j?,A|7-u(4ס5NvjUB!;|!;^kSݘvz#s˚{FO [vn)*s:ƶq2288Hgg'wߜ $nKK݄H3V-gEs"EJ~~!vF7TRVF,5 Csm^GPz[{ *skq5շ ._~Wu(89z"sW~g/oUT~N PuwW173?_R~W). 漊qhzW>yTqz3j6" ֝Ж l G8}39R=&ӕzoBc/Tn%{8[kvLp8D[O15nh,T-4 !P3v&t5{0R>h-dTܘSB5j;Jρ'쫮@uJn>=[=afn@7-,DQ~#/^C8mS-ǚCUBz]Tpݘf\.CCCtw҅bfj4j\vُݞҥ|rs3bˣ] cX(<}tX;`ޫӎaÆűv]/ic 9}6u;l#^DCHƛB!v?9PP(@򧴛R9y-V\α͏yȒh0DNaIYeKR__>g?Q .qFxy/,*K-Qi XKAcȘ-yp*|kVQ&ov 9k9\t|WN!#^% 7ބk1$vVkΤg^ٷX FƤcVPqq&{9+esqQ~CVKb:  +ﱖ.A v-b%[=z﬏g80ү`)eٝ{}nLrVsҽ5n%g8L iWҥQܝC!si~'Ҽ(7N/h,=ٳdED07 O% ( j@`FFVŶ_@V hԄB'|#aNωDEyu>Tӟ% qk0t|' Q*`?ա{ !B5 IDAT!v,/q3Xv*CqEϫBИ6~uX%+t 32ٹ5-ꥫhkmd$;mo?j#w33 ؎9|o!c_ {V B Hٳ}1& }j 7`_}%:k+c^:?zwTmW tę 16B)UT+~W8l{4nZB֩K0D=%i_jT k% w%7/g|>JJJH.<- 6pFD ۟Q1ގЪQA&o}XӇނA&fWkm( ɊC@{|rN/^%-m+> =<5y&zѱZ_=|AʘQ72XIKK; !B!zzJ=nc&L`,V>ab4jz=ذŇgUy ߬Z2w$$R57tt`6[HHJE}ѢrrᙈY^^([iigڰ~2ZNDoŒjTN=Pt* "E5m44 wKMc`&*m4zK >G+]}g8T16=<2kj-+:kjƘDZws ܕ >W~R`0H0D44M<6чBmoGP Ӵ 4u}{ MUgв g_D{M(:}Tf7%B.ҍ$M>ׅ{6T)bh/L[fNfKVN̓رf_z-^ /=JZZ_}\s NՊ_ 瘋@{IM9?k*+ބB!bpvVSg!)5Q6[س1@}@Ad!!1PQ{E[ԊBCl טi~g;vgK"p5VViz57G⚨F 7zL٩7:(K(Zlz>Ga5p0 N*B4{s0}܃;A-f3 cϻ OQJ8Ws519Ttz|v'c\*S߇іn  pvEQiݽ/bL0wى7[6{C gGqFGzdۣۻɕnfsDDgNk++ބB!8FɊcXţt8m-$^ohvaok?5'M xȧS ̺ P_UoIaO{C4mPMS!Z9wx_er['Ephu߼ZM(@UOP P]FM#s3[<_x$㸝d0ΦmހSU䏦x5Ȋ7!B!8]Έop`)Ԅbds@}7>ϕO0%eHԭ{B{U1Y-wDGZ9: TwxМk"~r5Zp__js:P>p,ţ*~m?cѢY^B5Zj_ED{jj<h3^q'<*n @ff2bq:QQvM RB!B!4Eou`O+ng#{@ǪbIa]>tF zstx_wWk"nrurt[tFÉ*{ݼ@WΚi]U#h Z8wryjDQVp^Yـl`Xz^:Ãqh\. X,/t>^˖-e̚5@ bg?_; !B!BAoav@}>w;W Av4GvbS7q%F9>CT\C54l0?Ꚉ'hX [jDž9چvZ`0h6VzJ[6N7GSS=:OJJ\}un//#5+m4z}_}YBo(I7!B!QRM$&Bj qtR9 F{GzJBq:W\mf]}Y"Z cYgl6:4M7 PU(?Oo}jx?MpFxcM|yhlNG0O TUյC^MoB!B$&F@HM!G-6>mNy$$edggq^keEwݍNo@Qt5U/1+^c455ӤeojR!B!B!BGa-T4URTTĄygZ#j%a}?VTToAoB!B!B!",9&Krjj۝N'QQQg6#juTn':s84E56%}ր jQU;wl@UU-:˿@ӂt:Zc{jj<v-x>4-HSS{[҄IM!B!B!B %j@5:k]vTU# E'h7_塪E^X Wc{eef cm\z|\.>ro5.7!B!B!4:=hBѣր 1 fTU@WiksІ[{jjZ߼A@8[PJ][͸.=`X~;cy3ao7q$PT|=4Jׯ柿ވ̂%K_w\8Ga-T4URTTĄy}p nP0 O{}+**ϧГB!B!8LHGBT<Gczhs4K~9cf<j埄G@axwtɛ14bgPʞ24fjf j02aޙx;Hwqxޑ:وaf2|:$$cI0STNm[mx$**nݭ[Gv3'`C{QTiCo6<:vaDoB!B!⨳Vg+q8UP͠M!l{[HJ`> NwA5~ 1 kȝ~"MTlz$XŖh':1 kF4c'osر;>G?\?\fTȩ#;:GцGT>dǰ32k" ?@~[K pdo|6m";;n{ru JC_*hy%--Hp-B!D:::Fx&#CQ!>rrᙈY^^#<q2$F'RVv5Nk/!vWD(4`x|mi(Ř>a=ޡT3si+5ގR[jMUe <ĥeu;p6"^%oQC[F&Tj{6TDLL1IoZuA7, !B!alI-VF :fOԳ/b̄)X|^<7zQLk_53{B!B) kjr=/Fl"N㮻+nCUՑ;w7Y3UrP 'oh2.M}`2Gbvt1Zz~KKjD1qNMuTtO&yhw)ӈOhbobx}{St*Y#13Pg;{wn|FBFF]t_zG.%R4ʇ]vZ'N忼:χk{lf'廫"2Ә0m >E)O{?PVA-@ Qw뫨z}SֿF`DkּSFm6sB!☒7祪|p鏦LdagGDuy=gˉs gP][V !`|ī;vNm7-\t?t ;|Zy8?焋rY{#ml?|m]\`baSqy輩)8\=c+eS\.I2ObWg7gWbeKO3s܃ꫯb8s?>---<|]װkG%%ÓWoB!BcJg=^ObbH`Ư"nC%6.{l6vIyqDԷSP~WUjn9k>z`$~&-{)E'r]dEݹʭЛL6xNv>_\;? 8&3qi$fs]Tmx]ҲprLMm)' :-+1~*YSf3O X6Ƴ>fc޼y455oo5zI9cIL ]^116FϘ^kʈc`1x֗1'w+N_ BBf.SgIg=x~۽%6~Psu,(B[]5%VzȘ0Q u$_~щr4Vɛ1)ț1S AP3g3g=t&-t.o.͝JTѣWt1Q1V~ )рsV ܹ$&B!8fgctLfdr:.0fdhXV+&L`5ٳYn>p:Xp*7E>{WuJIW~@UjuuT%b;@SGMv 78yħgog;o?_Ö:?6n?]n~3r?3~t7vÝX> sݏ98|EĶ[^Z-uԀaG9ƻЩz&^{&|{wnbxV2+ ԩSYd ---Y߾c  Ͼ,og9*@8p~SY?3 IDAT }9]7+nbQUsnKw7Sx0Ltz=Oz,~?_ǥeW{uuDѩԗٟ\~s ԗuu^{CmUmwL;#){ gnEaǝ]aΥX$ff D Vݬzq~YpSθt0~_b4X B+?gŊö2.%#Tӫ!gtP|馥+)vyxgɽא:*=p#4MA7MxїR3.g^+"gL{@\B,5;V+eeexj$&B!8; *ꛘJ%S_;CaS_?LϞ֪m<,MzXDq|GfûTG<̅w>)*'ͦ^wo`ٟ|YvMf5c_@sz zV*~w}3[n6m;u 8lxۚ~Q1@(E=7܎NzF{^zT_L٧2xe鏺e2NvgcI B]k?ށRɹ:kVt 8Z/=,J'gT=ZÝA7o'3Ob3}|_\bOEl 5ẑ0GeKׂjZ`YqW~rﵤd$_˯+|CJh[׸{׽൏q;ݸ]5-Q0X|嗇"DoB!BcBTL,顺'{YH"SǾ@[K Qd#&FRY豓tuUón_ wy'r o-bɒ%o :(qq6 ^}"gm I+׬@ ѩzFM>%&]D-jm-=lY1/`Ԥ&Pў?k>8&)qz}~anV&N]wEEEϿ[ǞoVw _VCcɟy2 t2vϚS9zm&Ɵx[VFKđ7}.?7A W{+V[y3'{}`Uo`zI/W?LrNړuo\Fi`A~O.Υ^ ~oG;wxԵj f1c~_h}_*x_eu$'rN7`v.~>_ /Ze{u>R+VxB!B:W9:tZa8 #t0-.TUUUJS{[p]w :vYgq5ν;7; kۍml6KPb2k}"d111boz2T{:夥yCe+:QLZ5sUVןl}O?MLL̰j_;Ɓ["^;Zy1==xtWWVӳh.h7Rq-Oo-D㲷ˏ|.b{e3jb!? yl]ZjƁJ[w^۷\)}zuw >'~v}yf{ANu4+uex} {į/~ct:>nʷswYL=~+}#ބB!G=@JvDb zZy-Aύw^qh7_z3.8Dn |sUD9>{`#ބB!G-@>Pvmtמ]EWNUIȢ߱2 qΝ; zx㍴pwރ:^ =n_[;i􁵵uP945j* 'pJnp_LX 7ޠwtMu$2i9y)+dѧo!!dogz!JKK8rٻRu;;^_`b NYM8i!:UO>YbV<V<F0nwU$eKNH0,:z?nIk|Om׌31cƠ=3Z{>`R2!=+s.8kzk/֯LцbnzRғyio>ڮqcbXt|>Z:b?[|W pʙB͎f@'7MT_C*dMKrSYӒi:kiɔ (l-6>OOIOAѪ`ғ33=kkQ>+Ppw9)%$&B!8gvvm>">P{a'z,֨/mC*h{,XM7DKK s555}^`0(xB)TU?qRB؎j}ڵKrop7=CmmNKtt4IIZ_LD\N.l-5PthcOAmzvLlJ(P}Ð?|ٗ^Gɡ4c1Yi;iCdaa!/}Y?F}ztOq6cK`I ٱr gk>~@RΘa Օl'XȖ{>Ißm}Á|?O:.]JBB;v?yD]2پqL!kt9c2io M6.hZ(瑁jnYz-?m5cK+Ȍ0zB{+12)__OL,6#o *B0DQuq{{=6ͯa5;un^ĖX#xhtIg]ԵxKKyL;R%kWvk?.t')y㙴`19cPH W_}5୷78d8;nj{&/yi4@m,gWSpbPtmв|g޻:z޴OFJzعf%N^ĤS3uuO9P]^SxIN8f͚E0'<$AqV.\ F>>htr\J=D oHބlvo9%lٮJ/46Z'6r|n))'MG|Rn.0~ *B?RZED|v~/T&]WnUԮ+6թJ3$߾N 9.WET!B!0[6|'Εfn3bOqZCⓒz=VLl\>eX=;سgK.z~_X j >}"Vf|>q!g Ɯp zȢt9J )?bi&/=\֬Ys=wHzK⪇^ );?+o'^Go?`u-q]ɬ3??&aTn 8ۀPMm.8ZOT\"pS9-aǡb4xw))NHhw黡@ \ BAK9ѡ^ :UTtXgxܡh+z}W^{v垽:ٍgOgefQXXHLb* bSMN~Ilrzx{lr1&aK5y&iDť_@\ZvxBr¯;yϿ9> FͿQ't~,p~'Nd޼yzmxB!B՜kLݡ'5M*v; cptt7o Zpyc'}TvO3JKKkFzaZcD[>/99I]oꔮgo:_m7#>?Mz!W5):e+JhĖArn(w~ic&qɽO`K@ٗ3~l_~?{0ElƅjIwwrzכµxo뇯s굿 );m4V`N ܶm=ؠ7ƅpN/|@{C-we |Q PWz,-@vgD޾2F>n O\?H3)Zu}]p#"ϜhbKJi2I =دp{[ތ\x:g~U헩n>uR>[_my?Pݮ/v~b vN{{;?HO__|1Ee["15}>aǦ]ej_ڟ_ f0zB%0C _xoXћ8Lz#_nn.EEEh?hs.`t懾xJ›u9NHp fAN'׿BT0 O{}+**ϧ)TgիWN}#B!Bqd u{ikiOU+qv(=3ܱ,ڄi *JwzW!ŘcYl=E-W. ܃3-axǃN^7ɘNXU}#F9ѱCt|STtL TcV[|7R-+j7M%\}jªcZԁر=RN9aic `LA-uX)]۲b'gZ.{Ymw/LQ?O{M>ny$f5uz{FSЩnmA{^N$e?k~eo9_|<)))\venÁ{?8٧d;g%eÚ-n G5{-{񺜘cI7;6aKւɌvSYYr;љ0Dš(Y硷HuQ" Lj'B!8Z G1P0mNG+OFE0s1h xbblC5Vھk䍙Bo|8#H*{/T+&~Dz}X~Փ?|>SN x..rFJ8t=đ@|FQ\68o#Eo2;k\"hmt'~ȤNK\zmU4W ՑfΜ9޽[oJ>4QZک*ᔒLB G]Umv `M4-kG'@vv6ehjj6WAT rqބB!]Έo\(BCW 㳗_t?궷Gz:B#ngժU#= qj,߅-5ÎmQ IDATJ-`0h6*zJ[6N7Gd؞SS=:OJJ\}un//#5+edII ewo.h eR\\IUUqqь9׬ B!B!BaيFzB!b;VB+f]}Yb>׮jf#_|Soh@@CQTUJeٹc-}_zoDUu~{5=|Z4-UWʲeqhZ0\MxB!B!(h(B q8NF'ī?@vv6 GOxK[5 imm PY`@QtCȘwի^ {7n7e[-9EQ7o U|fVs8gXLlQx<>EuخބB!B!8 }6[?|`PnB!'>*ވh2=77"&\   vQxï#>~+==-[Bu%Yb1b99lRFyyfsϝ twkM7l# !B!B!FLB̄4;m ow:DEEu7";n7TWWwTnGg4cCg4Hu^c'M8S;_K[g@@㫯v>RRNJZZ$B!BᙈcQ{䞕33<JKKGx&B!} >v4 M">0@vv6%%%;ki5z!CTz鬳f;`2j5qkRo__ [kN|Fעr3Rs >6 !BqvOxɐB!NgK-Yso5E\jj*@n5bwXk+PU`08ln&B!B!B!Flt [n˅f[nO/C}}xƈHM!B!BdfBq9~ 8A('x^Otw^{th4 B!B!B!5{W ˣ3րs>gPQQAssꨓB!B!B!)ĶqYY6<4Lyyף*` C| :v^o/ ~ F<ز%J%  #t"(,̦݅K(AG# c0{f3/7 [ZZSL!-- FC[[ &;;{II v={̙3Q`0ݻ@((( //lTUU@~~~ldرH$jRVV{LSSӰY^^ᠷqdee @CCC\Y2V+.K6!B!B!g8^Gg<"۫1#y!\./^ :ʴiE!V @iVp$>@@CAA@QOccz^K^^}0&j'YN=M j-;;x??aÇ~Feee|;/zҥ+ cu2~a0hnn=8SN宻bƌ +++yǹkk-ફk_Z֭[7d_ӧO_ٳ~-ZNj/[oWΜ9?@Q~?<B!B!B!ryv(ki4 ~`0D0f3#XXyՄmh4J}oopXuB[[7V)־`0`ڨޮzկb2Eccc̞=;nŋ l'O__ g'K.{=sLN']]]  >.)NKjEEE=H(@Oc%C7 Cf\;{8b6mO$2uj~"hIEQeh44(Jݚ5bc+߹_8@$$1󒌸ۂ ذaPhX\uU{@tVʕ+TJKKq8 G.^8v+++"jkkOFǷmz=l2>#:::X,ddd0mڴX_O>$w̙3xꩧ+퍏Ɩfݻɓ'hXp!˗/O8o}[\tE|>?}vh43uԤN:~`6o˺ul'B!B!EgNaU>mq]r/?=ɣ#}]2_Uޒ^ܸ+琝_ kIkТᲳSxttA" \sռ@=R? ϜsxOi\}\~>O $`…3ؿz{))v̄Oz9dq ],v 7p=&݁錥oD"|g۶mqu<7kΝ|GTTTh".]ьlضmbe. EUUUl[ss3q}~25f/Hx3gN,ƣ>odgt<c~wx7l'B!B!E3ŚS ӹo-zE!%Gl\5ۆH1"F/!~1=t#>_/m( յb2Xj޺lfE~OFF & vHIyffs'3Ӂa0.ZƂ{reX\}ձ˗/tJQQ%%%^u tD-=== 0 1۟Ve7nPVVƔ)S7kn / 7MN M=⾄B!8U?QZ!FV#B!)g>ݸ%c楱۞{P\rGqںoebZm\NCkk7^Ovv*%%!>@ fB ()e:,O_.@ Lkk#{TD"jH߾~Ƃk~Nxy~=]vZn7۷os%77r*++hBUS[oioo͞= 5kh@ EQXhcfs,PzO(%7̥^ @uu5ppp:A7U>/ 1V}n""?EA |VZM3ϼVMKj++nᄏ_?=Etuu~C &YCCð*’%KZf}իWs3Ylwq<<TVV駟G%\n$` 6j*n~X.e;w.fi)|I|>߈B!TVR>BN^{wnV[Gj:]]9 D0Mb4YX,X|V{ ~?>,VY9ddPTR M/!⬤(8gc˛Lg{4g%M!jѢsd.~@[gqUUMtbCp\Ds|5~4n# l#pB>W\FGoO;:!]o̎F\/4gbN/@gup%nJ[S !p瘱a˛`&vǐ9kn9Z סAیE'LQaBbCiZLS '`.B{ xZX'HH@G}5Û̡1M+C$ų@ЙsQ4:g j$B"S2^\БK0(hgg%jP= Hx<l6Zafw76IKKcڴiܹscҦ^+W!" W )cx˿nϽ%Ҿk5j)5uMXg?ƒ]BWf)e_g9\ΗqcRt} g+:ޮR 78J.wn&+e_β׾kֿ/Sz>%=`ZvUк]j? Ac48&0d/TU" a@Kށǣ1Ƚ/wӵf &>Ơ'+ȸz1@)J[JN(zk3EW4o0d=7g^.LE8!+ _CηkНH ɓ'0uԤo&^RLdxٴi6mB2o<|AGx^{l K.{}coMMMB!t:N2q:<|;! ?!B!NE%S8TUI8g]{"vmtK35<>֧3GQvގ0Lr.:ɐ즷2atdi|muh,KwV?GglHh,);6 FgNWљC1N` Uz jw^[њl.qu]5˞?I_ Zpʍ=]&(=gLDowK(k@J9X=Ww>'tEEoKŜYёFg`??T_[4:ÀMkn/,)>l"Ĝx6Y])/?I n4 Y__ @϶lC8!hߛt|n~t)6Ki![T`.<1ԋǔ(z߹ ܻ*q>d1w& nj7V Ч9Pt:|{ѥ:M+Ck1КM^8 ޹EØ>Ձ!3 cnt)6"- r{dY&=PUUgΝs9{?O?4?FqakvwB!BnJʧYeHό>4!= /#+ϋ{]:Nftp)J Nn:ѠWmW9c{O,LvQU_rl/W`LU? ~R :͚Sگ/tjK?"UoO9sF9om'O ;x>:Li|'RjS\p#c4]2Gk+vvӹbCt_f~쳧r~tH㲷uMCTon=KxƖ FmZ8Vڒ N.7>(F"X7N>'/~O~Μ9_WXZZZL8{9b3:͛rJ***hzM6sM7ŭs֧O>fqw&͛ٽ{wV_i&@3eʔXP*xGUU{dO]}\{mtJETU^{/\wu̙35kP[[KOOiiiTTTP[[˛o9XVZou]C=Duu5#>>!B!ƛ՞Bvn4KE1t:= B4N|@Q2_;7r(,@z\]UW4pZ,V;]+h;zLc٨jm!r eO}ߏN65n}:ɐfL͍͢6%^5!s3`)9;-N.)i:&aSt(NYg`i&B^52n B~mҧE'=tY/اu^ Tz-fz=`@o`,q$}j0qhO}JFԅѵn/m}68?tϞ~{5%#؊h5Yپc x]/X& NO  eRF߲e < wNUW]կwݵjժ]v-7hjJ@ ?/u 6 YVn=O[~ӹ+K/4Tyy9ܹs|l233 ϤI`0O#$ r !Bq:qxt֚fKzzZma܋(,L#ڃl^YµX"k?}jsѥF]A4 Yه47zܧ:&)~߈dgl.ru"  IDAT5ZWRbI~Foq5YQ#uGtIƜ0:sr:!ޮf9XsNh`OXWkb85y"@oKÒU4>8R׆EgmZFjGpDIv>0`o<(hMc6h$ ig)2C=l[IShZ"n׀h4QguVկr+SUrJ0_|ql͚5)--l63gV^=ZCNڵE:uj܇P(Į]Xlv>=}_hɾ[(bٲe^}k̚5 k ٶm+WLjLPyu.*i?}gg|  ''{mEoG{hv[Nrd z[8б{4gw1Sjdξh1Xs(}=mSs0Obw&F9u,;n9~fh@=ޝ=>dͳ 0NN{d{^>{ZXS:/sXJ P#w`{f%+őV}װmގ]YyF5qlOO/կ~EFF)))nT~Lχ~߶W_x<||@4jAOOq_OZ//&5^z^ziZ|Iz=X,:;;7^{^{mZZZۓB!rtz=pCUtގ+9qDOI4J+Qg;`__Vs/X3-fBD2'( s\LR>_)P_{$u\'"r=pO_Ȝ{hrt+3W=0{nQHzQtZIA$9rLyc6Ѣ3 ;0rMFSzyЙe'Ff>FKkI|!L ``yb3& َy{=iJxSAtMAj|cr| vx3B=Ib(c۬*{?& 8B!P\Mp@ ~at]OȆ#GSMUt;VUnJ*jɞP@}mS)T,=سsjbS>eE%șPKf=coHt-&5lpMMr,}*yGurD G)(a}_[ZKv >b…Ƃn>-ܯNJ"- JfNlB~Vw3}mG"]q&{\CߧzzWWN*fϿˍ1/Cftv^Od}>BnogMWwb*!_syέW0o튥p׹[cFB!BSDvn>6kl|xzwpV?]y`4ikix==l۴z.Xxt&8[9)D% u"#׉_[j8%tWnyrBayNt;|=D~4zAR鵃CO'E!NW?F AI> =*r3gv{Z9Žk`Njj1*24l3*h}gE x! I?#7!B!-U0m֜~ ̂%WљkM bu~TUEQ ǬH$F[;.5-G*Ulp5LȟHIJ3666Z0r\ D N!%Ehٳ;'ёGR)Q9jOQ5Oc%&X`Ox$0]D9½^܇c˛DY4oZ>.} cxkZNzKh`5yuDu؏c9ئu?Sؐutm@_&ǧFߟdg?R!B!N"ݑG!3{B?V{ 6|$'q'j ڎ@;O ~Q_:%I.#׉H\'–?E#  `,jl[Z*cjN AH0ڏޖ^0 sFaC Iibw5ۄI dξ5sa\@XrJƭbZ@;ݱ:Wo@kyWNv"ğɍñ1&B!8ز;7X^X\AIT>/VW[*lOؗ=+;vmu;lD"goՅ5Ġ:ɐDZkAUh̼"җRZm>H{-8,9_[]LoMƔ]`8Kv19R2ڢBƌK(!m]IzrR+ȴF Fg6~Z}@[1:(ooN^@h64?_NֹWa,dOQֳt+W4ZҦ^D۩|o4-CsȺR,eE:6:ٺgYdѳ}Ƚַ>&r$ѐ2g]CӫkQ?_mC,e,ߡh7}sM?HM!Bqz_tTD"tw n̙foqY4Ro-v""ZGՒWP @g{9Bndu"!zO_&]O]^C^mc2O_Qr.=ctdZqVK?;f̙]~_?-/'c33ꭨ 0S@w+'5֭=zT|ZjiCoq`,"a//( r/?e)5w^BnLy3h`~ Du^46_hoG`QSLSYLOl^FoĚS]+2wT-nL-(FڴuroזeB=ueJs/rn/_LkDk1uZ- u0MK݌ =k.>?gTrR% =HMzq4zڔ#i r_ ר]]$&B!8%yC0ݝ8R9ŬČ/`bFƾۈD"q{~UW2tSquwR[?dܹX7>u?B!NW -cg 5Q茘rqVCgSzãDt]=c4Vw(曘ȘqI]_ݰ5&w-證gjs]~/Zt)Ą%=H(L$b欉&FB5Zta1beaߡa2"@`Oǔ-kc!ѝd̼cjN\?ޖ7Iֿi!GI3PG΁KaZ8S)ރT>}u8ßue_Î0n?OG=֧hZӜ/]>I׼ӵv+AEL>k2Z)a?qP: Gfjd.qby|R!BPzzt-=#_eg0m7,|y΢ˮ`4 ikm"nwHڃ|ӄ5Z-]AFV릫p8l!-# FڶvBjޫ`E<q*+.L>rѦ2aJ\͎ý)X0:ZjR2P gXsљlxF'42Չ%HӍp 5T̙hF6cpK S L"^muCΚ>Ru3ސ8zEŘ>#@K;O^ QER6FWo ;S-D|'g=ǾL'D~NIxhq4u:}ʌ7!B!Hês瓑KVN^p`*w>fŻTLl V+WM ٹS{!t,=KN tW kI]t;oK ޖkmD$Kϡ8.z=O$HЏ oSո1\+/3Ŏ]xm}Fףh4s[ 74oh _3hA˅+xB!B;bw1-h5^=ݝIǦ*ྜྷXm)-Vt:=~<='\!lc/΄ Lw!J~τ80_ gA<l@9b1~MxB!Bqkv҈B1L8J%]4oZ?+KQd=zҦ,wrӘ&yrV.; 1\j0g_5%{}4Zq=NݟI!6xB!B!B\h߹K[}ppEAgNA]ڼ%%uki#q_a*ȡD ~sDL%7!B!B!Y@Fgzf_5ъhFqe{x7~Fo!똧ވby7YZxb?=M b5cv:=Vv{ӽqt8!Dr$&B!B!8K\Ah f4:HHU RsB{7O1aؠLnyR?m4FbBQ""_fh!B!8,hZ@@j'#wvq8FӹuuuHĩLq"pH51_#UCuol۱AzQTX8SŕR,Q @5gS_og $&B!5xIo"PɐDN sf2R[8}I57%''g !b &StQd!ęGUU~?cKOOvzB Z&^Y4#by$T&׉BSD""H)))9\qDs['ڧnT{B!Ia0Ζg8EQ0͘L&efB!BqӌB1|CnBEEp0B!B1 !!4CBd{/B!B$դBqnB}^i6?q8Ejy$T6ɁeB E!??.z{=!:EfѸHX%j"*ʤn[+2i=Ynn#ӿr& F! ~wtiE_ѹY~N3xB!B!BմZ fR\lNo" N嗟O ΰۿù?OggxI=BOy}!@ L)"svy`wEU^C, ` "SaU_NzZ}'pG)x+--Vˁ8xx*++CR]]M $%;;I[[I1 ЀZqۼ^/uuuG!B!B-C@zzJ2G}}ad[.t˗?=9V= 3frqg| `vP17VQZB  FFݽ2KCX>6plS6p8xᇙ;wng|4?1)))ןr-\uU,]W^y%6YYYGO=֭KFQF#ucaB!B!8-Y2}E RZ:L{gn[`2]Q=C~(:׶ըEgjP4 ׾|j65c莤ѧo Xژ;eo=se˖-|GvعsxMN>ÇQ rww7nƥ^:B!EQ)NF ,C((B$9!"yEQXn}68zߐAssv ַn&77w'lᜱl axlfؿUUc[f>_/p8x5RR,Bz4 >>ǖ-ѵRR Ca迻V0v~/PNC8!$܏Λv14xߨ>d-??ƓO>I(tzQ Z6LZķ|ܹu] R裏b?IMq1 \xqB---xmƬYXv-~_ٳ=_L{{;۷ǧ̜9,N'vvEWWW^VVӦMtlݺu}Dww} t2c 0 tuu`jժA?\tEtuucǎAp`Xb#.gJmmm›o g2O>Z-k֬IX~cپ};'|]knnfݱ}XOO6mp69sPYYI}}=eee :>n7&UWfg%P[ `|L 1[lBAښ9o'MXmO#7Ŋ(4Sg;=A !ee9r|B~D8tL5w&h4֬=tequtv)/#Qپ=ی{sN& ryQh(-СVM+" jUt2UiVS,OH @ Ѩ^^%//> j5 ,Ikk뉞Nĉh@Inv{907pxg̝s9|_?YfӝB+ĭޚ, { _{h%Lɭo1hof׮]qs9/8 >ՊVEUU^|Em@AAg޽PXp饗rףbݻwt~Ac߇zq7!oW\q999 ://7X^yٓtc(ӷј0vWr׳zjVX>oÆ qc1q:zO3gre%{z %S1-wau3M'`H$h"'̾"t:}=d`by%|vkFvpB!HJJчͺK!6^CvEĕi4 ~`0D0f3#XXyD B$9&d_ᰊ&==nVS}_y_'Y`033jeʔ)> aÆR3˒%KxGQUիWꫯrA?IM!石l2f3\wu\}՘L&^{~m4 seݔ3gΜ'#QT\[y IDATT׿uxbA;EQHOOgڴi۷_W_}'3f3999se#9^EQx>}:[nޣEQeɒ%̟?< S$_ζm R&@tdܹ| _x'2?q3g䪫{gi6P;__YlYs]7UU;yꩧ|Cywo;n2{:r+ObX FnZMg{)IHaqweph4Zٹu=nW4kOq2k΅gds%{wg !bp2tDL&N殄3F=Y--'3^i**j44cGfe3iR>&N7۷W ZM:} iiv{F7<?==CN4'DUUZ[ٵ&6/='cA0iRL<?o_]lQ2L&3gJooCZػnXvԸm>_`h^k9HMc4?ry㮫Lsj5uo_==ޤTP[^i~"SPUh44(Jݚ5bc+߹_އD!S577bQ-.jO0}t}u 7p=ώYn͚5,_wydmBX@={_ʕ+iiO1m4v;+W$ 2c RRRp\2 x饗hkkmWU6>ӄ@RD}ݬ[x.;wRYY9h?cqeuuu,]rwrm%qFf̘wO?M0xbD"I@믿fcL8{ksz466v;?~@mqς [ؼy3K.M%g8hYr%/[naҥCpϳ8qӍdy&<V}׸u݂{e>|;%@JzH$@MTT&KZu]]E]u-UeuEvm*P!t!zzOf&3 3:!s]^9ϙ3gڹϹ#]̼y{-n}?Ynb֬,n[|#O˟pd_3ik78KwӍ44Wc'ٴi.Wy^)~r_->9Z|'|۷[ѣ^_Ǔɓ'͏[o% krisIIIȑ#3]]]99޽{B //{6Q4 ,"B]CC7n;{VPSSáCpvvO>yBt`!VII K,gʔ)vuqqa񔖖LTzEjj*wﶸ˗3sLnk SIȒݻiӦ:׏iӦ~>Vկٳ~";;ZK^tȘ ,^޾t =-n-NPHW:ǏBn"]H?-!>Dxx sg BO[8v Dzz,aa ٶ- ٻ>}Aӧ).hy >0A\f.\) Ə̙3qrr2ާgرJ/<`Izz:SLwޱJg%frgIIIlٲNj;,vY9r$|ClǏCBBNѽ{w/oի67۶mo߾5{rqOLL :Q?^?='))Yfqa}]Csyvgϧ~J||<ӦM_Ir# 1ȉcK35u5=;W=9ATHA^FCCvI?-!D(JT*d5%&J% e+Z[Q /|O?][_䓓{ϿǃCN?/d>}>}"4i(KZolԳ~^s 7V Dk Yf]~ ?lA`ɒ y=?>p̛~k9ܣG{3`z2y0V=B6m$~n9ܒ-y|ŭ\\ӟ0dH2$fx*P}׬yR_GrkmwQR;w*!!L2n__Eii/0 oo7eBݍj[n|rz) TC3aYhr0aR^nxРACT̬Yի?4g+x'OQg}^xۘ1cxAUSSñcLj3E1Ŝ:up?\@\\۷o'22={nN۷*h9sX۹s'oQQQL>"H5|pbbb(,,yXii)ٳylqSN( U2ydZ-O󞞞<6됝;cq[[{/je˖9i_;ȑ#lܸaÆm3Ázxxy8%j'BFШqI @c1V  [X}0`ܣD.Yg4\V;<@{k7* ~"'T}1:]#55MMSVf;w7??/bb8|;SNN!EDD1|x&!ԌFPUUXhGe^>嶤mLuLM3~| Wu}ՙj~r^>jFa37o*6j(̙CYYO=E#mڴロt[VV 7RXXȄ <믿n7IKuG<|$&&rwi{VTWq.^ FmM3*S]m )3_»Erd`#B?˕&_zc|VVIo -H?7D޳MF4p5ԊJJ* &2 ?f֬1 ļy[5zR9JZqQ޽j[QÑm^)4_oc˖CTUKJJ4%%;g?}>ٺL }#䫯X{n9&n M~#g_ 9G}RΝۢ G1RRRPThضm'Ox`0yHvGػw/www(+3M8p۬[n͖eQ(fBGhh(q ܹs^kZ2LMM5<7Z" WWWvZ֮]Khh(ӦM#**S{B9sn-L+.OxUq7'-Zl2bbb1c/sNg&4??ݻw?-z4=zhVmqƒ%K#<¼ylzlh4|`VW\j'0r,NNOtsr&2:>Ndjkps{TݣE]m ]g #2'r]r/ AXnJk^aܫǻ#i6"!Dܨ(-9%.~dry5`;1lߞcod .ph-prRc|O8}m%8MgN=ILAbbz=YfUU'7vrRi0﬎|,޾֑madA=3}H́ӊ'??\T*[{9|M^}U==j559qO>lڴ2{2i.ڋ/殻O>iE3 lݺ=zaxkll4B+%%;3{69m?ۼy3>>>vm,XLzcڵ=&stɉF-5[x`voDaYMuE4HTl"Y ܳn˛!n%7g?e85QqTKO׆AJYâYODKH?*W⭭. (dd췘Zysf̚5Ad233,RL)LRGNNNxzzvHJ" cT** Ngi4 Cu3c5&ڑ7+Wd͚5V;99k_|`NGddŊKVOJwlF???e: ڣr呐@||<9996SvmTTT?` jWϞ$L|ЮItq4Tj'4/="v㹇MDR5S'-(cO?6`^>~fܙN?J/ު*+p(81嫧]]]qqqJ(RWT4v)'7'Fe/ٹeiVm4]?4ZI֬MbbLB@0 ]umKk۶qt8ݹޝB >>{3vl&^^n<8 | ;.n$afΜɐ!Ć7{i&M;/֚gSRRB@@=ztN"66HnjSSNddÍ/Ow%ҿ ;wyVeűcܹ3rH֮]kq3wqٳ|/6zIhh(쐠Jb̘1r-۷E˗?{w>jEf" >U+>S>}:*UgG* t?~7l>|87ond{-[7+WN&٢Xh8\8 IDATO>$'NlWY±BeL's83up̹N*F"\]3?tPW+3xz1iilscXk荫YYTWpss͙4|P]]uM l:BC1}Hk]p%))ɜfҤлwoRRRÔZwybccѣGS*Đkuرc^o:z( `ɭ@J%j\F_|Am~;ŠnL4f݌657B녇{ѣMܵkiii'~)u]$$$p!*++ "++ ___}]<#qAΞ=KYYt֍ X|,..f۶mvwvv&44|||X~=Z>,\S2g:D~~>555t֍D\]]Yb䟖磏>w]>3ܸT*HHH 00%K(N>dĉV7n7oތFisE裏2zh***Xn]Ş|)((`ժUM»G`0Inu'nd 34 K{E؈Jnr8#t90h\\\42e\#DƷ:sS=B9qȵЍ}֏^mr80 2i&Ms2$}prRնu6HeeUAk^^nxW%ڕĉCgPNh;k3:MG:UDrrO j*?"#5oeU+* VGddrr J4kb*ƆS@Hʹ|m_|شi <#T*)$7l@޽3gU*K0x`nv6nh+,,&P(x;w.Ҹ;ill?8gdffٳ_ ӦMF㕕TWWIVVͽKV^ 駟W_H=5n8FAEEK.8?&33T>.\h?LXXVi9!W4j5۷ooCQ__Ojj*˗/Z^~eƏOzz: 4nZcǎ1|=>sݻ75 ;v`ժUXHNN&99frrrlLkZ*++9z(6ln;vpq.ILL49''/9r72lذj8cGII ;vQe߾}9rc1IյQNNm?hZ~mq&L@eet-ёϟ=W&99Y ]Ej6BIPHW)U*gO[fuhllOo0Л c3:'ӏ :&1ݻ4Yκu$$tcРDWڵ80ٳ ]htlܸ]Ժu{2$//w&N’%'5Jmy]VSc#;z2cM/ZZUUzd+}#zi,F=& F+ilԣP(PtֿjAR{eTa[;U*u.߷PV̋VnUʹ+no٥ TaܸqӦMcԩ^'>>@4 ...){.'E{yO?oe/(ȇHzz_ʪGxNȳ.d۶{?:_z|RV>F!Vkر~JС}GRP~fNNj>Y<=)$>> 7y=K&m5iPfͺ%K嗛-S(OLDVqI~,gTz8z,ڢ.]6m׭\g3th2̙!!~s:ǎԞ65*sР;=`0.^F\Y eo߿>}}v7ݱcYYY qvO?C=DV\ɺuR0]Gy*,,t/ĉw}0p@Fjj*SLy%Kj~ 0z﷌ٳ̙3$ʽvpÊ+ ""PUU_|@CCy۷ogʕ?S\\-V !TTT4%ҨV?NqU[@W}^:-Ia1"zDO/jSS6zDœJ][7^A`h '%:};˯~5HE>Q( ?|{o|0>\ 儇ơC'אc W^?^y~Ξ-gw& W4Y'VǦM;6xc>}4ט0jTc?ٞ+̙71e0d~;vZ|=;}n=}m?N"997)9ttz~}JKeLFtUxlSQQ kZƙQPP@]]x{{ӥKN>Y^^^TWW7YWZMxx8:uU?]]]֭vsvvݝg7e[PPy5/ͲMqW !:x1'ښj|la7/bKfņw'mpJ%q&R(t AL>PYQMk֮x7֮d7'o` =-}'X+;߿Bo={2geMkd͚]̟w3w17/fμѣSqqܫĉ|OZEcɑƠФI/L-w6{{ٽʼnqyYWTT_o~za#ۤ- Çe„Ņ[)X[[Ϻu{V{2lXNAtez 6eɒn& x DEYU]]ǚ5Y+)}ͤ=m+[nk@ףju<<.\(oWX&vxז[KHMq7& u$0ŐQ⊾sh4 xyglS'صu2||OgJZCу7J 쀊6tv?ۍku Fmw^?;CEEMquu&<<@4-'O^hrZk}Q(;WJaaQʸǃ ). ?|~!!~BYYyyg$ $<<RIQQH&"nK &uORaֽRpssshB!G]Nu Ν:- ͖T9: y9˱Oݽٲ>_:vѼ'7荮 wfr)]u9ws.~^ZZKiLmy<&oq֨ђwq!W׶VTSQqq_ՙ&e Jdd$˖-3K/uVO !U`0XB\d/!B!'7qUc˖-Vޞ#GX;y9ZP__/zwXْM!B!p Ve2JJJxR֥+zUQQza0BB!B2P ! ggg|||$'u`0P__OEEM!B!pY&B\4 EEE] !B!B!/]!B!B!B!xB!B!B!$&B!B!B!HM!B!B!BPwcΞ=8qB! .*!B!Y&B!B!B!HM!B!B!BB!B!B! 7!B!B!B!@oB!B!B!B8ބB!B!B!p !B!B!BxB!B!B!ԝ]!B!xxxVQ(r=Nj(..C'B!\nV>>deeҳg||Q*44h#_׸V,v-ˋcu{qq סh>x?A=zO>Yչ!|^bb:u"z^zӤ|?OOO[vvuHOO[PSSPWWUT #""??+z]BNNNxyyѥK(,,W>B!B+ yxxo[6>TWWVEHH0c ť׹\DD8>nnl0nBBtx*Ң(Jk3k``!!wvU* ///jjju->W^:uz5sqq!$$^UiVk>D 77ή !!}dyw -ܮB:j jk0 ƕ$S!'$$~~~ٓ\Ο?UB!Bt6'+,V_7'/8*U_݁+|JrsS__ww+Z]yy>GÇWkvveg:*׌`~Iz?:su*5&MJ3̖-8pcj/ۦ2wVzN>CNQ6nr\TKJJ"""$///RSS),,]!B!]7Gڵ+]5]\\`ʯرcz׎U*\st:7G^Zi5hzFmm-+WUWWS__1N+DDCӕr]GשBJJO+xzzMCCCgWG!B xJy{{7>q"s+#u2u*9;;3v|Զ/͛vT՚w02vØ2W466vZ݄Ņ.dKΎ;$&B!uBo6$$1z۵Z-on K1'ҧOoFFxxWj'.\O?maӦL'33 g{M6*U֐{M ;,ʥRS2hΞ=ϊpͣ&'(yR_CB~3bPPTWpH.f]vaĈDNn&s[<<<;v IPWWǑ#|շ\PyodfICC?khh`:tz&N{lֶJb{pvvblݺT*xسg6]\yw5N\A7///RRRغU&!Bq= >>>ZޚH :IgO׮]4.ûx2sl^הr?laVYYEll `O6[Xb-avB\\,UMRdƟ,nWT0{=469<1lbes|ذ!A暚.\(" 3ѭ[8znz쎇ast9s,UUUu%%zIafۧ5n`#00sAY//Lt yK1[PP 7;wjIJJ$>> -23ә>}* *QУGw[oj2RS09{8^^^Cff@ XۚK5ש|gg'd]":: oo/v\?Jeu{^ )S~ŠAYg|cIH_B!?Y~B --馑tڅ94O-tEXtCreation TimeFri 05 Jan 2024 06:08:46 AM CSTY IDATxy|ս~3ɾ!!P{ګ֮ުm{w[mn]բW[E@M5Ⱦe1̐$3!Ay^$<>{˺up8Ȳ̏Nzc޼y\TkMMMW}%pu!IA_VգG2k֬v{AXf F1(ͩSxq\G#7MrrrB+cǎ hη-DJ`Fo~ )y^(x^~ngժU,Y-[~tyyy|_GydDZJJJ{ldڴiw}\uu5III$$$~;}F÷-8:ڵk9tP:t233mll/ |_(?=Fn6Bcǎ='G8R|PSSCrr2&)(yWXbs ny8rHь=Db͚5!%B\\\gYti@y8N}vo~pÆ l޼9,4O=_xIII6 }4@q뭷2}<>}:d^f;_̙-B__oǿ哇,,];w~Z[[INNK.뮻><=CVVmmm_Fz=X"lTz{{YnX,Ҹ[oGjHOOlذV[sss1<<̓O>}ǕW^Igg'{hݵk_~9EEEƠw*TUUw^vM[[jr/_NYY/wooo[RWWGoo/,UW]EJJ r ?8O}uDyy9˖-c kމߣ>ʴiӸihhW_Qy.\n믿NMM }}}F l999\8^}U>Zd&>6ڱg2u4$j*^~qN8-ҪE'+>իYr%#%%tZZZi| eM4N8 /W^y^̙3YhoϏv,F<7p8HJJ7,XOyI[[y͂BXV.--ePnSF#''ݻwsΈ}}$cY U7IIIA#k2MWWWȎ444 IҘh?>:{t&NN΄&xn25zѣG`1RPrGhp"im}ؽ{7nR@k3PT۷/d[,qTWWyb 9~twwgΜ"A.r|q)_'%%t,•>_ch INN-#2v iݵkn;n~ vC2h xݬ{p?C1]Fx7TUUq뭷bZyg.^ t8inn&77dyQ/>?~SG2sL/qQ;C./#={/GԐG^lvv6III,̚5]w|V Ie9{dh4(RD<'O?o#Gp嗓σ>ȁ8~8uuu!Wnf3a;x`HK|8|u83җTRKr_η-D`if. T< K)yA4u-Guu5̜93`=$|,z{{1c `41Lj!,YdRd/zz{{;رcԄGA`ɒ%cXp8Bӡ(J4~ddOXMMIIC}^MAAA4CCC sNϟOee%ׯ?7''D?#z2RfZRfq}v;_|ʳf㩧ӧ@򗛛rdb0j8 2\[v-̝;rrև~Ⱦ}&e`0ݻ7U!tw^-ZDee F|j?Qj5 .d֬YdddDl_'?)9+VZcc#>,W\q,]K244ġCxwJLLk _sssT DVfB>%V.m&hDΝ;)--ү, iiiT7AdܹNkT!>%g+Xv|I*fΜ?>N;R˅c˖-Q&֦bH++L;rHKK q1UhOW뮻"w,lyꩧONbFoiMeZ-o[oŌ3(//\rss>}zRTڵk'Dݹsg=b4׿NBBl޼FlYf:i >Kn!8y$'O$))*d̞=~e~:l^S3hJ&-Dė[Z o&>6h&qyi|)o2E[;bп! Q'FE.ʰX,رl̞={LXLEvmN^x^{5*++)++2xWgz!'S1VFSo}z5|ƶm7zΝ;Yz5UUU9sI``` βnݺ1 Q&hyd?T}H7l **b ᠯDzzw 9x (2w\֬YCEE۶mHa0D#o[[uuu撙IOOO/ԼyHHH` A#Ajj*IIIQɦMشifoո4zti9ėFE_ "nh]>|=.]JEE>gto߾\G W"Z$IA#bhh۷}vj5Vb\{ڵ ˅!9VFSo}emmm"IRPxػw/Vb֬YkL6 ^֭[#F[ON'$&&b٢9 "ŧ<;~ vannn HIIbD pgD#7D@⊨nv"2 _:%>4WVV2k,$IT3xcԂwGD4On|"8 6+c+WQϫl|\[w=8pIj-Sx7vw)囤_y)m̙l|bChƫ>Ksee%)))dffrСI]ISgΜ84HZ.@{8ptvvreyE)9|aŜN?xcD5>ݡ >:';ZWUt7BE DOEE j.>tOJu*оc#ILL䦛n`Æ dPŐN꾏rʠ a7v6mZ@ ~+V'$V6+HСC<0LTc٨dΜ9nܹshYYٸʸ: Jm݆Ve߾}AK#km۶aوo ڜ'B`Vۅ {ppЯoԂj W}y%% e!1RI}Veܹnjjjf亀97 E'551{ Z*_ltww#FGɊ+F4 EEE!4dYp\2g/| AIjj*- yG}DSSZz4M RVVӧZɈ_FNN7xcȃcB}o=.F*<_?E!v/xPT9r$(nL9x NSSS&==Okk+7o檫bTTTwN0 {ク\.b>}:'N A{I/#a%R6jN|">9s/~3gގl&''' -SwIQQf͢k??塇ĉXVRSŠhB} HJJ V_~9;(//gсhg7oկR]]Mss3$iӦ/~JCC qPLt9r oANNfo|466Nrr2YYYav/|cǎIII~>vF8FSO;|W馛7o%%%TWWىF!55b~_yIːe*6lE;EJii_y~g-Cq*/s̡i?7n g_;$))y100Wy'馛HKK[zzzXnV;#d6mk촭'x N7o̙3 vn:}v,ˤ* +Ν;q'r^ٟg>ϓ_eڵXVj>[Vv믿҇?뮻r̙@'ر6V^Mzzz­?~.1 PVVO}}=o}|ܛ6mth"f̘nЎh }fwmF$"isxkg .OK;_֭[ba瓟ȑ#lذ;ۜO=>|0zv=}0>ñollܹz(Nȑ#X餦_~9e{jmNNLq7l6~_rJ͛Gii)9ÅXΎ;1ce˖򏛉=Sk֬̿pGy+WRQQx8}2wŵ^j :J>'>K}}=_=+PÇAEuu5eeeO4]R[[˦M1{ @:L[8FˮhHII'Fqqq}o^Obb" !"? ?kcV1LzD ᘈYYY<tttldxF#nYf ,`Æ l޼YIIIn=\%su:݄7T*RRRp\E8L&.ODD8u:ܥ᳌_CGGGT"a"Ae~j<  X,z{{Ǭ{qrcq\AO&3gDQ]]=```†(őύ=],Lt='SΗ# x7\/|WwhP{#ňl'1bq[? ʱcKs:K/E?#PUUJfR#g>|'F1z3gseո\.v?PGG/¤Gň|v8O gΜa$&&"I'O۷_PVZ[[?T*'2r1޽{پ}-J===ݻ7bĈqpĈ#F1bĈqD#F1bĈ#F1bĈ#F1 @Lj#F1bĈ1:F1bĈ#F()1bC_ IDATĈ#F1bDAa.~A !)tD;GPtr pէet!HpRwm@2r0'&q9\Ȋ x4ZAm.jvBx< AגRf\N `3[>NK2'$ @g[ n$I"=+J9y 6v1"*Iw1>y&!Yc@T QFPuFC} 29$>ICU68ƟQV]lCcqY8EoDrPMAi]A<wG1q9tcTjUeqpϕѣI@R6lm8>TRPӑTZN;6CIA5q9q `mƌ;5$$AA4j8=JrF&cpJB<";pt0c((FWI%5pvb "(&A"a+q\QFDDY5<3|'ӧV蠩i 3ð\KLŘ릻62ޜZg@VkpZhag6,hLxnDr4T:= X:1˽mm9yj}TZ=N޶Fzǽ&bwp'U$BSp84֐ɜMLdj NPDn4DI40|6jL:ښbEE_O|<g[}#ZQ\9*vv:q SR $-FeyJw#e2yziǐif,&p67=t= Ҩ_(AiOWH*-Ia* 2-=tpg}ҠR6%qbF | Esdl9EˎWB%*+==};i߷a\撅}&`l< ;x\p*-dc^2\w:`*YZ#9H:m@:kC olUfNCT/z< g.c24-Df`7.8z饗={hii77@ew`H/wf֮&$da*nukX;ME&Oeb `BeLF.9~)5(8\aeU4gU6M(x<7 +a#id/jS284: Sr]4W[IE>(#J]rz}O1uNđ{՗Q'j^W! (sn# U=J1ԇq#g(Gu&o6S,+Dv7FK Z1J_C4\=֮F.ܫGA[;ز_95eKs$E7l/nGxzR4)i:\s<|FL/q^Wgf9"fZ_\qvURd teyo7[w۫mKڭנ/+"~~=)e!k pK>\!}-fr i$.]aZ.9x'r|Wt+!烢( G(П[}7 ?b_m\؇nghL$eTO۩l~'t5 9+{m7'e8@X:[yw?尣7'qӏ~6Dg)6<8(-_ƙHȥ9p{,`;O/] t \ˢۿۿe4Q-[FVVVDiwѣG,0p7hr( J,+)()nwtVR|f?Gcp?z|BJM_OP>3D3դg(*LĐɷSJ,7Ƨ%(=# 7Mq I mJa `nYwO0RM|LX2YFqaۀxӸIQhx2$y&1~m/Q_P4z_Jw3q;8&ͺ7v99򧇃9|$h}9K9lap٭/ܮI9$uiGlMi\l5%p62v D%7!itx>×0@cN#d!]G&`;ޭp o߶HRLLsy0d^i7]l㶎n,{c!Fҳu'xc2\SWE3wy'_ѳu 0L/dPuPP2I9:A e+#/ p!l#MdYF#b3M&fG%4^gTn8sFN CwSml>νʅx(eqe8mYQitdkWRn13\32 eIE{#?`2(--emjjرsn>@_,H"+Ȳ;r֏M&ߪlsÞDQ #\:JШFS<(t1It1\.'.V~M"yhCEDQb|z' (j}as)Czf.$19BRiuxZViTiq;zws$M=!u5@y|o3OITDWt.x7=qNe QQq3J 4h<֮ݨ2ϯ'pϘS>A(x-'m;H֡NV?6ehCaLI:!{ܮN%EAQkPԚK.YJáI#D~{=>Qg*~ZI"zAo_|8c`3'9q@C!c4ހ(*ے$:r.ť4֝snb 擎V x8jگD_ cLAöEgDR4bdBRk5q ބhp7IhUx%[hYFR4rDq93 zZЧc(_O-}d[ 4cT@>M=O{1I`ٶ6tRҙf!)C8,]chЧ?a3  ~׵lbtͲ.J  &zT܌>'C[)C%B#(K#eGҪdtj$ YI?Y9W>YYYj4 K,aɒ%X,n7+V/|E9pK]9QE0n"\qWkhLjwφɌ"080BsV(JAVk~ϯEfL9{I-NjaPLtɈϖ-JȒ$EnnlQ-uX$L q& P5ъA K9{[P"J*]K1epddq2-^yHRpD1Punc^D1>^_44IQO'{æ3z`_eJZ7fj7̘0xm 7хfG3`ep$ }Qk%6ClFRy: (5 +qIl.iyHa (b+#g}|駟_JfΜrssf߾}۷Çce D` /M(GJ^1Un'j!i\b3Q<'8J=Y 2`Ƨe5r )@zQZwH9> ( 7K.n| ǍmuAf^@Vn!94՟+0fΞOVN*EMÙjhzs Ibn;"}ϕ=nN$ElaAQ\mɤu+ACU_aDEC_E7q9i֔֋Թג} 89[__2L D QR0ӄӊ2  YgDp;v{.v5x`_ډ)Ђ~pTy&QFV>p*LTz3sV/o$'9{t2>L#>'go@%XvYMqVQ]i1DWn4o&ۏb^<Uj24>R&eDO__[ne֭ȲW,YO;!"]KC 4p_'m YϾ4@s tDYNGɣ7% 2ieQz^d&6^7ov:;ʎ H$!]WFx>Ρ=̜$I"+u5hZU? ŗx<Of \u9Ydqa#eEF QӀo=| z=@nkE$Xۅ6%Ycsfca7M2r @myѠʢ(_4a }(!Hrh`ћ&U6l~c)!q;c"]=sQ<+X?&V8e-n0Lo.:o7c7(}AQ"vC,F#Yo2PnU*on!urMH: DEKW,g؊xZpHC *? ![uK1/жw:fLC`"L2㣰%K 2dddp-sNƚl HJh%u$4Yvo}@#(IͿB}Fx]h !tDjvla[/}oC]F2swSYY9fy-Ђ=>{wtF|Brt5TOV^xAdKC),̽d) rTgUqLQ5<26F  l=b|zp(bnF schw=M&ɕW"Hnڷ}8 x1Be /la4AF26n)yM|}hL:5s=(Dpz.<ʠZ?p7$r'бijcUâ# x.o/CN[CH:"#TJ. > ~ oO<Υ^ʭڵkC?8.##}%)E{}Ae&; p>Уڡ/h!|.s!2q"eDC*DOϲO0(~Ɍ;:e_\NjNZhuƒO;mMQmp!5XuS¦& yKBLK.X.{1 ` ]GZ!唣I},ѽlEo&e5ʣvDG?-A8]r6b$_9n Щz<~S$Z*%qj?E|_ ׿&޽z˟f֬YS&m c'Oc G ǃޜĜ>޶s> `R K8yЃ|Gkg%GAS2nomNoo/M13cǀwY|29?⫤λ1 2yuF* &H2'iR#OϪ͘K\3dPv/ul_A k J2KCQOQYKH|ks(\-5Sr`d2z4rObl\vew4o@K뾍 H6Qp?Qpݷ-amhnRdJ~/~Kd_w^y\p4>wba6R0'ຒO֗oLh ո PT IDATyWU:35l#_Fb?-[m[`d~V>裀ϓMo0)7\d_3??_Zkھk?d ʁ)|.wbm$\ FnC/edhj>3$Q^5B¦oij>]ި9^ W;] t~:}cDSi7Q` d_uQ1cOI{-4 ?.cI!=#i֕H*-E7+k-:>YW| Egb?>=hgL1C%sm5QGǁM$W\E›HDE6) YǑ3Mǃ])yh)?j݌lǐY ;yL}̾KnB }Yk}}<8|h7>&> c~%˿Bl'IQfm yh |/$_`s5.)mRnm/b>}26zn%Dª\C4~=G(|'QL[p'f]u#*~8k'oӲɟ})˿C񛴜<xMI1Y&.|%L@NN͠s_{;imj7OYV+*p 4Zod3^ȃX|lVǍV[A`f|Lf2Xx5N}GT8׷EQ%_9T$ވ mxbepن8Rpݷ1W_4ypS=BoIe^r~(\]4tLptLg&D@yQC~KHk@ d88^$ͼ]J<{Y-4leրƜbHwE>H%7:ZTqp tӾ-Zv2tgl![ѥSvX;<ٝUwْ-j!! $@B 7$ܐ_ $Mc)6nHbVZﮊ-Y>c3g̖{Bw&T<WyRfctuAՠ`jF \o1ˬ Yih"%±mϽvB]sڇw߈.' mGh~9܇Bd\u%8TڬmSǓqggvo/y(_|>#`=-'_bqd\W?!q<]`Mkj_p9)(Bg{ >tȏơطk|"OGJJJH M`(]5g3FHD̾r|AI(. 9w a6beVPX2E L]M5 c^Q23,xJ _j=Z0Z L=kk-ô~?}gӁS1E`ܱ̙8O]#oV˧h±w&tRc(Bޏbh4Rz8^J8RSMG1ZO= s{۹-V㡷'>@p6Sp'=t~3q~ iō}Ő ied^s1]F<$,go\>\[l+}2~:;Zm?_y;.-T?͓>`8$󚋱̪ xu=!4问vN$Z#Y Z |n_%eV.# }l}hG C31ز:r'`p9Dφؗ'KɺRBnaT*d It\ޚ ggC@ Ɗ9%a)Bc"gS5{ߦkZ!I2BUƌ$a_:`(-[;p;Lqĉ gB@ Nk *BA DסjPB!J8yT@pb@pO@a%tsY(-@ !@ 1 @ @0@ ƀ@ Z @ B@ @ c@h@ IRVPFg8%T@0uFlХ I~g9ܺEm2b,-@f-(dOIpqOdr-3W -A?Vr~k}p? w;=Zp0R(Kz63W&B@ `R(qtΦ>4,3pY #ߣxRmݽy))BR(aE,G V69PMT?I>-WƟh~H*qAZ~1| ?d46kFv4>V[ ,[˙=5=)Eӣ( ncG8oԪ9ȲLjkƴ@ 81N%u ɞ In}9a%jÎ1b)uj>yLRiXfett곈V3R4)6TFFea(/BPa:_`cرEPG/>W$UH/,CRiiMGXי,,’Eo!oJ&bv$ވGwQ$QcQ֛sߟnɵoвJѪOVuJh~M_Y OD_^‹${51s>~?Xj%mHRn/ _wˈs]QrW܌ZLc8CY5Ι7)J Ho141sSp6UmGcH/Ĕ;s^ {p޶ ~7R2ٳP! bu+5&;n BpÛv61ٰOY!3ߧp|JgJa( 5a_O+}u;d %hd/:G[ǩ <)w ( t61Ik/H^2\G>T[|T_Ix!ۭLy\:׮Ԋڨ2gyU8w'=U.%$ %^4v 2ts A{s_vVބ a^iZYtMW}Ubځ`bX^63es[DIkDRVh3SQi5<LS#VtYiH !s##@ P^&WQrje$(JݶsGY!vz>/3U6K?]̩Hݶ Z-3FE{Ţk?1S:.WXv=M=.t& UVbѵ'5=c{8vpw̶Jc0Hk?L?riv- V`%-u} b y dv!+nGgu{ߦsh_ VR*j\-5ԽJ.J"V}Sn;iTC \q?O3sW1%L݋}k6ꯐ:my5ԄzyڌT}N>S]cͻ~5WOٷWF؟X ɿ$ ׁZsXFC1 +/=J?֠EnĶx6)-&k~ے$}ed] "?X@}H%UӳaO>G_~]n&aX4ןr* J*FkMiFD7?Xs?DR_Y qS]r]\鯳{8MmcٻE|1XS\E~{yg_^.a毾%ɞƒ#Vk[Q<;O&m<5lx-fOǖx/s.!*oŧmjY/Y_ò[oѸwrFlT NtD|$I%5 <dYFefv}#2<@ϡpAѓ :vD>=7ӵ:QDڌhx puSBb1D-C l5  lkا4_' hSgFΧGb3D|kIg,CR\ }\hL)ϺhL4Z&#ųh;@skea_ XyI"Kݏ~7йv= ۈ:K< лiW Vs] :3+Qh̩}"st_=A;֏:g\^}@@?@jj*W\qE86WƷM_{.a>5TLy;1`o"IKnwK/yh97͓^Tuƿ~ =aOIR_`;`:6$IbMi?D`_TFYE >C#Wa ah ;<>+DY!-rqNg ̔]zJ OG$Jc:?)KP0{LR5rK "u/2ɻN/< ̵uzPl G떗{)c$`4`Y1 ӣA!#"`(Nlec,/bNWM}`@LߨQMfn*Gs QaBDYr%_Wxꩧ̂ P1h_?̓O>=Ô)Sb {?T\Xϋ֚ƻVD>[_kB 5W#קlq+C0Xs>*J8/~5=9ubQ?Ӣ;v DQHwFwc;qQ?'//EQF3o^;fAS\\̡C"_yo&%3c}%inX g-B?DoHJN]MG~1 ZK|)**QtV*YgJ7t^7!Io@8 NMZv,,r1fX5-Vˈw`f:o:gu0M-Ai Ѥ - JgvLP #0 zK?<Yp! .dܹl)"N6o̦Mرc>_|v{҈=;+|y`EQ}sfTYr݄du`CSӋGt|.c\H~|棄C4w_akύ9 X>+J8idK`O!/>C8whۿ{+s.d %S=@WGltv8bYRdYC}M5La q:z1Rk d-r1$n`g_!H]#t1ڮH%S") l@8E!웼{VSxؗD,6zNp)r2/@ɼe̺ZhOp" g-O=O~cu!…c0w%JEQؽ} KPe4@}tz=3摝[@vnA@Q* Oy2hiIDzyD\ $)}ck1{G:V7smn$IEn,*W\ʼoBoq!I*(_'@a]dPn!H0)Iܷ#cڑ$ξâ'hAA<KYu8^< ׍;tlD *5~G{ޢe xF.yd̹$)_~K1df O:;u1 H1Jk81p h4c(V <}t~#C3nh[%"H@mBGy7dee/#j5;3~4AyX3sxTOnk? %L48|ylYy,3l|:Vz"DTXҲpt$.d̍bH-TSi Fr"V鶖K Fl&==(يĔkBkM~Dw&er*)q 4ޠj:ld;q)es^[ߑ᫫M6u;0LR4#hA9ס10d[Nqۃ|h{ZEm48 Z-+'G@Kw]-Z 73<3X_>"W^ka4w_ZTqt&BjYC%ֲOذe>rEб\=;7m&^ʛI5颭`t}2?ARE\T*;Fg/CF!Q,QN!_5&c(@FXh6R*z^A}ތJ36ގ!IdYp4NW:ƔBOh#;"˹:;H%JZ$e$ -QќeQ&ՖJE Ƚۘ3'_:jͬY&g֬Y#P!9Tgڹ{ >AĢG?Θ8œ6 0&~&7ܹߘd>T5&'yBv;WR\Vz&&Cccg1w)SoW})Z^9+W)!ώs*e.;ZCRh€UScJbW;Ag/h_}$ޭ1BHRyk_$Pya(ݐQH_":=9MZv D*,f/@AKtqJ("NӖSzq6t)ٔW>0g1I#SQ)ȿB!eD,R}>e14;wO)78F,eŴ6 %_ ]NFt>6nHss3֭cÆ 1m?8---lݺ#G8ڭK>M9Ö!A{v/ŴLr|`f w񹿼Ŋ>JY_ìiv'Cԯ(_s|LT~ F板<;an҈9&zY" ikibۯdexl:Z͌).ܠ:ڎ[&Mf˯ F˾][8ט0RR>,3ߊdH}X32#=7iG32u1s*[^$}EwsŕVL#[QNٍ!STnڶJv67#W+d̎t41#-/&L1ژaHێjF61U"I*}Cb5Ñ: /T.'gH =".BHW") hN蠧r$ Z#1.Fv3I< /,WAB~/:[¨/3Y׭"!gi,'9z&t`#?#cs}ǃ񫴖9(z{WqXVH}IV`'iXW!j:WF@_~'s|Puт+N*Σ 6NJ0X0--Cͼj#=nt+W\5_%Viُ 5)U/&)=o-,n${v>6nI]! R#֟r{6=IRybw}<ō!I*D}h9Y#jZP0~py !6,ٱ憺tFV.seYCqy%eSU ֱw,leǣV69%ZMKRh-iا,BkM'guHjW?;sɯRzXK`/_fi?cEO%_ \w(SrFLJC:v7!KKP}QKPk@tkw/e .3/Y-qCtYGú?L43ms*A+3C"gd-X֒mp #}?:ve7b.R8#xJ;0W6 *mv3P~0MDxu%XfUJC1)]h{5\Sb]sڇw߈.'!]sA ȸ456Yty'@RUW!Y?ᣜw(_*(JR!j{mB )E1ۆcWn,Nr*f3u&C>=oϰ:Sd͐F[J~湇,.9X3gstf|4<,g-*f$$ EQbWb}tz_HjE0laFY.֭=_Nv^!d!ıYW\{j_ DuGsK86Z0H*)=DJR%̶_ܜ4(n<21f޶hy:#Z[Ah) %׉!۔Sl{G_Fov)BՃN[7J>5- _o 92ٳPi][P§GMn{Sd}fYV3!O}SB I rfxZ tVӤّT8+`taqV[SBZAP0#'(dMÜ㢻~XFY%**0$ IlK). JzH a6b񓜼"rQ0maL)TG!W@!ŐQHsh۞| Q i")K$(mZeC>ױQ 8m7r J8szMx,sa(΋ۙfu*9 MLQm6Z06=]{&ۍ*DJ8ıC{` J(w&tRc(Bޏbh4Rz8CKwtc47kh-\ B~6!mo 'yZ\cׁa4?(!@pQzq9p9N2Nw[ TM8C5Pv̓4{OG#ACZk:iU+_ <9~Ǡaɜw٘no9cű \eV7\Fǫ ~ lD&;_9R   sS(sV.#roHcSLcG>j^Ej)qpFM^ 8A{ѳa+eɺRC bB];׮&oD(8AbΫ$guX ИaETM޷ڿii)~P;n1#Iؗ#%J b=+0F5G'qЂ!'JA3{PBGF`"QuPLJ)SFp3p(@}b+aԥRD3@ ` -@ !@ 1 @ @0@ ƀ@ 3$!!M4!@ N;Ž?3S-8a֘Q:zQLk%>~N!@ j=?Z*S*8SW7|]ӄ.!+PAj;NZT:-h`ɞ !@ 'ZRsEeJ F7Mؗ@zgSЧpZ ۧy.F/ ?QEkᱧNtI)Ѥ٢PGO}3m{wtqO=]pJ0Dߦݓ=1#[=:}[NB@"T*5FQ_$d73g dYZooLtNEWc-(GӤͣ2;4E*JG771.!ƲY;(ӷmM D5t9I#G?#:ɳ.&3f-h4FjKaov%"gQ2eYtzErrQ FʦV_dA$0}]=ȑ',ه9w*W)Sc-CW(0{'QI*vu͆u'48vV›HZ 4,s*Ιu~e|xj&ts 88#$(j.j9r`pTj4ZRӳFzIۧZi>/jYdP^926{#2_n~1s-G( PZƞ}~:yl|g-Q@ ĘYLƜKhXX)L ~RcNǴN@O_:^ySe)%ޅl? L~t8˰RUN,ۯ@0زÄmSGލE$2rhz+.bݚe$cTFWG`02m%Hfּ%l @p&Xff6XPed5SN9:{6}t_?aZ11 z[jtl V:`3lQBAx Ζ RZolT_J849Y0ffxpn~ZD Ey2x;q;tZƒ}-J;%1qP|m=hmY(A?8%hsm0g?q*%$НxuAm66QB!]crUR'(g-&+Rd˃p= D4 >'a0r{bRФ7uI8]N,u>\Osc&ʠXV mPB#2$4;R~  U:-eh3PM-VeV}}gI_^GȾ'srj"*zEedИ.G_Q2fD}Twh(490[lNpum-ڇFe1n][7+,k &$L^|5j1] PB^dMQNS;P/I:Fw1wc { ً>QcQ֛sߟnɵ VV}T6OGGkw%~>%O>H#_QѸ7ޮeTHv{q5b_D {Zչ\~j95'}vd(LK]έWz9xj8'}_~6 DZׁ:L%]c%odRty= h4[TZ-&khys11_+jHo%w딌KWː-6%\"O;Dȸ|%j!)tt::^ȸb%s'Ed]{ *.f?<;lƕ݆eveuhM1x AQGNe|IRtٳ zǺ1EV2tizۆ^p0Y n: ̩/C.7}b[4 y h{q$H@0O[P RI= bw9DEӴ2Jy^:9^d\$qK9$ev%ڌT>Q۰qM#ycD<+V%Hu2egPp䧏ӷ= IB[dLEPg0M-EԊ.+ Iq[:tmC0wR,p(cyt#=3{j*MܮNoكlnt@I+/߾Moh[tDMem?dϟ{le:VY& zw VrWLKy ͶfWAbiKƐQHxjF6 O\`4;e߼]^wH/nw`[4 ]n&<<휈gքΞ8+szޢџ,~yQֹv=O>O?hum2?r!ΤBdxv9Qg|mCM|fR]H5ؾׁĆ&H?YVְ/U[(h-ѿϖ)!T[L h1B" ev%}[))EQw_8d/^ut8i<ɸ Is}GxP!L7\E ksR@s(xd59\ @Kb3@o&@YHY9']^ϧ-8_kp@O6{*[ :G{k~D#UMa7T ]cLdl0M1gEr8iog#bL٣˻;Qx:"7RKXD:wk-ƬRR-璎sxSw]v:{S ש1 :gSQP0w;fOİ<3r]}<`vy'.ˁ/(àR=B-bKtjF0}\F?)gu[q}J+1/: y8r {w>d&nJƀ;GDT 7ѽT,V:IKɦ0LThqŘUBGkIMZSd4)^.Z$ES J(H˦s؞,ً@{ؽ_O @aR4{ق x1 d#v(CUVTTkt_g? hf9ٻ踮j{wzVq/qI\R !H&xGKǏPC :'N\nY-[U#5֌4#˖-Z^˚[tHs>{%{#,5'{&}mJsAZ%߿V#/CG1a1\4x?]liB,LN8" [Ŝ5R9,:zE=>1i"P~aoxl̵TI"kƹNecl7u)ӫ&͑$&g9IW DĂ/|CQU|??i?H L}ҙΔ3'7,gnOwGeU ٳ$kpFX%4JIE$.*9BAvlWM^.F`,H7Yn\z|E|.Ar1x9zw>J`ܖ[j$Hotcʫ@oϛr?H~u5콰/GpwPf+Y79/kc]vPxRZ %OȺb%cX*8Q!Fwn3[ =,>'g#<0$I(u}%៍L_m{|`OeaΞ hl^?E{ٹu3; wv`4)(JUD?q.7`Z͍噊R%Q{==q|[8y `tƚkg%4HMV.{bT"e7%FS518MV+b$qJY?vWhNJ=?σui]0Ic j4g;;ъK c$V\t[cenlt&M,3FNu4L33~.=ؖebS9ge[YzVPR@}`(i m{@U:l쟋᭻Q]N*Sq;Fb S@Im64yIјMF+;$M$[_5EM}:shIK-g}fgPxxkz}mLyD^?Ii4jjtG%[8ܴ6Wi|GǦ_4т/7j\{i}&DÃł&U}x /Qp vm~3k 9Ql2PF^cI2h}]5by\cOqDmi=ygcIC&6h%M 4rxp:zBQiؗ⹹sb>?uk{aFw,sxpk'N h@Di/ݑ: lsM9_8F_E{nNF_[SZNr1$W L>6jXЩA;|)%lEŹ`݁㦳Z6^s>dÔWޖ),>* z}qk_do?/0cp"ktDC~gDjފ1SN):cvXmW\m'G߄dC1z&ZPG^Jk #Q .civa00u쉲_q9\'~FNnA?5{8%h *K,l}o$D^ &) ApOOS%edpci: CA.\4f#ax%8}Щn i,E?oOXVH+# 3,%<. _Ԅ Ru0\Z }Nd|WS鞍Dc6kq}WҊ]PaLU1"ixs?M|Ntr m~)bE_K;NCQ>lF_>4i?#;Oa(C>/CQZ%f/s_% rۈxzEc5 <9ʸy5hjj'<&I2$dyɿ&k0XdnBA\CDaVV{SfۖXXRNyJьL{=nkYk+oHևkt-O.13ld-ogzTUa7Jė;1>#?|\"S'jq"bǂhѐ{Bi-FkNl tp򙇑d ZtV'oOKd^t,:(֌ Ys7W^P`$BC/Iz!eF6weT"ByN$9iPtO $=O>'e8"#nm]*c'2X$IHZ&/uT4PSNn՝pζ\QIEB_O'Ǜ9ݑVɺ u7AXq]Noo=Uc8#I2Fgs@F莋}SoO[R''}lDg#d;sۙn***_5y`Wֻ9;o팧55:ul= 9Qe~W9-{*nܷ};DkKي,>Q4?ʹof¡̪ux\ z[.exѐNYؔsQ5;-i!D/?kf}|ʯs _ aAܼ g`%O抧%D7QOѹ:NAo%gՔ^[\4rl!ҫIK"zO_0;b? gAD-¼ h'Tp' n؀N}lI}8Gp{O\D(ƒ7 F'7b+[D?^NU t53aK yUCDlJ4䟶pnZ  1-¼D(k A/D+oAAAȀAA!"AA ZAA2 hAAAȀAaI4fF Aa4z>3PY`֚U7P - Īrϳ8{Xta\K8 r*l4,y\~pGLكNhAAF/oًh9^#|j=x^Pz>쎹p  %/ z:<⽸BdD6gqh>}/}`4jnmo! sLp Yz·sY*a/y f|eK~ؖ! D<1m6<C밦}'3ߥ`#Y)޽6 6r`\K#">OdY*$IB f|A.m_qws ބj9G ӕލmYmKqY讃t|7DITv yc S};6_?+ l04O㾝cw8Yz}Z߽y|^OٹT.$z:8tp(8zu)*fB$TU5:BgqN;5VAkqً6643j4B=8iRh=(&O\wy(a?v|':Nh QI kI p`'ޞc)5:tג6N']D ϴyTh:#;'= UcO)~2ON 'roܙtZtYBd:٨0SxsIt`,AD  to>EF!u^?QT cY!FQ<uO{qOF'Wㅮsr&LŲ8 K ly?Liq3_ݣ9+Лxh߿Hkbd;*;P|l7M{gWQKNy a:3((AN7+r DjU!I#}4w31ohVk>AiE %3:"%Wµ7do}=IFʊbҾ/fZW!ɉ{<~,W" PcʫHyIǛ kXoMzơ&Βgs_I Fc< /ג7X|>cg/ޕ>~%ߊbozݳ~qZwLUHI`):#ƖS~;-o74 E wx.Ky+[-`Do$<>?}{<5kO?~  ^?v%qm+=GoIfݛދ9Z l s}ek'm;yM/E;]c۟7~!rʪ-?ļ E!UU8q0͇)9(*.GY8|}RVY;)z:ۈF#h4Z@1b̥Jdn,Y ֠ }{}Ub-'ou9طth0">fhFLXkԳ趯r'NmHc!UNQ$D|.!B"k]rM9Ybw2 =NFEK3QeD¸Fl[b=dPUUEA!e*ɥj2222@eAւ5g=;?>/ %)gzx{3Y PwКoz+眍cbMoۈ4SS5YūWN47>g8ix1TQ}]vk3 ݄BBjJ[){Ż1:(Xs#]mq ^/cZŴ?Ao`?,Z\Gūn' 5H+Ypݓq%w:3a*y_%]8XϠ/ż@`t֯ߕ4s K ZN8:lT!LŔ}m4R31VSw&adda ߾Cq>oϼRZy-T%c_O9Mk0r`7p'C]C7wهko{EVa)q'VѸ1 _l&qc4ny {^!0ٝ4l7ޖF{4r]_׾-iob0[m9o~WBHqrow}WXydO~}~]H61 i RNc%IbOo2حs.ZVj!++k#́0rBOߍV(A\73#+Éh$ {P&rb ;.nSZ7Ȑ(IgXDŽK)'6#MĜ_:> 6먳\Wnz6>N\PMx$FZvHQj~ `*@gqIFǖ_;5a a_@¦;'d:*IΪ ,ۤǽͭtO)ܴIE^?iQc5O]WTTE!N/ձ&0uoTQi.=&jTAŨ]oßF=hلN# qz&Lu}B~M/կuװ_N{^ьot'لǕhU`e{ߟ0Fs&o}8=vO7^$WfR`j'voeᨪDO2S"¦k_͞榪*sh.Tur `w8SΈge?aiJ 6e 0twgǿ3e43&0")Θtmy1њ:HEj3t>c3@nԁsA5š ް|}[ 9 ~/}v:=gy&ݿ|I^]aFz;qST4l%|W-HLz ,~~FKaq6huz$]/=7iz O_?+jWh:htnk999^Oba}g8W!ɚI%4zZ<],µ7ᨹ -sEo|KV&o;v7L,)s(e΃#ۧ\D;t:2k.|'Lh]gڲ.2Aٕڱ2xᡩ'>Cc] z4fSZ)%#N>=S w Iqd9hN6-ږ[#O_vIMסg_?bC;VJ=;S8ʱLߛC@ax>E!xFtp7+lZOah 1gHFiܷk6PZ^Vx3~JyU-%xܣXj)NW*Ov$a2YͧvUuܻh~*Z6&:ůQsƜR*/xYgdCFyn{$o[clKɪI~B3*ӬP'luAN*2>~u mz{N6q>@g/LlIٚvctUI&n:id`gz&ca%L0z1b6 fr5_ԱOt{bxf=Mc*">();=))BRZQ3)h?qHÒUQX\p*WF$Dڐ˲Lub/_Ê5CtwNq`lƫw[YxFo)@_B9ROzm׆D1T5Q"!i52)[x;_&B4V.O:JVj"f;_|OkIlTMLc)5Q"ĊI~~2ٴ?e2VD+k(yq/m\W0`kiǶb!\6KjJ'tqoǜM~U]UU9zek)Y-{^,n^F.6*/^,˨B!D&MW6R0~[Lϗ.َ)h`nnD7H5;>J)k-ytfik|QhE֥W0xxʹ$" }[cenklt'}\7x iIG:?o},C7dt?F$Y}oF*k#/Hٯ<>5bi/#gal.Ns:ٰ?s|w)M;mxq'+7}ǘp2bO櫩/6^KaI9}]oQhʅ}e*vnLOg{ΤhXr57^|餵JʪMku:^y1vd5MUUu~x QsuoL D w8dն`faȊ=#-1Xp֭CgˏZ4z',,[UmK3) Kqa0}{d[ vO;?FkXՏYgĔ[hKZOgv]`ʍufa-i@dB~sOf=N!?ޞc(XJuGh珧YgߋF=WA4eCFc.M`p!Z᱅M[9g~q w:~ta|/i$Q&t q{ee;NQU_O /oFO𘩲ʏuI4^ߣSw6$ > %e{:lk+dǿ >6jdS &[>`^K_I$aBљgw[H(H^V"IP5 uM:u֢7Y9 ]oܪ׾ށ$k*,bcw罹Ex~dAFjڋwd**c{G(D~O:= f+pX_}-ӎl vCvњ/kcG~VrA5u1b Ng )E,]u9:yeB|Ϊ˯h2(Q4%{]8j.Cgu"ɱEo!v-z{.EWIAM4կ { !k#83p`3T.DC$A/G9jn4FgXx%ffNn)rJ6FăD|LxO$M򷅰oÿe|K_9s~%@2ph [~Ah4y@3g vQvͻW,;}!?=/ҫcDB4TaucʯSOg3eR-k 4F+6ZLY/ϥqp GLSU:~[(xuˊ?]7<2e|IK:ą)^V.YWsqCgC^ˋ1~Ԩ_wH]CY4}Tdf-obUJ"Jy 6+=Ȇ~(_&-l+vW$<,*?w&c99s<f^PreKN_Kb_?H$:U6̈́N>>69lW3Ћ>v$Hk8!`z<_5MJ~B !"0V=umS)g K)\@~Q)qNz6M\qu8bypA"0&{VXnP=;_3ld-ogzTUa7ߚrQl;1>#?|\"S'jq"b[5وL{la5ZwPwZeg{<$kբ:x{Z%2da.FxGflHHϚJ|r731 t=R4V3`OB iC|ng$o}'ڞq8 J*,Eo&dřK^E-: WD.jZed9JI&L_0g'i~1܂ӳpζIQIEB_O'Ǜ9ݑVɺ` 3詮x;<krWt㾝 GfBUUZ[hmibc6[e߇=v Ax[(‰8ڈlbj xܣiKޖK+C4gS!6nnr]|/…)D/?kf}|ʯs E9Kܺn8pWl]x3gx[d} ?F撧%D7QOѹ:_4&9w0rl!`|{$o_ {Y_p FC|~ܹC D<4<<;pYP*^JAAA$a P - 97Ρ1e ba*<{s= a0yz, J$ IDATXU@ 0o~_qx#NTkOW33Դa5p%GU\"W~`?3:6&~|^wZ"  =@OaӵfJxLUUZо]#FLN(*Zȧ]’r7'[E- p̛E,˴8B=k:{tI[̊5s3^vTTӰdUH8yGtL(䉄m AAK@ax>E!'lk:k6R^UKyU-'04wD4Ŋ5(-F~߇l’rgvV@  @@h^or?{'=n4)(JvNnqfJYm_I ⦆a)5D!vn{6 AA2&5-NgrW"2ОVsb9YYB3  @π# ϓy Łǚ&)qFauT40CIF M}±N  0kD=`phN;Zfv Yv#CvuL{NVGjc4:ʞ/$mĒ.1, 0Uhjj'<&I^1yż?yɿ&!%b^,ə \tf-&aqiz sEkPQ#fA#v|#A\:CJ[n5!kd'1 Fz]}V"JYE]DmzJ^d4ǣ镭C$NK!_/28e-I25iX6vHz?\NOvn)BK}Ѳ.\: jXm,hXJiE ۶<=v3^:I0Vc"K#̚[x{Ck l9,_ ]YYMm^߆h&+ Um7Z LhM*v75[3J\sI7g(s` \|T=78q2%(>Lcж=Y\rASZ5sK- n!&Հ>ZtXƢQ3lr0ONíTi$_FΊdy.|XO+%YK)ޔǿds9el^:6QޘE81o\Ԥ/q{.nHpCZg?iԻued-Mxs Um8NۅɿV65[5kƨ y:>D YAl]v4N!)1.sF@{<Rz=?[\Nҳf)(> sov[OL&#`B-<]5 B#Iyk|C~.@L[ NufyT(rm3dLбL::j{hER.FMdaE%@EC7U ڜc5$I5THmm[j9$=M6\c,3=vsCwN;^@gVeBkRts3/cRN^誷nGcIӣ5_L! 9ɑ$Xr{>)%t7nC&H55c635FqYHgRDJ-œMlvz%1gX}_ {wע1:.Z Mm]v@jgZ)'QzC6'-gp 1%IObeR,%E U߇LO e-Ћh֗cNc016kokeZJܔ$7x-ܚC| IIrF=n/^:gsҦhB8]ϧثA>ݍ6~l&˙ޖ݃JhX͌_ƤrųA"ϟ $(ܘ@ΠVɝ5 9A?ޜC_~wޖ@_pwm8څ `:8j5׌Ddr+QKW2wI9ȥp |ٻ6'|#$t?Yg}nup|+~GZ9}L9%gsL,YyF; NaT%R")5rKOLAMww7=BD BR!^mN;o(_;ON\nje"I)ű2.OsUI괘&:-r|Fam ^<9Z;NQR;MK(GQ"k$HVCwmI32y]&Ny1*8ƅbqEF<߽33vɼi>L L2$j;m9Ac|_&/}7,-i`ݑkFx,FoKz@KDhXr} LMzpcz_p\l k9T hӲ+B.\6k/@W-V,HMhXv;vF{@;|V-Gp,w؜ԟhD;L".IJ;Q}?݃}>Zh4b2P 饫+:en/Ks5p4=bԘ^BZV"I&S@4aLТ!S MlH@xgx$0&&]7ݣ|W j&ݿġLFcmǘ iƸSOf5\3R9mMLy t9(TrRJb Ö=3$~T@r{8Zc2^_!A&Q{ 6rɩr KQ(U=Q1-M8JqxAdQ<os|wFK[k֞.N'FlFkownĸ8mOr"j& SO&e9ȕ2t1j qa1X3&cxb^-cHb:ŨQ_=&J$gEɅq 3яY;#K5l}z .lіKqrק! ĩ1|̿1+~y؂8hL1d47c"ɥqU7ƥ |h\"iZ^&x0Z~GZ4Zc2^_![yvaXGfN9Ԟ?CgX57xZ3sQ(Vf`@fNiY{0}p{\cp;p6x.$믽Bɂ%+(APL`2l};>Yb~ }ǔY!^CTߔMos?SOz) ;|xd].qF}F塭kA7^IMֲrh lC ht$`hcXu7&T.hR="¥0T;CY1TZY&.&(;H_OL]ڟN0!7Ɵg+6FsL"E1x<C/x^NrYyA 󗒜ArjF@g+z)*] }3NΟp"Pk4 A+kGLLKϺ5o tPsHY}~]>%Akohh3ZL$J ff`YzgJLP!4Ôt$g8ɕn_|k3}"ǀ ^I˹..ص0+U4u}2:t&_;݃TU;bk *RJb7Nwx{5|v$":c2TSrF|b 1Qg+XWCFN>FS $alݵtu❪Cm [OkSW2d]0Ɗg!(>7wLX%K|_;}^c9Ϻ硳Y|kn"Z.J%Nwp;=ȕACј|c$S//0PvmDUܢQ㤳Dc[:5 La:Px\ވDc\g' B%v"+:]У7~&sE3cۈƘ 3{y] [U>kN8~ko7U9r;8B=Q_ >i! Ҋ4z82/O\&hm [`Rܳ @zƠB;jWLm\F LXFI8Fw}b|.mpL㔟a1ADT.tsT:Ƅ&a(9Y+s[fU0!'k1f*W ep8# p(*]L&P{atViV]x Sg}n9=r9J5~{#7z⤖/Oܠ"-cqH>K{K tiX"58ԫ 8e}^yb2j%.NrDueɶ|)!, ˚C-E:/U4Ĥ@A1| 50hs"I3JRE!^/*%)XpR`>"ùv6->ϻ. K%%exU%mA|ZzFpB[sQFFm(rCcD.F[rXxKΤ5,Jf}c]ɖAɪ%AzQYsiߓ"|Ǡ7xaS3 4ԅ \%++G=&>1X` IDATAO$ݝY/| 0(&*|kM_N\M=E,j6>>.Ŷ.7=N[Ǣ[s2ˋxYhjNW7ͩdLE2&`JYׇf1%hE&(ٜ>cYS.?]J~.;r C^Dsi"$id.JņL.Òn@q{9juX%Jatm7N{1dhP"5&]ITz}SCHg^ICEGȕhh~@b~ j-݅ƨBOزӥ-dH"!̦/-nMʬĆcU)d,L m^M6<|[5MPA5}xG#WE ێZwMg]5ޘ1A˺KdωJUVPhTzl.%$l=ݝ47MئB$;RÆ8ÐXd%yVSy0#V/R¥}NW/ S!ι's>duYgŝv7GZ8q㤣Oś2Pf73mRMud.Nǜ\TijO#| ]}|tHJ@5ddU^/=6rW1,uVμ_vU|̱9ǮTs:3J!ky"_`|Aq@ӸnPɳY%SR+iGI3Uv-6'RP$ bwr*zl hM\{lIŔ xN:;9h:K\ dKwK_0S{kkSI1Q݃^=TmޏL)99y;fE_l9orJ>$$zʻ;Zlݵ|Az:q f &ߏ@ʞޤ&eObJWg&N[ek|7_Ow'}(J, ȇ}Tt ۻR-\0yk@|RJbi Ez%x-r {[=b)$L:4F%yQBQ`J֡P9i@LœR Oj*.}mH$It J7iH2 Ъ&Wre:}E$eA Z<\)EmPb:m|eDkV3 ڜ_1qP,:ց4adN e͟Ծ>+_1sI+&tP_SũG?fIz2s z=57r4-uSJYg0)Y dWG+g*8JFhT1iB$ 쨌r@ 1tMtd2~b$/J*ku8N@pe dK @ Z0gH8ϳ7hA&;+;8S_SӬC2U?|#W"P0'X^YR<(g,gYR+I!I8? -D:Cޥ@p`4h7^n.Ap8f?A8. v~ӘtITxfQ7i T&ݦèusmz-n81b4&'PX@FLm .~@z<1e|!$I}omefƳ|i fŢjp٧t^:E(rl;Q$P"ACGG+ȟt^:Ox<^Μm]\B kwעL^<VzU|`Kf \*6GGSjfXS9:JMG^%(r\b%J%.kU VK?nԭsmkڧ ˅XmEc#6-̊CY1* 4 4\];-GWǷzk=`\.=W7[4%mmfw޾x#{FnT sZ@'$$}v9{,;v𘬬,{Iۋٲe .d2ziiiaOoR#˹D$n7UUU[|WY4YR`v r&wW&NV[K&M֖ J,6G~W.o>ʅ)IQQ bPTTDff&ǎ{R7HL?5D&(z)E-nN*NYN-^ @`$yi%ʫsW|Vt>y${\xr-/+4JFQQ_v#G6#y'qa](n^y^~ez{{$E/X|Io+5I.OPTTD__?ٷoBAYY Nj#!AҡΞ鞢UYX`{^$#I2!Iqq}V;is2(*LAU LGUQRŢjZ(8#._hV|$afJEOoZ kDE22̈CU?H]]' n+1 F-X,z4%JQKRD  ޯ?gQXѨ['t0(^fn-ߞ>=h0Zqtq3x=$s^U_U Y8h4<\wuHDgg'O?4ׯ,6٦IV~z7m^cǎoo|'55k]vl릛nw]G\.{Fjҥ"7ez^L8&.ׇϩYpgbc'{*/Ibz?_?zckjU}~dՌG.q\$p+&<~ص:ȿ/|2na?䕿7mwiS)FCpQ&'>W}HL4п_[̈+yzA/^O~^rN$şKtwQ3U.bLEyl_*EU?]}/v7fǧSԝe\ȄBVDK_}d:)C$KϬ0 5=یJ`\  wfN>xRѠv  9$$XD/3C>bwө=kgeYVeush Y%&lC7aϝb;UU(  )w`AgYr),L;!l~i:z3!X0^ lPo@.t<@WWf<&=Vtt$Z:;3?;VԅNbL/ʢ_=~q+d'z9Y^Gss7&ŋ2)7q;#[WwjUdepa8&ϣY n~N'?F1YRR|:3 JKKETtb `Ν3+>-jCd[:νۢ$da~chDuֈ$ ,|B}lzU_<ϿI@疛qk&Vrr &~|)~ O$yG~x:qc~$Iy ݶ$Vgshnq^UG|(N{pGB?N5~Q.5J?}ӄlX_†x<^~VYJ̊yΈ:z1<~{^Ö?a2d';u.Ԍ$dxo%%%kA}o {7̌`ˌb#D|+JC>g΄A&FofjǍojYr9FW̩z<M+"KQv'" &gڪ[QP f?oLܾKӿ#J^{={-|_~de&fuAyfLJ__V|'U|nzl,lQ:/}F4wY4yq:}jbbtttg?>ly $*O5? ۆB!Ϭ|+"c]{b6iYwPQ^… Ksz&ozd2.׃N7WhI4,_ oYEİzUAг=|Wgή>~|O`~qGIWm~e$'' 6p뭷qFU8VfܰSUÙ@.\@FF>(я~7M6oތB1'L!ږ`ĉ*gfGA0FAo0:%l3Jrr ɾ G`훼[0G &4 CVMI7_=$%m2 fc ^2XFC'/](0s~i!itrpɢ!yI,NOJ4 rKd$&6t _m*Ʋje>ְ&jh$LQ_K=K5~B__IOOsNv2 ŋAR֝$/oDBoz(@,gddtRlqS](CԌT?-?Y? N_BQ@0>zA}}LO%ʥozR󑹴L^cB}hԢӪP(HB( ):;ݳ#%^$I|8N2uAӸ6U1Hfdv;|vFʲeFtLA' K].=Yn* 6 \ܽ{7f{˗|r6K/ᮻbpp2FCee%Һ|ۉgʕsR@O]֡ ΃q`VpMBdoXH6y8(٫RWx[o^Ͽ~o@Iqڬ uk([ÅŢREy6wRAlۆ%Fy6ӻl $ JLw߫@sgVv2jUdfġR)Gw'.Wh> f#Gkx8+r)WX@̗)8w~cȾ}شid2xw'&Z0{L&f\xxr!L^Ӣe!N}WRi\} 6 ۍBrpL>֣HR'283VH|Ţ{XIoop\ %;R<_ Ͽ V ^U5?0sr}#7h.~z*ܴ2og:Lh(D4$#Iԍ&+3Kv;[<I,'RfD@49gS|x1 illg bl2V㞞>2y\mj\^-x;\@tO\>v{dէŋ"s)oξ*v?[q*';YGP`ekd#,#+MsZ${t3Z3gy ^8rKy=4nqK : :liFiݳ,[nZDvVIfRScjUttqV >̂ ht5#"K|_{EyS IDAT+N(Lv)~mfıQRi=4Ez6Q|^SM\fj c8[X%Eb@ip16%neaF/;DŽ.nfތb];֡Yhаl(dH ˆuɓFJABAPմpV‚RS&rtSa;5<5)^ןcLL>IpT/qiv,+gسl4Ѥ/oq?](‰htKBv{p8"sd^IMe펍=cyfTe|򑠟ȵLuylZZnx [U[j+**B.7\|}Wq 6)$[l P&J*.DNv"њ#9̓_F|W2).J lLn]`X\0@?gI}fy< Ik_bWufF ?|Q:xOvSmM7v&zxעJg`/}:Ip\V,呇6eaHL0CwUcͪY˾1#^/11:mdZc-oO}΀gkHihP?͗cl摇6ٻWfuA4w|Yˍ7, ,(H_ƺE_n-K;77qg(fIqGǝÃ3Crr2.+d`ˉ%߻wUT<x㍀?vjY~=~>k֬x|G\f`h"jԹQ+= :z1|DGVl'8RB6jmw*sC]/.jy5elϸ #Ϸ )3^I~^{8WՌJ(^[odѢLC[tŮNdq6yIlu+mdRXBwޫP$m45uK\4^o;~aAfR) FB%e|8[nZDZ߳g'w+g|4%/wSYY!77窚R)/\B!o w+X$**Dv8W[o`~9ӈB! Vū9m.Α-7/abV*VN Z$ k\;g>ԡ|J?c{gWggvΜm7{'=-Hcc#gώ܈{`/ǰNm.IøG$/ ^%U"p.W0C[U1/^|bC* em'7Ӽ(1=n*%>H|o͎~'6p?|{^ڵ$'g7i{^dI>/^X!rx$'Yzĥn7oYҥ|Az}>wZ_R}}vA^{(m]w}zii=~Bf`z|Jc^?g$[ó~ny1F>mg9uB Ț6޲x G?P=iӍ L6xP&I\7 蚎*o[14C.>n:Ξk F4d:a0^_R)$%Y8^=un޲뮝%Fґu|)v:>)A$JfEVۭBdҙ.RrrOP\/G_ٺukg ?sF,O|+_ 7&&}k V+555 BZkmm婧u`+Vp5װtRtKK < (JPTTٳCII rZz)FRϒ8,%6e#wkM.ǫV+3lVԼּ^>ZQe8~ACrJ :nZ&WfBص<_btbIOE.=i"IG|EcS7XL#J ';^Mww?T RS-Ęutu۸x=b8߄MQźUF-I&F-}6;u;N-s<&fo>y;If:_wLFbR,ImZ!VFFCkKόM(攀&ossߚ<Lƍ7֭[l6>C{OFWWlڴ ͉'x뭷8tPȔuٸqc@)o;ó>`gV h"rc+ I_畦7.u8B h@0> dGMm;%ԝeluN,pv<|'Ď;رcǴx<ܹ;wBBB .i&b׮]sСiM>l6?OxAM]]]iK ?(w g7[޹snݺ t;TGo|9%MSSMMܹs;w.j}r=`S{!f́×;pA&>9c`r<>qI屼n9/2q,Tn]t}[NϨ*$ugr_p{WLfh@pR_?ƤP*cl #\ڈV>U!Z_z}rի }rn߶2 A.*xs׉i&Ƙ)h#'W/QJEW3!>HkVChop[3PnOx9u'Ik6sӍ|ᩐ/1&B@ .kWI+* DVT)p=Ix"\8@pUr]+b#J @ S@XWN JՕS5N0<.u@p!,Ђ+z;q:ߢ@ K@ z덬`ri!a@ @ W;20@ q{ǃ/Jr @ W#^q/ ExR@X@ B[{ŽH=v#Z *uWp~=(Z *“ƿ@א=>гL&" @ Y-[5k8{N`c2s-(T?NI/A(2ظq!+as2'$+*t r].פU*U'M>e,#W(/ =+=xN?~@ KNN 7ݴ_M;PjqgDuHs:6.E+b2[Е"sWqB`z=<OmIΝ>cЎ$I$$l=7j[_|R*x@0eT*员ET`4jILPXFrr,5|Yř(.$6ֈ㡻R;YYIѨĉ=&)ɂ$o0h$dKоmm=ݡFQpa.\uCZ,Fc`gg'QRVk0]tuYřX(=}}vNm~ee%HL^ik=ttXq:}AIX $$IGp8 |T~ w" H)ZRR1.륧R#gjVQW~(sRҨAvv:!5cr $zj5ʿJ'h1TG]!?xR.g>0~ ՌB!gk;O$وc?XWwhq]dy\cO/?QO|8-ҋʹ_-5h㿰F. 1Q͙: 9R}};}vdf;A{Pe2=]tAnAiz"vg)陡שg+Ʉힸ8I?9)·iV*' K[8x@ @yyW]~/80 #~;p:x<^l EQHMuti1~w3*+Ŭ48=lv.yysN)s=sxϞ#OyLOwD d߾glb`͖-twŠeXQOewAӒNr$rrRy/Nss7@0JӞ,]'~8fGj@W;ys?IRJ=$D]wQ]r+Eg4!9k{e Ό\ƘZ=:=)%ؒCurO`v& \Oj

    YwX|ŵė9e ϩ<]m< Gc͝ ih,'<2є@v^a}tt:V Ns#NSOW`֪y͕6c2Kf-EE:<}~ǬkVАx$;v|YGy'x3bjN0@5A 4]qΉ_sIx<ۏ??o/~^$U |cY]76=s+53@Q_rK3@ş|64 ?ňs* g9U[P)M}wf}<_c^Y]0f}}t̼y9q'ތW|} F'eb#,;vن59r+wMXwp.؁ /r`O^b#~W,hdds t)ZޏP#@Ց͑HѼ tz;uMks>@%lCTp /ܢ F?u=5edx1,ɂLc=LȈ?w0'g{kYsϝVD-3@jf-[O_\l..{7vwUG^]1n@i44mΦ(^;\vvhj27wNqFe!tt9^<9UmRؒG>_V1&{wjq"}xüaãϪk}#SF $>AwW;I`TjʽYb yRw ]'jߵ+VWNpCCV KΣՋL㠆xg(Bt:]ܛ舳,_>TZ~sHM&_ lf@Tj ٴiW^kQ^KRucݺط(?46F?2@m,vkxW`.]]LdZGrHޙ IDAT -Phi^\3~,*Y@ J?i_8֝E-Yf^?='C'OnᰰbEW_%KY~'ތ+^]ݩyOS1= ˟ ] 3}EM-AM7ݮMOwԍ{ܡ`ͥ#FڴNI}tw31ervTlv'B_ja.x֦Y6rxoR<4U&{R sw3ISŐ==]VK)Pojs*( H02<~J; %Ap'fe8m1f29k =J_e&cuք?v ?מ 7g,sիh4A}wG[WhտI_(_!2JԶju{δc1ZlV6L)p8C:' 2U=4|'S9qʑC&p,CCCEb\ODo`x1طuΧo_}fkXMjw:w#̒%EQtj{%bSW׊(qupmBhQ⢈VK?{Nj0;ޕ oW4dspdssF\7N#?^֪5G[K#-V;``̅}YZ{[}3LZ- 8J6We%>^}ϓJ{w+, Ьڶ/p_喈3Ŋe܆8k g׿~qF.;o>?Nnu}/\R&?~.kb>8Ct7zc% 0ZB/? ?7c/tQ+&l{ ހٙl%}֡(\w£}ʹڏ`$ ~~Ψ)2zC_vm6C35=e+AKS)t:=%ϫbx[6n"t:-Nz6oӟ~O|R.x p#shzjcxXk;~Ƨ>!23Lmg7jۏ4\s9Ë L_[n,-* slذ?1z&'J86O;-!yA ;p1-+ o2?>㦺JF z]m}7n!~!82+?vq_q0q-~+,Z3HV{]/ǾQ$fE>0Yrq4vNZ!\Tۉj~NrMǞ=u$x7?~QΩ$h4Mh;Q@^~6є5zza:C\ƞ‘G^A i9hCؿk+͍ߵgKO3hqtZV֬YH0v{&h4 2HJa4qkRZ3@NN ))^uumS`2(-n734֓Oy2H$!HwÇg|șh#?? @{{/ S-}; `(OLrN ^u5:O'z:(Z2cMN;4@wS}c/bS#EAQbk44syClzE ).b>r'yi8ZCn~OfNU6O1f'|Bqs ॗ޵𷿽˚5 Q UUiv{i5NBkkwx񙮻EwvQ͉/*ޛ~/lQ|AZjRsgSK ̩z6JmM%5Xlvf+%*:ښii7=ӟڕB1:;}l;nxsG!=xo_ >fs*>\}Sls[S鋄Bcx<^f#'xWo\KOҨm\ӟuVMkjLM~ fL-4W;@Bp(M/!@ !8k կ^+_kv"</nTU}oQ~'#XLpXӕabo!YϠ=vMh!g}n/d BARYYx}QkW~z#뎕l.x?0t%B΁u"uFA!τҢd,~L"B秷wńBq"[!BIZ!BIZ!BIZ!Yn8I?Z!BIZ!BIZ!BIZ!BIZ!BIZ!BL35Zf'ɂ֐0aI[!BL9,]ޚti*j?# `^/yޚ]F7a/Z}]tLOmFSDQjUUUVĖ !BI!ϑ:h"Oiur+Vcz uԟ&(Zl .3Y7_zSގ̬Mճܘ3цBΉg*;fa3#]m< B5uzf)ig}t::ՂӔ'B%=1r"i} \@hp=gFgÙ͑d4n 74Yӏ[vz!|sC.]!vlHWGkD.CUޘ#gQ"Ӂj8&0'"OD, z=>K`߽ӽ~%?CQs/ҕQǃ+SjL~>M`-x^=rAUUj`߮mjt`EHsdؙ@hzo]jce( 7hBӝh⟋vqL&;_~"#'b:l6v}}twwf=E;n]j09(x0pz؍x?cdT0*Vq(Bݑ Hh^:[Sǵ67y .X7D/*|9WK™=[ߙ3@rr2nwD'g.=4-#c'~r1v$m \{l7QvDS\A'ݦSO1 Dn(Mtwߟ$ 9CrvXC^a)y9HWG\` ]Xb59yEtzW144l%<]>7f[,V笺gR M4גS@Jz\BKS1[6RN{q~"!DN9z]Չ2=N%;J?1. T^? 4Ucta,EћƱtuuj”5we5K8gq `xgA!zhި`&=34*=91)5yݑcxd[P2'ɰZ8ʗ'"!uly`#Iנh$W\HצvLIY8 3Y#E(>7¯9TQJk2ePU Rw$:!:^exvI Ƙ&ÏNG!"s?$rgP~YFQȾ 4Ȏ9 u;*,) C{c=zl,As0s'F~"!DH/ T=z{`$FёTy^Ciu\u9aDoMfAX2`00BŒ\VkWղp* hkjΘe{{: (BfN>-c֙[@wg[̹gY#!DCzj>G)ȝێdH 04ZC˿lsFG9XIomtu`tX~~ՄcJ|}VQv/ }T_glZz:hiNNOAI9*0%hnڏl(ZlɨFcS#EAcIFF3r.V9{MoHAq9eXvRG|^Pwǘ '3U @*Zf xoͫd>-,V; f :̀7bB!85T} &L#7v;O^[Z[B!f[$!-׵~8-: B!ČәS5g5#B!q{EouhH-B!f`nI#; !B1 @ !B1 @ !B1 @ !B1 @ !B1 @ !B1 @ !B1 @ !B1 @ !B1 @ !B1 @ !B)SFfl7c4f;2aQ)hB!CrIg/#c.{ηD g!O|=c7/%#_¶pM8xѷuZΦk$>EEQUuuh44 `$L!"N44:=y+[A_|AbY&TUep憣TWJ+'lAqikiP\}=qU!bϿލ[&˜QsGHg0fa-:з8q]]?B9w㡘8ν2<@s2T_7b7ߋ1-ouMD=s2h*(XVz~|zI) <`rJ/tX6J_̻_;fY9,[Nh2[PBv^@SѸޛB!T\~[8xn~aZEc kS$/g35@ΈC~WWu.Eo Ͻ;^_AzLʾ zg9.5߿9| Xb5vG" tejk%~y~_sj08Ty sM^UqhHMbʵ̬Z{9_kzI){E(B_(ضٝ,Yt=b^븁B!t(& 7 @Qng IDATh:Bg|s& Ngլ|vG"A>xo=m SsG_o b^阿\bmx=nTU-js_4/f= /GQvu_=4(/:gQ!b"KOf5\mxێYCڗ^>>g`0Hjz&}34מvdfӇ\9TLOWm-F7%IMhMUjGݑC7L&l7AxH?b}%x;hq&P &EK8mܲACu7R5 ؼ }9bYl` {r8Q:#E{;Ǭc8W-Τ)yZ$t:g)4&DCh,%gz 7e#.c.\>6gh6 ZO!4b1QB G1ozF{n\tXc~"!D:s$?rg,ftϾ QLp=lg6G"F C{kӸ7|cs'a)==~NqLaOD^@ pJzAPtOtl6;qztz4E5'!o~q7F٧ю No>"[H=EQ;rZYͫ@7s;Q״67y .X7D/*|H /r㦽lJpPԔTV<7;sgZrr&Ï_C_/g,=4-#c'~r15K6mRtzF:+ƄHߓHҚ'ݦS%002V1L|CGC7@$` {t K+,A:"Gwmc;\ fR2w-o2][Iǰ\x:UDFV.e c0FT1/x7g/'"O*0YgK:nK<:[?0$)9i% ņ@  248󜪪ݱBZ9Q4@ݑM&CFV.YuT؅U,#|nlz_͑9.8t#լ:> ꛛtvL<j ?nmii/N%DG>OtFLypbzR1fNN'Qۋ',oLP>&$>;IIę8vi?Jna 6Fcprevw7^xRR pFUgtwg2'2G'~ɣֳLOxDLG,;v=1dc.\b4ǵtnzczA8=X463jKS0%cxX]Tuܔ`&=34*2N^<7ag[˔W%y[AؤxH? 1Пg-/\q5窏еilr݈15h-yѣ]N׷gC}(W`z} JVV EQP uG' "Djl3JxxH?"Rvr@g^Ut3{~H1h-~{qI!8u K)(.PqsJřҕklonuE===444ʗ'BDR^~w*LJ{ "rj:?bKhHd\H3;~h j0@E\/S8N` c.$%l6zZ- ft Yl%Z 7=곑,~"!DH}{6P71fRrulDkcH0tlf+& ?9CJ6Khı?MQe} )98_IƵ_!i=FgQ]ؽqУXV_|vm6"8MMbʵtuX?a:rU`J0x[6܈%FCVN p&,7`@B!3?3Xu[cnvE,;Ge}}-ywA|룮ddd;|9x,^NqYE1FAѠjD:⭗(k4%re$%|}]>VV{(ow׿S82+(!-3;~wm1)g%d`4x}>p~3Yaa(]Nmm/p_喈ә|xH?Κ1Aێ5p=hur'f@Oڜ( }NA9=Ħ7_ ,V;)>5Ts2)@U57P{֦IM;S>z;;RMњ] B!N57[g'3ttCGcg>;ќ ]TUښJjk*VE=4K0tqGWLKS=>Բu6ճoVB!Μ OW Nwg^B!g?ABN٤x<hI-B!fΖ4JuZ3=@ !BWw5&L\hH-B!fjpNB!BLB!BLB!BLB!BLB!BLB!BLB!BLB!BLB!BLD(B!▓dǤxQ4vfO B!Dܾx *r{c|0UYn̓)B!"n^_d:NzUU1E(S@ѠSC!b:c61ʗ._A`݄tmXx9fv:mD_LzV.z{:Uep5PRyX7MܿW;r(*F!{B!Ao(HVU:>=p:}b*?'hFehu'UG^a)KFQB\6:՗\3)׃'ld>e]cPXR@ vcJ0S@zV.7MSI7!Bb3uz\ƒ; 8^9,[Z@?y3nK_3)`0mRv1ΤV >G0YG~Ѽp|z?{ ,\RYu!}=B!' yx9t,9&{?@`I0?;D G;i銻=I SI襶{kF=V!d n8D3NbJ p or΋Vǒhj8ﭏ8?rqp8/s>$Fա(Z@K1>y9ryh:.c~B!S5#X^X8r=ʿ?-*,i6Es'moy퇗kDErc}PwE3t0$5=U rUvl KH0[ڒ_4CmMU9ElHⲘtFV.3:_V4svn{y=Q6& wsK!bvo=>bo۫x`\u$ZE hG,`Av*f}AKwnd&]8&fd[_ƒ{MQw a$N㢫(lecGs(cF~}ۏ'+>{C-=+F@ĹrL fgb 3uxCtE9IdfS[SEaI9BZF6ǎLsjt?@OOl7GxH?bl=nzZ"5wh?h9Ive>y`h6_67_NyV /(o)g/>dzf=ZF*8&K(HurǕo_ZU#6( /]NlV( )67Lހʸqbh/CgRT=dz*yf:NjtvsiJ!f6O P= S;r\X3@ϠG_ ϙVvw( %d`jჸ-6{sEel6CV#*`g:$2s9> #6co|%r:XcB ~"!DĒ`N@zNx<ןLX#tڨJr*+clmh{N+eCQ^.nXbr*߽#0G9Ɋtb:;ZhmBwY1JՅy'r]yR>=X\a4\xnn#&I&I?16 Îxzܾ>=Ozf7zXl׸vQ(5Fƚ 25@b0>\UU/pRSx=nlD*LJaѲwvȱVo,tIo#>OD<qz`ʉ/0F !(G{o~;"xAjkXr ɩQ4:%_4S6Eԥ OtFLypşqd\3ǧx'λN3H?*h8lB@ŬQ('cxreK πp.;7G#QYOxDLG,;ן̆N yRlq%?@2:^o-G"3#la"%GpE ́є͑HVn!5U{񜰨p:6Vyy> 0 <q6vNVnV+ڗ^TZڋR"Y*U{jZbsnVlڦupmccaً  v &ύGy >ϙsKqN֢q"8|BK,3"EvsbCuz\r^^NquӿuJ#wUZU_#Z'-l^ fVgos3L gW*s\fmvGfv/ GD q"u}19;jHqf`8=-gYe WÙU5k.OdP0wнO6 6v`w8v1瞚zBA ͆o  kjw;e;gddT;YƉBDd pf:/>ş*o{s$^,Zʪ˝9p<` mƭW34Vkwpo|$g2AvcLf[23>rbL$uJVa޳IB||hBD q"u=44Tso:lUa3 !NLA<{?k/+T4Vztw}~@?`u8Nw9dҀtк0.d2IߥmL g62=SVRZgbEs6.D ݺ@2n_Y3mOz:s|3""""ox x]>/_hmG*~|ѹ x_og?u""""Rǻ8q4,:멮000L(.PII)TxRf9ȓر?'XZɓk_;Kf?'؝u}j*S\޶ $;HܾǍc@PM-Nn/K-Cnd W ӠWC4zoO{$f#\r8Bhd;u;4WoLf$s-2O \pQnGv}h4m@'ӹ?Ǹp; -@z9^P;%Ȟow`;3xf 6R(=ggfjhDp\xE7;EiH!4NDrŒ^zuCIDAT9iFv=9Xqַ^D"ɓW3e?[]yn`vC7M _%.))Lgܘ*3-t< K> GnWVՊ˕^[q"8￑yo|W_}}OVR][ggH$Ətt4R[[S[SQŒf0MV;]c1f3NI-tEXtCreation TimeFri 05 Jan 2024 06:10:33 AM CST;l{ IDATxw|TUߔ{# A@^E@D\@q]wUT^]   Х@HiBBHi3a.frO̽3ϜsdjAAAhy[   @  B#ZAAA$Ђ  "AAF   4ήcAAhAAFPu  -Ђ  "AAF   4HAAD-   hAAAh@  B#ZAAA$Ђ  "AAF   4ϯCAmG@  B#hAZ\III[  ̬Cx`ZK]*aG&zUX9uEmM$Ђ >>c^^^d2:>|8 00PZАӳg{vAڜ!ǏYE7rE>AQ=x̟?˗f)NNNMNN&22 Gpp03f JZfǎmTj077FmןС( ښUk&ս3<UTO=ݺu#337dp6ΓO>ŋ)..YȘ1c2d$447oѶ&OL׮]9p@[(#Fwpڝ GGG&MDnn.ׯ_o/o(?h=ٳ\pe˖՚N0:ɓ}Goժy53`ooLRqq,X+W|N/)TUX 2 auk{Ԁ%??Z˜qrrm۶՟yyy<+~T*?#&L`"cŊCTTsYf7xW*Y:u*TVV2{:KСCzɜ9sx78x`Kc5kjm!n sssӓ={֚@T,w~xzzҩS' F=?>ǎk>׭[ǩS066///x뭷ضm~a|Aw%W^eѢE( lmmK.1{z[ܜ3gCZZ6m"##<,,,'$$ ZBRU8z VkNEF o>@֒Yн{wrss`9D]Yp!cڴi}4:t`ذa|-x Xx}GuS47}Fbȑdԩ w̜9޽{7|wC5֭[:O?OʤIXp4eC̘6mP_YnVDg233qrr)oo qZlp1vލmKEE˗/gӦMXXXhM4չs3gnbĈe:35GDD o\0NUĺl}7 2ӴFpߌmbbҬm㛼dccCLL &M" }~...ꫬ[u1g~[>>>ҨNZKE=:??wwwʖ=\EE>fb֬YTUU5kaT*wfу{XjoIHHG,^CzjƍGOZ(R]ٳ-ω'RQQҥK[l -oÀ@tߴFwؑ޽{兩)Ŝ;wիW@PPsѻ  ;;;*++t6mjP2ȈB.^ƍл=cmm\.GVs5N>͊+tJܜׯիWu֟2e 0x`ڷoBңGjmŅ˗k}~d2\±cذabZ|yqISSS1a)9bܹ/r9)))᭷{ &իWO3yd<==Yz5ǎۛzƏ?7nԺCT*3gGeƍ׏.]L&#++ÇO?Y.ձcǦRΞ=E.K?x^krQ333nܸKؼysuٶ6.\@V쌉I:̘1I&H Q(..f˖-M5}'k=Vp֝l{{{yK.2k,rrr8u'N $$Xbcc}fdds݃gUVOJ%\~A#@4Yr%cǎeԨQ|_޽;Ϟ=իWڵ+fffR-ϽΜ9C׮] lŧ7nAAA-~ :%lll#l&&qIc.]5>>>̘1;::2{lRkkkYjw ?-- ,--qrr"00P'yd >>}V%''777:wLpp0-IDpvv&&&Cbnn.-+--ɓ5 ggg\\\/ͻcxx8ZISLC\.Օ>}[oP_LuF&Si``@T*Ԋ7%%qIåKannNll,rooo֮]S[kjj* =XL믿T'/&&&oߞ@hѻGR`1[5622bʔ)ذtVb[jQQQ94\.'!!,Ν;TvbȐ!믿ޓX\4ήs/Bu| ZK';NNN|':_MգoRŨVj6'dP;N|w;i8zEii)_5FV# fҤI뭇urrb„ r~'6m$ OQr9dܼyN|I=z4ΝӺFx LLLYjVlddq۷/|gRdСӇ3gǏҥK|RWVV9wtڕ 6lսo>ÇӡC=ʗ_~)=KKK~i֊[[Lut7ʙ3g9s&QQQ<ӤWgL , G.S^^!Z넆"9uT 9eddB'99ZL&[n;@yZXjRT̜9`y._+Rkl&""\>;ؘQFѭ[7Cjj4tKl899씔>'=z`ر{Ijܹu~(% .$66pd2VN3իkmh O?4s=@X &y|׏uZoJ}j5/BKEñx!sT,hZ[Rjݟzf( lll?[[[ԩS2eJԫjK/00˗i~ *57%uoT)rcaddĐ!C/9tR8qDۈ#P(l߾ui%ټRRX+WgiWPPIKKC.3rHi7c:-eee:ɡ!C AVd/J{ҰuǏow^˓~ڵ ֚P5[$..,YϹu6 Sm49I\5_;vYֹsgj5WF]S;9sF:5 G}}њ@CѫW/uZ]oݺW_}%U9R뻶96\hl?>L4ΚݻSTTĻ{'KN\]]133cɒ%,Zo^x˗]xILL{IұcG4>V*[Ȩ/3f0qDOY̘1C>}:cǎe\z/RLsh>İl2Z ѓ0q7|}}$;;#G]D0j5?u jZjh߾TVVJe }iЏp???lll[SV篩[RRRߡCy&h-ɉT!""LÇHjUnJL$I͙E[+555%((TvIII 8::J( BBBPTzkNػw聆CCC++&] \.g޽~_JTBmJs.4cbjj*ϧ$:..'xׯ-s+$sν'kZt)i 5 -X¡I4Cbb!& xk׮%&&Ç5Ud}̤x޼y׽{w6nتFS)/iDlu9}4~!`zdggVйsV\AZi BN iKylSJdPTTҡ՟* \999j~7H޽ĉ8qSNM(5Rj}է$5::@g?MLֵƤIhSm4Qir iii*vBСCуH~'zDΜ9S \ yk w]ZzMKssm(WSG,p^)爍e„ >dggIǎ%jty;SNt]/^wycc߾}z;B+@?~}Cknjd2okRg<4`BCC裏\tR]FbbTu?iqW\I޽yGؼy`^uЯ_?qcjj… j5 ) IDAT,?Ʀۋ΃Sk.FEϞ=- SN WWWJKKYpaO۷/@u^^^XXXpa-Z$63qZtƹ֚)Q3Y@@߿_k/AoBZZ.]OOO"##Q(?(drYPuhVҨZs ._/Ի^}IRԩДf:uAJ~U皞Nyy9FFF$&&Z5j;wfՄUB߾} Oehtuuuo@XXvvvZK.c74̤Mw@ܸq/^OKKCR5;]Ο?O^[Э[7ֺߜmJӉdo[-0qD:gJJJZnnn_QR]RRG}/INN6z}M-[yg={ĉŋ.c̜9d24e}X n nH,fff 0@˗/~z֯__g[pƨBN<Ν;MB\^ee%yyydggd]6(//?g׮]7iPZM^^wfŊLfQOwݝ[ֹ6׮]wތ32¢Ωv׮]˳>Kll,lذAJr9Gޞ)StRZ߄zIUU4$FDD.\ЩT(RGظq#Çgg¢Y5eee8px&L Eرc8qP&OŋuhLYv̤###ݨSÇ;v,XZZrYrrrSSZ7r <|Ze/;wnН &p-Օ &5NBBB籍 AAAzQ>x }ۛӧk1l0VXu^6g[sS'155ERjGѴi022\k,v333&MD߾}9<ߵFڵk|GR❔Djj*̘17x?ڴH=t://z]|s"prrB&[/իW|9uVnZ:jQl%nX{1զҜДsg+`hh FFF6cж\]iH ;7m|~γ>Knn4Zw#9rc=FnHOOG&Ѯ];(++wHBCCIKK///Ӻnnn.ʕ+drU[nϏN:1ayΎ^xAݵٵktؑJV/_̙3quueٜ={,nݺ-\t)K3[fl$Ŝ9sF;X[$<==!%%FyBCCy7OOOd2}Y222J%Gljڴi۷M5PUUUgxaa!&""3grIT*yyywZfٲe̜9ggg^}U222Uj޴iZoζPLbb"k}jj233[ Ƒ/瞓jqwwAYXXH6ڵO?!C =J[PAh!zOs΁*JJJ~z>Z_*8W74֪u>C2Iyuj244 óKj&z*//g޽䄍 nnnr)V\Itt4UUUz{>rEEExxx`mmԲSPP@PP{ѺHtt4k֬!##???quuؘ/KviPVV9ؿ?_|QZ5۵o///d2ZIݻwƆÇ7N:aeeő#GXZZݻQTҔڵ#  8rNt7122;;wڵkٳ}rQT|Wz瓘L&c˖-zKZx~~~₋ ŬZ{ҵkW?u7O.Kw=IR{xx`gg'YjD={\֜999GHHQQQ <ccc6oތ\.֖d W^ҬxyyiRw|-cK9;; <45HctXk:kzV݉6'ZN2 ɡ___ëZّ GGG ήuB ҒbGTBƲnIcbb345YXX; ^mA3Y^^^ JR_HJ^ڨ@CÚZYYI311Jɳ.gϾ/σ8ovWUoqqq̞=BAff&~!'O䩧bȐ!_?q1zh֮]ҥK`X˗/op{kAsegybfyyTmWPz46ɣXTUU5\º4 Z[g[ҍ78z(Ջ׷1Yc޼yIk16cXsєmoݺӑgϞr=&ž={xfŊ]3gHeePOc\t5kִ͓HAhs V((ojJ3,'8%mܸΝ;ӻwonjT3R~;v`ǎZijCCCujլG/))>h UǁAh(JMڮ&y޽ƌ7.\Pk`ظ֩ą?^zajjΝ;[l͵kʢB/33aD B#1vXJ%eee\v B=rK.dɒ6Zƍ)//w'g۶m5zNA3%: ]l,ͭ >^P_w<C{` aYmJWTp IhhHA.`dd#\/.Y`c @iYŽMhemGJň]131jpHA.`Ŗ}mpۡӘ3(s[#ܧD B:0W{`]=٦Gv?9WkOT{T(HNn@Ɂ= Ɏs87p\k\.c@\8Cp̄"NeiQ'yd2w&6̟^GE>߰]ozI{ODW?B.gCP( Z<~]klbdHD7*Gh-6<7e1pXZW焁,߼WwQ$DSRQ:Cp;w6sc鱄<7$_t+MI ޘ2L&$&Fz=_ ?IX|x q25Sk{k yHɚL&cHHB<ZY<IF&1O4G9돳Z aʐKUZfdhʭnܼŪ_k%Ŀ9O] t/ gҲt:ߍ@Z/7+]:C&V2[8esZe2ou vV|};N_7Gf?1'SkM6?Jܢ@k^ K3FJZ+3Gc=ZŢտ7*~W{kꎞOb~;tZŪGep B# ΗNAR?fAr3C$O<;biyx:G<̝Fm]4 JvRQYsؐ-V榌BsJKnk]:㱸N: 1}J@Glgũ -:}q$igҰ`p =7y~rm\ٶ%Mٰ0[3iYx08>IsgsW ;>yGKl?U[HCvn<;,a0qd{￈J/6fAܠKv>e!lzE:/]nku{\.CMޅHA@[KҲY߿ζNLHyE%&]̹/I =>*'琙wMz,- [d`ÿ&n'mƆZW?J/^bDFcLhB|%8^Dz<52%V Յ#KlI as: #qkj,kxjcge@1ӳ,R21tq^u~,FJ>] M`XB^\V6v68]L%7j!'_"9=o>ǔ! ,~+w7Ęe?aϤǯ`ϱNe_OiGVs$9?뢒RO%_o~.=H2;$̱ՏyFaaV,xd2\]Gz$o61?qZ]+*FRT(45ir)%: 4P{8q:-KoJuI14Pt79QwkJJ5._kգGhd2[u[a_Yr͘zjK?JјXj-3#۲Ҳ{Om455iʾZ(<Qk^]Pj#ΤbgM\X:5[6N[<}A112dݨO׻L-ohdʐ**jl-yE8Y3"Hz{ttZN4T xq@{`enҨT*5E7hXYֳ0 B.uD!SSS((( #<}ѧd/h[[[pqqATOVVSjR^u |tY%(j -v:NW߿CzF2g$jRdoزW*pZ_7UUҍ;ۑ1}v]C8qֈ "}4MYvk/>Aψ L )-+јj~ݙcګ _JZՈ W`%h Ae\zҌͱƶ. 6#W{kڹ:4_ h4+8p ݻw`|z`޼yu 9ŋy;v, 7x뭷HIIw߂qm-)=ASm(#%ex{g_t(!~D?#|ZJ\.c[_pcPQAN~!=ƒp&j!D|ZދxtF4mmS<F ~{RYP]z끓{8j$5K4VpF|דQ5JB:n}fpl~ghajӻ Zѣqy{{K/裏6i;°e>}4i氰`޼y$&&Mʺw Ar'gͨnE hј4l05&kEdyE%;s>Ƶtyk)U*# v y߼]Gj{ظw#TOZrw{/GSlgmެ)脱:돳)W< QXHJF.hG]Z3㵿\?v˯L ڿVY׵(kl{N- ԤˁlXב#GqX[[Ӻ-vV#,ѣrZ͹s///.^Xg o׮]C.Ȁ `ʔ)ܹ֒}|}}UR IDAT D.ӭ[7~:}wٹs'JWWWƍGtt4C a֭ .}o(+s駟ɩ]Xr%(JlllܼQ <T3Ͼ vt*c0mX" )dZznDYvʪ*nb$Mht<~NLٗ5RYU8d~1Fat_mEMl6ƝGU^qz$&|M;pÔ1W${O`Tm}N]b(4Vvz+sFFVȝ+\)3FXr|1#eoȣ1yk^!~$<~XGESrOC zI͒ _3f]gVSee%;w)66VgwwwسgVi=y$?x|||1syRRRؽ{7/2տ gc)//g… JEyy9۔߾T999={V_͚P鹼{S^^͛7blڴEb~[umק|ܘ>m?;󱷶`[3oCzF/:T fjMُJ^k~ѡX"XI55[;_d57n"*ėE' лH˾g CXwދxvLjfcx:'BJ۝=5JJظ66y\+*ȐyF">QL;Z뙙+`mnʊ-IrѪJ'#qZ27Fz>e˖lSJN>{XVV 7T(Rx"ϟϏ rJVZ^!ɰ bhhkAKV5^U'q&=g"ڡTObnW0Ϳ+WS\-s{oB|wI0RͿNx~TVU˥JRqrp9:6f툓$;IBf54I{HLG?Aje,LFbŸwS,eIf{OM79w2DI@4ʭI Ix{o~;ttg.]愞 \T*pK4ύCBd ]dJv)CΣg112[8YͿ?֝5 3W$":ScNNڂ_ w:u!=R/^`ajv0(3?{wԙA6el;n.U۱cuh6v^SӎK7kuֺbeYA ޜ!~?*47O~wW n-"`ce='SIb5K͟Ex8#y8kyE冑C| HӪa.fB?>^gҰnm{d`skR=Xp![oݳdnW^Eqq1\]] dgw|f|z=N<bĈpss/23twoltGT*ĉAA" &&;vhsL&j% ͛B&lٲEԟ=ӧs:zׯ___+V8q7 WhNC~w*&}"濏uo!J kq.Ƣo@Na |97K󓅱MZ-_A߶2 056A hM?ovA3`y O^~1xh\]}v==6!ykiw'wyO ~LX'.FSo| LJ*wkA#J<^Ǚdǔ7m"s&4OnU]GO76ѤbK+>ECJhu:=uX yo!o=1UX0Sb1gBJ*qN:O yC} bmX# }+ƌ„V㧛JRZz_b_PWT ƅB !&9{ސcёG\<3l0|j=zCQQmK)~^n2(ݡ鑙W&Sx)671R˪Lnr*=` i Px16Vp;kު@C3n W*kRBzBw7DEB۽|k>v'E`0(ln f8-iee%|.}a[A,8x`qÆ ;yPn'|M6aӦM׿%$ϵBVVV‚h4 |pzכ>>>Bi>>>XjDK.᭷7pqqA||<֬YYfhLD$B2j+Ni]}ΥN W՜' qF!_@N/)QxW瞌:6}M^zo\z=gS46RfբK sJq Q3<|h6tZ趄؊*gD%7~]|MQeܼyB@(5Y6в]9bccKKK~aȐ!&{G%''|JJJ:<&** ryJᚚX˅:혘$&&ƍqT*;MR B,Z˵Ї2Y sX[[/,/^āDFvv6kbɒ%ѣG3&O!bNef2Ŀ^{}㧉(.kX.g\'(<RK9zKWnхN-;v /d2"""7^Ǒ#GZ]c}Q]V(hҥKLСCc?= ҿx{{*h۷aÆhšeffbʕBΔZ pq;?Wܮf2tz|y7>==ߩ-V.55o9;_lllRpܹvcظq#P(0f۷.w7m$thiĉ4i&fffpskEG;`8::8666¿nDD;3! *i Ľ'pNp+,ķ{;.9s:XYݐZ+WZOݭh#$$L~ׯvQ,$&& tyy9mۆsB"_K/dr溥كV߿_HcbbaÆV_x{{ IRc̘1pvn^TtAɬ}Y޽Ƌh*ٳo .\@NN///7N֮DdŌ\ 'dx x EԩSh4BiFCCܖ3mfI K>r_[v܉qO<;wy/<=M&@s Z 0x`/G||.]ڵkMS*P*&ϝ:uUirGy={h^ ٲ#JK'O4"ݹs'O~>}4Z]7zhM%jZؔEP 441444믿~~gVyt4v,l/<:u /_Fqm>C /kkkXQQ,Ya|G㯽/͛!ggg7LRDBBܹDj 8蛃_ . 44gƊ+z;nȄ3g vjU1eHRlܸ$''FbڴiǠA!u+."$""5Ѐ~222z:BSS~Gqz9.077ٳgQWW꼝C{h;v :ph""hNs*** B#Gv8D݊5DD₈(JڢgϞEjj*N6kb1|p(J{se2ƌ8;;ׯ_ÇhrT*Epp0FGGG( TUU!??Ν3Y*H0rH <Duu5rrro>3#Gvڅ۷o#88AAA077ǵkP[[ m9k,HRlݺhv9|p\.t:._ltn„ pppfT'O1}t#GLC`` t:v ^Ꚙ 0gΜAFF<<<0zhbϞ=&={6R)vFcZx{{c֭D\\J%˗/ܹsmP( RiiiFL|2GyGmZ h""F @Pǂ0qD@R!99U-0{l<䓐H$qJ'JR\±?O>Fclmm{ dM" <<G}d4K,AHH0F"GĉrJ\rhJرcXDFF r91eh4$'',} ´ipU#hR LVccc1h 7Foߎ@M6ԩS HOOիWK$̛78tSLAFFF SO=L(6 >(s4i>Ν;プ_~.66&c10$-_3_&DD""!!:}N\922U{{{ukI&!-- k׮Evv6lllӧ#66uuu/> Μ9@XX&M3oܸT*N?QobMDA2 sYƨ… x"/^Vc0a45574JJJJpE|L):*ʄcj)))Xbo`nn@z|F[?h!%%~p֭ """0i$lݺ/iͭfΜV % hp-666V 4w<]s ̛7prr2zCBB`nn{bܸqĆ KCpס;:t}-Dyy9rssQPPKbܸqؽ{71gφBÇj*xuu5ӑŋ̚t:d2({~B"B" ;[tm3f ̐drVU"v7^o<TUUa˖-ND"c(veak޽dt1bctnȑıcnjO[ u6+//6^ 6:^;v 33}1| tQQzǏ#++ 2(^\XhZ$&&罶tB9͉fL:pHGgΜ888 8{,7ī8ػ@󢱀VyRVV!<<蜡|=O[,,,w3 ;Z&1b^2ᚖBAA˥KJ(**2[G^GJK"2$ǺuzlGԠpvvwWZZѣG#<<޽{^@soc,kZmLJJBpp0dÆ j z"S 3]]|),XC\.GCCF\Ǐh޹Cdd$+t:aTV1CM~W߳_^I0cMDA haH:3h7nDbb"F0 >Gpp0>zAA*bڵvXlyQQQ!C_~(//Gtt4R)otmOca1UqmBR!00ϟGDDt:N:ҥKPTxG&t{1$-e{o,E&A.ED+&DDdQ6tiP*BEw2nTUU%6hjj‰'p d2= .DTTP'Z 777 IСC:u*"##? &&F^ROc!7,&dT*#55#F@zz:***k;J(`Ȑ!(,,D^^ѽ ܯ2÷+- ݅Yt ^7jGc 4Q N?~Qh1$ RmO0?>ZIII:OOOܵk4>>8uĿ'⹛F{Q:hhh@pp0T* Pz#$$2dFII R ÌX&?VVV^GZZp\V rbx Ž~Q@uPnn+1:鉉'o>zRpHRkQFaҤIhll͛Y[[f;Or0 IDATh燅 _ j5._,{?-;;znnn]h4poTaP__gO?4EEE\.ǜ9saTcǶ˕Ahh(,X`t%ЧO;v 9999N'ǨQgee%ulۚ{u zذHo~~~prr‡~,ܼynnn4hPX~=-[@^7 `rbݺuDee%J%j_ z˗C!33jApp0쐒bܔ?{3* /^Dqq1 <==1l0+FJJJBPP"##Q\\fwZ ~~~HMMooo5GAtt4QXXhrN 6`ɒ%8q" +++ 4tF;\2vXT*A.# (((0k@$$$`ʔ)Ά|}}af~ahїuD&DD"ܺu ˖-~; w^TTT`Μ9&ۧ?:-ZA 3UUUXnj5}6o>jL>]j/,^h4عs'm#G ##G@@swӧſpw9}5xv L6 QQQ]Jϟ?FB@CbmggCA xzzB#33Wȑ#v +++3F짮IIIMvjxL0AxNCJJ ֯_^z.}JN/zU|L"_xKR!(,,VŌ30sLl۶M xzzBt]T hllKKK8::(--P7Dggg(++֖qbT#hZ̘1#>}:Ə?)))F碣 /Z8VTT%Kt}*++1[XZZbժUhjj455vHbMDDd3ƍFtsJ sV1p@oT__tDEEaرؽ{woD &DDD&̜9fffصkW)S@*bƍطo 997¤`۶mqܹsC QQQhhhO?=|\pvvvp.077ٳgQWW꼝CrQ@\\\/GB"&DDDw;v#rh4 2=`XMD ...R- pYbԩ())i&ÇR w=W&a̘13 pu>|&HRcĈpttB@UUq9dee#H0rH <Duu5rrro>3#Gvڅ۷o#88AAA077ǵkP[[ m9k,HRlݺhv9|p\.t:._ltn„ ppp%`ɨo߾ӧO9rd;???BaΝpdddGFii)c2ٳgC*bǎh4F7n KKKAT|2Ν; P*߿?JJJn'x8x H 0R8}( <j5~g=zc sP(0m4ܸqppp@tt4<<h ,YaD">'bʕrJc"==r$''cʔ)h4HNN6YiӦիFG<%J2 bРAϿoo߾56mjuS"44WH$7oq!+L6觞z 2 Qmx- Ip.00&MÇ~z|||/zƢd, * F`L2?!J`Y!C ..qqqLP.cʔ)HKKCuu5-[>}瓒 J___&d 4HHHNw}'O7n2gaVnӦM|2Z-|}}1k,D>}`ժUz*z=y̙3xw/%} … |z(//oz ~~~8z(mZ gggL<cǎҥK`rW^?t455!77YYYu'Nk(hܯxڣT*uV{=XYYaŊupǎàA*Å###[%>>>G^^n޼nbL4 iiiXv-accpL>×_~i4o߾xP(p|(((0iҤv)ˡRuV\ppuu7xRX~=rrr ٳgcС6m]/?G^^lllg7nر~5@uL&ܹsk֬1*Ep.^ŋ#66X777L0MMMx7\x|0SlNë2ZFJJ VXo#00zQb}- ::~~~HII~*/**ºu`ccL4 [nm5_Z%pA̜9ѭhKKKAɓ=O[lllխ0#BkN8yNNNFsHHͱw^7ذat:pȑ#ug}&| Q^^\`ҥ7nvލ"aٳP(paZJ8^]]ta>wŊtsVV1 _јݻwCa5jQݕϡ[5kִQ=`xOp!QW-t:0f!))bmmmDnz(y6–-[4'DPq/ǏڵyCcw۽{w0b́ǎ3Jggx|>"3TX77N툚TWWBUV1zh#<<zػw/;& 3mZj3)) e;;; 6L6xL1̀ԩSX` \1r\X{ꫯgS5Z8:: 5=Y]exܽ 4Q:6?Ό(sss0-ƍ#G",, Ç?O>zuuutJXvm:[pyTTT`Ȑ!ׯ T]bXhee{ܾ}PT NSNh.tT*y 9|^ ZLJ ]exZJnL:Pl FQQ|}}T*rd(ݨ2KlԄ'Nĉd=z4.\(Njnnn?VšC0uTDFF~@LLнC2ՅcPTFjj*FtTTT;v * QQQ!CPXX<{%_Yݖń,+C ~XMDA!C߿ScǏoԷV D_&LVERRS8w5-d2Y⼗$z<N:e2xf^G8}4 JBѪӧQ__p@&,0Wssskf^|}}M^4ZFUUr=|2 20&"\!zWct'N49v߾}(---^V_?6E*^k5 &MBcc#6oltΐ...`4ۙF???,\`Vqe5znpssRF x z={vvvx.**BMM r9̙cKR;_ BCC`,-->}رct:5`||sǘ1cRpECPÆ +b/X$!22mv'xZEqq1N+99Fjj*Z]sDGG&7ptذa,Y'"00 ANfK8cBR!-- rGAA]ߏp"!!SLAvv6 3I#:9C& ;Jfggw~H[naٲe~Pa1^cc#݋ ̙3d_ǢE0h aƹ ֭ZƻZӧ cZ-_%ؾ};1x`~;wĶmی6 ̟?ݹsOBאV'ۉ'0m4DEEu)><4  E  ]:^/l ^L^#Glwu͘1cMFꐔd V?ψDŽ t:`x饗fw簣`cc/5&quu!V\sـ׿xT*$j1cfΜm۶ baaOOOTTT+J>} 77cii GGGؠj&H쌾}eee2NWFmm-ϟߥM"ָyЕ- .رc;vsE%ΰA^^^w=+@ll,6n܈?u}Gz9g:Iӵ2k;ws+\^X'^Z6* M=Oqq1._ mz=;=7nؒaAモ+öhllÇ"q!Q7Gxx84Q7z8m޼z=;>SN\.?`n 4H3gD``ГW&o6,--e˖v7CVV9gggv8t9D\\}~!XAD$#f̘!\SSBLN;wbݽ!u͛7=3 ~~|r׏[xS@PZZe˖!66JNNNCnn.=QyyC_ V>ƍ_&DD"m8A ;xsH5DDDDD"0&"""" 4LD`MDDDD$h"""""@H&DDDDD"0&"""" 4LD`MDDDD$h"""""@H&DDDDD"0&"""" 4LD`MDDDD$h"""""@H&DDDDD"0&"""" 4LD0t˻^}h|Fw>uuu}d2pΝ.߫^oLN-||\!"@݃-χgܫTWWֶ)))s7D}[@Pt>Z!`MDtn¬Y^[f1R)t:]7D}?D"D"ybͳݑvWFf}/_~ߟeiig=8MD$ /{ :9f͚5~;@=kܹ7n4 ϟ;wvHDDg:A*bŢf2 R)BD|&"N777>>x. @ll,*++}nTT-Zd4sI&Xv-Zm ǔ)Sj@KR,YFt:aaLL =A bXx1^~euxe0|pbӦM~:pL2o g۷/}( 9s= ၰ0L4gT*,]Xf0 UV ǫ<,^3e2{9zGFFpٳ())?̘1wFCCCg_1a\xQ8^UUNߗq!Qz^qqqи'njrg"%%q\XhZ$&&wffCEq5 77׮]ߡ?G[ ]у 4Q7(++A/Z .<}4C8T";ymǐwukǏz)oÓO>夜A"nr!!44ǧ~浶Ecc#ڼ͛WWWa`ii cCCCz^[駟1qDsEaa!=~C 4Q7Zv-:uJA[CCz=amm up`oo/:>s._kMxgٳDpp01k,DGG㭷jNDbMDԍ*++cҥXp!M^W__28::br ±"ͳr\B?}p+Wʕ+/KƍÎ;z,"h"nvq?~vvvX`$ ]5MfL&hsZ*vՙ Nwww wa`W\rߚ>hܹsM>wmvȰ4 +WĚ5k:UK$x{{2 o6CAXX ѹٳgcƍx饗:QW>}6֮]7x|lٲgɓk׮A"`ׯ ~Vcߏp"!!SLAvv6 3z߲e >.]BNN舠 dffbʕ˜hax̘1mh"xzz"777oDyy9d2 O>-`ggaL&ɓannlٲjuh"ٳ8x ڼCVV~ߢМOoMjxL0NCJJ ֯_^zhoF˗c„ OVqqq[?x@j׮]?&j4i^~eY p- >}F`sdua_҉'ڠ:@k @@3TTTDd2ԩS'EEE)**J6MK.ժU.W{k׮K:ɓ5f{Zbť hp89s諯$=m\r @@ r8.I VHHHWhi vQ;wN޽:l6hٲer8ۍ3FVU6mRMMFIRe6%IUUUШQԽ{wn+33S۶m`ս{w:r6oެ3g48l6kPNN﯄uU'|R# hhauuuknOڻwo6۷/á+V(66V&Mr폋S\\$c8p&Nۻ5jӵ`5h׿͘1CAAAm{Wg6~~~4iTQQi;v?(++) hha PMM=m۶MÆ f,???JӓO>GJNNի~zIRmmFGGkǎO'٬dM2E Є ճ>+á>@۶mSyyu]w)22R5k(??_ы/RO> \ :tP.]\zT͛7O)))z7]n+00P}u{`n+7759رC|mڴAϻCbb'á۫\>uf! #FЈ#lw8JKKӻᆱ*}֭Ӵidٴ{nv&}niN$5$""B胍ӧp߿-..$ÏCAf8q%Շ'N@S^^67nw߭_ΝT?ViiiUiI:y;~ԹsgC~ ۷ow[)˵sN 'N\s8y^SS1]}.\xIuX,SiiUUUuI5c %%%m_p>3[ڵӞ={TWWFU^9"## UWWu9U o߾o+I*//U[[U5O@@Ə+""cǎiZnڠBw/LmڴIٳrsssJ6P_,^yy{{kZlY[\p .900PSFFFWe\rrf̘K*++ӧO+00PڵN;wyנAT\\y橦m.]\嵺n{G^^=] LfY ҙ3gyf;V6 УG?,ɤtPaaaも6]dd$СC ³$eggu.= 3<{6_\\9sK.ڲeKTRRR-[(--McǎsεuyMҫW/=CE]0\>}Z;wl.bHR׹N۶mk͒x{ў={ں i76oެHڵkPە֮]K$4= T\\\I'f]t؀lfSTTv"egg7i:ժdS;vnoVokLLbccUYYߍ7ިxEEEK֞={***J˗/W4bEFF*$$Dv] Wxx=zHw{ǵ?''5ƹ>Sݮ]; :T={T.]TXX\i Պ+\?$ȑ#-[(//ϵ=22R6M;vuQ裏T]]-ɤnAZ ӧUPP5k֨| wr-pGq{xr„ X,ZzN<٠v///(::Z*//ÇcǎFE0aΜ9 6(""BC QDDf=-[W344Cjj$i˖-)Ҝ:11QضgϞ5kۃnqqqJMMUii;tP?lvm5f̘DI_hMa6zn&?ڵk[o5x-!!AcƌQXXS\\Ft-XuCCC5i$q]vU׮]]rhq~aٳg+44#FЭު9˖- }nk׮4iPߩSb o^ ,PXXW7p)55Un }Q.]76_WWgxO}v5n8I=3NOOݻe2d|zuكLOr|sOΝ ~K}4h09|#''뫔EEE)<< cG=F8:+k YYY[~ampKxHk gϞԼ{pyyy'ԀTRRui*..VUU ;EzqJZ sΊSmm^yOu릮]fiҥҥ\~5mTT%w:uBBB4}t͞=؍oT\\uM999s^l-Rs*=Csn7+;]筹.v==W=&6lL&v}0\HΝ8qBeeetu8lWll=q=8֥KW2.]SoWJJ'ɤ޽{+((Hv]oFjo߾fxwԿW_՗_~EEE)22mQ:y:u?Oԙ3g={^W^j̘1֭ s^AkJLL̙35qD>|X5ݻG5}fs}9rqbb())5絷nvjСZthm@@1BR}rS:{ ڹVsUQQ_~ڷofΜX⢢"׿֭[U[[X1B=zի裏6X.).]ٳgkÆ *++bQ~t7o߾Zk`ƍ+WjΜ9WppnfW|qr /e˖5_ϟ: :z, Tf/ѢE]י3g4w\egg]vJNNȑ#UVktuVV.\B[رt=^z%*))QddRSSJ-YD/bRuuꔙo׮]r8rP[[:6??x(.Shh(s&޷N籟jC:z奈}&IVUECOVIIIa6fժ@ܹs /=sرVkl{KX,ܹJKKuICf4;:mۛxsωjUEE*((hџkQ]$cTWrub"nWWW`p8tĉf}=^UUexEĶ{b3NTVVPޜo!ZFTVV6/o"??r4C8L&nI_|R 1BΝӞ={TQQ!~itڵp"@9wyVak쌌 -^-p#@9> xuY|9ݻw\|WگjwYĺkʕZre[$D!k~ꐖYy߁0 x$䦇Ucnj1䦇%};y`g+"AUMQ9{R*yUmEմsa*N}^XuYW-V"pM3u3_UIENDB`fwupd-1.9.16/docs/device-emulation.md000066400000000000000000000145041460375044200174520ustar00rootroot00000000000000--- title: Device Emulation --- ## Introduction Using device-tests, fwupd can prevent regressions by updating and downgrading firmware on real hardware. However, much past a few dozen devices this does not scale, either by time, or because real devices need plugging in and out. We can unit test the plugin internals, but this does not actually test devices being attached, removed, and being updated. By recording the backend devices we can build a "history" in GUsb of what control, interrupt and bulk transfers were sent to, and received from the device. By dumping these we can "replay" the update without the physical hardware connected. There are some problems that make this emulation slightly harder than the naive implementation: * Devices are sometimes detached into a different "bootloader" device with a new VID:PID * Devices might be "composite" and actually be multiple logical devices in one physical device * Devices might not be 100% deterimistic, e.g. queries might be processed out-of-order For people to generate and consume emulated devices, we do need to make the process easy to understand, and also easy to use. Some key points that we think are important, is the ability to: * Dump the device of an unmodified running daemon. * Filter to multiple or single devices, to avoid storing data for unrelated parts of the system. * Load an emulated device into an unmodified running daemon. Because we do not want to modify the daemon, we think it makes sense to *load* and *save* emulation state over D-Bus. Each phase can be controlled, which makes it easy to view, and edit, the recorded emulation data. For instance, calling `fwupdmgr emulation-tag` would ask the end user to choose a device to start *recording* so that subsequent re-plugs are available to save. As the device state may not be persistent we save the device-should-be-recorded metadata in the pending database like we would do for a successful firmware update. To demo this, something like this could be done: # connect ColorHug2 fwupdmgr modify-config AllowEmulation true fwupdmgr emulation-tag b0a78eb71f4eeea7df8fb114522556ba8ce22074 # or, using the GUID # fwupdmgr emulation-tag 2082b5e0-7a64-478a-b1b2-e3404fab6dad # remove and re-insert ColorHug2 fwupdmgr get-devices --filter emulation-tag fwupdmgr download https://fwupd.org/downloads/170f2c19f17b7819644d3fcc7617621cc3350a04-hughski-colorhug2-2.0.6.cab fwupdmgr install e5* --allow-reinstall fwupdmgr emulation-save colorhug.zip # remove ColorHug2 fwupdmgr emulation-load colorhug.zip fwupdmgr get-devices --filter emulated fwupdmgr install e5* --allow-reinstall fwupdmgr modify-config AllowEmulation false ## Device Tests The `emulation-url` string parameter can be specified in the `steps` section of a specific device test. This causes the front end to load the emulation data before running the specific step. Device tests without emulation data will be skipped. For example: fwupdmgr device-emulate ../data/device-tests/hughski-colorhug2.json Decompressing… [***************************************] Waiting… [***************************************] Hughski ColorHug2: OK! Decompressing… [***************************************] Waiting… [***************************************] Hughski ColorHug2: OK! ## Pcap file conversion Emulation can also be used during the development phase of the plugin if the hardware is not available or to reduce the number of write cycles on the device and the testing time. But this requires a way to create the emulation file while fwupd does not yet support the hardware. This can be done by converting a pcap file generated using WireShark, on Windows or Linux, while performing the device firmware update process using the official update program. The saved pcap file should contain all the events of the device plugin before the update starts until it ends. Since the pcap file contains the firmware uploaded to the device, it is closely related to the firmware file and both the pcap and firmware files must be provided to the plugin developer. ### Record USB events to pcap file #### Linux setup Check if you belong to the wireshark group with: groups $USER To add yourself to the wireshark group, run the below command, then logout and login: sudo usermod -a -G wireshark $USER Depending on the distribution used, you may have to load the USBmon kernel module using: sudo modprobe usbmon You may also need to adjust the permissions with which usbmon instances are created: echo 'SUBSYSTEM=="usbmon", GROUP="wireshark", MODE="640"' | sudo tee /etc/udev/rules.d/50-accessible-usbmon.rules sudo udevadm control --reload-rules sudo udevadm trigger If USBmon is builtin, you may need to reboot. ### WireShark record of USB events Start WireShark and open *Capture→Options…* menu (or Ctrl+K). This brings up the *Capture Interfaces* window then select the USB interface to record packets: * USBPcap[x] on Windows, * usbmon[x] on Linux, usbmon0 interface can be used to capture packets on all buses. When recording firmware update that uses big packets, it may be relevant to increase the packet snaplen by double clicking on the value in the *Snaplen* column of the selected interface and changing the value. Click on the *Start* button then plugin the device to record and start the firmware update process. Once done, save the USB packets from WireShark to a pcap file. ### Convert pcap file to emulation file To convert this file, the `contrib/pcap2emulation.py` tool is used to generate a json file for each series of USB events between the "GET DESCRIPTOR DEVICE" events, limited to a set of VendorIDs (and if necessary ProductIDs). Depending on the device there should be 2 (setup.json and reload.json) or 3 (setup.json, install.json and reload.json) phase files, which are zipped in the emulation file. For example: # convert the pcap file for the CalDigit dock with VendorID and ProductID 0451:ace1 contrib/pcap2emulation.py CalDigit.pcapng /tmp/caldigit 0451:ace1 # this will generate /tmp/caldigit.zip # the new emulation file can be used for emulation fwupdmgr modify-config AllowEmulation true fwupdmgr emulation-load /tmp/caldigit.zip fwupdmgr get-devices --filter emulated fwupdmgr modify-config AllowEmulation false fwupd-1.9.16/docs/ds20.md000066400000000000000000000202461460375044200147700ustar00rootroot00000000000000--- title: BOS DS20 Specification --- ## Introduction When `fwupd` starts it enumerates all PCI and USB hardware and generates *Instance IDs* based on the reported vendor and product so that it can match the device to a plugin. The plugin knows how to communicate with the device (for instance using vendor-specific USB control transfers) and also knows how to parse the firmware and deliver it to the device. Before a vendor can ship a firmware on the LVFS to a machine using Linux (or ChromeOS) the fwupd package often must be updated so that it knows what plugin to use for that specific device. At this point other overridden [quirk data](https://fwupd.github.io/libfwupdplugin/class.Quirks.html) can also be set. For instance, the icon, long summary or even per-plugin flags that change device behavior. For example `colorhug.quirk`: [USB\VID_273F&PID_1001] Plugin = colorhug Flags = self-recovery Icon = colorimeter-colorhug Updating the fwupd binary package might take anywhere from a few weeks to several years due to various Linux distribution policies. Clearly this isn't good when the majority of firmware updates are distributed to address security issues. One suggestion would be to put this quirk information into the existing update metadata provided by the configured remote, but this has several problems: * The daemon needs to *enumerate* hardware that does not have updates on the main LVFS remote. * A *lot* of machines using fwupd have never connected to the internet and we still want to enumerate hardware for audit and verification purposes. * Re-enumerating all physical hardware (to get the latest quirks) when updating the metadata is not straightforward. * The VID/PID is not necessarily the information that the plugin matches to assign the firmware stream, and so this would have to be a separate facet of metadata -- which may have to include quirks too. Matching devices to plugins should be thought of as *orthogonal* to matching firmware to devices. To simplify deployment it should be possible to allow the device itself to specify the plugin to use and optionally additional quirk data. ## Specification Devices that implement a fwupd BOS DS20 descriptor can be updated without always needing to update the runtime version of fwupd for updated quirk entries, assuming the device is updatable by the existing vendor plugin. USB devices can create a new platform device capability BOS *“Binary Object Store”* descriptor with `bDevCapabilityType=0x05` and a fwupd-specific UUID, specifically `010aec63-f574-52cd-9dda-2852550d94f0`. This UUID is generated type-5 SHA-1 hash of the word `fwupd` using a DNS namespace. Using this custom UUID will ensure that Microsoft Windows does not try to parse the capability descriptor as a *Microsoft OS 2.0 descriptor set*. ## Implementation Create a BOS DS20 descriptor such as: 1C 10 05 00 63 ec 0a 01 74 f5 cd 52 9d da 28 52 55 0d 94 f0 0e 09 01 00 20 00 2a 00 ├┘ ├┘ ├┘ ├┘ └─────────────┬───────────────────────────────┘ └─┬───────┘ └─┬─┘ ├┘ ├┘ │ │ │ │ └─PlatformCapabilityUUID │ wLength─┘ │ │ │ │ │ └─bReserved dwVersion─┘ bVendorCode─┘ │ │ │ └────bDevCapability bAltEnumCmd────┘ │ └───────bDescriptorType └──────────bLength The `dwVersion` encoded here is fwupd `1.9.14`, which is also the first version that with working BOS DS20 support. The BOS descriptors are sorted by the requester and can appear in any order. The descriptor with the highest `dwVersion` that is not newer than the current running daemon version is used. This means that `dwVersion` is effectively the minimum version of fwupd that should read the descriptor. This allows devices to set different quirks depending on the fwupd version, although in practice this should not be required. A suitable bVendorCode should be chosen that is not used for existing device operation. * The `dwVersion` parameter **must** be larger than `0x0001090e`, i.e. 1.9.14. * The `bAltEnumCmd` parameter **must** be zero. * The `PlatformCapabilityUUID` **must** be `010aec63-f574-52cd-9dda-2852550d94f0`. Then allow the device to reply to a USB control request with the following parameters: transfer-direction: device-to-host request-type: vendor recipient: device bRequest: {value of bVendorCode in the BOS descriptor} wValue: 0x00 wIndex: 0x07 wLength: {value of wLength in the BOS descriptor} The device should return something like: 50 6c 75 67 69 6e 3d 64 66 75 0a 49 63 6f 6e 3d 63 6f 6d 70 75 74 65 72 0a 00 00 00 00 00 00 00 ...which is the UTF-8 quirk data, e.g. Plugin=dfu Icon=computer The UTF-8 quirk data must **not** contain Windows *CRLF-style* line endings. ## Workflow To generate the fwupd DS20 descriptor save a file such as `fw-ds20.builder.xml`: 42 32 Then run `fwupdtool firmware-build fw-ds20.builder.xml fw-ds20.bin`. To generate the control transfer response, save a file such as `fw-ds20.quirk`: [USB\VID_273F&PID_1004] Plugin = dfu Icon = computer Then run `contrib/generate-ds20.py fw-ds20.quirk --bufsz 32`. The maximum buffer size is typically hardcoded for the device and may be specified in a microcontroller datasheet. ## Prior Art ### Hardcoded Class & Subclass The fwupd project already support two plugins that use the USB class code, rather than the exact instance ID with the VID/PID. For example, this DFU entry means *“match any USB device with class 0xFE (application specific) and subclass 0x01”*: [USB\CLASS_FE&SUBCLASS_01] Plugin = dfu These kind of devices do not need any device to plugin mapping (although, they still might need a quirk if they are non-complaint in some way, for example needing `Flags = detach-for-attach`) – but in the most cases they just work. The same can be done for Fastboot devices, matching class `0xFF` (vendor specific), subclass `0x42` and protocol `0x03`, although there is the same caveat for non-compliant devices that need quirk entries like `FastbootOperationDelay = 250`: [USB\CLASS_FF&SUBCLASS_42&PROT_03] Plugin = fastboot #### Microsoft OS Descriptors 1.0 The [Microsoft OS Descriptors 1.0 Specification](https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-os-1-0-descriptors-specification) defines a device-specific variable-length metadata block of `CompatibleID`:`SubCompatibleID` on a string index of `0xEE` where the `CompatibleID` and `SubCompatibleID` are also both hardcoded at 8 bytes. Using `FWUPDPLU` or `FWUPDFLA` as the `CompatibleID` would be acceptable, but we could not fit the plugin name (e.g. `logitech-bulkcontroller`) or the GUID (16 bytes) in an 8 byte `SubCompatibleID`. Some non-compliant devices also hang and stop working when probing this specific string index. #### Microsoft OS Descriptors 2.0 The [Microsoft OS Descriptors 2.0 Specification](https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-os-2-0-descriptors-specification) is more useful as it defines a new device capability that can return a variable length vendor-defined section of data, using a UUID as a key. Using the `BOS` *“Binary Object Store”* descriptor is only available for devices using USB specification version 2.1 and newer. The BOS descriptor is used for Wireless USB details, USB 2.0 extensions, SuperSpeed USB connection details and a Container ID. The BOS descriptor does give the OS the ability to parse the platform capability, which is `bDevCapabilityType=0x05`. For UUID `D8DD60DF-4589-4CC7-9CD2-659D9E648A9F` this is identified as a structured blob of data Microsoft Windows uses for the suspend mode of the device. Creating a new `bDevCapabilityType` would allow vendors to store a binary blob (e.g. `Plugin=foobarbaz\nFlags=QuirkValueHere\n`) but that would be out-of-specification and difficult to implement. fwupd-1.9.16/docs/env.md000066400000000000000000000061351460375044200150110ustar00rootroot00000000000000--- title: Environment Variables --- When running fwupd reads some variables from your environment and changes some behavior. This might be useful for debugging, or to make fwupd run somewhere with a non-standard filesystem layout. ## fwupdmgr and fwupdtool * `DISABLE_SSL_STRICT` disables strict SSL certificate checking, which may make downloading files work when using some antisocial corporate firewalls. * `FWUPD_CURL_VERBOSE` shows more information when downloading files * `FWUPD_SUPPORTED` overrides the `-Dsupported_build` meson option at runtime * `FWUPD_VERBOSE` is set when running `--verbose` * `FWUPD_XMLB_VERBOSE` can be set to show Xmlb silo regeneration and quirk matches * `FWUPD_DBUS_SOCKET` is used to set the socket filename if running without a dbus-daemon * `FWUPD_PROFILE` can be used to set the profile traceback threshold value in ms * `FWUPD_FUZZER_RUNNING` if the firmware format is being fuzzed * `FWUPD_POLKIT_NOCHECK` if we should not check for polkit policies to be installed * standard glibc variables like `LANG` are also honored for CLI tools that are translated * libcurl respects the session proxy, e.g. `http_proxy`, `all_proxy`, `sftp_proxy` and `no_proxy` ## daemon * `FWUPD_MACHINE_KIND` can be used to override the detected machine type, e.g. `physical`, `virtual`, or `container` * `FWUPD_HOST_EMULATE` can be used to load test data from `/usr/share/fwupd/host-emulate.d`, e.g. `thinkpad-p1-no-iommu.json.gz` ## Self Tests * `CI_NETWORK` if CI is running with network access * `TPM_SERVER_RUNNING` if an emulated TPM is running Other variables, include: * `FWUPD_DELL_FAKE_SMBIOS` if set, use fake SMBIOS information for tests * `FWUPD_FORCE_TPM2` ignores a TPM 1.2 device detected in the TPM self tests * `FWUPD_REDFISH_SELF_TEST` if set, do destructive tests on the actual device BMC * `FWUPD_REDFISH_SMBIOS_DATA` use this filename to emulate a specific SMBIOS blob * `FWUPD_SOLOKEY_EMULATE` emulates a fake device for testing * `FWUPD_SUPERIO_DISABLE_MIRROR` disables the e-flash fixup to get byte-accurate hardware dumps * `FWUPD_SUPERIO_RECOVER` allow recovery of a corrupted SuperIO by hardcoding the device size * `FWUPD_UEFI_CAPSULE_RECREATE_COD_DATA` if set, write the files in the example COD tree in srcdir * `FWUPD_UEFI_TEST` used by the UEFI plugins to disable specific sanity checks during self tests ## File system overrides These are not fully documented here, see for details. * `CACHE_DIRECTORY` * `CONFIGURATION_DIRECTORY` * `FWUPD_ACPITABLESDIR` * `FWUPD_DATADIR` * `FWUPD_DATADIR_QUIRKS` * `FWUPD_EFIAPPDIR` * `FWUPD_FIRMWARESEARCH` * `FWUPD_HOSTDIR` looks for host OS `os-release` in this sysroot, default is / * `FWUPD_LIBDIR_PKG` * `FWUPD_LOCALSTATEDIR` * `FWUPD_LOCALSTATEDIR_QUIRKS` * `FWUPD_OFFLINE_TRIGGER` * `FWUPD_PROCFS` * `FWUPD_SYSCONFDIR` * `FWUPD_SYSFSDMIDIR` * `FWUPD_SYSFSDRIVERDIR` * `FWUPD_SYSFSFWATTRIBDIR` * `FWUPD_SYSFSFWDIR` * `FWUPD_SYSFSSECURITYDIR` * `FWUPD_SYSFSTPMDIR` * `FWUPD_LOCKDIR` * `FWUPD_UEFI_ESP_PATH` * `HOME` * `RUNTIME_DIRECTORY` * `SNAP` * `SNAP_USER_DATA` * `STATE_DIRECTORY` fwupd-1.9.16/docs/fwupd-remotes.d.md000066400000000000000000000106771460375044200172520ustar00rootroot00000000000000--- title: fwupd remote file format --- % fwupd-remotes.d(5) {{PACKAGE_VERSION}} | Remote File Format ## NAME **fwupd-remotes.d** — remotes used for the fwupd daemon. ## SYNOPSIS The `{{SYSCONFDIR}}/fwupd/remotes.d` and `{{LOCALSTATEDIR}}/fwupd/remotes.d` directories are used to read information about remote metadata sources. The complete description of the file format and possible parameters are documented here for reference purposes. ## FILE FORMAT The file consists of a multiple sections with optional parameters. Parameters are of the form: ```text [section] key = value ``` The file is line-based, each newline-terminated line represents either a comment, a section name or a parameter. Section and parameter names are case sensitive. Only the first equals sign in a parameter is significant. Whitespace before or after the first equals sign is discarded as is leading and trailing whitespace in a parameter value. Internal whitespace within a parameter value is retained. Any line beginning with a hash (`#`) character is ignored, as are lines containing only whitespace. The values following the equals sign in parameters are all either a string (no quotes needed), unsigned integers, or a boolean, which may be given as **true** or **false**. Case is not significant in boolean values, but is preserved in string values. ## REMOTE PARAMETERS The `[fwupd Remote]` section can contain the following parameters: **Enabled=false** If the remote should be considered when finding releases for devices. Only enabled remotes are refreshed when using `fwupdmgr refresh` and when considering what updates are available for each device. This value can be modified using `fwupdmgr enable-remote`. **Title=** The single line description to show in any UI tools. **Keyring=jcat** The signing scheme to use when downloading and verifying the metadata. The options are `jcat`, `gpg`, `pkcs`, and `none`. **NOTE:** Using `Keyring=none` is only designed when local firmware installed to an immutable location, and should not be used when the metadata could be written by an untrusted user. **NOTE:** Using `Keyring=jcat` is usually a better choice than `Keyring=gpg` or `Keyring=pkcs` as deployments may disable either PKCS#7 or GPG support. Using `Keyring=jcat` means it works in both cases, and JCat also provides other benefits like desynchronized CDN mitigation and multiple types of checksum. **MetadataURI=** The URL of AppStream metadata to download and use. This should have a suffix of `.xml.gz` for legacy metadata and `.xml.xz` for the more modern format. Only prefixes of `http://`, `https://` and `file://` are supported here. **ApprovalRequired=false** If set to `true` then only releases allow-listed with `fwupdmgr set-approved-firmware` will show in CLI and GUI tools. **ReportURI=** The endpoint to use for sending success reports for firmware obtained from this remote, or blank to disable this feature. **AutomaticReports=false** If `true`, automatically sent success reports for firmware obtained from this remote after the firmware update has completed. **SecurityReportURI=** The endpoint to use for sending HSI platform security reports, or blank to disable this feature. **AutomaticSecurityReports=false** If `true`, automatically sent HSI platform security reports when running `fwupdmgr security`. **OrderBefore=** This remote will be ordered before any remotes listed here, using commas as the delimiter. **NOTE:** When the same firmware release is available from multiple remotes, the one with the highest priority will be used. **OrderAfter=** This remote will be ordered after any remotes listed here, using commas as the delimiter. **Username=** The username to use for BASIC authentication when downloading metadata and firmware from this remote, and for uploading success reports. **Password=** The password (although, in practice this will be a user *token*) to use for BASIC authentication when downloading both metadata and firmware from this remote, and for uploading success reports. **RefreshInterval={{FWUPD_REMOTE_CONFIG_DEFAULT_REFRESH_INTERVAL}}** The time in seconds after which the front end tools should re-download the metadata signature, or `0` to re-download every time. ## NOTES The basename of the path without the extension is used for the remote ID. For instance, the `{{SYSCONFDIR}}/fwupd/remotes.d/lvfs.conf` remote file will have ID of `lvfs`. ## SEE ALSO fwupd-1.9.16/docs/fwupd.conf.md000066400000000000000000000276341460375044200163010ustar00rootroot00000000000000--- title: fwupd.conf file format --- % fwupd.conf(5) {{PACKAGE_VERSION}} | Configuration File Format ## NAME **fwupd.conf** — configuration file for the fwupd daemon. ## SYNOPSIS The `{{SYSCONFDIR}}/fwupd/fwupd.conf` file is the main configuration file for the fwupd daemon. The complete description of the file format and possible parameters are documented here for reference purposes. ## FILE FORMAT The file consists of a multiple sections with optional parameters. Parameters are of the form: ```text [section] key = value ``` The file is line-based, each newline-terminated line represents either a comment, a section name or a parameter. Section and parameter names are case sensitive. Only the first equals sign in a parameter is significant. Whitespace before or after the first equals sign is discarded as is leading and trailing whitespace in a parameter value. Internal whitespace within a parameter value is retained. Any line beginning with a hash (`#`) character is ignored, as are lines containing only whitespace. The values following the equals sign in parameters are all either a string (no quotes needed), unsigned integers, or a boolean, which may be given as **true** or **false**. Case is not significant in boolean values, but is preserved in string values. ## DAEMON PARAMETERS The `[fwupd]` section can contain the following parameters: **DisabledDevices={{DisabledDevices}}** Allow blocking specific devices by their GUID, using semicolons as delimiter. **DisabledPlugins={{DisabledPlugins}}** Allow blocking specific plugins by name. Use **fwupdmgr get-plugins** to get the list of plugins. **ArchiveSizeMax=** Maximum archive size that can be loaded in Mb, with 25% of the total system memory as the default. **IdleTimeout={{IdleTimeout}}** Idle time in seconds to shut down the daemon, where a value of **0** specifies "never". **NOTE:** some plugins might inhibit the auto-shutdown, for instance thunderbolt. **IdleInhibitStartupThreshold={{IdleInhibitStartupThreshold}}** If the daemon takes more than this time to startup (in milliseconds) then inhibit the idle shutdown timer. A value of **0** specifies "never". **VerboseDomains={{VerboseDomains}}** Comma separated list of domains to log in verbose mode. If unset, no domains are set to verbose. If set to "*", all domains are verbose, which is the same as running the daemon with **--verbose --verbose**. **UpdateMotd={{UpdateMotd}}** Update the message of the day (MOTD) on device and metadata changes. **EnumerateAllDevices={{EnumerateAllDevices}}** For some plugins, enumerate only devices supported by metadata. **ApprovedFirmware={{ApprovedFirmware}}** A list of firmware checksums that has been approved by the site admin If unset, all firmware is approved. **BlockedFirmware={{BlockedFirmware}}** Allow blocking specific devices by their cabinet checksum, either SHA-1 or SHA-256. **UriSchemes={{UriSchemes}}** Allowed URI schemes in the preference order; failed downloads from the first scheme will be retried with the next in order until no choices remain. **IgnorePower={{IgnorePower}}** Ignore power levels of devices when running updates. **OnlyTrusted={{OnlyTrusted}}** Only support installing firmware signed with a trusted key. Do not set this to `false` on a production or trusted system. **ShowDevicePrivate={{ShowDevicePrivate}}** Show data such as device serial numbers which some users may consider private. **AllowEmulation={{AllowEmulation}}** Allow capturing and loading device emulation by logging all USB transfers. Enabling this will greatly increase the amount of memory fwupd uses when upgrading devices. **TrustedUids={{TrustedUids}}** UIDs matching these values that call the D-Bus interface should marked as trusted. **HostBkc={{HostBkc}}** Comma separated list of best known configuration IDs to be used when using `fwupdmgr sync`. This can downgrade firmware to factory versions or upgrade firmware to a supported config level. e.g. **vendor-factory-2021q1,mycompany-2023** **ReleaseDedupe={{ReleaseDedupe}}** Deduplicate duplicate releases by the archive checksum are available from more than one source. **ReleasePriority={{ReleasePriority}}** When the same version release is available from more than one source this option can be used to either prefer the local version (avoiding a potentially expensive download) or to prefer the remote version (which may have updated metadata such as release notes). The possible options are `local` or `remote` or empty to not make any adjustment to the policy, relying on the `OrderAfter` and `OrderBefore` sections in the remote. **EspLocation=** Set the preferred location used for the EFI system partition (ESP) path. This is typically used if UDisks was not able to automatically identify the location for any reason. **Manufacturer=** **ProductName=** **ProductSku=** **Family=** **EnclosureKind=** **BaseboardProduct=** **BaseboardManufacturer=** Override values for SMBIOS or Device Tree data on the local system. These are only required when the SMBIOS or Device Tree data is invalid, missing, or to simulate running on another system. Empty values should be used to populate blank entries or add values to populate specific entries. **TrustedReports={{TrustedReports}}** Vendor reports matching these expressions will have releases marked as `trusted-report`. Each *OR* section is delimited by a `;` and each *AND* section delimited by `&`, e.g. * `DistroId=chromeos` Any report uploaded from ChromeOS is trusted. * `DistroId=chromeos&RemoteId=lvfs` Any report found in the `lvfs` remote uploaded from a ChromeOS machine is trusted. * `DistroId=fedora&VendorId=19` Any report uploaded from Fedora 19 is trusted. * `DistroId=fedora&VendorId=$OEM` Any report uploaded from Fedora by the hardware OEM is trusted. * `DistroId=fedora;DistroId=rhel&DistroVersion=9` Any report uploaded from Fedora (any version) or from RHEL 9 is trusted. NOTE: a `VendorId` of `$OEM` represents the OEM vendor ID of the vendor that owns the firmware, for example, where Lenovo QA has generated a signed report for a Lenovo laptop. There are also three os-release values available, `$ID`, `$VERSION_ID` and `$VARIANT_ID`, which allow expressions like: * `DistroId=$ID` * `DistroId=$ID,DistroVersion=$VERSION_ID` * `Flags=is-upgrade,from-oem` Any flags listed here must all be matched by the report. **P2pPolicy={{P2pPolicy}}** This tells the daemon what peer-to-peer policy to use. For instance, using Passim, an optional local caching service. Using peer-to-peer data might reduce the amount of bandwidth used on your network considerably. There are three possible values: * `none`: Do not publish any files * `metadata`: Only publish shared metadata that is common to each machine. * `firmware`: Only publish firmware archives **after the next reboot** of the machine. At some point in the future fwupd will change the default to `metadata,firmware`. **TestDevices={{TestDevices}}** Create virtual test devices and remote for validating daemon flows. This is only intended for CI testing and development purposes. {% if plugin_uefi_capsule %} ## UEFI_CAPSULE PARAMETERS The `[uefi_capsule]` section can contain the following parameters: **EnableGrubChainLoad={{uefi_capsule_EnableGrubChainLoad}}** Configure GRUB to launch `fwupdx64.efi` instead of using other methods such as NVRAM or Capsule-On-Disk. **DisableShimForSecureBoot={{uefi_capsule_DisableShimForSecureBoot}}** The shim loader is required to chainload the fwupd EFI binary unless the `fwupd.efi` file has been self-signed manually. **RequireESPFreeSpace={{uefi_capsule_RequireESPFreeSpace}}** Amount of free space required on the ESP, for example using `32` for 32Mb. By default this is dynamically set to at least twice the size of the payload. **DisableCapsuleUpdateOnDisk={{uefi_capsule_DisableCapsuleUpdateOnDisk}}** Allow ignoring the CapsuleOnDisk support advertised by the firmware. **EnableEfiDebugging={{uefi_capsule_EnableEfiDebugging}}** Enable the low-level debugging of `fwupdx64.efi` to the `FWUPDATE_DEBUG_LOG` EFI variable. **NOTE:** enabling this option is going to fill up the NVRAM store much more quickly and should only be enabled when debugging an issue with the EFI binary. This value also has no affect when using Capsule-on-Disk as the EFI helper binary is not being used. **RebootCleanup={{uefi_capsule_RebootCleanup}}** Delete any capsule files copy to the ESP, and remove any EFI variables set for the update. **NOTE:** disabling this option is only required when debugging the flash process and normal users should not need to change this setting. {% endif %} {% if plugin_msr %} ## MSR PARAMETERS The `[msr]` section can contain the following parameter: **MinimumSmeKernelVersion={{msr_MinimumSmeKernelVersion}}** Minimum kernel version to allow probing for sme flag. This only needs to be modified by enterprise kernels that have cherry picked the feature into a kernel with an old version number. {% endif %} {% if plugin_redfish %} ## REDFISH PARAMETERS The `[redfish]` section can contain the following parameters: **Uri={{redfish_Uri}}** The URI to the Redfish service in the format `scheme://ip:port` for instance `https://192.168.0.133:443` **Username={{redfish_Username}}** The username to use when connecting to the Redfish service. **Password={{redfish_Password}}** The password to use when connecting to the Redfish service. **CACheck={{redfish_CACheck}}** Whether to verify the server certificate or not. This is turned off by default. BMCs using self-signed certificates will not work unless the plugin does not verify it against the system CAs. **IpmiDisableCreateUser={{redfish_IpmiDisableCreateUser}}** Do not use IPMI KCS to create an initial user account if no SMBIOS data. Setting this to **true** prevents creating user accounts on the BMC automatically. **ManagerResetTimeout={{redfish_ManagerResetTimeout}}** Amount of time in seconds to wait for a BMC restart. {% endif %} ## THUNDERBOLT PARAMETERS The `[thunderbolt]` section can contain the following parameters: **MinimumKernelVersion={{thunderbolt_MinimumKernelVersion}}** Minimum kernel version to allow use of this plugin. This only needs to be modified by enterprise kernels that have cherry picked the feature into a kernel with an old version number. **DelayedActivation={{thunderbolt_DelayedActivation}}** Forces delaying activation until shutdown/logout/reboot. ## TEST PARAMETERS The `[test]` section can contain the following parameters: **AnotherWriteRequired={{test_AnotherWriteRequired}}** Do two passes of the write function. **CompositeChild={{test_CompositeChild}}** If the device should have a child device. **DecompressDelay={{test_DecompressDelay}}** Delay in milliseconds to use when decompressing the test device. **NeedsActivation={{test_NeedsActivation}}** If the device needs activating before deploying the update. **NeedsReboot={{test_NeedsReboot}}** If the device needs a reboot before deploying the update. **RegistrationSupported={{RegistrationSupported}}** If the device should register with other plugins. **RequestDelay={{test_RequestDelay}}** Delay in milliseconds to use when requesting user input from the user. **RequestSupported={{test_RequestSupported}}** If the device interactive request is supported. **VerifyDelay={{test_DecompressDelay}}** Delay in milliseconds to use when verifying the test device. **WriteDelay={{test_DecompressDelay}}** Delay in milliseconds to use when writing the test device. **WriteSupported={{test_Supported}}** If the device write is supported. If unsupported the device write will not start. ## NOTES `{{SYSCONFDIR}}/fwupd/fwupd.conf` may contain either hardcoded or autogenerated credentials and must only be readable by the user that is running the fwupd process, which is typically `root`. ## SEE ALSO fwupd-1.9.16/docs/fwupd.toml.in000066400000000000000000000027701460375044200163270ustar00rootroot00000000000000[library] version = "@version@" browse_url = "https://github.com/fwupd/fwupd" repository_url = "https://github.com/fwupd/fwupd.git" website_url = "https://www.fwupd.org" authors = "fwupd Development Team" logo_url = "org.freedesktop.fwupd.svg" license = "LGPL-2.1-or-later" description = "Functionality exported by libfwupd for client applications" dependencies = [ "GObject-2.0", "Gio-2.0", "Json-1.0" ] devhelp = true search_index = true [dependencies."GObject-2.0"] name = "GObject" description = "The base type system library" docs_url = "https://docs.gtk.org/gobject/" [dependencies."Gio-2.0"] name = "Gio" description = "A modern, easy-to-use VFS API" docs_url = "https://docs.gtk.org/gio/" [dependencies."Json-1.0"] name = "Json" description = "API for efficient parsing and writing of JSON (JavaScript Object Notation) streams" docs_url = "https://gnome.pages.gitlab.gnome.org/json-glib/" [theme] name = "basic" show_index_summary = true show_class_hierarchy = true [source-location] base_url = "https://github.com/fwupd/fwupd/blob/@version@/" [extra] content_images = [ "../data/org.freedesktop.fwupd.svg", ] urlmap_file = "urlmap_fwupd.js" [[object]] name = "build_user_agent_system" hidden = true [[object]] name = "hash_kv_to_variant" hidden = true [[object]] name = "variant_to_hash_kv" hidden = true [[object]] name = "input_stream_read_bytes_async" hidden = true [[object]] name = "input_stream_read_bytes_finish" hidden = true fwupd-1.9.16/docs/fwupdplugin.toml.in000066400000000000000000000041151460375044200175410ustar00rootroot00000000000000[library] version = "@version@" browse_url = "https://github.com/fwupd/fwupd" repository_url = "https://github.com/fwupd/fwupd.git" website_url = "https://www.fwupd.org" authors = "fwupd Development Team" logo_url = "org.freedesktop.fwupd.svg" license = "LGPL-2.1-or-later" description = "Functionality available to fwupd plugins" dependencies = [ "GObject-2.0", "Gio-2.0", "Fwupd-2.0", "Xmlb-2.0", "GUsb-1.0" ] devhelp = true search_index = true [dependencies."GObject-2.0"] name = "GObject" description = "The base type system library" docs_url = "https://developer.gnome.org/gobject/stable/" [dependencies."Gio-2.0"] name = "Gio" description = "A modern, easy-to-use VFS API" docs_url = "https://developer.gnome.org/gio/stable/" [dependencies."Fwupd-2.0"] name = "Fwupd" description = "Firmware update daemon client library" docs_url = "../libfwupd/index.html" [theme] name = "basic" show_index_summary = true show_class_hierarchy = true [source-location] base_url = "https://github.com/fwupd/fwupd/blob/@version@/" [extra] content_files = [ "env.md", "building.md", "tutorial.md", "hsi.md", "device-emulation.md", "ds20.md", "hwids.md", "bios-settings.md", "best-known-configuration.md", "only-trusted.md", "supermicro-license.md", @man_md@, @plugin_readme_outputs@, ] content_images = [ "architecture-plan.svg", "../data/org.freedesktop.fwupd.svg", "debug_attached.png", "debug_breakpoint.png", "debug_tool_selector.png", ] urlmap_file = "urlmap_fwupdplugin.js" [[object]] name = "Device" [[object.method]] name = "incorporate_from_component" hidden = true [[object]] name = "Cabinet" [[object.method]] name = "set_jcat_context" hidden = true [[object.method]] name = "get_silo" hidden = true [[object.function]] name = "common_cab_build_silo" hidden = true [[object]] name = "Fmap" hidden = true [[object]] name = "FmapArea" hidden = true [[object]] name = "IhexFirmwareRecord" hidden = true [[object]] name = "SrecFirmwareRecord" hidden = true fwupd-1.9.16/docs/generate-hsi-spec.py000077500000000000000000000072441460375044200175610ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2022 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import argparse import sys import json import os from typing import Dict, List, Any if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( "filename_src", action="store", type=str, help="markdown source" ) parser.add_argument( "filename_dst", action="store", type=str, help="markdown destination" ) parser.add_argument("json_attrs", nargs="+", help="JSON attributes") args = parser.parse_args() with open(args.filename_src, "rb") as f: template = f.read() txt: List[str] = [] for fn in sorted(args.json_attrs): try: with open(fn, "rb") as f: item = json.loads(f.read()) except json.decoder.JSONDecodeError as e: print(f"failed to parse {fn}: {str(e)}") sys.exit(1) if "id" not in item: print(f"skipping {fn} as no id") continue txt += [f""] if "deprecated-ids" in item: for deprecated_id in item["deprecated-ids"]: txt += [f''] if "name" in item: txt += [f"### [{item['name']}](#{item['id']})"] if "description" in item: for para in item["description"]: txt += [para] if "failure-impact" in item: txt += ["**Impact:**"] for para in item["failure-impact"]: txt += [para] if "failure-results" in item and "success-results" in item: txt += ["**Possible results:**"] tmp: List[str] = [] for value, desc in item["failure-results"].items(): tmp += [f"- `{value}`: {desc} (failure)"] for value, desc in item["success-results"].items(): tmp += [f"- `{value}`: {desc} (success)"] txt += ["\n".join(tmp)] if "hsi-level" in item and "fwupd-version" in item: txt += [ "A test success result is needed to meet HSI-{} on " "systems that run this test. *[v{}]*".format( item["hsi-level"], item["fwupd-version"] ) ] if "resolution" in item: txt += [f"**Resolution:** {item['resolution']}"] if "issues" in item: txt += ["**Issues:**"] tmp: List[str] = [] for issue in item["issues"]: if issue.startswith("CVE-"): tmp += [f"- [{issue}](https://nvd.nist.gov/vuln/detail/{issue})"] else: tmp += [f"- {issue}"] txt += ["\n".join(tmp)] if "references" in item: txt += ["**References:**"] tmp: List[str] = [] for url, title in item["references"].items(): tmp += [f"- [{title}]({url})"] txt += ["\n".join(tmp)] if "requires" in item: txt += ["**Hardware requirements:**"] if "CPUID\\VID_GenuineIntel" in item["requires"]: txt += ["This attribute will only be available when using Intel CPUs."] elif "CPUID\\VID_AuthenticAMD" in item["requires"]: txt += ["This attribute will only be available when using AMD CPUs."] if "more-information" in item: txt += ["**More information:**"] for para in item["more-information"]: txt += [para] with open(args.filename_dst, "wb") as f: f.write(template.decode().replace("{{tests}}", "\n\n".join(txt)).encode()) fwupd-1.9.16/docs/generate-oval.py000066400000000000000000000133461460375044200170040ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2022 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import argparse import datetime import sys import json import os from typing import Dict, List, Any import xml.etree.ElementTree as ET if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-v", "--version", type=str, help="fwupd version") parser.add_argument("-s", "--schema-version", type=str, help="schema version") parser.add_argument( "filename_dst", action="store", type=str, help="XML destination" ) parser.add_argument("json_attrs", nargs="+", help="JSON attributes") args = parser.parse_args() # parse JSON items: List[str] = [] for fn in sorted(args.json_attrs): try: with open(fn, "rb") as f: item = json.loads(f.read()) except json.decoder.JSONDecodeError as e: print(f"failed to parse {fn}: {str(e)}") sys.exit(1) for tag in ["id", "name", "failure-results"]: if tag not in item: print("skipping {} as no {}".format(fn), tag) continue items.append(item) oval_definitions = ET.Element("oval_definitions") oval_definitions.set("xmlns", "http://oval.mitre.org/XMLSchema/oval-definitions-5") oval_definitions.set("xmlns:oval", "http://oval.mitre.org/XMLSchema/oval-common-5") oval_definitions.set( "xmlns:unix-def", "http://oval.mitre.org/XMLSchema/oval-definitions-5#unix" ) oval_definitions.set( "xmlns:red-def", "http://oval.mitre.org/XMLSchema/oval-definitions-5#linux" ) oval_definitions.set( "xmlns:ind-def", "http://oval.mitre.org/XMLSchema/oval-definitions-5#independent", ) oval_definitions.set("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") oval_definitions.set( "xsi:schemaLocation", "http://oval.mitre.org/XMLSchema/oval-common-5 oval-common-schema.xsd " "http://oval.mitre.org/XMLSchema/oval-definitions-5 oval-definitions-schema.xsd " "http://oval.mitre.org/XMLSchema/oval-definitions-5#unix unix-definitions-schema.xsd " "http://oval.mitre.org/XMLSchema/oval-definitions-5#linux linux-definitions-schema.xsd", ) generator = ET.SubElement(oval_definitions, "generator") for key, value in { "oval:product_name": "fwupd", "oval:product_version": args.version, "oval:schema_version": args.schema_version, "oval:timestamp": datetime.datetime.now().isoformat(), }.items(): oval = ET.SubElement(generator, key) oval.text = value definitions = ET.SubElement(oval_definitions, "definitions") for item in items: definition = ET.SubElement(definitions, "definition") definition.set("class", "patch") definition.set("id", f"oval:{item['id']}:def:1") definition.set("version", "1") metadata = ET.SubElement(definition, "metadata") ET.SubElement(metadata, "title").text = item["name"] affected = ET.SubElement(metadata, "affected") affected.set("family", "unix") ET.SubElement(affected, "platform").text = "All" # is this valid? if "issues" in item: for issue in item["issues"]: reference = ET.SubElement(metadata, "reference") reference.set("ref_id", issue) if issue.startswith("CVE-"): reference.set( "ref_url", f"https://nvd.nist.gov/vuln/detail/{issue}" ) reference.set("source", "CVE") if "description" in item: ET.SubElement(metadata, "description").text = "\n".join(item["description"]) criteria = ET.SubElement(definition, "criteria") criteria.set("operator", "OR") criterion = ET.SubElement(criteria, "criterion") criterion.set("comment", item["name"]) criterion.set("test_ref", f"oval:{item['id']}:tst:1") tests = ET.SubElement(oval_definitions, "tests") for item in items: red_def = ET.SubElement(tests, "red-def:fwupdsecattr_test") red_def.set("check", "at least one") red_def.set("comment", item["name"]) red_def.set("id", f"oval:{item['id']}:tst:1") red_def.set("version", "1") red_def_object = ET.SubElement(red_def, "red-def:object") red_def_object.set("object_ref", f"oval:{item['id']}:obj:1") red_def_state = ET.SubElement(red_def, "red-def:state") red_def_state.set("state_ref", f"oval:{item['id']}:ste:1") objects = ET.SubElement(oval_definitions, "objects") for item in items: red_def = ET.SubElement(objects, "red-def:fwupdsecattr_object") red_def.set("id", f"oval:{item['id']}:obj:1") red_def.set("version", "1") red_def_stream_id = ET.SubElement(red_def, "red-def:stream-id") red_def_stream_id.set("datatype", "string") red_def_stream_id.text = format(item["id"]) states = ET.SubElement(oval_definitions, "states") for item in items: red_def = ET.SubElement(states, "red-def:fwupdsecattr_state") red_def.set("id", f"oval:{item['id']}:ste:1") red_def.set("version", "1") red_def_security_attr = ET.SubElement(red_def, "red-def:security-attr") red_def_security_attr.set("datatype", "string") red_def_security_attr.set("operation", "pattern match") red_def_security_attr.text = "|".join( [value for value, _ in item["failure-results"].items()] ) ET.indent(oval_definitions, space=" ", level=0) with open(args.filename_dst, "wb") as f: f.write(ET.tostring(oval_definitions, encoding="utf-8", xml_declaration=True)) fwupd-1.9.16/docs/hsi-tests.d/000077500000000000000000000000001460375044200160375ustar00rootroot00000000000000fwupd-1.9.16/docs/hsi-tests.d/meson.build000066400000000000000000000030261460375044200202020ustar00rootroot00000000000000hsi_test_jsons = files([ 'org.fwupd.hsi.Amd.RollbackProtection.json', 'org.fwupd.hsi.Amd.SpiReplayProtection.json', 'org.fwupd.hsi.Amd.SpiWriteProtection.json', 'org.fwupd.hsi.EncryptedRam.json', 'org.fwupd.hsi.IntelBootguard.Acm.json', 'org.fwupd.hsi.IntelBootguard.Enabled.json', 'org.fwupd.hsi.IntelBootguard.Otp.json', 'org.fwupd.hsi.IntelBootguard.Policy.json', 'org.fwupd.hsi.IntelBootguard.Verified.json', 'org.fwupd.hsi.IntelCet.Active.json', 'org.fwupd.hsi.IntelCet.Enabled.json', 'org.fwupd.hsi.IntelGds.json', 'org.fwupd.hsi.IntelSmap.json', 'org.fwupd.hsi.Iommu.json', 'org.fwupd.hsi.Kernel.Lockdown.json', 'org.fwupd.hsi.Kernel.Tainted.json', 'org.fwupd.hsi.Mei.KeyManifest.json', 'org.fwupd.hsi.Mei.ManufacturingMode.json', 'org.fwupd.hsi.Mei.OverrideStrap.json', 'org.fwupd.hsi.Mei.Version.json', 'org.fwupd.hsi.PlatformDebugEnabled.json', 'org.fwupd.hsi.PlatformDebugLocked.json', 'org.fwupd.hsi.PlatformFused.json', 'org.fwupd.hsi.PrebootDma.json', 'org.fwupd.hsi.Spi.Bioswe.json', 'org.fwupd.hsi.Spi.Ble.json', 'org.fwupd.hsi.Spi.Descriptor.json', 'org.fwupd.hsi.Spi.SmmBwp.json', 'org.fwupd.hsi.SupportedCpu.json', 'org.fwupd.hsi.SuspendToIdle.json', 'org.fwupd.hsi.SuspendToRam.json', 'org.fwupd.hsi.Tpm.EmptyPcr.json', 'org.fwupd.hsi.Tpm.ReconstructionPcr0.json', 'org.fwupd.hsi.Tpm.Version20.json', 'org.fwupd.hsi.Uefi.BootserviceVars.json', 'org.fwupd.hsi.Uefi.Pk.json', 'org.fwupd.hsi.Uefi.SecureBoot.json', 'org.fwupd.hsi.Bios.RollbackProtection.json', ]) fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Amd.RollbackProtection.json000066400000000000000000000030501460375044200257240ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Amd.RollbackProtection", "name": "AMD Secure Processor Rollback protection", "description": [ "AMD SOCs include the ability to prevent a rollback attack by a rollback protection feature on the secure processor.", "This feature prevents an attacker from loading an older firmware onto the part after a security vulnerability has been fixed." ], "more-information": [ "This particular check is not for the Microsoft Pluton Security processor which is present on some chips.", "End users are not able to directly modify rollback protection, this is controlled by the manufacturer.", "On Lenovo systems it has been reported that if this is disabled it may potentially be enabled by loading 'OS Optimized Defaults' in BIOS setup." ], "failure-impact": [ "SOCs without this feature may be attacked by an attacker installing an older firmware that takes advantage of a well-known vulnerability." ], "failure-results": { "not-enabled": "rollback protection disabled" }, "success-results": { "enabled": "rollback protection enabled" }, "hsi-level": 4, "references": { "https://www.psacertified.org/blog/anti-rollback-explained/": "Rollback protection", "https://www.amd.com/en/technologies/pro-security": "AMD Secure Processor", "https://forums.lenovo.com/t5/Fedora/AMD-Rollback-protection-not-detected-by-fwupd-on-T14-G3-AMD/m-p/5182708?page=1#5810366": "Loading OS Optimized Defaults on Lenovo systems" }, "requires": [ "CPUID\\VID_AuthenticAMD" ], "fwupd-version": "1.8.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Amd.SpiReplayProtection.json000066400000000000000000000010611460375044200261030ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Amd.SpiReplayProtection", "name": "AMD SPI Write protections", "description": [ "SOCs may enforce control of the SPI bus to prevent writes other than by verified entities." ], "failure-impact": [ "SOCs without this feature may be attacked by an attacker modifying the SPI." ], "failure-results": { "not-enabled": "SPI protections disabled" }, "success-results": { "enabled": "SPI protections enabled" }, "hsi-level": 2, "requires": [ "CPUID\\VID_AuthenticAMD" ], "fwupd-version": "1.8.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Amd.SpiWriteProtection.json000066400000000000000000000010621460375044200257420ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Amd.SpiWriteProtection", "name": "AMD SPI Replay protections", "description": [ "SOCs may include support for replay-protected monotonic counters to prevent replay attacks." ], "failure-impact": [ "SOCs without this feature may be attacked by an attacker modifying the SPI." ], "failure-results": { "not-enabled": "SPI protections disabled" }, "success-results": { "enabled": "SPI protections enabled" }, "hsi-level": 3, "requires": [ "CPUID\\VID_AuthenticAMD" ], "fwupd-version": "1.8.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Bios.CapsuleUpdates.json000066400000000000000000000007751460375044200252540ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Bios.CapsuleUpdates", "name": "BIOS Capsule updates", "description": [ "Some OEMs offer a switch in the BIOS to allow capsule updates to be enabled." ], "failure-impact": [ "When this switch is present but disabled, BIOS firmware updates for Linux may not work." ], "failure-results": { "not-enabled": "BIOS capsule updates disabled" }, "success-results": { "enabled": "BIOS capsule updates enabled" }, "hsi-level": 1, "fwupd-version": "1.9.6" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Bios.RollbackProtection.json000066400000000000000000000014161460375044200261230ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Bios.RollbackProtection", "name": "BIOS Firmware Rollback protection", "description": [ "Some OEMs include an optional firmware protection feature in their BIOS that would prevent installation of older firmware that may have security vulnerabilities." ], "failure-impact": [ "Firmware without this feature enabled may be attacked by an attacker installing an older firmware that takes advantage of a well-known vulnerability." ], "failure-results": { "not-enabled": "rollback protection disabled" }, "success-results": { "enabled": "rollback protection enabled" }, "hsi-level": 2, "references": { "https://www.psacertified.org/blog/anti-rollback-explained/": "Rollback protection" }, "fwupd-version": "1.8.8" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.EncryptedRam.json000066400000000000000000000022631460375044200240260ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.EncryptedRam", "name": "DRAM memory encryption", "description": [ "TME (Intel) or SME (AMD) is used by the hardware on supported SOCs to encrypt all data on external memory buses.", "It mitigates against an attacker being able to capture memory data while the system is running or to capture memory by removing a DRAM chip.", "This encryption may be activated by either transparently via firmware configuration or by code running in the Linux kernel." ], "failure-impact": [ "A local attacker can either extract unencrypted content by attaching debug probes on the DIMM modules, or by removing them and inserting them into a computer with a modified DRAM controller." ], "failure-results": { "not-encrypted": "detected but disabled", "not-supported": "not available" }, "success-results": { "encrypted": "detected and enabled" }, "hsi-level": 4, "references": { "https://software.intel.com/content/www/us/en/develop/blogs/intel-releases-new-technology-specification-for-memory-encryption.html": "Intel TME Press Release", "https://en.wikichip.org/wiki/x86/sme": "WikiChip SME Overview" }, "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.IntelBootguard.Acm.json000066400000000000000000000014171460375044200250520ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.IntelBootguard.Acm", "name": "Intel BootGuard: ACM", "description": [ "BootGuard is a processor feature that prevents the machine from running firmware images not released by the system manufacturer.", "It forms a root-of-trust by fusing in cryptographic keys into the processor itself that are used to verify the Authenticated Code Modules found in the SPI flash." ], "failure-impact": [ "When BootGuard is not set up correctly then the chain-of-trust between the CPU and the bootloader can not be verified." ], "failure-results": { "not-valid": "boot is not verified" }, "success-results": { "valid": "ACM protected" }, "hsi-level": 2, "requires": [ "CPUID\\VID_GenuineIntel" ], "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.IntelBootguard.Enabled.json000066400000000000000000000022031460375044200256760ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.IntelBootguard.Enabled", "deprecated-ids": [ "org.fwupd.hsi.Kernel.IntelBootguard" ], "name": "Intel BootGuard: Enabled", "description": [ "BootGuard is a processor feature that prevents the machine from running firmware images not released by the system manufacturer.", "It forms a root-of-trust by fusing in cryptographic keys into the processor itself that are used to verify the Authenticated Code Modules found in the SPI flash." ], "failure-impact": [ "When BootGuard is not set up correctly then the chain-of-trust between the CPU and the bootloader can not be verified.", "This would allow subverting the Secure Boot protection which gives the attacker full access to your hardware." ], "failure-results": { "not-enabled": "not detected, or detected but not enabled" }, "success-results": { "enabled": "detected and enabled" }, "hsi-level": 2, "references": { "https://github.com/coreboot/coreboot/blob/master/src/soc/intel/jasperlake/include/soc/me.h": "Coreboot documentation" }, "requires": [ "CPUID\\VID_GenuineIntel" ], "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.IntelBootguard.Otp.json000066400000000000000000000016011460375044200251070ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.IntelBootguard.Otp", "name": "Intel BootGuard: OTP", "description": [ "BootGuard is a processor feature that prevents the machine from running firmware images not released by the system manufacturer.", "It forms a root-of-trust by fusing in cryptographic keys into the processor itself that are used to verify the Authenticated Code Modules found in the SPI flash." ], "failure-impact": [ "When BootGuard is not set up correctly then the chain-of-trust between the CPU and the bootloader can not be verified.", "This would allow subverting the Secure Boot protection which gives the attacker full access to your hardware." ], "failure-results": { "not-valid": "SOC is not locked" }, "success-results": { "valid": "SOC is locked" }, "hsi-level": 2, "requires": [ "CPUID\\VID_GenuineIntel" ], "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.IntelBootguard.Policy.json000066400000000000000000000015071460375044200256110ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.IntelBootguard.Policy", "name": "Intel BootGuard: Policy", "description": [ "BootGuard is a processor feature that prevents the machine from running firmware images not released by the system manufacturer.", "It forms a root-of-trust by fusing in cryptographic keys into the processor itself that are used to verify the Authenticated Code Modules found in the SPI flash." ], "failure-impact": [ "The attacker can invalidate the chain of trust (subverting Secure Boot), and the user would get just a console warning and then continue to boot." ], "failure-results": { "not-valid": "policy is invalid" }, "success-results": { "valid": "error enforce policy is set to shutdown" }, "hsi-level": 3, "requires": [ "CPUID\\VID_GenuineIntel" ], "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.IntelBootguard.Verified.json000066400000000000000000000016261460375044200261110ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.IntelBootguard.Verified", "name": "Intel BootGuard: Verified", "description": [ "BootGuard is a processor feature that prevents the machine from running firmware images not released by the system manufacturer.", "It forms a root-of-trust by fusing in cryptographic keys into the processor itself that are used to verify the Authenticated Code Modules found in the SPI flash." ], "failure-impact": [ "When BootGuard is not set up correctly then the chain-of-trust between the CPU and the bootloader can not be verified.", "This would allow subverting the Secure Boot protection which gives the attacker full access to your hardware." ], "failure-results": { "not-valid": "boot is not verified" }, "success-results": { "success": "verified boot chain" }, "hsi-level": 2, "requires": [ "CPUID\\VID_GenuineIntel" ], "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.IntelCet.Active.json000066400000000000000000000014241460375044200243500ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.IntelCet.Active", "name": "CET Utilized by OS", "description": [ "Control enforcement technology prevents exploits from hijacking the control-flow transfer instructions for both forward-edge (indirect call/jmp) and back-edge transfer (ret)." ], "failure-impact": [ "A local or physical attacker with an existing unrelated vulnerability can use a ROP gadget to run arbitrary code." ], "failure-results": { "not-supported": "CET not being used by the host" }, "success-results": { "supported": "CET being used" }, "hsi-level": 3, "references": { "https://software.intel.com/sites/default/files/managed/4d/2a/control-flow-enforcement-technology-preview.pdf": "Intel CET Technology Preview" }, "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.IntelCet.Enabled.json000066400000000000000000000015431460375044200244710ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.IntelCet.Enabled", "name": "CET: Available", "description": [ "Control enforcement technology prevents exploits from hijacking the control-flow transfer instructions for both forward-edge (indirect call/jmp) and back-edge transfer (ret)." ], "failure-impact": [ "A local or physical attacker with an existing unrelated vulnerability can use a reliable and well-known method to run arbitrary code." ], "failure-results": { "not-supported": "CET not supported" }, "success-results": { "enabled": "CET feature enabled by the platform", "supported": "CET feature support by the platform" }, "hsi-level": 3, "references": { "https://software.intel.com/sites/default/files/managed/4d/2a/control-flow-enforcement-technology-preview.pdf": "Intel CET Technology Preview" }, "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.IntelGds.json000066400000000000000000000016271460375044200231450ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.IntelGds", "name": "Intel GDS Mitigation", "description": [ "CPU Microcode must be updated to mitigate against an information-disclosure security issue called Gather Data Sampling." ], "failure-impact": [ "Firmware without this feature enabled allow an attacker to exfiltrate secrets by running a malicious program or script." ], "failure-results": { "not-valid": "microcode is not new enough to support required mitigations", "not-enabled": "mitigation is not enabled", "not-locked": "mitigation is not locked" }, "success-results": { "enabled": "microcode mitigation is supported, enabled and locked" }, "hsi-level": 2, "references": { "https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/technical-documentation/gather-data-sampling.html": "Gather Data Sampling" }, "fwupd-version": "1.9.4" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.IntelSmap.json000066400000000000000000000014701460375044200233240ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.IntelSmap", "name": "SMAP", "description": [ "Without Supervisor Mode Access Prevention, the supervisor code usually has full read and write access to user-space memory mappings.", "This can make exploits easier to write, as it allows the kernel to access user-space memory when it did not intend to." ], "failure-impact": [ "A local or remote attacker can use a simple exploit to modify the contents of kernel memory which can lead to privilege escalation." ], "failure-results": { "not-supported": "SMAP not enabled" }, "success-results": { "enabled": "SMAP features are detected and enabled" }, "hsi-level": 4, "references": { "https://en.wikipedia.org/wiki/Supervisor_Mode_Access_Prevention": "SMAP Wikipedia Page" }, "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Iommu.json000066400000000000000000000017321460375044200225170ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Iommu", "name": "DMA protection", "description": [ "The IOMMU on modern systems is used to mitigate against DMA attacks.", "All I/O for devices capable of DMA is mapped into a private virtual memory region.", "Common implementations are Intel VT-d and AMD-Vi." ], "failure-impact": [ "An attacker with inexpensive PCIe development hardware can write to system RAM from the ThunderBolt or Firewire ports which can lead to privilege escalation." ], "failure-results": { "not-found": "IOMMU hardware was not detected" }, "success-results": { "enabled": "IOMMU hardware detected and enabled" }, "hsi-level": 2, "resolution": "If available, turn on IOMMU in the system BIOS. You may also have to use additional kernel boot parameters, for example `iommu=force`.", "references": { "https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit": "IOMMU Wikipedia Page" }, "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Kernel.Lockdown.json000066400000000000000000000014521460375044200244270ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Kernel.Lockdown", "name": "Kernel Lockdown", "description": [ "Kernel lockdown is an important mechanism to limit what hardware actions userspace programs can perform.", "Turning on this feature means that often-used mechanisms like /dev/mem used to raise privileges or exfiltrate data are no longer available." ], "failure-impact": [ "An unlocked kernel can be easily abused by a malicious userspace program running as root, which can include replacing system firmware." ], "failure-results": { "not-valid": "could not read lockdown status, perhaps from an old kernel", "not-enabled": "lockdown is set to `none`" }, "success-results": { "enabled": "lockdown is set to either `integrity` or `confidentiality`." }, "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Kernel.Tainted.json000066400000000000000000000013211460375044200242320ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Kernel.Tainted", "name": "Kernel Tainted", "description": [ "When calculating the HSI value fwupd has to ask the Linux Kernel for information.", "If the kernel has been tainted by overriding a firmware table or by loading a proprietary module then we cannot trust the data it reports." ], "failure-impact": [ "Using a tainted kernel means that values obtained from the kernel cannot be trusted." ], "failure-results": { "not-valid": "could not detect kernel taint status", "tainted": "the kernel is untrusted, perhaps because a proprietary module was loaded" }, "success-results": { "not-tainted": "the kernel is trusted" }, "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Mei.KeyManifest.json000066400000000000000000000024141460375044200243570ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Mei.KeyManifest", "name": "ME BootGuard Platform Key", "description": [ "The BootGuard Platform Key is fused into the CPU PCH during manufacturing by the OEM.", "At bootup, an authenticated code module computes a hash of the Platform Key and compares it with the one stored in field-programmable fuses.", "If the key matches the ACM will pass control to the firmware, otherwise the boot process will stop.", "In 2022 a number of Platform **secret** Keys were leaked by Lenovo and confirmed by Intel." ], "failure-impact": [ "A custom system firmware can be signed using the leaked private key to completely disable UEFI Secure Boot and allow complete persistent compromise of the affected machine." ], "failure-results": { "not-valid": "device uses a key that is compromised" }, "success-results": { "valid": "device uses a BootGuard Platform Key that is not known to be compromised" }, "hsi-level": 1, "references": { "https://github.com/phretor/intel-leak-checker/": "Intel leak checker", "https://www.tomshardware.com/news/intel-confirms-6gb-alder-lake-bios-source-code-leak-new-details-emerge": "Tom's Hardware Article" }, "requires": [ "CPUID\\VID_GenuineIntel" ], "fwupd-version": "1.8.7" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Mei.ManufacturingMode.json000066400000000000000000000022131460375044200255450ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Mei.ManufacturingMode", "name": "ME not in manufacturing mode", "description": [ "There have been some unfortunate cases of the ME being distributed in manufacturing mode.", "In manufacturing mode many features from the ME can be interacted with that decrease the platform's security." ], "failure-impact": [ "If the ME is in manufacturing mode then any user with root access can provision the ME engine with new keys.", "This gives them full access to the system even when the system is powered off." ], "failure-results": { "not-locked": "device is in manufacturing mode" }, "success-results": { "locked": "device has had manufacturing mode disabled" }, "hsi-level": 1, "references": { "https://malware.news/t/intel-me-manufacturing-mode-obscured-dangers-and-their-relationship-to-apple-macbook-vulnerability-cve-2018-4251/23214": "ME Manufacturing Mode: obscured dangers", "https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00086.html": "Intel security advisory SA-00086" }, "requires": [ "CPUID\\VID_GenuineIntel" ], "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Mei.OverrideStrap.json000066400000000000000000000017161460375044200247350ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Mei.OverrideStrap", "name": "ME Flash Descriptor Override", "description": [ "The Flash Descriptor Security Override Strap is not accessible to end users on consumer boards and Intel stresses that this is for debugging only." ], "failure-impact": [ "The system firmware can be written from userspace by changing the protected region.", "This gives any attacker with root access a method to write persistent executable code to the firmware, which survives even a full disk wipe and OS reinstall." ], "failure-results": { "not-locked": "device is in debugging mode" }, "success-results": { "locked": "device in in normal runtime mode" }, "hsi-level": 1, "references": { "https://chromium.googlesource.com/chromiumos/third_party/flashrom/+/master/Documentation/mysteries_intel.txt": "Chromium documentation for Intel ME" }, "requires": [ "CPUID\\VID_GenuineIntel" ], "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Mei.Version.json000066400000000000000000000024351460375044200235700ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Mei.Version", "name": "CSME Version", "description": [ "Converged Security and Manageability Engine is a standalone management module that can manage and control some local devices without the host CPU involvement.", "The CSME lives in the PCH and can only be updated by the OEM vendor.", "The version of the CSME module can be checked to detect the most common and serious vulnerabilities." ], "failure-impact": [ "Using any one of the critical vulnerabilities, a remote attacker can take full control of the system and all connected devices, even when the system is powered off." ], "failure-results": { "not-valid": "affected by one of the critical CVEs" }, "success-results": { "valid": "is not affected by the most critical CVEs" }, "hsi-level": 1, "resolution": "Update your Management Engine firmware", "references": { "https://www.intel.com/content/www/us/en/security-center/advisory/intel-sa-00086.html": "Intel CSME Security Review Cumulative Update" }, "issues": [ "CVE-2017-5705", "CVE-2017-5706", "CVE-2017-5707", "CVE-2017-5708", "CVE-2017-5709", "CVE-2017-5710", "CVE-2017-5711", "CVE-2017-5712" ], "requires": [ "CPUID\\VID_GenuineIntel" ], "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.PlatformDebugEnabled.json000066400000000000000000000027371460375044200254450ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.PlatformDebugEnabled", "deprecated-ids": [ "org.fwupd.hsi.IntelDci.Enabled" ], "name": "Intel DCI", "description": [ "Newer Intel CPUs support debugging over USB3 via a proprietary Direct Connection Interface (DCI) with the use of off-the-shelf hardware." ], "failure-impact": [ "Using DCI an attacker with physical access to the computer has full access to all registers and memory in the system, and is able to make changes.", "This makes privilege escalation from user to root possible, and also modifying SMM makes it possible to write to system firmware for a persistent backdoor." ], "failure-results": { "enabled": "debugging is currently enabled" }, "success-results": { "not-enabled": "debugging is not currently enabled" }, "hsi-level": 1, "references": { "https://www.intel.co.uk/content/www/uk/en/support/articles/000029393/processors.html": "Intel Direct Connect Interface", "https://github.com/chipsec/chipsec/blob/master/chipsec/cfg/8086/pch_4xxlp.xml#L270": "Chipsec 4xxlp register definitions", "https://github.com/riscv/riscv-edk2-platforms/blob/85a50de1b459d1d6644a402081120770aa6dd8c7/Silicon/Intel/CoffeelakeSiliconPkg/Pch/Include/Register/PchRegsDci.h": "RISC-V EDK PCH register definitions" }, "more-information": [ "This attribute was previously known as `org.fwupd.hsi.IntelDci.Enabled` in 1.5.0, but was renamed in 1.8.0 to support other vendors." ], "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.PlatformDebugLocked.json000066400000000000000000000020351460375044200253030ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.PlatformDebugLocked", "deprecated-ids": [ "org.fwupd.hsi.IntelDci.Locked" ], "name": "Part is debug locked", "description": [ "Some devices support a concept of whether a part has been unlocked for debugging using proprietary hardware. Such parts allow access to registers that are typically restricted when parts are fused.", "On Intel systems access to this interface is done via a proprietary Direct Connection Interface (DCI)." ], "failure-impact": [ "If using a debug unlocked part, the platform's overall security will be decreased as an attacker may have elevated access to registers and memory within the system and can potentially enable persistent backdoors." ], "failure-results": { "not-locked": "device is not locked" }, "success-results": { "locked": "device is locked" }, "hsi-level": 2, "references": { "https://www.intel.co.uk/content/www/uk/en/support/articles/000029393/processors.html": "Intel Direct Connect Interface" }, "fwupd-version": "1.8.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.PlatformFused.json000066400000000000000000000010201460375044200241720ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.PlatformFused", "name": "Part is fused", "description": [ "When fuses are blown in parts from some manufacturers the hardware will enforce protections against tampering or accessing of certain registers." ], "failure-impact": [ "If using an unfused part, the platform's overall security will be decreased." ], "failure-results": { "not-locked": "device is not fused" }, "success-results": { "locked": "device is fused" }, "hsi-level": 1, "fwupd-version": "1.8.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.PrebootDma.json000066400000000000000000000027221460375044200234650ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.PrebootDma", "deprecated-ids": [ "org.fwupd.hsi.AcpiDmar" ], "name": "Pre-boot DMA protection", "description": [ "The IOMMU on modern systems is used to mitigate against DMA attacks.", "All I/O for devices capable of DMA is mapped into a private virtual memory region.", "On Intel systems the ACPI DMAR table indicated the system is configured with pre-boot DMA protection which eliminates some firmware attacks.", "On AMD systems the ACPI IVRS table indicates the same." ], "failure-impact": [ "An attacker could connect a malicious peripheral using ThunderBolt and reboot the machine, which would allow the attacker to modify the system memory.", "This would allow subverting the Secure Boot protection, and also invalidate any system attestation." ], "failure-results": { "not-valid": "could not determine state", "not-enabled": "was not enabled" }, "success-results": { "enabled": "detected correctly" }, "hsi-level": 3, "references": { "https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit": "IOMMU Wikipedia Page", "https://www.amd.com/en/support/tech-docs/amd-io-virtualization-technology-iommu-specification": "AMD I/O Virtualization Technology (IOMMU) Specification" }, "more-information": [ "This attribute was previously known as `org.fwupd.hsi.AcpiDmar` in 1.5.0, but was renamed in 1.8.0 to support other vendors." ], "fwupd-version": "1.8.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Spi.Bioswe.json000066400000000000000000000020111460375044200234020ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Spi.Bioswe", "name": "BIOS Write Enable (BWE)", "description": [ "Intel hardware provides this mechanism to protect the SPI ROM chip located on the motherboard from being overwritten by the operating system.", "The `BIOSWE` bit must be unset otherwise userspace can write to the SPI chip." ], "failure-impact": [ "The system firmware can be written from userspace.", "This gives any attacker with root access a method to write persistent executable code to the firmware, which survives even a full disk wipe and OS reinstall." ], "failure-results": { "not-found": "the SPI device was not found", "enabled": "write enable is enabled" }, "success-results": { "not-enabled": "write enable is disabled" }, "hsi-level": 1, "references": { "https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/6-chipset-c200-chipset-datasheet.pdf": "Intel C200 Datasheet" }, "requires": [ "CPUID\\VID_GenuineIntel" ], "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Spi.Ble.json000066400000000000000000000016651460375044200226720ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Spi.Ble", "name": "BIOS Lock Enable (BLE)", "description": [ "If the lock bit is set then System Management Interrupts (SMIs) are raised when setting BIOS Write Enable.", "The `BLE` bit must be enabled in the PCH otherwise `BIOSWE` can easily be unset." ], "failure-impact": [ "The system firmware can be written from userspace.", "This gives any attacker with root access a method to write persistent executable code to the firmware, which survives even a full disk wipe and OS reinstall." ], "failure-results": { "not-enabled": "the register is not locked" }, "success-results": { "enabled": "the register is locked" }, "hsi-level": 1, "references": { "https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/6-chipset-c200-chipset-datasheet.pdf": "Intel C200 Datasheet" }, "requires": [ "CPUID\\VID_GenuineIntel" ], "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Spi.Descriptor.json000066400000000000000000000016541460375044200243040ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Spi.Descriptor", "name": "Read-only SPI Descriptor", "description": [ "The SPI descriptor must always be read only from all other regions.", "Additionally on Intel architectures the FLOCKDN register must be set to prevent configuration registers in the SPI BAR from being changed." ], "failure-impact": [ "The system firmware can be written from userspace by changing the protected region.", "This gives any attacker with root access a method to write persistent executable code to the firmware, which survives even a full disk wipe and OS reinstall." ], "failure-results": { "not-valid": "any region can write to the flash descriptor", "not-locked": "the SPI BAR is not locked" }, "success-results": { "locked": "the SPI BAR is locked and read only from all regions" }, "hsi-level": 1, "requires": [ "CPUID\\VID_GenuineIntel" ], "fwupd-version": "1.6.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Spi.SmmBwp.json000066400000000000000000000017571460375044200233770ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Spi.SmmBwp", "name": "SMM Bios Write Protect (SMM_BWP)", "description": [ "This bit set defines when the BIOS region can be written by the host.", "The `SMM_BWP` bit must be set to make the BIOS region non-writable unless all processors are in system management mode." ], "failure-impact": [ "The system firmware can be written from userspace by exploiting a race condition in checking `BLE`.", "This gives any attacker with root access a method to write persistent executable code to the firmware, which survives even a full disk wipe and OS reinstall." ], "failure-results": { "not-locked": "the region is not locked" }, "success-results": { "locked": "the region is locked" }, "hsi-level": 1, "references": { "https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/6-chipset-c200-chipset-datasheet.pdf": "Intel C200 Datasheet" }, "requires": [ "CPUID\\VID_GenuineIntel" ], "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.SupportedCpu.json000066400000000000000000000022771460375044200240730ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.SupportedCpu", "name": "Supported CPU", "description": [ "Most platform checks are specific to the CPU vendor.", "To avoid giving a very high HSI result for a platform we do not know how to verify, we include this attribute to ensure that the result is meaningful." ], "failure-impact": [ "If using an unsupported CPU then fwupd is unable to verify the platform security.", "You should contact your platform vendor and ask them to contribute HSI tests for this CPU type." ], "failure-results": { "unknown": "platform security is unknown" }, "success-results": { "valid": "the CPU platform is supported and has HSI tests" }, "more-information": [ "On AMD APUs or CPUs this information is reported on kernel 5.19 or later via the `ccp` kernel module. ", "If the kernel module is enabled but is not being auto-loaded, this is a kernel bug and should be reported to kernel bugzilla. ", "If the kernel module has loaded but you still don't have data this is NOT a fwupd bug. You will have to contact ", "your motherboard or system manufacturer to enable reporting this information." ], "hsi-level": 1, "fwupd-version": "1.8.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.SuspendToIdle.json000066400000000000000000000010711460375044200241470ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.SuspendToIdle", "name": "Suspend-to-Idle", "description": [ "The platform should be set up with Suspend-to-Idle as the default S3 sleep state." ], "failure-impact": [ "A local attacker could overwrite the S3 resume script to modify system RAM which can lead to privilege escalation." ], "failure-results": { "enabled": "deep sleep enabled", "not-valid": "could not determine the default" }, "success-results": { "not-enabled": "suspend-to-idle being used" }, "hsi-level": 3, "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.SuspendToRam.json000066400000000000000000000021151460375044200240110ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.SuspendToRam", "name": "Suspend to RAM disabled", "description": [ "Suspend to Ram (S3) keeps the raw contents of the DRAM refreshed when the system is asleep.", "This means that the memory modules can be physically removed and the contents recovered, or a cold boot attack can be performed with a USB device.", "The firmware should be configured to prefer using suspend to idle instead of suspend to ram or to not offer suspend to RAM." ], "failure-impact": [ "An attacker with physical access to a system can obtain the un-encrypted contents of the RAM by suspending the machine, removing the DIMM and inserting it into another machine with modified DRAM controller before the memory contents decay." ], "failure-results": { "enabled": "sleep enabled", "not-valid": "could not determine the default" }, "success-results": { "not-enabled": "suspend-to-ram being used" }, "hsi-level": 3, "references": { "https://en.wikipedia.org/wiki/Cold_boot_attack": "Cold Boot Attack Wikipedia Page" }, "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Tpm.EmptyPcr.json000066400000000000000000000021701460375044200237300ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Tpm.EmptyPcr", "name": "Empty PCR in TPM", "description": [ "The system firmware is responsible for measuring values about its boot stage in PCRs 0 through 7.", "Some firmwares have bugs that prevent them from measuring some of those values, breaking the fundamental assumption of the Measured Boot chain-of-trust." ], "failure-impact": [ "A local attacker could measure fake values into the empty PCR, corresponding to a firmware and OS that do not match the ones actually loaded.", "This allows hiding a compromised boot chain or fooling a remote-attestation server into believing that a different kernel is running." ], "failure-results": { "not-found": "no TPM hardware could be found", "not-valid": "at least one empty checksum has been found" }, "success-results": { "valid": "all PCRs from 0 to 7 must have non-empty measurements" }, "hsi-level": 1, "references": { "https://github.com/google/security-research/blob/master/pocs/bios/tpm-carte-blanche/writeup.md": "TPM Carte Blanche" }, "issues": [ "CVE-2021-42299" ], "fwupd-version": "1.7.2" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Tpm.ReconstructionPcr0.json000066400000000000000000000021561460375044200257370ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Tpm.ReconstructionPcr0", "name": "PCR0 TPM Event Log Reconstruction", "description": [ "The TPM event log records which events are registered for the PCR0 hash.", "When reconstructed the event log values should always match the TPM PCR0.", "If extra events are included in the event log, or some are missing, the reconstitution will fail." ], "failure-impact": [ "This is not a vulnerability per-se, but it shows that the system firmware checksum cannot be verified as the PCR result has been calculated incorrectly." ], "more-information": [ "Additional information about specific bugs and debugging steps are available here https://github.com/fwupd/fwupd/wiki/TPM-PCR0-differs-from-reconstruction" ], "failure-results": { "not-valid": "could not reconstitute the hash value", "not-found": "no TPM hardware could be found" }, "success-results": { "valid": "all correct" }, "hsi-level": 2, "references": { "https://www.kernel.org/doc/html/latest/security/tpm/tpm_event_log.html": "Linux Kernel TPM Documentation" }, "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Tpm.Version20.json000066400000000000000000000014531460375044200237570ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Tpm.Version20", "name": "TPM 2.0 Present", "description": [ "A TPM securely stores platform specific secrets that can only be divulged to trusted consumers in a secure environment." ], "failure-impact": [ "The PCR registers will not be available for use by the bootloader and kernel.", "This means userspace cannot either encrypt disks to the specific machine, and also can't know if the system firmware was externally modified." ], "failure-results": { "not-found": "no TPM device found", "not-enabled": "TPM not in v2 mode" }, "success-results": { "found": "TPM device found in v2 mode" }, "hsi-level": 1, "references": { "https://en.wikipedia.org/wiki/Trusted_Platform_Module": "TPM Wikipedia Page" }, "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Uefi.BootserviceVars.json000066400000000000000000000012221460375044200254320ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Uefi.BootserviceVars", "name": "UEFI BootService Variables", "description": [ "UEFI boot service variables should not be readable from runtime mode." ], "failure-impact": [ "It is possible to read security-sensitive data that should not be readable by the runtime mode." ], "failure-results": { "not-locked": "bootservice-only data is readable in runtime mode" }, "success-results": { "locked": "bootservice-only data is not visible" }, "hsi-level": 1, "references": { "https://uefi.org/specs/UEFI/2.10/07_Services_Boot_Services.html": "UEFI Specification" }, "fwupd-version": "1.9.3" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Uefi.Pk.json000066400000000000000000000013101460375044200226620ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Uefi.Pk", "name": "UEFI PK", "description": [ "UEFI defines a platform key for the system.", "This should not be a test key, e.g. `DO NOT TRUST - AMI Test PK`" ], "failure-impact": [ "It is possible to sign an EFI binary with the test platform key, which invalidates the Secure Boot trust chain.", "It effectively gives the local attacker full access to your hardware." ], "failure-results": { "not-valid": "an invalid key has been enrolled" }, "success-results": { "valid": "valid key" }, "hsi-level": 1, "references": { "https://wiki.ubuntu.com/UEFI/SecureBoot/Testing": "Ubuntu SecureBoot Wiki Page" }, "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi-tests.d/org.fwupd.hsi.Uefi.SecureBoot.json000066400000000000000000000015721460375044200243740ustar00rootroot00000000000000{ "id": "org.fwupd.hsi.Uefi.SecureBoot", "name": "UEFI SecureBoot", "description": [ "UEFI Secure boot is a verification mechanism for ensuring that code launched by firmware is trusted.", "Secure Boot requires that each binary loaded at boot is validated against trusted certificates." ], "failure-impact": [ "When Secure Boot is not enabled any EFI binary can be run at startup, which gives the attacker full access to your hardware." ], "failure-results": { "not-found": "support has not been detected", "not-enabled": "detected, but has been turned off" }, "success-results": { "enabled": "supported and enabled" }, "hsi-level": 1, "resolution": "Turn off CSM boot and enable Secure Boot in the BIOS setup.", "references": { "https://wiki.ubuntu.com/UEFI/SecureBoot": "Ubuntu SecureBoot Wiki Page" }, "fwupd-version": "1.5.0" } fwupd-1.9.16/docs/hsi.html000066400000000000000000000010541460375044200153430ustar00rootroot00000000000000 Redirecting to https://fwupd.github.io/libfwupdplugin/hsi.html fwupd-1.9.16/docs/hsi.md.in000066400000000000000000000232641460375044200154130ustar00rootroot00000000000000--- title: Host Security ID Specification --- **WARNING: This specification is still in active development: it is incomplete, subject to change, and may have errors; use this at your own risk. It is based on publicly available information.** Authors: - Richard Hughes - Mario Limonciello - Alex Bazhaniuk - Alex Matrosov --- ## Introduction Not all system vendors prioritize building a secure platform. The truth is that **security costs money**. Vendors have to choose between saving a few cents on a bill-of-materials by sharing a SPI chip, or correctly implementing BootGuard. Discovering security vulnerabilities often takes an external researcher filing a disclosure. These disclosures are often technical in nature and difficult for an average consumer to decipher. The Linux Vendor Firmware Service (LVFS) could provide some **easy-to-understand** information to people buying hardware. The service already knows a huge amount of information about machines from signed reports uploaded to the LVFS and from analyzing firmware binaries. However this information alone does not explain firmware security to the user in a way they can actually interpret. ### Other Tools Traditionally, figuring out the true security of your hardware and firmware requires sifting through the marketing documentation provided by the OEM and in many cases just "trusting" they did it right. Tools such as Chipsec can check the hardware configuration, but they do not work out of the box and use technical jargon that an average user cannot interpret. Unfortunately, running a tool like Chipsec requires that you actively turn off some security layers such as UEFI Secure Boot, and allow 3rd party unsigned kernel modules to be loaded. ## [Verifying Host Firmware Security](#verifying) To start out some core protections must be assigned a relative importance. Then an evaluation must be done to determine how each vendor is conforming to the model. For instance, a user might say that for home use any hardware the bare minimum security level (`HSI:1`) is *good enough*. For a work laptop the company IT department might restrict the choice of models to anything meeting the criteria of level `HSI:2` or above. A journalist or a security researcher would only buy level `HSI:3` and above. The reality is that `HSI:4` is going to be more expensive than some unbranded hardware that is rated `HSI:0`. To be trusted, this rating information should be distributed in a centralized agnostic database such as the LVFS. Of course, tools need to detect implementation errors, and to verify that the model that is measured does indeed match the HSI level advertised by the LVFS. Some existing compliance solutions place the burden on the OEM to define what firmware security has been implemented, which is easy to get wrong and in some cases impossible to verify. For this reason HSI will only measure security protections that can be verified by the end user without requiring any extra hardware to be connected, additional software to be installed, or disabling any existing security layers to measure. The HSI specification is primarily designed for laptop and desktop hardware, although some tests *may* still make sense on server or embedded hardware. It is not expected that non-consumer hardware will publish an HSI number. ## [Runtime Behavior](#runtime-behaviour) Orthogonal to the security features provided by the firmware there are other security considerations related to the firmware which may require internet access to discover or that runtime OS changes directly affect the security of the firmware. It would not make sense to have *have updates on the LVFS* as a requirement for a specific security level as this would mean offline the platform might be a higher level initially but as soon as it is brought online it is downgraded which would be really confusing to users. The *core* security level will not change at Operating System runtime, but the suffix may. **More information** Additional information about specific bugs and debugging steps are available on the [fwupd wiki](https://github.com/fwupd/fwupd/wiki/Host-security-ID-runtime-issues). ### [HSI:0 (Insecure State)](#hsi-level0) Limited firmware protection. The lowest security level with little or no detected firmware protections. This is the default security level if no tests can be run or some tests in the next security level have failed. ### [HSI:1 (Critical State)](#hsi-level1) Basic protection but any failure would lead to a critical security impact. This security level corresponds to the most basic of security protections considered essential by security professionals. Any failures at this level would have critical security impact and could likely be used to compromise the system firmware without physical access. ### [HSI:2 (Risky State)](#hsi-level2) The failure is only happened by the theoretical exploit in the lab. This security level corresponds to firmware security issues that pose a theoretical concern or where any exploit would be difficult or impractical to use. At this level various technologies may be employed to protect the boot process from modification by an attacker with local access to the machine. ### [HSI:3 (Protected State)](#hsi-level3) The system firmware only has few minor issues which do not affect the security status. This security level corresponds to out-of-band protection of the system firmware perhaps including recovery. ### [HSI:4 (Secure State)](#hsi-level4) The system is in a robust secure state. The system is corresponding several kind of encryption and execution protection for the system firmware. ### [HSI:5 (Secure Proven State)](#hsi-level5) This security level corresponds to out-of-band attestation of the system firmware. There are currently no tests implemented for HSI:5 and so this security level cannot yet be obtained. ### [HSI Runtime Suffix `!`](#runtime-bang) A runtime security issue detected. - UEFI [Secure Boot](https://wiki.ubuntu.com/UEFI/SecureBoot) has been turned off. *[v1.5.0]* - The kernel is [tainted](https://www.kernel.org/doc/html/latest/admin-guide/tainted-kernels.html) due to a non-free module or critical firmware issue. *[v1.5.0]* - The kernel is not [locked down](https://mjg59.dreamwidth.org/50577.html). *[v1.5.0]* - Unencrypted [swap partition](https://wiki.archlinux.org/index.php/Dm-crypt/Swap_encryption). *[v1.5.0]* - The installed fwupd is running with [custom or modified plugins](https://github.com/fwupd/fwupd/tree/main/plugins). *[v1.5.0]* ## [Low Security Level](#low-security-level) A safe baseline for security should be HSI-1. If your system isn't at least meeting this criteria, you should adjust firmware setup options, contact your manufacturer or replace the hardware. The command line tool `fwupdmgr security` included with fwupd 1.8.4 or later will make individual recommendations on what you can do for individual test failures. GUI tools built against `libfwupd` 1.8.4 or later may also make these recommendation as well. ## [Tests included in fwupd](#tests) The set of tests is currently x86 UEFI-centric, but will be expanded in the future for various ARM or RISC-V firmware protections as required. Where the requirement is architecture or processor specific it has been noted. {{tests}} ## [Conclusion](#conclusions) Any system with a Host Security ID of `0` can easily be modified from userspace. PCs with confidential documents should have a `HSI:3` or higher level of protection. In a graphical tool that would show details about the computer (such as GNOME Control Center's details tab) the OS could display a field indicating Host Security ID. The ID should be shown with an alert color if the security is not at least `HSI:1` or the suffix is `!`. On Linux `fwupd` is used to enumerate and update firmware. It exports a property `HostSecurityId` and a `GetHostSecurityAttrs()` method. The attributes are supposed to represent the *system as a whole* but individual (internal) devices are able to make a claim that they worsened the state of the security of the system. Certain attributes can "obsolete" other attributes. An example is BIOSGuard will set obsoletes to `org.intel.prx`. A plugin method gets called on each plugin which adds attributes directly from the hardware or kernel. Several attributes may be dependent upon the kernel performing measurements and it will take time for these to be upstreamed. In some cases security level measurements will only be possible on systems with a newer kernel. The long term goal is to increase the `HSI:x` level of systems being sold to consumers. By making some of the `HSI:x` attributes part of the LVFS uploaded report we can allow users to compare vendors and models before purchasing hardware. ## [Intentional Omissions](#omissions) ### Intel SGX This is not widely used as it has several high severity security issues. ### Intel MPX MPX support was removed from GCC and the Linux kernel in 2019 and it is now considered obsolete. ## Further Work More internal and external devices should be factored into the security equation. For now the focus for further tests should be around internal device firmware as it is what can be most directly controlled by fwupd and the hardware manufacturer. Security conscious manufacturers are actively participating in the development of future initiatives in the Trusted Computing Group (TCG). As those become ratified standards that are available in hardware, there are opportunities for synergy with this specification. fwupd-1.9.16/docs/hwids.md000066400000000000000000000121011460375044200153250ustar00rootroot00000000000000--- title: Hardware IDs --- ## Introduction Hardware IDs are used by fwupd to identify specific hardware. This is useful as the device-specific identifiers may be the same for different firmware streams. Each hardware ID has varying levels of specificity for the hardware, for instance matching only the system OEM, or matching up to 8 fields including the system BIOS version. For instance, Dell and Lenovo might ship a wireless broadband modem with the same chip vendor and product IDs of `USB\VID_0BDA&PID_5850` and although the two OEMs share the same internal device, the firmware may be different. To cover this case fwupd allows adding `hardware` requirements that mean we can deploy firmware that targets `USB\VID_0BDA&PID_5850`, but *only for Dell* or *only for Lenovo* systems. Microsoft calls these values "CHIDs" and they are generated on Windows from the SBMIOS tables using `ComputerHardwareIds.exe` binary which can be found [here](https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/computerhardwareids). The list of CHIDs used in Microsoft Windows is: HardwareID-0 ← Manufacturer + Family + Product Name + SKU Number + BIOS Vendor + BIOS Version + BIOS Major Release + BIOS Minor Release HardwareID-1 ← Manufacturer + Family + Product Name + BIOS Vendor + BIOS Version + BIOS Major Release + BIOS Minor Release HardwareID-2 ← Manufacturer + Product Name + BIOS Vendor + BIOS Version + BIOS Major Release + BIOS Minor Release HardwareID-3 ← Manufacturer + Family + ProductName + SKU Number + Baseboard_Manufacturer + Baseboard_Product HardwareID-4 ← Manufacturer + Family + ProductName + SKU Number HardwareID-5 ← Manufacturer + Family + ProductName HardwareID-6 ← Manufacturer + SKU Number + Baseboard_Manufacturer + Baseboard_Product HardwareID-7 ← Manufacturer + SKU Number HardwareID-8 ← Manufacturer + ProductName + Baseboard_Manufacturer + Baseboard_Product HardwareID-9 ← Manufacturer + ProductName HardwareID-10 ← Manufacturer + Family + Baseboard_Manufacturer + Baseboard_Product HardwareID-11 ← Manufacturer + Family HardwareID-12 ← Manufacturer + Enclosure Type HardwareID-13 ← Manufacturer + Baseboard_Manufacturer + Baseboard_Product HardwareID-14 ← Manufacturer On Windows, CHIDs are generated from the ASCII representation of SMBIOS strings, and on Linux the same mechanism is used. Additionally, on Linux, the Device Tree, DMI and `kenv` data sources are used to construct emulations of the Microsoft CHIDs. When installing firmware and drivers in Windows vendors *already use* the generated HardwareID GUIDs that match SMBIOS keys like the BIOS vendor and the product SKU. Both `fwupdtool hwids` and `ComputerHardwareIds.exe` only compute results that have the necessary data values available. If a data field is missing, then any related CHIDs are not generated. For example, if the SKU field is missing, then `HardwareID` 0, 3, 4 6 and 7 will not be available for that particular system. ## Implementation Users with versions of fwupd newer than 1.1.1 can run `sudo fwupdtool hwids`. For example: Computer Information -------------------- BiosVendor: LENOVO BiosVersion: GJET75WW (2.25 ) Manufacturer: LENOVO Family: ThinkPad T440s ProductName: 20ARS19C0C ProductSku: LENOVO_MT_20AR_BU_Think_FM_ThinkPad T440s EnclosureKind: 10 BaseboardManufacturer: LENOVO BaseboardProduct: 20ARS19C0C Hardware IDs ------------ {c4159f74-3d2c-526f-b6d1-fe24a2fbc881} <- Manufacturer + Family + ProductName + ProductSku + BiosVendor + BiosVersion + BiosMajorRelease + BiosMinorRelease {ff66cb74-5f5d-5669-875a-8a8f97be22c1} <- Manufacturer + Family + ProductName + BiosVendor + BiosVersion + BiosMajorRelease + BiosMinorRelease {2e4dad4e-27a0-5de0-8e92-f395fc3fa5ba} <- Manufacturer + ProductName + BiosVendor + BiosVersion + BiosMajorRelease + BiosMinorRelease {3faec92a-3ae3-5744-be88-495e90a7d541} <- Manufacturer + Family + ProductName + ProductSku + BaseboardManufacturer + BaseboardProduct {660ccba8-1b78-5a33-80e6-9fb8354ee873} <- Manufacturer + Family + ProductName + ProductSku {8dc9b7c5-f5d5-5850-9ab3-bd6f0549d814} <- Manufacturer + Family + ProductName {178cd22d-ad9f-562d-ae0a-34009822cdbe} <- Manufacturer + ProductSku + BaseboardManufacturer + BaseboardProduct {da1da9b6-62f5-5f22-8aaa-14db7eeda2a4} <- Manufacturer + ProductSku {059eb22d-6dc7-59af-abd3-94bbe017f67c} <- Manufacturer + ProductName + BaseboardManufacturer + BaseboardProduct {0cf8618d-9eff-537c-9f35-46861406eb9c} <- Manufacturer + ProductName {f4275c1f-6130-5191-845c-3426247eb6a1} <- Manufacturer + Family + BaseboardManufacturer + BaseboardProduct {db73af4c-4612-50f7-b8a7-787cf4871847} <- Manufacturer + Family {5e820764-888e-529d-a6f9-dfd12bacb160} <- Manufacturer + EnclosureKind {f8e1de5f-b68c-5f52-9d1a-f1ba52f1f773} <- Manufacturer + BaseboardManufacturer + BaseboardProduct {6de5d951-d755-576b-bd09-c5cf66b27234} <- Manufacturer Which matches the output of `ComputerHardwareIds.exe` on the same hardware. fwupd-1.9.16/docs/index.html000066400000000000000000000070721460375044200156750ustar00rootroot00000000000000 fwupd
    {% if man %}

    Manual pages

    Manual pages for interacting with fwupd

    {% for obj in man %} {% endfor %}{% endif %}

    Building fwupd

    Building using the fwupd development environment

    libfwupd

    Functionality exported by libfwupd for client applications.

    libfwupdplugin

    Functionality available to fwupd plugins.

    Host Security ID

    The fwupd HSI specification.

    BIOS Settings

    The fwupd BIOS settings interface

    BOS DS20 Specification

    The fwupd Binary Object Store descriptor specification

    Supermicro BMC license issue

    Fixing the missing license issue when updating Supermicro boards

    fwupd-1.9.16/docs/meson.build000066400000000000000000000170101460375044200160330ustar00rootroot00000000000000plugin_readme_targets = [] plugin_readme_outputs = [] foreach plugin, enabled: plugins plugin_readme_output = '@0@-README.md'.format(plugin) plugin_readme_targets += custom_target('doc-plugin-@0@'.format(plugin), input: join_paths(meson.project_source_root(), 'plugins', plugin, 'README.md'), output: plugin_readme_output, command: ['ln', '-sf', '@INPUT0@', '@OUTPUT@'], build_by_default: true, ) plugin_readme_outputs += '"@0@"'.format(plugin_readme_output) endforeach if build_standalone if get_option('man') custom_target('fwupd.conf.5', input: 'fwupd.conf.md', output: 'fwupd.conf.5', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, '--replace', 'SYSCONFDIR', sysconfdir, '--replace', 'plugin_uefi_capsule', '@0@'.format(get_option('plugin_uefi_capsule').enabled()), '--replace', 'plugin_msr', '@0@'.format(get_option('plugin_msr').enabled()), '--replace', 'plugin_redfish', '@0@'.format(get_option('plugin_redfish').enabled()), '--replace', 'P2pPolicy', '@0@'.format(get_option('p2p_policy')), '--defines', join_paths(meson.project_source_root(), 'plugins', 'msr', 'fu-msr-plugin.c'), '--defines', join_paths(meson.project_source_root(), 'plugins', 'redfish', 'fu-redfish-plugin.c'), '--defines', join_paths(meson.project_source_root(), 'plugins', 'thunderbolt', 'fu-thunderbolt-plugin.c'), '--defines', join_paths(meson.project_source_root(), 'plugins', 'uefi-capsule', 'fu-uefi-capsule-plugin.c'), '--defines', join_paths(meson.project_source_root(), 'src', 'fu-engine-config.c'), ], install: true, install_dir: join_paths(mandir, 'man5'), ) custom_target('fwupd-remotes.d.5', input: 'fwupd-remotes.d.md', output: 'fwupd-remotes.d.5', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, '--replace', 'SYSCONFDIR', sysconfdir, '--replace', 'LOCALSTATEDIR', localstatedir, '--defines', join_paths(meson.project_source_root(), 'libfwupd', 'fwupd-remote.c'), ], install: true, install_dir: join_paths(mandir, 'man5'), ) endif if build_docs md_targets += custom_target('fwupd.conf.md', input: 'fwupd.conf.md', output: 'fwupd.conf.md', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, '--replace', 'SYSCONFDIR', sysconfdir, '--replace', 'plugin_uefi_capsule', '@0@'.format(get_option('plugin_uefi_capsule').enabled()), '--replace', 'plugin_msr', '@0@'.format(get_option('plugin_msr').enabled()), '--replace', 'plugin_redfish', '@0@'.format(get_option('plugin_redfish').enabled()), '--defines', join_paths(meson.project_source_root(), 'plugins', 'msr', 'fu-msr-plugin.c'), '--defines', join_paths(meson.project_source_root(), 'plugins', 'redfish', 'fu-redfish-plugin.c'), '--defines', join_paths(meson.project_source_root(), 'plugins', 'thunderbolt', 'fu-thunderbolt-plugin.c'), '--defines', join_paths(meson.project_source_root(), 'plugins', 'uefi-capsule', 'fu-uefi-capsule-plugin.c'), '--defines', join_paths(meson.project_source_root(), 'src', 'fu-engine-config.c'), '--md', ], ) md_targets += custom_target('fwupd-remotes.d.md', input: 'fwupd-remotes.d.md', output: 'fwupd-remotes.d.md', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, '--replace', 'SYSCONFDIR', sysconfdir, '--replace', 'LOCALSTATEDIR', localstatedir, '--defines', join_paths(meson.project_source_root(), 'libfwupd', 'fwupd-remote.c'), '--md', ], ) man_md += ['"fwupd.conf.md"', '"fwupd-remotes.d.md"'] endif endif if build_docs and introspection.allowed() toml_conf = configuration_data() docgen_version = meson.project_version() toml_conf.set('version', docgen_version) toml_conf.set('plugin_readme_outputs', ','.join(plugin_readme_outputs)) toml_conf.set('man_md', ','.join(man_md)) fwupd_toml = configure_file( input: 'fwupd.toml.in', output: 'fwupd.toml', configuration: toml_conf ) fwupdplugin_toml = configure_file( input: 'fwupdplugin.toml.in', output: 'fwupdplugin.toml', configuration: toml_conf ) custom_target('doc-fwupd', input: [ fwupd_toml, fwupd_gir[0], ], output: 'libfwupd', command: [ gidocgen_app, 'generate', '--quiet', '--add-include-path=@0@'.format(meson.current_build_dir() / '../libfwupd'), '--config=@INPUT0@', '--output-dir=@OUTPUT@', '--no-namespace-dir', '--content-dir=@0@'.format(meson.current_source_dir()), '@INPUT1@', ], depends: [ fwupd_gir[0], ], build_by_default: true, install: true, install_dir: join_paths(datadir, 'doc'), ) subdir('hsi-tests.d') hsi_md = custom_target('generate-hsi-spec', input: hsi_test_jsons, output : 'hsi.md', command : [ python3, files(['generate-hsi-spec.py', 'hsi.md.in']), '@OUTPUT@', '@INPUT@', ], ) custom_target('generate-oval', input: hsi_test_jsons, output : 'oval.xml', command : [ python3, files(['generate-oval.py']), '--version', fwupd_version, '--schema-version', '5.11.3', '@OUTPUT@', '@INPUT@', ], ) custom_target('doc-fwupdplugin', input: [ fwupdplugin_toml, fwupdplugin_gir[0], ], output: 'libfwupdplugin', command: [ gidocgen_app, 'generate', '--quiet', '--add-include-path=@0@'.format(meson.current_build_dir() / '../libfwupd'), '--add-include-path=@0@'.format(meson.current_build_dir() / '../libfwupdplugin'), '--config=@INPUT0@', '--output-dir=@OUTPUT@', '--no-namespace-dir', '--content-dir=@0@'.format(meson.current_build_dir()), '--content-dir=@0@'.format(meson.current_source_dir()), '--content-dir=@0@'.format(meson.current_build_dir() / '../src'), '--content-dir=@0@'.format(meson.current_build_dir() / '../plugins/uefi-dbx'), '--content-dir=@0@'.format(meson.current_build_dir() / '../data/motd'), '--content-dir=@0@'.format(meson.current_build_dir() / '../plugins/uefi-capsule'), '@INPUT1@', ], depends: [ fwupdplugin_gir[0], hsi_md, plugin_readme_targets, md_targets, ], build_by_default: true, install: true, install_dir: join_paths(datadir, 'doc'), ) man_cmd = [] foreach man: man_md man_cmd += '-m @0@'.format(man) endforeach custom_target('index.html', input: 'index.html', output: 'index.html', command: [ generate_index, '@INPUT@', '-o', '@OUTPUT@', man_cmd, ], install: true, install_dir: join_paths(datadir, 'doc', 'fwupd') ) if hsi install_data(['hsi.html'], install_dir : join_paths(datadir, 'doc', 'fwupd') ) endif install_data(['urlmap_fwupd.js'], install_dir: join_paths(datadir, 'doc', 'libfwupd') ) install_data(['urlmap_fwupdplugin.js'], install_dir: join_paths(datadir, 'doc', 'libfwupdplugin') ) #make devhelp work install_symlink('libfwupd', install_dir: join_paths(datadir, 'doc', 'fwupd'), pointing_to: join_paths('..', 'libfwupd'), ) install_symlink('libfwupdplugin', install_dir: join_paths(datadir, 'doc', 'fwupd'), pointing_to: join_paths('..', 'libfwupdplugin'), ) endif fwupd-1.9.16/docs/only-trusted.md000066400000000000000000000141141460375044200166660ustar00rootroot00000000000000--- title: Signing Test Firmware Payloads --- ## Introduction In the normal vendor update flow, firmware is optionally signed and encrypted by the vendor, and then uploaded to the LVFS wrapped in a cabinet archive with a small XML metadata file to describe how the firmware should be matched to hardware. Upon uploading, the LVFS signs the firmware and metadata XML contained in the archive and adds it to a `.jcat` file also included in the image. The original firmware is never modified, which is why the [Jcat](https://github.com/hughsie/libjcat) file exists as both a detached checksum (SHA-1, SHA-256 and SHA-512) and a detached signature. The LVFS can add either GPG or PKCS#7 signatures in the Jcat file, and currently does *both* for maxmum compatibility with how client systems have been configured. The keys in `/etc/pki/fwupd` and `/etc/pki/fwupd-metadata` are used for per-system trust and are currently used for "did the firmware update *come from* somewhere I trust" rather than "verify the vendor signed the update" -- on the logic the "signed the update" is probably already covered by a signature on the payload that the device verifies. Notably, The LVFS both verifies the vendor OEM → ODM → IHV relationships and assigns restrictions on what devices each legal entity can upload for. There's no way to separate the keys so that you could say "only use this certificate for per-system-trust when the DMI vendor of the device is Dell" and there's no way to do key rotation or revocation. The trusted certificate mechanism was not really designed for any keys except the static LVFS. If the intent is to use a test key to sign the firmware files and get installed purely offline with an unmodified fwupd package (without uploading to the LVFS) then the following instructions can be modified to suit. First, lets verify that an existing firmware binary and metainfo file without a Jcat signature refuses to install when packaged into a cabinet archive: $ gcab -c firmware.cab firmware.bin firmware.metainfo.xml $ fwupdmgr install firmware.cab --allow-reinstall Decompressing… [ - ] firmware signature missing or not trusted; set OnlyTrusted=false in /etc/fwupd/fwupd.conf ONLY if you are a firmware developer Let's download a script that can generate some test certificates -- feel free to copy the commands used and of course you need to modify the details of both the CA and user certificate. Please do not use the unmodified `ACME-CA.pem` or `rhughes_signed.pem` files for signing any cabinet archives you're going to redistribute anywhere (even internally), otherwise it is going to be very confusing to debug *which* `rhughes_signed.pem` is being used. $ wget https://raw.githubusercontent.com/hughsie/libjcat/main/contrib/build-certs.py $ python ./build-certs.py Signing certificate... $ ls ACME* rhughes* ACME-CA.key ACME-CA.pem rhughes.csr rhughes.key rhughes.pem rhughes_signed.pem We now have a CA key from ACME, and a user key signed by the CA key, along with a CSR and the two private keys. Lets now use the signed user key to create a Jcat file and also add a SHA256 checksum: $ jcat-tool --appstream-id com.redhat.rhughes sign firmware.jcat firmware.bin rhughes_signed.pem rhughes.key $ jcat-tool self-sign firmware.jcat firmware.bin --kind sha256 $ jcat-tool info firmware.jcat JcatFile: Version: 0.1 JcatItem: ID: firmware.bin JcatBlob: Kind: pkcs7 Flags: is-utf8 AppstreamId: com.redhat.rhughes Timestamp: 2023-02-22T10:24:25Z Size: 0xdcc Data: -----BEGIN PKCS7----- MIIKCwYJKoZIhvcNAQcCoIIJ/DCCCfgCAQExDTALBglghkgBZQMEAgEwCwYJKoZI ... ysAcwqcDY7+k9TWB8V2MeZCHg6/aF4Oj3R16Nvag3w== -----END PKCS7----- JcatBlob: Kind: sha256 Flags: is-utf8 Timestamp: 2023-02-22T10:30:19Z Size: 0x40 Data: fce1847b0599bb19cd913d02268f15107691a79221ce16822b4c931cd1bda2c5 We can then create the new firmware archive, this time with the self-signed Jcat file as well. gcab -c firmware.cab firmware.bin firmware.metainfo.xml firmware.jcat Now we need to install the **CA** certificate to the system-wide system store. If fwupd is running in a prefix then you need to use that instead, e.g. `/home/emily/root/etc/pki/fwupd/`. $ sudo cp ACME-CA.pem /etc/pki/fwupd/ [sudo] password for emily: foobarbaz Then, the firmware should install **without** needing to change `OnlyTrusted` in `fwupd.conf`. $ fwupdmgr install firmware.cab --allow-reinstall Writing… [***************************************] Successfully installed firmware Vendors are allowed to sign the Jcat with their own user certificate if desired, although please note that maintaining a certificate authority is a serious business including HSMs, time-limited and *revokable* user-certificates -- and typically lots of legal paperwork. Shipping the custom vendor CA certificate in the fwupd project is **not possible**, or a good idea, secure or practical -- or how fwupd and LVFS were designed to be used. So please do not ask. That said, if a vendor included the `.jcat` in the firmware cabinet archive, the LVFS will **append** its own signature rather than replace it -- which may make testing the archive easier. ## Debugging Using `sudo fwupdtool get-details firmware.cab --verbose --verbose` should indicate why the certificate isn't being trusted, e.g. FuCabinet processing file: firmware.metainfo.xml FuCabinet processing release: 1.2.3 FuCabinet failed to verify payload firmware.bin: checksums were required, but none supplied This indicates that the `jcat-tool self-sign firmware.jcat firmware.bin --kind sha256` step was missed as the JCat file does not have any supported checksums. fwupd-1.9.16/docs/supermicro-license.md000066400000000000000000000015571460375044200200340ustar00rootroot00000000000000--- title: Supermicro BMC License --- ## Introduction While all newer (some X10, all X11 and H12 series) mainboard for Supermicro support Redfish some features are only available after buying them as additional feature. One of those are applying BIOS and BMC firmware updates. ## Details If you want to update your Supermicro board via redfish using fwupd you will need either the [SFT-OOB-LIC](https://store.supermicro.com/out-of-band-sft-oob-lic.html) or the [SFT-DCMS-Single](https://store.supermicro.com/supermicro-server-manager-dcms-license-key-sft-dcms-single.html) license. The license can be installed via redfish by POSTing it to `/redfish/v1/Managers/1/LicenseManager/ActivateLicense`, using the web interface or using the `contrib/upload-smc-license.py` If the license is not installed fwupd will add the FWUPD_DEVICE_PROBLEM_MISSING_LICENSE flag to the device. fwupd-1.9.16/docs/tutorial.md000066400000000000000000001206111460375044200160600ustar00rootroot00000000000000--- title: Plugin Tutorial --- ## Introduction At the heart of fwupd are plugins that gets run at startup, when devices get hotplugged and when updates are done. The idea is we have lots of small plugins that each do one thing, and are ordered by dependencies against each other at runtime. Using plugins we can add support for new hardware or new policies without making big changes all over the source tree. There are broadly 3 types of plugin methods: - **Mechanism**: Upload binary data into a specific hardware device. - **Policy**: Control the system when updates are happening, e.g. preventing the user from powering-off. - **Helpers**: Providing more metadata about devices, for instance handling device quirks. A plugin only needs to define the vfuncs that are required, and the plugin name is taken automatically from the GType. /* fu-foo-plugin.h * * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuFooPlugin, fu_foo_plugin, FU, FOO_PLUGIN, FuPlugin) /* fu-foo-plugin.c * * Copyright (C) Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-foo-plugin.h" struct _FuFooPlugin { FuPlugin parent_instance; gpointer proxy; }; G_DEFINE_TYPE(FuFooPlugin, fu_foo_plugin, FU_TYPE_PLUGIN) static gboolean fu_foo_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { FuPluginData *data = fu_plugin_get_data(plugin); self->proxy = create_proxy(); if(self->proxy == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to create proxy"); return FALSE; } return TRUE; } static void fu_foo_plugin_init(FuFooPlugin *self) { } static void fu_foo_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_RUN_BEFORE, "dfu"); } static void fu_foo_finalize(GObject *obj) { FuFooPlugin *self = FU_FOO_PLUGIN(obj); destroy_proxy(self->proxy); G_OBJECT_CLASS(fu_foo_plugin_parent_class)->finalize(obj); } static void fu_foo_plugin_class_init(FuFooPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->constructed = fu_foo_constructed; object_class->finalize = fu_foo_finalize; plugin_class->startup = fu_foo_plugin_startup; } We have to define when our plugin is run in reference to other plugins, in this case, making sure we run before the `dfu` plugin. For most plugins it does not matter in what order they are run and this information is not required. ## Creating an abstract device This section shows how you would create a device which is exported to the daemon and thus can be queried and updated by the client software. The example here is all hardcoded, and a true plugin would have to derive the details about the `FuDevice` from the hardware, for example reading data from `sysfs` or `/dev`. static gboolean fu_foo_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { g_autoptr(FuDevice) dev = NULL; fu_device_set_id(dev, "dummy-1:2:3"); fu_device_add_guid(dev, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e"); fu_device_set_version(dev, "1.2.3"); fu_device_get_version_lowest(dev, "1.2.2"); fu_device_get_version_bootloader(dev, "0.1.2"); fu_device_add_icon(dev, "computer"); fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE); fu_plugin_device_add(plugin, dev); return TRUE; } static void fu_foo_plugin_class_init(FuFooPluginClass *klass) { … plugin_class->coldplug = fu_foo_plugin_coldplug; … } This shows a lot of the plugin architecture in action. Some notable points: - The device ID (`dummy-1:2:3`) has to be unique on the system between all plugins, so including the plugin name as a prefix is probably a good idea. - The GUID value can be generated automatically using `fu_device_add_guid(dev,"some-identifier")` but is quoted here explicitly. The GUID value has to match the `provides` value in the `.metainfo.xml` file for the firmware update to succeed. - Setting a display name and an icon is a good idea in case the GUI software needs to display the device to the user. Icons can be specified using a full path, although icon theme names should be preferred for most devices. - The `FWUPD_DEVICE_FLAG_UPDATABLE` flag tells the client code that the device is in a state where it can be updated. If the device needs to be in a special mode (e.g. a bootloader) then the `FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER` flag can also be used. If the update should only be allowed when there is AC power available to the computer (i.e. not on battery) then `FWUPD_DEVICE_FLAG_REQUIRE_AC` should be used as well. There are other flags and the API documentation should be used when choosing what flags to use for each kind of device. - Setting the lowest allows client software to refuse downgrading the device to specific versions. This is required in case the upgrade migrates some kind of data-store so as to be incompatible with previous versions. Similarly, setting the version of the bootloader (if known) allows the firmware to depend on a specific bootloader version, for instance allowing signed firmware to only be installable on hardware with a bootloader new enough to deploy it. ## Mechanism Plugins Although it would be a wonderful world if we could update all hardware using a standard shared protocol this is not the universe we live in. Using a mechanism like DFU or UpdateCapsule means that fwupd will just work without requiring any special code, but for the real world we need to support vendor-specific update protocols with layers of backwards compatibility. When a plugin has created a device that is `FWUPD_DEVICE_FLAG_UPDATABLE` we can ask the daemon to update the device with a suitable `.cab` file. When this is done the daemon checks the update for compatibility with the device, and then calls the vfuncs to update the device. static gboolean fu_foo_plugin_write_firmware(FuPlugin *plugin, FuDevice *dev, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) { gsize sz = 0; guint8 *buf = g_bytes_get_data(blob_fw, &sz); /* write 'buf' of size 'sz' to the hardware */ return TRUE; } static void fu_foo_plugin_class_init(FuFooPluginClass *klass) { … plugin_class->write_firmware = fu_foo_plugin_write_firmware; … } It's important to note that the `blob_fw` is the binary firmware file (e.g. `.dfu`) and **not** the `.cab` binary data. If `FWUPD_INSTALL_FLAG_FORCE` is used then the usual checks done by the flashing process can be relaxed (e.g. checking for quirks), but please don't brick the users hardware even if they ask you to. ## Policy Helpers For some hardware, we might want to do an action before or after the actual firmware is squirted into the device. This could be something as simple as checking the system battery level is over a certain threshold, or it could be as complicated as ensuring a vendor-specific GPIO is asserted when specific types of hardware are updated. static gboolean fu_foo_plugin_prepare(FuPlugin *plugin, FuDevice *device, GError **error) { if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REQUIRE_AC && !on_ac_power()) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_AC_POWER_REQUIRED, "Cannot install update " "when not on AC power"); return FALSE; } return TRUE; } static gboolean fu_foo_plugin_cleanup(FuPlugin *plugin, FuDevice *device, GError **error) { return g_file_set_contents("/var/lib/fwupd/something", fu_device_get_id(device), -1, error); } static void fu_foo_plugin_class_init(FuFooPluginClass *klass) { … plugin_class->prepare = fu_foo_plugin_prepare; plugin_class->cleanup = fu_foo_plugin_cleanup; … } ## Detaching to bootloader mode Some hardware can only be updated in a special bootloader mode, which for most devices can be switched to automatically. In some cases the user to do something manually, for instance re-inserting the hardware with a secret button pressed. Before the device update is performed the fwupd daemon runs an optional `update_detach()` vfunc which switches the device to bootloader mode. After the update (or if the update fails) an the daemon runs an optional `update_attach()` vfunc which should switch the hardware back to runtime mode. Finally an optional `update_reload()` vfunc is run to get the new firmware version from the hardware. The optional vfuncs are **only** run on the plugin currently registered to handle the device ID, although the registered plugin can change during the attach and detach phases. static gboolean fu_foo_plugin_detach(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { if (hardware_in_bootloader) return TRUE; return _device_detach(device, progress, error); } static gboolean fu_foo_plugin_attach(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { if (!hardware_in_bootloader) return TRUE; return _device_attach(device, progress, error); } static gboolean fu_foo_plugin_reload(FuPlugin *plugin, FuDevice *device, GError **error) { g_autofree gchar *version = _get_version(plugin, device, error); if (version == NULL) return FALSE; fu_device_set_version(device, version); return TRUE; } static void fu_foo_plugin_class_init(FuFooPluginClass *klass) { … plugin_class->detach = fu_foo_plugin_detach; plugin_class->attach = fu_foo_plugin_attach; plugin_class->reload = fu_foo_plugin_reload; … } ## The Plugin Object Cache The fwupd daemon provides a per-plugin cache which allows objects to be added, removed and queried using a specified key. Objects added to the cache must be `GObject`s to enable the cache objects to be properly refcounted. ## Debugging a Plugin If the fwupd daemon is started with `--plugin-verbose=$plugin` then the environment variable `FWUPD_$PLUGIN_VERBOSE` is set process-wide. This allows plugins to detect when they should output detailed debugging information that would normally be too verbose to keep in the journal. For example, using `--plugin-verbose=logitech_hidpp` would set `FWUPD_LOGITECH_HID_VERBOSE=1`. ## Using existing code to develop a plugin It is not usually possible to share a plugin codebase with firmware update programs designed for other operating systems. Matching the same rationale as the Linux kernel, trying to use one code base between projects with a compatibility shim layer in-between is real headache to maintain. The general consensus is that trying to use a abstraction layer for hardware is a very bad idea as you're not able to take advantage of the platform specific helpers -- for instance quirk files and the custom GType device creation. The time the vendor saves by creating a shim layer and importing existing source code into fwupd will be overtaken 100x by upstream maintenance costs longer term, which isn't fair. In a similar way, using C++ rather than GObject C means expanding the test matrix to include clang in C++ mode and GNU g++ too. It's also doubled the runtime requirements to now include both the C standard library as well as the C++ standard library and increases the dependency surface. Most rewritten fwupd plugins at up to x10 smaller than the standalone code as they can take advantage of helpers provided by fwupd rather than re-implementing error handling, device quirking and data chunking. ## General guidelines for plugin developers ### General considerations When adding support for a new device in fwupd some things need to be evaluated beforehand: - how the hardware is discovered, identified and polled. - how to communicate with the device (USB? file open/read/write?) - does the device need to be switched to bootloader mode to make it upgradable? - about the format of the firmware files, do they follow any standard? are they already supported in fwupd? - about the update protocol, is it already supported in fwupd? - Is the device composed of multiple different devices? Are those devices enumerated and programmed independently or are they accessed and flashed through a "root" device? In most cases, even if the features you need aren't implemented yet, there's already a plugin that does something similar and can be used as an example, so it's always a good idea to read the code of the existing plugins to understand how they work and how to write a new one, as no documentation will be as complete and updated as the code itself. Besides, the mechanisms implemented in the plugin collection are very diverse and the best way of knowing what can be done is to check what is already been done. ### Leveraging existing fwupd code Depending on how much of the key items for the device update (firmware format, update protocol, transport layer) are already supported in fwupd, the work needed to add support for a new device can range from editing a quirk file to having to fully implement new device and firmware types, although in most cases fwupd already implements helper code that can be extended. #### If the firmware format, update protocol and device communication are already supported This is the simplest case, where an existing plugin fully implements the update process for the new device and we only have to let fwupd know that that plugin should be used for our device. In this case the only thing to do is to edit the plugin quirk file and add the device identifier in the format expected by the plugin together with any required options for it (at least a "Plugin" key to declare that this is the plugin to use for this device). Example: #### If the device type is not supported Then we have to take a look at the existing device types and check if there's any of them that have similarities and which can be partially reused or extended for our device. If the device type is derivable and it can support our new device by implementing the proper vfuncs, then we can simply subclass it and add the required functionalities. If not, we'll need to study what is the best way to reuse it for our needs. If a plugin already implements most of the things we need besides the device type, we can add our new device type to that plugin. Otherwise we should create a plugin that will hold the new device type. The core fwupd code contains some basic device types (such as [FuUdevDevice](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-udev-device.c), [FuUsbDevice](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-usb-device.c), [FuBluezDevice](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-bluez-device.c)) that can be used as a base type for most devices in case we have to implement our own device access, identification and communication from scratch. If the device is natively visible by the OS, most of the time fwupd can detect the device connection and disconnection by listening to udev events, but a supported device may also be not directly accessible from the OS -- for example, a composite device that contains an updatable chip that's connected through I2C to a USB hub that acts as an interface. In that case, the device discovery and enumeration must be programmed by the developer, but the same device identification and management mechanisms apply in all cases. See the "Creating a new device type" and "Device identification" below for more details. #### If the firmware type is not supported Same as with the new device type, there could be an existing firmware type that can be used as a base type for our new type, so first of all we should look for firmware types that are similar to the one we're using. Then, choosing where to define the new type depends on whether there's already a plugin that implements most of the functionalities we need or not. ### Example: extending a firmware type Our firmware files are Intel HEX files that have optional vendor-specific sections at fixed addresses, this is not supported by any firmware type in fwupd out of the box but the [FuIhexFirmare](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-ihex-firmware.c) class parses and models a standard Intel HEX file, so we can create a subclass of it for our firmware type and override the parse method so that it calls the method from the parent class, which would parse the file, and then we can get the data with `fu_firmware_get_bytes()` and do the rest of the custom parsing. Example: ### Example: extending a device type Communication with our new device is carried out by doing read/write/ioctl operations on a device file, but using a custom protocol that is not supported in fwupd. For this type of device we can create a new type derived from `FuUdevDevice`, which takes care of discovering this type of devices, possibly using a vendor-specific protocol, as well as of opening, reading and writing device files, so we would only have to implement the protocol on top of those primitives. (Example: `fu_logitech_hidpp_runtime_bolt_poll_peripherals()` in ) The process would be similar if our device was handled by a different backend (USB or BlueZ). ### Creating a new plugin The bare minimum a plugin should have is a `constructed` function that defines the plugin characteristics such as the device type and firmware type handled by it, the build hash and any plugin-specific quirk keys that can be used for the plugin. static void fu_foo_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_MOUSE); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_GAMEPAD); } static void fu_foo_plugin_class_init(FuFooPluginClass *klass) { plugin_class->init = fu_foo_plugin_constructed; } ### Creating a new device type Besides defining its attributes as a data type, a device type should implement at least the usual `init`, `finalize` and `class_init` functions, and then, depending on its parent type, which methods it overrides and what it does, it must implement a set of device methods. These are some of them, the complete list is in [libfwupdplugin/fu-device.h](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.h). #### to_string Called whenever fwupd needs a human-readable representation of the device. #### probe The `probe` method is called the first time a device is opened, before actually opening it. The generic probe methods implemented in the base device types (such as USB/udev) take care of basic device identification and setting the non-specific parameters that don't need the device to be opened or the interface claimed (vendor id, product id, guids, etc.). The device-specific probe method should start by calling the generic method upwards in the class tree and then do any other specific setup such as setting the appropriate device flags. #### open Depending on the type of device, opening it means different things. For instance, opening a udev device means opening its device file. If there's no interface-specific `open` method, then opening a device simply calls the `probe()` and `setup()` methods (the `open()` method would be called in between if it exists). #### setup Sets parameters on the device object that require the device to be open and have the interface claimed. USB/udev generic devices don't implement this method, this is normally implemented for each different plugin device type if needed. #### prepare If implemented, this takes care of decompressing or parsing the firmware data. For example, to check if the firmware is valid, if it's suitable for the device, etc. It takes a stream of bytes (`GBytes`) as a parameter, representing the raw binary firmware data. It should create the firmware object and call the appropriate method to load the firmware. Otherwise, if it's not implemented for the specific device type, the generic implementation in [libfwupdplugin/fu-device.c](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.c):`fu_device_prepare_firmware()` creates a firmware object loaded with a provided image. #### detach Implemented if the device needs to be put in bootloader mode before updating, this does all the necessary operations to put the device in that mode. fwupd can handle the case where a device needs to be disconnected to do the mode switch if the device has the `FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG` flag. #### attach The inverse of `detach()`, to configure the device back to application mode. #### reload If implemented, this is called after the device update if it needs to perform any kind of post-update operation. #### write_firmware Writes a firmware passed as a raw byte stream. The firmware parsing and processing is done by the firmware object, so that when this method gets the blob it simply has to write it to the device in the appropriate way following the device update protocol. #### read_firmware Reads the firmware data from the device without any device-specific configuration or serial numbers. This is meant to retrieve the current firmware contents for verification purposes. The data read can then be output to a binary blob using `fu_firmware_write()`. #### set_progress Informs the daemon of the expected duration percentages for the different phases of update. The daemon runs the `->detach()`, `->write_firmware()`, `->attach()` and `->reload()` phases as part of the engine during the firmware update (rather than being done by plugin-specific code) and so this vfunc informs the daemon how to scale the progress output accordingly. For instance, if your update takes 2 seconds to detach into bootloader mode, 10 seconds to write the firmware, 7 seconds to attach back into runtime mode (which includes the time required for USB enumeration) and then 1 second to read the new firmware version you would use: fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 10, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 45, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 40, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "reload"); If however your device does not require `->detach()` or `->attach()`, and `->reload()` is instantaneous, you still however need to include 4 steps: fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); If the device has multiple phases that occur when actually in the write phase then it is perfectly okay to split up the `FuProgress` steps in the `->write_firmware()` vfunc further. For instance: fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "wait-for-idle"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "reset"); It should be noted that actions that are required to be done *before* the update should be added as a `->prepare()` vfunc, and those to be done after in the `->cleanup()` as the daemon will then recover the hardware if the update fails. For instance, putting the device back into a *normal runtime power saving* state should always be done during cleanup. ### Creating a new firmware type The same way a device type implements some methods to complete its functionality and override certain behaviors, there's a set of firmware methods that a firmware class can (or must) implement: #### parse If implemented, it parses the firmware file passed as a byte sequence. If the firmware to be used contains a custom header, a specific structured format or multiple images embedded, this method should take care of processing the format and appropriately populating the `FuFirmware` object passed as a parameter. If not implemented, the whole data blob is taken as is. #### write Returns a `FuFirmware` object as a byte sequence. This can be used to output a firmware read with `fu_device_read_firmware()` as a binary blob. #### export Converts a `FuFirmware` object to an xml representation. If not implemented, the default implementation generates an xml representation containing only generic attributes and, optionally, the firmware data as well as the representation of children firmware nodes. When testing the implementation of a new firmware type, this is useful to show if the parsing and processing of the firmware are correct and can be checked with: fwupdtool firmware-parse --plugins #### tokenize If implemented it tokenizes a firmware, breaking it into records. #### build This is the reverse of `export()`, it builds a `FuFirmware` object from an xml representation. #### get_checksum The default implementation returns a checksum of the payload data of a `FuFirmware` object. Subclass it only if the checksum of your firmware needs to be computed differently. ### Generating a skeleton Rather than copy-and-pasting from other plugins, or using the `FuDeviceClass` as a guide we have also provided a script that can generate a plugin skeleton. This skeleton contains all the parts typically needed by a plugin, and plugin developers might find it easier to delete unneeded code rather then trying to copy and paste the correct code from other plugins. To use this, navivate to the root directory and run: ./contrib/create-plugin.py \ --vendor VendorName \ --example ProductName \ --parent Usb \ --author "Your Name" \ --email "your@email.com" ### Device identification A device is identified in fwupd by its physical and logical ids. A physical id represents the electrical connection of the device to the system and many devices can have the same physical id. For example, `PCI_SLOT_NAME=0000:3e:00:0` (see [libfwupdplugin/fu-udev-device.c](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-udev-device.c):`fu_udev_device_set_physical_id()` for examples) . The logical id is used to disambiguate devices with the same physical id. Together they identify a device uniquely. There are many examples of this in the existing plugins, such as `fu_pxi_receiver_device_add_peripherals()` in Besides that, each device type will have a unique instance id, which is a string representing the device subsystem, vendor, model and revision (specific details depend on the device type). This should identify a device type in the system, that is, a particular device type, model and revision by a specific vendor will have a defined instance id and two of the same device will have the same instance id (see [libfwupdplugin/fu-udev-device.c](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-udev-device.c):`fu_udev_device_probe()` for examples). One or more GUIDs are generated for a device from its identifying attributes, these GUIDs are then used to match a firmware metadata against a specific device type. See the implementation of the many `probe()` methods for examples. ### Support for BLE devices BLE support in fwupd on Linux is provided by BlueZ. If the device implements the standard HID-over-GATT BLE profile, then communication with the device can be done through the [hidraw interface](https://www.kernel.org/doc/html/latest/hid/hidraw.html). If the device implements a custom BLE profile instead, then it will have to be managed by the `FuBluezBackend`, which uses the BlueZ DBus interface to communicate with the devices. The `FuBluezDevice` type implements device enumeration as well as the basic primitives to read and write BLE characteristics, and can be used as the base type for a more specific BLE device. ### Battery checks If the device can be updated wirelessly or if the update process doesn't rely on an external power supply, the vendor might define a minimum operative battery level to guarantee a correct update. fwupd provides a simple API to define these requirements per-device. [fu_device_set_battery_threshold()](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.c) can be used to define the minimum battery level required to allow a firmware update on a device (10% by default). If the battery level is below that threshold, fwupd will inhibit the device to prevent the user from starting a firmware update. Then, the battery level of a device can be queried and then set with [fu_device_set_battery_level()](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.c). ## Howtos ### How to create a child device fwupd devices can be hierarchically ordered to model dependent and composite devices such as docking stations composed of multiple updatable chips. When writing support for a new composite device the parent device should, at some point, poll the devices that "hang" from it and register them in fwupd. The process of polling and identifying a child device is totally vendor and device-specific, although the main requirement for it is that the child device is properly identified (having physical/logical and instance ids). Then, [fu_device_add_child()](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.c) can be used to add a new child device to an existing one. See `fu_logitech_hidpp_runtime_bolt_poll_peripherals()` in for an example. Note that when deploying and installing a firmware set for a composite device, there might be firmware dependencies between parent and child devices that require a specific update ordering (for instance, child devices first, then the parent). This can be modeled by setting an appropriate firmware priority in the firmware metainfo or by setting the `FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST` device flag. ### How to add a delay In certain scenarios you may need to introduce small controlled delays in the plugin code, for instance, to comply with a communications protocol or to wait for the device to be ready after a particular operation. In this case you can insert a delay in microseconds with `g_usleep` or a delay in seconds that shows a progress bar with `fu_device_sleep_with_progress`. Note that, in both cases, this will stop the application main loop during the wait, so use it only when necessary. ### How to define private flags Besides the regular flags and internal flags that any device can have, a device can define private flags for specific uses. These can be enabled in the code as well as in quirk files, just as the rest of flags. To define a private flag: 1. Define the flag value. This is normally defined as a macro that expands to a binary flag, for example: `#define MY_PRIVATE_FLAG (1 << 2)`. Note that this will be part of the ABI, so it must be versioned 1. Call `fu_device_register_private_flag` in the device init function and assign a string identifier to the flag: `fu_device_register_private_flag (FU_DEVICE (self), MY_PRIVATE_FLAG, "myflag");` You can then add it to the device programmatically with `fu_device_add_private_flag`, remove it with `fu_device_remove_private_flag` and query it with `fu_device_has_private_flag`. In a quirk file, you can add the flag identifier to the Flags attribute of a device (eg. `Flags = myflag,is-bootloader`) ### How to make fwupd wait for a device replug Certain devices require a disconnection and reconnection to start the update process. A common example are devices that have two booting modes: application or runtime mode, and bootloader mode, where the runtime mode is the normal operation mode and the bootloader mode is exclusively used to update the device firmware. It's common for these devices to require some operation from fwupd to switch the booting mode and then to need a reset to enter bootloader mode. Often, the device is enumerated differently in both modes, so fwupd needs to know that the same device will be identified differently depending on the boot mode. The common way to do this is to add the `FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG` flag in the device before its detach method returns. This will make fwupd wait for a predetermined amount of time for the device to be detected again. Then, to inform fwupd about the two identities of the same device, the `CounterpartGuid` key can be used in a device entry to match it with another defined device (example: ). ### Inhibiting a device If a device becomes unsuitable for an update for whatever reason (see "Battery checks" above for an example), a plugin can temporarily disable firmware updates on it by calling [fu_device_inhibit()](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.c). The device will still be listed as present by `fwupdmgr get-devices`, but fwupd won't allow firmware updates on it. Device inhibition can be disabled with [fu_device_uninhibit()](https://github.com/fwupd/fwupd/blob/main/libfwupdplugin/fu-device.c). Note that there might be multiple inhibits on a specific device, the device will only be updatable when all of them are removed. ## Debugging tips The most important rule when debugging is using the `--verbose` and duplicate `--verbose` flag when running fwupd or fwupdtool. ### Adding debug messages The usual way to print a debug message is using the `g_debug` macro. Each relevant module will define its own `G_LOG_DOMAIN` to tag the debug traces accordingly. See and for more information. ### Inspecting raw binary data The `fu_dump_full` and `fu_dump_raw` functions implement the printing of a binary buffer to the console as a stream of bytes in hexadecimal. See `libfwupdplugin/fu-common.c` for their definitions, you can find many examples of how to use them in the plugins code. ## The rustgen Helper The rustgen script generates C source files that allow parsing, modifying and querying a packed structure or enumeration. This functionality is provided as parsing untrusted structured data from devices or firmware files is something fwupd does *a lot*, and so it makes sense to abstract out common code for maintainability reasons. It also allows us to force best-practices into the plugins without having to do careful review of buffer reading and writing. Structures support integers of specific widths, arrays, GUIDs, strings, default and constant data of variable size. The generated code is endian safe and if used correctly, is also safe against malicious data. In most cases the structure or enumeration will be defined in a `.rs` file -- which is the usual file extension of Rust programs. This was done as the format is heavily inspired by Rust, and it makes editor highlighting support work correctly. Although these files *look like* Rust files they're *not actually compiled by rustc*, so small differences may be noticeable. #[derive(New, Validate, Parse)] struct ExampleHdr { magic: Guid, hdrver: u8, hdrsz: u16le = $struct_size, payloadsz: u32le, flags: u8, } #[derive(ToString, FromString)] #[repr(u8)] // optional, and only required if using the enum as a struct item type enum ExampleFamily { Unknown, Sps, Txe = 0x5, Me, Csme, } struct ExamplePacket { family: ExampleFamily = Csme, data: [u8; 254], } The struct types currently supported are: - `u8`: a `guint8` - `u16le`: a `guint16 - `u24`: a 24 bit number represented as a `guint32` - `u32le`: little endian `guint32` - `u64be`: big endian `guint64` - `char`: a `NUL`-terminated string - `Guid`: a GUID - Any `enum` created in the `.rs` file with `#[repr(type)]` - Any `struct` previously created in the `.rs` file Arrays of types are also allowed, with the format `[type; multiple]`, for example: - `buf: [u8; 3] = 0x123456` for a C array of `guint8 buf[3] = {0x12, 0x34, 0x56};` - `val: [u64be; 7]` for a C array of `guint64 val[7] = {0};` - `str: [char; 4] = "ABCD"` for a C array of `gchar buf[4] = {'A','B','C','D'};` -- NOTE: `fu_struct_example_get_str()` would return a `NUL`-terminated string of `ABCD\0`. Additionally, default or constant values can be auto-populated: - `$struct_size`: the total struct size - `$struct_offset`: the internal offset in the struct - string values, specified **without** double or single quotes - integer values, specified with a `0x` prefix for base-16 and with no prefix for base-10 - previously specified `enum` values Per-field metadata can also be defined, such as: - ` = `: set as the default value, or for `u8` arrays initialize with a padding byte - ` == `: set as the default, and is **also** verified during unpacking. Default values and padding will be used when creating a new structure, for instance using `fu_struct_example_new()`. ### Building When building a plugin with meson a generator can be used: diff --git a/plugins/example/meson.build b/plugins/example/meson.build @@ -3,7 +3,6 @@ plugin_quirks += files('example.quirk') plugin_builtins += static_library('fu_plugin_example', + rustgen.process('fu-example.rs'), sources: ...which creates the files `plugins/libfu_plugin_example.a.p/fu-example-struct.c` and `plugins/libfu_plugin_example.a.p/fu-example-struct.h` in the build tree. The latter can be included using `#include fu-example-struct.h` in the existing plugin code. ### Structs There are traits that control the generation of struct code. These include: - `New`: for `fu_struct_example_new()`, needed to create new instances - `Validate`: for `fu_struct_example_validate()`, needed to check memory buffers are valid - `Parse`: for `fu_struct_example_parse()`, to create a struct from a memory buffer - `Getters`: for `fu_struct_example_get_XXXX()`, to get access to field values - `Setters`: for `fu_struct_example_set_XXXX()`, to set specific field values `Getters` is implied by `Parse`, and `[Getters,Setters]` is implied by `New`. Regardless of traits used, the header offset addresses are defined, for instance: #define FU_STRUCT_EXAMPLE_OFFSET_MAGIC 0x0 #define FU_STRUCT_EXAMPLE_OFFSET_HDRVER 0x10 #define FU_STRUCT_EXAMPLE_OFFSET_HDRSZ 0x11 #define FU_STRUCT_EXAMPLE_OFFSET_PAYLOADSZ 0x13 #define FU_STRUCT_EXAMPLE_OFFSET_FLAGS 0x17 Any elements defined as a typed array (e.g. `[u8; 16]`) will also have the element size defined in bytes: #define FU_STRUCT_EXAMPLE_SIZE_MAGIC 0x10 If the default has been set (but not a constant value) the default is also defined: #define FU_STRUCT_EXAMPLE_DEFAULT_HDRSZ 24 Finally, the size in bytes of the whole structure is also included: #define FU_STRUCT_EXAMPLE_SIZE 0x18 **NOTE:** constants never have getters or setters defined -- they're constant after all. They are verified during `_validate()` and `_parse()` however. ### Enums There are traits that control the generation of enum code. These include: - `ToString`: for `fu_example_family_to_string()`, needed to create output - `ToBitString`: for `fu_example_family_to_string()`, needed to create output for bitfields - `FromString`: for `fu_example_family_from_string()`, needed to parse input **NOTE:** Enums are defined as a native unsigned type, and should not be copied by reference without first casting to an integer of known width. fwupd-1.9.16/docs/urlmap_fwupd.js000066400000000000000000000002311460375044200167310ustar00rootroot00000000000000baseURLs = [ [ 'Gio', 'https://people.gnome.org/~ebassi/docs/_build/Gio/' ], [ 'GObject', 'https://people.gnome.org/~ebassi/docs/_build/GObject/' ], ] fwupd-1.9.16/docs/urlmap_fwupdplugin.js000066400000000000000000000002671460375044200201610ustar00rootroot00000000000000baseURLs = [ [ 'Gio', 'https://people.gnome.org/~ebassi/docs/_build/Gio/' ], [ 'GObject', 'https://people.gnome.org/~ebassi/docs/_build/GObject/' ], [ 'Fwupd', '../libfwupd/' ], ] fwupd-1.9.16/generate-build/000077500000000000000000000000001460375044200156315ustar00rootroot00000000000000fwupd-1.9.16/generate-build/README.md000066400000000000000000000001071460375044200171060ustar00rootroot00000000000000Build Tools =========== These scripts are only useful to build fwupd. fwupd-1.9.16/generate-build/fix_translations.py000077500000000000000000000023231460375044200215750ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ import sys import os import subprocess def _do_msgattrib(fn): argv = [ "msgattrib", "--no-location", "--translated", "--no-wrap", "--sort-output", fn, "--output-file=" + fn, ] ret = subprocess.run(argv) if ret.returncode != 0: return def _do_nukeheader(fn): clean_lines = [] with open(fn) as f: lines = f.readlines() for line in lines: if line.startswith('"POT-Creation-Date:'): continue if line.startswith('"PO-Revision-Date:'): continue if line.startswith('"Last-Translator:'): continue clean_lines.append(line) with open(fn, "w") as f: f.writelines(clean_lines) def _process_file(fn): _do_msgattrib(fn) _do_nukeheader(fn) if __name__ == "__main__": if len(sys.argv) == 1: print("path required") sys.exit(1) try: dirname = sys.argv[1] for fn in os.listdir(dirname): if fn.endswith(".po"): _process_file(os.path.join(dirname, fn)) except NotADirectoryError: print("path required") sys.exit(2) fwupd-1.9.16/generate-build/generate-dbus-interface.py000077500000000000000000000031021460375044200226650ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2022 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import sys import argparse import xml.etree.ElementTree as ET def _remove_docs(parent): namespaces = {"doc": "http://www.freedesktop.org/dbus/1.0/doc.dtd"} for node in parent.findall("doc:doc", namespaces): parent.remove(node) parent.text = "" if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("src", action="store", type=str, help="metainfo source") parser.add_argument("dst", action="store", type=str, help="metainfo destination") args = parser.parse_args() tree = ET.parse(args.src) tree.text = "" for node in tree.findall("interface"): for node_prop in node.findall("property"): _remove_docs(node_prop) for node_signal in node.findall("signal"): for node_arg in node_signal.findall("arg"): _remove_docs(node_arg) _remove_docs(node_signal) for node_method in node.findall("method"): for node_arg in node_method.findall("arg"): _remove_docs(node_arg) _remove_docs(node_method) _remove_docs(node) try: ET.indent(tree, space=" ", level=0) except AttributeError: print( f"WARNING: indenting of {args.dst} disabled as python is too old", file=sys.stderr, ) pass with open(args.dst, "wb") as f: tree.write(f, encoding="UTF-8", xml_declaration=True) fwupd-1.9.16/generate-build/generate-index.py000077500000000000000000000022171460375044200211070ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring,consider-using-f-string # # Copyright (C) 2023 Richard Hughes # Copyright (C) 2023 Mario Limonciello # # SPDX-License-Identifier: LGPL-2.1+ import os import sys import argparse from jinja2 import Environment, FileSystemLoader if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-o", "--output") parser.add_argument("-m", "--man", action="append", nargs=1) args, argv = parser.parse_known_args() if len(argv) != 1: print(f"usage: {sys.argv[0]} IN_HTML [-o OUT_HTML]\n") sys.exit(1) # strip the suffix of all args man = [] if args.man: for obj in args.man: man += [obj[0].strip('" ').split(".md")[0]] subst = {"man": man} env = Environment( loader=FileSystemLoader(os.path.dirname(argv[0])), ) template = env.get_template(os.path.basename(argv[0])) out = template.render(subst) # success if args.output: with open(args.output, "wb") as f_out: f_out.write(out.encode()) else: print(out) fwupd-1.9.16/generate-build/generate-man.py000077500000000000000000000145001460375044200205510ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring,consider-using-f-string # # Copyright (C) 2023 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import os import sys import argparse from typing import List, Dict from jinja2 import Environment, FileSystemLoader, select_autoescape def _replace_bookend(line: str, search: str, replace_l: str, replace_r: str) -> str: try: while line.find(search) != -1: it = iter(line.split(search, maxsplit=2)) line_tmp: str = "" for token_before in it: line_tmp += token_before line_tmp += replace_l line_tmp += next(it) # token_mid line_tmp += replace_r line_tmp += next(it) # token_after line = line_tmp except StopIteration: pass return line def _strip_md(data: str) -> str: content = "" for line in data.split("\n"): # skip the man page header if line.startswith("%"): continue if line.startswith("|"): line = line[2:] # create links to other "man" pages if line.startswith("<") and (line.endswith("(1)>") or line.endswith("(5)>")): line = line.strip("<>") name = line.split("(")[0] line = f"[`{line}`](./{name}.html)" content += f"{line}\n" return content def _convert_md_to_man(data: str) -> str: sections = data.split("\n\n") troff_lines: List[str] = [] # ignore the docgen header if sections[0].startswith("---"): sections = sections[1:] # header split = sections[0].split(" ", maxsplit=4) if split[0] != "%" or split[3] != "|": print( "no man header detected, expected something like " "'% fwupdagent(1) 1.2.5 | fwupdagent man page' and got {}".format( sections[0] ) ) sys.exit(1) man_cmd = split[1][:-3] man_sect = int(split[1][-2:-1]) troff_lines.append(f'.TH "{man_cmd}" "{man_sect}" "" {split[2]} "{split[4]}"') troff_lines.append(".hy") # hyphenate # content for section in sections[1:]: lines = section.split("\n") sectkind: str = ".PP" # begin a new paragraph sectalign: int = 4 # convert markdown headers to section headers if lines[-1].startswith("##"): lines = [lines[-1].strip("#").strip()] sectkind = ".SH" # join long lines line = "" indent = False for line_tmp in lines: if not line_tmp: continue if line_tmp.startswith("```"): indent = not indent line_tmp = "```" # strip the language if line_tmp.startswith("| "): line_tmp = line_tmp[2:] if indent: line += ".nf\n" line += line_tmp + "\n" line += ".fi\n" continue elif line_tmp.startswith("* "): line += ".IP \\[bu] 2\n" line += line_tmp[2:] continue line_tmp = line_tmp.replace("\\-", "-") line += line_tmp if line_tmp.endswith("."): line += " " line += " " # make bold, and add smart quotes line = _replace_bookend(line, "**", "\\f[B]", "\\f[R]") line = _replace_bookend(line, "`", "\\f[B]", "\\f[R]") line = _replace_bookend(line, '"', "“", "”") # add troff if sectalign != 4: troff_lines.append(f".RS {sectalign}") troff_lines.append(sectkind) troff_lines.append(line) if sectalign != 4: troff_lines.append(".RE") # success return "\n".join(troff_lines) def _add_defines(defines: Dict[str, str], fn: str) -> None: with open(fn, "rb") as f: for line in f.read().decode().split("\n"): if line.find("set_config_default") == -1: continue try: _, wrapped_key, wrapped_value = line.split(",", maxsplit=2) wrapped_value = wrapped_value.rsplit(")", maxsplit=1)[0] except ValueError: print(f"failed to define value for {line} in {fn}") continue try: _, key, _ = wrapped_key.split('"', maxsplit=2) except ValueError: continue try: _, value, _ = wrapped_value.split('"', maxsplit=2) except ValueError: value = wrapped_value.strip() source_prefix: str = ( os.path.basename(os.path.dirname(fn)).replace("-", "_") + "_" ) if source_prefix == "src_": source_prefix = "" defines[f"{source_prefix}{key}"] = {"NULL": ""}.get(value, value) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-o", "--output") parser.add_argument( "-r", "--replace", action="append", nargs=2, metavar=("symbol", "version") ) parser.add_argument("-d", "--defines", action="append") parser.add_argument("--md", action="store_true") args, argv = parser.parse_known_args() if len(argv) != 1: print(f"usage: {sys.argv[0]} MARKDOWN [-o TROFF]\n") sys.exit(1) # load in #defines to populate the defaults subst: Dict[str, str] = {} if args.defines: for fn_define in args.defines: try: _add_defines(subst, fn_define) except FileNotFoundError: print(f"{fn_define} not found") sys.exit(1) # static defines if args.replace: for key, value in args.replace: subst[key] = value # use Jinja2 to process as a template env = Environment( loader=FileSystemLoader(os.path.dirname(argv[0])), autoescape=select_autoescape(), keep_trailing_newline=True, ) template = env.get_template(os.path.basename(argv[0])) rendered = template.render(subst) # Stripped markdown mode is used for HTML docs if args.md: out = _strip_md(rendered) else: out = _convert_md_to_man(rendered) # success if args.output: with open(args.output, "wb") as f_out: f_out.write(out.encode()) else: print(out) fwupd-1.9.16/generate-build/generate-metainfo.py000077500000000000000000000016321460375044200216020ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2022 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import argparse import xml.etree.ElementTree as ET if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( "-r", "--releases", type=int, default=5, ) parser.add_argument( "filename_src", action="store", type=str, help="metainfo source" ) parser.add_argument( "filename_dst", action="store", type=str, help="metainfo destination" ) args = parser.parse_args() tree = ET.parse(args.filename_src) root = tree.getroot().findall("releases")[0] for release in root.findall("release")[args.releases :]: root.remove(release) with open(args.filename_dst, "wb") as f: tree.write(f, encoding="UTF-8", xml_declaration=True) fwupd-1.9.16/generate-build/generate-plugins-header.py000066400000000000000000000022061460375044200227020ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2022 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import sys if len(sys.argv) < 3: print("not enough arguments") sys.exit(1) with open(sys.argv[1], "w") as f: # empty argument is no plugins plugin_names = [] if sys.argv[3]: for fullpath in sys.argv[3].split(","): parts = fullpath.split("/") name = parts[-1] if name.startswith("libfu_plugin_"): name = name[13:] if name.endswith(".a"): name = name[:-2] plugin_names.append((parts[-2], name)) # includes for dirname, name in plugin_names: f.write( '#include "{srcdir}/plugins/{dirname}/fu-{name}-plugin.h"\n'.format( srcdir=sys.argv[2], dirname=dirname, name=name.replace("_", "-") ) ) # GTypes gtypes = [f"fu_{name}_plugin_get_type" for _, name in plugin_names] f.write( "GType (*fu_plugin_externals[])(void) = { %s };\n" % ", ".join(gtypes + ["NULL"]) ) sys.exit(0) fwupd-1.9.16/generate-build/generate-quirk-builtin.py000077500000000000000000000015311460375044200225750ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2023 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import argparse import gzip from typing import List if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("output", action="store", type=str, help="output") parser.add_argument("input", nargs="*", help="input") args = parser.parse_args() lines: List[str] = [] for fn in args.input: with open(fn, "rb") as f: for line in f.read().decode().split("\n"): if not line: continue if line.startswith("#"): continue lines.append(line) with gzip.GzipFile(args.output, "wb", mtime=0) as f: f.write("\n".join(lines).encode()) fwupd-1.9.16/generate-build/generate-version-script.py000077500000000000000000000107561460375044200227760ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2017 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import sys import argparse import xml.etree.ElementTree as ET XMLNS = "{http://www.gtk.org/introspection/core/1.0}" XMLNS_C = "{http://www.gtk.org/introspection/c/1.0}" def parse_version(ver): return tuple(map(int, ver.split("."))) def usage(return_code): """print usage and exit with the supplied return code""" if return_code == 0: out = sys.stdout else: out = sys.stderr out.write(f"usage: {sys.argv[0]} \n") sys.exit(return_code) class LdVersionScript: """Rasterize some text""" def __init__(self, library_name): self.library_name = library_name self.releases = {} self.overrides = {} def _add_node(self, node): identifier = node.attrib[XMLNS_C + "identifier"] introspectable = int(node.get("introspectable", 1)) version = node.get("version", None) if introspectable and not version: print("No version for", identifier) sys.exit(1) if not version: return None version = node.attrib["version"] if version not in self.releases: self.releases[version] = [] release = self.releases[version] if identifier not in release: release.append(identifier) return version def _add_cls(self, cls): # add all class functions for node in cls.findall(XMLNS + "function"): self._add_node(node) # choose the lowest version method for the _get_type symbol version_lowest = None # add all class methods for node in cls.findall(XMLNS + "method"): version_tmp = self._add_node(node) if version_tmp: if not version_lowest or parse_version(version_tmp) < parse_version( version_lowest ): version_lowest = version_tmp # add the constructor for node in cls.findall(XMLNS + "constructor"): version_tmp = self._add_node(node) if version_tmp: if not version_lowest or parse_version(version_tmp) < parse_version( version_lowest ): version_lowest = version_tmp if "{http://www.gtk.org/introspection/glib/1.0}get-type" not in cls.attrib: return type_name = cls.attrib["{http://www.gtk.org/introspection/glib/1.0}get-type"] # finally add the get_type symbol version = self.overrides.get(type_name, version_lowest) if version: self.releases[version].append(type_name) def import_gir(self, filename): tree = ET.parse(filename) root = tree.getroot() for ns in root.findall(XMLNS + "namespace"): for node in ns.findall(XMLNS + "function"): self._add_node(node) for cls in ns.findall(XMLNS + "record"): self._add_cls(cls) for cls in ns.findall(XMLNS + "class"): self._add_cls(cls) def render(self): # get a sorted list of all the versions versions = [] for version in self.releases: versions.append(version) # output the version data to a file verout = "# generated automatically, do not edit!\n" oldversion = None for version in sorted(versions, key=parse_version): symbols = sorted(self.releases[version]) verout += "\n%s_%s {\n" % (self.library_name, version) verout += " global:\n" for symbol in symbols: verout += f" {symbol};\n" verout += " local: *;\n" if oldversion: verout += "} %s_%s;\n" % (self.library_name, oldversion) else: verout += "};\n" oldversion = version return verout if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( "-r", "--override", action="append", nargs=2, metavar=("symbol", "version") ) args, argv = parser.parse_known_args() if len(argv) != 3: usage(1) ld = LdVersionScript(library_name=argv[0]) if args.override: for override_symbol, override_version in args.override: ld.overrides[override_symbol] = override_version ld.import_gir(argv[1]) open(argv[2], "w").write(ld.render()) fwupd-1.9.16/generate-build/meson.build000066400000000000000000000010041460375044200177660ustar00rootroot00000000000000generate_metainfo = [python3, files('generate-metainfo.py')] generate_version_script = [python3, files('generate-version-script.py')] generate_plugins_header = [python3, files('generate-plugins-header.py')] generate_quirk_builtin = [python3, files('generate-quirk-builtin.py')] generate_dbus_interface = [python3, files('generate-dbus-interface.py')] generate_man = [python3, files('generate-man.py')] generate_index = [python3, files('generate-index.py')] fix_translations = [python3, files('fix_translations.py')] fwupd-1.9.16/libfwupd/000077500000000000000000000000001460375044200145565ustar00rootroot00000000000000fwupd-1.9.16/libfwupd/README.md000066400000000000000000000044601460375044200160410ustar00rootroot00000000000000# libfwupd ## Planned API/ABI changes for next release * Typedef `FwupdFeatureFlags` to `guint64` so it's the same size on all platforms * Remove the `soup-session` fallback property in `FwupdClient`. * Remove fwupd_device_set_vendor_id() and fwupd_device_get_vendor_id() * Remove the deprecated flags like `FWUPD_DEVICE_FLAG_MD_SET_ICON` * Remove `fwupd_release_get_uri()` and `fwupd_release_set_uri()` * Rename `fwupd_client_install_release2_async()` to `fwupd_client_install_release_async()` * Remove fwupd_device_set_protocol() and fwupd_device_get_protocol() * Remove deprecated install flag `FWUPD_INSTALL_FLAG_IGNORE_POWER` * Rename `fwupd_remote_set_checksum()` to `fwupd_remote_set_checksum_sig()` * Remove the deprecated flag `FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_IPFS` * Rename `fwupd_client_refresh_remote2_async()` to `fwupd_client_refresh_remote_async()` * Remove the deprecated method `fwupd_remote_get_enabled()` * Remove the deprecated method `fwupd_remote_get_approval_required()` * Remove the deprecated method `fwupd_remote_get_automatic_reports()` * Remove the deprecated method `fwupd_remote_get_automatic_security_reports()` * Rename CET and SMAP security attributes `FWUPD_SECURITY_ATTR_ID_INTEL_SMAP` and `FWUPD_SECURITY_ATTR_ID_INTEL_CET_ENABLED` and `FWUPD_SECURITY_ATTR_ID_INTEL_CET_ACTIVE` ## Migration from Version 0.9.x * Rename FU_DEVICE_FLAG -> FWUPD_DEVICE_FLAG * Rename FWUPD_DEVICE_FLAG_ALLOW_ONLINE -> FWUPD_DEVICE_FLAG_UPDATABLE * Rename FWUPD_DEVICE_FLAG_ALLOW_OFFLINE -> FWUPD_DEVICE_FLAG_ONLY_OFFLINE * Rename fwupd_client_get_devices_simple -> fwupd_client_get_devices * Rename fwupd_client_get_details_local -> fwupd_client_get_details * Rename fwupd_client_update_metadata_with_id -> fwupd_client_update_metadata * Rename fwupd_remote_get_uri -> fwupd_remote_get_metadata_uri * Rename fwupd_remote_get_uri_asc -> fwupd_remote_get_metadata_uri_sig * Rename fwupd_remote_build_uri -> fwupd_remote_build_firmware_uri * Switch FWUPD_RESULT_KEY_DEVICE_CHECKSUM_KIND to fwupd_checksum_guess_kind() * Rename fwupd_result_update_*() to fwupd_release_*() * Rename fwupd_result_*() to fwupd_device_*() * Convert FwupdResult to FwupdDevice in all callbacks * Rename fwupd_device_**provider -> fwupd_device**_plugin * Convert hash types sa{sv} -> a{sv} * Convert fwupd_client_get_updates() -> fwupd_client_get_upgrades() fwupd-1.9.16/libfwupd/fwupd-bios-setting-private.h000066400000000000000000000010201460375044200221220ustar00rootroot00000000000000/* * Copyright (C) 2022 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include #include "fwupd-bios-setting.h" #pragma once GVariant * fwupd_bios_setting_to_variant(FwupdBiosSetting *self, gboolean trusted) G_GNUC_NON_NULL(1); void fwupd_bios_setting_to_json(FwupdBiosSetting *self, JsonBuilder *builder) G_GNUC_NON_NULL(1, 2); gboolean fwupd_bios_setting_from_json(FwupdBiosSetting *self, JsonNode *json_node, GError **error) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupd/fwupd-bios-setting.c000066400000000000000000001013321460375044200204540ustar00rootroot00000000000000/* * Copyright (C) 2022 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fwupd-bios-setting-private.h" #include "fwupd-common-private.h" #include "fwupd-enums-private.h" #include "fwupd-error.h" /** * FwupdBiosSetting: * * A BIOS setting that represents a setting in the firmware. */ static void fwupd_bios_setting_finalize(GObject *object); typedef struct { FwupdBiosSettingKind kind; gchar *id; gchar *name; gchar *description; gchar *path; gchar *current_value; guint64 lower_bound; guint64 upper_bound; guint64 scalar_increment; gboolean read_only; GPtrArray *possible_values; } FwupdBiosSettingPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FwupdBiosSetting, fwupd_bios_setting, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_bios_setting_get_instance_private(o)) /** * fwupd_bios_setting_get_id * @self: a #FwupdBiosSetting * * Gets the unique attribute identifier for this attribute/driver * * Returns: attribute ID if set otherwise NULL * * Since: 1.8.4 **/ const gchar * fwupd_bios_setting_get_id(FwupdBiosSetting *self) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), NULL); return priv->id; } /** * fwupd_bios_setting_set_id * @self: a #FwupdBiosSetting * * Sets the unique attribute identifier for this attribute * * Since: 1.8.4 **/ void fwupd_bios_setting_set_id(FwupdBiosSetting *self, const gchar *id) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_BIOS_SETTING(self)); /* not changed */ if (g_strcmp0(priv->id, id) == 0) return; g_free(priv->id); priv->id = g_strdup(id); } /** * fwupd_bios_setting_get_read_only: * @self: a #FwupdBiosSetting * * Determines if a BIOS setting is read only * * Returns: gboolean * * Since: 1.8.4 **/ gboolean fwupd_bios_setting_get_read_only(FwupdBiosSetting *self) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), FALSE); return priv->read_only; } /** * fwupd_bios_setting_set_read_only: * @self: a #FwupdBiosSetting * * Configures whether an attribute is read only * maximum length for string attributes. * * * Since: 1.8.4 **/ void fwupd_bios_setting_set_read_only(FwupdBiosSetting *self, gboolean val) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_BIOS_SETTING(self)); priv->read_only = val; } /** * fwupd_bios_setting_get_lower_bound: * @self: a #FwupdBiosSetting * * Gets the lower bound for integer attributes or * minimum length for string attributes. * * Returns: guint64 * * Since: 1.8.4 **/ guint64 fwupd_bios_setting_get_lower_bound(FwupdBiosSetting *self) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), 0); return priv->lower_bound; } /** * fwupd_bios_setting_get_upper_bound: * @self: a #FwupdBiosSetting * * Gets the upper bound for integer attributes or * maximum length for string attributes. * * Returns: guint64 * * Since: 1.8.4 **/ guint64 fwupd_bios_setting_get_upper_bound(FwupdBiosSetting *self) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), 0); return priv->upper_bound; } /** * fwupd_bios_setting_get_scalar_increment: * @self: a #FwupdBiosSetting * * Gets the scalar increment used for integer attributes. * * Returns: guint64 * * Since: 1.8.4 **/ guint64 fwupd_bios_setting_get_scalar_increment(FwupdBiosSetting *self) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), 0); return priv->scalar_increment; } /** * fwupd_bios_setting_set_upper_bound: * @self: a #FwupdBiosSetting * @val: a guint64 value to set bound to * * Sets the upper bound used for BIOS integer attributes or max * length for string attributes. * * Since: 1.8.4 **/ void fwupd_bios_setting_set_upper_bound(FwupdBiosSetting *self, guint64 val) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_BIOS_SETTING(self)); priv->upper_bound = val; } /** * fwupd_bios_setting_set_lower_bound: * @self: a #FwupdBiosSetting * @val: a guint64 value to set bound to * * Sets the lower bound used for BIOS integer attributes or max * length for string attributes. * * Since: 1.8.4 **/ void fwupd_bios_setting_set_lower_bound(FwupdBiosSetting *self, guint64 val) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_BIOS_SETTING(self)); priv->lower_bound = val; } /** * fwupd_bios_setting_set_scalar_increment: * @self: a #FwupdBiosSetting * @val: a guint64 value to set increment to * * Sets the scalar increment used for BIOS integer attributes. * * Since: 1.8.4 **/ void fwupd_bios_setting_set_scalar_increment(FwupdBiosSetting *self, guint64 val) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_BIOS_SETTING(self)); priv->scalar_increment = val; } /** * fwupd_bios_setting_get_kind: * @self: a #FwupdBiosSetting * * Gets the BIOS setting type used by the kernel interface. * * Returns: the bios setting type, or %FWUPD_BIOS_SETTING_KIND_UNKNOWN if unset. * * Since: 1.8.4 **/ FwupdBiosSettingKind fwupd_bios_setting_get_kind(FwupdBiosSetting *self) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), 0); return priv->kind; } /** * fwupd_bios_setting_set_kind: * @self: a #FwupdBiosSetting * @type: a bios setting type, e.g. %FWUPD_BIOS_SETTING_KIND_ENUMERATION * * Sets the BIOS setting type used by the kernel interface. * * Since: 1.8.4 **/ void fwupd_bios_setting_set_kind(FwupdBiosSetting *self, FwupdBiosSettingKind type) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_BIOS_SETTING(self)); priv->kind = type; } /** * fwupd_bios_setting_set_name: * @self: a #FwupdBiosSetting * @name: (nullable): the attribute name * * Sets the attribute name provided by a kernel driver. * * Since: 1.8.4 **/ void fwupd_bios_setting_set_name(FwupdBiosSetting *self, const gchar *name) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_BIOS_SETTING(self)); /* not changed */ if (g_strcmp0(priv->name, name) == 0) return; g_free(priv->name); priv->name = g_strdup(name); } /** * fwupd_bios_setting_set_path: * @self: a #FwupdBiosSetting * @path: (nullable): the path the driver providing the attribute uses * * Sets path to the attribute. * * Since: 1.8.4 **/ void fwupd_bios_setting_set_path(FwupdBiosSetting *self, const gchar *path) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_BIOS_SETTING(self)); /* not changed */ if (g_strcmp0(priv->path, path) == 0) return; g_free(priv->path); priv->path = g_strdup(path); } /** * fwupd_bios_setting_set_description: * @self: a #FwupdBiosSetting * @description: (nullable): the attribute description * * Sets the attribute description. * * Since: 1.8.4 **/ void fwupd_bios_setting_set_description(FwupdBiosSetting *self, const gchar *description) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_BIOS_SETTING(self)); /* not changed */ if (g_strcmp0(priv->description, description) == 0) return; g_free(priv->description); priv->description = g_strdup(description); } /* determine if key is supposed to be positive */ static gboolean fu_bios_setting_key_is_positive(const gchar *key) { if (g_strrstr(key, "enable")) return TRUE; if (g_strcmp0(key, "true") == 0) return TRUE; if (g_strcmp0(key, "1") == 0) return TRUE; if (g_strcmp0(key, "on") == 0) return TRUE; return FALSE; } /* determine if key is supposed to be negative */ static gboolean fu_bios_setting_key_is_negative(const gchar *key) { if (g_strrstr(key, "disable")) return TRUE; if (g_strcmp0(key, "false") == 0) return TRUE; if (g_strcmp0(key, "0") == 0) return TRUE; if (g_strcmp0(key, "off") == 0) return TRUE; return FALSE; } /** * fwupd_bios_setting_map_possible_value: * @self: a #FwupdBiosSetting * @key: the string to try to map * @error: (nullable): optional return location for an error * * Attempts to map a user provided string into strings that a #FwupdBiosSetting can * support. The following heuristics are used: * - Ignore case sensitivity * - Map obviously "positive" phrases into a value that turns on the #FwupdBiosSetting * - Map obviously "negative" phrases into a value that turns off the #FwupdBiosSetting * * Returns: (transfer none): the possible value that maps or NULL if none if found * * Since: 1.8.4 **/ const gchar * fwupd_bios_setting_map_possible_value(FwupdBiosSetting *self, const gchar *key, GError **error) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); gboolean positive_key = FALSE; gboolean negative_key = FALSE; g_autofree gchar *lower_key = NULL; g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), NULL); g_return_val_if_fail(priv->kind == FWUPD_BIOS_SETTING_KIND_ENUMERATION, NULL); if (priv->possible_values->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s doesn't contain any possible values", priv->name); return NULL; } lower_key = g_utf8_strdown(key, -1); positive_key = fu_bios_setting_key_is_positive(lower_key); negative_key = fu_bios_setting_key_is_negative(lower_key); for (guint i = 0; i < priv->possible_values->len; i++) { const gchar *possible = g_ptr_array_index(priv->possible_values, i); g_autofree gchar *lower_possible = g_utf8_strdown(possible, -1); gboolean positive_possible; gboolean negative_possible; /* perfect match */ if (g_strcmp0(lower_possible, lower_key) == 0) return possible; /* fuzzy match */ positive_possible = fu_bios_setting_key_is_positive(lower_possible); negative_possible = fu_bios_setting_key_is_negative(lower_possible); if ((positive_possible && positive_key) || (negative_possible && negative_key)) return possible; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s doesn't map to any possible values for %s", key, priv->name); return NULL; } /** * fwupd_bios_setting_has_possible_value: * @self: a #FwupdBiosSetting * @val: the possible value string * * Finds out if a specific possible value was added to the attribute. * * Returns: %TRUE if the self matches. * * Since: 1.8.4 **/ gboolean fwupd_bios_setting_has_possible_value(FwupdBiosSetting *self, const gchar *val) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), FALSE); g_return_val_if_fail(val != NULL, FALSE); if (priv->possible_values->len == 0) return TRUE; for (guint i = 0; i < priv->possible_values->len; i++) { const gchar *tmp = g_ptr_array_index(priv->possible_values, i); if (g_strcmp0(tmp, val) == 0) return TRUE; } return FALSE; } /** * fwupd_bios_setting_add_possible_value: * @self: a #FwupdBiosSetting * @possible_value: the possible * * Adds a possible value to the attribute. This indicates one of the values the * kernel driver will accept from userspace. * * Since: 1.8.4 **/ void fwupd_bios_setting_add_possible_value(FwupdBiosSetting *self, const gchar *possible_value) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_BIOS_SETTING(self)); if (priv->possible_values->len > 0 && fwupd_bios_setting_has_possible_value(self, possible_value)) return; g_ptr_array_add(priv->possible_values, g_strdup(possible_value)); } /** * fwupd_bios_setting_get_possible_values: * @self: a #FwupdBiosSetting * * Find all possible values for an enumeration attribute. * * Returns: (transfer container) (element-type gchar*): all possible values. * * Since: 1.8.4 **/ GPtrArray * fwupd_bios_setting_get_possible_values(FwupdBiosSetting *self) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), NULL); g_return_val_if_fail(priv->kind == FWUPD_BIOS_SETTING_KIND_ENUMERATION, NULL); return priv->possible_values; } /** * fwupd_bios_setting_get_name: * @self: a #FwupdBiosSetting * * Gets the attribute name. * * Returns: the attribute name, or %NULL if unset. * * Since: 1.8.4 **/ const gchar * fwupd_bios_setting_get_name(FwupdBiosSetting *self) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), NULL); return priv->name; } /** * fwupd_bios_setting_get_path: * @self: a #FwupdBiosSetting * * Gets the path for the driver providing the attribute. * * Returns: (nullable): the driver, or %NULL if unfound. * * Since: 1.8.4 **/ const gchar * fwupd_bios_setting_get_path(FwupdBiosSetting *self) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), NULL); return priv->path; } /** * fwupd_bios_setting_get_description: * @self: a #FwupdBiosSetting * * Gets the attribute description which is provided by some drivers to explain * what they change. * * Returns: the attribute description, or %NULL if unset. * * Since: 1.8.4 **/ const gchar * fwupd_bios_setting_get_description(FwupdBiosSetting *self) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), NULL); return priv->description; } /** * fwupd_bios_setting_get_current_value: * @self: a #FwupdBiosSetting * * Gets the string representation of the current_value stored in an attribute * from the kernel. This value is cached; so changing it outside of fwupd may * may put it out of sync. * * Returns: the current value of the attribute. * * Since: 1.8.4 **/ const gchar * fwupd_bios_setting_get_current_value(FwupdBiosSetting *self) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), NULL); return priv->current_value; } /** * fwupd_bios_setting_set_current_value: * @self: a #FwupdBiosSetting * @value: (nullable): The string to set an attribute to * * Sets the string stored in an attribute. * This doesn't change the representation in the kernel. * * Since: 1.8.4 **/ void fwupd_bios_setting_set_current_value(FwupdBiosSetting *self, const gchar *value) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->current_value, value) == 0) return; g_free(priv->current_value); priv->current_value = g_strdup(value); } static gboolean _fu_strtoull_simple(const gchar *str, guint64 *value, GError **error) { gchar *endptr = NULL; guint base = 10; /* convert */ if (g_str_has_prefix(str, "0x")) { str += 2; base = 16; } *value = g_ascii_strtoull(str, &endptr, base); if ((gsize)(endptr - str) != strlen(str) && *endptr != '\n') { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse %s", str); return FALSE; } return TRUE; } static gboolean fwupd_bios_setting_validate_value(FwupdBiosSetting *self, const gchar *value, GError **error) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); if (priv->kind == FWUPD_BIOS_SETTING_KIND_INTEGER) { guint64 tmp = 0; if (!_fu_strtoull_simple(value, &tmp, error)) return FALSE; if (tmp < priv->lower_bound) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s is too small (%" G_GUINT64_FORMAT "); expected at least %" G_GUINT64_FORMAT, value, tmp, priv->lower_bound); return FALSE; } if (tmp > priv->upper_bound) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s is too big (%" G_GUINT64_FORMAT "); expected no more than %" G_GUINT64_FORMAT, value, tmp, priv->upper_bound); return FALSE; } return TRUE; } if (priv->kind == FWUPD_BIOS_SETTING_KIND_STRING) { gsize tmp = strlen(value); if (tmp < priv->lower_bound) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s is too short (%" G_GSIZE_FORMAT "); expected at least %" G_GUINT64_FORMAT, value, tmp, priv->lower_bound); return FALSE; } if (tmp > priv->upper_bound) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s is too long (%" G_GSIZE_FORMAT "); expected no more than %" G_GUINT64_FORMAT, value, tmp, priv->upper_bound); return FALSE; } return TRUE; } if (priv->kind == FWUPD_BIOS_SETTING_KIND_ENUMERATION) return TRUE; /* not supported */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unknown attribute type"); return FALSE; } /** * fwupd_bios_setting_write_value: * @self: a #FwupdBiosSetting * @value: (not nullable): The string to write * @error: (nullable): optional return location for an error * * Writes a new value into the setting if it is different from the current value. * * NOTE: A subclass should handle the `->write_value()` vfunc and actually write the value to the * firmware. * * Returns: %TRUE for success * * Since: 1.9.4 **/ gboolean fwupd_bios_setting_write_value(FwupdBiosSetting *self, const gchar *value, GError **error) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); FwupdBiosSettingClass *klass = FWUPD_BIOS_SETTING_GET_CLASS(self); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), FALSE); g_return_val_if_fail(value != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not changed */ if (g_strcmp0(priv->current_value, value) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "%s is already set to %s", priv->id, value); return FALSE; } /* sanity check */ if (fwupd_bios_setting_get_read_only(self)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s is read only", priv->name); return FALSE; } /* convert the value */ if (priv->kind == FWUPD_BIOS_SETTING_KIND_ENUMERATION) { value = fwupd_bios_setting_map_possible_value(self, value, error); if (value == NULL) return FALSE; } /* also done by the kernel or firmware, doing it here too allows for better errors */ if (!fwupd_bios_setting_validate_value(self, value, error)) return FALSE; /* not implemented */ if (klass->write_value == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return FALSE; } /* proxy */ return klass->write_value(self, value, error); } static gboolean fwupd_bios_setting_trusted(FwupdBiosSetting *self, gboolean trusted) { g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), FALSE); if (trusted) return TRUE; if (g_strcmp0(fwupd_bios_setting_get_name(self), "pending_reboot") == 0) return TRUE; return FALSE; } /** * fwupd_bios_setting_to_variant: * @self: a #FwupdBiosSetting * @trusted: whether the caller should receive trusted values * * Serialize the bios setting. * * Returns: the serialized data, or %NULL for error. * * Since: 1.8.4 **/ GVariant * fwupd_bios_setting_to_variant(FwupdBiosSetting *self, gboolean trusted) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); GVariantBuilder builder; g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), NULL); g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BIOS_SETTING_TYPE, g_variant_new_uint64(priv->kind)); if (priv->id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BIOS_SETTING_ID, g_variant_new_string(priv->id)); } if (priv->name != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_NAME, g_variant_new_string(priv->name)); } if (priv->path != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_FILENAME, g_variant_new_string(priv->path)); } if (priv->description != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_DESCRIPTION, g_variant_new_string(priv->description)); } g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BIOS_SETTING_READ_ONLY, g_variant_new_boolean(priv->read_only)); if (fwupd_bios_setting_trusted(self, trusted)) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE, g_variant_new_string(priv->current_value)); } if (priv->kind == FWUPD_BIOS_SETTING_KIND_INTEGER || priv->kind == FWUPD_BIOS_SETTING_KIND_STRING) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BIOS_SETTING_LOWER_BOUND, g_variant_new_uint64(priv->lower_bound)); g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BIOS_SETTING_UPPER_BOUND, g_variant_new_uint64(priv->upper_bound)); if (priv->kind == FWUPD_BIOS_SETTING_KIND_INTEGER) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BIOS_SETTING_SCALAR_INCREMENT, g_variant_new_uint64(priv->scalar_increment)); } } else if (priv->kind == FWUPD_BIOS_SETTING_KIND_ENUMERATION) { if (priv->possible_values->len > 0) { g_autofree const gchar **strv = g_new0(const gchar *, priv->possible_values->len + 1); for (guint i = 0; i < priv->possible_values->len; i++) strv[i] = (const gchar *)g_ptr_array_index(priv->possible_values, i); g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BIOS_SETTING_POSSIBLE_VALUES, g_variant_new_strv(strv, -1)); } } return g_variant_new("a{sv}", &builder); } static void fwupd_bios_setting_from_key_value(FwupdBiosSetting *self, const gchar *key, GVariant *value) { if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_TYPE) == 0) { fwupd_bios_setting_set_kind(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_ID) == 0) { fwupd_bios_setting_set_id(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_NAME) == 0) { fwupd_bios_setting_set_name(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_FILENAME) == 0) { fwupd_bios_setting_set_path(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE) == 0) { fwupd_bios_setting_set_current_value(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_DESCRIPTION) == 0) { fwupd_bios_setting_set_description(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_POSSIBLE_VALUES) == 0) { g_autofree const gchar **strv = g_variant_get_strv(value, NULL); for (guint i = 0; strv[i] != NULL; i++) fwupd_bios_setting_add_possible_value(self, strv[i]); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_LOWER_BOUND) == 0) { fwupd_bios_setting_set_lower_bound(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_UPPER_BOUND) == 0) { fwupd_bios_setting_set_upper_bound(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_SCALAR_INCREMENT) == 0) { fwupd_bios_setting_set_scalar_increment(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_READ_ONLY) == 0) { fwupd_bios_setting_set_read_only(self, g_variant_get_boolean(value)); return; } } /** * fwupd_bios_setting_from_json: * @self: a #FwupdBiosSetting * @json_node: a JSON node * @error: (nullable): optional return location for an error * * Loads a fwupd bios setting from a JSON node. * * Returns: %TRUE for success * * Since: 1.8.4 **/ gboolean fwupd_bios_setting_from_json(FwupdBiosSetting *self, JsonNode *json_node, GError **error) { JsonObject *obj; /* sanity check */ if (!JSON_NODE_HOLDS_OBJECT(json_node)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not JSON object"); return FALSE; } obj = json_node_get_object(json_node); fwupd_bios_setting_set_kind( self, json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_BIOS_SETTING_TYPE, 0)); fwupd_bios_setting_set_id( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_BIOS_SETTING_ID, NULL)); fwupd_bios_setting_set_name( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_NAME, NULL)); fwupd_bios_setting_set_description( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_DESCRIPTION, NULL)); fwupd_bios_setting_set_path( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_FILENAME, NULL)); fwupd_bios_setting_set_current_value( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE, NULL)); if (json_object_has_member(obj, FWUPD_RESULT_KEY_BIOS_SETTING_POSSIBLE_VALUES)) { JsonArray *array = json_object_get_array_member(obj, FWUPD_RESULT_KEY_BIOS_SETTING_POSSIBLE_VALUES); for (guint i = 0; i < json_array_get_length(array); i++) { const gchar *tmp = json_array_get_string_element(array, i); fwupd_bios_setting_add_possible_value(self, tmp); } } fwupd_bios_setting_set_lower_bound( self, json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_BIOS_SETTING_LOWER_BOUND, 0)); fwupd_bios_setting_set_upper_bound( self, json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_BIOS_SETTING_UPPER_BOUND, 0)); fwupd_bios_setting_set_scalar_increment( self, json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_BIOS_SETTING_SCALAR_INCREMENT, 0)); fwupd_bios_setting_set_read_only( self, json_object_get_boolean_member_with_default(obj, FWUPD_RESULT_KEY_BIOS_SETTING_READ_ONLY, FALSE)); /* success */ return TRUE; } /** * fwupd_bios_setting_to_json: * @self: a #FwupdBiosSetting * @builder: a JSON builder * * Adds a fwupd bios setting to a JSON builder. * * Since: 1.8.4 **/ void fwupd_bios_setting_to_json(FwupdBiosSetting *self, JsonBuilder *builder) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_BIOS_SETTING(self)); g_return_if_fail(builder != NULL); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_NAME, priv->name); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_DESCRIPTION, priv->description); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_FILENAME, priv->path); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_BIOS_SETTING_ID, priv->id); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE, priv->current_value); fwupd_common_json_add_boolean(builder, FWUPD_RESULT_KEY_BIOS_SETTING_READ_ONLY, priv->read_only); fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_BIOS_SETTING_TYPE, priv->kind); if (priv->kind == FWUPD_BIOS_SETTING_KIND_ENUMERATION) { if (priv->possible_values->len > 0) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_BIOS_SETTING_POSSIBLE_VALUES); json_builder_begin_array(builder); for (guint i = 0; i < priv->possible_values->len; i++) { const gchar *tmp = g_ptr_array_index(priv->possible_values, i); json_builder_add_string_value(builder, tmp); } json_builder_end_array(builder); } } if (priv->kind == FWUPD_BIOS_SETTING_KIND_INTEGER || priv->kind == FWUPD_BIOS_SETTING_KIND_STRING) { fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_BIOS_SETTING_LOWER_BOUND, priv->lower_bound); fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_BIOS_SETTING_UPPER_BOUND, priv->upper_bound); if (priv->kind == FWUPD_BIOS_SETTING_KIND_INTEGER) { fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_BIOS_SETTING_SCALAR_INCREMENT, priv->scalar_increment); } } } /** * fwupd_bios_setting_to_string: * @self: a #FwupdBiosSetting * * Builds a text representation of the object. * * Returns: text, or %NULL for invalid * * Since: 1.8.4 **/ gchar * fwupd_bios_setting_to_string(FwupdBiosSetting *self) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); GString *str; g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(self), NULL); str = g_string_new(NULL); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_NAME, priv->name); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_BIOS_SETTING_ID, priv->id); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_DESCRIPTION, priv->description); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_FILENAME, priv->path); fwupd_pad_kv_int(str, FWUPD_RESULT_KEY_BIOS_SETTING_TYPE, priv->kind); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE, priv->current_value); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_BIOS_SETTING_READ_ONLY, priv->read_only ? "True" : "False"); if (priv->kind == FWUPD_BIOS_SETTING_KIND_ENUMERATION) { for (guint i = 0; i < priv->possible_values->len; i++) { const gchar *tmp = g_ptr_array_index(priv->possible_values, i); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_BIOS_SETTING_POSSIBLE_VALUES, tmp); } } if (priv->kind == FWUPD_BIOS_SETTING_KIND_INTEGER || priv->kind == FWUPD_BIOS_SETTING_KIND_STRING) { fwupd_pad_kv_int(str, FWUPD_RESULT_KEY_BIOS_SETTING_LOWER_BOUND, priv->lower_bound); fwupd_pad_kv_int(str, FWUPD_RESULT_KEY_BIOS_SETTING_UPPER_BOUND, priv->upper_bound); if (priv->kind == FWUPD_BIOS_SETTING_KIND_INTEGER) { fwupd_pad_kv_int(str, FWUPD_RESULT_KEY_BIOS_SETTING_SCALAR_INCREMENT, priv->scalar_increment); } } return g_string_free(str, FALSE); } static void fwupd_bios_setting_class_init(FwupdBiosSettingClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fwupd_bios_setting_finalize; } static void fwupd_bios_setting_init(FwupdBiosSetting *self) { FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); priv->possible_values = g_ptr_array_new_with_free_func(g_free); } static void fwupd_bios_setting_finalize(GObject *object) { FwupdBiosSetting *self = FWUPD_BIOS_SETTING(object); FwupdBiosSettingPrivate *priv = GET_PRIVATE(self); g_free(priv->current_value); g_free(priv->id); g_free(priv->name); g_free(priv->description); g_free(priv->path); g_ptr_array_unref(priv->possible_values); G_OBJECT_CLASS(fwupd_bios_setting_parent_class)->finalize(object); } static void fwupd_bios_setting_set_from_variant_iter(FwupdBiosSetting *self, GVariantIter *iter) { GVariant *value; const gchar *key; while (g_variant_iter_next(iter, "{&sv}", &key, &value)) { fwupd_bios_setting_from_key_value(self, key, value); g_variant_unref(value); } } /** * fwupd_bios_setting_from_variant: * @value: (not nullable): the serialized data * * Creates a new bios setting using serialized data. * * Returns: (transfer full): a new #FwupdBiosSetting, or %NULL if @value was invalid. * * Since: 1.8.4 **/ FwupdBiosSetting * fwupd_bios_setting_from_variant(GVariant *value) { FwupdBiosSetting *rel = NULL; const gchar *type_string; g_autoptr(GVariantIter) iter = NULL; g_return_val_if_fail(value != NULL, NULL); type_string = g_variant_get_type_string(value); if (g_strcmp0(type_string, "(a{sv})") == 0) { rel = g_object_new(FWUPD_TYPE_BIOS_SETTING, NULL); g_variant_get(value, "(a{sv})", &iter); fwupd_bios_setting_set_from_variant_iter(rel, iter); } else if (g_strcmp0(type_string, "a{sv}") == 0) { rel = g_object_new(FWUPD_TYPE_BIOS_SETTING, NULL); g_variant_get(value, "a{sv}", &iter); fwupd_bios_setting_set_from_variant_iter(rel, iter); } else { g_warning("type %s not known", type_string); } return rel; } /** * fwupd_bios_setting_array_from_variant: * @value: (not nullable): the serialized data * * Creates an array of new bios settings using serialized data. * * Returns: (transfer container) (element-type FwupdBiosSetting): attributes, * or %NULL if @value was invalid. * * Since: 1.8.4 **/ GPtrArray * fwupd_bios_setting_array_from_variant(GVariant *value) { GPtrArray *array = NULL; gsize sz; g_autoptr(GVariant) untuple = NULL; g_return_val_if_fail(value != NULL, NULL); array = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); untuple = g_variant_get_child_value(value, 0); sz = g_variant_n_children(untuple); for (guint i = 0; i < sz; i++) { FwupdBiosSetting *rel; g_autoptr(GVariant) data = NULL; data = g_variant_get_child_value(untuple, i); rel = fwupd_bios_setting_from_variant(data); if (rel == NULL) continue; g_ptr_array_add(array, rel); } return array; } /** * fwupd_bios_setting_new: * @name: (nullable): the attribute name * @path: (nullable): the path the driver providing this attribute uses * * Creates a new bios setting. * * Returns: a new #FwupdBiosSetting. * * Since: 1.8.4 **/ FwupdBiosSetting * fwupd_bios_setting_new(const gchar *name, const gchar *path) { FwupdBiosSetting *self; self = g_object_new(FWUPD_TYPE_BIOS_SETTING, NULL); if (name != NULL) fwupd_bios_setting_set_name(self, name); if (path != NULL) fwupd_bios_setting_set_path(self, path); return FWUPD_BIOS_SETTING(self); } fwupd-1.9.16/libfwupd/fwupd-bios-setting.h000066400000000000000000000107041460375044200204630ustar00rootroot00000000000000/* * Copyright (C) 2022 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-build.h" #include "fwupd-enums.h" G_BEGIN_DECLS #define FWUPD_TYPE_BIOS_SETTING (fwupd_bios_setting_get_type()) G_DECLARE_DERIVABLE_TYPE(FwupdBiosSetting, fwupd_bios_setting, FWUPD, BIOS_SETTING, GObject) struct _FwupdBiosSettingClass { GObjectClass parent_class; gboolean (*write_value)(FwupdBiosSetting *self, const gchar *value, GError **error); /*< private >*/ void (*_fwupd_reserved2)(void); void (*_fwupd_reserved3)(void); void (*_fwupd_reserved4)(void); void (*_fwupd_reserved5)(void); void (*_fwupd_reserved6)(void); void (*_fwupd_reserved7)(void); }; /* special attributes */ #define FWUPD_BIOS_SETTING_PENDING_REBOOT "pending_reboot" #define FWUPD_BIOS_SETTING_RESET_BIOS "reset_bios" #define FWUPD_BIOS_SETTING_DEBUG_CMD "debug_cmd" /** * FwupdBiosSettingKind: * @FWUPD_BIOS_SETTING_KIND_UNKNOWN: BIOS setting type is unknown * @FWUPD_BIOS_SETTING_KIND_ENUMERATION: BIOS setting that has enumerated possible *values * @FWUPD_BIOS_SETTING_KIND_INTEGER: BIOS setting that is an integer * @FWUPD_BIOS_SETTING_KIND_STRING: BIOS setting that accepts a string * * The type of BIOS setting. **/ typedef enum { FWUPD_BIOS_SETTING_KIND_UNKNOWN = 0, /* Since: 1.8.4 */ FWUPD_BIOS_SETTING_KIND_ENUMERATION = 1, /* Since: 1.8.4 */ FWUPD_BIOS_SETTING_KIND_INTEGER = 2, /* Since: 1.8.4 */ FWUPD_BIOS_SETTING_KIND_STRING = 3, /* Since: 1.8.4 */ /*< private >*/ FWUPD_BIOS_SETTING_KIND_LAST = 4 /* perhaps increased in the future */ } FwupdBiosSettingKind; FwupdBiosSetting * fwupd_bios_setting_new(const gchar *name, const gchar *path); gchar * fwupd_bios_setting_to_string(FwupdBiosSetting *self) G_GNUC_NON_NULL(1); gboolean fwupd_bios_setting_get_read_only(FwupdBiosSetting *self) G_GNUC_NON_NULL(1); void fwupd_bios_setting_set_read_only(FwupdBiosSetting *self, gboolean val) G_GNUC_NON_NULL(1); guint64 fwupd_bios_setting_get_upper_bound(FwupdBiosSetting *self) G_GNUC_NON_NULL(1); guint64 fwupd_bios_setting_get_lower_bound(FwupdBiosSetting *self) G_GNUC_NON_NULL(1); guint64 fwupd_bios_setting_get_scalar_increment(FwupdBiosSetting *self) G_GNUC_NON_NULL(1); void fwupd_bios_setting_set_upper_bound(FwupdBiosSetting *self, guint64 val) G_GNUC_NON_NULL(1); void fwupd_bios_setting_set_lower_bound(FwupdBiosSetting *self, guint64 val) G_GNUC_NON_NULL(1); void fwupd_bios_setting_set_scalar_increment(FwupdBiosSetting *self, guint64 val) G_GNUC_NON_NULL(1); void fwupd_bios_setting_set_kind(FwupdBiosSetting *self, FwupdBiosSettingKind type) G_GNUC_NON_NULL(1); void fwupd_bios_setting_set_name(FwupdBiosSetting *self, const gchar *name) G_GNUC_NON_NULL(1); void fwupd_bios_setting_set_path(FwupdBiosSetting *self, const gchar *path) G_GNUC_NON_NULL(1); void fwupd_bios_setting_set_description(FwupdBiosSetting *self, const gchar *description) G_GNUC_NON_NULL(1); FwupdBiosSettingKind fwupd_bios_setting_get_kind(FwupdBiosSetting *self) G_GNUC_NON_NULL(1); const gchar * fwupd_bios_setting_get_name(FwupdBiosSetting *self) G_GNUC_NON_NULL(1); const gchar * fwupd_bios_setting_get_path(FwupdBiosSetting *self) G_GNUC_NON_NULL(1); const gchar * fwupd_bios_setting_get_description(FwupdBiosSetting *self) G_GNUC_NON_NULL(1); const gchar * fwupd_bios_setting_map_possible_value(FwupdBiosSetting *self, const gchar *key, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fwupd_bios_setting_has_possible_value(FwupdBiosSetting *self, const gchar *val) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_bios_setting_add_possible_value(FwupdBiosSetting *self, const gchar *possible_value) G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_bios_setting_get_possible_values(FwupdBiosSetting *self) G_GNUC_NON_NULL(1); FwupdBiosSetting * fwupd_bios_setting_from_variant(GVariant *value) G_GNUC_NON_NULL(1); GPtrArray * fwupd_bios_setting_array_from_variant(GVariant *value) G_GNUC_NON_NULL(1); const gchar * fwupd_bios_setting_get_current_value(FwupdBiosSetting *self) G_GNUC_NON_NULL(1); void fwupd_bios_setting_set_current_value(FwupdBiosSetting *self, const gchar *value) G_GNUC_NON_NULL(1); gboolean fwupd_bios_setting_write_value(FwupdBiosSetting *self, const gchar *value, GError **error) G_GNUC_NON_NULL(1, 2); const gchar * fwupd_bios_setting_get_id(FwupdBiosSetting *self) G_GNUC_NON_NULL(1); void fwupd_bios_setting_set_id(FwupdBiosSetting *self, const gchar *id) G_GNUC_NON_NULL(1); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-build.h000066400000000000000000000007541460375044200171570ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /* see https://bugzilla.gnome.org/show_bug.cgi?id=113075 */ #ifndef G_GNUC_NON_NULL #if !defined(SUPPORTED_BUILD) && !defined(_WIN32) && (__GNUC__ > 3) || \ (__GNUC__ == 3 && __GNUC_MINOR__ >= 3) #define G_GNUC_NON_NULL(params...) __attribute__((nonnull(params))) #else #define G_GNUC_NON_NULL(params...) #endif #endif fwupd-1.9.16/libfwupd/fwupd-client-private.h000066400000000000000000000024601460375044200210020ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fwupd-client.h" #ifdef HAVE_GIO_UNIX #include #endif void fwupd_client_download_bytes2_async(FwupdClient *self, GPtrArray *urls, FwupdClientDownloadFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); #ifdef HAVE_GIO_UNIX void fwupd_client_get_details_stream_async(FwupdClient *self, GUnixInputStream *istr, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); void fwupd_client_install_stream_async(FwupdClient *self, const gchar *device_id, GUnixInputStream *istr, const gchar *filename_hint, FwupdInstallFlags install_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2, 3); void fwupd_client_update_metadata_stream_async(FwupdClient *self, const gchar *remote_id, GUnixInputStream *istr, GUnixInputStream *istr_sig, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2, 3, 4); #endif fwupd-1.9.16/libfwupd/fwupd-client-sync.c000066400000000000000000002453071460375044200203100ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_GIO_UNIX #include #endif #include "fwupd-client-private.h" #include "fwupd-client-sync.h" #include "fwupd-client.h" #include "fwupd-common-private.h" #include "fwupd-error.h" typedef struct { gboolean ret; gchar *str; GError *error; GPtrArray *array; GMainContext *context; GMainLoop *loop; GVariant *val; GHashTable *hash; GBytes *bytes; FwupdDevice *device; } FwupdClientHelper; static void fwupd_client_helper_free(FwupdClientHelper *helper) { if (helper->val != NULL) g_variant_unref(helper->val); if (helper->error != NULL) g_error_free(helper->error); if (helper->array != NULL) g_ptr_array_unref(helper->array); if (helper->hash != NULL) g_hash_table_unref(helper->hash); if (helper->bytes != NULL) g_bytes_unref(helper->bytes); if (helper->device != NULL) g_object_unref(helper->device); g_free(helper->str); g_main_loop_unref(helper->loop); g_main_context_unref(helper->context); g_main_context_pop_thread_default(helper->context); g_free(helper); } static FwupdClientHelper * fwupd_client_helper_new(FwupdClient *self) { FwupdClientHelper *helper; helper = g_new0(FwupdClientHelper, 1); helper->context = fwupd_client_get_main_context(self); helper->loop = g_main_loop_new(helper->context, FALSE); g_main_context_push_thread_default(helper->context); return helper; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FwupdClientHelper, fwupd_client_helper_free) #pragma clang diagnostic pop static void fwupd_client_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_connect_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_connect: (skip) * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Sets up the client ready for use. Most other methods call this * for you, and do you only need to call this if you are just watching * the client. * * Returns: %TRUE for success * * Since: 0.7.1 **/ gboolean fwupd_client_connect(FwupdClient *self, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_connect_async(self, cancellable, fwupd_client_connect_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_quit_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_quit_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_quit: (skip) * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Asks the daemon to quit. This can only be called by the root user. * * NOTE: This will only actually quit if an install is not already in progress. * * Returns: %TRUE for success * * Since: 1.8.11 **/ gboolean fwupd_client_quit(FwupdClient *self, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_quit_async(self, cancellable, fwupd_client_quit_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_get_devices_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_devices_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_devices: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets all the devices registered with the daemon. * * Returns: (element-type FwupdDevice) (transfer container): results * * Since: 0.9.2 **/ GPtrArray * fwupd_client_get_devices(FwupdClient *self, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_devices_async(self, cancellable, fwupd_client_get_devices_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->array); } static void fwupd_client_get_plugins_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_plugins_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_plugins: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets all the plugins being used the daemon. * * Returns: (element-type FwupdPlugin) (transfer container): results * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_plugins(FwupdClient *self, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_plugins_async(self, cancellable, fwupd_client_get_plugins_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->array); } static void fwupd_client_get_history_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_history_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_history: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets all the history. * * Returns: (element-type FwupdDevice) (transfer container): results * * Since: 1.0.4 **/ GPtrArray * fwupd_client_get_history(FwupdClient *self, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_history_async(self, cancellable, fwupd_client_get_history_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->array); } static void fwupd_client_get_releases_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_releases_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_releases: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets all the releases for a specific device * * Returns: (element-type FwupdRelease) (transfer container): results * * Since: 0.9.3 **/ GPtrArray * fwupd_client_get_releases(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(device_id != NULL, NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_releases_async(self, device_id, cancellable, fwupd_client_get_releases_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->array); } static void fwupd_client_get_downgrades_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_downgrades_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_downgrades: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets all the downgrades for a specific device. * * Returns: (element-type FwupdRelease) (transfer container): results * * Since: 0.9.8 **/ GPtrArray * fwupd_client_get_downgrades(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(device_id != NULL, NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_downgrades_async(self, device_id, cancellable, fwupd_client_get_downgrades_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->array); } static void fwupd_client_get_upgrades_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_upgrades_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_upgrades: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets all the upgrades for a specific device. * * Returns: (element-type FwupdRelease) (transfer container): results * * Since: 0.9.8 **/ GPtrArray * fwupd_client_get_upgrades(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(device_id != NULL, NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_upgrades_async(self, device_id, cancellable, fwupd_client_get_upgrades_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->array); } static void fwupd_client_get_details_bytes_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_details_bytes_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_details_bytes: * @self: a #FwupdClient * @bytes: the firmware archive * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets details about a specific firmware file. * * Returns: (transfer container) (element-type FwupdDevice): an array of results * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_details_bytes(FwupdClient *self, GBytes *bytes, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(bytes != NULL, NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_details_bytes_async(self, bytes, cancellable, fwupd_client_get_details_bytes_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->array); } #ifdef HAVE_GIO_UNIX static void fwupd_client_get_details_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_details_bytes_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } #endif /** * fwupd_client_get_details: * @self: a #FwupdClient * @filename: the firmware filename, e.g. `firmware.cab` * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets details about a specific firmware file. * * Returns: (transfer container) (element-type FwupdDevice): an array of results * * Since: 1.0.0 **/ GPtrArray * fwupd_client_get_details(FwupdClient *self, const gchar *filename, GCancellable *cancellable, GError **error) { #ifdef HAVE_GIO_UNIX g_autoptr(GUnixInputStream) istr = NULL; g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(filename != NULL, NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ istr = fwupd_unix_input_stream_from_fn(filename, error); if (istr == NULL) return NULL; helper = fwupd_client_helper_new(self); fwupd_client_get_details_stream_async(self, istr, cancellable, fwupd_client_get_details_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->array); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Get Details only supported on Linux"); return NULL; #endif } static void fwupd_client_verify_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_verify_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_verify: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Verify a specific device. * * Returns: %TRUE for verification success * * Since: 0.7.0 **/ gboolean fwupd_client_verify(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_verify_async(self, device_id, cancellable, fwupd_client_verify_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_verify_update_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_verify_update_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_verify_update: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Update the verification record for a specific device. * * Returns: %TRUE for verification success * * Since: 0.8.0 **/ gboolean fwupd_client_verify_update(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_verify_update_async(self, device_id, cancellable, fwupd_client_verify_update_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_unlock_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_unlock_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_unlock: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Unlocks a specific device so firmware can be read or wrote. * * Returns: %TRUE for success * * Since: 0.7.0 **/ gboolean fwupd_client_unlock(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_unlock_async(self, device_id, cancellable, fwupd_client_unlock_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_inhibit_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->str = fwupd_client_inhibit_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_inhibit: * @self: a #FwupdClient * @reason: (not nullable): the inhibit reason, e.g. `user active` * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Marks all devices as unavailable for update. Update is only available if there is no other * inhibit imposed by other applications or by the system (e.g. low power state). * * The same application can inhibit the system multiple times. * * Returns: (transfer full): a string to use for [method@FwupdClient.uninhibit_async], * or %NULL for failure * * Since: 1.8.11 **/ gchar * fwupd_client_inhibit(FwupdClient *self, const gchar *reason, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(reason != NULL, NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_inhibit_async(self, reason, cancellable, fwupd_client_inhibit_cb, helper); g_main_loop_run(helper->loop); if (helper->str == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->str); } static void fwupd_client_uninhibit_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_uninhibit_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_uninhibit: * @self: a #FwupdClient * @inhibit_id: (not nullable): the inhibit ID * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Removes the inhibit token added by the application. * * Returns: %TRUE for success * * Since: 1.8.11 **/ gboolean fwupd_client_uninhibit(FwupdClient *self, const gchar *inhibit_id, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(inhibit_id != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_uninhibit_async(self, inhibit_id, cancellable, fwupd_client_uninhibit_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_modify_config_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_modify_config_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_modify_config * @self: a #FwupdClient * @key: config key, e.g. `DisabledPlugins` * @value: config value, e.g. `*` * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Modifies a daemon config option. * The daemon will only respond to this request with proper permissions. * * Returns: %TRUE for success * * Since: 1.2.8 **/ gboolean fwupd_client_modify_config(FwupdClient *self, const gchar *key, const gchar *value, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(value != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_modify_config_async(self, key, value, cancellable, fwupd_client_modify_config_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_activate_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_activate_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_activate: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @device_id: a device * @error: (nullable): optional return location for an error * * Activates up a device, which normally means the device switches to a new * firmware version. This should only be called when data loss cannot occur. * * Returns: %TRUE for success * * Since: 1.2.6 **/ gboolean fwupd_client_activate(FwupdClient *self, GCancellable *cancellable, const gchar *device_id, /* yes, this is the wrong way around :/ */ GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_activate_async(self, device_id, cancellable, fwupd_client_activate_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_clear_results_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_clear_results_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_clear_results: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Clears the results for a specific device. * * Returns: %TRUE for success * * Since: 0.7.0 **/ gboolean fwupd_client_clear_results(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_clear_results_async(self, device_id, cancellable, fwupd_client_clear_results_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_get_results_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->device = fwupd_client_get_results_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_results: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets the results of a previous firmware update for a specific device. * * Returns: (transfer full): a device, or %NULL for failure * * Since: 0.7.0 **/ FwupdDevice * fwupd_client_get_results(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(device_id != NULL, NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_results_async(self, device_id, cancellable, fwupd_client_get_results_cb, helper); g_main_loop_run(helper->loop); if (helper->device == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->device); } static void fwupd_client_modify_bios_setting_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_modify_bios_setting_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_modify_bios_setting * @self: a #FwupdClient * @settings: (transfer container): BIOS settings * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Modifies a BIOS setting using kernel API. * The daemon will only respond to this request with proper permissions. * * Returns: %TRUE for success * * Since: 1.8.4 **/ gboolean fwupd_client_modify_bios_setting(FwupdClient *self, GHashTable *settings, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(settings != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_modify_bios_setting_async(self, settings, cancellable, fwupd_client_modify_bios_setting_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_get_bios_settings_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_bios_settings_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_bios_settings: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets all the BIOS settings from the daemon. * * Returns: (element-type FwupdBiosSetting) (transfer container): attributes * * Since: 1.8.4 **/ GPtrArray * fwupd_client_get_bios_settings(FwupdClient *self, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_bios_settings_async(self, cancellable, fwupd_client_get_bios_settings_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->array); } static void fwupd_client_get_host_security_attrs_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_host_security_attrs_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_host_security_attrs: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets all the host security attributes from the daemon. * * Returns: (element-type FwupdSecurityAttr) (transfer container): attributes * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_host_security_attrs(FwupdClient *self, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_host_security_attrs_async(self, cancellable, fwupd_client_get_host_security_attrs_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->array); } static void fwupd_client_get_host_security_events_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_host_security_events_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_host_security_events: * @self: a #FwupdClient * @limit: maximum number of events, or 0 for no limit * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets all the host security events from the daemon. * * Returns: (element-type FwupdSecurityAttr) (transfer container): attributes * * Since: 1.7.1 **/ GPtrArray * fwupd_client_get_host_security_events(FwupdClient *self, guint limit, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_host_security_events_async(self, limit, cancellable, fwupd_client_get_host_security_events_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->array); } static void fwupd_client_get_device_by_id_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->device = fwupd_client_get_device_by_id_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_device_by_id: * @self: a #FwupdClient * @device_id: the device ID, e.g. `usb:00:01:03:03` * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets a device by its device ID. * * Returns: (transfer full): a device or %NULL * * Since: 0.9.3 **/ FwupdDevice * fwupd_client_get_device_by_id(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(device_id != NULL, NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_device_by_id_async(self, device_id, cancellable, fwupd_client_get_device_by_id_cb, helper); g_main_loop_run(helper->loop); if (helper->device == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->device); } static void fwupd_client_get_devices_by_guid_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_devices_by_guid_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_devices_by_guid: * @self: a #FwupdClient * @guid: the GUID, e.g. `e22c4520-43dc-5bb3-8245-5787fead9b63` * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets any devices that provide a specific GUID. An error is returned if no * devices contains this GUID. * * Returns: (element-type FwupdDevice) (transfer container): devices or %NULL * * Since: 1.4.1 **/ GPtrArray * fwupd_client_get_devices_by_guid(FwupdClient *self, const gchar *guid, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(guid != NULL, NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_devices_by_guid_async(self, guid, cancellable, fwupd_client_get_devices_by_guid_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->array); } #ifdef HAVE_GIO_UNIX static void fwupd_client_install_fd_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_install_bytes_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } #endif /** * fwupd_client_install: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @filename: the filename to install * @install_flags: install flags, e.g. %FWUPD_INSTALL_FLAG_ALLOW_REINSTALL * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Install a file onto a specific device. * * Returns: %TRUE for success * * Since: 0.7.0 **/ gboolean fwupd_client_install(FwupdClient *self, const gchar *device_id, const gchar *filename, FwupdInstallFlags install_flags, GCancellable *cancellable, GError **error) { #ifdef HAVE_GIO_UNIX g_autoptr(GUnixInputStream) istr = NULL; g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(filename != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* move to a thread if this ever takes more than a few ms */ istr = fwupd_unix_input_stream_from_fn(filename, error); if (istr == NULL) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_install_stream_async(self, device_id, istr, filename, install_flags, cancellable, fwupd_client_install_fd_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Install CAB only supported on Linux"); return FALSE; #endif } static void fwupd_client_install_bytes_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_install_bytes_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_install_bytes: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @bytes: cabinet archive * @install_flags: install flags, e.g. %FWUPD_INSTALL_FLAG_ALLOW_REINSTALL * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Install firmware onto a specific device. * * Returns: %TRUE for success * * Since: 1.4.5 **/ gboolean fwupd_client_install_bytes(FwupdClient *self, const gchar *device_id, GBytes *bytes, FwupdInstallFlags install_flags, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(bytes != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_install_bytes_async(self, device_id, bytes, install_flags, cancellable, fwupd_client_install_bytes_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_install_release_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_install_release_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_install_release2: * @self: a #FwupdClient * @device: a device * @release: a release * @install_flags: install flags, e.g. %FWUPD_INSTALL_FLAG_ALLOW_REINSTALL * @download_flags: download flags, e.g. %FWUPD_CLIENT_DOWNLOAD_FLAG_NONE * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Installs a new release on a device, downloading the firmware if required. * * Returns: %TRUE for success * * Since: 1.5.6 **/ gboolean fwupd_client_install_release2(FwupdClient *self, FwupdDevice *device, FwupdRelease *release, FwupdInstallFlags install_flags, FwupdClientDownloadFlags download_flags, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(FWUPD_IS_DEVICE(device), FALSE); g_return_val_if_fail(FWUPD_IS_RELEASE(release), FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_install_release2_async(self, device, release, install_flags, download_flags, cancellable, fwupd_client_install_release_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } /** * fwupd_client_install_release: * @self: a #FwupdClient * @device: a device * @release: a release * @install_flags: install flags, e.g. %FWUPD_INSTALL_FLAG_ALLOW_REINSTALL * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Installs a new release on a device, downloading the firmware if required. * * Returns: %TRUE for success * * Since: 1.4.5 * Deprecated: 1.5.6 **/ gboolean fwupd_client_install_release(FwupdClient *self, FwupdDevice *device, FwupdRelease *release, FwupdInstallFlags install_flags, GCancellable *cancellable, GError **error) { return fwupd_client_install_release2(self, device, release, install_flags, FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, cancellable, error); } #ifdef HAVE_GIO_UNIX static void fwupd_client_update_metadata_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_update_metadata_bytes_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } #endif /** * fwupd_client_update_metadata: * @self: a #FwupdClient * @remote_id: the remote ID, e.g. `lvfs-testing` * @metadata_fn: the XML metadata filename * @signature_fn: the GPG signature file * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Updates the metadata. This allows a session process to download the metadata * and metadata signing file to be passed into the daemon to be checked and * parsed. * * The @remote_id allows the firmware to be tagged so that the remote can be * matched when the firmware is downloaded. * * Returns: %TRUE for success * * Since: 1.0.0 **/ gboolean fwupd_client_update_metadata(FwupdClient *self, const gchar *remote_id, const gchar *metadata_fn, const gchar *signature_fn, GCancellable *cancellable, GError **error) { #ifdef HAVE_GIO_UNIX g_autoptr(GUnixInputStream) istr = NULL; g_autoptr(GUnixInputStream) istr_sig = NULL; g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(remote_id != NULL, FALSE); g_return_val_if_fail(metadata_fn != NULL, FALSE); g_return_val_if_fail(signature_fn != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; istr = fwupd_unix_input_stream_from_fn(metadata_fn, error); if (istr == NULL) return FALSE; istr_sig = fwupd_unix_input_stream_from_fn(signature_fn, error); if (istr_sig == NULL) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_update_metadata_stream_async(self, remote_id, istr, istr_sig, cancellable, fwupd_client_update_metadata_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Update metadata only supported on Linux"); return FALSE; #endif } static void fwupd_client_update_metadata_bytes_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_update_metadata_bytes_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_update_metadata_bytes: * @self: a #FwupdClient * @remote_id: remote ID, e.g. `lvfs-testing` * @metadata: XML metadata data * @signature: signature data * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Updates the metadata. This allows a session process to download the metadata * and metadata signing file to be passed into the daemon to be checked and * parsed. * * The @remote_id allows the firmware to be tagged so that the remote can be * matched when the firmware is downloaded. * * Returns: %TRUE for success * * Since: 1.4.5 **/ gboolean fwupd_client_update_metadata_bytes(FwupdClient *self, const gchar *remote_id, GBytes *metadata, GBytes *signature, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(remote_id != NULL, FALSE); g_return_val_if_fail(metadata != NULL, FALSE); g_return_val_if_fail(signature != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_update_metadata_bytes_async(self, remote_id, metadata, signature, cancellable, fwupd_client_update_metadata_bytes_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_refresh_remote_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_refresh_remote_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_refresh_remote2: * @self: a #FwupdClient * @remote: a #FwupdRemote * @download_flags: download flags, e.g. %FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_P2P * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Refreshes a remote by downloading new metadata. * * Returns: %TRUE for success * * Since: 1.9.4 **/ gboolean fwupd_client_refresh_remote2(FwupdClient *self, FwupdRemote *remote, FwupdClientDownloadFlags download_flags, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(FWUPD_IS_REMOTE(remote), FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_refresh_remote2_async(self, remote, download_flags, cancellable, fwupd_client_refresh_remote_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } /** * fwupd_client_refresh_remote: * @self: a #FwupdClient * @remote: a #FwupdRemote * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Refreshes a remote by downloading new metadata. * * Returns: %TRUE for success * * Since: 1.4.5 **/ gboolean fwupd_client_refresh_remote(FwupdClient *self, FwupdRemote *remote, GCancellable *cancellable, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(FWUPD_IS_REMOTE(remote), FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return fwupd_client_refresh_remote2(self, remote, FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, cancellable, error); } static void fwupd_client_modify_remote_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_modify_remote_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_modify_remote: * @self: a #FwupdClient * @remote_id: the remote ID, e.g. `lvfs-testing` * @key: the key, e.g. `Enabled` * @value: the key, e.g. `true` * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Modifies a system remote in a specific way. * * NOTE: User authentication may be required to complete this action. * * Returns: %TRUE for success * * Since: 0.9.8 **/ gboolean fwupd_client_modify_remote(FwupdClient *self, const gchar *remote_id, const gchar *key, const gchar *value, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(remote_id != NULL, FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(value != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_modify_remote_async(self, remote_id, key, value, cancellable, fwupd_client_modify_remote_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_get_report_metadata_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->hash = fwupd_client_get_report_metadata_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_report_metadata: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets all the report metadata from the daemon. * * Returns: (transfer container): attributes * * Since: 1.5.0 **/ GHashTable * fwupd_client_get_report_metadata(FwupdClient *self, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_report_metadata_async(self, cancellable, fwupd_client_get_report_metadata_cb, helper); g_main_loop_run(helper->loop); if (helper->hash == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->hash); } static void fwupd_client_modify_device_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_modify_device_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_modify_device: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @key: (not nullable): the key, e.g. `Flags` * @value: (not nullable): the key, e.g. `reported` * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Modifies a device in a specific way. Not all properties on the #FwupdDevice * are settable by the client, and some may have other restrictions on @value. * * NOTE: User authentication may be required to complete this action. * * Returns: %TRUE for success * * Since: 1.0.4 **/ gboolean fwupd_client_modify_device(FwupdClient *self, const gchar *device_id, const gchar *key, const gchar *value, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(value != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_modify_device_async(self, device_id, key, value, cancellable, fwupd_client_modify_device_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_get_remotes_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_remotes_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_remotes: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets the list of remotes that have been configured for the system. * * Returns: (element-type FwupdRemote) (transfer container): list of remotes, or %NULL * * Since: 0.9.3 **/ GPtrArray * fwupd_client_get_remotes(FwupdClient *self, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_remotes_async(self, cancellable, fwupd_client_get_remotes_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->array); } static FwupdRemote * fwupd_client_get_remote_by_id_noref(GPtrArray *remotes, const gchar *remote_id) { for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index(remotes, i); if (g_strcmp0(remote_id, fwupd_remote_get_id(remote)) == 0) return remote; } return NULL; } /** * fwupd_client_get_remote_by_id: * @self: a #FwupdClient * @remote_id: the remote ID, e.g. `lvfs-testing` * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets a specific remote that has been configured for the system. * * Returns: (transfer full): a #FwupdRemote, or %NULL if not found * * Since: 0.9.3 **/ FwupdRemote * fwupd_client_get_remote_by_id(FwupdClient *self, const gchar *remote_id, GCancellable *cancellable, GError **error) { FwupdRemote *remote; g_autoptr(GPtrArray) remotes = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(remote_id != NULL, NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* find remote in list */ remotes = fwupd_client_get_remotes(self, cancellable, error); if (remotes == NULL) return NULL; remote = fwupd_client_get_remote_by_id_noref(remotes, remote_id); if (remote == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No remote '%s' found in search paths", remote_id); return NULL; } /* success */ return g_object_ref(remote); } static void fwupd_client_get_approved_firmware_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_approved_firmware_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_approved_firmware: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets the list of approved firmware. * * Returns: (transfer full): checksums, or %NULL for error * * Since: 1.2.6 **/ gchar ** fwupd_client_get_approved_firmware(FwupdClient *self, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; gchar **argv; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_approved_firmware_async(self, cancellable, fwupd_client_get_approved_firmware_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } argv = g_new0(gchar *, helper->array->len + 1); for (guint i = 0; i < helper->array->len; i++) { const gchar *tmp = g_ptr_array_index(helper->array, i); argv[i] = g_strdup(tmp); } return argv; } static void fwupd_client_set_approved_firmware_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_set_approved_firmware_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_set_approved_firmware: * @self: a #FwupdClient * @checksums: (not nullable): Array of checksums * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Sets the list of approved firmware. * * Returns: %TRUE for success * * Since: 1.2.6 **/ gboolean fwupd_client_set_approved_firmware(FwupdClient *self, gchar **checksums, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free); g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(checksums != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* convert */ for (guint i = 0; checksums[i] != NULL; i++) g_ptr_array_add(array, g_strdup(checksums[i])); /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_set_approved_firmware_async(self, array, cancellable, fwupd_client_set_approved_firmware_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_get_blocked_firmware_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->array = fwupd_client_get_blocked_firmware_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_get_blocked_firmware: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets the list of blocked firmware. * * Returns: (transfer full): checksums, or %NULL for error * * Since: 1.4.6 **/ gchar ** fwupd_client_get_blocked_firmware(FwupdClient *self, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; gchar **argv; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_get_blocked_firmware_async(self, cancellable, fwupd_client_get_blocked_firmware_cb, helper); g_main_loop_run(helper->loop); if (helper->array == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } argv = g_new0(gchar *, helper->array->len + 1); for (guint i = 0; i < helper->array->len; i++) { const gchar *tmp = g_ptr_array_index(helper->array, i); argv[i] = g_strdup(tmp); } return argv; } static void fwupd_client_set_blocked_firmware_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_set_blocked_firmware_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_set_blocked_firmware: * @self: a #FwupdClient * @checksums: Array of checksums * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Sets the list of approved firmware. * * Returns: %TRUE for success * * Since: 1.4.6 **/ gboolean fwupd_client_set_blocked_firmware(FwupdClient *self, gchar **checksums, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free); g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(checksums != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; for (guint i = 0; checksums[i] != NULL; i++) g_ptr_array_add(array, g_strdup(checksums[i])); /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_set_blocked_firmware_async(self, array, cancellable, fwupd_client_set_blocked_firmware_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_set_feature_flags_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_set_feature_flags_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_set_feature_flags: * @self: a #FwupdClient * @feature_flags: feature flags, e.g. %FWUPD_FEATURE_FLAG_UPDATE_TEXT * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Sets the features the client supports. This allows firmware to depend on * specific front-end features, for instance showing the user an image on * how to detach the hardware. * * Clients can call this none or multiple times. * * Returns: %TRUE for success * * Since: 1.4.5 **/ gboolean fwupd_client_set_feature_flags(FwupdClient *self, FwupdFeatureFlags feature_flags, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_set_feature_flags_async(self, feature_flags, cancellable, fwupd_client_set_feature_flags_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_self_sign_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->str = fwupd_client_self_sign_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_self_sign: * @self: a #FwupdClient * @value: (not nullable): a string to sign, typically a JSON blob * @flags: signing flags, e.g. %FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Signs the data using the client self-signed certificate. * * Returns: a signature, or %NULL for failure * * Since: 1.2.6 **/ gchar * fwupd_client_self_sign(FwupdClient *self, const gchar *value, FwupdSelfSignFlags flags, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(value != NULL, NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_self_sign_async(self, value, flags, cancellable, fwupd_client_self_sign_cb, helper); g_main_loop_run(helper->loop); if (helper->str == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->str); } static void fwupd_client_download_bytes_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->bytes = fwupd_client_download_bytes_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_download_bytes: * @self: a #FwupdClient * @url: (not nullable): the remote URL * @flags: download flags, e.g. %FWUPD_CLIENT_DOWNLOAD_FLAG_NONE * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Downloads data from a remote server. The [method@Client.set_user_agent] function * should be called before this method is used. * * Returns: (transfer full): downloaded data, or %NULL for error * * Since: 1.4.5 **/ GBytes * fwupd_client_download_bytes(FwupdClient *self, const gchar *url, FwupdClientDownloadFlags flags, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(url != NULL, NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); g_return_val_if_fail(fwupd_client_get_user_agent(self) != NULL, NULL); /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_download_bytes_async(self, url, flags, cancellable, fwupd_client_download_bytes_cb, helper); g_main_loop_run(helper->loop); if (helper->bytes == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->bytes); } /** * fwupd_client_download_file: * @self: a #FwupdClient * @url: (not nullable): the remote URL * @file: (not nullable): a file * @flags: download flags, e.g. %FWUPD_CLIENT_DOWNLOAD_FLAG_NONE * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Downloads data from a remote server. The [method@Client.set_user_agent] function * should be called before this method is used. * * Returns: %TRUE if the file was written * * Since: 1.5.2 **/ gboolean fwupd_client_download_file(FwupdClient *self, const gchar *url, GFile *file, FwupdClientDownloadFlags flags, GCancellable *cancellable, GError **error) { gssize size; g_autoptr(GBytes) bytes = NULL; g_autoptr(GOutputStream) ostream = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(url != NULL, FALSE); g_return_val_if_fail(G_IS_FILE(file), FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); g_return_val_if_fail(fwupd_client_get_user_agent(self) != NULL, FALSE); /* download then write */ bytes = fwupd_client_download_bytes(self, url, flags, cancellable, error); if (bytes == NULL) return FALSE; ostream = G_OUTPUT_STREAM(g_file_replace(file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error)); if (ostream == NULL) return FALSE; size = g_output_stream_write_bytes(ostream, bytes, NULL, error); if (size < 0) return FALSE; /* success */ return TRUE; } static void fwupd_client_upload_bytes_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->bytes = fwupd_client_upload_bytes_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_upload_bytes: * @self: a #FwupdClient * @url: (not nullable): the remote URL * @payload: (not nullable): payload string * @signature: (nullable): signature string * @flags: download flags, e.g. %FWUPD_CLIENT_DOWNLOAD_FLAG_NONE * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Uploads data to a remote server. The [method@Client.set_user_agent] function * should be called before this method is used. * * Returns: (transfer full): response data, or %NULL for error * * Since: 1.4.5 **/ GBytes * fwupd_client_upload_bytes(FwupdClient *self, const gchar *url, const gchar *payload, const gchar *signature, FwupdClientUploadFlags flags, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(url != NULL, NULL); g_return_val_if_fail(payload != NULL, NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_upload_bytes_async(self, url, payload, signature, flags, cancellable, fwupd_client_upload_bytes_cb, helper); g_main_loop_run(helper->loop); if (helper->bytes == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->bytes); } static void fwupd_client_emulation_load_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_emulation_load_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_emulation_load * @self: a #FwupdClient * @data: archive data of JSON files * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Loads an emulated device into the daemon backend that has the phases set by the JSON data, * for instance, having one USB device emulated for the bootloader and another emulated for the * runtime interface. * * Returns: %TRUE for success * * Since: 1.8.11 **/ gboolean fwupd_client_emulation_load(FwupdClient *self, GBytes *data, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(data != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return FALSE; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_emulation_load_async(self, data, cancellable, fwupd_client_emulation_load_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_emulation_save_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->bytes = fwupd_client_emulation_save_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_emulation_save: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Gets the captured data from all filtered devices for all recorded phases. The data is returned * in a ZIP archive of JSON output. * * NOTE: Device events are not automatically recorded for all devices. You must call something * like `ModifyDevice(device_id, 'flags','emulation-tag')` to start the recording the backend. * * Once the device has been re-inserted then the emulation data will be available using * this API call. * * Returns: (transfer full): archive data * * Since: 1.8.11 **/ GBytes * fwupd_client_emulation_save(FwupdClient *self, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect */ if (!fwupd_client_connect(self, cancellable, error)) return NULL; /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_emulation_save_async(self, cancellable, fwupd_client_emulation_save_cb, helper); g_main_loop_run(helper->loop); if (helper->bytes == NULL) { g_propagate_error(error, g_steal_pointer(&helper->error)); return NULL; } return g_steal_pointer(&helper->bytes); } static void fwupd_client_fix_host_security_attr_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_fix_host_security_attr_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_fix_host_security_attr: * @self: a #FwupdClient * @appstream_id: the HSI AppStream ID * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Fix one specific security attribute. * * Returns: %TRUE for success * * Since: 1.9.6 **/ gboolean fwupd_client_fix_host_security_attr(FwupdClient *self, const gchar *appstream_id, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(appstream_id != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_fix_host_security_attr_async(self, appstream_id, cancellable, fwupd_client_fix_host_security_attr_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } static void fwupd_client_undo_host_security_attr_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClientHelper *helper = (FwupdClientHelper *)user_data; helper->ret = fwupd_client_undo_host_security_attr_finish(FWUPD_CLIENT(source), res, &helper->error); g_main_loop_quit(helper->loop); } /** * fwupd_client_undo_host_security_attr: * @self: a #FwupdClient * @appstream_id: the HSI AppStream ID * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Revert the fix to one specific security attribute. * * Returns: %TRUE for success * * Since: 1.9.6 **/ gboolean fwupd_client_undo_host_security_attr(FwupdClient *self, const gchar *appstream_id, GCancellable *cancellable, GError **error) { g_autoptr(FwupdClientHelper) helper = NULL; g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(appstream_id != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* call async version and run loop until complete */ helper = fwupd_client_helper_new(self); fwupd_client_undo_host_security_attr_async(self, appstream_id, cancellable, fwupd_client_undo_host_security_attr_cb, helper); g_main_loop_run(helper->loop); if (!helper->ret) { g_propagate_error(error, g_steal_pointer(&helper->error)); return FALSE; } return TRUE; } fwupd-1.9.16/libfwupd/fwupd-client-sync.h000066400000000000000000000242161460375044200203070ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fwupd-client.h" G_BEGIN_DECLS gboolean fwupd_client_connect(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fwupd_client_quit(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_devices(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_plugins(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_history(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_releases(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_downgrades(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_client_get_upgrades(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_client_get_details(FwupdClient *self, const gchar *filename, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_client_get_details_bytes(FwupdClient *self, GBytes *bytes, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_verify(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_verify_update(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_unlock(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gchar * fwupd_client_inhibit(FwupdClient *self, const gchar *reason, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_uninhibit(FwupdClient *self, const gchar *inhibit_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_modify_config(FwupdClient *self, const gchar *key, const gchar *value, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_activate(FwupdClient *self, GCancellable *cancellable, const gchar *device_id, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 3); gboolean fwupd_client_clear_results(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); FwupdDevice * fwupd_client_get_results(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_modify_bios_setting(FwupdClient *self, GHashTable *settings, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_client_get_bios_settings(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_host_security_attrs(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_host_security_events(FwupdClient *self, guint limit, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); FwupdDevice * fwupd_client_get_device_by_id(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_client_get_devices_by_guid(FwupdClient *self, const gchar *guid, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_install(FwupdClient *self, const gchar *device_id, const gchar *filename, FwupdInstallFlags install_flags, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 3); gboolean fwupd_client_install_bytes(FwupdClient *self, const gchar *device_id, GBytes *bytes, FwupdInstallFlags install_flags, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 3); G_DEPRECATED_FOR(fwupd_client_install_release2) gboolean fwupd_client_install_release(FwupdClient *self, FwupdDevice *device, FwupdRelease *release, FwupdInstallFlags install_flags, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2, 3); gboolean fwupd_client_install_release2(FwupdClient *self, FwupdDevice *device, FwupdRelease *release, FwupdInstallFlags install_flags, FwupdClientDownloadFlags download_flags, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3); gboolean fwupd_client_update_metadata(FwupdClient *self, const gchar *remote_id, const gchar *metadata_fn, const gchar *signature_fn, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3, 4); gboolean fwupd_client_update_metadata_bytes(FwupdClient *self, const gchar *remote_id, GBytes *metadata, GBytes *signature, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3, 4); G_DEPRECATED_FOR(fwupd_client_refresh_remote2) gboolean fwupd_client_refresh_remote(FwupdClient *self, FwupdRemote *remote, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_refresh_remote2(FwupdClient *self, FwupdRemote *remote, FwupdClientDownloadFlags download_flags, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_modify_remote(FwupdClient *self, const gchar *remote_id, const gchar *key, const gchar *value, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3); gboolean fwupd_client_modify_device(FwupdClient *self, const gchar *device_id, const gchar *key, const gchar *value, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3); GHashTable * fwupd_client_get_report_metadata(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_remotes(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); FwupdRemote * fwupd_client_get_remote_by_id(FwupdClient *self, const gchar *remote_id, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gchar ** fwupd_client_get_approved_firmware(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fwupd_client_set_approved_firmware(FwupdClient *self, gchar **checksums, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gchar ** fwupd_client_get_blocked_firmware(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fwupd_client_set_blocked_firmware(FwupdClient *self, gchar **checksums, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gchar * fwupd_client_self_sign(FwupdClient *self, const gchar *value, FwupdSelfSignFlags flags, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_set_feature_flags(FwupdClient *self, FwupdFeatureFlags feature_flags, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GBytes * fwupd_client_download_bytes(FwupdClient *self, const gchar *url, FwupdClientDownloadFlags flags, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_download_file(FwupdClient *self, const gchar *url, GFile *file, FwupdClientDownloadFlags flags, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3); GBytes * fwupd_client_upload_bytes(FwupdClient *self, const gchar *url, const gchar *payload, const gchar *signature, FwupdClientUploadFlags flags, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3); gboolean fwupd_client_emulation_load(FwupdClient *self, GBytes *data, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); GBytes * fwupd_client_emulation_save(FwupdClient *self, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fwupd_client_fix_host_security_attr(FwupdClient *self, const gchar *appstream_id, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_undo_host_security_attr(FwupdClient *self, const gchar *appstream_id, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-client.c000066400000000000000000006015671460375044200173420ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #ifdef HAVE_LIBCURL #include #endif #ifdef HAVE_GIO_UNIX #include #endif #include #include #include #include #include "fwupd-bios-setting-private.h" #include "fwupd-client-private.h" #include "fwupd-client-sync.h" #include "fwupd-common-private.h" #include "fwupd-device-private.h" #include "fwupd-enums-private.h" #include "fwupd-error.h" #include "fwupd-plugin-private.h" #include "fwupd-release-private.h" #include "fwupd-remote-private.h" #include "fwupd-request-private.h" #include "fwupd-security-attr-private.h" static void fwupd_client_fixup_dbus_error(GError *error); typedef GObject *(*FwupdClientObjectNewFunc)(void); #define FWUPD_CLIENT_DBUS_PROXY_TIMEOUT 180000 /* ms */ /** * FwupdClient: * * Allow client code to call the daemon methods. * * See also: [class@FwupdDevice] */ static void fwupd_client_finalize(GObject *object); typedef struct { GMainContext *main_ctx; FwupdStatus status; gboolean tainted; gboolean interactive; guint percentage; guint32 battery_level; guint32 battery_threshold; GMutex idle_mutex; /* for @idle_id and @idle_sources */ guint idle_id; GPtrArray *idle_sources; /* element-type FwupdClientContextHelper */ gchar *daemon_version; gchar *host_bkc; gchar *host_product; gchar *host_vendor; gchar *host_machine_id; gchar *host_security_id; gboolean only_trusted; GMutex proxy_mutex; /* for @proxy */ GDBusProxy *proxy; GProxyResolver *proxy_resolver; gchar *package_name; gchar *package_version; gchar *user_agent; GHashTable *hints; /* str:str */ } FwupdClientPrivate; #ifdef HAVE_LIBCURL typedef struct { GPtrArray *urls; CURL *curl; curl_mime *mime; struct curl_slist *headers; } FwupdCurlHelper; #endif enum { SIGNAL_CHANGED, SIGNAL_STATUS_CHANGED, SIGNAL_DEVICE_ADDED, SIGNAL_DEVICE_REMOVED, SIGNAL_DEVICE_CHANGED, SIGNAL_DEVICE_REQUEST, SIGNAL_LAST }; enum { PROP_0, PROP_STATUS, PROP_PERCENTAGE, PROP_DAEMON_VERSION, PROP_TAINTED, PROP_SOUP_SESSION, /* not set, do not use! */ PROP_HOST_PRODUCT, PROP_HOST_VENDOR, PROP_HOST_MACHINE_ID, PROP_HOST_SECURITY_ID, PROP_HOST_BKC, PROP_INTERACTIVE, PROP_ONLY_TRUSTED, PROP_BATTERY_LEVEL, PROP_BATTERY_THRESHOLD, PROP_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE_WITH_PRIVATE(FwupdClient, fwupd_client, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_client_get_instance_private(o)) #ifdef HAVE_LIBCURL G_DEFINE_AUTOPTR_CLEANUP_FUNC(CURLU, curl_url_cleanup) static void fwupd_client_curl_helper_free(FwupdCurlHelper *helper) { if (helper->curl != NULL) curl_easy_cleanup(helper->curl); if (helper->mime != NULL) curl_mime_free(helper->mime); if (helper->headers != NULL) curl_slist_free_all(helper->headers); if (helper->urls != NULL) g_ptr_array_unref(helper->urls); g_free(helper); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FwupdCurlHelper, fwupd_client_curl_helper_free) #endif typedef struct { FwupdClient *self; gchar *property_name; guint signal_id; GObject *payload; } FwupdClientContextHelper; static void fwupd_client_context_helper_free(FwupdClientContextHelper *helper) { g_clear_object(&helper->payload); g_object_unref(helper->self); g_free(helper->property_name); g_free(helper); } /* always executed in the main context given by priv->main_ctx */ static void fwupd_client_context_object_notify(FwupdClient *self, const gchar *property_name) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_if_fail(g_main_context_is_owner(priv->main_ctx)); /* property */ g_object_notify(G_OBJECT(self), property_name); /* legacy signal name */ if (g_strcmp0(property_name, "status") == 0) g_signal_emit(self, signals[SIGNAL_STATUS_CHANGED], 0, priv->status); } /* emits all pending context helpers in the correct GMainContext */ static gboolean fwupd_client_context_idle_cb(gpointer user_data) { FwupdClient *self = FWUPD_CLIENT(user_data); FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->idle_mutex); g_assert(locker != NULL); for (guint i = 0; i < priv->idle_sources->len; i++) { FwupdClientContextHelper *helper = g_ptr_array_index(priv->idle_sources, i); /* property */ if (helper->property_name != NULL) fwupd_client_context_object_notify(self, helper->property_name); /* payload signal */ if (helper->signal_id != 0 && helper->payload != NULL) g_signal_emit(self, signals[helper->signal_id], 0, helper->payload); } /* all done */ g_ptr_array_set_size(priv->idle_sources, 0); priv->idle_id = 0; return G_SOURCE_REMOVE; } static void fwupd_client_context_helper(FwupdClient *self, FwupdClientContextHelper *helper) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->idle_mutex); g_assert(locker != NULL); /* no source already attached to the context */ if (priv->idle_id == 0) { g_autoptr(GSource) source = g_idle_source_new(); g_source_set_callback(source, fwupd_client_context_idle_cb, self, NULL); priv->idle_id = g_source_attach(source, priv->main_ctx); } /* run in the correct GMainContext and thread */ g_ptr_array_add(priv->idle_sources, helper); } /* run callback in the correct thread */ static void fwupd_client_object_notify(FwupdClient *self, const gchar *property_name) { FwupdClientPrivate *priv = GET_PRIVATE(self); FwupdClientContextHelper *helper = NULL; /* shortcut */ if (g_main_context_is_owner(priv->main_ctx)) { fwupd_client_context_object_notify(self, property_name); return; } /* run in the correct GMainContext and thread */ helper = g_new0(FwupdClientContextHelper, 1); helper->self = g_object_ref(self); helper->property_name = g_strdup(property_name); fwupd_client_context_helper(self, helper); } /* run callback in the correct thread */ static void fwupd_client_signal_emit_object(FwupdClient *self, guint signal_id, GObject *payload) { FwupdClientPrivate *priv = GET_PRIVATE(self); FwupdClientContextHelper *helper = NULL; /* shortcut */ if (g_main_context_is_owner(priv->main_ctx)) { g_signal_emit(self, signals[signal_id], 0, payload); return; } /* run in the correct GMainContext and thread */ helper = g_new0(FwupdClientContextHelper, 1); helper->self = g_object_ref(self); helper->signal_id = signal_id; helper->payload = g_object_ref(payload); fwupd_client_context_helper(self, helper); } static void fwupd_client_rebuild_user_agent(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GString) str = g_string_new(NULL); g_autofree gchar *system = NULL; /* application name and version */ if (priv->package_name != NULL && priv->package_version != NULL) g_string_append_printf(str, "%s/%s ", priv->package_name, priv->package_version); /* system information */ system = fwupd_build_user_agent_system(); if (system != NULL) g_string_append_printf(str, "(%s) ", system); /* platform, unless the application name is fwupd itself */ if (priv->daemon_version != NULL && g_strcmp0(priv->package_name, "fwupd") != 0) g_string_append_printf(str, "fwupd/%s", priv->daemon_version); /* success */ g_free(priv->user_agent); priv->user_agent = g_string_free(g_steal_pointer(&str), FALSE); } static void fwupd_client_set_host_vendor(FwupdClient *self, const gchar *host_vendor) { FwupdClientPrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->host_vendor, host_vendor) == 0) return; g_free(priv->host_vendor); priv->host_vendor = g_strdup(host_vendor); fwupd_client_object_notify(self, "host-vendor"); } static void fwupd_client_set_host_product(FwupdClient *self, const gchar *host_product) { FwupdClientPrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->host_product, host_product) == 0) return; g_free(priv->host_product); priv->host_product = g_strdup(host_product); fwupd_client_object_notify(self, "host-product"); } static void fwupd_client_set_host_machine_id(FwupdClient *self, const gchar *host_machine_id) { FwupdClientPrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->host_machine_id, host_machine_id) == 0) return; g_free(priv->host_machine_id); priv->host_machine_id = g_strdup(host_machine_id); fwupd_client_object_notify(self, "host-machine-id"); } static void fwupd_client_set_host_security_id(FwupdClient *self, const gchar *host_security_id) { FwupdClientPrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->host_security_id, host_security_id) == 0) return; g_free(priv->host_security_id); priv->host_security_id = g_strdup(host_security_id); fwupd_client_object_notify(self, "host-security-id"); } /** * fwupd_client_set_daemon_version: * @self: a #FwupdClient * @daemon_version: A semantic version, e.g. "1.2.3" * * Sets the daemon version number. * * Since: 1.8.11 **/ void fwupd_client_set_daemon_version(FwupdClient *self, const gchar *daemon_version) { FwupdClientPrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->daemon_version, daemon_version) == 0) return; g_free(priv->daemon_version); priv->daemon_version = g_strdup(daemon_version); fwupd_client_object_notify(self, "daemon-version"); fwupd_client_rebuild_user_agent(self); } static void fwupd_client_set_host_bkc(FwupdClient *self, const gchar *host_bkc) { FwupdClientPrivate *priv = GET_PRIVATE(self); /* emulate a D-Bus maybe type */ if (g_strcmp0(host_bkc, "") == 0) host_bkc = NULL; /* not changed */ if (g_strcmp0(priv->host_bkc, host_bkc) == 0) return; g_free(priv->host_bkc); priv->host_bkc = g_strdup(host_bkc); fwupd_client_object_notify(self, "host-bkc"); } static void fwupd_client_set_status(FwupdClient *self, FwupdStatus status) { FwupdClientPrivate *priv = GET_PRIVATE(self); if (priv->status == status) return; priv->status = status; g_debug("Emitting ::status-changed() [%s]", fwupd_status_to_string(priv->status)); fwupd_client_object_notify(self, "status"); } static void fwupd_client_set_percentage(FwupdClient *self, guint percentage) { FwupdClientPrivate *priv = GET_PRIVATE(self); if (priv->percentage == percentage) return; priv->percentage = percentage; fwupd_client_object_notify(self, "percentage"); } static void fwupd_client_set_battery_level(FwupdClient *self, guint32 battery_level) { FwupdClientPrivate *priv = GET_PRIVATE(self); if (priv->battery_level == battery_level) return; priv->battery_level = battery_level; g_object_notify(G_OBJECT(self), "battery-level"); } static void fwupd_client_set_battery_threshold(FwupdClient *self, guint32 battery_threshold) { FwupdClientPrivate *priv = GET_PRIVATE(self); if (priv->battery_threshold == battery_threshold) return; priv->battery_threshold = battery_threshold; g_object_notify(G_OBJECT(self), "battery-threshold"); } static void fwupd_client_properties_changed_cb(GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GVariantDict) dict = NULL; /* print to the console */ dict = g_variant_dict_new(changed_properties); if (g_variant_dict_contains(dict, "Status")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy, "Status"); if (val != NULL) fwupd_client_set_status(self, g_variant_get_uint32(val)); } if (g_variant_dict_contains(dict, "Tainted")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy, "Tainted"); if (val != NULL) { priv->tainted = g_variant_get_boolean(val); fwupd_client_object_notify(self, "tainted"); } } if (g_variant_dict_contains(dict, "Interactive")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy, "Interactive"); if (val != NULL) { priv->interactive = g_variant_get_boolean(val); fwupd_client_object_notify(self, "interactive"); } } if (g_variant_dict_contains(dict, "Percentage")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy, "Percentage"); if (val != NULL) fwupd_client_set_percentage(self, g_variant_get_uint32(val)); } if (g_variant_dict_contains(dict, FWUPD_RESULT_KEY_BATTERY_LEVEL)) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy, FWUPD_RESULT_KEY_BATTERY_LEVEL); if (val != NULL) fwupd_client_set_battery_level(self, g_variant_get_uint32(val)); } if (g_variant_dict_contains(dict, FWUPD_RESULT_KEY_BATTERY_THRESHOLD)) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy, FWUPD_RESULT_KEY_BATTERY_THRESHOLD); if (val != NULL) fwupd_client_set_battery_threshold(self, g_variant_get_uint32(val)); } if (g_variant_dict_contains(dict, "DaemonVersion")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy, "DaemonVersion"); if (val != NULL) fwupd_client_set_daemon_version(self, g_variant_get_string(val, NULL)); } if (g_variant_dict_contains(dict, "HostBkc")) { g_autoptr(GVariant) val = g_dbus_proxy_get_cached_property(proxy, "HostBkc"); if (val != NULL) fwupd_client_set_host_bkc(self, g_variant_get_string(val, NULL)); } if (g_variant_dict_contains(dict, "HostVendor")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy, "HostVendor"); if (val != NULL) fwupd_client_set_host_vendor(self, g_variant_get_string(val, NULL)); } if (g_variant_dict_contains(dict, "HostProduct")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy, "HostProduct"); if (val != NULL) fwupd_client_set_host_product(self, g_variant_get_string(val, NULL)); } if (g_variant_dict_contains(dict, "HostMachineId")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy, "HostMachineId"); if (val != NULL) fwupd_client_set_host_machine_id(self, g_variant_get_string(val, NULL)); } if (g_variant_dict_contains(dict, "HostSecurityId")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy, "HostSecurityId"); if (val != NULL) fwupd_client_set_host_security_id(self, g_variant_get_string(val, NULL)); } if (g_variant_dict_contains(dict, "OnlyTrusted")) { g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy, "OnlyTrusted"); if (val != NULL) { priv->only_trusted = g_variant_get_boolean(val); fwupd_client_object_notify(self, "only-trusted"); } } } static void fwupd_client_signal_cb(GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters, FwupdClient *self) { g_autoptr(FwupdDevice) dev = NULL; if (g_strcmp0(signal_name, "Changed") == 0) { g_debug("Emitting ::changed()"); g_signal_emit(self, signals[SIGNAL_CHANGED], 0); return; } if (g_strcmp0(signal_name, "DeviceAdded") == 0) { dev = fwupd_device_from_variant(parameters); g_debug("Emitting ::device-added(%s)", fwupd_device_get_id(dev)); fwupd_client_signal_emit_object(self, SIGNAL_DEVICE_ADDED, G_OBJECT(dev)); return; } if (g_strcmp0(signal_name, "DeviceRemoved") == 0) { dev = fwupd_device_from_variant(parameters); g_debug("Emitting ::device-removed(%s)", fwupd_device_get_id(dev)); fwupd_client_signal_emit_object(self, SIGNAL_DEVICE_REMOVED, G_OBJECT(dev)); return; } if (g_strcmp0(signal_name, "DeviceChanged") == 0) { dev = fwupd_device_from_variant(parameters); g_debug("Emitting ::device-changed(%s)", fwupd_device_get_id(dev)); fwupd_client_signal_emit_object(self, SIGNAL_DEVICE_CHANGED, G_OBJECT(dev)); return; } if (g_strcmp0(signal_name, "DeviceRequest") == 0) { g_autoptr(FwupdRequest) req = fwupd_request_from_variant(parameters); g_debug("Emitting ::device-request(%s)", fwupd_request_get_id(req)); fwupd_client_signal_emit_object(self, SIGNAL_DEVICE_REQUEST, G_OBJECT(req)); return; } g_debug("Unknown signal name '%s' from %s", signal_name, sender_name); } /** * fwupd_client_get_main_context: * @self: a #FwupdClient * * Gets the internal #GMainContext to use for synchronous methods. * By default the value is set a new #GMainContext. * * Returns: (transfer full): the main context * * Since: 1.5.3 **/ GMainContext * fwupd_client_get_main_context(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); if (priv->main_ctx != NULL) return g_main_context_ref(priv->main_ctx); return g_main_context_new(); } /** * fwupd_client_set_main_context: * @self: a #FwupdClient * @main_ctx: (nullable): the global default main context to use * * Sets the internal main context to use for returning progress signals. * * Since: 1.5.3 **/ void fwupd_client_set_main_context(FwupdClient *self, GMainContext *main_ctx) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_CLIENT(self)); if (main_ctx == priv->main_ctx) return; g_clear_pointer(&priv->main_ctx, g_main_context_unref); if (main_ctx != NULL) priv->main_ctx = g_main_context_ref(main_ctx); } /** * fwupd_client_ensure_networking: * @self: a #FwupdClient * @error: (nullable): optional return location for an error * * Sets up the client networking support ready for use. Most other download and * upload methods call this automatically, and do you only need to call this if * the session is being used outside the [class@FwupdClient]. * * Returns: %TRUE for success * * Since: 1.4.5 **/ gboolean fwupd_client_ensure_networking(FwupdClient *self, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* check the user agent is sane */ if (priv->user_agent == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "user agent unset"); return FALSE; } if (g_strstr_len(priv->user_agent, -1, "fwupd/") == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "user agent unsuitable; fwupd version required"); return FALSE; } return TRUE; } #ifdef HAVE_LIBCURL static int fwupd_client_progress_callback_cb(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { FwupdClient *self = FWUPD_CLIENT(clientp); FwupdClientPrivate *priv = GET_PRIVATE(self); /* calculate percentage */ if (dltotal > 0 && dlnow >= 0 && dlnow <= dltotal) { guint percentage = (guint)((100 * dlnow) / dltotal); if (priv->percentage != percentage) g_info("download progress: %u%%", percentage); fwupd_client_set_percentage(self, percentage); } else if (ultotal > 0 && ulnow >= 0 && ulnow <= ultotal) { guint percentage = (guint)((100 * ulnow) / ultotal); if (priv->percentage != percentage) g_info("upload progress: %u%%", percentage); fwupd_client_set_percentage(self, percentage); } return 0; } static void fwupd_client_curl_helper_set_proxy(FwupdClient *self, FwupdCurlHelper *helper, const gchar *url) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_auto(GStrv) proxies = NULL; g_autoptr(GError) error_local = NULL; proxies = g_proxy_resolver_lookup(priv->proxy_resolver, url, NULL, &error_local); if (proxies == NULL) { g_warning("failed to lookup proxy for %s: %s", url, error_local->message); return; } if (g_strcmp0(proxies[0], "direct://") != 0) (void)curl_easy_setopt(helper->curl, CURLOPT_PROXY, proxies[0]); } static FwupdCurlHelper * fwupd_client_curl_new(FwupdClient *self, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(FwupdCurlHelper) helper = g_new0(FwupdCurlHelper, 1); /* check the user agent is sane */ if (!fwupd_client_ensure_networking(self, error)) return NULL; /* create the session */ helper->curl = curl_easy_init(); if (helper->curl == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to setup networking"); return NULL; } if (g_getenv("FWUPD_CURL_VERBOSE") != NULL) (void)curl_easy_setopt(helper->curl, CURLOPT_VERBOSE, 1L); (void)curl_easy_setopt(helper->curl, CURLOPT_XFERINFOFUNCTION, fwupd_client_progress_callback_cb); (void)curl_easy_setopt(helper->curl, CURLOPT_XFERINFODATA, self); (void)curl_easy_setopt(helper->curl, CURLOPT_USERAGENT, priv->user_agent); (void)curl_easy_setopt(helper->curl, CURLOPT_CONNECTTIMEOUT, 60L); (void)curl_easy_setopt(helper->curl, CURLOPT_NOPROGRESS, 0L); (void)curl_easy_setopt(helper->curl, CURLOPT_FOLLOWLOCATION, 1L); (void)curl_easy_setopt(helper->curl, CURLOPT_MAXREDIRS, 5L); #if CURL_AT_LEAST_VERSION(7, 71, 0) (void)curl_easy_setopt(helper->curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); #endif /* relax the SSL checks for broken corporate proxies */ if (g_getenv("DISABLE_SSL_STRICT") != NULL) (void)curl_easy_setopt(helper->curl, CURLOPT_SSL_VERIFYPEER, 0L); /* this disables the double-compression of the firmware.xml.gz file */ (void)curl_easy_setopt(helper->curl, CURLOPT_HTTP_CONTENT_DECODING, 0L); return g_steal_pointer(&helper); } #endif static void fwupd_client_set_hints_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { /* new libfwupd and old daemon, just swallow the error */ if (g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { g_debug("ignoring %s", error->message); g_task_return_boolean(task, TRUE); return; } g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } static void fwupd_client_connect_get_proxy_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); GVariantBuilder builder; GHashTableIter iter; gpointer key, value; FwupdClient *self = g_task_get_source_object(task); FwupdClientPrivate *priv = GET_PRIVATE(self); GCancellable *cancellable = g_task_get_cancellable(task); g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; g_autoptr(GVariant) val2 = NULL; g_autoptr(GVariant) val3 = NULL; g_autoptr(GVariant) val4 = NULL; g_autoptr(GVariant) val5 = NULL; g_autoptr(GVariant) val6 = NULL; g_autoptr(GVariant) val7 = NULL; g_autoptr(GVariant) val8 = NULL; g_autoptr(GVariant) val9 = NULL; g_autoptr(GVariant) val10 = NULL; g_autoptr(GMutexLocker) locker = NULL; proxy = g_dbus_proxy_new_finish(res, &error); if (proxy == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } /* another thread did this for us */ locker = g_mutex_locker_new(&priv->proxy_mutex); if (locker == NULL || priv->proxy != NULL) { g_task_return_boolean(task, TRUE); return; } priv->proxy = g_steal_pointer(&proxy); /* connect signals, etc. */ g_signal_connect(G_DBUS_PROXY(priv->proxy), "g-properties-changed", G_CALLBACK(fwupd_client_properties_changed_cb), self); g_signal_connect(G_DBUS_PROXY(priv->proxy), "g-signal", G_CALLBACK(fwupd_client_signal_cb), self); val = g_dbus_proxy_get_cached_property(priv->proxy, "DaemonVersion"); if (val != NULL) fwupd_client_set_daemon_version(self, g_variant_get_string(val, NULL)); val2 = g_dbus_proxy_get_cached_property(priv->proxy, "Tainted"); if (val2 != NULL) priv->tainted = g_variant_get_boolean(val2); val3 = g_dbus_proxy_get_cached_property(priv->proxy, "Status"); if (val3 != NULL) fwupd_client_set_status(self, g_variant_get_uint32(val3)); val4 = g_dbus_proxy_get_cached_property(priv->proxy, "Interactive"); if (val4 != NULL) priv->interactive = g_variant_get_boolean(val4); val5 = g_dbus_proxy_get_cached_property(priv->proxy, "HostProduct"); if (val5 != NULL) fwupd_client_set_host_product(self, g_variant_get_string(val5, NULL)); val10 = g_dbus_proxy_get_cached_property(priv->proxy, "HostVendor"); if (val10 != NULL) fwupd_client_set_host_vendor(self, g_variant_get_string(val10, NULL)); val6 = g_dbus_proxy_get_cached_property(priv->proxy, "HostMachineId"); if (val6 != NULL) fwupd_client_set_host_machine_id(self, g_variant_get_string(val6, NULL)); val7 = g_dbus_proxy_get_cached_property(priv->proxy, "HostSecurityId"); if (val7 != NULL) fwupd_client_set_host_security_id(self, g_variant_get_string(val7, NULL)); val8 = g_dbus_proxy_get_cached_property(priv->proxy, "HostBkc"); if (val8 != NULL) fwupd_client_set_host_bkc(self, g_variant_get_string(val8, NULL)); val9 = g_dbus_proxy_get_cached_property(priv->proxy, "OnlyTrusted"); if (val9 != NULL) priv->only_trusted = g_variant_get_boolean(val9); /* build client hints */ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}")); g_hash_table_iter_init(&iter, priv->hints); while (g_hash_table_iter_next(&iter, &key, &value)) { if (value == NULL) continue; g_variant_builder_add(&builder, "{ss}", (const gchar *)key, (const gchar *)value); } /* only supported on fwupd >= 1.7.1 */ g_dbus_proxy_call(priv->proxy, "SetHints", g_variant_new("(a{ss})", &builder), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_set_hints_cb, g_steal_pointer(&task)); } static void fwupd_client_connect_get_connection_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); GCancellable *cancellable = g_task_get_cancellable(task); g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GError) error = NULL; connection = g_dbus_connection_new_for_address_finish(res, &error); if (connection == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } g_dbus_proxy_new(connection, G_DBUS_PROXY_FLAGS_NONE, NULL, NULL, /* bus_name */ FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, cancellable, fwupd_client_connect_get_proxy_cb, g_steal_pointer(&task)); } /** * fwupd_client_connect_async: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Sets up the client ready for use. This is probably the first method you call * when wanting to use libfwupd in an asynchronous manner. * * Other methods such as [method@FwupdClient.get_devices_async] should only be called * after [method@FwupdClient.connect_finish] has been called without an error. * * Since: 1.5.0 **/ void fwupd_client_connect_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); const gchar *socket_filename = g_getenv("FWUPD_DBUS_SOCKET"); g_autofree gchar *socket_address = NULL; g_autoptr(GTask) task = g_task_new(self, cancellable, callback, callback_data); g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->proxy_mutex); g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_assert(locker != NULL); /* nothing to do */ if (priv->proxy != NULL) { g_task_return_boolean(task, TRUE); return; } #ifdef FWUPD_DBUS_SOCKET_ADDRESS /* this is set for macOS and Windows */ if (socket_filename == NULL) socket_filename = g_strdup(FWUPD_DBUS_SOCKET_ADDRESS); #endif /* convert from filename to address, if required */ if (socket_filename != NULL) { if (g_strrstr(socket_filename, "=") == NULL) { socket_address = g_strdup_printf("unix:path=%s", socket_filename); } else { socket_address = g_strdup(socket_filename); } } /* use peer-to-peer only if the env variable is set */ if (socket_address != NULL) { g_dbus_connection_new_for_address(socket_address, G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, NULL, cancellable, fwupd_client_connect_get_connection_cb, g_steal_pointer(&task)); return; } /* typical case */ g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, NULL, FWUPD_DBUS_SERVICE, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, cancellable, fwupd_client_connect_get_proxy_cb, g_steal_pointer(&task)); } /** * fwupd_client_connect_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@Client.connect_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_connect_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } /** * fwupd_client_disconnect: (skip) * @self: a #FwupdClient * @error: (nullable): optional return location for an error * * Tears down client after use. You only need to call this method if you are: * * - connecting to the daemon in one thread and finalizing the client in another one * - to change the `FWUPD_DBUS_SOCKET` for a different peer-to-peer connection * - to add or change connection hints as specified by [method@FwupdClient.add_hint]. * * Returns: %TRUE for success * * Since: 1.8.0 **/ gboolean fwupd_client_disconnect(FwupdClient *self, GError **error) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->proxy_mutex); g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); g_assert(locker != NULL); /* sanity check */ if (priv->proxy == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "not connected"); return FALSE; } g_signal_handlers_disconnect_by_data(priv->proxy, self); g_clear_object(&priv->proxy); /* success */ return TRUE; } static void fwupd_client_quit_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdClient *self = FWUPD_CLIENT(g_task_get_source_object(G_TASK(user_data))); FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_clear_object(&priv->proxy); g_task_return_boolean(task, TRUE); } /** * fwupd_client_quit_async: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Asks the daemon to quit. This can only be called by the root user. * * NOTE: This will only actually quit if an install is not already in progress. * * Since: 1.8.11 **/ void fwupd_client_quit_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "Quit", NULL, G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_quit_cb, g_steal_pointer(&task)); } /** * fwupd_client_quit_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.quit_async]. * * Returns: %TRUE for success * * Since: 1.8.11 **/ gboolean fwupd_client_quit_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_fixup_dbus_error(GError *error) { g_autofree gchar *name = NULL; g_return_if_fail(error != NULL); /* is a remote error? */ if (!g_dbus_error_is_remote_error(error)) return; /* parse the remote error */ name = g_dbus_error_get_remote_error(error); if (name == NULL) return; if (g_str_has_prefix(name, FWUPD_DBUS_INTERFACE)) { error->domain = FWUPD_ERROR; error->code = fwupd_error_from_string(name); } else if (g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN) || g_error_matches(error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR)) { error->domain = FWUPD_ERROR; error->code = FWUPD_ERROR_NOT_SUPPORTED; } else { error->domain = FWUPD_ERROR; error->code = FWUPD_ERROR_INTERNAL; } g_dbus_error_strip_remote_error(error); } static void fwupd_client_get_host_security_attrs_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, fwupd_security_attr_array_from_variant(val), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_host_security_attrs_async: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets all the host security attributes from the daemon. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_host_security_attrs_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetHostSecurityAttrs", NULL, G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_host_security_attrs_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_host_security_attrs_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_host_security_attrs_async]. * * Returns: (element-type FwupdSecurityAttr) (transfer container): attributes * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_host_security_attrs_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_modify_bios_setting_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_modify_bios_setting_async: * @self: a #FwupdClient * @settings: (transfer container): BIOS settings * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Modifies a BIOS setting using kernel API. * The daemon will only respond to this request with proper permissions. * * Since: 1.8.4 **/ void fwupd_client_modify_bios_setting_async(FwupdClient *self, GHashTable *settings, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); GVariantBuilder builder; GHashTableIter iter; gpointer key, value; g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(settings != NULL); g_return_if_fail(g_hash_table_size(settings) > 0); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}")); g_hash_table_iter_init(&iter, settings); while (g_hash_table_iter_next(&iter, &key, &value)) { if (value == NULL) continue; g_variant_builder_add(&builder, "{ss}", (const gchar *)key, (const gchar *)value); } g_dbus_proxy_call(priv->proxy, "SetBiosSettings", g_variant_new("(a{ss})", &builder), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_modify_bios_setting_cb, g_steal_pointer(&task)); } /** * fwupd_client_modify_bios_setting_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.modify_bios_setting_async]. * * Returns: %TRUE for success * * Since: 1.8.4 **/ gboolean fwupd_client_modify_bios_setting_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_get_bios_settings_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, fwupd_bios_setting_array_from_variant(val), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_bios_settings_async: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets all the host security attributes from the daemon. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.8.4 **/ void fwupd_client_get_bios_settings_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetBiosSettings", NULL, G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_bios_settings_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_bios_settings_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_bios_settings_async]. * * Returns: (element-type FwupdBiosSetting) (transfer container): attributes * * Since: 1.8.4 **/ GPtrArray * fwupd_client_get_bios_settings_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_get_host_security_events_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, fwupd_security_attr_array_from_variant(val), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_host_security_events_async: * @self: a #FwupdClient * @limit: maximum number of events, or 0 for no limit * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets all the host security events from the daemon. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.7.1 **/ void fwupd_client_get_host_security_events_async(FwupdClient *self, guint limit, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetHostSecurityEvents", g_variant_new("(u)", limit), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_host_security_events_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_host_security_events_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_host_security_events_async]. * * Returns: (element-type FwupdSecurityAttr) (transfer container): attributes * * Since: 1.7.1 **/ GPtrArray * fwupd_client_get_host_security_events_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static GHashTable * fwupd_report_metadata_hash_from_variant(GVariant *value) { GHashTable *hash; gsize sz; g_autoptr(GVariant) untuple = NULL; hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); untuple = g_variant_get_child_value(value, 0); sz = g_variant_n_children(untuple); for (guint i = 0; i < sz; i++) { g_autoptr(GVariant) data = NULL; const gchar *key = NULL; const gchar *val = NULL; data = g_variant_get_child_value(untuple, i); g_variant_get(data, "{&s&s}", &key, &val); g_hash_table_insert(hash, g_strdup(key), g_strdup(val)); } return hash; } static void fwupd_client_get_report_metadata_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, fwupd_report_metadata_hash_from_variant(val), (GDestroyNotify)g_hash_table_unref); } /** * fwupd_client_get_report_metadata_async: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets all the report metadata from the daemon. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_report_metadata_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetReportMetadata", NULL, G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_report_metadata_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_report_metadata_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_report_metadata_async]. * * Returns: (transfer container): attributes * * Since: 1.5.0 **/ GHashTable * fwupd_client_get_report_metadata_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_get_devices_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, fwupd_device_array_from_variant(val), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_devices_async: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets all the devices registered with the daemon. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_devices_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetDevices", NULL, G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_devices_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_devices_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_devices_async]. * * Returns: (element-type FwupdDevice) (transfer container): results * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_devices_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_get_plugins_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, fwupd_plugin_array_from_variant(val), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_plugins_async: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets all the plugins being used by the daemon. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_plugins_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetPlugins", NULL, G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_plugins_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_plugins_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_plugins_async]. * * Returns: (element-type FwupdDevice) (transfer container): results * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_plugins_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_get_history_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, fwupd_device_array_from_variant(val), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_history_async: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets all the history. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_history_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetHistory", NULL, G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_history_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_history_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_history_async]. * * Returns: (element-type FwupdDevice) (transfer container): results * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_history_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_get_device_by_id_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdDevice *device_result = NULL; gsize device_id_len; g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; const gchar *device_id = g_task_get_task_data(task); devices = fwupd_client_get_devices_finish(FWUPD_CLIENT(source), res, &error); if (devices == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } /* support abbreviated hashes (client side) */ device_id_len = strlen(device_id); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); if (strncmp(fwupd_device_get_id(dev), device_id, device_id_len) == 0) { if (device_result != NULL) { g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "more than one matching ID prefix '%s'", device_id); return; } device_result = dev; } } /* one result */ if (device_result != NULL) { g_task_return_pointer(task, g_object_ref(device_result), (GDestroyNotify)g_object_unref); return; } /* failed */ g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to find %s", device_id); } /** * fwupd_client_get_device_by_id_async: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets a device by it's device ID. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_device_by_id_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(device_id != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_task_set_task_data(task, g_strdup(device_id), g_free); fwupd_client_get_devices_async(self, cancellable, fwupd_client_get_device_by_id_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_device_by_id_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_device_by_id_async]. * * Returns: (transfer full): a device, or %NULL for failure * * Since: 1.5.0 **/ FwupdDevice * fwupd_client_get_device_by_id_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_get_devices_by_guid_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices_tmp = NULL; const gchar *guid = g_task_get_task_data(task); /* get all the devices */ devices_tmp = fwupd_client_get_devices_finish(FWUPD_CLIENT(source), res, &error); if (devices_tmp == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } /* find the devices by GUID (client side) */ devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < devices_tmp->len; i++) { FwupdDevice *dev_tmp = g_ptr_array_index(devices_tmp, i); if (fwupd_device_has_guid(dev_tmp, guid)) g_ptr_array_add(devices, g_object_ref(dev_tmp)); } /* nothing */ if (devices->len == 0) { g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to find any device providing %s", guid); return; } /* success */ g_task_return_pointer(task, g_steal_pointer(&devices), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_devices_by_guid_async: * @self: a #FwupdClient * @guid: the GUID, e.g. `e22c4520-43dc-5bb3-8245-5787fead9b63` * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets any devices that provide a specific GUID. An error is returned if no * devices contains this GUID. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_devices_by_guid_async(FwupdClient *self, const gchar *guid, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(guid != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_task_set_task_data(task, g_strdup(guid), g_free); fwupd_client_get_devices_async(self, cancellable, fwupd_client_get_devices_by_guid_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_devices_by_guid_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_devices_by_guid_async]. * * Returns: (element-type FwupdRelease) (transfer container): results * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_devices_by_guid_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_get_releases_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, fwupd_release_array_from_variant(val), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_releases_async: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets all the releases for a specific device * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_releases_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(device_id != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetReleases", g_variant_new("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_releases_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_releases_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_releases_async]. * * Returns: (element-type FwupdRelease) (transfer container): results * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_releases_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_get_downgrades_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, fwupd_release_array_from_variant(val), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_downgrades_async: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets all the downgrades for a specific device. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_downgrades_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(device_id != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetDowngrades", g_variant_new("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_downgrades_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_downgrades_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_downgrades_async]. * * Returns: (element-type FwupdRelease) (transfer container): results * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_downgrades_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_get_upgrades_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, fwupd_release_array_from_variant(val), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_upgrades_async: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets all the upgrades for a specific device. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_upgrades_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(device_id != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetUpgrades", g_variant_new("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_upgrades_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_upgrades_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_upgrades_async]. * * Returns: (element-type FwupdRelease) (transfer container): results * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_upgrades_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_modify_config_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_modify_config_async: * @self: a #FwupdClient * @key: config key, e.g. `DisabledPlugins` * @value: config value, e.g. `*` * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Modifies a daemon config option. * The daemon will only respond to this request with proper permissions. * * Since: 1.5.0 **/ void fwupd_client_modify_config_async(FwupdClient *self, const gchar *key, const gchar *value, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(key != NULL); g_return_if_fail(value != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "ModifyConfig", g_variant_new("(ss)", key, value), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_modify_config_cb, g_steal_pointer(&task)); } /** * fwupd_client_modify_config_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.modify_config_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_modify_config_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_activate_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_activate_async: * @self: a #FwupdClient * @device_id: a device * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Activates up a device, which normally means the device switches to a new * firmware version. This should only be called when data loss cannot occur. * * Since: 1.5.0 **/ void fwupd_client_activate_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(device_id != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "Activate", g_variant_new("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_activate_cb, g_steal_pointer(&task)); } /** * fwupd_client_activate_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.activate_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_activate_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_verify_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_verify_async: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Verify a specific device. * * Since: 1.5.0 **/ void fwupd_client_verify_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(device_id != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "Verify", g_variant_new("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_verify_cb, g_steal_pointer(&task)); } /** * fwupd_client_verify_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.verify_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_verify_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_verify_update_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_verify_update_async: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Update the verification record for a specific device. * * Since: 1.5.0 **/ void fwupd_client_verify_update_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(device_id != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "VerifyUpdate", g_variant_new("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_verify_update_cb, g_steal_pointer(&task)); } /** * fwupd_client_verify_update_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.verify_update_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_verify_update_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_unlock_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_unlock_async: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Unlocks a specific device so firmware can be read or wrote. * * Since: 1.5.0 **/ void fwupd_client_unlock_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(device_id != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "Unlock", g_variant_new("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_unlock_cb, g_steal_pointer(&task)); } /** * fwupd_client_unlock_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.unlock_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_unlock_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_clear_results_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_clear_results_async: * @self: a #FwupdClient * @device_id: a device * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Clears the results for a specific device. * * Since: 1.5.0 **/ void fwupd_client_clear_results_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(device_id != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "ClearResults", g_variant_new("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_clear_results_cb, g_steal_pointer(&task)); } /** * fwupd_client_clear_results_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.clear_results_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_clear_results_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_get_results_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, fwupd_device_from_variant(val), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_results_async: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets the results of a previous firmware update for a specific device. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_results_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(device_id != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetResults", g_variant_new("(s)", device_id), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_results_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_results_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_results_async]. * * Returns: (transfer full): a device, or %NULL for failure * * Since: 1.5.0 **/ FwupdDevice * fwupd_client_get_results_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } #ifdef HAVE_GIO_UNIX static void fwupd_client_install_stream_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GDBusMessage) msg = NULL; g_autoptr(GError) error = NULL; g_autoptr(GTask) task = G_TASK(user_data); msg = g_dbus_connection_send_message_with_reply_finish(G_DBUS_CONNECTION(source), res, &error); if (msg == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } if (g_dbus_message_to_gerror(msg, &error)) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } void fwupd_client_install_stream_async(FwupdClient *self, const gchar *device_id, GUnixInputStream *istr, const gchar *filename_hint, FwupdInstallFlags install_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); GVariantBuilder builder; g_autoptr(GDBusMessage) request = NULL; g_autoptr(GUnixFDList) fd_list = NULL; g_autoptr(GTask) task = g_task_new(self, cancellable, callback, callback_data); /* set options */ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add(&builder, "{sv}", "reason", g_variant_new_string("user-action")); if (filename_hint != NULL) { g_variant_builder_add(&builder, "{sv}", "filename", g_variant_new_string(filename_hint)); } if (install_flags & FWUPD_INSTALL_FLAG_OFFLINE) { g_variant_builder_add(&builder, "{sv}", "offline", g_variant_new_boolean(TRUE)); } if (install_flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) { g_variant_builder_add(&builder, "{sv}", "allow-older", g_variant_new_boolean(TRUE)); } if (install_flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) { g_variant_builder_add(&builder, "{sv}", "allow-reinstall", g_variant_new_boolean(TRUE)); } if (install_flags & FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH) { g_variant_builder_add(&builder, "{sv}", "allow-branch-switch", g_variant_new_boolean(TRUE)); } if (install_flags & FWUPD_INSTALL_FLAG_FORCE) { g_variant_builder_add(&builder, "{sv}", "force", g_variant_new_boolean(TRUE)); } if (install_flags & FWUPD_INSTALL_FLAG_IGNORE_POWER) { g_variant_builder_add(&builder, "{sv}", "ignore-power", g_variant_new_boolean(TRUE)); } if (install_flags & FWUPD_INSTALL_FLAG_NO_HISTORY) { g_variant_builder_add(&builder, "{sv}", "no-history", g_variant_new_boolean(TRUE)); } /* set out of band file descriptor */ fd_list = g_unix_fd_list_new(); g_unix_fd_list_append(fd_list, g_unix_input_stream_get_fd(istr), NULL); request = g_dbus_message_new_method_call(FWUPD_DBUS_SERVICE, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "Install"); g_dbus_message_set_unix_fd_list(request, fd_list); /* call into daemon */ g_dbus_message_set_body( request, g_variant_new("(sha{sv})", device_id, g_unix_input_stream_get_fd(istr), &builder)); g_dbus_connection_send_message_with_reply(g_dbus_proxy_get_connection(priv->proxy), request, G_DBUS_SEND_MESSAGE_FLAGS_NONE, G_MAXINT, NULL, cancellable, fwupd_client_install_stream_cb, g_steal_pointer(&task)); } #endif /** * fwupd_client_install_bytes_async: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @bytes: cabinet archive * @install_flags: install flags, e.g. %FWUPD_INSTALL_FLAG_ALLOW_REINSTALL * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Install firmware onto a specific device. * * NOTE: This method is thread-safe, but progress signals will be * emitted in the global default main context, if not explicitly set with * [method@Client.set_main_context]. * * Since: 1.5.0 **/ void fwupd_client_install_bytes_async(FwupdClient *self, const gchar *device_id, GBytes *bytes, FwupdInstallFlags install_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { #ifdef HAVE_GIO_UNIX FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GError) error = NULL; g_autoptr(GUnixInputStream) istr = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* move to a thread if this ever takes more than a few ms */ istr = fwupd_unix_input_stream_from_bytes(bytes, &error); if (istr == NULL) { g_autoptr(GTask) task = g_task_new(self, cancellable, callback, callback_data); g_task_return_error(task, g_steal_pointer(&error)); return; } /* call into daemon */ fwupd_client_install_stream_async(self, device_id, istr, NULL, install_flags, cancellable, callback, callback_data); #else g_autoptr(GTask) task = g_task_new(self, cancellable, callback, callback_data); g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Install CAB only supported on Linux"); #endif } /** * fwupd_client_install_bytes_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.install_bytes_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_install_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } /** * fwupd_client_install_async: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @filename: the filename to install * @install_flags: install flags, e.g. %FWUPD_INSTALL_FLAG_ALLOW_REINSTALL * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Install firmware onto a specific device. * * NOTE: This method is thread-safe, but progress signals will be * emitted in the global default main context, if not explicitly set with * [method@Client.set_main_context]. * * Since: 1.5.0 **/ void fwupd_client_install_async(FwupdClient *self, const gchar *device_id, const gchar *filename, FwupdInstallFlags install_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { #ifdef HAVE_GIO_UNIX FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GError) error = NULL; g_autoptr(GUnixInputStream) istr = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(device_id != NULL); g_return_if_fail(filename != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* move to a thread if this ever takes more than a few ms */ istr = fwupd_unix_input_stream_from_fn(filename, &error); if (istr == NULL) { g_autoptr(GTask) task = g_task_new(self, cancellable, callback, callback_data); g_task_return_error(task, g_steal_pointer(&error)); return; } /* call into daemon */ fwupd_client_install_stream_async(self, device_id, istr, NULL, install_flags, cancellable, callback, callback_data); #else g_autoptr(GTask) task = g_task_new(self, cancellable, callback, callback_data); g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Install CAB async only supported on Linux"); #endif } /** * fwupd_client_install_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.install_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_install_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } typedef struct { FwupdDevice *device; FwupdRelease *release; FwupdInstallFlags install_flags; FwupdClientDownloadFlags download_flags; } FwupdClientInstallReleaseData; static void fwupd_client_install_release_data_free(FwupdClientInstallReleaseData *data) { g_object_unref(data->device); g_object_unref(data->release); g_free(data); } static void fwupd_client_install_release_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(GTask) task = G_TASK(user_data); if (!fwupd_client_install_release_finish(FWUPD_CLIENT(source), res, &error)) { g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } static void fwupd_client_install_release_bytes_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(GTask) task = G_TASK(user_data); if (!fwupd_client_install_bytes_finish(FWUPD_CLIENT(source), res, &error)) { g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } static void fwupd_client_install_release_download_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; g_autoptr(GTask) task = G_TASK(user_data); FwupdClientInstallReleaseData *data = g_task_get_task_data(task); GChecksumType checksum_type; GCancellable *cancellable = g_task_get_cancellable(task); const gchar *checksum_expected; g_autofree gchar *checksum_actual = NULL; blob = fwupd_client_download_bytes_finish(FWUPD_CLIENT(source), res, &error); if (blob == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } /* verify checksum */ checksum_expected = fwupd_checksum_get_best(fwupd_release_get_checksums(data->release)); checksum_type = fwupd_checksum_guess_kind(checksum_expected); checksum_actual = g_compute_checksum_for_bytes(checksum_type, blob); if (g_strcmp0(checksum_expected, checksum_actual) != 0) { g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum invalid, expected %s got %s", checksum_expected, checksum_actual); return; } /* if the device specifies ONLY_OFFLINE automatically set this flag */ if (fwupd_device_has_flag(data->device, FWUPD_DEVICE_FLAG_ONLY_OFFLINE)) data->install_flags |= FWUPD_INSTALL_FLAG_OFFLINE; fwupd_client_install_bytes_async(FWUPD_CLIENT(source), fwupd_device_get_id(data->device), blob, data->install_flags, cancellable, fwupd_client_install_release_bytes_cb, g_steal_pointer(&task)); } static gboolean fwupd_client_is_url_http(const gchar *perhaps_url) { #ifdef HAVE_LIBCURL g_autoptr(CURLU) h = curl_url(); return curl_url_set(h, CURLUPART_URL, perhaps_url, 0) == CURLUE_OK; #else return g_str_has_prefix(perhaps_url, "http://") || g_str_has_prefix(perhaps_url, "https://"); #endif } static gboolean fwupd_client_is_url_ipfs(const gchar *perhaps_url) { if (perhaps_url == NULL) return FALSE; return g_str_has_prefix(perhaps_url, "ipfs://") || g_str_has_prefix(perhaps_url, "ipns://"); } static gboolean fwupd_client_is_url_p2p(const gchar *perhaps_url) { if (perhaps_url == NULL) return FALSE; if (fwupd_client_is_url_ipfs(perhaps_url)) return TRUE; if (g_str_has_prefix(perhaps_url, "https://localhost/")) return TRUE; return FALSE; } static void fwupd_client_install_release_remote_cb(GObject *source, GAsyncResult *res, gpointer user_data) { GPtrArray *locations; const gchar *uri_tmp; g_autofree gchar *fn = NULL; g_autoptr(FwupdRemote) remote = NULL; g_autoptr(GError) error = NULL; g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GPtrArray) uris_built = g_ptr_array_new_with_free_func(g_free); FwupdClientInstallReleaseData *data = g_task_get_task_data(task); GCancellable *cancellable = g_task_get_cancellable(task); /* if a remote-id was specified, the remote has to exist */ remote = fwupd_client_get_remote_by_id_finish(FWUPD_CLIENT(source), res, &error); if (remote == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } /* get the default release only until other parts of fwupd can cope */ locations = fwupd_release_get_locations(data->release); if (locations->len == 0) { g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "release missing URI"); return; } uri_tmp = g_ptr_array_index(locations, 0); /* local and directory remotes may have the firmware already */ if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_LOCAL && !fwupd_client_is_url_http(uri_tmp)) { const gchar *fn_cache = fwupd_remote_get_filename_cache(remote); g_autofree gchar *path = g_path_get_dirname(fn_cache); fn = g_build_filename(path, uri_tmp, NULL); } else if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DIRECTORY) { fn = g_strdup(uri_tmp + 7); } /* install with flags chosen by the user */ if (fn != NULL) { fwupd_client_install_async(FWUPD_CLIENT(source), fwupd_device_get_id(data->device), fn, data->install_flags, cancellable, fwupd_client_install_release_cb, g_steal_pointer(&task)); return; } /* maybe get payload from Passim */ if (fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ALLOW_P2P_FIRMWARE)) { const gchar *checksum_sha256 = fwupd_checksum_get_by_kind(fwupd_release_get_checksums(data->release), G_CHECKSUM_SHA256); if (checksum_sha256 != NULL) { g_autofree gchar *basename = g_path_get_basename(fwupd_release_get_filename(data->release)); g_ptr_array_add(uris_built, g_strdup_printf("https://localhost:27500/%s?sha256=%s", basename, checksum_sha256)); } } /* remote file */ for (guint i = 0; i < locations->len; i++) { uri_tmp = g_ptr_array_index(locations, i); if (fwupd_client_is_url_p2p(uri_tmp)) { g_ptr_array_add(uris_built, g_strdup(uri_tmp)); } else if (fwupd_client_is_url_http(uri_tmp)) { g_autofree gchar *uri_str = NULL; uri_str = fwupd_remote_build_firmware_uri(remote, uri_tmp, &error); if (uri_str == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } g_ptr_array_add(uris_built, g_steal_pointer(&uri_str)); } else { g_debug("do not how to handle URI %s", uri_tmp); } } if (uris_built->len == 0) { g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "No URIs to download"); return; } /* download file */ fwupd_client_download_bytes2_async(FWUPD_CLIENT(source), uris_built, data->download_flags, cancellable, fwupd_client_install_release_download_cb, g_steal_pointer(&task)); } #ifdef HAVE_LIBCURL static GPtrArray * fwupd_client_filter_locations(GPtrArray *locations, FwupdClientDownloadFlags download_flags, GError **error) { g_autoptr(GPtrArray) uris_filtered = g_ptr_array_new_with_free_func(g_free); g_return_val_if_fail(locations != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); for (guint i = 0; i < locations->len; i++) { const gchar *uri = g_ptr_array_index(locations, i); if ((download_flags & FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_P2P) > 0 && !fwupd_client_is_url_p2p(uri)) continue; g_ptr_array_add(uris_filtered, g_strdup(uri)); } if (uris_filtered->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no valid release URIs"); return NULL; } return g_steal_pointer(&uris_filtered); } #endif /** * fwupd_client_install_release2_async: * @self: a #FwupdClient * @device: (not nullable): a device * @release: (not nullable): a release * @install_flags: install flags, e.g. %FWUPD_INSTALL_FLAG_ALLOW_REINSTALL * @download_flags: download flags, e.g. %FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_P2P * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Installs a new release on a device, downloading the firmware if required. * * NOTE: This method is thread-safe, but progress signals will be * emitted in the global default main context, if not explicitly set with * [method@Client.set_main_context]. * * Since: 1.5.6 **/ void fwupd_client_install_release2_async(FwupdClient *self, FwupdDevice *device, FwupdRelease *release, FwupdInstallFlags install_flags, FwupdClientDownloadFlags download_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; FwupdClientInstallReleaseData *data; const gchar *remote_id; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(FWUPD_IS_DEVICE(device)); g_return_if_fail(FWUPD_IS_RELEASE(release)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); data = g_new0(FwupdClientInstallReleaseData, 1); data->device = g_object_ref(device); data->release = g_object_ref(release); data->download_flags = download_flags; data->install_flags = install_flags; g_task_set_task_data(task, data, (GDestroyNotify)fwupd_client_install_release_data_free); /* work out what remote-specific URI fields this should use */ remote_id = fwupd_release_get_remote_id(release); if (remote_id == NULL) { fwupd_client_download_bytes2_async(self, fwupd_release_get_locations(release), download_flags, cancellable, fwupd_client_install_release_download_cb, g_steal_pointer(&task)); return; } /* if a remote-id was specified, the remote has to exist */ fwupd_client_get_remote_by_id_async(self, remote_id, cancellable, fwupd_client_install_release_remote_cb, g_steal_pointer(&task)); } /** * fwupd_client_install_release_async: * @self: a #FwupdClient * @device: (not nullable): a device * @release: (not nullable): a release * @install_flags: install flags, e.g. %FWUPD_INSTALL_FLAG_ALLOW_REINSTALL * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Installs a new release on a device, downloading the firmware if required. * * NOTE: This method is thread-safe, but progress signals will be * emitted in the global default main context, if not explicitly set with * [method@Client.set_main_context]. * * Since: 1.5.0 * Deprecated: 1.5.6 **/ void fwupd_client_install_release_async(FwupdClient *self, FwupdDevice *device, FwupdRelease *release, FwupdInstallFlags install_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { return fwupd_client_install_release2_async(self, device, release, install_flags, FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, cancellable, callback, callback_data); } /** * fwupd_client_install_release_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.install_release_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_install_release_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } #ifdef HAVE_GIO_UNIX static void fwupd_client_get_details_stream_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GDBusMessage) msg = NULL; g_autoptr(GError) error = NULL; g_autoptr(GTask) task = G_TASK(user_data); msg = g_dbus_connection_send_message_with_reply_finish(G_DBUS_CONNECTION(source), res, &error); if (msg == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } if (g_dbus_message_to_gerror(msg, &error)) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, fwupd_device_array_from_variant(g_dbus_message_get_body(msg)), (GDestroyNotify)g_ptr_array_unref); } void fwupd_client_get_details_stream_async(FwupdClient *self, GUnixInputStream *istr, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); gint fd = g_unix_input_stream_get_fd(istr); g_autoptr(GDBusMessage) request = NULL; g_autoptr(GUnixFDList) fd_list = NULL; g_autoptr(GTask) task = g_task_new(self, cancellable, callback, callback_data); /* set out of band file descriptor */ fd_list = g_unix_fd_list_new(); g_unix_fd_list_append(fd_list, fd, NULL); request = g_dbus_message_new_method_call(FWUPD_DBUS_SERVICE, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "GetDetails"); g_dbus_message_set_unix_fd_list(request, fd_list); /* call into daemon */ g_dbus_message_set_body(request, g_variant_new("(h)", fd)); g_dbus_connection_send_message_with_reply(g_dbus_proxy_get_connection(priv->proxy), request, G_DBUS_SEND_MESSAGE_FLAGS_NONE, G_MAXINT, NULL, cancellable, fwupd_client_get_details_stream_cb, g_steal_pointer(&task)); } #endif /** * fwupd_client_get_details_bytes_async: * @self: a #FwupdClient * @bytes: firmware archive * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets details about a specific firmware file. * * Since: 1.5.0 **/ void fwupd_client_get_details_bytes_async(FwupdClient *self, GBytes *bytes, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { #ifdef HAVE_GIO_UNIX FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GError) error = NULL; g_autoptr(GUnixInputStream) istr = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* move to a thread if this ever takes more than a few ms */ istr = fwupd_unix_input_stream_from_bytes(bytes, &error); if (istr == NULL) { g_autoptr(GTask) task = g_task_new(self, cancellable, callback, callback_data); g_task_return_error(task, g_steal_pointer(&error)); return; } /* call into daemon */ fwupd_client_get_details_stream_async(self, istr, cancellable, callback, callback_data); #else g_autoptr(GTask) task = g_task_new(self, cancellable, callback, callback_data); g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Get Details only supported on Linux"); #endif } /** * fwupd_client_get_details_bytes_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_details_bytes_async]. * * Returns: (transfer container) (element-type FwupdDevice): an array of results * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_details_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } /** * fwupd_client_get_percentage: * @self: a #FwupdClient * * Gets the last returned percentage value. * * Returns: a percentage, or 0 for unknown. * * Since: 0.7.3 **/ guint fwupd_client_get_percentage(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), 0); return priv->percentage; } /** * fwupd_client_get_daemon_version: * @self: a #FwupdClient * * Gets the daemon version number. * * Returns: a string, or %NULL for unknown. * * Since: 0.9.6 **/ const gchar * fwupd_client_get_daemon_version(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); return priv->daemon_version; } /** * fwupd_client_get_host_bkc: * @self: a #FwupdClient * * Gets the host best known configuration, e.g. `vendor-factory-2021q1,mycompany-2023`. * * Returns: a string, or %NULL for unknown. * * Since: 1.7.3 **/ const gchar * fwupd_client_get_host_bkc(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); return priv->host_bkc; } /** * fwupd_client_get_host_product: * @self: a #FwupdClient * * Gets the string that represents the host running fwupd * * Returns: a string, or %NULL for unknown. * * Since: 1.3.1 **/ const gchar * fwupd_client_get_host_product(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); return priv->host_product; } /** * fwupd_client_get_host_vendor: * @self: a #FwupdClient * * Gets the string that represents the vendor of the host running fwupd * * Returns: a string, or %NULL for unknown. * * Since: 1.8.2 **/ const gchar * fwupd_client_get_host_vendor(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); return priv->host_vendor; } /** * fwupd_client_get_host_machine_id: * @self: a #FwupdClient * * Gets the string that represents the host machine ID * * Returns: a string, or %NULL for unknown. * * Since: 1.3.2 **/ const gchar * fwupd_client_get_host_machine_id(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); return priv->host_machine_id; } /** * fwupd_client_get_host_security_id: * @self: a #FwupdClient * * Gets the string that represents the host machine ID * * Returns: a string, or %NULL for unknown. * * Since: 1.5.0 **/ const gchar * fwupd_client_get_host_security_id(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); return priv->host_security_id; } /** * fwupd_client_get_battery_level: * @self: a #FwupdClient * * Returns the system battery level. * * Returns: value in percent * * Since: 1.8.1 **/ guint32 fwupd_client_get_battery_level(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), FWUPD_BATTERY_LEVEL_INVALID); return priv->battery_level; } /** * fwupd_client_get_battery_threshold: * @self: a #FwupdClient * * Returns the system battery threshold under which a firmware update cannot be * performed. * * Returns: value in percent * * Since: 1.8.1 **/ guint32 fwupd_client_get_battery_threshold(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), FWUPD_BATTERY_LEVEL_INVALID); return priv->battery_threshold; } /** * fwupd_client_get_status: * @self: a #FwupdClient * * Gets the last returned status value. * * Returns: a #FwupdStatus, or %FWUPD_STATUS_UNKNOWN for unknown. * * Since: 0.7.3 **/ FwupdStatus fwupd_client_get_status(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), FWUPD_STATUS_UNKNOWN); return priv->status; } /** * fwupd_client_get_tainted: * @self: a #FwupdClient * * Gets if the daemon has been tainted by 3rd party code. * * Returns: %TRUE if the daemon is unsupported * * Since: 1.2.4 **/ gboolean fwupd_client_get_tainted(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); return priv->tainted; } /** * fwupd_client_get_only_trusted: * @self: a #FwupdClient * * Gets if the daemon is verifying signatures from a trusted authority. * * Returns: %TRUE if the daemon is checking signatures * * Since: 1.8.0 **/ gboolean fwupd_client_get_only_trusted(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); return priv->only_trusted; } /** * fwupd_client_get_daemon_interactive: * @self: a #FwupdClient * * Gets if the daemon is running in an interactive terminal. * * Returns: %TRUE if the daemon is running in an interactive terminal * * Since: 1.3.4 **/ gboolean fwupd_client_get_daemon_interactive(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); return priv->interactive; } #ifdef HAVE_GIO_UNIX static void fwupd_client_update_metadata_stream_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GDBusMessage) msg = NULL; g_autoptr(GError) error = NULL; g_autoptr(GTask) task = G_TASK(user_data); msg = g_dbus_connection_send_message_with_reply_finish(G_DBUS_CONNECTION(source), res, &error); if (msg == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } if (g_dbus_message_to_gerror(msg, &error)) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } void fwupd_client_update_metadata_stream_async(FwupdClient *self, const gchar *remote_id, GUnixInputStream *istr, GUnixInputStream *istr_sig, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GDBusMessage) request = NULL; g_autoptr(GUnixFDList) fd_list = NULL; g_autoptr(GTask) task = g_task_new(self, cancellable, callback, callback_data); /* set out of band file descriptor */ fd_list = g_unix_fd_list_new(); g_unix_fd_list_append(fd_list, g_unix_input_stream_get_fd(istr), NULL); g_unix_fd_list_append(fd_list, g_unix_input_stream_get_fd(istr_sig), NULL); request = g_dbus_message_new_method_call(FWUPD_DBUS_SERVICE, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "UpdateMetadata"); g_dbus_message_set_unix_fd_list(request, fd_list); /* call into daemon */ g_dbus_message_set_body(request, g_variant_new("(shh)", remote_id, g_unix_input_stream_get_fd(istr), g_unix_input_stream_get_fd(istr_sig))); g_dbus_connection_send_message_with_reply(g_dbus_proxy_get_connection(priv->proxy), request, G_DBUS_SEND_MESSAGE_FLAGS_NONE, G_MAXINT, NULL, cancellable, fwupd_client_update_metadata_stream_cb, g_steal_pointer(&task)); } #endif /** * fwupd_client_update_metadata_bytes_async: * @self: a #FwupdClient * @remote_id: remote ID, e.g. `lvfs-testing` * @metadata: XML metadata data * @signature: signature data * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Updates the metadata. This allows a session process to download the metadata * and metadata signing file to be passed into the daemon to be checked and * parsed. * * The @remote_id allows the firmware to be tagged so that the remote can be * matched when the firmware is downloaded. * * NOTE: This method is thread-safe, but progress signals will be * emitted in the global default main context, if not explicitly set with * [method@Client.set_main_context]. * * Since: 1.5.0 **/ void fwupd_client_update_metadata_bytes_async(FwupdClient *self, const gchar *remote_id, GBytes *metadata, GBytes *signature, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { #ifdef HAVE_GIO_UNIX FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GError) error = NULL; g_autoptr(GUnixInputStream) istr = NULL; g_autoptr(GUnixInputStream) istr_sig = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(remote_id != NULL); g_return_if_fail(metadata != NULL); g_return_if_fail(signature != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* move to a thread if this ever takes more than a few ms */ istr = fwupd_unix_input_stream_from_bytes(metadata, &error); if (istr == NULL) { g_autoptr(GTask) task = g_task_new(self, cancellable, callback, callback_data); g_task_return_error(task, g_steal_pointer(&error)); return; } istr_sig = fwupd_unix_input_stream_from_bytes(signature, &error); if (istr_sig == NULL) { g_autoptr(GTask) task = g_task_new(self, cancellable, callback, callback_data); g_task_return_error(task, g_steal_pointer(&error)); return; } /* call into daemon */ fwupd_client_update_metadata_stream_async(self, remote_id, istr, istr_sig, cancellable, callback, callback_data); #else g_autoptr(GTask) task = g_task_new(self, cancellable, callback, callback_data); g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Update metadata only supported on Linux"); #endif } /** * fwupd_client_update_metadata_bytes_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.update_metadata_bytes_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_update_metadata_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } typedef struct { FwupdRemote *remote; FwupdClientDownloadFlags download_flags; GBytes *signature; GBytes *metadata; } FwupdClientRefreshRemoteData; static void fwupd_client_refresh_remote_data_free(FwupdClientRefreshRemoteData *data) { if (data->signature != NULL) g_bytes_unref(data->signature); if (data->metadata != NULL) g_bytes_unref(data->metadata); g_object_unref(data->remote); g_free(data); } static void fwupd_client_refresh_remote_update_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(GTask) task = G_TASK(user_data); /* save metadata */ if (!fwupd_client_update_metadata_bytes_finish(FWUPD_CLIENT(source), res, &error)) { g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } static void fwupd_client_refresh_remote_metadata_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GBytes) bytes = NULL; g_autoptr(GError) error = NULL; g_autoptr(GTask) task = G_TASK(user_data); FwupdClientRefreshRemoteData *data = g_task_get_task_data(task); FwupdClient *self = g_task_get_source_object(task); GCancellable *cancellable = g_task_get_cancellable(task); /* save metadata */ bytes = fwupd_client_download_bytes_finish(FWUPD_CLIENT(source), res, &error); if (bytes == NULL) { g_prefix_error(&error, "Failed to download metadata for %s: ", fwupd_remote_get_id(data->remote)); g_task_return_error(task, g_steal_pointer(&error)); return; } data->metadata = g_steal_pointer(&bytes); /* verify this was what we expected */ if (fwupd_remote_get_checksum_metadata(data->remote) != NULL) { GChecksumType checksum_kind = fwupd_checksum_guess_kind(fwupd_remote_get_checksum_metadata(data->remote)); g_autofree gchar *checksum = g_compute_checksum_for_bytes(checksum_kind, data->metadata); if (g_strcmp0(checksum, fwupd_remote_get_checksum_metadata(data->remote)) != 0) { g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "metadata checksum expected %s and got %s", fwupd_remote_get_checksum_metadata(data->remote), checksum); return; } } /* send all this to fwupd */ fwupd_client_update_metadata_bytes_async(self, fwupd_remote_get_id(data->remote), data->metadata, data->signature, cancellable, fwupd_client_refresh_remote_update_cb, g_steal_pointer(&task)); } static void fwupd_client_refresh_remote_signature_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GBytes) bytes = NULL; g_autoptr(GError) error = NULL; g_autoptr(GTask) task = G_TASK(user_data); FwupdClientRefreshRemoteData *data = g_task_get_task_data(task); FwupdClient *self = g_task_get_source_object(task); GCancellable *cancellable = g_task_get_cancellable(task); g_autoptr(GPtrArray) urls = g_ptr_array_new_with_free_func(g_free); /* save signature */ bytes = fwupd_client_download_bytes_finish(FWUPD_CLIENT(source), res, &error); if (bytes == NULL) { g_prefix_error(&error, "Failed to download metadata for %s: ", fwupd_remote_get_id(data->remote)); g_task_return_error(task, g_steal_pointer(&error)); return; } data->signature = g_steal_pointer(&bytes); if (fwupd_remote_get_keyring_kind(data->remote) == FWUPD_KEYRING_KIND_JCAT) { if (!fwupd_remote_load_signature_bytes(data->remote, data->signature, &error)) { g_prefix_error(&error, "Failed to load signature: "); g_task_return_error(task, g_steal_pointer(&error)); return; } } /* is the signature checksum the same? */ if (fwupd_remote_get_checksum(data->remote) != NULL) { GChecksumType checksum_kind = fwupd_checksum_guess_kind(fwupd_remote_get_checksum(data->remote)); g_autofree gchar *checksum = g_compute_checksum_for_data( checksum_kind, (const guchar *)g_bytes_get_data(data->signature, NULL), g_bytes_get_size(data->signature)); if (g_strcmp0(checksum, fwupd_remote_get_checksum(data->remote)) == 0) { g_info("metadata signature of %s is unchanged, skipping", fwupd_remote_get_id(data->remote)); g_task_return_boolean(task, TRUE); return; } } /* maybe get metadata from Passim */ if (fwupd_remote_has_flag(data->remote, FWUPD_REMOTE_FLAG_ALLOW_P2P_METADATA) && fwupd_remote_get_checksum_metadata(data->remote) != NULL && fwupd_remote_get_username(data->remote) == NULL && fwupd_remote_get_password(data->remote) == NULL) { g_autofree gchar *basename = g_path_get_basename(fwupd_remote_get_metadata_uri(data->remote)); g_ptr_array_add(urls, g_strdup_printf("https://localhost:27500/%s?sha256=%s", basename, fwupd_remote_get_checksum_metadata(data->remote))); } if ((data->download_flags & FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_P2P) == 0) { g_autofree gchar *uri = fwupd_remote_build_metadata_uri(data->remote, &error); if (uri == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } g_ptr_array_add(urls, g_steal_pointer(&uri)); } fwupd_client_download_bytes2_async(self, urls, FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, cancellable, fwupd_client_refresh_remote_metadata_cb, g_steal_pointer(&task)); } /** * fwupd_client_refresh_remote2_async: * @self: a #FwupdClient * @remote: a #FwupdRemote * @download_flags: download flags, e.g. %FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_P2P * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Refreshes a remote by downloading new metadata. * * NOTE: This method is thread-safe, but progress signals will be * emitted in the global default main context, if not explicitly set with * [method@Client.set_main_context]. * * Since: 1.9.4 **/ void fwupd_client_refresh_remote2_async(FwupdClient *self, FwupdRemote *remote, FwupdClientDownloadFlags download_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientRefreshRemoteData *data; g_autofree gchar *uri = NULL; g_autoptr(GTask) task = NULL; g_autoptr(GError) error = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(FWUPD_IS_REMOTE(remote)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); task = g_task_new(self, cancellable, callback, callback_data); data = g_new0(FwupdClientRefreshRemoteData, 1); data->download_flags = download_flags; data->remote = g_object_ref(remote); g_task_set_task_data(task, g_steal_pointer(&data), (GDestroyNotify)fwupd_client_refresh_remote_data_free); /* nothing to do */ if (fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD) { g_debug("ignoring %s as %s", fwupd_remote_get_id(remote), fwupd_remote_kind_to_string(fwupd_remote_get_kind(remote))); g_task_return_boolean(task, TRUE); return; } /* sanity check */ if (fwupd_remote_get_metadata_uri_sig(remote) == NULL || fwupd_remote_get_metadata_uri(remote) == NULL) { g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no metadata URIs for %s", fwupd_remote_get_id(remote)); return; } /* download signature */ uri = fwupd_remote_build_metadata_sig_uri(remote, &error); if (uri == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } fwupd_client_download_bytes_async(self, uri, download_flags, cancellable, fwupd_client_refresh_remote_signature_cb, g_steal_pointer(&task)); } /** * fwupd_client_refresh_remote_async: * @self: a #FwupdClient * @remote: a #FwupdRemote * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Refreshes a remote by downloading new metadata. * * NOTE: This method is thread-safe, but progress signals will be * emitted in the global default main context, if not explicitly set with * [method@Client.set_main_context]. * * Since: 1.5.0 **/ void fwupd_client_refresh_remote_async(FwupdClient *self, FwupdRemote *remote, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(FWUPD_IS_REMOTE(remote)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); return fwupd_client_refresh_remote2_async(self, remote, FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, cancellable, callback, callback_data); } /** * fwupd_client_refresh_remote_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.refresh_remote_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_refresh_remote_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_get_remotes_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, fwupd_remote_array_from_variant(val), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_remotes_async: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets the list of remotes that have been configured for the system. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_remotes_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetRemotes", NULL, G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_remotes_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_remotes_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_remotes_async]. * * Returns: (element-type FwupdRemote) (transfer container): results * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_remotes_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_get_approved_firmware_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_auto(GStrv) strv = NULL; g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free); g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } g_variant_get(val, "(^as)", &strv); for (guint i = 0; strv[i] != NULL; i++) g_ptr_array_add(array, g_strdup(strv[i])); /* success */ g_task_return_pointer(task, g_steal_pointer(&array), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_approved_firmware_async: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets the list of approved firmware. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_approved_firmware_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetApprovedFirmware", NULL, G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_approved_firmware_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_approved_firmware_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_approved_firmware_async]. * * Returns: (element-type utf8) (transfer container): checksums, or %NULL for error * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_approved_firmware_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_set_approved_firmware_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_set_approved_firmware_async: * @self: a #FwupdClient * @checksums: (element-type utf8): firmware checksums * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Sets the list of approved firmware. * * Since: 1.5.0 **/ void fwupd_client_set_approved_firmware_async(FwupdClient *self, GPtrArray *checksums, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_auto(GStrv) strv = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); strv = g_new0(gchar *, checksums->len + 1); for (guint i = 0; i < checksums->len; i++) { const gchar *tmp = g_ptr_array_index(checksums, i); strv[i] = g_strdup(tmp); } g_dbus_proxy_call(priv->proxy, "SetApprovedFirmware", g_variant_new("(^as)", strv), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_set_approved_firmware_cb, g_steal_pointer(&task)); } /** * fwupd_client_set_approved_firmware_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.set_approved_firmware_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_set_approved_firmware_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_get_blocked_firmware_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_auto(GStrv) strv = NULL; g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free); g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } g_variant_get(val, "(^as)", &strv); for (guint i = 0; strv[i] != NULL; i++) g_ptr_array_add(array, g_strdup(strv[i])); /* success */ g_task_return_pointer(task, g_steal_pointer(&array), (GDestroyNotify)g_ptr_array_unref); } /** * fwupd_client_get_blocked_firmware_async: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets the list of blocked firmware. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_get_blocked_firmware_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "GetBlockedFirmware", NULL, G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_get_blocked_firmware_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_blocked_firmware_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_blocked_firmware_async]. * * Returns: (element-type utf8) (transfer container): checksums, or %NULL for error * * Since: 1.5.0 **/ GPtrArray * fwupd_client_get_blocked_firmware_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_set_blocked_firmware_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_set_blocked_firmware_async: * @self: a #FwupdClient * @checksums: (element-type utf8): firmware checksums * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Sets the list of blocked firmware. * * Since: 1.5.0 **/ void fwupd_client_set_blocked_firmware_async(FwupdClient *self, GPtrArray *checksums, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_auto(GStrv) strv = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); strv = g_new0(gchar *, checksums->len + 1); for (guint i = 0; i < checksums->len; i++) { const gchar *tmp = g_ptr_array_index(checksums, i); strv[i] = g_strdup(tmp); } g_dbus_proxy_call(priv->proxy, "SetBlockedFirmware", g_variant_new("(^as)", strv), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_set_blocked_firmware_cb, g_steal_pointer(&task)); } /** * fwupd_client_set_blocked_firmware_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.set_blocked_firmware_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_set_blocked_firmware_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_set_feature_flags_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_set_feature_flags_async: * @self: a #FwupdClient * @feature_flags: feature flags, e.g. %FWUPD_FEATURE_FLAG_UPDATE_TEXT * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Sets the features the client supports. This allows firmware to depend on * specific front-end features, for instance showing the user an image on * how to detach the hardware. * * Since: 1.5.0 **/ void fwupd_client_set_feature_flags_async(FwupdClient *self, FwupdFeatureFlags feature_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "SetFeatureFlags", g_variant_new("(t)", (guint64)feature_flags), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_set_feature_flags_cb, g_steal_pointer(&task)); } /** * fwupd_client_set_feature_flags_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.set_feature_flags_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_set_feature_flags_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_self_sign_cb(GObject *source, GAsyncResult *res, gpointer user_data) { gchar *str = NULL; g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_variant_get(val, "(s)", &str); g_task_return_pointer(task, g_steal_pointer(&str), (GDestroyNotify)g_free); } /** * fwupd_client_self_sign_async: * @self: a #FwupdClient * @value: a string to sign, typically a JSON blob * @flags: signing flags, e.g. %FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Signs the data using the client self-signed certificate. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.5.0 **/ void fwupd_client_self_sign_async(FwupdClient *self, const gchar *value, FwupdSelfSignFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); GVariantBuilder builder; g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(value != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* set options */ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); if (flags & FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP) { g_variant_builder_add(&builder, "{sv}", "add-timestamp", g_variant_new_boolean(TRUE)); } if (flags & FWUPD_SELF_SIGN_FLAG_ADD_CERT) { g_variant_builder_add(&builder, "{sv}", "add-cert", g_variant_new_boolean(TRUE)); } /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "SelfSign", g_variant_new("(sa{sv})", value, &builder), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_self_sign_cb, g_steal_pointer(&task)); } /** * fwupd_client_self_sign_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.self_sign_async]. * * Returns: a signature, or %NULL for failure * * Since: 1.5.0 **/ gchar * fwupd_client_self_sign_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_modify_remote_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_modify_remote_async: * @self: a #FwupdClient * @remote_id: the remote ID, e.g. `lvfs-testing` * @key: the key, e.g. `Enabled` * @value: the key, e.g. `true` * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Modifies a system remote in a specific way. * * Since: 1.5.0 **/ void fwupd_client_modify_remote_async(FwupdClient *self, const gchar *remote_id, const gchar *key, const gchar *value, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(remote_id != NULL); g_return_if_fail(key != NULL); g_return_if_fail(value != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "ModifyRemote", g_variant_new("(sss)", remote_id, key, value), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_modify_remote_cb, g_steal_pointer(&task)); } /** * fwupd_client_modify_remote_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.modify_remote_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_modify_remote_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_modify_device_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_modify_device_async: * @self: a #FwupdClient * @device_id: (not nullable): the device ID * @key: (not nullable): the key, e.g. `Flags` * @value: (not nullable): the value, e.g. `reported` * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Modifies a device in a specific way. Not all properties on the #FwupdDevice * are settable by the client, and some may have other restrictions on @value. * * Since: 1.5.0 **/ void fwupd_client_modify_device_async(FwupdClient *self, const gchar *device_id, const gchar *key, const gchar *value, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(device_id != NULL); g_return_if_fail(key != NULL); g_return_if_fail(value != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "ModifyDevice", g_variant_new("(sss)", device_id, key, value), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_modify_device_cb, g_steal_pointer(&task)); } /** * fwupd_client_modify_device_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.modify_device_async]. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fwupd_client_modify_device_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static FwupdRemote * fwupd_client_get_remote_by_id_noref(GPtrArray *remotes, const gchar *remote_id) { for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index(remotes, i); if (g_strcmp0(remote_id, fwupd_remote_get_id(remote)) == 0) return remote; } return NULL; } static void fwupd_client_get_remote_by_id_cb(GObject *source, GAsyncResult *res, gpointer user_data) { FwupdRemote *remote_tmp; g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) remotes = NULL; const gchar *remote_id = g_task_get_task_data(task); remotes = fwupd_client_get_remotes_finish(FWUPD_CLIENT(source), res, &error); if (remotes == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } remote_tmp = fwupd_client_get_remote_by_id_noref(remotes, remote_id); if (remote_tmp == NULL) { g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no remote '%s' found in search paths", remote_id); return; } /* success */ g_task_return_pointer(task, g_object_ref(remote_tmp), (GDestroyNotify)g_object_unref); } /** * fwupd_client_get_remote_by_id_async: * @self: a #FwupdClient * @remote_id: (not nullable): the remote ID, e.g. `lvfs-testing` * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets a specific remote that has been configured for the system. * * Since: 1.5.0 **/ void fwupd_client_get_remote_by_id_async(FwupdClient *self, const gchar *remote_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(remote_id != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_task_set_task_data(task, g_strdup(remote_id), g_free); fwupd_client_get_remotes_async(self, cancellable, fwupd_client_get_remote_by_id_cb, g_steal_pointer(&task)); } /** * fwupd_client_get_remote_by_id_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.get_remote_by_id_async]. * * Returns: (transfer full): a #FwupdRemote, or %NULL if not found * * Since: 1.5.0 **/ FwupdRemote * fwupd_client_get_remote_by_id_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } /** * fwupd_client_set_user_agent: * @self: a #FwupdClient * @user_agent: the user agent ID, e.g. `gnome-software/3.34.1` * * Manually sets the user agent that is used for downloading. The user agent * should contain the runtime version of fwupd somewhere in the provided string. * * Since: 1.4.5 **/ void fwupd_client_set_user_agent(FwupdClient *self, const gchar *user_agent) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(user_agent != NULL); /* not changed */ if (g_strcmp0(priv->user_agent, user_agent) == 0) return; g_free(priv->user_agent); priv->user_agent = g_strdup(user_agent); } /** * fwupd_client_get_user_agent: * @self: a #FwupdClient * * Gets the string that represents the user agent that is used for * uploading and downloading. The user agent will contain the runtime * version of fwupd somewhere in the provided string. * * Returns: a string, or %NULL for unknown. * * Since: 1.5.2 **/ const gchar * fwupd_client_get_user_agent(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); return priv->user_agent; } /** * fwupd_client_set_user_agent_for_package: * @self: a #FwupdClient * @package_name: (not nullable): client program name, e.g. `gnome-software` * @package_version: (not nullable): client program version, e.g. `3.28.1` * * Builds a user-agent to use for the download. * * Supplying harmless details to the server means it knows more about each * client. This allows the web service to respond in a different way, for * instance sending a different metadata file for old versions of fwupd, or * returning an error for Solaris machines. * * Before freaking out about theoretical privacy implications, much more data * than this is sent to each and every website you visit. * * Since: 1.4.5 **/ void fwupd_client_set_user_agent_for_package(FwupdClient *self, const gchar *package_name, const gchar *package_version) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(package_name != NULL); g_return_if_fail(package_version != NULL); g_free(priv->package_name); g_free(priv->package_version); priv->package_name = g_path_get_basename(package_name); priv->package_version = g_strdup(package_version); fwupd_client_rebuild_user_agent(self); } #ifdef HAVE_LIBCURL static size_t fwupd_client_download_write_callback_cb(char *ptr, size_t size, size_t nmemb, void *userdata) { GByteArray *buf = (GByteArray *)userdata; gsize realsize = size * nmemb; g_byte_array_append(buf, (const guint8 *)ptr, realsize); return realsize; } static GBytes * fwupd_client_download_ipfs(FwupdClient *self, const gchar *url, GCancellable *cancellable, GError **error) { GSubprocessFlags flags = G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE; g_autofree gchar *path = NULL; g_autoptr(GBytes) bstdout = NULL; g_autoptr(GBytes) bstderr = NULL; g_autoptr(GSubprocess) subprocess = NULL; /* we get no detailed progress details */ fwupd_client_set_status(self, FWUPD_STATUS_DOWNLOADING); fwupd_client_set_percentage(self, 0); /* convert from URI to path */ if (g_str_has_prefix(url, "ipfs://")) { path = g_strdup_printf("/ipfs/%s", url + 7); } else if (g_str_has_prefix(url, "ipns://")) { path = g_strdup_printf("/ipns/%s", url + 7); } else { path = g_strdup(url); } /* run sync */ subprocess = g_subprocess_new(flags, error, "ipfs", "cat", path, NULL); if (subprocess == NULL) return NULL; if (!g_subprocess_communicate(subprocess, NULL, cancellable, &bstdout, &bstderr, error)) return NULL; fwupd_client_set_status(self, FWUPD_STATUS_IDLE); if (g_subprocess_get_exit_status(subprocess) != 0) { const gchar *msg = g_bytes_get_data(bstderr, NULL); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to download file: %s", msg); return NULL; } return g_steal_pointer(&bstdout); } static GBytes * fwupd_client_download_http(FwupdClient *self, CURL *curl, const gchar *url, GError **error) { CURLcode res; gchar errbuf[CURL_ERROR_SIZE] = {'\0'}; glong status_code = 0; g_autoptr(GByteArray) buf = g_byte_array_new(); fwupd_client_set_status(self, FWUPD_STATUS_DOWNLOADING); (void)curl_easy_setopt(curl, CURLOPT_URL, url); (void)curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); (void)curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwupd_client_download_write_callback_cb); (void)curl_easy_setopt(curl, CURLOPT_WRITEDATA, buf); res = curl_easy_perform(curl); fwupd_client_set_status(self, FWUPD_STATUS_IDLE); fwupd_client_set_percentage(self, 100); if (res != CURLE_OK) { if (errbuf[0] != '\0') { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to download file: %s", errbuf); return NULL; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to download file: %s", curl_easy_strerror(res)); return NULL; } /* check for server limit */ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code); g_info("status-code was %ld", status_code); if (status_code == 429) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to download due to server limit"); return NULL; } if (status_code >= 400) { g_autofree gchar *str = g_strndup((const gchar *)buf->data, MIN(buf->len, 4000)); if (g_str_is_ascii(str)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to download, server response was %u: %s", (guint)status_code, str); return NULL; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to download, server response was %u", (guint)status_code); return NULL; } return g_bytes_new(buf->data, buf->len); } static void fwupd_client_download_bytes_thread_cb(GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { FwupdClient *self = FWUPD_CLIENT(source_object); FwupdCurlHelper *helper = g_task_get_task_data(task); g_autoptr(GBytes) blob = NULL; for (guint i = 0; i < helper->urls->len; i++) { const gchar *url = g_ptr_array_index(helper->urls, i); g_autoptr(GError) error = NULL; g_info("downloading %s", url); fwupd_client_curl_helper_set_proxy(self, helper, url); if (fwupd_client_is_url_http(url)) { blob = fwupd_client_download_http(self, helper->curl, url, &error); if (blob != NULL) break; } else if (fwupd_client_is_url_ipfs(url)) { blob = fwupd_client_download_ipfs(self, url, cancellable, &error); if (blob != NULL) break; } else { g_set_error(&error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "not sure how to handle: %s", url); } if (i == helper->urls->len - 1) { g_task_return_error(task, g_steal_pointer(&error)); return; } fwupd_client_set_percentage(self, 0); fwupd_client_set_status(self, FWUPD_STATUS_IDLE); g_info("failed to download %s: %s, trying next URI…", url, error->message); } g_task_return_pointer(task, g_steal_pointer(&blob), (GDestroyNotify)g_bytes_unref); } #endif /* private */ void fwupd_client_download_bytes2_async(FwupdClient *self, GPtrArray *urls, FwupdClientDownloadFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { g_autoptr(GTask) task = NULL; #ifdef HAVE_LIBCURL g_autoptr(GError) error = NULL; g_autoptr(FwupdCurlHelper) helper = NULL; #endif g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(urls != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); /* ensure networking set up */ task = g_task_new(self, cancellable, callback, callback_data); #ifdef HAVE_LIBCURL helper = fwupd_client_curl_new(self, &error); if (helper == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } helper->urls = fwupd_client_filter_locations(urls, flags, &error); if (helper->urls == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } g_task_set_task_data(task, g_steal_pointer(&helper), (GDestroyNotify)fwupd_client_curl_helper_free); /* download data */ g_task_run_in_thread(task, fwupd_client_download_bytes_thread_cb); #else g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no libcurl support"); #endif } /** * fwupd_client_download_bytes_async: * @self: a #FwupdClient * @url: (not nullable): the remote URL * @flags: download flags, e.g. %FWUPD_CLIENT_DOWNLOAD_FLAG_NONE * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Downloads data from a remote server. The [method@Client.set_user_agent] function * should be called before this method is used. * * You must have called [method@Client.connect_async] on @self before using * this method. * * NOTE: This method is thread-safe, but progress signals will be * emitted in the global default main context, if not explicitly set with * [method@Client.set_main_context]. * * Since: 1.5.0 **/ void fwupd_client_download_bytes_async(FwupdClient *self, const gchar *url, FwupdClientDownloadFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { g_autoptr(GPtrArray) urls = g_ptr_array_new_with_free_func(g_free); g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(url != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); /* just proxy */ g_ptr_array_add(urls, g_strdup(url)); fwupd_client_download_bytes2_async(self, urls, flags, cancellable, callback, callback_data); } /** * fwupd_client_download_bytes_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.download_bytes_async]. * * Returns: (transfer full): downloaded data, or %NULL for error * * Since: 1.5.0 **/ GBytes * fwupd_client_download_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } #ifdef HAVE_LIBCURL static void fwupd_client_upload_bytes_thread_cb(GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { FwupdClient *self = FWUPD_CLIENT(source_object); FwupdCurlHelper *helper = g_task_get_task_data(task); CURLcode res; gchar errbuf[CURL_ERROR_SIZE] = {'\0'}; g_autoptr(GByteArray) buf = g_byte_array_new(); (void)curl_easy_setopt(helper->curl, CURLOPT_ERRORBUFFER, errbuf); (void)curl_easy_setopt(helper->curl, CURLOPT_WRITEFUNCTION, fwupd_client_download_write_callback_cb); (void)curl_easy_setopt(helper->curl, CURLOPT_WRITEDATA, buf); res = curl_easy_perform(helper->curl); fwupd_client_set_status(self, FWUPD_STATUS_IDLE); if (res != CURLE_OK) { glong status_code = 0; curl_easy_getinfo(helper->curl, CURLINFO_RESPONSE_CODE, &status_code); g_info("status-code was %ld", status_code); if (errbuf[0] != '\0') { g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to upload file: %s", errbuf); return; } g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to upload file: %s", curl_easy_strerror(res)); return; } g_task_return_pointer(task, g_bytes_new(buf->data, buf->len), (GDestroyNotify)g_bytes_unref); } #endif /** * fwupd_client_upload_bytes_async: * @self: a #FwupdClient * @url: (not nullable): the remote URL * @payload: (not nullable): payload string * @signature: (nullable): signature string * @flags: download flags, e.g. %FWUPD_CLIENT_DOWNLOAD_FLAG_NONE * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Uploads data to a remote server. The [method@Client.set_user_agent] function * should be called before this method is used. * * You must have called [method@Client.connect_async] on @self before using * this method. * * NOTE: This method is thread-safe, but progress signals will be * emitted in the global default main context, if not explicitly set with * [method@Client.set_main_context]. * * Since: 1.5.0 **/ void fwupd_client_upload_bytes_async(FwupdClient *self, const gchar *url, const gchar *payload, const gchar *signature, FwupdClientUploadFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; #ifdef HAVE_LIBCURL g_autoptr(FwupdCurlHelper) helper = NULL; g_autoptr(GError) error = NULL; #endif g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(url != NULL); g_return_if_fail(payload != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* ensure networking set up */ task = g_task_new(self, cancellable, callback, callback_data); #ifdef HAVE_LIBCURL helper = fwupd_client_curl_new(self, &error); if (helper == NULL) { g_task_return_error(task, g_steal_pointer(&error)); return; } /* build message */ if ((flags & FWUPD_CLIENT_UPLOAD_FLAG_ALWAYS_MULTIPART) > 0 || signature != NULL) { curl_mimepart *part; helper->mime = curl_mime_init(helper->curl); (void)curl_easy_setopt(helper->curl, CURLOPT_MIMEPOST, helper->mime); part = curl_mime_addpart(helper->mime); (void)curl_mime_data(part, payload, CURL_ZERO_TERMINATED); curl_mime_name(part, "payload"); if (signature != NULL) { part = curl_mime_addpart(helper->mime); (void)curl_mime_data(part, signature, CURL_ZERO_TERMINATED); curl_mime_name(part, "signature"); } } else { helper->headers = curl_slist_append(helper->headers, "Content-Type: text/plain"); (void)curl_easy_setopt(helper->curl, CURLOPT_HTTPHEADER, helper->headers); (void)curl_easy_setopt(helper->curl, CURLOPT_POST, 1L); (void)curl_easy_setopt(helper->curl, CURLOPT_POSTFIELDSIZE, strlen(payload)); (void)curl_easy_setopt(helper->curl, CURLOPT_COPYPOSTFIELDS, payload); } fwupd_client_set_status(self, FWUPD_STATUS_IDLE); g_info("uploading to %s", url); (void)curl_easy_setopt(helper->curl, CURLOPT_URL, url); g_task_set_task_data(task, g_steal_pointer(&helper), (GDestroyNotify)fwupd_client_curl_helper_free); g_task_run_in_thread(task, fwupd_client_upload_bytes_thread_cb); #else g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no libcurl support"); #endif } /** * fwupd_client_upload_bytes_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.upload_bytes_async]. * * Returns: (transfer full): response data, or %NULL for error * * Since: 1.5.0 **/ GBytes * fwupd_client_upload_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_inhibit_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autofree gchar *inhibit_id = NULL; g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_variant_get(val, "(s)", &inhibit_id); g_task_return_pointer(task, g_steal_pointer(&inhibit_id), g_free); } /** * fwupd_client_inhibit_async: * @self: a #FwupdClient * @reason: (not nullable): the inhibit reason, e.g. `user active` * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Marks all devices as unavailable for update. Update is only available if there is no other * inhibit imposed by other applications or by the system (e.g. low power state). * * The same application can inhibit the system multiple times. * * Since: 1.8.11 **/ void fwupd_client_inhibit_async(FwupdClient *self, const gchar *reason, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(reason != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "Inhibit", g_variant_new("(s)", reason), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_inhibit_cb, g_steal_pointer(&task)); } /** * fwupd_client_inhibit_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.inhibit_async]. * * Returns: (transfer full): a string to use for [method@FwupdClient.uninhibit_async], * or %NULL for failure * * Since: 1.8.11 **/ gchar * fwupd_client_inhibit_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_uninhibit_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_uninhibit_async: * @self: a #FwupdClient * @inhibit_id: (not nullable): the inhibit ID * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Removes the inhibit token added by the application. * * Since: 1.8.11 **/ void fwupd_client_uninhibit_async(FwupdClient *self, const gchar *inhibit_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(inhibit_id != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "Uninhibit", g_variant_new("(s)", inhibit_id), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_uninhibit_cb, g_steal_pointer(&task)); } /** * fwupd_client_uninhibit_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.uninhibit_async]. * * Returns: %TRUE for success * * Since: 1.8.11 **/ gboolean fwupd_client_uninhibit_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } /** * fwupd_client_add_hint: * @self: a #FwupdClient * @key: (not nullable): the key, e.g. `locale` * @value: (nullable): the value @key should be set * * Sets optional hints from the client that may affect the list of devices. * * Since: 1.7.1 **/ void fwupd_client_add_hint(FwupdClient *self, const gchar *key, const gchar *value) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(key != NULL); g_hash_table_insert(priv->hints, g_strdup(key), g_strdup(value)); } static void fwupd_client_emulation_load_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_emulation_load_async: * @self: a #FwupdClient * @data: archive data of JSON files * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Loads an emulated device into the daemon backend that has the phases set by the JSON data, * for instance, having one USB device emulated for the bootloader and another emulated for the * runtime interface. * * Since: 1.8.11 **/ void fwupd_client_emulation_load_async(FwupdClient *self, GBytes *data, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; GVariant *variant; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(data != NULL); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); variant = g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, data, FALSE); g_dbus_proxy_call(priv->proxy, "EmulationLoad", g_variant_new_tuple(&variant, 1), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_emulation_load_cb, g_steal_pointer(&task)); } /** * fwupd_client_emulation_load_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.emulation_load_async]. * * Returns: %TRUE for success * * Since: 1.8.11 **/ gboolean fwupd_client_emulation_load_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_emulation_save_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_pointer(task, g_variant_get_data_as_bytes(val), (GDestroyNotify)g_bytes_unref); } /** * fwupd_client_emulation_save_async: * @self: a #FwupdClient * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Gets the captured data from all filtered devices for all recorded phases. The data is returned * in a ZIP archive of JSON output. * * NOTE: Device events are not automatically recorded for all devices. You must call something * like `ModifyDevice(device_id, 'flags','emulation-tag')` to start the recording the backend. * * Once the device has been re-inserted then the emulation data will be available using * this API call. * * You must have called [method@Client.connect_async] on @self before using * this method. * * Since: 1.8.11 **/ void fwupd_client_emulation_save_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "EmulationSave", NULL, G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_emulation_save_cb, g_steal_pointer(&task)); } /** * fwupd_client_emulation_save_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.emulation_save_async]. * * Returns: (transfer full): archive data * * Since: 1.8.11 **/ GBytes * fwupd_client_emulation_save_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL); g_return_val_if_fail(g_task_is_valid(res, self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return g_task_propagate_pointer(G_TASK(res), error); } static void fwupd_client_fix_host_security_attr_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_fix_host_security_attr_async: * @self: a #FwupdClient * @appstream_id: HSI AppStream ID * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Fix one specific security attribute. * * Since: 1.9.6 **/ void fwupd_client_fix_host_security_attr_async(FwupdClient *self, const gchar *appstream_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(appstream_id != NULL); g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "FixHostSecurityAttr", g_variant_new("(s)", appstream_id), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_fix_host_security_attr_cb, g_steal_pointer(&task)); } /** * fwupd_client_fix_host_security_attr_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.fix_host_security_attr_async]. * * Returns: %TRUE for success * * Since: 1.9.6 **/ gboolean fwupd_client_fix_host_security_attr_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_undo_host_security_attr_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(GError) error = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (val == NULL) { fwupd_client_fixup_dbus_error(error); g_task_return_error(task, g_steal_pointer(&error)); return; } /* success */ g_task_return_boolean(task, TRUE); } /** * fwupd_client_undo_host_security_attr_async: * @self: a #FwupdClient * @appstream_id: HSI AppStream ID * @cancellable: (nullable): optional #GCancellable * @callback: (scope async) (closure callback_data): the function to run on completion * @callback_data: the data to pass to @callback * * Reverts the fix to one specific security attribute. * * Since: 1.9.6 **/ void fwupd_client_undo_host_security_attr_async(FwupdClient *self, const gchar *appstream_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_autoptr(GTask) task = NULL; g_return_if_fail(appstream_id != NULL); g_return_if_fail(FWUPD_IS_CLIENT(self)); g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable)); g_return_if_fail(priv->proxy != NULL); /* call into daemon */ task = g_task_new(self, cancellable, callback, callback_data); g_dbus_proxy_call(priv->proxy, "UndoHostSecurityAttr", g_variant_new("(s)", appstream_id), G_DBUS_CALL_FLAGS_NONE, FWUPD_CLIENT_DBUS_PROXY_TIMEOUT, cancellable, fwupd_client_undo_host_security_attr_cb, g_steal_pointer(&task)); } /** * fwupd_client_undo_host_security_attr_finish: * @self: a #FwupdClient * @res: (not nullable): the asynchronous result * @error: (nullable): optional return location for an error * * Gets the result of [method@FwupdClient.undo_host_security_attr_async]. * * Returns: %TRUE for success * * Since: 1.9.6 **/ gboolean fwupd_client_undo_host_security_attr_finish(FwupdClient *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } static void fwupd_client_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FwupdClient *self = FWUPD_CLIENT(object); FwupdClientPrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_STATUS: g_value_set_uint(value, priv->status); break; case PROP_TAINTED: g_value_set_boolean(value, priv->tainted); break; case PROP_SOUP_SESSION: g_value_set_object(value, NULL); break; case PROP_PERCENTAGE: g_value_set_uint(value, priv->percentage); break; case PROP_DAEMON_VERSION: g_value_set_string(value, priv->daemon_version); break; case PROP_HOST_BKC: g_value_set_string(value, priv->host_bkc); break; case PROP_HOST_VENDOR: g_value_set_string(value, priv->host_vendor); break; case PROP_HOST_PRODUCT: g_value_set_string(value, priv->host_product); break; case PROP_HOST_MACHINE_ID: g_value_set_string(value, priv->host_machine_id); break; case PROP_HOST_SECURITY_ID: g_value_set_string(value, priv->host_security_id); break; case PROP_ONLY_TRUSTED: g_value_set_boolean(value, priv->only_trusted); break; case PROP_INTERACTIVE: g_value_set_boolean(value, priv->interactive); break; case PROP_BATTERY_LEVEL: g_value_set_uint(value, priv->battery_level); break; case PROP_BATTERY_THRESHOLD: g_value_set_uint(value, priv->battery_threshold); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fwupd_client_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FwupdClient *self = FWUPD_CLIENT(object); FwupdClientPrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_STATUS: priv->status = g_value_get_uint(value); break; case PROP_PERCENTAGE: priv->percentage = g_value_get_uint(value); break; case PROP_BATTERY_LEVEL: fwupd_client_set_battery_level(self, g_value_get_uint(value)); break; case PROP_BATTERY_THRESHOLD: fwupd_client_set_battery_threshold(self, g_value_get_uint(value)); break; case PROP_HOST_BKC: fwupd_client_set_host_bkc(self, g_value_get_string(value)); break; case PROP_HOST_VENDOR: fwupd_client_set_host_vendor(self, g_value_get_string(value)); break; case PROP_HOST_PRODUCT: fwupd_client_set_host_product(self, g_value_get_string(value)); break; case PROP_HOST_MACHINE_ID: fwupd_client_set_host_machine_id(self, g_value_get_string(value)); break; case PROP_HOST_SECURITY_ID: fwupd_client_set_host_security_id(self, g_value_get_string(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fwupd_client_class_init(FwupdClientClass *klass) { GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fwupd_client_finalize; object_class->get_property = fwupd_client_get_property; object_class->set_property = fwupd_client_set_property; /** * FwupdClient::changed: * @self: the #FwupdClient instance that emitted the signal * * The ::changed signal is emitted when the daemon internal has * changed, for instance when a device has been added or removed. * * Since: 0.7.0 **/ signals[SIGNAL_CHANGED] = g_signal_new("changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FwupdClientClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * FwupdClient::state-changed: * @self: the #FwupdClient instance that emitted the signal * @status: the #FwupdStatus * * The ::state-changed signal is emitted when the daemon status has * changed, e.g. going from %FWUPD_STATUS_IDLE to %FWUPD_STATUS_DEVICE_WRITE. * * Since: 0.7.0 **/ signals[SIGNAL_STATUS_CHANGED] = g_signal_new("status-changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FwupdClientClass, status_changed), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); /** * FwupdClient::device-added: * @self: the #FwupdClient instance that emitted the signal * @result: the #FwupdDevice * * The ::device-added signal is emitted when a device has been * added. * * Since: 0.7.1 **/ signals[SIGNAL_DEVICE_ADDED] = g_signal_new("device-added", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FwupdClientClass, device_added), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, FWUPD_TYPE_DEVICE); /** * FwupdClient::device-removed: * @self: the #FwupdClient instance that emitted the signal * @result: the #FwupdDevice * * The ::device-removed signal is emitted when a device has been * removed. * * Since: 0.7.1 **/ signals[SIGNAL_DEVICE_REMOVED] = g_signal_new("device-removed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FwupdClientClass, device_removed), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, FWUPD_TYPE_DEVICE); /** * FwupdClient::device-changed: * @self: the #FwupdClient instance that emitted the signal * @result: the #FwupdDevice * * The ::device-changed signal is emitted when a device has been * changed in some way, e.g. the version number is updated. * * Since: 0.7.1 **/ signals[SIGNAL_DEVICE_CHANGED] = g_signal_new("device-changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FwupdClientClass, device_changed), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, FWUPD_TYPE_DEVICE); /** * FwupdClient::device-request: * @self: the #FwupdClient instance that emitted the signal * @msg: the #FwupdRequest * * The ::device-request signal is emitted when a device has been * emitted some kind of event, e.g. a manual action is required. * * Since: 1.6.2 **/ signals[SIGNAL_DEVICE_REQUEST] = g_signal_new("device-request", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FwupdClientClass, device_request), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, FWUPD_TYPE_REQUEST); /** * FwupdClient:status: * * The last-reported status of the daemon. * * Since: 0.7.0 */ pspec = g_param_spec_uint("status", NULL, NULL, 0, FWUPD_STATUS_LAST, FWUPD_STATUS_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_STATUS, pspec); /** * FwupdClient:tainted: * * If the daemon is tainted by 3rd party code. * * Since: 1.2.4 */ pspec = g_param_spec_boolean("tainted", NULL, NULL, FALSE, G_PARAM_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_TAINTED, pspec); /** * FwupdClient:interactive: * * If the daemon is running in an interactive terminal * * Since: 1.3.4 */ pspec = g_param_spec_boolean("interactive", NULL, NULL, FALSE, G_PARAM_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_INTERACTIVE, pspec); /** * FwupdClient:percentage: * * The last-reported percentage of the daemon. * * Since: 0.7.3 */ pspec = g_param_spec_uint("percentage", NULL, NULL, 0, 100, 0, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PERCENTAGE, pspec); /** * FwupdClient:daemon-version: * * The daemon version number. * * Since: 0.9.6 */ pspec = g_param_spec_string("daemon-version", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_DAEMON_VERSION, pspec); /** * FwupdClient:host-bkc: * * The host best known configuration. * * Since: 1.7.3 */ pspec = g_param_spec_string("host-bkc", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_HOST_BKC, pspec); /** * FwupdClient:soup-session: * * The libsoup session, now unused. * * Since: 1.4.5 */ pspec = g_param_spec_object("soup-session", NULL, NULL, G_TYPE_OBJECT, G_PARAM_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_SOUP_SESSION, pspec); /** * FwupdClient:host-vendor: * * The host vendor string * * Since: 1.8.2 */ pspec = g_param_spec_string("host-vendor", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_HOST_VENDOR, pspec); /** * FwupdClient:host-product: * * The host product string * * Since: 1.3.1 */ pspec = g_param_spec_string("host-product", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_HOST_PRODUCT, pspec); /** * FwupdClient:host-machine-id: * * The host machine-id string * * Since: 1.3.2 */ pspec = g_param_spec_string("host-machine-id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_HOST_MACHINE_ID, pspec); /** * FwupdClient:host-security-id: * * The host machine-id string * * Since: 1.5.0 */ pspec = g_param_spec_string("host-security-id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_HOST_SECURITY_ID, pspec); /** * FwupdClient:only-trusted: * * If the daemon is verifying signatures from a trusted authority. * * Since: 1.8.0 */ pspec = g_param_spec_boolean("only-trusted", NULL, NULL, TRUE, G_PARAM_READABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_ONLY_TRUSTED, pspec); /** * FwupdClient:battery-level: * * The system battery level in percent. * * Since: 1.8.1 */ pspec = g_param_spec_uint("battery-level", NULL, NULL, 0, FWUPD_BATTERY_LEVEL_INVALID, FWUPD_BATTERY_LEVEL_INVALID, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BATTERY_LEVEL, pspec); /** * FwupdClient:battery-threshold: * * The system battery threshold in percent. * * Since: 1.8.1 */ pspec = g_param_spec_uint("battery-threshold", NULL, NULL, 0, FWUPD_BATTERY_LEVEL_INVALID, FWUPD_BATTERY_LEVEL_INVALID, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BATTERY_THRESHOLD, pspec); } static void fwupd_client_init(FwupdClient *self) { FwupdClientPrivate *priv = GET_PRIVATE(self); g_mutex_init(&priv->proxy_mutex); g_mutex_init(&priv->idle_mutex); priv->idle_sources = g_ptr_array_new_with_free_func((GDestroyNotify)fwupd_client_context_helper_free); priv->proxy_resolver = g_proxy_resolver_get_default(); priv->hints = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); priv->battery_level = FWUPD_BATTERY_LEVEL_INVALID; priv->battery_threshold = FWUPD_BATTERY_LEVEL_INVALID; /* we get this one for free */ fwupd_client_add_hint(self, "locale", g_getenv("LANG")); } static void fwupd_client_finalize(GObject *object) { FwupdClient *self = FWUPD_CLIENT(object); FwupdClientPrivate *priv = GET_PRIVATE(self); g_clear_pointer(&priv->main_ctx, g_main_context_unref); g_free(priv->user_agent); g_free(priv->package_name); g_free(priv->package_version); g_free(priv->daemon_version); g_free(priv->host_bkc); g_free(priv->host_vendor); g_free(priv->host_product); g_free(priv->host_machine_id); g_free(priv->host_security_id); g_hash_table_unref(priv->hints); g_mutex_clear(&priv->idle_mutex); if (priv->idle_id != 0) g_source_remove(priv->idle_id); g_ptr_array_unref(priv->idle_sources); g_mutex_clear(&priv->proxy_mutex); if (priv->proxy != NULL) g_object_unref(priv->proxy); G_OBJECT_CLASS(fwupd_client_parent_class)->finalize(object); } /** * fwupd_client_new: * * Creates a new client. * * Returns: a new #FwupdClient * * Since: 0.7.0 **/ FwupdClient * fwupd_client_new(void) { FwupdClient *self; self = g_object_new(FWUPD_TYPE_CLIENT, NULL); return FWUPD_CLIENT(self); } fwupd-1.9.16/libfwupd/fwupd-client.h000066400000000000000000000533611460375044200173400ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-build.h" #include "fwupd-device.h" #include "fwupd-enums.h" #include "fwupd-plugin.h" #include "fwupd-remote.h" #include "fwupd-request.h" G_BEGIN_DECLS #define FWUPD_TYPE_CLIENT (fwupd_client_get_type()) G_DECLARE_DERIVABLE_TYPE(FwupdClient, fwupd_client, FWUPD, CLIENT, GObject) struct _FwupdClientClass { GObjectClass parent_class; void (*changed)(FwupdClient *client); void (*status_changed)(FwupdClient *client, FwupdStatus status); void (*device_added)(FwupdClient *client, FwupdDevice *result); void (*device_removed)(FwupdClient *client, FwupdDevice *result); void (*device_changed)(FwupdClient *client, FwupdDevice *result); void (*device_request)(FwupdClient *client, FwupdRequest *request); /*< private >*/ void (*_fwupd_reserved1)(void); void (*_fwupd_reserved2)(void); void (*_fwupd_reserved3)(void); void (*_fwupd_reserved4)(void); void (*_fwupd_reserved5)(void); void (*_fwupd_reserved6)(void); }; /** * FwupdClientDownloadFlags: * @FWUPD_CLIENT_DOWNLOAD_FLAG_NONE: No flags set * @FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_P2P: Only use peer-to-peer when downloading URIs * * The options to use for downloading. **/ typedef enum { FWUPD_CLIENT_DOWNLOAD_FLAG_NONE = 0, /* Since: 1.4.5 */ FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_P2P = 1 << 0, /* Since: 1.9.4 */ /*< private >*/ FWUPD_CLIENT_DOWNLOAD_FLAG_LAST } FwupdClientDownloadFlags; /** * FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_IPFS: * * For API compatibility: * * Since: 1.5.6 * Deprecated: 1.9.4 **/ #define FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_IPFS FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_P2P /** * FwupdClientUploadFlags: * @FWUPD_CLIENT_UPLOAD_FLAG_NONE: No flags set * @FWUPD_CLIENT_UPLOAD_FLAG_ALWAYS_MULTIPART: Always use multipart/form-data * * The options to use for uploading. **/ typedef enum { FWUPD_CLIENT_UPLOAD_FLAG_NONE = 0, /* Since: 1.4.5 */ FWUPD_CLIENT_UPLOAD_FLAG_ALWAYS_MULTIPART = 1 << 0, /* Since: 1.4.5 */ /*< private >*/ FWUPD_CLIENT_UPLOAD_FLAG_LAST } FwupdClientUploadFlags; FwupdClient * fwupd_client_new(void); GMainContext * fwupd_client_get_main_context(FwupdClient *self) G_GNUC_NON_NULL(1); void fwupd_client_set_main_context(FwupdClient *self, GMainContext *main_ctx) G_GNUC_NON_NULL(1); void fwupd_client_connect_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); gboolean fwupd_client_connect_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_disconnect(FwupdClient *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); void fwupd_client_quit_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); gboolean fwupd_client_quit_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_devices_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_devices_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_plugins_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_plugins_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_history_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_history_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_releases_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_client_get_releases_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_downgrades_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_client_get_downgrades_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_upgrades_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_client_get_upgrades_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_details_bytes_async(FwupdClient *self, GBytes *bytes, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_client_get_details_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_verify_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_verify_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_verify_update_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_verify_update_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_unlock_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); gboolean fwupd_client_unlock_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_modify_config_async(FwupdClient *self, const gchar *key, const gchar *value, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_modify_config_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_activate_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_activate_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_clear_results_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_clear_results_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_results_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); FwupdDevice * fwupd_client_get_results_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_modify_bios_setting_async(FwupdClient *self, GHashTable *settings, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_modify_bios_setting_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_bios_settings_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_bios_settings_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_host_security_attrs_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_host_security_attrs_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_host_security_events_async(FwupdClient *self, guint limit, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_host_security_events_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_device_by_id_async(FwupdClient *self, const gchar *device_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); FwupdDevice * fwupd_client_get_device_by_id_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_devices_by_guid_async(FwupdClient *self, const gchar *guid, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_devices_by_guid_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_install_async(FwupdClient *self, const gchar *device_id, const gchar *filename, FwupdInstallFlags install_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2, 3); gboolean fwupd_client_install_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_install_bytes_async(FwupdClient *self, const gchar *device_id, GBytes *bytes, FwupdInstallFlags install_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2, 3); gboolean fwupd_client_install_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); G_DEPRECATED_FOR(fwupd_client_install_release2_async) void fwupd_client_install_release_async(FwupdClient *self, FwupdDevice *device, FwupdRelease *release, FwupdInstallFlags install_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2, 3); void fwupd_client_install_release2_async(FwupdClient *self, FwupdDevice *device, FwupdRelease *release, FwupdInstallFlags install_flags, FwupdClientDownloadFlags download_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2, 3); gboolean fwupd_client_install_release_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_update_metadata_bytes_async(FwupdClient *self, const gchar *remote_id, GBytes *metadata, GBytes *signature, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2, 3, 4); gboolean fwupd_client_update_metadata_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); G_DEPRECATED_FOR(fwupd_client_refresh_remote2_async) void fwupd_client_refresh_remote_async(FwupdClient *self, FwupdRemote *remote, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); void fwupd_client_refresh_remote2_async(FwupdClient *self, FwupdRemote *remote, FwupdClientDownloadFlags download_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_refresh_remote_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_modify_remote_async(FwupdClient *self, const gchar *remote_id, const gchar *key, const gchar *value, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2, 3); gboolean fwupd_client_modify_remote_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_modify_device_async(FwupdClient *self, const gchar *device_id, const gchar *key, const gchar *value, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2, 3); gboolean fwupd_client_modify_device_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_report_metadata_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); GHashTable * fwupd_client_get_report_metadata_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_inhibit_async(FwupdClient *self, const gchar *reason, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gchar * fwupd_client_inhibit_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_uninhibit_async(FwupdClient *self, const gchar *inhibit_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_uninhibit_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_emulation_load_async(FwupdClient *self, GBytes *data, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_emulation_load_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_NON_NULL(1, 2); void fwupd_client_emulation_save_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); GBytes * fwupd_client_emulation_save_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_fix_host_security_attr_async(FwupdClient *self, const gchar *appstream_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_fix_host_security_attr_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_NON_NULL(1); void fwupd_client_undo_host_security_attr_async(FwupdClient *self, const gchar *appstream_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_undo_host_security_attr_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_NON_NULL(1, 2); FwupdStatus fwupd_client_get_status(FwupdClient *self) G_GNUC_NON_NULL(1); gboolean fwupd_client_get_tainted(FwupdClient *self) G_GNUC_NON_NULL(1); gboolean fwupd_client_get_only_trusted(FwupdClient *self) G_GNUC_NON_NULL(1); gboolean fwupd_client_get_daemon_interactive(FwupdClient *self) G_GNUC_NON_NULL(1); guint fwupd_client_get_percentage(FwupdClient *self) G_GNUC_NON_NULL(1); const gchar * fwupd_client_get_daemon_version(FwupdClient *self) G_GNUC_NON_NULL(1); void fwupd_client_set_daemon_version(FwupdClient *self, const gchar *daemon_version) G_GNUC_NON_NULL(1); const gchar * fwupd_client_get_host_bkc(FwupdClient *self) G_GNUC_NON_NULL(1); const gchar * fwupd_client_get_host_vendor(FwupdClient *self) G_GNUC_NON_NULL(1); const gchar * fwupd_client_get_host_product(FwupdClient *self) G_GNUC_NON_NULL(1); const gchar * fwupd_client_get_host_machine_id(FwupdClient *self) G_GNUC_NON_NULL(1); const gchar * fwupd_client_get_host_security_id(FwupdClient *self) G_GNUC_NON_NULL(1); guint32 fwupd_client_get_battery_level(FwupdClient *self) G_GNUC_NON_NULL(1); guint32 fwupd_client_get_battery_threshold(FwupdClient *self) G_GNUC_NON_NULL(1); void fwupd_client_get_remotes_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_remotes_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_remote_by_id_async(FwupdClient *self, const gchar *remote_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); FwupdRemote * fwupd_client_get_remote_by_id_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_approved_firmware_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); GPtrArray * fwupd_client_get_approved_firmware_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_set_approved_firmware_async(FwupdClient *self, GPtrArray *checksums, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_set_approved_firmware_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_get_blocked_firmware_async(FwupdClient *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_client_get_blocked_firmware_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_set_blocked_firmware_async(FwupdClient *self, GPtrArray *checksums, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_set_blocked_firmware_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_self_sign_async(FwupdClient *self, const gchar *value, FwupdSelfSignFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); gchar * fwupd_client_self_sign_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_set_feature_flags_async(FwupdClient *self, FwupdFeatureFlags feature_flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1); gboolean fwupd_client_set_feature_flags_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); const gchar * fwupd_client_get_user_agent(FwupdClient *self) G_GNUC_NON_NULL(1); void fwupd_client_set_user_agent(FwupdClient *self, const gchar *user_agent) G_GNUC_NON_NULL(1); void fwupd_client_set_user_agent_for_package(FwupdClient *self, const gchar *package_name, const gchar *package_version) G_GNUC_NON_NULL(1, 2, 3); void fwupd_client_download_bytes_async(FwupdClient *self, const gchar *url, FwupdClientDownloadFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2); GBytes * fwupd_client_download_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fwupd_client_upload_bytes_async(FwupdClient *self, const gchar *url, const gchar *payload, const gchar *signature, FwupdClientUploadFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer callback_data) G_GNUC_NON_NULL(1, 2, 3) G_GNUC_NON_NULL(1); GBytes * fwupd_client_upload_bytes_finish(FwupdClient *self, GAsyncResult *res, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fwupd_client_ensure_networking(FwupdClient *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); void fwupd_client_add_hint(FwupdClient *self, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2, 3); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-common-private.h000066400000000000000000000027721460375044200210220ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #ifdef HAVE_GIO_UNIX #include #endif #include #include "fwupd-common.h" G_BEGIN_DECLS GVariant * fwupd_hash_kv_to_variant(GHashTable *hash) G_GNUC_NON_NULL(1); GHashTable * fwupd_variant_to_hash_kv(GVariant *dict) G_GNUC_NON_NULL(1); gchar * fwupd_build_user_agent_system(void); void fwupd_common_json_add_string(JsonBuilder *builder, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2); void fwupd_common_json_add_stringv(JsonBuilder *builder, const gchar *key, gchar **value) G_GNUC_NON_NULL(1, 2); void fwupd_common_json_add_int(JsonBuilder *builder, const gchar *key, guint64 value) G_GNUC_NON_NULL(1, 2); void fwupd_common_json_add_boolean(JsonBuilder *builder, const gchar *key, gboolean value) G_GNUC_NON_NULL(1, 2); #ifdef HAVE_GIO_UNIX GUnixInputStream * fwupd_unix_input_stream_from_bytes(GBytes *bytes, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GUnixInputStream * fwupd_unix_input_stream_from_fn(const gchar *fn, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); #endif void fwupd_pad_kv_unx(GString *str, const gchar *key, guint64 value) G_GNUC_NON_NULL(1); void fwupd_pad_kv_str(GString *str, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1); void fwupd_pad_kv_int(GString *str, const gchar *key, guint32 value) G_GNUC_NON_NULL(1); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-common.c000066400000000000000000001021571460375044200173430ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fwupd-common-private.h" #include "fwupd-device.h" #include "fwupd-error.h" #include "fwupd-release.h" #ifdef HAVE_GIO_UNIX #include #include #include #include #endif #ifdef HAVE_MEMFD_CREATE #include #endif #include #include #ifdef HAVE_UTSNAME_H #include #endif #include /** * fwupd_checksum_guess_kind: * @checksum: (nullable): a checksum * * Guesses the checksum kind based on the length of the hash. * * Returns: a checksum type, e.g. %G_CHECKSUM_SHA1 * * Since: 0.9.3 **/ GChecksumType fwupd_checksum_guess_kind(const gchar *checksum) { guint len; g_return_val_if_fail(checksum != NULL, G_CHECKSUM_SHA1); len = strlen(checksum); if (len == 32) return G_CHECKSUM_MD5; if (len == 40) return G_CHECKSUM_SHA1; if (len == 64) return G_CHECKSUM_SHA256; if (len == 96) return G_CHECKSUM_SHA384; if (len == 128) return G_CHECKSUM_SHA512; return G_CHECKSUM_SHA1; } /** * fwupd_checksum_type_to_string_display: * @checksum_type: a #GChecksumType, e.g. %G_CHECKSUM_SHA1 * * Formats a checksum type for display. * * Returns: text, or %NULL for invalid * * Since: 1.9.6 **/ const gchar * fwupd_checksum_type_to_string_display(GChecksumType checksum_type) { if (checksum_type == G_CHECKSUM_MD5) return "MD5"; if (checksum_type == G_CHECKSUM_SHA1) return "SHA1"; if (checksum_type == G_CHECKSUM_SHA256) return "SHA256"; if (checksum_type == G_CHECKSUM_SHA384) return "SHA384"; if (checksum_type == G_CHECKSUM_SHA512) return "SHA512"; return NULL; } /** * fwupd_checksum_format_for_display: * @checksum: (nullable): a checksum * * Formats a checksum for display. * * Returns: text, or %NULL for invalid * * Since: 0.9.3 **/ gchar * fwupd_checksum_format_for_display(const gchar *checksum) { GChecksumType kind = fwupd_checksum_guess_kind(checksum); return g_strdup_printf("%s(%s)", fwupd_checksum_type_to_string_display(kind), checksum); } /** * fwupd_checksum_get_by_kind: * @checksums: (element-type utf8): checksums * @kind: a checksum type, e.g. %G_CHECKSUM_SHA512 * * Gets a specific checksum kind. * * Returns: a checksum from the array, or %NULL if not found * * Since: 0.9.4 **/ const gchar * fwupd_checksum_get_by_kind(GPtrArray *checksums, GChecksumType kind) { g_return_val_if_fail(checksums != NULL, NULL); for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index(checksums, i); if (fwupd_checksum_guess_kind(checksum) == kind) return checksum; } return NULL; } /** * fwupd_checksum_get_best: * @checksums: (element-type utf8): checksums * * Gets a the best possible checksum kind. * * Returns: a checksum from the array, or %NULL if nothing was suitable * * Since: 0.9.4 **/ const gchar * fwupd_checksum_get_best(GPtrArray *checksums) { GChecksumType checksum_types[] = {G_CHECKSUM_SHA512, G_CHECKSUM_SHA256, G_CHECKSUM_SHA384, G_CHECKSUM_SHA1, 0}; g_return_val_if_fail(checksums != NULL, NULL); for (guint i = 0; checksum_types[i] != 0; i++) { for (guint j = 0; j < checksums->len; j++) { const gchar *checksum = g_ptr_array_index(checksums, j); if (fwupd_checksum_guess_kind(checksum) == checksum_types[i]) return checksum; } } return NULL; } static gchar * fwupd_get_os_release_filename(void) { #ifndef _WIN32 const gchar *hostdir = g_getenv("FWUPD_HOSTDIR"); const gchar *sysconfdir = g_getenv("FWUPD_SYSCONFDIR"); g_autofree gchar *fn1 = NULL; if (hostdir == NULL) hostdir = "/"; /* override */ if (sysconfdir != NULL) { g_autofree gchar *fn2 = g_build_filename(hostdir, sysconfdir, "os-release", NULL); if (g_file_test(fn2, G_FILE_TEST_EXISTS)) return g_steal_pointer(&fn2); } /* host locations */ if (g_strcmp0(sysconfdir, "/etc") != 0) { g_autofree gchar *fn2 = g_build_filename(hostdir, "/etc/os-release", NULL); if (g_file_test(fn2, G_FILE_TEST_EXISTS)) return g_steal_pointer(&fn2); } fn1 = g_build_filename(hostdir, "/usr/lib/os-release", NULL); if (g_file_test(fn1, G_FILE_TEST_EXISTS)) return g_steal_pointer(&fn1); #endif return NULL; } #ifdef HOST_MACHINE_SYSTEM_DARWIN static GHashTable * fwupd_get_os_release_darwin(GError **error) { g_autofree gchar *stdout = NULL; g_autofree gchar *sw_vers = g_find_program_in_path("sw_vers"); g_auto(GStrv) split = NULL; g_autoptr(GHashTable) hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); struct { const gchar *key; const gchar *val; } kvs[] = {{"ProductName:", "NAME"}, {"ProductVersion:", "VERSION_ID"}, {"BuildVersion:", "VARIANT_ID"}, {NULL, NULL}}; g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* macOS */ if (sw_vers == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "No os-release found"); return NULL; } /* parse in format: * * ProductName: Mac OS X * ProductVersion: 10.14.6 * BuildVersion: 18G103 */ if (!g_spawn_command_line_sync(sw_vers, &stdout, NULL, NULL, error)) return NULL; split = g_strsplit(stdout, "\n", -1); for (guint j = 0; split[j] != NULL; j++) { for (guint i = 0; kvs[i].key != NULL; i++) { if (g_str_has_prefix(split[j], kvs[i].key)) { g_autofree gchar *tmp = g_strdup(split[j] + strlen(kvs[i].key)); g_hash_table_insert(hash, g_strdup(kvs[i].val), g_strdup(g_strstrip(tmp))); } } } g_hash_table_insert(hash, g_strdup("ID"), g_strdup("macos")); return g_steal_pointer(&hash); } #endif /** * fwupd_get_os_release_full: * @filename: (nullable): optional filename to load * @error: (nullable): optional return location for an error * * Loads information from a defined system os-release file. * * Returns: (transfer container) (element-type utf8 utf8): keys from os-release * * Since: 1.8.8 **/ GHashTable * fwupd_get_os_release_full(const gchar *filename, GError **error) { g_autofree gchar *buf = NULL; g_autofree gchar *filename2 = g_strdup(filename); g_auto(GStrv) lines = NULL; g_autoptr(GHashTable) hash = NULL; g_return_val_if_fail(error == NULL || *error == NULL, NULL); hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); if (filename2 == NULL) filename2 = fwupd_get_os_release_filename(); #ifdef HOST_MACHINE_SYSTEM_DARWIN if (filename2 == NULL) return fwupd_get_os_release_darwin(error); #endif if (filename2 == NULL) { #if defined(_WIN32) /* TODO: Read the Windows version */ g_hash_table_insert(hash, g_strdup("OS"), g_strdup("Windows")); #elif defined(__NetBSD__) g_hash_table_insert(hash, g_strdup("OS"), g_strdup("NetBSD")); #elif defined(__OpenBSD__) g_hash_table_insert(hash, g_strdup("OS"), g_strdup("OpenBSD")); #endif if (g_hash_table_size(hash) > 0) return g_steal_pointer(&hash); g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "No os-release found"); return NULL; } /* load each line */ if (!g_file_get_contents(filename2, &buf, NULL, error)) return NULL; lines = g_strsplit(buf, "\n", -1); for (guint i = 0; lines[i] != NULL; i++) { gsize len, off = 0; g_auto(GStrv) split = NULL; /* split up into sections */ split = g_strsplit(lines[i], "=", 2); if (g_strv_length(split) < 2) continue; /* remove double quotes if set both ends */ len = strlen(split[1]); if (len == 0) continue; if (split[1][0] == '\"' && split[1][len - 1] == '\"') { off++; len -= 2; } g_hash_table_insert(hash, g_strdup(split[0]), g_strndup(split[1] + off, len)); } return g_steal_pointer(&hash); } /** * fwupd_get_os_release: * @error: (nullable): optional return location for an error * * Loads information from the system os-release file. * * Returns: (transfer container) (element-type utf8 utf8): keys from os-release * * Since: 1.0.7 **/ GHashTable * fwupd_get_os_release(GError **error) { g_return_val_if_fail(error == NULL || *error == NULL, NULL); return fwupd_get_os_release_full(NULL, error); } static gchar * fwupd_build_user_agent_os_release(void) { const gchar *keys[] = {"NAME", "VERSION_ID", "VARIANT", NULL}; g_autoptr(GHashTable) hash = NULL; g_autoptr(GPtrArray) ids_os = g_ptr_array_new(); /* get all keys */ hash = fwupd_get_os_release(NULL); if (hash == NULL) return NULL; /* create an array of the keys that exist */ for (guint i = 0; keys[i] != NULL; i++) { const gchar *value = g_hash_table_lookup(hash, keys[i]); if (value != NULL) g_ptr_array_add(ids_os, (gpointer)value); } if (ids_os->len == 0) return NULL; g_ptr_array_add(ids_os, NULL); return g_strjoinv(" ", (gchar **)ids_os->pdata); } /** * fwupd_build_user_agent_system: (skip): **/ gchar * fwupd_build_user_agent_system(void) { #ifdef HAVE_UTSNAME_H struct utsname name_tmp; #endif g_autofree gchar *locale = NULL; g_autofree gchar *os_release = NULL; g_autoptr(GPtrArray) ids = g_ptr_array_new_with_free_func(g_free); /* system, architecture and kernel, e.g. "Linux i686 4.14.5" */ #ifdef HAVE_UTSNAME_H memset(&name_tmp, 0, sizeof(struct utsname)); if (uname(&name_tmp) >= 0) { g_ptr_array_add(ids, g_strdup_printf("%s %s %s", name_tmp.sysname, name_tmp.machine, name_tmp.release)); } #endif /* current locale, e.g. "en-gb" */ #ifdef HAVE_LC_MESSAGES locale = g_strdup(setlocale(LC_MESSAGES, NULL)); #endif if (locale != NULL) { g_strdelimit(locale, ".", '\0'); g_strdelimit(locale, "_", '-'); g_ptr_array_add(ids, g_steal_pointer(&locale)); } /* OS release, e.g. "Fedora 27 Workstation" */ os_release = fwupd_build_user_agent_os_release(); if (os_release != NULL) g_ptr_array_add(ids, g_steal_pointer(&os_release)); /* convert to string */ if (ids->len == 0) return NULL; g_ptr_array_add(ids, NULL); return g_strjoinv("; ", (gchar **)ids->pdata); } /** * fwupd_build_user_agent: * @package_name: (not nullable): client program name, e.g. `gnome-software` * @package_version: (not nullable): client program version, e.g. `3.28.1` * * Builds a user-agent to use for the download. * * Supplying harmless details to the server means it knows more about each * client. This allows the web service to respond in a different way, for * instance sending a different metadata file for old versions of fwupd, or * returning an error for Solaris machines. * * Before freaking out about theoretical privacy implications, much more data * than this is sent to each and every website you visit. * * Rather that using this function you should use [method@Client.set_user_agent_for_package] * which uses the *runtime* version of the daemon rather than the *build-time* * version. * * Returns: a string, e.g. `foo/0.1 (Linux i386 4.14.5; en; Fedora 27) fwupd/1.0.3` * * Since: 1.0.3 **/ gchar * fwupd_build_user_agent(const gchar *package_name, const gchar *package_version) { g_autoptr(GString) str = g_string_new(NULL); g_autofree gchar *system = NULL; g_return_val_if_fail(package_name != NULL, NULL); g_return_val_if_fail(package_version != NULL, NULL); /* application name and version */ g_string_append_printf(str, "%s/%s", package_name, package_version); /* system information */ system = fwupd_build_user_agent_system(); if (system != NULL) g_string_append_printf(str, " (%s)", system); /* platform, which in our case is just fwupd */ if (g_strcmp0(package_name, "fwupd") != 0) g_string_append_printf(str, " fwupd/%s", PACKAGE_VERSION); /* success */ return g_string_free(g_steal_pointer(&str), FALSE); } /** * fwupd_build_machine_id: * @salt: (nullable): optional salt * @error: (nullable): optional return location for an error * * Gets a salted hash of the /etc/machine-id contents. This can be used to * identify a specific machine. It is not possible to recover the original * machine-id from the machine-hash. * * Returns: the SHA256 machine hash, or %NULL if the ID is not present * * Since: 1.0.4 **/ gchar * fwupd_build_machine_id(const gchar *salt, GError **error) { const gchar *fn = NULL; g_autofree gchar *buf = NULL; g_auto(GStrv) fns = g_new0(gchar *, 6); g_autoptr(GChecksum) csum = NULL; gsize sz = 0; g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* one of these has to exist */ fns[0] = g_build_filename(FWUPD_SYSCONFDIR, "machine-id", NULL); fns[1] = g_build_filename(FWUPD_LOCALSTATEDIR, "lib", "dbus", "machine-id", NULL); fns[2] = g_strdup("/etc/machine-id"); fns[3] = g_strdup("/var/lib/dbus/machine-id"); fns[4] = g_strdup("/var/db/dbus/machine-id"); for (guint i = 0; fns[i] != NULL; i++) { if (g_file_test(fns[i], G_FILE_TEST_EXISTS)) { fn = fns[i]; break; } } if (fn == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "The machine-id is not present"); return NULL; } if (!g_file_get_contents(fn, &buf, &sz, error)) return NULL; if (sz == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "The machine-id is present but unset"); return NULL; } csum = g_checksum_new(G_CHECKSUM_SHA256); if (salt != NULL) g_checksum_update(csum, (const guchar *)salt, (gssize)strlen(salt)); g_checksum_update(csum, (const guchar *)buf, (gssize)sz); return g_strdup(g_checksum_get_string(csum)); } static void fwupd_build_history_report_json_metadata_device(JsonBuilder *builder, FwupdDevice *dev) { FwupdRelease *rel = fwupd_device_get_release_default(dev); GHashTable *metadata = fwupd_release_get_metadata(rel); g_autoptr(GList) keys = NULL; /* add each metadata value */ keys = g_hash_table_get_keys(metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup(metadata, key); json_builder_set_member_name(builder, key); json_builder_add_string_value(builder, value); } } static void fwupd_build_history_report_json_device(JsonBuilder *builder, FwupdDevice *dev) { FwupdRelease *rel = fwupd_device_get_release_default(dev); GChecksumType checksum_types[] = {G_CHECKSUM_SHA256, G_CHECKSUM_SHA1, 0}; GPtrArray *checksums; GPtrArray *guids; /* identify the firmware used */ checksums = fwupd_release_get_checksums(rel); for (guint i = 0; checksum_types[i] != 0; i++) { const gchar *checksum = fwupd_checksum_get_by_kind(checksums, checksum_types[i]); if (checksum != NULL) { json_builder_set_member_name(builder, "Checksum"); json_builder_add_string_value(builder, checksum); break; } } /* identify the firmware written */ checksums = fwupd_device_get_checksums(dev); if (checksums->len > 0) { json_builder_set_member_name(builder, "ChecksumDevice"); json_builder_begin_array(builder); for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index(checksums, i); json_builder_add_string_value(builder, checksum); } json_builder_end_array(builder); } /* allow matching the specific component */ json_builder_set_member_name(builder, "ReleaseId"); json_builder_add_string_value(builder, fwupd_release_get_id(rel)); /* include the protocol used */ if (fwupd_release_get_protocol(rel) != NULL) { json_builder_set_member_name(builder, "Protocol"); json_builder_add_string_value(builder, fwupd_release_get_protocol(rel)); } /* set the error state of the report */ json_builder_set_member_name(builder, "UpdateState"); json_builder_add_int_value(builder, fwupd_device_get_update_state(dev)); if (fwupd_device_get_update_error(dev) != NULL) { json_builder_set_member_name(builder, "UpdateError"); json_builder_add_string_value(builder, fwupd_device_get_update_error(dev)); } if (fwupd_release_get_update_message(rel) != NULL) { json_builder_set_member_name(builder, "UpdateMessage"); json_builder_add_string_value(builder, fwupd_release_get_update_message(rel)); } /* find out if the predicted duration was accurate */ if (fwupd_device_get_install_duration(dev) != 0) { json_builder_set_member_name(builder, "InstallDuration"); json_builder_add_int_value(builder, fwupd_device_get_install_duration(dev)); } /* map back to the dev type on the LVFS */ guids = fwupd_device_get_guids(dev); if (guids->len > 0) { json_builder_set_member_name(builder, "Guid"); json_builder_begin_array(builder); for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index(guids, i); json_builder_add_string_value(builder, guid); } json_builder_end_array(builder); } json_builder_set_member_name(builder, "Plugin"); json_builder_add_string_value(builder, fwupd_device_get_plugin(dev)); /* report what we're trying to update *from* and *to* */ json_builder_set_member_name(builder, "VersionOld"); json_builder_add_string_value(builder, fwupd_device_get_version(dev)); json_builder_set_member_name(builder, "VersionNew"); json_builder_add_string_value(builder, fwupd_release_get_version(rel)); /* to know the state of the dev we're trying to update */ json_builder_set_member_name(builder, "Flags"); json_builder_add_int_value(builder, fwupd_device_get_flags(dev)); /* to know when the update tried to happen, and how soon after boot */ json_builder_set_member_name(builder, "Created"); json_builder_add_int_value(builder, fwupd_device_get_created(dev)); json_builder_set_member_name(builder, "Modified"); json_builder_add_int_value(builder, fwupd_device_get_modified(dev)); /* add saved metadata to the report */ json_builder_set_member_name(builder, "Metadata"); json_builder_begin_object(builder); fwupd_build_history_report_json_metadata_device(builder, dev); json_builder_end_object(builder); } static gboolean fwupd_build_history_report_json_metadata(JsonBuilder *builder, GError **error) { g_autoptr(GHashTable) hash = NULL; struct { const gchar *key; const gchar *val; } distro_kv[] = {{"ID", "DistroId"}, {"VERSION_ID", "DistroVersion"}, {"VARIANT_ID", "DistroVariant"}, {NULL, NULL}}; /* get all required os-release keys */ hash = fwupd_get_os_release(error); if (hash == NULL) return FALSE; for (guint i = 0; distro_kv[i].key != NULL; i++) { const gchar *tmp = g_hash_table_lookup(hash, distro_kv[i].key); if (tmp != NULL) { json_builder_set_member_name(builder, distro_kv[i].val); json_builder_add_string_value(builder, tmp); } } return TRUE; } /** * fwupd_build_history_report_json: * @devices: (element-type FwupdDevice): devices * @error: (nullable): optional return location for an error * * Builds a JSON report for the list of devices. No filtering is done on the * @devices array, and it is expected that the caller will filter to something * sane, e.g. %FWUPD_DEVICE_FLAG_REPORTED at the bare minimum. * * Returns: a string, or %NULL if the ID is not present * * Since: 1.0.4 **/ gchar * fwupd_build_history_report_json(GPtrArray *devices, GError **error) { gchar *data; g_autofree gchar *machine_id = NULL; g_autoptr(JsonBuilder) builder = NULL; g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonNode) json_root = NULL; g_return_val_if_fail(devices != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* get a hash that represents the machine */ machine_id = fwupd_build_machine_id("fwupd", error); if (machine_id == NULL) return NULL; /* create header */ builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "ReportVersion"); json_builder_add_int_value(builder, 2); json_builder_set_member_name(builder, "MachineId"); json_builder_add_string_value(builder, machine_id); /* this is system metadata not stored in the database */ json_builder_set_member_name(builder, "Metadata"); json_builder_begin_object(builder); if (!fwupd_build_history_report_json_metadata(builder, error)) return NULL; json_builder_end_object(builder); /* add each device */ json_builder_set_member_name(builder, "Reports"); json_builder_begin_array(builder); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); json_builder_begin_object(builder); fwupd_build_history_report_json_device(builder, dev); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); /* export as a string */ json_root = json_builder_get_root(builder); json_generator = json_generator_new(); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); data = json_generator_to_data(json_generator, NULL); if (data == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to convert to JSON string"); return NULL; } return data; } #define FWUPD_GUID_NAMESPACE_DEFAULT "6ba7b810-9dad-11d1-80b4-00c04fd430c8" #define FWUPD_GUID_NAMESPACE_MICROSOFT "70ffd812-4c7f-4c7d-0000-000000000000" typedef struct __attribute__((packed)) { guint32 a; guint16 b; guint16 c; guint16 d; guint8 e[6]; } fwupd_guid_native_t; /** * fwupd_guid_to_string: * @guid: a #fwupd_guid_t to read * @flags: GUID flags, e.g. %FWUPD_GUID_FLAG_MIXED_ENDIAN * * Returns a text GUID of mixed or BE endian for a packed buffer. * * Returns: a new GUID string * * Since: 1.2.5 **/ gchar * fwupd_guid_to_string(const fwupd_guid_t *guid, FwupdGuidFlags flags) { fwupd_guid_native_t gnat; g_return_val_if_fail(guid != NULL, NULL); /* copy to avoid issues with aligning */ memcpy(&gnat, guid, sizeof(gnat)); /* mixed is bizaar, but specified as the DCE encoding */ if (flags & FWUPD_GUID_FLAG_MIXED_ENDIAN) { return g_strdup_printf("%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x", (guint)GUINT32_FROM_LE(gnat.a), (guint)GUINT16_FROM_LE(gnat.b), (guint)GUINT16_FROM_LE(gnat.c), (guint)GUINT16_FROM_BE(gnat.d), gnat.e[0], gnat.e[1], gnat.e[2], gnat.e[3], gnat.e[4], gnat.e[5]); } return g_strdup_printf("%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x", (guint)GUINT32_FROM_BE(gnat.a), (guint)GUINT16_FROM_BE(gnat.b), (guint)GUINT16_FROM_BE(gnat.c), (guint)GUINT16_FROM_BE(gnat.d), gnat.e[0], gnat.e[1], gnat.e[2], gnat.e[3], gnat.e[4], gnat.e[5]); } /** * fwupd_guid_from_string: * @guidstr: (not nullable): a GUID, e.g. `00112233-4455-6677-8899-aabbccddeeff` * @guid: (nullable): a #fwupd_guid_t, or NULL to just check the GUID * @flags: GUID flags, e.g. %FWUPD_GUID_FLAG_MIXED_ENDIAN * @error: (nullable): optional return location for an error * * Converts a string GUID into its binary encoding. All string GUIDs are * formatted as big endian but on-disk can be encoded in different ways. * * Returns: %TRUE for success * * Since: 1.2.5 **/ gboolean fwupd_guid_from_string(const gchar *guidstr, fwupd_guid_t *guid, FwupdGuidFlags flags, GError **error) { fwupd_guid_native_t gu = {0x0}; gboolean mixed_endian = flags & FWUPD_GUID_FLAG_MIXED_ENDIAN; guint64 tmp; g_auto(GStrv) split = NULL; g_return_val_if_fail(guidstr != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* split into sections */ if (strlen(guidstr) != 36) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "GUID is not valid format"); return FALSE; } split = g_strsplit(guidstr, "-", 5); if (g_strv_length(split) != 5) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "GUID is not valid format, no dashes"); return FALSE; } if (strlen(split[0]) != 8 && strlen(split[1]) != 4 && strlen(split[2]) != 4 && strlen(split[3]) != 4 && strlen(split[4]) != 12) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "GUID is not valid format, not GUID"); return FALSE; } /* parse */ if (!g_ascii_string_to_unsigned(split[0], 16, 0, 0xffffffff, &tmp, error)) return FALSE; gu.a = mixed_endian ? GUINT32_TO_LE(tmp) : GUINT32_TO_BE(tmp); if (!g_ascii_string_to_unsigned(split[1], 16, 0, 0xffff, &tmp, error)) return FALSE; gu.b = mixed_endian ? GUINT16_TO_LE(tmp) : GUINT16_TO_BE(tmp); if (!g_ascii_string_to_unsigned(split[2], 16, 0, 0xffff, &tmp, error)) return FALSE; gu.c = mixed_endian ? GUINT16_TO_LE(tmp) : GUINT16_TO_BE(tmp); if (!g_ascii_string_to_unsigned(split[3], 16, 0, 0xffff, &tmp, error)) return FALSE; gu.d = GUINT16_TO_BE(tmp); for (guint i = 0; i < 6; i++) { gchar buffer[3] = {0x0}; memcpy(buffer, split[4] + (i * 2), 2); if (!g_ascii_string_to_unsigned(buffer, 16, 0, 0xff, &tmp, error)) return FALSE; gu.e[i] = tmp; } if (guid != NULL) memcpy(guid, &gu, sizeof(gu)); /* success */ return TRUE; } /** * fwupd_guid_hash_data: * @data: data to hash * @datasz: length of @data * @flags: GUID flags, e.g. %FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT * * Returns a GUID for some data. This uses a hash and so even small * differences in the @data will produce radically different return values. * * The implementation is taken from RFC4122, Section 4.1.3; specifically * using a type-5 SHA-1 hash. * * Returns: a new GUID, or %NULL for internal error * * Since: 1.2.5 **/ gchar * fwupd_guid_hash_data(const guint8 *data, gsize datasz, FwupdGuidFlags flags) { gsize digestlen = 20; guint8 hash[20]; fwupd_guid_t uu_new; g_autoptr(GChecksum) csum = NULL; const fwupd_guid_t uu_default = {0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}; const fwupd_guid_t uu_microso = {0x70, 0xff, 0xd8, 0x12, 0x4c, 0x7f, 0x4c, 0x7d}; const fwupd_guid_t *uu_namespace = &uu_default; g_return_val_if_fail(data != NULL, NULL); g_return_val_if_fail(datasz != 0, NULL); /* old MS GUID */ if (flags & FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT) uu_namespace = &uu_microso; /* hash the namespace and then the string */ csum = g_checksum_new(G_CHECKSUM_SHA1); g_checksum_update(csum, (guchar *)uu_namespace, sizeof(*uu_namespace)); g_checksum_update(csum, (guchar *)data, (gssize)datasz); g_checksum_get_digest(csum, hash, &digestlen); /* copy most parts of the hash 1:1 */ memcpy(uu_new, hash, sizeof(uu_new)); /* set specific bits according to Section 4.1.3 */ uu_new[6] = (guint8)((uu_new[6] & 0x0f) | (5 << 4)); uu_new[8] = (guint8)((uu_new[8] & 0x3f) | 0x80); return fwupd_guid_to_string((const fwupd_guid_t *)&uu_new, flags); } /** * fwupd_device_id_is_valid: * @device_id: string to check, e.g. `d3fae86d95e5d56626129d00e332c4b8dac95442` * * Checks the string is a valid non-partial device ID. It is important to note * that the wildcard ID of `*` is not considered a valid ID in this function and * the client must check for this manually if this should be allowed. * * Returns: %TRUE if @guid was a fwupd device ID, %FALSE otherwise * * Since: 1.4.1 **/ gboolean fwupd_device_id_is_valid(const gchar *device_id) { if (device_id == NULL) return FALSE; if (strlen(device_id) != 40) return FALSE; for (guint i = 0; device_id[i] != '\0'; i++) { gchar tmp = device_id[i]; /* isalnum isn't case specific */ if ((tmp < 'a' || tmp > 'f') && (tmp < '0' || tmp > '9')) return FALSE; } return TRUE; } /** * fwupd_guid_is_valid: * @guid: string to check, e.g. `00112233-4455-6677-8899-aabbccddeeff` * * Checks the string is a valid GUID. * * Returns: %TRUE if @guid was a valid GUID, %FALSE otherwise * * Since: 1.2.5 **/ gboolean fwupd_guid_is_valid(const gchar *guid) { const gchar zeroguid[] = {"00000000-0000-0000-0000-000000000000"}; /* sanity check */ if (guid == NULL) return FALSE; /* check for dashes and hexdigits in the right place */ for (guint i = 0; i < sizeof(zeroguid) - 1; i++) { if (guid[i] == '\0') return FALSE; if (zeroguid[i] == '-') { if (guid[i] != '-') return FALSE; continue; } if (!g_ascii_isxdigit(guid[i])) return FALSE; } /* longer than required */ if (guid[sizeof(zeroguid) - 1] != '\0') return FALSE; /* not valid */ return g_strcmp0(guid, zeroguid) != 0; } /** * fwupd_guid_hash_string: * @str: (nullable): a source string to use as a key * * Returns a GUID for a given string. This uses a hash and so even small * differences in the @str will produce radically different return values. * * The default implementation is taken from RFC4122, Section 4.1.3; specifically * using a type-5 SHA-1 hash with a DNS namespace. * The same result can be obtained with this simple python program: * * #!/usr/bin/python * import uuid * print uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org') * * Returns: a new GUID, or %NULL if the string was invalid * * Since: 1.2.5 **/ gchar * fwupd_guid_hash_string(const gchar *str) { if (str == NULL || str[0] == '\0') return NULL; return fwupd_guid_hash_data((const guint8 *)str, strlen(str), FWUPD_GUID_FLAG_NONE); } /** * fwupd_hash_kv_to_variant: (skip): **/ GVariant * fwupd_hash_kv_to_variant(GHashTable *hash) { GVariantBuilder builder; g_autoptr(GList) keys = g_hash_table_get_keys(hash); g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup(hash, key); g_variant_builder_add(&builder, "{ss}", key, value); } return g_variant_builder_end(&builder); } /** * fwupd_variant_to_hash_kv: (skip): **/ GHashTable * fwupd_variant_to_hash_kv(GVariant *dict) { GHashTable *hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); GVariantIter iter; const gchar *key; const gchar *value; g_variant_iter_init(&iter, dict); while (g_variant_iter_loop(&iter, "{&s&s}", &key, &value)) g_hash_table_insert(hash, g_strdup(key), g_strdup(value)); return hash; } #ifdef HAVE_GIO_UNIX /** * fwupd_unix_input_stream_from_bytes: (skip): **/ GUnixInputStream * fwupd_unix_input_stream_from_bytes(GBytes *bytes, GError **error) { gint fd; gssize rc; #ifndef HAVE_MEMFD_CREATE gchar tmp_file[] = "/tmp/fwupd.XXXXXX"; #endif #ifdef HAVE_MEMFD_CREATE fd = memfd_create("fwupd", MFD_CLOEXEC); #else /* emulate in-memory file by an unlinked temporary file */ fd = g_mkstemp(tmp_file); if (fd != -1) { rc = g_unlink(tmp_file); if (rc != 0) { if (!g_close(fd, error)) { g_prefix_error(error, "failed to close temporary file: "); return NULL; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to unlink temporary file"); return NULL; } } #endif if (fd < 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to create memfd"); return NULL; } rc = write(fd, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes)); if (rc < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to write %" G_GSSIZE_FORMAT, rc); return NULL; } if (lseek(fd, 0, SEEK_SET) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to seek: %s", g_strerror(errno)); return NULL; } return G_UNIX_INPUT_STREAM(g_unix_input_stream_new(fd, TRUE)); } /** * fwupd_unix_input_stream_from_fn: (skip): **/ GUnixInputStream * fwupd_unix_input_stream_from_fn(const gchar *fn, GError **error) { gint fd = open(fn, O_RDONLY); if (fd < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to open %s", fn); return NULL; } return G_UNIX_INPUT_STREAM(g_unix_input_stream_new(fd, TRUE)); } #endif /** * fwupd_common_json_add_string: (skip): **/ void fwupd_common_json_add_string(JsonBuilder *builder, const gchar *key, const gchar *value) { if (value == NULL) return; json_builder_set_member_name(builder, key); json_builder_add_string_value(builder, value); } /** * fwupd_common_json_add_int: (skip): **/ void fwupd_common_json_add_int(JsonBuilder *builder, const gchar *key, guint64 value) { json_builder_set_member_name(builder, key); json_builder_add_int_value(builder, value); } /** * fwupd_common_json_add_boolean: (skip): **/ void fwupd_common_json_add_boolean(JsonBuilder *builder, const gchar *key, gboolean value) { json_builder_set_member_name(builder, key); json_builder_add_string_value(builder, value ? "true" : "false"); } /** * fwupd_common_json_add_stringv: (skip): **/ void fwupd_common_json_add_stringv(JsonBuilder *builder, const gchar *key, gchar **value) { if (value == NULL) return; json_builder_set_member_name(builder, key); json_builder_begin_array(builder); for (guint i = 0; value[i] != NULL; i++) json_builder_add_string_value(builder, value[i]); json_builder_end_array(builder); } /** * fwupd_pad_kv_str: (skip): **/ void fwupd_pad_kv_str(GString *str, const gchar *key, const gchar *value) { /* ignore */ if (key == NULL || value == NULL) return; g_string_append_printf(str, " %s: ", key); for (gsize i = strlen(key); i < 20; i++) g_string_append(str, " "); g_string_append_printf(str, "%s\n", value); } /** * fwupd_pad_kv_unx: (skip): **/ void fwupd_pad_kv_unx(GString *str, const gchar *key, guint64 value) { g_autoptr(GDateTime) date = NULL; g_autofree gchar *tmp = NULL; /* ignore */ if (value == 0) return; date = g_date_time_new_from_unix_utc((gint64)value); tmp = g_date_time_format(date, "%F"); fwupd_pad_kv_str(str, key, tmp); } /** * fwupd_pad_kv_int: (skip): **/ void fwupd_pad_kv_int(GString *str, const gchar *key, guint32 value) { g_autofree gchar *tmp = NULL; /* ignore */ if (value == 0) return; tmp = g_strdup_printf("%" G_GUINT32_FORMAT, value); fwupd_pad_kv_str(str, key, tmp); } fwupd-1.9.16/libfwupd/fwupd-common.h000066400000000000000000000061631460375044200173500ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fwupd-build.h" G_BEGIN_DECLS /** * FWUPD_DBUS_PATH: * * The dbus path **/ #define FWUPD_DBUS_PATH "/" /** * FWUPD_DBUS_SERVICE: * * The dbus service **/ #define FWUPD_DBUS_SERVICE "org.freedesktop.fwupd" /** * FWUPD_DBUS_INTERFACE: * * The dbus interface **/ #define FWUPD_DBUS_INTERFACE "org.freedesktop.fwupd" /** * FWUPD_DBUS_P2P_SOCKET_ADDRESS: * * The D-Bus socket address when using point-to-point connections. * * NOTE: This is no longer used as the value is set at compile time. **/ #define FWUPD_DBUS_P2P_SOCKET_ADDRESS "tcp:host=localhost,port=1341" /** * FWUPD_DEVICE_ID_ANY: * * Wildcard used for matching all device ids in fwupd **/ #define FWUPD_DEVICE_ID_ANY "*" /** * FwupdGuidFlags: * @FWUPD_GUID_FLAG_NONE: No trust * @FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT: Use the Microsoft-compatible namespace * @FWUPD_GUID_FLAG_MIXED_ENDIAN: Use EFI mixed endian representation * * The flags to show how the data should be converted. **/ typedef enum { FWUPD_GUID_FLAG_NONE = 0, /* Since: 1.2.5 */ FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT = 1 << 0, /* Since: 1.2.5 */ FWUPD_GUID_FLAG_MIXED_ENDIAN = 1 << 1, /* Since: 1.2.5 */ /*< private >*/ FWUPD_GUID_FLAG_LAST } FwupdGuidFlags; /* GObject Introspection does not understand typedefs with sizes */ #ifdef __GI_SCANNER__ typedef guint8 *fwupd_guid_t; #else typedef guint8 fwupd_guid_t[16]; #endif const gchar * fwupd_checksum_get_best(GPtrArray *checksums) G_GNUC_NON_NULL(1); const gchar * fwupd_checksum_get_by_kind(GPtrArray *checksums, GChecksumType kind) G_GNUC_NON_NULL(1); GChecksumType fwupd_checksum_guess_kind(const gchar *checksum) G_GNUC_NON_NULL(1); const gchar * fwupd_checksum_type_to_string_display(GChecksumType checksum_type); gchar * fwupd_checksum_format_for_display(const gchar *checksum) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(fwupd_client_set_user_agent_for_package) gchar * fwupd_build_user_agent(const gchar *package_name, const gchar *package_version) G_GNUC_NON_NULL(1, 2); gchar * fwupd_build_machine_id(const gchar *salt, GError **error); GHashTable * fwupd_get_os_release(GError **error); GHashTable * fwupd_get_os_release_full(const gchar *filename, GError **error); gchar * fwupd_build_history_report_json(GPtrArray *devices, GError **error) G_GNUC_NON_NULL(1); gboolean fwupd_device_id_is_valid(const gchar *device_id); #ifndef __GI_SCANNER__ gchar * fwupd_guid_to_string(const fwupd_guid_t *guid, FwupdGuidFlags flags) G_GNUC_NON_NULL(1); gboolean fwupd_guid_from_string(const gchar *guidstr, fwupd_guid_t *guid, FwupdGuidFlags flags, GError **error) G_GNUC_NON_NULL(1); #else gchar * fwupd_guid_to_string(const guint8 guid[16], FwupdGuidFlags flags); gboolean fwupd_guid_from_string(const gchar *guidstr, guint8 guid[16], FwupdGuidFlags flags, GError **error); #endif gboolean fwupd_guid_is_valid(const gchar *guid); gchar * fwupd_guid_hash_string(const gchar *str); gchar * fwupd_guid_hash_data(const guint8 *data, gsize datasz, FwupdGuidFlags flags) G_GNUC_NON_NULL(1); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-context-test.c000066400000000000000000000063341460375044200205140ustar00rootroot00000000000000/* * Copyright (C) 2020 Philip Withnall * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include typedef struct { GApplication *app; FwupdClient *client; GThread *main_thread; GThread *worker_thread; } FuThreadTestSelf; static gboolean fwupd_thread_test_exit_idle_cb(gpointer user_data) { FuThreadTestSelf *self = user_data; g_application_release(self->app); return G_SOURCE_REMOVE; } static gpointer fwupd_thread_test_thread_cb(gpointer user_data) { FuThreadTestSelf *self = user_data; g_autoptr(GError) error_local = NULL; g_autoptr(GMainContext) context = g_main_context_new(); g_autoptr(GMainContextPusher) pusher = g_main_context_pusher_new(context); g_assert_nonnull(pusher); g_message("Calling fwupd_client_get_devices() in thread %p with main context %p", g_thread_self(), g_main_context_get_thread_default()); if (!fwupd_client_connect(self->client, NULL, &error_local)) g_warning("%s", error_local->message); g_idle_add(fwupd_thread_test_exit_idle_cb, self); return NULL; } static gboolean fwupd_thread_test_idle_cb(gpointer user_data) { FuThreadTestSelf *self = user_data; g_message("fwupd_thread_test_idle_cb() in thread %p with main context %p", g_thread_self(), g_main_context_get_thread_default()); self->worker_thread = g_thread_new("worker00", fwupd_thread_test_thread_cb, self); return G_SOURCE_REMOVE; } static void fwupd_thread_test_activate_cb(GApplication *app, gpointer user_data) { FuThreadTestSelf *self = user_data; g_application_hold(self->app); g_idle_add(fwupd_thread_test_idle_cb, self); } static void fwupd_thread_test_notify_cb(GObject *object, GParamSpec *pspec, gpointer user_data) { FuThreadTestSelf *self = user_data; g_message("fwupd_thread_test_notify_cb() in thread %p with main context %p", g_thread_self(), g_main_context_get_thread_default()); g_assert_true(g_thread_self() == self->main_thread); g_assert_null(g_main_context_get_thread_default()); } static gboolean fwupd_thread_test_has_system_bus(void) { g_autoptr(GDBusConnection) conn = NULL; conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL); return conn != NULL; } int main(void) { gint retval; g_autoptr(FwupdClient) client = fwupd_client_new(); g_autoptr(GApplication) app = g_application_new("org.fwupd.ContextTest", G_APPLICATION_NON_UNIQUE); g_autoptr(GThread) worker_thread = NULL; FuThreadTestSelf self = { .app = app, .client = client, .worker_thread = worker_thread, .main_thread = g_thread_self(), }; /* only some of the CI targets have a DBus daemon */ if (!fwupd_thread_test_has_system_bus()) { g_message("D-Bus system bus unavailable, skipping tests."); return 0; } g_message("Created FwupdClient in thread %p with main context %p", g_thread_self(), g_main_context_get_thread_default()); g_signal_connect(FWUPD_CLIENT(client), "notify::status", G_CALLBACK(fwupd_thread_test_notify_cb), &self); g_signal_connect(G_APPLICATION(app), "activate", G_CALLBACK(fwupd_thread_test_activate_cb), &self); retval = g_application_run(app, 0, NULL); if (self.worker_thread != NULL) g_thread_join(g_steal_pointer(&self.worker_thread)); return retval; } fwupd-1.9.16/libfwupd/fwupd-device-private.h000066400000000000000000000014501460375044200207610ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-device.h" G_BEGIN_DECLS GVariant * fwupd_device_to_variant(FwupdDevice *self) G_GNUC_NON_NULL(1); GVariant * fwupd_device_to_variant_full(FwupdDevice *self, FwupdDeviceFlags flags) G_GNUC_NON_NULL(1); void fwupd_device_incorporate(FwupdDevice *self, FwupdDevice *donor) G_GNUC_NON_NULL(1, 2); void fwupd_device_to_json(FwupdDevice *self, JsonBuilder *builder) G_GNUC_NON_NULL(1, 2); void fwupd_device_to_json_full(FwupdDevice *self, JsonBuilder *builder, FwupdDeviceFlags flags) G_GNUC_NON_NULL(1, 2); gboolean fwupd_device_from_json(FwupdDevice *self, JsonNode *json_node, GError **error) G_GNUC_NON_NULL(1, 2); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-device.c000066400000000000000000003554061460375044200173210ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fwupd-common-private.h" #include "fwupd-device-private.h" #include "fwupd-enums-private.h" #include "fwupd-error.h" #include "fwupd-release-private.h" /** * FwupdDevice: * * A physical device on the host with optionally updatable firmware. * * See also: [class@FwupdRelease] */ static void fwupd_device_finalize(GObject *object); typedef struct { gchar *id; gchar *parent_id; gchar *composite_id; guint64 created; guint64 modified; guint64 flags; guint64 request_flags; guint64 problems; GPtrArray *guids; GPtrArray *vendor_ids; GPtrArray *protocols; GPtrArray *instance_ids; GPtrArray *icons; GPtrArray *issues; /* of utf-8 */ gchar *name; gchar *serial; gchar *summary; gchar *branch; gchar *description; gchar *vendor; gchar *vendor_id; /* for compat only */ gchar *homepage; gchar *plugin; gchar *protocol; gchar *version; gchar *version_lowest; gchar *version_bootloader; FwupdVersionFormat version_format; guint64 version_raw; guint64 version_build_date; guint64 version_lowest_raw; guint64 version_bootloader_raw; GPtrArray *checksums; GPtrArray *children; guint32 flashes_left; guint32 battery_level; guint32 battery_threshold; guint32 install_duration; FwupdUpdateState update_state; gchar *update_error; gchar *update_message; gchar *update_image; FwupdStatus status; guint percentage; GPtrArray *releases; FwupdDevice *parent; /* noref */ } FwupdDevicePrivate; enum { PROP_0, PROP_VERSION, PROP_VERSION_FORMAT, PROP_FLAGS, PROP_REQUEST_FLAGS, PROP_PROTOCOL, PROP_STATUS, PROP_PERCENTAGE, PROP_PARENT, PROP_UPDATE_STATE, PROP_UPDATE_MESSAGE, PROP_UPDATE_ERROR, PROP_UPDATE_IMAGE, PROP_BATTERY_LEVEL, PROP_BATTERY_THRESHOLD, PROP_PROBLEMS, PROP_LAST }; G_DEFINE_TYPE_WITH_PRIVATE(FwupdDevice, fwupd_device, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_device_get_instance_private(o)) #define FWUPD_BATTERY_THRESHOLD_DEFAULT 10 /* % */ /** * fwupd_device_get_checksums: * @self: a #FwupdDevice * * Gets the device checksums. * * Returns: (element-type utf8) (transfer none): the checksums, which may be empty * * Since: 0.9.3 **/ GPtrArray * fwupd_device_get_checksums(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->checksums; } /** * fwupd_device_has_checksum: * @self: a #FwupdDevice * @checksum: (not nullable): the device checksum * * Finds out if the device has this specific checksum. * * Returns: %TRUE if the checksum name is found * * Since: 1.8.7 **/ gboolean fwupd_device_has_checksum(FwupdDevice *self, const gchar *checksum) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), FALSE); g_return_val_if_fail(checksum != NULL, FALSE); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum_tmp = g_ptr_array_index(priv->checksums, i); if (g_strcmp0(checksum, checksum_tmp) == 0) return TRUE; } return FALSE; } /** * fwupd_device_add_checksum: * @self: a #FwupdDevice * @checksum: (not nullable): the device checksum * * Adds a device checksum. * * Since: 0.9.3 **/ void fwupd_device_add_checksum(FwupdDevice *self, const gchar *checksum) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(checksum != NULL); if (fwupd_device_has_checksum(self, checksum)) return; g_ptr_array_add(priv->checksums, g_strdup(checksum)); } /** * fwupd_device_get_issues: * @self: a #FwupdDevice * * Gets the list of issues currently affecting this device. * * Returns: (element-type utf8) (transfer none): the issues, which may be empty * * Since: 1.7.6 **/ GPtrArray * fwupd_device_get_issues(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->issues; } /** * fwupd_device_add_issue: * @self: a #FwupdDevice * @issue: (not nullable): the update issue, e.g. `CVE-2019-12345` * * Adds an current issue to this device. * * Since: 1.7.6 **/ void fwupd_device_add_issue(FwupdDevice *self, const gchar *issue) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(issue != NULL); for (guint i = 0; i < priv->issues->len; i++) { const gchar *issue_tmp = g_ptr_array_index(priv->issues, i); if (g_strcmp0(issue_tmp, issue) == 0) return; } g_ptr_array_add(priv->issues, g_strdup(issue)); } /** * fwupd_device_get_children: * @self: a #FwupdDevice * * Gets the device children. These can only be assigned using fwupd_device_set_parent(). * * Returns: (element-type FwupdDevice) (transfer none): the children, which may be empty * * Since: 1.3.7 **/ GPtrArray * fwupd_device_get_children(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->children; } /** * fwupd_device_get_summary: * @self: a #FwupdDevice * * Gets the device summary. * * Returns: the device summary, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_summary(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->summary; } /** * fwupd_device_set_summary: * @self: a #FwupdDevice * @summary: (nullable): the device one line summary * * Sets the device summary. * * Since: 0.9.3 **/ void fwupd_device_set_summary(FwupdDevice *self, const gchar *summary) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->summary, summary) == 0) return; g_free(priv->summary); priv->summary = g_strdup(summary); } /** * fwupd_device_get_branch: * @self: a #FwupdDevice * * Gets the current device branch. * * Returns: the device branch, or %NULL if unset * * Since: 1.5.0 **/ const gchar * fwupd_device_get_branch(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->branch; } /** * fwupd_device_set_branch: * @self: a #FwupdDevice * @branch: (nullable): the device one line branch * * Sets the current device branch. * * Since: 1.5.0 **/ void fwupd_device_set_branch(FwupdDevice *self, const gchar *branch) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->branch, branch) == 0) return; g_free(priv->branch); priv->branch = g_strdup(branch); } /** * fwupd_device_get_serial: * @self: a #FwupdDevice * * Gets the serial number for the device. * * Returns: a string value, or %NULL if never set. * * Since: 1.1.2 **/ const gchar * fwupd_device_get_serial(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->serial; } /** * fwupd_device_set_serial: * @self: a #FwupdDevice * @serial: (nullable): the device serial number * * Sets the serial number for the device. * * Since: 1.1.2 **/ void fwupd_device_set_serial(FwupdDevice *self, const gchar *serial) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->serial, serial) == 0) return; g_free(priv->serial); priv->serial = g_strdup(serial); } /** * fwupd_device_get_id: * @self: a #FwupdDevice * * Gets the ID. * * Returns: the ID, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_id(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->id; } /** * fwupd_device_set_id: * @self: a #FwupdDevice * @id: (nullable): the device ID, e.g. `USB:foo` * * Sets the ID. * * Since: 0.9.3 **/ void fwupd_device_set_id(FwupdDevice *self, const gchar *id) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->id, id) == 0) return; g_free(priv->id); priv->id = g_strdup(id); } /** * fwupd_device_get_parent_id: * @self: a #FwupdDevice * * Gets the parent ID. * * Returns: the parent ID, or %NULL if unset * * Since: 1.0.8 **/ const gchar * fwupd_device_get_parent_id(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->parent_id; } /** * fwupd_device_set_parent_id: * @self: a #FwupdDevice * @parent_id: (nullable): the device ID, e.g. `USB:foo` * * Sets the parent ID. * * Since: 1.0.8 **/ void fwupd_device_set_parent_id(FwupdDevice *self, const gchar *parent_id) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->parent_id, parent_id) == 0) return; g_free(priv->parent_id); priv->parent_id = g_strdup(parent_id); } /** * fwupd_device_get_composite_id: * @self: a #FwupdDevice * * Gets the composite ID, falling back to the device ID if unset. * * The composite ID will be the same value for all parent, child and sibling * devices. * * Returns: (nullable): the composite ID * * Since: 1.6.0 **/ const gchar * fwupd_device_get_composite_id(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); if (priv->composite_id != NULL) return priv->composite_id; return priv->id; } /** * fwupd_device_set_composite_id: * @self: a #FwupdDevice * @composite_id: (nullable): a device ID * * Sets the composite ID, which is usually a SHA1 hash of a grandparent or * parent device. * * Since: 1.6.0 **/ void fwupd_device_set_composite_id(FwupdDevice *self, const gchar *composite_id) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->composite_id, composite_id) == 0) return; g_free(priv->composite_id); priv->composite_id = g_strdup(composite_id); } /** * fwupd_device_get_parent: * @self: a #FwupdDevice * * Gets the parent. * * Returns: (transfer none): the parent device, or %NULL if unset * * Since: 1.0.8 **/ FwupdDevice * fwupd_device_get_parent(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->parent; } /** * fwupd_device_get_root: * @self: a #FwupdDevice * * Gets the device root. * * Returns: (transfer none): the root device, or %NULL if unset * * Since: 1.7.4 **/ FwupdDevice * fwupd_device_get_root(FwupdDevice *self) { g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); while (1) { FwupdDevicePrivate *priv = GET_PRIVATE(self); if (priv->parent == NULL) break; self = priv->parent; } return self; } /** * fwupd_device_set_parent: * @self: a #FwupdDevice * @parent: (nullable): another #FwupdDevice * * Sets the parent. Only used internally. * * Since: 1.0.8 **/ void fwupd_device_set_parent(FwupdDevice *self, FwupdDevice *parent) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(self != parent); if (priv->parent != NULL) g_object_remove_weak_pointer(G_OBJECT(priv->parent), (gpointer *)&priv->parent); if (parent != NULL) g_object_add_weak_pointer(G_OBJECT(parent), (gpointer *)&priv->parent); priv->parent = parent; /* this is what goes over D-Bus */ fwupd_device_set_parent_id(self, parent != NULL ? fwupd_device_get_id(parent) : NULL); } static void fwupd_device_child_finalized_cb(gpointer data, GObject *where_the_object_was) { FwupdDevice *self = FWUPD_DEVICE(data); g_critical("FuDevice child %p was finalized while still having parent %s [%s]!", where_the_object_was, fwupd_device_get_name(self), fwupd_device_get_id(self)); } /** * fwupd_device_add_child: * @self: a #FwupdDevice * @child: (not nullable): Another #FwupdDevice * * Adds a child device. An child device is logically linked to the primary * device in some way. * * NOTE: You should never call this function from user code, it is for daemon * use only. Only use fwupd_device_set_parent() to set up a logical tree. * * Since: 1.5.1 **/ void fwupd_device_add_child(FwupdDevice *self, FwupdDevice *child) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(FWUPD_IS_DEVICE(child)); g_return_if_fail(self != child); /* add if the child does not already exist */ for (guint i = 0; i < priv->children->len; i++) { FwupdDevice *devtmp = g_ptr_array_index(priv->children, i); if (devtmp == child) return; } g_object_weak_ref(G_OBJECT(child), fwupd_device_child_finalized_cb, self); g_ptr_array_add(priv->children, g_object_ref(child)); } /** * fwupd_device_remove_child: * @self: a #FwupdDevice * @child: Another #FwupdDevice * * Removes a child device. * * NOTE: You should never call this function from user code, it is for daemon * use only. * * Since: 1.6.2 **/ void fwupd_device_remove_child(FwupdDevice *self, FwupdDevice *child) { FwupdDevicePrivate *priv = GET_PRIVATE(self); /* remove if the child exists */ for (guint i = 0; i < priv->children->len; i++) { FwupdDevice *child_tmp = g_ptr_array_index(priv->children, i); if (child_tmp == child) { g_object_weak_unref(G_OBJECT(child), fwupd_device_child_finalized_cb, self); g_ptr_array_remove_index(priv->children, i); return; } } } /** * fwupd_device_get_guids: * @self: a #FwupdDevice * * Gets the GUIDs. * * Returns: (element-type utf8) (transfer none): the GUIDs * * Since: 0.9.3 **/ GPtrArray * fwupd_device_get_guids(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->guids; } /** * fwupd_device_has_guid: * @self: a #FwupdDevice * @guid: (not nullable): the GUID, e.g. `2082b5e0-7a64-478a-b1b2-e3404fab6dad` * * Finds out if the device has this specific GUID. * * Returns: %TRUE if the GUID is found * * Since: 0.9.3 **/ gboolean fwupd_device_has_guid(FwupdDevice *self, const gchar *guid) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), FALSE); g_return_val_if_fail(guid != NULL, FALSE); for (guint i = 0; i < priv->guids->len; i++) { const gchar *guid_tmp = g_ptr_array_index(priv->guids, i); if (g_strcmp0(guid, guid_tmp) == 0) return TRUE; } return FALSE; } /** * fwupd_device_add_guid: * @self: a #FwupdDevice * @guid: the GUID, e.g. `2082b5e0-7a64-478a-b1b2-e3404fab6dad` * * Adds the GUID if it does not already exist. * * Since: 0.9.3 **/ void fwupd_device_add_guid(FwupdDevice *self, const gchar *guid) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(guid != NULL); if (fwupd_device_has_guid(self, guid)) return; g_ptr_array_add(priv->guids, g_strdup(guid)); } /** * fwupd_device_get_guid_default: * @self: a #FwupdDevice * * Gets the default GUID. * * Returns: the GUID, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_guid_default(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); if (priv->guids->len == 0) return NULL; return g_ptr_array_index(priv->guids, 0); } /** * fwupd_device_get_instance_ids: * @self: a #FwupdDevice * * Gets the instance IDs. * * Returns: (element-type utf8) (transfer none): the instance IDs * * Since: 1.2.5 **/ GPtrArray * fwupd_device_get_instance_ids(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->instance_ids; } /** * fwupd_device_has_instance_id: * @self: a #FwupdDevice * @instance_id: (not nullable): the instance ID, e.g. `PCI\VEN_10EC&DEV_525A` * * Finds out if the device has this specific instance ID. * * Returns: %TRUE if the instance ID is found * * Since: 1.2.5 **/ gboolean fwupd_device_has_instance_id(FwupdDevice *self, const gchar *instance_id) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), FALSE); g_return_val_if_fail(instance_id != NULL, FALSE); for (guint i = 0; i < priv->instance_ids->len; i++) { const gchar *instance_id_tmp = g_ptr_array_index(priv->instance_ids, i); if (g_strcmp0(instance_id, instance_id_tmp) == 0) return TRUE; } return FALSE; } /** * fwupd_device_add_instance_id: * @self: a #FwupdDevice * @instance_id: (not nullable): the instance ID, e.g. `PCI\VEN_10EC&DEV_525A` * * Adds the instance ID if it does not already exist. * * Since: 1.2.5 **/ void fwupd_device_add_instance_id(FwupdDevice *self, const gchar *instance_id) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(instance_id != NULL); if (fwupd_device_has_instance_id(self, instance_id)) return; g_ptr_array_add(priv->instance_ids, g_strdup(instance_id)); } /** * fwupd_device_get_icons: * @self: a #FwupdDevice * * Gets the icon names to use for the device. * * NOTE: Icons specified without a full path are stock icons and should * be loaded from the users icon theme. * * Returns: (element-type utf8) (transfer none): an array of icon names * * Since: 0.9.8 **/ GPtrArray * fwupd_device_get_icons(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->icons; } /** * fwupd_device_has_icon: * @self: a #FwupdDevice * @icon: the icon name, e.g. `input-mouse` or `/usr/share/icons/foo.png` * * Finds out if the device has this specific icon. * * Returns: %TRUE if the icon name is found * * Since: 1.6.2 **/ gboolean fwupd_device_has_icon(FwupdDevice *self, const gchar *icon) { FwupdDevicePrivate *priv = GET_PRIVATE(self); for (guint i = 0; i < priv->icons->len; i++) { const gchar *icon_tmp = g_ptr_array_index(priv->icons, i); if (g_strcmp0(icon, icon_tmp) == 0) return TRUE; } return FALSE; } /** * fwupd_device_add_icon: * @self: a #FwupdDevice * @icon: (not nullable): the icon name, e.g. `input-mouse` or `/usr/share/icons/foo.png` * * Adds the icon name if it does not already exist. * * Since: 0.9.8 **/ void fwupd_device_add_icon(FwupdDevice *self, const gchar *icon) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(icon != NULL); if (fwupd_device_has_icon(self, icon)) return; g_ptr_array_add(priv->icons, g_strdup(icon)); } /** * fwupd_device_get_name: * @self: a #FwupdDevice * * Gets the device name. * * Returns: the device name, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_name(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->name; } /** * fwupd_device_set_name: * @self: a #FwupdDevice * @name: (nullable): the device name, e.g. `ColorHug2` * * Sets the device name. * * Since: 0.9.3 **/ void fwupd_device_set_name(FwupdDevice *self, const gchar *name) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->name, name) == 0) return; g_free(priv->name); priv->name = g_strdup(name); } /** * fwupd_device_get_vendor: * @self: a #FwupdDevice * * Gets the device vendor. * * Returns: the device vendor, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_vendor(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->vendor; } /** * fwupd_device_set_vendor: * @self: a #FwupdDevice * @vendor: (nullable): the vendor * * Sets the device vendor. * * Since: 0.9.3 **/ void fwupd_device_set_vendor(FwupdDevice *self, const gchar *vendor) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->vendor, vendor) == 0) return; g_free(priv->vendor); priv->vendor = g_strdup(vendor); } /** * fwupd_device_get_vendor_id: * @self: a #FwupdDevice * * Gets the combined device vendor ID. * * Returns: the device vendor, e.g. 'USB:0x1234|PCI:0x5678', or %NULL if unset * * Since: 0.9.4 * * Deprecated: 1.5.5: Use fwupd_device_get_vendor_ids() instead. **/ const gchar * fwupd_device_get_vendor_id(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->vendor_id; } /** * fwupd_device_set_vendor_id: * @self: a #FwupdDevice * @vendor_id: (not nullable): the vendor ID, e.g. 'USB:0x1234' or 'USB:0x1234|PCI:0x5678' * * Sets the device vendor ID. * * Since: 0.9.4 * * Deprecated: 1.5.5: Use fwupd_device_add_vendor_id() instead. **/ void fwupd_device_set_vendor_id(FwupdDevice *self, const gchar *vendor_id) { g_auto(GStrv) vendor_ids = NULL; g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(vendor_id != NULL); /* add all */ vendor_ids = g_strsplit(vendor_id, "|", -1); for (guint i = 0; vendor_ids[i] != NULL; i++) fwupd_device_add_vendor_id(self, vendor_ids[i]); } /** * fwupd_device_get_vendor_ids: * @self: a #FwupdDevice * * Gets the device vendor ID. * * Returns: (element-type utf8) (transfer none): the device vendor ID * * Since: 1.5.5 **/ GPtrArray * fwupd_device_get_vendor_ids(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->vendor_ids; } /** * fwupd_device_has_vendor_id: * @self: a #FwupdDevice * @vendor_id: (not nullable): the vendor ID, e.g. 'USB:0x1234' * * Finds out if the device has this specific vendor ID. * * Returns: %TRUE if the vendor ID is found * * Since: 1.5.5 **/ gboolean fwupd_device_has_vendor_id(FwupdDevice *self, const gchar *vendor_id) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), FALSE); g_return_val_if_fail(vendor_id != NULL, FALSE); for (guint i = 0; i < priv->vendor_ids->len; i++) { const gchar *vendor_id_tmp = g_ptr_array_index(priv->vendor_ids, i); if (g_strcmp0(vendor_id, vendor_id_tmp) == 0) return TRUE; } return FALSE; } /** * fwupd_device_add_vendor_id: * @self: a #FwupdDevice * @vendor_id: (not nullable): the ID, e.g. 'USB:0x1234' * * Adds a device vendor ID. * * Since: 1.5.5 **/ void fwupd_device_add_vendor_id(FwupdDevice *self, const gchar *vendor_id) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_auto(GStrv) vendor_ids_tmp = NULL; g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(vendor_id != NULL); if (fwupd_device_has_vendor_id(self, vendor_id)) return; g_ptr_array_add(priv->vendor_ids, g_strdup(vendor_id)); /* build for compatibility */ vendor_ids_tmp = g_new0(gchar *, priv->vendor_ids->len + 1); for (guint i = 0; i < priv->vendor_ids->len; i++) { const gchar *vendor_id_tmp = g_ptr_array_index(priv->vendor_ids, i); vendor_ids_tmp[i] = g_strdup(vendor_id_tmp); } g_free(priv->vendor_id); priv->vendor_id = g_strjoinv("|", vendor_ids_tmp); } /** * fwupd_device_get_description: * @self: a #FwupdDevice * * Gets the device description in AppStream markup format. * * Returns: the device description, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_description(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->description; } /** * fwupd_device_set_description: * @self: a #FwupdDevice * @description: (nullable): the description in AppStream markup format * * Sets the device description. * * Since: 0.9.3 **/ void fwupd_device_set_description(FwupdDevice *self, const gchar *description) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->description, description) == 0) return; g_free(priv->description); priv->description = g_strdup(description); } /** * fwupd_device_get_version: * @self: a #FwupdDevice * * Gets the device version. * * Returns: the device version, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_version(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->version; } /** * fwupd_device_set_version: * @self: a #FwupdDevice * @version: (nullable): the device version, e.g. `1.2.3` * * Sets the device version. * * Since: 0.9.3 **/ void fwupd_device_set_version(FwupdDevice *self, const gchar *version) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->version, version) == 0) return; g_free(priv->version); priv->version = g_strdup(version); g_object_notify(G_OBJECT(self), "version"); } /** * fwupd_device_get_version_lowest: * @self: a #FwupdDevice * * Gets the lowest version of firmware the device will accept. * * Returns: the device version_lowest, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_version_lowest(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->version_lowest; } /** * fwupd_device_set_version_lowest: * @self: a #FwupdDevice * @version_lowest: (nullable): the version * * Sets the lowest version of firmware the device will accept. * * Since: 0.9.3 **/ void fwupd_device_set_version_lowest(FwupdDevice *self, const gchar *version_lowest) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->version_lowest, version_lowest) == 0) return; g_free(priv->version_lowest); priv->version_lowest = g_strdup(version_lowest); } /** * fwupd_device_get_version_lowest_raw: * @self: a #FwupdDevice * * Gets the lowest version of firmware the device will accept in raw format. * * Returns: integer version number, or %0 if unset * * Since: 1.4.0 **/ guint64 fwupd_device_get_version_lowest_raw(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), 0); return priv->version_lowest_raw; } /** * fwupd_device_set_version_lowest_raw: * @self: a #FwupdDevice * @version_lowest_raw: the raw hardware version * * Sets the raw lowest version number from the hardware before converted to a string. * * Since: 1.4.0 **/ void fwupd_device_set_version_lowest_raw(FwupdDevice *self, guint64 version_lowest_raw) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); priv->version_lowest_raw = version_lowest_raw; } /** * fwupd_device_get_version_bootloader: * @self: a #FwupdDevice * * Gets the version of the bootloader. * * Returns: the device version_bootloader, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_device_get_version_bootloader(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->version_bootloader; } /** * fwupd_device_set_version_bootloader: * @self: a #FwupdDevice * @version_bootloader: (nullable): the version * * Sets the bootloader version. * * Since: 0.9.3 **/ void fwupd_device_set_version_bootloader(FwupdDevice *self, const gchar *version_bootloader) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->version_bootloader, version_bootloader) == 0) return; g_free(priv->version_bootloader); priv->version_bootloader = g_strdup(version_bootloader); } /** * fwupd_device_get_version_bootloader_raw: * @self: a #FwupdDevice * * Gets the bootloader version of firmware the device will accept in raw format. * * Returns: integer version number, or %0 if unset * * Since: 1.4.0 **/ guint64 fwupd_device_get_version_bootloader_raw(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), 0); return priv->version_bootloader_raw; } /** * fwupd_device_set_version_bootloader_raw: * @self: a #FwupdDevice * @version_bootloader_raw: the raw hardware version * * Sets the raw bootloader version number from the hardware before converted to a string. * * Since: 1.4.0 **/ void fwupd_device_set_version_bootloader_raw(FwupdDevice *self, guint64 version_bootloader_raw) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); priv->version_bootloader_raw = version_bootloader_raw; } /** * fwupd_device_get_flashes_left: * @self: a #FwupdDevice * * Gets the number of flash cycles left on the device * * Returns: the flash cycles left, or %NULL if unset * * Since: 0.9.3 **/ guint32 fwupd_device_get_flashes_left(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), 0); return priv->flashes_left; } /** * fwupd_device_set_flashes_left: * @self: a #FwupdDevice * @flashes_left: the description * * Sets the number of flash cycles left on the device * * Since: 0.9.3 **/ void fwupd_device_set_flashes_left(FwupdDevice *self, guint32 flashes_left) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); priv->flashes_left = flashes_left; } /** * fwupd_device_get_battery_level: * @self: a #FwupdDevice * * Returns the battery level. * * Returns: value in percent * * Since: 1.8.1 **/ guint32 fwupd_device_get_battery_level(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), G_MAXUINT); return priv->battery_level; } /** * fwupd_device_set_battery_level: * @self: a #FwupdDevice * @battery_level: the percentage value * * Sets the battery level, or %FWUPD_BATTERY_LEVEL_INVALID. * * Setting this allows fwupd to show a warning if the device change is too low * to perform the update. * * Since: 1.8.1 **/ void fwupd_device_set_battery_level(FwupdDevice *self, guint32 battery_level) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(battery_level <= FWUPD_BATTERY_LEVEL_INVALID); if (priv->battery_level == battery_level) return; priv->battery_level = battery_level; g_object_notify(G_OBJECT(self), "battery-level"); } /** * fwupd_device_get_battery_threshold: * @self: a #FwupdDevice * * Returns the battery threshold under which a firmware update cannot be * performed. * * If fwupd_device_set_battery_threshold() has not been used, a default value is * used instead. * * Returns: value in percent * * Since: 1.8.1 **/ guint32 fwupd_device_get_battery_threshold(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), FWUPD_BATTERY_LEVEL_INVALID); /* default value */ if (priv->battery_threshold == FWUPD_BATTERY_LEVEL_INVALID) return FWUPD_BATTERY_THRESHOLD_DEFAULT; return priv->battery_threshold; } /** * fwupd_device_set_battery_threshold: * @self: a #FwupdDevice * @battery_threshold: the percentage value * * Sets the battery level, or %FWUPD_BATTERY_LEVEL_INVALID for the default. * * Setting this allows fwupd to show a warning if the device change is too low * to perform the update. * * Since: 1.8.1 **/ void fwupd_device_set_battery_threshold(FwupdDevice *self, guint32 battery_threshold) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(battery_threshold <= FWUPD_BATTERY_LEVEL_INVALID); if (priv->battery_threshold == battery_threshold) return; priv->battery_threshold = battery_threshold; g_object_notify(G_OBJECT(self), "battery-threshold"); } /** * fwupd_device_get_install_duration: * @self: a #FwupdDevice * * Gets the time estimate for firmware installation (in seconds) * * Returns: the estimated time to flash this device (or 0 if unset) * * Since: 1.1.3 **/ guint32 fwupd_device_get_install_duration(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), 0); return priv->install_duration; } /** * fwupd_device_set_install_duration: * @self: a #FwupdDevice * @duration: the amount of time * * Sets the time estimate for firmware installation (in seconds) * * Since: 1.1.3 **/ void fwupd_device_set_install_duration(FwupdDevice *self, guint32 duration) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); priv->install_duration = duration; } /** * fwupd_device_get_plugin: * @self: a #FwupdDevice * * Gets the plugin that created the device. * * Returns: the plugin name, or %NULL if unset * * Since: 1.0.0 **/ const gchar * fwupd_device_get_plugin(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->plugin; } /** * fwupd_device_set_plugin: * @self: a #FwupdDevice * @plugin: (nullable): the plugin name, e.g. `colorhug` * * Sets the plugin that created the device. * * Since: 1.0.0 **/ void fwupd_device_set_plugin(FwupdDevice *self, const gchar *plugin) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->plugin, plugin) == 0) return; g_free(priv->plugin); priv->plugin = g_strdup(plugin); } /** * fwupd_device_get_protocol: * @self: a #FwupdDevice * * Gets the protocol name that the device uses for updating. * * Returns: the protocol name, or %NULL if unset * * Since: 1.3.6 * * Deprecated: 1.5.8: Use fwupd_device_get_protocols() instead. **/ const gchar * fwupd_device_get_protocol(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->protocol; } /** * fwupd_device_set_protocol: * @self: a #FwupdDevice * @protocol: (not nullable): the protocol name, e.g. `com.hughski.colorhug` * * Sets the protocol name that is used to update the device. * * Since: 1.3.6 * * Deprecated: 1.5.8: Use fwupd_device_add_protocol() instead. **/ void fwupd_device_set_protocol(FwupdDevice *self, const gchar *protocol) { g_auto(GStrv) protocols = NULL; g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(protocol != NULL); /* add all */ protocols = g_strsplit(protocol, "|", -1); for (guint i = 0; protocols[i] != NULL; i++) fwupd_device_add_protocol(self, protocols[i]); } /** * fwupd_device_get_protocols: * @self: a #FwupdDevice * * Gets the device protocol names. * * Returns: (element-type utf8) (transfer none): the device protocol names * * Since: 1.5.8 **/ GPtrArray * fwupd_device_get_protocols(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->protocols; } /** * fwupd_device_has_protocol: * @self: a #FwupdDevice * @protocol: (not nullable): the protocol name, e.g. `com.hughski.colorhug` * * Finds out if the device has this specific protocol name. * * Returns: %TRUE if the protocol name is found * * Since: 1.5.8 **/ gboolean fwupd_device_has_protocol(FwupdDevice *self, const gchar *protocol) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), FALSE); g_return_val_if_fail(protocol != NULL, FALSE); for (guint i = 0; i < priv->protocols->len; i++) { const gchar *protocol_tmp = g_ptr_array_index(priv->protocols, i); if (g_strcmp0(protocol, protocol_tmp) == 0) return TRUE; } return FALSE; } /** * fwupd_device_add_protocol: * @self: a #FwupdDevice * @protocol: (not nullable): the protocol name, e.g. `com.hughski.colorhug` * * Adds a device protocol name. * * Since: 1.5.8 **/ void fwupd_device_add_protocol(FwupdDevice *self, const gchar *protocol) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_auto(GStrv) protocols_tmp = NULL; g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(protocol != NULL); if (fwupd_device_has_protocol(self, protocol)) return; g_ptr_array_add(priv->protocols, g_strdup(protocol)); /* build for compatibility */ protocols_tmp = g_new0(gchar *, priv->protocols->len + 1); for (guint i = 0; i < priv->protocols->len; i++) { const gchar *protocol_tmp = g_ptr_array_index(priv->protocols, i); protocols_tmp[i] = g_strdup(protocol_tmp); } g_free(priv->protocol); priv->protocol = g_strjoinv("|", protocols_tmp); } /** * fwupd_device_get_flags: * @self: a #FwupdDevice * * Gets device flags. * * Returns: device flags, or 0 if unset * * Since: 0.9.3 **/ guint64 fwupd_device_get_flags(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), 0); return priv->flags; } /** * fwupd_device_set_flags: * @self: a #FwupdDevice * @flags: device flags, e.g. %FWUPD_DEVICE_FLAG_REQUIRE_AC * * Sets device flags. * * Since: 0.9.3 **/ void fwupd_device_set_flags(FwupdDevice *self, guint64 flags) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); if (priv->flags == flags) return; priv->flags = flags; g_object_notify(G_OBJECT(self), "flags"); } /** * fwupd_device_add_flag: * @self: a #FwupdDevice * @flag: the #FwupdDeviceFlags * * Adds a specific device flag to the device. * * Since: 0.9.3 **/ void fwupd_device_add_flag(FwupdDevice *self, FwupdDeviceFlags flag) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); if (flag == 0) return; if ((priv->flags | flag) == priv->flags) return; priv->flags |= flag; g_object_notify(G_OBJECT(self), "flags"); } /** * fwupd_device_remove_flag: * @self: a #FwupdDevice * @flag: the #FwupdDeviceFlags * * Removes a specific device flag from the device. * * Since: 0.9.3 **/ void fwupd_device_remove_flag(FwupdDevice *self, FwupdDeviceFlags flag) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); if (flag == 0) return; if ((priv->flags & flag) == 0) return; priv->flags &= ~flag; g_object_notify(G_OBJECT(self), "flags"); } /** * fwupd_device_has_flag: * @self: a #FwupdDevice * @flag: the #FwupdDeviceFlags * * Finds if the device has a specific device flag. * * Returns: %TRUE if the flag is set * * Since: 0.9.3 **/ gboolean fwupd_device_has_flag(FwupdDevice *self, FwupdDeviceFlags flag) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), FALSE); return (priv->flags & flag) > 0; } /** * fwupd_device_get_problems: * @self: a #FwupdDevice * * Gets device problems. * * Returns: device problems, or 0 if unset * * Since: 1.8.1 **/ guint64 fwupd_device_get_problems(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), 0); return priv->problems; } /** * fwupd_device_set_problems: * @self: a #FwupdDevice * @problems: device problems, e.g. %FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW * * Sets device problems. * * Since: 1.8.1 **/ void fwupd_device_set_problems(FwupdDevice *self, guint64 problems) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); if (priv->problems == problems) return; priv->problems = problems; g_object_notify(G_OBJECT(self), "problems"); } /** * fwupd_device_add_problem: * @self: a #FwupdDevice * @problem: the #FwupdDeviceProblem, e.g. #FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW * * Adds a specific device problem kind to the device. * * Since: 1.8.1 **/ void fwupd_device_add_problem(FwupdDevice *self, FwupdDeviceProblem problem) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); if (problem == FWUPD_DEVICE_PROBLEM_NONE) return; if (fwupd_device_has_problem(self, problem)) return; priv->problems |= problem; g_object_notify(G_OBJECT(self), "problems"); } /** * fwupd_device_remove_problem: * @self: a #FwupdDevice * @problem: the #FwupdDeviceProblem, e.g. #FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW * * Removes a specific device problem kind from the device. * * Since: 1.8.1 **/ void fwupd_device_remove_problem(FwupdDevice *self, FwupdDeviceProblem problem) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); if (problem == FWUPD_DEVICE_PROBLEM_NONE) return; if (!fwupd_device_has_problem(self, problem)) return; priv->problems &= ~problem; g_object_notify(G_OBJECT(self), "problems"); } /** * fwupd_device_has_problem: * @self: a #FwupdDevice * @problem: the #FwupdDeviceProblem * * Finds if the device has a specific device problem kind. * * Returns: %TRUE if the problem is set * * Since: 1.8.1 **/ gboolean fwupd_device_has_problem(FwupdDevice *self, FwupdDeviceProblem problem) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), FALSE); return (priv->problems & problem) > 0; } /** * fwupd_device_get_request_flags: * @self: a #FwupdDevice * * Gets device request flags. * * Returns: device request flags, or 0 if unset * * Since: 1.9.10 **/ guint64 fwupd_device_get_request_flags(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), 0); return priv->request_flags; } /** * fwupd_device_set_request_flags: * @self: a #FwupdDevice * @request_flags: device request flags, e.g. %FWUPD_DEVICE_REQUEST_FLAG_REQUIRE_AC * * Sets device request flags. * * Since: 1.9.10 **/ void fwupd_device_set_request_flags(FwupdDevice *self, guint64 request_flags) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); if (priv->request_flags == request_flags) return; priv->request_flags = request_flags; g_object_notify(G_OBJECT(self), "request-flags"); } /** * fwupd_device_add_request_flag: * @self: a #FwupdDevice * @request_flag: the #FwupdRequestFlags * * Adds a specific device request flag to the device. * * Since: 1.9.10 **/ void fwupd_device_add_request_flag(FwupdDevice *self, FwupdRequestFlags request_flag) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); if (request_flag == 0) return; if ((priv->request_flags | request_flag) == priv->request_flags) return; priv->request_flags |= request_flag; g_object_notify(G_OBJECT(self), "request-flags"); } /** * fwupd_device_remove_request_flag: * @self: a #FwupdDevice * @request_flag: the #FwupdRequestFlags * * Removes a specific device request flag from the device. * * Since: 1.9.10 **/ void fwupd_device_remove_request_flag(FwupdDevice *self, FwupdRequestFlags request_flag) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); if (request_flag == 0) return; if ((priv->request_flags & request_flag) == 0) return; priv->request_flags &= ~request_flag; g_object_notify(G_OBJECT(self), "request-flags"); } /** * fwupd_device_has_request_flag: * @self: a #FwupdDevice * @request_flag: the #FwupdRequestFlags * * Finds if the device has a specific device request flag. * * Returns: %TRUE if the request_flag is set * * Since: 1.9.10 **/ gboolean fwupd_device_has_request_flag(FwupdDevice *self, FwupdRequestFlags request_flag) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), FALSE); return (priv->request_flags & request_flag) > 0; } /** * fwupd_device_get_created: * @self: a #FwupdDevice * * Gets when the device was created. * * Returns: the UNIX time, or 0 if unset * * Since: 0.9.3 **/ guint64 fwupd_device_get_created(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), 0); return priv->created; } /** * fwupd_device_set_created: * @self: a #FwupdDevice * @created: the UNIX time * * Sets when the device was created. * * Since: 0.9.3 **/ void fwupd_device_set_created(FwupdDevice *self, guint64 created) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); priv->created = created; } /** * fwupd_device_get_modified: * @self: a #FwupdDevice * * Gets when the device was modified. * * Returns: the UNIX time, or 0 if unset * * Since: 0.9.3 **/ guint64 fwupd_device_get_modified(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), 0); return priv->modified; } /** * fwupd_device_set_modified: * @self: a #FwupdDevice * @modified: the UNIX time * * Sets when the device was modified. * * Since: 0.9.3 **/ void fwupd_device_set_modified(FwupdDevice *self, guint64 modified) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); priv->modified = modified; } /** * fwupd_device_incorporate: * @self: a #FwupdDevice * @donor: Another #FwupdDevice * * Copy all properties from the donor object if they have not already been set. * * Since: 1.1.0 **/ void fwupd_device_incorporate(FwupdDevice *self, FwupdDevice *donor) { FwupdDevicePrivate *priv = GET_PRIVATE(self); FwupdDevicePrivate *priv_donor = GET_PRIVATE(donor); g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(FWUPD_IS_DEVICE(donor)); fwupd_device_add_flag(self, priv_donor->flags); fwupd_device_add_request_flag(self, priv_donor->request_flags); fwupd_device_add_problem(self, priv_donor->problems); if (priv->created == 0) fwupd_device_set_created(self, priv_donor->created); if (priv->modified == 0) fwupd_device_set_modified(self, priv_donor->modified); if (priv->version_build_date == 0) fwupd_device_set_version_build_date(self, priv_donor->version_build_date); if (priv->flashes_left == 0) fwupd_device_set_flashes_left(self, priv_donor->flashes_left); if (priv->battery_level == FWUPD_BATTERY_LEVEL_INVALID) fwupd_device_set_battery_level(self, priv_donor->battery_level); if (priv->battery_threshold == FWUPD_BATTERY_LEVEL_INVALID) fwupd_device_set_battery_threshold(self, priv_donor->battery_threshold); if (priv->install_duration == 0) fwupd_device_set_install_duration(self, priv_donor->install_duration); if (priv->update_state == FWUPD_UPDATE_STATE_UNKNOWN) fwupd_device_set_update_state(self, priv_donor->update_state); if (priv->description == NULL) fwupd_device_set_description(self, priv_donor->description); if (priv->id == NULL) fwupd_device_set_id(self, priv_donor->id); if (priv->parent_id == NULL) fwupd_device_set_parent_id(self, priv_donor->parent_id); if (priv->composite_id == NULL) fwupd_device_set_composite_id(self, priv_donor->composite_id); if (priv->name == NULL) fwupd_device_set_name(self, priv_donor->name); if (priv->serial == NULL) fwupd_device_set_serial(self, priv_donor->serial); if (priv->summary == NULL) fwupd_device_set_summary(self, priv_donor->summary); if (priv->branch == NULL) fwupd_device_set_branch(self, priv_donor->branch); if (priv->vendor == NULL) fwupd_device_set_vendor(self, priv_donor->vendor); for (guint i = 0; i < priv_donor->vendor_ids->len; i++) { const gchar *tmp = g_ptr_array_index(priv_donor->vendor_ids, i); fwupd_device_add_vendor_id(self, tmp); } if (priv->plugin == NULL) fwupd_device_set_plugin(self, priv_donor->plugin); for (guint i = 0; i < priv_donor->protocols->len; i++) { const gchar *tmp = g_ptr_array_index(priv_donor->protocols, i); fwupd_device_add_protocol(self, tmp); } if (priv->update_error == NULL) fwupd_device_set_update_error(self, priv_donor->update_error); if (priv->update_message == NULL) fwupd_device_set_update_message(self, priv_donor->update_message); if (priv->update_image == NULL) fwupd_device_set_update_image(self, priv_donor->update_image); if (priv->version == NULL) fwupd_device_set_version(self, priv_donor->version); if (priv->version_lowest == NULL) fwupd_device_set_version_lowest(self, priv_donor->version_lowest); if (priv->version_bootloader == NULL) fwupd_device_set_version_bootloader(self, priv_donor->version_bootloader); if (priv->version_format == FWUPD_VERSION_FORMAT_UNKNOWN) fwupd_device_set_version_format(self, priv_donor->version_format); if (priv->version_raw == 0) fwupd_device_set_version_raw(self, priv_donor->version_raw); if (priv->version_lowest_raw == 0) fwupd_device_set_version_lowest_raw(self, priv_donor->version_lowest_raw); if (priv->version_bootloader_raw == 0) fwupd_device_set_version_bootloader_raw(self, priv_donor->version_bootloader_raw); for (guint i = 0; i < priv_donor->guids->len; i++) { const gchar *tmp = g_ptr_array_index(priv_donor->guids, i); fwupd_device_add_guid(self, tmp); } for (guint i = 0; i < priv_donor->instance_ids->len; i++) { const gchar *tmp = g_ptr_array_index(priv_donor->instance_ids, i); fwupd_device_add_instance_id(self, tmp); } for (guint i = 0; i < priv_donor->icons->len; i++) { const gchar *tmp = g_ptr_array_index(priv_donor->icons, i); fwupd_device_add_icon(self, tmp); } for (guint i = 0; i < priv_donor->checksums->len; i++) { const gchar *tmp = g_ptr_array_index(priv_donor->checksums, i); fwupd_device_add_checksum(self, tmp); } for (guint i = 0; i < priv_donor->releases->len; i++) { FwupdRelease *tmp = g_ptr_array_index(priv_donor->releases, i); fwupd_device_add_release(self, tmp); } } /** * fwupd_device_to_variant_full: * @self: a #FwupdDevice * @flags: device flags * * Serialize the device data. * Optionally provides additional data based upon flags * * Returns: the serialized data, or %NULL for error * * Since: 1.1.2 **/ GVariant * fwupd_device_to_variant_full(FwupdDevice *self, FwupdDeviceFlags flags) { FwupdDevicePrivate *priv = GET_PRIVATE(self); GVariantBuilder builder; g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); /* create an array with all the metadata in */ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); if (priv->id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_DEVICE_ID, g_variant_new_string(priv->id)); } if (priv->parent_id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_PARENT_DEVICE_ID, g_variant_new_string(priv->parent_id)); } if (priv->composite_id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_COMPOSITE_ID, g_variant_new_string(priv->composite_id)); } if (priv->guids->len > 0) { const gchar *const *tmp = (const gchar *const *)priv->guids->pdata; g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_GUID, g_variant_new_strv(tmp, priv->guids->len)); } if (priv->icons->len > 0) { const gchar *const *tmp = (const gchar *const *)priv->icons->pdata; g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_ICON, g_variant_new_strv(tmp, priv->icons->len)); } if (priv->name != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_NAME, g_variant_new_string(priv->name)); } if (priv->vendor != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VENDOR, g_variant_new_string(priv->vendor)); } if (priv->vendor_ids->len > 0) { g_autoptr(GString) str = g_string_new(NULL); for (guint i = 0; i < priv->vendor_ids->len; i++) { const gchar *tmp = g_ptr_array_index(priv->vendor_ids, i); g_string_append_printf(str, "%s|", tmp); } if (str->len > 0) g_string_truncate(str, str->len - 1); g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VENDOR_ID, g_variant_new_string(str->str)); } if (priv->flags > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_FLAGS, g_variant_new_uint64(priv->flags)); } if (priv->request_flags > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_REQUEST_FLAGS, g_variant_new_uint64(priv->request_flags)); } if (priv->problems > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_PROBLEMS, g_variant_new_uint64(priv->problems)); } if (priv->created > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_CREATED, g_variant_new_uint64(priv->created)); } if (priv->modified > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_MODIFIED, g_variant_new_uint64(priv->modified)); } if (priv->version_build_date > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VERSION_BUILD_DATE, g_variant_new_uint64(priv->version_build_date)); } if (priv->description != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_DESCRIPTION, g_variant_new_string(priv->description)); } if (priv->summary != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_SUMMARY, g_variant_new_string(priv->summary)); } if (priv->branch != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BRANCH, g_variant_new_string(priv->branch)); } if (priv->checksums->len > 0) { g_autoptr(GString) str = g_string_new(""); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum = g_ptr_array_index(priv->checksums, i); g_string_append_printf(str, "%s,", checksum); } if (str->len > 0) g_string_truncate(str, str->len - 1); g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_CHECKSUM, g_variant_new_string(str->str)); } if (priv->plugin != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_PLUGIN, g_variant_new_string(priv->plugin)); } if (priv->protocols->len > 0) { g_autoptr(GString) str = g_string_new(NULL); for (guint i = 0; i < priv->protocols->len; i++) { const gchar *tmp = g_ptr_array_index(priv->protocols, i); g_string_append_printf(str, "%s|", tmp); } if (str->len > 0) g_string_truncate(str, str->len - 1); g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_PROTOCOL, g_variant_new_string(str->str)); } if (priv->issues->len > 0) { g_autofree const gchar **strv = g_new0(const gchar *, priv->issues->len + 1); for (guint i = 0; i < priv->issues->len; i++) strv[i] = (const gchar *)g_ptr_array_index(priv->issues, i); g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_ISSUES, g_variant_new_strv(strv, -1)); } if (priv->version != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VERSION, g_variant_new_string(priv->version)); } if (priv->version_lowest != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VERSION_LOWEST, g_variant_new_string(priv->version_lowest)); } if (priv->version_bootloader != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VERSION_BOOTLOADER, g_variant_new_string(priv->version_bootloader)); } if (priv->version_raw > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VERSION_RAW, g_variant_new_uint64(priv->version_raw)); } if (priv->version_lowest_raw > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VERSION_LOWEST_RAW, g_variant_new_uint64(priv->version_lowest_raw)); } if (priv->version_bootloader_raw > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VERSION_BOOTLOADER_RAW, g_variant_new_uint64(priv->version_raw)); } if (priv->flashes_left > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_FLASHES_LEFT, g_variant_new_uint32(priv->flashes_left)); } if (priv->battery_level != FWUPD_BATTERY_LEVEL_INVALID) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BATTERY_LEVEL, g_variant_new_uint32(priv->battery_level)); } if (priv->battery_threshold != FWUPD_BATTERY_LEVEL_INVALID) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BATTERY_THRESHOLD, g_variant_new_uint32(priv->battery_threshold)); } if (priv->install_duration > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_INSTALL_DURATION, g_variant_new_uint32(priv->install_duration)); } if (priv->update_error != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_UPDATE_ERROR, g_variant_new_string(priv->update_error)); } if (priv->update_message != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_UPDATE_MESSAGE, g_variant_new_string(priv->update_message)); } if (priv->update_image != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_UPDATE_IMAGE, g_variant_new_string(priv->update_image)); } if (priv->update_state != FWUPD_UPDATE_STATE_UNKNOWN) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_UPDATE_STATE, g_variant_new_uint32(priv->update_state)); } if (priv->status != FWUPD_STATUS_UNKNOWN) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_STATUS, g_variant_new_uint32(priv->status)); } if (priv->percentage != 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_PERCENTAGE, g_variant_new_uint32(priv->percentage)); } if (priv->version_format != FWUPD_VERSION_FORMAT_UNKNOWN) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VERSION_FORMAT, g_variant_new_uint32(priv->version_format)); } if (flags & FWUPD_DEVICE_FLAG_TRUSTED) { if (priv->serial != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_SERIAL, g_variant_new_string(priv->serial)); } if (priv->instance_ids->len > 0) { const gchar *const *tmp = (const gchar *const *)priv->instance_ids->pdata; g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_INSTANCE_IDS, g_variant_new_strv(tmp, priv->instance_ids->len)); } } /* create an array with all the metadata in */ if (priv->releases->len > 0) { g_autofree GVariant **children = NULL; children = g_new0(GVariant *, priv->releases->len); for (guint i = 0; i < priv->releases->len; i++) { FwupdRelease *release = g_ptr_array_index(priv->releases, i); children[i] = fwupd_release_to_variant(release); } g_variant_builder_add( &builder, "{sv}", FWUPD_RESULT_KEY_RELEASE, g_variant_new_array(G_VARIANT_TYPE("a{sv}"), children, priv->releases->len)); } return g_variant_new("a{sv}", &builder); } /** * fwupd_device_to_variant: * @self: a #FwupdDevice * * Serialize the device data omitting sensitive fields * * Returns: the serialized data, or %NULL for error * * Since: 1.0.0 **/ GVariant * fwupd_device_to_variant(FwupdDevice *self) { return fwupd_device_to_variant_full(self, FWUPD_DEVICE_FLAG_NONE); } static void fwupd_device_from_key_value(FwupdDevice *self, const gchar *key, GVariant *value) { if (g_strcmp0(key, FWUPD_RESULT_KEY_RELEASE) == 0) { GVariantIter iter; GVariant *child; g_variant_iter_init(&iter, value); while ((child = g_variant_iter_next_value(&iter))) { g_autoptr(FwupdRelease) release = fwupd_release_from_variant(child); if (release != NULL) fwupd_device_add_release(self, release); g_variant_unref(child); } return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_DEVICE_ID) == 0) { fwupd_device_set_id(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_PARENT_DEVICE_ID) == 0) { fwupd_device_set_parent_id(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_COMPOSITE_ID) == 0) { fwupd_device_set_composite_id(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_FLAGS) == 0) { fwupd_device_set_flags(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_PROBLEMS) == 0) { fwupd_device_set_problems(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_REQUEST_FLAGS) == 0) { fwupd_device_set_request_flags(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_CREATED) == 0) { fwupd_device_set_created(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_MODIFIED) == 0) { fwupd_device_set_modified(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VERSION_BUILD_DATE) == 0) { fwupd_device_set_version_build_date(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_GUID) == 0) { g_autofree const gchar **guids = g_variant_get_strv(value, NULL); for (guint i = 0; guids != NULL && guids[i] != NULL; i++) fwupd_device_add_guid(self, guids[i]); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_INSTANCE_IDS) == 0) { g_autofree const gchar **instance_ids = g_variant_get_strv(value, NULL); for (guint i = 0; instance_ids != NULL && instance_ids[i] != NULL; i++) fwupd_device_add_instance_id(self, instance_ids[i]); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_ICON) == 0) { g_autofree const gchar **icons = g_variant_get_strv(value, NULL); for (guint i = 0; icons != NULL && icons[i] != NULL; i++) fwupd_device_add_icon(self, icons[i]); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_NAME) == 0) { fwupd_device_set_name(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VENDOR) == 0) { fwupd_device_set_vendor(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VENDOR_ID) == 0) { g_auto(GStrv) vendor_ids = NULL; vendor_ids = g_strsplit(g_variant_get_string(value, NULL), "|", -1); for (guint i = 0; vendor_ids[i] != NULL; i++) fwupd_device_add_vendor_id(self, vendor_ids[i]); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_SERIAL) == 0) { fwupd_device_set_serial(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_SUMMARY) == 0) { fwupd_device_set_summary(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BRANCH) == 0) { fwupd_device_set_branch(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_DESCRIPTION) == 0) { fwupd_device_set_description(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_CHECKSUM) == 0) { const gchar *checksums = g_variant_get_string(value, NULL); if (checksums != NULL) { g_auto(GStrv) split = g_strsplit(checksums, ",", -1); for (guint i = 0; split[i] != NULL; i++) fwupd_device_add_checksum(self, split[i]); } return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_PLUGIN) == 0) { fwupd_device_set_plugin(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_PROTOCOL) == 0) { g_auto(GStrv) protocols = NULL; protocols = g_strsplit(g_variant_get_string(value, NULL), "|", -1); for (guint i = 0; protocols[i] != NULL; i++) fwupd_device_add_protocol(self, protocols[i]); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_ISSUES) == 0) { g_autofree const gchar **strv = g_variant_get_strv(value, NULL); for (guint i = 0; strv[i] != NULL; i++) fwupd_device_add_issue(self, strv[i]); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VERSION) == 0) { fwupd_device_set_version(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VERSION_LOWEST) == 0) { fwupd_device_set_version_lowest(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VERSION_BOOTLOADER) == 0) { fwupd_device_set_version_bootloader(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_FLASHES_LEFT) == 0) { fwupd_device_set_flashes_left(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BATTERY_LEVEL) == 0) { fwupd_device_set_battery_level(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BATTERY_THRESHOLD) == 0) { fwupd_device_set_battery_threshold(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_INSTALL_DURATION) == 0) { fwupd_device_set_install_duration(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_UPDATE_ERROR) == 0) { fwupd_device_set_update_error(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_UPDATE_MESSAGE) == 0) { fwupd_device_set_update_message(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_UPDATE_IMAGE) == 0) { fwupd_device_set_update_image(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_UPDATE_STATE) == 0) { fwupd_device_set_update_state(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_STATUS) == 0) { fwupd_device_set_status(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_PERCENTAGE) == 0) { fwupd_device_set_percentage(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VERSION_FORMAT) == 0) { fwupd_device_set_version_format(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VERSION_RAW) == 0) { fwupd_device_set_version_raw(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VERSION_LOWEST_RAW) == 0) { fwupd_device_set_version_lowest_raw(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VERSION_BOOTLOADER_RAW) == 0) { fwupd_device_set_version_bootloader_raw(self, g_variant_get_uint64(value)); return; } } static void fwupd_pad_kv_dfl(GString *str, const gchar *key, guint64 device_flags) { g_autoptr(GString) tmp = g_string_new(""); for (guint i = 0; i < 64; i++) { if ((device_flags & ((guint64)1 << i)) == 0) continue; g_string_append_printf(tmp, "%s|", fwupd_device_flag_to_string((guint64)1 << i)); } if (tmp->len == 0) { g_string_append(tmp, fwupd_device_flag_to_string(0)); } else { g_string_truncate(tmp, tmp->len - 1); } fwupd_pad_kv_str(str, key, tmp->str); } static void fwupd_pad_kv_drfl(GString *str, const gchar *key, guint64 request_flags) { g_autoptr(GString) tmp = g_string_new(""); for (guint i = 0; i < 64; i++) { if ((request_flags & ((guint64)1 << i)) == 0) continue; g_string_append_printf(tmp, "%s|", fwupd_request_flag_to_string((guint64)1 << i)); } if (tmp->len == 0) { g_string_append(tmp, fwupd_request_flag_to_string(0)); } else { g_string_truncate(tmp, tmp->len - 1); } fwupd_pad_kv_str(str, key, tmp->str); } static void fwupd_device_pad_kv_problems(GString *str, const gchar *key, guint64 device_problems) { g_autoptr(GString) tmp = g_string_new(""); for (guint i = 0; i < 64; i++) { if ((device_problems & ((guint64)1 << i)) == 0) continue; g_string_append_printf(tmp, "%s|", fwupd_device_problem_to_string((guint64)1 << i)); } if (tmp->len == 0) { g_string_append(tmp, fwupd_device_problem_to_string(0)); } else { g_string_truncate(tmp, tmp->len - 1); } fwupd_pad_kv_str(str, key, tmp->str); } /** * fwupd_device_get_update_state: * @self: a #FwupdDevice * * Gets the update state. * * Returns: the update state, or %FWUPD_UPDATE_STATE_UNKNOWN if unset * * Since: 0.9.8 **/ FwupdUpdateState fwupd_device_get_update_state(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), FWUPD_UPDATE_STATE_UNKNOWN); return priv->update_state; } /** * fwupd_device_set_update_state: * @self: a #FwupdDevice * @update_state: the state, e.g. %FWUPD_UPDATE_STATE_PENDING * * Sets the update state. * * Since: 0.9.8 **/ void fwupd_device_set_update_state(FwupdDevice *self, FwupdUpdateState update_state) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); if (priv->update_state == update_state) return; priv->update_state = update_state; g_object_notify(G_OBJECT(self), "update-state"); } /** * fwupd_device_get_version_format: * @self: a #FwupdDevice * * Gets the version format. * * Returns: the version format, or %FWUPD_VERSION_FORMAT_UNKNOWN if unset * * Since: 1.2.9 **/ FwupdVersionFormat fwupd_device_get_version_format(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), FWUPD_VERSION_FORMAT_UNKNOWN); return priv->version_format; } /** * fwupd_device_set_version_format: * @self: a #FwupdDevice * @version_format: the version format, e.g. %FWUPD_VERSION_FORMAT_NUMBER * * Sets the version format. * * Since: 1.2.9 **/ void fwupd_device_set_version_format(FwupdDevice *self, FwupdVersionFormat version_format) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); priv->version_format = version_format; } /** * fwupd_device_get_version_raw: * @self: a #FwupdDevice * * Gets the raw version number from the hardware before converted to a string. * * Returns: the hardware version, or 0 if unset * * Since: 1.3.6 **/ guint64 fwupd_device_get_version_raw(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), 0); return priv->version_raw; } /** * fwupd_device_set_version_raw: * @self: a #FwupdDevice * @version_raw: the raw hardware version * * Sets the raw version number from the hardware before converted to a string. * * Since: 1.3.6 **/ void fwupd_device_set_version_raw(FwupdDevice *self, guint64 version_raw) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); priv->version_raw = version_raw; } /** * fwupd_device_get_version_build_date: * @self: a #FwupdDevice * * Gets the date when the firmware was built. * * Returns: the UNIX time, or 0 if unset * * Since: 1.6.2 **/ guint64 fwupd_device_get_version_build_date(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), 0); return priv->version_build_date; } /** * fwupd_device_set_version_build_date: * @self: a #FwupdDevice * @version_build_date: the UNIX time * * Sets the date when the firmware was built. * * Since: 1.6.2 **/ void fwupd_device_set_version_build_date(FwupdDevice *self, guint64 version_build_date) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); priv->version_build_date = version_build_date; } /** * fwupd_device_get_update_message: * @self: a #FwupdDevice * * Gets the update message string. * * Returns: the update message string, or %NULL if unset * * Since: 1.2.4 **/ const gchar * fwupd_device_get_update_message(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->update_message; } /** * fwupd_device_set_update_message: * @self: a #FwupdDevice * @update_message: (nullable): the update message string * * Sets the update message string. * * Since: 1.2.4 **/ void fwupd_device_set_update_message(FwupdDevice *self, const gchar *update_message) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->update_message, update_message) == 0) return; g_free(priv->update_message); priv->update_message = g_strdup(update_message); g_object_notify(G_OBJECT(self), "update-message"); } /** * fwupd_device_get_update_image: * @self: a #FwupdDevice * * Gets the update image URL. * * Returns: the update image URL, or %NULL if unset * * Since: 1.4.5 **/ const gchar * fwupd_device_get_update_image(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->update_image; } /** * fwupd_device_set_update_image: * @self: a #FwupdDevice * @update_image: (nullable): the update image URL * * Sets the update image URL. * * Since: 1.4.5 **/ void fwupd_device_set_update_image(FwupdDevice *self, const gchar *update_image) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->update_image, update_image) == 0) return; g_free(priv->update_image); priv->update_image = g_strdup(update_image); g_object_notify(G_OBJECT(self), "update-image"); } /** * fwupd_device_get_update_error: * @self: a #FwupdDevice * * Gets the update error string. * * Returns: the update error string, or %NULL if unset * * Since: 0.9.8 **/ const gchar * fwupd_device_get_update_error(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->update_error; } /** * fwupd_device_set_update_error: * @self: a #FwupdDevice * @update_error: (nullable): the update error string * * Sets the update error string. * * Since: 0.9.8 **/ void fwupd_device_set_update_error(FwupdDevice *self, const gchar *update_error) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->update_error, update_error) == 0) return; g_free(priv->update_error); priv->update_error = g_strdup(update_error); g_object_notify(G_OBJECT(self), "update-error"); } /** * fwupd_device_get_release_default: * @self: a #FwupdDevice * * Gets the default release for this device. * * Returns: (transfer none): the #FwupdRelease, or %NULL if not set * * Since: 0.9.8 **/ FwupdRelease * fwupd_device_get_release_default(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); if (priv->releases->len == 0) return NULL; return FWUPD_RELEASE(g_ptr_array_index(priv->releases, 0)); } /** * fwupd_device_get_releases: * @self: a #FwupdDevice * * Gets all the releases for this device. * * Returns: (transfer none) (element-type FwupdRelease): array of releases * * Since: 0.9.8 **/ GPtrArray * fwupd_device_get_releases(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); return priv->releases; } /** * fwupd_device_add_release: * @self: a #FwupdDevice * @release: (not nullable): a release * * Adds a release for this device. * * Since: 0.9.8 **/ void fwupd_device_add_release(FwupdDevice *self, FwupdRelease *release) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(FWUPD_IS_RELEASE(release)); g_ptr_array_add(priv->releases, g_object_ref(release)); } /** * fwupd_device_get_status: * @self: a #FwupdDevice * * Returns what the device is currently doing. * * Returns: the status value, e.g. %FWUPD_STATUS_DEVICE_WRITE * * Since: 1.4.0 **/ FwupdStatus fwupd_device_get_status(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), 0); return priv->status; } /** * fwupd_device_set_status: * @self: a #FwupdDevice * @status: the status value, e.g. %FWUPD_STATUS_DEVICE_WRITE * * Sets what the device is currently doing. * * Since: 1.4.0 **/ void fwupd_device_set_status(FwupdDevice *self, FwupdStatus status) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); if (priv->status == status) return; priv->status = status; g_object_notify(G_OBJECT(self), "status"); } /** * fwupd_device_get_percentage: * @self: a #FwupdDevice * * Returns the percentage completion of the device. * * Returns: the percentage value * * Since: 1.8.11 **/ guint fwupd_device_get_percentage(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_DEVICE(self), 0); return priv->percentage; } /** * fwupd_device_set_percentage: * @self: a #FwupdDevice * @percentage: the percentage value * * Sets the percentage completion of the device. * * Since: 1.8.11 **/ void fwupd_device_set_percentage(FwupdDevice *self, guint percentage) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); if (priv->percentage == percentage) return; priv->percentage = percentage; g_object_notify(G_OBJECT(self), "percentage"); } static void fwupd_pad_kv_ups(GString *str, const gchar *key, FwupdUpdateState value) { if (value == FWUPD_UPDATE_STATE_UNKNOWN) return; fwupd_pad_kv_str(str, key, fwupd_update_state_to_string(value)); } /** * fwupd_device_to_json_full: * @self: a #FwupdDevice * @builder: (not nullable): a JSON builder * @flags: device flags * * Adds a fwupd device to a JSON builder * Optionally provides additional data based upon flags * * Since: 1.8.2 **/ void fwupd_device_to_json_full(FwupdDevice *self, JsonBuilder *builder, FwupdDeviceFlags flags) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_DEVICE(self)); g_return_if_fail(builder != NULL); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_NAME, priv->name); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_DEVICE_ID, priv->id); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_PARENT_DEVICE_ID, priv->parent_id); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_COMPOSITE_ID, priv->composite_id); if (flags & FWUPD_DEVICE_FLAG_TRUSTED && priv->instance_ids->len > 0) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_INSTANCE_IDS); json_builder_begin_array(builder); for (guint i = 0; i < priv->instance_ids->len; i++) { const gchar *instance_id = g_ptr_array_index(priv->instance_ids, i); json_builder_add_string_value(builder, instance_id); } json_builder_end_array(builder); } if (priv->guids->len > 0) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_GUID); json_builder_begin_array(builder); for (guint i = 0; i < priv->guids->len; i++) { const gchar *guid = g_ptr_array_index(priv->guids, i); json_builder_add_string_value(builder, guid); } json_builder_end_array(builder); } if (flags & FWUPD_DEVICE_FLAG_TRUSTED) fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_SERIAL, priv->serial); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_SUMMARY, priv->summary); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_DESCRIPTION, priv->description); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_BRANCH, priv->branch); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_PLUGIN, priv->plugin); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_PROTOCOL, priv->protocol); if (priv->protocols->len > 1) { /* --> 0 when bumping API */ json_builder_set_member_name(builder, "Protocols"); json_builder_begin_array(builder); for (guint i = 0; i < priv->protocols->len; i++) { const gchar *tmp = g_ptr_array_index(priv->protocols, i); json_builder_add_string_value(builder, tmp); } json_builder_end_array(builder); } if (priv->issues->len > 0) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_ISSUES); json_builder_begin_array(builder); for (guint i = 0; i < priv->issues->len; i++) { const gchar *tmp = g_ptr_array_index(priv->issues, i); json_builder_add_string_value(builder, tmp); } json_builder_end_array(builder); } if (priv->flags != FWUPD_DEVICE_FLAG_NONE) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_FLAGS); json_builder_begin_array(builder); for (guint i = 0; i < 64; i++) { const gchar *tmp; if ((priv->flags & ((guint64)1 << i)) == 0) continue; tmp = fwupd_device_flag_to_string((guint64)1 << i); json_builder_add_string_value(builder, tmp); } json_builder_end_array(builder); } if (priv->request_flags != FWUPD_REQUEST_FLAG_NONE) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_REQUEST_FLAGS); json_builder_begin_array(builder); for (guint i = 0; i < 64; i++) { const gchar *tmp; if ((priv->request_flags & ((guint64)1 << i)) == 0) continue; tmp = fwupd_request_flag_to_string((guint64)1 << i); json_builder_add_string_value(builder, tmp); } json_builder_end_array(builder); } if (priv->problems != FWUPD_DEVICE_PROBLEM_NONE) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_PROBLEMS); json_builder_begin_array(builder); for (guint i = 0; i < 64; i++) { const gchar *tmp; if ((priv->problems & ((guint64)1 << i)) == 0) continue; tmp = fwupd_device_problem_to_string((guint64)1 << i); json_builder_add_string_value(builder, tmp); } json_builder_end_array(builder); } if (priv->checksums->len > 0) { json_builder_set_member_name(builder, "Checksums"); json_builder_begin_array(builder); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum = g_ptr_array_index(priv->checksums, i); json_builder_add_string_value(builder, checksum); } json_builder_end_array(builder); } fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_VENDOR, priv->vendor); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_VENDOR_ID, priv->vendor_id); if (priv->vendor_ids->len > 1) { /* --> 0 when bumping API */ json_builder_set_member_name(builder, "VendorIds"); json_builder_begin_array(builder); for (guint i = 0; i < priv->vendor_ids->len; i++) { const gchar *tmp = g_ptr_array_index(priv->vendor_ids, i); json_builder_add_string_value(builder, tmp); } json_builder_end_array(builder); } fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_VERSION, priv->version); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_VERSION_LOWEST, priv->version_lowest); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_VERSION_BOOTLOADER, priv->version_bootloader); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_VERSION_FORMAT, fwupd_version_format_to_string(priv->version_format)); if (priv->flashes_left > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_FLASHES_LEFT, priv->flashes_left); if (priv->battery_level != FWUPD_BATTERY_LEVEL_INVALID) { fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_BATTERY_LEVEL, priv->battery_level); } if (priv->battery_threshold != FWUPD_BATTERY_LEVEL_INVALID) { fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_BATTERY_THRESHOLD, priv->battery_threshold); } if (priv->version_raw > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_VERSION_RAW, priv->version_raw); if (priv->version_lowest_raw > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_VERSION_LOWEST_RAW, priv->version_lowest_raw); if (priv->version_bootloader_raw > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_VERSION_BOOTLOADER_RAW, priv->version_bootloader_raw); if (priv->version_build_date > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_VERSION_BUILD_DATE, priv->version_build_date); if (priv->icons->len > 0) { json_builder_set_member_name(builder, "Icons"); json_builder_begin_array(builder); for (guint i = 0; i < priv->icons->len; i++) { const gchar *icon = g_ptr_array_index(priv->icons, i); json_builder_add_string_value(builder, icon); } json_builder_end_array(builder); } if (priv->install_duration > 0) { fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_INSTALL_DURATION, priv->install_duration); } if (priv->created > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_CREATED, priv->created); if (priv->modified > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_MODIFIED, priv->modified); if (priv->update_state > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_UPDATE_STATE, priv->update_state); if (priv->status > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_STATUS, priv->status); if (priv->percentage > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_PERCENTAGE, priv->percentage); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_UPDATE_ERROR, priv->update_error); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_UPDATE_MESSAGE, priv->update_message); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_UPDATE_IMAGE, priv->update_image); if (priv->releases->len > 0) { json_builder_set_member_name(builder, "Releases"); json_builder_begin_array(builder); for (guint i = 0; i < priv->releases->len; i++) { FwupdRelease *release = g_ptr_array_index(priv->releases, i); json_builder_begin_object(builder); fwupd_release_to_json(release, builder); json_builder_end_object(builder); } json_builder_end_array(builder); } } /** * fwupd_device_from_json: * @self: a #FwupdDevice * @json_node: (not nullable): a JSON node * @error: (nullable): optional return location for an error * * Loads a fwupd security attribute from a JSON node. * * Returns: %TRUE for success * * Since: 1.8.3 **/ gboolean fwupd_device_from_json(FwupdDevice *self, JsonNode *json_node, GError **error) { JsonObject *obj; g_return_val_if_fail(FWUPD_IS_DEVICE(self), FALSE); g_return_val_if_fail(json_node != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* sanity check */ if (!JSON_NODE_HOLDS_OBJECT(json_node)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not JSON object"); return FALSE; } obj = json_node_get_object(json_node); /* this has to exist */ if (!json_object_has_member(obj, FWUPD_RESULT_KEY_DEVICE_ID)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no %s property in object", FWUPD_RESULT_KEY_DEVICE_ID); return FALSE; } fwupd_device_set_id(self, json_object_get_string_member(obj, FWUPD_RESULT_KEY_DEVICE_ID)); /* also optional */ if (json_object_has_member(obj, FWUPD_RESULT_KEY_NAME)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_NAME, NULL); fwupd_device_set_name(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_PARENT_DEVICE_ID)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_PARENT_DEVICE_ID, NULL); fwupd_device_set_parent_id(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_COMPOSITE_ID)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_COMPOSITE_ID, NULL); fwupd_device_set_composite_id(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_PROTOCOL)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_PROTOCOL, NULL); fwupd_device_add_protocol(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_SERIAL)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_SERIAL, NULL); fwupd_device_set_serial(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_SUMMARY)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_SUMMARY, NULL); fwupd_device_set_summary(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_DESCRIPTION)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_DESCRIPTION, NULL); fwupd_device_set_description(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_BRANCH)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_BRANCH, NULL); fwupd_device_set_branch(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_PLUGIN)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_PLUGIN, NULL); fwupd_device_set_plugin(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_VENDOR)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_VENDOR, NULL); fwupd_device_set_vendor(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_VENDOR_ID)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_VENDOR_ID, NULL); if (tmp != NULL) { g_auto(GStrv) split = g_strsplit(tmp, "|", -1); for (guint i = 0; split[i] != NULL; i++) fwupd_device_add_vendor_id(self, split[i]); } } if (json_object_has_member(obj, FWUPD_RESULT_KEY_VERSION)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_VERSION, NULL); fwupd_device_set_version(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_VERSION_LOWEST)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_VERSION_LOWEST, NULL); fwupd_device_set_version_lowest(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_VERSION_BOOTLOADER)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_VERSION_BOOTLOADER, NULL); fwupd_device_set_version_bootloader(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_VERSION_FORMAT)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_VERSION_FORMAT, NULL); fwupd_device_set_version_format(self, fwupd_version_format_from_string(tmp)); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_FLASHES_LEFT)) { gint64 tmp = json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_FLASHES_LEFT, 0); fwupd_device_set_flashes_left(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_BATTERY_LEVEL)) { gint64 tmp = json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_BATTERY_LEVEL, 0); fwupd_device_set_battery_level(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_BATTERY_THRESHOLD)) { gint64 tmp = json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_BATTERY_THRESHOLD, 0); fwupd_device_set_battery_threshold(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_VERSION_RAW)) { gint64 tmp = json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_VERSION_RAW, 0); fwupd_device_set_version_raw(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_VERSION_LOWEST_RAW)) { gint64 tmp = json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_VERSION_LOWEST_RAW, 0); fwupd_device_set_version_lowest_raw(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_VERSION_BOOTLOADER_RAW)) { gint64 tmp = json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_VERSION_BOOTLOADER_RAW, 0); fwupd_device_set_version_bootloader_raw(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_VERSION_BUILD_DATE)) { gint64 tmp = json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_VERSION_BUILD_DATE, 0); fwupd_device_set_version_build_date(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_INSTALL_DURATION)) { gint64 tmp = json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_INSTALL_DURATION, 0); fwupd_device_set_install_duration(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_CREATED)) { gint64 tmp = json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_CREATED, 0); fwupd_device_set_created(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_MODIFIED)) { gint64 tmp = json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_MODIFIED, 0); fwupd_device_set_modified(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_UPDATE_STATE)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_UPDATE_STATE, NULL); fwupd_device_set_update_state(self, fwupd_update_state_from_string(tmp)); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_STATUS)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_STATUS, NULL); fwupd_device_set_status(self, fwupd_status_from_string(tmp)); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_PERCENTAGE)) { gint64 tmp = json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_PERCENTAGE, 0); fwupd_device_set_percentage(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_UPDATE_ERROR)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_UPDATE_ERROR, NULL); fwupd_device_set_update_error(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_UPDATE_MESSAGE)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_UPDATE_MESSAGE, NULL); fwupd_device_set_update_message(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_UPDATE_IMAGE)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_UPDATE_IMAGE, NULL); fwupd_device_set_update_image(self, tmp); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_INSTANCE_IDS)) { JsonArray *array = json_object_get_array_member(obj, FWUPD_RESULT_KEY_INSTANCE_IDS); for (guint i = 0; i < json_array_get_length(array); i++) fwupd_device_add_instance_id(self, json_array_get_string_element(array, i)); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_GUID)) { JsonArray *array = json_object_get_array_member(obj, FWUPD_RESULT_KEY_GUID); for (guint i = 0; i < json_array_get_length(array); i++) fwupd_device_add_guid(self, json_array_get_string_element(array, i)); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_ISSUES)) { JsonArray *array = json_object_get_array_member(obj, FWUPD_RESULT_KEY_ISSUES); for (guint i = 0; i < json_array_get_length(array); i++) fwupd_device_add_issue(self, json_array_get_string_element(array, i)); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_FLAGS)) { JsonArray *array = json_object_get_array_member(obj, FWUPD_RESULT_KEY_FLAGS); for (guint i = 0; i < json_array_get_length(array); i++) { const gchar *tmp = json_array_get_string_element(array, i); fwupd_device_add_flag(self, fwupd_device_flag_from_string(tmp)); } } if (json_object_has_member(obj, FWUPD_RESULT_KEY_PROBLEMS)) { JsonArray *array = json_object_get_array_member(obj, FWUPD_RESULT_KEY_PROBLEMS); for (guint i = 0; i < json_array_get_length(array); i++) { const gchar *tmp = json_array_get_string_element(array, i); fwupd_device_add_problem(self, fwupd_device_problem_from_string(tmp)); } } if (json_object_has_member(obj, FWUPD_RESULT_KEY_REQUEST_FLAGS)) { JsonArray *array = json_object_get_array_member(obj, FWUPD_RESULT_KEY_REQUEST_FLAGS); for (guint i = 0; i < json_array_get_length(array); i++) { const gchar *tmp = json_array_get_string_element(array, i); fwupd_device_add_request_flag(self, fwupd_request_flag_from_string(tmp)); } } if (json_object_has_member(obj, "VendorIds")) { JsonArray *array = json_object_get_array_member(obj, "VendorIds"); for (guint i = 0; i < json_array_get_length(array); i++) fwupd_device_add_vendor_id(self, json_array_get_string_element(array, i)); } if (json_object_has_member(obj, "Protocols")) { JsonArray *array = json_object_get_array_member(obj, "Protocols"); for (guint i = 0; i < json_array_get_length(array); i++) fwupd_device_add_protocol(self, json_array_get_string_element(array, i)); } if (json_object_has_member(obj, "Icons")) { JsonArray *array = json_object_get_array_member(obj, "Icons"); for (guint i = 0; i < json_array_get_length(array); i++) fwupd_device_add_icon(self, json_array_get_string_element(array, i)); } if (json_object_has_member(obj, "Checksums")) { JsonArray *array = json_object_get_array_member(obj, "Checksums"); for (guint i = 0; i < json_array_get_length(array); i++) fwupd_device_add_checksum(self, json_array_get_string_element(array, i)); } /* success */ return TRUE; } /** * fwupd_device_to_json: * @self: a #FwupdDevice * @builder: (not nullable): a JSON builder * * Adds a fwupd device to a JSON builder * * Since: 1.2.6 **/ void fwupd_device_to_json(FwupdDevice *self, JsonBuilder *builder) { return fwupd_device_to_json_full(self, builder, FWUPD_DEVICE_FLAG_NONE); } static gchar * fwupd_device_verstr_raw(guint64 value_raw) { if (value_raw > 0xffffffff) { return g_strdup_printf("0x%08x%08x", (guint)(value_raw >> 32), (guint)(value_raw & 0xffffffff)); } return g_strdup_printf("0x%08x", (guint)value_raw); } typedef struct { gchar *guid; gchar *instance_id; } FwupdDeviceGuidHelper; static void fwupd_device_guid_helper_new(FwupdDeviceGuidHelper *helper) { g_free(helper->guid); g_free(helper->instance_id); g_free(helper); } static FwupdDeviceGuidHelper * fwupd_device_guid_helper_array_find(GPtrArray *array, const gchar *guid) { for (guint i = 0; i < array->len; i++) { FwupdDeviceGuidHelper *helper = g_ptr_array_index(array, i); if (g_strcmp0(helper->guid, guid) == 0) return helper; } return NULL; } /** * fwupd_device_to_string: * @self: a #FwupdDevice * * Builds a text representation of the object. * * Returns: text, or %NULL for invalid * * Since: 0.9.3 **/ gchar * fwupd_device_to_string(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GString) str = g_string_new(NULL); g_autoptr(GPtrArray) guid_helpers = NULL; g_return_val_if_fail(FWUPD_IS_DEVICE(self), NULL); g_string_append_printf(str, "%s:\n", G_OBJECT_TYPE_NAME(self)); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_DEVICE_ID, priv->id); if (g_strcmp0(priv->composite_id, priv->parent_id) != 0) fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_PARENT_DEVICE_ID, priv->parent_id); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_COMPOSITE_ID, priv->composite_id); if (priv->name != NULL) fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_NAME, priv->name); if (priv->status != FWUPD_STATUS_UNKNOWN) { fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_STATUS, fwupd_status_to_string(priv->status)); } if (priv->percentage != 0) fwupd_pad_kv_int(str, FWUPD_RESULT_KEY_PERCENTAGE, priv->percentage); /* show instance IDs optionally mapped to GUIDs, and also "standalone" GUIDs */ guid_helpers = g_ptr_array_new_with_free_func((GDestroyNotify)fwupd_device_guid_helper_new); for (guint i = 0; i < priv->instance_ids->len; i++) { FwupdDeviceGuidHelper *helper = g_new0(FwupdDeviceGuidHelper, 1); const gchar *instance_id = g_ptr_array_index(priv->instance_ids, i); helper->guid = fwupd_guid_hash_string(instance_id); helper->instance_id = g_strdup(instance_id); g_ptr_array_add(guid_helpers, helper); } for (guint i = 0; i < priv->guids->len; i++) { const gchar *guid = g_ptr_array_index(priv->guids, i); if (fwupd_device_guid_helper_array_find(guid_helpers, guid) == NULL) { FwupdDeviceGuidHelper *helper = g_new0(FwupdDeviceGuidHelper, 1); helper->guid = g_strdup(guid); g_ptr_array_add(guid_helpers, helper); } } for (guint i = 0; i < guid_helpers->len; i++) { FwupdDeviceGuidHelper *helper = g_ptr_array_index(guid_helpers, i); g_autoptr(GString) tmp = g_string_new(helper->guid); if (helper->instance_id != NULL) g_string_append_printf(tmp, " ← %s", helper->instance_id); if (!fwupd_device_has_guid(self, helper->guid)) g_string_append(tmp, " ⚠"); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_GUID, tmp->str); } fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_SERIAL, priv->serial); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_SUMMARY, priv->summary); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_DESCRIPTION, priv->description); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_BRANCH, priv->branch); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_PLUGIN, priv->plugin); for (guint i = 0; i < priv->protocols->len; i++) { const gchar *tmp = g_ptr_array_index(priv->protocols, i); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_PROTOCOL, tmp); } for (guint i = 0; i < priv->issues->len; i++) { const gchar *tmp = g_ptr_array_index(priv->issues, i); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_ISSUES, tmp); } fwupd_pad_kv_dfl(str, FWUPD_RESULT_KEY_FLAGS, priv->flags); if (priv->problems != FWUPD_DEVICE_PROBLEM_NONE) { fwupd_device_pad_kv_problems(str, FWUPD_RESULT_KEY_PROBLEMS, priv->problems); } if (priv->request_flags > 0) fwupd_pad_kv_drfl(str, FWUPD_RESULT_KEY_REQUEST_FLAGS, priv->request_flags); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum = g_ptr_array_index(priv->checksums, i); g_autofree gchar *checksum_display = fwupd_checksum_format_for_display(checksum); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_CHECKSUM, checksum_display); } fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_VENDOR, priv->vendor); for (guint i = 0; i < priv->vendor_ids->len; i++) { const gchar *tmp = g_ptr_array_index(priv->vendor_ids, i); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_VENDOR_ID, tmp); } fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_VERSION, priv->version); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_VERSION_LOWEST, priv->version_lowest); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_VERSION_BOOTLOADER, priv->version_bootloader); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_VERSION_FORMAT, fwupd_version_format_to_string(priv->version_format)); if (priv->flashes_left < 2) fwupd_pad_kv_int(str, FWUPD_RESULT_KEY_FLASHES_LEFT, priv->flashes_left); if (priv->battery_level != FWUPD_BATTERY_LEVEL_INVALID) fwupd_pad_kv_int(str, FWUPD_RESULT_KEY_BATTERY_LEVEL, priv->battery_level); if (priv->battery_threshold != FWUPD_BATTERY_LEVEL_INVALID) fwupd_pad_kv_int(str, FWUPD_RESULT_KEY_BATTERY_THRESHOLD, priv->battery_threshold); if (priv->version_raw > 0) { g_autofree gchar *tmp = fwupd_device_verstr_raw(priv->version_raw); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_VERSION_RAW, tmp); } if (priv->version_lowest_raw > 0) { g_autofree gchar *tmp = fwupd_device_verstr_raw(priv->version_lowest_raw); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_VERSION_LOWEST_RAW, tmp); } if (priv->version_build_date > 0) { fwupd_pad_kv_unx(str, FWUPD_RESULT_KEY_VERSION_BUILD_DATE, priv->version_build_date); } if (priv->version_bootloader_raw > 0) { g_autofree gchar *tmp = fwupd_device_verstr_raw(priv->version_bootloader_raw); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_VERSION_BOOTLOADER_RAW, tmp); } if (priv->icons->len > 0) { g_autoptr(GString) tmp = g_string_new(NULL); for (guint i = 0; i < priv->icons->len; i++) { const gchar *icon = g_ptr_array_index(priv->icons, i); g_string_append_printf(tmp, "%s,", icon); } if (tmp->len > 1) g_string_truncate(tmp, tmp->len - 1); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_ICON, tmp->str); } fwupd_pad_kv_int(str, FWUPD_RESULT_KEY_INSTALL_DURATION, priv->install_duration); fwupd_pad_kv_unx(str, FWUPD_RESULT_KEY_CREATED, priv->created); fwupd_pad_kv_unx(str, FWUPD_RESULT_KEY_MODIFIED, priv->modified); fwupd_pad_kv_ups(str, FWUPD_RESULT_KEY_UPDATE_STATE, priv->update_state); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_UPDATE_ERROR, priv->update_error); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_UPDATE_MESSAGE, priv->update_message); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_UPDATE_IMAGE, priv->update_image); for (guint i = 0; i < priv->releases->len; i++) { FwupdRelease *release = g_ptr_array_index(priv->releases, i); g_autofree gchar *tmp = fwupd_release_to_string(release); g_string_append_printf(str, " \n [%s]\n%s", FWUPD_RESULT_KEY_RELEASE, tmp); } return g_string_free(g_steal_pointer(&str), FALSE); } static void fwupd_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FwupdDevice *self = FWUPD_DEVICE(object); FwupdDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_VERSION: g_value_set_string(value, priv->version); break; case PROP_VERSION_FORMAT: g_value_set_uint(value, priv->version_format); break; case PROP_FLAGS: g_value_set_uint64(value, priv->flags); break; case PROP_PROBLEMS: g_value_set_uint64(value, priv->problems); break; case PROP_REQUEST_FLAGS: g_value_set_uint64(value, priv->request_flags); break; case PROP_PROTOCOL: g_value_set_string(value, priv->protocol); break; case PROP_UPDATE_MESSAGE: g_value_set_string(value, priv->update_message); break; case PROP_UPDATE_ERROR: g_value_set_string(value, priv->update_error); break; case PROP_UPDATE_IMAGE: g_value_set_string(value, priv->update_image); break; case PROP_STATUS: g_value_set_uint(value, priv->status); break; case PROP_PERCENTAGE: g_value_set_uint(value, priv->percentage); break; case PROP_PARENT: g_value_set_object(value, priv->parent); break; case PROP_UPDATE_STATE: g_value_set_uint(value, priv->update_state); break; case PROP_BATTERY_LEVEL: g_value_set_uint(value, priv->battery_level); break; case PROP_BATTERY_THRESHOLD: g_value_set_uint(value, priv->battery_threshold); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fwupd_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FwupdDevice *self = FWUPD_DEVICE(object); switch (prop_id) { case PROP_VERSION: fwupd_device_set_version(self, g_value_get_string(value)); break; case PROP_VERSION_FORMAT: fwupd_device_set_version_format(self, g_value_get_uint(value)); break; case PROP_FLAGS: fwupd_device_set_flags(self, g_value_get_uint64(value)); break; case PROP_PROBLEMS: fwupd_device_set_problems(self, g_value_get_uint64(value)); break; case PROP_REQUEST_FLAGS: fwupd_device_set_request_flags(self, g_value_get_uint64(value)); break; case PROP_PROTOCOL: fwupd_device_add_protocol(self, g_value_get_string(value)); break; case PROP_UPDATE_MESSAGE: fwupd_device_set_update_message(self, g_value_get_string(value)); break; case PROP_UPDATE_ERROR: fwupd_device_set_update_error(self, g_value_get_string(value)); break; case PROP_UPDATE_IMAGE: fwupd_device_set_update_image(self, g_value_get_string(value)); break; case PROP_STATUS: fwupd_device_set_status(self, g_value_get_uint(value)); break; case PROP_PERCENTAGE: fwupd_device_set_percentage(self, g_value_get_uint(value)); break; case PROP_PARENT: fwupd_device_set_parent(self, g_value_get_object(value)); break; case PROP_UPDATE_STATE: fwupd_device_set_update_state(self, g_value_get_uint(value)); break; case PROP_BATTERY_LEVEL: fwupd_device_set_battery_level(self, g_value_get_uint(value)); break; case PROP_BATTERY_THRESHOLD: fwupd_device_set_battery_threshold(self, g_value_get_uint(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fwupd_device_class_init(FwupdDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->finalize = fwupd_device_finalize; object_class->get_property = fwupd_device_get_property; object_class->set_property = fwupd_device_set_property; /** * FwupdDevice:version: * * The device version. * * Since: 1.8.15 */ pspec = g_param_spec_string("version", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_VERSION, pspec); /** * FwupdDevice:version-format: * * The version format of the device. * * Since: 1.2.9 */ pspec = g_param_spec_uint("version-format", NULL, NULL, FWUPD_VERSION_FORMAT_UNKNOWN, FWUPD_VERSION_FORMAT_LAST, FWUPD_VERSION_FORMAT_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_VERSION_FORMAT, pspec); /** * FwupdDevice:flags: * * The device flags. * * Since: 0.9.3 */ pspec = g_param_spec_uint64("flags", NULL, NULL, FWUPD_DEVICE_FLAG_NONE, FWUPD_DEVICE_FLAG_UNKNOWN, FWUPD_DEVICE_FLAG_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FLAGS, pspec); /** * FwupdDevice:problems: * * The problems with the device that the user could fix, e.g. "lid open". * * Since: 1.8.1 */ pspec = g_param_spec_uint64("problems", NULL, NULL, FWUPD_DEVICE_PROBLEM_NONE, FWUPD_DEVICE_PROBLEM_UNKNOWN, FWUPD_DEVICE_PROBLEM_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PROBLEMS, pspec); /** * FwupdDevice:request-flags: * * The device request flags. * * Since: 1.9.10 */ pspec = g_param_spec_uint64("request-flags", NULL, NULL, FWUPD_REQUEST_FLAG_NONE, FWUPD_REQUEST_FLAG_UNKNOWN, FWUPD_REQUEST_FLAG_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_REQUEST_FLAGS, pspec); /** * FwupdDevice:protocol: * * The device protocol. * * Since: 1.3.6 * Deprecated: 1.5.8 */ pspec = g_param_spec_string("protocol", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PROTOCOL, pspec); /** * FwupdDevice:status: * * The current device status. * * Since: 1.4.0 */ pspec = g_param_spec_uint("status", NULL, NULL, FWUPD_STATUS_UNKNOWN, FWUPD_STATUS_LAST, FWUPD_STATUS_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_STATUS, pspec); /** * FwupdDevice:percentage: * * The current device percentage. * * Since: 1.8.11 */ pspec = g_param_spec_uint("percentage", NULL, NULL, 0, 100, 0, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PERCENTAGE, pspec); /** * FwupdDevice:parent: * * The device parent. * * Since: 1.0.8 */ pspec = g_param_spec_object("parent", NULL, NULL, FWUPD_TYPE_DEVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PARENT, pspec); /** * FwupdDevice:update-state: * * The device update state. * * Since: 0.9.8 */ pspec = g_param_spec_uint("update-state", NULL, NULL, FWUPD_UPDATE_STATE_UNKNOWN, FWUPD_UPDATE_STATE_LAST, FWUPD_UPDATE_STATE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_UPDATE_STATE, pspec); /** * FwupdDevice:update-message: * * The device update message. * * Since: 1.2.4 */ pspec = g_param_spec_string("update-message", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_UPDATE_MESSAGE, pspec); /** * FwupdDevice:update-error: * * The device update error. * * Since: 0.9.8 */ pspec = g_param_spec_string("update-error", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_UPDATE_ERROR, pspec); /** * FwupdDevice:update-image: * * The update image for the device. * * Since: 1.4.5 */ pspec = g_param_spec_string("update-image", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_UPDATE_IMAGE, pspec); /** * FwupdDevice:battery-level: * * The device battery level in percent. * * Since: 1.5.8 */ pspec = g_param_spec_uint("battery-level", NULL, NULL, 0, FWUPD_BATTERY_LEVEL_INVALID, FWUPD_BATTERY_LEVEL_INVALID, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BATTERY_LEVEL, pspec); /** * FwupdDevice:battery-threshold: * * The device battery threshold in percent. * * Since: 1.5.8 */ pspec = g_param_spec_uint("battery-threshold", NULL, NULL, 0, FWUPD_BATTERY_LEVEL_INVALID, FWUPD_BATTERY_LEVEL_INVALID, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BATTERY_THRESHOLD, pspec); } static void fwupd_device_init(FwupdDevice *self) { FwupdDevicePrivate *priv = GET_PRIVATE(self); priv->guids = g_ptr_array_new_with_free_func(g_free); priv->instance_ids = g_ptr_array_new_with_free_func(g_free); priv->icons = g_ptr_array_new_with_free_func(g_free); priv->checksums = g_ptr_array_new_with_free_func(g_free); priv->vendor_ids = g_ptr_array_new_with_free_func(g_free); priv->protocols = g_ptr_array_new_with_free_func(g_free); priv->issues = g_ptr_array_new_with_free_func(g_free); priv->children = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); priv->releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); priv->battery_level = FWUPD_BATTERY_LEVEL_INVALID; priv->battery_threshold = FWUPD_BATTERY_LEVEL_INVALID; } static void fwupd_device_finalize(GObject *object) { FwupdDevice *self = FWUPD_DEVICE(object); FwupdDevicePrivate *priv = GET_PRIVATE(self); if (priv->parent != NULL) g_object_remove_weak_pointer(G_OBJECT(priv->parent), (gpointer *)&priv->parent); for (guint i = 0; i < priv->children->len; i++) { FwupdDevice *child = g_ptr_array_index(priv->children, i); g_object_weak_unref(G_OBJECT(child), fwupd_device_child_finalized_cb, self); } g_free(priv->description); g_free(priv->id); g_free(priv->parent_id); g_free(priv->composite_id); g_free(priv->name); g_free(priv->serial); g_free(priv->summary); g_free(priv->branch); g_free(priv->vendor); g_free(priv->vendor_id); g_free(priv->plugin); g_free(priv->protocol); g_free(priv->update_error); g_free(priv->update_message); g_free(priv->update_image); g_free(priv->version); g_free(priv->version_lowest); g_free(priv->version_bootloader); g_ptr_array_unref(priv->guids); g_ptr_array_unref(priv->vendor_ids); g_ptr_array_unref(priv->protocols); g_ptr_array_unref(priv->instance_ids); g_ptr_array_unref(priv->icons); g_ptr_array_unref(priv->checksums); g_ptr_array_unref(priv->children); g_ptr_array_unref(priv->releases); g_ptr_array_unref(priv->issues); G_OBJECT_CLASS(fwupd_device_parent_class)->finalize(object); } static void fwupd_device_set_from_variant_iter(FwupdDevice *self, GVariantIter *iter) { GVariant *value; const gchar *key; while (g_variant_iter_next(iter, "{&sv}", &key, &value)) { fwupd_device_from_key_value(self, key, value); g_variant_unref(value); } } /** * fwupd_device_from_variant: * @value: (not nullable): the serialized data * * Creates a new device using serialized data. * * Returns: (transfer full): a new #FwupdDevice, or %NULL if @value was invalid * * Since: 1.0.0 **/ FwupdDevice * fwupd_device_from_variant(GVariant *value) { FwupdDevice *dev = NULL; const gchar *type_string; g_autoptr(GVariantIter) iter = NULL; g_return_val_if_fail(value != NULL, NULL); /* format from GetDetails */ type_string = g_variant_get_type_string(value); if (g_strcmp0(type_string, "(a{sv})") == 0) { dev = fwupd_device_new(); g_variant_get(value, "(a{sv})", &iter); fwupd_device_set_from_variant_iter(dev, iter); } else if (g_strcmp0(type_string, "a{sv}") == 0) { dev = fwupd_device_new(); g_variant_get(value, "a{sv}", &iter); fwupd_device_set_from_variant_iter(dev, iter); } else { g_warning("type %s not known", type_string); } return dev; } /** * fwupd_device_array_ensure_parents: * @devices: (not nullable) (element-type FwupdDevice): devices * * Sets the parent object on all devices in the array using the parent ID. * * Since: 1.3.7 **/ void fwupd_device_array_ensure_parents(GPtrArray *devices) { g_autoptr(GHashTable) devices_by_id = NULL; g_return_if_fail(devices != NULL); /* create hash of ID->FwupdDevice */ devices_by_id = g_hash_table_new(g_str_hash, g_str_equal); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); if (fwupd_device_get_id(dev) == NULL) continue; g_hash_table_insert(devices_by_id, (gpointer)fwupd_device_get_id(dev), (gpointer)dev); } /* set the parent on each child */ for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); const gchar *parent_id = fwupd_device_get_parent_id(dev); if (parent_id != NULL) { FwupdDevice *dev_tmp; dev_tmp = g_hash_table_lookup(devices_by_id, parent_id); if (dev_tmp != NULL) fwupd_device_set_parent(dev, dev_tmp); } } } /** * fwupd_device_array_from_variant: * @value: (not nullable): the serialized data * * Creates an array of new devices using serialized data. * * Returns: (transfer container) (element-type FwupdDevice): devices, or %NULL if @value was invalid * * Since: 1.2.10 **/ GPtrArray * fwupd_device_array_from_variant(GVariant *value) { GPtrArray *array = NULL; gsize sz; g_autoptr(GVariant) untuple = NULL; g_return_val_if_fail(value != NULL, NULL); array = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); untuple = g_variant_get_child_value(value, 0); sz = g_variant_n_children(untuple); for (guint i = 0; i < sz; i++) { FwupdDevice *dev; g_autoptr(GVariant) data = NULL; data = g_variant_get_child_value(untuple, i); dev = fwupd_device_from_variant(data); if (dev == NULL) continue; g_ptr_array_add(array, dev); } /* set the parent on each child */ fwupd_device_array_ensure_parents(array); return array; } /** * fwupd_device_compare: * @self1: (not nullable): a device * @self2: (not nullable): a different device * * Comparison function for comparing two device objects. * * Returns: negative, 0 or positive * * Since: 1.1.1 **/ gint fwupd_device_compare(FwupdDevice *self1, FwupdDevice *self2) { FwupdDevicePrivate *priv1 = GET_PRIVATE(self1); FwupdDevicePrivate *priv2 = GET_PRIVATE(self2); g_return_val_if_fail(FWUPD_IS_DEVICE(self1), 0); g_return_val_if_fail(FWUPD_IS_DEVICE(self2), 0); return g_strcmp0(priv1->id, priv2->id); } /** * fwupd_device_match_flags: * @include: #FwupdDeviceFlags, or %FWUPD_DEVICE_FLAG_NONE * @exclude: #FwupdDeviceFlags, or %FWUPD_DEVICE_FLAG_NONE * * Check if the device flags match. * * Returns: %TRUE if the device flags match * * Since: 1.9.3 **/ gboolean fwupd_device_match_flags(FwupdDevice *self, FwupdDeviceFlags include, FwupdDeviceFlags exclude) { g_return_val_if_fail(FWUPD_IS_DEVICE(self), FALSE); for (guint i = 0; i < 64; i++) { FwupdDeviceFlags flag = 1LLU << i; if ((include & flag) > 0) { if (!fwupd_device_has_flag(self, flag)) return FALSE; } if ((exclude & flag) > 0) { if (fwupd_device_has_flag(self, flag)) return FALSE; } } return TRUE; } /** * fwupd_device_array_filter_flags: * @devices: (not nullable) (element-type FwupdDevice): devices * @include: #FwupdDeviceFlags, or %FWUPD_DEVICE_FLAG_NONE * @exclude: #FwupdDeviceFlags, or %FWUPD_DEVICE_FLAG_NONE * @error: (nullable): optional return location for an error * * Creates an array of new devices that match using fwupd_device_match_flags(). * * Returns: (transfer container) (element-type FwupdDevice): devices * * Since: 1.9.3 **/ GPtrArray * fwupd_device_array_filter_flags(GPtrArray *devices, FwupdDeviceFlags include, FwupdDeviceFlags exclude, GError **error) { g_autoptr(GPtrArray) devices_filtered = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_return_val_if_fail(devices != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); for (guint i = 0; i < devices->len; i++) { FwupdDevice *device = g_ptr_array_index(devices, i); if (!fwupd_device_match_flags(device, include, exclude)) continue; g_ptr_array_add(devices_filtered, g_object_ref(device)); } if (devices_filtered->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "no devices"); return NULL; } return g_steal_pointer(&devices_filtered); } /** * fwupd_device_new: * * Creates a new device. * * Returns: a new #FwupdDevice * * Since: 0.9.3 **/ FwupdDevice * fwupd_device_new(void) { FwupdDevice *self; self = g_object_new(FWUPD_TYPE_DEVICE, NULL); return FWUPD_DEVICE(self); } fwupd-1.9.16/libfwupd/fwupd-device.h000066400000000000000000000264731460375044200173250ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-enums.h" #include "fwupd-release.h" #include "fwupd-request.h" G_BEGIN_DECLS #define FWUPD_TYPE_DEVICE (fwupd_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FwupdDevice, fwupd_device, FWUPD, DEVICE, GObject) struct _FwupdDeviceClass { GObjectClass parent_class; /*< private >*/ void (*_fwupd_reserved1)(void); void (*_fwupd_reserved2)(void); void (*_fwupd_reserved3)(void); void (*_fwupd_reserved4)(void); void (*_fwupd_reserved5)(void); void (*_fwupd_reserved6)(void); void (*_fwupd_reserved7)(void); }; FwupdDevice * fwupd_device_new(void); gchar * fwupd_device_to_string(FwupdDevice *self) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_id(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_id(FwupdDevice *self, const gchar *id) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_parent_id(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_parent_id(FwupdDevice *self, const gchar *parent_id) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_composite_id(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_composite_id(FwupdDevice *self, const gchar *composite_id) G_GNUC_NON_NULL(1); FwupdDevice * fwupd_device_get_root(FwupdDevice *self) G_GNUC_NON_NULL(1); FwupdDevice * fwupd_device_get_parent(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_parent(FwupdDevice *self, FwupdDevice *parent) G_GNUC_NON_NULL(1); void fwupd_device_add_child(FwupdDevice *self, FwupdDevice *child) G_GNUC_NON_NULL(1, 2); void fwupd_device_remove_child(FwupdDevice *self, FwupdDevice *child) G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_device_get_children(FwupdDevice *self) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_name(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_name(FwupdDevice *self, const gchar *name) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_serial(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_serial(FwupdDevice *self, const gchar *serial) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_summary(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_summary(FwupdDevice *self, const gchar *summary) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_branch(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_branch(FwupdDevice *self, const gchar *branch) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_description(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_description(FwupdDevice *self, const gchar *description) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_version(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_version(FwupdDevice *self, const gchar *version) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_version_lowest(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_version_lowest(FwupdDevice *self, const gchar *version_lowest) G_GNUC_NON_NULL(1); guint64 fwupd_device_get_version_lowest_raw(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_version_lowest_raw(FwupdDevice *self, guint64 version_lowest_raw) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_version_bootloader(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_version_bootloader(FwupdDevice *self, const gchar *version_bootloader) G_GNUC_NON_NULL(1); guint64 fwupd_device_get_version_bootloader_raw(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_version_bootloader_raw(FwupdDevice *self, guint64 version_bootloader_raw) G_GNUC_NON_NULL(1); guint64 fwupd_device_get_version_raw(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_version_raw(FwupdDevice *self, guint64 version_raw) G_GNUC_NON_NULL(1); guint64 fwupd_device_get_version_build_date(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_version_build_date(FwupdDevice *self, guint64 version_build_date) G_GNUC_NON_NULL(1); FwupdVersionFormat fwupd_device_get_version_format(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_version_format(FwupdDevice *self, FwupdVersionFormat version_format) G_GNUC_NON_NULL(1); guint32 fwupd_device_get_flashes_left(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_flashes_left(FwupdDevice *self, guint32 flashes_left) G_GNUC_NON_NULL(1); guint32 fwupd_device_get_battery_level(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_battery_level(FwupdDevice *self, guint32 battery_level) G_GNUC_NON_NULL(1); guint32 fwupd_device_get_battery_threshold(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_battery_threshold(FwupdDevice *self, guint32 battery_threshold) G_GNUC_NON_NULL(1); guint32 fwupd_device_get_install_duration(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_install_duration(FwupdDevice *self, guint32 duration) G_GNUC_NON_NULL(1); guint64 fwupd_device_get_flags(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_flags(FwupdDevice *self, guint64 flags) G_GNUC_NON_NULL(1); void fwupd_device_add_flag(FwupdDevice *self, FwupdDeviceFlags flag) G_GNUC_NON_NULL(1); void fwupd_device_remove_flag(FwupdDevice *self, FwupdDeviceFlags flag) G_GNUC_NON_NULL(1); gboolean fwupd_device_has_flag(FwupdDevice *self, FwupdDeviceFlags flag) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); guint64 fwupd_device_get_problems(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_problems(FwupdDevice *self, guint64 problems) G_GNUC_NON_NULL(1); void fwupd_device_add_problem(FwupdDevice *self, FwupdDeviceProblem problem) G_GNUC_NON_NULL(1); void fwupd_device_remove_problem(FwupdDevice *self, FwupdDeviceProblem problem) G_GNUC_NON_NULL(1); gboolean fwupd_device_has_problem(FwupdDevice *self, FwupdDeviceProblem problem) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); guint64 fwupd_device_get_request_flags(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_request_flags(FwupdDevice *self, guint64 request_flags) G_GNUC_NON_NULL(1); void fwupd_device_add_request_flag(FwupdDevice *self, FwupdRequestFlags request_flag) G_GNUC_NON_NULL(1); void fwupd_device_remove_request_flag(FwupdDevice *self, FwupdRequestFlags request_flag) G_GNUC_NON_NULL(1); gboolean fwupd_device_has_request_flag(FwupdDevice *self, FwupdRequestFlags request_flag) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); guint64 fwupd_device_get_created(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_created(FwupdDevice *self, guint64 created) G_GNUC_NON_NULL(1); guint64 fwupd_device_get_modified(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_modified(FwupdDevice *self, guint64 modified) G_GNUC_NON_NULL(1); GPtrArray * fwupd_device_get_checksums(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_add_checksum(FwupdDevice *self, const gchar *checksum) G_GNUC_NON_NULL(1, 2); gboolean fwupd_device_has_checksum(FwupdDevice *self, const gchar *checksum) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); const gchar * fwupd_device_get_plugin(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_plugin(FwupdDevice *self, const gchar *plugin) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(fwupd_device_get_protocols) const gchar * fwupd_device_get_protocol(FwupdDevice *self) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(fwupd_device_add_protocol) void fwupd_device_set_protocol(FwupdDevice *self, const gchar *protocol) G_GNUC_NON_NULL(1); void fwupd_device_add_protocol(FwupdDevice *self, const gchar *protocol) G_GNUC_NON_NULL(1, 2); gboolean fwupd_device_has_protocol(FwupdDevice *self, const gchar *protocol) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_device_get_protocols(FwupdDevice *self) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_vendor(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_vendor(FwupdDevice *self, const gchar *vendor) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(fwupd_device_get_vendor_ids) const gchar * fwupd_device_get_vendor_id(FwupdDevice *self) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(fwupd_device_add_vendor_id) void fwupd_device_set_vendor_id(FwupdDevice *self, const gchar *vendor_id) G_GNUC_NON_NULL(1); void fwupd_device_add_vendor_id(FwupdDevice *self, const gchar *vendor_id) G_GNUC_NON_NULL(1, 2); gboolean fwupd_device_has_vendor_id(FwupdDevice *self, const gchar *vendor_id) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_device_get_vendor_ids(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_add_guid(FwupdDevice *self, const gchar *guid) G_GNUC_NON_NULL(1, 2); gboolean fwupd_device_has_guid(FwupdDevice *self, const gchar *guid) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_device_get_guids(FwupdDevice *self) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_guid_default(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_add_instance_id(FwupdDevice *self, const gchar *instance_id) G_GNUC_NON_NULL(1, 2); gboolean fwupd_device_has_instance_id(FwupdDevice *self, const gchar *instance_id) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_device_get_instance_ids(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_add_icon(FwupdDevice *self, const gchar *icon) G_GNUC_NON_NULL(1, 2); gboolean fwupd_device_has_icon(FwupdDevice *self, const gchar *icon) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_device_get_icons(FwupdDevice *self) G_GNUC_NON_NULL(1); GPtrArray * fwupd_device_get_issues(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_add_issue(FwupdDevice *self, const gchar *issue) G_GNUC_NON_NULL(1, 2); FwupdUpdateState fwupd_device_get_update_state(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_update_state(FwupdDevice *self, FwupdUpdateState update_state) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_update_error(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_update_error(FwupdDevice *self, const gchar *update_error) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_update_message(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_update_message(FwupdDevice *self, const gchar *update_message) G_GNUC_NON_NULL(1); const gchar * fwupd_device_get_update_image(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_update_image(FwupdDevice *self, const gchar *update_image) G_GNUC_NON_NULL(1); FwupdStatus fwupd_device_get_status(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_status(FwupdDevice *self, FwupdStatus status) G_GNUC_NON_NULL(1); guint fwupd_device_get_percentage(FwupdDevice *self) G_GNUC_NON_NULL(1); void fwupd_device_set_percentage(FwupdDevice *self, guint percentage) G_GNUC_NON_NULL(1); void fwupd_device_add_release(FwupdDevice *self, FwupdRelease *release) G_GNUC_NON_NULL(1); GPtrArray * fwupd_device_get_releases(FwupdDevice *self) G_GNUC_NON_NULL(1); FwupdRelease * fwupd_device_get_release_default(FwupdDevice *self) G_GNUC_NON_NULL(1); gint fwupd_device_compare(FwupdDevice *self1, FwupdDevice *self2) G_GNUC_NON_NULL(1, 2); gboolean fwupd_device_match_flags(FwupdDevice *self, FwupdDeviceFlags include, FwupdDeviceFlags exclude) G_GNUC_NON_NULL(1); FwupdDevice * fwupd_device_from_variant(GVariant *value) G_GNUC_NON_NULL(1); GPtrArray * fwupd_device_array_from_variant(GVariant *value) G_GNUC_NON_NULL(1); void fwupd_device_array_ensure_parents(GPtrArray *devices) G_GNUC_NON_NULL(1); GPtrArray * fwupd_device_array_filter_flags(GPtrArray *devices, FwupdDeviceFlags include, FwupdDeviceFlags exclude, GError **error) G_GNUC_NON_NULL(1); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-enums-private.h000066400000000000000000000423121460375044200206530ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS /** * FWUPD_RESULT_KEY_APPSTREAM_ID: * * Result key to represent AppstreamId * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_APPSTREAM_ID "AppstreamId" /** * FWUPD_RESULT_KEY_RELEASE_ID: * * Result key to represent the release ID. * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_RELEASE_ID "ReleaseId" /** * FWUPD_RESULT_KEY_CHECKSUM: * * Result key to represent Checksum * * The D-Bus type signature string is 'as' i.e. an array of strings. **/ #define FWUPD_RESULT_KEY_CHECKSUM "Checksum" /** * FWUPD_RESULT_KEY_TAGS: * * Result key to represent release tags * * The D-Bus type signature string is 'as' i.e. an array of strings. **/ #define FWUPD_RESULT_KEY_TAGS "Tags" /** * FWUPD_RESULT_KEY_CREATED: * * Result key to represent Created * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_CREATED "Created" /** * FWUPD_RESULT_KEY_DESCRIPTION: * * Result key to represent Description * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_DESCRIPTION "Description" /** * FWUPD_RESULT_KEY_DETACH_CAPTION: * * Result key to represent DetachCaption * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_DETACH_CAPTION "DetachCaption" /** * FWUPD_RESULT_KEY_DETACH_IMAGE: * * Result key to represent DetachImage * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_DETACH_IMAGE "DetachImage" /** * FWUPD_RESULT_KEY_DEVICE_ID: * * Result key to represent DeviceId * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_DEVICE_ID "DeviceId" /** * FWUPD_RESULT_KEY_PARENT_DEVICE_ID: * * Result key to represent ParentDeviceId * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_PARENT_DEVICE_ID "ParentDeviceId" /** * FWUPD_RESULT_KEY_COMPOSITE_ID: * * Result key to represent CompositeId * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_COMPOSITE_ID "CompositeId" /** * FWUPD_RESULT_KEY_FILENAME: * * Result key to represent Filename * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_FILENAME "Filename" /** * FWUPD_RESULT_KEY_PROTOCOL: * * Result key to represent Protocol * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_PROTOCOL "Protocol" /** * FWUPD_RESULT_KEY_CATEGORIES: * * Result key to represent Categories * * The D-Bus type signature string is 'as' i.e. an array of strings. **/ #define FWUPD_RESULT_KEY_CATEGORIES "Categories" /** * FWUPD_RESULT_KEY_ISSUES: * * Result key to represent Issues * * The D-Bus type signature string is 'as' i.e. an array of strings. **/ #define FWUPD_RESULT_KEY_ISSUES "Issues" /** * FWUPD_RESULT_KEY_FLAGS: * * Result key to represent Flags * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_FLAGS "Flags" /** * FWUPD_RESULT_KEY_REQUEST_FLAGS: * * Result key to represent RequestFlags * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_REQUEST_FLAGS "RequestFlags" /** * FWUPD_RESULT_KEY_FLASHES_LEFT: * * Result key to represent FlashesLeft * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_FLASHES_LEFT "FlashesLeft" /** * FWUPD_RESULT_KEY_URGENCY: * * Result key to represent Urgency * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_URGENCY "Urgency" /** * FWUPD_RESULT_KEY_REQUEST_KIND: * * Result key to represent RequestKind * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_REQUEST_KIND "RequestKind" /** * FWUPD_RESULT_KEY_HSI_LEVEL: * * Result key to represent HsiLevel * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_HSI_LEVEL "HsiLevel" /** * FWUPD_RESULT_KEY_HSI_RESULT: * * Result key to represent HsiResult * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_HSI_RESULT "HsiResult" /** * FWUPD_RESULT_KEY_HSI_RESULT_FALLBACK: * * Result key to represent the fallback HsiResult * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_HSI_RESULT_FALLBACK "HsiResultFallback" /** * FWUPD_RESULT_KEY_HSI_RESULT_SUCCESS: * * Result key to represent the desired HsiResult * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_HSI_RESULT_SUCCESS "HsiResultSuccess" /** * FWUPD_RESULT_KEY_INSTALL_DURATION: * * Result key to represent InstallDuration * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_INSTALL_DURATION "InstallDuration" /** * FWUPD_RESULT_KEY_GUID: * * Result key to represent Guid * * The D-Bus type signature string is 'as' i.e. an array of strings. **/ #define FWUPD_RESULT_KEY_GUID "Guid" /** * FWUPD_RESULT_KEY_INSTANCE_IDS: * * Result key to represent InstanceIds * * The D-Bus type signature string is 'as' i.e. an array of strings. **/ #define FWUPD_RESULT_KEY_INSTANCE_IDS "InstanceIds" /** * FWUPD_RESULT_KEY_HOMEPAGE: * * Result key to represent Homepage * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_HOMEPAGE "Homepage" /** * FWUPD_RESULT_KEY_DETAILS_URL: * * Result key to represent DetailsUrl * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_DETAILS_URL "DetailsUrl" /** * FWUPD_RESULT_KEY_SOURCE_URL: * * Result key to represent SourceUrl * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_SOURCE_URL "SourceUrl" /** * FWUPD_RESULT_KEY_ICON: * * Result key to represent Icon * * The D-Bus type signature string is 'as' i.e. an array of strings. **/ #define FWUPD_RESULT_KEY_ICON "Icon" /** * FWUPD_RESULT_KEY_LICENSE: * * Result key to represent License * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_LICENSE "License" /** * FWUPD_RESULT_KEY_MODIFIED: * * Result key to represent Modified * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_MODIFIED "Modified" /** * FWUPD_RESULT_KEY_VERSION_BUILD_DATE: * * Result key to represent VersionBuildDate * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_VERSION_BUILD_DATE "VersionBuildDate" /** * FWUPD_RESULT_KEY_METADATA: * * Result key to represent Metadata * * The D-Bus type signature string is 'a{ss}' i.e. a string dictionary. **/ #define FWUPD_RESULT_KEY_METADATA "Metadata" /** * FWUPD_RESULT_KEY_NAME: * * Result key to represent Name * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_NAME "Name" /** * FWUPD_RESULT_KEY_NAME_VARIANT_SUFFIX: * * Result key to represent NameVariantSuffix * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_NAME_VARIANT_SUFFIX "NameVariantSuffix" /** * FWUPD_RESULT_KEY_PLUGIN: * * Result key to represent Plugin * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_PLUGIN "Plugin" /** * FWUPD_RESULT_KEY_RELEASE: * * Result key to represent Release * * The D-Bus type signature string is 'a{sv}' i.e. a variant dictionary. **/ #define FWUPD_RESULT_KEY_RELEASE "Release" /** * FWUPD_RESULT_KEY_REMOTE_ID: * * Result key to represent RemoteId * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_REMOTE_ID "RemoteId" /** * FWUPD_RESULT_KEY_SERIAL: * * Result key to represent Serial * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_SERIAL "Serial" /** * FWUPD_RESULT_KEY_SIZE: * * Result key to represent Size * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_SIZE "Size" /** * FWUPD_RESULT_KEY_STATUS: * * Result key to represent Status * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_STATUS "Status" /** * FWUPD_RESULT_KEY_PERCENTAGE: * * Result key to represent progress percentage, typically installation or verification * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_PERCENTAGE "Percentage" /** * FWUPD_RESULT_KEY_SUMMARY: * * Result key to represent Summary * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_SUMMARY "Summary" /** * FWUPD_RESULT_KEY_BRANCH: * * Result key to represent Branch * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_BRANCH "Branch" /** * FWUPD_RESULT_KEY_TRUST_FLAGS: * * Result key to represent TrustFlags * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_TRUST_FLAGS "TrustFlags" /** * FWUPD_RESULT_KEY_PROBLEMS: * * Result key to represent problems * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_PROBLEMS "Problems" /** * FWUPD_RESULT_KEY_UPDATE_MESSAGE: * * Result key to represent UpdateMessage * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_UPDATE_MESSAGE "UpdateMessage" /** * FWUPD_RESULT_KEY_UPDATE_IMAGE: * * Result key to represent UpdateImage * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_UPDATE_IMAGE "UpdateImage" /** * FWUPD_RESULT_KEY_UPDATE_ERROR: * * Result key to represent UpdateError * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_UPDATE_ERROR "UpdateError" /** * FWUPD_RESULT_KEY_UPDATE_STATE: * * Result key to represent UpdateState * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_UPDATE_STATE "UpdateState" /** * FWUPD_RESULT_KEY_URI: * * Result key to represent Uri * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_URI "Uri" /** * FWUPD_RESULT_KEY_LOCATIONS: * * Result key to represent Locations * * The D-Bus type signature string is 'as' i.e. an array of strings. **/ #define FWUPD_RESULT_KEY_LOCATIONS "Locations" /** * FWUPD_RESULT_KEY_VENDOR_ID: * * Result key to represent VendorId * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_VENDOR_ID "VendorId" /** * FWUPD_RESULT_KEY_VENDOR: * * Result key to represent Vendor * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_VENDOR "Vendor" /** * FWUPD_RESULT_KEY_VERSION_BOOTLOADER: * * Result key to represent VersionBootloader * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_VERSION_BOOTLOADER "VersionBootloader" /** * FWUPD_RESULT_KEY_VERSION_BOOTLOADER_RAW: * * Result key to represent VersionBootloaderRaw * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_VERSION_BOOTLOADER_RAW "VersionBootloaderRaw" /** * FWUPD_RESULT_KEY_VERSION_FORMAT: * * Result key to represent VersionFormat * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_VERSION_FORMAT "VersionFormat" /** * FWUPD_RESULT_KEY_VERSION_RAW: * * Result key to represent VersionRaw * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_VERSION_RAW "VersionRaw" /** * FWUPD_RESULT_KEY_VERSION_LOWEST: * * Result key to represent VersionLowest * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_VERSION_LOWEST "VersionLowest" /** * FWUPD_RESULT_KEY_VERSION_LOWEST_RAW: * * Result key to represent VersionLowestRaw * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_VERSION_LOWEST_RAW "VersionLowestRaw" /** * FWUPD_RESULT_KEY_VERSION: * * Result key to represent Version * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_VERSION "Version" /** * FWUPD_RESULT_KEY_VERSION_OLD: * * Result key to represent the old version string. * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_VERSION_OLD "VersionOld" /** * FWUPD_RESULT_KEY_BATTERY_LEVEL: * * Result key to represent the current battery level in percent. * Expressed from 0-100%, or 101 for invalid or unset. * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_BATTERY_LEVEL "BatteryLevel" /** * FWUPD_RESULT_KEY_BATTERY_THRESHOLD: * * Result key to represent the minimum battery level required to perform an update. * Expressed from 0-100%, or 101 for invalid or unset. * * The D-Bus type signature string is 'u' i.e. a unsigned 32 bit integer. **/ #define FWUPD_RESULT_KEY_BATTERY_THRESHOLD "BatteryThreshold" /** * FWUPD_RESULT_KEY_BIOS_SETTING_ID: * * Result key to represent the unique identifier of the BIOS setting. * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_BIOS_SETTING_ID "BiosSettingId" /** * FWUPD_RESULT_KEY_BIOS_SETTING_TARGET_VALUE: * * Result key to represent the value that would enable this attribute. * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_BIOS_SETTING_TARGET_VALUE "BiosSettingTargetValue" /** * FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE: * * Result key to represent the current value of BIOS setting. * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE "BiosSettingCurrentValue" /** * FWUPD_RESULT_KEY_BIOS_SETTING_TYPE: * * Result key to represent the type of BIOS setting. * 0 is invalid, 1+ represent an attribute type * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_BIOS_SETTING_TYPE "BiosSettingType" /** * FWUPD_RESULT_KEY_BIOS_SETTING_POSSIBLE_VALUES: * * Result key to represent possible values * * The D-Bus type signature string is 'as' i.e. an array of strings. **/ #define FWUPD_RESULT_KEY_BIOS_SETTING_POSSIBLE_VALUES "BiosSettingPossibleValues" /** * FWUPD_RESULT_KEY_BIOS_SETTING_LOWER_BOUND: * * Result key to represent the upper bound for an integer BIOS setting. * or minimum length for string BIOS setting. * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_BIOS_SETTING_LOWER_BOUND "BiosSettingLowerBound" /** * FWUPD_RESULT_KEY_BIOS_SETTING_UPPER_BOUND: * * Result key to represent the lower bound for an integer BIOS setting * or maximum length for string BIOS setting. * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_BIOS_SETTING_UPPER_BOUND "BiosSettingUpperBound" /** * FWUPD_RESULT_KEY_BIOS_SETTING_SCALAR_INCREMENT: * * Result key to represent the scalar increment for an integer BIOS setting. * * The D-Bus type signature string is 't' i.e. a unsigned 64 bit integer. **/ #define FWUPD_RESULT_KEY_BIOS_SETTING_SCALAR_INCREMENT "BiosSettingScalarIncrement" /** * FWUPD_RESULT_KEY_BIOS_SETTING_READ_ONLY: * * Result key to represent whether BIOS setting is read only * * The D-Bus type signature string is 'b' i.e. a boolean. **/ #define FWUPD_RESULT_KEY_BIOS_SETTING_READ_ONLY "BiosSettingReadOnly" /** * FWUPD_RESULT_KEY_KERNEL_CURRENT_VALUE: * * Result key to represent the current kernel setting. * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_KERNEL_CURRENT_VALUE "KernelCurrentValue" /** * FWUPD_RESULT_KEY_KERNEL_TARGET_VALUE: * * Result key to represent the target kernel setting. * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_KERNEL_TARGET_VALUE "KernelTargetValue" /** * FWUPD_RESULT_KEY_DISTRO_ID: * * Result key to represent the distribution ID. * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_DISTRO_ID "DistroId" /** * FWUPD_RESULT_KEY_DISTRO_VARIANT: * * Result key to represent the distribution variant. * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_DISTRO_VARIANT "DistroVariant" /** * FWUPD_RESULT_KEY_DISTRO_VERSION: * * Result key to represent the distribution version. * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_DISTRO_VERSION "DistroVersion" /** * FWUPD_RESULT_KEY_REPORTS: * * Result key to represent an array of reports. * * The D-Bus type signature string is 'a{sv}' i.e. a variant dictionary. **/ #define FWUPD_RESULT_KEY_REPORTS "Reports" /** * FWUPD_RESULT_KEY_DEVICE_NAME: * * Result key to represent the device name. * * The D-Bus type signature string is 's' i.e. a string. **/ #define FWUPD_RESULT_KEY_DEVICE_NAME "DeviceName" G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-enums.c000066400000000000000000000775111460375044200172070ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fwupd-enums.h" /** * fwupd_status_to_string: * @status: a status, e.g. %FWUPD_STATUS_DECOMPRESSING * * Converts an enumerated status to a string. * * Returns: identifier string * * Since: 0.1.1 **/ const gchar * fwupd_status_to_string(FwupdStatus status) { if (status == FWUPD_STATUS_UNKNOWN) return "unknown"; if (status == FWUPD_STATUS_IDLE) return "idle"; if (status == FWUPD_STATUS_DECOMPRESSING) return "decompressing"; if (status == FWUPD_STATUS_LOADING) return "loading"; if (status == FWUPD_STATUS_DEVICE_RESTART) return "device-restart"; if (status == FWUPD_STATUS_DEVICE_WRITE) return "device-write"; if (status == FWUPD_STATUS_DEVICE_READ) return "device-read"; if (status == FWUPD_STATUS_DEVICE_ERASE) return "device-erase"; if (status == FWUPD_STATUS_DEVICE_VERIFY) return "device-verify"; if (status == FWUPD_STATUS_DEVICE_BUSY) return "device-busy"; if (status == FWUPD_STATUS_SCHEDULING) return "scheduling"; if (status == FWUPD_STATUS_DOWNLOADING) return "downloading"; if (status == FWUPD_STATUS_WAITING_FOR_AUTH) return "waiting-for-auth"; if (status == FWUPD_STATUS_SHUTDOWN) return "shutdown"; if (status == FWUPD_STATUS_WAITING_FOR_USER) return "waiting-for-user"; return NULL; } /** * fwupd_status_from_string: * @status: (nullable): a string, e.g. `decompressing` * * Converts a string to an enumerated status. * * Returns: enumerated value * * Since: 0.1.1 **/ FwupdStatus fwupd_status_from_string(const gchar *status) { if (g_strcmp0(status, "unknown") == 0) return FWUPD_STATUS_UNKNOWN; if (g_strcmp0(status, "idle") == 0) return FWUPD_STATUS_IDLE; if (g_strcmp0(status, "decompressing") == 0) return FWUPD_STATUS_DECOMPRESSING; if (g_strcmp0(status, "loading") == 0) return FWUPD_STATUS_LOADING; if (g_strcmp0(status, "device-restart") == 0) return FWUPD_STATUS_DEVICE_RESTART; if (g_strcmp0(status, "device-write") == 0) return FWUPD_STATUS_DEVICE_WRITE; if (g_strcmp0(status, "device-verify") == 0) return FWUPD_STATUS_DEVICE_VERIFY; if (g_strcmp0(status, "scheduling") == 0) return FWUPD_STATUS_SCHEDULING; if (g_strcmp0(status, "downloading") == 0) return FWUPD_STATUS_DOWNLOADING; if (g_strcmp0(status, "device-read") == 0) return FWUPD_STATUS_DEVICE_READ; if (g_strcmp0(status, "device-erase") == 0) return FWUPD_STATUS_DEVICE_ERASE; if (g_strcmp0(status, "device-busy") == 0) return FWUPD_STATUS_DEVICE_BUSY; if (g_strcmp0(status, "waiting-for-auth") == 0) return FWUPD_STATUS_WAITING_FOR_AUTH; if (g_strcmp0(status, "shutdown") == 0) return FWUPD_STATUS_SHUTDOWN; if (g_strcmp0(status, "waiting-for-user") == 0) return FWUPD_STATUS_WAITING_FOR_USER; return FWUPD_STATUS_LAST; } /** * fwupd_device_flag_to_string: * @device_flag: a device flag, e.g. %FWUPD_DEVICE_FLAG_REQUIRE_AC * * Converts a device flag to a string. * * Returns: identifier string * * Since: 0.7.0 **/ const gchar * fwupd_device_flag_to_string(FwupdDeviceFlags device_flag) { if (device_flag == FWUPD_DEVICE_FLAG_NONE) return "none"; if (device_flag == FWUPD_DEVICE_FLAG_INTERNAL) return "internal"; if (device_flag == FWUPD_DEVICE_FLAG_UPDATABLE) return "updatable"; if (device_flag == FWUPD_DEVICE_FLAG_ONLY_OFFLINE) return "only-offline"; if (device_flag == FWUPD_DEVICE_FLAG_REQUIRE_AC) return "require-ac"; if (device_flag == FWUPD_DEVICE_FLAG_LOCKED) return "locked"; if (device_flag == FWUPD_DEVICE_FLAG_SUPPORTED) return "supported"; if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER) return "needs-bootloader"; if (device_flag == FWUPD_DEVICE_FLAG_REGISTERED) return "registered"; if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_REBOOT) return "needs-reboot"; if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN) return "needs-shutdown"; if (device_flag == FWUPD_DEVICE_FLAG_REPORTED) return "reported"; if (device_flag == FWUPD_DEVICE_FLAG_NOTIFIED) return "notified"; if (device_flag == FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION) return "use-runtime-version"; if (device_flag == FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST) return "install-parent-first"; if (device_flag == FWUPD_DEVICE_FLAG_IS_BOOTLOADER) return "is-bootloader"; if (device_flag == FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG) return "wait-for-replug"; if (device_flag == FWUPD_DEVICE_FLAG_IGNORE_VALIDATION) return "ignore-validation"; if (device_flag == FWUPD_DEVICE_FLAG_TRUSTED) return "trusted"; if (device_flag == FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED) return "another-write-required"; if (device_flag == FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS) return "no-auto-instance-ids"; if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION) return "needs-activation"; if (device_flag == FWUPD_DEVICE_FLAG_ENSURE_SEMVER) return "ensure-semver"; if (device_flag == FWUPD_DEVICE_FLAG_HISTORICAL) return "historical"; if (device_flag == FWUPD_DEVICE_FLAG_ONLY_SUPPORTED) return "only-supported"; if (device_flag == FWUPD_DEVICE_FLAG_WILL_DISAPPEAR) return "will-disappear"; if (device_flag == FWUPD_DEVICE_FLAG_CAN_VERIFY) return "can-verify"; if (device_flag == FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE) return "can-verify-image"; if (device_flag == FWUPD_DEVICE_FLAG_DUAL_IMAGE) return "dual-image"; if (device_flag == FWUPD_DEVICE_FLAG_SELF_RECOVERY) return "self-recovery"; if (device_flag == FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE) return "usable-during-update"; if (device_flag == FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED) return "version-check-required"; if (device_flag == FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES) return "install-all-releases"; if (device_flag == FWUPD_DEVICE_FLAG_MD_SET_NAME) return "md-set-name"; if (device_flag == FWUPD_DEVICE_FLAG_MD_SET_NAME_CATEGORY) return "md-set-name-category"; if (device_flag == FWUPD_DEVICE_FLAG_MD_SET_VERFMT) return "md-set-verfmt"; if (device_flag == FWUPD_DEVICE_FLAG_MD_SET_ICON) return "md-set-icon"; if (device_flag == FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS) return "add-counterpart-guids"; if (device_flag == FWUPD_DEVICE_FLAG_NO_GUID_MATCHING) return "no-guid-matching"; if (device_flag == FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN) return "updatable-hidden"; if (device_flag == FWUPD_DEVICE_FLAG_SKIPS_RESTART) return "skips-restart"; if (device_flag == FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES) return "has-multiple-branches"; if (device_flag == FWUPD_DEVICE_FLAG_BACKUP_BEFORE_INSTALL) return "backup-before-install"; if (device_flag == FWUPD_DEVICE_FLAG_WILDCARD_INSTALL) return "wildcard-install"; if (device_flag == FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE) return "only-version-upgrade"; if (device_flag == FWUPD_DEVICE_FLAG_UNREACHABLE) return "unreachable"; if (device_flag == FWUPD_DEVICE_FLAG_AFFECTS_FDE) return "affects-fde"; if (device_flag == FWUPD_DEVICE_FLAG_END_OF_LIFE) return "end-of-life"; if (device_flag == FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD) return "signed-payload"; if (device_flag == FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD) return "unsigned-payload"; if (device_flag == FWUPD_DEVICE_FLAG_EMULATED) return "emulated"; if (device_flag == FWUPD_DEVICE_FLAG_EMULATION_TAG) return "emulation-tag"; if (device_flag == FWUPD_DEVICE_FLAG_ONLY_EXPLICIT_UPDATES) return "only-explicit-updates"; if (device_flag == FWUPD_DEVICE_FLAG_UNKNOWN) return "unknown"; return NULL; } /** * fwupd_device_flag_from_string: * @device_flag: (nullable): a string, e.g. `require-ac` * * Converts a string to an enumerated device flag. * * Returns: enumerated value * * Since: 0.7.0 **/ FwupdDeviceFlags fwupd_device_flag_from_string(const gchar *device_flag) { if (g_strcmp0(device_flag, "none") == 0) return FWUPD_DEVICE_FLAG_NONE; if (g_strcmp0(device_flag, "internal") == 0) return FWUPD_DEVICE_FLAG_INTERNAL; if (g_strcmp0(device_flag, "updatable") == 0 || g_strcmp0(device_flag, "allow-online") == 0) return FWUPD_DEVICE_FLAG_UPDATABLE; if (g_strcmp0(device_flag, "only-offline") == 0 || g_strcmp0(device_flag, "allow-offline") == 0) return FWUPD_DEVICE_FLAG_ONLY_OFFLINE; if (g_strcmp0(device_flag, "require-ac") == 0) return FWUPD_DEVICE_FLAG_REQUIRE_AC; if (g_strcmp0(device_flag, "locked") == 0) return FWUPD_DEVICE_FLAG_LOCKED; if (g_strcmp0(device_flag, "supported") == 0) return FWUPD_DEVICE_FLAG_SUPPORTED; if (g_strcmp0(device_flag, "needs-bootloader") == 0) return FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER; if (g_strcmp0(device_flag, "registered") == 0) return FWUPD_DEVICE_FLAG_REGISTERED; if (g_strcmp0(device_flag, "needs-reboot") == 0) return FWUPD_DEVICE_FLAG_NEEDS_REBOOT; if (g_strcmp0(device_flag, "needs-shutdown") == 0) return FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN; if (g_strcmp0(device_flag, "reported") == 0) return FWUPD_DEVICE_FLAG_REPORTED; if (g_strcmp0(device_flag, "notified") == 0) return FWUPD_DEVICE_FLAG_NOTIFIED; if (g_strcmp0(device_flag, "use-runtime-version") == 0) return FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION; if (g_strcmp0(device_flag, "install-parent-first") == 0) return FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST; if (g_strcmp0(device_flag, "is-bootloader") == 0) return FWUPD_DEVICE_FLAG_IS_BOOTLOADER; if (g_strcmp0(device_flag, "wait-for-replug") == 0) return FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG; if (g_strcmp0(device_flag, "ignore-validation") == 0) return FWUPD_DEVICE_FLAG_IGNORE_VALIDATION; if (g_strcmp0(device_flag, "trusted") == 0) return FWUPD_DEVICE_FLAG_TRUSTED; if (g_strcmp0(device_flag, "another-write-required") == 0) return FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED; if (g_strcmp0(device_flag, "no-auto-instance-ids") == 0) return FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS; if (g_strcmp0(device_flag, "needs-activation") == 0) return FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION; if (g_strcmp0(device_flag, "ensure-semver") == 0) return FWUPD_DEVICE_FLAG_ENSURE_SEMVER; if (g_strcmp0(device_flag, "historical") == 0) return FWUPD_DEVICE_FLAG_HISTORICAL; if (g_strcmp0(device_flag, "only-supported") == 0) return FWUPD_DEVICE_FLAG_ONLY_SUPPORTED; if (g_strcmp0(device_flag, "will-disappear") == 0) return FWUPD_DEVICE_FLAG_WILL_DISAPPEAR; if (g_strcmp0(device_flag, "can-verify") == 0) return FWUPD_DEVICE_FLAG_CAN_VERIFY; if (g_strcmp0(device_flag, "can-verify-image") == 0) return FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE; if (g_strcmp0(device_flag, "dual-image") == 0) return FWUPD_DEVICE_FLAG_DUAL_IMAGE; if (g_strcmp0(device_flag, "self-recovery") == 0) return FWUPD_DEVICE_FLAG_SELF_RECOVERY; if (g_strcmp0(device_flag, "usable-during-update") == 0) return FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE; if (g_strcmp0(device_flag, "version-check-required") == 0) return FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED; if (g_strcmp0(device_flag, "install-all-releases") == 0) return FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES; if (g_strcmp0(device_flag, "md-set-name") == 0) return FWUPD_DEVICE_FLAG_MD_SET_NAME; if (g_strcmp0(device_flag, "md-set-name-category") == 0) return FWUPD_DEVICE_FLAG_MD_SET_NAME_CATEGORY; if (g_strcmp0(device_flag, "md-set-verfmt") == 0) return FWUPD_DEVICE_FLAG_MD_SET_VERFMT; if (g_strcmp0(device_flag, "md-set-icon") == 0) return FWUPD_DEVICE_FLAG_MD_SET_ICON; if (g_strcmp0(device_flag, "add-counterpart-guids") == 0) return FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS; if (g_strcmp0(device_flag, "no-guid-matching") == 0) return FWUPD_DEVICE_FLAG_NO_GUID_MATCHING; if (g_strcmp0(device_flag, "updatable-hidden") == 0) return FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN; if (g_strcmp0(device_flag, "skips-restart") == 0) return FWUPD_DEVICE_FLAG_SKIPS_RESTART; if (g_strcmp0(device_flag, "has-multiple-branches") == 0) return FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES; if (g_strcmp0(device_flag, "backup-before-install") == 0) return FWUPD_DEVICE_FLAG_BACKUP_BEFORE_INSTALL; if (g_strcmp0(device_flag, "wildcard-install") == 0) return FWUPD_DEVICE_FLAG_WILDCARD_INSTALL; if (g_strcmp0(device_flag, "only-version-upgrade") == 0) return FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE; if (g_strcmp0(device_flag, "unreachable") == 0) return FWUPD_DEVICE_FLAG_UNREACHABLE; if (g_strcmp0(device_flag, "affects-fde") == 0) return FWUPD_DEVICE_FLAG_AFFECTS_FDE; if (g_strcmp0(device_flag, "end-of-life") == 0) return FWUPD_DEVICE_FLAG_END_OF_LIFE; if (g_strcmp0(device_flag, "signed-payload") == 0) return FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD; if (g_strcmp0(device_flag, "unsigned-payload") == 0) return FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD; if (g_strcmp0(device_flag, "emulated") == 0) return FWUPD_DEVICE_FLAG_EMULATED; if (g_strcmp0(device_flag, "emulation-tag") == 0) return FWUPD_DEVICE_FLAG_EMULATION_TAG; if (g_strcmp0(device_flag, "only-explicit-updates") == 0) return FWUPD_DEVICE_FLAG_ONLY_EXPLICIT_UPDATES; return FWUPD_DEVICE_FLAG_UNKNOWN; } /** * fwupd_device_problem_to_string: * @device_problem: a device inhibit kind, e.g. %FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW * * Converts a device inhibit kind to a string. * * Returns: identifier string * * Since: 1.8.1 **/ const gchar * fwupd_device_problem_to_string(FwupdDeviceProblem device_problem) { if (device_problem == FWUPD_DEVICE_PROBLEM_NONE) return "none"; if (device_problem == FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW) return "system-power-too-low"; if (device_problem == FWUPD_DEVICE_PROBLEM_UNREACHABLE) return "unreachable"; if (device_problem == FWUPD_DEVICE_PROBLEM_POWER_TOO_LOW) return "power-too-low"; if (device_problem == FWUPD_DEVICE_PROBLEM_UPDATE_PENDING) return "update-pending"; if (device_problem == FWUPD_DEVICE_PROBLEM_REQUIRE_AC_POWER) return "require-ac-power"; if (device_problem == FWUPD_DEVICE_PROBLEM_LID_IS_CLOSED) return "lid-is-closed"; if (device_problem == FWUPD_DEVICE_PROBLEM_IS_EMULATED) return "is-emulated"; if (device_problem == FWUPD_DEVICE_PROBLEM_MISSING_LICENSE) return "missing-license"; if (device_problem == FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT) return "system-inhibit"; if (device_problem == FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS) return "update-in-progress"; if (device_problem == FWUPD_DEVICE_PROBLEM_IN_USE) return "in-use"; if (device_problem == FWUPD_DEVICE_PROBLEM_DISPLAY_REQUIRED) return "display-required"; if (device_problem == FWUPD_DEVICE_PROBLEM_UNKNOWN) return "unknown"; return NULL; } /** * fwupd_device_problem_from_string: * @device_problem: (nullable): a string, e.g. `require-ac` * * Converts a string to a enumerated device inhibit kind. * * Returns: enumerated value * * Since: 1.8.1 **/ FwupdDeviceProblem fwupd_device_problem_from_string(const gchar *device_problem) { if (g_strcmp0(device_problem, "none") == 0) return FWUPD_DEVICE_PROBLEM_NONE; if (g_strcmp0(device_problem, "system-power-too-low") == 0) return FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW; if (g_strcmp0(device_problem, "unreachable") == 0) return FWUPD_DEVICE_PROBLEM_UNREACHABLE; if (g_strcmp0(device_problem, "power-too-low") == 0) return FWUPD_DEVICE_PROBLEM_POWER_TOO_LOW; if (g_strcmp0(device_problem, "update-pending") == 0) return FWUPD_DEVICE_PROBLEM_UPDATE_PENDING; if (g_strcmp0(device_problem, "require-ac-power") == 0) return FWUPD_DEVICE_PROBLEM_REQUIRE_AC_POWER; if (g_strcmp0(device_problem, "lid-is-closed") == 0) return FWUPD_DEVICE_PROBLEM_LID_IS_CLOSED; if (g_strcmp0(device_problem, "is-emulated") == 0) return FWUPD_DEVICE_PROBLEM_IS_EMULATED; if (g_strcmp0(device_problem, "missing-license") == 0) return FWUPD_DEVICE_PROBLEM_MISSING_LICENSE; if (g_strcmp0(device_problem, "system-inhibit") == 0) return FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT; if (g_strcmp0(device_problem, "update-in-progress") == 0) return FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS; if (g_strcmp0(device_problem, "in-use") == 0) return FWUPD_DEVICE_PROBLEM_IN_USE; if (g_strcmp0(device_problem, "display-required") == 0) return FWUPD_DEVICE_PROBLEM_DISPLAY_REQUIRED; return FWUPD_DEVICE_PROBLEM_UNKNOWN; } /** * fwupd_plugin_flag_to_string: * @plugin_flag: plugin flags, e.g. %FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE * * Converts an enumerated plugin flag to a string. * * Returns: identifier string * * Since: 1.5.0 **/ const gchar * fwupd_plugin_flag_to_string(FwupdPluginFlags plugin_flag) { if (plugin_flag == FWUPD_DEVICE_FLAG_NONE) return "none"; if (plugin_flag == FWUPD_PLUGIN_FLAG_DISABLED) return "disabled"; if (plugin_flag == FWUPD_PLUGIN_FLAG_USER_WARNING) return "user-warning"; if (plugin_flag == FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE) return "clear-updatable"; if (plugin_flag == FWUPD_PLUGIN_FLAG_NO_HARDWARE) return "no-hardware"; if (plugin_flag == FWUPD_PLUGIN_FLAG_CAPSULES_UNSUPPORTED) return "capsules-unsupported"; if (plugin_flag == FWUPD_PLUGIN_FLAG_UNLOCK_REQUIRED) return "unlock-required"; if (plugin_flag == FWUPD_PLUGIN_FLAG_EFIVAR_NOT_MOUNTED) return "efivar-not-mounted"; if (plugin_flag == FWUPD_PLUGIN_FLAG_ESP_NOT_FOUND) return "esp-not-found"; if (plugin_flag == FWUPD_PLUGIN_FLAG_ESP_NOT_VALID) return "esp-not-valid"; if (plugin_flag == FWUPD_PLUGIN_FLAG_LEGACY_BIOS) return "legacy-bios"; if (plugin_flag == FWUPD_PLUGIN_FLAG_FAILED_OPEN) return "failed-open"; if (plugin_flag == FWUPD_PLUGIN_FLAG_REQUIRE_HWID) return "require-hwid"; if (plugin_flag == FWUPD_PLUGIN_FLAG_KERNEL_TOO_OLD) return "kernel-too-old"; if (plugin_flag == FWUPD_DEVICE_FLAG_UNKNOWN) return "unknown"; if (plugin_flag == FWUPD_PLUGIN_FLAG_AUTH_REQUIRED) return "auth-required"; if (plugin_flag == FWUPD_PLUGIN_FLAG_SECURE_CONFIG) return "secure-config"; if (plugin_flag == FWUPD_PLUGIN_FLAG_MODULAR) return "modular"; if (plugin_flag == FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY) return "measure-system-integrity"; if (plugin_flag == FWUPD_PLUGIN_FLAG_READY) return "ready"; if (plugin_flag == FWUPD_PLUGIN_FLAG_TEST_ONLY) return "test-only"; return NULL; } /** * fwupd_plugin_flag_from_string: * @plugin_flag: (nullable): a string, e.g. `require-ac` * * Converts a string to an enumerated plugin flag. * * Returns: enumerated value * * Since: 1.5.0 **/ FwupdPluginFlags fwupd_plugin_flag_from_string(const gchar *plugin_flag) { if (g_strcmp0(plugin_flag, "none") == 0) return FWUPD_DEVICE_FLAG_NONE; if (g_strcmp0(plugin_flag, "disabled") == 0) return FWUPD_PLUGIN_FLAG_DISABLED; if (g_strcmp0(plugin_flag, "user-warning") == 0) return FWUPD_PLUGIN_FLAG_USER_WARNING; if (g_strcmp0(plugin_flag, "clear-updatable") == 0) return FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE; if (g_strcmp0(plugin_flag, "no-hardware") == 0) return FWUPD_PLUGIN_FLAG_NO_HARDWARE; if (g_strcmp0(plugin_flag, "capsules-unsupported") == 0) return FWUPD_PLUGIN_FLAG_CAPSULES_UNSUPPORTED; if (g_strcmp0(plugin_flag, "unlock-required") == 0) return FWUPD_PLUGIN_FLAG_UNLOCK_REQUIRED; if (g_strcmp0(plugin_flag, "efivar-not-mounted") == 0) return FWUPD_PLUGIN_FLAG_EFIVAR_NOT_MOUNTED; if (g_strcmp0(plugin_flag, "esp-not-found") == 0) return FWUPD_PLUGIN_FLAG_ESP_NOT_FOUND; if (g_strcmp0(plugin_flag, "esp-not-valid") == 0) return FWUPD_PLUGIN_FLAG_ESP_NOT_VALID; if (g_strcmp0(plugin_flag, "legacy-bios") == 0) return FWUPD_PLUGIN_FLAG_LEGACY_BIOS; if (g_strcmp0(plugin_flag, "failed-open") == 0) return FWUPD_PLUGIN_FLAG_FAILED_OPEN; if (g_strcmp0(plugin_flag, "require-hwid") == 0) return FWUPD_PLUGIN_FLAG_REQUIRE_HWID; if (g_strcmp0(plugin_flag, "kernel-too-old") == 0) return FWUPD_PLUGIN_FLAG_KERNEL_TOO_OLD; if (g_strcmp0(plugin_flag, "auth-required") == 0) return FWUPD_PLUGIN_FLAG_AUTH_REQUIRED; if (g_strcmp0(plugin_flag, "secure-config") == 0) return FWUPD_PLUGIN_FLAG_SECURE_CONFIG; if (g_strcmp0(plugin_flag, "modular") == 0) return FWUPD_PLUGIN_FLAG_MODULAR; if (g_strcmp0(plugin_flag, "measure-system-integrity") == 0) return FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY; if (g_strcmp0(plugin_flag, "ready") == 0) return FWUPD_PLUGIN_FLAG_READY; if (g_strcmp0(plugin_flag, "test-only") == 0) return FWUPD_PLUGIN_FLAG_TEST_ONLY; return FWUPD_DEVICE_FLAG_UNKNOWN; } /** * fwupd_update_state_to_string: * @update_state: the update state, e.g. %FWUPD_UPDATE_STATE_PENDING * * Converts an enumerated update state to a string. * * Returns: identifier string * * Since: 0.7.0 **/ const gchar * fwupd_update_state_to_string(FwupdUpdateState update_state) { if (update_state == FWUPD_UPDATE_STATE_UNKNOWN) return "unknown"; if (update_state == FWUPD_UPDATE_STATE_PENDING) return "pending"; if (update_state == FWUPD_UPDATE_STATE_SUCCESS) return "success"; if (update_state == FWUPD_UPDATE_STATE_FAILED) return "failed"; if (update_state == FWUPD_UPDATE_STATE_FAILED_TRANSIENT) return "failed-transient"; if (update_state == FWUPD_UPDATE_STATE_NEEDS_REBOOT) return "needs-reboot"; return NULL; } /** * fwupd_update_state_from_string: * @update_state: (nullable): a string, e.g. `pending` * * Converts a string to an enumerated update state. * * Returns: enumerated value * * Since: 0.7.0 **/ FwupdUpdateState fwupd_update_state_from_string(const gchar *update_state) { if (g_strcmp0(update_state, "unknown") == 0) return FWUPD_UPDATE_STATE_UNKNOWN; if (g_strcmp0(update_state, "pending") == 0) return FWUPD_UPDATE_STATE_PENDING; if (g_strcmp0(update_state, "success") == 0) return FWUPD_UPDATE_STATE_SUCCESS; if (g_strcmp0(update_state, "failed") == 0) return FWUPD_UPDATE_STATE_FAILED; if (g_strcmp0(update_state, "failed-transient") == 0) return FWUPD_UPDATE_STATE_FAILED_TRANSIENT; if (g_strcmp0(update_state, "needs-reboot") == 0) return FWUPD_UPDATE_STATE_NEEDS_REBOOT; return FWUPD_UPDATE_STATE_UNKNOWN; } /** * fwupd_trust_flag_to_string: * @trust_flag: the trust flags, e.g. %FWUPD_TRUST_FLAG_PAYLOAD * * Converts an enumerated trust flag to a string. * * Returns: identifier string * * Since: 0.7.0 **/ const gchar * fwupd_trust_flag_to_string(FwupdTrustFlags trust_flag) { if (trust_flag == FWUPD_TRUST_FLAG_NONE) return "none"; if (trust_flag == FWUPD_TRUST_FLAG_PAYLOAD) return "payload"; if (trust_flag == FWUPD_TRUST_FLAG_METADATA) return "metadata"; return NULL; } /** * fwupd_trust_flag_from_string: * @trust_flag: (nullable): a string, e.g. `payload` * * Converts a string to an enumerated trust flag. * * Returns: enumerated value * * Since: 0.7.0 **/ FwupdTrustFlags fwupd_trust_flag_from_string(const gchar *trust_flag) { if (g_strcmp0(trust_flag, "none") == 0) return FWUPD_TRUST_FLAG_NONE; if (g_strcmp0(trust_flag, "payload") == 0) return FWUPD_TRUST_FLAG_PAYLOAD; if (g_strcmp0(trust_flag, "metadata") == 0) return FWUPD_TRUST_FLAG_METADATA; return FWUPD_TRUST_FLAG_LAST; } /** * fwupd_feature_flag_to_string: * @feature_flag: a single feature flag, e.g. %FWUPD_FEATURE_FLAG_DETACH_ACTION * * Converts a feature flag to a string. * * Returns: identifier string * * Since: 1.4.5 **/ const gchar * fwupd_feature_flag_to_string(FwupdFeatureFlags feature_flag) { if (feature_flag == FWUPD_FEATURE_FLAG_NONE) return "none"; if (feature_flag == FWUPD_FEATURE_FLAG_CAN_REPORT) return "can-report"; if (feature_flag == FWUPD_FEATURE_FLAG_DETACH_ACTION) return "detach-action"; if (feature_flag == FWUPD_FEATURE_FLAG_UPDATE_ACTION) return "update-action"; if (feature_flag == FWUPD_FEATURE_FLAG_SWITCH_BRANCH) return "switch-branch"; if (feature_flag == FWUPD_FEATURE_FLAG_REQUESTS) return "requests"; if (feature_flag == FWUPD_FEATURE_FLAG_FDE_WARNING) return "fde-warning"; if (feature_flag == FWUPD_FEATURE_FLAG_COMMUNITY_TEXT) return "community-text"; if (feature_flag == FWUPD_FEATURE_FLAG_SHOW_PROBLEMS) return "show-problems"; if (feature_flag == FWUPD_FEATURE_FLAG_ALLOW_AUTHENTICATION) return "allow-authentication"; if (feature_flag == FWUPD_FEATURE_FLAG_REQUESTS_NON_GENERIC) return "requests-non-generic"; return NULL; } /** * fwupd_feature_flag_from_string: * @feature_flag: (nullable): a string, e.g. `detach-action` * * Converts a string to an enumerated feature flag. * * Returns: enumerated value * * Since: 1.4.5 **/ FwupdFeatureFlags fwupd_feature_flag_from_string(const gchar *feature_flag) { if (g_strcmp0(feature_flag, "none") == 0) return FWUPD_FEATURE_FLAG_NONE; if (g_strcmp0(feature_flag, "can-report") == 0) return FWUPD_FEATURE_FLAG_CAN_REPORT; if (g_strcmp0(feature_flag, "detach-action") == 0) return FWUPD_FEATURE_FLAG_DETACH_ACTION; if (g_strcmp0(feature_flag, "update-action") == 0) return FWUPD_FEATURE_FLAG_UPDATE_ACTION; if (g_strcmp0(feature_flag, "switch-branch") == 0) return FWUPD_FEATURE_FLAG_SWITCH_BRANCH; if (g_strcmp0(feature_flag, "requests") == 0) return FWUPD_FEATURE_FLAG_REQUESTS; if (g_strcmp0(feature_flag, "fde-warning") == 0) return FWUPD_FEATURE_FLAG_FDE_WARNING; if (g_strcmp0(feature_flag, "community-text") == 0) return FWUPD_FEATURE_FLAG_COMMUNITY_TEXT; if (g_strcmp0(feature_flag, "show-problems") == 0) return FWUPD_FEATURE_FLAG_SHOW_PROBLEMS; if (g_strcmp0(feature_flag, "allow-authentication") == 0) return FWUPD_FEATURE_FLAG_ALLOW_AUTHENTICATION; if (g_strcmp0(feature_flag, "requests-non-generic") == 0) return FWUPD_FEATURE_FLAG_REQUESTS_NON_GENERIC; return FWUPD_FEATURE_FLAG_LAST; } /** * fwupd_keyring_kind_from_string: * @keyring_kind: (nullable): a string, e.g. `gpg` * * Converts an printable string to an enumerated keyring kind. * * Returns: keyring kind, e.g. %FWUPD_KEYRING_KIND_GPG * * Since: 0.9.7 **/ FwupdKeyringKind fwupd_keyring_kind_from_string(const gchar *keyring_kind) { if (g_strcmp0(keyring_kind, "none") == 0) return FWUPD_KEYRING_KIND_NONE; if (g_strcmp0(keyring_kind, "gpg") == 0) return FWUPD_KEYRING_KIND_GPG; if (g_strcmp0(keyring_kind, "pkcs7") == 0) return FWUPD_KEYRING_KIND_PKCS7; if (g_strcmp0(keyring_kind, "jcat") == 0) return FWUPD_KEYRING_KIND_JCAT; return FWUPD_KEYRING_KIND_UNKNOWN; } /** * fwupd_keyring_kind_to_string: * @keyring_kind: a #FwupdKeyringKind, e.g. %FWUPD_KEYRING_KIND_GPG * * Converts an enumerated keyring kind to a printable string. * * Returns: a string, e.g. `gpg` * * Since: 0.9.7 **/ const gchar * fwupd_keyring_kind_to_string(FwupdKeyringKind keyring_kind) { if (keyring_kind == FWUPD_KEYRING_KIND_NONE) return "none"; if (keyring_kind == FWUPD_KEYRING_KIND_GPG) return "gpg"; if (keyring_kind == FWUPD_KEYRING_KIND_PKCS7) return "pkcs7"; if (keyring_kind == FWUPD_KEYRING_KIND_JCAT) return "jcat"; return NULL; } /** * fwupd_release_flag_to_string: * @release_flag: a release flag, e.g. %FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD * * Converts an enumerated release flag to a string. * * Returns: identifier string * * Since: 1.2.6 **/ const gchar * fwupd_release_flag_to_string(FwupdReleaseFlags release_flag) { if (release_flag == FWUPD_RELEASE_FLAG_NONE) return "none"; if (release_flag == FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD) return "trusted-payload"; if (release_flag == FWUPD_RELEASE_FLAG_TRUSTED_METADATA) return "trusted-metadata"; if (release_flag == FWUPD_RELEASE_FLAG_IS_UPGRADE) return "is-upgrade"; if (release_flag == FWUPD_RELEASE_FLAG_IS_DOWNGRADE) return "is-downgrade"; if (release_flag == FWUPD_RELEASE_FLAG_BLOCKED_VERSION) return "blocked-version"; if (release_flag == FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL) return "blocked-approval"; if (release_flag == FWUPD_RELEASE_FLAG_IS_ALTERNATE_BRANCH) return "is-alternate-branch"; if (release_flag == FWUPD_RELEASE_FLAG_IS_COMMUNITY) return "is-community"; if (release_flag == FWUPD_RELEASE_FLAG_TRUSTED_REPORT) return "trusted-report"; return NULL; } /** * fwupd_release_flag_from_string: * @release_flag: (nullable): a string, e.g. `trusted-payload` * * Converts a string to an enumerated release flag. * * Returns: enumerated value * * Since: 1.2.6 **/ FwupdReleaseFlags fwupd_release_flag_from_string(const gchar *release_flag) { if (g_strcmp0(release_flag, "trusted-payload") == 0) return FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD; if (g_strcmp0(release_flag, "trusted-metadata") == 0) return FWUPD_RELEASE_FLAG_TRUSTED_METADATA; if (g_strcmp0(release_flag, "is-upgrade") == 0) return FWUPD_RELEASE_FLAG_IS_UPGRADE; if (g_strcmp0(release_flag, "is-downgrade") == 0) return FWUPD_RELEASE_FLAG_IS_DOWNGRADE; if (g_strcmp0(release_flag, "blocked-version") == 0) return FWUPD_RELEASE_FLAG_BLOCKED_VERSION; if (g_strcmp0(release_flag, "blocked-approval") == 0) return FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL; if (g_strcmp0(release_flag, "is-alternate-branch") == 0) return FWUPD_RELEASE_FLAG_IS_ALTERNATE_BRANCH; if (g_strcmp0(release_flag, "is-community") == 0) return FWUPD_RELEASE_FLAG_IS_COMMUNITY; if (g_strcmp0(release_flag, "trusted-report") == 0) return FWUPD_RELEASE_FLAG_TRUSTED_REPORT; return FWUPD_RELEASE_FLAG_NONE; } /** * fwupd_release_urgency_to_string: * @release_urgency: a release urgency, e.g. %FWUPD_RELEASE_URGENCY_HIGH * * Converts an enumerated release urgency to a string. * * Returns: identifier string * * Since: 1.4.0 **/ const gchar * fwupd_release_urgency_to_string(FwupdReleaseUrgency release_urgency) { if (release_urgency == FWUPD_RELEASE_URGENCY_LOW) return "low"; if (release_urgency == FWUPD_RELEASE_URGENCY_MEDIUM) return "medium"; if (release_urgency == FWUPD_RELEASE_URGENCY_HIGH) return "high"; if (release_urgency == FWUPD_RELEASE_URGENCY_CRITICAL) return "critical"; return NULL; } /** * fwupd_release_urgency_from_string: * @release_urgency: (nullable): a string, e.g. `low` * * Converts a string to an enumerated release urgency value. * * Returns: enumerated value * * Since: 1.4.0 **/ FwupdReleaseUrgency fwupd_release_urgency_from_string(const gchar *release_urgency) { if (g_strcmp0(release_urgency, "low") == 0) return FWUPD_RELEASE_URGENCY_LOW; if (g_strcmp0(release_urgency, "medium") == 0) return FWUPD_RELEASE_URGENCY_MEDIUM; if (g_strcmp0(release_urgency, "high") == 0) return FWUPD_RELEASE_URGENCY_HIGH; if (g_strcmp0(release_urgency, "critical") == 0) return FWUPD_RELEASE_URGENCY_CRITICAL; return FWUPD_RELEASE_URGENCY_UNKNOWN; } /** * fwupd_version_format_from_string: * @str: (nullable): a string, e.g. `quad` * * Converts text to a display version type. * * Returns: an enumerated version format, e.g. %FWUPD_VERSION_FORMAT_TRIPLET * * Since: 1.2.9 **/ FwupdVersionFormat fwupd_version_format_from_string(const gchar *str) { if (g_strcmp0(str, "plain") == 0) return FWUPD_VERSION_FORMAT_PLAIN; if (g_strcmp0(str, "pair") == 0) return FWUPD_VERSION_FORMAT_PAIR; if (g_strcmp0(str, "number") == 0) return FWUPD_VERSION_FORMAT_NUMBER; if (g_strcmp0(str, "triplet") == 0) return FWUPD_VERSION_FORMAT_TRIPLET; if (g_strcmp0(str, "quad") == 0) return FWUPD_VERSION_FORMAT_QUAD; if (g_strcmp0(str, "bcd") == 0) return FWUPD_VERSION_FORMAT_BCD; if (g_strcmp0(str, "intel-me") == 0) return FWUPD_VERSION_FORMAT_INTEL_ME; if (g_strcmp0(str, "intel-me2") == 0) return FWUPD_VERSION_FORMAT_INTEL_ME2; if (g_strcmp0(str, "surface-legacy") == 0) return FWUPD_VERSION_FORMAT_SURFACE_LEGACY; if (g_strcmp0(str, "surface") == 0) return FWUPD_VERSION_FORMAT_SURFACE; if (g_strcmp0(str, "dell-bios") == 0) return FWUPD_VERSION_FORMAT_DELL_BIOS; if (g_strcmp0(str, "hex") == 0) return FWUPD_VERSION_FORMAT_HEX; return FWUPD_VERSION_FORMAT_UNKNOWN; } /** * fwupd_version_format_to_string: * @kind: a version format, e.g. %FWUPD_VERSION_FORMAT_TRIPLET * * Converts an enumerated version format to text. * * Returns: a string, e.g. `quad`, or %NULL if not known * * Since: 1.2.9 **/ const gchar * fwupd_version_format_to_string(FwupdVersionFormat kind) { if (kind == FWUPD_VERSION_FORMAT_PLAIN) return "plain"; if (kind == FWUPD_VERSION_FORMAT_NUMBER) return "number"; if (kind == FWUPD_VERSION_FORMAT_PAIR) return "pair"; if (kind == FWUPD_VERSION_FORMAT_TRIPLET) return "triplet"; if (kind == FWUPD_VERSION_FORMAT_QUAD) return "quad"; if (kind == FWUPD_VERSION_FORMAT_BCD) return "bcd"; if (kind == FWUPD_VERSION_FORMAT_INTEL_ME) return "intel-me"; if (kind == FWUPD_VERSION_FORMAT_INTEL_ME2) return "intel-me2"; if (kind == FWUPD_VERSION_FORMAT_SURFACE_LEGACY) return "surface-legacy"; if (kind == FWUPD_VERSION_FORMAT_SURFACE) return "surface"; if (kind == FWUPD_VERSION_FORMAT_DELL_BIOS) return "dell-bios"; if (kind == FWUPD_VERSION_FORMAT_HEX) return "hex"; return NULL; } fwupd-1.9.16/libfwupd/fwupd-enums.h000066400000000000000000001015301460375044200172010ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS /** * FwupdStatus: * @FWUPD_STATUS_UNKNOWN: Unknown state * @FWUPD_STATUS_IDLE: Idle * @FWUPD_STATUS_LOADING: Loading a resource * @FWUPD_STATUS_DECOMPRESSING: Decompressing firmware * @FWUPD_STATUS_DEVICE_RESTART: Restarting the device * @FWUPD_STATUS_DEVICE_WRITE: Writing to a device * @FWUPD_STATUS_DEVICE_VERIFY: Verifying (reading) a device * @FWUPD_STATUS_SCHEDULING: Scheduling an offline update * @FWUPD_STATUS_DOWNLOADING: A file is downloading * @FWUPD_STATUS_DEVICE_READ: Reading from a device * @FWUPD_STATUS_DEVICE_ERASE: Erasing a device * @FWUPD_STATUS_WAITING_FOR_AUTH: Waiting for authentication * @FWUPD_STATUS_DEVICE_BUSY: The device is busy * @FWUPD_STATUS_SHUTDOWN: The daemon is shutting down * * The flags to show daemon status. **/ typedef enum { FWUPD_STATUS_UNKNOWN, /* Since: 0.1.1 */ FWUPD_STATUS_IDLE, /* Since: 0.1.1 */ FWUPD_STATUS_LOADING, /* Since: 0.1.1 */ FWUPD_STATUS_DECOMPRESSING, /* Since: 0.1.1 */ FWUPD_STATUS_DEVICE_RESTART, /* Since: 0.1.1 */ FWUPD_STATUS_DEVICE_WRITE, /* Since: 0.1.1 */ FWUPD_STATUS_DEVICE_VERIFY, /* Since: 0.1.1 */ FWUPD_STATUS_SCHEDULING, /* Since: 0.1.1 */ FWUPD_STATUS_DOWNLOADING, /* Since: 0.9.4 */ FWUPD_STATUS_DEVICE_READ, /* Since: 1.0.0 */ FWUPD_STATUS_DEVICE_ERASE, /* Since: 1.0.0 */ FWUPD_STATUS_WAITING_FOR_AUTH, /* Since: 1.0.0 */ FWUPD_STATUS_DEVICE_BUSY, /* Since: 1.0.1 */ FWUPD_STATUS_SHUTDOWN, /* Since: 1.2.1 */ FWUPD_STATUS_WAITING_FOR_USER, /* Since: 1.9.8 */ /*< private >*/ FWUPD_STATUS_LAST } FwupdStatus; /** * FwupdTrustFlags: * @FWUPD_TRUST_FLAG_NONE: No trust * @FWUPD_TRUST_FLAG_PAYLOAD: The firmware is trusted * @FWUPD_TRUST_FLAG_METADATA: The metadata is trusted * * The flags to show the level of trust. **/ typedef enum { FWUPD_TRUST_FLAG_NONE = 0, /* Since: 0.1.2 */ FWUPD_TRUST_FLAG_PAYLOAD = 1 << 0, /* Since: 0.1.2 */ FWUPD_TRUST_FLAG_METADATA = 1 << 1, /* Since: 0.1.2 */ /*< private >*/ FWUPD_TRUST_FLAG_LAST } FwupdTrustFlags; /** * FwupdFeatureFlags: * @FWUPD_FEATURE_FLAG_NONE: No trust * @FWUPD_FEATURE_FLAG_CAN_REPORT: Can upload a report of the update back to the server * @FWUPD_FEATURE_FLAG_DETACH_ACTION: Can perform detach action, typically showing text * @FWUPD_FEATURE_FLAG_UPDATE_ACTION: Can perform update action, typically showing text * @FWUPD_FEATURE_FLAG_SWITCH_BRANCH: Can switch the firmware branch * @FWUPD_FEATURE_FLAG_REQUESTS: Can show interactive requests * @FWUPD_FEATURE_FLAG_FDE_WARNING: Can warn about full disk encryption * @FWUPD_FEATURE_FLAG_COMMUNITY_TEXT: Can show information about community supported * @FWUPD_FEATURE_FLAG_SHOW_PROBLEMS: Can show problems when getting the update list * @FWUPD_FEATURE_FLAG_ALLOW_AUTHENTICATION: Can authenticate with PolicyKit for requests * @FWUPD_FEATURE_FLAG_REQUESTS_NON_GENERIC: Can handle showing non-generic request message text * * The flags to the feature capabilities of the front-end client. **/ typedef enum { FWUPD_FEATURE_FLAG_NONE = 0, /* Since: 1.4.5 */ FWUPD_FEATURE_FLAG_CAN_REPORT = 1 << 0, /* Since: 1.4.5 */ FWUPD_FEATURE_FLAG_DETACH_ACTION = 1 << 1, /* Since: 1.4.5 */ FWUPD_FEATURE_FLAG_UPDATE_ACTION = 1 << 2, /* Since: 1.4.5 */ FWUPD_FEATURE_FLAG_SWITCH_BRANCH = 1 << 3, /* Since: 1.5.0 */ FWUPD_FEATURE_FLAG_REQUESTS = 1 << 4, /* Since: 1.6.2 */ FWUPD_FEATURE_FLAG_FDE_WARNING = 1 << 5, /* Since: 1.7.1 */ FWUPD_FEATURE_FLAG_COMMUNITY_TEXT = 1 << 6, /* Since: 1.7.5 */ FWUPD_FEATURE_FLAG_SHOW_PROBLEMS = 1 << 7, /* Since: 1.8.1 */ FWUPD_FEATURE_FLAG_ALLOW_AUTHENTICATION = 1 << 8, /* Since: 1.8.4 */ FWUPD_FEATURE_FLAG_REQUESTS_NON_GENERIC = 1 << 9, /* Since: 1.9.8 */ /*< private >*/ FWUPD_FEATURE_FLAG_LAST } FwupdFeatureFlags; /** * FWUPD_DEVICE_FLAG_NONE: * * No flags set * * Since 0.1.3 */ #define FWUPD_DEVICE_FLAG_NONE (0ull) /** * FWUPD_DEVICE_FLAG_INTERNAL: * * Device is internal to the platform and cannot be removed easily. * * Since 0.1.3 */ #define FWUPD_DEVICE_FLAG_INTERNAL (1ull << 0) /** * FWUPD_DEVICE_FLAG_UPDATABLE: * * Device has the ability to be updated in this or any other mode. * * Since 0.9.7 */ #define FWUPD_DEVICE_FLAG_UPDATABLE (1ull << 1) /** * FWUPD_DEVICE_FLAG_ONLY_OFFLINE: * * Update can only be done from a limited functionality OS (offline mode). * * Since 0.9.7 */ #define FWUPD_DEVICE_FLAG_ONLY_OFFLINE (1ull << 2) /** * FWUPD_DEVICE_FLAG_REQUIRE_AC: * * Device requires an external power source to be connected or the battery * level at a minimum threshold to update. * * Since 0.6.3 */ #define FWUPD_DEVICE_FLAG_REQUIRE_AC (1ull << 3) /** * FWUPD_DEVICE_FLAG_LOCKED: * * The device can not be updated without manual user interaction. * * Since 0.6.3 */ #define FWUPD_DEVICE_FLAG_LOCKED (1ull << 4) /** * FWUPD_DEVICE_FLAG_SUPPORTED: * * The device is found in metadata loaded into the daemon. * * Since 0.7.1 */ #define FWUPD_DEVICE_FLAG_SUPPORTED (1ull << 5) /** * FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER: * * The device requires entering a bootloader mode to be manually. * * Since 0.7.3 */ #define FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER (1ull << 6) /** * FWUPD_DEVICE_FLAG_REGISTERED: * * The device has been registered with other plugins. * * Since 0.9.7 */ #define FWUPD_DEVICE_FLAG_REGISTERED (1ull << 7) /** * FWUPD_DEVICE_FLAG_NEEDS_REBOOT: * * The device requires a system reboot to apply firmware or to reload hardware. * * Since 0.9.7 */ #define FWUPD_DEVICE_FLAG_NEEDS_REBOOT (1ull << 8) /** * FWUPD_DEVICE_FLAG_REPORTED: * * The success or failure of a previous update has been reported to a metadata server. * * Since: 1.0.4 */ #define FWUPD_DEVICE_FLAG_REPORTED (1ull << 9) /** * FWUPD_DEVICE_FLAG_NOTIFIED: * * The user has been notified about a change in the device state. * * Since: 1.0.5 */ #define FWUPD_DEVICE_FLAG_NOTIFIED (1ull << 10) /** * FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION: * * The device will always display use the runtime version rather than the bootloader version. * * Since: 1.0.6 */ #define FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION (1ull << 11) /** * FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST: * * The composite device requires installation of composite firmware on the parent before the child. * Normally the child is installed before the parent. * * Since: 1.0.8 */ #define FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST (1ull << 12) /** * FWUPD_DEVICE_FLAG_IS_BOOTLOADER: * * The device is currently in a read-only bootloader mode and not running application code. * * Since: 1.0.8 */ #define FWUPD_DEVICE_FLAG_IS_BOOTLOADER (1ull << 13) /** * FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG: * * The device is in the middle of and update and the hardware is waiting to be probed/replugged. * * Since: 1.1.2 */ #define FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG (1ull << 14) /** * FWUPD_DEVICE_FLAG_IGNORE_VALIDATION: * * When processing an update for the device, plugins should ignore all validation safety checks. * * Since: 1.1.2 */ #define FWUPD_DEVICE_FLAG_IGNORE_VALIDATION (1ull << 15) /** * FWUPD_DEVICE_FLAG_TRUSTED: * * A trusted client is reading information about the device. * Extra metadata such as serial number can be exposed about this device. * * Since: 1.1.2 */ #define FWUPD_DEVICE_FLAG_TRUSTED (1ull << 16) /** * FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN: * * The device requires the system to be shutdown to finish application of new firmware. * * Since: 1.2.4 */ #define FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN (1ull << 17) /** * FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED: * * The device requires the update to be retried, possibly with a different plugin. * * Since: 1.2.5 */ #define FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED (1ull << 18) /** * FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS: * * Deprecated, no not use * * Since: 1.2.5 * Deprecated 1.5.5 */ #define FWUPD_DEVICE_FLAG_NO_AUTO_INSTANCE_IDS (1ull << 19) /** * FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION: * * The device update needs to be separately activated. * This process may occur automatically on shutdown in some operating systems * or when the device is unplugged with some devices. * * Since: 1.2.6 */ #define FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION (1ull << 20) /** * FWUPD_DEVICE_FLAG_ENSURE_SEMVER: * * Deprecated, no not use * * Since: 1.2.9 * Deprecate: 1.5.5 */ #define FWUPD_DEVICE_FLAG_ENSURE_SEMVER (1ull << 21) /** * FWUPD_DEVICE_FLAG_HISTORICAL: * * The device is used for historical data only. * * Since: 1.3.2 */ #define FWUPD_DEVICE_FLAG_HISTORICAL (1ull << 22) /** * FWUPD_DEVICE_FLAG_ONLY_SUPPORTED: * * Deprecated, no not use * * Since: 1.3.3 * Deprecated 1.5.5 */ #define FWUPD_DEVICE_FLAG_ONLY_SUPPORTED (1ull << 23) /** * FWUPD_DEVICE_FLAG_WILL_DISAPPEAR: * * The device will disappear after the update is complete and success * or failure can't be verified. * * Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_WILL_DISAPPEAR (1ull << 24) /** * FWUPD_DEVICE_FLAG_CAN_VERIFY: * * The device checksums can be compared against metadata. * * Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_CAN_VERIFY (1ull << 25) /** * FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE: * * The device application firmware image can be dumped from device for verification. * * Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE (1ull << 26) /** * FWUPD_DEVICE_FLAG_DUAL_IMAGE: * * The device firmware update architecture uses a redundancy mechanism such * as A/B partitions for updates. * * Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_DUAL_IMAGE (1ull << 27) /** * FWUPD_DEVICE_FLAG_SELF_RECOVERY: * * In flashing mode, the device will only accept intended payloads and will * revert back to a valid firmware image if an invalid or incomplete payload was sent. * * Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_SELF_RECOVERY (1ull << 28) /** * FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE: * * The device remains usable while the update flashes or schedules the update. * The update will implicitly be applied next time the device is power cycled or possibly activated. * * Since: 1.3.3 */ #define FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE (1ull << 29) /** * FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED: * * All firmware updates for this device require a firmware version check. * * Since: 1.3.7 */ #define FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED (1ull << 30) /** * FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES: * * Install each intermediate releases for the device rather than jumping directly to the newest. * * Since: 1.3.7 */ #define FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES (1ull << 31) /** * FWUPD_DEVICE_FLAG_MD_SET_NAME: * * Deprecated, no not use * * Since: 1.4.0 * Deprecated 1.5.5 */ #define FWUPD_DEVICE_FLAG_MD_SET_NAME (1ull << 32) /** * FWUPD_DEVICE_FLAG_MD_SET_NAME_CATEGORY: * * Deprecated, no not use * * Since: 1.4.0 * Deprecated 1.5.5 */ #define FWUPD_DEVICE_FLAG_MD_SET_NAME_CATEGORY (1ull << 33) /** * FWUPD_DEVICE_FLAG_MD_SET_VERFMT: * * Deprecated, no not use * * Since: 1.4.0 * Deprecated 1.5.5 */ #define FWUPD_DEVICE_FLAG_MD_SET_VERFMT (1ull << 34) /** * FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS: * * The device will add counterpart GUIDs from an alternate mode like bootloader. * This flag is typically specified in a quirk. * * Since: 1.4.0 */ #define FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS (1ull << 35) /** * FWUPD_DEVICE_FLAG_NO_GUID_MATCHING: * * Deprecated, no not use * * Since: 1.4.1 * Deprecated 1.5.8 */ #define FWUPD_DEVICE_FLAG_NO_GUID_MATCHING (1ull << 36) /** * FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN: * * The device is updatable but is currently inhibited from updates in the client. * Reasons include but are not limited to low power or requiring reboot from a previous update. * * Since: 1.4.1 */ #define FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN (1ull << 37) /** * FWUPD_DEVICE_FLAG_SKIPS_RESTART: * * The device relies upon activation or power cycle to load firmware. * * Since: 1.5.0 */ #define FWUPD_DEVICE_FLAG_SKIPS_RESTART (1ull << 38) /** * FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES: * * The device supports switching to a different stream of firmware. * * Since: 1.5.0 */ #define FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES (1ull << 39) /** * FWUPD_DEVICE_FLAG_BACKUP_BEFORE_INSTALL: * * The device firmware should be saved before installing firmware. * * Since: 1.5.0 */ #define FWUPD_DEVICE_FLAG_BACKUP_BEFORE_INSTALL (1ull << 40) /** * FWUPD_DEVICE_FLAG_MD_SET_ICON: * * Deprecated, no not use * * Since: 1.5.2 * Deprecated 1.5.5 */ #define FWUPD_DEVICE_FLAG_MD_SET_ICON (1ull << 41) /** * FWUPD_DEVICE_FLAG_WILDCARD_INSTALL: * * All devices with matching GUIDs will be updated at the same time. * * For some devices it is not possible to have different versions of firmware * for hardware of the same type. Updating one device will force update of * others with exactly the same instance IDs. * * Since: 1.6.2 */ #define FWUPD_DEVICE_FLAG_WILDCARD_INSTALL (1ull << 42) /** * FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE: * * The device firmware can only be updated to a newer version and never downgraded or reinstalled. * * Since 1.6.2 */ #define FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE (1ull << 43) /** * FWUPD_DEVICE_FLAG_UNREACHABLE: * * The device is currently unreachable, perhaps because it is in a lower power state or is out of * wireless range. * * Since 1.7.0 */ #define FWUPD_DEVICE_FLAG_UNREACHABLE (1ull << 44) /** * FWUPD_DEVICE_FLAG_AFFECTS_FDE: * * The device is warning that a volume with full-disk-encryption was found on this machine, * typically a Windows NTFS partition with BitLocker. * Updating the firmware on this device may invalidate secrets used to decrypt the volume, and * the recovery key may be required. * * Supported clients will display this information as a warning to the user. * * Since: 1.7.1 */ #define FWUPD_DEVICE_FLAG_AFFECTS_FDE (1ull << 45) /** * FWUPD_DEVICE_FLAG_END_OF_LIFE: * * The device is no longer supported by the original hardware vendor as it is considered * end-of-life. It it unlikely to receive firmware updates, even for security issues. * * Since: 1.7.5 */ #define FWUPD_DEVICE_FLAG_END_OF_LIFE (1ull << 46) /** * FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD: * * The firmware payload is verified on-device the payload using strong cryptography such * as RSA, AES or ECC. * * It is usually not possible to modify or flash custom firmware not provided by the vendor. * * Since: 1.7.6 */ #define FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD (1ull << 47) /** * FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD: * * The firmware payload is unsigned and it is possible to modify and flash custom firmware. * * Since: 1.7.6 */ #define FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD (1ull << 48) /** * FWUPD_DEVICE_FLAG_EMULATED: * * The device is emulated and should not be recorded by the backend. * * Since: 1.8.11 */ #define FWUPD_DEVICE_FLAG_EMULATED (1ull << 49) /** * FWUPD_DEVICE_FLAG_EMULATION_TAG: * * The device should be recorded by the backend, allowing emulation. * * Since: 1.8.11 */ #define FWUPD_DEVICE_FLAG_EMULATION_TAG (1ull << 50) /** * FWUPD_DEVICE_FLAG_ONLY_EXPLICIT_UPDATES: * * The device should stay on one firmware version unless the new version is explicitly specified. * * This can either be done using `fwupdmgr install`, using GNOME Firmware, or using a BKC config. * * Since: 1.9.3 */ #define FWUPD_DEVICE_FLAG_ONLY_EXPLICIT_UPDATES (1ull << 51) /** * FWUPD_DEVICE_FLAG_UNKNOWN: * * This flag is not defined, this typically will happen from mismatched * fwupd library and clients. * * Since 0.7.3 */ #define FWUPD_DEVICE_FLAG_UNKNOWN G_MAXUINT64 /** * FwupdDeviceFlags: * * Flags used to represent device attributes */ typedef guint64 FwupdDeviceFlags; /** * FWUPD_DEVICE_PROBLEM_NONE: * * No device problems detected. * * Since 1.8.1 */ #define FWUPD_DEVICE_PROBLEM_NONE (0u) /** * FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW: * * The system power is too low to perform the update. * * Since 1.8.1 */ #define FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW (1ull << 0) /** * FWUPD_DEVICE_PROBLEM_UNREACHABLE: * * The device is unreachable, or out of wireless range. * * Since 1.8.1 */ #define FWUPD_DEVICE_PROBLEM_UNREACHABLE (1ull << 1) /** * FWUPD_DEVICE_PROBLEM_POWER_TOO_LOW: * * The device battery power is too low. * * Since 1.8.1 */ #define FWUPD_DEVICE_PROBLEM_POWER_TOO_LOW (1ull << 2) /** * FWUPD_DEVICE_PROBLEM_UPDATE_PENDING: * * The device is waiting for the update to be applied. * * Since 1.8.1 */ #define FWUPD_DEVICE_PROBLEM_UPDATE_PENDING (1ull << 3) /** * FWUPD_DEVICE_PROBLEM_REQUIRE_AC_POWER: * * The device requires AC power to be connected. * * Since 1.8.1 */ #define FWUPD_DEVICE_PROBLEM_REQUIRE_AC_POWER (1ull << 4) /** * FWUPD_DEVICE_PROBLEM_LID_IS_CLOSED: * * The device cannot be used while the laptop lid is closed. * * Since 1.8.1 */ #define FWUPD_DEVICE_PROBLEM_LID_IS_CLOSED (1ull << 5) /** * FWUPD_DEVICE_PROBLEM_IS_EMULATED: * * The device is emulated from a different host. * * Since 1.8.3 */ #define FWUPD_DEVICE_PROBLEM_IS_EMULATED (1ull << 6) /** * FWUPD_DEVICE_PROBLEM_MISSING_LICENSE: * * The device cannot be updated due to missing vendor's license. * * Since 1.8.6 */ #define FWUPD_DEVICE_PROBLEM_MISSING_LICENSE (1ull << 7) /** * FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT: * * The device cannot be updated due to a system-wide inhibit. * * Since 1.8.10 */ #define FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT (1ull << 8) /** * FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS: * * The device cannot be updated as it is already being updated. * * Since 1.8.11 */ #define FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS (1ull << 9) /** * FWUPD_DEVICE_PROBLEM_IN_USE: * * The device is in use and cannot be interrupted, for instance taking a phone call. * * Since 1.9.1 */ #define FWUPD_DEVICE_PROBLEM_IN_USE (1ull << 10) /** * FWUPD_DEVICE_PROBLEM_DISPLAY_REQUIRED: * * The device cannot be used while there are no displays plugged in. * * Since 1.9.6 */ #define FWUPD_DEVICE_PROBLEM_DISPLAY_REQUIRED (1ull << 11) /** * FWUPD_DEVICE_PROBLEM_UNKNOWN: * * This problem is not defined, this typically will happen from mismatched * fwupd library and clients. * * Since 1.8.1 */ #define FWUPD_DEVICE_PROBLEM_UNKNOWN G_MAXUINT64 /** * FwupdDeviceProblem: * * Problems are reasons why the device is not updatable. * * All problems have to be fixable by the user, rather than the plugin author. */ typedef guint64 FwupdDeviceProblem; /** * FWUPD_RELEASE_FLAG_NONE: * * No flags are set. * * Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_NONE (0u) /** * FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD: * * The payload binary is trusted. * * Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD (1ull << 0) /** * FWUPD_RELEASE_FLAG_TRUSTED_METADATA: * * The payload metadata is trusted. * * Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_TRUSTED_METADATA (1ull << 1) /** * FWUPD_RELEASE_FLAG_IS_UPGRADE: * * The release is newer than the device version. * * Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_IS_UPGRADE (1ull << 2) /** * FWUPD_RELEASE_FLAG_IS_DOWNGRADE: * * The release is older than the device version. * * Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_IS_DOWNGRADE (1ull << 3) /** * FWUPD_RELEASE_FLAG_BLOCKED_VERSION: * * The installation of the release is blocked as below device version-lowest. * * Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_BLOCKED_VERSION (1ull << 4) /** * FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL: * * The installation of the release is blocked as release not approved by an administrator. * * Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL (1ull << 5) /** * FWUPD_RELEASE_FLAG_IS_ALTERNATE_BRANCH: * * The release is an alternate branch of firmware. * * Since: 1.5.0 */ #define FWUPD_RELEASE_FLAG_IS_ALTERNATE_BRANCH (1ull << 6) /** * FWUPD_RELEASE_FLAG_IS_COMMUNITY: * * The release is supported by the community and not the hardware vendor. * * Since: 1.7.5 */ #define FWUPD_RELEASE_FLAG_IS_COMMUNITY (1ull << 7) /** * FWUPD_RELEASE_FLAG_TRUSTED_REPORT: * * The payload has been tested by a report we trust. * * Since: 1.9.1 */ #define FWUPD_RELEASE_FLAG_TRUSTED_REPORT (1ull << 8) /** * FWUPD_RELEASE_FLAG_UNKNOWN: * * The release flag is unknown, typically caused by using mismatched client and daemon. * * Since: 1.2.6 */ #define FWUPD_RELEASE_FLAG_UNKNOWN G_MAXUINT64 /** * FwupdReleaseFlags: * * Flags used to represent release attributes */ typedef guint64 FwupdReleaseFlags; /** * FwupdReleaseUrgency: * @FWUPD_RELEASE_URGENCY_UNKNOWN: Unknown * @FWUPD_RELEASE_URGENCY_LOW: Low * @FWUPD_RELEASE_URGENCY_MEDIUM: Medium * @FWUPD_RELEASE_URGENCY_HIGH: High * @FWUPD_RELEASE_URGENCY_CRITICAL: Critical, e.g. a security fix * * The release urgency. **/ typedef enum { FWUPD_RELEASE_URGENCY_UNKNOWN, /* Since: 1.4.0 */ FWUPD_RELEASE_URGENCY_LOW, /* Since: 1.4.0 */ FWUPD_RELEASE_URGENCY_MEDIUM, /* Since: 1.4.0 */ FWUPD_RELEASE_URGENCY_HIGH, /* Since: 1.4.0 */ FWUPD_RELEASE_URGENCY_CRITICAL, /* Since: 1.4.0 */ /*< private >*/ FWUPD_RELEASE_URGENCY_LAST } FwupdReleaseUrgency; /** * FWUPD_PLUGIN_FLAG_NONE: * * No plugin flags are set. * * Since: 1.5.0 */ #define FWUPD_PLUGIN_FLAG_NONE (0u) /** * FWUPD_PLUGIN_FLAG_DISABLED: * * The plugin has been disabled, either by daemon configuration or a problem. * * Since: 1.5.0 */ #define FWUPD_PLUGIN_FLAG_DISABLED (1ull << 0) /** * FWUPD_PLUGIN_FLAG_USER_WARNING: * * The plugin has a problem and would like to show a user warning to a supported client. * * Since: 1.5.0 */ #define FWUPD_PLUGIN_FLAG_USER_WARNING (1ull << 1) /** * FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE: * * When the plugin loads it should clear the UPDATABLE flag from any devices. * This typically happens when the device requires a system restart. * * Since: 1.5.0 */ #define FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE (1ull << 2) /** * FWUPD_PLUGIN_FLAG_NO_HARDWARE: * * The plugin won't load because no supported hardware was found. * This typically happens with plugins designed for a specific platform design * (such as the dell plugin only works on Dell systems). * * Since: 1.5.0 */ #define FWUPD_PLUGIN_FLAG_NO_HARDWARE (1ull << 3) /** * FWUPD_PLUGIN_FLAG_CAPSULES_UNSUPPORTED: * * The plugin discovered that UEFI UpdateCapsule are unsupported. * Supported clients will display this information to a user. * * Since: 1.5.0 */ #define FWUPD_PLUGIN_FLAG_CAPSULES_UNSUPPORTED (1ull << 4) /** * FWUPD_PLUGIN_FLAG_UNLOCK_REQUIRED: * * The plugin discovered that hardware unlock is required. * Supported clients will display this information to a user. * * Since: 1.5.0 */ #define FWUPD_PLUGIN_FLAG_UNLOCK_REQUIRED (1ull << 5) /** * FWUPD_PLUGIN_FLAG_EFIVAR_NOT_MOUNTED: * * The plugin discovered the efivar filesystem is not found and is required for this plugin. * Supported clients will display this information to a user. * * Since: 1.5.0 */ #define FWUPD_PLUGIN_FLAG_EFIVAR_NOT_MOUNTED (1ull << 6) /** * FWUPD_PLUGIN_FLAG_ESP_NOT_FOUND: * * The plugins discovered that the EFI system partition was not found. * Supported clients will display this information to a user. * * Since: 1.5.0 */ #define FWUPD_PLUGIN_FLAG_ESP_NOT_FOUND (1ull << 7) /** * FWUPD_PLUGIN_FLAG_LEGACY_BIOS: * * The plugin discovered the system is running in legacy CSM mode. * Supported clients will display this information to a user. * * Since: 1.5.0 */ #define FWUPD_PLUGIN_FLAG_LEGACY_BIOS (1ull << 8) /** * FWUPD_PLUGIN_FLAG_FAILED_OPEN: * * Failed to open plugin (missing dependency). * Supported clients will display this information to a user. * * Since: 1.5.0 */ #define FWUPD_PLUGIN_FLAG_FAILED_OPEN (1ull << 9) /** * FWUPD_PLUGIN_FLAG_REQUIRE_HWID: * * A specific HWID is required to use this plugin. * * Since: 1.5.8 */ #define FWUPD_PLUGIN_FLAG_REQUIRE_HWID (1ull << 10) /** * FWUPD_PLUGIN_FLAG_KERNEL_TOO_OLD: * * The feature is not supported as the kernel is too old. * * Since: 1.6.2 */ #define FWUPD_PLUGIN_FLAG_KERNEL_TOO_OLD (1ull << 11) /** * FWUPD_PLUGIN_FLAG_AUTH_REQUIRED: * * The plugin requires the user to provide authentication details. * Supported clients will display this information to a user. * * Since: 1.6.2 */ #define FWUPD_PLUGIN_FLAG_AUTH_REQUIRED (1ull << 12) /** * FWUPD_PLUGIN_FLAG_SECURE_CONFIG: * * The plugin requires the config file to be saved with permissions that only allow the root user * to read. * * Since: 1.8.5 */ #define FWUPD_PLUGIN_FLAG_SECURE_CONFIG (1ull << 13) /** * FWUPD_PLUGIN_FLAG_MODULAR: * * The plugin is loaded from an external module. * * Since: 1.8.6 */ #define FWUPD_PLUGIN_FLAG_MODULAR (1ull << 14) /** * FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY: * * The plugin will be checked that it preserves system state such as `KEK`, `PK`, `BOOT####` etc. * * Since: 1.8.7 */ #define FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY (1ull << 15) /** * FWUPD_PLUGIN_FLAG_ESP_NOT_VALID: * * The plugins discovered that the EFI system partition may not be valid. * Supported clients will display this information to a user. * * Since: 1.9.3 */ #define FWUPD_PLUGIN_FLAG_ESP_NOT_VALID (1ull << 16) /** * FWUPD_PLUGIN_FLAG_READY: * * The plugin is ready for use and all devices have been coldplugged. * * Since: 1.9.6 */ #define FWUPD_PLUGIN_FLAG_READY (1ull << 17) /** * FWUPD_PLUGIN_FLAG_UNKNOWN: * * The plugin flag is Unknown. * This is usually caused by a mismatched libfwupdplugin and daemon. * * Since: 1.5.0 */ #define FWUPD_PLUGIN_FLAG_TEST_ONLY (1ull << 18) /** * FWUPD_PLUGIN_FLAG_TEST_ONLY: * * The plugin is used for virtual devices that exercising daemon flows. * * Since: 1.9.13 */ #define FWUPD_PLUGIN_FLAG_UNKNOWN G_MAXUINT64 /** * FwupdPluginFlags: * * Flags used to represent plugin attributes */ typedef guint64 FwupdPluginFlags; /** * FwupdInstallFlags: * @FWUPD_INSTALL_FLAG_NONE: No flags set * @FWUPD_INSTALL_FLAG_OFFLINE: Schedule this for next boot * @FWUPD_INSTALL_FLAG_ALLOW_REINSTALL: Allow reinstalling the same version * @FWUPD_INSTALL_FLAG_ALLOW_OLDER: Allow downgrading firmware * @FWUPD_INSTALL_FLAG_FORCE: Force the update even if not a good idea * @FWUPD_INSTALL_FLAG_NO_HISTORY: Do not write to the history database * @FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH: Allow firmware branch switching * @FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM: Ignore firmware CRCs and checksums * @FWUPD_INSTALL_FLAG_IGNORE_VID_PID: Ignore firmware vendor and project checks * @FWUPD_INSTALL_FLAG_IGNORE_POWER: Ignore requirement of external power source *(Deprecated since 1.7.0) * @FWUPD_INSTALL_FLAG_NO_SEARCH: Do not use heuristics when parsing the image * * Flags to set when performing the firmware update or install. **/ typedef enum { FWUPD_INSTALL_FLAG_NONE = 0, /* Since: 0.7.0 */ FWUPD_INSTALL_FLAG_OFFLINE = 1 << 0, /* Since: 0.7.0 */ FWUPD_INSTALL_FLAG_ALLOW_REINSTALL = 1 << 1, /* Since: 0.7.0 */ FWUPD_INSTALL_FLAG_ALLOW_OLDER = 1 << 2, /* Since: 0.7.0 */ FWUPD_INSTALL_FLAG_FORCE = 1 << 3, /* Since: 0.7.1 */ FWUPD_INSTALL_FLAG_NO_HISTORY = 1 << 4, /* Since: 1.0.8 */ FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH = 1 << 5, /* Since: 1.5.0 */ FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM = 1 << 6, /* Since: 1.5.0 */ FWUPD_INSTALL_FLAG_IGNORE_VID_PID = 1 << 7, /* Since: 1.5.0 */ FWUPD_INSTALL_FLAG_IGNORE_POWER = 1 << 8, /* Since: 1.5.0 */ FWUPD_INSTALL_FLAG_NO_SEARCH = 1 << 9, /* Since: 1.5.0 */ /*< private >*/ FWUPD_INSTALL_FLAG_LAST } FwupdInstallFlags; /** * FwupdSelfSignFlags: * @FWUPD_SELF_SIGN_FLAG_NONE: No flags set * @FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP: Add the timestamp to the detached signature * @FWUPD_SELF_SIGN_FLAG_ADD_CERT: Add the certificate to the detached signature * * Flags to set when performing the firmware update or install. **/ typedef enum { FWUPD_SELF_SIGN_FLAG_NONE = 0, /* Since: 1.2.6 */ FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP = 1 << 0, /* Since: 1.2.6 */ FWUPD_SELF_SIGN_FLAG_ADD_CERT = 1 << 1, /* Since: 1.2.6 */ /*< private >*/ FWUPD_SELF_SIGN_FLAG_LAST } FwupdSelfSignFlags; /** * FwupdUpdateState: * @FWUPD_UPDATE_STATE_UNKNOWN: Unknown * @FWUPD_UPDATE_STATE_PENDING: Update is pending * @FWUPD_UPDATE_STATE_SUCCESS: Update was successful * @FWUPD_UPDATE_STATE_FAILED: Update failed * @FWUPD_UPDATE_STATE_NEEDS_REBOOT: Waiting for a reboot to apply * @FWUPD_UPDATE_STATE_FAILED_TRANSIENT: Update failed due to transient issue, e.g. AC power *required * * The update state. **/ typedef enum { FWUPD_UPDATE_STATE_UNKNOWN, /* Since: 0.7.0 */ FWUPD_UPDATE_STATE_PENDING, /* Since: 0.7.0 */ FWUPD_UPDATE_STATE_SUCCESS, /* Since: 0.7.0 */ FWUPD_UPDATE_STATE_FAILED, /* Since: 0.7.0 */ FWUPD_UPDATE_STATE_NEEDS_REBOOT, /* Since: 1.0.4 */ FWUPD_UPDATE_STATE_FAILED_TRANSIENT, /* Since: 1.2.7 */ /*< private >*/ FWUPD_UPDATE_STATE_LAST } FwupdUpdateState; /** * FwupdKeyringKind: * @FWUPD_KEYRING_KIND_UNKNOWN: Unknown * @FWUPD_KEYRING_KIND_NONE: No verification * @FWUPD_KEYRING_KIND_GPG: Verification using GPG * @FWUPD_KEYRING_KIND_PKCS7: Verification using PKCS7 * @FWUPD_KEYRING_KIND_JCAT: Verification using Jcat * * Type of keyring used on a remote. **/ typedef enum { FWUPD_KEYRING_KIND_UNKNOWN, /* Since: 0.9.7 */ FWUPD_KEYRING_KIND_NONE, /* Since: 0.9.7 */ FWUPD_KEYRING_KIND_GPG, /* Since: 0.9.7 */ FWUPD_KEYRING_KIND_PKCS7, /* Since: 0.9.7 */ FWUPD_KEYRING_KIND_JCAT, /* Since: 1.4.0 */ /*< private >*/ FWUPD_KEYRING_KIND_LAST } FwupdKeyringKind; /** * FwupdVersionFormat: * @FWUPD_VERSION_FORMAT_UNKNOWN: Unknown version format * @FWUPD_VERSION_FORMAT_PLAIN: An unidentified format text string * @FWUPD_VERSION_FORMAT_NUMBER: A single integer version number * @FWUPD_VERSION_FORMAT_PAIR: Two AABB.CCDD version numbers * @FWUPD_VERSION_FORMAT_TRIPLET: Microsoft-style AA.BB.CCDD version numbers * @FWUPD_VERSION_FORMAT_QUAD: UEFI-style AA.BB.CC.DD version numbers * @FWUPD_VERSION_FORMAT_BCD: Binary coded decimal notation * @FWUPD_VERSION_FORMAT_INTEL_ME: Intel ME-style bitshifted notation * @FWUPD_VERSION_FORMAT_INTEL_ME2: Intel ME-style A.B.CC.DDDD notation notation * @FWUPD_VERSION_FORMAT_SURFACE_LEGACY: Legacy Microsoft Surface 10b.12b.10b * @FWUPD_VERSION_FORMAT_SURFACE: Microsoft Surface 8b.16b.8b * @FWUPD_VERSION_FORMAT_DELL_BIOS: Dell BIOS BB.CC.DD style * @FWUPD_VERSION_FORMAT_HEX: Hexadecimal 0xAABCCDD style * * The flags used when parsing version numbers. * * If no verification is required then %FWUPD_VERSION_FORMAT_PLAIN should * be used to signify an unparsable text string. **/ typedef enum { FWUPD_VERSION_FORMAT_UNKNOWN, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_PLAIN, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_NUMBER, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_PAIR, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_TRIPLET, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_QUAD, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_BCD, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_INTEL_ME, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_INTEL_ME2, /* Since: 1.2.9 */ FWUPD_VERSION_FORMAT_SURFACE_LEGACY, /* Since: 1.3.4 */ FWUPD_VERSION_FORMAT_SURFACE, /* Since: 1.3.4 */ FWUPD_VERSION_FORMAT_DELL_BIOS, /* Since: 1.3.6 */ FWUPD_VERSION_FORMAT_HEX, /* Since: 1.4.0 */ /*< private >*/ FWUPD_VERSION_FORMAT_LAST } FwupdVersionFormat; /** * FWUPD_BATTERY_LEVEL_INVALID: * * This value signifies the battery level is either unset, or the value cannot * be discovered. */ #define FWUPD_BATTERY_LEVEL_INVALID 101 const gchar * fwupd_status_to_string(FwupdStatus status); FwupdStatus fwupd_status_from_string(const gchar *status); const gchar * fwupd_device_flag_to_string(FwupdDeviceFlags device_flag); FwupdDeviceFlags fwupd_device_flag_from_string(const gchar *device_flag); const gchar * fwupd_device_problem_to_string(FwupdDeviceProblem device_problem); FwupdDeviceProblem fwupd_device_problem_from_string(const gchar *device_problem); const gchar * fwupd_plugin_flag_to_string(FwupdPluginFlags plugin_flag); FwupdPluginFlags fwupd_plugin_flag_from_string(const gchar *plugin_flag); const gchar * fwupd_release_flag_to_string(FwupdReleaseFlags release_flag); FwupdReleaseFlags fwupd_release_flag_from_string(const gchar *release_flag); const gchar * fwupd_release_urgency_to_string(FwupdReleaseUrgency release_urgency); FwupdReleaseUrgency fwupd_release_urgency_from_string(const gchar *release_urgency); const gchar * fwupd_update_state_to_string(FwupdUpdateState update_state); FwupdUpdateState fwupd_update_state_from_string(const gchar *update_state); const gchar * fwupd_trust_flag_to_string(FwupdTrustFlags trust_flag); FwupdTrustFlags fwupd_trust_flag_from_string(const gchar *trust_flag); const gchar * fwupd_feature_flag_to_string(FwupdFeatureFlags feature_flag); FwupdFeatureFlags fwupd_feature_flag_from_string(const gchar *feature_flag); FwupdKeyringKind fwupd_keyring_kind_from_string(const gchar *keyring_kind); const gchar * fwupd_keyring_kind_to_string(FwupdKeyringKind keyring_kind); FwupdVersionFormat fwupd_version_format_from_string(const gchar *str); const gchar * fwupd_version_format_to_string(FwupdVersionFormat kind); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-error.c000066400000000000000000000107261460375044200172040ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fwupd-common.h" #include "fwupd-enums.h" #include "fwupd-error.h" /** * fwupd_error_to_string: * @error: an enumerated error, e.g. %FWUPD_ERROR_VERSION_NEWER * * Converts an enumerated error to a string. * * Returns: identifier string * * Since: 0.7.0 **/ const gchar * fwupd_error_to_string(FwupdError error) { if (error == FWUPD_ERROR_INTERNAL) return FWUPD_DBUS_INTERFACE ".Internal"; if (error == FWUPD_ERROR_VERSION_NEWER) return FWUPD_DBUS_INTERFACE ".VersionNewer"; if (error == FWUPD_ERROR_VERSION_SAME) return FWUPD_DBUS_INTERFACE ".VersionSame"; if (error == FWUPD_ERROR_ALREADY_PENDING) return FWUPD_DBUS_INTERFACE ".AlreadyPending"; if (error == FWUPD_ERROR_AUTH_FAILED) return FWUPD_DBUS_INTERFACE ".AuthFailed"; if (error == FWUPD_ERROR_READ) return FWUPD_DBUS_INTERFACE ".Read"; if (error == FWUPD_ERROR_WRITE) return FWUPD_DBUS_INTERFACE ".Write"; if (error == FWUPD_ERROR_INVALID_FILE) return FWUPD_DBUS_INTERFACE ".InvalidFile"; if (error == FWUPD_ERROR_NOT_FOUND) return FWUPD_DBUS_INTERFACE ".NotFound"; if (error == FWUPD_ERROR_NOTHING_TO_DO) return FWUPD_DBUS_INTERFACE ".NothingToDo"; if (error == FWUPD_ERROR_NOT_SUPPORTED) return FWUPD_DBUS_INTERFACE ".NotSupported"; if (error == FWUPD_ERROR_SIGNATURE_INVALID) return FWUPD_DBUS_INTERFACE ".SignatureInvalid"; if (error == FWUPD_ERROR_AC_POWER_REQUIRED) return FWUPD_DBUS_INTERFACE ".AcPowerRequired"; if (error == FWUPD_ERROR_PERMISSION_DENIED) return FWUPD_DBUS_INTERFACE ".PermissionDenied"; if (error == FWUPD_ERROR_BROKEN_SYSTEM) return FWUPD_DBUS_INTERFACE ".BrokenSystem"; if (error == FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW) return FWUPD_DBUS_INTERFACE ".BatteryLevelTooLow"; if (error == FWUPD_ERROR_NEEDS_USER_ACTION) return FWUPD_DBUS_INTERFACE ".NeedsUserAction"; if (error == FWUPD_ERROR_AUTH_EXPIRED) return FWUPD_DBUS_INTERFACE ".AuthExpired"; return NULL; } /** * fwupd_error_from_string: * @error: (nullable): a string, e.g. `org.freedesktop.fwupd.VersionNewer` * * Converts a string to an enumerated error. * * Returns: enumerated value * * Since: 0.7.0 **/ FwupdError fwupd_error_from_string(const gchar *error) { if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".Internal") == 0) return FWUPD_ERROR_INTERNAL; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".VersionNewer") == 0) return FWUPD_ERROR_VERSION_NEWER; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".VersionSame") == 0) return FWUPD_ERROR_VERSION_SAME; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".AlreadyPending") == 0) return FWUPD_ERROR_ALREADY_PENDING; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".AuthFailed") == 0) return FWUPD_ERROR_AUTH_FAILED; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".Read") == 0) return FWUPD_ERROR_READ; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".Write") == 0) return FWUPD_ERROR_WRITE; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".InvalidFile") == 0) return FWUPD_ERROR_INVALID_FILE; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".NotFound") == 0) return FWUPD_ERROR_NOT_FOUND; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".NothingToDo") == 0) return FWUPD_ERROR_NOTHING_TO_DO; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".NotSupported") == 0) return FWUPD_ERROR_NOT_SUPPORTED; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".SignatureInvalid") == 0) return FWUPD_ERROR_SIGNATURE_INVALID; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".AcPowerRequired") == 0) return FWUPD_ERROR_AC_POWER_REQUIRED; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".PermissionDenied") == 0) return FWUPD_ERROR_PERMISSION_DENIED; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".BrokenSystem") == 0) return FWUPD_ERROR_BROKEN_SYSTEM; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".BatteryLevelTooLow") == 0) return FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".NeedsUserAction") == 0) return FWUPD_ERROR_NEEDS_USER_ACTION; if (g_strcmp0(error, FWUPD_DBUS_INTERFACE ".AuthExpired") == 0) return FWUPD_ERROR_AUTH_EXPIRED; return FWUPD_ERROR_LAST; } /** * fwupd_error_quark: * * An error quark. * * Returns: an error quark * * Since: 0.1.1 **/ GQuark fwupd_error_quark(void) { static GQuark quark = 0; if (!quark) { quark = g_quark_from_static_string("FwupdError"); for (gint i = 0; i < FWUPD_ERROR_LAST; i++) { g_dbus_error_register_error(quark, i, fwupd_error_to_string(i)); } } return quark; } fwupd-1.9.16/libfwupd/fwupd-error.h000066400000000000000000000046651460375044200172160ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FWUPD_ERROR fwupd_error_quark() /** * FwupdError: * @FWUPD_ERROR_INTERNAL: Internal error * @FWUPD_ERROR_VERSION_NEWER: Installed newer firmware version * @FWUPD_ERROR_VERSION_SAME: Installed same firmware version * @FWUPD_ERROR_ALREADY_PENDING: Already set to be installed offline * @FWUPD_ERROR_AUTH_FAILED: Failed to get authentication * @FWUPD_ERROR_READ: Failed to read from device * @FWUPD_ERROR_WRITE: Failed to write to the device * @FWUPD_ERROR_INVALID_FILE: Invalid file format * @FWUPD_ERROR_NOT_FOUND: No matching device exists * @FWUPD_ERROR_NOTHING_TO_DO: Nothing to do * @FWUPD_ERROR_NOT_SUPPORTED: Action was not possible * @FWUPD_ERROR_SIGNATURE_INVALID: Signature was invalid * @FWUPD_ERROR_AC_POWER_REQUIRED: AC power was required * @FWUPD_ERROR_PERMISSION_DENIED: Permission was denied * @FWUPD_ERROR_BROKEN_SYSTEM: User has configured their system in a broken way * @FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW: The system battery level is too low * @FWUPD_ERROR_NEEDS_USER_ACTION: User needs to do an action to complete the update * @FWUPD_ERROR_AUTH_EXPIRED: Failed to get auth as credentials have expired * * The error code. **/ typedef enum { FWUPD_ERROR_INTERNAL, /* Since: 0.1.1 */ FWUPD_ERROR_VERSION_NEWER, /* Since: 0.1.1 */ FWUPD_ERROR_VERSION_SAME, /* Since: 0.1.1 */ FWUPD_ERROR_ALREADY_PENDING, /* Since: 0.1.1 */ FWUPD_ERROR_AUTH_FAILED, /* Since: 0.1.1 */ FWUPD_ERROR_READ, /* Since: 0.1.1 */ FWUPD_ERROR_WRITE, /* Since: 0.1.1 */ FWUPD_ERROR_INVALID_FILE, /* Since: 0.1.1 */ FWUPD_ERROR_NOT_FOUND, /* Since: 0.1.1 */ FWUPD_ERROR_NOTHING_TO_DO, /* Since: 0.1.1 */ FWUPD_ERROR_NOT_SUPPORTED, /* Since: 0.1.1 */ FWUPD_ERROR_SIGNATURE_INVALID, /* Since: 0.1.2 */ FWUPD_ERROR_AC_POWER_REQUIRED, /* Since: 0.8.0 */ FWUPD_ERROR_PERMISSION_DENIED, /* Since: 0.9.8 */ FWUPD_ERROR_BROKEN_SYSTEM, /* Since: 1.2.8 */ FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW, /* Since: 1.2.10 */ FWUPD_ERROR_NEEDS_USER_ACTION, /* Since: 1.3.3 */ FWUPD_ERROR_AUTH_EXPIRED, /* Since: 1.7.5 */ /*< private >*/ FWUPD_ERROR_LAST } FwupdError; GQuark fwupd_error_quark(void); const gchar * fwupd_error_to_string(FwupdError error); FwupdError fwupd_error_from_string(const gchar *error); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-plugin-private.h000066400000000000000000000005671460375044200210300ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-plugin.h" G_BEGIN_DECLS GVariant * fwupd_plugin_to_variant(FwupdPlugin *self) G_GNUC_NON_NULL(1); void fwupd_plugin_to_json(FwupdPlugin *self, JsonBuilder *builder) G_GNUC_NON_NULL(1, 2); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-plugin.c000066400000000000000000000254521460375044200173530ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fwupd-common-private.h" #include "fwupd-enums-private.h" #include "fwupd-plugin-private.h" /** * FwupdPlugin: * * A plugin which is used by fwupd to enumerate and update devices. * * See also: [class@FwupdRelease] */ static void fwupd_plugin_finalize(GObject *object); typedef struct { gchar *name; guint64 flags; } FwupdPluginPrivate; enum { PROP_0, PROP_NAME, PROP_FLAGS, PROP_LAST }; G_DEFINE_TYPE_WITH_PRIVATE(FwupdPlugin, fwupd_plugin, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_plugin_get_instance_private(o)) /** * fwupd_plugin_get_name: * @self: a #FwupdPlugin * * Gets the plugin name. * * Returns: the plugin name, or %NULL if unset * * Since: 1.5.0 **/ const gchar * fwupd_plugin_get_name(FwupdPlugin *self) { FwupdPluginPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_PLUGIN(self), NULL); return priv->name; } /** * fwupd_plugin_set_name: * @self: a #FwupdPlugin * @name: the plugin name, e.g. `bios` * * Sets the plugin name. * * Since: 1.5.0 **/ void fwupd_plugin_set_name(FwupdPlugin *self, const gchar *name) { FwupdPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_PLUGIN(self)); g_return_if_fail(name != NULL); /* not changed */ if (g_strcmp0(priv->name, name) == 0) return; g_free(priv->name); priv->name = g_strdup(name); g_object_notify(G_OBJECT(self), "name"); } /** * fwupd_plugin_get_flags: * @self: a #FwupdPlugin * * Gets the plugin flags. * * Returns: plugin flags, or 0 if unset * * Since: 1.5.0 **/ guint64 fwupd_plugin_get_flags(FwupdPlugin *self) { FwupdPluginPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_PLUGIN(self), 0); return priv->flags; } /** * fwupd_plugin_set_flags: * @self: a #FwupdPlugin * @flags: plugin flags, e.g. %FWUPD_PLUGIN_FLAG_CAPSULES_UNSUPPORTED * * Sets the plugin flags. * * Since: 1.5.0 **/ void fwupd_plugin_set_flags(FwupdPlugin *self, guint64 flags) { FwupdPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_PLUGIN(self)); if (priv->flags == flags) return; priv->flags = flags; g_object_notify(G_OBJECT(self), "flags"); } /** * fwupd_plugin_add_flag: * @self: a #FwupdPlugin * @flag: the #FwupdPluginFlags * * Adds a specific plugin flag to the plugin. * * Since: 1.5.0 **/ void fwupd_plugin_add_flag(FwupdPlugin *self, FwupdPluginFlags flag) { FwupdPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_PLUGIN(self)); if (flag == 0) return; if ((priv->flags & flag) > 0) return; priv->flags |= flag; g_object_notify(G_OBJECT(self), "flags"); } /** * fwupd_plugin_remove_flag: * @self: a #FwupdPlugin * @flag: a plugin flag * * Removes a specific plugin flag from the plugin. * * Since: 1.5.0 **/ void fwupd_plugin_remove_flag(FwupdPlugin *self, FwupdPluginFlags flag) { FwupdPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_PLUGIN(self)); if (flag == 0) return; if ((priv->flags & flag) == 0) return; priv->flags &= ~flag; g_object_notify(G_OBJECT(self), "flags"); } /** * fwupd_plugin_has_flag: * @self: a #FwupdPlugin * @flag: a plugin flag * * Finds if the plugin has a specific plugin flag. * * Returns: %TRUE if the flag is set * * Since: 1.5.0 **/ gboolean fwupd_plugin_has_flag(FwupdPlugin *self, FwupdPluginFlags flag) { FwupdPluginPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_PLUGIN(self), FALSE); return (priv->flags & flag) > 0; } /** * fwupd_plugin_to_variant: * @self: a #FwupdPlugin * * Serialize the plugin data omitting sensitive fields * * Returns: the serialized data, or %NULL for error * * Since: 1.5.0 **/ GVariant * fwupd_plugin_to_variant(FwupdPlugin *self) { FwupdPluginPrivate *priv = GET_PRIVATE(self); GVariantBuilder builder; g_return_val_if_fail(FWUPD_IS_PLUGIN(self), NULL); /* create an array with all the metadata in */ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); if (priv->name != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_NAME, g_variant_new_string(priv->name)); } if (priv->flags > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_FLAGS, g_variant_new_uint64(priv->flags)); } return g_variant_new("a{sv}", &builder); } static void fwupd_plugin_from_key_value(FwupdPlugin *self, const gchar *key, GVariant *value) { if (g_strcmp0(key, FWUPD_RESULT_KEY_NAME) == 0) { fwupd_plugin_set_name(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_FLAGS) == 0) { fwupd_plugin_set_flags(self, g_variant_get_uint64(value)); return; } } static void fwupd_pad_kv_dfl(GString *str, const gchar *key, guint64 plugin_flags) { g_autoptr(GString) tmp = g_string_new(""); for (guint i = 0; i < 64; i++) { if ((plugin_flags & ((guint64)1 << i)) == 0) continue; g_string_append_printf(tmp, "%s|", fwupd_plugin_flag_to_string((guint64)1 << i)); } if (tmp->len == 0) { g_string_append(tmp, fwupd_plugin_flag_to_string(0)); } else { g_string_truncate(tmp, tmp->len - 1); } fwupd_pad_kv_str(str, key, tmp->str); } /** * fwupd_plugin_to_json: * @self: a #FwupdPlugin * @builder: a JSON builder * * Adds a fwupd plugin to a JSON builder * * Since: 1.5.0 **/ void fwupd_plugin_to_json(FwupdPlugin *self, JsonBuilder *builder) { FwupdPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_PLUGIN(self)); g_return_if_fail(builder != NULL); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_NAME, priv->name); if (priv->flags != FWUPD_PLUGIN_FLAG_NONE) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_FLAGS); json_builder_begin_array(builder); for (guint i = 0; i < 64; i++) { const gchar *tmp; if ((priv->flags & ((guint64)1 << i)) == 0) continue; tmp = fwupd_plugin_flag_to_string((guint64)1 << i); json_builder_add_string_value(builder, tmp); } json_builder_end_array(builder); } } /** * fwupd_plugin_to_string: * @self: a #FwupdPlugin * * Builds a text representation of the object. * * Returns: text, or %NULL for invalid * * Since: 1.5.0 **/ gchar * fwupd_plugin_to_string(FwupdPlugin *self) { FwupdPluginPrivate *priv = GET_PRIVATE(self); GString *str; g_return_val_if_fail(FWUPD_IS_PLUGIN(self), NULL); str = g_string_new(NULL); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_NAME, priv->name); fwupd_pad_kv_dfl(str, FWUPD_RESULT_KEY_FLAGS, priv->flags); return g_string_free(str, FALSE); } static void fwupd_plugin_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FwupdPlugin *self = FWUPD_PLUGIN(object); FwupdPluginPrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_NAME: g_value_set_string(value, priv->name); break; case PROP_FLAGS: g_value_set_uint64(value, priv->flags); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fwupd_plugin_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FwupdPlugin *self = FWUPD_PLUGIN(object); switch (prop_id) { case PROP_NAME: fwupd_plugin_set_name(self, g_value_get_string(value)); break; case PROP_FLAGS: fwupd_plugin_set_flags(self, g_value_get_uint64(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fwupd_plugin_class_init(FwupdPluginClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->finalize = fwupd_plugin_finalize; object_class->get_property = fwupd_plugin_get_property; object_class->set_property = fwupd_plugin_set_property; /** * FwupdPlugin:name: * * The plugin name. * * Since: 1.5.0 */ pspec = g_param_spec_string("name", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_NAME, pspec); /** * FwupdPlugin:flags: * * The plugin flags. * * Since: 1.5.0 */ pspec = g_param_spec_uint64("flags", NULL, NULL, FWUPD_PLUGIN_FLAG_NONE, FWUPD_PLUGIN_FLAG_UNKNOWN, FWUPD_PLUGIN_FLAG_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FLAGS, pspec); } static void fwupd_plugin_init(FwupdPlugin *self) { } static void fwupd_plugin_finalize(GObject *object) { FwupdPlugin *self = FWUPD_PLUGIN(object); FwupdPluginPrivate *priv = GET_PRIVATE(self); g_free(priv->name); G_OBJECT_CLASS(fwupd_plugin_parent_class)->finalize(object); } static void fwupd_plugin_set_from_variant_iter(FwupdPlugin *self, GVariantIter *iter) { GVariant *value; const gchar *key; while (g_variant_iter_next(iter, "{&sv}", &key, &value)) { fwupd_plugin_from_key_value(self, key, value); g_variant_unref(value); } } /** * fwupd_plugin_from_variant: * @value: (not nullable): the serialized data * * Creates a new plugin using serialized data. * * Returns: (transfer full): a new #FwupdPlugin, or %NULL if @value was invalid * * Since: 1.5.0 **/ FwupdPlugin * fwupd_plugin_from_variant(GVariant *value) { FwupdPlugin *self = NULL; const gchar *type_string; g_autoptr(GVariantIter) iter = NULL; g_return_val_if_fail(value != NULL, NULL); /* format from GetDetails */ type_string = g_variant_get_type_string(value); if (g_strcmp0(type_string, "(a{sv})") == 0) { self = fwupd_plugin_new(); g_variant_get(value, "(a{sv})", &iter); fwupd_plugin_set_from_variant_iter(self, iter); } else if (g_strcmp0(type_string, "a{sv}") == 0) { self = fwupd_plugin_new(); g_variant_get(value, "a{sv}", &iter); fwupd_plugin_set_from_variant_iter(self, iter); } else { g_warning("type %s not known", type_string); } return self; } /** * fwupd_plugin_array_from_variant: * @value: (not nullable): the serialized data * * Creates an array of new plugins using serialized data. * * Returns: (transfer container) (element-type FwupdPlugin): plugins, or %NULL if @value was invalid * * Since: 1.5.0 **/ GPtrArray * fwupd_plugin_array_from_variant(GVariant *value) { GPtrArray *array = NULL; gsize sz; g_autoptr(GVariant) untuple = NULL; g_return_val_if_fail(value != NULL, NULL); array = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); untuple = g_variant_get_child_value(value, 0); sz = g_variant_n_children(untuple); for (guint i = 0; i < sz; i++) { FwupdPlugin *self; g_autoptr(GVariant) data = NULL; data = g_variant_get_child_value(untuple, i); self = fwupd_plugin_from_variant(data); if (self == NULL) continue; g_ptr_array_add(array, self); } return array; } /** * fwupd_plugin_new: * * Creates a new plugin. * * Returns: a new #FwupdPlugin * * Since: 1.5.0 **/ FwupdPlugin * fwupd_plugin_new(void) { FwupdPlugin *self; self = g_object_new(FWUPD_TYPE_PLUGIN, NULL); return FWUPD_PLUGIN(self); } fwupd-1.9.16/libfwupd/fwupd-plugin.h000066400000000000000000000027621460375044200173570ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-enums.h" G_BEGIN_DECLS #define FWUPD_TYPE_PLUGIN (fwupd_plugin_get_type()) G_DECLARE_DERIVABLE_TYPE(FwupdPlugin, fwupd_plugin, FWUPD, PLUGIN, GObject) struct _FwupdPluginClass { GObjectClass parent_class; /*< private >*/ void (*_fwupd_reserved1)(void); void (*_fwupd_reserved2)(void); void (*_fwupd_reserved3)(void); void (*_fwupd_reserved4)(void); void (*_fwupd_reserved5)(void); void (*_fwupd_reserved6)(void); void (*_fwupd_reserved7)(void); }; FwupdPlugin * fwupd_plugin_new(void); gchar * fwupd_plugin_to_string(FwupdPlugin *self) G_GNUC_NON_NULL(1); const gchar * fwupd_plugin_get_name(FwupdPlugin *self) G_GNUC_NON_NULL(1); void fwupd_plugin_set_name(FwupdPlugin *self, const gchar *name) G_GNUC_NON_NULL(1); guint64 fwupd_plugin_get_flags(FwupdPlugin *self) G_GNUC_NON_NULL(1); void fwupd_plugin_set_flags(FwupdPlugin *self, guint64 flags) G_GNUC_NON_NULL(1); void fwupd_plugin_add_flag(FwupdPlugin *self, FwupdPluginFlags flag) G_GNUC_NON_NULL(1); void fwupd_plugin_remove_flag(FwupdPlugin *self, FwupdPluginFlags flag) G_GNUC_NON_NULL(1); gboolean fwupd_plugin_has_flag(FwupdPlugin *self, FwupdPluginFlags flag) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); FwupdPlugin * fwupd_plugin_from_variant(GVariant *value) G_GNUC_NON_NULL(1); GPtrArray * fwupd_plugin_array_from_variant(GVariant *value) G_GNUC_NON_NULL(1); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-release-private.h000066400000000000000000000007641460375044200211510ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-build.h" #include "fwupd-release.h" G_BEGIN_DECLS GVariant * fwupd_release_to_variant(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_to_json(FwupdRelease *self, JsonBuilder *builder) G_GNUC_NON_NULL(1, 2); void fwupd_release_incorporate(FwupdRelease *self, FwupdRelease *donor) G_GNUC_NON_NULL(1, 2); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-release.c000066400000000000000000002021521460375044200174670ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fwupd-common-private.h" #include "fwupd-enums-private.h" #include "fwupd-error.h" #include "fwupd-release-private.h" #include "fwupd-report-private.h" /** * FwupdRelease: * * A firmware release with a specific version. * * Devices can have more than one release, and the releases are typically * ordered by their version. * * See also: [class@FwupdDevice] */ static void fwupd_release_finalize(GObject *object); typedef struct { GPtrArray *checksums; GPtrArray *tags; GPtrArray *categories; GPtrArray *issues; GHashTable *metadata; gchar *description; gchar *filename; gchar *protocol; gchar *homepage; gchar *details_url; gchar *source_url; gchar *appstream_id; gchar *id; gchar *detach_caption; gchar *detach_image; gchar *license; gchar *name; gchar *name_variant_suffix; gchar *summary; gchar *branch; GPtrArray *locations; gchar *vendor; gchar *version; gchar *remote_id; guint64 size; guint64 created; guint32 install_duration; FwupdReleaseFlags flags; FwupdReleaseUrgency urgency; gchar *update_message; gchar *update_image; GPtrArray *reports; } FwupdReleasePrivate; enum { PROP_0, PROP_REMOTE_ID, PROP_LAST }; G_DEFINE_TYPE_WITH_PRIVATE(FwupdRelease, fwupd_release, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_release_get_instance_private(o)) /* the deprecated fwupd_release_get_trust_flags() function should only * return the last two bits of the #FwupdReleaseFlags */ #define FWUPD_RELEASE_TRUST_FLAGS_MASK 0x3 /** * fwupd_release_get_remote_id: * @self: a #FwupdRelease * * Gets the remote ID that can be used for downloading. * * Returns: the ID, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_remote_id(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->remote_id; } /** * fwupd_release_set_remote_id: * @self: a #FwupdRelease * @remote_id: the release ID, e.g. `USB:foo` * * Sets the remote ID that can be used for downloading. * * Since: 0.9.3 **/ void fwupd_release_set_remote_id(FwupdRelease *self, const gchar *remote_id) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->remote_id, remote_id) == 0) return; g_free(priv->remote_id); priv->remote_id = g_strdup(remote_id); g_object_notify(G_OBJECT(self), "remote-id"); } /** * fwupd_release_get_version: * @self: a #FwupdRelease * * Gets the update version. * * Returns: the update version, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_version(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->version; } /** * fwupd_release_set_version: * @self: a #FwupdRelease * @version: (nullable): the update version, e.g. `1.2.4` * * Sets the update version. * * Since: 0.9.3 **/ void fwupd_release_set_version(FwupdRelease *self, const gchar *version) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->version, version) == 0) return; g_free(priv->version); priv->version = g_strdup(version); } /** * fwupd_release_get_filename: * @self: a #FwupdRelease * * Gets the update filename. * * Returns: the update filename, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_filename(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->filename; } /** * fwupd_release_set_filename: * @self: a #FwupdRelease * @filename: (nullable): the update filename on disk * * Sets the update filename. * * Since: 0.9.3 **/ void fwupd_release_set_filename(FwupdRelease *self, const gchar *filename) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->filename, filename) == 0) return; g_free(priv->filename); priv->filename = g_strdup(filename); } /** * fwupd_release_get_update_message: * @self: a #FwupdRelease * * Gets the update message. * * Returns: the update message, or %NULL if unset * * Since: 1.2.4 **/ const gchar * fwupd_release_get_update_message(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->update_message; } /** * fwupd_release_set_update_message: * @self: a #FwupdRelease * @update_message: (nullable): the update message string * * Sets the update message. * * Since: 1.2.4 **/ void fwupd_release_set_update_message(FwupdRelease *self, const gchar *update_message) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->update_message, update_message) == 0) return; g_free(priv->update_message); priv->update_message = g_strdup(update_message); } /** * fwupd_release_get_update_image: * @self: a #FwupdRelease * * Gets the update image. * * Returns: the update image URL, or %NULL if unset * * Since: 1.4.5 **/ const gchar * fwupd_release_get_update_image(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->update_image; } /** * fwupd_release_set_update_image: * @self: a #FwupdRelease * @update_image: (nullable): the update image URL * * Sets the update image. * * Since: 1.4.5 **/ void fwupd_release_set_update_image(FwupdRelease *self, const gchar *update_image) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->update_image, update_image) == 0) return; g_free(priv->update_image); priv->update_image = g_strdup(update_image); } /** * fwupd_release_get_protocol: * @self: a #FwupdRelease * * Gets the update protocol. * * Returns: the update protocol, or %NULL if unset * * Since: 1.2.2 **/ const gchar * fwupd_release_get_protocol(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->protocol; } /** * fwupd_release_set_protocol: * @self: a #FwupdRelease * @protocol: (nullable): the update protocol, e.g. `org.usb.dfu` * * Sets the update protocol. * * Since: 1.2.2 **/ void fwupd_release_set_protocol(FwupdRelease *self, const gchar *protocol) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->protocol, protocol) == 0) return; g_free(priv->protocol); priv->protocol = g_strdup(protocol); } /** * fwupd_release_get_issues: * @self: a #FwupdRelease * * Gets the list of issues fixed in this release. * * Returns: (element-type utf8) (transfer none): the issues, which may be empty * * Since: 1.3.2 **/ GPtrArray * fwupd_release_get_issues(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->issues; } /** * fwupd_release_add_issue: * @self: a #FwupdRelease * @issue: (not nullable): the update issue, e.g. `CVE-2019-12345` * * Adds an resolved issue to this release. * * Since: 1.3.2 **/ void fwupd_release_add_issue(FwupdRelease *self, const gchar *issue) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); g_return_if_fail(issue != NULL); for (guint i = 0; i < priv->issues->len; i++) { const gchar *issue_tmp = g_ptr_array_index(priv->issues, i); if (g_strcmp0(issue_tmp, issue) == 0) return; } g_ptr_array_add(priv->issues, g_strdup(issue)); } /** * fwupd_release_get_categories: * @self: a #FwupdRelease * * Gets the release categories. * * Returns: (element-type utf8) (transfer none): the categories, which may be empty * * Since: 1.2.7 **/ GPtrArray * fwupd_release_get_categories(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->categories; } /** * fwupd_release_add_category: * @self: a #FwupdRelease * @category: (not nullable): the update category, e.g. `X-EmbeddedController` * * Adds the update category. * * Since: 1.2.7 **/ void fwupd_release_add_category(FwupdRelease *self, const gchar *category) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); g_return_if_fail(category != NULL); for (guint i = 0; i < priv->categories->len; i++) { const gchar *category_tmp = g_ptr_array_index(priv->categories, i); if (g_strcmp0(category_tmp, category) == 0) return; } g_ptr_array_add(priv->categories, g_strdup(category)); } /** * fwupd_release_has_category: * @self: a #FwupdRelease * @category: (not nullable): the update category, e.g. `X-EmbeddedController` * * Finds out if the release has the update category. * * Returns: %TRUE if the release matches * * Since: 1.2.7 **/ gboolean fwupd_release_has_category(FwupdRelease *self, const gchar *category) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), FALSE); g_return_val_if_fail(category != NULL, FALSE); for (guint i = 0; i < priv->categories->len; i++) { const gchar *category_tmp = g_ptr_array_index(priv->categories, i); if (g_strcmp0(category_tmp, category) == 0) return TRUE; } return FALSE; } /** * fwupd_release_get_checksums: * @self: a #FwupdRelease * * Gets the release container checksums. * * Returns: (element-type utf8) (transfer none): the checksums, which may be empty * * Since: 0.9.3 **/ GPtrArray * fwupd_release_get_checksums(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->checksums; } /** * fwupd_release_add_checksum: * @self: a #FwupdRelease * @checksum: (not nullable): the update container checksum * * Sets the update checksum. * * Since: 0.9.3 **/ void fwupd_release_add_checksum(FwupdRelease *self, const gchar *checksum) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); g_return_if_fail(checksum != NULL); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum_tmp = g_ptr_array_index(priv->checksums, i); if (g_strcmp0(checksum_tmp, checksum) == 0) return; } g_ptr_array_add(priv->checksums, g_strdup(checksum)); } /** * fwupd_release_has_checksum: * @self: a #FwupdRelease * @checksum: (not nullable): the update checksum * * Finds out if the release has the update container checksum. * * Returns: %TRUE if the release matches * * Since: 1.2.6 **/ gboolean fwupd_release_has_checksum(FwupdRelease *self, const gchar *checksum) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), FALSE); g_return_val_if_fail(checksum != NULL, FALSE); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum_tmp = g_ptr_array_index(priv->checksums, i); if (g_strcmp0(checksum_tmp, checksum) == 0) return TRUE; } return FALSE; } /** * fwupd_release_get_tags: * @self: a #FwupdRelease * * Gets the release tags. * * Returns: (element-type utf8) (transfer none): the tags, which may be empty * * Since: 1.7.3 **/ GPtrArray * fwupd_release_get_tags(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->tags; } /** * fwupd_release_add_tag: * @self: a #FwupdRelease * @tag: (not nullable): the update tag, e.g. `vendor-factory-2021q1` * * Adds a specific release tag. * * Since: 1.7.3 **/ void fwupd_release_add_tag(FwupdRelease *self, const gchar *tag) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); g_return_if_fail(tag != NULL); for (guint i = 0; i < priv->tags->len; i++) { const gchar *tag_tmp = g_ptr_array_index(priv->tags, i); if (g_strcmp0(tag_tmp, tag) == 0) return; } g_ptr_array_add(priv->tags, g_strdup(tag)); } /** * fwupd_release_has_tag: * @self: a #FwupdRelease * @tag: (not nullable): the update tag, e.g. `vendor-factory-2021q1` * * Finds out if the release has a specific tag. * * Returns: %TRUE if the release matches * * Since: 1.7.3 **/ gboolean fwupd_release_has_tag(FwupdRelease *self, const gchar *tag) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), FALSE); g_return_val_if_fail(tag != NULL, FALSE); for (guint i = 0; i < priv->tags->len; i++) { const gchar *tag_tmp = g_ptr_array_index(priv->tags, i); if (g_strcmp0(tag_tmp, tag) == 0) return TRUE; } return FALSE; } /** * fwupd_release_get_metadata: * @self: a #FwupdRelease * * Gets the release metadata. * * Returns: (transfer none): the metadata, which may be empty * * Since: 1.0.4 **/ GHashTable * fwupd_release_get_metadata(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->metadata; } /** * fwupd_release_add_metadata_item: * @self: a #FwupdRelease * @key: (not nullable): the key * @value: (not nullable): the value * * Sets a release metadata item. * * Since: 1.0.4 **/ void fwupd_release_add_metadata_item(FwupdRelease *self, const gchar *key, const gchar *value) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); g_return_if_fail(key != NULL); g_return_if_fail(value != NULL); g_hash_table_insert(priv->metadata, g_strdup(key), g_strdup(value)); } /** * fwupd_release_add_metadata: * @self: a #FwupdRelease * @hash: (not nullable): the key-values * * Sets multiple release metadata items. * * Since: 1.0.4 **/ void fwupd_release_add_metadata(FwupdRelease *self, GHashTable *hash) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_autoptr(GList) keys = NULL; g_return_if_fail(FWUPD_IS_RELEASE(self)); g_return_if_fail(hash != NULL); /* deep copy the whole map */ keys = g_hash_table_get_keys(hash); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup(hash, key); g_hash_table_insert(priv->metadata, g_strdup(key), g_strdup(value)); } } /** * fwupd_release_get_metadata_item: * @self: a #FwupdRelease * @key: (not nullable): the key * * Gets a release metadata item. * * Returns: the value, or %NULL if unset * * Since: 1.0.4 **/ const gchar * fwupd_release_get_metadata_item(FwupdRelease *self, const gchar *key) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); g_return_val_if_fail(key != NULL, NULL); return g_hash_table_lookup(priv->metadata, key); } /** * fwupd_release_get_uri: * @self: a #FwupdRelease * * Gets the default update URI. * * Returns: the update URI, or %NULL if unset * * Since: 0.9.3 * Deprecated: 1.5.6: Use fwupd_release_get_locations() instead. **/ const gchar * fwupd_release_get_uri(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); if (priv->locations->len == 0) return NULL; return (const gchar *)g_ptr_array_index(priv->locations, 0); } /** * fwupd_release_set_uri: * @self: a #FwupdRelease * @uri: the update URI * * Sets the update URI, i.e. where you can download the firmware from. * * Since: 0.9.3 * Deprecated: 1.5.6: Use fwupd_release_add_location() instead. **/ void fwupd_release_set_uri(FwupdRelease *self, const gchar *uri) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); g_ptr_array_set_size(priv->locations, 0); g_ptr_array_add(priv->locations, g_strdup(uri)); } /** * fwupd_release_get_locations: * @self: a #FwupdRelease * * Gets the update URI, i.e. where you can download the firmware from. * * Typically the first URI will be the main HTTP mirror, but all URIs may not * be valid HTTP URIs. For example, "ipns://QmSrPmba" is valid here. * * Returns: (element-type utf8) (transfer none): the URIs * * Since: 1.5.6 **/ GPtrArray * fwupd_release_get_locations(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->locations; } /** * fwupd_release_add_location: * @self: a #FwupdRelease * @location: (not nullable): the update URI * * Adds an update URI, i.e. where you can download the firmware from. * * Since: 1.5.6 **/ void fwupd_release_add_location(FwupdRelease *self, const gchar *location) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); g_return_if_fail(location != NULL); for (guint i = 0; i < priv->locations->len; i++) { const gchar *location_tmp = g_ptr_array_index(priv->locations, i); if (g_strcmp0(location_tmp, location) == 0) return; } g_ptr_array_add(priv->locations, g_strdup(location)); } /** * fwupd_release_get_homepage: * @self: a #FwupdRelease * * Gets the update homepage. * * Returns: the update homepage, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_homepage(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->homepage; } /** * fwupd_release_set_homepage: * @self: a #FwupdRelease * @homepage: (nullable): the URL * * Sets the update homepage URL. * * Since: 0.9.3 **/ void fwupd_release_set_homepage(FwupdRelease *self, const gchar *homepage) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->homepage, homepage) == 0) return; g_free(priv->homepage); priv->homepage = g_strdup(homepage); } /** * fwupd_release_get_details_url: * @self: a #FwupdRelease * * Gets the URL for the online update notes. * * Returns: the update URL, or %NULL if unset * * Since: 1.2.4 **/ const gchar * fwupd_release_get_details_url(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->details_url; } /** * fwupd_release_set_details_url: * @self: a #FwupdRelease * @details_url: (nullable): the URL * * Sets the URL for the online update notes. * * Since: 1.2.4 **/ void fwupd_release_set_details_url(FwupdRelease *self, const gchar *details_url) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->details_url, details_url) == 0) return; g_free(priv->details_url); priv->details_url = g_strdup(details_url); } /** * fwupd_release_get_source_url: * @self: a #FwupdRelease * * Gets the URL of the source code used to build this release. * * Returns: the update source_url, or %NULL if unset * * Since: 1.2.4 **/ const gchar * fwupd_release_get_source_url(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->source_url; } /** * fwupd_release_set_source_url: * @self: a #FwupdRelease * @source_url: (nullable): the URL * * Sets the URL of the source code used to build this release. * * Since: 1.2.4 **/ void fwupd_release_set_source_url(FwupdRelease *self, const gchar *source_url) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->source_url, source_url) == 0) return; g_free(priv->source_url); priv->source_url = g_strdup(source_url); } /** * fwupd_release_get_description: * @self: a #FwupdRelease * * Gets the update description in AppStream markup format. * * Returns: the update description, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_description(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->description; } /** * fwupd_release_set_description: * @self: a #FwupdRelease * @description: (nullable): the update description in AppStream markup format * * Sets the update description. * * Since: 0.9.3 **/ void fwupd_release_set_description(FwupdRelease *self, const gchar *description) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->description, description) == 0) return; g_free(priv->description); priv->description = g_strdup(description); } /** * fwupd_release_get_appstream_id: * @self: a #FwupdRelease * * Gets the AppStream ID. * * Returns: the AppStream ID, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_appstream_id(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->appstream_id; } /** * fwupd_release_set_appstream_id: * @self: a #FwupdRelease * @appstream_id: (nullable): the AppStream component ID, e.g. `org.hughski.ColorHug2.firmware` * * Sets the AppStream ID. * * Since: 0.9.3 **/ void fwupd_release_set_appstream_id(FwupdRelease *self, const gchar *appstream_id) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->appstream_id, appstream_id) == 0) return; g_free(priv->appstream_id); priv->appstream_id = g_strdup(appstream_id); } /** * fwupd_release_get_id: * @self: a #FwupdRelease * * Gets the release ID, which allows identifying the specific uploaded component. * * Returns: the ID, or %NULL if unset * * Since: 1.7.2 **/ const gchar * fwupd_release_get_id(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->id; } /** * fwupd_release_set_id: * @self: a #FwupdRelease * @id: (nullable): the AppStream component ID, e.g. `component:1234` * * Sets the ID, which allows identifying the specific uploaded component. * * Since: 1.7.2 **/ void fwupd_release_set_id(FwupdRelease *self, const gchar *id) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->id, id) == 0) return; g_free(priv->id); priv->id = g_strdup(id); } /** * fwupd_release_get_detach_caption: * @self: a #FwupdRelease * * Gets the optional text caption used to manually detach the device. * * Returns: the string caption, or %NULL if unset * * Since: 1.3.3 **/ const gchar * fwupd_release_get_detach_caption(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->detach_caption; } /** * fwupd_release_set_detach_caption: * @self: a #FwupdRelease * @detach_caption: (nullable): string caption * * Sets the optional text caption used to manually detach the device. * * Since: 1.3.3 **/ void fwupd_release_set_detach_caption(FwupdRelease *self, const gchar *detach_caption) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->detach_caption, detach_caption) == 0) return; g_free(priv->detach_caption); priv->detach_caption = g_strdup(detach_caption); } /** * fwupd_release_get_detach_image: * @self: a #FwupdRelease * * Gets the optional image used to manually detach the device. * * Returns: the URI, or %NULL if unset * * Since: 1.3.3 **/ const gchar * fwupd_release_get_detach_image(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->detach_image; } /** * fwupd_release_set_detach_image: * @self: a #FwupdRelease * @detach_image: (nullable): a fully qualified URI * * Sets the optional image used to manually detach the device. * * Since: 1.3.3 **/ void fwupd_release_set_detach_image(FwupdRelease *self, const gchar *detach_image) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->detach_image, detach_image) == 0) return; g_free(priv->detach_image); priv->detach_image = g_strdup(detach_image); } /** * fwupd_release_get_size: * @self: a #FwupdRelease * * Gets the update size. * * Returns: the update size in bytes, or 0 if unset * * Since: 0.9.3 **/ guint64 fwupd_release_get_size(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), 0); return priv->size; } /** * fwupd_release_set_size: * @self: a #FwupdRelease * @size: the update size in bytes * * Sets the update size. * * Since: 0.9.3 **/ void fwupd_release_set_size(FwupdRelease *self, guint64 size) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); priv->size = size; } /** * fwupd_release_get_created: * @self: a #FwupdRelease * * Gets when the update was created. * * Returns: UTC timestamp in UNIX format, or 0 if unset * * Since: 1.4.0 **/ guint64 fwupd_release_get_created(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), 0); return priv->created; } /** * fwupd_release_set_created: * @self: a #FwupdRelease * @created: UTC timestamp in UNIX format * * Sets when the update was created. * * Since: 1.4.0 **/ void fwupd_release_set_created(FwupdRelease *self, guint64 created) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); priv->created = created; } /** * fwupd_release_get_summary: * @self: a #FwupdRelease * * Gets the update summary. * * Returns: the update summary, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_summary(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->summary; } /** * fwupd_release_set_summary: * @self: a #FwupdRelease * @summary: (nullable): the update one line summary * * Sets the update summary. * * Since: 0.9.3 **/ void fwupd_release_set_summary(FwupdRelease *self, const gchar *summary) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->summary, summary) == 0) return; g_free(priv->summary); priv->summary = g_strdup(summary); } /** * fwupd_release_get_branch: * @self: a #FwupdRelease * * Gets the update branch. * * Returns: the alternate branch, or %NULL if unset * * Since: 1.5.0 **/ const gchar * fwupd_release_get_branch(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->branch; } /** * fwupd_release_set_branch: * @self: a #FwupdRelease * @branch: (nullable): the update one line branch * * Sets the alternate branch. * * Since: 1.5.0 **/ void fwupd_release_set_branch(FwupdRelease *self, const gchar *branch) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->branch, branch) == 0) return; g_free(priv->branch); priv->branch = g_strdup(branch); } /** * fwupd_release_get_vendor: * @self: a #FwupdRelease * * Gets the update vendor. * * Returns: the update vendor, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_vendor(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->vendor; } /** * fwupd_release_set_vendor: * @self: a #FwupdRelease * @vendor: (nullable): the vendor name, e.g. `Hughski Limited` * * Sets the update vendor. * * Since: 0.9.3 **/ void fwupd_release_set_vendor(FwupdRelease *self, const gchar *vendor) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->vendor, vendor) == 0) return; g_free(priv->vendor); priv->vendor = g_strdup(vendor); } /** * fwupd_release_get_license: * @self: a #FwupdRelease * * Gets the update license. * * Returns: the update license, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_license(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->license; } /** * fwupd_release_set_license: * @self: a #FwupdRelease * @license: (nullable): the update license. * * Sets the update license. * * Since: 0.9.3 **/ void fwupd_release_set_license(FwupdRelease *self, const gchar *license) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->license, license) == 0) return; g_free(priv->license); priv->license = g_strdup(license); } /** * fwupd_release_get_name: * @self: a #FwupdRelease * * Gets the update name. * * Returns: the update name, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_release_get_name(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->name; } /** * fwupd_release_set_name: * @self: a #FwupdRelease * @name: (nullable): the update name. * * Sets the update name. * * Since: 0.9.3 **/ void fwupd_release_set_name(FwupdRelease *self, const gchar *name) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->name, name) == 0) return; g_free(priv->name); priv->name = g_strdup(name); } /** * fwupd_release_get_name_variant_suffix: * @self: a #FwupdRelease * * Gets the update variant suffix. * * Returns: the update variant, or %NULL if unset * * Since: 1.3.2 **/ const gchar * fwupd_release_get_name_variant_suffix(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->name_variant_suffix; } /** * fwupd_release_set_name_variant_suffix: * @self: a #FwupdRelease * @name_variant_suffix: (nullable): the description * * Sets the update variant suffix. * * Since: 1.3.2 **/ void fwupd_release_set_name_variant_suffix(FwupdRelease *self, const gchar *name_variant_suffix) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(priv->name_variant_suffix, name_variant_suffix) == 0) return; g_free(priv->name_variant_suffix); priv->name_variant_suffix = g_strdup(name_variant_suffix); } /** * fwupd_release_get_trust_flags: * @self: a #FwupdRelease * * Gets the trust level of the release. * * Returns: the trust bitfield, e.g. #FWUPD_TRUST_FLAG_PAYLOAD * * Since: 0.9.8 **/ FwupdTrustFlags fwupd_release_get_trust_flags(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), 0); return priv->flags & FWUPD_RELEASE_TRUST_FLAGS_MASK; } /** * fwupd_release_set_trust_flags: * @self: a #FwupdRelease * @trust_flags: the bitfield, e.g. #FWUPD_TRUST_FLAG_PAYLOAD * * Sets the trust level of the release. * * Since: 0.9.8 **/ void fwupd_release_set_trust_flags(FwupdRelease *self, FwupdTrustFlags trust_flags) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); /* only overwrite the last two bits of the flags */ priv->flags &= ~FWUPD_RELEASE_TRUST_FLAGS_MASK; priv->flags |= trust_flags; } /** * fwupd_release_get_flags: * @self: a #FwupdRelease * * Gets the release flags. * * Returns: release flags, or 0 if unset * * Since: 1.2.6 **/ FwupdReleaseFlags fwupd_release_get_flags(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), 0); return priv->flags; } /** * fwupd_release_set_flags: * @self: a #FwupdRelease * @flags: release flags, e.g. %FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD * * Sets the release flags. * * Since: 1.2.6 **/ void fwupd_release_set_flags(FwupdRelease *self, FwupdReleaseFlags flags) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); priv->flags = flags; } /** * fwupd_release_add_flag: * @self: a #FwupdRelease * @flag: the #FwupdReleaseFlags * * Adds a specific release flag to the release. * * Since: 1.2.6 **/ void fwupd_release_add_flag(FwupdRelease *self, FwupdReleaseFlags flag) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); priv->flags |= flag; } /** * fwupd_release_remove_flag: * @self: a #FwupdRelease * @flag: the #FwupdReleaseFlags * * Removes a specific release flag from the release. * * Since: 1.2.6 **/ void fwupd_release_remove_flag(FwupdRelease *self, FwupdReleaseFlags flag) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); priv->flags &= ~flag; } /** * fwupd_release_has_flag: * @self: a #FwupdRelease * @flag: the #FwupdReleaseFlags * * Finds if the release has a specific release flag. * * Returns: %TRUE if the flag is set * * Since: 1.2.6 **/ gboolean fwupd_release_has_flag(FwupdRelease *self, FwupdReleaseFlags flag) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), FALSE); return (priv->flags & flag) > 0; } /** * fwupd_release_get_urgency: * @self: a #FwupdRelease * * Gets the release urgency. * * Returns: the release urgency, or 0 if unset * * Since: 1.4.0 **/ FwupdReleaseUrgency fwupd_release_get_urgency(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), 0); return priv->urgency; } /** * fwupd_release_set_urgency: * @self: a #FwupdRelease * @urgency: the release urgency, e.g. %FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD * * Sets the release urgency. * * Since: 1.4.0 **/ void fwupd_release_set_urgency(FwupdRelease *self, FwupdReleaseUrgency urgency) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); priv->urgency = urgency; } /** * fwupd_release_get_install_duration: * @self: a #FwupdRelease * * Gets the time estimate for firmware installation (in seconds) * * Returns: the estimated time to flash this release (or 0 if unset) * * Since: 1.2.1 **/ guint32 fwupd_release_get_install_duration(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), 0); return priv->install_duration; } /** * fwupd_release_get_reports: * @self: a #FwupdRelease * * Gets all the reports for this release. * * Returns: (transfer none) (element-type FwupdReport): array of reports * * Since: 1.8.8 **/ GPtrArray * fwupd_release_get_reports(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); return priv->reports; } /** * fwupd_release_add_report: * @self: a #FwupdRelease * @report: (not nullable): a #FwupdReport * * Adds a report for this release. * * Since: 1.8.8 **/ void fwupd_release_add_report(FwupdRelease *self, FwupdReport *report) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); g_return_if_fail(FWUPD_IS_REPORT(report)); g_ptr_array_add(priv->reports, g_object_ref(report)); } /** * fwupd_release_set_install_duration: * @self: a #FwupdRelease * @duration: amount of time in seconds * * Sets the time estimate for firmware installation (in seconds) * * Since: 1.2.1 **/ void fwupd_release_set_install_duration(FwupdRelease *self, guint32 duration) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_RELEASE(self)); priv->install_duration = duration; } /** * fwupd_release_to_variant: * @self: a #FwupdRelease * * Serialize the release data. * * Returns: the serialized data, or %NULL for error * * Since: 1.0.0 **/ GVariant * fwupd_release_to_variant(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); GVariantBuilder builder; g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); /* create an array with all the metadata in */ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); if (priv->remote_id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_REMOTE_ID, g_variant_new_string(priv->remote_id)); } if (priv->appstream_id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_APPSTREAM_ID, g_variant_new_string(priv->appstream_id)); } if (priv->id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_RELEASE_ID, g_variant_new_string(priv->id)); } if (priv->detach_caption != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_DETACH_CAPTION, g_variant_new_string(priv->detach_caption)); } if (priv->detach_image != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_DETACH_IMAGE, g_variant_new_string(priv->detach_image)); } if (priv->update_message != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_UPDATE_MESSAGE, g_variant_new_string(priv->update_message)); } if (priv->update_image != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_UPDATE_IMAGE, g_variant_new_string(priv->update_image)); } if (priv->filename != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_FILENAME, g_variant_new_string(priv->filename)); } if (priv->protocol != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_PROTOCOL, g_variant_new_string(priv->protocol)); } if (priv->license != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_LICENSE, g_variant_new_string(priv->license)); } if (priv->name != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_NAME, g_variant_new_string(priv->name)); } if (priv->name_variant_suffix != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_NAME_VARIANT_SUFFIX, g_variant_new_string(priv->name_variant_suffix)); } if (priv->size != 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_SIZE, g_variant_new_uint64(priv->size)); } if (priv->created != 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_CREATED, g_variant_new_uint64(priv->created)); } if (priv->summary != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_SUMMARY, g_variant_new_string(priv->summary)); } if (priv->branch != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BRANCH, g_variant_new_string(priv->branch)); } if (priv->description != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_DESCRIPTION, g_variant_new_string(priv->description)); } if (priv->categories->len > 0) { g_autofree const gchar **strv = g_new0(const gchar *, priv->categories->len + 1); for (guint i = 0; i < priv->categories->len; i++) strv[i] = (const gchar *)g_ptr_array_index(priv->categories, i); g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_CATEGORIES, g_variant_new_strv(strv, -1)); } if (priv->issues->len > 0) { g_autofree const gchar **strv = g_new0(const gchar *, priv->issues->len + 1); for (guint i = 0; i < priv->issues->len; i++) strv[i] = (const gchar *)g_ptr_array_index(priv->issues, i); g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_ISSUES, g_variant_new_strv(strv, -1)); } if (priv->checksums->len > 0) { g_autoptr(GString) str = g_string_new(""); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum = g_ptr_array_index(priv->checksums, i); g_string_append_printf(str, "%s,", checksum); } if (str->len > 0) g_string_truncate(str, str->len - 1); g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_CHECKSUM, g_variant_new_string(str->str)); } if (priv->locations->len > 0) { g_variant_builder_add( &builder, "{sv}", FWUPD_RESULT_KEY_LOCATIONS, g_variant_new_strv((const gchar *const *)priv->locations->pdata, priv->locations->len)); /* for compatibility */ g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_URI, g_variant_new_string(g_ptr_array_index(priv->locations, 0))); } if (priv->tags->len > 0) { g_variant_builder_add( &builder, "{sv}", FWUPD_RESULT_KEY_TAGS, g_variant_new_strv((const gchar *const *)priv->tags->pdata, priv->tags->len)); } if (priv->homepage != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_HOMEPAGE, g_variant_new_string(priv->homepage)); } if (priv->details_url != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_DETAILS_URL, g_variant_new_string(priv->details_url)); } if (priv->source_url != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_SOURCE_URL, g_variant_new_string(priv->source_url)); } if (priv->version != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VERSION, g_variant_new_string(priv->version)); } if (priv->vendor != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VENDOR, g_variant_new_string(priv->vendor)); } if (priv->flags != 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_TRUST_FLAGS, g_variant_new_uint64(priv->flags)); } if (priv->urgency != 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_URGENCY, g_variant_new_uint32(priv->urgency)); } if (g_hash_table_size(priv->metadata) > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_METADATA, fwupd_hash_kv_to_variant(priv->metadata)); } if (priv->install_duration > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_INSTALL_DURATION, g_variant_new_uint32(priv->install_duration)); } if (priv->reports->len > 0) { g_autofree GVariant **children = NULL; children = g_new0(GVariant *, priv->reports->len); for (guint i = 0; i < priv->reports->len; i++) { FwupdReport *report = g_ptr_array_index(priv->reports, i); children[i] = fwupd_report_to_variant(report); } g_variant_builder_add( &builder, "{sv}", FWUPD_RESULT_KEY_REPORTS, g_variant_new_array(G_VARIANT_TYPE("a{sv}"), children, priv->reports->len)); } return g_variant_new("a{sv}", &builder); } static void fwupd_release_from_key_value(FwupdRelease *self, const gchar *key, GVariant *value) { FwupdReleasePrivate *priv = GET_PRIVATE(self); if (g_strcmp0(key, FWUPD_RESULT_KEY_REMOTE_ID) == 0) { fwupd_release_set_remote_id(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_APPSTREAM_ID) == 0) { fwupd_release_set_appstream_id(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_RELEASE_ID) == 0) { fwupd_release_set_id(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_DETACH_CAPTION) == 0) { fwupd_release_set_detach_caption(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_DETACH_IMAGE) == 0) { fwupd_release_set_detach_image(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_FILENAME) == 0) { fwupd_release_set_filename(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_PROTOCOL) == 0) { fwupd_release_set_protocol(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_LICENSE) == 0) { fwupd_release_set_license(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_NAME) == 0) { fwupd_release_set_name(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_NAME_VARIANT_SUFFIX) == 0) { fwupd_release_set_name_variant_suffix(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_SIZE) == 0) { fwupd_release_set_size(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_CREATED) == 0) { fwupd_release_set_created(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_SUMMARY) == 0) { fwupd_release_set_summary(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BRANCH) == 0) { fwupd_release_set_branch(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_DESCRIPTION) == 0) { fwupd_release_set_description(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_CATEGORIES) == 0) { g_autofree const gchar **strv = g_variant_get_strv(value, NULL); for (guint i = 0; strv[i] != NULL; i++) fwupd_release_add_category(self, strv[i]); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_ISSUES) == 0) { g_autofree const gchar **strv = g_variant_get_strv(value, NULL); for (guint i = 0; strv[i] != NULL; i++) fwupd_release_add_issue(self, strv[i]); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_CHECKSUM) == 0) { const gchar *checksums = g_variant_get_string(value, NULL); g_auto(GStrv) split = g_strsplit(checksums, ",", -1); for (guint i = 0; split[i] != NULL; i++) fwupd_release_add_checksum(self, split[i]); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_LOCATIONS) == 0) { g_autofree const gchar **strv = g_variant_get_strv(value, NULL); for (guint i = 0; strv[i] != NULL; i++) fwupd_release_add_location(self, strv[i]); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_TAGS) == 0) { g_autofree const gchar **strv = g_variant_get_strv(value, NULL); for (guint i = 0; strv[i] != NULL; i++) fwupd_release_add_tag(self, strv[i]); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_URI) == 0) { fwupd_release_add_location(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_HOMEPAGE) == 0) { fwupd_release_set_homepage(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_DETAILS_URL) == 0) { fwupd_release_set_details_url(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_SOURCE_URL) == 0) { fwupd_release_set_source_url(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VERSION) == 0) { fwupd_release_set_version(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VENDOR) == 0) { fwupd_release_set_vendor(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_TRUST_FLAGS) == 0) { fwupd_release_set_flags(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_URGENCY) == 0) { fwupd_release_set_urgency(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_INSTALL_DURATION) == 0) { fwupd_release_set_install_duration(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_UPDATE_MESSAGE) == 0) { fwupd_release_set_update_message(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_UPDATE_IMAGE) == 0) { fwupd_release_set_update_image(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_METADATA) == 0) { g_hash_table_unref(priv->metadata); priv->metadata = fwupd_variant_to_hash_kv(value); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_REPORTS) == 0) { GVariantIter iter; GVariant *child; g_variant_iter_init(&iter, value); while ((child = g_variant_iter_next_value(&iter))) { g_autoptr(FwupdReport) report = fwupd_report_from_variant(child); if (report != NULL) fwupd_release_add_report(self, report); g_variant_unref(child); } return; } } static void fwupd_pad_kv_siz(GString *str, const gchar *key, guint64 value) { g_autofree gchar *tmp = NULL; /* ignore */ if (value == 0) return; tmp = g_format_size(value); fwupd_pad_kv_str(str, key, tmp); } static void fwupd_pad_kv_tfl(GString *str, const gchar *key, FwupdReleaseFlags release_flags) { g_autoptr(GString) tmp = g_string_new(""); for (guint i = 0; i < 64; i++) { if ((release_flags & ((guint64)1 << i)) == 0) continue; g_string_append_printf(tmp, "%s|", fwupd_release_flag_to_string((guint64)1 << i)); } if (tmp->len == 0) { g_string_append(tmp, fwupd_release_flag_to_string(0)); } else { g_string_truncate(tmp, tmp->len - 1); } fwupd_pad_kv_str(str, key, tmp->str); } /** * fwupd_release_to_json: * @self: a #FwupdRelease * @builder: a JSON builder * * Adds a fwupd release to a JSON builder * * Since: 1.2.6 **/ void fwupd_release_to_json(FwupdRelease *self, JsonBuilder *builder) { FwupdReleasePrivate *priv = GET_PRIVATE(self); g_autoptr(GList) keys = NULL; g_return_if_fail(FWUPD_IS_RELEASE(self)); g_return_if_fail(builder != NULL); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_APPSTREAM_ID, priv->appstream_id); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_RELEASE_ID, priv->id); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_REMOTE_ID, priv->remote_id); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_NAME, priv->name); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_NAME_VARIANT_SUFFIX, priv->name_variant_suffix); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_SUMMARY, priv->summary); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_DESCRIPTION, priv->description); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_BRANCH, priv->branch); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_VERSION, priv->version); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_FILENAME, priv->filename); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_PROTOCOL, priv->protocol); if (priv->categories->len > 0) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_CATEGORIES); json_builder_begin_array(builder); for (guint i = 0; i < priv->categories->len; i++) { const gchar *tmp = g_ptr_array_index(priv->categories, i); json_builder_add_string_value(builder, tmp); } json_builder_end_array(builder); } if (priv->issues->len > 0) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_ISSUES); json_builder_begin_array(builder); for (guint i = 0; i < priv->issues->len; i++) { const gchar *tmp = g_ptr_array_index(priv->issues, i); json_builder_add_string_value(builder, tmp); } json_builder_end_array(builder); } if (priv->checksums->len > 0) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_CHECKSUM); json_builder_begin_array(builder); for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum = g_ptr_array_index(priv->checksums, i); json_builder_add_string_value(builder, checksum); } json_builder_end_array(builder); } if (priv->tags->len > 0) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_TAGS); json_builder_begin_array(builder); for (guint i = 0; i < priv->tags->len; i++) { const gchar *tag = g_ptr_array_index(priv->tags, i); json_builder_add_string_value(builder, tag); } json_builder_end_array(builder); } fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_LICENSE, priv->license); if (priv->size > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_SIZE, priv->size); if (priv->created > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_CREATED, priv->created); if (priv->locations->len > 0) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_LOCATIONS); json_builder_begin_array(builder); for (guint i = 0; i < priv->locations->len; i++) { const gchar *location = g_ptr_array_index(priv->locations, i); json_builder_add_string_value(builder, location); } json_builder_end_array(builder); /* for compatibility */ fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_URI, (const gchar *)g_ptr_array_index(priv->locations, 0)); } fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_HOMEPAGE, priv->homepage); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_DETAILS_URL, priv->details_url); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_SOURCE_URL, priv->source_url); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_VENDOR, priv->vendor); if (priv->flags != FWUPD_RELEASE_FLAG_NONE) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_FLAGS); json_builder_begin_array(builder); for (guint i = 0; i < 64; i++) { const gchar *tmp; if ((priv->flags & ((guint64)1 << i)) == 0) continue; tmp = fwupd_release_flag_to_string((guint64)1 << i); json_builder_add_string_value(builder, tmp); } json_builder_end_array(builder); } if (priv->install_duration > 0) { fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_INSTALL_DURATION, priv->install_duration); } fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_DETACH_CAPTION, priv->detach_caption); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_DETACH_IMAGE, priv->detach_image); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_UPDATE_MESSAGE, priv->update_message); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_UPDATE_IMAGE, priv->update_image); /* metadata */ keys = g_hash_table_get_keys(priv->metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup(priv->metadata, key); fwupd_common_json_add_string(builder, key, value); } /* reports */ if (priv->reports->len > 0) { json_builder_set_member_name(builder, "Reports"); json_builder_begin_array(builder); for (guint i = 0; i < priv->reports->len; i++) { FwupdReport *report = g_ptr_array_index(priv->reports, i); json_builder_begin_object(builder); fwupd_report_to_json(report, builder); json_builder_end_object(builder); } json_builder_end_array(builder); } } /** * fwupd_release_to_string: * @self: a #FwupdRelease * * Builds a text representation of the object. * * Returns: text, or %NULL for invalid * * Since: 0.9.3 **/ gchar * fwupd_release_to_string(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); GString *str; g_autoptr(GList) keys = NULL; g_return_val_if_fail(FWUPD_IS_RELEASE(self), NULL); str = g_string_new(""); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_APPSTREAM_ID, priv->appstream_id); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_RELEASE_ID, priv->id); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_REMOTE_ID, priv->remote_id); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_NAME, priv->name); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_NAME_VARIANT_SUFFIX, priv->name_variant_suffix); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_SUMMARY, priv->summary); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_DESCRIPTION, priv->description); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_BRANCH, priv->branch); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_VERSION, priv->version); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_FILENAME, priv->filename); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_PROTOCOL, priv->protocol); for (guint i = 0; i < priv->categories->len; i++) { const gchar *tmp = g_ptr_array_index(priv->categories, i); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_CATEGORIES, tmp); } for (guint i = 0; i < priv->issues->len; i++) { const gchar *tmp = g_ptr_array_index(priv->issues, i); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_ISSUES, tmp); } for (guint i = 0; i < priv->checksums->len; i++) { const gchar *checksum = g_ptr_array_index(priv->checksums, i); g_autofree gchar *checksum_display = fwupd_checksum_format_for_display(checksum); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_CHECKSUM, checksum_display); } for (guint i = 0; i < priv->tags->len; i++) { const gchar *tag = g_ptr_array_index(priv->tags, i); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_TAGS, tag); } fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_LICENSE, priv->license); fwupd_pad_kv_siz(str, FWUPD_RESULT_KEY_SIZE, priv->size); fwupd_pad_kv_unx(str, FWUPD_RESULT_KEY_CREATED, priv->created); for (guint i = 0; i < priv->locations->len; i++) { const gchar *location = g_ptr_array_index(priv->locations, i); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_URI, location); } fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_HOMEPAGE, priv->homepage); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_DETAILS_URL, priv->details_url); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_SOURCE_URL, priv->source_url); if (priv->urgency != FWUPD_RELEASE_URGENCY_UNKNOWN) { fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_URGENCY, fwupd_release_urgency_to_string(priv->urgency)); } fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_VENDOR, priv->vendor); fwupd_pad_kv_tfl(str, FWUPD_RESULT_KEY_FLAGS, priv->flags); fwupd_pad_kv_int(str, FWUPD_RESULT_KEY_INSTALL_DURATION, priv->install_duration); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_DETACH_CAPTION, priv->detach_caption); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_DETACH_IMAGE, priv->detach_image); if (priv->update_message != NULL) fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_UPDATE_MESSAGE, priv->update_message); if (priv->update_image != NULL) fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_UPDATE_IMAGE, priv->update_image); /* metadata */ keys = g_hash_table_get_keys(priv->metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup(priv->metadata, key); fwupd_pad_kv_str(str, key, value); } for (guint i = 0; i < priv->reports->len; i++) { FwupdReport *report = g_ptr_array_index(priv->reports, i); g_autofree gchar *tmp = fwupd_report_to_string(report); g_string_append_printf(str, " \n [%s]\n%s", FWUPD_RESULT_KEY_REPORTS, tmp); } return g_string_free(str, FALSE); } static void fwupd_release_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { FwupdRelease *self = FWUPD_RELEASE(obj); FwupdReleasePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_REMOTE_ID: g_value_set_string(value, priv->remote_id); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } } static void fwupd_release_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { FwupdRelease *self = FWUPD_RELEASE(obj); switch (prop_id) { case PROP_REMOTE_ID: fwupd_release_set_remote_id(self, g_value_get_string(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } } static void fwupd_release_class_init(FwupdReleaseClass *klass) { GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fwupd_release_finalize; object_class->get_property = fwupd_release_get_property; object_class->set_property = fwupd_release_set_property; /** * FwupdRelease:remote-id: * * The remote ID. * * Since: 1.8.0 */ pspec = g_param_spec_string("remote-id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_REMOTE_ID, pspec); } static void fwupd_release_init(FwupdRelease *self) { FwupdReleasePrivate *priv = GET_PRIVATE(self); priv->categories = g_ptr_array_new_with_free_func(g_free); priv->issues = g_ptr_array_new_with_free_func(g_free); priv->checksums = g_ptr_array_new_with_free_func(g_free); priv->tags = g_ptr_array_new_with_free_func(g_free); priv->locations = g_ptr_array_new_with_free_func(g_free); priv->reports = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); priv->metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); } static void fwupd_release_finalize(GObject *object) { FwupdRelease *self = FWUPD_RELEASE(object); FwupdReleasePrivate *priv = GET_PRIVATE(self); g_free(priv->description); g_free(priv->filename); g_free(priv->protocol); g_free(priv->appstream_id); g_free(priv->id); g_free(priv->detach_caption); g_free(priv->detach_image); g_free(priv->license); g_free(priv->name); g_free(priv->name_variant_suffix); g_free(priv->summary); g_free(priv->branch); g_ptr_array_unref(priv->locations); g_free(priv->homepage); g_free(priv->details_url); g_free(priv->source_url); g_free(priv->vendor); g_free(priv->version); g_free(priv->remote_id); g_free(priv->update_message); g_free(priv->update_image); g_ptr_array_unref(priv->categories); g_ptr_array_unref(priv->issues); g_ptr_array_unref(priv->checksums); g_ptr_array_unref(priv->tags); g_ptr_array_unref(priv->reports); g_hash_table_unref(priv->metadata); G_OBJECT_CLASS(fwupd_release_parent_class)->finalize(object); } static void fwupd_release_set_from_variant_iter(FwupdRelease *self, GVariantIter *iter) { GVariant *value; const gchar *key; while (g_variant_iter_next(iter, "{&sv}", &key, &value)) { fwupd_release_from_key_value(self, key, value); g_variant_unref(value); } } /** * fwupd_release_from_variant: * @value: (not nullable): the serialized data * * Creates a new release using serialized data. * * Returns: (transfer full): a new #FwupdRelease, or %NULL if @value was invalid * * Since: 1.0.0 **/ FwupdRelease * fwupd_release_from_variant(GVariant *value) { FwupdRelease *self = NULL; const gchar *type_string; g_autoptr(GVariantIter) iter = NULL; g_return_val_if_fail(value != NULL, NULL); /* format from GetDetails */ type_string = g_variant_get_type_string(value); if (g_strcmp0(type_string, "(a{sv})") == 0) { self = fwupd_release_new(); g_variant_get(value, "(a{sv})", &iter); fwupd_release_set_from_variant_iter(self, iter); } else if (g_strcmp0(type_string, "a{sv}") == 0) { self = fwupd_release_new(); g_variant_get(value, "a{sv}", &iter); fwupd_release_set_from_variant_iter(self, iter); } else { g_warning("type %s not known", type_string); } return self; } /** * fwupd_release_array_from_variant: * @value: (not nullable): the serialized data * * Creates an array of new releases using serialized data. * * Returns: (transfer container) (element-type FwupdRelease): releases, or %NULL if @value was *invalid * * Since: 1.2.10 **/ GPtrArray * fwupd_release_array_from_variant(GVariant *value) { GPtrArray *array = NULL; gsize sz; g_autoptr(GVariant) untuple = NULL; g_return_val_if_fail(value != NULL, NULL); array = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); untuple = g_variant_get_child_value(value, 0); sz = g_variant_n_children(untuple); for (guint i = 0; i < sz; i++) { FwupdRelease *self; g_autoptr(GVariant) data = NULL; data = g_variant_get_child_value(untuple, i); self = fwupd_release_from_variant(data); if (self == NULL) continue; g_ptr_array_add(array, self); } return array; } /** * fwupd_release_incorporate: * @self: a #FwupdRelease * @donor: another #FwupdRelease * * Copy all properties from the donor object. * * Since: 1.8.8 **/ void fwupd_release_incorporate(FwupdRelease *self, FwupdRelease *donor) { g_autoptr(GVariant) variant = NULL; g_autoptr(GVariantIter) iter = NULL; g_return_if_fail(FWUPD_IS_RELEASE(self)); g_return_if_fail(FWUPD_IS_RELEASE(donor)); variant = fwupd_release_to_variant(donor); g_variant_get(variant, "a{sv}", &iter); fwupd_release_set_from_variant_iter(self, iter); } /** * fwupd_release_match_flags: * @include: #FwupdReleaseFlags, or %FWUPD_RELEASE_FLAG_NONE * @exclude: #FwupdReleaseFlags, or %FWUPD_RELEASE_FLAG_NONE * * Check if the release flags match. * * Returns: %TRUE if the release flags match * * Since: 1.9.3 **/ gboolean fwupd_release_match_flags(FwupdRelease *self, FwupdReleaseFlags include, FwupdReleaseFlags exclude) { g_return_val_if_fail(FWUPD_IS_RELEASE(self), FALSE); for (guint i = 0; i < 64; i++) { FwupdReleaseFlags flag = 1LLU << i; if ((include & flag) > 0) { if (!fwupd_release_has_flag(self, flag)) return FALSE; } if ((exclude & flag) > 0) { if (fwupd_release_has_flag(self, flag)) return FALSE; } } return TRUE; } /** * fwupd_release_array_filter_flags: * @rels: (not nullable) (element-type FwupdRelease): releases * @include: #FwupdReleaseFlags, or %FWUPD_RELEASE_FLAG_NONE * @exclude: #FwupdReleaseFlags, or %FWUPD_RELEASE_FLAG_NONE * @error: (nullable): optional return location for an error * * Creates an array of new releases that match using fwupd_release_match_flags(). * * Returns: (transfer container) (element-type FwupdRelease): releases * * Since: 1.9.3 **/ GPtrArray * fwupd_release_array_filter_flags(GPtrArray *rels, FwupdReleaseFlags include, FwupdReleaseFlags exclude, GError **error) { g_autoptr(GPtrArray) rels_filtered = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_return_val_if_fail(rels != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel = g_ptr_array_index(rels, i); if (!fwupd_release_match_flags(rel, include, exclude)) continue; g_ptr_array_add(rels_filtered, g_object_ref(rel)); } if (rels_filtered->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "no releases"); return NULL; } return g_steal_pointer(&rels_filtered); } /** * fwupd_release_new: * * Creates a new release. * * Returns: a new #FwupdRelease * * Since: 0.9.3 **/ FwupdRelease * fwupd_release_new(void) { FwupdRelease *self; self = g_object_new(FWUPD_TYPE_RELEASE, NULL); return FWUPD_RELEASE(self); } fwupd-1.9.16/libfwupd/fwupd-release.h000066400000000000000000000202151460375044200174720ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fwupd-enums.h" #include "fwupd-report.h" G_BEGIN_DECLS #define FWUPD_TYPE_RELEASE (fwupd_release_get_type()) G_DECLARE_DERIVABLE_TYPE(FwupdRelease, fwupd_release, FWUPD, RELEASE, GObject) struct _FwupdReleaseClass { GObjectClass parent_class; /*< private >*/ void (*_fwupd_reserved1)(void); void (*_fwupd_reserved2)(void); void (*_fwupd_reserved3)(void); void (*_fwupd_reserved4)(void); void (*_fwupd_reserved5)(void); void (*_fwupd_reserved6)(void); void (*_fwupd_reserved7)(void); }; FwupdRelease * fwupd_release_new(void); gchar * fwupd_release_to_string(FwupdRelease *self) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_version(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_version(FwupdRelease *self, const gchar *version) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(fwupd_release_get_locations) const gchar * fwupd_release_get_uri(FwupdRelease *self) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(fwupd_release_add_location) void fwupd_release_set_uri(FwupdRelease *self, const gchar *uri) G_GNUC_NON_NULL(1); GPtrArray * fwupd_release_get_locations(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_add_location(FwupdRelease *self, const gchar *location) G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_release_get_issues(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_add_issue(FwupdRelease *self, const gchar *issue) G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_release_get_categories(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_add_category(FwupdRelease *self, const gchar *category) G_GNUC_NON_NULL(1, 2); gboolean fwupd_release_has_category(FwupdRelease *self, const gchar *category) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_release_get_checksums(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_add_checksum(FwupdRelease *self, const gchar *checksum) G_GNUC_NON_NULL(1, 2); gboolean fwupd_release_has_checksum(FwupdRelease *self, const gchar *checksum) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_release_get_tags(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_add_tag(FwupdRelease *self, const gchar *tag) G_GNUC_NON_NULL(1, 2); gboolean fwupd_release_has_tag(FwupdRelease *self, const gchar *tag) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GHashTable * fwupd_release_get_metadata(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_add_metadata(FwupdRelease *self, GHashTable *hash) G_GNUC_NON_NULL(1, 2); void fwupd_release_add_metadata_item(FwupdRelease *self, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2); const gchar * fwupd_release_get_metadata_item(FwupdRelease *self, const gchar *key) G_GNUC_NON_NULL(1, 2); const gchar * fwupd_release_get_filename(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_filename(FwupdRelease *self, const gchar *filename) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_protocol(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_protocol(FwupdRelease *self, const gchar *protocol) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_id(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_id(FwupdRelease *self, const gchar *id) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_appstream_id(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_appstream_id(FwupdRelease *self, const gchar *appstream_id) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_detach_caption(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_detach_caption(FwupdRelease *self, const gchar *detach_caption) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_detach_image(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_detach_image(FwupdRelease *self, const gchar *detach_image) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_remote_id(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_remote_id(FwupdRelease *self, const gchar *remote_id) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_vendor(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_vendor(FwupdRelease *self, const gchar *vendor) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_name(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_name(FwupdRelease *self, const gchar *name) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_name_variant_suffix(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_name_variant_suffix(FwupdRelease *self, const gchar *name_variant_suffix) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_summary(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_summary(FwupdRelease *self, const gchar *summary) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_branch(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_branch(FwupdRelease *self, const gchar *branch) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_description(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_description(FwupdRelease *self, const gchar *description) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_homepage(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_homepage(FwupdRelease *self, const gchar *homepage) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_details_url(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_details_url(FwupdRelease *self, const gchar *details_url) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_source_url(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_source_url(FwupdRelease *self, const gchar *source_url) G_GNUC_NON_NULL(1); guint64 fwupd_release_get_size(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_size(FwupdRelease *self, guint64 size) G_GNUC_NON_NULL(1); guint64 fwupd_release_get_created(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_created(FwupdRelease *self, guint64 created) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_license(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_license(FwupdRelease *self, const gchar *license) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(fwupd_release_get_flags) FwupdTrustFlags fwupd_release_get_trust_flags(FwupdRelease *self) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(fwupd_release_set_flags) void fwupd_release_set_trust_flags(FwupdRelease *self, FwupdTrustFlags trust_flags) G_GNUC_NON_NULL(1); FwupdReleaseFlags fwupd_release_get_flags(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_flags(FwupdRelease *self, FwupdReleaseFlags flags) G_GNUC_NON_NULL(1); void fwupd_release_add_flag(FwupdRelease *self, FwupdReleaseFlags flag) G_GNUC_NON_NULL(1); void fwupd_release_remove_flag(FwupdRelease *self, FwupdReleaseFlags flag) G_GNUC_NON_NULL(1); gboolean fwupd_release_has_flag(FwupdRelease *self, FwupdReleaseFlags flag) G_GNUC_WARN_UNUSED_RESULT; FwupdReleaseUrgency fwupd_release_get_urgency(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_urgency(FwupdRelease *self, FwupdReleaseUrgency urgency) G_GNUC_NON_NULL(1); guint32 fwupd_release_get_install_duration(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_install_duration(FwupdRelease *self, guint32 duration) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_update_message(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_update_message(FwupdRelease *self, const gchar *update_message) G_GNUC_NON_NULL(1); const gchar * fwupd_release_get_update_image(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_set_update_image(FwupdRelease *self, const gchar *update_image) G_GNUC_NON_NULL(1); GPtrArray * fwupd_release_get_reports(FwupdRelease *self) G_GNUC_NON_NULL(1); void fwupd_release_add_report(FwupdRelease *self, FwupdReport *report) G_GNUC_NON_NULL(1); gboolean fwupd_release_match_flags(FwupdRelease *self, FwupdReleaseFlags include, FwupdReleaseFlags exclude) G_GNUC_NON_NULL(1); GPtrArray * fwupd_release_array_filter_flags(GPtrArray *rels, FwupdReleaseFlags include, FwupdReleaseFlags exclude, GError **error) G_GNUC_NON_NULL(1); FwupdRelease * fwupd_release_from_variant(GVariant *value) G_GNUC_NON_NULL(1); GPtrArray * fwupd_release_array_from_variant(GVariant *value) G_GNUC_NON_NULL(1); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-remote-private.h000066400000000000000000000044371460375044200210250ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-build.h" #include "fwupd-remote.h" G_BEGIN_DECLS GVariant * fwupd_remote_to_variant(FwupdRemote *self) G_GNUC_NON_NULL(1); gboolean fwupd_remote_load_from_filename(FwupdRemote *self, const gchar *filename, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fwupd_remote_save_to_filename(FwupdRemote *self, const gchar *filename, GCancellable *cancellable, GError **error) G_GNUC_NON_NULL(1, 2); G_DEPRECATED_FOR(fwupd_remote_add_flag) void fwupd_remote_set_enabled(FwupdRemote *self, gboolean enabled) G_GNUC_NON_NULL(1); void fwupd_remote_set_id(FwupdRemote *self, const gchar *id) G_GNUC_NON_NULL(1); void fwupd_remote_set_title(FwupdRemote *self, const gchar *title) G_GNUC_NON_NULL(1); void fwupd_remote_set_priority(FwupdRemote *self, gint priority) G_GNUC_NON_NULL(1); void fwupd_remote_set_agreement(FwupdRemote *self, const gchar *agreement) G_GNUC_NON_NULL(1); void fwupd_remote_set_checksum(FwupdRemote *self, const gchar *checksum_sig) G_GNUC_NON_NULL(1); void fwupd_remote_set_filename_cache(FwupdRemote *self, const gchar *filename) G_GNUC_NON_NULL(1); void fwupd_remote_set_metadata_uri(FwupdRemote *self, const gchar *metadata_uri) G_GNUC_NON_NULL(1); void fwupd_remote_set_mtime(FwupdRemote *self, guint64 mtime) G_GNUC_NON_NULL(1); gchar ** fwupd_remote_get_order_after(FwupdRemote *self) G_GNUC_NON_NULL(1); gchar ** fwupd_remote_get_order_before(FwupdRemote *self) G_GNUC_NON_NULL(1); void fwupd_remote_set_remotes_dir(FwupdRemote *self, const gchar *directory) G_GNUC_NON_NULL(1); void fwupd_remote_set_filename_source(FwupdRemote *self, const gchar *filename_source) G_GNUC_NON_NULL(1); void fwupd_remote_set_keyring_kind(FwupdRemote *self, FwupdKeyringKind keyring_kind) G_GNUC_NON_NULL(1); gboolean fwupd_remote_setup(FwupdRemote *self, GError **error) G_GNUC_NON_NULL(1); gchar * fwupd_remote_build_metadata_sig_uri(FwupdRemote *self, GError **error) G_GNUC_NON_NULL(1); gchar * fwupd_remote_build_metadata_uri(FwupdRemote *self, GError **error) G_GNUC_NON_NULL(1); void fwupd_remote_to_json(FwupdRemote *self, JsonBuilder *builder) G_GNUC_NON_NULL(1, 2); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-remote.c000066400000000000000000002015161460375044200173450ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_LIBCURL #include #endif #include #include "fwupd-common-private.h" #include "fwupd-enums-private.h" #include "fwupd-error.h" #include "fwupd-remote-private.h" /** * FwupdRemote: * * A source of metadata that provides firmware. * * Remotes can be local (e.g. folders on a disk) or remote (e.g. downloaded * over HTTP or IPFS). * * See also: [class@FwupdClient] */ #define FWUPD_REMOTE_CONFIG_DEFAULT_REFRESH_INTERVAL 86400 /* 24h */ static void fwupd_remote_finalize(GObject *obj); typedef struct { FwupdRemoteKind kind; FwupdRemoteFlags flags; FwupdKeyringKind keyring_kind; gchar *id; gchar *firmware_base_uri; gchar *report_uri; gchar *security_report_uri; gchar *metadata_uri; gchar *metadata_uri_sig; gchar *username; gchar *password; gchar *title; gchar *agreement; gchar *checksum; /* of metadata */ gchar *checksum_sig; /* of the signature */ gchar *filename_cache; gchar *filename_cache_sig; gchar *filename_source; gint priority; guint64 mtime; guint64 refresh_interval; gchar **order_after; gchar **order_before; gchar *remotes_dir; } FwupdRemotePrivate; enum { PROP_0, PROP_ID, PROP_ENABLED, PROP_APPROVAL_REQUIRED, PROP_AUTOMATIC_REPORTS, PROP_AUTOMATIC_SECURITY_REPORTS, PROP_FLAGS, PROP_LAST }; G_DEFINE_TYPE_WITH_PRIVATE(FwupdRemote, fwupd_remote, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_remote_get_instance_private(o)) #ifdef HAVE_LIBCURL typedef gchar curlptr; G_DEFINE_AUTOPTR_CLEANUP_FUNC(curlptr, curl_free) G_DEFINE_AUTOPTR_CLEANUP_FUNC(CURLU, curl_url_cleanup) #endif /** * fwupd_remote_flag_to_string: * @flag: remote attribute flags, e.g. %FWUPD_REMOTE_FLAG_ENABLED * * Returns the printable string for the flag. * * Returns: string, or %NULL * * Since: 1.9.4 **/ const gchar * fwupd_remote_flag_to_string(FwupdRemoteFlags flag) { if (flag == FWUPD_REMOTE_FLAG_NONE) return "none"; if (flag == FWUPD_REMOTE_FLAG_ENABLED) return "enabled"; if (flag == FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED) return "approval-required"; if (flag == FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS) return "automatic-reports"; if (flag == FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS) return "automatic-security-reports"; if (flag == FWUPD_REMOTE_FLAG_ALLOW_P2P_METADATA) return "allow-p2p-metadata"; if (flag == FWUPD_REMOTE_FLAG_ALLOW_P2P_FIRMWARE) return "allow-p2p-firmware"; return NULL; } /** * fwupd_remote_flag_from_string: * @flag: (nullable): a string, e.g. `enabled` * * Converts a string to an enumerated flag. * * Returns: enumerated value * * Since: 1.9.4 **/ FwupdRemoteFlags fwupd_remote_flag_from_string(const gchar *flag) { if (g_strcmp0(flag, "enabled") == 0) return FWUPD_REMOTE_FLAG_ENABLED; if (g_strcmp0(flag, "approval-required") == 0) return FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED; if (g_strcmp0(flag, "automatic-reports") == 0) return FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS; if (g_strcmp0(flag, "automatic-security-reports") == 0) return FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS; if (g_strcmp0(flag, "allow-p2p-metadata") == 0) return FWUPD_REMOTE_FLAG_ALLOW_P2P_METADATA; if (g_strcmp0(flag, "allow-p2p-firmware") == 0) return FWUPD_REMOTE_FLAG_ALLOW_P2P_FIRMWARE; return FWUPD_REMOTE_FLAG_NONE; } /** * fwupd_remote_to_json: * @self: a #FwupdRemote * @builder: a JSON builder * * Adds a fwupd remote to a JSON builder * * Since: 1.6.2 **/ void fwupd_remote_to_json(FwupdRemote *self, JsonBuilder *builder) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REMOTE(self)); g_return_if_fail(builder != NULL); fwupd_common_json_add_string(builder, "Id", priv->id); if (priv->kind != FWUPD_REMOTE_KIND_UNKNOWN) { fwupd_common_json_add_string(builder, "Kind", fwupd_remote_kind_to_string(priv->kind)); } if (priv->keyring_kind != FWUPD_KEYRING_KIND_UNKNOWN) { fwupd_common_json_add_string(builder, "KeyringKind", fwupd_keyring_kind_to_string(priv->keyring_kind)); } fwupd_common_json_add_string(builder, "FirmwareBaseUri", priv->firmware_base_uri); fwupd_common_json_add_string(builder, "ReportUri", priv->report_uri); fwupd_common_json_add_string(builder, "SecurityReportUri", priv->security_report_uri); fwupd_common_json_add_string(builder, "MetadataUri", priv->metadata_uri); fwupd_common_json_add_string(builder, "MetadataUriSig", priv->metadata_uri_sig); fwupd_common_json_add_string(builder, "Username", priv->username); fwupd_common_json_add_string(builder, "Password", priv->password); fwupd_common_json_add_string(builder, "Title", priv->title); fwupd_common_json_add_string(builder, "Agreement", priv->agreement); fwupd_common_json_add_string(builder, "Checksum", priv->checksum); fwupd_common_json_add_string(builder, "ChecksumSig", priv->checksum_sig); fwupd_common_json_add_string(builder, "FilenameCache", priv->filename_cache); fwupd_common_json_add_string(builder, "FilenameCacheSig", priv->filename_cache_sig); fwupd_common_json_add_string(builder, "FilenameSource", priv->filename_source); fwupd_common_json_add_int(builder, "Flags", priv->flags); fwupd_common_json_add_boolean(builder, "Enabled", fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_ENABLED)); fwupd_common_json_add_boolean( builder, "ApprovalRequired", fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED)); fwupd_common_json_add_boolean( builder, "AutomaticReports", fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS)); fwupd_common_json_add_boolean( builder, "AutomaticSecurityReports", fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS)); fwupd_common_json_add_int(builder, "Priority", priv->priority); fwupd_common_json_add_int(builder, "Mtime", priv->mtime); fwupd_common_json_add_int(builder, "RefreshInterval", priv->refresh_interval); fwupd_common_json_add_string(builder, "RemotesDir", priv->remotes_dir); fwupd_common_json_add_stringv(builder, "OrderAfter", priv->order_after); fwupd_common_json_add_stringv(builder, "OrderBefore", priv->order_before); } /** * fwupd_remote_get_flags: * @self: a #FwupdRemote * * Gets the self flags. * * Returns: remote attribute flags, or 0 if unset * * Since: 1.9.4 **/ FwupdRemoteFlags fwupd_remote_get_flags(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), 0); return priv->flags; } /** * fwupd_remote_set_flags: * @self: a #FwupdRemote * @flags: remote attribute flags, e.g. %FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED * * Sets the attribute flags. * * Since: 1.9.4 **/ void fwupd_remote_set_flags(FwupdRemote *self, FwupdRemoteFlags flags) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REMOTE(self)); if (flags == priv->flags) return; priv->flags = flags; g_object_notify(G_OBJECT(self), "flags"); } /** * fwupd_remote_add_flag: * @self: a #FwupdRemote * @flag: the #FwupdRemoteFlags, e.g. %FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED * * Adds a specific attribute flag to the attribute. * * Since: 1.9.4 **/ void fwupd_remote_add_flag(FwupdRemote *self, FwupdRemoteFlags flag) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REMOTE(self)); priv->flags |= flag; g_object_notify(G_OBJECT(self), "flags"); } /** * fwupd_remote_remove_flag: * @self: a #FwupdRemote * @flag: the #FwupdRemoteFlags, e.g. %FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED * * Removes a specific attribute flag from the remote. * * Since: 1.9.4 **/ void fwupd_remote_remove_flag(FwupdRemote *self, FwupdRemoteFlags flag) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REMOTE(self)); priv->flags &= ~flag; g_object_notify(G_OBJECT(self), "flags"); } /** * fwupd_remote_has_flag: * @self: a #FwupdRemote * @flag: the remote flag, e.g. %FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED * * Finds if the remote has a specific flag. * * Returns: %TRUE if the flag is set * * Since: 1.9.4 **/ gboolean fwupd_remote_has_flag(FwupdRemote *self, FwupdRemoteFlags flag) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); return (priv->flags & flag) > 0; } static gchar * fwupd_strdup_nonempty(const gchar *text) { if (text == NULL || text[0] == '\0') return NULL; return g_strdup(text); } static void fwupd_remote_set_username(FwupdRemote *self, const gchar *username) { FwupdRemotePrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->username, username) == 0) return; g_free(priv->username); priv->username = g_strdup(username); } /** * fwupd_remote_set_title: * @self: a #FwupdRemote * @title: (nullable): title text, e.g. "Backup" * * Sets the remote title. * * Since: 1.8.13 **/ void fwupd_remote_set_title(FwupdRemote *self, const gchar *title) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REMOTE(self)); /* not changed */ if (g_strcmp0(priv->title, title) == 0) return; g_free(priv->title); priv->title = g_strdup(title); } /** * fwupd_remote_set_agreement: * @self: a #FwupdRemote * @agreement: (nullable): agreement markup text * * Sets the remote agreement in AppStream markup format * * Since: 1.0.7 **/ void fwupd_remote_set_agreement(FwupdRemote *self, const gchar *agreement) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REMOTE(self)); /* not changed */ if (g_strcmp0(priv->agreement, agreement) == 0) return; g_free(priv->agreement); priv->agreement = g_strdup(agreement); } /** * fwupd_remote_set_checksum: * @self: a #FwupdRemote * @checksum_sig: (nullable): checksum string * * Sets the remote signature checksum, typically only useful in the self tests. * * NOTE: This should have been called fwupd_remote_set_checksum_sig() but alas, ABI. * * Since: 1.8.2 **/ void fwupd_remote_set_checksum(FwupdRemote *self, const gchar *checksum_sig) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REMOTE(self)); /* not changed */ if (g_strcmp0(priv->checksum_sig, checksum_sig) == 0) return; g_free(priv->checksum_sig); priv->checksum_sig = g_strdup(checksum_sig); } static void fwupd_remote_set_checksum_metadata(FwupdRemote *self, const gchar *checksum) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REMOTE(self)); /* not changed */ if (g_strcmp0(priv->checksum, checksum) == 0) return; g_free(priv->checksum); priv->checksum = g_strdup(checksum); } static void fwupd_remote_set_password(FwupdRemote *self, const gchar *password) { FwupdRemotePrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->password, password) == 0) return; g_free(priv->password); priv->password = g_strdup(password); } static void fwupd_remote_set_kind(FwupdRemote *self, FwupdRemoteKind kind) { FwupdRemotePrivate *priv = GET_PRIVATE(self); priv->kind = kind; } /** * fwupd_remote_set_keyring_kind: * @self: a #FwupdRemote * @keyring_kind: keyring kind e.g. #FWUPD_KEYRING_KIND_PKCS7 * * Sets the keyring kind * * Since: 1.5.3 **/ void fwupd_remote_set_keyring_kind(FwupdRemote *self, FwupdKeyringKind keyring_kind) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REMOTE(self)); priv->keyring_kind = keyring_kind; } /** * fwupd_remote_set_id: * @self: a #FwupdRemote * @id: (nullable): remote ID, e.g. "lvfs" * * Sets the remote title. * * NOTE: the ID has to be set before the URL. * * Since: 1.9.3 **/ void fwupd_remote_set_id(FwupdRemote *self, const gchar *id) { FwupdRemotePrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->id, id) == 0) return; g_free(priv->id); priv->id = g_strdup(id); g_strdelimit(priv->id, ".", '\0'); } /** * fwupd_remote_set_filename_source: * @self: a #FwupdRemote * @filename_source: (nullable): filename * * Sets the source filename. This is typically a file in `/etc/fwupd/remotes/`. * * Since: 1.6.1 **/ void fwupd_remote_set_filename_source(FwupdRemote *self, const gchar *filename_source) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REMOTE(self)); if (priv->filename_source == filename_source) return; g_free(priv->filename_source); priv->filename_source = g_strdup(filename_source); } static const gchar * fwupd_remote_get_suffix_for_keyring_kind(FwupdKeyringKind keyring_kind) { if (keyring_kind == FWUPD_KEYRING_KIND_JCAT) return ".jcat"; if (keyring_kind == FWUPD_KEYRING_KIND_GPG) return ".asc"; if (keyring_kind == FWUPD_KEYRING_KIND_PKCS7) return ".p7b"; return NULL; } static gchar * fwupd_remote_build_uri(FwupdRemote *self, const gchar *base_uri, const gchar *url_noauth, GError **error) { FwupdRemotePrivate *priv = GET_PRIVATE(self); #ifdef HAVE_LIBCURL g_autofree gchar *url = NULL; g_autoptr(curlptr) tmp_uri = NULL; g_autoptr(CURLU) uri = curl_url(); /* the LVFS can't accept basic auth on an endpoint not expecting authentication */ if (!g_str_has_suffix(url_noauth, "/auth") && (priv->username != NULL || priv->password != NULL)) { url = g_strdup_printf("%s/auth", url_noauth); } else { url = g_strdup(url_noauth); } /* create URI, substituting if required */ if (base_uri != NULL) { g_autofree gchar *basename = NULL; g_autofree gchar *path_new = NULL; g_autoptr(curlptr) path = NULL; g_autoptr(CURLU) uri_tmp = curl_url(); if (curl_url_set(uri_tmp, CURLUPART_URL, url, 0) != CURLUE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to parse url '%s'", url); return NULL; } (void)curl_url_get(uri_tmp, CURLUPART_PATH, &path, 0); basename = g_path_get_basename(path); path_new = g_build_filename(priv->firmware_base_uri, basename, NULL); (void)curl_url_set(uri, CURLUPART_URL, path_new, 0); /* use the base URI of the metadata to build the full path */ } else if (g_strstr_len(url, -1, "/") == NULL) { g_autofree gchar *basename = NULL; g_autofree gchar *path_new = NULL; g_autoptr(curlptr) path = NULL; if (curl_url_set(uri, CURLUPART_URL, priv->metadata_uri, 0) != CURLUE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to parse url '%s'", priv->metadata_uri); return NULL; } (void)curl_url_get(uri, CURLUPART_PATH, &path, 0); basename = g_path_get_dirname(path); path_new = g_build_filename(basename, url, NULL); (void)curl_url_set(uri, CURLUPART_URL, path_new, 0); /* a normal URI */ } else { if (curl_url_set(uri, CURLUPART_URL, url, 0) != CURLUE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to parse URI '%s'", url); return NULL; } } /* set the escaped username and password */ if (priv->username != NULL) { g_autofree gchar *user_escaped = g_uri_escape_string(priv->username, NULL, FALSE); (void)curl_url_set(uri, CURLUPART_USER, user_escaped, 0); } if (priv->password != NULL) { g_autofree gchar *pass_escaped = g_uri_escape_string(priv->password, NULL, FALSE); (void)curl_url_set(uri, CURLUPART_PASSWORD, pass_escaped, 0); } (void)curl_url_get(uri, CURLUPART_URL, &tmp_uri, 0); return g_strdup(tmp_uri); #else if (priv->firmware_base_uri != NULL) { g_autofree gchar *basename = g_path_get_basename(url_noauth); return g_build_filename(priv->firmware_base_uri, basename, NULL); } if (g_strstr_len(url_noauth, -1, "/") == NULL) { g_autofree gchar *basename = g_path_get_dirname(priv->metadata_uri); return g_build_filename(basename, url_noauth, NULL); } return g_strdup(url_noauth); #endif } /** * fwupd_remote_set_metadata_uri: * @self: a #FwupdRemote * @metadata_uri: (nullable): metadata URI * * Sets the remote metadata URI. * * NOTE: This has to be set before the username and password. * * Since: 1.8.13 **/ void fwupd_remote_set_metadata_uri(FwupdRemote *self, const gchar *metadata_uri) { FwupdRemotePrivate *priv = GET_PRIVATE(self); const gchar *suffix; g_return_if_fail(FWUPD_IS_REMOTE(self)); /* not changed */ if (g_strcmp0(priv->metadata_uri, metadata_uri) == 0) return; /* save this so we can export the object as a GVariant */ g_free(priv->metadata_uri); priv->metadata_uri = g_strdup(metadata_uri); /* generate the signature URI too */ suffix = fwupd_remote_get_suffix_for_keyring_kind(priv->keyring_kind); if (suffix != NULL) { g_free(priv->metadata_uri_sig); priv->metadata_uri_sig = g_strconcat(metadata_uri, suffix, NULL); } } /* note, this has to be set after MetadataURI */ static void fwupd_remote_set_firmware_base_uri(FwupdRemote *self, const gchar *firmware_base_uri) { FwupdRemotePrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->firmware_base_uri, firmware_base_uri) == 0) return; g_free(priv->firmware_base_uri); priv->firmware_base_uri = g_strdup(firmware_base_uri); } static void fwupd_remote_set_report_uri(FwupdRemote *self, const gchar *report_uri) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_autofree gchar *report_uri_safe = fwupd_strdup_nonempty(report_uri); /* not changed */ if (g_strcmp0(priv->report_uri, report_uri_safe) == 0) return; g_free(priv->report_uri); priv->report_uri = g_steal_pointer(&report_uri_safe); } static void fwupd_remote_set_security_report_uri(FwupdRemote *self, const gchar *security_report_uri) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_autofree gchar *security_report_uri_safe = fwupd_strdup_nonempty(security_report_uri); /* not changed */ if (g_strcmp0(priv->security_report_uri, security_report_uri_safe) == 0) return; g_free(priv->security_report_uri); priv->security_report_uri = g_steal_pointer(&security_report_uri_safe); } /** * fwupd_remote_kind_from_string: * @kind: (nullable): a string, e.g. `download` * * Converts an printable string to an enumerated type. * * Returns: a #FwupdRemoteKind, e.g. %FWUPD_REMOTE_KIND_DOWNLOAD * * Since: 0.9.6 **/ FwupdRemoteKind fwupd_remote_kind_from_string(const gchar *kind) { if (g_strcmp0(kind, "download") == 0) return FWUPD_REMOTE_KIND_DOWNLOAD; if (g_strcmp0(kind, "local") == 0) return FWUPD_REMOTE_KIND_LOCAL; if (g_strcmp0(kind, "directory") == 0) return FWUPD_REMOTE_KIND_DIRECTORY; return FWUPD_REMOTE_KIND_UNKNOWN; } /** * fwupd_remote_kind_to_string: * @kind: a #FwupdRemoteKind, e.g. %FWUPD_REMOTE_KIND_DOWNLOAD * * Converts an enumerated type to a printable string. * * Returns: a string, e.g. `download` * * Since: 0.9.6 **/ const gchar * fwupd_remote_kind_to_string(FwupdRemoteKind kind) { if (kind == FWUPD_REMOTE_KIND_DOWNLOAD) return "download"; if (kind == FWUPD_REMOTE_KIND_LOCAL) return "local"; if (kind == FWUPD_REMOTE_KIND_DIRECTORY) return "directory"; return NULL; } /** * fwupd_remote_set_filename_cache: * @self: a #FwupdRemote * @filename: (nullable): filename string * * Sets the remote filename cache filename, typically only useful in the self tests. * * Since: 1.8.2 **/ void fwupd_remote_set_filename_cache(FwupdRemote *self, const gchar *filename) { FwupdRemotePrivate *priv = GET_PRIVATE(self); const gchar *suffix; g_return_if_fail(FWUPD_IS_REMOTE(self)); /* not changed */ if (g_strcmp0(priv->filename_cache, filename) == 0) return; g_free(priv->filename_cache); priv->filename_cache = g_strdup(filename); /* create for all remote types */ suffix = fwupd_remote_get_suffix_for_keyring_kind(priv->keyring_kind); if (suffix != NULL) { g_free(priv->filename_cache_sig); priv->filename_cache_sig = g_strconcat(filename, suffix, NULL); } } static void fwupd_remote_set_order_before(FwupdRemote *self, const gchar *order_before) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_clear_pointer(&priv->order_before, g_strfreev); if (order_before != NULL) priv->order_before = g_strsplit_set(order_before, ",:;", -1); } static void fwupd_remote_set_order_after(FwupdRemote *self, const gchar *order_after) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_clear_pointer(&priv->order_after, g_strfreev); if (order_after != NULL) priv->order_after = g_strsplit_set(order_after, ",:;", -1); } /** * fwupd_remote_setup: * @self: a #FwupdRemote * @error: (nullable): optional return location for an error * * Sets up the remote ready for use, checking that required parameters have * been set. Calling this method multiple times has no effect. * * Returns: %TRUE for success * * Since: 1.6.1 **/ gboolean fwupd_remote_setup(FwupdRemote *self, GError **error) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* we can override, hence the extra section */ if (priv->kind == FWUPD_REMOTE_KIND_UNKNOWN) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "metadata kind invalid"); return FALSE; } /* some validation for DOWNLOAD types */ if (priv->kind == FWUPD_REMOTE_KIND_DOWNLOAD) { g_autofree gchar *filename_cache = NULL; if (priv->remotes_dir == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "remotes directory not set"); return FALSE; } /* set cache to /var/lib... */ if (priv->metadata_uri == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "metadata URI not set"); return FALSE; } if (g_str_has_suffix(priv->metadata_uri, ".xml.zst")) { filename_cache = g_build_filename(priv->remotes_dir, priv->id, "metadata.xml.zst", NULL); } else if (g_str_has_suffix(priv->metadata_uri, ".xml.xz")) { filename_cache = g_build_filename(priv->remotes_dir, priv->id, "metadata.xml.xz", NULL); } else { filename_cache = g_build_filename(priv->remotes_dir, priv->id, "metadata.xml.gz", NULL); } fwupd_remote_set_filename_cache(self, filename_cache); } /* some validation for DIRECTORY types */ if (priv->kind == FWUPD_REMOTE_KIND_DIRECTORY) { if (priv->keyring_kind != FWUPD_KEYRING_KIND_NONE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "keyring kind %s is not supported with directory remote", fwupd_keyring_kind_to_string(priv->keyring_kind)); return FALSE; } if (priv->firmware_base_uri != NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Directory remotes don't support firmware base URI"); return FALSE; } } /* load the signature checksum */ if (priv->filename_cache_sig != NULL && g_file_test(priv->filename_cache_sig, G_FILE_TEST_EXISTS)) { gsize sz = 0; g_autofree gchar *buf = NULL; g_autoptr(GChecksum) checksum_sig = g_checksum_new(G_CHECKSUM_SHA256); if (!g_file_get_contents(priv->filename_cache_sig, &buf, &sz, error)) { g_prefix_error(error, "failed to get signature checksum: "); return FALSE; } g_checksum_update(checksum_sig, (guchar *)buf, (gssize)sz); fwupd_remote_set_checksum(self, g_checksum_get_string(checksum_sig)); } else { fwupd_remote_set_checksum(self, NULL); } /* success */ return TRUE; } /** * fwupd_remote_load_from_filename: * @self: a #FwupdRemote * @filename: (not nullable): a filename * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Loads metadata about the remote from a keyfile. * This can be called zero or multiple times for each remote. * * Returns: %TRUE for success * * Since: 0.9.3 **/ gboolean fwupd_remote_load_from_filename(FwupdRemote *self, const gchar *filename, GCancellable *cancellable, GError **error) { FwupdRemotePrivate *priv = GET_PRIVATE(self); const gchar *group = "fwupd Remote"; g_autofree gchar *id = NULL; g_autoptr(GKeyFile) kf = NULL; g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); g_return_val_if_fail(filename != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* set ID */ id = g_path_get_basename(filename); fwupd_remote_set_id(self, id); /* load file */ kf = g_key_file_new(); if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_NONE, error)) return FALSE; /* optional verification type */ if (g_key_file_has_key(kf, group, "Keyring", NULL)) { g_autofree gchar *tmp = g_key_file_get_string(kf, group, "Keyring", NULL); priv->keyring_kind = fwupd_keyring_kind_from_string(tmp); if (priv->keyring_kind == FWUPD_KEYRING_KIND_UNKNOWN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "keyring kind '%s' unknown", tmp); return FALSE; } } /* the first remote sets the URI, even if it's file:// to the cache */ if (g_key_file_has_key(kf, group, "MetadataURI", NULL)) { g_autofree gchar *tmp = g_key_file_get_string(kf, group, "MetadataURI", NULL); if (g_str_has_prefix(tmp, "file://")) { const gchar *filename_cache = tmp; if (g_str_has_prefix(filename_cache, "file://")) filename_cache += 7; fwupd_remote_set_filename_cache(self, filename_cache); if (g_file_test(filename_cache, G_FILE_TEST_IS_DIR)) priv->kind = FWUPD_REMOTE_KIND_DIRECTORY; else priv->kind = FWUPD_REMOTE_KIND_LOCAL; } else if (g_str_has_prefix(tmp, "http://") || g_str_has_prefix(tmp, "https://") || g_str_has_prefix(tmp, "ipfs://") || g_str_has_prefix(tmp, "ipns://")) { priv->kind = FWUPD_REMOTE_KIND_DOWNLOAD; priv->refresh_interval = FWUPD_REMOTE_CONFIG_DEFAULT_REFRESH_INTERVAL; fwupd_remote_set_metadata_uri(self, tmp); } } /* all keys are optional */ if (g_key_file_has_key(kf, group, "Enabled", NULL)) { if (g_key_file_get_boolean(kf, group, "Enabled", NULL)) fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_ENABLED); else fwupd_remote_remove_flag(self, FWUPD_REMOTE_FLAG_ENABLED); } if (g_key_file_has_key(kf, group, "ApprovalRequired", NULL)) { if (g_key_file_get_boolean(kf, group, "ApprovalRequired", NULL)) fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED); else fwupd_remote_remove_flag(self, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED); } if (g_key_file_has_key(kf, group, "Title", NULL)) { g_autofree gchar *tmp = g_key_file_get_string(kf, group, "Title", NULL); fwupd_remote_set_title(self, tmp); } if (g_key_file_has_key(kf, group, "RefreshInterval", NULL)) priv->refresh_interval = g_key_file_get_uint64(kf, group, "RefreshInterval", NULL); if (g_key_file_has_key(kf, group, "ReportURI", NULL)) { g_autofree gchar *tmp = g_key_file_get_string(kf, group, "ReportURI", NULL); fwupd_remote_set_report_uri(self, tmp); } if (g_key_file_has_key(kf, group, "SecurityReportURI", NULL)) { g_autofree gchar *tmp = g_key_file_get_string(kf, group, "SecurityReportURI", NULL); fwupd_remote_set_security_report_uri(self, tmp); } if (g_key_file_has_key(kf, group, "Username", NULL)) { g_autofree gchar *tmp = g_key_file_get_string(kf, group, "Username", NULL); fwupd_remote_set_username(self, tmp); } if (g_key_file_has_key(kf, group, "Password", NULL)) { g_autofree gchar *tmp = g_key_file_get_string(kf, group, "Password", NULL); fwupd_remote_set_password(self, tmp); } if (g_key_file_has_key(kf, group, "FirmwareBaseURI", NULL)) { g_autofree gchar *tmp = g_key_file_get_string(kf, group, "FirmwareBaseURI", NULL); fwupd_remote_set_firmware_base_uri(self, tmp); } if (g_key_file_has_key(kf, group, "OrderBefore", NULL)) { g_autofree gchar *tmp = g_key_file_get_string(kf, group, "OrderBefore", NULL); fwupd_remote_set_order_before(self, tmp); } if (g_key_file_has_key(kf, group, "OrderAfter", NULL)) { g_autofree gchar *tmp = g_key_file_get_string(kf, group, "OrderAfter", NULL); fwupd_remote_set_order_after(self, tmp); } if (g_key_file_has_key(kf, group, "AutomaticReports", NULL)) { if (g_key_file_get_boolean(kf, group, "AutomaticReports", NULL)) fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS); else fwupd_remote_remove_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS); } if (g_key_file_has_key(kf, group, "AutomaticSecurityReports", NULL)) { if (g_key_file_get_boolean(kf, group, "AutomaticSecurityReports", NULL)) fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS); else fwupd_remote_remove_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS); } /* old versions of fwupd used empty strings to mean "unset" and the package manager * might not have replaced the file marked as a config file due to modification */ if (g_strcmp0(priv->username, "") == 0 && g_strcmp0(priv->password, "") == 0) { fwupd_remote_set_username(self, NULL); fwupd_remote_set_password(self, NULL); } /* success */ fwupd_remote_set_filename_source(self, filename); return TRUE; } /** * fwupd_remote_save_to_filename: * @self: a #FwupdRemote * @filename: (not nullable): a filename * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Saves metadata about the remote to a keyfile. * * Returns: %TRUE for success * * Since: 1.8.13 **/ gboolean fwupd_remote_save_to_filename(FwupdRemote *self, const gchar *filename, GCancellable *cancellable, GError **error) { FwupdRemotePrivate *priv = GET_PRIVATE(self); const gchar *group = "fwupd Remote"; g_autoptr(GKeyFile) kf = g_key_file_new(); g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); g_return_val_if_fail(filename != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* optional keys */ if (priv->keyring_kind != FWUPD_KEYRING_KIND_UNKNOWN) { g_key_file_set_string(kf, group, "Keyring", fwupd_keyring_kind_to_string(priv->keyring_kind)); } if (priv->metadata_uri != NULL) g_key_file_set_string(kf, group, "MetadataURI", priv->metadata_uri); if (priv->title != NULL) g_key_file_set_string(kf, group, "Title", priv->title); if (priv->report_uri != NULL) g_key_file_set_string(kf, group, "ReportURI", priv->report_uri); if (priv->refresh_interval != 0) g_key_file_set_uint64(kf, group, "RefreshInterval", priv->refresh_interval); if (priv->security_report_uri != NULL) g_key_file_set_string(kf, group, "SecurityReportURI", priv->security_report_uri); if (priv->username != NULL) g_key_file_set_string(kf, group, "Username", priv->username); if (priv->password != NULL) g_key_file_set_string(kf, group, "Password", priv->password); if (priv->firmware_base_uri != NULL) g_key_file_set_string(kf, group, "FirmwareBaseURI", priv->firmware_base_uri); if (priv->order_after != NULL) { g_autofree gchar *str = g_strjoinv(";", priv->order_after); g_key_file_set_string(kf, group, "OrderAfter", str); } if (priv->order_before != NULL) { g_autofree gchar *str = g_strjoinv(";", priv->order_before); g_key_file_set_string(kf, group, "OrderBefore", str); } if (fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_ENABLED)) g_key_file_set_boolean(kf, group, "Enabled", TRUE); if (fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED)) g_key_file_set_boolean(kf, group, "ApprovalRequired", TRUE); if (fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS)) g_key_file_set_boolean(kf, group, "AutomaticReports", TRUE); if (fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS)) g_key_file_set_boolean(kf, group, "AutomaticSecurityReports", TRUE); /* save file */ return g_key_file_save_to_file(kf, filename, error); } /** * fwupd_remote_get_order_after: * @self: a #FwupdRemote * * Gets the list of remotes this plugin should be ordered after. * * Returns: (transfer none): an array * * Since: 0.9.5 **/ gchar ** fwupd_remote_get_order_after(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->order_after; } /** * fwupd_remote_get_order_before: * @self: a #FwupdRemote * * Gets the list of remotes this plugin should be ordered before. * * Returns: (transfer none): an array * * Since: 0.9.5 **/ gchar ** fwupd_remote_get_order_before(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->order_before; } /** * fwupd_remote_get_filename_cache: * @self: a #FwupdRemote * * Gets the path and filename that the remote is using for a cache. * * Returns: a string, or %NULL for unset * * Since: 0.9.6 **/ const gchar * fwupd_remote_get_filename_cache(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->filename_cache; } /** * fwupd_remote_get_filename_cache_sig: * @self: a #FwupdRemote * * Gets the path and filename that the remote is using for a signature cache. * * Returns: a string, or %NULL for unset * * Since: 0.9.7 **/ const gchar * fwupd_remote_get_filename_cache_sig(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->filename_cache_sig; } /** * fwupd_remote_get_filename_source: * @self: a #FwupdRemote * * Gets the path and filename of the remote itself, typically a `.conf` file. * * Returns: a string, or %NULL for unset * * Since: 0.9.8 **/ const gchar * fwupd_remote_get_filename_source(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->filename_source; } /** * fwupd_remote_get_priority: * @self: a #FwupdRemote * * Gets the priority of the remote, where bigger numbers are better. * * Returns: a priority, or 0 for the default value * * Since: 0.9.5 **/ gint fwupd_remote_get_priority(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), 0); return priv->priority; } /** * fwupd_remote_get_kind: * @self: a #FwupdRemote * * Gets the kind of the remote. * * Returns: a #FwupdRemoteKind, e.g. #FWUPD_REMOTE_KIND_LOCAL * * Since: 0.9.6 **/ FwupdRemoteKind fwupd_remote_get_kind(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), 0); return priv->kind; } /** * fwupd_remote_get_keyring_kind: * @self: a #FwupdRemote * * Gets the keyring kind of the remote. * * Returns: a #FwupdKeyringKind, e.g. #FWUPD_KEYRING_KIND_GPG * * Since: 0.9.7 **/ FwupdKeyringKind fwupd_remote_get_keyring_kind(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), 0); return priv->keyring_kind; } /** * fwupd_remote_get_age: * @self: a #FwupdRemote * * Gets the age of the remote in seconds. * * Returns: a age, or %G_MAXUINT64 for unavailable * * Since: 0.9.5 **/ guint64 fwupd_remote_get_age(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); guint64 now; g_return_val_if_fail(FWUPD_IS_REMOTE(self), 0); now = (guint64)g_get_real_time() / G_USEC_PER_SEC; if (priv->mtime > now) return G_MAXUINT64; return now - priv->mtime; } /** * fwupd_remote_set_remotes_dir: * @self: a #FwupdRemote * @directory: (nullable): Remotes directory * * Sets the directory to store remote data * * Since: 1.3.1 **/ void fwupd_remote_set_remotes_dir(FwupdRemote *self, const gchar *directory) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REMOTE(self)); /* not changed */ if (g_strcmp0(priv->remotes_dir, directory) == 0) return; g_free(priv->remotes_dir); priv->remotes_dir = g_strdup(directory); } /** * fwupd_remote_set_priority: * @self: a #FwupdRemote * @priority: an integer, where 1 is better * * Sets the plugin priority. * * Since: 0.9.5 **/ void fwupd_remote_set_priority(FwupdRemote *self, gint priority) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REMOTE(self)); priv->priority = priority; } /** * fwupd_remote_set_mtime: * @self: a #FwupdRemote * @mtime: a UNIX timestamp * * Sets the plugin modification time. * * Since: 0.9.5 **/ void fwupd_remote_set_mtime(FwupdRemote *self, guint64 mtime) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REMOTE(self)); priv->mtime = mtime; } /** * fwupd_remote_get_refresh_interval: * @self: a #FwupdRemote * * Sets the plugin refresh interval in seconds. * * Returns: value in seconds * * Since: 1.9.4 **/ guint64 fwupd_remote_get_refresh_interval(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), G_MAXUINT64); return priv->refresh_interval; } /** * fwupd_remote_get_username: * @self: a #FwupdRemote * * Gets the username configured for the remote. * * Returns: a string, or %NULL for unset * * Since: 0.9.5 **/ const gchar * fwupd_remote_get_username(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->username; } /** * fwupd_remote_get_password: * @self: a #FwupdRemote * * Gets the password configured for the remote. * * Returns: a string, or %NULL for unset * * Since: 0.9.5 **/ const gchar * fwupd_remote_get_password(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->password; } /** * fwupd_remote_get_title: * @self: a #FwupdRemote * * Gets the remote title, e.g. `Linux Vendor Firmware Service`. * * Returns: a string, or %NULL if unset * * Since: 0.9.8 **/ const gchar * fwupd_remote_get_title(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->title; } /** * fwupd_remote_get_agreement: * @self: a #FwupdRemote * * Gets the remote agreement in AppStream markup format * * Returns: a string, or %NULL if unset * * Since: 1.0.7 **/ const gchar * fwupd_remote_get_agreement(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->agreement; } /** * fwupd_remote_get_remotes_dir: * @self: a #FwupdRemote * * Gets the base directory for storing remote metadata * * Returns: a string, or %NULL if unset * * Since: 1.3.1 **/ const gchar * fwupd_remote_get_remotes_dir(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->remotes_dir; } /** * fwupd_remote_get_checksum: * @self: a #FwupdRemote * * Gets the remote signature checksum. * * Returns: a string, or %NULL if unset * * Since: 1.0.0 **/ const gchar * fwupd_remote_get_checksum(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->checksum_sig; } /** * fwupd_remote_get_checksum_metadata: * @self: a #FwupdRemote * * Gets the remote metadata checksum. * * Returns: a string, or %NULL if unset * * Since: 1.9.4 **/ const gchar * fwupd_remote_get_checksum_metadata(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->checksum; } /** * fwupd_remote_build_firmware_uri: * @self: a #FwupdRemote * @url: (not nullable): the URL to use * @error: (nullable): optional return location for an error * * Builds a URI for the URL using the username and password set for the remote, * including any basename URI substitution. * * Returns: (transfer full): a URI, or %NULL for error * * Since: 0.9.7 **/ gchar * fwupd_remote_build_firmware_uri(FwupdRemote *self, const gchar *url, GError **error) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); g_return_val_if_fail(url != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return fwupd_remote_build_uri(self, priv->firmware_base_uri, url, error); } /** * fwupd_remote_build_report_uri: * @self: a #FwupdRemote * @error: (nullable): optional return location for an error * * Builds a URI for the URL using the username and password set for the remote. * * Returns: (transfer full): a URI, or %NULL for error * * Since: 1.9.1 **/ gchar * fwupd_remote_build_report_uri(FwupdRemote *self, GError **error) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return fwupd_remote_build_uri(self, NULL, priv->report_uri, error); } /** * fwupd_remote_build_metadata_sig_uri: * @self: a #FwupdRemote * @error: (nullable): optional return location for an error * * Builds a URI for the metadata using the username and password set for the remote. * * Returns: (transfer full): a URI, or %NULL for error * * Since: 1.9.8 **/ gchar * fwupd_remote_build_metadata_sig_uri(FwupdRemote *self, GError **error) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return fwupd_remote_build_uri(self, NULL, priv->metadata_uri_sig, error); } /** * fwupd_remote_build_metadata_uri: * @self: a #FwupdRemote * @error: (nullable): optional return location for an error * * Builds a URI for the metadata signature using the username and password set for the remote. * * Returns: (transfer full): a URI, or %NULL for error * * Since: 1.9.8 **/ gchar * fwupd_remote_build_metadata_uri(FwupdRemote *self, GError **error) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return fwupd_remote_build_uri(self, NULL, priv->metadata_uri, error); } /** * fwupd_remote_get_report_uri: * @self: a #FwupdRemote * * Gets the URI for the remote reporting. * * Returns: (transfer none): a URI, or %NULL for invalid. * * Since: 1.0.4 **/ const gchar * fwupd_remote_get_report_uri(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->report_uri; } /** * fwupd_remote_get_security_report_uri: * @self: a #FwupdRemote * * Gets the URI for the security report. * * Returns: (transfer none): a URI, or %NULL for invalid. * * Since: 1.5.0 **/ const gchar * fwupd_remote_get_security_report_uri(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->security_report_uri; } /** * fwupd_remote_get_metadata_uri: * @self: a #FwupdRemote * * Gets the URI for the remote metadata. * * Returns: (transfer none): a URI, or %NULL for invalid. * * Since: 0.9.7 **/ const gchar * fwupd_remote_get_metadata_uri(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->metadata_uri; } static gboolean fwupd_remote_load_signature_jcat(FwupdRemote *self, JcatFile *jcat_file, GError **error) { FwupdRemotePrivate *priv = GET_PRIVATE(self); const gchar *id; g_autofree gchar *basename = NULL; g_autofree gchar *baseuri = NULL; g_autofree gchar *metadata_uri = NULL; g_autoptr(GPtrArray) jcat_blobs = NULL; g_autoptr(JcatItem) jcat_item = NULL; /* this seems pointless to get the item by ID then just read the ID, * but _get_item_by_id() uses the AliasIds as a fallback */ basename = g_path_get_basename(priv->metadata_uri); jcat_item = jcat_file_get_item_by_id(jcat_file, basename, NULL); if (jcat_item == NULL) { /* if we're using libjcat 0.1.0 just get the default item */ jcat_item = jcat_file_get_item_default(jcat_file, error); if (jcat_item == NULL) return FALSE; } id = jcat_item_get_id(jcat_item); if (id == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "No ID for JCat item"); return FALSE; } /* replace the URI if required */ baseuri = g_path_get_dirname(priv->metadata_uri); metadata_uri = g_build_path("/", baseuri, id, NULL); if (g_strcmp0(metadata_uri, priv->metadata_uri) != 0) { g_info("changing metadata URI from %s to %s", priv->metadata_uri, metadata_uri); g_free(priv->metadata_uri); priv->metadata_uri = g_steal_pointer(&metadata_uri); } /* look for the metadata hash */ jcat_blobs = jcat_item_get_blobs_by_kind(jcat_item, JCAT_BLOB_KIND_SHA256); if (jcat_blobs->len == 1) { JcatBlob *blob = g_ptr_array_index(jcat_blobs, 0); g_autofree gchar *hash = jcat_blob_get_data_as_string(blob); fwupd_remote_set_checksum_metadata(self, hash); } /* success */ return TRUE; } /** * fwupd_remote_load_signature_bytes: * @self: a #FwupdRemote * @bytes: (not nullable): data blob * @error: (nullable): optional return location for an error * * Parses the signature, updating the metadata URI as appropriate. * * This can only be called for remotes with `Keyring=jcat` which is * the default for most remotes. * * Returns: %TRUE for success * * Since: 1.4.5 **/ gboolean fwupd_remote_load_signature_bytes(FwupdRemote *self, GBytes *bytes, GError **error) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_autoptr(GInputStream) istr = NULL; g_autoptr(JcatFile) jcat_file = jcat_file_new(); g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); g_return_val_if_fail(bytes != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* sanity check */ if (priv->keyring_kind != FWUPD_KEYRING_KIND_JCAT) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "only supported for JCat remotes"); return FALSE; } istr = g_memory_input_stream_new_from_bytes(bytes); if (!jcat_file_import_stream(jcat_file, istr, JCAT_IMPORT_FLAG_NONE, NULL, error)) return FALSE; return fwupd_remote_load_signature_jcat(self, jcat_file, error); } /** * fwupd_remote_load_signature: * @self: a #FwupdRemote * @filename: (not nullable): a filename * @error: (nullable): optional return location for an error * * Parses the signature, updating the metadata URI as appropriate. * * Returns: %TRUE for success * * Since: 1.4.0 **/ gboolean fwupd_remote_load_signature(FwupdRemote *self, const gchar *filename, GError **error) { g_autoptr(GFile) gfile = NULL; g_autoptr(JcatFile) jcat_file = jcat_file_new(); g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); g_return_val_if_fail(filename != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* load JCat file */ gfile = g_file_new_for_path(filename); if (!jcat_file_import_file(jcat_file, gfile, JCAT_IMPORT_FLAG_NONE, NULL, error)) return FALSE; return fwupd_remote_load_signature_jcat(self, jcat_file, error); } /** * fwupd_remote_get_metadata_uri_sig: * @self: a #FwupdRemote * * Gets the URI for the remote metadata signature. * * Returns: (transfer none): a URI, or %NULL for invalid. * * Since: 0.9.7 **/ const gchar * fwupd_remote_get_metadata_uri_sig(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->metadata_uri_sig; } /** * fwupd_remote_get_firmware_base_uri: * @self: a #FwupdRemote * * Gets the base URI for firmware. * * Returns: (transfer none): a URI, or %NULL for unset. * * Since: 0.9.7 **/ const gchar * fwupd_remote_get_firmware_base_uri(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->firmware_base_uri; } /** * fwupd_remote_get_enabled: * @self: a #FwupdRemote * * Gets if the remote is enabled and should be used. * * Returns: a #TRUE if the remote is enabled * * Since: 0.9.3 **/ gboolean fwupd_remote_get_enabled(FwupdRemote *self) { g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); return fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_ENABLED); } /** * fwupd_remote_set_enabled: * @self: a #FwupdRemote * @enabled: boolean * * Sets if the remote is enabled and should be used. * * Since: 1.8.13 **/ void fwupd_remote_set_enabled(FwupdRemote *self, gboolean enabled) { g_return_if_fail(FWUPD_IS_REMOTE(self)); if (enabled) fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_ENABLED); else fwupd_remote_remove_flag(self, FWUPD_REMOTE_FLAG_ENABLED); } /** * fwupd_remote_get_automatic_reports: * @self: a #FwupdRemote * * Gets if reports should be automatically uploaded to this remote * * Returns: a #TRUE if the remote should have reports uploaded automatically * * Since: 1.3.3 **/ gboolean fwupd_remote_get_automatic_reports(FwupdRemote *self) { g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); return fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS); } /** * fwupd_remote_get_automatic_security_reports: * @self: a #FwupdRemote * * Gets if security reports should be automatically uploaded to this remote * * Returns: a #TRUE if the remote should have reports uploaded automatically * * Since: 1.5.0 **/ gboolean fwupd_remote_get_automatic_security_reports(FwupdRemote *self) { g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); return fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS); } /** * fwupd_remote_needs_refresh: * @self: a #FwupdRemote * * Gets if the metadata remote needs re-downloading. * * Returns: a #TRUE if the remote contents are considered old * * Since: 1.9.4 **/ gboolean fwupd_remote_needs_refresh(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); if (!fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_ENABLED)) return FALSE; if (priv->kind != FWUPD_REMOTE_KIND_DOWNLOAD) return FALSE; return fwupd_remote_get_age(self) > priv->refresh_interval; } /** * fwupd_remote_get_approval_required: * @self: a #FwupdRemote * * Gets if firmware from the remote should be checked against the list * of a approved checksums. * * Returns: a #TRUE if the remote is restricted * * Since: 1.2.6 **/ gboolean fwupd_remote_get_approval_required(FwupdRemote *self) { g_return_val_if_fail(FWUPD_IS_REMOTE(self), FALSE); return fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED); } /** * fwupd_remote_get_id: * @self: a #FwupdRemote * * Gets the remote ID, e.g. `lvfs-testing`. * * Returns: a string, or %NULL if unset * * Since: 0.9.3 **/ const gchar * fwupd_remote_get_id(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); return priv->id; } static void fwupd_remote_set_from_variant_iter(FwupdRemote *self, GVariantIter *iter) { FwupdRemotePrivate *priv = GET_PRIVATE(self); GVariant *value; const gchar *key; g_autoptr(GVariantIter) iter2 = g_variant_iter_copy(iter); g_autoptr(GVariantIter) iter3 = g_variant_iter_copy(iter); /* three passes, as we have to construct Id -> Url -> * */ while (g_variant_iter_loop(iter, "{&sv}", &key, &value)) { if (g_strcmp0(key, FWUPD_RESULT_KEY_REMOTE_ID) == 0) fwupd_remote_set_id(self, g_variant_get_string(value, NULL)); if (g_strcmp0(key, "Type") == 0) fwupd_remote_set_kind(self, g_variant_get_uint32(value)); if (g_strcmp0(key, FWUPD_RESULT_KEY_FLAGS) == 0) fwupd_remote_set_flags(self, g_variant_get_uint64(value)); if (g_strcmp0(key, "Keyring") == 0) fwupd_remote_set_keyring_kind(self, g_variant_get_uint32(value)); } while (g_variant_iter_loop(iter2, "{&sv}", &key, &value)) { if (g_strcmp0(key, FWUPD_RESULT_KEY_URI) == 0) fwupd_remote_set_metadata_uri(self, g_variant_get_string(value, NULL)); if (g_strcmp0(key, "FilenameCache") == 0) fwupd_remote_set_filename_cache(self, g_variant_get_string(value, NULL)); if (g_strcmp0(key, "FilenameSource") == 0) fwupd_remote_set_filename_source(self, g_variant_get_string(value, NULL)); if (g_strcmp0(key, "ReportUri") == 0) fwupd_remote_set_report_uri(self, g_variant_get_string(value, NULL)); if (g_strcmp0(key, "SecurityReportUri") == 0) fwupd_remote_set_security_report_uri(self, g_variant_get_string(value, NULL)); } while (g_variant_iter_loop(iter3, "{&sv}", &key, &value)) { if (g_strcmp0(key, "Username") == 0) { fwupd_remote_set_username(self, g_variant_get_string(value, NULL)); } else if (g_strcmp0(key, "Password") == 0) { fwupd_remote_set_password(self, g_variant_get_string(value, NULL)); } else if (g_strcmp0(key, "Title") == 0) { fwupd_remote_set_title(self, g_variant_get_string(value, NULL)); } else if (g_strcmp0(key, "Agreement") == 0) { fwupd_remote_set_agreement(self, g_variant_get_string(value, NULL)); } else if (g_strcmp0(key, FWUPD_RESULT_KEY_CHECKSUM) == 0) { fwupd_remote_set_checksum(self, g_variant_get_string(value, NULL)); } else if (g_strcmp0(key, "Enabled") == 0) { if (g_variant_get_boolean(value)) fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_ENABLED); } else if (g_strcmp0(key, "ApprovalRequired") == 0) { if (g_variant_get_boolean(value)) fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED); } else if (g_strcmp0(key, "Priority") == 0) { priv->priority = g_variant_get_int32(value); } else if (g_strcmp0(key, "ModificationTime") == 0) { priv->mtime = g_variant_get_uint64(value); } else if (g_strcmp0(key, "RefreshInterval") == 0) { priv->refresh_interval = g_variant_get_uint64(value); } else if (g_strcmp0(key, "FirmwareBaseUri") == 0) { fwupd_remote_set_firmware_base_uri(self, g_variant_get_string(value, NULL)); } else if (g_strcmp0(key, "AutomaticReports") == 0) { /* we can probably stop doing proxying flags when we next branch */ if (g_variant_get_boolean(value)) fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS); } else if (g_strcmp0(key, "AutomaticSecurityReports") == 0) { if (g_variant_get_boolean(value)) fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS); } } } /** * fwupd_remote_to_variant: * @self: a #FwupdRemote * * Serialize the remote data. * * Returns: the serialized data, or %NULL for error * * Since: 1.0.0 **/ GVariant * fwupd_remote_to_variant(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); GVariantBuilder builder; g_return_val_if_fail(FWUPD_IS_REMOTE(self), NULL); /* create an array with all the metadata in */ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); if (priv->id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_REMOTE_ID, g_variant_new_string(priv->id)); } if (priv->flags != 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_FLAGS, g_variant_new_uint64(priv->flags)); } if (priv->username != NULL) { g_variant_builder_add(&builder, "{sv}", "Username", g_variant_new_string(priv->username)); } if (priv->password != NULL) { g_variant_builder_add(&builder, "{sv}", "Password", g_variant_new_string(priv->password)); } if (priv->title != NULL) { g_variant_builder_add(&builder, "{sv}", "Title", g_variant_new_string(priv->title)); } if (priv->agreement != NULL) { g_variant_builder_add(&builder, "{sv}", "Agreement", g_variant_new_string(priv->agreement)); } if (priv->checksum_sig != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_CHECKSUM, g_variant_new_string(priv->checksum_sig)); } if (priv->metadata_uri != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_URI, g_variant_new_string(priv->metadata_uri)); } if (priv->report_uri != NULL) { g_variant_builder_add(&builder, "{sv}", "ReportUri", g_variant_new_string(priv->report_uri)); } if (priv->security_report_uri != NULL) { g_variant_builder_add(&builder, "{sv}", "SecurityReportUri", g_variant_new_string(priv->security_report_uri)); } if (priv->firmware_base_uri != NULL) { g_variant_builder_add(&builder, "{sv}", "FirmwareBaseUri", g_variant_new_string(priv->firmware_base_uri)); } if (priv->priority != 0) { g_variant_builder_add(&builder, "{sv}", "Priority", g_variant_new_int32(priv->priority)); } if (priv->kind != FWUPD_REMOTE_KIND_UNKNOWN) { g_variant_builder_add(&builder, "{sv}", "Type", g_variant_new_uint32(priv->kind)); } if (priv->keyring_kind != FWUPD_KEYRING_KIND_UNKNOWN) { g_variant_builder_add(&builder, "{sv}", "Keyring", g_variant_new_uint32(priv->keyring_kind)); } if (priv->mtime != 0) { g_variant_builder_add(&builder, "{sv}", "ModificationTime", g_variant_new_uint64(priv->mtime)); } if (priv->refresh_interval != 0) { g_variant_builder_add(&builder, "{sv}", "RefreshInterval", g_variant_new_uint64(priv->refresh_interval)); } if (priv->filename_cache != NULL) { g_variant_builder_add(&builder, "{sv}", "FilenameCache", g_variant_new_string(priv->filename_cache)); } if (priv->filename_source != NULL) { g_variant_builder_add(&builder, "{sv}", "FilenameSource", g_variant_new_string(priv->filename_source)); } if (priv->remotes_dir != NULL) { g_variant_builder_add(&builder, "{sv}", "RemotesDir", g_variant_new_string(priv->remotes_dir)); } /* we can probably stop doing proxying flags when we next branch */ g_variant_builder_add( &builder, "{sv}", "Enabled", g_variant_new_boolean(fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_ENABLED))); g_variant_builder_add( &builder, "{sv}", "ApprovalRequired", g_variant_new_boolean( fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED))); g_variant_builder_add( &builder, "{sv}", "AutomaticReports", g_variant_new_boolean( fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS))); g_variant_builder_add( &builder, "{sv}", "AutomaticSecurityReports", g_variant_new_boolean( fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS))); return g_variant_new("a{sv}", &builder); } static void fwupd_remote_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { FwupdRemote *self = FWUPD_REMOTE(obj); FwupdRemotePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_ENABLED: g_value_set_boolean(value, fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_ENABLED)); break; case PROP_APPROVAL_REQUIRED: g_value_set_boolean( value, fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED)); break; case PROP_ID: g_value_set_string(value, priv->id); break; case PROP_AUTOMATIC_REPORTS: g_value_set_boolean( value, fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS)); break; case PROP_AUTOMATIC_SECURITY_REPORTS: g_value_set_boolean( value, fwupd_remote_has_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS)); break; case PROP_FLAGS: g_value_set_uint64(value, priv->flags); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } } static void fwupd_remote_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { FwupdRemote *self = FWUPD_REMOTE(obj); switch (prop_id) { case PROP_ENABLED: if (g_value_get_boolean(value)) fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_ENABLED); else fwupd_remote_remove_flag(self, FWUPD_REMOTE_FLAG_ENABLED); break; case PROP_APPROVAL_REQUIRED: if (g_value_get_boolean(value)) fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED); else fwupd_remote_remove_flag(self, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED); break; case PROP_ID: fwupd_remote_set_id(self, g_value_get_string(value)); break; case PROP_AUTOMATIC_REPORTS: if (g_value_get_boolean(value)) fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS); else fwupd_remote_remove_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS); break; case PROP_AUTOMATIC_SECURITY_REPORTS: if (g_value_get_boolean(value)) fwupd_remote_add_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS); else fwupd_remote_remove_flag(self, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS); break; case PROP_FLAGS: fwupd_remote_set_flags(self, g_value_get_uint64(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } } static void fwupd_remote_class_init(FwupdRemoteClass *klass) { GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fwupd_remote_finalize; object_class->get_property = fwupd_remote_get_property; object_class->set_property = fwupd_remote_set_property; /** * FwupdRemote:id: * * The remote ID. * * Since: 0.9.3 */ pspec = g_param_spec_string("id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_ID, pspec); /** * FwupdRemote:enabled: * * If the remote is enabled and should be used. * * Since: 0.9.3 */ pspec = g_param_spec_boolean("enabled", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_ENABLED, pspec); /** * FwupdRemote:approval-required: * * If firmware from the remote should be checked against the system * list of approved firmware. * * Since: 1.2.6 */ pspec = g_param_spec_boolean("approval-required", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_APPROVAL_REQUIRED, pspec); /** * FwupdRemote:automatic-reports: * * The behavior for auto-uploading reports. * * Since: 1.3.3 */ pspec = g_param_spec_boolean("automatic-reports", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_AUTOMATIC_REPORTS, pspec); /** * FwupdRemote:automatic-security-reports: * * The behavior for auto-uploading security reports. * * Since: 1.5.0 */ pspec = g_param_spec_boolean("automatic-security-reports", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_AUTOMATIC_SECURITY_REPORTS, pspec); /** * FwupdRemote:flags: * * The remote flags. * * Since: 1.9.4 */ pspec = g_param_spec_uint64("flags", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FLAGS, pspec); } static void fwupd_remote_init(FwupdRemote *self) { FwupdRemotePrivate *priv = GET_PRIVATE(self); priv->keyring_kind = FWUPD_KEYRING_KIND_JCAT; } static void fwupd_remote_finalize(GObject *obj) { FwupdRemote *self = FWUPD_REMOTE(obj); FwupdRemotePrivate *priv = GET_PRIVATE(self); g_free(priv->id); g_free(priv->metadata_uri); g_free(priv->metadata_uri_sig); g_free(priv->firmware_base_uri); g_free(priv->report_uri); g_free(priv->security_report_uri); g_free(priv->username); g_free(priv->password); g_free(priv->title); g_free(priv->agreement); g_free(priv->remotes_dir); g_free(priv->checksum); g_free(priv->checksum_sig); g_free(priv->filename_cache); g_free(priv->filename_cache_sig); g_free(priv->filename_source); g_strfreev(priv->order_after); g_strfreev(priv->order_before); G_OBJECT_CLASS(fwupd_remote_parent_class)->finalize(obj); } /** * fwupd_remote_from_variant: * @value: (not nullable): the serialized data * * Creates a new remote using serialized data. * * Returns: (transfer full): a new #FwupdRemote, or %NULL if @value was invalid * * Since: 1.0.0 **/ FwupdRemote * fwupd_remote_from_variant(GVariant *value) { FwupdRemote *rel = NULL; const gchar *type_string; g_autoptr(GVariantIter) iter = NULL; g_return_val_if_fail(value != NULL, NULL); type_string = g_variant_get_type_string(value); if (g_strcmp0(type_string, "(a{sv})") == 0) { rel = fwupd_remote_new(); g_variant_get(value, "(a{sv})", &iter); fwupd_remote_set_from_variant_iter(rel, iter); fwupd_remote_set_from_variant_iter(rel, iter); } else if (g_strcmp0(type_string, "a{sv}") == 0) { rel = fwupd_remote_new(); g_variant_get(value, "a{sv}", &iter); fwupd_remote_set_from_variant_iter(rel, iter); } else { g_warning("type %s not known", type_string); } return rel; } /** * fwupd_remote_array_from_variant: * @value: (not nullable): the serialized data * * Creates an array of new devices using serialized data. * * Returns: (transfer container) (element-type FwupdRemote): remotes, or %NULL if @value was invalid * * Since: 1.2.10 **/ GPtrArray * fwupd_remote_array_from_variant(GVariant *value) { GPtrArray *remotes = NULL; gsize sz; g_autoptr(GVariant) untuple = NULL; g_return_val_if_fail(value != NULL, NULL); remotes = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); untuple = g_variant_get_child_value(value, 0); sz = g_variant_n_children(untuple); for (guint i = 0; i < sz; i++) { g_autoptr(GVariant) data = g_variant_get_child_value(untuple, i); FwupdRemote *remote = fwupd_remote_from_variant(data); g_ptr_array_add(remotes, remote); } return remotes; } /** * fwupd_remote_new: * * Creates a new fwupd remote. * * Returns: a new #FwupdRemote * * Since: 0.9.3 **/ FwupdRemote * fwupd_remote_new(void) { FwupdRemote *self; self = g_object_new(FWUPD_TYPE_REMOTE, NULL); return FWUPD_REMOTE(self); } fwupd-1.9.16/libfwupd/fwupd-remote.h000066400000000000000000000136251460375044200173540ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-enums.h" G_BEGIN_DECLS #define FWUPD_TYPE_REMOTE (fwupd_remote_get_type()) G_DECLARE_DERIVABLE_TYPE(FwupdRemote, fwupd_remote, FWUPD, REMOTE, GObject) struct _FwupdRemoteClass { GObjectClass parent_class; /*< private >*/ void (*_fwupd_reserved1)(void); void (*_fwupd_reserved2)(void); void (*_fwupd_reserved3)(void); void (*_fwupd_reserved4)(void); void (*_fwupd_reserved5)(void); void (*_fwupd_reserved6)(void); void (*_fwupd_reserved7)(void); }; /** * FwupdRemoteKind: * @FWUPD_REMOTE_KIND_UNKNOWN: Unknown kind * @FWUPD_REMOTE_KIND_DOWNLOAD: Requires files to be downloaded * @FWUPD_REMOTE_KIND_LOCAL: Reads files from the local machine * @FWUPD_REMOTE_KIND_DIRECTORY: Reads directory from the local machine * * The kind of remote. **/ typedef enum { FWUPD_REMOTE_KIND_UNKNOWN, FWUPD_REMOTE_KIND_DOWNLOAD, FWUPD_REMOTE_KIND_LOCAL, FWUPD_REMOTE_KIND_DIRECTORY, /* Since: 1.2.4 */ /*< private >*/ FWUPD_REMOTE_KIND_LAST } FwupdRemoteKind; /** * FwupdRemoteFlags: * @FWUPD_REMOTE_FLAG_NONE: No flags set * @FWUPD_REMOTE_FLAG_ENABLED: Is enabled * @FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED: Requires approval for each firmware * @FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS: Send firmware reports automatically * @FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS: Send security reports automatically * @FWUPD_REMOTE_FLAG_ALLOW_P2P_METADATA: Use peer-to-peer locations for metadata * @FWUPD_REMOTE_FLAG_ALLOW_P2P_FIRMWARE: Use peer-to-peer locations for firmware * * The flags available for the remote. **/ typedef enum { FWUPD_REMOTE_FLAG_NONE = 0, /* Since: 1.9.4 */ FWUPD_REMOTE_FLAG_ENABLED = 1 << 0, /* Since: 1.9.4 */ FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED = 1 << 1, /* Since: 1.9.4 */ FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS = 1 << 2, /* Since: 1.9.4 */ FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS = 1 << 3, /* Since: 1.9.4 */ FWUPD_REMOTE_FLAG_ALLOW_P2P_METADATA = 1 << 4, /* Since: 1.9.5 */ FWUPD_REMOTE_FLAG_ALLOW_P2P_FIRMWARE = 1 << 5, /* Since: 1.9.5 */ } FwupdRemoteFlags; FwupdRemoteKind fwupd_remote_kind_from_string(const gchar *kind); const gchar * fwupd_remote_kind_to_string(FwupdRemoteKind kind); const gchar * fwupd_remote_flag_to_string(FwupdRemoteFlags flag); FwupdRemoteFlags fwupd_remote_flag_from_string(const gchar *flag); FwupdRemote * fwupd_remote_new(void); const gchar * fwupd_remote_get_id(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_title(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_agreement(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_remotes_dir(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_checksum(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_checksum_metadata(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_username(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_password(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_filename_cache(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_filename_cache_sig(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_filename_source(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_firmware_base_uri(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_report_uri(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_security_report_uri(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_metadata_uri(FwupdRemote *self) G_GNUC_NON_NULL(1); const gchar * fwupd_remote_get_metadata_uri_sig(FwupdRemote *self) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(fwupd_remote_has_flag) gboolean fwupd_remote_get_enabled(FwupdRemote *self) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(fwupd_remote_has_flag) gboolean fwupd_remote_get_approval_required(FwupdRemote *self) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(fwupd_remote_has_flag) gboolean fwupd_remote_get_automatic_reports(FwupdRemote *self) G_GNUC_NON_NULL(1); G_DEPRECATED_FOR(fwupd_remote_has_flag) gboolean fwupd_remote_get_automatic_security_reports(FwupdRemote *self) G_GNUC_NON_NULL(1); guint64 fwupd_remote_get_refresh_interval(FwupdRemote *self) G_GNUC_NON_NULL(1); FwupdRemoteFlags fwupd_remote_get_flags(FwupdRemote *self) G_GNUC_NON_NULL(1); void fwupd_remote_set_flags(FwupdRemote *self, FwupdRemoteFlags flags) G_GNUC_NON_NULL(1); void fwupd_remote_add_flag(FwupdRemote *self, FwupdRemoteFlags flag) G_GNUC_NON_NULL(1); void fwupd_remote_remove_flag(FwupdRemote *self, FwupdRemoteFlags flag) G_GNUC_NON_NULL(1); gboolean fwupd_remote_has_flag(FwupdRemote *self, FwupdRemoteFlags flag) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fwupd_remote_needs_refresh(FwupdRemote *self) G_GNUC_NON_NULL(1); gint fwupd_remote_get_priority(FwupdRemote *self) G_GNUC_NON_NULL(1); guint64 fwupd_remote_get_age(FwupdRemote *self) G_GNUC_NON_NULL(1); FwupdRemoteKind fwupd_remote_get_kind(FwupdRemote *self) G_GNUC_NON_NULL(1); FwupdKeyringKind fwupd_remote_get_keyring_kind(FwupdRemote *self) G_GNUC_NON_NULL(1); gchar * fwupd_remote_build_firmware_uri(FwupdRemote *self, const gchar *url, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gchar * fwupd_remote_build_report_uri(FwupdRemote *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fwupd_remote_load_signature(FwupdRemote *self, const gchar *filename, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fwupd_remote_load_signature_bytes(FwupdRemote *self, GBytes *bytes, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); FwupdRemote * fwupd_remote_from_variant(GVariant *value) G_GNUC_NON_NULL(1); GPtrArray * fwupd_remote_array_from_variant(GVariant *value) G_GNUC_NON_NULL(1); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-report-private.h000066400000000000000000000005061460375044200210360ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-build.h" #include "fwupd-report.h" G_BEGIN_DECLS void fwupd_report_to_json(FwupdReport *self, JsonBuilder *builder) G_GNUC_NON_NULL(1, 2); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-report.c000066400000000000000000000575431460375044200173760ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fwupd-common-private.h" #include "fwupd-enums-private.h" #include "fwupd-error.h" #include "fwupd-report-private.h" /** * FwupdReport: * * A firmware report from a vendor. * * This is the LVFS formatted report that the fwupd user consumes, NOT the thing that gets uploaded. * * See also: [class@FwupdRelease] */ typedef struct { guint64 created; gchar *version_old; gchar *vendor; guint32 vendor_id; gchar *device_name; gchar *distro_id; gchar *distro_version; GHashTable *metadata; gchar *distro_variant; gchar *remote_id; FwupdReportFlags flags; } FwupdReportPrivate; enum { PROP_0, PROP_FLAGS, PROP_LAST }; G_DEFINE_TYPE_WITH_PRIVATE(FwupdReport, fwupd_report, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_report_get_instance_private(o)) /** * fwupd_report_get_created: * @self: a #FwupdReport * * Gets when the report was created. * * Returns: UTC timestamp in UNIX format, or 0 if unset * * Since: 1.8.8 **/ guint64 fwupd_report_get_created(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REPORT(self), 0); return priv->created; } /** * fwupd_report_set_created: * @self: a #FwupdReport * @created: UTC timestamp in UNIX format * * Sets when the report was created. * * Since: 1.8.8 **/ void fwupd_report_set_created(FwupdReport *self, guint64 created) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REPORT(self)); priv->created = created; } /** * fwupd_report_get_version_old: * @self: a #FwupdReport * * Gets the old version, i.e. what the upser was upgrading *from*. * * Returns: the version, or %NULL if unset * * Since: 1.8.8 **/ const gchar * fwupd_report_get_version_old(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REPORT(self), NULL); return priv->version_old; } /** * fwupd_report_set_version_old: * @self: a #FwupdReport * @version_old: (nullable): the version, e.g. `1.2.3` * * Sets the old version, i.e. what the upser was upgrading *from*. * * Since: 1.8.8 **/ void fwupd_report_set_version_old(FwupdReport *self, const gchar *version_old) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REPORT(self)); /* not changed */ if (g_strcmp0(priv->version_old, version_old) == 0) return; g_free(priv->version_old); priv->version_old = g_strdup(version_old); } /** * fwupd_report_get_vendor: * @self: a #FwupdReport * * Gets the vendor that uploaded the test result. * * Returns: the test vendor, or %NULL if unset * * Since: 1.8.8 **/ const gchar * fwupd_report_get_vendor(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REPORT(self), NULL); return priv->vendor; } /** * fwupd_report_set_vendor: * @self: a #FwupdReport * @vendor: (nullable): the vendor name * * Sets the vendor that uploaded the test result. * * Since: 1.8.8 **/ void fwupd_report_set_vendor(FwupdReport *self, const gchar *vendor) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REPORT(self)); /* not changed */ if (g_strcmp0(priv->vendor, vendor) == 0) return; g_free(priv->vendor); priv->vendor = g_strdup(vendor); } /** * fwupd_report_get_vendor_id: * @self: a #FwupdReport * * Gets the vendor identifier. The mapping is only known on the remote server, and this can be * useful to filter on different QA teams that work for the same OEM. * * Returns: the vendor ID, or 0 if unset * * Since: 1.8.8 **/ guint32 fwupd_report_get_vendor_id(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REPORT(self), 0); return priv->vendor_id; } /** * fwupd_report_set_vendor_id: * @self: a #FwupdReport * @vendor_id: the vendor ID, or 0 * * Sets the vendor identifier. The mapping is only known on the remote server, and this can be * useful to filter on different QA teams that work for the same OEM. * * Since: 1.8.8 **/ void fwupd_report_set_vendor_id(FwupdReport *self, guint32 vendor_id) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REPORT(self)); priv->vendor_id = vendor_id; } /** * fwupd_report_get_device_name: * @self: a #FwupdReport * * Gets the name of the device the update was performed on. * * Returns: the name, or %NULL if unset * * Since: 1.8.8 **/ const gchar * fwupd_report_get_device_name(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REPORT(self), NULL); return priv->device_name; } /** * fwupd_report_set_device_name: * @self: a #FwupdReport * @device_name: (nullable): the name, e.g. `LENOVO ThinkPad P1 Gen 3` * * Sets the name of the device the update was performed on. * * Since: 1.8.8 **/ void fwupd_report_set_device_name(FwupdReport *self, const gchar *device_name) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REPORT(self)); /* not changed */ if (g_strcmp0(priv->device_name, device_name) == 0) return; g_free(priv->device_name); priv->device_name = g_strdup(device_name); } /** * fwupd_report_get_distro_id: * @self: a #FwupdReport * * Gets the distribution name. * * Returns: the name, or %NULL if unset * * Since: 1.8.8 **/ const gchar * fwupd_report_get_distro_id(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REPORT(self), NULL); return priv->distro_id; } /** * fwupd_report_set_distro_id: * @self: a #FwupdReport * @distro_id: (nullable): the name, e.g. `fedora` * * Sets the distribution name. * * Since: 1.8.8 **/ void fwupd_report_set_distro_id(FwupdReport *self, const gchar *distro_id) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REPORT(self)); /* not changed */ if (g_strcmp0(priv->distro_id, distro_id) == 0) return; g_free(priv->distro_id); priv->distro_id = g_strdup(distro_id); } /** * fwupd_report_get_distro_variant: * @self: a #FwupdReport * * Gets the distribution variant. * * Returns: variant, or %NULL if unset * * Since: 1.8.8 **/ const gchar * fwupd_report_get_distro_variant(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REPORT(self), NULL); return priv->distro_variant; } /** * fwupd_report_set_distro_variant: * @self: a #FwupdReport * @distro_variant: (nullable): the variant, e.g. `workstation` * * Sets the distribution variant. * * Since: 1.8.8 **/ void fwupd_report_set_distro_variant(FwupdReport *self, const gchar *distro_variant) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REPORT(self)); /* not changed */ if (g_strcmp0(priv->distro_variant, distro_variant) == 0) return; g_free(priv->distro_variant); priv->distro_variant = g_strdup(distro_variant); } /** * fwupd_report_get_remote_id: * @self: a #FwupdReport * * Gets the remote ID. * * Returns: ID, or %NULL if unset * * Since: 1.9.3 **/ const gchar * fwupd_report_get_remote_id(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REPORT(self), NULL); return priv->remote_id; } /** * fwupd_report_set_remote_id: * @self: a #FwupdReport * @remote_id: (nullable): the remote, e.g. `lvfs` * * Sets the remote ID. * * Since: 1.9.3 **/ void fwupd_report_set_remote_id(FwupdReport *self, const gchar *remote_id) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REPORT(self)); /* not changed */ if (g_strcmp0(priv->remote_id, remote_id) == 0) return; g_free(priv->remote_id); priv->remote_id = g_strdup(remote_id); } /** * fwupd_report_get_distro_version: * @self: a #FwupdReport * * Gets the distribution version. * * Returns: a string, or %NULL if unset * * Since: 1.8.8 **/ const gchar * fwupd_report_get_distro_version(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REPORT(self), NULL); return priv->distro_version; } /** * fwupd_report_set_distro_version: * @self: a #FwupdReport * @distro_version: (nullable): a string * * Sets the distribution version. * * Since: 1.8.8 **/ void fwupd_report_set_distro_version(FwupdReport *self, const gchar *distro_version) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REPORT(self)); /* not changed */ if (g_strcmp0(priv->distro_version, distro_version) == 0) return; g_free(priv->distro_version); priv->distro_version = g_strdup(distro_version); } /** * fwupd_report_get_metadata: * @self: a #FwupdReport * * Gets the report metadata. * * Returns: (transfer none): the metadata, which may be empty * * Since: 1.8.8 **/ GHashTable * fwupd_report_get_metadata(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REPORT(self), NULL); return priv->metadata; } /** * fwupd_report_add_metadata_item: * @self: a #FwupdReport * @key: (not nullable): the key * @value: (not nullable): the value * * Sets a report metadata item. * * Since: 1.8.8 **/ void fwupd_report_add_metadata_item(FwupdReport *self, const gchar *key, const gchar *value) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REPORT(self)); g_return_if_fail(key != NULL); g_return_if_fail(value != NULL); g_hash_table_insert(priv->metadata, g_strdup(key), g_strdup(value)); } /** * fwupd_report_get_metadata_item: * @self: a #FwupdReport * @key: (not nullable): the key * * Gets a report metadata item. * * Returns: the value, or %NULL if unset * * Since: 1.8.8 **/ const gchar * fwupd_report_get_metadata_item(FwupdReport *self, const gchar *key) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REPORT(self), NULL); g_return_val_if_fail(key != NULL, NULL); return g_hash_table_lookup(priv->metadata, key); } /** * fwupd_report_to_variant: * @self: a #FwupdReport * * Serialize the report data. * * Returns: the serialized data, or %NULL for error * * Since: 1.8.8 **/ GVariant * fwupd_report_to_variant(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); GVariantBuilder builder; g_return_val_if_fail(FWUPD_IS_REPORT(self), NULL); /* create an array with all the metadata in */ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); if (priv->distro_id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_DISTRO_ID, g_variant_new_string(priv->distro_id)); } if (priv->distro_variant != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_DISTRO_VARIANT, g_variant_new_string(priv->distro_variant)); } if (priv->distro_version != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_DISTRO_VERSION, g_variant_new_string(priv->distro_version)); } if (priv->vendor != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VENDOR, g_variant_new_string(priv->vendor)); } if (priv->device_name != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_DEVICE_NAME, g_variant_new_string(priv->device_name)); } if (priv->created != 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_CREATED, g_variant_new_uint64(priv->created)); } if (priv->version_old != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VERSION_OLD, g_variant_new_string(priv->version_old)); } if (priv->vendor_id > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_VENDOR_ID, g_variant_new_uint32(priv->vendor_id)); } if (priv->remote_id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_REMOTE_ID, g_variant_new_string(priv->remote_id)); } if (g_hash_table_size(priv->metadata) > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_METADATA, fwupd_hash_kv_to_variant(priv->metadata)); } if (priv->flags > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_FLAGS, g_variant_new_uint64(priv->flags)); } return g_variant_new("a{sv}", &builder); } static void fwupd_report_from_key_value(FwupdReport *self, const gchar *key, GVariant *value) { FwupdReportPrivate *priv = GET_PRIVATE(self); if (g_strcmp0(key, FWUPD_RESULT_KEY_DISTRO_ID) == 0) { fwupd_report_set_distro_id(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_DISTRO_VARIANT) == 0) { fwupd_report_set_distro_variant(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_DISTRO_VERSION) == 0) { fwupd_report_set_distro_version(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VENDOR) == 0) { fwupd_report_set_vendor(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VENDOR_ID) == 0) { fwupd_report_set_vendor_id(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_DEVICE_NAME) == 0) { fwupd_report_set_device_name(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_CREATED) == 0) { fwupd_report_set_created(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_VERSION_OLD) == 0) { fwupd_report_set_version_old(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_REMOTE_ID) == 0) { fwupd_report_set_remote_id(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_FLAGS) == 0) { fwupd_report_set_flags(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_METADATA) == 0) { g_hash_table_unref(priv->metadata); priv->metadata = fwupd_variant_to_hash_kv(value); return; } } /** * fwupd_report_to_json: * @self: a #FwupdReport * @builder: a JSON builder * * Adds a fwupd report to a JSON builder * * Since: 1.8.8 **/ void fwupd_report_to_json(FwupdReport *self, JsonBuilder *builder) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_autoptr(GList) keys = NULL; g_return_if_fail(FWUPD_IS_REPORT(self)); g_return_if_fail(builder != NULL); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_DEVICE_NAME, priv->device_name); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_DISTRO_ID, priv->distro_id); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_DISTRO_VARIANT, priv->distro_variant); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_DISTRO_VERSION, priv->distro_version); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_VERSION_OLD, priv->version_old); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_VENDOR, priv->vendor); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_REMOTE_ID, priv->remote_id); if (priv->vendor_id > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_VENDOR_ID, priv->vendor_id); if (priv->flags != FWUPD_REPORT_FLAG_NONE) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_FLAGS); json_builder_begin_array(builder); for (guint i = 0; i < 64; i++) { const gchar *tmp; if ((priv->flags & ((guint64)1 << i)) == 0) continue; tmp = fwupd_report_flag_to_string((guint64)1 << i); json_builder_add_string_value(builder, tmp); } json_builder_end_array(builder); } /* metadata */ keys = g_hash_table_get_keys(priv->metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup(priv->metadata, key); fwupd_common_json_add_string(builder, key, value); } } static void fwupd_pad_kv_dfl(GString *str, const gchar *key, guint64 report_flags) { g_autoptr(GString) tmp = g_string_new(""); for (guint i = 0; i < 64; i++) { if ((report_flags & ((guint64)1 << i)) == 0) continue; g_string_append_printf(tmp, "%s|", fwupd_report_flag_to_string((guint64)1 << i)); } if (tmp->len == 0) { g_string_append(tmp, fwupd_report_flag_to_string(0)); } else { g_string_truncate(tmp, tmp->len - 1); } fwupd_pad_kv_str(str, key, tmp->str); } /** * fwupd_report_to_string: * @self: a #FwupdReport * * Builds a text representation of the object. * * Returns: text, or %NULL for invalid * * Since: 1.8.8 **/ gchar * fwupd_report_to_string(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); GString *str; g_autoptr(GList) keys = NULL; g_return_val_if_fail(FWUPD_IS_REPORT(self), NULL); str = g_string_new(""); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_DEVICE_NAME, priv->device_name); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_DISTRO_ID, priv->distro_id); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_DISTRO_VARIANT, priv->distro_variant); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_DISTRO_VERSION, priv->distro_version); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_VERSION_OLD, priv->version_old); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_VENDOR, priv->vendor); fwupd_pad_kv_int(str, FWUPD_RESULT_KEY_VENDOR_ID, priv->vendor_id); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_REMOTE_ID, priv->remote_id); fwupd_pad_kv_dfl(str, FWUPD_RESULT_KEY_FLAGS, priv->flags); /* metadata */ keys = g_hash_table_get_keys(priv->metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup(priv->metadata, key); fwupd_pad_kv_str(str, key, value); } return g_string_free(str, FALSE); } /** * fwupd_report_get_flags: * @self: a #FwupdReport * * Gets the report flags. * * Returns: report flags, or 0 if unset * * Since: 1.9.1 **/ guint64 fwupd_report_get_flags(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REPORT(self), 0); return priv->flags; } /** * fwupd_report_set_flags: * @self: a #FwupdReport * @flags: report flags, e.g. %FWUPD_REPORT_FLAG_FROM_OEM * * Sets the report flags. * * Since: 1.9.1 **/ void fwupd_report_set_flags(FwupdReport *self, guint64 flags) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REPORT(self)); if (priv->flags == flags) return; priv->flags = flags; g_object_notify(G_OBJECT(self), "flags"); } /** * fwupd_report_add_flag: * @self: a #FwupdReport * @flag: the #FwupdReportFlags * * Adds a specific report flag to the report. * * Since: 1.9.1 **/ void fwupd_report_add_flag(FwupdReport *self, FwupdReportFlags flag) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REPORT(self)); if (flag == 0) return; if ((priv->flags & flag) > 0) return; priv->flags |= flag; g_object_notify(G_OBJECT(self), "flags"); } /** * fwupd_report_remove_flag: * @self: a #FwupdReport * @flag: a report flag * * Removes a specific report flag from the report. * * Since: 1.9.1 **/ void fwupd_report_remove_flag(FwupdReport *self, FwupdReportFlags flag) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REPORT(self)); if (flag == 0) return; if ((priv->flags & flag) == 0) return; priv->flags &= ~flag; g_object_notify(G_OBJECT(self), "flags"); } /** * fwupd_report_has_flag: * @self: a #FwupdReport * @flag: a report flag * * Finds if the report has a specific report flag. * * Returns: %TRUE if the flag is set * * Since: 1.9.1 **/ gboolean fwupd_report_has_flag(FwupdReport *self, FwupdReportFlags flag) { FwupdReportPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REPORT(self), FALSE); return (priv->flags & flag) > 0; } /** * fwupd_report_flag_to_string: * @report_flag: report flags, e.g. %FWUPD_REPORT_FLAG_FROM_OEM * * Converts an enumerated report flag to a string. * * Returns: identifier string * * Since: 1.9.1 **/ const gchar * fwupd_report_flag_to_string(FwupdReportFlags report_flag) { if (report_flag == FWUPD_REPORT_FLAG_NONE) return "none"; if (report_flag == FWUPD_REPORT_FLAG_FROM_OEM) return "from-oem"; if (report_flag == FWUPD_REPORT_FLAG_IS_UPGRADE) return "is-upgrade"; return NULL; } /** * fwupd_report_flag_from_string: * @report_flag: (nullable): a string, e.g. `from-oem` * * Converts a string to an enumerated report flag. * * Returns: enumerated value * * Since: 1.9.1 **/ FwupdReportFlags fwupd_report_flag_from_string(const gchar *report_flag) { if (g_strcmp0(report_flag, "none") == 0) return FWUPD_REPORT_FLAG_NONE; if (g_strcmp0(report_flag, "from-oem") == 0) return FWUPD_REPORT_FLAG_FROM_OEM; if (g_strcmp0(report_flag, "is-upgrade") == 0) return FWUPD_REPORT_FLAG_IS_UPGRADE; return FWUPD_REPORT_FLAG_UNKNOWN; } static void fwupd_report_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FwupdReport *self = FWUPD_REPORT(object); FwupdReportPrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_FLAGS: g_value_set_uint64(value, priv->flags); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fwupd_report_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FwupdReport *self = FWUPD_REPORT(object); switch (prop_id) { case PROP_FLAGS: fwupd_report_set_flags(self, g_value_get_uint64(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fwupd_report_init(FwupdReport *self) { FwupdReportPrivate *priv = GET_PRIVATE(self); priv->metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); } static void fwupd_report_finalize(GObject *object) { FwupdReport *self = FWUPD_REPORT(object); FwupdReportPrivate *priv = GET_PRIVATE(self); g_free(priv->vendor); g_free(priv->device_name); g_free(priv->distro_id); g_free(priv->distro_version); g_free(priv->distro_variant); g_free(priv->remote_id); g_free(priv->version_old); g_hash_table_unref(priv->metadata); G_OBJECT_CLASS(fwupd_report_parent_class)->finalize(object); } static void fwupd_report_class_init(FwupdReportClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->finalize = fwupd_report_finalize; object_class->get_property = fwupd_report_get_property; object_class->set_property = fwupd_report_set_property; /** * FwupdReport:flags: * * The report flags. * * Since: 1.9.1 */ pspec = g_param_spec_uint64("flags", NULL, NULL, FWUPD_REPORT_FLAG_NONE, FWUPD_REPORT_FLAG_UNKNOWN, FWUPD_REPORT_FLAG_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FLAGS, pspec); } static void fwupd_report_set_from_variant_iter(FwupdReport *self, GVariantIter *iter) { GVariant *value; const gchar *key; while (g_variant_iter_next(iter, "{&sv}", &key, &value)) { fwupd_report_from_key_value(self, key, value); g_variant_unref(value); } } /** * fwupd_report_from_variant: * @value: (not nullable): the serialized data * * Creates a new report using serialized data. * * Returns: (transfer full): a new #FwupdReport, or %NULL if @value was invalid * * Since: 1.8.8 **/ FwupdReport * fwupd_report_from_variant(GVariant *value) { FwupdReport *self = NULL; const gchar *type_string; g_autoptr(GVariantIter) iter = NULL; /* format from GetDetails */ type_string = g_variant_get_type_string(value); if (g_strcmp0(type_string, "(a{sv})") == 0) { self = fwupd_report_new(); g_variant_get(value, "(a{sv})", &iter); fwupd_report_set_from_variant_iter(self, iter); } else if (g_strcmp0(type_string, "a{sv}") == 0) { self = fwupd_report_new(); g_variant_get(value, "a{sv}", &iter); fwupd_report_set_from_variant_iter(self, iter); } else { g_warning("type %s not known", type_string); } return self; } /** * fwupd_report_new: * * Creates a new report. * * Returns: a new #FwupdReport * * Since: 1.8.8 **/ FwupdReport * fwupd_report_new(void) { FwupdReport *self; self = g_object_new(FWUPD_TYPE_REPORT, NULL); return FWUPD_REPORT(self); } fwupd-1.9.16/libfwupd/fwupd-report.h000066400000000000000000000100321460375044200173610ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FWUPD_TYPE_REPORT (fwupd_report_get_type()) G_DECLARE_DERIVABLE_TYPE(FwupdReport, fwupd_report, FWUPD, REPORT, GObject) struct _FwupdReportClass { GObjectClass parent_class; /*< private >*/ void (*_fwupd_reserved1)(void); void (*_fwupd_reserved2)(void); void (*_fwupd_reserved3)(void); void (*_fwupd_reserved4)(void); void (*_fwupd_reserved5)(void); void (*_fwupd_reserved6)(void); void (*_fwupd_reserved7)(void); }; /** * FWUPD_REPORT_FLAG_NONE: * * No report flags are set. * * Since: 1.9.1 */ #define FWUPD_REPORT_FLAG_NONE (0u) /** * FWUPD_REPORT_FLAG_FROM_OEM: * * The report was generated by the OEM. * * Since: 1.9.1 */ #define FWUPD_REPORT_FLAG_FROM_OEM (1ull << 0) /** * FWUPD_REPORT_FLAG_IS_UPGRADE: * * The new firmware was newer than the old firmware. * * Since: 1.9.14 */ #define FWUPD_REPORT_FLAG_IS_UPGRADE (1ull << 1) /** * FWUPD_REPORT_FLAG_UNKNOWN: * * The report flag is unknown. * * This is usually caused by a mismatched libfwupdplugin and daemon. * * Since: 1.9.1 */ #define FWUPD_REPORT_FLAG_UNKNOWN G_MAXUINT64 /** * FwupdReportFlags: * * Flags used to represent report attributes */ typedef guint64 FwupdReportFlags; FwupdReport * fwupd_report_new(void); gchar * fwupd_report_to_string(FwupdReport *self) G_GNUC_NON_NULL(1); guint64 fwupd_report_get_created(FwupdReport *self) G_GNUC_NON_NULL(1); void fwupd_report_set_created(FwupdReport *self, guint64 created) G_GNUC_NON_NULL(1); const gchar * fwupd_report_get_version_old(FwupdReport *self) G_GNUC_NON_NULL(1); void fwupd_report_set_version_old(FwupdReport *self, const gchar *version_old) G_GNUC_NON_NULL(1); const gchar * fwupd_report_get_vendor(FwupdReport *self) G_GNUC_NON_NULL(1); void fwupd_report_set_vendor(FwupdReport *self, const gchar *vendor) G_GNUC_NON_NULL(1); guint32 fwupd_report_get_vendor_id(FwupdReport *self) G_GNUC_NON_NULL(1); void fwupd_report_set_vendor_id(FwupdReport *self, guint32 vendor_id) G_GNUC_NON_NULL(1); const gchar * fwupd_report_get_device_name(FwupdReport *self) G_GNUC_NON_NULL(1); void fwupd_report_set_device_name(FwupdReport *self, const gchar *device_name) G_GNUC_NON_NULL(1); const gchar * fwupd_report_get_distro_id(FwupdReport *self) G_GNUC_NON_NULL(1); void fwupd_report_set_distro_id(FwupdReport *self, const gchar *distro_id) G_GNUC_NON_NULL(1); const gchar * fwupd_report_get_distro_version(FwupdReport *self) G_GNUC_NON_NULL(1); void fwupd_report_set_distro_version(FwupdReport *self, const gchar *distro_version) G_GNUC_NON_NULL(1); const gchar * fwupd_report_get_distro_variant(FwupdReport *self) G_GNUC_NON_NULL(1); void fwupd_report_set_distro_variant(FwupdReport *self, const gchar *distro_variant) G_GNUC_NON_NULL(1); const gchar * fwupd_report_get_remote_id(FwupdReport *self) G_GNUC_NON_NULL(1); void fwupd_report_set_remote_id(FwupdReport *self, const gchar *remote_id) G_GNUC_NON_NULL(1); GHashTable * fwupd_report_get_metadata(FwupdReport *self) G_GNUC_NON_NULL(1); void fwupd_report_add_metadata_item(FwupdReport *self, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2); const gchar * fwupd_report_get_metadata_item(FwupdReport *self, const gchar *key) G_GNUC_NON_NULL(1, 2); FwupdReport * fwupd_report_from_variant(GVariant *value) G_GNUC_NON_NULL(1); GVariant * fwupd_report_to_variant(FwupdReport *self) G_GNUC_NON_NULL(1); guint64 fwupd_report_get_flags(FwupdReport *self) G_GNUC_NON_NULL(1); void fwupd_report_set_flags(FwupdReport *self, guint64 flags) G_GNUC_NON_NULL(1); void fwupd_report_add_flag(FwupdReport *self, FwupdReportFlags flag) G_GNUC_NON_NULL(1); void fwupd_report_remove_flag(FwupdReport *self, FwupdReportFlags flag) G_GNUC_NON_NULL(1); gboolean fwupd_report_has_flag(FwupdReport *self, FwupdReportFlags flag) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); const gchar * fwupd_report_flag_to_string(FwupdReportFlags report_flag); FwupdReportFlags fwupd_report_flag_from_string(const gchar *report_flag); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-request-private.h000066400000000000000000000003761460375044200212200ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fwupd-request.h" G_BEGIN_DECLS GVariant * fwupd_request_to_variant(FwupdRequest *self) G_GNUC_NON_NULL(1); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-request.c000066400000000000000000000476131460375044200175500ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fwupd-common-private.h" #include "fwupd-enums-private.h" #include "fwupd-request-private.h" /** * FwupdRequest: * * A user request from the device. * * See also: [class@FwupdDevice] */ typedef struct { gchar *id; FwupdRequestKind kind; FwupdRequestFlags flags; guint64 created; gchar *device_id; gchar *message; gchar *image; } FwupdRequestPrivate; enum { PROP_0, PROP_ID, PROP_KIND, PROP_FLAGS, PROP_MESSAGE, PROP_IMAGE, PROP_DEVICE_ID, PROP_LAST }; G_DEFINE_TYPE_WITH_PRIVATE(FwupdRequest, fwupd_request, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_request_get_instance_private(o)) /** * fwupd_request_kind_to_string: * @kind: a update message kind, e.g. %FWUPD_REQUEST_KIND_IMMEDIATE * * Converts an enumerated update message kind to a string. * * Returns: identifier string * * Since: 1.6.2 **/ const gchar * fwupd_request_kind_to_string(FwupdRequestKind kind) { if (kind == FWUPD_REQUEST_KIND_UNKNOWN) return "unknown"; if (kind == FWUPD_REQUEST_KIND_POST) return "post"; if (kind == FWUPD_REQUEST_KIND_IMMEDIATE) return "immediate"; return NULL; } /** * fwupd_request_kind_from_string: * @kind: (nullable): a string, e.g. `immediate` * * Converts a string to an enumerated update message kind. * * Returns: enumerated value * * Since: 1.6.2 **/ FwupdRequestKind fwupd_request_kind_from_string(const gchar *kind) { if (g_strcmp0(kind, "unknown") == 0) return FWUPD_REQUEST_KIND_UNKNOWN; if (g_strcmp0(kind, "post") == 0) return FWUPD_REQUEST_KIND_POST; if (g_strcmp0(kind, "immediate") == 0) return FWUPD_REQUEST_KIND_IMMEDIATE; return FWUPD_REQUEST_KIND_LAST; } /** * fwupd_request_flag_to_string: * @flag: a request flag, e.g. %FWUPD_REQUEST_FLAG_NONE * * Converts an enumerated request flag to a string. * * Returns: identifier string * * Since: 1.8.6 **/ const gchar * fwupd_request_flag_to_string(FwupdRequestFlags flag) { if (flag == FWUPD_REQUEST_FLAG_NONE) return "none"; if (flag == FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE) return "allow-generic-message"; if (flag == FWUPD_REQUEST_FLAG_ALLOW_GENERIC_IMAGE) return "allow-generic-image"; if (flag == FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE) return "non-generic-message"; if (flag == FWUPD_REQUEST_FLAG_NON_GENERIC_IMAGE) return "non-generic-image"; return NULL; } /** * fwupd_request_flag_from_string: * @flag: (nullable): a string, e.g. `none` * * Converts a string to an enumerated request flag. * * Returns: enumerated value * * Since: 1.8.6 **/ FwupdRequestFlags fwupd_request_flag_from_string(const gchar *flag) { if (g_strcmp0(flag, "allow-generic-message") == 0) return FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE; if (g_strcmp0(flag, "allow-generic-image") == 0) return FWUPD_REQUEST_FLAG_ALLOW_GENERIC_IMAGE; if (g_strcmp0(flag, "non-generic-message") == 0) return FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE; if (g_strcmp0(flag, "non-generic-image") == 0) return FWUPD_REQUEST_FLAG_NON_GENERIC_IMAGE; return FWUPD_REQUEST_FLAG_NONE; } /** * fwupd_request_get_id: * @self: a #FwupdRequest * * Gets the ID. * * Returns: the ID, or %NULL if unset * * Since: 1.6.2 **/ const gchar * fwupd_request_get_id(FwupdRequest *self) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REQUEST(self), NULL); return priv->id; } /** * fwupd_request_set_id: * @self: a #FwupdRequest * @id: (nullable): the request ID, e.g. `USB:foo` * * Sets the ID. * * Since: 1.6.2 **/ void fwupd_request_set_id(FwupdRequest *self, const gchar *id) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REQUEST(self)); /* not changed */ if (g_strcmp0(priv->id, id) == 0) return; g_free(priv->id); priv->id = g_strdup(id); } /** * fwupd_request_get_device_id: * @self: a #FwupdRequest * * Gets the device_id that created the request. * * Returns: the device_id, or %NULL if unset * * Since: 1.6.2 **/ const gchar * fwupd_request_get_device_id(FwupdRequest *self) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REQUEST(self), NULL); return priv->device_id; } /** * fwupd_request_set_device_id: * @self: a #FwupdRequest * @device_id: (nullable): the device_id, e.g. `colorhug` * * Sets the device_id that created the request. * * Since: 1.6.2 **/ void fwupd_request_set_device_id(FwupdRequest *self, const gchar *device_id) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REQUEST(self)); /* not changed */ if (g_strcmp0(priv->device_id, device_id) == 0) return; g_free(priv->device_id); priv->device_id = g_strdup(device_id); } /** * fwupd_request_get_created: * @self: a #FwupdRequest * * Gets when the request was created. * * Returns: the UNIX time, or 0 if unset * * Since: 1.6.2 **/ guint64 fwupd_request_get_created(FwupdRequest *self) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REQUEST(self), 0); return priv->created; } /** * fwupd_request_set_created: * @self: a #FwupdRequest * @created: the UNIX time * * Sets when the request was created. * * Since: 1.6.2 **/ void fwupd_request_set_created(FwupdRequest *self, guint64 created) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REQUEST(self)); priv->created = created; } /** * fwupd_request_to_variant: * @self: a #FwupdRequest * * Serialize the request data. * * Returns: the serialized data, or %NULL for error * * Since: 1.6.2 **/ GVariant * fwupd_request_to_variant(FwupdRequest *self) { FwupdRequestPrivate *priv = GET_PRIVATE(self); GVariantBuilder builder; g_return_val_if_fail(FWUPD_IS_REQUEST(self), NULL); /* create an array with all the metadata in */ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); if (priv->id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_APPSTREAM_ID, g_variant_new_string(priv->id)); } if (priv->created > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_CREATED, g_variant_new_uint64(priv->created)); } if (priv->device_id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_DEVICE_ID, g_variant_new_string(priv->device_id)); } if (priv->message != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_UPDATE_MESSAGE, g_variant_new_string(priv->message)); } if (priv->image != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_UPDATE_IMAGE, g_variant_new_string(priv->image)); } if (priv->kind != FWUPD_REQUEST_KIND_UNKNOWN) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_REQUEST_KIND, g_variant_new_uint32(priv->kind)); } if (priv->flags != FWUPD_REQUEST_FLAG_NONE) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_FLAGS, g_variant_new_uint64(priv->flags)); } return g_variant_new("a{sv}", &builder); } static void fwupd_request_from_key_value(FwupdRequest *self, const gchar *key, GVariant *value) { if (g_strcmp0(key, FWUPD_RESULT_KEY_APPSTREAM_ID) == 0) { fwupd_request_set_id(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_CREATED) == 0) { fwupd_request_set_created(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_DEVICE_ID) == 0) { fwupd_request_set_device_id(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_UPDATE_MESSAGE) == 0) { fwupd_request_set_message(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_UPDATE_IMAGE) == 0) { fwupd_request_set_image(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_REQUEST_KIND) == 0) { fwupd_request_set_kind(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_FLAGS) == 0) { fwupd_request_set_flags(self, g_variant_get_uint64(value)); return; } } /** * fwupd_request_get_message: * @self: a #FwupdRequest * * Gets the update message, generating a generic one using the request ID if possible. * * Returns: the update message, or %NULL if unset * * Since: 1.6.2 **/ const gchar * fwupd_request_get_message(FwupdRequest *self) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REQUEST(self), NULL); /* something custom */ if (priv->message != NULL) return priv->message; /* untranslated canned messages */ if (fwupd_request_has_flag(self, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE)) { if (g_strcmp0(priv->id, FWUPD_REQUEST_ID_REMOVE_REPLUG) == 0) return "Please unplug and then re-insert the device USB cable."; if (g_strcmp0(priv->id, FWUPD_REQUEST_ID_INSERT_USB_CABLE) == 0) return "Please re-insert the device USB cable."; if (g_strcmp0(priv->id, FWUPD_REQUEST_ID_REMOVE_USB_CABLE) == 0) return "Please unplug the device USB cable."; if (g_strcmp0(priv->id, FWUPD_REQUEST_ID_REPLUG_POWER) == 0) return "Please unplug and then re-insert the device power cable."; if (g_strcmp0(priv->id, FWUPD_REQUEST_ID_PRESS_UNLOCK) == 0) return "Press unlock on the device."; if (g_strcmp0(priv->id, FWUPD_REQUEST_ID_DO_NOT_POWER_OFF) == 0) return "Do not turn off your computer or remove the AC adaptor."; } /* unknown */ return NULL; } /** * fwupd_request_set_message: * @self: a #FwupdRequest * @message: (nullable): the update message string * * Sets the update message. * * Since: 1.6.2 **/ void fwupd_request_set_message(FwupdRequest *self, const gchar *message) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REQUEST(self)); /* not changed */ if (g_strcmp0(priv->message, message) == 0) return; g_free(priv->message); priv->message = g_strdup(message); g_object_notify(G_OBJECT(self), "message"); } /** * fwupd_request_get_image: * @self: a #FwupdRequest * * Gets the update image. * * Returns: the update image URL, or %NULL if unset * * Since: 1.6.2 **/ const gchar * fwupd_request_get_image(FwupdRequest *self) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REQUEST(self), NULL); return priv->image; } /** * fwupd_request_set_image: * @self: a #FwupdRequest * @image: (nullable): the update image URL * * Sets the update image. * * Since: 1.6.2 **/ void fwupd_request_set_image(FwupdRequest *self, const gchar *image) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REQUEST(self)); /* not changed */ if (g_strcmp0(priv->image, image) == 0) return; g_free(priv->image); priv->image = g_strdup(image); g_object_notify(G_OBJECT(self), "image"); } /** * fwupd_request_get_kind: * @self: a #FwupdRequest * * Returns what the request is currently doing. * * Returns: the kind value, e.g. %FWUPD_STATUS_REQUEST_WRITE * * Since: 1.6.2 **/ FwupdRequestKind fwupd_request_get_kind(FwupdRequest *self) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REQUEST(self), 0); return priv->kind; } /** * fwupd_request_set_kind: * @self: a #FwupdRequest * @kind: the kind value, e.g. %FWUPD_STATUS_REQUEST_WRITE * * Sets what the request is currently doing. * * Since: 1.6.2 **/ void fwupd_request_set_kind(FwupdRequest *self, FwupdRequestKind kind) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REQUEST(self)); if (priv->kind == kind) return; priv->kind = kind; g_object_notify(G_OBJECT(self), "kind"); } /** * fwupd_request_get_flags: * @self: a #FwupdRequest * * Gets the request flags. * * Returns: request flags, or 0 if unset * * Since: 1.8.6 **/ FwupdRequestFlags fwupd_request_get_flags(FwupdRequest *self) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REQUEST(self), 0); return priv->flags; } /** * fwupd_request_set_flags: * @self: a #FwupdRequest * @flags: request flags, e.g. %FWUPD_REQUEST_FLAG_NONE * * Sets the request flags. * * Since: 1.8.6 **/ void fwupd_request_set_flags(FwupdRequest *self, FwupdRequestFlags flags) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REQUEST(self)); /* not changed */ if (priv->flags == flags) return; priv->flags = flags; g_object_notify(G_OBJECT(self), "flags"); } /** * fwupd_request_add_flag: * @self: a #FwupdRequest * @flag: the #FwupdRequestFlags * * Adds a specific flag to the request. * * Since: 1.8.6 **/ void fwupd_request_add_flag(FwupdRequest *self, FwupdRequestFlags flag) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REQUEST(self)); priv->flags |= flag; } /** * fwupd_request_remove_flag: * @self: a #FwupdRequest * @flag: the #FwupdRequestFlags * * Removes a specific flag from the request. * * Since: 1.8.6 **/ void fwupd_request_remove_flag(FwupdRequest *self, FwupdRequestFlags flag) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_REQUEST(self)); priv->flags &= ~flag; } /** * fwupd_request_has_flag: * @self: a #FwupdRequest * @flag: the #FwupdRequestFlags * * Finds if the request has a specific flag. * * Returns: %TRUE if the flag is set * * Since: 1.8.6 **/ gboolean fwupd_request_has_flag(FwupdRequest *self, FwupdRequestFlags flag) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_REQUEST(self), FALSE); return (priv->flags & flag) > 0; } /** * fwupd_request_to_string: * @self: a #FwupdRequest * * Builds a text representation of the object. * * Returns: text, or %NULL for invalid * * Since: 1.6.2 **/ gchar * fwupd_request_to_string(FwupdRequest *self) { FwupdRequestPrivate *priv = GET_PRIVATE(self); g_autoptr(GString) str = g_string_new(NULL); g_return_val_if_fail(FWUPD_IS_REQUEST(self), NULL); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_APPSTREAM_ID, priv->id); if (priv->kind != FWUPD_REQUEST_KIND_UNKNOWN) { fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_REQUEST_KIND, fwupd_request_kind_to_string(priv->kind)); } fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_FLAGS, fwupd_request_flag_to_string(priv->flags)); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_DEVICE_ID, priv->device_id); fwupd_pad_kv_unx(str, FWUPD_RESULT_KEY_CREATED, priv->created); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_UPDATE_MESSAGE, priv->message); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_UPDATE_IMAGE, priv->image); return g_string_free(g_steal_pointer(&str), FALSE); } static void fwupd_request_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FwupdRequest *self = FWUPD_REQUEST(object); FwupdRequestPrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_ID: g_value_set_string(value, priv->id); break; case PROP_MESSAGE: g_value_set_string(value, priv->message); break; case PROP_IMAGE: g_value_set_string(value, priv->image); break; case PROP_DEVICE_ID: g_value_set_string(value, priv->device_id); break; case PROP_KIND: g_value_set_uint(value, priv->kind); break; case PROP_FLAGS: g_value_set_uint64(value, priv->flags); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fwupd_request_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FwupdRequest *self = FWUPD_REQUEST(object); switch (prop_id) { case PROP_ID: fwupd_request_set_id(self, g_value_get_string(value)); break; case PROP_MESSAGE: fwupd_request_set_message(self, g_value_get_string(value)); break; case PROP_IMAGE: fwupd_request_set_image(self, g_value_get_string(value)); break; case PROP_DEVICE_ID: fwupd_request_set_device_id(self, g_value_get_string(value)); break; case PROP_KIND: fwupd_request_set_kind(self, g_value_get_uint(value)); break; case PROP_FLAGS: fwupd_request_set_flags(self, g_value_get_uint64(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fwupd_request_finalize(GObject *object) { FwupdRequest *self = FWUPD_REQUEST(object); FwupdRequestPrivate *priv = GET_PRIVATE(self); g_free(priv->id); g_free(priv->device_id); g_free(priv->message); g_free(priv->image); G_OBJECT_CLASS(fwupd_request_parent_class)->finalize(object); } static void fwupd_request_init(FwupdRequest *self) { FwupdRequestPrivate *priv = GET_PRIVATE(self); priv->created = g_get_real_time() / G_USEC_PER_SEC; } static void fwupd_request_class_init(FwupdRequestClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->finalize = fwupd_request_finalize; object_class->get_property = fwupd_request_get_property; object_class->set_property = fwupd_request_set_property; /** * FwupdRequest:id: * * The request identifier. * * Since: 1.6.2 */ pspec = g_param_spec_string("id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_ID, pspec); /** * FwupdRequest:kind: * * The kind of the request. * * Since: 1.6.2 */ pspec = g_param_spec_uint("kind", NULL, NULL, FWUPD_REQUEST_KIND_UNKNOWN, FWUPD_REQUEST_KIND_LAST, FWUPD_REQUEST_KIND_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_KIND, pspec); /** * FwupdRequest:flags: * * The flags for the request. * * Since: 1.8.6 */ pspec = g_param_spec_uint64("flags", NULL, NULL, FWUPD_REQUEST_FLAG_NONE, FWUPD_REQUEST_FLAG_UNKNOWN, FWUPD_REQUEST_FLAG_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FLAGS, pspec); /** * FwupdRequest:message: * * The message text in the request. * * Since: 1.6.2 */ pspec = g_param_spec_string("message", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_MESSAGE, pspec); /** * FwupdRequest:image: * * The image link for the request. * * Since: 1.6.2 */ pspec = g_param_spec_string("image", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_IMAGE, pspec); /** * FwupdRequest:device-id: * * The device ID for the request. * * Since: 1.8.2 */ pspec = g_param_spec_string("device-id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_DEVICE_ID, pspec); } static void fwupd_request_set_from_variant_iter(FwupdRequest *self, GVariantIter *iter) { GVariant *value; const gchar *key; while (g_variant_iter_next(iter, "{&sv}", &key, &value)) { fwupd_request_from_key_value(self, key, value); g_variant_unref(value); } } /** * fwupd_request_from_variant: * @value: (not nullable): the serialized data * * Creates a new request using serialized data. * * Returns: (transfer full): a new #FwupdRequest, or %NULL if @value was invalid * * Since: 1.6.2 **/ FwupdRequest * fwupd_request_from_variant(GVariant *value) { FwupdRequest *self = NULL; const gchar *type_string; g_autoptr(GVariantIter) iter = NULL; g_return_val_if_fail(value != NULL, NULL); /* format from GetDetails */ type_string = g_variant_get_type_string(value); if (g_strcmp0(type_string, "(a{sv})") == 0) { self = fwupd_request_new(); g_variant_get(value, "(a{sv})", &iter); fwupd_request_set_from_variant_iter(self, iter); } else if (g_strcmp0(type_string, "a{sv}") == 0) { self = fwupd_request_new(); g_variant_get(value, "a{sv}", &iter); fwupd_request_set_from_variant_iter(self, iter); } else { g_warning("type %s not known", type_string); } return self; } /** * fwupd_request_new: * * Creates a new request. * * Returns: a new #FwupdRequest * * Since: 1.6.2 **/ FwupdRequest * fwupd_request_new(void) { FwupdRequest *self; self = g_object_new(FWUPD_TYPE_REQUEST, NULL); return FWUPD_REQUEST(self); } fwupd-1.9.16/libfwupd/fwupd-request.h000066400000000000000000000143561460375044200175530ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_BEGIN_DECLS #define FWUPD_TYPE_REQUEST (fwupd_request_get_type()) G_DECLARE_DERIVABLE_TYPE(FwupdRequest, fwupd_request, FWUPD, REQUEST, GObject) struct _FwupdRequestClass { GObjectClass parent_class; /*< private >*/ void (*_fwupd_reserved1)(void); void (*_fwupd_reserved2)(void); void (*_fwupd_reserved3)(void); void (*_fwupd_reserved4)(void); void (*_fwupd_reserved5)(void); void (*_fwupd_reserved6)(void); void (*_fwupd_reserved7)(void); }; /** * FwupdRequestKind: * @FWUPD_REQUEST_KIND_UNKNOWN: Unknown kind * @FWUPD_REQUEST_KIND_POST: After the update * @FWUPD_REQUEST_KIND_IMMEDIATE: Immediately * * The kind of request we are asking of the user. **/ typedef enum { FWUPD_REQUEST_KIND_UNKNOWN, /* Since: 1.6.2 */ FWUPD_REQUEST_KIND_POST, /* Since: 1.6.2 */ FWUPD_REQUEST_KIND_IMMEDIATE, /* Since: 1.6.2 */ /*< private >*/ FWUPD_REQUEST_KIND_LAST } FwupdRequestKind; /** * FWUPD_REQUEST_ID_REMOVE_REPLUG: * * The user needs to remove and reinsert the device to complete the update, e.g. * "The update will continue when the device USB cable has been unplugged and then re-inserted." * * Since 1.6.2 */ #define FWUPD_REQUEST_ID_REMOVE_REPLUG "org.freedesktop.fwupd.request.remove-replug" /** * FWUPD_REQUEST_ID_PRESS_UNLOCK: * * The user needs to press unlock on the device to continue, e.g. * "Press unlock on the device to continue the update process." * * Since 1.6.2 */ #define FWUPD_REQUEST_ID_PRESS_UNLOCK "org.freedesktop.fwupd.request.press-unlock" /** * FWUPD_REQUEST_ID_REMOVE_USB_CABLE: * * The user needs to remove the device to complete the update, e.g. * "The update will continue when the device USB cable has been unplugged." * * Since 1.8.6 */ #define FWUPD_REQUEST_ID_REMOVE_USB_CABLE "org.freedesktop.fwupd.request.remove-usb-cable" /** * FWUPD_REQUEST_ID_INSERT_USB_CABLE: * * The user needs to insert the cable to complete the update, e.g. * "The update will continue when the device USB cable has been re-inserted." * * Since 1.8.9 */ #define FWUPD_REQUEST_ID_INSERT_USB_CABLE "org.freedesktop.fwupd.request.insert-usb-cable" /** * FWUPD_REQUEST_ID_DO_NOT_POWER_OFF: * * Show the user a message not to unplug the machine from the AC power, e.g. * "Do not turn off your computer or remove the AC adaptor until you are sure the update has * completed." * * Since 1.8.6 */ #define FWUPD_REQUEST_ID_DO_NOT_POWER_OFF "org.freedesktop.fwupd.request.do-not-power-off" /** * FWUPD_REQUEST_ID_REPLUG_INSTALL: * * Show the user a message to replug the device and then install the firmware, e.g. * "Unplug and replug the device, to continue the update process." * * Since 1.8.11 */ #define FWUPD_REQUEST_ID_REPLUG_INSTALL "org.freedesktop.fwupd.replug-install" /** * FWUPD_REQUEST_ID_REPLUG_POWER: * * Show the user a message to replug the power connector, e.g. * "The update will continue when the device power cable has been unplugged and then re-inserted." * * Since 1.9.9 */ #define FWUPD_REQUEST_ID_REPLUG_POWER "org.freedesktop.fwupd.replug-power" /** * FWUPD_REQUEST_FLAG_NONE: * * No flags are set. * * Since: 1.8.6 */ #define FWUPD_REQUEST_FLAG_NONE (0u) /** * FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE: * * Use a generic (translated) request message. * * Since: 1.8.6 */ #define FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE (1u << 0) /** * FWUPD_REQUEST_FLAG_ALLOW_GENERIC_IMAGE: * * Use a generic (translated) request image. * * Since: 1.8.6 */ #define FWUPD_REQUEST_FLAG_ALLOW_GENERIC_IMAGE (1u << 1) /** * FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE: * * Device requires a non-generic interaction with custom non-translatable text. * * Since: 1.9.10 */ #define FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE (1ull << 2) /** * FWUPD_REQUEST_FLAG_NON_GENERIC_IMAGE: * * Device requires to show the user a custom image for the action to make sense. * * Since: 1.9.10 */ #define FWUPD_REQUEST_FLAG_NON_GENERIC_IMAGE (1ull << 3) /** * FWUPD_REQUEST_FLAG_UNKNOWN: * * The request flag is unknown, typically caused by using mismatched client and daemon. * * Since: 1.8.6 */ #define FWUPD_REQUEST_FLAG_UNKNOWN G_MAXUINT64 /** * FwupdRequestFlags: * * Flags used to represent request attributes */ typedef guint64 FwupdRequestFlags; const gchar * fwupd_request_kind_to_string(FwupdRequestKind kind); FwupdRequestKind fwupd_request_kind_from_string(const gchar *kind); const gchar * fwupd_request_flag_to_string(FwupdRequestFlags flag); FwupdRequestFlags fwupd_request_flag_from_string(const gchar *flag); FwupdRequest * fwupd_request_new(void); gchar * fwupd_request_to_string(FwupdRequest *self) G_GNUC_NON_NULL(1); const gchar * fwupd_request_get_id(FwupdRequest *self) G_GNUC_NON_NULL(1); void fwupd_request_set_id(FwupdRequest *self, const gchar *id) G_GNUC_NON_NULL(1); guint64 fwupd_request_get_created(FwupdRequest *self) G_GNUC_NON_NULL(1); void fwupd_request_set_created(FwupdRequest *self, guint64 created) G_GNUC_NON_NULL(1); const gchar * fwupd_request_get_device_id(FwupdRequest *self) G_GNUC_NON_NULL(1); void fwupd_request_set_device_id(FwupdRequest *self, const gchar *device_id) G_GNUC_NON_NULL(1); const gchar * fwupd_request_get_message(FwupdRequest *self) G_GNUC_NON_NULL(1); void fwupd_request_set_message(FwupdRequest *self, const gchar *message) G_GNUC_NON_NULL(1); const gchar * fwupd_request_get_image(FwupdRequest *self) G_GNUC_NON_NULL(1); void fwupd_request_set_image(FwupdRequest *self, const gchar *image) G_GNUC_NON_NULL(1); FwupdRequestKind fwupd_request_get_kind(FwupdRequest *self) G_GNUC_NON_NULL(1); void fwupd_request_set_kind(FwupdRequest *self, FwupdRequestKind kind) G_GNUC_NON_NULL(1); FwupdRequestFlags fwupd_request_get_flags(FwupdRequest *self) G_GNUC_NON_NULL(1); void fwupd_request_set_flags(FwupdRequest *self, FwupdRequestFlags flags) G_GNUC_NON_NULL(1); void fwupd_request_add_flag(FwupdRequest *self, FwupdRequestFlags flag) G_GNUC_NON_NULL(1); void fwupd_request_remove_flag(FwupdRequest *self, FwupdRequestFlags flag) G_GNUC_NON_NULL(1); gboolean fwupd_request_has_flag(FwupdRequest *self, FwupdRequestFlags flag) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); FwupdRequest * fwupd_request_from_variant(GVariant *value); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-security-attr-private.h000066400000000000000000000241141460375044200223430ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-build.h" #include "fwupd-security-attr.h" G_BEGIN_DECLS /** * FWUPD_SECURITY_ATTR_ID_PREBOOT_DMA_PROTECTION: * * Host Security ID attribute for Pre-boot DMA protection * * This was previously known as org.fwupd.hsi.AcpiDmar for Intel from 1.5.0+. * * Since: 1.8.0 **/ #define FWUPD_SECURITY_ATTR_ID_PREBOOT_DMA_PROTECTION "org.fwupd.hsi.PrebootDma" /** * FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM: * * Host Security ID attribute indicating encrypted RAM available * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM "org.fwupd.hsi.EncryptedRam" /** * FWUPD_SECURITY_ATTR_ID_FWUPD_ATTESTATION: * * Host Security ID attribute for attestation * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_FWUPD_ATTESTATION "org.fwupd.hsi.Fwupd.Attestation" /** * FWUPD_SECURITY_ATTR_ID_FWUPD_PLUGINS: * * Host Security ID attribute for plugins * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_FWUPD_PLUGINS "org.fwupd.hsi.Fwupd.Plugins" /** * FWUPD_SECURITY_ATTR_ID_FWUPD_UPDATES: * * Host Security ID attribute for updates * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_FWUPD_UPDATES "org.fwupd.hsi.Fwupd.Updates" /** * FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ENABLED: * * Host Security ID attribute for Intel Bootguard enabled * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ENABLED "org.fwupd.hsi.IntelBootguard.Enabled" /** * FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_VERIFIED: * * Host Security ID attribute for Intel Bootguard verified * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_VERIFIED "org.fwupd.hsi.IntelBootguard.Verified" /** * FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ACM: * * Host Security ID attribute for Intel Bootguard ACM * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ACM "org.fwupd.hsi.IntelBootguard.Acm" /** * FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_POLICY: * * Host Security ID attribute for Intel Bootguard policy * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_POLICY "org.fwupd.hsi.IntelBootguard.Policy" /** * FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_OTP: * * Host Security ID attribute for Intel Bootguard OTP fuse * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_OTP "org.fwupd.hsi.IntelBootguard.Otp" /** * FWUPD_SECURITY_ATTR_ID_INTEL_CET_ENABLED: * * Host Security ID attribute for Intel CET enabled * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_INTEL_CET_ENABLED "org.fwupd.hsi.IntelCet.Enabled" /** * FWUPD_SECURITY_ATTR_ID_INTEL_CET_ACTIVE: * * Host Security ID attribute for Intel CET active * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_INTEL_CET_ACTIVE "org.fwupd.hsi.IntelCet.Active" /** * FWUPD_SECURITY_ATTR_ID_INTEL_SMAP: * * Host Security ID attribute for Intel SMAP * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_INTEL_SMAP "org.fwupd.hsi.IntelSmap" /** * FWUPD_SECURITY_ATTR_ID_IOMMU: * * Host Security ID attribute for IOMMU * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_IOMMU "org.fwupd.hsi.Iommu" /** * FWUPD_SECURITY_ATTR_ID_KERNEL_LOCKDOWN: * * Host Security ID attribute for kernel lockdown * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_KERNEL_LOCKDOWN "org.fwupd.hsi.Kernel.Lockdown" /** * FWUPD_SECURITY_ATTR_ID_KERNEL_SWAP: * * Host Security ID attribute for kernel swap * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_KERNEL_SWAP "org.fwupd.hsi.Kernel.Swap" /** * FWUPD_SECURITY_ATTR_ID_KERNEL_TAINTED: * * Host Security ID attribute for kernel taint * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_KERNEL_TAINTED "org.fwupd.hsi.Kernel.Tainted" /** * FWUPD_SECURITY_ATTR_ID_MEI_MANUFACTURING_MODE: * * Host Security ID attribute for Intel ME manufacturing mode * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_MEI_MANUFACTURING_MODE "org.fwupd.hsi.Mei.ManufacturingMode" /** * FWUPD_SECURITY_ATTR_ID_MEI_OVERRIDE_STRAP: * * Host Security ID attribute for Intel ME override strap * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_MEI_OVERRIDE_STRAP "org.fwupd.hsi.Mei.OverrideStrap" /** * FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST: * * Host Security ID attribute for Intel ME Key Manifest * * Since: 1.8.7 **/ #define FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST "org.fwupd.hsi.Mei.KeyManifest" /** * FWUPD_SECURITY_ATTR_ID_MEI_VERSION: * * Host Security ID attribute for Intel ME version * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_MEI_VERSION "org.fwupd.hsi.Mei.Version" /** * FWUPD_SECURITY_ATTR_ID_SPI_BIOSWE: * * Host Security ID attribute for Intel SPI BIOSWE configuration * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_SPI_BIOSWE "org.fwupd.hsi.Spi.Bioswe" /** * FWUPD_SECURITY_ATTR_ID_SPI_BLE: * * Host Security ID attribute for Intel SPI BLE configuration * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_SPI_BLE "org.fwupd.hsi.Spi.Ble" /** * FWUPD_SECURITY_ATTR_ID_SPI_SMM_BWP: * * Host Security ID attribute for Intel SPI SMM BWP * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_SPI_SMM_BWP "org.fwupd.hsi.Spi.SmmBwp" /** * FWUPD_SECURITY_ATTR_ID_SPI_DESCRIPTOR: * * Host Security ID attribute for Intel SPI descriptor * * Since: 1.6.0 **/ #define FWUPD_SECURITY_ATTR_ID_SPI_DESCRIPTOR "org.fwupd.hsi.Spi.Descriptor" /** * FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_IDLE: * * Host Security ID attribute for Suspend to Idle * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_IDLE "org.fwupd.hsi.SuspendToIdle" /** * FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_RAM: * * Host Security ID attribute for Suspend to RAM * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_RAM "org.fwupd.hsi.SuspendToRam" /** * FWUPD_SECURITY_ATTR_ID_TPM_EMPTY_PCR: * * Host Security ID attribute for empty PCR * * Since: 1.7.2 **/ #define FWUPD_SECURITY_ATTR_ID_TPM_EMPTY_PCR "org.fwupd.hsi.Tpm.EmptyPcr" /** * FWUPD_SECURITY_ATTR_ID_TPM_RECONSTRUCTION_PCR0: * * Host Security ID attribute for TPM PCR0 reconstruction * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_TPM_RECONSTRUCTION_PCR0 "org.fwupd.hsi.Tpm.ReconstructionPcr0" /** * FWUPD_SECURITY_ATTR_ID_TPM_VERSION_20: * * Host Security ID attribute for TPM 2.0 * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_TPM_VERSION_20 "org.fwupd.hsi.Tpm.Version20" /** * FWUPD_SECURITY_ATTR_ID_UEFI_SECUREBOOT: * * Host Security ID attribute for UEFI secure boot * * Since: 1.5.0 **/ #define FWUPD_SECURITY_ATTR_ID_UEFI_SECUREBOOT "org.fwupd.hsi.Uefi.SecureBoot" /** * FWUPD_SECURITY_ATTR_ID_UEFI_BOOTSERVICE_VARS: * * Host Security ID attribute indicating if Bootservice-only variables are hidden. * * Since: 1.9.3 **/ #define FWUPD_SECURITY_ATTR_ID_UEFI_BOOTSERVICE_VARS "org.fwupd.hsi.Uefi.BootserviceVars" /** * FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_ENABLED: * * Host Security ID attribute for parts with debugging capabilities enabled * * This was previously known as org.fwupd.hsi.PlatformDebugEnabled for Intel 1.5.0+ * It was renamed for all vendor support in 1.8.0. * * * Since: 1.8.0 **/ #define FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_ENABLED "org.fwupd.hsi.PlatformDebugEnabled" /** * FWUPD_SECURITY_ATTR_ID_PLATFORM_FUSED: * * Host Security ID attribute for fused parts * * Since: 1.8.0 **/ #define FWUPD_SECURITY_ATTR_ID_PLATFORM_FUSED "org.fwupd.hsi.PlatformFused" /** * FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_LOCKED: * * Host Security ID attribute for parts locked from debugging * * This was previously known as org.fwupd.hsi.IntelDci.Locked for Intel 1.5.0+ * It was renamed for all vendor support in 1.8.0. * * Since: 1.8.0 **/ #define FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_LOCKED "org.fwupd.hsi.PlatformDebugLocked" /** * FWUPD_SECURITY_ATTR_ID_UEFI_PK: * * Host Security ID attribute for UEFI PK * * Since: 1.5.5 **/ #define FWUPD_SECURITY_ATTR_ID_UEFI_PK "org.fwupd.hsi.Uefi.Pk" /** * FWUPD_SECURITY_ATTR_ID_SUPPORTED_CPU * * Host Security ID attribute for Supported CPU * * Since: 1.8.0 **/ #define FWUPD_SECURITY_ATTR_ID_SUPPORTED_CPU "org.fwupd.hsi.SupportedCpu" /** * FWUPD_SECURITY_ATTR_ID_AMD_ROLLBACK_PROTECTION * * Host Security ID attribute for Rollback protection of AMD platform * firmware * * Since: 1.8.0 **/ #define FWUPD_SECURITY_ATTR_ID_AMD_ROLLBACK_PROTECTION "org.fwupd.hsi.Amd.RollbackProtection" /** * FWUPD_SECURITY_ATTR_ID_AMD_SPI_WRITE_PROTECTION * * Host Security ID attribute for SPI Write protection * * Since: 1.8.0 **/ #define FWUPD_SECURITY_ATTR_ID_AMD_SPI_WRITE_PROTECTION "org.fwupd.hsi.Amd.SpiWriteProtection" /** * FWUPD_SECURITY_ATTR_ID_AMD_SPI_REPLAY_PROTECTION * * Host Security ID attribute for SPI replay protection * * Since: 1.8.0 **/ #define FWUPD_SECURITY_ATTR_ID_AMD_SPI_REPLAY_PROTECTION "org.fwupd.hsi.Amd.SpiReplayProtection" /** * FWUPD_SECURITY_ATTR_ID_HOST_EMULATION * * Host Security ID attribute for host emulation * * Since: 1.8.3 **/ #define FWUPD_SECURITY_ATTR_ID_HOST_EMULATION "org.fwupd.hsi.HostEmulation" /** * FWUPD_SECURITY_ATTR_ID_BIOS_ROLLBACK_PROTECTION * * Host Security ID attribute for Rollback protection of BIOS firmware * * Since: 1.8.8 **/ #define FWUPD_SECURITY_ATTR_ID_BIOS_ROLLBACK_PROTECTION "org.fwupd.hsi.Bios.RollbackProtection" /** * FWUPD_SECURITY_ATTR_ID_INTEL_GDS: * * Host Security ID attribute indicating the processor is safe against Gather Data Sampling. * * Since: 1.9.4 **/ #define FWUPD_SECURITY_ATTR_ID_INTEL_GDS "org.fwupd.hsi.IntelGds" /** * FWUPD_SECURITY_ATTR_ID_BIOS_CAPSULE_UPDATES: * * Host Security ID attribute indicating Capsule updates are supported by the BIOS. * * Since: 1.9.6 **/ #define FWUPD_SECURITY_ATTR_ID_BIOS_CAPSULE_UPDATES "org.fwupd.hsi.Bios.CapsuleUpdates" GVariant * fwupd_security_attr_to_variant(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_to_json(FwupdSecurityAttr *self, JsonBuilder *builder) G_GNUC_NON_NULL(1, 2); gboolean fwupd_security_attr_from_json(FwupdSecurityAttr *self, JsonNode *json_node, GError **error) G_GNUC_NON_NULL(1, 2); FwupdSecurityAttr * fwupd_security_attr_copy(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-security-attr.c000066400000000000000000001567131460375044200207010ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fwupd-common-private.h" #include "fwupd-enums-private.h" #include "fwupd-error.h" #include "fwupd-security-attr-private.h" /** * FwupdSecurityAttr: * * A Host Security ID attribute that represents something that was measured. */ static void fwupd_security_attr_finalize(GObject *object); typedef struct { gchar *appstream_id; GPtrArray *obsoletes; GPtrArray *guids; GHashTable *metadata; /* (nullable) */ gchar *name; gchar *title; gchar *description; gchar *plugin; gchar *url; guint64 created; FwupdSecurityAttrLevel level; FwupdSecurityAttrResult result; FwupdSecurityAttrResult result_fallback; FwupdSecurityAttrResult result_success; FwupdSecurityAttrFlags flags; gchar *bios_setting_id; gchar *bios_setting_target_value; gchar *bios_setting_current_value; gchar *kernel_current_value; gchar *kernel_target_value; } FwupdSecurityAttrPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FwupdSecurityAttr, fwupd_security_attr, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fwupd_security_attr_get_instance_private(o)) /** * fwupd_security_attr_flag_to_string: * @flag: security attribute flags, e.g. %FWUPD_SECURITY_ATTR_FLAG_SUCCESS * * Returns the printable string for the flag. * * Returns: string, or %NULL * * Since: 1.5.0 **/ const gchar * fwupd_security_attr_flag_to_string(FwupdSecurityAttrFlags flag) { if (flag == FWUPD_SECURITY_ATTR_FLAG_NONE) return "none"; if (flag == FWUPD_SECURITY_ATTR_FLAG_SUCCESS) return "success"; if (flag == FWUPD_SECURITY_ATTR_FLAG_OBSOLETED) return "obsoleted"; if (flag == FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA) return "missing-data"; if (flag == FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES) return "runtime-updates"; if (flag == FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION) return "runtime-attestation"; if (flag == FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE) return "runtime-issue"; if (flag == FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM) return "action-contact-oem"; if (flag == FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW) return "action-config-fw"; if (flag == FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS) return "action-config-os"; if (flag == FWUPD_SECURITY_ATTR_FLAG_CAN_FIX) return "can-fix"; if (flag == FWUPD_SECURITY_ATTR_FLAG_CAN_UNDO) return "can-undo"; return NULL; } /** * fwupd_security_attr_flag_from_string: * @flag: (nullable): a string, e.g. `success` * * Converts a string to an enumerated flag. * * Returns: enumerated value * * Since: 1.7.1 **/ FwupdSecurityAttrFlags fwupd_security_attr_flag_from_string(const gchar *flag) { if (g_strcmp0(flag, "success") == 0) return FWUPD_SECURITY_ATTR_FLAG_SUCCESS; if (g_strcmp0(flag, "obsoleted") == 0) return FWUPD_SECURITY_ATTR_FLAG_OBSOLETED; if (g_strcmp0(flag, "missing-data") == 0) return FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA; if (g_strcmp0(flag, "runtime-updates") == 0) return FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES; if (g_strcmp0(flag, "runtime-attestation") == 0) return FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION; if (g_strcmp0(flag, "runtime-issue") == 0) return FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE; if (g_strcmp0(flag, "action-contact-oem") == 0) return FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM; if (g_strcmp0(flag, "action-config-fw") == 0) return FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW; if (g_strcmp0(flag, "action-config-os") == 0) return FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS; if (g_strcmp0(flag, "can-fix") == 0) return FWUPD_SECURITY_ATTR_FLAG_CAN_FIX; if (g_strcmp0(flag, "can-undo") == 0) return FWUPD_SECURITY_ATTR_FLAG_CAN_UNDO; return FWUPD_SECURITY_ATTR_FLAG_NONE; } /** * fwupd_security_attr_result_to_string: * @result: security attribute result, e.g. %FWUPD_SECURITY_ATTR_RESULT_ENABLED * * Returns the printable string for the result enum. * * Returns: string, or %NULL * * Since: 1.5.0 **/ const gchar * fwupd_security_attr_result_to_string(FwupdSecurityAttrResult result) { if (result == FWUPD_SECURITY_ATTR_RESULT_VALID) return "valid"; if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_VALID) return "not-valid"; if (result == FWUPD_SECURITY_ATTR_RESULT_ENABLED) return "enabled"; if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED) return "not-enabled"; if (result == FWUPD_SECURITY_ATTR_RESULT_LOCKED) return "locked"; if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED) return "not-locked"; if (result == FWUPD_SECURITY_ATTR_RESULT_ENCRYPTED) return "encrypted"; if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED) return "not-encrypted"; if (result == FWUPD_SECURITY_ATTR_RESULT_TAINTED) return "tainted"; if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED) return "not-tainted"; if (result == FWUPD_SECURITY_ATTR_RESULT_FOUND) return "found"; if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND) return "not-found"; if (result == FWUPD_SECURITY_ATTR_RESULT_SUPPORTED) return "supported"; if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED) return "not-supported"; return NULL; } /** * fwupd_security_attr_result_from_string: * @result: (nullable): a string, e.g. `not-encrypted` * * Converts a string to an enumerated result. * * Returns: enumerated value * * Since: 1.7.1 **/ FwupdSecurityAttrResult fwupd_security_attr_result_from_string(const gchar *result) { if (g_strcmp0(result, "valid") == 0) return FWUPD_SECURITY_ATTR_RESULT_VALID; if (g_strcmp0(result, "not-valid") == 0) return FWUPD_SECURITY_ATTR_RESULT_NOT_VALID; if (g_strcmp0(result, "enabled") == 0) return FWUPD_SECURITY_ATTR_RESULT_ENABLED; if (g_strcmp0(result, "not-enabled") == 0) return FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED; if (g_strcmp0(result, "locked") == 0) return FWUPD_SECURITY_ATTR_RESULT_LOCKED; if (g_strcmp0(result, "not-locked") == 0) return FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED; if (g_strcmp0(result, "encrypted") == 0) return FWUPD_SECURITY_ATTR_RESULT_ENCRYPTED; if (g_strcmp0(result, "not-encrypted") == 0) return FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED; if (g_strcmp0(result, "tainted") == 0) return FWUPD_SECURITY_ATTR_RESULT_TAINTED; if (g_strcmp0(result, "not-tainted") == 0) return FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED; if (g_strcmp0(result, "found") == 0) return FWUPD_SECURITY_ATTR_RESULT_FOUND; if (g_strcmp0(result, "not-found") == 0) return FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND; if (g_strcmp0(result, "supported") == 0) return FWUPD_SECURITY_ATTR_RESULT_SUPPORTED; if (g_strcmp0(result, "not-supported") == 0) return FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED; return FWUPD_SECURITY_ATTR_RESULT_UNKNOWN; } /** * fwupd_security_attr_flag_to_suffix: * @flag: security attribute flags, e.g. %FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES * * Returns the string suffix for the flag. * * Returns: string, or %NULL * * Since: 1.5.0 **/ const gchar * fwupd_security_attr_flag_to_suffix(FwupdSecurityAttrFlags flag) { if (flag == FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES) return "U"; if (flag == FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION) return "A"; if (flag == FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE) return "!"; return NULL; } /** * fwupd_security_attr_get_bios_setting_id: * @self: a #FwupdSecurityAttr * * Gets the #FwupdBiosSetting that can be used to improve this * #FwupdSecurityAttr. * * Returns: The unique ID used for #FwupdBiosSetting or NULL * * Since: 1.8.4 **/ const gchar * fwupd_security_attr_get_bios_setting_id(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); return priv->bios_setting_id; } /** * fwupd_security_attr_set_bios_setting_id: * @self: a #FwupdSecurityAttr * @id: (nullable): Unique identifier used for #FwupdBiosSetting * * Sets the #FwupdBiosSetting that can be used to improve this * #FwupdSecurityAttr. * * Since: 1.8.4 **/ void fwupd_security_attr_set_bios_setting_id(FwupdSecurityAttr *self, const gchar *id) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); if (priv->bios_setting_id == id) return; g_free(priv->bios_setting_id); priv->bios_setting_id = g_strdup(id); } /** * fwupd_security_attr_get_obsoletes: * @self: a #FwupdSecurityAttr * * Gets the list of attribute obsoletes. The obsoleted attributes will not * contribute to the calculated HSI value or be visible in command line tools. * * Returns: (element-type utf8) (transfer none): the obsoletes, which may be empty * * Since: 1.5.0 **/ GPtrArray * fwupd_security_attr_get_obsoletes(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); return priv->obsoletes; } /** * fwupd_security_attr_add_obsolete: * @self: a #FwupdSecurityAttr * @appstream_id: the appstream_id or plugin name * * Adds an attribute appstream_id to obsolete. The obsoleted attribute will not * contribute to the calculated HSI value or be visible in command line tools. * * Since: 1.5.0 **/ void fwupd_security_attr_add_obsolete(FwupdSecurityAttr *self, const gchar *appstream_id) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); g_return_if_fail(appstream_id != NULL); if (fwupd_security_attr_has_obsolete(self, appstream_id)) return; g_ptr_array_add(priv->obsoletes, g_strdup(appstream_id)); } /** * fwupd_security_attr_has_obsolete: * @self: a #FwupdSecurityAttr * @appstream_id: the attribute appstream_id * * Finds out if the attribute obsoletes a specific appstream_id. * * Returns: %TRUE if the self matches * * Since: 1.5.0 **/ gboolean fwupd_security_attr_has_obsolete(FwupdSecurityAttr *self, const gchar *appstream_id) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), FALSE); g_return_val_if_fail(appstream_id != NULL, FALSE); for (guint i = 0; i < priv->obsoletes->len; i++) { const gchar *obsolete_tmp = g_ptr_array_index(priv->obsoletes, i); if (g_strcmp0(obsolete_tmp, appstream_id) == 0) return TRUE; } return FALSE; } /** * fwupd_security_attr_get_guids: * @self: a #FwupdSecurityAttr * * Gets the list of attribute GUIDs. The GUID values will not modify the calculated HSI value. * * Returns: (element-type utf8) (transfer none): the GUIDs, which may be empty * * Since: 1.7.0 **/ GPtrArray * fwupd_security_attr_get_guids(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); return priv->guids; } /** * fwupd_security_attr_add_guid: * @self: a #FwupdSecurityAttr * @guid: (not nullable): the GUID * * Adds a device GUID to the attribute. This indicates the GUID in some way contributed to the * result decided. * * Since: 1.7.0 **/ void fwupd_security_attr_add_guid(FwupdSecurityAttr *self, const gchar *guid) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); g_return_if_fail(fwupd_guid_is_valid(guid)); if (fwupd_security_attr_has_guid(self, guid)) return; g_ptr_array_add(priv->guids, g_strdup(guid)); } /** * fwupd_security_attr_add_guids: * @self: a #FwupdSecurityAttr * @guids: (element-type utf8): the GUIDs * * Adds device GUIDs to the attribute. This indicates the GUIDs in some way contributed to the * result decided. * * Since: 1.7.0 **/ void fwupd_security_attr_add_guids(FwupdSecurityAttr *self, GPtrArray *guids) { g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); g_return_if_fail(guids != NULL); for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index(guids, i); fwupd_security_attr_add_guid(self, guid); } } /** * fwupd_security_attr_has_guid: * @self: a #FwupdSecurityAttr * @guid: the attribute guid * * Finds out if a specific GUID was added to the attribute. * * Returns: %TRUE if the self matches * * Since: 1.7.0 **/ gboolean fwupd_security_attr_has_guid(FwupdSecurityAttr *self, const gchar *guid) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), FALSE); g_return_val_if_fail(guid != NULL, FALSE); for (guint i = 0; i < priv->guids->len; i++) { const gchar *guid_tmp = g_ptr_array_index(priv->guids, i); if (g_strcmp0(guid_tmp, guid) == 0) return TRUE; } return FALSE; } /** * fwupd_security_attr_get_appstream_id: * @self: a #FwupdSecurityAttr * * Gets the AppStream ID. * * Returns: the AppStream ID, or %NULL if unset * * Since: 1.5.0 **/ const gchar * fwupd_security_attr_get_appstream_id(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); return priv->appstream_id; } /** * fwupd_security_attr_set_appstream_id: * @self: a #FwupdSecurityAttr * @appstream_id: (nullable): the AppStream component ID, e.g. `com.intel.BiosGuard` * * Sets the AppStream ID. * * Since: 1.5.0 **/ void fwupd_security_attr_set_appstream_id(FwupdSecurityAttr *self, const gchar *appstream_id) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); /* not changed */ if (g_strcmp0(priv->appstream_id, appstream_id) == 0) return; /* sanity check */ if (appstream_id != NULL && !g_str_has_prefix(appstream_id, "org.fwupd.hsi.")) g_critical("HSI attributes need to have a 'org.fwupd.hsi.' prefix"); g_free(priv->appstream_id); priv->appstream_id = g_strdup(appstream_id); } /** * fwupd_security_attr_get_url: * @self: a #FwupdSecurityAttr * * Gets the attribute URL. * * Returns: the attribute result, or %NULL if unset * * Since: 1.5.0 **/ const gchar * fwupd_security_attr_get_url(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); return priv->url; } /** * fwupd_security_attr_set_name: * @self: a #FwupdSecurityAttr * @name: (nullable): the attribute name * * Sets the attribute name. * * Since: 1.5.0 **/ void fwupd_security_attr_set_name(FwupdSecurityAttr *self, const gchar *name) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); /* not changed */ if (g_strcmp0(priv->name, name) == 0) return; g_free(priv->name); priv->name = g_strdup(name); } /** * fwupd_security_attr_get_bios_setting_target_value: * @self: a #FwupdSecurityAttr * * Gets the value that when written to an attribute would activate it or satisfy * a security requirement. * * Returns: the target value of the attribute. * * Since: 1.8.4 **/ const gchar * fwupd_security_attr_get_bios_setting_target_value(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); return priv->bios_setting_target_value; } /** * fwupd_security_attr_set_bios_setting_target_value: * @self: a #FwupdSecurityAttr * @value: (nullable): The string to set target value to * * Sets the string used for the target value of an attribute. * * Since: 1.8.4 **/ void fwupd_security_attr_set_bios_setting_target_value(FwupdSecurityAttr *self, const gchar *value) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->bios_setting_target_value, value) == 0) return; g_free(priv->bios_setting_target_value); priv->bios_setting_target_value = g_strdup(value); } /** * fwupd_security_attr_get_bios_setting_current_value: * @self: a #FwupdSecurityAttr * * Gets the current value of the BIOS setting that can be changed. * * Returns: the current value of the attribute. * * Since: 1.8.4 **/ const gchar * fwupd_security_attr_get_bios_setting_current_value(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); return priv->bios_setting_current_value; } /** * fwupd_security_attr_set_bios_setting_current_value: * @self: a #FwupdSecurityAttr * @value: (nullable): The string to set current value to * * Sets the current value of the BIOS setting that can be changed. * * Since: 1.8.4 **/ void fwupd_security_attr_set_bios_setting_current_value(FwupdSecurityAttr *self, const gchar *value) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->bios_setting_current_value, value) == 0) return; g_free(priv->bios_setting_current_value); priv->bios_setting_current_value = g_strdup(value); } /** * fwupd_security_attr_get_kernel_current_value: * @self: a #FwupdSecurityAttr * * Gets the current value of the BIOS setting that can be changed. * * Returns: the current value of the attribute. * * Since: 1.9.6 **/ const gchar * fwupd_security_attr_get_kernel_current_value(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); return priv->kernel_current_value; } /** * fwupd_security_attr_set_kernel_current_value: * @self: a #FwupdSecurityAttr * @value: (nullable): The string to set current value to * * Sets the current value of the BIOS setting that can be changed. * * Since: 1.9.6 **/ void fwupd_security_attr_set_kernel_current_value(FwupdSecurityAttr *self, const gchar *value) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->kernel_current_value, value) == 0) return; g_free(priv->kernel_current_value); priv->kernel_current_value = g_strdup(value); } /** * fwupd_security_attr_get_kernel_target_value: * @self: a #FwupdSecurityAttr * * Gets the target value of the kernel setting that can be changed. * * Returns: the current value of the attribute. * * Since: 1.9.6 **/ const gchar * fwupd_security_attr_get_kernel_target_value(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); return priv->kernel_target_value; } /** * fwupd_security_attr_set_kernel_target_value: * @self: a #FwupdSecurityAttr * @value: (nullable): The string to set current value to * * Sets the target value of the kernel setting that can be changed. * * Since: 1.9.6 **/ void fwupd_security_attr_set_kernel_target_value(FwupdSecurityAttr *self, const gchar *value) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->kernel_target_value, value) == 0) return; g_free(priv->kernel_target_value); priv->kernel_target_value = g_strdup(value); } /** * fwupd_security_attr_set_title: * @self: a #FwupdSecurityAttr * @title: (nullable): the attribute title * * Sets the attribute title. * * Since: 1.8.2 **/ void fwupd_security_attr_set_title(FwupdSecurityAttr *self, const gchar *title) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); /* not changed */ if (g_strcmp0(priv->title, title) == 0) return; g_free(priv->title); priv->title = g_strdup(title); } /** * fwupd_security_attr_set_description: * @self: a #FwupdSecurityAttr * @description: (nullable): the attribute description * * Sets the attribute description. * * Since: 1.8.2 **/ void fwupd_security_attr_set_description(FwupdSecurityAttr *self, const gchar *description) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); /* not changed */ if (g_strcmp0(priv->description, description) == 0) return; g_free(priv->description); priv->description = g_strdup(description); } /** * fwupd_security_attr_set_plugin: * @self: a #FwupdSecurityAttr * @plugin: (nullable): the plugin name * * Sets the plugin that created the attribute. * * Since: 1.5.0 **/ void fwupd_security_attr_set_plugin(FwupdSecurityAttr *self, const gchar *plugin) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); /* not changed */ if (g_strcmp0(priv->plugin, plugin) == 0) return; g_free(priv->plugin); priv->plugin = g_strdup(plugin); } /** * fwupd_security_attr_set_url: * @self: a #FwupdSecurityAttr * @url: (nullable): the attribute URL * * Sets the attribute result. * * Since: 1.5.0 **/ void fwupd_security_attr_set_url(FwupdSecurityAttr *self, const gchar *url) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); /* not changed */ if (g_strcmp0(priv->url, url) == 0) return; g_free(priv->url); priv->url = g_strdup(url); } /** * fwupd_security_attr_get_created: * @self: a #FwupdSecurityAttr * * Gets when the attribute was created. * * Returns: the UNIX time, or 0 if unset * * Since: 1.7.1 **/ guint64 fwupd_security_attr_get_created(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), 0); return priv->created; } /** * fwupd_security_attr_set_created: * @self: a #FwupdSecurityAttr * @created: the UNIX time * * Sets when the attribute was created. * * Since: 1.7.1 **/ void fwupd_security_attr_set_created(FwupdSecurityAttr *self, guint64 created) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); priv->created = created; } /** * fwupd_security_attr_get_name: * @self: a #FwupdSecurityAttr * * Gets the attribute name. * * Returns: the attribute name, or %NULL if unset * * Since: 1.5.0 **/ const gchar * fwupd_security_attr_get_name(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); return priv->name; } /** * fwupd_security_attr_get_title: * @self: a #FwupdSecurityAttr * * Gets the attribute title, which is typically a two word title. * * The fwupd client program may be able to get translations for this value using a method call * like `dgettext("fwupd",str)`. * * Returns: the attribute title, or %NULL if unset * * Since: 1.8.2 **/ const gchar * fwupd_security_attr_get_title(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); return priv->title; } /** * fwupd_security_attr_get_description: * @self: a #FwupdSecurityAttr * * Gets the attribute description which is a few lines of prose that normal users will understand. * * The fwupd client program may be able to get translations for this value using a method call * like `dgettext("fwupd",str)`. * * NOTE: The returned string may contain placeholders such as `$HostVendor$` or `$HostProduct$` * and these should be replaced with the values from [method@FwupdClient.get_host_vendor] and * [method@FwupdClient.get_host_product]. * * Returns: the attribute description, or %NULL if unset * * Since: 1.8.2 **/ const gchar * fwupd_security_attr_get_description(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); return priv->description; } /** * fwupd_security_attr_get_plugin: * @self: a #FwupdSecurityAttr * * Gets the plugin that created the attribute. * * Returns: the plugin name, or %NULL if unset * * Since: 1.5.0 **/ const gchar * fwupd_security_attr_get_plugin(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); return priv->plugin; } /** * fwupd_security_attr_get_flags: * @self: a #FwupdSecurityAttr * * Gets the self flags. * * Returns: security attribute flags, or 0 if unset * * Since: 1.5.0 **/ FwupdSecurityAttrFlags fwupd_security_attr_get_flags(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), 0); return priv->flags; } /** * fwupd_security_attr_set_flags: * @self: a #FwupdSecurityAttr * @flags: security attribute flags, e.g. %FWUPD_SECURITY_ATTR_FLAG_OBSOLETED * * Sets the attribute flags. * * Since: 1.5.0 **/ void fwupd_security_attr_set_flags(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flags) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); priv->flags = flags; } /* copy over the success result if not already set */ static void fwupd_security_attr_ensure_result(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); if (!fwupd_security_attr_has_flag(self, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) return; if (fwupd_security_attr_has_flag(self, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA)) fwupd_security_attr_remove_flag(self, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); if (priv->result == FWUPD_SECURITY_ATTR_RESULT_UNKNOWN && priv->result_success != FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) { g_debug("auto-setting %s result %s", priv->appstream_id, fwupd_security_attr_result_to_string(priv->result_success)); priv->result = priv->result_success; } } /** * fwupd_security_attr_add_flag: * @self: a #FwupdSecurityAttr * @flag: the #FwupdSecurityAttrFlags, e.g. %FWUPD_SECURITY_ATTR_FLAG_OBSOLETED * * Adds a specific attribute flag to the attribute. * * Since: 1.5.0 **/ void fwupd_security_attr_add_flag(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flag) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); priv->flags |= flag; fwupd_security_attr_ensure_result(self); } /** * fwupd_security_attr_remove_flag: * @self: a #FwupdSecurityAttr * @flag: the #FwupdSecurityAttrFlags, e.g. %FWUPD_SECURITY_ATTR_FLAG_OBSOLETED * * Removes a specific attribute flag from the attribute. * * Since: 1.8.3 **/ void fwupd_security_attr_remove_flag(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flag) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); priv->flags &= ~flag; } /** * fwupd_security_attr_has_flag: * @self: a #FwupdSecurityAttr * @flag: the attribute flag, e.g. %FWUPD_SECURITY_ATTR_FLAG_OBSOLETED * * Finds if the attribute has a specific attribute flag. * * Returns: %TRUE if the flag is set * * Since: 1.5.0 **/ gboolean fwupd_security_attr_has_flag(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flag) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), FALSE); return (priv->flags & flag) > 0; } /** * fwupd_security_attr_get_level: * @self: a #FwupdSecurityAttr * * Gets the HSI level. * * Returns: the security attribute level, or %FWUPD_SECURITY_ATTR_LEVEL_NONE if unset * * Since: 1.5.0 **/ FwupdSecurityAttrLevel fwupd_security_attr_get_level(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), 0); return priv->level; } /** * fwupd_security_attr_set_level: * @self: a #FwupdSecurityAttr * @level: a security attribute level, e.g. %FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT * * Sets the HSI level. A @level of %FWUPD_SECURITY_ATTR_LEVEL_NONE is not used * for the HSI calculation. * * Since: 1.5.0 **/ void fwupd_security_attr_set_level(FwupdSecurityAttr *self, FwupdSecurityAttrLevel level) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); priv->level = level; } /** * fwupd_security_attr_set_result: * @self: a #FwupdSecurityAttr * @result: a security attribute result, e.g. %FWUPD_SECURITY_ATTR_LEVEL_LOCKED * * Sets the optional HSI result. This is required because some attributes may * be a "success" when something is `locked` or may be "failed" if `found`. * * Since: 1.5.0 **/ void fwupd_security_attr_set_result(FwupdSecurityAttr *self, FwupdSecurityAttrResult result) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); /* fixup any legacy attributes to the 'modern' value */ if (g_strcmp0(priv->appstream_id, FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM) == 0 && result == FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED) { result = FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED; } priv->result = result; } /** * fwupd_security_attr_get_result: * @self: a #FwupdSecurityAttr * * Gets the optional HSI result. * * Returns: the #FwupdSecurityAttrResult, e.g %FWUPD_SECURITY_ATTR_LEVEL_LOCKED * * Since: 1.5.0 **/ FwupdSecurityAttrResult fwupd_security_attr_get_result(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), 0); return priv->result; } /** * fwupd_security_attr_set_result_fallback: * @self: a #FwupdSecurityAttr * @result: a security attribute, e.g. %FWUPD_SECURITY_ATTR_LEVEL_LOCKED * * Sets the optional fallback HSI result. The fallback may represent the old state, or a state * that may be considered equivalent. * * Since: 1.7.1 **/ void fwupd_security_attr_set_result_fallback(FwupdSecurityAttr *self, FwupdSecurityAttrResult result) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); priv->result_fallback = result; } /** * fwupd_security_attr_get_result_fallback: * @self: a #FwupdSecurityAttr * * Gets the optional fallback HSI result. * * Returns: the #FwupdSecurityAttrResult, e.g %FWUPD_SECURITY_ATTR_LEVEL_LOCKED * * Since: 1.7.1 **/ FwupdSecurityAttrResult fwupd_security_attr_get_result_fallback(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), 0); return priv->result_fallback; } /** * fwupd_security_attr_set_result_success: * @self: a #FwupdSecurityAttr * @result: a security attribute, e.g. %FWUPD_SECURITY_ATTR_LEVEL_LOCKED * * Sets the desired HSI result. * * Since: 1.9.3 **/ void fwupd_security_attr_set_result_success(FwupdSecurityAttr *self, FwupdSecurityAttrResult result) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); priv->result_success = result; fwupd_security_attr_ensure_result(self); } /** * fwupd_security_attr_get_result_success: * @self: a #FwupdSecurityAttr * * Gets the desired HSI result. * * Returns: the #FwupdSecurityAttrResult, e.g %FWUPD_SECURITY_ATTR_LEVEL_LOCKED * * Since: 1.9.3 **/ FwupdSecurityAttrResult fwupd_security_attr_get_result_success(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), 0); return priv->result_success; } /** * fwupd_security_attr_to_variant: * @self: a #FwupdSecurityAttr * * Serialize the security attribute. * * Returns: the serialized data, or %NULL for error * * Since: 1.5.0 **/ GVariant * fwupd_security_attr_to_variant(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); GVariantBuilder builder; g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); if (priv->appstream_id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_APPSTREAM_ID, g_variant_new_string(priv->appstream_id)); } if (priv->created > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_CREATED, g_variant_new_uint64(priv->created)); } if (priv->name != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_NAME, g_variant_new_string(priv->name)); } if (priv->title != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_SUMMARY, g_variant_new_string(priv->title)); } if (priv->description != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_DESCRIPTION, g_variant_new_string(priv->description)); } if (priv->plugin != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_PLUGIN, g_variant_new_string(priv->plugin)); } if (priv->url != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_URI, g_variant_new_string(priv->url)); } if (priv->obsoletes->len > 0) { g_autofree const gchar **strv = g_new0(const gchar *, priv->obsoletes->len + 1); for (guint i = 0; i < priv->obsoletes->len; i++) strv[i] = (const gchar *)g_ptr_array_index(priv->obsoletes, i); g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_CATEGORIES, g_variant_new_strv(strv, -1)); } if (priv->guids->len > 0) { g_autofree const gchar **strv = g_new0(const gchar *, priv->guids->len + 1); for (guint i = 0; i < priv->guids->len; i++) strv[i] = (const gchar *)g_ptr_array_index(priv->guids, i); g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_GUID, g_variant_new_strv(strv, -1)); } if (priv->flags != 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_FLAGS, g_variant_new_uint64(priv->flags)); } if (priv->level > 0) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_HSI_LEVEL, g_variant_new_uint32(priv->level)); } if (priv->result != FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_HSI_RESULT, g_variant_new_uint32(priv->result)); } if (priv->result_fallback != FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_HSI_RESULT_FALLBACK, g_variant_new_uint32(priv->result_fallback)); } if (priv->result_success != FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_HSI_RESULT_SUCCESS, g_variant_new_uint32(priv->result_success)); } if (priv->metadata != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_METADATA, fwupd_hash_kv_to_variant(priv->metadata)); } if (priv->bios_setting_id != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BIOS_SETTING_ID, g_variant_new_string(priv->bios_setting_id)); } if (priv->bios_setting_target_value != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BIOS_SETTING_TARGET_VALUE, g_variant_new_string(priv->bios_setting_target_value)); } if (priv->bios_setting_current_value != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE, g_variant_new_string(priv->bios_setting_current_value)); } if (priv->kernel_current_value != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_KERNEL_CURRENT_VALUE, g_variant_new_string(priv->kernel_current_value)); } if (priv->kernel_target_value != NULL) { g_variant_builder_add(&builder, "{sv}", FWUPD_RESULT_KEY_KERNEL_TARGET_VALUE, g_variant_new_string(priv->kernel_target_value)); } return g_variant_new("a{sv}", &builder); } /** * fwupd_security_attr_get_metadata: * @self: a #FwupdSecurityAttr * @key: metadata key * * Gets private metadata from the attribute which may be used in the name. * * Returns: (nullable): the metadata value, or %NULL if unfound * * Since: 1.5.0 **/ const gchar * fwupd_security_attr_get_metadata(FwupdSecurityAttr *self, const gchar *key) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); g_return_val_if_fail(key != NULL, NULL); if (priv->metadata == NULL) return NULL; return g_hash_table_lookup(priv->metadata, key); } /** * fwupd_security_attr_add_metadata: * @self: a #FwupdSecurityAttr * @key: metadata key * @value: (nullable): metadata value * * Adds metadata to the attribute which may be used in the name. * * Since: 1.5.0 **/ void fwupd_security_attr_add_metadata(FwupdSecurityAttr *self, const gchar *key, const gchar *value) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); g_return_if_fail(key != NULL); if (priv->metadata == NULL) { priv->metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); } g_hash_table_insert(priv->metadata, g_strdup(key), g_strdup(value)); } static void fwupd_security_attr_from_key_value(FwupdSecurityAttr *self, const gchar *key, GVariant *value) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); if (g_strcmp0(key, FWUPD_RESULT_KEY_APPSTREAM_ID) == 0) { fwupd_security_attr_set_appstream_id(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_CREATED) == 0) { fwupd_security_attr_set_created(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_NAME) == 0) { fwupd_security_attr_set_name(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_SUMMARY) == 0) { fwupd_security_attr_set_title(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_DESCRIPTION) == 0) { fwupd_security_attr_set_description(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_PLUGIN) == 0) { fwupd_security_attr_set_plugin(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_URI) == 0) { fwupd_security_attr_set_url(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_FLAGS) == 0) { fwupd_security_attr_set_flags(self, g_variant_get_uint64(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_HSI_LEVEL) == 0) { fwupd_security_attr_set_level(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_HSI_RESULT) == 0) { fwupd_security_attr_set_result(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_HSI_RESULT_FALLBACK) == 0) { fwupd_security_attr_set_result_fallback(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_HSI_RESULT_SUCCESS) == 0) { fwupd_security_attr_set_result_success(self, g_variant_get_uint32(value)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_GUID) == 0) { g_autofree const gchar **strv = g_variant_get_strv(value, NULL); for (guint i = 0; strv[i] != NULL; i++) fwupd_security_attr_add_guid(self, strv[i]); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_METADATA) == 0) { if (priv->metadata != NULL) g_hash_table_unref(priv->metadata); priv->metadata = fwupd_variant_to_hash_kv(value); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_ID) == 0) { fwupd_security_attr_set_bios_setting_id(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_TARGET_VALUE) == 0) { fwupd_security_attr_set_bios_setting_target_value( self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE) == 0) { fwupd_security_attr_set_bios_setting_current_value( self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_KERNEL_CURRENT_VALUE) == 0) { fwupd_security_attr_set_kernel_current_value(self, g_variant_get_string(value, NULL)); return; } if (g_strcmp0(key, FWUPD_RESULT_KEY_KERNEL_TARGET_VALUE) == 0) { fwupd_security_attr_set_kernel_target_value(self, g_variant_get_string(value, NULL)); return; } } static void fwupd_pad_kv_tfl(GString *str, const gchar *key, FwupdSecurityAttrFlags security_attr_flags) { g_autoptr(GString) tmp = g_string_new(""); for (guint i = 0; i < 64; i++) { if ((security_attr_flags & ((guint64)1 << i)) == 0) continue; g_string_append_printf(tmp, "%s|", fwupd_security_attr_flag_to_string((guint64)1 << i)); } if (tmp->len == 0) { g_string_append(tmp, fwupd_security_attr_flag_to_string(0)); } else { g_string_truncate(tmp, tmp->len - 1); } fwupd_pad_kv_str(str, key, tmp->str); } /** * fwupd_security_attr_from_json: * @self: a #FwupdSecurityAttr * @json_node: (not nullable): a JSON node * @error: (nullable): optional return location for an error * * Loads a fwupd security attribute from a JSON node. * * Returns: %TRUE for success * * Since: 1.7.1 **/ gboolean fwupd_security_attr_from_json(FwupdSecurityAttr *self, JsonNode *json_node, GError **error) { JsonObject *obj; g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), FALSE); g_return_val_if_fail(json_node != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* sanity check */ if (!JSON_NODE_HOLDS_OBJECT(json_node)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not JSON object"); return FALSE; } obj = json_node_get_object(json_node); /* this has to exist */ if (!json_object_has_member(obj, FWUPD_RESULT_KEY_APPSTREAM_ID)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no %s property in object", FWUPD_RESULT_KEY_APPSTREAM_ID); return FALSE; } /* all optional */ fwupd_security_attr_set_appstream_id( self, json_object_get_string_member(obj, FWUPD_RESULT_KEY_APPSTREAM_ID)); fwupd_security_attr_set_name( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_NAME, NULL)); fwupd_security_attr_set_title( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_SUMMARY, NULL)); fwupd_security_attr_set_description( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_DESCRIPTION, NULL)); fwupd_security_attr_set_plugin( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_PLUGIN, NULL)); fwupd_security_attr_set_url( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_URI, NULL)); fwupd_security_attr_set_level( self, json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_HSI_LEVEL, 0)); fwupd_security_attr_set_created( self, json_object_get_int_member_with_default(obj, FWUPD_RESULT_KEY_CREATED, 0)); fwupd_security_attr_set_bios_setting_id( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_BIOS_SETTING_ID, NULL)); fwupd_security_attr_set_bios_setting_target_value( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_BIOS_SETTING_TARGET_VALUE, NULL)); fwupd_security_attr_set_bios_setting_current_value( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE, NULL)); fwupd_security_attr_set_kernel_current_value( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_KERNEL_CURRENT_VALUE, NULL)); fwupd_security_attr_set_kernel_target_value( self, json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_KERNEL_TARGET_VALUE, NULL)); /* also optional */ if (json_object_has_member(obj, FWUPD_RESULT_KEY_HSI_RESULT)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_HSI_RESULT, NULL); fwupd_security_attr_set_result(self, fwupd_security_attr_result_from_string(tmp)); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_HSI_RESULT_FALLBACK)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_HSI_RESULT_FALLBACK, NULL); fwupd_security_attr_set_result_fallback( self, fwupd_security_attr_result_from_string(tmp)); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_HSI_RESULT_SUCCESS)) { const gchar *tmp = json_object_get_string_member_with_default(obj, FWUPD_RESULT_KEY_HSI_RESULT_SUCCESS, NULL); fwupd_security_attr_set_result_success(self, fwupd_security_attr_result_from_string(tmp)); } if (json_object_has_member(obj, FWUPD_RESULT_KEY_FLAGS)) { JsonArray *array = json_object_get_array_member(obj, FWUPD_RESULT_KEY_FLAGS); for (guint i = 0; i < json_array_get_length(array); i++) { const gchar *tmp = json_array_get_string_element(array, i); FwupdSecurityAttrFlags flag = fwupd_security_attr_flag_from_string(tmp); if (flag != FWUPD_SECURITY_ATTR_FLAG_NONE) fwupd_security_attr_add_flag(self, flag); } } if (json_object_has_member(obj, FWUPD_RESULT_KEY_GUID)) { JsonArray *array = json_object_get_array_member(obj, FWUPD_RESULT_KEY_GUID); for (guint i = 0; i < json_array_get_length(array); i++) { const gchar *tmp = json_array_get_string_element(array, i); fwupd_security_attr_add_guid(self, tmp); } } /* success */ return TRUE; } /** * fwupd_security_attr_to_json: * @self: a #FwupdSecurityAttr * @builder: a JSON builder * * Adds a fwupd security attribute to a JSON builder * * Since: 1.5.0 **/ void fwupd_security_attr_to_json(FwupdSecurityAttr *self, JsonBuilder *builder) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self)); g_return_if_fail(builder != NULL); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_APPSTREAM_ID, priv->appstream_id); if (priv->created > 0) fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_CREATED, priv->created); fwupd_common_json_add_int(builder, FWUPD_RESULT_KEY_HSI_LEVEL, priv->level); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_HSI_RESULT, fwupd_security_attr_result_to_string(priv->result)); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_HSI_RESULT_FALLBACK, fwupd_security_attr_result_to_string(priv->result_fallback)); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_HSI_RESULT_SUCCESS, fwupd_security_attr_result_to_string(priv->result_success)); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_NAME, priv->name); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_SUMMARY, priv->title); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_DESCRIPTION, priv->description); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_PLUGIN, priv->plugin); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_URI, priv->url); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_BIOS_SETTING_TARGET_VALUE, priv->bios_setting_target_value); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE, priv->bios_setting_current_value); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_BIOS_SETTING_ID, priv->bios_setting_id); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_KERNEL_CURRENT_VALUE, priv->kernel_current_value); fwupd_common_json_add_string(builder, FWUPD_RESULT_KEY_KERNEL_TARGET_VALUE, priv->kernel_target_value); if (priv->flags != FWUPD_SECURITY_ATTR_FLAG_NONE) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_FLAGS); json_builder_begin_array(builder); for (guint i = 0; i < 64; i++) { const gchar *tmp; if ((priv->flags & ((guint64)1 << i)) == 0) continue; tmp = fwupd_security_attr_flag_to_string((guint64)1 << i); json_builder_add_string_value(builder, tmp); } json_builder_end_array(builder); } if (priv->guids->len > 0) { json_builder_set_member_name(builder, FWUPD_RESULT_KEY_GUID); json_builder_begin_array(builder); for (guint i = 0; i < priv->guids->len; i++) { const gchar *guid = g_ptr_array_index(priv->guids, i); json_builder_add_string_value(builder, guid); } json_builder_end_array(builder); } if (priv->metadata != NULL) { g_autoptr(GList) keys = g_hash_table_get_keys(priv->metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup(priv->metadata, key); fwupd_common_json_add_string(builder, key, value); } } } /** * fwupd_security_attr_to_string: * @self: a #FwupdSecurityAttr * * Builds a text representation of the object. * * Returns: text, or %NULL for invalid * * Since: 1.5.0 **/ gchar * fwupd_security_attr_to_string(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); GString *str; g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); str = g_string_new(""); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_APPSTREAM_ID, priv->appstream_id); if (priv->created > 0) fwupd_pad_kv_unx(str, FWUPD_RESULT_KEY_CREATED, priv->created); fwupd_pad_kv_int(str, FWUPD_RESULT_KEY_HSI_LEVEL, priv->level); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_HSI_RESULT, fwupd_security_attr_result_to_string(priv->result)); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_HSI_RESULT_FALLBACK, fwupd_security_attr_result_to_string(priv->result_fallback)); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_HSI_RESULT_SUCCESS, fwupd_security_attr_result_to_string(priv->result_success)); if (priv->flags != FWUPD_SECURITY_ATTR_FLAG_NONE) fwupd_pad_kv_tfl(str, FWUPD_RESULT_KEY_FLAGS, priv->flags); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_NAME, priv->name); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_SUMMARY, priv->title); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_DESCRIPTION, priv->description); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_PLUGIN, priv->plugin); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_URI, priv->url); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_BIOS_SETTING_ID, priv->bios_setting_id); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_BIOS_SETTING_TARGET_VALUE, priv->bios_setting_target_value); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE, priv->bios_setting_current_value); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_KERNEL_CURRENT_VALUE, priv->kernel_current_value); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_KERNEL_TARGET_VALUE, priv->kernel_target_value); for (guint i = 0; i < priv->obsoletes->len; i++) { const gchar *appstream_id = g_ptr_array_index(priv->obsoletes, i); fwupd_pad_kv_str(str, "Obsolete", appstream_id); } for (guint i = 0; i < priv->guids->len; i++) { const gchar *guid = g_ptr_array_index(priv->guids, i); fwupd_pad_kv_str(str, FWUPD_RESULT_KEY_GUID, guid); } if (priv->metadata != NULL) { g_autoptr(GList) keys = g_hash_table_get_keys(priv->metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup(priv->metadata, key); fwupd_pad_kv_str(str, key, value); } } return g_string_free(str, FALSE); } static void fwupd_security_attr_class_init(FwupdSecurityAttrClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fwupd_security_attr_finalize; } static void fwupd_security_attr_init(FwupdSecurityAttr *self) { FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); priv->level = FWUPD_SECURITY_ATTR_LEVEL_NONE; priv->obsoletes = g_ptr_array_new_with_free_func(g_free); priv->guids = g_ptr_array_new_with_free_func(g_free); priv->created = (guint64)g_get_real_time() / G_USEC_PER_SEC; } static void fwupd_security_attr_finalize(GObject *object) { FwupdSecurityAttr *self = FWUPD_SECURITY_ATTR(object); FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); if (priv->metadata != NULL) g_hash_table_unref(priv->metadata); g_free(priv->bios_setting_id); g_free(priv->bios_setting_target_value); g_free(priv->bios_setting_current_value); g_free(priv->kernel_current_value); g_free(priv->kernel_target_value); g_free(priv->appstream_id); g_free(priv->name); g_free(priv->title); g_free(priv->description); g_free(priv->plugin); g_free(priv->url); g_ptr_array_unref(priv->obsoletes); g_ptr_array_unref(priv->guids); G_OBJECT_CLASS(fwupd_security_attr_parent_class)->finalize(object); } static void fwupd_security_attr_set_from_variant_iter(FwupdSecurityAttr *self, GVariantIter *iter) { GVariant *value; const gchar *key; while (g_variant_iter_next(iter, "{&sv}", &key, &value)) { fwupd_security_attr_from_key_value(self, key, value); g_variant_unref(value); } } /** * fwupd_security_attr_from_variant: * @value: (not nullable): the serialized data * * Creates a new security attribute using serialized data. * * Returns: (transfer full): a new #FwupdSecurityAttr, or %NULL if @value was invalid * * Since: 1.5.0 **/ FwupdSecurityAttr * fwupd_security_attr_from_variant(GVariant *value) { FwupdSecurityAttr *rel = NULL; const gchar *type_string; g_autoptr(GVariantIter) iter = NULL; g_return_val_if_fail(value != NULL, NULL); type_string = g_variant_get_type_string(value); if (g_strcmp0(type_string, "(a{sv})") == 0) { rel = fwupd_security_attr_new(NULL); g_variant_get(value, "(a{sv})", &iter); fwupd_security_attr_set_from_variant_iter(rel, iter); } else if (g_strcmp0(type_string, "a{sv}") == 0) { rel = fwupd_security_attr_new(NULL); g_variant_get(value, "a{sv}", &iter); fwupd_security_attr_set_from_variant_iter(rel, iter); } else { g_warning("type %s not known", type_string); } return rel; } /** * fwupd_security_attr_array_from_variant: * @value: (not nullable): the serialized data * * Creates an array of new security attributes using serialized data. * * Returns: (transfer container) (element-type FwupdSecurityAttr): attributes, or %NULL if @value *was invalid * * Since: 1.5.0 **/ GPtrArray * fwupd_security_attr_array_from_variant(GVariant *value) { GPtrArray *array = NULL; gsize sz; g_autoptr(GVariant) untuple = NULL; g_return_val_if_fail(value != NULL, NULL); array = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); untuple = g_variant_get_child_value(value, 0); sz = g_variant_n_children(untuple); for (guint i = 0; i < sz; i++) { FwupdSecurityAttr *rel; g_autoptr(GVariant) data = NULL; data = g_variant_get_child_value(untuple, i); rel = fwupd_security_attr_from_variant(data); if (rel == NULL) continue; g_ptr_array_add(array, rel); } return array; } /** * fwupd_security_attr_copy: * @self: (nullable): a #FwupdSecurityAttr * * Makes a full (deep) copy of a security attribute. * * Returns: (transfer full): a new #FwupdSecurityAttr * * Since: 1.7.1 **/ FwupdSecurityAttr * fwupd_security_attr_copy(FwupdSecurityAttr *self) { g_autoptr(FwupdSecurityAttr) new = g_object_new(FWUPD_TYPE_SECURITY_ATTR, NULL); FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL); fwupd_security_attr_set_appstream_id(new, priv->appstream_id); fwupd_security_attr_set_name(new, priv->name); fwupd_security_attr_set_title(new, priv->title); fwupd_security_attr_set_description(new, priv->description); fwupd_security_attr_set_plugin(new, priv->plugin); fwupd_security_attr_set_url(new, priv->url); fwupd_security_attr_set_level(new, priv->level); fwupd_security_attr_set_flags(new, priv->flags); fwupd_security_attr_set_result(new, priv->result); fwupd_security_attr_set_created(new, priv->created); fwupd_security_attr_set_bios_setting_id(new, priv->bios_setting_id); for (guint i = 0; i < priv->guids->len; i++) { const gchar *guid = g_ptr_array_index(priv->guids, i); fwupd_security_attr_add_guid(new, guid); } for (guint i = 0; i < priv->obsoletes->len; i++) { const gchar *obsolete = g_ptr_array_index(priv->obsoletes, i); fwupd_security_attr_add_obsolete(new, obsolete); } if (priv->metadata != NULL) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, priv->metadata); while (g_hash_table_iter_next(&iter, &key, &value)) { fwupd_security_attr_add_metadata(new, (const gchar *)key, (const gchar *)value); } } return g_steal_pointer(&new); } /** * fwupd_security_attr_new: * @appstream_id: (nullable): the AppStream component ID, e.g. `com.intel.BiosGuard` * * Creates a new security attribute. * * Plugins should not use this method, and should instead use `fu_plugin_security_attr_new()` or * `fu_security_attr_new()`. * * Returns: a new #FwupdSecurityAttr * * Since: 1.5.0 **/ FwupdSecurityAttr * fwupd_security_attr_new(const gchar *appstream_id) { FwupdSecurityAttr *self; self = g_object_new(FWUPD_TYPE_SECURITY_ATTR, NULL); if (appstream_id != NULL) fwupd_security_attr_set_appstream_id(self, appstream_id); return FWUPD_SECURITY_ATTR(self); } fwupd-1.9.16/libfwupd/fwupd-security-attr.h000066400000000000000000000253061460375044200206770ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-build.h" #include "fwupd-enums.h" G_BEGIN_DECLS #define FWUPD_TYPE_SECURITY_ATTR (fwupd_security_attr_get_type()) G_DECLARE_DERIVABLE_TYPE(FwupdSecurityAttr, fwupd_security_attr, FWUPD, SECURITY_ATTR, GObject) struct _FwupdSecurityAttrClass { GObjectClass parent_class; /*< private >*/ void (*_fwupd_reserved1)(void); void (*_fwupd_reserved2)(void); void (*_fwupd_reserved3)(void); void (*_fwupd_reserved4)(void); void (*_fwupd_reserved5)(void); void (*_fwupd_reserved6)(void); void (*_fwupd_reserved7)(void); }; /** * FwupdSecurityAttrFlags: * @FWUPD_SECURITY_ATTR_FLAG_NONE: No flags set * @FWUPD_SECURITY_ATTR_FLAG_SUCCESS: Success * @FWUPD_SECURITY_ATTR_FLAG_OBSOLETED: Obsoleted by another attribute * @FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA: Missing data * @FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES: Suffix `U` * @FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION: Suffix `A` * @FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE: Suffix `!` * @FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM: Contact the firmware vendor for a update * @FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW: Failure may be fixed by changing FW config * @FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS: Failure may be fixed by changing OS config * @FWUPD_SECURITY_ATTR_FLAG_CAN_FIX: The failure can be automatically fixed * @FWUPD_SECURITY_ATTR_FLAG_CAN_UNDO: The fix can be automatically reverted * * The flags available for HSI attributes. **/ typedef enum { FWUPD_SECURITY_ATTR_FLAG_NONE = 0, FWUPD_SECURITY_ATTR_FLAG_SUCCESS = 1 << 0, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED = 1 << 1, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA = 1 << 2, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES = 1 << 8, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION = 1 << 9, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE = 1 << 10, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM = 1 << 11, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW = 1 << 12, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS = 1 << 13, FWUPD_SECURITY_ATTR_FLAG_CAN_FIX = 1 << 14, FWUPD_SECURITY_ATTR_FLAG_CAN_UNDO = 1 << 15, } FwupdSecurityAttrFlags; /** * FwupdSecurityAttrLevel: * @FWUPD_SECURITY_ATTR_LEVEL_NONE: Very few detected firmware protections * @FWUPD_SECURITY_ATTR_LEVEL_CRITICAL: The most basic of security protections * @FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT: Firmware security issues considered *important * @FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL: Firmware security issues that pose a *theoretical concern * @FWUPD_SECURITY_ATTR_LEVEL_SYSTEM_PROTECTION: Out-of-band protection of the system *firmware * @FWUPD_SECURITY_ATTR_LEVEL_SYSTEM_ATTESTATION: Out-of-band attestation of the system *firmware * * The HSI level. **/ typedef enum { FWUPD_SECURITY_ATTR_LEVEL_NONE = 0, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_LEVEL_CRITICAL = 1, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT = 2, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL = 3, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_LEVEL_SYSTEM_PROTECTION = 4, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_LEVEL_SYSTEM_ATTESTATION = 5, /* Since: 1.5.0 */ /*< private >*/ FWUPD_SECURITY_ATTR_LEVEL_LAST = 6 /* perhaps increased in the future */ } FwupdSecurityAttrLevel; /** * FwupdSecurityAttrResult: * @FWUPD_SECURITY_ATTR_RESULT_UNKNOWN: Not known * @FWUPD_SECURITY_ATTR_RESULT_ENABLED: Enabled * @FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED: Not enabled * @FWUPD_SECURITY_ATTR_RESULT_VALID: Valid * @FWUPD_SECURITY_ATTR_RESULT_NOT_VALID: Not valid * @FWUPD_SECURITY_ATTR_RESULT_LOCKED: Locked * @FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED: Not locked * @FWUPD_SECURITY_ATTR_RESULT_ENCRYPTED: Encrypted * @FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED: Not encrypted * @FWUPD_SECURITY_ATTR_RESULT_TAINTED: Tainted * @FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED: Not tainted * @FWUPD_SECURITY_ATTR_RESULT_FOUND: Found * @FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND: NOt found * @FWUPD_SECURITY_ATTR_RESULT_SUPPORTED: Supported * @FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED: Not supported * * The HSI result. **/ typedef enum { FWUPD_SECURITY_ATTR_RESULT_UNKNOWN, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_ENABLED, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_VALID, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_NOT_VALID, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_LOCKED, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_ENCRYPTED, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_TAINTED, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_FOUND, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_SUPPORTED, /* Since: 1.5.0 */ FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED, /* Since: 1.5.0 */ /*< private >*/ FWUPD_SECURITY_ATTR_RESULT_LAST } FwupdSecurityAttrResult; FwupdSecurityAttr * fwupd_security_attr_new(const gchar *appstream_id); gchar * fwupd_security_attr_to_string(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); const gchar * fwupd_security_attr_get_bios_setting_id(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_bios_setting_id(FwupdSecurityAttr *self, const gchar *id) G_GNUC_NON_NULL(1); const gchar * fwupd_security_attr_get_bios_setting_target_value(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_bios_setting_target_value(FwupdSecurityAttr *self, const gchar *value) G_GNUC_NON_NULL(1); const gchar * fwupd_security_attr_get_bios_setting_current_value(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_bios_setting_current_value(FwupdSecurityAttr *self, const gchar *value) G_GNUC_NON_NULL(1); const gchar * fwupd_security_attr_get_kernel_current_value(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_kernel_current_value(FwupdSecurityAttr *self, const gchar *value) G_GNUC_NON_NULL(1); const gchar * fwupd_security_attr_get_kernel_target_value(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_kernel_target_value(FwupdSecurityAttr *self, const gchar *value) G_GNUC_NON_NULL(1); const gchar * fwupd_security_attr_get_appstream_id(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_appstream_id(FwupdSecurityAttr *self, const gchar *appstream_id) G_GNUC_NON_NULL(1); FwupdSecurityAttrLevel fwupd_security_attr_get_level(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_level(FwupdSecurityAttr *self, FwupdSecurityAttrLevel level) G_GNUC_NON_NULL(1); FwupdSecurityAttrResult fwupd_security_attr_get_result(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_result(FwupdSecurityAttr *self, FwupdSecurityAttrResult result) G_GNUC_NON_NULL(1); FwupdSecurityAttrResult fwupd_security_attr_get_result_fallback(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_result_fallback(FwupdSecurityAttr *self, FwupdSecurityAttrResult result) G_GNUC_NON_NULL(1); FwupdSecurityAttrResult fwupd_security_attr_get_result_success(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_result_success(FwupdSecurityAttr *self, FwupdSecurityAttrResult result) G_GNUC_NON_NULL(1); const gchar * fwupd_security_attr_get_name(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_name(FwupdSecurityAttr *self, const gchar *name) G_GNUC_NON_NULL(1); const gchar * fwupd_security_attr_get_title(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_title(FwupdSecurityAttr *self, const gchar *title) G_GNUC_NON_NULL(1); const gchar * fwupd_security_attr_get_description(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_description(FwupdSecurityAttr *self, const gchar *description) G_GNUC_NON_NULL(1); const gchar * fwupd_security_attr_get_plugin(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_plugin(FwupdSecurityAttr *self, const gchar *plugin) G_GNUC_NON_NULL(1); const gchar * fwupd_security_attr_get_url(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_url(FwupdSecurityAttr *self, const gchar *url) G_GNUC_NON_NULL(1); guint64 fwupd_security_attr_get_created(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_created(FwupdSecurityAttr *self, guint64 created) G_GNUC_NON_NULL(1); GPtrArray * fwupd_security_attr_get_obsoletes(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_add_obsolete(FwupdSecurityAttr *self, const gchar *appstream_id) G_GNUC_NON_NULL(1, 2); gboolean fwupd_security_attr_has_obsolete(FwupdSecurityAttr *self, const gchar *appstream_id) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fwupd_security_attr_get_guids(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_add_guid(FwupdSecurityAttr *self, const gchar *guid) G_GNUC_NON_NULL(1, 2); void fwupd_security_attr_add_guids(FwupdSecurityAttr *self, GPtrArray *guids) G_GNUC_NON_NULL(1, 2); gboolean fwupd_security_attr_has_guid(FwupdSecurityAttr *self, const gchar *guid) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); const gchar * fwupd_security_attr_get_metadata(FwupdSecurityAttr *self, const gchar *key) G_GNUC_NON_NULL(1, 2); void fwupd_security_attr_add_metadata(FwupdSecurityAttr *self, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2); FwupdSecurityAttrFlags fwupd_security_attr_get_flags(FwupdSecurityAttr *self) G_GNUC_NON_NULL(1); void fwupd_security_attr_set_flags(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flags) G_GNUC_NON_NULL(1); void fwupd_security_attr_add_flag(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flag) G_GNUC_NON_NULL(1); void fwupd_security_attr_remove_flag(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flag) G_GNUC_NON_NULL(1); gboolean fwupd_security_attr_has_flag(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flag) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); const gchar * fwupd_security_attr_flag_to_string(FwupdSecurityAttrFlags flag); FwupdSecurityAttrFlags fwupd_security_attr_flag_from_string(const gchar *flag); const gchar * fwupd_security_attr_flag_to_suffix(FwupdSecurityAttrFlags flag); const gchar * fwupd_security_attr_result_to_string(FwupdSecurityAttrResult result); FwupdSecurityAttrResult fwupd_security_attr_result_from_string(const gchar *result); FwupdSecurityAttr * fwupd_security_attr_from_variant(GVariant *value) G_GNUC_NON_NULL(1); GPtrArray * fwupd_security_attr_array_from_variant(GVariant *value) G_GNUC_NON_NULL(1); G_END_DECLS fwupd-1.9.16/libfwupd/fwupd-self-test.c000066400000000000000000001743101460375044200177610ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fwupd-bios-setting-private.h" #include "fwupd-client-sync.h" #include "fwupd-client.h" #include "fwupd-common.h" #include "fwupd-device-private.h" #include "fwupd-enums.h" #include "fwupd-error.h" #include "fwupd-plugin-private.h" #include "fwupd-release-private.h" #include "fwupd-remote-private.h" #include "fwupd-report-private.h" #include "fwupd-request-private.h" #include "fwupd-security-attr-private.h" static gboolean fu_test_compare_lines(const gchar *txt1, const gchar *txt2, GError **error) { g_autofree gchar *output = NULL; /* exactly the same */ if (g_strcmp0(txt1, txt2) == 0) return TRUE; /* matches a pattern */ if (g_pattern_match_simple(txt2, txt1)) return TRUE; /* save temp files and diff them */ if (!g_file_set_contents("/tmp/a", txt1, -1, error)) return FALSE; if (!g_file_set_contents("/tmp/b", txt2, -1, error)) return FALSE; if (!g_spawn_command_line_sync("diff -urNp /tmp/b /tmp/a", &output, NULL, NULL, error)) return FALSE; /* just output the diff */ g_set_error_literal(error, 1, 0, output); return FALSE; } static void fwupd_enums_func(void) { /* enums */ for (guint i = 0; i < FWUPD_ERROR_LAST; i++) { const gchar *tmp = fwupd_error_to_string(i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_error_from_string(tmp), ==, i); } for (guint i = 0; i < FWUPD_STATUS_LAST; i++) { const gchar *tmp = fwupd_status_to_string(i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_status_from_string(tmp), ==, i); } for (guint i = 0; i < FWUPD_UPDATE_STATE_LAST; i++) { const gchar *tmp = fwupd_update_state_to_string(i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_update_state_from_string(tmp), ==, i); } for (guint i = 0; i < FWUPD_TRUST_FLAG_LAST; i++) { const gchar *tmp = fwupd_trust_flag_to_string(i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_trust_flag_from_string(tmp), ==, i); } for (guint i = 0; i < FWUPD_REQUEST_KIND_LAST; i++) { const gchar *tmp = fwupd_request_kind_to_string(i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_request_kind_from_string(tmp), ==, i); } for (guint i = FWUPD_RELEASE_URGENCY_UNKNOWN + 1; i < FWUPD_RELEASE_URGENCY_LAST; i++) { const gchar *tmp = fwupd_release_urgency_to_string(i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_release_urgency_from_string(tmp), ==, i); } for (guint i = 1; i < FWUPD_VERSION_FORMAT_LAST; i++) { const gchar *tmp = fwupd_version_format_to_string(i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_version_format_from_string(tmp), ==, i); } for (guint i = 1; i < FWUPD_REMOTE_KIND_LAST; i++) { const gchar *tmp = fwupd_remote_kind_to_string(i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_remote_kind_from_string(tmp), ==, i); } /* bitfield */ for (guint64 i = 1; i <= FWUPD_DEVICE_FLAG_EMULATED; i *= 2) { const gchar *tmp = fwupd_device_flag_to_string(i); if (tmp == NULL) g_warning("missing device flag 0x%x", (guint)i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_device_flag_from_string(tmp), ==, i); } for (guint64 i = 1; i <= FWUPD_DEVICE_PROBLEM_IN_USE; i *= 2) { const gchar *tmp = fwupd_device_problem_to_string(i); if (tmp == NULL) g_warning("missing device problem 0x%x", (guint)i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_device_problem_from_string(tmp), ==, i); } for (guint64 i = 1; i <= FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY; i *= 2) { const gchar *tmp = fwupd_plugin_flag_to_string(i); if (tmp == NULL) g_warning("missing plugin flag 0x%x", (guint)i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_plugin_flag_from_string(tmp), ==, i); } for (guint64 i = 1; i <= FWUPD_FEATURE_FLAG_SHOW_PROBLEMS; i *= 2) { const gchar *tmp = fwupd_feature_flag_to_string(i); if (tmp == NULL) g_warning("missing feature flag 0x%x", (guint)i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_feature_flag_from_string(tmp), ==, i); } for (guint64 i = 1; i <= FWUPD_FEATURE_FLAG_ALLOW_AUTHENTICATION; i *= 2) { const gchar *tmp = fwupd_feature_flag_to_string(i); if (tmp == NULL) g_warning("missing feature flag 0x%x", (guint)i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_feature_flag_from_string(tmp), ==, i); } for (guint64 i = 1; i <= FWUPD_RELEASE_FLAG_TRUSTED_REPORT; i *= 2) { const gchar *tmp = fwupd_release_flag_to_string(i); if (tmp == NULL) g_warning("missing release flag 0x%x", (guint)i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_release_flag_from_string(tmp), ==, i); } for (guint64 i = 1; i <= FWUPD_REQUEST_FLAG_ALLOW_GENERIC_IMAGE; i *= 2) { const gchar *tmp = fwupd_request_flag_to_string(i); if (tmp == NULL) g_warning("missing request flag 0x%x", (guint)i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_request_flag_from_string(tmp), ==, i); } for (guint i = 1; i < FWUPD_KEYRING_KIND_LAST; i++) { const gchar *tmp = fwupd_keyring_kind_to_string(i); if (tmp == NULL) g_warning("missing keyring kind 0x%x", (guint)i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_keyring_kind_from_string(tmp), ==, i); } } static void fwupd_remote_download_func(void) { gboolean ret; g_autofree gchar *fn = NULL; g_autofree gchar *directory = NULL; g_autofree gchar *expected_metadata = NULL; g_autofree gchar *expected_signature = NULL; g_autoptr(FwupdRemote) remote = NULL; g_autoptr(GError) error = NULL; remote = fwupd_remote_new(); directory = g_build_filename(FWUPD_LOCALSTATEDIR, "lib", "fwupd", "remotes.d", NULL); expected_metadata = g_build_filename(FWUPD_LOCALSTATEDIR, "lib", "fwupd", "remotes.d", "lvfs-testing", "metadata.xml.gz", NULL); expected_signature = g_strdup_printf("%s.jcat", expected_metadata); fwupd_remote_set_remotes_dir(remote, directory); fn = g_test_build_filename(G_TEST_DIST, "tests", "remotes.d", "lvfs-testing.conf", NULL); ret = fwupd_remote_load_from_filename(remote, fn, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fwupd_remote_setup(remote, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fwupd_remote_get_kind(remote), ==, FWUPD_REMOTE_KIND_DOWNLOAD); g_assert_cmpint(fwupd_remote_get_keyring_kind(remote), ==, FWUPD_KEYRING_KIND_JCAT); g_assert_cmpint(fwupd_remote_get_priority(remote), ==, 0); g_assert_false(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)); g_assert_nonnull(fwupd_remote_get_metadata_uri(remote)); g_assert_nonnull(fwupd_remote_get_metadata_uri_sig(remote)); g_assert_cmpstr(fwupd_remote_get_title(remote), ==, "Linux Vendor Firmware Service (testing)"); g_assert_cmpstr(fwupd_remote_get_report_uri(remote), ==, "https://fwupd.org/lvfs/firmware/report"); g_assert_cmpstr(fwupd_remote_get_filename_cache(remote), ==, expected_metadata); g_assert_cmpstr(fwupd_remote_get_filename_cache_sig(remote), ==, expected_signature); } /* verify we used the FirmwareBaseURI just for firmware */ static void fwupd_remote_baseuri_func(void) { gboolean ret; g_autofree gchar *firmware_uri = NULL; g_autofree gchar *fn = NULL; g_autoptr(FwupdRemote) remote = NULL; g_autofree gchar *directory = NULL; g_autoptr(GError) error = NULL; remote = fwupd_remote_new(); directory = g_build_filename(FWUPD_LOCALSTATEDIR, "lib", "fwupd", "remotes.d", NULL); fwupd_remote_set_remotes_dir(remote, directory); fn = g_test_build_filename(G_TEST_DIST, "tests", "firmware-base-uri.conf", NULL); ret = fwupd_remote_load_from_filename(remote, fn, NULL, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fwupd_remote_get_kind(remote), ==, FWUPD_REMOTE_KIND_DOWNLOAD); g_assert_cmpint(fwupd_remote_get_keyring_kind(remote), ==, FWUPD_KEYRING_KIND_JCAT); g_assert_cmpint(fwupd_remote_get_priority(remote), ==, 0); g_assert_true(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)); g_assert_cmpstr(fwupd_remote_get_firmware_base_uri(remote), ==, "https://my.fancy.cdn/"); g_assert_cmpstr(fwupd_remote_get_agreement(remote), ==, NULL); g_assert_cmpstr(fwupd_remote_get_checksum(remote), ==, NULL); g_assert_cmpstr(fwupd_remote_get_metadata_uri(remote), ==, "https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz"); g_assert_cmpstr(fwupd_remote_get_metadata_uri_sig(remote), ==, "https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz.jcat"); firmware_uri = fwupd_remote_build_firmware_uri(remote, "http://bbc.co.uk/firmware.cab", &error); g_assert_no_error(error); g_assert_cmpstr(firmware_uri, ==, "https://my.fancy.cdn/firmware.cab"); } static gchar * fwupd_remote_to_json_string(FwupdRemote *remote, GError **error) { g_autofree gchar *data = NULL; g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonBuilder) builder = json_builder_new(); g_autoptr(JsonNode) json_root = NULL; json_builder_begin_object(builder); fwupd_remote_to_json(remote, builder); json_builder_end_object(builder); json_root = json_builder_get_root(builder); json_generator = json_generator_new(); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); data = json_generator_to_data(json_generator, NULL); if (data == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to convert remote to json."); return NULL; } return g_steal_pointer(&data); } static void fwupd_remote_auth_func(void) { gboolean ret; gchar **order; g_autofree gchar *fn = NULL; g_autofree gchar *remotes_dir = NULL; g_autofree gchar *json = NULL; g_autoptr(FwupdRemote) remote = fwupd_remote_new(); g_autoptr(FwupdRemote) remote2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GVariant) data = NULL; remotes_dir = g_test_build_filename(G_TEST_BUILT, "tests", NULL); fwupd_remote_set_remotes_dir(remote, remotes_dir); fn = g_test_build_filename(G_TEST_DIST, "tests", "auth.conf", NULL); ret = fwupd_remote_load_from_filename(remote, fn, NULL, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpstr(fwupd_remote_get_username(remote), ==, "user"); g_assert_cmpstr(fwupd_remote_get_password(remote), ==, "pass"); g_assert_cmpstr(fwupd_remote_get_security_report_uri(remote), ==, "https://fwupd.org/lvfs/hsireports/upload"); g_assert_false(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED)); g_assert_false(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS)); g_assert_true(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS)); g_assert_true( g_str_has_suffix(fwupd_remote_get_filename_source(remote), "tests/auth.conf")); g_assert_true(g_str_has_suffix(fwupd_remote_get_remotes_dir(remote), "/libfwupd/tests")); g_assert_cmpint(fwupd_remote_get_age(remote), >, 1000000); ret = fwupd_remote_setup(remote, &error); g_assert_no_error(error); g_assert_true(ret); order = fwupd_remote_get_order_before(remote); g_assert_nonnull(order); g_assert_cmpint(g_strv_length(order), ==, 1); g_assert_cmpstr(order[0], ==, "before"); order = fwupd_remote_get_order_after(remote); g_assert_nonnull(order); g_assert_cmpint(g_strv_length(order), ==, 1); g_assert_cmpstr(order[0], ==, "after"); /* to/from GVariant */ fwupd_remote_set_priority(remote, 999); data = fwupd_remote_to_variant(remote); remote2 = fwupd_remote_from_variant(data); g_assert_cmpstr(fwupd_remote_get_username(remote2), ==, "user"); g_assert_cmpint(fwupd_remote_get_priority(remote2), ==, 999); /* jcat-tool is not a hard dep, and the tests create an empty file if unfound */ ret = fwupd_remote_load_signature(remote, fwupd_remote_get_filename_cache_sig(remote), &error); if (!ret) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT)) { g_test_skip("no jcat-tool, so skipping test"); return; } } g_assert_no_error(error); g_assert_true(ret); /* to JSON */ fwupd_remote_set_filename_source(remote2, NULL); fwupd_remote_set_checksum( remote2, "dd1b4fd2a59bb0e4d9ea760c658ac3cf9336c7b6729357bab443485b5cf071b2"); fwupd_remote_set_filename_cache(remote2, "./libfwupd/tests/auth/metadata.xml.gz"); json = fwupd_remote_to_json_string(remote2, &error); g_assert_no_error(error); g_assert_nonnull(json); ret = fu_test_compare_lines( json, "{\n" " \"Id\" : \"auth\",\n" " \"Kind\" : \"download\",\n" " \"KeyringKind\" : \"jcat\",\n" " \"FirmwareBaseUri\" : \"https://my.fancy.cdn/\",\n" " \"ReportUri\" : \"https://fwupd.org/lvfs/firmware/report\",\n" " \"SecurityReportUri\" : \"https://fwupd.org/lvfs/hsireports/upload\",\n" " \"MetadataUri\" : \"https://cdn.fwupd.org/downloads/firmware.xml.gz\",\n" " \"MetadataUriSig\" : \"https://cdn.fwupd.org/downloads/firmware.xml.gz.jcat\",\n" " \"Username\" : \"user\",\n" " \"Password\" : \"pass\",\n" " \"ChecksumSig\" : " "\"dd1b4fd2a59bb0e4d9ea760c658ac3cf9336c7b6729357bab443485b5cf071b2\",\n" " \"FilenameCache\" : \"./libfwupd/tests/auth/metadata.xml.gz\",\n" " \"FilenameCacheSig\" : \"./libfwupd/tests/auth/metadata.xml.gz.jcat\",\n" " \"Flags\" : 9,\n" " \"Enabled\" : \"true\",\n" " \"ApprovalRequired\" : \"false\",\n" " \"AutomaticReports\" : \"false\",\n" " \"AutomaticSecurityReports\" : \"true\",\n" " \"Priority\" : 999,\n" " \"Mtime\" : 0,\n" " \"RefreshInterval\" : 86400\n" "}", &error); g_assert_no_error(error); g_assert_true(ret); } static void fwupd_remote_duplicate_func(void) { gboolean ret; g_autofree gchar *fn2 = NULL; g_autofree gchar *fn = NULL; g_autoptr(FwupdRemote) remote = fwupd_remote_new(); g_autoptr(GError) error = NULL; fn = g_test_build_filename(G_TEST_DIST, "tests", "stable.conf", NULL); ret = fwupd_remote_load_from_filename(remote, fn, NULL, &error); g_assert_no_error(error); g_assert_true(ret); fn2 = g_test_build_filename(G_TEST_DIST, "tests", "disabled.conf", NULL); ret = fwupd_remote_load_from_filename(remote, fn2, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fwupd_remote_setup(remote, &error); g_assert_no_error(error); g_assert_true(ret); ret = fwupd_remote_setup(remote, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_false(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)); g_assert_cmpint(fwupd_remote_get_keyring_kind(remote), ==, FWUPD_KEYRING_KIND_NONE); g_assert_cmpstr(fwupd_remote_get_username(remote), ==, NULL); g_assert_cmpstr(fwupd_remote_get_password(remote), ==, ""); g_assert_cmpstr(fwupd_remote_get_filename_cache(remote), ==, "/tmp/fwupd-self-test/stable.xml"); } /* verify we used the metadata path for firmware */ static void fwupd_remote_nopath_func(void) { gboolean ret; g_autofree gchar *firmware_uri = NULL; g_autofree gchar *fn = NULL; g_autoptr(FwupdRemote) remote = NULL; g_autoptr(GError) error = NULL; g_autofree gchar *directory = NULL; remote = fwupd_remote_new(); directory = g_build_filename(FWUPD_LOCALSTATEDIR, "lib", "fwupd", "remotes.d", NULL); fwupd_remote_set_remotes_dir(remote, directory); fn = g_test_build_filename(G_TEST_DIST, "tests", "firmware-nopath.conf", NULL); ret = fwupd_remote_load_from_filename(remote, fn, NULL, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fwupd_remote_get_kind(remote), ==, FWUPD_REMOTE_KIND_DOWNLOAD); g_assert_cmpint(fwupd_remote_get_keyring_kind(remote), ==, FWUPD_KEYRING_KIND_JCAT); g_assert_cmpint(fwupd_remote_get_priority(remote), ==, 0); g_assert_true(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)); g_assert_cmpstr(fwupd_remote_get_checksum(remote), ==, NULL); g_assert_cmpstr(fwupd_remote_get_metadata_uri(remote), ==, "https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz"); g_assert_cmpstr(fwupd_remote_get_metadata_uri_sig(remote), ==, "https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz.jcat"); firmware_uri = fwupd_remote_build_firmware_uri(remote, "firmware.cab", &error); g_assert_no_error(error); g_assert_cmpstr(firmware_uri, ==, "https://s3.amazonaws.com/lvfsbucket/downloads/firmware.cab"); } static void fwupd_remote_local_func(void) { gboolean ret; g_autofree gchar *fn = NULL; g_autofree gchar *json = NULL; g_autoptr(FwupdRemote) remote = NULL; g_autoptr(FwupdRemote) remote2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GVariant) data = NULL; remote = fwupd_remote_new(); fn = g_test_build_filename(G_TEST_DIST, "tests", "dell-esrt.conf", NULL); ret = fwupd_remote_load_from_filename(remote, fn, NULL, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fwupd_remote_get_kind(remote), ==, FWUPD_REMOTE_KIND_LOCAL); g_assert_cmpint(fwupd_remote_get_keyring_kind(remote), ==, FWUPD_KEYRING_KIND_NONE); g_assert_true(fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)); g_assert_null(fwupd_remote_get_metadata_uri(remote)); g_assert_null(fwupd_remote_get_metadata_uri_sig(remote)); g_assert_null(fwupd_remote_get_report_uri(remote)); g_assert_cmpstr(fwupd_remote_get_title(remote), ==, "Enable UEFI capsule updates on Dell systems"); g_assert_cmpstr(fwupd_remote_get_filename_cache(remote), ==, "@datadir@/fwupd/remotes.d/dell-esrt/metadata.xml"); g_assert_cmpstr(fwupd_remote_get_filename_cache_sig(remote), ==, NULL); g_assert_cmpstr(fwupd_remote_get_checksum(remote), ==, NULL); /* to/from GVariant */ data = fwupd_remote_to_variant(remote); remote2 = fwupd_remote_from_variant(data); g_assert_null(fwupd_remote_get_metadata_uri(remote)); /* to JSON */ fwupd_remote_set_filename_source(remote2, NULL); json = fwupd_remote_to_json_string(remote2, &error); g_assert_no_error(error); g_assert_nonnull(json); ret = fu_test_compare_lines( json, "{\n" " \"Id\" : \"dell-esrt\",\n" " \"Kind\" : \"local\",\n" " \"KeyringKind\" : \"none\",\n" " \"Title\" : \"Enable UEFI capsule updates on Dell systems\",\n" " \"FilenameCache\" : \"@datadir@/fwupd/remotes.d/dell-esrt/metadata.xml\",\n" " \"Flags\" : 1,\n" " \"Enabled\" : \"true\",\n" " \"ApprovalRequired\" : \"false\",\n" " \"AutomaticReports\" : \"false\",\n" " \"AutomaticSecurityReports\" : \"false\",\n" " \"Priority\" : 0,\n" " \"Mtime\" : 0,\n" " \"RefreshInterval\" : 0\n" "}", &error); g_assert_no_error(error); g_assert_true(ret); } static gchar * fwupd_release_to_json_string(FwupdRelease *release, GError **error) { g_autofree gchar *data = NULL; g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonBuilder) builder = json_builder_new(); g_autoptr(JsonNode) json_root = NULL; json_builder_begin_object(builder); fwupd_release_to_json(release, builder); json_builder_end_object(builder); json_root = json_builder_get_root(builder); json_generator = json_generator_new(); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); data = json_generator_to_data(json_generator, NULL); if (data == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to convert release to json."); return NULL; } return g_steal_pointer(&data); } static void fwupd_release_func(void) { gboolean ret; g_autofree gchar *json1 = NULL; g_autofree gchar *json2 = NULL; g_autofree gchar *str = NULL; g_autoptr(FwupdRelease) release1 = NULL; g_autoptr(FwupdRelease) release2 = NULL; g_autoptr(FwupdRelease) release3 = fwupd_release_new(); g_autoptr(GError) error = NULL; g_autoptr(GVariant) data = NULL; release1 = fwupd_release_new(); fwupd_release_add_metadata_item(release1, "foo", "bar"); fwupd_release_add_metadata_item(release1, "baz", "bam"); fwupd_release_set_remote_id(release1, "remote-id"); fwupd_release_set_appstream_id(release1, "appstream-id"); fwupd_release_set_id(release1, "id"); fwupd_release_set_detach_caption(release1, "detach_caption"); fwupd_release_set_detach_image(release1, "detach_image"); fwupd_release_set_update_message(release1, "update_message"); fwupd_release_set_update_image(release1, "update_image"); fwupd_release_set_filename(release1, "filename"); fwupd_release_set_protocol(release1, "protocol"); fwupd_release_set_license(release1, "license"); fwupd_release_set_name(release1, "name"); fwupd_release_set_name_variant_suffix(release1, "name_variant_suffix"); fwupd_release_set_summary(release1, "summary"); fwupd_release_set_branch(release1, "branch"); fwupd_release_set_description(release1, "description"); fwupd_release_set_homepage(release1, "homepage"); fwupd_release_set_details_url(release1, "details_url"); fwupd_release_set_source_url(release1, "source_url"); fwupd_release_set_version(release1, "version"); fwupd_release_set_vendor(release1, "vendor"); fwupd_release_set_size(release1, 1234); fwupd_release_set_created(release1, 5678); fwupd_release_set_install_duration(release1, 2468); fwupd_release_add_category(release1, "category"); fwupd_release_add_category(release1, "category"); fwupd_release_add_issue(release1, "issue"); fwupd_release_add_issue(release1, "issue"); fwupd_release_add_location(release1, "location"); fwupd_release_add_location(release1, "location"); fwupd_release_add_tag(release1, "tag"); fwupd_release_add_tag(release1, "tag"); fwupd_release_add_checksum(release1, "checksum"); fwupd_release_add_checksum(release1, "checksum"); fwupd_release_add_flag(release1, FWUPD_RELEASE_FLAG_IS_UPGRADE); fwupd_release_add_flag(release1, FWUPD_RELEASE_FLAG_IS_UPGRADE); fwupd_release_add_flag(release1, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL); fwupd_release_remove_flag(release1, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL); fwupd_release_set_urgency(release1, FWUPD_RELEASE_URGENCY_MEDIUM); data = fwupd_release_to_variant(release1); release2 = fwupd_release_from_variant(data); g_assert_cmpstr(fwupd_release_get_metadata_item(release2, "foo"), ==, "bar"); g_assert_cmpstr(fwupd_release_get_metadata_item(release2, "baz"), ==, "bam"); g_assert_cmpstr(fwupd_release_get_remote_id(release2), ==, "remote-id"); g_assert_cmpstr(fwupd_release_get_appstream_id(release2), ==, "appstream-id"); g_assert_cmpstr(fwupd_release_get_id(release2), ==, "id"); g_assert_cmpstr(fwupd_release_get_detach_caption(release2), ==, "detach_caption"); g_assert_cmpstr(fwupd_release_get_detach_image(release2), ==, "detach_image"); g_assert_cmpstr(fwupd_release_get_update_message(release2), ==, "update_message"); g_assert_cmpstr(fwupd_release_get_update_image(release2), ==, "update_image"); g_assert_cmpstr(fwupd_release_get_filename(release2), ==, "filename"); g_assert_cmpstr(fwupd_release_get_protocol(release2), ==, "protocol"); g_assert_cmpstr(fwupd_release_get_license(release2), ==, "license"); g_assert_cmpstr(fwupd_release_get_name(release2), ==, "name"); g_assert_cmpstr(fwupd_release_get_name_variant_suffix(release2), ==, "name_variant_suffix"); g_assert_cmpstr(fwupd_release_get_summary(release2), ==, "summary"); g_assert_cmpstr(fwupd_release_get_branch(release2), ==, "branch"); g_assert_cmpstr(fwupd_release_get_description(release2), ==, "description"); g_assert_cmpstr(fwupd_release_get_homepage(release2), ==, "homepage"); g_assert_cmpstr(fwupd_release_get_details_url(release2), ==, "details_url"); g_assert_cmpstr(fwupd_release_get_source_url(release2), ==, "source_url"); g_assert_cmpstr(fwupd_release_get_version(release2), ==, "version"); g_assert_cmpstr(fwupd_release_get_vendor(release2), ==, "vendor"); g_assert_cmpint(fwupd_release_get_size(release2), ==, 1234); g_assert_cmpint(fwupd_release_get_created(release2), ==, 5678); g_assert_true(fwupd_release_has_category(release2, "category")); g_assert_true(fwupd_release_has_tag(release2, "tag")); g_assert_true(fwupd_release_has_checksum(release2, "checksum")); g_assert_true(fwupd_release_has_flag(release2, FWUPD_RELEASE_FLAG_IS_UPGRADE)); g_assert_false(fwupd_release_has_flag(release2, FWUPD_RELEASE_FLAG_IS_COMMUNITY)); g_assert_cmpint(fwupd_release_get_issues(release2)->len, ==, 1); g_assert_cmpint(fwupd_release_get_locations(release2)->len, ==, 1); g_assert_cmpint(fwupd_release_get_categories(release2)->len, ==, 1); g_assert_cmpint(fwupd_release_get_tags(release2)->len, ==, 1); g_assert_cmpint(fwupd_release_get_checksums(release2)->len, ==, 1); g_assert_cmpint(fwupd_release_get_urgency(release2), ==, FWUPD_RELEASE_URGENCY_MEDIUM); g_assert_cmpint(fwupd_release_get_install_duration(release2), ==, 2468); /* to JSON */ json1 = fwupd_release_to_json_string(release1, &error); g_assert_no_error(error); g_assert_nonnull(json1); json2 = fwupd_release_to_json_string(release2, &error); g_assert_no_error(error); g_assert_nonnull(json2); ret = fu_test_compare_lines(json1, json2, &error); g_assert_no_error(error); g_assert_true(ret); /* to string */ str = fwupd_release_to_string(release2); ret = fu_test_compare_lines(str, " AppstreamId: appstream-id\n" " ReleaseId: id\n" " RemoteId: remote-id\n" " Name: name\n" " NameVariantSuffix: name_variant_suffix\n" " Summary: summary\n" " Description: description\n" " Branch: branch\n" " Version: version\n" " Filename: filename\n" " Protocol: protocol\n" " Categories: category\n" " Issues: issue\n" " Checksum: SHA1(checksum)\n" " Tags: tag\n" " License: license\n" " Size: 1.2 kB\n" " Created: 1970-01-01\n" " Uri: location\n" " Homepage: homepage\n" " DetailsUrl: details_url\n" " SourceUrl: source_url\n" " Urgency: medium\n" " Vendor: vendor\n" " Flags: is-upgrade\n" " InstallDuration: 2468\n" " DetachCaption: detach_caption\n" " DetachImage: detach_image\n" " UpdateMessage: update_message\n" " UpdateImage: update_image\n" " foo: bar\n" " baz: bam\n", &error); g_assert_no_error(error); g_assert_true(ret); /* copy properties */ fwupd_release_incorporate(release3, release2); g_assert_cmpstr(fwupd_release_get_metadata_item(release3, "foo"), ==, "bar"); g_assert_cmpstr(fwupd_release_get_remote_id(release3), ==, "remote-id"); g_assert_cmpstr(fwupd_release_get_appstream_id(release3), ==, "appstream-id"); g_assert_cmpstr(fwupd_release_get_id(release3), ==, "id"); } static gchar * fwupd_report_to_json_string(FwupdReport *report, GError **error) { g_autofree gchar *data = NULL; g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonBuilder) builder = json_builder_new(); g_autoptr(JsonNode) json_root = NULL; json_builder_begin_object(builder); fwupd_report_to_json(report, builder); json_builder_end_object(builder); json_root = json_builder_get_root(builder); json_generator = json_generator_new(); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); data = json_generator_to_data(json_generator, NULL); if (data == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to convert report to json."); return NULL; } return g_steal_pointer(&data); } static void fwupd_report_func(void) { gboolean ret; g_autofree gchar *json1 = NULL; g_autofree gchar *json2 = NULL; g_autofree gchar *str = NULL; g_autoptr(FwupdReport) report1 = NULL; g_autoptr(FwupdReport) report2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GVariant) data = NULL; report1 = fwupd_report_new(); fwupd_report_add_metadata_item(report1, "foo", "bar"); fwupd_report_add_metadata_item(report1, "baz", "bam"); fwupd_report_set_version_old(report1, "1.2.3"); fwupd_report_set_created(report1, 5678); fwupd_report_set_vendor(report1, "acme"); fwupd_report_set_vendor_id(report1, 2468); fwupd_report_set_device_name(report1, "name"); fwupd_report_set_distro_id(report1, "distro_id"); fwupd_report_set_distro_version(report1, "distro_version"); fwupd_report_set_remote_id(report1, "lvfs"); data = fwupd_report_to_variant(report1); report2 = fwupd_report_from_variant(data); g_assert_cmpstr(fwupd_report_get_metadata_item(report2, "foo"), ==, "bar"); g_assert_cmpstr(fwupd_report_get_metadata_item(report2, "baz"), ==, "bam"); g_assert_cmpstr(fwupd_report_get_version_old(report2), ==, "1.2.3"); g_assert_cmpstr(fwupd_report_get_vendor(report2), ==, "acme"); g_assert_cmpint(fwupd_report_get_vendor_id(report2), ==, 2468); g_assert_cmpstr(fwupd_report_get_device_name(report2), ==, "name"); g_assert_cmpstr(fwupd_report_get_distro_id(report2), ==, "distro_id"); g_assert_cmpstr(fwupd_report_get_distro_version(report2), ==, "distro_version"); g_assert_cmpint(fwupd_report_get_created(report2), ==, 5678); /* to JSON */ json1 = fwupd_report_to_json_string(report1, &error); g_assert_no_error(error); g_assert_nonnull(json1); json2 = fwupd_report_to_json_string(report2, &error); g_assert_no_error(error); g_assert_nonnull(json2); ret = fu_test_compare_lines(json1, json2, &error); g_assert_no_error(error); g_assert_true(ret); /* to string */ str = fwupd_report_to_string(report2); ret = fu_test_compare_lines(str, " DeviceName: name\n" " DistroId: distro_id\n" " DistroVersion: distro_version\n" " VersionOld: 1.2.3\n" " Vendor: acme\n" " VendorId: 2468\n" " RemoteId: lvfs\n" " Flags: none\n" " foo: bar\n" " baz: bam\n", &error); g_assert_no_error(error); g_assert_true(ret); } static void fwupd_plugin_func(void) { gboolean ret; g_autofree gchar *str = NULL; g_autoptr(FwupdPlugin) plugin1 = NULL; g_autoptr(FwupdPlugin) plugin2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GVariant) data = NULL; plugin1 = fwupd_plugin_new(); fwupd_plugin_set_name(plugin1, "foo"); fwupd_plugin_set_flags(plugin1, FWUPD_PLUGIN_FLAG_USER_WARNING); fwupd_plugin_add_flag(plugin1, FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE); fwupd_plugin_add_flag(plugin1, FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE); fwupd_plugin_add_flag(plugin1, FWUPD_PLUGIN_FLAG_NO_HARDWARE); fwupd_plugin_remove_flag(plugin1, FWUPD_PLUGIN_FLAG_NO_HARDWARE); fwupd_plugin_remove_flag(plugin1, FWUPD_PLUGIN_FLAG_NO_HARDWARE); data = fwupd_plugin_to_variant(plugin1); plugin2 = fwupd_plugin_from_variant(data); g_assert_cmpstr(fwupd_plugin_get_name(plugin2), ==, "foo"); g_assert_cmpint(fwupd_plugin_get_flags(plugin2), ==, FWUPD_PLUGIN_FLAG_USER_WARNING | FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE); g_assert_true(fwupd_plugin_has_flag(plugin2, FWUPD_PLUGIN_FLAG_USER_WARNING)); g_assert_true(fwupd_plugin_has_flag(plugin2, FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE)); g_assert_false(fwupd_plugin_has_flag(plugin2, FWUPD_PLUGIN_FLAG_NO_HARDWARE)); str = fwupd_plugin_to_string(plugin2); ret = fu_test_compare_lines(str, " Name: foo\n" " Flags: user-warning|clear-updatable\n", &error); g_assert_no_error(error); g_assert_true(ret); } static void fwupd_request_func(void) { g_autofree gchar *str = NULL; g_autoptr(FwupdRequest) request = fwupd_request_new(); g_autoptr(FwupdRequest) request2 = NULL; g_autoptr(GVariant) data = NULL; /* create dummy */ fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); fwupd_request_set_message(request, "foo"); fwupd_request_set_image(request, "bar"); fwupd_request_set_device_id(request, "950da62d4c753a26e64f7f7d687104ce38e32ca5"); str = fwupd_request_to_string(request); g_debug("%s", str); g_assert_true(fwupd_request_has_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE)); g_assert_cmpint(fwupd_request_get_flags(request), ==, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); fwupd_request_remove_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); g_assert_false(fwupd_request_has_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE)); g_assert_cmpint(fwupd_request_get_flags(request), ==, FWUPD_REQUEST_FLAG_NONE); /* set in init */ g_assert_cmpint(fwupd_request_get_created(request), >, 0); /* to serialized and back again */ data = fwupd_request_to_variant(request); request2 = fwupd_request_from_variant(data); g_assert_cmpint(fwupd_request_get_kind(request2), ==, FWUPD_REQUEST_KIND_IMMEDIATE); g_assert_cmpint(fwupd_request_get_created(request2), >, 0); g_assert_cmpstr(fwupd_request_get_id(request2), ==, FWUPD_REQUEST_ID_REMOVE_REPLUG); g_assert_cmpstr(fwupd_request_get_message(request2), ==, "foo"); g_assert_cmpstr(fwupd_request_get_image(request2), ==, "bar"); g_assert_cmpstr(fwupd_request_get_device_id(request2), ==, "950da62d4c753a26e64f7f7d687104ce38e32ca5"); } static void fwupd_device_filter_func(void) { g_autoptr(FwupdDevice) dev = fwupd_device_new(); fwupd_device_add_flag(dev, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fwupd_device_add_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED); /* none */ g_assert_true( fwupd_device_match_flags(dev, FWUPD_DEVICE_FLAG_NONE, FWUPD_DEVICE_FLAG_NONE)); /* include */ g_assert_true(fwupd_device_match_flags(dev, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD, FWUPD_DEVICE_FLAG_NONE)); g_assert_true( fwupd_device_match_flags(dev, FWUPD_DEVICE_FLAG_SUPPORTED, FWUPD_DEVICE_FLAG_NONE)); g_assert_true( fwupd_device_match_flags(dev, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD | FWUPD_DEVICE_FLAG_SUPPORTED, FWUPD_DEVICE_FLAG_NONE)); g_assert_false(fwupd_device_match_flags(dev, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED, FWUPD_DEVICE_FLAG_NONE)); /* exclude, i.e. ~flag */ g_assert_false(fwupd_device_match_flags(dev, FWUPD_DEVICE_FLAG_NONE, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD)); g_assert_false( fwupd_device_match_flags(dev, FWUPD_DEVICE_FLAG_NONE, FWUPD_DEVICE_FLAG_SUPPORTED)); g_assert_false(fwupd_device_match_flags(dev, FWUPD_DEVICE_FLAG_NONE, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD | FWUPD_DEVICE_FLAG_SUPPORTED)); g_assert_true(fwupd_device_match_flags(dev, FWUPD_DEVICE_FLAG_NONE, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED)); } static void fwupd_device_func(void) { gboolean ret; g_autofree gchar *data = NULL; g_autofree gchar *str = NULL; g_autoptr(FwupdDevice) dev = NULL; g_autoptr(FwupdDevice) dev2 = fwupd_device_new(); g_autoptr(FwupdDevice) dev_new = fwupd_device_new(); g_autoptr(FwupdRelease) rel = NULL; g_autoptr(GError) error = NULL; g_autoptr(GString) str_ascii = NULL; g_autoptr(JsonBuilder) builder = NULL; g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonNode) json_root = NULL; g_autoptr(JsonParser) parser = json_parser_new(); /* create dummy object */ dev = fwupd_device_new(); fwupd_device_add_checksum(dev, "beefdead"); fwupd_device_set_created(dev, 1); fwupd_device_add_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE); fwupd_device_set_id(dev, "USB:foo"); fwupd_device_set_modified(dev, 60 * 60 * 24); fwupd_device_set_name(dev, "ColorHug2"); fwupd_device_add_guid(dev, "2082b5e0-7a64-478a-b1b2-e3404fab6dad"); fwupd_device_add_guid(dev, "00000000-0000-0000-0000-000000000000"); fwupd_device_add_instance_id(dev, "USB\\VID_1234&PID_0001"); fwupd_device_add_icon(dev, "input-gaming"); fwupd_device_add_icon(dev, "input-mouse"); fwupd_device_add_vendor_id(dev, "USB:0x1234"); fwupd_device_add_vendor_id(dev, "PCI:0x5678"); fwupd_device_add_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE | FWUPD_DEVICE_FLAG_REQUIRE_AC); g_assert_true(fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_REQUIRE_AC)); g_assert_true(fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_false(fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_HISTORICAL)); rel = fwupd_release_new(); fwupd_release_add_flag(rel, FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD); fwupd_release_add_checksum(rel, "deadbeef"); fwupd_release_set_description(rel, "

    Hi there!

    "); fwupd_release_set_filename(rel, "firmware.bin"); fwupd_release_set_appstream_id(rel, "org.dave.ColorHug.firmware"); fwupd_release_set_size(rel, 1024); fwupd_release_add_location(rel, "http://foo.com"); fwupd_release_add_location(rel, "ftp://foo.com"); fwupd_release_add_tag(rel, "vendor-2021q1"); fwupd_release_add_tag(rel, "vendor-2021q2"); fwupd_release_set_version(rel, "1.2.3"); fwupd_device_add_release(dev, rel); str = fwupd_device_to_string(dev); g_print("\n%s", str); /* check GUIDs */ g_assert_true(fwupd_device_has_guid(dev, "2082b5e0-7a64-478a-b1b2-e3404fab6dad")); g_assert_true(fwupd_device_has_guid(dev, "00000000-0000-0000-0000-000000000000")); g_assert_false(fwupd_device_has_guid(dev, "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")); /* convert the new non-breaking space back into a normal space: * https://gitlab.gnome.org/GNOME/glib/commit/76af5dabb4a25956a6c41a75c0c7feeee74496da */ str_ascii = g_string_new(str); g_string_replace(str_ascii, " ", " ", 0); ret = fu_test_compare_lines(str_ascii->str, "FwupdDevice:\n" " DeviceId: USB:foo\n" " Name: ColorHug2\n" " Guid: 18f514d2-c12e-581f-a696-cc6d6c271699 " "← USB\\VID_1234&PID_0001 ⚠\n" " Guid: 2082b5e0-7a64-478a-b1b2-e3404fab6dad\n" " Guid: 00000000-0000-0000-0000-000000000000\n" " Flags: updatable|require-ac\n" " Checksum: SHA1(beefdead)\n" " VendorId: USB:0x1234\n" " VendorId: PCI:0x5678\n" " Icon: input-gaming,input-mouse\n" " Created: 1970-01-01\n" " Modified: 1970-01-02\n" " \n" " [Release]\n" " AppstreamId: org.dave.ColorHug.firmware\n" " Description:

    Hi there!

    \n" " Version: 1.2.3\n" " Filename: firmware.bin\n" " Checksum: SHA1(deadbeef)\n" " Tags: vendor-2021q1\n" " Tags: vendor-2021q2\n" " Size: 1.0 kB\n" " Uri: http://foo.com\n" " Uri: ftp://foo.com\n" " Flags: trusted-payload\n", &error); g_assert_no_error(error); g_assert_true(ret); /* export to json */ builder = json_builder_new(); json_builder_begin_object(builder); fwupd_device_to_json_full(dev, builder, FWUPD_DEVICE_FLAG_TRUSTED); json_builder_end_object(builder); json_root = json_builder_get_root(builder); json_generator = json_generator_new(); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); data = json_generator_to_data(json_generator, NULL); g_assert_nonnull(data); ret = fu_test_compare_lines(data, "{\n" " \"Name\" : \"ColorHug2\",\n" " \"DeviceId\" : \"USB:foo\",\n" " \"InstanceIds\" : [\n" " \"USB\\\\VID_1234&PID_0001\"\n" " ],\n" " \"Guid\" : [\n" " \"2082b5e0-7a64-478a-b1b2-e3404fab6dad\",\n" " \"00000000-0000-0000-0000-000000000000\"\n" " ],\n" " \"Flags\" : [\n" " \"updatable\",\n" " \"require-ac\"\n" " ],\n" " \"Checksums\" : [\n" " \"beefdead\"\n" " ],\n" " \"VendorId\" : \"USB:0x1234|PCI:0x5678\",\n" " \"VendorIds\" : [\n" " \"USB:0x1234\",\n" " \"PCI:0x5678\"\n" " ],\n" " \"Icons\" : [\n" " \"input-gaming\",\n" " \"input-mouse\"\n" " ],\n" " \"Created\" : 1,\n" " \"Modified\" : 86400,\n" " \"Releases\" : [\n" " {\n" " \"AppstreamId\" : \"org.dave.ColorHug.firmware\",\n" " \"Description\" : \"

    Hi there!

    \",\n" " \"Version\" : \"1.2.3\",\n" " \"Filename\" : \"firmware.bin\",\n" " \"Checksum\" : [\n" " \"deadbeef\"\n" " ],\n" " \"Tags\" : [\n" " \"vendor-2021q1\",\n" " \"vendor-2021q2\"\n" " ],\n" " \"Size\" : 1024,\n" " \"Locations\" : [\n" " \"http://foo.com\",\n" " \"ftp://foo.com\"\n" " ],\n" " \"Uri\" : \"http://foo.com\",\n" " \"Flags\" : [\n" " \"trusted-payload\"\n" " ]\n" " }\n" " ]\n" "}", &error); g_assert_no_error(error); g_assert_true(ret); /* incorporate */ fwupd_device_incorporate(dev_new, dev); g_assert_true(fwupd_device_has_vendor_id(dev_new, "USB:0x1234")); g_assert_true(fwupd_device_has_vendor_id(dev_new, "PCI:0x5678")); g_assert_true(fwupd_device_has_instance_id(dev_new, "USB\\VID_1234&PID_0001")); /* from JSON */ ret = json_parser_load_from_data(parser, data, -1, &error); g_assert_no_error(error); g_assert_true(ret); ret = fwupd_device_from_json(dev2, json_parser_get_root(parser), &error); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip(error->message); return; } g_assert_no_error(error); g_assert_true(ret); g_assert_true(fwupd_device_has_vendor_id(dev2, "USB:0x1234")); g_assert_true(fwupd_device_has_instance_id(dev2, "USB\\VID_1234&PID_0001")); g_assert_true(fwupd_device_has_flag(dev2, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_false(fwupd_device_has_flag(dev2, FWUPD_DEVICE_FLAG_LOCKED)); } static void fwupd_client_devices_func(void) { FwupdDevice *dev; gboolean ret; g_autoptr(FwupdClient) client = NULL; g_autoptr(GPtrArray) array = NULL; g_autoptr(GError) error = NULL; client = fwupd_client_new(); /* only run if running fwupd is new enough */ ret = fwupd_client_connect(client, NULL, &error); if (ret == FALSE && (g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_TIMED_OUT) || g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER) || g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))) { g_debug("%s", error->message); g_test_skip("timeout connecting to daemon"); return; } g_assert_no_error(error); g_assert_true(ret); if (fwupd_client_get_daemon_version(client) == NULL) { g_test_skip("no enabled fwupd daemon"); return; } if (!g_str_has_prefix(fwupd_client_get_daemon_version(client), "1.")) { g_test_skip("running fwupd is too old"); return; } array = fwupd_client_get_devices(client, NULL, &error); if (array == NULL && g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_test_skip("no available fwupd devices"); return; } if (array == NULL && g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip("no available fwupd daemon"); return; } g_assert_no_error(error); g_assert_nonnull(array); g_assert_cmpint(array->len, >, 0); /* check device */ dev = g_ptr_array_index(array, 0); g_assert_true(FWUPD_IS_DEVICE(dev)); g_assert_cmpstr(fwupd_device_get_guid_default(dev), !=, NULL); g_assert_cmpstr(fwupd_device_get_id(dev), !=, NULL); } static void fwupd_client_remotes_func(void) { gboolean ret; g_autofree gchar *remotesdir = NULL; g_autoptr(FwupdClient) client = NULL; g_autoptr(FwupdRemote) remote2 = NULL; g_autoptr(FwupdRemote) remote3 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) array = NULL; remotesdir = g_test_build_filename(G_TEST_DIST, "tests", "remotes.d", NULL); (void)g_setenv("FU_SELF_TEST_REMOTES_DIR", remotesdir, TRUE); client = fwupd_client_new(); /* only run if running fwupd is new enough */ ret = fwupd_client_connect(client, NULL, &error); if (ret == FALSE && (g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_TIMED_OUT) || g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER) || g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))) { g_debug("%s", error->message); g_test_skip("timeout connecting to daemon"); return; } g_assert_no_error(error); g_assert_true(ret); if (fwupd_client_get_daemon_version(client) == NULL) { g_test_skip("no enabled fwupd daemon"); return; } if (!g_str_has_prefix(fwupd_client_get_daemon_version(client), "1.")) { g_test_skip("running fwupd is too old"); return; } array = fwupd_client_get_remotes(client, NULL, &error); if (array == NULL && g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_test_skip("no available fwupd remotes"); return; } if (array == NULL && g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip("no available fwupd daemon"); return; } g_assert_no_error(error); g_assert_nonnull(array); g_assert_cmpint(array->len, >, 0); /* check we can find the right thing */ remote2 = fwupd_client_get_remote_by_id(client, "lvfs", NULL, &error); g_assert_no_error(error); g_assert_nonnull(remote2); g_assert_cmpstr(fwupd_remote_get_id(remote2), ==, "lvfs"); g_assert_nonnull(fwupd_remote_get_metadata_uri(remote2)); /* check we set an error when unfound */ remote3 = fwupd_client_get_remote_by_id(client, "XXXX", NULL, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_null(remote3); } static gboolean fwupd_has_system_bus(void) { g_autoptr(GDBusConnection) conn = NULL; conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL); if (conn != NULL) return TRUE; g_debug("D-Bus system bus unavailable, skipping tests."); return FALSE; } static void fwupd_common_machine_hash_func(void) { gsize sz = 0; g_autofree gchar *buf = NULL; g_autofree gchar *mhash1 = NULL; g_autofree gchar *mhash2 = NULL; g_autoptr(GError) error = NULL; if (!g_file_test("/etc/machine-id", G_FILE_TEST_EXISTS)) { g_test_skip("Missing /etc/machine-id"); return; } if (!g_file_get_contents("/etc/machine-id", &buf, &sz, &error)) { g_test_skip("/etc/machine-id is unreadable"); return; } if (sz == 0) { g_test_skip("Empty /etc/machine-id"); return; } mhash1 = fwupd_build_machine_id("salt1", &error); g_assert_no_error(error); g_assert_cmpstr(mhash1, !=, NULL); mhash2 = fwupd_build_machine_id("salt2", &error); g_assert_no_error(error); g_assert_cmpstr(mhash2, !=, NULL); g_assert_cmpstr(mhash2, !=, mhash1); } static void fwupd_common_device_id_func(void) { g_assert_false(fwupd_device_id_is_valid(NULL)); g_assert_false(fwupd_device_id_is_valid("")); g_assert_false(fwupd_device_id_is_valid("1ff60ab2-3905-06a1-b476-0371f00c9e9b")); g_assert_false(fwupd_device_id_is_valid("aaaaaad3fae86d95e5d56626129d00e332c4b8dac95442")); g_assert_false(fwupd_device_id_is_valid("x3fae86d95e5d56626129d00e332c4b8dac95442")); g_assert_false(fwupd_device_id_is_valid("D3FAE86D95E5D56626129D00E332C4B8DAC95442")); g_assert_false(fwupd_device_id_is_valid(FWUPD_DEVICE_ID_ANY)); g_assert_true(fwupd_device_id_is_valid("d3fae86d95e5d56626129d00e332c4b8dac95442")); } static void fwupd_common_guid_func(void) { const guint8 msbuf[] = "hello world!"; g_autofree gchar *guid1 = NULL; g_autofree gchar *guid2 = NULL; g_autofree gchar *guid3 = NULL; g_autofree gchar *guid_be = NULL; g_autofree gchar *guid_me = NULL; fwupd_guid_t buf = {0x0}; gboolean ret; g_autoptr(GError) error = NULL; /* invalid */ g_assert_false(fwupd_guid_is_valid(NULL)); g_assert_false(fwupd_guid_is_valid("")); g_assert_false(fwupd_guid_is_valid("1ff60ab2-3905-06a1-b476")); g_assert_false(fwupd_guid_is_valid("1ff60ab2-XXXX-XXXX-XXXX-0371f00c9e9b")); g_assert_false(fwupd_guid_is_valid("1ff60ab2-XXXX-XXXX-XXXX-0371f00c9e9bf")); g_assert_false(fwupd_guid_is_valid(" 1ff60ab2-3905-06a1-b476-0371f00c9e9b")); g_assert_false(fwupd_guid_is_valid("00000000-0000-0000-0000-000000000000")); /* valid */ g_assert_true(fwupd_guid_is_valid("1ff60ab2-3905-06a1-b476-0371f00c9e9b")); /* make valid */ guid1 = fwupd_guid_hash_string("python.org"); g_assert_cmpstr(guid1, ==, "886313e1-3b8a-5372-9b90-0c9aee199e5d"); guid2 = fwupd_guid_hash_string("8086:0406"); g_assert_cmpstr(guid2, ==, "1fbd1f2c-80f4-5d7c-a6ad-35c7b9bd5486"); guid3 = fwupd_guid_hash_data(msbuf, sizeof(msbuf), FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT); g_assert_cmpstr(guid3, ==, "6836cfac-f77a-527f-b375-4f92f01449c5"); /* round-trip BE */ ret = fwupd_guid_from_string("00112233-4455-6677-8899-aabbccddeeff", &buf, FWUPD_GUID_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(memcmp(buf, "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff", sizeof(buf)), ==, 0); guid_be = fwupd_guid_to_string((const fwupd_guid_t *)&buf, FWUPD_GUID_FLAG_NONE); g_assert_cmpstr(guid_be, ==, "00112233-4455-6677-8899-aabbccddeeff"); /* round-trip mixed encoding */ ret = fwupd_guid_from_string("00112233-4455-6677-8899-aabbccddeeff", &buf, FWUPD_GUID_FLAG_MIXED_ENDIAN, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(memcmp(buf, "\x33\x22\x11\x00\x55\x44\x77\x66\x88\x99\xaa\xbb\xcc\xdd\xee\xff", sizeof(buf)), ==, 0); guid_me = fwupd_guid_to_string((const fwupd_guid_t *)&buf, FWUPD_GUID_FLAG_MIXED_ENDIAN); g_assert_cmpstr(guid_me, ==, "00112233-4455-6677-8899-aabbccddeeff"); /* check failure */ g_assert_false( fwupd_guid_from_string("001122334455-6677-8899-aabbccddeeff", NULL, 0, NULL)); g_assert_false( fwupd_guid_from_string("0112233-4455-6677-8899-aabbccddeeff", NULL, 0, NULL)); } static gchar * fwupd_attr_to_json_string(GObject *attr, GError **error) { g_autofree gchar *data = NULL; g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonBuilder) builder = json_builder_new(); g_autoptr(JsonNode) json_root = NULL; json_builder_begin_object(builder); if (FWUPD_IS_SECURITY_ATTR(attr)) fwupd_security_attr_to_json(FWUPD_SECURITY_ATTR(attr), builder); else if (FWUPD_IS_BIOS_SETTING(attr)) fwupd_bios_setting_to_json(FWUPD_BIOS_SETTING(attr), builder); json_builder_end_object(builder); json_root = json_builder_get_root(builder); json_generator = json_generator_new(); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); data = json_generator_to_data(json_generator, NULL); if (data == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to convert security attribute to json."); return NULL; } return g_steal_pointer(&data); } static void fwupd_security_attr_func(void) { gboolean ret; g_autofree gchar *str1 = NULL; g_autofree gchar *str2 = NULL; g_autofree gchar *str3 = NULL; g_autofree gchar *json = NULL; g_autoptr(FwupdSecurityAttr) attr1 = fwupd_security_attr_new("org.fwupd.hsi.bar"); g_autoptr(FwupdSecurityAttr) attr2 = fwupd_security_attr_new(NULL); g_autoptr(FwupdSecurityAttr) attr3 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GVariant) data = NULL; g_autoptr(JsonParser) parser = json_parser_new(); for (guint i = 1; i < FWUPD_SECURITY_ATTR_RESULT_LAST; i++) { const gchar *tmp = fwupd_security_attr_result_to_string(i); g_assert_cmpstr(tmp, !=, NULL); g_assert_cmpint(fwupd_security_attr_result_from_string(tmp), ==, i); } g_assert_cmpstr(fwupd_security_attr_get_appstream_id(attr1), ==, "org.fwupd.hsi.bar"); fwupd_security_attr_set_appstream_id(attr1, "org.fwupd.hsi.baz"); g_assert_cmpstr(fwupd_security_attr_get_appstream_id(attr1), ==, "org.fwupd.hsi.baz"); fwupd_security_attr_set_level(attr1, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT); g_assert_cmpint(fwupd_security_attr_get_level(attr1), ==, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT); fwupd_security_attr_set_result(attr1, FWUPD_SECURITY_ATTR_RESULT_ENABLED); g_assert_cmpint(fwupd_security_attr_get_result(attr1), ==, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fwupd_security_attr_add_flag(attr1, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_add_flag(attr1, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); fwupd_security_attr_remove_flag(attr1, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); g_assert_true(fwupd_security_attr_has_flag(attr1, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)); g_assert_false(fwupd_security_attr_has_flag(attr1, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA)); g_assert_false(fwupd_security_attr_has_flag(attr1, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)); fwupd_security_attr_set_name(attr1, "DCI"); g_assert_cmpstr(fwupd_security_attr_get_name(attr1), ==, "DCI"); fwupd_security_attr_set_plugin(attr1, "uefi-capsule"); g_assert_cmpstr(fwupd_security_attr_get_plugin(attr1), ==, "uefi-capsule"); fwupd_security_attr_set_url(attr1, "https://foo.bar"); g_assert_cmpstr(fwupd_security_attr_get_url(attr1), ==, "https://foo.bar"); fwupd_security_attr_add_guid(attr1, "af3fc12c-d090-5783-8a67-845b90d3cfec"); g_assert_true(fwupd_security_attr_has_guid(attr1, "af3fc12c-d090-5783-8a67-845b90d3cfec")); g_assert_false(fwupd_security_attr_has_guid(attr1, "NOT_GOING_TO_EXIST")); fwupd_security_attr_add_metadata(attr1, "KEY", "VALUE"); g_assert_cmpstr(fwupd_security_attr_get_metadata(attr1, "KEY"), ==, "VALUE"); /* remove this from the output */ fwupd_security_attr_set_created(attr1, 0); str1 = fwupd_security_attr_to_string(attr1); ret = fu_test_compare_lines(str1, " AppstreamId: org.fwupd.hsi.baz\n" " HsiLevel: 2\n" " HsiResult: enabled\n" " Flags: success\n" " Name: DCI\n" " Plugin: uefi-capsule\n" " Uri: https://foo.bar\n" " Guid: af3fc12c-d090-5783-8a67-845b90d3cfec\n" " KEY: VALUE\n", &error); g_assert_no_error(error); g_assert_true(ret); /* roundtrip GVariant */ data = fwupd_security_attr_to_variant(attr1); attr3 = fwupd_security_attr_from_variant(data); fwupd_security_attr_set_created(attr3, 0); str3 = fwupd_security_attr_to_string(attr3); ret = fu_test_compare_lines(str3, " AppstreamId: org.fwupd.hsi.baz\n" " HsiLevel: 2\n" " HsiResult: enabled\n" " Flags: success\n" " Name: DCI\n" " Plugin: uefi-capsule\n" " Uri: https://foo.bar\n" " Guid: af3fc12c-d090-5783-8a67-845b90d3cfec\n" " KEY: VALUE\n", &error); g_assert_no_error(error); g_assert_true(ret); /* to JSON */ json = fwupd_attr_to_json_string(G_OBJECT(attr1), &error); g_assert_no_error(error); g_assert_nonnull(json); ret = fu_test_compare_lines(json, "{\n" " \"AppstreamId\" : \"org.fwupd.hsi.baz\",\n" " \"HsiLevel\" : 2,\n" " \"HsiResult\" : \"enabled\",\n" " \"Name\" : \"DCI\",\n" " \"Plugin\" : \"uefi-capsule\",\n" " \"Uri\" : \"https://foo.bar\",\n" " \"Flags\" : [\n" " \"success\"\n" " ],\n" " \"Guid\" : [\n" " \"af3fc12c-d090-5783-8a67-845b90d3cfec\"\n" " ],\n" " \"KEY\" : \"VALUE\"\n" "}", &error); g_assert_no_error(error); g_assert_true(ret); /* from JSON */ ret = json_parser_load_from_data(parser, json, -1, &error); g_assert_no_error(error); g_assert_true(ret); ret = fwupd_security_attr_from_json(attr2, json_parser_get_root(parser), &error); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip(error->message); return; } g_assert_no_error(error); g_assert_true(ret); /* we don't load unconditionally load metadata from the JSON */ fwupd_security_attr_add_metadata(attr2, "KEY", "VALUE"); str2 = fwupd_security_attr_to_string(attr2); ret = fu_test_compare_lines(str2, str1, &error); g_assert_no_error(error); g_assert_true(ret); } static void fwupd_bios_settings_func(void) { gboolean ret; g_autofree gchar *str1 = NULL; g_autofree gchar *str2 = NULL; g_autofree gchar *str3 = NULL; g_autofree gchar *str4 = NULL; g_autofree gchar *json1 = NULL; g_autofree gchar *json2 = NULL; g_autoptr(FwupdBiosSetting) attr1 = fwupd_bios_setting_new("foo", "/path/to/bar"); g_autoptr(FwupdBiosSetting) attr2 = NULL; g_autoptr(FwupdBiosSetting) attr3 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GVariant) data1 = NULL; g_autoptr(GVariant) data2 = NULL; g_autoptr(JsonParser) parser = json_parser_new(); g_assert_cmpstr(fwupd_bios_setting_get_name(attr1), ==, "foo"); fwupd_bios_setting_set_name(attr1, "UEFISecureBoot"); g_assert_cmpstr(fwupd_bios_setting_get_name(attr1), ==, "UEFISecureBoot"); fwupd_bios_setting_set_kind(attr1, FWUPD_BIOS_SETTING_KIND_ENUMERATION); g_assert_cmpint(fwupd_bios_setting_get_kind(attr1), ==, FWUPD_BIOS_SETTING_KIND_ENUMERATION); fwupd_bios_setting_set_description(attr1, "Controls Secure boot"); g_assert_cmpstr(fwupd_bios_setting_get_description(attr1), ==, "Controls Secure boot"); fwupd_bios_setting_set_current_value(attr1, "Disabled"); g_assert_cmpstr(fwupd_bios_setting_get_current_value(attr1), ==, "Disabled"); fwupd_bios_setting_add_possible_value(attr1, "Disabled"); fwupd_bios_setting_add_possible_value(attr1, "Enabled"); g_assert_true(fwupd_bios_setting_has_possible_value(attr1, "Disabled")); g_assert_false(fwupd_bios_setting_has_possible_value(attr1, "NOT_GOING_TO_EXIST")); str1 = fwupd_bios_setting_to_string(attr1); ret = fu_test_compare_lines(str1, " Name: UEFISecureBoot\n" " Description: Controls Secure boot\n" " Filename: /path/to/bar\n" " BiosSettingType: 1\n" " BiosSettingCurrentValue: Disabled\n" " BiosSettingReadOnly: False\n" " BiosSettingPossibleValues: Disabled\n" " BiosSettingPossibleValues: Enabled\n", &error); g_assert_no_error(error); g_assert_true(ret); /* roundtrip GVariant */ data1 = fwupd_bios_setting_to_variant(attr1, TRUE); attr2 = fwupd_bios_setting_from_variant(data1); str2 = fwupd_bios_setting_to_string(attr2); ret = fu_test_compare_lines(str2, " Name: UEFISecureBoot\n" " Description: Controls Secure boot\n" " Filename: /path/to/bar\n" " BiosSettingType: 1\n" " BiosSettingCurrentValue: Disabled\n" " BiosSettingReadOnly: False\n" " BiosSettingPossibleValues: Disabled\n" " BiosSettingPossibleValues: Enabled\n", &error); g_assert_no_error(error); g_assert_true(ret); /* to JSON */ json1 = fwupd_attr_to_json_string(G_OBJECT(attr1), &error); g_assert_no_error(error); g_assert_nonnull(json1); ret = fu_test_compare_lines(json1, "{\n" " \"Name\" : \"UEFISecureBoot\",\n" " \"Description\" : \"Controls Secure boot\",\n" " \"Filename\" : \"/path/to/bar\",\n" " \"BiosSettingCurrentValue\" : \"Disabled\",\n" " \"BiosSettingReadOnly\" : \"false\",\n" " \"BiosSettingType\" : 1,\n" " \"BiosSettingPossibleValues\" : [\n" " \"Disabled\",\n" " \"Enabled\"\n" " ]\n" "}", &error); g_assert_no_error(error); g_assert_true(ret); /* from JSON */ ret = json_parser_load_from_data(parser, json1, -1, &error); g_assert_no_error(error); g_assert_true(ret); ret = fwupd_bios_setting_from_json(attr2, json_parser_get_root(parser), &error); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip(error->message); return; } g_assert_no_error(error); g_assert_true(ret); str3 = fwupd_bios_setting_to_string(attr2); ret = fu_test_compare_lines(str3, str1, &error); g_assert_no_error(error); g_assert_true(ret); /* make sure we filter CurrentValue if not trusted */ data2 = fwupd_bios_setting_to_variant(attr1, FALSE); attr3 = fwupd_bios_setting_from_variant(data2); str4 = fwupd_bios_setting_to_string(attr3); ret = fu_test_compare_lines(str4, " Name: UEFISecureBoot\n" " Description: Controls Secure boot\n" " Filename: /path/to/bar\n" " BiosSettingType: 1\n" " BiosSettingReadOnly: False\n" " BiosSettingPossibleValues: Disabled\n" " BiosSettingPossibleValues: Enabled\n", &error); g_assert_no_error(error); g_assert_true(ret); /* convert to JSON */ json2 = fwupd_attr_to_json_string(G_OBJECT(attr1), &error); g_assert_no_error(error); g_assert_nonnull(json2); ret = fu_test_compare_lines(json2, "{\n" " \"Name\" : \"UEFISecureBoot\",\n" " \"Description\" : \"Controls Secure boot\",\n" " \"Filename\" : \"/path/to/bar\",\n" " \"BiosSettingCurrentValue\" : \"Disabled\",\n" " \"BiosSettingReadOnly\" : \"false\",\n" " \"BiosSettingType\" : 1,\n" " \"BiosSettingPossibleValues\" : [\n" " \"Disabled\",\n" " \"Enabled\"\n" " ]\n" "}", &error); g_assert_no_error(error); g_assert_true(ret); } int main(int argc, char **argv) { setlocale(LC_ALL, ""); (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); (void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE); /* tests go here */ g_test_add_func("/fwupd/enums", fwupd_enums_func); g_test_add_func("/fwupd/common{machine-hash}", fwupd_common_machine_hash_func); g_test_add_func("/fwupd/common{device-id}", fwupd_common_device_id_func); g_test_add_func("/fwupd/common{guid}", fwupd_common_guid_func); g_test_add_func("/fwupd/release", fwupd_release_func); g_test_add_func("/fwupd/report", fwupd_report_func); g_test_add_func("/fwupd/plugin", fwupd_plugin_func); g_test_add_func("/fwupd/request", fwupd_request_func); g_test_add_func("/fwupd/device", fwupd_device_func); g_test_add_func("/fwupd/device{filter}", fwupd_device_filter_func); g_test_add_func("/fwupd/security-attr", fwupd_security_attr_func); g_test_add_func("/fwupd/remote{download}", fwupd_remote_download_func); g_test_add_func("/fwupd/remote{base-uri}", fwupd_remote_baseuri_func); g_test_add_func("/fwupd/remote{no-path}", fwupd_remote_nopath_func); g_test_add_func("/fwupd/remote{local}", fwupd_remote_local_func); g_test_add_func("/fwupd/remote{duplicate}", fwupd_remote_duplicate_func); g_test_add_func("/fwupd/remote{auth}", fwupd_remote_auth_func); g_test_add_func("/fwupd/bios-attrs", fwupd_bios_settings_func); if (fwupd_has_system_bus()) { g_test_add_func("/fwupd/client{remotes}", fwupd_client_remotes_func); g_test_add_func("/fwupd/client{devices}", fwupd_client_devices_func); } return g_test_run(); } fwupd-1.9.16/libfwupd/fwupd-thread-test.c000066400000000000000000000060701460375044200202740ustar00rootroot00000000000000/* * Copyright (C) 2020 Philip Withnall * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include typedef struct { GApplication *app; FwupdClient *client; GPtrArray *worker_threads; } FuThreadTestSelf; static gboolean fwupd_thread_test_exit_idle_cb(gpointer user_data) { FuThreadTestSelf *self = user_data; g_application_release(self->app); return G_SOURCE_REMOVE; } static gpointer fwupd_thread_test_thread_cb(gpointer user_data) { FuThreadTestSelf *self = user_data; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GMainContext) context = g_main_context_new(); g_autoptr(GMainContextPusher) pusher = g_main_context_pusher_new(context); g_assert_nonnull(pusher); g_message("Calling fwupd_client_get_devices() in thread %p with main context %p", g_thread_self(), g_main_context_get_thread_default()); devices = fwupd_client_get_devices(self->client, NULL, &error_local); if (devices == NULL) g_warning("%s", error_local->message); g_idle_add(fwupd_thread_test_exit_idle_cb, self); return NULL; } static gboolean fwupd_thread_test_idle_cb(gpointer user_data) { FuThreadTestSelf *self = user_data; g_message("fwupd_thread_test_idle_cb() in thread %p with main context %p", g_thread_self(), g_main_context_get_thread_default()); /* create 'n' threads with a small delay, and 'n-1' references on the app */ for (guint i = 0; i < 30; i++) { g_autofree gchar *thread_str = g_strdup_printf("worker%02u", i); GThread *thread = g_thread_new(thread_str, fwupd_thread_test_thread_cb, self); g_usleep(g_random_int_range(0, 1000)); g_ptr_array_add(self->worker_threads, thread); if (i > 0) g_application_hold(self->app); } return G_SOURCE_REMOVE; } static void fwupd_thread_test_activate_cb(GApplication *app, gpointer user_data) { FuThreadTestSelf *self = user_data; g_application_hold(self->app); g_idle_add(fwupd_thread_test_idle_cb, self); } static gboolean fwupd_thread_test_has_system_bus(void) { g_autoptr(GDBusConnection) conn = NULL; conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL); return conn != NULL; } int main(void) { gint retval; g_autoptr(FwupdClient) client = fwupd_client_new(); g_autoptr(GApplication) app = g_application_new("org.test.Test", G_APPLICATION_NON_UNIQUE); g_autoptr(GPtrArray) worker_threads = g_ptr_array_new(); FuThreadTestSelf self = {app, client, worker_threads}; /* only some of the CI targets have a DBus daemon */ if (!fwupd_thread_test_has_system_bus()) { g_message("D-Bus system bus unavailable, skipping tests."); return 0; } g_message("Created FwupdClient in thread %p with main context %p", g_thread_self(), g_main_context_get_thread_default()); g_signal_connect(G_APPLICATION(app), "activate", G_CALLBACK(fwupd_thread_test_activate_cb), &self); retval = g_application_run(app, 0, NULL); for (guint i = 0; i < self.worker_threads->len; i++) { GThread *thread = g_ptr_array_index(self.worker_threads, i); g_thread_join(thread); } return retval; } fwupd-1.9.16/libfwupd/fwupd-version.c000066400000000000000000000011001460375044200175220ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fwupd-version.h" /** * fwupd_version_string: * * Gets the libfwupd installed runtime version. * * This may be different to the *build-time* version if the daemon and library * objects somehow get out of sync. * * Returns: version string * * Since: 1.6.1 **/ const gchar * fwupd_version_string(void) { return G_STRINGIFY(FWUPD_MAJOR_VERSION) "." G_STRINGIFY( FWUPD_MINOR_VERSION) "." G_STRINGIFY(FWUPD_MICRO_VERSION); } fwupd-1.9.16/libfwupd/fwupd-version.h.in000066400000000000000000000026521460375044200201510ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #if !defined(__FWUPD_H_INSIDE__) && !defined(FWUPD_COMPILATION) #error "Only can be included directly." #endif /* clang-format off */ /** * FWUPD_MAJOR_VERSION: * * The compile-time major version */ #define FWUPD_MAJOR_VERSION @MAJOR_VERSION@ /** * FWUPD_MINOR_VERSION: * * The compile-time minor version */ #define FWUPD_MINOR_VERSION @MINOR_VERSION@ /** * FWUPD_MICRO_VERSION: * * The compile-time micro version */ #define FWUPD_MICRO_VERSION @MICRO_VERSION@ /* clang-format on */ /** * FWUPD_CHECK_VERSION: * @major: Major version number * @minor: Minor version number * @micro: Micro version number * * Check whether a fwupd version equal to or greater than * major.minor.micro. * * These compile time macros allow the user to enable parts of client code * depending on the version of libfwupd installed. */ #define FWUPD_CHECK_VERSION(major, minor, micro) \ (FWUPD_MAJOR_VERSION > major || \ (FWUPD_MAJOR_VERSION == major && FWUPD_MINOR_VERSION > minor) || \ (FWUPD_MAJOR_VERSION == major && FWUPD_MINOR_VERSION == minor && \ FWUPD_MICRO_VERSION >= micro)) const gchar * fwupd_version_string(void); fwupd-1.9.16/libfwupd/fwupd.h000066400000000000000000000013201460375044200160500ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #define __FWUPD_H_INSIDE__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef __FWUPD_H_INSIDE__ fwupd-1.9.16/libfwupd/fwupd.map000066400000000000000000000637041460375044200164140ustar00rootroot00000000000000# generated automatically, do not edit! LIBFWUPD_0.1.1 { global: fwupd_error_quark; fwupd_status_from_string; fwupd_status_to_string; local: *; }; LIBFWUPD_0.7.0 { global: fwupd_client_clear_results; fwupd_client_get_results; fwupd_client_get_type; fwupd_client_install; fwupd_client_new; fwupd_client_unlock; fwupd_client_verify; fwupd_device_flag_from_string; fwupd_device_flag_to_string; fwupd_error_from_string; fwupd_error_to_string; fwupd_trust_flag_from_string; fwupd_trust_flag_to_string; fwupd_update_state_from_string; fwupd_update_state_to_string; local: *; } LIBFWUPD_0.1.1; LIBFWUPD_0.7.1 { global: fwupd_client_connect; local: *; } LIBFWUPD_0.7.0; LIBFWUPD_0.7.3 { global: fwupd_client_get_percentage; fwupd_client_get_status; local: *; } LIBFWUPD_0.7.1; LIBFWUPD_0.8.0 { global: fwupd_client_verify_update; local: *; } LIBFWUPD_0.7.3; LIBFWUPD_0.9.2 { global: fwupd_client_get_devices; local: *; } LIBFWUPD_0.8.0; LIBFWUPD_0.9.3 { global: fwupd_checksum_format_for_display; fwupd_checksum_guess_kind; fwupd_client_get_device_by_id; fwupd_client_get_releases; fwupd_client_get_remote_by_id; fwupd_client_get_remotes; fwupd_device_add_checksum; fwupd_device_add_flag; fwupd_device_add_guid; fwupd_device_get_checksums; fwupd_device_get_created; fwupd_device_get_description; fwupd_device_get_flags; fwupd_device_get_flashes_left; fwupd_device_get_guid_default; fwupd_device_get_guids; fwupd_device_get_id; fwupd_device_get_modified; fwupd_device_get_name; fwupd_device_get_summary; fwupd_device_get_type; fwupd_device_get_vendor; fwupd_device_get_version; fwupd_device_get_version_bootloader; fwupd_device_get_version_lowest; fwupd_device_has_flag; fwupd_device_has_guid; fwupd_device_new; fwupd_device_remove_flag; fwupd_device_set_created; fwupd_device_set_description; fwupd_device_set_flags; fwupd_device_set_flashes_left; fwupd_device_set_id; fwupd_device_set_modified; fwupd_device_set_name; fwupd_device_set_summary; fwupd_device_set_vendor; fwupd_device_set_version; fwupd_device_set_version_bootloader; fwupd_device_set_version_lowest; fwupd_device_to_string; fwupd_release_add_checksum; fwupd_release_get_appstream_id; fwupd_release_get_checksums; fwupd_release_get_description; fwupd_release_get_filename; fwupd_release_get_homepage; fwupd_release_get_license; fwupd_release_get_name; fwupd_release_get_remote_id; fwupd_release_get_size; fwupd_release_get_summary; fwupd_release_get_type; fwupd_release_get_uri; fwupd_release_get_vendor; fwupd_release_get_version; fwupd_release_new; fwupd_release_set_appstream_id; fwupd_release_set_description; fwupd_release_set_filename; fwupd_release_set_homepage; fwupd_release_set_license; fwupd_release_set_name; fwupd_release_set_remote_id; fwupd_release_set_size; fwupd_release_set_summary; fwupd_release_set_uri; fwupd_release_set_vendor; fwupd_release_set_version; fwupd_release_to_string; fwupd_remote_get_enabled; fwupd_remote_get_id; fwupd_remote_get_type; fwupd_remote_load_from_filename; fwupd_remote_new; local: *; } LIBFWUPD_0.9.2; LIBFWUPD_0.9.4 { global: fwupd_checksum_get_best; fwupd_checksum_get_by_kind; fwupd_device_get_vendor_id; fwupd_device_set_vendor_id; local: *; } LIBFWUPD_0.9.3; LIBFWUPD_0.9.5 { global: fwupd_remote_get_age; fwupd_remote_get_order_after; fwupd_remote_get_order_before; fwupd_remote_get_password; fwupd_remote_get_priority; fwupd_remote_get_username; fwupd_remote_set_mtime; fwupd_remote_set_priority; local: *; } LIBFWUPD_0.9.4; LIBFWUPD_0.9.6 { global: fwupd_client_get_daemon_version; fwupd_remote_get_filename_cache; fwupd_remote_get_kind; fwupd_remote_kind_from_string; fwupd_remote_kind_to_string; local: *; } LIBFWUPD_0.9.5; LIBFWUPD_0.9.7 { global: fwupd_keyring_kind_from_string; fwupd_keyring_kind_to_string; fwupd_remote_build_firmware_uri; fwupd_remote_get_filename_cache_sig; fwupd_remote_get_firmware_base_uri; fwupd_remote_get_keyring_kind; fwupd_remote_get_metadata_uri; fwupd_remote_get_metadata_uri_sig; local: *; } LIBFWUPD_0.9.6; LIBFWUPD_0.9.8 { global: fwupd_client_get_downgrades; fwupd_client_get_upgrades; fwupd_client_modify_remote; fwupd_device_add_icon; fwupd_device_add_release; fwupd_device_get_icons; fwupd_device_get_release_default; fwupd_device_get_releases; fwupd_device_get_update_error; fwupd_device_get_update_state; fwupd_device_set_update_error; fwupd_device_set_update_state; fwupd_release_get_trust_flags; fwupd_release_set_trust_flags; fwupd_remote_get_filename_source; fwupd_remote_get_title; local: *; } LIBFWUPD_0.9.7; LIBFWUPD_1.0.0 { global: fwupd_client_get_details; fwupd_client_update_metadata; fwupd_device_from_variant; fwupd_device_get_plugin; fwupd_device_set_plugin; fwupd_device_to_variant; fwupd_release_from_variant; fwupd_release_to_variant; fwupd_remote_from_variant; fwupd_remote_get_checksum; fwupd_remote_to_variant; local: *; } LIBFWUPD_0.9.8; LIBFWUPD_1.0.3 { global: fwupd_build_user_agent; local: *; } LIBFWUPD_1.0.0; LIBFWUPD_1.0.4 { global: fwupd_build_history_report_json; fwupd_build_machine_id; fwupd_client_get_history; fwupd_client_modify_device; fwupd_release_add_metadata; fwupd_release_add_metadata_item; fwupd_release_get_metadata; fwupd_release_get_metadata_item; fwupd_remote_get_report_uri; local: *; } LIBFWUPD_1.0.3; LIBFWUPD_1.0.7 { global: fwupd_get_os_release; fwupd_remote_get_agreement; fwupd_remote_set_agreement; local: *; } LIBFWUPD_1.0.4; LIBFWUPD_1.0.8 { global: fwupd_device_get_parent; fwupd_device_get_parent_id; fwupd_device_set_parent; fwupd_device_set_parent_id; local: *; } LIBFWUPD_1.0.7; LIBFWUPD_1.1.0 { global: fwupd_device_incorporate; local: *; } LIBFWUPD_1.0.8; LIBFWUPD_1.1.1 { global: fwupd_device_compare; local: *; } LIBFWUPD_1.1.0; LIBFWUPD_1.1.2 { global: fwupd_device_get_serial; fwupd_device_set_serial; fwupd_device_to_variant_full; local: *; } LIBFWUPD_1.1.1; LIBFWUPD_1.1.3 { global: fwupd_device_get_install_duration; fwupd_device_set_install_duration; local: *; } LIBFWUPD_1.1.2; LIBFWUPD_1.2.1 { global: fwupd_release_get_install_duration; fwupd_release_set_install_duration; local: *; } LIBFWUPD_1.1.3; LIBFWUPD_1.2.2 { global: fwupd_release_get_protocol; fwupd_release_set_protocol; local: *; } LIBFWUPD_1.2.1; LIBFWUPD_1.2.4 { global: fwupd_client_get_tainted; fwupd_device_get_update_message; fwupd_device_set_update_message; fwupd_release_get_details_url; fwupd_release_get_source_url; fwupd_release_get_update_message; fwupd_release_set_details_url; fwupd_release_set_source_url; fwupd_release_set_update_message; local: *; } LIBFWUPD_1.2.2; LIBFWUPD_1.2.5 { global: fwupd_device_add_instance_id; fwupd_device_get_instance_ids; fwupd_device_has_instance_id; fwupd_guid_from_string; fwupd_guid_hash_data; fwupd_guid_hash_string; fwupd_guid_is_valid; fwupd_guid_to_string; local: *; } LIBFWUPD_1.2.4; LIBFWUPD_1.2.6 { global: fwupd_client_activate; fwupd_client_get_approved_firmware; fwupd_client_self_sign; fwupd_client_set_approved_firmware; fwupd_device_to_json; fwupd_release_add_flag; fwupd_release_flag_from_string; fwupd_release_flag_to_string; fwupd_release_get_flags; fwupd_release_has_checksum; fwupd_release_has_flag; fwupd_release_remove_flag; fwupd_release_set_flags; fwupd_release_to_json; fwupd_remote_get_approval_required; local: *; } LIBFWUPD_1.2.5; LIBFWUPD_1.2.7 { global: fwupd_release_add_category; fwupd_release_get_categories; fwupd_release_has_category; local: *; } LIBFWUPD_1.2.6; LIBFWUPD_1.2.8 { global: fwupd_client_modify_config; local: *; } LIBFWUPD_1.2.7; LIBFWUPD_1.2.9 { global: fwupd_device_get_version_format; fwupd_device_set_version_format; fwupd_version_format_from_string; fwupd_version_format_to_string; local: *; } LIBFWUPD_1.2.8; LIBFWUPD_1.2.10 { global: fwupd_device_array_from_variant; fwupd_release_array_from_variant; fwupd_remote_array_from_variant; local: *; } LIBFWUPD_1.2.9; LIBFWUPD_1.3.1 { global: fwupd_client_get_host_product; fwupd_remote_get_remotes_dir; fwupd_remote_set_remotes_dir; local: *; } LIBFWUPD_1.2.10; LIBFWUPD_1.3.2 { global: fwupd_client_get_host_machine_id; fwupd_release_add_issue; fwupd_release_get_issues; fwupd_release_get_name_variant_suffix; fwupd_release_set_name_variant_suffix; local: *; } LIBFWUPD_1.3.1; LIBFWUPD_1.3.3 { global: fwupd_release_get_detach_caption; fwupd_release_get_detach_image; fwupd_release_set_detach_caption; fwupd_release_set_detach_image; fwupd_remote_get_automatic_reports; local: *; } LIBFWUPD_1.3.2; LIBFWUPD_1.3.4 { global: fwupd_client_get_daemon_interactive; local: *; } LIBFWUPD_1.3.3; LIBFWUPD_1.3.6 { global: fwupd_device_get_protocol; fwupd_device_get_version_raw; fwupd_device_set_protocol; fwupd_device_set_version_raw; local: *; } LIBFWUPD_1.3.4; LIBFWUPD_1.3.7 { global: fwupd_device_array_ensure_parents; fwupd_device_get_children; local: *; } LIBFWUPD_1.3.6; LIBFWUPD_1.4.0 { global: fwupd_device_get_status; fwupd_device_get_version_bootloader_raw; fwupd_device_get_version_lowest_raw; fwupd_device_set_status; fwupd_device_set_version_bootloader_raw; fwupd_device_set_version_lowest_raw; fwupd_release_get_created; fwupd_release_get_urgency; fwupd_release_set_created; fwupd_release_set_urgency; fwupd_release_urgency_from_string; fwupd_release_urgency_to_string; fwupd_remote_load_signature; local: *; } LIBFWUPD_1.3.7; LIBFWUPD_1.4.1 { global: fwupd_client_get_devices_by_guid; fwupd_device_id_is_valid; local: *; } LIBFWUPD_1.4.0; LIBFWUPD_1.4.5 { global: fwupd_client_download_bytes; fwupd_client_ensure_networking; fwupd_client_install_bytes; fwupd_client_install_release; fwupd_client_refresh_remote; fwupd_client_set_feature_flags; fwupd_client_set_user_agent; fwupd_client_set_user_agent_for_package; fwupd_client_update_metadata_bytes; fwupd_client_upload_bytes; fwupd_device_get_update_image; fwupd_device_set_update_image; fwupd_feature_flag_from_string; fwupd_feature_flag_to_string; fwupd_release_get_update_image; fwupd_release_set_update_image; fwupd_remote_load_signature_bytes; local: *; } LIBFWUPD_1.4.1; LIBFWUPD_1.4.6 { global: fwupd_client_get_blocked_firmware; fwupd_client_set_blocked_firmware; local: *; } LIBFWUPD_1.4.5; LIBFWUPD_1.5.0 { global: fwupd_client_activate_async; fwupd_client_activate_finish; fwupd_client_clear_results_async; fwupd_client_clear_results_finish; fwupd_client_connect_async; fwupd_client_connect_finish; fwupd_client_download_bytes_async; fwupd_client_download_bytes_finish; fwupd_client_get_approved_firmware_async; fwupd_client_get_approved_firmware_finish; fwupd_client_get_blocked_firmware_async; fwupd_client_get_blocked_firmware_finish; fwupd_client_get_details_bytes; fwupd_client_get_details_bytes_async; fwupd_client_get_details_bytes_finish; fwupd_client_get_device_by_id_async; fwupd_client_get_device_by_id_finish; fwupd_client_get_devices_async; fwupd_client_get_devices_by_guid_async; fwupd_client_get_devices_by_guid_finish; fwupd_client_get_devices_finish; fwupd_client_get_downgrades_async; fwupd_client_get_downgrades_finish; fwupd_client_get_history_async; fwupd_client_get_history_finish; fwupd_client_get_host_security_attrs; fwupd_client_get_host_security_attrs_async; fwupd_client_get_host_security_attrs_finish; fwupd_client_get_host_security_id; fwupd_client_get_plugins; fwupd_client_get_plugins_async; fwupd_client_get_plugins_finish; fwupd_client_get_releases_async; fwupd_client_get_releases_finish; fwupd_client_get_remote_by_id_async; fwupd_client_get_remote_by_id_finish; fwupd_client_get_remotes_async; fwupd_client_get_remotes_finish; fwupd_client_get_report_metadata; fwupd_client_get_report_metadata_async; fwupd_client_get_report_metadata_finish; fwupd_client_get_results_async; fwupd_client_get_results_finish; fwupd_client_get_upgrades_async; fwupd_client_get_upgrades_finish; fwupd_client_install_async; fwupd_client_install_bytes_async; fwupd_client_install_bytes_finish; fwupd_client_install_finish; fwupd_client_install_release_async; fwupd_client_install_release_finish; fwupd_client_modify_config_async; fwupd_client_modify_config_finish; fwupd_client_modify_device_async; fwupd_client_modify_device_finish; fwupd_client_modify_remote_async; fwupd_client_modify_remote_finish; fwupd_client_refresh_remote_async; fwupd_client_refresh_remote_finish; fwupd_client_self_sign_async; fwupd_client_self_sign_finish; fwupd_client_set_approved_firmware_async; fwupd_client_set_approved_firmware_finish; fwupd_client_set_blocked_firmware_async; fwupd_client_set_blocked_firmware_finish; fwupd_client_set_feature_flags_async; fwupd_client_set_feature_flags_finish; fwupd_client_unlock_async; fwupd_client_unlock_finish; fwupd_client_update_metadata_bytes_async; fwupd_client_update_metadata_bytes_finish; fwupd_client_upload_bytes_async; fwupd_client_upload_bytes_finish; fwupd_client_verify_async; fwupd_client_verify_finish; fwupd_client_verify_update_async; fwupd_client_verify_update_finish; fwupd_device_get_branch; fwupd_device_set_branch; fwupd_plugin_add_flag; fwupd_plugin_array_from_variant; fwupd_plugin_flag_from_string; fwupd_plugin_flag_to_string; fwupd_plugin_from_variant; fwupd_plugin_get_flags; fwupd_plugin_get_name; fwupd_plugin_get_type; fwupd_plugin_has_flag; fwupd_plugin_new; fwupd_plugin_remove_flag; fwupd_plugin_set_flags; fwupd_plugin_set_name; fwupd_plugin_to_json; fwupd_plugin_to_string; fwupd_plugin_to_variant; fwupd_release_get_branch; fwupd_release_set_branch; fwupd_remote_get_automatic_security_reports; fwupd_remote_get_security_report_uri; fwupd_security_attr_add_flag; fwupd_security_attr_add_metadata; fwupd_security_attr_add_obsolete; fwupd_security_attr_array_from_variant; fwupd_security_attr_flag_to_string; fwupd_security_attr_flag_to_suffix; fwupd_security_attr_from_variant; fwupd_security_attr_get_appstream_id; fwupd_security_attr_get_flags; fwupd_security_attr_get_level; fwupd_security_attr_get_metadata; fwupd_security_attr_get_name; fwupd_security_attr_get_obsoletes; fwupd_security_attr_get_plugin; fwupd_security_attr_get_result; fwupd_security_attr_get_type; fwupd_security_attr_get_url; fwupd_security_attr_has_flag; fwupd_security_attr_has_obsolete; fwupd_security_attr_new; fwupd_security_attr_result_to_string; fwupd_security_attr_set_appstream_id; fwupd_security_attr_set_flags; fwupd_security_attr_set_level; fwupd_security_attr_set_name; fwupd_security_attr_set_plugin; fwupd_security_attr_set_result; fwupd_security_attr_set_url; fwupd_security_attr_to_json; fwupd_security_attr_to_string; fwupd_security_attr_to_variant; local: *; } LIBFWUPD_1.4.6; LIBFWUPD_1.5.1 { global: fwupd_device_add_child; local: *; } LIBFWUPD_1.5.0; LIBFWUPD_1.5.2 { global: fwupd_client_download_file; fwupd_client_get_user_agent; local: *; } LIBFWUPD_1.5.1; LIBFWUPD_1.5.3 { global: fwupd_client_get_main_context; fwupd_client_set_main_context; fwupd_remote_set_keyring_kind; local: *; } LIBFWUPD_1.5.2; LIBFWUPD_1.5.5 { global: fwupd_device_add_vendor_id; fwupd_device_get_vendor_ids; fwupd_device_has_vendor_id; local: *; } LIBFWUPD_1.5.3; LIBFWUPD_1.5.6 { global: fwupd_client_install_release2; fwupd_client_install_release2_async; fwupd_release_add_location; fwupd_release_get_locations; local: *; } LIBFWUPD_1.5.5; LIBFWUPD_1.5.8 { global: fwupd_device_add_protocol; fwupd_device_get_protocols; fwupd_device_has_protocol; local: *; } LIBFWUPD_1.5.6; LIBFWUPD_1.6.0 { global: fwupd_device_get_composite_id; fwupd_device_set_composite_id; local: *; } LIBFWUPD_1.5.8; LIBFWUPD_1.6.1 { global: fwupd_remote_set_filename_source; fwupd_remote_setup; fwupd_version_string; local: *; } LIBFWUPD_1.6.0; LIBFWUPD_1.6.2 { global: fwupd_device_get_version_build_date; fwupd_device_has_icon; fwupd_device_remove_child; fwupd_device_set_version_build_date; fwupd_remote_to_json; fwupd_request_from_variant; fwupd_request_get_created; fwupd_request_get_device_id; fwupd_request_get_id; fwupd_request_get_image; fwupd_request_get_kind; fwupd_request_get_message; fwupd_request_get_type; fwupd_request_kind_from_string; fwupd_request_kind_to_string; fwupd_request_new; fwupd_request_set_created; fwupd_request_set_device_id; fwupd_request_set_id; fwupd_request_set_image; fwupd_request_set_kind; fwupd_request_set_message; fwupd_request_to_string; fwupd_request_to_variant; local: *; } LIBFWUPD_1.6.1; LIBFWUPD_1.7.0 { global: fwupd_security_attr_add_guid; fwupd_security_attr_add_guids; fwupd_security_attr_get_guids; fwupd_security_attr_has_guid; local: *; } LIBFWUPD_1.6.2; LIBFWUPD_1.7.1 { global: fwupd_client_add_hint; fwupd_client_get_host_security_events; fwupd_client_get_host_security_events_async; fwupd_client_get_host_security_events_finish; fwupd_security_attr_copy; fwupd_security_attr_flag_from_string; fwupd_security_attr_from_json; fwupd_security_attr_get_created; fwupd_security_attr_get_result_fallback; fwupd_security_attr_result_from_string; fwupd_security_attr_set_created; fwupd_security_attr_set_result_fallback; local: *; } LIBFWUPD_1.7.0; LIBFWUPD_1.7.2 { global: fwupd_release_get_id; fwupd_release_set_id; local: *; } LIBFWUPD_1.7.1; LIBFWUPD_1.7.3 { global: fwupd_client_get_host_bkc; fwupd_release_add_tag; fwupd_release_get_tags; fwupd_release_has_tag; local: *; } LIBFWUPD_1.7.2; LIBFWUPD_1.7.4 { global: fwupd_device_get_root; local: *; } LIBFWUPD_1.7.3; LIBFWUPD_1.7.6 { global: fwupd_device_add_issue; fwupd_device_get_issues; local: *; } LIBFWUPD_1.7.4; LIBFWUPD_1.8.0 { global: fwupd_client_disconnect; fwupd_client_get_only_trusted; local: *; } LIBFWUPD_1.7.6; LIBFWUPD_1.8.1 { global: fwupd_client_get_battery_level; fwupd_client_get_battery_threshold; fwupd_device_add_problem; fwupd_device_get_battery_level; fwupd_device_get_battery_threshold; fwupd_device_get_problems; fwupd_device_has_problem; fwupd_device_problem_from_string; fwupd_device_problem_to_string; fwupd_device_remove_problem; fwupd_device_set_battery_level; fwupd_device_set_battery_threshold; fwupd_device_set_problems; local: *; } LIBFWUPD_1.8.0; LIBFWUPD_1.8.2 { global: fwupd_client_get_host_vendor; fwupd_device_to_json_full; fwupd_remote_set_checksum; fwupd_remote_set_filename_cache; fwupd_security_attr_get_description; fwupd_security_attr_get_title; fwupd_security_attr_set_description; fwupd_security_attr_set_title; local: *; } LIBFWUPD_1.8.1; LIBFWUPD_1.8.3 { global: fwupd_device_from_json; fwupd_security_attr_remove_flag; local: *; } LIBFWUPD_1.8.2; LIBFWUPD_1.8.4 { global: fwupd_bios_setting_add_possible_value; fwupd_bios_setting_array_from_variant; fwupd_bios_setting_from_json; fwupd_bios_setting_from_variant; fwupd_bios_setting_get_current_value; fwupd_bios_setting_get_description; fwupd_bios_setting_get_id; fwupd_bios_setting_get_kind; fwupd_bios_setting_get_lower_bound; fwupd_bios_setting_get_name; fwupd_bios_setting_get_path; fwupd_bios_setting_get_possible_values; fwupd_bios_setting_get_read_only; fwupd_bios_setting_get_scalar_increment; fwupd_bios_setting_get_type; fwupd_bios_setting_get_upper_bound; fwupd_bios_setting_has_possible_value; fwupd_bios_setting_map_possible_value; fwupd_bios_setting_new; fwupd_bios_setting_set_current_value; fwupd_bios_setting_set_description; fwupd_bios_setting_set_id; fwupd_bios_setting_set_kind; fwupd_bios_setting_set_lower_bound; fwupd_bios_setting_set_name; fwupd_bios_setting_set_path; fwupd_bios_setting_set_read_only; fwupd_bios_setting_set_scalar_increment; fwupd_bios_setting_set_upper_bound; fwupd_bios_setting_to_json; fwupd_bios_setting_to_string; fwupd_bios_setting_to_variant; fwupd_client_get_bios_settings; fwupd_client_get_bios_settings_async; fwupd_client_get_bios_settings_finish; fwupd_client_modify_bios_setting; fwupd_client_modify_bios_setting_async; fwupd_client_modify_bios_setting_finish; fwupd_security_attr_get_bios_setting_current_value; fwupd_security_attr_get_bios_setting_id; fwupd_security_attr_get_bios_setting_target_value; fwupd_security_attr_set_bios_setting_current_value; fwupd_security_attr_set_bios_setting_id; fwupd_security_attr_set_bios_setting_target_value; local: *; } LIBFWUPD_1.8.3; LIBFWUPD_1.8.6 { global: fwupd_request_add_flag; fwupd_request_flag_from_string; fwupd_request_flag_to_string; fwupd_request_get_flags; fwupd_request_has_flag; fwupd_request_remove_flag; fwupd_request_set_flags; local: *; } LIBFWUPD_1.8.4; LIBFWUPD_1.8.7 { global: fwupd_device_has_checksum; local: *; } LIBFWUPD_1.8.6; LIBFWUPD_1.8.8 { global: fwupd_get_os_release_full; fwupd_release_add_report; fwupd_release_get_reports; fwupd_release_incorporate; fwupd_report_add_metadata_item; fwupd_report_from_variant; fwupd_report_get_created; fwupd_report_get_device_name; fwupd_report_get_distro_id; fwupd_report_get_distro_variant; fwupd_report_get_distro_version; fwupd_report_get_metadata; fwupd_report_get_metadata_item; fwupd_report_get_type; fwupd_report_get_vendor; fwupd_report_get_vendor_id; fwupd_report_get_version_old; fwupd_report_new; fwupd_report_set_created; fwupd_report_set_device_name; fwupd_report_set_distro_id; fwupd_report_set_distro_variant; fwupd_report_set_distro_version; fwupd_report_set_vendor; fwupd_report_set_vendor_id; fwupd_report_set_version_old; fwupd_report_to_json; fwupd_report_to_string; fwupd_report_to_variant; local: *; } LIBFWUPD_1.8.7; LIBFWUPD_1.8.11 { global: fwupd_client_emulation_load; fwupd_client_emulation_load_async; fwupd_client_emulation_load_finish; fwupd_client_emulation_save; fwupd_client_emulation_save_async; fwupd_client_emulation_save_finish; fwupd_client_inhibit; fwupd_client_inhibit_async; fwupd_client_inhibit_finish; fwupd_client_quit; fwupd_client_quit_async; fwupd_client_quit_finish; fwupd_client_set_daemon_version; fwupd_client_uninhibit; fwupd_client_uninhibit_async; fwupd_client_uninhibit_finish; fwupd_device_get_percentage; fwupd_device_set_percentage; local: *; } LIBFWUPD_1.8.8; LIBFWUPD_1.8.13 { global: fwupd_remote_save_to_filename; fwupd_remote_set_enabled; fwupd_remote_set_metadata_uri; fwupd_remote_set_title; local: *; } LIBFWUPD_1.8.11; LIBFWUPD_1.9.1 { global: fwupd_remote_build_report_uri; fwupd_report_add_flag; fwupd_report_flag_from_string; fwupd_report_flag_to_string; fwupd_report_get_flags; fwupd_report_has_flag; fwupd_report_remove_flag; fwupd_report_set_flags; local: *; } LIBFWUPD_1.8.13; LIBFWUPD_1.9.3 { global: fwupd_device_array_filter_flags; fwupd_device_match_flags; fwupd_release_array_filter_flags; fwupd_release_match_flags; fwupd_remote_set_id; fwupd_report_get_remote_id; fwupd_report_set_remote_id; fwupd_security_attr_get_result_success; fwupd_security_attr_set_result_success; local: *; } LIBFWUPD_1.9.1; LIBFWUPD_1.9.4 { global: fwupd_bios_setting_write_value; fwupd_client_refresh_remote2; fwupd_client_refresh_remote2_async; fwupd_remote_add_flag; fwupd_remote_flag_from_string; fwupd_remote_flag_to_string; fwupd_remote_get_checksum_metadata; fwupd_remote_get_flags; fwupd_remote_get_refresh_interval; fwupd_remote_has_flag; fwupd_remote_needs_refresh; fwupd_remote_remove_flag; fwupd_remote_set_flags; local: *; } LIBFWUPD_1.9.3; LIBFWUPD_1.9.6 { global: fwupd_checksum_type_to_string_display; fwupd_client_fix_host_security_attr; fwupd_client_fix_host_security_attr_async; fwupd_client_fix_host_security_attr_finish; fwupd_client_undo_host_security_attr; fwupd_client_undo_host_security_attr_async; fwupd_client_undo_host_security_attr_finish; fwupd_security_attr_get_kernel_current_value; fwupd_security_attr_get_kernel_target_value; fwupd_security_attr_set_kernel_current_value; fwupd_security_attr_set_kernel_target_value; local: *; } LIBFWUPD_1.9.4; LIBFWUPD_1.9.8 { global: fwupd_remote_build_metadata_sig_uri; fwupd_remote_build_metadata_uri; local: *; } LIBFWUPD_1.9.6; LIBFWUPD_1.9.10 { global: fwupd_device_add_request_flag; fwupd_device_get_request_flags; fwupd_device_has_request_flag; fwupd_device_remove_request_flag; fwupd_device_set_request_flags; local: *; } LIBFWUPD_1.9.8; fwupd-1.9.16/libfwupd/meson.build000066400000000000000000000134071460375044200167250ustar00rootroot00000000000000if get_option('tests') subdir('tests') endif fwupd_version_h = configure_file( input: 'fwupd-version.h.in', output: 'fwupd-version.h', configuration: conf ) install_headers( 'fwupd.h', subdir: 'fwupd-1', ) install_headers([ 'fwupd-build.h', 'fwupd-client.h', 'fwupd-client-sync.h', 'fwupd-common.h', 'fwupd-device.h', 'fwupd-enums.h', 'fwupd-error.h', 'fwupd-remote.h', 'fwupd-report.h', 'fwupd-request.h', 'fwupd-bios-setting.h', 'fwupd-security-attr.h', 'fwupd-release.h', 'fwupd-plugin.h', fwupd_version_h, ], subdir: 'fwupd-1/libfwupd', ) libfwupd_deps = [ giounix, libjcat, libjsonglib, libcurl, ] libfwupd_src = [ 'fwupd-client.c', 'fwupd-client-sync.c', 'fwupd-common.c', # fuzzing 'fwupd-device.c', # fuzzing 'fwupd-enums.c', # fuzzing 'fwupd-error.c', # fuzzing 'fwupd-bios-setting.c', # fuzzing 'fwupd-security-attr.c', # fuzzing 'fwupd-release.c', # fuzzing 'fwupd-plugin.c', 'fwupd-remote.c', 'fwupd-report.c', # fuzzing 'fwupd-request.c', # fuzzing 'fwupd-version.c', ] fwupd_mapfile = 'fwupd.map' vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), fwupd_mapfile) fwupd = library( 'fwupd', sources: libfwupd_src, soversion: libfwupd_lt_current, version: libfwupd_lt_version, dependencies: libfwupd_deps, c_args: [ '-DG_LOG_DOMAIN="Fwupd"', '-DLOCALSTATEDIR="' + localstatedir + '"', ], include_directories: root_incdir, link_args: cc.get_supported_link_arguments([vflag]), link_depends: fwupd_mapfile, install: true ) libfwupd_dep = declare_dependency( link_with: fwupd, include_directories: [root_incdir, include_directories('.')], dependencies: libfwupd_deps ) pkgg = import('pkgconfig') pkgg.generate( fwupd, requires: [ 'gio-2.0' ], subdirs: 'fwupd-1', version: meson.project_version(), name: 'fwupd', filebase: 'fwupd', description: 'fwupd is a system daemon for installing device firmware', ) if introspection.allowed() fwupd_gir_deps = [ giounix, libcurl, ] fwupd_gir = gnome.generate_gir(fwupd, sources: [ 'fwupd-client.c', 'fwupd-client.h', 'fwupd-client-sync.c', 'fwupd-client-sync.h', 'fwupd-common.c', 'fwupd-common.h', 'fwupd-common-private.h', 'fwupd-device.c', 'fwupd-device.h', 'fwupd-device-private.h', 'fwupd-enums.c', 'fwupd-enums.h', 'fwupd-enums-private.h', 'fwupd-error.c', 'fwupd-error.h', 'fwupd-bios-setting.c', 'fwupd-bios-setting.h', 'fwupd-bios-setting-private.h', 'fwupd-security-attr.c', 'fwupd-security-attr.h', 'fwupd-security-attr-private.h', 'fwupd-release.c', 'fwupd-release.h', 'fwupd-release-private.h', 'fwupd-plugin.c', 'fwupd-plugin.h', 'fwupd-plugin-private.h', 'fwupd-remote.c', 'fwupd-remote.h', 'fwupd-remote-private.h', 'fwupd-report.c', 'fwupd-report.h', 'fwupd-report-private.h', 'fwupd-request.c', 'fwupd-request.h', 'fwupd-request-private.h', 'fwupd-version.c', fwupd_version_h, ], nsversion: '2.0', namespace: 'Fwupd', symbol_prefix: 'fwupd', identifier_prefix: ['Fwupd', 'fwupd'], export_packages: 'fwupd', header: 'fwupd.h', dependencies: fwupd_gir_deps, includes: [ 'Gio-2.0', 'GObject-2.0', 'Json-1.0', ], install: true ) gnome.generate_vapi('fwupd', sources: fwupd_gir[0], packages: ['gio-2.0', 'json-glib-1.0'], install: true, ) # Verify the map file is correct -- note we can't actually use the generated # file for two reasons: # # 1. We don't hard depend on GObject Introspection # 2. The map file is required to build the lib that the GIR is built from # # To avoid the circular dep, and to ensure we don't change exported API # accidentally actually check in a version of the version script to git. mapfile_target = custom_target('fwupd_mapfile', input: fwupd_gir[0], output: 'fwupd.map', command: [ generate_version_script, 'LIBFWUPD', '@INPUT@', '@OUTPUT@', ], ) test('fwupd-exported-api', diffcmd, args: [ '-urNp', files('fwupd.map'), mapfile_target, ], ) endif if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'fwupd-self-test', metadata_xml_gz_jcat, sources: [ 'fwupd-self-test.c' ], include_directories: [ root_incdir, ], dependencies: [ libfwupd_deps, ], link_with: fwupd, c_args: [ '-DG_LOG_DOMAIN="Fwupd"', '-DSRCDIR="' + meson.current_source_dir() + '"', '-DLOCALSTATEDIR="' + localstatedir + '"', ], ) test('fwupd-self-test', e, timeout: 60, env: env) if run_sanitize_unsafe_tests and gio.version().version_compare ('>= 2.64.0') e = executable( 'fwupd-thread-test', sources: [ 'fwupd-thread-test.c' ], include_directories: [ root_incdir, ], dependencies: [ libfwupd_deps, ], link_with: fwupd, c_args: [ '-DG_LOG_DOMAIN="Fwupd"', ], ) test('fwupd-thread-test', e, timeout: 60) e = executable( 'fwupd-context-test', sources: [ 'fwupd-context-test.c' ], include_directories: [ root_incdir, ], dependencies: [ libfwupd_deps, ], link_with: fwupd, c_args: [ '-DG_LOG_DOMAIN="Fwupd"', ], ) test('fwupd-context-test', e, timeout: 60) endif endif fwupd_incdir = include_directories('.') fwupd-1.9.16/libfwupd/tests/000077500000000000000000000000001460375044200157205ustar00rootroot00000000000000fwupd-1.9.16/libfwupd/tests/auth.conf000066400000000000000000000005101460375044200175240ustar00rootroot00000000000000[fwupd Remote] Enabled=true MetadataURI=https://cdn.fwupd.org/downloads/firmware.xml.gz ReportURI=https://fwupd.org/lvfs/firmware/report SecurityReportURI=https://fwupd.org/lvfs/hsireports/upload AutomaticSecurityReports=true Username=user Password=pass OrderBefore=before OrderAfter=after FirmwareBaseURI=https://my.fancy.cdn/ fwupd-1.9.16/libfwupd/tests/auth/000077500000000000000000000000001460375044200166615ustar00rootroot00000000000000fwupd-1.9.16/libfwupd/tests/auth/meson.build000066400000000000000000000010051460375044200210170ustar00rootroot00000000000000jcat_tool = find_program('jcat-tool', required: false) if jcat_tool.found() metadata_xml_gz_jcat = custom_target('metadata-xml-gz-jcat', input: [ 'metadata.xml.gz', ], output: 'metadata.xml.gz.jcat', command: [ jcat_tool, '--basename', '--appstream-id', 'localhost', 'self-sign', '@OUTPUT@', '@INPUT@', ], ) else metadata_xml_gz_jcat = custom_target('metadata-xml-gz-jcat', input: [ 'metadata.xml.gz', ], output: 'metadata.xml.gz.jcat', command: [ 'touch', '@OUTPUT@', ], ) endif fwupd-1.9.16/libfwupd/tests/auth/metadata.xml.gz000066400000000000000000000000141460375044200215750ustar00rootroot00000000000000hello world fwupd-1.9.16/libfwupd/tests/dell-esrt.conf000066400000000000000000000003661460375044200204670ustar00rootroot00000000000000[fwupd Remote] # this remote provides metadata shipped with the fwupd package Enabled=true Title=Enable UEFI capsule updates on Dell systems Keyring=none MetadataURI=file://@datadir@/fwupd/remotes.d/dell-esrt/metadata.xml ApprovalRequired=false fwupd-1.9.16/libfwupd/tests/disabled.conf000066400000000000000000000000641460375044200203360ustar00rootroot00000000000000[fwupd Remote] Enabled=false Keyring=none Password= fwupd-1.9.16/libfwupd/tests/firmware-base-uri.conf000066400000000000000000000002471460375044200221130ustar00rootroot00000000000000[fwupd Remote] Enabled=true Type=download Keyring=jcat MetadataURI=https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz FirmwareBaseURI=https://my.fancy.cdn/ fwupd-1.9.16/libfwupd/tests/firmware-nopath.conf000066400000000000000000000002011460375044200216630ustar00rootroot00000000000000[fwupd Remote] Enabled=true Type=download Keyring=jcat MetadataURI=https://s3.amazonaws.com/lvfsbucket/downloads/firmware.xml.gz fwupd-1.9.16/libfwupd/tests/meson.build000066400000000000000000000000171460375044200200600ustar00rootroot00000000000000subdir('auth') fwupd-1.9.16/libfwupd/tests/remotes.d000077700000000000000000000000001460375044200227772../../data/remotes.d/ustar00rootroot00000000000000fwupd-1.9.16/libfwupd/tests/stable.conf000077700000000000000000000000001460375044200264342../../src/tests/remotes.d/stable.confustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/000077500000000000000000000000001460375044200157755ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/README.md000066400000000000000000000166321460375044200172640ustar00rootroot00000000000000# libfwupdplugin This library is only partially API and ABI stable. Keeping unused, unsafe and deprecated functions around forever is a maintenance burden and so symbols are removed when branching for new minor versions. Use `./contrib/migrate.py` to migrate up out-of-tree plugins to the new API. Remember: Plugins should be upstream! ## 1.5.5 * `fu_common_is_cpu_intel()`: Use `fu_common_get_cpu_vendor()` instead. * `fu_firmware_strparse_uintXX()`: Use `fu_firmware_strparse_uintXX_safe()` instead. * `fu_plugin_get_usb_context()`: Remove, as no longer required. * `fu_plugin_set_usb_context()`: Remove, as no longer required. * `fu_plugin_runner_usb_device_added()`: Use `fu_plugin_runner_backend_device_added()` instead. * `fu_plugin_runner_udev_device_added()`: Use `fu_plugin_runner_backend_device_added()` instead. * `fu_plugin_runner_udev_device_changed()`: Use `fu_plugin_runner_backend_device_added()` instead. * `FuHidDevice->open()`: Use the `FuDevice` superclass instead. * `FuHidDevice->close()`: Use the `FuDevice` superclass instead. * `FuUsbDevice->probe()`: Use the `FuDevice` superclass instead. * `FuUsbDevice->open()`: Use the `FuDevice` superclass instead. * `FuUsbDevice->close()`: Use the `FuDevice` superclass instead. * `FuUdevDevice->to_string()`: Use the `FuDevice` superclass instead. * `FuUdevDevice->probe()`: Use the `FuDevice` superclass instead. * `FuUdevDevice->open()`: Use the `FuDevice` superclass instead. * `FuUdevDevice->close()`: Use the `FuDevice` superclass instead. ## 1.5.6 * `fu_device_get_protocol()`: Use `fu_device_get_protocols()` instead. * `fu_device_set_protocol()`: Use `fu_device_add_protocol()` instead. ## 1.6.2 * `fu_device_has_custom_flag()`: Use `fu_device_has_private_flag()` instead. ## 1.6.3 * `fu_device_sleep_with_progress()`: Use `fu_progress_sleep()` instead -- but be aware the unit of time has changed from *seconds* to *milliseconds*. * `fu_device_get_status()`: Use `fu_progress_get_status()` instead. * `fu_device_set_status()`: Use `fu_progress_set_status()` instead. * `fu_device_get_progress()`: Use `fu_progress_get_percentage()` instead. * `fu_device_set_progress_full()`: Use `fu_progress_set_percentage_full()` instead. * `fu_device_set_progress()`: Use `fu_progress_set_steps()`, `fu_progress_add_step()` and `fu_progress_done()` -- see the `FuProgress` docs for more details! ## 1.8.2 * `fu_udev_device_pread_full()`: Use `fu_udev_device_pread()` instead -- as the latter now specifies the buffer length. * `fu_udev_device_pread_full()`: Use `fu_udev_device_pwrite()` instead -- as the latter now specifies the buffer length. * `fu_udev_device_ioctl_full()`: Use `fu_udev_device_ioctl()` instead -- as the latter now always specifies the timeout. * `fu_udev_device_new_full()`: Use `fu_udev_device_new()` instead -- as the latter always specifies the context. * `fu_usb_device_new_full()`: Use `fu_usb_device_new()` instead -- as the latter always specifies the context. * `fu_device_new_with_context()`: Use `fu_device_new()` instead -- as the latter always specifies the context. * `fu_plugin_has_custom_flag()`: Use `fu_plugin_has_private_flag()` instead. * `fu_efivar_secure_boot_enabled_full()`: Use `fu_efivar_secure_boot_enabled()` instead -- as the latter always specifies the error. * `fu_progress_add_step()`: Add a 4th parameter to the function to specify the nice name for the step, or NULL. * `fu_backend_setup()`: Now requires a `FuProgress`, although it can be ignored. * `fu_backend_coldplug`: Now requires a `FuProgress`, although it can be ignored. * `FuPluginVfuncs->setup`: Now requires a `FuProgress`, although it can be ignored. * `FuPluginVfuncs->coldplug`: Now requires a `FuProgress`, although it can be ignored. * `fu_common_crc*`: Use `fu_crc` prefix, i.e. remove the `_common` * `fu_common_sum*`: Use `fu_sum` prefix, i.e. remove the `_common` * `fu_byte_array_set_size_full()`: Use `fu_byte_array_set_size` instead -- as the latter now always specifies the fill char. * `fu_common_string*`: Use `fu_string` prefix, i.e. remove the `_common` * `fu_common_bytes*`: Use `fu_bytes` prefix, i.e. remove the `_common` * `fu_common_set_contents_bytes()`: Use `fu_bytes_set_contents()` instead * `fu_common_get_contents_bytes()`: Use `fu_bytes_get_contents()` instead * `fu_common_read*`: Use `fu_memread` prefix, i.e. replace the `_common` with `_mem` * `fu_common_write*`: Use `fu_memwrite` prefix, i.e. replace the `_common` with `_mem` * `fu_common_bytes_compare_raw()`: Use `fu_memcmp_safe()` instead * `fu_common_spawn_sync()`: Use `g_spawn_sync()` instead, or ideally not at all! * `fu_common_extract_archive()`: Use `FuArchiveFirmware()` instead. * `fu_common_instance_id_strsafe()`: Use `fu_device_add_instance_strsafe()` instead. * `fu_common_kernel_locked_down()`: Use `fu_kernel_locked_down` instead. * `fu_common_check_kernel_version()`: Use `fu_kernel_check_version` instead. * `fu_common_get_firmware_search_path()`: Use `fu_kernel_get_firmware_search_path` instead. * `fu_common_set_firmware_search_path()`: Use `fu_kernel_set_firmware_search_path` instead. * `fu_common_reset_firmware_search_path()`: Use `fu_kernel_reset_firmware_search_path` instead. * `fu_common_firmware_builder()`: You should not be using this. * `fu_common_realpath()`: You should not be using this. * `fu_common_uri_get_scheme()`: You should not be using this. * `fu_common_dump*`: Use `fu_dump` prefix, i.e. remove the `_common` * `fu_common_error_array_get_best()`: You should not be using this. * `fu_common_cpuid()`: Use `fu_cpuid` instead. * `fu_common_get_cpu_vendor()`: Use `fu_cpu_get_vendor` instead. * `fu_common_vercmp_full()`: Use `fu_version_compare()` instead. * `fu_common_version_ensure_semver()`: Use `fu_version_ensure_semver()` instead. * `fu_common_version_from_uint*()`: Use `fu_version_from_uint*()` instead. * `fu_common_strtoull()`: Use `fu_strtoull()` instead -- as the latter always specifies the error. * `fu_smbios_to_string()`: Use `fu_firmware_to_string()` instead -- as `FuSmbios` is a `FuFirmware` superclass. * `fu_common_cab_build_silo()`: You should not be using this. * `fu_i2c_device_read_full()`: Use `fu_i2c_device_read` instead. * `fu_i2c_device_write_full()`: Use `fu_i2c_device_write` instead. * `fu_firmware_parse_full()`: Remove the `addr_end` parameter, and ensure that `offset` is a `gsize`. ## 1.8.5 * `fu_volume_new_esp_default()`: Use `fu_context_get_esp_volumes()` instead. * `fu_plugin_set_secure_config_value()`: Set `FWUPD_PLUGIN_FLAG_SECURE_CONFIG` and use `fu_plugin_set_config_value()` ## 1.8.7 * `fu_mei_device_connect()`: Drop the explicit GUID parameter and match in the quirk file instead. * `fu_context_get_smbios_data()`: Add a `GError` ## 1.9.1 * `fu_plugin_get_config_value()`: Add the default value as the last parameter * `fu_plugin_get_config_value_boolean()`: Add the default value as the last parameter ## 1.9.6 * `fu_security_attrs_get_by_appstream_id()`: Add a `GError` ## 1.9.8 * `fu_device_build_instance_id_quirk": rename to`fu_device_build_instance_id_full()` * `fu_smbios_get_data()`: Now returns a array of `GByte`s * `fu_udev_device_get_fd()`: Use `fu_udev_device_get_io_channel()` instead * `fu_device_emit_request()`: Add `FuProgress` and `GError` * `fu_device_set_version_from_uint16()`: Use `fu_device_set_version_u16()` instead * `fu_device_set_version_from_uint24()`: Use `fu_device_set_version_u24()` instead * `fu_device_set_version_from_uint32()`: Use `fu_device_set_version_u32()` instead * `fu_device_set_version_from_uint64()`: Use `fu_device_set_version_u64()` instead fwupd-1.9.16/libfwupdplugin/fu-acpi-table.c000066400000000000000000000115011460375044200205500ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-table-struct.h" #include "fu-acpi-table.h" #include "fu-sum.h" /** * FuAcpiTable: * * An generic ACPI table. * * See also: [class@FuFirmware] */ typedef struct { guint8 revision; gchar *oem_id; gchar *oem_table_id; guint32 oem_revision; } FuAcpiTablePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuAcpiTable, fu_acpi_table, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_acpi_table_get_instance_private(o)) static void fu_acpi_table_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuAcpiTable *self = FU_ACPI_TABLE(firmware); FuAcpiTablePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "revision", priv->revision); fu_xmlb_builder_insert_kv(bn, "oem_id", priv->oem_id); fu_xmlb_builder_insert_kv(bn, "oem_table_id", priv->oem_table_id); fu_xmlb_builder_insert_kx(bn, "oem_revision", priv->oem_revision); } /** * fu_acpi_table_get_revision: * @self: a #FuAcpiTable * * Gets the revision of the table. * * Returns: integer, default 0x0 * * Since: 1.8.11 **/ guint8 fu_acpi_table_get_revision(FuAcpiTable *self) { FuAcpiTablePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_ACPI_TABLE(self), G_MAXUINT8); return priv->revision; } /** * fu_acpi_table_get_oem_id: * @self: a #FuAcpiTable * * Gets an optional OEM ID. * * Returns: a string, or %NULL * * Since: 1.8.11 **/ const gchar * fu_acpi_table_get_oem_id(FuAcpiTable *self) { FuAcpiTablePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_ACPI_TABLE(self), NULL); return priv->oem_id; } /** * fu_acpi_table_get_oem_table_id: * @self: a #FuAcpiTable * * Gets an optional OEM table ID. * * Returns: a string, or %NULL * * Since: 1.8.11 **/ const gchar * fu_acpi_table_get_oem_table_id(FuAcpiTable *self) { FuAcpiTablePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_ACPI_TABLE(self), NULL); return priv->oem_table_id; } /** * fu_acpi_table_get_oem_revision: * @self: a #FuAcpiTable * * Gets the OEM revision. * * Returns: integer, default 0x0 * * Since: 1.8.11 **/ guint32 fu_acpi_table_get_oem_revision(FuAcpiTable *self) { FuAcpiTablePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_ACPI_TABLE(self), G_MAXUINT32); return priv->oem_revision; } static gboolean fu_acpi_table_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuAcpiTable *self = FU_ACPI_TABLE(firmware); FuAcpiTablePrivate *priv = GET_PRIVATE(self); gsize bufsz = 0; guint32 length; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autofree gchar *id = NULL; g_autoptr(GByteArray) st = NULL; /* parse */ st = fu_struct_acpi_table_parse(buf, bufsz, offset, error); if (st == NULL) return FALSE; id = fu_struct_acpi_table_get_signature(st); fu_firmware_set_id(FU_FIRMWARE(self), id); priv->revision = fu_struct_acpi_table_get_revision(st); priv->oem_id = fu_struct_acpi_table_get_oem_id(st); priv->oem_table_id = fu_struct_acpi_table_get_oem_table_id(st); priv->oem_revision = fu_struct_acpi_table_get_oem_revision(st); /* length */ length = fu_struct_acpi_table_get_length(st); if (length > bufsz || length < st->len) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "table length not valid: got 0x%x but expected 0x%x", (guint)bufsz, (guint)length); return FALSE; } fu_firmware_set_size(firmware, length); /* checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint8 checksum_actual = fu_sum8(buf, length); if (checksum_actual != 0x0) { guint8 checksum = fu_struct_acpi_table_get_checksum(st); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "CRC failed, expected %02x, got %02x", (guint)checksum - checksum_actual, checksum); return FALSE; } } /* success */ return TRUE; } static void fu_acpi_table_init(FuAcpiTable *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); } static void fu_acpi_table_finalize(GObject *object) { FuAcpiTable *self = FU_ACPI_TABLE(object); FuAcpiTablePrivate *priv = GET_PRIVATE(self); g_free(priv->oem_table_id); g_free(priv->oem_id); G_OBJECT_CLASS(fu_acpi_table_parent_class)->finalize(object); } static void fu_acpi_table_class_init(FuAcpiTableClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_acpi_table_finalize; klass_firmware->parse = fu_acpi_table_parse; klass_firmware->export = fu_acpi_table_export; } /** * fu_acpi_table_new: * * Creates a new #FuFirmware * * Since: 1.8.11 **/ FuFirmware * fu_acpi_table_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_ACPI_TABLE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-acpi-table.h000066400000000000000000000012711460375044200205600ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_ACPI_TABLE (fu_acpi_table_get_type()) G_DECLARE_DERIVABLE_TYPE(FuAcpiTable, fu_acpi_table, FU, ACPI_TABLE, FuFirmware) struct _FuAcpiTableClass { FuFirmwareClass parent_class; }; FuFirmware * fu_acpi_table_new(void); guint8 fu_acpi_table_get_revision(FuAcpiTable *self) G_GNUC_NON_NULL(1); const gchar * fu_acpi_table_get_oem_id(FuAcpiTable *self) G_GNUC_NON_NULL(1); const gchar * fu_acpi_table_get_oem_table_id(FuAcpiTable *self) G_GNUC_NON_NULL(1); guint32 fu_acpi_table_get_oem_revision(FuAcpiTable *self) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-acpi-table.rs000066400000000000000000000005571460375044200207630ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, Parse)] struct AcpiTable { signature: [char; 4], length: u32le, revision: u8, checksum: u8, oem_id: [char; 6], oem_table_id: [char; 8], oem_revision: u32be, _asl_compiler_id: [char; 4], _asl_compiler_revision: u32le, } fwupd-1.9.16/libfwupdplugin/fu-archive-firmware.c000066400000000000000000000176311460375044200220140ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-archive-firmware.h" #include "fu-archive.h" #include "fu-common.h" /** * FuArchiveFirmware: * * An archive firmware image, typically for nested firmware volumes. * * See also: [class@FuFirmware] */ typedef struct { FuArchiveFormat format; FuArchiveCompression compression; } FuArchiveFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuArchiveFirmware, fu_archive_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_archive_firmware_get_instance_private(o)) static void fu_archive_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuArchiveFirmware *self = FU_ARCHIVE_FIRMWARE(firmware); FuArchiveFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kv(bn, "format", fu_archive_format_to_string(priv->format)); fu_xmlb_builder_insert_kv(bn, "compression", fu_archive_compression_to_string(priv->compression)); } static gboolean fu_archive_firmware_parse_cb(FuArchive *self, const gchar *filename, GBytes *bytes, gpointer user_data, GError **error) { FuFirmware *firmware = FU_FIRMWARE(user_data); g_autoptr(FuFirmware) img = fu_firmware_new_from_bytes(bytes); fu_firmware_set_id(img, filename); return fu_firmware_add_image_full(firmware, img, error); } static gboolean fu_archive_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { g_autoptr(FuArchive) archive = NULL; /* load archive */ archive = fu_archive_new(fw, FU_ARCHIVE_FLAG_IGNORE_PATH, error); if (archive == NULL) return FALSE; /* decompress each image in the archive */ return fu_archive_iterate(archive, fu_archive_firmware_parse_cb, firmware, error); } /** * fu_archive_firmware_get_format: * @self: a #FuArchiveFirmware * * Gets the archive format. * * Returns: format * * Since: 1.8.1 **/ FuArchiveFormat fu_archive_firmware_get_format(FuArchiveFirmware *self) { FuArchiveFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_ARCHIVE_FIRMWARE(self), FU_ARCHIVE_FORMAT_UNKNOWN); return priv->format; } /** * fu_archive_firmware_set_format: * @self: a #FuArchiveFirmware * @format: the archive format, e.g. %FU_ARCHIVE_FORMAT_ZIP * * Sets the archive format. * * Since: 1.8.1 **/ void fu_archive_firmware_set_format(FuArchiveFirmware *self, FuArchiveFormat format) { FuArchiveFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_ARCHIVE_FIRMWARE(self)); priv->format = format; } /** * fu_archive_firmware_get_compression: * @self: A #FuArchiveFirmware * * Returns the compression. * * Returns: compression * * Since: 1.8.1 **/ FuArchiveCompression fu_archive_firmware_get_compression(FuArchiveFirmware *self) { FuArchiveFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_ARCHIVE_FIRMWARE(self), FU_ARCHIVE_COMPRESSION_UNKNOWN); return priv->compression; } /** * fu_archive_firmware_set_compression: * @self: A #FuArchiveFirmware * @compression: the compression, e.g. %FU_ARCHIVE_COMPRESSION_NONE * * Sets the compression. * * Since: 1.8.1 **/ void fu_archive_firmware_set_compression(FuArchiveFirmware *self, FuArchiveCompression compression) { FuArchiveFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_ARCHIVE_FIRMWARE(self)); priv->compression = compression; } /** * fu_archive_firmware_get_image_fnmatch: * @self: a #FuPlugin * @pattern: (not nullable): a glob pattern, e.g. `*foo*` * @error: (nullable): optional return location for an error * * Gets a single firmware image using the image ID pattern. It is also an error for multiple images * to match. * * Returns: (transfer full): a #FuFirmware, or %NULL if the image is not found * * Since: 1.8.9 **/ FuFirmware * fu_archive_firmware_get_image_fnmatch(FuArchiveFirmware *self, const gchar *pattern, GError **error) { g_autoptr(FuFirmware) img_match = NULL; g_autoptr(GPtrArray) imgs = fu_firmware_get_images(FU_FIRMWARE(self)); g_return_val_if_fail(FU_IS_ARCHIVE_FIRMWARE(self), NULL); g_return_val_if_fail(pattern != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); const gchar *fn = fu_firmware_get_id(img); if (!g_pattern_match_simple(pattern, fn)) continue; if (img_match != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "multiple images matched %s", pattern); return NULL; } img_match = g_object_ref(img); } if (img_match == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no image matched %s", pattern); return NULL; } return g_steal_pointer(&img_match); } static GByteArray * fu_archive_firmware_write(FuFirmware *firmware, GError **error) { FuArchiveFirmware *self = FU_ARCHIVE_FIRMWARE(firmware); FuArchiveFirmwarePrivate *priv = GET_PRIVATE(self); g_autoptr(FuArchive) archive = NULL; g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); /* sanity check */ if (priv->format == FU_ARCHIVE_FORMAT_UNKNOWN) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware archive format unspecified"); return NULL; } if (priv->compression == FU_ARCHIVE_COMPRESSION_UNKNOWN) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware archive compression unspecified"); return NULL; } /* save archive and compress each image to the archive */ archive = fu_archive_new(NULL, FU_ARCHIVE_FLAG_NONE, NULL); for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) blob = NULL; if (fu_firmware_get_id(img) == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "image has no ID"); return NULL; } blob = fu_firmware_get_bytes(img, error); if (blob == NULL) return NULL; fu_archive_add_entry(archive, fu_firmware_get_id(img), blob); } return fu_archive_write(archive, priv->format, priv->compression, error); } static gboolean fu_archive_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuArchiveFirmware *self = FU_ARCHIVE_FIRMWARE(firmware); const gchar *tmp; /* simple properties */ tmp = xb_node_query_text(n, "format", NULL); if (tmp != NULL) { FuArchiveFormat format = fu_archive_format_from_string(tmp); if (format == FU_ARCHIVE_FORMAT_UNKNOWN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "format %s not supported", tmp); return FALSE; } fu_archive_firmware_set_format(self, format); } tmp = xb_node_query_text(n, "compression", NULL); if (tmp != NULL) { FuArchiveCompression compression = fu_archive_compression_from_string(tmp); if (compression == FU_ARCHIVE_COMPRESSION_UNKNOWN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "compression %s not supported", tmp); return FALSE; } fu_archive_firmware_set_compression(self, compression); } /* success */ return TRUE; } static void fu_archive_firmware_init(FuArchiveFirmware *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 10000); } static void fu_archive_firmware_class_init(FuArchiveFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_archive_firmware_parse; klass_firmware->write = fu_archive_firmware_write; klass_firmware->build = fu_archive_firmware_build; klass_firmware->export = fu_archive_firmware_export; } /** * fu_archive_firmware_new: * * Creates a new archive #FuFirmware * * Since: 1.7.3 **/ FuFirmware * fu_archive_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_ARCHIVE_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-archive-firmware.h000066400000000000000000000022051460375044200220100ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-archive.h" #include "fu-firmware.h" #define FU_TYPE_ARCHIVE_FIRMWARE (fu_archive_firmware_get_type()) #define FU_TYPE_ARCHIVE_FIRMWARE_RECORD (fu_archive_firmware_record_get_type()) G_DECLARE_DERIVABLE_TYPE(FuArchiveFirmware, fu_archive_firmware, FU, ARCHIVE_FIRMWARE, FuFirmware) struct _FuArchiveFirmwareClass { FuFirmwareClass parent_class; }; FuFirmware * fu_archive_firmware_new(void); FuArchiveFormat fu_archive_firmware_get_format(FuArchiveFirmware *self) G_GNUC_NON_NULL(1); void fu_archive_firmware_set_format(FuArchiveFirmware *self, FuArchiveFormat format) G_GNUC_NON_NULL(1); FuArchiveCompression fu_archive_firmware_get_compression(FuArchiveFirmware *self) G_GNUC_NON_NULL(1); void fu_archive_firmware_set_compression(FuArchiveFirmware *self, FuArchiveCompression compression) G_GNUC_NON_NULL(1); FuFirmware * fu_archive_firmware_get_image_fnmatch(FuArchiveFirmware *self, const gchar *pattern, GError **error) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-archive.c000066400000000000000000000320101460375044200201660ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuArchive" #include "config.h" #include #ifdef HAVE_LIBARCHIVE #include #include #endif #include "fwupd-error.h" #include "fu-archive.h" /** * FuArchive: * * An in-memory archive decompressor */ struct _FuArchive { GObject parent_instance; GHashTable *entries; /* str:GBytes */ }; G_DEFINE_TYPE(FuArchive, fu_archive, G_TYPE_OBJECT) static void fu_archive_finalize(GObject *obj) { FuArchive *self = FU_ARCHIVE(obj); g_hash_table_unref(self->entries); G_OBJECT_CLASS(fu_archive_parent_class)->finalize(obj); } static void fu_archive_class_init(FuArchiveClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_archive_finalize; } static void fu_archive_init(FuArchive *self) { self->entries = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_bytes_unref); } /** * fu_archive_add_entry: * @self: a #FuArchive * @fn: (not nullable): a filename * @blob: (not nullable): a #GBytes * * Adds, or replaces an entry to an archive. * * Since: 1.8.1 **/ void fu_archive_add_entry(FuArchive *self, const gchar *fn, GBytes *blob) { g_return_if_fail(FU_IS_ARCHIVE(self)); g_return_if_fail(fn != NULL); g_return_if_fail(blob != NULL); g_hash_table_insert(self->entries, g_strdup(fn), g_bytes_ref(blob)); } /** * fu_archive_lookup_by_fn: * @self: a #FuArchive * @fn: a filename * @error: (nullable): optional return location for an error * * Finds the blob referenced by filename * * Returns: (transfer full): a #GBytes, or %NULL if the filename was not found * * Since: 1.2.2 **/ GBytes * fu_archive_lookup_by_fn(FuArchive *self, const gchar *fn, GError **error) { GBytes *bytes; g_return_val_if_fail(FU_IS_ARCHIVE(self), NULL); g_return_val_if_fail(fn != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); bytes = g_hash_table_lookup(self->entries, fn); if (bytes == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no blob for %s", fn); return NULL; } return g_bytes_ref(bytes); } /** * fu_archive_iterate: * @self: a #FuArchive * @callback: (scope call) (closure user_data): a #FuArchiveIterateFunc. * @user_data: user data * @error: (nullable): optional return location for an error * * Iterates over the archive contents, calling the given function for each * of the files found. If any @callback returns %FALSE scanning is aborted. * * Returns: True if no @callback returned FALSE * * Since: 1.3.4 */ gboolean fu_archive_iterate(FuArchive *self, FuArchiveIterateFunc callback, gpointer user_data, GError **error) { GHashTableIter iter; gpointer key, value; g_return_val_if_fail(FU_IS_ARCHIVE(self), FALSE); g_return_val_if_fail(callback != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); g_hash_table_iter_init(&iter, self->entries); while (g_hash_table_iter_next(&iter, &key, &value)) { if (!callback(self, (const gchar *)key, (GBytes *)value, user_data, error)) return FALSE; } return TRUE; } #ifdef HAVE_LIBARCHIVE /* workaround the struct types of libarchive */ typedef struct archive _archive_read_ctx; static void _archive_read_ctx_free(_archive_read_ctx *arch) { archive_read_close(arch); archive_read_free(arch); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(_archive_read_ctx, _archive_read_ctx_free) typedef struct archive _archive_write_ctx; static void _archive_write_ctx_free(_archive_write_ctx *arch) { archive_write_close(arch); archive_write_free(arch); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(_archive_write_ctx, _archive_write_ctx_free) typedef struct archive_entry _archive_entry_ctx; static void _archive_entry_ctx_free(_archive_entry_ctx *entry) { archive_entry_free(entry); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(_archive_entry_ctx, _archive_entry_ctx_free) static void fu_archive_set_format(_archive_write_ctx *arch, FuArchiveFormat format) { if (format == FU_ARCHIVE_FORMAT_CPIO) archive_write_set_format_cpio(arch); if (format == FU_ARCHIVE_FORMAT_SHAR) archive_write_set_format_shar(arch); if (format == FU_ARCHIVE_FORMAT_TAR) archive_write_set_format_pax_restricted(arch); if (format == FU_ARCHIVE_FORMAT_USTAR) archive_write_set_format_ustar(arch); if (format == FU_ARCHIVE_FORMAT_PAX) archive_write_set_format_pax(arch); if (format == FU_ARCHIVE_FORMAT_GNUTAR) archive_write_set_format_gnutar(arch); if (format == FU_ARCHIVE_FORMAT_ISO9660) archive_write_set_format_iso9660(arch); if (format == FU_ARCHIVE_FORMAT_ZIP) archive_write_set_format_zip(arch); if (format == FU_ARCHIVE_FORMAT_AR) archive_write_set_format_ar_bsd(arch); if (format == FU_ARCHIVE_FORMAT_AR_SVR4) archive_write_set_format_ar_svr4(arch); if (format == FU_ARCHIVE_FORMAT_MTREE) archive_write_set_format_mtree(arch); if (format == FU_ARCHIVE_FORMAT_RAW) archive_write_set_format_raw(arch); if (format == FU_ARCHIVE_FORMAT_XAR) archive_write_set_format_xar(arch); if (format == FU_ARCHIVE_FORMAT_7ZIP) archive_write_set_format_7zip(arch); if (format == FU_ARCHIVE_FORMAT_WARC) archive_write_set_format_warc(arch); } static void fu_archive_set_compression(_archive_write_ctx *arch, FuArchiveCompression compression) { if (compression == FU_ARCHIVE_COMPRESSION_BZIP2) archive_write_add_filter_bzip2(arch); if (compression == FU_ARCHIVE_COMPRESSION_COMPRESS) archive_write_add_filter_compress(arch); if (compression == FU_ARCHIVE_COMPRESSION_GRZIP) archive_write_add_filter_grzip(arch); if (compression == FU_ARCHIVE_COMPRESSION_GZIP) archive_write_add_filter_gzip(arch); if (compression == FU_ARCHIVE_COMPRESSION_LRZIP) archive_write_add_filter_lrzip(arch); if (compression == FU_ARCHIVE_COMPRESSION_LZ4) archive_write_add_filter_lz4(arch); if (compression == FU_ARCHIVE_COMPRESSION_LZIP) archive_write_add_filter_lzip(arch); if (compression == FU_ARCHIVE_COMPRESSION_LZMA) archive_write_add_filter_lzma(arch); if (compression == FU_ARCHIVE_COMPRESSION_LZOP) archive_write_add_filter_lzop(arch); if (compression == FU_ARCHIVE_COMPRESSION_UU) archive_write_add_filter_uuencode(arch); if (compression == FU_ARCHIVE_COMPRESSION_XZ) archive_write_add_filter_xz(arch); #ifdef HAVE_LIBARCHIVE_WRITE_ADD_COMPRESSION_ZSTD if (compression == FU_ARCHIVE_COMPRESSION_ZSTD) archive_write_add_filter_zstd(arch); #endif } #endif static gboolean fu_archive_load(FuArchive *self, GBytes *blob, FuArchiveFlags flags, GError **error) { #ifdef HAVE_LIBARCHIVE int r; g_autoptr(_archive_read_ctx) arch = NULL; /* decompress anything matching either glob */ arch = archive_read_new(); if (arch == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "libarchive startup failed"); return FALSE; } archive_read_support_format_all(arch); archive_read_support_filter_all(arch); r = archive_read_open_memory(arch, (void *)g_bytes_get_data(blob, NULL), (size_t)g_bytes_get_size(blob)); if (r != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot open: %s", archive_error_string(arch)); return FALSE; } while (TRUE) { const gchar *fn; gint64 bufsz; gssize rc; struct archive_entry *entry; g_autofree gchar *fn_key = NULL; g_autofree guint8 *buf = NULL; g_autoptr(GBytes) bytes = NULL; r = archive_read_next_header(arch, &entry); if (r == ARCHIVE_EOF) break; if (r != ARCHIVE_OK) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot read header: %s", archive_error_string(arch)); return FALSE; } /* only extract if valid */ fn = archive_entry_pathname(entry); if (fn == NULL) continue; bufsz = archive_entry_size(entry); if (bufsz > 1024 * 1024 * 1024) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot read huge files"); return FALSE; } buf = g_malloc(bufsz); rc = archive_read_data(arch, buf, (gsize)bufsz); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot read data: %s", archive_error_string(arch)); return FALSE; } if (rc != bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "read %" G_GSSIZE_FORMAT " of %" G_GINT64_FORMAT, rc, bufsz); return FALSE; } if (flags & FU_ARCHIVE_FLAG_IGNORE_PATH) { fn_key = g_path_get_basename(fn); } else { fn_key = g_strdup(fn); } g_debug("adding %s [%" G_GINT64_FORMAT "]", fn_key, bufsz); bytes = g_bytes_new_take(g_steal_pointer(&buf), bufsz); fu_archive_add_entry(self, fn_key, bytes); } /* success */ return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "missing libarchive support"); return FALSE; #endif } /** * fu_archive_new: * @data: (nullable): archive contents * @flags: archive flags, e.g. %FU_ARCHIVE_FLAG_NONE * @error: (nullable): optional return location for an error * * Parses @data as an archive and decompresses all files to memory blobs. * * If @data is unspecified then a new empty archive is created. * * Returns: a #FuArchive, or %NULL if the archive was invalid in any way. * * Since: 1.2.2 **/ FuArchive * fu_archive_new(GBytes *data, FuArchiveFlags flags, GError **error) { g_autoptr(FuArchive) self = g_object_new(FU_TYPE_ARCHIVE, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (data != NULL) { if (!fu_archive_load(self, data, flags, error)) return NULL; } return g_steal_pointer(&self); } #ifdef HAVE_LIBARCHIVE static gssize fu_archive_write_cb(struct archive *arch, void *user_data, const void *buf, gsize bufsz) { GByteArray *blob = (GByteArray *)user_data; g_byte_array_append(blob, buf, bufsz); return (gssize)bufsz; } #endif /** * fu_archive_write: * @self: a #FuArchive * @format: a compression, e.g. `FU_ARCHIVE_FORMAT_ZIP` * @compression: a compression, e.g. `FU_ARCHIVE_COMPRESSION_NONE` * @error: (nullable): optional return location for an error * * Writes an archive with specified @format and @compression. * * Returns: (transfer full): the archive blob * * Since: 1.8.1 **/ GByteArray * fu_archive_write(FuArchive *self, FuArchiveFormat format, FuArchiveCompression compression, GError **error) { #ifdef HAVE_LIBARCHIVE int r; g_autoptr(_archive_write_ctx) arch = NULL; g_autoptr(GByteArray) blob = g_byte_array_new(); g_autoptr(GList) keys = NULL; g_return_val_if_fail(FU_IS_ARCHIVE(self), NULL); g_return_val_if_fail(format != FU_ARCHIVE_FORMAT_UNKNOWN, NULL); g_return_val_if_fail(compression != FU_ARCHIVE_COMPRESSION_UNKNOWN, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* sanity check */ #ifndef HAVE_LIBARCHIVE_WRITE_ADD_COMPRESSION_ZSTD if (compression == FU_ARCHIVE_COMPRESSION_ZSTD) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "archive_write_add_filter_zstd() not supported"); return NULL; } #endif /* compress anything matching either glob */ arch = archive_write_new(); if (arch == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "libarchive startup failed"); return NULL; } fu_archive_set_format(arch, format); if (format == FU_ARCHIVE_FORMAT_ZIP) { if (compression != FU_ARCHIVE_COMPRESSION_NONE) archive_write_set_options(arch, "zip:compression=deflate"); } else { fu_archive_set_compression(arch, compression); } r = archive_write_open(arch, blob, NULL, fu_archive_write_cb, NULL); if (r != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot open: %s", archive_error_string(arch)); return NULL; } keys = g_hash_table_get_keys(self->entries); for (GList *l = keys; l != NULL; l = l->next) { const gchar *fn = l->data; GBytes *bytes = g_hash_table_lookup(self->entries, fn); gssize rc; g_autoptr(_archive_entry_ctx) entry = NULL; entry = archive_entry_new(); archive_entry_set_pathname(entry, fn); archive_entry_set_filetype(entry, AE_IFREG); archive_entry_set_perm(entry, 0644); archive_entry_set_size(entry, g_bytes_get_size(bytes)); r = archive_write_header(arch, entry); if (r != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot write header: %s", archive_error_string(arch)); return NULL; } rc = archive_write_data(arch, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes)); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot write data: %s", archive_error_string(arch)); return NULL; } } r = archive_write_close(arch); if (r != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot close: %s", archive_error_string(arch)); return NULL; } /* success */ return g_steal_pointer(&blob); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "missing libarchive support"); return NULL; #endif } fwupd-1.9.16/libfwupdplugin/fu-archive.h000066400000000000000000000033751460375044200202070ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-archive-struct.h" #define FU_TYPE_ARCHIVE (fu_archive_get_type()) G_DECLARE_FINAL_TYPE(FuArchive, fu_archive, FU, ARCHIVE, GObject) /** * FuArchiveFlags: * @FU_ARCHIVE_FLAG_NONE: No flags set * @FU_ARCHIVE_FLAG_IGNORE_PATH: Ignore any path component * * The flags to use when loading the archive. **/ typedef enum { FU_ARCHIVE_FLAG_NONE = 0, FU_ARCHIVE_FLAG_IGNORE_PATH = 1 << 0, /*< private >*/ FU_ARCHIVE_FLAG_LAST } FuArchiveFlags; /** * FuArchiveIterateFunc: * @self: a #FuArchive * @filename: a filename * @bytes: the blob referenced by @filename * @user_data: user data * @error: a #GError or NULL * * The archive iteration callback. */ typedef gboolean (*FuArchiveIterateFunc)(FuArchive *self, const gchar *filename, GBytes *bytes, gpointer user_data, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); FuArchive * fu_archive_new(GBytes *data, FuArchiveFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fu_archive_add_entry(FuArchive *self, const gchar *fn, GBytes *blob) G_GNUC_NON_NULL(1, 2); GBytes * fu_archive_lookup_by_fn(FuArchive *self, const gchar *fn, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GByteArray * fu_archive_write(FuArchive *self, FuArchiveFormat format, FuArchiveCompression compression, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_archive_iterate(FuArchive *self, FuArchiveIterateFunc callback, gpointer user_data, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-archive.rs000066400000000000000000000021121460375044200203700ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(FromString, ToString)] enum ArchiveFormat { Unknown, // Since: 1.8.1 Cpio, // Since: 1.8.1 Shar, // Since: 1.8.1 Tar, // Since: 1.8.1 Ustar, // Since: 1.8.1 Pax, // Since: 1.8.1 Gnutar, // Since: 1.8.1 Iso9660, // Since: 1.8.1 Zip, // Since: 1.8.1 Ar, // Since: 1.8.1 ArSvr4, // Since: 1.8.1 Mtree, // Since: 1.8.1 Raw, // Since: 1.8.1 Xar, // Since: 1.8.1 7zip, // Since: 1.8.1 Warc, // Since: 1.8.1 } #[derive(FromString, ToString)] enum ArchiveCompression { Unknown, // Since: 1.8.1 None, // Since: 1.8.1 Gzip, // Since: 1.8.1 Bzip2, // Since: 1.8.1 Compress, // Since: 1.8.1 Lzma, // Since: 1.8.1 Xz, // Since: 1.8.1 Uu, // Since: 1.8.1 Lzip, // Since: 1.8.1 Lrzip, // Since: 1.8.1 Lzop, // Since: 1.8.1 Grzip, // Since: 1.8.1 Lz4, // Since: 1.8.1 Zstd, // Since: 1.8.1 } fwupd-1.9.16/libfwupdplugin/fu-backend-private.h000066400000000000000000000007211460375044200216150ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-backend.h" gboolean fu_backend_load(FuBackend *self, JsonObject *json_object, const gchar *tag, FuBackendLoadFlags flags, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_backend_save(FuBackend *self, JsonBuilder *json_builder, const gchar *tag, FuBackendSaveFlags flags, GError **error) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-backend.c000066400000000000000000000367461460375044200201600ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuBackend" #include "config.h" #include "fu-backend-private.h" #include "fu-string.h" /** * FuBackend: * * An device discovery backend, for instance USB, BlueZ or UDev. * * See also: [class@FuDevice] */ typedef struct { FuContext *ctx; gchar *name; gboolean enabled; gboolean done_setup; gboolean can_invalidate; GHashTable *devices; /* device_id : * FuDevice */ GThread *thread_init; } FuBackendPrivate; enum { SIGNAL_ADDED, SIGNAL_REMOVED, SIGNAL_CHANGED, SIGNAL_LAST }; enum { PROP_0, PROP_NAME, PROP_CAN_INVALIDATE, PROP_CONTEXT, PROP_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE_WITH_PRIVATE(FuBackend, fu_backend, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fu_backend_get_instance_private(o)) /** * fu_backend_device_added: * @self: a #FuBackend * @device: a device * * Emits a signal that indicates the device has been added. * * Since: 1.6.1 **/ void fu_backend_device_added(FuBackend *self, FuDevice *device) { FuBackendPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_BACKEND(self)); g_return_if_fail(FU_IS_DEVICE(device)); g_return_if_fail(priv->thread_init == g_thread_self()); /* assign context if set */ if (priv->ctx != NULL) fu_device_set_context(device, priv->ctx); /* set backend ID if required */ if (fu_device_get_backend_id(device) == NULL) fu_device_set_backend_id(device, priv->name); /* add */ g_hash_table_insert(priv->devices, g_strdup(fu_device_get_backend_id(device)), g_object_ref(device)); g_signal_emit(self, signals[SIGNAL_ADDED], 0, device); } /** * fu_backend_device_removed: * @self: a #FuBackend * @device: a device * * Emits a signal that indicates the device has been removed. * * Since: 1.6.1 **/ void fu_backend_device_removed(FuBackend *self, FuDevice *device) { FuBackendPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_BACKEND(self)); g_return_if_fail(FU_IS_DEVICE(device)); g_return_if_fail(priv->thread_init == g_thread_self()); g_signal_emit(self, signals[SIGNAL_REMOVED], 0, device); g_hash_table_remove(priv->devices, fu_device_get_backend_id(device)); } /** * fu_backend_device_changed: * @self: a #FuBackend * @device: a device * * Emits a signal that indicates the device has been changed. * * Since: 1.6.1 **/ void fu_backend_device_changed(FuBackend *self, FuDevice *device) { FuBackendPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_BACKEND(self)); g_return_if_fail(FU_IS_DEVICE(device)); g_return_if_fail(priv->thread_init == g_thread_self()); g_signal_emit(self, signals[SIGNAL_CHANGED], 0, device); } /** * fu_backend_registered: * @self: a #FuBackend * @device: a device * * Calls the ->registered() vfunc for the backend. This allows the backend to perform shared * backend actions on superclassed devices. * * Since: 1.7.4 **/ void fu_backend_registered(FuBackend *self, FuDevice *device) { FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); g_return_if_fail(FU_IS_BACKEND(self)); g_return_if_fail(FU_IS_DEVICE(device)); if (klass->registered != NULL) klass->registered(self, device); } /** * fu_backend_invalidate: * @self: a #FuBackend * * Normally when calling [method@FuBackend.setup] multiple times it is only actually done once. * Calling this method causes the next requests to [method@FuBackend.setup] to actually probe the * hardware. * * Only subclassed backends setting `can-invalidate=TRUE` at construction time can use this * method, as it is not always safe to call for backends shared between multiple plugins. * * This should be done in case the backing information source has changed, for instance if * a platform subsystem has been updated. * * This also optionally calls the ->invalidate() vfunc for the backend. This allows the backend * to perform shared backend actions on superclassed devices. * * Since: 1.8.0 **/ void fu_backend_invalidate(FuBackend *self) { FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); FuBackendPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_BACKEND(self)); g_return_if_fail(priv->can_invalidate); priv->done_setup = FALSE; if (klass->invalidate != NULL) klass->invalidate(self); } /** * fu_backend_add_string: * @self: a #FuBackend * @idt: indent level * @str: a string to append to * * Add backend-specific device metadata to an existing string. * * Since: 1.8.4 **/ void fu_backend_add_string(FuBackend *self, guint idt, GString *str) { FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); FuBackendPrivate *priv = GET_PRIVATE(self); fu_string_append(str, idt, G_OBJECT_TYPE_NAME(self), ""); if (priv->name != NULL) fu_string_append(str, idt + 1, "Name", priv->name); fu_string_append_kb(str, idt + 1, "Enabled", priv->enabled); fu_string_append_kb(str, idt + 1, "DoneSetup", priv->done_setup); fu_string_append_kb(str, idt + 1, "CanInvalidate", priv->can_invalidate); /* subclassed */ if (klass->to_string != NULL) klass->to_string(self, idt, str); } /** * fu_backend_setup: * @self: a #FuBackend * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Sets up the backend ready for use, which typically calls the subclassed setup * function. No devices should be added or removed at this point. * * Returns: %TRUE for success * * Since: 1.6.1 **/ gboolean fu_backend_setup(FuBackend *self, FuProgress *progress, GError **error) { FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); FuBackendPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_BACKEND(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (priv->done_setup) return TRUE; if (klass->setup != NULL) { if (!klass->setup(self, progress, error)) { priv->enabled = FALSE; return FALSE; } } priv->done_setup = TRUE; return TRUE; } /** * fu_backend_load: * @self: a #FuBackend * @json_object: a #JsonObject * @tag: a string backend tag, or %NULL * @flags: %FuBackendLoadFlags, typically `FU_BACKEND_LOAD_FLAG_NONE` * @error: (nullable): optional return location for an error * * Loads the backend from a JSON object. * * Returns: %TRUE for success * * Since: 1.8.5 **/ gboolean fu_backend_load(FuBackend *self, JsonObject *json_object, const gchar *tag, FuBackendLoadFlags flags, GError **error) { FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); g_return_val_if_fail(FU_IS_BACKEND(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* optional */ if (klass->load != NULL) { if (!klass->load(self, json_object, tag, flags, error)) return FALSE; } return TRUE; } /** * fu_backend_save: * @self: a #FuBackend * @json_builder: a #JsonBuilder * @tag: a string backend tag, or %NULL * @flags: %FuBackendSaveFlags, typically `FU_BACKEND_SAVE_FLAG_NONE` * @error: (nullable): optional return location for an error * * Saves the backend to a JSON builder. * * Returns: %TRUE for success * * Since: 1.8.5 **/ gboolean fu_backend_save(FuBackend *self, JsonBuilder *json_builder, const gchar *tag, FuBackendSaveFlags flags, GError **error) { FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); g_return_val_if_fail(FU_IS_BACKEND(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* optional */ if (klass->save != NULL) { if (!klass->save(self, json_builder, tag, flags, error)) return FALSE; } return TRUE; } /** * fu_backend_coldplug: * @self: a #FuBackend * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Adds devices using the subclassed backend. If [method@FuBackend.setup] has not * already been called then it is run before this function automatically. * * Returns: %TRUE for success * * Since: 1.6.1 **/ gboolean fu_backend_coldplug(FuBackend *self, FuProgress *progress, GError **error) { FuBackendClass *klass = FU_BACKEND_GET_CLASS(self); g_return_val_if_fail(FU_IS_BACKEND(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fu_backend_setup(self, progress, error)) return FALSE; if (klass->coldplug == NULL) return TRUE; return klass->coldplug(self, progress, error); } /** * fu_backend_get_name: * @self: a #FuBackend * * Return the name of the backend, which is normally set by the subclass. * * Returns: backend name * * Since: 1.6.1 **/ const gchar * fu_backend_get_name(FuBackend *self) { FuBackendPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_BACKEND(self), NULL); return priv->name; } /** * fu_backend_get_context: * @self: a #FuBackend * * Gets the context for a backend. * * Returns: (transfer none): a #FuContext or %NULL if not set * * Since: 1.6.1 **/ FuContext * fu_backend_get_context(FuBackend *self) { FuBackendPrivate *priv = GET_PRIVATE(self); return priv->ctx; } /** * fu_backend_get_enabled: * @self: a #FuBackend * * Return the boolean value of a key if it's been configured * * Returns: %TRUE if the backend is enabled * * Since: 1.6.1 **/ gboolean fu_backend_get_enabled(FuBackend *self) { FuBackendPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_BACKEND(self), FALSE); return priv->enabled; } /** * fu_backend_set_enabled: * @self: a #FuBackend * @enabled: enabled state * * Sets the backend enabled state. * * Since: 1.6.1 **/ void fu_backend_set_enabled(FuBackend *self, gboolean enabled) { FuBackendPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_BACKEND(self)); priv->enabled = FALSE; } /** * fu_backend_lookup_by_id: * @self: a #FuBackend * @backend_id: a device backend ID * * Gets a device previously added by the backend. * * Returns: (transfer none): device, or %NULL if not found * * Since: 1.6.1 **/ FuDevice * fu_backend_lookup_by_id(FuBackend *self, const gchar *backend_id) { FuBackendPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_BACKEND(self), NULL); g_return_val_if_fail(backend_id != NULL, NULL); return g_hash_table_lookup(priv->devices, backend_id); } static gint fu_backend_get_devices_sort_cb(gconstpointer a, gconstpointer b) { FuDevice *deva = *((FuDevice **)a); FuDevice *devb = *((FuDevice **)b); return g_strcmp0(fu_device_get_backend_id(deva), fu_device_get_backend_id(devb)); } /** * fu_backend_get_devices: * @self: a #FuBackend * * Gets all the devices added by the backend. * * Returns: (transfer container) (element-type FuDevice): devices * * Since: 1.6.1 **/ GPtrArray * fu_backend_get_devices(FuBackend *self) { FuBackendPrivate *priv = GET_PRIVATE(self); g_autoptr(GList) values = NULL; g_autoptr(GPtrArray) devices = NULL; g_return_val_if_fail(FU_IS_BACKEND(self), NULL); devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); values = g_hash_table_get_values(priv->devices); for (GList *l = values; l != NULL; l = l->next) g_ptr_array_add(devices, g_object_ref(l->data)); g_ptr_array_sort(devices, fu_backend_get_devices_sort_cb); return g_steal_pointer(&devices); } static void fu_backend_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuBackend *self = FU_BACKEND(object); FuBackendPrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_NAME: g_value_set_string(value, priv->name); break; case PROP_CAN_INVALIDATE: g_value_set_boolean(value, priv->can_invalidate); break; case PROP_CONTEXT: g_value_set_object(value, priv->ctx); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_backend_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuBackend *self = FU_BACKEND(object); FuBackendPrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_NAME: priv->name = g_value_dup_string(value); break; case PROP_CAN_INVALIDATE: priv->can_invalidate = g_value_get_boolean(value); break; case PROP_CONTEXT: g_set_object(&priv->ctx, g_value_get_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_backend_init(FuBackend *self) { FuBackendPrivate *priv = GET_PRIVATE(self); priv->enabled = TRUE; priv->thread_init = g_thread_self(); priv->devices = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_object_unref); } static void fu_backend_dispose(GObject *object) { FuBackend *self = FU_BACKEND(object); FuBackendPrivate *priv = GET_PRIVATE(self); g_hash_table_remove_all(priv->devices); G_OBJECT_CLASS(fu_backend_parent_class)->dispose(object); } static void fu_backend_finalize(GObject *object) { FuBackend *self = FU_BACKEND(object); FuBackendPrivate *priv = GET_PRIVATE(self); if (priv->ctx != NULL) g_object_unref(priv->ctx); g_free(priv->name); g_hash_table_unref(priv->devices); G_OBJECT_CLASS(fu_backend_parent_class)->finalize(object); } static void fu_backend_class_init(FuBackendClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->get_property = fu_backend_get_property; object_class->set_property = fu_backend_set_property; object_class->finalize = fu_backend_finalize; object_class->dispose = fu_backend_dispose; /** * FuBackend:name: * * The backend name. * * Since: 1.6.1 */ pspec = g_param_spec_string("name", NULL, NULL, NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_NAME, pspec); /** * FuBackend:can-invalidate: * * If the backend can be invalidated. * * Since: 1.8.0 */ pspec = g_param_spec_boolean("can-invalidate", NULL, NULL, FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_CAN_INVALIDATE, pspec); /** * FuBackend:context: * * The #FuContent to use. * * Since: 1.6.1 */ pspec = g_param_spec_object("context", NULL, NULL, FU_TYPE_CONTEXT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_CONTEXT, pspec); /** * FuBackend::device-added: * @self: the #FuBackend instance that emitted the signal * @device: the #FuDevice * * The ::device-added signal is emitted when a device has been added. * * Since: 1.6.1 **/ signals[SIGNAL_ADDED] = g_signal_new("device-added", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuBackend::device-removed: * @self: the #FuBackend instance that emitted the signal * @device: the #FuDevice * * The ::device-removed signal is emitted when a device has been removed. * * Since: 1.6.1 **/ signals[SIGNAL_REMOVED] = g_signal_new("device-removed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuBackend::device-changed: * @self: the #FuBackend instance that emitted the signal * @device: the #FuDevice * * The ::device-changed signal is emitted when a device has been changed. * * Since: 1.6.1 **/ signals[SIGNAL_CHANGED] = g_signal_new("device-changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); } fwupd-1.9.16/libfwupdplugin/fu-backend.h000066400000000000000000000045771460375044200201620ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-context.h" #include "fu-device.h" #define FU_TYPE_BACKEND (fu_backend_get_type()) G_DECLARE_DERIVABLE_TYPE(FuBackend, fu_backend, FU, BACKEND, GObject) typedef enum { FU_BACKEND_LOAD_FLAG_NONE, } FuBackendLoadFlags; typedef enum { FU_BACKEND_SAVE_FLAG_NONE, } FuBackendSaveFlags; struct _FuBackendClass { GObjectClass parent_class; /* signals */ gboolean (*setup)(FuBackend *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*coldplug)(FuBackend *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT; void (*registered)(FuBackend *self, FuDevice *device); void (*invalidate)(FuBackend *self); void (*to_string)(FuBackend *self, guint indent, GString *str); gboolean (*load)(FuBackend *self, JsonObject *json_object, const gchar *tag, FuBackendLoadFlags flags, GError **error); gboolean (*save)(FuBackend *self, JsonBuilder *json_builder, const gchar *tag, FuBackendSaveFlags flags, GError **error); }; const gchar * fu_backend_get_name(FuBackend *self) G_GNUC_NON_NULL(1); FuContext * fu_backend_get_context(FuBackend *self) G_GNUC_NON_NULL(1); gboolean fu_backend_get_enabled(FuBackend *self) G_GNUC_NON_NULL(1); void fu_backend_set_enabled(FuBackend *self, gboolean enabled) G_GNUC_NON_NULL(1); GPtrArray * fu_backend_get_devices(FuBackend *self) G_GNUC_NON_NULL(1); FuDevice * fu_backend_lookup_by_id(FuBackend *self, const gchar *backend_id) G_GNUC_NON_NULL(1, 2); gboolean fu_backend_setup(FuBackend *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_backend_coldplug(FuBackend *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); void fu_backend_device_added(FuBackend *self, FuDevice *device) G_GNUC_NON_NULL(1, 2); void fu_backend_device_removed(FuBackend *self, FuDevice *device) G_GNUC_NON_NULL(1, 2); void fu_backend_device_changed(FuBackend *self, FuDevice *device) G_GNUC_NON_NULL(1, 2); void fu_backend_registered(FuBackend *self, FuDevice *device) G_GNUC_NON_NULL(1, 2); void fu_backend_invalidate(FuBackend *self) G_GNUC_NON_NULL(1); void fu_backend_add_string(FuBackend *self, guint idt, GString *str) G_GNUC_NON_NULL(1, 3); fwupd-1.9.16/libfwupdplugin/fu-bios-setting.c000066400000000000000000000031361460375044200211630ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuBiosSettings" #include "config.h" #include #include "fu-bios-setting.h" #include "fu-io-channel.h" struct _FuBiosSetting { FwupdBiosSetting parent_instance; }; G_DEFINE_TYPE(FuBiosSetting, fu_bios_setting, FWUPD_TYPE_BIOS_SETTING) static gboolean fu_bios_setting_write_value(FwupdBiosSetting *self, const gchar *value, GError **error) { int fd; g_autofree gchar *fn = g_build_filename(fwupd_bios_setting_get_path(self), "current_value", NULL); g_autoptr(FuIOChannel) io = NULL; fd = open(fn, O_WRONLY); if (fd < 0) { g_set_error(error, G_IO_ERROR, #ifdef HAVE_ERRNO_H g_io_error_from_errno(errno), #else G_IO_ERROR_FAILED, #endif "could not open %s: %s", fn, g_strerror(errno)); return FALSE; } io = fu_io_channel_unix_new(fd); if (!fu_io_channel_write_raw(io, (const guint8 *)value, strlen(value), 1000, FU_IO_CHANNEL_FLAG_NONE, error)) return FALSE; /* success */ fwupd_bios_setting_set_current_value(self, value); g_debug("set %s to %s", fwupd_bios_setting_get_id(self), value); return TRUE; } static void fu_bios_setting_class_init(FuBiosSettingClass *klass) { FwupdBiosSettingClass *bios_setting_class = FWUPD_BIOS_SETTING_CLASS(klass); bios_setting_class->write_value = fu_bios_setting_write_value; } static void fu_bios_setting_init(FuBiosSetting *self) { } FwupdBiosSetting * fu_bios_setting_new(void) { return g_object_new(FU_TYPE_BIOS_SETTING, NULL); } fwupd-1.9.16/libfwupdplugin/fu-bios-setting.h000066400000000000000000000005211460375044200211630ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_BIOS_SETTING (fu_bios_setting_get_type()) G_DECLARE_FINAL_TYPE(FuBiosSetting, fu_bios_setting, FU, BIOS_SETTING, FwupdBiosSetting) FwupdBiosSetting * fu_bios_setting_new(void); fwupd-1.9.16/libfwupdplugin/fu-bios-settings-private.h000066400000000000000000000014321460375044200230200ustar00rootroot00000000000000/* * Copyright (C) 2022 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-bios-settings.h" gboolean fu_bios_settings_setup(FuBiosSettings *self, GError **error) G_GNUC_NON_NULL(1); GPtrArray * fu_bios_settings_get_all(FuBiosSettings *self) G_GNUC_NON_NULL(1); GVariant * fu_bios_settings_to_variant(FuBiosSettings *self, gboolean trusted) G_GNUC_NON_NULL(1); gboolean fu_bios_settings_from_json(FuBiosSettings *self, JsonNode *json_node, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_bios_settings_from_json_file(FuBiosSettings *self, const gchar *fn, GError **error) G_GNUC_NON_NULL(1, 2); GHashTable * fu_bios_settings_to_hash_kv(FuBiosSettings *self) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-bios-settings.c000066400000000000000000000522221460375044200213460ustar00rootroot00000000000000/* * Copyright (C) 2022 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuBiosSettings" #include "config.h" #include #include "fwupd-bios-setting-private.h" #include "fwupd-error.h" #include "fu-bios-setting.h" #include "fu-bios-settings-private.h" #include "fu-path.h" #include "fu-string.h" #define LENOVO_READ_ONLY_NEEDLE "[Status:ShowOnly]" struct _FuBiosSettings { GObject parent_instance; GHashTable *descriptions; GHashTable *read_only; GPtrArray *attrs; }; G_DEFINE_TYPE(FuBiosSettings, fu_bios_settings, G_TYPE_OBJECT) static void fu_bios_settings_finalize(GObject *obj) { FuBiosSettings *self = FU_BIOS_SETTINGS(obj); g_ptr_array_unref(self->attrs); g_hash_table_unref(self->descriptions); g_hash_table_unref(self->read_only); G_OBJECT_CLASS(fu_bios_settings_parent_class)->finalize(obj); } static void fu_bios_settings_class_init(FuBiosSettingsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_bios_settings_finalize; } static gboolean fu_bios_setting_get_key(FwupdBiosSetting *attr, const gchar *key, gchar **value_out, GError **error) { g_autofree gchar *tmp = NULL; g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(attr), FALSE); g_return_val_if_fail(&value_out != NULL, FALSE); tmp = g_build_filename(fwupd_bios_setting_get_path(attr), key, NULL); if (!g_file_get_contents(tmp, value_out, NULL, error)) { g_prefix_error(error, "failed to load %s: ", key); return FALSE; } g_strchomp(*value_out); return TRUE; } static gboolean fu_bios_setting_set_description(FuBiosSettings *self, FwupdBiosSetting *attr, GError **error) { g_autofree gchar *data = NULL; const gchar *value; g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(attr), FALSE); /* Try ID, then name, and then key */ value = g_hash_table_lookup(self->descriptions, fwupd_bios_setting_get_id(attr)); if (value != NULL) { fwupd_bios_setting_set_description(attr, value); return TRUE; } value = g_hash_table_lookup(self->descriptions, fwupd_bios_setting_get_name(attr)); if (value != NULL) { fwupd_bios_setting_set_description(attr, value); return TRUE; } if (!fu_bios_setting_get_key(attr, "display_name", &data, error)) return FALSE; fwupd_bios_setting_set_description(attr, data); return TRUE; } static guint64 fu_bios_setting_get_key_as_integer(FwupdBiosSetting *attr, const gchar *key, GError **error) { g_autofree gchar *str = NULL; guint64 tmp; if (!fu_bios_setting_get_key(attr, key, &str, error)) return G_MAXUINT64; if (!fu_strtoull(str, &tmp, 0, G_MAXUINT64, error)) { g_prefix_error(error, "failed to convert %s to integer: ", key); return G_MAXUINT64; } return tmp; } static gboolean fu_bios_setting_set_enumeration_attrs(FwupdBiosSetting *attr, GError **error) { const gchar *delimiters[] = {",", ";", NULL}; g_autofree gchar *str = NULL; if (!fu_bios_setting_get_key(attr, "possible_values", &str, error)) return FALSE; for (guint j = 0; delimiters[j] != NULL; j++) { g_auto(GStrv) vals = NULL; if (g_strrstr(str, delimiters[j]) == NULL) continue; vals = fu_strsplit(str, strlen(str), delimiters[j], -1); if (vals[0] != NULL) fwupd_bios_setting_set_kind(attr, FWUPD_BIOS_SETTING_KIND_ENUMERATION); for (guint i = 0; vals[i] != NULL && vals[i][0] != '\0'; i++) fwupd_bios_setting_add_possible_value(attr, vals[i]); } return TRUE; } static gboolean fu_bios_setting_set_string_attrs(FwupdBiosSetting *attr, GError **error) { guint64 tmp; tmp = fu_bios_setting_get_key_as_integer(attr, "min_length", error); if (tmp == G_MAXUINT64) return FALSE; fwupd_bios_setting_set_lower_bound(attr, tmp); tmp = fu_bios_setting_get_key_as_integer(attr, "max_length", error); if (tmp == G_MAXUINT64) return FALSE; fwupd_bios_setting_set_upper_bound(attr, tmp); fwupd_bios_setting_set_kind(attr, FWUPD_BIOS_SETTING_KIND_STRING); return TRUE; } static gboolean fu_bios_setting_set_integer_attrs(FwupdBiosSetting *attr, GError **error) { guint64 tmp; tmp = fu_bios_setting_get_key_as_integer(attr, "min_value", error); if (tmp == G_MAXUINT64) return FALSE; fwupd_bios_setting_set_lower_bound(attr, tmp); tmp = fu_bios_setting_get_key_as_integer(attr, "max_value", error); if (tmp == G_MAXUINT64) return FALSE; fwupd_bios_setting_set_upper_bound(attr, tmp); tmp = fu_bios_setting_get_key_as_integer(attr, "scalar_increment", error); if (tmp == G_MAXUINT64) return FALSE; fwupd_bios_setting_set_scalar_increment(attr, tmp); fwupd_bios_setting_set_kind(attr, FWUPD_BIOS_SETTING_KIND_INTEGER); return TRUE; } static gboolean fu_bios_setting_set_current_value(FwupdBiosSetting *attr, GError **error) { g_autofree gchar *str = NULL; if (!fu_bios_setting_get_key(attr, "current_value", &str, error)) return FALSE; fwupd_bios_setting_set_current_value(attr, str); return TRUE; } static void fu_bios_setting_set_read_only(FuBiosSettings *self, FwupdBiosSetting *attr) { const gchar *tmp; if (fwupd_bios_setting_get_kind(attr) == FWUPD_BIOS_SETTING_KIND_ENUMERATION) { const gchar *value = g_hash_table_lookup(self->read_only, fwupd_bios_setting_get_id(attr)); if (g_strcmp0(value, fwupd_bios_setting_get_current_value(attr)) == 0) fwupd_bios_setting_set_read_only(attr, TRUE); } tmp = g_strrstr(fwupd_bios_setting_get_current_value(attr), LENOVO_READ_ONLY_NEEDLE); if (tmp != NULL) fwupd_bios_setting_set_read_only(attr, TRUE); } #ifdef FU_THINKLMI_COMPAT #define LENOVO_POSSIBLE_NEEDLE "[Optional:" #define LENOVO_EXCLUDED "[Excluded from boot order:" static gboolean fu_bios_setting_fixup_lenovo_thinklmi_bug(FwupdBiosSetting *attr, GError **error) { const gchar *current_value = fwupd_bios_setting_get_current_value(attr); const gchar *tmp; g_autoptr(GString) str = NULL; g_autoptr(GString) right_str = NULL; g_auto(GStrv) vals = NULL; /* debug */ g_debug("processing %s: (%s)", fwupd_bios_setting_get_name(attr), fwupd_bios_setting_get_current_value(attr)); str = g_string_new(current_value); /* empty string */ if (str->len == 0) return TRUE; /* split into left and right */ vals = fu_strsplit(str->str, str->len, ";", 2); /* use left half for current value */ fwupd_bios_setting_set_current_value(attr, vals[0]); if (vals[1] == NULL) return TRUE; /* use the right half to process further */ right_str = g_string_new(vals[1]); /* Strip boot order exclusion info */ tmp = g_strrstr(right_str->str, LENOVO_EXCLUDED); if (tmp != NULL) g_string_truncate(str, tmp - right_str->str); /* Look for possible values to populate */ tmp = g_strrstr(right_str->str, LENOVO_POSSIBLE_NEEDLE); if (tmp != NULL) { g_auto(GStrv) possible_vals = NULL; g_string_erase(right_str, 0, strlen(LENOVO_POSSIBLE_NEEDLE)); possible_vals = fu_strsplit(right_str->str, right_str->len, ",", -1); if (possible_vals[0] != NULL) fwupd_bios_setting_set_kind(attr, FWUPD_BIOS_SETTING_KIND_ENUMERATION); for (guint i = 0; possible_vals[i] != NULL && possible_vals[i][0] != '\0'; i++) { /* last string */ if (possible_vals[i + 1] == NULL && g_strrstr(possible_vals[i], "]") != NULL) { g_auto(GStrv) stripped_vals = fu_strsplit(possible_vals[i], strlen(possible_vals[i]), "]", -1); fwupd_bios_setting_add_possible_value(attr, stripped_vals[0]); continue; } fwupd_bios_setting_add_possible_value(attr, possible_vals[i]); } } return TRUE; } static gboolean fu_bios_settings_run_folder_fixups(FwupdBiosSetting *attr, GError **error) { if (fwupd_bios_setting_get_kind(attr) == FWUPD_BIOS_SETTING_KIND_UNKNOWN) return fu_bios_setting_fixup_lenovo_thinklmi_bug(attr, error); return TRUE; } #endif static gboolean fu_bios_setting_set_type(FuBiosSettings *self, FwupdBiosSetting *attr, GError **error) { gboolean kernel_bug = FALSE; g_autofree gchar *data = NULL; g_autoptr(GError) error_key = NULL; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_BIOS_SETTINGS(self), FALSE); g_return_val_if_fail(FWUPD_IS_BIOS_SETTING(attr), FALSE); /* lenovo thinklmi seems to be missing it even though it's mandatory :/ */ if (!fu_bios_setting_get_key(attr, "type", &data, &error_key)) { #ifdef FU_THINKLMI_COMPAT static gboolean kernel_bug_notified = FALSE; if (!kernel_bug_notified) { g_info("using think-lmi compatibility for kernels less than 6.3"); kernel_bug_notified = TRUE; } kernel_bug = TRUE; #else g_debug("%s", error_key->message); g_propagate_error(error, g_steal_pointer(&error_key)); return FALSE; #endif } if (g_strcmp0(data, "enumeration") == 0 || kernel_bug) { if (!fu_bios_setting_set_enumeration_attrs(attr, &error_local)) g_debug("failed to add enumeration attrs: %s", error_local->message); } else if (g_strcmp0(data, "integer") == 0) { if (!fu_bios_setting_set_integer_attrs(attr, &error_local)) g_debug("failed to add integer attrs: %s", error_local->message); } else if (g_strcmp0(data, "string") == 0) { if (!fu_bios_setting_set_string_attrs(attr, &error_local)) g_debug("failed to add string attrs: %s", error_local->message); } return TRUE; } /* Special case attribute that is a file not a folder * https://github.com/torvalds/linux/blob/v5.18/Documentation/ABI/testing/sysfs-class-firmware-attributes#L300 */ static gboolean fu_bios_setting_set_file_attributes(FuBiosSettings *self, FwupdBiosSetting *attr, GError **error) { g_autofree gchar *value = NULL; if (g_strcmp0(fwupd_bios_setting_get_name(attr), FWUPD_BIOS_SETTING_PENDING_REBOOT) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s attribute is not supported", fwupd_bios_setting_get_name(attr)); return FALSE; } if (!fu_bios_setting_set_description(self, attr, error)) return FALSE; if (!fu_bios_setting_get_key(attr, NULL, &value, error)) return FALSE; fwupd_bios_setting_set_current_value(attr, value); fwupd_bios_setting_set_read_only(attr, TRUE); return TRUE; } static gboolean fu_bios_settings_set_folder_attributes(FuBiosSettings *self, FwupdBiosSetting *attr, GError **error) { g_autoptr(GError) error_local = NULL; if (!fu_bios_setting_set_type(self, attr, error)) return FALSE; if (!fu_bios_setting_set_current_value(attr, error)) return FALSE; if (!fu_bios_setting_set_description(self, attr, &error_local)) g_debug("%s", error_local->message); #ifdef FU_THINKLMI_COMPAT if (!fu_bios_settings_run_folder_fixups(attr, error)) return FALSE; #endif fu_bios_setting_set_read_only(self, attr); return TRUE; } static gboolean fu_bios_settings_populate_attribute(FuBiosSettings *self, const gchar *driver, const gchar *path, const gchar *name, GError **error) { g_autoptr(FwupdBiosSetting) attr = NULL; g_autofree gchar *id = NULL; g_return_val_if_fail(FU_IS_BIOS_SETTINGS(self), FALSE); g_return_val_if_fail(name != NULL, FALSE); g_return_val_if_fail(path != NULL, FALSE); g_return_val_if_fail(driver != NULL, FALSE); attr = fu_bios_setting_new(); id = g_strdup_printf("com.%s.%s", driver, name); fwupd_bios_setting_set_name(attr, name); fwupd_bios_setting_set_path(attr, path); fwupd_bios_setting_set_id(attr, id); if (g_file_test(path, G_FILE_TEST_IS_DIR)) { if (!fu_bios_settings_set_folder_attributes(self, attr, error)) return FALSE; } else { if (!fu_bios_setting_set_file_attributes(self, attr, error)) return FALSE; } g_ptr_array_add(self->attrs, g_object_ref(attr)); return TRUE; } static void fu_bios_settings_populate_descriptions(FuBiosSettings *self) { g_return_if_fail(FU_IS_BIOS_SETTINGS(self)); g_hash_table_insert(self->descriptions, g_strdup("pending_reboot"), /* TRANSLATORS: description of a BIOS setting */ g_strdup(_("Settings will apply after system reboots"))); g_hash_table_insert(self->descriptions, g_strdup("com.thinklmi.WindowsUEFIFirmwareUpdate"), /* TRANSLATORS: description of a BIOS setting */ g_strdup(_("BIOS updates delivered via LVFS or Windows Update"))); } static void fu_bios_settings_populate_read_only(FuBiosSettings *self) { g_return_if_fail(FU_IS_BIOS_SETTINGS(self)); g_hash_table_insert(self->read_only, g_strdup("com.thinklmi.SecureBoot"), g_strdup(_("Enable"))); g_hash_table_insert(self->read_only, g_strdup("com.dell-wmi-sysman.SecureBoot"), g_strdup(_("Enabled"))); } static void fu_bios_settings_combination_fixups(FuBiosSettings *self) { FwupdBiosSetting *thinklmi_sb = fu_bios_settings_get_attr(self, "com.thinklmi.SecureBoot"); FwupdBiosSetting *thinklmi_3rd = fu_bios_settings_get_attr(self, "com.thinklmi.Allow3rdPartyUEFICA"); if (thinklmi_sb != NULL && thinklmi_3rd != NULL) { const gchar *val = fwupd_bios_setting_get_current_value(thinklmi_3rd); if (g_strcmp0(val, "Disable") == 0) { g_info("Disabling changing %s since %s is %s", fwupd_bios_setting_get_name(thinklmi_sb), fwupd_bios_setting_get_name(thinklmi_3rd), val); fwupd_bios_setting_set_read_only(thinklmi_sb, TRUE); } } } /** * fu_bios_settings_setup: * @self: a #FuBiosSettings * * Clears all attributes and re-initializes them. * Mostly used for the test suite, but could potentially be connected to udev * events for drivers being loaded or unloaded too. * * Since: 1.8.4 **/ gboolean fu_bios_settings_setup(FuBiosSettings *self, GError **error) { guint count = 0; g_autofree gchar *sysfsfwdir = NULL; g_autoptr(GDir) class_dir = NULL; g_return_val_if_fail(FU_IS_BIOS_SETTINGS(self), FALSE); if (self->attrs->len > 0) { g_debug("re-initializing attributes"); g_ptr_array_set_size(self->attrs, 0); } if (g_hash_table_size(self->descriptions) == 0) fu_bios_settings_populate_descriptions(self); if (g_hash_table_size(self->read_only) == 0) fu_bios_settings_populate_read_only(self); sysfsfwdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_FW_ATTRIB); class_dir = g_dir_open(sysfsfwdir, 0, error); if (class_dir == NULL) return FALSE; do { g_autofree gchar *path = NULL; g_autoptr(GDir) driver_dir = NULL; const gchar *driver = g_dir_read_name(class_dir); if (driver == NULL) break; path = g_build_filename(sysfsfwdir, driver, "attributes", NULL); if (!g_file_test(path, G_FILE_TEST_IS_DIR)) { g_debug("skipping non-directory %s", path); continue; } driver_dir = g_dir_open(path, 0, error); if (driver_dir == NULL) return FALSE; do { const gchar *name = g_dir_read_name(driver_dir); g_autofree gchar *full_path = NULL; g_autoptr(GError) error_local = NULL; if (name == NULL) break; full_path = g_build_filename(path, name, NULL); if (!fu_bios_settings_populate_attribute(self, driver, full_path, name, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_debug("%s is not supported", name); continue; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } while (++count); } while (TRUE); g_info("loaded %u BIOS settings", count); fu_bios_settings_combination_fixups(self); return TRUE; } static void fu_bios_settings_init(FuBiosSettings *self) { self->attrs = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); self->descriptions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); self->read_only = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); } /** * fu_bios_settings_get_attr: * @self: a #FuBiosSettings * @val: the attribute ID or name to check for * * Returns: (transfer none): the attribute with the given ID or name or NULL if it doesn't exist. * * Since: 1.8.4 **/ FwupdBiosSetting * fu_bios_settings_get_attr(FuBiosSettings *self, const gchar *val) { g_return_val_if_fail(FU_IS_BIOS_SETTINGS(self), NULL); g_return_val_if_fail(val != NULL, NULL); for (guint i = 0; i < self->attrs->len; i++) { FwupdBiosSetting *attr = g_ptr_array_index(self->attrs, i); const gchar *tmp_id = fwupd_bios_setting_get_id(attr); const gchar *tmp_name = fwupd_bios_setting_get_name(attr); if (g_strcmp0(val, tmp_id) == 0 || g_strcmp0(val, tmp_name) == 0) return attr; } return NULL; } /** * fu_bios_settings_get_all: * @self: a #FuBiosSettings * * Gets all the attributes in the object. * * Returns: (transfer container) (element-type FwupdBiosSetting): attributes * * Since: 1.8.4 **/ GPtrArray * fu_bios_settings_get_all(FuBiosSettings *self) { g_return_val_if_fail(FU_IS_BIOS_SETTINGS(self), NULL); return g_ptr_array_ref(self->attrs); } /** * fu_bios_settings_get_pending_reboot: * @self: a #FuBiosSettings * @result: (out): Whether a reboot is pending * @error: (nullable): optional return location for an error * * Determines if the system will apply changes to attributes upon reboot * * Since: 1.8.4 **/ gboolean fu_bios_settings_get_pending_reboot(FuBiosSettings *self, gboolean *result, GError **error) { FwupdBiosSetting *attr = NULL; g_autofree gchar *data = NULL; guint64 val = 0; g_return_val_if_fail(result != NULL, FALSE); g_return_val_if_fail(FU_IS_BIOS_SETTINGS(self), FALSE); for (guint i = 0; i < self->attrs->len; i++) { FwupdBiosSetting *attr_tmp = g_ptr_array_index(self->attrs, i); const gchar *tmp = fwupd_bios_setting_get_name(attr_tmp); if (g_strcmp0(tmp, FWUPD_BIOS_SETTING_PENDING_REBOOT) == 0) { attr = attr_tmp; break; } } if (attr == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find pending reboot attribute"); return FALSE; } /* refresh/re-read */ if (!fu_bios_setting_get_key(attr, NULL, &data, error)) return FALSE; fwupd_bios_setting_set_current_value(attr, data); if (!fu_strtoull(data, &val, 0, G_MAXUINT32, error)) return FALSE; *result = (val == 1); return TRUE; } /** * fu_bios_settings_to_variant: * @self: a #FuBiosSettings * @trusted: whether the caller should receive trusted values * * Serializes the #FwupdBiosSetting objects. * * Returns: a #GVariant or %NULL * * Since: 1.8.4 **/ GVariant * fu_bios_settings_to_variant(FuBiosSettings *self, gboolean trusted) { GVariantBuilder builder; g_return_val_if_fail(FU_IS_BIOS_SETTINGS(self), NULL); g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); for (guint i = 0; i < self->attrs->len; i++) { FwupdBiosSetting *bios_setting = g_ptr_array_index(self->attrs, i); g_variant_builder_add_value(&builder, fwupd_bios_setting_to_variant(bios_setting, trusted)); } return g_variant_new("(aa{sv})", &builder); } /** * fu_bios_settings_from_json: * @self: a #FuBiosSettings * @json_node: a Json node to parse from * @error: (nullable): optional return location for an error * * Loads #FwupdBiosSetting objects from a JSON node. * * Returns: TRUE if the objects were imported * * Since: 1.8.4 **/ gboolean fu_bios_settings_from_json(FuBiosSettings *self, JsonNode *json_node, GError **error) { JsonArray *array; JsonObject *obj; /* sanity check */ if (!JSON_NODE_HOLDS_OBJECT(json_node)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not JSON object"); return FALSE; } obj = json_node_get_object(json_node); /* this has to exist */ if (!json_object_has_member(obj, "BiosSettings")) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no BiosSettings property in object"); return FALSE; } array = json_object_get_array_member(obj, "BiosSettings"); for (guint i = 0; i < json_array_get_length(array); i++) { JsonNode *node_tmp = json_array_get_element(array, i); g_autoptr(FwupdBiosSetting) attr = fwupd_bios_setting_new(NULL, NULL); if (!fwupd_bios_setting_from_json(attr, node_tmp, error)) return FALSE; g_ptr_array_add(self->attrs, g_steal_pointer(&attr)); } /* success */ return TRUE; } /** * fu_bios_settings_from_json_file: * @self: A #FuBiosSettings * @fn: a path to a JSON file * @error: (nullable): optional return location for an error * * Adds all BIOS attributes from a JSON filename * * Returns: TRUE for success, FALSE for failure * * Since: 1.8.4 **/ gboolean fu_bios_settings_from_json_file(FuBiosSettings *self, const gchar *fn, GError **error) { g_autofree gchar *data = NULL; g_autoptr(JsonParser) parser = json_parser_new(); if (!g_file_get_contents(fn, &data, NULL, error)) return FALSE; if (!json_parser_load_from_data(parser, data, -1, error)) { g_prefix_error(error, "%s doesn't look like JSON data: ", fn); return FALSE; } return fu_bios_settings_from_json(self, json_parser_get_root(parser), error); } /** * fu_bios_settings_to_hash_kv: * @self: A #FuBiosSettings * * Creates a #GHashTable with the name and current value of * all BIOS settings. * * Returns: (transfer full): name/value pairs * Since: 1.8.4 **/ GHashTable * fu_bios_settings_to_hash_kv(FuBiosSettings *self) { GHashTable *bios_settings = NULL; g_return_val_if_fail(self != NULL, NULL); bios_settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); for (guint i = 0; i < self->attrs->len; i++) { FwupdBiosSetting *item_setting = g_ptr_array_index(self->attrs, i); g_hash_table_insert(bios_settings, g_strdup(fwupd_bios_setting_get_id(item_setting)), g_strdup(fwupd_bios_setting_get_current_value(item_setting))); } return bios_settings; } /** * fu_bios_settings_new: * * Returns: #FuBiosSettings * * Since: 1.8.4 **/ FuBiosSettings * fu_bios_settings_new(void) { return g_object_new(FU_TYPE_FIRMWARE_ATTRS, NULL); } fwupd-1.9.16/libfwupdplugin/fu-bios-settings.h000066400000000000000000000011251460375044200213470ustar00rootroot00000000000000/* * Copyright (C) 2022 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_FIRMWARE_ATTRS (fu_bios_settings_get_type()) G_DECLARE_FINAL_TYPE(FuBiosSettings, fu_bios_settings, FU, BIOS_SETTINGS, GObject) FuBiosSettings * fu_bios_settings_new(void); gboolean fu_bios_settings_get_pending_reboot(FuBiosSettings *self, gboolean *result, GError **error) G_GNUC_NON_NULL(1); FwupdBiosSetting * fu_bios_settings_get_attr(FuBiosSettings *self, const gchar *val) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-bluez-device.c000066400000000000000000000515411460375044200211350ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuBluezDevice" #include "config.h" #include #include "fu-bluez-device.h" #include "fu-common.h" #include "fu-device-private.h" #include "fu-firmware-common.h" #include "fu-string.h" #define DEFAULT_PROXY_TIMEOUT 5000 /** * FuBluezDevice: * * A BlueZ Bluetooth device. * * See also: [class@FuDevice] */ typedef struct { GDBusObjectManager *object_manager; GDBusProxy *proxy; GHashTable *uuids; /* utf8 : FuBluezDeviceUuidHelper */ } FuBluezDevicePrivate; typedef struct { FuBluezDevice *self; gchar *uuid; gchar *path; gulong signal_id; GDBusProxy *proxy; } FuBluezDeviceUuidHelper; enum { PROP_0, PROP_OBJECT_MANAGER, PROP_PROXY, PROP_LAST }; enum { SIGNAL_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE_WITH_PRIVATE(FuBluezDevice, fu_bluez_device, FU_TYPE_DEVICE) #define GET_PRIVATE(o) (fu_bluez_device_get_instance_private(o)) static void fu_bluez_uuid_free(FuBluezDeviceUuidHelper *uuid_helper) { if (uuid_helper->path != NULL) g_free(uuid_helper->path); if (uuid_helper->proxy != NULL) g_object_unref(uuid_helper->proxy); g_free(uuid_helper->uuid); g_object_unref(uuid_helper->self); g_free(uuid_helper); } /* * Looks up a UUID in the FuBluezDevice uuids table. */ static FuBluezDeviceUuidHelper * fu_bluez_device_get_uuid_helper(FuBluezDevice *self, const gchar *uuid, GError **error) { FuBluezDevicePrivate *priv = GET_PRIVATE(self); FuBluezDeviceUuidHelper *uuid_helper; uuid_helper = g_hash_table_lookup(priv->uuids, uuid); if (uuid_helper == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "UUID %s not supported", uuid); return NULL; } return uuid_helper; } static void fu_bluez_device_signal_cb(GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, FuBluezDeviceUuidHelper *uuid_helper) { g_signal_emit(uuid_helper->self, signals[SIGNAL_CHANGED], 0, uuid_helper->uuid); } /* * Builds the GDBusProxy of the BlueZ object identified by a UUID * string. If the object doesn't have a dedicated proxy yet, this * creates it and saves it in the FuBluezDeviceUuidHelper object. * * NOTE: Currently limited to GATT characteristics. */ static gboolean fu_bluez_device_ensure_uuid_helper_proxy(FuBluezDeviceUuidHelper *uuid_helper, GError **error) { if (uuid_helper->proxy != NULL) return TRUE; uuid_helper->proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.bluez", uuid_helper->path, "org.bluez.GattCharacteristic1", NULL, error); if (uuid_helper->proxy == NULL) { g_prefix_error(error, "Failed to create GDBusProxy for uuid_helper: "); return FALSE; } g_dbus_proxy_set_default_timeout(uuid_helper->proxy, DEFAULT_PROXY_TIMEOUT); uuid_helper->signal_id = g_signal_connect(G_DBUS_PROXY(uuid_helper->proxy), "g-properties-changed", G_CALLBACK(fu_bluez_device_signal_cb), uuid_helper); if (uuid_helper->signal_id <= 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot connect to signal of UUID %s", uuid_helper->uuid); return FALSE; } return TRUE; } static void fu_bluez_device_add_uuid_path(FuBluezDevice *self, const gchar *uuid, const gchar *path) { FuBluezDevicePrivate *priv = GET_PRIVATE(self); FuBluezDeviceUuidHelper *uuid_helper; g_return_if_fail(FU_IS_BLUEZ_DEVICE(self)); g_return_if_fail(uuid != NULL); g_return_if_fail(path != NULL); uuid_helper = g_new0(FuBluezDeviceUuidHelper, 1); uuid_helper->self = g_object_ref(self); uuid_helper->uuid = g_strdup(uuid); uuid_helper->path = g_strdup(path); g_hash_table_insert(priv->uuids, g_strdup(uuid), uuid_helper); } static void fu_bluez_device_set_modalias(FuBluezDevice *self, const gchar *modalias) { gsize modaliaslen = strlen(modalias); guint16 vid = 0x0; guint16 pid = 0x0; guint16 rev = 0x0; g_return_if_fail(modalias != NULL); /* usb:v0461p4EEFd0001 */ if (g_str_has_prefix(modalias, "usb:")) { fu_firmware_strparse_uint16_safe(modalias, modaliaslen, 5, &vid, NULL); fu_firmware_strparse_uint16_safe(modalias, modaliaslen, 10, &pid, NULL); fu_firmware_strparse_uint16_safe(modalias, modaliaslen, 15, &rev, NULL); /* bluetooth:v000ApFFFFdFFFF */ } else if (g_str_has_prefix(modalias, "bluetooth:")) { fu_firmware_strparse_uint16_safe(modalias, modaliaslen, 11, &vid, NULL); fu_firmware_strparse_uint16_safe(modalias, modaliaslen, 16, &pid, NULL); fu_firmware_strparse_uint16_safe(modalias, modaliaslen, 21, &rev, NULL); } /* add generated IDs */ if (vid != 0x0) fu_device_add_instance_u16(FU_DEVICE(self), "VID", vid); if (pid != 0x0) fu_device_add_instance_u16(FU_DEVICE(self), "PID", pid); fu_device_add_instance_u16(FU_DEVICE(self), "REV", rev); fu_device_build_instance_id_full(FU_DEVICE(self), FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "BLUETOOTH", "VID", NULL); fu_device_build_instance_id_full(FU_DEVICE(self), FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "BLUETOOTH", "VID", "PID", NULL); if (fu_device_has_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV)) { fu_device_build_instance_id_full(FU_DEVICE(self), FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "BLUETOOTH", "VID", "PID", "REV", NULL); } /* set vendor ID */ if (vid != 0x0) { g_autofree gchar *vendor_id = g_strdup_printf("BLUETOOTH:%04X", vid); fu_device_add_vendor_id(FU_DEVICE(self), vendor_id); } /* set version if the revision has been set */ if (rev != 0x0 && fu_device_get_version_format(FU_DEVICE(self)) == FWUPD_VERSION_FORMAT_UNKNOWN) { fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_BCD); fu_device_set_version_u16(FU_DEVICE(self), rev); } } static void fu_bluez_device_to_string(FuDevice *device, guint idt, GString *str) { FuBluezDevice *self = FU_BLUEZ_DEVICE(device); FuBluezDevicePrivate *priv = GET_PRIVATE(self); if (priv->uuids != NULL) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, priv->uuids); while (g_hash_table_iter_next(&iter, &key, &value)) { FuBluezDeviceUuidHelper *uuid_helper = (FuBluezDeviceUuidHelper *)value; fu_string_append(str, idt + 1, (const gchar *)key, uuid_helper->path); } } } /* * Returns the value of a property of an object specified by its path as * a GVariant, or NULL if the property wasn't found. */ static GVariant * fu_bluez_device_get_ble_property(const gchar *obj_path, const gchar *iface, const gchar *prop_name, GError **error) { g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GVariant) val = NULL; proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.bluez", obj_path, iface, NULL, error); if (proxy == NULL) { g_prefix_error(error, "failed to connect to %s: ", iface); return NULL; } g_dbus_proxy_set_default_timeout(proxy, DEFAULT_PROXY_TIMEOUT); val = g_dbus_proxy_get_cached_property(proxy, prop_name); if (val == NULL) { g_prefix_error(error, "property %s not found in %s: ", prop_name, obj_path); return NULL; } return g_steal_pointer(&val); } /* * Returns the value of the string property of an object specified by * its path, or NULL if the property wasn't found. * * The returned string must be freed using g_free(). */ static gchar * fu_bluez_device_get_ble_string_property(const gchar *obj_path, const gchar *iface, const gchar *prop_name, GError **error) { g_autoptr(GVariant) val = NULL; val = fu_bluez_device_get_ble_property(obj_path, iface, prop_name, error); if (val == NULL) return NULL; return g_variant_dup_string(val, NULL); } /* * Populates the {uuid_helper : object_path} entries of a device for all its * characteristics. * * TODO: Extend to services and descriptors too? */ static void fu_bluez_device_add_device_uuids(FuBluezDevice *self) { FuBluezDevicePrivate *priv = GET_PRIVATE(self); g_autolist(GDBusObject) obj_list = NULL; obj_list = g_dbus_object_manager_get_objects(priv->object_manager); for (GList *l = obj_list; l != NULL; l = l->next) { GDBusObject *obj = G_DBUS_OBJECT(l->data); g_autoptr(GDBusInterface) iface = NULL; g_autoptr(GError) error_local = NULL; g_autofree gchar *obj_uuid = NULL; const gchar *obj_path = g_dbus_object_get_object_path(obj); /* not us */ if (!g_str_has_prefix(g_dbus_object_get_object_path(obj), g_dbus_proxy_get_object_path(priv->proxy))) continue; /* * TODO: Currently restricted to getting UUIDs for * characteristics only, as the only use case we're * going to need for now is reading/writing * characteristics. */ iface = g_dbus_object_get_interface(obj, "org.bluez.GattCharacteristic1"); if (iface == NULL) continue; obj_uuid = fu_bluez_device_get_ble_string_property(obj_path, "org.bluez.GattCharacteristic1", "UUID", &error_local); if (obj_uuid == NULL) { g_debug("failed to get property: %s", error_local->message); continue; } fu_bluez_device_add_uuid_path(self, obj_uuid, obj_path); } } static gboolean fu_bluez_device_setup(FuDevice *device, GError **error) { FuBluezDevice *self = FU_BLUEZ_DEVICE(device); fu_bluez_device_add_device_uuids(self); return TRUE; } static gboolean fu_bluez_device_probe(FuDevice *device, GError **error) { FuBluezDevice *self = FU_BLUEZ_DEVICE(device); FuBluezDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GVariant) val_adapter = NULL; g_autoptr(GVariant) val_address = NULL; g_autoptr(GVariant) val_icon = NULL; g_autoptr(GVariant) val_modalias = NULL; g_autoptr(GVariant) val_name = NULL; val_address = g_dbus_proxy_get_cached_property(priv->proxy, "Address"); if (val_address == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No required BLE address"); return FALSE; } fu_device_set_logical_id(device, g_variant_get_string(val_address, NULL)); val_adapter = g_dbus_proxy_get_cached_property(priv->proxy, "Adapter"); if (val_adapter != NULL) fu_device_set_physical_id(device, g_variant_get_string(val_adapter, NULL)); val_name = g_dbus_proxy_get_cached_property(priv->proxy, "Name"); if (val_name != NULL) fu_device_set_name(device, g_variant_get_string(val_name, NULL)); val_icon = g_dbus_proxy_get_cached_property(priv->proxy, "Icon"); if (val_icon != NULL) fu_device_add_icon(device, g_variant_get_string(val_name, NULL)); val_modalias = g_dbus_proxy_get_cached_property(priv->proxy, "Modalias"); if (val_modalias != NULL) fu_bluez_device_set_modalias(self, g_variant_get_string(val_modalias, NULL)); /* success */ return TRUE; } /** * fu_bluez_device_read: * @self: a #FuBluezDevice * @uuid: the UUID, e.g. `00cde35c-7062-11eb-9439-0242ac130002` * @error: (nullable): optional return location for an error * * Reads from a UUID on the device. * * Returns: (transfer full): data, or %NULL for error * * Since: 1.5.7 **/ GByteArray * fu_bluez_device_read(FuBluezDevice *self, const gchar *uuid, GError **error) { FuBluezDeviceUuidHelper *uuid_helper; guint8 byte; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GVariantBuilder) builder = NULL; g_autoptr(GVariantIter) iter = NULL; g_autoptr(GVariant) val = NULL; uuid_helper = fu_bluez_device_get_uuid_helper(self, uuid, error); if (uuid_helper == NULL) return NULL; if (!fu_bluez_device_ensure_uuid_helper_proxy(uuid_helper, error)) return NULL; /* * Call the "ReadValue" method through the proxy synchronously. * * The method takes one argument: an array of dicts of * {string:value} specifying the options (here the option is * "offset":0. * The result is a byte array. */ builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(builder, "{sv}", "offset", g_variant_new("q", 0)); val = g_dbus_proxy_call_sync(uuid_helper->proxy, "ReadValue", g_variant_new("(a{sv})", builder), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (val == NULL) { g_prefix_error(error, "Failed to read GattCharacteristic1: "); return NULL; } g_variant_get(val, "(ay)", &iter); while (g_variant_iter_loop(iter, "y", &byte)) g_byte_array_append(buf, &byte, 1); /* success */ return g_steal_pointer(&buf); } /** * fu_bluez_device_read_string: * @self: a #FuBluezDevice * @uuid: the UUID, e.g. `00cde35c-7062-11eb-9439-0242ac130002` * @error: (nullable): optional return location for an error * * Reads a string from a UUID on the device. * * Returns: (transfer full): data, or %NULL for error * * Since: 1.5.7 **/ gchar * fu_bluez_device_read_string(FuBluezDevice *self, const gchar *uuid, GError **error) { GByteArray *buf = fu_bluez_device_read(self, uuid, error); if (buf == NULL) return NULL; return (gchar *)g_byte_array_free(buf, FALSE); } /** * fu_bluez_device_write: * @self: a #FuBluezDevice * @uuid: the UUID, e.g. `00cde35c-7062-11eb-9439-0242ac130002` * @buf: data array * @error: (nullable): optional return location for an error * * Writes to a UUID on the device. * * Returns: %TRUE if all the data was written * * Since: 1.5.7 **/ gboolean fu_bluez_device_write(FuBluezDevice *self, const gchar *uuid, GByteArray *buf, GError **error) { FuBluezDeviceUuidHelper *uuid_helper; g_autoptr(GVariantBuilder) opt_builder = NULL; g_autoptr(GVariantBuilder) val_builder = NULL; g_autoptr(GVariant) ret = NULL; GVariant *opt_variant = NULL; GVariant *val_variant = NULL; uuid_helper = fu_bluez_device_get_uuid_helper(self, uuid, error); if (uuid_helper == NULL) return FALSE; if (!fu_bluez_device_ensure_uuid_helper_proxy(uuid_helper, error)) return FALSE; /* build the value variant */ val_builder = g_variant_builder_new(G_VARIANT_TYPE("ay")); for (gsize i = 0; i < buf->len; i++) g_variant_builder_add(val_builder, "y", buf->data[i]); val_variant = g_variant_new("ay", val_builder); /* build the options variant (offset = 0) */ opt_builder = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(opt_builder, "{sv}", "offset", g_variant_new_uint16(0)); opt_variant = g_variant_new("a{sv}", opt_builder); ret = g_dbus_proxy_call_sync(uuid_helper->proxy, "WriteValue", g_variant_new("(@ay@a{sv})", val_variant, opt_variant), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (ret == NULL) { g_prefix_error(error, "Failed to write GattCharacteristic1: "); return FALSE; } /* success */ return TRUE; } /** * fu_bluez_device_notify_start: * @self: a #FuBluezDevice * @uuid: the UUID, e.g. `00cde35c-7062-11eb-9439-0242ac130002` * @error: (nullable): optional return location for an error * * Enables notifications for property changes in a UUID (StartNotify * method). * * Returns: %TRUE if the method call completed successfully. * * Since: 1.5.8 **/ gboolean fu_bluez_device_notify_start(FuBluezDevice *self, const gchar *uuid, GError **error) { FuBluezDeviceUuidHelper *uuid_helper; g_autoptr(GVariant) retval = NULL; uuid_helper = fu_bluez_device_get_uuid_helper(self, uuid, error); if (uuid_helper == NULL) return FALSE; if (!fu_bluez_device_ensure_uuid_helper_proxy(uuid_helper, error)) return FALSE; retval = g_dbus_proxy_call_sync(uuid_helper->proxy, "StartNotify", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (retval == NULL) { g_prefix_error(error, "Failed to enable notifications: "); return FALSE; } return TRUE; } /** * fu_bluez_device_notify_stop: * @self: a #FuBluezDevice * @uuid: the UUID, e.g. `00cde35c-7062-11eb-9439-0242ac130002` * @error: (nullable): optional return location for an error * * Disables notifications for property changes in a UUID (StopNotify * method). * * Returns: %TRUE if the method call completed successfully. * * Since: 1.5.8 **/ gboolean fu_bluez_device_notify_stop(FuBluezDevice *self, const gchar *uuid, GError **error) { FuBluezDeviceUuidHelper *uuid_helper; g_autoptr(GVariant) retval = NULL; uuid_helper = fu_bluez_device_get_uuid_helper(self, uuid, error); if (uuid_helper == NULL) return FALSE; if (!fu_bluez_device_ensure_uuid_helper_proxy(uuid_helper, error)) return FALSE; retval = g_dbus_proxy_call_sync(uuid_helper->proxy, "StopNotify", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (retval == NULL) { g_prefix_error(error, "Failed to enable notifications: "); return FALSE; } return TRUE; } static void fu_bluez_device_incorporate(FuDevice *self, FuDevice *donor) { FuBluezDevice *uself = FU_BLUEZ_DEVICE(self); FuBluezDevice *udonor = FU_BLUEZ_DEVICE(donor); FuBluezDevicePrivate *priv = GET_PRIVATE(uself); FuBluezDevicePrivate *privdonor = GET_PRIVATE(udonor); g_return_if_fail(FU_IS_BLUEZ_DEVICE(self)); g_return_if_fail(FU_IS_BLUEZ_DEVICE(donor)); if (g_hash_table_size(priv->uuids) == 0) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, privdonor->uuids); while (g_hash_table_iter_next(&iter, &key, &value)) { FuBluezDeviceUuidHelper *uuid_helper = (FuBluezDeviceUuidHelper *)value; fu_bluez_device_add_uuid_path(uself, (const gchar *)key, uuid_helper->path); } } if (priv->object_manager == NULL) priv->object_manager = g_object_ref(privdonor->object_manager); if (priv->proxy == NULL) priv->proxy = g_object_ref(privdonor->proxy); } static void fu_bluez_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuBluezDevice *self = FU_BLUEZ_DEVICE(object); FuBluezDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_OBJECT_MANAGER: g_value_set_object(value, priv->object_manager); break; case PROP_PROXY: g_value_set_object(value, priv->proxy); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_bluez_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuBluezDevice *self = FU_BLUEZ_DEVICE(object); FuBluezDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_OBJECT_MANAGER: priv->object_manager = g_value_dup_object(value); break; case PROP_PROXY: priv->proxy = g_value_dup_object(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_bluez_device_finalize(GObject *object) { FuBluezDevice *self = FU_BLUEZ_DEVICE(object); FuBluezDevicePrivate *priv = GET_PRIVATE(self); g_hash_table_unref(priv->uuids); g_object_unref(priv->proxy); g_object_unref(priv->object_manager); G_OBJECT_CLASS(fu_bluez_device_parent_class)->finalize(object); } static void fu_bluez_device_init(FuBluezDevice *self) { FuBluezDevicePrivate *priv = GET_PRIVATE(self); priv->uuids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)fu_bluez_uuid_free); } static void fu_bluez_device_class_init(FuBluezDeviceClass *klass) { FuDeviceClass *device_class = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->get_property = fu_bluez_device_get_property; object_class->set_property = fu_bluez_device_set_property; object_class->finalize = fu_bluez_device_finalize; device_class->probe = fu_bluez_device_probe; device_class->setup = fu_bluez_device_setup; device_class->to_string = fu_bluez_device_to_string; device_class->incorporate = fu_bluez_device_incorporate; /** * FuBluezDevice::changed: * @self: the #FuBluezDevice instance that emitted the signal * @uuid: the UUID that changed * * The ::changed signal is emitted when a service with a specific UUID changed. * * Since: 1.5.8 **/ signals[SIGNAL_CHANGED] = g_signal_new("changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); /** * FuBluezDevice:object-manager: * * The object manager instance for all devices. * * Since: 1.5.8 */ pspec = g_param_spec_object("object-manager", NULL, NULL, G_TYPE_DBUS_OBJECT_MANAGER, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_OBJECT_MANAGER, pspec); /** * FuBluezDevice:proxy: * * The D-Bus proxy for the object. * * Since: 1.5.8 */ pspec = g_param_spec_object("proxy", NULL, NULL, G_TYPE_DBUS_PROXY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PROXY, pspec); } fwupd-1.9.16/libfwupdplugin/fu-bluez-device.h000066400000000000000000000017131460375044200211360ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-device.h" #define FU_TYPE_BLUEZ_DEVICE (fu_bluez_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuBluezDevice, fu_bluez_device, FU, BLUEZ_DEVICE, FuDevice) struct _FuBluezDeviceClass { FuDeviceClass parent_class; }; GByteArray * fu_bluez_device_read(FuBluezDevice *self, const gchar *uuid, GError **error) G_GNUC_NON_NULL(1, 2); gchar * fu_bluez_device_read_string(FuBluezDevice *self, const gchar *uuid, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_bluez_device_write(FuBluezDevice *self, const gchar *uuid, GByteArray *buf, GError **error) G_GNUC_NON_NULL(1, 2, 3); gboolean fu_bluez_device_notify_start(FuBluezDevice *self, const gchar *uuid, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_bluez_device_notify_stop(FuBluezDevice *self, const gchar *uuid, GError **error) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-byte-array.c000066400000000000000000000122401460375044200206270ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include "fu-byte-array.h" #include "fu-common.h" #include "fu-firmware-common.h" #include "fu-mem.h" /** * fu_byte_array_to_string: * @array: a #GByteArray * * Converts the byte array to a lowercase hex string. * * Returns: (transfer full): a string, which may be zero length * * Since: 1.8.9 **/ gchar * fu_byte_array_to_string(GByteArray *array) { g_autoptr(GString) str = g_string_new(NULL); g_return_val_if_fail(array != NULL, NULL); for (guint i = 0; i < array->len; i++) g_string_append_printf(str, "%02x", array->data[i]); return g_string_free(g_steal_pointer(&str), FALSE); } /** * fu_byte_array_from_string: * @str: a hex string * @error: (nullable): optional return location for an error * * Converts a lowercase hex string to a byte array. * * Returns: (transfer full): a #GByteArray, or %NULL on error * * Since: 1.9.6 **/ GByteArray * fu_byte_array_from_string(const gchar *str, GError **error) { gsize strsz; g_autoptr(GByteArray) buf = g_byte_array_new(); g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); strsz = strlen(str); for (guint i = 0; i < strsz; i += 2) { guint8 value = 0; if (!fu_firmware_strparse_uint8_safe(str, strsz, i, &value, error)) return NULL; fu_byte_array_append_uint8(buf, value); } return g_steal_pointer(&buf); } /** * fu_byte_array_append_uint8: * @array: a #GByteArray * @data: value * * Adds a 8 bit integer to a byte array. * * Since: 1.3.1 **/ void fu_byte_array_append_uint8(GByteArray *array, guint8 data) { g_byte_array_append(array, &data, sizeof(data)); } /** * fu_byte_array_append_uint16: * @array: a #GByteArray * @data: value * @endian: endian type, e.g. #G_LITTLE_ENDIAN * * Adds a 16 bit integer to a byte array. * * Since: 1.3.1 **/ void fu_byte_array_append_uint16(GByteArray *array, guint16 data, FuEndianType endian) { guint8 buf[2]; fu_memwrite_uint16(buf, data, endian); g_byte_array_append(array, buf, sizeof(buf)); } /** * fu_byte_array_append_uint24: * @array: a #GByteArray * @data: value * @endian: endian type, e.g. #G_LITTLE_ENDIAN * * Adds a 24 bit integer to a byte array. * * Since: 1.8.13 **/ void fu_byte_array_append_uint24(GByteArray *array, guint32 data, FuEndianType endian) { guint8 buf[3]; fu_memwrite_uint24(buf, data, endian); g_byte_array_append(array, buf, sizeof(buf)); } /** * fu_byte_array_append_uint32: * @array: a #GByteArray * @data: value * @endian: endian type, e.g. #G_LITTLE_ENDIAN * * Adds a 32 bit integer to a byte array. * * Since: 1.3.1 **/ void fu_byte_array_append_uint32(GByteArray *array, guint32 data, FuEndianType endian) { guint8 buf[4]; fu_memwrite_uint32(buf, data, endian); g_byte_array_append(array, buf, sizeof(buf)); } /** * fu_byte_array_append_uint64: * @array: a #GByteArray * @data: value * @endian: endian type, e.g. #G_LITTLE_ENDIAN * * Adds a 64 bit integer to a byte array. * * Since: 1.5.8 **/ void fu_byte_array_append_uint64(GByteArray *array, guint64 data, FuEndianType endian) { guint8 buf[8]; fu_memwrite_uint64(buf, data, endian); g_byte_array_append(array, buf, sizeof(buf)); } /** * fu_byte_array_append_bytes: * @array: a #GByteArray * @bytes: data blob * * Adds the contents of a GBytes to a byte array. * * Since: 1.5.8 **/ void fu_byte_array_append_bytes(GByteArray *array, GBytes *bytes) { g_byte_array_append(array, g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes)); } /** * fu_byte_array_set_size: * @array: a #GByteArray * @length: the new size of the GByteArray * @data: the byte used to pad the array * * Sets the size of the GByteArray, expanding with @data as required. * * Since: 1.8.2 **/ void fu_byte_array_set_size(GByteArray *array, guint length, guint8 data) { guint oldlength = array->len; g_byte_array_set_size(array, length); if (length > oldlength) memset(array->data + oldlength, data, length - oldlength); } /** * fu_byte_array_align_up: * @array: a #GByteArray * @alignment: align to this power of 2 * @data: the byte used to pad the array * * Align a byte array length to a power of 2 boundary, where @alignment is the * bit position to align to. If @alignment is zero then @array is unchanged. * * Since: 1.6.0 **/ void fu_byte_array_align_up(GByteArray *array, guint8 alignment, guint8 data) { fu_byte_array_set_size(array, fu_common_align_up(array->len, alignment), data); } /** * fu_byte_array_compare: * @buf1: a data blob * @buf2: another #GByteArray * @error: (nullable): optional return location for an error * * Compares two buffers for equality. * * Returns: %TRUE if @buf1 and @buf2 are identical * * Since: 1.8.0 **/ gboolean fu_byte_array_compare(GByteArray *buf1, GByteArray *buf2, GError **error) { g_return_val_if_fail(buf1 != NULL, FALSE); g_return_val_if_fail(buf2 != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return fu_memcmp_safe(buf1->data, buf1->len, 0x0, buf2->data, buf2->len, 0x0, MAX(buf1->len, buf2->len), error); } fwupd-1.9.16/libfwupdplugin/fu-byte-array.h000066400000000000000000000023351460375044200206400ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-common.h" gchar * fu_byte_array_to_string(GByteArray *array) G_GNUC_NON_NULL(1); GByteArray * fu_byte_array_from_string(const gchar *str, GError **error) G_GNUC_NON_NULL(1); void fu_byte_array_set_size(GByteArray *array, guint length, guint8 data) G_GNUC_NON_NULL(1); void fu_byte_array_align_up(GByteArray *array, guint8 alignment, guint8 data) G_GNUC_NON_NULL(1); void fu_byte_array_append_uint8(GByteArray *array, guint8 data) G_GNUC_NON_NULL(1); void fu_byte_array_append_uint16(GByteArray *array, guint16 data, FuEndianType endian) G_GNUC_NON_NULL(1); void fu_byte_array_append_uint24(GByteArray *array, guint32 data, FuEndianType endian) G_GNUC_NON_NULL(1); void fu_byte_array_append_uint32(GByteArray *array, guint32 data, FuEndianType endian) G_GNUC_NON_NULL(1); void fu_byte_array_append_uint64(GByteArray *array, guint64 data, FuEndianType endian) G_GNUC_NON_NULL(1); void fu_byte_array_append_bytes(GByteArray *array, GBytes *bytes) G_GNUC_NON_NULL(1, 2); gboolean fu_byte_array_compare(GByteArray *buf1, GByteArray *buf2, GError **error) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-bytes.c000066400000000000000000000261101460375044200176770ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #ifdef HAVE_GIO_UNIX #include #endif #include "fwupd-error.h" #include "fu-bytes.h" #include "fu-common.h" #include "fu-mem.h" /** * fu_bytes_set_contents: * @filename: a filename * @bytes: data to write * @error: (nullable): optional return location for an error * * Writes a blob of data to a filename, creating the parent directories as * required. * * Returns: %TRUE for success * * Since: 1.8.2 **/ gboolean fu_bytes_set_contents(const gchar *filename, GBytes *bytes, GError **error) { const gchar *data; gsize size; g_autoptr(GFile) file = NULL; g_autoptr(GFile) file_parent = NULL; g_return_val_if_fail(filename != NULL, FALSE); g_return_val_if_fail(bytes != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); file = g_file_new_for_path(filename); file_parent = g_file_get_parent(file); if (!g_file_query_exists(file_parent, NULL)) { if (!g_file_make_directory_with_parents(file_parent, NULL, error)) return FALSE; } data = g_bytes_get_data(bytes, &size); g_debug("writing %s with 0x%x bytes", filename, (guint)size); return g_file_set_contents(filename, data, size, error); } /** * fu_bytes_get_contents: * @filename: a filename * @error: (nullable): optional return location for an error * * Reads a blob of data from a file. * * Returns: a #GBytes, or %NULL for failure * * Since: 1.8.2 **/ GBytes * fu_bytes_get_contents(const gchar *filename, GError **error) { g_autoptr(GError) error_local = NULL; g_autoptr(GMappedFile) mapped_file = NULL; g_return_val_if_fail(filename != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* try as a mapped file, falling back to reading it as a blob instead */ mapped_file = g_mapped_file_new(filename, FALSE, &error_local); if (mapped_file == NULL || g_mapped_file_get_length(mapped_file) == 0) { gchar *data = NULL; gsize len = 0; if (!g_file_get_contents(filename, &data, &len, error)) return NULL; g_debug("failed to read as mapped file, " "so reading %s of size 0x%x: %s", filename, (guint)len, error_local != NULL ? error_local->message : "zero size"); return g_bytes_new_take(data, len); } g_debug("mapped file %s of size 0x%x", filename, (guint)g_mapped_file_get_length(mapped_file)); return g_mapped_file_get_bytes(mapped_file); } /** * fu_bytes_get_contents_fd: * @fd: a file descriptor * @count: the maximum number of bytes to read * @error: (nullable): optional return location for an error * * Reads a blob from a specific file descriptor. * * Note: this will close the fd when done * * Returns: (transfer full): a #GBytes, or %NULL * * Since: 1.8.2 **/ GBytes * fu_bytes_get_contents_fd(gint fd, gsize count, GError **error) { #ifdef HAVE_GIO_UNIX g_autoptr(GInputStream) stream = NULL; g_return_val_if_fail(fd > 0, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* read the entire fd to a data blob */ stream = g_unix_input_stream_new(fd, TRUE); return fu_bytes_get_contents_stream(stream, count, error); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); return NULL; #endif } /** * fu_bytes_get_contents_stream: * @stream: input stream * @count: the maximum number of bytes to read * @error: (nullable): optional return location for an error * * Reads a blob from a specific input stream. * * Returns: (transfer full): a #GBytes, or %NULL * * Since: 1.8.2 **/ GBytes * fu_bytes_get_contents_stream(GInputStream *stream, gsize count, GError **error) { guint8 tmp[0x8000] = {0x0}; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GError) error_local = NULL; g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* this is invalid */ if (count == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "A maximum read size must be specified"); return NULL; } /* read from stream in 32kB chunks */ while (TRUE) { gssize sz; sz = g_input_stream_read(stream, tmp, sizeof(tmp), NULL, &error_local); if (sz == 0) break; if (sz < 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, error_local->message); return NULL; } g_byte_array_append(buf, tmp, sz); if (buf->len > count) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot read from fd: 0x%x > 0x%x", buf->len, (guint)count); return NULL; } } return g_bytes_new(buf->data, buf->len); } /** * fu_bytes_get_contents_stream_full: * @stream: input stream * @offset: the offset to read from * @count: the maximum number of bytes to read up to * @error: (nullable): optional return location for an error * * Reads a blob from a specific input stream. * * NOTE: if the input stream can provide more data than @offset+@count it will be truncated. * * Returns: (transfer full): a #GBytes, or %NULL * * Since: 1.9.1 **/ GBytes * fu_bytes_get_contents_stream_full(GInputStream *stream, gsize offset, gsize count, GError **error) { guint8 tmp[0x8000] = {0x0}; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GError) error_local = NULL; g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* this is invalid */ if (count == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "A maximum read size must be specified"); return NULL; } /* seek */ if (offset > 0) { if (!g_seekable_can_seek(G_SEEKABLE(stream))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "input stream is not seekable"); return NULL; } if (!g_seekable_seek(G_SEEKABLE(stream), offset, G_SEEK_CUR, NULL, error)) return NULL; } /* read from stream in 32kB chunks */ while (TRUE) { gssize sz; sz = g_input_stream_read(stream, tmp, sizeof(tmp), NULL, &error_local); if (sz == 0) break; if (sz < 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, error_local->message); return NULL; } g_byte_array_append(buf, tmp, sz); if (buf->len >= count) break; } return g_bytes_new(buf->data, buf->len); } /** * fu_bytes_align: * @bytes: data blob * @blksz: block size in bytes * @padval: the byte used to pad the byte buffer * * Aligns a block of memory to @blksize using the @padval value; if * the block is already aligned then the original @bytes is returned. * * Returns: (transfer full): a #GBytes, possibly @bytes * * Since: 1.8.2 **/ GBytes * fu_bytes_align(GBytes *bytes, gsize blksz, gchar padval) { const guint8 *data; gsize sz; g_return_val_if_fail(bytes != NULL, NULL); g_return_val_if_fail(blksz > 0, NULL); /* pad */ data = g_bytes_get_data(bytes, &sz); if (sz % blksz != 0) { gsize sz_align = ((sz / blksz) + 1) * blksz; guint8 *data_align = g_malloc(sz_align); memcpy(data_align, data, sz); memset(data_align + sz, padval, sz_align - sz); g_debug("aligning 0x%x bytes to 0x%x", (guint)sz, (guint)sz_align); return g_bytes_new_take(data_align, sz_align); } /* perfectly aligned */ return g_bytes_ref(bytes); } /** * fu_bytes_is_empty: * @bytes: data blob * * Checks if a byte array are just empty (0xff) bytes. * * Returns: %TRUE if @bytes is empty * * Since: 1.8.2 **/ gboolean fu_bytes_is_empty(GBytes *bytes) { gsize sz = 0; const guint8 *buf = g_bytes_get_data(bytes, &sz); for (gsize i = 0; i < sz; i++) { if (buf[i] != 0xff) return FALSE; } return TRUE; } /** * fu_bytes_compare: * @bytes1: a data blob * @bytes2: another #GBytes * @error: (nullable): optional return location for an error * * Compares the buffers for equality. * * Returns: %TRUE if @bytes1 and @bytes2 are identical * * Since: 1.8.2 **/ gboolean fu_bytes_compare(GBytes *bytes1, GBytes *bytes2, GError **error) { const guint8 *buf1; const guint8 *buf2; gsize bufsz1; gsize bufsz2; g_return_val_if_fail(bytes1 != NULL, FALSE); g_return_val_if_fail(bytes2 != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); buf1 = g_bytes_get_data(bytes1, &bufsz1); buf2 = g_bytes_get_data(bytes2, &bufsz2); return fu_memcmp_safe(buf1, bufsz1, 0x0, buf2, bufsz2, 0x0, MAX(bufsz1, bufsz2), error); } /** * fu_bytes_pad: * @bytes: data blob * @sz: the desired size in bytes * * Pads a GBytes to a minimum @sz with `0xff`. * * Returns: (transfer full): a data blob * * Since: 1.8.2 **/ GBytes * fu_bytes_pad(GBytes *bytes, gsize sz) { gsize bytes_sz; g_return_val_if_fail(bytes != NULL, NULL); g_return_val_if_fail(sz != 0, NULL); /* pad */ bytes_sz = g_bytes_get_size(bytes); if (bytes_sz < sz) { const guint8 *data = g_bytes_get_data(bytes, NULL); guint8 *data_new = g_malloc(sz); if (data != NULL) memcpy(data_new, data, bytes_sz); memset(data_new + bytes_sz, 0xff, sz - bytes_sz); return g_bytes_new_take(data_new, sz); } /* not required */ return g_bytes_ref(bytes); } /** * fu_bytes_new_offset: * @bytes: data blob * @offset: where subsection starts at * @length: length of subsection * @error: (nullable): optional return location for an error * * Creates a #GBytes which is a subsection of another #GBytes. * * Returns: (transfer full): a #GBytes, or #NULL if range is invalid * * Since: 1.8.2 **/ GBytes * fu_bytes_new_offset(GBytes *bytes, gsize offset, gsize length, GError **error) { g_return_val_if_fail(bytes != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* optimize */ if (offset == 0x0 && g_bytes_get_size(bytes) == length) return g_bytes_ref(bytes); /* sanity check */ if (offset + length < length || offset + length > g_bytes_get_size(bytes)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot create bytes @0x%02x for 0x%02x " "as buffer only 0x%04x bytes in size", (guint)offset, (guint)length, (guint)g_bytes_get_size(bytes)); return NULL; } return g_bytes_new_from_bytes(bytes, offset, length); } /** * fu_bytes_get_data_safe: * @bytes: data blob * @bufsz: (out) (optional): location to return size of byte data * @error: (nullable): optional return location for an error * * Get the byte data in the #GBytes. This data should not be modified. * This function will always return the same pointer for a given #GBytes. * * If the size of @bytes is zero, then %NULL is returned and the @error is set, * which differs in behavior to that of g_bytes_get_data(). * * This may be useful when calling g_mapped_file_new() on a zero-length file. * * Returns: a pointer to the byte data, or %NULL. * * Since: 1.6.0 **/ const guint8 * fu_bytes_get_data_safe(GBytes *bytes, gsize *bufsz, GError **error) { const guint8 *buf = g_bytes_get_data(bytes, bufsz); if (buf == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid data"); return NULL; } return buf; } fwupd-1.9.16/libfwupdplugin/fu-bytes.h000066400000000000000000000025741460375044200177140ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gboolean fu_bytes_set_contents(const gchar *filename, GBytes *bytes, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GBytes * fu_bytes_get_contents(const gchar *filename, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GBytes * fu_bytes_get_contents_stream(GInputStream *stream, gsize count, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GBytes * fu_bytes_get_contents_stream_full(GInputStream *stream, gsize offset, gsize count, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GBytes * fu_bytes_get_contents_fd(gint fd, gsize count, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes * fu_bytes_align(GBytes *bytes, gsize blksz, gchar padval) G_GNUC_NON_NULL(1); const guint8 * fu_bytes_get_data_safe(GBytes *bytes, gsize *bufsz, GError **error) G_GNUC_NON_NULL(1); gboolean fu_bytes_is_empty(GBytes *bytes) G_GNUC_NON_NULL(1); gboolean fu_bytes_compare(GBytes *bytes1, GBytes *bytes2, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GBytes * fu_bytes_pad(GBytes *bytes, gsize sz) G_GNUC_NON_NULL(1); GBytes * fu_bytes_new_offset(GBytes *bytes, gsize offset, gsize length, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-cab-firmware.c000066400000000000000000000605661460375044200211250ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCabFirmware" #include "config.h" #include #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-cab-firmware.h" #include "fu-cab-image.h" #include "fu-cab-struct.h" #include "fu-chunk-array.h" #include "fu-mem-private.h" #include "fu-string.h" typedef struct { gboolean compressed; gboolean only_basename; } FuCabFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuCabFirmware, fu_cab_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_cab_firmware_get_instance_private(o)) #define FU_CAB_FIRMWARE_MAX_FILES 1024 #define FU_CAB_FIRMWARE_MAX_FOLDERS 64 /** * fu_cab_firmware_get_compressed: * @self: a #FuCabFirmware * * Gets if the cabinet archive should be compressed. * * Returns: boolean * * Since: 1.9.7 **/ gboolean fu_cab_firmware_get_compressed(FuCabFirmware *self) { FuCabFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CAB_FIRMWARE(self), FALSE); return priv->compressed; } /** * fu_cab_firmware_set_compressed: * @self: a #FuCabFirmware * @compressed: boolean * * Sets if the cabinet archive should be compressed. * * Since: 1.9.7 **/ void fu_cab_firmware_set_compressed(FuCabFirmware *self, gboolean compressed) { FuCabFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CAB_FIRMWARE(self)); priv->compressed = compressed; } /** * fu_cab_firmware_get_only_basename: * @self: a #FuCabFirmware * * Gets if the cabinet archive filenames should have the path component removed. * * Returns: boolean * * Since: 1.9.7 **/ gboolean fu_cab_firmware_get_only_basename(FuCabFirmware *self) { FuCabFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CAB_FIRMWARE(self), FALSE); return priv->only_basename; } /** * fu_cab_firmware_set_only_basename: * @self: a #FuCabFirmware * @only_basename: boolean * * Sets if the cabinet archive filenames should have the path component removed. * * Since: 1.9.7 **/ void fu_cab_firmware_set_only_basename(FuCabFirmware *self, gboolean only_basename) { FuCabFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CAB_FIRMWARE(self)); priv->only_basename = only_basename; } typedef struct { GBytes *fw; FwupdInstallFlags install_flags; gsize rsvd_folder; gsize rsvd_block; gsize size_total; FuCabCompression compression; GPtrArray *folder_data; /* of GBytes */ z_stream zstrm; } FuCabFirmwareParseHelper; static void fu_cab_firmware_parse_helper_free(FuCabFirmwareParseHelper *helper) { inflateEnd(&helper->zstrm); if (helper->fw != NULL) g_bytes_unref(helper->fw); if (helper->folder_data != NULL) g_ptr_array_unref(helper->folder_data); g_free(helper); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCabFirmwareParseHelper, fu_cab_firmware_parse_helper_free) /* compute the MS cabinet checksum */ static guint32 fu_cab_firmware_compute_checksum(const guint8 *buf, gsize bufsz, guint32 seed) { guint32 csum = seed; for (gsize i = 0; i < bufsz; i += 4) { guint32 ul = 0; guint chunksz = MIN(bufsz - i, 4); if (chunksz == 4) { ul = fu_memread_uint32(buf + i, G_LITTLE_ENDIAN); } else if (chunksz == 3) { ul = fu_memread_uint24(buf + i, G_BIG_ENDIAN); /* err.. */ } else if (chunksz == 2) { ul = fu_memread_uint16(buf + i, G_BIG_ENDIAN); /* err.. */ } else if (chunksz == 1) { ul = buf[i]; } csum ^= ul; } return csum; } static voidpf zalloc(voidpf opaque, uInt items, uInt size) { return g_malloc0_n(items, size); } static void zfree(voidpf opaque, voidpf address) { g_free(address); } typedef z_stream z_stream_deflater; static void zstream_deflater_free(z_stream_deflater *zstrm) { deflateEnd(zstrm); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(z_stream_deflater, zstream_deflater_free) static gboolean fu_cab_firmware_parse_data(FuCabFirmware *self, FuCabFirmwareParseHelper *helper, gsize *offset, GByteArray *folder_data, GError **error) { gsize blob_comp; gsize blob_uncomp; gsize hdr_sz; gsize size_max = fu_firmware_get_size_max(FU_FIRMWARE(self)); g_autoptr(GByteArray) st = NULL; g_autoptr(GBytes) data_blob = NULL; /* parse header */ st = fu_struct_cab_data_parse_bytes(helper->fw, *offset, error); if (st == NULL) return FALSE; /* sanity check */ blob_comp = fu_struct_cab_data_get_comp(st); blob_uncomp = fu_struct_cab_data_get_uncomp(st); if (helper->compression == FU_CAB_COMPRESSION_NONE && blob_comp != blob_uncomp) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "mismatched compressed data"); return FALSE; } helper->size_total += blob_uncomp; if (size_max > 0 && helper->size_total > size_max) { g_autofree gchar *sz_val = g_format_size(helper->size_total); g_autofree gchar *sz_max = g_format_size(size_max); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "uncompressed data too large (%s, limit %s)", sz_val, sz_max); return FALSE; } hdr_sz = st->len + helper->rsvd_block; data_blob = fu_bytes_new_offset(helper->fw, *offset + hdr_sz, blob_comp, error); if (data_blob == NULL) return FALSE; /* verify checksum */ if ((helper->install_flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint32 checksum = fu_struct_cab_data_get_checksum(st); if (checksum != 0) { guint32 checksum_actual = fu_cab_firmware_compute_checksum(g_bytes_get_data(data_blob, NULL), g_bytes_get_size(data_blob), 0); g_autoptr(GByteArray) hdr = g_byte_array_new(); fu_byte_array_append_uint16(hdr, blob_comp, G_LITTLE_ENDIAN); fu_byte_array_append_uint16(hdr, blob_uncomp, G_LITTLE_ENDIAN); checksum_actual = fu_cab_firmware_compute_checksum(hdr->data, hdr->len, checksum_actual); if (checksum_actual != checksum) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "invalid checksum at 0x%x, expected 0x%x, got 0x%x", (guint)*offset, checksum, checksum_actual); return FALSE; } } } /* decompress Zlib data after removing *another *header... */ if (helper->compression == FU_CAB_COMPRESSION_MSZIP) { guint8 tmp[0x4000] = {0x0}; int zret; g_autofree gchar *kind = NULL; g_autoptr(GByteArray) buf = g_byte_array_new(); /* check compressed header */ kind = fu_memstrsafe(g_bytes_get_data(data_blob, NULL), g_bytes_get_size(data_blob), 0x0, 2, error); if (kind == NULL) return FALSE; if (g_strcmp0(kind, "CK") != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "compressed header invalid: %s", kind); return FALSE; } helper->zstrm.avail_in = g_bytes_get_size(data_blob) - 2; helper->zstrm.next_in = (z_const Bytef *)g_bytes_get_data(data_blob, NULL) + 2; while (1) { helper->zstrm.avail_out = sizeof(tmp); helper->zstrm.next_out = tmp; zret = inflate(&helper->zstrm, Z_BLOCK); if (zret == Z_STREAM_END) break; g_byte_array_append(buf, tmp, sizeof(tmp) - helper->zstrm.avail_out); if (zret != Z_OK) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "inflate error @0x%x: %s", (guint)*offset, zError(zret)); return FALSE; } } zret = inflateReset(&helper->zstrm); if (zret != Z_OK) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to reset inflate: %s", zError(zret)); return FALSE; } zret = inflateSetDictionary(&helper->zstrm, buf->data, buf->len); if (zret != Z_OK) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to set inflate dictionary: %s", zError(zret)); return FALSE; } g_byte_array_append(folder_data, buf->data, buf->len); } else { fu_byte_array_append_bytes(folder_data, data_blob); } /* success */ *offset += blob_comp + hdr_sz; return TRUE; } static gboolean fu_cab_firmware_parse_folder(FuCabFirmware *self, FuCabFirmwareParseHelper *helper, guint idx, gsize offset, GByteArray *folder_data, GError **error) { FuCabFirmwarePrivate *priv = GET_PRIVATE(self); gsize offset_folder; g_autoptr(GByteArray) st = NULL; /* parse header */ st = fu_struct_cab_folder_parse_bytes(helper->fw, offset, error); if (st == NULL) return FALSE; /* sanity check */ if (fu_struct_cab_folder_get_ndatab(st) == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no CFDATA blocks"); return FALSE; } helper->compression = fu_struct_cab_folder_get_compression(st); if (helper->compression != FU_CAB_COMPRESSION_NONE) priv->compressed = TRUE; if (helper->compression != FU_CAB_COMPRESSION_NONE && helper->compression != FU_CAB_COMPRESSION_MSZIP) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "compression %s not supported", fu_cab_compression_to_string(helper->compression)); return FALSE; } /* parse CDATA */ offset_folder = fu_struct_cab_folder_get_offset(st); for (guint i = 0; i < fu_struct_cab_folder_get_ndatab(st); i++) { if (!fu_cab_firmware_parse_data(self, helper, &offset_folder, folder_data, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_cab_firmware_parse_file(FuCabFirmware *self, FuCabFirmwareParseHelper *helper, gsize *offset, GError **error) { FuCabFirmwarePrivate *priv = GET_PRIVATE(self); GBytes *folder_data; gsize bufsz = 0; guint16 date; guint16 index; guint16 time; const guint8 *buf = g_bytes_get_data(helper->fw, &bufsz); g_autoptr(FuCabImage) img = fu_cab_image_new(); g_autoptr(GByteArray) st = NULL; g_autoptr(GBytes) img_blob = NULL; g_autoptr(GDateTime) created = NULL; g_autoptr(GString) filename = g_string_new(NULL); g_autoptr(GTimeZone) tz_utc = g_time_zone_new_utc(); /* parse header */ st = fu_struct_cab_file_parse_bytes(helper->fw, *offset, error); if (st == NULL) return FALSE; /* sanity check */ index = fu_struct_cab_file_get_index(st); if (index >= helper->folder_data->len) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to get folder data for 0x%x", index); return FALSE; } folder_data = g_ptr_array_index(helper->folder_data, index); /* parse filename */ *offset += FU_STRUCT_CAB_FILE_SIZE; for (guint i = 0; i < 255; i++) { guint8 value = 0; if (!fu_memread_uint8_safe(buf, bufsz, *offset + i, &value, error)) return FALSE; if (value == 0) break; /* convert to UNIX path */ if (value == '\\') value = '/'; g_string_append_c(filename, (gchar)value); } /* add image */ if (priv->only_basename) { g_autofree gchar *id = g_path_get_basename(filename->str); fu_firmware_set_id(FU_FIRMWARE(img), id); } else { fu_firmware_set_id(FU_FIRMWARE(img), filename->str); } fu_firmware_add_image(FU_FIRMWARE(self), FU_FIRMWARE(img)); img_blob = fu_bytes_new_offset(folder_data, fu_struct_cab_file_get_uoffset(st), fu_struct_cab_file_get_usize(st), error); if (img_blob == NULL) return FALSE; fu_firmware_set_bytes(FU_FIRMWARE(img), img_blob); /* set created date time */ date = fu_struct_cab_file_get_date(st); time = fu_struct_cab_file_get_time(st); created = g_date_time_new(tz_utc, 1980 + ((date & 0xFE00) >> 9), (date & 0x01E0) >> 5, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5, (time & 0x001F) * 2); fu_cab_image_set_created(img, created); /* offset to next entry */ *offset += filename->len + 1; return TRUE; } static gboolean fu_cab_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_cab_header_validate_bytes(fw, offset, error); } static FuCabFirmwareParseHelper * fu_cab_firmware_parse_helper_new(GBytes *fw, FwupdInstallFlags flags, GError **error) { int zret; g_autoptr(FuCabFirmwareParseHelper) helper = g_new0(FuCabFirmwareParseHelper, 1); /* zlib */ helper->zstrm.zalloc = zalloc; helper->zstrm.zfree = zfree; zret = inflateInit2(&helper->zstrm, -MAX_WBITS); if (zret != Z_OK) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to initialize inflate: %s", zError(zret)); return NULL; } helper->fw = g_bytes_ref(fw); helper->install_flags = flags; helper->folder_data = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); return g_steal_pointer(&helper); } static gboolean fu_cab_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuCabFirmware *self = FU_CAB_FIRMWARE(firmware); gsize off_cffile = 0; g_autoptr(GByteArray) st = NULL; g_autoptr(FuCabFirmwareParseHelper) helper = NULL; /* parse header */ st = fu_struct_cab_header_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; /* sanity checks */ if (fu_struct_cab_header_get_size(st) < g_bytes_get_size(fw)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "buffer size 0x%x is less than archive size 0x%x", (guint)g_bytes_get_size(fw), fu_struct_cab_header_get_size(st)); return FALSE; } if (fu_struct_cab_header_get_idx_cabinet(st) != 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "chained archive not supported"); return FALSE; } if (fu_struct_cab_header_get_nr_folders(st) == 0 || fu_struct_cab_header_get_nr_files(st) == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "archive is empty"); return FALSE; } if (fu_struct_cab_header_get_nr_folders(st) > FU_CAB_FIRMWARE_MAX_FOLDERS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "too many CFFOLDERS, parsed %u and limit was %u", fu_struct_cab_header_get_nr_folders(st), (guint)FU_CAB_FIRMWARE_MAX_FOLDERS); return FALSE; } if (fu_struct_cab_header_get_nr_files(st) > FU_CAB_FIRMWARE_MAX_FILES) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "too many CFFILES, parsed %u and limit was %u", fu_struct_cab_header_get_nr_files(st), (guint)FU_CAB_FIRMWARE_MAX_FILES); return FALSE; } off_cffile = fu_struct_cab_header_get_off_cffile(st); if (off_cffile > g_bytes_get_size(fw)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "archive is corrupt"); return FALSE; } /* create helper */ helper = fu_cab_firmware_parse_helper_new(fw, flags, error); if (helper == NULL) return FALSE; /* reserved sizes */ offset += st->len; if (fu_struct_cab_header_get_flags(st) & 0x0004) { g_autoptr(GByteArray) st2 = NULL; st2 = fu_struct_cab_header_parse_bytes(fw, offset, error); if (st2 == NULL) return FALSE; offset += st2->len; offset += fu_struct_cab_header_reserve_get_rsvd_hdr(st2); helper->rsvd_block = fu_struct_cab_header_reserve_get_rsvd_block(st2); helper->rsvd_folder = fu_struct_cab_header_reserve_get_rsvd_folder(st2); } /* parse CFFOLDER */ for (guint i = 0; i < fu_struct_cab_header_get_nr_folders(st); i++) { g_autoptr(GByteArray) folder_data = g_byte_array_new(); if (!fu_cab_firmware_parse_folder(self, helper, i, offset, folder_data, error)) return FALSE; if (folder_data->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no folder data"); return FALSE; } g_ptr_array_add( helper->folder_data, g_byte_array_free_to_bytes(g_steal_pointer(&folder_data))); /* nocheck */ offset += FU_STRUCT_CAB_FOLDER_SIZE + helper->rsvd_folder; } /* parse CFFILEs */ for (guint i = 0; i < fu_struct_cab_header_get_nr_files(st); i++) { if (!fu_cab_firmware_parse_file(self, helper, &off_cffile, error)) return FALSE; } /* success */ return TRUE; } static GByteArray * fu_cab_firmware_write(FuFirmware *firmware, GError **error) { FuCabFirmware *self = FU_CAB_FIRMWARE(firmware); FuCabFirmwarePrivate *priv = GET_PRIVATE(self); gsize archive_size; gsize offset; guint32 index_into = 0; g_autoptr(GByteArray) st_hdr = fu_struct_cab_header_new(); g_autoptr(GByteArray) st_folder = fu_struct_cab_folder_new(); g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); g_autoptr(GByteArray) cfdata_linear = g_byte_array_new(); g_autoptr(GBytes) cfdata_linear_blob = NULL; g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GPtrArray) chunks_zlib = g_ptr_array_new_with_free_func((GDestroyNotify)g_byte_array_unref); /* create linear CFDATA block */ for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); const gchar *filename_win32 = fu_cab_image_get_win32_filename(FU_CAB_IMAGE(img)); g_autoptr(GBytes) img_blob = NULL; if (filename_win32 == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no image filename"); return NULL; } img_blob = fu_firmware_get_bytes(img, error); if (img_blob == NULL) return NULL; fu_byte_array_append_bytes(cfdata_linear, img_blob); } /* chunkify and compress with a fixed size */ if (cfdata_linear->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no data to compress"); return NULL; } cfdata_linear_blob = g_byte_array_free_to_bytes(g_steal_pointer(&cfdata_linear)); /* nocheck */ chunks = fu_chunk_array_new_from_bytes(cfdata_linear_blob, 0x0, 0x8000); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GByteArray) chunk_zlib = g_byte_array_new(); g_autoptr(GByteArray) buf = g_byte_array_new(); fu_byte_array_set_size(chunk_zlib, fu_chunk_get_data_sz(chk) * 2, 0x0); if (priv->compressed) { int zret; z_stream zstrm = { .zalloc = zalloc, .zfree = zfree, .opaque = Z_NULL, .next_in = (guint8 *)fu_chunk_get_data(chk), .avail_in = fu_chunk_get_data_sz(chk), .next_out = chunk_zlib->data, .avail_out = chunk_zlib->len, }; g_autoptr(z_stream_deflater) zstrm_deflater = &zstrm; zret = deflateInit2(zstrm_deflater, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); if (zret != Z_OK) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to initialize deflate: %s", zError(zret)); return NULL; } zret = deflate(zstrm_deflater, Z_FINISH); if (zret != Z_OK && zret != Z_STREAM_END) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "zlib deflate failed: %s", zError(zret)); return NULL; } fu_byte_array_append_uint8(buf, (guint8)'C'); fu_byte_array_append_uint8(buf, (guint8)'K'); g_byte_array_append(buf, chunk_zlib->data, zstrm.total_out); } else { g_byte_array_append(buf, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); } g_ptr_array_add(chunks_zlib, g_steal_pointer(&buf)); } /* create header */ archive_size = FU_STRUCT_CAB_HEADER_SIZE; archive_size += FU_STRUCT_CAB_FOLDER_SIZE; for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); const gchar *filename_win32 = fu_cab_image_get_win32_filename(FU_CAB_IMAGE(img)); archive_size += FU_STRUCT_CAB_FILE_SIZE + strlen(filename_win32) + 1; } for (guint i = 0; i < chunks_zlib->len; i++) { GByteArray *chunk = g_ptr_array_index(chunks_zlib, i); archive_size += FU_STRUCT_CAB_DATA_SIZE + chunk->len; } offset = FU_STRUCT_CAB_HEADER_SIZE; offset += FU_STRUCT_CAB_FOLDER_SIZE; fu_struct_cab_header_set_size(st_hdr, archive_size); fu_struct_cab_header_set_off_cffile(st_hdr, offset); fu_struct_cab_header_set_nr_files(st_hdr, imgs->len); /* create folder */ for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); const gchar *filename_win32 = fu_cab_image_get_win32_filename(FU_CAB_IMAGE(img)); offset += FU_STRUCT_CAB_FILE_SIZE; offset += strlen(filename_win32) + 1; } fu_struct_cab_folder_set_offset(st_folder, offset); fu_struct_cab_folder_set_ndatab(st_folder, fu_chunk_array_length(chunks)); fu_struct_cab_folder_set_compression(st_folder, priv->compressed ? FU_CAB_COMPRESSION_MSZIP : FU_CAB_COMPRESSION_NONE); g_byte_array_append(st_hdr, st_folder->data, st_folder->len); /* create each CFFILE */ for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); FuCabFileAttribute fattr = FU_CAB_FILE_ATTRIBUTE_NONE; GDateTime *created = fu_cab_image_get_created(FU_CAB_IMAGE(img)); const gchar *filename_win32 = fu_cab_image_get_win32_filename(FU_CAB_IMAGE(img)); g_autoptr(GByteArray) st_file = fu_struct_cab_file_new(); g_autoptr(GBytes) img_blob = fu_firmware_get_bytes(img, NULL); if (!g_str_is_ascii(filename_win32)) fattr |= FU_CAB_FILE_ATTRIBUTE_NAME_UTF8; fu_struct_cab_file_set_fattr(st_file, fattr); fu_struct_cab_file_set_usize(st_file, g_bytes_get_size(img_blob)); fu_struct_cab_file_set_uoffset(st_file, index_into); if (created != NULL) { fu_struct_cab_file_set_date(st_file, ((g_date_time_get_year(created) - 1980) << 9) + (g_date_time_get_month(created) << 5) + g_date_time_get_day_of_month(created)); fu_struct_cab_file_set_time(st_file, (g_date_time_get_hour(created) << 11) + (g_date_time_get_minute(created) << 5) + (g_date_time_get_second(created) / 2)); } g_byte_array_append(st_hdr, st_file->data, st_file->len); g_byte_array_append(st_hdr, (const guint8 *)filename_win32, strlen(filename_win32)); fu_byte_array_append_uint8(st_hdr, 0x0); index_into += g_bytes_get_size(img_blob); } /* create each CFDATA */ for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { guint32 checksum; GByteArray *chunk_zlib = g_ptr_array_index(chunks_zlib, i); g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GByteArray) hdr = g_byte_array_new(); g_autoptr(GByteArray) st_data = fu_struct_cab_data_new(); /* first do the 'checksum' on the data, then the partial header -- slightly crazy */ checksum = fu_cab_firmware_compute_checksum(chunk_zlib->data, chunk_zlib->len, 0); fu_byte_array_append_uint16(hdr, chunk_zlib->len, G_LITTLE_ENDIAN); fu_byte_array_append_uint16(hdr, fu_chunk_get_data_sz(chk), G_LITTLE_ENDIAN); checksum = fu_cab_firmware_compute_checksum(hdr->data, hdr->len, checksum); fu_struct_cab_data_set_checksum(st_data, checksum); fu_struct_cab_data_set_comp(st_data, chunk_zlib->len); fu_struct_cab_data_set_uncomp(st_data, fu_chunk_get_data_sz(chk)); g_byte_array_append(st_hdr, st_data->data, st_data->len); g_byte_array_append(st_hdr, chunk_zlib->data, chunk_zlib->len); } /* success */ return g_steal_pointer(&st_hdr); } static gboolean fu_cab_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuCabFirmware *self = FU_CAB_FIRMWARE(firmware); FuCabFirmwarePrivate *priv = GET_PRIVATE(self); const gchar *tmp; /* simple properties */ tmp = xb_node_query_text(n, "compressed", NULL); if (tmp != NULL) { if (!fu_strtobool(tmp, &priv->compressed, error)) return FALSE; } tmp = xb_node_query_text(n, "only_basename", NULL); if (tmp != NULL) { if (!fu_strtobool(tmp, &priv->only_basename, error)) return FALSE; } /* success */ return TRUE; } static void fu_cab_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuCabFirmware *self = FU_CAB_FIRMWARE(firmware); FuCabFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kb(bn, "compressed", priv->compressed); fu_xmlb_builder_insert_kb(bn, "only_basename", priv->only_basename); } static void fu_cab_firmware_class_init(FuCabFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_cab_firmware_check_magic; klass_firmware->parse = fu_cab_firmware_parse; klass_firmware->write = fu_cab_firmware_write; klass_firmware->build = fu_cab_firmware_build; klass_firmware->export = fu_cab_firmware_export; } static void fu_cab_firmware_init(FuCabFirmware *self) { g_type_ensure(FU_TYPE_CAB_IMAGE); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_DEDUPE_ID); fu_firmware_set_images_max(FU_FIRMWARE(self), G_MAXUINT16); } /** * fu_cab_firmware_new: * * Returns: (transfer full): a #FuCabFirmware * * Since: 1.9.7 **/ FuCabFirmware * fu_cab_firmware_new(void) { return g_object_new(FU_TYPE_CAB_FIRMWARE, NULL); } fwupd-1.9.16/libfwupdplugin/fu-cab-firmware.h000066400000000000000000000014341460375044200211170ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_CAB_FIRMWARE (fu_cab_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuCabFirmware, fu_cab_firmware, FU, CAB_FIRMWARE, FuFirmware) struct _FuCabFirmwareClass { FuFirmwareClass parent_class; }; gboolean fu_cab_firmware_get_compressed(FuCabFirmware *self) G_GNUC_NON_NULL(1); void fu_cab_firmware_set_compressed(FuCabFirmware *self, gboolean compressed) G_GNUC_NON_NULL(1); gboolean fu_cab_firmware_get_only_basename(FuCabFirmware *self) G_GNUC_NON_NULL(1); void fu_cab_firmware_set_only_basename(FuCabFirmware *self, gboolean only_basename) G_GNUC_NON_NULL(1); FuCabFirmware * fu_cab_firmware_new(void) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.9.16/libfwupdplugin/fu-cab-image.c000066400000000000000000000077251460375044200203710ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCabFirmware" #include "config.h" #include "fu-cab-image.h" #include "fu-string.h" struct _FuCabImage { FuFirmware parent_instance; gchar *win32_filename; GDateTime *created; }; G_DEFINE_TYPE(FuCabImage, fu_cab_image, FU_TYPE_FIRMWARE) /** * fu_cab_image_get_win32_filename: * @self: a #FuCabImage * * Gets the in-archive Windows filename, with a possible path component -- creating from the * firmware ID if required. * * Returns: filename, or %NULL * * Since: 1.9.7 **/ const gchar * fu_cab_image_get_win32_filename(FuCabImage *self) { g_return_val_if_fail(FU_IS_CAB_IMAGE(self), NULL); /* generate from the id */ if (self->win32_filename == NULL) { g_autoptr(GString) str = g_string_new(fu_firmware_get_id(FU_FIRMWARE(self))); g_string_replace(str, "/", "\\", 0); if (str->len == 0) return NULL; fu_cab_image_set_win32_filename(self, str->str); } return self->win32_filename; } /** * fu_cab_image_set_win32_filename: * @self: a #FuCabImage * @win32_filename: filename, or %NULL * * Sets the in-archive Windows filename, with a possible path component. * * Since: 1.9.7 **/ void fu_cab_image_set_win32_filename(FuCabImage *self, const gchar *win32_filename) { g_return_if_fail(FU_IS_CAB_IMAGE(self)); g_free(self->win32_filename); self->win32_filename = g_strdup(win32_filename); } /** * fu_cab_image_get_created: * @self: a #FuCabImage * * Gets the created timestamp. * * Returns: (transfer none): a #GDateTime, or %NULL * * Since: 1.9.7 **/ GDateTime * fu_cab_image_get_created(FuCabImage *self) { g_return_val_if_fail(FU_IS_CAB_IMAGE(self), NULL); return self->created; } /** * fu_cab_image_set_created: * @self: a #FuCabImage * @created: a #GDateTime, or %NULL * * Sets the created timestamp. * * Since: 1.9.7 **/ void fu_cab_image_set_created(FuCabImage *self, GDateTime *created) { g_return_if_fail(FU_IS_CAB_IMAGE(self)); if (self->created != NULL) { g_date_time_unref(self->created); self->created = NULL; } if (created != NULL) self->created = g_date_time_ref(created); } static gboolean fu_cab_image_build(FuFirmware *firmware, XbNode *n, GError **error) { FuCabImage *self = FU_CAB_IMAGE(firmware); const gchar *tmp; /* simple properties */ tmp = xb_node_query_text(n, "win32_filename", NULL); if (tmp != NULL) fu_cab_image_set_win32_filename(self, tmp); tmp = xb_node_query_text(n, "created", NULL); if (tmp != NULL) { g_autoptr(GDateTime) created = g_date_time_new_from_iso8601(tmp, NULL); if (created == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not iso8601: %s", tmp); return FALSE; } fu_cab_image_set_created(self, created); } /* success */ return TRUE; } static void fu_cab_image_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuCabImage *self = FU_CAB_IMAGE(firmware); fu_xmlb_builder_insert_kv(bn, "win32_filename", self->win32_filename); if (self->created != NULL) { g_autofree gchar *str = g_date_time_format_iso8601(self->created); fu_xmlb_builder_insert_kv(bn, "created", str); } } static void fu_cab_image_finalize(GObject *object) { FuCabImage *self = FU_CAB_IMAGE(object); g_free(self->win32_filename); if (self->created != NULL) g_date_time_unref(self->created); G_OBJECT_CLASS(fu_cab_image_parent_class)->finalize(object); } static void fu_cab_image_class_init(FuCabImageClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_cab_image_finalize; klass_firmware->build = fu_cab_image_build; klass_firmware->export = fu_cab_image_export; } static void fu_cab_image_init(FuCabImage *self) { } /** * fu_cab_image_new: * * Returns: (transfer full): a #FuCabImage * * Since: 1.9.7 **/ FuCabImage * fu_cab_image_new(void) { return g_object_new(FU_TYPE_CAB_IMAGE, NULL); } fwupd-1.9.16/libfwupdplugin/fu-cab-image.h000066400000000000000000000012631460375044200203650ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_CAB_IMAGE (fu_cab_image_get_type()) G_DECLARE_FINAL_TYPE(FuCabImage, fu_cab_image, FU, CAB_IMAGE, FuFirmware) const gchar * fu_cab_image_get_win32_filename(FuCabImage *self) G_GNUC_NON_NULL(1); void fu_cab_image_set_win32_filename(FuCabImage *self, const gchar *win32_filename) G_GNUC_NON_NULL(1); GDateTime * fu_cab_image_get_created(FuCabImage *self) G_GNUC_NON_NULL(1); void fu_cab_image_set_created(FuCabImage *self, GDateTime *created) G_GNUC_NON_NULL(1); FuCabImage * fu_cab_image_new(void) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.9.16/libfwupdplugin/fu-cab.rs000066400000000000000000000025271460375044200175060ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ParseBytes, New)] struct CabData { checksum: u32le, comp: u16le, uncomp: u16le, } #[repr(u16)] #[derive(ToString)] enum CabCompression { None = 0x0000, Mszip = 0x0001, Quantum = 0x0002, Lzx = 0x0003, } #[repr(u16)] enum CabFileAttribute { None = 0x00, Readonly = 0x01, Hidden = 0x02, System = 0x04, Arch = 0x20, Exec = 0x40, NameUtf8 = 0x80, } #[derive(ParseBytes, New)] struct CabFile { usize: u32le, // uncompressed uoffset: u32le, // uncompressed index: u16le, date: u16le, time: u16le, fattr: CabFileAttribute, } #[derive(ParseBytes, New)] struct CabFolder { offset: u32le, ndatab: u16le, compression: CabCompression, } #[derive(ParseBytes, ValidateBytes, New)] struct CabHeader { signature: [char; 4] == "MSCF", _reserved1: [u8; 4], size: u32le, // in bytes _reserved2: [u8; 4], off_cffile: u32le, // to the first CabFile entry _reserved3: [u8; 4], version_minor: u8 == 3, version_major: u8 == 1, nr_folders: u16le = 1, nr_files: u16le, flags: u16le, set_id: u16le, idx_cabinet: u16le, } #[derive(ParseBytes, New)] struct CabHeaderReserve { rsvd_hdr: u16le, rsvd_folder: u8, rsvd_block: u8, } fwupd-1.9.16/libfwupdplugin/fu-cfi-device.c000066400000000000000000000674551460375044200205700ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCfiDevice" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-cfi-device.h" #include "fu-chunk-array.h" #include "fu-dump.h" #include "fu-mem.h" #include "fu-quirks.h" #include "fu-string.h" /** * FuCfiDevice: * * A chip conforming to the Common Flash Memory Interface, typically a SPI flash chip. * * Where required, the quirks instance IDs will be added in ->setup(). * * The defaults are set as follows, and can be overridden in quirk files: * * * `PageSize`: 0x100 * * `SectorSize`: 0x1000 * * `BlockSize`: 0x10000 * * See also: [class@FuDevice] */ typedef struct { gchar *flash_id; guint8 cmd_read_id_sz; guint32 page_size; guint32 sector_size; guint32 block_size; FuCfiDeviceCmd cmds[FU_CFI_DEVICE_CMD_LAST]; } FuCfiDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuCfiDevice, fu_cfi_device, FU_TYPE_DEVICE) enum { PROP_0, PROP_FLASH_ID, PROP_LAST }; #define GET_PRIVATE(o) (fu_cfi_device_get_instance_private(o)) #define FU_CFI_DEVICE_PAGE_SIZE_DEFAULT 0x100 #define FU_CFI_DEVICE_SECTOR_SIZE_DEFAULT 0x1000 #define FU_CFI_DEVICE_BLOCK_SIZE_DEFAULT 0x10000 static const gchar * fu_cfi_device_cmd_to_string(FuCfiDeviceCmd cmd) { if (cmd == FU_CFI_DEVICE_CMD_READ_ID) return "ReadId"; if (cmd == FU_CFI_DEVICE_CMD_PAGE_PROG) return "PageProg"; if (cmd == FU_CFI_DEVICE_CMD_CHIP_ERASE) return "ChipErase"; if (cmd == FU_CFI_DEVICE_CMD_READ_DATA) return "ReadData"; if (cmd == FU_CFI_DEVICE_CMD_READ_STATUS) return "ReadStatus"; if (cmd == FU_CFI_DEVICE_CMD_SECTOR_ERASE) return "SectorErase"; if (cmd == FU_CFI_DEVICE_CMD_WRITE_EN) return "WriteEn"; if (cmd == FU_CFI_DEVICE_CMD_WRITE_STATUS) return "WriteStatus"; if (cmd == FU_CFI_DEVICE_CMD_BLOCK_ERASE) return "BlockErase"; return NULL; } /** * fu_cfi_device_get_size: * @self: a #FuCfiDevice * * Gets the chip maximum size. * * This is typically set with the `FirmwareSizeMax` quirk key. * * Returns: size in bytes, or 0 if unknown * * Since: 1.7.1 **/ guint64 fu_cfi_device_get_size(FuCfiDevice *self) { g_return_val_if_fail(FU_IS_CFI_DEVICE(self), G_MAXUINT64); return fu_device_get_firmware_size_max(FU_DEVICE(self)); } /** * fu_cfi_device_set_size: * @self: a #FuCfiDevice * @size: maximum size in bytes, or 0 if unknown * * Sets the chip maximum size. * * Since: 1.7.1 **/ void fu_cfi_device_set_size(FuCfiDevice *self, guint64 size) { g_return_if_fail(FU_IS_CFI_DEVICE(self)); fu_device_set_firmware_size_max(FU_DEVICE(self), size); } /** * fu_cfi_device_get_flash_id: * @self: a #FuCfiDevice * * Gets the chip ID used to identify the device. * * Returns: the ID, or %NULL * * Since: 1.7.1 **/ const gchar * fu_cfi_device_get_flash_id(FuCfiDevice *self) { FuCfiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFI_DEVICE(self), NULL); return priv->flash_id; } /** * fu_cfi_device_set_flash_id: * @self: a #FuCfiDevice * @flash_id: (nullable): The chip ID * * Sets the chip ID used to identify the device. * * Since: 1.7.1 **/ void fu_cfi_device_set_flash_id(FuCfiDevice *self, const gchar *flash_id) { FuCfiDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFI_DEVICE(self)); if (g_strcmp0(flash_id, priv->flash_id) == 0) return; g_free(priv->flash_id); priv->flash_id = g_strdup(flash_id); } static void fu_cfi_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuCfiDevice *self = FU_CFI_DEVICE(object); FuCfiDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_FLASH_ID: g_value_set_object(value, priv->flash_id); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_cfi_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuCfiDevice *self = FU_CFI_DEVICE(object); switch (prop_id) { case PROP_FLASH_ID: fu_cfi_device_set_flash_id(self, g_value_get_string(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_cfi_device_finalize(GObject *object) { FuCfiDevice *self = FU_CFI_DEVICE(object); FuCfiDevicePrivate *priv = GET_PRIVATE(self); g_free(priv->flash_id); G_OBJECT_CLASS(fu_cfi_device_parent_class)->finalize(object); } typedef struct { guint8 mask; guint8 value; } FuCfiDeviceHelper; static gboolean fu_cfi_device_wait_for_status_cb(FuDevice *device, gpointer user_data, GError **error) { FuCfiDeviceHelper *helper = (FuCfiDeviceHelper *)user_data; FuCfiDevice *self = FU_CFI_DEVICE(device); guint8 buf[2] = {0x0}; g_autoptr(FuDeviceLocker) cslocker = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* enable chip */ cslocker = fu_cfi_device_chip_select_locker_new(self, error); if (cslocker == NULL) return FALSE; if (!fu_cfi_device_get_cmd(self, FU_CFI_DEVICE_CMD_READ_STATUS, &buf[0], error)) return FALSE; if (!fu_cfi_device_send_command(self, buf, sizeof(buf), buf, sizeof(buf), progress, error)) { g_prefix_error(error, "failed to want to status: "); return FALSE; } if ((buf[0x1] & helper->mask) != helper->value) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "wanted 0x%x, got 0x%x", helper->value, buf[0x1] & helper->mask); return FALSE; } /* success */ return TRUE; } static gboolean fu_cfi_device_wait_for_status(FuCfiDevice *self, guint8 mask, guint8 value, guint count, guint delay, GError **error) { FuCfiDeviceHelper helper = {.mask = mask, .value = value}; return fu_device_retry_full(FU_DEVICE(self), fu_cfi_device_wait_for_status_cb, count, delay, &helper, error); } static gboolean fu_cfi_device_read_jedec(FuCfiDevice *self, GError **error) { guint8 buf_res[] = {0x9F}; guint8 buf_req[3] = {0x0}; g_autoptr(FuDeviceLocker) cslocker = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GString) flash_id = g_string_new(NULL); /* enable chip */ cslocker = fu_cfi_device_chip_select_locker_new(self, error); if (cslocker == NULL) return FALSE; /* read JEDEC ID */ if (!fu_cfi_device_send_command(self, buf_res, sizeof(buf_res), buf_req, sizeof(buf_req), progress, error)) { g_prefix_error(error, "failed to request JEDEC ID: "); return FALSE; } if ((buf_req[0] == 0x0 && buf_req[1] == 0x0 && buf_req[2] == 0x0) || (buf_req[0] == 0xFF && buf_req[1] == 0xFF && buf_req[2] == 0xFF)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device not detected, flash ID 0x%02X%02X%02X", buf_req[0], buf_req[1], buf_req[2]); return FALSE; } g_string_append_printf(flash_id, "%02X", buf_req[0]); g_string_append_printf(flash_id, "%02X", buf_req[1]); g_string_append_printf(flash_id, "%02X", buf_req[2]); fu_cfi_device_set_flash_id(self, flash_id->str); /* success */ return TRUE; } static gboolean fu_cfi_device_setup(FuDevice *device, GError **error) { gsize flash_idsz = 0; FuCfiDevice *self = FU_CFI_DEVICE(device); FuCfiDevicePrivate *priv = GET_PRIVATE(self); /* setup SPI chip */ if (priv->flash_id == NULL) { if (!fu_cfi_device_read_jedec(self, error)) return FALSE; } /* sanity check */ if (priv->flash_id != NULL) flash_idsz = strlen(priv->flash_id); if (flash_idsz == 0 || flash_idsz % 2 != 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "not a valid flash-id"); return FALSE; } /* typically this will add quirk strings of 2, 4, then 6 bytes */ for (guint i = 0; i < flash_idsz; i += 2) { g_autofree gchar *flash_id = g_strndup(priv->flash_id, i + 2); fu_device_add_instance_str(device, "FLASHID", flash_id); if (!fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_QUIRKS, error, "CFI", "FLASHID", NULL)) return FALSE; } /* success */ return TRUE; } /** * fu_cfi_device_get_cmd: * @self: a #FuCfiDevice * @cmd: a #FuCfiDeviceCmd, e.g. %FU_CFI_DEVICE_CMD_CHIP_ERASE * @value: the API command value to use * @error: (nullable): optional return location for an error * * Gets the self vendor code. * * Returns: %TRUE on success * * Since: 1.7.1 **/ gboolean fu_cfi_device_get_cmd(FuCfiDevice *self, FuCfiDeviceCmd cmd, guint8 *value, GError **error) { FuCfiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFI_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (cmd >= FU_CFI_DEVICE_CMD_LAST) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CFI cmd invalid"); return FALSE; } if (priv->cmds[cmd] == 0x0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No defined CFI cmd for %s", fu_cfi_device_cmd_to_string(cmd)); return FALSE; } if (value != NULL) *value = priv->cmds[cmd]; return TRUE; } /** * fu_cfi_device_get_page_size: * @self: a #FuCfiDevice * * Gets the chip page size. This is typically the largest writable block size. * * This is typically set with the `CfiDevicePageSize` quirk key. * * Returns: page size in bytes * * Since: 1.7.3 **/ guint32 fu_cfi_device_get_page_size(FuCfiDevice *self) { FuCfiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFI_DEVICE(self), G_MAXUINT32); return priv->page_size; } /** * fu_cfi_device_set_page_size: * @self: a #FuCfiDevice * @page_size: page size in bytes, or 0 if unknown * * Sets the chip page size. This is typically the largest writable block size. * * Since: 1.7.3 **/ void fu_cfi_device_set_page_size(FuCfiDevice *self, guint32 page_size) { FuCfiDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFI_DEVICE(self)); priv->page_size = page_size; } /** * fu_cfi_device_get_sector_size: * @self: a #FuCfiDevice * * Gets the chip sector size. This is typically the smallest erasable page size. * * This is typically set with the `CfiDeviceSectorSize` quirk key. * * Returns: sector size in bytes * * Since: 1.7.3 **/ guint32 fu_cfi_device_get_sector_size(FuCfiDevice *self) { FuCfiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFI_DEVICE(self), G_MAXUINT32); return priv->sector_size; } /** * fu_cfi_device_set_block_size: * @self: a #FuCfiDevice * @block_size: block size in bytes, or 0 if unknown * * Sets the chip block size. This is typically the largest erasable chunk size. * * Since: 1.7.4 **/ void fu_cfi_device_set_block_size(FuCfiDevice *self, guint32 block_size) { FuCfiDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFI_DEVICE(self)); priv->block_size = block_size; } /** * fu_cfi_device_get_block_size: * @self: a #FuCfiDevice * * Gets the chip block size. This is typically the largest erasable block size. * * This is typically set with the `CfiDeviceBlockSize` quirk key. * * Returns: block size in bytes * * Since: 1.7.4 **/ guint32 fu_cfi_device_get_block_size(FuCfiDevice *self) { FuCfiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFI_DEVICE(self), G_MAXUINT32); return priv->block_size; } /** * fu_cfi_device_set_sector_size: * @self: a #FuCfiDevice * @sector_size: sector size in bytes, or 0 if unknown * * Sets the chip sector size. This is typically the smallest erasable page size. * * Since: 1.7.3 **/ void fu_cfi_device_set_sector_size(FuCfiDevice *self, guint32 sector_size) { FuCfiDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFI_DEVICE(self)); priv->sector_size = sector_size; } static gboolean fu_cfi_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuCfiDevice *self = FU_CFI_DEVICE(device); FuCfiDevicePrivate *priv = GET_PRIVATE(self); guint64 tmp; if (g_strcmp0(key, FU_QUIRKS_CFI_DEVICE_CMD_READ_ID) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_READ_ID] = tmp; return TRUE; } if (g_strcmp0(key, FU_QUIRKS_CFI_DEVICE_CMD_READ_ID_SZ) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmd_read_id_sz = tmp; return TRUE; } if (g_strcmp0(key, FU_QUIRKS_CFI_DEVICE_CMD_CHIP_ERASE) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_CHIP_ERASE] = tmp; return TRUE; } if (g_strcmp0(key, FU_QUIRKS_CFI_DEVICE_CMD_BLOCK_ERASE) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_BLOCK_ERASE] = tmp; return TRUE; } if (g_strcmp0(key, FU_QUIRKS_CFI_DEVICE_CMD_SECTOR_ERASE) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_SECTOR_ERASE] = tmp; return TRUE; } if (g_strcmp0(key, FU_QUIRKS_CFI_DEVICE_CMD_WRITE_STATUS) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_WRITE_STATUS] = tmp; return TRUE; } if (g_strcmp0(key, FU_QUIRKS_CFI_DEVICE_CMD_PAGE_PROG) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_PAGE_PROG] = tmp; return TRUE; } if (g_strcmp0(key, FU_QUIRKS_CFI_DEVICE_CMD_READ_DATA) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_READ_DATA] = tmp; return TRUE; } if (g_strcmp0(key, FU_QUIRKS_CFI_DEVICE_CMD_READ_STATUS) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_READ_STATUS] = tmp; return TRUE; } if (g_strcmp0(key, FU_QUIRKS_CFI_DEVICE_CMD_WRITE_EN) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->cmds[FU_CFI_DEVICE_CMD_WRITE_EN] = tmp; return TRUE; } if (g_strcmp0(key, FU_QUIRKS_CFI_DEVICE_PAGE_SIZE) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; priv->page_size = tmp; return TRUE; } if (g_strcmp0(key, FU_QUIRKS_CFI_DEVICE_SECTOR_SIZE) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; priv->sector_size = tmp; return TRUE; } if (g_strcmp0(key, FU_QUIRKS_CFI_DEVICE_BLOCK_SIZE) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; priv->block_size = tmp; return TRUE; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_cfi_device_to_string(FuDevice *device, guint idt, GString *str) { FuCfiDevice *self = FU_CFI_DEVICE(device); FuCfiDevicePrivate *priv = GET_PRIVATE(self); fu_string_append(str, idt, "FlashId", priv->flash_id); for (guint i = 0; i < FU_CFI_DEVICE_CMD_LAST; i++) { fu_string_append_kx(str, idt, fu_cfi_device_cmd_to_string(i), priv->cmds[i]); } if (priv->page_size > 0) fu_string_append_kx(str, idt, "PageSize", priv->page_size); if (priv->sector_size > 0) fu_string_append_kx(str, idt, "SectorSize", priv->sector_size); if (priv->block_size > 0) fu_string_append_kx(str, idt, "BlockSize", priv->block_size); } /** * fu_cfi_device_send_command: * @self: a #FuCfiDevice * @wbuf: buffer * @wbufsz: size of @wbuf, possibly zero * @rbuf: buffer * @rbufsz: size of @rbuf, possibly zero * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Sends an unspecified command stream to the CFI device. * * Returns: %TRUE on success * * Since: 1.9.1 **/ gboolean fu_cfi_device_send_command(FuCfiDevice *self, const guint8 *wbuf, gsize wbufsz, guint8 *rbuf, gsize rbufsz, FuProgress *progress, GError **error) { FuCfiDeviceClass *klass = FU_CFI_DEVICE_GET_CLASS(self); g_return_val_if_fail(FU_IS_CFI_DEVICE(self), FALSE); g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (klass->send_command == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "send_command is not implemented on this device"); return FALSE; } if (wbufsz > 0) fu_dump_raw(G_LOG_DOMAIN, "SPI write", wbuf, wbufsz); if (!klass->send_command(self, wbuf, wbufsz, rbuf, rbufsz, progress, error)) return FALSE; if (rbufsz > 0) fu_dump_raw(G_LOG_DOMAIN, "SPI read", rbuf, rbufsz); return TRUE; } /** * fu_cfi_device_chip_select: * @self: a #FuCfiDevice * @value: boolean * @error: (nullable): optional return location for an error * * Sets the chip select value. * * Returns: %TRUE on success * * Since: 1.8.0 **/ gboolean fu_cfi_device_chip_select(FuCfiDevice *self, gboolean value, GError **error) { FuCfiDeviceClass *klass = FU_CFI_DEVICE_GET_CLASS(self); g_return_val_if_fail(FU_IS_CFI_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (klass->chip_select == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "chip select is not implemented on this device"); return FALSE; } return klass->chip_select(self, value, error); } static gboolean fu_cfi_device_chip_select_assert(GObject *device, GError **error) { return fu_cfi_device_chip_select(FU_CFI_DEVICE(device), TRUE, error); } static gboolean fu_cfi_device_chip_select_deassert(GObject *device, GError **error) { return fu_cfi_device_chip_select(FU_CFI_DEVICE(device), FALSE, error); } /** * fu_cfi_device_chip_select_locker_new: * @self: a #FuCfiDevice * * Creates a custom device locker that asserts and deasserts the chip select signal. * * Returns: (transfer full): (nullable): a #FuDeviceLocker * * Since: 1.8.0 **/ FuDeviceLocker * fu_cfi_device_chip_select_locker_new(FuCfiDevice *self, GError **error) { g_return_val_if_fail(FU_IS_CFI_DEVICE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return fu_device_locker_new_full(self, fu_cfi_device_chip_select_assert, fu_cfi_device_chip_select_deassert, error); } static gboolean fu_cfi_device_write_enable(FuCfiDevice *self, GError **error) { guint8 buf[1] = {0x0}; g_autoptr(FuDeviceLocker) cslocker = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* write enable */ if (!fu_cfi_device_get_cmd(self, FU_CFI_DEVICE_CMD_WRITE_EN, &buf[0], error)) return FALSE; cslocker = fu_cfi_device_chip_select_locker_new(self, error); if (cslocker == NULL) return FALSE; if (!fu_cfi_device_send_command(self, buf, sizeof(buf), NULL, 0, progress, error)) return FALSE; if (!fu_device_locker_close(cslocker, error)) return FALSE; /* check that WEL is now set */ return fu_cfi_device_wait_for_status(self, 0b10, 0b10, 10, 5, error); } static gboolean fu_cfi_device_chip_erase(FuCfiDevice *self, GError **error) { guint8 buf[] = {0x0}; g_autoptr(FuDeviceLocker) cslocker = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* enable chip */ cslocker = fu_cfi_device_chip_select_locker_new(self, error); if (cslocker == NULL) return FALSE; /* erase */ if (!fu_cfi_device_get_cmd(self, FU_CFI_DEVICE_CMD_CHIP_ERASE, &buf[0], error)) return FALSE; if (!fu_cfi_device_send_command(self, buf, sizeof(buf), NULL, 0, progress, error)) return FALSE; if (!fu_device_locker_close(cslocker, error)) return FALSE; /* poll Read Status register BUSY */ return fu_cfi_device_wait_for_status(self, 0b1, 0b0, 100, 500, error); } static gboolean fu_cfi_device_write_page(FuCfiDevice *self, FuChunk *page, FuProgress *progress, GError **error) { guint8 cmd = 0x0; g_autoptr(FuDeviceLocker) cslocker = NULL; g_autoptr(GByteArray) buf = g_byte_array_new(); if (!fu_cfi_device_write_enable(self, error)) return FALSE; cslocker = fu_cfi_device_chip_select_locker_new(self, error); if (cslocker == NULL) return FALSE; /* cmd, 24 bit starting address, then data */ if (!fu_cfi_device_get_cmd(self, FU_CFI_DEVICE_CMD_PAGE_PROG, &cmd, error)) return FALSE; fu_byte_array_append_uint8(buf, cmd); fu_byte_array_append_uint24(buf, fu_chunk_get_address(page), G_BIG_ENDIAN); g_byte_array_append(buf, fu_chunk_get_data(page), fu_chunk_get_data_sz(page)); g_debug("writing page at 0x%x", (guint)fu_chunk_get_address(page)); if (!fu_cfi_device_send_command(self, buf->data, buf->len, NULL, 0, progress, error)) return FALSE; if (!fu_device_locker_close(cslocker, error)) return FALSE; /* poll Read Status register BUSY */ return fu_cfi_device_wait_for_status(self, 0b1, 0b0, 100, 50, error); } static gboolean fu_cfi_device_write_pages(FuCfiDevice *self, FuChunkArray *pages, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(pages)); for (guint i = 0; i < fu_chunk_array_length(pages); i++) { g_autoptr(FuChunk) page = fu_chunk_array_index(pages, i); if (!fu_cfi_device_write_page(self, page, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_cfi_device_read_block(FuCfiDevice *self, FuChunk *block, FuProgress *progress, GError **error) { guint8 buf_req[4] = {0x0}; /* cmd, then 24 bit starting address */ g_autoptr(FuDeviceLocker) cslocker = NULL; /* enable chip */ cslocker = fu_cfi_device_chip_select_locker_new(self, error); if (cslocker == NULL) return FALSE; if (!fu_cfi_device_get_cmd(self, FU_CFI_DEVICE_CMD_READ_DATA, &buf_req[0], error)) return FALSE; fu_memwrite_uint24(buf_req + 0x1, fu_chunk_get_address(block), G_BIG_ENDIAN); return fu_cfi_device_send_command(self, buf_req, sizeof(buf_req), fu_chunk_get_data_out(block), fu_chunk_get_data_sz(block), progress, error); } static GBytes * fu_cfi_device_read_firmware(FuCfiDevice *self, gsize bufsz, FuProgress *progress, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GPtrArray) pages = NULL; /* progress */ fu_byte_array_set_size(buf, bufsz, 0x0); pages = fu_chunk_array_mutable_new(buf->data, buf->len, 0x0, 0x0, fu_cfi_device_get_block_size(self)); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, pages->len); for (guint i = 0; i < pages->len; i++) { FuChunk *block = g_ptr_array_index(pages, i); if (!fu_cfi_device_read_block(self, block, fu_progress_get_child(progress), error)) return NULL; fu_progress_step_done(progress); } /* success */ return g_bytes_new(buf->data, buf->len); } static GBytes * fu_cfi_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuCfiDevice *self = FU_CFI_DEVICE(device); gsize bufsz = fu_device_get_firmware_size_max(device); g_autoptr(FuDeviceLocker) locker = NULL; /* open programmer */ locker = fu_device_locker_new(device, error); if (locker == NULL) return NULL; /* sanity check */ if (bufsz == 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "device firmware size not set"); return NULL; } return fu_cfi_device_read_firmware(self, bufsz, progress, error); } static gboolean fu_cfi_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuCfiDevice *self = FU_CFI_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) fw_verify = NULL; g_autoptr(FuChunkArray) pages = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* open programmer */ locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 85, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 5, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* erase */ if (!fu_cfi_device_write_enable(self, error)) { g_prefix_error(error, "failed to enable writes: "); return FALSE; } if (!fu_cfi_device_chip_erase(self, error)) { g_prefix_error(error, "failed to erase: "); return FALSE; } fu_progress_step_done(progress); /* write each block */ pages = fu_chunk_array_new_from_bytes(fw, 0x0, fu_cfi_device_get_page_size(self)); if (!fu_cfi_device_write_pages(self, pages, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write pages: "); return FALSE; } fu_progress_step_done(progress); /* verify each block */ fw_verify = fu_cfi_device_read_firmware(self, g_bytes_get_size(fw), fu_progress_get_child(progress), error); if (fw_verify == NULL) { g_prefix_error(error, "failed to verify blocks: "); return FALSE; } if (!fu_bytes_compare(fw_verify, fw, error)) { g_prefix_error(error, "verify failed: "); return FALSE; } fu_progress_step_done(progress); /* success! */ return TRUE; } static void fu_cfi_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_cfi_device_init(FuCfiDevice *self) { FuCfiDevicePrivate *priv = GET_PRIVATE(self); priv->page_size = FU_CFI_DEVICE_PAGE_SIZE_DEFAULT; priv->sector_size = FU_CFI_DEVICE_SECTOR_SIZE_DEFAULT; priv->block_size = FU_CFI_DEVICE_BLOCK_SIZE_DEFAULT; priv->cmds[FU_CFI_DEVICE_CMD_WRITE_STATUS] = 0x01; priv->cmds[FU_CFI_DEVICE_CMD_PAGE_PROG] = 0x02; priv->cmds[FU_CFI_DEVICE_CMD_READ_DATA] = 0x03; priv->cmds[FU_CFI_DEVICE_CMD_READ_STATUS] = 0x05; priv->cmds[FU_CFI_DEVICE_CMD_WRITE_EN] = 0x06; priv->cmds[FU_CFI_DEVICE_CMD_SECTOR_ERASE] = 0x20; priv->cmds[FU_CFI_DEVICE_CMD_CHIP_ERASE] = 0x60; priv->cmds[FU_CFI_DEVICE_CMD_READ_ID] = 0x9f; fu_device_add_protocol(FU_DEVICE(self), "org.jedec.cfi"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); fu_device_add_vendor_id(FU_DEVICE(self), "SPI:*"); fu_device_set_summary(FU_DEVICE(self), "CFI flash chip"); } static void fu_cfi_device_constructed(GObject *obj) { FuCfiDevice *self = FU_CFI_DEVICE(obj); fu_device_add_instance_id(FU_DEVICE(self), "SPI"); } static void fu_cfi_device_class_init(FuCfiDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GParamSpec *pspec; object_class->finalize = fu_cfi_device_finalize; object_class->get_property = fu_cfi_device_get_property; object_class->set_property = fu_cfi_device_set_property; object_class->constructed = fu_cfi_device_constructed; klass_device->setup = fu_cfi_device_setup; klass_device->to_string = fu_cfi_device_to_string; klass_device->set_quirk_kv = fu_cfi_device_set_quirk_kv; klass_device->write_firmware = fu_cfi_device_write_firmware; klass_device->dump_firmware = fu_cfi_device_dump_firmware; klass_device->set_progress = fu_cfi_device_set_progress; /** * FuCfiDevice:flash-id: * * The CCI JEDEC flash ID. * * Since: 1.7.1 */ pspec = g_param_spec_string("flash-id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FLASH_ID, pspec); } /** * fu_cfi_device_new: * @ctx: a #FuContext * * Creates a new #FuCfiDevice. * * Returns: (transfer full): a #FuCfiDevice * * Since: 1.7.1 **/ FuCfiDevice * fu_cfi_device_new(FuContext *ctx, const gchar *flash_id) { return g_object_new(FU_TYPE_CFI_DEVICE, "context", ctx, "flash-id", flash_id, NULL); } fwupd-1.9.16/libfwupdplugin/fu-cfi-device.h000066400000000000000000000055661460375044200205700ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-device.h" #define FU_TYPE_CFI_DEVICE (fu_cfi_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuCfiDevice, fu_cfi_device, FU, CFI_DEVICE, FuDevice) struct _FuCfiDeviceClass { FuDeviceClass parent_class; gboolean (*chip_select)(FuCfiDevice *self, gboolean value, GError **error); gboolean (*send_command)(FuCfiDevice *self, const guint8 *wbuf, gsize wbufsz, guint8 *rbuf, gsize rbufsz, FuProgress *progress, GError **error); }; /** * FuCfiDeviceCmd: * @FU_CFI_DEVICE_CMD_READ_ID: Read the chip ID * @FU_CFI_DEVICE_CMD_PAGE_PROG: Page program * @FU_CFI_DEVICE_CMD_CHIP_ERASE: Whole chip erase * @FU_CFI_DEVICE_CMD_READ_DATA: Read data * @FU_CFI_DEVICE_CMD_READ_STATUS: Read status * @FU_CFI_DEVICE_CMD_SECTOR_ERASE: Sector erase * @FU_CFI_DEVICE_CMD_WRITE_EN: Write enable * @FU_CFI_DEVICE_CMD_WRITE_STATUS: Write status * @FU_CFI_DEVICE_CMD_BLOCK_ERASE: Block erase * * Commands used when calling fu_cfi_device_get_cmd(). **/ typedef enum { FU_CFI_DEVICE_CMD_READ_ID, FU_CFI_DEVICE_CMD_PAGE_PROG, FU_CFI_DEVICE_CMD_CHIP_ERASE, FU_CFI_DEVICE_CMD_READ_DATA, FU_CFI_DEVICE_CMD_READ_STATUS, FU_CFI_DEVICE_CMD_SECTOR_ERASE, FU_CFI_DEVICE_CMD_WRITE_EN, FU_CFI_DEVICE_CMD_WRITE_STATUS, FU_CFI_DEVICE_CMD_BLOCK_ERASE, /*< private >*/ FU_CFI_DEVICE_CMD_LAST } FuCfiDeviceCmd; FuCfiDevice * fu_cfi_device_new(FuContext *ctx, const gchar *flash_id) G_GNUC_NON_NULL(1); const gchar * fu_cfi_device_get_flash_id(FuCfiDevice *self) G_GNUC_NON_NULL(1); void fu_cfi_device_set_flash_id(FuCfiDevice *self, const gchar *flash_id) G_GNUC_NON_NULL(1); guint64 fu_cfi_device_get_size(FuCfiDevice *self) G_GNUC_NON_NULL(1); void fu_cfi_device_set_size(FuCfiDevice *self, guint64 size) G_GNUC_NON_NULL(1); guint32 fu_cfi_device_get_page_size(FuCfiDevice *self) G_GNUC_NON_NULL(1); void fu_cfi_device_set_page_size(FuCfiDevice *self, guint32 page_size) G_GNUC_NON_NULL(1); guint32 fu_cfi_device_get_sector_size(FuCfiDevice *self) G_GNUC_NON_NULL(1); void fu_cfi_device_set_sector_size(FuCfiDevice *self, guint32 sector_size) G_GNUC_NON_NULL(1); guint32 fu_cfi_device_get_block_size(FuCfiDevice *self) G_GNUC_NON_NULL(1); void fu_cfi_device_set_block_size(FuCfiDevice *self, guint32 block_size) G_GNUC_NON_NULL(1); gboolean fu_cfi_device_get_cmd(FuCfiDevice *self, FuCfiDeviceCmd cmd, guint8 *value, GError **error) G_GNUC_NON_NULL(1); gboolean fu_cfi_device_send_command(FuCfiDevice *self, const guint8 *wbuf, gsize wbufsz, guint8 *rbuf, gsize rbufsz, FuProgress *progress, GError **error) G_GNUC_NON_NULL(1); gboolean fu_cfi_device_chip_select(FuCfiDevice *self, gboolean value, GError **error) G_GNUC_NON_NULL(1); FuDeviceLocker * fu_cfi_device_chip_select_locker_new(FuCfiDevice *self, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-cfu-firmware.rs000066400000000000000000000011271460375044200213430ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, ParseBytes)] struct CfuPayload { addr: u32le, size: u8, } #[repr(u8)] enum CfuOfferComponentId { NotUsed = 0, // values in between are either valid or reserved OfferInformation = 0xFF, OfferInformation2 = 0xFE, } #[derive(New, ParseBytes)] struct CfuOffer { segment_number: u8, flags1: u8, component_id: CfuOfferComponentId, token: u8, version: u32le, compat_variant_mask: u32le, flags2: u8, flags3: u8, product_id: u16le, } fwupd-1.9.16/libfwupdplugin/fu-cfu-offer.c000066400000000000000000000345711460375044200204370ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-byte-array.h" #include "fu-cfu-firmware-struct.h" #include "fu-cfu-offer.h" #include "fu-common.h" #include "fu-string.h" #include "fu-version-common.h" /** * FuCfuOffer: * * A CFU offer. This is a 16 byte blob which contains enough data for the device to either accept * or refuse a firmware payload. The offer may be loaded from disk, network, or even constructed * manually. There is much left to how the specific firmware implements CFU, and it's expected * that multiple different plugins will use this offer in different ways. * * Documented: https://docs.microsoft.com/en-us/windows-hardware/drivers/cfu/cfu-specification * * See also: [class@FuFirmware] */ typedef struct { guint8 segment_number; gboolean force_immediate_reset; gboolean force_ignore_version; guint8 component_id; guint8 token; guint32 hw_variant; guint8 protocol_revision; guint8 bank; guint8 milestone; guint16 product_id; } FuCfuOfferPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuCfuOffer, fu_cfu_offer, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_cfu_offer_get_instance_private(o)) static void fu_cfu_offer_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuCfuOffer *self = FU_CFU_OFFER(firmware); FuCfuOfferPrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "segment_number", priv->segment_number); fu_xmlb_builder_insert_kb(bn, "force_immediate_reset", priv->force_immediate_reset); fu_xmlb_builder_insert_kb(bn, "force_ignore_version", priv->force_ignore_version); fu_xmlb_builder_insert_kx(bn, "component_id", priv->component_id); fu_xmlb_builder_insert_kx(bn, "token", priv->token); fu_xmlb_builder_insert_kx(bn, "hw_variant", priv->hw_variant); fu_xmlb_builder_insert_kx(bn, "protocol_revision", priv->protocol_revision); fu_xmlb_builder_insert_kx(bn, "bank", priv->bank); fu_xmlb_builder_insert_kx(bn, "milestone", priv->milestone); fu_xmlb_builder_insert_kx(bn, "product_id", priv->product_id); } /** * fu_cfu_offer_get_segment_number: * @self: a #FuCfuOffer * * Gets the part of the firmware that is being transferred. * * Returns: integer * * Since: 1.7.0 **/ guint8 fu_cfu_offer_get_segment_number(FuCfuOffer *self) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0); return priv->segment_number; } /** * fu_cfu_offer_get_force_immediate_reset: * @self: a #FuCfuOffer * * Gets if the in-situ firmware should reset into the new firmware immediately, rather than waiting * for the next time the device is replugged. * * Returns: boolean * * Since: 1.7.0 **/ gboolean fu_cfu_offer_get_force_immediate_reset(FuCfuOffer *self) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFU_OFFER(self), FALSE); return priv->force_immediate_reset; } /** * fu_cfu_offer_get_force_ignore_version: * @self: a #FuCfuOffer * * Gets if the in-situ firmware should ignore version mismatch (e.g. downgrade). * * Returns: boolean * * Since: 1.7.0 **/ gboolean fu_cfu_offer_get_force_ignore_version(FuCfuOffer *self) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFU_OFFER(self), FALSE); return priv->force_ignore_version; } /** * fu_cfu_offer_get_component_id: * @self: a #FuCfuOffer * * Gets the component in the device to apply the firmware update. * * Returns: integer * * Since: 1.7.0 **/ guint8 fu_cfu_offer_get_component_id(FuCfuOffer *self) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0); return priv->component_id; } /** * fu_cfu_offer_get_token: * @self: a #FuCfuOffer * * Gets the token to identify the user specific software making the offer. * * Returns: integer * * Since: 1.7.0 **/ guint8 fu_cfu_offer_get_token(FuCfuOffer *self) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0); return priv->token; } /** * fu_cfu_offer_get_hw_variant: * @self: a #FuCfuOffer * * Gets the hardware variant bitmask corresponding with compatible firmware. * * Returns: integer * * Since: 1.7.0 **/ guint32 fu_cfu_offer_get_hw_variant(FuCfuOffer *self) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0); return priv->hw_variant; } /** * fu_cfu_offer_get_protocol_revision: * @self: a #FuCfuOffer * * Gets the CFU protocol version. * * Returns: integer * * Since: 1.7.0 **/ guint8 fu_cfu_offer_get_protocol_revision(FuCfuOffer *self) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0); return priv->protocol_revision; } /** * fu_cfu_offer_get_bank: * @self: a #FuCfuOffer * * Gets the bank register, used if multiple banks are supported. * * Returns: integer * * Since: 1.7.0 **/ guint8 fu_cfu_offer_get_bank(FuCfuOffer *self) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0); return priv->bank; } /** * fu_cfu_offer_get_milestone: * @self: a #FuCfuOffer * * Gets the milestone, which can be used as a version for example EV1, EVT etc. * * Returns: integer * * Since: 1.7.0 **/ guint8 fu_cfu_offer_get_milestone(FuCfuOffer *self) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0); return priv->milestone; } /** * fu_cfu_offer_get_product_id: * @self: a #FuCfuOffer * * Gets the product ID for this CFU image. * * Returns: integer * * Since: 1.7.0 **/ guint16 fu_cfu_offer_get_product_id(FuCfuOffer *self) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CFU_OFFER(self), 0x0); return priv->product_id; } /** * fu_cfu_offer_set_segment_number: * @self: a #FuCfuOffer * @segment_number: integer * * Sets the part of the firmware that is being transferred. * * Since: 1.7.0 **/ void fu_cfu_offer_set_segment_number(FuCfuOffer *self, guint8 segment_number) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFU_OFFER(self)); priv->segment_number = segment_number; } /** * fu_cfu_offer_set_force_immediate_reset: * @self: a #FuCfuOffer * @force_immediate_reset: boolean * * Sets if the in-situ firmware should reset into the new firmware immediately, rather than waiting * for the next time the device is replugged. * * Since: 1.7.0 **/ void fu_cfu_offer_set_force_immediate_reset(FuCfuOffer *self, gboolean force_immediate_reset) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFU_OFFER(self)); priv->force_immediate_reset = force_immediate_reset; } /** * fu_cfu_offer_set_force_ignore_version: * @self: a #FuCfuOffer * @force_ignore_version: boolean * * Sets if the in-situ firmware should ignore version mismatch (e.g. downgrade). * * Since: 1.7.0 **/ void fu_cfu_offer_set_force_ignore_version(FuCfuOffer *self, gboolean force_ignore_version) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFU_OFFER(self)); priv->force_ignore_version = force_ignore_version; } /** * fu_cfu_offer_set_component_id: * @self: a #FuCfuOffer * @component_id: integer * * Sets the component in the device to apply the firmware update. * * Since: 1.7.0 **/ void fu_cfu_offer_set_component_id(FuCfuOffer *self, guint8 component_id) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFU_OFFER(self)); priv->component_id = component_id; } /** * fu_cfu_offer_set_token: * @self: a #FuCfuOffer * @token: integer * * Sets the token to identify the user specific software making the offer. * * Since: 1.7.0 **/ void fu_cfu_offer_set_token(FuCfuOffer *self, guint8 token) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFU_OFFER(self)); priv->token = token; } /** * fu_cfu_offer_set_hw_variant: * @self: a #FuCfuOffer * @hw_variant: integer * * Sets the hardware variant bitmask corresponding with compatible firmware. * * Since: 1.7.0 **/ void fu_cfu_offer_set_hw_variant(FuCfuOffer *self, guint32 hw_variant) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFU_OFFER(self)); priv->hw_variant = hw_variant; } /** * fu_cfu_offer_set_protocol_revision: * @self: a #FuCfuOffer * @protocol_revision: integer * * Sets the CFU protocol version. * * Since: 1.7.0 **/ void fu_cfu_offer_set_protocol_revision(FuCfuOffer *self, guint8 protocol_revision) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFU_OFFER(self)); g_return_if_fail(protocol_revision <= 0b1111); priv->protocol_revision = protocol_revision; } /** * fu_cfu_offer_set_bank: * @self: a #FuCfuOffer * @bank: integer * * Sets bank register, used if multiple banks are supported. * * Since: 1.7.0 **/ void fu_cfu_offer_set_bank(FuCfuOffer *self, guint8 bank) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFU_OFFER(self)); g_return_if_fail(bank <= 0b11); priv->bank = bank; } /** * fu_cfu_offer_set_milestone: * @self: a #FuCfuOffer * @milestone: integer * * Sets the milestone, which can be used as a version for example EV1, EVT etc. * * Since: 1.7.0 **/ void fu_cfu_offer_set_milestone(FuCfuOffer *self, guint8 milestone) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFU_OFFER(self)); g_return_if_fail(milestone <= 0b111); priv->milestone = milestone; } /** * fu_cfu_offer_set_product_id: * @self: a #FuCfuOffer * @product_id: integer * * Sets the product ID for this CFU image. * * Since: 1.7.0 **/ void fu_cfu_offer_set_product_id(FuCfuOffer *self, guint16 product_id) { FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CFU_OFFER(self)); priv->product_id = product_id; } static gboolean fu_cfu_offer_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuCfuOffer *self = FU_CFU_OFFER(firmware); FuCfuOfferPrivate *priv = GET_PRIVATE(self); guint8 flags1; guint8 flags2; guint8 flags3; g_autoptr(GByteArray) st = NULL; g_autofree gchar *version = NULL; /* parse */ st = fu_struct_cfu_offer_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; priv->segment_number = fu_struct_cfu_offer_get_segment_number(st); priv->component_id = fu_struct_cfu_offer_get_component_id(st); priv->token = fu_struct_cfu_offer_get_token(st); priv->hw_variant = fu_struct_cfu_offer_get_compat_variant_mask(st); priv->product_id = fu_struct_cfu_offer_get_product_id(st); /* AA.BBCC.DD */ version = fu_version_from_uint32(fu_struct_cfu_offer_get_version(st), FWUPD_VERSION_FORMAT_SURFACE); fu_firmware_set_version(firmware, version); fu_firmware_set_version_raw(firmware, fu_struct_cfu_offer_get_version(st)); /* component info */ flags1 = fu_struct_cfu_offer_get_flags1(st); priv->force_ignore_version = (flags1 & 0b10000000) > 0; priv->force_immediate_reset = (flags1 & 0b01000000) > 0; /* product info */ flags2 = fu_struct_cfu_offer_get_flags2(st); priv->protocol_revision = (flags2 >> 4) & 0b1111; priv->bank = (flags2 >> 2) & 0b11; flags3 = fu_struct_cfu_offer_get_flags3(st); priv->milestone = (flags3 >> 5) & 0b111; /* success */ return TRUE; } static GByteArray * fu_cfu_offer_write(FuFirmware *firmware, GError **error) { FuCfuOffer *self = FU_CFU_OFFER(firmware); FuCfuOfferPrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) st = fu_struct_cfu_offer_new(); /* component info */ fu_struct_cfu_offer_set_segment_number(st, priv->segment_number); fu_struct_cfu_offer_set_flags1(st, priv->force_ignore_version << 7 | (priv->force_immediate_reset << 6)); fu_struct_cfu_offer_set_component_id(st, priv->component_id); fu_struct_cfu_offer_set_token(st, priv->token); /* version */ fu_struct_cfu_offer_set_version(st, fu_firmware_get_version_raw(firmware)); fu_struct_cfu_offer_set_compat_variant_mask(st, priv->hw_variant); /* product info */ fu_struct_cfu_offer_set_flags2(st, (priv->protocol_revision << 4) | (priv->bank << 2)); fu_struct_cfu_offer_set_flags3(st, priv->milestone << 5); fu_struct_cfu_offer_set_product_id(st, priv->product_id); /* success */ return g_steal_pointer(&st); } static gboolean fu_cfu_offer_build(FuFirmware *firmware, XbNode *n, GError **error) { FuCfuOffer *self = FU_CFU_OFFER(firmware); FuCfuOfferPrivate *priv = GET_PRIVATE(self); guint64 tmp; const gchar *tmpb; /* optional properties */ tmp = xb_node_query_text_as_uint(n, "segment_number", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->segment_number = tmp; tmpb = xb_node_query_text(n, "force_immediate_reset", NULL); if (tmpb != NULL) { if (!fu_strtobool(tmpb, &priv->force_immediate_reset, error)) return FALSE; } tmpb = xb_node_query_text(n, "force_ignore_version", NULL); if (tmpb != NULL) { if (!fu_strtobool(tmpb, &priv->force_ignore_version, error)) return FALSE; } tmp = xb_node_query_text_as_uint(n, "component_id", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->component_id = tmp; tmp = xb_node_query_text_as_uint(n, "token", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->token = tmp; tmp = xb_node_query_text_as_uint(n, "hw_variant", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) priv->hw_variant = tmp; tmp = xb_node_query_text_as_uint(n, "protocol_revision", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->protocol_revision = tmp; tmp = xb_node_query_text_as_uint(n, "bank", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->bank = tmp; tmp = xb_node_query_text_as_uint(n, "milestone", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->milestone = tmp; tmp = xb_node_query_text_as_uint(n, "product_id", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) priv->product_id = tmp; /* success */ return TRUE; } static void fu_cfu_offer_init(FuCfuOffer *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); } static void fu_cfu_offer_class_init(FuCfuOfferClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->export = fu_cfu_offer_export; klass_firmware->parse = fu_cfu_offer_parse; klass_firmware->write = fu_cfu_offer_write; klass_firmware->build = fu_cfu_offer_build; } /** * fu_cfu_offer_new: * * Creates a new #FuFirmware for a CFU offer * * Since: 1.7.0 **/ FuFirmware * fu_cfu_offer_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_CFU_OFFER, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-cfu-offer.h000066400000000000000000000040561460375044200204370ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_CFU_OFFER (fu_cfu_offer_get_type()) G_DECLARE_DERIVABLE_TYPE(FuCfuOffer, fu_cfu_offer, FU, CFU_OFFER, FuFirmware) struct _FuCfuOfferClass { FuFirmwareClass parent_class; }; FuFirmware * fu_cfu_offer_new(void); guint8 fu_cfu_offer_get_segment_number(FuCfuOffer *self) G_GNUC_NON_NULL(1); gboolean fu_cfu_offer_get_force_immediate_reset(FuCfuOffer *self) G_GNUC_NON_NULL(1); gboolean fu_cfu_offer_get_force_ignore_version(FuCfuOffer *self) G_GNUC_NON_NULL(1); guint8 fu_cfu_offer_get_component_id(FuCfuOffer *self) G_GNUC_NON_NULL(1); guint8 fu_cfu_offer_get_token(FuCfuOffer *self) G_GNUC_NON_NULL(1); guint32 fu_cfu_offer_get_hw_variant(FuCfuOffer *self) G_GNUC_NON_NULL(1); guint8 fu_cfu_offer_get_protocol_revision(FuCfuOffer *self) G_GNUC_NON_NULL(1); guint8 fu_cfu_offer_get_bank(FuCfuOffer *self) G_GNUC_NON_NULL(1); guint8 fu_cfu_offer_get_milestone(FuCfuOffer *self) G_GNUC_NON_NULL(1); guint16 fu_cfu_offer_get_product_id(FuCfuOffer *self) G_GNUC_NON_NULL(1); void fu_cfu_offer_set_segment_number(FuCfuOffer *self, guint8 segment_number) G_GNUC_NON_NULL(1); void fu_cfu_offer_set_force_immediate_reset(FuCfuOffer *self, gboolean force_immediate_reset) G_GNUC_NON_NULL(1); void fu_cfu_offer_set_force_ignore_version(FuCfuOffer *self, gboolean force_ignore_version) G_GNUC_NON_NULL(1); void fu_cfu_offer_set_component_id(FuCfuOffer *self, guint8 component_id) G_GNUC_NON_NULL(1); void fu_cfu_offer_set_token(FuCfuOffer *self, guint8 token) G_GNUC_NON_NULL(1); void fu_cfu_offer_set_hw_variant(FuCfuOffer *self, guint32 hw_variant) G_GNUC_NON_NULL(1); void fu_cfu_offer_set_protocol_revision(FuCfuOffer *self, guint8 protocol_revision) G_GNUC_NON_NULL(1); void fu_cfu_offer_set_bank(FuCfuOffer *self, guint8 bank) G_GNUC_NON_NULL(1); void fu_cfu_offer_set_milestone(FuCfuOffer *self, guint8 milestone) G_GNUC_NON_NULL(1); void fu_cfu_offer_set_product_id(FuCfuOffer *self, guint16 product_id) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-cfu-payload.c000066400000000000000000000056101460375044200207570ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-cfu-firmware-struct.h" #include "fu-cfu-payload.h" #include "fu-common.h" /** * FuCfuPayload: * * A CFU payload. This contains of a variable number of blocks, each containing the address, size * and the chunk data. The chunks do not have to be the same size, and the address ranges do not * have to be continuous. * * Documented: https://docs.microsoft.com/en-us/windows-hardware/drivers/cfu/cfu-specification * * See also: [class@FuFirmware] */ G_DEFINE_TYPE(FuCfuPayload, fu_cfu_payload, FU_TYPE_FIRMWARE) static gboolean fu_cfu_payload_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { gsize bufsz = g_bytes_get_size(fw); /* process into chunks */ while (offset < bufsz) { guint8 chunk_size = 0; g_autoptr(FuChunk) chk = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GByteArray) st = NULL; st = fu_struct_cfu_payload_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; offset += st->len; chunk_size = fu_struct_cfu_payload_get_size(st); if (chunk_size == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "payload size was invalid"); return FALSE; } blob = fu_bytes_new_offset(fw, offset, chunk_size, error); if (blob == NULL) return FALSE; chk = fu_chunk_bytes_new(blob); fu_chunk_set_address(chk, fu_struct_cfu_payload_get_addr(st)); fu_firmware_add_chunk(firmware, chk); /* next! */ offset += chunk_size; } /* success */ return TRUE; } static GByteArray * fu_cfu_payload_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GPtrArray) chunks = NULL; chunks = fu_firmware_get_chunks(firmware, error); if (chunks == NULL) return NULL; for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); g_autoptr(GByteArray) st = fu_struct_cfu_payload_new(); fu_struct_cfu_payload_set_addr(st, fu_chunk_get_address(chk)); fu_struct_cfu_payload_set_size(st, fu_chunk_get_data_sz(chk)); g_byte_array_append(buf, st->data, st->len); g_byte_array_append(buf, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); } return g_steal_pointer(&buf); } static void fu_cfu_payload_init(FuCfuPayload *self) { } static void fu_cfu_payload_class_init(FuCfuPayloadClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_cfu_payload_parse; klass_firmware->write = fu_cfu_payload_write; } /** * fu_cfu_payload_new: * * Creates a new #FuFirmware for a CFU payload * * Since: 1.7.0 **/ FuFirmware * fu_cfu_payload_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_CFU_PAYLOAD, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-cfu-payload.h000066400000000000000000000006071460375044200207650ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_CFU_PAYLOAD (fu_cfu_payload_get_type()) G_DECLARE_DERIVABLE_TYPE(FuCfuPayload, fu_cfu_payload, FU, CFU_PAYLOAD, FuFirmware) struct _FuCfuPayloadClass { FuFirmwareClass parent_class; }; FuFirmware * fu_cfu_payload_new(void); fwupd-1.9.16/libfwupdplugin/fu-chunk-array.c000066400000000000000000000060371460375044200210030ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuChunkArray" #include "config.h" #include "fu-chunk-array.h" /** * FuChunkArray: * * Create chunked data with address and index as required. * * NOTE: If you need a page size, either use fu_chunk_array_new() or use two #FuChunkArray's -- * e.g. once to split to page size, and once to split to packet size. */ struct _FuChunkArray { GObject parent_instance; GBytes *blob; guint32 addr_start; guint32 packet_sz; guint total_chunks; }; G_DEFINE_TYPE(FuChunkArray, fu_chunk_array, G_TYPE_OBJECT) /** * fu_chunk_array_length: * @self: a #FuChunkArray * * Gets the number of chunks. * * Returns: integer * * Since: 1.9.6 **/ guint fu_chunk_array_length(FuChunkArray *self) { g_return_val_if_fail(FU_IS_CHUNK_ARRAY(self), G_MAXUINT); return self->total_chunks; } /** * fu_chunk_array_index: * @self: a #FuChunkArray * @idx: the chunk index * * Gets the next chunk. * * Returns: (transfer full): a #FuChunk or %NULL if not valid * * Since: 1.9.6 **/ FuChunk * fu_chunk_array_index(FuChunkArray *self, guint idx) { gsize length; gsize offset; g_autoptr(FuChunk) chk = NULL; g_autoptr(GBytes) blob_chk = NULL; g_return_val_if_fail(FU_IS_CHUNK_ARRAY(self), NULL); /* calculate offset and length */ offset = (gsize)idx * (gsize)self->packet_sz; if (offset >= g_bytes_get_size(self->blob)) return NULL; length = MIN(self->packet_sz, g_bytes_get_size(self->blob) - offset); if (length == 0) return NULL; /* create new chunk */ blob_chk = g_bytes_new_from_bytes(self->blob, offset, length); chk = fu_chunk_bytes_new(blob_chk); fu_chunk_set_idx(chk, idx); fu_chunk_set_address(chk, self->addr_start + offset); return g_steal_pointer(&chk); } /** * fu_chunk_array_new_from_bytes: * @blob: data * @addr_start: the hardware address offset, or 0x0 * @packet_sz: the packet size, or 0x0 * * Chunks a linear blob of memory into packets, ensuring each packet is less that a specific * transfer size. * * Returns: (transfer full): a #FuChunkArray * * Since: 1.9.6 **/ FuChunkArray * fu_chunk_array_new_from_bytes(GBytes *blob, guint32 addr_start, guint32 packet_sz) { g_autoptr(FuChunkArray) self = g_object_new(FU_TYPE_CHUNK_ARRAY, NULL); g_return_val_if_fail(blob != NULL, NULL); self->addr_start = addr_start; self->packet_sz = packet_sz; self->blob = g_bytes_ref(blob); self->total_chunks = g_bytes_get_size(self->blob) / self->packet_sz; if (g_bytes_get_size(self->blob) % self->packet_sz != 0) self->total_chunks++; return g_steal_pointer(&self); } static void fu_chunk_array_finalize(GObject *object) { FuChunkArray *self = FU_CHUNK_ARRAY(object); if (self->blob != NULL) g_bytes_unref(self->blob); G_OBJECT_CLASS(fu_chunk_array_parent_class)->finalize(object); } static void fu_chunk_array_class_init(FuChunkArrayClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_chunk_array_finalize; } static void fu_chunk_array_init(FuChunkArray *self) { } fwupd-1.9.16/libfwupdplugin/fu-chunk-array.h000066400000000000000000000010741460375044200210040ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-chunk.h" #define FU_TYPE_CHUNK_ARRAY (fu_chunk_array_get_type()) G_DECLARE_FINAL_TYPE(FuChunkArray, fu_chunk_array, FU, CHUNK_ARRAY, GObject) FuChunkArray * fu_chunk_array_new_from_bytes(GBytes *blob, guint32 addr_start, guint32 packet_sz) G_GNUC_NON_NULL(1); guint fu_chunk_array_length(FuChunkArray *self) G_GNUC_NON_NULL(1); FuChunk * fu_chunk_array_index(FuChunkArray *self, guint idx) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-chunk-private.h000066400000000000000000000006051460375044200213370ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-chunk.h" #include "fu-firmware.h" void fu_chunk_export(FuChunk *self, FuFirmwareExportFlags flags, XbBuilderNode *bn) G_GNUC_NON_NULL(1, 3); gboolean fu_chunk_build(FuChunk *self, XbNode *n, GError **error) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-chunk.c000066400000000000000000000275771460375044200177030ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuChunk" #include "config.h" #include #include "fu-bytes.h" #include "fu-chunk-private.h" #include "fu-common.h" #include "fu-mem.h" /** * FuChunk: * * A optionally mutable packet of chunked data with address, page and index. */ struct _FuChunk { GObject parent_instance; guint32 idx; guint32 page; guint32 address; const guint8 *data; guint32 data_sz; gboolean is_mutable; GBytes *bytes; }; G_DEFINE_TYPE(FuChunk, fu_chunk, G_TYPE_OBJECT) /** * fu_chunk_set_idx: * @self: a #FuChunk * @idx: index, starting at 0 * * Sets the index of the chunk. * * Since: 1.5.6 **/ void fu_chunk_set_idx(FuChunk *self, guint32 idx) { g_return_if_fail(FU_IS_CHUNK(self)); self->idx = idx; } /** * fu_chunk_get_idx: * @self: a #FuChunk * * Gets the index of the chunk. * * Returns: index * * Since: 1.5.6 **/ guint32 fu_chunk_get_idx(FuChunk *self) { g_return_val_if_fail(FU_IS_CHUNK(self), G_MAXUINT32); return self->idx; } /** * fu_chunk_set_page: * @self: a #FuChunk * @page: page number, starting at 0 * * Sets the page of the chunk. * * Since: 1.5.6 **/ void fu_chunk_set_page(FuChunk *self, guint32 page) { g_return_if_fail(FU_IS_CHUNK(self)); self->page = page; } /** * fu_chunk_get_page: * @self: a #FuChunk * * Gets the page of the chunk. * * Returns: page * * Since: 1.5.6 **/ guint32 fu_chunk_get_page(FuChunk *self) { g_return_val_if_fail(FU_IS_CHUNK(self), G_MAXUINT32); return self->page; } /** * fu_chunk_set_address: * @self: a #FuChunk * @address: memory address * * Sets the address of the chunk. * * Since: 1.5.6 **/ void fu_chunk_set_address(FuChunk *self, guint32 address) { g_return_if_fail(FU_IS_CHUNK(self)); self->address = address; } /** * fu_chunk_get_address: * @self: a #FuChunk * * Gets the address of the chunk. * * Returns: address * * Since: 1.5.6 **/ guint32 fu_chunk_get_address(FuChunk *self) { g_return_val_if_fail(FU_IS_CHUNK(self), G_MAXUINT32); return self->address; } /** * fu_chunk_get_data: * @self: a #FuChunk * * Gets the data of the chunk. * * Returns: bytes * * Since: 1.5.6 **/ const guint8 * fu_chunk_get_data(FuChunk *self) { g_return_val_if_fail(FU_IS_CHUNK(self), NULL); return self->data; } /** * fu_chunk_get_data_out: * @self: a #FuChunk * * Gets the mutable data of the chunk. * * WARNING: At the moment fu_chunk_get_data_out() returns the same data as * fu_chunk_get_data() in all cases. The caller should verify the data passed to * fu_chunk_array_new() is also writable (i.e. not `const` or `mmap`) before * using this function. * * Returns: (transfer none): bytes * * Since: 1.5.6 **/ guint8 * fu_chunk_get_data_out(FuChunk *self) { g_return_val_if_fail(FU_IS_CHUNK(self), NULL); /* warn, but allow to proceed */ if (!self->is_mutable) { g_critical("calling fu_chunk_get_data_out() from immutable chunk"); self->is_mutable = TRUE; } return (guint8 *)self->data; } /** * fu_chunk_get_data_sz: * @self: a #FuChunk * * Gets the data size of the chunk. * * Returns: size in bytes * * Since: 1.5.6 **/ guint32 fu_chunk_get_data_sz(FuChunk *self) { g_return_val_if_fail(FU_IS_CHUNK(self), G_MAXUINT32); return self->data_sz; } /** * fu_chunk_set_bytes: * @self: a #FuChunk * @bytes: (nullable): data * * Sets the data to use for the chunk. * * Since: 1.5.6 **/ void fu_chunk_set_bytes(FuChunk *self, GBytes *bytes) { g_return_if_fail(FU_IS_CHUNK(self)); /* not changed */ if (self->bytes == bytes) return; if (self->bytes != NULL) { g_bytes_unref(self->bytes); self->bytes = NULL; } if (bytes != NULL) { self->bytes = g_bytes_ref(bytes); self->data = g_bytes_get_data(bytes, NULL); self->data_sz = g_bytes_get_size(bytes); } } /** * fu_chunk_get_bytes: * @self: a #FuChunk * * Gets the data of the chunk. * * Returns: (transfer full): data * * Since: 1.5.6 **/ GBytes * fu_chunk_get_bytes(FuChunk *self) { g_return_val_if_fail(FU_IS_CHUNK(self), NULL); if (self->bytes != NULL) return g_bytes_ref(self->bytes); return g_bytes_new_static(self->data, self->data_sz); } /** * fu_chunk_new: * @idx: the packet number * @page: the hardware memory page * @address: the address *within* the page * @data: the data * @data_sz: size of @data_sz * * Creates a new packet of chunked data. * * Returns: (transfer full): a #FuChunk * * Since: 1.1.2 **/ FuChunk * fu_chunk_new(guint32 idx, guint32 page, guint32 address, const guint8 *data, guint32 data_sz) { FuChunk *self = g_object_new(FU_TYPE_CHUNK, NULL); self->idx = idx; self->page = page; self->address = address; self->data = data; self->data_sz = data_sz; return self; } /** * fu_chunk_bytes_new: * @bytes: (nullable): data * * Creates a new packet of data. * * Returns: (transfer full): a #FuChunk * * Since: 1.5.6 **/ FuChunk * fu_chunk_bytes_new(GBytes *bytes) { FuChunk *self = g_object_new(FU_TYPE_CHUNK, NULL); fu_chunk_set_bytes(self, bytes); return self; } void fu_chunk_export(FuChunk *self, FuFirmwareExportFlags flags, XbBuilderNode *bn) { fu_xmlb_builder_insert_kx(bn, "idx", self->idx); fu_xmlb_builder_insert_kx(bn, "page", self->page); fu_xmlb_builder_insert_kx(bn, "addr", self->address); if (self->data != NULL) { g_autofree gchar *datastr = NULL; g_autofree gchar *dataszstr = g_strdup_printf("0x%x", (guint)self->data_sz); if (flags & FU_FIRMWARE_EXPORT_FLAG_ASCII_DATA) { datastr = fu_memstrsafe(self->data, self->data_sz, 0x0, MIN(self->data_sz, 16), NULL); } else { datastr = g_base64_encode(self->data, self->data_sz); } xb_builder_node_insert_text(bn, "data", datastr, "size", dataszstr, NULL); } } /** * fu_chunk_to_string: * @self: a #FuChunk * * Converts the chunked packet to a string representation. * * Returns: (transfer full): a string * * Since: 1.1.2 **/ gchar * fu_chunk_to_string(FuChunk *self) { g_autoptr(XbBuilderNode) bn = xb_builder_node_new("chunk"); fu_chunk_export(self, FU_FIRMWARE_EXPORT_FLAG_ASCII_DATA, bn); return xb_builder_node_export(bn, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY | XB_NODE_EXPORT_FLAG_FORMAT_INDENT, NULL); } /** * fu_chunk_array_to_string: * @chunks: (element-type FuChunk): array of chunks * * Converts all the chunked packets in an array to a string representation. * * Returns: (transfer full): a string * * Since: 1.0.1 **/ gchar * fu_chunk_array_to_string(GPtrArray *chunks) { g_autoptr(XbBuilderNode) bn = xb_builder_node_new("chunks"); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "chunk", NULL); fu_chunk_export(chk, FU_FIRMWARE_EXPORT_FLAG_ASCII_DATA, bc); } return xb_builder_node_export(bn, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY | XB_NODE_EXPORT_FLAG_FORMAT_INDENT, NULL); } /** * fu_chunk_array_mutable_new: * @data: a mutable blob of memory * @data_sz: size of @data_sz * @addr_start: the hardware address offset, or 0 * @page_sz: the hardware page size, or 0 * @packet_sz: the transfer size, or 0 * * Chunks a mutable blob of memory into packets, ensuring each packet does not * cross a package boundary and is less that a specific transfer size. * * Returns: (transfer container) (element-type FuChunk): array of packets * * Since: 1.5.6 **/ GPtrArray * fu_chunk_array_mutable_new(guint8 *data, guint32 data_sz, guint32 addr_start, guint32 page_sz, guint32 packet_sz) { GPtrArray *chunks; g_return_val_if_fail(data != NULL, NULL); g_return_val_if_fail(data_sz > 0, NULL); chunks = fu_chunk_array_new(data, data_sz, addr_start, page_sz, packet_sz); if (chunks == NULL) return NULL; for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); chk->is_mutable = TRUE; } return chunks; } /** * fu_chunk_array_new: * @data: (nullable): an optional linear blob of memory * @data_sz: size of @data_sz * @addr_start: the hardware address offset, or 0 * @page_sz: the hardware page size, or 0 * @packet_sz: the transfer size, or 0 * * Chunks a linear blob of memory into packets, ensuring each packet does not * cross a package boundary and is less that a specific transfer size. * * Returns: (transfer container) (element-type FuChunk): array of packets * * Since: 1.1.2 **/ GPtrArray * fu_chunk_array_new(const guint8 *data, guint32 data_sz, guint32 addr_start, guint32 page_sz, guint32 packet_sz) { GPtrArray *chunks = NULL; guint32 page_old = G_MAXUINT32; guint32 idx; guint32 last_flush = 0; chunks = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); if (data_sz == 0) return chunks; for (idx = 1; idx < data_sz; idx++) { guint32 page = 0; if (page_sz > 0) page = (addr_start + idx) / page_sz; if (page_old == G_MAXUINT32) { page_old = page; } else if (page != page_old) { const guint8 *data_offset = data != NULL ? data + last_flush : 0x0; guint32 address_offset = addr_start + last_flush; if (page_sz > 0) address_offset %= page_sz; g_ptr_array_add(chunks, fu_chunk_new(chunks->len, page_old, address_offset, data_offset, idx - last_flush)); last_flush = idx; page_old = page; continue; } if (packet_sz > 0 && idx - last_flush >= packet_sz) { const guint8 *data_offset = data != NULL ? data + last_flush : 0x0; guint32 address_offset = addr_start + last_flush; if (page_sz > 0) address_offset %= page_sz; g_ptr_array_add(chunks, fu_chunk_new(chunks->len, page, address_offset, data_offset, idx - last_flush)); last_flush = idx; continue; } } if (last_flush != idx) { const guint8 *data_offset = data != NULL ? data + last_flush : 0x0; guint32 address_offset = addr_start + last_flush; guint32 page = 0; if (page_sz > 0) { address_offset %= page_sz; page = (addr_start + (idx - 1)) / page_sz; } g_ptr_array_add(chunks, fu_chunk_new(chunks->len, page, address_offset, data_offset, data_sz - last_flush)); } #ifndef SUPPORTED_BUILD /* show the programmer a warning */ if (page_sz == 0x0 && chunks->len > 10000) { g_warning("fu_chunk_array_new() generated a lot of chunks (%u), " "maybe use FuChunkArray instead?", chunks->len); } #endif return chunks; } /* private */ gboolean fu_chunk_build(FuChunk *self, XbNode *n, GError **error) { guint64 tmp; g_autoptr(XbNode) data = NULL; g_return_val_if_fail(FU_IS_CHUNK(self), FALSE); g_return_val_if_fail(XB_IS_NODE(n), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* optional properties */ tmp = xb_node_query_text_as_uint(n, "idx", NULL); if (tmp != G_MAXUINT64) self->idx = tmp; tmp = xb_node_query_text_as_uint(n, "page", NULL); if (tmp != G_MAXUINT64) self->page = tmp; tmp = xb_node_query_text_as_uint(n, "addr", NULL); if (tmp != G_MAXUINT64) self->address = tmp; data = xb_node_query_first(n, "data", NULL); if (data != NULL && xb_node_get_text(data) != NULL) { gsize bufsz = 0; g_autofree guchar *buf = NULL; g_autoptr(GBytes) blob = NULL; buf = g_base64_decode(xb_node_get_text(data), &bufsz); blob = g_bytes_new(buf, bufsz); fu_chunk_set_bytes(self, blob); } else if (data != NULL) { g_autoptr(GBytes) blob = NULL; blob = g_bytes_new(NULL, 0); fu_chunk_set_bytes(self, blob); } /* success */ return TRUE; } static void fu_chunk_finalize(GObject *object) { FuChunk *self = FU_CHUNK(object); if (self->bytes != NULL) g_bytes_unref(self->bytes); G_OBJECT_CLASS(fu_chunk_parent_class)->finalize(object); } static void fu_chunk_class_init(FuChunkClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_chunk_finalize; } static void fu_chunk_init(FuChunk *self) { } fwupd-1.9.16/libfwupdplugin/fu-chunk.h000066400000000000000000000030731460375044200176710ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CHUNK (fu_chunk_get_type()) G_DECLARE_FINAL_TYPE(FuChunk, fu_chunk, FU, CHUNK, GObject) FuChunk * fu_chunk_bytes_new(GBytes *bytes); void fu_chunk_set_idx(FuChunk *self, guint32 idx) G_GNUC_NON_NULL(1); guint32 fu_chunk_get_idx(FuChunk *self) G_GNUC_NON_NULL(1); void fu_chunk_set_page(FuChunk *self, guint32 page) G_GNUC_NON_NULL(1); guint32 fu_chunk_get_page(FuChunk *self) G_GNUC_NON_NULL(1); void fu_chunk_set_address(FuChunk *self, guint32 address) G_GNUC_NON_NULL(1); guint32 fu_chunk_get_address(FuChunk *self) G_GNUC_NON_NULL(1); const guint8 * fu_chunk_get_data(FuChunk *self) G_GNUC_NON_NULL(1); guint8 * fu_chunk_get_data_out(FuChunk *self) G_GNUC_NON_NULL(1); guint32 fu_chunk_get_data_sz(FuChunk *self) G_GNUC_NON_NULL(1); void fu_chunk_set_bytes(FuChunk *self, GBytes *bytes) G_GNUC_NON_NULL(1); GBytes * fu_chunk_get_bytes(FuChunk *self) G_GNUC_NON_NULL(1); FuChunk * fu_chunk_new(guint32 idx, guint32 page, guint32 address, const guint8 *data, guint32 data_sz); gchar * fu_chunk_to_string(FuChunk *self) G_GNUC_NON_NULL(1); gchar * fu_chunk_array_to_string(GPtrArray *chunks) G_GNUC_NON_NULL(1); GPtrArray * fu_chunk_array_new(const guint8 *data, guint32 data_sz, guint32 addr_start, guint32 page_sz, guint32 packet_sz); GPtrArray * fu_chunk_array_mutable_new(guint8 *data, guint32 data_sz, guint32 addr_start, guint32 page_sz, guint32 packet_sz) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-common-darwin.c000066400000000000000000000021341460375044200213230ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include #include "fu-common-private.h" GPtrArray * fu_common_get_block_devices(GError **error) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "getting block devices is not supported on Darwin"); return NULL; } guint64 fu_common_get_memory_size_impl(void) { gint mib[] = {CTL_HW, HW_MEMSIZE}; gint64 physical_memory = 0; gsize length = sizeof(physical_memory); sysctl(mib, 2, &physical_memory, &length, NULL, 0); return (guint64)physical_memory; } gchar * fu_common_get_kernel_cmdline_impl(GError **error) { gchar cmdline[1024] = {0}; gsize cmdlinesz = sizeof(cmdline); sysctlbyname("kern.bootargs", cmdline, &cmdlinesz, NULL, 0); return g_strndup(cmdline, sizeof(cmdline)); } gchar * fu_common_get_olson_timezone_id_impl(GError **error) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "getting the Olson timezone ID is not supported on Darwin"); return NULL; } fwupd-1.9.16/libfwupdplugin/fu-common-freebsd.c000066400000000000000000000060771460375044200214630ustar00rootroot00000000000000/* * Copyright (C) 2021 Sergii Dmytruk * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include #include #include #include "fu-common-private.h" #include "fu-kenv.h" /* bsdisks doesn't provide Manager object */ #define UDISKS_DBUS_PATH "/org/freedesktop/UDisks2" #define UDISKS_DBUS_MANAGER_INTERFACE "org.freedesktop.DBus.ObjectManager" #define UDISKS_BLOCK_DEVICE_PATH "/org/freedesktop/UDisks2/block_devices/" GPtrArray * fu_common_get_block_devices(GError **error) { GVariant *ifaces; const size_t device_path_len = strlen(UDISKS_BLOCK_DEVICE_PATH); const gchar *obj; g_autoptr(GVariant) output = NULL; g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GVariantIter) obj_iter = NULL; g_autoptr(GDBusConnection) connection = NULL; connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) { g_prefix_error(error, "failed to get system bus: "); return NULL; } proxy = g_dbus_proxy_new_sync(connection, G_DBUS_PROXY_FLAGS_NONE, NULL, UDISKS_DBUS_SERVICE, UDISKS_DBUS_PATH, UDISKS_DBUS_MANAGER_INTERFACE, NULL, error); if (proxy == NULL) { g_prefix_error(error, "failed to find %s: ", UDISKS_DBUS_SERVICE); return NULL; } devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); output = g_dbus_proxy_call_sync(proxy, "GetManagedObjects", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (output == NULL) { if (error != NULL) g_dbus_error_strip_remote_error(*error); g_prefix_error(error, "failed to call %s.%s(): ", UDISKS_DBUS_MANAGER_INTERFACE, "GetManagedObjects"); return NULL; } g_variant_get(output, "(a{oa{sa{sv}}})", &obj_iter); while (g_variant_iter_next(obj_iter, "{&o@a{sa{sv}}}", &obj, &ifaces)) { const gchar *iface; GVariant *props; GVariantIter iface_iter; if (strncmp(obj, UDISKS_BLOCK_DEVICE_PATH, device_path_len) != 0) continue; g_variant_iter_init(&iface_iter, ifaces); while (g_variant_iter_next(&iface_iter, "{&s@a{sv}}", &iface, &props)) { g_autoptr(GDBusProxy) proxy_blk = NULL; g_variant_unref(props); if (strcmp(iface, UDISKS_DBUS_INTERFACE_BLOCK) != 0) continue; proxy_blk = g_dbus_proxy_new_sync(connection, G_DBUS_PROXY_FLAGS_NONE, NULL, UDISKS_DBUS_SERVICE, obj, UDISKS_DBUS_INTERFACE_BLOCK, NULL, error); if (proxy_blk == NULL) { g_prefix_error(error, "failed to initialize d-bus proxy for %s: ", obj); return NULL; } g_ptr_array_add(devices, g_steal_pointer(&proxy_blk)); } g_variant_unref(ifaces); } return g_steal_pointer(&devices); } guint64 fu_common_get_memory_size_impl(void) { return (guint64)sysconf(_SC_PHYS_PAGES) * (guint64)sysconf(_SC_PAGE_SIZE); } gchar * fu_common_get_kernel_cmdline_impl(GError **error) { return fu_kenv_get_string("kernel_options", error); } fwupd-1.9.16/libfwupdplugin/fu-common-guid.c000066400000000000000000000011741460375044200207720ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include "fu-common-guid.h" /** * fu_common_guid_is_plausible: * @buf: a buffer of data * * Checks whether a chunk of memory looks like it could be a GUID. * * Returns: TRUE if it looks like a GUID, FALSE if not * * Since: 1.2.5 **/ gboolean fu_common_guid_is_plausible(const guint8 *buf) { guint guint_sum = 0; for (guint i = 0; i < 16; i++) guint_sum += buf[i]; if (guint_sum == 0x00) return FALSE; if (guint_sum < 0xff) return FALSE; return TRUE; } fwupd-1.9.16/libfwupdplugin/fu-common-guid.h000066400000000000000000000003111460375044200207670ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gboolean fu_common_guid_is_plausible(const guint8 *buf); fwupd-1.9.16/libfwupdplugin/fu-common-linux.c000066400000000000000000000134011460375044200211750ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include #include #include "fu-common-private.h" #include "fu-kernel.h" #include "fu-path.h" #define UDISKS_DBUS_PATH "/org/freedesktop/UDisks2/Manager" #define UDISKS_DBUS_MANAGER_INTERFACE "org.freedesktop.UDisks2.Manager" GPtrArray * fu_common_get_block_devices(GError **error) { GVariantBuilder builder; const gchar *obj; g_autoptr(GVariant) output = NULL; g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GVariantIter) obj_iter = NULL; g_autoptr(GDBusConnection) connection = NULL; connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) { g_prefix_error(error, "failed to get system bus: "); return NULL; } proxy = g_dbus_proxy_new_sync(connection, G_DBUS_PROXY_FLAGS_NONE, NULL, UDISKS_DBUS_SERVICE, UDISKS_DBUS_PATH, UDISKS_DBUS_MANAGER_INTERFACE, NULL, error); if (proxy == NULL) { g_prefix_error(error, "failed to find %s: ", UDISKS_DBUS_SERVICE); return NULL; } devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); output = g_dbus_proxy_call_sync(proxy, "GetBlockDevices", g_variant_new("(a{sv})", &builder), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (output == NULL) { if (error != NULL) g_dbus_error_strip_remote_error(*error); g_prefix_error(error, "failed to call %s.%s(): ", UDISKS_DBUS_MANAGER_INTERFACE, "GetBlockDevices"); return NULL; } g_variant_get(output, "(ao)", &obj_iter); while (g_variant_iter_next(obj_iter, "&o", &obj)) { g_autoptr(GDBusProxy) proxy_blk = NULL; proxy_blk = g_dbus_proxy_new_sync(connection, G_DBUS_PROXY_FLAGS_NONE, NULL, UDISKS_DBUS_SERVICE, obj, UDISKS_DBUS_INTERFACE_BLOCK, NULL, error); if (proxy_blk == NULL) { g_prefix_error(error, "failed to initialize d-bus proxy for %s: ", obj); return NULL; } g_ptr_array_add(devices, g_steal_pointer(&proxy_blk)); } return g_steal_pointer(&devices); } guint64 fu_common_get_memory_size_impl(void) { return (guint64)sysconf(_SC_PHYS_PAGES) * (guint64)sysconf(_SC_PAGE_SIZE); } gchar * fu_common_get_kernel_cmdline_impl(GError **error) { GHashTableIter iter; gpointer key; gpointer value; g_autoptr(GHashTable) hash = NULL; g_autoptr(GString) cmdline_safe = g_string_new(NULL); const gchar *ignore[] = { "", "apparmor", "audit", "auto", "boot", "BOOT_IMAGE", "console", "crashkernel", "cryptdevice", "cryptkey", "dm", "earlycon", "earlyprintk", "ether", "initrd", "ip", "LANG", "loglevel", "luks.key", "luks.name", "luks.options", "luks.uuid", "mitigations", "mount.usr", "mount.usrflags", "mount.usrfstype", "netdev", "netroot", "nfsaddrs", "nfs.nfs4_unique_id", "nfsroot", "noplymouth", "ostree", "quiet", "rd.dm.uuid", "rd.luks.allow-discards", "rd.luks.key", "rd.luks.name", "rd.luks.options", "rd.luks.uuid", "rd.lvm.lv", "rd.lvm.vg", "rd.md.uuid", "rd.systemd.mask", "rd.systemd.wants", "resume", "resumeflags", "rhgb", "ro", "root", "rootflags", "roothash", "rw", "security", "showopts", "splash", "swap", "systemd.mask", "systemd.show_status", "systemd.unit", "systemd.verity_root_data", "systemd.verity_root_hash", "systemd.wants", "udev.log_priority", "verbose", "vt.handoff", "zfs", NULL, /* last entry */ }; /* get a PII-safe kernel command line */ hash = fu_kernel_get_cmdline(error); if (hash == NULL) return NULL; for (guint i = 0; ignore[i] != NULL; i++) g_hash_table_remove(hash, ignore[i]); g_hash_table_iter_init(&iter, hash); while (g_hash_table_iter_next(&iter, &key, &value)) { if (cmdline_safe->len > 0) g_string_append(cmdline_safe, " "); if (value == NULL) { g_string_append(cmdline_safe, (gchar *)key); continue; } g_string_append_printf(cmdline_safe, "%s=%s", (gchar *)key, (gchar *)value); } return g_string_free(g_steal_pointer(&cmdline_safe), FALSE); } gchar * fu_common_get_olson_timezone_id_impl(GError **error) { g_autofree gchar *fn_localtime = fu_path_from_kind(FU_PATH_KIND_LOCALTIME); g_autoptr(GFile) file_localtime = g_file_new_for_path(fn_localtime); /* use the last two sections of the symlink target */ g_debug("looking for timezone file %s", fn_localtime); if (g_file_query_file_type(file_localtime, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_SYMBOLIC_LINK) { const gchar *target; g_autoptr(GFileInfo) info = NULL; info = g_file_query_info(file_localtime, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, error); if (info == NULL) return NULL; target = g_file_info_get_symlink_target(info); if (target != NULL) { g_auto(GStrv) sections = g_strsplit(target, "/", -1); guint sections_len = g_strv_length(sections); if (sections_len < 2) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, "invalid symlink target: %s", target); return NULL; } return g_strdup_printf("%s/%s", sections[sections_len - 2], sections[sections_len - 1]); } } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no timezone or localtime is available"); return NULL; } fwupd-1.9.16/libfwupdplugin/fu-common-private.h000066400000000000000000000012131460375044200215130ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-common.h" #define UDISKS_DBUS_SERVICE "org.freedesktop.UDisks2" #define UDISKS_DBUS_INTERFACE_PARTITION "org.freedesktop.UDisks2.Partition" #define UDISKS_DBUS_INTERFACE_FILESYSTEM "org.freedesktop.UDisks2.Filesystem" #define UDISKS_DBUS_INTERFACE_BLOCK "org.freedesktop.UDisks2.Block" GPtrArray * fu_common_get_block_devices(GError **error); guint64 fu_common_get_memory_size_impl(void); gchar * fu_common_get_kernel_cmdline_impl(GError **error); gchar * fu_common_get_olson_timezone_id_impl(GError **error); fwupd-1.9.16/libfwupdplugin/fu-common-windows.c000066400000000000000000000163261460375044200215410ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include #include #include #include #include "fu-common-private.h" GPtrArray * fu_common_get_block_devices(GError **error) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "getting block devices is not supported on Windows"); return NULL; } guint64 fu_common_get_memory_size_impl(void) { MEMORYSTATUSEX status; status.dwLength = sizeof(status); GlobalMemoryStatusEx(&status); return (guint64)status.ullTotalPhys; } gchar * fu_common_get_kernel_cmdline_impl(GError **error) { return g_strdup(""); } static gchar * fu_common_convert_tzinfo_to_olson_id(const gchar *tzinfo, GError **error) { struct { const gchar *tzinfo; const gchar *olson_id; } map[] = {{"Afghanistan", "Asia/Kabul"}, {"Alaskan", "America/Anchorage"}, {"Aleutian", "America/Adak"}, {"Altai", "Asia/Barnaul"}, {"Arab", "Asia/Riyadh"}, {"Arabian", "Asia/Dubai"}, {"Arabic", "Asia/Baghdad"}, {"Argentina", "America/Buenos_Aires"}, {"Astrakhan", "Europe/Astrakhan"}, {"Atlantic", "America/Halifax"}, {"AUS Central", "Australia/Darwin"}, {"Aus Central W.", "Australia/Eucla"}, {"AUS Eastern", "Australia/Sydney"}, {"Azerbaijan", "Asia/Baku"}, {"Azores", "Atlantic/Azores"}, {"Bahia", "America/Bahia"}, {"Bangladesh", "Asia/Dhaka"}, {"Belarus", "Europe/Minsk"}, {"Bougainville", "Pacific/Bougainville"}, {"Canada Central", "America/Regina"}, {"Cape Verde", "Atlantic/Cape_Verde"}, {"Caucasus", "Asia/Yerevan"}, {"Cen. Australia", "Australia/Adelaide"}, {"Central America", "America/Guatemala"}, {"Central", "America/Chicago"}, {"Central Asia", "Asia/Almaty"}, {"Central Brazilian", "America/Cuiaba"}, {"Central European", "Europe/Warsaw"}, {"Central Europe", "Europe/Budapest"}, {"Central Pacific", "Pacific/Guadalcanal"}, {"Central Standard Time (Mexico)", "America/Mexico_City"}, {"Chatham Islands", "Pacific/Chatham"}, {"China", "Asia/Shanghai"}, {"Cuba", "America/Havana"}, {"Dateline", "Etc/GMT+12"}, {"E. Africa", "Africa/Nairobi"}, {"Easter Island", "Pacific/Easter"}, {"Eastern", "America/New_York"}, {"Eastern Standard Time (Mexico)", "America/Cancun"}, {"E. Australia", "Australia/Brisbane"}, {"E. Europe", "Europe/Chisinau"}, {"Egypt", "Africa/Cairo"}, {"Ekaterinburg", "Asia/Yekaterinburg"}, {"E. South America", "America/Sao_Paulo"}, {"Fiji", "Pacific/Fiji"}, {"FILE", "Europe/Kiev"}, {"Georgian", "Asia/Tbilisi"}, {"GMT", "Europe/London"}, {"Greenland", "America/Godthab"}, {"Greenwich", "Atlantic/Reykjavik"}, {"GTB", "Europe/Bucharest"}, {"Haiti", "America/Port-au-Prince"}, {"Hawaiian", "Pacific/Honolulu"}, {"India", "Asia/Calcutta"}, {"Iran", "Asia/Tehran"}, {"Israel", "Asia/Jerusalem"}, {"Jordan", "Asia/Amman"}, {"Kaliningrad", "Europe/Kaliningrad"}, {"Korea", "Asia/Seoul"}, {"Libya", "Africa/Tripoli"}, {"Line Islands", "Pacific/Kiritimati"}, {"Lord Howe", "Australia/Lord_Howe"}, {"Magadan", "Asia/Magadan"}, {"Magallanes", "America/Punta_Arenas"}, {"Marquesas", "Pacific/Marquesas"}, {"Mauritius", "Indian/Mauritius"}, {"Middle East", "Asia/Beirut"}, {"Montevideo", "America/Montevideo"}, {"Morocco", "Africa/Casablanca"}, {"Mountain", "America/Denver"}, {"Mountain Standard Time (Mexico)", "America/Mazatlan"}, {"Myanmar", "Asia/Rangoon"}, {"Namibia", "Africa/Windhoek"}, {"N. Central Asia", "Asia/Novosibirsk"}, {"Nepal", "Asia/Katmandu"}, {"Newfoundland", "America/St_Johns"}, {"New Zealand", "Pacific/Auckland"}, {"Norfolk", "Pacific/Norfolk"}, {"North Asia", "Asia/Krasnoyarsk"}, {"North Asia East", "Asia/Irkutsk"}, {"North Korea", "Asia/Pyongyang"}, {"Omsk", "Asia/Omsk"}, {"Pacific", "America/Los_Angeles"}, {"Pacific SA", "America/Santiago"}, {"Pacific Standard Time (Mexico)", "America/Tijuana"}, {"Pakistan", "Asia/Karachi"}, {"Paraguay", "America/Asuncion"}, {"Qyzylorda", "Asia/Qyzylorda"}, {"Romance", "Europe/Paris"}, {"Russian", "Europe/Moscow"}, {"Russia Time Zone 10", "Asia/Srednekolymsk"}, {"Russia Time Zone 11", "Asia/Kamchatka"}, {"Russia Time Zone 3", "Europe/Samara"}, {"SA Eastern", "America/Cayenne"}, {"Saint Pierre", "America/Miquelon"}, {"Sakhalin", "Asia/Sakhalin"}, {"Samoa", "Pacific/Apia"}, {"Sao Tome", "Africa/Sao_Tome"}, {"SA Pacific", "America/Bogota"}, {"Saratov", "Europe/Saratov"}, {"SA Western", "America/La_Paz"}, {"SE Asia", "Asia/Bangkok"}, {"Singapore", "Asia/Singapore"}, {"South Africa", "Africa/Johannesburg"}, {"South Sudan", "Africa/Juba"}, {"Sri Lanka", "Asia/Colombo"}, {"Sudan", "Africa/Khartoum"}, {"Syria", "Asia/Damascus"}, {"Taipei", "Asia/Taipei"}, {"Tasmania", "Australia/Hobart"}, {"Tocantins", "America/Araguaina"}, {"Tokyo", "Asia/Tokyo"}, {"Tomsk", "Asia/Tomsk"}, {"Tonga", "Pacific/Tongatapu"}, {"Transbaikal", "Asia/Chita"}, {"Turkey", "Europe/Istanbul"}, {"Turks And Caicos", "America/Grand_Turk"}, {"Ulaanbaatar", "Asia/Ulaanbaatar"}, {"US Eastern", "America/Indianapolis"}, {"US Mountain", "America/Phoenix"}, {"UTC-02", "Etc/GMT+2"}, {"UTC-08", "Etc/GMT+8"}, {"UTC-09", "Etc/GMT+9"}, {"UTC-11", "Etc/GMT+11"}, {"UTC+12", "Etc/GMT-12"}, {"UTC+13", "Etc/GMT-13"}, {"UTC", "Etc/UTC"}, {"Venezuela", "America/Caracas"}, {"Vladivostok", "Asia/Vladivostok"}, {"Volgograd", "Europe/Volgograd"}, {"W. Australia", "Australia/Perth"}, {"W. Central Africa", "Africa/Lagos"}, {"West Asia", "Asia/Tashkent"}, {"West Bank", "Asia/Hebron"}, {"West Pacific", "Pacific/Port_Moresby"}, {"W. Europe", "Europe/Berlin"}, {"W. Mongolia", "Asia/Hovd"}, {"Yakutsk", "Asia/Yakutsk"}, {"Yukon", "America/Whitehorse"}, {NULL, NULL}}; for (guint i = 0; map[i].tzinfo != NULL; i++) { if (g_strcmp0(tzinfo, map[i].tzinfo) == 0) return g_strdup(map[i].olson_id); } g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot map tzinfo '%s' to Olson ID", tzinfo); return NULL; } gchar * fu_common_get_olson_timezone_id_impl(GError **error) { DWORD rc; TIME_ZONE_INFORMATION tzinfo = {0}; gchar *suffix; g_autofree gchar *name = NULL; rc = GetTimeZoneInformation(&tzinfo); if (rc == TIME_ZONE_ID_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot get timezone information [%u]", (guint)GetLastError()); return NULL; } name = g_utf16_to_utf8(tzinfo.StandardName, -1, NULL, NULL, error); if (name == NULL) { g_prefix_error(error, "cannot convert timezone name to UTF-8: "); return NULL; } /* make the lookup key shorter, then convert */ suffix = g_strstr_len(name, -1, " Standard Time"); if (suffix != NULL) *suffix = '\0'; return fu_common_convert_tzinfo_to_olson_id(name, error); } fwupd-1.9.16/libfwupdplugin/fu-common.c000066400000000000000000000222341460375044200200440ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #ifdef HAVE_CPUID_H #include #endif #include "fu-common-private.h" #include "fu-firmware.h" #include "fu-string.h" /** * fu_cpuid: * @leaf: the CPUID level, now called the 'leaf' by Intel * @eax: (out) (nullable): EAX register * @ebx: (out) (nullable): EBX register * @ecx: (out) (nullable): ECX register * @edx: (out) (nullable): EDX register * @error: (nullable): optional return location for an error * * Calls CPUID and returns the registers for the given leaf. * * Returns: %TRUE if the registers are set. * * Since: 1.8.2 **/ gboolean fu_cpuid(guint32 leaf, guint32 *eax, guint32 *ebx, guint32 *ecx, guint32 *edx, GError **error) { #ifdef HAVE_CPUID_H guint eax_tmp = 0; guint ebx_tmp = 0; guint ecx_tmp = 0; guint edx_tmp = 0; g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* get vendor */ __get_cpuid_count(leaf, 0x0, &eax_tmp, &ebx_tmp, &ecx_tmp, &edx_tmp); if (eax != NULL) *eax = eax_tmp; if (ebx != NULL) *ebx = ebx_tmp; if (ecx != NULL) *ecx = ecx_tmp; if (edx != NULL) *edx = edx_tmp; return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no support"); return FALSE; #endif } /** * fu_cpu_get_vendor: * * Uses CPUID to discover the CPU vendor. * * Returns: a CPU vendor, e.g. %FU_CPU_VENDOR_AMD if the vendor was AMD. * * Since: 1.8.2 **/ FuCpuVendor fu_cpu_get_vendor(void) { #ifdef HAVE_CPUID_H guint ebx = 0; guint ecx = 0; guint edx = 0; if (fu_cpuid(0x0, NULL, &ebx, &ecx, &edx, NULL)) { if (ebx == signature_INTEL_ebx && edx == signature_INTEL_edx && ecx == signature_INTEL_ecx) { return FU_CPU_VENDOR_INTEL; } if (ebx == signature_AMD_ebx && edx == signature_AMD_edx && ecx == signature_AMD_ecx) { return FU_CPU_VENDOR_AMD; } } #endif /* failed */ return FU_CPU_VENDOR_UNKNOWN; } /** * fu_common_is_live_media: * * Checks if the user is running from a live media using various heuristics. * * Returns: %TRUE if live * * Since: 1.4.6 **/ gboolean fu_common_is_live_media(void) { gsize bufsz = 0; g_autofree gchar *buf = NULL; g_auto(GStrv) tokens = NULL; const gchar *args[] = { "rd.live.image", "boot=live", NULL, /* last entry */ }; if (g_file_test("/cdrom/.disk/info", G_FILE_TEST_EXISTS)) return TRUE; if (!g_file_get_contents("/proc/cmdline", &buf, &bufsz, NULL)) return FALSE; if (bufsz <= 1) return FALSE; tokens = fu_strsplit(buf, bufsz - 1, " ", -1); for (guint i = 0; args[i] != NULL; i++) { if (g_strv_contains((const gchar *const *)tokens, args[i])) return TRUE; } return FALSE; } /** * fu_common_get_memory_size: * * Returns the size of physical memory. * * Returns: bytes * * Since: 1.5.6 **/ guint64 fu_common_get_memory_size(void) { return fu_common_get_memory_size_impl(); } /** * fu_common_get_kernel_cmdline: * @error: (nullable): optional return location for an error * * Returns the current kernel command line options. * * Returns: options as a string, or %NULL on error * * Since: 1.5.6 **/ gchar * fu_common_get_kernel_cmdline(GError **error) { return fu_common_get_kernel_cmdline_impl(error); } /** * fu_common_check_full_disk_encryption: * @error: (nullable): optional return location for an error * * Checks that all FDE volumes are not going to be affected by a firmware update. If unsure, * return with failure and let the user decide. * * Returns: %TRUE for success * * Since: 1.7.1 **/ gboolean fu_common_check_full_disk_encryption(GError **error) { g_autoptr(GPtrArray) devices = NULL; g_return_val_if_fail(error == NULL || *error == NULL, FALSE); devices = fu_common_get_block_devices(error); if (devices == NULL) return FALSE; for (guint i = 0; i < devices->len; i++) { GDBusProxy *proxy = g_ptr_array_index(devices, i); g_autoptr(GVariant) id_type = g_dbus_proxy_get_cached_property(proxy, "IdType"); g_autoptr(GVariant) device = g_dbus_proxy_get_cached_property(proxy, "Device"); if (id_type == NULL || device == NULL) continue; if (g_strcmp0(g_variant_get_string(id_type, NULL), "BitLocker") == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "%s device %s is encrypted", g_variant_get_string(id_type, NULL), g_variant_get_bytestring(device)); return FALSE; } } return TRUE; } /** * fu_common_get_olson_timezone_id: * @error: (nullable): optional return location for an error * * Gets the system Olson timezone ID, as used in the CLDR and ICU specifications. * * Returns: timezone string, e.g. `Europe/London` or %NULL on error * * Since: 1.9.7 **/ gchar * fu_common_get_olson_timezone_id(GError **error) { return fu_common_get_olson_timezone_id_impl(error); } /** * fu_common_align_up: * @value: value to align * @alignment: align to this power of 2, where 0x1F is the maximum value of 2GB * * Align a value to a power of 2 boundary, where @alignment is the bit position * to align to. If @alignment is zero then @value is always returned unchanged. * * Returns: aligned value, which will be the same as @value if already aligned, * or %G_MAXSIZE if the value would overflow * * Since: 1.6.0 **/ gsize fu_common_align_up(gsize value, guint8 alignment) { gsize value_new; gsize mask = (gsize)1 << alignment; g_return_val_if_fail(alignment <= FU_FIRMWARE_ALIGNMENT_2G, G_MAXSIZE); /* no alignment required */ if ((value & (mask - 1)) == 0) return value; /* increment up to the next alignment value */ value_new = value + mask; value_new &= ~(mask - 1); /* overflow */ if (value_new < value) return G_MAXSIZE; /* success */ return value_new; } /** * fu_power_state_to_string: * @power_state: a power state, e.g. %FU_POWER_STATE_AC_FULLY_CHARGED * * Converts an enumerated type to a string. * * Returns: a string, or %NULL for invalid * * Since: 1.8.11 **/ const gchar * fu_power_state_to_string(FuPowerState power_state) { if (power_state == FU_POWER_STATE_UNKNOWN) return "unknown"; if (power_state == FU_POWER_STATE_BATTERY) return "battery"; if (power_state == FU_POWER_STATE_BATTERY_DISCHARGING) return "battery-discharging"; if (power_state == FU_POWER_STATE_BATTERY_EMPTY) return "battery-empty"; if (power_state == FU_POWER_STATE_AC) return "ac"; if (power_state == FU_POWER_STATE_AC_CHARGING) return "ac-charging"; if (power_state == FU_POWER_STATE_AC_FULLY_CHARGED) return "ac-fully-charged"; return NULL; } /** * fu_power_state_is_ac: * @power_state: a power state, e.g. %FU_POWER_STATE_AC_FULLY_CHARGED * * Determines if the power state can be considered "on AC power". * * Returns: %TRUE for success * * Since: 1.8.11 **/ gboolean fu_power_state_is_ac(FuPowerState power_state) { if (power_state == FU_POWER_STATE_UNKNOWN || power_state == FU_POWER_STATE_AC || power_state == FU_POWER_STATE_AC_CHARGING || power_state == FU_POWER_STATE_AC_FULLY_CHARGED) return TRUE; return FALSE; } /** * fu_lid_state_to_string: * @lid_state: a lid state, e.g. %FU_LID_STATE_CLOSED * * Converts an enumerated type to a string. * * Returns: a string, or %NULL for invalid * * Since: 1.7.4 **/ const gchar * fu_lid_state_to_string(FuLidState lid_state) { if (lid_state == FU_LID_STATE_UNKNOWN) return "unknown"; if (lid_state == FU_LID_STATE_OPEN) return "open"; if (lid_state == FU_LID_STATE_CLOSED) return "closed"; return NULL; } /** * fu_display_state_to_string: * @display_state: a lid state, e.g. %FU_DISPLAY_STATE_CONNECTED * * Converts an enumerated type to a string. * * Returns: a string, or %NULL for invalid * * Since: 1.9.6 **/ const gchar * fu_display_state_to_string(FuDisplayState display_state) { if (display_state == FU_DISPLAY_STATE_UNKNOWN) return "unknown"; if (display_state == FU_DISPLAY_STATE_CONNECTED) return "connected"; if (display_state == FU_DISPLAY_STATE_DISCONNECTED) return "disconnected"; return NULL; } /** * fu_xmlb_builder_insert_kv: * @bn: #XbBuilderNode * @key: string key * @value: string value * * Convenience function to add an XML node with a string value. If @value is %NULL * then no member is added. * * Since: 1.6.0 **/ void fu_xmlb_builder_insert_kv(XbBuilderNode *bn, const gchar *key, const gchar *value) { if (value == NULL) return; xb_builder_node_insert_text(bn, key, value, NULL); } /** * fu_xmlb_builder_insert_kx: * @bn: #XbBuilderNode * @key: string key * @value: integer value * * Convenience function to add an XML node with an integer value. If @value is 0 * then no member is added. * * Since: 1.6.0 **/ void fu_xmlb_builder_insert_kx(XbBuilderNode *bn, const gchar *key, guint64 value) { g_autofree gchar *value_hex = NULL; if (value == 0) return; value_hex = g_strdup_printf("0x%x", (guint)value); xb_builder_node_insert_text(bn, key, value_hex, NULL); } /** * fu_xmlb_builder_insert_kb: * @bn: #XbBuilderNode * @key: string key * @value: boolean value * * Convenience function to add an XML node with a boolean value. * * Since: 1.6.0 **/ void fu_xmlb_builder_insert_kb(XbBuilderNode *bn, const gchar *key, gboolean value) { xb_builder_node_insert_text(bn, key, value ? "true" : "false", NULL); } fwupd-1.9.16/libfwupdplugin/fu-common.h000066400000000000000000000063111460375044200200470ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include /** * FuEndianType: * * The endian type, e.g. %G_LITTLE_ENDIAN **/ typedef guint FuEndianType; /** * FuCpuVendor: * @FU_CPU_VENDOR_UNKNOWN: Unknown * @FU_CPU_VENDOR_INTEL: Intel * @FU_CPU_VENDOR_AMD: AMD * * The CPU vendor. **/ typedef enum { FU_CPU_VENDOR_UNKNOWN, FU_CPU_VENDOR_INTEL, FU_CPU_VENDOR_AMD, /*< private >*/ FU_CPU_VENDOR_LAST } FuCpuVendor; /** * FuPowerState: * @FU_POWER_STATE_UNKNOWN: Unknown * @FU_POWER_STATE_AC: On AC power * @FU_POWER_STATE_AC_CHARGING: Charging on AC * @FU_POWER_STATE_AC_FULLY_CHARGED: Fully charged on AC * @FU_POWER_STATE_BATTERY: On system battery * @FU_POWER_STATE_BATTERY_DISCHARGING: System battery discharging * @FU_POWER_STATE_BATTERY_EMPTY: System battery empty * * The system power state. * * This does not have to be exactly what the battery is doing, but is supposed to represent the * 40,000ft view of the system power state. * * For example, it is perfectly correct to set %FU_POWER_STATE_AC if the system is connected to * AC power, but the battery cells are discharging for health or for other performance reasons. **/ typedef enum { FU_POWER_STATE_UNKNOWN, FU_POWER_STATE_AC, FU_POWER_STATE_AC_CHARGING, FU_POWER_STATE_AC_FULLY_CHARGED, FU_POWER_STATE_BATTERY, FU_POWER_STATE_BATTERY_DISCHARGING, FU_POWER_STATE_BATTERY_EMPTY, /*< private >*/ FU_POWER_STATE_LAST } FuPowerState; /** * FuLidState: * @FU_LID_STATE_UNKNOWN: Unknown * @FU_LID_STATE_OPEN: Charging * @FU_LID_STATE_CLOSED: Discharging * * The device lid state. **/ typedef enum { FU_LID_STATE_UNKNOWN, FU_LID_STATE_OPEN, FU_LID_STATE_CLOSED, /*< private >*/ FU_LID_STATE_LAST } FuLidState; /** * FuDisplayState: * @FU_DISPLAY_STATE_UNKNOWN: Unknown * @FU_DISPLAY_STATE_CONNECTED: A monitor is connected * @FU_DISPLAY_STATE_DISCONNECTED: No monitor is connected * * The device lid state. **/ typedef enum { FU_DISPLAY_STATE_UNKNOWN, FU_DISPLAY_STATE_CONNECTED, FU_DISPLAY_STATE_DISCONNECTED, /*< private >*/ FU_DISPLAY_STATE_LAST } FuDisplayState; gboolean fu_cpuid(guint32 leaf, guint32 *eax, guint32 *ebx, guint32 *ecx, guint32 *edx, GError **error) G_GNUC_WARN_UNUSED_RESULT; FuCpuVendor fu_cpu_get_vendor(void); gboolean fu_common_is_live_media(void); guint64 fu_common_get_memory_size(void); gchar * fu_common_get_kernel_cmdline(GError **error); gboolean fu_common_check_full_disk_encryption(GError **error); gchar * fu_common_get_olson_timezone_id(GError **error); gsize fu_common_align_up(gsize value, guint8 alignment); const gchar * fu_power_state_to_string(FuPowerState power_state); gboolean fu_power_state_is_ac(FuPowerState power_state); const gchar * fu_lid_state_to_string(FuLidState lid_state); const gchar * fu_display_state_to_string(FuDisplayState display_state); void fu_xmlb_builder_insert_kv(XbBuilderNode *bn, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1); void fu_xmlb_builder_insert_kx(XbBuilderNode *bn, const gchar *key, guint64 value) G_GNUC_NON_NULL(1); void fu_xmlb_builder_insert_kb(XbBuilderNode *bn, const gchar *key, gboolean value) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-config-private.h000066400000000000000000000005611460375044200214750ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-config.h" FuConfig * fu_config_new(void); gboolean fu_config_load(FuConfig *self, GError **error) G_GNUC_NON_NULL(1); gboolean fu_config_reset_defaults(FuConfig *self, const gchar *section, GError **error) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-config.c000066400000000000000000000574131460375044200200300ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuConfig" #include "config.h" #include #include #include #include #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-config-private.h" #include "fu-path.h" #include "fu-string.h" enum { SIGNAL_CHANGED, SIGNAL_LOADED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; #define FU_CONFIG_FILE_MODE_SECURE 0640 typedef struct { gchar *filename; GFile *file; GFileMonitor *monitor; /* nullable */ gboolean is_writable; gboolean is_mutable; } FuConfigItem; typedef struct { GKeyFile *keyfile; GHashTable *default_values; GPtrArray *items; /* (element-type FuConfigItem) */ } FuConfigPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuConfig, fu_config, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fu_config_get_instance_private(o)) #if !GLIB_CHECK_VERSION(2, 66, 0) #define G_FILE_SET_CONTENTS_CONSISTENT 0 typedef guint GFileSetContentsFlags; static gboolean g_file_set_contents_full(const gchar *filename, const gchar *contents, gssize length, GFileSetContentsFlags flags, int mode, GError **error) { gint fd; gssize wrote; if (length < 0) length = strlen(contents); fd = g_open(filename, O_CREAT, mode); if (fd < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "could not open %s file", filename); return FALSE; } wrote = write(fd, contents, length); if (wrote != length) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "did not write %s file", filename); g_close(fd, NULL); return FALSE; } return g_close(fd, error); } #endif static void fu_config_item_free(FuConfigItem *item) { if (item->monitor != NULL) { g_file_monitor_cancel(item->monitor); g_object_unref(item->monitor); } g_object_unref(item->file); g_free(item->filename); g_free(item); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuConfigItem, fu_config_item_free) static void fu_config_emit_changed(FuConfig *self) { g_debug("::configuration changed"); g_signal_emit(self, signals[SIGNAL_CHANGED], 0); } static void fu_config_emit_loaded(FuConfig *self) { g_debug("::configuration loaded"); g_signal_emit(self, signals[SIGNAL_LOADED], 0); } static gchar * fu_config_build_section_key(const gchar *section, const gchar *key) { return g_strdup_printf("%s::%s", section, key); } static gboolean fu_config_load_bytes_replace(FuConfig *self, GBytes *blob, GError **error) { FuConfigPrivate *priv = GET_PRIVATE(self); g_auto(GStrv) groups = NULL; g_autoptr(GKeyFile) kf = g_key_file_new(); if (!g_key_file_load_from_data(kf, (const gchar *)g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), G_KEY_FILE_KEEP_COMMENTS, error)) return FALSE; groups = g_key_file_get_groups(kf, NULL); for (guint i = 0; groups[i] != NULL; i++) { g_auto(GStrv) keys = NULL; g_autofree gchar *comment_group = NULL; keys = g_key_file_get_keys(kf, groups[i], NULL, error); if (keys == NULL) { g_prefix_error(error, "failed to get keys for [%s]: ", groups[i]); return FALSE; } for (guint j = 0; keys[j] != NULL; j++) { const gchar *default_value; g_autofree gchar *comment_key = NULL; g_autofree gchar *section_key = NULL; g_autofree gchar *value = NULL; value = g_key_file_get_string(kf, groups[i], keys[j], error); if (value == NULL) { g_prefix_error(error, "failed to get string for %s=%s: ", groups[i], keys[j]); return FALSE; } /* is the same as the default */ section_key = fu_config_build_section_key(groups[i], keys[j]); default_value = g_hash_table_lookup(priv->default_values, section_key); if (g_strcmp0(value, default_value) == 0) { g_debug("default config, ignoring [%s] %s=%s", groups[i], keys[j], value); continue; } g_debug("setting config [%s] %s=%s", groups[i], keys[j], value); g_key_file_set_string(priv->keyfile, groups[i], keys[j], value); comment_key = g_key_file_get_comment(kf, groups[i], keys[j], NULL); if (comment_key != NULL && comment_key[0] != '\0') { if (!g_key_file_set_comment(priv->keyfile, groups[i], keys[j], comment_key, error)) { g_prefix_error(error, "failed to set comment '%s' for %s=%s: ", comment_key, groups[i], keys[j]); return FALSE; } } } comment_group = g_key_file_get_comment(kf, groups[i], NULL, NULL); if (comment_group != NULL && comment_group[0] != '\0') { if (!g_key_file_set_comment(priv->keyfile, groups[i], NULL, comment_group, error)) { g_prefix_error(error, "failed to set comment '%s' for [%s]: ", comment_group, groups[i]); return FALSE; } } } /* success */ return TRUE; } static void fu_config_migrate_keyfile(FuConfig *self) { FuConfigPrivate *priv = GET_PRIVATE(self); struct { const gchar *group; const gchar *key; const gchar *value; } key_values[] = {{"fwupd", "ApprovedFirmware", NULL}, {"fwupd", "ArchiveSizeMax", "0"}, {"fwupd", "BlockedFirmware", NULL}, {"fwupd", "DisabledDevices", NULL}, {"fwupd", "EnumerateAllDevices", NULL}, {"fwupd", "EspLocation", NULL}, {"fwupd", "HostBkc", NULL}, {"fwupd", "IdleTimeout", "7200"}, {"fwupd", "IdleTimeout", NULL}, {"fwupd", "IgnorePower", NULL}, {"fwupd", "ShowDevicePrivate", NULL}, {"fwupd", "TrustedUids", NULL}, {"fwupd", "UpdateMotd", NULL}, {"fwupd", "UriSchemes", NULL}, {"fwupd", "VerboseDomains", NULL}, {"fwupd", "OnlyTrusted", NULL}, {"fwupd", "IgnorePower", NULL}, {"fwupd", "DisabledPlugins", "test;test_ble;invalid"}, {"fwupd", "DisabledPlugins", "test;test_ble"}, {"fwupd", "AllowEmulation", NULL}, {"redfish", "IpmiDisableCreateUser", NULL}, {"redfish", "ManagerResetTimeout", NULL}, {"msr", "MinimumSmeKernelVersion", NULL}, {"thunderbolt", "MinimumKernelVersion", NULL}, {"thunderbolt", "DelayedActivation", NULL}, {NULL, NULL, NULL}}; for (guint i = 0; key_values[i].group != NULL; i++) { const gchar *default_value; g_autofree gchar *value = NULL; g_auto(GStrv) keys = NULL; value = g_key_file_get_value(priv->keyfile, key_values[i].group, key_values[i].key, NULL); if (value == NULL) continue; if (key_values[i].value == NULL) { g_autofree gchar *section_key = fu_config_build_section_key(key_values[i].group, key_values[i].key); default_value = g_hash_table_lookup(priv->default_values, section_key); } else { default_value = key_values[i].value; } if ((default_value != NULL && g_ascii_strcasecmp(value, default_value) == 0) || (key_values[i].value == NULL && g_strcmp0(value, "") == 0)) { g_debug("not migrating default value of [%s] %s=%s", key_values[i].group, key_values[i].key, default_value); g_key_file_remove_comment(priv->keyfile, key_values[i].group, key_values[i].key, NULL); g_key_file_remove_key(priv->keyfile, key_values[i].group, key_values[i].key, NULL); } /* remove the group if there are no keys left */ keys = g_key_file_get_keys(priv->keyfile, key_values[i].group, NULL, NULL); if (g_strv_length(keys) == 0) { g_key_file_remove_comment(priv->keyfile, key_values[i].group, NULL, NULL); g_key_file_remove_group(priv->keyfile, key_values[i].group, NULL); } } } static gboolean fu_config_reload(FuConfig *self, GError **error) { FuConfigPrivate *priv = GET_PRIVATE(self); g_autoptr(GPtrArray) legacy_cfg_files = g_ptr_array_new_with_free_func(g_free); const gchar *fn_merge[] = {"daemon.conf", "msr.conf", "redfish.conf", "thunderbolt.conf", "uefi_capsule.conf", NULL}; #ifndef _WIN32 /* ensure mutable config files are set to the correct permissions */ for (guint i = 0; i < priv->items->len; i++) { FuConfigItem *item = g_ptr_array_index(priv->items, i); guint32 st_mode; g_autoptr(GFileInfo) info = NULL; /* check permissions */ if (!item->is_writable) { g_debug("skipping mode check for %s as not writable", item->filename); continue; } info = g_file_query_info(item->file, G_FILE_ATTRIBUTE_UNIX_MODE, G_FILE_QUERY_INFO_NONE, NULL, error); if (info == NULL) { g_prefix_error(error, "failed to query info about %s", item->filename); return FALSE; } st_mode = g_file_info_get_attribute_uint32(info, G_FILE_ATTRIBUTE_UNIX_MODE) & 0777; if (st_mode != FU_CONFIG_FILE_MODE_SECURE) { g_info("fixing %s from mode 0%o to 0%o", item->filename, st_mode, (guint)FU_CONFIG_FILE_MODE_SECURE); g_file_info_set_attribute_uint32(info, G_FILE_ATTRIBUTE_UNIX_MODE, FU_CONFIG_FILE_MODE_SECURE); if (!g_file_set_attributes_from_info(item->file, info, G_FILE_QUERY_INFO_NONE, NULL, error)) { g_prefix_error(error, "failed to set mode attribute of %s: ", item->filename); return FALSE; } } } #endif /* we have to copy each group/key from a temporary GKeyFile as g_key_file_load_from_file() * clears all keys before loading each file, and we want to allow the mutable version to be * incomplete and just *override* a specific option */ if (!g_key_file_load_from_data(priv->keyfile, "", -1, G_KEY_FILE_NONE, error)) return FALSE; for (guint i = 0; i < priv->items->len; i++) { FuConfigItem *item = g_ptr_array_index(priv->items, i); g_autoptr(GError) error_load = NULL; g_autoptr(GBytes) blob_item = NULL; g_debug("trying to load config values from %s", item->filename); blob_item = fu_bytes_get_contents(item->filename, &error_load); if (blob_item == NULL) { if (g_error_matches(error_load, G_FILE_ERROR, G_FILE_ERROR_ACCES)) { g_debug("ignoring config file %s: ", error_load->message); continue; } else if (g_error_matches(error_load, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { g_debug("%s", error_load->message); continue; } g_propagate_error(error, g_steal_pointer(&error_load)); g_prefix_error(error, "failed to read %s: ", item->filename); return FALSE; } if (!fu_config_load_bytes_replace(self, blob_item, error)) { g_prefix_error(error, "failed to load %s: ", item->filename); return FALSE; } } /* are any of the legacy files found in this location? */ for (guint i = 0; i < priv->items->len; i++) { FuConfigItem *item = g_ptr_array_index(priv->items, i); g_autofree gchar *dirname = g_path_get_dirname(item->filename); for (guint j = 0; fn_merge[j] != NULL; j++) { g_autofree gchar *fncompat = g_build_filename(dirname, fn_merge[j], NULL); if (g_file_test(fncompat, G_FILE_TEST_EXISTS)) { g_autoptr(GBytes) blob_compat = fu_bytes_get_contents(fncompat, error); if (blob_compat == NULL) { g_prefix_error(error, "failed to read %s: ", fncompat); return FALSE; } if (!fu_config_load_bytes_replace(self, blob_compat, error)) { g_prefix_error(error, "failed to load %s: ", fncompat); return FALSE; } g_ptr_array_add(legacy_cfg_files, g_steal_pointer(&fncompat)); } } } /* migration needed */ if (legacy_cfg_files->len > 0) { FuConfigItem *item = g_ptr_array_index(priv->items, 0); const gchar *fn_default = item->filename; g_autofree gchar *data = NULL; /* do not write empty keys migrated from daemon.conf */ fu_config_migrate_keyfile(self); /* make sure we can save the new file first */ data = g_key_file_to_data(priv->keyfile, NULL, error); if (data == NULL) return FALSE; if (!g_file_set_contents_full( fn_default, data, -1, G_FILE_SET_CONTENTS_CONSISTENT, FU_CONFIG_FILE_MODE_SECURE, /* only readable by root */ error)) { g_prefix_error(error, "failed to save %s: ", fn_default); return FALSE; } /* give the legacy files a .old extension */ for (guint i = 0; i < legacy_cfg_files->len; i++) { const gchar *fn_old = g_ptr_array_index(legacy_cfg_files, i); g_autofree gchar *fn_new = g_strdup_printf("%s.old", fn_old); g_info("renaming legacy config file %s to %s", fn_old, fn_new); if (g_rename(fn_old, fn_new) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to change rename %s to %s", fn_old, fn_new); return FALSE; } } } /* success */ return TRUE; } static void fu_config_monitor_changed_cb(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { FuConfig *self = FU_CONFIG(user_data); g_autoptr(GError) error = NULL; g_autofree gchar *fn = g_file_get_path(file); /* nothing we need to care about */ if (event_type == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) { g_debug("%s attributes changed, ignoring", fn); return; } /* reload everything */ g_info("%s changed, reloading all configs", fn); if (!fu_config_reload(self, &error)) g_warning("failed to rescan daemon config: %s", error->message); fu_config_emit_changed(self); } /** * fu_config_set_default: * @self: a #FuConfig * @section: a settings section * @key: a settings key * @value: (nullable): a settings value * * Sets a default config value. * * Since: 2.0.0 **/ void fu_config_set_default(FuConfig *self, const gchar *section, const gchar *key, const gchar *value) { FuConfigPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONFIG(self)); g_return_if_fail(section != NULL); g_return_if_fail(key != NULL); g_hash_table_insert(priv->default_values, fu_config_build_section_key(section, key), g_strdup(value)); } static gboolean fu_config_save(FuConfig *self, GError **error) { FuConfigPrivate *priv = GET_PRIVATE(self); g_autofree gchar *data = NULL; data = g_key_file_to_data(priv->keyfile, NULL, error); if (data == NULL) return FALSE; for (guint i = 0; i < priv->items->len; i++) { FuConfigItem *item = g_ptr_array_index(priv->items, i); if (!item->is_mutable) continue; if (!fu_path_mkdir_parent(item->filename, error)) return FALSE; if (!g_file_set_contents_full(item->filename, data, -1, G_FILE_SET_CONTENTS_CONSISTENT, FU_CONFIG_FILE_MODE_SECURE, /* only for root */ error)) return FALSE; return fu_config_reload(self, error); } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, "no writable config"); return FALSE; } /** * fu_config_set_value: * @self: a #FuConfig * @section: a settings section * @key: a settings key * @value: (nullable): a settings value * @error: (nullable): optional return location for an error * * Sets a plugin config value, saving the new data back to the default config file. * * Returns: %TRUE for success * * Since: 1.9.1 **/ gboolean fu_config_set_value(FuConfig *self, const gchar *section, const gchar *key, const gchar *value, GError **error) { FuConfigPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); g_return_val_if_fail(section != NULL, FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* sanity check */ if (priv->items->len == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, "no config to load"); return FALSE; } /* do not write default keys */ fu_config_migrate_keyfile(self); /* only write the file to a mutable location */ g_key_file_set_string(priv->keyfile, section, key, value); return fu_config_save(self, error); } /** * fu_config_reset_defaults: * @self: a #FuConfig * @section: a settings section * * Reset all the keys back to the default values. * * Since: 1.9.15 **/ gboolean fu_config_reset_defaults(FuConfig *self, const gchar *section, GError **error) { FuConfigPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); g_return_val_if_fail(section != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* remove all keys, and save */ g_key_file_remove_group(priv->keyfile, section, NULL); return fu_config_save(self, error); } /** * fu_config_get_value: * @self: a #FuConfig * @section: a settings section * @key: a settings key * * Return the value of a key, falling back to the default value if missing. * * NOTE: this function will return an empty string for `key=`. * * Returns: (transfer full): key value * * Since: 1.9.1 **/ gchar * fu_config_get_value(FuConfig *self, const gchar *section, const gchar *key) { FuConfigPrivate *priv = GET_PRIVATE(self); g_autofree gchar *value = NULL; g_return_val_if_fail(FU_IS_CONFIG(self), NULL); g_return_val_if_fail(section != NULL, NULL); g_return_val_if_fail(key != NULL, NULL); value = g_key_file_get_string(priv->keyfile, section, key, NULL); if (value == NULL) { g_autofree gchar *section_key = fu_config_build_section_key(section, key); const gchar *value_tmp = g_hash_table_lookup(priv->default_values, section_key); return g_strdup(value_tmp); } return g_steal_pointer(&value); } /** * fu_config_get_value_strv: * @self: a #FuConfig * @section: a settings section * @key: a settings key * * Return the value of a key, falling back to the default value if missing. * * NOTE: this function will return an empty string for `key=`. * * Returns: (transfer full) (nullable): key value * * Since: 1.9.1 **/ gchar ** fu_config_get_value_strv(FuConfig *self, const gchar *section, const gchar *key) { g_autofree gchar *value = NULL; g_return_val_if_fail(FU_IS_CONFIG(self), NULL); g_return_val_if_fail(section != NULL, NULL); g_return_val_if_fail(key != NULL, NULL); value = fu_config_get_value(self, section, key); if (value == NULL) return NULL; return g_strsplit(value, ";", -1); } /** * fu_config_get_value_bool: * @self: a #FuConfig * @section: a settings section * @key: a settings key * * Return the value of a key, falling back to the default value if missing or empty. * * Returns: boolean * * Since: 1.9.1 **/ gboolean fu_config_get_value_bool(FuConfig *self, const gchar *section, const gchar *key) { FuConfigPrivate *priv = GET_PRIVATE(self); g_autofree gchar *value = fu_config_get_value(self, section, key); if (value == NULL || value[0] == '\0') { g_autofree gchar *section_key = fu_config_build_section_key(section, key); const gchar *value_tmp = g_hash_table_lookup(priv->default_values, section_key); if (value_tmp == NULL) { g_critical("no default for [%s] %s", section, key); return FALSE; } return g_ascii_strcasecmp(value_tmp, "true") == 0; } return g_ascii_strcasecmp(value, "true") == 0; } /** * fu_config_get_value_u64: * @self: a #FuConfig * @section: a settings section * @key: a settings key * * Return the value of a key, falling back to the default value if missing or empty. * * Returns: uint64 * * Since: 1.9.1 **/ guint64 fu_config_get_value_u64(FuConfig *self, const gchar *section, const gchar *key) { FuConfigPrivate *priv = GET_PRIVATE(self); guint64 value = 0; const gchar *value_tmp; g_autofree gchar *tmp = fu_config_get_value(self, section, key); g_autoptr(GError) error_local = NULL; if (tmp == NULL || tmp[0] == '\0') { g_autofree gchar *section_key = fu_config_build_section_key(section, key); value_tmp = g_hash_table_lookup(priv->default_values, section_key); if (value_tmp == NULL) { g_critical("no default for [%s] %s", section, key); return G_MAXUINT64; } } else { value_tmp = tmp; } if (!fu_strtoull(value_tmp, &value, 0, G_MAXUINT64, &error_local)) { g_warning("failed to parse [%s] %s = %s as integer", section, key, value_tmp); return G_MAXUINT64; } return value; } static gboolean fu_config_add_location(FuConfig *self, const gchar *dirname, gboolean is_mutable, GError **error) { FuConfigPrivate *priv = GET_PRIVATE(self); g_autoptr(FuConfigItem) item = g_new0(FuConfigItem, 1); item->is_mutable = is_mutable; item->filename = g_build_filename(dirname, "fwupd.conf", NULL); item->file = g_file_new_for_path(item->filename); /* is writable */ if (g_file_query_exists(item->file, NULL)) { g_autoptr(GFileInfo) info = NULL; g_debug("loading config %s", item->filename); info = g_file_query_info(item->file, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, G_FILE_QUERY_INFO_NONE, NULL, error); if (info == NULL) return FALSE; item->is_writable = g_file_info_get_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE); if (!item->is_writable) g_debug("config %s is immutable", item->filename); } else { g_debug("not loading config %s", item->filename); } /* success */ g_ptr_array_add(priv->items, g_steal_pointer(&item)); return TRUE; } /** * fu_config_load: * @self: a #FuConfig * @error: (nullable): optional return location for an error * * Loads the configuration files from all possible locations. * * Returns: %TRUE for success * * Since: 1.9.1 **/ gboolean fu_config_load(FuConfig *self, GError **error) { FuConfigPrivate *priv = GET_PRIVATE(self); g_autofree gchar *configdir_mut = fu_path_from_kind(FU_PATH_KIND_LOCALCONFDIR_PKG); g_autofree gchar *configdir = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR_PKG); g_return_val_if_fail(FU_IS_CONFIG(self), FALSE); g_return_val_if_fail(priv->items->len == 0, FALSE); /* load the main daemon config file */ if (!fu_config_add_location(self, configdir, FALSE, error)) return FALSE; if (!fu_config_add_location(self, configdir_mut, TRUE, error)) return FALSE; if (!fu_config_reload(self, error)) return FALSE; /* set up a notify watches */ for (guint i = 0; i < priv->items->len; i++) { FuConfigItem *item = g_ptr_array_index(priv->items, i); g_autoptr(GFile) file = g_file_new_for_path(item->filename); item->monitor = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, error); if (item->monitor == NULL) return FALSE; g_signal_connect(G_FILE_MONITOR(item->monitor), "changed", G_CALLBACK(fu_config_monitor_changed_cb), self); } /* success */ fu_config_emit_loaded(self); return TRUE; } static void fu_config_init(FuConfig *self) { FuConfigPrivate *priv = GET_PRIVATE(self); priv->keyfile = g_key_file_new(); priv->items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_config_item_free); priv->default_values = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); } static void fu_config_finalize(GObject *obj) { FuConfig *self = FU_CONFIG(obj); FuConfigPrivate *priv = GET_PRIVATE(self); g_key_file_unref(priv->keyfile); g_ptr_array_unref(priv->items); g_hash_table_unref(priv->default_values); G_OBJECT_CLASS(fu_config_parent_class)->finalize(obj); } static void fu_config_class_init(FuConfigClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_config_finalize; /** * FuConfig::changed: * @self: the #FuConfig instance that emitted the signal * * The ::changed signal is emitted when the config file has * changed, for instance when a parameter has been modified. **/ signals[SIGNAL_CHANGED] = g_signal_new("changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * FuConfig::loaded: * @self: the #FuConfig instance that emitted the signal * * The ::loaded signal is emitted when the config file has * loaded, typically at startup. **/ signals[SIGNAL_LOADED] = g_signal_new("loaded", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } /** * fu_config_new: * * Creates a new #FuConfig. * * Returns: (transfer full): a new #FuConfig * * Since: 1.9.1 **/ FuConfig * fu_config_new(void) { return FU_CONFIG(g_object_new(FU_TYPE_CONFIG, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-config.h000066400000000000000000000020521460375044200200220ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CONFIG (fu_config_get_type()) G_DECLARE_DERIVABLE_TYPE(FuConfig, fu_config, FU, CONFIG, GObject) struct _FuConfigClass { GObjectClass parent_class; }; void fu_config_set_default(FuConfig *self, const gchar *section, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2); gboolean fu_config_set_value(FuConfig *self, const gchar *section, const gchar *key, const gchar *value, GError **error) G_GNUC_NON_NULL(1, 2); gchar * fu_config_get_value(FuConfig *self, const gchar *section, const gchar *key) G_GNUC_NON_NULL(1, 2); gchar ** fu_config_get_value_strv(FuConfig *self, const gchar *section, const gchar *key) G_GNUC_NON_NULL(1, 2); gboolean fu_config_get_value_bool(FuConfig *self, const gchar *section, const gchar *key) G_GNUC_NON_NULL(1, 2); guint64 fu_config_get_value_u64(FuConfig *self, const gchar *section, const gchar *key) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-context-private.h000066400000000000000000000044061460375044200217160ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-config.h" #include "fu-context.h" #include "fu-hwids.h" #include "fu-progress.h" #include "fu-quirks.h" #include "fu-volume.h" typedef enum { FU_CONTEXT_HWID_FLAG_NONE = 0, FU_CONTEXT_HWID_FLAG_LOAD_CONFIG = 1 << 0, FU_CONTEXT_HWID_FLAG_LOAD_SMBIOS = 1 << 1, FU_CONTEXT_HWID_FLAG_LOAD_FDT = 1 << 2, FU_CONTEXT_HWID_FLAG_LOAD_DMI = 1 << 3, FU_CONTEXT_HWID_FLAG_LOAD_KENV = 1 << 4, FU_CONTEXT_HWID_FLAG_LOAD_DARWIN = 1 << 5, FU_CONTEXT_HWID_FLAG_LOAD_ALL = G_MAXUINT, } FuContextHwidFlags; FuContext * fu_context_new(void); gboolean fu_context_reload_bios_settings(FuContext *self, GError **error); gboolean fu_context_load_hwinfo(FuContext *self, FuProgress *progress, FuContextHwidFlags flags, GError **error) G_GNUC_NON_NULL(1); gboolean fu_context_load_quirks(FuContext *self, FuQuirksLoadFlags flags, GError **error) G_GNUC_NON_NULL(1); GHashTable * fu_context_get_runtime_versions(FuContext *self) G_GNUC_NON_NULL(1); GHashTable * fu_context_get_compile_versions(FuContext *self) G_GNUC_NON_NULL(1); void fu_context_add_firmware_gtype(FuContext *self, const gchar *id, GType gtype) G_GNUC_NON_NULL(1, 2); GPtrArray * fu_context_get_firmware_gtype_ids(FuContext *self) G_GNUC_NON_NULL(1); GArray * fu_context_get_firmware_gtypes(FuContext *self) G_GNUC_NON_NULL(1); GType fu_context_get_firmware_gtype_by_id(FuContext *self, const gchar *id) G_GNUC_NON_NULL(1, 2); void fu_context_add_udev_subsystem(FuContext *self, const gchar *subsystem, const gchar *plugin_name) G_GNUC_NON_NULL(1, 2); GPtrArray * fu_context_get_udev_subsystems(FuContext *self) G_GNUC_NON_NULL(1); GPtrArray * fu_context_get_plugin_names_for_udev_subsystem(FuContext *self, const gchar *subsystem, GError **error) G_GNUC_NON_NULL(1, 2); void fu_context_add_esp_volume(FuContext *self, FuVolume *volume) G_GNUC_NON_NULL(1); FuSmbios * fu_context_get_smbios(FuContext *self) G_GNUC_NON_NULL(1); FuHwids * fu_context_get_hwids(FuContext *self) G_GNUC_NON_NULL(1); FuConfig * fu_context_get_config(FuContext *self) G_GNUC_NON_NULL(1); void fu_context_set_chassis_kind(FuContext *self, FuSmbiosChassisKind chassis_kind) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-context.c000066400000000000000000001334251460375044200202450ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuContext" #include "config.h" #include "fu-bios-settings-private.h" #include "fu-common-private.h" #include "fu-config-private.h" #include "fu-context-private.h" #include "fu-fdt-firmware.h" #include "fu-hwids-private.h" #include "fu-path.h" #include "fu-smbios-private.h" #include "fu-volume-private.h" /** * FuContext: * * A context that represents the shared system state. This object is shared * between the engine, the plugins and the devices. */ typedef struct { FuContextFlags flags; FuHwids *hwids; FuConfig *config; FuSmbios *smbios; FuSmbiosChassisKind chassis_kind; FuQuirks *quirks; GHashTable *runtime_versions; GHashTable *compile_versions; GHashTable *udev_subsystems; /* utf8:GPtrArray */ GPtrArray *esp_volumes; GHashTable *firmware_gtypes; /* utf8:GType */ GHashTable *hwid_flags; /* str: */ FuPowerState power_state; FuLidState lid_state; FuDisplayState display_state; guint battery_level; guint battery_threshold; FuBiosSettings *host_bios_settings; FuFirmware *fdt; /* optional */ gchar *esp_location; } FuContextPrivate; enum { SIGNAL_SECURITY_CHANGED, SIGNAL_LAST }; enum { PROP_0, PROP_POWER_STATE, PROP_LID_STATE, PROP_DISPLAY_STATE, PROP_BATTERY_LEVEL, PROP_BATTERY_THRESHOLD, PROP_FLAGS, PROP_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE_WITH_PRIVATE(FuContext, fu_context, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fu_context_get_instance_private(o)) static GFile * fu_context_get_fdt_file(GError **error) { g_autofree gchar *fdtfn_local = NULL; g_autofree gchar *fdtfn_sys = NULL; g_autofree gchar *localstatedir_pkg = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); g_autofree gchar *sysfsdir = NULL; /* look for override first, fall back to system value */ fdtfn_local = g_build_filename(localstatedir_pkg, "system.dtb", NULL); if (g_file_test(fdtfn_local, G_FILE_TEST_EXISTS)) return g_file_new_for_path(fdtfn_local); /* actual hardware value */ sysfsdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_FW); fdtfn_sys = g_build_filename(sysfsdir, "fdt", NULL); if (g_file_test(fdtfn_sys, G_FILE_TEST_EXISTS)) return g_file_new_for_path(fdtfn_sys); /* failed */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot find %s or override %s", fdtfn_sys, fdtfn_local); return NULL; } /** * fu_context_get_fdt: * @self: a #FuContext * @error: (nullable): optional return location for an error * * Gets and parses the system FDT, aka. the Flat Device Tree. * * The results are cached internally to the context, and subsequent calls to this function * returns the pre-parsed object. * * Returns: (transfer full): a #FuFdtFirmware, or %NULL * * Since: 1.8.10 **/ FuFirmware * fu_context_get_fdt(FuContext *self, GError **error) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* load if not already parsed */ if (priv->fdt == NULL) { g_autoptr(FuFirmware) fdt_tmp = fu_fdt_firmware_new(); g_autoptr(GFile) file = fu_context_get_fdt_file(error); if (file == NULL) return NULL; if (!fu_firmware_parse_file(fdt_tmp, file, FWUPD_INSTALL_FLAG_NO_SEARCH, error)) { g_prefix_error(error, "failed to parse FDT: "); return NULL; } priv->fdt = g_steal_pointer(&fdt_tmp); } /* success */ return g_object_ref(priv->fdt); } /** * fu_context_get_smbios: * @self: a #FuContext * * Gets the SMBIOS store. * * Returns: (transfer none): a #FuSmbios * * Since: 1.8.10 **/ FuSmbios * fu_context_get_smbios(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); return priv->smbios; } /** * fu_context_get_hwids: * @self: a #FuContext * * Gets the HWIDs store. * * Returns: (transfer none): a #FuHwids * * Since: 1.8.10 **/ FuHwids * fu_context_get_hwids(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); return priv->hwids; } /** * fu_context_get_config: * @self: a #FuContext * * Gets the system config. * * Returns: (transfer none): a #FuHwids * * Since: 1.9.1 **/ FuConfig * fu_context_get_config(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); return priv->config; } /** * fu_context_get_smbios_string: * @self: a #FuContext * @structure_type: a SMBIOS structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS * @offset: a SMBIOS offset * @error: (nullable): optional return location for an error * * Gets a hardware SMBIOS string. * * The @type and @offset can be referenced from the DMTF SMBIOS specification: * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf * * Returns: a string, or %NULL * * Since: 1.6.0 **/ const gchar * fu_context_get_smbios_string(FuContext *self, guint8 structure_type, guint8 offset, GError **error) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); if (!fu_context_has_flag(self, FU_CONTEXT_FLAG_LOADED_HWINFO)) { g_critical("cannot use SMBIOS before calling ->load_hwinfo()"); return NULL; } return fu_smbios_get_string(priv->smbios, structure_type, offset, error); } /** * fu_context_get_smbios_data: * @self: a #FuContext * @structure_type: a SMBIOS structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS * @error: (nullable): optional return location for an error * * Gets all hardware SMBIOS data for a specific type. * * Returns: (transfer container) (element-type GBytes): a #GBytes, or %NULL if not found * * Since: 1.9.8 **/ GPtrArray * fu_context_get_smbios_data(FuContext *self, guint8 structure_type, GError **error) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); /* must be valid and non-zero length */ if (!fu_context_has_flag(self, FU_CONTEXT_FLAG_LOADED_HWINFO)) { g_critical("cannot use SMBIOS before calling ->load_hwinfo()"); g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, "no data"); return NULL; } return fu_smbios_get_data(priv->smbios, structure_type, error); } /** * fu_context_get_smbios_integer: * @self: a #FuContext * @type: a structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS * @offset: a structure offset * @error: (nullable): optional return location for an error * * Reads an integer value from the SMBIOS string table of a specific structure. * * The @type and @offset can be referenced from the DMTF SMBIOS specification: * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf * * Returns: an integer, or %G_MAXUINT if invalid or not found * * Since: 1.6.0 **/ guint fu_context_get_smbios_integer(FuContext *self, guint8 type, guint8 offset, GError **error) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), G_MAXUINT); if (!fu_context_has_flag(self, FU_CONTEXT_FLAG_LOADED_HWINFO)) { g_critical("cannot use SMBIOS before calling ->load_hwinfo()"); return G_MAXUINT; } return fu_smbios_get_integer(priv->smbios, type, offset, error); } /** * fu_context_reload_bios_settings: * @self: a #FuContext * @error: (nullable): optional return location for an error * * Refreshes the list of firmware attributes on the system. * * Since: 1.8.4 **/ gboolean fu_context_reload_bios_settings(FuContext *self, GError **error) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), FALSE); return fu_bios_settings_setup(priv->host_bios_settings, error); } /** * fu_context_get_bios_settings: * @self: a #FuContext * * Returns all the firmware attributes defined in the system. * * Returns: (transfer full): A #FuBiosSettings * * Since: 1.8.4 **/ FuBiosSettings * fu_context_get_bios_settings(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); return g_object_ref(priv->host_bios_settings); } /** * fu_context_get_bios_setting: * @self: a #FuContext * @name: a BIOS setting title, e.g. `BootOrderLock` * * Finds out if a system supports a given BIOS setting. * * Returns: (transfer none): #FwupdBiosSetting if the attr exists. * * Since: 1.8.4 **/ FwupdBiosSetting * fu_context_get_bios_setting(FuContext *self, const gchar *name) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); g_return_val_if_fail(name != NULL, NULL); return fu_bios_settings_get_attr(priv->host_bios_settings, name); } /** * fu_context_get_bios_setting_pending_reboot: * @self: a #FuContext * * Determine if updates to BIOS settings are pending until next boot. * * Returns: %TRUE if updates are pending. * * Since: 1.8.4 **/ gboolean fu_context_get_bios_setting_pending_reboot(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); gboolean ret; g_return_val_if_fail(FU_IS_CONTEXT(self), FALSE); fu_bios_settings_get_pending_reboot(priv->host_bios_settings, &ret, NULL); return ret; } /** * fu_context_get_chassis_kind: * @self: a #FuContext * * Gets the chassis kind, if known. * * Returns: a #FuSmbiosChassisKind, e.g. %FU_SMBIOS_CHASSIS_KIND_LAPTOP * * Since: 1.8.10 **/ FuSmbiosChassisKind fu_context_get_chassis_kind(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), FALSE); return priv->chassis_kind; } /** * fu_context_set_chassis_kind: * @self: a #FuContext * @chassis_kind: a #FuSmbiosChassisKind, e.g. %FU_SMBIOS_CHASSIS_KIND_TABLET * * Sets the chassis kind. * * Since: 1.8.10 **/ void fu_context_set_chassis_kind(FuContext *self, FuSmbiosChassisKind chassis_kind) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); priv->chassis_kind = chassis_kind; } /** * fu_context_has_hwid_guid: * @self: a #FuContext * @guid: a GUID, e.g. `059eb22d-6dc7-59af-abd3-94bbe017f67c` * * Finds out if a hardware GUID exists. * * Returns: %TRUE if the GUID exists * * Since: 1.6.0 **/ gboolean fu_context_has_hwid_guid(FuContext *self, const gchar *guid) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), FALSE); if (!fu_context_has_flag(self, FU_CONTEXT_FLAG_LOADED_HWINFO)) { g_critical("cannot use HWIDs before calling ->load_hwinfo()"); return FALSE; } return fu_hwids_has_guid(priv->hwids, guid); } /** * fu_context_get_hwid_guids: * @self: a #FuContext * * Returns all the HWIDs defined in the system. All hardware IDs on a * specific system can be shown using the `fwupdmgr hwids` command. * * Returns: (transfer none) (element-type utf8): an array of GUIDs * * Since: 1.6.0 **/ GPtrArray * fu_context_get_hwid_guids(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); if (!fu_context_has_flag(self, FU_CONTEXT_FLAG_LOADED_HWINFO)) { g_critical("cannot use HWIDs before calling ->load_hwinfo()"); return NULL; } return fu_hwids_get_guids(priv->hwids); } /** * fu_context_get_hwid_value: * @self: a #FuContext * @key: a DMI ID, e.g. `BiosVersion` * * Gets the cached value for one specific key that is valid ASCII and suitable * for display. * * Returns: the string, e.g. `1.2.3`, or %NULL if not found * * Since: 1.6.0 **/ const gchar * fu_context_get_hwid_value(FuContext *self, const gchar *key) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); g_return_val_if_fail(key != NULL, NULL); if (!fu_context_has_flag(self, FU_CONTEXT_FLAG_LOADED_HWINFO)) { g_critical("cannot use HWIDs before calling ->load_hwinfo()"); return NULL; } return fu_hwids_get_value(priv->hwids, key); } /** * fu_context_get_hwid_replace_value: * @self: a #FuContext * @keys: a key, e.g. `HardwareID-3` or %FU_HWIDS_KEY_PRODUCT_SKU * @error: (nullable): optional return location for an error * * Gets the replacement value for a specific key. All hardware IDs on a * specific system can be shown using the `fwupdmgr hwids` command. * * Returns: (transfer full): a string, or %NULL for error. * * Since: 1.6.0 **/ gchar * fu_context_get_hwid_replace_value(FuContext *self, const gchar *keys, GError **error) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); g_return_val_if_fail(keys != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (!fu_context_has_flag(self, FU_CONTEXT_FLAG_LOADED_HWINFO)) { g_critical("cannot use HWIDs before calling ->load_hwinfo()"); g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, "no data"); return NULL; } return fu_hwids_get_replace_values(priv->hwids, keys, error); } /** * fu_context_add_runtime_version: * @self: a #FuContext * @component_id: an AppStream component id, e.g. `org.gnome.Software` * @version: a version string, e.g. `1.2.3` * * Sets a runtime version of a specific dependency. * * Since: 1.6.0 **/ void fu_context_add_runtime_version(FuContext *self, const gchar *component_id, const gchar *version) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); g_return_if_fail(component_id != NULL); g_return_if_fail(version != NULL); if (priv->runtime_versions == NULL) return; g_hash_table_insert(priv->runtime_versions, g_strdup(component_id), g_strdup(version)); } /** * fu_context_get_runtime_version: * @self: a #FuContext * @component_id: an AppStream component id, e.g. `org.gnome.Software` * * Sets a runtime version of a specific dependency. * * Returns: a version string, e.g. `1.2.3`, or %NULL * * Since: 1.9.10 **/ const gchar * fu_context_get_runtime_version(FuContext *self, const gchar *component_id) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); g_return_val_if_fail(component_id != NULL, NULL); if (priv->runtime_versions == NULL) return NULL; return g_hash_table_lookup(priv->runtime_versions, component_id); } /** * fu_context_get_runtime_versions: * @self: a #FuContext * * Gets the runtime versions for the context. * * Returns: (transfer none) (element-type utf8 utf8): dictionary of versions * * Since: 1.9.10 **/ GHashTable * fu_context_get_runtime_versions(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); return priv->runtime_versions; } /** * fu_context_add_compile_version: * @self: a #FuContext * @component_id: an AppStream component id, e.g. `org.gnome.Software` * @version: a version string, e.g. `1.2.3` * * Sets a compile-time version of a specific dependency. * * Since: 1.6.0 **/ void fu_context_add_compile_version(FuContext *self, const gchar *component_id, const gchar *version) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); g_return_if_fail(component_id != NULL); g_return_if_fail(version != NULL); if (priv->compile_versions == NULL) return; g_hash_table_insert(priv->compile_versions, g_strdup(component_id), g_strdup(version)); } /** * fu_context_get_compile_versions: * @self: a #FuContext * * Gets the compile time versions for the context. * * Returns: (transfer none) (element-type utf8 utf8): dictionary of versions * * Since: 1.9.10 **/ GHashTable * fu_context_get_compile_versions(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); return priv->compile_versions; } static gint fu_context_udev_plugin_names_sort_cb(gconstpointer a, gconstpointer b) { const gchar *str_a = *((const gchar **)a); const gchar *str_b = *((const gchar **)b); return g_strcmp0(str_a, str_b); } /** * fu_context_add_udev_subsystem: * @self: a #FuContext * @subsystem: a subsystem name, e.g. `pciport` * @plugin_name: (nullable): a plugin name, e.g. `iommu` * * Registers the udev subsystem to be watched by the daemon. * * Plugins can use this method only in fu_plugin_init() * * Since: 1.6.0 **/ void fu_context_add_udev_subsystem(FuContext *self, const gchar *subsystem, const gchar *plugin_name) { FuContextPrivate *priv = GET_PRIVATE(self); GPtrArray *plugin_names; g_return_if_fail(FU_IS_CONTEXT(self)); g_return_if_fail(subsystem != NULL); /* already exists */ plugin_names = g_hash_table_lookup(priv->udev_subsystems, subsystem); if (plugin_names != NULL) { if (plugin_name != NULL) { for (guint i = 0; i < plugin_names->len; i++) { const gchar *tmp = g_ptr_array_index(plugin_names, i); if (g_strcmp0(tmp, plugin_name) == 0) return; } g_ptr_array_add(plugin_names, g_strdup(plugin_name)); g_ptr_array_sort(plugin_names, fu_context_udev_plugin_names_sort_cb); } return; } /* add */ plugin_names = g_ptr_array_new_with_free_func(g_free); if (plugin_name != NULL) g_ptr_array_add(plugin_names, g_strdup(plugin_name)); g_hash_table_insert(priv->udev_subsystems, g_strdup(subsystem), g_steal_pointer(&plugin_names)); if (plugin_name != NULL) g_info("added udev subsystem watch of %s for plugin %s", subsystem, plugin_name); else g_info("added udev subsystem watch of %s", subsystem); } /** * fu_context_get_plugin_names_for_udev_subsystem: * @self: a #FuContext * @subsystem: a subsystem name, e.g. `pciport` * @error: (nullable): optional return location for an error * * Gets the plugins which registered for a specific subsystem. * * Returns: (transfer container) (element-type utf8): List of plugin names * * Since: 1.9.3 **/ GPtrArray * fu_context_get_plugin_names_for_udev_subsystem(FuContext *self, const gchar *subsystem, GError **error) { FuContextPrivate *priv = GET_PRIVATE(self); GPtrArray *plugin_names; g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); g_return_val_if_fail(subsystem != NULL, NULL); plugin_names = g_hash_table_lookup(priv->udev_subsystems, subsystem); if (plugin_names == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no plugins registered for %s", subsystem); return NULL; } return g_ptr_array_ref(plugin_names); } /** * fu_context_get_udev_subsystems: * @self: a #FuContext * * Gets the udev subsystems required by all plugins. * * Returns: (transfer container) (element-type utf8): List of subsystems * * Since: 1.6.0 **/ GPtrArray * fu_context_get_udev_subsystems(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_autoptr(GList) keys = g_hash_table_get_keys(priv->udev_subsystems); g_autoptr(GPtrArray) subsystems = g_ptr_array_new_with_free_func(g_free); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); for (GList *l = keys; l != NULL; l = l->next) { const gchar *subsystem = (const gchar *)l->data; g_ptr_array_add(subsystems, g_strdup(subsystem)); } return g_steal_pointer(&subsystems); } /** * fu_context_add_firmware_gtype: * @self: a #FuContext * @id: (nullable): an optional string describing the type, e.g. `ihex` * @gtype: a #GType e.g. `FU_TYPE_FOO_FIRMWARE` * * Adds a firmware #GType which is used when creating devices. If @id is not * specified then it is guessed using the #GType name. * * Plugins can use this method only in fu_plugin_init() * * Since: 1.6.0 **/ void fu_context_add_firmware_gtype(FuContext *self, const gchar *id, GType gtype) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); g_return_if_fail(id != NULL); g_return_if_fail(gtype != G_TYPE_INVALID); g_type_ensure(gtype); g_hash_table_insert(priv->firmware_gtypes, g_strdup(id), GSIZE_TO_POINTER(gtype)); } /** * fu_context_get_firmware_gtype_by_id: * @self: a #FuContext * @id: an string describing the type, e.g. `ihex` * * Returns the #GType using the firmware @id. * * Returns: a #GType, or %G_TYPE_INVALID * * Since: 1.6.0 **/ GType fu_context_get_firmware_gtype_by_id(FuContext *self, const gchar *id) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), G_TYPE_INVALID); g_return_val_if_fail(id != NULL, G_TYPE_INVALID); return GPOINTER_TO_SIZE(g_hash_table_lookup(priv->firmware_gtypes, id)); } static gint fu_context_gtypes_sort_cb(gconstpointer a, gconstpointer b) { const gchar *stra = *((const gchar **)a); const gchar *strb = *((const gchar **)b); return g_strcmp0(stra, strb); } /** * fu_context_get_firmware_gtype_ids: * @self: a #FuContext * * Returns all the firmware #GType IDs. * * Returns: (transfer container) (element-type utf8): firmware IDs * * Since: 1.6.0 **/ GPtrArray * fu_context_get_firmware_gtype_ids(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); GPtrArray *firmware_gtypes = g_ptr_array_new_with_free_func(g_free); g_autoptr(GList) keys = g_hash_table_get_keys(priv->firmware_gtypes); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); for (GList *l = keys; l != NULL; l = l->next) { const gchar *id = l->data; g_ptr_array_add(firmware_gtypes, g_strdup(id)); } g_ptr_array_sort(firmware_gtypes, fu_context_gtypes_sort_cb); return firmware_gtypes; } /** * fu_context_get_firmware_gtypes: * @self: a #FuContext * * Returns all the firmware #GType's. * * Returns: (transfer none) (element-type GType): Firmware types * * Since: 1.9.1 **/ GArray * fu_context_get_firmware_gtypes(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); GArray *firmware_gtypes = g_array_new(FALSE, FALSE, sizeof(GType)); g_autoptr(GList) values = g_hash_table_get_values(priv->firmware_gtypes); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); for (GList *l = values; l != NULL; l = l->next) { GType gtype = GPOINTER_TO_SIZE(l->data); g_array_append_val(firmware_gtypes, gtype); } return firmware_gtypes; } /** * fu_context_add_quirk_key: * @self: a #FuContext * @key: a quirk string, e.g. `DfuVersion` * * Adds a possible quirk key. If added by a plugin it should be namespaced * using the plugin name, where possible. * * Plugins can use this method only in fu_plugin_init() * * Since: 1.6.0 **/ void fu_context_add_quirk_key(FuContext *self, const gchar *key) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); g_return_if_fail(key != NULL); if (priv->quirks == NULL) return; fu_quirks_add_possible_key(priv->quirks, key); } /** * fu_context_lookup_quirk_by_id: * @self: a #FuContext * @guid: GUID to lookup * @key: an ID to match the entry, e.g. `Summary` * * Looks up an entry in the hardware database using a string value. * * Returns: (transfer none): values from the database, or %NULL if not found * * Since: 1.6.0 **/ const gchar * fu_context_lookup_quirk_by_id(FuContext *self, const gchar *guid, const gchar *key) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); g_return_val_if_fail(guid != NULL, NULL); g_return_val_if_fail(key != NULL, NULL); /* exact ID */ return fu_quirks_lookup_by_id(priv->quirks, guid, key); } typedef struct { FuContext *self; /* noref */ FuContextLookupIter iter_cb; gpointer user_data; } FuContextQuirkLookupHelper; static void fu_context_lookup_quirk_by_id_iter_cb(FuQuirks *self, const gchar *key, const gchar *value, gpointer user_data) { FuContextQuirkLookupHelper *helper = (FuContextQuirkLookupHelper *)user_data; helper->iter_cb(helper->self, key, value, helper->user_data); } /** * fu_context_lookup_quirk_by_id_iter: * @self: a #FuContext * @guid: GUID to lookup * @key: (nullable): an ID to match the entry, e.g. `Name`, or %NULL for all keys * @iter_cb: (scope call) (closure user_data): a function to call for each result * @user_data: user data passed to @iter_cb * * Looks up all entries in the hardware database using a GUID value. * * Returns: %TRUE if the ID was found, and @iter was called * * Since: 1.6.0 **/ gboolean fu_context_lookup_quirk_by_id_iter(FuContext *self, const gchar *guid, const gchar *key, FuContextLookupIter iter_cb, gpointer user_data) { FuContextPrivate *priv = GET_PRIVATE(self); FuContextQuirkLookupHelper helper = { .self = self, .iter_cb = iter_cb, .user_data = user_data, }; g_return_val_if_fail(FU_IS_CONTEXT(self), FALSE); g_return_val_if_fail(guid != NULL, FALSE); g_return_val_if_fail(iter_cb != NULL, FALSE); return fu_quirks_lookup_by_id_iter(priv->quirks, guid, key, fu_context_lookup_quirk_by_id_iter_cb, &helper); } /** * fu_context_security_changed: * @self: a #FuContext * * Informs the daemon that the HSI state may have changed. * * Since: 1.6.0 **/ void fu_context_security_changed(FuContext *self) { g_return_if_fail(FU_IS_CONTEXT(self)); g_signal_emit(self, signals[SIGNAL_SECURITY_CHANGED], 0); } typedef gboolean (*FuContextHwidsSetupFunc)(FuContext *self, FuHwids *hwids, GError **error); static void fu_context_hwid_quirk_cb(FuContext *self, const gchar *key, const gchar *value, gpointer user_data) { FuContextPrivate *priv = GET_PRIVATE(self); if (value != NULL) { g_auto(GStrv) values = g_strsplit(value, ",", -1); for (guint j = 0; values[j] != NULL; j++) g_hash_table_add(priv->hwid_flags, g_strdup(values[j])); } } /** * fu_context_load_hwinfo: * @self: a #FuContext * @progress: a #FuProgress * @flags: a #FuContextHwidFlags, e.g. %FU_CONTEXT_HWID_FLAG_LOAD_SMBIOS * @error: (nullable): optional return location for an error * * Loads all hardware information parts of the context. * * Returns: %TRUE for success * * Since: 1.8.10 **/ gboolean fu_context_load_hwinfo(FuContext *self, FuProgress *progress, FuContextHwidFlags flags, GError **error) { FuContextPrivate *priv = GET_PRIVATE(self); GPtrArray *guids; g_autoptr(GError) error_hwids = NULL; g_autoptr(GError) error_bios_settings = NULL; struct { const gchar *name; FuContextHwidFlags flag; FuContextHwidsSetupFunc func; } hwids_setup_map[] = {{"config", FU_CONTEXT_HWID_FLAG_LOAD_CONFIG, fu_hwids_config_setup}, {"smbios", FU_CONTEXT_HWID_FLAG_LOAD_SMBIOS, fu_hwids_smbios_setup}, {"fdt", FU_CONTEXT_HWID_FLAG_LOAD_FDT, fu_hwids_fdt_setup}, {"kenv", FU_CONTEXT_HWID_FLAG_LOAD_KENV, fu_hwids_kenv_setup}, {"dmi", FU_CONTEXT_HWID_FLAG_LOAD_DMI, fu_hwids_dmi_setup}, {"darwin", FU_CONTEXT_HWID_FLAG_LOAD_DARWIN, fu_hwids_darwin_setup}, {NULL, FU_CONTEXT_HWID_FLAG_NONE, NULL}}; g_return_val_if_fail(FU_IS_CONTEXT(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "hwids-setup-funcs"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "hwids-setup"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 3, "set-flags"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 95, "reload-bios-settings"); /* required always */ if (!fu_config_load(priv->config, error)) return FALSE; /* run all the HWID setup funcs */ for (guint i = 0; hwids_setup_map[i].name != NULL; i++) { if ((flags & hwids_setup_map[i].flag) > 0) { g_autoptr(GError) error_local = NULL; if (!hwids_setup_map[i].func(self, priv->hwids, &error_local)) { g_info("failed to load %s: %s", hwids_setup_map[i].name, error_local->message); continue; } } } fu_context_add_flag(self, FU_CONTEXT_FLAG_LOADED_HWINFO); fu_progress_step_done(progress); if (!fu_hwids_setup(priv->hwids, &error_hwids)) g_warning("Failed to load HWIDs: %s", error_hwids->message); fu_progress_step_done(progress); /* set the hwid flags */ guids = fu_context_get_hwid_guids(self); for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index(guids, i); fu_context_lookup_quirk_by_id_iter(self, guid, FU_QUIRKS_FLAGS, fu_context_hwid_quirk_cb, NULL); } fu_progress_step_done(progress); fu_context_add_udev_subsystem(self, "firmware-attributes", NULL); if (!fu_context_reload_bios_settings(self, &error_bios_settings)) g_debug("%s", error_bios_settings->message); fu_progress_step_done(progress); /* always */ return TRUE; } /** * fu_context_has_hwid_flag: * @self: a #FuContext * @flag: flag, e.g. `use-legacy-bootmgr-desc` * * Returns if a HwId custom flag exists, typically added from a DMI quirk. * * Returns: %TRUE if the flag exists * * Since: 1.7.2 **/ gboolean fu_context_has_hwid_flag(FuContext *self, const gchar *flag) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), FALSE); g_return_val_if_fail(flag != NULL, FALSE); return g_hash_table_lookup(priv->hwid_flags, flag) != NULL; } /** * fu_context_load_quirks: * @self: a #FuContext * @flags: quirks load flags, e.g. %FU_QUIRKS_LOAD_FLAG_READONLY_FS * @error: (nullable): optional return location for an error * * Loads all quirks into the context. * * Returns: %TRUE for success * * Since: 1.6.0 **/ gboolean fu_context_load_quirks(FuContext *self, FuQuirksLoadFlags flags, GError **error) { FuContextPrivate *priv = GET_PRIVATE(self); g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_CONTEXT(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* rebuild silo if required */ if (!fu_quirks_load(priv->quirks, flags, &error_local)) g_warning("Failed to load quirks: %s", error_local->message); /* always */ return TRUE; } /** * fu_context_get_power_state: * @self: a #FuContext * * Gets if the system is on battery power, e.g. UPS or laptop battery. * * Returns: a power state, e.g. %FU_POWER_STATE_BATTERY_DISCHARGING * * Since: 1.8.11 **/ FuPowerState fu_context_get_power_state(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), FALSE); return priv->power_state; } /** * fu_context_set_power_state: * @self: a #FuContext * @power_state: a power state, e.g. %FU_POWER_STATE_BATTERY_DISCHARGING * * Sets if the system is on battery power, e.g. UPS or laptop battery. * * Since: 1.8.11 **/ void fu_context_set_power_state(FuContext *self, FuPowerState power_state) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); if (priv->power_state == power_state) return; priv->power_state = power_state; g_info("power state now %s", fu_power_state_to_string(power_state)); g_object_notify(G_OBJECT(self), "power-state"); } /** * fu_context_get_lid_state: * @self: a #FuContext * * Gets the laptop lid state, if applicable. * * Returns: a lid state, e.g. %FU_LID_STATE_CLOSED * * Since: 1.7.4 **/ FuLidState fu_context_get_lid_state(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), FALSE); return priv->lid_state; } /** * fu_context_set_lid_state: * @self: a #FuContext * @lid_state: a lid state, e.g. %FU_LID_STATE_CLOSED * * Sets the laptop lid state, if applicable. * * Since: 1.7.4 **/ void fu_context_set_lid_state(FuContext *self, FuLidState lid_state) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); if (priv->lid_state == lid_state) return; priv->lid_state = lid_state; g_info("lid state now %s", fu_lid_state_to_string(lid_state)); g_object_notify(G_OBJECT(self), "lid-state"); } /** * fu_context_get_display_state: * @self: a #FuContext * * Gets the display state, if applicable. * * Returns: a display state, e.g. %FU_DISPLAY_STATE_CONNECTED * * Since: 1.9.6 **/ FuDisplayState fu_context_get_display_state(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), FALSE); return priv->display_state; } /** * fu_context_set_display_state: * @self: a #FuContext * @display_state: a display state, e.g. %FU_DISPLAY_STATE_CONNECTED * * Sets the display state, if applicable. * * Since: 1.9.6 **/ void fu_context_set_display_state(FuContext *self, FuDisplayState display_state) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); if (priv->display_state == display_state) return; priv->display_state = display_state; g_info("display-state now %s", fu_display_state_to_string(display_state)); g_object_notify(G_OBJECT(self), "display-state"); } /** * fu_context_get_battery_level: * @self: a #FuContext * * Gets the system battery level in percent. * * Returns: percentage value, or %FWUPD_BATTERY_LEVEL_INVALID for unknown * * Since: 1.6.0 **/ guint fu_context_get_battery_level(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), G_MAXUINT); return priv->battery_level; } /** * fu_context_set_battery_level: * @self: a #FuContext * @battery_level: value * * Sets the system battery level in percent. * * Since: 1.6.0 **/ void fu_context_set_battery_level(FuContext *self, guint battery_level) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); g_return_if_fail(battery_level <= FWUPD_BATTERY_LEVEL_INVALID); if (priv->battery_level == battery_level) return; priv->battery_level = battery_level; g_info("battery level now %u", battery_level); g_object_notify(G_OBJECT(self), "battery-level"); } /** * fu_context_get_battery_threshold: * @self: a #FuContext * * Gets the system battery threshold in percent. * * Returns: percentage value, or %FWUPD_BATTERY_LEVEL_INVALID for unknown * * Since: 1.6.0 **/ guint fu_context_get_battery_threshold(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), G_MAXUINT); return priv->battery_threshold; } /** * fu_context_set_battery_threshold: * @self: a #FuContext * @battery_threshold: value * * Sets the system battery threshold in percent. * * Since: 1.6.0 **/ void fu_context_set_battery_threshold(FuContext *self, guint battery_threshold) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); g_return_if_fail(battery_threshold <= FWUPD_BATTERY_LEVEL_INVALID); if (priv->battery_threshold == battery_threshold) return; priv->battery_threshold = battery_threshold; g_info("battery threshold now %u", battery_threshold); g_object_notify(G_OBJECT(self), "battery-threshold"); } /** * fu_context_add_flag: * @context: a #FuContext * @flag: the context flag * * Adds a specific flag to the context. * * Since: 1.8.5 **/ void fu_context_add_flag(FuContext *context, FuContextFlags flag) { FuContextPrivate *priv = GET_PRIVATE(context); g_return_if_fail(FU_IS_CONTEXT(context)); if (priv->flags & flag) return; priv->flags |= flag; g_object_notify(G_OBJECT(context), "flags"); } /** * fu_context_remove_flag: * @context: a #FuContext * @flag: the context flag * * Removes a specific flag from the context. * * Since: 1.8.10 **/ void fu_context_remove_flag(FuContext *context, FuContextFlags flag) { FuContextPrivate *priv = GET_PRIVATE(context); g_return_if_fail(FU_IS_CONTEXT(context)); if ((priv->flags & flag) == 0) return; priv->flags &= ~flag; g_object_notify(G_OBJECT(context), "flags"); } /** * fu_context_has_flag: * @context: a #FuContext * @flag: the context flag * * Finds if the context has a specific flag. * * Returns: %TRUE if the flag is set * * Since: 1.8.5 **/ gboolean fu_context_has_flag(FuContext *context, FuContextFlags flag) { FuContextPrivate *priv = GET_PRIVATE(context); g_return_val_if_fail(FU_IS_CONTEXT(context), FALSE); return (priv->flags & flag) > 0; } /** * fu_context_add_esp_volume: * @self: a #FuContext * @volume: a #FuVolume * * Adds an ESP volume location. * * Since: 1.8.5 **/ void fu_context_add_esp_volume(FuContext *self, FuVolume *volume) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); g_return_if_fail(FU_IS_VOLUME(volume)); /* check for dupes */ for (guint i = 0; i < priv->esp_volumes->len; i++) { FuVolume *volume_tmp = g_ptr_array_index(priv->esp_volumes, i); if (g_strcmp0(fu_volume_get_id(volume_tmp), fu_volume_get_id(volume)) == 0) { g_debug("not adding duplicate volume %s", fu_volume_get_id(volume)); return; } } /* add */ g_ptr_array_add(priv->esp_volumes, g_object_ref(volume)); } /** * fu_context_set_esp_location: * @self: A #FuContext object. * @location: The path to the preferred ESP. * * Sets the user's desired ESP (EFI System Partition) location for the given #FuContext. */ void fu_context_set_esp_location(FuContext *self, const gchar *location) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CONTEXT(self)); g_return_if_fail(location != NULL); g_free(priv->esp_location); priv->esp_location = g_strdup(location); } /** * fu_context_get_esp_location: * @self: The FuContext object. * * Retrieves the user's desired ESP (EFI System Partition) location for the given #FuContext * * Return: The preferred ESP location as a string. */ const gchar * fu_context_get_esp_location(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); return priv->esp_location; } /** * fu_context_get_esp_volumes: * @self: a #FuContext * @error: (nullable): optional return location for an error * * Finds all volumes that could be an ESP. * * The volumes are cached and so subsequent calls to this function will be much faster. * * Returns: (transfer container) (element-type FuVolume): a #GPtrArray, or %NULL if no ESP was found * * Since: 1.8.5 **/ GPtrArray * fu_context_get_esp_volumes(FuContext *self, GError **error) { FuContextPrivate *priv = GET_PRIVATE(self); const gchar *path_tmp; g_autoptr(GError) error_bdp = NULL; g_autoptr(GError) error_esp = NULL; g_autoptr(GPtrArray) volumes_bdp = NULL; g_autoptr(GPtrArray) volumes_esp = NULL; g_return_val_if_fail(FU_IS_CONTEXT(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* cached result */ if (priv->esp_volumes->len > 0) return g_ptr_array_ref(priv->esp_volumes); /* for the test suite use local directory for ESP */ path_tmp = g_getenv("FWUPD_UEFI_ESP_PATH"); if (path_tmp != NULL) { g_autoptr(FuVolume) vol = fu_volume_new_from_mount_path(path_tmp); fu_context_add_esp_volume(self, vol); return g_ptr_array_ref(priv->esp_volumes); } /* ESP */ volumes_esp = fu_volume_new_by_kind(FU_VOLUME_KIND_ESP, &error_esp); if (volumes_esp == NULL) { g_debug("%s", error_esp->message); } else { for (guint i = 0; i < volumes_esp->len; i++) { FuVolume *vol = g_ptr_array_index(volumes_esp, i); g_autofree gchar *type = fu_volume_get_id_type(vol); if (g_strcmp0(type, "ext4") == 0) continue; fu_context_add_esp_volume(self, vol); } } /* BDP */ volumes_bdp = fu_volume_new_by_kind(FU_VOLUME_KIND_BDP, &error_bdp); if (volumes_bdp == NULL) { g_debug("%s", error_bdp->message); } else { for (guint i = 0; i < volumes_bdp->len; i++) { FuVolume *vol = g_ptr_array_index(volumes_bdp, i); g_autofree gchar *type = fu_volume_get_id_type(vol); if (g_strcmp0(type, "vfat") != 0) continue; if (!fu_volume_is_internal(vol)) continue; fu_context_add_esp_volume(self, vol); } } /* nothing found */ if (priv->esp_volumes->len == 0) { g_autofree gchar *base = NULL; g_autofree gchar *path = NULL; g_autoptr(GPtrArray) devices = NULL; /* udisks2 is working */ devices = fu_common_get_block_devices(NULL); if (devices != NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "No ESP or BDP found"); return NULL; } base = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR_PKG); path = g_build_filename(base, "fwupd.conf", NULL); g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "No valid 'EspLocation' specified in %s", path); return NULL; } /* success */ return g_ptr_array_ref(priv->esp_volumes); } static void fu_context_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuContext *self = FU_CONTEXT(object); FuContextPrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_POWER_STATE: g_value_set_uint(value, priv->power_state); break; case PROP_LID_STATE: g_value_set_uint(value, priv->lid_state); break; case PROP_DISPLAY_STATE: g_value_set_uint(value, priv->display_state); break; case PROP_BATTERY_LEVEL: g_value_set_uint(value, priv->battery_level); break; case PROP_BATTERY_THRESHOLD: g_value_set_uint(value, priv->battery_threshold); break; case PROP_FLAGS: g_value_set_uint64(value, priv->flags); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_context_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuContext *self = FU_CONTEXT(object); FuContextPrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_POWER_STATE: fu_context_set_power_state(self, g_value_get_uint(value)); break; case PROP_LID_STATE: fu_context_set_lid_state(self, g_value_get_uint(value)); break; case PROP_DISPLAY_STATE: fu_context_set_display_state(self, g_value_get_uint(value)); break; case PROP_BATTERY_LEVEL: fu_context_set_battery_level(self, g_value_get_uint(value)); break; case PROP_BATTERY_THRESHOLD: fu_context_set_battery_threshold(self, g_value_get_uint(value)); break; case PROP_FLAGS: priv->flags = g_value_get_uint64(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_context_finalize(GObject *object) { FuContext *self = FU_CONTEXT(object); FuContextPrivate *priv = GET_PRIVATE(self); if (priv->fdt != NULL) g_object_unref(priv->fdt); g_free(priv->esp_location); g_hash_table_unref(priv->runtime_versions); g_hash_table_unref(priv->compile_versions); g_object_unref(priv->hwids); g_object_unref(priv->config); g_hash_table_unref(priv->hwid_flags); g_object_unref(priv->quirks); g_object_unref(priv->smbios); g_object_unref(priv->host_bios_settings); g_hash_table_unref(priv->firmware_gtypes); g_hash_table_unref(priv->udev_subsystems); g_ptr_array_unref(priv->esp_volumes); G_OBJECT_CLASS(fu_context_parent_class)->finalize(object); } static void fu_context_class_init(FuContextClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->get_property = fu_context_get_property; object_class->set_property = fu_context_set_property; /** * FuContext:power-state: * * The system power state. * * Since: 1.8.11 */ pspec = g_param_spec_uint("power-state", NULL, NULL, FU_POWER_STATE_UNKNOWN, FU_POWER_STATE_LAST, FU_POWER_STATE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_POWER_STATE, pspec); /** * FuContext:lid-state: * * The system lid state. * * Since: 1.7.4 */ pspec = g_param_spec_uint("lid-state", NULL, NULL, FU_LID_STATE_UNKNOWN, FU_LID_STATE_LAST, FU_LID_STATE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_LID_STATE, pspec); /** * FuContext:display-state: * * The display state. * * Since: 1.9.6 */ pspec = g_param_spec_uint("display-state", NULL, NULL, FU_DISPLAY_STATE_UNKNOWN, FU_DISPLAY_STATE_LAST, FU_DISPLAY_STATE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_DISPLAY_STATE, pspec); /** * FuContext:battery-level: * * The system battery level in percent. * * Since: 1.6.0 */ pspec = g_param_spec_uint("battery-level", NULL, NULL, 0, FWUPD_BATTERY_LEVEL_INVALID, FWUPD_BATTERY_LEVEL_INVALID, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BATTERY_LEVEL, pspec); /** * FuContext:battery-threshold: * * The system battery threshold in percent. * * Since: 1.6.0 */ pspec = g_param_spec_uint("battery-threshold", NULL, NULL, 0, FWUPD_BATTERY_LEVEL_INVALID, FWUPD_BATTERY_LEVEL_INVALID, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BATTERY_THRESHOLD, pspec); /** * FuContext:flags: * * The context flags. * * Since: 1.8.10 */ pspec = g_param_spec_uint64("flags", NULL, NULL, FU_CONTEXT_FLAG_NONE, G_MAXUINT64, FU_CONTEXT_FLAG_NONE, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FLAGS, pspec); /** * FuContext::security-changed: * @self: the #FuContext instance that emitted the signal * * The ::security-changed signal is emitted when some system state has changed that could * have affected the security level. * * Since: 1.6.0 **/ signals[SIGNAL_SECURITY_CHANGED] = g_signal_new("security-changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FuContextClass, security_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); object_class->finalize = fu_context_finalize; } static void fu_context_init(FuContext *self) { FuContextPrivate *priv = GET_PRIVATE(self); priv->chassis_kind = FU_SMBIOS_CHASSIS_KIND_UNKNOWN; priv->battery_level = FWUPD_BATTERY_LEVEL_INVALID; priv->battery_threshold = FWUPD_BATTERY_LEVEL_INVALID; priv->smbios = fu_smbios_new(); priv->hwids = fu_hwids_new(); priv->config = fu_config_new(); priv->hwid_flags = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); priv->udev_subsystems = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); priv->firmware_gtypes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); priv->quirks = fu_quirks_new(); priv->host_bios_settings = fu_bios_settings_new(); priv->esp_volumes = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); priv->runtime_versions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); priv->compile_versions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); } /** * fu_context_new: * * Creates a new #FuContext * * Returns: (transfer full): the object * * Since: 1.6.0 **/ FuContext * fu_context_new(void) { return FU_CONTEXT(g_object_new(FU_TYPE_CONTEXT, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-context.h000066400000000000000000000120021460375044200202350ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-bios-settings.h" #include "fu-common.h" #include "fu-firmware.h" #include "fu-smbios.h" #define FU_TYPE_CONTEXT (fu_context_get_type()) G_DECLARE_DERIVABLE_TYPE(FuContext, fu_context, FU, CONTEXT, GObject) struct _FuContextClass { GObjectClass parent_class; /* signals */ void (*security_changed)(FuContext *self); }; /** * FuContextLookupIter: * @self: a #FuContext * @key: a key * @value: a value * @user_data: user data * * The context lookup iteration callback. */ typedef void (*FuContextLookupIter)(FuContext *self, const gchar *key, const gchar *value, gpointer user_data); /** * FU_CONTEXT_FLAG_NONE: * * No flags set. * * Since: 1.8.5 **/ #define FU_CONTEXT_FLAG_NONE (0u) /** * FU_CONTEXT_FLAG_SAVE_EVENTS: * * Save events so that they can be replayed to emulate devices. * * Since: 1.8.5 **/ #define FU_CONTEXT_FLAG_SAVE_EVENTS (1u << 0) /** * FU_CONTEXT_FLAG_SYSTEM_INHIBIT: * * All devices are not updatable due to a system-wide inhibit. * * Since: 1.8.10 **/ #define FU_CONTEXT_FLAG_SYSTEM_INHIBIT (1u << 1) /** * FU_CONTEXT_FLAG_LOADED_HWINFO: * * Hardware information has been loaded with a call to fu_context_load_hwinfo(). * * Since: 1.9.10 **/ #define FU_CONTEXT_FLAG_LOADED_HWINFO (1u << 2) /** * FuContextFlags: * * The context flags. **/ typedef guint64 FuContextFlags; void fu_context_add_flag(FuContext *context, FuContextFlags flag) G_GNUC_NON_NULL(1); void fu_context_remove_flag(FuContext *context, FuContextFlags flag) G_GNUC_NON_NULL(1); gboolean fu_context_has_flag(FuContext *context, FuContextFlags flag) G_GNUC_NON_NULL(1); const gchar * fu_context_get_smbios_string(FuContext *self, guint8 structure_type, guint8 offset, GError **error) G_GNUC_NON_NULL(1); guint fu_context_get_smbios_integer(FuContext *self, guint8 type, guint8 offset, GError **error) G_GNUC_NON_NULL(1); GPtrArray * fu_context_get_smbios_data(FuContext *self, guint8 structure_type, GError **error) G_GNUC_NON_NULL(1); gboolean fu_context_has_hwid_guid(FuContext *self, const gchar *guid) G_GNUC_NON_NULL(1); GPtrArray * fu_context_get_hwid_guids(FuContext *self) G_GNUC_NON_NULL(1); gboolean fu_context_has_hwid_flag(FuContext *self, const gchar *flag) G_GNUC_NON_NULL(1, 2); const gchar * fu_context_get_hwid_value(FuContext *self, const gchar *key) G_GNUC_NON_NULL(1, 2); gchar * fu_context_get_hwid_replace_value(FuContext *self, const gchar *keys, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); void fu_context_add_runtime_version(FuContext *self, const gchar *component_id, const gchar *version) G_GNUC_NON_NULL(1, 2, 3); const gchar * fu_context_get_runtime_version(FuContext *self, const gchar *component_id) G_GNUC_NON_NULL(1, 2); void fu_context_add_compile_version(FuContext *self, const gchar *component_id, const gchar *version) G_GNUC_NON_NULL(1, 2, 3); const gchar * fu_context_lookup_quirk_by_id(FuContext *self, const gchar *guid, const gchar *key) G_GNUC_NON_NULL(1, 2, 3); gboolean fu_context_lookup_quirk_by_id_iter(FuContext *self, const gchar *guid, const gchar *key, FuContextLookupIter iter_cb, gpointer user_data) G_GNUC_NON_NULL(1, 2); void fu_context_add_quirk_key(FuContext *self, const gchar *key) G_GNUC_NON_NULL(1, 2); void fu_context_security_changed(FuContext *self) G_GNUC_NON_NULL(1); FuPowerState fu_context_get_power_state(FuContext *self) G_GNUC_NON_NULL(1); void fu_context_set_power_state(FuContext *self, FuPowerState power_state) G_GNUC_NON_NULL(1); FuLidState fu_context_get_lid_state(FuContext *self) G_GNUC_NON_NULL(1); void fu_context_set_lid_state(FuContext *self, FuLidState lid_state) G_GNUC_NON_NULL(1); FuDisplayState fu_context_get_display_state(FuContext *self) G_GNUC_NON_NULL(1); void fu_context_set_display_state(FuContext *self, FuDisplayState display_state) G_GNUC_NON_NULL(1); guint fu_context_get_battery_level(FuContext *self) G_GNUC_NON_NULL(1); void fu_context_set_battery_level(FuContext *self, guint battery_level) G_GNUC_NON_NULL(1); guint fu_context_get_battery_threshold(FuContext *self) G_GNUC_NON_NULL(1); void fu_context_set_battery_threshold(FuContext *self, guint battery_threshold) G_GNUC_NON_NULL(1); FuBiosSettings * fu_context_get_bios_settings(FuContext *self) G_GNUC_NON_NULL(1); gboolean fu_context_get_bios_setting_pending_reboot(FuContext *self) G_GNUC_NON_NULL(1); FwupdBiosSetting * fu_context_get_bios_setting(FuContext *self, const gchar *name) G_GNUC_NON_NULL(1, 2); GPtrArray * fu_context_get_esp_volumes(FuContext *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); FuFirmware * fu_context_get_fdt(FuContext *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); FuSmbiosChassisKind fu_context_get_chassis_kind(FuContext *self) G_GNUC_NON_NULL(1); void fu_context_set_esp_location(FuContext *self, const gchar *location); const gchar * fu_context_get_esp_location(FuContext *self); fwupd-1.9.16/libfwupdplugin/fu-coswid-firmware.c000066400000000000000000001065641460375044200216670ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #ifdef HAVE_CBOR #include #endif #include "fu-byte-array.h" #include "fu-common.h" #include "fu-coswid-firmware.h" #include "fu-coswid-struct.h" /** * FuCoswidFirmware: * * A coSWID SWID section. * * See also: [class@FuCoswidFirmware] */ typedef struct { gchar *product; gchar *summary; gchar *colloquial_version; FuCoswidVersionScheme version_scheme; GPtrArray *links; /* of FuCoswidFirmwareLink */ GPtrArray *entities; /* of FuCoswidFirmwareEntity */ GPtrArray *payloads; /* of FuCoswidFirmwarePayload */ } FuCoswidFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuCoswidFirmware, fu_coswid_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_coswid_firmware_get_instance_private(o)) typedef struct { gchar *name; gchar *regid; FuCoswidEntityRole roles[6]; } FuCoswidFirmwareEntity; typedef struct { gchar *href; FuCoswidLinkRel rel; } FuCoswidFirmwareLink; typedef struct { GByteArray *value; FuCoswidHashAlg alg_id; } FuCoswidFirmwareHash; typedef struct { gchar *name; guint64 size; GPtrArray *hashes; /* of FuCoswidFirmwareHash */ } FuCoswidFirmwarePayload; static void fu_coswid_firmware_entity_free(FuCoswidFirmwareEntity *entity) { g_free(entity->name); g_free(entity->regid); g_free(entity); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCoswidFirmwareEntity, fu_coswid_firmware_entity_free) static void fu_coswid_firmware_link_free(FuCoswidFirmwareLink *link) { g_free(link->href); g_free(link); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCoswidFirmwareLink, fu_coswid_firmware_link_free) static void fu_coswid_firmware_hash_free(FuCoswidFirmwareHash *hash) { if (hash->value != NULL) g_byte_array_unref(hash->value); g_free(hash); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCoswidFirmwareHash, fu_coswid_firmware_hash_free) static FuCoswidFirmwarePayload * fu_coswid_firmware_payload_new(void) { FuCoswidFirmwarePayload *payload = g_new0(FuCoswidFirmwarePayload, 1); payload->hashes = g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_hash_free); return payload; } static void fu_coswid_firmware_payload_free(FuCoswidFirmwarePayload *payload) { g_ptr_array_unref(payload->hashes); g_free(payload->name); g_free(payload); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCoswidFirmwarePayload, fu_coswid_firmware_payload_free) #ifdef HAVE_CBOR G_DEFINE_AUTOPTR_CLEANUP_FUNC(cbor_item_t, cbor_intermediate_decref) static gchar * fu_coswid_firmware_strndup(cbor_item_t *item) { if (!cbor_string_is_definite(item)) return NULL; return g_strndup((const gchar *)cbor_string_handle(item), cbor_string_length(item)); } typedef gboolean (*FuCoswidFirmwareItemFunc)(FuCoswidFirmware *self, cbor_item_t *item, gpointer user_data, GError **error) G_GNUC_WARN_UNUSED_RESULT; static gboolean fu_coswid_firmware_parse_one_or_many(FuCoswidFirmware *self, cbor_item_t *item, FuCoswidFirmwareItemFunc func, gpointer user_data, GError **error) { /* one */ if (cbor_isa_map(item)) return func(self, item, user_data, error); /* many */ if (cbor_isa_array(item)) { for (guint j = 0; j < cbor_array_size(item); j++) { g_autoptr(cbor_item_t) value = cbor_array_get(item, j); if (!func(self, value, user_data, error)) return FALSE; } return TRUE; } /* not sure what to do */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "neither an array or map"); return FALSE; } static gboolean fu_coswid_firmware_parse_meta(FuCoswidFirmware *self, cbor_item_t *item, gpointer user_data, GError **error) { FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); struct cbor_pair *pairs = cbor_map_handle(item); for (gsize i = 0; i < cbor_map_size(item); i++) { FuCoswidTag tag_id = cbor_get_uint8(pairs[i].key); if (tag_id == FU_COSWID_TAG_SUMMARY) { priv->summary = fu_coswid_firmware_strndup(pairs[i].value); } else if (tag_id == FU_COSWID_TAG_COLLOQUIAL_VERSION) { priv->colloquial_version = fu_coswid_firmware_strndup(pairs[i].value); } else { g_debug("unhandled tag %s from %s", fu_coswid_tag_to_string(tag_id), fu_coswid_tag_to_string(FU_COSWID_TAG_SOFTWARE_META)); } } /* success */ return TRUE; } static gboolean fu_coswid_firmware_parse_link(FuCoswidFirmware *self, cbor_item_t *item, gpointer user_data, GError **error) { FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); struct cbor_pair *pairs = cbor_map_handle(item); g_autoptr(FuCoswidFirmwareLink) link = g_new0(FuCoswidFirmwareLink, 1); for (gsize i = 0; i < cbor_map_size(item); i++) { FuCoswidTag tag_id = cbor_get_uint8(pairs[i].key); if (tag_id == FU_COSWID_TAG_HREF) { link->href = fu_coswid_firmware_strndup(pairs[i].value); } else if (tag_id == FU_COSWID_TAG_REL) { if (cbor_isa_negint(pairs[i].value)) link->rel = (-1) - cbor_get_uint8(pairs[i].value); else link->rel = cbor_get_uint8(pairs[i].value); } else { g_debug("unhandled tag %s from %s", fu_coswid_tag_to_string(tag_id), fu_coswid_tag_to_string(FU_COSWID_TAG_LINK)); } } /* success */ g_ptr_array_add(priv->links, g_steal_pointer(&link)); return TRUE; } static gboolean fu_coswid_firmware_parse_hash(FuCoswidFirmware *self, cbor_item_t *item, gpointer user_data, GError **error) { FuCoswidFirmwarePayload *payload = (FuCoswidFirmwarePayload *)user_data; g_autoptr(FuCoswidFirmwareHash) hash = g_new0(FuCoswidFirmwareHash, 1); g_autoptr(cbor_item_t) hash_item_alg_id = cbor_array_get(item, 0); g_autoptr(cbor_item_t) hash_item_value = cbor_array_get(item, 1); /* sanity check */ if (hash_item_alg_id == NULL || hash_item_value == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid hash item"); return FALSE; } /* success */ hash->alg_id = cbor_get_uint8(hash_item_alg_id); hash->value = g_byte_array_new(); g_byte_array_append(hash->value, cbor_bytestring_handle(hash_item_value), cbor_bytestring_length(hash_item_value)); g_ptr_array_add(payload->hashes, g_steal_pointer(&hash)); return TRUE; } static gboolean fu_coswid_firmware_parse_hash_array(FuCoswidFirmware *self, cbor_item_t *item, gpointer user_data, GError **error) { for (guint j = 0; j < cbor_array_size(item); j++) { g_autoptr(cbor_item_t) value = cbor_array_get(item, j); if (!fu_coswid_firmware_parse_hash(self, value, user_data, error)) return FALSE; } return TRUE; } static gboolean fu_coswid_firmware_parse_file(FuCoswidFirmware *self, cbor_item_t *item, gpointer user_data, GError **error) { FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); struct cbor_pair *pairs = cbor_map_handle(item); g_autoptr(FuCoswidFirmwarePayload) payload = fu_coswid_firmware_payload_new(); for (gsize i = 0; i < cbor_map_size(item); i++) { FuCoswidTag tag_id = cbor_get_uint8(pairs[i].key); if (tag_id == FU_COSWID_TAG_FS_NAME) { payload->name = fu_coswid_firmware_strndup(pairs[i].value); } else if (tag_id == FU_COSWID_TAG_SIZE) { payload->size = cbor_get_int(pairs[i].value); } else if (tag_id == FU_COSWID_TAG_HASH) { if (cbor_isa_array(pairs[i].value) && cbor_array_size(pairs[i].value) >= 1) { g_autoptr(cbor_item_t) value = cbor_array_get(pairs[i].value, 0); /* we can't use fu_coswid_firmware_parse_one_or_many() here as * the hash is an array, not a map -- for some reason */ if (cbor_isa_array(value)) { if (!fu_coswid_firmware_parse_hash_array(self, pairs[i].value, payload, error)) return FALSE; } else { if (!fu_coswid_firmware_parse_hash(self, pairs[i].value, payload, error)) return FALSE; } } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "hashes neither an array or array of array"); return FALSE; } } else { g_debug("unhandled tag %s from %s", fu_coswid_tag_to_string(tag_id), fu_coswid_tag_to_string(FU_COSWID_TAG_FILE)); } } /* success */ g_ptr_array_add(priv->payloads, g_steal_pointer(&payload)); return TRUE; } static gboolean fu_coswid_firmware_parse_path_elements(FuCoswidFirmware *self, cbor_item_t *item, gpointer user_data, GError **error) { struct cbor_pair *pairs = cbor_map_handle(item); for (gsize i = 0; i < cbor_map_size(item); i++) { FuCoswidTag tag_id = cbor_get_uint8(pairs[i].key); if (tag_id == FU_COSWID_TAG_FILE) { if (!fu_coswid_firmware_parse_one_or_many(self, pairs[i].value, fu_coswid_firmware_parse_file, NULL, /* user_data */ error)) return FALSE; } else { g_debug("unhandled tag %s from %s", fu_coswid_tag_to_string(tag_id), fu_coswid_tag_to_string(FU_COSWID_TAG_PATH_ELEMENTS)); } } /* success */ return TRUE; } static gboolean fu_coswid_firmware_parse_directory(FuCoswidFirmware *self, cbor_item_t *item, gpointer user_data, GError **error) { struct cbor_pair *pairs = cbor_map_handle(item); for (gsize i = 0; i < cbor_map_size(item); i++) { FuCoswidTag tag_id = cbor_get_uint8(pairs[i].key); if (tag_id == FU_COSWID_TAG_PATH_ELEMENTS) { if (!fu_coswid_firmware_parse_one_or_many( self, pairs[i].value, fu_coswid_firmware_parse_path_elements, NULL, /* user_data */ error)) return FALSE; } else { g_debug("unhandled tag %s from %s", fu_coswid_tag_to_string(tag_id), fu_coswid_tag_to_string(FU_COSWID_TAG_DIRECTORY)); } } /* success */ return TRUE; } static gboolean fu_coswid_firmware_parse_payload(FuCoswidFirmware *self, cbor_item_t *item, gpointer user_data, GError **error) { struct cbor_pair *pairs = cbor_map_handle(item); for (gsize i = 0; i < cbor_map_size(item); i++) { FuCoswidTag tag_id = cbor_get_uint8(pairs[i].key); if (tag_id == FU_COSWID_TAG_FILE) { if (!fu_coswid_firmware_parse_one_or_many(self, pairs[i].value, fu_coswid_firmware_parse_file, NULL, /* user_data */ error)) return FALSE; } else if (tag_id == FU_COSWID_TAG_DIRECTORY) { if (!fu_coswid_firmware_parse_one_or_many( self, pairs[i].value, fu_coswid_firmware_parse_directory, NULL, /* user_data */ error)) return FALSE; } else { g_debug("unhandled tag %s from %s", fu_coswid_tag_to_string(tag_id), fu_coswid_tag_to_string(FU_COSWID_TAG_PAYLOAD)); } } /* success */ return TRUE; } static gboolean fu_coswid_firmware_parse_entity(FuCoswidFirmware *self, cbor_item_t *item, gpointer user_data, GError **error) { FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); struct cbor_pair *pairs = cbor_map_handle(item); guint entity_role_cnt = 0; g_autoptr(FuCoswidFirmwareEntity) entity = g_new0(FuCoswidFirmwareEntity, 1); for (gsize i = 0; i < cbor_map_size(item); i++) { FuCoswidTag tag_id = cbor_get_uint8(pairs[i].key); if (tag_id == FU_COSWID_TAG_ENTITY_NAME) { entity->name = fu_coswid_firmware_strndup(pairs[i].value); } else if (tag_id == FU_COSWID_TAG_REG_ID) { entity->regid = fu_coswid_firmware_strndup(pairs[i].value); } else if (tag_id == FU_COSWID_TAG_ROLE) { if (cbor_isa_uint(pairs[i].value)) { FuCoswidEntityRole role = cbor_get_uint8(pairs[i].value); entity->roles[entity_role_cnt++] = role; } for (guint j = 0; j < cbor_array_size(pairs[i].value); j++) { g_autoptr(cbor_item_t) value = cbor_array_get(pairs[i].value, j); FuCoswidEntityRole role = cbor_get_uint8(value); if (entity_role_cnt >= G_N_ELEMENTS(entity->roles)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "too many roles"); return FALSE; } entity->roles[entity_role_cnt++] = role; } } else { g_debug("unhandled tag %s from %s", fu_coswid_tag_to_string(tag_id), fu_coswid_tag_to_string(FU_COSWID_TAG_ENTITY)); } } /* success */ g_ptr_array_add(priv->entities, g_steal_pointer(&entity)); return TRUE; } #endif static gboolean fu_coswid_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { #ifdef HAVE_CBOR FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware); FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); struct cbor_load_result result = {0x0}; struct cbor_pair *pairs = NULL; g_autoptr(cbor_item_t) item = NULL; item = cbor_load(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), &result); if (item == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to parse CBOR at offset 0x%x: 0x%x", (guint)result.error.position, result.error.code); return FALSE; } fu_firmware_set_size(firmware, result.read); /* pretty-print the result */ if (g_getenv("FWUPD_CBOR_VERBOSE") != NULL) { cbor_describe(item, stdout); fflush(stdout); } /* sanity check */ if (!cbor_isa_map(item)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "root item is not a map"); return FALSE; } /* parse out anything interesting */ pairs = cbor_map_handle(item); for (gsize i = 0; i < cbor_map_size(item); i++) { FuCoswidTag tag_id = cbor_get_uint8(pairs[i].key); /* identity can be specified as a string or in binary */ if (tag_id == FU_COSWID_TAG_TAG_ID) { g_autofree gchar *str = NULL; if (cbor_isa_string(pairs[i].value)) { str = fu_coswid_firmware_strndup(pairs[i].value); } else if (cbor_isa_bytestring(pairs[i].value) && cbor_bytestring_length(pairs[i].value) == 16) { str = fwupd_guid_to_string( (const fwupd_guid_t *)cbor_bytestring_handle(pairs[i].value), FWUPD_GUID_FLAG_NONE); } if (str != NULL) fu_firmware_set_id(firmware, str); } else if (tag_id == FU_COSWID_TAG_SOFTWARE_NAME) { priv->product = fu_coswid_firmware_strndup(pairs[i].value); } else if (tag_id == FU_COSWID_TAG_SOFTWARE_VERSION) { g_autofree gchar *str = fu_coswid_firmware_strndup(pairs[i].value); fu_firmware_set_version(firmware, str); } else if (tag_id == FU_COSWID_TAG_VERSION_SCHEME) { priv->version_scheme = cbor_get_uint16(pairs[i].value); } else if (tag_id == FU_COSWID_TAG_SOFTWARE_META) { if (!fu_coswid_firmware_parse_one_or_many(self, pairs[i].value, fu_coswid_firmware_parse_meta, NULL, /* user_data */ error)) return FALSE; } else if (tag_id == FU_COSWID_TAG_LINK) { if (!fu_coswid_firmware_parse_one_or_many(self, pairs[i].value, fu_coswid_firmware_parse_link, NULL, /* user_data */ error)) return FALSE; } else if (tag_id == FU_COSWID_TAG_PAYLOAD) { if (!fu_coswid_firmware_parse_one_or_many(self, pairs[i].value, fu_coswid_firmware_parse_payload, NULL, /* user_data */ error)) return FALSE; } else if (tag_id == FU_COSWID_TAG_ENTITY) { if (!fu_coswid_firmware_parse_one_or_many(self, pairs[i].value, fu_coswid_firmware_parse_entity, NULL, /* user_data */ error)) return FALSE; } else { g_debug("unhandled tag %s from root", fu_coswid_tag_to_string(tag_id)); } } /* device not supported */ if (fu_firmware_get_id(firmware) == NULL && fu_firmware_get_version(firmware) == NULL && priv->product == NULL && priv->entities->len == 0 && priv->links->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "not enough SBoM data"); return FALSE; } /* success */ return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "not compiled with CBOR support"); return FALSE; #endif } static gchar * fu_coswid_firmware_get_checksum(FuFirmware *firmware, GChecksumType csum_kind, GError **error) { FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware); FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); FuCoswidHashAlg alg_id = FU_COSWID_HASH_ALG_UNKNOWN; struct { GChecksumType kind; FuCoswidHashAlg alg_id; } csum_kinds[] = {{G_CHECKSUM_SHA256, FU_COSWID_HASH_ALG_SHA256}, {G_CHECKSUM_SHA384, FU_COSWID_HASH_ALG_SHA384}, {G_CHECKSUM_SHA512, FU_COSWID_HASH_ALG_SHA512}, {0, FU_COSWID_HASH_ALG_UNKNOWN}}; /* convert to FuCoswidHashAlg */ for (guint i = 0; csum_kinds[i].kind != 0; i++) { if (csum_kinds[i].kind == csum_kind) { alg_id = csum_kinds[i].alg_id; break; } } if (alg_id == FU_COSWID_HASH_ALG_UNKNOWN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot convert %s", fwupd_checksum_type_to_string_display(csum_kind)); return NULL; } /* find the correct hash kind */ for (guint i = 0; i < priv->payloads->len; i++) { FuCoswidFirmwarePayload *payload = g_ptr_array_index(priv->payloads, i); for (guint j = 0; j < payload->hashes->len; j++) { FuCoswidFirmwareHash *hash = g_ptr_array_index(payload->hashes, j); if (hash->alg_id == alg_id) return fu_byte_array_to_string(hash->value); } } g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no hash kind %s", fwupd_checksum_type_to_string_display(csum_kind)); return NULL; } #ifdef HAVE_CBOR static void fu_coswid_firmware_write_tag_string(cbor_item_t *root, FuCoswidTag tag, const gchar *item) { g_autoptr(cbor_item_t) key = cbor_build_uint8(tag); g_autoptr(cbor_item_t) val = cbor_build_string(item); if (!cbor_map_add(root, (struct cbor_pair){.key = key, .value = val})) g_critical("failed to push to indefinite map"); } static void fu_coswid_firmware_write_tag_bytestring(cbor_item_t *root, FuCoswidTag tag, const guint8 *buf, gsize bufsz) { g_autoptr(cbor_item_t) key = cbor_build_uint8(tag); g_autoptr(cbor_item_t) val = cbor_build_bytestring((cbor_data)buf, bufsz); if (!cbor_map_add(root, (struct cbor_pair){.key = key, .value = val})) g_critical("failed to push to indefinite map"); } static void fu_coswid_firmware_write_tag_bool(cbor_item_t *root, FuCoswidTag tag, gboolean item) { g_autoptr(cbor_item_t) key = cbor_build_uint8(tag); g_autoptr(cbor_item_t) val = cbor_build_bool(item); if (!cbor_map_add(root, (struct cbor_pair){.key = key, .value = val})) g_critical("failed to push to indefinite map"); } static void fu_coswid_firmware_write_tag_uint16(cbor_item_t *root, FuCoswidTag tag, guint16 item) { g_autoptr(cbor_item_t) key = cbor_build_uint8(tag); g_autoptr(cbor_item_t) val = cbor_build_uint16(item); if (!cbor_map_add(root, (struct cbor_pair){.key = key, .value = val})) g_critical("failed to push to indefinite map"); } static void fu_coswid_firmware_write_tag_uint64(cbor_item_t *root, FuCoswidTag tag, guint64 item) { g_autoptr(cbor_item_t) key = cbor_build_uint8(tag); g_autoptr(cbor_item_t) val = cbor_build_uint64(item); if (!cbor_map_add(root, (struct cbor_pair){.key = key, .value = val})) g_critical("failed to push to indefinite map"); } static void fu_coswid_firmware_write_tag_int8(cbor_item_t *root, FuCoswidTag tag, gint8 item) { g_autoptr(cbor_item_t) key = cbor_build_uint8(tag); g_autoptr(cbor_item_t) val = cbor_new_int8(); if (item >= 0) { cbor_set_uint8(val, item); } else { cbor_set_uint8(val, 0xFF - item); cbor_mark_negint(val); } if (!cbor_map_add(root, (struct cbor_pair){.key = key, .value = val})) g_critical("failed to push to indefinite map"); } static void fu_coswid_firmware_write_tag_item(cbor_item_t *root, FuCoswidTag tag, cbor_item_t *item) { g_autoptr(cbor_item_t) key = cbor_build_uint8(tag); if (!cbor_map_add(root, (struct cbor_pair){.key = key, .value = item})) g_critical("failed to push to indefinite map"); } static void fu_coswid_firmware_write_hash(cbor_item_t *root, FuCoswidFirmwareHash *hash) { g_autoptr(cbor_item_t) item_hash = cbor_new_definite_array(2); g_autoptr(cbor_item_t) item_hash_alg_id = cbor_build_uint8(hash->alg_id); g_autoptr(cbor_item_t) item_hash_value = cbor_build_bytestring(hash->value->data, hash->value->len); if (!cbor_array_push(item_hash, item_hash_alg_id)) g_critical("failed to push to definite array"); if (!cbor_array_push(item_hash, item_hash_value)) g_critical("failed to push to definite array"); if (!cbor_array_push(root, item_hash)) g_critical("failed to push to indefinite array"); } static void fu_coswid_firmware_write_payload(cbor_item_t *root, FuCoswidFirmwarePayload *payload) { g_autoptr(cbor_item_t) item_payload = cbor_new_indefinite_map(); g_autoptr(cbor_item_t) item_file = cbor_new_indefinite_map(); if (payload->name != NULL) { fu_coswid_firmware_write_tag_string(item_file, FU_COSWID_TAG_FS_NAME, payload->name); } if (payload->size != 0) { fu_coswid_firmware_write_tag_uint64(item_file, FU_COSWID_TAG_SIZE, payload->size); } if (payload->hashes->len > 0) { g_autoptr(cbor_item_t) item_hashes = cbor_new_indefinite_array(); for (guint j = 0; j < payload->hashes->len; j++) { FuCoswidFirmwareHash *hash = g_ptr_array_index(payload->hashes, j); fu_coswid_firmware_write_hash(item_hashes, hash); } fu_coswid_firmware_write_tag_item(item_file, FU_COSWID_TAG_HASH, item_hashes); } fu_coswid_firmware_write_tag_item(item_payload, FU_COSWID_TAG_FILE, item_file); if (!cbor_array_push(root, item_payload)) g_critical("failed to push to indefinite array"); } #endif static GByteArray * fu_coswid_firmware_write(FuFirmware *firmware, GError **error) { #ifdef HAVE_CBOR FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware); FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); gsize buflen; gsize bufsz = 0; g_autofree guchar *buf = NULL; g_autoptr(cbor_item_t) root = cbor_new_indefinite_map(); g_autoptr(cbor_item_t) item_meta = cbor_new_indefinite_map(); /* preallocate the map structure */ fu_coswid_firmware_write_tag_string(root, FU_COSWID_TAG_LANG, "en-US"); if (fu_firmware_get_id(firmware) != NULL) { fwupd_guid_t uuid = {0}; if (fwupd_guid_from_string(fu_firmware_get_id(firmware), &uuid, FWUPD_GUID_FLAG_NONE, NULL)) { fu_coswid_firmware_write_tag_bytestring(root, FU_COSWID_TAG_TAG_ID, (const guint8 *)&uuid, sizeof(uuid)); } else { fu_coswid_firmware_write_tag_string(root, FU_COSWID_TAG_TAG_ID, fu_firmware_get_id(firmware)); } } fu_coswid_firmware_write_tag_bool(root, FU_COSWID_TAG_CORPUS, TRUE); if (priv->product != NULL) { fu_coswid_firmware_write_tag_string(root, FU_COSWID_TAG_SOFTWARE_NAME, priv->product); } if (fu_firmware_get_version(firmware) != NULL) { fu_coswid_firmware_write_tag_string(root, FU_COSWID_TAG_SOFTWARE_VERSION, fu_firmware_get_version(firmware)); } if (priv->version_scheme != FU_COSWID_VERSION_SCHEME_UNKNOWN) { fu_coswid_firmware_write_tag_uint16(root, FU_COSWID_TAG_VERSION_SCHEME, priv->version_scheme); } fu_coswid_firmware_write_tag_item(root, FU_COSWID_TAG_SOFTWARE_META, item_meta); fu_coswid_firmware_write_tag_string(item_meta, FU_COSWID_TAG_GENERATOR, PACKAGE_NAME); if (priv->summary != NULL) { fu_coswid_firmware_write_tag_string(item_meta, FU_COSWID_TAG_SUMMARY, priv->summary); } if (priv->colloquial_version != NULL) { fu_coswid_firmware_write_tag_string(item_meta, FU_COSWID_TAG_COLLOQUIAL_VERSION, priv->colloquial_version); } /* add entities */ if (priv->entities->len > 0) { g_autoptr(cbor_item_t) item_entities = cbor_new_indefinite_array(); for (guint i = 0; i < priv->entities->len; i++) { FuCoswidFirmwareEntity *entity = g_ptr_array_index(priv->entities, i); g_autoptr(cbor_item_t) item_entity = cbor_new_indefinite_map(); g_autoptr(cbor_item_t) item_roles = cbor_new_indefinite_array(); if (entity->name != NULL) { fu_coswid_firmware_write_tag_string(item_entity, FU_COSWID_TAG_ENTITY_NAME, entity->name); } if (entity->regid != NULL) { fu_coswid_firmware_write_tag_string(item_entity, FU_COSWID_TAG_REG_ID, entity->regid); } for (guint j = 0; entity->roles[j] != FU_COSWID_ENTITY_ROLE_UNKNOWN; j++) { g_autoptr(cbor_item_t) item_role = cbor_build_uint8(entity->roles[j]); if (!cbor_array_push(item_roles, item_role)) g_critical("failed to push to indefinite array"); } fu_coswid_firmware_write_tag_item(item_entity, FU_COSWID_TAG_ROLE, item_roles); if (!cbor_array_push(item_entities, item_entity)) g_critical("failed to push to indefinite array"); } fu_coswid_firmware_write_tag_item(root, FU_COSWID_TAG_ENTITY, item_entities); } /* add links */ if (priv->links->len > 0) { g_autoptr(cbor_item_t) item_links = cbor_new_indefinite_array(); for (guint i = 0; i < priv->links->len; i++) { FuCoswidFirmwareLink *link = g_ptr_array_index(priv->links, i); g_autoptr(cbor_item_t) item_link = cbor_new_indefinite_map(); if (link->href != NULL) { fu_coswid_firmware_write_tag_string(item_link, FU_COSWID_TAG_HREF, link->href); } fu_coswid_firmware_write_tag_int8(item_link, FU_COSWID_TAG_REL, link->rel); if (!cbor_array_push(item_links, item_link)) g_critical("failed to push to indefinite array"); } fu_coswid_firmware_write_tag_item(root, FU_COSWID_TAG_LINK, item_links); } /* add payloads */ if (priv->payloads->len > 0) { g_autoptr(cbor_item_t) item_payloads = cbor_new_indefinite_array(); for (guint i = 0; i < priv->payloads->len; i++) { FuCoswidFirmwarePayload *payload = g_ptr_array_index(priv->payloads, i); fu_coswid_firmware_write_payload(item_payloads, payload); } fu_coswid_firmware_write_tag_item(root, FU_COSWID_TAG_PAYLOAD, item_payloads); } /* serialize */ buflen = cbor_serialize_alloc(root, &buf, &bufsz); if (buflen > bufsz) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CBOR allocation failure"); return NULL; } return g_byte_array_new_take(g_steal_pointer(&buf), buflen); #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "not compiled with CBOR support"); return NULL; #endif } static gboolean fu_coswid_firmware_build_entity(FuCoswidFirmware *self, XbNode *n, GError **error) { FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); const gchar *tmp; guint entity_role_cnt = 0; FuCoswidEntityRole role; g_autoptr(GPtrArray) roles = NULL; g_autoptr(FuCoswidFirmwareEntity) entity = g_new0(FuCoswidFirmwareEntity, 1); /* these are required */ tmp = xb_node_query_text(n, "name", error); if (tmp == NULL) return FALSE; entity->name = g_strdup(tmp); tmp = xb_node_query_text(n, "regid", error); if (tmp == NULL) return FALSE; entity->regid = g_strdup(tmp); /* optional */ roles = xb_node_query(n, "role", 0, NULL); if (roles != NULL) { for (guint i = 0; i < roles->len; i++) { XbNode *c = g_ptr_array_index(roles, i); tmp = xb_node_get_text(c); role = fu_coswid_entity_role_from_string(tmp); if (role == FU_COSWID_ENTITY_ROLE_UNKNOWN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to parse entity role %s", tmp); return FALSE; } if (entity_role_cnt >= G_N_ELEMENTS(entity->roles)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "too many roles"); return FALSE; } entity->roles[entity_role_cnt++] = role; } } /* success */ g_ptr_array_add(priv->entities, g_steal_pointer(&entity)); return TRUE; } static gboolean fu_coswid_firmware_build_link(FuCoswidFirmware *self, XbNode *n, GError **error) { FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); const gchar *tmp; g_autoptr(FuCoswidFirmwareLink) link = g_new0(FuCoswidFirmwareLink, 1); /* required */ tmp = xb_node_query_text(n, "href", error); if (tmp == NULL) return FALSE; link->href = g_strdup(tmp); /* optional */ tmp = xb_node_query_text(n, "rel", NULL); if (tmp != NULL) { link->rel = fu_coswid_link_rel_from_string(tmp); if (link->rel == FU_COSWID_LINK_REL_UNKNOWN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to parse link rel %s", tmp); return FALSE; } } /* success */ g_ptr_array_add(priv->links, g_steal_pointer(&link)); return TRUE; } static gboolean fu_coswid_firmware_build_hash(FuCoswidFirmware *self, XbNode *n, FuCoswidFirmwarePayload *payload, GError **error) { const gchar *tmp; g_autoptr(FuCoswidFirmwareHash) hash = g_new0(FuCoswidFirmwareHash, 1); /* required */ tmp = xb_node_query_text(n, "value", error); if (tmp == NULL) return FALSE; hash->value = fu_byte_array_from_string(tmp, error); if (hash->value == NULL) return FALSE; /* optional */ tmp = xb_node_query_text(n, "alg_id", NULL); if (tmp != NULL) { hash->alg_id = fu_coswid_hash_alg_from_string(tmp); if (hash->alg_id == FU_COSWID_HASH_ALG_UNKNOWN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to parse alg_id %s", tmp); return FALSE; } } /* success */ g_ptr_array_add(payload->hashes, g_steal_pointer(&hash)); return TRUE; } static gboolean fu_coswid_firmware_build_payload(FuCoswidFirmware *self, XbNode *n, GError **error) { FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); const gchar *tmp; guint64 tmp64; g_autoptr(FuCoswidFirmwarePayload) payload = fu_coswid_firmware_payload_new(); g_autoptr(GPtrArray) hashes = NULL; /* required */ tmp = xb_node_query_text(n, "name", NULL); if (tmp != NULL) payload->name = g_strdup(tmp); tmp64 = xb_node_query_text_as_uint(n, "size", NULL); if (tmp64 != G_MAXUINT64) payload->size = tmp64; /* multiple hashes allowed */ hashes = xb_node_query(n, "hash", 0, NULL); if (hashes != NULL) { for (guint i = 0; i < hashes->len; i++) { XbNode *c = g_ptr_array_index(hashes, i); if (!fu_coswid_firmware_build_hash(self, c, payload, error)) return FALSE; } } /* success */ g_ptr_array_add(priv->payloads, g_steal_pointer(&payload)); return TRUE; } static gboolean fu_coswid_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware); FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); const gchar *tmp; g_autoptr(GPtrArray) links = NULL; g_autoptr(GPtrArray) payloads = NULL; g_autoptr(GPtrArray) entities = NULL; /* simple properties */ tmp = xb_node_query_text(n, "product", NULL); if (tmp != NULL) priv->product = g_strdup(tmp); tmp = xb_node_query_text(n, "summary", NULL); if (tmp != NULL) priv->summary = g_strdup(tmp); tmp = xb_node_query_text(n, "colloquial_version", NULL); if (tmp != NULL) priv->colloquial_version = g_strdup(tmp); tmp = xb_node_query_text(n, "version_scheme", NULL); if (tmp != NULL) { priv->version_scheme = fu_coswid_version_scheme_from_string(tmp); if (priv->version_scheme == FU_COSWID_VERSION_SCHEME_UNKNOWN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to parse version_scheme %s", tmp); return FALSE; } } /* multiple links allowed */ links = xb_node_query(n, "link", 0, NULL); if (links != NULL) { for (guint i = 0; i < links->len; i++) { XbNode *c = g_ptr_array_index(links, i); if (!fu_coswid_firmware_build_link(self, c, error)) return FALSE; } } /* multiple payloads allowed */ payloads = xb_node_query(n, "payload", 0, NULL); if (payloads != NULL) { for (guint i = 0; i < payloads->len; i++) { XbNode *c = g_ptr_array_index(payloads, i); if (!fu_coswid_firmware_build_payload(self, c, error)) return FALSE; } } /* multiple entities allowed */ entities = xb_node_query(n, "entity", 0, NULL); if (entities != NULL) { for (guint i = 0; i < entities->len; i++) { XbNode *c = g_ptr_array_index(entities, i); if (!fu_coswid_firmware_build_entity(self, c, error)) return FALSE; } } /* success */ return TRUE; } static void fu_coswid_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuCoswidFirmware *self = FU_COSWID_FIRMWARE(firmware); FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); if (priv->version_scheme != FU_COSWID_VERSION_SCHEME_UNKNOWN) { fu_xmlb_builder_insert_kv(bn, "version_scheme", fu_coswid_version_scheme_to_string(priv->version_scheme)); } fu_xmlb_builder_insert_kv(bn, "product", priv->product); fu_xmlb_builder_insert_kv(bn, "summary", priv->summary); fu_xmlb_builder_insert_kv(bn, "colloquial_version", priv->colloquial_version); for (guint i = 0; i < priv->links->len; i++) { FuCoswidFirmwareLink *link = g_ptr_array_index(priv->links, i); g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "link", NULL); fu_xmlb_builder_insert_kv(bc, "href", link->href); if (link->rel != FU_COSWID_LINK_REL_UNKNOWN) { fu_xmlb_builder_insert_kv(bc, "rel", fu_coswid_link_rel_to_string(link->rel)); } } for (guint i = 0; i < priv->payloads->len; i++) { FuCoswidFirmwarePayload *payload = g_ptr_array_index(priv->payloads, i); g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "payload", NULL); fu_xmlb_builder_insert_kv(bc, "name", payload->name); fu_xmlb_builder_insert_kx(bc, "size", payload->size); for (guint j = 0; j < payload->hashes->len; j++) { FuCoswidFirmwareHash *hash = g_ptr_array_index(payload->hashes, j); g_autoptr(XbBuilderNode) bh = xb_builder_node_insert(bc, "hash", NULL); g_autofree gchar *value = fu_byte_array_to_string(hash->value); fu_xmlb_builder_insert_kv(bh, "alg_id", fu_coswid_hash_alg_to_string(hash->alg_id)); fu_xmlb_builder_insert_kv(bh, "value", value); } } for (guint i = 0; i < priv->entities->len; i++) { FuCoswidFirmwareEntity *entity = g_ptr_array_index(priv->entities, i); g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "entity", NULL); fu_xmlb_builder_insert_kv(bc, "name", entity->name); fu_xmlb_builder_insert_kv(bc, "regid", entity->regid); for (guint j = 0; entity->roles[j] != FU_COSWID_ENTITY_ROLE_UNKNOWN; j++) { fu_xmlb_builder_insert_kv( bc, "role", fu_coswid_entity_role_to_string(entity->roles[j])); } } } static void fu_coswid_firmware_init(FuCoswidFirmware *self) { FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); priv->version_scheme = FU_COSWID_VERSION_SCHEME_SEMVER; priv->links = g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_link_free); priv->payloads = g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_payload_free); priv->entities = g_ptr_array_new_with_free_func((GDestroyNotify)fu_coswid_firmware_entity_free); } static void fu_coswid_firmware_finalize(GObject *object) { FuCoswidFirmware *self = FU_COSWID_FIRMWARE(object); FuCoswidFirmwarePrivate *priv = GET_PRIVATE(self); g_free(priv->product); g_free(priv->summary); g_free(priv->colloquial_version); g_ptr_array_unref(priv->links); g_ptr_array_unref(priv->payloads); g_ptr_array_unref(priv->entities); G_OBJECT_CLASS(fu_coswid_firmware_parent_class)->finalize(object); } static void fu_coswid_firmware_class_init(FuCoswidFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_coswid_firmware_finalize; klass_firmware->parse = fu_coswid_firmware_parse; klass_firmware->write = fu_coswid_firmware_write; klass_firmware->build = fu_coswid_firmware_build; klass_firmware->export = fu_coswid_firmware_export; klass_firmware->get_checksum = fu_coswid_firmware_get_checksum; } /** * fu_coswid_firmware_new: * * Creates a new #FuFirmware of sub type coSWID * * Since: 1.8.0 **/ FuFirmware * fu_coswid_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_COSWID_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-coswid-firmware.h000066400000000000000000000006431460375044200216630ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_COSWID_FIRMWARE (fu_coswid_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuCoswidFirmware, fu_coswid_firmware, FU, COSWID_FIRMWARE, FuFirmware) struct _FuCoswidFirmwareClass { FuFirmwareClass parent_class; }; FuFirmware * fu_coswid_firmware_new(void); fwupd-1.9.16/libfwupdplugin/fu-coswid.rs000066400000000000000000000034111460375044200202420ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString, FromString)] enum CoswidTag { TagId, SoftwareName, Entity, Evidence, Link, SoftwareMeta, Payload, Hash, Corpus, Patch, Media, Supplemental, TagVersion, SoftwareVersion, VersionScheme, Lang, Directory, File, Process, Resource, Size, FileVersion, Key, Location, FsName, Root, PathElements, ProcessName, Pid, Type, Missing30, // not in the spec! EntityName, RegId, Role, Thumbprint, Date, DeviceId, Artifact, Href, Ownership, Rel, MediaType, Use, ActivationStatus, ChannelType, ColloquialVersion, Description, Edition, EntitlementDataRequired, EntitlementKey, Generator, PersistentId, Product, ProductFamily, Revision, Summary, UnspscCode, UnspscVersion, } #[derive(ToString, FromString)] enum CoswidVersionScheme { Unknown, Multipartnumeric, MultipartnumericSuffix, Alphanumeric, Decimal, Semver = 16384, } #[derive(ToString, FromString)] enum CoswidLinkRel { License = -2, Compiler = -1, Unknown = 0, Ancestor = 1, Component = 2, Feature = 3, Installationmedia = 4, Packageinstaller = 5, Parent = 6, Patches = 7, Requires = 8, SeeAlso = 9, Supersedes = 10, Supplemental = 11, } #[derive(ToString, FromString)] enum CoswidEntityRole { Unknown, TagCreator, SoftwareCreator, Aggregator, Distributor, Licensor, Maintainer, } #[derive(ToString, FromString)] enum CoswidHashAlg { Unknown = 0, SHA256 = 1, SHA384 = 7, SHA512 = 8, } fwupd-1.9.16/libfwupdplugin/fu-crc.c000066400000000000000000000055701460375044200173270ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include "fu-crc.h" /** * fu_crc8_full: * @buf: memory buffer * @bufsz: size of @buf * @crc_init: initial CRC value, typically 0x00 * @polynomial: CRC polynomial, e.g. 0x07 for CCITT * * Returns the cyclic redundancy check value for the given memory buffer. * * Returns: CRC value * * Since: 1.8.2 **/ guint8 fu_crc8_full(const guint8 *buf, gsize bufsz, guint8 crc_init, guint8 polynomial) { guint32 crc = crc_init; for (gsize j = bufsz; j > 0; j--) { crc ^= (*(buf++) << 8); for (guint32 i = 8; i; i--) { if (crc & 0x8000) crc ^= ((polynomial | 0x100) << 7); crc <<= 1; } } return ~((guint8)(crc >> 8)); } /** * fu_crc8: * @buf: memory buffer * @bufsz: size of @buf * * Returns the cyclic redundancy check value for the given memory buffer. * * Returns: CRC value * * Since: 1.8.2 **/ guint8 fu_crc8(const guint8 *buf, gsize bufsz) { return fu_crc8_full(buf, bufsz, 0x00, 0x07); } /** * fu_crc16_full: * @buf: memory buffer * @bufsz: size of @buf * @crc: initial CRC value, typically 0xFFFF * @polynomial: CRC polynomial, typically 0xA001 for IBM or 0x1021 for CCITT * * Returns the cyclic redundancy check value for the given memory buffer. * * Returns: CRC value * * Since: 1.8.2 **/ guint16 fu_crc16_full(const guint8 *buf, gsize bufsz, guint16 crc, guint16 polynomial) { for (gsize len = bufsz; len > 0; len--) { crc = (guint16)(crc ^ (*buf++)); for (guint8 i = 0; i < 8; i++) { if (crc & 0x1) { crc = (crc >> 1) ^ polynomial; } else { crc >>= 1; } } } return ~crc; } /** * fu_crc16: * @buf: memory buffer * @bufsz: size of @buf * * Returns the CRC-16-IBM cyclic redundancy value for the given memory buffer. * * Returns: CRC value * * Since: 1.8.2 **/ guint16 fu_crc16(const guint8 *buf, gsize bufsz) { return fu_crc16_full(buf, bufsz, 0xFFFF, 0xA001); } /** * fu_crc32_full: * @buf: memory buffer * @bufsz: size of @buf * @crc: initial CRC value, typically 0xFFFFFFFF * @polynomial: CRC polynomial, typically 0xEDB88320 * * Returns the cyclic redundancy check value for the given memory buffer. * * Returns: CRC value * * Since: 1.8.2 **/ guint32 fu_crc32_full(const guint8 *buf, gsize bufsz, guint32 crc, guint32 polynomial) { for (guint32 idx = 0; idx < bufsz; idx++) { guint8 data = *buf++; crc = crc ^ data; for (guint32 bit = 0; bit < 8; bit++) { guint32 mask = -(crc & 1); crc = (crc >> 1) ^ (polynomial & mask); } } return ~crc; } /** * fu_crc32: * @buf: memory buffer * @bufsz: size of @buf * * Returns the cyclic redundancy check value for the given memory buffer. * * Returns: CRC value * * Since: 1.8.2 **/ guint32 fu_crc32(const guint8 *buf, gsize bufsz) { return fu_crc32_full(buf, bufsz, 0xFFFFFFFF, 0xEDB88320); } fwupd-1.9.16/libfwupdplugin/fu-crc.h000066400000000000000000000010571460375044200173300ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-common.h" guint8 fu_crc8(const guint8 *buf, gsize bufsz); guint8 fu_crc8_full(const guint8 *buf, gsize bufsz, guint8 crc_init, guint8 polynomial); guint16 fu_crc16(const guint8 *buf, gsize bufsz); guint16 fu_crc16_full(const guint8 *buf, gsize bufsz, guint16 crc, guint16 polynomial); guint32 fu_crc32(const guint8 *buf, gsize bufsz); guint32 fu_crc32_full(const guint8 *buf, gsize bufsz, guint32 crc, guint32 polynomial); fwupd-1.9.16/libfwupdplugin/fu-csv-entry.c000066400000000000000000000164471460375044200205170ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-common.h" #include "fu-csv-entry.h" #include "fu-csv-firmware-private.h" #include "fu-string.h" /** * FuCsvEntry: * * A comma seporated value entry. * * See also: [class@FuFirmware] */ typedef struct { GPtrArray *values; } FuCsvEntryPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuCsvEntry, fu_csv_entry, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_csv_entry_get_instance_private(o)) #define FU_CSV_ENTRY_COLUMNS_MAX 1000u /** * fu_csv_entry_add_value: * @self: a #FuFirmware * @value: (not nullable): string * * Adds a string value to the entry. * * Since: 1.9.3 **/ void fu_csv_entry_add_value(FuCsvEntry *self, const gchar *value) { FuCsvEntryPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CSV_ENTRY(self)); g_return_if_fail(value != NULL); g_ptr_array_add(priv->values, g_strdup(value)); } /** * fu_csv_entry_get_value_by_idx: * @self: a #FuFirmware * @idx: column ID idx * * Gets the entry value for a specific index. * * Returns: a string, or %NULL if unset * * Since: 1.9.3 **/ const gchar * fu_csv_entry_get_value_by_idx(FuCsvEntry *self, guint idx) { FuCsvEntryPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CSV_ENTRY(self), NULL); if (idx >= priv->values->len) return NULL; return g_ptr_array_index(priv->values, idx); } /** * fu_csv_entry_get_value_by_column_id: * @self: a #FuFirmware * @column_id: (not nullable): string, e.g. `component_generation` * * Gets the entry value for a specific column ID. * * Returns: a string, or %NULL if unset or the column ID cannot be found * * Since: 1.9.3 **/ const gchar * fu_csv_entry_get_value_by_column_id(FuCsvEntry *self, const gchar *column_id) { FuCsvEntryPrivate *priv = GET_PRIVATE(self); FuCsvFirmware *parent = FU_CSV_FIRMWARE(fu_firmware_get_parent(FU_FIRMWARE(self))); guint idx = fu_csv_firmware_get_idx_for_column_id(parent, column_id); g_return_val_if_fail(FU_IS_CSV_ENTRY(self), NULL); g_return_val_if_fail(FU_IS_CSV_FIRMWARE(parent), NULL); g_return_val_if_fail(idx != G_MAXUINT, NULL); g_return_val_if_fail(column_id != NULL, NULL); return g_ptr_array_index(priv->values, idx); } gboolean fu_csv_entry_get_value_by_column_id_uint64(FuCsvEntry *self, const gchar *column_id, guint64 *value, GError **error) { const gchar *str_value = fu_csv_entry_get_value_by_column_id(self, column_id); if (str_value == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "CSV value not found"); return FALSE; } return fu_strtoull(str_value, value, 0, G_MAXUINT64, error); } static void fu_ifd_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuCsvEntry *self = FU_CSV_ENTRY(firmware); FuCsvEntryPrivate *priv = GET_PRIVATE(self); FuCsvFirmware *parent = FU_CSV_FIRMWARE(fu_firmware_get_parent(firmware)); g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "values", NULL); for (guint i = 0; i < priv->values->len; i++) { const gchar *value = g_ptr_array_index(priv->values, i); const gchar *key = fu_csv_firmware_get_column_id(parent, i); if (key != NULL) fu_xmlb_builder_insert_kv(bc, key, value); } } static gboolean fu_archive_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuCsvEntry *self = FU_CSV_ENTRY(firmware); FuCsvFirmware *parent = FU_CSV_FIRMWARE(fu_firmware_get_parent(firmware)); gboolean add_columns = fu_csv_firmware_get_column_id(parent, 0) == NULL; g_autoptr(GPtrArray) values = NULL; values = xb_node_query(n, "values/*", 0, error); if (values == NULL) return FALSE; for (guint i = 0; i < values->len; i++) { XbNode *c = g_ptr_array_index(values, i); if (add_columns && xb_node_get_element(c) != NULL) fu_csv_firmware_add_column_id(parent, xb_node_get_element(c)); fu_csv_entry_add_value(self, xb_node_get_text(c)); } return TRUE; } static gboolean fu_csv_entry_parse_token_cb(GString *token, guint token_idx, gpointer user_data, GError **error) { FuCsvEntry *self = FU_CSV_ENTRY(user_data); FuCsvEntryPrivate *priv = GET_PRIVATE(self); FuCsvFirmware *parent = FU_CSV_FIRMWARE(fu_firmware_get_parent(FU_FIRMWARE(self))); const gchar *column_id = fu_csv_firmware_get_column_id(parent, token_idx); /* sanity check */ if (token_idx > FU_CSV_ENTRY_COLUMNS_MAX) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "too many columns, limit is %u", FU_CSV_ENTRY_COLUMNS_MAX); return FALSE; } if (g_strcmp0(column_id, "$id") == 0) { g_ptr_array_add(priv->values, NULL); fu_firmware_set_id(FU_FIRMWARE(self), token->str); return TRUE; } if (g_strcmp0(column_id, "$idx") == 0) { guint64 value = 0; if (!fu_strtoull(token->str, &value, 0, G_MAXUINT64, error)) return FALSE; g_ptr_array_add(priv->values, NULL); fu_firmware_set_idx(FU_FIRMWARE(self), value); return TRUE; } if (g_strcmp0(column_id, "$version") == 0) { g_ptr_array_add(priv->values, NULL); fu_firmware_set_version(FU_FIRMWARE(self), token->str); return TRUE; } if (g_strcmp0(column_id, "$version_raw") == 0) { guint64 value = 0; if (!fu_strtoull(token->str, &value, 0, G_MAXUINT64, error)) return FALSE; g_ptr_array_add(priv->values, NULL); fu_firmware_set_version_raw(FU_FIRMWARE(self), value); return TRUE; } g_ptr_array_add(priv->values, g_strdup(token->str)); return TRUE; } static gboolean fu_csv_entry_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuCsvEntry *self = FU_CSV_ENTRY(firmware); return fu_strsplit_full((const gchar *)g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), ",", fu_csv_entry_parse_token_cb, self, error); } static GByteArray * fu_csv_entry_write(FuFirmware *firmware, GError **error) { FuCsvEntry *self = FU_CSV_ENTRY(firmware); FuCsvEntryPrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GString) str = g_string_new(NULL); /* single line */ for (guint i = 0; i < priv->values->len; i++) { const gchar *value = g_ptr_array_index(priv->values, i); if (str->len > 0) g_string_append(str, ","); if (value != NULL) g_string_append(str, value); } g_string_append(str, "\n"); g_byte_array_append(buf, (const guint8 *)str->str, str->len); /* success */ return g_steal_pointer(&buf); } static void fu_csv_entry_init(FuCsvEntry *self) { FuCsvEntryPrivate *priv = GET_PRIVATE(self); priv->values = g_ptr_array_new_with_free_func(g_free); } static void fu_csv_entry_finalize(GObject *object) { FuCsvEntry *self = FU_CSV_ENTRY(object); FuCsvEntryPrivate *priv = GET_PRIVATE(self); g_ptr_array_unref(priv->values); G_OBJECT_CLASS(fu_csv_entry_parent_class)->finalize(object); } static void fu_csv_entry_class_init(FuCsvEntryClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_csv_entry_finalize; klass_firmware->parse = fu_csv_entry_parse; klass_firmware->write = fu_csv_entry_write; klass_firmware->build = fu_archive_firmware_build; klass_firmware->export = fu_ifd_firmware_export; } /** * fu_csv_entry_new: * * Creates a new #FuFirmware * * Since: 1.9.3 **/ FuFirmware * fu_csv_entry_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_CSV_ENTRY, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-csv-entry.h000066400000000000000000000015141460375044200205110ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_CSV_ENTRY (fu_csv_entry_get_type()) G_DECLARE_DERIVABLE_TYPE(FuCsvEntry, fu_csv_entry, FU, CSV_ENTRY, FuFirmware) struct _FuCsvEntryClass { FuFirmwareClass parent_class; }; FuFirmware * fu_csv_entry_new(void); void fu_csv_entry_add_value(FuCsvEntry *self, const gchar *value) G_GNUC_NON_NULL(1); const gchar * fu_csv_entry_get_value_by_idx(FuCsvEntry *self, guint idx) G_GNUC_NON_NULL(1); const gchar * fu_csv_entry_get_value_by_column_id(FuCsvEntry *self, const gchar *column_id) G_GNUC_NON_NULL(1, 2); gboolean fu_csv_entry_get_value_by_column_id_uint64(FuCsvEntry *self, const gchar *column_id, guint64 *value, GError **error) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-csv-firmware-private.h000066400000000000000000000004141460375044200226320ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-csv-firmware.h" guint fu_csv_firmware_get_idx_for_column_id(FuCsvFirmware *self, const gchar *column_id) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-csv-firmware.c000066400000000000000000000137521460375044200211660ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-byte-array.h" #include "fu-csv-entry.h" #include "fu-csv-firmware-private.h" #include "fu-string.h" /** * FuCsvFirmware: * * A comma seporated value file. * * See also: [class@FuFirmware] */ typedef struct { GPtrArray *column_ids; } FuCsvFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuCsvFirmware, fu_csv_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_csv_firmware_get_instance_private(o)) /** * fu_csv_firmware_add_column_id: * @self: a #FuFirmware * @column_id: (not nullable): string, e.g. `component_generation` * * Adds a column ID. * * There are several optional magic column IDs that map to #FuFirmware properties: * * * `$id` sets the firmware ID * * `$idx` sets the firmware index * * `$version` sets the firmware version * * `$version_raw` sets the raw firmware version * * Since: 1.9.3 **/ void fu_csv_firmware_add_column_id(FuCsvFirmware *self, const gchar *column_id) { FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_CSV_FIRMWARE(self)); g_return_if_fail(column_id != NULL); g_ptr_array_add(priv->column_ids, g_strdup(column_id)); } /** * fu_csv_firmware_get_column_id: * @self: a #FuFirmware * @idx: column ID idx * * Gets the column ID for a specific index position. * * Returns: a string, or %NULL if not found * * Since: 1.9.3 **/ const gchar * fu_csv_firmware_get_column_id(FuCsvFirmware *self, guint idx) { FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CSV_FIRMWARE(self), NULL); if (idx >= priv->column_ids->len) return NULL; return g_ptr_array_index(priv->column_ids, idx); } /** * fu_csv_firmware_get_idx_for_column_id: * @self: a #FuFirmware * @column_id: (not nullable): string, e.g. `component_generation` * * Gets the column index for a given column ID. * * Returns: position, or %G_MAXUINT if unset * * Since: 1.9.3 **/ guint fu_csv_firmware_get_idx_for_column_id(FuCsvFirmware *self, const gchar *column_id) { FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CSV_FIRMWARE(self), G_MAXUINT); g_return_val_if_fail(column_id != NULL, G_MAXUINT); for (guint i = 0; i < priv->column_ids->len; i++) { const gchar *column_id_tmp = g_ptr_array_index(priv->column_ids, i); if (g_strcmp0(column_id_tmp, column_id) == 0) return i; } return G_MAXUINT; } static gboolean fu_csv_firmware_parse_token_cb(GString *token, guint token_idx, gpointer user_data, GError **error) { FuCsvFirmware *self = FU_CSV_FIRMWARE(user_data); fu_csv_firmware_add_column_id(self, token->str); return TRUE; } static gboolean fu_csv_firmware_parse_line_cb(GString *token, guint token_idx, gpointer user_data, GError **error) { FuCsvFirmware *self = FU_CSV_FIRMWARE(user_data); g_autoptr(FuFirmware) entry = fu_csv_entry_new(); g_autoptr(GBytes) fw = NULL; /* title */ if (g_str_has_prefix(token->str, "#")) { return fu_strsplit_full(token->str + 1, token->len - 1, ",", fu_csv_firmware_parse_token_cb, self, error); return TRUE; } /* parse entry */ fw = g_bytes_new(token->str, token->len); fu_firmware_set_idx(entry, token_idx); if (!fu_firmware_add_image_full(FU_FIRMWARE(self), entry, error)) return FALSE; if (!fu_firmware_parse(entry, fw, FWUPD_INSTALL_FLAG_NONE, error)) return FALSE; return TRUE; } static gboolean fu_csv_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuCsvFirmware *self = FU_CSV_FIRMWARE(firmware); gsize bufsz = 0; const gchar *buf = (const gchar *)g_bytes_get_data(fw, &bufsz); /* sanity check */ if (!g_utf8_validate_len(buf, bufsz, NULL)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "text must be UTF-8"); return FALSE; } return fu_strsplit_full(buf, bufsz, "\n", fu_csv_firmware_parse_line_cb, self, error); } static GByteArray * fu_csv_firmware_write(FuFirmware *firmware, GError **error) { FuCsvFirmware *self = FU_CSV_FIRMWARE(firmware); FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); g_autoptr(GString) str = g_string_new("#"); /* title section */ for (guint i = 0; i < priv->column_ids->len; i++) { const gchar *column_id = g_ptr_array_index(priv->column_ids, i); if (str->len > 1) g_string_append(str, ","); g_string_append(str, column_id); } g_string_append(str, "\n"); g_byte_array_append(buf, (const guint8 *)str->str, str->len); /* each entry */ for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); g_autoptr(GBytes) img_blob = fu_firmware_write(img, error); if (img_blob == NULL) return NULL; fu_byte_array_append_bytes(buf, img_blob); } /* success */ return g_steal_pointer(&buf); } static void fu_csv_firmware_init(FuCsvFirmware *self) { FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); priv->column_ids = g_ptr_array_new_with_free_func(g_free); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); fu_firmware_set_images_max(FU_FIRMWARE(self), 10000); g_type_ensure(FU_TYPE_CSV_ENTRY); } static void fu_csv_firmware_finalize(GObject *object) { FuCsvFirmware *self = FU_CSV_FIRMWARE(object); FuCsvFirmwarePrivate *priv = GET_PRIVATE(self); g_ptr_array_unref(priv->column_ids); G_OBJECT_CLASS(fu_csv_firmware_parent_class)->finalize(object); } static void fu_csv_firmware_class_init(FuCsvFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_csv_firmware_finalize; klass_firmware->parse = fu_csv_firmware_parse; klass_firmware->write = fu_csv_firmware_write; } /** * fu_csv_firmware_new: * * Creates a new #FuFirmware * * Since: 1.9.3 **/ FuFirmware * fu_csv_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_CSV_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-csv-firmware.h000066400000000000000000000011251460375044200211620ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_CSV_FIRMWARE (fu_csv_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuCsvFirmware, fu_csv_firmware, FU, CSV_FIRMWARE, FuFirmware) struct _FuCsvFirmwareClass { FuFirmwareClass parent_class; }; FuFirmware * fu_csv_firmware_new(void); void fu_csv_firmware_add_column_id(FuCsvFirmware *self, const gchar *column_id) G_GNUC_NON_NULL(1, 2); const gchar * fu_csv_firmware_get_column_id(FuCsvFirmware *self, guint idx) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-device-locker.c000066400000000000000000000130471460375044200212720ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDeviceLocker" #include "config.h" #include #ifdef HAVE_GUSB #include #endif #include "fu-device-locker.h" #include "fu-usb-device.h" /** * FuDeviceLocker: * * Easily close a shared resource (such as a device) when an object goes out of * scope. * * See also: [class@FuDevice] */ struct _FuDeviceLocker { GObject parent_instance; GObject *device; gboolean device_open; FuDeviceLockerFunc open_func; FuDeviceLockerFunc close_func; }; G_DEFINE_TYPE(FuDeviceLocker, fu_device_locker, G_TYPE_OBJECT) static void fu_device_locker_finalize(GObject *obj) { FuDeviceLocker *self = FU_DEVICE_LOCKER(obj); /* close device */ if (self->device_open) { g_autoptr(GError) error = NULL; if (!self->close_func(self->device, &error)) g_warning("failed to close device: %s", error->message); } if (self->device != NULL) g_object_unref(self->device); G_OBJECT_CLASS(fu_device_locker_parent_class)->finalize(obj); } static void fu_device_locker_class_init(FuDeviceLockerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_device_locker_finalize; } static void fu_device_locker_init(FuDeviceLocker *self) { } /** * fu_device_locker_close: * @self: a #FuDeviceLocker * @error: (nullable): optional return location for an error * * Closes the locker before it gets cleaned up. * * This function can be used to manually close a device managed by a locker, * and allows the caller to properly handle the error. * * Returns: %TRUE for success * * Since: 1.4.0 **/ gboolean fu_device_locker_close(FuDeviceLocker *self, GError **error) { g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_DEVICE_LOCKER(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!self->device_open) return TRUE; if (!self->close_func(self->device, &error_local)) { #ifdef HAVE_GUSB if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE)) { g_debug("ignoring: %s", error_local->message); return TRUE; } #endif g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } self->device_open = FALSE; return TRUE; } /** * fu_device_locker_new: * @device: a #GObject * @error: (nullable): optional return location for an error * * Opens the device for use. When the #FuDeviceLocker is deallocated the device * will be closed and any error will just be directed to the console. * This object is typically called using g_autoptr() but the device can also be * manually closed using g_clear_object(). * * The functions used for opening and closing the device are set automatically. * If the @device is not a type or supertype of #GUsbDevice or #FuDevice then * this function will not work. * * For custom objects please use fu_device_locker_new_full(). * * NOTE: If the @open_func failed then the @close_func will not be called. * * Think of this object as the device ownership. * * Returns: a device locker, or %NULL if the @open_func failed. * * Since: 1.0.0 **/ FuDeviceLocker * fu_device_locker_new(gpointer device, GError **error) { g_return_val_if_fail(device != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); #ifdef HAVE_GUSB /* GUsbDevice */ if (G_USB_IS_DEVICE(device)) { return fu_device_locker_new_full(device, (FuDeviceLockerFunc)g_usb_device_open, (FuDeviceLockerFunc)g_usb_device_close, error); } #endif /* FuDevice */ if (FU_IS_DEVICE(device)) { return fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_device_open, (FuDeviceLockerFunc)fu_device_close, error); } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "device object type not supported"); return NULL; } /** * fu_device_locker_new_full: * @device: a #GObject * @open_func: (scope async): a function to open the device * @close_func: (scope async): a function to close the device * @error: (nullable): optional return location for an error * * Opens the device for use. When the #FuDeviceLocker is deallocated the device * will be closed and any error will just be directed to the console. * This object is typically called using g_autoptr() but the device can also be * manually closed using g_clear_object(). * * NOTE: If the @open_func failed then the @close_func will not be called. * * Think of this object as the device ownership. * * Returns: a device locker, or %NULL if the @open_func failed. * * Since: 1.0.0 **/ FuDeviceLocker * fu_device_locker_new_full(gpointer device, FuDeviceLockerFunc open_func, FuDeviceLockerFunc close_func, GError **error) { g_autoptr(FuDeviceLocker) self = NULL; g_return_val_if_fail(device != NULL, NULL); g_return_val_if_fail(open_func != NULL, NULL); g_return_val_if_fail(close_func != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* create object */ self = g_object_new(FU_TYPE_DEVICE_LOCKER, NULL); self->device = g_object_ref(device); self->open_func = open_func; self->close_func = close_func; /* open device */ if (!self->open_func(device, error)) { g_autoptr(GError) error_local = NULL; if (!self->close_func(device, &error_local)) { if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_debug("ignoring close error on aborted open: %s", error_local->message); } } return NULL; } /* success */ self->device_open = TRUE; return g_steal_pointer(&self); } fwupd-1.9.16/libfwupdplugin/fu-device-locker.h000066400000000000000000000016171460375044200212770ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_DEVICE_LOCKER (fu_device_locker_get_type()) G_DECLARE_FINAL_TYPE(FuDeviceLocker, fu_device_locker, FU, DEVICE_LOCKER, GObject) /** * FuDeviceLockerFunc: * * Callback to use when opening and closing using [ctor@DeviceLocker.new_full]. **/ typedef gboolean (*FuDeviceLockerFunc)(GObject *device, GError **error); FuDeviceLocker * fu_device_locker_new(gpointer device, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); FuDeviceLocker * fu_device_locker_new_full(gpointer device, FuDeviceLockerFunc open_func, FuDeviceLockerFunc close_func, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_device_locker_close(FuDeviceLocker *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-device-metadata.h000066400000000000000000000027621460375044200216020ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /** * FU_DEVICE_METADATA_TBT_IS_SAFE_MODE: * * If the Thunderbolt hardware is stuck in safe mode. * Consumed by the thunderbolt plugin. */ #define FU_DEVICE_METADATA_TBT_IS_SAFE_MODE "Thunderbolt::IsSafeMode" /** * FU_DEVICE_METADATA_UEFI_DEVICE_KIND: * * The type of UEFI device, e.g. "system-firmware" or "device-firmware" * Consumed by the uefi plugin when other devices register fake devices that * need to be handled as a capsule update. */ #define FU_DEVICE_METADATA_UEFI_DEVICE_KIND "UefiDeviceKind" /** * FU_DEVICE_METADATA_UEFI_FW_VERSION: * * The firmware version of the UEFI device specified as a 32 bit unsigned * integer. * Consumed by the uefi plugin when other devices register fake devices that * need to be handled as a capsule update. */ #define FU_DEVICE_METADATA_UEFI_FW_VERSION "UefiFwVersion" /** * FU_DEVICE_METADATA_UEFI_CAPSULE_FLAGS: * * The capsule flags for the UEFI device, e.g. %EFI_CAPSULE_HEADER_FLAGS_PERSIST_ACROSS_RESET * Consumed by the uefi plugin when other devices register fake devices that * need to be handled as a capsule update. */ #define FU_DEVICE_METADATA_UEFI_CAPSULE_FLAGS "UefiCapsuleFlags" /** * FU_DEVICE_METADATA_CPU_MITIGATIONS_REQUIRED: * * CPU mitigations required. See the CPU plugin for more details. */ #define FU_DEVICE_METADATA_CPU_MITIGATIONS_REQUIRED "CpuMitigationsRequired" fwupd-1.9.16/libfwupdplugin/fu-device-private.h000066400000000000000000000052301460375044200214650ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #define fu_device_set_plugin(d, v) fwupd_device_set_plugin(FWUPD_DEVICE(d), v) const gchar * fu_device_internal_flag_to_string(FuDeviceInternalFlags flag); FuDeviceInternalFlags fu_device_internal_flag_from_string(const gchar *flag); GPtrArray * fu_device_get_parent_guids(FuDevice *self) G_GNUC_NON_NULL(1); gboolean fu_device_has_parent_guid(FuDevice *self, const gchar *guid) G_GNUC_NON_NULL(1, 2); GPtrArray * fu_device_get_parent_physical_ids(FuDevice *self) G_GNUC_NON_NULL(1); gboolean fu_device_has_parent_physical_id(FuDevice *self, const gchar *physical_id) G_GNUC_NON_NULL(1, 2); GPtrArray * fu_device_get_parent_backend_ids(FuDevice *self) G_GNUC_NON_NULL(1); gboolean fu_device_has_parent_backend_id(FuDevice *self, const gchar *backend_id) G_GNUC_NON_NULL(1, 2); void fu_device_set_parent(FuDevice *self, FuDevice *parent) G_GNUC_NON_NULL(1); gint fu_device_get_order(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_order(FuDevice *self, gint order) G_GNUC_NON_NULL(1); const gchar * fu_device_get_update_request_id(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_update_request_id(FuDevice *self, const gchar *update_request_id) G_GNUC_NON_NULL(1); void fu_device_set_alternate(FuDevice *self, FuDevice *alternate) G_GNUC_NON_NULL(1); gboolean fu_device_ensure_id(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); void fu_device_incorporate_from_component(FuDevice *self, XbNode *component) G_GNUC_NON_NULL(1, 2); void fu_device_replace(FuDevice *self, FuDevice *donor) G_GNUC_NON_NULL(1); void fu_device_ensure_from_component(FuDevice *self, XbNode *component) G_GNUC_NON_NULL(1, 2); void fu_device_convert_instance_ids(FuDevice *self) G_GNUC_NON_NULL(1); GPtrArray * fu_device_get_possible_plugins(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_add_possible_plugin(FuDevice *self, const gchar *plugin) G_GNUC_NON_NULL(1, 2); guint fu_device_get_request_cnt(FuDevice *self, FwupdRequestKind request_kind) G_GNUC_NON_NULL(1); guint64 fu_device_get_private_flags(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_private_flags(FuDevice *self, guint64 flag) G_GNUC_NON_NULL(1); void fu_device_set_progress(FuDevice *self, FuProgress *progress) G_GNUC_NON_NULL(1); FuDeviceInternalFlags fu_device_get_internal_flags(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_internal_flags(FuDevice *self, FuDeviceInternalFlags flags) G_GNUC_NON_NULL(1); gboolean fu_device_set_quirk_kv(FuDevice *self, const gchar *key, const gchar *value, GError **error) G_GNUC_NON_NULL(1, 2, 3); fwupd-1.9.16/libfwupdplugin/fu-device-progress.c000066400000000000000000000054611460375044200216600ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDeviceProgress" #include "config.h" #include #ifdef HAVE_GUSB #include #endif #include "fu-device-progress.h" struct _FuDeviceProgress { GObject parent_instance; FuDevice *device; FuProgress *progress; guint percentage_changed_id; guint status_changed_id; }; G_DEFINE_TYPE(FuDeviceProgress, fu_device_progress, G_TYPE_OBJECT) static void fu_device_progress_percentage_changed_cb(FuProgress *progress, guint percentage, gpointer user_data) { FuDeviceProgress *self = FU_DEVICE_PROGRESS(user_data); fu_device_set_percentage(self->device, percentage); } static void fu_device_progress_status_changed_cb(FuProgress *progress, FwupdStatus status, gpointer user_data) { FuDeviceProgress *self = FU_DEVICE_PROGRESS(user_data); fu_device_set_status(self->device, status); } static void fu_device_progress_finalize(GObject *obj) { FuDeviceProgress *self = FU_DEVICE_PROGRESS(obj); g_signal_handler_disconnect(self->progress, self->percentage_changed_id); g_signal_handler_disconnect(self->progress, self->status_changed_id); fu_device_set_status(self->device, FWUPD_STATUS_IDLE); fu_device_set_percentage(self->device, 0); g_object_unref(self->device); g_object_unref(self->progress); G_OBJECT_CLASS(fu_device_progress_parent_class)->finalize(obj); } static void fu_device_progress_class_init(FuDeviceProgressClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_device_progress_finalize; } static void fu_device_progress_init(FuDeviceProgress *self) { } /** * fu_device_progress_new: * @device: a #FuDevice * @progress: a #FuProgress * * Binds the device to the progress object so that the status and percentage will be coped from * the progress all the time this object is alive. * * When this object is finalized the *device* status will be set to `idle` and the percentage reset * back to 0%. * * Returns: a #FuDeviceProgress * * Since: 1.8.11 **/ FuDeviceProgress * fu_device_progress_new(FuDevice *device, FuProgress *progress) { g_autoptr(FuDeviceProgress) self = g_object_new(FU_TYPE_DEVICE_PROGRESS, NULL); g_return_val_if_fail(FU_IS_DEVICE(device), NULL); g_return_val_if_fail(FU_IS_PROGRESS(progress), NULL); /* connect up to device */ self->percentage_changed_id = g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_device_progress_percentage_changed_cb), self); self->status_changed_id = g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(fu_device_progress_status_changed_cb), self); self->device = g_object_ref(device); self->progress = g_object_ref(progress); /* success */ return g_steal_pointer(&self); } fwupd-1.9.16/libfwupdplugin/fu-device-progress.h000066400000000000000000000007151460375044200216620ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-device.h" #include "fu-progress.h" #define FU_TYPE_DEVICE_PROGRESS (fu_device_progress_get_type()) G_DECLARE_FINAL_TYPE(FuDeviceProgress, fu_device_progress, FU, DEVICE_PROGRESS, GObject) FuDeviceProgress * fu_device_progress_new(FuDevice *device, FuProgress *progress) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-device.c000066400000000000000000005651111460375044200200210ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDevice" #include "config.h" #include #include #include "fwupd-common.h" #include "fwupd-device-private.h" #include "fu-common.h" #include "fu-device-private.h" #include "fu-quirks.h" #include "fu-security-attr.h" #include "fu-string.h" #include "fu-version-common.h" #define FU_DEVICE_RETRY_OPEN_COUNT 5 #define FU_DEVICE_RETRY_OPEN_DELAY 500 /* ms */ /** * FuDevice: * * A physical or logical device that is exported to the daemon. * * See also: [class@FuDeviceLocker], [class@Fwupd.Device] */ static void fu_device_finalize(GObject *object); static void fu_device_inhibit_full(FuDevice *self, FwupdDeviceProblem problem, const gchar *inhibit_id, const gchar *reason); typedef struct { gchar *alternate_id; gchar *equivalent_id; gchar *physical_id; gchar *logical_id; gchar *backend_id; gchar *update_request_id; gchar *proxy_guid; FuDevice *alternate; FuDevice *proxy; /* noref */ FuContext *ctx; GHashTable *inhibits; /* (nullable) */ GHashTable *metadata; /* (nullable) */ GPtrArray *parent_guids; GPtrArray *parent_physical_ids; /* (nullable) */ GPtrArray *parent_backend_ids; /* (nullable) */ guint remove_delay; /* ms */ guint acquiesce_delay; /* ms */ guint request_cnts[FWUPD_REQUEST_KIND_LAST]; gint order; guint priority; guint poll_id; gint poll_locker_cnt; gboolean done_probe; gboolean done_setup; gboolean device_id_valid; guint64 size_min; guint64 size_max; gint open_refcount; /* atomic */ GType specialized_gtype; GType proxy_gtype; GType firmware_gtype; GPtrArray *possible_plugins; GPtrArray *instance_id_quirks; /* of utf-8 */ GPtrArray *retry_recs; /* of FuDeviceRetryRecovery */ guint retry_delay; FuDeviceInternalFlags internal_flags; guint64 private_flags; GPtrArray *private_flag_items; /* (nullable) */ gchar *custom_flags; gulong notify_flags_handler_id; GHashTable *instance_hash; FuProgress *progress; /* provided for FuDevice notify callbacks */ } FuDevicePrivate; typedef struct { GQuark domain; gint code; FuDeviceRetryFunc recovery_func; } FuDeviceRetryRecovery; typedef struct { FwupdDeviceProblem problem; gchar *inhibit_id; gchar *reason; } FuDeviceInhibit; typedef struct { guint64 value; gchar *value_str; } FuDevicePrivateFlagItem; enum { PROP_0, PROP_PHYSICAL_ID, PROP_LOGICAL_ID, PROP_BACKEND_ID, PROP_CONTEXT, PROP_PROXY, PROP_PARENT, PROP_INTERNAL_FLAGS, PROP_PRIVATE_FLAGS, PROP_LAST }; enum { SIGNAL_CHILD_ADDED, SIGNAL_CHILD_REMOVED, SIGNAL_REQUEST, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE_WITH_PRIVATE(FuDevice, fu_device, FWUPD_TYPE_DEVICE) #define GET_PRIVATE(o) (fu_device_get_instance_private(o)) static FuDevicePrivateFlagItem * fu_device_private_flag_item_find_by_val(FuDevice *self, guint64 value); static void fu_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuDevice *self = FU_DEVICE(object); FuDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_PHYSICAL_ID: g_value_set_string(value, priv->physical_id); break; case PROP_LOGICAL_ID: g_value_set_string(value, priv->logical_id); break; case PROP_BACKEND_ID: g_value_set_string(value, priv->backend_id); break; case PROP_CONTEXT: g_value_set_object(value, priv->ctx); break; case PROP_PROXY: g_value_set_object(value, priv->proxy); break; case PROP_PARENT: g_value_set_object(value, fu_device_get_parent(self)); break; case PROP_INTERNAL_FLAGS: g_value_set_uint64(value, fu_device_get_internal_flags(self)); break; case PROP_PRIVATE_FLAGS: g_value_set_uint64(value, fu_device_get_private_flags(self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuDevice *self = FU_DEVICE(object); switch (prop_id) { case PROP_PHYSICAL_ID: fu_device_set_physical_id(self, g_value_get_string(value)); break; case PROP_LOGICAL_ID: fu_device_set_logical_id(self, g_value_get_string(value)); break; case PROP_BACKEND_ID: fu_device_set_backend_id(self, g_value_get_string(value)); break; case PROP_CONTEXT: fu_device_set_context(self, g_value_get_object(value)); break; case PROP_PROXY: fu_device_set_proxy(self, g_value_get_object(value)); break; case PROP_PARENT: fu_device_set_parent(self, g_value_get_object(value)); break; case PROP_INTERNAL_FLAGS: fu_device_set_internal_flags(self, g_value_get_uint64(value)); break; case PROP_PRIVATE_FLAGS: fu_device_set_private_flags(self, g_value_get_uint64(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * fu_device_internal_flag_to_string: * @flag: an internal device flag, e.g. %FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON * * Converts an internal device flag to a string. * * Returns: identifier string * * Since: 1.5.5 **/ const gchar * fu_device_internal_flag_to_string(FuDeviceInternalFlags flag) { if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON) return "md-set-icon"; if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME) return "md-set-name"; if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY) return "md-set-name-category"; if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT) return "md-set-verfmt"; if (flag == FU_DEVICE_INTERNAL_FLAG_ONLY_SUPPORTED) return "only-supported"; if (flag == FU_DEVICE_INTERNAL_FLAG_NO_AUTO_INSTANCE_IDS) return "no-auto-instance-ids"; if (flag == FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER) return "ensure-semver"; if (flag == FU_DEVICE_INTERNAL_FLAG_RETRY_OPEN) return "retry-open"; if (flag == FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID) return "replug-match-guid"; if (flag == FU_DEVICE_INTERNAL_FLAG_INHERIT_ACTIVATION) return "inherit-activation"; if (flag == FU_DEVICE_INTERNAL_FLAG_IS_OPEN) return "is-open"; if (flag == FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER) return "no-serial-number"; if (flag == FU_DEVICE_INTERNAL_FLAG_AUTO_PARENT_CHILDREN) return "auto-parent-children"; if (flag == FU_DEVICE_INTERNAL_FLAG_ATTACH_EXTRA_RESET) return "attach-extra-reset"; if (flag == FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN) return "inhibit-children"; if (flag == FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE_CHILDREN) return "no-auto-remove-children"; if (flag == FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN) return "use-parent-for-open"; if (flag == FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FOR_OPEN) return "use-proxy-for-open"; if (flag == FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_BATTERY) return "use-parent-for-battery"; if (flag == FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FALLBACK) return "use-proxy-fallback"; if (flag == FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE) return "no-auto-remove"; if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR) return "md-set-vendor"; if (flag == FU_DEVICE_INTERNAL_FLAG_NO_LID_CLOSED) return "no-lid-closed"; if (flag == FU_DEVICE_INTERNAL_FLAG_NO_PROBE) return "no-probe"; if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED) return "md-set-signed"; if (flag == FU_DEVICE_INTERNAL_AUTO_PAUSE_POLLING) return "auto-pause-polling"; if (flag == FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG) return "only-wait-for-replug"; if (flag == FU_DEVICE_INTERNAL_FLAG_IGNORE_SYSTEM_POWER) return "ignore-system-power"; if (flag == FU_DEVICE_INTERNAL_FLAG_NO_PROBE_COMPLETE) return "no-probe-complete"; if (flag == FU_DEVICE_INTERNAL_FLAG_SAVE_INTO_BACKUP_REMOTE) return "save-into-backup-remote"; if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS) return "md-set-flags"; if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_VERSION) return "md-set-version"; if (flag == FU_DEVICE_INTERNAL_FLAG_MD_ONLY_CHECKSUM) return "md-only-checksum"; if (flag == FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV) return "add-instance-id-rev"; if (flag == FU_DEVICE_INTERNAL_FLAG_UNCONNECTED) return "unconnected"; if (flag == FU_DEVICE_INTERNAL_FLAG_DISPLAY_REQUIRED) return "display-required"; if (flag == FU_DEVICE_INTERNAL_FLAG_UPDATE_PENDING) return "update-pending"; if (flag == FU_DEVICE_INTERNAL_FLAG_NO_GENERIC_GUIDS) return "no-generic-guids"; if (flag == FU_DEVICE_INTERNAL_FLAG_ENFORCE_REQUIRES) return "enforce-requires"; if (flag == FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE) return "host-firmware"; if (flag == FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE_CHILD) return "host-firmware-child"; if (flag == FU_DEVICE_INTERNAL_FLAG_HOST_CPU) return "host-cpu"; if (flag == FU_DEVICE_INTERNAL_FLAG_HOST_CPU_CHILD) return "host-cpu-child"; if (flag == FU_DEVICE_INTERNAL_FLAG_EXPLICIT_ORDER) return "explicit-order"; if (flag == FU_DEVICE_INTERNAL_FLAG_REFCOUNTED_PROXY) return "refcounted-proxy"; return NULL; } /** * fu_device_internal_flag_from_string: * @flag: a string, e.g. `md-set-icon` * * Converts a string to an internal device flag. * * Returns: enumerated value * * Since: 1.5.5 **/ FuDeviceInternalFlags fu_device_internal_flag_from_string(const gchar *flag) { if (g_strcmp0(flag, "md-set-icon") == 0) return FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON; if (g_strcmp0(flag, "md-set-name") == 0) return FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME; if (g_strcmp0(flag, "md-set-name-category") == 0) return FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY; if (g_strcmp0(flag, "md-set-verfmt") == 0) return FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT; if (g_strcmp0(flag, "only-supported") == 0) return FU_DEVICE_INTERNAL_FLAG_ONLY_SUPPORTED; if (g_strcmp0(flag, "no-auto-instance-ids") == 0) return FU_DEVICE_INTERNAL_FLAG_NO_AUTO_INSTANCE_IDS; if (g_strcmp0(flag, "ensure-semver") == 0) return FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER; if (g_strcmp0(flag, "retry-open") == 0) return FU_DEVICE_INTERNAL_FLAG_RETRY_OPEN; if (g_strcmp0(flag, "replug-match-guid") == 0) return FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID; if (g_strcmp0(flag, "inherit-activation") == 0) return FU_DEVICE_INTERNAL_FLAG_INHERIT_ACTIVATION; if (g_strcmp0(flag, "is-open") == 0) return FU_DEVICE_INTERNAL_FLAG_IS_OPEN; if (g_strcmp0(flag, "no-serial-number") == 0) return FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER; if (g_strcmp0(flag, "auto-parent-children") == 0) return FU_DEVICE_INTERNAL_FLAG_AUTO_PARENT_CHILDREN; if (g_strcmp0(flag, "attach-extra-reset") == 0) return FU_DEVICE_INTERNAL_FLAG_ATTACH_EXTRA_RESET; if (g_strcmp0(flag, "inhibit-children") == 0) return FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN; if (g_strcmp0(flag, "no-auto-remove-children") == 0) return FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE_CHILDREN; if (g_strcmp0(flag, "use-parent-for-open") == 0) return FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN; if (g_strcmp0(flag, "use-proxy-for-open") == 0) return FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FOR_OPEN; if (g_strcmp0(flag, "use-parent-for-battery") == 0) return FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_BATTERY; if (g_strcmp0(flag, "use-proxy-fallback") == 0) return FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FALLBACK; if (g_strcmp0(flag, "no-auto-remove") == 0) return FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE; if (g_strcmp0(flag, "md-set-vendor") == 0) return FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR; if (g_strcmp0(flag, "no-lid-closed") == 0) return FU_DEVICE_INTERNAL_FLAG_NO_LID_CLOSED; if (g_strcmp0(flag, "no-probe") == 0) return FU_DEVICE_INTERNAL_FLAG_NO_PROBE; if (g_strcmp0(flag, "md-set-signed") == 0) return FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED; if (g_strcmp0(flag, "auto-pause-polling") == 0) return FU_DEVICE_INTERNAL_AUTO_PAUSE_POLLING; if (g_strcmp0(flag, "only-wait-for-replug") == 0) return FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG; if (g_strcmp0(flag, "ignore-system-power") == 0) return FU_DEVICE_INTERNAL_FLAG_IGNORE_SYSTEM_POWER; if (g_strcmp0(flag, "no-probe-complete") == 0) return FU_DEVICE_INTERNAL_FLAG_NO_PROBE_COMPLETE; if (g_strcmp0(flag, "save-into-backup-remote") == 0) return FU_DEVICE_INTERNAL_FLAG_SAVE_INTO_BACKUP_REMOTE; if (g_strcmp0(flag, "md-set-flags") == 0) return FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS; if (g_strcmp0(flag, "md-set-version") == 0) return FU_DEVICE_INTERNAL_FLAG_MD_SET_VERSION; if (g_strcmp0(flag, "md-only-checksum") == 0) return FU_DEVICE_INTERNAL_FLAG_MD_ONLY_CHECKSUM; if (g_strcmp0(flag, "add-instance-id-rev") == 0) return FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV; if (g_strcmp0(flag, "unconnected") == 0) return FU_DEVICE_INTERNAL_FLAG_UNCONNECTED; if (g_strcmp0(flag, "display-required") == 0) return FU_DEVICE_INTERNAL_FLAG_DISPLAY_REQUIRED; if (g_strcmp0(flag, "update-pending") == 0) return FU_DEVICE_INTERNAL_FLAG_UPDATE_PENDING; if (g_strcmp0(flag, "no-generic-guids") == 0) return FU_DEVICE_INTERNAL_FLAG_NO_GENERIC_GUIDS; if (g_strcmp0(flag, "enforce-requires") == 0) return FU_DEVICE_INTERNAL_FLAG_ENFORCE_REQUIRES; if (g_strcmp0(flag, "host-firmware") == 0) return FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE; if (g_strcmp0(flag, "host-firmware-child") == 0) return FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE_CHILD; if (g_strcmp0(flag, "host-cpu") == 0) return FU_DEVICE_INTERNAL_FLAG_HOST_CPU; if (g_strcmp0(flag, "host-cpu-child") == 0) return FU_DEVICE_INTERNAL_FLAG_HOST_CPU_CHILD; if (g_strcmp0(flag, "explicit-order") == 0) return FU_DEVICE_INTERNAL_FLAG_EXPLICIT_ORDER; if (g_strcmp0(flag, "refcounted-proxy") == 0) return FU_DEVICE_INTERNAL_FLAG_REFCOUNTED_PROXY; return FU_DEVICE_INTERNAL_FLAG_UNKNOWN; } /** * fu_device_add_internal_flag: * @self: a #FuDevice * @flag: an internal device flag, e.g. %FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON * * Adds a private flag that stays internal to the engine and is not leaked to the client. * * Since: 1.5.5 **/ void fu_device_add_internal_flag(FuDevice *self, FuDeviceInternalFlags flag) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); /* do not let devices be updated until re-connected */ if (flag & FU_DEVICE_INTERNAL_FLAG_UNCONNECTED) fu_device_inhibit(self, "unconnected", "Device has been removed"); /* reset this back to the default */ if (flag & FU_DEVICE_INTERNAL_FLAG_EXPLICIT_ORDER) { GPtrArray *children = fu_device_get_children(self); for (guint i = 0; i < children->len; i++) { FuDevice *child_tmp = g_ptr_array_index(children, i); fu_device_set_order(child_tmp, G_MAXINT); } fu_device_set_order(self, G_MAXINT); } priv->internal_flags |= flag; g_object_notify(G_OBJECT(self), "internal-flags"); } /** * fu_device_remove_internal_flag: * @self: a #FuDevice * @flag: an internal device flag, e.g. %FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON * * Removes a private flag that stays internal to the engine and is not leaked to the client. * * Since: 1.5.5 **/ void fu_device_remove_internal_flag(FuDevice *self, FuDeviceInternalFlags flag) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); if (flag & FU_DEVICE_INTERNAL_FLAG_UNCONNECTED) fu_device_uninhibit(self, "unconnected"); priv->internal_flags &= ~flag; g_object_notify(G_OBJECT(self), "internal-flags"); } /** * fu_device_has_internal_flag: * @self: a #FuDevice * @flag: an internal device flag, e.g. %FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON * * Tests for a private flag that stays internal to the engine and is not leaked to the client. * * Since: 1.5.5 **/ gboolean fu_device_has_internal_flag(FuDevice *self, FuDeviceInternalFlags flag) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); return (priv->internal_flags & flag) > 0; } /** * fu_device_get_internal_flags: * @self: a #FuDevice * * Gets all the internal flags. * * Returns: flags, e.g. %FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON * * Since: 1.7.1 **/ FuDeviceInternalFlags fu_device_get_internal_flags(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_UNKNOWN); return priv->internal_flags; } /** * fu_device_set_internal_flags: * @self: a #FuDevice * @flags: internal device flags, e.g. %FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON * * Sets the internal flags. * * Since: 1.7.1 **/ void fu_device_set_internal_flags(FuDevice *self, FuDeviceInternalFlags flags) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); priv->internal_flags = flags; g_object_notify(G_OBJECT(self), "internal-flags"); } /** * fu_device_add_private_flag: * @self: a #FuDevice * @flag: a device flag * * Adds a private flag that can be used by the plugin for any purpose. * * Since: 1.6.2 **/ void fu_device_add_private_flag(FuDevice *self, guint64 flag) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); #ifndef SUPPORTED_BUILD if (fu_device_private_flag_item_find_by_val(self, flag) == NULL) { g_critical("%s flag 0x%x is unknown -- use fu_device_register_private_flag()", G_OBJECT_TYPE_NAME(self), (guint)flag); } #endif priv->private_flags |= flag; g_object_notify(G_OBJECT(self), "private-flags"); } /** * fu_device_remove_private_flag: * @self: a #FuDevice * @flag: a device flag * * Removes a private flag that can be used by the plugin for any purpose. * * Since: 1.6.2 **/ void fu_device_remove_private_flag(FuDevice *self, guint64 flag) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); #ifndef SUPPORTED_BUILD if (fu_device_private_flag_item_find_by_val(self, flag) == NULL) { g_critical("%s flag 0x%x is unknown -- use fu_device_register_private_flag()", G_OBJECT_TYPE_NAME(self), (guint)flag); } #endif priv->private_flags &= ~flag; g_object_notify(G_OBJECT(self), "private-flags"); } /** * fu_device_has_private_flag: * @self: a #FuDevice * @flag: a device flag * * Tests for a private flag that can be used by the plugin for any purpose. * * Since: 1.6.2 **/ gboolean fu_device_has_private_flag(FuDevice *self, guint64 flag) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); #ifndef SUPPORTED_BUILD if (fu_device_private_flag_item_find_by_val(self, flag) == NULL) { g_critical("%s flag 0x%x is unknown -- use fu_device_register_private_flag()", G_OBJECT_TYPE_NAME(self), (guint)flag); } #endif return (priv->private_flags & flag) > 0; } /** * fu_device_get_private_flags: * @self: a #FuDevice * * Returns all the private flags that can be used by the plugin for any purpose. * * Returns: flags * * Since: 1.6.2 **/ guint64 fu_device_get_private_flags(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), G_MAXUINT64); return priv->private_flags; } /** * fu_device_get_request_cnt: * @self: a #FuDevice * @request_kind: the type of request * * Returns the number of requests of a specific kind. This function is only * useful to the daemon, which uses it to synthesize artificial events for * plugins not yet ported to [class@Fwupd.Request]. * * Returns: integer, usually 0 * * Since: 1.6.2 **/ guint fu_device_get_request_cnt(FuDevice *self, FwupdRequestKind request_kind) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), G_MAXUINT); g_return_val_if_fail(request_kind < FWUPD_REQUEST_KIND_LAST, G_MAXUINT); return priv->request_cnts[request_kind]; } /** * fu_device_set_private_flags: * @self: a #FuDevice * @flag: flags * * Sets private flags that can be used by the plugin for any purpose. * * Since: 1.6.2 **/ void fu_device_set_private_flags(FuDevice *self, guint64 flag) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); priv->private_flags = flag; g_object_notify(G_OBJECT(self), "private-flags"); } /** * fu_device_get_possible_plugins: * @self: a #FuDevice * * Gets the list of possible plugin names, typically added from quirk files. * * Returns: (element-type utf8) (transfer container): plugin names * * Since: 1.3.3 **/ GPtrArray * fu_device_get_possible_plugins(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); return g_ptr_array_ref(priv->possible_plugins); } /** * fu_device_add_possible_plugin: * @self: a #FuDevice * @plugin: a plugin name, e.g. `dfu` * * Adds a plugin name to the list of plugins that *might* be able to handle this * device. This is typically called from a quirk handler. * * Duplicate plugin names are ignored. * * Since: 1.5.1 **/ void fu_device_add_possible_plugin(FuDevice *self, const gchar *plugin) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(plugin != NULL); /* add if it does not already exist */ if (g_ptr_array_find_with_equal_func(priv->possible_plugins, plugin, g_str_equal, NULL)) return; g_ptr_array_add(priv->possible_plugins, g_strdup(plugin)); } /** * fu_device_retry_add_recovery: * @self: a #FuDevice * @domain: a #GQuark, or %0 for all domains * @code: a #GError code * @func: (scope async) (nullable): a function to recover the device * * Sets the optional function to be called when fu_device_retry() fails, which * is possibly a device reset. * * If @func is %NULL then recovery is not possible and an error is returned * straight away. * * Since: 1.4.0 **/ void fu_device_retry_add_recovery(FuDevice *self, GQuark domain, gint code, FuDeviceRetryFunc func) { FuDevicePrivate *priv = GET_PRIVATE(self); FuDeviceRetryRecovery *rec; g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(domain != 0); rec = g_new(FuDeviceRetryRecovery, 1); rec->domain = domain; rec->code = code; rec->recovery_func = func; g_ptr_array_add(priv->retry_recs, rec); } /** * fu_device_retry_set_delay: * @self: a #FuDevice * @delay: delay in ms * * Sets the recovery delay between failed retries. * * Since: 1.4.0 **/ void fu_device_retry_set_delay(FuDevice *self, guint delay) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); priv->retry_delay = delay; } /** * fu_device_retry_full: * @self: a #FuDevice * @func: (scope async) (closure user_data): a function to execute * @count: the number of tries to try the function * @delay: the delay between each try in ms * @user_data: (nullable): a helper to pass to @func * @error: (nullable): optional return location for an error * * Calls a specific function a number of times, optionally handling the error * with a reset action. * * If fu_device_retry_add_recovery() has not been used then all errors are * considered non-fatal until the last try. * * If the reset function returns %FALSE, then the function returns straight away * without processing any pending retries. * * Since: 1.5.5 **/ gboolean fu_device_retry_full(FuDevice *self, FuDeviceRetryFunc func, guint count, guint delay, gpointer user_data, GError **error) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(func != NULL, FALSE); g_return_val_if_fail(count >= 1, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); for (guint i = 0;; i++) { g_autoptr(GError) error_local = NULL; /* delay */ if (i > 0) fu_device_sleep(self, delay); /* run function, if success return success */ if (func(self, user_data, &error_local)) break; /* sanity check */ if (error_local == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "exec failed but no error set!"); return FALSE; } /* too many retries */ if (i >= count - 1) { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed after %u retries: ", count); return FALSE; } /* show recoverable error on the console */ if (priv->retry_recs->len == 0) { g_info("failed on try %u of %u: %s", i + 1, count, error_local->message); continue; } /* find the condition that matches */ for (guint j = 0; j < priv->retry_recs->len; j++) { FuDeviceRetryRecovery *rec = g_ptr_array_index(priv->retry_recs, j); if (g_error_matches(error_local, rec->domain, rec->code)) { if (rec->recovery_func != NULL) { if (!rec->recovery_func(self, user_data, error)) return FALSE; } else { g_propagate_prefixed_error( error, g_steal_pointer(&error_local), "device recovery not possible: "); return FALSE; } } } } /* success */ return TRUE; } /** * fu_device_retry: * @self: a #FuDevice * @func: (scope async) (closure user_data): a function to execute * @count: the number of tries to try the function * @user_data: (nullable): a helper to pass to @func * @error: (nullable): optional return location for an error * * Calls a specific function a number of times, optionally handling the error * with a reset action. * * If fu_device_retry_add_recovery() has not been used then all errors are * considered non-fatal until the last try. * * If the reset function returns %FALSE, then the function returns straight away * without processing any pending retries. * * Since: 1.4.0 **/ gboolean fu_device_retry(FuDevice *self, FuDeviceRetryFunc func, guint count, gpointer user_data, GError **error) { FuDevicePrivate *priv = GET_PRIVATE(self); return fu_device_retry_full(self, func, count, priv->retry_delay, user_data, error); } /** * fu_device_sleep: * @self: a #FuDevice * @delay_ms: delay in milliseconds * * Delays program execution up to 100 seconds, unless the device is emulated where no delays is * performed. * * Long unavoidable delays (more than 1 second) should really use `fu_device_sleep_full()` so that * the percentage progress bar is updated. * * Since: 1.8.11 **/ void fu_device_sleep(FuDevice *self, guint delay_ms) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(delay_ms < 100000); if (delay_ms > 0 && !fu_device_has_flag(self, FWUPD_DEVICE_FLAG_EMULATED)) g_usleep(delay_ms * 1000); } /** * fu_device_sleep_full: * @self: a #FuDevice * @delay_ms: delay in milliseconds * @progress: a #FuProgress * * Delays program execution up to 1000 seconds, unless the device is emulated where no delays is * performed. * * Since: 1.8.11 **/ void fu_device_sleep_full(FuDevice *self, guint delay_ms, FuProgress *progress) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(delay_ms < 1000000); g_return_if_fail(FU_IS_PROGRESS(progress)); if (delay_ms > 0 && !fu_device_has_flag(self, FWUPD_DEVICE_FLAG_EMULATED)) fu_progress_sleep(progress, delay_ms); } static gboolean fu_device_poll_locker_open_cb(GObject *device, GError **error) { FuDevice *self = FU_DEVICE(device); FuDevicePrivate *priv = GET_PRIVATE(self); g_atomic_int_inc(&priv->poll_locker_cnt); return TRUE; } static gboolean fu_device_poll_locker_close_cb(GObject *device, GError **error) { FuDevice *self = FU_DEVICE(device); FuDevicePrivate *priv = GET_PRIVATE(self); g_atomic_int_dec_and_test(&priv->poll_locker_cnt); return TRUE; } /** * fu_device_poll_locker_new: * @self: a #FuDevice * @error: (nullable): optional return location for an error * * Returns a device locker that prevents polling on the device. If there are no open poll lockers * then the poll callback will be called. * * Use %FU_DEVICE_INTERNAL_AUTO_PAUSE_POLLING to opt into this functionality. * * Returns: (transfer full): a #FuDeviceLocker * * Since: 1.8.1 **/ FuDeviceLocker * fu_device_poll_locker_new(FuDevice *self, GError **error) { g_return_val_if_fail(FU_IS_DEVICE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return fu_device_locker_new_full(self, fu_device_poll_locker_open_cb, fu_device_poll_locker_close_cb, error); } /** * fu_device_poll: * @self: a #FuDevice * @error: (nullable): optional return location for an error * * Polls a device, typically querying the hardware for status. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_poll(FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* subclassed */ if (klass->poll != NULL) { if (!klass->poll(self, error)) return FALSE; } return TRUE; } static gboolean fu_device_poll_cb(gpointer user_data) { FuDevice *self = FU_DEVICE(user_data); FuDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GError) error_local = NULL; /* device is being detached, written, read, or attached */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_AUTO_PAUSE_POLLING) && priv->poll_locker_cnt > 0) { g_debug("ignoring poll callback as an action is in progress"); return G_SOURCE_CONTINUE; } if (!fu_device_poll(self, &error_local)) { g_warning("disabling polling: %s", error_local->message); priv->poll_id = 0; return G_SOURCE_REMOVE; } return G_SOURCE_CONTINUE; } /** * fu_device_set_poll_interval: * @self: a #FuPlugin * @interval: duration in ms, or 0 to disable * * Polls the hardware every interval period. If the subclassed `->poll()` method * returns %FALSE then a warning is printed to the console and the poll is * disabled until the next call to fu_device_set_poll_interval(). * * Since: 1.1.2 **/ void fu_device_set_poll_interval(FuDevice *self, guint interval) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); if (priv->poll_id != 0) { g_source_remove(priv->poll_id); priv->poll_id = 0; } if (interval == 0) return; if (interval % 1000 == 0) { priv->poll_id = g_timeout_add_seconds(interval / 1000, fu_device_poll_cb, self); } else { priv->poll_id = g_timeout_add(interval, fu_device_poll_cb, self); } } /** * fu_device_get_order: * @self: a #FuPlugin * * Gets the device order, where higher numbers are installed after lower * numbers. * * Returns: the integer value * * Since: 1.0.8 **/ gint fu_device_get_order(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), 0); return priv->order; } /** * fu_device_set_order: * @self: a #FuDevice * @order: an integer value * * Sets the device order, where higher numbers are installed after lower * numbers. * * Since: 1.0.8 **/ void fu_device_set_order(FuDevice *self, gint order) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); priv->order = order; } /** * fu_device_get_priority: * @self: a #FuPlugin * * Gets the device priority, where higher numbers are better. * * Returns: the integer value * * Since: 1.1.1 **/ guint fu_device_get_priority(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), 0); return priv->priority; } /** * fu_device_set_priority: * @self: a #FuDevice * @priority: an integer value * * Sets the device priority, where higher numbers are better. * * Since: 1.1.1 **/ void fu_device_set_priority(FuDevice *self, guint priority) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); priv->priority = priority; } /** * fu_device_get_equivalent_id: * @self: a #FuDevice * * Gets any equivalent ID for a device * * Returns: (transfer none): a #gchar or NULL * * Since: 0.6.1 **/ const gchar * fu_device_get_equivalent_id(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return priv->equivalent_id; } /** * fu_device_set_equivalent_id: * @self: a #FuDevice * @equivalent_id: (nullable): a string * * Sets any equivalent ID for a device * * Since: 0.6.1 **/ void fu_device_set_equivalent_id(FuDevice *self, const gchar *equivalent_id) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->equivalent_id, equivalent_id) == 0) return; g_free(priv->equivalent_id); priv->equivalent_id = g_strdup(equivalent_id); } /** * fu_device_get_alternate_id: * @self: a #FuDevice * * Gets any alternate device ID. An alternate device may be linked to the primary * device in some way. * * Returns: (transfer none): a device or %NULL * * Since: 1.1.0 **/ const gchar * fu_device_get_alternate_id(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return priv->alternate_id; } /** * fu_device_set_alternate_id: * @self: a #FuDevice * @alternate_id: (nullable): Another #FuDevice ID * * Sets any alternate device ID. An alternate device may be linked to the primary * device in some way. * * Since: 1.1.0 **/ void fu_device_set_alternate_id(FuDevice *self, const gchar *alternate_id) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->alternate_id, alternate_id) == 0) return; g_free(priv->alternate_id); priv->alternate_id = g_strdup(alternate_id); } /** * fu_device_get_alternate: * @self: a #FuDevice * * Gets any alternate device. An alternate device may be linked to the primary * device in some way. * * The alternate object will be matched from the ID set in fu_device_set_alternate_id() * and will be assigned by the daemon. This means if the ID is not found as an * added device, then this function will return %NULL. * * Returns: (transfer none): a device or %NULL * * Since: 0.7.2 **/ FuDevice * fu_device_get_alternate(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return priv->alternate; } /** * fu_device_set_alternate: * @self: a #FuDevice * @alternate: Another #FuDevice * * Sets any alternate device. An alternate device may be linked to the primary * device in some way. * * This function is only usable by the daemon, not directly from plugins. * * Since: 0.7.2 **/ void fu_device_set_alternate(FuDevice *self, FuDevice *alternate) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_set_object(&priv->alternate, alternate); } /** * fu_device_get_parent: * @self: a #FuDevice * * Gets any parent device. An parent device is logically "above" the current * device and this may be reflected in client tools. * * This information also allows the plugin to optionally verify the parent * device, for instance checking the parent device firmware version. * * The parent object is not refcounted and if destroyed this function will then * return %NULL. * * Returns: (transfer none): a device or %NULL * * Since: 1.0.8 **/ FuDevice * fu_device_get_parent(FuDevice *self) { g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return FU_DEVICE(fwupd_device_get_parent(FWUPD_DEVICE(self))); } /** * fu_device_get_root: * @self: a #FuDevice * * Gets the root parent device. A parent device is logically "above" the current * device and this may be reflected in client tools. * * If there is no parent device defined, then @self is returned. * * Returns: (transfer full): a device * * Since: 1.4.0 **/ FuDevice * fu_device_get_root(FuDevice *self) { FuDevice *parent; g_return_val_if_fail(FU_IS_DEVICE(self), NULL); do { parent = fu_device_get_parent(self); if (parent != NULL) self = parent; } while (parent != NULL); return g_object_ref(self); } static void fu_device_set_composite_id(FuDevice *self, const gchar *composite_id) { GPtrArray *children; /* subclassed simple setter */ fwupd_device_set_composite_id(FWUPD_DEVICE(self), composite_id); /* all children */ children = fu_device_get_children(self); for (guint i = 0; i < children->len; i++) { FuDevice *child_tmp = g_ptr_array_index(children, i); fu_device_set_composite_id(child_tmp, composite_id); } } /** * fu_device_set_parent: * @self: a #FuDevice * @parent: (nullable): a device * * Sets any parent device. An parent device is logically "above" the current * device and this may be reflected in client tools. * * This information also allows the plugin to optionally verify the parent * device, for instance checking the parent device firmware version. * * Since: 1.0.8 **/ void fu_device_set_parent(FuDevice *self, FuDevice *parent) { g_return_if_fail(FU_IS_DEVICE(self)); /* debug */ if (parent != NULL) { g_info("setting parent of %s [%s] to be %s [%s]", fu_device_get_name(self), fu_device_get_id(self), fu_device_get_name(parent), fu_device_get_id(parent)); } /* set the composite ID on the children and grandchildren */ if (parent != NULL) fu_device_set_composite_id(self, fu_device_get_composite_id(parent)); /* if the parent has a context, make the child inherit it */ if (parent != NULL) { if (fu_device_get_context(self) == NULL && fu_device_get_context(parent) != NULL) fu_device_set_context(self, fu_device_get_context(parent)); } fwupd_device_set_parent(FWUPD_DEVICE(self), FWUPD_DEVICE(parent)); g_object_notify(G_OBJECT(self), "parent"); } /* if the proxy sets this flag copy it to the logical device */ static void fu_device_incorporate_from_proxy_flags(FuDevice *self, FuDevice *proxy) { const FwupdDeviceFlags flags[] = {FWUPD_DEVICE_FLAG_EMULATED, FWUPD_DEVICE_FLAG_UNREACHABLE}; for (guint i = 0; i < G_N_ELEMENTS(flags); i++) { if (fu_device_has_flag(proxy, flags[i])) { g_debug("propagating %s from proxy", fwupd_device_flag_to_string(flags[i])); fu_device_add_flag(self, flags[i]); } } } static void fu_device_proxy_flags_notify_cb(FuDevice *proxy, GParamSpec *pspec, gpointer user_data) { FuDevice *self = FU_DEVICE(user_data); fu_device_incorporate_from_proxy_flags(self, proxy); } /** * fu_device_set_proxy: * @self: a #FuDevice * @proxy: a device * * Sets any proxy device. A proxy device can be used to perform an action on * behalf of another device, for instance attach()ing it after a successful * update. * * If the %FU_DEVICE_INTERNAL_FLAG_REFCOUNTED_PROXY flag is present on the device then a *strong* * reference is used, otherwise the proxy will be cleared if @proxy is destroyed. * * Since: 1.4.1 **/ void fu_device_set_proxy(FuDevice *self, FuDevice *proxy) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); /* unchanged */ if (proxy == priv->proxy) return; /* copy from proxy */ if (proxy != NULL) { if (fu_device_get_context(self) == NULL && fu_device_get_context(proxy) != NULL) fu_device_set_context(self, fu_device_get_context(proxy)); if (fu_device_get_physical_id(self) == NULL && fu_device_get_physical_id(proxy) != NULL) fu_device_set_physical_id(self, fu_device_get_physical_id(proxy)); g_signal_connect(FWUPD_DEVICE(proxy), "notify::flags", G_CALLBACK(fu_device_proxy_flags_notify_cb), self); fu_device_incorporate_from_proxy_flags(self, proxy); } /* sometimes strong, sometimes weak */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_REFCOUNTED_PROXY)) { g_set_object(&priv->proxy, proxy); } else { if (priv->proxy != NULL) g_object_remove_weak_pointer(G_OBJECT(priv->proxy), (gpointer *)&priv->proxy); if (proxy != NULL) g_object_add_weak_pointer(G_OBJECT(proxy), (gpointer *)&priv->proxy); priv->proxy = proxy; } g_object_notify(G_OBJECT(self), "proxy"); } /** * fu_device_get_proxy: * @self: a #FuDevice * * Gets any proxy device. A proxy device can be used to perform an action on * behalf of another device, for instance attach()ing it after a successful * update. * * Unless %FU_DEVICE_INTERNAL_FLAG_REFCOUNTED_PROXY is set, the proxy object is not refcounted and * if destroyed this function will then return %NULL. * * Returns: (transfer none): a device or %NULL * * Since: 1.4.1 **/ FuDevice * fu_device_get_proxy(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return priv->proxy; } /** * fu_device_get_proxy_with_fallback: * @self: a #FuDevice * * Gets the proxy device if %FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FALLBACK is set, falling back to the * device itself. * * Returns: (transfer none): a device * * Since: 1.6.2 **/ FuDevice * fu_device_get_proxy_with_fallback(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FALLBACK) && priv->proxy != NULL) return priv->proxy; return self; } /** * fu_device_get_children: * @self: a #FuDevice * * Gets any child devices. A child device is logically "below" the current * device and this may be reflected in client tools. * * Returns: (transfer none) (element-type FuDevice): child devices * * Since: 1.0.8 **/ GPtrArray * fu_device_get_children(FuDevice *self) { g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return fwupd_device_get_children(FWUPD_DEVICE(self)); } /** * fu_device_add_child: * @self: a #FuDevice * @child: Another #FuDevice * * Sets any child device. An child device is logically linked to the primary * device in some way. * * Since: 1.0.8 **/ void fu_device_add_child(FuDevice *self, FuDevice *child) { FuDevicePrivate *priv = GET_PRIVATE(self); FuDevicePrivate *priv_child = GET_PRIVATE(child); GPtrArray *children; g_autoptr(GError) error = NULL; g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(FU_IS_DEVICE(child)); /* add if the child does not already exist */ fwupd_device_add_child(FWUPD_DEVICE(self), FWUPD_DEVICE(child)); /* propagate inhibits to children */ if (priv->inhibits != NULL && fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN)) { g_autoptr(GList) values = g_hash_table_get_values(priv->inhibits); for (GList *l = values; l != NULL; l = l->next) { FuDeviceInhibit *inhibit = (FuDeviceInhibit *)l->data; fu_device_inhibit_full(child, inhibit->problem, inhibit->inhibit_id, inhibit->reason); } } /* ensure the parent has the MAX() of the children's removal delay */ children = fu_device_get_children(self); for (guint i = 0; i < children->len; i++) { FuDevice *child_tmp = g_ptr_array_index(children, i); guint remove_delay = fu_device_get_remove_delay(child_tmp); if (remove_delay > priv->remove_delay) { g_debug("setting remove delay to %ums as child is greater than %ums", remove_delay, priv->remove_delay); priv->remove_delay = remove_delay; } } /* ensure the parent has the MAX() of the children's acquiesce delay */ children = fu_device_get_children(self); for (guint i = 0; i < children->len; i++) { FuDevice *child_tmp = g_ptr_array_index(children, i); guint acquiesce_delay = fu_device_get_acquiesce_delay(child_tmp); if (acquiesce_delay > priv->acquiesce_delay) { g_debug("setting acquiesce delay to %ums as child is greater than %ums", acquiesce_delay, priv->acquiesce_delay); priv->acquiesce_delay = acquiesce_delay; } } /* ensure child has the parent acquiesce delay */ for (guint i = 0; i < children->len; i++) { FuDevice *child_tmp = g_ptr_array_index(children, i); fu_device_set_acquiesce_delay(child_tmp, priv->acquiesce_delay); } /* copy from main device if unset */ if (fu_device_get_physical_id(child) == NULL && fu_device_get_physical_id(self) != NULL) fu_device_set_physical_id(child, fu_device_get_physical_id(self)); if (priv_child->backend_id == NULL && priv->backend_id != NULL) fu_device_set_backend_id(child, priv->backend_id); if (priv_child->ctx == NULL && priv->ctx != NULL) fu_device_set_context(child, priv->ctx); if (fu_device_get_vendor(child) == NULL) fu_device_set_vendor(child, fu_device_get_vendor(self)); if (priv_child->remove_delay == 0 && priv->remove_delay != 0) fu_device_set_remove_delay(child, priv->remove_delay); if (priv_child->acquiesce_delay == 0 && priv->acquiesce_delay != 0) fu_device_set_acquiesce_delay(child, priv->acquiesce_delay); if (fu_device_get_vendor_ids(child)->len == 0) { GPtrArray *vendor_ids = fu_device_get_vendor_ids(self); for (guint i = 0; i < vendor_ids->len; i++) { const gchar *vendor_id = g_ptr_array_index(vendor_ids, i); fu_device_add_vendor_id(child, vendor_id); } } if (fu_device_get_icons(child)->len == 0) { GPtrArray *icons = fu_device_get_icons(self); for (guint i = 0; i < icons->len; i++) { const gchar *icon_name = g_ptr_array_index(icons, i); fu_device_add_icon(child, icon_name); } } /* ensure the ID is converted */ if (!fu_device_ensure_id(child, &error)) g_warning("failed to ensure child: %s", error->message); /* ensure the parent is also set on the child */ fu_device_set_parent(child, self); /* signal to the plugin in case this is done after setup */ g_signal_emit(self, signals[SIGNAL_CHILD_ADDED], 0, child); } /** * fu_device_remove_child: * @self: a #FuDevice * @child: Another #FuDevice * * Removes child device. * * Since: 1.6.2 **/ void fu_device_remove_child(FuDevice *self, FuDevice *child) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(FU_IS_DEVICE(child)); /* proxy */ fwupd_device_remove_child(FWUPD_DEVICE(self), FWUPD_DEVICE(child)); /* signal to the plugin */ g_signal_emit(self, signals[SIGNAL_CHILD_REMOVED], 0, child); } /** * fu_device_get_parent_guids: * @self: a #FuDevice * * Gets any parent device GUIDs. If a device is added to the daemon that matches * any GUIDs added from fu_device_add_parent_guid() then this device is marked the parent of @self. * * Returns: (transfer none) (element-type utf8): a list of GUIDs * * Since: 1.0.8 **/ GPtrArray * fu_device_get_parent_guids(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return priv->parent_guids; } /** * fu_device_has_parent_guid: * @self: a #FuDevice * @guid: a GUID * * Searches the list of parent GUIDs for a string match. * * Returns: %TRUE if the parent GUID exists * * Since: 1.0.8 **/ gboolean fu_device_has_parent_guid(FuDevice *self, const gchar *guid) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(guid != NULL, FALSE); for (guint i = 0; i < priv->parent_guids->len; i++) { const gchar *guid_tmp = g_ptr_array_index(priv->parent_guids, i); if (g_strcmp0(guid_tmp, guid) == 0) return TRUE; } return FALSE; } /** * fu_device_add_parent_guid: * @self: a #FuDevice * @guid: a GUID * * Sets any parent device using a GUID. An parent device is logically linked to * the primary device in some way and can be added before or after @self. * * The GUIDs are searched in order, and so the order of adding GUIDs may be * important if more than one parent device might match. * * If the parent device is removed, any children logically linked to it will * also be removed. * * Since: 1.0.8 **/ void fu_device_add_parent_guid(FuDevice *self, const gchar *guid) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(guid != NULL); /* make valid */ if (!fwupd_guid_is_valid(guid)) { g_autofree gchar *tmp = fwupd_guid_hash_string(guid); if (fu_device_has_parent_guid(self, tmp)) return; g_debug("using %s for %s", tmp, guid); g_ptr_array_add(priv->parent_guids, g_steal_pointer(&tmp)); return; } /* already valid */ if (fu_device_has_parent_guid(self, guid)) return; g_ptr_array_add(priv->parent_guids, g_strdup(guid)); } /** * fu_device_get_parent_physical_ids: * @self: a #FuDevice * * Gets any parent device IDs. If a device is added to the daemon that matches * the physical ID added from fu_device_add_parent_physical_id() then this * device is marked the parent of @self. * * Returns: (transfer none) (element-type utf8) (nullable): a list of IDs * * Since: 1.6.2 **/ GPtrArray * fu_device_get_parent_physical_ids(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return priv->parent_physical_ids; } /** * fu_device_has_parent_physical_id: * @self: a #FuDevice * @physical_id: a device physical ID * * Searches the list of parent IDs for a string match. * * Returns: %TRUE if the parent ID exists * * Since: 1.6.2 **/ gboolean fu_device_has_parent_physical_id(FuDevice *self, const gchar *physical_id) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(physical_id != NULL, FALSE); if (priv->parent_physical_ids == NULL) return FALSE; for (guint i = 0; i < priv->parent_physical_ids->len; i++) { const gchar *tmp = g_ptr_array_index(priv->parent_physical_ids, i); if (g_strcmp0(tmp, physical_id) == 0) return TRUE; } return FALSE; } /** * fu_device_add_parent_physical_id: * @self: a #FuDevice * @physical_id: a device physical ID * * Sets any parent device using the physical ID. An parent device is logically * linked to the primary device in some way and can be added before or after @self. * * The IDs are searched in order, and so the order of adding IDs may be * important if more than one parent device might match. * * Since: 1.6.2 **/ void fu_device_add_parent_physical_id(FuDevice *self, const gchar *physical_id) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(physical_id != NULL); /* ensure exists */ if (priv->parent_physical_ids == NULL) priv->parent_physical_ids = g_ptr_array_new_with_free_func(g_free); /* already present */ if (fu_device_has_parent_physical_id(self, physical_id)) return; g_ptr_array_add(priv->parent_physical_ids, g_strdup(physical_id)); } /** * fu_device_get_parent_backend_ids: * @self: a #FuDevice * * Gets any parent device IDs. If a device is added to the daemon that matches * the physical ID added from fu_device_add_parent_backend_id() then this * device is marked the parent of @self. * * Returns: (transfer none) (element-type utf8) (nullable): a list of IDs * * Since: 1.9.7 **/ GPtrArray * fu_device_get_parent_backend_ids(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return priv->parent_backend_ids; } /** * fu_device_has_parent_backend_id: * @self: a #FuDevice * @backend_id: a device physical ID * * Searches the list of parent IDs for a string match. * * Returns: %TRUE if the parent ID exists * * Since: 1.9.7 **/ gboolean fu_device_has_parent_backend_id(FuDevice *self, const gchar *backend_id) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(backend_id != NULL, FALSE); if (priv->parent_backend_ids == NULL) return FALSE; for (guint i = 0; i < priv->parent_backend_ids->len; i++) { const gchar *tmp = g_ptr_array_index(priv->parent_backend_ids, i); if (g_strcmp0(tmp, backend_id) == 0) return TRUE; } return FALSE; } /** * fu_device_add_parent_backend_id: * @self: a #FuDevice * @backend_id: a device physical ID * * Sets any parent device using the physical ID. An parent device is logically * linked to the primary device in some way and can be added before or after @self. * * The IDs are searched in order, and so the order of adding IDs may be * important if more than one parent device might match. * * Since: 1.9.7 **/ void fu_device_add_parent_backend_id(FuDevice *self, const gchar *backend_id) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(backend_id != NULL); /* ensure exists */ if (priv->parent_backend_ids == NULL) priv->parent_backend_ids = g_ptr_array_new_with_free_func(g_free); /* already present */ if (fu_device_has_parent_backend_id(self, backend_id)) return; g_ptr_array_add(priv->parent_backend_ids, g_strdup(backend_id)); } static gboolean fu_device_add_child_by_type_guid(FuDevice *self, GType type, const gchar *guid, GError **error) { FuDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(FuDevice) child = NULL; child = g_object_new(type, "context", priv->ctx, "logical-id", guid, NULL); fu_device_add_guid(child, guid); if (fu_device_get_physical_id(self) != NULL) fu_device_set_physical_id(child, fu_device_get_physical_id(self)); if (!fu_device_ensure_id(self, error)) return FALSE; if (!fu_device_probe(child, error)) return FALSE; fu_device_convert_instance_ids(child); fu_device_add_child(self, child); return TRUE; } static gboolean fu_device_add_child_by_kv(FuDevice *self, const gchar *str, GError **error) { g_auto(GStrv) split = g_strsplit(str, "|", -1); /* type same as parent */ if (g_strv_length(split) == 1) { return fu_device_add_child_by_type_guid(self, G_OBJECT_TYPE(self), split[1], error); } /* type specified */ if (g_strv_length(split) == 2) { GType devtype = g_type_from_name(split[0]); if (devtype == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no GType registered"); return FALSE; } return fu_device_add_child_by_type_guid(self, devtype, split[1], error); } /* more than one '|' */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "unable to add parse child section"); return FALSE; } static gboolean fu_device_set_quirk_inhibit_section(FuDevice *self, const gchar *value, GError **error) { g_auto(GStrv) sections = NULL; g_return_val_if_fail(value != NULL, FALSE); /* sanity check */ sections = g_strsplit(value, ":", -1); if (g_strv_length(sections) != 2) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported, expected k1:v1[,k2:v2][,k3:]"); return FALSE; } /* allow empty string to unset quirk */ if (g_strcmp0(sections[1], "") != 0) fu_device_inhibit(self, sections[0], sections[1]); else fu_device_uninhibit(self, sections[0]); /* success */ return TRUE; } /** * fu_device_set_quirk_kv: * @self: a #FuDevice * @key: a string key * @value: a string value * @error: (nullable): optional return location for an error * * Sets a specific quirk on the device. * * Returns: %TRUE on success * * Since: 1.8.5 **/ gboolean fu_device_set_quirk_kv(FuDevice *self, const gchar *key, const gchar *value, GError **error) { FuDevicePrivate *priv = GET_PRIVATE(self); FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); guint64 tmp; g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(value != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (g_strcmp0(key, FU_QUIRKS_PLUGIN) == 0) { g_auto(GStrv) sections = g_strsplit(value, ",", -1); for (guint i = 0; sections[i] != NULL; i++) fu_device_add_possible_plugin(self, sections[i]); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_FLAGS) == 0) { fu_device_set_custom_flags(self, value); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_NAME) == 0) { fu_device_set_name(self, value); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_SUMMARY) == 0) { fu_device_set_summary(self, value); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_BRANCH) == 0) { fu_device_set_branch(self, value); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_VENDOR) == 0) { fu_device_set_vendor(self, value); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_VENDOR_ID) == 0) { g_auto(GStrv) sections = g_strsplit(value, ",", -1); for (guint i = 0; sections[i] != NULL; i++) fu_device_add_vendor_id(self, sections[i]); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_PROTOCOL) == 0) { g_auto(GStrv) sections = g_strsplit(value, ",", -1); for (guint i = 0; sections[i] != NULL; i++) fu_device_add_protocol(self, sections[i]); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_ISSUE) == 0) { g_auto(GStrv) sections = g_strsplit(value, ",", -1); for (guint i = 0; sections[i] != NULL; i++) fu_device_add_issue(self, sections[i]); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_VERSION) == 0) { fu_device_set_version(self, value); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_UPDATE_MESSAGE) == 0) { fu_device_set_update_message(self, value); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_UPDATE_IMAGE) == 0) { fu_device_set_update_image(self, value); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_ICON) == 0) { g_auto(GStrv) sections = g_strsplit(value, ",", -1); for (guint i = 0; sections[i] != NULL; i++) fu_device_add_icon(self, sections[i]); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_GUID) == 0) { g_auto(GStrv) sections = g_strsplit(value, ",", -1); for (guint i = 0; sections[i] != NULL; i++) fu_device_add_guid(self, sections[i]); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_GUID_QUIRK) == 0) { g_auto(GStrv) sections = g_strsplit(value, ",", -1); for (guint i = 0; sections[i] != NULL; i++) fu_device_add_guid_full(self, sections[i], FU_DEVICE_INSTANCE_FLAG_QUIRKS); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_COUNTERPART_GUID) == 0) { g_auto(GStrv) sections = g_strsplit(value, ",", -1); for (guint i = 0; sections[i] != NULL; i++) fu_device_add_counterpart_guid(self, sections[i]); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_PARENT_GUID) == 0) { g_auto(GStrv) sections = g_strsplit(value, ",", -1); for (guint i = 0; sections[i] != NULL; i++) fu_device_add_parent_guid(self, sections[i]); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_PROXY_GUID) == 0) { fu_device_set_proxy_guid(self, value); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_FIRMWARE_SIZE_MIN) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT64, error)) return FALSE; fu_device_set_firmware_size_min(self, tmp); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_FIRMWARE_SIZE_MAX) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT64, error)) return FALSE; fu_device_set_firmware_size_max(self, tmp); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_FIRMWARE_SIZE) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT64, error)) return FALSE; fu_device_set_firmware_size(self, tmp); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_INSTALL_DURATION) == 0) { if (!fu_strtoull(value, &tmp, 0, 60 * 60 * 24, error)) return FALSE; fu_device_set_install_duration(self, tmp); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_PRIORITY) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; fu_device_set_priority(self, tmp); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_BATTERY_THRESHOLD) == 0) { if (!fu_strtoull(value, &tmp, 0, 100, error)) return FALSE; fu_device_set_battery_threshold(self, tmp); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_REMOVE_DELAY) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT, error)) return FALSE; fu_device_set_remove_delay(self, tmp); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_ACQUIESCE_DELAY) == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT, error)) return FALSE; fu_device_set_acquiesce_delay(self, tmp); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_VERSION_FORMAT) == 0) { fu_device_set_version_format(self, fwupd_version_format_from_string(value)); return TRUE; } if (g_strcmp0(key, FU_QUIRKS_INHIBIT) == 0) { g_auto(GStrv) sections = g_strsplit(value, ",", -1); for (guint i = 0; sections[i] != NULL; i++) { if (!fu_device_set_quirk_inhibit_section(self, sections[i], error)) return FALSE; } return TRUE; } if (g_strcmp0(key, FU_QUIRKS_GTYPE) == 0) { if (priv->specialized_gtype != G_TYPE_INVALID) { g_debug("already set GType to %s, ignoring %s", g_type_name(priv->specialized_gtype), value); return TRUE; } priv->specialized_gtype = g_type_from_name(value); if (priv->specialized_gtype == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unknown GType name %s", value); return FALSE; } return TRUE; } if (g_strcmp0(key, FU_QUIRKS_PROXY_GTYPE) == 0) { if (priv->proxy_gtype != G_TYPE_INVALID) { g_debug("already set proxy GType to %s, ignoring %s", g_type_name(priv->proxy_gtype), value); return TRUE; } priv->proxy_gtype = g_type_from_name(value); if (priv->proxy_gtype == G_TYPE_INVALID) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unknown GType name %s", value); return FALSE; } return TRUE; } if (g_strcmp0(key, FU_QUIRKS_FIRMWARE_GTYPE) == 0) { if (priv->firmware_gtype != G_TYPE_INVALID) { g_debug("already set firmware GType to %s, ignoring %s", g_type_name(priv->firmware_gtype), value); return TRUE; } priv->firmware_gtype = g_type_from_name(value); if (priv->firmware_gtype == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unknown GType name %s", value); return FALSE; } return TRUE; } if (g_strcmp0(key, FU_QUIRKS_CHILDREN) == 0) { g_auto(GStrv) sections = g_strsplit(value, ",", -1); for (guint i = 0; sections[i] != NULL; i++) { if (!fu_device_add_child_by_kv(self, sections[i], error)) return FALSE; } return TRUE; } /* optional device-specific method */ if (klass->set_quirk_kv != NULL) return klass->set_quirk_kv(self, key, value, error); /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } /** * fu_device_get_specialized_gtype: * @self: a #FuDevice * * Gets the specialized type of the device * * Returns:#GType * * Since: 1.3.3 **/ GType fu_device_get_specialized_gtype(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); return priv->specialized_gtype; } /** * fu_device_get_proxy_gtype: * @self: a #FuDevice * * Gets the specialized type of the device * * Returns:#GType * * Since: 1.9.15 **/ GType fu_device_get_proxy_gtype(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); return priv->proxy_gtype; } /** * fu_device_get_firmware_gtype: * @self: a #FuDevice * * Gets the default firmware type for the device. * * Returns: #GType * * Since: 1.7.2 **/ GType fu_device_get_firmware_gtype(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); return priv->firmware_gtype; } /** * fu_device_set_firmware_gtype: * @self: a #FuDevice * @firmware_gtype: a #GType * * Sets the default firmware type for the device. * * Since: 1.7.2 **/ void fu_device_set_firmware_gtype(FuDevice *self, GType firmware_gtype) { FuDevicePrivate *priv = GET_PRIVATE(self); priv->firmware_gtype = firmware_gtype; } static void fu_device_quirks_iter_cb(FuContext *ctx, const gchar *key, const gchar *value, gpointer user_data) { FuDevice *self = FU_DEVICE(user_data); g_autoptr(GError) error = NULL; if (!fu_device_set_quirk_kv(self, key, value, &error)) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { g_warning("failed to set quirk key %s=%s: %s", key, value, error->message); } } } static void fu_device_add_guid_quirks(FuDevice *self, const gchar *guid) { FuDevicePrivate *priv = GET_PRIVATE(self); if (priv->ctx == NULL) { g_autofree gchar *str = fu_device_to_string(self); g_critical("no FuContext assigned for %s", str); return; } fu_context_lookup_quirk_by_id_iter(priv->ctx, guid, NULL, fu_device_quirks_iter_cb, self); } /** * fu_device_set_firmware_size: * @self: a #FuDevice * @size: Size in bytes * * Sets the exact allowed size of the firmware blob. * * Since: 1.2.6 **/ void fu_device_set_firmware_size(FuDevice *self, guint64 size) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); priv->size_min = size; priv->size_max = size; } /** * fu_device_set_firmware_size_min: * @self: a #FuDevice * @size_min: Size in bytes * * Sets the minimum allowed size of the firmware blob. * * Since: 1.1.2 **/ void fu_device_set_firmware_size_min(FuDevice *self, guint64 size_min) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); priv->size_min = size_min; } /** * fu_device_set_firmware_size_max: * @self: a #FuDevice * @size_max: Size in bytes * * Sets the maximum allowed size of the firmware blob. * * Since: 1.1.2 **/ void fu_device_set_firmware_size_max(FuDevice *self, guint64 size_max) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); priv->size_max = size_max; } /** * fu_device_get_firmware_size_min: * @self: a #FuDevice * * Gets the minimum size of the firmware blob. * * Returns: Size in bytes, or 0 if unset * * Since: 1.2.6 **/ guint64 fu_device_get_firmware_size_min(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), 0); return priv->size_min; } /** * fu_device_get_firmware_size_max: * @self: a #FuDevice * * Gets the maximum size of the firmware blob. * * Returns: Size in bytes, or 0 if unset * * Since: 1.2.6 **/ guint64 fu_device_get_firmware_size_max(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), 0); return priv->size_max; } static void fu_device_add_guid_safe(FuDevice *self, const gchar *guid, FuDeviceInstanceFlags flags) { /* add the device GUID before adding additional GUIDs from quirks * to ensure the bootloader GUID is listed after the runtime GUID */ if (flags & FU_DEVICE_INSTANCE_FLAG_VISIBLE) fwupd_device_add_guid(FWUPD_DEVICE(self), guid); if (flags & FU_DEVICE_INSTANCE_FLAG_QUIRKS) fu_device_add_guid_quirks(self, guid); } /** * fu_device_has_guid: * @self: a #FuDevice * @guid: a GUID, e.g. `WacomAES` * * Finds out if the device has a specific GUID. * * Returns: %TRUE if the GUID is found * * Since: 1.2.2 **/ gboolean fu_device_has_guid(FuDevice *self, const gchar *guid) { g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(guid != NULL, FALSE); /* make valid */ if (!fwupd_guid_is_valid(guid)) { g_autofree gchar *tmp = fwupd_guid_hash_string(guid); return fwupd_device_has_guid(FWUPD_DEVICE(self), tmp); } /* already valid */ return fwupd_device_has_guid(FWUPD_DEVICE(self), guid); } static gboolean fu_device_has_instance_id_quirk(FuDevice *self, const gchar *instance_id) { FuDevicePrivate *priv = GET_PRIVATE(self); for (guint i = 0; i < priv->instance_id_quirks->len; i++) { const gchar *instance_id_tmp = g_ptr_array_index(priv->instance_id_quirks, i); if (g_strcmp0(instance_id, instance_id_tmp) == 0) return TRUE; } return FALSE; } static void fu_device_add_instance_id_quirk(FuDevice *self, const gchar *instance_id) { FuDevicePrivate *priv = GET_PRIVATE(self); if (fu_device_has_instance_id(self, instance_id)) return; if (fu_device_has_instance_id_quirk(self, instance_id)) return; g_ptr_array_add(priv->instance_id_quirks, g_strdup(instance_id)); } /** * fu_device_add_instance_id_full: * @self: a #FuDevice * @instance_id: a instance ID, e.g. `WacomAES` * @flags: instance ID flags * * Adds an instance ID with all parameters set * * Since: 1.2.9 **/ void fu_device_add_instance_id_full(FuDevice *self, const gchar *instance_id, FuDeviceInstanceFlags flags) { FuDevicePrivate *priv = GET_PRIVATE(self); g_autofree gchar *guid = NULL; g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(instance_id != NULL); if (fwupd_guid_is_valid(instance_id)) { g_warning("use fu_device_add_guid(\"%s\") instead!", instance_id); fu_device_add_guid_safe(self, instance_id, flags); return; } /* it seems odd adding the instance ID and the GUID quirks and not just * calling fu_device_add_guid_safe() -- but we want the quirks to match * so the plugin is set, but not the LVFS metadata to match firmware * until we're sure the device isn't using _NO_AUTO_INSTANCE_IDS */ guid = fwupd_guid_hash_string(instance_id); if (flags & FU_DEVICE_INSTANCE_FLAG_QUIRKS) fu_device_add_guid_quirks(self, guid); if ((flags & FU_DEVICE_INSTANCE_FLAG_GENERIC) > 0 && fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_NO_GENERIC_GUIDS)) { flags &= ~FU_DEVICE_INSTANCE_FLAG_VISIBLE; } if (flags & FU_DEVICE_INSTANCE_FLAG_VISIBLE) fwupd_device_add_instance_id(FWUPD_DEVICE(self), instance_id); /* save this to make debugging easier, and also so we can incorporate */ if ((flags & FU_DEVICE_INSTANCE_FLAG_VISIBLE) == 0 && (flags & FU_DEVICE_INSTANCE_FLAG_QUIRKS) > 0) fu_device_add_instance_id_quirk(self, instance_id); /* already done by ->setup(), so this must be ->registered() */ if (priv->done_setup) fwupd_device_add_guid(FWUPD_DEVICE(self), guid); } /** * fu_device_add_instance_id: * @self: a #FuDevice * @instance_id: the instance ID, e.g. `PCI\VEN_10EC&DEV_525A` * * Adds an instance ID to the device. If the @instance_id argument is already a * valid GUID then fu_device_add_guid() should be used instead. * * Since: 1.2.5 **/ void fu_device_add_instance_id(FuDevice *self, const gchar *instance_id) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(instance_id != NULL); fu_device_add_instance_id_full(self, instance_id, FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS); } /** * fu_device_add_guid: * @self: a #FuDevice * @guid: a GUID, e.g. `2082b5e0-7a64-478a-b1b2-e3404fab6dad` * * Adds a GUID to the device. If the @guid argument is not a valid GUID then it * is converted to a GUID using fwupd_guid_hash_string(). * * Since: 0.7.2 **/ void fu_device_add_guid(FuDevice *self, const gchar *guid) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(guid != NULL); if (!fwupd_guid_is_valid(guid)) { fu_device_add_instance_id(self, guid); return; } fu_device_add_guid_safe(self, guid, FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS); } /** * fu_device_add_guid_full: * @self: a #FuDevice * @guid: a GUID, e.g. `2082b5e0-7a64-478a-b1b2-e3404fab6dad` * @flags: instance ID flags * * Adds a GUID to the device. If the @guid argument is not a valid GUID then it * is converted to a GUID using fwupd_guid_hash_string(). * * Since: 1.6.2 **/ void fu_device_add_guid_full(FuDevice *self, const gchar *guid, FuDeviceInstanceFlags flags) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(guid != NULL); if (!fwupd_guid_is_valid(guid)) { fu_device_add_instance_id_full(self, guid, flags); return; } fu_device_add_guid_safe(self, guid, flags); } /** * fu_device_add_counterpart_guid: * @self: a #FuDevice * @guid: a GUID, e.g. `2082b5e0-7a64-478a-b1b2-e3404fab6dad` * * Adds a GUID to the device. If the @guid argument is not a valid GUID then it * is converted to a GUID using fwupd_guid_hash_string(). * * A counterpart GUID is typically the GUID of the same device in bootloader * or runtime mode, if they have a different device PCI or USB ID. Adding this * type of GUID does not cause a "cascade" by matching using the quirk database. * * Since: 1.1.2 **/ void fu_device_add_counterpart_guid(FuDevice *self, const gchar *guid) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(guid != NULL); /* make valid */ if (!fwupd_guid_is_valid(guid)) { g_autofree gchar *tmp = fwupd_guid_hash_string(guid); fwupd_device_add_guid(FWUPD_DEVICE(self), tmp); return; } /* already valid */ fwupd_device_add_guid(FWUPD_DEVICE(self), guid); } /** * fu_device_get_metadata: * @self: a #FuDevice * @key: the key * * Gets an item of metadata from the device. * * Returns: a string value, or %NULL for unfound. * * Since: 0.1.0 **/ const gchar * fu_device_get_metadata(FuDevice *self, const gchar *key) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); g_return_val_if_fail(key != NULL, NULL); if (priv->metadata == NULL) return NULL; return g_hash_table_lookup(priv->metadata, key); } /** * fu_device_get_metadata_boolean: * @self: a #FuDevice * @key: the key * * Gets an item of metadata from the device. * * Returns: a boolean value, or %FALSE for unfound or failure to parse. * * Since: 0.9.7 **/ gboolean fu_device_get_metadata_boolean(FuDevice *self, const gchar *key) { FuDevicePrivate *priv = GET_PRIVATE(self); const gchar *tmp; g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(key != NULL, FALSE); if (priv->metadata == NULL) return FALSE; tmp = g_hash_table_lookup(priv->metadata, key); if (tmp == NULL) return FALSE; return g_strcmp0(tmp, "true") == 0; } /** * fu_device_get_metadata_integer: * @self: a #FuDevice * @key: the key * * Gets an item of metadata from the device. * * Returns: an integer value, or %G_MAXUINT for unfound or failure to parse. * * Since: 0.9.7 **/ guint fu_device_get_metadata_integer(FuDevice *self, const gchar *key) { FuDevicePrivate *priv = GET_PRIVATE(self); const gchar *tmp; gchar *endptr = NULL; guint64 val; g_return_val_if_fail(FU_IS_DEVICE(self), G_MAXUINT); g_return_val_if_fail(key != NULL, G_MAXUINT); if (priv->metadata == NULL) return G_MAXUINT; tmp = g_hash_table_lookup(priv->metadata, key); if (tmp == NULL) return G_MAXUINT; val = g_ascii_strtoull(tmp, &endptr, 10); if (endptr != NULL && endptr[0] != '\0') return G_MAXUINT; if (val > G_MAXUINT) return G_MAXUINT; return (guint)val; } /** * fu_device_remove_metadata: * @self: a #FuDevice * @key: the key * * Removes an item of metadata on the device. * * Since: 1.3.3 **/ void fu_device_remove_metadata(FuDevice *self, const gchar *key) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(key != NULL); if (priv->metadata == NULL) return; g_hash_table_remove(priv->metadata, key); } /** * fu_device_set_metadata: * @self: a #FuDevice * @key: the key * @value: the string value * * Sets an item of metadata on the device. * * Since: 0.1.0 **/ void fu_device_set_metadata(FuDevice *self, const gchar *key, const gchar *value) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(key != NULL); g_return_if_fail(value != NULL); if (priv->metadata == NULL) priv->metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_hash_table_insert(priv->metadata, g_strdup(key), g_strdup(value)); } /** * fu_device_set_metadata_boolean: * @self: a #FuDevice * @key: the key * @value: the boolean value * * Sets an item of metadata on the device. When @value is set to %TRUE * the actual stored value is `true`. * * Since: 0.9.7 **/ void fu_device_set_metadata_boolean(FuDevice *self, const gchar *key, gboolean value) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(key != NULL); fu_device_set_metadata(self, key, value ? "true" : "false"); } /** * fu_device_set_metadata_integer: * @self: a #FuDevice * @key: the key * @value: the unsigned integer value * * Sets an item of metadata on the device. The integer is stored as a * base-10 string internally. * * Since: 0.9.7 **/ void fu_device_set_metadata_integer(FuDevice *self, const gchar *key, guint value) { g_autofree gchar *tmp = g_strdup_printf("%u", value); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(key != NULL); fu_device_set_metadata(self, key, tmp); } /* ensure the name does not have the vendor name as the prefix */ static void fu_device_fixup_vendor_name(FuDevice *self) { const gchar *name = fu_device_get_name(self); const gchar *vendor = fu_device_get_vendor(self); if (name != NULL && vendor != NULL) { g_autofree gchar *name_up = g_utf8_strup(name, -1); g_autofree gchar *vendor_up = g_utf8_strup(vendor, -1); if (g_str_has_prefix(name_up, vendor_up)) { gsize vendor_len = strlen(vendor); g_autofree gchar *name1 = g_strdup(name + vendor_len); g_autofree gchar *name2 = fu_strstrip(name1); g_debug("removing vendor prefix of '%s' from '%s'", vendor, name); fwupd_device_set_name(FWUPD_DEVICE(self), name2); } } } /** * fu_device_set_vendor: * @self: a #FuDevice * @vendor: a device vendor * * Sets the vendor name on the device. * * Since: 1.6.2 **/ void fu_device_set_vendor(FuDevice *self, const gchar *vendor) { g_autofree gchar *vendor_safe = NULL; /* trim any leading and trailing spaces */ if (vendor != NULL) vendor_safe = fu_strstrip(vendor); /* proxy */ fwupd_device_set_vendor(FWUPD_DEVICE(self), vendor_safe); fu_device_fixup_vendor_name(self); } static gchar * fu_device_sanitize_name(const gchar *value) { gboolean last_was_space = FALSE; guint last_non_space = 0; g_autoptr(GString) new = g_string_new(NULL); /* add each printable char with maximum of one whitespace char */ for (guint i = 0; value[i] != '\0'; i++) { const gchar tmp = value[i]; if (!g_ascii_isprint(tmp)) continue; if (g_ascii_isspace(tmp) || tmp == '_') { if (new->len == 0) continue; if (last_was_space) continue; last_was_space = TRUE; g_string_append_c(new, ' '); } else { last_was_space = FALSE; g_string_append_c(new, tmp); last_non_space = new->len; } } g_string_truncate(new, last_non_space); g_string_replace(new, "(TM)", "™", 0); g_string_replace(new, "(R)", "", 0); if (new->len == 0) return NULL; return g_string_free(g_steal_pointer(&new), FALSE); } /** * fu_device_set_name: * @self: a #FuDevice * @value: a device name * * Sets the name on the device. Any invalid parts will be converted or removed. * * Since: 0.7.1 **/ void fu_device_set_name(FuDevice *self, const gchar *value) { g_autofree gchar *value_safe = NULL; g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(value != NULL); /* overwriting? */ value_safe = fu_device_sanitize_name(value); if (value_safe == NULL) { g_info("ignoring name value: '%s'", value); return; } if (g_strcmp0(value_safe, fu_device_get_name(self)) == 0) return; /* changing */ if (fu_device_get_name(self) != NULL) { const gchar *id = fu_device_get_id(self); g_debug("%s device overwriting name value: %s->%s", id != NULL ? id : "unknown", fu_device_get_name(self), value_safe); } fwupd_device_set_name(FWUPD_DEVICE(self), value_safe); fu_device_fixup_vendor_name(self); } /** * fu_device_set_id: * @self: a #FuDevice * @id: a string, e.g. `tbt-port1` * * Sets the ID on the device. The ID should represent the *connection* of the * device, so that any similar device plugged into a different slot will * have a different @id string. * * The @id will be converted to a SHA1 hash if required before the device is * added to the daemon, and plugins should not assume that the ID that is set * here is the same as what is returned by fu_device_get_id(). * * Since: 0.7.1 **/ void fu_device_set_id(FuDevice *self, const gchar *id) { FuDevicePrivate *priv = GET_PRIVATE(self); GPtrArray *children; g_autofree gchar *id_hash = NULL; g_autofree gchar *id_hash_old = g_strdup(fwupd_device_get_id(FWUPD_DEVICE(self))); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(id != NULL); /* allow sane device-id to be set directly */ if (fwupd_device_id_is_valid(id)) { id_hash = g_strdup(id); } else { id_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA1, id, -1); g_debug("using %s for %s", id_hash, id); } fwupd_device_set_id(FWUPD_DEVICE(self), id_hash); priv->device_id_valid = TRUE; /* ensure the parent ID is set */ children = fu_device_get_children(self); for (guint i = 0; i < children->len; i++) { FuDevice *devtmp = g_ptr_array_index(children, i); fwupd_device_set_parent_id(FWUPD_DEVICE(devtmp), id_hash); /* update the composite ID of the child with the new ID if required; this will * propagate to grandchildren and great-grandchildren as required */ if (id_hash_old != NULL && g_strcmp0(fu_device_get_composite_id(devtmp), id_hash_old) == 0) fu_device_set_composite_id(devtmp, id_hash); } } /** * fu_device_set_version_format: * @self: a #FuDevice * @fmt: the version format, e.g. %FWUPD_VERSION_FORMAT_PLAIN * * Sets the device version format. * * Since: 1.4.0 **/ void fu_device_set_version_format(FuDevice *self, FwupdVersionFormat fmt) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); /* same */ if (fu_device_get_version_format(self) == fmt) return; if (fu_device_get_version_format(self) != FWUPD_VERSION_FORMAT_UNKNOWN) { g_debug("changing verfmt for %s: %s->%s", fu_device_get_id(self), fwupd_version_format_to_string(fu_device_get_version_format(self)), fwupd_version_format_to_string(fmt)); } fwupd_device_set_version_format(FWUPD_DEVICE(self), fmt); /* convert this, now we know */ if (klass->convert_version != NULL && fu_device_get_version(self) != NULL && fu_device_get_version_raw(self) != 0) { g_autofree gchar *version = klass->convert_version(self, fu_device_get_version_raw(self)); fu_device_set_version(self, version); } } /** * fu_device_set_version: * @self: a #FuDevice * @version: (nullable): a string, e.g. `1.2.3` * * Sets the device version, sanitizing the string if required. * * Since: 1.2.9 **/ void fu_device_set_version(FuDevice *self, const gchar *version) { g_autofree gchar *version_safe = NULL; g_autoptr(GError) error = NULL; g_return_if_fail(FU_IS_DEVICE(self)); /* sanitize if required */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER)) { version_safe = fu_version_ensure_semver(version, fu_device_get_version_format(self)); if (g_strcmp0(version, version_safe) != 0) g_debug("converted '%s' to '%s'", version, version_safe); } else { version_safe = g_strdup(version); } /* print a console warning for an invalid version, if semver */ if (version_safe != NULL && !fu_version_verify_format(version_safe, fu_device_get_version_format(self), &error)) g_warning("%s", error->message); /* if different */ if (g_strcmp0(fu_device_get_version(self), version_safe) != 0) { if (fu_device_get_version(self) != NULL) { g_debug("changing version for %s: %s->%s", fu_device_get_id(self), fu_device_get_version(self), version_safe); } fwupd_device_set_version(FWUPD_DEVICE(self), version_safe); } } /** * fu_device_set_version_lowest: * @self: a #FuDevice * @version: (nullable): a string, e.g. `1.2.3` * * Sets the device lowest version, sanitizing the string if required. * * Since: 1.4.0 **/ void fu_device_set_version_lowest(FuDevice *self, const gchar *version) { g_autofree gchar *version_safe = NULL; g_autoptr(GError) error = NULL; g_return_if_fail(FU_IS_DEVICE(self)); /* sanitize if required */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER)) { version_safe = fu_version_ensure_semver(version, fu_device_get_version_format(self)); if (g_strcmp0(version, version_safe) != 0) g_debug("converted '%s' to '%s'", version, version_safe); } else { version_safe = g_strdup(version); } /* print a console warning for an invalid version, if semver */ if (version_safe != NULL && !fu_version_verify_format(version_safe, fu_device_get_version_format(self), &error)) g_warning("%s", error->message); /* if different */ if (g_strcmp0(fu_device_get_version_lowest(self), version_safe) != 0) { if (fu_device_get_version_lowest(self) != NULL) { g_debug("changing version lowest for %s: %s->%s", fu_device_get_id(self), fu_device_get_version_lowest(self), version_safe); } fwupd_device_set_version_lowest(FWUPD_DEVICE(self), version_safe); } } /** * fu_device_set_version_bootloader: * @self: a #FuDevice * @version: (nullable): a string, e.g. `1.2.3` * * Sets the device bootloader version, sanitizing the string if required. * * Since: 1.4.0 **/ void fu_device_set_version_bootloader(FuDevice *self, const gchar *version) { g_autofree gchar *version_safe = NULL; g_autoptr(GError) error = NULL; g_return_if_fail(FU_IS_DEVICE(self)); /* sanitize if required */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER)) { version_safe = fu_version_ensure_semver(version, fu_device_get_version_format(self)); if (g_strcmp0(version, version_safe) != 0) g_debug("converted '%s' to '%s'", version, version_safe); } else { version_safe = g_strdup(version); } /* print a console warning for an invalid version, if semver */ if (version_safe != NULL && !fu_version_verify_format(version_safe, fu_device_get_version_format(self), &error)) g_warning("%s", error->message); /* if different */ if (g_strcmp0(fu_device_get_version_bootloader(self), version_safe) != 0) { if (fu_device_get_version_bootloader(self) != NULL) { g_debug("changing version for %s: %s->%s", fu_device_get_id(self), fu_device_get_version_bootloader(self), version_safe); } fwupd_device_set_version_bootloader(FWUPD_DEVICE(self), version_safe); } } /** * fu_device_set_version_raw: * @self: a #FuDevice * @version_raw: an integer * * Sets the raw device version from a integer value and the device version format. * * Since: 1.9.8 **/ void fu_device_set_version_raw(FuDevice *self, guint64 version_raw) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_if_fail(FU_IS_DEVICE(self)); fwupd_device_set_version_raw(FWUPD_DEVICE(self), version_raw); if (klass->convert_version != NULL) { g_autofree gchar *version = klass->convert_version(self, version_raw); if (version != NULL) fu_device_set_version(self, version); } } /** * fu_device_set_version_u16: * @self: a #FuDevice * @version_raw: an integer * * Sets the device version from a integer value and the device version format. * * Since: 1.9.8 **/ void fu_device_set_version_u16(FuDevice *self, guint16 version_raw) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_if_fail(FU_IS_DEVICE(self)); fu_device_set_version_raw(self, version_raw); if (klass->convert_version == NULL) { g_autofree gchar *version = fu_version_from_uint16(version_raw, fu_device_get_version_format(self)); fwupd_device_set_version(FWUPD_DEVICE(self), version); } } /** * fu_device_set_version_u24: * @self: a #FuDevice * @version_raw: an integer * * Sets the device version from a integer value and the device version format. * * Since: 1.9.8 **/ void fu_device_set_version_u24(FuDevice *self, guint32 version_raw) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_if_fail(FU_IS_DEVICE(self)); fu_device_set_version_raw(self, version_raw); if (klass->convert_version == NULL) { g_autofree gchar *version = fu_version_from_uint24(version_raw, fu_device_get_version_format(self)); fwupd_device_set_version(FWUPD_DEVICE(self), version); } } /** * fu_device_set_version_u32: * @self: a #FuDevice * @version_raw: an integer * * Sets the device version from a integer value and the device version format. * * Since: 1.9.8 **/ void fu_device_set_version_u32(FuDevice *self, guint32 version_raw) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_if_fail(FU_IS_DEVICE(self)); fu_device_set_version_raw(self, version_raw); if (klass->convert_version == NULL) { g_autofree gchar *version = fu_version_from_uint32(version_raw, fu_device_get_version_format(self)); fwupd_device_set_version(FWUPD_DEVICE(self), version); } } /** * fu_device_set_version_u64: * @self: a #FuDevice * @version_raw: an integer * * Sets the device version from a integer value and the device version format. * * Since: 1.9.8 **/ void fu_device_set_version_u64(FuDevice *self, guint64 version_raw) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_if_fail(FU_IS_DEVICE(self)); fu_device_set_version_raw(self, version_raw); if (klass->convert_version == NULL) { g_autofree gchar *version = fu_version_from_uint64(version_raw, fu_device_get_version_format(self)); fwupd_device_set_version(FWUPD_DEVICE(self), version); } } static void fu_device_inhibit_free(FuDeviceInhibit *inhibit) { g_free(inhibit->inhibit_id); g_free(inhibit->reason); g_free(inhibit); } static void fu_device_ensure_inhibits(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); FwupdDeviceProblem problems = FWUPD_DEVICE_PROBLEM_NONE; guint nr_inhibits = g_hash_table_size(priv->inhibits); /* disable */ if (priv->notify_flags_handler_id != 0) g_signal_handler_block(self, priv->notify_flags_handler_id); /* was okay -> not okay */ if (nr_inhibits > 0) { g_autofree gchar *reasons_str = NULL; g_autoptr(GList) values = g_hash_table_get_values(priv->inhibits); g_autoptr(GPtrArray) reasons = g_ptr_array_new(); /* updatable -> updatable-hidden -- which is required as devices might have * inhibits and *not* be automatically updatable */ if (fu_device_has_flag(self, FWUPD_DEVICE_FLAG_UPDATABLE)) { fu_device_remove_flag(self, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(self, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN); } /* update the update error */ for (GList *l = values; l != NULL; l = l->next) { FuDeviceInhibit *inhibit = (FuDeviceInhibit *)l->data; g_ptr_array_add(reasons, inhibit->reason); problems |= inhibit->problem; } reasons_str = fu_strjoin(", ", reasons); fu_device_set_update_error(self, reasons_str); } else { if (fu_device_has_flag(self, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)) { fu_device_remove_flag(self, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN); fu_device_add_flag(self, FWUPD_DEVICE_FLAG_UPDATABLE); } fu_device_set_update_error(self, NULL); } /* sync with baseclass */ fwupd_device_set_problems(FWUPD_DEVICE(self), problems); /* enable */ if (priv->notify_flags_handler_id != 0) g_signal_handler_unblock(self, priv->notify_flags_handler_id); } static gchar * fu_device_problem_to_inhibit_reason(FuDevice *self, guint64 device_problem) { FuDevicePrivate *priv = GET_PRIVATE(self); if (device_problem == FWUPD_DEVICE_PROBLEM_UNREACHABLE) return g_strdup("Device is unreachable, or out of wireless range"); if (device_problem == FWUPD_DEVICE_PROBLEM_UPDATE_PENDING) return g_strdup("Device is waiting for the update to be applied"); if (device_problem == FWUPD_DEVICE_PROBLEM_REQUIRE_AC_POWER) return g_strdup("Device requires AC power to be connected"); if (device_problem == FWUPD_DEVICE_PROBLEM_LID_IS_CLOSED) return g_strdup("Device cannot be used while the lid is closed"); if (device_problem == FWUPD_DEVICE_PROBLEM_IS_EMULATED) return g_strdup("Device is emulated"); if (device_problem == FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS) return g_strdup("An update is in progress"); if (device_problem == FWUPD_DEVICE_PROBLEM_IN_USE) return g_strdup("Device is in use"); if (device_problem == FWUPD_DEVICE_PROBLEM_DISPLAY_REQUIRED) return g_strdup("Device requires a display to be plugged in"); if (device_problem == FWUPD_DEVICE_PROBLEM_MISSING_LICENSE) return g_strdup("Device does not have the necessary license installed"); if (device_problem == FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW) { if (priv->ctx == NULL) return g_strdup("System power is too low to perform the update"); return g_strdup_printf( "System power is too low to perform the update (%u%%, requires %u%%)", fu_context_get_battery_level(priv->ctx), fu_context_get_battery_threshold(priv->ctx)); } if (device_problem == FWUPD_DEVICE_PROBLEM_POWER_TOO_LOW) { if (fu_device_get_battery_level(self) == FWUPD_BATTERY_LEVEL_INVALID || fu_device_get_battery_threshold(self) == FWUPD_BATTERY_LEVEL_INVALID) { return g_strdup_printf("Device battery power is too low"); } return g_strdup_printf("Device battery power is too low (%u%%, requires %u%%)", fu_device_get_battery_level(self), fu_device_get_battery_threshold(self)); } return NULL; } static void fu_device_inhibit_full(FuDevice *self, FwupdDeviceProblem problem, const gchar *inhibit_id, const gchar *reason) { FuDevicePrivate *priv = GET_PRIVATE(self); FuDeviceInhibit *inhibit; g_return_if_fail(FU_IS_DEVICE(self)); /* lazy create as most devices will not need this */ if (priv->inhibits == NULL) { priv->inhibits = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)fu_device_inhibit_free); } /* can fallback */ if (inhibit_id == NULL) inhibit_id = fwupd_device_problem_to_string(problem); /* already exists */ inhibit = g_hash_table_lookup(priv->inhibits, inhibit_id); if (inhibit != NULL) return; /* create new */ inhibit = g_new0(FuDeviceInhibit, 1); inhibit->problem = problem; inhibit->inhibit_id = g_strdup(inhibit_id); if (reason != NULL) { inhibit->reason = g_strdup(reason); } else { inhibit->reason = fu_device_problem_to_inhibit_reason(self, problem); } g_hash_table_insert(priv->inhibits, inhibit->inhibit_id, inhibit); /* refresh */ fu_device_ensure_inhibits(self); /* propagate to children */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN)) { GPtrArray *children = fu_device_get_children(self); for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index(children, i); fu_device_inhibit(child, inhibit_id, reason); } } } /** * fu_device_inhibit: * @self: a #FuDevice * @inhibit_id: an ID used for uninhibiting, e.g. `low-power` * @reason: (nullable): a string, e.g. `Cannot update as foo [bar] needs reboot` * * Prevent the device from being updated, changing it from %FWUPD_DEVICE_FLAG_UPDATABLE * to %FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN if not already inhibited. * * If the device already has an inhibit with the same @inhibit_id then the request * is ignored. * * Since: 1.6.0 **/ void fu_device_inhibit(FuDevice *self, const gchar *inhibit_id, const gchar *reason) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(inhibit_id != NULL); fu_device_inhibit_full(self, FWUPD_DEVICE_PROBLEM_NONE, inhibit_id, reason); } /** * fu_device_has_inhibit: * @self: a #FuDevice * @inhibit_id: an ID used for inhibiting, e.g. `low-power` * * Check if the device already has an inhibit with a specific ID. * * Returns: %TRUE if added * * Since: 1.8.0 **/ gboolean fu_device_has_inhibit(FuDevice *self, const gchar *inhibit_id) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(inhibit_id != NULL, FALSE); if (priv->inhibits == NULL) return FALSE; return g_hash_table_contains(priv->inhibits, inhibit_id); } /** * fu_device_remove_problem: * @self: a #FuDevice * @problem: a #FwupdDeviceProblem, e.g. %FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW * * Allow the device from being updated if there are no other inhibitors, * changing it from %FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN to %FWUPD_DEVICE_FLAG_UPDATABLE. * * If the device already has no inhibit with the @inhibit_id then the request * is ignored. * * Since: 1.8.1 **/ void fu_device_remove_problem(FuDevice *self, FwupdDeviceProblem problem) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(problem != FWUPD_DEVICE_PROBLEM_UNKNOWN); return fu_device_uninhibit(self, fwupd_device_problem_to_string(problem)); } /** * fu_device_has_problem: * @self: a #FuDevice * @problem: a #FwupdDeviceProblem, e.g. %FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW * * Query if a device has a specific problem. * * Returns: %TRUE if the device has this problem * * Since: 1.8.11 **/ gboolean fu_device_has_problem(FuDevice *self, FwupdDeviceProblem problem) { g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(problem != FWUPD_DEVICE_PROBLEM_UNKNOWN, FALSE); return fu_device_has_inhibit(self, fwupd_device_problem_to_string(problem)); } /** * fu_device_add_problem: * @self: a #FuDevice * @problem: a #FwupdDeviceProblem, e.g. %FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW * * Prevent the device from being updated, changing it from %FWUPD_DEVICE_FLAG_UPDATABLE * to %FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN if not already inhibited. * * If the device already has an inhibit with the same @problem then the request * is ignored. * * Since: 1.8.1 **/ void fu_device_add_problem(FuDevice *self, FwupdDeviceProblem problem) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(problem != FWUPD_DEVICE_PROBLEM_UNKNOWN); fu_device_inhibit_full(self, problem, NULL, NULL); } /** * fu_device_uninhibit: * @self: a #FuDevice * @inhibit_id: an ID used for uninhibiting, e.g. `low-power` * * Allow the device from being updated if there are no other inhibitors, * changing it from %FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN to %FWUPD_DEVICE_FLAG_UPDATABLE. * * If the device already has no inhibit with the @inhibit_id then the request * is ignored. * * Since: 1.6.0 **/ void fu_device_uninhibit(FuDevice *self, const gchar *inhibit_id) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(inhibit_id != NULL); if (priv->inhibits == NULL) return; if (g_hash_table_remove(priv->inhibits, inhibit_id)) fu_device_ensure_inhibits(self); /* propagate to children */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN)) { GPtrArray *children = fu_device_get_children(self); for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index(children, i); fu_device_uninhibit(child, inhibit_id); } } } /** * fu_device_ensure_id: * @self: a #FuDevice * @error: (nullable): optional return location for an error * * If not already set, generates a device ID with the optional physical and * logical IDs. * * Returns: %TRUE on success * * Since: 1.1.2 **/ gboolean fu_device_ensure_id(FuDevice *self, GError **error) { FuDevicePrivate *priv = GET_PRIVATE(self); g_autofree gchar *device_id = NULL; g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* already set */ if (priv->device_id_valid) return TRUE; /* nothing we can do! */ if (priv->physical_id == NULL) { g_autofree gchar *tmp = fu_device_to_string(self); g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot ensure ID: %s", tmp); return FALSE; } /* logical may be NULL */ device_id = g_strjoin(":", fu_device_get_physical_id(self), fu_device_get_logical_id(self), NULL); fu_device_set_id(self, device_id); return TRUE; } /** * fu_device_get_logical_id: * @self: a #FuDevice * * Gets the logical ID set for the device, which disambiguates devices with the * same physical ID. * * Returns: a string value, or %NULL if never set. * * Since: 1.1.2 **/ const gchar * fu_device_get_logical_id(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return priv->logical_id; } /** * fu_device_set_logical_id: * @self: a #FuDevice * @logical_id: a string, e.g. `dev2` * * Sets the logical ID on the device. This is designed to disambiguate devices * with the same physical ID. * * Since: 1.1.2 **/ void fu_device_set_logical_id(FuDevice *self, const gchar *logical_id) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->logical_id, logical_id) == 0) return; /* not allowed after ->probe() and ->setup() have completed */ if (priv->done_setup) { g_warning("cannot change %s logical ID from %s to %s as " "FuDevice->setup() has already completed", fu_device_get_id(self), priv->logical_id, logical_id); return; } g_free(priv->logical_id); priv->logical_id = g_strdup(logical_id); priv->device_id_valid = FALSE; g_object_notify(G_OBJECT(self), "logical-id"); } /** * fu_device_get_backend_id: * @self: a #FuDevice * * Gets the ID set for the device as recognized by the backend. This is typically * a Linux sysfs path or USB platform ID. If unset, it also falls back to the * physical ID as this may be the same value. * * Returns: a string value, or %NULL if never set. * * Since: 1.5.8 **/ const gchar * fu_device_get_backend_id(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); if (priv->backend_id != NULL) return priv->backend_id; return priv->physical_id; } /** * fu_device_set_backend_id: * @self: a #FuDevice * @backend_id: a string, e.g. `dev2` * * Sets the backend ID on the device. This is designed to disambiguate devices * with the same physical ID. This is typically a Linux sysfs path or USB * platform ID. * * Since: 1.5.8 **/ void fu_device_set_backend_id(FuDevice *self, const gchar *backend_id) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->backend_id, backend_id) == 0) return; g_free(priv->backend_id); priv->backend_id = g_strdup(backend_id); priv->device_id_valid = FALSE; g_object_notify(G_OBJECT(self), "backend-id"); } /** * fu_device_get_update_request_id: * @self: a #FuDevice * * Gets the update request ID as specified from `LVFS::UpdateRequestId`. * * Returns: a string value, or %NULL if never set. * * Since: 1.8.6 **/ const gchar * fu_device_get_update_request_id(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return priv->update_request_id; } /** * fu_device_set_update_request_id: * @self: a #FuDevice * @update_request_id: a string, e.g. `org.freedesktop.fwupd.request.do-not-power-off` * * Sets the update request ID as specified in `LVFS::UpdateRequestId`. * * Since: 1.8.6 **/ void fu_device_set_update_request_id(FuDevice *self, const gchar *update_request_id) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->update_request_id, update_request_id) == 0) return; g_free(priv->update_request_id); priv->update_request_id = g_strdup(update_request_id); } /** * fu_device_get_proxy_guid: * @self: a #FuDevice * * Gets the proxy GUID device, which is set to let the engine match up the * proxy between plugins. * * Returns: a string value, or %NULL if never set. * * Since: 1.4.1 **/ const gchar * fu_device_get_proxy_guid(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return priv->proxy_guid; } /** * fu_device_set_proxy_guid: * @self: a #FuDevice * @proxy_guid: a string, e.g. `USB\VID_413C&PID_B06E&hub` * * Sets the GUID of the proxy device. The proxy device may update @self. * * Since: 1.4.1 **/ void fu_device_set_proxy_guid(FuDevice *self, const gchar *proxy_guid) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); /* not changed */ if (g_strcmp0(priv->proxy_guid, proxy_guid) == 0) return; g_free(priv->proxy_guid); priv->proxy_guid = g_strdup(proxy_guid); } /** * fu_device_set_physical_id: * @self: a #FuDevice * @physical_id: a string that identifies the physical device connection * * Sets the physical ID on the device which represents the electrical connection * of the device to the system. Multiple #FuDevices can share a physical ID. * * The physical ID is used to remove logical devices when a physical device has * been removed from the system. * * A sysfs or devpath is not a physical ID, but could be something like * `PCI_SLOT_NAME=0000:3e:00.0`. * * Since: 1.1.2 **/ void fu_device_set_physical_id(FuDevice *self, const gchar *physical_id) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(physical_id != NULL); /* not changed */ if (g_strcmp0(priv->physical_id, physical_id) == 0) return; /* not allowed after ->probe() and ->setup() have completed */ if (priv->done_setup) { g_warning("cannot change %s physical ID from %s to %s as " "FuDevice->setup() has already completed", fu_device_get_id(self), priv->physical_id, physical_id); return; } g_free(priv->physical_id); priv->physical_id = g_strdup(physical_id); priv->device_id_valid = FALSE; g_object_notify(G_OBJECT(self), "physical-id"); } /** * fu_device_get_physical_id: * @self: a #FuDevice * * Gets the physical ID set for the device, which represents the electrical * connection used to compare devices. * * Multiple #FuDevices can share a single physical ID. * * Returns: a string value, or %NULL if never set. * * Since: 1.1.2 **/ const gchar * fu_device_get_physical_id(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return priv->physical_id; } /** * fu_device_remove_flag: * @self: a #FuDevice * @flag: a device flag * * Removes a device flag from the device. * * Since: 1.6.0 **/ void fu_device_remove_flag(FuDevice *self, FwupdDeviceFlags flag) { /* proxy */ fwupd_device_remove_flag(FWUPD_DEVICE(self), flag); /* allow it to be updatable again */ if (flag & FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION) fu_device_uninhibit(self, "needs-activation"); if (flag & FWUPD_DEVICE_FLAG_UNREACHABLE) fu_device_uninhibit(self, "unreachable"); } /** * fu_device_add_flag: * @self: a #FuDevice * @flag: a device flag * * Adds a device flag to the device. * * Since: 0.1.0 **/ void fu_device_add_flag(FuDevice *self, FwupdDeviceFlags flag) { /* none is not used as an "exported" flag */ if (flag == FWUPD_DEVICE_FLAG_NONE) return; /* being both a bootloader and requiring a bootloader is invalid */ if (flag & FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER) fu_device_remove_flag(self, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); if (flag & FWUPD_DEVICE_FLAG_IS_BOOTLOADER) fu_device_remove_flag(self, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); /* being both a signed and unsigned is invalid */ if (flag & FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD) fu_device_remove_flag(self, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); if (flag & FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD) fu_device_remove_flag(self, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); /* one implies the other */ if (flag & FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE) flag |= FWUPD_DEVICE_FLAG_CAN_VERIFY; if (flag & FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES) flag |= FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED; fwupd_device_add_flag(FWUPD_DEVICE(self), flag); /* activatable devices shouldn't be allowed to update again until activated */ /* don't let devices be updated until activated */ if (flag & FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION) fu_device_inhibit(self, "needs-activation", "Pending activation"); /* do not let devices be updated until back in range */ if (flag & FWUPD_DEVICE_FLAG_UNREACHABLE) fu_device_add_problem(self, FWUPD_DEVICE_PROBLEM_UNREACHABLE); } static void fu_device_private_flag_item_free(FuDevicePrivateFlagItem *item) { g_free(item->value_str); g_free(item); } static FuDevicePrivateFlagItem * fu_device_private_flag_item_find_by_str(FuDevice *self, const gchar *value_str) { FuDevicePrivate *priv = GET_PRIVATE(self); if (priv->private_flag_items == NULL) return NULL; for (guint i = 0; i < priv->private_flag_items->len; i++) { FuDevicePrivateFlagItem *item = g_ptr_array_index(priv->private_flag_items, i); if (g_strcmp0(item->value_str, value_str) == 0) return item; } return NULL; } static FuDevicePrivateFlagItem * fu_device_private_flag_item_find_by_val(FuDevice *self, guint64 value) { FuDevicePrivate *priv = GET_PRIVATE(self); if (priv->private_flag_items == NULL) return NULL; for (guint i = 0; i < priv->private_flag_items->len; i++) { FuDevicePrivateFlagItem *item = g_ptr_array_index(priv->private_flag_items, i); if (item->value == value) return item; } return NULL; } /** * fu_device_register_private_flag: * @self: a #FuDevice * @value: an integer value * @value_str: a string that represents @value * * Registers a private device flag so that it can be set from quirk files and printed * correctly in debug output. * * Since: 1.6.2 **/ void fu_device_register_private_flag(FuDevice *self, guint64 value, const gchar *value_str) { FuDevicePrivateFlagItem *item; FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(value != 0); g_return_if_fail(value_str != NULL); #ifndef SUPPORTED_BUILD /* ensure not already the name of an internal or exported flag */ if (fwupd_device_flag_from_string(value_str) != FWUPD_DEVICE_FLAG_UNKNOWN) { g_critical("%s private flag %s already exists as an exported flag", G_OBJECT_TYPE_NAME(self), value_str); return; } if (fu_device_internal_flag_from_string(value_str) != FU_DEVICE_INTERNAL_FLAG_UNKNOWN) { g_critical("%s private flag %s already exists as an internal flag", G_OBJECT_TYPE_NAME(self), value_str); return; } #endif /* ensure exists */ if (priv->private_flag_items == NULL) { priv->private_flag_items = g_ptr_array_new_with_free_func( (GDestroyNotify)fu_device_private_flag_item_free); } /* sanity check */ item = fu_device_private_flag_item_find_by_val(self, value); if (item != NULL) { g_critical("already registered private %s flag with value: %s:0x%x", G_OBJECT_TYPE_NAME(self), value_str, (guint)value); return; } item = fu_device_private_flag_item_find_by_str(self, value_str); if (item != NULL) { g_critical("already registered private %s flag with string: %s:0x%x", G_OBJECT_TYPE_NAME(self), value_str, (guint)value); return; } /* add new */ item = g_new0(FuDevicePrivateFlagItem, 1); item->value = value; item->value_str = g_strdup(value_str); g_ptr_array_add(priv->private_flag_items, item); } static void fu_device_set_custom_flag(FuDevice *self, const gchar *hint) { FwupdDeviceFlags flag; FuDevicePrivateFlagItem *item; FuDeviceInternalFlags internal_flag; g_return_if_fail(hint != NULL); /* is this a negated device flag */ if (g_str_has_prefix(hint, "~")) { flag = fwupd_device_flag_from_string(hint + 1); if (flag != FWUPD_DEVICE_FLAG_UNKNOWN) { fu_device_remove_flag(self, flag); return; } internal_flag = fu_device_internal_flag_from_string(hint + 1); if (internal_flag != FU_DEVICE_INTERNAL_FLAG_UNKNOWN) { fu_device_remove_internal_flag(self, internal_flag); return; } item = fu_device_private_flag_item_find_by_str(self, hint + 1); if (item != NULL) { fu_device_remove_private_flag(self, item->value); return; } return; } /* is this a known device flag */ flag = fwupd_device_flag_from_string(hint); if (flag != FWUPD_DEVICE_FLAG_UNKNOWN) { fu_device_add_flag(self, flag); return; } internal_flag = fu_device_internal_flag_from_string(hint); if (internal_flag != FU_DEVICE_INTERNAL_FLAG_UNKNOWN) { fu_device_add_internal_flag(self, internal_flag); return; } item = fu_device_private_flag_item_find_by_str(self, hint); if (item != NULL) { fu_device_add_private_flag(self, item->value); return; } } /** * fu_device_set_custom_flags: * @self: a #FuDevice * @custom_flags: a string * * Sets the custom flags from the quirk system that can be used to * affect device matching. The actual string format is defined by the plugin. * * Since: 1.1.0 **/ void fu_device_set_custom_flags(FuDevice *self, const gchar *custom_flags) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(custom_flags != NULL); /* save what was set so we can use it for incorporating a superclass */ g_free(priv->custom_flags); priv->custom_flags = g_strdup(custom_flags); /* look for any standard FwupdDeviceFlags */ if (custom_flags != NULL) { g_auto(GStrv) hints = g_strsplit(custom_flags, ",", -1); for (guint i = 0; hints[i] != NULL; i++) fu_device_set_custom_flag(self, hints[i]); } } /** * fu_device_get_custom_flags: * @self: a #FuDevice * * Gets the custom flags for the device from the quirk system. * * Returns: a string value, or %NULL if never set. * * Since: 1.1.0 **/ const gchar * fu_device_get_custom_flags(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return priv->custom_flags; } /** * fu_device_get_remove_delay: * @self: a #FuDevice * * Returns the maximum delay expected when replugging the device going into * bootloader mode. * * Returns: time in milliseconds * * Since: 1.0.2 **/ guint fu_device_get_remove_delay(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), 0); return priv->remove_delay; } /** * fu_device_set_remove_delay: * @self: a #FuDevice * @remove_delay: the value in milliseconds * * Sets the amount of time a device is allowed to return in bootloader mode. * * NOTE: this should be less than 3000ms for devices that just have to reset * and automatically re-enumerate, but significantly longer if it involves a * user removing a cable, pressing several buttons and removing a cable. * A suggested value for this would be 10,000ms. * * Since: 1.0.2 **/ void fu_device_set_remove_delay(FuDevice *self, guint remove_delay) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); priv->remove_delay = remove_delay; } /** * fu_device_get_acquiesce_delay: * @self: a #FuDevice * * Returns the time the daemon should wait for devices to finish hotplugging * after the update has completed. * * Returns: time in milliseconds * * Since: 1.8.3 **/ guint fu_device_get_acquiesce_delay(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), 0); return priv->acquiesce_delay; } /** * fu_device_set_acquiesce_delay: * @self: a #FuDevice * @acquiesce_delay: the value in milliseconds * * Sets the time the daemon should wait for devices to finish hotplugging * after the update has completed. * * Devices subclassing from [class@FuUsbDevice] and [class@FuUdevDevice] use * a value of 2,500ms, and other devices use 50ms by default. This can be also * be set using `AcquiesceDelay=` in a quirk file. * * Since: 1.8.3 **/ void fu_device_set_acquiesce_delay(FuDevice *self, guint acquiesce_delay) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); priv->acquiesce_delay = acquiesce_delay; } /** * fu_device_set_update_state: * @self: a #FuDevice * @update_state: the state, e.g. %FWUPD_UPDATE_STATE_PENDING * * Sets the update state, clearing the update error as required. * * Since: 1.6.2 **/ void fu_device_set_update_state(FuDevice *self, FwupdUpdateState update_state) { g_return_if_fail(FU_IS_DEVICE(self)); if (update_state == FWUPD_UPDATE_STATE_SUCCESS || update_state == FWUPD_UPDATE_STATE_PENDING || update_state == FWUPD_UPDATE_STATE_NEEDS_REBOOT) fu_device_set_update_error(self, NULL); fwupd_device_set_update_state(FWUPD_DEVICE(self), update_state); } static void fu_device_ensure_battery_inhibit(FuDevice *self) { if (fu_device_get_battery_level(self) == FWUPD_BATTERY_LEVEL_INVALID || fu_device_get_battery_level(self) >= fu_device_get_battery_threshold(self)) { fu_device_remove_problem(self, FWUPD_DEVICE_PROBLEM_POWER_TOO_LOW); return; } fu_device_add_problem(self, FWUPD_DEVICE_PROBLEM_POWER_TOO_LOW); } /** * fu_device_get_battery_level: * @self: a #FuDevice * * Returns the battery level. * * Returns: value in percent * * Since: 1.5.8 **/ guint fu_device_get_battery_level(FuDevice *self) { g_return_val_if_fail(FU_IS_DEVICE(self), G_MAXUINT); /* use the parent if the child is unset */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_BATTERY) && fwupd_device_get_battery_level(FWUPD_DEVICE(self)) == FWUPD_BATTERY_LEVEL_INVALID) { FuDevice *parent = fu_device_get_parent(self); if (parent != NULL) return fu_device_get_battery_level(parent); } return fwupd_device_get_battery_level(FWUPD_DEVICE(self)); } /** * fu_device_set_battery_level: * @self: a #FuDevice * @battery_level: the percentage value * * Sets the battery level, or %FWUPD_BATTERY_LEVEL_INVALID. * * Setting this allows fwupd to show a warning if the device change is too low * to perform the update. * * Since: 1.5.8 **/ void fu_device_set_battery_level(FuDevice *self, guint battery_level) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(battery_level <= FWUPD_BATTERY_LEVEL_INVALID); fwupd_device_set_battery_level(FWUPD_DEVICE(self), battery_level); fu_device_ensure_battery_inhibit(self); } /** * fu_device_get_battery_threshold: * @self: a #FuDevice * * Returns the battery threshold under which a firmware update cannot be * performed. * * If fu_device_set_battery_threshold() has not been used, a default value is * used instead. * * Returns: value in percent * * Since: 1.6.0 **/ guint fu_device_get_battery_threshold(FuDevice *self) { g_return_val_if_fail(FU_IS_DEVICE(self), FWUPD_BATTERY_LEVEL_INVALID); /* use the parent if the child is unset */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_BATTERY) && fwupd_device_get_battery_threshold(FWUPD_DEVICE(self)) == FWUPD_BATTERY_LEVEL_INVALID) { FuDevice *parent = fu_device_get_parent(self); if (parent != NULL) return fu_device_get_battery_threshold(parent); } return fwupd_device_get_battery_threshold(FWUPD_DEVICE(self)); } /** * fu_device_set_battery_threshold: * @self: a #FuDevice * @battery_threshold: the percentage value * * Sets the battery level, or %FWUPD_BATTERY_LEVEL_INVALID for the default. * * Setting this allows fwupd to show a warning if the device change is too low * to perform the update. * * Since: 1.6.0 **/ void fu_device_set_battery_threshold(FuDevice *self, guint battery_threshold) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(battery_threshold <= FWUPD_BATTERY_LEVEL_INVALID); fwupd_device_set_battery_threshold(FWUPD_DEVICE(self), battery_threshold); fu_device_ensure_battery_inhibit(self); } /** * fu_device_add_string: * @self: a #FuDevice * @idt: indent level * @str: a string to append to * * Add daemon-specific device metadata to an existing string. * * Since: 1.7.1 **/ void fu_device_add_string(FuDevice *self, guint idt, GString *str) { GPtrArray *children; FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); FuDevicePrivate *priv = GET_PRIVATE(self); g_autofree gchar *tmp = NULL; tmp = fwupd_device_to_string(FWUPD_DEVICE(self)); if (tmp != NULL && tmp[0] != '\0') g_string_append(str, tmp); for (guint i = 0; i < priv->instance_id_quirks->len; i++) { const gchar *instance_id = g_ptr_array_index(priv->instance_id_quirks, i); g_autofree gchar *guid = fwupd_guid_hash_string(instance_id); g_autofree gchar *tmp2 = g_strdup_printf("%s ← %s", guid, instance_id); fu_string_append(str, idt + 1, "Guid[quirk]", tmp2); } if (priv->alternate_id != NULL) fu_string_append(str, idt + 1, "AlternateId", priv->alternate_id); if (priv->equivalent_id != NULL) fu_string_append(str, idt + 1, "EquivalentId", priv->equivalent_id); if (priv->physical_id != NULL) fu_string_append(str, idt + 1, "PhysicalId", priv->physical_id); if (priv->logical_id != NULL) fu_string_append(str, idt + 1, "LogicalId", priv->logical_id); if (priv->backend_id != NULL) fu_string_append(str, idt + 1, "BackendId", priv->backend_id); if (priv->update_request_id != NULL) fu_string_append(str, idt + 1, "UpdateRequestId", priv->update_request_id); if (priv->proxy != NULL) fu_string_append(str, idt + 1, "ProxyId", fu_device_get_id(priv->proxy)); if (priv->proxy_guid != NULL) fu_string_append(str, idt + 1, "ProxyGuid", priv->proxy_guid); if (priv->remove_delay != 0) fu_string_append_ku(str, idt + 1, "RemoveDelay", priv->remove_delay); if (priv->acquiesce_delay != 0) fu_string_append_ku(str, idt + 1, "AcquiesceDelay", priv->acquiesce_delay); if (priv->custom_flags != NULL) fu_string_append(str, idt + 1, "CustomFlags", priv->custom_flags); if (priv->firmware_gtype != G_TYPE_INVALID) { fu_string_append(str, idt + 1, "FirmwareGType", g_type_name(priv->firmware_gtype)); } if (priv->specialized_gtype != G_TYPE_INVALID) fu_string_append(str, idt + 1, "GType", g_type_name(priv->specialized_gtype)); if (priv->proxy_gtype != G_TYPE_INVALID) fu_string_append(str, idt + 1, "ProxyGType", g_type_name(priv->proxy_gtype)); if (priv->firmware_gtype != G_TYPE_INVALID) fu_string_append(str, idt + 1, "FirmwareGType", g_type_name(priv->firmware_gtype)); if (priv->size_min > 0) { g_autofree gchar *sz = g_strdup_printf("%" G_GUINT64_FORMAT, priv->size_min); fu_string_append(str, idt + 1, "FirmwareSizeMin", sz); } if (priv->size_max > 0) { g_autofree gchar *sz = g_strdup_printf("%" G_GUINT64_FORMAT, priv->size_max); fu_string_append(str, idt + 1, "FirmwareSizeMax", sz); } if (priv->order != G_MAXINT) { g_autofree gchar *order = g_strdup_printf("%i", priv->order); fu_string_append(str, idt + 1, "Order", order); } if (priv->priority > 0) fu_string_append_ku(str, idt + 1, "Priority", priv->priority); if (priv->metadata != NULL) { g_autoptr(GList) keys = g_hash_table_get_keys(priv->metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup(priv->metadata, key); fu_string_append(str, idt + 1, key, value); } } for (guint i = 0; i < priv->possible_plugins->len; i++) { const gchar *name = g_ptr_array_index(priv->possible_plugins, i); fu_string_append(str, idt + 1, "PossiblePlugin", name); } if (priv->parent_physical_ids != NULL && priv->parent_physical_ids->len > 0) { g_autofree gchar *flags = fu_strjoin(",", priv->parent_physical_ids); fu_string_append(str, idt + 1, "ParentPhysicalIds", flags); } if (priv->parent_backend_ids != NULL && priv->parent_backend_ids->len > 0) { g_autofree gchar *flags = fu_strjoin(",", priv->parent_backend_ids); fu_string_append(str, idt + 1, "ParentBackendIds", flags); } if (priv->internal_flags != FU_DEVICE_INTERNAL_FLAG_NONE) { g_autoptr(GString) tmp2 = g_string_new(""); for (guint i = 0; i < 64; i++) { if ((priv->internal_flags & ((guint64)1 << i)) == 0) continue; g_string_append_printf(tmp2, "%s|", fu_device_internal_flag_to_string((guint64)1 << i)); } if (tmp2->len > 0) g_string_truncate(tmp2, tmp2->len - 1); fu_string_append(str, idt + 1, "InternalFlags", tmp2->str); } if (priv->private_flags > 0) { g_autoptr(GPtrArray) tmpv = g_ptr_array_new(); g_autofree gchar *tmps = NULL; for (guint64 i = 0; i < 64; i++) { FuDevicePrivateFlagItem *item; guint64 value = 1ull << i; if ((priv->private_flags & value) == 0) continue; item = fu_device_private_flag_item_find_by_val(self, value); if (item == NULL) continue; g_ptr_array_add(tmpv, item->value_str); } tmps = fu_strjoin(",", tmpv); fu_string_append(str, idt + 1, "PrivateFlags", tmps); } if (priv->inhibits != NULL) { g_autoptr(GList) values = g_hash_table_get_values(priv->inhibits); for (GList *l = values; l != NULL; l = l->next) { FuDeviceInhibit *inhibit = (FuDeviceInhibit *)l->data; g_autofree gchar *val = g_strdup_printf("[%s] %s", inhibit->inhibit_id, inhibit->reason); fu_string_append(str, idt + 1, "Inhibit", val); } } /* subclassed */ if (klass->to_string != NULL) klass->to_string(self, idt + 1, str); /* print children also */ children = fu_device_get_children(self); for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index(children, i); fu_device_add_string(child, idt + 1, str); } } /** * fu_device_to_string: * @self: a #FuDevice * * This allows us to easily print the device, the release and the * daemon-specific metadata. * * Returns: a string value, or %NULL for invalid. * * Since: 0.9.8 **/ gchar * fu_device_to_string(FuDevice *self) { GString *str = g_string_new(NULL); fu_device_add_string(self, 0, str); return g_string_free(str, FALSE); } /** * fu_device_set_context: * @self: a #FuDevice * @ctx: (nullable): optional #FuContext * * Sets the optional context which may be useful to this device. * This is typically set after the device has been created, but before * the device has been opened or probed. * * Since: 1.6.0 **/ void fu_device_set_context(FuDevice *self, FuContext *ctx) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(FU_IS_CONTEXT(ctx) || ctx == NULL); #ifndef SUPPORTED_BUILD if (priv->ctx != NULL && ctx == NULL) { g_critical("clearing device context for %s [%s]", fu_device_get_name(self), fu_device_get_id(self)); return; } #endif if (g_set_object(&priv->ctx, ctx)) g_object_notify(G_OBJECT(self), "context"); } /** * fu_device_get_context: * @self: a #FuDevice * * Gets the context assigned for this device. * * Returns: (transfer none): the #FuContext object, or %NULL * * Since: 1.6.0 **/ FuContext * fu_device_get_context(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); return priv->ctx; } /** * fu_device_get_results: * @self: a #FuDevice * @error: (nullable): optional return location for an error * * Gets the results of the last update operation on the device by calling a vfunc. * * Returns: %TRUE on success * * Since: 1.6.2 **/ gboolean fu_device_get_results(FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* no plugin-specific method */ if (klass->get_results == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "getting results not supported by device"); return FALSE; } /* call vfunc */ return klass->get_results(self, error); } /** * fu_device_write_firmware: * @self: a #FuDevice * @fw: firmware blob * @progress: a #FuProgress * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: (nullable): optional return location for an error * * Writes firmware to the device by calling a plugin-specific vfunc. * * Returns: %TRUE on success * * Since: 1.0.8 **/ gboolean fu_device_write_firmware(FuDevice *self, GBytes *fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); FuDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(FuFirmware) firmware = NULL; g_autofree gchar *str = NULL; g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* no plugin-specific method */ if (klass->write_firmware == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "writing firmware not supported by device"); return FALSE; } /* prepare (e.g. decompress) firmware */ fu_progress_set_status(progress, FWUPD_STATUS_DECOMPRESSING); firmware = fu_device_prepare_firmware(self, fw, flags, error); if (firmware == NULL) return FALSE; str = fu_firmware_to_string(firmware); g_info("installing onto %s:\n%s", fu_device_get_id(self), str); /* call vfunc */ g_set_object(&priv->progress, progress); if (!klass->write_firmware(self, firmware, progress, flags, error)) return FALSE; /* the device set an UpdateMessage (possibly from a quirk, or XML file) * but did not do an event; guess something */ if (priv->request_cnts[FWUPD_REQUEST_KIND_POST] == 0 && fu_device_get_update_message(self) != NULL) { const gchar *update_request_id = fu_device_get_update_request_id(self); g_autoptr(FwupdRequest) request = fwupd_request_new(); fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_POST); if (update_request_id != NULL) { fwupd_request_set_id(request, update_request_id); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); } else { fu_device_add_request_flag(self, FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG); } fwupd_request_set_message(request, fu_device_get_update_message(self)); fwupd_request_set_image(request, fu_device_get_update_image(self)); if (!fu_device_emit_request(self, request, progress, error)) return FALSE; } /* success */ return TRUE; } /** * fu_device_prepare_firmware: * @self: a #FuDevice * @fw: firmware blob * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: (nullable): optional return location for an error * * Prepares the firmware by calling an optional device-specific vfunc for the * device, which can do things like decompressing or parsing of the firmware * data. * * For all firmware, this checks the size of the firmware if limits have been * set using fu_device_set_firmware_size_min(), fu_device_set_firmware_size_max() * or using a quirk entry. * * Returns: (transfer full): a new #GBytes, or %NULL for error * * Since: 1.1.2 **/ FuFirmware * fu_device_prepare_firmware(FuDevice *self, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); FuDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(FuFirmware) firmware = NULL; g_autoptr(GBytes) fw_def = NULL; g_return_val_if_fail(FU_IS_DEVICE(self), NULL); g_return_val_if_fail(fw != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* optionally subclassed */ if (klass->prepare_firmware != NULL) { firmware = klass->prepare_firmware(self, fw, flags, error); if (firmware == NULL) return NULL; } else if (priv->firmware_gtype != G_TYPE_INVALID) { firmware = g_object_new(priv->firmware_gtype, NULL); if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; } else { firmware = fu_firmware_new_from_bytes(fw); } /* check size */ fw_def = fu_firmware_get_bytes(firmware, NULL); if (fw_def != NULL) { guint64 fw_sz = (guint64)g_bytes_get_size(fw_def); if (priv->size_max > 0 && fw_sz > priv->size_max) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware is 0x%04x bytes larger than the allowed " "maximum size of 0x%04x bytes", (guint)(fw_sz - priv->size_max), (guint)priv->size_max); return NULL; } if (priv->size_min > 0 && fw_sz < priv->size_min) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware is %04x bytes smaller than the allowed " "minimum size of %04x bytes", (guint)(priv->size_min - fw_sz), (guint)priv->size_max); return NULL; } } /* success */ return g_steal_pointer(&firmware); } /** * fu_device_read_firmware: * @self: a #FuDevice * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Reads firmware from the device by calling a plugin-specific vfunc. * The device subclass should try to ensure the firmware does not contain any * serial numbers or user-configuration values and can be used to calculate the * device checksum. * * The return value can be converted to a blob of memory using fu_firmware_write(). * * Returns: (transfer full): a #FuFirmware, or %NULL for error * * Since: 1.0.8 **/ FuFirmware * fu_device_read_firmware(FuDevice *self, FuProgress *progress, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); FuDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GBytes) fw = NULL; g_return_val_if_fail(FU_IS_DEVICE(self), NULL); g_return_val_if_fail(FU_IS_PROGRESS(progress), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* device does not support reading for verification CRCs */ if (!fu_device_has_flag(self, FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "reading firmware is not supported by device"); return NULL; } /* call vfunc */ g_set_object(&priv->progress, progress); if (klass->read_firmware != NULL) return klass->read_firmware(self, progress, error); /* use the default FuFirmware when only ->dump_firmware is provided */ fw = fu_device_dump_firmware(self, progress, error); if (fw == NULL) return NULL; return fu_firmware_new_from_bytes(fw); } /** * fu_device_dump_firmware: * @self: a #FuDevice * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Reads the raw firmware image from the device by calling a plugin-specific * vfunc. This raw firmware image may contain serial numbers or device-specific * configuration but should be a byte-for-byte match compared to using an * external SPI programmer. * * Returns: (transfer full): a #GBytes, or %NULL for error * * Since: 1.5.0 **/ GBytes * fu_device_dump_firmware(FuDevice *self, FuProgress *progress, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); g_return_val_if_fail(FU_IS_PROGRESS(progress), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* use the default FuFirmware when only ->dump_firmware is provided */ if (klass->dump_firmware == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "dumping firmware is not supported by device"); return NULL; } /* proxy */ g_set_object(&priv->progress, progress); return klass->dump_firmware(self, progress, error); } /** * fu_device_detach: * @self: a #FuDevice * @error: (nullable): optional return location for an error * * Detaches a device from the application into bootloader mode. * * Returns: %TRUE on success * * Since: 1.0.8 **/ gboolean fu_device_detach(FuDevice *self, GError **error) { g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); return fu_device_detach_full(self, progress, error); } /** * fu_device_detach_full: * @self: a #FuDevice * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Detaches a device from the application into bootloader mode. * * Returns: %TRUE on success * * Since: 1.7.0 **/ gboolean fu_device_detach_full(FuDevice *self, FuProgress *progress, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* no plugin-specific method */ if (klass->detach == NULL) return TRUE; /* call vfunc */ g_set_object(&priv->progress, progress); return klass->detach(self, progress, error); } /** * fu_device_attach: * @self: a #FuDevice * @error: (nullable): optional return location for an error * * Attaches a device from the bootloader into application mode. * * Returns: %TRUE on success * * Since: 1.0.8 **/ gboolean fu_device_attach(FuDevice *self, GError **error) { g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); return fu_device_attach_full(self, progress, error); } /** * fu_device_attach_full: * @self: a #FuDevice * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Attaches a device from the bootloader into application mode. * * Returns: %TRUE on success * * Since: 1.7.0 **/ gboolean fu_device_attach_full(FuDevice *self, FuProgress *progress, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* no plugin-specific method */ if (klass->attach == NULL) return TRUE; /* call vfunc */ g_set_object(&priv->progress, progress); return klass->attach(self, progress, error); } /** * fu_device_reload: * @self: a #FuDevice * @error: (nullable): optional return location for an error * * Reloads a device that has just gone from bootloader into application mode. * * Returns: %TRUE on success * * Since: 1.3.3 **/ gboolean fu_device_reload(FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* no plugin-specific method */ if (klass->reload == NULL) return TRUE; /* call vfunc */ return klass->reload(self, error); } /** * fu_device_prepare: * @self: a #FuDevice * @progress: a #FuProgress * @flags: install flags * @error: (nullable): optional return location for an error * * Prepares a device for update. A different plugin can handle each of * FuDevice->prepare(), FuDevice->detach() and FuDevice->write_firmware(). * * Returns: %TRUE on success * * Since: 1.3.3 **/ gboolean fu_device_prepare(FuDevice *self, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* no plugin-specific method */ if (klass->prepare == NULL) return TRUE; /* call vfunc */ g_set_object(&priv->progress, progress); return klass->prepare(self, progress, flags, error); } /** * fu_device_cleanup: * @self: a #FuDevice * @progress: a #FuProgress * @flags: install flags * @error: (nullable): optional return location for an error * * Cleans up a device after an update. A different plugin can handle each of * FuDevice->write_firmware(), FuDevice->attach() and FuDevice->cleanup(). * * Returns: %TRUE on success * * Since: 1.3.3 **/ gboolean fu_device_cleanup(FuDevice *self, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* no plugin-specific method */ if (klass->cleanup == NULL) return TRUE; /* call vfunc */ g_set_object(&priv->progress, progress); return klass->cleanup(self, progress, flags, error); } static gboolean fu_device_open_cb(FuDevice *self, gpointer user_data, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); return klass->open(self, error); } static gboolean fu_device_open_internal(FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); FuDevicePrivate *priv = GET_PRIVATE(self); /* already open */ g_atomic_int_inc(&priv->open_refcount); if (priv->open_refcount > 1) return TRUE; /* probe */ if (!fu_device_probe(self, error)) return FALSE; /* ensure the device ID is already setup */ if (!fu_device_ensure_id(self, error)) return FALSE; /* subclassed */ if (klass->open != NULL) { if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_RETRY_OPEN)) { if (!fu_device_retry_full(self, fu_device_open_cb, FU_DEVICE_RETRY_OPEN_COUNT, FU_DEVICE_RETRY_OPEN_DELAY, NULL, error)) return FALSE; } else { if (!klass->open(self, error)) return FALSE; } } /* setup */ if (!fu_device_setup(self, error)) return FALSE; /* ensure the device ID is still valid */ if (!fu_device_ensure_id(self, error)) return FALSE; /* success */ fu_device_add_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_IS_OPEN); return TRUE; } /** * fu_device_open: * @self: a #FuDevice * @error: (nullable): optional return location for an error * * Opens a device, optionally running a object-specific vfunc. * * Plugins can call fu_device_open() multiple times without calling * fu_device_close(), but only the first call will actually invoke the vfunc. * * It is expected that plugins issue the same number of fu_device_open() and * fu_device_close() methods when using a specific @self. * * If the `->probe()`, `->open()` and `->setup()` actions all complete * successfully the internal device flag %FU_DEVICE_INTERNAL_FLAG_IS_OPEN will * be set. * * NOTE: It is important to still call fu_device_close() even if this function * fails as the device may still be partially initialized. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_open(FuDevice *self, GError **error) { g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* use parent */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN)) { FuDevice *parent = fu_device_get_parent(self); if (parent == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no parent device"); return FALSE; } return fu_device_open_internal(parent, error); } if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FOR_OPEN)) { FuDevice *proxy = fu_device_get_proxy(self); if (proxy == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no proxy device"); return FALSE; } if (!fu_device_open_internal(proxy, error)) return FALSE; } return fu_device_open_internal(self, error); } static gboolean fu_device_close_internal(FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); FuDevicePrivate *priv = GET_PRIVATE(self); /* not yet open */ if (priv->open_refcount == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "cannot close device, refcount already zero"); return FALSE; } if (!g_atomic_int_dec_and_test(&priv->open_refcount)) return TRUE; /* subclassed */ if (klass->close != NULL) { if (!klass->close(self, error)) return FALSE; } /* success */ fu_device_remove_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_IS_OPEN); return TRUE; } /** * fu_device_close: * @self: a #FuDevice * @error: (nullable): optional return location for an error * * Closes a device, optionally running a object-specific vfunc. * * Plugins can call fu_device_close() multiple times without calling * fu_device_open(), but only the last call will actually invoke the vfunc. * * It is expected that plugins issue the same number of fu_device_open() and * fu_device_close() methods when using a specific @self. * * An error is returned if this method is called without having used the * fu_device_open() method beforehand. * * If the close action completed successfully the internal device flag * %FU_DEVICE_INTERNAL_FLAG_IS_OPEN will be cleared. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_close(FuDevice *self, GError **error) { g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* use parent */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN)) { FuDevice *parent = fu_device_get_parent(self); if (parent == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no parent device"); return FALSE; } return fu_device_close_internal(parent, error); } if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FOR_OPEN)) { FuDevice *proxy = fu_device_get_proxy(self); if (proxy == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no proxy device"); return FALSE; } if (!fu_device_close_internal(proxy, error)) return FALSE; } return fu_device_close_internal(self, error); } /** * fu_device_probe: * @self: a #FuDevice * @error: (nullable): optional return location for an error * * Probes a device, setting parameters on the object that does not need * the device open or the interface claimed. * If the device is not compatible then an error should be returned. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_probe(FuDevice *self, GError **error) { FuDevicePrivate *priv = GET_PRIVATE(self); FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* already done */ if (priv->done_probe) return TRUE; /* device self-assigned */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_NO_PROBE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not probing"); return FALSE; } /* subclassed */ if (klass->probe != NULL) { if (!klass->probe(self, error)) return FALSE; } /* vfunc skipped device */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_NO_PROBE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not probing"); return FALSE; } /* success */ priv->done_probe = TRUE; return TRUE; } /** * fu_device_probe_complete: * @self: a #FuDevice * * Tell the device that all probing has finished. This allows it to release any resources that are * only valid during coldplug or hotplug. * * Since: 1.8.12 **/ void fu_device_probe_complete(FuDevice *self) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_if_fail(FU_IS_DEVICE(self)); if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_NO_PROBE_COMPLETE)) return; if (klass->probe_complete != NULL) klass->probe_complete(self); } /** * fu_device_rescan: * @self: a #FuDevice * @error: (nullable): optional return location for an error * * Rescans a device, re-adding GUIDs or flags based on some hardware change. * * Returns: %TRUE for success * * Since: 1.3.1 **/ gboolean fu_device_rescan(FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* remove all GUIDs */ g_ptr_array_set_size(fu_device_get_instance_ids(self), 0); g_ptr_array_set_size(fu_device_get_guids(self), 0); /* subclassed */ if (klass->rescan != NULL) { if (!klass->rescan(self, error)) { fu_device_convert_instance_ids(self); return FALSE; } } fu_device_convert_instance_ids(self); return TRUE; } /** * fu_device_set_progress: * @self: a #FuDevice * @progress: a #FuProgress * * Sets steps on the progress object used to write firmware. * * Since: 1.7.0 **/ void fu_device_set_progress(FuDevice *self, FuProgress *progress) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(FU_IS_PROGRESS(progress)); /* subclassed */ if (klass->set_progress == NULL) return; klass->set_progress(self, progress); } /** * fu_device_convert_instance_ids: * @self: a #FuDevice * * Converts all the Device instance IDs added using fu_device_add_instance_id() * into actual GUIDs, **unless** %FU_DEVICE_INTERNAL_FLAG_NO_AUTO_INSTANCE_IDS has * been set. * * Plugins will only need to need to call this manually when adding child * devices, as fu_device_setup() automatically calls this after the * fu_device_probe() and fu_device_setup() virtual functions have been run. * * Since: 1.2.5 **/ void fu_device_convert_instance_ids(FuDevice *self) { GPtrArray *instance_ids; /* OEM specific hardware */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_NO_AUTO_INSTANCE_IDS)) return; instance_ids = fwupd_device_get_instance_ids(FWUPD_DEVICE(self)); for (guint i = 0; i < instance_ids->len; i++) { const gchar *instance_id = g_ptr_array_index(instance_ids, i); g_autofree gchar *guid = fwupd_guid_hash_string(instance_id); fwupd_device_add_guid(FWUPD_DEVICE(self), guid); } } /** * fu_device_setup: * @self: a #FuDevice * @error: (nullable): optional return location for an error * * Sets up a device, setting parameters on the object that requires * the device to be open and have the interface claimed. * If the device is not compatible then an error should be returned. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_setup(FuDevice *self, GError **error) { FuDevicePrivate *priv = GET_PRIVATE(self); FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); GPtrArray *children; g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* should have already been called */ if (!fu_device_probe(self, error)) return FALSE; /* already done */ if (priv->done_setup) return TRUE; /* subclassed */ if (klass->setup != NULL) { if (!klass->setup(self, error)) return FALSE; } /* vfunc skipped device */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_NO_PROBE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not probing"); return FALSE; } /* run setup on the children too (unless done already) */ children = fu_device_get_children(self); for (guint i = 0; i < children->len; i++) { FuDevice *child_tmp = g_ptr_array_index(children, i); if (!fu_device_setup(child_tmp, error)) return FALSE; } /* convert the instance IDs to GUIDs */ fu_device_convert_instance_ids(self); /* subclassed */ if (klass->ready != NULL) { if (!klass->ready(self, error)) return FALSE; } priv->done_setup = TRUE; return TRUE; } /** * fu_device_activate: * @self: a #FuDevice * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Activates up a device, which normally means the device switches to a new * firmware version. This should only be called when data loss cannot occur. * * Returns: %TRUE for success * * Since: 1.2.6 **/ gboolean fu_device_activate(FuDevice *self, FuProgress *progress, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* subclassed */ if (klass->activate != NULL) { g_set_object(&priv->progress, progress); if (!klass->activate(self, progress, error)) return FALSE; } return TRUE; } /** * fu_device_probe_invalidate: * @self: a #FuDevice * * Normally when calling fu_device_probe() multiple times it is only done once. * Calling this method causes the next requests to fu_device_probe() and * fu_device_setup() actually probe the hardware. * * This should be done in case the backing device has changed, for instance if * a USB device has been replugged. * * Since: 1.1.2 **/ void fu_device_probe_invalidate(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_if_fail(FU_IS_DEVICE(self)); priv->done_probe = FALSE; priv->done_setup = FALSE; if (klass->invalidate != NULL) klass->invalidate(self); } /** * fu_device_report_metadata_pre: * @self: a #FuDevice * * Collects metadata that would be useful for debugging a failed update report. * * Returns: (transfer full) (nullable): a #GHashTable, or %NULL if there is no data * * Since: 1.5.0 **/ GHashTable * fu_device_report_metadata_pre(FuDevice *self) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_autoptr(GHashTable) metadata = NULL; g_return_val_if_fail(FU_IS_DEVICE(self), NULL); /* not implemented */ if (klass->report_metadata_pre == NULL) return NULL; /* metadata for all devices */ metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); klass->report_metadata_pre(self, metadata); return g_steal_pointer(&metadata); } /** * fu_device_report_metadata_post: * @self: a #FuDevice * * Collects metadata that would be useful for debugging a failed update report. * * Returns: (transfer full) (nullable): a #GHashTable, or %NULL if there is no data * * Since: 1.5.0 **/ GHashTable * fu_device_report_metadata_post(FuDevice *self) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_autoptr(GHashTable) metadata = NULL; g_return_val_if_fail(FU_IS_DEVICE(self), NULL); /* not implemented */ if (klass->report_metadata_post == NULL) return NULL; /* metadata for all devices */ metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); klass->report_metadata_post(self, metadata); return g_steal_pointer(&metadata); } /** * fu_device_add_security_attrs: * @self: a #FuDevice * @attrs: a security attribute * * Adds HSI security attributes. * * Since: 1.6.0 **/ void fu_device_add_security_attrs(FuDevice *self, FuSecurityAttrs *attrs) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_if_fail(FU_IS_DEVICE(self)); /* optional */ if (klass->add_security_attrs != NULL) return klass->add_security_attrs(self, attrs); } /** * fu_device_bind_driver: * @self: a #FuDevice * @subsystem: a subsystem string, e.g. `pci` * @driver: a kernel module name, e.g. `tg3` * @error: (nullable): optional return location for an error * * Binds a driver to the device, which normally means the kernel driver takes * control of the hardware. * * Returns: %TRUE if driver was bound. * * Since: 1.5.0 **/ gboolean fu_device_bind_driver(FuDevice *self, const gchar *subsystem, const gchar *driver, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(subsystem != NULL, FALSE); g_return_val_if_fail(driver != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not implemented */ if (klass->bind_driver == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "binding drivers is not supported by device"); return FALSE; } /* subclass */ return klass->bind_driver(self, subsystem, driver, error); } /** * fu_device_unbind_driver: * @self: a #FuDevice * @error: (nullable): optional return location for an error * * Unbinds the driver from the device, which normally means the kernel releases * the hardware so it can be used from userspace. * * If there is no driver bound then this function will return with success * without actually doing anything. * * Returns: %TRUE if driver was unbound. * * Since: 1.5.0 **/ gboolean fu_device_unbind_driver(FuDevice *self, GError **error) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not implemented */ if (klass->unbind_driver == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unbinding drivers is not supported by device"); return FALSE; } /* subclass */ return klass->unbind_driver(self, error); } /** * fu_device_get_instance_str: * @self: a #FuDevice * @key: (not nullable): a key, e.g. `REV` * * Looks up an instance ID by a key. * * Returns: (nullable) (transfer none): The instance key, or %NULL. * * Since: 1.8.15 **/ const gchar * fu_device_get_instance_str(FuDevice *self, const gchar *key) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), NULL); g_return_val_if_fail(key != NULL, NULL); return g_hash_table_lookup(priv->instance_hash, key); } /** * fu_device_incorporate: * @self: a #FuDevice * @donor: Another #FuDevice * * Copy all properties from the donor object if they have not already been set. * * Since: 1.1.0 **/ void fu_device_incorporate(FuDevice *self, FuDevice *donor) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); FuDevicePrivate *priv = GET_PRIVATE(self); FuDevicePrivate *priv_donor = GET_PRIVATE(donor); GPtrArray *instance_ids = fu_device_get_instance_ids(donor); GPtrArray *parent_guids = fu_device_get_parent_guids(donor); GPtrArray *parent_physical_ids = fu_device_get_parent_physical_ids(donor); GPtrArray *parent_backend_ids = fu_device_get_parent_backend_ids(donor); GHashTableIter iter; gpointer key, value; g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(FU_IS_DEVICE(donor)); /* copy from donor FuDevice if has not already been set */ fu_device_add_internal_flag(self, fu_device_get_internal_flags(donor)); if (priv->alternate_id == NULL && fu_device_get_alternate_id(donor) != NULL) fu_device_set_alternate_id(self, fu_device_get_alternate_id(donor)); if (priv->equivalent_id == NULL && fu_device_get_equivalent_id(donor) != NULL) fu_device_set_equivalent_id(self, fu_device_get_equivalent_id(donor)); if (priv->physical_id == NULL && priv_donor->physical_id != NULL) fu_device_set_physical_id(self, priv_donor->physical_id); if (priv->logical_id == NULL && priv_donor->logical_id != NULL) fu_device_set_logical_id(self, priv_donor->logical_id); if (priv->backend_id == NULL && priv_donor->backend_id != NULL) fu_device_set_backend_id(self, priv_donor->backend_id); if (priv->update_request_id == NULL && priv_donor->update_request_id != NULL) fu_device_set_update_request_id(self, priv_donor->update_request_id); if (priv->proxy == NULL && priv_donor->proxy != NULL) fu_device_set_proxy(self, priv_donor->proxy); if (priv->proxy_guid == NULL && priv_donor->proxy_guid != NULL) fu_device_set_proxy_guid(self, priv_donor->proxy_guid); if (priv->custom_flags == NULL && priv_donor->custom_flags != NULL) fu_device_set_custom_flags(self, priv_donor->custom_flags); if (priv->ctx == NULL) fu_device_set_context(self, fu_device_get_context(donor)); for (guint i = 0; i < parent_guids->len; i++) fu_device_add_parent_guid(self, g_ptr_array_index(parent_guids, i)); if (parent_physical_ids != NULL) { for (guint i = 0; i < parent_physical_ids->len; i++) { const gchar *tmp = g_ptr_array_index(parent_physical_ids, i); fu_device_add_parent_physical_id(self, tmp); } } if (parent_backend_ids != NULL) { for (guint i = 0; i < parent_backend_ids->len; i++) { const gchar *tmp = g_ptr_array_index(parent_backend_ids, i); fu_device_add_parent_backend_id(self, tmp); } } if (priv->metadata != NULL) { g_hash_table_iter_init(&iter, priv_donor->metadata); while (g_hash_table_iter_next(&iter, &key, &value)) { if (fu_device_get_metadata(self, key) == NULL) fu_device_set_metadata(self, key, value); } } /* probably not required, but seems safer */ for (guint i = 0; i < priv_donor->possible_plugins->len; i++) { const gchar *possible_plugin = g_ptr_array_index(priv_donor->possible_plugins, i); fu_device_add_possible_plugin(self, possible_plugin); } for (guint i = 0; i < priv_donor->instance_id_quirks->len; i++) { const gchar *instance_id = g_ptr_array_index(priv_donor->instance_id_quirks, i); fu_device_add_instance_id_full(self, instance_id, FU_DEVICE_INSTANCE_FLAG_QUIRKS); } /* copy all instance ID keys if not already set */ g_hash_table_iter_init(&iter, priv_donor->instance_hash); while (g_hash_table_iter_next(&iter, &key, &value)) { if (fu_device_get_instance_str(self, key) == NULL) fu_device_add_instance_str(self, key, value); } /* now the base class, where all the interesting bits are */ fwupd_device_incorporate(FWUPD_DEVICE(self), FWUPD_DEVICE(donor)); /* remove the baseclass-added serial number if set */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER)) fwupd_device_set_serial(FWUPD_DEVICE(self), NULL); /* remove the baseclass-added GUIDs */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_NO_GENERIC_GUIDS)) g_ptr_array_set_size(fwupd_device_get_instance_ids(FWUPD_DEVICE(self)), 0); /* set by the superclass */ if (fu_device_get_id(self) != NULL) priv->device_id_valid = TRUE; /* optional subclass */ if (klass->incorporate != NULL) klass->incorporate(self, donor); /* call the set_quirk_kv() vfunc for the superclassed object */ for (guint i = 0; i < instance_ids->len; i++) { const gchar *instance_id = g_ptr_array_index(instance_ids, i); g_autofree gchar *guid = fwupd_guid_hash_string(instance_id); fu_device_add_guid_quirks(self, guid); } } /** * fu_device_replace: * @self: a #FuDevice * @donor: the old #FuDevice * * Copy properties from the old (no-longer-connected) device to the new (connected) device. * * This is typcically called from the daemon device list and should not be called from plugin code. * * Since: 1.9.2 **/ void fu_device_replace(FuDevice *self, FuDevice *donor) { FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(FU_IS_DEVICE(donor)); /* optional subclass */ if (klass->replace != NULL) klass->replace(self, donor); } /** * fu_device_incorporate_flag: * @self: a #FuDevice * @donor: another device * @flag: device flags * * Copy the value of a specific flag from the donor object. * * Since: 1.3.5 **/ void fu_device_incorporate_flag(FuDevice *self, FuDevice *donor, FwupdDeviceFlags flag) { if (fu_device_has_flag(donor, flag) && !fu_device_has_flag(self, flag)) { g_debug("donor set %s", fwupd_device_flag_to_string(flag)); fu_device_add_flag(self, flag); } else if (!fu_device_has_flag(donor, flag) && fu_device_has_flag(self, flag)) { g_debug("donor unset %s", fwupd_device_flag_to_string(flag)); fu_device_remove_flag(self, flag); } } /** * fu_device_incorporate_from_component: (skip): * @self: a device * @component: a Xmlb node * * Copy all properties from the donor AppStream component. * * Since: 1.2.4 **/ void fu_device_incorporate_from_component(FuDevice *self, XbNode *component) { const gchar *tmp; g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(XB_IS_NODE(component)); tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateMessage']", NULL); if (tmp != NULL) fwupd_device_set_update_message(FWUPD_DEVICE(self), tmp); tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateImage']", NULL); if (tmp != NULL) fwupd_device_set_update_image(FWUPD_DEVICE(self), tmp); } static void fu_device_ensure_from_component_name(FuDevice *self, XbNode *component) { const gchar *name = NULL; /* copy 1:1 */ name = xb_node_query_text(component, "name", NULL); if (name != NULL) { fu_device_set_name(self, name); fu_device_remove_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME); } } static void fu_device_ensure_from_component_vendor(FuDevice *self, XbNode *component) { const gchar *vendor = NULL; /* copy 1:1 */ vendor = xb_node_query_text(component, "developer_name", NULL); if (vendor != NULL) { fu_device_set_vendor(self, vendor); fu_device_remove_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR); } } static void fu_device_ensure_from_component_signed(FuDevice *self, XbNode *component) { const gchar *value = NULL; /* already set, possibly by a quirk */ if (fu_device_has_flag(self, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD) || fu_device_has_flag(self, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD)) return; /* copy 1:1 */ value = xb_node_query_text(component, "custom/value[@key='LVFS::DeviceIntegrity']", NULL); if (value != NULL) { if (g_strcmp0(value, "signed") == 0) { fu_device_add_flag(self, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); } else if (g_strcmp0(value, "unsigned") == 0) { fu_device_add_flag(self, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); } else { g_warning("payload value unexpected: %s, expected signed|unsigned", value); } fu_device_remove_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR); } } static void fu_device_ensure_from_component_icon(FuDevice *self, XbNode *component) { const gchar *icon = NULL; /* copy 1:1 */ icon = xb_node_query_text(component, "icon", NULL); if (icon != NULL) { fu_device_add_icon(self, icon); fu_device_remove_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON); } } static void fu_device_ensure_from_component_flags(FuDevice *self, XbNode *component) { const gchar *tmp = xb_node_query_text(component, "custom/value[@key='LVFS::DeviceFlags']", NULL); if (tmp != NULL) { g_auto(GStrv) hints = g_strsplit(tmp, ",", -1); for (guint i = 0; hints[i] != NULL; i++) fu_device_set_custom_flag(self, hints[i]); fu_device_remove_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS); } } static const gchar * fu_device_category_to_name(const gchar *cat) { if (g_strcmp0(cat, "X-EmbeddedController") == 0) return "Embedded Controller"; if (g_strcmp0(cat, "X-ManagementEngine") == 0) return "Intel Management Engine"; if (g_strcmp0(cat, "X-CorporateManagementEngine") == 0) return "Intel Management Engine"; if (g_strcmp0(cat, "X-ConsumerManagementEngine") == 0) return "Intel Management Engine"; if (g_strcmp0(cat, "X-ThunderboltController") == 0) return "Thunderbolt Controller"; if (g_strcmp0(cat, "X-PlatformSecurityProcessor") == 0) return "Platform Security Processor"; if (g_strcmp0(cat, "X-CpuMicrocode") == 0) return "CPU Microcode"; if (g_strcmp0(cat, "X-Battery") == 0) return "Battery"; if (g_strcmp0(cat, "X-Camera") == 0) return "Camera"; if (g_strcmp0(cat, "X-TPM") == 0) return "TPM"; if (g_strcmp0(cat, "X-Touchpad") == 0) return "Touchpad"; if (g_strcmp0(cat, "X-Mouse") == 0) return "Mouse"; if (g_strcmp0(cat, "X-Keyboard") == 0) return "Keyboard"; if (g_strcmp0(cat, "X-VideoDisplay") == 0) return "Display"; if (g_strcmp0(cat, "X-BaseboardManagementController") == 0) return "BMC"; if (g_strcmp0(cat, "X-UsbReceiver") == 0) return "USB Receiver"; if (g_strcmp0(cat, "X-Gpu") == 0) return "GPU"; if (g_strcmp0(cat, "X-Dock") == 0) return "Dock"; if (g_strcmp0(cat, "X-UsbDock") == 0) return "USB Dock"; if (g_strcmp0(cat, "X-FingerprintReader") == 0) return "Fingerprint Reader"; if (g_strcmp0(cat, "X-GraphicsTablet") == 0) return "Graphics Tablet"; return NULL; } static void fu_device_ensure_from_component_name_category(FuDevice *self, XbNode *component) { const gchar *name = NULL; g_autoptr(GPtrArray) cats = NULL; /* get AppStream and safe-compat categories */ cats = xb_node_query(component, "categories/category|X-categories/category", 0, NULL); if (cats == NULL) return; for (guint i = 0; i < cats->len; i++) { XbNode *n = g_ptr_array_index(cats, i); name = fu_device_category_to_name(xb_node_get_text(n)); if (name != NULL) break; } if (name != NULL) { fu_device_set_name(self, name); fu_device_remove_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY); } /* batteries updated using capsules should ignore the system power restriction */ if (g_strcmp0(fu_device_get_plugin(self), "uefi_capsule") == 0) { gboolean is_battery = FALSE; for (guint i = 0; i < cats->len; i++) { XbNode *n = g_ptr_array_index(cats, i); if (g_strcmp0(xb_node_get_text(n), "X-Battery") == 0) { is_battery = TRUE; break; } } if (is_battery) { g_info("ignoring system power for %s battery", fu_device_get_id(self)); fu_device_add_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_IGNORE_SYSTEM_POWER); } } } static void _g_ptr_array_reverse(GPtrArray *array) { guint last_idx = array->len - 1; for (guint i = 0; i < array->len / 2; i++) { gpointer tmp = array->pdata[i]; array->pdata[i] = array->pdata[last_idx - i]; array->pdata[last_idx - i] = tmp; } } static void fu_device_ensure_from_component_verfmt(FuDevice *self, XbNode *component) { FwupdVersionFormat verfmt = FWUPD_VERSION_FORMAT_UNKNOWN; g_autoptr(GPtrArray) verfmts = NULL; /* get metadata */ verfmts = xb_node_query(component, "custom/value[@key='LVFS::VersionFormat']", 0, NULL); if (verfmts == NULL) return; _g_ptr_array_reverse(verfmts); for (guint i = 0; i < verfmts->len; i++) { XbNode *value = g_ptr_array_index(verfmts, i); verfmt = fwupd_version_format_from_string(xb_node_get_text(value)); if (verfmt != FWUPD_VERSION_FORMAT_UNKNOWN) break; } /* found and different to existing */ if (verfmt != FWUPD_VERSION_FORMAT_UNKNOWN && fu_device_get_version_format(self) != verfmt) { fu_device_set_version_format(self, verfmt); if (fu_device_get_version_raw(self) != 0x0) { g_autofree gchar *version = NULL; version = fu_version_from_uint32(fu_device_get_version_raw(self), verfmt); fu_device_set_version(self, version); } if (fu_device_get_version_lowest_raw(self) != 0x0) { g_autofree gchar *version = NULL; version = fu_version_from_uint32(fu_device_get_version_lowest_raw(self), verfmt); fu_device_set_version_lowest(self, version); } if (fu_device_get_version_bootloader_raw(self) != 0x0) { g_autofree gchar *version = NULL; version = fu_version_from_uint32(fu_device_get_version_bootloader_raw(self), verfmt); fu_device_set_version_bootloader(self, version); } } /* do not try to do this again */ fu_device_remove_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT); } /** * fu_device_ensure_from_component: (skip): * @self: a device * @component: a #XbNode * * Ensure all properties from the donor AppStream component as required. * * Since: 1.8.13 **/ void fu_device_ensure_from_component(FuDevice *self, XbNode *component) { g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(XB_IS_NODE(component)); /* set the name */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME)) fu_device_ensure_from_component_name(self, component); if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY)) fu_device_ensure_from_component_name_category(self, component); if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON)) fu_device_ensure_from_component_icon(self, component); if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR)) fu_device_ensure_from_component_vendor(self, component); if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED)) fu_device_ensure_from_component_signed(self, component); if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT)) fu_device_ensure_from_component_verfmt(self, component); if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS)) fu_device_ensure_from_component_flags(self, component); } /** * fu_device_emit_request: * @self: a device * @request: a request * @progress: (nullable): a #FuProgress * @error: (nullable): optional return location for an error * * Emit a request from a plugin to the client. * * If the device is emulated then this request is ignored. * * Since: 1.9.8 **/ gboolean fu_device_emit_request(FuDevice *self, FwupdRequest *request, FuProgress *progress, GError **error) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(FWUPD_IS_REQUEST(request), FALSE); g_return_val_if_fail(progress == NULL || FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); #ifndef SUPPORTED_BUILD /* nag the developer */ if (fwupd_request_has_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE) && !fu_device_has_request_flag(self, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "request %s emitted but device %s [%s] does not set " "FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE", fwupd_request_get_id(request), fu_device_get_id(self), fu_device_get_plugin(self)); return FALSE; } if (!fwupd_request_has_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE) && !fu_device_has_request_flag(self, FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "request %s is not a GENERIC_MESSAGE and device %s [%s] does not set " "FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE", fwupd_request_get_id(request), fu_device_get_id(self), fu_device_get_plugin(self)); return FALSE; } #endif /* sanity check */ if (fwupd_request_get_kind(request) == FWUPD_REQUEST_KIND_UNKNOWN) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "a request must have an assigned kind"); return FALSE; } if (fwupd_request_get_id(request) == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "a request must have an assigned ID"); return FALSE; } if (fwupd_request_get_kind(request) >= FWUPD_REQUEST_KIND_LAST) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "invalid request kind"); return FALSE; } /* already cancelled */ if (progress != NULL && fu_progress_has_flag(progress, FU_PROGRESS_FLAG_NO_SENDER)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "no sender, and so cannot process request"); return FALSE; } /* ignore */ if (fu_device_has_flag(self, FWUPD_DEVICE_FLAG_EMULATED)) { g_info("ignoring device %s request of %s as emulated", fu_device_get_id(self), fwupd_request_get_id(request)); return TRUE; } /* ensure set */ fwupd_request_set_device_id(request, fu_device_get_id(self)); /* for compatibility with older clients */ if (fwupd_request_get_kind(request) == FWUPD_REQUEST_KIND_POST) { fu_device_set_update_message(self, fwupd_request_get_message(request)); fu_device_set_update_image(self, fwupd_request_get_image(request)); } /* proxy to the engine */ if (progress != NULL) { fu_progress_set_status(progress, FWUPD_STATUS_WAITING_FOR_USER); } else if (priv->progress != NULL) { g_debug("using fallback progress"); fu_progress_set_status(priv->progress, FWUPD_STATUS_WAITING_FOR_USER); } else { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no progress"); return FALSE; } g_signal_emit(self, signals[SIGNAL_REQUEST], 0, request); if (fwupd_request_get_kind(request) < FWUPD_REQUEST_KIND_LAST) priv->request_cnts[fwupd_request_get_kind(request)]++; return TRUE; } static void fu_device_flags_notify_cb(FuDevice *self, GParamSpec *pspec, gpointer user_data) { FuDevicePrivate *priv = GET_PRIVATE(self); /* we only inhibit when the flags contains UPDATABLE, and that might be discovered by * probing the hardware *after* the battery level has been set */ if (priv->inhibits != NULL) fu_device_ensure_inhibits(self); } /** * fu_device_add_instance_str: * @self: a #FuDevice * @key: (not nullable): string * @value: (nullable): value * * Assign a value for the @key. * * Since: 1.7.7 **/ void fu_device_add_instance_str(FuDevice *self, const gchar *key, const gchar *value) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(key != NULL); g_hash_table_insert(priv->instance_hash, g_strdup(key), g_strdup(value)); } static gboolean fu_strsafe_instance_id_is_valid_char(gchar c) { if (c == ' ') return FALSE; if (c == '_') return FALSE; if (c == '&') return FALSE; if (c == '/') return FALSE; if (c == '\\') return FALSE; return g_ascii_isprint(c); } /* NOTE: we can't use fu_strsafe as this behavior is now effectively ABI */ static gchar * fu_common_instance_id_strsafe(const gchar *str) { g_autoptr(GString) tmp = g_string_new(NULL); gboolean has_content = FALSE; /* sanity check */ if (str == NULL) return NULL; /* use - to replace problematic chars -- but only once per section */ for (guint i = 0; str[i] != '\0'; i++) { gchar c = str[i]; if (!fu_strsafe_instance_id_is_valid_char(c)) { if (has_content) { g_string_append_c(tmp, '-'); has_content = FALSE; } } else { g_string_append_c(tmp, c); has_content = TRUE; } } /* remove any trailing replacements */ if (tmp->len > 0 && tmp->str[tmp->len - 1] == '-') g_string_truncate(tmp, tmp->len - 1); /* nothing left! */ if (tmp->len == 0) return NULL; /* success */ return g_string_free(g_steal_pointer(&tmp), FALSE); } /** * fu_device_add_instance_strsafe: * @self: a #FuDevice * @key: (not nullable): string * @value: (nullable): value * * Assign a sanitized value for the @key. * * Since: 1.7.7 **/ void fu_device_add_instance_strsafe(FuDevice *self, const gchar *key, const gchar *value) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(key != NULL); g_hash_table_insert(priv->instance_hash, g_strdup(key), fu_common_instance_id_strsafe(value)); } /** * fu_device_add_instance_strup: * @self: a #FuDevice * @key: (not nullable): string * @value: (nullable): value * * Assign a uppercase value for the @key. * * Since: 1.7.7 **/ void fu_device_add_instance_strup(FuDevice *self, const gchar *key, const gchar *value) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(key != NULL); g_hash_table_insert(priv->instance_hash, g_strdup(key), value != NULL ? g_utf8_strup(value, -1) : NULL); } /** * fu_device_add_instance_u4: * @self: a #FuDevice * @key: (not nullable): string * @value: value * * Assign a value to the @key, which is padded as %1X. * * Since: 1.7.7 **/ void fu_device_add_instance_u4(FuDevice *self, const gchar *key, guint8 value) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(key != NULL); g_hash_table_insert(priv->instance_hash, g_strdup(key), g_strdup_printf("%01X", value)); } /** * fu_device_add_instance_u8: * @self: a #FuDevice * @key: (not nullable): string * @value: value * * Assign a value to the @key, which is padded as %2X. * * Since: 1.7.7 **/ void fu_device_add_instance_u8(FuDevice *self, const gchar *key, guint8 value) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(key != NULL); g_hash_table_insert(priv->instance_hash, g_strdup(key), g_strdup_printf("%02X", value)); } /** * fu_device_add_instance_u16: * @self: a #FuDevice * @key: (not nullable): string * @value: value * * Assign a value to the @key, which is padded as %4X. * * Since: 1.7.7 **/ void fu_device_add_instance_u16(FuDevice *self, const gchar *key, guint16 value) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(key != NULL); g_hash_table_insert(priv->instance_hash, g_strdup(key), g_strdup_printf("%04X", value)); } /** * fu_device_add_instance_u32: * @self: a #FuDevice * @key: (not nullable): string * @value: value * * Assign a value to the @key, which is padded as %8X. * * Since: 1.7.7 **/ void fu_device_add_instance_u32(FuDevice *self, const gchar *key, guint32 value) { FuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DEVICE(self)); g_return_if_fail(key != NULL); g_hash_table_insert(priv->instance_hash, g_strdup(key), g_strdup_printf("%08X", value)); } /** * fu_device_build_instance_id: * @self: a #FuDevice * @error: (nullable): optional return location for an error * @subsystem: (not nullable): subsystem, e.g. `NVME` * @...: pairs of string key values, ending with %NULL * * Creates an instance ID from a prefix and some key values. * If the key value cannot be found, the parent and then proxy is also queried. * * If any of the key values remain unset then no instance ID is added. * * fu_device_add_instance_str(dev, "VID", "1234"); * fu_device_add_instance_u16(dev, "PID", 5678); * if (!fu_device_build_instance_id(dev, &error, "NVME", "VID", "PID", NULL)) * g_warning("failed to add ID: %s", error->message); * * Returns: %TRUE if the instance ID was added. * * Since: 1.7.7 **/ gboolean fu_device_build_instance_id(FuDevice *self, GError **error, const gchar *subsystem, ...) { FuDevice *parent = fu_device_get_parent(self); FuDevicePrivate *priv = GET_PRIVATE(self); gboolean ret = TRUE; va_list args; g_autoptr(GString) str = g_string_new(subsystem); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(subsystem != NULL, FALSE); va_start(args, subsystem); for (guint i = 0;; i++) { const gchar *key = va_arg(args, const gchar *); const gchar *value; if (key == NULL) break; value = fu_device_get_instance_str(self, key); if (value == NULL && parent != NULL) value = fu_device_get_instance_str(parent, key); if (value == NULL && priv->proxy != NULL) value = fu_device_get_instance_str(priv->proxy, key); if (value == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no value for %s", key); ret = FALSE; break; } g_string_append(str, i == 0 ? "\\" : "&"); g_string_append_printf(str, "%s_%s", key, value); } va_end(args); /* we set an error above */ if (!ret) return FALSE; /* success */ fu_device_add_instance_id(self, str->str); return TRUE; } /** * fu_device_build_instance_id_full: * @self: a #FuDevice * @flags: instance ID flags, e.g. %FU_DEVICE_INSTANCE_FLAG_QUIRKS * @error: (nullable): optional return location for an error * @subsystem: (not nullable): subsystem, e.g. `NVME` * @...: pairs of string key values, ending with %NULL * * Creates an instance ID with specific flags from a prefix and some key values. If any of the key * values are unset then no instance ID is added. * * Returns: %TRUE if the instance ID was added. * * Since: 1.9.8 **/ gboolean fu_device_build_instance_id_full(FuDevice *self, FuDeviceInstanceFlags flags, GError **error, const gchar *subsystem, ...) { FuDevicePrivate *priv = GET_PRIVATE(self); gboolean ret = TRUE; va_list args; g_autoptr(GString) str = g_string_new(subsystem); g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(subsystem != NULL, FALSE); va_start(args, subsystem); for (guint i = 0;; i++) { const gchar *key = va_arg(args, const gchar *); const gchar *value; if (key == NULL) break; value = g_hash_table_lookup(priv->instance_hash, key); if (value == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no value for %s", key); ret = FALSE; break; } g_string_append(str, i == 0 ? "\\" : "&"); g_string_append_printf(str, "%s_%s", key, value); } va_end(args); /* we set an error above */ if (!ret) return FALSE; /* success */ fu_device_add_instance_id_full(self, str->str, flags); return TRUE; } /** * fu_device_security_attr_new: * @self: a #FuDevice * @appstream_id: (nullable): the AppStream component ID, e.g. `com.intel.BiosGuard` * * Creates a new #FwupdSecurityAttr for this specific device. * * Returns: (transfer full): a #FwupdSecurityAttr * * Since: 1.8.4 **/ FwupdSecurityAttr * fu_device_security_attr_new(FuDevice *self, const gchar *appstream_id) { FuDevicePrivate *priv = fu_device_get_instance_private(self); g_autoptr(FwupdSecurityAttr) attr = NULL; g_return_val_if_fail(FU_IS_DEVICE(self), NULL); g_return_val_if_fail(appstream_id != NULL, NULL); attr = fu_security_attr_new(priv->ctx, appstream_id); fwupd_security_attr_set_plugin(attr, fu_device_get_plugin(FU_DEVICE(self))); fwupd_security_attr_add_guids(attr, fu_device_get_guids(FU_DEVICE(self))); /* if the device is a child of the host firmware then add those GUIDs too */ if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE_CHILD)) { FuDevice *msf_device = fu_device_get_parent(self); if (msf_device != NULL) { GPtrArray *guids = fu_device_get_guids(msf_device); for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index(guids, i); fwupd_security_attr_add_guid(attr, guid); } } } return g_steal_pointer(&attr); } static void fu_device_class_init(FuDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->finalize = fu_device_finalize; object_class->get_property = fu_device_get_property; object_class->set_property = fu_device_set_property; /** * FuDevice::child-added: * @self: the #FuDevice instance that emitted the signal * @device: the #FuDevice child * * The ::child-added signal is emitted when a device has been added as a child. * * Since: 1.0.8 **/ signals[SIGNAL_CHILD_ADDED] = g_signal_new("child-added", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FuDeviceClass, child_added), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuDevice::child-removed: * @self: the #FuDevice instance that emitted the signal * @device: the #FuDevice child * * The ::child-removed signal is emitted when a device has been removed as a child. * * Since: 1.0.8 **/ signals[SIGNAL_CHILD_REMOVED] = g_signal_new("child-removed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FuDeviceClass, child_removed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuDevice::request: * @self: the #FuDevice instance that emitted the signal * @request: the #FwupdRequest * * The ::request signal is emitted when the device needs interactive action from the user. * * Since: 1.6.2 **/ signals[SIGNAL_REQUEST] = g_signal_new("request", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FuDeviceClass, request), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FWUPD_TYPE_REQUEST); /** * FuDevice:physical-id: * * The device physical ID. * * Since: 1.1.2 */ pspec = g_param_spec_string("physical-id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PHYSICAL_ID, pspec); /** * FuDevice:logical-id: * * The device logical ID. * * Since: 1.1.2 */ pspec = g_param_spec_string("logical-id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_LOGICAL_ID, pspec); /** * FuDevice:backend-id: * * The device backend ID. * * Since: 1.5.8 */ pspec = g_param_spec_string("backend-id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BACKEND_ID, pspec); /** * FuDevice:context: * * The #FuContext to use. * * Since: 1.6.0 */ pspec = g_param_spec_object("context", NULL, NULL, FU_TYPE_CONTEXT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_CONTEXT, pspec); /** * FuDevice:proxy: * * The device proxy to use. * * Since: 1.4.1 */ pspec = g_param_spec_object("proxy", NULL, NULL, FU_TYPE_DEVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PROXY, pspec); /** * FuDevice:parent: * * The device parent. * * Since: 1.0.8 */ pspec = g_param_spec_object("parent", NULL, NULL, FU_TYPE_DEVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PARENT, pspec); /** * FuDevice:internal-flags: * * The device internal flags. * * Since: 1.9.1 */ pspec = g_param_spec_uint64("internal-flags", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_INTERNAL_FLAGS, pspec); /** * FuDevice:private-flags: * * The device private flags. * * Since: 1.9.1 */ pspec = g_param_spec_uint64("private-flags", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PRIVATE_FLAGS, pspec); } static void fu_device_init(FuDevice *self) { FuDevicePrivate *priv = GET_PRIVATE(self); priv->order = G_MAXINT; priv->parent_guids = g_ptr_array_new_with_free_func(g_free); priv->possible_plugins = g_ptr_array_new_with_free_func(g_free); priv->instance_id_quirks = g_ptr_array_new_with_free_func(g_free); priv->retry_recs = g_ptr_array_new_with_free_func(g_free); priv->instance_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); priv->acquiesce_delay = 50; /* ms */ priv->notify_flags_handler_id = g_signal_connect(FWUPD_DEVICE(self), "notify::flags", G_CALLBACK(fu_device_flags_notify_cb), NULL); } static void fu_device_finalize(GObject *object) { FuDevice *self = FU_DEVICE(object); FuDevicePrivate *priv = GET_PRIVATE(self); if (priv->progress != NULL) g_object_unref(priv->progress); if (priv->alternate != NULL) g_object_unref(priv->alternate); if (priv->proxy != NULL) { if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_REFCOUNTED_PROXY)) { g_object_unref(priv->proxy); } else { g_object_remove_weak_pointer(G_OBJECT(priv->proxy), (gpointer *)&priv->proxy); } } if (priv->ctx != NULL) g_object_unref(priv->ctx); if (priv->poll_id != 0) g_source_remove(priv->poll_id); if (priv->metadata != NULL) g_hash_table_unref(priv->metadata); if (priv->inhibits != NULL) g_hash_table_unref(priv->inhibits); if (priv->parent_physical_ids != NULL) g_ptr_array_unref(priv->parent_physical_ids); if (priv->parent_backend_ids != NULL) g_ptr_array_unref(priv->parent_backend_ids); if (priv->private_flag_items != NULL) g_ptr_array_unref(priv->private_flag_items); g_ptr_array_unref(priv->parent_guids); g_ptr_array_unref(priv->possible_plugins); g_ptr_array_unref(priv->instance_id_quirks); g_ptr_array_unref(priv->retry_recs); g_free(priv->alternate_id); g_free(priv->equivalent_id); g_free(priv->physical_id); g_free(priv->logical_id); g_free(priv->backend_id); g_free(priv->update_request_id); g_free(priv->proxy_guid); g_free(priv->custom_flags); g_hash_table_unref(priv->instance_hash); G_OBJECT_CLASS(fu_device_parent_class)->finalize(object); } /** * fu_device_new: * * Creates a new #Fudevice * * Since: 1.8.2 **/ FuDevice * fu_device_new(FuContext *ctx) { FuDevice *self = g_object_new(FU_TYPE_DEVICE, "context", ctx, NULL); return FU_DEVICE(self); } fwupd-1.9.16/libfwupdplugin/fu-device.h000066400000000000000000001043571460375044200200270ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-context.h" #include "fu-device-locker.h" #include "fu-firmware.h" #include "fu-progress.h" #include "fu-security-attrs.h" #include "fu-version-common.h" #define FU_TYPE_DEVICE (fu_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuDevice, fu_device, FU, DEVICE, FwupdDevice) struct _FuDeviceClass { FwupdDeviceClass parent_class; #ifndef __GI_SCANNER__ void (*to_string)(FuDevice *self, guint indent, GString *str); gboolean (*write_firmware)(FuDevice *self, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; FuFirmware *(*read_firmware)(FuDevice *self, FuProgress *progress, GError **error)G_GNUC_WARN_UNUSED_RESULT; gboolean (*detach)(FuDevice *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*attach)(FuDevice *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*open)(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*close)(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*probe)(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*rescan)(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; FuFirmware *(*prepare_firmware)(FuDevice *self, GBytes *fw, FwupdInstallFlags flags, GError **error)G_GNUC_WARN_UNUSED_RESULT; gboolean (*set_quirk_kv)(FuDevice *self, const gchar *key, const gchar *value, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*setup)(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; void (*incorporate)(FuDevice *self, FuDevice *donor); void (*replace)(FuDevice *self, FuDevice *donor); void (*probe_complete)(FuDevice *self); gboolean (*poll)(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*activate)(FuDevice *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*reload)(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*prepare)(FuDevice *self, FuProgress *progress, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*cleanup)(FuDevice *self, FuProgress *progress, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; void (*report_metadata_pre)(FuDevice *self, GHashTable *metadata); void (*report_metadata_post)(FuDevice *self, GHashTable *metadata); gboolean (*bind_driver)(FuDevice *self, const gchar *subsystem, const gchar *driver, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*unbind_driver)(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; GBytes *(*dump_firmware)(FuDevice *self, FuProgress *progress, GError **error)G_GNUC_WARN_UNUSED_RESULT; void (*add_security_attrs)(FuDevice *self, FuSecurityAttrs *attrs); gboolean (*ready)(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; void (*child_added)(FuDevice *self, /* signal */ FuDevice *child); void (*child_removed)(FuDevice *self, /* signal */ FuDevice *child); void (*request)(FuDevice *self, /* signal */ FwupdRequest *request); gboolean (*get_results)(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT; void (*set_progress)(FuDevice *self, FuProgress *progress); void (*invalidate)(FuDevice *self); gchar *(*convert_version)(FuDevice *self, guint64 version_raw); #endif }; /** * FuDeviceInstanceFlags: * @FU_DEVICE_INSTANCE_FLAG_NONE: No flags set * @FU_DEVICE_INSTANCE_FLAG_VISIBLE: Show to the user * @FU_DEVICE_INSTANCE_FLAG_QUIRKS: Match against quirk files * @FU_DEVICE_INSTANCE_FLAG_GENERIC: Generic GUID added by a baseclass * * The flags to use when interacting with a device instance **/ typedef enum { FU_DEVICE_INSTANCE_FLAG_NONE = 0, FU_DEVICE_INSTANCE_FLAG_VISIBLE = 1 << 0, FU_DEVICE_INSTANCE_FLAG_QUIRKS = 1 << 1, FU_DEVICE_INSTANCE_FLAG_GENERIC = 1 << 2, /*< private >*/ FU_DEVICE_INSTANCE_FLAG_LAST } FuDeviceInstanceFlags; /** * FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE: * * The default removal delay for device re-enumeration taking into account a * chain of slow USB hubs. This should be used when the device is able to * reset itself between bootloader->runtime->bootloader. */ #define FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE 10000 /* ms */ /** * FU_DEVICE_REMOVE_DELAY_USER_REPLUG: * * The default removal delay for device re-plug taking into account humans * being slow and clumsy. This should be used when the user has to do something, * e.g. unplug, press a magic button and then replug. */ #define FU_DEVICE_REMOVE_DELAY_USER_REPLUG 40000 /* ms */ /** * FuDeviceRetryFunc: * @self: a #FuDevice * @user_data: (closure): user data * @error: (nullable): optional return location for an error * * The device retry iteration callback. * * Returns: %TRUE on success */ typedef gboolean (*FuDeviceRetryFunc)(FuDevice *self, gpointer user_data, GError **error) G_GNUC_WARN_UNUSED_RESULT; FuDevice * fu_device_new(FuContext *ctx); /* helpful casting macros */ #define fu_device_has_flag(d, v) fwupd_device_has_flag(FWUPD_DEVICE(d), v) #define fu_device_has_flag(d, v) fwupd_device_has_flag(FWUPD_DEVICE(d), v) #define fu_device_has_request_flag(d, v) fwupd_device_has_request_flag(FWUPD_DEVICE(d), v) #define fu_device_add_request_flag(d, v) fwupd_device_add_request_flag(FWUPD_DEVICE(d), v) #define fu_device_has_instance_id(d, v) fwupd_device_has_instance_id(FWUPD_DEVICE(d), v) #define fu_device_has_vendor_id(d, v) fwupd_device_has_vendor_id(FWUPD_DEVICE(d), v) #define fu_device_has_protocol(d, v) fwupd_device_has_protocol(FWUPD_DEVICE(d), v) #define fu_device_has_checksum(d, v) fwupd_device_has_checksum(FWUPD_DEVICE(d), v) #define fu_device_add_checksum(d, v) fwupd_device_add_checksum(FWUPD_DEVICE(d), v) #define fu_device_add_release(d, v) fwupd_device_add_release(FWUPD_DEVICE(d), v) #define fu_device_add_icon(d, v) fwupd_device_add_icon(FWUPD_DEVICE(d), v) #define fu_device_has_icon(d, v) fwupd_device_has_icon(FWUPD_DEVICE(d), v) #define fu_device_add_issue(d, v) fwupd_device_add_issue(FWUPD_DEVICE(d), v) #define fu_device_set_created(d, v) fwupd_device_set_created(FWUPD_DEVICE(d), v) #define fu_device_set_description(d, v) fwupd_device_set_description(FWUPD_DEVICE(d), v) #define fu_device_set_flags(d, v) fwupd_device_set_flags(FWUPD_DEVICE(d), v) #define fu_device_set_modified(d, v) fwupd_device_set_modified(FWUPD_DEVICE(d), v) #define fu_device_set_plugin(d, v) fwupd_device_set_plugin(FWUPD_DEVICE(d), v) #define fu_device_set_serial(d, v) fwupd_device_set_serial(FWUPD_DEVICE(d), v) #define fu_device_set_summary(d, v) fwupd_device_set_summary(FWUPD_DEVICE(d), v) #define fu_device_set_branch(d, v) fwupd_device_set_branch(FWUPD_DEVICE(d), v) #define fu_device_set_update_message(d, v) fwupd_device_set_update_message(FWUPD_DEVICE(d), v) #define fu_device_set_update_image(d, v) fwupd_device_set_update_image(FWUPD_DEVICE(d), v) #define fu_device_set_update_error(d, v) fwupd_device_set_update_error(FWUPD_DEVICE(d), v) #define fu_device_add_vendor_id(d, v) fwupd_device_add_vendor_id(FWUPD_DEVICE(d), v) #define fu_device_add_protocol(d, v) fwupd_device_add_protocol(FWUPD_DEVICE(d), v) #define fu_device_set_version_lowest_raw(d, v) \ fwupd_device_set_version_lowest_raw(FWUPD_DEVICE(d), v) #define fu_device_set_version_bootloader_raw(d, v) \ fwupd_device_set_version_bootloader_raw(FWUPD_DEVICE(d), v) #define fu_device_set_version_build_date(d, v) \ fwupd_device_set_version_build_date(FWUPD_DEVICE(d), v) #define fu_device_set_flashes_left(d, v) fwupd_device_set_flashes_left(FWUPD_DEVICE(d), v) #define fu_device_set_install_duration(d, v) fwupd_device_set_install_duration(FWUPD_DEVICE(d), v) #define fu_device_get_checksums(d) fwupd_device_get_checksums(FWUPD_DEVICE(d)) #define fu_device_get_flags(d) fwupd_device_get_flags(FWUPD_DEVICE(d)) #define fu_device_get_created(d) fwupd_device_get_created(FWUPD_DEVICE(d)) #define fu_device_get_modified(d) fwupd_device_get_modified(FWUPD_DEVICE(d)) #define fu_device_get_guids(d) fwupd_device_get_guids(FWUPD_DEVICE(d)) #define fu_device_get_guid_default(d) fwupd_device_get_guid_default(FWUPD_DEVICE(d)) #define fu_device_get_instance_ids(d) fwupd_device_get_instance_ids(FWUPD_DEVICE(d)) #define fu_device_get_icons(d) fwupd_device_get_icons(FWUPD_DEVICE(d)) #define fu_device_get_issues(d) fwupd_device_get_issues(FWUPD_DEVICE(d)) #define fu_device_get_name(d) fwupd_device_get_name(FWUPD_DEVICE(d)) #define fu_device_get_serial(d) fwupd_device_get_serial(FWUPD_DEVICE(d)) #define fu_device_get_summary(d) fwupd_device_get_summary(FWUPD_DEVICE(d)) #define fu_device_get_branch(d) fwupd_device_get_branch(FWUPD_DEVICE(d)) #define fu_device_get_id(d) fwupd_device_get_id(FWUPD_DEVICE(d)) #define fu_device_get_composite_id(d) fwupd_device_get_composite_id(FWUPD_DEVICE(d)) #define fu_device_get_plugin(d) fwupd_device_get_plugin(FWUPD_DEVICE(d)) #define fu_device_get_update_error(d) fwupd_device_get_update_error(FWUPD_DEVICE(d)) #define fu_device_get_update_state(d) fwupd_device_get_update_state(FWUPD_DEVICE(d)) #define fu_device_get_update_message(d) fwupd_device_get_update_message(FWUPD_DEVICE(d)) #define fu_device_get_update_image(d) fwupd_device_get_update_image(FWUPD_DEVICE(d)) #define fu_device_get_vendor(d) fwupd_device_get_vendor(FWUPD_DEVICE(d)) #define fu_device_get_version(d) fwupd_device_get_version(FWUPD_DEVICE(d)) #define fu_device_get_version_lowest(d) fwupd_device_get_version_lowest(FWUPD_DEVICE(d)) #define fu_device_get_version_bootloader(d) fwupd_device_get_version_bootloader(FWUPD_DEVICE(d)) #define fu_device_get_version_format(d) fwupd_device_get_version_format(FWUPD_DEVICE(d)) #define fu_device_get_version_raw(d) fwupd_device_get_version_raw(FWUPD_DEVICE(d)) #define fu_device_get_version_lowest_raw(d) fwupd_device_get_version_lowest_raw(FWUPD_DEVICE(d)) #define fu_device_get_version_bootloader_raw(d) \ fwupd_device_get_version_bootloader_raw(FWUPD_DEVICE(d)) #define fu_device_get_version_build_date(d) fwupd_device_get_version_build_date(FWUPD_DEVICE(d)) #define fu_device_get_vendor_ids(d) fwupd_device_get_vendor_ids(FWUPD_DEVICE(d)) #define fu_device_get_protocols(d) fwupd_device_get_protocols(FWUPD_DEVICE(d)) #define fu_device_get_flashes_left(d) fwupd_device_get_flashes_left(FWUPD_DEVICE(d)) #define fu_device_get_install_duration(d) fwupd_device_get_install_duration(FWUPD_DEVICE(d)) #define fu_device_get_release_default(d) fwupd_device_get_release_default(FWUPD_DEVICE(d)) #define fu_device_get_status(d) fwupd_device_get_status(FWUPD_DEVICE(d)) #define fu_device_set_status(d, v) fwupd_device_set_status(FWUPD_DEVICE(d), v) #define fu_device_get_percentage(d) fwupd_device_get_percentage(FWUPD_DEVICE(d)) #define fu_device_set_percentage(d, v) fwupd_device_set_percentage(FWUPD_DEVICE(d), v) /** * FuDeviceInternalFlags: * * The device internal flags. **/ typedef guint64 FuDeviceInternalFlags; /** * FU_DEVICE_INTERNAL_FLAG_NONE: * * No flags set. * * Since: 1.5.5 */ #define FU_DEVICE_INTERNAL_FLAG_NONE (0) /** * FU_DEVICE_INTERNAL_FLAG_UNKNOWN: * * Unknown flag value. * * Since: 1.5.5 */ #define FU_DEVICE_INTERNAL_FLAG_UNKNOWN G_MAXUINT64 /** * FU_DEVICE_INTERNAL_FLAG_NO_AUTO_INSTANCE_IDS: * * Do not add instance IDs from the device baseclass. * * Since: 1.5.5 */ #define FU_DEVICE_INTERNAL_FLAG_NO_AUTO_INSTANCE_IDS (1ull << 0) /** * FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER: * * Ensure the version is a valid semantic version, e.g. numbers separated with dots. * * Since: 1.5.5 */ #define FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER (1ull << 1) /** * FU_DEVICE_INTERNAL_FLAG_ONLY_SUPPORTED: * * Only devices supported in the metadata will be opened * * Since: 1.5.5 */ #define FU_DEVICE_INTERNAL_FLAG_ONLY_SUPPORTED (1ull << 2) /** * FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME: * * Set the device name from the metadata `name` if available. * * Since: 1.5.5 */ #define FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME (1ull << 3) /** * FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY: * * Set the device name from the metadata `category` if available. * * Since: 1.5.5 */ #define FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY (1ull << 4) /** * FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT: * * Set the device version format from the metadata if available. * * Since: 1.5.5 */ #define FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT (1ull << 5) /** * FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON: * * Set the device icon from the metadata if available. * * Since: 1.5.5 */ #define FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON (1ull << 6) /** * FU_DEVICE_INTERNAL_FLAG_RETRY_OPEN: * * Retry the device open up to 5 times if it fails. * * Since: 1.5.5 */ #define FU_DEVICE_INTERNAL_FLAG_RETRY_OPEN (1ull << 7) /** * FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID: * * Match GUIDs on device replug where the physical and logical IDs will be different. * * Since: 1.5.8 */ #define FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID (1ull << 8) /** * FU_DEVICE_INTERNAL_FLAG_INHERIT_ACTIVATION: * * Inherit activation status from the history database on startup. * * Since: 1.5.9 */ #define FU_DEVICE_INTERNAL_FLAG_INHERIT_ACTIVATION (1ull << 9) /** * FU_DEVICE_INTERNAL_FLAG_IS_OPEN: * * The device opened successfully and ready to use. * * Since: 1.6.1 */ #define FU_DEVICE_INTERNAL_FLAG_IS_OPEN (1ull << 10) /** * FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER: * * Do not attempt to read the device serial number. * * Since: 1.6.2 */ #define FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER (1ull << 11) /** * FU_DEVICE_INTERNAL_FLAG_AUTO_PARENT_CHILDREN: * * Automatically assign the parent for children of this device. * * Since: 1.6.2 */ #define FU_DEVICE_INTERNAL_FLAG_AUTO_PARENT_CHILDREN (1ull << 12) /** * FU_DEVICE_INTERNAL_FLAG_ATTACH_EXTRA_RESET: * * Device needs resetting twice for attach after the firmware update. * * Since: 1.6.2 */ #define FU_DEVICE_INTERNAL_FLAG_ATTACH_EXTRA_RESET (1ull << 13) /** * FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN: * * Children of the device are inhibited by the parent. * * Since: 1.6.2 */ #define FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN (1ull << 14) /** * FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE_CHILDREN: * * Do not auto-remove children in the device list. * * Since: 1.6.2 */ #define FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE_CHILDREN (1ull << 15) /** * FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN: * * Use parent to open and close the device. * * Since: 1.6.2 */ #define FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN (1ull << 16) /** * FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_BATTERY: * * Use parent for the battery level and threshold. * * Since: 1.6.3 */ #define FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_BATTERY (1ull << 17) /** * FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FALLBACK: * * Use parent for the battery level and threshold. * * Since: 1.6.4 */ #define FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FALLBACK (1ull << 18) /** * FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE: * * The device is not auto removed. * * Since 1.7.3 */ #define FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE (1ull << 19) /** * FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR: * * Set the device vendor from the metadata `developer_name` if available. * * Since: 1.7.4 */ #define FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR (1ull << 20) /** * FU_DEVICE_INTERNAL_FLAG_NO_LID_CLOSED: * * Do not allow updating when the laptop lid is closed. * * Since: 1.7.4 */ #define FU_DEVICE_INTERNAL_FLAG_NO_LID_CLOSED (1ull << 21) /** * FU_DEVICE_INTERNAL_FLAG_NO_PROBE: * * Do not probe this device. * * Since: 1.7.6 */ #define FU_DEVICE_INTERNAL_FLAG_NO_PROBE (1ull << 22) /** * FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED: * * Set the signed/unsigned payload from the metadata if available. * * Since: 1.7.6 */ #define FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED (1ull << 23) /** * FU_DEVICE_INTERNAL_AUTO_PAUSE_POLLING: * * Pause polling when reading or writing to the device * * Since: 1.8.1 */ #define FU_DEVICE_INTERNAL_AUTO_PAUSE_POLLING (1ull << 24) /** * FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG: * * Only use the device removal delay when explicitly waiting for a replug, rather than every time * the device is removed. * * Since: 1.8.1 */ #define FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG (1ull << 25) /** * FU_DEVICE_INTERNAL_FLAG_IGNORE_SYSTEM_POWER: * * Allow updating firmware when the system power is otherwise too low. * This is only really useful when updating the system battery firmware. * * Since: 1.8.11 */ #define FU_DEVICE_INTERNAL_FLAG_IGNORE_SYSTEM_POWER (1ull << 26) /** * FU_DEVICE_INTERNAL_FLAG_NO_PROBE_COMPLETE: * * Do not deallocate resources typically only required during `->probe`. * * Note: the daemon will not actually free or unref device resources, but the plugin should * still use this flag. After a a few releases and a lot of testing we'll actually flip the switch. * * Since: 1.8.12 */ #define FU_DEVICE_INTERNAL_FLAG_NO_PROBE_COMPLETE (1ull << 27) /** * FU_DEVICE_INTERNAL_FLAG_SAVE_INTO_BACKUP_REMOTE: * * Save the cabinet archive to persistent storage remote before starting the update process. * * This is useful when the network device is being updated, and different blobs inside the archive * could be required in different scenarios. For instance, if the user installs a firmware update * for a specific network device and then changes the SIM -- it might be they need the archive * again and have no internet access. * * Since: 1.8.13 */ #define FU_DEVICE_INTERNAL_FLAG_SAVE_INTO_BACKUP_REMOTE (1ull << 28) /** * FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS: * * Set the device flags from the metadata if available. * * NOTE: These flags should only affect device update, and should never be used to affect * enumeration. * * Since: 1.9.1 */ #define FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS (1ull << 29) /** * FU_DEVICE_INTERNAL_FLAG_MD_SET_VERSION: * * Set the device version from the metadata if available. * * Since: 1.9.1 */ #define FU_DEVICE_INTERNAL_FLAG_MD_SET_VERSION (1ull << 30) /** * FU_DEVICE_INTERNAL_FLAG_MD_ONLY_CHECKSUM: * * Only use the metadata *checksum* to set device attributes. * * Since: 1.9.1 */ #define FU_DEVICE_INTERNAL_FLAG_MD_ONLY_CHECKSUM (1ull << 31) /** * FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV: * * Add the `_REV` instance ID suffix. * * Since: 1.9.3 */ #define FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV (1ull << 32) /** * FU_DEVICE_INTERNAL_FLAG_UNCONNECTED: * * The device is not connected and is probably awaiting replug. * * Since: 1.9.4 */ #define FU_DEVICE_INTERNAL_FLAG_UNCONNECTED (1ull << 33) /** * FU_DEVICE_INTERNAL_FLAG_DISPLAY_REQUIRED: * * The device requires a display to be plugged in. * * Since: 1.9.6 */ #define FU_DEVICE_INTERNAL_FLAG_DISPLAY_REQUIRED (1ull << 34) /** * FU_DEVICE_INTERNAL_FLAG_UPDATE_PENDING: * * The device has an update that is waiting to be applied. * * Since: 1.9.7 */ #define FU_DEVICE_INTERNAL_FLAG_UPDATE_PENDING (1ull << 35) /** * FU_DEVICE_INTERNAL_FLAG_NO_GENERIC_GUIDS: * * Do not add generic GUIDs from outside the plugin. * * Since: 1.9.8 */ #define FU_DEVICE_INTERNAL_FLAG_NO_GENERIC_GUIDS (1ull << 36) /** * FU_DEVICE_INTERNAL_FLAG_ENFORCE_REQUIRES: * * The device uses a generic instance ID and firmware requires a parent, child, sibling or CHID * requirement. * * Since: 1.9.8 */ #define FU_DEVICE_INTERNAL_FLAG_ENFORCE_REQUIRES (1ull << 37) /** * FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE: * * The device represents the main system host firmware. * * Since: 1.9.10 */ #define FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE (1ull << 39) /** * FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE_CHILD: * * The device should be a child of the main system host firmware device. * * Since: 1.9.10 */ #define FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE_CHILD (1ull << 40) /** * FU_DEVICE_INTERNAL_FLAG_HOST_CPU: * * The device represents the main CPU device. * * Since: 1.9.10 */ #define FU_DEVICE_INTERNAL_FLAG_HOST_CPU (1ull << 41) /** * FU_DEVICE_INTERNAL_FLAG_HOST_CPU_CHILD: * * The device should be a child of the main CPU device. * * Since: 1.9.10 */ #define FU_DEVICE_INTERNAL_FLAG_HOST_CPU_CHILD (1ull << 42) /** * FU_DEVICE_INTERNAL_FLAG_EXPLICIT_ORDER: * * Do not automatically set the device order, e.g. updating the child before the parent. * * Since: 1.9.13 */ #define FU_DEVICE_INTERNAL_FLAG_EXPLICIT_ORDER (1ull << 43) /** * FU_DEVICE_INTERNAL_FLAG_REFCOUNTED_PROXY: * * Reference-count the proxy -- which is useful when using `ProxyGType`. * * Since: 1.9.15 */ #define FU_DEVICE_INTERNAL_FLAG_REFCOUNTED_PROXY (1ull << 44) /** * FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FOR_OPEN: * * Use proxy to open and close the device. * * Since: 1.9.16 */ #define FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FOR_OPEN (1ull << 45) /* accessors */ gchar * fu_device_to_string(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_add_string(FuDevice *self, guint idt, GString *str) G_GNUC_NON_NULL(1, 3); const gchar * fu_device_get_alternate_id(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_alternate_id(FuDevice *self, const gchar *alternate_id) G_GNUC_NON_NULL(1, 2); const gchar * fu_device_get_equivalent_id(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_equivalent_id(FuDevice *self, const gchar *equivalent_id) G_GNUC_NON_NULL(1, 2); void fu_device_add_guid(FuDevice *self, const gchar *guid) G_GNUC_NON_NULL(1, 2); void fu_device_add_guid_full(FuDevice *self, const gchar *guid, FuDeviceInstanceFlags flags) G_GNUC_NON_NULL(1, 2); gboolean fu_device_has_guid(FuDevice *self, const gchar *guid) G_GNUC_NON_NULL(1); void fu_device_add_instance_id(FuDevice *self, const gchar *instance_id) G_GNUC_NON_NULL(1); void fu_device_add_instance_id_full(FuDevice *self, const gchar *instance_id, FuDeviceInstanceFlags flags) G_GNUC_NON_NULL(1, 2); FuDevice * fu_device_get_alternate(FuDevice *self) G_GNUC_NON_NULL(1); FuDevice * fu_device_get_root(FuDevice *self) G_GNUC_NON_NULL(1); FuDevice * fu_device_get_parent(FuDevice *self) G_GNUC_NON_NULL(1); GPtrArray * fu_device_get_children(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_add_child(FuDevice *self, FuDevice *child) G_GNUC_NON_NULL(1, 2); void fu_device_remove_child(FuDevice *self, FuDevice *child) G_GNUC_NON_NULL(1, 2); void fu_device_add_parent_guid(FuDevice *self, const gchar *guid) G_GNUC_NON_NULL(1, 2); void fu_device_add_parent_physical_id(FuDevice *self, const gchar *physical_id) G_GNUC_NON_NULL(1, 2); void fu_device_add_parent_backend_id(FuDevice *self, const gchar *backend_id) G_GNUC_NON_NULL(1, 2); void fu_device_add_counterpart_guid(FuDevice *self, const gchar *guid) G_GNUC_NON_NULL(1, 2); FuDevice * fu_device_get_proxy(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_proxy(FuDevice *self, FuDevice *proxy) G_GNUC_NON_NULL(1); FuDevice * fu_device_get_proxy_with_fallback(FuDevice *self) G_GNUC_NON_NULL(1); const gchar * fu_device_get_metadata(FuDevice *self, const gchar *key) G_GNUC_NON_NULL(1, 2); gboolean fu_device_get_metadata_boolean(FuDevice *self, const gchar *key) G_GNUC_NON_NULL(1, 2); guint fu_device_get_metadata_integer(FuDevice *self, const gchar *key) G_GNUC_NON_NULL(1, 2); void fu_device_remove_metadata(FuDevice *self, const gchar *key) G_GNUC_NON_NULL(1, 2); void fu_device_set_metadata(FuDevice *self, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2); void fu_device_set_metadata_boolean(FuDevice *self, const gchar *key, gboolean value) G_GNUC_NON_NULL(1, 2); void fu_device_set_metadata_integer(FuDevice *self, const gchar *key, guint value) G_GNUC_NON_NULL(1, 2); void fu_device_set_id(FuDevice *self, const gchar *id) G_GNUC_NON_NULL(1); void fu_device_set_version_format(FuDevice *self, FwupdVersionFormat fmt) G_GNUC_NON_NULL(1); void fu_device_set_version(FuDevice *self, const gchar *version) G_GNUC_NON_NULL(1); void fu_device_set_version_lowest(FuDevice *self, const gchar *version) G_GNUC_NON_NULL(1); void fu_device_set_version_bootloader(FuDevice *self, const gchar *version) G_GNUC_NON_NULL(1); void fu_device_set_version_raw(FuDevice *self, guint64 version_raw) G_GNUC_NON_NULL(1); void fu_device_set_version_u16(FuDevice *self, guint16 version_raw) G_GNUC_NON_NULL(1); void fu_device_set_version_u24(FuDevice *self, guint32 version_raw) G_GNUC_NON_NULL(1); void fu_device_set_version_u32(FuDevice *self, guint32 version_raw) G_GNUC_NON_NULL(1); void fu_device_set_version_u64(FuDevice *self, guint64 version_raw) G_GNUC_NON_NULL(1); void fu_device_inhibit(FuDevice *self, const gchar *inhibit_id, const gchar *reason) G_GNUC_NON_NULL(1, 2); void fu_device_uninhibit(FuDevice *self, const gchar *inhibit_id) G_GNUC_NON_NULL(1, 2); void fu_device_add_problem(FuDevice *self, FwupdDeviceProblem problem) G_GNUC_NON_NULL(1); void fu_device_remove_problem(FuDevice *self, FwupdDeviceProblem problem) G_GNUC_NON_NULL(1); gboolean fu_device_has_problem(FuDevice *self, FwupdDeviceProblem problem) G_GNUC_NON_NULL(1); gboolean fu_device_has_inhibit(FuDevice *self, const gchar *inhibit_id) G_GNUC_NON_NULL(1, 2); const gchar * fu_device_get_physical_id(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_physical_id(FuDevice *self, const gchar *physical_id) G_GNUC_NON_NULL(1); const gchar * fu_device_get_logical_id(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_logical_id(FuDevice *self, const gchar *logical_id) G_GNUC_NON_NULL(1); const gchar * fu_device_get_backend_id(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_backend_id(FuDevice *self, const gchar *backend_id) G_GNUC_NON_NULL(1); const gchar * fu_device_get_proxy_guid(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_proxy_guid(FuDevice *self, const gchar *proxy_guid) G_GNUC_NON_NULL(1); guint fu_device_get_priority(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_priority(FuDevice *self, guint priority) G_GNUC_NON_NULL(1); void fu_device_add_flag(FuDevice *self, FwupdDeviceFlags flag) G_GNUC_NON_NULL(1); void fu_device_remove_flag(FuDevice *self, FwupdDeviceFlags flag) G_GNUC_NON_NULL(1); const gchar * fu_device_get_custom_flags(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_custom_flags(FuDevice *self, const gchar *custom_flags) G_GNUC_NON_NULL(1); void fu_device_set_name(FuDevice *self, const gchar *value) G_GNUC_NON_NULL(1); void fu_device_set_vendor(FuDevice *self, const gchar *vendor) G_GNUC_NON_NULL(1); guint fu_device_get_remove_delay(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_remove_delay(FuDevice *self, guint remove_delay) G_GNUC_NON_NULL(1); guint fu_device_get_acquiesce_delay(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_acquiesce_delay(FuDevice *self, guint acquiesce_delay) G_GNUC_NON_NULL(1); void fu_device_set_firmware_size(FuDevice *self, guint64 size) G_GNUC_NON_NULL(1); void fu_device_set_firmware_size_min(FuDevice *self, guint64 size_min) G_GNUC_NON_NULL(1); void fu_device_set_firmware_size_max(FuDevice *self, guint64 size_max) G_GNUC_NON_NULL(1); guint64 fu_device_get_firmware_size_min(FuDevice *self) G_GNUC_NON_NULL(1); guint64 fu_device_get_firmware_size_max(FuDevice *self) G_GNUC_NON_NULL(1); guint fu_device_get_battery_level(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_battery_level(FuDevice *self, guint battery_level) G_GNUC_NON_NULL(1); guint fu_device_get_battery_threshold(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_battery_threshold(FuDevice *self, guint battery_threshold) G_GNUC_NON_NULL(1); void fu_device_set_update_state(FuDevice *self, FwupdUpdateState update_state) G_GNUC_NON_NULL(1); void fu_device_set_context(FuDevice *self, FuContext *ctx) G_GNUC_NON_NULL(1); FuContext * fu_device_get_context(FuDevice *self) G_GNUC_NON_NULL(1); GType fu_device_get_specialized_gtype(FuDevice *self) G_GNUC_NON_NULL(1); GType fu_device_get_proxy_gtype(FuDevice *self) G_GNUC_NON_NULL(1); GType fu_device_get_firmware_gtype(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_set_firmware_gtype(FuDevice *self, GType firmware_gtype) G_GNUC_NON_NULL(1); void fu_device_add_internal_flag(FuDevice *self, FuDeviceInternalFlags flag) G_GNUC_NON_NULL(1); void fu_device_remove_internal_flag(FuDevice *self, FuDeviceInternalFlags flag) G_GNUC_NON_NULL(1); gboolean fu_device_has_internal_flag(FuDevice *self, FuDeviceInternalFlags flag) G_GNUC_NON_NULL(1); gboolean fu_device_get_results(FuDevice *self, GError **error) G_GNUC_NON_NULL(1); gboolean fu_device_write_firmware(FuDevice *self, GBytes *fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3); FuFirmware * fu_device_prepare_firmware(FuDevice *self, GBytes *fw, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); FuFirmware * fu_device_read_firmware(FuDevice *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GBytes * fu_device_dump_firmware(FuDevice *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_device_attach(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_device_detach(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_device_attach_full(FuDevice *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_device_detach_full(FuDevice *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_device_reload(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_device_prepare(FuDevice *self, FuProgress *progress, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_device_cleanup(FuDevice *self, FuProgress *progress, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fu_device_incorporate(FuDevice *self, FuDevice *donor) G_GNUC_NON_NULL(1); void fu_device_incorporate_flag(FuDevice *self, FuDevice *donor, FwupdDeviceFlags flag) G_GNUC_NON_NULL(1); gboolean fu_device_open(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_device_close(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_device_probe(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_device_setup(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_device_rescan(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_device_activate(FuDevice *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); void fu_device_probe_invalidate(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_probe_complete(FuDevice *self) G_GNUC_NON_NULL(1); gboolean fu_device_poll(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); void fu_device_set_poll_interval(FuDevice *self, guint interval) G_GNUC_NON_NULL(1); void fu_device_retry_set_delay(FuDevice *self, guint delay) G_GNUC_NON_NULL(1); void fu_device_retry_add_recovery(FuDevice *self, GQuark domain, gint code, FuDeviceRetryFunc func) G_GNUC_NON_NULL(1); gboolean fu_device_retry(FuDevice *self, FuDeviceRetryFunc func, guint count, gpointer user_data, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_device_retry_full(FuDevice *self, FuDeviceRetryFunc func, guint count, guint delay, gpointer user_data, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); void fu_device_sleep(FuDevice *self, guint delay_ms) G_GNUC_NON_NULL(1); void fu_device_sleep_full(FuDevice *self, guint delay_ms, FuProgress *progress) G_GNUC_NON_NULL(1); gboolean fu_device_bind_driver(FuDevice *self, const gchar *subsystem, const gchar *driver, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_device_unbind_driver(FuDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GHashTable * fu_device_report_metadata_pre(FuDevice *self) G_GNUC_NON_NULL(1); GHashTable * fu_device_report_metadata_post(FuDevice *self) G_GNUC_NON_NULL(1); void fu_device_add_security_attrs(FuDevice *self, FuSecurityAttrs *attrs) G_GNUC_NON_NULL(1, 2); void fu_device_register_private_flag(FuDevice *self, guint64 value, const gchar *value_str) G_GNUC_NON_NULL(1, 3); void fu_device_add_private_flag(FuDevice *self, guint64 flag) G_GNUC_NON_NULL(1); void fu_device_remove_private_flag(FuDevice *self, guint64 flag) G_GNUC_NON_NULL(1); gboolean fu_device_has_private_flag(FuDevice *self, guint64 flag) G_GNUC_NON_NULL(1); gboolean fu_device_emit_request(FuDevice *self, FwupdRequest *request, FuProgress *progress, GError **error) G_GNUC_NON_NULL(1, 2); FwupdSecurityAttr * fu_device_security_attr_new(FuDevice *self, const gchar *appstream_id) G_GNUC_NON_NULL(1, 2); const gchar * fu_device_get_instance_str(FuDevice *self, const gchar *key) G_GNUC_NON_NULL(1, 2); void fu_device_add_instance_str(FuDevice *self, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2); void fu_device_add_instance_strsafe(FuDevice *self, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2); void fu_device_add_instance_strup(FuDevice *self, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2); void fu_device_add_instance_u4(FuDevice *self, const gchar *key, guint8 value) G_GNUC_NON_NULL(1, 2); void fu_device_add_instance_u8(FuDevice *self, const gchar *key, guint8 value) G_GNUC_NON_NULL(1, 2); void fu_device_add_instance_u16(FuDevice *self, const gchar *key, guint16 value) G_GNUC_NON_NULL(1, 2); void fu_device_add_instance_u32(FuDevice *self, const gchar *key, guint32 value) G_GNUC_NON_NULL(1, 2); gboolean fu_device_build_instance_id(FuDevice *self, GError **error, const gchar *subsystem, ...) G_GNUC_NULL_TERMINATED G_GNUC_NON_NULL(1, 3); gboolean fu_device_build_instance_id_full(FuDevice *self, FuDeviceInstanceFlags flags, GError **error, const gchar *subsystem, ...) G_GNUC_NULL_TERMINATED G_GNUC_NON_NULL(1, 4); FuDeviceLocker * fu_device_poll_locker_new(FuDevice *self, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-dfu-firmware-private.h000066400000000000000000000010031460375044200226100ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-dfu-firmware.h" guint8 fu_dfu_firmware_get_footer_len(FuDfuFirmware *self) G_GNUC_NON_NULL(1); GByteArray * fu_dfu_firmware_append_footer(FuDfuFirmware *self, GBytes *contents, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_dfu_firmware_parse_footer(FuDfuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-dfu-firmware.c000066400000000000000000000224221460375044200211430ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-common.h" #include "fu-crc.h" #include "fu-dfu-firmware-private.h" #include "fu-dfu-firmware-struct.h" /** * FuDfuFirmware: * * A DFU firmware image. * * See also: [class@FuFirmware] */ typedef struct { guint16 vid; guint16 pid; guint16 release; guint16 dfu_version; guint8 footer_len; } FuDfuFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuDfuFirmware, fu_dfu_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_dfu_firmware_get_instance_private(o)) static void fu_dfu_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware); FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "vendor", priv->vid); fu_xmlb_builder_insert_kx(bn, "product", priv->pid); fu_xmlb_builder_insert_kx(bn, "release", priv->release); fu_xmlb_builder_insert_kx(bn, "dfu_version", priv->dfu_version); } /* private */ guint8 fu_dfu_firmware_get_footer_len(FuDfuFirmware *self) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0); return priv->footer_len; } /** * fu_dfu_firmware_get_vid: * @self: a #FuDfuFirmware * * Gets the vendor ID, or 0xffff for no restriction. * * Returns: integer * * Since: 1.3.3 **/ guint16 fu_dfu_firmware_get_vid(FuDfuFirmware *self) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0); return priv->vid; } /** * fu_dfu_firmware_get_pid: * @self: a #FuDfuFirmware * * Gets the product ID, or 0xffff for no restriction. * * Returns: integer * * Since: 1.3.3 **/ guint16 fu_dfu_firmware_get_pid(FuDfuFirmware *self) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0); return priv->pid; } /** * fu_dfu_firmware_get_release: * @self: a #FuDfuFirmware * * Gets the device ID, or 0xffff for no restriction. * * Returns: integer * * Since: 1.3.3 **/ guint16 fu_dfu_firmware_get_release(FuDfuFirmware *self) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0); return priv->release; } /** * fu_dfu_firmware_get_version: * @self: a #FuDfuFirmware * * Gets the file format version with is 0x0100 by default. * * Returns: integer * * Since: 1.3.3 **/ guint16 fu_dfu_firmware_get_version(FuDfuFirmware *self) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_FIRMWARE(self), 0x0); return priv->dfu_version; } /** * fu_dfu_firmware_set_vid: * @self: a #FuDfuFirmware * @vid: vendor ID, or 0xffff if the firmware should match any vendor * * Sets the vendor ID. * * Since: 1.3.3 **/ void fu_dfu_firmware_set_vid(FuDfuFirmware *self, guint16 vid) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DFU_FIRMWARE(self)); priv->vid = vid; } /** * fu_dfu_firmware_set_pid: * @self: a #FuDfuFirmware * @pid: product ID, or 0xffff if the firmware should match any product * * Sets the product ID. * * Since: 1.3.3 **/ void fu_dfu_firmware_set_pid(FuDfuFirmware *self, guint16 pid) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DFU_FIRMWARE(self)); priv->pid = pid; } /** * fu_dfu_firmware_set_release: * @self: a #FuDfuFirmware * @release: release, or 0xffff if the firmware should match any release * * Sets the release for the dfu firmware. * * Since: 1.3.3 **/ void fu_dfu_firmware_set_release(FuDfuFirmware *self, guint16 release) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DFU_FIRMWARE(self)); priv->release = release; } /** * fu_dfu_firmware_set_version: * @self: a #FuDfuFirmware * @version: integer * * Sets the file format version. * * Since: 1.3.3 **/ void fu_dfu_firmware_set_version(FuDfuFirmware *self, guint16 version) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DFU_FIRMWARE(self)); priv->dfu_version = version; } static gboolean fu_dfu_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_dfu_ftr_validate_bytes(fw, g_bytes_get_size(fw) - FU_STRUCT_DFU_FTR_SIZE, error); } gboolean fu_dfu_firmware_parse_footer(FuDfuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); gsize bufsz; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GByteArray) st = NULL; /* parse */ st = fu_struct_dfu_ftr_parse(buf, bufsz, bufsz - FU_STRUCT_DFU_FTR_SIZE, error); if (st == NULL) return FALSE; priv->vid = fu_struct_dfu_ftr_get_vid(st); priv->pid = fu_struct_dfu_ftr_get_pid(st); priv->release = fu_struct_dfu_ftr_get_release(st); priv->dfu_version = fu_struct_dfu_ftr_get_ver(st); priv->footer_len = fu_struct_dfu_ftr_get_len(st); /* verify the checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint32 crc_new = ~fu_crc32(buf, bufsz - 4); if (fu_struct_dfu_ftr_get_crc(st) != crc_new) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "CRC failed, expected 0x%04x, got 0x%04x", crc_new, fu_struct_dfu_ftr_get_crc(st)); return FALSE; } } /* check reported length */ if (priv->footer_len > bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "reported footer size 0x%04x larger than file 0x%04x", (guint)priv->footer_len, (guint)bufsz); return FALSE; } /* success */ return TRUE; } static gboolean fu_dfu_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware); FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); gsize len = g_bytes_get_size(fw); g_autoptr(GBytes) contents = NULL; /* parse footer */ if (!fu_dfu_firmware_parse_footer(self, fw, flags, error)) return FALSE; /* trim footer off */ contents = fu_bytes_new_offset(fw, 0, len - priv->footer_len, error); if (contents == NULL) return FALSE; fu_firmware_set_bytes(firmware, contents); return TRUE; } GByteArray * fu_dfu_firmware_append_footer(FuDfuFirmware *self, GBytes *contents, GError **error) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) st = fu_struct_dfu_ftr_new(); /* add the raw firmware data, the footer-less-CRC, and only then the CRC */ fu_byte_array_append_bytes(buf, contents); fu_struct_dfu_ftr_set_release(st, priv->release); fu_struct_dfu_ftr_set_pid(st, priv->pid); fu_struct_dfu_ftr_set_vid(st, priv->vid); fu_struct_dfu_ftr_set_ver(st, priv->dfu_version); g_byte_array_append(buf, st->data, st->len - sizeof(guint32)); fu_byte_array_append_uint32(buf, ~fu_crc32(buf->data, buf->len), G_LITTLE_ENDIAN); return g_steal_pointer(&buf); } static GByteArray * fu_dfu_firmware_write(FuFirmware *firmware, GError **error) { FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware); g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); g_autoptr(GBytes) fw = NULL; /* can only contain one image */ if (images->len > 1) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "DFU only supports writing one image"); return NULL; } /* add footer */ fw = fu_firmware_get_bytes_with_patches(firmware, error); if (fw == NULL) return NULL; return fu_dfu_firmware_append_footer(self, fw, error); } static gboolean fu_dfu_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuDfuFirmware *self = FU_DFU_FIRMWARE(firmware); FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); guint64 tmp; /* optional properties */ tmp = xb_node_query_text_as_uint(n, "vendor", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) priv->vid = tmp; tmp = xb_node_query_text_as_uint(n, "product", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) priv->pid = tmp; tmp = xb_node_query_text_as_uint(n, "release", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) priv->release = tmp; tmp = xb_node_query_text_as_uint(n, "dfu_version", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) priv->dfu_version = tmp; /* success */ return TRUE; } static void fu_dfu_firmware_init(FuDfuFirmware *self) { FuDfuFirmwarePrivate *priv = GET_PRIVATE(self); priv->vid = 0xffff; priv->pid = 0xffff; priv->release = 0xffff; priv->dfu_version = FU_DFU_FIRMARE_VERSION_DFU_1_0; fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); } static void fu_dfu_firmware_class_init(FuDfuFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_dfu_firmware_check_magic; klass_firmware->export = fu_dfu_firmware_export; klass_firmware->parse = fu_dfu_firmware_parse; klass_firmware->write = fu_dfu_firmware_write; klass_firmware->build = fu_dfu_firmware_build; } /** * fu_dfu_firmware_new: * * Creates a new #FuFirmware of sub type Dfu * * Since: 1.3.3 **/ FuFirmware * fu_dfu_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_DFU_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-dfu-firmware.h000066400000000000000000000035661460375044200211600ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_DFU_FIRMWARE (fu_dfu_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuDfuFirmware, fu_dfu_firmware, FU, DFU_FIRMWARE, FuFirmware) struct _FuDfuFirmwareClass { FuFirmwareClass parent_class; }; /** * FU_DFU_FIRMARE_VERSION_UNKNOWN: * * Unknown version of the DFU standard in BCD format. * * Since: 1.6.1 **/ #define FU_DFU_FIRMARE_VERSION_UNKNOWN (0u) /** * FU_DFU_FIRMARE_VERSION_DFU_1_0: * * The 1.0 version of the DFU standard in BCD format. * * Since: 1.6.1 **/ #define FU_DFU_FIRMARE_VERSION_DFU_1_0 (0x0100) /** * FU_DFU_FIRMARE_VERSION_DFU_1_1: * * The 1.1 version of the DFU standard in BCD format. * * Since: 1.6.1 **/ #define FU_DFU_FIRMARE_VERSION_DFU_1_1 (0x0110) /** * FU_DFU_FIRMARE_VERSION_DFUSE: * * The DfuSe version of the DFU standard in BCD format, defined by ST. * * Since: 1.6.1 **/ #define FU_DFU_FIRMARE_VERSION_DFUSE (0x011a) /** * FU_DFU_FIRMARE_VERSION_ATMEL_AVR: * * The Atmel AVR version of the DFU standard in BCD format. * * Since: 1.6.1 **/ #define FU_DFU_FIRMARE_VERSION_ATMEL_AVR (0xff01) FuFirmware * fu_dfu_firmware_new(void); guint16 fu_dfu_firmware_get_vid(FuDfuFirmware *self) G_GNUC_NON_NULL(1); guint16 fu_dfu_firmware_get_pid(FuDfuFirmware *self) G_GNUC_NON_NULL(1); guint16 fu_dfu_firmware_get_release(FuDfuFirmware *self) G_GNUC_NON_NULL(1); guint16 fu_dfu_firmware_get_version(FuDfuFirmware *self) G_GNUC_NON_NULL(1); void fu_dfu_firmware_set_vid(FuDfuFirmware *self, guint16 vid) G_GNUC_NON_NULL(1); void fu_dfu_firmware_set_pid(FuDfuFirmware *self, guint16 pid) G_GNUC_NON_NULL(1); void fu_dfu_firmware_set_release(FuDfuFirmware *self, guint16 release) G_GNUC_NON_NULL(1); void fu_dfu_firmware_set_version(FuDfuFirmware *self, guint16 version) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-dfu-firmware.rs000066400000000000000000000013571460375044200213510ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, ValidateBytes, Parse)] struct DfuFtr { release: u16le, pid: u16le, vid: u16le, ver: u16le, sig: [char; 3] == "UFD", len: u8 = $struct_size, crc: u32le, } #[derive(New, ValidateBytes, ParseBytes)] struct DfuseHdr { sig: [char; 5] == "DfuSe", ver: u8 == 0x01, image_size: u32le, targets: u8, } #[derive(New, Validate, ParseBytes)] struct DfuseImage { sig: [char; 6] == "Target", alt_setting: u8, target_named: u32le, target_name: [char; 255], target_size: u32le, chunks: u32le, } #[derive(New, Validate, Parse)] struct DfuseElement { address: u32le, size: u32le, } fwupd-1.9.16/libfwupdplugin/fu-dfuse-firmware.c000066400000000000000000000175601460375044200215020ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-chunk-private.h" #include "fu-common.h" #include "fu-dfu-firmware-private.h" #include "fu-dfu-firmware-struct.h" #include "fu-dfuse-firmware.h" /** * FuDfuseFirmware: * * A DfuSe firmware image. * * See also: [class@FuDfuFirmware] */ G_DEFINE_TYPE(FuDfuseFirmware, fu_dfuse_firmware, FU_TYPE_DFU_FIRMWARE) static FuChunk * fu_firmware_image_chunk_parse(FuDfuseFirmware *self, GBytes *bytes, gsize *offset, GError **error) { gsize bufsz = 0; gsize ftrlen = fu_dfu_firmware_get_footer_len(FU_DFU_FIRMWARE(self)); const guint8 *buf = g_bytes_get_data(bytes, &bufsz); g_autoptr(FuChunk) chk = NULL; g_autoptr(GByteArray) st_ele = NULL; g_autoptr(GBytes) blob = NULL; /* create new chunk */ st_ele = fu_struct_dfuse_element_parse(buf, bufsz - ftrlen, *offset, error); if (st_ele == NULL) return NULL; *offset += st_ele->len; blob = fu_bytes_new_offset(bytes, *offset, fu_struct_dfuse_element_get_size(st_ele), error); if (blob == NULL) return NULL; chk = fu_chunk_bytes_new(blob); fu_chunk_set_address(chk, fu_struct_dfuse_element_get_address(st_ele)); *offset += fu_chunk_get_data_sz(chk); /* success */ return g_steal_pointer(&chk); } static FuFirmware * fu_dfuse_firmware_image_parse(FuDfuseFirmware *self, GBytes *bytes, gsize *offset, GError **error) { guint chunks; g_autoptr(FuFirmware) image = fu_firmware_new(); g_autoptr(GByteArray) st_img = NULL; /* verify image signature */ st_img = fu_struct_dfuse_image_parse_bytes(bytes, *offset, error); if (st_img == NULL) return NULL; /* set properties */ fu_firmware_set_idx(image, fu_struct_dfuse_image_get_alt_setting(st_img)); if (fu_struct_dfuse_image_get_target_named(st_img) == 0x01) { g_autofree gchar *target_name = fu_struct_dfuse_image_get_target_name(st_img); fu_firmware_set_id(image, target_name); } /* no chunks */ chunks = fu_struct_dfuse_image_get_chunks(st_img); if (chunks == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "DfuSe image has no chunks"); return NULL; } /* parse chunks */ *offset += st_img->len; for (guint j = 0; j < chunks; j++) { g_autoptr(FuChunk) chk = NULL; chk = fu_firmware_image_chunk_parse(self, bytes, offset, error); if (chk == NULL) return NULL; fu_firmware_add_chunk(image, chk); } /* success */ return g_steal_pointer(&image); } static gboolean fu_dfuse_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_dfuse_hdr_validate_bytes(fw, offset, error); } static gboolean fu_dfuse_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuDfuFirmware *dfu_firmware = FU_DFU_FIRMWARE(firmware); gsize bufsz = g_bytes_get_size(fw); guint8 targets = 0; g_autoptr(GByteArray) st_hdr = NULL; /* DFU footer first */ if (!fu_dfu_firmware_parse_footer(dfu_firmware, fw, flags, error)) return FALSE; /* parse */ st_hdr = fu_struct_dfuse_hdr_parse_bytes(fw, offset, error); if (st_hdr == NULL) return FALSE; /* check image size */ if (fu_struct_dfuse_hdr_get_image_size(st_hdr) != bufsz - fu_dfu_firmware_get_footer_len(dfu_firmware)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid DfuSe image size, " "got %" G_GUINT32_FORMAT ", " "expected %" G_GSIZE_FORMAT, fu_struct_dfuse_hdr_get_image_size(st_hdr), bufsz - fu_dfu_firmware_get_footer_len(dfu_firmware)); return FALSE; } /* parse the image targets */ targets = fu_struct_dfuse_hdr_get_targets(st_hdr); offset += st_hdr->len; for (guint i = 0; i < targets; i++) { g_autoptr(FuFirmware) image = NULL; image = fu_dfuse_firmware_image_parse(FU_DFUSE_FIRMWARE(firmware), fw, &offset, error); if (image == NULL) return FALSE; if (!fu_firmware_add_image_full(firmware, image, error)) return FALSE; } return TRUE; } static GBytes * fu_firmware_chunk_write(FuDfuseFirmware *self, FuChunk *chk) { g_autoptr(GByteArray) st_ele = fu_struct_dfuse_element_new(); fu_struct_dfuse_element_set_address(st_ele, fu_chunk_get_address(chk)); fu_struct_dfuse_element_set_size(st_ele, fu_chunk_get_data_sz(chk)); g_byte_array_append(st_ele, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); return g_bytes_new(st_ele->data, st_ele->len); } static GBytes * fu_dfuse_firmware_write_image(FuDfuseFirmware *self, FuFirmware *image, GError **error) { gsize totalsz = 0; g_autoptr(GByteArray) st_img = fu_struct_dfuse_image_new(); g_autoptr(GPtrArray) blobs = NULL; g_autoptr(GPtrArray) chunks = NULL; /* get total size */ blobs = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); chunks = fu_firmware_get_chunks(image, error); if (chunks == NULL) return NULL; for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); GBytes *bytes = fu_firmware_chunk_write(self, chk); g_ptr_array_add(blobs, bytes); totalsz += g_bytes_get_size(bytes); } /* add prefix */ fu_struct_dfuse_image_set_alt_setting(st_img, fu_firmware_get_idx(image)); if (fu_firmware_get_id(image) != NULL) { fu_struct_dfuse_image_set_target_named(st_img, 0x01); if (!fu_struct_dfuse_image_set_target_name(st_img, fu_firmware_get_id(image), error)) return NULL; } fu_struct_dfuse_image_set_target_size(st_img, totalsz); fu_struct_dfuse_image_set_chunks(st_img, chunks->len); /* copy data */ for (guint i = 0; i < blobs->len; i++) { GBytes *blob = g_ptr_array_index(blobs, i); fu_byte_array_append_bytes(st_img, blob); } return g_bytes_new(st_img->data, st_img->len); } static GByteArray * fu_dfuse_firmware_write(FuFirmware *firmware, GError **error) { FuDfuseFirmware *self = FU_DFUSE_FIRMWARE(firmware); gsize totalsz = 0; g_autoptr(GByteArray) st_hdr = fu_struct_dfuse_hdr_new(); g_autoptr(GBytes) blob_noftr = NULL; g_autoptr(GPtrArray) blobs = NULL; g_autoptr(GPtrArray) images = NULL; /* create mutable output buffer */ blobs = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); images = fu_firmware_get_images(FU_FIRMWARE(firmware)); for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) blob = NULL; blob = fu_dfuse_firmware_write_image(self, img, error); if (blob == NULL) return NULL; totalsz += g_bytes_get_size(blob); g_ptr_array_add(blobs, g_steal_pointer(&blob)); } /* DfuSe header */ fu_struct_dfuse_hdr_set_image_size(st_hdr, st_hdr->len + totalsz); if (images->len > G_MAXUINT8) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "too many (%u) images to write DfuSe file", images->len); return NULL; } fu_struct_dfuse_hdr_set_targets(st_hdr, (guint8)images->len); /* copy images */ for (guint i = 0; i < blobs->len; i++) { GBytes *blob = g_ptr_array_index(blobs, i); fu_byte_array_append_bytes(st_hdr, blob); } /* return blob */ blob_noftr = g_bytes_new(st_hdr->data, st_hdr->len); return fu_dfu_firmware_append_footer(FU_DFU_FIRMWARE(firmware), blob_noftr, error); } static void fu_dfuse_firmware_init(FuDfuseFirmware *self) { fu_dfu_firmware_set_version(FU_DFU_FIRMWARE(self), FU_DFU_FIRMARE_VERSION_DFUSE); fu_firmware_set_images_max(FU_FIRMWARE(self), 255); } static void fu_dfuse_firmware_class_init(FuDfuseFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_dfuse_firmware_check_magic; klass_firmware->parse = fu_dfuse_firmware_parse; klass_firmware->write = fu_dfuse_firmware_write; } /** * fu_dfuse_firmware_new: * * Creates a new #FuFirmware of sub type DfuSe * * Since: 1.5.6 **/ FuFirmware * fu_dfuse_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_DFUSE_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-dfuse-firmware.h000066400000000000000000000006461460375044200215040ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-dfu-firmware.h" #define FU_TYPE_DFUSE_FIRMWARE (fu_dfuse_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuDfuseFirmware, fu_dfuse_firmware, FU, DFUSE_FIRMWARE, FuDfuFirmware) struct _FuDfuseFirmwareClass { FuDfuFirmwareClass parent_class; }; FuFirmware * fu_dfuse_firmware_new(void); fwupd-1.9.16/libfwupdplugin/fu-dpaux-device.c000066400000000000000000000301741460375044200211340ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDpauxDevice" #include "config.h" #include #include "fu-dpaux-device.h" #include "fu-dpaux-struct.h" #include "fu-dump.h" #include "fu-io-channel.h" #include "fu-string.h" /** * FuDpauxDevice * * A Display Port AUX device. * * See also: #FuUdevDevice */ typedef struct { guint32 dpcd_ieee_oui; guint8 dpcd_hw_rev; gchar *dpcd_dev_id; } FuDpauxDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuDpauxDevice, fu_dpaux_device, FU_TYPE_UDEV_DEVICE) enum { PROP_0, PROP_DPCD_IEEE_OUI, PROP_LAST }; #define GET_PRIVATE(o) (fu_dpaux_device_get_instance_private(o)) #define FU_DPAUX_DEVICE_READ_TIMEOUT 10 /* ms */ static void fu_dpaux_device_to_string(FuDevice *device, guint idt, GString *str) { FuDpauxDevice *self = FU_DPAUX_DEVICE(device); FuDpauxDevicePrivate *priv = GET_PRIVATE(self); FU_DEVICE_CLASS(fu_dpaux_device_parent_class)->to_string(device, idt, str); if (priv->dpcd_ieee_oui != 0) fu_string_append_kx(str, idt, "DpcdIeeeOui", priv->dpcd_ieee_oui); if (priv->dpcd_hw_rev != 0) fu_string_append_kx(str, idt, "DpcdHwRev", priv->dpcd_hw_rev); if (priv->dpcd_dev_id != NULL) fu_string_append(str, idt, "DpcdDevId", priv->dpcd_dev_id); } static void fu_dpaux_device_invalidate(FuDevice *device) { FuDpauxDevice *self = FU_DPAUX_DEVICE(device); FuDpauxDevicePrivate *priv = GET_PRIVATE(self); priv->dpcd_ieee_oui = 0; priv->dpcd_hw_rev = 0; g_clear_pointer(&priv->dpcd_dev_id, g_free); } static gboolean fu_dpaux_device_probe(FuDevice *device, GError **error) { const gchar *tmp; /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_dpaux_device_parent_class)->probe(device, error)) return FALSE; /* get from sysfs if not set from tests */ if (fu_device_get_logical_id(device) == NULL && fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device)) != NULL) { g_autofree gchar *logical_id = NULL; logical_id = g_path_get_basename(fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device))); fu_device_set_logical_id(device, logical_id); } if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "pci,drm_dp_aux_dev", error)) return FALSE; /* only populated on real system, test suite won't have udev_device set */ tmp = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "name", NULL); if (tmp != NULL) fu_device_set_name(device, tmp); return TRUE; } static gboolean fu_dpaux_device_setup(FuDevice *device, GError **error) { FuContext *ctx = fu_device_get_context(device); FuDpauxDevice *self = FU_DPAUX_DEVICE(device); FuDpauxDevicePrivate *priv = GET_PRIVATE(self); guint8 buf[FU_STRUCT_DPAUX_DPCD_SIZE] = {0x0}; g_autoptr(GByteArray) st = NULL; /* ignore all Framework FRANDGCP07 BIOS version 3.02 */ if (fu_device_get_name(device) != NULL && g_str_has_prefix(fu_device_get_name(device), "AMDGPU DM") && fu_context_has_hwid_guid(ctx, "32d49d99-414b-55d5-813b-12aaf0335b58")) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "reading %s DPCD is broken on this hardware, " "you need to update the system BIOS", fu_device_get_name(device)); return FALSE; } if (!fu_dpaux_device_read(self, FU_DPAUX_DEVICE_DPCD_OFFSET_BRANCH_DEVICE, buf, sizeof(buf), FU_DPAUX_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "DPCD read failed: "); return FALSE; } st = fu_struct_dpaux_dpcd_parse(buf, sizeof(buf), 0x0, error); if (st == NULL) return FALSE; g_clear_pointer(&priv->dpcd_dev_id, g_free); priv->dpcd_ieee_oui = fu_struct_dpaux_dpcd_get_ieee_oui(st); priv->dpcd_hw_rev = fu_struct_dpaux_dpcd_get_hw_rev(st); priv->dpcd_dev_id = fu_struct_dpaux_dpcd_get_dev_id(st); fu_device_set_version_raw(device, fu_struct_dpaux_dpcd_get_fw_ver(st)); return TRUE; } /** * fu_dpaux_device_get_dpcd_ieee_oui: * @self: a #FuDpauxDevice * * Gets the DPCD IEEE OUI. * * Returns: integer * * Since: 1.9.8 **/ guint32 fu_dpaux_device_get_dpcd_ieee_oui(FuDpauxDevice *self) { FuDpauxDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DPAUX_DEVICE(self), G_MAXUINT32); return priv->dpcd_ieee_oui; } /** * fu_dpaux_device_set_dpcd_ieee_oui: * @self: a #FuDpauxDevice * @dpcd_ieee_oui: integer * * Sets the DPCD IEEE OUI. * * Since: 1.9.8 **/ void fu_dpaux_device_set_dpcd_ieee_oui(FuDpauxDevice *self, guint32 dpcd_ieee_oui) { FuDpauxDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DPAUX_DEVICE(self)); if (priv->dpcd_ieee_oui == dpcd_ieee_oui) return; priv->dpcd_ieee_oui = dpcd_ieee_oui; g_object_notify(G_OBJECT(self), "dpcd-ieee-oui"); } /** * fu_dpaux_device_get_dpcd_hw_rev: * @self: a #FuDpauxDevice * * Gets the DPCD hardware revision number. * * Returns: integer * * Since: 1.9.8 **/ guint8 fu_dpaux_device_get_dpcd_hw_rev(FuDpauxDevice *self) { FuDpauxDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DPAUX_DEVICE(self), G_MAXUINT8); return priv->dpcd_hw_rev; } /** * fu_dpaux_device_set_dpcd_hw_rev: * @self: a #FuDpauxDevice * @dpcd_hw_rev: integer * * Sets the DPCD hardware revision number. * * Since: 1.9.8 **/ void fu_dpaux_device_set_dpcd_hw_rev(FuDpauxDevice *self, guint8 dpcd_hw_rev) { FuDpauxDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DPAUX_DEVICE(self)); priv->dpcd_hw_rev = dpcd_hw_rev; } /** * fu_dpaux_device_get_dpcd_dev_id: * @self: a #FuDpauxDevice * * Gets the DPCD device ID. * * Returns: integer * * Since: 1.9.8 **/ const gchar * fu_dpaux_device_get_dpcd_dev_id(FuDpauxDevice *self) { FuDpauxDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DPAUX_DEVICE(self), NULL); return priv->dpcd_dev_id; } /** * fu_dpaux_device_set_dpcd_dev_id: * @self: a #FuDpauxDevice * @dpcd_dev_id: integer * * Sets the DPCD device ID. * * Since: 1.9.8 **/ void fu_dpaux_device_set_dpcd_dev_id(FuDpauxDevice *self, const gchar *dpcd_dev_id) { FuDpauxDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DPAUX_DEVICE(self)); if (g_strcmp0(priv->dpcd_dev_id, dpcd_dev_id) == 0) return; g_free(priv->dpcd_dev_id); priv->dpcd_dev_id = g_strdup(dpcd_dev_id); } /** * fu_dpaux_device_write: * @self: a #FuDpauxDevice * @buf: (out): data * @bufsz: size of @data * @error: (nullable): optional return location for an error * * Write multiple bytes to the DP AUX device. * * Returns: %TRUE for success * * Since: 1.9.8 **/ gboolean fu_dpaux_device_write(FuDpauxDevice *self, goffset offset, const guint8 *buf, gsize bufsz, guint timeout_ms, GError **error) { FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); g_autofree gchar *title = g_strdup_printf("DPAUX write @0x%x", (guint)offset); g_return_val_if_fail(FU_IS_DPAUX_DEVICE(self), FALSE); g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* sanity check */ if (io_channel == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, "device is not open"); return FALSE; } /* seek, then write */ fu_dump_raw(G_LOG_DOMAIN, title, buf, bufsz); if (lseek(fu_io_channel_unix_get_fd(io_channel), offset, SEEK_SET) != offset) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to lseek to 0x%x", (guint)offset); return FALSE; } return fu_io_channel_write_raw(io_channel, buf, bufsz, timeout_ms, FU_IO_CHANNEL_FLAG_NONE, error); } /** * fu_dpaux_device_read: * @self: a #FuDpauxDevice * @buf: (out): data * @bufsz: size of @data * @error: (nullable): optional return location for an error * * Read multiple bytes from the DP AUX device. * * Returns: %TRUE for success * * Since: 1.9.8 **/ gboolean fu_dpaux_device_read(FuDpauxDevice *self, goffset offset, guint8 *buf, gsize bufsz, guint timeout_ms, GError **error) { FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); g_autofree gchar *title = g_strdup_printf("DPAUX read @0x%x", (guint)offset); g_return_val_if_fail(FU_IS_DPAUX_DEVICE(self), FALSE); g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* sanity check */ if (io_channel == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, "device is not open"); return FALSE; } /* seek, then read */ if (lseek(fu_io_channel_unix_get_fd(io_channel), offset, SEEK_SET) != offset) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to lseek to 0x%x", (guint)offset); return FALSE; } if (!fu_io_channel_read_raw(io_channel, buf, bufsz, NULL, timeout_ms, FU_IO_CHANNEL_FLAG_NONE, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, title, buf, bufsz); /* success */ return TRUE; } static void fu_dpaux_device_incorporate(FuDevice *device, FuDevice *donor) { FuDpauxDevice *self = FU_DPAUX_DEVICE(device); FuDpauxDevicePrivate *priv = GET_PRIVATE(self); FuDpauxDevicePrivate *priv_donor = GET_PRIVATE(FU_DPAUX_DEVICE(donor)); g_return_if_fail(FU_IS_DPAUX_DEVICE(self)); g_return_if_fail(FU_IS_DPAUX_DEVICE(donor)); /* FuUdevDevice->incorporate */ FU_DEVICE_CLASS(fu_dpaux_device_parent_class)->incorporate(device, donor); /* copy private instance data */ priv->dpcd_ieee_oui = priv_donor->dpcd_ieee_oui; priv->dpcd_hw_rev = priv_donor->dpcd_hw_rev; fu_dpaux_device_set_dpcd_dev_id(self, fu_dpaux_device_get_dpcd_dev_id(FU_DPAUX_DEVICE(donor))); } static gchar * fu_dpaux_device_convert_version(FuDevice *device, guint64 version_raw) { return fu_version_from_uint24(version_raw, fu_device_get_version_format(device)); } static void fu_dpaux_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuDpauxDevice *self = FU_DPAUX_DEVICE(object); FuDpauxDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_DPCD_IEEE_OUI: g_value_set_uint(value, priv->dpcd_ieee_oui); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_dpaux_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuDpauxDevice *self = FU_DPAUX_DEVICE(object); switch (prop_id) { case PROP_DPCD_IEEE_OUI: fu_dpaux_device_set_dpcd_ieee_oui(self, g_value_get_uint(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_dpaux_device_init(FuDpauxDevice *self) { fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_GENERIC_GUIDS); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | FU_UDEV_DEVICE_FLAG_OPEN_NONBLOCK); } static void fu_dpaux_device_finalize(GObject *object) { FuDpauxDevice *self = FU_DPAUX_DEVICE(object); FuDpauxDevicePrivate *priv = GET_PRIVATE(self); g_free(priv->dpcd_dev_id); G_OBJECT_CLASS(fu_dpaux_device_parent_class)->finalize(object); } static void fu_dpaux_device_class_init(FuDpauxDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->finalize = fu_dpaux_device_finalize; object_class->get_property = fu_dpaux_device_get_property; object_class->set_property = fu_dpaux_device_set_property; klass_device->probe = fu_dpaux_device_probe; klass_device->setup = fu_dpaux_device_setup; klass_device->invalidate = fu_dpaux_device_invalidate; klass_device->to_string = fu_dpaux_device_to_string; klass_device->incorporate = fu_dpaux_device_incorporate; klass_device->convert_version = fu_dpaux_device_convert_version; /** * FuDpauxDevice:dpcd-ieee-oui: * * The DPCD IEEE OUI. * * Since: 1.9.11 */ pspec = g_param_spec_uint("dpcd-ieee-oui", NULL, NULL, 0x0, G_MAXUINT32, 0x0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_DPCD_IEEE_OUI, pspec); } fwupd-1.9.16/libfwupdplugin/fu-dpaux-device.h000066400000000000000000000032411460375044200211340ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-udev-device.h" #define FU_TYPE_DPAUX_DEVICE (fu_dpaux_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuDpauxDevice, fu_dpaux_device, FU, DPAUX_DEVICE, FuUdevDevice) struct _FuDpauxDeviceClass { FuUdevDeviceClass parent_class; }; #define FU_DPAUX_DEVICE_DPCD_OFFSET_RECEIVER_CAPABILITY 0x0 #define FU_DPAUX_DEVICE_DPCD_OFFSET_LINK_CONFIGURATION 0x100 #define FU_DPAUX_DEVICE_DPCD_OFFSET_LINK_SINK_STATUS 0x200 #define FU_DPAUX_DEVICE_DPCD_OFFSET_SOURCE_DEVICE 0x300 #define FU_DPAUX_DEVICE_DPCD_OFFSET_SINK_DEVICE 0x400 #define FU_DPAUX_DEVICE_DPCD_OFFSET_BRANCH_DEVICE 0x500 guint32 fu_dpaux_device_get_dpcd_ieee_oui(FuDpauxDevice *self) G_GNUC_NON_NULL(1); void fu_dpaux_device_set_dpcd_ieee_oui(FuDpauxDevice *self, guint32 dpcd_ieee_oui) G_GNUC_NON_NULL(1); guint8 fu_dpaux_device_get_dpcd_hw_rev(FuDpauxDevice *self) G_GNUC_NON_NULL(1); void fu_dpaux_device_set_dpcd_hw_rev(FuDpauxDevice *self, guint8 dpcd_hw_rev) G_GNUC_NON_NULL(1); const gchar * fu_dpaux_device_get_dpcd_dev_id(FuDpauxDevice *self) G_GNUC_NON_NULL(1); void fu_dpaux_device_set_dpcd_dev_id(FuDpauxDevice *self, const gchar *dpcd_dev_id) G_GNUC_NON_NULL(1); gboolean fu_dpaux_device_read(FuDpauxDevice *self, goffset offset, guint8 *buf, gsize bufsz, guint timeout_ms, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_dpaux_device_write(FuDpauxDevice *self, goffset offset, const guint8 *buf, gsize bufsz, guint timeout_ms, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-dpaux.rs000066400000000000000000000004221460375044200200720ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(Parse)] struct DpauxDpcd { ieee_oui: u24be, dev_id: [char; 6], hw_rev: u8, fw_ver: u24be, // technically this is u16be, but both MST vendors do this } fwupd-1.9.16/libfwupdplugin/fu-drm-device.c000066400000000000000000000140071460375044200205720ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDrmDevice" #include "config.h" #include "fu-bytes.h" #include "fu-drm-device.h" #include "fu-string.h" /** * FuDrmDevice * * A DRM device. * * See also: #FuUdevDevice */ typedef struct { gchar *connector_id; gboolean enabled; FuDisplayState display_state; FuEdid *edid; } FuDrmDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuDrmDevice, fu_drm_device, FU_TYPE_UDEV_DEVICE) #define GET_PRIVATE(o) (fu_drm_device_get_instance_private(o)) static FuDisplayState fu_display_state_from_string(const gchar *display_state) { if (g_strcmp0(display_state, "connected") == 0) return FU_DISPLAY_STATE_CONNECTED; if (g_strcmp0(display_state, "disconnected") == 0) return FU_DISPLAY_STATE_DISCONNECTED; return FU_DISPLAY_STATE_UNKNOWN; } static void fu_drm_device_to_string(FuDevice *device, guint idt, GString *str) { FuDrmDevice *self = FU_DRM_DEVICE(device); FuDrmDevicePrivate *priv = GET_PRIVATE(self); if (priv->connector_id != NULL) fu_string_append(str, idt, "ConnectorId", priv->connector_id); fu_string_append_kb(str, idt, "Enabled", priv->enabled); fu_string_append(str, idt, "State", fu_display_state_to_string(priv->display_state)); } /** * fu_drm_device_get_state: * @self: a #FuDrmDevice * * Gets the current status of the DRM device. * * Returns: a #FuDisplayState, e.g. %FU_DISPLAY_STATE_CONNECTED * * Since: 1.9.7 **/ FuDisplayState fu_drm_device_get_state(FuDrmDevice *self) { FuDrmDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DRM_DEVICE(self), FU_DISPLAY_STATE_UNKNOWN); return priv->display_state; } /** * fu_drm_device_get_enabled: * @self: a #FuDrmDevice * * Gets if the DRM device is currently enabled. * * Returns: %TRUE if enabled, %FALSE otherwise * * Since: 1.9.7 **/ gboolean fu_drm_device_get_enabled(FuDrmDevice *self) { FuDrmDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DRM_DEVICE(self), FALSE); return priv->enabled; } /** * fu_drm_device_get_connector_id: * @self: a #FuDrmDevice * * Gets the DRM device connector ID. * * Returns: a string, or %NULL if not found * * Since: 1.9.7 **/ const gchar * fu_drm_device_get_connector_id(FuDrmDevice *self) { FuDrmDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DRM_DEVICE(self), NULL); return priv->connector_id; } /** * fu_drm_device_get_edid: * @self: a #FuDrmDevice * * Returns the cached EDID from the DRM device. * * Returns: (transfer none): a #FuEdid, or %NULL * * Since: 1.9.7 **/ FuEdid * fu_drm_device_get_edid(FuDrmDevice *self) { FuDrmDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DRM_DEVICE(self), NULL); return priv->edid; } static gboolean fu_drm_device_probe(FuDevice *device, GError **error) { g_autoptr(FuUdevDevice) parent = NULL; FuDrmDevice *self = FU_DRM_DEVICE(device); FuDrmDevicePrivate *priv = GET_PRIVATE(self); const gchar *sysfs_path = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device)); const gchar *tmp; g_autofree gchar *physical_id = g_path_get_basename(sysfs_path); /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_drm_device_parent_class)->probe(device, error)) return FALSE; /* basic properties */ tmp = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(self), "enabled", NULL); priv->enabled = g_strcmp0(tmp, "enabled") == 0; tmp = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(self), "status", NULL); priv->display_state = fu_display_state_from_string(tmp); tmp = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(self), "connector_id", NULL); if (tmp != NULL && tmp[0] != '\0') priv->connector_id = g_strdup(tmp); /* this is a heuristic */ if (physical_id != NULL) { g_auto(GStrv) parts = g_strsplit(physical_id, "-", -1); for (guint i = 0; parts[i] != NULL; i++) { if (g_strcmp0(parts[i], "eDP") == 0) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL); } fu_device_set_physical_id(device, physical_id); } /* set the parent */ parent = fu_udev_device_get_parent_with_subsystem(FU_UDEV_DEVICE(self), "pci"); if (parent != NULL) fu_device_add_parent_backend_id(device, fu_udev_device_get_sysfs_path(parent)); /* read EDID and parse it */ if (priv->display_state == FU_DISPLAY_STATE_CONNECTED) { g_autofree gchar *edid_path = g_build_filename(sysfs_path, "edid", NULL); g_autoptr(FuEdid) edid = fu_edid_new(); g_autoptr(GBytes) edid_blob = NULL; edid_blob = fu_bytes_get_contents(edid_path, error); if (edid_blob == NULL) return FALSE; if (!fu_firmware_parse(FU_FIRMWARE(edid), edid_blob, FWUPD_INSTALL_FLAG_NONE, error)) return FALSE; g_set_object(&priv->edid, edid); /* add instance ID */ fu_device_add_instance_str(device, "VEN", fu_edid_get_pnp_id(edid)); fu_device_add_instance_u16(device, "DEV", fu_edid_get_product_code(edid)); if (!fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS, error, "DRM", "VEN", "DEV", NULL)) return FALSE; if (fu_edid_get_eisa_id(edid) != NULL) fu_device_set_name(device, fu_edid_get_eisa_id(edid)); if (fu_edid_get_serial_number(edid) != NULL) fu_device_set_serial(device, fu_edid_get_serial_number(edid)); } /* success */ return TRUE; } static void fu_drm_device_init(FuDrmDevice *self) { } static void fu_drm_device_finalize(GObject *object) { FuDrmDevice *self = FU_DRM_DEVICE(object); FuDrmDevicePrivate *priv = GET_PRIVATE(self); g_free(priv->connector_id); if (priv->edid != NULL) g_object_unref(priv->edid); G_OBJECT_CLASS(fu_drm_device_parent_class)->finalize(object); } static void fu_drm_device_class_init(FuDrmDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_drm_device_finalize; klass_device->probe = fu_drm_device_probe; klass_device->to_string = fu_drm_device_to_string; } fwupd-1.9.16/libfwupdplugin/fu-drm-device.h000066400000000000000000000012511460375044200205740ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-edid.h" #include "fu-udev-device.h" #define FU_TYPE_DRM_DEVICE (fu_drm_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuDrmDevice, fu_drm_device, FU, DRM_DEVICE, FuUdevDevice) struct _FuDrmDeviceClass { FuUdevDeviceClass parent_class; }; gboolean fu_drm_device_get_enabled(FuDrmDevice *self) G_GNUC_NON_NULL(1); FuDisplayState fu_drm_device_get_state(FuDrmDevice *self) G_GNUC_NON_NULL(1); const gchar * fu_drm_device_get_connector_id(FuDrmDevice *self) G_GNUC_NON_NULL(1); FuEdid * fu_drm_device_get_edid(FuDrmDevice *self) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-dump.c000066400000000000000000000064311460375044200175220ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include "fu-dump.h" /** * fu_dump_full: * @log_domain: (nullable): optional log domain, typically %G_LOG_DOMAIN * @title: (nullable): optional prefix title * @data: buffer to print * @len: the size of @data * @columns: break new lines after this many bytes * @flags: dump flags, e.g. %FU_DUMP_FLAGS_SHOW_ASCII * * Dumps a raw buffer to the screen. * * Since: 1.8.2 **/ void fu_dump_full(const gchar *log_domain, const gchar *title, const guint8 *data, gsize len, guint columns, FuDumpFlags flags) { g_autoptr(GString) str = g_string_new(NULL); g_return_if_fail(columns > 0); /* this is CPU intensive enough to pre-filter here rather than building * the string and handling in a GLogFunc */ if (g_getenv("FWUPD_VERBOSE") == NULL) return; /* optional */ if (title != NULL) g_string_append_printf(str, "%s:", title); /* if more than can fit on one line then start afresh */ if (len > columns || flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) { g_string_append(str, "\n"); } else { for (gsize i = str->len; i < 16; i++) g_string_append(str, " "); } /* offset line */ if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) { g_string_append(str, " │ "); for (gsize i = 0; i < columns; i++) { g_string_append_printf(str, "%02x ", (guint)i); if (flags & FU_DUMP_FLAGS_SHOW_ASCII) g_string_append(str, " "); } g_string_append(str, "\n───────┼"); for (gsize i = 0; i < columns; i++) { g_string_append(str, "───"); if (flags & FU_DUMP_FLAGS_SHOW_ASCII) g_string_append(str, "────"); } g_string_append_printf(str, "\n0x%04x │ ", (guint)0); } /* print each row */ for (gsize i = 0; i < len; i++) { g_string_append_printf(str, "%02x ", data[i]); /* optionally print ASCII char */ if (flags & FU_DUMP_FLAGS_SHOW_ASCII) { if (g_ascii_isprint(data[i])) g_string_append_printf(str, "[%c] ", data[i]); else g_string_append(str, "[?] "); } /* new row required */ if (i > 0 && i != len - 1 && (i + 1) % columns == 0) { g_string_append(str, "\n"); if (flags & FU_DUMP_FLAGS_SHOW_ADDRESSES) g_string_append_printf(str, "0x%04x │ ", (guint)i + 1); } } g_log(log_domain, G_LOG_LEVEL_DEBUG, "%s", str->str); } /** * fu_dump_raw: * @log_domain: (nullable): optional log domain, typically %G_LOG_DOMAIN * @title: (nullable): optional prefix title * @data: buffer to print * @len: the size of @data * * Dumps a raw buffer to the screen. * * Since: 1.8.2 **/ void fu_dump_raw(const gchar *log_domain, const gchar *title, const guint8 *data, gsize len) { FuDumpFlags flags = FU_DUMP_FLAGS_NONE; if (len > 64) flags |= FU_DUMP_FLAGS_SHOW_ADDRESSES; fu_dump_full(log_domain, title, data, len, 32, flags); } /** * fu_dump_bytes: * @log_domain: (nullable): optional log domain, typically %G_LOG_DOMAIN * @title: (nullable): optional prefix title * @bytes: data blob * * Dumps a byte buffer to the screen. * * Since: 1.8.2 **/ void fu_dump_bytes(const gchar *log_domain, const gchar *title, GBytes *bytes) { gsize len = 0; const guint8 *data = g_bytes_get_data(bytes, &len); fu_dump_raw(log_domain, title, data, len); } fwupd-1.9.16/libfwupdplugin/fu-dump.h000066400000000000000000000016671460375044200175350ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /** * FuDumpFlags: * @FU_DUMP_FLAGS_NONE: No flags set * @FU_DUMP_FLAGS_SHOW_ASCII: Show ASCII in debugging dumps * @FU_DUMP_FLAGS_SHOW_ADDRESSES: Show addresses in debugging dumps * * The flags to use when configuring debugging **/ typedef enum { FU_DUMP_FLAGS_NONE = 0, FU_DUMP_FLAGS_SHOW_ASCII = 1 << 0, FU_DUMP_FLAGS_SHOW_ADDRESSES = 1 << 1, /*< private >*/ FU_DUMP_FLAGS_LAST } FuDumpFlags; void fu_dump_raw(const gchar *log_domain, const gchar *title, const guint8 *data, gsize len) G_GNUC_NON_NULL(1); void fu_dump_full(const gchar *log_domain, const gchar *title, const guint8 *data, gsize len, guint columns, FuDumpFlags flags) G_GNUC_NON_NULL(1); void fu_dump_bytes(const gchar *log_domain, const gchar *title, GBytes *bytes) G_GNUC_NON_NULL(1, 3); fwupd-1.9.16/libfwupdplugin/fu-edid.c000066400000000000000000000257001460375044200174620ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEdid" #include "config.h" #include "fu-byte-array.h" #include "fu-edid-struct.h" #include "fu-edid.h" #include "fu-string.h" struct _FuEdid { FuFirmware parent_instance; gchar *pnp_id; gchar *serial_number; gchar *product_name; gchar *eisa_id; guint16 product_code; }; G_DEFINE_TYPE(FuEdid, fu_edid, FU_TYPE_FIRMWARE) /** * fu_edid_get_pnp_id: * @self: a #FuEdid * * Gets the PNP ID, e.g. `IBM`. * * Returns: string value, or %NULL for unset * * Since: 1.9.6 **/ const gchar * fu_edid_get_pnp_id(FuEdid *self) { g_return_val_if_fail(FU_IS_EDID(self), NULL); return self->pnp_id; } /** * fu_edid_set_pnp_id: * @self: a #FuEdid * @pnp_id: (nullable): three digit string value, or %NULL * * Sets the PNP ID, which has a length equal to or less than 3 ASCII characters. * * Since: 1.9.6 **/ void fu_edid_set_pnp_id(FuEdid *self, const gchar *pnp_id) { g_return_if_fail(FU_IS_EDID(self)); if (g_strcmp0(self->pnp_id, pnp_id) == 0) return; g_free(self->pnp_id); self->pnp_id = g_strdup(pnp_id); } /** * fu_edid_get_eisa_id: * @self: a #FuEdid * * Gets the EISA ID, e.g. `LTN154P2-L05`. * * Returns: string value, or %NULL for unset * * Since: 1.9.6 **/ const gchar * fu_edid_get_eisa_id(FuEdid *self) { g_return_val_if_fail(FU_IS_EDID(self), NULL); return self->eisa_id; } /** * fu_edid_set_eisa_id: * @self: a #FuEdid * @eisa_id: (nullable): string value, or %NULL * * Sets the EISA ID, which has to be equal to or less than 13 ASCII characters long. * * Since: 1.9.6 **/ void fu_edid_set_eisa_id(FuEdid *self, const gchar *eisa_id) { g_return_if_fail(FU_IS_EDID(self)); if (g_strcmp0(self->eisa_id, eisa_id) == 0) return; g_free(self->eisa_id); self->eisa_id = g_strdup(eisa_id); } /** * fu_edid_get_serial_number: * @self: a #FuEdid * * Gets the serial number. * * Returns: string value, or %NULL for unset * * Since: 1.9.6 **/ const gchar * fu_edid_get_serial_number(FuEdid *self) { g_return_val_if_fail(FU_IS_EDID(self), NULL); return self->serial_number; } /** * fu_edid_set_serial_number: * @self: a #FuEdid * @serial_number: (nullable): string value, or %NULL * * Sets the serial number, which can either be a unsigned 32 bit integer, or a string equal to or * less than 13 ASCII characters long. * * Since: 1.9.6 **/ void fu_edid_set_serial_number(FuEdid *self, const gchar *serial_number) { g_return_if_fail(FU_IS_EDID(self)); if (g_strcmp0(self->serial_number, serial_number) == 0) return; g_free(self->serial_number); self->serial_number = g_strdup(serial_number); } /** * fu_edid_get_product_code: * @self: a #FuEdid * * Gets the product code. * * Returns: integer, or 0x0 for unset * * Since: 1.9.6 **/ guint16 fu_edid_get_product_code(FuEdid *self) { g_return_val_if_fail(FU_IS_EDID(self), G_MAXUINT16); return self->product_code; } /** * fu_edid_set_product_code: * @self: a #FuEdid * @product_code: integer, or 0x0 for unset * * Sets the product code. * * Since: 1.9.6 **/ void fu_edid_set_product_code(FuEdid *self, guint16 product_code) { g_return_if_fail(FU_IS_EDID(self)); self->product_code = product_code; } /* return as soon as the first non-printable char is encountered */ static gchar * fu_edid_strsafe(const guint8 *buf, gsize bufsz) { g_autoptr(GString) str = g_string_new(NULL); for (gsize i = 0; i < bufsz; i++) { if (!g_ascii_isprint((gchar)buf[i])) break; g_string_append_c(str, (gchar)buf[i]); } if (str->len == 0) return NULL; return g_string_free(g_steal_pointer(&str), FALSE); } static gboolean fu_edid_parse_descriptor(FuEdid *self, GBytes *fw, gsize offset, GError **error) { gsize buf2sz = 0; const guint8 *buf2; g_autoptr(GByteArray) st = NULL; st = fu_struct_edid_descriptor_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; /* ignore pixel clock data */ if (fu_struct_edid_descriptor_get_kind(st) != 0x0 || fu_struct_edid_descriptor_get_subkind(st) != 0x0) return TRUE; buf2 = fu_struct_edid_descriptor_get_data(st, &buf2sz); if (fu_struct_edid_descriptor_get_tag(st) == FU_EDID_DESCRIPTOR_TAG_DISPLAY_PRODUCT_NAME) { g_free(self->product_name); self->product_name = fu_edid_strsafe(buf2, buf2sz); } else if (fu_struct_edid_descriptor_get_tag(st) == FU_EDID_DESCRIPTOR_TAG_DISPLAY_PRODUCT_SERIAL_NUMBER) { g_free(self->serial_number); self->serial_number = fu_edid_strsafe(buf2, buf2sz); } else if (fu_struct_edid_descriptor_get_tag(st) == FU_EDID_DESCRIPTOR_TAG_ALPHANUMERIC_DATA_STRING) { g_free(self->eisa_id); self->eisa_id = fu_edid_strsafe(buf2, buf2sz); } /* success */ return TRUE; } static gboolean fu_edid_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuEdid *self = FU_EDID(firmware); const guint8 *manu_id; g_autofree gchar *pnp_id = NULL; g_autoptr(GByteArray) st = NULL; /* parse header */ st = fu_struct_edid_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; /* decode the PNP ID from three 5 bit words packed into 2 bytes * /--00--\/--01--\ * 7654321076543210 * |\---/\---/\---/ * R C1 C2 C3 */ manu_id = fu_struct_edid_get_manufacturer_name(st, NULL); pnp_id = g_strdup_printf( "%c%c%c", 'A' + ((manu_id[0] & 0b01111100) >> 2) - 1, 'A' + (((manu_id[0] & 0b00000011) << 3) + ((manu_id[1] & 0b11100000) >> 5)) - 1, 'A' + (manu_id[1] & 0b00011111) - 1); fu_edid_set_pnp_id(self, pnp_id); fu_edid_set_product_code(self, fu_struct_edid_get_product_code(st)); if (fu_struct_edid_get_serial_number(st) != 0x0) { g_autofree gchar *serial_number = g_strdup_printf("%u", fu_struct_edid_get_serial_number(st)); fu_edid_set_serial_number(self, serial_number); } /* parse 4x18 byte sections */ offset += FU_STRUCT_EDID_OFFSET_DATA_BLOCKS; for (guint i = 0; i < 4; i++) { if (!fu_edid_parse_descriptor(self, fw, offset, error)) return FALSE; offset += FU_STRUCT_EDID_DESCRIPTOR_SIZE; } /* success */ return TRUE; } static GByteArray * fu_edid_write(FuFirmware *firmware, GError **error) { FuEdid *self = FU_EDID(firmware); g_autoptr(GByteArray) st = fu_struct_edid_new(); guint64 serial_number = 0; gsize offset_desc = FU_STRUCT_EDID_OFFSET_DATA_BLOCKS; fu_struct_edid_set_product_code(st, self->product_code); /* if this is a integer, store it in the header rather than in a descriptor */ if (fu_strtoull(self->serial_number, &serial_number, 0, G_MAXUINT32, NULL)) fu_struct_edid_set_serial_number(st, serial_number); /* store descriptors */ if (self->product_name != NULL) { g_autoptr(GByteArray) st_desc = fu_struct_edid_descriptor_new(); fu_struct_edid_descriptor_set_tag(st_desc, FU_EDID_DESCRIPTOR_TAG_DISPLAY_PRODUCT_NAME); if (!fu_struct_edid_descriptor_set_data(st_desc, (const guint8 *)self->product_name, strlen(self->product_name), error)) { g_prefix_error(error, "cannot write product name: "); return NULL; } memcpy(st->data + offset_desc, st_desc->data, st_desc->len); offset_desc += st_desc->len; } if (self->serial_number != NULL) { g_autoptr(GByteArray) st_desc = fu_struct_edid_descriptor_new(); fu_struct_edid_descriptor_set_tag( st_desc, FU_EDID_DESCRIPTOR_TAG_DISPLAY_PRODUCT_SERIAL_NUMBER); if (!fu_struct_edid_descriptor_set_data(st_desc, (const guint8 *)self->serial_number, strlen(self->serial_number), error)) { g_prefix_error(error, "cannot write serial number: "); return NULL; } memcpy(st->data + offset_desc, st_desc->data, st_desc->len); offset_desc += st_desc->len; } if (self->eisa_id != NULL) { g_autoptr(GByteArray) st_desc = fu_struct_edid_descriptor_new(); fu_struct_edid_descriptor_set_tag(st_desc, FU_EDID_DESCRIPTOR_TAG_ALPHANUMERIC_DATA_STRING); if (!fu_struct_edid_descriptor_set_data(st_desc, (const guint8 *)self->eisa_id, strlen(self->eisa_id), error)) { g_prefix_error(error, "cannot write EISA ID: "); return NULL; } memcpy(st->data + offset_desc, st_desc->data, st_desc->len); offset_desc += st_desc->len; } /* success */ return g_steal_pointer(&st); } static gboolean fu_edid_build(FuFirmware *firmware, XbNode *n, GError **error) { FuEdid *self = FU_EDID(firmware); const gchar *value; /* simple properties */ value = xb_node_query_text(n, "pnp_id", NULL); if (value != NULL) { gsize valuesz = strlen(value); if (valuesz != 3) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "pnp_id not supported, %u of %u bytes", (guint)valuesz, (guint)3); return FALSE; } fu_edid_set_pnp_id(self, value); } value = xb_node_query_text(n, "serial_number", NULL); if (value != NULL) { gsize valuesz = strlen(value); if (valuesz > FU_STRUCT_EDID_DESCRIPTOR_SIZE_DATA) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "serial_number not supported, %u of %u bytes", (guint)valuesz, (guint)FU_STRUCT_EDID_DESCRIPTOR_SIZE_DATA); return FALSE; } fu_edid_set_serial_number(self, value); } value = xb_node_query_text(n, "eisa_id", NULL); if (value != NULL) { gsize valuesz = strlen(value); if (valuesz > FU_STRUCT_EDID_DESCRIPTOR_SIZE_DATA) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "eisa_id not supported, %u of %u bytes", (guint)valuesz, (guint)FU_STRUCT_EDID_DESCRIPTOR_SIZE_DATA); return FALSE; } fu_edid_set_eisa_id(self, value); } value = xb_node_query_text(n, "product_code", NULL); if (value != NULL) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; fu_edid_set_product_code(self, tmp); } /* success */ return TRUE; } static void fu_edid_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuEdid *self = FU_EDID(firmware); fu_xmlb_builder_insert_kv(bn, "pnp_id", self->pnp_id); fu_xmlb_builder_insert_kv(bn, "serial_number", self->serial_number); fu_xmlb_builder_insert_kv(bn, "product_name", self->product_name); fu_xmlb_builder_insert_kv(bn, "eisa_id", self->eisa_id); fu_xmlb_builder_insert_kx(bn, "product_code", self->product_code); } static void fu_edid_finalize(GObject *obj) { FuEdid *self = FU_EDID(obj); g_free(self->pnp_id); g_free(self->serial_number); g_free(self->product_name); g_free(self->eisa_id); G_OBJECT_CLASS(fu_edid_parent_class)->finalize(obj); } static void fu_edid_class_init(FuEdidClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_edid_finalize; klass_firmware->parse = fu_edid_parse; klass_firmware->write = fu_edid_write; klass_firmware->build = fu_edid_build; klass_firmware->export = fu_edid_export; } static void fu_edid_init(FuEdid *self) { } /** * fu_edid_new: * * Returns: (transfer full): a #FuEdid * * Since: 1.9.6 **/ FuEdid * fu_edid_new(void) { return g_object_new(FU_TYPE_EDID, NULL); } fwupd-1.9.16/libfwupdplugin/fu-edid.h000066400000000000000000000016321460375044200174650ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_EDID (fu_edid_get_type()) G_DECLARE_FINAL_TYPE(FuEdid, fu_edid, FU, EDID, FuFirmware) const gchar * fu_edid_get_pnp_id(FuEdid *self) G_GNUC_NON_NULL(1); void fu_edid_set_pnp_id(FuEdid *self, const gchar *pnp_id) G_GNUC_NON_NULL(1); const gchar * fu_edid_get_eisa_id(FuEdid *self) G_GNUC_NON_NULL(1); void fu_edid_set_eisa_id(FuEdid *self, const gchar *eisa_id) G_GNUC_NON_NULL(1); const gchar * fu_edid_get_serial_number(FuEdid *self) G_GNUC_NON_NULL(1); void fu_edid_set_serial_number(FuEdid *self, const gchar *serial_number) G_GNUC_NON_NULL(1); guint16 fu_edid_get_product_code(FuEdid *self) G_GNUC_NON_NULL(1); void fu_edid_set_product_code(FuEdid *self, guint16 product_code) G_GNUC_NON_NULL(1); FuEdid * fu_edid_new(void) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.9.16/libfwupdplugin/fu-edid.rs000066400000000000000000000022421460375044200176600ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[repr(u8)] enum EdidDescriptorTag { DisplayProductSerialNumber = 0xFF, AlphanumericDataString = 0xFE, DisplayRangeLimits = 0xFD, DisplayProductName = 0xFC, ColorPointData = 0xFB, StandardTimingIdentifications = 0xFA, DisplayColorManagementData = 0xF9, CvtTimingCodes = 0xF8, EstablishedTimings = 0xF7, DummyDescriptor = 0x10, } #[derive(ParseBytes, New)] struct EdidDescriptor { kind: u16le, subkind: u8, tag: EdidDescriptorTag, _reserved: u8, data: [u8; 13], } #[derive(New, ParseBytes)] struct Edid { header: [u8; 8] == 0x00FFFFFFFFFFFF00, manufacturer_name: [u8; 2], product_code: u16le, serial_number: u32le, week_of_manufacture: u8, year_of_manufacture: u8, edid_version_number: u8 == 0x1, revision_number: u8 = 0x3, _basic_display_parameters_and_features: [u8; 5], _color_characteristics: [u8; 10], _established_timings: [u8; 3], _standard_timings: [u8; 16], data_blocks: [u8; 72], // should be [EdidDescriptor: 4], extension_block_count: u8, checksum: u8, } fwupd-1.9.16/libfwupdplugin/fu-efi-common.c000066400000000000000000000040721460375044200206050ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-efi-common.h" /** * fu_efi_guid_to_name: * @guid: A lowercase GUID string, e.g. `8c8ce578-8a3d-4f1c-9935-896185c32dd3` * * Converts a GUID to the known nice name. * * Returns: identifier string, or %NULL if unknown * * Since: 1.6.2 **/ const gchar * fu_efi_guid_to_name(const gchar *guid) { if (g_strcmp0(guid, FU_EFI_FIRMWARE_VOLUME_GUID_FFS1) == 0) return "Volume:Ffs1"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_VOLUME_GUID_FFS2) == 0) return "Volume:Ffs2"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_VOLUME_GUID_FFS3) == 0) return "Volume:Ffs3"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_VOLUME_GUID_NVRAM_EVSA) == 0) return "Volume:NvramEvsa"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_VOLUME_GUID_NVRAM_NVAR) == 0) return "Volume:NvramNvar"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_VOLUME_GUID_NVRAM_EVSA2) == 0) return "Volume:NvramEvsa2"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_VOLUME_GUID_APPLE_BOOT) == 0) return "Volume:AppleBoot"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_VOLUME_GUID_PFH1) == 0) return "Volume:Pfh1"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_VOLUME_GUID_PFH2) == 0) return "Volume:Pfh2"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_FILE_FV_IMAGE) == 0) return "File:FvImage"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_FILE_MICROCODE) == 0) return "File:Microcode"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_FILE_BIOS_GUARD) == 0) return "File:BiosGuard"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_SECTION_LZMA_COMPRESS) == 0) return "Section:LzmaCompress"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_SECTION_TIANO_COMPRESS) == 0) return "Section:TianoCompress"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_SECTION_SMBIOS_TABLE) == 0) return "Section:SmbiosTable"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_SECTION_ESRT_TABLE) == 0) return "Section:EsrtTable"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_SECTION_ACPI1_TABLE) == 0) return "Section:Acpi1Table"; if (g_strcmp0(guid, FU_EFI_FIRMWARE_SECTION_ACPI2_TABLE) == 0) return "Section:Acpi2Table"; return NULL; } fwupd-1.9.16/libfwupdplugin/fu-efi-common.h000066400000000000000000000032401460375044200206060ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_EFI_FIRMWARE_VOLUME_GUID_FFS1 "7a9354d9-0468-444a-81ce-0bf617d890df" #define FU_EFI_FIRMWARE_VOLUME_GUID_FFS2 "8c8ce578-8a3d-4f1c-9935-896185c32dd3" #define FU_EFI_FIRMWARE_VOLUME_GUID_FFS3 "5473c07a-3dcb-4dca-bd6f-1e9689e7349a" #define FU_EFI_FIRMWARE_VOLUME_GUID_NVRAM_EVSA "fff12b8d-7696-4c8b-a985-2747075b4f50" #define FU_EFI_FIRMWARE_VOLUME_GUID_NVRAM_NVAR "cef5b9a3-476d-497f-9fdc-e98143e0422c" #define FU_EFI_FIRMWARE_VOLUME_GUID_NVRAM_EVSA2 "00504624-8a59-4eeb-bd0f-6b36e96128e0" #define FU_EFI_FIRMWARE_VOLUME_GUID_APPLE_BOOT "04adeead-61ff-4d31-b6ba-64f8bf901f5a" #define FU_EFI_FIRMWARE_VOLUME_GUID_PFH1 "16b45da2-7d70-4aea-a58d-760e9ecb841d" #define FU_EFI_FIRMWARE_VOLUME_GUID_PFH2 "e360bdba-c3ce-46be-8f37-b231e5cb9f35" #define FU_EFI_FIRMWARE_FILE_FV_IMAGE "4e35fd93-9c72-4c15-8c4b-e77f1db2d792" #define FU_EFI_FIRMWARE_FILE_MICROCODE "197db236-f856-4924-90f8-cdf12fb875f3" #define FU_EFI_FIRMWARE_FILE_BIOS_GUARD "7934156d-cfce-460e-92f5-a07909a59eca" #define FU_EFI_FIRMWARE_SECTION_LZMA_COMPRESS "ee4e5898-3914-4259-9d6e-dc7bd79403cf" #define FU_EFI_FIRMWARE_SECTION_TIANO_COMPRESS "a31280ad-481e-41b6-95e8-127f4c984779" #define FU_EFI_FIRMWARE_SECTION_SMBIOS_TABLE "eb9d2d31-2d88-11d3-9a16-0090273fc14d" #define FU_EFI_FIRMWARE_SECTION_ESRT_TABLE "b122a263-3661-4f68-9929-78f8b0d62180" #define FU_EFI_FIRMWARE_SECTION_ACPI1_TABLE "eb9d2d30-2d88-11d3-9a16-0090273fc14d" #define FU_EFI_FIRMWARE_SECTION_ACPI2_TABLE "8868e871-e4f1-11d3-bc22-0080c73c8881" const gchar * fu_efi_guid_to_name(const gchar *guid); fwupd-1.9.16/libfwupdplugin/fu-efi-device-path-list.c000066400000000000000000000070621460375044200224610ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEfiDevicePath" #include "config.h" #include "fu-byte-array.h" #include "fu-efi-device-path-list.h" #include "fu-efi-file-path-device-path.h" #include "fu-efi-hard-drive-device-path.h" #include "fu-efi-struct.h" struct _FuEfiDevicePathList { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuEfiDevicePathList, fu_efi_device_path_list, FU_TYPE_FIRMWARE) #define FU_EFI_DEVICE_PATH_LIST_IMAGES_MAX 1000u static gboolean fu_efi_device_path_list_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { while (offset < g_bytes_get_size(fw)) { g_autoptr(FuEfiDevicePath) efi_dp = NULL; g_autoptr(GByteArray) st_dp = NULL; /* parse the header so we can work out what GType to create */ st_dp = fu_struct_efi_device_path_parse_bytes(fw, offset, error); if (st_dp == NULL) return FALSE; if (fu_struct_efi_device_path_get_type(st_dp) == FU_EFI_DEVICE_PATH_TYPE_END) break; if (fu_struct_efi_device_path_get_type(st_dp) == FU_EFI_DEVICE_PATH_TYPE_MEDIA && fu_struct_efi_device_path_get_subtype(st_dp) == FU_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE_FILE_PATH) { efi_dp = FU_EFI_DEVICE_PATH(fu_efi_file_path_device_path_new()); } else if (fu_struct_efi_device_path_get_type(st_dp) == FU_EFI_DEVICE_PATH_TYPE_MEDIA && fu_struct_efi_device_path_get_subtype(st_dp) == FU_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE_HARD_DRIVE) { efi_dp = FU_EFI_DEVICE_PATH(fu_efi_hard_drive_device_path_new()); } else { efi_dp = fu_efi_device_path_new(); } fu_firmware_set_offset(FU_FIRMWARE(efi_dp), offset); if (!fu_firmware_parse_full(FU_FIRMWARE(efi_dp), fw, offset, flags, error)) return FALSE; if (!fu_firmware_add_image_full(firmware, FU_FIRMWARE(efi_dp), error)) return FALSE; offset += fu_firmware_get_size(FU_FIRMWARE(efi_dp)); } /* success */ return TRUE; } static GByteArray * fu_efi_device_path_list_write(FuFirmware *firmware, GError **error) { g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) st_dp_end = NULL; /* add each image */ for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); g_autoptr(GBytes) dp_blob = fu_firmware_write(img, error); if (dp_blob == NULL) return NULL; fu_byte_array_append_bytes(buf, dp_blob); } /* add end marker */ st_dp_end = fu_struct_efi_device_path_new(); fu_struct_efi_device_path_set_type(st_dp_end, FU_EFI_DEVICE_PATH_TYPE_END); fu_struct_efi_device_path_set_subtype(st_dp_end, 0xFF); g_byte_array_append(buf, st_dp_end->data, st_dp_end->len); /* success */ return g_steal_pointer(&buf); } static void fu_efi_device_path_list_class_init(FuEfiDevicePathListClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_efi_device_path_list_parse; klass_firmware->write = fu_efi_device_path_list_write; } static void fu_efi_device_path_list_init(FuEfiDevicePathList *self) { g_type_ensure(FU_TYPE_EFI_FILE_PATH_DEVICE_PATH); g_type_ensure(FU_TYPE_EFI_HARD_DRIVE_DEVICE_PATH); fu_firmware_set_images_max(FU_FIRMWARE(self), FU_EFI_DEVICE_PATH_LIST_IMAGES_MAX); } /** * fu_efi_device_path_list_new: * * Creates a new EFI DEVICE_PATH list. * * Returns: (transfer full): a #FuEfiDevicePathList * * Since: 1.9.3 **/ FuEfiDevicePathList * fu_efi_device_path_list_new(void) { return g_object_new(FU_TYPE_EFI_DEVICE_PATH_LIST, NULL); } fwupd-1.9.16/libfwupdplugin/fu-efi-device-path-list.h000066400000000000000000000007301460375044200224610ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-efi-device-path.h" #include "fu-firmware.h" #define FU_TYPE_EFI_DEVICE_PATH_LIST (fu_efi_device_path_list_get_type()) G_DECLARE_FINAL_TYPE(FuEfiDevicePathList, fu_efi_device_path_list, FU, EFI_DEVICE_PATH_LIST, FuFirmware) FuEfiDevicePathList * fu_efi_device_path_list_new(void) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.9.16/libfwupdplugin/fu-efi-device-path.c000066400000000000000000000111501460375044200215010ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEfiDevicePath" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-efi-device-path.h" #include "fu-efi-struct.h" /** * FuEfiDevicePath: * * See also: [class@FuFirmware] */ typedef struct { guint8 subtype; } FuEfiDevicePathPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuEfiDevicePath, fu_efi_device_path, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_efi_device_path_get_instance_private(o)) static void fu_efi_device_path_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuEfiDevicePath *self = FU_EFI_DEVICE_PATH(firmware); FuEfiDevicePathPrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "subtype", priv->subtype); } /** * fu_efi_device_path_get_subtype: * @self: a #FuEfiDevicePath * * Gets the DEVICE_PATH subtype. * * Returns: integer * * Since: 1.9.3 **/ guint8 fu_efi_device_path_get_subtype(FuEfiDevicePath *self) { FuEfiDevicePathPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_EFI_DEVICE_PATH(self), 0x0); return priv->subtype; } /** * fu_efi_device_path_set_subtype: * @self: a #FuEfiDevicePath * @subtype: integer * * Sets the DEVICE_PATH subtype. * * Since: 1.9.3 **/ void fu_efi_device_path_set_subtype(FuEfiDevicePath *self, guint8 subtype) { FuEfiDevicePathPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_EFI_DEVICE_PATH(self)); priv->subtype = subtype; } static gboolean fu_efi_device_path_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuEfiDevicePath *self = FU_EFI_DEVICE_PATH(firmware); FuEfiDevicePathPrivate *priv = GET_PRIVATE(self); gsize bufsz = g_bytes_get_size(fw); gsize dp_length; g_autoptr(GByteArray) st = NULL; g_autoptr(GBytes) payload = NULL; /* parse */ st = fu_struct_efi_device_path_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; if (fu_struct_efi_device_path_get_length(st) < FU_STRUCT_EFI_DEVICE_PATH_SIZE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "EFI DEVICE_PATH length invalid: 0x%x", fu_struct_efi_device_path_get_length(st)); return FALSE; } fu_firmware_set_idx(firmware, fu_struct_efi_device_path_get_type(st)); priv->subtype = fu_struct_efi_device_path_get_subtype(st); /* work around a efiboot bug */ dp_length = fu_struct_efi_device_path_get_length(st); if (offset + dp_length > bufsz) { dp_length = (bufsz - offset) - 0x4; g_debug("fixing up DP length from 0x%x to 0x%x, because of a bug in efiboot", fu_struct_efi_device_path_get_length(st), (guint)dp_length); } payload = fu_bytes_new_offset(fw, offset + st->len, dp_length - st->len, error); if (payload == NULL) return FALSE; fu_firmware_set_offset(firmware, offset); fu_firmware_set_bytes(firmware, payload); fu_firmware_set_size(firmware, dp_length); /* success */ return TRUE; } static GByteArray * fu_efi_device_path_write(FuFirmware *firmware, GError **error) { FuEfiDevicePath *self = FU_EFI_DEVICE_PATH(firmware); FuEfiDevicePathPrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) st = fu_struct_efi_device_path_new(); g_autoptr(GBytes) payload = NULL; /* required */ payload = fu_firmware_get_bytes(firmware, error); if (payload == NULL) return NULL; fu_struct_efi_device_path_set_type(st, fu_firmware_get_idx(firmware)); fu_struct_efi_device_path_set_subtype(st, priv->subtype); fu_struct_efi_device_path_set_length(st, st->len + g_bytes_get_size(payload)); fu_byte_array_append_bytes(st, payload); /* success */ return g_steal_pointer(&st); } static gboolean fu_efi_device_path_build(FuFirmware *firmware, XbNode *n, GError **error) { FuEfiDevicePath *self = FU_EFI_DEVICE_PATH(firmware); FuEfiDevicePathPrivate *priv = GET_PRIVATE(self); guint64 tmp; /* optional properties */ tmp = xb_node_query_text_as_uint(n, "subtype", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->subtype = tmp; /* success */ return TRUE; } static void fu_efi_device_path_init(FuEfiDevicePath *self) { } static void fu_efi_device_path_class_init(FuEfiDevicePathClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->export = fu_efi_device_path_export; klass_firmware->parse = fu_efi_device_path_parse; klass_firmware->write = fu_efi_device_path_write; klass_firmware->build = fu_efi_device_path_build; } /** * fu_efi_device_path_new: * * Creates a new EFI DEVICE_PATH * * Since: 1.9.3 **/ FuEfiDevicePath * fu_efi_device_path_new(void) { return g_object_new(FU_TYPE_EFI_DEVICE_PATH, NULL); } fwupd-1.9.16/libfwupdplugin/fu-efi-device-path.h000066400000000000000000000011261460375044200215100ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_EFI_DEVICE_PATH (fu_efi_device_path_get_type()) G_DECLARE_DERIVABLE_TYPE(FuEfiDevicePath, fu_efi_device_path, FU, EFI_DEVICE_PATH, FuFirmware) struct _FuEfiDevicePathClass { FuFirmwareClass parent_class; }; FuEfiDevicePath * fu_efi_device_path_new(void); guint8 fu_efi_device_path_get_subtype(FuEfiDevicePath *self) G_GNUC_NON_NULL(1); void fu_efi_device_path_set_subtype(FuEfiDevicePath *self, guint8 subtype) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-efi-file-path-device-path.c000066400000000000000000000100651460375044200233540ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEfiDevicePath" #include "config.h" #include "fu-common.h" #include "fu-efi-file-path-device-path.h" #include "fu-efi-struct.h" #include "fu-string.h" /** * FuEfiFilePathDevicePath: * * See also: [class@FuEfiDevicePath] */ struct _FuEfiFilePathDevicePath { FuEfiDevicePath parent_instance; }; G_DEFINE_TYPE(FuEfiFilePathDevicePath, fu_efi_file_path_device_path, FU_TYPE_EFI_DEVICE_PATH) /** * fu_efi_file_path_device_path_get_name: * @self: a #FuEfiFilePathDevicePath * @error: (nullable): optional return location for an error * * Gets the `DEVICE_PATH` name. * Any backslash characters are automatically converted to forward slashes. * * Returns: (transfer full): UTF-8 filename, or %NULL on error * * Since: 1.9.3 **/ gchar * fu_efi_file_path_device_path_get_name(FuEfiFilePathDevicePath *self, GError **error) { g_autofree gchar *name = NULL; g_autoptr(GBytes) blob = NULL; g_return_val_if_fail(FU_IS_EFI_FILE_PATH_DEVICE_PATH(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); blob = fu_firmware_get_bytes(FU_FIRMWARE(self), error); if (blob == NULL) return NULL; name = fu_utf16_to_utf8_bytes(blob, G_LITTLE_ENDIAN, error); if (name == NULL) return NULL; g_strdelimit(name, "\\", '/'); return g_steal_pointer(&name); } /** * fu_efi_file_path_device_path_set_name: * @self: a #FuEfiFilePathDevicePath * @name: (nullable): a path to a EFI binary, typically prefixed with a backslash * @error: (nullable): optional return location for an error * * Sets the `DEVICE_PATH` name. * Any forward slash characters are automatically converted to backslashes. * * Since: 1.9.3 **/ gboolean fu_efi_file_path_device_path_set_name(FuEfiFilePathDevicePath *self, const gchar *name, GError **error) { g_autoptr(GBytes) blob = NULL; g_return_val_if_fail(FU_IS_EFI_FILE_PATH_DEVICE_PATH(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (name != NULL) { g_autofree gchar *name_bs = g_strdup(name); g_autoptr(GByteArray) buf = NULL; g_strdelimit(name_bs, "/", '\\'); buf = fu_utf8_to_utf16_byte_array(name_bs, G_LITTLE_ENDIAN, FU_UTF_CONVERT_FLAG_APPEND_NUL, error); if (buf == NULL) return FALSE; blob = g_bytes_new(buf->data, buf->len); } else { blob = g_bytes_new(NULL, 0); } fu_firmware_set_bytes(FU_FIRMWARE(self), blob); return TRUE; } static void fu_efi_file_path_device_path_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuEfiFilePathDevicePath *self = FU_EFI_FILE_PATH_DEVICE_PATH(firmware); g_autofree gchar *name = fu_efi_file_path_device_path_get_name(self, NULL); fu_xmlb_builder_insert_kv(bn, "name", name); } static gboolean fu_efi_file_path_device_path_build(FuFirmware *firmware, XbNode *n, GError **error) { FuEfiFilePathDevicePath *self = FU_EFI_FILE_PATH_DEVICE_PATH(firmware); g_autoptr(XbNode) data = NULL; /* optional data */ data = xb_node_query_first(n, "name", NULL); if (data != NULL) { if (!fu_efi_file_path_device_path_set_name(self, xb_node_get_text(data), error)) return FALSE; } /* success */ return TRUE; } static void fu_efi_file_path_device_path_init(FuEfiFilePathDevicePath *self) { fu_firmware_set_idx(FU_FIRMWARE(self), FU_EFI_DEVICE_PATH_TYPE_MEDIA); fu_efi_device_path_set_subtype(FU_EFI_DEVICE_PATH(self), FU_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE_FILE_PATH); } static void fu_efi_file_path_device_path_class_init(FuEfiFilePathDevicePathClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->export = fu_efi_file_path_device_path_export; klass_firmware->build = fu_efi_file_path_device_path_build; } /** * fu_efi_file_path_device_path_new: * * Creates a new EFI `DEVICE_PATH`. * * Returns: (transfer full): a #FuEfiFilePathDevicePath * * Since: 1.9.3 **/ FuEfiFilePathDevicePath * fu_efi_file_path_device_path_new(void) { return g_object_new(FU_TYPE_EFI_FILE_PATH_DEVICE_PATH, NULL); } fwupd-1.9.16/libfwupdplugin/fu-efi-file-path-device-path.h000066400000000000000000000013311460375044200233550ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-efi-device-path.h" #define FU_TYPE_EFI_FILE_PATH_DEVICE_PATH (fu_efi_file_path_device_path_get_type()) G_DECLARE_FINAL_TYPE(FuEfiFilePathDevicePath, fu_efi_file_path_device_path, FU, EFI_FILE_PATH_DEVICE_PATH, FuEfiDevicePath) FuEfiFilePathDevicePath * fu_efi_file_path_device_path_new(void); gchar * fu_efi_file_path_device_path_get_name(FuEfiFilePathDevicePath *self, GError **error) G_GNUC_NON_NULL(1); gboolean fu_efi_file_path_device_path_set_name(FuEfiFilePathDevicePath *self, const gchar *name, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-efi-firmware-common.c000066400000000000000000000027771460375044200224310ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-bytes.h" #include "fu-common.h" #include "fu-efi-firmware-common.h" #include "fu-efi-firmware-section.h" #include "fu-lzma-common.h" /** * fu_efi_firmware_parse_sections: * @firmware: #FuFirmware * @fw: data * @flags: flags * @error: (nullable): optional return location for an error * * Parses a UEFI section. * * Returns: %TRUE for success * * Since: 1.6.2 **/ gboolean fu_efi_firmware_parse_sections(FuFirmware *firmware, GBytes *fw, FwupdInstallFlags flags, GError **error) { gsize offset = 0; gsize bufsz = g_bytes_get_size(fw); while (offset < bufsz) { g_autoptr(FuFirmware) img = fu_efi_firmware_section_new(); g_autoptr(GBytes) blob = NULL; /* maximum payload */ blob = fu_bytes_new_offset(fw, offset, bufsz - offset, error); if (blob == NULL) { g_prefix_error(error, "failed to build maximum payload: "); return FALSE; } /* parse section */ if (!fu_firmware_parse(img, blob, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) { g_prefix_error(error, "failed to parse section of size 0x%x: ", (guint)g_bytes_get_size(blob)); return FALSE; } fu_firmware_set_offset(img, offset); if (!fu_firmware_add_image_full(firmware, img, error)) return FALSE; /* next! */ offset += fu_common_align_up(fu_firmware_get_size(img), FU_FIRMWARE_ALIGNMENT_4); } /* success */ return TRUE; } fwupd-1.9.16/libfwupdplugin/fu-efi-firmware-common.h000066400000000000000000000004741460375044200224260ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" gboolean fu_efi_firmware_parse_sections(FuFirmware *firmware, GBytes *fw, FwupdInstallFlags flags, GError **error) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-efi-firmware-file.c000066400000000000000000000202341460375044200220440ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-efi-common.h" #include "fu-efi-firmware-common.h" #include "fu-efi-firmware-file.h" #include "fu-efi-firmware-section.h" #include "fu-efi-struct.h" #include "fu-sum.h" /** * FuEfiFirmwareFile: * * A UEFI file. * * See also: [class@FuFirmware] */ typedef struct { guint8 type; guint8 attrib; } FuEfiFirmwareFilePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuEfiFirmwareFile, fu_efi_firmware_file, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_efi_firmware_file_get_instance_private(o)) #define FU_EFI_FIRMWARE_FILE_SIZE_MAX 0x1000000 /* 16 MB */ static void fu_efi_firmware_file_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuEfiFirmwareFile *self = FU_EFI_FIRMWARE_FILE(firmware); FuEfiFirmwareFilePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "attrib", priv->attrib); fu_xmlb_builder_insert_kx(bn, "type", priv->type); if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) { fu_xmlb_builder_insert_kv(bn, "name", fu_efi_guid_to_name(fu_firmware_get_id(firmware))); fu_xmlb_builder_insert_kv(bn, "type_name", fu_efi_file_type_to_string(priv->type)); } } static guint8 fu_efi_firmware_file_hdr_checksum8(GBytes *blob) { gsize bufsz = 0; guint8 checksum = 0; const guint8 *buf = g_bytes_get_data(blob, &bufsz); for (gsize i = 0; i < bufsz; i++) { if (i == FU_STRUCT_EFI_FILE_OFFSET_HDR_CHECKSUM) continue; if (i == FU_STRUCT_EFI_FILE_OFFSET_DATA_CHECKSUM) continue; if (i == FU_STRUCT_EFI_FILE_OFFSET_STATE) continue; checksum += buf[i]; } return 0x100 - checksum; } static gboolean fu_efi_firmware_file_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuEfiFirmwareFile *self = FU_EFI_FIRMWARE_FILE(firmware); FuEfiFirmwareFilePrivate *priv = GET_PRIVATE(self); guint32 size = 0x0; g_autofree gchar *guid_str = NULL; g_autoptr(GByteArray) st = NULL; g_autoptr(GBytes) blob = NULL; /* parse */ st = fu_struct_efi_file_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; priv->type = fu_struct_efi_file_get_type(st); priv->attrib = fu_struct_efi_file_get_attrs(st); guid_str = fwupd_guid_to_string(fu_struct_efi_file_get_name(st), FWUPD_GUID_FLAG_MIXED_ENDIAN); fu_firmware_set_id(firmware, guid_str); size = fu_struct_efi_file_get_size(st); if (size < st->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid FFS length, got 0x%x", (guint)size); return FALSE; } /* verify header checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint8 hdr_checksum_verify; g_autoptr(GBytes) hdr_blob = NULL; hdr_blob = fu_bytes_new_offset(fw, 0x0, st->len, error); if (hdr_blob == NULL) return FALSE; hdr_checksum_verify = fu_efi_firmware_file_hdr_checksum8(hdr_blob); if (hdr_checksum_verify != fu_struct_efi_file_get_hdr_checksum(st)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum invalid, got %02x, expected %02x", hdr_checksum_verify, fu_struct_efi_file_get_hdr_checksum(st)); return FALSE; } } /* add simple blob */ blob = fu_bytes_new_offset(fw, st->len, size - st->len, error); if (blob == NULL) { g_prefix_error(error, "failed to add payload: "); return FALSE; } /* add fv-image */ if (priv->type == FU_EFI_FILE_TYPE_FIRMWARE_VOLUME_IMAGE) { if (!fu_efi_firmware_parse_sections(firmware, blob, flags, error)) { g_prefix_error(error, "failed to add firmware image: "); return FALSE; } } else { fu_firmware_set_bytes(firmware, blob); } /* verify data checksum */ if ((priv->attrib & FU_EFI_FILE_ATTRIB_CHECKSUM) > 0 && (flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint8 data_checksum_verify = 0x100 - fu_sum8_bytes(blob); if (data_checksum_verify != fu_struct_efi_file_get_data_checksum(st)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum invalid, got %02x, expected %02x", data_checksum_verify, fu_struct_efi_file_get_data_checksum(st)); return FALSE; } } /* align size for volume */ fu_firmware_set_size(firmware, fu_common_align_up(size, fu_firmware_get_alignment(firmware))); /* success */ return TRUE; } static GBytes * fu_efi_firmware_file_write_sections(FuFirmware *firmware, GError **error) { g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); g_autoptr(GByteArray) buf = g_byte_array_new(); /* sanity check */ if (fu_firmware_get_alignment(firmware) > FU_FIRMWARE_ALIGNMENT_1M) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "alignment invalid, got 0x%02x", fu_firmware_get_alignment(firmware)); return NULL; } /* no sections defined */ if (images->len == 0) return fu_firmware_get_bytes_with_patches(firmware, error); /* add each section */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) blob = NULL; fu_firmware_set_offset(img, buf->len); blob = fu_firmware_write(img, error); if (blob == NULL) return NULL; fu_byte_array_append_bytes(buf, blob); fu_byte_array_align_up(buf, FU_FIRMWARE_ALIGNMENT_4, 0xFF); /* sanity check */ if (buf->len > FU_EFI_FIRMWARE_FILE_SIZE_MAX) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "EFI file too large, 0x%02x > 0x%02x", (guint)buf->len, (guint)FU_EFI_FIRMWARE_FILE_SIZE_MAX); return NULL; } } /* success */ return g_bytes_new(buf->data, buf->len); } static GByteArray * fu_efi_firmware_file_write(FuFirmware *firmware, GError **error) { FuEfiFirmwareFile *self = FU_EFI_FIRMWARE_FILE(firmware); FuEfiFirmwareFilePrivate *priv = GET_PRIVATE(self); fwupd_guid_t guid = {0x0}; g_autoptr(GByteArray) st = fu_struct_efi_file_new(); g_autoptr(GBytes) blob = NULL; g_autoptr(GBytes) hdr_blob = NULL; /* simple blob for now */ blob = fu_efi_firmware_file_write_sections(firmware, error); if (blob == NULL) return NULL; if (!fwupd_guid_from_string(fu_firmware_get_id(firmware), &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return NULL; fu_struct_efi_file_set_name(st, &guid); fu_struct_efi_file_set_hdr_checksum(st, 0x0); fu_struct_efi_file_set_data_checksum(st, 0x100 - fu_sum8_bytes(blob)); fu_struct_efi_file_set_type(st, priv->type); fu_struct_efi_file_set_attrs(st, priv->attrib); fu_struct_efi_file_set_size(st, g_bytes_get_size(blob) + st->len); /* fix up header checksum */ hdr_blob = g_bytes_new_static(st->data, st->len); fu_struct_efi_file_set_hdr_checksum(st, fu_efi_firmware_file_hdr_checksum8(hdr_blob)); /* success */ fu_byte_array_append_bytes(st, blob); return g_steal_pointer(&st); } static gboolean fu_efi_firmware_file_build(FuFirmware *firmware, XbNode *n, GError **error) { FuEfiFirmwareFile *self = FU_EFI_FIRMWARE_FILE(firmware); FuEfiFirmwareFilePrivate *priv = GET_PRIVATE(self); guint64 tmp; /* simple properties */ tmp = xb_node_query_text_as_uint(n, "type", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->type = tmp; tmp = xb_node_query_text_as_uint(n, "attrib", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->attrib = tmp; /* success */ return TRUE; } static void fu_efi_firmware_file_init(FuEfiFirmwareFile *self) { FuEfiFirmwareFilePrivate *priv = GET_PRIVATE(self); priv->attrib = FU_EFI_FILE_ATTRIB_NONE; priv->type = FU_EFI_FILE_TYPE_RAW; fu_firmware_set_alignment(FU_FIRMWARE(self), FU_FIRMWARE_ALIGNMENT_8); } static void fu_efi_firmware_file_class_init(FuEfiFirmwareFileClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_efi_firmware_file_parse; klass_firmware->write = fu_efi_firmware_file_write; klass_firmware->build = fu_efi_firmware_file_build; klass_firmware->export = fu_efi_firmware_file_export; } /** * fu_efi_firmware_file_new: * * Creates a new #FuFirmware * * Since: 1.6.2 **/ FuFirmware * fu_efi_firmware_file_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_FIRMWARE_FILE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-efi-firmware-file.h000066400000000000000000000006571460375044200220600ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_EFI_FIRMWARE_FILE (fu_efi_firmware_file_get_type()) G_DECLARE_DERIVABLE_TYPE(FuEfiFirmwareFile, fu_efi_firmware_file, FU, EFI_FIRMWARE_FILE, FuFirmware) struct _FuEfiFirmwareFileClass { FuFirmwareClass parent_class; }; FuFirmware * fu_efi_firmware_file_new(void); fwupd-1.9.16/libfwupdplugin/fu-efi-firmware-filesystem.c000066400000000000000000000073031460375044200233130ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-efi-firmware-file.h" #include "fu-efi-firmware-filesystem.h" /** * FuEfiFirmwareFilesystem: * * A UEFI filesystem. * * See also: [class@FuFirmware] */ G_DEFINE_TYPE(FuEfiFirmwareFilesystem, fu_efi_firmware_filesystem, FU_TYPE_FIRMWARE) #define FU_EFI_FIRMWARE_FILESYSTEM_FILES_MAX 10000 #define FU_EFI_FIRMWARE_FILESYSTEM_SIZE_MAX 0x10000000 /* 256 MB */ static gboolean fu_efi_firmware_filesystem_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0x0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); while (offset + 0x18 < bufsz) { g_autoptr(FuFirmware) img = fu_efi_firmware_file_new(); g_autoptr(GBytes) fw_tmp = NULL; gboolean is_freespace = TRUE; /* ignore free space */ for (guint i = 0; i < 0x18; i++) { if (buf[offset + i] != 0xff) { is_freespace = FALSE; break; } } if (is_freespace) break; fw_tmp = fu_bytes_new_offset(fw, offset, bufsz - offset, error); if (fw_tmp == NULL) return FALSE; if (!fu_firmware_parse(img, fw_tmp, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) { g_prefix_error(error, "failed to parse EFI file at 0x%x: ", (guint)offset); return FALSE; } fu_firmware_set_offset(firmware, offset); if (!fu_firmware_add_image_full(firmware, img, error)) return FALSE; /* next! */ offset += fu_firmware_get_size(img); } /* success */ return TRUE; } static GByteArray * fu_efi_firmware_filesystem_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); /* sanity check */ if (fu_firmware_get_alignment(firmware) > FU_FIRMWARE_ALIGNMENT_1M) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "alignment invalid, got 0x%02x", fu_firmware_get_alignment(firmware)); return NULL; } /* add each file */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) blob = NULL; fu_firmware_set_offset(img, buf->len); blob = fu_firmware_write(img, error); if (blob == NULL) return NULL; fu_byte_array_append_bytes(buf, blob); fu_byte_array_align_up(buf, fu_firmware_get_alignment(firmware), 0xFF); /* sanity check */ if (buf->len > FU_EFI_FIRMWARE_FILESYSTEM_SIZE_MAX) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "EFI filesystem too large, 0x%02x > 0x%02x", (guint)buf->len, (guint)FU_EFI_FIRMWARE_FILESYSTEM_SIZE_MAX); return NULL; } } /* success */ return g_steal_pointer(&buf); } static void fu_efi_firmware_filesystem_init(FuEfiFirmwareFilesystem *self) { /* if fuzzing, artificially limit the number of files to avoid using large amounts of RSS * when printing the FuEfiFirmwareFilesystem XML output */ fu_firmware_set_images_max( FU_FIRMWARE(self), g_getenv("FWUPD_FUZZER_RUNNING") == NULL ? FU_EFI_FIRMWARE_FILESYSTEM_FILES_MAX : 50); fu_firmware_set_alignment(FU_FIRMWARE(self), FU_FIRMWARE_ALIGNMENT_8); } static void fu_efi_firmware_filesystem_class_init(FuEfiFirmwareFilesystemClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_efi_firmware_filesystem_parse; klass_firmware->write = fu_efi_firmware_filesystem_write; } /** * fu_efi_firmware_filesystem_new: * * Creates a new #FuFirmware * * Since: 1.6.2 **/ FuFirmware * fu_efi_firmware_filesystem_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_FIRMWARE_FILESYSTEM, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-efi-firmware-filesystem.h000066400000000000000000000007511460375044200233200ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_EFI_FIRMWARE_FILESYSTEM (fu_efi_firmware_filesystem_get_type()) G_DECLARE_DERIVABLE_TYPE(FuEfiFirmwareFilesystem, fu_efi_firmware_filesystem, FU, EFI_FIRMWARE_FILESYSTEM, FuFirmware) struct _FuEfiFirmwareFilesystemClass { FuFirmwareClass parent_class; }; FuFirmware * fu_efi_firmware_filesystem_new(void); fwupd-1.9.16/libfwupdplugin/fu-efi-firmware-section.c000066400000000000000000000147071460375044200226010ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-efi-common.h" #include "fu-efi-firmware-common.h" #include "fu-efi-firmware-section.h" #include "fu-efi-firmware-volume.h" #include "fu-efi-struct.h" #include "fu-lzma-common.h" #include "fu-mem.h" /** * FuEfiFirmwareSection: * * A UEFI firmware section. * * See also: [class@FuFirmware] */ typedef struct { guint8 type; } FuEfiFirmwareSectionPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuEfiFirmwareSection, fu_efi_firmware_section, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_efi_firmware_section_get_instance_private(o)) static void fu_efi_firmware_section_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuEfiFirmwareSection *self = FU_EFI_FIRMWARE_SECTION(firmware); FuEfiFirmwareSectionPrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "type", priv->type); if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) { fu_xmlb_builder_insert_kv(bn, "name", fu_efi_guid_to_name(fu_firmware_get_id(firmware))); fu_xmlb_builder_insert_kv(bn, "type_name", fu_efi_section_type_to_string(priv->type)); } } static gboolean fu_efi_firmware_section_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuEfiFirmwareSection *self = FU_EFI_FIRMWARE_SECTION(firmware); FuEfiFirmwareSectionPrivate *priv = GET_PRIVATE(self); guint32 size; g_autoptr(GBytes) blob = NULL; g_autoptr(GByteArray) st = NULL; /* parse */ st = fu_struct_efi_section_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; size = fu_struct_efi_section_get_size(st); if (size < FU_STRUCT_EFI_SECTION_SIZE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid section size, got 0x%x", (guint)size); return FALSE; } /* name */ priv->type = fu_struct_efi_section_get_type(st); if (priv->type == FU_EFI_SECTION_TYPE_GUID_DEFINED) { g_autofree gchar *guid_str = NULL; g_autoptr(GByteArray) st_def = NULL; st_def = fu_struct_efi_section_guid_defined_parse_bytes(fw, st->len, error); if (st_def == NULL) return FALSE; guid_str = fwupd_guid_to_string(fu_struct_efi_section_guid_defined_get_name(st_def), FWUPD_GUID_FLAG_MIXED_ENDIAN); fu_firmware_set_id(firmware, guid_str); if (fu_struct_efi_section_guid_defined_get_offset(st_def) < st_def->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid section size, got 0x%x", (guint)fu_struct_efi_section_guid_defined_get_offset(st_def)); return FALSE; } offset += fu_struct_efi_section_guid_defined_get_offset(st_def) - st->len; } /* create blob */ offset += st->len; blob = fu_bytes_new_offset(fw, offset, size - offset, error); if (blob == NULL) { g_prefix_error(error, "cannot parse payload of size 0x%x: ", size); return FALSE; } fu_firmware_set_offset(firmware, offset); fu_firmware_set_size(firmware, size); fu_firmware_set_bytes(firmware, blob); /* nested volume */ if (priv->type == FU_EFI_SECTION_TYPE_VOLUME_IMAGE) { g_autoptr(FuFirmware) img = fu_efi_firmware_volume_new(); if (!fu_firmware_parse(img, blob, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) { g_prefix_error(error, "failed to parse nested volume: "); return FALSE; } fu_firmware_add_image(firmware, img); /* LZMA */ } else if (priv->type == FU_EFI_SECTION_TYPE_GUID_DEFINED && g_strcmp0(fu_firmware_get_id(firmware), FU_EFI_FIRMWARE_SECTION_LZMA_COMPRESS) == 0) { g_autoptr(GBytes) blob_uncomp = NULL; /* parse all sections */ blob_uncomp = fu_lzma_decompress_bytes(blob, error); if (blob_uncomp == NULL) { g_prefix_error(error, "failed to decompress lzma section: "); return FALSE; } if (!fu_efi_firmware_parse_sections(firmware, blob_uncomp, flags, error)) { g_prefix_error(error, "failed to parse sections: "); return FALSE; } } /* success */ return TRUE; } static GByteArray * fu_efi_firmware_section_write(FuFirmware *firmware, GError **error) { FuEfiFirmwareSection *self = FU_EFI_FIRMWARE_SECTION(firmware); FuEfiFirmwareSectionPrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) buf = fu_struct_efi_section_new(); g_autoptr(GBytes) blob = NULL; /* simple blob for now */ blob = fu_firmware_get_bytes_with_patches(firmware, error); if (blob == NULL) return NULL; /* header */ if (priv->type == FU_EFI_SECTION_TYPE_GUID_DEFINED) { fwupd_guid_t guid = {0x0}; g_autoptr(GByteArray) st_def = fu_struct_efi_section_guid_defined_new(); if (!fwupd_guid_from_string(fu_firmware_get_id(firmware), &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return NULL; fu_struct_efi_section_guid_defined_set_name(st_def, &guid); fu_struct_efi_section_guid_defined_set_offset(st_def, buf->len + st_def->len); g_byte_array_append(buf, st_def->data, st_def->len); } fu_struct_efi_section_set_type(buf, priv->type); fu_struct_efi_section_set_size(buf, buf->len + g_bytes_get_size(blob)); /* blob */ fu_byte_array_append_bytes(buf, blob); return g_steal_pointer(&buf); } static gboolean fu_efi_firmware_section_build(FuFirmware *firmware, XbNode *n, GError **error) { FuEfiFirmwareSection *self = FU_EFI_FIRMWARE_SECTION(firmware); FuEfiFirmwareSectionPrivate *priv = GET_PRIVATE(self); guint64 tmp; /* simple properties */ tmp = xb_node_query_text_as_uint(n, "type", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->type = tmp; /* success */ return TRUE; } static void fu_efi_firmware_section_init(FuEfiFirmwareSection *self) { FuEfiFirmwareSectionPrivate *priv = GET_PRIVATE(self); priv->type = FU_EFI_SECTION_TYPE_RAW; fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); // fu_firmware_set_alignment (FU_FIRMWARE (self), FU_FIRMWARE_ALIGNMENT_8); } static void fu_efi_firmware_section_class_init(FuEfiFirmwareSectionClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_efi_firmware_section_parse; klass_firmware->write = fu_efi_firmware_section_write; klass_firmware->build = fu_efi_firmware_section_build; klass_firmware->export = fu_efi_firmware_section_export; } /** * fu_efi_firmware_section_new: * * Creates a new #FuFirmware * * Since: 1.6.2 **/ FuFirmware * fu_efi_firmware_section_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_FIRMWARE_SECTION, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-efi-firmware-section.h000066400000000000000000000007241460375044200226000ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_EFI_FIRMWARE_SECTION (fu_efi_firmware_section_get_type()) G_DECLARE_DERIVABLE_TYPE(FuEfiFirmwareSection, fu_efi_firmware_section, FU, EFI_FIRMWARE_SECTION, FuFirmware) struct _FuEfiFirmwareSectionClass { FuFirmwareClass parent_class; }; FuFirmware * fu_efi_firmware_section_new(void); fwupd-1.9.16/libfwupdplugin/fu-efi-firmware-volume.c000066400000000000000000000205161460375044200224370ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEfiFirmwareVolume" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-efi-common.h" #include "fu-efi-firmware-filesystem.h" #include "fu-efi-firmware-volume.h" #include "fu-efi-struct.h" #include "fu-sum.h" /** * FuEfiFirmwareVolume: * * A UEFI file volume. * * See also: [class@FuFirmware] */ typedef struct { guint16 attrs; } FuEfiFirmwareVolumePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuEfiFirmwareVolume, fu_efi_firmware_volume, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_efi_firmware_volume_get_instance_private(o)) static void fu_ifd_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuEfiFirmwareVolume *self = FU_EFI_FIRMWARE_VOLUME(firmware); FuEfiFirmwareVolumePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "attrs", priv->attrs); if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) { fu_xmlb_builder_insert_kv(bn, "name", fu_efi_guid_to_name(fu_firmware_get_id(firmware))); } } static gboolean fu_efi_firmware_volume_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_efi_volume_validate_bytes(fw, offset, error); } static gboolean fu_efi_firmware_volume_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuEfiFirmwareVolume *self = FU_EFI_FIRMWARE_VOLUME(firmware); FuEfiFirmwareVolumePrivate *priv = GET_PRIVATE(self); gsize blockmap_sz = 0; gsize bufsz = g_bytes_get_size(fw); guint16 hdr_length = 0; guint32 attrs = 0; guint64 fv_length = 0; guint8 alignment; g_autofree gchar *guid_str = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GByteArray) st_hdr = NULL; /* parse */ st_hdr = fu_struct_efi_volume_parse_bytes(fw, offset, error); if (st_hdr == NULL) return FALSE; /* guid */ guid_str = fwupd_guid_to_string(fu_struct_efi_volume_get_guid(st_hdr), FWUPD_GUID_FLAG_MIXED_ENDIAN); g_debug("volume GUID: %s [%s]", guid_str, fu_efi_guid_to_name(guid_str)); /* length */ fv_length = fu_struct_efi_volume_get_length(st_hdr); if (fv_length == 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid volume length"); return FALSE; } fu_firmware_set_size(firmware, fv_length); attrs = fu_struct_efi_volume_get_attrs(st_hdr); alignment = (attrs & 0x00ff0000) >> 16; if (alignment > FU_FIRMWARE_ALIGNMENT_2G) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "0x%x invalid, maximum is 0x%x", (guint)alignment, (guint)FU_FIRMWARE_ALIGNMENT_2G); return FALSE; } fu_firmware_set_alignment(firmware, alignment); priv->attrs = attrs & 0xffff; hdr_length = fu_struct_efi_volume_get_hdr_len(st_hdr); if (hdr_length < st_hdr->len || hdr_length > fv_length || hdr_length > bufsz || hdr_length % 2 != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid volume header length 0x%x", (guint)hdr_length); return FALSE; } /* verify checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint16 checksum_verify; g_autoptr(GBytes) blob_hdr = NULL; blob_hdr = fu_bytes_new_offset(fw, offset, hdr_length, error); if (blob_hdr == NULL) return FALSE; checksum_verify = fu_sum16w_bytes(blob_hdr, G_LITTLE_ENDIAN); if (checksum_verify != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum invalid, got %02x, expected %02x", checksum_verify, fu_struct_efi_volume_get_checksum(st_hdr)); return FALSE; } } /* add image */ blob = fu_bytes_new_offset(fw, offset + hdr_length, fv_length - hdr_length, error); if (blob == NULL) return FALSE; fu_firmware_set_offset(firmware, offset); fu_firmware_set_id(firmware, guid_str); fu_firmware_set_size(firmware, fv_length); /* parse, which might cascade and do something like FFS2 */ if (g_strcmp0(guid_str, FU_EFI_FIRMWARE_VOLUME_GUID_FFS2) == 0) { g_autoptr(FuFirmware) img = fu_efi_firmware_filesystem_new(); fu_firmware_set_alignment(img, fu_firmware_get_alignment(firmware)); if (!fu_firmware_parse(img, blob, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return FALSE; fu_firmware_add_image(firmware, img); } else { fu_firmware_set_bytes(firmware, blob); } /* skip the blockmap */ offset += st_hdr->len; while (offset < bufsz) { guint32 num_blocks; guint32 length; g_autoptr(GByteArray) st_blk = NULL; st_blk = fu_struct_efi_volume_block_map_parse_bytes(fw, offset, error); if (st_blk == NULL) return FALSE; num_blocks = fu_struct_efi_volume_block_map_get_num_blocks(st_blk); length = fu_struct_efi_volume_block_map_get_length(st_blk); offset += st_blk->len; if (num_blocks == 0x0 && length == 0x0) break; blockmap_sz += (gsize)num_blocks * (gsize)length; } if (blockmap_sz < (gsize)fv_length) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "blocks allocated is less than volume length"); return FALSE; } /* success */ return TRUE; } static GByteArray * fu_efi_firmware_volume_write(FuFirmware *firmware, GError **error) { FuEfiFirmwareVolume *self = FU_EFI_FIRMWARE_VOLUME(firmware); FuEfiFirmwareVolumePrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) buf = fu_struct_efi_volume_new(); g_autoptr(GByteArray) st_blk = fu_struct_efi_volume_block_map_new(); fwupd_guid_t guid = {0x0}; guint32 hdr_length = 0x48; guint64 fv_length; g_autoptr(GBytes) img_blob = NULL; g_autoptr(FuFirmware) img = NULL; /* sanity check */ if (fu_firmware_get_alignment(firmware) > FU_FIRMWARE_ALIGNMENT_1M) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "alignment invalid, got 0x%02x", fu_firmware_get_alignment(firmware)); return NULL; } /* GUID */ if (fu_firmware_get_id(firmware) == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no GUID set for EFI FV"); return NULL; } if (!fwupd_guid_from_string(fu_firmware_get_id(firmware), &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return NULL; /* length */ img = fu_firmware_get_image_by_id(firmware, NULL, NULL); if (img != NULL) { img_blob = fu_firmware_write(img, error); if (img_blob == NULL) { g_prefix_error(error, "no EFI FV child payload: "); return NULL; } } else { img_blob = fu_firmware_get_bytes_with_patches(firmware, error); if (img_blob == NULL) { g_prefix_error(error, "no EFI FV payload: "); return NULL; } } /* pack */ fu_struct_efi_volume_set_guid(buf, &guid); fv_length = fu_common_align_up(hdr_length + g_bytes_get_size(img_blob), fu_firmware_get_alignment(firmware)); fu_struct_efi_volume_set_length(buf, fv_length); fu_struct_efi_volume_set_attrs(buf, priv->attrs | ((guint32)fu_firmware_get_alignment(firmware) << 16)); fu_struct_efi_volume_set_hdr_len(buf, hdr_length); /* blockmap */ fu_struct_efi_volume_block_map_set_num_blocks(st_blk, fv_length); fu_struct_efi_volume_block_map_set_length(st_blk, 0x1); g_byte_array_append(buf, st_blk->data, st_blk->len); fu_struct_efi_volume_block_map_set_num_blocks(st_blk, 0x0); fu_struct_efi_volume_block_map_set_length(st_blk, 0x0); g_byte_array_append(buf, st_blk->data, st_blk->len); /* fix up checksum */ fu_struct_efi_volume_set_checksum(buf, 0x10000 - fu_sum16w(buf->data, buf->len, G_LITTLE_ENDIAN)); /* pad contents to alignment */ fu_byte_array_append_bytes(buf, img_blob); fu_byte_array_set_size(buf, fv_length, 0xFF); /* success */ return g_steal_pointer(&buf); } static void fu_efi_firmware_volume_init(FuEfiFirmwareVolume *self) { FuEfiFirmwareVolumePrivate *priv = GET_PRIVATE(self); priv->attrs = 0xfeff; } static void fu_efi_firmware_volume_class_init(FuEfiFirmwareVolumeClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_efi_firmware_volume_check_magic; klass_firmware->parse = fu_efi_firmware_volume_parse; klass_firmware->write = fu_efi_firmware_volume_write; klass_firmware->export = fu_ifd_firmware_export; } /** * fu_efi_firmware_volume_new: * * Creates a new #FuFirmware * * Since: 1.6.2 **/ FuFirmware * fu_efi_firmware_volume_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_EFI_FIRMWARE_VOLUME, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-efi-firmware-volume.h000066400000000000000000000007151460375044200224430ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_EFI_FIRMWARE_VOLUME (fu_efi_firmware_volume_get_type()) G_DECLARE_DERIVABLE_TYPE(FuEfiFirmwareVolume, fu_efi_firmware_volume, FU, EFI_FIRMWARE_VOLUME, FuFirmware) struct _FuEfiFirmwareVolumeClass { FuFirmwareClass parent_class; }; FuFirmware * fu_efi_firmware_volume_new(void); fwupd-1.9.16/libfwupdplugin/fu-efi-hard-drive-device-path.c000066400000000000000000000221231460375044200235260ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEfiDevicePath" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-efi-hard-drive-device-path.h" #include "fu-efi-struct.h" #include "fu-firmware-common.h" #include "fu-mem.h" #include "fu-string.h" /** * FuEfiHardDriveDevicePath: * * See also: [class@FuEfiDevicePath] */ struct _FuEfiHardDriveDevicePath { FuEfiDevicePath parent_instance; guint32 partition_number; guint64 partition_start; /* blocks */ guint64 partition_size; /* blocks */ fwupd_guid_t partition_signature; FuEfiHardDriveDevicePathPartitionFormat partition_format; FuEfiHardDriveDevicePathSignatureType signature_type; }; G_DEFINE_TYPE(FuEfiHardDriveDevicePath, fu_efi_hard_drive_device_path, FU_TYPE_EFI_DEVICE_PATH) #define BLOCK_SIZE_FALLBACK 0x200 static void fu_efi_hard_drive_device_path_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuEfiHardDriveDevicePath *self = FU_EFI_HARD_DRIVE_DEVICE_PATH(firmware); g_autofree gchar *partition_signature = fwupd_guid_to_string(&self->partition_signature, FWUPD_GUID_FLAG_MIXED_ENDIAN); fu_xmlb_builder_insert_kx(bn, "partition_number", self->partition_number); fu_xmlb_builder_insert_kx(bn, "partition_start", self->partition_start); fu_xmlb_builder_insert_kx(bn, "partition_size", self->partition_size); fu_xmlb_builder_insert_kv(bn, "partition_signature", partition_signature); fu_xmlb_builder_insert_kv( bn, "partition_format", fu_efi_hard_drive_device_path_partition_format_to_string(self->partition_format)); fu_xmlb_builder_insert_kv( bn, "signature_type", fu_efi_hard_drive_device_path_signature_type_to_string(self->signature_type)); } static gboolean fu_efi_hard_drive_device_path_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuEfiHardDriveDevicePath *self = FU_EFI_HARD_DRIVE_DEVICE_PATH(firmware); g_autoptr(GByteArray) st = NULL; /* re-parse */ st = fu_struct_efi_hard_drive_device_path_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; self->partition_number = fu_struct_efi_hard_drive_device_path_get_partition_number(st); self->partition_start = fu_struct_efi_hard_drive_device_path_get_partition_start(st); self->partition_size = fu_struct_efi_hard_drive_device_path_get_partition_size(st); memcpy(self->partition_signature, fu_struct_efi_hard_drive_device_path_get_partition_signature(st), sizeof(self->partition_signature)); self->partition_format = fu_struct_efi_hard_drive_device_path_get_partition_format(st); self->signature_type = fu_struct_efi_hard_drive_device_path_get_signature_type(st); /* success */ fu_firmware_set_size(firmware, fu_struct_efi_device_path_get_length(st)); return TRUE; } static GByteArray * fu_efi_hard_drive_device_path_write(FuFirmware *firmware, GError **error) { FuEfiHardDriveDevicePath *self = FU_EFI_HARD_DRIVE_DEVICE_PATH(firmware); g_autoptr(GByteArray) st = fu_struct_efi_hard_drive_device_path_new(); /* required */ fu_struct_efi_hard_drive_device_path_set_partition_number(st, self->partition_number); fu_struct_efi_hard_drive_device_path_set_partition_start(st, self->partition_start); fu_struct_efi_hard_drive_device_path_set_partition_size(st, self->partition_size); fu_struct_efi_hard_drive_device_path_set_partition_signature(st, &self->partition_signature); fu_struct_efi_hard_drive_device_path_set_partition_format(st, self->partition_format); fu_struct_efi_hard_drive_device_path_set_signature_type(st, self->signature_type); /* success */ return g_steal_pointer(&st); } static gboolean fu_efi_hard_drive_device_path_build(FuFirmware *firmware, XbNode *n, GError **error) { FuEfiHardDriveDevicePath *self = FU_EFI_HARD_DRIVE_DEVICE_PATH(firmware); const gchar *tmp; guint64 value = 0; /* optional data */ tmp = xb_node_query_text(n, "partition_number", NULL); if (tmp != NULL) { if (!fu_strtoull(tmp, &value, 0x0, G_MAXUINT32, error)) return FALSE; self->partition_number = value; } tmp = xb_node_query_text(n, "partition_start", NULL); if (tmp != NULL) { if (!fu_strtoull(tmp, &value, 0x0, G_MAXUINT64, error)) return FALSE; self->partition_start = value; } tmp = xb_node_query_text(n, "partition_size", NULL); if (tmp != NULL) { if (!fu_strtoull(tmp, &value, 0x0, G_MAXUINT64, error)) return FALSE; self->partition_size = value; } tmp = xb_node_query_text(n, "partition_signature", NULL); if (tmp != NULL) { if (!fwupd_guid_from_string(tmp, &self->partition_signature, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return FALSE; } tmp = xb_node_query_text(n, "partition_format", NULL); if (tmp != NULL) { self->partition_format = fu_efi_hard_drive_device_path_partition_format_from_string(tmp); } tmp = xb_node_query_text(n, "signature_type", NULL); if (tmp != NULL) { self->signature_type = fu_efi_hard_drive_device_path_signature_type_from_string(tmp); } /* success */ return TRUE; } static void fu_efi_hard_drive_device_path_init(FuEfiHardDriveDevicePath *self) { fu_firmware_set_idx(FU_FIRMWARE(self), FU_EFI_DEVICE_PATH_TYPE_MEDIA); fu_efi_device_path_set_subtype(FU_EFI_DEVICE_PATH(self), FU_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE_HARD_DRIVE); } static void fu_efi_hard_drive_device_path_class_init(FuEfiHardDriveDevicePathClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->export = fu_efi_hard_drive_device_path_export; klass_firmware->parse = fu_efi_hard_drive_device_path_parse; klass_firmware->write = fu_efi_hard_drive_device_path_write; klass_firmware->build = fu_efi_hard_drive_device_path_build; } /** * fu_efi_hard_drive_device_path_new: * * Creates a new EFI `DEVICE_PATH`. * * Returns: (transfer full): a #FuEfiHardDriveDevicePath * * Since: 1.9.3 **/ FuEfiHardDriveDevicePath * fu_efi_hard_drive_device_path_new(void) { return g_object_new(FU_TYPE_EFI_HARD_DRIVE_DEVICE_PATH, NULL); } /** * fu_efi_hard_drive_device_path_new_from_volume: * @volume: a #FuVolume * @error: (nullable): optional return location for an error * * Creates a new EFI `DEVICE_PATH` for a specific volume. * * Returns: (transfer full): a #FuEfiHardDriveDevicePath, or %NULL on error * * Since: 1.9.3 **/ FuEfiHardDriveDevicePath * fu_efi_hard_drive_device_path_new_from_volume(FuVolume *volume, GError **error) { guint16 block_size; g_autoptr(FuEfiHardDriveDevicePath) self = fu_efi_hard_drive_device_path_new(); g_autofree gchar *partition_kind = NULL; g_autofree gchar *partition_uuid = NULL; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_VOLUME(volume), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* common to both */ block_size = fu_volume_get_block_size(volume, &error_local); if (block_size == 0) { g_warning("failed to get volume block size, falling back to 0x%x: %s", (guint)BLOCK_SIZE_FALLBACK, error_local->message); block_size = BLOCK_SIZE_FALLBACK; } self->partition_number = fu_volume_get_partition_number(volume); self->partition_start = fu_volume_get_partition_offset(volume) / block_size; self->partition_size = fu_volume_get_partition_size(volume) / block_size; /* set up the rest of the struct */ partition_kind = fu_volume_get_partition_kind(volume); if (partition_kind == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "partition kind required"); return NULL; } partition_uuid = fu_volume_get_partition_uuid(volume); if (partition_uuid == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "partition UUID required"); return NULL; } if (g_strcmp0(partition_kind, FU_VOLUME_KIND_ESP) == 0 || g_strcmp0(partition_kind, FU_VOLUME_KIND_BDP) == 0) { self->partition_format = FU_EFI_HARD_DRIVE_DEVICE_PATH_PARTITION_FORMAT_GUID_PARTITION_TABLE; self->signature_type = FU_EFI_HARD_DRIVE_DEVICE_PATH_SIGNATURE_TYPE_GUID; if (!fwupd_guid_from_string(partition_uuid, &self->partition_signature, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return NULL; } else if (g_strcmp0(partition_kind, "0xef") == 0) { guint32 value = 0; g_auto(GStrv) parts = g_strsplit(partition_uuid, "-", -1); if (!fu_firmware_strparse_uint32_safe(parts[0], strlen(parts[0]), 0x0, &value, error)) { g_prefix_error(error, "failed to parse %s: ", parts[0]); return NULL; } if (!fu_memwrite_uint32_safe(self->partition_signature, sizeof(self->partition_signature), 0x0, value, G_LITTLE_ENDIAN, error)) return NULL; self->partition_format = FU_EFI_HARD_DRIVE_DEVICE_PATH_PARTITION_FORMAT_LEGACY_MBR; self->signature_type = FU_EFI_HARD_DRIVE_DEVICE_PATH_SIGNATURE_TYPE_ADDR1B8; } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "partition kind %s not supported", partition_kind); return NULL; } /* success */ return g_steal_pointer(&self); } fwupd-1.9.16/libfwupdplugin/fu-efi-hard-drive-device-path.h000066400000000000000000000011471460375044200235360ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-efi-device-path.h" #include "fu-volume.h" #define FU_TYPE_EFI_HARD_DRIVE_DEVICE_PATH (fu_efi_hard_drive_device_path_get_type()) G_DECLARE_FINAL_TYPE(FuEfiHardDriveDevicePath, fu_efi_hard_drive_device_path, FU, EFI_HARD_DRIVE_DEVICE_PATH, FuEfiDevicePath) FuEfiHardDriveDevicePath * fu_efi_hard_drive_device_path_new(void); FuEfiHardDriveDevicePath * fu_efi_hard_drive_device_path_new_from_volume(FuVolume *volume, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-efi-load-option.c000066400000000000000000000212331460375044200215400ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEfiLoadOption" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-efi-device-path-list.h" #include "fu-efi-load-option.h" #include "fu-efi-struct.h" #include "fu-efivar.h" #include "fu-mem.h" #include "fu-string.h" struct _FuEfiLoadOption { FuFirmware parent_instance; guint32 attrs; GBytes *optional_data; }; G_DEFINE_TYPE(FuEfiLoadOption, fu_efi_load_option, FU_TYPE_FIRMWARE) #define FU_EFI_LOAD_OPTION_DESCRIPTION_SIZE_MAX 0x1000u /* bytes */ /** * fu_efi_load_option_get_optional_data: * @self: a #FuEfiLoadOption * * Gets any optional data. * * Returns: (transfer none): optional data, or %NULL * * Since: 1.9.3 **/ GBytes * fu_efi_load_option_get_optional_data(FuEfiLoadOption *self) { g_return_val_if_fail(FU_IS_EFI_LOAD_OPTION(self), NULL); return self->optional_data; } /** * fu_efi_load_option_set_optional_data: * @self: a #FuEfiLoadOption * @optional_data: (nullable): a #GBytes, or %NULL * * Sets any optional data. * * Since: 1.9.3 **/ void fu_efi_load_option_set_optional_data(FuEfiLoadOption *self, GBytes *optional_data) { g_return_if_fail(FU_IS_EFI_LOAD_OPTION(self)); if (self->optional_data != NULL) { g_bytes_unref(self->optional_data); self->optional_data = NULL; } if (optional_data != NULL) self->optional_data = g_bytes_ref(optional_data); } /** * fu_efi_load_option_set_optional_path: * @self: a #FuEfiLoadOption * @optional_path: UTF-8 path * @error: (nullable): optional return location for an error * * Sets UTF-16 optional data from a path. If required, a leading backslash will be added. * * Since: 1.9.3 **/ gboolean fu_efi_load_option_set_optional_path(FuEfiLoadOption *self, const gchar *optional_path, GError **error) { g_autoptr(GString) str = g_string_new(optional_path); g_autoptr(GBytes) opt_blob = NULL; g_return_val_if_fail(FU_IS_EFI_LOAD_OPTION(self), FALSE); g_return_val_if_fail(optional_path != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* is required if a path */ if (!g_str_has_prefix(str->str, "\\")) g_string_prepend(str, "\\"); opt_blob = fu_utf8_to_utf16_bytes(str->str, G_LITTLE_ENDIAN, FU_UTF_CONVERT_FLAG_APPEND_NUL, error); if (opt_blob == NULL) return FALSE; fu_efi_load_option_set_optional_data(self, opt_blob); /* success */ return TRUE; } static gboolean fu_efi_load_option_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(firmware); gsize bufsz = 0; g_autofree gchar *id = NULL; g_autoptr(FuEfiDevicePathList) device_path_list = fu_efi_device_path_list_new(); g_autoptr(GByteArray) buf_utf16 = g_byte_array_new(); g_autoptr(GByteArray) st = NULL; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* parse header */ st = fu_struct_efi_load_option_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; self->attrs = fu_struct_efi_load_option_get_attrs(st); offset += st->len; /* parse UTF-16 description */ for (; offset < bufsz; offset += 2) { guint16 tmp = 0; if (buf_utf16->len > FU_EFI_LOAD_OPTION_DESCRIPTION_SIZE_MAX) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "description was too long, limit is 0x%x chars", FU_EFI_LOAD_OPTION_DESCRIPTION_SIZE_MAX / 2); return FALSE; } if (!fu_memread_uint16_safe(buf, bufsz, offset, &tmp, G_LITTLE_ENDIAN, error)) return FALSE; if (tmp == 0) break; fu_byte_array_append_uint16(buf_utf16, tmp, G_LITTLE_ENDIAN); } id = fu_utf16_to_utf8_byte_array(buf_utf16, G_LITTLE_ENDIAN, error); if (id == NULL) return FALSE; fu_firmware_set_id(firmware, id); offset += 2; /* parse dp blob */ if (!fu_firmware_parse_full(FU_FIRMWARE(device_path_list), fw, offset, flags, error)) return FALSE; fu_firmware_add_image(firmware, FU_FIRMWARE(device_path_list)); offset += fu_struct_efi_load_option_get_dp_size(st); /* optional data */ if (offset < bufsz) { g_autoptr(GBytes) opt_blob = NULL; opt_blob = fu_bytes_new_offset(fw, offset, bufsz - offset, error); if (opt_blob == NULL) return FALSE; fu_efi_load_option_set_optional_data(self, opt_blob); } /* success */ return TRUE; } static GByteArray * fu_efi_load_option_write(FuFirmware *firmware, GError **error) { FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(firmware); g_autoptr(GByteArray) buf_utf16 = NULL; g_autoptr(GByteArray) st = fu_struct_efi_load_option_new(); g_autoptr(GBytes) dpbuf = NULL; /* header */ fu_struct_efi_load_option_set_attrs(st, self->attrs); /* label */ if (fu_firmware_get_id(firmware) == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware ID required"); return NULL; } buf_utf16 = fu_utf8_to_utf16_byte_array(fu_firmware_get_id(firmware), G_LITTLE_ENDIAN, FU_UTF_CONVERT_FLAG_APPEND_NUL, error); if (buf_utf16 == NULL) return NULL; g_byte_array_append(st, buf_utf16->data, buf_utf16->len); /* dpbuf */ dpbuf = fu_firmware_get_image_by_gtype_bytes(firmware, FU_TYPE_EFI_DEVICE_PATH_LIST, error); if (dpbuf == NULL) return NULL; fu_struct_efi_load_option_set_dp_size(st, g_bytes_get_size(dpbuf)); fu_byte_array_append_bytes(st, dpbuf); /* optional content */ if (self->optional_data != NULL) fu_byte_array_append_bytes(st, self->optional_data); return g_steal_pointer(&st); } static gboolean fu_efi_load_option_build(FuFirmware *firmware, XbNode *n, GError **error) { FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(firmware); guint64 tmp; g_autoptr(XbNode) optional_data = NULL; /* simple properties */ tmp = xb_node_query_text_as_uint(n, "attrs", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) self->attrs = tmp; /* optional data */ optional_data = xb_node_query_first(n, "optional_data", NULL); if (optional_data != NULL) { g_autoptr(GBytes) blob = NULL; if (xb_node_get_text(optional_data) != NULL) { gsize bufsz = 0; g_autofree guchar *buf = NULL; buf = g_base64_decode(xb_node_get_text(optional_data), &bufsz); blob = g_bytes_new(buf, bufsz); } else { blob = g_bytes_new(NULL, 0); } fu_efi_load_option_set_optional_data(self, blob); } /* success */ return TRUE; } static void fu_efi_load_option_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(firmware); fu_xmlb_builder_insert_kx(bn, "attrs", self->attrs); if (self->optional_data != NULL) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(self->optional_data, &bufsz); g_autofree gchar *datastr = g_base64_encode(buf, bufsz); xb_builder_node_insert_text(bn, "optional_data", datastr, NULL); } } static void fu_efi_load_option_finalize(GObject *obj) { FuEfiLoadOption *self = FU_EFI_LOAD_OPTION(obj); if (self->optional_data != NULL) g_bytes_unref(self->optional_data); G_OBJECT_CLASS(fu_efi_load_option_parent_class)->finalize(obj); } static void fu_efi_load_option_class_init(FuEfiLoadOptionClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_efi_load_option_finalize; klass_firmware->parse = fu_efi_load_option_parse; klass_firmware->write = fu_efi_load_option_write; klass_firmware->build = fu_efi_load_option_build; klass_firmware->export = fu_efi_load_option_export; } static void fu_efi_load_option_init(FuEfiLoadOption *self) { self->attrs = FU_EFI_LOAD_OPTION_ATTRS_ACTIVE; g_type_ensure(FU_TYPE_EFI_DEVICE_PATH_LIST); } /** * fu_efi_load_option_new_esp_for_boot_entry: * @boot_entry: a boot entry number * @error: (nullable): optional return location for an error * * Gets the platform ESP using a UNIX or UDisks path * * Returns: (transfer full): a #FuEfiLoadOption, or %NULL if invalid * * Since: 1.9.3 **/ FuEfiLoadOption * fu_efi_load_option_new_esp_for_boot_entry(guint16 boot_entry, GError **error) { g_autofree gchar *name = g_strdup_printf("Boot%04X", boot_entry); g_autoptr(FuEfiLoadOption) self = g_object_new(FU_TYPE_EFI_LOAD_OPTION, NULL); g_autoptr(GBytes) fw = NULL; g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* get data */ fw = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_EFI_GLOBAL, name, NULL, error); if (fw == NULL) return NULL; if (!fu_firmware_parse(FU_FIRMWARE(self), fw, FWUPD_INSTALL_FLAG_NONE, error)) return NULL; return g_steal_pointer(&self); } /** * fu_efi_load_option_new: * * Returns: (transfer full): a #FuEfiLoadOption * * Since: 1.9.3 **/ FuEfiLoadOption * fu_efi_load_option_new(void) { return g_object_new(FU_TYPE_EFI_LOAD_OPTION, NULL); } fwupd-1.9.16/libfwupdplugin/fu-efi-load-option.h000066400000000000000000000015361460375044200215510ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_EFI_LOAD_OPTION (fu_efi_load_option_get_type()) G_DECLARE_FINAL_TYPE(FuEfiLoadOption, fu_efi_load_option, FU, EFI_LOAD_OPTION, FuFirmware) GBytes * fu_efi_load_option_get_optional_data(FuEfiLoadOption *self) G_GNUC_NON_NULL(1); void fu_efi_load_option_set_optional_data(FuEfiLoadOption *self, GBytes *optional_data) G_GNUC_NON_NULL(1); gboolean fu_efi_load_option_set_optional_path(FuEfiLoadOption *self, const gchar *optional_path, GError **error) G_GNUC_NON_NULL(1); FuEfiLoadOption * fu_efi_load_option_new(void) G_GNUC_WARN_UNUSED_RESULT; FuEfiLoadOption * fu_efi_load_option_new_esp_for_boot_entry(guint16 boot_entry, GError **error) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.9.16/libfwupdplugin/fu-efi-signature-list.c000066400000000000000000000172611460375044200222730ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEfiSignatureList" #include "config.h" #include #include #include "fu-byte-array.h" #include "fu-common.h" #include "fu-efi-signature-list.h" #include "fu-efi-signature-private.h" #include "fu-efi-struct.h" #include "fu-mem.h" /** * FuEfiSignatureList: * * A UEFI signature list typically found in the `PK` and `KEK` keys. * * See also: [class@FuFirmware] */ struct _FuEfiSignatureList { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuEfiSignatureList, fu_efi_signature_list, FU_TYPE_FIRMWARE) const guint8 FU_EFI_SIGLIST_HEADER_MAGIC[] = {0x26, 0x16, 0xC4, 0xC1, 0x4C}; static gboolean fu_efi_signature_list_parse_item(FuEfiSignatureList *self, FuEfiSignatureKind sig_kind, const guint8 *buf, gsize bufsz, gsize offset, guint32 size, GError **error) { fwupd_guid_t guid; gsize sig_datasz; g_autofree gchar *sig_owner = NULL; g_autofree guint8 *sig_data = NULL; g_autoptr(FuEfiSignature) sig = NULL; g_autoptr(GBytes) data = NULL; /* allocate data buf */ if (size <= sizeof(fwupd_guid_t)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "SignatureSize invalid: 0x%x", (guint)size); return FALSE; } sig_datasz = size - sizeof(fwupd_guid_t); sig_data = g_malloc0(sig_datasz); /* read both blocks of data */ if (!fu_memcpy_safe((guint8 *)&guid, sizeof(guid), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(guid), error)) { g_prefix_error(error, "failed to read signature GUID: "); return FALSE; } if (!fu_memcpy_safe(sig_data, sig_datasz, 0x0, /* dst */ buf, bufsz, offset + sizeof(fwupd_guid_t), /* src */ sig_datasz, error)) { g_prefix_error(error, "failed to read signature data: "); return FALSE; } /* create item */ sig_owner = fwupd_guid_to_string(&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN); data = g_bytes_new(sig_data, sig_datasz); sig = fu_efi_signature_new(sig_kind, sig_owner); fu_firmware_set_bytes(FU_FIRMWARE(sig), data); return fu_firmware_add_image_full(FU_FIRMWARE(self), FU_FIRMWARE(sig), error); } static gboolean fu_efi_signature_list_parse_list(FuEfiSignatureList *self, const guint8 *buf, gsize bufsz, gsize *offset, GError **error) { FuEfiSignatureKind sig_kind = FU_EFI_SIGNATURE_KIND_UNKNOWN; gsize offset_tmp; guint32 header_size; guint32 list_size; guint32 size; g_autofree gchar *sig_type = NULL; g_autoptr(GByteArray) st = NULL; /* read EFI_SIGNATURE_LIST */ st = fu_struct_efi_signature_list_parse(buf, bufsz, *offset, error); if (st == NULL) return FALSE; sig_type = fwupd_guid_to_string(fu_struct_efi_signature_list_get_type(st), FWUPD_GUID_FLAG_MIXED_ENDIAN); if (g_strcmp0(sig_type, "c1c41626-504c-4092-aca9-41f936934328") == 0) { sig_kind = FU_EFI_SIGNATURE_KIND_SHA256; } else if (g_strcmp0(sig_type, "a5c059a1-94e4-4aa7-87b5-ab155c2bf072") == 0) { sig_kind = FU_EFI_SIGNATURE_KIND_X509; } list_size = fu_struct_efi_signature_list_get_list_size(st); if (list_size < 0x1c || list_size > 1024 * 1024) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "SignatureListSize invalid: 0x%x", list_size); return FALSE; } header_size = fu_struct_efi_signature_list_get_header_size(st); if (header_size > 1024 * 1024) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "SignatureHeaderSize invalid: 0x%x", header_size); return FALSE; } size = fu_struct_efi_signature_list_get_size(st); if (size < sizeof(fwupd_guid_t) || size > 1024 * 1024) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "SignatureSize invalid: 0x%x", size); return FALSE; } /* header is typically unused */ offset_tmp = *offset + 0x1c + header_size; for (guint i = 0; i < (list_size - 0x1c) / size; i++) { if (!fu_efi_signature_list_parse_item(self, sig_kind, buf, bufsz, offset_tmp, size, error)) return FALSE; offset_tmp += size; } *offset += list_size; return TRUE; } static gchar * fu_efi_signature_list_get_version(FuEfiSignatureList *self) { guint csum_cnt = 0; const gchar *valid_owners[] = {FU_EFI_SIGNATURE_GUID_MICROSOFT, NULL}; g_autoptr(GPtrArray) sigs = fu_firmware_get_images(FU_FIRMWARE(self)); for (guint i = 0; i < sigs->len; i++) { FuEfiSignature *sig = g_ptr_array_index(sigs, i); if (fu_efi_signature_get_kind(sig) != FU_EFI_SIGNATURE_KIND_SHA256) { g_debug("ignoring dbx certificate in position %u", i); continue; } if (!g_strv_contains(valid_owners, fu_efi_signature_get_owner(sig))) { g_debug("ignoring non-Microsoft dbx hash: %s", fu_efi_signature_get_owner(sig)); continue; } csum_cnt++; } return g_strdup_printf("%u", csum_cnt); } static gboolean fu_efi_signature_list_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { fwupd_guid_t guid = {0x0}; g_autofree gchar *sig_type = NULL; /* read EFI_SIGNATURE_LIST */ if (!fu_memcpy_safe((guint8 *)&guid, sizeof(guid), 0x0, /* dst */ g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset, /* src */ sizeof(guid), error)) { g_prefix_error(error, "failed to read magic: "); return FALSE; } sig_type = fwupd_guid_to_string(&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN); if (g_strcmp0(sig_type, "c1c41626-504c-4092-aca9-41f936934328") != 0 && g_strcmp0(sig_type, "a5c059a1-94e4-4aa7-87b5-ab155c2bf072") != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid magic for file"); return FALSE; } /* success */ return TRUE; } static gboolean fu_efi_signature_list_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuEfiSignatureList *self = FU_EFI_SIGNATURE_LIST(firmware); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autofree gchar *version_str = NULL; /* parse each EFI_SIGNATURE_LIST */ while (offset < bufsz) { if (!fu_efi_signature_list_parse_list(self, buf, bufsz, &offset, error)) return FALSE; } /* set version */ version_str = fu_efi_signature_list_get_version(self); if (version_str != NULL) fu_firmware_set_version(firmware, version_str); /* success */ return TRUE; } static GByteArray * fu_efi_signature_list_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = fu_struct_efi_signature_list_new(); /* entry */ fu_struct_efi_signature_list_set_list_size(buf, buf->len + 16 + 32); fu_struct_efi_signature_list_set_header_size(buf, 0); fu_struct_efi_signature_list_set_size(buf, 16 + 32); /* SignatureOwner + SignatureData */ for (guint i = 0; i < 16; i++) fu_byte_array_append_uint8(buf, '1'); for (guint i = 0; i < 16; i++) fu_byte_array_append_uint8(buf, '2'); return g_steal_pointer(&buf); } /** * fu_efi_signature_list_new: * * Creates a new #FuFirmware that can parse an EFI_SIGNATURE_LIST * * Since: 1.5.5 **/ FuFirmware * fu_efi_signature_list_new(void) { return g_object_new(FU_TYPE_EFI_SIGNATURE_LIST, NULL); } static void fu_efi_signature_list_class_init(FuEfiSignatureListClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_efi_signature_list_check_magic; klass_firmware->parse = fu_efi_signature_list_parse; klass_firmware->write = fu_efi_signature_list_write; } static void fu_efi_signature_list_init(FuEfiSignatureList *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_ALWAYS_SEARCH); fu_firmware_set_images_max(FU_FIRMWARE(self), 2000); } fwupd-1.9.16/libfwupdplugin/fu-efi-signature-list.h000066400000000000000000000005551460375044200222760ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_EFI_SIGNATURE_LIST (fu_efi_signature_list_get_type()) G_DECLARE_FINAL_TYPE(FuEfiSignatureList, fu_efi_signature_list, FU, EFI_SIGNATURE_LIST, FuFirmware) FuFirmware * fu_efi_signature_list_new(void); fwupd-1.9.16/libfwupdplugin/fu-efi-signature-private.h000066400000000000000000000003551460375044200227730ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-efi-signature.h" FuEfiSignature * fu_efi_signature_new(FuEfiSignatureKind kind, const gchar *owner); fwupd-1.9.16/libfwupdplugin/fu-efi-signature.c000066400000000000000000000055571460375044200213270ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-efi-signature-private.h" /** * FuEfiSignature: * * A UEFI Signature as found in an `EFI_SIGNATURE_LIST`. * * See also: [class@FuFirmware] */ struct _FuEfiSignature { FuFirmware parent_instance; FuEfiSignatureKind kind; gchar *owner; }; G_DEFINE_TYPE(FuEfiSignature, fu_efi_signature, FU_TYPE_FIRMWARE) /** * fu_efi_signature_new: (skip): * @kind: A #FuEfiSignatureKind * @owner: A GUID, e.g. %FU_EFI_SIGNATURE_GUID_MICROSOFT * * Creates a new EFI_SIGNATURE. * * Returns: (transfer full): signature * * Since: 1.5.5 **/ FuEfiSignature * fu_efi_signature_new(FuEfiSignatureKind kind, const gchar *owner) { g_autoptr(FuEfiSignature) self = g_object_new(FU_TYPE_EFI_SIGNATURE, NULL); self->kind = kind; self->owner = g_strdup(owner); return g_steal_pointer(&self); } /** * fu_efi_signature_get_kind: * @self: A #FuEfiSignature * * Returns the signature kind. * * Returns: #FuEfiSignatureKind, e.g. %FU_EFI_SIGNATURE_KIND_SHA256 * * Since: 1.5.5 **/ FuEfiSignatureKind fu_efi_signature_get_kind(FuEfiSignature *self) { g_return_val_if_fail(FU_IS_EFI_SIGNATURE(self), FU_EFI_SIGNATURE_KIND_UNKNOWN); return self->kind; } /** * fu_efi_signature_get_owner: * @self: A #FuEfiSignature * * Returns the GUID of the signature owner. * * Returns: GUID owner, perhaps %FU_EFI_SIGNATURE_GUID_MICROSOFT * * Since: 1.5.5 **/ const gchar * fu_efi_signature_get_owner(FuEfiSignature *self) { g_return_val_if_fail(FU_IS_EFI_SIGNATURE(self), NULL); return self->owner; } static gchar * fu_efi_signature_get_checksum(FuFirmware *firmware, GChecksumType csum_kind, GError **error) { FuEfiSignature *self = FU_EFI_SIGNATURE(firmware); g_autoptr(GBytes) data = fu_firmware_get_bytes_with_patches(firmware, error); if (data == NULL) return NULL; /* special case: this is *literally* a hash */ if (self->kind == FU_EFI_SIGNATURE_KIND_SHA256 && csum_kind == G_CHECKSUM_SHA256) { GString *str; const guint8 *buf; gsize bufsz = 0; buf = g_bytes_get_data(data, &bufsz); str = g_string_new(NULL); for (gsize i = 0; i < bufsz; i++) g_string_append_printf(str, "%02x", buf[i]); return g_string_free(str, FALSE); } /* fallback */ return g_compute_checksum_for_bytes(csum_kind, data); } static void fu_efi_signature_finalize(GObject *obj) { FuEfiSignature *self = FU_EFI_SIGNATURE(obj); g_free(self->owner); G_OBJECT_CLASS(fu_efi_signature_parent_class)->finalize(obj); } static void fu_efi_signature_class_init(FuEfiSignatureClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_efi_signature_finalize; firmware_class->get_checksum = fu_efi_signature_get_checksum; } static void fu_efi_signature_init(FuEfiSignature *self) { } fwupd-1.9.16/libfwupdplugin/fu-efi-signature.h000066400000000000000000000014751460375044200213270ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-efi-struct.h" #include "fu-firmware.h" #define FU_TYPE_EFI_SIGNATURE (fu_efi_signature_get_type()) G_DECLARE_FINAL_TYPE(FuEfiSignature, fu_efi_signature, FU, EFI_SIGNATURE, FuFirmware) #define FU_EFI_SIGNATURE_GUID_ZERO "00000000-0000-0000-0000-000000000000" #define FU_EFI_SIGNATURE_GUID_MICROSOFT "77fa9abd-0359-4d32-bd60-28f4e78f784b" #define FU_EFI_SIGNATURE_GUID_OVMF "a0baa8a3-041d-48a8-bc87-c36d121b5e3d" #define FU_EFI_SIGNATURE_GUID_OVMF_LEGACY "d5c1df0b-1bac-4edf-ba48-08834009ca5a" FuEfiSignatureKind fu_efi_signature_get_kind(FuEfiSignature *self) G_GNUC_NON_NULL(1); const gchar * fu_efi_signature_get_owner(FuEfiSignature *self) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-efi.rs000066400000000000000000000071041460375044200175200ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum EfiSignatureKind { Unknown, Sha256, X509, } #[repr(u8)] enum EfiFileAttrib { None = 0x00, LargeFile = 0x01, DataAlignment2 = 0x02, Fixed = 0x04, DataAlignment = 0x38, Checksum = 0x40, } #[repr(u8)] #[derive(ToString)] enum EfiFileType { All = 0x00, Raw = 0x01, Freeform = 0x02, SecurityCore = 0x03, PeiCore = 0x04, DxeCore = 0x05, Peim = 0x06, Driver = 0x07, CombinedPeimDriver = 0x08, Application = 0x09, Mm = 0x0A, FirmwareVolumeImage = 0x0B, CombinedMmDxe = 0x0C, MmCore = 0x0D, MmStandalone = 0x0E, MmCoreStandalone = 0x0F, FfsPad = 0xF0, } #[derive(New, Validate, ParseBytes)] struct EfiFile { name: Guid, hdr_checksum: u8, data_checksum: u8, type: EfiFileType, attrs: u8, size: u24le, state: u8 == 0xF8, } #[repr(u8)] #[derive(ToString)] enum EfiSectionType { Compression = 0x01, GuidDefined = 0x02, Disposable = 0x03, Pe32 = 0x10, Pic = 0x11, Te = 0x12, DxeDepex = 0x13, Version = 0x14, UserInterface = 0x15, Compatibility16 = 0x16, VolumeImage = 0x17, FreeformSubtypeGuid = 0x18, Raw = 0x19, PeiDepex = 0x1B, MmDepex = 0x1C, } #[derive(New, Validate, ParseBytes)] struct EfiSection { size: u24le, type: EfiSectionType, } #[derive(New, Validate, ParseBytes)] struct EfiSectionGuidDefined { name: Guid, offset: u16le, attr: u16le, } #[derive(New, ValidateBytes, ParseBytes)] struct EfiVolume { zero_vector: Guid, guid: Guid, length: u64le, signature: u32le == 0x4856465F, attrs: u32le, hdr_len: u16le, checksum: u16le, ext_hdr: u16le, reserved: u8, revision: u8 == 0x02, } #[derive(New, Validate, ParseBytes)] struct EfiVolumeBlockMap { num_blocks: u32le, length: u32le, } #[derive(New, Validate, Parse)] struct EfiSignatureList { type: Guid, list_size: u32le, header_size: u32le, size: u32le, } #[repr(u32le)] enum EfiLoadOptionAttrs { Active = 0x1, ForceReconnect = 0x2, Hidden = 0x8, Category = 0x1F00, CategoryBoot = 0x0, CategoryAp = 0x100, } #[derive(ParseBytes, New)] struct EfiLoadOption { attrs: EfiLoadOptionAttrs, dp_size: u16le, } #[repr(u8)] enum EfiDevicePathType { Hardware = 0x01, Acpi, Message, Media, BiosBoot, End = 0x7F, } #[derive(ParseBytes, New)] struct EfiDevicePath { type: EfiDevicePathType, subtype: u8 = 0xFF, length: u16le = $struct_size, } #[repr(u8)] enum EfiHardDriveDevicePathSubtype { HardDrive = 0x01, Cdrom = 0x02, Vendor = 0x03, FilePath = 0x04, MediaProtocol = 0x05, PiwgFirmwareFile = 0x06, PiwgFirmwareVolume = 0x07, RelativeOffsetRange = 0x08, RamDiskDevicePath = 0x09, } #[repr(u8)] #[derive(ToString, FromString)] enum EfiHardDriveDevicePathPartitionFormat { LegacyMbr = 0x01, GuidPartitionTable = 0x02, } #[repr(u8)] #[derive(ToString, FromString)] enum EfiHardDriveDevicePathSignatureType { None, Addr1b8, Guid, } #[derive(ParseBytes, New)] struct EfiHardDriveDevicePath { type: EfiDevicePathType == Media, subtype: EfiHardDriveDevicePathSubtype = HardDrive, length: u16le == $struct_size, partition_number: u32le, partition_start: u64le, partition_size: u64le, partition_signature: Guid, partition_format: EfiHardDriveDevicePathPartitionFormat = GuidPartitionTable, signature_type: EfiHardDriveDevicePathSignatureType = Guid, } fwupd-1.9.16/libfwupdplugin/fu-efivar-darwin.c000066400000000000000000000043371460375044200213160ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fwupd-error.h" #include "fu-efivar-impl.h" gboolean fu_efivar_supported_impl(GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "efivarfs not currently supported on darwin"); return FALSE; } gboolean fu_efivar_delete_impl(const gchar *guid, const gchar *name, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "efivarfs not currently supported on darwin"); return FALSE; } gboolean fu_efivar_delete_with_glob_impl(const gchar *guid, const gchar *name_glob, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "efivarfs not currently supported on darwin"); return FALSE; } gboolean fu_efivar_exists_impl(const gchar *guid, const gchar *name) { return FALSE; } gboolean fu_efivar_get_data_impl(const gchar *guid, const gchar *name, guint8 **data, gsize *data_sz, guint32 *attr, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "efivarfs not currently supported on darwin"); return FALSE; } GPtrArray * fu_efivar_get_names_impl(const gchar *guid, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "efivarfs not currently supported on darwin"); return NULL; } GFileMonitor * fu_efivar_get_monitor_impl(const gchar *guid, const gchar *name, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "efivarfs not currently supported on darwin"); return NULL; } guint64 fu_efivar_space_used_impl(GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "efivarfs not currently supported on darwin"); return G_MAXUINT64; } gboolean fu_efivar_set_data_impl(const gchar *guid, const gchar *name, const guint8 *data, gsize sz, guint32 attr, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "efivarfs not currently supported on darwin"); return FALSE; } fwupd-1.9.16/libfwupdplugin/fu-efivar-freebsd.c000066400000000000000000000077141460375044200214460ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2021 Norbert Kamiński * Copyright (C) 2021 Michał Kopeć * Copyright (C) 2021 Sergii Dmytruk * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fwupd-error.h" #include "fu-common.h" #include "fu-efivar-impl.h" gboolean fu_efivar_supported_impl(GError **error) { if (efi_variables_supported() == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "kernel efivars support missing"); return FALSE; } return TRUE; } gboolean fu_efivar_delete_impl(const gchar *guid, const gchar *name, GError **error) { efi_guid_t guidt; efi_str_to_guid(guid, &guidt); if (efi_del_variable(guidt, name) == 0) return TRUE; g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to delete efivar %s", name); return FALSE; } gboolean fu_efivar_delete_with_glob_impl(const gchar *guid, const gchar *name_glob, GError **error) { efi_guid_t *guidt = NULL; gchar *name = NULL; gboolean rv = FALSE; efi_guid_t guid_to_delete; efi_str_to_guid(guid, &guid_to_delete); while (efi_get_next_variable_name(&guidt, &name)) { if (memcmp(&guid_to_delete, guidt, sizeof(guid_to_delete)) != 0) continue; if (!g_pattern_match_simple(name, name_glob)) continue; rv = fu_efivar_delete(guid, name, error); if (!rv) break; } return rv; } static gboolean fu_efivar_exists_guid(const gchar *guid) { efi_guid_t *guidt = NULL; gchar *name = NULL; efi_guid_t test; efi_str_to_guid(guid, &test); while (efi_get_next_variable_name(&guidt, &name)) { if (memcmp(&test, guidt, sizeof(test)) == 0) { return TRUE; } } return FALSE; } gboolean fu_efivar_exists_impl(const gchar *guid, const gchar *name) { /* any name */ if (name == NULL) return fu_efivar_exists_guid(guid); return fu_efivar_get_data(guid, name, NULL, NULL, NULL, NULL); } gboolean fu_efivar_get_data_impl(const gchar *guid, const gchar *name, guint8 **data, gsize *data_sz, guint32 *attr, GError **error) { efi_guid_t guidt; efi_str_to_guid(guid, &guidt); return (efi_get_variable(guidt, name, data, data_sz, attr) != 0); } GPtrArray * fu_efivar_get_names_impl(const gchar *guid, GError **error) { g_autoptr(GPtrArray) names = g_ptr_array_new_with_free_func(g_free); efi_guid_t *guidt = NULL; gchar *name = NULL; efi_guid_t test; efi_str_to_guid(guid, &test); /* find names with matching GUID */ while (efi_get_next_variable_name(&guidt, &name)) { if (memcmp(&test, guidt, sizeof(test)) == 0) { g_ptr_array_add(names, g_strdup(name)); } } /* nothing found */ if (names->len == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no names for GUID %s", guid); return NULL; } /* success */ return g_steal_pointer(&names); } GFileMonitor * fu_efivar_get_monitor_impl(const gchar *guid, const gchar *name, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "efivarfs monitoring not supported on FreeBSD"); return NULL; } guint64 fu_efivar_space_used_impl(GError **error) { guint64 total = 0; efi_guid_t *guidt = NULL; char *name = NULL; while (efi_get_next_variable_name(&guidt, &name)) { size_t size = 0; if (efi_get_variable_size(*guidt, name, &size) < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to get efivar size"); return G_MAXUINT64; } total += size; } /* success */ return total; } gboolean fu_efivar_set_data_impl(const gchar *guid, const gchar *name, const guint8 *data, gsize sz, guint32 attr, GError **error) { efi_guid_t guidt; efi_str_to_guid(guid, &guidt); if (efi_set_variable(guidt, name, (guint8 *)data, sz, attr) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write data to efivar %s", name); return FALSE; } /* success */ return TRUE; } fwupd-1.9.16/libfwupdplugin/fu-efivar-impl.c000066400000000000000000000035171460375044200207720ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fwupd-error.h" #include "fu-efivar-impl.h" gboolean fu_efivar_supported_impl(GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return FALSE; } gboolean fu_efivar_delete_impl(const gchar *guid, const gchar *name, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return FALSE; } gboolean fu_efivar_delete_with_glob_impl(const gchar *guid, const gchar *name_glob, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return FALSE; } gboolean fu_efivar_exists_impl(const gchar *guid, const gchar *name) { return FALSE; } gboolean fu_efivar_get_data_impl(const gchar *guid, const gchar *name, guint8 **data, gsize *data_sz, guint32 *attr, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return FALSE; } GPtrArray * fu_efivar_get_names_impl(const gchar *guid, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return NULL; } GFileMonitor * fu_efivar_get_monitor_impl(const gchar *guid, const gchar *name, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return NULL; } guint64 fu_efivar_space_used_impl(GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return G_MAXUINT64; } gboolean fu_efivar_set_data_impl(const gchar *guid, const gchar *name, const guint8 *data, gsize sz, guint32 attr, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported"); return FALSE; } fwupd-1.9.16/libfwupdplugin/fu-efivar-impl.h000066400000000000000000000022351460375044200207730ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-efivar.h" gboolean fu_efivar_supported_impl(GError **error); guint64 fu_efivar_space_used_impl(GError **error); gboolean fu_efivar_exists_impl(const gchar *guid, const gchar *name) G_GNUC_NON_NULL(1); GFileMonitor * fu_efivar_get_monitor_impl(const gchar *guid, const gchar *name, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_efivar_get_data_impl(const gchar *guid, const gchar *name, guint8 **data, gsize *data_sz, guint32 *attr, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_efivar_set_data_impl(const gchar *guid, const gchar *name, const guint8 *data, gsize sz, guint32 attr, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_efivar_delete_impl(const gchar *guid, const gchar *name, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_efivar_delete_with_glob_impl(const gchar *guid, const gchar *name_glob, GError **error) G_GNUC_NON_NULL(1, 2); GPtrArray * fu_efivar_get_names_impl(const gchar *guid, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-efivar-linux.c000066400000000000000000000257641460375044200212000ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include #include "fwupd-error.h" #include "fu-common.h" #include "fu-efivar-impl.h" #include "fu-path.h" static gchar * fu_efivar_get_path(void) { g_autofree gchar *sysfsfwdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_FW); return g_build_filename(sysfsfwdir, "efi", "efivars", NULL); } static gchar * fu_efivar_get_filename(const gchar *guid, const gchar *name) { g_autofree gchar *efivardir = fu_efivar_get_path(); return g_strdup_printf("%s/%s-%s", efivardir, name, guid); } gboolean fu_efivar_supported_impl(GError **error) { g_autofree gchar *efivardir = fu_efivar_get_path(); if (!g_file_test(efivardir, G_FILE_TEST_IS_DIR)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "kernel efivars support missing: %s", efivardir); return FALSE; } return TRUE; } static gboolean fu_efivar_set_immutable_fd(int fd, gboolean value, gboolean *value_old, GError **error) { guint flags; gboolean is_immutable; int rc; /* get existing status */ rc = ioctl(fd, FS_IOC_GETFLAGS, &flags); if (rc < 0) { /* check for tmpfs */ if (errno == ENOTTY || errno == ENOSYS) { is_immutable = FALSE; } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to get flags: %s", g_strerror(errno)); return FALSE; } } else { is_immutable = (flags & FS_IMMUTABLE_FL) > 0; } /* save the old value */ if (value_old != NULL) *value_old = is_immutable; /* is this already correct */ if (value) { if (is_immutable) return TRUE; flags |= FS_IMMUTABLE_FL; } else { if (!is_immutable) return TRUE; flags &= ~FS_IMMUTABLE_FL; } /* set the new status */ rc = ioctl(fd, FS_IOC_SETFLAGS, &flags); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to set flags: %s", g_strerror(errno)); return FALSE; } return TRUE; } static gboolean fu_efivar_set_immutable(const gchar *fn, gboolean value, gboolean *value_old, GError **error) { gint fd; g_autoptr(GInputStream) istr = NULL; /* open file readonly */ fd = open(fn, O_RDONLY); if (fd < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, "failed to open: %s", g_strerror(errno)); return FALSE; } istr = g_unix_input_stream_new(fd, TRUE); if (istr == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to create stream"); return FALSE; } return fu_efivar_set_immutable_fd(fd, value, value_old, error); } gboolean fu_efivar_delete_impl(const gchar *guid, const gchar *name, GError **error) { g_autofree gchar *fn = NULL; g_autoptr(GFile) file = NULL; fn = fu_efivar_get_filename(guid, name); file = g_file_new_for_path(fn); if (!g_file_query_exists(file, NULL)) return TRUE; if (!fu_efivar_set_immutable(fn, FALSE, NULL, error)) { g_prefix_error(error, "failed to set %s as mutable: ", fn); return FALSE; } return g_file_delete(file, NULL, error); } gboolean fu_efivar_delete_with_glob_impl(const gchar *guid, const gchar *name_glob, GError **error) { const gchar *fn; g_autofree gchar *nameguid_glob = NULL; g_autofree gchar *efivardir = fu_efivar_get_path(); g_autoptr(GDir) dir = NULL; dir = g_dir_open(efivardir, 0, error); if (dir == NULL) return FALSE; nameguid_glob = g_strdup_printf("%s-%s", name_glob, guid); while ((fn = g_dir_read_name(dir)) != NULL) { if (g_pattern_match_simple(nameguid_glob, fn)) { g_autofree gchar *keyfn = g_build_filename(efivardir, fn, NULL); g_autoptr(GFile) file = g_file_new_for_path(keyfn); if (!fu_efivar_set_immutable(keyfn, FALSE, NULL, error)) { g_prefix_error(error, "failed to set %s as mutable: ", keyfn); return FALSE; } if (!g_file_delete(file, NULL, error)) return FALSE; } } return TRUE; } static gboolean fu_efivar_exists_guid(const gchar *guid) { const gchar *fn; g_autofree gchar *efivardir = fu_efivar_get_path(); g_autoptr(GDir) dir = NULL; dir = g_dir_open(efivardir, 0, NULL); if (dir == NULL) return FALSE; while ((fn = g_dir_read_name(dir)) != NULL) { if (g_str_has_suffix(fn, guid)) return TRUE; } return TRUE; } gboolean fu_efivar_exists_impl(const gchar *guid, const gchar *name) { g_autofree gchar *fn = NULL; /* any name */ if (name == NULL) return fu_efivar_exists_guid(guid); fn = fu_efivar_get_filename(guid, name); return g_file_test(fn, G_FILE_TEST_EXISTS); } gboolean fu_efivar_get_data_impl(const gchar *guid, const gchar *name, guint8 **data, gsize *data_sz, guint32 *attr, GError **error) { gssize attr_sz; gssize data_sz_tmp; guint32 attr_tmp; guint64 sz; g_autofree gchar *fn = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GFileInfo) info = NULL; g_autoptr(GInputStream) istr = NULL; /* open file as stream */ fn = fu_efivar_get_filename(guid, name); file = g_file_new_for_path(fn); istr = G_INPUT_STREAM(g_file_read(file, NULL, error)); if (istr == NULL) return FALSE; info = g_file_input_stream_query_info(G_FILE_INPUT_STREAM(istr), G_FILE_ATTRIBUTE_STANDARD_SIZE, NULL, error); if (info == NULL) { g_prefix_error(error, "failed to get stream info: "); return FALSE; } /* get total stream size */ sz = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE); if (sz < 4) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "efivars file too small: %" G_GUINT64_FORMAT, sz); return FALSE; } /* read out the attributes */ attr_sz = g_input_stream_read(istr, &attr_tmp, sizeof(attr_tmp), NULL, error); if (attr_sz == -1) { g_prefix_error(error, "failed to read attr: "); return FALSE; } if (attr != NULL) *attr = attr_tmp; /* read out the data */ data_sz_tmp = sz - sizeof(attr_tmp); if (data_sz_tmp == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no data to read"); return FALSE; } if (data_sz != NULL) *data_sz = data_sz_tmp; if (data != NULL) { g_autofree guint8 *data_tmp = g_malloc0(data_sz_tmp); if (!g_input_stream_read_all(istr, data_tmp, data_sz_tmp, NULL, NULL, error)) { g_prefix_error(error, "failed to read data: "); return FALSE; } *data = g_steal_pointer(&data_tmp); } return TRUE; } GPtrArray * fu_efivar_get_names_impl(const gchar *guid, GError **error) { const gchar *name_guid; g_autofree gchar *path = fu_efivar_get_path(); g_autoptr(GDir) dir = NULL; g_autoptr(GPtrArray) names = g_ptr_array_new_with_free_func(g_free); /* find names with matching GUID */ dir = g_dir_open(path, 0, error); if (dir == NULL) return NULL; while ((name_guid = g_dir_read_name(dir)) != NULL) { gsize name_guidsz = strlen(name_guid); if (name_guidsz < 38) continue; if (g_strcmp0(name_guid + name_guidsz - 36, guid) == 0) { g_ptr_array_add(names, g_strndup(name_guid, name_guidsz - 37)); } } /* nothing found */ if (names->len == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no names for GUID %s", guid); return NULL; } /* success */ return g_steal_pointer(&names); } GFileMonitor * fu_efivar_get_monitor_impl(const gchar *guid, const gchar *name, GError **error) { g_autofree gchar *fn = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GFileMonitor) monitor = NULL; fn = fu_efivar_get_filename(guid, name); file = g_file_new_for_path(fn); monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, error); if (monitor == NULL) return NULL; g_file_monitor_set_rate_limit(monitor, 5000); return g_steal_pointer(&monitor); } guint64 fu_efivar_space_used_impl(GError **error) { const gchar *fn; guint64 total = 0; g_autoptr(GDir) dir = NULL; g_autofree gchar *path = fu_efivar_get_path(); g_autoptr(GFile) file_fs = g_file_new_for_path(path); g_autoptr(GFileInfo) info_fs = NULL; g_autoptr(GError) error_local = NULL; /* this is only supported in new kernels */ info_fs = g_file_query_info(file_fs, G_FILE_ATTRIBUTE_FILESYSTEM_USED, G_FILE_QUERY_INFO_NONE, NULL, &error_local); if (info_fs == NULL) { g_debug("failed to get efivar used space: %s", error_local->message); } else { total = g_file_info_get_attribute_uint64(info_fs, G_FILE_ATTRIBUTE_FILESYSTEM_USED); if (total > 0) return total; } /* stat each file */ dir = g_dir_open(path, 0, error); if (dir == NULL) return G_MAXUINT64; while ((fn = g_dir_read_name(dir)) != NULL) { guint64 sz; g_autofree gchar *pathfn = g_build_filename(path, fn, NULL); g_autoptr(GFile) file = g_file_new_for_path(pathfn); g_autoptr(GFileInfo) info = NULL; info = g_file_query_info(file, G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE "," G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE, NULL, error); if (info == NULL) return G_MAXUINT64; sz = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_ALLOCATED_SIZE); if (sz == 0) sz = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_STANDARD_SIZE); total += sz; } /* success */ return total; } gboolean fu_efivar_set_data_impl(const gchar *guid, const gchar *name, const guint8 *data, gsize sz, guint32 attr, GError **error) { int fd; int open_wflags; gboolean was_immutable; g_autofree gchar *fn = fu_efivar_get_filename(guid, name); g_autofree guint8 *buf = g_malloc0(sizeof(guint32) + sz); g_autoptr(GFile) file = g_file_new_for_path(fn); g_autoptr(GOutputStream) ostr = NULL; /* create empty file so we can clear the immutable bit before writing */ if (!g_file_query_exists(file, NULL)) { g_autoptr(GFileOutputStream) ostr_tmp = NULL; ostr_tmp = g_file_create(file, G_FILE_CREATE_NONE, NULL, error); if (ostr_tmp == NULL) return FALSE; if (!g_output_stream_close(G_OUTPUT_STREAM(ostr_tmp), NULL, error)) { g_prefix_error(error, "failed to touch efivarfs: "); return FALSE; } } if (!fu_efivar_set_immutable(fn, FALSE, &was_immutable, error)) { g_prefix_error(error, "failed to set %s as mutable: ", fn); return FALSE; } /* open file for writing, optionally append */ open_wflags = O_WRONLY; if (attr & FU_EFIVAR_ATTR_APPEND_WRITE) open_wflags |= O_APPEND; fd = open(fn, open_wflags); if (fd < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to open %s: %s", fn, g_strerror(errno)); return FALSE; } ostr = g_unix_output_stream_new(fd, TRUE); memcpy(buf, &attr, sizeof(attr)); memcpy(buf + sizeof(attr), data, sz); if (g_output_stream_write(ostr, buf, sizeof(attr) + sz, NULL, error) < 0) { g_prefix_error(error, "failed to write data to efivarfs: "); return FALSE; } /* set as immutable again */ if (was_immutable && !fu_efivar_set_immutable(fn, TRUE, NULL, error)) { g_prefix_error(error, "failed to set %s as immutable: ", fn); return FALSE; } /* success */ return TRUE; } fwupd-1.9.16/libfwupdplugin/fu-efivar-windows.c000066400000000000000000000211701460375044200215160ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fwupd-error.h" #include "fu-byte-array.h" #include "fu-efivar-impl.h" gboolean fu_efivar_supported_impl(GError **error) { FIRMWARE_TYPE firmware_type = {0}; DWORD rc; /* sanity check */ if (!GetFirmwareType(&firmware_type)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot get firmware type [%u]", (guint)GetLastError()); return FALSE; } if (firmware_type != FirmwareTypeUefi) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "only supported on UEFI firmware"); return FALSE; } /* check supported */ rc = GetFirmwareEnvironmentVariableA("", "{00000000-0000-0000-0000-000000000000}", NULL, 0); if (rc == 0 && GetLastError() == ERROR_INVALID_FUNCTION) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "getting EFI variables is not supported on this system"); return FALSE; } /* success */ return TRUE; } gboolean fu_efivar_delete_impl(const gchar *guid, const gchar *name, GError **error) { /* size of 0 bytes -> delete */ return fu_efivar_set_data_impl(guid, name, NULL, 0, 0, error); } gboolean fu_efivar_delete_with_glob_impl(const gchar *guid, const gchar *name_glob, GError **error) { g_autoptr(GPtrArray) names = NULL; g_autoptr(GError) error_local = NULL; names = fu_efivar_get_names_impl(guid, &error_local); if (names == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) return TRUE; g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } for (guint i = 0; i < names->len; i++) { const gchar *name = g_ptr_array_index(names, i); if (g_pattern_match_simple(name_glob, name)) { if (!fu_efivar_delete_impl(guid, name, error)) return FALSE; } } return TRUE; } gboolean fu_efivar_exists_impl(const gchar *guid, const gchar *name) { return fu_efivar_get_data_impl(guid, name, NULL, NULL, NULL, NULL); } static gboolean fu_efivar_is_running_under_wine(void) { HKEY hKey = NULL; LONG lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Wine", 0, KEY_READ, &hKey); if (lResult == ERROR_SUCCESS) { RegCloseKey(hKey); return TRUE; } return FALSE; } gboolean fu_efivar_get_data_impl(const gchar *guid, const gchar *name, guint8 **data, gsize *data_sz, guint32 *attr, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autofree gchar *guid_win32 = g_strdup_printf("{%s}", guid); fu_byte_array_set_size(buf, 0x1000, 0xFF); /* unimplemented function KERNEL32.dll.GetFirmwareEnvironmentVariableExA on wine */ if (fu_efivar_is_running_under_wine()) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "GetFirmwareEnvironmentVariableExA is not implemented"); return FALSE; } do { DWORD dwAttribubutes = 0; DWORD rc = GetFirmwareEnvironmentVariableExA(name, guid_win32, buf->data, buf->len, &dwAttribubutes); if (rc > 0) { if (data != NULL) *data = g_byte_array_free(g_steal_pointer(&buf), FALSE); if (data_sz != NULL) *data_sz = rc; if (attr != NULL) *attr = dwAttribubutes; return TRUE; } if (rc == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) break; fu_byte_array_set_size(buf, buf->len * 2, 0xFF); } while (buf->len < 0x400000); /* failed */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to get get variable [%u]", (guint)GetLastError()); return FALSE; } /* there is no win32 kernel interface for GetNextVariable so use from UEFI spec v2.8 */ GPtrArray * fu_efivar_get_names_impl(const gchar *guid, GError **error) { g_autoptr(GPtrArray) names = g_ptr_array_new_with_free_func(g_free); struct { const gchar *guid; const gchar *name; } variable_names[] = {{FU_EFIVAR_GUID_EFI_GLOBAL, "AuditMode"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "BootCurrent"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "BootNext"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "BootOptionSupport"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "BootOrder"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "BootOrderDefault"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "BootXXXX"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "ConIn"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "ConInDev"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "ConOut"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "ConOutDev"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "CurrentPolicy"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "dbDefault"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "dbrDefault"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "dbtDefault"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "dbxDefault"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "DeployedMode"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "DriverOrder"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "DriverXXXX"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "ErrOut"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "ErrOutDev"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "HwErrRecSupprot"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "KEK"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "KEKDefault"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "KeyXXXX"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "Lang"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "LangCodes"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "OsIndications"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "OsIndicationsSupported"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "OsRecoveryOrder"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "PK"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "PKDefault"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "PlatformLang"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "PlatformLangCodes"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "PlatformRecoveryXXXX"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "RuntimeServicesSupported"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "SecureBoot"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "SetupMode"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "SignatureSupport"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "SysPrepOrder"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "SysPrepXXXX"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "Timeout"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "VendorKeys"}, {FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_DEBUG_LOG"}, {FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_VERBOSE"}, {FU_EFIVAR_GUID_FWUPDATE, "fwupd-ux-capsule"}, {FU_EFIVAR_GUID_SECURITY_DATABASE, "db"}, {FU_EFIVAR_GUID_SECURITY_DATABASE, "dbx"}, {NULL, NULL}}; /* look for each possible guid+name */ for (guint i = 0; variable_names[i].guid != NULL; i++) { if (g_strcmp0(FU_EFIVAR_GUID_EFI_GLOBAL, variable_names[i].guid) != 0) continue; if (g_str_has_suffix(variable_names[i].name, "XXXX")) { g_autoptr(GString) name_root = g_string_new(variable_names[i].name); g_string_truncate(name_root, name_root->len - 4); for (guint j = 0; j < G_MAXUINT16; j++) { g_autofree gchar *name = g_strdup_printf("%s%04X", name_root->str, j); if (fu_efivar_exists_impl(variable_names[i].guid, name)) g_ptr_array_add(names, g_steal_pointer(&name)); } } else { if (fu_efivar_exists_impl(variable_names[i].guid, variable_names[i].name)) g_ptr_array_add(names, g_strdup(variable_names[i].name)); } } /* nothing found */ if (names->len == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no names for GUID %s", guid); return NULL; } /* success */ return g_steal_pointer(&names); } GFileMonitor * fu_efivar_get_monitor_impl(const gchar *guid, const gchar *name, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "monitoring EFI variables is not supported on Windows"); return NULL; } guint64 fu_efivar_space_used_impl(GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "getting EFI used space is not supported on Windows"); return G_MAXUINT64; } gboolean fu_efivar_set_data_impl(const gchar *guid, const gchar *name, const guint8 *data, gsize sz, guint32 attr, GError **error) { g_autofree gchar *guid_win32 = g_strdup_printf("{%s}", guid); /* unimplemented function KERNEL32.dll.SetFirmwareEnvironmentVariableExA on wine */ if (fu_efivar_is_running_under_wine()) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SetFirmwareEnvironmentVariableExA is not implemented"); return FALSE; } if (!SetFirmwareEnvironmentVariableExA(name, guid_win32, (PVOID)data, sz, attr)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to get set variable [%u]", (guint)GetLastError()); return FALSE; } return TRUE; } fwupd-1.9.16/libfwupdplugin/fu-efivar.c000066400000000000000000000161531460375044200200330ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fwupd-error.h" #include "fu-efivar-impl.h" /** * fu_efivar_supported: * @error: #GError * * Determines if the kernel supports EFI variables * * Returns: %TRUE on success * * Since: 1.4.0 **/ gboolean fu_efivar_supported(GError **error) { return fu_efivar_supported_impl(error); } /** * fu_efivar_delete: * @guid: Globally unique identifier * @name: Variable name * @error: #GError * * Removes a variable from NVRAM * * Returns: %TRUE on success * * Since: 1.4.0 **/ gboolean fu_efivar_delete(const gchar *guid, const gchar *name, GError **error) { g_return_val_if_fail(guid != NULL, FALSE); g_return_val_if_fail(name != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return fu_efivar_delete_impl(guid, name, error); } /** * fu_efivar_delete_with_glob: * @guid: Globally unique identifier * @name_glob: Variable name * @error: #GError * * Removes a group of variables from NVRAM * * Returns: %TRUE on success * * Since: 1.4.0 **/ gboolean fu_efivar_delete_with_glob(const gchar *guid, const gchar *name_glob, GError **error) { g_return_val_if_fail(guid != NULL, FALSE); g_return_val_if_fail(name_glob != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return fu_efivar_delete_with_glob_impl(guid, name_glob, error); } /** * fu_efivar_exists: * @guid: Globally unique identifier * @name: (nullable): Variable name * * Test if a variable exists * * Returns: %TRUE on success * * Since: 1.4.0 **/ gboolean fu_efivar_exists(const gchar *guid, const gchar *name) { g_return_val_if_fail(guid != NULL, FALSE); return fu_efivar_exists_impl(guid, name); } /** * fu_efivar_get_data: * @guid: Globally unique identifier * @name: Variable name * @data: Data to set * @data_sz: size of data * @attr: Attributes * @error: (nullable): optional return location for an error * * Gets the data from a UEFI variable in NVRAM * * Returns: %TRUE on success * * Since: 1.4.0 **/ gboolean fu_efivar_get_data(const gchar *guid, const gchar *name, guint8 **data, gsize *data_sz, guint32 *attr, GError **error) { g_return_val_if_fail(guid != NULL, FALSE); g_return_val_if_fail(name != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return fu_efivar_get_data_impl(guid, name, data, data_sz, attr, error); } /** * fu_efivar_get_data_bytes: * @guid: Globally unique identifier * @name: Variable name * @attr: (nullable): Attributes * @error: (nullable): optional return location for an error * * Gets the data from a UEFI variable in NVRAM * * Returns: (transfer full): a #GBytes, or %NULL * * Since: 1.5.0 **/ GBytes * fu_efivar_get_data_bytes(const gchar *guid, const gchar *name, guint32 *attr, GError **error) { guint8 *data = NULL; gsize datasz = 0; g_return_val_if_fail(guid != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (!fu_efivar_get_data(guid, name, &data, &datasz, attr, error)) return NULL; return g_bytes_new_take(data, datasz); } /** * fu_efivar_get_names: * @guid: Globally unique identifier * @error: (nullable): optional return location for an error * * Gets the list of names where the GUID matches. An error is set if there are * no names matching the GUID. * * Returns: (transfer container) (element-type utf8): array of names * * Since: 1.4.7 **/ GPtrArray * fu_efivar_get_names(const gchar *guid, GError **error) { g_return_val_if_fail(guid != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return fu_efivar_get_names_impl(guid, error); } /** * fu_efivar_get_monitor: * @guid: Globally unique identifier * @name: Variable name * @error: (nullable): optional return location for an error * * Returns a file monitor for a specific key. * * Returns: (transfer full): a #GFileMonitor, or %NULL for an error * * Since: 1.5.5 **/ GFileMonitor * fu_efivar_get_monitor(const gchar *guid, const gchar *name, GError **error) { g_return_val_if_fail(guid != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); return fu_efivar_get_monitor_impl(guid, name, error); } /** * fu_efivar_space_used: * @error: (nullable): optional return location for an error * * Gets the total size used by all EFI variables. This may be less than the size reported by the * kernel as some (hopefully small) variables are hidden from userspace. * * Returns: total allocated size of all visible variables, or %G_MAXUINT64 on error * * Since: 1.5.1 **/ guint64 fu_efivar_space_used(GError **error) { g_return_val_if_fail(error == NULL || *error == NULL, G_MAXUINT64); return fu_efivar_space_used_impl(error); } /** * fu_efivar_set_data: * @guid: Globally unique identifier * @name: Variable name * @data: Data to set * @sz: size of @data * @attr: Attributes * @error: (nullable): optional return location for an error * * Sets the data to a UEFI variable in NVRAM * * Returns: %TRUE on success * * Since: 1.4.0 **/ gboolean fu_efivar_set_data(const gchar *guid, const gchar *name, const guint8 *data, gsize sz, guint32 attr, GError **error) { g_return_val_if_fail(guid != NULL, FALSE); g_return_val_if_fail(name != NULL, FALSE); g_return_val_if_fail(data != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return fu_efivar_set_data_impl(guid, name, data, sz, attr, error); } /** * fu_efivar_set_data_bytes: * @guid: globally unique identifier * @name: variable name * @bytes: data blob * @attr: attributes * @error: (nullable): optional return location for an error * * Sets the data to a UEFI variable in NVRAM * * Returns: %TRUE on success * * Since: 1.5.0 **/ gboolean fu_efivar_set_data_bytes(const gchar *guid, const gchar *name, GBytes *bytes, guint32 attr, GError **error) { gsize bufsz = 0; const guint8 *buf; g_return_val_if_fail(guid != NULL, FALSE); g_return_val_if_fail(name != NULL, FALSE); g_return_val_if_fail(bytes != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); buf = g_bytes_get_data(bytes, &bufsz); return fu_efivar_set_data(guid, name, buf, bufsz, attr, error); } /** * fu_efivar_secure_boot_enabled: * @error: (nullable): optional return location for an error * * Determines if secure boot was enabled * * Returns: %TRUE on success * * Since: 1.8.2 **/ gboolean fu_efivar_secure_boot_enabled(GError **error) { gsize data_size = 0; g_autofree guint8 *data = NULL; g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fu_efivar_get_data(FU_EFIVAR_GUID_EFI_GLOBAL, "SecureBoot", &data, &data_size, NULL, NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "SecureBoot is not available"); return FALSE; } if (data_size >= 1 && data[0] & 1) return TRUE; /* available, but not enabled */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "SecureBoot is not enabled"); return FALSE; } fwupd-1.9.16/libfwupdplugin/fu-efivar.h000066400000000000000000000050541460375044200200360ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_EFIVAR_GUID_EFI_GLOBAL "8be4df61-93ca-11d2-aa0d-00e098032b8c" #define FU_EFIVAR_GUID_FWUPDATE "0abba7dc-e516-4167-bbf5-4d9d1c739416" #define FU_EFIVAR_GUID_UX_CAPSULE "3b8c8162-188c-46a4-aec9-be43f1d65697" #define FU_EFIVAR_GUID_SECURITY_DATABASE "d719b2cb-3d3a-4596-a3bc-dad00e67656f" #define FU_EFIVAR_GUID_UX_CAPSULE "3b8c8162-188c-46a4-aec9-be43f1d65697" #define FU_EFIVAR_GUID_EFI_CAPSULE_REPORT "39b68c46-f7fb-441b-b6ec-16b0f69821f3" #define FU_EFIVAR_GUID_SHIM "605dab50-e046-4300-abb6-3dd810dd8b23" #define FU_EFIVAR_ATTR_NON_VOLATILE (1 << 0) #define FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS (1 << 1) #define FU_EFIVAR_ATTR_RUNTIME_ACCESS (1 << 2) #define FU_EFIVAR_ATTR_HARDWARE_ERROR_RECORD (1 << 3) #define FU_EFIVAR_ATTR_AUTHENTICATED_WRITE_ACCESS (1 << 4) #define FU_EFIVAR_ATTR_TIME_BASED_AUTHENTICATED_WRITE_ACCESS (1 << 5) #define FU_EFIVAR_ATTR_APPEND_WRITE (1 << 6) gboolean fu_efivar_supported(GError **error); guint64 fu_efivar_space_used(GError **error); gboolean fu_efivar_exists(const gchar *guid, const gchar *name) G_GNUC_NON_NULL(1); GFileMonitor * fu_efivar_get_monitor(const gchar *guid, const gchar *name, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_efivar_get_data(const gchar *guid, const gchar *name, guint8 **data, gsize *data_sz, guint32 *attr, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GBytes * fu_efivar_get_data_bytes(const gchar *guid, const gchar *name, guint32 *attr, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean fu_efivar_set_data(const gchar *guid, const gchar *name, const guint8 *data, gsize sz, guint32 attr, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_efivar_set_data_bytes(const gchar *guid, const gchar *name, GBytes *bytes, guint32 attr, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_efivar_delete(const gchar *guid, const gchar *name, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_efivar_delete_with_glob(const gchar *guid, const gchar *name_glob, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fu_efivar_get_names(const gchar *guid, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_efivar_secure_boot_enabled(GError **error); fwupd-1.9.16/libfwupdplugin/fu-elf-firmware.c000066400000000000000000000107531460375044200211370ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-bytes.h" #include "fu-elf-firmware.h" #include "fu-elf-struct.h" #include "fu-string.h" /** * FuElfFirmware: * * Executable and Linkable Format is a common standard file format for executable files, * object code, shared libraries, core dumps -- and sometimes firmware. * * Documented: * https://en.wikipedia.org/wiki/Executable_and_Linkable_Format */ G_DEFINE_TYPE(FuElfFirmware, fu_elf_firmware, FU_TYPE_FIRMWARE) static gboolean fu_elf_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_elf_file_header64le_validate_bytes(fw, offset, error); } static gboolean fu_elf_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { gsize offset_secthdr = offset; gsize offset_proghdr = offset; guint16 phentsize; guint16 phnum; guint16 shnum; g_autoptr(GByteArray) st_fhdr = NULL; g_autoptr(GBytes) shstrndx_blob = NULL; g_autoptr(GPtrArray) sections = g_ptr_array_new_with_free_func((GDestroyNotify)g_byte_array_unref); /* file header */ st_fhdr = fu_struct_elf_file_header64le_parse_bytes(fw, offset, error); if (st_fhdr == NULL) return FALSE; /* parse each program header, unused here */ offset_proghdr += fu_struct_elf_file_header64le_get_phoff(st_fhdr); phentsize = fu_struct_elf_file_header64le_get_phentsize(st_fhdr); phnum = fu_struct_elf_file_header64le_get_phnum(st_fhdr); for (guint i = 0; i < phnum; i++) { g_autoptr(GByteArray) st_phdr = fu_struct_elf_program_header64le_parse_bytes(fw, offset_proghdr, error); if (st_phdr == NULL) return FALSE; offset_proghdr += phentsize; } /* parse all the sections ahead of time */ offset_secthdr += fu_struct_elf_file_header64le_get_shoff(st_fhdr); shnum = fu_struct_elf_file_header64le_get_shnum(st_fhdr); for (guint i = 0; i < shnum; i++) { g_autoptr(GByteArray) st_shdr = fu_struct_elf_section_header64le_parse_bytes(fw, offset_secthdr, error); if (st_shdr == NULL) return FALSE; g_ptr_array_add(sections, g_steal_pointer(&st_shdr)); offset_secthdr += fu_struct_elf_file_header64le_get_shentsize(st_fhdr); } /* add sections as images */ for (guint i = 0; i < sections->len; i++) { GByteArray *st_shdr = g_ptr_array_index(sections, i); guint64 sect_offset = fu_struct_elf_section_header64le_get_offset(st_shdr); guint64 sect_size = fu_struct_elf_section_header64le_get_size(st_shdr); g_autoptr(FuFirmware) img = fu_firmware_new(); if (sect_size > 0) { g_autoptr(GBytes) blob = fu_bytes_new_offset(fw, offset + sect_offset, sect_size, error); if (blob == NULL) return FALSE; fu_firmware_set_bytes(img, blob); } fu_firmware_set_idx(img, i); fu_firmware_add_image(firmware, img); } /* fix up the section names */ shstrndx_blob = fu_firmware_get_image_by_idx_bytes(firmware, fu_struct_elf_file_header64le_get_shstrndx(st_fhdr), error); if (shstrndx_blob == NULL) return FALSE; for (guint i = 0; i < sections->len; i++) { GByteArray *st_shdr = g_ptr_array_index(sections, i); gsize shstrndx_bufsz = 0; guint32 sh_name = fu_struct_elf_section_header64le_get_name(st_shdr); const gchar *shstrndx_buf = g_bytes_get_data(shstrndx_blob, &shstrndx_bufsz); g_autofree gchar *name = NULL; g_autoptr(FuFirmware) img = NULL; if (sh_name == 0) continue; if (sh_name > shstrndx_bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "offset into shstrndx invalid for section 0x%x", i); return FALSE; } img = fu_firmware_get_image_by_idx(firmware, i, error); if (img == NULL) return FALSE; name = g_strndup(shstrndx_buf + sh_name, shstrndx_bufsz - sh_name); if (name != NULL && name[0] != '\0') fu_firmware_set_id(img, name); } /* success */ return TRUE; } static void fu_elf_firmware_init(FuElfFirmware *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); } static void fu_elf_firmware_class_init(FuElfFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_elf_firmware_check_magic; klass_firmware->parse = fu_elf_firmware_parse; } /** * fu_elf_firmware_new: * * Creates a new #FuElfFirmware * * Since: 1.9.3 **/ FuFirmware * fu_elf_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_ELF_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-elf-firmware.h000066400000000000000000000006161460375044200211410ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_ELF_FIRMWARE (fu_elf_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuElfFirmware, fu_elf_firmware, FU, ELF_FIRMWARE, FuFirmware) struct _FuElfFirmwareClass { FuFirmwareClass parent_class; }; FuFirmware * fu_elf_firmware_new(void); fwupd-1.9.16/libfwupdplugin/fu-elf.rs000066400000000000000000000031751460375044200175270ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[repr(u16le)] enum ElfFileHeaderType { None = 0x00, Rel = 0x01, Exec = 0x02, Dyn = 0x03, Core = 0x04, } #[derive(ParseBytes, ValidateBytes)] struct ElfFileHeader64le { ei_magic: [char; 4] == "\x7F\x45\x4C\x46", ei_class: u8 == 0x2, // 64-bit format ei_data: u8 == 0x1, // LE ei_version: u8 == 0x1, ei_osabi: u8 = 0x3, ei_abiversion: u8, _ei_padding: [u8; 7] = 0x00000000000000, type: ElfFileHeaderType, machine: u16le, version: u32le == 0x1, entry: u64le, phoff: u64le = $struct_size, shoff: u64le, flags: u32le, ehsize: u16le = $struct_size, phentsize: u16le, phnum: u16le, shentsize: u16le, shnum: u16le, shstrndx: u16le, } #[derive(ParseBytes)] struct ElfProgramHeader64le { flags: u32le, offset: u64le, vaddr: u64le, paddr: u64le, filesz: u64le, memsz: u64le, flags2: u32le, align: u64le, } #[repr(u32le)] enum ElfSectionHeaderType { Null = 0x0, Progbits = 0x1, Symtab = 0x2, Strtab = 0x3, Rela = 0x4, Hash = 0x5, Dynamic = 0x6, Note = 0x7, Nobits = 0x8, Rel = 0x9, Shlib = 0x0a, Dynsym = 0x0b, InitArray = 0x0e, FiniArray = 0x0f, PreinitArray = 0x10, Group = 0x11, SymtabShndx = 0x12, Num = 0x13, } #[derive(ParseBytes)] struct ElfSectionHeader64le { name: u32le, type: ElfSectionHeaderType, flags: u64le, addr: u64le, offset: u64le, size: u64le, link: u32le, info: u32le, addralign: u64le, entsize: u64le, } fwupd-1.9.16/libfwupdplugin/fu-fdt-firmware.c000066400000000000000000000404311460375044200211420ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-common.h" #include "fu-crc.h" #include "fu-dump.h" #include "fu-fdt-firmware.h" #include "fu-fdt-image.h" #include "fu-fdt-struct.h" #include "fu-mem.h" /** * FuFdtFirmware: * * A Flattened DeviceTree firmware image. * * Documented: * https://devicetree-specification.readthedocs.io/en/latest/chapter5-flattened-format.html * * See also: [class@FuFirmware] */ typedef struct { guint32 cpuid; } FuFdtFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuFdtFirmware, fu_fdt_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_fdt_firmware_get_instance_private(o)) #define FDT_BEGIN_NODE 0x00000001 #define FDT_END_NODE 0x00000002 #define FDT_PROP 0x00000003 #define FDT_NOP 0x00000004 #define FDT_END 0x00000009 #define FDT_LAST_COMP_VERSION 2 #define FDT_DEPTH_MAX 128 static GString * fu_string_new_safe(const guint8 *buf, gsize bufsz, gsize offset, GError **error) { g_autoptr(GString) str = g_string_new(NULL); for (gsize i = offset; i < bufsz; i++) { if (buf[i] == '\0') return g_steal_pointer(&str); g_string_append_c(str, (gchar)buf[i]); } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "buffer not NULL terminated"); return NULL; } static void fu_fdt_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuFdtFirmware *self = FU_FDT_FIRMWARE(firmware); FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "cpuid", priv->cpuid); } /** * fu_fdt_firmware_get_cpuid: * @self: a #FuFdtFirmware * * Gets the CPUID. * * Returns: integer * * Since: 1.8.2 **/ guint32 fu_fdt_firmware_get_cpuid(FuFdtFirmware *self) { FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FDT_FIRMWARE(self), 0x0); return priv->cpuid; } /** * fu_fdt_firmware_set_cpuid: * @self: a #FuFdtFirmware * @cpuid: integer value * * Sets the CPUID. * * Since: 1.8.2 **/ void fu_fdt_firmware_set_cpuid(FuFdtFirmware *self, guint32 cpuid) { FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FDT_FIRMWARE(self)); priv->cpuid = cpuid; } /** * fu_fdt_firmware_get_image_by_path: * @self: a #FuFdtFirmware * @path: ID path, e.g. `/images/firmware-1` * @error: (nullable): optional return location for an error * * Gets the FDT image for a specific path. * * Returns: (transfer full): a #FuFirmware, or %NULL * * Since: 1.8.2 **/ FuFdtImage * fu_fdt_firmware_get_image_by_path(FuFdtFirmware *self, const gchar *path, GError **error) { g_auto(GStrv) paths = NULL; g_autoptr(FuFirmware) img_current = g_object_ref(FU_FIRMWARE(self)); g_return_val_if_fail(FU_IS_FDT_FIRMWARE(self), NULL); g_return_val_if_fail(path != NULL, NULL); g_return_val_if_fail(path[0] != '\0', NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); paths = g_strsplit(path, "/", -1); for (guint i = 0; paths[i] != NULL; i++) { const gchar *id = paths[i]; g_autoptr(FuFirmware) img_tmp = NULL; /* special case for empty */ if (id[0] == '\0') id = NULL; img_tmp = fu_firmware_get_image_by_id(img_current, id, error); if (img_tmp == NULL) return NULL; g_set_object(&img_current, img_tmp); } /* success */ return FU_FDT_IMAGE(g_steal_pointer(&img_current)); } static gboolean fu_fdt_firmware_parse_dt_struct(FuFdtFirmware *self, GBytes *fw, GBytes *strtab, GError **error) { gsize bufsz = 0; gsize offset = 0; guint depth = 0; gboolean has_end = FALSE; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(FuFirmware) firmware_current = g_object_ref(FU_FIRMWARE(self)); /* debug */ fu_dump_bytes(G_LOG_DOMAIN, "dt_struct", fw); /* parse */ while (offset < bufsz) { guint32 token = 0; /* read tag from aligned offset */ offset = fu_common_align_up(offset, FU_FIRMWARE_ALIGNMENT_4); if (!fu_memread_uint32_safe(buf, bufsz, offset, &token, G_BIG_ENDIAN, error)) return FALSE; g_debug("token: 0x%x", token); offset += sizeof(guint32); /* nothing to do */ if (token == FDT_NOP) continue; /* END */ if (token == FDT_END) { if (firmware_current != FU_FIRMWARE(self)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got END with unclosed node"); return FALSE; } has_end = TRUE; break; } /* BEGIN NODE */ if (token == FDT_BEGIN_NODE) { g_autoptr(GString) str = NULL; g_autoptr(FuFirmware) image = NULL; /* sanity check */ if (depth++ > FDT_DEPTH_MAX) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "node depth exceeded maximum: 0x%x", (guint)FDT_DEPTH_MAX); return FALSE; } str = fu_string_new_safe(buf, bufsz, offset, error); if (str == NULL) return FALSE; offset += str->len + 1; image = fu_fdt_image_new(); if (str->len > 0) fu_firmware_set_id(image, str->str); fu_firmware_set_offset(image, offset); if (!fu_firmware_add_image_full(firmware_current, image, error)) return FALSE; g_set_object(&firmware_current, image); continue; } /* END NODE */ if (token == FDT_END_NODE) { if (firmware_current == FU_FIRMWARE(self)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got END NODE with no node to end"); return FALSE; } g_set_object(&firmware_current, fu_firmware_get_parent(firmware_current)); if (depth > 0) depth--; continue; } /* PROP */ if (token == FDT_PROP) { guint32 prop_len; guint32 prop_nameoff; g_autoptr(GBytes) blob = NULL; g_autoptr(GString) str = NULL; g_autoptr(GByteArray) st_prp = NULL; /* sanity check */ if (firmware_current == FU_FIRMWARE(self)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got PROP with unopen node"); return FALSE; } /* parse */ st_prp = fu_struct_fdt_prop_parse(buf, bufsz, offset, error); if (st_prp == NULL) return FALSE; prop_len = fu_struct_fdt_prop_get_len(st_prp); prop_nameoff = fu_struct_fdt_prop_get_nameoff(st_prp); offset += st_prp->len; /* add property */ str = fu_string_new_safe(g_bytes_get_data(strtab, NULL), g_bytes_get_size(strtab), prop_nameoff, error); if (str == NULL) { g_prefix_error(error, "invalid strtab offset 0x%x: ", prop_nameoff); return FALSE; } blob = fu_bytes_new_offset(fw, offset, prop_len, error); if (blob == NULL) return FALSE; fu_fdt_image_set_attr(FU_FDT_IMAGE(firmware_current), str->str, blob); offset += prop_len; continue; } /* unknown token */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid token 0x%x @0%x", token, (guint)offset); return FALSE; } /* did not see FDT_END */ if (!has_end) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "did not see FDT_END"); return FALSE; } /* success */ return TRUE; } static gboolean fu_fdt_firmware_parse_mem_rsvmap(FuFdtFirmware *self, GBytes *fw, gsize offset, GError **error) { /* parse */ for (; offset < g_bytes_get_size(fw); offset += FU_STRUCT_FDT_RESERVE_ENTRY_SIZE) { guint64 address = 0; guint64 size = 0; g_autoptr(GByteArray) st_res = NULL; st_res = fu_struct_fdt_reserve_entry_parse_bytes(fw, offset, error); if (st_res == NULL) return FALSE; address = fu_struct_fdt_reserve_entry_get_address(st_res); size = fu_struct_fdt_reserve_entry_get_size(st_res); g_debug("mem_rsvmap: 0x%x, 0x%x", (guint)address, (guint)size); if (address == 0x0 && size == 0x0) break; } /* success */ return TRUE; } static gboolean fu_fdt_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_fdt_validate_bytes(fw, offset, error); } static gboolean fu_fdt_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuFdtFirmware *self = FU_FDT_FIRMWARE(firmware); FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); guint32 totalsize; gsize bufsz = g_bytes_get_size(fw); guint32 off_mem_rsvmap = 0; g_autoptr(GByteArray) st_hdr = NULL; /* sanity check */ st_hdr = fu_struct_fdt_parse_bytes(fw, offset, error); if (st_hdr == NULL) return FALSE; totalsize = fu_struct_fdt_get_totalsize(st_hdr); if (totalsize > bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "truncated image, got 0x%x, expected >= 0x%x", (guint)bufsz, (guint)totalsize); return FALSE; } fu_firmware_set_size(firmware, totalsize); /* read header */ priv->cpuid = fu_struct_fdt_get_boot_cpuid_phys(st_hdr); off_mem_rsvmap = fu_struct_fdt_get_off_mem_rsvmap(st_hdr); if (off_mem_rsvmap != 0x0) { if (!fu_fdt_firmware_parse_mem_rsvmap(self, fw, offset + off_mem_rsvmap, error)) return FALSE; } if (fu_struct_fdt_get_last_comp_version(st_hdr) < FDT_LAST_COMP_VERSION) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid header version, got 0x%x, expected >= 0x%x", (guint)fu_struct_fdt_get_last_comp_version(st_hdr), (guint)FDT_LAST_COMP_VERSION); return FALSE; } fu_firmware_set_version_raw(firmware, fu_struct_fdt_get_version(st_hdr)); /* parse device tree struct */ if (fu_struct_fdt_get_size_dt_struct(st_hdr) != 0x0 && fu_struct_fdt_get_size_dt_strings(st_hdr) != 0x0) { g_autoptr(GBytes) dt_strings = NULL; g_autoptr(GBytes) dt_struct = NULL; dt_strings = fu_bytes_new_offset(fw, offset + fu_struct_fdt_get_off_dt_strings(st_hdr), fu_struct_fdt_get_size_dt_strings(st_hdr), error); if (dt_strings == NULL) return FALSE; dt_struct = fu_bytes_new_offset(fw, offset + fu_struct_fdt_get_off_dt_struct(st_hdr), fu_struct_fdt_get_size_dt_struct(st_hdr), error); if (dt_struct == NULL) return FALSE; if (!fu_fdt_firmware_parse_dt_struct(self, dt_struct, dt_strings, error)) return FALSE; } /* success */ return TRUE; } typedef struct { GByteArray *dt_strings; GByteArray *dt_struct; GHashTable *strtab; } FuFdtFirmwareBuildHelper; static guint32 fu_fdt_firmware_append_to_strtab(FuFdtFirmwareBuildHelper *helper, const gchar *key) { gpointer tmp = NULL; guint32 offset; /* already exists */ if (g_hash_table_lookup_extended(helper->strtab, key, NULL, &tmp)) return GPOINTER_TO_UINT(tmp); g_debug("adding strtab: %s", key); offset = helper->dt_strings->len; g_byte_array_append(helper->dt_strings, (const guint8 *)key, strlen(key)); fu_byte_array_append_uint8(helper->dt_strings, 0x0); g_hash_table_insert(helper->strtab, g_strdup(key), GUINT_TO_POINTER(offset)); return offset; } static gboolean fu_fdt_firmware_write_image(FuFdtFirmware *self, FuFdtImage *img, FuFdtFirmwareBuildHelper *helper, guint depth, GError **error) { const gchar *id = fu_firmware_get_id(FU_FIRMWARE(img)); g_autoptr(GPtrArray) images = fu_firmware_get_images(FU_FIRMWARE(img)); g_autoptr(GPtrArray) attrs = fu_fdt_image_get_attrs(img); /* sanity check */ if (depth > 0 && id == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "child FuFdtImage requires ID"); return FALSE; } /* BEGIN_NODE, ID, NUL */ fu_byte_array_append_uint32(helper->dt_struct, FDT_BEGIN_NODE, G_BIG_ENDIAN); if (id != NULL) { g_byte_array_append(helper->dt_struct, (const guint8 *)id, strlen(id) + 1); } else { fu_byte_array_append_uint8(helper->dt_struct, 0x0); } fu_byte_array_align_up(helper->dt_struct, FU_FIRMWARE_ALIGNMENT_4, 0x0); /* write properties */ for (guint i = 0; i < attrs->len; i++) { const gchar *key = g_ptr_array_index(attrs, i); g_autoptr(GBytes) blob = NULL; g_autoptr(GByteArray) st_prp = fu_struct_fdt_prop_new(); blob = fu_fdt_image_get_attr(img, key, error); if (blob == NULL) return FALSE; fu_byte_array_append_uint32(helper->dt_struct, FDT_PROP, G_BIG_ENDIAN); fu_struct_fdt_prop_set_len(st_prp, g_bytes_get_size(blob)); fu_struct_fdt_prop_set_nameoff(st_prp, fu_fdt_firmware_append_to_strtab(helper, key)); g_byte_array_append(helper->dt_struct, st_prp->data, st_prp->len); fu_byte_array_append_bytes(helper->dt_struct, blob); fu_byte_array_align_up(helper->dt_struct, FU_FIRMWARE_ALIGNMENT_4, 0x0); } /* write children, recursively */ for (guint i = 0; i < images->len; i++) { FuFdtImage *img_child = g_ptr_array_index(images, i); if (!fu_fdt_firmware_write_image(self, img_child, helper, depth + 1, error)) return FALSE; } /* END_NODE */ fu_byte_array_append_uint32(helper->dt_struct, FDT_END_NODE, G_BIG_ENDIAN); return TRUE; } static GByteArray * fu_fdt_firmware_write(FuFirmware *firmware, GError **error) { FuFdtFirmware *self = FU_FDT_FIRMWARE(firmware); FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); guint32 off_dt_struct; guint32 off_dt_strings; guint32 off_mem_rsvmap; g_autoptr(GByteArray) dt_strings = g_byte_array_new(); g_autoptr(GByteArray) dt_struct = g_byte_array_new(); g_autoptr(GHashTable) strtab = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); g_autoptr(GByteArray) st_hdr = fu_struct_fdt_new(); g_autoptr(GByteArray) mem_rsvmap = fu_struct_fdt_reserve_entry_new(); FuFdtFirmwareBuildHelper helper = { .dt_strings = dt_strings, .dt_struct = dt_struct, .strtab = strtab, }; /* empty mem_rsvmap */ off_mem_rsvmap = fu_common_align_up(st_hdr->len, FU_FIRMWARE_ALIGNMENT_4); /* dt_struct */ off_dt_struct = fu_common_align_up(off_mem_rsvmap + mem_rsvmap->len, FU_FIRMWARE_ALIGNMENT_4); /* only one root node supported */ if (images->len != 1) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no root node"); return NULL; } if (!fu_fdt_firmware_write_image(self, FU_FDT_IMAGE(g_ptr_array_index(images, 0)), &helper, 0, error)) return NULL; fu_byte_array_append_uint32(dt_struct, FDT_END, G_BIG_ENDIAN); /* dt_strings */ off_dt_strings = fu_common_align_up(off_dt_struct + dt_struct->len, FU_FIRMWARE_ALIGNMENT_4); /* write header */ fu_struct_fdt_set_totalsize(st_hdr, off_dt_strings + dt_strings->len); fu_struct_fdt_set_off_dt_struct(st_hdr, off_dt_struct); fu_struct_fdt_set_off_dt_strings(st_hdr, off_dt_strings); fu_struct_fdt_set_off_mem_rsvmap(st_hdr, off_mem_rsvmap); fu_struct_fdt_set_version(st_hdr, fu_firmware_get_version_raw(firmware)); fu_struct_fdt_set_boot_cpuid_phys(st_hdr, priv->cpuid); fu_struct_fdt_set_size_dt_strings(st_hdr, dt_strings->len); fu_struct_fdt_set_size_dt_struct(st_hdr, dt_struct->len); fu_byte_array_align_up(st_hdr, FU_FIRMWARE_ALIGNMENT_4, 0x0); /* write mem_rsvmap, dt_struct, dt_strings */ g_byte_array_append(st_hdr, mem_rsvmap->data, mem_rsvmap->len); fu_byte_array_align_up(st_hdr, FU_FIRMWARE_ALIGNMENT_4, 0x0); g_byte_array_append(st_hdr, dt_struct->data, dt_struct->len); fu_byte_array_align_up(st_hdr, FU_FIRMWARE_ALIGNMENT_4, 0x0); g_byte_array_append(st_hdr, dt_strings->data, dt_strings->len); fu_byte_array_align_up(st_hdr, FU_FIRMWARE_ALIGNMENT_4, 0x0); /* success */ return g_steal_pointer(&st_hdr); } static gboolean fu_fdt_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuFdtFirmware *self = FU_FDT_FIRMWARE(firmware); FuFdtFirmwarePrivate *priv = GET_PRIVATE(self); guint64 tmp; /* optional properties */ tmp = xb_node_query_text_as_uint(n, "cpuid", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) priv->cpuid = tmp; /* success */ return TRUE; } static void fu_fdt_firmware_init(FuFdtFirmware *self) { g_type_ensure(FU_TYPE_FDT_IMAGE); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); } static void fu_fdt_firmware_class_init(FuFdtFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_fdt_firmware_check_magic; klass_firmware->export = fu_fdt_firmware_export; klass_firmware->parse = fu_fdt_firmware_parse; klass_firmware->write = fu_fdt_firmware_write; klass_firmware->build = fu_fdt_firmware_build; } /** * fu_fdt_firmware_new: * * Creates a new #FuFirmware of sub type FDT * * Since: 1.8.2 **/ FuFirmware * fu_fdt_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_FDT_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-fdt-firmware.h000066400000000000000000000013111460375044200211410ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-fdt-image.h" #include "fu-firmware.h" #define FU_TYPE_FDT_FIRMWARE (fu_fdt_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuFdtFirmware, fu_fdt_firmware, FU, FDT_FIRMWARE, FuFirmware) struct _FuFdtFirmwareClass { FuFirmwareClass parent_class; }; FuFirmware * fu_fdt_firmware_new(void); guint32 fu_fdt_firmware_get_cpuid(FuFdtFirmware *self) G_GNUC_NON_NULL(1); void fu_fdt_firmware_set_cpuid(FuFdtFirmware *self, guint32 cpuid) G_GNUC_NON_NULL(1); FuFdtImage * fu_fdt_firmware_get_image_by_path(FuFdtFirmware *self, const gchar *path, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-fdt-image.c000066400000000000000000000426651460375044200204230ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-byte-array.h" #include "fu-common.h" #include "fu-fdt-image.h" #include "fu-mem.h" #include "fu-string.h" /** * FuFdtImage: * * A Flattened DeviceTree firmware image. * * See also: [class@FuFdtFirmware] */ typedef struct { GHashTable *hash_attrs; GHashTable *hash_attrs_format; } FuFdtImagePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuFdtImage, fu_fdt_image, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_fdt_image_get_instance_private(o)) #define FU_FDT_IMAGE_FORMAT_STR "str" #define FU_FDT_IMAGE_FORMAT_STRLIST "strlist" #define FU_FDT_IMAGE_FORMAT_UINT32 "uint32" #define FU_FDT_IMAGE_FORMAT_UINT64 "uint64" #define FU_FDT_IMAGE_FORMAT_DATA "data" static const gchar * fu_fdt_image_guess_format_from_key(const gchar *key) { struct { const gchar *key; const gchar *format; } key_format_map[] = {{"#address-cells", FU_FDT_IMAGE_FORMAT_UINT32}, {"algo", FU_FDT_IMAGE_FORMAT_STR}, {"arch", FU_FDT_IMAGE_FORMAT_STR}, {"compatible", FU_FDT_IMAGE_FORMAT_STRLIST}, {"compression", FU_FDT_IMAGE_FORMAT_STR}, {"creator", FU_FDT_IMAGE_FORMAT_STR}, {"data-offset", FU_FDT_IMAGE_FORMAT_UINT32}, {"data-size", FU_FDT_IMAGE_FORMAT_UINT32}, {"default", FU_FDT_IMAGE_FORMAT_STR}, {"description", FU_FDT_IMAGE_FORMAT_STR}, {"entry", FU_FDT_IMAGE_FORMAT_STR}, {"firmware", FU_FDT_IMAGE_FORMAT_STR}, {"load", FU_FDT_IMAGE_FORMAT_UINT32}, {"os", FU_FDT_IMAGE_FORMAT_STR}, {"timestamp", FU_FDT_IMAGE_FORMAT_UINT32}, {"type", FU_FDT_IMAGE_FORMAT_STR}, {"version", FU_FDT_IMAGE_FORMAT_STR}, {NULL, NULL}}; for (guint i = 0; key_format_map[i].key != NULL; i++) { if (g_strcmp0(key, key_format_map[i].key) == 0) return key_format_map[i].format; } return NULL; } static gchar ** fu_fdt_image_strlist_from_blob(GBytes *blob) { gchar **val; gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(blob, &bufsz); g_autoptr(GPtrArray) strs = g_ptr_array_new(); /* delimit by NUL */ for (gsize i = 0; i < bufsz; i++) { const gchar *tmp = (const gchar *)buf + i; g_ptr_array_add(strs, (gpointer)tmp); i += strlen(tmp); } /* copy to GStrv */ val = g_new0(gchar *, strs->len + 1); for (guint i = 0; i < strs->len; i++) val[i] = g_strdup(g_ptr_array_index(strs, i)); return val; } static void fu_fdt_image_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuFdtImage *self = FU_FDT_IMAGE(firmware); FuFdtImagePrivate *priv = GET_PRIVATE(self); GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, priv->hash_attrs); while (g_hash_table_iter_next(&iter, &key, &value)) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(value, &bufsz); const gchar *format = g_hash_table_lookup(priv->hash_attrs_format, key); g_autofree gchar *str = NULL; g_autoptr(XbBuilderNode) bc = NULL; /* guess format based on key name to improve debugging experience */ if (format == NULL) format = fu_fdt_image_guess_format_from_key(key); if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_UINT32) == 0 && bufsz == 4) { guint64 tmp = fu_memread_uint32(buf, G_BIG_ENDIAN); str = g_strdup_printf("0x%x", (guint)tmp); } else if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_UINT64) == 0 && bufsz == 8) { guint64 tmp = fu_memread_uint64(buf, G_BIG_ENDIAN); str = g_strdup_printf("0x%x", (guint)tmp); } else if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_STR) == 0 && bufsz > 0) { str = g_strndup((const gchar *)buf, bufsz); } else if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_STRLIST) == 0 && bufsz > 0) { g_auto(GStrv) tmp = fu_fdt_image_strlist_from_blob(value); str = g_strjoinv(":", tmp); } else { str = g_base64_encode(buf, bufsz); } bc = xb_builder_node_insert(bn, "metadata", "key", key, NULL); if (str != NULL) xb_builder_node_set_text(bc, str, -1); if (format != NULL) xb_builder_node_set_attr(bc, "format", format); } } /** * fu_fdt_image_get_attrs: * @self: a #FuFdtImage * * Gets all the attributes stored on the image. * * Returns: (transfer container) (element-type utf8): keys * * Since: 1.8.2 **/ GPtrArray * fu_fdt_image_get_attrs(FuFdtImage *self) { FuFdtImagePrivate *priv = GET_PRIVATE(self); GPtrArray *array = g_ptr_array_new_with_free_func(g_free); g_autoptr(GList) keys = NULL; g_return_val_if_fail(FU_IS_FDT_IMAGE(self), NULL); keys = g_hash_table_get_keys(priv->hash_attrs); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; g_ptr_array_add(array, g_strdup(key)); } return array; } /** * fu_fdt_image_get_attr: * @self: a #FuFdtImage * @key: string, e.g. `creator` * @error: (nullable): optional return location for an error * * Gets a attribute from the image. * * Returns: (transfer full): blob * * Since: 1.8.2 **/ GBytes * fu_fdt_image_get_attr(FuFdtImage *self, const gchar *key, GError **error) { FuFdtImagePrivate *priv = GET_PRIVATE(self); GBytes *blob; g_return_val_if_fail(FU_IS_FDT_IMAGE(self), NULL); g_return_val_if_fail(key != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); blob = g_hash_table_lookup(priv->hash_attrs, key); if (blob == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no data for %s", key); return NULL; } /* success */ return g_bytes_ref(blob); } /** * fu_fdt_image_get_attr_u32: * @self: a #FuFdtImage * @key: string, e.g. `creator` * @val: (out) (nullable): value * @error: (nullable): optional return location for an error * * Gets a uint32 attribute from the image. * * Returns: %TRUE if @val was set. * * Since: 1.8.2 **/ gboolean fu_fdt_image_get_attr_u32(FuFdtImage *self, const gchar *key, guint32 *val, GError **error) { g_autoptr(GBytes) blob = NULL; g_return_val_if_fail(FU_IS_FDT_IMAGE(self), FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); blob = fu_fdt_image_get_attr(self, key, error); if (blob == NULL) return FALSE; if (g_bytes_get_size(blob) != sizeof(guint32)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid data size for %s, got 0x%x, expected 0x%x", key, (guint)g_bytes_get_size(blob), (guint)sizeof(guint32)); return FALSE; } if (val != NULL) *val = fu_memread_uint32(g_bytes_get_data(blob, NULL), G_BIG_ENDIAN); return TRUE; } /** * fu_fdt_image_get_attr_u64: * @self: a #FuFdtImage * @key: string, e.g. `creator` * @val: (out) (nullable): value * @error: (nullable): optional return location for an error * * Gets a uint64 attribute from the image. * * Returns: %TRUE if @val was set. * * Since: 1.8.2 **/ gboolean fu_fdt_image_get_attr_u64(FuFdtImage *self, const gchar *key, guint64 *val, GError **error) { g_autoptr(GBytes) blob = NULL; g_return_val_if_fail(FU_IS_FDT_IMAGE(self), FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); blob = fu_fdt_image_get_attr(self, key, error); if (blob == NULL) return FALSE; if (g_bytes_get_size(blob) != sizeof(guint64)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid data size for %s, got 0x%x, expected 0x%x", key, (guint)g_bytes_get_size(blob), (guint)sizeof(guint64)); return FALSE; } if (val != NULL) *val = fu_memread_uint64(g_bytes_get_data(blob, NULL), G_BIG_ENDIAN); return TRUE; } /** * fu_fdt_image_get_attr_strlist: * @self: a #FuFdtImage * @key: string, e.g. `compatible` * @val: (out) (nullable) (transfer full): values * @error: (nullable): optional return location for an error * * Gets a stringlist attribute from the image. @val is always `NUL` terminated. * * Returns: %TRUE if @val was set. * * Since: 1.8.2 **/ gboolean fu_fdt_image_get_attr_strlist(FuFdtImage *self, const gchar *key, gchar ***val, GError **error) { g_autoptr(GBytes) blob = NULL; const guint8 *buf; gsize bufsz = 0; g_return_val_if_fail(FU_IS_FDT_IMAGE(self), FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); blob = fu_fdt_image_get_attr(self, key, error); if (blob == NULL) return FALSE; if (g_bytes_get_size(blob) == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid data size for %s, got 0x%x", key, (guint)g_bytes_get_size(blob)); return FALSE; } /* sanity check */ buf = g_bytes_get_data(blob, &bufsz); for (gsize i = 0; i < bufsz; i++) { if (buf[i] != 0x0 && !g_ascii_isprint((gchar)buf[i])) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "nonprintable character 0x%02x at offset 0x%x in %s", buf[i], (guint)i, key); return FALSE; } } /* success */ if (val != NULL) *val = fu_fdt_image_strlist_from_blob(blob); return TRUE; } /** * fu_fdt_image_get_attr_str: * @self: a #FuFdtImage * @key: string, e.g. `creator` * @val: (out) (nullable) (transfer full): value * @error: (nullable): optional return location for an error * * Gets a string attribute from the image. @val is always `NUL` terminated. * * Returns: %TRUE if @val was set. * * Since: 1.8.2 **/ gboolean fu_fdt_image_get_attr_str(FuFdtImage *self, const gchar *key, gchar **val, GError **error) { g_autoptr(GBytes) blob = NULL; const guint8 *buf; gsize bufsz = 0; g_return_val_if_fail(FU_IS_FDT_IMAGE(self), FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); blob = fu_fdt_image_get_attr(self, key, error); if (blob == NULL) return FALSE; if (g_bytes_get_size(blob) == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid data size for %s, got 0x%x", key, (guint)g_bytes_get_size(blob)); return FALSE; } /* sanity check */ buf = g_bytes_get_data(blob, &bufsz); for (gsize i = 0; i < bufsz; i++) { if (buf[i] != 0x0 && !g_ascii_isprint((gchar)buf[i])) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "nonprintable character 0x%02x at offset 0x%x in %s", buf[i], (guint)i, key); return FALSE; } } /* success */ if (val != NULL) *val = g_strndup(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob)); return TRUE; } /** * fu_fdt_image_set_attr: * @self: a #FuFdtImage * @key: string, e.g. `creator` * @blob: a #GBytes * * Sets a attribute for the image. * * Since: 1.8.2 **/ void fu_fdt_image_set_attr(FuFdtImage *self, const gchar *key, GBytes *blob) { FuFdtImagePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FDT_IMAGE(self)); g_return_if_fail(key != NULL); g_hash_table_insert(priv->hash_attrs, g_strdup(key), g_bytes_ref(blob)); } static void fu_fdt_image_set_attr_format(FuFdtImage *self, const gchar *key, const gchar *format) { FuFdtImagePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FDT_IMAGE(self)); g_return_if_fail(format != NULL); g_hash_table_insert(priv->hash_attrs_format, g_strdup(key), strdup(format)); } /** * fu_fdt_image_set_attr_uint32: * @self: a #FuFdtImage * @key: string, e.g. `creator` * @value: value to store * * Sets a uint32 attribute for the image. * * Since: 1.8.2 **/ void fu_fdt_image_set_attr_uint32(FuFdtImage *self, const gchar *key, guint32 value) { guint8 buf[4] = {0x0}; g_autoptr(GBytes) blob = NULL; g_return_if_fail(FU_IS_FDT_IMAGE(self)); g_return_if_fail(key != NULL); fu_memwrite_uint32(buf, value, G_BIG_ENDIAN); blob = g_bytes_new(buf, sizeof(buf)); fu_fdt_image_set_attr(self, key, blob); fu_fdt_image_set_attr_format(self, key, FU_FDT_IMAGE_FORMAT_UINT32); } /** * fu_fdt_image_set_attr_uint64: * @self: a #FuFdtImage * @key: string, e.g. `creator` * @value: value to store * * Sets a uint64 attribute for the image. * * Since: 1.8.2 **/ void fu_fdt_image_set_attr_uint64(FuFdtImage *self, const gchar *key, guint64 value) { guint8 buf[8] = {0x0}; g_autoptr(GBytes) blob = NULL; g_return_if_fail(FU_IS_FDT_IMAGE(self)); g_return_if_fail(key != NULL); fu_memwrite_uint64(buf, value, G_BIG_ENDIAN); blob = g_bytes_new(buf, sizeof(buf)); fu_fdt_image_set_attr(self, key, blob); fu_fdt_image_set_attr_format(self, key, FU_FDT_IMAGE_FORMAT_UINT64); } /** * fu_fdt_image_set_attr_str: * @self: a #FuFdtImage * @key: string, e.g. `creator` * @value: value to store * * Sets a string attribute for the image. * * Since: 1.8.2 **/ void fu_fdt_image_set_attr_str(FuFdtImage *self, const gchar *key, const gchar *value) { g_autoptr(GBytes) blob = NULL; g_return_if_fail(FU_IS_FDT_IMAGE(self)); g_return_if_fail(key != NULL); g_return_if_fail(value != NULL); blob = g_bytes_new((const guint8 *)value, strlen(value) + 1); fu_fdt_image_set_attr(self, key, blob); fu_fdt_image_set_attr_format(self, key, FU_FDT_IMAGE_FORMAT_STR); } /** * fu_fdt_image_set_attr_strlist: * @self: a #FuFdtImage * @key: string, e.g. `compatible` * @value: values to store * * Sets a stringlist attribute for the image. * * Since: 1.8.2 **/ void fu_fdt_image_set_attr_strlist(FuFdtImage *self, const gchar *key, gchar **value) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) blob = NULL; g_return_if_fail(FU_IS_FDT_IMAGE(self)); g_return_if_fail(key != NULL); g_return_if_fail(value != NULL); g_return_if_fail(value[0] != NULL); for (guint i = 0; value[i] != NULL; i++) { g_byte_array_append(buf, (const guint8 *)value[i], strlen(value[i])); fu_byte_array_append_uint8(buf, 0x0); } blob = g_bytes_new(buf->data, buf->len); fu_fdt_image_set_attr(self, key, blob); fu_fdt_image_set_attr_format(self, key, FU_FDT_IMAGE_FORMAT_STRLIST); } static gboolean fu_fdt_image_build_metadata_node(FuFdtImage *self, XbNode *n, GError **error) { const gchar *key; const gchar *format; const gchar *value = xb_node_get_text(n); key = xb_node_get_attr(n, "key"); if (key == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "key invalid"); return FALSE; } format = xb_node_get_attr(n, "format"); if (format == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "format unspecified for %s, expected uint64|uint32|str|strlist|data", key); return FALSE; } fu_fdt_image_set_attr_format(self, key, format); /* actually parse values */ if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_UINT32) == 0) { guint64 tmp = 0; if (value != NULL) { if (!fu_strtoull(value, &tmp, 0x0, G_MAXUINT32, error)) return FALSE; } fu_fdt_image_set_attr_uint32(self, key, tmp); return TRUE; } if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_UINT64) == 0) { guint64 tmp = 0; if (value != NULL) { if (!fu_strtoull(value, &tmp, 0x0, G_MAXUINT64, error)) return FALSE; } fu_fdt_image_set_attr_uint64(self, key, tmp); return TRUE; } if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_STR) == 0) { if (value != NULL) { fu_fdt_image_set_attr_str(self, key, value); } else { g_autoptr(GBytes) blob = g_bytes_new(NULL, 0); fu_fdt_image_set_attr(self, key, blob); } return TRUE; } if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_STRLIST) == 0) { if (value != NULL) { g_auto(GStrv) split = g_strsplit(value, ":", -1); fu_fdt_image_set_attr_strlist(self, key, split); } else { g_autoptr(GBytes) blob = g_bytes_new(NULL, 0); fu_fdt_image_set_attr(self, key, blob); } return TRUE; } if (g_strcmp0(format, FU_FDT_IMAGE_FORMAT_DATA) == 0) { g_autoptr(GBytes) blob = NULL; if (value != NULL) { gsize bufsz = 0; g_autofree guchar *buf = g_base64_decode(value, &bufsz); blob = g_bytes_new(buf, bufsz); } else { blob = g_bytes_new(NULL, 0); } fu_fdt_image_set_attr(self, key, blob); return TRUE; } /* failed */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "format for %s invalid, expected uint64|uint32|str|strlist|data", key); return FALSE; } static gboolean fu_fdt_image_build(FuFirmware *firmware, XbNode *n, GError **error) { FuFdtImage *self = FU_FDT_IMAGE(firmware); g_autoptr(GPtrArray) metadata = NULL; metadata = xb_node_query(n, "metadata", 0, NULL); if (metadata != NULL) { for (guint i = 0; i < metadata->len; i++) { XbNode *c = g_ptr_array_index(metadata, i); if (!fu_fdt_image_build_metadata_node(self, c, error)) return FALSE; } } /* success */ return TRUE; } static void fu_fdt_image_init(FuFdtImage *self) { FuFdtImagePrivate *priv = GET_PRIVATE(self); priv->hash_attrs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_bytes_unref); priv->hash_attrs_format = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); fu_firmware_set_images_max(FU_FIRMWARE(self), 10000); } static void fu_fdt_image_finalize(GObject *object) { FuFdtImage *self = FU_FDT_IMAGE(object); FuFdtImagePrivate *priv = GET_PRIVATE(self); g_hash_table_unref(priv->hash_attrs); g_hash_table_unref(priv->hash_attrs_format); G_OBJECT_CLASS(fu_fdt_image_parent_class)->finalize(object); } static void fu_fdt_image_class_init(FuFdtImageClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_fdt_image_finalize; klass_firmware->export = fu_fdt_image_export; klass_firmware->build = fu_fdt_image_build; } /** * fu_fdt_image_new: * * Creates a new #FuFirmware of sub type FDT image * * Since: 1.8.2 **/ FuFirmware * fu_fdt_image_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_FDT_IMAGE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-fdt-image.h000066400000000000000000000031271460375044200204160ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_FDT_IMAGE (fu_fdt_image_get_type()) G_DECLARE_DERIVABLE_TYPE(FuFdtImage, fu_fdt_image, FU, FDT_IMAGE, FuFirmware) struct _FuFdtImageClass { FuFirmwareClass parent_class; }; FuFirmware * fu_fdt_image_new(void); GBytes * fu_fdt_image_get_attr(FuFdtImage *self, const gchar *key, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_fdt_image_get_attr_u32(FuFdtImage *self, const gchar *key, guint32 *val, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_fdt_image_get_attr_u64(FuFdtImage *self, const gchar *key, guint64 *val, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_fdt_image_get_attr_str(FuFdtImage *self, const gchar *key, gchar **val, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_fdt_image_get_attr_strlist(FuFdtImage *self, const gchar *key, gchar ***val, GError **error) G_GNUC_NON_NULL(1, 2); void fu_fdt_image_set_attr(FuFdtImage *self, const gchar *key, GBytes *blob) G_GNUC_NON_NULL(1, 2); void fu_fdt_image_set_attr_uint32(FuFdtImage *self, const gchar *key, guint32 value) G_GNUC_NON_NULL(1, 2); void fu_fdt_image_set_attr_uint64(FuFdtImage *self, const gchar *key, guint64 value) G_GNUC_NON_NULL(1, 2); void fu_fdt_image_set_attr_str(FuFdtImage *self, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2); void fu_fdt_image_set_attr_strlist(FuFdtImage *self, const gchar *key, gchar **value) G_GNUC_NON_NULL(1, 2); GPtrArray * fu_fdt_image_get_attrs(FuFdtImage *self) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-fdt.rs000066400000000000000000000011221460375044200175240ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, ValidateBytes, ParseBytes)] struct Fdt { magic: u32be == 0xD00DFEED, totalsize: u32be, off_dt_struct: u32be, off_dt_strings: u32be, off_mem_rsvmap: u32be, version: u32be, last_comp_version: u32be = 2, boot_cpuid_phys: u32be, size_dt_strings: u32be, size_dt_struct: u32be, } #[derive(New, ParseBytes)] struct FdtReserveEntry { address: u64be, size: u64be, } #[derive(New, Parse)] struct FdtProp { len: u32be, nameoff: u32be, } fwupd-1.9.16/libfwupdplugin/fu-firmware-common.c000066400000000000000000000147051460375044200216620ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include #include "fu-common.h" #include "fu-firmware-common.h" #include "fu-mem.h" #include "fu-string.h" /** * fu_firmware_strparse_uint4_safe: * @data: destination buffer * @datasz: size of @data, typically the same as `strlen(data)` * @offset: offset in chars into @data to read * @value: (out) (nullable): parsed value * @error: (nullable): optional return location for an error * * Parses a base 16 number from a string of 1 character in length. * The returned @value will range from 0 to 0xf. * * Returns: %TRUE if parsed, %FALSE otherwise * * Since: 1.5.6 **/ gboolean fu_firmware_strparse_uint4_safe(const gchar *data, gsize datasz, gsize offset, guint8 *value, GError **error) { gchar buffer[2] = {'\0'}; gchar *endptr = NULL; guint64 valuetmp; if (!fu_memcpy_safe((guint8 *)buffer, sizeof(buffer), 0x0, /* dst */ (const guint8 *)data, datasz, offset, /* src */ sizeof(buffer) - 1, error)) return FALSE; valuetmp = g_ascii_strtoull(buffer, &endptr, 16); if (endptr - buffer != sizeof(buffer) - 1) { g_autofree gchar *str = fu_strsafe(buffer, sizeof(buffer)); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse %s as hex", str); return FALSE; } if (value != NULL) *value = (guint8)valuetmp; return TRUE; } /** * fu_firmware_strparse_uint8_safe: * @data: destination buffer * @datasz: size of @data, typically the same as `strlen(data)` * @offset: offset in chars into @data to read * @value: (out) (nullable): parsed value * @error: (nullable): optional return location for an error * * Parses a base 16 number from a string of 2 characters in length. * The returned @value will range from 0 to 0xff. * * Returns: %TRUE if parsed, %FALSE otherwise * * Since: 1.5.6 **/ gboolean fu_firmware_strparse_uint8_safe(const gchar *data, gsize datasz, gsize offset, guint8 *value, GError **error) { gchar buffer[3] = {'\0'}; gchar *endptr = NULL; guint64 valuetmp; if (!fu_memcpy_safe((guint8 *)buffer, sizeof(buffer), 0x0, /* dst */ (const guint8 *)data, datasz, offset, /* src */ sizeof(buffer) - 1, error)) return FALSE; valuetmp = g_ascii_strtoull(buffer, &endptr, 16); if (endptr - buffer != sizeof(buffer) - 1) { g_autofree gchar *str = fu_strsafe(buffer, sizeof(buffer)); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse %s as hex", str); return FALSE; } if (value != NULL) *value = (guint8)valuetmp; return TRUE; } /** * fu_firmware_strparse_uint16_safe: * @data: destination buffer * @datasz: size of @data, typically the same as `strlen(data)` * @offset: offset in chars into @data to read * @value: (out) (nullable): parsed value * @error: (nullable): optional return location for an error * * Parses a base 16 number from a string of 4 characters in length. * The returned @value will range from 0 to 0xffff. * * Returns: %TRUE if parsed, %FALSE otherwise * * Since: 1.5.6 **/ gboolean fu_firmware_strparse_uint16_safe(const gchar *data, gsize datasz, gsize offset, guint16 *value, GError **error) { gchar buffer[5] = {'\0'}; gchar *endptr = NULL; guint64 valuetmp; if (!fu_memcpy_safe((guint8 *)buffer, sizeof(buffer), 0x0, /* dst */ (const guint8 *)data, datasz, offset, /* src */ sizeof(buffer) - 1, error)) return FALSE; valuetmp = g_ascii_strtoull(buffer, &endptr, 16); if (endptr - buffer != sizeof(buffer) - 1) { g_autofree gchar *str = fu_strsafe(buffer, sizeof(buffer)); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse %s as hex", str); return FALSE; } if (value != NULL) *value = (guint16)valuetmp; return TRUE; } /** * fu_firmware_strparse_uint24_safe: * @data: destination buffer * @datasz: size of @data, typically the same as `strlen(data)` * @offset: offset in chars into @data to read * @value: (out) (nullable): parsed value * @error: (nullable): optional return location for an error * * Parses a base 16 number from a string of 6 characters in length. * The returned @value will range from 0 to 0xffffff. * * Returns: %TRUE if parsed, %FALSE otherwise * * Since: 1.5.6 **/ gboolean fu_firmware_strparse_uint24_safe(const gchar *data, gsize datasz, gsize offset, guint32 *value, GError **error) { gchar buffer[7] = {'\0'}; gchar *endptr = NULL; guint64 valuetmp; if (!fu_memcpy_safe((guint8 *)buffer, sizeof(buffer), 0x0, /* dst */ (const guint8 *)data, datasz, offset, /* src */ sizeof(buffer) - 1, error)) return FALSE; valuetmp = g_ascii_strtoull(buffer, &endptr, 16); if (endptr - buffer != sizeof(buffer) - 1) { g_autofree gchar *str = fu_strsafe(buffer, sizeof(buffer)); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse %s as hex", str); return FALSE; } if (value != NULL) *value = (guint16)valuetmp; return TRUE; } /** * fu_firmware_strparse_uint32_safe: * @data: destination buffer * @datasz: size of @data, typically the same as `strlen(data)` * @offset: offset in chars into @data to read * @value: (out) (nullable): parsed value * @error: (nullable): optional return location for an error * * Parses a base 16 number from a string of 8 characters in length. * The returned @value will range from 0 to 0xffffffff. * * Returns: %TRUE if parsed, %FALSE otherwise * * Since: 1.5.6 **/ gboolean fu_firmware_strparse_uint32_safe(const gchar *data, gsize datasz, gsize offset, guint32 *value, GError **error) { gchar buffer[9] = {'\0'}; gchar *endptr = NULL; guint64 valuetmp; if (!fu_memcpy_safe((guint8 *)buffer, sizeof(buffer), 0x0, /* dst */ (const guint8 *)data, datasz, offset, /* src */ sizeof(buffer) - 1, error)) return FALSE; valuetmp = g_ascii_strtoull(buffer, &endptr, 16); if (endptr - buffer != sizeof(buffer) - 1) { g_autofree gchar *str = fu_strsafe(buffer, sizeof(buffer)); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse %s as hex", str); return FALSE; } if (value != NULL) *value = (guint32)valuetmp; return TRUE; } fwupd-1.9.16/libfwupdplugin/fu-firmware-common.h000066400000000000000000000016471460375044200216700ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gboolean fu_firmware_strparse_uint4_safe(const gchar *data, gsize datasz, gsize offset, guint8 *value, GError **error) G_GNUC_NON_NULL(1); gboolean fu_firmware_strparse_uint8_safe(const gchar *data, gsize datasz, gsize offset, guint8 *value, GError **error) G_GNUC_NON_NULL(1); gboolean fu_firmware_strparse_uint16_safe(const gchar *data, gsize datasz, gsize offset, guint16 *value, GError **error) G_GNUC_NON_NULL(1); gboolean fu_firmware_strparse_uint24_safe(const gchar *data, gsize datasz, gsize offset, guint32 *value, GError **error) G_GNUC_NON_NULL(1); gboolean fu_firmware_strparse_uint32_safe(const gchar *data, gsize datasz, gsize offset, guint32 *value, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-firmware.c000066400000000000000000001632501460375044200203740ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-chunk-private.h" #include "fu-common.h" #include "fu-firmware.h" #include "fu-mem.h" #include "fu-string.h" /** * FuFirmware: * * A firmware file which can have children which represent the images within. * * See also: [class@FuDfuFirmware], [class@FuIhexFirmware], [class@FuSrecFirmware] */ typedef struct { FuFirmwareFlags flags; FuFirmware *parent; /* noref */ GPtrArray *images; /* FuFirmware */ gchar *version; guint64 version_raw; GBytes *bytes; guint8 alignment; gchar *id; gchar *filename; guint64 idx; guint64 addr; guint64 offset; gsize size; gsize size_max; guint images_max; GPtrArray *chunks; /* nullable, element-type FuChunk */ GPtrArray *patches; /* nullable, element-type FuFirmwarePatch */ } FuFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuFirmware, fu_firmware, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fu_firmware_get_instance_private(o)) enum { PROP_0, PROP_PARENT, PROP_LAST }; /** * fu_firmware_flag_to_string: * @flag: a #FuFirmwareFlags, e.g. %FU_FIRMWARE_FLAG_DEDUPE_ID * * Converts a #FuFirmwareFlags to a string. * * Returns: identifier string * * Since: 1.5.0 **/ const gchar * fu_firmware_flag_to_string(FuFirmwareFlags flag) { if (flag == FU_FIRMWARE_FLAG_NONE) return "none"; if (flag == FU_FIRMWARE_FLAG_DEDUPE_ID) return "dedupe-id"; if (flag == FU_FIRMWARE_FLAG_DEDUPE_IDX) return "dedupe-idx"; if (flag == FU_FIRMWARE_FLAG_HAS_CHECKSUM) return "has-checksum"; if (flag == FU_FIRMWARE_FLAG_HAS_VID_PID) return "has-vid-pid"; if (flag == FU_FIRMWARE_FLAG_DONE_PARSE) return "done-parse"; if (flag == FU_FIRMWARE_FLAG_HAS_STORED_SIZE) return "has-stored-size"; if (flag == FU_FIRMWARE_FLAG_ALWAYS_SEARCH) return "always-search"; if (flag == FU_FIRMWARE_FLAG_NO_AUTO_DETECTION) return "no-auto-detection"; return NULL; } /** * fu_firmware_flag_from_string: * @flag: a string, e.g. `dedupe-id` * * Converts a string to a #FuFirmwareFlags. * * Returns: enumerated value * * Since: 1.5.0 **/ FuFirmwareFlags fu_firmware_flag_from_string(const gchar *flag) { if (g_strcmp0(flag, "dedupe-id") == 0) return FU_FIRMWARE_FLAG_DEDUPE_ID; if (g_strcmp0(flag, "dedupe-idx") == 0) return FU_FIRMWARE_FLAG_DEDUPE_IDX; if (g_strcmp0(flag, "has-checksum") == 0) return FU_FIRMWARE_FLAG_HAS_CHECKSUM; if (g_strcmp0(flag, "has-vid-pid") == 0) return FU_FIRMWARE_FLAG_HAS_VID_PID; if (g_strcmp0(flag, "done-parse") == 0) return FU_FIRMWARE_FLAG_DONE_PARSE; if (g_strcmp0(flag, "has-stored-size") == 0) return FU_FIRMWARE_FLAG_HAS_STORED_SIZE; if (g_strcmp0(flag, "always-search") == 0) return FU_FIRMWARE_FLAG_ALWAYS_SEARCH; if (g_strcmp0(flag, "no-auto-detection") == 0) return FU_FIRMWARE_FLAG_NO_AUTO_DETECTION; return FU_FIRMWARE_FLAG_NONE; } typedef struct { gsize offset; GBytes *blob; } FuFirmwarePatch; static void fu_firmware_patch_free(FuFirmwarePatch *ptch) { g_bytes_unref(ptch->blob); g_free(ptch); } /** * fu_firmware_add_flag: * @firmware: a #FuFirmware * @flag: the firmware flag * * Adds a specific firmware flag to the firmware. * * Since: 1.5.0 **/ void fu_firmware_add_flag(FuFirmware *firmware, FuFirmwareFlags flag) { FuFirmwarePrivate *priv = GET_PRIVATE(firmware); g_return_if_fail(FU_IS_FIRMWARE(firmware)); priv->flags |= flag; } /** * fu_firmware_has_flag: * @firmware: a #FuFirmware * @flag: the firmware flag * * Finds if the firmware has a specific firmware flag. * * Returns: %TRUE if the flag is set * * Since: 1.5.0 **/ gboolean fu_firmware_has_flag(FuFirmware *firmware, FuFirmwareFlags flag) { FuFirmwarePrivate *priv = GET_PRIVATE(firmware); g_return_val_if_fail(FU_IS_FIRMWARE(firmware), FALSE); return (priv->flags & flag) > 0; } /** * fu_firmware_get_version: * @self: a #FuFirmware * * Gets an optional version that represents the firmware. * * Returns: a string, or %NULL * * Since: 1.3.3 **/ const gchar * fu_firmware_get_version(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); return priv->version; } /** * fu_firmware_set_version: * @self: a #FuFirmware * @version: (nullable): optional string version * * Sets an optional version that represents the firmware. * * Since: 1.3.3 **/ void fu_firmware_set_version(FuFirmware *self, const gchar *version) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); /* not changed */ if (g_strcmp0(priv->version, version) == 0) return; g_free(priv->version); priv->version = g_strdup(version); } /** * fu_firmware_get_version_raw: * @self: a #FuFirmware * * Gets an raw version that represents the firmware. This is most frequently * used when building firmware with `0x123456` in a * `firmware.builder.xml` file to avoid string splitting and sanity checks. * * Returns: an integer, or %G_MAXUINT64 for invalid * * Since: 1.5.7 **/ guint64 fu_firmware_get_version_raw(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXUINT64); return priv->version_raw; } /** * fu_firmware_set_version_raw: * @self: a #FuFirmware * @version_raw: a raw version, or %G_MAXUINT64 for invalid * * Sets an raw version that represents the firmware. * * This is optional, and is typically only used for debugging. * * Since: 1.5.7 **/ void fu_firmware_set_version_raw(FuFirmware *self, guint64 version_raw) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->version_raw = version_raw; } /** * fu_firmware_get_filename: * @self: a #FuFirmware * * Gets an optional filename that represents the image source or destination. * * Returns: a string, or %NULL * * Since: 1.6.0 **/ const gchar * fu_firmware_get_filename(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); return priv->filename; } /** * fu_firmware_set_filename: * @self: a #FuFirmware * @filename: (nullable): a string filename * * Sets an optional filename that represents the image source or destination. * * Since: 1.6.0 **/ void fu_firmware_set_filename(FuFirmware *self, const gchar *filename) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); /* not changed */ if (g_strcmp0(priv->filename, filename) == 0) return; g_free(priv->filename); priv->filename = g_strdup(filename); } /** * fu_firmware_set_id: * @self: a #FuPlugin * @id: (nullable): image ID, e.g. `config` * * Since: 1.6.0 **/ void fu_firmware_set_id(FuFirmware *self, const gchar *id) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); /* not changed */ if (g_strcmp0(priv->id, id) == 0) return; g_free(priv->id); priv->id = g_strdup(id); } /** * fu_firmware_get_id: * @self: a #FuPlugin * * Gets the image ID, typically set at construction. * * Returns: image ID, e.g. `config` * * Since: 1.6.0 **/ const gchar * fu_firmware_get_id(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); return priv->id; } /** * fu_firmware_set_addr: * @self: a #FuPlugin * @addr: integer * * Sets the base address of the image. * * Since: 1.6.0 **/ void fu_firmware_set_addr(FuFirmware *self, guint64 addr) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->addr = addr; } /** * fu_firmware_get_addr: * @self: a #FuPlugin * * Gets the base address of the image. * * Returns: integer * * Since: 1.6.0 **/ guint64 fu_firmware_get_addr(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXUINT64); return priv->addr; } /** * fu_firmware_set_offset: * @self: a #FuPlugin * @offset: integer * * Sets the base offset of the image. * * Since: 1.6.0 **/ void fu_firmware_set_offset(FuFirmware *self, guint64 offset) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->offset = offset; } /** * fu_firmware_get_offset: * @self: a #FuPlugin * * Gets the base offset of the image. * * Returns: integer * * Since: 1.6.0 **/ guint64 fu_firmware_get_offset(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXUINT64); return priv->offset; } /** * fu_firmware_get_parent: * @self: a #FuFirmware * * Gets the parent. * * Returns: (transfer none): the parent firmware, or %NULL if unset * * Since: 1.8.2 **/ FuFirmware * fu_firmware_get_parent(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); return priv->parent; } /** * fu_firmware_set_parent: * @self: a #FuFirmware * @parent: (nullable): another #FuFirmware * * Sets the parent. Only used internally. * * Since: 1.8.2 **/ void fu_firmware_set_parent(FuFirmware *self, FuFirmware *parent) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); if (priv->parent != NULL) g_object_remove_weak_pointer(G_OBJECT(priv->parent), (gpointer *)&priv->parent); if (parent != NULL) g_object_add_weak_pointer(G_OBJECT(parent), (gpointer *)&priv->parent); priv->parent = parent; } /** * fu_firmware_set_size: * @self: a #FuPlugin * @size: integer * * Sets the total size of the image, which should be the same size as the * data from fu_firmware_write(). * * Since: 1.6.0 **/ void fu_firmware_set_size(FuFirmware *self, gsize size) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->size = size; } /** * fu_firmware_get_size: * @self: a #FuPlugin * * Gets the total size of the image, which is typically the same size as the * data from fu_firmware_write(). * * If the size has not been explicitly set, and fu_firmware_set_bytes() has been * used then the size of this is used instead. * * Returns: integer * * Since: 1.6.0 **/ gsize fu_firmware_get_size(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXSIZE); if (priv->size != 0) return priv->size; if (priv->bytes != NULL) return g_bytes_get_size(priv->bytes); return 0; } /** * fu_firmware_set_size_max: * @self: a #FuPlugin * @size_max: integer, or 0 for no limit * * Sets the maximum size of the image allowed during parsing. * Implementations should query fu_firmware_get_size_max() during parsing when adding images to * ensure the limit is not exceeded. * * Since: 1.9.7 **/ void fu_firmware_set_size_max(FuFirmware *self, gsize size_max) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->size_max = size_max; } /** * fu_firmware_get_size_max: * @self: a #FuPlugin * * Gets the maximum size of the image allowed during parsing. * * Returns: integer, or 0 if not set * * Since: 1.9.7 **/ gsize fu_firmware_get_size_max(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXSIZE); return priv->size_max; } /** * fu_firmware_set_idx: * @self: a #FuPlugin * @idx: integer * * Sets the index of the image which is used for ordering. * * Since: 1.6.0 **/ void fu_firmware_set_idx(FuFirmware *self, guint64 idx) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->idx = idx; } /** * fu_firmware_get_idx: * @self: a #FuPlugin * * Gets the index of the image which is used for ordering. * * Returns: integer * * Since: 1.6.0 **/ guint64 fu_firmware_get_idx(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXUINT64); return priv->idx; } /** * fu_firmware_set_bytes: * @self: a #FuPlugin * @bytes: data blob * * Sets the contents of the image if not created with fu_firmware_new_from_bytes(). * * Since: 1.6.0 **/ void fu_firmware_set_bytes(FuFirmware *self, GBytes *bytes) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); g_return_if_fail(bytes != NULL); if (priv->bytes == bytes) return; if (priv->bytes != NULL) g_bytes_unref(priv->bytes); priv->bytes = g_bytes_ref(bytes); } /** * fu_firmware_get_bytes: * @self: a #FuPlugin * @error: (nullable): optional return location for an error * * Gets the firmware payload, which does not have any header or footer included. * * If there is more than one potential payload or image section then fu_firmware_add_image() * should be used instead. * * Returns: (transfer full): a #GBytes, or %NULL if the payload has never been set * * Since: 1.6.0 **/ GBytes * fu_firmware_get_bytes(FuFirmware *self, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); if (priv->bytes == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no payload set"); return NULL; } return g_bytes_ref(priv->bytes); } /** * fu_firmware_get_bytes_with_patches: * @self: a #FuPlugin * @error: (nullable): optional return location for an error * * Gets the firmware payload, with any defined patches applied. * * Returns: (transfer full): a #GBytes, or %NULL if the payload has never been set * * Since: 1.7.4 **/ GBytes * fu_firmware_get_bytes_with_patches(FuFirmware *self, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) buf = g_byte_array_new(); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); if (priv->bytes == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no payload set"); return NULL; } /* usual case */ if (priv->patches == NULL) return g_bytes_ref(priv->bytes); /* convert to a mutable buffer, apply each patch, aborting if the offset isn't valid */ fu_byte_array_append_bytes(buf, priv->bytes); for (guint i = 0; i < priv->patches->len; i++) { FuFirmwarePatch *ptch = g_ptr_array_index(priv->patches, i); if (!fu_memcpy_safe(buf->data, buf->len, ptch->offset, /* dst */ g_bytes_get_data(ptch->blob, NULL), g_bytes_get_size(ptch->blob), 0x0, /* src */ g_bytes_get_size(ptch->blob), error)) { g_prefix_error(error, "failed to apply patch @0x%x: ", (guint)ptch->offset); return NULL; } } /* success */ return g_bytes_new(buf->data, buf->len); } /** * fu_firmware_set_alignment: * @self: a #FuFirmware * @alignment: integer, or 0 to disable * * Sets the alignment of the firmware. * * This allows a firmware to pad to a power of 2 boundary, where @alignment * is the bit position to align to. * * Since: 1.6.0 **/ void fu_firmware_set_alignment(FuFirmware *self, guint8 alignment) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->alignment = alignment; } /** * fu_firmware_get_alignment: * @self: a #FuFirmware * * Gets the alignment of the firmware. * * This allows a firmware to pad to a power of 2 boundary, where @alignment * is the bit position to align to. * * Returns: integer * * Since: 1.6.0 **/ guint8 fu_firmware_get_alignment(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXUINT8); return priv->alignment; } /** * fu_firmware_get_chunks: * @self: a #FuFirmware * @error: (nullable): optional return location for an error * * Gets the optional image chunks. * * Returns: (transfer container) (element-type FuChunk) (nullable): chunk data, or %NULL * * Since: 1.6.0 **/ GPtrArray * fu_firmware_get_chunks(FuFirmware *self, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* set */ if (priv->chunks != NULL) return g_ptr_array_ref(priv->chunks); /* lets build something plausible */ if (priv->bytes != NULL) { g_autoptr(GPtrArray) chunks = NULL; g_autoptr(FuChunk) chk = NULL; chunks = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); chk = fu_chunk_bytes_new(priv->bytes); fu_chunk_set_idx(chk, priv->idx); fu_chunk_set_address(chk, priv->addr); g_ptr_array_add(chunks, g_steal_pointer(&chk)); return g_steal_pointer(&chunks); } /* nothing to do */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no bytes or chunks found in firmware"); return NULL; } /** * fu_firmware_add_chunk: * @self: a #FuFirmware * @chk: a #FuChunk * * Adds a chunk to the image. * * Since: 1.6.0 **/ void fu_firmware_add_chunk(FuFirmware *self, FuChunk *chk) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); g_return_if_fail(FU_IS_CHUNK(chk)); if (priv->chunks == NULL) priv->chunks = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_ptr_array_add(priv->chunks, g_object_ref(chk)); } /** * fu_firmware_get_checksum: * @self: a #FuPlugin * @csum_kind: a checksum type, e.g. %G_CHECKSUM_SHA256 * @error: (nullable): optional return location for an error * * Returns a checksum of the payload data. * * Returns: (transfer full): a checksum string, or %NULL if the checksum is not available * * Since: 1.6.0 **/ gchar * fu_firmware_get_checksum(FuFirmware *self, GChecksumType csum_kind, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); g_autoptr(GBytes) blob = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* subclassed */ if (klass->get_checksum != NULL) return klass->get_checksum(self, csum_kind, error); /* internal data */ if (priv->bytes != NULL) return g_compute_checksum_for_bytes(csum_kind, priv->bytes); /* write */ blob = fu_firmware_write(self, error); if (blob == NULL) return NULL; return g_compute_checksum_for_bytes(csum_kind, blob); } /** * fu_firmware_tokenize: * @self: a #FuFirmware * @fw: firmware blob * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: (nullable): optional return location for an error * * Tokenizes a firmware, typically breaking the firmware into records. * * Records can be enumerated using subclass-specific functionality, for example * using fu_srec_firmware_get_records(). * * Returns: %TRUE for success * * Since: 1.3.2 **/ gboolean fu_firmware_tokenize(FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(fw != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* optionally subclassed */ if (klass->tokenize != NULL) return klass->tokenize(self, fw, flags, error); return TRUE; } /** * fu_firmware_check_compatible: * @self: a #FuFirmware * @other: a #FuFirmware * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: (nullable): optional return location for an error * * Check a new firmware is compatible with the existing firmware. * * Returns: %TRUE for success * * Since: 1.8.4 **/ gboolean fu_firmware_check_compatible(FuFirmware *self, FuFirmware *other, FwupdInstallFlags flags, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(FU_IS_FIRMWARE(other), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* optionally subclassed */ if (klass->check_compatible == NULL) return TRUE; return klass->check_compatible(self, other, flags, error); } static gboolean fu_firmware_check_magic_for_offset(FuFirmware *self, GBytes *fw, gsize *offset, FwupdInstallFlags flags, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); /* not implemented */ if (klass->check_magic == NULL) return TRUE; /* fuzzing */ if (!fu_firmware_has_flag(self, FU_FIRMWARE_FLAG_ALWAYS_SEARCH) && (flags & FWUPD_INSTALL_FLAG_NO_SEARCH) > 0) { if (!klass->check_magic(self, fw, *offset, error)) return FALSE; return TRUE; } /* limit the size of firmware we search */ if (g_bytes_get_size(fw) > FU_FIRMWARE_SEARCH_MAGIC_BUFSZ_MAX) { if (!klass->check_magic(self, fw, *offset, error)) { g_prefix_error(error, "failed to search for magic as firmware size was 0x%x and " "limit was 0x%x: ", (guint)g_bytes_get_size(fw), (guint)FU_FIRMWARE_SEARCH_MAGIC_BUFSZ_MAX); return FALSE; } return TRUE; } /* increment the offset, looking for the magic */ for (gsize offset_tmp = *offset; offset_tmp < g_bytes_get_size(fw); offset_tmp++) { if (klass->check_magic(self, fw, offset_tmp, NULL)) { fu_firmware_set_offset(self, offset_tmp); *offset = offset_tmp; return TRUE; } } /* did not find what we were looking for */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "did not find magic"); return FALSE; } /** * fu_firmware_parse_full: * @self: a #FuFirmware * @fw: firmware blob * @offset: start offset, useful for ignoring a bootloader * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: (nullable): optional return location for an error * * Parses a firmware, typically breaking the firmware into images. * * Returns: %TRUE for success * * Since: 1.8.2 **/ gboolean fu_firmware_parse_full(FuFirmware *self, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(fw != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* sanity check */ if (fu_firmware_has_flag(self, FU_FIRMWARE_FLAG_DONE_PARSE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware object cannot be reused"); return FALSE; } if (g_bytes_get_size(fw) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid firmware as zero sized"); return FALSE; } if (priv->size_max > 0 && g_bytes_get_size(fw) > priv->size_max) { g_autofree gchar *sz_val = g_format_size(g_bytes_get_size(fw)); g_autofree gchar *sz_max = g_format_size(priv->size_max); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware is too large (%s, limit %s)", sz_val, sz_max); return FALSE; } /* any FuFirmware subclass that gets past this point might have allocated memory in * ->tokenize() or ->parse() and needs to be destroyed before parsing again */ fu_firmware_add_flag(self, FU_FIRMWARE_FLAG_DONE_PARSE); /* subclassed */ if (klass->tokenize != NULL) { if (!klass->tokenize(self, fw, flags, error)) return FALSE; } if (!fu_firmware_check_magic_for_offset(self, fw, &offset, flags, error)) return FALSE; /* always set by default */ if (offset == 0x0) { fu_firmware_set_bytes(self, fw); } else { g_autoptr(GBytes) fw_offset = NULL; fw_offset = fu_bytes_new_offset(fw, offset, g_bytes_get_size(fw) - offset, error); if (fw_offset == NULL) return FALSE; fu_firmware_set_bytes(self, fw_offset); } /* handled by the subclass */ if (klass->parse != NULL) return klass->parse(self, fw, offset, flags, error); /* verify alignment */ if (g_bytes_get_size(fw) % (1ull << priv->alignment) != 0) { g_autofree gchar *str = NULL; str = g_format_size_full(1ull << priv->alignment, G_FORMAT_SIZE_IEC_UNITS); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "raw firmware is not aligned to 0x%x (%s)", (guint)(1ull << priv->alignment), str); return FALSE; } /* success */ return TRUE; } /** * fu_firmware_parse: * @self: a #FuFirmware * @fw: firmware blob * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: (nullable): optional return location for an error * * Parses a firmware, typically breaking the firmware into images. * * Returns: %TRUE for success * * Since: 1.3.1 **/ gboolean fu_firmware_parse(FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) { return fu_firmware_parse_full(self, fw, 0x0, flags, error); } /** * fu_firmware_build: * @self: a #FuFirmware * @n: a Xmlb node * @error: (nullable): optional return location for an error * * Builds a firmware from an XML manifest. The manifest would typically have the * following form: * * |[ * * * 1.2.3 * * 7.8.9 * stage1 * 0x01 * stage1.bin * * * stage2 * * * * ape * 0x7 * aGVsbG8gd29ybGQ= * * * ]| * * This would be used in a build-system to merge images from generated files: * `fwupdtool firmware-build fw.builder.xml test.fw` * * Static binary content can be specified in the `/` section and * is encoded as base64 text if not empty. * * Additionally, extra nodes can be included under nested `` objects * which can be parsed by the subclassed objects. You should verify the * subclassed object `FuFirmware->build` vfunc for the specific additional * options supported. * * Plugins should manually g_type_ensure() subclassed image objects if not * constructed as part of the plugin fu_plugin_init() or fu_plugin_setup() * functions. * * Returns: %TRUE for success * * Since: 1.5.0 **/ gboolean fu_firmware_build(FuFirmware *self, XbNode *n, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); const gchar *tmp; guint64 tmpval; guint64 version_raw; g_autoptr(GPtrArray) chunks = NULL; g_autoptr(GPtrArray) xb_images = NULL; g_autoptr(XbNode) data = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(XB_IS_NODE(n), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* set attributes */ tmp = xb_node_query_text(n, "version", NULL); if (tmp != NULL) fu_firmware_set_version(self, tmp); version_raw = xb_node_query_text_as_uint(n, "version_raw", NULL); if (version_raw != G_MAXUINT64) fu_firmware_set_version_raw(self, version_raw); tmp = xb_node_query_text(n, "id", NULL); if (tmp != NULL) fu_firmware_set_id(self, tmp); tmpval = xb_node_query_text_as_uint(n, "idx", NULL); if (tmpval != G_MAXUINT64) fu_firmware_set_idx(self, tmpval); tmpval = xb_node_query_text_as_uint(n, "addr", NULL); if (tmpval != G_MAXUINT64) fu_firmware_set_addr(self, tmpval); tmpval = xb_node_query_text_as_uint(n, "offset", NULL); if (tmpval != G_MAXUINT64) fu_firmware_set_offset(self, tmpval); tmpval = xb_node_query_text_as_uint(n, "size", NULL); if (tmpval != G_MAXUINT64) fu_firmware_set_size(self, tmpval); tmpval = xb_node_query_text_as_uint(n, "size_max", NULL); if (tmpval != G_MAXUINT64) fu_firmware_set_size_max(self, tmpval); tmpval = xb_node_query_text_as_uint(n, "alignment", NULL); if (tmpval != G_MAXUINT64) { if (tmpval > FU_FIRMWARE_ALIGNMENT_2G) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "0x%x invalid, maximum is 0x%x", (guint)tmpval, (guint)FU_FIRMWARE_ALIGNMENT_2G); return FALSE; } fu_firmware_set_alignment(self, (guint8)tmpval); } tmp = xb_node_query_text(n, "filename", NULL); if (tmp != NULL) { g_autoptr(GBytes) blob = NULL; blob = fu_bytes_get_contents(tmp, error); if (blob == NULL) return FALSE; fu_firmware_set_bytes(self, blob); fu_firmware_set_filename(self, tmp); } data = xb_node_query_first(n, "data", NULL); if (data != NULL) { guint64 sz = xb_node_get_attr_as_uint(data, "size"); g_autoptr(GBytes) blob = NULL; /* base64 encoded data */ if (xb_node_get_text(data) != NULL) { gsize bufsz = 0; g_autofree guchar *buf = NULL; buf = g_base64_decode(xb_node_get_text(data), &bufsz); blob = g_bytes_new(buf, bufsz); } else { blob = g_bytes_new(NULL, 0); } /* padding is optional */ if (sz == 0 || sz == G_MAXUINT64) { fu_firmware_set_bytes(self, blob); } else { g_autoptr(GBytes) blob_padded = fu_bytes_pad(blob, (gsize)sz); fu_firmware_set_bytes(self, blob_padded); } } /* optional chunks */ chunks = xb_node_query(n, "chunks/chunk", 0, NULL); if (chunks != NULL) { for (guint i = 0; i < chunks->len; i++) { XbNode *c = g_ptr_array_index(chunks, i); g_autoptr(FuChunk) chk = fu_chunk_bytes_new(NULL); fu_chunk_set_idx(chk, i); if (!fu_chunk_build(chk, c, error)) return FALSE; fu_firmware_add_chunk(self, chk); } } /* parse images */ xb_images = xb_node_query(n, "firmware", 0, NULL); if (xb_images != NULL) { for (guint i = 0; i < xb_images->len; i++) { XbNode *xb_image = g_ptr_array_index(xb_images, i); g_autoptr(FuFirmware) img = NULL; tmp = xb_node_get_attr(xb_image, "gtype"); if (tmp != NULL) { GType gtype = g_type_from_name(tmp); if (gtype == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GType %s not registered", tmp); return FALSE; } img = g_object_new(gtype, NULL); } else { img = fu_firmware_new(); } if (!fu_firmware_add_image_full(self, img, error)) return FALSE; if (!fu_firmware_build(img, xb_image, error)) return FALSE; } } /* subclassed */ if (klass->build != NULL) { if (!klass->build(self, n, error)) return FALSE; } /* success */ return TRUE; } /** * fu_firmware_build_from_xml: * @self: a #FuFirmware * @xml: XML text * @error: (nullable): optional return location for an error * * Builds a firmware from an XML manifest. The manifest would typically have the * following form: * * |[ * * * 1.2.3 * * 7.8.9 * stage1 * 0x01 * stage1.bin * * * stage2 * * * * ape * 0x7 * aGVsbG8gd29ybGQ= * * * ]| * * This would be used in a build-system to merge images from generated files: * `fwupdtool firmware-build fw.builder.xml test.fw` * * Static binary content can be specified in the `/` section and * is encoded as base64 text if not empty. * * Additionally, extra nodes can be included under nested `` objects * which can be parsed by the subclassed objects. You should verify the * subclassed object `FuFirmware->build` vfunc for the specific additional * options supported. * * Plugins should manually g_type_ensure() subclassed image objects if not * constructed as part of the plugin fu_plugin_init() or fu_plugin_setup() * functions. * * Returns: %TRUE for success * * Since: 1.6.0 **/ gboolean fu_firmware_build_from_xml(FuFirmware *self, const gchar *xml, GError **error) { g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; /* parse XML */ if (!xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, error)) { g_prefix_error(error, "could not parse XML: "); return FALSE; } xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; /* create FuFirmware of specific GType */ n = xb_silo_query_first(silo, "firmware", error); if (n == NULL) return FALSE; return fu_firmware_build(self, n, error); } /** * fu_firmware_parse_file: * @self: a #FuFirmware * @file: a file * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: (nullable): optional return location for an error * * Parses a firmware file, typically breaking the firmware into images. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_firmware_parse_file(FuFirmware *self, GFile *file, FwupdInstallFlags flags, GError **error) { gchar *buf = NULL; gsize bufsz = 0; g_autoptr(GBytes) fw = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(G_IS_FILE(file), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!g_file_load_contents(file, NULL, &buf, &bufsz, NULL, error)) return FALSE; fw = g_bytes_new_take(buf, bufsz); return fu_firmware_parse(self, fw, flags, error); } /** * fu_firmware_write: * @self: a #FuFirmware * @error: (nullable): optional return location for an error * * Writes a firmware, typically packing the images into a binary blob. * * Returns: (transfer full): a data blob * * Since: 1.3.1 **/ GBytes * fu_firmware_write(FuFirmware *self, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* subclassed */ if (klass->write != NULL) { g_autoptr(GByteArray) buf = klass->write(self, error); if (buf == NULL) return NULL; return g_bytes_new(buf->data, buf->len); } /* just add default blob */ return fu_firmware_get_bytes_with_patches(self, error); } /** * fu_firmware_add_patch: * @self: a #FuFirmware * @offset: an address smaller than fu_firmware_get_size() * @blob: (not nullable): bytes to replace * * Adds a byte patch at a specific offset. If a patch already exists at the specified address then * it is replaced. * * If the @address is larger than the size of the image then an error is returned. * * Since: 1.7.4 **/ void fu_firmware_add_patch(FuFirmware *self, gsize offset, GBytes *blob) { FuFirmwarePrivate *priv = GET_PRIVATE(self); FuFirmwarePatch *ptch; g_return_if_fail(FU_IS_FIRMWARE(self)); g_return_if_fail(blob != NULL); /* ensure exists */ if (priv->patches == NULL) { priv->patches = g_ptr_array_new_with_free_func((GDestroyNotify)fu_firmware_patch_free); } /* find existing of exact same size */ for (guint i = 0; i < priv->patches->len; i++) { ptch = g_ptr_array_index(priv->patches, i); if (ptch->offset == offset && g_bytes_get_size(ptch->blob) == g_bytes_get_size(blob)) { g_bytes_unref(ptch->blob); ptch->blob = g_bytes_ref(blob); return; } } /* add new */ ptch = g_new0(FuFirmwarePatch, 1); ptch->offset = offset; ptch->blob = g_bytes_ref(blob); g_ptr_array_add(priv->patches, ptch); } /** * fu_firmware_write_chunk: * @self: a #FuFirmware * @address: an address smaller than fu_firmware_get_addr() * @chunk_sz_max: the size of the new chunk * @error: (nullable): optional return location for an error * * Gets a block of data from the image. If the contents of the image is * smaller than the requested chunk size then the #GBytes will be smaller * than @chunk_sz_max. Use fu_bytes_pad() if padding is required. * * If the @address is larger than the size of the image then an error is returned. * * Returns: (transfer full): a #GBytes, or %NULL * * Since: 1.6.0 **/ GBytes * fu_firmware_write_chunk(FuFirmware *self, guint64 address, guint64 chunk_sz_max, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); gsize chunk_left; guint64 offset; g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* check address requested is larger than base address */ if (address < priv->addr) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "requested address 0x%x less than base address 0x%x", (guint)address, (guint)priv->addr); return NULL; } /* offset into data */ offset = address - priv->addr; if (offset > g_bytes_get_size(priv->bytes)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "offset 0x%x larger than data size 0x%x", (guint)offset, (guint)g_bytes_get_size(priv->bytes)); return NULL; } /* if we have less data than requested */ chunk_left = g_bytes_get_size(priv->bytes) - offset; if (chunk_sz_max > chunk_left) { return fu_bytes_new_offset(priv->bytes, offset, chunk_left, error); } /* check chunk */ return fu_bytes_new_offset(priv->bytes, offset, chunk_sz_max, error); } /** * fu_firmware_write_file: * @self: a #FuFirmware * @file: a file * @error: (nullable): optional return location for an error * * Writes a firmware, typically packing the images into a binary blob. * * Returns: %TRUE for success * * Since: 1.3.3 **/ gboolean fu_firmware_write_file(FuFirmware *self, GFile *file, GError **error) { g_autoptr(GBytes) blob = NULL; g_autoptr(GFile) parent = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(G_IS_FILE(file), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); blob = fu_firmware_write(self, error); if (blob == NULL) return FALSE; parent = g_file_get_parent(file); if (!g_file_query_exists(parent, NULL)) { if (!g_file_make_directory_with_parents(parent, NULL, error)) return FALSE; } return g_file_replace_contents(file, g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, error); } /** * fu_firmware_add_image_full: * @self: a #FuPlugin * @img: a child firmware image * @error: (nullable): optional return location for an error * * Adds an image to the firmware. This method will fail if the number of images would be * above the limit set by fu_firmware_set_images_max(). * * If %FU_FIRMWARE_FLAG_DEDUPE_ID is set, an image with the same ID is already * present it is replaced. * * Returns: %TRUE if the image was added * * Since: 1.9.3 **/ gboolean fu_firmware_add_image_full(FuFirmware *self, FuFirmware *img, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(FU_IS_FIRMWARE(img), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* dedupe */ for (guint i = 0; i < priv->images->len; i++) { FuFirmware *img_tmp = g_ptr_array_index(priv->images, i); if (priv->flags & FU_FIRMWARE_FLAG_DEDUPE_ID) { if (g_strcmp0(fu_firmware_get_id(img_tmp), fu_firmware_get_id(img)) == 0) { g_ptr_array_remove_index(priv->images, i); break; } } if (priv->flags & FU_FIRMWARE_FLAG_DEDUPE_IDX) { if (fu_firmware_get_idx(img_tmp) == fu_firmware_get_idx(img)) { g_ptr_array_remove_index(priv->images, i); break; } } } /* sanity check */ if (priv->images_max > 0 && priv->images->len >= priv->images_max) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "too many images, limit is %u", priv->images_max); return FALSE; } g_ptr_array_add(priv->images, g_object_ref(img)); /* set the other way around */ fu_firmware_set_parent(img, self); /* success */ return TRUE; } /** * fu_firmware_add_image: * @self: a #FuPlugin * @img: a child firmware image * * Adds an image to the firmware. * * NOTE: If adding images in a loop of any kind then fu_firmware_add_image_full() should be used * instead, and fu_firmware_set_images_max() should be set before adding images. * * If %FU_FIRMWARE_FLAG_DEDUPE_ID is set, an image with the same ID is already * present it is replaced. * * Since: 1.3.1 **/ void fu_firmware_add_image(FuFirmware *self, FuFirmware *img) { g_autoptr(GError) error_local = NULL; g_return_if_fail(FU_IS_FIRMWARE(self)); g_return_if_fail(FU_IS_FIRMWARE(img)); if (!fu_firmware_add_image_full(self, img, &error_local)) g_critical("failed to add image: %s", error_local->message); } /** * fu_firmware_set_images_max: * @self: a #FuPlugin * @images_max: integer, or 0 for unlimited * * Sets the maximum number of images this container can hold. * * Since: 1.9.3 **/ void fu_firmware_set_images_max(FuFirmware *self, guint images_max) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_FIRMWARE(self)); priv->images_max = images_max; } /** * fu_firmware_get_images_max: * @self: a #FuPlugin * * Gets the maximum number of images this container can hold. * * Returns: integer, or 0 for unlimited. * * Since: 1.9.3 **/ guint fu_firmware_get_images_max(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), G_MAXUINT); return priv->images_max; } /** * fu_firmware_remove_image: * @self: a #FuPlugin * @img: a child firmware image * @error: (nullable): optional return location for an error * * Remove an image from the firmware. * * Returns: %TRUE if the image was removed * * Since: 1.5.0 **/ gboolean fu_firmware_remove_image(FuFirmware *self, FuFirmware *img, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(FU_IS_FIRMWARE(img), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (g_ptr_array_remove(priv->images, img)) return TRUE; /* did not exist */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "image %s not found in firmware", fu_firmware_get_id(img)); return FALSE; } /** * fu_firmware_remove_image_by_idx: * @self: a #FuPlugin * @idx: index * @error: (nullable): optional return location for an error * * Removes the first image from the firmware matching the index. * * Returns: %TRUE if an image was removed * * Since: 1.5.0 **/ gboolean fu_firmware_remove_image_by_idx(FuFirmware *self, guint64 idx, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_autoptr(FuFirmware) img = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); img = fu_firmware_get_image_by_idx(self, idx, error); if (img == NULL) return FALSE; g_ptr_array_remove(priv->images, img); return TRUE; } /** * fu_firmware_remove_image_by_id: * @self: a #FuPlugin * @id: (nullable): image ID, e.g. `config` * @error: (nullable): optional return location for an error * * Removes the first image from the firmware matching the ID. * * Returns: %TRUE if an image was removed * * Since: 1.5.0 **/ gboolean fu_firmware_remove_image_by_id(FuFirmware *self, const gchar *id, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_autoptr(FuFirmware) img = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); img = fu_firmware_get_image_by_id(self, id, error); if (img == NULL) return FALSE; g_ptr_array_remove(priv->images, img); return TRUE; } /** * fu_firmware_get_images: * @self: a #FuFirmware * * Returns all the images in the firmware. * * Returns: (transfer container) (element-type FuFirmware): images * * Since: 1.3.1 **/ GPtrArray * fu_firmware_get_images(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_autoptr(GPtrArray) imgs = NULL; g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); imgs = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < priv->images->len; i++) { FuFirmware *img = g_ptr_array_index(priv->images, i); g_ptr_array_add(imgs, g_object_ref(img)); } return g_steal_pointer(&imgs); } /** * fu_firmware_get_image_by_id: * @self: a #FuPlugin * @id: (nullable): image ID, e.g. `config` * @error: (nullable): optional return location for an error * * Gets the firmware image using the image ID. * * Returns: (transfer full): a #FuFirmware, or %NULL if the image is not found * * Since: 1.3.1 **/ FuFirmware * fu_firmware_get_image_by_id(FuFirmware *self, const gchar *id, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); for (guint i = 0; i < priv->images->len; i++) { FuFirmware *img = g_ptr_array_index(priv->images, i); if (g_strcmp0(fu_firmware_get_id(img), id) == 0) return g_object_ref(img); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no image id %s found in firmware", id); return NULL; } /** * fu_firmware_get_image_by_id_bytes: * @self: a #FuPlugin * @id: (nullable): image ID, e.g. `config` * @error: (nullable): optional return location for an error * * Gets the firmware image bytes using the image ID. * * Returns: (transfer full): a #GBytes of a #FuFirmware, or %NULL if the image is not found * * Since: 1.3.1 **/ GBytes * fu_firmware_get_image_by_id_bytes(FuFirmware *self, const gchar *id, GError **error) { g_autoptr(FuFirmware) img = fu_firmware_get_image_by_id(self, id, error); if (img == NULL) return NULL; return fu_firmware_write(img, error); } /** * fu_firmware_get_image_by_idx: * @self: a #FuPlugin * @idx: image index * @error: (nullable): optional return location for an error * * Gets the firmware image using the image index. * * Returns: (transfer full): a #FuFirmware, or %NULL if the image is not found * * Since: 1.3.1 **/ FuFirmware * fu_firmware_get_image_by_idx(FuFirmware *self, guint64 idx, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); for (guint i = 0; i < priv->images->len; i++) { FuFirmware *img = g_ptr_array_index(priv->images, i); if (fu_firmware_get_idx(img) == idx) return g_object_ref(img); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no image idx %" G_GUINT64_FORMAT " found in firmware", idx); return NULL; } /** * fu_firmware_get_image_by_checksum: * @self: a #FuPlugin * @checksum: checksum string of any format * @error: (nullable): optional return location for an error * * Gets the firmware image using the image checksum. The checksum type is guessed * based on the length of the input string. * * Returns: (transfer full): a #FuFirmware, or %NULL if the image is not found * * Since: 1.5.5 **/ FuFirmware * fu_firmware_get_image_by_checksum(FuFirmware *self, const gchar *checksum, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); GChecksumType csum_kind; g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(checksum != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); csum_kind = fwupd_checksum_guess_kind(checksum); for (guint i = 0; i < priv->images->len; i++) { FuFirmware *img = g_ptr_array_index(priv->images, i); g_autofree gchar *checksum_tmp = NULL; /* if this expensive then the subclassed FuFirmware can * cache the result as required */ checksum_tmp = fu_firmware_get_checksum(img, csum_kind, error); if (checksum_tmp == NULL) return NULL; if (g_strcmp0(checksum_tmp, checksum) == 0) return g_object_ref(img); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no image with checksum %s found in firmware", checksum); return NULL; } /** * fu_firmware_get_image_by_idx_bytes: * @self: a #FuPlugin * @idx: image index * @error: (nullable): optional return location for an error * * Gets the firmware image bytes using the image index. * * Returns: (transfer full): a #GBytes of a #FuFirmware, or %NULL if the image is not found * * Since: 1.3.1 **/ GBytes * fu_firmware_get_image_by_idx_bytes(FuFirmware *self, guint64 idx, GError **error) { g_autoptr(FuFirmware) img = fu_firmware_get_image_by_idx(self, idx, error); if (img == NULL) return NULL; return fu_firmware_write(img, error); } /** * fu_firmware_get_image_by_gtype_bytes: * @self: a #FuPlugin * @gtype: an image #GType * @error: (nullable): optional return location for an error * * Gets the firmware image bytes using the image #GType. * * Returns: (transfer full): a #GBytes of a #FuFirmware, or %NULL if the image is not found * * Since: 1.9.3 **/ GBytes * fu_firmware_get_image_by_gtype_bytes(FuFirmware *self, GType gtype, GError **error) { g_autoptr(FuFirmware) img = fu_firmware_get_image_by_gtype(self, gtype, error); if (img == NULL) return NULL; return fu_firmware_write(img, error); } /** * fu_firmware_get_image_by_gtype: * @self: a #FuPlugin * @gtype: an image #GType * @error: (nullable): optional return location for an error * * Gets the firmware image using the image #GType. * * Returns: (transfer full): a #FuFirmware, or %NULL if the image is not found * * Since: 1.9.3 **/ FuFirmware * fu_firmware_get_image_by_gtype(FuFirmware *self, GType gtype, GError **error) { FuFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_FIRMWARE(self), NULL); g_return_val_if_fail(gtype != G_TYPE_INVALID, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); for (guint i = 0; i < priv->images->len; i++) { FuFirmware *img = g_ptr_array_index(priv->images, i); if (g_type_is_a(G_OBJECT_TYPE(img), gtype)) return g_object_ref(img); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no image GType %s found in firmware", g_type_name(gtype)); return NULL; } /** * fu_firmware_export: * @self: a #FuFirmware * @flags: firmware export flags, e.g. %FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG * @bn: a Xmlb builder node * * This allows us to build an XML object for the nested firmware. * * Since: 1.6.0 **/ void fu_firmware_export(FuFirmware *self, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuFirmwareClass *klass = FU_FIRMWARE_GET_CLASS(self); FuFirmwarePrivate *priv = GET_PRIVATE(self); const gchar *gtypestr = G_OBJECT_TYPE_NAME(self); /* object */ if (g_strcmp0(gtypestr, "FuFirmware") != 0) xb_builder_node_set_attr(bn, "gtype", gtypestr); /* subclassed type */ if (priv->flags != FU_FIRMWARE_FLAG_NONE) { g_autoptr(GString) tmp = g_string_new(""); for (guint i = 0; i < 64; i++) { guint64 flag = (guint64)1 << i; if (flag == FU_FIRMWARE_FLAG_DONE_PARSE) continue; if ((priv->flags & flag) == 0) continue; g_string_append_printf(tmp, "%s|", fu_firmware_flag_to_string(flag)); } if (tmp->len > 0) { g_string_truncate(tmp, tmp->len - 1); fu_xmlb_builder_insert_kv(bn, "flags", tmp->str); } } fu_xmlb_builder_insert_kv(bn, "id", priv->id); fu_xmlb_builder_insert_kx(bn, "idx", priv->idx); fu_xmlb_builder_insert_kv(bn, "version", priv->version); fu_xmlb_builder_insert_kx(bn, "version_raw", priv->version_raw); fu_xmlb_builder_insert_kx(bn, "addr", priv->addr); fu_xmlb_builder_insert_kx(bn, "offset", priv->offset); fu_xmlb_builder_insert_kx(bn, "alignment", priv->alignment); fu_xmlb_builder_insert_kx(bn, "size", priv->size); fu_xmlb_builder_insert_kx(bn, "size_max", priv->size_max); fu_xmlb_builder_insert_kv(bn, "filename", priv->filename); if (priv->bytes != NULL) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(priv->bytes, &bufsz); g_autofree gchar *datastr = NULL; g_autofree gchar *dataszstr = g_strdup_printf("0x%x", (guint)bufsz); if (flags & FU_FIRMWARE_EXPORT_FLAG_ASCII_DATA) { datastr = fu_memstrsafe(buf, bufsz, 0x0, MIN(bufsz, 16), NULL); } else { datastr = g_base64_encode(buf, bufsz); } xb_builder_node_insert_text(bn, "data", datastr, "size", dataszstr, NULL); } /* chunks */ if (priv->chunks != NULL && priv->chunks->len > 0) { g_autoptr(XbBuilderNode) bp = xb_builder_node_insert(bn, "chunks", NULL); for (guint i = 0; i < priv->chunks->len; i++) { FuChunk *chk = g_ptr_array_index(priv->chunks, i); g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bp, "chunk", NULL); fu_chunk_export(chk, flags, bc); } } /* vfunc */ if (klass->export != NULL) klass->export(self, flags, bn); /* children */ if (priv->images->len > 0) { for (guint i = 0; i < priv->images->len; i++) { FuFirmware *img = g_ptr_array_index(priv->images, i); g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "firmware", NULL); fu_firmware_export(img, flags, bc); } } } /** * fu_firmware_export_to_xml: * @self: a #FuFirmware * @flags: firmware export flags, e.g. %FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG * @error: (nullable): optional return location for an error * * This allows us to build an XML object for the nested firmware. * * Returns: a string value, or %NULL for invalid. * * Since: 1.6.0 **/ gchar * fu_firmware_export_to_xml(FuFirmware *self, FuFirmwareExportFlags flags, GError **error) { g_autoptr(XbBuilderNode) bn = xb_builder_node_new("firmware"); fu_firmware_export(self, flags, bn); return xb_builder_node_export(bn, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY | XB_NODE_EXPORT_FLAG_FORMAT_INDENT, error); } /** * fu_firmware_to_string: * @self: a #FuFirmware * * This allows us to easily print the object. * * Returns: a string value, or %NULL for invalid. * * Since: 1.3.1 **/ gchar * fu_firmware_to_string(FuFirmware *self) { g_autoptr(XbBuilderNode) bn = xb_builder_node_new("firmware"); fu_firmware_export(self, FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG | FU_FIRMWARE_EXPORT_FLAG_ASCII_DATA, bn); return xb_builder_node_export(bn, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY | XB_NODE_EXPORT_FLAG_FORMAT_INDENT, NULL); } static void fu_firmware_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuFirmware *self = FU_FIRMWARE(object); FuFirmwarePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_PARENT: g_value_set_object(value, priv->parent); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_firmware_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuFirmware *self = FU_FIRMWARE(object); switch (prop_id) { case PROP_PARENT: fu_firmware_set_parent(self, g_value_get_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_firmware_init(FuFirmware *self) { FuFirmwarePrivate *priv = GET_PRIVATE(self); priv->images = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); } static void fu_firmware_finalize(GObject *object) { FuFirmware *self = FU_FIRMWARE(object); FuFirmwarePrivate *priv = GET_PRIVATE(self); g_free(priv->version); g_free(priv->id); g_free(priv->filename); if (priv->bytes != NULL) g_bytes_unref(priv->bytes); if (priv->chunks != NULL) g_ptr_array_unref(priv->chunks); if (priv->patches != NULL) g_ptr_array_unref(priv->patches); if (priv->parent != NULL) g_object_remove_weak_pointer(G_OBJECT(priv->parent), (gpointer *)&priv->parent); g_ptr_array_unref(priv->images); G_OBJECT_CLASS(fu_firmware_parent_class)->finalize(object); } static void fu_firmware_class_init(FuFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->finalize = fu_firmware_finalize; object_class->get_property = fu_firmware_get_property; object_class->set_property = fu_firmware_set_property; /** * FuFirmware:parent: * * The firmware parent. * * Since: 1.8.2 */ pspec = g_param_spec_object("parent", NULL, NULL, FU_TYPE_FIRMWARE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PARENT, pspec); } /** * fu_firmware_new: * * Creates an empty firmware object. * * Returns: a #FuFirmware * * Since: 1.3.1 **/ FuFirmware * fu_firmware_new(void) { FuFirmware *self = g_object_new(FU_TYPE_FIRMWARE, NULL); return FU_FIRMWARE(self); } /** * fu_firmware_new_from_bytes: * @fw: firmware blob image * * Creates a firmware object with the provided image set as default. * * Returns: a #FuFirmware * * Since: 1.3.1 **/ FuFirmware * fu_firmware_new_from_bytes(GBytes *fw) { FuFirmware *self = fu_firmware_new(); fu_firmware_set_bytes(self, fw); return self; } /** * fu_firmware_new_from_gtypes: * @fw: firmware blob * @offset: start offset, useful for ignoring a bootloader * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM * @error: (nullable): optional return location for an error * @...: an array of #GTypes, ending with %G_TYPE_INVALID * * Tries to parse the firmware with each #GType in order. * * Returns: (transfer full) (nullable): a #FuFirmware, or %NULL * * Since: 1.5.6 **/ FuFirmware * fu_firmware_new_from_gtypes(GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error, ...) { va_list args; g_autoptr(GArray) gtypes = g_array_new(FALSE, FALSE, sizeof(GType)); g_autoptr(GError) error_all = NULL; g_return_val_if_fail(fw != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* create array of GTypes */ va_start(args, error); while (TRUE) { GType gtype = va_arg(args, GType); if (gtype == G_TYPE_INVALID) break; g_array_append_val(gtypes, gtype); } va_end(args); /* invalid */ if (gtypes->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no GTypes specified"); return NULL; } /* try each GType in turn */ for (guint i = 0; i < gtypes->len; i++) { GType gtype = g_array_index(gtypes, GType, i); g_autoptr(FuFirmware) firmware = g_object_new(gtype, NULL); g_autoptr(GError) error_local = NULL; if (!fu_firmware_parse_full(firmware, fw, offset, flags, &error_local)) { if (error_all == NULL) { g_propagate_error(&error_all, g_steal_pointer(&error_local)); } else { g_prefix_error(&error_all, "%s: ", error_local->message); } continue; } return g_steal_pointer(&firmware); } /* failed */ g_propagate_error(error, g_steal_pointer(&error_all)); return NULL; } fwupd-1.9.16/libfwupdplugin/fu-firmware.h000066400000000000000000000265161460375044200204040ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-chunk.h" #include "fu-firmware.h" #define FU_TYPE_FIRMWARE (fu_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuFirmware, fu_firmware, FU, FIRMWARE, GObject) /** * FU_FIRMWARE_EXPORT_FLAG_NONE: * * No flags set. * * Since: 1.6.0 **/ #define FU_FIRMWARE_EXPORT_FLAG_NONE (0u) /** * FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG: * * Include debug information when exporting. * * Since: 1.6.0 **/ #define FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG (1u << 0) /** * FU_FIRMWARE_EXPORT_FLAG_ASCII_DATA: * * Write the data as UTF-8 strings. * * Since: 1.6.0 **/ #define FU_FIRMWARE_EXPORT_FLAG_ASCII_DATA (1u << 1) /** * FuFirmwareExportFlags: * * The firmware export flags. **/ typedef guint64 FuFirmwareExportFlags; struct _FuFirmwareClass { GObjectClass parent_class; gboolean (*parse)(FuFirmware *self, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; GByteArray *(*write)(FuFirmware *self, GError **error)G_GNUC_WARN_UNUSED_RESULT; void (*export)(FuFirmware *self, FuFirmwareExportFlags flags, XbBuilderNode *bn); gboolean (*tokenize)(FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; gboolean (*build)(FuFirmware *self, XbNode *n, GError **error) G_GNUC_WARN_UNUSED_RESULT; gchar *(*get_checksum)(FuFirmware *self, GChecksumType csum_kind, GError **error)G_GNUC_WARN_UNUSED_RESULT; gboolean (*check_magic)(FuFirmware *self, GBytes *fw, gsize offset, GError **error); gboolean (*check_compatible)(FuFirmware *self, FuFirmware *other, FwupdInstallFlags flags, GError **error); }; /** * FU_FIRMWARE_FLAG_NONE: * * No flags set. * * Since: 1.5.0 **/ #define FU_FIRMWARE_FLAG_NONE (0u) /** * FU_FIRMWARE_FLAG_DEDUPE_ID: * * Dedupe images by ID. * * Since: 1.5.0 **/ #define FU_FIRMWARE_FLAG_DEDUPE_ID (1u << 0) /** * FU_FIRMWARE_FLAG_DEDUPE_IDX: * * Dedupe images by IDX. * * Since: 1.5.0 **/ #define FU_FIRMWARE_FLAG_DEDUPE_IDX (1u << 1) /** * FU_FIRMWARE_FLAG_HAS_CHECKSUM: * * Has a CRC or checksum to test internal consistency. * * Since: 1.5.6 **/ #define FU_FIRMWARE_FLAG_HAS_CHECKSUM (1u << 2) /** * FU_FIRMWARE_FLAG_HAS_VID_PID: * * Has a vendor or product ID in the firmware. * * Since: 1.5.6 **/ #define FU_FIRMWARE_FLAG_HAS_VID_PID (1u << 3) /** * FU_FIRMWARE_FLAG_DONE_PARSE: * * The firmware object has been used by fu_firmware_parse_full(). * * Since: 1.7.3 **/ #define FU_FIRMWARE_FLAG_DONE_PARSE (1u << 4) /** * FU_FIRMWARE_FLAG_HAS_STORED_SIZE: * * Encodes the image size in the firmware. * * Since: 1.8.2 **/ #define FU_FIRMWARE_FLAG_HAS_STORED_SIZE (1u << 5) /** * FU_FIRMWARE_FLAG_ALWAYS_SEARCH: * * Always searches for magic regardless of the install flags. * This is useful for firmware that always has an *unparsed* variable-length * header. * * Since: 1.8.6 **/ #define FU_FIRMWARE_FLAG_ALWAYS_SEARCH (1u << 6) /** * FU_FIRMWARE_FLAG_NO_AUTO_DETECTION: * * Do not use this firmware type when auto-detecting firmware. * This should be used when there is no valid signature or CRC to check validity when parsing. * * Since: 1.9.3 **/ #define FU_FIRMWARE_FLAG_NO_AUTO_DETECTION (1u << 7) /** * FuFirmwareFlags: * * The firmware flags. **/ typedef guint64 FuFirmwareFlags; /** * FU_FIRMWARE_ID_PAYLOAD: * * The usual firmware ID string for the payload. * * Since: 1.6.0 **/ #define FU_FIRMWARE_ID_PAYLOAD "payload" /** * FU_FIRMWARE_ID_SIGNATURE: * * The usual firmware ID string for the signature. * * Since: 1.6.0 **/ #define FU_FIRMWARE_ID_SIGNATURE "signature" /** * FU_FIRMWARE_ID_HEADER: * * The usual firmware ID string for the header. * * Since: 1.6.0 **/ #define FU_FIRMWARE_ID_HEADER "header" #define FU_FIRMWARE_ALIGNMENT_1 0x00 #define FU_FIRMWARE_ALIGNMENT_2 0x01 #define FU_FIRMWARE_ALIGNMENT_4 0x02 #define FU_FIRMWARE_ALIGNMENT_8 0x03 #define FU_FIRMWARE_ALIGNMENT_16 0x04 #define FU_FIRMWARE_ALIGNMENT_32 0x05 #define FU_FIRMWARE_ALIGNMENT_64 0x06 #define FU_FIRMWARE_ALIGNMENT_128 0x07 #define FU_FIRMWARE_ALIGNMENT_256 0x08 #define FU_FIRMWARE_ALIGNMENT_512 0x09 #define FU_FIRMWARE_ALIGNMENT_1K 0x0A #define FU_FIRMWARE_ALIGNMENT_2K 0x0B #define FU_FIRMWARE_ALIGNMENT_4K 0x0C #define FU_FIRMWARE_ALIGNMENT_8K 0x0D #define FU_FIRMWARE_ALIGNMENT_16K 0x0E #define FU_FIRMWARE_ALIGNMENT_32K 0x0F #define FU_FIRMWARE_ALIGNMENT_64K 0x10 #define FU_FIRMWARE_ALIGNMENT_128K 0x11 #define FU_FIRMWARE_ALIGNMENT_256K 0x12 #define FU_FIRMWARE_ALIGNMENT_512K 0x13 #define FU_FIRMWARE_ALIGNMENT_1M 0x14 #define FU_FIRMWARE_ALIGNMENT_2M 0x15 #define FU_FIRMWARE_ALIGNMENT_4M 0x16 #define FU_FIRMWARE_ALIGNMENT_8M 0x17 #define FU_FIRMWARE_ALIGNMENT_16M 0x18 #define FU_FIRMWARE_ALIGNMENT_32M 0x19 #define FU_FIRMWARE_ALIGNMENT_64M 0x1A #define FU_FIRMWARE_ALIGNMENT_128M 0x1B #define FU_FIRMWARE_ALIGNMENT_256M 0x1C #define FU_FIRMWARE_ALIGNMENT_512M 0x1D #define FU_FIRMWARE_ALIGNMENT_1G 0x1E #define FU_FIRMWARE_ALIGNMENT_2G 0x1F #define FU_FIRMWARE_ALIGNMENT_4G 0x20 #define FU_FIRMWARE_SEARCH_MAGIC_BUFSZ_MAX (32 * 1024 * 1024) const gchar * fu_firmware_flag_to_string(FuFirmwareFlags flag); FuFirmwareFlags fu_firmware_flag_from_string(const gchar *flag); FuFirmware * fu_firmware_new(void); FuFirmware * fu_firmware_new_from_bytes(GBytes *fw); FuFirmware * fu_firmware_new_from_gtypes(GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error, ...) G_GNUC_NON_NULL(1); gchar * fu_firmware_to_string(FuFirmware *self) G_GNUC_NON_NULL(1); void fu_firmware_export(FuFirmware *self, FuFirmwareExportFlags flags, XbBuilderNode *bn) G_GNUC_NON_NULL(1, 3); gchar * fu_firmware_export_to_xml(FuFirmware *self, FuFirmwareExportFlags flags, GError **error) G_GNUC_NON_NULL(1); const gchar * fu_firmware_get_version(FuFirmware *self) G_GNUC_NON_NULL(1); void fu_firmware_set_version(FuFirmware *self, const gchar *version) G_GNUC_NON_NULL(1); guint64 fu_firmware_get_version_raw(FuFirmware *self) G_GNUC_NON_NULL(1); void fu_firmware_set_version_raw(FuFirmware *self, guint64 version_raw) G_GNUC_NON_NULL(1); void fu_firmware_add_flag(FuFirmware *firmware, FuFirmwareFlags flag) G_GNUC_NON_NULL(1); gboolean fu_firmware_has_flag(FuFirmware *firmware, FuFirmwareFlags flag) G_GNUC_NON_NULL(1); const gchar * fu_firmware_get_filename(FuFirmware *self) G_GNUC_NON_NULL(1); void fu_firmware_set_filename(FuFirmware *self, const gchar *filename) G_GNUC_NON_NULL(1); const gchar * fu_firmware_get_id(FuFirmware *self) G_GNUC_NON_NULL(1); void fu_firmware_set_id(FuFirmware *self, const gchar *id) G_GNUC_NON_NULL(1); guint64 fu_firmware_get_addr(FuFirmware *self) G_GNUC_NON_NULL(1); void fu_firmware_set_addr(FuFirmware *self, guint64 addr) G_GNUC_NON_NULL(1); guint64 fu_firmware_get_offset(FuFirmware *self) G_GNUC_NON_NULL(1); void fu_firmware_set_offset(FuFirmware *self, guint64 offset) G_GNUC_NON_NULL(1); gsize fu_firmware_get_size(FuFirmware *self) G_GNUC_NON_NULL(1); void fu_firmware_set_size(FuFirmware *self, gsize size) G_GNUC_NON_NULL(1); void fu_firmware_set_size_max(FuFirmware *self, gsize size_max) G_GNUC_NON_NULL(1); gsize fu_firmware_get_size_max(FuFirmware *self) G_GNUC_NON_NULL(1); void fu_firmware_set_images_max(FuFirmware *self, guint images_max) G_GNUC_NON_NULL(1); guint fu_firmware_get_images_max(FuFirmware *self) G_GNUC_NON_NULL(1); guint64 fu_firmware_get_idx(FuFirmware *self) G_GNUC_NON_NULL(1); void fu_firmware_set_idx(FuFirmware *self, guint64 idx) G_GNUC_NON_NULL(1); GBytes * fu_firmware_get_bytes(FuFirmware *self, GError **error) G_GNUC_NON_NULL(1); GBytes * fu_firmware_get_bytes_with_patches(FuFirmware *self, GError **error) G_GNUC_NON_NULL(1); void fu_firmware_set_bytes(FuFirmware *self, GBytes *bytes) G_GNUC_NON_NULL(1); guint8 fu_firmware_get_alignment(FuFirmware *self) G_GNUC_NON_NULL(1); void fu_firmware_set_alignment(FuFirmware *self, guint8 alignment) G_GNUC_NON_NULL(1); void fu_firmware_add_chunk(FuFirmware *self, FuChunk *chk) G_GNUC_NON_NULL(1); GPtrArray * fu_firmware_get_chunks(FuFirmware *self, GError **error) G_GNUC_NON_NULL(1); FuFirmware * fu_firmware_get_parent(FuFirmware *self) G_GNUC_NON_NULL(1); void fu_firmware_set_parent(FuFirmware *self, FuFirmware *parent) G_GNUC_NON_NULL(1); gboolean fu_firmware_tokenize(FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_firmware_build(FuFirmware *self, XbNode *n, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_firmware_build_from_xml(FuFirmware *self, const gchar *xml, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_firmware_parse(FuFirmware *self, GBytes *fw, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_firmware_parse_file(FuFirmware *self, GFile *file, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_firmware_parse_full(FuFirmware *self, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GBytes * fu_firmware_write(FuFirmware *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GBytes * fu_firmware_write_chunk(FuFirmware *self, guint64 address, guint64 chunk_sz_max, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_firmware_write_file(FuFirmware *self, GFile *file, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gchar * fu_firmware_get_checksum(FuFirmware *self, GChecksumType csum_kind, GError **error) G_GNUC_NON_NULL(1); gboolean fu_firmware_check_compatible(FuFirmware *self, FuFirmware *other, FwupdInstallFlags flags, GError **error) G_GNUC_NON_NULL(1, 2); void fu_firmware_add_image(FuFirmware *self, FuFirmware *img) G_GNUC_NON_NULL(1, 2); gboolean fu_firmware_add_image_full(FuFirmware *self, FuFirmware *img, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_firmware_remove_image(FuFirmware *self, FuFirmware *img, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_firmware_remove_image_by_idx(FuFirmware *self, guint64 idx, GError **error) G_GNUC_NON_NULL(1); gboolean fu_firmware_remove_image_by_id(FuFirmware *self, const gchar *id, GError **error) G_GNUC_NON_NULL(1); GPtrArray * fu_firmware_get_images(FuFirmware *self) G_GNUC_NON_NULL(1); FuFirmware * fu_firmware_get_image_by_id(FuFirmware *self, const gchar *id, GError **error) G_GNUC_NON_NULL(1); GBytes * fu_firmware_get_image_by_id_bytes(FuFirmware *self, const gchar *id, GError **error) G_GNUC_NON_NULL(1); FuFirmware * fu_firmware_get_image_by_idx(FuFirmware *self, guint64 idx, GError **error) G_GNUC_NON_NULL(1); GBytes * fu_firmware_get_image_by_idx_bytes(FuFirmware *self, guint64 idx, GError **error) G_GNUC_NON_NULL(1); FuFirmware * fu_firmware_get_image_by_gtype(FuFirmware *self, GType gtype, GError **error) G_GNUC_NON_NULL(1); GBytes * fu_firmware_get_image_by_gtype_bytes(FuFirmware *self, GType gtype, GError **error) G_GNUC_NON_NULL(1); FuFirmware * fu_firmware_get_image_by_checksum(FuFirmware *self, const gchar *checksum, GError **error) G_GNUC_NON_NULL(1, 2); void fu_firmware_add_patch(FuFirmware *self, gsize offset, GBytes *blob) G_GNUC_NON_NULL(1, 3); fwupd-1.9.16/libfwupdplugin/fu-fit-firmware.c000066400000000000000000000227421460375044200211540ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-bytes.h" #include "fu-common.h" #include "fu-crc.h" #include "fu-dump.h" #include "fu-fdt-image.h" #include "fu-fit-firmware.h" #include "fu-mem.h" /** * FuFitFirmware: * * A Flat Image Tree. * * Documented: * https://github.com/u-boot/u-boot/blob/master/doc/uImage.FIT/source_file_format.txt * * See also: [class@FuFdtFirmware] */ G_DEFINE_TYPE(FuFitFirmware, fu_fit_firmware, FU_TYPE_FDT_FIRMWARE) static FuFdtImage * fu_fit_firmware_get_image_root(FuFitFirmware *self) { FuFirmware *img = fu_firmware_get_image_by_id(FU_FIRMWARE(self), NULL, NULL); if (img != NULL) return FU_FDT_IMAGE(img); img = fu_fdt_image_new(); fu_fdt_image_set_attr_uint32(FU_FDT_IMAGE(img), FU_FIT_FIRMWARE_ATTR_TIMESTAMP, 0x0); fu_fdt_image_set_attr_str(FU_FDT_IMAGE(img), "description", "Firmware image"); fu_fdt_image_set_attr_str(FU_FDT_IMAGE(img), "creator", "fwupd"); fu_firmware_add_image(FU_FIRMWARE(self), img); return FU_FDT_IMAGE(img); } /** * fu_fit_firmware_get_timestamp: * @self: a #FuFitFirmware * * Gets the creation timestamp. * * Returns: integer * * Since: 1.8.2 **/ guint32 fu_fit_firmware_get_timestamp(FuFitFirmware *self) { guint32 tmp = 0; g_autoptr(FuFdtImage) img_root = fu_fit_firmware_get_image_root(self); g_return_val_if_fail(FU_IS_FIT_FIRMWARE(self), 0x0); /* this has to exist */ (void)fu_fdt_image_get_attr_u32(img_root, FU_FIT_FIRMWARE_ATTR_TIMESTAMP, &tmp, NULL); return tmp; } /** * fu_fit_firmware_set_timestamp: * @self: a #FuFitFirmware * @timestamp: integer value * * Sets the creation timestamp. * * Since: 1.8.2 **/ void fu_fit_firmware_set_timestamp(FuFitFirmware *self, guint32 timestamp) { g_autoptr(FuFdtImage) img_root = fu_fit_firmware_get_image_root(self); g_return_if_fail(FU_IS_FIT_FIRMWARE(self)); fu_fdt_image_set_attr_uint32(img_root, FU_FIT_FIRMWARE_ATTR_TIMESTAMP, timestamp); } static gboolean fu_fit_firmware_verify_crc32(FuFirmware *firmware, FuFirmware *img, FuFirmware *img_hash, GBytes *blob, GError **error) { guint32 value = 0; guint32 value_calc; /* get value and verify */ if (!fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img_hash), FU_FIT_FIRMWARE_ATTR_VALUE, &value, error)) return FALSE; value_calc = fu_crc32(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob)); if (value_calc != value) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%s CRC did not match, got 0x%x, expected 0x%x", fu_firmware_get_id(img), value, value_calc); return FALSE; } /* success */ fu_firmware_add_flag(firmware, FU_FIRMWARE_FLAG_HAS_CHECKSUM); return TRUE; } static gboolean fu_fit_firmware_verify_checksum(FuFirmware *firmware, FuFirmware *img, FuFirmware *img_hash, GChecksumType checksum_type, GBytes *blob, GError **error) { gsize digest_len = g_checksum_type_get_length(checksum_type); g_autofree guint8 *buf = g_malloc0(digest_len); g_autoptr(GBytes) value = NULL; g_autoptr(GBytes) value_calc = NULL; g_autoptr(GChecksum) checksum = g_checksum_new(checksum_type); /* get value and verify */ value = fu_fdt_image_get_attr(FU_FDT_IMAGE(img_hash), FU_FIT_FIRMWARE_ATTR_VALUE, error); if (value == NULL) return FALSE; if (g_bytes_get_size(value) != digest_len) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%s invalid hash value size, got 0x%x, expected 0x%x", fu_firmware_get_id(img), (guint)g_bytes_get_size(value), (guint)digest_len); return FALSE; } g_checksum_update(checksum, (const guchar *)g_bytes_get_data(value, NULL), g_bytes_get_size(value)); g_checksum_get_digest(checksum, buf, &digest_len); value_calc = g_bytes_new(buf, digest_len); if (!fu_bytes_compare(value, value_calc, error)) return FALSE; /* success */ fu_firmware_add_flag(firmware, FU_FIRMWARE_FLAG_HAS_CHECKSUM); return TRUE; } static gboolean fu_fit_firmware_verify_hash(FuFirmware *firmware, FuFirmware *img, FuFirmware *img_hash, GBytes *blob, GError **error) { g_autofree gchar *algo = NULL; /* what is this */ if (!fu_fdt_image_get_attr_str(FU_FDT_IMAGE(img_hash), FU_FIT_FIRMWARE_ATTR_ALGO, &algo, error)) { g_prefix_error(error, "cannot get algo for %s: ", fu_firmware_get_id(img)); return FALSE; } if (g_strcmp0(algo, "crc32") == 0) return fu_fit_firmware_verify_crc32(firmware, img, img_hash, blob, error); if (g_strcmp0(algo, "md5") == 0) { return fu_fit_firmware_verify_checksum(firmware, img, img_hash, G_CHECKSUM_MD5, blob, error); } if (g_strcmp0(algo, "sha1") == 0) { return fu_fit_firmware_verify_checksum(firmware, img, img_hash, G_CHECKSUM_SHA1, blob, error); } if (g_strcmp0(algo, "sha256") == 0) { return fu_fit_firmware_verify_checksum(firmware, img, img_hash, G_CHECKSUM_SHA256, blob, error); } /* ignore any hashes we do not support: success */ return TRUE; } static gboolean fu_fit_firmware_verify_image(FuFirmware *firmware, GBytes *fw, FuFirmware *img, FwupdInstallFlags flags, GError **error) { g_autoptr(GBytes) blob = NULL; /* sanity check */ if (!fu_fdt_image_get_attr_str(FU_FDT_IMAGE(img), "type", NULL, error)) return FALSE; if (!fu_fdt_image_get_attr_str(FU_FDT_IMAGE(img), "description", NULL, error)) return FALSE; /* if has data */ blob = fu_fdt_image_get_attr(FU_FDT_IMAGE(img), FU_FIT_FIRMWARE_ATTR_DATA, NULL); if (blob == NULL) { guint32 data_size = 0x0; guint32 data_offset = 0x0; /* extra data outside of FIT image */ if (!fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img), FU_FIT_FIRMWARE_ATTR_DATA_OFFSET, &data_offset, error)) return FALSE; if (!fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img), FU_FIT_FIRMWARE_ATTR_DATA_SIZE, &data_size, error)) return FALSE; blob = fu_bytes_new_offset(fw, data_offset, data_size, error); if (blob == NULL) return FALSE; } fu_dump_bytes(G_LOG_DOMAIN, "data", blob); /* verify any hashes we recognize */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { g_autoptr(GPtrArray) img_hashes = fu_firmware_get_images(img); for (guint i = 0; i < img_hashes->len; i++) { FuFirmware *img_hash = g_ptr_array_index(img_hashes, i); if (fu_firmware_get_id(img_hash) == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no ID for image hash"); return FALSE; } if (g_str_has_prefix(fu_firmware_get_id(img_hash), "hash")) { if (!fu_fit_firmware_verify_hash(firmware, img, img_hash, blob, error)) return FALSE; } } } /* success */ return TRUE; } static gboolean fu_fit_firmware_verify_configuration(FuFirmware *firmware, FuFirmware *img, FwupdInstallFlags flags, GError **error) { /* sanity check */ if (!fu_fdt_image_get_attr_strlist(FU_FDT_IMAGE(img), FU_FIT_FIRMWARE_ATTR_COMPATIBLE, NULL, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_fit_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) img_cfgs = NULL; g_autoptr(FuFirmware) img_images = NULL; g_autoptr(FuFirmware) img_root = NULL; g_autoptr(GPtrArray) img_images_array = NULL; g_autoptr(GPtrArray) img_cfgs_array = NULL; /* FuFdtFirmware->parse */ if (!FU_FIRMWARE_CLASS(fu_fit_firmware_parent_class) ->parse(firmware, fw, offset, flags, error)) return FALSE; /* sanity check */ img_root = fu_firmware_get_image_by_id(firmware, NULL, error); if (img_root == NULL) return FALSE; if (!fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img_root), FU_FIT_FIRMWARE_ATTR_TIMESTAMP, NULL, error)) return FALSE; /* check the checksums of each image */ img_images = fu_firmware_get_image_by_id(img_root, FU_FIT_FIRMWARE_ID_IMAGES, error); if (img_images == NULL) return FALSE; img_images_array = fu_firmware_get_images(img_images); for (guint i = 0; i < img_images_array->len; i++) { FuFirmware *img = g_ptr_array_index(img_images_array, i); if (!fu_fit_firmware_verify_image(firmware, fw, img, flags, error)) return FALSE; } /* check the setup of each configuration */ img_cfgs = fu_firmware_get_image_by_id(img_root, FU_FIT_FIRMWARE_ID_CONFIGURATIONS, error); if (img_cfgs == NULL) return FALSE; img_cfgs_array = fu_firmware_get_images(img_cfgs); for (guint i = 0; i < img_cfgs_array->len; i++) { FuFirmware *img = g_ptr_array_index(img_cfgs_array, i); if (!fu_fit_firmware_verify_configuration(firmware, img, flags, error)) return FALSE; } /* success */ return TRUE; } static void fu_fit_firmware_init(FuFitFirmware *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); } static void fu_fit_firmware_class_init(FuFitFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_fit_firmware_parse; } /** * fu_fit_firmware_new: * * Creates a new #FuFirmware of sub type FIT * * Since: 1.8.2 **/ FuFirmware * fu_fit_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_FIT_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-fit-firmware.h000066400000000000000000000053221460375044200211540ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-fdt-firmware.h" #define FU_TYPE_FIT_FIRMWARE (fu_fit_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuFitFirmware, fu_fit_firmware, FU, FIT_FIRMWARE, FuFdtFirmware) struct _FuFitFirmwareClass { FuFdtFirmwareClass parent_class; }; FuFirmware * fu_fit_firmware_new(void); guint32 fu_fit_firmware_get_timestamp(FuFitFirmware *self) G_GNUC_NON_NULL(1); void fu_fit_firmware_set_timestamp(FuFitFirmware *self, guint32 timestamp) G_GNUC_NON_NULL(1); /** * FU_FIT_FIRMWARE_ATTR_COMPATIBLE: * * The compatible metadata for the FIT image, typically a string list, e.g. *`pine64,rockpro64-v2.1:pine64,rockpro64`. * * Since: 1.8.2 **/ #define FU_FIT_FIRMWARE_ATTR_COMPATIBLE "compatible" /** * FU_FIT_FIRMWARE_ATTR_DATA: * * The raw data for the FIT image, typically a blob. * * Since: 1.8.2 **/ #define FU_FIT_FIRMWARE_ATTR_DATA "data" /** * FU_FIT_FIRMWARE_ATTR_ALGO: * * The checksum algorithm for the FIT image, typically a string, e.g. `crc32`. * * Since: 1.8.2 **/ #define FU_FIT_FIRMWARE_ATTR_ALGO "algo" /** * FU_FIT_FIRMWARE_ATTR_DATA_OFFSET: * * The external data offset after the FIT image, typically a uint32. * * Since: 1.8.2 **/ #define FU_FIT_FIRMWARE_ATTR_DATA_OFFSET "data-offset" /** * FU_FIT_FIRMWARE_ATTR_DATA_SIZE: * * The data size of the external image, typically a uint32. * * Since: 1.8.2 **/ #define FU_FIT_FIRMWARE_ATTR_DATA_SIZE "data-size" /** * FU_FIT_FIRMWARE_ATTR_STORE_OFFSET: * * The store offset for the FIT image, typically a uint32. * * Since: 1.8.2 **/ #define FU_FIT_FIRMWARE_ATTR_STORE_OFFSET "store-offset" /** * FU_FIT_FIRMWARE_ATTR_VALUE: * * The value of the checksum, which is typically a blob. * * Since: 1.8.2 **/ #define FU_FIT_FIRMWARE_ATTR_VALUE "value" /** * FU_FIT_FIRMWARE_ATTR_SKIP_OFFSET: * * The offset to skip when writing the FIT image, typically a uint32. * * Since: 1.8.2 **/ #define FU_FIT_FIRMWARE_ATTR_SKIP_OFFSET "skip-offset" /** * FU_FIT_FIRMWARE_ATTR_VERSION: * * The version of the FIT image, typically a string, e.g. `1.2.3`. * * Since: 1.8.2 **/ #define FU_FIT_FIRMWARE_ATTR_VERSION "version" /** * FU_FIT_FIRMWARE_ATTR_TIMESTAMP: * * The creation timestamp of FIT image, typically a uint32. * * Since: 1.8.2 **/ #define FU_FIT_FIRMWARE_ATTR_TIMESTAMP "timestamp" /** * FU_FIT_FIRMWARE_ID_IMAGES: * * The usual firmware ID string for the images. * * Since: 1.8.2 **/ #define FU_FIT_FIRMWARE_ID_IMAGES "images" /** * FU_FIT_FIRMWARE_ID_CONFIGURATIONS: * * The usual firmware ID string for the configurations. * * Since: 1.8.2 **/ #define FU_FIT_FIRMWARE_ID_CONFIGURATIONS "configurations" fwupd-1.9.16/libfwupdplugin/fu-fmap-firmware.c000066400000000000000000000126251460375044200213140ustar00rootroot00000000000000/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-common.h" #include "fu-fmap-firmware.h" #include "fu-fmap-struct.h" /** * FuFmapFirmware: * * A FMAP firmware image. * * See also: [class@FuFirmware] */ #define FMAP_AREANAME "FMAP" G_DEFINE_TYPE(FuFmapFirmware, fu_fmap_firmware, FU_TYPE_FIRMWARE) static gboolean fu_fmap_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_fmap_validate_bytes(fw, offset, error); } static gboolean fu_fmap_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuFmapFirmwareClass *klass_firmware = FU_FMAP_FIRMWARE_GET_CLASS(firmware); guint32 nareas; g_autoptr(GByteArray) st_hdr = NULL; /* parse */ st_hdr = fu_struct_fmap_parse_bytes(fw, offset, error); if (st_hdr == NULL) return FALSE; fu_firmware_set_addr(firmware, fu_struct_fmap_get_base(st_hdr)); if (fu_struct_fmap_get_size(st_hdr) != g_bytes_get_size(fw)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "file size incorrect, expected 0x%04x got 0x%04x", fu_struct_fmap_get_size(st_hdr), (guint)g_bytes_get_size(fw)); return FALSE; } nareas = fu_struct_fmap_get_nareas(st_hdr); if (nareas < 1) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "number of areas invalid"); return FALSE; } offset += st_hdr->len; for (gsize i = 0; i < nareas; i++) { guint32 area_offset; guint32 area_size; g_autofree gchar *area_name = NULL; g_autoptr(FuFirmware) img = NULL; g_autoptr(GByteArray) st_area = NULL; g_autoptr(GBytes) bytes = NULL; /* load area */ st_area = fu_struct_fmap_area_parse_bytes(fw, offset, error); if (st_area == NULL) return FALSE; area_size = fu_struct_fmap_area_get_size(st_area); if (area_size == 0) continue; area_offset = fu_struct_fmap_area_get_offset(st_area); bytes = fu_bytes_new_offset(fw, (gsize)area_offset, (gsize)area_size, error); if (bytes == NULL) return FALSE; area_name = fu_struct_fmap_area_get_name(st_area); img = fu_firmware_new_from_bytes(bytes); fu_firmware_set_id(img, area_name); fu_firmware_set_idx(img, i + 1); fu_firmware_set_addr(img, area_offset); fu_firmware_add_image(firmware, img); if (g_strcmp0(area_name, FMAP_AREANAME) == 0) { g_autofree gchar *version = NULL; version = g_strdup_printf("%d.%d", fu_struct_fmap_get_ver_major(st_hdr), fu_struct_fmap_get_ver_minor(st_hdr)); fu_firmware_set_version(img, version); } offset += st_area->len; } /* subclassed */ if (klass_firmware->parse != NULL) { if (!klass_firmware->parse(firmware, fw, offset, flags, error)) return FALSE; } /* success */ return TRUE; } static GByteArray * fu_fmap_firmware_write(FuFirmware *firmware, GError **error) { gsize total_sz; gsize offset; g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) st_hdr = fu_struct_fmap_new(); /* pad to offset */ if (fu_firmware_get_offset(firmware) > 0) fu_byte_array_set_size(buf, fu_firmware_get_offset(firmware), 0x00); /* add header */ total_sz = offset = st_hdr->len + (FU_STRUCT_FMAP_AREA_SIZE * images->len); for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) fw = fu_firmware_get_bytes_with_patches(img, error); if (fw == NULL) return NULL; total_sz += g_bytes_get_size(fw); } /* header */ fu_struct_fmap_set_base(st_hdr, fu_firmware_get_addr(firmware)); fu_struct_fmap_set_nareas(st_hdr, images->len); fu_struct_fmap_set_size(st_hdr, fu_firmware_get_offset(firmware) + total_sz); g_byte_array_append(buf, st_hdr->data, st_hdr->len); /* add each area */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) fw = fu_firmware_get_bytes_with_patches(img, NULL); g_autoptr(GByteArray) st_area = fu_struct_fmap_area_new(); fu_struct_fmap_area_set_offset(st_area, fu_firmware_get_offset(firmware) + offset); fu_struct_fmap_area_set_size(st_area, g_bytes_get_size(fw)); if (fu_firmware_get_id(img) != NULL) { if (!fu_struct_fmap_area_set_name(st_area, fu_firmware_get_id(img), error)) return NULL; } g_byte_array_append(buf, st_area->data, st_area->len); offset += g_bytes_get_size(fw); } /* add the images */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) fw = fu_firmware_get_bytes_with_patches(img, error); if (fw == NULL) return NULL; fu_byte_array_append_bytes(buf, fw); } /* success */ return g_steal_pointer(&buf); } static void fu_fmap_firmware_init(FuFmapFirmware *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); } static void fu_fmap_firmware_class_init(FuFmapFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_fmap_firmware_check_magic; klass_firmware->parse = fu_fmap_firmware_parse; klass_firmware->write = fu_fmap_firmware_write; } /** * fu_fmap_firmware_new * * Creates a new #FuFirmware of sub type fmap * * Since: 1.5.0 **/ FuFirmware * fu_fmap_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_FMAP_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-fmap-firmware.h000066400000000000000000000011751460375044200213170ustar00rootroot00000000000000/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_FMAP_FIRMWARE_STRLEN 32 /* maximum length for strings, */ /* including null-terminator */ #define FU_TYPE_FMAP_FIRMWARE (fu_fmap_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuFmapFirmware, fu_fmap_firmware, FU, FMAP_FIRMWARE, FuFirmware) struct _FuFmapFirmwareClass { FuFirmwareClass parent_class; gboolean (*parse)(FuFirmware *self, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error); }; FuFirmware * fu_fmap_firmware_new(void); fwupd-1.9.16/libfwupdplugin/fu-fmap.rs000066400000000000000000000011371460375044200177000ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, ValidateBytes, ParseBytes)] struct Fmap { signature: [char; 8] == "__FMAP__", ver_major: u8 = 0x1, ver_minor: u8 = 0x1, base: u64le, // address of the firmware binary size: u32le, // bytes name: [char; 32], nareas: u16le, // number of areas } #[derive(New, ParseBytes)] struct FmapArea { // area of volatile and static regions offset: u32le, // offset relative to base size: u32le, // bytes name: [char; 32], // descriptive name flags: u16le, } fwupd-1.9.16/libfwupdplugin/fu-fuzzer-firmware.c.in000066400000000000000000000016521460375044200223210ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "@INCLUDE@" int LLVMFuzzerTestOneInput(const guint8 *data, gsize size) { g_autoptr(FuFirmware) firmware = FU_FIRMWARE(@FIRMWARENEW@); g_autoptr(GBytes) fw = g_bytes_new(data, size); gboolean ret; (void)g_setenv("G_DEBUG", "fatal-criticals", TRUE); (void)g_setenv("FWUPD_FUZZER_RUNNING", "1", TRUE); ret = fu_firmware_parse(firmware, fw, FWUPD_INSTALL_FLAG_NONE, NULL); if (!ret && fu_firmware_has_flag(firmware, FU_FIRMWARE_FLAG_HAS_CHECKSUM)) { g_clear_object(&firmware); firmware = FU_FIRMWARE(@FIRMWARENEW@); ret = fu_firmware_parse(firmware, fw, FWUPD_INSTALL_FLAG_NO_SEARCH | FWUPD_INSTALL_FLAG_IGNORE_VID_PID | FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM, NULL); } if (ret) { g_autoptr(GBytes) fw2 = fu_firmware_write(firmware, NULL); } return 0; } fwupd-1.9.16/libfwupdplugin/fu-fuzzer-main.c000066400000000000000000000014761460375044200210300ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include __attribute__((weak)) extern int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size); __attribute__((weak)) extern int LLVMFuzzerInitialize(int *argc, char ***argv); int main(int argc, char **argv) { g_assert_nonnull(LLVMFuzzerTestOneInput); if (LLVMFuzzerInitialize != NULL) LLVMFuzzerInitialize(&argc, &argv); for (int i = 1; i < argc; i++) { gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autoptr(GError) error = NULL; g_printerr("Running: %s\n", argv[i]); if (!g_file_get_contents(argv[i], &buf, &bufsz, &error)) { g_printerr("Failed to load: %s\n", error->message); continue; } LLVMFuzzerTestOneInput((const guint8 *)buf, bufsz); g_printerr("Done\n"); } } fwupd-1.9.16/libfwupdplugin/fu-gcab.c000066400000000000000000000064571460375044200174610ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #ifdef _WIN32 #include #endif int main(int argc, char **argv) { gboolean do_compression = FALSE; gboolean do_create = FALSE; gboolean do_extract = FALSE; gboolean do_list = FALSE; gboolean no_path = FALSE; gboolean verbose = FALSE; g_autoptr(FuCabFirmware) cab_firmware = fu_cab_firmware_new(); g_autoptr(GOptionContext) context = g_option_context_new(NULL); g_autoptr(GError) error = NULL; const GOptionEntry options[] = { {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be verbose", NULL}, {"create", 'c', 0, G_OPTION_ARG_NONE, &do_create, "Create archive", NULL}, {"extract", 'x', 0, G_OPTION_ARG_NONE, &do_extract, "Extract all files", NULL}, {"list", 'l', 0, G_OPTION_ARG_NONE, &do_list, "List contents", NULL}, {"zip", 'z', 0, G_OPTION_ARG_NONE, &do_compression, "Use zip compression", NULL}, {"nopath", 'n', 0, G_OPTION_ARG_NONE, &no_path, "Do not include path", NULL}, {NULL}}; #ifdef _WIN32 /* required for Windows */ SetConsoleOutputCP(CP_UTF8); SetConsoleCP(CP_UTF8); (void)g_setenv("LANG", "C.UTF-8", FALSE); #endif setlocale(LC_ALL, ""); #ifndef SUPPORTED_BUILD /* make critical warnings fatal */ (void)g_setenv("G_DEBUG", "fatal-criticals", FALSE); #endif g_option_context_add_main_entries(context, options, NULL); if (!g_option_context_parse(context, &argc, &argv, &error)) { g_printerr("Failed to parse arguments: %s\n", error->message); return EXIT_FAILURE; } if (verbose) g_setenv("G_MESSAGES_DEBUG", "all", TRUE); if (do_create && argc > 1) { g_autoptr(GFile) file = g_file_new_for_path(argv[1]); for (gint i = 2; i < argc; i++) { g_autoptr(GBytes) img_blob = NULL; g_autoptr(FuCabImage) img = fu_cab_image_new(); if (no_path) { g_autofree gchar *basename = g_path_get_basename(argv[i]); fu_firmware_set_id(FU_FIRMWARE(img), basename); } else { fu_firmware_set_id(FU_FIRMWARE(img), argv[i]); } img_blob = fu_bytes_get_contents(argv[i], &error); if (img_blob == NULL) { g_printerr("Failed to load file %s: %s\n", argv[i], error->message); return EXIT_FAILURE; } fu_firmware_set_bytes(FU_FIRMWARE(img), img_blob); fu_firmware_add_image(FU_FIRMWARE(cab_firmware), FU_FIRMWARE(img)); } if (do_compression) fu_cab_firmware_set_compressed(cab_firmware, TRUE); if (!fu_firmware_write_file(FU_FIRMWARE(cab_firmware), file, &error)) { g_printerr("Failed to write file %s: %s\n", argv[1], error->message); return EXIT_FAILURE; } return EXIT_SUCCESS; } if (do_list && argc > 1) { g_autoptr(GBytes) img_blob = NULL; g_autofree gchar *str = NULL; img_blob = fu_bytes_get_contents(argv[1], &error); if (img_blob == NULL) { g_printerr("Failed to load file %s: %s\n", argv[1], error->message); return EXIT_FAILURE; } if (!fu_firmware_parse(FU_FIRMWARE(cab_firmware), img_blob, FWUPD_INSTALL_FLAG_NONE, &error)) { g_printerr("Failed to parse file: %s\n", error->message); return EXIT_FAILURE; } str = fu_firmware_to_string(FU_FIRMWARE(cab_firmware)); g_print("%s", str); return EXIT_SUCCESS; } g_printerr("Please specify a single operation\n"); return EXIT_FAILURE; } fwupd-1.9.16/libfwupdplugin/fu-hid-descriptor.c000066400000000000000000000224351460375044200214770ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuHidDevice" #include "config.h" #include "fu-byte-array.h" #include "fu-hid-descriptor.h" #include "fu-hid-report-item.h" #include "fu-hid-struct.h" #include "fu-mem.h" /** * FuHidDescriptor: * * A HID descriptor. * * Each report is a image of this firmware object and each report has children of #FuHidReportItem. * * Documented: https://www.usb.org/sites/default/files/hid1_11.pdf * * See also: [class@FuFirmware] */ G_DEFINE_TYPE(FuHidDescriptor, fu_hid_descriptor, FU_TYPE_FIRMWARE) #define FU_HID_DESCRIPTOR_TABLE_LOCAL_SIZE_MAX 1024 #define FU_HID_DESCRIPTOR_TABLE_LOCAL_DUPES_MAX 16 #define FU_HID_DESCRIPTOR_TABLE_GLOBAL_SIZE_MAX 1024 #define FU_HID_DESCRIPTOR_TABLE_GLOBAL_DUPES_MAX 64 static guint fu_hid_descriptor_count_table_dupes(GPtrArray *table, FuHidReportItem *item) { guint cnt = 0; for (guint i = 0; i < table->len; i++) { FuHidReportItem *item_tmp = g_ptr_array_index(table, i); if (fu_hid_report_item_get_kind(item) == fu_hid_report_item_get_kind(item_tmp) && fu_hid_report_item_get_value(item) == fu_hid_report_item_get_value(item_tmp) && fu_firmware_get_idx(FU_FIRMWARE(item)) == fu_firmware_get_idx(FU_FIRMWARE(item_tmp))) cnt++; } return cnt; } static gboolean fu_hid_descriptor_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { g_autoptr(GPtrArray) table_state = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_autoptr(GPtrArray) table_local = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); while (offset < g_bytes_get_size(fw)) { g_autofree gchar *itemstr = NULL; g_autoptr(FuHidReportItem) item = fu_hid_report_item_new(); /* sanity check */ if (table_state->len > FU_HID_DESCRIPTOR_TABLE_GLOBAL_SIZE_MAX) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "HID table state too large, limit is %u", (guint)FU_HID_DESCRIPTOR_TABLE_GLOBAL_SIZE_MAX); return FALSE; } if (table_local->len > FU_HID_DESCRIPTOR_TABLE_LOCAL_SIZE_MAX) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "HID table state too large, limit is %u", (guint)FU_HID_DESCRIPTOR_TABLE_LOCAL_SIZE_MAX); return FALSE; } if (!fu_firmware_parse_full(FU_FIRMWARE(item), fw, offset, flags, error)) return FALSE; offset += fu_firmware_get_size(FU_FIRMWARE(item)); /* only for debugging */ itemstr = fu_firmware_to_string(FU_FIRMWARE(item)); g_debug("add to table-state:\n%s", itemstr); /* if there is a sane number of duplicate tokens then add to table */ if (fu_hid_report_item_get_kind(item) == FU_HID_ITEM_KIND_GLOBAL) { if (fu_hid_descriptor_count_table_dupes(table_state, item) > FU_HID_DESCRIPTOR_TABLE_GLOBAL_DUPES_MAX) { g_set_error( error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "table invalid @0x%x, too many duplicate global %s tokens", (guint)offset, fu_firmware_get_id(FU_FIRMWARE(item))); return FALSE; } g_ptr_array_add(table_state, g_object_ref(item)); } else if (fu_hid_report_item_get_kind(item) == FU_HID_ITEM_KIND_LOCAL || fu_hid_report_item_get_kind(item) == FU_HID_ITEM_KIND_MAIN) { if (fu_hid_descriptor_count_table_dupes(table_local, item) > FU_HID_DESCRIPTOR_TABLE_LOCAL_DUPES_MAX) { g_set_error( error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "table invalid @0x%x, too many duplicate %s %s:0x%x tokens", (guint)offset, fu_hid_item_kind_to_string(fu_hid_report_item_get_kind(item)), fu_firmware_get_id(FU_FIRMWARE(item)), fu_hid_report_item_get_value(item)); return FALSE; } g_ptr_array_add(table_local, g_object_ref(item)); } /* add report */ if (fu_hid_report_item_get_kind(item) == FU_HID_ITEM_KIND_MAIN) { g_autoptr(FuHidReport) report = fu_hid_report_new(); /* copy the table state to the new report */ for (guint i = 0; i < table_state->len; i++) { FuHidReportItem *item_tmp = g_ptr_array_index(table_state, i); if (!fu_firmware_add_image_full(FU_FIRMWARE(report), FU_FIRMWARE(item_tmp), error)) return FALSE; } for (guint i = 0; i < table_local->len; i++) { FuHidReportItem *item_tmp = g_ptr_array_index(table_local, i); if (!fu_firmware_add_image_full(FU_FIRMWARE(report), FU_FIRMWARE(item_tmp), error)) return FALSE; } if (!fu_firmware_add_image_full(firmware, FU_FIRMWARE(report), error)) return FALSE; /* remove all the local items */ g_ptr_array_set_size(table_local, 0); } } /* success */ return TRUE; } static gboolean fu_hid_descriptor_write_report_item(FuFirmware *report_item, GByteArray *buf, GHashTable *globals, GError **error) { g_autoptr(GBytes) fw = NULL; /* dedupe any globals */ if (fu_hid_report_item_get_kind(FU_HID_REPORT_ITEM(report_item)) == FU_HID_ITEM_KIND_GLOBAL) { guint8 tag = fu_firmware_get_idx(report_item); FuFirmware *report_item_tmp = g_hash_table_lookup(globals, GUINT_TO_POINTER(tag)); if (report_item_tmp != NULL && fu_hid_report_item_get_value(FU_HID_REPORT_ITEM(report_item)) == fu_hid_report_item_get_value(FU_HID_REPORT_ITEM(report_item_tmp))) { g_debug("skipping duplicate global tag 0x%x", tag); return TRUE; } g_hash_table_insert(globals, GUINT_TO_POINTER(tag), report_item); } fw = fu_firmware_write(report_item, error); if (fw == NULL) return FALSE; fu_byte_array_append_bytes(buf, fw); /* success */ return TRUE; } static gboolean fu_hid_descriptor_write_report(FuFirmware *report, GByteArray *buf, GHashTable *globals, GError **error) { g_autoptr(GPtrArray) report_items = fu_firmware_get_images(report); /* for each item */ for (guint i = 0; i < report_items->len; i++) { FuFirmware *report_item = g_ptr_array_index(report_items, i); if (!fu_hid_descriptor_write_report_item(report_item, buf, globals, error)) return FALSE; } /* success */ return TRUE; } static GByteArray * fu_hid_descriptor_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GHashTable) globals = g_hash_table_new(g_direct_hash, g_direct_equal); g_autoptr(GPtrArray) reports = fu_firmware_get_images(firmware); /* for each report */ for (guint i = 0; i < reports->len; i++) { FuFirmware *report = g_ptr_array_index(reports, i); if (!fu_hid_descriptor_write_report(report, buf, globals, error)) return NULL; } /* success */ return g_steal_pointer(&buf); } typedef struct { const gchar *id; guint32 value; } FuHidDescriptorCondition; /** * fu_hid_descriptor_find_report: * @self: a #FuHidDescriptor * @error: (nullable): optional return location for an error * @...: pairs of string-integer values, ending with %NULL * * Finds the first HID report that matches all the report attributes. * * Returns: (transfer full): A #FuHidReport, or %NULL if not found. * * Since: 1.9.4 **/ FuHidReport * fu_hid_descriptor_find_report(FuHidDescriptor *self, GError **error, ...) { va_list args; g_autoptr(GPtrArray) conditions = g_ptr_array_new_with_free_func(g_free); g_autoptr(GPtrArray) reports = fu_firmware_get_images(FU_FIRMWARE(self)); g_return_val_if_fail(FU_IS_HID_DESCRIPTOR(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* parse varargs */ va_start(args, error); for (guint i = 0; i < 1000; i++) { g_autofree FuHidDescriptorCondition *cond = g_new0(FuHidDescriptorCondition, 1); cond->id = va_arg(args, const gchar *); if (cond->id == NULL) break; cond->value = va_arg(args, guint32); g_ptr_array_add(conditions, g_steal_pointer(&cond)); } va_end(args); /* return the first report that matches *all* conditions */ for (guint i = 0; i < reports->len; i++) { FuHidReport *report = g_ptr_array_index(reports, i); gboolean matched = TRUE; for (guint j = 0; j < conditions->len; j++) { FuHidDescriptorCondition *cond = g_ptr_array_index(conditions, j); g_autoptr(FuFirmware) item = fu_firmware_get_image_by_id(FU_FIRMWARE(report), cond->id, NULL); if (item == NULL) { matched = FALSE; break; } if (fu_hid_report_item_get_value(FU_HID_REPORT_ITEM(item)) != cond->value) { matched = FALSE; break; } } if (matched) return g_object_ref(report); } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no report found"); return NULL; } static void fu_hid_descriptor_init(FuHidDescriptor *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); fu_firmware_set_size_max(FU_FIRMWARE(self), 64 * 1024); fu_firmware_set_images_max(FU_FIRMWARE(self), g_getenv("FWUPD_FUZZER_RUNNING") != NULL ? 10 : 1024); g_type_ensure(FU_TYPE_HID_REPORT); g_type_ensure(FU_TYPE_HID_REPORT_ITEM); } static void fu_hid_descriptor_class_init(FuHidDescriptorClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_hid_descriptor_parse; klass_firmware->write = fu_hid_descriptor_write; } /** * fu_hid_descriptor_new: * * Creates a new #FuFirmware to parse a HID descriptor * * Since: 1.9.4 **/ FuFirmware * fu_hid_descriptor_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_HID_DESCRIPTOR, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-hid-descriptor.h000066400000000000000000000010461460375044200214770ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-hid-report.h" #define FU_TYPE_HID_DESCRIPTOR (fu_hid_descriptor_get_type()) G_DECLARE_DERIVABLE_TYPE(FuHidDescriptor, fu_hid_descriptor, FU, HID_DESCRIPTOR, FuFirmware) struct _FuHidDescriptorClass { FuFirmwareClass parent_class; }; FuFirmware * fu_hid_descriptor_new(void); FuHidReport * fu_hid_descriptor_find_report(FuHidDescriptor *self, GError **error, ...) G_GNUC_NULL_TERMINATED G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-hid-device.c000066400000000000000000000455351460375044200205660ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuHidDevice" #include "config.h" #include "fu-dump.h" #include "fu-hid-device.h" #include "fu-string.h" #define FU_HID_REPORT_GET 0x01 #define FU_HID_REPORT_SET 0x09 #define FU_HID_REPORT_TYPE_INPUT 0x01 #define FU_HID_REPORT_TYPE_OUTPUT 0x02 #define FU_HID_REPORT_TYPE_FEATURE 0x03 #define FU_HID_DEVICE_RETRIES 10 /** * FuHidDevice: * * A Human Interface Device (HID) device. * * See also: [class@FuDevice], [class@FuUsbDevice] */ typedef struct { guint8 interface; guint8 ep_addr_in; /* only for _USE_INTERRUPT_TRANSFER */ guint8 ep_addr_out; /* only for _USE_INTERRUPT_TRANSFER */ gboolean interface_autodetect; FuHidDeviceFlags flags; } FuHidDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuHidDevice, fu_hid_device, FU_TYPE_USB_DEVICE) enum { PROP_0, PROP_INTERFACE, PROP_LAST }; #define GET_PRIVATE(o) (fu_hid_device_get_instance_private(o)) static void fu_hid_device_to_string(FuDevice *device, guint idt, GString *str) { FuHidDevice *self = FU_HID_DEVICE(device); FuHidDevicePrivate *priv = GET_PRIVATE(self); fu_string_append_kb(str, idt, "InterfaceAutodetect", priv->interface_autodetect); fu_string_append_kx(str, idt, "Interface", priv->interface); if (priv->ep_addr_in != 0) fu_string_append_kx(str, idt, "EpAddrIn", priv->ep_addr_in); if (priv->ep_addr_out != 0) fu_string_append_kx(str, idt, "EpAddrOut", priv->ep_addr_out); } static void fu_hid_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuHidDevice *device = FU_HID_DEVICE(object); FuHidDevicePrivate *priv = GET_PRIVATE(device); switch (prop_id) { case PROP_INTERFACE: g_value_set_uint(value, priv->interface); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_hid_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuHidDevice *device = FU_HID_DEVICE(object); switch (prop_id) { case PROP_INTERFACE: fu_hid_device_set_interface(device, g_value_get_uint(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * fu_hid_device_parse_descriptor: * @self: a #FuHidDevice * @error: (nullable): optional return location for an error * * Parses the HID descriptor. * * Returns: (transfer full): a #FuHidDescriptor, or %NULL for error * * Since: 1.9.4 **/ FuHidDescriptor * fu_hid_device_parse_descriptor(FuHidDevice *self, GError **error) { #if G_USB_CHECK_VERSION(0, 4, 7) GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); g_autoptr(FuFirmware) descriptor = fu_hid_descriptor_new(); g_autoptr(GBytes) fw = NULL; g_return_val_if_fail(FU_HID_DEVICE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); fw = g_usb_device_get_hid_descriptor_default(usb_device, error); if (fw == NULL) return NULL; fu_dump_bytes(G_LOG_DOMAIN, "HidDescriptor", fw); if (!fu_firmware_parse(descriptor, fw, FWUPD_INSTALL_FLAG_NONE, error)) return NULL; return FU_HID_DESCRIPTOR(g_steal_pointer(&descriptor)); #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "only supported in libgusb >= 0.4.7"); return NULL; #endif } #ifdef HAVE_GUSB static gboolean fu_hid_device_autodetect_eps(FuHidDevice *self, GUsbInterface *iface, GError **error) { FuHidDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GPtrArray) eps = g_usb_interface_get_endpoints(iface); for (guint i = 0; i < eps->len; i++) { GUsbEndpoint *ep = g_ptr_array_index(eps, i); if (g_usb_endpoint_get_direction(ep) == G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST && priv->ep_addr_in == 0) { priv->ep_addr_in = g_usb_endpoint_get_address(ep); continue; } if (g_usb_endpoint_get_direction(ep) == G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE && priv->ep_addr_out == 0) { priv->ep_addr_out = g_usb_endpoint_get_address(ep); continue; } } if (priv->ep_addr_in == 0x0 && priv->ep_addr_out == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "could not autodetect EP addresses"); return FALSE; } return TRUE; } #endif static gboolean fu_hid_device_open(FuDevice *device, GError **error) { #ifdef HAVE_GUSB FuHidDevice *self = FU_HID_DEVICE(device); FuHidDevicePrivate *priv = GET_PRIVATE(self); GUsbDeviceClaimInterfaceFlags flags = 0; GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); /* FuUsbDevice->open */ if (!FU_DEVICE_CLASS(fu_hid_device_parent_class)->open(device, error)) return FALSE; /* auto-detect */ if (priv->interface_autodetect) { g_autoptr(GPtrArray) ifaces = NULL; ifaces = g_usb_device_get_interfaces(usb_device, error); if (ifaces == NULL) return FALSE; for (guint i = 0; i < ifaces->len; i++) { GUsbInterface *iface = g_ptr_array_index(ifaces, i); if (g_usb_interface_get_class(iface) == G_USB_DEVICE_CLASS_HID) { priv->interface = g_usb_interface_get_number(iface); priv->interface_autodetect = FALSE; if (priv->flags & FU_HID_DEVICE_FLAG_AUTODETECT_EPS) { if (!fu_hid_device_autodetect_eps(self, iface, error)) return FALSE; } break; } } if (priv->interface_autodetect) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "could not autodetect HID interface"); return FALSE; } } /* claim */ if ((priv->flags & FU_HID_DEVICE_FLAG_NO_KERNEL_UNBIND) == 0) flags |= G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER; if (!g_usb_device_claim_interface(usb_device, priv->interface, flags, error)) { g_prefix_error(error, "failed to claim HID interface: "); return FALSE; } #endif /* success */ return TRUE; } static gboolean fu_hid_device_close(FuDevice *device, GError **error) { #ifdef HAVE_GUSB FuHidDevice *self = FU_HID_DEVICE(device); FuHidDevicePrivate *priv = GET_PRIVATE(self); GUsbDeviceClaimInterfaceFlags flags = 0; GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); g_autoptr(GError) error_local = NULL; #endif #ifdef HAVE_GUSB /* release */ if ((priv->flags & FU_HID_DEVICE_FLAG_NO_KERNEL_REBIND) == 0) flags |= G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER; if (!g_usb_device_release_interface(usb_device, priv->interface, flags, &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE) || g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_INTERNAL)) { g_debug("ignoring: %s", error_local->message); return TRUE; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to release HID interface: "); return FALSE; } #endif /* FuUsbDevice->close */ return FU_DEVICE_CLASS(fu_hid_device_parent_class)->close(device, error); } /** * fu_hid_device_set_interface: * @self: a #FuHidDevice * @interface_number: an interface number, e.g. `0x03` * * Sets the HID USB interface number. * * In most cases the HID interface is auto-detected, but this function can be * used where there are multiple HID interfaces or where the device USB * interface descriptor is invalid. * * Since: 1.4.0 **/ void fu_hid_device_set_interface(FuHidDevice *self, guint8 interface_number) { FuHidDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_HID_DEVICE(self)); priv->interface = interface_number; priv->interface_autodetect = FALSE; } /** * fu_hid_device_get_interface: * @self: a #FuHidDevice * * Gets the HID USB interface number. * * Returns: integer * * Since: 1.4.0 **/ guint8 fu_hid_device_get_interface(FuHidDevice *self) { FuHidDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_HID_DEVICE(self), 0xff); return priv->interface; } /** * fu_hid_device_set_ep_addr_in: * @self: a #FuHidDevice * @ep_addr_in: an endpoint, e.g. `0x03` * * Sets the HID USB interrupt in endpoint. * * In most cases the HID ep_addr_in is auto-detected, but this function can be * used where there are multiple HID EPss or where the device USB EP is invalid. * * Since: 1.9.4 **/ void fu_hid_device_set_ep_addr_in(FuHidDevice *self, guint8 ep_addr_in) { FuHidDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_HID_DEVICE(self)); priv->ep_addr_in = ep_addr_in; priv->interface_autodetect = FALSE; } /** * fu_hid_device_get_ep_addr_in: * @self: a #FuHidDevice * * Gets the HID USB in endpoint. * * Returns: integer * * Since: 1.9.4 **/ guint8 fu_hid_device_get_ep_addr_in(FuHidDevice *self) { FuHidDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_HID_DEVICE(self), 0xff); return priv->ep_addr_in; } /** * fu_hid_device_set_ep_addr_out: * @self: a #FuHidDevice * @ep_addr_out: an endpoint, e.g. `0x03` * * Sets the HID USB interrupt out endpoint. * * In most cases the HID EPs are auto-detected, but this function can be * used where there are multiple HID EPs or where the device USB EP is invalid. * * Since: 1.9.4 **/ void fu_hid_device_set_ep_addr_out(FuHidDevice *self, guint8 ep_addr_out) { FuHidDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_HID_DEVICE(self)); priv->ep_addr_out = ep_addr_out; priv->interface_autodetect = FALSE; } /** * fu_hid_device_get_ep_addr_out: * @self: a #FuHidDevice * * Gets the HID USB out endpoint. * * Returns: integer * * Since: 1.9.4 **/ guint8 fu_hid_device_get_ep_addr_out(FuHidDevice *self) { FuHidDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_HID_DEVICE(self), 0xff); return priv->ep_addr_out; } /** * fu_hid_device_add_flag: * @self: a #FuHidDevice * @flag: HID device flags, e.g. %FU_HID_DEVICE_FLAG_RETRY_FAILURE * * Adds a flag to be used for all set and get report messages. * * Since: 1.5.2 **/ void fu_hid_device_add_flag(FuHidDevice *self, FuHidDeviceFlags flag) { FuHidDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_HID_DEVICE(self)); priv->flags |= flag; } typedef struct { guint8 value; guint8 *buf; gsize bufsz; guint timeout; FuHidDeviceFlags flags; } FuHidDeviceRetryHelper; static gboolean fu_hid_device_set_report_internal(FuHidDevice *self, FuHidDeviceRetryHelper *helper, GError **error) { #ifdef HAVE_GUSB FuHidDevicePrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_len = 0; /* what method do we use? */ if (helper->flags & FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER) { g_autofree gchar *title = g_strdup_printf("HID::SetReport [EP=0x%02x]", priv->ep_addr_out); fu_dump_raw(G_LOG_DOMAIN, title, helper->buf, helper->bufsz); if (priv->ep_addr_out == 0x0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no EpAddrOut set"); return FALSE; } if (!g_usb_device_interrupt_transfer(usb_device, priv->ep_addr_out, helper->buf, helper->bufsz, &actual_len, helper->timeout, NULL, /* cancellable */ error)) { return FALSE; } } else { guint16 wvalue = (FU_HID_REPORT_TYPE_OUTPUT << 8) | helper->value; g_autofree gchar *title = NULL; /* special case */ if (helper->flags & FU_HID_DEVICE_FLAG_IS_FEATURE) wvalue = (FU_HID_REPORT_TYPE_FEATURE << 8) | helper->value; title = g_strdup_printf("HID::SetReport [wValue=0x%04x, wIndex=%u]", wvalue, priv->interface); fu_dump_raw(G_LOG_DOMAIN, title, helper->buf, helper->bufsz); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, FU_HID_REPORT_SET, wvalue, priv->interface, helper->buf, helper->bufsz, &actual_len, helper->timeout, NULL, error)) { g_prefix_error(error, "failed to SetReport: "); return FALSE; } } if ((helper->flags & FU_HID_DEVICE_FLAG_ALLOW_TRUNC) == 0 && actual_len != helper->bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "wrote %" G_GSIZE_FORMAT ", requested %" G_GSIZE_FORMAT " bytes", actual_len, helper->bufsz); return FALSE; } #endif return TRUE; } static gboolean fu_hid_device_set_report_internal_cb(FuDevice *device, gpointer user_data, GError **error) { FuHidDevice *self = FU_HID_DEVICE(device); FuHidDeviceRetryHelper *helper = (FuHidDeviceRetryHelper *)user_data; return fu_hid_device_set_report_internal(self, helper, error); } /** * fu_hid_device_set_report: * @self: a #FuHidDevice * @value: low byte of wValue, but unused when using %FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER * @buf: (nullable): a mutable buffer of data to send * @bufsz: size of @buf * @timeout: timeout in ms * @flags: HID device flags e.g. %FU_HID_DEVICE_FLAG_ALLOW_TRUNC * @error: (nullable): optional return location for an error * * Calls SetReport on the hardware. * * Returns: %TRUE for success * * Since: 1.4.0 **/ gboolean fu_hid_device_set_report(FuHidDevice *self, guint8 value, guint8 *buf, gsize bufsz, guint timeout, FuHidDeviceFlags flags, GError **error) { FuHidDeviceRetryHelper helper; FuHidDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_HID_DEVICE(self), FALSE); g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(bufsz != 0, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* create helper */ helper.value = value; helper.buf = buf; helper.bufsz = bufsz; helper.timeout = timeout; helper.flags = priv->flags | flags; /* special case */ if (flags & FU_HID_DEVICE_FLAG_RETRY_FAILURE) { return fu_device_retry(FU_DEVICE(self), fu_hid_device_set_report_internal_cb, FU_HID_DEVICE_RETRIES, &helper, error); } /* just one */ return fu_hid_device_set_report_internal(self, &helper, error); } static gboolean fu_hid_device_get_report_internal(FuHidDevice *self, FuHidDeviceRetryHelper *helper, GError **error) { #ifdef HAVE_GUSB FuHidDevicePrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_len = 0; /* what method do we use? */ if (helper->flags & FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER) { g_autofree gchar *title = NULL; if (priv->ep_addr_in == 0x0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no EpAddrIn set"); return FALSE; } if (!g_usb_device_interrupt_transfer(usb_device, priv->ep_addr_in, helper->buf, helper->bufsz, &actual_len, helper->timeout, NULL, /* cancellable */ error)) { return FALSE; } title = g_strdup_printf("HID::GetReport [EP=0x%02x]", priv->ep_addr_in); fu_dump_raw(G_LOG_DOMAIN, title, helper->buf, helper->bufsz); } else { guint16 wvalue = (FU_HID_REPORT_TYPE_INPUT << 8) | helper->value; g_autofree gchar *title = NULL; /* special case */ if (helper->flags & FU_HID_DEVICE_FLAG_IS_FEATURE) wvalue = (FU_HID_REPORT_TYPE_FEATURE << 8) | helper->value; title = g_strdup_printf("HID::GetReport [wValue=0x%04x, wIndex=%u]", wvalue, priv->interface); fu_dump_raw(G_LOG_DOMAIN, title, helper->buf, helper->bufsz); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, FU_HID_REPORT_GET, wvalue, priv->interface, helper->buf, helper->bufsz, &actual_len, /* actual length */ helper->timeout, NULL, error)) { g_prefix_error(error, "failed to GetReport: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, title, helper->buf, actual_len); } if ((helper->flags & FU_HID_DEVICE_FLAG_ALLOW_TRUNC) == 0 && actual_len != helper->bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "read %" G_GSIZE_FORMAT ", requested %" G_GSIZE_FORMAT " bytes", actual_len, helper->bufsz); return FALSE; } #endif return TRUE; } static gboolean fu_hid_device_get_report_internal_cb(FuDevice *device, gpointer user_data, GError **error) { FuHidDevice *self = FU_HID_DEVICE(device); FuHidDeviceRetryHelper *helper = (FuHidDeviceRetryHelper *)user_data; return fu_hid_device_get_report_internal(self, helper, error); } /** * fu_hid_device_get_report: * @self: a #FuHidDevice * @value: low byte of wValue, but unused when using %FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER * @buf: (nullable): a mutable buffer of data to send * @bufsz: size of @buf * @timeout: timeout in ms * @flags: HID device flags e.g. %FU_HID_DEVICE_FLAG_ALLOW_TRUNC * @error: (nullable): optional return location for an error * * Calls GetReport on the hardware. * * Returns: %TRUE for success * * Since: 1.4.0 **/ gboolean fu_hid_device_get_report(FuHidDevice *self, guint8 value, guint8 *buf, gsize bufsz, guint timeout, FuHidDeviceFlags flags, GError **error) { FuHidDeviceRetryHelper helper; FuHidDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_HID_DEVICE(self), FALSE); g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(bufsz != 0, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* create helper */ helper.value = value; helper.buf = buf; helper.bufsz = bufsz; helper.timeout = timeout; helper.flags = priv->flags | flags; /* special case */ if (flags & FU_HID_DEVICE_FLAG_RETRY_FAILURE) { return fu_device_retry(FU_DEVICE(self), fu_hid_device_get_report_internal_cb, FU_HID_DEVICE_RETRIES, &helper, error); } /* just one */ return fu_hid_device_get_report_internal(self, &helper, error); } static void fu_hid_device_init(FuHidDevice *self) { FuHidDevicePrivate *priv = GET_PRIVATE(self); priv->interface_autodetect = TRUE; } /** * fu_hid_device_new: * @usb_device: a USB device * * Creates a new HID device. * * Returns: (transfer full): a #FuHidDevice * * Since: 1.4.0 **/ FuHidDevice * fu_hid_device_new(GUsbDevice *usb_device) { FuHidDevice *device = g_object_new(FU_TYPE_HID_DEVICE, NULL); fu_usb_device_set_dev(FU_USB_DEVICE(device), usb_device); return FU_HID_DEVICE(device); } static void fu_hid_device_class_init(FuHidDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->get_property = fu_hid_device_get_property; object_class->set_property = fu_hid_device_set_property; klass_device->open = fu_hid_device_open; klass_device->close = fu_hid_device_close; klass_device->to_string = fu_hid_device_to_string; /** * FuHidDevice:interface: * * The HID interface to use. * * Since: 1.4.0 */ pspec = g_param_spec_uint("interface", NULL, NULL, 0x00, 0xff, 0x00, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_INTERFACE, pspec); } fwupd-1.9.16/libfwupdplugin/fu-hid-device.h000066400000000000000000000052551460375044200205660ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-hid-descriptor.h" #include "fu-usb-device.h" #define FU_TYPE_HID_DEVICE (fu_hid_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuHidDevice, fu_hid_device, FU, HID_DEVICE, FuUsbDevice) struct _FuHidDeviceClass { FuUsbDeviceClass parent_class; }; /** * FuHidDeviceFlags: * @FU_HID_DEVICE_FLAG_NONE: No flags set * @FU_HID_DEVICE_FLAG_ALLOW_TRUNC: Allow truncated reads and writes * @FU_HID_DEVICE_FLAG_IS_FEATURE: Use %FU_HID_REPORT_TYPE_FEATURE for wValue * @FU_HID_DEVICE_FLAG_RETRY_FAILURE: Retry up to 10 times on failure * @FU_HID_DEVICE_FLAG_NO_KERNEL_UNBIND: Do not unbind the kernel driver on open * @FU_HID_DEVICE_FLAG_NO_KERNEL_REBIND: Do not rebind the kernel driver on close * @FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER: Use interrupt transfers, not control transfers * @FU_HID_DEVICE_FLAG_AUTODETECT_EPS: Autodetect interface endpoints * * Flags used when calling fu_hid_device_get_report() and fu_hid_device_set_report(). **/ typedef enum { FU_HID_DEVICE_FLAG_NONE = 0, FU_HID_DEVICE_FLAG_ALLOW_TRUNC = 1 << 0, FU_HID_DEVICE_FLAG_IS_FEATURE = 1 << 1, FU_HID_DEVICE_FLAG_RETRY_FAILURE = 1 << 2, FU_HID_DEVICE_FLAG_NO_KERNEL_UNBIND = 1 << 3, FU_HID_DEVICE_FLAG_NO_KERNEL_REBIND = 1 << 4, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER = 1 << 5, FU_HID_DEVICE_FLAG_AUTODETECT_EPS = 1 << 6, /*< private >*/ FU_HID_DEVICE_FLAG_LAST } FuHidDeviceFlags; FuHidDevice * fu_hid_device_new(GUsbDevice *usb_device) G_GNUC_NON_NULL(1); void fu_hid_device_add_flag(FuHidDevice *self, FuHidDeviceFlags flag) G_GNUC_NON_NULL(1); void fu_hid_device_set_interface(FuHidDevice *self, guint8 interface_number) G_GNUC_NON_NULL(1); guint8 fu_hid_device_get_interface(FuHidDevice *self) G_GNUC_NON_NULL(1); void fu_hid_device_set_ep_addr_in(FuHidDevice *self, guint8 ep_addr_in) G_GNUC_NON_NULL(1); guint8 fu_hid_device_get_ep_addr_in(FuHidDevice *self) G_GNUC_NON_NULL(1); void fu_hid_device_set_ep_addr_out(FuHidDevice *self, guint8 ep_addr_out) G_GNUC_NON_NULL(1); guint8 fu_hid_device_get_ep_addr_out(FuHidDevice *self) G_GNUC_NON_NULL(1); FuHidDescriptor * fu_hid_device_parse_descriptor(FuHidDevice *self, GError **error) G_GNUC_NON_NULL(1); gboolean fu_hid_device_set_report(FuHidDevice *self, guint8 value, guint8 *buf, gsize bufsz, guint timeout, FuHidDeviceFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_hid_device_get_report(FuHidDevice *self, guint8 value, guint8 *buf, gsize bufsz, guint timeout, FuHidDeviceFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-hid-report-item.c000066400000000000000000000121471460375044200215670ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuHidDevice" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-hid-report-item.h" #include "fu-mem-private.h" #include "fu-string.h" /** * FuHidReportItem: * * See also: [class@FuHidDescriptor] */ struct _FuHidReportItem { FuFirmware parent_instance; guint32 value; }; G_DEFINE_TYPE(FuHidReportItem, fu_hid_report_item, FU_TYPE_FIRMWARE) FuHidItemKind fu_hid_report_item_get_kind(FuHidReportItem *self) { g_return_val_if_fail(FU_IS_HID_REPORT_ITEM(self), 0); return fu_firmware_get_idx(FU_FIRMWARE(self)) & 0b11; } guint32 fu_hid_report_item_get_value(FuHidReportItem *self) { g_return_val_if_fail(FU_IS_HID_REPORT_ITEM(self), 0); return self->value; } static void fu_hid_report_item_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuHidReportItem *self = FU_HID_REPORT_ITEM(firmware); fu_xmlb_builder_insert_kv(bn, "kind", fu_hid_item_kind_to_string(fu_hid_report_item_get_kind(self))); fu_xmlb_builder_insert_kx(bn, "value", self->value); } static gboolean fu_hid_report_item_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuHidReportItem *self = FU_HID_REPORT_ITEM(firmware); const guint8 size_lookup[] = {0, 1, 2, 4}; gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); guint8 val = buf[offset]; guint8 data_size = size_lookup[val & 0b11]; guint8 tag = (val & 0b11111100) >> 2; fu_firmware_set_idx(firmware, tag); fu_firmware_set_id(firmware, fu_hid_item_tag_to_string(tag)); if (!fu_memchk_read(bufsz, offset, data_size + 1, error)) return FALSE; if (tag == FU_HID_ITEM_TAG_LONG && data_size == 2) { if (offset + 1 >= bufsz) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "not enough data to read long tag"); return FALSE; } data_size = buf[++offset]; } else { g_autoptr(GBytes) img = NULL; if (data_size == 1) { guint8 value = 0; if (!fu_memread_uint8_safe(buf, bufsz, offset + 1, &value, error)) return FALSE; self->value = value; } else if (data_size == 2) { guint16 value = 0; if (!fu_memread_uint16_safe(buf, bufsz, offset + 1, &value, G_LITTLE_ENDIAN, error)) return FALSE; self->value = value; } else if (data_size == 4) { if (!fu_memread_uint32_safe(buf, bufsz, offset + 1, &self->value, G_LITTLE_ENDIAN, error)) return FALSE; } img = fu_bytes_new_offset(fw, offset + 1, data_size, error); if (img == NULL) return FALSE; fu_firmware_set_bytes(firmware, img); } /* success */ fu_firmware_set_size(firmware, 1 + data_size); return TRUE; } static GByteArray * fu_hid_report_item_write(FuFirmware *firmware, GError **error) { FuHidReportItem *self = FU_HID_REPORT_ITEM(firmware); g_autoptr(GByteArray) st = g_byte_array_new(); guint8 tmp = fu_firmware_get_idx(firmware) << 2; if (self->value == 0) { fu_byte_array_append_uint8(st, tmp); } else if (self->value <= G_MAXUINT8) { tmp |= 0b01; fu_byte_array_append_uint8(st, tmp); fu_byte_array_append_uint8(st, self->value); } else if (self->value <= G_MAXUINT16) { tmp |= 0b10; fu_byte_array_append_uint8(st, tmp); fu_byte_array_append_uint16(st, self->value, G_LITTLE_ENDIAN); } else { tmp |= 0b11; fu_byte_array_append_uint8(st, tmp); fu_byte_array_append_uint32(st, self->value, G_LITTLE_ENDIAN); } /* success */ return g_steal_pointer(&st); } static gboolean fu_hid_report_item_build(FuFirmware *firmware, XbNode *n, GError **error) { FuHidReportItem *self = FU_HID_REPORT_ITEM(firmware); const gchar *tmp; guint64 value = 0; /* optional data */ tmp = xb_node_query_text(n, "idx", NULL); if (tmp != NULL) { if (!fu_strtoull(tmp, &value, 0x0, G_MAXUINT8, error)) return FALSE; fu_firmware_set_idx(firmware, value); fu_firmware_set_id(firmware, fu_hid_item_tag_to_string(value)); } tmp = xb_node_query_text(n, "id", NULL); if (tmp != NULL) { fu_firmware_set_id(firmware, tmp); fu_firmware_set_idx(firmware, fu_hid_item_tag_from_string(tmp)); } tmp = xb_node_query_text(n, "value", NULL); if (tmp != NULL) { if (!fu_strtoull(tmp, &value, 0x0, G_MAXUINT32, error)) return FALSE; self->value = value; } /* success */ return TRUE; } static void fu_hid_report_item_init(FuHidReportItem *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); } static void fu_hid_report_item_class_init(FuHidReportItemClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->export = fu_hid_report_item_export; klass_firmware->parse = fu_hid_report_item_parse; klass_firmware->write = fu_hid_report_item_write; klass_firmware->build = fu_hid_report_item_build; } /** * fu_hid_report_item_new: * * Creates a new HID report item * * Returns: (transfer full): a #FuHidReportItem * * Since: 1.9.4 **/ FuHidReportItem * fu_hid_report_item_new(void) { return g_object_new(FU_TYPE_HID_REPORT_ITEM, NULL); } fwupd-1.9.16/libfwupdplugin/fu-hid-report-item.h000066400000000000000000000010411460375044200215630ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #include "fu-hid-struct.h" #define FU_TYPE_HID_REPORT_ITEM (fu_hid_report_item_get_type()) G_DECLARE_FINAL_TYPE(FuHidReportItem, fu_hid_report_item, FU, HID_REPORT_ITEM, FuFirmware) FuHidReportItem * fu_hid_report_item_new(void); FuHidItemKind fu_hid_report_item_get_kind(FuHidReportItem *self) G_GNUC_NON_NULL(1); guint32 fu_hid_report_item_get_value(FuHidReportItem *self) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-hid-report.c000066400000000000000000000016401460375044200206270ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuHidDevice" #include "config.h" #include "fu-hid-report.h" /** * FuHidReport: * * See also: [class@FuHidDescriptor] */ struct _FuHidReport { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuHidReport, fu_hid_report, FU_TYPE_FIRMWARE) static void fu_hid_report_init(FuHidReport *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_DEDUPE_IDX); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); fu_firmware_set_images_max(FU_FIRMWARE(self), G_MAXUINT8); } static void fu_hid_report_class_init(FuHidReportClass *klass) { } /** * fu_hid_report_new: * * Creates a new HID report item * * Returns: (transfer full): a #FuHidReport * * Since: 1.9.4 **/ FuHidReport * fu_hid_report_new(void) { return g_object_new(FU_TYPE_HID_REPORT, NULL); } fwupd-1.9.16/libfwupdplugin/fu-hid-report.h000066400000000000000000000004771460375044200206430ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_HID_REPORT (fu_hid_report_get_type()) G_DECLARE_FINAL_TYPE(FuHidReport, fu_hid_report, FU, HID_REPORT, FuFirmware) FuHidReport * fu_hid_report_new(void); fwupd-1.9.16/libfwupdplugin/fu-hid.rs000066400000000000000000000017131460375044200175210ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString, FromString)] enum HidItemTag { Unknown = 0b0, // Main Input = 0b1000_00, Output = 0b1001_00, Feature = 0b1011_00, Collection = 0b1010_00, EndCollection = 0b1100_00, // Global UsagePage = 0b0000_01, LogicalMinimum = 0b0001_01, LogicalMaximum = 0b0010_01, PhysicalMinimum = 0b0011_01, PhysicalMaximum = 0b0100_01, Unit = 0b0101_01, ReportSize = 0b0111_01, ReportId = 0b1000_01, ReportCount = 0b1001_01, Push = 0b1010_01, Pop = 0b1011_01, // Local Usage = 0b0000_10, UsageMinimum = 0b0001_10, UsageMaximum = 0b0010_10, DesignatorIndex = 0b0011_10, DesignatorMinimum = 0b0100_10, DesignatorMaximum = 0b0101_10, StringIndex = 0b0111_10, StringMinimum = 0b1000_10, StringMaximum = 0b1001_10, // 'just' supported Long = 0b1111, } #[derive(ToString)] enum HidItemKind { Main, Global, Local, } fwupd-1.9.16/libfwupdplugin/fu-hwids-config.c000066400000000000000000000013441460375044200211340ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuContext" #include "config.h" #include "fu-config.h" #include "fu-context-private.h" #include "fu-hwids-private.h" #include "fu-path.h" gboolean fu_hwids_config_setup(FuContext *ctx, FuHwids *self, GError **error) { FuConfig *config = fu_context_get_config(ctx); g_autoptr(GPtrArray) keys = fu_hwids_get_keys(self); /* all keys are optional */ for (guint i = 0; i < keys->len; i++) { const gchar *key = g_ptr_array_index(keys, i); g_autofree gchar *value = fu_config_get_value(config, "fwupd", key); if (value != NULL) fu_hwids_add_value(self, key, value); } /* success */ return TRUE; } fwupd-1.9.16/libfwupdplugin/fu-hwids-darwin.c000066400000000000000000000034041460375044200211520ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuContext" #include "config.h" #include "fu-context-private.h" #include "fu-hwids-private.h" gboolean fu_hwids_darwin_setup(FuContext *ctx, FuHwids *self, GError **error) { #ifdef HOST_MACHINE_SYSTEM_DARWIN struct { const gchar *hwid; const gchar *key; } map[] = {{FU_HWIDS_KEY_BIOS_VERSION, "System Firmware Version"}, {FU_HWIDS_KEY_FAMILY, "Model Name"}, {FU_HWIDS_KEY_PRODUCT_NAME, "Model Identifier"}, {NULL}}; const gchar *family = NULL; g_autofree gchar *standard_output = NULL; g_auto(GStrv) lines = NULL; /* parse the profiler output */ if (!g_spawn_command_line_sync("system_profiler SPHardwareDataType", &standard_output, NULL, NULL, error)) return FALSE; lines = g_strsplit(standard_output, "\n", -1); for (guint j = 0; lines[j] != NULL; j++) { for (guint i = 0; map[i].key != NULL; i++) { g_auto(GStrv) chunks = g_strsplit(lines[j], ":", 2); if (g_strv_length(chunks) != 2) continue; if (g_strstr_len(chunks[0], -1, map[i].key) != NULL) fu_hwids_add_value(self, map[i].hwid, g_strstrip(chunks[1])); } } /* this has to be hardcoded */ fu_hwids_add_value(self, FU_HWIDS_KEY_BASEBOARD_MANUFACTURER, "Apple"); fu_hwids_add_value(self, FU_HWIDS_KEY_MANUFACTURER, "Apple"); fu_hwids_add_value(self, FU_HWIDS_KEY_BIOS_VENDOR, "Apple"); /* set the chassis kind using the family */ family = fu_hwids_get_value(self, FU_HWIDS_KEY_FAMILY); if (g_strcmp0(family, "MacBook Pro") == 0) { fu_hwids_add_value(self, FU_HWIDS_KEY_ENCLOSURE_KIND, "a"); fu_context_set_chassis_kind(ctx, FU_SMBIOS_CHASSIS_KIND_LAPTOP); } #endif /* success */ return TRUE; } fwupd-1.9.16/libfwupdplugin/fu-hwids-dmi.c000066400000000000000000000040231460375044200204350ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuContext" #include "config.h" #include "fu-context-private.h" #include "fu-hwids-private.h" #include "fu-path.h" #include "fu-string.h" gboolean fu_hwids_dmi_setup(FuContext *ctx, FuHwids *self, GError **error) { g_autofree gchar *path = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_DMI); struct { const gchar *hwid; const gchar *key; } map[] = {{FU_HWIDS_KEY_BASEBOARD_MANUFACTURER, "board_vendor"}, {FU_HWIDS_KEY_BASEBOARD_PRODUCT, "board_name"}, {FU_HWIDS_KEY_BIOS_VENDOR, "bios_vendor"}, {FU_HWIDS_KEY_BIOS_VERSION, "bios_version"}, {FU_HWIDS_KEY_FAMILY, "product_family"}, {FU_HWIDS_KEY_MANUFACTURER, "sys_vendor"}, {FU_HWIDS_KEY_PRODUCT_NAME, "product_name"}, {FU_HWIDS_KEY_PRODUCT_SKU, "product_sku"}, {FU_HWIDS_KEY_ENCLOSURE_KIND, "chassis_type"}, {NULL, NULL}}; /* the values the kernel parsed; these are world-readable */ if (!g_file_test(path, G_FILE_TEST_IS_DIR)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no %s", path); return FALSE; } for (guint i = 0; map[i].key != NULL; i++) { gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autofree gchar *fn = g_build_filename(path, map[i].key, NULL); g_autoptr(GError) error_local = NULL; if (!g_file_get_contents(fn, &buf, &bufsz, &error_local)) { g_debug("unable to read SMBIOS data from %s: %s", fn, error_local->message); continue; } if (bufsz == 0) continue; /* trim trailing newline added by kernel */ if (buf[bufsz - 1] == '\n') buf[bufsz - 1] = 0; fu_hwids_add_value(self, map[i].hwid, buf); if (g_strcmp0(map[i].hwid, FU_HWIDS_KEY_ENCLOSURE_KIND) == 0) { guint64 val = 0; if (!fu_strtoull(buf, &val, FU_SMBIOS_CHASSIS_KIND_OTHER, FU_SMBIOS_CHASSIS_KIND_LAST, &error_local)) { g_warning("ignoring enclosure kind %s", buf); continue; } fu_context_set_chassis_kind(ctx, val); } } /* success */ return TRUE; } fwupd-1.9.16/libfwupdplugin/fu-hwids-fdt.c000066400000000000000000000062451460375044200204510ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuContext" #include "config.h" #include "fu-context-private.h" #include "fu-fdt-firmware.h" #include "fu-hwids-private.h" gboolean fu_hwids_fdt_setup(FuContext *ctx, FuHwids *self, GError **error) { g_autofree gchar *chassis_type = NULL; g_auto(GStrv) compatible = NULL; g_autoptr(FuFirmware) fdt_img = NULL; g_autoptr(FuFdtImage) fdt_img_fwver = NULL; g_autoptr(FuFirmware) fdt = NULL; struct { const gchar *hwid; const gchar *key; } map[] = {{FU_HWIDS_KEY_MANUFACTURER, "vendor"}, {FU_HWIDS_KEY_FAMILY, "model-name"}, {FU_HWIDS_KEY_PRODUCT_NAME, "model"}, {NULL, NULL}}; /* adds compatible GUIDs */ fdt = fu_context_get_fdt(ctx, error); if (fdt == NULL) return FALSE; fdt_img = fu_firmware_get_image_by_id(fdt, NULL, error); if (fdt_img == NULL) return FALSE; if (!fu_fdt_image_get_attr_strlist(FU_FDT_IMAGE(fdt_img), "compatible", &compatible, error)) return FALSE; for (guint i = 0; compatible[i] != NULL; i++) { g_autofree gchar *guid = fwupd_guid_hash_string(compatible[i]); g_debug("using %s for DT compatible %s", guid, compatible[i]); fu_hwids_add_guid(self, guid); } /* root node */ for (guint i = 0; map[i].key != NULL; i++) { g_autofree gchar *tmp = NULL; fu_fdt_image_get_attr_str(FU_FDT_IMAGE(fdt_img), map[i].key, &tmp, NULL); if (tmp == NULL) continue; fu_hwids_add_value(self, map[i].hwid, tmp); } /* chassis kind */ fu_fdt_image_get_attr_str(FU_FDT_IMAGE(fdt_img), "chassis-type", &chassis_type, NULL); if (chassis_type != NULL) { struct { FuSmbiosChassisKind chassis_kind; const gchar *dt; } chassis_map[] = {{FU_SMBIOS_CHASSIS_KIND_CONVERTIBLE, "convertible"}, {FU_SMBIOS_CHASSIS_KIND_EMBEDDED_PC, "embedded"}, {FU_SMBIOS_CHASSIS_KIND_HAND_HELD, "handset"}, {FU_SMBIOS_CHASSIS_KIND_LAPTOP, "laptop"}, {FU_SMBIOS_CHASSIS_KIND_TABLET, "tablet"}, {FU_SMBIOS_CHASSIS_KIND_UNKNOWN, NULL}}; for (guint i = 0; chassis_map[i].dt != NULL; i++) { if (g_strcmp0(chassis_type, chassis_map[i].dt) == 0) { fu_context_set_chassis_kind(ctx, chassis_map[i].chassis_kind); break; } } } /* fallback */ if (g_strv_length(compatible) > 0) { g_auto(GStrv) compatible0 = g_strsplit(compatible[0], ",", -1); fu_hwids_add_value(self, FU_HWIDS_KEY_MANUFACTURER, compatible0[0]); if (g_strv_length(compatible0) > 1) fu_hwids_add_value(self, FU_HWIDS_KEY_PRODUCT_NAME, compatible0[1]); } if (g_strv_length(compatible) > 1) fu_hwids_add_value(self, FU_HWIDS_KEY_FAMILY, compatible[1]); if (fu_context_get_chassis_kind(ctx) == FU_SMBIOS_CHASSIS_KIND_UNKNOWN) { if (fu_fdt_image_get_attr_str(FU_FDT_IMAGE(fdt_img), "battery", NULL, NULL)) fu_context_set_chassis_kind(ctx, FU_SMBIOS_CHASSIS_KIND_PORTABLE); } fdt_img_fwver = fu_fdt_firmware_get_image_by_path(FU_FDT_FIRMWARE(fdt), "ibm,firmware-versions", NULL); if (fdt_img_fwver != NULL) { g_autofree gchar *version = NULL; fu_fdt_image_get_attr_str(FU_FDT_IMAGE(fdt_img), "version", &version, NULL); fu_hwids_add_value(self, FU_HWIDS_KEY_BIOS_VERSION, version); } /* success */ return TRUE; } fwupd-1.9.16/libfwupdplugin/fu-hwids-kenv.c000066400000000000000000000023141460375044200206300ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuContext" #include "config.h" #include "fu-context-private.h" #include "fu-hwids-private.h" #include "fu-kenv.h" gboolean fu_hwids_kenv_setup(FuContext *ctx, FuHwids *self, GError **error) { #ifdef HAVE_KENV_H struct { const gchar *hwid; const gchar *key; } map[] = {{FU_HWIDS_KEY_BASEBOARD_MANUFACTURER, "smbios.planar.maker"}, {FU_HWIDS_KEY_BASEBOARD_PRODUCT, "smbios.planar.product"}, {FU_HWIDS_KEY_BIOS_VENDOR, "smbios.bios.vendor"}, {FU_HWIDS_KEY_BIOS_VERSION, "smbios.bios.version"}, {FU_HWIDS_KEY_FAMILY, "smbios.system.family"}, {FU_HWIDS_KEY_MANUFACTURER, "smbios.system.maker"}, {FU_HWIDS_KEY_PRODUCT_NAME, "smbios.system.product"}, {FU_HWIDS_KEY_PRODUCT_SKU, "smbios.system.sku"}, {{NULL, NULL}}}; for (guint i = 0; map[i].key != NULL; i++) { g_autoptr(GError) error_local = NULL; g_autofree gchar *value = fu_kenv_get_string(map[i].key, error_local); if (value == NULL) { g_debug("ignoring: %s", error_local->message); continue; } fu_hwids_add_value(self, map[i].hwid, value); } #endif /* success */ return TRUE; } fwupd-1.9.16/libfwupdplugin/fu-hwids-private.h000066400000000000000000000015601460375044200213460ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-context.h" #include "fu-hwids.h" FuHwids * fu_hwids_new(void); gboolean fu_hwids_setup(FuHwids *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_hwids_config_setup(FuContext *ctx, FuHwids *self, GError **error) G_GNUC_NON_NULL(1); gboolean fu_hwids_dmi_setup(FuContext *ctx, FuHwids *self, GError **error) G_GNUC_NON_NULL(1); gboolean fu_hwids_fdt_setup(FuContext *ctx, FuHwids *self, GError **error) G_GNUC_NON_NULL(1); gboolean fu_hwids_kenv_setup(FuContext *ctx, FuHwids *self, GError **error) G_GNUC_NON_NULL(1); gboolean fu_hwids_darwin_setup(FuContext *ctx, FuHwids *self, GError **error) G_GNUC_NON_NULL(1); gboolean fu_hwids_smbios_setup(FuContext *ctx, FuHwids *self, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-hwids-smbios.c000066400000000000000000000101151460375044200211570ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuContext" #include "config.h" #include "fu-context-private.h" #include "fu-hwids-private.h" #include "fu-smbios-private.h" #include "fu-string.h" typedef gchar *(*FuContextHwidConvertFunc)(FuSmbios *smbios, guint8 type, guint8 offset, GError **error); static gchar * fu_hwids_smbios_convert_string_table_cb(FuSmbios *smbios, guint8 type, guint8 offset, GError **error) { const gchar *tmp = fu_smbios_get_string(smbios, type, offset, error); if (tmp == NULL) return NULL; /* ComputerHardwareIds.exe seems to strip spaces */ return fu_strstrip(tmp); } static gchar * fu_hwids_smbios_convert_padded_integer_cb(FuSmbios *smbios, guint8 type, guint8 offset, GError **error) { guint tmp = fu_smbios_get_integer(smbios, type, offset, error); if (tmp == G_MAXUINT) return NULL; return g_strdup_printf("%02x", tmp); } static gchar * fu_hwids_smbios_convert_integer_cb(FuSmbios *smbios, guint8 type, guint8 offset, GError **error) { guint tmp = fu_smbios_get_integer(smbios, type, offset, error); if (tmp == G_MAXUINT) return NULL; return g_strdup_printf("%x", tmp); } gboolean fu_hwids_smbios_setup(FuContext *ctx, FuHwids *self, GError **error) { FuSmbios *smbios = fu_context_get_smbios(ctx); struct { const gchar *key; guint8 type; guint8 offset; FuContextHwidConvertFunc func; } map[] = {{FU_HWIDS_KEY_MANUFACTURER, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x04, fu_hwids_smbios_convert_string_table_cb}, {FU_HWIDS_KEY_ENCLOSURE_KIND, FU_SMBIOS_STRUCTURE_TYPE_CHASSIS, 0x05, fu_hwids_smbios_convert_integer_cb}, {FU_HWIDS_KEY_FAMILY, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x1a, fu_hwids_smbios_convert_string_table_cb}, {FU_HWIDS_KEY_PRODUCT_NAME, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x05, fu_hwids_smbios_convert_string_table_cb}, {FU_HWIDS_KEY_PRODUCT_SKU, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM, 0x19, fu_hwids_smbios_convert_string_table_cb}, {FU_HWIDS_KEY_BIOS_VENDOR, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x04, fu_hwids_smbios_convert_string_table_cb}, {FU_HWIDS_KEY_BIOS_VERSION, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x05, fu_hwids_smbios_convert_string_table_cb}, {FU_HWIDS_KEY_BIOS_MAJOR_RELEASE, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x14, fu_hwids_smbios_convert_padded_integer_cb}, {FU_HWIDS_KEY_BIOS_MINOR_RELEASE, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x15, fu_hwids_smbios_convert_padded_integer_cb}, {FU_HWIDS_KEY_FIRMWARE_MAJOR_RELEASE, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x16, fu_hwids_smbios_convert_padded_integer_cb}, {FU_HWIDS_KEY_FIRMWARE_MINOR_RELEASE, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x17, fu_hwids_smbios_convert_padded_integer_cb}, {FU_HWIDS_KEY_BASEBOARD_MANUFACTURER, FU_SMBIOS_STRUCTURE_TYPE_BASEBOARD, 0x04, fu_hwids_smbios_convert_string_table_cb}, {FU_HWIDS_KEY_BASEBOARD_PRODUCT, FU_SMBIOS_STRUCTURE_TYPE_BASEBOARD, 0x05, fu_hwids_smbios_convert_string_table_cb}, {NULL, 0x00, 0x00, NULL}}; if (!fu_smbios_setup(smbios, error)) return FALSE; /* get all DMI data from SMBIOS */ fu_context_set_chassis_kind( ctx, fu_smbios_get_integer(smbios, FU_SMBIOS_STRUCTURE_TYPE_CHASSIS, 0x05, NULL)); for (guint i = 0; map[i].key != NULL; i++) { const gchar *contents_hdr = NULL; g_autofree gchar *contents = NULL; g_autoptr(GError) error_local = NULL; contents = map[i].func(smbios, map[i].type, map[i].offset, &error_local); if (contents == NULL) { g_debug("ignoring %s: %s", map[i].key, error_local->message); continue; } g_info("SMBIOS %s=%s", map[i].key, contents); /* weirdly, remove leading zeros */ contents_hdr = contents; while (contents_hdr[0] == '0' && map[i].func != fu_hwids_smbios_convert_padded_integer_cb) contents_hdr++; fu_hwids_add_value(self, map[i].key, contents_hdr); } /* success */ return TRUE; } fwupd-1.9.16/libfwupdplugin/fu-hwids.c000066400000000000000000000272651460375044200177030ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuHwids" #include "config.h" #include #include #include "fwupd-common.h" #include "fwupd-error.h" #include "fu-common.h" #include "fu-hwids-private.h" #include "fu-path.h" #include "fu-string.h" /** * FuHwids: * * A the hardware IDs on the system. * * Note, these are called "CHIDs" in Microsoft Windows and the results here * will match that of `ComputerHardwareIds.exe`. * * See also: [class@FuSmbios] */ struct _FuHwids { GObject parent_instance; GHashTable *hash_values; /* BiosVersion->"1.2.3 " */ GHashTable *hash_values_display; /* BiosVersion->"1.2.3" */ GHashTable *hash_guid; /* a-c-b-d->1 */ GPtrArray *array_guids; /* a-c-b-d */ }; G_DEFINE_TYPE(FuHwids, fu_hwids, G_TYPE_OBJECT) /** * fu_hwids_get_value: * @self: a #FuHwids * @key: a DMI ID, e.g. `BiosVersion` * * Gets the cached value for one specific key that is valid ASCII and suitable * for display. * * Returns: the string, e.g. `1.2.3`, or %NULL if not found * * Since: 0.9.3 **/ const gchar * fu_hwids_get_value(FuHwids *self, const gchar *key) { return g_hash_table_lookup(self->hash_values_display, key); } /** * fu_hwids_has_guid: * @self: a #FuHwids * @guid: a GUID, e.g. `059eb22d-6dc7-59af-abd3-94bbe017f67c` * * Finds out if a hardware GUID exists. * * Returns: %TRUE if the GUID exists * * Since: 0.9.3 **/ gboolean fu_hwids_has_guid(FuHwids *self, const gchar *guid) { return g_hash_table_lookup(self->hash_guid, guid) != NULL; } /** * fu_hwids_get_guids: * @self: a #FuHwids * * Returns all the defined HWIDs * * Returns: (transfer none) (element-type utf8): an array of GUIDs * * Since: 0.9.3 **/ GPtrArray * fu_hwids_get_guids(FuHwids *self) { return self->array_guids; } /** * fu_hwids_get_keys: * @self: a #FuHwids * * Returns all the defined HWID keys. * * Returns: (transfer container) (element-type utf8): All the known keys, * e.g. %FU_HWIDS_KEY_FAMILY * * Since: 1.5.6 **/ GPtrArray * fu_hwids_get_keys(FuHwids *self) { GPtrArray *array = g_ptr_array_new(); const gchar *keys[] = {FU_HWIDS_KEY_BIOS_VENDOR, FU_HWIDS_KEY_BIOS_VERSION, FU_HWIDS_KEY_BIOS_MAJOR_RELEASE, FU_HWIDS_KEY_BIOS_MINOR_RELEASE, FU_HWIDS_KEY_FIRMWARE_MAJOR_RELEASE, FU_HWIDS_KEY_FIRMWARE_MINOR_RELEASE, FU_HWIDS_KEY_MANUFACTURER, FU_HWIDS_KEY_FAMILY, FU_HWIDS_KEY_PRODUCT_NAME, FU_HWIDS_KEY_PRODUCT_SKU, FU_HWIDS_KEY_ENCLOSURE_KIND, FU_HWIDS_KEY_BASEBOARD_MANUFACTURER, FU_HWIDS_KEY_BASEBOARD_PRODUCT, NULL}; g_return_val_if_fail(FU_IS_HWIDS(self), NULL); for (guint i = 0; keys[i] != NULL; i++) g_ptr_array_add(array, (gpointer)keys[i]); return array; } static gchar * fu_hwids_get_guid_for_str(const gchar *str, GError **error) { glong items_written = 0; g_autofree gunichar2 *data = NULL; /* convert to UTF-16 and convert to GUID using custom namespace */ data = g_utf8_to_utf16(str, -1, NULL, &items_written, error); if (data == NULL) return NULL; if (items_written == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no GUIDs in data"); return NULL; } /* ensure the data is in little endian format */ for (glong i = 0; i < items_written; i++) data[i] = GUINT16_TO_LE(data[i]); /* convert to a GUID */ return fwupd_guid_hash_data((guint8 *)data, items_written * 2, FWUPD_GUID_FLAG_NAMESPACE_MICROSOFT); } /** * fu_hwids_get_replace_keys: * @self: a #FuHwids * @key: a HardwareID key, e.g. `HardwareID-3` * * Gets the replacement key for a well known value. * * Returns: the replacement value, e.g. `Manufacturer&ProductName`, or %NULL for error. * * Since: 0.9.3 **/ const gchar * fu_hwids_get_replace_keys(FuHwids *self, const gchar *key) { struct { const gchar *search; const gchar *replace; } msdefined[] = { {"HardwareID-0", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_PRODUCT_SKU "&" FU_HWIDS_KEY_BIOS_VENDOR "&" FU_HWIDS_KEY_BIOS_VERSION "&" FU_HWIDS_KEY_BIOS_MAJOR_RELEASE "&" FU_HWIDS_KEY_BIOS_MINOR_RELEASE}, {"HardwareID-1", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_BIOS_VENDOR "&" FU_HWIDS_KEY_BIOS_VERSION "&" FU_HWIDS_KEY_BIOS_MAJOR_RELEASE "&" FU_HWIDS_KEY_BIOS_MINOR_RELEASE}, {"HardwareID-2", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_BIOS_VENDOR "&" FU_HWIDS_KEY_BIOS_VERSION "&" FU_HWIDS_KEY_BIOS_MAJOR_RELEASE "&" FU_HWIDS_KEY_BIOS_MINOR_RELEASE}, {"HardwareID-3", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_PRODUCT_SKU "&" FU_HWIDS_KEY_BASEBOARD_MANUFACTURER "&" FU_HWIDS_KEY_BASEBOARD_PRODUCT}, {"HardwareID-4", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_PRODUCT_SKU}, {"HardwareID-5", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_PRODUCT_NAME}, {"HardwareID-6", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_PRODUCT_SKU "&" FU_HWIDS_KEY_BASEBOARD_MANUFACTURER "&" FU_HWIDS_KEY_BASEBOARD_PRODUCT}, {"HardwareID-7", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_PRODUCT_SKU}, {"HardwareID-8", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_BASEBOARD_MANUFACTURER "&" FU_HWIDS_KEY_BASEBOARD_PRODUCT}, {"HardwareID-9", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_PRODUCT_NAME}, {"HardwareID-10", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_BASEBOARD_MANUFACTURER "&" FU_HWIDS_KEY_BASEBOARD_PRODUCT}, {"HardwareID-11", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY}, {"HardwareID-12", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_ENCLOSURE_KIND}, {"HardwareID-13", FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_BASEBOARD_MANUFACTURER "&" FU_HWIDS_KEY_BASEBOARD_PRODUCT}, {"HardwareID-14", FU_HWIDS_KEY_MANUFACTURER}, {NULL, NULL}}; g_return_val_if_fail(FU_IS_HWIDS(self), NULL); g_return_val_if_fail(key != NULL, NULL); /* defined for Windows 10 */ for (guint i = 0; msdefined[i].search != NULL; i++) { if (g_strcmp0(msdefined[i].search, key) == 0) { key = msdefined[i].replace; break; } } return key; } /** * fu_hwids_add_value: * @self: a #FuHwids * @key: a key, e.g. %FU_HWIDS_KEY_PRODUCT_SKU * @value: (nullable): a new value, e.g. `ExampleModel` * * Sets override values so you can emulate another system. * * This function has no effect if called after fu_hwids_setup() * * Since: 1.8.10 **/ void fu_hwids_add_value(FuHwids *self, const gchar *key, const gchar *value) { g_return_if_fail(FU_IS_HWIDS(self)); g_return_if_fail(key != NULL); /* does not replace; first value set wins */ if (g_hash_table_contains(self->hash_values, key)) return; g_hash_table_insert(self->hash_values, g_strdup(key), g_strdup(value)); /* make suitable for display */ if (value != NULL) { g_autofree gchar *value_safe = g_str_to_ascii(value, "C"); g_strdelimit(value_safe, "\n\r", '\0'); g_strchomp(value_safe); g_hash_table_insert(self->hash_values_display, g_strdup(key), g_steal_pointer(&value_safe)); } else { g_hash_table_insert(self->hash_values_display, g_strdup(key), NULL); } } /** * fu_hwids_get_replace_values: * @self: a #FuHwids * @keys: a key, e.g. `HardwareID-3` or %FU_HWIDS_KEY_PRODUCT_SKU * @error: (nullable): optional return location for an error * * Gets the replacement values for a HardwareID key or plain key. * * Returns: a string, e.g. `LENOVO&ThinkPad T440s`, or %NULL for error. * * Since: 0.9.3 **/ gchar * fu_hwids_get_replace_values(FuHwids *self, const gchar *keys, GError **error) { g_auto(GStrv) split = NULL; g_autoptr(GString) str = g_string_new(NULL); g_return_val_if_fail(FU_IS_HWIDS(self), NULL); g_return_val_if_fail(keys != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* do any replacements */ keys = fu_hwids_get_replace_keys(self, keys); /* get each part of the HWID */ split = g_strsplit(keys, "&", -1); for (guint j = 0; split[j] != NULL; j++) { const gchar *tmp = g_hash_table_lookup(self->hash_values, split[j]); if (tmp == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "not available as '%s' unknown", split[j]); return NULL; } g_string_append_printf(str, "%s&", tmp); } if (str->len > 0) g_string_truncate(str, str->len - 1); return g_strdup(str->str); } /** * fu_hwids_get_guid: * @self: a #FuHwids * @keys: a key, e.g. `HardwareID-3` or %FU_HWIDS_KEY_PRODUCT_SKU * @error: (nullable): optional return location for an error * * Gets the GUID for a specific key. * * Returns: a string, or %NULL for error. * * Since: 0.9.3 **/ gchar * fu_hwids_get_guid(FuHwids *self, const gchar *keys, GError **error) { g_autofree gchar *tmp = NULL; g_return_val_if_fail(FU_IS_HWIDS(self), NULL); g_return_val_if_fail(keys != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); tmp = fu_hwids_get_replace_values(self, keys, error); if (tmp == NULL) return NULL; return fu_hwids_get_guid_for_str(tmp, error); } /** * fu_hwids_add_guid: * @self: a #FuHwids * @guid: a GUID * * Adds a HWID GUID value. * * Since: 1.8.10 **/ void fu_hwids_add_guid(FuHwids *self, const gchar *guid) { g_return_if_fail(FU_IS_HWIDS(self)); g_return_if_fail(guid != NULL); g_hash_table_insert(self->hash_guid, g_strdup(guid), GUINT_TO_POINTER(1)); g_ptr_array_add(self->array_guids, g_strdup(guid)); } /** * fu_hwids_setup: * @self: a #FuHwids * @error: (nullable): optional return location for an error * * Adds all the `HardwareID` GUIDs from the previously supplied data. * * Returns: %TRUE for success * * Since: 0.9.3 **/ gboolean fu_hwids_setup(FuHwids *self, GError **error) { g_return_val_if_fail(FU_IS_HWIDS(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* add GUIDs */ for (guint i = 0; i < 15; i++) { g_autofree gchar *guid = NULL; g_autofree gchar *key = NULL; g_autoptr(GError) error_local = NULL; /* get the GUID and add to hash */ key = g_strdup_printf("HardwareID-%u", i); guid = fu_hwids_get_guid(self, key, &error_local); if (guid == NULL) { g_debug("%s is not available, %s", key, error_local->message); continue; } fu_hwids_add_guid(self, guid); } return TRUE; } static void fu_hwids_finalize(GObject *object) { FuHwids *self; g_return_if_fail(FU_IS_HWIDS(object)); self = FU_HWIDS(object); g_hash_table_unref(self->hash_values); g_hash_table_unref(self->hash_values_display); g_hash_table_unref(self->hash_guid); g_ptr_array_unref(self->array_guids); G_OBJECT_CLASS(fu_hwids_parent_class)->finalize(object); } static void fu_hwids_class_init(FuHwidsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_hwids_finalize; } static void fu_hwids_init(FuHwids *self) { self->hash_values = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); self->hash_values_display = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); self->hash_guid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); self->array_guids = g_ptr_array_new_with_free_func(g_free); } /** * fu_hwids_new: * * Creates a new #FuHwids * * Since: 0.9.3 **/ FuHwids * fu_hwids_new(void) { FuHwids *self; self = g_object_new(FU_TYPE_HWIDS, NULL); return FU_HWIDS(self); } fwupd-1.9.16/libfwupdplugin/fu-hwids.h000066400000000000000000000062421460375044200177000ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-smbios.h" #define FU_TYPE_HWIDS (fu_hwids_get_type()) G_DECLARE_FINAL_TYPE(FuHwids, fu_hwids, FU, HWIDS, GObject) /** * FU_HWIDS_KEY_BASEBOARD_MANUFACTURER: * * The HwID key for the baseboard (motherboard) vendor. * * Since: 1.3.7 **/ #define FU_HWIDS_KEY_BASEBOARD_MANUFACTURER "BaseboardManufacturer" /** * FU_HWIDS_KEY_BASEBOARD_PRODUCT: * * The HwID key for baseboard (motherboard) product. * * Since: 1.3.7 **/ #define FU_HWIDS_KEY_BASEBOARD_PRODUCT "BaseboardProduct" /** * FU_HWIDS_KEY_BIOS_MAJOR_RELEASE: * * The HwID key for the BIOS major version. * * Since: 1.3.7 **/ #define FU_HWIDS_KEY_BIOS_MAJOR_RELEASE "BiosMajorRelease" /** * FU_HWIDS_KEY_BIOS_MINOR_RELEASE: * * The HwID key for the BIOS minor version. * * Since: 1.3.7 **/ #define FU_HWIDS_KEY_BIOS_MINOR_RELEASE "BiosMinorRelease" /** * FU_HWIDS_KEY_BIOS_VENDOR: * * The HwID key for the BIOS vendor. * * Since: 1.3.7 **/ #define FU_HWIDS_KEY_BIOS_VENDOR "BiosVendor" /** * FU_HWIDS_KEY_BIOS_VERSION: * * The HwID key for the BIOS version. * * Since: 1.3.7 **/ #define FU_HWIDS_KEY_BIOS_VERSION "BiosVersion" /** * FU_HWIDS_KEY_FIRMWARE_MAJOR_RELEASE: * * The HwID key for the firmware major version. * * Since: 1.6.1 **/ #define FU_HWIDS_KEY_FIRMWARE_MAJOR_RELEASE "FirmwareMajorRelease" /** * FU_HWIDS_KEY_FIRMWARE_MINOR_RELEASE: * * The HwID key for the firmware minor version. * * Since: 1.6.1 **/ #define FU_HWIDS_KEY_FIRMWARE_MINOR_RELEASE "FirmwareMinorRelease" /** * FU_HWIDS_KEY_ENCLOSURE_KIND: * * The HwID key for the enclosure kind. * * Since: 1.3.7 **/ #define FU_HWIDS_KEY_ENCLOSURE_KIND "EnclosureKind" /** * FU_HWIDS_KEY_FAMILY: * * The HwID key for the deice family. * * Since: 1.3.7 **/ #define FU_HWIDS_KEY_FAMILY "Family" /** * FU_HWIDS_KEY_MANUFACTURER: * * The HwID key for the top-level product vendor. * * Since: 1.3.7 **/ #define FU_HWIDS_KEY_MANUFACTURER "Manufacturer" /** * FU_HWIDS_KEY_PRODUCT_NAME: * * The HwID key for the top-level product name. * * Since: 1.3.7 **/ #define FU_HWIDS_KEY_PRODUCT_NAME "ProductName" /** * FU_HWIDS_KEY_PRODUCT_SKU: * * The HwID key for the top-level product SKU. * * Since: 1.3.7 **/ #define FU_HWIDS_KEY_PRODUCT_SKU "ProductSku" GPtrArray * fu_hwids_get_keys(FuHwids *self) G_GNUC_NON_NULL(1); const gchar * fu_hwids_get_value(FuHwids *self, const gchar *key) G_GNUC_NON_NULL(1, 2); void fu_hwids_add_value(FuHwids *self, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2); const gchar * fu_hwids_get_replace_keys(FuHwids *self, const gchar *key) G_GNUC_NON_NULL(1, 2); gchar * fu_hwids_get_replace_values(FuHwids *self, const gchar *keys, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gchar * fu_hwids_get_guid(FuHwids *self, const gchar *keys, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GPtrArray * fu_hwids_get_guids(FuHwids *self) G_GNUC_NON_NULL(1); void fu_hwids_add_guid(FuHwids *self, const gchar *guid) G_GNUC_NON_NULL(1); gboolean fu_hwids_has_guid(FuHwids *self, const gchar *guid) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-i2c-device.c000066400000000000000000000157471460375044200205010ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuI2cDevice" #include "config.h" #include #include #include #include #ifdef HAVE_ERRNO_H #include #endif #include "fu-i2c-device.h" #include "fu-string.h" #include "fu-udev-device-private.h" /** * FuI2cDevice * * A I²C device with an assigned bus number. * * See also: #FuUdevDevice */ typedef struct { guint bus_number; } FuI2cDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuI2cDevice, fu_i2c_device, FU_TYPE_UDEV_DEVICE) enum { PROP_0, PROP_BUS_NUMBER, PROP_LAST }; #define GET_PRIVATE(o) (fu_i2c_device_get_instance_private(o)) static void fu_i2c_device_to_string(FuDevice *device, guint idt, GString *str) { FuI2cDevice *self = FU_I2C_DEVICE(device); FuI2cDevicePrivate *priv = GET_PRIVATE(self); fu_string_append_kx(str, idt, "BusNumber", priv->bus_number); } static void fu_i2c_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuI2cDevice *self = FU_I2C_DEVICE(object); FuI2cDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_BUS_NUMBER: g_value_set_uint(value, priv->bus_number); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_i2c_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuI2cDevice *self = FU_I2C_DEVICE(object); FuI2cDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_BUS_NUMBER: priv->bus_number = g_value_get_uint(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static gboolean fu_i2c_device_open(FuDevice *device, GError **error) { #ifdef HAVE_GUDEV FuI2cDevice *self = FU_I2C_DEVICE(device); FuI2cDevicePrivate *priv = GET_PRIVATE(self); gint bus_fd; g_autofree gchar *bus_path = NULL; g_autoptr(FuIOChannel) io_channel = NULL; /* open the bus, not the device represented by self */ bus_path = g_strdup_printf("/dev/i2c-%u", priv->bus_number); if ((bus_fd = g_open(bus_path, O_RDWR, 0)) == -1) { g_set_error(error, G_IO_ERROR, #ifdef HAVE_ERRNO_H g_io_error_from_errno(errno), #else G_IO_ERROR_FAILED, #endif "failed to open %s read-write", bus_path); return FALSE; } io_channel = fu_io_channel_unix_new(bus_fd); fu_udev_device_set_io_channel(FU_UDEV_DEVICE(self), io_channel); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_NONE); #endif /* FuUdevDevice->open */ return FU_DEVICE_CLASS(fu_i2c_device_parent_class)->open(device, error); } static gboolean fu_i2c_device_probe(FuDevice *device, GError **error) { #ifdef HAVE_GUDEV FuI2cDevice *self = FU_I2C_DEVICE(device); FuI2cDevicePrivate *priv = GET_PRIVATE(self); GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); const gchar *tmp; g_autofree gchar *devname = NULL; g_autoptr(GUdevDevice) udev_parent = NULL; #endif /* set physical ID */ if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "i2c", error)) return FALSE; #ifdef HAVE_GUDEV /* i2c devices all expose a name */ tmp = g_udev_device_get_sysfs_attr(udev_device, "name"); fu_device_add_instance_strsafe(device, "NAME", tmp); if (!fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS, error, "I2C", "NAME", NULL)) return FALSE; /* get bus number out of sysfs path */ udev_parent = g_udev_device_get_parent(udev_device); if (udev_parent != NULL) devname = g_path_get_basename(g_udev_device_get_sysfs_path(udev_parent)); if (devname != NULL && g_str_has_prefix(devname, "i2c-")) { guint64 tmp64 = 0; g_autoptr(GError) error_local = NULL; if (!fu_strtoull(devname + 4, &tmp64, 0, G_MAXUINT, &error_local)) { g_debug("ignoring i2c devname bus number: %s", error_local->message); } else { priv->bus_number = tmp64; } } #endif /* success */ return TRUE; } /** * fu_i2c_device_get_bus_number: * @self: a #FuI2cDevice * * Gets the I²C bus number. * * Returns: integer * * Since: 1.6.1 **/ guint fu_i2c_device_get_bus_number(FuI2cDevice *self) { FuI2cDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_I2C_DEVICE(self), G_MAXUINT); return priv->bus_number; } /** * fu_i2c_device_set_bus_number: * @self: a #FuI2cDevice * @bus_number: integer, typically the output of g_udev_device_get_number() * * Sets the I²C bus number. * * Since: 1.6.2 **/ void fu_i2c_device_set_bus_number(FuI2cDevice *self, guint bus_number) { FuI2cDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_I2C_DEVICE(self)); priv->bus_number = bus_number; } /** * fu_i2c_device_write: * @self: a #FuI2cDevice * @buf: (out): data * @bufsz: size of @data * @error: (nullable): optional return location for an error * * Write multiple bytes to the I²C device. * * Returns: %TRUE for success * * Since: 1.8.2 **/ gboolean fu_i2c_device_write(FuI2cDevice *self, const guint8 *buf, gsize bufsz, GError **error) { return fu_udev_device_pwrite(FU_UDEV_DEVICE(self), 0x0, buf, bufsz, error); } /** * fu_i2c_device_read: * @self: a #FuI2cDevice * @buf: (out): data * @bufsz: size of @data * @error: (nullable): optional return location for an error * * Read multiple bytes from the I²C device. * * Returns: %TRUE for success * * Since: 1.8.2 **/ gboolean fu_i2c_device_read(FuI2cDevice *self, guint8 *buf, gsize bufsz, GError **error) { return fu_udev_device_pread(FU_UDEV_DEVICE(self), 0x0, buf, bufsz, error); } static void fu_i2c_device_incorporate(FuDevice *device, FuDevice *donor) { FuI2cDevice *self = FU_I2C_DEVICE(device); FuI2cDevicePrivate *priv = GET_PRIVATE(self); FuI2cDevicePrivate *priv_donor = GET_PRIVATE(FU_I2C_DEVICE(donor)); g_return_if_fail(FU_IS_I2C_DEVICE(self)); g_return_if_fail(FU_IS_I2C_DEVICE(donor)); /* FuUdevDevice->incorporate */ FU_DEVICE_CLASS(fu_i2c_device_parent_class)->incorporate(device, donor); /* copy private instance data */ priv->bus_number = priv_donor->bus_number; } static void fu_i2c_device_init(FuI2cDevice *self) { } static void fu_i2c_device_class_init(FuI2cDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->get_property = fu_i2c_device_get_property; object_class->set_property = fu_i2c_device_set_property; klass_device->open = fu_i2c_device_open; klass_device->probe = fu_i2c_device_probe; klass_device->to_string = fu_i2c_device_to_string; klass_device->incorporate = fu_i2c_device_incorporate; /** * FuI2cDevice:bus-number: * * The I²C bus number. * * Since: 1.6.2 */ pspec = g_param_spec_uint("bus-number", NULL, NULL, 0x0, G_MAXUINT, 0x0, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BUS_NUMBER, pspec); } fwupd-1.9.16/libfwupdplugin/fu-i2c-device.h000066400000000000000000000014511460375044200204710ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-udev-device.h" #define FU_TYPE_I2C_DEVICE (fu_i2c_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuI2cDevice, fu_i2c_device, FU, I2C_DEVICE, FuUdevDevice) struct _FuI2cDeviceClass { FuUdevDeviceClass parent_class; }; guint fu_i2c_device_get_bus_number(FuI2cDevice *self) G_GNUC_NON_NULL(1); void fu_i2c_device_set_bus_number(FuI2cDevice *self, guint bus_number) G_GNUC_NON_NULL(1); gboolean fu_i2c_device_read(FuI2cDevice *self, guint8 *buf, gsize bufsz, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_i2c_device_write(FuI2cDevice *self, const guint8 *buf, gsize bufsz, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-ifd-bios.c000066400000000000000000000046751460375044200202610ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuIfdBios" #include "config.h" #include "fu-bytes.h" #include "fu-common.h" #include "fu-efi-firmware-volume.h" #include "fu-ifd-bios.h" #include "fu-mem.h" /** * FuIfdBios: * * An Intel BIOS section. * * See also: [class@FuFirmware] */ G_DEFINE_TYPE(FuIfdBios, fu_ifd_bios, FU_TYPE_IFD_IMAGE) #define FU_IFD_BIOS_FIT_SIGNATURE 0x5449465F #define FU_IFD_BIOS_FIT_SIZE 0x150000 static gboolean fu_ifd_bios_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0; guint32 sig; guint img_cnt = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* jump 16MiB as required */ if (bufsz > 0x100000) offset += 0x100000; /* read each volume in order */ while (offset < bufsz) { g_autoptr(FuFirmware) firmware_tmp = NULL; /* ignore _FIT_ as EOF */ if (!fu_memread_uint32_safe(buf, bufsz, offset, &sig, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read start signature: "); return FALSE; } if (sig == FU_IFD_BIOS_FIT_SIGNATURE) break; if (sig == 0xffffffff) break; /* FV */ firmware_tmp = fu_firmware_new_from_gtypes(fw, offset, flags, error, FU_TYPE_EFI_FIRMWARE_VOLUME, G_TYPE_INVALID); if (firmware_tmp == NULL) { g_prefix_error(error, "failed to read @0x%x of 0x%x: ", (guint)offset, (guint)bufsz); return FALSE; } fu_firmware_set_offset(firmware_tmp, offset); fu_firmware_add_image(firmware, firmware_tmp); /* next! */ offset += fu_firmware_get_size(firmware_tmp); img_cnt++; } /* found nothing */ if (img_cnt == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no EFI firmware volumes"); return FALSE; } /* success */ return TRUE; } static void fu_ifd_bios_init(FuIfdBios *self) { fu_firmware_set_alignment(FU_FIRMWARE(self), FU_FIRMWARE_ALIGNMENT_4K); fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); } static void fu_ifd_bios_class_init(FuIfdBiosClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_ifd_bios_parse; } /** * fu_ifd_bios_new: * * Creates a new #FuFirmware * * Since: 1.6.2 **/ FuFirmware * fu_ifd_bios_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_IFD_BIOS, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-ifd-bios.h000066400000000000000000000005631460375044200202560ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-ifd-image.h" #define FU_TYPE_IFD_BIOS (fu_ifd_bios_get_type()) G_DECLARE_DERIVABLE_TYPE(FuIfdBios, fu_ifd_bios, FU, IFD_BIOS, FuIfdImage) struct _FuIfdBiosClass { FuIfdImageClass parent_class; }; FuFirmware * fu_ifd_bios_new(void); fwupd-1.9.16/libfwupdplugin/fu-ifd-common.c000066400000000000000000000052041460375044200206020ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ifd-common.h" /** * fu_ifd_region_to_name: * @region: A #FuIfdRegion, e.g. %FU_IFD_REGION_BIOS * * Converts a #FuIfdRegion to a name the user might recognize. * * Returns: identifier string * * Since: 1.6.2 **/ const gchar * fu_ifd_region_to_name(FuIfdRegion region) { if (region == FU_IFD_REGION_DESC) return "IFD descriptor region"; if (region == FU_IFD_REGION_BIOS) return "BIOS"; if (region == FU_IFD_REGION_ME) return "Intel Management Engine"; if (region == FU_IFD_REGION_GBE) return "Gigabit Ethernet"; if (region == FU_IFD_REGION_PLATFORM) return "Platform firmware"; if (region == FU_IFD_REGION_DEVEXP) return "Device Firmware"; if (region == FU_IFD_REGION_BIOS2) return "BIOS Backup"; if (region == FU_IFD_REGION_EC) return "Embedded Controller"; if (region == FU_IFD_REGION_IE) return "Innovation Engine"; if (region == FU_IFD_REGION_10GBE) return "10 Gigabit Ethernet"; return NULL; } /** * fu_ifd_access_to_string: * @access: A #FuIfdAccess, e.g. %FU_IFD_ACCESS_READ * * Converts a #FuIfdAccess to a string. * * Returns: identifier string * * Since: 1.6.2 **/ const gchar * fu_ifd_access_to_string(FuIfdAccess access) { if (access == FU_IFD_ACCESS_NONE) return "--"; if (access == FU_IFD_ACCESS_READ) return "ro"; if (access == FU_IFD_ACCESS_WRITE) return "wr"; if (access == (FU_IFD_ACCESS_READ | FU_IFD_ACCESS_WRITE)) return "rw"; return NULL; } /** * fu_ifd_region_to_access: * @region: A #FuIfdRegion, e.g. %FU_IFD_REGION_BIOS * @flash_master: flash master number * @new_layout: if Skylake or newer * * Converts a #FuIfdRegion to an access level. * * Returns: access * * Since: 1.6.2 **/ FuIfdAccess fu_ifd_region_to_access(FuIfdRegion region, guint32 flash_master, gboolean new_layout) { guint8 bit_r = 0; guint8 bit_w = 0; /* new layout */ if (new_layout) { bit_r = (flash_master >> (region + 8)) & 0b1; bit_w = (flash_master >> (region + 20)) & 0b1; return (bit_r ? FU_IFD_ACCESS_READ : FU_IFD_ACCESS_NONE) | (bit_w ? FU_IFD_ACCESS_WRITE : FU_IFD_ACCESS_NONE); } /* old layout */ if (region == FU_IFD_REGION_DESC) { bit_r = 16; bit_w = 24; } else if (region == FU_IFD_REGION_BIOS) { bit_r = 17; bit_w = 25; } else if (region == FU_IFD_REGION_ME) { bit_r = 18; bit_w = 26; } else if (region == FU_IFD_REGION_GBE) { bit_r = 19; bit_w = 27; } return ((flash_master >> bit_r) & 0b1 ? FU_IFD_ACCESS_READ : FU_IFD_ACCESS_NONE) | ((flash_master >> bit_w) & 0b1 ? FU_IFD_ACCESS_WRITE : FU_IFD_ACCESS_NONE); } fwupd-1.9.16/libfwupdplugin/fu-ifd-common.h000066400000000000000000000014621460375044200206110ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-ifd-struct.h" /** * FuIfdAccess: * @FU_IFD_ACCESS_NONE: None * @FU_IFD_ACCESS_READ: Readable * @FU_IFD_ACCESS_WRITE: Writable * * The flags to use for IFD access permissions. **/ typedef enum { FU_IFD_ACCESS_NONE = 0, FU_IFD_ACCESS_READ = 1 << 0, FU_IFD_ACCESS_WRITE = 1 << 1, } FuIfdAccess; #define FU_IFD_FREG_BASE(freg) (((freg) << 12) & 0x07FFF000) #define FU_IFD_FREG_LIMIT(freg) ((((freg) >> 4) & 0x07FFF000) | 0x00000FFF) const gchar * fu_ifd_region_to_name(FuIfdRegion region); const gchar * fu_ifd_access_to_string(FuIfdAccess access); FuIfdAccess fu_ifd_region_to_access(FuIfdRegion region, guint32 flash_master, gboolean new_layout); fwupd-1.9.16/libfwupdplugin/fu-ifd-firmware.c000066400000000000000000000344521460375044200211350ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuIfdFirmware" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-ifd-bios.h" #include "fu-ifd-common.h" #include "fu-ifd-firmware.h" #include "fu-ifd-image.h" #include "fu-mem.h" /** * FuIfdFirmware: * * An Intel Flash Descriptor. * * See also: [class@FuFirmware] */ typedef struct { gboolean new_layout; guint32 descriptor_map0; guint32 descriptor_map1; guint32 descriptor_map2; guint8 num_regions; guint8 num_components; guint32 flash_region_base_addr; guint32 flash_component_base_addr; guint32 flash_master_base_addr; guint32 flash_master[4]; /* indexed from 1, ignore [0] */ guint32 flash_ich_strap_base_addr; guint32 flash_mch_strap_base_addr; guint32 components_rcd; guint32 illegal_jedec; guint32 illegal_jedec1; guint32 *flash_descriptor_regs; } FuIfdFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuIfdFirmware, fu_ifd_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_ifd_firmware_get_instance_private(o)) #define FU_IFD_SIZE 0x1000 #define FU_IFD_FDBAR_FLASH_UPPER_MAP1 0x0EFC #define FU_IFD_FDBAR_OEM_SECTION 0x0F00 #define FU_IFD_FREG_BASE(freg) (((freg) << 12) & 0x07FFF000) #define FU_IFD_FREG_LIMIT(freg) ((((freg) >> 4) & 0x07FFF000) | 0x00000FFF) static void fu_ifd_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuIfdFirmware *self = FU_IFD_FIRMWARE(firmware); FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "descriptor_map0", priv->descriptor_map0); fu_xmlb_builder_insert_kx(bn, "descriptor_map1", priv->descriptor_map1); fu_xmlb_builder_insert_kx(bn, "descriptor_map2", priv->descriptor_map2); fu_xmlb_builder_insert_kx(bn, "num_regions", priv->num_regions); fu_xmlb_builder_insert_kx(bn, "num_components", priv->num_components + 1); fu_xmlb_builder_insert_kx(bn, "flash_region_base_addr", priv->flash_region_base_addr); fu_xmlb_builder_insert_kx(bn, "flash_component_base_addr", priv->flash_component_base_addr); fu_xmlb_builder_insert_kx(bn, "flash_master_base_addr", priv->flash_master_base_addr); fu_xmlb_builder_insert_kx(bn, "flash_ich_strap_base_addr", priv->flash_ich_strap_base_addr); fu_xmlb_builder_insert_kx(bn, "flash_mch_strap_base_addr", priv->flash_mch_strap_base_addr); fu_xmlb_builder_insert_kx(bn, "components_rcd", priv->components_rcd); fu_xmlb_builder_insert_kx(bn, "illegal_jedec", priv->illegal_jedec); fu_xmlb_builder_insert_kx(bn, "illegal_jedec1", priv->illegal_jedec1); if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) { for (guint i = 1; i < 3; i++) { g_autofree gchar *title = g_strdup_printf("flash_master%x", i + 1); fu_xmlb_builder_insert_kx(bn, title, priv->flash_master[i]); } if (priv->flash_descriptor_regs != NULL) { for (guint i = 0; i < priv->num_regions; i++) { g_autofree gchar *title = g_strdup_printf("flash_descriptor_reg%x", i); fu_xmlb_builder_insert_kx(bn, title, priv->flash_descriptor_regs[i]); } } } } static gboolean fu_ifd_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_ifd_fdbar_validate_bytes(fw, offset, error); } static gboolean fu_ifd_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuIfdFirmware *self = FU_IFD_FIRMWARE(firmware); FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GByteArray) st_fcba = NULL; g_autoptr(GByteArray) st_fdbar = NULL; /* check size */ if (bufsz < FU_IFD_SIZE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "file is too small, expected bufsz >= 0x%x", (guint)FU_IFD_SIZE); return FALSE; } /* descriptor registers */ st_fdbar = fu_struct_ifd_fdbar_parse_bytes(fw, 0x0, error); if (st_fdbar == NULL) return FALSE; priv->descriptor_map0 = fu_struct_ifd_fdbar_get_descriptor_map0(st_fdbar); priv->num_regions = (priv->descriptor_map0 >> 24) & 0b111; if (priv->num_regions == 0) priv->num_regions = 10; priv->num_components = (priv->descriptor_map0 >> 8) & 0b11; priv->flash_component_base_addr = (priv->descriptor_map0 << 4) & 0x00000FF0; priv->flash_region_base_addr = (priv->descriptor_map0 >> 12) & 0x00000FF0; priv->descriptor_map1 = fu_struct_ifd_fdbar_get_descriptor_map1(st_fdbar); priv->flash_master_base_addr = (priv->descriptor_map1 << 4) & 0x00000FF0; priv->flash_ich_strap_base_addr = (priv->descriptor_map1 >> 12) & 0x00000FF0; priv->descriptor_map2 = fu_struct_ifd_fdbar_get_descriptor_map2(st_fdbar); priv->flash_mch_strap_base_addr = (priv->descriptor_map2 << 4) & 0x00000FF0; /* FCBA */ st_fcba = fu_struct_ifd_fcba_parse_bytes(fw, priv->flash_component_base_addr, error); if (st_fcba == NULL) return FALSE; priv->components_rcd = fu_struct_ifd_fcba_get_flcomp(st_fcba); priv->illegal_jedec = fu_struct_ifd_fcba_get_flill(st_fcba); priv->illegal_jedec1 = fu_struct_ifd_fcba_get_flill1(st_fcba); /* FMBA */ if (!fu_memread_uint32_safe(buf, bufsz, priv->flash_master_base_addr + 0x0, &priv->flash_master[1], G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, priv->flash_master_base_addr + 0x4, &priv->flash_master[2], G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, priv->flash_master_base_addr + 0x8, &priv->flash_master[3], G_LITTLE_ENDIAN, error)) return FALSE; /* FRBA */ priv->flash_descriptor_regs = g_new0(guint32, priv->num_regions); for (guint i = 0; i < priv->num_regions; i++) { if (!fu_memread_uint32_safe(buf, bufsz, priv->flash_region_base_addr + (i * sizeof(guint32)), &priv->flash_descriptor_regs[i], G_LITTLE_ENDIAN, error)) return FALSE; } for (guint i = 0; i < priv->num_regions; i++) { const gchar *freg_str = fu_ifd_region_to_string(i); guint32 freg_base = FU_IFD_FREG_BASE(priv->flash_descriptor_regs[i]); guint32 freg_limt = FU_IFD_FREG_LIMIT(priv->flash_descriptor_regs[i]); guint32 freg_size = (freg_limt - freg_base) + 1; g_autoptr(FuFirmware) img = NULL; g_autoptr(GBytes) contents = NULL; /* invalid */ if (freg_base > freg_limt) continue; /* create image */ g_debug("freg %s 0x%04x -> 0x%04x", freg_str, freg_base, freg_limt); contents = fu_bytes_new_offset(fw, freg_base, freg_size, error); if (contents == NULL) return FALSE; if (i == FU_IFD_REGION_BIOS) { img = fu_ifd_bios_new(); } else { img = fu_ifd_image_new(); } if (!fu_firmware_parse(img, contents, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return FALSE; fu_firmware_set_addr(img, freg_base); fu_firmware_set_idx(img, i); if (freg_str != NULL) fu_firmware_set_id(img, freg_str); fu_firmware_add_image(firmware, img); /* is writable by anything other than the region itself */ for (FuIfdRegion r = 1; r <= 3; r++) { FuIfdAccess acc; acc = fu_ifd_region_to_access(i, priv->flash_master[r], priv->new_layout); fu_ifd_image_set_access(FU_IFD_IMAGE(img), r, acc); } } /* success */ return TRUE; } /** * fu_ifd_firmware_check_jedec_cmd: * @self: a #FuIfdFirmware * @cmd: a JEDEC command, e.g. 0x42 for "whole chip erase" * * Checks a JEDEC command to see if it has been put on the "illegal_jedec" list. * * Returns: %TRUE if the command is allowed * * Since: 1.6.2 **/ gboolean fu_ifd_firmware_check_jedec_cmd(FuIfdFirmware *self, guint8 cmd) { FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); for (guint j = 0; j < 32; j += 8) { if (((priv->illegal_jedec >> j) & 0xff) == cmd) return FALSE; if (((priv->illegal_jedec1 >> j) & 0xff) == cmd) return FALSE; } return TRUE; } static GByteArray * fu_ifd_firmware_write(FuFirmware *firmware, GError **error) { FuIfdFirmware *self = FU_IFD_FIRMWARE(firmware); FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); gsize bufsz_max = 0x0; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) st_fcba = fu_struct_ifd_fcba_new(); g_autoptr(GByteArray) st_fdbar = fu_struct_ifd_fdbar_new(); g_autoptr(GHashTable) blobs = NULL; g_autoptr(FuFirmware) img_desc = NULL; /* if the descriptor does not exist, then add something plausible */ img_desc = fu_firmware_get_image_by_idx(firmware, FU_IFD_REGION_DESC, NULL); if (img_desc == NULL) { g_autoptr(GByteArray) buf_desc = g_byte_array_new(); g_autoptr(GBytes) blob_desc = NULL; fu_byte_array_set_size(buf_desc, FU_IFD_SIZE, 0x00); /* success */ blob_desc = g_bytes_new(buf_desc->data, buf_desc->len); img_desc = fu_firmware_new_from_bytes(blob_desc); fu_firmware_set_addr(img_desc, 0x0); fu_firmware_set_idx(img_desc, FU_IFD_REGION_DESC); fu_firmware_set_id(img_desc, "desc"); fu_firmware_add_image(firmware, img_desc); } /* generate ahead of time */ blobs = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)g_bytes_unref); for (guint i = 0; i < priv->num_regions; i++) { g_autoptr(FuFirmware) img = fu_firmware_get_image_by_idx(firmware, i, NULL); g_autoptr(GBytes) blob = NULL; if (img == NULL) continue; blob = fu_firmware_write(img, error); if (blob == NULL) { g_prefix_error(error, "failed to write %s: ", fu_firmware_get_id(img)); return NULL; } if (g_bytes_get_data(blob, NULL) == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to write %s", fu_firmware_get_id(img)); return NULL; } g_hash_table_insert(blobs, GUINT_TO_POINTER(i), g_bytes_ref(blob)); /* check total size */ bufsz_max = MAX(fu_firmware_get_addr(img) + g_bytes_get_size(blob), bufsz_max); } fu_byte_array_set_size(buf, bufsz_max, 0x00); /* descriptor map */ fu_struct_ifd_fdbar_set_descriptor_map0(st_fdbar, priv->descriptor_map0); fu_struct_ifd_fdbar_set_descriptor_map1(st_fdbar, priv->descriptor_map1); fu_struct_ifd_fdbar_set_descriptor_map2(st_fdbar, priv->descriptor_map2); if (!fu_memcpy_safe(buf->data, buf->len, 0x0, st_fdbar->data, st_fdbar->len, 0x0, st_fdbar->len, error)) return NULL; /* FCBA */ fu_struct_ifd_fcba_set_flcomp(st_fcba, priv->components_rcd); fu_struct_ifd_fcba_set_flill(st_fcba, priv->illegal_jedec); fu_struct_ifd_fcba_set_flill1(st_fcba, priv->illegal_jedec1); if (!fu_memcpy_safe(buf->data, buf->len, priv->flash_component_base_addr, st_fcba->data, st_fcba->len, 0x0, st_fcba->len, error)) return NULL; /* FRBA */ for (guint i = 0; i < priv->num_regions; i++) { guint32 freg_base = 0x7FFF000; guint32 freg_limt = 0x0; guint32 flreg; g_autoptr(FuFirmware) img = fu_firmware_get_image_by_idx(firmware, i, NULL); if (img != NULL) { GBytes *blob = g_hash_table_lookup(blobs, GUINT_TO_POINTER(fu_firmware_get_idx(img))); freg_base = fu_firmware_get_addr(img); freg_limt = (freg_base + g_bytes_get_size(blob)) - 1; } flreg = ((freg_limt << 4) & 0xFFFF0000) | (freg_base >> 12); g_debug("freg 0x%04x -> 0x%04x = 0x%08x", freg_base, freg_limt, flreg); if (!fu_memwrite_uint32_safe(buf->data, buf->len, priv->flash_region_base_addr + (i * sizeof(guint32)), flreg, G_LITTLE_ENDIAN, error)) return NULL; } /* write images at correct offsets */ for (guint i = 1; i < priv->num_regions; i++) { GBytes *blob; g_autoptr(FuFirmware) img = fu_firmware_get_image_by_idx(firmware, i, NULL); if (img == NULL) continue; blob = g_hash_table_lookup(blobs, GUINT_TO_POINTER(fu_firmware_get_idx(img))); if (!fu_memcpy_safe(buf->data, buf->len, fu_firmware_get_addr(img), g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), 0x0, g_bytes_get_size(blob), error)) return NULL; } /* success */ return g_steal_pointer(&buf); } static gboolean fu_ifd_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuIfdFirmware *self = FU_IFD_FIRMWARE(firmware); FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); guint64 tmp; /* optional properties */ tmp = xb_node_query_text_as_uint(n, "descriptor_map0", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) priv->descriptor_map0 = tmp; tmp = xb_node_query_text_as_uint(n, "descriptor_map1", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) priv->descriptor_map1 = tmp; tmp = xb_node_query_text_as_uint(n, "descriptor_map2", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) priv->descriptor_map2 = tmp; tmp = xb_node_query_text_as_uint(n, "components_rcd", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) priv->components_rcd = tmp; tmp = xb_node_query_text_as_uint(n, "illegal_jedec", NULL); if (tmp != G_MAXUINT64) { priv->illegal_jedec = tmp & 0xFFFFFFFF; priv->illegal_jedec1 = tmp >> 32; } /* success */ return TRUE; } static void fu_ifd_firmware_init(FuIfdFirmware *self) { FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); /* some good defaults */ priv->new_layout = TRUE; priv->num_regions = 10; priv->flash_region_base_addr = 0x40; priv->flash_component_base_addr = 0x30; priv->flash_master_base_addr = 0x80; priv->flash_master[1] = 0x00A00F00; priv->flash_master[2] = 0x00400D00; priv->flash_master[3] = 0x00800900; priv->flash_ich_strap_base_addr = 0x100; priv->flash_mch_strap_base_addr = 0x300; } static void fu_ifd_firmware_finalize(GObject *object) { FuIfdFirmware *self = FU_IFD_FIRMWARE(object); FuIfdFirmwarePrivate *priv = GET_PRIVATE(self); g_free(priv->flash_descriptor_regs); G_OBJECT_CLASS(fu_ifd_firmware_parent_class)->finalize(object); } static void fu_ifd_firmware_class_init(FuIfdFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_ifd_firmware_finalize; klass_firmware->check_magic = fu_ifd_firmware_check_magic; klass_firmware->export = fu_ifd_firmware_export; klass_firmware->parse = fu_ifd_firmware_parse; klass_firmware->write = fu_ifd_firmware_write; klass_firmware->build = fu_ifd_firmware_build; } /** * fu_ifd_firmware_new: * * Creates a new #FuFirmware of sub type Ifd * * Since: 1.6.2 **/ FuFirmware * fu_ifd_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_IFD_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-ifd-firmware.h000066400000000000000000000007541460375044200211400ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_IFD_FIRMWARE (fu_ifd_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuIfdFirmware, fu_ifd_firmware, FU, IFD_FIRMWARE, FuFirmware) struct _FuIfdFirmwareClass { FuFirmwareClass parent_class; }; FuFirmware * fu_ifd_firmware_new(void); gboolean fu_ifd_firmware_check_jedec_cmd(FuIfdFirmware *self, guint8 cmd) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-ifd-image.c000066400000000000000000000065601460375044200204020ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-byte-array.h" #include "fu-ifd-image.h" /** * FuIfdImage: * * An Intel Flash Descriptor image, e.g. BIOS. * * See also: [class@FuFirmware] */ typedef struct { FuIfdAccess access[FU_IFD_REGION_MAX]; } FuIfdImagePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuIfdImage, fu_ifd_image, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_ifd_image_get_instance_private(o)) static void fu_ifd_image_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuIfdImage *self = FU_IFD_IMAGE(firmware); FuIfdImagePrivate *priv = GET_PRIVATE(self); for (guint i = 0; i < FU_IFD_REGION_MAX; i++) { if (priv->access[i] == FU_IFD_ACCESS_NONE) continue; xb_builder_node_insert_text(bn, "access", fu_ifd_access_to_string(priv->access[i]), "region", fu_ifd_region_to_string(i), NULL); } } /** * fu_ifd_image_set_access: * @self: a #FuIfdImage * @region: a #FuIfdRegion, e.g. %FU_IFD_REGION_BIOS * @access: a #FuIfdAccess, e.g. %FU_IFD_ACCESS_NONE * * Sets the access control for a specific reason. * * Since: 1.6.2 **/ void fu_ifd_image_set_access(FuIfdImage *self, FuIfdRegion region, FuIfdAccess access) { FuIfdImagePrivate *priv = GET_PRIVATE(self); priv->access[region] = access; } /** * fu_ifd_image_get_access: * @self: a #FuIfdImage * @region: a #FuIfdRegion, e.g. %FU_IFD_REGION_BIOS * * Gets the access control for a specific reason. * * Returns: a #FuIfdAccess, e.g. %FU_IFD_ACCESS_NONE * * Since: 1.6.2 **/ FuIfdAccess fu_ifd_image_get_access(FuIfdImage *self, FuIfdRegion region) { FuIfdImagePrivate *priv = GET_PRIVATE(self); return priv->access[region]; } static GByteArray * fu_ifd_image_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); /* sanity check */ if (fu_firmware_get_alignment(firmware) > FU_FIRMWARE_ALIGNMENT_1M) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "alignment invalid, got 0x%02x", fu_firmware_get_alignment(firmware)); return NULL; } /* add each volume */ if (images->len > 0) { for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) bytes = fu_firmware_write(img, error); if (bytes == NULL) return NULL; fu_byte_array_append_bytes(buf, bytes); } } else { g_autoptr(GBytes) bytes = NULL; bytes = fu_firmware_get_bytes_with_patches(firmware, error); if (bytes == NULL) return NULL; fu_byte_array_append_bytes(buf, bytes); } /* align up */ fu_byte_array_set_size(buf, fu_common_align_up(buf->len, fu_firmware_get_alignment(firmware)), 0x00); /* success */ return g_steal_pointer(&buf); } static void fu_ifd_image_init(FuIfdImage *self) { fu_firmware_set_alignment(FU_FIRMWARE(self), FU_FIRMWARE_ALIGNMENT_4K); } static void fu_ifd_image_class_init(FuIfdImageClass *klass) { FuFirmwareClass *klass_image = FU_FIRMWARE_CLASS(klass); klass_image->export = fu_ifd_image_export; klass_image->write = fu_ifd_image_write; } /** * fu_ifd_image_new: * * Creates a new #FuFirmware * * Since: 1.6.2 **/ FuFirmware * fu_ifd_image_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_IFD_IMAGE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-ifd-image.h000066400000000000000000000011411460375044200203750ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #include "fu-ifd-common.h" #define FU_TYPE_IFD_IMAGE (fu_ifd_image_get_type()) G_DECLARE_DERIVABLE_TYPE(FuIfdImage, fu_ifd_image, FU, IFD_IMAGE, FuFirmware) struct _FuIfdImageClass { FuFirmwareClass parent_class; }; FuFirmware * fu_ifd_image_new(void); void fu_ifd_image_set_access(FuIfdImage *self, FuIfdRegion region, FuIfdAccess access) G_GNUC_NON_NULL(1); FuIfdAccess fu_ifd_image_get_access(FuIfdImage *self, FuIfdRegion region) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-ifd.rs000066400000000000000000000012371460375044200175200ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum IfdRegion { Desc = 0x00, Bios = 0x01, Me = 0x02, Gbe = 0x03, Platform = 0x04, Devexp = 0x05, Bios2 = 0x06, Ec = 0x08, Ie = 0x0A, 10gbe = 0x0B, Max = 0x0F, } #[derive(ParseBytes, New, ValidateBytes)] struct IfdFdbar { reserved: [u8; 16] = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, signature: u32le == 0x0FF0A55A, descriptor_map0: u32le, descriptor_map1: u32le, descriptor_map2: u32le, } #[derive(ParseBytes, New)] struct IfdFcba { flcomp: u32le, flill: u32le, flill1: u32le, } fwupd-1.9.16/libfwupdplugin/fu-ifwi-cpd-firmware.c000066400000000000000000000230701460375044200220670ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2022 Intel * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-ifwi-cpd-firmware.h" #include "fu-ifwi-struct.h" #include "fu-string.h" /** * FuIfwiCpdFirmware: * * An Intel Code Partition Directory (aka CPD) can be found in IFWI (Integrated Firmware Image) * firmware blobs which are used in various Intel products using an IPU (Infrastructure Processing * Unit). * * This could include hardware like SmartNICs, GPUs, camera and audio devices. * * See also: [class@FuFirmware] */ typedef struct { guint8 header_version; guint8 entry_version; } FuIfwiCpdFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuIfwiCpdFirmware, fu_ifwi_cpd_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_ifwi_cpd_firmware_get_instance_private(o)) #define FU_IFWI_CPD_FIRMWARE_ENTRIES_MAX 1024 static void fu_ifwi_cpd_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuIfwiCpdFirmware *self = FU_IFWI_CPD_FIRMWARE(firmware); FuIfwiCpdFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "header_version", priv->header_version); fu_xmlb_builder_insert_kx(bn, "entry_version", priv->entry_version); } static gboolean fu_ifwi_cpd_firmware_parse_manifest(FuFirmware *firmware, GBytes *fw, GError **error) { gsize bufsz = g_bytes_get_size(fw); guint32 size; gsize offset = 0; g_autoptr(GByteArray) st_mhd = NULL; /* raw version */ st_mhd = fu_struct_ifwi_cpd_manifest_parse_bytes(fw, offset, error); if (st_mhd == NULL) return FALSE; fu_firmware_set_version_raw(firmware, fu_struct_ifwi_cpd_manifest_get_version(st_mhd)); /* verify the size */ size = fu_struct_ifwi_cpd_manifest_get_size(st_mhd); if (size * 4 != bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid manifest invalid length, got 0x%x, expected 0x%x", size * 4, (guint)bufsz); return FALSE; } /* parse extensions */ offset += fu_struct_ifwi_cpd_manifest_get_header_length(st_mhd) * 4; while (offset < bufsz) { guint32 extension_type = 0; guint32 extension_length = 0; g_autoptr(FuFirmware) img = fu_firmware_new(); g_autoptr(GByteArray) st_mex = NULL; g_autoptr(GBytes) blob = NULL; /* set the extension type as the index */ st_mex = fu_struct_ifwi_cpd_manifest_ext_parse_bytes(fw, offset, error); if (st_mex == NULL) return FALSE; extension_type = fu_struct_ifwi_cpd_manifest_ext_get_extension_type(st_mex); if (extension_type == 0x0) break; fu_firmware_set_idx(img, extension_type); /* add data section */ extension_length = fu_struct_ifwi_cpd_manifest_ext_get_extension_length(st_mex); if (extension_length == 0x0) break; if (extension_length < st_mex->len) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid manifest extension header length 0x%x", (guint)extension_length); return FALSE; } blob = fu_bytes_new_offset(fw, offset + st_mex->len, extension_length - st_mex->len, error); if (blob == NULL) return FALSE; fu_firmware_set_bytes(img, blob); /* success */ fu_firmware_set_offset(img, offset); if (!fu_firmware_add_image_full(firmware, img, error)) return FALSE; offset += extension_length; } /* success */ return TRUE; } static gboolean fu_ifwi_cpd_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_ifwi_cpd_validate_bytes(fw, offset, error); } static gboolean fu_ifwi_cpd_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuIfwiCpdFirmware *self = FU_IFWI_CPD_FIRMWARE(firmware); FuIfwiCpdFirmwarePrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) st_hdr = NULL; guint32 num_of_entries; /* other header fields */ st_hdr = fu_struct_ifwi_cpd_parse_bytes(fw, offset, error); if (st_hdr == NULL) return FALSE; priv->header_version = fu_struct_ifwi_cpd_get_header_version(st_hdr); priv->entry_version = fu_struct_ifwi_cpd_get_entry_version(st_hdr); fu_firmware_set_idx(firmware, fu_struct_ifwi_cpd_get_partition_name(st_hdr)); /* read out entries */ num_of_entries = fu_struct_ifwi_cpd_get_num_of_entries(st_hdr); if (num_of_entries > FU_IFWI_CPD_FIRMWARE_ENTRIES_MAX) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "too many entries 0x%x, expected <= 0x%x", num_of_entries, (guint)FU_IFWI_CPD_FIRMWARE_ENTRIES_MAX); return FALSE; } offset += fu_struct_ifwi_cpd_get_header_length(st_hdr); for (guint32 i = 0; i < num_of_entries; i++) { guint32 img_offset = 0; g_autofree gchar *id = NULL; g_autoptr(FuFirmware) img = fu_firmware_new(); g_autoptr(GByteArray) st_ent = NULL; g_autoptr(GBytes) img_blob = NULL; /* the IDX is the position in the file */ fu_firmware_set_idx(img, i); st_ent = fu_struct_ifwi_cpd_entry_parse_bytes(fw, offset, error); if (st_ent == NULL) return FALSE; /* copy name as id */ id = fu_struct_ifwi_cpd_entry_get_name(st_ent); fu_firmware_set_id(img, id); /* copy offset, ignoring huffman and reserved bits */ img_offset = fu_struct_ifwi_cpd_entry_get_offset(st_ent); img_offset &= 0x1FFFFFF; fu_firmware_set_offset(img, img_offset); /* copy data */ img_blob = fu_bytes_new_offset(fw, img_offset, fu_struct_ifwi_cpd_entry_get_length(st_ent), error); if (img_blob == NULL) return FALSE; fu_firmware_set_bytes(img, img_blob); /* read the manifest */ if (i == FU_IFWI_CPD_FIRMWARE_IDX_MANIFEST && g_bytes_get_size(img_blob) > FU_STRUCT_IFWI_CPD_MANIFEST_SIZE) { if (!fu_ifwi_cpd_firmware_parse_manifest(img, img_blob, error)) return FALSE; } /* success */ if (!fu_firmware_add_image_full(firmware, img, error)) return FALSE; offset += st_ent->len; } /* success */ return TRUE; } static GByteArray * fu_ifwi_cpd_firmware_write(FuFirmware *firmware, GError **error) { FuIfwiCpdFirmware *self = FU_IFWI_CPD_FIRMWARE(firmware); FuIfwiCpdFirmwarePrivate *priv = GET_PRIVATE(self); gsize offset = 0; g_autoptr(GByteArray) buf = fu_struct_ifwi_cpd_new(); g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); /* write the header */ fu_struct_ifwi_cpd_set_num_of_entries(buf, imgs->len); fu_struct_ifwi_cpd_set_header_version(buf, priv->header_version); fu_struct_ifwi_cpd_set_entry_version(buf, priv->entry_version); fu_struct_ifwi_cpd_set_checksum(buf, 0x0); fu_struct_ifwi_cpd_set_partition_name(buf, fu_firmware_get_idx(firmware)); fu_struct_ifwi_cpd_set_crc32(buf, 0x0); /* fixup the image offsets */ offset += buf->len; offset += FU_STRUCT_IFWI_CPD_ENTRY_SIZE * imgs->len; for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); g_autoptr(GBytes) blob = NULL; blob = fu_firmware_get_bytes(img, error); if (blob == NULL) { g_prefix_error(error, "image 0x%x: ", i); return NULL; } fu_firmware_set_offset(img, offset); offset += g_bytes_get_size(blob); } /* add entry headers */ for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); g_autoptr(GByteArray) st_ent = fu_struct_ifwi_cpd_entry_new(); /* sanity check */ if (fu_firmware_get_id(img) == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "image 0x%x must have an ID", (guint)fu_firmware_get_idx(img)); return NULL; } if (!fu_struct_ifwi_cpd_entry_set_name(st_ent, fu_firmware_get_id(img), error)) return NULL; fu_struct_ifwi_cpd_entry_set_offset(st_ent, fu_firmware_get_offset(img)); fu_struct_ifwi_cpd_entry_set_length(st_ent, fu_firmware_get_size(img)); g_byte_array_append(buf, st_ent->data, st_ent->len); } /* add entry data */ for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); g_autoptr(GBytes) blob = NULL; blob = fu_firmware_get_bytes(img, error); if (blob == NULL) return NULL; fu_byte_array_append_bytes(buf, blob); } /* success */ return g_steal_pointer(&buf); } static gboolean fu_ifwi_cpd_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuIfwiCpdFirmware *self = FU_IFWI_CPD_FIRMWARE(firmware); FuIfwiCpdFirmwarePrivate *priv = GET_PRIVATE(self); const gchar *tmp; /* simple properties */ tmp = xb_node_query_text(n, "header_version", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT8, error)) return FALSE; priv->header_version = val; } tmp = xb_node_query_text(n, "entry_version", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT8, error)) return FALSE; priv->entry_version = val; } /* success */ return TRUE; } static void fu_ifwi_cpd_firmware_init(FuIfwiCpdFirmware *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), FU_IFWI_CPD_FIRMWARE_ENTRIES_MAX); } static void fu_ifwi_cpd_firmware_class_init(FuIfwiCpdFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_ifwi_cpd_firmware_check_magic; klass_firmware->export = fu_ifwi_cpd_firmware_export; klass_firmware->parse = fu_ifwi_cpd_firmware_parse; klass_firmware->write = fu_ifwi_cpd_firmware_write; klass_firmware->build = fu_ifwi_cpd_firmware_build; } /** * fu_ifwi_cpd_firmware_new: * * Creates a new #FuFirmware of Intel Code Partition Directory format * * Since: 1.8.2 **/ FuFirmware * fu_ifwi_cpd_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_IFWI_CPD_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-ifwi-cpd-firmware.h000066400000000000000000000016641460375044200221010ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2022 Intel * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_IFWI_CPD_FIRMWARE (fu_ifwi_cpd_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuIfwiCpdFirmware, fu_ifwi_cpd_firmware, FU, IFWI_CPD_FIRMWARE, FuFirmware) struct _FuIfwiCpdFirmwareClass { FuFirmwareClass parent_class; }; /** * FU_IFWI_CPD_FIRMWARE_IDX_MANIFEST: * * The index for the IFWI manifest image. * * Since: 1.8.2 **/ #define FU_IFWI_CPD_FIRMWARE_IDX_MANIFEST 0x0 /** * FU_IFWI_CPD_FIRMWARE_IDX_METADATA: * * The index for the IFWI metadata image. * * Since: 1.8.2 **/ #define FU_IFWI_CPD_FIRMWARE_IDX_METADATA 0x1 /** * FU_IFWI_CPD_FIRMWARE_IDX_MODULEDATA_IDX: * * The index for the IFWI module data image. * * Since: 1.8.2 **/ #define FU_IFWI_CPD_FIRMWARE_IDX_MODULEDATA_IDX 0x2 FuFirmware * fu_ifwi_cpd_firmware_new(void); fwupd-1.9.16/libfwupdplugin/fu-ifwi-fpt-firmware.c000066400000000000000000000124771460375044200221230ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2022 Intel * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-ifwi-fpt-firmware.h" #include "fu-ifwi-struct.h" #include "fu-string.h" /** * FuIfwiFptFirmware: * * An Intel Flash Program Tool (aka FPT) header can be found in IFWI (Integrated Firmware Image) * firmware blobs which are used in various Intel products using an IPU (Infrastructure Processing * Unit). * * This could include hardware like SmartNICs, GPUs, camera and audio devices. * * See also: [class@FuFirmware] */ G_DEFINE_TYPE(FuIfwiFptFirmware, fu_ifwi_fpt_firmware, FU_TYPE_FIRMWARE) #define FU_IFWI_FPT_MAX_ENTRIES 56 static gboolean fu_ifwi_fpt_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_ifwi_fpt_validate_bytes(fw, offset, error); } static gboolean fu_ifwi_fpt_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { guint32 num_of_entries; g_autoptr(GByteArray) st_hdr = NULL; /* sanity check */ st_hdr = fu_struct_ifwi_fpt_parse_bytes(fw, offset, error); if (st_hdr == NULL) return FALSE; num_of_entries = fu_struct_ifwi_fpt_get_num_of_entries(st_hdr); if (num_of_entries > FU_IFWI_FPT_MAX_ENTRIES) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid FPT number of entries %u", num_of_entries); return FALSE; } if (fu_struct_ifwi_fpt_get_header_version(st_hdr) < FU_STRUCT_IFWI_FPT_DEFAULT_HEADER_VERSION) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid FPT header version: 0x%x", fu_struct_ifwi_fpt_get_header_version(st_hdr)); return FALSE; } /* offset by header length */ offset += fu_struct_ifwi_fpt_get_header_length(st_hdr); /* read out entries */ for (guint i = 0; i < num_of_entries; i++) { guint32 data_length; guint32 partition_name; g_autofree gchar *id = NULL; g_autoptr(FuFirmware) img = fu_firmware_new(); g_autoptr(GByteArray) st_ent = NULL; /* read IDX */ st_ent = fu_struct_ifwi_fpt_entry_parse_bytes(fw, offset, error); if (st_ent == NULL) return FALSE; partition_name = fu_struct_ifwi_fpt_entry_get_partition_name(st_ent); fu_firmware_set_idx(img, partition_name); /* convert to text form for convenience */ id = fu_strsafe((const gchar *)&partition_name, sizeof(partition_name)); if (id != NULL) fu_firmware_set_id(img, id); /* get data at offset using zero-copy */ data_length = fu_struct_ifwi_fpt_entry_get_length(st_ent); if (data_length != 0x0) { g_autoptr(GBytes) blob = NULL; guint32 data_offset = fu_struct_ifwi_fpt_entry_get_offset(st_ent); blob = fu_bytes_new_offset(fw, data_offset, data_length, error); if (blob == NULL) return FALSE; fu_firmware_set_bytes(img, blob); fu_firmware_set_offset(img, data_offset); } if (!fu_firmware_add_image_full(firmware, img, error)) return FALSE; /* next */ offset += st_ent->len; } /* success */ return TRUE; } static GByteArray * fu_ifwi_fpt_firmware_write(FuFirmware *firmware, GError **error) { gsize offset = 0; g_autoptr(GByteArray) buf = fu_struct_ifwi_fpt_new(); g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); /* fixup the image offsets */ offset += buf->len; offset += FU_STRUCT_IFWI_FPT_ENTRY_SIZE * imgs->len; for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); g_autoptr(GBytes) blob = NULL; blob = fu_firmware_get_bytes(img, error); if (blob == NULL) { g_prefix_error(error, "image 0x%x: ", i); return NULL; } fu_firmware_set_offset(img, offset); offset += g_bytes_get_size(blob); } /* write the header */ fu_struct_ifwi_fpt_set_num_of_entries(buf, imgs->len); /* add entries */ for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); g_autoptr(GByteArray) st_ent = fu_struct_ifwi_fpt_entry_new(); fu_struct_ifwi_fpt_entry_set_partition_name(st_ent, fu_firmware_get_idx(img)); fu_struct_ifwi_fpt_entry_set_offset(st_ent, fu_firmware_get_offset(img)); fu_struct_ifwi_fpt_entry_set_length(st_ent, fu_firmware_get_size(img)); g_byte_array_append(buf, st_ent->data, st_ent->len); } /* add entry data */ for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); g_autoptr(GBytes) blob = NULL; blob = fu_firmware_get_bytes(img, error); if (blob == NULL) return NULL; fu_byte_array_append_bytes(buf, blob); } /* success */ return g_steal_pointer(&buf); } static void fu_ifwi_fpt_firmware_init(FuIfwiFptFirmware *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), FU_IFWI_FPT_MAX_ENTRIES); } static void fu_ifwi_fpt_firmware_class_init(FuIfwiFptFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_ifwi_fpt_firmware_check_magic; klass_firmware->parse = fu_ifwi_fpt_firmware_parse; klass_firmware->write = fu_ifwi_fpt_firmware_write; } /** * fu_ifwi_fpt_firmware_new: * * Creates a new #FuFirmware of Intel Flash Program Tool format * * Since: 1.8.2 **/ FuFirmware * fu_ifwi_fpt_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_IFWI_FPT_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-ifwi-fpt-firmware.h000066400000000000000000000023351460375044200221200ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2022 Intel * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_IFWI_FPT_FIRMWARE (fu_ifwi_fpt_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuIfwiFptFirmware, fu_ifwi_fpt_firmware, FU, IFWI_FPT_FIRMWARE, FuFirmware) struct _FuIfwiFptFirmwareClass { FuFirmwareClass parent_class; }; /** * FU_IFWI_FPT_FIRMWARE_IDX_INFO: * * The index for the IFWI info image. * * Since: 1.8.2 **/ #define FU_IFWI_FPT_FIRMWARE_IDX_INFO 0x4f464e49 /** * FU_IFWI_FPT_FIRMWARE_IDX_FWIM: * * The index for the IFWI firmware image. * * Since: 1.8.2 **/ #define FU_IFWI_FPT_FIRMWARE_IDX_FWIM 0x4d495746 /** * FU_IFWI_FPT_FIRMWARE_IDX_IMGI: * * The index for the IFWI image instance. * * Since: 1.8.2 **/ #define FU_IFWI_FPT_FIRMWARE_IDX_IMGI 0x49474d49 /** * FU_IFWI_FPT_FIRMWARE_IDX_SDTA: * * The index for the IFWI firmware data image. * * Since: 1.8.2 **/ #define FU_IFWI_FPT_FIRMWARE_IDX_SDTA 0x41544447 /** * FU_IFWI_FPT_FIRMWARE_IDX_CKSM: * * The index for the IFWI checksum image. * * Since: 1.8.2 **/ #define FU_IFWI_FPT_FIRMWARE_IDX_CKSM 0x4d534b43 FuFirmware * fu_ifwi_fpt_firmware_new(void); fwupd-1.9.16/libfwupdplugin/fu-ifwi.rs000066400000000000000000000030611460375044200177110ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, ValidateBytes, ParseBytes)] struct IfwiCpd { header_marker: u32le == 0x44504324, num_of_entries: u32le, header_version: u8, entry_version: u8, header_length: u8 = $struct_size, checksum: u8, partition_name: u32le, crc32: u32le, } #[derive(New, ParseBytes)] struct IfwiCpdEntry { name: [char; 12], offset: u32le, length: u32le, _reserved1: [u8; 4], } #[derive(New, ParseBytes)] struct IfwiCpdManifest { header_type: u32le, header_length: u32le, // dwords header_version: u32le, flags: u32le, vendor: u32le, date: u32le, size: u32le, // dwords id: u32le, rsvd: u32le, version: u64le, svn: u32le, } #[derive(New, ParseBytes)] struct IfwiCpdManifestExt { extension_type: u32le, extension_length: u32le, } #[derive(New, ValidateBytes, ParseBytes)] struct IfwiFpt { header_marker: u32le == 0x54504624, num_of_entries: u32le, header_version: u8 = 0x20, entry_version: u8 == 0x10, header_length: u8 = $struct_size, flags: u8, ticks_to_add: u16le, tokens_to_add: u16le, uma_size: u32le, crc32: u32le, fitc_major: u16le, fitc_minor: u16le, fitc_hotfix: u16le, fitc_build: u16le, } #[derive(New, ParseBytes)] struct IfwiFptEntry { partition_name: u32le, _reserved1: [u8; 4], offset: u32le, length: u32le, // bytes _reserved2: [u8; 12], partition_type: u32le, // 0 for code, 1 for data, 2 for GLUT } fwupd-1.9.16/libfwupdplugin/fu-ihex-firmware.c000066400000000000000000000376761460375044200213430ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-common.h" #include "fu-firmware-common.h" #include "fu-ihex-firmware.h" #include "fu-mem.h" #include "fu-string.h" /** * FuIhexFirmware: * * A Intel hex (ihex) firmware image. * * See also: [class@FuFirmware] */ typedef struct { GPtrArray *records; guint8 padding_value; } FuIhexFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuIhexFirmware, fu_ihex_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_ihex_firmware_get_instance_private(o)) #define FU_IHEX_FIRMWARE_TOKENS_MAX 100000 /* lines */ /** * fu_ihex_firmware_get_records: * @self: A #FuIhexFirmware * * Returns the raw lines from tokenization. * * This might be useful if the plugin is expecting the hex file to be a list * of operations, rather than a simple linear image with filled holes. * * Returns: (transfer none) (element-type FuIhexFirmwareRecord): records * * Since: 1.3.4 **/ GPtrArray * fu_ihex_firmware_get_records(FuIhexFirmware *self) { FuIhexFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_IHEX_FIRMWARE(self), NULL); return priv->records; } /** * fu_ihex_firmware_set_padding_value: * @self: A #FuIhexFirmware * @padding_value: the byte used to pad the image * * Set the padding value to fill incomplete address ranges. * * The default value of zero can be changed to `0xff` if functions like * fu_bytes_is_empty() are going to be used on subsections of the data. * * Since: 1.6.0 **/ void fu_ihex_firmware_set_padding_value(FuIhexFirmware *self, guint8 padding_value) { FuIhexFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_IHEX_FIRMWARE(self)); priv->padding_value = padding_value; } static void fu_ihex_firmware_record_free(FuIhexFirmwareRecord *rcd) { g_string_free(rcd->buf, TRUE); g_byte_array_unref(rcd->data); g_free(rcd); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuIhexFirmwareRecord, fu_ihex_firmware_record_free) static FuIhexFirmwareRecord * fu_ihex_firmware_record_new(guint ln, const gchar *line, FwupdInstallFlags flags, GError **error) { g_autoptr(FuIhexFirmwareRecord) rcd = NULL; gsize linesz = strlen(line); guint line_end; guint16 addr16 = 0; /* check starting token */ if (line[0] != ':') { g_autofree gchar *strsafe = fu_strsafe(line, 5); if (strsafe != NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid starting token: %s", strsafe); return NULL; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid starting token"); return NULL; } /* length, 16-bit address, type */ rcd = g_new0(FuIhexFirmwareRecord, 1); rcd->ln = ln; rcd->data = g_byte_array_new(); rcd->buf = g_string_new(line); if (!fu_firmware_strparse_uint8_safe(line, linesz, 1, &rcd->byte_cnt, error)) return NULL; if (!fu_firmware_strparse_uint16_safe(line, linesz, 3, &addr16, error)) return NULL; rcd->addr = addr16; if (!fu_firmware_strparse_uint8_safe(line, linesz, 7, &rcd->record_type, error)) return NULL; /* position of checksum */ line_end = 9 + rcd->byte_cnt * 2; if (line_end > (guint)rcd->buf->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "line malformed, length: %u", line_end); return NULL; } /* verify checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint8 checksum = 0; for (guint i = 1; i < line_end + 2; i += 2) { guint8 data_tmp = 0; if (!fu_firmware_strparse_uint8_safe(line, linesz, i, &data_tmp, error)) return NULL; checksum += data_tmp; } if (checksum != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid checksum (0x%02x)", checksum); return NULL; } } /* add data */ for (guint i = 9; i < line_end; i += 2) { guint8 tmp_c = 0; if (!fu_firmware_strparse_uint8_safe(line, linesz, i, &tmp_c, error)) return NULL; fu_byte_array_append_uint8(rcd->data, tmp_c); } return g_steal_pointer(&rcd); } static const gchar * fu_ihex_firmware_record_type_to_string(guint8 record_type) { if (record_type == FU_IHEX_FIRMWARE_RECORD_TYPE_DATA) return "DATA"; if (record_type == FU_IHEX_FIRMWARE_RECORD_TYPE_EOF) return "EOF"; if (record_type == FU_IHEX_FIRMWARE_RECORD_TYPE_EXTENDED_SEGMENT) return "EXTENDED_SEGMENT"; if (record_type == FU_IHEX_FIRMWARE_RECORD_TYPE_START_SEGMENT) return "START_SEGMENT"; if (record_type == FU_IHEX_FIRMWARE_RECORD_TYPE_EXTENDED_LINEAR) return "EXTENDED_LINEAR"; if (record_type == FU_IHEX_FIRMWARE_RECORD_TYPE_START_LINEAR) return "ADDR32"; if (record_type == FU_IHEX_FIRMWARE_RECORD_TYPE_SIGNATURE) return "SIGNATURE"; return NULL; } typedef struct { FuIhexFirmware *self; FwupdInstallFlags flags; } FuIhexFirmwareTokenHelper; static gboolean fu_ihex_firmware_tokenize_cb(GString *token, guint token_idx, gpointer user_data, GError **error) { FuIhexFirmwareTokenHelper *helper = (FuIhexFirmwareTokenHelper *)user_data; FuIhexFirmwarePrivate *priv = GET_PRIVATE(helper->self); g_autoptr(FuIhexFirmwareRecord) rcd = NULL; /* sanity check */ if (token_idx > FU_IHEX_FIRMWARE_TOKENS_MAX) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "file has too many lines"); return FALSE; } /* remove WIN32 line endings */ g_strdelimit(token->str, "\r\x1a", '\0'); token->len = strlen(token->str); /* ignore blank lines */ if (token->len == 0) return TRUE; /* ignore comments */ if (g_str_has_prefix(token->str, ";")) return TRUE; /* parse record */ rcd = fu_ihex_firmware_record_new(token_idx + 1, token->str, helper->flags, error); if (rcd == NULL) { g_prefix_error(error, "invalid line %u: ", token_idx + 1); return FALSE; } g_ptr_array_add(priv->records, g_steal_pointer(&rcd)); return TRUE; } static gboolean fu_ihex_firmware_tokenize(FuFirmware *firmware, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuIhexFirmware *self = FU_IHEX_FIRMWARE(firmware); FuIhexFirmwareTokenHelper helper = {.self = self, .flags = flags}; return fu_strsplit_full(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), "\n", fu_ihex_firmware_tokenize_cb, &helper, error); } static gboolean fu_ihex_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuIhexFirmware *self = FU_IHEX_FIRMWARE(firmware); FuIhexFirmwarePrivate *priv = GET_PRIVATE(self); gboolean got_eof = FALSE; gboolean got_sig = FALSE; guint32 abs_addr = 0x0; guint32 addr_last = 0x0; guint32 img_addr = G_MAXUINT32; guint32 seg_addr = 0x0; g_autoptr(GBytes) img_bytes = NULL; g_autoptr(GByteArray) buf = g_byte_array_new(); /* parse records */ for (guint k = 0; k < priv->records->len; k++) { FuIhexFirmwareRecord *rcd = g_ptr_array_index(priv->records, k); guint16 addr16 = 0; guint32 addr = rcd->addr + seg_addr + abs_addr; guint32 len_hole; /* debug */ g_debug("%s:", fu_ihex_firmware_record_type_to_string(rcd->record_type)); g_debug("length:\t0x%02x", rcd->data->len); g_debug("addr:\t0x%08x", addr); /* sanity check */ if (rcd->record_type != FU_IHEX_FIRMWARE_RECORD_TYPE_EOF && rcd->data->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "record 0x%x had zero size", k); return FALSE; } /* process different record types */ switch (rcd->record_type) { case FU_IHEX_FIRMWARE_RECORD_TYPE_DATA: /* does not make sense */ if (got_eof) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot process data after EOF"); return FALSE; } if (rcd->data->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot parse invalid data"); return FALSE; } /* base address for element */ if (img_addr == G_MAXUINT32) img_addr = addr; /* does not make sense */ if (addr < addr_last) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid address 0x%x, last was 0x%x on line %u", (guint)addr, (guint)addr_last, rcd->ln); return FALSE; } /* any holes in the hex record */ len_hole = addr - addr_last; if (addr_last > 0 && len_hole > 0x100000) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "hole of 0x%x bytes too large to fill on line %u", (guint)len_hole, rcd->ln); return FALSE; } if (addr_last > 0x0 && len_hole > 1) { g_debug("filling address 0x%08x to 0x%08x on line %u", addr_last + 1, addr_last + len_hole - 1, rcd->ln); for (guint j = 1; j < len_hole; j++) fu_byte_array_append_uint8(buf, priv->padding_value); } addr_last = addr + rcd->data->len - 1; if (addr_last < addr) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "overflow of address 0x%x on line %u", (guint)addr, rcd->ln); return FALSE; } /* write into buf */ g_byte_array_append(buf, rcd->data->data, rcd->data->len); break; case FU_IHEX_FIRMWARE_RECORD_TYPE_EOF: if (got_eof) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "duplicate EOF, perhaps " "corrupt file"); return FALSE; } got_eof = TRUE; break; case FU_IHEX_FIRMWARE_RECORD_TYPE_EXTENDED_LINEAR: if (!fu_memread_uint16_safe(rcd->data->data, rcd->data->len, 0x0, &addr16, G_BIG_ENDIAN, error)) return FALSE; abs_addr = (guint32)addr16 << 16; g_debug("abs_addr:\t0x%02x on line %u", abs_addr, rcd->ln); break; case FU_IHEX_FIRMWARE_RECORD_TYPE_START_LINEAR: if (!fu_memread_uint32_safe(rcd->data->data, rcd->data->len, 0x0, &abs_addr, G_BIG_ENDIAN, error)) return FALSE; g_debug("abs_addr:\t0x%08x on line %u", abs_addr, rcd->ln); break; case FU_IHEX_FIRMWARE_RECORD_TYPE_EXTENDED_SEGMENT: if (!fu_memread_uint16_safe(rcd->data->data, rcd->data->len, 0x0, &addr16, G_BIG_ENDIAN, error)) return FALSE; /* segment base address, so ~1Mb addressable */ seg_addr = (guint32)addr16 * 16; g_debug("seg_addr:\t0x%08x on line %u", seg_addr, rcd->ln); break; case FU_IHEX_FIRMWARE_RECORD_TYPE_START_SEGMENT: /* initial content of the CS:IP registers */ if (!fu_memread_uint32_safe(rcd->data->data, rcd->data->len, 0x0, &seg_addr, G_BIG_ENDIAN, error)) return FALSE; g_debug("seg_addr:\t0x%02x on line %u", seg_addr, rcd->ln); break; case FU_IHEX_FIRMWARE_RECORD_TYPE_SIGNATURE: if (got_sig) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "duplicate signature, perhaps " "corrupt file"); return FALSE; } if (rcd->data->len > 0) { g_autoptr(GBytes) data_sig = g_bytes_new(rcd->data->data, rcd->data->len); g_autoptr(FuFirmware) img_sig = fu_firmware_new_from_bytes(data_sig); fu_firmware_set_id(img_sig, FU_FIRMWARE_ID_SIGNATURE); if (!fu_firmware_add_image_full(firmware, img_sig, error)) return FALSE; } got_sig = TRUE; break; default: /* vendors sneak in nonstandard sections past the EOF */ if (got_eof) break; g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid ihex record type %i on line %u", rcd->record_type, rcd->ln); return FALSE; } } /* no EOF */ if (!got_eof) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no EOF, perhaps truncated file"); return FALSE; } /* add single image */ img_bytes = g_bytes_new(buf->data, buf->len); if (img_addr != G_MAXUINT32) fu_firmware_set_addr(firmware, img_addr); fu_firmware_set_bytes(firmware, img_bytes); return TRUE; } static void fu_ihex_firmware_emit_chunk(GString *str, guint16 address, guint8 record_type, const guint8 *data, gsize sz) { guint8 checksum = 0x00; g_string_append_printf(str, ":%02X%04X%02X", (guint)sz, (guint)address, (guint)record_type); for (gsize j = 0; j < sz; j++) g_string_append_printf(str, "%02X", data[j]); checksum = (guint8)sz; checksum += (guint8)((address & 0xff00) >> 8); checksum += (guint8)(address & 0xff); checksum += record_type; for (gsize j = 0; j < sz; j++) checksum += data[j]; g_string_append_printf(str, "%02X\n", (guint)(((~checksum) + 0x01) & 0xff)); } static gboolean fu_ihex_firmware_image_to_string(GBytes *bytes, guint32 addr, guint8 record_type, GString *str, GError **error) { const guint8 *data; const guint chunk_size = 16; gsize len; guint32 address_offset_last = 0x0; /* get number of chunks */ data = g_bytes_get_data(bytes, &len); for (gsize i = 0; i < len; i += chunk_size) { guint32 address_tmp = addr + i; guint32 address_offset = (address_tmp >> 16) & 0xffff; gsize chunk_len = MIN(len - i, 16); /* need to offset */ if (address_offset != address_offset_last) { guint8 buf[2]; fu_memwrite_uint16(buf, address_offset, G_BIG_ENDIAN); fu_ihex_firmware_emit_chunk(str, 0x0, FU_IHEX_FIRMWARE_RECORD_TYPE_EXTENDED_LINEAR, buf, 2); address_offset_last = address_offset; } address_tmp &= 0xffff; fu_ihex_firmware_emit_chunk(str, address_tmp, record_type, data + i, chunk_len); } return TRUE; } static GByteArray * fu_ihex_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) fw = NULL; g_autoptr(FuFirmware) img_sig = NULL; g_autoptr(GString) str = g_string_new(""); /* payload */ fw = fu_firmware_get_bytes_with_patches(firmware, error); if (fw == NULL) return NULL; if (!fu_ihex_firmware_image_to_string(fw, fu_firmware_get_addr(firmware), FU_IHEX_FIRMWARE_RECORD_TYPE_DATA, str, error)) return NULL; /* signature */ img_sig = fu_firmware_get_image_by_id(firmware, FU_FIRMWARE_ID_SIGNATURE, NULL); if (img_sig != NULL) { g_autoptr(GBytes) img_fw = fu_firmware_get_bytes(img_sig, error); if (img_fw == NULL) return NULL; if (!fu_ihex_firmware_image_to_string(img_fw, 0, FU_IHEX_FIRMWARE_RECORD_TYPE_SIGNATURE, str, error)) return NULL; } /* add EOF */ fu_ihex_firmware_emit_chunk(str, 0x0, FU_IHEX_FIRMWARE_RECORD_TYPE_EOF, NULL, 0); g_byte_array_append(buf, (const guint8 *)str->str, str->len); return g_steal_pointer(&buf); } static void fu_ihex_firmware_finalize(GObject *object) { FuIhexFirmware *self = FU_IHEX_FIRMWARE(object); FuIhexFirmwarePrivate *priv = GET_PRIVATE(self); g_ptr_array_unref(priv->records); G_OBJECT_CLASS(fu_ihex_firmware_parent_class)->finalize(object); } static void fu_ihex_firmware_init(FuIhexFirmware *self) { FuIhexFirmwarePrivate *priv = GET_PRIVATE(self); priv->padding_value = 0x00; /* chosen as we can't write 0xffff to PIC14 */ priv->records = g_ptr_array_new_with_free_func((GFreeFunc)fu_ihex_firmware_record_free); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); fu_firmware_set_images_max(FU_FIRMWARE(self), 10); } static void fu_ihex_firmware_class_init(FuIhexFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_ihex_firmware_finalize; klass_firmware->parse = fu_ihex_firmware_parse; klass_firmware->tokenize = fu_ihex_firmware_tokenize; klass_firmware->write = fu_ihex_firmware_write; } /** * fu_ihex_firmware_new: * * Creates a new #FuFirmware of sub type Ihex * * Since: 1.3.1 **/ FuFirmware * fu_ihex_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_IHEX_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-ihex-firmware.h000066400000000000000000000022601460375044200213250ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_IHEX_FIRMWARE (fu_ihex_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuIhexFirmware, fu_ihex_firmware, FU, IHEX_FIRMWARE, FuFirmware) struct _FuIhexFirmwareClass { FuFirmwareClass parent_class; }; /** * FuIhexFirmwareRecord: * * A single Intel HEX record. **/ typedef struct { guint ln; GString *buf; guint8 byte_cnt; guint32 addr; guint8 record_type; GByteArray *data; } FuIhexFirmwareRecord; #define FU_IHEX_FIRMWARE_RECORD_TYPE_DATA 0x00 #define FU_IHEX_FIRMWARE_RECORD_TYPE_EOF 0x01 #define FU_IHEX_FIRMWARE_RECORD_TYPE_EXTENDED_SEGMENT 0x02 #define FU_IHEX_FIRMWARE_RECORD_TYPE_START_SEGMENT 0x03 #define FU_IHEX_FIRMWARE_RECORD_TYPE_EXTENDED_LINEAR 0x04 #define FU_IHEX_FIRMWARE_RECORD_TYPE_START_LINEAR 0x05 #define FU_IHEX_FIRMWARE_RECORD_TYPE_SIGNATURE 0xfd FuFirmware * fu_ihex_firmware_new(void); GPtrArray * fu_ihex_firmware_get_records(FuIhexFirmware *self) G_GNUC_NON_NULL(1); void fu_ihex_firmware_set_padding_value(FuIhexFirmware *self, guint8 padding_value) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-intel-thunderbolt-firmware.c000066400000000000000000000065161460375044200240360ustar00rootroot00000000000000/* * Copyright (C) 2021 Dell Inc. * Copyright (C) 2020 Richard Hughes * Copyright (C) 2020 Mario Limonciello * Copyright (C) 2017 Intel Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-byte-array.h" #include "fu-intel-thunderbolt-firmware.h" #include "fu-mem.h" /** * FuIntelThunderboltFirmware: * * The Non-Volatile-Memory file-format specification. This is what you would find as the update * payload. * * See also: [class@FuFirmware] */ G_DEFINE_TYPE(FuIntelThunderboltFirmware, fu_intel_thunderbolt_firmware, FU_TYPE_INTEL_THUNDERBOLT_NVM) static gboolean fu_intel_thunderbolt_nvm_valid_farb_pointer(guint32 pointer) { return pointer != 0 && pointer != 0xFFFFFF; } static gboolean fu_intel_thunderbolt_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { const guint32 farb_offsets[] = {0x0, 0x1000}; gboolean valid = FALSE; guint32 farb_pointer = 0x0; /* get header offset */ for (guint i = 0; i < G_N_ELEMENTS(farb_offsets); i++) { if (!fu_memread_uint24_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset + farb_offsets[i], &farb_pointer, G_LITTLE_ENDIAN, error)) return FALSE; if (fu_intel_thunderbolt_nvm_valid_farb_pointer(farb_pointer)) { valid = TRUE; break; } } if (!valid) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no valid farb pointer found"); return FALSE; } g_debug("detected digital section begins at 0x%x", farb_pointer); fu_firmware_set_offset(firmware, farb_pointer); /* FuIntelThunderboltNvm->parse */ return FU_FIRMWARE_CLASS(fu_intel_thunderbolt_firmware_parent_class) ->parse(firmware, fw, offset + farb_pointer, flags, error); } static GByteArray * fu_intel_thunderbolt_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) buf_nvm = NULL; /* sanity check */ if (fu_firmware_get_offset(firmware) < 0x08) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "not valid offset"); return NULL; } /* offset */ fu_byte_array_append_uint32(buf, fu_firmware_get_offset(firmware), G_LITTLE_ENDIAN); fu_byte_array_set_size(buf, fu_firmware_get_offset(firmware), 0x00); /* FuIntelThunderboltNvm->write */ buf_nvm = FU_FIRMWARE_CLASS(fu_intel_thunderbolt_firmware_parent_class)->write(firmware, error); if (buf_nvm == NULL) return NULL; g_byte_array_append(buf, buf_nvm->data, buf_nvm->len); /* success */ return g_steal_pointer(&buf); } static void fu_intel_thunderbolt_firmware_init(FuIntelThunderboltFirmware *self) { } static void fu_intel_thunderbolt_firmware_class_init(FuIntelThunderboltFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_intel_thunderbolt_firmware_parse; klass_firmware->write = fu_intel_thunderbolt_firmware_write; } /** * fu_intel_thunderbolt_firmware_new: * * Creates a new #FuFirmware of Intel NVM format * * Since: 1.8.5 **/ FuFirmware * fu_intel_thunderbolt_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_INTEL_THUNDERBOLT_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-intel-thunderbolt-firmware.h000066400000000000000000000012571460375044200240400ustar00rootroot00000000000000/* * Copyright (C) 2021 Dell Inc. * Copyright (C) 2020 Richard Hughes * Copyright (C) 2020 Mario Limonciello * Copyright (C) 2017 Intel Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-intel-thunderbolt-nvm.h" #define FU_TYPE_INTEL_THUNDERBOLT_FIRMWARE (fu_intel_thunderbolt_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuIntelThunderboltFirmware, fu_intel_thunderbolt_firmware, FU, INTEL_THUNDERBOLT_FIRMWARE, FuIntelThunderboltNvm) struct _FuIntelThunderboltFirmwareClass { FuIntelThunderboltNvmClass parent_class; }; FuFirmware * fu_intel_thunderbolt_firmware_new(void); fwupd-1.9.16/libfwupdplugin/fu-intel-thunderbolt-nvm.c000066400000000000000000000642331460375044200230220ustar00rootroot00000000000000/* * Copyright (C) 2021 Dell Inc. * Copyright (C) 2020 Richard Hughes * Copyright (C) 2020 Mario Limonciello * Copyright (C) 2017 Intel Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-intel-thunderbolt-nvm.h" #include "fu-intel-thunderbolt-struct.h" #include "fu-mem.h" #include "fu-string.h" #include "fu-version-common.h" /** * FuIntelThunderboltNvm: * * The Non-Volatile-Memory device specification. This is what you would find on the device SPI chip. * * See also: [class@FuFirmware] */ typedef struct { guint32 sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_LAST]; FuIntelThunderboltNvmFamily family; gboolean is_host; gboolean is_native; gboolean has_pd; guint16 vendor_id; guint16 device_id; guint16 model_id; guint gen; guint ports; guint8 flash_size; } FuIntelThunderboltNvmPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuIntelThunderboltNvm, fu_intel_thunderbolt_nvm, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_intel_thunderbolt_nvm_get_instance_private(o)) #define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_AVAILABLE_SECTIONS 0x0002 #define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_UCODE 0x0003 #define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DEVICE_ID 0x0005 #define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_VERSION 0x0009 #define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_HOST 0x0010 #define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLASH_SIZE 0x0045 #define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_ARC_PARAMS 0x0075 #define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_IS_NATIVE 0x007B #define FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DROM 0x010E #define FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_VENDOR_ID 0x0010 #define FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_MODEL_ID 0x0012 #define FU_INTEL_THUNDERBOLT_NVM_ARC_PARAMS_OFFSET_PD_POINTER 0x010C /** * fu_intel_thunderbolt_nvm_get_vendor_id: * @self: a #FuFirmware * * Gets the vendor ID. * * Returns: an integer, or 0x0 for unset * * Since: 1.8.5 **/ guint16 fu_intel_thunderbolt_nvm_get_vendor_id(FuIntelThunderboltNvm *self) { FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), G_MAXUINT16); return priv->vendor_id; } /** * fu_intel_thunderbolt_nvm_get_device_id: * @self: a #FuFirmware * * Gets the device ID. * * Returns: an integer, or 0x0 for unset * * Since: 1.8.5 **/ guint16 fu_intel_thunderbolt_nvm_get_device_id(FuIntelThunderboltNvm *self) { FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); return priv->device_id; } /** * fu_intel_thunderbolt_nvm_is_host: * @self: a #FuFirmware * * Gets if the firmware is designed for a host controller rather than a device. * * Returns: %TRUE for controller, %FALSE for device * * Since: 1.8.5 **/ gboolean fu_intel_thunderbolt_nvm_is_host(FuIntelThunderboltNvm *self) { FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), FALSE); return priv->is_host; } /** * fu_intel_thunderbolt_nvm_is_native: * @self: a #FuFirmware * * Gets if the device is native, i.e. not in recovery mode. * * Returns: %TRUE if set * * Since: 1.8.5 **/ gboolean fu_intel_thunderbolt_nvm_is_native(FuIntelThunderboltNvm *self) { FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), FALSE); return priv->is_native; } /** * fu_intel_thunderbolt_nvm_has_pd: * @self: a #FuFirmware * * Gets if the device has power delivery capability. * * Returns: %TRUE if set * * Since: 1.8.5 **/ gboolean fu_intel_thunderbolt_nvm_has_pd(FuIntelThunderboltNvm *self) { FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), FALSE); return priv->has_pd; } /** * fu_intel_thunderbolt_nvm_get_model_id: * @self: a #FuFirmware * * Gets the model ID. * * Returns: an integer, or 0x0 for unset * * Since: 1.8.5 **/ guint16 fu_intel_thunderbolt_nvm_get_model_id(FuIntelThunderboltNvm *self) { FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), 0x0); return priv->model_id; } /** * fu_intel_thunderbolt_nvm_get_flash_size: * @self: a #FuFirmware * * Gets the flash size. * * NOTE: This does not correspond to a size in bytes, or a power of 2 and is only useful for * comparison between firmware and device. * * Returns: an integer, or 0x0 for unset * * Since: 1.8.5 **/ guint8 fu_intel_thunderbolt_nvm_get_flash_size(FuIntelThunderboltNvm *self) { FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_INTEL_THUNDERBOLT_NVM(self), 0x0); return priv->flash_size; } static void fu_intel_thunderbolt_nvm_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware); FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "vendor_id", priv->vendor_id); fu_xmlb_builder_insert_kx(bn, "device_id", priv->device_id); fu_xmlb_builder_insert_kx(bn, "model_id", priv->model_id); fu_xmlb_builder_insert_kv(bn, "family", fu_intel_thunderbolt_nvm_family_to_string(priv->family)); fu_xmlb_builder_insert_kb(bn, "is_host", priv->is_host); fu_xmlb_builder_insert_kb(bn, "is_native", priv->is_native); fu_xmlb_builder_insert_kx(bn, "flash_size", priv->flash_size); fu_xmlb_builder_insert_kx(bn, "generation", priv->gen); fu_xmlb_builder_insert_kx(bn, "ports", priv->ports); fu_xmlb_builder_insert_kb(bn, "has_pd", priv->has_pd); for (guint i = 0; i < FU_INTEL_THUNDERBOLT_NVM_SECTION_LAST; i++) { if (priv->sections[i] != 0x0) { g_autofree gchar *tmp = g_strdup_printf("0x%x", priv->sections[i]); g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "section", "type", fu_intel_thunderbolt_nvm_section_to_string(i), "offset", tmp, NULL); g_assert(bc != NULL); } } } static inline gboolean fu_intel_thunderbolt_nvm_valid_pd_pointer(guint32 pointer) { return pointer != 0 && pointer != 0xFFFFFFFF; } static gboolean fu_intel_thunderbolt_nvm_read_uint8(FuIntelThunderboltNvm *self, FuIntelThunderboltNvmSection section, guint32 offset, guint8 *value, GError **error) { FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); g_autoptr(GBytes) fw = NULL; /* get blob and read */ fw = fu_firmware_get_bytes(FU_FIRMWARE(self), error); if (fw == NULL) return FALSE; return fu_memread_uint8_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), priv->sections[section] + offset, value, error); } static gboolean fu_intel_thunderbolt_nvm_read_uint16(FuIntelThunderboltNvm *self, FuIntelThunderboltNvmSection section, guint32 offset, guint16 *value, GError **error) { FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); g_autoptr(GBytes) fw = NULL; /* get blob and read */ fw = fu_firmware_get_bytes(FU_FIRMWARE(self), error); if (fw == NULL) return FALSE; return fu_memread_uint16_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), priv->sections[section] + offset, value, G_LITTLE_ENDIAN, error); } static gboolean fu_intel_thunderbolt_nvm_read_uint32(FuIntelThunderboltNvm *self, FuIntelThunderboltNvmSection section, guint32 offset, guint32 *value, GError **error) { FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); g_autoptr(GBytes) fw = NULL; /* get blob and read */ fw = fu_firmware_get_bytes(FU_FIRMWARE(self), error); if (fw == NULL) return FALSE; return fu_memread_uint32_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), priv->sections[section] + offset, value, G_LITTLE_ENDIAN, error); } /* * Size of ucode sections is uint16 value saved at the start of the section, * it's in DWORDS (4-bytes) units and it doesn't include itself. We need the * offset to the next section, so we translate it to bytes and add 2 for the * size field itself. * * offset parameter must be relative to digital section */ static gboolean fu_intel_thunderbolt_nvm_read_ucode_section_len(FuIntelThunderboltNvm *self, guint32 offset, guint16 *value, GError **error) { if (!fu_intel_thunderbolt_nvm_read_uint16(self, FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, offset, value, error)) { g_prefix_error(error, "failed to read ucode section len: "); return FALSE; } *value *= sizeof(guint32); *value += sizeof(guint16); return TRUE; } /* assumes sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL].offset is already set */ static gboolean fu_intel_thunderbolt_nvm_read_sections(FuIntelThunderboltNvm *self, GError **error) { guint32 offset; FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); if (priv->gen >= 3 || priv->gen == 0) { if (!fu_intel_thunderbolt_nvm_read_uint32( self, FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DROM, &offset, error)) return FALSE; priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] = offset + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL]; if (!fu_intel_thunderbolt_nvm_read_uint32( self, FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_ARC_PARAMS, &offset, error)) return FALSE; priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS] = offset + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL]; } if (priv->is_host && priv->gen > 2) { /* * To find the DRAM section, we have to jump from section to * section in a chain of sections. * available_sections location tells what sections exist at all * (with a flag per section). * ee_ucode_start_addr location tells the offset of the first * section in the list relatively to the digital section start. * After having the offset of the first section, we have a loop * over the section list. If the section exists, we read its * length (2 bytes at section start) and add it to current * offset to find the start of the next section. Otherwise, we * already have the next section offset... */ const guint8 DRAM_FLAG = 1 << 6; guint16 ucode_offset; guint8 available_sections = 0; if (!fu_intel_thunderbolt_nvm_read_uint8( self, FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_AVAILABLE_SECTIONS, &available_sections, error)) { g_prefix_error(error, "failed to read available sections: "); return FALSE; } if (!fu_intel_thunderbolt_nvm_read_uint16( self, FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_UCODE, &ucode_offset, error)) { g_prefix_error(error, "failed to read ucode offset: "); return FALSE; } offset = ucode_offset; if ((available_sections & DRAM_FLAG) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot find needed FW sections in the FW image file"); return FALSE; } for (guint8 i = 1; i < DRAM_FLAG; i <<= 1) { if (available_sections & i) { if (!fu_intel_thunderbolt_nvm_read_ucode_section_len(self, offset, &ucode_offset, error)) return FALSE; offset += ucode_offset; } } priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DRAM_UCODE] = offset + priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL]; } return TRUE; } static gboolean fu_intel_thunderbolt_nvm_missing_needed_drom(FuIntelThunderboltNvm *self) { FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); if (priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] != 0) return FALSE; if (priv->is_host && priv->gen < 3) return FALSE; return TRUE; } static gboolean fu_intel_thunderbolt_nvm_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware); FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); guint8 tmp = 0; guint16 version_raw = 0; struct { guint16 device_id; guint gen; FuIntelThunderboltNvmFamily family; guint ports; } hw_info_arr[] = {{0x156D, 2, FU_INTEL_THUNDERBOLT_NVM_FAMILY_FALCON_RIDGE, 2}, {0x156B, 2, FU_INTEL_THUNDERBOLT_NVM_FAMILY_FALCON_RIDGE, 1}, {0x157E, 2, FU_INTEL_THUNDERBOLT_NVM_FAMILY_WIN_RIDGE, 1}, {0x1578, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE, 2}, {0x1576, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE, 1}, {0x15C0, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE, 1}, {0x15D3, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE_C, 2}, {0x15DA, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE_C, 1}, {0x15E7, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE, 1}, {0x15EA, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE, 2}, {0x15EF, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE, 2}, {0x15EE, 3, FU_INTEL_THUNDERBOLT_NVM_FAMILY_BB, 0}, {0x0B26, 4, FU_INTEL_THUNDERBOLT_NVM_FAMILY_GOSHEN_RIDGE, 2}, /* Maple ridge devices * NOTE: These are expected to be flashed via UEFI capsules *not* * Thunderbolt plugin Flashing via fwupd will require matching kernel * work. They're left here only for parsing the binaries */ {0x1136, 4, FU_INTEL_THUNDERBOLT_NVM_FAMILY_MAPLE_RIDGE, 2}, {0x1137, 4, FU_INTEL_THUNDERBOLT_NVM_FAMILY_MAPLE_RIDGE, 2}, {0}}; g_autofree gchar *version = NULL; g_autoptr(FuFirmware) img_payload = NULL; g_autoptr(GBytes) fw_payload = NULL; /* add this straight away */ priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL] = offset; /* is native */ if (!fu_intel_thunderbolt_nvm_read_uint8( self, FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_IS_NATIVE, &tmp, error)) { g_prefix_error(error, "failed to read native: "); return FALSE; } priv->is_native = tmp & 0x20; /* we're only reading the first chunk */ if (g_bytes_get_size(fw) == 0x80) return TRUE; /* host or device */ if (!fu_intel_thunderbolt_nvm_read_uint8(self, FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_HOST, &tmp, error)) { g_prefix_error(error, "failed to read is-host: "); return FALSE; } priv->is_host = tmp & (1 << 1); /* device ID */ if (!fu_intel_thunderbolt_nvm_read_uint16(self, FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DEVICE_ID, &priv->device_id, error)) { g_prefix_error(error, "failed to read device-id: "); return FALSE; } /* this is best-effort */ for (guint i = 0; hw_info_arr[i].device_id != 0; i++) { if (hw_info_arr[i].device_id == priv->device_id) { priv->family = hw_info_arr[i].family; priv->gen = hw_info_arr[i].gen; priv->ports = hw_info_arr[i].ports; break; } } if (priv->family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_UNKNOWN) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unknown NVM family"); return FALSE; } if (priv->ports == 0 && priv->is_host) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unknown controller: %x", priv->device_id); return FALSE; } /* read sections from file */ if (!fu_intel_thunderbolt_nvm_read_sections(self, error)) return FALSE; if (fu_intel_thunderbolt_nvm_missing_needed_drom(self)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cannot find required drom section"); return FALSE; } /* vendor:model */ if (priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM] != 0x0) { if (!fu_intel_thunderbolt_nvm_read_uint16( self, FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM, FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_VENDOR_ID, &priv->vendor_id, error)) { g_prefix_error(error, "failed to read vendor-id: "); return FALSE; } if (!fu_intel_thunderbolt_nvm_read_uint16( self, FU_INTEL_THUNDERBOLT_NVM_SECTION_DROM, FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_MODEL_ID, &priv->model_id, error)) { g_prefix_error(error, "failed to read model-id: "); return FALSE; } } /* versions */ switch (priv->family) { case FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE: case FU_INTEL_THUNDERBOLT_NVM_FAMILY_GOSHEN_RIDGE: if (!fu_intel_thunderbolt_nvm_read_uint16( self, FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_VERSION, &version_raw, error)) { g_prefix_error(error, "failed to read version: "); return FALSE; } fu_firmware_set_version_raw(FU_FIRMWARE(self), version_raw); version = fu_version_from_uint16(version_raw, FWUPD_VERSION_FORMAT_BCD); fu_firmware_set_version(FU_FIRMWARE(self), version); break; default: break; } if (priv->is_host) { switch (priv->family) { case FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE: case FU_INTEL_THUNDERBOLT_NVM_FAMILY_ALPINE_RIDGE_C: case FU_INTEL_THUNDERBOLT_NVM_FAMILY_TITAN_RIDGE: /* used for comparison between old and new image, not a raw number */ if (!fu_intel_thunderbolt_nvm_read_uint8( self, FU_INTEL_THUNDERBOLT_NVM_SECTION_DIGITAL, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLASH_SIZE, &tmp, error)) { g_prefix_error(error, "failed to read flash size: "); return FALSE; } priv->flash_size = tmp & 0x07; break; default: break; } } /* we're only reading enough to get the vendor-id and model-id */ if (offset == 0x0 && g_bytes_get_size(fw) < priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS]) return TRUE; /* has PD */ if (priv->sections[FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS] != 0x0) { guint32 pd_pointer = 0x0; if (!fu_intel_thunderbolt_nvm_read_uint32( self, FU_INTEL_THUNDERBOLT_NVM_SECTION_ARC_PARAMS, FU_INTEL_THUNDERBOLT_NVM_ARC_PARAMS_OFFSET_PD_POINTER, &pd_pointer, error)) { g_prefix_error(error, "failed to read pd-pointer: "); return FALSE; } priv->has_pd = fu_intel_thunderbolt_nvm_valid_pd_pointer(pd_pointer); } /* as as easy-to-grab payload blob */ if (offset > 0) { fw_payload = fu_bytes_new_offset(fw, offset, g_bytes_get_size(fw) - offset, error); if (fw_payload == NULL) return FALSE; } else { fw_payload = g_bytes_ref(fw); } img_payload = fu_firmware_new_from_bytes(fw_payload); fu_firmware_set_id(img_payload, FU_FIRMWARE_ID_PAYLOAD); if (!fu_firmware_add_image_full(firmware, img_payload, error)) return FALSE; /* success */ return TRUE; } /* can only write version 3 NVM */ static GByteArray * fu_intel_thunderbolt_nvm_write(FuFirmware *firmware, GError **error) { FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware); FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); guint32 digital_size = 0x120; guint32 drom_offset = 0 + digital_size; guint32 drom_size = 0x20; guint32 arc_param_offset = drom_offset + drom_size; guint32 arc_param_size = 0x120; g_autoptr(GByteArray) buf = g_byte_array_new(); /* minimal size */ fu_byte_array_set_size(buf, arc_param_offset + arc_param_size, 0x0); /* digital section */ if (!fu_memwrite_uint8_safe(buf->data, buf->len, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_AVAILABLE_SECTIONS, 0x0, error)) return NULL; if (!fu_memwrite_uint16_safe(buf->data, buf->len, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_UCODE, 0x0, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memwrite_uint8_safe(buf->data, buf->len, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_IS_NATIVE, priv->is_native ? 0x20 : 0x0, error)) return NULL; if (!fu_memwrite_uint8_safe(buf->data, buf->len, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLAGS_HOST, priv->is_host ? 0x2 : 0x0, error)) return NULL; if (!fu_memwrite_uint32_safe(buf->data, buf->len, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DEVICE_ID, priv->device_id, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memwrite_uint16_safe(buf->data, buf->len, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_VERSION, fu_firmware_get_version_raw(firmware), G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memwrite_uint8_safe(buf->data, buf->len, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_FLASH_SIZE, priv->flash_size, error)) return NULL; /* drom section */ if (!fu_memwrite_uint32_safe(buf->data, buf->len, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_DROM, drom_offset, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memwrite_uint16_safe(buf->data, buf->len, drom_offset + FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_VENDOR_ID, priv->vendor_id, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memwrite_uint16_safe(buf->data, buf->len, drom_offset + FU_INTEL_THUNDERBOLT_NVM_DROM_OFFSET_MODEL_ID, priv->model_id, G_LITTLE_ENDIAN, error)) return NULL; /* ARC param section */ if (!fu_memwrite_uint32_safe(buf->data, buf->len, FU_INTEL_THUNDERBOLT_NVM_DIGITAL_OFFSET_ARC_PARAMS, arc_param_offset, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memwrite_uint32_safe(buf->data, buf->len, arc_param_offset + FU_INTEL_THUNDERBOLT_NVM_ARC_PARAMS_OFFSET_PD_POINTER, priv->has_pd ? 0x1 : 0x0, G_LITTLE_ENDIAN, error)) return NULL; /* success */ return g_steal_pointer(&buf); } static gboolean fu_intel_thunderbolt_nvm_check_compatible(FuFirmware *firmware, FuFirmware *firmware_other, FwupdInstallFlags flags, GError **error) { FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware); FuIntelThunderboltNvm *other = FU_INTEL_THUNDERBOLT_NVM(firmware_other); FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); FuIntelThunderboltNvmPrivate *priv_other = GET_PRIVATE(other); if (priv->is_host != priv_other->is_host) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect firmware mode, got %s, expected %s", priv->is_host ? "host" : "device", priv_other->is_host ? "host" : "device"); return FALSE; } if (priv->vendor_id != priv_other->vendor_id) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect device vendor, got 0x%04x, expected 0x%04x", priv->vendor_id, priv_other->vendor_id); return FALSE; } if (priv->device_id != priv_other->device_id) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect device type, got 0x%04x, expected 0x%04x", priv->device_id, priv_other->device_id); return FALSE; } if ((flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) == 0) { if (priv->model_id != priv_other->model_id) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect device model, got 0x%04x, expected 0x%04x", priv->model_id, priv_other->model_id); return FALSE; } /* old firmware has PD but new doesn't (we don't care about other way around) */ if (priv->has_pd && !priv_other->has_pd) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect PD section"); return FALSE; } if (priv->flash_size != priv_other->flash_size) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect flash size, got 0x%x and expected 0x%x", priv->flash_size, priv_other->flash_size); return FALSE; } } return TRUE; } static gboolean fu_intel_thunderbolt_nvm_build(FuFirmware *firmware, XbNode *n, GError **error) { FuIntelThunderboltNvm *self = FU_INTEL_THUNDERBOLT_NVM(firmware); FuIntelThunderboltNvmPrivate *priv = GET_PRIVATE(self); const gchar *tmp; /* simple properties */ tmp = xb_node_query_text(n, "vendor_id", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) return FALSE; priv->vendor_id = val; } tmp = xb_node_query_text(n, "device_id", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) return FALSE; priv->device_id = val; } tmp = xb_node_query_text(n, "model_id", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) return FALSE; priv->model_id = val; } tmp = xb_node_query_text(n, "family", NULL); if (tmp != NULL) { priv->family = fu_intel_thunderbolt_nvm_family_from_string(tmp); if (priv->family == FU_INTEL_THUNDERBOLT_NVM_FAMILY_UNKNOWN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unknown family: %s", tmp); return FALSE; } } tmp = xb_node_query_text(n, "flash_size", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, 0x07, error)) return FALSE; priv->flash_size = val; } tmp = xb_node_query_text(n, "is_host", NULL); if (tmp != NULL) { if (!fu_strtobool(tmp, &priv->is_host, error)) return FALSE; } tmp = xb_node_query_text(n, "is_native", NULL); if (tmp != NULL) { if (!fu_strtobool(tmp, &priv->is_native, error)) return FALSE; } /* success */ return TRUE; } static void fu_intel_thunderbolt_nvm_init(FuIntelThunderboltNvm *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); } static void fu_intel_thunderbolt_nvm_class_init(FuIntelThunderboltNvmClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->export = fu_intel_thunderbolt_nvm_export; klass_firmware->parse = fu_intel_thunderbolt_nvm_parse; klass_firmware->write = fu_intel_thunderbolt_nvm_write; klass_firmware->build = fu_intel_thunderbolt_nvm_build; klass_firmware->check_compatible = fu_intel_thunderbolt_nvm_check_compatible; } /** * fu_intel_thunderbolt_nvm_new: * * Creates a new #FuFirmware of Intel NVM format * * Since: 1.8.5 **/ FuFirmware * fu_intel_thunderbolt_nvm_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_INTEL_THUNDERBOLT_NVM, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-intel-thunderbolt-nvm.h000066400000000000000000000023731460375044200230240ustar00rootroot00000000000000/* * Copyright (C) 2021 Dell Inc. * Copyright (C) 2020 Richard Hughes * Copyright (C) 2020 Mario Limonciello * Copyright (C) 2017 Intel Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_INTEL_THUNDERBOLT_NVM (fu_intel_thunderbolt_nvm_get_type()) G_DECLARE_DERIVABLE_TYPE(FuIntelThunderboltNvm, fu_intel_thunderbolt_nvm, FU, INTEL_THUNDERBOLT_NVM, FuFirmware) struct _FuIntelThunderboltNvmClass { FuFirmwareClass parent_class; }; guint16 fu_intel_thunderbolt_nvm_get_vendor_id(FuIntelThunderboltNvm *self) G_GNUC_NON_NULL(1); guint16 fu_intel_thunderbolt_nvm_get_device_id(FuIntelThunderboltNvm *self) G_GNUC_NON_NULL(1); guint16 fu_intel_thunderbolt_nvm_get_model_id(FuIntelThunderboltNvm *self) G_GNUC_NON_NULL(1); gboolean fu_intel_thunderbolt_nvm_is_host(FuIntelThunderboltNvm *self) G_GNUC_NON_NULL(1); gboolean fu_intel_thunderbolt_nvm_is_native(FuIntelThunderboltNvm *self) G_GNUC_NON_NULL(1); gboolean fu_intel_thunderbolt_nvm_has_pd(FuIntelThunderboltNvm *self) G_GNUC_NON_NULL(1); guint8 fu_intel_thunderbolt_nvm_get_flash_size(FuIntelThunderboltNvm *self) G_GNUC_NON_NULL(1); FuFirmware * fu_intel_thunderbolt_nvm_new(void); fwupd-1.9.16/libfwupdplugin/fu-intel-thunderbolt.rs000066400000000000000000000006451460375044200224230ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum IntelThunderboltNvmSection { Digital, Drom, ArcParams, DramUcode, Last, } #[derive(ToString, FromString)] enum IntelThunderboltNvmFamily { Unknown, FalconRidge, WinRidge, AlpineRidge, AlpineRidgeC, TitanRidge, Bb, MapleRidge, GoshenRidge, } fwupd-1.9.16/libfwupdplugin/fu-io-channel.c000066400000000000000000000303761460375044200205770ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuIOChannel" #include "config.h" #include #include #include #include #ifdef HAVE_POLL_H #include #endif #include #include #include "fwupd-error.h" #include "fu-common.h" #include "fu-io-channel.h" /** * FuIOChannel: * * A bidirectional IO channel which can be read from and written to. */ struct _FuIOChannel { GObject parent_instance; gint fd; }; G_DEFINE_TYPE(FuIOChannel, fu_io_channel, G_TYPE_OBJECT) /** * fu_io_channel_unix_get_fd: * @self: a #FuIOChannel * * Gets the file descriptor for the device. * * Returns: fd, or -1 for not open. * * Since: 1.2.2 **/ gint fu_io_channel_unix_get_fd(FuIOChannel *self) { g_return_val_if_fail(FU_IS_IO_CHANNEL(self), -1); return self->fd; } /** * fu_io_channel_shutdown: * @self: a #FuIOChannel * @error: (nullable): optional return location for an error * * Closes the file descriptor for the device if open. * * Returns: %TRUE if all the FD was closed. * * Since: 1.2.2 **/ gboolean fu_io_channel_shutdown(FuIOChannel *self, GError **error) { g_return_val_if_fail(FU_IS_IO_CHANNEL(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (self->fd != -1) { if (!g_close(self->fd, error)) return FALSE; self->fd = -1; } return TRUE; } static gboolean fu_io_channel_flush_input(FuIOChannel *self, GError **error) { GPollFD poll = { .fd = self->fd, .events = G_IO_IN | G_IO_ERR, }; while (g_poll(&poll, 1, 0) > 0) { gchar c; gint r = read(self->fd, &c, 1); if (r < 0 && errno != EINTR) break; } return TRUE; } /** * fu_io_channel_write_bytes: * @self: a #FuIOChannel * @bytes: buffer to write * @timeout_ms: timeout in ms * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: (nullable): optional return location for an error * * Writes bytes to the TTY, that will fail if exceeding @timeout_ms. * * Returns: %TRUE if all the bytes was written * * Since: 1.2.2 **/ gboolean fu_io_channel_write_bytes(FuIOChannel *self, GBytes *bytes, guint timeout_ms, FuIOChannelFlags flags, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(bytes, &bufsz); return fu_io_channel_write_raw(self, buf, bufsz, timeout_ms, flags, error); } /** * fu_io_channel_write_byte_array: * @self: a #FuIOChannel * @buf: buffer to write * @timeout_ms: timeout in ms * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: (nullable): optional return location for an error * * Writes bytes to the TTY, that will fail if exceeding @timeout_ms. * * Returns: %TRUE if all the bytes was written * * Since: 1.3.2 **/ gboolean fu_io_channel_write_byte_array(FuIOChannel *self, GByteArray *buf, guint timeout_ms, FuIOChannelFlags flags, GError **error) { return fu_io_channel_write_raw(self, buf->data, buf->len, timeout_ms, flags, error); } /** * fu_io_channel_write_raw: * @self: a #FuIOChannel * @data: buffer to write * @datasz: size of @data * @timeout_ms: timeout in ms * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: (nullable): optional return location for an error * * Writes bytes to the TTY, that will fail if exceeding @timeout_ms. * * Returns: %TRUE if all the bytes was written * * Since: 1.2.2 **/ gboolean fu_io_channel_write_raw(FuIOChannel *self, const guint8 *data, gsize datasz, guint timeout_ms, FuIOChannelFlags flags, GError **error) { gsize idx = 0; g_return_val_if_fail(FU_IS_IO_CHANNEL(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* flush pending reads */ if (flags & FU_IO_CHANNEL_FLAG_FLUSH_INPUT) { if (!fu_io_channel_flush_input(self, error)) return FALSE; } /* blocking IO */ if (flags & FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO) { gssize wrote = write(self->fd, data, datasz); if (wrote != (gssize)datasz) { if (errno == EPROTO) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to write: %s", g_strerror(errno)); return FALSE; } g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write: " "wrote %" G_GSSIZE_FORMAT " of %" G_GSIZE_FORMAT, wrote, datasz); return FALSE; } return TRUE; } /* nonblocking IO */ while (idx < datasz) { gint rc; GPollFD fds = { .fd = self->fd, .events = G_IO_OUT | G_IO_ERR, }; /* wait for data to be allowed to write without blocking */ rc = g_poll(&fds, 1, (gint)timeout_ms); if (rc == 0) break; if (rc < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to poll %i", self->fd); return FALSE; } /* we can write data */ if (fds.revents & G_IO_OUT) { gssize len = write(self->fd, data + idx, datasz - idx); if (len < 0) { if (errno == EAGAIN) { g_debug("got EAGAIN, trying harder"); continue; } if (errno == EPROTO) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to write: %s", g_strerror(errno)); return FALSE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to write %" G_GSIZE_FORMAT " bytes to %i: %s", datasz, self->fd, g_strerror(errno)); return FALSE; } if (flags & FU_IO_CHANNEL_FLAG_SINGLE_SHOT) break; idx += len; } } return TRUE; } /** * fu_io_channel_read_bytes: * @self: a #FuIOChannel * @count: number of bytes to read, or -1 for no limit * @timeout_ms: timeout in ms * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: (nullable): optional return location for an error * * Reads bytes from the TTY, that will fail if exceeding @timeout_ms. * * Returns: a #GBytes (which may be bigger than @count), or %NULL for error * * Since: 1.2.2 **/ GBytes * fu_io_channel_read_bytes(FuIOChannel *self, gssize count, guint timeout_ms, FuIOChannelFlags flags, GError **error) { g_autoptr(GByteArray) buf = fu_io_channel_read_byte_array(self, count, timeout_ms, flags, error); if (buf == NULL) return NULL; return g_bytes_new(buf->data, buf->len); } /** * fu_io_channel_read_byte_array: * @self: a #FuIOChannel * @count: number of bytes to read, or -1 for no limit * @timeout_ms: timeout in ms * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: (nullable): optional return location for an error * * Reads bytes from the TTY, that will fail if exceeding @timeout_ms. * * Returns: (transfer full): a #GByteArray (which may be bigger than @count), or %NULL for error * * Since: 1.3.2 **/ GByteArray * fu_io_channel_read_byte_array(FuIOChannel *self, gssize count, guint timeout_ms, FuIOChannelFlags flags, GError **error) { GPollFD fds = { .fd = self->fd, .events = G_IO_IN | G_IO_PRI | G_IO_ERR, }; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) buf_tmp = g_byte_array_new(); g_return_val_if_fail(FU_IS_IO_CHANNEL(self), NULL); /* a temp buf of 1k or smaller size */ g_byte_array_set_size(buf_tmp, count >= 0 ? MIN(count, 1024) : 1024); /* blocking IO */ if (flags & FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO) { do { gssize len = read(self->fd, buf_tmp->data, buf_tmp->len); if (len < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read %i: %s", self->fd, g_strerror(errno)); return NULL; } if (len == 0) break; if (flags & FU_IO_CHANNEL_FLAG_SINGLE_SHOT) break; g_byte_array_append(buf, buf_tmp->data, len); } while (count < 0 || buf->len < (gsize)count); return g_steal_pointer(&buf); } /* nonblocking IO */ while (TRUE) { /* wait for data to appear */ gint rc = g_poll(&fds, 1, (gint)timeout_ms); if (rc == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "timeout"); return NULL; } if (rc < 0) { if (errno == EINTR) continue; g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to poll %i", self->fd); return NULL; } /* we have data to read */ if (fds.revents & G_IO_IN) { gssize len = read(self->fd, buf_tmp->data, buf_tmp->len); if (len < 0) { if (errno == EINTR) continue; if (errno == EAGAIN) continue; g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read %i: %s", self->fd, g_strerror(errno)); return NULL; } if (len == 0) break; if (len > 0) g_byte_array_append(buf, buf_tmp->data, len); /* check maximum size */ if (count > 0 && buf->len >= (guint)count) break; if (flags & FU_IO_CHANNEL_FLAG_SINGLE_SHOT) break; continue; } if (fds.revents & G_IO_ERR) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "error condition"); return NULL; } if (fds.revents & G_IO_HUP) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "connection hung up"); return NULL; } if (fds.revents & G_IO_NVAL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "invalid request"); return NULL; } } /* no data */ if (buf->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "no data received from device in %ums", timeout_ms); return NULL; } /* return blob */ return g_steal_pointer(&buf); } /** * fu_io_channel_read_raw: * @self: a #FuIOChannel * @buf: (nullable): optional buffer * @bufsz: size of @buf * @bytes_read: (out) (nullable): data written to @buf * @timeout_ms: timeout in ms * @flags: channel flags, e.g. %FU_IO_CHANNEL_FLAG_SINGLE_SHOT * @error: (nullable): optional return location for an error * * Reads bytes from the TTY, that will fail if exceeding @timeout_ms. * * Returns: a #GBytes, or %NULL for error * * Since: 1.2.2 **/ gboolean fu_io_channel_read_raw(FuIOChannel *self, guint8 *buf, gsize bufsz, gsize *bytes_read, guint timeout_ms, FuIOChannelFlags flags, GError **error) { g_autoptr(GByteArray) tmp = NULL; g_return_val_if_fail(FU_IS_IO_CHANNEL(self), FALSE); tmp = fu_io_channel_read_byte_array(self, bufsz, timeout_ms, flags, error); if (tmp == NULL) return FALSE; if (buf != NULL) memcpy(buf, tmp->data, MIN(tmp->len, bufsz)); if (bytes_read != NULL) *bytes_read = tmp->len; return TRUE; } static void fu_io_channel_finalize(GObject *object) { FuIOChannel *self = FU_IO_CHANNEL(object); if (self->fd != -1) g_close(self->fd, NULL); G_OBJECT_CLASS(fu_io_channel_parent_class)->finalize(object); } static void fu_io_channel_class_init(FuIOChannelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_io_channel_finalize; } static void fu_io_channel_init(FuIOChannel *self) { self->fd = -1; } /** * fu_io_channel_unix_new: * @fd: file descriptor * * Creates a new object to write and read from. * * Returns: a #FuIOChannel * * Since: 1.2.2 **/ FuIOChannel * fu_io_channel_unix_new(gint fd) { FuIOChannel *self; self = g_object_new(FU_TYPE_IO_CHANNEL, NULL); self->fd = fd; return FU_IO_CHANNEL(self); } /** * fu_io_channel_new_file: * @filename: device file * @error: (nullable): optional return location for an error * * Creates a new object to write and read from. * * Returns: a #FuIOChannel * * Since: 1.2.2 **/ FuIOChannel * fu_io_channel_new_file(const gchar *filename, GError **error) { #ifdef HAVE_POLL_H gint fd; g_return_val_if_fail(filename != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); fd = g_open(filename, O_RDWR | O_NONBLOCK, S_IRWXU); if (fd < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to open %s", filename); return NULL; } return fu_io_channel_unix_new(fd); #else g_return_val_if_fail(filename != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); return NULL; #endif } fwupd-1.9.16/libfwupdplugin/fu-io-channel.h000066400000000000000000000050331460375044200205740ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_IO_CHANNEL (fu_io_channel_get_type()) G_DECLARE_FINAL_TYPE(FuIOChannel, fu_io_channel, FU, IO_CHANNEL, GObject) /** * FuIOChannelFlags: * @FU_IO_CHANNEL_FLAG_NONE: No flags are set * @FU_IO_CHANNEL_FLAG_SINGLE_SHOT: Only one read or write is expected * @FU_IO_CHANNEL_FLAG_FLUSH_INPUT: Flush pending input before writing * @FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO: Block waiting for the TTY * * The flags used when reading data from the TTY. **/ typedef enum { FU_IO_CHANNEL_FLAG_NONE = 0, /* Since: 1.2.2 */ FU_IO_CHANNEL_FLAG_SINGLE_SHOT = 1 << 0, /* Since: 1.2.2 */ FU_IO_CHANNEL_FLAG_FLUSH_INPUT = 1 << 1, /* Since: 1.2.2 */ FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO = 1 << 2, /* Since: 1.2.2 */ /*< private >*/ FU_IO_CHANNEL_FLAG_LAST } FuIOChannelFlags; FuIOChannel * fu_io_channel_unix_new(gint fd); FuIOChannel * fu_io_channel_new_file(const gchar *filename, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gint fu_io_channel_unix_get_fd(FuIOChannel *self) G_GNUC_NON_NULL(1); gboolean fu_io_channel_shutdown(FuIOChannel *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_io_channel_write_raw(FuIOChannel *self, const guint8 *data, gsize datasz, guint timeout_ms, FuIOChannelFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_io_channel_read_raw(FuIOChannel *self, guint8 *buf, gsize bufsz, gsize *bytes_read, guint timeout_ms, FuIOChannelFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_io_channel_write_bytes(FuIOChannel *self, GBytes *bytes, guint timeout_ms, FuIOChannelFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_io_channel_write_byte_array(FuIOChannel *self, GByteArray *buf, guint timeout_ms, FuIOChannelFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); GBytes * fu_io_channel_read_bytes(FuIOChannel *self, gssize count, guint timeout_ms, FuIOChannelFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GByteArray * fu_io_channel_read_byte_array(FuIOChannel *self, gssize count, guint timeout_ms, FuIOChannelFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-kenv.c000066400000000000000000000021231460375044200175120ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_KENV_H #include #endif #include "fwupd-error.h" #include "fu-kenv.h" /** * fu_kenv_get_string: * @key: a kenv key, e.g. `smbios.bios.version` * @error: (nullable): optional return location for an error * * Gets a BSD kernel environment string. This will not work on Linux or * Windows. * * Returns: (transfer full): a string, or %NULL if the @key was not found * * Since: 1.6.1 **/ gchar * fu_kenv_get_string(const gchar *key, GError **error) { #ifdef HAVE_KENV_H gchar buf[128] = {'\0'}; g_return_val_if_fail(key != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (kenv(KENV_GET, key, buf, sizeof(buf)) == -1) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cannot get kenv request for %s", key); return NULL; } return g_strndup(buf, sizeof(buf)); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "kenv not supported"); return NULL; #endif } fwupd-1.9.16/libfwupdplugin/fu-kenv.h000066400000000000000000000003371460375044200175240ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gchar * fu_kenv_get_string(const gchar *key, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-kernel.c000066400000000000000000000355341460375044200200430ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include #include #ifdef HAVE_UTSNAME_H #include #endif #include "fu-bytes.h" #include "fu-common.h" #include "fu-kernel.h" #include "fu-path.h" #include "fu-string.h" #include "fu-version-common.h" /** * fu_kernel_locked_down: * * Determines if kernel lockdown in effect * * Since: 1.8.2 **/ gboolean fu_kernel_locked_down(void) { #ifdef __linux__ gsize len = 0; g_autofree gchar *dir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_SECURITY); g_autofree gchar *fname = g_build_filename(dir, "lockdown", NULL); g_autofree gchar *data = NULL; g_auto(GStrv) options = NULL; if (!g_file_test(fname, G_FILE_TEST_EXISTS)) return FALSE; if (!g_file_get_contents(fname, &data, &len, NULL)) return FALSE; if (len < 1) return FALSE; options = g_strsplit(data, " ", -1); for (guint i = 0; options[i] != NULL; i++) { if (g_strcmp0(options[i], "[none]") == 0) return FALSE; } return TRUE; #else return FALSE; #endif } /** * fu_kernel_check_version: * @minimum_kernel: (not nullable): The minimum kernel version to check against * @error: (nullable): optional return location for an error * * Determines if the system is running at least a certain required kernel version * * Since: 1.8.2 **/ gboolean fu_kernel_check_version(const gchar *minimum_kernel, GError **error) { #ifdef HAVE_UTSNAME_H struct utsname name_tmp; g_return_val_if_fail(error == NULL || *error == NULL, FALSE); g_return_val_if_fail(minimum_kernel != NULL, FALSE); memset(&name_tmp, 0, sizeof(struct utsname)); if (uname(&name_tmp) < 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to read kernel version"); return FALSE; } if (fu_version_compare(name_tmp.release, minimum_kernel, FWUPD_VERSION_FORMAT_TRIPLET) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "kernel %s doesn't meet minimum %s", name_tmp.release, minimum_kernel); return FALSE; } return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "platform doesn't support checking for minimum Linux kernel"); return FALSE; #endif } /** * fu_kernel_get_firmware_search_path: * @error: (nullable): optional return location for an error * * Reads the FU_PATH_KIND_FIRMWARE_SEARCH and * returns its contents * * Returns: a pointer to a gchar array * * Since: 1.8.2 **/ gchar * fu_kernel_get_firmware_search_path(GError **error) { gsize sz = 0; g_autofree gchar *sys_fw_search_path = NULL; g_autofree gchar *contents = NULL; sys_fw_search_path = fu_path_from_kind(FU_PATH_KIND_FIRMWARE_SEARCH); if (!g_file_get_contents(sys_fw_search_path, &contents, &sz, error)) return NULL; /* sanity check */ if (contents == NULL || sz == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get firmware search path from %s", sys_fw_search_path); return NULL; } /* remove newline character */ if (contents[sz - 1] == '\n') contents[sz - 1] = 0; g_debug("read firmware search path (%" G_GSIZE_FORMAT "): %s", sz, contents); return g_steal_pointer(&contents); } /** * fu_kernel_set_firmware_search_path: * @path: NUL-terminated string * @error: (nullable): optional return location for an error * * Writes path to the FU_PATH_KIND_FIRMWARE_SEARCH * * Returns: %TRUE if successful * * Since: 1.8.2 **/ gboolean fu_kernel_set_firmware_search_path(const gchar *path, GError **error) { #if GLIB_CHECK_VERSION(2, 66, 0) g_autofree gchar *sys_fw_search_path_prm = NULL; g_return_val_if_fail(path != NULL, FALSE); g_return_val_if_fail(strlen(path) < PATH_MAX, FALSE); sys_fw_search_path_prm = fu_path_from_kind(FU_PATH_KIND_FIRMWARE_SEARCH); g_debug("writing firmware search path (%" G_GSIZE_FORMAT "): %s", strlen(path), path); return g_file_set_contents_full(sys_fw_search_path_prm, path, strlen(path), G_FILE_SET_CONTENTS_NONE, 0644, error); #else FILE *fd; gsize res; g_autofree gchar *sys_fw_search_path_prm = NULL; g_return_val_if_fail(path != NULL, FALSE); g_return_val_if_fail(strlen(path) < PATH_MAX, FALSE); sys_fw_search_path_prm = fu_path_from_kind(FU_PATH_KIND_FIRMWARE_SEARCH); /* g_file_set_contents will try to create backup files in sysfs, so use fopen here */ fd = fopen(sys_fw_search_path_prm, "w"); if (fd == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "Failed to open %s: %s", sys_fw_search_path_prm, g_strerror(errno)); return FALSE; } g_debug("writing firmware search path (%" G_GSIZE_FORMAT "): %s", strlen(path), path); res = fwrite(path, sizeof(gchar), strlen(path), fd); fclose(fd); if (res != strlen(path)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "Failed to write firmware search path: %s", g_strerror(errno)); return FALSE; } return TRUE; #endif } /** * fu_kernel_reset_firmware_search_path: * @error: (nullable): optional return location for an error * * Resets the FU_PATH_KIND_FIRMWARE_SEARCH to an empty string * * Returns: %TRUE if successful * * Since: 1.8.2 **/ gboolean fu_kernel_reset_firmware_search_path(GError **error) { const gchar *contents = " "; return fu_kernel_set_firmware_search_path(contents, error); } typedef struct { GHashTable *hash; GHashTable *values; } FuKernelConfigHelper; static gboolean fu_kernel_parse_config_line_cb(GString *token, guint token_idx, gpointer user_data, GError **error) { g_auto(GStrv) kv = NULL; FuKernelConfigHelper *helper = (FuKernelConfigHelper *)user_data; GRefString *value; if (token->len == 0) return TRUE; if (token->str[0] == '#') return TRUE; kv = g_strsplit(token->str, "=", 2); if (g_strv_length(kv) != 2) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid format for '%s'", token->str); return FALSE; } value = g_hash_table_lookup(helper->values, kv[1]); if (value != NULL) { g_hash_table_insert(helper->hash, g_strdup(kv[0]), g_ref_string_acquire(value)); } else { g_hash_table_insert(helper->hash, g_strdup(kv[0]), g_ref_string_new(kv[1])); } return TRUE; } /** * fu_kernel_parse_config: * @buf: (not nullable): cmdline to parse * @bufsz: size of @bufsz * * Parses all the kernel options into a hash table. Commented out options are not included. * * Returns: (transfer container) (element-type utf8 utf8): config keys * * Since: 1.9.6 **/ GHashTable * fu_kernel_parse_config(const gchar *buf, gsize bufsz, GError **error) { g_autoptr(GHashTable) hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ref_string_release); g_autoptr(GHashTable) values = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_ref_string_release); FuKernelConfigHelper helper = {.hash = hash, .values = values}; const gchar *value_keys[] = {"y", "m", "0", NULL}; g_return_val_if_fail(buf != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* add 99.9% of the most common keys to avoid thousands of small allocations */ for (guint i = 0; value_keys[i] != NULL; i++) { g_hash_table_insert(values, (gpointer)value_keys[i], g_ref_string_new(value_keys[i])); } if (!fu_strsplit_full(buf, bufsz, "\n", fu_kernel_parse_config_line_cb, &helper, error)) return NULL; return g_steal_pointer(&hash); } #ifdef __linux__ static gchar * fu_kernel_get_config_path(GError **error) { #ifdef HAVE_UTSNAME_H struct utsname name_tmp; g_autofree gchar *config_fn = NULL; g_autofree gchar *bootdir = fu_path_from_kind(FU_PATH_KIND_HOSTFS_BOOT); memset(&name_tmp, 0, sizeof(struct utsname)); if (uname(&name_tmp) < 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to read kernel version"); return NULL; } config_fn = g_strdup_printf("config-%s", name_tmp.release); return g_build_filename(bootdir, config_fn, NULL); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "platform does not support uname"); return NULL; #endif } #endif /** * fu_kernel_get_config: * @error: (nullable): optional return location for an error * * Loads all the kernel options into a hash table. Commented out options are not included. * * Returns: (transfer container) (element-type utf8 utf8): options from the kernel * * Since: 1.8.5 **/ GHashTable * fu_kernel_get_config(GError **error) { #ifdef __linux__ gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autofree gchar *fn = NULL; g_autofree gchar *procdir = fu_path_from_kind(FU_PATH_KIND_PROCFS); g_autofree gchar *config_fngz = g_build_filename(procdir, "config.gz", NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* try /proc/config.gz -- which will only work with CONFIG_IKCONFIG */ if (g_file_test(config_fngz, G_FILE_TEST_EXISTS)) { g_autoptr(GBytes) payload = NULL; g_autoptr(GConverter) conv = NULL; g_autoptr(GFile) file = g_file_new_for_path(config_fngz); g_autoptr(GInputStream) istream1 = NULL; g_autoptr(GInputStream) istream2 = NULL; istream1 = G_INPUT_STREAM(g_file_read(file, NULL, error)); if (istream1 == NULL) return NULL; conv = G_CONVERTER(g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP)); istream2 = g_converter_input_stream_new(istream1, conv); payload = fu_bytes_get_contents_stream(istream2, G_MAXSIZE, error); if (payload == NULL) return NULL; return fu_kernel_parse_config(g_bytes_get_data(payload, NULL), g_bytes_get_size(payload), error); } /* fall back to /boot/config-$(uname -r) */ fn = fu_kernel_get_config_path(error); if (fn == NULL) return NULL; if (!g_file_get_contents(fn, &buf, &bufsz, error)) return NULL; return fu_kernel_parse_config(buf, bufsz, error); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "platform does not support getting the kernel config"); return NULL; #endif } /** * fu_kernel_parse_cmdline: * @buf: (not nullable): cmdline to parse * @bufsz: size of @bufsz * * Parses all the kernel key/values into a hash table, respecting double quotes when required. * * Returns: (transfer container) (element-type utf8 utf8): keys from the cmdline * * Since: 1.9.1 **/ GHashTable * fu_kernel_parse_cmdline(const gchar *buf, gsize bufsz) { gboolean is_escape = FALSE; g_autoptr(GHashTable) hash = NULL; g_autoptr(GString) acc = g_string_new(NULL); g_return_val_if_fail(buf != NULL, NULL); hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); if (bufsz == 0) return g_steal_pointer(&hash); for (gsize i = 0; i < bufsz; i++) { if (!is_escape && (buf[i] == ' ' || buf[i] == '\n') && acc->len > 0) { g_auto(GStrv) kv = g_strsplit(acc->str, "=", 2); g_hash_table_insert(hash, g_strdup(kv[0]), g_strdup(kv[1])); g_string_set_size(acc, 0); continue; } if (buf[i] == '"') { is_escape = !is_escape; continue; } g_string_append_c(acc, buf[i]); } if (acc->len > 0) { g_auto(GStrv) kv = g_strsplit(acc->str, "=", 2); g_hash_table_insert(hash, g_strdup(kv[0]), g_strdup(kv[1])); } /* success */ return g_steal_pointer(&hash); } /** * fu_kernel_get_cmdline: * @error: (nullable): optional return location for an error * * Loads all the kernel /proc/cmdline key/values into a hash table. * * Returns: (transfer container) (element-type utf8 utf8): keys from the kernel command line * * Since: 1.8.5 **/ GHashTable * fu_kernel_get_cmdline(GError **error) { #ifdef __linux__ gsize bufsz = 0; g_autofree gchar *buf = NULL; g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (!g_file_get_contents("/proc/cmdline", &buf, &bufsz, error)) return NULL; return fu_kernel_parse_cmdline(buf, bufsz); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "platform does not support getting the kernel cmdline"); return NULL; #endif } gboolean fu_kernel_check_cmdline_mutable(GError **error) { g_autofree gchar *bootdir = fu_path_from_kind(FU_PATH_KIND_HOSTFS_BOOT); g_autofree gchar *grubby_path = NULL; g_autofree gchar *sysconfdir = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR); g_auto(GStrv) config_files = g_new0(gchar *, 3); /* not found */ grubby_path = fu_path_find_program("grubby", error); if (grubby_path == NULL) return FALSE; /* check all the config files are writable */ config_files[0] = g_build_filename(bootdir, "grub2", "grub.cfg", NULL); config_files[1] = g_build_filename(sysconfdir, "grub.cfg", NULL); for (guint i = 0; config_files[i] != NULL; i++) { g_autoptr(GFile) file = g_file_new_for_path(config_files[i]); g_autoptr(GFileInfo) info = NULL; g_autoptr(GError) error_local = NULL; if (!g_file_query_exists(file, NULL)) continue; info = g_file_query_info(file, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, G_FILE_QUERY_INFO_NONE, NULL, &error_local); if (info == NULL) { g_warning("failed to get info for %s: %s", config_files[i], error_local->message); continue; } if (!g_file_info_get_attribute_boolean(info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s is not writable", config_files[i]); return FALSE; } } /* success */ return TRUE; } static gboolean fu_kernel_set_commandline(const gchar *arg, gboolean enable, GError **error) { g_autofree gchar *output = NULL; g_autofree gchar *arg_string = NULL; g_autofree gchar *grubby_path = NULL; const gchar *argv_grubby[] = {"", "--update-kernel=DEFAULT", "", NULL}; grubby_path = fu_path_find_program("grubby", error); if (grubby_path == NULL) { g_prefix_error(error, "failed to find grubby: "); return FALSE; } if (enable) arg_string = g_strdup_printf("--args=%s", arg); else arg_string = g_strdup_printf("--remove-args=%s", arg); argv_grubby[0] = grubby_path; argv_grubby[2] = arg_string; return g_spawn_sync(NULL, (gchar **)argv_grubby, NULL, G_SPAWN_DEFAULT, NULL, NULL, &output, NULL, NULL, error); } /** * fu_kernel_add_cmdline_arg: * @arg: (not nullable): key to set * @error: (nullable): optional return location for an error * * Add a kernel command line argument. * * Returns: %TRUE if successful * * Since: 1.9.5 **/ gboolean fu_kernel_add_cmdline_arg(const gchar *arg, GError **error) { return fu_kernel_set_commandline(arg, TRUE, error); } /** * fu_kernel_remove_cmdline_arg: * @arg: (not nullable): key to set * @error: (nullable): optional return location for an error * * Remove a kernel command line argument. * * Returns: %TRUE if successful * * Since: 1.9.5 **/ gboolean fu_kernel_remove_cmdline_arg(const gchar *arg, GError **error) { return fu_kernel_set_commandline(arg, FALSE, error); } fwupd-1.9.16/libfwupdplugin/fu-kernel.h000066400000000000000000000017661460375044200200500ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gboolean fu_kernel_locked_down(void); gboolean fu_kernel_check_version(const gchar *minimum_kernel, GError **error) G_GNUC_NON_NULL(1); gchar * fu_kernel_get_firmware_search_path(GError **error); gboolean fu_kernel_set_firmware_search_path(const gchar *path, GError **error) G_GNUC_NON_NULL(1); gboolean fu_kernel_reset_firmware_search_path(GError **error); GHashTable * fu_kernel_get_config(GError **error); GHashTable * fu_kernel_parse_config(const gchar *buf, gsize bufsz, GError **error); GHashTable * fu_kernel_get_cmdline(GError **error); GHashTable * fu_kernel_parse_cmdline(const gchar *buf, gsize bufsz) G_GNUC_NON_NULL(1); gboolean fu_kernel_check_cmdline_mutable(GError **error); gboolean fu_kernel_add_cmdline_arg(const gchar *arg, GError **error) G_GNUC_NON_NULL(1); gboolean fu_kernel_remove_cmdline_arg(const gchar *arg, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-linear-firmware.c000066400000000000000000000127341460375044200216440ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-linear-firmware.h" /** * FuLinearFirmware: * * A firmware made up of concatenated blobs of a different firmware type. * * NOTE: All the child images will be of the specified `GType`. * * See also: [class@FuFirmware] */ typedef struct { GType image_gtype; } FuLinearFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuLinearFirmware, fu_linear_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_linear_firmware_get_instance_private(o)) enum { PROP_0, PROP_IMAGE_GTYPE, PROP_LAST }; static void fu_linear_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuLinearFirmware *self = FU_LINEAR_FIRMWARE(firmware); FuLinearFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kv(bn, "image_gtype", g_type_name(priv->image_gtype)); } /** * fu_linear_firmware_get_image_gtype: * @self: a #FuLinearFirmware * * Gets the image #GType to use when parsing a byte buffer. * * Returns: integer * * Since: 1.8.2 **/ GType fu_linear_firmware_get_image_gtype(FuLinearFirmware *self) { FuLinearFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_LINEAR_FIRMWARE(self), G_TYPE_INVALID); return priv->image_gtype; } static gboolean fu_linear_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuLinearFirmware *self = FU_LINEAR_FIRMWARE(firmware); FuLinearFirmwarePrivate *priv = GET_PRIVATE(self); const gchar *tmp; /* simple properties */ tmp = xb_node_query_text(n, "image_gtype", NULL); if (tmp != NULL) { priv->image_gtype = g_type_from_name(tmp); if (priv->image_gtype == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GType %s not registered", tmp); return FALSE; } } /* success */ return TRUE; } static gboolean fu_linear_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuLinearFirmware *self = FU_LINEAR_FIRMWARE(firmware); FuLinearFirmwarePrivate *priv = GET_PRIVATE(self); gsize bufsz = g_bytes_get_size(fw); while (offset < bufsz) { g_autoptr(FuFirmware) img = g_object_new(priv->image_gtype, NULL); g_autoptr(GBytes) fw_tmp = NULL; fw_tmp = fu_bytes_new_offset(fw, offset, bufsz - offset, error); if (fw_tmp == NULL) return FALSE; if (!fu_firmware_parse(img, fw_tmp, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) { g_prefix_error(error, "failed to parse at 0x%x: ", (guint)offset); return FALSE; } fu_firmware_set_offset(firmware, offset); if (!fu_firmware_add_image_full(firmware, img, error)) return FALSE; /* next! */ offset += fu_firmware_get_size(img); } /* success */ return TRUE; } static GByteArray * fu_linear_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); /* add each file */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) blob = NULL; fu_firmware_set_offset(img, buf->len); blob = fu_firmware_write(img, error); if (blob == NULL) return NULL; fu_byte_array_append_bytes(buf, blob); } /* success */ return g_steal_pointer(&buf); } static void fu_linear_firmware_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuLinearFirmware *self = FU_LINEAR_FIRMWARE(object); FuLinearFirmwarePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_IMAGE_GTYPE: g_value_set_gtype(value, priv->image_gtype); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_linear_firmware_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuLinearFirmware *self = FU_LINEAR_FIRMWARE(object); FuLinearFirmwarePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_IMAGE_GTYPE: priv->image_gtype = g_value_get_gtype(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_linear_firmware_init(FuLinearFirmware *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); } static void fu_linear_firmware_class_init(FuLinearFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->get_property = fu_linear_firmware_get_property; object_class->set_property = fu_linear_firmware_set_property; klass_firmware->parse = fu_linear_firmware_parse; klass_firmware->write = fu_linear_firmware_write; klass_firmware->export = fu_linear_firmware_export; klass_firmware->build = fu_linear_firmware_build; /** * FuLinearFirmware:image-gtype: * * The image #GType * * Since: 1.8.2 */ pspec = g_param_spec_gtype("image-gtype", NULL, NULL, FU_TYPE_FIRMWARE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_IMAGE_GTYPE, pspec); } /** * fu_linear_firmware_new: * @image_gtype: a #GType, e.g. %FU_TYPE_OPROM_FIRMWARE * * Creates a new #FuFirmware made up of concatenated images. * * Since: 1.8.2 **/ FuFirmware * fu_linear_firmware_new(GType image_gtype) { return g_object_new(FU_TYPE_LINEAR_FIRMWARE, "image-gtype", image_gtype, NULL); } fwupd-1.9.16/libfwupdplugin/fu-linear-firmware.h000066400000000000000000000010051460375044200216360ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_LINEAR_FIRMWARE (fu_linear_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuLinearFirmware, fu_linear_firmware, FU, LINEAR_FIRMWARE, FuFirmware) struct _FuLinearFirmwareClass { FuFirmwareClass parent_class; }; FuFirmware * fu_linear_firmware_new(GType image_gtype); GType fu_linear_firmware_get_image_gtype(FuLinearFirmware *self) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-lzma-common.c000066400000000000000000000060501460375044200210030ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_LZMA #include #endif #include "fu-lzma-common.h" /** * fu_lzma_decompress_bytes: * @blob: data * @error: (nullable): optional return location for an error * * Decompresses a LZMA stream. * * Returns: decompressed data * * Since: 1.9.8 **/ GBytes * fu_lzma_decompress_bytes(GBytes *blob, GError **error) { #ifdef HAVE_LZMA const gsize tmpbufsz = 0x20000; lzma_ret rc; lzma_stream strm = LZMA_STREAM_INIT; uint64_t memlimit = G_MAXUINT32; g_autofree guint8 *tmpbuf = g_malloc0(tmpbufsz); g_autoptr(GByteArray) buf = g_byte_array_new(); strm.next_in = g_bytes_get_data(blob, NULL); strm.avail_in = g_bytes_get_size(blob); rc = lzma_auto_decoder(&strm, memlimit, LZMA_TELL_UNSUPPORTED_CHECK); if (rc != LZMA_OK) { lzma_end(&strm); g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to set up LZMA decoder rc=%u", rc); return NULL; } do { strm.next_out = tmpbuf; strm.avail_out = tmpbufsz; rc = lzma_code(&strm, LZMA_RUN); if (rc != LZMA_OK && rc != LZMA_STREAM_END) break; g_byte_array_append(buf, tmpbuf, tmpbufsz - strm.avail_out); } while (rc == LZMA_OK); lzma_end(&strm); /* success */ if (rc != LZMA_OK && rc != LZMA_STREAM_END) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to decode LZMA data rc=%u", rc); return NULL; } return g_bytes_new(buf->data, buf->len); #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "missing lzma support"); return NULL; #endif } /** * fu_lzma_compress_bytes: * @blob: data * @error: (nullable): optional return location for an error * * Compresses into a LZMA stream. * * Returns: compressed data * * Since: 1.9.8 **/ GBytes * fu_lzma_compress_bytes(GBytes *blob, GError **error) { #ifdef HAVE_LZMA const gsize tmpbufsz = 0x20000; lzma_ret rc; lzma_stream strm = LZMA_STREAM_INIT; g_autofree guint8 *tmpbuf = g_malloc0(tmpbufsz); g_autoptr(GByteArray) buf = g_byte_array_new(); strm.next_in = g_bytes_get_data(blob, NULL); strm.avail_in = g_bytes_get_size(blob); rc = lzma_easy_encoder(&strm, 9, LZMA_CHECK_CRC64); if (rc != LZMA_OK) { lzma_end(&strm); g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to set up LZMA encoder rc=%u", rc); return NULL; } do { strm.next_out = tmpbuf; strm.avail_out = tmpbufsz; rc = lzma_code(&strm, LZMA_FINISH); if (rc != LZMA_OK && rc != LZMA_STREAM_END) break; g_byte_array_append(buf, tmpbuf, tmpbufsz - strm.avail_out); } while (rc == LZMA_OK); lzma_end(&strm); /* success */ if (rc != LZMA_OK && rc != LZMA_STREAM_END) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to encode LZMA data rc=%u", rc); return NULL; } return g_bytes_new(buf->data, buf->len); #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "missing lzma support"); return NULL; #endif } fwupd-1.9.16/libfwupdplugin/fu-lzma-common.h000066400000000000000000000004641460375044200210130ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include GBytes * fu_lzma_decompress_bytes(GBytes *blob, GError **error) G_GNUC_NON_NULL(1); GBytes * fu_lzma_compress_bytes(GBytes *blob, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-mei-device.c000066400000000000000000000325201460375044200205620ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMeiDevice" #include "config.h" #include #ifdef HAVE_MEI_H #include #endif #ifdef HAVE_IOCTL_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_SELECT_H #include #endif #include #include #include #include "fu-bytes.h" #include "fu-dump.h" #include "fu-mei-device.h" #include "fu-string.h" #define FU_MEI_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ /** * FuMeiDevice * * The Intel proprietary Management Engine Interface. * * See also: #FuUdevDevice */ typedef struct { guint32 max_msg_length; guint8 protocol_version; gchar *uuid; gchar *parent_device_file; } FuMeiDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuMeiDevice, fu_mei_device, FU_TYPE_UDEV_DEVICE) #define GET_PRIVATE(o) (fu_mei_device_get_instance_private(o)) static void fu_mei_device_to_string(FuDevice *device, guint idt, GString *str) { FuMeiDevice *self = FU_MEI_DEVICE(device); FuMeiDevicePrivate *priv = GET_PRIVATE(self); FU_DEVICE_CLASS(fu_mei_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "Uuid", priv->uuid); fu_string_append(str, idt, "ParentDeviceFile", priv->parent_device_file); if (priv->max_msg_length > 0x0) fu_string_append_kx(str, idt, "MaxMsgLength", priv->max_msg_length); if (priv->protocol_version > 0x0) fu_string_append_kx(str, idt, "ProtocolVer", priv->protocol_version); } static gboolean fu_mei_device_ensure_parent_device_file(FuMeiDevice *self, GError **error) { FuMeiDevicePrivate *priv = GET_PRIVATE(self); const gchar *fn; g_autofree gchar *parent_tmp = NULL; g_autofree gchar *parent_mei_path = NULL; g_autoptr(FuUdevDevice) parent = NULL; g_autoptr(GDir) dir = NULL; /* get direct parent */ parent = fu_udev_device_get_parent_with_subsystem(FU_UDEV_DEVICE(self), NULL); if (parent == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no MEI parent"); return FALSE; } /* look for the only child with this subsystem */ parent_mei_path = g_build_filename(fu_udev_device_get_sysfs_path(parent), "mei", NULL); dir = g_dir_open(parent_mei_path, 0, NULL); if (dir == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no MEI parent dir for %s", fu_udev_device_get_sysfs_path(parent)); return FALSE; } fn = g_dir_read_name(dir); if (fn == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no MEI parent in %s", parent_mei_path); return FALSE; } /* success */ parent_tmp = g_build_filename(fu_udev_device_get_sysfs_path(parent), "mei", fn, NULL); if (g_strcmp0(parent_tmp, priv->parent_device_file) != 0) { g_free(priv->parent_device_file); priv->parent_device_file = g_steal_pointer(&parent_tmp); } return TRUE; } static void fu_mei_device_set_uuid(FuMeiDevice *self, const gchar *uuid) { FuMeiDevicePrivate *priv = GET_PRIVATE(self); if (g_strcmp0(priv->uuid, uuid) == 0) return; g_free(priv->uuid); priv->uuid = g_strdup(uuid); } static gboolean fu_mei_device_probe(FuDevice *device, GError **error) { FuMeiDevice *self = FU_MEI_DEVICE(device); FuMeiDevicePrivate *priv = GET_PRIVATE(self); const gchar *uuid; /* this has to exist */ uuid = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "uuid", NULL); if (uuid == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "UUID not provided"); return FALSE; } fu_mei_device_set_uuid(self, uuid); fu_device_add_guid(device, uuid); /* get the mei[0-9] device file the parent is using */ if (!fu_mei_device_ensure_parent_device_file(self, error)) return FALSE; /* the kernel is missing `dev` on mei_me children */ if (fu_udev_device_get_device_file(FU_UDEV_DEVICE(device)) == NULL) { g_autofree gchar *basename = g_path_get_basename(priv->parent_device_file); g_autofree gchar *device_file = g_build_filename("/dev", basename, NULL); fu_udev_device_set_device_file(FU_UDEV_DEVICE(device), device_file); } /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_mei_device_parent_class)->probe(device, error)) return FALSE; /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "pci", error); } static gchar * fu_mei_device_get_parent_attr(FuMeiDevice *self, const gchar *basename, guint idx, GError **error) { FuMeiDevicePrivate *priv = GET_PRIVATE(self); g_autofree gchar *fn = NULL; g_auto(GStrv) lines = NULL; g_autoptr(GBytes) blob = NULL; /* sanity check */ if (priv->parent_device_file == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no parent device file"); return NULL; } /* load lines */ fn = g_build_filename(priv->parent_device_file, basename, NULL); blob = fu_bytes_get_contents(fn, error); if (blob == NULL) return NULL; lines = fu_strsplit((const gchar *)g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), "\n", -1); if (g_strv_length(lines) <= idx) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "requested line %u of %u", idx, g_strv_length(lines)); return NULL; } /* success */ return g_strdup(lines[idx]); } /** * fu_mei_device_get_fw_ver: * @self: a #FuMeiDevice * @idx: line index * @error: (nullable): optional return location for an error * * Gets the firmware version for a specific index. * * Returns: string value * * Since: 1.8.7 **/ gchar * fu_mei_device_get_fw_ver(FuMeiDevice *self, guint idx, GError **error) { g_return_val_if_fail(FU_IS_MEI_DEVICE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return fu_mei_device_get_parent_attr(self, "fw_ver", idx, error); } /** * fu_mei_device_get_fw_status: * @self: a #FuMeiDevice * @idx: line index * @error: (nullable): optional return location for an error * * Gets the firmware status for a specific index. * * Returns: string value * * Since: 1.8.7 **/ gchar * fu_mei_device_get_fw_status(FuMeiDevice *self, guint idx, GError **error) { g_return_val_if_fail(FU_IS_MEI_DEVICE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); return fu_mei_device_get_parent_attr(self, "fw_status", idx, error); } /** * fu_mei_device_get_max_msg_length: * @self: a #FuMeiDevice * * Gets the maximum message length. * * Returns: integer * * Since: 1.8.2 **/ guint32 fu_mei_device_get_max_msg_length(FuMeiDevice *self) { FuMeiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_MEI_DEVICE(self), G_MAXUINT32); return priv->max_msg_length; } /** * fu_mei_device_get_protocol_version: * @self: a #FuMeiDevice * * Gets the protocol version, or 0x for unset. * * Returns: integer * * Since: 1.8.2 **/ guint8 fu_mei_device_get_protocol_version(FuMeiDevice *self) { FuMeiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_MEI_DEVICE(self), G_MAXUINT8); return priv->protocol_version; } /** * fu_mei_device_connect: * @self: a #FuMeiDevice * @req_protocol_version: required protocol version, or 0 * @error: (nullable): optional return location for an error * * Connects to the MEI device. * * Returns: %TRUE for success * * Since: 1.8.2 **/ gboolean fu_mei_device_connect(FuMeiDevice *self, guchar req_protocol_version, GError **error) { #ifdef HAVE_MEI_H FuMeiDevicePrivate *priv = GET_PRIVATE(self); fwupd_guid_t guid_le = {0x0}; struct mei_client *cl; struct mei_connect_client_data data = {0x0}; g_return_val_if_fail(FU_IS_MEI_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fwupd_guid_from_string(priv->uuid, &guid_le, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "guid_le", (guint8 *)&guid_le, sizeof(guid_le)); memcpy(&data.in_client_uuid, &guid_le, sizeof(guid_le)); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), IOCTL_MEI_CONNECT_CLIENT, (guint8 *)&data, NULL, /* rc */ FU_MEI_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; cl = &data.out_client_properties; if (req_protocol_version > 0 && cl->protocol_version != req_protocol_version) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Intel MEI protocol version not supported %i", cl->protocol_version); return FALSE; } /* success */ priv->max_msg_length = cl->max_msg_length; priv->protocol_version = cl->protocol_version; return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "linux/mei.h not supported"); return FALSE; #endif } /** * fu_mei_device_read: * @self: a #FuMeiDevice * @buf: (out): data * @bufsz: size of @data * @bytes_read: (nullable): bytes read * @timeout_ms: timeout * @error: (nullable): optional return location for an error * * Read raw bytes from the device. * * Returns: %TRUE for success * * Since: 1.8.2 **/ gboolean fu_mei_device_read(FuMeiDevice *self, guint8 *buf, gsize bufsz, gsize *bytes_read, guint timeout_ms, GError **error) { gssize rc; FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); g_return_val_if_fail(FU_IS_MEI_DEVICE(self), FALSE); g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); rc = read(fu_io_channel_unix_get_fd(io_channel), buf, bufsz); if (rc < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "read failed %u: %s", (guint)rc, g_strerror(errno)); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "read", buf, rc); if (bytes_read != NULL) *bytes_read = (gsize)rc; return TRUE; } /** * fu_mei_device_write: * @self: a #FuMeiDevice * @buf: (out): data * @bufsz: size of @data * @timeout_ms: timeout * @error: (nullable): optional return location for an error * * Write raw bytes to the device. * * Returns: %TRUE for success * * Since: 1.8.2 **/ gboolean fu_mei_device_write(FuMeiDevice *self, const guint8 *buf, gsize bufsz, guint timeout_ms, GError **error) { #ifdef HAVE_SELECT_H struct timeval tv; gssize written; gssize rc; fd_set set; FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); guint fd = fu_io_channel_unix_get_fd(io_channel); g_return_val_if_fail(FU_IS_MEI_DEVICE(self), FALSE); g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); tv.tv_sec = timeout_ms / 1000; tv.tv_usec = (timeout_ms % 1000) * 1000; fu_dump_raw(G_LOG_DOMAIN, "write", buf, bufsz); written = write(fd, buf, bufsz); if (written < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "write failed with status %" G_GSSIZE_FORMAT " %s", written, g_strerror(errno)); return FALSE; } if ((gsize)written != bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "only wrote %" G_GSSIZE_FORMAT " of %" G_GSIZE_FORMAT, written, bufsz); return FALSE; } FD_ZERO(&set); FD_SET(fd, &set); rc = select(fd + 1, &set, NULL, NULL, &tv); if (rc > 0 && FD_ISSET(fd, &set)) return TRUE; /* timed out */ if (rc == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "write failed on timeout with status"); return FALSE; } /* rc < 0 */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "write failed on select with status %" G_GSSIZE_FORMAT, rc); return FALSE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "linux/select.h not supported"); return FALSE; #endif } static void fu_mei_device_incorporate(FuDevice *device, FuDevice *donor) { FuMeiDevice *self = FU_MEI_DEVICE(device); FuMeiDevicePrivate *priv = GET_PRIVATE(self); FuMeiDevicePrivate *priv_donor = GET_PRIVATE(FU_MEI_DEVICE(donor)); g_return_if_fail(FU_IS_MEI_DEVICE(self)); g_return_if_fail(FU_IS_MEI_DEVICE(donor)); /* FuUdevDevice->incorporate */ FU_DEVICE_CLASS(fu_mei_device_parent_class)->incorporate(device, donor); /* copy private instance data */ priv->max_msg_length = priv_donor->max_msg_length; priv->protocol_version = priv_donor->protocol_version; if (priv->uuid == NULL) fu_mei_device_set_uuid(self, priv_donor->uuid); if (priv->parent_device_file == NULL) priv->parent_device_file = g_strdup(priv_donor->parent_device_file); } static void fu_mei_device_init(FuMeiDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_PROBE_COMPLETE); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT); } static void fu_mei_device_finalize(GObject *object) { FuMeiDevice *self = FU_MEI_DEVICE(object); FuMeiDevicePrivate *priv = GET_PRIVATE(self); g_free(priv->uuid); g_free(priv->parent_device_file); G_OBJECT_CLASS(fu_mei_device_parent_class)->finalize(object); } static void fu_mei_device_class_init(FuMeiDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_mei_device_finalize; klass_device->probe = fu_mei_device_probe; klass_device->to_string = fu_mei_device_to_string; klass_device->incorporate = fu_mei_device_incorporate; } fwupd-1.9.16/libfwupdplugin/fu-mei-device.h000066400000000000000000000022111460375044200205610ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-udev-device.h" #define FU_TYPE_MEI_DEVICE (fu_mei_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuMeiDevice, fu_mei_device, FU, MEI_DEVICE, FuUdevDevice) struct _FuMeiDeviceClass { FuUdevDeviceClass parent_class; }; gboolean fu_mei_device_connect(FuMeiDevice *self, guchar req_protocol_version, GError **error) G_GNUC_NON_NULL(1); gboolean fu_mei_device_read(FuMeiDevice *self, guint8 *buf, gsize bufsz, gsize *bytes_read, guint timeout_ms, GError **error) G_GNUC_NON_NULL(1); gboolean fu_mei_device_write(FuMeiDevice *self, const guint8 *buf, gsize bufsz, guint timeout_ms, GError **error) G_GNUC_NON_NULL(1); guint fu_mei_device_get_max_msg_length(FuMeiDevice *self) G_GNUC_NON_NULL(1); guint8 fu_mei_device_get_protocol_version(FuMeiDevice *self) G_GNUC_NON_NULL(1); gchar * fu_mei_device_get_fw_ver(FuMeiDevice *self, guint idx, GError **error) G_GNUC_NON_NULL(1); gchar * fu_mei_device_get_fw_status(FuMeiDevice *self, guint idx, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-mem-private.h000066400000000000000000000004751460375044200210120ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-mem.h" gboolean fu_memchk_read(gsize bufsz, gsize offset, gsize n, GError **error); gboolean fu_memchk_write(gsize bufsz, gsize offset, gsize n, GError **error); fwupd-1.9.16/libfwupdplugin/fu-mem.c000066400000000000000000000611411460375044200173320ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include "fwupd-error.h" #include "fu-mem-private.h" #include "fu-string.h" /** * fu_memwrite_uint16: * @buf: a writable buffer * @val_native: a value in host byte-order * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * * Writes a value to a buffer using a specified endian. * * Since: 1.8.2 **/ void fu_memwrite_uint16(guint8 *buf, guint16 val_native, FuEndianType endian) { guint16 val_hw; switch (endian) { case G_BIG_ENDIAN: val_hw = GUINT16_TO_BE(val_native); break; case G_LITTLE_ENDIAN: val_hw = GUINT16_TO_LE(val_native); break; default: val_hw = val_native; break; } memcpy(buf, &val_hw, sizeof(val_hw)); } /** * fu_memwrite_uint24: * @buf: a writable buffer * @val_native: a value in host byte-order * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * * Writes a value to a buffer using a specified endian. * * Since: 1.8.2 **/ void fu_memwrite_uint24(guint8 *buf, guint32 val_native, FuEndianType endian) { guint32 val_hw; switch (endian) { case G_BIG_ENDIAN: val_hw = GUINT32_TO_BE(val_native); memcpy(buf, ((const guint8 *)&val_hw) + 0x1, 0x3); break; case G_LITTLE_ENDIAN: val_hw = GUINT32_TO_LE(val_native); memcpy(buf, &val_hw, 0x3); break; default: g_assert_not_reached(); } } /** * fu_memwrite_uint32: * @buf: a writable buffer * @val_native: a value in host byte-order * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * * Writes a value to a buffer using a specified endian. * * Since: 1.8.2 **/ void fu_memwrite_uint32(guint8 *buf, guint32 val_native, FuEndianType endian) { guint32 val_hw; switch (endian) { case G_BIG_ENDIAN: val_hw = GUINT32_TO_BE(val_native); break; case G_LITTLE_ENDIAN: val_hw = GUINT32_TO_LE(val_native); break; default: val_hw = val_native; break; } memcpy(buf, &val_hw, sizeof(val_hw)); } /** * fu_memwrite_uint64: * @buf: a writable buffer * @val_native: a value in host byte-order * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * * Writes a value to a buffer using a specified endian. * * Since: 1.8.2 **/ void fu_memwrite_uint64(guint8 *buf, guint64 val_native, FuEndianType endian) { guint64 val_hw; switch (endian) { case G_BIG_ENDIAN: val_hw = GUINT64_TO_BE(val_native); break; case G_LITTLE_ENDIAN: val_hw = GUINT64_TO_LE(val_native); break; default: val_hw = val_native; break; } memcpy(buf, &val_hw, sizeof(val_hw)); } /** * fu_memread_uint16: * @buf: a readable buffer * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * * Read a value from a buffer using a specified endian. * * Returns: a value in host byte-order * * Since: 1.8.2 **/ guint16 fu_memread_uint16(const guint8 *buf, FuEndianType endian) { guint16 val_hw, val_native; memcpy(&val_hw, buf, sizeof(val_hw)); switch (endian) { case G_BIG_ENDIAN: val_native = GUINT16_FROM_BE(val_hw); break; case G_LITTLE_ENDIAN: val_native = GUINT16_FROM_LE(val_hw); break; default: val_native = val_hw; break; } return val_native; } /** * fu_memread_uint24: * @buf: a readable buffer * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * * Read a value from a buffer using a specified endian. * * Returns: a value in host byte-order * * Since: 1.8.2 **/ guint32 fu_memread_uint24(const guint8 *buf, FuEndianType endian) { guint32 val_hw = 0; guint32 val_native; switch (endian) { case G_BIG_ENDIAN: memcpy(((guint8 *)&val_hw) + 0x1, buf, 0x3); val_native = GUINT32_FROM_BE(val_hw); break; case G_LITTLE_ENDIAN: memcpy(&val_hw, buf, 0x3); val_native = GUINT32_FROM_LE(val_hw); break; default: val_native = val_hw; break; } return val_native; } /** * fu_memread_uint32: * @buf: a readable buffer * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * * Read a value from a buffer using a specified endian. * * Returns: a value in host byte-order * * Since: 1.8.2 **/ guint32 fu_memread_uint32(const guint8 *buf, FuEndianType endian) { guint32 val_hw, val_native; memcpy(&val_hw, buf, sizeof(val_hw)); switch (endian) { case G_BIG_ENDIAN: val_native = GUINT32_FROM_BE(val_hw); break; case G_LITTLE_ENDIAN: val_native = GUINT32_FROM_LE(val_hw); break; default: val_native = val_hw; break; } return val_native; } /** * fu_memread_uint64: * @buf: a readable buffer * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * * Read a value from a buffer using a specified endian. * * Returns: a value in host byte-order * * Since: 1.8.2 **/ guint64 fu_memread_uint64(const guint8 *buf, FuEndianType endian) { guint64 val_hw, val_native; memcpy(&val_hw, buf, sizeof(val_hw)); switch (endian) { case G_BIG_ENDIAN: val_native = GUINT64_FROM_BE(val_hw); break; case G_LITTLE_ENDIAN: val_native = GUINT64_FROM_LE(val_hw); break; default: val_native = val_hw; break; } return val_native; } /** * fu_memcmp_safe: * @buf1: a buffer * @buf1_sz: sizeof @buf1 * @buf1_offset: offset into @buf1 * @buf2: another buffer * @buf2_sz: sizeof @buf2 * @buf2_offset: offset into @buf1 * @n: number of bytes to compare from @buf1+@buf1_offset from * @error: (nullable): optional return location for an error * * Compares the buffers for equality. * * Returns: %TRUE if @buf1 and @buf2 are identical * * Since: 1.8.2 **/ gboolean fu_memcmp_safe(const guint8 *buf1, gsize buf1_sz, gsize buf1_offset, const guint8 *buf2, gsize buf2_sz, gsize buf2_offset, gsize n, GError **error) { g_return_val_if_fail(buf1 != NULL, FALSE); g_return_val_if_fail(buf2 != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fu_memchk_read(buf1_sz, buf1_offset, n, error)) return FALSE; if (!fu_memchk_read(buf2_sz, buf2_offset, n, error)) return FALSE; /* check matches */ for (guint i = 0x0; i < n; i++) { if (buf1[buf1_offset + i] != buf2[buf2_offset + i]) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got 0x%02x, expected 0x%02x @ 0x%04x", buf1[buf1_offset + i], buf2[buf2_offset + i], i); return FALSE; } } /* success */ return TRUE; } /** * fu_memchk_read: * @bufsz: maximum size of a buffer, typically `sizeof(buf)` * @offset: offset in bytes * @n: number of bytes * @error: (nullable): optional return location for an error * * Works out if reading from a buffer is safe. Providing the buffer sizes allows us to check for * buffer overflow. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Returns: %TRUE if the access is safe, %FALSE otherwise * * Since: 1.9.1 **/ gboolean fu_memchk_read(gsize bufsz, gsize offset, gsize n, GError **error) { g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (n == 0) return TRUE; if (n > bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "attempted to read 0x%02x bytes from buffer of 0x%02x", (guint)n, (guint)bufsz); return FALSE; } if (offset > bufsz || n + offset > bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "attempted to read 0x%02x bytes at offset 0x%02x from buffer of 0x%02x", (guint)n, (guint)offset, (guint)bufsz); return FALSE; } return TRUE; } /** * fu_memchk_write: * @bufsz: maximum size of a buffer, typically `sizeof(buf)` * @offset: offset in bytes * @n: number of bytes * @error: (nullable): optional return location for an error * * Works out if writing to a buffer is safe. Providing the buffer sizes allows us to check for * buffer overflow. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Returns: %TRUE if the access is safe, %FALSE otherwise * * Since: 1.9.1 **/ gboolean fu_memchk_write(gsize bufsz, gsize offset, gsize n, GError **error) { g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (n == 0) return TRUE; if (n > bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "attempted to write 0x%02x bytes to buffer of 0x%02x", (guint)n, (guint)bufsz); return FALSE; } if (offset > bufsz || n + offset > bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "attempted to write 0x%02x bytes at offset 0x%02x to buffer of 0x%02x", (guint)n, (guint)offset, (guint)bufsz); return FALSE; } return TRUE; } /** * fu_memcpy_safe: * @dst: destination buffer * @dst_sz: maximum size of @dst, typically `sizeof(dst)` * @dst_offset: offset in bytes into @dst to copy to * @src: source buffer * @src_sz: maximum size of @dst, typically `sizeof(src)` * @src_offset: offset in bytes into @src to copy from * @n: number of bytes to copy from @src+@offset from * @error: (nullable): optional return location for an error * * Copies some memory using memcpy in a safe way. Providing the buffer sizes * of both the destination and the source allows us to check for buffer overflow. * * Providing the buffer offsets also allows us to check reading past the end of * the source buffer. For this reason the caller should NEVER add an offset to * @src or @dst. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Returns: %TRUE if the bytes were copied, %FALSE otherwise * * Since: 1.8.2 **/ gboolean fu_memcpy_safe(guint8 *dst, gsize dst_sz, gsize dst_offset, const guint8 *src, gsize src_sz, gsize src_offset, gsize n, GError **error) { g_return_val_if_fail(dst != NULL, FALSE); g_return_val_if_fail(src != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fu_memchk_read(src_sz, src_offset, n, error)) return FALSE; if (!fu_memchk_write(dst_sz, dst_offset, n, error)) return FALSE; memcpy(dst + dst_offset, src + src_offset, n); return TRUE; } /** * fu_memmem_safe: * @haystack: destination buffer * @haystack_sz: maximum size of @haystack, typically `sizeof(haystack)` * @needle: source buffer * @needle_sz: maximum size of @haystack, typically `sizeof(needle)` * @offset: (out) (nullable): offset in bytes @needle has been found in @haystack * @error: (nullable): optional return location for an error * * Finds a block of memory in another block of memory in a safe way. * * Returns: %TRUE if the needle was found in the haystack, %FALSE otherwise * * Since: 1.8.2 **/ gboolean fu_memmem_safe(const guint8 *haystack, gsize haystack_sz, const guint8 *needle, gsize needle_sz, gsize *offset, GError **error) { #ifdef HAVE_MEMMEM const guint8 *tmp; #endif g_return_val_if_fail(haystack != NULL, FALSE); g_return_val_if_fail(needle != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* nothing to find */ if (needle_sz == 0) { if (offset != NULL) *offset = 0; return TRUE; } /* impossible */ if (needle_sz > haystack_sz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "needle of 0x%02x bytes is larger than haystack of 0x%02x bytes", (guint)needle_sz, (guint)haystack_sz); return FALSE; } #ifdef HAVE_MEMMEM /* trust glibc to do a binary or linear search as appropriate */ tmp = memmem(haystack, haystack_sz, needle, needle_sz); if (tmp != NULL) { if (offset != NULL) *offset = tmp - haystack; return TRUE; } #else for (gsize i = 0; i < haystack_sz - needle_sz; i++) { if (memcmp(haystack + i, needle, needle_sz) == 0) { if (offset != NULL) *offset = i; return TRUE; } } #endif /* not found */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "needle of 0x%02x bytes was not found in haystack of 0x%02x bytes", (guint)needle_sz, (guint)haystack_sz); return FALSE; } /** * fu_memdup_safe: * @src: (nullable): source buffer * @n: number of bytes to copy from @src * @error: (nullable): optional return location for an error * * Duplicates some memory using memdup in a safe way. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * NOTE: This function intentionally limits allocation size to 1GB. * * Returns: (transfer full): block of allocated memory, or %NULL for an error. * * Since: 1.8.2 **/ guint8 * fu_memdup_safe(const guint8 *src, gsize n, GError **error) { /* sanity check */ if (n > 0x40000000) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot allocate %uGB of memory", (guint)(n / 0x40000000)); return NULL; } #if GLIB_CHECK_VERSION(2, 67, 3) /* linear block of memory */ return g_memdup2(src, n); #else return g_memdup(src, (guint)n); #endif } /** * fu_memread_uint8_safe: * @buf: source buffer * @bufsz: maximum size of @buf, typically `sizeof(buf)` * @offset: offset in bytes into @buf to copy from * @value: (out) (nullable): the parsed value * @error: (nullable): optional return location for an error * * Read a value from a buffer in a safe way. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Returns: %TRUE if @value was set, %FALSE otherwise * * Since: 1.8.2 **/ gboolean fu_memread_uint8_safe(const guint8 *buf, gsize bufsz, gsize offset, guint8 *value, GError **error) { guint8 tmp; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fu_memcpy_safe(&tmp, sizeof(tmp), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(tmp), error)) return FALSE; if (value != NULL) *value = tmp; return TRUE; } /** * fu_memread_uint16_safe: * @buf: source buffer * @bufsz: maximum size of @buf, typically `sizeof(buf)` * @offset: offset in bytes into @buf to copy from * @value: (out) (nullable): the parsed value * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @error: (nullable): optional return location for an error * * Read a value from a buffer using a specified endian in a safe way. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Returns: %TRUE if @value was set, %FALSE otherwise * * Since: 1.8.2 **/ gboolean fu_memread_uint16_safe(const guint8 *buf, gsize bufsz, gsize offset, guint16 *value, FuEndianType endian, GError **error) { guint8 dst[2] = {0x0}; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fu_memcpy_safe(dst, sizeof(dst), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(dst), error)) return FALSE; if (value != NULL) *value = fu_memread_uint16(dst, endian); return TRUE; } /** * fu_memread_uint24_safe: * @buf: source buffer * @bufsz: maximum size of @buf, typically `sizeof(buf)` * @offset: offset in bytes into @buf to copy from * @value: (out) (nullable): the parsed value * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @error: (nullable): optional return location for an error * * Read a value from a buffer using a specified endian in a safe way. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Returns: %TRUE if @value was set, %FALSE otherwise * * Since: 1.8.3 **/ gboolean fu_memread_uint24_safe(const guint8 *buf, gsize bufsz, gsize offset, guint32 *value, FuEndianType endian, GError **error) { guint8 dst[3] = {0x0}; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fu_memcpy_safe(dst, sizeof(dst), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(dst), error)) return FALSE; if (value != NULL) *value = fu_memread_uint24(dst, endian); return TRUE; } /** * fu_memread_uint32_safe: * @buf: source buffer * @bufsz: maximum size of @buf, typically `sizeof(buf)` * @offset: offset in bytes into @buf to copy from * @value: (out) (nullable): the parsed value * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @error: (nullable): optional return location for an error * * Read a value from a buffer using a specified endian in a safe way. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Returns: %TRUE if @value was set, %FALSE otherwise * * Since: 1.8.2 **/ gboolean fu_memread_uint32_safe(const guint8 *buf, gsize bufsz, gsize offset, guint32 *value, FuEndianType endian, GError **error) { guint8 dst[4] = {0x0}; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fu_memcpy_safe(dst, sizeof(dst), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(dst), error)) return FALSE; if (value != NULL) *value = fu_memread_uint32(dst, endian); return TRUE; } /** * fu_memread_uint64_safe: * @buf: source buffer * @bufsz: maximum size of @buf, typically `sizeof(buf)` * @offset: offset in bytes into @buf to copy from * @value: (out) (nullable): the parsed value * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @error: (nullable): optional return location for an error * * Read a value from a buffer using a specified endian in a safe way. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Returns: %TRUE if @value was set, %FALSE otherwise * * Since: 1.8.2 **/ gboolean fu_memread_uint64_safe(const guint8 *buf, gsize bufsz, gsize offset, guint64 *value, FuEndianType endian, GError **error) { guint8 dst[8] = {0x0}; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fu_memcpy_safe(dst, sizeof(dst), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(dst), error)) return FALSE; if (value != NULL) *value = fu_memread_uint64(dst, endian); return TRUE; } /** * fu_memwrite_uint8_safe: * @buf: source buffer * @bufsz: maximum size of @buf, typically `sizeof(buf)` * @offset: offset in bytes into @buf to write to * @value: the value to write * @error: (nullable): optional return location for an error * * Write a value to a buffer in a safe way. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Returns: %TRUE if @value was written, %FALSE otherwise * * Since: 1.8.2 **/ gboolean fu_memwrite_uint8_safe(guint8 *buf, gsize bufsz, gsize offset, guint8 value, GError **error) { g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return fu_memcpy_safe(buf, bufsz, offset, /* dst */ &value, sizeof(value), 0x0, /* src */ sizeof(value), error); } /** * fu_memwrite_uint16_safe: * @buf: source buffer * @bufsz: maximum size of @buf, typically `sizeof(buf)` * @offset: offset in bytes into @buf to write to * @value: the value to write * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @error: (nullable): optional return location for an error * * Write a value to a buffer using a specified endian in a safe way. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Returns: %TRUE if @value was written, %FALSE otherwise * * Since: 1.8.2 **/ gboolean fu_memwrite_uint16_safe(guint8 *buf, gsize bufsz, gsize offset, guint16 value, FuEndianType endian, GError **error) { guint8 tmp[2] = {0x0}; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); fu_memwrite_uint16(tmp, value, endian); return fu_memcpy_safe(buf, bufsz, offset, /* dst */ tmp, sizeof(tmp), 0x0, /* src */ sizeof(tmp), error); } /** * fu_memwrite_uint32_safe: * @buf: source buffer * @bufsz: maximum size of @buf, typically `sizeof(buf)` * @offset: offset in bytes into @buf to write to * @value: the value to write * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @error: (nullable): optional return location for an error * * Write a value to a buffer using a specified endian in a safe way. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Returns: %TRUE if @value was written, %FALSE otherwise * * Since: 1.8.2 **/ gboolean fu_memwrite_uint32_safe(guint8 *buf, gsize bufsz, gsize offset, guint32 value, FuEndianType endian, GError **error) { guint8 tmp[4] = {0x0}; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); fu_memwrite_uint32(tmp, value, endian); return fu_memcpy_safe(buf, bufsz, offset, /* dst */ tmp, sizeof(tmp), 0x0, /* src */ sizeof(tmp), error); } /** * fu_memwrite_uint64_safe: * @buf: source buffer * @bufsz: maximum size of @buf, typically `sizeof(buf)` * @offset: offset in bytes into @buf to write to * @value: the value to write * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @error: (nullable): optional return location for an error * * Write a value to a buffer using a specified endian in a safe way. * * You don't need to use this function in "obviously correct" cases, nor should * you use it when performance is a concern. Only us it when you're not sure if * malicious data from a device or firmware could cause memory corruption. * * Returns: %TRUE if @value was written, %FALSE otherwise * * Since: 1.8.2 **/ gboolean fu_memwrite_uint64_safe(guint8 *buf, gsize bufsz, gsize offset, guint64 value, FuEndianType endian, GError **error) { guint8 tmp[8] = {0x0}; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); fu_memwrite_uint64(tmp, value, endian); return fu_memcpy_safe(buf, bufsz, offset, /* dst */ tmp, sizeof(tmp), 0x0, /* src */ sizeof(tmp), error); } /** * fu_memstrsafe: * @buf: source buffer * @bufsz: maximum size of @buf, typically `sizeof(buf)` * @offset: offset in bytes into @buf to read from * @maxsz: maximum size of returned string * @error: (nullable): optional return location for an error * * Converts a byte buffer to a ASCII string. * * Returns: (transfer full): a string, or %NULL on error * * Since: 1.9.3 **/ gchar * fu_memstrsafe(const guint8 *buf, gsize bufsz, gsize offset, gsize maxsz, GError **error) { g_autofree gchar *str = NULL; g_return_val_if_fail(buf != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (!fu_memchk_read(bufsz, offset, maxsz, error)) return NULL; str = fu_strsafe((const gchar *)buf + offset, maxsz); if (str == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid ASCII string"); return NULL; } return g_steal_pointer(&str); } fwupd-1.9.16/libfwupdplugin/fu-mem.h000066400000000000000000000071041460375044200173360ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-common.h" gboolean fu_memcmp_safe(const guint8 *buf1, gsize buf1_sz, gsize buf1_offset, const guint8 *buf2, gsize buf2_sz, gsize buf2_offset, gsize n, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 4); guint8 * fu_memdup_safe(const guint8 *src, gsize n, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(2); gboolean fu_memcpy_safe(guint8 *dst, gsize dst_sz, gsize dst_offset, const guint8 *src, gsize src_sz, gsize src_offset, gsize n, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 4); gboolean fu_memmem_safe(const guint8 *haystack, gsize haystack_sz, const guint8 *needle, gsize needle_sz, gsize *offset, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 3); gboolean fu_memread_uint8_safe(const guint8 *buf, gsize bufsz, gsize offset, guint8 *value, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_memread_uint16_safe(const guint8 *buf, gsize bufsz, gsize offset, guint16 *value, FuEndianType endian, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_memread_uint24_safe(const guint8 *buf, gsize bufsz, gsize offset, guint32 *value, FuEndianType endian, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_memread_uint32_safe(const guint8 *buf, gsize bufsz, gsize offset, guint32 *value, FuEndianType endian, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_memread_uint64_safe(const guint8 *buf, gsize bufsz, gsize offset, guint64 *value, FuEndianType endian, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_memwrite_uint8_safe(guint8 *buf, gsize bufsz, gsize offset, guint8 value, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_memwrite_uint16_safe(guint8 *buf, gsize bufsz, gsize offset, guint16 value, FuEndianType endian, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_memwrite_uint32_safe(guint8 *buf, gsize bufsz, gsize offset, guint32 value, FuEndianType endian, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_memwrite_uint64_safe(guint8 *buf, gsize bufsz, gsize offset, guint64 value, FuEndianType endian, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); void fu_memwrite_uint16(guint8 *buf, guint16 val_native, FuEndianType endian) G_GNUC_NON_NULL(1); void fu_memwrite_uint24(guint8 *buf, guint32 val_native, FuEndianType endian) G_GNUC_NON_NULL(1); void fu_memwrite_uint32(guint8 *buf, guint32 val_native, FuEndianType endian) G_GNUC_NON_NULL(1); void fu_memwrite_uint64(guint8 *buf, guint64 val_native, FuEndianType endian) G_GNUC_NON_NULL(1); guint16 fu_memread_uint16(const guint8 *buf, FuEndianType endian) G_GNUC_NON_NULL(1); guint32 fu_memread_uint24(const guint8 *buf, FuEndianType endian) G_GNUC_NON_NULL(1); guint32 fu_memread_uint32(const guint8 *buf, FuEndianType endian) G_GNUC_NON_NULL(1); guint64 fu_memread_uint64(const guint8 *buf, FuEndianType endian) G_GNUC_NON_NULL(1); gchar * fu_memstrsafe(const guint8 *buf, gsize bufsz, gsize offset, gsize maxsz, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-oprom-firmware.c000066400000000000000000000214451460375044200215250ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2022 Intel * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-ifwi-cpd-firmware.h" #include "fu-oprom-firmware.h" #include "fu-oprom-struct.h" #include "fu-string.h" /** * FuOpromFirmware: * * An OptionROM can be found in nearly every PCI device. Multiple OptionROM images may be appended. * * See also: [class@FuFirmware] */ typedef struct { guint16 machine_type; guint16 subsystem; guint16 compression_type; guint16 vendor_id; guint16 device_id; } FuOpromFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuOpromFirmware, fu_oprom_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_oprom_firmware_get_instance_private(o)) #define FU_OPROM_FIRMWARE_ALIGN_LEN 512u #define FU_OPROM_FIRMWARE_LAST_IMAGE_INDICATOR_BIT (1u << 7) /** * fu_oprom_firmware_get_machine_type: * @self: a #FuFirmware * * Gets the machine type. * * Returns: an integer * * Since: 1.8.2 **/ guint16 fu_oprom_firmware_get_machine_type(FuOpromFirmware *self) { FuOpromFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_OPROM_FIRMWARE(self), G_MAXUINT16); return priv->machine_type; } /** * fu_oprom_firmware_get_subsystem: * @self: a #FuFirmware * * Gets the machine type. * * Returns: an integer * * Since: 1.8.2 **/ guint16 fu_oprom_firmware_get_subsystem(FuOpromFirmware *self) { FuOpromFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_OPROM_FIRMWARE(self), G_MAXUINT16); return priv->subsystem; } /** * fu_oprom_firmware_get_compression_type: * @self: a #FuFirmware * * Gets the machine type. * * Returns: an integer * * Since: 1.8.2 **/ guint16 fu_oprom_firmware_get_compression_type(FuOpromFirmware *self) { FuOpromFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_OPROM_FIRMWARE(self), G_MAXUINT16); return priv->compression_type; } static void fu_oprom_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuOpromFirmware *self = FU_OPROM_FIRMWARE(firmware); FuOpromFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "machine_type", priv->machine_type); fu_xmlb_builder_insert_kx(bn, "subsystem", priv->subsystem); fu_xmlb_builder_insert_kx(bn, "compression_type", priv->compression_type); fu_xmlb_builder_insert_kx(bn, "vendor_id", priv->vendor_id); fu_xmlb_builder_insert_kx(bn, "device_id", priv->device_id); } static gboolean fu_oprom_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_oprom_validate_bytes(fw, offset, error); } static gboolean fu_oprom_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuOpromFirmware *self = FU_OPROM_FIRMWARE(firmware); FuOpromFirmwarePrivate *priv = GET_PRIVATE(self); guint16 expansion_header_offset = 0; guint16 pci_header_offset; guint16 image_length = 0; g_autoptr(GByteArray) st_hdr = NULL; g_autoptr(GByteArray) st_pci = NULL; /* parse header */ st_hdr = fu_struct_oprom_parse_bytes(fw, offset, error); if (st_hdr == NULL) return FALSE; priv->subsystem = fu_struct_oprom_get_subsystem(st_hdr); priv->compression_type = fu_struct_oprom_get_compression_type(st_hdr); priv->machine_type = fu_struct_oprom_get_machine_type(st_hdr); /* get PCI offset */ pci_header_offset = fu_struct_oprom_get_pci_header_offset(st_hdr); if (pci_header_offset == 0x0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no PCI data structure offset provided"); return FALSE; } /* verify signature */ st_pci = fu_struct_oprom_pci_parse_bytes(fw, offset + pci_header_offset, error); if (st_pci == NULL) return FALSE; priv->vendor_id = fu_struct_oprom_pci_get_vendor_id(st_pci); priv->device_id = fu_struct_oprom_pci_get_device_id(st_pci); /* get length */ image_length = fu_struct_oprom_pci_get_image_length(st_pci); if (image_length == 0x0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid image length"); return FALSE; } fu_firmware_set_size(firmware, image_length * FU_OPROM_FIRMWARE_ALIGN_LEN); fu_firmware_set_idx(firmware, fu_struct_oprom_pci_get_code_type(st_pci)); /* get CPD offset */ expansion_header_offset = fu_struct_oprom_get_expansion_header_offset(st_hdr); if (expansion_header_offset != 0x0) { g_autoptr(FuFirmware) img = NULL; img = fu_firmware_new_from_gtypes(fw, offset + expansion_header_offset, FWUPD_INSTALL_FLAG_NONE, error, FU_TYPE_IFWI_CPD_FIRMWARE, FU_TYPE_FIRMWARE, G_TYPE_INVALID); if (img == NULL) { g_prefix_error(error, "failed to build firmware: "); return FALSE; } fu_firmware_set_id(img, "cpd"); fu_firmware_set_offset(img, expansion_header_offset); fu_firmware_add_image(firmware, img); } /* success */ return TRUE; } static GByteArray * fu_oprom_firmware_write(FuFirmware *firmware, GError **error) { FuOpromFirmware *self = FU_OPROM_FIRMWARE(firmware); FuOpromFirmwarePrivate *priv = GET_PRIVATE(self); gsize image_size = 0; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) st_hdr = fu_struct_oprom_new(); g_autoptr(GByteArray) st_pci = fu_struct_oprom_pci_new(); g_autoptr(GBytes) blob_cpd = NULL; /* the smallest each image (and header) can be is 512 bytes */ image_size += fu_common_align_up(st_hdr->len, FU_FIRMWARE_ALIGNMENT_512); blob_cpd = fu_firmware_get_image_by_id_bytes(firmware, "cpd", NULL); if (blob_cpd != NULL) { image_size += fu_common_align_up(g_bytes_get_size(blob_cpd), FU_FIRMWARE_ALIGNMENT_512); } /* write the header */ fu_struct_oprom_set_image_size(st_hdr, image_size / FU_OPROM_FIRMWARE_ALIGN_LEN); fu_struct_oprom_set_subsystem(st_hdr, priv->subsystem); fu_struct_oprom_set_machine_type(st_hdr, priv->machine_type); fu_struct_oprom_set_compression_type(st_hdr, priv->compression_type); if (blob_cpd != NULL) { fu_struct_oprom_set_expansion_header_offset(st_hdr, image_size - FU_OPROM_FIRMWARE_ALIGN_LEN); } g_byte_array_append(buf, st_hdr->data, st_hdr->len); /* add PCI section */ fu_struct_oprom_pci_set_vendor_id(st_pci, priv->vendor_id); fu_struct_oprom_pci_set_device_id(st_pci, priv->device_id); fu_struct_oprom_pci_set_image_length(st_pci, image_size / FU_OPROM_FIRMWARE_ALIGN_LEN); fu_struct_oprom_pci_set_code_type(st_pci, fu_firmware_get_idx(firmware)); fu_struct_oprom_pci_set_indicator(st_pci, FU_OPROM_FIRMWARE_LAST_IMAGE_INDICATOR_BIT); g_byte_array_append(buf, st_pci->data, st_pci->len); fu_byte_array_align_up(buf, FU_FIRMWARE_ALIGNMENT_512, 0xFF); /* add CPD */ if (blob_cpd != NULL) { fu_byte_array_append_bytes(buf, blob_cpd); fu_byte_array_align_up(buf, FU_FIRMWARE_ALIGNMENT_512, 0xFF); } /* success */ return g_steal_pointer(&buf); } static gboolean fu_oprom_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuOpromFirmware *self = FU_OPROM_FIRMWARE(firmware); FuOpromFirmwarePrivate *priv = GET_PRIVATE(self); const gchar *tmp; /* simple properties */ tmp = xb_node_query_text(n, "machine_type", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) return FALSE; priv->machine_type = val; } tmp = xb_node_query_text(n, "subsystem", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) return FALSE; priv->subsystem = val; } tmp = xb_node_query_text(n, "compression_type", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) return FALSE; priv->compression_type = val; } tmp = xb_node_query_text(n, "vendor_id", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) return FALSE; priv->vendor_id = val; } tmp = xb_node_query_text(n, "device_id", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) return FALSE; priv->device_id = val; } /* success */ return TRUE; } static void fu_oprom_firmware_init(FuOpromFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE); } static void fu_oprom_firmware_class_init(FuOpromFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_oprom_firmware_check_magic; klass_firmware->export = fu_oprom_firmware_export; klass_firmware->parse = fu_oprom_firmware_parse; klass_firmware->write = fu_oprom_firmware_write; klass_firmware->build = fu_oprom_firmware_build; } /** * fu_oprom_firmware_new: * * Creates a new #FuFirmware of OptionROM format * * Since: 1.8.2 **/ FuFirmware * fu_oprom_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_OPROM_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-oprom-firmware.h000066400000000000000000000021661460375044200215310ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2022 Intel * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_OPROM_FIRMWARE (fu_oprom_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuOpromFirmware, fu_oprom_firmware, FU, OPROM_FIRMWARE, FuFirmware) struct _FuOpromFirmwareClass { FuFirmwareClass parent_class; }; /** * FU_OPROM_FIRMWARE_COMPRESSION_TYPE_NONE: * * No compression. * * Since: 1.8.2 **/ #define FU_OPROM_FIRMWARE_COMPRESSION_TYPE_NONE 0x00 /** * FU_OPROM_FIRMWARE_SUBSYSTEM_EFI_BOOT_SRV_DRV: * * EFI boot. * * Since: 1.8.2 **/ #define FU_OPROM_FIRMWARE_SUBSYSTEM_EFI_BOOT_SRV_DRV 0x00 /** * FU_OPROM_FIRMWARE_MACHINE_TYPE_X64: * * AMD64 machine type. * * Since: 1.8.2 **/ #define FU_OPROM_FIRMWARE_MACHINE_TYPE_X64 0x00 FuFirmware * fu_oprom_firmware_new(void); guint16 fu_oprom_firmware_get_machine_type(FuOpromFirmware *self) G_GNUC_NON_NULL(1); guint16 fu_oprom_firmware_get_subsystem(FuOpromFirmware *self) G_GNUC_NON_NULL(1); guint16 fu_oprom_firmware_get_compression_type(FuOpromFirmware *self) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-oprom.rs000066400000000000000000000016511460375044200201120ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, ValidateBytes, ParseBytes)] struct Oprom { signature: u16le == 0xAA55, image_size: u16le, // of 512 bytes init_func_entry_point: u32le, subsystem: u16le, machine_type: u16le, compression_type: u16le, _reserved: [u8; 8], efi_image_offset: u16le, pci_header_offset: u16le = $struct_size, expansion_header_offset: u16le, } #[derive(New, ParseBytes)] struct OpromPci { signature: u32le == 0x52494350, vendor_id: u16le, device_id: u16le, device_list_pointer: u16le, structure_length: u16le, structure_revision: u8, class_code: u24, image_length: u16le, // of 512 bytes image_revision: u16le, code_type: u8, indicator: u8, max_runtime_image_length: u16le, conf_util_code_header_pointer: u16le, dmtf_clp_entry_point_pointer: u16le, } fwupd-1.9.16/libfwupdplugin/fu-path.c000066400000000000000000000364501460375044200175150ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include #include #ifdef _WIN32 #include #endif #include "fwupd-error.h" #include "fu-path.h" /** * fu_path_rmtree: * @directory: a directory name * @error: (nullable): optional return location for an error * * Recursively removes a directory. * * Returns: %TRUE for success, %FALSE otherwise * * Since: 1.8.2 **/ gboolean fu_path_rmtree(const gchar *directory, GError **error) { const gchar *filename; g_autoptr(GDir) dir = NULL; g_return_val_if_fail(directory != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* try to open */ g_debug("removing %s", directory); dir = g_dir_open(directory, 0, error); if (dir == NULL) return FALSE; /* find each */ while ((filename = g_dir_read_name(dir))) { g_autofree gchar *src = NULL; src = g_build_filename(directory, filename, NULL); if (g_file_test(src, G_FILE_TEST_IS_DIR)) { if (!fu_path_rmtree(src, error)) return FALSE; } else { if (g_unlink(src) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to delete: %s", src); return FALSE; } } } if (g_remove(directory) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to delete: %s", directory); return FALSE; } return TRUE; } static gboolean fu_path_get_file_list_internal(GPtrArray *files, const gchar *directory, GError **error) { const gchar *filename; g_autoptr(GDir) dir = NULL; /* try to open */ dir = g_dir_open(directory, 0, error); if (dir == NULL) return FALSE; /* find each */ while ((filename = g_dir_read_name(dir))) { g_autofree gchar *src = g_build_filename(directory, filename, NULL); if (g_file_test(src, G_FILE_TEST_IS_SYMLINK)) continue; if (g_file_test(src, G_FILE_TEST_IS_DIR)) { if (!fu_path_get_file_list_internal(files, src, error)) return FALSE; } else { g_ptr_array_add(files, g_steal_pointer(&src)); } } return TRUE; } /** * fu_path_get_files: * @path: a directory name * @error: (nullable): optional return location for an error * * Returns every file found under @directory, and any subdirectory. * If any path under @directory cannot be accessed due to permissions an error * will be returned. * * Returns: (transfer container) (element-type utf8): array of files, or %NULL for error * * Since: 1.8.2 **/ GPtrArray * fu_path_get_files(const gchar *path, GError **error) { g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func(g_free); g_return_val_if_fail(path != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (!fu_path_get_file_list_internal(files, path, error)) return NULL; return g_steal_pointer(&files); } /** * fu_path_mkdir: * @dirname: a directory name * @error: (nullable): optional return location for an error * * Creates any required directories, including any parent directories. * * Returns: %TRUE for success * * Since: 1.8.2 **/ gboolean fu_path_mkdir(const gchar *dirname, GError **error) { g_return_val_if_fail(dirname != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) g_debug("creating path %s", dirname); if (g_mkdir_with_parents(dirname, 0755) == -1) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to create '%s': %s", dirname, g_strerror(errno)); return FALSE; } return TRUE; } /** * fu_path_mkdir_parent: * @filename: a full pathname * @error: (nullable): optional return location for an error * * Creates any required directories, including any parent directories. * * Returns: %TRUE for success * * Since: 1.8.2 **/ gboolean fu_path_mkdir_parent(const gchar *filename, GError **error) { g_autofree gchar *parent = NULL; g_return_val_if_fail(filename != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); parent = g_path_get_dirname(filename); return fu_path_mkdir(parent, error); } /** * fu_path_find_program: * @basename: the program to search * @error: (nullable): optional return location for an error * * Looks for a program in the PATH variable * * Returns: a new #gchar, or %NULL for error * * Since: 1.8.2 **/ gchar * fu_path_find_program(const gchar *basename, GError **error) { gchar *fn = g_find_program_in_path(basename); if (fn == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "missing executable %s in PATH", basename); return NULL; } return fn; } /** * fu_path_get_win32_basedir: * * Gets the base directory that fwupd has been launched from on Windows. * This is the directory containing all subdirectories (IE 'C:\Program Files (x86)\fwupd\') * * Returns: The system path, or %NULL if invalid * * Since: 1.8.2 **/ static gchar * fu_path_get_win32_basedir(void) { #ifdef _WIN32 char drive_buf[_MAX_DRIVE]; char dir_buf[_MAX_DIR]; _splitpath(_pgmptr, drive_buf, dir_buf, NULL, NULL); return g_build_filename(drive_buf, dir_buf, "..", NULL); #endif return NULL; } /** * fu_path_from_kind: * @path_kind: a #FuPathKind e.g. %FU_PATH_KIND_DATADIR_PKG * * Gets a fwupd-specific system path. These can be overridden with various * environment variables, for instance %FWUPD_DATADIR. * * Returns: a system path, or %NULL if invalid * * Since: 1.8.2 **/ gchar * fu_path_from_kind(FuPathKind path_kind) { const gchar *tmp; g_autofree gchar *basedir = NULL; switch (path_kind) { /* /var */ case FU_PATH_KIND_LOCALSTATEDIR: tmp = g_getenv("FWUPD_LOCALSTATEDIR"); if (tmp != NULL) return g_strdup(tmp); #ifdef _WIN32 return g_build_filename(g_getenv("USERPROFILE"), PACKAGE_NAME, FWUPD_LOCALSTATEDIR, NULL); #else tmp = g_getenv("SNAP_COMMON"); if (tmp != NULL) return g_build_filename(tmp, FWUPD_LOCALSTATEDIR, NULL); return g_build_filename(FWUPD_LOCALSTATEDIR, NULL); #endif /* /proc */ case FU_PATH_KIND_PROCFS: tmp = g_getenv("FWUPD_PROCFS"); if (tmp != NULL) return g_strdup(tmp); return g_strdup("/proc"); /* /sys/firmware */ case FU_PATH_KIND_SYSFSDIR_FW: tmp = g_getenv("FWUPD_SYSFSFWDIR"); if (tmp != NULL) return g_strdup(tmp); return g_strdup("/sys/firmware"); /* /sys/class/tpm */ case FU_PATH_KIND_SYSFSDIR_TPM: tmp = g_getenv("FWUPD_SYSFSTPMDIR"); if (tmp != NULL) return g_strdup(tmp); return g_strdup("/sys/class/tpm"); /* /sys/bus/platform/drivers */ case FU_PATH_KIND_SYSFSDIR_DRIVERS: tmp = g_getenv("FWUPD_SYSFSDRIVERDIR"); if (tmp != NULL) return g_strdup(tmp); return g_strdup("/sys/bus/platform/drivers"); /* /sys/kernel/security */ case FU_PATH_KIND_SYSFSDIR_SECURITY: tmp = g_getenv("FWUPD_SYSFSSECURITYDIR"); if (tmp != NULL) return g_strdup(tmp); return g_strdup("/sys/kernel/security"); /* /sys/class/dmi/id */ case FU_PATH_KIND_SYSFSDIR_DMI: tmp = g_getenv("FWUPD_SYSFSDMIDIR"); if (tmp != NULL) return g_strdup(tmp); return g_strdup("/sys/class/dmi/id"); /* /sys/firmware/acpi/tables */ case FU_PATH_KIND_ACPI_TABLES: tmp = g_getenv("FWUPD_ACPITABLESDIR"); if (tmp != NULL) return g_strdup(tmp); return g_strdup("/sys/firmware/acpi/tables"); /* /sys/module/firmware_class/parameters/path */ case FU_PATH_KIND_FIRMWARE_SEARCH: tmp = g_getenv("FWUPD_FIRMWARESEARCH"); if (tmp != NULL) return g_strdup(tmp); return g_strdup("/sys/module/firmware_class/parameters/path"); /* /etc */ case FU_PATH_KIND_SYSCONFDIR: tmp = g_getenv("FWUPD_SYSCONFDIR"); if (tmp != NULL) return g_strdup(tmp); tmp = g_getenv("SNAP"); if (tmp != NULL) return g_build_filename(tmp, FWUPD_SYSCONFDIR, NULL); basedir = fu_path_get_win32_basedir(); if (basedir != NULL) return g_build_filename(basedir, FWUPD_SYSCONFDIR, NULL); return g_strdup(FWUPD_SYSCONFDIR); /* /usr/libexec/ */ case FU_PATH_KIND_LIBEXECDIR: tmp = g_getenv("FWUPD_LIBEXECDIR"); if (tmp != NULL) return g_strdup(tmp); tmp = g_getenv("SNAP"); if (tmp != NULL) return g_build_filename(tmp, FWUPD_LIBEXECDIR, NULL); return g_strdup(FWUPD_LIBEXECDIR); /* /usr/lib//fwupd-#VERSION# */ case FU_PATH_KIND_LIBDIR_PKG: tmp = g_getenv("FWUPD_LIBDIR_PKG"); if (tmp != NULL) return g_strdup(tmp); tmp = g_getenv("SNAP"); if (tmp != NULL) return g_build_filename(tmp, FWUPD_LIBDIR_PKG, NULL); basedir = fu_path_get_win32_basedir(); if (basedir != NULL) return g_build_filename(basedir, FWUPD_LIBDIR_PKG, NULL); return g_build_filename(FWUPD_LIBDIR_PKG, NULL); /* /usr/share/fwupd */ case FU_PATH_KIND_DATADIR_PKG: tmp = g_getenv("FWUPD_DATADIR"); if (tmp != NULL) return g_strdup(tmp); tmp = g_getenv("SNAP"); if (tmp != NULL) return g_build_filename(tmp, FWUPD_DATADIR, PACKAGE_NAME, NULL); basedir = fu_path_get_win32_basedir(); if (basedir != NULL) return g_build_filename(basedir, FWUPD_DATADIR, PACKAGE_NAME, NULL); return g_build_filename(FWUPD_DATADIR, PACKAGE_NAME, NULL); /* /usr/libexec/fwupd */ case FU_PATH_KIND_LIBEXECDIR_PKG: tmp = g_getenv("FWUPD_LIBEXECDIR_PKG"); if (tmp != NULL) return g_strdup(tmp); tmp = g_getenv("SNAP"); if (tmp != NULL) return g_build_filename(tmp, FWUPD_LIBEXECDIR, PACKAGE_NAME, NULL); return g_build_filename(FWUPD_LIBEXECDIR, PACKAGE_NAME, NULL); /* /usr/share/fwupd/quirks.d */ case FU_PATH_KIND_DATADIR_QUIRKS: tmp = g_getenv("FWUPD_DATADIR_QUIRKS"); if (tmp != NULL) return g_strdup(tmp); basedir = fu_path_from_kind(FU_PATH_KIND_DATADIR_PKG); return g_build_filename(basedir, "quirks.d", NULL); /* /usr/libexec/fwupd/efi */ case FU_PATH_KIND_EFIAPPDIR: tmp = g_getenv("FWUPD_EFIAPPDIR"); if (tmp != NULL) return g_strdup(tmp); #ifdef EFI_APP_LOCATION tmp = g_getenv("SNAP"); if (tmp != NULL) return g_build_filename(tmp, EFI_APP_LOCATION, NULL); return g_strdup(EFI_APP_LOCATION); #else return NULL; #endif /* /etc/fwupd */ case FU_PATH_KIND_SYSCONFDIR_PKG: tmp = g_getenv("CONFIGURATION_DIRECTORY"); if (tmp != NULL && g_file_test(tmp, G_FILE_TEST_EXISTS)) return g_build_filename(tmp, NULL); basedir = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR); return g_build_filename(basedir, PACKAGE_NAME, NULL); /* /var/lib/fwupd */ case FU_PATH_KIND_LOCALSTATEDIR_PKG: tmp = g_getenv("STATE_DIRECTORY"); if (tmp != NULL && g_file_test(tmp, G_FILE_TEST_EXISTS)) return g_build_filename(tmp, NULL); basedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR); return g_build_filename(basedir, "lib", PACKAGE_NAME, NULL); /* /var/lib/fwupd/quirks.d */ case FU_PATH_KIND_LOCALSTATEDIR_QUIRKS: tmp = g_getenv("FWUPD_LOCALSTATEDIR_QUIRKS"); if (tmp != NULL) return g_build_filename(tmp, NULL); basedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); return g_build_filename(basedir, "quirks.d", NULL); /* /var/lib/fwupd/metadata */ case FU_PATH_KIND_LOCALSTATEDIR_METADATA: tmp = g_getenv("FWUPD_LOCALSTATEDIR_METADATA"); if (tmp != NULL) return g_build_filename(tmp, NULL); basedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); return g_build_filename(basedir, "metadata", NULL); /* /var/lib/fwupd/remotes.d */ case FU_PATH_KIND_LOCALSTATEDIR_REMOTES: tmp = g_getenv("FWUPD_LOCALSTATEDIR_REMOTES"); if (tmp != NULL) return g_build_filename(tmp, NULL); basedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); return g_build_filename(basedir, "remotes.d", NULL); /* /var/cache/fwupd */ case FU_PATH_KIND_CACHEDIR_PKG: tmp = g_getenv("CACHE_DIRECTORY"); if (tmp != NULL && g_file_test(tmp, G_FILE_TEST_EXISTS)) return g_build_filename(tmp, NULL); basedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR); return g_build_filename(basedir, "cache", PACKAGE_NAME, NULL); /* /var/etc/fwupd */ case FU_PATH_KIND_LOCALCONFDIR_PKG: tmp = g_getenv("LOCALCONF_DIRECTORY"); if (tmp != NULL && g_file_test(tmp, G_FILE_TEST_EXISTS)) return g_build_filename(tmp, NULL); basedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR); return g_build_filename(basedir, "etc", PACKAGE_NAME, NULL); /* /run/lock */ case FU_PATH_KIND_LOCKDIR: tmp = g_getenv("FWUPD_LOCKDIR"); if (tmp != NULL) return g_strdup(tmp); if (g_file_test("/run/lock", G_FILE_TEST_EXISTS)) return g_strdup("/run/lock"); return g_strdup("/var/run"); /* /sys/class/firmware-attributes */ case FU_PATH_KIND_SYSFSDIR_FW_ATTRIB: tmp = g_getenv("FWUPD_SYSFSFWATTRIBDIR"); if (tmp != NULL) return g_strdup(tmp); return g_strdup("/sys/class/firmware-attributes"); case FU_PATH_KIND_OFFLINE_TRIGGER: tmp = g_getenv("FWUPD_OFFLINE_TRIGGER"); if (tmp != NULL) return g_strdup(tmp); return g_strdup("/system-update"); case FU_PATH_KIND_POLKIT_ACTIONS: #ifdef POLKIT_ACTIONDIR return g_strdup(POLKIT_ACTIONDIR); #else return NULL; #endif /* C:\Program Files (x86)\fwupd\ */ case FU_PATH_KIND_WIN32_BASEDIR: return fu_path_get_win32_basedir(); /* / */ case FU_PATH_KIND_HOSTFS_ROOT: tmp = g_getenv("FWUPD_HOSTFS_ROOT"); if (tmp != NULL) return g_strdup(tmp); return g_strdup("/"); /* /boot */ case FU_PATH_KIND_HOSTFS_BOOT: tmp = g_getenv("FWUPD_HOSTFS_BOOT"); if (tmp != NULL) return g_strdup(tmp); return g_strdup("/boot"); /* /dev */ case FU_PATH_KIND_DEVFS: tmp = g_getenv("FWUPD_DEVFS"); if (tmp != NULL) return g_strdup(tmp); return g_strdup("/dev"); /* /etc/localtime or /var/lib/timezone/localtime */ case FU_PATH_KIND_LOCALTIME: { g_autofree gchar *sysconfdir = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR); g_autofree gchar *localstatedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR); tmp = g_getenv("FWUPD_LOCALTIME"); if (tmp != NULL) return g_strdup(tmp); basedir = g_build_filename(localstatedir, "lib", "timezone", "localtime", NULL); if (g_file_test(basedir, G_FILE_TEST_EXISTS)) return g_steal_pointer(&basedir); return g_build_filename(sysconfdir, "localtime", NULL); } /* this shouldn't happen */ default: g_warning("cannot build path for unknown kind %u", path_kind); } return NULL; } static gint fu_path_glob_sort_cb(gconstpointer a, gconstpointer b) { return g_strcmp0(*(const gchar **)a, *(const gchar **)b); } /** * fu_path_glob: * @directory: a directory path * @pattern: a glob pattern, e.g. `*foo*` * @error: (nullable): optional return location for an error * * Returns all the filenames that match a specific glob pattern. * Any results are sorted. No matching files will set @error. * * Returns: (element-type utf8) (transfer container): matching files, or %NULL * * Since: 1.8.2 **/ GPtrArray * fu_path_glob(const gchar *directory, const gchar *pattern, GError **error) { const gchar *basename; g_autoptr(GDir) dir = NULL; g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func(g_free); g_return_val_if_fail(directory != NULL, NULL); g_return_val_if_fail(pattern != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); dir = g_dir_open(directory, 0, error); if (dir == NULL) return NULL; while ((basename = g_dir_read_name(dir)) != NULL) { if (!g_pattern_match_simple(pattern, basename)) continue; g_ptr_array_add(files, g_build_filename(directory, basename, NULL)); } if (files->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no files matched pattern"); return NULL; } g_ptr_array_sort(files, fu_path_glob_sort_cb); return g_steal_pointer(&files); } fwupd-1.9.16/libfwupdplugin/fu-path.h000066400000000000000000000112301460375044200175070ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /** * FuPathKind: * @FU_PATH_KIND_CACHEDIR_PKG: The cache directory (IE /var/cache/fwupd) * @FU_PATH_KIND_DATADIR_PKG: The non-volatile data store (IE /usr/share/fwupd) * @FU_PATH_KIND_EFIAPPDIR: The location to store EFI apps before install (IE * /usr/libexec/fwupd/efi) * @FU_PATH_KIND_LOCALSTATEDIR: The local state directory (IE /var) * @FU_PATH_KIND_LOCALSTATEDIR_PKG: The local state directory for the package (IE * /var/lib/fwupd) * @FU_PATH_KIND_LIBDIR_PKG: The location to look for plugins for package (IE * /usr/lib/[triplet]/fwupd-plugins-3) * @FU_PATH_KIND_SYSCONFDIR: The configuration location (IE /etc) * @FU_PATH_KIND_SYSCONFDIR_PKG: The package configuration location (IE /etc/fwupd) * @FU_PATH_KIND_SYSFSDIR_FW: The sysfs firmware location (IE /sys/firmware) * @FU_PATH_KIND_SYSFSDIR_DRIVERS: The platform sysfs directory (IE /sys/bus/platform/drivers) * @FU_PATH_KIND_SYSFSDIR_TPM: The TPM sysfs directory (IE /sys/class/tpm) * @FU_PATH_KIND_PROCFS: The procfs location (IE /proc) * @FU_PATH_KIND_POLKIT_ACTIONS: The directory for policy kit actions (IE * /usr/share/polkit-1/actions/) * @FU_PATH_KIND_OFFLINE_TRIGGER: The file for the offline trigger (IE /system-update) * @FU_PATH_KIND_SYSFSDIR_SECURITY: The sysfs security location (IE /sys/kernel/security) * @FU_PATH_KIND_ACPI_TABLES: The location of the ACPI tables * @FU_PATH_KIND_LOCKDIR: The lock directory (IE /run/lock) * @FU_PATH_KIND_SYSFSDIR_FW_ATTRIB The firmware attributes directory (IE * /sys/class/firmware-attributes) * @FU_PATH_KIND_FIRMWARE_SEARCH: The path to configure the kernel policy for runtime loading *other than /lib/firmware (IE /sys/module/firmware_class/parameters/path) * @FU_PATH_KIND_DATADIR_QUIRKS: The quirks data store (IE /usr/share/fwupd/quirks.d) * @FU_PATH_KIND_LOCALSTATEDIR_QUIRKS: The local state directory for quirks (IE * /var/lib/fwupd/quirks.d) * @FU_PATH_KIND_LOCALSTATEDIR_METADATA: The local state directory for metadata (IE * /var/lib/fwupd/metadata) * @FU_PATH_KIND_LOCALSTATEDIR_REMOTES: The local state directory for remotes (IE * /var/lib/fwupd/remotes.d) * @FU_PATH_KIND_WIN32_BASEDIR: The root of the install directory on Windows * @FU_PATH_KIND_LOCALCONFDIR_PKG: The package configuration override (IE /var/etc/fwupd) * @FU_PATH_KIND_SYSFSDIR_DMI: The sysfs DMI location, (IE /sys/class/dmi/id) * @FU_PATH_KIND_HOSTFS_ROOT: The root of the host filesystem (IE /) * @FU_PATH_KIND_HOSTFS_BOOT: The host boot directory, (IE /boot) * @FU_PATH_KIND_DEVFS: The host dev directory, (IE /dev) * @FU_PATH_KIND_LOCALTIME: The timezone symlink (IE /etc/localtime) * @FU_PATH_KIND_LIBEXECDIR: The directory to launch executables * @FU_PATH_KIND_LIBEXECDIR_PKG The directory launch executables packaged with daemon * * Path types to use when dynamically determining a path at runtime **/ typedef enum { FU_PATH_KIND_CACHEDIR_PKG, FU_PATH_KIND_DATADIR_PKG, FU_PATH_KIND_EFIAPPDIR, FU_PATH_KIND_LOCALSTATEDIR, FU_PATH_KIND_LOCALSTATEDIR_PKG, FU_PATH_KIND_LIBDIR_PKG, FU_PATH_KIND_SYSCONFDIR, FU_PATH_KIND_SYSCONFDIR_PKG, FU_PATH_KIND_SYSFSDIR_FW, FU_PATH_KIND_SYSFSDIR_DRIVERS, FU_PATH_KIND_SYSFSDIR_TPM, FU_PATH_KIND_PROCFS, FU_PATH_KIND_POLKIT_ACTIONS, FU_PATH_KIND_OFFLINE_TRIGGER, FU_PATH_KIND_SYSFSDIR_SECURITY, FU_PATH_KIND_ACPI_TABLES, FU_PATH_KIND_LOCKDIR, FU_PATH_KIND_SYSFSDIR_FW_ATTRIB, FU_PATH_KIND_FIRMWARE_SEARCH, FU_PATH_KIND_DATADIR_QUIRKS, FU_PATH_KIND_LOCALSTATEDIR_QUIRKS, FU_PATH_KIND_LOCALSTATEDIR_METADATA, FU_PATH_KIND_LOCALSTATEDIR_REMOTES, FU_PATH_KIND_WIN32_BASEDIR, FU_PATH_KIND_LOCALCONFDIR_PKG, FU_PATH_KIND_SYSFSDIR_DMI, FU_PATH_KIND_HOSTFS_ROOT, FU_PATH_KIND_HOSTFS_BOOT, FU_PATH_KIND_DEVFS, FU_PATH_KIND_LOCALTIME, FU_PATH_KIND_LIBEXECDIR, FU_PATH_KIND_LIBEXECDIR_PKG, /*< private >*/ FU_PATH_KIND_LAST } FuPathKind; gchar * fu_path_from_kind(FuPathKind path_kind); GPtrArray * fu_path_glob(const gchar *directory, const gchar *pattern, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_path_rmtree(const gchar *directory, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); GPtrArray * fu_path_get_files(const gchar *path, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_path_mkdir(const gchar *dirname, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_path_mkdir_parent(const gchar *filename, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gchar * fu_path_find_program(const gchar *basename, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-pefile-firmware.c000066400000000000000000000131061460375044200216300ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-bytes.h" #include "fu-coswid-firmware.h" #include "fu-csv-firmware.h" #include "fu-mem.h" #include "fu-pefile-firmware.h" #include "fu-pefile-struct.h" #include "fu-sbatlevel-section.h" #include "fu-string.h" /** * FuPefileFirmware: * * A PE file consists of a Microsoft MS-DOS stub, the PE signature, the COFF file header, and an * optional header, followed by section data. * * Documented: * https://learn.microsoft.com/en-gb/windows/win32/debug/pe-format */ G_DEFINE_TYPE(FuPefileFirmware, fu_pefile_firmware, FU_TYPE_FIRMWARE) static gboolean fu_pefile_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_pe_dos_header_validate_bytes(fw, offset, error); } static gboolean fu_pefile_firmware_parse_section(FuFirmware *firmware, GBytes *fw, gsize hdr_offset, gsize strtab_offset, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0; guint32 sect_offset; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autofree gchar *sect_id = NULL; g_autofree gchar *sect_id_tmp = NULL; g_autoptr(FuFirmware) img = NULL; g_autoptr(GByteArray) st = NULL; g_autoptr(GBytes) blob = NULL; st = fu_struct_pe_coff_section_parse_bytes(fw, hdr_offset, error); if (st == NULL) return FALSE; sect_id_tmp = fu_struct_pe_coff_section_get_name(st); if (sect_id_tmp == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid section name"); return FALSE; } if (sect_id_tmp[0] == '/') { guint64 str_idx = 0x0; if (!fu_strtoull(sect_id_tmp + 1, &str_idx, 0, G_MAXUINT32, error)) return FALSE; sect_id = fu_memstrsafe(buf, bufsz, strtab_offset + str_idx, 16, error); if (sect_id == NULL) { g_prefix_error(error, "no section name: "); return FALSE; } } else { sect_id = g_steal_pointer(§_id_tmp); } /* create new firmware */ if (g_strcmp0(sect_id, ".sbom") == 0) { img = fu_coswid_firmware_new(); } else if (g_strcmp0(sect_id, ".sbat") == 0) { img = fu_csv_firmware_new(); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(img), "$id"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(img), "$version_raw"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(img), "vendor_name"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(img), "vendor_package_name"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(img), "$version"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(img), "vendor_url"); } else if (g_strcmp0(sect_id, ".sbatlevel") == 0) { img = fu_sbatlevel_section_new(); } else { img = fu_firmware_new(); } fu_firmware_set_id(img, sect_id); /* add data */ sect_offset = fu_struct_pe_coff_section_get_pointer_to_raw_data(st); fu_firmware_set_offset(img, sect_offset); blob = fu_bytes_new_offset(fw, sect_offset, fu_struct_pe_coff_section_get_virtual_size(st), error); if (blob == NULL) { g_prefix_error(error, "failed to get raw data for %s: ", sect_id); return FALSE; } if (!fu_firmware_parse(img, blob, flags, error)) { g_prefix_error(error, "failed to parse %s: ", sect_id); return FALSE; } return fu_firmware_add_image_full(firmware, img, error); } static gboolean fu_pefile_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { gsize strtab_offset; guint32 nr_sections; g_autoptr(GByteArray) st_coff = NULL; g_autoptr(GByteArray) st_doshdr = NULL; /* parse the DOS header to get the COFF header */ st_doshdr = fu_struct_pe_dos_header_parse_bytes(fw, offset, error); if (st_doshdr == NULL) return FALSE; offset += fu_struct_pe_dos_header_get_lfanew(st_doshdr); st_coff = fu_struct_pe_coff_file_header_parse_bytes(fw, offset, error); if (st_coff == NULL) return FALSE; offset += st_coff->len; /* verify optional extra header */ if (fu_struct_pe_coff_file_header_get_size_of_optional_header(st_coff) > 0) { g_autoptr(GByteArray) st_opt = fu_struct_pe_coff_optional_header64_parse_bytes(fw, offset, error); if (st_opt == NULL) return FALSE; offset += fu_struct_pe_coff_file_header_get_size_of_optional_header(st_coff); } /* read number of sections */ nr_sections = fu_struct_pe_coff_file_header_get_number_of_sections(st_coff); if (nr_sections == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid number of sections"); return FALSE; } strtab_offset = fu_struct_pe_coff_file_header_get_pointer_to_symbol_table(st_coff) + fu_struct_pe_coff_file_header_get_number_of_symbols(st_coff) * FU_STRUCT_PE_COFF_SYMBOL_SIZE; /* read out each section */ for (guint idx = 0; idx < nr_sections; idx++) { if (!fu_pefile_firmware_parse_section(firmware, fw, offset, strtab_offset, flags, error)) return FALSE; offset += FU_STRUCT_PE_COFF_SECTION_SIZE; } /* success */ return TRUE; } static void fu_pefile_firmware_init(FuPefileFirmware *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 100); } static void fu_pefile_firmware_class_init(FuPefileFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_pefile_firmware_check_magic; klass_firmware->parse = fu_pefile_firmware_parse; } /** * fu_pefile_firmware_new: * * Creates a new #FuPefileFirmware * * Since: 1.8.10 **/ FuFirmware * fu_pefile_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_PEFILE_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-pefile-firmware.h000066400000000000000000000006431460375044200216370ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_PEFILE_FIRMWARE (fu_pefile_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuPefileFirmware, fu_pefile_firmware, FU, PEFILE_FIRMWARE, FuFirmware) struct _FuPefileFirmwareClass { FuFirmwareClass parent_class; }; FuFirmware * fu_pefile_firmware_new(void); fwupd-1.9.16/libfwupdplugin/fu-pefile.rs000066400000000000000000000066421460375044200202270ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ParseBytes, ValidateBytes)] struct PeDosHeader { magic: u16le == 0x5A4D, _cblp: u16le = 0x90, _cp: u16le = 0x3, _crlc: u16le, _cparhdr: u16le = 0x4, _minalloc: u16le, _maxalloc: u16le = 0xFFFF, _ss: u16le, _sp: u16le = 0xB8, _csum: u16le, _ip: u16le, _cs: u16le, _lfarlc: u16le = 0x40, _ovno: u16le, _res: [u8; 8], _oemid: u16le, _oeminfo: u16le, _res2: [u8; 20], lfanew: u32le = 0x80, } #[repr(u16le)] enum PeCoffMachine { Unknown, Alpha = 0x184, Alpha64 = 0x284, Am33 = 0x1d3, Amd64 = 0x8664, Arm = 0x1c0, Arm64 = 0xaa64, Armnt = 0x1c4, Ebc = 0xebc, I386 = 0x14c, Ia64 = 0x200, Loongarch32 = 0x6232, Loongarch64 = 0x6264, M32r = 0x9041, Mips16 = 0x266, Mipsfpu = 0x366, Mipsfpu16 = 0x466, Powerpc = 0x1f0, Powerpcfp = 0x1f1, R4000 = 0x166, Riscv32 = 0x5032, Riscv64 = 0x5064, Riscv128 = 0x5128, Sh3 = 0x1a2, Sh3dsp = 0x1a3, Sh4 = 0x1a6, Sh5 = 0x1a8, Thumb = 0x1c2, Wcemipsv2 = 0x169, } #[repr(u16le)] enum PeCoffMagic { Pe32 = 0x10b, Pe32Plus = 0x20b, } #[repr(u16le)] enum FuCoffSubsystem { Unknown, Native = 1, WindowsGui = 2, WindowsCui = 3, Os2Cui = 5, PosixCui = 7, NativeWindows = 8, WindowsCeGui = 9, EfiApplication = 10, EfiBootServiceDriver = 11, EfiRuntimeDriver = 12, EfiRom = 13, Xbox = 14, WindowsBootApplication = 16, } #[derive(ParseBytes)] struct PeCoffFileHeader { signature: u32le == 0x4550, // "PE\0\0" machine: PeCoffMachine = Amd64, number_of_sections: u16le, _time_date_stamp: u32le, pointer_to_symbol_table: u32le, number_of_symbols: u32le, size_of_optional_header: u16le = 0xf0, characteristics: u16le = 0x2022, } #[derive(ParseBytes)] struct PeCoffOptionalHeader64 { magic: PeCoffMagic = Pe32Plus, _major_linker_version: u8 = 0x0e, _minor_linker_version: u8 = 0x0e, size_of_code: u32le, size_of_initialized_data: u32le, size_of_uninitialized_data: u32le, addressofentrypoint: u32le, base_of_code: u32le, image_base: u64le, section_alignment: u32le = 0x200, file_alignment: u32le = 0x200, _major_operating_system_version: u16le, _minor_operating_system_version: u16le, _major_image_version: u16le, _minor_image_version: u16le, _major_subsystem_version: u16le, _minor_subsystem_version: u16le, _win32_versionvalue: u32le, size_of_image: u32le, size_of_headers: u32le, check_sum: u32le, subsystem: FuCoffSubsystem = EfiApplication, _dll_characteristics: u16le, _size_of_stackreserve: u64le, _size_of_stack_commit: u64le, _size_of_heap_reserve: u64le, _size_of_heap_commit: u64le, loader_flags: u32le, number_of_rva_and_sizes: u32le, } struct PeCoffSymbol { name: [char; 8], value: u32le, section_number: u16le, type: u16le, storage_class: u8, number_of_aux_symbols: u8, } #[derive(ParseBytes)] struct PeCoffSection { name: [char; 8], virtual_size: u32le, virtual_address: u32le, size_of_raw_data: u32le, pointer_to_raw_data: u32le, _pointer_to_relocations: u32le, _pointer_to_linenumbers: u32le, _number_of_relocations: u16le, _number_of_linenumbers: u16le, characteristics: u32le, } fwupd-1.9.16/libfwupdplugin/fu-plugin-private.h000066400000000000000000000131701460375044200215260ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-context.h" #include "fu-plugin.h" #include "fu-security-attrs.h" FuPlugin * fu_plugin_new(FuContext *ctx); FuPlugin * fu_plugin_new_from_gtype(GType gtype, FuContext *ctx) G_GNUC_NON_NULL(2); void fu_plugin_set_context(FuPlugin *self, FuContext *ctx) G_GNUC_NON_NULL(1); gboolean fu_plugin_is_open(FuPlugin *self) G_GNUC_NON_NULL(1); guint fu_plugin_get_order(FuPlugin *self) G_GNUC_NON_NULL(1); void fu_plugin_set_order(FuPlugin *self, guint order) G_GNUC_NON_NULL(1); guint fu_plugin_get_priority(FuPlugin *self) G_GNUC_NON_NULL(1); void fu_plugin_set_priority(FuPlugin *self, guint priority) G_GNUC_NON_NULL(1); gchar * fu_plugin_to_string(FuPlugin *self) G_GNUC_NON_NULL(1); void fu_plugin_add_string(FuPlugin *self, guint idt, GString *str) G_GNUC_NON_NULL(1); GPtrArray * fu_plugin_get_rules(FuPlugin *self, FuPluginRule rule) G_GNUC_NON_NULL(1); gboolean fu_plugin_has_rule(FuPlugin *self, FuPluginRule rule, const gchar *name) G_GNUC_NON_NULL(1); GHashTable * fu_plugin_get_report_metadata(FuPlugin *self) G_GNUC_NON_NULL(1); gboolean fu_plugin_open(FuPlugin *self, const gchar *filename, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_reset_config_values(FuPlugin *self, GError **error) G_GNUC_NON_NULL(1); void fu_plugin_runner_init(FuPlugin *self) G_GNUC_NON_NULL(1); gboolean fu_plugin_runner_startup(FuPlugin *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_runner_ready(FuPlugin *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_runner_coldplug(FuPlugin *self, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_runner_prepare(FuPlugin *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3); gboolean fu_plugin_runner_cleanup(FuPlugin *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3); gboolean fu_plugin_runner_composite_prepare(FuPlugin *self, GPtrArray *devices, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_runner_composite_cleanup(FuPlugin *self, GPtrArray *devices, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_runner_attach(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3); gboolean fu_plugin_runner_detach(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3); gboolean fu_plugin_runner_reload(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_runner_backend_device_added(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3); gboolean fu_plugin_runner_backend_device_changed(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_runner_device_created(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fu_plugin_runner_device_added(FuPlugin *self, FuDevice *device) G_GNUC_NON_NULL(1, 2); void fu_plugin_runner_device_removed(FuPlugin *self, FuDevice *device) G_GNUC_NON_NULL(1, 2); void fu_plugin_runner_device_register(FuPlugin *self, FuDevice *device) G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_runner_write_firmware(FuPlugin *self, FuDevice *device, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3, 4); gboolean fu_plugin_runner_verify(FuPlugin *self, FuDevice *device, FuProgress *progress, FuPluginVerifyFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3); gboolean fu_plugin_runner_activate(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) G_GNUC_NON_NULL(1, 2, 3); gboolean fu_plugin_runner_unlock(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_runner_clear_results(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_runner_get_results(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_runner_fix_host_security_attr(FuPlugin *self, FwupdSecurityAttr *attr, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_runner_undo_host_security_attr(FuPlugin *self, FwupdSecurityAttr *attr, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_runner_reboot_cleanup(FuPlugin *self, FuDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); void fu_plugin_runner_add_security_attrs(FuPlugin *self, FuSecurityAttrs *attrs) G_GNUC_NON_NULL(1, 2); gint fu_plugin_name_compare(FuPlugin *plugin1, FuPlugin *plugin2) G_GNUC_NON_NULL(1, 2); gint fu_plugin_order_compare(FuPlugin *plugin1, FuPlugin *plugin2) G_GNUC_NON_NULL(1, 2); /* utils */ gchar * fu_plugin_guess_name_from_fn(const gchar *filename) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-plugin-vfuncs.h000066400000000000000000000005201460375044200213530ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-plugin.h" /** * fu_plugin_init_vfuncs: * @vfuncs: #FuPluginVfuncs * * Initializes the plugin vfuncs. * * Since: 1.7.2 **/ void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-plugin.c000066400000000000000000002413761460375044200200640ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuPlugin" #include "config.h" #include #include #include #include #include #include #include "fu-bytes.h" #include "fu-config-private.h" #include "fu-context-private.h" #include "fu-device-private.h" #include "fu-kernel.h" #include "fu-path.h" #include "fu-plugin-private.h" #include "fu-security-attr.h" #include "fu-string.h" /** * FuPlugin: * * A plugin which is used by fwupd to enumerate and update devices. * * See also: [class@FuDevice], [class@Fwupd.Plugin] */ static void fu_plugin_finalize(GObject *object); typedef struct { GModule *module; guint order; guint priority; gboolean done_init; GPtrArray *rules[FU_PLUGIN_RULE_LAST]; GPtrArray *devices; /* (nullable) (element-type FuDevice) */ GHashTable *runtime_versions; GHashTable *compile_versions; FuContext *ctx; GArray *device_gtypes; /* (nullable): of #GType */ GType device_gtype_default; GHashTable *cache; /* (nullable): platform_id:GObject */ GHashTable *report_metadata; /* (nullable): key:value */ GFileMonitor *config_monitor; FuPluginData *data; FuPluginVfuncs vfuncs; } FuPluginPrivate; enum { PROP_0, PROP_CONTEXT, PROP_LAST }; enum { SIGNAL_DEVICE_ADDED, SIGNAL_DEVICE_REMOVED, SIGNAL_DEVICE_REGISTER, SIGNAL_RULES_CHANGED, SIGNAL_CHECK_SUPPORTED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE_WITH_PRIVATE(FuPlugin, fu_plugin, FWUPD_TYPE_PLUGIN) #define GET_PRIVATE(o) (fu_plugin_get_instance_private(o)) typedef void (*FuPluginInitVfuncsFunc)(FuPluginVfuncs *vfuncs); typedef gboolean (*FuPluginDeviceFunc)(FuPlugin *self, FuDevice *device, GError **error); typedef gboolean (*FuPluginDeviceProgressFunc)(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error); typedef gboolean (*FuPluginFlaggedDeviceFunc)(FuPlugin *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error); typedef gboolean (*FuPluginDeviceArrayFunc)(FuPlugin *self, GPtrArray *devices, GError **error); /** * fu_plugin_is_open: * @self: a #FuPlugin * * Determines if the plugin is opened * * Returns: TRUE for opened, FALSE for not * * Since: 1.3.5 **/ gboolean fu_plugin_is_open(FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE(self); return priv->module != NULL; } /** * fu_plugin_get_name: * @self: a #FuPlugin * * Gets the plugin name. * * Returns: a plugin name, or %NULL for unknown. * * Since: 0.8.0 **/ const gchar * fu_plugin_get_name(FuPlugin *self) { g_return_val_if_fail(FU_IS_PLUGIN(self), NULL); return fwupd_plugin_get_name(FWUPD_PLUGIN(self)); } /** * fu_plugin_set_name: * @self: a #FuPlugin * @name: a string * * Sets the plugin name. * * Since: 0.8.0 **/ void fu_plugin_set_name(FuPlugin *self, const gchar *name) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PLUGIN(self)); g_return_if_fail(!priv->done_init); if (g_strcmp0(name, fwupd_plugin_get_name(FWUPD_PLUGIN(self))) == 0) { g_critical("plugin name set to original value: %s", name); return; } if (fwupd_plugin_get_name(FWUPD_PLUGIN(self)) != NULL) { g_debug("overwriting plugin name %s -> %s", fwupd_plugin_get_name(FWUPD_PLUGIN(self)), name); } fwupd_plugin_set_name(FWUPD_PLUGIN(self), name); } static FuPluginVfuncs * fu_plugin_get_vfuncs(FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE(self); if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_MODULAR)) return &priv->vfuncs; return FU_PLUGIN_GET_CLASS(self); } /** * fu_plugin_cache_lookup: * @self: a #FuPlugin * @id: the key * * Finds an object in the per-plugin cache. * * Returns: (transfer none): a #GObject, or %NULL for unfound. * * Since: 0.8.0 **/ gpointer fu_plugin_cache_lookup(FuPlugin *self, const gchar *id) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_PLUGIN(self), NULL); g_return_val_if_fail(id != NULL, NULL); if (priv->cache == NULL) return NULL; return g_hash_table_lookup(priv->cache, id); } /** * fu_plugin_cache_add: * @self: a #FuPlugin * @id: the key * @dev: a #GObject, typically a #FuDevice * * Adds an object to the per-plugin cache. * * Since: 0.8.0 **/ void fu_plugin_cache_add(FuPlugin *self, const gchar *id, gpointer dev) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PLUGIN(self)); g_return_if_fail(id != NULL); g_return_if_fail(G_IS_OBJECT(dev)); if (priv->cache == NULL) { priv->cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_object_unref); } g_hash_table_insert(priv->cache, g_strdup(id), g_object_ref(dev)); } /** * fu_plugin_cache_remove: * @self: a #FuPlugin * @id: the key * * Removes an object from the per-plugin cache. * * Since: 0.8.0 **/ void fu_plugin_cache_remove(FuPlugin *self, const gchar *id) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PLUGIN(self)); g_return_if_fail(id != NULL); if (priv->cache == NULL) return; g_hash_table_remove(priv->cache, id); } /** * fu_plugin_get_data: * @self: a #FuPlugin * * Gets the per-plugin allocated private data. This will return %NULL unless * fu_plugin_alloc_data() has been called by the plugin. * * Returns: (transfer none): a pointer to a structure, or %NULL for unset. * * Since: 0.8.0 **/ FuPluginData * fu_plugin_get_data(FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_PLUGIN(self), NULL); return priv->data; } /** * fu_plugin_alloc_data: (skip): * @self: a #FuPlugin * @data_sz: the size to allocate * * Allocates the per-plugin allocated private data. * * Returns: (transfer full): a pointer to a structure, or %NULL for unset. * * Since: 0.8.0 **/ FuPluginData * fu_plugin_alloc_data(FuPlugin *self, gsize data_sz) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_PLUGIN(self), NULL); if (priv->data != NULL) { g_critical("fu_plugin_alloc_data() already used by plugin"); return priv->data; } priv->data = g_malloc0(data_sz); return priv->data; } /** * fu_plugin_guess_name_from_fn: * @filename: filename to guess * * Tries to guess the name of the plugin from a filename * * Returns: (transfer full): the guessed name of the plugin * * Since: 1.0.8 **/ gchar * fu_plugin_guess_name_from_fn(const gchar *filename) { const gchar *prefix = "libfu_plugin_"; gchar *name; gchar *str = g_strstr_len(filename, -1, prefix); if (str == NULL) return NULL; name = g_strdup(str + strlen(prefix)); g_strdelimit(name, ".", '\0'); return name; } /** * fu_plugin_open: * @self: a #FuPlugin * @filename: the shared object filename to open * @error: (nullable): optional return location for an error * * Opens the plugin module, and calls `->load()` on it. * * Returns: TRUE for success, FALSE for fail * * Since: 0.8.0 **/ gboolean fu_plugin_open(FuPlugin *self, const gchar *filename, GError **error) { FuPluginPrivate *priv = GET_PRIVATE(self); FuPluginVfuncs *vfuncs; FuPluginInitVfuncsFunc init_vfuncs = NULL; g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(filename != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); priv->module = g_module_open(filename, 0); if (priv->module == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to open plugin %s: %s", filename, g_module_error()); fu_plugin_add_flag(self, FWUPD_PLUGIN_FLAG_FAILED_OPEN); fu_plugin_add_flag(self, FWUPD_PLUGIN_FLAG_USER_WARNING); return FALSE; } /* call the vfunc setup */ g_module_symbol(priv->module, "fu_plugin_init_vfuncs", (gpointer *)&init_vfuncs); if (init_vfuncs == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to init_vfuncs() on plugin %s", filename); fu_plugin_add_flag(self, FWUPD_PLUGIN_FLAG_FAILED_OPEN); fu_plugin_add_flag(self, FWUPD_PLUGIN_FLAG_USER_WARNING); return FALSE; } /* we can't "fallback" from modular to built-in so this is safe */ fu_plugin_add_flag(self, FWUPD_PLUGIN_FLAG_MODULAR); vfuncs = fu_plugin_get_vfuncs(self); init_vfuncs(vfuncs); /* set automatically */ if (fu_plugin_get_name(self) == NULL) { g_autofree gchar *str = fu_plugin_guess_name_from_fn(filename); fu_plugin_set_name(self, str); } /* optional */ if (vfuncs->load != NULL) { FuContext *ctx = fu_plugin_get_context(self); g_debug("load(%s)", filename); vfuncs->load(ctx); } return TRUE; } static gchar * fu_plugin_flags_to_string(FwupdPluginFlags flags) { g_autoptr(GString) str = g_string_new(NULL); for (guint i = 0; i < 64; i++) { FwupdPluginFlags flag = (guint64)1 << i; if ((flags & flag) == 0) continue; if (str->len > 0) g_string_append_c(str, ','); g_string_append(str, fwupd_plugin_flag_to_string(flag)); } if (str->len == 0) return NULL; return g_string_free(g_steal_pointer(&str), FALSE); } /** * fu_plugin_add_string: * @self: a #FuPlugin * @idt: indent level * @str: a string to append to * * Add daemon-specific device metadata to an existing string. * * Since: 1.8.4 **/ void fu_plugin_add_string(FuPlugin *self, guint idt, GString *str) { FuPluginPrivate *priv = GET_PRIVATE(self); FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); const gchar *name = fwupd_plugin_get_name(FWUPD_PLUGIN(self)); g_autofree gchar *flags = NULL; g_return_if_fail(FU_IS_PLUGIN(self)); g_return_if_fail(str != NULL); /* attributes */ fu_string_append(str, idt, G_OBJECT_TYPE_NAME(self), ""); if (name != NULL) fu_string_append(str, idt + 1, "Name", name); flags = fu_plugin_flags_to_string(fwupd_plugin_get_flags(FWUPD_PLUGIN(self))); if (flags != NULL) fu_string_append(str, idt + 1, "Flags", flags); if (priv->order != 0) fu_string_append_ku(str, idt + 1, "Order", priv->order); if (priv->priority != 0) fu_string_append_ku(str, idt + 1, "Priority", priv->priority); if (priv->device_gtype_default != G_TYPE_INVALID) { fu_string_append(str, idt, "DeviceGTypeDefault", g_type_name(priv->device_gtype_default)); } /* optional */ if (vfuncs->to_string != NULL) vfuncs->to_string(self, idt + 1, str); } /** * fu_plugin_to_string: * @self: a #FuPlugin * * This allows us to easily print the plugin metadata. * * Returns: a string value, or %NULL for invalid. * * Since: 1.8.4 **/ gchar * fu_plugin_to_string(FuPlugin *self) { g_autoptr(GString) str = g_string_new(NULL); g_return_val_if_fail(FU_IS_PLUGIN(self), NULL); fu_plugin_add_string(self, 0, str); return g_string_free(g_steal_pointer(&str), FALSE); } /* order of usefulness to the user */ static const gchar * fu_plugin_build_device_update_error(FuPlugin *self) { if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_NO_HARDWARE)) return "Not updatable as required hardware was not found"; if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_LEGACY_BIOS)) return "Not updatable in legacy BIOS mode"; if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_CAPSULES_UNSUPPORTED)) return "Not updatable as UEFI capsule updates not enabled in firmware setup"; if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_UNLOCK_REQUIRED)) return "Not updatable as requires unlock"; if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_AUTH_REQUIRED)) return "Not updatable as requires authentication"; if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_EFIVAR_NOT_MOUNTED)) return "Not updatable as efivarfs was not found"; if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_ESP_NOT_FOUND)) return "Not updatable as UEFI ESP partition not detected"; if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return "Not updatable as plugin was disabled"; return NULL; } static void fu_plugin_ensure_devices(FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE(self); if (priv->devices != NULL) return; priv->devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); } static void fu_plugin_device_child_added_cb(FuDevice *device, FuDevice *child, FuPlugin *self) { g_debug("child %s added to parent %s after setup, adding to daemon", fu_device_get_id(child), fu_device_get_id(device)); fu_plugin_device_add(self, child); } static void fu_plugin_device_child_removed_cb(FuDevice *device, FuDevice *child, FuPlugin *self) { g_debug("child %s removed from parent %s after setup, removing from daemon", fu_device_get_id(child), fu_device_get_id(device)); fu_plugin_device_remove(self, child); } /** * fu_plugin_device_add: * @self: a #FuPlugin * @device: a device * * Asks the daemon to add a device to the exported list. If this device ID * has already been added by a different plugin then this request will be * ignored. * * Since: 0.8.0 **/ void fu_plugin_device_add(FuPlugin *self, FuDevice *device) { FuPluginPrivate *priv = GET_PRIVATE(self); GPtrArray *children; g_autoptr(GError) error = NULL; g_return_if_fail(FU_IS_PLUGIN(self)); g_return_if_fail(FU_IS_DEVICE(device)); /* ensure the device ID is set from the physical and logical IDs */ if (!fu_device_ensure_id(device, &error)) { g_warning("ignoring add: %s", error->message); return; } /* add to array */ fu_plugin_ensure_devices(self); g_ptr_array_add(priv->devices, g_object_ref(device)); /* proxy to device where required */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE)) { if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_USER_WARNING)) { fu_device_inhibit(device, "clear-updatable", fu_plugin_build_device_update_error(self)); } else { fu_device_inhibit(device, "clear-updatable", "Plugin disallowed updates with no user warning"); } } g_debug("emit added from %s: %s", fu_plugin_get_name(self), fu_device_get_id(device)); fu_device_set_created(device, (guint64)g_get_real_time() / G_USEC_PER_SEC); fu_device_set_plugin(device, fu_plugin_get_name(self)); g_signal_emit(self, signals[SIGNAL_DEVICE_ADDED], 0, device); /* add children if they have not already been added */ children = fu_device_get_children(device); for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index(children, i); if (fu_device_get_created(child) == 0) fu_plugin_device_add(self, child); } /* watch to see if children are added or removed at runtime */ g_signal_connect(FU_DEVICE(device), "child-added", G_CALLBACK(fu_plugin_device_child_added_cb), self); g_signal_connect(FU_DEVICE(device), "child-removed", G_CALLBACK(fu_plugin_device_child_removed_cb), self); } /** * fu_plugin_get_devices: * @self: a #FuPlugin * * Returns all devices added by the plugin using [method@FuPlugin.device_add] and * not yet removed with [method@FuPlugin.device_remove]. * * Returns: (transfer none) (element-type FuDevice): devices * * Since: 1.5.6 **/ GPtrArray * fu_plugin_get_devices(FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_PLUGIN(self), NULL); fu_plugin_ensure_devices(self); return priv->devices; } /** * fu_plugin_device_register: * @self: a #FuPlugin * @device: a device * * Registers the device with other plugins so they can set metadata. * * Plugins do not have to call this manually as this is done automatically * when using [method@FuPlugin.device_add]. They may wish to use this manually * if for instance the coldplug should be ignored based on the metadata * set from other plugins. * * Since: 0.9.7 **/ void fu_plugin_device_register(FuPlugin *self, FuDevice *device) { g_autoptr(GError) error = NULL; g_return_if_fail(FU_IS_PLUGIN(self)); g_return_if_fail(FU_IS_DEVICE(device)); /* ensure the device ID is set from the physical and logical IDs */ if (!fu_device_ensure_id(device, &error)) { g_warning("ignoring registration: %s", error->message); return; } g_debug("emit device-register from %s: %s", fu_plugin_get_name(self), fu_device_get_id(device)); g_signal_emit(self, signals[SIGNAL_DEVICE_REGISTER], 0, device); } /** * fu_plugin_device_remove: * @self: a #FuPlugin * @device: a device * * Asks the daemon to remove a device from the exported list. * * Since: 0.8.0 **/ void fu_plugin_device_remove(FuPlugin *self, FuDevice *device) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PLUGIN(self)); g_return_if_fail(FU_IS_DEVICE(device)); /* remove from array */ if (priv->devices != NULL) g_ptr_array_remove(priv->devices, device); g_debug("emit removed from %s: %s", fu_plugin_get_name(self), fu_device_get_id(device)); g_signal_emit(self, signals[SIGNAL_DEVICE_REMOVED], 0, device); } /** * fu_plugin_check_supported: * @self: a #FuPlugin * @guid: a hardware ID GUID, e.g. `6de5d951-d755-576b-bd09-c5cf66b27234` * * Checks to see if a specific device GUID is supported, i.e. available in the * AppStream metadata. * * Returns: %TRUE if the device is supported. * * Since: 1.0.0 **/ static gboolean fu_plugin_check_supported(FuPlugin *self, const gchar *guid) { gboolean retval = FALSE; g_signal_emit(self, signals[SIGNAL_CHECK_SUPPORTED], 0, guid, &retval); return retval; } /** * fu_plugin_get_context: * @self: a #FuPlugin * * Gets the context for a plugin. * * Returns: (transfer none): a #FuContext or %NULL if not set * * Since: 1.6.0 **/ FuContext * fu_plugin_get_context(FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE(self); return priv->ctx; } /** * fu_plugin_set_context: * @self: a #FuPlugin * @ctx: (nullable): optional #FuContext * * Sets the context for this plugin. * * Since: 1.8.6 **/ void fu_plugin_set_context(FuPlugin *self, FuContext *ctx) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PLUGIN(self)); g_return_if_fail(FU_IS_CONTEXT(ctx) || ctx == NULL); if (g_set_object(&priv->ctx, ctx)) g_object_notify(G_OBJECT(self), "context"); } static gboolean fu_plugin_device_attach(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) { FuDevice *proxy = fu_device_get_proxy_with_fallback(device); FuDeviceClass *proxy_klass = FU_DEVICE_GET_CLASS(proxy); g_autoptr(FuDeviceLocker) locker = NULL; if (proxy_klass->attach == NULL) return TRUE; locker = fu_device_locker_new(proxy, error); if (locker == NULL) return FALSE; return fu_device_attach_full(device, progress, error); } static gboolean fu_plugin_device_detach(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) { FuDevice *proxy = fu_device_get_proxy_with_fallback(device); FuDeviceClass *proxy_klass = FU_DEVICE_GET_CLASS(proxy); g_autoptr(FuDeviceLocker) locker = NULL; if (proxy_klass->detach == NULL) return TRUE; locker = fu_device_locker_new(proxy, error); if (locker == NULL) return FALSE; return fu_device_detach_full(device, progress, error); } static gboolean fu_plugin_device_activate(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) { FuDevice *proxy = fu_device_get_proxy_with_fallback(device); FuDeviceClass *proxy_klass = FU_DEVICE_GET_CLASS(proxy); g_autoptr(FuDeviceLocker) locker = NULL; if (proxy_klass->activate == NULL) return TRUE; locker = fu_device_locker_new(proxy, error); if (locker == NULL) return FALSE; return fu_device_activate(device, progress, error); } static gboolean fu_plugin_device_write_firmware(FuPlugin *self, FuDevice *device, GBytes *fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDevice *proxy = fu_device_get_proxy_with_fallback(device); g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new(proxy, error); if (locker == NULL) return FALSE; /* back the old firmware up to /var/lib/fwupd */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_BACKUP_BEFORE_INSTALL)) { g_autoptr(GBytes) fw_old = NULL; g_autofree gchar *path = NULL; g_autofree gchar *fn = NULL; g_autofree gchar *localstatedir = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 25, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 75, NULL); fw_old = fu_device_dump_firmware(device, fu_progress_get_child(progress), error); if (fw_old == NULL) { g_prefix_error(error, "failed to backup old firmware: "); return FALSE; } localstatedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); fn = g_strdup_printf("%s.bin", fu_device_get_version(device)); path = g_build_filename( localstatedir, "backup", fu_device_get_id(device), fu_device_get_serial(device) != NULL ? fu_device_get_serial(device) : "default", fn, NULL); fu_progress_step_done(progress); if (!fu_bytes_set_contents(path, fw_old, error)) return FALSE; if (!fu_device_write_firmware(device, fw, fu_progress_get_child(progress), flags, error)) return FALSE; fu_progress_step_done(progress); return TRUE; } return fu_device_write_firmware(device, fw, progress, flags, error); } static gboolean fu_plugin_device_get_results(FuPlugin *self, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; if (!fu_device_get_results(device, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) return TRUE; g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } return TRUE; } static gboolean fu_plugin_device_read_firmware(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) { FuDevice *proxy = fu_device_get_proxy_with_fallback(device); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuFirmware) firmware = NULL; g_autoptr(GBytes) fw = NULL; GChecksumType checksum_types[] = {G_CHECKSUM_SHA1, G_CHECKSUM_SHA256, 0}; locker = fu_device_locker_new(proxy, error); if (locker == NULL) return FALSE; if (!fu_device_detach_full(device, progress, error)) return FALSE; firmware = fu_device_read_firmware(device, progress, error); if (firmware == NULL) { g_autoptr(GError) error_local = NULL; if (!fu_device_attach_full(device, progress, &error_local)) g_debug("ignoring attach failure: %s", error_local->message); g_prefix_error(error, "failed to read firmware: "); return FALSE; } fw = fu_firmware_write(firmware, error); if (fw == NULL) { g_autoptr(GError) error_local = NULL; if (!fu_device_attach_full(device, progress, &error_local)) g_debug("ignoring attach failure: %s", error_local->message); g_prefix_error(error, "failed to write firmware: "); return FALSE; } for (guint i = 0; checksum_types[i] != 0; i++) { g_autofree gchar *hash = NULL; hash = g_compute_checksum_for_bytes(checksum_types[i], fw); fu_device_add_checksum(device, hash); } return fu_device_attach_full(device, progress, error); } /** * fu_plugin_runner_startup: * @self: a #FuPlugin * @error: (nullable): optional return location for an error * * Runs the startup routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ gboolean fu_plugin_runner_startup(FuPlugin *self, FuProgress *progress, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); /* progress */ fu_progress_set_name(progress, fu_plugin_get_name(self)); /* be helpful for unit tests */ fu_plugin_runner_init(self); /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* optional */ if (vfuncs->startup != NULL) { g_debug("startup(%s)", fu_plugin_get_name(self)); if (!vfuncs->startup(self, progress, &error_local)) { if (error_local == NULL) { g_critical("unset plugin error in startup(%s)", fu_plugin_get_name(self)); g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to startup using %s: ", fu_plugin_get_name(self)); return FALSE; } } /* success */ return TRUE; } /** * fu_plugin_runner_ready: * @self: a #FuPlugin * @error: (nullable): optional return location for an error * * Runs the ready routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.9.6 **/ gboolean fu_plugin_runner_ready(FuPlugin *self, FuProgress *progress, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); /* progress */ fu_progress_set_name(progress, fu_plugin_get_name(self)); if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; fu_plugin_add_flag(self, FWUPD_PLUGIN_FLAG_READY); if (vfuncs->ready == NULL) return TRUE; /* optional */ g_debug("ready(%s)", fu_plugin_get_name(self)); if (!vfuncs->ready(self, progress, &error_local)) { if (error_local == NULL) { g_critical("unset plugin error in ready(%s)", fu_plugin_get_name(self)); g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to ready using %s: ", fu_plugin_get_name(self)); return FALSE; } /* success */ return TRUE; } /** * fu_plugin_runner_init: * @self: a #FuPlugin * * Runs the constructed routine for the plugin, if enabled. * * Since: 1.8.1 **/ void fu_plugin_runner_init(FuPlugin *self) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); FuPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PLUGIN(self)); /* already done */ if (priv->done_init) return; /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return; /* optional */ if (vfuncs->constructed != NULL) { g_debug("constructed(%s)", fu_plugin_get_name(self)); vfuncs->constructed(G_OBJECT(self)); priv->done_init = TRUE; } } static gboolean fu_plugin_runner_device_generic(FuPlugin *self, FuDevice *device, const gchar *symbol_name, FuPluginDeviceFunc device_func, GError **error) { g_autoptr(GError) error_local = NULL; /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* optional */ if (device_func == NULL) return TRUE; g_debug("%s(%s)", symbol_name + 10, fu_plugin_get_name(self)); if (!device_func(self, device, &error_local)) { if (error_local == NULL) { g_critical("unset plugin error in %s(%s)", fu_plugin_get_name(self), symbol_name + 10); g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to %s using %s: ", symbol_name + 10, fu_plugin_get_name(self)); return FALSE; } return TRUE; } static gboolean fu_plugin_runner_device_generic_progress(FuPlugin *self, FuDevice *device, FuProgress *progress, const gchar *symbol_name, FuPluginDeviceProgressFunc device_func, GError **error) { g_autoptr(GError) error_local = NULL; /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* optional */ if (device_func == NULL) return TRUE; g_debug("%s(%s)", symbol_name + 10, fu_plugin_get_name(self)); if (!device_func(self, device, progress, &error_local)) { if (error_local == NULL) { g_critical("unset plugin error in %s(%s)", fu_plugin_get_name(self), symbol_name + 10); g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to %s using %s: ", symbol_name + 10, fu_plugin_get_name(self)); return FALSE; } return TRUE; } static gboolean fu_plugin_runner_flagged_device_generic(FuPlugin *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, const gchar *symbol_name, FuPluginFlaggedDeviceFunc func, GError **error) { g_autoptr(GError) error_local = NULL; /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* optional */ if (func == NULL) return TRUE; g_debug("%s(%s)", symbol_name + 10, fu_plugin_get_name(self)); if (!func(self, device, progress, flags, &error_local)) { if (error_local == NULL) { g_critical("unset plugin error in %s(%s)", fu_plugin_get_name(self), symbol_name + 10); g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to %s using %s: ", symbol_name + 10, fu_plugin_get_name(self)); return FALSE; } return TRUE; } static gboolean fu_plugin_runner_device_array_generic(FuPlugin *self, GPtrArray *devices, const gchar *symbol_name, FuPluginDeviceArrayFunc func, GError **error) { g_autoptr(GError) error_local = NULL; /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* optional */ if (func == NULL) return TRUE; g_debug("%s(%s)", symbol_name + 10, fu_plugin_get_name(self)); if (!func(self, devices, &error_local)) { if (error_local == NULL) { g_critical("unset plugin error in for %s(%s)", fu_plugin_get_name(self), symbol_name + 10); g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to %s using %s: ", symbol_name + 10, fu_plugin_get_name(self)); return FALSE; } return TRUE; } /** * fu_plugin_runner_coldplug: * @self: a #FuPlugin * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Runs the coldplug routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ gboolean fu_plugin_runner_coldplug(FuPlugin *self, FuProgress *progress, GError **error) { FuPluginPrivate *priv = GET_PRIVATE(self); FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); /* progress */ fu_progress_set_name(progress, fu_plugin_get_name(self)); /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* no HwId */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_REQUIRE_HWID)) return TRUE; /* optional */ if (vfuncs->coldplug == NULL) return TRUE; g_debug("coldplug(%s)", fu_plugin_get_name(self)); if (!vfuncs->coldplug(self, progress, &error_local)) { if (error_local == NULL) { g_critical("unset plugin error in coldplug(%s)", fu_plugin_get_name(self)); g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } /* coldplug failed, but we might have already added devices to the daemon... */ if (priv->devices != NULL) { for (guint i = 0; i < priv->devices->len; i++) { FuDevice *device = g_ptr_array_index(priv->devices, i); g_warning("removing device %s due to failed coldplug", fu_device_get_id(device)); fu_plugin_device_remove(self, device); } } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to coldplug using %s: ", fu_plugin_get_name(self)); return FALSE; } return TRUE; } /** * fu_plugin_runner_composite_prepare: * @self: a #FuPlugin * @devices: (element-type FuDevice): an array of devices * @error: (nullable): optional return location for an error * * Runs the composite_prepare routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.0.9 **/ gboolean fu_plugin_runner_composite_prepare(FuPlugin *self, GPtrArray *devices, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); return fu_plugin_runner_device_array_generic(self, devices, "fu_plugin_composite_prepare", vfuncs->composite_prepare, error); } /** * fu_plugin_runner_composite_cleanup: * @self: a #FuPlugin * @devices: (element-type FuDevice): an array of devices * @error: (nullable): optional return location for an error * * Runs the composite_cleanup routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.0.9 **/ gboolean fu_plugin_runner_composite_cleanup(FuPlugin *self, GPtrArray *devices, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); return fu_plugin_runner_device_array_generic(self, devices, "fu_plugin_composite_cleanup", vfuncs->composite_cleanup, error); } /** * fu_plugin_runner_prepare: * @self: a #FuPlugin * @device: a device * @progress: a #FuProgress * @flags: install flags * @error: (nullable): optional return location for an error * * Runs the update_prepare routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.7.0 **/ gboolean fu_plugin_runner_prepare(FuPlugin *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); return fu_plugin_runner_flagged_device_generic(self, device, progress, flags, "fu_plugin_prepare", vfuncs->prepare, error); } /** * fu_plugin_runner_cleanup: * @self: a #FuPlugin * @device: a device * @progress: a #FuProgress * @flags: install flags * @error: (nullable): optional return location for an error * * Runs the update_cleanup routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.7.0 **/ gboolean fu_plugin_runner_cleanup(FuPlugin *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); return fu_plugin_runner_flagged_device_generic(self, device, progress, flags, "fu_plugin_cleanup", vfuncs->cleanup, error); } /** * fu_plugin_runner_attach: * @self: a #FuPlugin * @device: a device * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Runs the update_attach routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.7.0 **/ gboolean fu_plugin_runner_attach(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); return fu_plugin_runner_device_generic_progress( self, device, progress, "fu_plugin_attach", vfuncs->attach != NULL ? vfuncs->attach : fu_plugin_device_attach, error); } /** * fu_plugin_runner_detach: * @self: a #FuPlugin * @device: a device * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Runs the update_detach routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.7.0 **/ gboolean fu_plugin_runner_detach(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); return fu_plugin_runner_device_generic_progress( self, device, progress, "fu_plugin_detach", vfuncs->detach != NULL ? vfuncs->detach : fu_plugin_device_detach, error); } /** * fu_plugin_runner_reload: * @self: a #FuPlugin * @device: a device * @error: (nullable): optional return location for an error * * Runs reload routine for a device * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.7.0 **/ gboolean fu_plugin_runner_reload(FuPlugin *self, FuDevice *device, GError **error) { FuDevice *proxy = fu_device_get_proxy_with_fallback(device); g_autoptr(FuDeviceLocker) locker = NULL; /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* no object loaded */ locker = fu_device_locker_new(proxy, error); if (locker == NULL) return FALSE; return fu_device_reload(device, error); } /** * fu_plugin_runner_reboot_cleanup: * @self: a #FuPlugin * @device: a device * @error: (nullable): optional return location for an error * * Performs cleanup actions after the reboot has been performed. * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.9.7 **/ gboolean fu_plugin_runner_reboot_cleanup(FuPlugin *self, FuDevice *device, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); /* optional */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; if (vfuncs->reboot_cleanup == NULL) return TRUE; g_debug("reboot_cleanup(%s)", fu_plugin_get_name(self)); return vfuncs->reboot_cleanup(self, device, error); } /** * fu_plugin_runner_add_security_attrs: * @self: a #FuPlugin * @attrs: a security attribute * * Runs the `add_security_attrs()` routine for the plugin * * Since: 1.5.0 **/ void fu_plugin_runner_add_security_attrs(FuPlugin *self, FuSecurityAttrs *attrs) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); /* optional, but gets called even for disabled plugins */ if (vfuncs->add_security_attrs == NULL) return; g_debug("add_security_attrs(%s)", fu_plugin_get_name(self)); vfuncs->add_security_attrs(self, attrs); } /** * fu_plugin_add_device_gtype: * @self: a #FuPlugin * @device_gtype: a #GType, e.g. `FU_TYPE_DEVICE` * * Adds the device #GType which is used when creating devices. * * If this method is used then fu_plugin_backend_device_added() is not called, and * instead the object is created in the daemon for the plugin. * * Plugins can use this method only in fu_plugin_init() * * See also: fu_plugin_set_device_gtype_default() * * Since: 1.6.0 **/ void fu_plugin_add_device_gtype(FuPlugin *self, GType device_gtype) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PLUGIN(self)); /* create as required */ if (priv->device_gtypes == NULL) priv->device_gtypes = g_array_new(FALSE, FALSE, sizeof(GType)); /* check for duplicates */ for (guint i = 0; i < priv->device_gtypes->len; i++) { GType device_gtype_tmp = g_array_index(priv->device_gtypes, GType, i); if (device_gtype == device_gtype_tmp) return; } /* ensure (to allow quirks to use it) then add */ g_type_ensure(device_gtype); g_array_append_val(priv->device_gtypes, device_gtype); } /** * fu_plugin_get_device_gtype_default: * @self: a #FuPlugin * * Gets the default device #GType. * * If there is only one possible #GType added from fu_plugin_add_device_gtype() it will also be * returned here. * * Returns: a #GType, or %G_TYPE_INVALID on error * * Since: 1.9.14 **/ GType fu_plugin_get_device_gtype_default(FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_PLUGIN(self), G_TYPE_INVALID); if (priv->device_gtype_default != G_TYPE_INVALID) return priv->device_gtype_default; if (priv->device_gtypes->len == 1) return g_array_index(priv->device_gtypes, GType, 0); return G_TYPE_INVALID; } /** * fu_plugin_set_device_gtype_default: * @self: a #FuPlugin * @device_gtype: a #GType, e.g. `FU_TYPE_DEVICE` * * Sets the default device #GType. * * This will also add the device #GType using fu_plugin_add_device_gtype(). * * Plugins can use this method only in fu_plugin_init() * * Since: 1.9.14 **/ void fu_plugin_set_device_gtype_default(FuPlugin *self, GType device_gtype) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PLUGIN(self)); fu_plugin_add_device_gtype(self, device_gtype); priv->device_gtype_default = device_gtype; } static gchar * fu_common_string_uncamelcase(const gchar *str) { GString *tmp = g_string_new(NULL); for (guint i = 0; str[i] != '\0'; i++) { if (g_ascii_islower(str[i]) || g_ascii_isdigit(str[i])) { g_string_append_c(tmp, str[i]); continue; } if (i > 0) g_string_append_c(tmp, '-'); g_string_append_c(tmp, g_ascii_tolower(str[i])); } return g_string_free(tmp, FALSE); } static gboolean fu_plugin_check_amdgpu_dpaux(FuPlugin *self, GError **error) { #ifdef __linux__ gsize bufsz = 0; g_autofree gchar *buf = NULL; g_auto(GStrv) lines = NULL; /* no module support in the kernel, we can't test for amdgpu module */ if (!g_file_test("/proc/modules", G_FILE_TEST_EXISTS)) return TRUE; if (!g_file_get_contents("/proc/modules", &buf, &bufsz, error)) return FALSE; lines = g_strsplit(buf, "\n", -1); for (guint i = 0; lines[i] != NULL; i++) { if (g_str_has_prefix(lines[i], "amdgpu ")) { /* released 2019! */ return fu_kernel_check_version("5.2.0", error); } } #endif return TRUE; } /** * fu_plugin_add_device_udev_subsystem: * @self: a #FuPlugin * @subsystem: a subsystem name, e.g. `pciport` * * Add this plugin as a possible handler of devices with this udev subsystem. * Use fu_plugin_add_udev_subsystem() if you just want to ensure the subsystem is watched. * * Plugins can use this method only in fu_plugin_init() * * Since: 1.9.3 **/ void fu_plugin_add_device_udev_subsystem(FuPlugin *self, const gchar *subsystem) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PLUGIN(self)); g_return_if_fail(subsystem != NULL); /* see https://github.com/fwupd/fwupd/issues/1121 for more details */ if (g_strcmp0(subsystem, "drm_dp_aux_dev") == 0) { g_autoptr(GError) error = NULL; if (!fu_plugin_check_amdgpu_dpaux(self, &error)) { g_warning("failed to add subsystem: %s", error->message); fu_plugin_add_flag(self, FWUPD_PLUGIN_FLAG_DISABLED); fu_plugin_add_flag(self, FWUPD_PLUGIN_FLAG_KERNEL_TOO_OLD); return; } } /* proxy */ fu_context_add_udev_subsystem(priv->ctx, subsystem, fu_plugin_get_name(self)); } /** * fu_plugin_add_udev_subsystem: * @self: a #FuPlugin * @subsystem: a subsystem name, e.g. `pciport` * * Registers the udev subsystem to be watched by the daemon. * * Plugins can use this method only in fu_plugin_init() * * Since: 1.6.2 **/ void fu_plugin_add_udev_subsystem(FuPlugin *self, const gchar *subsystem) { FuPluginPrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_PLUGIN(self)); g_return_if_fail(subsystem != NULL); /* proxy */ fu_context_add_udev_subsystem(priv->ctx, subsystem, NULL); } /** * fu_plugin_add_firmware_gtype: * @self: a #FuPlugin * @id: (nullable): an optional string describing the type, e.g. `ihex` * @gtype: a #GType e.g. `FU_TYPE_FOO_FIRMWARE` * * Adds a firmware #GType which is used when creating devices. If @id is not * specified then it is guessed using the #GType name. * * Plugins can use this method only in fu_plugin_init() * * Since: 1.3.3 **/ void fu_plugin_add_firmware_gtype(FuPlugin *self, const gchar *id, GType gtype) { FuPluginPrivate *priv = GET_PRIVATE(self); g_autofree gchar *id_safe = NULL; if (id != NULL) { id_safe = g_strdup(id); } else { g_autoptr(GString) str = g_string_new(g_type_name(gtype)); if (g_str_has_prefix(str->str, "Fu")) g_string_erase(str, 0, 2); g_string_replace(str, "Firmware", "", 1); id_safe = fu_common_string_uncamelcase(str->str); } fu_context_add_firmware_gtype(priv->ctx, id_safe, gtype); } static gboolean fu_plugin_check_supported_device(FuPlugin *self, FuDevice *device) { GPtrArray *instance_ids = fu_device_get_instance_ids(device); for (guint i = 0; i < instance_ids->len; i++) { const gchar *instance_id = g_ptr_array_index(instance_ids, i); g_autofree gchar *guid = fwupd_guid_hash_string(instance_id); if (fu_plugin_check_supported(self, guid)) return TRUE; } return FALSE; } static gboolean fu_plugin_backend_device_added(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) { FuDevice *proxy; FuPluginPrivate *priv = GET_PRIVATE(self); GType device_gtype = fu_device_get_specialized_gtype(FU_DEVICE(device)); GType proxy_gtype = fu_device_get_proxy_gtype(FU_DEVICE(device)); g_autoptr(FuDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 4, "created"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 48, "open"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 48, "add"); /* fall back to plugin default */ if (device_gtype == G_TYPE_INVALID) device_gtype = fu_plugin_get_device_gtype_default(self); if (device_gtype == G_TYPE_INVALID) { if (priv->device_gtypes->len > 1) { g_autoptr(GString) str = g_string_new(NULL); for (guint i = 0; i < priv->device_gtypes->len; i++) { device_gtype = g_array_index(priv->device_gtypes, GType, i); if (str->len > 0) g_string_append(str, ","); g_string_append(str, g_type_name(device_gtype)); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "too many GTypes to choose a default, got: %s", str->str); return FALSE; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no possible device GTypes"); return FALSE; } /* create new device and incorporate existing properties */ dev = g_object_new(device_gtype, "context", priv->ctx, NULL); fu_device_incorporate(dev, FU_DEVICE(device)); /* any proxy device to create too? */ if (proxy_gtype != G_TYPE_INVALID) { g_autoptr(FuDevice) proxy_tmp = g_object_new(proxy_gtype, "context", priv->ctx, NULL); fu_device_incorporate(proxy_tmp, device); fu_device_add_internal_flag(dev, FU_DEVICE_INTERNAL_FLAG_REFCOUNTED_PROXY); fu_device_set_proxy(dev, proxy_tmp); } /* notify plugins */ if (!fu_plugin_runner_device_created(self, dev, error)) return FALSE; fu_progress_step_done(progress); /* there are a lot of different devices that match, but not all respond * well to opening -- so limit some ones with issued updates */ if (fu_device_has_internal_flag(dev, FU_DEVICE_INTERNAL_FLAG_ONLY_SUPPORTED)) { if (!fu_device_probe(dev, error)) return FALSE; fu_device_convert_instance_ids(dev); if (!fu_plugin_check_supported_device(self, dev)) { GPtrArray *guids = fu_device_get_guids(dev); g_autofree gchar *guids_str = fu_strjoin(",", guids); g_info("%s has no updates, so ignoring device", guids_str); fu_progress_finished(progress); return TRUE; } } /* open */ proxy = fu_device_get_proxy(dev); if (proxy != NULL) { g_autoptr(FuDeviceLocker) locker_proxy = NULL; locker_proxy = fu_device_locker_new(proxy, error); if (locker_proxy == NULL) return FALSE; fu_device_incorporate(dev, proxy); } locker = fu_device_locker_new(dev, error); if (locker == NULL) return FALSE; fu_progress_step_done(progress); /* add */ fu_plugin_device_add(self, dev); fu_plugin_runner_device_added(self, dev); fu_progress_step_done(progress); return TRUE; } /** * fu_plugin_runner_backend_device_added: * @self: a #FuPlugin * @device: a device * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Call the backend_device_added routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.5.6 **/ gboolean fu_plugin_runner_backend_device_added(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) { FuPluginPrivate *priv = GET_PRIVATE(self); FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(FU_IS_DEVICE(device), FALSE); g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* optional */ if (vfuncs->backend_device_added == NULL) { if (priv->device_gtypes != NULL || fu_device_get_specialized_gtype(device) != G_TYPE_INVALID) { return fu_plugin_backend_device_added(self, device, progress, error); } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "No device GType set"); return FALSE; } g_debug("backend_device_added(%s)", fu_plugin_get_name(self)); if (!vfuncs->backend_device_added(self, device, progress, &error_local)) { if (error_local == NULL) { g_critical("unset plugin error in backend_device_added(%s)", fu_plugin_get_name(self)); g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to add device using on %s: ", fu_plugin_get_name(self)); return FALSE; } return TRUE; } /** * fu_plugin_runner_backend_device_changed: * @self: a #FuPlugin * @device: a device * @error: (nullable): optional return location for an error * * Call the backend_device_changed routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.5.6 **/ gboolean fu_plugin_runner_backend_device_changed(FuPlugin *self, FuDevice *device, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(FU_IS_DEVICE(device), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* optional */ if (vfuncs->backend_device_changed == NULL) return TRUE; g_debug("udev_device_changed(%s)", fu_plugin_get_name(self)); if (!vfuncs->backend_device_changed(self, device, &error_local)) { if (error_local == NULL) { g_critical("unset plugin error in udev_device_changed(%s)", fu_plugin_get_name(self)); g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to change device on %s: ", fu_plugin_get_name(self)); return FALSE; } return TRUE; } /** * fu_plugin_runner_device_added: * @self: a #FuPlugin * @device: a device * * Call the device_added routine for the plugin * * Since: 1.5.0 **/ void fu_plugin_runner_device_added(FuPlugin *self, FuDevice *device) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return; /* optional */ if (vfuncs->device_added == NULL) return; g_debug("fu_plugin_device_added(%s)", fu_plugin_get_name(self)); vfuncs->device_added(self, device); } /** * fu_plugin_runner_device_removed: * @self: a #FuPlugin * @device: a device * * Call the device_removed routine for the plugin * * Since: 1.1.2 **/ void fu_plugin_runner_device_removed(FuPlugin *self, FuDevice *device) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_autoptr(GError) error_local = NULL; if (!fu_plugin_runner_device_generic(self, device, "fu_plugin_backend_device_removed", vfuncs->backend_device_removed, &error_local)) g_warning("%s", error_local->message); } /** * fu_plugin_runner_device_register: * @self: a #FuPlugin * @device: a device * * Call the device_registered routine for the plugin * * Since: 0.9.7 **/ void fu_plugin_runner_device_register(FuPlugin *self, FuDevice *device) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return; /* optional */ if (vfuncs->device_registered != NULL) { g_debug("fu_plugin_device_registered(%s)", fu_plugin_get_name(self)); vfuncs->device_registered(self, device); } } /** * fu_plugin_runner_device_created: * @self: a #FuPlugin * @device: a device * @error: (nullable): optional return location for an error * * Call the device_created routine for the plugin * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.4.0 **/ gboolean fu_plugin_runner_device_created(FuPlugin *self, FuDevice *device, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(FU_IS_DEVICE(device), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* optional */ if (vfuncs->device_created == NULL) return TRUE; g_debug("fu_plugin_device_created(%s)", fu_plugin_get_name(self)); return vfuncs->device_created(self, device, error); } /** * fu_plugin_runner_verify: * @self: a #FuPlugin * @device: a device * @progress: a #FuProgress * @flags: verify flags * @error: (nullable): optional return location for an error * * Call into the plugin's verify routine * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ gboolean fu_plugin_runner_verify(FuPlugin *self, FuDevice *device, FuProgress *progress, FuPluginVerifyFlags flags, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); GPtrArray *checksums; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(FU_IS_DEVICE(device), FALSE); g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* optional */ if (vfuncs->verify == NULL) { if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_CAN_VERIFY)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device %s does not support verification", fu_device_get_id(device)); return FALSE; } return fu_plugin_device_read_firmware(self, device, progress, error); } /* clear any existing verification checksums */ checksums = fu_device_get_checksums(device); g_ptr_array_set_size(checksums, 0); /* run additional detach */ if (!fu_plugin_runner_device_generic_progress( self, device, progress, "fu_plugin_detach", vfuncs->detach != NULL ? vfuncs->detach : fu_plugin_device_detach, error)) return FALSE; /* run vfunc */ g_debug("verify(%s)", fu_plugin_get_name(self)); if (!vfuncs->verify(self, device, progress, flags, &error_local)) { g_autoptr(GError) error_attach = NULL; if (error_local == NULL) { g_critical("unset plugin error in verify(%s)", fu_plugin_get_name(self)); g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to verify using %s: ", fu_plugin_get_name(self)); /* make the device "work" again, but don't prefix the error */ if (!fu_plugin_runner_device_generic_progress( self, device, progress, "fu_plugin_attach", vfuncs->attach != NULL ? vfuncs->attach : fu_plugin_device_attach, &error_attach)) { g_warning("failed to attach whilst aborting verify(): %s", error_attach->message); } return FALSE; } /* run optional attach */ if (!fu_plugin_runner_device_generic_progress( self, device, progress, "fu_plugin_attach", vfuncs->attach != NULL ? vfuncs->attach : fu_plugin_device_attach, error)) return FALSE; /* success */ return TRUE; } /** * fu_plugin_runner_activate: * @self: a #FuPlugin * @device: a device * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Call into the plugin's activate routine * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.2.6 **/ gboolean fu_plugin_runner_activate(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); guint64 flags; g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(FU_IS_DEVICE(device), FALSE); g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* final check */ flags = fu_device_get_flags(device); if ((flags & FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device %s does not need activation", fu_device_get_id(device)); return FALSE; } /* run vfunc */ if (!fu_plugin_runner_device_generic_progress( self, device, progress, "fu_plugin_activate", vfuncs->activate != NULL ? vfuncs->activate : fu_plugin_device_activate, error)) return FALSE; /* update with correct flags */ fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); fu_device_set_modified(device, (guint64)g_get_real_time() / G_USEC_PER_SEC); return TRUE; } /** * fu_plugin_runner_unlock: * @self: a #FuPlugin * @device: a device * @error: (nullable): optional return location for an error * * Call into the plugin's unlock routine * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ gboolean fu_plugin_runner_unlock(FuPlugin *self, FuDevice *device, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); guint64 flags; g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(FU_IS_DEVICE(device), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* final check */ flags = fu_device_get_flags(device); if ((flags & FWUPD_DEVICE_FLAG_LOCKED) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device %s is not locked", fu_device_get_id(device)); return FALSE; } /* run vfunc */ if (!fu_plugin_runner_device_generic(self, device, "fu_plugin_unlock", vfuncs->unlock, error)) return FALSE; /* update with correct flags */ fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_LOCKED); fu_device_set_modified(device, (guint64)g_get_real_time() / G_USEC_PER_SEC); return TRUE; } /** * fu_plugin_runner_write_firmware: * @self: a #FuPlugin * @device: a device * @blob_fw: a data blob * @progress: a #FuProgress * @flags: install flags * @error: (nullable): optional return location for an error * * Call into the plugin's write firmware routine * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.7.0 **/ gboolean fu_plugin_runner_write_firmware(FuPlugin *self, FuDevice *device, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(FU_IS_DEVICE(device), FALSE); g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) { g_debug("plugin not enabled, skipping"); return TRUE; } /* optional */ if (vfuncs->write_firmware == NULL) { g_debug("superclassed write_firmware(%s)", fu_plugin_get_name(self)); return fu_plugin_device_write_firmware(self, device, blob_fw, progress, flags, error); } /* online */ if (!vfuncs->write_firmware(self, device, blob_fw, progress, flags, &error_local)) { if (error_local == NULL) { g_critical("unset plugin error in update(%s)", fu_plugin_get_name(self)); g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); return FALSE; } fu_device_set_update_error(device, error_local->message); g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* no longer valid */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) { GPtrArray *checksums = fu_device_get_checksums(device); g_ptr_array_set_size(checksums, 0); } /* success */ return TRUE; } /** * fu_plugin_runner_clear_results: * @self: a #FuPlugin * @device: a device * @error: (nullable): optional return location for an error * * Call into the plugin's clear results routine * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ gboolean fu_plugin_runner_clear_results(FuPlugin *self, FuDevice *device, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(FU_IS_DEVICE(device), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* optional */ if (vfuncs->clear_results == NULL) return TRUE; g_debug("clear_result(%s)", fu_plugin_get_name(self)); if (!vfuncs->clear_results(self, device, &error_local)) { if (error_local == NULL) { g_critical("unset plugin error in clear_result(%s)", fu_plugin_get_name(self)); g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to clear_result using %s: ", fu_plugin_get_name(self)); return FALSE; } return TRUE; } /** * fu_plugin_runner_get_results: * @self: a #FuPlugin * @device: a device * @error: (nullable): optional return location for an error * * Call into the plugin's get results routine * * Returns: #TRUE for success, #FALSE for failure * * Since: 0.8.0 **/ gboolean fu_plugin_runner_get_results(FuPlugin *self, FuDevice *device, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(FU_IS_DEVICE(device), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not enabled */ if (fu_plugin_has_flag(self, FWUPD_PLUGIN_FLAG_DISABLED)) return TRUE; /* optional */ if (vfuncs->get_results == NULL) { g_debug("superclassed get_results(%s)", fu_plugin_get_name(self)); return fu_plugin_device_get_results(self, device, error); } g_debug("get_results(%s)", fu_plugin_get_name(self)); if (!vfuncs->get_results(self, device, &error_local)) { if (error_local == NULL) { g_critical("unset plugin error in get_results(%s)", fu_plugin_get_name(self)); g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified error"); } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to get_results using %s: ", fu_plugin_get_name(self)); return FALSE; } return TRUE; } /** * fu_plugin_runner_fix_host_security_attr: * @self: a #FuPlugin * @attr: (nullable): a security attribute * @error: (nullable): optional return location for an error * * Fix the specific security attribute. * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.9.6 **/ gboolean fu_plugin_runner_fix_host_security_attr(FuPlugin *self, FwupdSecurityAttr *attr, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (vfuncs->fix_host_security_attr == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "fix is not supported"); return FALSE; } return vfuncs->fix_host_security_attr(self, attr, error); } /** * fu_plugin_runner_undo_host_security_attr: * @self: a #FuPlugin * @attr: (nullable): a security attribute * @error: (nullable): optional return location for an error * * Fix the security issue of given security attribute. * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.9.6 **/ gboolean fu_plugin_runner_undo_host_security_attr(FuPlugin *self, FwupdSecurityAttr *attr, GError **error) { FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (vfuncs->undo_host_security_attr == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "undo is not supported"); return FALSE; } return vfuncs->undo_host_security_attr(self, attr, error); } /** * fu_plugin_get_order: * @self: a #FuPlugin * * Gets the plugin order, where higher numbers are run after lower * numbers. * * Returns: the integer value * * Since: 1.0.0 **/ guint fu_plugin_get_order(FuPlugin *self) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); return priv->order; } /** * fu_plugin_set_order: * @self: a #FuPlugin * @order: an integer value * * Sets the plugin order, where higher numbers are run after lower * numbers. * * Since: 1.0.0 **/ void fu_plugin_set_order(FuPlugin *self, guint order) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); priv->order = order; } /** * fu_plugin_get_priority: * @self: a #FuPlugin * * Gets the plugin priority, where higher numbers are better. * * Returns: the integer value * * Since: 1.1.1 **/ guint fu_plugin_get_priority(FuPlugin *self) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); return priv->priority; } /** * fu_plugin_set_priority: * @self: a #FuPlugin * @priority: an integer value * * Sets the plugin priority, where higher numbers are better. * * Since: 1.0.0 **/ void fu_plugin_set_priority(FuPlugin *self, guint priority) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); priv->priority = priority; } /** * fu_plugin_add_rule: * @self: a #FuPlugin * @rule: a plugin rule, e.g. %FU_PLUGIN_RULE_CONFLICTS * @name: a plugin name, e.g. `upower` * * If the plugin name is found, the rule will be used to sort the plugin list, * for example the plugin specified by @name will be ordered after this plugin * when %FU_PLUGIN_RULE_RUN_AFTER is used. * * NOTE: The depsolver is iterative and may not solve overly-complicated rules; * If depsolving fails then fwupd will not start. * * Since: 1.0.0 **/ void fu_plugin_add_rule(FuPlugin *self, FuPluginRule rule, const gchar *name) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); if (priv->rules[rule] == NULL) priv->rules[rule] = g_ptr_array_new_with_free_func(g_free); g_ptr_array_add(priv->rules[rule], g_strdup(name)); g_signal_emit(self, signals[SIGNAL_RULES_CHANGED], 0); } /** * fu_plugin_get_rules: * @self: a #FuPlugin * @rule: a plugin rule, e.g. %FU_PLUGIN_RULE_CONFLICTS * * Gets the plugin IDs that should be run after this plugin. * * Returns: (element-type utf8) (transfer none) (nullable): the list of plugin names, e.g. *`['appstream']` * * Since: 1.0.0 **/ GPtrArray * fu_plugin_get_rules(FuPlugin *self, FuPluginRule rule) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); g_return_val_if_fail(rule < FU_PLUGIN_RULE_LAST, NULL); return priv->rules[rule]; } /** * fu_plugin_has_rule: * @self: a #FuPlugin * @rule: a plugin rule, e.g. %FU_PLUGIN_RULE_CONFLICTS * @name: a plugin name, e.g. `upower` * * Gets the plugin IDs that should be run after this plugin. * * Returns: %TRUE if the name exists for the specific rule * * Since: 1.0.0 **/ gboolean fu_plugin_has_rule(FuPlugin *self, FuPluginRule rule, const gchar *name) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); if (priv->rules[rule] == NULL) return FALSE; for (guint i = 0; i < priv->rules[rule]->len; i++) { const gchar *tmp = g_ptr_array_index(priv->rules[rule], i); if (g_strcmp0(tmp, name) == 0) return TRUE; } return FALSE; } /** * fu_plugin_add_report_metadata: * @self: a #FuPlugin * @key: a string, e.g. `FwupdateVersion` * @value: a string, e.g. `10` * * Sets any additional metadata to be included in the firmware report to aid * debugging problems. * * Any data included here will be sent to the metadata server after user * confirmation. * * Since: 1.0.4 **/ void fu_plugin_add_report_metadata(FuPlugin *self, const gchar *key, const gchar *value) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); if (priv->report_metadata == NULL) { priv->report_metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); } g_hash_table_insert(priv->report_metadata, g_strdup(key), g_strdup(value)); } /** * fu_plugin_get_report_metadata: * @self: a #FuPlugin * * Returns the list of additional metadata to be added when filing a report. * * Returns: (transfer none) (nullable): the map of report metadata * * Since: 1.0.4 **/ GHashTable * fu_plugin_get_report_metadata(FuPlugin *self) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); return priv->report_metadata; } /** * fu_plugin_get_config_value: * @self: a #FuPlugin * @key: a settings key * * Return the value of a key, falling back to the default value. * * Since: 1.0.6 **/ gchar * fu_plugin_get_config_value(FuPlugin *self, const gchar *key) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); FuConfig *config = fu_context_get_config(priv->ctx); if (config == NULL) { g_critical("cannot get config value with no loaded context!"); return NULL; } return fu_config_get_value(config, fu_plugin_get_name(self), key); } /** * fu_plugin_set_config_default: * @self: a #FuPlugin * @key: a settings key * @value: the default value of the key if not found * * Sets the config default value. * * Since: 2.0.0 **/ void fu_plugin_set_config_default(FuPlugin *self, const gchar *key, const gchar *value) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); FuConfig *config = fu_context_get_config(priv->ctx); if (config == NULL) { g_critical("cannot set config default with no loaded context!"); return; } fu_config_set_default(config, fu_plugin_get_name(self), key, value); } /** * fu_plugin_security_attr_new: * @self: a #FuPlugin * @appstream_id: (nullable): the AppStream component ID, e.g. `com.intel.BiosGuard` * * Creates a new #FwupdSecurityAttr for this specific plugin. * * Returns: (transfer full): a #FwupdSecurityAttr * * Since: 1.8.4 **/ FwupdSecurityAttr * fu_plugin_security_attr_new(FuPlugin *self, const gchar *appstream_id) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); g_autoptr(FwupdSecurityAttr) attr = NULL; g_return_val_if_fail(FU_IS_PLUGIN(self), NULL); g_return_val_if_fail(appstream_id != NULL, NULL); attr = fu_security_attr_new(priv->ctx, appstream_id); fwupd_security_attr_set_plugin(attr, fu_plugin_get_name(self)); return g_steal_pointer(&attr); } #if !GLIB_CHECK_VERSION(2, 66, 0) #define G_FILE_SET_CONTENTS_CONSISTENT 0 typedef guint GFileSetContentsFlags; static gboolean g_file_set_contents_full(const gchar *filename, const gchar *contents, gssize length, GFileSetContentsFlags flags, int mode, GError **error) { gint fd; gssize wrote; if (length < 0) length = strlen(contents); fd = g_open(filename, O_CREAT, mode); if (fd < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "could not open %s file", filename); return FALSE; } wrote = write(fd, contents, length); if (wrote != length) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "did not write %s file", filename); g_close(fd, NULL); return FALSE; } return g_close(fd, error); } #endif /** * fu_plugin_set_config_value: * @self: a #FuPlugin * @key: a settings key * @value: (nullable): a settings value * @error: (nullable): optional return location for an error * * Sets a plugin config value. * * Returns: %TRUE for success * * Since: 1.7.0 **/ gboolean fu_plugin_set_config_value(FuPlugin *self, const gchar *key, const gchar *value, GError **error) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); FuConfig *config = fu_context_get_config(priv->ctx); g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (config == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot get config value with no loaded context"); return FALSE; } return fu_config_set_value(config, fu_plugin_get_name(self), key, value, error); } /** * fu_plugin_reset_config_values: * @self: a #FuPlugin * @error: (nullable): optional return location for an error * * Reset all the plugin keys back to the default values. * * Returns: %TRUE for success * * Since: 1.9.15 **/ gboolean fu_plugin_reset_config_values(FuPlugin *self, GError **error) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); FuConfig *config = fu_context_get_config(priv->ctx); g_return_val_if_fail(FU_IS_PLUGIN(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (config == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot reset config values with no loaded context"); return FALSE; } return fu_config_reset_defaults(config, fu_plugin_get_name(self), error); } /** * fu_plugin_get_config_value_boolean: * @self: a #FuPlugin * @key: a settings key * * Return the boolean value of a key if it's been configured * * Returns: %TRUE if the value is `true` (case insensitive), %FALSE otherwise * * Since: 1.4.0 **/ gboolean fu_plugin_get_config_value_boolean(FuPlugin *self, const gchar *key) { FuPluginPrivate *priv = fu_plugin_get_instance_private(self); FuConfig *config = fu_context_get_config(priv->ctx); if (config == NULL) { g_critical("cannot get config value with no loaded context!"); return FALSE; } return fu_config_get_value_bool(config, fu_plugin_get_name(self), key); } /** * fu_plugin_name_compare: * @plugin1: first #FuPlugin to compare. * @plugin2: second #FuPlugin to compare. * * Compares two plugins by their names. * * Returns: 1, 0 or -1 if @plugin1 is greater, equal, or less than @plugin2. * * Since: 1.0.8 **/ gint fu_plugin_name_compare(FuPlugin *plugin1, FuPlugin *plugin2) { return g_strcmp0(fu_plugin_get_name(plugin1), fu_plugin_get_name(plugin2)); } /** * fu_plugin_order_compare: * @plugin1: first #FuPlugin to compare. * @plugin2: second #FuPlugin to compare. * * Compares two plugins by their depsolved order, and then by name. * * Returns: 1, 0 or -1 if @plugin1 is greater, equal, or less than @plugin2. * * Since: 1.0.8 **/ gint fu_plugin_order_compare(FuPlugin *plugin1, FuPlugin *plugin2) { FuPluginPrivate *priv1 = fu_plugin_get_instance_private(plugin1); FuPluginPrivate *priv2 = fu_plugin_get_instance_private(plugin2); if (priv1->order < priv2->order) return -1; if (priv1->order > priv2->order) return 1; return fu_plugin_name_compare(plugin1, plugin2); } static gchar * fu_plugin_convert_gtype_to_name(GType gtype) { const gchar *gtype_name = g_type_name(gtype); gsize len = strlen(gtype_name); g_autoptr(GString) str = g_string_new(NULL); g_return_val_if_fail(g_str_has_prefix(gtype_name, "Fu"), NULL); g_return_val_if_fail(g_str_has_suffix(gtype_name, "Plugin"), NULL); /* self tests */ if (g_strcmp0(gtype_name, "FuPlugin") == 0) return g_strdup("plugin"); /* normal plugins */ for (guint j = 2; j < len - 6; j++) { gchar tmp = gtype_name[j]; if (g_ascii_isupper(tmp)) { if (str->len > 0) g_string_append_c(str, '_'); g_string_append_c(str, g_ascii_tolower(tmp)); } else { g_string_append_c(str, tmp); } } if (str->len == 0) return NULL; return g_string_free(g_steal_pointer(&str), FALSE); } static void fu_plugin_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuPlugin *self = FU_PLUGIN(object); FuPluginPrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_CONTEXT: g_value_set_object(value, priv->ctx); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_plugin_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuPlugin *self = FU_PLUGIN(object); switch (prop_id) { case PROP_CONTEXT: fu_plugin_set_context(self, g_value_get_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_plugin_class_init(FuPluginClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->finalize = fu_plugin_finalize; object_class->get_property = fu_plugin_get_property; object_class->set_property = fu_plugin_set_property; /** * FuPlugin::device-added: * @self: the #FuPlugin instance that emitted the signal * @device: the #FuDevice * * The ::device-added signal is emitted when a device has been added by the plugin. * * Since: 0.8.0 **/ signals[SIGNAL_DEVICE_ADDED] = g_signal_new("device-added", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FuPluginClass, _device_added), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuPlugin::device-removed: * @self: the #FuPlugin instance that emitted the signal * @device: the #FuDevice * * The ::device-removed signal is emitted when a device has been removed by the plugin. * * Since: 0.8.0 **/ signals[SIGNAL_DEVICE_REMOVED] = g_signal_new("device-removed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FuPluginClass, _device_removed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuPlugin::device-register: * @self: the #FuPlugin instance that emitted the signal * @device: the #FuDevice * * The ::device-register signal is emitted when another plugin has added the device. * * Since: 0.9.7 **/ signals[SIGNAL_DEVICE_REGISTER] = g_signal_new("device-register", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FuPluginClass, _device_register), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuPlugin::check-supported: * @self: the #FuPlugin instance that emitted the signal * @guid: a device GUID * * The ::check-supported signal is emitted when a plugin wants to ask the daemon if a * specific device GUID is supported in the existing system metadata. * * Returns: %TRUE if the GUID is found * * Since: 1.0.0 **/ signals[SIGNAL_CHECK_SUPPORTED] = g_signal_new("check-supported", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FuPluginClass, _check_supported), NULL, NULL, g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 1, G_TYPE_STRING); signals[SIGNAL_RULES_CHANGED] = g_signal_new("rules-changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(FuPluginClass, _rules_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * FuPlugin:context: * * The #FuContext to use. * * Since: 1.8.6 */ pspec = g_param_spec_object("context", NULL, NULL, FU_TYPE_CONTEXT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_CONTEXT, pspec); } static void fu_plugin_init(FuPlugin *self) { FuPluginPrivate *priv = GET_PRIVATE(self); priv->device_gtype_default = G_TYPE_INVALID; } static void fu_plugin_finalize(GObject *object) { FuPlugin *self = FU_PLUGIN(object); FuPluginPrivate *priv = GET_PRIVATE(self); FuPluginVfuncs *vfuncs = fu_plugin_get_vfuncs(self); /* optional */ if (priv->done_init && vfuncs->finalize != NULL) { g_debug("finalize(%s)", fu_plugin_get_name(self)); vfuncs->finalize(G_OBJECT(self)); } for (guint i = 0; i < FU_PLUGIN_RULE_LAST; i++) { if (priv->rules[i] != NULL) g_ptr_array_unref(priv->rules[i]); } if (priv->devices != NULL) g_ptr_array_unref(priv->devices); if (priv->ctx != NULL) g_object_unref(priv->ctx); if (priv->runtime_versions != NULL) g_hash_table_unref(priv->runtime_versions); if (priv->compile_versions != NULL) g_hash_table_unref(priv->compile_versions); if (priv->report_metadata != NULL) g_hash_table_unref(priv->report_metadata); if (priv->cache != NULL) g_hash_table_unref(priv->cache); if (priv->device_gtypes != NULL) g_array_unref(priv->device_gtypes); if (priv->config_monitor != NULL) g_object_unref(priv->config_monitor); g_free(priv->data); G_OBJECT_CLASS(fu_plugin_parent_class)->finalize(object); } /** * fu_plugin_new_from_gtype: * @ctx: (nullable): a #FuContext * @gtype: a #GType, possibly even `G_TYPE_PLUGIN` * * Creates a new #FuPlugin * * Since: 1.8.6 **/ FuPlugin * fu_plugin_new_from_gtype(GType gtype, FuContext *ctx) { FuPlugin *self; g_return_val_if_fail(gtype != G_TYPE_INVALID, NULL); g_return_val_if_fail(ctx == NULL || FU_IS_CONTEXT(ctx), NULL); self = g_object_new(gtype, "context", ctx, NULL); if (fu_plugin_get_name(self) == NULL) { g_autofree gchar *name = fu_plugin_convert_gtype_to_name(gtype); fu_plugin_set_name(self, name); } return self; } /** * fu_plugin_new: * @ctx: (nullable): a #FuContext * * Creates a new #FuPlugin * * Since: 0.8.0 **/ FuPlugin * fu_plugin_new(FuContext *ctx) { FuPlugin *self = FU_PLUGIN(g_object_new(FU_TYPE_PLUGIN, NULL)); if (ctx != NULL) fu_plugin_set_context(self, ctx); return self; } fwupd-1.9.16/libfwupdplugin/fu-plugin.h000066400000000000000000000344471460375044200200700ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #ifdef HAVE_GUSB #include #endif #include "fu-bluez-device.h" #include "fu-common-guid.h" #include "fu-common.h" #include "fu-context.h" #include "fu-device-locker.h" #include "fu-device.h" #include "fu-plugin.h" #include "fu-quirks.h" #include "fu-security-attrs.h" #include "fu-usb-device.h" #include "fu-version-common.h" //#include "fu-hid-device.h" #ifdef HAVE_GUDEV #include "fu-udev-device.h" #endif #include #include /* only until HSI is declared stable */ #include "fwupd-security-attr-private.h" #define FU_TYPE_PLUGIN (fu_plugin_get_type()) G_DECLARE_DERIVABLE_TYPE(FuPlugin, fu_plugin, FU, PLUGIN, FwupdPlugin) #define fu_plugin_get_flags(p) fwupd_plugin_get_flags(FWUPD_PLUGIN(p)) #define fu_plugin_has_flag(p, f) fwupd_plugin_has_flag(FWUPD_PLUGIN(p), f) #define fu_plugin_add_flag(p, f) fwupd_plugin_add_flag(FWUPD_PLUGIN(p), f) #define fu_plugin_remove_flag(p, f) fwupd_plugin_remove_flag(FWUPD_PLUGIN(p), f) /** * FuPluginVerifyFlags: * @FU_PLUGIN_VERIFY_FLAG_NONE: No flags set * * Flags used when verifying, currently unused. **/ typedef enum { FU_PLUGIN_VERIFY_FLAG_NONE = 0, /*< private >*/ FU_PLUGIN_VERIFY_FLAG_LAST } FuPluginVerifyFlags; struct _FuPluginClass { FwupdPluginClass parent_class; /* signals */ void (*_device_added)(FuPlugin *self, FuDevice *device); void (*_device_removed)(FuPlugin *self, FuDevice *device); void (*_status_changed)(FuPlugin *self, FwupdStatus status); void (*_percentage_changed)(FuPlugin *self, guint percentage); void (*_device_register)(FuPlugin *self, FuDevice *device); gboolean (*_check_supported)(FuPlugin *self, const gchar *guid); void (*_rules_changed)(FuPlugin *self); /* vfuncs */ /** * init: * @self: A #FuPlugin * * Initializes the modular plugin. * Sets up any static data structures for the plugin. * * Since: 1.7.2 **/ void (*constructed)(GObject *obj); /** * finalize: * @self: a plugin * * Destroys the modular plugin. * Any allocated memory should be freed here. * * Since: 1.7.2 **/ void (*finalize)(GObject *obj); /** * startup: * @self: a #FuPlugin * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Tries to start the plugin. * Returns: TRUE for success or FALSE for failure. * * Any plugins not intended for the system or that have failure communicating * with the device should return FALSE. * Any allocated memory should be freed here. * * Since: 1.7.2 **/ gboolean (*startup)(FuPlugin *self, FuProgress *progress, GError **error); /** * ready: * @self: a #FuPlugin * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Tells the plugin that all devices have been coldplugged and the plugin is * ready to be used. * * Returns: TRUE for success or FALSE for failure. * * NOTE: Any plugins not intended for the system or that have failure communicating * with the device should return %FALSE and set @error. * * Since: 1.9.6 **/ gboolean (*ready)(FuPlugin *self, FuProgress *progress, GError **error); /** * coldplug: * @self: a #FuPlugin * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Probes for devices. * * Since: 1.7.2 **/ gboolean (*coldplug)(FuPlugin *self, FuProgress *progress, GError **error); /** * device_created * @self: a #FuPlugin * @dev: a device * @error: (nullable): optional return location for an error * * Function run when the subclassed device has been created. * * Since: 1.7.2 **/ gboolean (*device_created)(FuPlugin *self, FuDevice *device, GError **error); /** * device_registered * @self: a #FuPlugin * @dev: a device * * Function run when device registered from another plugin. * * Since: 1.7.2 **/ void (*device_registered)(FuPlugin *self, FuDevice *device); /** * device_added * @self: a #FuPlugin * @dev: a device * * Function run when the subclassed device has been added. * * Since: 1.7.2 **/ void (*device_added)(FuPlugin *self, FuDevice *device); /** * verify: * @self: a #FuPlugin * @dev: a device * @progress: a #FuProgress * @flags: verify flags * @error: (nullable): optional return location for an error * * Verifies the firmware on the device matches the value stored in the database * * Since: 1.7.2 **/ gboolean (*verify)(FuPlugin *self, FuDevice *device, FuProgress *progress, FuPluginVerifyFlags flags, GError **error); /** * get_results: * @self: a #FuPlugin * @dev: a device * @error: (nullable): optional return location for an error * * Obtains historical update results for the device. * * Since: 1.7.2 **/ gboolean (*get_results)(FuPlugin *self, FuDevice *device, GError **error); /** * clear_results: * @self: a #FuPlugin * @dev: a device * @error: (nullable): optional return location for an error * * Clears stored update results for the device. * * Since: 1.7.2 **/ gboolean (*clear_results)(FuPlugin *self, FuDevice *device, GError **error); /** * backend_device_added * @self: a #FuPlugin * @device: a device * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Function to run after a device is added by a backend, e.g. by USB or Udev. * * Since: 1.7.2 **/ gboolean (*backend_device_added)(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error); /** * backend_device_changed * @self: a #FuPlugin * @device: a device * @error: (nullable): optional return location for an error * * Function run when the device changed. * * Since: 1.7.2 **/ gboolean (*backend_device_changed)(FuPlugin *self, FuDevice *device, GError **error); /** * backend_device_removed * @self: a #FuPlugin * @device: a device * @error: (nullable): optional return location for an error * * Function to run when device is physically removed. * * Since: 1.7.2 **/ gboolean (*backend_device_removed)(FuPlugin *self, FuDevice *device, GError **error); /** * add_security_attrs * @self: a #FuPlugin * @attrs: a security attribute * * Function that asks plugins to add Host Security Attributes. * * Since: 1.7.2 **/ void (*add_security_attrs)(FuPlugin *self, FuSecurityAttrs *attrs); /** * write_firmware: * @self: a #FuPlugin * @dev: a device * @blob_fw: a data blob * @progress: a #FuProgress * @flags: install flags * @error: (nullable): optional return location for an error * * Updates the firmware on the device with blob_fw * * Since: 1.7.2 **/ gboolean (*write_firmware)(FuPlugin *self, FuDevice *device, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, GError **error); /** * unlock: * @self: a #FuPlugin * @dev: a device * @error: (nullable): optional return location for an error * * Unlocks the device for writes. * * Since: 1.7.2 **/ gboolean (*unlock)(FuPlugin *self, FuDevice *device, GError **error); /** * activate: * @self: a #FuPlugin * @dev: a device * @error: (nullable): optional return location for an error * * Activates the new firmware on the device. * * This is intended for devices that it is not safe to immediately activate * the firmware. It may be called at a more convenient time instead. * * Since: 1.7.2 **/ gboolean (*activate)(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error); /** * attach: * @self: a #FuPlugin * @dev: a device * @error: (nullable): optional return location for an error * * Swaps the device from bootloader mode to runtime mode. * * Since: 1.7.2 **/ gboolean (*attach)(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error); /** * detach: * @self: a #FuPlugin * @dev: a device * @error: (nullable): optional return location for an error * * Swaps the device from runtime mode to bootloader mode. * * Since: 1.7.2 **/ gboolean (*detach)(FuPlugin *self, FuDevice *device, FuProgress *progress, GError **error); /** * prepare: * @self: a #FuPlugin * @device: a device * @progress: a #FuProgress * @flags: install flags * @error: (nullable): optional return location for an error * * Prepares the device to receive an update. * * Since: 1.7.2 **/ gboolean (*prepare)(FuPlugin *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error); /** * cleanup * @self: a #FuPlugin * @device: a device * @progress: a #FuProgress * @flags: install flags * @error: (nullable): optional return location for an error * * Cleans up the device after receiving an update. * * Since: 1.7.2 **/ gboolean (*cleanup)(FuPlugin *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error); /** * composite_prepare * @self: a #FuPlugin * @devices: (element-type FuDevice): array of devices * @error: (nullable): optional return location for an error * * Function run before updating group of composite devices. * * Since: 1.7.2 **/ gboolean (*composite_prepare)(FuPlugin *self, GPtrArray *devices, GError **error); /** * composite_cleanup * @self: a #FuPlugin * @devices: (element-type FuDevice): array of devices * @error: (nullable): optional return location for an error * * Function run after updating group of composite devices. * * Since: 1.7.2 **/ gboolean (*composite_cleanup)(FuPlugin *self, GPtrArray *devices, GError **error); /** * load * @ctx: a #FuContext * * Function to register context attributes, run during early startup even on plugins which * will be later disabled. * * Since: 1.8.1 **/ void (*load)(FuContext *ctx); /** * to_string: * @self: A #FuPlugin * * Prints plugin private data to the console. * * Since: 1.8.4 **/ void (*to_string)(FuPlugin *self, guint idt, GString *str); /** * fix_host_security_attr: * @self: a #FuPlugin * @attr: a #FwupdSecurityAttr * @error: (nullable): optional return location for an error * * Fix a host security issue. * * Since: 1.9.6 **/ gboolean (*fix_host_security_attr)(FuPlugin *self, FwupdSecurityAttr *attr, GError **error); /** * undo_host_security_attr: * @self: a #FuPlugin * @attr: a #FwupdSecurityAttr * @error: (nullable): optional return location for an error * * Undo the fix for a host security issue. * * Since: 1.9.6 **/ gboolean (*undo_host_security_attr)(FuPlugin *self, FwupdSecurityAttr *attr, GError **error); /** * reboot_cleanup: * @self: a #FuPlugin * @device: a device * @error: (nullable): optional return location for an error * * Performs cleanup actions after the reboot has been performed. * * Since: 1.9.7 **/ gboolean (*reboot_cleanup)(FuPlugin *self, FuDevice *device, GError **error); }; /** * FuPluginVfuncs: * * A subset of virtual functions that are implemented by modular plugins. **/ typedef struct _FuPluginClass FuPluginVfuncs; /** * FuPluginRule: * @FU_PLUGIN_RULE_CONFLICTS: The plugin conflicts with another * @FU_PLUGIN_RULE_RUN_AFTER: Order the plugin after another * @FU_PLUGIN_RULE_RUN_BEFORE: Order the plugin before another * @FU_PLUGIN_RULE_BETTER_THAN: Is better than another plugin * @FU_PLUGIN_RULE_INHIBITS_IDLE: The plugin inhibits the idle shutdown * @FU_PLUGIN_RULE_METADATA_SOURCE: Uses another plugin as a source of report metadata * * The rules used for ordering plugins. * Plugins are expected to add rules in fu_plugin_initialize(). **/ typedef enum { FU_PLUGIN_RULE_CONFLICTS, FU_PLUGIN_RULE_RUN_AFTER, FU_PLUGIN_RULE_RUN_BEFORE, FU_PLUGIN_RULE_BETTER_THAN, FU_PLUGIN_RULE_INHIBITS_IDLE, FU_PLUGIN_RULE_METADATA_SOURCE, /* Since: 1.3.6 */ /*< private >*/ FU_PLUGIN_RULE_LAST } FuPluginRule; /** * FuPluginData: * * The plugin-allocated private data. **/ typedef struct FuPluginData FuPluginData; /* for plugins to use */ const gchar * fu_plugin_get_name(FuPlugin *self) G_GNUC_NON_NULL(1); void fu_plugin_set_name(FuPlugin *self, const gchar *name) G_GNUC_NON_NULL(1); FuPluginData * fu_plugin_get_data(FuPlugin *self) G_GNUC_NON_NULL(1); FuPluginData * fu_plugin_alloc_data(FuPlugin *self, gsize data_sz) G_GNUC_NON_NULL(1); FuContext * fu_plugin_get_context(FuPlugin *self) G_GNUC_NON_NULL(1); void fu_plugin_device_add(FuPlugin *self, FuDevice *device) G_GNUC_NON_NULL(1, 2); void fu_plugin_device_remove(FuPlugin *self, FuDevice *device) G_GNUC_NON_NULL(1, 2); void fu_plugin_device_register(FuPlugin *self, FuDevice *device) G_GNUC_NON_NULL(1, 2); void fu_plugin_add_device_gtype(FuPlugin *self, GType device_gtype) G_GNUC_NON_NULL(1); GType fu_plugin_get_device_gtype_default(FuPlugin *self) G_GNUC_NON_NULL(1); void fu_plugin_set_device_gtype_default(FuPlugin *self, GType device_gtype) G_GNUC_NON_NULL(1); void fu_plugin_add_firmware_gtype(FuPlugin *self, const gchar *id, GType gtype) G_GNUC_NON_NULL(1); void fu_plugin_add_device_udev_subsystem(FuPlugin *self, const gchar *subsystem) G_GNUC_NON_NULL(1, 2); void fu_plugin_add_udev_subsystem(FuPlugin *self, const gchar *subsystem) G_GNUC_NON_NULL(1, 2); gpointer fu_plugin_cache_lookup(FuPlugin *self, const gchar *id) G_GNUC_NON_NULL(1, 2); void fu_plugin_cache_remove(FuPlugin *self, const gchar *id) G_GNUC_NON_NULL(1, 2); void fu_plugin_cache_add(FuPlugin *self, const gchar *id, gpointer dev) G_GNUC_NON_NULL(1, 2); GPtrArray * fu_plugin_get_devices(FuPlugin *self) G_GNUC_NON_NULL(1); void fu_plugin_add_rule(FuPlugin *self, FuPluginRule rule, const gchar *name) G_GNUC_NON_NULL(1, 3); void fu_plugin_add_report_metadata(FuPlugin *self, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2, 3); void fu_plugin_set_config_default(FuPlugin *self, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1, 2); gchar * fu_plugin_get_config_value(FuPlugin *self, const gchar *key) G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_get_config_value_boolean(FuPlugin *self, const gchar *key) G_GNUC_NON_NULL(1, 2); gboolean fu_plugin_set_config_value(FuPlugin *self, const gchar *key, const gchar *value, GError **error) G_GNUC_NON_NULL(1, 2); FwupdSecurityAttr * fu_plugin_security_attr_new(FuPlugin *self, const gchar *appstream_id) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-progress.c000066400000000000000000000672401460375044200204260ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuProgress" #include "config.h" #include #include "fu-progress.h" #include "fu-string.h" /** * FuProgress: * * Objects can use fu_progress_set_percentage() if the absolute percentage * is known. Percentages should always go up, not down. * * Modules usually set the number of steps that are expected using * fu_progress_set_steps() and then after each section is completed, * the fu_progress_step_done() function should be called. This will automatically * call fu_progress_set_percentage() with the correct values. * * #FuProgress allows sub-modules to be "chained up" to the parent module * so that as the sub-module progresses, so does the parent. * The child can be reused for each section, and chains can be deep. * * To get a child object, you should use [method@FuProgress.get_child]. and then * use the result in any sub-process. You should ensure that the child * is not re-used without calling fu_progress_step_done(). * * There are a few nice touches in this module, so that if a module only has * one progress step, the child progress is used for parent updates. * * static void * _do_something(FuProgress *self) * { * // setup correct number of steps * fu_progress_set_steps(self, 2); * * // run a sub function * _do_something_else1(fu_progress_get_child(self)); * * // this section done * fu_progress_step_done(self); * * // run another sub function * _do_something_else2(fu_progress_get_child(self)); * * // this progress done (all complete) * fu_progress_step_done(self); * } * * See also: [class@FuDevice] */ struct _FuProgress { GObject parent_instance; gchar *id; gchar *name; FuProgressFlag flags; guint percentage; FwupdStatus status; GPtrArray *children; /* of FuProgress */ gboolean profile; gdouble duration; /* seconds */ guint step_weighting; GTimer *timer; GTimer *timer_child; guint step_now; guint step_done; guint step_scaling; FuProgress *parent; /* no-ref */ }; enum { SIGNAL_PERCENTAGE_CHANGED, SIGNAL_STATUS_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE(FuProgress, fu_progress, G_TYPE_OBJECT) #define FU_PROGRESS_STEPS_MAX 1000 /** * fu_progress_get_id: * @self: a #FuProgress * * Return the id of the progress, which is normally set by the caller. * * Returns: progress ID * * Since: 1.7.0 **/ const gchar * fu_progress_get_id(FuProgress *self) { g_return_val_if_fail(FU_IS_PROGRESS(self), NULL); return self->id; } /** * fu_progress_set_id: * @self: a #FuProgress * @id: progress ID, normally `G_STRLOC` * * Sets the id of the progress. * * Since: 1.7.0 **/ void fu_progress_set_id(FuProgress *self, const gchar *id) { g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(id != NULL); /* not changed */ if (g_strcmp0(self->id, id) == 0) return; /* set id */ g_free(self->id); self->id = g_strdup(id); } /** * fu_progress_get_name: * @self: a #FuProgress * * Return the nice name of the progress, which is normally set by the caller. * * Returns: progress nice name, e.g. `add-devices` * * Since: 1.8.2 **/ const gchar * fu_progress_get_name(FuProgress *self) { g_return_val_if_fail(FU_IS_PROGRESS(self), NULL); return self->name; } static const gchar * fu_progress_get_name_fallback(FuProgress *self) { if (self->name != NULL) return self->name; return fwupd_status_to_string(self->status); } /** * fu_progress_set_name: * @self: a #FuProgress * @name: progress nice name, e.g. `add-devices`, or perhaps just `G_STRFUNC` * * Sets the nice name of the progress. * * Since: 1.8.2 **/ void fu_progress_set_name(FuProgress *self, const gchar *name) { g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(name != NULL); /* not changed */ if (g_strcmp0(self->name, name) == 0) return; /* set name */ g_free(self->name); self->name = g_strdup(name); } /** * fu_progress_get_status: * @self: a #FuProgress * * Return the status of the progress, which is normally indirectly by fu_progress_add_step(). * * Returns: status * * Since: 1.7.0 **/ FwupdStatus fu_progress_get_status(FuProgress *self) { g_return_val_if_fail(FU_IS_PROGRESS(self), FWUPD_STATUS_UNKNOWN); return self->status; } /** * fu_progress_add_flag: * @self: a #FuProgress * @flag: an internal progress flag, e.g. %FU_PROGRESS_FLAG_GUESSED * * Adds a flag. * * Since: 1.7.0 **/ void fu_progress_add_flag(FuProgress *self, FuProgressFlag flag) { g_return_if_fail(FU_IS_PROGRESS(self)); self->flags |= flag; } /** * fu_progress_remove_flag: * @self: a #FuProgress * @flag: an internal progress flag, e.g. %FU_PROGRESS_FLAG_GUESSED * * Removes a flag. * * Since: 1.7.0 **/ void fu_progress_remove_flag(FuProgress *self, FuProgressFlag flag) { g_return_if_fail(FU_IS_PROGRESS(self)); self->flags &= ~flag; } /** * fu_progress_has_flag: * @self: a #FuProgress * @flag: an internal progress flag, e.g. %FU_PROGRESS_FLAG_GUESSED * * Tests for a flag. * * Since: 1.7.0 **/ gboolean fu_progress_has_flag(FuProgress *self, FuProgressFlag flag) { g_return_val_if_fail(FU_IS_PROGRESS(self), FALSE); return (self->flags & flag) > 0; } /** * fu_progress_set_status: * @self: a #FuProgress * @status: device status * * Sets the status of the progress. * * Since: 1.7.0 **/ void fu_progress_set_status(FuProgress *self, FwupdStatus status) { g_return_if_fail(FU_IS_PROGRESS(self)); /* not changed */ if (self->status == status) return; /* save */ self->status = status; g_signal_emit(self, signals[SIGNAL_STATUS_CHANGED], 0, status); } /** * fu_progress_get_percentage: * @self: a #FuProgress * * Get the last set progress percentage. * * Return value: The percentage value, or %G_MAXUINT for error * * Since: 1.7.0 **/ guint fu_progress_get_percentage(FuProgress *self) { g_return_val_if_fail(FU_IS_PROGRESS(self), G_MAXUINT); if (self->percentage == G_MAXUINT) return 0; return self->percentage; } static void fu_progress_set_parent(FuProgress *self, FuProgress *parent) { g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(FU_IS_PROGRESS(parent)); self->parent = parent; /* no ref! */ self->profile = fu_progress_get_profile(parent); } /** * fu_progress_get_duration: * @self: a #FuProgress * * Get the duration of the step. * * Return value: The duration value in seconds * * Since: 1.8.2 **/ gdouble fu_progress_get_duration(FuProgress *self) { return self->duration; } static void fu_progress_set_duration(FuProgress *self, gdouble duration) { self->duration = duration; } static void fu_progress_build_parent_chain(FuProgress *self, GString *str, guint level) { if (self->parent != NULL) fu_progress_build_parent_chain(self->parent, str, level + 1); g_string_append_printf(str, "%u) %s (%u/%u)\n", level, self->id, self->step_now, self->children->len); } /** * fu_progress_set_percentage: * @self: a #FuProgress * @percentage: value between 0% and 100% * * Sets the progress percentage complete. * * NOTE: this must be above what was previously set, or it will be rejected. * * Since: 1.7.0 **/ void fu_progress_set_percentage(FuProgress *self, guint percentage) { g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(percentage <= 100); /* is it the same */ if (percentage == self->percentage) return; /* is it less */ if (self->percentage != G_MAXUINT && percentage < self->percentage) { if (self->profile) { g_autoptr(GString) str = g_string_new(NULL); fu_progress_build_parent_chain(self, str, 0); g_warning("percentage should not go down from %u to %u: %s", self->percentage, percentage, str->str); } return; } /* done */ if (percentage == 100) { fu_progress_set_duration(self, g_timer_elapsed(self->timer, NULL)); for (guint i = 0; i < self->children->len; i++) { FuProgress *child = g_ptr_array_index(self->children, i); g_signal_handlers_disconnect_by_data(child, self); } } /* save */ self->percentage = percentage; g_signal_emit(self, signals[SIGNAL_PERCENTAGE_CHANGED], 0, percentage); } /** * fu_progress_set_percentage_full: * @self: a #FuDevice * @progress_done: the bytes already done * @progress_total: the total number of bytes * * Sets the progress completion using the raw progress values. * * Since: 1.7.0 **/ void fu_progress_set_percentage_full(FuProgress *self, gsize progress_done, gsize progress_total) { gdouble percentage = 0.f; g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(progress_done <= progress_total); if (progress_total > 0) percentage = (100.f * (gdouble)progress_done) / (gdouble)progress_total; fu_progress_set_percentage(self, (guint)percentage); } /** * fu_progress_set_profile: * @self: A #FuProgress * @profile: if profiling should be enabled * * This enables profiling of FuProgress. This may be useful in development, * but be warned; enabling profiling makes #FuProgress very slow. * * Since: 1.7.0 **/ void fu_progress_set_profile(FuProgress *self, gboolean profile) { g_return_if_fail(FU_IS_PROGRESS(self)); self->profile = profile; } /** * fu_progress_get_profile: * @self: A #FuProgress * * Returns if the profile is enabled for this progress. * * Return value: if profiling has been enabled * * Since: 1.8.2 **/ gboolean fu_progress_get_profile(FuProgress *self) { g_return_val_if_fail(FU_IS_PROGRESS(self), FALSE); return self->profile; } /** * fu_progress_reset: * @self: A #FuProgress * * Resets the #FuProgress object to unset * * Since: 1.7.0 **/ void fu_progress_reset(FuProgress *self) { g_return_if_fail(FU_IS_PROGRESS(self)); /* reset values */ self->step_now = 0; self->percentage = G_MAXUINT; /* only use the timer if profiling; it's expensive */ if (self->profile) { g_timer_start(self->timer); g_timer_start(self->timer_child); } /* no more step data */ g_ptr_array_set_size(self->children, 0); } /** * fu_progress_set_steps: * @self: A #FuProgress * @step_max: The number of sub-tasks in this progress, can be 0 * * Sets the number of sub-tasks, i.e. how many times the fu_progress_step_done() * function will be called in the loop. * * The progress ID must be set fu_progress_set_id() before this method is used. * * Since: 1.7.0 **/ void fu_progress_set_steps(FuProgress *self, guint step_max) { g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(self->id != NULL); /* if there is an insane number of steps, scale these */ if (step_max > FU_PROGRESS_STEPS_MAX) { self->step_scaling = step_max / 100; step_max = 100; } /* create fake steps */ for (guint i = 0; i < step_max; i++) fu_progress_add_step(self, self->status, 0, NULL); /* show that the sub-progress has been created */ fu_progress_set_percentage(self, 0); fu_progress_add_flag(self, FU_PROGRESS_FLAG_NO_PROFILE); /* reset child timer */ g_timer_start(self->timer_child); } /** * fu_progress_get_steps: * @self: A #FuProgress * * Gets the number of sub-tasks, i.e. how many times the fu_progress_step_done() * function will be called in the loop. * * Return value: number of sub-tasks in this progress * * Since: 1.7.0 **/ guint fu_progress_get_steps(FuProgress *self) { g_return_val_if_fail(FU_IS_PROGRESS(self), G_MAXUINT); return self->children->len; } static gdouble fu_progress_discrete_to_percent(guint discrete, guint step_max) { /* check we are in range */ if (discrete > step_max) return 100; if (step_max == 0) { g_warning("step_max is 0!"); return 0; } return ((gdouble)discrete * (100.0f / (gdouble)(step_max))); } static gdouble fu_progress_get_step_percentage(FuProgress *self, guint idx) { guint current = 0; guint total = 0; gboolean any_step_weighting = FALSE; /* we set the step weighting manually */ for (guint i = 0; i < self->children->len; i++) { FuProgress *child = g_ptr_array_index(self->children, i); if (child->step_weighting > 0) { any_step_weighting = TRUE; break; } } /* just use proportional */ if (!any_step_weighting) return -1; /* work out percentage */ for (guint i = 0; i < self->children->len; i++) { FuProgress *child = g_ptr_array_index(self->children, i); if (i <= idx) current += child->step_weighting; total += child->step_weighting; } if (total == 0) return -1; return ((gdouble)current * 100.f) / (gdouble)total; } static void fu_progress_child_status_changed_cb(FuProgress *child, FwupdStatus status, FuProgress *self) { fu_progress_set_status(self, status); } static void fu_progress_child_percentage_changed_cb(FuProgress *child, guint percentage, FuProgress *self) { gdouble offset; gdouble range; gdouble extra; guint parent_percentage = G_MAXUINT; /* propagate up the stack if FuProgress has only one priv */ if (self->children->len == 1) { fu_progress_set_percentage(self, percentage); return; } /* did we call done on a step that did not have a size set? */ if (self->children->len == 0) return; /* already at >= 100% */ if (self->step_now >= self->children->len) { g_warning("already at %u/%u step_max", self->step_now, self->children->len); return; } /* if the child finished, set the status back to the last parent status */ if (percentage == 100) { FuProgress *child_tmp = g_ptr_array_index(self->children, self->step_now); if (fu_progress_get_status(child_tmp) != FWUPD_STATUS_UNKNOWN) fu_progress_set_status(self, fu_progress_get_status(child_tmp)); } /* we don't store zero */ if (self->step_now == 0) { gdouble pc = fu_progress_get_step_percentage(self, 0); if (pc > 0) parent_percentage = percentage * pc / 100; } else { gdouble pc1 = fu_progress_get_step_percentage(self, self->step_now - 1); gdouble pc2 = fu_progress_get_step_percentage(self, self->step_now); /* bi-linearly interpolate */ if (pc1 >= 0 && pc2 >= 0) parent_percentage = (((100 - percentage) * pc1) + (percentage * pc2)) / 100; } if (parent_percentage != G_MAXUINT) { fu_progress_set_percentage(self, parent_percentage); return; } /* get the range between the parent priv and the next parent priv */ offset = fu_progress_discrete_to_percent(self->step_now, self->children->len); range = fu_progress_discrete_to_percent(self->step_now + 1, self->children->len) - offset; if (range < 0.01) return; /* get the extra contributed by the child */ extra = ((gdouble)percentage / 100.0f) * range; /* emit from the parent */ parent_percentage = (guint)(offset + extra); fu_progress_set_percentage(self, parent_percentage); } /** * fu_progress_add_step: * @self: A #FuProgress * @status: status value to use for this phase * @value: A step weighting variable argument array * @name: (nullable): Human readable name to identify the step * * This sets the step weighting, which you will want to do if one action * will take a bigger chunk of time than another. * * The progress ID must be set fu_progress_set_id() before this method is used. * * Since: 1.8.2 **/ void fu_progress_add_step(FuProgress *self, FwupdStatus status, guint value, const gchar *name) { g_autoptr(FuProgress) child = fu_progress_new(NULL); g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(self->id != NULL); g_return_if_fail(self->children->len < 100 * 1000); /* save data */ fu_progress_set_status(child, status); child->step_weighting = value; /* connect signals */ g_signal_connect(FU_PROGRESS(child), "percentage-changed", G_CALLBACK(fu_progress_child_percentage_changed_cb), self); g_signal_connect(FU_PROGRESS(child), "status-changed", G_CALLBACK(fu_progress_child_status_changed_cb), self); fu_progress_set_parent(child, self); if (name != NULL) fu_progress_set_name(child, name); /* use first child status */ if (self->children->len == 0) fu_progress_set_status(self, status); /* add child */ g_ptr_array_add(self->children, g_steal_pointer(&child)); /* reset child timer */ g_timer_start(self->timer_child); } /** * fu_progress_finished: * @self: A #FuProgress * * Called when the step_now sub-task wants to finish early and still complete. * * Since: 1.7.0 **/ void fu_progress_finished(FuProgress *self) { g_return_if_fail(FU_IS_PROGRESS(self)); /* is already at 100%? */ if (self->step_now == self->children->len) return; /* all done */ self->step_now = self->children->len; fu_progress_set_percentage(self, 100); /* we finished early, so invalidate children */ for (guint i = 0; i < self->children->len; i++) { FuProgress *child = g_ptr_array_index(self->children, i); fu_progress_add_flag(child, FU_PROGRESS_FLAG_NO_TRACEBACK); } } /** * fu_progress_get_child: * @self: A #FuProgress * * Monitor a child and proxy back up to the parent with the correct percentage. * * Return value: (transfer none): A new %FuProgress or %NULL for failure * * Since: 1.7.0 **/ FuProgress * fu_progress_get_child(FuProgress *self) { guint step_now; g_return_val_if_fail(FU_IS_PROGRESS(self), NULL); g_return_val_if_fail(self->id != NULL, NULL); step_now = self->step_now / self->step_scaling; g_return_val_if_fail(self->children->len > 0, NULL); g_return_val_if_fail(self->children->len > step_now, NULL); /* all preallocated, nothing to do */ return FU_PROGRESS(g_ptr_array_index(self->children, step_now)); } static void fu_progress_show_profile(FuProgress *self) { gdouble division; gdouble total_time = 0.0f; gboolean close_enough = TRUE; g_autoptr(GString) str = NULL; /* not accurate enough for a profile result */ if (self->flags & FU_PROGRESS_FLAG_NO_PROFILE) return; /* get the total time so we can work out the divisor */ str = g_string_new("raw timing data was { "); for (guint i = 0; i < self->children->len; i++) { FuProgress *child = g_ptr_array_index(self->children, i); g_string_append_printf(str, "%.3f, ", fu_progress_get_duration(child)); } if (self->children->len > 0) g_string_set_size(str, str->len - 2); g_string_append(str, " } -- "); /* get the total time so we can work out the divisor */ for (guint i = 0; i < self->children->len; i++) { FuProgress *child = g_ptr_array_index(self->children, i); total_time += fu_progress_get_duration(child); } if (total_time < 0.001) return; division = total_time / 100.0f; /* what we set */ g_string_append(str, "steps were set as [ "); for (guint i = 0; i < self->children->len; i++) { FuProgress *child = g_ptr_array_index(self->children, i); g_string_append_printf(str, "%u ", child->step_weighting); } /* what we _should_ have set */ g_string_append_printf(str, "] but should have been [ "); for (guint i = 0; i < self->children->len; i++) { FuProgress *child = g_ptr_array_index(self->children, i); g_string_append_printf(str, "%.0f ", fu_progress_get_duration(child) / division); /* this is sufficiently different to what we guessed */ if (fabs((fu_progress_get_duration(child) / division) - (gdouble)child->step_weighting) > 5) { close_enough = FALSE; } } g_string_append(str, "]"); if (self->flags & FU_PROGRESS_FLAG_GUESSED) { #ifdef SUPPORTED_BUILD g_debug("%s at %s [%s]", str->str, self->id, fu_progress_get_name_fallback(self)); #else g_warning("%s at %s [%s]", str->str, self->id, fu_progress_get_name_fallback(self)); g_warning("Please see " "https://github.com/fwupd/fwupd/wiki/Daemon-Warning:-FuProgress-steps"); #endif } else if (!close_enough) { g_debug("%s at %s", str->str, self->id); } } /** * fu_progress_step_done: * @self: A #FuProgress * * Called when the step_now sub-task has finished. * * Since: 1.7.0 **/ void fu_progress_step_done(FuProgress *self) { FuProgress *child = NULL; gdouble percentage; g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(self->id != NULL); /* ignore steps */ if (self->step_scaling > 1) { if (self->step_now >= self->children->len || self->step_done++ % self->step_scaling != 0) return; } /* did we call done when no size set? */ if (self->children->len == 0) { g_autoptr(GString) str = g_string_new(NULL); fu_progress_build_parent_chain(self, str, 0); g_warning("progress done when no size set! [%s]: %s", self->id, str->str); return; } /* get the active child */ if (self->children->len > 0) child = g_ptr_array_index(self->children, self->step_now); /* save the duration in the array */ if (self->profile) { if (child != NULL) fu_progress_set_duration(child, g_timer_elapsed(self->timer_child, NULL)); g_timer_start(self->timer_child); } /* is already at 100%? */ if (self->step_now >= self->children->len) { g_autoptr(GString) str = g_string_new(NULL); fu_progress_build_parent_chain(self, str, 0); g_warning("already at 100%% [%s]: %s", self->id, str->str); return; } /* is child not at 100%? */ if (!fu_progress_has_flag(self, FU_PROGRESS_FLAG_CHILD_FINISHED) && child != NULL) { if (child->step_now != child->children->len) { g_autoptr(GString) str = g_string_new(NULL); fu_progress_build_parent_chain(child, str, 0); g_warning("child is at %u/%u step_max and parent done [%s]\n%s", child->step_now, child->children->len, self->id, str->str); /* do not abort, as we want to clean this up */ } } /* another */ self->step_now++; /* update status */ if (self->step_now < self->children->len) { FuProgress *child_tmp = g_ptr_array_index(self->children, self->step_now); if (fu_progress_get_status(child_tmp) != FWUPD_STATUS_UNKNOWN) fu_progress_set_status(self, fu_progress_get_status(child_tmp)); } else if (self->parent != NULL) { fu_progress_set_status(self, fu_progress_get_status(self->parent)); } else { fu_progress_set_status(self, FWUPD_STATUS_UNKNOWN); } /* find new percentage */ percentage = fu_progress_get_step_percentage(self, self->step_now - 1); if (percentage < 0) percentage = fu_progress_discrete_to_percent(self->step_now, self->children->len); fu_progress_set_percentage(self, (guint)percentage); /* show any profiling stats */ if (self->profile && self->step_now == self->children->len) fu_progress_show_profile(self); } /** * fu_progress_sleep: * @self: a #FuProgress * @delay_ms: the delay in milliseconds * * Sleeps, setting the device progress from 0..100% as time continues. * * NOTE: You should try to avoid calling this function for emulated devices. * * Since: 1.7.0 **/ void fu_progress_sleep(FuProgress *self, guint delay_ms) { gulong delay_us_pc = (delay_ms * 1000) / 100; g_return_if_fail(FU_IS_PROGRESS(self)); g_return_if_fail(delay_ms > 0); fu_progress_set_percentage(self, 0); for (guint i = 0; i < 100; i++) { g_usleep(delay_us_pc); fu_progress_set_percentage(self, i + 1); } } static void fu_progress_traceback_cb(FuProgress *self, guint idt, guint child_idx, guint threshold_ms, GString *str) { if (self->flags & FU_PROGRESS_FLAG_NO_TRACEBACK) return; if (self->children->len == 0 && fu_progress_get_duration(self) < 0.0001) return; if (threshold_ms == 0 || fu_progress_get_duration(self) * 1000 > threshold_ms) { for (guint i = 0; i < idt; i++) g_string_append(str, " "); if (self->id != NULL) g_string_append(str, self->id); if (self->name != NULL) g_string_append_printf(str, ":%s", self->name); if (self->id == NULL && self->name == NULL && child_idx != G_MAXUINT) g_string_append_printf(str, "@%u", child_idx); g_string_append_printf(str, " [%.2fms]", fu_progress_get_duration(self) * 1000.f); g_string_append(str, self->children->len > 0 ? ":\n" : "\n"); } for (guint i = 0; i < self->children->len; i++) { FuProgress *child = g_ptr_array_index(self->children, i); fu_progress_traceback_cb(child, idt + 4, i, threshold_ms, str); } } /** * fu_progress_traceback: * @self: A #FuProgress * * Create a traceback used for profiling startup. * * Return value: (transfer full): string * * Since: 1.8.2 **/ gchar * fu_progress_traceback(FuProgress *self) { const gchar *tmp = g_getenv("FWUPD_PROFILE"); guint64 threshold_ms = 5000; g_autoptr(GString) str = g_string_new(NULL); /* allow override */ if (tmp != NULL) { g_autoptr(GError) error_local = NULL; if (!fu_strtoull(tmp, &threshold_ms, 0, G_MAXUINT, &error_local)) g_warning("invalid threshold value: %s", tmp); } fu_progress_traceback_cb(self, 0, G_MAXUINT, threshold_ms, str); if (str->len == 0) return NULL; return g_string_free(g_steal_pointer(&str), FALSE); } static void fu_progress_to_string_cb(FuProgress *self, guint idt, GString *str) { /* not interesting */ if (self->id == NULL && self->name == NULL) return; if (self->id != NULL) fu_string_append(str, idt, "Id", self->id); if (self->name != NULL) fu_string_append(str, idt, "Name", self->name); if (self->percentage != G_MAXUINT) fu_string_append_ku(str, idt, "Percentage", self->percentage); if (self->status != FWUPD_STATUS_UNKNOWN) fu_string_append(str, idt, "Status", fwupd_status_to_string(self->status)); if (self->duration > 0.0001) fu_string_append_ku(str, idt, "DurationMs", self->duration * 1000.f); if (self->step_weighting > 0) fu_string_append_ku(str, idt, "StepWeighting", self->step_weighting); if (self->step_now > 0) fu_string_append_ku(str, idt, "StepNow", self->step_now); for (guint i = 0; i < self->children->len; i++) { FuProgress *child = g_ptr_array_index(self->children, i); fu_progress_to_string_cb(child, idt + 1, str); } } /** * fu_progress_to_string: * @self: A #FuProgress * * Prints the progress for debugging. * * Return value: (transfer full): string * * Since: 1.8.7 **/ gchar * fu_progress_to_string(FuProgress *self) { g_autoptr(GString) str = g_string_new(NULL); fu_progress_to_string_cb(self, 0, str); if (str->len == 0) return NULL; return g_string_free(g_steal_pointer(&str), FALSE); } static void fu_progress_init(FuProgress *self) { self->status = FWUPD_STATUS_UNKNOWN; self->step_scaling = 1; self->percentage = G_MAXUINT; self->timer = g_timer_new(); self->timer_child = g_timer_new(); self->children = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); self->duration = 0.f; } static void fu_progress_finalize(GObject *object) { FuProgress *self = FU_PROGRESS(object); /* show any profiling stats */ if (self->profile) fu_progress_show_profile(self); fu_progress_reset(self); g_free(self->id); g_free(self->name); g_ptr_array_unref(self->children); g_timer_destroy(self->timer); g_timer_destroy(self->timer_child); G_OBJECT_CLASS(fu_progress_parent_class)->finalize(object); } static void fu_progress_class_init(FuProgressClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_progress_finalize; /** * FuProgress::percentage-changed: * @self: the #FuProgress instance that emitted the signal * @percentage: the new value * * The ::percentage-changed signal is emitted when the tasks completion has changed. * * Since: 1.7.0 **/ signals[SIGNAL_PERCENTAGE_CHANGED] = g_signal_new("percentage-changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); /** * FuProgress::status-changed: * @self: the #FuProgress instance that emitted the signal * @status: the new #FwupdStatus * * The ::status-changed signal is emitted when the task status has changed. * * Since: 1.7.0 **/ signals[SIGNAL_STATUS_CHANGED] = g_signal_new("status-changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); } /** * fu_progress_new: * @id: (nullable): progress ID, normally `G_STRLOC` * * Return value: A new #FuProgress instance. * * Since: 1.7.0 **/ FuProgress * fu_progress_new(const gchar *id) { FuProgress *self; self = g_object_new(FU_TYPE_PROGRESS, NULL); if (id != NULL) fu_progress_set_id(self, id); return FU_PROGRESS(self); } fwupd-1.9.16/libfwupdplugin/fu-progress.h000066400000000000000000000044531460375044200204300ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-progress-struct.h" #define FU_TYPE_PROGRESS (fu_progress_get_type()) G_DECLARE_FINAL_TYPE(FuProgress, fu_progress, FU, PROGRESS, GObject) FuProgress * fu_progress_new(const gchar *id); const gchar * fu_progress_get_id(FuProgress *self) G_GNUC_NON_NULL(1); void fu_progress_set_id(FuProgress *self, const gchar *id) G_GNUC_NON_NULL(1, 2); const gchar * fu_progress_get_name(FuProgress *self) G_GNUC_NON_NULL(1); void fu_progress_set_name(FuProgress *self, const gchar *name) G_GNUC_NON_NULL(1, 2); void fu_progress_add_flag(FuProgress *self, FuProgressFlag flag) G_GNUC_NON_NULL(1); void fu_progress_remove_flag(FuProgress *self, FuProgressFlag flag) G_GNUC_NON_NULL(1); gboolean fu_progress_has_flag(FuProgress *self, FuProgressFlag flag) G_GNUC_NON_NULL(1); FwupdStatus fu_progress_get_status(FuProgress *self) G_GNUC_NON_NULL(1); void fu_progress_set_status(FuProgress *self, FwupdStatus status) G_GNUC_NON_NULL(1); void fu_progress_set_percentage(FuProgress *self, guint percentage) G_GNUC_NON_NULL(1); void fu_progress_set_percentage_full(FuProgress *self, gsize progress_done, gsize progress_total) G_GNUC_NON_NULL(1); guint fu_progress_get_percentage(FuProgress *self) G_GNUC_NON_NULL(1); gdouble fu_progress_get_duration(FuProgress *self) G_GNUC_NON_NULL(1); void fu_progress_set_profile(FuProgress *self, gboolean profile) G_GNUC_NON_NULL(1); gboolean fu_progress_get_profile(FuProgress *self) G_GNUC_NON_NULL(1); void fu_progress_reset(FuProgress *self) G_GNUC_NON_NULL(1); void fu_progress_set_steps(FuProgress *self, guint step_max) G_GNUC_NON_NULL(1); guint fu_progress_get_steps(FuProgress *self) G_GNUC_NON_NULL(1); void fu_progress_add_step(FuProgress *self, FwupdStatus status, guint value, const gchar *name) G_GNUC_NON_NULL(1); void fu_progress_finished(FuProgress *self) G_GNUC_NON_NULL(1); void fu_progress_step_done(FuProgress *self) G_GNUC_NON_NULL(1); FuProgress * fu_progress_get_child(FuProgress *self) G_GNUC_NON_NULL(1); void fu_progress_sleep(FuProgress *self, guint delay_ms) G_GNUC_NON_NULL(1); gchar * fu_progress_traceback(FuProgress *self) G_GNUC_NON_NULL(1); gchar * fu_progress_to_string(FuProgress *self) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-progress.rs000066400000000000000000000007111460375044200206160ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ enum ProgressFlag { None = 0, // Since: 1.7.0 Guessed = 1 << 0, // Since: 1.7.0 NoProfile = 1 << 1, // Since: 1.7.0 ChildFinished = 1 << 2, // Since: 1.8.2 NoTraceback = 1 << 3, // Since: 1.8.2 NoSender = 1 << 4, // Since: 1.9.10 Unknown = u64::MAX, // Since: 1.7.0 } fwupd-1.9.16/libfwupdplugin/fu-quirks.c000066400000000000000000000503351460375044200200750ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuQuirks" #include "config.h" #include #include #include "fwupd-common.h" #include "fwupd-error.h" #include "fwupd-remote-private.h" #include "fu-bytes.h" #include "fu-common.h" #include "fu-path.h" #include "fu-quirks.h" #include "fu-string.h" /** * FuQuirks: * * Quirks can be used to modify device behavior. * When fwupd is installed in long-term support distros it's very hard to * backport new versions as new hardware is released. * * There are several reasons why we can't just include the mapping and quirk * information in the AppStream metadata: * * * The extra data is hugely specific to the installed fwupd plugin versions * * The device-id is per-device, and the mapping is usually per-plugin * * Often the information is needed before the FuDevice is created * * There are security implications in allowing plugins to handle new devices * * The idea with quirks is that the end user can drop an additional (or replace * an existing) file in a .d director with a simple format and the hardware will * magically start working. This assumes no new quirks are required, as this would * obviously need code changes, but allows us to get most existing devices working * in an easy way without the user compiling anything. * * Plugins may add support for additional quirks that are relevant only for those plugins, * and should be documented in the per-plugin `README.md` files. * * You can add quirk files in `/usr/share/fwupd/quirks.d` or `/var/lib/fwupd/quirks.d/`. * * Here is an example as seen in the CSR plugin: * * |[ * [USB\VID_0A12&PID_1337] * Plugin = dfu_csr * Name = H05 * Summary = Bluetooth Headphones * Icon = audio-headphones * Vendor = AIAIAI * [USB\VID_0A12&PID_1337&REV_2520] * Version = 1.2 * ]| * * See also: [class@FuDevice], [class@FuPlugin] */ static void fu_quirks_finalize(GObject *obj); struct _FuQuirks { GObject parent_instance; FuQuirksLoadFlags load_flags; GHashTable *possible_keys; GPtrArray *invalid_keys; XbSilo *silo; XbQuery *query_kv; XbQuery *query_vs; gboolean verbose; }; G_DEFINE_TYPE(FuQuirks, fu_quirks, G_TYPE_OBJECT) static gchar * fu_quirks_build_group_key(const gchar *group) { const gchar *guid_prefixes[] = {"DeviceInstanceId=", "Guid=", "HwId=", NULL}; /* this is a GUID */ for (guint i = 0; guid_prefixes[i] != NULL; i++) { if (g_str_has_prefix(group, guid_prefixes[i])) { gsize len = strlen(guid_prefixes[i]); g_warning("using %s for %s in quirk files is deprecated!", guid_prefixes[i], group); if (fwupd_guid_is_valid(group + len)) return g_strdup(group + len); return fwupd_guid_hash_string(group + len); } } /* fallback */ if (fwupd_guid_is_valid(group)) return g_strdup(group); return fwupd_guid_hash_string(group); } static gboolean fu_quirks_validate_flags(const gchar *value, GError **error) { g_return_val_if_fail(value != NULL, FALSE); for (gsize i = 0; value[i] != '\0'; i++) { gchar tmp = value[i]; /* allowed special chars */ if (tmp == ',' || tmp == '~' || tmp == '-') continue; if (!g_ascii_isalnum(tmp)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%c is not alphanumeric", tmp); return FALSE; } if (g_ascii_isalpha(tmp) && !g_ascii_islower(tmp)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%c is not lowercase", tmp); return FALSE; } } /* success */ return TRUE; } typedef struct { GString *group; XbBuilderNode *bn; XbBuilderNode *root; } FuQuirksConvertHelper; static void fu_quirks_convert_helper_free(FuQuirksConvertHelper *helper) { g_string_free(helper->group, TRUE); g_object_unref(helper->root); if (helper->bn != NULL) g_object_unref(helper->bn); g_free(helper); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuQuirksConvertHelper, fu_quirks_convert_helper_free) static gboolean fu_quirks_convert_keyfile_to_xml_cb(GString *token, guint token_idx, gpointer user_data, GError **error) { FuQuirksConvertHelper *helper = (FuQuirksConvertHelper *)user_data; g_autofree gchar *key = NULL; g_autofree gchar *value = NULL; g_auto(GStrv) kv = NULL; /* blank line */ if (token->len == 0) return TRUE; /* comment */ if (token->str[0] == '#') return TRUE; /* neither a key=value or [group] */ if (token->len < 3) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid line: %s", token->str); return FALSE; } /* a group */ if (token->str[0] == '[' && token->str[token->len - 1] == ']') { g_autofree gchar *group_id = NULL; g_autofree gchar *group_tmp = NULL; g_autoptr(XbBuilderNode) bn_tmp = NULL; /* trim off the [] and convert to a GUID */ group_tmp = g_strndup(token->str + 1, token->len - 2); group_id = fu_quirks_build_group_key(group_tmp); bn_tmp = xb_builder_node_insert(helper->root, "device", "id", group_id, NULL); g_set_object(&helper->bn, bn_tmp); g_string_assign(helper->group, group_tmp); return TRUE; } /* no current group */ if (helper->bn == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid line when group unset: %s", token->str); return FALSE; } /* parse as key=value */ kv = g_strsplit(token->str, "=", 2); if (kv[1] == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid line: not key=value: %s", token->str); return FALSE; } /* sanity check flags */ key = fu_strstrip(kv[0]); value = fu_strstrip(kv[1]); if (g_strcmp0(key, FU_QUIRKS_FLAGS) == 0) { g_autoptr(GError) error_local = NULL; if (!fu_quirks_validate_flags(value, &error_local)) { g_warning("[%s] %s = %s is invalid: %s", helper->group->str, key, value, error_local->message); } } /* add */ xb_builder_node_insert_text(helper->bn, "value", value, "key", key, NULL); return TRUE; } static GBytes * fu_quirks_convert_keyfile_to_xml(FuQuirks *self, GBytes *bytes, GError **error) { gsize xmlsz; g_autofree gchar *xml = NULL; g_autoptr(FuQuirksConvertHelper) helper = g_new0(FuQuirksConvertHelper, 1); /* split into lines */ helper->root = xb_builder_node_new("quirk"); helper->group = g_string_new(NULL); if (!fu_strsplit_full((const gchar *)g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes), "\n", fu_quirks_convert_keyfile_to_xml_cb, helper, error)) return NULL; /* export as XML blob */ xml = xb_builder_node_export(helper->root, XB_NODE_EXPORT_FLAG_ADD_HEADER, error); if (xml == NULL) return NULL; xmlsz = strlen(xml); return g_bytes_new_take(g_steal_pointer(&xml), xmlsz); } static GInputStream * fu_quirks_convert_quirk_to_xml_cb(XbBuilderSource *source, XbBuilderSourceCtx *ctx, gpointer user_data, GCancellable *cancellable, GError **error) { FuQuirks *self = FU_QUIRKS(user_data); g_autoptr(GBytes) bytes = NULL; g_autoptr(GBytes) bytes_xml = NULL; bytes = xb_builder_source_ctx_get_bytes(ctx, cancellable, error); if (bytes == NULL) return NULL; bytes_xml = fu_quirks_convert_keyfile_to_xml(self, bytes, error); if (bytes_xml == NULL) return NULL; return g_memory_input_stream_new_from_bytes(bytes_xml); } static gint fu_quirks_filename_sort_cb(gconstpointer a, gconstpointer b) { const gchar *stra = *((const gchar **)a); const gchar *strb = *((const gchar **)b); return g_strcmp0(stra, strb); } static gboolean fu_quirks_add_quirks_for_path(FuQuirks *self, XbBuilder *builder, const gchar *path, GError **error) { const gchar *tmp; g_autoptr(GDir) dir = NULL; g_autoptr(GPtrArray) filenames = g_ptr_array_new_with_free_func(g_free); g_info("loading quirks from %s", path); /* add valid files to the array */ if (!g_file_test(path, G_FILE_TEST_EXISTS)) return TRUE; dir = g_dir_open(path, 0, error); if (dir == NULL) return FALSE; while ((tmp = g_dir_read_name(dir)) != NULL) { if (!g_str_has_suffix(tmp, ".quirk") && !g_str_has_suffix(tmp, ".quirk.gz")) { g_debug("skipping invalid file %s", tmp); continue; } g_ptr_array_add(filenames, g_build_filename(path, tmp, NULL)); } /* sort */ g_ptr_array_sort(filenames, fu_quirks_filename_sort_cb); /* process files */ for (guint i = 0; i < filenames->len; i++) { const gchar *filename = g_ptr_array_index(filenames, i); g_autoptr(GFile) file = g_file_new_for_path(filename); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); /* load from keyfile */ xb_builder_source_add_simple_adapter(source, "text/plain,application/octet-stream,.quirk", fu_quirks_convert_quirk_to_xml_cb, self, NULL); if (!xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_WATCH_FILE | XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT, NULL, error)) { g_prefix_error(error, "failed to load %s: ", filename); return FALSE; } /* watch the file for changes */ xb_builder_import_source(builder, source); } /* success */ return TRUE; } static gint fu_quirks_strcasecmp_cb(gconstpointer a, gconstpointer b) { const gchar *entry1 = *((const gchar **)a); const gchar *entry2 = *((const gchar **)b); return g_ascii_strcasecmp(entry1, entry2); } static gboolean fu_quirks_check_silo(FuQuirks *self, GError **error) { XbBuilderCompileFlags compile_flags = XB_BUILDER_COMPILE_FLAG_WATCH_BLOB; g_autofree gchar *datadir = NULL; g_autofree gchar *localstatedir = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = NULL; g_autoptr(XbNode) n_any = NULL; /* everything is okay */ if (self->silo != NULL && xb_silo_is_valid(self->silo)) return TRUE; /* system datadir */ builder = xb_builder_new(); datadir = fu_path_from_kind(FU_PATH_KIND_DATADIR_QUIRKS); if (!fu_quirks_add_quirks_for_path(self, builder, datadir, error)) return FALSE; /* something we can write when using Ostree */ localstatedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_QUIRKS); if (!fu_quirks_add_quirks_for_path(self, builder, localstatedir, error)) return FALSE; /* load silo */ if (self->load_flags & FU_QUIRKS_LOAD_FLAG_NO_CACHE) { g_autoptr(GFileIOStream) iostr = NULL; file = g_file_new_tmp(NULL, &iostr, error); if (file == NULL) return FALSE; } else { g_autofree gchar *cachedirpkg = fu_path_from_kind(FU_PATH_KIND_CACHEDIR_PKG); g_autofree gchar *xmlbfn = g_build_filename(cachedirpkg, "quirks.xmlb", NULL); file = g_file_new_for_path(xmlbfn); } if (g_getenv("FWUPD_XMLB_VERBOSE") != NULL) { xb_builder_set_profile_flags(builder, XB_SILO_PROFILE_FLAG_XPATH | XB_SILO_PROFILE_FLAG_DEBUG); } if (self->load_flags & FU_QUIRKS_LOAD_FLAG_READONLY_FS) compile_flags |= XB_BUILDER_COMPILE_FLAG_IGNORE_GUID; self->silo = xb_builder_ensure(builder, file, compile_flags, NULL, error); if (self->silo == NULL) return FALSE; /* dump warnings to console, just once */ if (self->invalid_keys->len > 0) { g_autofree gchar *str = NULL; g_ptr_array_sort(self->invalid_keys, fu_quirks_strcasecmp_cb); str = fu_strjoin(",", self->invalid_keys); g_info("invalid key names: %s", str); } /* check if there is any quirk data to load, as older libxmlb versions will not be able to * create the prepared query with an unknown text ID */ n_any = xb_silo_query_first(self->silo, "quirk", NULL); if (n_any == NULL) { g_debug("no quirk data, not creating prepared queries"); return TRUE; } /* create prepared queries to save time later */ self->query_kv = xb_query_new_full(self->silo, "quirk/device[@id=?]/value[@key=?]", XB_QUERY_FLAG_OPTIMIZE, error); if (self->query_kv == NULL) { g_prefix_error(error, "failed to prepare query: "); return FALSE; } self->query_vs = xb_query_new_full(self->silo, "quirk/device[@id=?]/value", XB_QUERY_FLAG_OPTIMIZE, error); if (self->query_vs == NULL) { g_prefix_error(error, "failed to prepare query: "); return FALSE; } if (!xb_silo_query_build_index(self->silo, "quirk/device", "id", error)) return FALSE; if (!xb_silo_query_build_index(self->silo, "quirk/device/value", "key", error)) return FALSE; /* success */ return TRUE; } /** * fu_quirks_lookup_by_id: * @self: a #FuQuirks * @guid: GUID to lookup * @key: an ID to match the entry, e.g. `Name` * * Looks up an entry in the hardware database using a string value. * * Returns: (transfer none): values from the database, or %NULL if not found * * Since: 1.0.1 **/ const gchar * fu_quirks_lookup_by_id(FuQuirks *self, const gchar *guid, const gchar *key) { g_autoptr(GError) error = NULL; g_autoptr(XbNode) n = NULL; g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); g_return_val_if_fail(FU_IS_QUIRKS(self), NULL); g_return_val_if_fail(guid != NULL, NULL); g_return_val_if_fail(key != NULL, NULL); /* ensure up to date */ if (!fu_quirks_check_silo(self, &error)) { g_warning("failed to build silo: %s", error->message); return NULL; } /* no quirk data */ if (self->query_kv == NULL) return NULL; /* query */ xb_query_context_set_flags(&context, XB_QUERY_FLAG_USE_INDEXES); xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, guid, NULL); xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 1, key, NULL); n = xb_silo_query_first_with_context(self->silo, self->query_kv, &context, &error); if (n == NULL) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) return NULL; if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) return NULL; g_warning("failed to query: %s", error->message); return NULL; } if (self->verbose) g_debug("%s:%s → %s", guid, key, xb_node_get_text(n)); return xb_node_get_text(n); } /** * fu_quirks_lookup_by_id_iter: * @self: a #FuQuirks * @guid: GUID to lookup * @key: (nullable): an ID to match the entry, e.g. `Name`, or %NULL for all keys * @iter_cb: (scope call) (closure user_data): a function to call for each result * @user_data: user data passed to @iter_cb * * Looks up all entries in the hardware database using a GUID value. * * Returns: %TRUE if the ID was found, and @iter was called * * Since: 1.3.3 **/ gboolean fu_quirks_lookup_by_id_iter(FuQuirks *self, const gchar *guid, const gchar *key, FuQuirksIter iter_cb, gpointer user_data) { g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) results = NULL; g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); g_return_val_if_fail(FU_IS_QUIRKS(self), FALSE); g_return_val_if_fail(guid != NULL, FALSE); g_return_val_if_fail(iter_cb != NULL, FALSE); /* ensure up to date */ if (!fu_quirks_check_silo(self, &error)) { g_warning("failed to build silo: %s", error->message); return FALSE; } /* no quirk data */ if (self->query_vs == NULL) return FALSE; /* query */ xb_query_context_set_flags(&context, XB_QUERY_FLAG_USE_INDEXES); xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, guid, NULL); if (key != NULL) { xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 1, key, NULL); results = xb_silo_query_with_context(self->silo, self->query_kv, &context, &error); } else { results = xb_silo_query_with_context(self->silo, self->query_vs, &context, &error); } if (results == NULL) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) return FALSE; if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) return FALSE; g_warning("failed to query: %s", error->message); return FALSE; } for (guint i = 0; i < results->len; i++) { XbNode *n = g_ptr_array_index(results, i); if (self->verbose) g_debug("%s → %s", guid, xb_node_get_text(n)); iter_cb(self, xb_node_get_attr(n, "key"), xb_node_get_text(n), user_data); } return TRUE; } /** * fu_quirks_load: (skip) * @self: a #FuQuirks * @load_flags: load flags * @error: (nullable): optional return location for an error * * Loads the various files that define the hardware quirks used in plugins. * * Returns: %TRUE for success * * Since: 1.0.1 **/ gboolean fu_quirks_load(FuQuirks *self, FuQuirksLoadFlags load_flags, GError **error) { g_return_val_if_fail(FU_IS_QUIRKS(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); self->load_flags = load_flags; self->verbose = g_getenv("FWUPD_XMLB_VERBOSE") != NULL; return fu_quirks_check_silo(self, error); } /** * fu_quirks_add_possible_key: * @self: a #FuQuirks * @possible_key: a key name, e.g. `Flags` * * Adds a possible quirk key. If added by a plugin it should be namespaced * using the plugin name, where possible. * * Since: 1.5.8 **/ void fu_quirks_add_possible_key(FuQuirks *self, const gchar *possible_key) { g_return_if_fail(FU_IS_QUIRKS(self)); g_return_if_fail(possible_key != NULL); g_hash_table_add(self->possible_keys, g_strdup(possible_key)); } static void fu_quirks_class_init(FuQuirksClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_quirks_finalize; } static void fu_quirks_init(FuQuirks *self) { self->possible_keys = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); self->invalid_keys = g_ptr_array_new_with_free_func(g_free); /* built in */ fu_quirks_add_possible_key(self, FU_QUIRKS_BRANCH); fu_quirks_add_possible_key(self, FU_QUIRKS_CHILDREN); fu_quirks_add_possible_key(self, FU_QUIRKS_COUNTERPART_GUID); fu_quirks_add_possible_key(self, FU_QUIRKS_FIRMWARE_SIZE); fu_quirks_add_possible_key(self, FU_QUIRKS_FIRMWARE_SIZE_MAX); fu_quirks_add_possible_key(self, FU_QUIRKS_FIRMWARE_SIZE_MIN); fu_quirks_add_possible_key(self, FU_QUIRKS_FLAGS); fu_quirks_add_possible_key(self, FU_QUIRKS_GTYPE); fu_quirks_add_possible_key(self, FU_QUIRKS_FIRMWARE_GTYPE); fu_quirks_add_possible_key(self, FU_QUIRKS_GUID); fu_quirks_add_possible_key(self, FU_QUIRKS_ICON); fu_quirks_add_possible_key(self, FU_QUIRKS_INHIBIT); fu_quirks_add_possible_key(self, FU_QUIRKS_INSTALL_DURATION); fu_quirks_add_possible_key(self, FU_QUIRKS_ISSUE); fu_quirks_add_possible_key(self, FU_QUIRKS_NAME); fu_quirks_add_possible_key(self, FU_QUIRKS_PARENT_GUID); fu_quirks_add_possible_key(self, FU_QUIRKS_PLUGIN); fu_quirks_add_possible_key(self, FU_QUIRKS_PRIORITY); fu_quirks_add_possible_key(self, FU_QUIRKS_PROTOCOL); fu_quirks_add_possible_key(self, FU_QUIRKS_PROXY_GUID); fu_quirks_add_possible_key(self, FU_QUIRKS_BATTERY_THRESHOLD); fu_quirks_add_possible_key(self, FU_QUIRKS_REMOVE_DELAY); fu_quirks_add_possible_key(self, FU_QUIRKS_SUMMARY); fu_quirks_add_possible_key(self, FU_QUIRKS_UPDATE_IMAGE); fu_quirks_add_possible_key(self, FU_QUIRKS_UPDATE_MESSAGE); fu_quirks_add_possible_key(self, FU_QUIRKS_VENDOR); fu_quirks_add_possible_key(self, FU_QUIRKS_VENDOR_ID); fu_quirks_add_possible_key(self, FU_QUIRKS_VERSION); fu_quirks_add_possible_key(self, FU_QUIRKS_VERSION_FORMAT); fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_READ_ID); fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_READ_ID_SZ); fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_CHIP_ERASE); fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_BLOCK_ERASE); fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_SECTOR_ERASE); fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_WRITE_STATUS); fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_PAGE_PROG); fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_READ_DATA); fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_READ_STATUS); fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_CMD_WRITE_EN); fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_PAGE_SIZE); fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_SECTOR_SIZE); fu_quirks_add_possible_key(self, FU_QUIRKS_CFI_DEVICE_BLOCK_SIZE); } static void fu_quirks_finalize(GObject *obj) { FuQuirks *self = FU_QUIRKS(obj); if (self->query_kv != NULL) g_object_unref(self->query_kv); if (self->query_vs != NULL) g_object_unref(self->query_vs); if (self->silo != NULL) g_object_unref(self->silo); g_hash_table_unref(self->possible_keys); g_ptr_array_unref(self->invalid_keys); G_OBJECT_CLASS(fu_quirks_parent_class)->finalize(obj); } /** * fu_quirks_new: (skip) * * Creates a new quirks object. * * Returns: a new #FuQuirks * * Since: 1.0.1 **/ FuQuirks * fu_quirks_new(void) { FuQuirks *self; self = g_object_new(FU_TYPE_QUIRKS, NULL); return FU_QUIRKS(self); } fwupd-1.9.16/libfwupdplugin/fu-quirks.h000066400000000000000000000245061460375044200201030ustar00rootroot00000000000000/* * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_QUIRKS (fu_quirks_get_type()) G_DECLARE_FINAL_TYPE(FuQuirks, fu_quirks, FU, QUIRKS, GObject) /** * FuQuirksLoadFlags: * @FU_QUIRKS_LOAD_FLAG_NONE: No flags set * @FU_QUIRKS_LOAD_FLAG_READONLY_FS: Ignore readonly filesystem errors * @FU_QUIRKS_LOAD_FLAG_NO_CACHE: Do not save to a persistent cache * @FU_QUIRKS_LOAD_FLAG_NO_VERIFY: Do not check the key files for errors * * The flags to use when loading quirks. **/ typedef enum { FU_QUIRKS_LOAD_FLAG_NONE = 0, FU_QUIRKS_LOAD_FLAG_READONLY_FS = 1 << 0, FU_QUIRKS_LOAD_FLAG_NO_CACHE = 1 << 1, FU_QUIRKS_LOAD_FLAG_NO_VERIFY = 1 << 2, /*< private >*/ FU_QUIRKS_LOAD_FLAG_LAST } FuQuirksLoadFlags; /** * FuQuirksIter: * @self: a #FuQuirks * @key: a key * @value: a value * @user_data: (closure): user data * * The quirks iteration callback. */ typedef void (*FuQuirksIter)(FuQuirks *self, const gchar *key, const gchar *value, gpointer user_data); FuQuirks * fu_quirks_new(void); gboolean fu_quirks_load(FuQuirks *self, FuQuirksLoadFlags load_flags, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); const gchar * fu_quirks_lookup_by_id(FuQuirks *self, const gchar *guid, const gchar *key) G_GNUC_NON_NULL(1, 2, 3); gboolean fu_quirks_lookup_by_id_iter(FuQuirks *self, const gchar *guid, const gchar *key, FuQuirksIter iter_cb, gpointer user_data) G_GNUC_NON_NULL(1, 2); void fu_quirks_add_possible_key(FuQuirks *self, const gchar *possible_key) G_GNUC_NON_NULL(1, 2); /** * FU_QUIRKS_PLUGIN: * * The quirk key for the plugin name, e.g. `csr`. * * Since: 1.3.7 **/ #define FU_QUIRKS_PLUGIN "Plugin" /** * FU_QUIRKS_FLAGS: * * The quirk key for either for public, internal or private flags, e.g. `is-bootloader`. * * Since: 1.3.7 **/ #define FU_QUIRKS_FLAGS "Flags" /** * FU_QUIRKS_SUMMARY: * * The quirk key for the summary, e.g. `An open source display colorimeter`. * * Since: 1.3.7 **/ #define FU_QUIRKS_SUMMARY "Summary" /** * FU_QUIRKS_ICON: * * The quirk key for the icon, e.g. `media-removable`. * * Since: 1.3.7 **/ #define FU_QUIRKS_ICON "Icon" /** * FU_QUIRKS_NAME: * * The quirk key for the name, e.g. `ColorHug`. * * Since: 1.3.7 **/ #define FU_QUIRKS_NAME "Name" /** * FU_QUIRKS_BRANCH: * * The quirk key for the firmware branch. * * Since: 1.5.0 **/ #define FU_QUIRKS_BRANCH "Branch" /** * FU_QUIRKS_GUID: * * The quirk key for the GUID, e.g. `537f7800-8529-5656-b2fa-b0901fe91696`. * * If the value provided is not already a suitable GUID, it will be converted to one. * * Since: 1.3.7 **/ #define FU_QUIRKS_GUID "Guid" /** * FU_QUIRKS_GUID_QUIRK: * * The quirk key for the GUID, only used for quirk matching, e.g. `SYNAPTICS_CAPE\CX31993`. * * If the value provided is not already a suitable GUID, it will be converted to one. * * Since: 1.9.6 **/ #define FU_QUIRKS_GUID_QUIRK "Guid[quirk]" /** * FU_QUIRKS_COUNTERPART_GUID: * * The quirk key for the counterpart GUID, e.g. `537f7800-8529-5656-b2fa-b0901fe91696`. * * A counterpart GUID is typically the GUID of the same device in bootloader or runtime mode, * if they have a different device PCI or USB ID. * Adding this type of GUID does not cause a "cascade" by matching using the quirk database. * * If the value provided is not already a suitable GUID, it will be converted to one. * * Since: 1.3.7 **/ #define FU_QUIRKS_COUNTERPART_GUID "CounterpartGuid" /** * FU_QUIRKS_PARENT_GUID: * * The quirk key for the parent GUID, e.g. `537f7800-8529-5656-b2fa-b0901fe91696`. * * If the value provided is not already a suitable GUID, it will be converted to one. * * Since: 1.3.7 **/ #define FU_QUIRKS_PARENT_GUID "ParentGuid" /** * FU_QUIRKS_PROXY_GUID: * * The quirk key for the proxy GUID, e.g. `537f7800-8529-5656-b2fa-b0901fe91696`. * * Since: 1.4.1 **/ #define FU_QUIRKS_PROXY_GUID "ProxyGuid" /** * FU_QUIRKS_CHILDREN: * * The quirk key for the children. This should contain the custom GType, e.g. * `FuRts54xxDeviceUSB\VID_0763&PID_2806&I2C_01`. * * This allows the quirk entry to adds one or more virtual devices to a physical device. * If the type of device is not specified the parent device type is used. * * Since: 1.3.7 **/ #define FU_QUIRKS_CHILDREN "Children" /** * FU_QUIRKS_VERSION: * * The quirk key for the version, e.g. `1.2.3`. * * Since: 1.3.7 **/ #define FU_QUIRKS_VERSION "Version" /** * FU_QUIRKS_VENDOR: * * The quirk key for the vendor name, e.g. `Hughski Limited`. * * Since: 1.3.7 **/ #define FU_QUIRKS_VENDOR "Vendor" /** * FU_QUIRKS_VENDOR_ID: * * The quirk key for the vendor ID, e.g. `USB:0x123A`. * * Since: 1.3.7 **/ #define FU_QUIRKS_VENDOR_ID "VendorId" /** * FU_QUIRKS_FIRMWARE_SIZE_MIN: * * The quirk key for the minimum firmware size in bytes, e.g. `512`. * * Since: 1.3.7 **/ #define FU_QUIRKS_FIRMWARE_SIZE_MIN "FirmwareSizeMin" /** * FU_QUIRKS_FIRMWARE_SIZE_MAX: * * The quirk key for the maximum firmware size in bytes, e.g. `1024`. * * Since: 1.3.7 **/ #define FU_QUIRKS_FIRMWARE_SIZE_MAX "FirmwareSizeMax" /** * FU_QUIRKS_FIRMWARE_SIZE: * * The quirk key for the exact required firmware size in bytes, e.g. `1024`. * * Since: 1.3.7 **/ #define FU_QUIRKS_FIRMWARE_SIZE "FirmwareSize" /** * FU_QUIRKS_INSTALL_DURATION: * * The quirk key for the install duration in seconds, e.g. `60`. * * Since: 1.3.7 **/ #define FU_QUIRKS_INSTALL_DURATION "InstallDuration" /** * FU_QUIRKS_VERSION_FORMAT: * * The quirk key for the version format, e.g. `quad`. * * Since: 1.3.7 **/ #define FU_QUIRKS_VERSION_FORMAT "VersionFormat" /** * FU_QUIRKS_GTYPE: * * The quirk key for the custom GType, e.g. `FuCcgxHidDevice`. * * Since: 1.3.7 **/ #define FU_QUIRKS_GTYPE "GType" /** * FU_QUIRKS_PROXY_GTYPE: * * The quirk key for the custom proxy GType, e.g. `FuCcgxHidDevice`. * * Since: 1.9.15 **/ #define FU_QUIRKS_PROXY_GTYPE "ProxyGType" /** * FU_QUIRKS_FIRMWARE_GTYPE: * * The quirk key for the custom firmware GType, e.g. `FuUswidFirmware`. * * Since: 1.7.2 **/ #define FU_QUIRKS_FIRMWARE_GTYPE "FirmwareGType" /** * FU_QUIRKS_PROTOCOL: * * The quirk key for the protocol, e.g. `org.usb.dfu`. * * Since: 1.3.7 **/ #define FU_QUIRKS_PROTOCOL "Protocol" /** * FU_QUIRKS_UPDATE_MESSAGE: * * The quirk key for the update message shown after the transaction has completed. * * Since: 1.4.0 **/ #define FU_QUIRKS_UPDATE_MESSAGE "UpdateMessage" /** * FU_QUIRKS_UPDATE_IMAGE: * * The quirk key for the update image shown before the update is performed. * * Since: 1.5.0 **/ #define FU_QUIRKS_UPDATE_IMAGE "UpdateImage" /** * FU_QUIRKS_PRIORITY: * * The quirk key for the device priority, e.g. `2`. * * Since: 1.4.1 **/ #define FU_QUIRKS_PRIORITY "Priority" /** * FU_QUIRKS_BATTERY_THRESHOLD: * * The quirk key for the battery threshold in percent, e.g. `80`. * * Since: 1.6.0 **/ #define FU_QUIRKS_BATTERY_THRESHOLD "BatteryThreshold" /** * FU_QUIRKS_REMOVE_DELAY: * * The quirk key for the device removal delay in milliseconds, e.g. `2500`. * * Since: 1.5.0 **/ #define FU_QUIRKS_REMOVE_DELAY "RemoveDelay" /** * FU_QUIRKS_ACQUIESCE_DELAY: * * The quirk key for the device removal delay in milliseconds, e.g. `2500`. * * Since: 1.8.3 **/ #define FU_QUIRKS_ACQUIESCE_DELAY "AcquiesceDelay" /** * FU_QUIRKS_INHIBIT: * * The quirk key to inhibit the UPDATABLE flag and to set an update error, e.g. `In safe mode`. * * Since: 1.6.2 **/ #define FU_QUIRKS_INHIBIT "Inhibit" /** * FU_QUIRKS_ISSUE: * * The quirk key to add security issues affecting a specific device, e.g. * `https://www.pugetsystems.com/support/guides/critical-samsung-ssd-firmware-update/`. * * Since: 1.7.6 **/ #define FU_QUIRKS_ISSUE "Issue" /** * FU_QUIRKS_CFI_DEVICE_CMD_READ_ID * * The quirk key to set the CFI read ID command, e.g. `0xF8`. * * Since: 1.8.2 **/ #define FU_QUIRKS_CFI_DEVICE_CMD_READ_ID "CfiDeviceCmdReadId" /** * FU_QUIRKS_CFI_DEVICE_CMD_READ_ID_SZ * * The quirk key to set the CFI read ID size, e.g. `0xF8`. * * Since: 1.8.2 **/ #define FU_QUIRKS_CFI_DEVICE_CMD_READ_ID_SZ "CfiDeviceCmdReadIdSz" /** * FU_QUIRKS_CFI_DEVICE_CMD_CHIP_ERASE * * The quirk key to set the CFI chip erase command, e.g. `0xF8`. * * Since: 1.8.2 **/ #define FU_QUIRKS_CFI_DEVICE_CMD_CHIP_ERASE "CfiDeviceCmdChipErase" /** * FU_QUIRKS_CFI_DEVICE_CMD_BLOCK_ERASE * * The quirk key to set the CFI block erase command, e.g. `0xF8`. * * Since: 1.8.2 **/ #define FU_QUIRKS_CFI_DEVICE_CMD_BLOCK_ERASE "CfiDeviceCmdBlockErase" /** * FU_QUIRKS_CFI_DEVICE_CMD_SECTOR_ERASE * * The quirk key to set the CFI sector erase command, e.g. `0xF8`. * * Since: 1.8.2 **/ #define FU_QUIRKS_CFI_DEVICE_CMD_SECTOR_ERASE "CfiDeviceCmdSectorErase" /** * FU_QUIRKS_CFI_DEVICE_CMD_WRITE_STATUS * * The quirk key to set the CFI write status command, e.g. `0xF8`. * * Since: 1.8.2 **/ #define FU_QUIRKS_CFI_DEVICE_CMD_WRITE_STATUS "CfiDeviceCmdWriteStatus" /** * FU_QUIRKS_CFI_DEVICE_CMD_PAGE_PROG * * The quirk key to set the CFI page program command, e.g. `0xF8`. * * Since: 1.8.2 **/ #define FU_QUIRKS_CFI_DEVICE_CMD_PAGE_PROG "CfiDeviceCmdPageProg" /** * FU_QUIRKS_CFI_DEVICE_CMD_READ_DATA * * The quirk key to set the CFI read data command, e.g. `0xF8`. * * Since: 1.8.2 **/ #define FU_QUIRKS_CFI_DEVICE_CMD_READ_DATA "CfiDeviceCmdReadData" /** * FU_QUIRKS_CFI_DEVICE_CMD_READ_STATUS * * The quirk key to set the CFI read status command, e.g. `0xF8`. * * Since: 1.8.2 **/ #define FU_QUIRKS_CFI_DEVICE_CMD_READ_STATUS "CfiDeviceCmdReadStatus" /** * FU_QUIRKS_CFI_DEVICE_CMD_WRITE_EN * * The quirk key to set the CFI write en command, e.g. `0xF8`. * * Since: 1.8.2 **/ #define FU_QUIRKS_CFI_DEVICE_CMD_WRITE_EN "CfiDeviceCmdWriteEn" /** * FU_QUIRKS_CFI_DEVICE_PAGE_SIZE * * The quirk key to set the CFI page size, e.g. `0xF8`. * * Since: 1.8.2 **/ #define FU_QUIRKS_CFI_DEVICE_PAGE_SIZE "CfiDevicePageSize" /** * FU_QUIRKS_CFI_DEVICE_SECTOR_SIZE * * The quirk key to set the CFI sector size, e.g. `0x100`. * * Since: 1.8.2 **/ #define FU_QUIRKS_CFI_DEVICE_SECTOR_SIZE "CfiDeviceSectorSize" /** * FU_QUIRKS_CFI_DEVICE_BLOCK_SIZE * * The quirk key to set the CFI block size, e.g. `0x100`. * * Since: 1.8.2 **/ #define FU_QUIRKS_CFI_DEVICE_BLOCK_SIZE "CfiDeviceBlockSize" fwupd-1.9.16/libfwupdplugin/fu-rustgen-enum.c.in000066400000000000000000000022501460375044200216060ustar00rootroot00000000000000 {%- set export = obj.export('ToString') %} {%- if export in [Export.PUBLIC, Export.PRIVATE] %} {{export.value}}const gchar * {{obj.c_method('ToString')}}({{obj.c_type}} val) { {%- for item in obj.items %} if (val == {{item.c_define}}) return "{{item.value}}"; {%- endfor %} return NULL; } {%- endif %} {%- set export = obj.export('ToBitString') %} {%- if export in [Export.PUBLIC, Export.PRIVATE] %} {{export.value}}gchar * {{obj.c_method('ToString')}}({{obj.c_type}} val) { const gchar *data[{{obj.items|length}}] = {0}; guint idx = 0; if (val == {{obj.items[0].c_define}}) return g_strdup("{{obj.items[0].value}}"); {%- for item in obj.items[1:] %} if (val & {{item.c_define}}) data[idx++] = "{{item.value}}"; {%- endfor %} return g_strjoinv(",", (gchar **)data); } {%- endif %} {%- set export = obj.export('FromString') %} {%- if export in [Export.PUBLIC, Export.PRIVATE] %} {{export.value}}{{obj.c_type}} {{obj.c_method('FromString')}}(const gchar *val) { {%- for item in obj.items %} if (g_strcmp0(val, "{{item.value}}") == 0) return {{item.c_define}}; {%- endfor %} return {{obj.items[0].c_define}}; } {%- endif %} fwupd-1.9.16/libfwupdplugin/fu-rustgen-enum.h.in000066400000000000000000000010711460375044200216130ustar00rootroot00000000000000typedef enum { {%- for item in obj.items %} {%- if item.default %} {{item.c_define}} = {{item.default}}, {%- else %} {{item.c_define}}, {%- endif %} {%- endfor %} } {{obj.c_type}}; {%- if obj.export('ToString') == Export.PUBLIC %} const gchar *{{obj.c_method('ToString')}}({{obj.c_type}} val); {%- endif %} {%- if obj.export('ToBitString') == Export.PUBLIC %} gchar *{{obj.c_method('ToString')}}({{obj.c_type}} val); {%- endif %} {%- if obj.export('FromString') == Export.PUBLIC %} {{obj.c_type}} {{obj.c_method('FromString')}}(const gchar *val); {%- endif %} fwupd-1.9.16/libfwupdplugin/fu-rustgen-struct.c.in000066400000000000000000000276451460375044200222050ustar00rootroot00000000000000/* getters */ {%- for item in obj.items | selectattr('enabled') %} {%- set export = item.export('Getters') %} {%- if export in [Export.PUBLIC, Export.PRIVATE] %} /** * {{item.c_getter}}: (skip): **/ {%- if item.type == Type.STRING %} {{export.value}}gchar * {{item.c_getter}}(const GByteArray *st) { g_return_val_if_fail(st != NULL, NULL); return fu_memstrsafe(st->data, st->len, {{item.offset}}, {{item.size}}, NULL); } {%- elif item.struct_obj %} {{export.value}}GByteArray * {{item.c_getter}}(const GByteArray *st) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_return_val_if_fail(st != NULL, NULL); g_byte_array_append(buf, st->data + {{item.c_define('OFFSET')}}, {{item.size}}); return g_steal_pointer(&buf); } {%- elif item.type == Type.U8 and item.multiplier %} {{export.value}}const guint8 * {{item.c_getter}}(const GByteArray *st, gsize *bufsz) { g_return_val_if_fail(st != NULL, NULL); if (bufsz != NULL) *bufsz = {{item.size}}; return st->data + {{item.offset}}; } {%- elif item.type == Type.GUID %} {{export.value}}const fwupd_guid_t * {{item.c_getter}}(const GByteArray *st) { g_return_val_if_fail(st != NULL, NULL); return (const fwupd_guid_t *) (st->data + {{item.offset}}); } {%- elif item.type == Type.U8 %} {{export.value}}{{item.type_glib}} {{item.c_getter}}(const GByteArray *st) { g_return_val_if_fail(st != NULL, 0x0); return st->data[{{item.offset}}]; } {%- elif not item.multiplier and item.type in [Type.U16, Type.U24, Type.U32, Type.U64] %} {{export.value}}{{item.type_glib}} {{item.c_getter}}(const GByteArray *st) { g_return_val_if_fail(st != NULL, 0x0); return fu_memread_{{item.type_mem}}(st->data + {{item.offset}}, {{item.endian_glib}}); } {%- endif %} {%- endif %} {%- endfor %} /* setters */ {%- for item in obj.items | selectattr('enabled') %} {%- set export = item.export('Setters') %} {%- if export in [Export.PUBLIC, Export.PRIVATE] %} /** * {{item.c_setter}}: (skip): **/ {%- if item.type == Type.STRING %} {{export.value}}gboolean {{item.c_setter}}(GByteArray *st, const gchar *value, GError **error) { gsize len; g_return_val_if_fail(st != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (value == NULL) { memset(st->data + {{item.offset}}, 0x0, {{item.size}}); return TRUE; } len = strlen(value); if (len > {{item.size}}) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "string '%s' (0x%x bytes) does not fit in {{obj.name}}.{{item.element_id}} (0x%x bytes)", value, (guint) len, (guint) {{item.size}}); return FALSE; } return fu_memcpy_safe(st->data, st->len, {{item.offset}}, (const guint8 *)value, len, 0x0, len, error); } {%- elif item.struct_obj %} {{export.value}}gboolean {{item.c_setter}}(GByteArray *st, GByteArray *st_donor, GError **error) { g_return_val_if_fail(st != NULL, FALSE); g_return_val_if_fail(st_donor != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return fu_memcpy_safe(st->data, st->len, {{item.c_define('OFFSET')}}, /* dst */ st_donor->data, st_donor->len, 0x0, /* src */ {{item.size}}, error); } {%- elif item.type == Type.U8 and item.multiplier %} {{export.value}}gboolean {{item.c_setter}}(GByteArray *st, const guint8 *buf, gsize bufsz, GError **error) { g_return_val_if_fail(st != NULL, FALSE); g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return fu_memcpy_safe(st->data, st->len, {{item.offset}}, buf, bufsz, 0x0, bufsz, error); } {%- elif item.type == Type.GUID %} {{export.value}}void {{item.c_setter}}(GByteArray *st, const fwupd_guid_t *value) { g_return_if_fail(st != NULL); g_return_if_fail(value != NULL); memcpy(st->data + {{item.offset}}, value, sizeof(*value)); } {%- elif item.type == Type.U8 %} {{export.value}}void {{item.c_setter}}(GByteArray *st, {{item.type_glib}} value) { g_return_if_fail(st != NULL); st->data[{{item.offset}}] = value; } {%- elif not item.multiplier and item.type in [Type.U16, Type.U24, Type.U32, Type.U64] %} {{export.value}}void {{item.c_setter}}(GByteArray *st, {{item.type_glib}} value) { g_return_if_fail(st != NULL); fu_memwrite_{{item.type_mem}}(st->data + {{item.offset}}, value, {{item.endian_glib}}); } {%- endif %} {%- endif %} {%- endfor %} {%- set export = obj.export('New') %} {%- if export in [Export.PUBLIC, Export.PRIVATE] %} /** * {{obj.c_method('New')}}: (skip): **/ {{export.value}}GByteArray * {{obj.c_method('New')}}(void) { GByteArray *st = g_byte_array_sized_new({{obj.size}}); fu_byte_array_set_size(st, {{obj.size}}, 0x0); {%- for item in obj.items | selectattr('padding') %} memset(st->data + {{item.offset}}, {{item.padding}}, {{item.size}}); {%- endfor %} {%- for item in obj.items | selectattr('struct_obj') %} { g_autoptr(GByteArray) st_donor = {{item.struct_obj.c_method('New')}}(); memcpy(st->data + 0x{{'{:X}'.format(item.offset)}}, st_donor->data, st_donor->len); } {%- endfor %} {%- for item in obj.items | selectattr('default') %} {%- if item.type == Type.STRING %} {{item.c_setter}}(st, "{{item.default}}", NULL); {%- elif item.type == Type.GUID %} {{item.c_setter}}(st, (fwupd_guid_t *) "{{item.default}}"); {%- elif item.type == Type.U8 and item.multiplier %} memcpy(st->data + 0x{{'{:X}'.format(item.offset)}}, "{{item.default}}", {{item.size}}); {%- else %} {{item.c_setter}}(st, {{item.default}}); {%- endif %} {%- endfor %} return st; } {%- endif %} {%- set export = obj.export('ToString') %} {%- if export in [Export.PUBLIC, Export.PRIVATE] %} /** * {{obj.c_method('ToString')}}: (skip): **/ {{export.value}}gchar * {{obj.c_method('ToString')}}(const GByteArray *st) { g_autoptr(GString) str = g_string_new("{{obj.name}}:\n"); g_return_val_if_fail(st != NULL, NULL); {%- for item in obj.items | selectattr('enabled') | rejectattr('constant') %} {%- if not item.multiplier and item.type in [Type.U8, Type.U16, Type.U24, Type.U32, Type.U64] %} {%- if item.enum_obj %} { const gchar *tmp = {{item.enum_obj.c_method('ToString')}}({{item.c_getter}}(st)); if (tmp != NULL) { g_string_append_printf(str, " {{item.element_id}}: 0x%x [%s]\n", (guint) {{item.c_getter}}(st), tmp); } else { g_string_append_printf(str, " {{item.element_id}}: 0x%x\n", (guint) {{item.c_getter}}(st)); } } {%- else %} g_string_append_printf(str, " {{item.element_id}}: 0x%x\n", (guint) {{item.c_getter}}(st)); {%- endif %} {%- elif item.type == Type.GUID %} { g_autofree gchar *tmp = fwupd_guid_to_string({{item.c_getter}}(st), FWUPD_GUID_FLAG_MIXED_ENDIAN); g_string_append_printf(str, " {{item.element_id}}: %s\n", tmp); } {%- elif item.type == Type.STRING %} { g_autofree gchar *tmp = {{item.c_getter}}(st); if (tmp != NULL) g_string_append_printf(str, " {{item.element_id}}: %s\n", tmp); } {%- elif item.struct_obj %} { g_autoptr(GByteArray) st_tmp = g_byte_array_new(); g_autofree gchar *tmp = NULL; g_byte_array_append(st_tmp, st->data + {{item.offset}}, {{item.size}}); tmp = {{item.struct_obj.c_method('ToString')}}(st_tmp); g_string_append_printf(str, " {{item.element_id}}: %s\n", tmp); } {%- else %} { gsize bufsz = 0; const guint8 *buf = {{item.c_getter}}(st, &bufsz); g_autoptr(GString) tmp = g_string_new(NULL); for (gsize i = 0; i < bufsz; i++) g_string_append_printf(tmp, "%02X", buf[i]); g_string_append_printf(str, " {{item.element_id}}: 0x%s\n", tmp->str); } {%- endif %} {%- endfor %} if (str->len > 0) g_string_set_size(str, str->len - 1); return g_string_free(g_steal_pointer(&str), FALSE); } {%- endif %} {%- set export = obj.export('Parse') %} {%- if export in [Export.PUBLIC, Export.PRIVATE] %} /** * {{obj.c_method('Parse')}}: (skip): **/ {{export.value}}GByteArray * {{obj.c_method('Parse')}}(const guint8 *buf, gsize bufsz, gsize offset, GError **error) { g_autoptr(GByteArray) st = g_byte_array_new(); g_autofree gchar *str = NULL; g_return_val_if_fail(buf != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (!fu_memchk_read(bufsz, offset, {{obj.size}}, error)) { g_prefix_error(error, "invalid struct {{obj.name}}: "); return NULL; } g_byte_array_append(st, buf + offset, {{obj.size}}); {%- for item in obj.items | selectattr('struct_obj') %} if (!{{item.struct_obj.c_method('Validate')}}(st->data, st->len, {{item.c_define('OFFSET')}}, error)) return NULL; {%- endfor %} {%- for item in obj.items | selectattr('constant') %} {%- if item.type == Type.STRING %} if (strncmp((const gchar *) (st->data + {{item.offset}}), "{{item.constant}}", {{item.size}}) != 0) { {%- elif item.type == Type.GUID or (item.type == Type.U8 and item.multiplier) %} if (memcmp(st->data + {{item.offset}}, "{{item.constant}}", {{item.size}}) != 0) { {%- else %} if ({{item.c_getter}}(st) != {{item.constant}}) { {%- endif %} g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "constant {{obj.name}}.{{item.element_id}} was not valid, expected {{item.constant}}"); return NULL; } {%- endfor %} str = {{obj.c_method('ToString')}}(st); g_debug("%s", str); return g_steal_pointer(&st); } {%- endif %} {%- set export = obj.export('ParseBytes') %} {%- if export in [Export.PUBLIC, Export.PRIVATE] %} /** * {{obj.c_method('ParseBytes')}}: (skip): **/ {{export.value}}GByteArray * {{obj.c_method('ParseBytes')}}(GBytes *blob, gsize offset, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(blob, &bufsz); return {{obj.c_method('Parse')}}(buf, bufsz, offset, error); } {%- endif %} {%- set export = obj.export('Validate') %} {%- if export in [Export.PUBLIC, Export.PRIVATE] %} /** * {{obj.c_method('Validate')}}: (skip): **/ {{export.value}}gboolean {{obj.c_method('Validate')}}(const guint8 *buf, gsize bufsz, gsize offset, GError **error) { {%- if obj.has_constant %} GByteArray st = {.data = (guint8 *) buf + offset, .len = bufsz - offset, }; {%- endif %} g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fu_memchk_read(bufsz, offset, {{obj.size}}, error)) { g_prefix_error(error, "invalid struct {{obj.name}}: "); return FALSE; } {%- for item in obj.items | selectattr('struct_obj') %} if (!{{item.struct_obj.c_method('Validate')}}(buf, bufsz, offset + {{item.c_define('OFFSET')}}, error)) return FALSE; {%- endfor %} {%- for item in obj.items | selectattr('constant') %} {%- if item.type == Type.STRING %} if (strncmp((const gchar *) (st.data + {{item.offset}}), "{{item.constant}}", {{item.size}}) != 0) { {%- elif item.type == Type.GUID or (item.type == Type.U8 and item.multiplier) %} if (memcmp({{item.c_getter}}(&st), "{{item.constant}}", {{item.size}}) != 0) { {%- else %} if ({{item.c_getter}}(&st) != {{item.constant}}) { {%- endif %} g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "constant {{obj.name}}.{{item.element_id}} was not valid"); return FALSE; } {%- endfor %} return TRUE; } {%- endif %} {%- set export = obj.export('ValidateBytes') %} {%- if export in [Export.PUBLIC, Export.PRIVATE] %} /** * {{obj.c_method('ValidateBytes')}}: (skip): **/ {{export.value}}gboolean {{obj.c_method('ValidateBytes')}}(GBytes *blob, gsize offset, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(blob, &bufsz); return {{obj.c_method('Validate')}}(buf, bufsz, offset, error); } {%- endif %} fwupd-1.9.16/libfwupdplugin/fu-rustgen-struct.h.in000066400000000000000000000056241460375044200222030ustar00rootroot00000000000000{%- if obj.export('New') == Export.PUBLIC %} GByteArray *{{obj.c_method('New')}}(void); {%- endif %} {%- if obj.export('Parse') == Export.PUBLIC %} GByteArray *{{obj.c_method('Parse')}}(const guint8 *buf, gsize bufsz, gsize offset, GError **error); {%- endif %} {%- if obj.export('ParseBytes') == Export.PUBLIC %} GByteArray *{{obj.c_method('ParseBytes')}}(GBytes *blob, gsize offset, GError **error); {%- endif %} {%- if obj.export('Validate') == Export.PUBLIC %} gboolean {{obj.c_method('Validate')}}(const guint8 *buf, gsize bufsz, gsize offset, GError **error); {%- endif %} {%- if obj.export('ValidateBytes') == Export.PUBLIC %} gboolean {{obj.c_method('ValidateBytes')}}(GBytes *blob, gsize offset, GError **error); {%- endif %} {%- if obj.export('ToString') == Export.PUBLIC %} gchar *{{obj.c_method('ToString')}}(const GByteArray *st); {%- endif %} {%- for item in obj.items | selectattr('enabled') %} {%- if item.export('Getters') == Export.PUBLIC %} {%- if item.type == Type.STRING %} gchar *{{item.c_getter}}(const GByteArray *st); {%- elif item.struct_obj %} GByteArray *{{item.c_getter}}(const GByteArray *st); {%- elif item.type == Type.U8 and item.multiplier %} const guint8 *{{item.c_getter}}(const GByteArray *st, gsize *bufsz); {%- elif item.type == Type.GUID %} const fwupd_guid_t *{{item.c_getter}}(const GByteArray *st); {%- elif not item.multiplier and item.type in [Type.U8, Type.U16, Type.U24, Type.U32, Type.U64] %} {{item.type_glib}} {{item.c_getter}}(const GByteArray *st); {%- endif %} {%- endif %} {%- endfor %} {%- for item in obj.items | selectattr('enabled') %} {%- if item.export('Setters') == Export.PUBLIC %} {%- if item.type == Type.STRING %} gboolean {{item.c_setter}}(GByteArray *st, const gchar *value, GError **error); {%- elif item.struct_obj %} gboolean {{item.c_setter}}(GByteArray *st, GByteArray *st_donor, GError **error); {%- elif item.type == Type.U8 and item.multiplier %} gboolean {{item.c_setter}}(GByteArray *st, const guint8 *buf, gsize bufsz, GError **error); {%- elif item.type == Type.GUID %} void {{item.c_setter}}(GByteArray *st, const fwupd_guid_t *value); {%- elif not item.multiplier and item.type in [Type.U8, Type.U16, Type.U24, Type.U32, Type.U64] %} void {{item.c_setter}}(GByteArray *st, {{item.type_glib}} value); {%- endif %} {%- endif %} {%- endfor %} {%- for item in obj.items | selectattr('enabled') %} #define {{item.c_define('OFFSET')}} 0x{{'{:X}'.format(item.offset)}} {%- endfor %} {%- for item in obj.items | selectattr('enabled') | selectattr('multiplier') %} #define {{item.c_define('SIZE')}} 0x{{'{:X}'.format(item.size)}} {%- endfor %} #define {{obj.c_define('SIZE')}} 0x{{'{:X}'.format(obj.size)}} {%- for item in obj.items | selectattr('enabled') | selectattr('default') %} {%- if item.type == Type.STRING or item.multiplier %} #define {{item.c_define('DEFAULT')}} "{{item.default}}" {%- else %} #define {{item.c_define('DEFAULT')}} {{item.default}} {%- endif %} {%- endfor %} fwupd-1.9.16/libfwupdplugin/fu-rustgen.c.in000066400000000000000000000003611460375044200206450ustar00rootroot00000000000000/* auto-generated, do not modify */ #include "config.h" #include "{{basename}}" #include "fu-byte-array.h" #include "fu-mem-private.h" #include "fu-string.h" #ifdef G_LOG_DOMAIN #undef G_LOG_DOMAIN #endif #define G_LOG_DOMAIN "FuStruct" fwupd-1.9.16/libfwupdplugin/fu-rustgen.h.in000066400000000000000000000001131460375044200206450ustar00rootroot00000000000000/* auto-generated, do not modify */ #pragma once #include fwupd-1.9.16/libfwupdplugin/fu-sbatlevel-section.c000066400000000000000000000057071460375044200222050ustar00rootroot00000000000000/* * Copyright (C) 2023 Canonical Ltd. * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-bytes.h" #include "fu-csv-firmware.h" #include "fu-mem.h" #include "fu-sbatlevel-section-struct.h" #include "fu-sbatlevel-section.h" G_DEFINE_TYPE(FuSbatlevelSection, fu_sbatlevel_section, FU_TYPE_FIRMWARE); static gboolean fu_sbatlevel_section_add_entry(FuFirmware *firmware, GBytes *fw, gsize offset, const gchar *entry_name, guint64 entry_idx, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); gsize size = 0; g_autoptr(FuFirmware) entry_fw = NULL; g_autoptr(GBytes) entry_blob = NULL; /* look for the null terminator */ for (size = 0; ((offset + size) < bufsz); ++size) { if (buf[offset + size] == 0) break; } entry_fw = fu_csv_firmware_new(); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(entry_fw), "$id"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(entry_fw), "component_generation"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(entry_fw), "date_stamp"); fu_firmware_set_idx(entry_fw, entry_idx); fu_firmware_set_id(entry_fw, entry_name); fu_firmware_set_offset(entry_fw, offset); entry_blob = fu_bytes_new_offset(fw, offset, size, error); if (entry_blob == NULL) return FALSE; if (!fu_firmware_add_image_full(firmware, entry_fw, error)) return FALSE; if (!fu_firmware_parse(entry_fw, entry_blob, flags, error)) { g_prefix_error(error, "failed to parse %s: ", entry_name); return FALSE; } return TRUE; } static gboolean fu_sbatlevel_section_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { gsize header_offset = offset + FU_STRUCT_SBAT_LEVEL_SECTION_HEADER_OFFSET_PREVIOUS; guint32 previous_addr; guint32 latest_addr; g_autoptr(GByteArray) st = NULL; st = fu_struct_sbat_level_section_header_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; previous_addr = fu_struct_sbat_level_section_header_get_previous(st); if (!fu_sbatlevel_section_add_entry(firmware, fw, header_offset + previous_addr, "previous", 0, flags, error)) return FALSE; latest_addr = fu_struct_sbat_level_section_header_get_latest(st); if (!fu_sbatlevel_section_add_entry(firmware, fw, header_offset + latest_addr, "latest", 1, flags, error)) return FALSE; return TRUE; } static void fu_sbatlevel_section_init(FuSbatlevelSection *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 2); } static void fu_sbatlevel_section_class_init(FuSbatlevelSectionClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_sbatlevel_section_parse; } FuFirmware * fu_sbatlevel_section_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_SBATLEVEL_SECTION, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-sbatlevel-section.h000066400000000000000000000006531460375044200222050ustar00rootroot00000000000000/* * Copyright (C) 2023 Canonical Ltd. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_SBATLEVEL_SECTION (fu_sbatlevel_section_get_type()) G_DECLARE_DERIVABLE_TYPE(FuSbatlevelSection, fu_sbatlevel_section, FU, SBATLEVEL_SECTION, FuFirmware) struct _FuSbatlevelSectionClass { FuFirmwareClass parent_class; }; FuFirmware * fu_sbatlevel_section_new(void); fwupd-1.9.16/libfwupdplugin/fu-sbatlevel-section.rs000066400000000000000000000003001460375044200223670ustar00rootroot00000000000000// Copyright (C) 2023 Canonical Ltd // SPDX-License-Identifier: LGPL-2.1+ #[derive(ParseBytes)] struct SbatLevelSectionHeader { version: u32 == 0x0, previous: u32, latest: u32, } fwupd-1.9.16/libfwupdplugin/fu-security-attr.c000066400000000000000000000064511460375044200213760ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FwupdSecurityAttr" #include "config.h" #include "fu-security-attr.h" typedef struct { FuContext *ctx; } FuSecurityAttrPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuSecurityAttr, fu_security_attr, FWUPD_TYPE_SECURITY_ATTR) #define GET_PRIVATE(o) (fu_security_attr_get_instance_private(o)) /** * fu_security_attr_add_bios_target_value: * @attr: a #FwupdSecurityAttr * @id: a #FwupdBiosSetting ID or name * @needle: The substring of a target value * * Checks all configured possible values of an enumeration attribute and * if any match @needle then set as the target value. * * Since: 1.8.4 **/ void fu_security_attr_add_bios_target_value(FwupdSecurityAttr *attr, const gchar *id, const gchar *needle) { FuSecurityAttr *self = FU_SECURITY_ATTR(attr); FuSecurityAttrPrivate *priv = GET_PRIVATE(self); FwupdBiosSetting *bios_setting; GPtrArray *values; const gchar *current; bios_setting = fu_context_get_bios_setting(priv->ctx, id); if (bios_setting == NULL) return; current = fwupd_bios_setting_get_current_value(bios_setting); fwupd_security_attr_set_bios_setting_id(attr, fwupd_bios_setting_get_id(bios_setting)); fwupd_security_attr_set_bios_setting_current_value(attr, current); if (fwupd_bios_setting_get_kind(bios_setting) != FWUPD_BIOS_SETTING_KIND_ENUMERATION) return; if (fwupd_bios_setting_get_read_only(bios_setting)) return; values = fwupd_bios_setting_get_possible_values(bios_setting); for (guint i = 0; i < values->len; i++) { const gchar *possible = g_ptr_array_index(values, i); g_autofree gchar *lower = g_utf8_strdown(possible, -1); if (g_strrstr(lower, needle)) { fwupd_security_attr_set_bios_setting_target_value(attr, possible); /* this is built-in to the engine */ if (g_strcmp0(possible, current) != 0) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_CAN_FIX); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_CAN_UNDO); } return; } } } static void fu_security_attr_init(FuSecurityAttr *self) { } static void fu_security_attr_finalize(GObject *object) { FuSecurityAttr *self = FU_SECURITY_ATTR(object); FuSecurityAttrPrivate *priv = GET_PRIVATE(self); if (priv->ctx != NULL) g_object_unref(priv->ctx); G_OBJECT_CLASS(fu_security_attr_parent_class)->finalize(object); } static void fu_security_attr_class_init(FuSecurityAttrClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_security_attr_finalize; } /** * fu_security_attr_new: * @ctx: a #FuContext * @appstream_id: (nullable): the AppStream component ID, e.g. `com.intel.BiosGuard` * * Creates a new #FwupdSecurityAttr with context set. * * Returns: (transfer full): a #FwupdSecurityAttr * * Since: 1.8.4 **/ FwupdSecurityAttr * fu_security_attr_new(FuContext *ctx, const gchar *appstream_id) { g_autoptr(FuSecurityAttr) self = g_object_new(FU_TYPE_SECURITY_ATTR, NULL); FuSecurityAttrPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_CONTEXT(ctx), NULL); if (appstream_id != NULL) fwupd_security_attr_set_appstream_id(FWUPD_SECURITY_ATTR(self), appstream_id); priv->ctx = g_object_ref(ctx); return FWUPD_SECURITY_ATTR(g_steal_pointer(&self)); } fwupd-1.9.16/libfwupdplugin/fu-security-attr.h000066400000000000000000000012441460375044200213760ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-context.h" #define FU_TYPE_SECURITY_ATTR (fu_security_attr_get_type()) G_DECLARE_DERIVABLE_TYPE(FuSecurityAttr, fu_security_attr, FU, SECURITY_ATTR, FwupdSecurityAttr) struct _FuSecurityAttrClass { FwupdSecurityAttrClass parent_class; }; FwupdSecurityAttr * fu_security_attr_new(FuContext *ctx, const gchar *appstream_id) G_GNUC_NON_NULL(1); void fu_security_attr_add_bios_target_value(FwupdSecurityAttr *attr, const gchar *id, const gchar *needle) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-security-attrs-private.h000066400000000000000000000027271460375044200232400ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once /** * FuSecurityAttrsFlags: * @FU_SECURITY_ATTRS_FLAG_NONE: No flags set * @FU_SECURITY_ATTRS_FLAG_ADD_VERSION: Add the daemon version to the HSI string * * The flags to use when calculating an HSI version. **/ typedef enum { FU_SECURITY_ATTRS_FLAG_NONE = 0, FU_SECURITY_ATTRS_FLAG_ADD_VERSION = 1 << 0, /*< private >*/ FU_SECURITY_ATTRS_FLAG_LAST } FuSecurityAttrsFlags; #include "fu-security-attrs.h" FuSecurityAttrs * fu_security_attrs_new(void); gchar * fu_security_attrs_calculate_hsi(FuSecurityAttrs *self, FuSecurityAttrsFlags flags) G_GNUC_NON_NULL(1); void fu_security_attrs_depsolve(FuSecurityAttrs *self) G_GNUC_NON_NULL(1); GVariant * fu_security_attrs_to_variant(FuSecurityAttrs *self) G_GNUC_NON_NULL(1); GPtrArray * fu_security_attrs_get_all(FuSecurityAttrs *self) G_GNUC_NON_NULL(1); void fu_security_attrs_append_internal(FuSecurityAttrs *self, FwupdSecurityAttr *attr) G_GNUC_NON_NULL(1, 2); gchar * fu_security_attrs_to_json_string(FuSecurityAttrs *self, GError **error) G_GNUC_NON_NULL(1); gboolean fu_security_attrs_from_json(FuSecurityAttrs *self, JsonNode *json_node, GError **error) G_GNUC_NON_NULL(1, 2); gboolean fu_security_attrs_equal(FuSecurityAttrs *attrs1, FuSecurityAttrs *attrs2) G_GNUC_NON_NULL(1, 2); GPtrArray * fu_security_attrs_compare(FuSecurityAttrs *attrs1, FuSecurityAttrs *attrs2) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-security-attrs.c000066400000000000000000000567161460375044200215720ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuSecurityAttrs" #include "config.h" #include #include #include "fwupd-security-attr-private.h" #include "fu-security-attrs-private.h" #include "fu-security-attrs.h" /** * FuSecurityAttrs: * * A set of Host Security ID attributes that represents the system state. */ struct _FuSecurityAttrs { GObject parent_instance; GPtrArray *attrs; }; /* probably sane to *not* make this part of the ABI */ #define FWUPD_SECURITY_ATTR_ID_DOC_URL "https://fwupd.github.io/libfwupdplugin/hsi.html" G_DEFINE_TYPE(FuSecurityAttrs, fu_security_attrs, G_TYPE_OBJECT) static void fu_security_attrs_finalize(GObject *obj) { FuSecurityAttrs *self = FU_SECURITY_ATTRS(obj); g_ptr_array_unref(self->attrs); G_OBJECT_CLASS(fu_security_attrs_parent_class)->finalize(obj); } static void fu_security_attrs_class_init(FuSecurityAttrsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_security_attrs_finalize; } static void fu_security_attrs_init(FuSecurityAttrs *self) { self->attrs = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); } /** * fu_security_attrs_append_internal: * @self: a #FuSecurityAttrs * @attr: a #FwupdSecurityAttr * * Adds a #FwupdSecurityAttr to the array with no sanity checks. * * Since: 1.7.1 **/ void fu_security_attrs_append_internal(FuSecurityAttrs *self, FwupdSecurityAttr *attr) { g_return_if_fail(FU_IS_SECURITY_ATTRS(self)); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(attr)); g_ptr_array_add(self->attrs, g_object_ref(attr)); } /** * fu_security_attrs_append: * @self: a #FuSecurityAttrs * @attr: a #FwupdSecurityAttr * * Adds a #FwupdSecurityAttr to the array. * * Since: 1.5.0 **/ void fu_security_attrs_append(FuSecurityAttrs *self, FwupdSecurityAttr *attr) { g_return_if_fail(FU_IS_SECURITY_ATTRS(self)); g_return_if_fail(FWUPD_IS_SECURITY_ATTR(attr)); /* sanity check */ if (fwupd_security_attr_get_plugin(attr) == NULL) { g_warning("%s has no plugin set", fwupd_security_attr_get_appstream_id(attr)); } /* sanity check, and correctly prefix the URLs with the current mirror */ if (fwupd_security_attr_get_url(attr) == NULL) { g_autofree gchar *url = NULL; url = g_strdup_printf("%s#%s", FWUPD_SECURITY_ATTR_ID_DOC_URL, fwupd_security_attr_get_appstream_id(attr)); fwupd_security_attr_set_url(attr, url); } else if (g_str_has_prefix(fwupd_security_attr_get_url(attr), "#")) { g_autofree gchar *url = NULL; url = g_strdup_printf("%s%s", FWUPD_SECURITY_ATTR_ID_DOC_URL, fwupd_security_attr_get_url(attr)); fwupd_security_attr_set_url(attr, url); } fu_security_attrs_append_internal(self, attr); } /** * fu_security_attrs_get_by_appstream_id: * @self: a #FuSecurityAttrs * @appstream_id: an ID, e.g. %FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM * @error: (nullable): optional return location for an error * * Gets a specific #FwupdSecurityAttr from the array. * * Returns: (transfer full): a #FwupdSecurityAttr or %NULL * * Since: 1.9.6 **/ FwupdSecurityAttr * fu_security_attrs_get_by_appstream_id(FuSecurityAttrs *self, const gchar *appstream_id, GError **error) { g_return_val_if_fail(FU_IS_SECURITY_ATTRS(self), NULL); if (self->attrs->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no attributes are loaded"); return NULL; } for (guint i = 0; i < self->attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(self->attrs, i); if (g_strcmp0(fwupd_security_attr_get_appstream_id(attr), appstream_id) == 0) return g_object_ref(attr); } g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no attr with ID %s", appstream_id); return NULL; } /** * fu_security_attrs_to_variant: * @self: a #FuSecurityAttrs * * Serializes the #FwupdSecurityAttr objects. * * Returns: a #GVariant or %NULL * * Since: 1.5.0 **/ GVariant * fu_security_attrs_to_variant(FuSecurityAttrs *self) { GVariantBuilder builder; g_return_val_if_fail(FU_IS_SECURITY_ATTRS(self), NULL); g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); for (guint i = 0; i < self->attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(self->attrs, i); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) continue; g_variant_builder_add_value(&builder, fwupd_security_attr_to_variant(attr)); } return g_variant_new("(aa{sv})", &builder); } /** * fu_security_attrs_get_all: * @self: a #FuSecurityAttrs * * Gets all the non-obsoleted attributes in the object. * * Returns: (transfer container) (element-type FwupdSecurityAttr): attributes * * Since: 1.5.0 **/ GPtrArray * fu_security_attrs_get_all(FuSecurityAttrs *self) { g_autoptr(GPtrArray) all = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_return_val_if_fail(FU_IS_SECURITY_ATTRS(self), NULL); for (guint i = 0; i < self->attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(self->attrs, i); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) continue; g_ptr_array_add(all, g_object_ref(attr)); } return g_steal_pointer(&all); } /** * fu_security_attrs_remove_all: * @self: a #FuSecurityAttrs * * Removes all the attributes in the object. * * Since: 1.5.0 **/ void fu_security_attrs_remove_all(FuSecurityAttrs *self) { g_return_if_fail(FU_IS_SECURITY_ATTRS(self)); return g_ptr_array_set_size(self->attrs, 0); } /** * fu_security_attrs_calculate_hsi: * @self: a #FuSecurityAttrs * @flags: HSI attribute flags * * Calculates the HSI string from the appended attributes. * * Returns: (transfer full): a string or %NULL * * Since: 1.5.0 **/ gchar * fu_security_attrs_calculate_hsi(FuSecurityAttrs *self, FuSecurityAttrsFlags flags) { guint hsi_number = 0; FwupdSecurityAttrFlags attr_flags = FWUPD_SECURITY_ATTR_FLAG_NONE; g_autoptr(GString) str = g_string_new("HSI:"); const FwupdSecurityAttrFlags hpi_suffixes[] = { FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE, FWUPD_SECURITY_ATTR_FLAG_NONE, }; g_return_val_if_fail(FU_IS_SECURITY_ATTRS(self), NULL); /* find the highest HSI number where there are no failures and at least * one success */ for (guint j = 1; j <= FWUPD_SECURITY_ATTR_LEVEL_LAST; j++) { gboolean success_cnt = 0; gboolean failure_cnt = 0; for (guint i = 0; i < self->attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(self->attrs, i); if (fwupd_security_attr_get_level(attr) != j) continue; if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) success_cnt++; else if (!fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) failure_cnt++; } /* abort */ if (failure_cnt > 0) break; /* we matched at least one thing on this level */ if (success_cnt > 0) hsi_number = j; } /* get a logical OR of the runtime flags */ for (guint i = 0; i < self->attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(self->attrs, i); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) continue; if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE) && fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) continue; attr_flags |= fwupd_security_attr_get_flags(attr); } g_string_append_printf(str, "%u", hsi_number); if (attr_flags & FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE) { for (guint j = 0; hpi_suffixes[j] != FWUPD_SECURITY_ATTR_FLAG_NONE; j++) { if (attr_flags & hpi_suffixes[j]) g_string_append( str, fwupd_security_attr_flag_to_suffix(hpi_suffixes[j])); } } if (flags & FU_SECURITY_ATTRS_FLAG_ADD_VERSION) { g_string_append_printf(str, " (v%d.%d.%d)", FWUPD_MAJOR_VERSION, FWUPD_MINOR_VERSION, FWUPD_MICRO_VERSION); } return g_string_free(g_steal_pointer(&str), FALSE); } static gchar * fu_security_attrs_get_sort_key(FwupdSecurityAttr *attr) { GString *str = g_string_new(NULL); /* level */ g_string_append_printf(str, "%u", fwupd_security_attr_get_level(attr)); /* success -> fail -> obsoletes */ if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { g_string_append(str, "0"); } else if (!fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS) && !fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) { g_string_append(str, "1"); } else { g_string_append(str, "9"); } /* prefer name, but fallback to appstream-id for tests */ if (fwupd_security_attr_get_name(attr) != NULL) { g_string_append(str, fwupd_security_attr_get_name(attr)); } else { g_string_append(str, fwupd_security_attr_get_appstream_id(attr)); } return g_string_free(str, FALSE); } static gint fu_security_attrs_sort_cb(gconstpointer item1, gconstpointer item2) { FwupdSecurityAttr *attr1 = *((FwupdSecurityAttr **)item1); FwupdSecurityAttr *attr2 = *((FwupdSecurityAttr **)item2); g_autofree gchar *sort1 = fu_security_attrs_get_sort_key(attr1); g_autofree gchar *sort2 = fu_security_attrs_get_sort_key(attr2); return g_strcmp0(sort1, sort2); } static struct { const gchar *appstream_id; FwupdSecurityAttrLevel level; } appstream_id_level_map[] = { {FWUPD_SECURITY_ATTR_ID_AMD_ROLLBACK_PROTECTION, FWUPD_SECURITY_ATTR_LEVEL_SYSTEM_PROTECTION}, {FWUPD_SECURITY_ATTR_ID_AMD_SPI_REPLAY_PROTECTION, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL}, {FWUPD_SECURITY_ATTR_ID_AMD_SPI_WRITE_PROTECTION, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT}, {FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM, FWUPD_SECURITY_ATTR_LEVEL_SYSTEM_PROTECTION}, {FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ACM, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT}, {FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ENABLED, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT}, {FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_OTP, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT}, {FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_POLICY, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL}, {FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_VERIFIED, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT}, {FWUPD_SECURITY_ATTR_ID_INTEL_CET_ACTIVE, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL}, {FWUPD_SECURITY_ATTR_ID_INTEL_CET_ENABLED, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL}, {FWUPD_SECURITY_ATTR_ID_INTEL_GDS, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT}, {FWUPD_SECURITY_ATTR_ID_INTEL_SMAP, FWUPD_SECURITY_ATTR_LEVEL_SYSTEM_PROTECTION}, {FWUPD_SECURITY_ATTR_ID_IOMMU, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT}, {FWUPD_SECURITY_ATTR_ID_MEI_MANUFACTURING_MODE, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_MEI_OVERRIDE_STRAP, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_MEI_VERSION, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_ENABLED, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_LOCKED, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT}, {FWUPD_SECURITY_ATTR_ID_PLATFORM_FUSED, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_PREBOOT_DMA_PROTECTION, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL}, {FWUPD_SECURITY_ATTR_ID_SPI_BIOSWE, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_SPI_BLE, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_SPI_DESCRIPTOR, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_SPI_SMM_BWP, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_SUPPORTED_CPU, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_IDLE, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL}, {FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_RAM, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL}, {FWUPD_SECURITY_ATTR_ID_TPM_EMPTY_PCR, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_TPM_RECONSTRUCTION_PCR0, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT}, {FWUPD_SECURITY_ATTR_ID_TPM_VERSION_20, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_UEFI_PK, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_UEFI_SECUREBOOT, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_UEFI_BOOTSERVICE_VARS, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {FWUPD_SECURITY_ATTR_ID_BIOS_ROLLBACK_PROTECTION, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT}, {FWUPD_SECURITY_ATTR_ID_BIOS_CAPSULE_UPDATES, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL}, {NULL, FWUPD_SECURITY_ATTR_LEVEL_NONE}}; static void fu_security_attrs_ensure_level(FwupdSecurityAttr *attr) { const gchar *appstream_id = fwupd_security_attr_get_appstream_id(attr); /* already set */ if (fwupd_security_attr_get_level(attr) != FWUPD_SECURITY_ATTR_LEVEL_NONE) return; /* not required */ if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE)) return; /* map ID to level in one place */ for (guint i = 0; appstream_id_level_map[i].appstream_id != NULL; i++) { if (g_strcmp0(appstream_id, appstream_id_level_map[i].appstream_id) == 0) { fwupd_security_attr_set_level(attr, appstream_id_level_map[i].level); return; } } /* somebody forgot to add to the level map... */ g_warning("cannot map %s to a HSI level, assuming critical", appstream_id); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); } /** * fu_security_attrs_depsolve: * @self: a #FuSecurityAttrs * * Marks any attributes with %FWUPD_SECURITY_ATTR_FLAG_OBSOLETED that have been * defined as obsoleted by other attributes. * * It is only required to call this function once, and should be done when all * attributes have been added. This will also sort the attrs. * * Since: 1.5.0 **/ void fu_security_attrs_depsolve(FuSecurityAttrs *self) { g_return_if_fail(FU_IS_SECURITY_ATTRS(self)); /* assign HSI levels if not already done */ for (guint i = 0; i < self->attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(self->attrs, i); fu_security_attrs_ensure_level(attr); } /* set flat where required */ for (guint i = 0; i < self->attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(self->attrs, i); const gchar *attr_id = fwupd_security_attr_get_appstream_id(attr); const gchar *attr_plugin = fwupd_security_attr_get_plugin(attr); GPtrArray *obsoletes = fwupd_security_attr_get_obsoletes(attr); for (guint j = 0; j < self->attrs->len; j++) { FwupdSecurityAttr *attr_tmp = g_ptr_array_index(self->attrs, j); const gchar *attr_tmp_id = fwupd_security_attr_get_appstream_id(attr_tmp); const gchar *attr_tmp_plugin = fwupd_security_attr_get_plugin(attr_tmp); /* skip self */ if (g_strcmp0(attr_plugin, attr_tmp_plugin) == 0 && g_strcmp0(attr_id, attr_tmp_id) == 0) continue; /* add duplicate (negative) attributes when obsolete not explicitly set */ if (obsoletes->len == 0) { if (g_strcmp0(attr_id, attr_tmp_id) != 0) continue; if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) continue; if (fwupd_security_attr_has_flag(attr_tmp, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) continue; if (fwupd_security_attr_has_obsolete(attr, attr_id)) continue; if (fwupd_security_attr_has_obsolete(attr_tmp, attr_id)) continue; g_debug("duplicate security attr %s from plugin %s implicitly " "obsoleted by plugin %s", attr_id, attr_plugin, attr_tmp_plugin); fwupd_security_attr_add_obsolete(attr, attr_id); } /* walk all the obsoletes for matches appstream ID or plugin */ for (guint k = 0; k < obsoletes->len; k++) { const gchar *obsolete = g_ptr_array_index(obsoletes, k); if (g_strcmp0(attr_tmp_id, obsolete) == 0 || g_strcmp0(attr_tmp_plugin, obsolete) == 0) { g_debug("security attr %s:%s obsoleted by %s:%s", attr_tmp_id, attr_tmp_plugin, attr_id, attr_plugin); fwupd_security_attr_add_flag( attr_tmp, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED); } } } } /* sort */ g_ptr_array_sort(self->attrs, fu_security_attrs_sort_cb); } static void fu_security_attrs_to_json(FuSecurityAttrs *self, JsonBuilder *builder) { g_autoptr(GPtrArray) items = NULL; json_builder_begin_object(builder); json_builder_set_member_name(builder, "SecurityAttributes"); json_builder_begin_array(builder); items = fu_security_attrs_get_all(self); for (guint i = 0; i < items->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(items, i); guint64 created = fwupd_security_attr_get_created(attr); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) continue; json_builder_begin_object(builder); fwupd_security_attr_set_created(attr, 0); fwupd_security_attr_to_json(attr, builder); fwupd_security_attr_set_created(attr, created); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); } /** * fu_security_attrs_to_json_string: * @self: a pointer for a FuSecurityAttrs data structure. * @error: (nullable): optional return location for an error * * Convert security attribute to JSON string. e.g.: * { * "SecurityAttributes": [ * { * "name": "aaa", * "value": "bbb" * } * ] * } * * Returns: (transfer full): JSON string * * Since: 1.9.2 */ gchar * fu_security_attrs_to_json_string(FuSecurityAttrs *self, GError **error) { g_autofree gchar *data = NULL; g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonBuilder) builder = json_builder_new(); g_autoptr(JsonNode) json_root = NULL; g_return_val_if_fail(FU_IS_SECURITY_ATTRS(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); fu_security_attrs_to_json(self, builder); json_root = json_builder_get_root(builder); json_generator = json_generator_new(); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); data = json_generator_to_data(json_generator, NULL); if (data == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to convert security attribute to json."); return NULL; } return g_steal_pointer(&data); } /** * fu_security_attrs_from_json: * @self: a #FuSecurityAttrs * @json_node: a #JsonNode * @error: (nullable): optional return location for an error * * Imports a JSON node. * * Returns: %TRUE on success * * Since: 1.9.2 */ gboolean fu_security_attrs_from_json(FuSecurityAttrs *self, JsonNode *json_node, GError **error) { JsonArray *array; JsonObject *obj; g_return_val_if_fail(FU_IS_SECURITY_ATTRS(self), FALSE); g_return_val_if_fail(json_node != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* sanity check */ if (!JSON_NODE_HOLDS_OBJECT(json_node)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not JSON object"); return FALSE; } obj = json_node_get_object(json_node); /* this has to exist */ if (!json_object_has_member(obj, "SecurityAttributes")) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no SecurityAttributes property in object"); return FALSE; } array = json_object_get_array_member(obj, "SecurityAttributes"); for (guint i = 0; i < json_array_get_length(array); i++) { JsonNode *node_tmp = json_array_get_element(array, i); g_autoptr(FwupdSecurityAttr) attr = fwupd_security_attr_new(NULL); if (!fwupd_security_attr_from_json(attr, node_tmp, error)) return FALSE; if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) continue; fu_security_attrs_append(self, attr); } /* success */ return TRUE; } /** * fu_security_attrs_compare: * @attrs1: a #FuSecurityAttrs * @attrs2: another #FuSecurityAttrs, perhaps newer in some way * * Compares the two objects, returning the differences. * * If the two sets of attrs are considered the same then an empty array is returned. * Only the AppStream ID results are compared, extra metadata is ignored. * * Returns: (element-type FwupdSecurityAttr) (transfer container): differences * * Since: 1.9.2 */ GPtrArray * fu_security_attrs_compare(FuSecurityAttrs *attrs1, FuSecurityAttrs *attrs2) { g_autoptr(GHashTable) hash1 = g_hash_table_new(g_str_hash, g_str_equal); g_autoptr(GHashTable) hash2 = g_hash_table_new(g_str_hash, g_str_equal); g_autoptr(GPtrArray) array1 = fu_security_attrs_get_all(attrs1); g_autoptr(GPtrArray) array2 = fu_security_attrs_get_all(attrs2); g_autoptr(GPtrArray) results = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_return_val_if_fail(FU_IS_SECURITY_ATTRS(attrs1), NULL); g_return_val_if_fail(FU_IS_SECURITY_ATTRS(attrs2), NULL); /* create hash tables of appstream-id -> FwupdSecurityAttr */ for (guint i = 0; i < array1->len; i++) { FwupdSecurityAttr *attr1 = g_ptr_array_index(array1, i); g_hash_table_insert(hash1, (gpointer)fwupd_security_attr_get_appstream_id(attr1), (gpointer)attr1); } for (guint i = 0; i < array2->len; i++) { FwupdSecurityAttr *attr2 = g_ptr_array_index(array2, i); g_hash_table_insert(hash2, (gpointer)fwupd_security_attr_get_appstream_id(attr2), (gpointer)attr2); } /* present in attrs2, not present in attrs1 */ for (guint i = 0; i < array2->len; i++) { FwupdSecurityAttr *attr1; FwupdSecurityAttr *attr2 = g_ptr_array_index(array2, i); attr1 = g_hash_table_lookup(hash1, fwupd_security_attr_get_appstream_id(attr2)); if (attr1 == NULL) { g_autoptr(FwupdSecurityAttr) attr = fwupd_security_attr_copy(attr2); g_ptr_array_add(results, g_steal_pointer(&attr)); continue; } } /* present in attrs1, not present in attrs2 */ for (guint i = 0; i < array1->len; i++) { FwupdSecurityAttr *attr1 = g_ptr_array_index(array1, i); FwupdSecurityAttr *attr2; attr2 = g_hash_table_lookup(hash2, fwupd_security_attr_get_appstream_id(attr1)); if (attr2 == NULL) { g_autoptr(FwupdSecurityAttr) attr = fwupd_security_attr_copy(attr1); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_UNKNOWN); fwupd_security_attr_set_result_fallback( attr, /* flip these around */ fwupd_security_attr_get_result(attr1)); g_ptr_array_add(results, g_steal_pointer(&attr)); continue; } } /* find any attributes that differ */ for (guint i = 0; i < array2->len; i++) { FwupdSecurityAttr *attr1; FwupdSecurityAttr *attr2 = g_ptr_array_index(array2, i); attr1 = g_hash_table_lookup(hash1, fwupd_security_attr_get_appstream_id(attr2)); if (attr1 == NULL) continue; /* result of specific attr differed */ if (fwupd_security_attr_get_result(attr1) != fwupd_security_attr_get_result(attr2)) { g_autoptr(FwupdSecurityAttr) attr = fwupd_security_attr_copy(attr1); fwupd_security_attr_set_result(attr, fwupd_security_attr_get_result(attr2)); fwupd_security_attr_set_result_fallback( attr, fwupd_security_attr_get_result(attr1)); fwupd_security_attr_set_flags(attr, fwupd_security_attr_get_flags(attr2)); g_ptr_array_add(results, g_steal_pointer(&attr)); } } /* success */ return g_steal_pointer(&results); } /** * fu_security_attrs_equal: * @attrs1: a #FuSecurityAttrs * @attrs2: another #FuSecurityAttrs * * Tests the objects for equality. Only the AppStream ID results are compared, extra metadata * is ignored. * * Returns: %TRUE if the set of attrs can be considered equal * * Since: 1.9.2 */ gboolean fu_security_attrs_equal(FuSecurityAttrs *attrs1, FuSecurityAttrs *attrs2) { g_autoptr(GPtrArray) compare = fu_security_attrs_compare(attrs1, attrs2); return compare->len == 0; } /** * fu_security_attrs_new: * * Returns: a security attribute * * Since: 1.5.0 **/ FuSecurityAttrs * fu_security_attrs_new(void) { return g_object_new(FU_TYPE_SECURITY_ATTRS, NULL); } fwupd-1.9.16/libfwupdplugin/fu-security-attrs.h000066400000000000000000000012241460375044200215570ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_SECURITY_ATTRS (fu_security_attrs_get_type()) G_DECLARE_FINAL_TYPE(FuSecurityAttrs, fu_security_attrs, FU, SECURITY_ATTRS, GObject) void fu_security_attrs_append(FuSecurityAttrs *self, FwupdSecurityAttr *attr) G_GNUC_NON_NULL(1, 2); void fu_security_attrs_remove_all(FuSecurityAttrs *self) G_GNUC_NON_NULL(1); FwupdSecurityAttr * fu_security_attrs_get_by_appstream_id(FuSecurityAttrs *self, const gchar *appstream_id, GError **error) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-self-test.c000066400000000000000000004752001460375044200204670ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #define G_LOG_DOMAIN "FuSelfTest" #include #include #include #include "fwupd-bios-setting-private.h" #include "fwupd-security-attr-private.h" #include "fu-bios-settings-private.h" #include "fu-common-private.h" #include "fu-config-private.h" #include "fu-context-private.h" #include "fu-coswid-firmware.h" #include "fu-device-private.h" #include "fu-device-progress.h" #include "fu-plugin-private.h" #include "fu-security-attrs-private.h" #include "fu-self-test-struct.h" #include "fu-smbios-private.h" static GMainLoop *_test_loop = NULL; static guint _test_loop_timeout_id = 0; static gboolean fu_test_hang_check_cb(gpointer user_data) { g_main_loop_quit(_test_loop); _test_loop_timeout_id = 0; return G_SOURCE_REMOVE; } static void fu_test_loop_run_with_timeout(guint timeout_ms) { g_assert_cmpint(_test_loop_timeout_id, ==, 0); g_assert_null(_test_loop); _test_loop = g_main_loop_new(NULL, FALSE); _test_loop_timeout_id = g_timeout_add(timeout_ms, fu_test_hang_check_cb, NULL); g_main_loop_run(_test_loop); } static void fu_test_loop_quit(void) { if (_test_loop_timeout_id > 0) { g_source_remove(_test_loop_timeout_id); _test_loop_timeout_id = 0; } if (_test_loop != NULL) { g_main_loop_quit(_test_loop); g_main_loop_unref(_test_loop); _test_loop = NULL; } } static void fu_archive_invalid_func(void) { g_autofree gchar *filename = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(GBytes) data = NULL; g_autoptr(GError) error = NULL; #ifndef HAVE_LIBARCHIVE g_test_skip("no libarchive support"); return; #endif filename = g_test_build_filename(G_TEST_DIST, "tests", "metadata.xml", NULL); data = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(data); archive = fu_archive_new(data, FU_ARCHIVE_FLAG_NONE, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); g_assert_null(archive); } static void fu_archive_cab_func(void) { g_autofree gchar *checksum1 = NULL; g_autofree gchar *checksum2 = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(GBytes) data = NULL; g_autoptr(GBytes) data_tmp1 = NULL; g_autoptr(GBytes) data_tmp2 = NULL; g_autoptr(GBytes) data_tmp3 = NULL; g_autoptr(GError) error = NULL; #ifndef HAVE_LIBARCHIVE g_test_skip("no libarchive support"); return; #endif filename = g_test_build_filename(G_TEST_BUILT, "tests", "colorhug", "colorhug-als-3.0.2.cab", NULL); data = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(data); archive = fu_archive_new(data, FU_ARCHIVE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(archive); data_tmp1 = fu_archive_lookup_by_fn(archive, "firmware.metainfo.xml", &error); g_assert_no_error(error); g_assert_nonnull(data_tmp1); checksum1 = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, data_tmp1); g_assert_cmpstr(checksum1, ==, "8611114f51f7151f190de86a5c9259d79ff34216"); data_tmp2 = fu_archive_lookup_by_fn(archive, "firmware.bin", &error); g_assert_no_error(error); g_assert_nonnull(data_tmp2); checksum2 = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, data_tmp2); g_assert_cmpstr(checksum2, ==, "7c0ae84b191822bcadbdcbe2f74a011695d783c7"); data_tmp3 = fu_archive_lookup_by_fn(archive, "NOTGOINGTOEXIST.xml", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(data_tmp3); } static void fu_volume_gpt_type_func(void) { g_assert_cmpstr(fu_volume_kind_convert_to_gpt("0xef"), ==, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"); g_assert_cmpstr(fu_volume_kind_convert_to_gpt("0x0b"), ==, "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7"); g_assert_cmpstr(fu_volume_kind_convert_to_gpt("fat32lba"), ==, "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7"); g_assert_cmpstr(fu_volume_kind_convert_to_gpt("0x00"), ==, "0x00"); } static void fu_common_align_up_func(void) { g_assert_cmpint(fu_common_align_up(0, 0), ==, 0); g_assert_cmpint(fu_common_align_up(5, 0), ==, 5); g_assert_cmpint(fu_common_align_up(5, 3), ==, 8); g_assert_cmpint(fu_common_align_up(1023, 10), ==, 1024); g_assert_cmpint(fu_common_align_up(1024, 10), ==, 1024); g_assert_cmpint(fu_common_align_up(G_MAXSIZE - 1, 10), ==, G_MAXSIZE); } static void fu_common_byte_array_func(void) { g_autofree gchar *str = NULL; g_autoptr(GByteArray) array = g_byte_array_new(); g_autoptr(GByteArray) array2 = NULL; g_autoptr(GByteArray) array3 = NULL; g_autoptr(GError) error = NULL; fu_byte_array_append_uint8(array, (guint8)'h'); fu_byte_array_append_uint8(array, (guint8)'e'); fu_byte_array_append_uint8(array, (guint8)'l'); fu_byte_array_append_uint8(array, (guint8)'l'); fu_byte_array_append_uint8(array, (guint8)'o'); g_assert_cmpint(array->len, ==, 5); g_assert_cmpint(memcmp(array->data, "hello", array->len), ==, 0); fu_byte_array_set_size(array, 10, 0x00); g_assert_cmpint(array->len, ==, 10); g_assert_cmpint(memcmp(array->data, "hello\0\0\0\0\0", array->len), ==, 0); str = fu_byte_array_to_string(array); g_assert_cmpstr(str, ==, "68656c6c6f0000000000"); array2 = fu_byte_array_from_string(str, &error); g_assert_no_error(error); g_assert_nonnull(array2); g_assert_cmpint(array2->len, ==, 10); g_assert_cmpint(memcmp(array2->data, "hello\0\0\0\0\0", array2->len), ==, 0); array3 = fu_byte_array_from_string("ZZZ", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_null(array3); } static void fu_common_crc_func(void) { guint8 buf[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}; g_assert_cmpint(fu_crc8(buf, sizeof(buf)), ==, 0x7A); g_assert_cmpint(fu_crc16(buf, sizeof(buf)), ==, 0x4DF1); g_assert_cmpint(fu_crc32(buf, sizeof(buf)), ==, 0x40EFAB9E); } static void fu_string_append_func(void) { g_autoptr(GString) str = g_string_new(NULL); fu_string_append(str, 0, "hdr", NULL); fu_string_append(str, 0, "key", "value"); fu_string_append(str, 0, "key1", "value1"); fu_string_append(str, 1, "key2", "value2"); fu_string_append(str, 1, "", "value2"); fu_string_append(str, 2, "key3", "value3"); g_assert_cmpstr(str->str, ==, "hdr:\n" "key: value\n" "key1: value1\n" " key2: value2\n" " value2\n" " key3: value3\n"); } static void fu_version_guess_format_func(void) { g_assert_cmpint(fu_version_guess_format(NULL), ==, FWUPD_VERSION_FORMAT_UNKNOWN); g_assert_cmpint(fu_version_guess_format(""), ==, FWUPD_VERSION_FORMAT_UNKNOWN); g_assert_cmpint(fu_version_guess_format("1234ac"), ==, FWUPD_VERSION_FORMAT_PLAIN); g_assert_cmpint(fu_version_guess_format("1.2"), ==, FWUPD_VERSION_FORMAT_PAIR); g_assert_cmpint(fu_version_guess_format("1.2.3"), ==, FWUPD_VERSION_FORMAT_TRIPLET); g_assert_cmpint(fu_version_guess_format("1.2.3.4"), ==, FWUPD_VERSION_FORMAT_QUAD); g_assert_cmpint(fu_version_guess_format("1.2.3.4.5"), ==, FWUPD_VERSION_FORMAT_UNKNOWN); g_assert_cmpint(fu_version_guess_format("1a.2b.3"), ==, FWUPD_VERSION_FORMAT_PLAIN); g_assert_cmpint(fu_version_guess_format("1"), ==, FWUPD_VERSION_FORMAT_NUMBER); g_assert_cmpint(fu_version_guess_format("0x10201"), ==, FWUPD_VERSION_FORMAT_NUMBER); } static void fu_device_version_format_func(void) { g_autoptr(FuDevice) device = fu_device_new(NULL); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "Ver1.2.3 RELEASE"); g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.3"); } static void fu_device_open_refcount_func(void) { gboolean ret; g_autoptr(FuDevice) device = fu_device_new(NULL); g_autoptr(GError) error = NULL; fu_device_set_id(device, "test_device"); ret = fu_device_open(device, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_device_open(device, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_device_close(device, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_device_close(device, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_device_close(device, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO); g_assert_false(ret); } static void fu_device_name_func(void) { g_autoptr(FuDevice) device1 = fu_device_new(NULL); g_autoptr(FuDevice) device2 = fu_device_new(NULL); /* vendor then name */ fu_device_set_vendor(device1, " Hughski "); fu_device_set_name(device1, "HUGHSKI ColorHug(TM)__Pro "); g_assert_cmpstr(fu_device_get_vendor(device1), ==, "Hughski"); g_assert_cmpstr(fu_device_get_name(device1), ==, "ColorHug™ Pro"); /* name then vendor */ fu_device_set_name(device2, "Hughski ColorHug(TM)_Pro"); fu_device_set_vendor(device2, "Hughski"); g_assert_cmpstr(fu_device_get_vendor(device2), ==, "Hughski"); g_assert_cmpstr(fu_device_get_name(device2), ==, "ColorHug™ Pro"); /* a real example */ fu_device_set_name(device2, "Intel(R) Core(TM) i7-10850H CPU @ 2.70GHz"); fu_device_set_vendor(device2, "Intel"); g_assert_cmpstr(fu_device_get_name(device2), ==, "Core™ i7-10850H CPU @ 2.70GHz"); } static void fu_device_cfi_device_func(void) { gboolean ret; guint8 cmd = 0; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuCfiDevice) cfi_device = NULL; g_autoptr(GError) error = NULL; ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); cfi_device = fu_cfi_device_new(ctx, "3730"); ret = fu_device_setup(FU_DEVICE(cfi_device), &error); g_assert_no_error(error); g_assert_true(ret); /* fallback */ ret = fu_cfi_device_get_cmd(cfi_device, FU_CFI_DEVICE_CMD_READ_DATA, &cmd, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(cmd, ==, 0x03); /* from quirk */ ret = fu_cfi_device_get_cmd(cfi_device, FU_CFI_DEVICE_CMD_CHIP_ERASE, &cmd, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(cmd, ==, 0xC7); g_assert_cmpint(fu_cfi_device_get_size(cfi_device), ==, 0x10000); g_assert_cmpint(fu_cfi_device_get_page_size(cfi_device), ==, 0x200); g_assert_cmpint(fu_cfi_device_get_sector_size(cfi_device), ==, 0x2000); g_assert_cmpint(fu_cfi_device_get_block_size(cfi_device), ==, 0x8000); } static void fu_device_metadata_func(void) { g_autoptr(FuDevice) device = fu_device_new(NULL); /* string */ fu_device_set_metadata(device, "foo", "bar"); g_assert_cmpstr(fu_device_get_metadata(device, "foo"), ==, "bar"); fu_device_set_metadata(device, "foo", "baz"); g_assert_cmpstr(fu_device_get_metadata(device, "foo"), ==, "baz"); g_assert_null(fu_device_get_metadata(device, "unknown")); /* boolean */ fu_device_set_metadata_boolean(device, "baz", TRUE); g_assert_cmpstr(fu_device_get_metadata(device, "baz"), ==, "true"); g_assert_true(fu_device_get_metadata_boolean(device, "baz")); g_assert_false(fu_device_get_metadata_boolean(device, "unknown")); /* integer */ fu_device_set_metadata_integer(device, "bam", 12345); g_assert_cmpstr(fu_device_get_metadata(device, "bam"), ==, "12345"); g_assert_cmpint(fu_device_get_metadata_integer(device, "bam"), ==, 12345); g_assert_cmpint(fu_device_get_metadata_integer(device, "unknown"), ==, G_MAXUINT); /* broken integer */ fu_device_set_metadata(device, "bam", "123junk"); g_assert_cmpint(fu_device_get_metadata_integer(device, "bam"), ==, G_MAXUINT); fu_device_set_metadata(device, "huge", "4294967296"); /* not 32 bit */ g_assert_cmpint(fu_device_get_metadata_integer(device, "huge"), ==, G_MAXUINT); } static void fu_string_utf16_func(void) { g_autofree gchar *str1 = NULL; g_autofree gchar *str2 = NULL; g_autoptr(GByteArray) buf = NULL; g_autoptr(GError) error = NULL; buf = fu_utf8_to_utf16_byte_array("hello world", G_LITTLE_ENDIAN, FU_UTF_CONVERT_FLAG_APPEND_NUL, &error); g_assert_no_error(error); g_assert_nonnull(buf); g_assert_cmpint(buf->len, ==, 24); g_assert_cmpint(buf->data[0], ==, 'h'); g_assert_cmpint(buf->data[1], ==, '\0'); g_assert_cmpint(buf->data[2], ==, 'e'); g_assert_cmpint(buf->data[3], ==, '\0'); str1 = fu_utf16_to_utf8_byte_array(buf, G_LITTLE_ENDIAN, &error); g_assert_no_error(error); g_assert_cmpstr(str1, ==, "hello world"); /* failure */ g_byte_array_set_size(buf, buf->len - 1); str2 = fu_utf16_to_utf8_byte_array(buf, G_LITTLE_ENDIAN, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_cmpstr(str2, ==, NULL); } static void fu_smbios_func(void) { const gchar *str; gboolean ret; g_autofree gchar *dump = NULL; g_autofree gchar *testdatadir = NULL; g_autoptr(FuSmbios) smbios = NULL; g_autoptr(GError) error = NULL; #ifdef _WIN32 g_test_skip("Windows uses GetSystemFirmwareTable rather than parsing the fake test data"); return; #endif /* these tests will not write */ testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); (void)g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); smbios = fu_smbios_new(); ret = fu_smbios_setup(smbios, &error); g_assert_no_error(error); g_assert_true(ret); dump = fu_firmware_to_string(FU_FIRMWARE(smbios)); g_debug("%s", dump); /* test for missing table */ str = fu_smbios_get_string(smbios, 0xff, 0, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null(str); g_clear_error(&error); /* check for invalid offset */ str = fu_smbios_get_string(smbios, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0xff, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null(str); g_clear_error(&error); /* get vendor */ str = fu_smbios_get_string(smbios, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x04, &error); g_assert_no_error(error); g_assert_cmpstr(str, ==, "LENOVO"); } static void fu_kernel_cmdline_func(void) { const gchar *buf = "key=val foo bar=\"baz baz baz\" tail\n"; g_autoptr(GHashTable) hash = NULL; hash = fu_kernel_parse_cmdline(buf, strlen(buf)); g_assert_nonnull(hash); g_assert_true(g_hash_table_contains(hash, "key")); g_assert_cmpstr(g_hash_table_lookup(hash, "key"), ==, "val"); g_assert_true(g_hash_table_contains(hash, "foo")); g_assert_cmpstr(g_hash_table_lookup(hash, "foo"), ==, NULL); g_assert_true(g_hash_table_contains(hash, "bar")); g_assert_cmpstr(g_hash_table_lookup(hash, "bar"), ==, "baz baz baz"); g_assert_true(g_hash_table_contains(hash, "tail")); g_assert_false(g_hash_table_contains(hash, "")); } static void fu_kernel_config_func(void) { const gchar *buf = "CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE=y\n\n" "# CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY is not set\n"; g_autoptr(GHashTable) hash = NULL; g_autoptr(GError) error = NULL; hash = fu_kernel_parse_config(buf, strlen(buf), &error); g_assert_no_error(error); g_assert_nonnull(hash); g_assert_true(g_hash_table_contains(hash, "CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE")); g_assert_cmpstr(g_hash_table_lookup(hash, "CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE"), ==, "y"); g_assert_false(g_hash_table_contains(hash, "CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY")); } static void fu_smbios3_func(void) { const gchar *str; gboolean ret; g_autofree gchar *dump = NULL; g_autofree gchar *path = NULL; g_autoptr(FuSmbios) smbios = NULL; g_autoptr(GError) error = NULL; path = g_test_build_filename(G_TEST_DIST, "tests", "dmi", "tables64", NULL); smbios = fu_smbios_new(); ret = fu_smbios_setup_from_path(smbios, path, &error); g_assert_no_error(error); g_assert_true(ret); dump = fu_firmware_to_string(FU_FIRMWARE(smbios)); g_debug("%s", dump); /* get vendor */ str = fu_smbios_get_string(smbios, FU_SMBIOS_STRUCTURE_TYPE_BIOS, 0x04, &error); g_assert_no_error(error); g_assert_cmpstr(str, ==, "Dell Inc."); } static void fu_context_flags_func(void) { g_autoptr(FuContext) ctx = fu_context_new(); g_assert_false(fu_context_has_flag(ctx, FU_CONTEXT_FLAG_SAVE_EVENTS)); fu_context_add_flag(ctx, FU_CONTEXT_FLAG_SAVE_EVENTS); g_assert_true(fu_context_has_flag(ctx, FU_CONTEXT_FLAG_SAVE_EVENTS)); fu_context_remove_flag(ctx, FU_CONTEXT_FLAG_SAVE_EVENTS); fu_context_remove_flag(ctx, FU_CONTEXT_FLAG_SAVE_EVENTS); g_assert_false(fu_context_has_flag(ctx, FU_CONTEXT_FLAG_SAVE_EVENTS)); fu_context_add_flag(ctx, FU_CONTEXT_FLAG_SAVE_EVENTS); fu_context_add_flag(ctx, FU_CONTEXT_FLAG_SAVE_EVENTS); g_assert_true(fu_context_has_flag(ctx, FU_CONTEXT_FLAG_SAVE_EVENTS)); } static void fu_context_hwids_dmi_func(void) { g_autofree gchar *dump = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; gboolean ret; ret = fu_context_load_hwinfo(ctx, progress, FU_CONTEXT_HWID_FLAG_LOAD_DMI, &error); g_assert_no_error(error); g_assert_true(ret); dump = fu_firmware_to_string(FU_FIRMWARE(fu_context_get_smbios(ctx))); g_debug("%s", dump); g_assert_cmpstr(fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_MANUFACTURER), ==, "FwupdTest"); g_assert_cmpuint(fu_context_get_chassis_kind(ctx), ==, 16); } static gboolean _strnsplit_add_cb(GString *token, guint token_idx, gpointer user_data, GError **error) { GPtrArray *array = (GPtrArray *)user_data; g_debug("TOKEN: [%s] (%u)", token->str, token_idx); g_ptr_array_add(array, g_strdup(token->str)); return TRUE; } static gboolean _strnsplit_nop_cb(GString *token, guint token_idx, gpointer user_data, GError **error) { guint *cnt = (guint *)user_data; (*cnt)++; return TRUE; } static void fu_common_memmem_func(void) { const guint8 haystack[] = {'H', 'A', 'Y', 'S'}; const guint8 needle[] = {'A', 'Y'}; gboolean ret; gsize offset = 0; g_autoptr(GError) error = NULL; ret = fu_memmem_safe(haystack, sizeof(haystack), needle, sizeof(needle), &offset, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(offset, ==, 0x1); ret = fu_memmem_safe(haystack + 2, sizeof(haystack) - 2, needle, sizeof(needle), &offset, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_false(ret); } static void fu_strpassmask_func(void) { struct { const gchar *in; const gchar *op; } strs[] = {{"foo https://test.com/auth bar", "foo https://test.com/auth bar"}, {"foo https://user%40host:SECRET@test.com/auth bar", "foo https://user%40host:XXXXXX@test.com/auth bar"}, {"foo https://user1%40host:SECRET@test.com/auth " "https://user2%40host:SECRET2@test.com/auth bar", "foo https://user1%40host:XXXXXX@test.com/auth " "https://user2%40host:XXXXXXX@test.com/auth bar"}, {NULL, NULL}}; for (guint i = 0; strs[i].in != NULL; i++) { g_autofree gchar *tmp = fu_strpassmask(strs[i].in); g_assert_cmpstr(tmp, ==, strs[i].op); } } static void fu_strsplit_func(void) { const gchar *str = "123foo123bar123"; const guint bigsz = 1024 * 1024; gboolean ret; guint cnt = 0; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free); g_autoptr(GString) bigstr = g_string_sized_new(bigsz * 2); /* works for me */ ret = fu_strsplit_full(str, -1, "123", _strnsplit_add_cb, array, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(array->len, ==, 3); g_assert_cmpstr(g_ptr_array_index(array, 0), ==, ""); g_assert_cmpstr(g_ptr_array_index(array, 1), ==, "foo"); g_assert_cmpstr(g_ptr_array_index(array, 2), ==, "bar"); /* lets try something insane */ for (guint i = 0; i < bigsz; i++) g_string_append(bigstr, "X\n"); ret = fu_strsplit_full(bigstr->str, -1, "\n", _strnsplit_nop_cb, &cnt, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(cnt, ==, bigsz); } static void fu_common_olson_timezone_id_func(void) { g_autofree gchar *timezone_id = NULL; g_autoptr(GError) error = NULL; #ifdef HOST_MACHINE_SYSTEM_DARWIN g_test_skip("not supported on Darwin"); return; #endif timezone_id = fu_common_get_olson_timezone_id(&error); g_assert_no_error(error); #ifdef _WIN32 /* we do not emulate this on Windows, so just check for anything */ g_assert_nonnull(timezone_id); #else g_assert_cmpstr(timezone_id, ==, "America/New_York"); #endif } static void fu_strsafe_func(void) { struct { const gchar *in; const gchar *op; } strs[] = {{"dave123", "dave123"}, {"dave123XXX", "dave123"}, {"dave\x03XXX", "dave.XX"}, {"dave\x03\x04XXX", "dave..X"}, {"\x03\x03", NULL}, {NULL, NULL}}; GPtrArray *instance_ids; gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuDevice) dev = fu_device_new(ctx); g_autoptr(GError) error = NULL; /* check bespoke legacy instance ID behavior */ fu_device_add_instance_strsafe(dev, "KEY", "_ _LEN&VO&\\&"); ret = fu_device_build_instance_id(dev, &error, "SUB", "KEY", NULL); g_assert_no_error(error); g_assert_true(ret); instance_ids = fu_device_get_instance_ids(dev); g_assert_cmpint(instance_ids->len, ==, 1); g_assert_cmpstr(g_ptr_array_index(instance_ids, 0), ==, "SUB\\KEY_LEN-VO"); for (guint i = 0; strs[i].in != NULL; i++) { g_autofree gchar *tmp = fu_strsafe(strs[i].in, 7); g_assert_cmpstr(tmp, ==, strs[i].op); } } static void fu_hwids_func(void) { g_autofree gchar *testdatadir = NULL; g_autoptr(FuContext) context = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; gboolean ret; struct { const gchar *key; const gchar *value; } guids[] = {{"Manufacturer", "6de5d951-d755-576b-bd09-c5cf66b27234"}, {"HardwareID-14", "6de5d951-d755-576b-bd09-c5cf66b27234"}, {"HardwareID-13", "f8e1de5f-b68c-5f52-9d1a-f1ba52f1f773"}, {"HardwareID-12", "e093d715-70f7-51f4-b6c8-b4a7e31def85"}, {"HardwareID-11", "db73af4c-4612-50f7-b8a7-787cf4871847"}, {"HardwareID-10", "f4275c1f-6130-5191-845c-3426247eb6a1"}, {"HardwareID-9", "0cf8618d-9eff-537c-9f35-46861406eb9c"}, {"HardwareID-8", "059eb22d-6dc7-59af-abd3-94bbe017f67c"}, {"HardwareID-7", "da1da9b6-62f5-5f22-8aaa-14db7eeda2a4"}, {"HardwareID-6", "178cd22d-ad9f-562d-ae0a-34009822cdbe"}, {"HardwareID-5", "8dc9b7c5-f5d5-5850-9ab3-bd6f0549d814"}, {"HardwareID-4", "660ccba8-1b78-5a33-80e6-9fb8354ee873"}, {"HardwareID-3", "3faec92a-3ae3-5744-be88-495e90a7d541"}, {"HardwareID-2", "f5ff077f-3eeb-5bae-be1c-e98ffe8ce5f8"}, {"HardwareID-1", "b7cceb67-774c-537e-bf8b-22c6107e9a74"}, {"HardwareID-0", "147efce9-f201-5fc8-ab0c-c859751c3440"}, {NULL, NULL}}; #ifdef _WIN32 g_test_skip("Windows uses GetSystemFirmwareTable rather than parsing the fake test data"); return; #endif /* these tests will not write */ testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); (void)g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); context = fu_context_new(); ret = fu_context_load_hwinfo(context, progress, FU_CONTEXT_HWID_FLAG_LOAD_SMBIOS, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpstr(fu_context_get_hwid_value(context, FU_HWIDS_KEY_MANUFACTURER), ==, "LENOVO"); g_assert_cmpstr(fu_context_get_hwid_value(context, FU_HWIDS_KEY_ENCLOSURE_KIND), ==, "a"); g_assert_cmpstr(fu_context_get_hwid_value(context, FU_HWIDS_KEY_FAMILY), ==, "ThinkPad T440s"); g_assert_cmpstr(fu_context_get_hwid_value(context, FU_HWIDS_KEY_PRODUCT_NAME), ==, "20ARS19C0C"); g_assert_cmpstr(fu_context_get_hwid_value(context, FU_HWIDS_KEY_BIOS_VENDOR), ==, "LENOVO"); g_assert_cmpstr(fu_context_get_hwid_value(context, FU_HWIDS_KEY_BIOS_VERSION), ==, "GJET75WW (2.25 )"); g_assert_cmpstr(fu_context_get_hwid_value(context, FU_HWIDS_KEY_BIOS_MAJOR_RELEASE), ==, "02"); g_assert_cmpstr(fu_context_get_hwid_value(context, FU_HWIDS_KEY_BIOS_MINOR_RELEASE), ==, "19"); g_assert_cmpstr(fu_context_get_hwid_value(context, FU_HWIDS_KEY_PRODUCT_SKU), ==, "LENOVO_MT_20AR_BU_Think_FM_ThinkPad T440s"); for (guint i = 0; guids[i].key != NULL; i++) { FuHwids *hwids = fu_context_get_hwids(context); g_autofree gchar *guid = fu_hwids_get_guid(hwids, guids[i].key, &error); g_assert_no_error(error); g_assert_cmpstr(guid, ==, guids[i].value); } for (guint i = 0; guids[i].key != NULL; i++) g_assert_true(fu_context_has_hwid_guid(context, guids[i].value)); } static void _plugin_device_added_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data) { FuDevice **dev = (FuDevice **)user_data; *dev = g_object_ref(device); fu_test_loop_quit(); } static void fu_config_func(void) { GStatBuf statbuf = {0}; gboolean ret; g_autofree gchar *composite_data = NULL; g_autoptr(FuConfig) config = fu_config_new(); g_autoptr(GError) error = NULL; g_autofree gchar *fn_imu = NULL; g_autofree gchar *fn_mut = NULL; #ifdef _WIN32 /* the Windows file permission model is different than a simple octal value */ g_test_skip("chmod not supported on Windows"); return; #endif /* immutable file */ (void)g_setenv("FWUPD_SYSCONFDIR", "/tmp/fwupd-self-test/etc/fwupd", TRUE); fn_imu = g_build_filename(g_getenv("FWUPD_SYSCONFDIR"), "fwupd.conf", NULL); ret = fu_path_mkdir_parent(fn_imu, &error); g_assert_no_error(error); g_assert_true(ret); g_remove(fn_imu); ret = g_file_set_contents(fn_imu, "[fwupd]\n" "Key=true\n", -1, &error); g_assert_no_error(error); g_assert_true(ret); g_chmod(fn_imu, 0640); ret = g_stat(fn_imu, &statbuf); g_assert_cmpint(ret, ==, 0); g_assert_cmpint(statbuf.st_mode & 0777, ==, 0640); /* mutable file */ (void)g_setenv("LOCALCONF_DIRECTORY", "/tmp/fwupd-self-test/var/etc/fwupd", TRUE); fn_mut = g_build_filename(g_getenv("LOCALCONF_DIRECTORY"), "fwupd.conf", NULL); ret = fu_path_mkdir_parent(fn_mut, &error); g_assert_no_error(error); g_assert_true(ret); g_remove(fn_mut); ret = g_file_set_contents(fn_mut, "# group comment\n" "[fwupd]\n" "# key comment\n" "Key=false\n", -1, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_config_load(config, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_config_set_value(config, "fwupd", "Key", "false", &error); g_assert_no_error(error); g_assert_true(ret); ret = g_file_get_contents(fn_mut, &composite_data, NULL, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_true(g_strstr_len(composite_data, -1, "Key=false") != NULL); g_assert_true(g_strstr_len(composite_data, -1, "Key=true") == NULL); g_assert_true(g_strstr_len(composite_data, -1, "# group comment") != NULL); g_assert_true(g_strstr_len(composite_data, -1, "# key comment") != NULL); g_remove(fn_mut); } static void fu_plugin_config_func(void) { GStatBuf statbuf = {0}; gboolean ret; gint rc; g_autofree gchar *conf_dir = NULL; g_autofree gchar *fn = NULL; g_autofree gchar *value = NULL; g_autofree gchar *value_missing = NULL; g_autofree gchar *fn_mut = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuPlugin) plugin = fu_plugin_new(ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; #ifdef _WIN32 /* the Windows file permission model is different than a simple octal value */ g_test_skip("chmod not supported on Windows"); return; #endif /* remove existing file */ (void)g_setenv("FWUPD_SYSCONFDIR", "/tmp/fwupd-self-test/etc/fwupd", TRUE); conf_dir = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR_PKG); fu_plugin_set_name(plugin, "test"); fn = g_build_filename(conf_dir, "fwupd.conf", NULL); ret = fu_path_mkdir_parent(fn, &error); g_assert_no_error(error); g_assert_true(ret); g_remove(fn); ret = g_file_set_contents(fn, "", -1, &error); g_assert_no_error(error); g_assert_true(ret); /* mutable file we'll be writing */ (void)g_setenv("LOCALCONF_DIRECTORY", "/tmp/fwupd-self-test/var/etc/fwupd", TRUE); fn_mut = g_build_filename(g_getenv("LOCALCONF_DIRECTORY"), "fwupd.conf", NULL); g_assert_nonnull(fn_mut); ret = fu_path_mkdir_parent(fn_mut, &error); g_assert_no_error(error); g_assert_true(ret); g_remove(fn_mut); ret = g_file_set_contents(fn_mut, "", -1, &error); g_assert_no_error(error); g_assert_true(ret); /* load context */ ret = fu_context_load_hwinfo(ctx, progress, FU_CONTEXT_HWID_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); /* set a value */ ret = fu_plugin_set_config_value(plugin, "Key", "True", &error); g_assert_no_error(error); g_assert_true(ret); g_assert_true(g_file_test(fn, G_FILE_TEST_EXISTS)); /* check it is only readable by the user/group */ rc = g_stat(fn_mut, &statbuf); g_assert_cmpint(rc, ==, 0); g_assert_cmpint(statbuf.st_mode & 0777, ==, 0640); /* read back the value */ fu_plugin_set_config_default(plugin, "NotGoingToExist", "Foo"); value_missing = fu_plugin_get_config_value(plugin, "NotGoingToExist"); g_assert_cmpstr(value_missing, ==, "Foo"); value = fu_plugin_get_config_value(plugin, "Key"); g_assert_cmpstr(value, ==, "True"); g_assert_true(fu_plugin_get_config_value_boolean(plugin, "Key")); } static void fu_plugin_devices_func(void) { g_autoptr(FuDevice) device = fu_device_new(NULL); g_autoptr(FuDevice) child = fu_device_new(NULL); g_autoptr(FuPlugin) plugin = fu_plugin_new(NULL); GPtrArray *devices; devices = fu_plugin_get_devices(plugin); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 0); fu_device_set_id(device, "testdev"); fu_device_set_name(device, "testdev"); fu_plugin_device_add(plugin, device); g_assert_cmpint(devices->len, ==, 1); fu_plugin_device_remove(plugin, device); g_assert_cmpint(devices->len, ==, 0); /* add a child after adding the parent to the plugin */ fu_device_set_id(child, "child"); fu_device_set_name(child, "child"); fu_device_add_child(device, child); g_assert_cmpint(devices->len, ==, 1); /* remove said child */ fu_device_remove_child(device, child); g_assert_cmpint(devices->len, ==, 0); } static void fu_plugin_device_inhibit_children_func(void) { g_autoptr(FuDevice) parent = fu_device_new(NULL); g_autoptr(FuDevice) child1 = fu_device_new(NULL); g_autoptr(FuDevice) child2 = fu_device_new(NULL); fu_device_set_id(parent, "testdev"); fu_device_set_name(parent, "testdev"); fu_device_add_flag(parent, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_id(child1, "child1"); fu_device_set_name(child1, "child1"); fu_device_add_flag(child1, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_child(parent, child1); /* inhibit the parent */ fu_device_inhibit(parent, "test", "because"); g_assert_false(fu_device_has_flag(parent, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_true(fu_device_has_flag(child1, FWUPD_DEVICE_FLAG_UPDATABLE)); fu_device_uninhibit(parent, "test"); /* make the inhibit propagate to children */ fu_device_add_internal_flag(parent, FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN); fu_device_inhibit(parent, "test", "because"); g_assert_false(fu_device_has_flag(parent, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_false(fu_device_has_flag(child1, FWUPD_DEVICE_FLAG_UPDATABLE)); /* add a child after the inhibit, which should also be inhibited too */ fu_device_set_id(child2, "child2"); fu_device_set_name(child2, "child2"); fu_device_add_flag(child2, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_child(parent, child2); g_assert_false(fu_device_has_flag(parent, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_false(fu_device_has_flag(child1, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_false(fu_device_has_flag(child2, FWUPD_DEVICE_FLAG_UPDATABLE)); } static void fu_plugin_delay_func(void) { FuDevice *device_tmp; g_autoptr(FuPlugin) plugin = NULL; g_autoptr(FuDevice) device = NULL; plugin = fu_plugin_new(NULL); g_signal_connect(FU_PLUGIN(plugin), "device-added", G_CALLBACK(_plugin_device_added_cb), &device_tmp); g_signal_connect(FU_PLUGIN(plugin), "device-removed", G_CALLBACK(_plugin_device_added_cb), &device_tmp); /* add device straight away */ device = fu_device_new(NULL); fu_device_set_id(device, "testdev"); fu_plugin_device_add(plugin, device); g_assert_nonnull(device_tmp); g_assert_cmpstr(fu_device_get_id(device_tmp), ==, "b7eccd0059d6d7dc2ef76c35d6de0048cc8c029d"); g_clear_object(&device_tmp); /* remove device */ fu_plugin_device_remove(plugin, device); g_assert_nonnull(device_tmp); g_assert_cmpstr(fu_device_get_id(device_tmp), ==, "b7eccd0059d6d7dc2ef76c35d6de0048cc8c029d"); g_clear_object(&device_tmp); } static void fu_plugin_fdt_func(void) { gboolean ret; g_autofree gchar *compatible = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuFirmware) fdt = NULL; g_autoptr(FuFirmware) fdt_root = NULL; g_autoptr(FuFirmware) fdt_tmp = fu_fdt_firmware_new(); g_autoptr(FuFirmware) img2 = NULL; g_autoptr(FuFirmware) img3 = NULL; g_autoptr(FuFirmware) img4 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file = g_file_new_for_path("/tmp/fwupd-self-test/var/lib/fwupd/system.dtb"); /* write file */ ret = fu_firmware_build_from_xml( FU_FIRMWARE(fdt_tmp), "\n" " \n" " pine64,rockpro64-v2.1\n" " \n" "\n", &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_write_file(FU_FIRMWARE(fdt_tmp), file, &error); g_assert_no_error(error); g_assert_true(ret); /* get compatible from the context */ fdt = fu_context_get_fdt(ctx, &error); g_assert_no_error(error); g_assert_nonnull(fdt); fdt_root = fu_firmware_get_image_by_id(fdt, NULL, &error); g_assert_no_error(error); g_assert_nonnull(fdt_root); ret = fu_fdt_image_get_attr_str(FU_FDT_IMAGE(fdt_root), "compatible", &compatible, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpstr(compatible, ==, "pine64,rockpro64-v2.1"); /* get by GType */ img2 = fu_firmware_get_image_by_gtype(fdt, FU_TYPE_FIRMWARE, &error); g_assert_no_error(error); g_assert_nonnull(img2); img3 = fu_firmware_get_image_by_gtype(fdt, FU_TYPE_FDT_IMAGE, &error); g_assert_no_error(error); g_assert_nonnull(img3); img4 = fu_firmware_get_image_by_gtype(fdt, G_TYPE_STRING, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_null(img4); } static void fu_plugin_quirks_func(void) { const gchar *tmp; gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(GError) error = NULL; ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); /* USB\\VID_0A5C&PID_6412 */ tmp = fu_context_lookup_quirk_by_id(ctx, "7a1ba7b9-6bcd-54a4-8a36-d60cc5ee935c", "Flags"); g_assert_cmpstr(tmp, ==, "ignore-runtime"); /* ACME Inc.=True */ tmp = fu_context_lookup_quirk_by_id(ctx, "ec77e295-7c63-5935-9957-be0472d9593a", "Name"); g_assert_cmpstr(tmp, ==, "awesome"); /* CORP* */ tmp = fu_context_lookup_quirk_by_id(ctx, "3731cce4-484c-521f-a652-892c8e0a65c7", "Name"); g_assert_cmpstr(tmp, ==, "town"); /* baz */ tmp = fu_context_lookup_quirk_by_id(ctx, "579a3b1c-d1db-5bdc-b6b9-e2c1b28d5b8a", "Unfound"); g_assert_cmpstr(tmp, ==, NULL); /* unfound */ tmp = fu_context_lookup_quirk_by_id(ctx, "8ff2ed23-b37e-5f61-b409-b7fe9563be36", "tests"); g_assert_cmpstr(tmp, ==, NULL); /* unfound */ tmp = fu_context_lookup_quirk_by_id(ctx, "8ff2ed23-b37e-5f61-b409-b7fe9563be36", "unfound"); g_assert_cmpstr(tmp, ==, NULL); /* GUID */ tmp = fu_context_lookup_quirk_by_id(ctx, "bb9ec3e2-77b3-53bc-a1f1-b05916715627", "Flags"); g_assert_cmpstr(tmp, ==, "clever"); } static void fu_plugin_quirks_performance_func(void) { gboolean ret; g_autoptr(FuQuirks) quirks = fu_quirks_new(); g_autoptr(GTimer) timer = g_timer_new(); g_autoptr(GError) error = NULL; const gchar *keys[] = {"Name", "Children", "Flags", NULL}; ret = fu_quirks_load(quirks, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); /* lookup */ g_timer_reset(timer); for (guint j = 0; j < 1000; j++) { const gchar *group = "bb9ec3e2-77b3-53bc-a1f1-b05916715627"; for (guint i = 0; keys[i] != NULL; i++) { const gchar *tmp = fu_quirks_lookup_by_id(quirks, group, keys[i]); g_assert_cmpstr(tmp, !=, NULL); } } g_print("lookup=%.3fms ", g_timer_elapsed(timer, NULL) * 1000.f); } typedef struct { gboolean seen_one; gboolean seen_two; } FuPluginQuirksAppendHelper; static void fu_plugin_quirks_append_cb(FuQuirks *quirks, const gchar *key, const gchar *value, gpointer user_data) { FuPluginQuirksAppendHelper *helper = (FuPluginQuirksAppendHelper *)user_data; g_debug("key=%s, value=%s", key, value); if (g_strcmp0(key, "Plugin") == 0 && g_strcmp0(value, "one") == 0) { helper->seen_one = TRUE; return; } if (g_strcmp0(key, "Plugin") == 0 && g_strcmp0(value, "two") == 0) { helper->seen_two = TRUE; return; } g_assert_not_reached(); } static void fu_plugin_device_progress_func(void) { g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuDevice) device = fu_device_new(ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(FuDeviceProgress) device_progress = fu_device_progress_new(device, progress); /* proxy */ fu_progress_set_percentage(progress, 50); fu_progress_set_status(progress, FWUPD_STATUS_SHUTDOWN); g_assert_cmpint(fu_device_get_percentage(device), ==, 50); g_assert_cmpint(fu_device_get_status(device), ==, FWUPD_STATUS_SHUTDOWN); /* clear */ g_clear_object(&device_progress); g_assert_cmpint(fu_device_get_percentage(device), ==, 0); g_assert_cmpint(fu_device_get_status(device), ==, FWUPD_STATUS_IDLE); /* do not proxy */ fu_progress_set_percentage(progress, 100); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_BUSY); g_assert_cmpint(fu_device_get_percentage(device), ==, 0); g_assert_cmpint(fu_device_get_status(device), ==, FWUPD_STATUS_IDLE); } static void fu_plugin_quirks_append_func(void) { FuPluginQuirksAppendHelper helper = {0}; gboolean ret; g_autoptr(FuQuirks) quirks = fu_quirks_new(); g_autoptr(GError) error = NULL; /* lookup a duplicate group name */ ret = fu_quirks_load(quirks, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_quirks_lookup_by_id_iter(quirks, "b19d1c67-a29a-51ce-9cae-f7b40fe5505b", NULL, fu_plugin_quirks_append_cb, &helper); g_assert_true(ret); g_assert_true(helper.seen_one); g_assert_true(helper.seen_two); } static void fu_plugin_device_gtype_func(void) { g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuPlugin) plugin = fu_plugin_new(ctx); /* add the same gtype multiple times */ fu_plugin_add_device_gtype(plugin, FU_TYPE_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_DEVICE); g_assert_cmpint(fu_plugin_get_device_gtype_default(plugin), ==, FU_TYPE_DEVICE); /* now there's no explicit default */ fu_plugin_add_device_gtype(plugin, FU_TYPE_USB_DEVICE); g_assert_cmpint(fu_plugin_get_device_gtype_default(plugin), ==, G_TYPE_INVALID); /* make it explicit */ fu_plugin_set_device_gtype_default(plugin, FU_TYPE_USB_DEVICE); g_assert_cmpint(fu_plugin_get_device_gtype_default(plugin), ==, FU_TYPE_USB_DEVICE); } static void fu_plugin_quirks_device_func(void) { FuDevice *device_tmp; GPtrArray *children; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(NULL); g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(GError) error = NULL; ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); /* use quirk file to set device attributes */ fu_device_set_physical_id(device, "usb:00:05"); fu_device_set_context(device, ctx); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_instance_id_full(device, "USB\\VID_0BDA&PID_1100", FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_QUIRKS | FU_DEVICE_INSTANCE_FLAG_VISIBLE); fu_device_add_instance_id(device, "USB\\VID_0BDA&PID_1100&CID_1234"); fu_device_convert_instance_ids(device); g_assert_cmpstr(fu_device_get_name(device), ==, "Hub"); /* ensure the non-customer-id instance ID is not available */ g_assert_true(fu_device_has_instance_id(device, "USB\\VID_0BDA&PID_1100&CID_1234")); g_assert_false(fu_device_has_instance_id(device, "USB\\VID_0BDA&PID_1100")); /* ensure children are created */ children = fu_device_get_children(device); g_assert_cmpint(children->len, ==, 1); device_tmp = g_ptr_array_index(children, 0); g_assert_cmpstr(fu_device_get_name(device_tmp), ==, "HDMI"); g_assert_true(fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_UPDATABLE)); } static void fu_common_kernel_lockdown_func(void) { gboolean ret; g_autofree gchar *locked_dir = NULL; g_autofree gchar *none_dir = NULL; g_autofree gchar *old_kernel_dir = NULL; #ifndef __linux__ g_test_skip("only works on Linux"); return; #endif old_kernel_dir = g_test_build_filename(G_TEST_DIST, "tests", "lockdown", NULL); (void)g_setenv("FWUPD_SYSFSSECURITYDIR", old_kernel_dir, TRUE); ret = fu_kernel_locked_down(); g_assert_false(ret); locked_dir = g_test_build_filename(G_TEST_DIST, "tests", "lockdown", "locked", NULL); (void)g_setenv("FWUPD_SYSFSSECURITYDIR", locked_dir, TRUE); ret = fu_kernel_locked_down(); g_assert_true(ret); none_dir = g_test_build_filename(G_TEST_DIST, "tests", "lockdown", "none", NULL); (void)g_setenv("FWUPD_SYSFSSECURITYDIR", none_dir, TRUE); ret = fu_kernel_locked_down(); g_assert_false(ret); } static gboolean _open_cb(GObject *device, GError **error) { g_assert_cmpstr(g_object_get_data(device, "state"), ==, "closed"); g_object_set_data(device, "state", (gpointer) "opened"); return TRUE; } static gboolean _close_cb(GObject *device, GError **error) { g_assert_cmpstr(g_object_get_data(device, "state"), ==, "opened"); g_object_set_data(device, "state", (gpointer) "closed-on-unref"); return TRUE; } static void fu_device_locker_func(void) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error = NULL; g_autoptr(GObject) device = g_object_new(G_TYPE_OBJECT, NULL); g_object_set_data(device, "state", (gpointer) "closed"); locker = fu_device_locker_new_full(device, _open_cb, _close_cb, &error); g_assert_no_error(error); g_assert_nonnull(locker); g_clear_object(&locker); g_assert_cmpstr(g_object_get_data(device, "state"), ==, "closed-on-unref"); } static gboolean _fail_open_cb(FuDevice *device, GError **error) { fu_device_set_metadata_boolean(device, "Test::Open", TRUE); g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "fail"); return FALSE; } static gboolean _fail_close_cb(FuDevice *device, GError **error) { fu_device_set_metadata_boolean(device, "Test::Close", TRUE); g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "busy"); return FALSE; } static void fu_device_locker_fail_func(void) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error = NULL; g_autoptr(FuDevice) device = fu_device_new(NULL); locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)_fail_open_cb, (FuDeviceLockerFunc)_fail_close_cb, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_FAILED); g_assert_null(locker); g_assert_true(fu_device_get_metadata_boolean(device, "Test::Open")); g_assert_true(fu_device_get_metadata_boolean(device, "Test::Close")); g_assert_false(fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_IS_OPEN)); } static void fu_common_endian_func(void) { guint8 buf[3]; fu_memwrite_uint16(buf, 0x1234, G_LITTLE_ENDIAN); g_assert_cmpint(buf[0], ==, 0x34); g_assert_cmpint(buf[1], ==, 0x12); g_assert_cmpint(fu_memread_uint16(buf, G_LITTLE_ENDIAN), ==, 0x1234); fu_memwrite_uint16(buf, 0x1234, G_BIG_ENDIAN); g_assert_cmpint(buf[0], ==, 0x12); g_assert_cmpint(buf[1], ==, 0x34); g_assert_cmpint(fu_memread_uint16(buf, G_BIG_ENDIAN), ==, 0x1234); fu_memwrite_uint24(buf, 0x123456, G_LITTLE_ENDIAN); g_assert_cmpint(buf[0], ==, 0x56); g_assert_cmpint(buf[1], ==, 0x34); g_assert_cmpint(buf[2], ==, 0x12); g_assert_cmpint(fu_memread_uint24(buf, G_LITTLE_ENDIAN), ==, 0x123456); fu_memwrite_uint24(buf, 0x123456, G_BIG_ENDIAN); g_assert_cmpint(buf[0], ==, 0x12); g_assert_cmpint(buf[1], ==, 0x34); g_assert_cmpint(buf[2], ==, 0x56); g_assert_cmpint(fu_memread_uint24(buf, G_BIG_ENDIAN), ==, 0x123456); } static void fu_common_bytes_get_data_func(void) { const gchar *fn = "/tmp/fwupdzero"; const guint8 *buf; gboolean ret; g_autoptr(GBytes) bytes1 = NULL; g_autoptr(GBytes) bytes2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GMappedFile) mmap = NULL; /* create file with zero size */ ret = g_file_set_contents(fn, NULL, 0, &error); g_assert_no_error(error); g_assert_true(ret); /* check we got zero sized data */ bytes1 = fu_bytes_get_contents(fn, &error); g_assert_no_error(error); g_assert_nonnull(bytes1); g_assert_cmpint(g_bytes_get_size(bytes1), ==, 0); g_assert_nonnull(g_bytes_get_data(bytes1, NULL)); /* do the same with an mmap mapping, which returns NULL on empty file */ mmap = g_mapped_file_new(fn, FALSE, &error); g_assert_no_error(error); g_assert_nonnull(mmap); bytes2 = g_mapped_file_get_bytes(mmap); g_assert_nonnull(bytes2); g_assert_cmpint(g_bytes_get_size(bytes2), ==, 0); g_assert_null(g_bytes_get_data(bytes2, NULL)); /* use the safe function */ buf = fu_bytes_get_data_safe(bytes2, NULL, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_null(buf); } static gboolean fu_device_poll_cb(FuDevice *device, GError **error) { guint64 cnt = fu_device_get_metadata_integer(device, "cnt"); g_debug("poll cnt=%" G_GUINT64_FORMAT, cnt); fu_device_set_metadata_integer(device, "cnt", cnt + 1); return TRUE; } static void fu_device_poll_func(void) { g_autoptr(FuDevice) device = fu_device_new(NULL); FuDeviceClass *klass = FU_DEVICE_GET_CLASS(device); guint cnt; /* set up a 10ms poll */ klass->poll = fu_device_poll_cb; fu_device_set_metadata_integer(device, "cnt", 0); fu_device_set_poll_interval(device, 10); fu_test_loop_run_with_timeout(100); fu_test_loop_quit(); cnt = fu_device_get_metadata_integer(device, "cnt"); g_assert_cmpint(cnt, >=, 8); /* disable the poll */ fu_device_set_poll_interval(device, 0); fu_test_loop_run_with_timeout(100); fu_test_loop_quit(); g_assert_cmpint(fu_device_get_metadata_integer(device, "cnt"), ==, cnt); } static void fu_device_func(void) { g_autoptr(FuDevice) device = fu_device_new(NULL); g_autoptr(GPtrArray) possible_plugins = NULL; /* only add one plugin name of the same type */ fu_device_add_possible_plugin(device, "test"); fu_device_add_possible_plugin(device, "test"); possible_plugins = fu_device_get_possible_plugins(device); g_assert_cmpint(possible_plugins->len, ==, 1); } static void fu_device_instance_ids_func(void) { gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuDevice) device = fu_device_new(ctx); g_autoptr(GError) error = NULL; /* do not save silo */ ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); /* sanity check */ g_assert_false(fu_device_has_guid(device, "c0a26214-223b-572a-9477-cde897fe8619")); /* add a deferred instance ID that only gets converted on ->setup */ fu_device_add_instance_id(device, "foobarbaz"); g_assert_false(fu_device_has_guid(device, "c0a26214-223b-572a-9477-cde897fe8619")); ret = fu_device_setup(device, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_true(fu_device_has_guid(device, "c0a26214-223b-572a-9477-cde897fe8619")); /* this gets added immediately */ fu_device_add_instance_id(device, "bazbarfoo"); g_assert_true(fu_device_has_guid(device, "77e49bb0-2cd6-5faf-bcee-5b7fbe6e944d")); } static void fu_device_composite_id_func(void) { g_autoptr(FuDevice) dev1 = fu_device_new(NULL); g_autoptr(FuDevice) dev2 = fu_device_new(NULL); g_autoptr(FuDevice) dev3 = fu_device_new(NULL); g_autoptr(FuDevice) dev4 = fu_device_new(NULL); /* single device */ fu_device_set_id(dev1, "dev1"); g_assert_cmpstr(fu_device_get_composite_id(dev1), ==, "3b42553c4e3241e8f3f8fbc19a69fa2f95708a9d"); fu_device_set_id(dev2, "dev2"); /* one child */ fu_device_add_child(dev1, dev2); g_assert_cmpstr(fu_device_get_composite_id(dev1), ==, "3b42553c4e3241e8f3f8fbc19a69fa2f95708a9d"); g_assert_cmpstr(fu_device_get_composite_id(dev2), ==, "3b42553c4e3241e8f3f8fbc19a69fa2f95708a9d"); /* add a different "family" */ fu_device_set_id(dev3, "dev3"); fu_device_set_id(dev4, "dev4"); fu_device_add_child(dev3, dev4); fu_device_add_child(dev2, dev3); g_assert_cmpstr(fu_device_get_composite_id(dev1), ==, "3b42553c4e3241e8f3f8fbc19a69fa2f95708a9d"); g_assert_cmpstr(fu_device_get_composite_id(dev2), ==, "3b42553c4e3241e8f3f8fbc19a69fa2f95708a9d"); g_assert_cmpstr(fu_device_get_composite_id(dev3), ==, "3b42553c4e3241e8f3f8fbc19a69fa2f95708a9d"); g_assert_cmpstr(fu_device_get_composite_id(dev4), ==, "3b42553c4e3241e8f3f8fbc19a69fa2f95708a9d"); /* change the parent ID */ fu_device_set_id(dev1, "dev1-NEW"); g_assert_cmpstr(fu_device_get_composite_id(dev1), ==, "a4c8efc6a0a58c2dc14c05fd33186703f7352997"); g_assert_cmpstr(fu_device_get_composite_id(dev2), ==, "a4c8efc6a0a58c2dc14c05fd33186703f7352997"); } static void fu_device_inhibit_func(void) { g_autoptr(FuDevice) device = fu_device_new(NULL); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_battery_threshold(device, 25); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)); /* does not exist -> fine */ fu_device_uninhibit(device, "NOTGOINGTOEXIST"); g_assert_false(fu_device_has_inhibit(device, "NOTGOINGTOEXIST")); /* first one */ fu_device_inhibit(device, "needs-activation", "Device is pending activation"); g_assert_true(fu_device_has_inhibit(device, "needs-activation")); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)); g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE)); /* another */ fu_device_set_battery_level(device, 5); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)); g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE)); /* activated, power still too low */ fu_device_uninhibit(device, "needs-activation"); g_assert_false(fu_device_has_inhibit(device, "needs-activation")); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)); g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE)); /* we got some more power -> fine */ fu_device_set_battery_level(device, 95); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)); } static void fu_device_inhibit_updateable_func(void) { g_autoptr(FuDevice) device = fu_device_new(NULL); g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)); g_assert_cmpstr(fu_device_get_update_error(device), ==, NULL); /* first one */ fu_device_inhibit(device, "needs-activation", "Device is pending activation"); g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)); g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_cmpstr(fu_device_get_update_error(device), ==, "Device is pending activation"); /* activated, but still not updatable */ fu_device_uninhibit(device, "needs-activation"); g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)); g_assert_cmpstr(fu_device_get_update_error(device), ==, NULL); } #define TEST_FLAG_FOO (1 << 0) #define TEST_FLAG_BAR (1 << 1) #define TEST_FLAG_BAZ (1 << 2) static void fu_device_private_flags_func(void) { g_autofree gchar *tmp = NULL; g_autoptr(FuDevice) device = fu_device_new(NULL); fu_device_register_private_flag(device, TEST_FLAG_FOO, "foo"); fu_device_register_private_flag(device, TEST_FLAG_BAR, "bar"); fu_device_set_custom_flags(device, "foo"); g_assert_cmpint(fu_device_get_private_flags(device), ==, TEST_FLAG_FOO); fu_device_set_custom_flags(device, "bar"); g_assert_cmpint(fu_device_get_private_flags(device), ==, TEST_FLAG_FOO | TEST_FLAG_BAR); fu_device_set_custom_flags(device, "~bar"); g_assert_cmpint(fu_device_get_private_flags(device), ==, TEST_FLAG_FOO); fu_device_set_custom_flags(device, "baz"); g_assert_cmpint(fu_device_get_private_flags(device), ==, TEST_FLAG_FOO); tmp = fu_device_to_string(device); g_assert_cmpstr(tmp, ==, "FuDevice:\n" " Flags: none\n" " AcquiesceDelay: 50\n" " CustomFlags: baz\n" " PrivateFlags: foo\n"); } static void fu_device_flags_func(void) { g_autoptr(FuDevice) device = fu_device_new(NULL); g_autoptr(FuDevice) proxy = fu_device_new(NULL); /* bitfield */ for (guint64 i = 1; i < FU_DEVICE_INTERNAL_FLAG_UNKNOWN; i *= 2) { const gchar *tmp = fu_device_internal_flag_to_string(i); if (tmp == NULL) break; g_assert_cmpint(fu_device_internal_flag_from_string(tmp), ==, i); } g_assert_cmpint(fu_device_get_flags(device), ==, FWUPD_DEVICE_FLAG_NONE); /* remove IS_BOOTLOADER if is a BOOTLOADER */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); g_assert_cmpint(fu_device_get_flags(device), ==, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER); /* check implication */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); g_assert_cmpint(fu_device_get_flags(device), ==, FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE | FWUPD_DEVICE_FLAG_CAN_VERIFY); fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_CAN_VERIFY | FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); /* negation */ fu_device_set_custom_flags(device, "is-bootloader,updatable"); g_assert_cmpint(fu_device_get_flags(device), ==, FWUPD_DEVICE_FLAG_IS_BOOTLOADER | FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_custom_flags(device, "~is-bootloader"); g_assert_cmpint(fu_device_get_flags(device), ==, FWUPD_DEVICE_FLAG_UPDATABLE); /* setting flags on the proxy should propagate to the device that *uses* the proxy */ fu_device_set_proxy(device, proxy); fu_device_add_flag(proxy, FWUPD_DEVICE_FLAG_EMULATED); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)); } static void fu_device_children_func(void) { gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuDevice) child = fu_device_new(NULL); g_autoptr(FuDevice) parent = fu_device_new(ctx); g_autoptr(GError) error = NULL; /* do not save silo */ ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); fu_device_set_physical_id(child, "dummy"); fu_device_set_physical_id(parent, "dummy"); /* set up family */ fu_device_add_child(parent, child); /* set an instance ID that will be converted to a GUID when the parent * calls ->setup */ fu_device_add_instance_id(child, "foo"); g_assert_false(fu_device_has_guid(child, "b84ed8ed-a7b1-502f-83f6-90132e68adef")); /* setup parent, which also calls setup on child too (and thus also * converts the instance ID to a GUID) */ ret = fu_device_setup(parent, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_true(fu_device_has_guid(child, "b84ed8ed-a7b1-502f-83f6-90132e68adef")); } static void fu_device_parent_func(void) { g_autoptr(FuDevice) child = fu_device_new(NULL); g_autoptr(FuDevice) child_root = NULL; g_autoptr(FuDevice) grandparent = fu_device_new(NULL); g_autoptr(FuDevice) grandparent_root = NULL; g_autoptr(FuDevice) parent = fu_device_new(NULL); g_autoptr(FuDevice) parent_root = NULL; fu_device_set_physical_id(child, "dummy"); fu_device_set_physical_id(grandparent, "dummy"); fu_device_set_physical_id(parent, "dummy"); /* set up three layer family */ fu_device_add_child(grandparent, parent); fu_device_add_child(parent, child); /* check parents */ g_assert_true(fu_device_get_parent(child) == parent); g_assert_true(fu_device_get_parent(parent) == grandparent); g_assert_true(fu_device_get_parent(grandparent) == NULL); /* check root */ child_root = fu_device_get_root(child); g_assert_true(child_root == grandparent); parent_root = fu_device_get_root(parent); g_assert_true(parent_root == grandparent); grandparent_root = fu_device_get_root(child); g_assert_true(grandparent_root == grandparent); } static void fu_device_incorporate_func(void) { gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuDevice) device = fu_device_new(ctx); g_autoptr(FuDevice) donor = fu_device_new(ctx); g_autoptr(GError) error = NULL; /* load quirks */ ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); /* set up donor device */ fu_device_set_alternate_id(donor, "alt-id"); fu_device_set_equivalent_id(donor, "equiv-id"); fu_device_set_metadata(donor, "test", "me"); fu_device_set_metadata(donor, "test2", "me"); fu_device_add_instance_str(donor, "VID", "0A5C"); fu_device_add_instance_str(donor, "PID", "6412"); /* match a quirk entry, and then clear to ensure encorporate uses the quirk instance ID */ ret = fu_device_build_instance_id_full(donor, FU_DEVICE_INSTANCE_FLAG_QUIRKS, &error, "USB", "VID", "PID", NULL); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpstr(fu_device_get_custom_flags(donor), ==, "ignore-runtime"); fu_device_set_custom_flags(donor, "SHOULD_BE_REPLACED_WITH_QUIRK_VALUE"); /* base properties */ fu_device_add_flag(donor, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_set_created(donor, 123); fu_device_set_modified(donor, 456); fu_device_add_icon(donor, "computer"); /* existing properties */ fu_device_set_equivalent_id(device, "DO_NOT_OVERWRITE"); fu_device_set_metadata(device, "test2", "DO_NOT_OVERWRITE"); fu_device_set_modified(device, 789); /* incorporate properties from donor to device */ fu_device_incorporate(device, donor); g_assert_cmpstr(fu_device_get_alternate_id(device), ==, "alt-id"); g_assert_cmpstr(fu_device_get_equivalent_id(device), ==, "DO_NOT_OVERWRITE"); g_assert_cmpstr(fu_device_get_metadata(device, "test"), ==, "me"); g_assert_cmpstr(fu_device_get_metadata(device, "test2"), ==, "DO_NOT_OVERWRITE"); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REQUIRE_AC)); g_assert_cmpint(fu_device_get_created(device), ==, 123); g_assert_cmpint(fu_device_get_modified(device), ==, 789); g_assert_cmpint(fu_device_get_icons(device)->len, ==, 1); ret = fu_device_build_instance_id(device, &error, "USB", "VID", NULL); g_assert_no_error(error); g_assert_true(ret); g_assert_true(fu_device_has_instance_id(device, "USB\\VID_0A5C")); g_assert_cmpstr(fu_device_get_custom_flags(device), ==, "ignore-runtime"); } static void fu_backend_func(void) { FuDevice *dev; gboolean ret; g_autoptr(FuBackend) backend = g_object_new(FU_TYPE_BACKEND, NULL); g_autoptr(FuDevice) dev1 = fu_device_new(NULL); g_autoptr(FuDevice) dev2 = fu_device_new(NULL); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; /* defaults */ g_assert_null(fu_backend_get_name(backend)); g_assert_true(fu_backend_get_enabled(backend)); /* load */ ret = fu_backend_setup(backend, progress, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_backend_coldplug(backend, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* add two devices, then remove one of them */ fu_device_set_physical_id(dev1, "dev1"); fu_backend_device_added(backend, dev1); fu_device_set_physical_id(dev2, "dev2"); fu_backend_device_added(backend, dev2); fu_backend_device_changed(backend, dev2); fu_backend_device_removed(backend, dev2); dev = fu_backend_lookup_by_id(backend, "dev1"); g_assert_nonnull(dev); g_assert_true(dev == dev1); /* should have been removed */ dev = fu_backend_lookup_by_id(backend, "dev2"); g_assert_null(dev); /* get linear array */ devices = fu_backend_get_devices(backend); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 1); dev = g_ptr_array_index(devices, 0); g_assert_nonnull(dev); g_assert_true(dev == dev1); } static void fu_chunk_array_func(void) { g_autoptr(FuChunk) chk1 = NULL; g_autoptr(FuChunk) chk2 = NULL; g_autoptr(FuChunk) chk3 = NULL; g_autoptr(FuChunk) chk4 = NULL; g_autoptr(GBytes) fw = g_bytes_new_static("hello world", 11); g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(fw, 100, 5); g_assert_cmpint(fu_chunk_array_length(chunks), ==, 3); chk1 = fu_chunk_array_index(chunks, 0); g_assert_nonnull(chk1); g_assert_cmpint(fu_chunk_get_idx(chk1), ==, 0x0); g_assert_cmpint(fu_chunk_get_address(chk1), ==, 100); g_assert_cmpint(fu_chunk_get_data_sz(chk1), ==, 0x5); g_assert_cmpint(strncmp((const gchar *)fu_chunk_get_data(chk1), "hello", 5), ==, 0); chk2 = fu_chunk_array_index(chunks, 1); g_assert_nonnull(chk2); g_assert_cmpint(fu_chunk_get_idx(chk2), ==, 0x1); g_assert_cmpint(fu_chunk_get_address(chk2), ==, 105); g_assert_cmpint(fu_chunk_get_data_sz(chk2), ==, 0x5); g_assert_cmpint(strncmp((const gchar *)fu_chunk_get_data(chk2), " world", 5), ==, 0); chk3 = fu_chunk_array_index(chunks, 2); g_assert_nonnull(chk3); g_assert_cmpint(fu_chunk_get_idx(chk3), ==, 0x2); g_assert_cmpint(fu_chunk_get_address(chk3), ==, 110); g_assert_cmpint(fu_chunk_get_data_sz(chk3), ==, 0x1); g_assert_cmpint(strncmp((const gchar *)fu_chunk_get_data(chk3), "d", 1), ==, 0); chk4 = fu_chunk_array_index(chunks, 3); g_assert_null(chk4); chk4 = fu_chunk_array_index(chunks, 1024); g_assert_null(chk4); } static void fu_chunk_func(void) { g_autofree gchar *chunked1_str = NULL; g_autofree gchar *chunked2_str = NULL; g_autofree gchar *chunked3_str = NULL; g_autofree gchar *chunked4_str = NULL; g_autofree gchar *chunked5_str = NULL; g_autoptr(GPtrArray) chunked1 = NULL; g_autoptr(GPtrArray) chunked2 = NULL; g_autoptr(GPtrArray) chunked3 = NULL; g_autoptr(GPtrArray) chunked4 = NULL; g_autoptr(GPtrArray) chunked5 = NULL; chunked3 = fu_chunk_array_new((const guint8 *)"123456", 6, 0x0, 3, 3); chunked3_str = fu_chunk_array_to_string(chunked3); g_assert_cmpstr(chunked3_str, ==, "\n" " \n" " 123\n" " \n" " \n" " 0x1\n" " 0x1\n" " 456\n" " \n" "\n"); chunked4 = fu_chunk_array_new((const guint8 *)"123456", 6, 0x4, 4, 4); chunked4_str = fu_chunk_array_to_string(chunked4); g_assert_cmpstr(chunked4_str, ==, "\n" " \n" " 0x1\n" " 1234\n" " \n" " \n" " 0x1\n" " 0x2\n" " 56\n" " \n" "\n"); chunked5 = fu_chunk_array_new(NULL, 0, 0x0, 0x0, 4); g_assert_cmpint(chunked5->len, ==, 0); chunked5_str = fu_chunk_array_to_string(chunked5); g_assert_cmpstr(chunked5_str, ==, "\n\n"); chunked1 = fu_chunk_array_new((const guint8 *)"0123456789abcdef", 16, 0x0, 10, 4); chunked1_str = fu_chunk_array_to_string(chunked1); g_assert_cmpstr(chunked1_str, ==, "\n" " \n" " 0123\n" " \n" " \n" " 0x1\n" " 0x4\n" " 4567\n" " \n" " \n" " 0x2\n" " 0x8\n" " 89\n" " \n" " \n" " 0x3\n" " 0x1\n" " abcd\n" " \n" " \n" " 0x4\n" " 0x1\n" " 0x4\n" " ef\n" " \n" "\n"); chunked2 = fu_chunk_array_new((const guint8 *)"XXXXXXYYYYYYZZZZZZ", 18, 0x0, 6, 4); chunked2_str = fu_chunk_array_to_string(chunked2); g_print("\n%s", chunked2_str); g_assert_cmpstr(chunked2_str, ==, "\n" " \n" " XXXX\n" " \n" " \n" " 0x1\n" " 0x4\n" " XX\n" " \n" " \n" " 0x2\n" " 0x1\n" " YYYY\n" " \n" " \n" " 0x3\n" " 0x1\n" " 0x4\n" " YY\n" " \n" " \n" " 0x4\n" " 0x2\n" " ZZZZ\n" " \n" " \n" " 0x5\n" " 0x2\n" " 0x4\n" " ZZ\n" " \n" "\n"); } static void fu_strstrip_func(void) { struct { const gchar *old; const gchar *new; } map[] = {{"same", "same"}, {" leading", "leading"}, {"tailing ", "tailing"}, {" b ", "b"}, {" ", ""}, {NULL, NULL}}; for (guint i = 0; map[i].old != NULL; i++) { g_autofree gchar *tmp = fu_strstrip(map[i].old); g_assert_cmpstr(tmp, ==, map[i].new); } } static void fu_version_semver_func(void) { struct { const gchar *old; const gchar *new; FwupdVersionFormat fmt; } map[] = {{"1.2.3", "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET}, {"1.2.3.4", "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET}, {"1.2", "0.1.2", FWUPD_VERSION_FORMAT_TRIPLET}, {"1", "0.0.1", FWUPD_VERSION_FORMAT_TRIPLET}, {"CBET1.2.3", "1.2.3", FWUPD_VERSION_FORMAT_TRIPLET}, {"4.11-1190-g12d8072e6b-dirty", "4.11.1190", FWUPD_VERSION_FORMAT_TRIPLET}, {"4.11-1190-g12d8072e6b-dirty", "4.11", FWUPD_VERSION_FORMAT_PAIR}, {NULL, NULL}}; for (guint i = 0; map[i].old != NULL; i++) { g_autofree gchar *tmp = fu_version_ensure_semver(map[i].old, map[i].fmt); g_assert_cmpstr(tmp, ==, map[i].new); } } static void fu_strtoull_func(void) { gboolean ret; guint64 val = 0; g_autoptr(GError) error = NULL; ret = fu_strtoull("123", &val, 123, 200, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(val, ==, 123); ret = fu_strtoull("123\n", &val, 0, 200, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(val, ==, 123); ret = fu_strtoull("0x123", &val, 0, 0x123, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(val, ==, 0x123); ret = fu_strtoull(NULL, &val, 0, G_MAXUINT32, NULL); g_assert_false(ret); ret = fu_strtoull("", &val, 120, 123, NULL); g_assert_false(ret); ret = fu_strtoull("124", &val, 120, 123, NULL); g_assert_false(ret); ret = fu_strtoull("119", &val, 120, 123, NULL); g_assert_false(ret); } static void fu_strtoll_func(void) { gboolean ret; gint64 val = 0; g_autoptr(GError) error = NULL; ret = fu_strtoll("123", &val, 123, 200, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(val, ==, 123); ret = fu_strtoll("-123\n", &val, -123, 200, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(val, ==, -123); ret = fu_strtoll("0x123", &val, 0, 0x123, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(val, ==, 0x123); ret = fu_strtoll(NULL, &val, 0, G_MAXINT32, NULL); g_assert_false(ret); ret = fu_strtoll("", &val, 120, 123, NULL); g_assert_false(ret); ret = fu_strtoll("124", &val, 120, 123, NULL); g_assert_false(ret); ret = fu_strtoll("-124", &val, -123, 123, NULL); g_assert_false(ret); } static void fu_common_version_func(void) { guint i; struct { guint32 val; const gchar *ver; FwupdVersionFormat flags; } version_from_uint32[] = { {0x0, "0.0.0.0", FWUPD_VERSION_FORMAT_QUAD}, {0xff, "0.0.0.255", FWUPD_VERSION_FORMAT_QUAD}, {0xff01, "0.0.255.1", FWUPD_VERSION_FORMAT_QUAD}, {0xff0001, "0.255.0.1", FWUPD_VERSION_FORMAT_QUAD}, {0xff000100, "255.0.1.0", FWUPD_VERSION_FORMAT_QUAD}, {0x0, "0.0.0", FWUPD_VERSION_FORMAT_TRIPLET}, {0xff, "0.0.255", FWUPD_VERSION_FORMAT_TRIPLET}, {0xff01, "0.0.65281", FWUPD_VERSION_FORMAT_TRIPLET}, {0xff0001, "0.255.1", FWUPD_VERSION_FORMAT_TRIPLET}, {0xff000100, "255.0.256", FWUPD_VERSION_FORMAT_TRIPLET}, {0x0, "0", FWUPD_VERSION_FORMAT_NUMBER}, {0xff000100, "4278190336", FWUPD_VERSION_FORMAT_NUMBER}, {0x0, "11.0.0.0", FWUPD_VERSION_FORMAT_INTEL_ME}, {0xffffffff, "18.31.255.65535", FWUPD_VERSION_FORMAT_INTEL_ME}, {0x0b32057a, "11.11.50.1402", FWUPD_VERSION_FORMAT_INTEL_ME}, {0xb8320d84, "11.8.50.3460", FWUPD_VERSION_FORMAT_INTEL_ME2}, {0x226a4b00, "137.2706.768", FWUPD_VERSION_FORMAT_SURFACE_LEGACY}, {0x6001988, "6.25.136", FWUPD_VERSION_FORMAT_SURFACE}, {0x00ff0001, "255.0.1", FWUPD_VERSION_FORMAT_DELL_BIOS}, {0xc8, "0x000000c8", FWUPD_VERSION_FORMAT_HEX}, }; struct { guint32 val; const gchar *ver; FwupdVersionFormat flags; } version_from_uint24[] = { {0x0, "0.0.0", FWUPD_VERSION_FORMAT_TRIPLET}, {0xff, "0.0.255", FWUPD_VERSION_FORMAT_TRIPLET}, {0x0, "0", FWUPD_VERSION_FORMAT_NUMBER}, {0xc8, "0x0000c8", FWUPD_VERSION_FORMAT_HEX}, }; struct { guint64 val; const gchar *ver; FwupdVersionFormat flags; } version_from_uint64[] = { {0x0, "0.0.0.0", FWUPD_VERSION_FORMAT_QUAD}, {0xff, "0.0.0.255", FWUPD_VERSION_FORMAT_QUAD}, {0xffffffffffffffff, "65535.65535.65535.65535", FWUPD_VERSION_FORMAT_QUAD}, {0xff, "0.255", FWUPD_VERSION_FORMAT_PAIR}, {0xffffffffffffffff, "4294967295.4294967295", FWUPD_VERSION_FORMAT_PAIR}, {0x0, "0", FWUPD_VERSION_FORMAT_NUMBER}, {0x11000000c8, "0x00000011000000c8", FWUPD_VERSION_FORMAT_HEX}, }; struct { guint16 val; const gchar *ver; FwupdVersionFormat flags; } version_from_uint16[] = { {0x0, "0.0", FWUPD_VERSION_FORMAT_PAIR}, {0xff, "0.255", FWUPD_VERSION_FORMAT_PAIR}, {0xff01, "255.1", FWUPD_VERSION_FORMAT_PAIR}, {0x0, "0.0", FWUPD_VERSION_FORMAT_BCD}, {0x0110, "1.10", FWUPD_VERSION_FORMAT_BCD}, {0x9999, "99.99", FWUPD_VERSION_FORMAT_BCD}, {0x0, "0", FWUPD_VERSION_FORMAT_NUMBER}, {0x1234, "4660", FWUPD_VERSION_FORMAT_NUMBER}, {0x1234, "1.2.52", FWUPD_VERSION_FORMAT_TRIPLET}, }; struct { const gchar *old; const gchar *new; } version_parse[] = { {"0", "0"}, {"0x1a", "0.0.26"}, {"257", "0.0.257"}, {"1.2.3", "1.2.3"}, {"0xff0001", "0.255.1"}, {"16711681", "0.255.1"}, {"20150915", "20150915"}, {"dave", "dave"}, {"0x1x", "0x1x"}, }; /* check version conversion */ for (i = 0; i < G_N_ELEMENTS(version_from_uint64); i++) { g_autofree gchar *ver = NULL; ver = fu_version_from_uint64(version_from_uint64[i].val, version_from_uint64[i].flags); g_assert_cmpstr(ver, ==, version_from_uint64[i].ver); } for (i = 0; i < G_N_ELEMENTS(version_from_uint32); i++) { g_autofree gchar *ver = NULL; ver = fu_version_from_uint32(version_from_uint32[i].val, version_from_uint32[i].flags); g_assert_cmpstr(ver, ==, version_from_uint32[i].ver); } for (i = 0; i < G_N_ELEMENTS(version_from_uint24); i++) { g_autofree gchar *ver = NULL; ver = fu_version_from_uint24(version_from_uint24[i].val, version_from_uint24[i].flags); g_assert_cmpstr(ver, ==, version_from_uint24[i].ver); } for (i = 0; i < G_N_ELEMENTS(version_from_uint16); i++) { g_autofree gchar *ver = NULL; ver = fu_version_from_uint16(version_from_uint16[i].val, version_from_uint16[i].flags); g_assert_cmpstr(ver, ==, version_from_uint16[i].ver); } /* check version parsing */ for (i = 0; i < G_N_ELEMENTS(version_parse); i++) { g_autofree gchar *ver = NULL; ver = fu_version_parse_from_format(version_parse[i].old, FWUPD_VERSION_FORMAT_TRIPLET); g_assert_cmpstr(ver, ==, version_parse[i].new); } } static void fu_common_vercmp_func(void) { /* same */ g_assert_cmpint(fu_version_compare("1.2.3", "1.2.3", FWUPD_VERSION_FORMAT_UNKNOWN), ==, 0); g_assert_cmpint( fu_version_compare("001.002.003", "001.002.003", FWUPD_VERSION_FORMAT_UNKNOWN), ==, 0); g_assert_cmpint(fu_version_compare("0x00000002", "0x2", FWUPD_VERSION_FORMAT_HEX), ==, 0); /* upgrade and downgrade */ g_assert_cmpint(fu_version_compare("1.2.3", "1.2.4", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint( fu_version_compare("001.002.000", "001.002.009", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_version_compare("1.2.3", "1.2.2", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); g_assert_cmpint( fu_version_compare("001.002.009", "001.002.000", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); /* unequal depth */ g_assert_cmpint(fu_version_compare("1.2.3", "1.2.3.1", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_version_compare("1.2.3.1", "1.2.4", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); /* mixed-alpha-numeric */ g_assert_cmpint(fu_version_compare("1.2.3a", "1.2.3a", FWUPD_VERSION_FORMAT_UNKNOWN), ==, 0); g_assert_cmpint(fu_version_compare("1.2.3a", "1.2.3b", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_version_compare("1.2.3b", "1.2.3a", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); /* alpha version append */ g_assert_cmpint(fu_version_compare("1.2.3", "1.2.3a", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_version_compare("1.2.3a", "1.2.3", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); /* alpha only */ g_assert_cmpint(fu_version_compare("alpha", "alpha", FWUPD_VERSION_FORMAT_UNKNOWN), ==, 0); g_assert_cmpint(fu_version_compare("alpha", "beta", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_version_compare("beta", "alpha", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); /* alpha-compare */ g_assert_cmpint(fu_version_compare("1.2a.3", "1.2a.3", FWUPD_VERSION_FORMAT_UNKNOWN), ==, 0); g_assert_cmpint(fu_version_compare("1.2a.3", "1.2b.3", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_version_compare("1.2b.3", "1.2a.3", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); /* tilde is all-powerful */ g_assert_cmpint(fu_version_compare("1.2.3~rc1", "1.2.3~rc1", FWUPD_VERSION_FORMAT_UNKNOWN), ==, 0); g_assert_cmpint(fu_version_compare("1.2.3~rc1", "1.2.3", FWUPD_VERSION_FORMAT_UNKNOWN), <, 0); g_assert_cmpint(fu_version_compare("1.2.3", "1.2.3~rc1", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); g_assert_cmpint(fu_version_compare("1.2.3~rc2", "1.2.3~rc1", FWUPD_VERSION_FORMAT_UNKNOWN), >, 0); /* invalid */ g_assert_cmpint(fu_version_compare("1", NULL, FWUPD_VERSION_FORMAT_UNKNOWN), ==, G_MAXINT); g_assert_cmpint(fu_version_compare(NULL, "1", FWUPD_VERSION_FORMAT_UNKNOWN), ==, G_MAXINT); g_assert_cmpint(fu_version_compare(NULL, NULL, FWUPD_VERSION_FORMAT_UNKNOWN), ==, G_MAXINT); } static void fu_firmware_raw_aligned_func(void) { gboolean ret; g_autoptr(FuFirmware) firmware1 = fu_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_firmware_new(); g_autoptr(GError) error = NULL; g_autoptr(GBytes) blob = g_bytes_new_static("hello", 5); /* no alignment */ ret = fu_firmware_parse(firmware1, blob, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); /* invalid alignment */ fu_firmware_set_alignment(firmware2, FU_FIRMWARE_ALIGNMENT_4K); ret = fu_firmware_parse(firmware2, blob, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_false(ret); } static void fu_firmware_ihex_func(void) { const guint8 *data; gboolean ret; gsize len; g_autofree gchar *filename_hex = NULL; g_autofree gchar *filename_ref = NULL; g_autofree gchar *str = NULL; g_autoptr(FuFirmware) firmware = fu_ihex_firmware_new(); g_autoptr(GBytes) data_file = NULL; g_autoptr(GBytes) data_fw = NULL; g_autoptr(GBytes) data_hex = NULL; g_autoptr(GBytes) data_ref = NULL; g_autoptr(GError) error = NULL; /* load a Intel hex32 file */ filename_hex = g_test_build_filename(G_TEST_DIST, "tests", "firmware.hex", NULL); data_file = fu_bytes_get_contents(filename_hex, &error); g_assert_no_error(error); g_assert_nonnull(data_file); ret = fu_firmware_parse(firmware, data_file, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); data_fw = fu_firmware_get_bytes(firmware, &error); g_assert_no_error(error); g_assert_nonnull(data_fw); g_assert_cmpint(g_bytes_get_size(data_fw), ==, 136); /* did we match the reference file? */ filename_ref = g_test_build_filename(G_TEST_DIST, "tests", "firmware.bin", NULL); data_ref = fu_bytes_get_contents(filename_ref, &error); g_assert_no_error(error); g_assert_nonnull(data_ref); ret = fu_bytes_compare(data_fw, data_ref, &error); g_assert_no_error(error); g_assert_true(ret); /* export a ihex file (which will be slightly different due to * non-continuous regions being expanded */ data_hex = fu_firmware_write(firmware, &error); g_assert_no_error(error); g_assert_nonnull(data_hex); data = g_bytes_get_data(data_hex, &len); str = g_strndup((const gchar *)data, len); g_assert_cmpstr(str, ==, ":104000003DEF20F000000000FACF01F0FBCF02F0FE\n" ":10401000E9CF03F0EACF04F0E1CF05F0E2CF06F0FC\n" ":10402000D9CF07F0DACF08F0F3CF09F0F4CF0AF0D8\n" ":10403000F6CF0BF0F7CF0CF0F8CF0DF0F5CF0EF078\n" ":104040000EC0F5FF0DC0F8FF0CC0F7FF0BC0F6FF68\n" ":104050000AC0F4FF09C0F3FF08C0DAFF07C0D9FFA8\n" ":1040600006C0E2FF05C0E1FF04C0EAFF03C0E9FFAC\n" ":1040700002C0FBFF01C0FAFF11003FEF20F000017A\n" ":0840800042EF20F03DEF20F0BB\n" ":00000001FF\n"); } static void fu_firmware_ihex_signed_func(void) { const guint8 *data; gboolean ret; gsize len; g_autofree gchar *filename_shex = NULL; g_autoptr(FuFirmware) firmware = fu_ihex_firmware_new(); g_autoptr(GBytes) data_file = NULL; g_autoptr(GBytes) data_fw = NULL; g_autoptr(GBytes) data_sig = NULL; g_autoptr(GError) error = NULL; /* load a signed Intel hex32 file */ filename_shex = g_test_build_filename(G_TEST_DIST, "tests", "firmware.shex", NULL); data_file = fu_bytes_get_contents(filename_shex, &error); g_assert_no_error(error); g_assert_nonnull(data_file); ret = fu_firmware_parse(firmware, data_file, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); data_fw = fu_firmware_get_bytes(firmware, &error); g_assert_no_error(error); g_assert_nonnull(data_fw); g_assert_cmpint(g_bytes_get_size(data_fw), ==, 136); /* get the signed image */ data_sig = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_SIGNATURE, &error); g_assert_no_error(error); g_assert_nonnull(data_sig); data = g_bytes_get_data(data_sig, &len); g_assert_cmpint(len, ==, 8); g_assert_nonnull(data); g_assert_cmpint(memcmp(data, "deadbeef", 8), ==, 0); } static void fu_firmware_ihex_offset_func(void) { const guint8 *data; gboolean ret; gsize len; g_autofree gchar *str = NULL; g_autoptr(FuFirmware) firmware = fu_ihex_firmware_new(); g_autoptr(FuFirmware) firmware_verify = fu_ihex_firmware_new(); g_autoptr(GBytes) data_bin = NULL; g_autoptr(GBytes) data_dummy = NULL; g_autoptr(GBytes) data_verify = NULL; g_autoptr(GError) error = NULL; /* add a 4 byte image in high memory */ data_dummy = g_bytes_new_static("foo", 4); fu_firmware_set_addr(firmware, 0x80000000); fu_firmware_set_bytes(firmware, data_dummy); data_bin = fu_firmware_write(firmware, &error); g_assert_no_error(error); g_assert_nonnull(data_bin); data = g_bytes_get_data(data_bin, &len); str = g_strndup((const gchar *)data, len); g_assert_cmpstr(str, ==, ":0200000480007A\n" ":04000000666F6F00B8\n" ":00000001FF\n"); /* check we can load it too */ ret = fu_firmware_parse(firmware_verify, data_bin, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_firmware_get_addr(firmware_verify), ==, 0x80000000); data_verify = fu_firmware_get_bytes(firmware_verify, &error); g_assert_no_error(error); g_assert_nonnull(data_verify); g_assert_cmpint(g_bytes_get_size(data_verify), ==, 0x4); } static void fu_firmware_srec_func(void) { gboolean ret; g_autofree gchar *filename_srec = NULL; g_autofree gchar *filename_ref = NULL; g_autoptr(FuFirmware) firmware = fu_srec_firmware_new(); g_autoptr(GBytes) data_ref = NULL; g_autoptr(GBytes) data_srec = NULL; g_autoptr(GBytes) data_bin = NULL; g_autoptr(GError) error = NULL; filename_srec = g_test_build_filename(G_TEST_DIST, "tests", "firmware.srec", NULL); data_srec = fu_bytes_get_contents(filename_srec, &error); g_assert_no_error(error); g_assert_nonnull(data_srec); ret = fu_firmware_parse(firmware, data_srec, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); data_bin = fu_firmware_get_bytes(firmware, &error); g_assert_no_error(error); g_assert_nonnull(data_bin); g_assert_cmpint(g_bytes_get_size(data_bin), ==, 136); /* did we match the reference file? */ filename_ref = g_test_build_filename(G_TEST_DIST, "tests", "firmware.bin", NULL); data_ref = fu_bytes_get_contents(filename_ref, &error); g_assert_no_error(error); g_assert_nonnull(data_ref); ret = fu_bytes_compare(data_bin, data_ref, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_firmware_fdt_func(void) { gboolean ret; guint32 val32 = 0; guint64 val64 = 0; g_autofree gchar *filename = NULL; g_autofree gchar *val = NULL; g_autofree gchar *str = NULL; g_autoptr(FuFirmware) firmware = fu_fdt_firmware_new(); g_autoptr(FuFirmware) img1 = NULL; g_autoptr(FuFdtImage) img2 = NULL; g_autoptr(GBytes) data = NULL; g_autoptr(GError) error = NULL; filename = g_test_build_filename(G_TEST_DIST, "tests", "fdt.bin", NULL); data = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(data); ret = fu_firmware_parse(firmware, data, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_fdt_firmware_get_cpuid(FU_FDT_FIRMWARE(firmware)), ==, 0x0); str = fu_firmware_to_string(firmware); g_debug("%s", str); img1 = fu_firmware_get_image_by_id(firmware, NULL, &error); g_assert_no_error(error); g_assert_nonnull(img1); ret = fu_fdt_image_get_attr_str(FU_FDT_IMAGE(img1), "key", &val, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpstr(val, ==, "hello world"); /* get image, and get the uint32 attr */ img2 = fu_fdt_firmware_get_image_by_path(FU_FDT_FIRMWARE(firmware), "/images/firmware-1", &error); g_assert_no_error(error); g_assert_nonnull(img2); ret = fu_fdt_image_get_attr_u32(FU_FDT_IMAGE(img2), "key", &val32, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(val32, ==, 0x123); /* wrong type */ ret = fu_fdt_image_get_attr_u64(img2, "key", &val64, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_false(ret); } static void fu_firmware_fit_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *str = NULL; g_auto(GStrv) val = NULL; g_autoptr(FuFdtImage) img1 = NULL; g_autoptr(FuFirmware) firmware = fu_fit_firmware_new(); g_autoptr(GBytes) data = NULL; g_autoptr(GError) error = NULL; filename = g_test_build_filename(G_TEST_DIST, "tests", "fit.bin", NULL); data = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(data); ret = fu_firmware_parse(firmware, data, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_fit_firmware_get_timestamp(FU_FIT_FIRMWARE(firmware)), ==, 0x629D4ABD); str = fu_firmware_to_string(firmware); g_debug("%s", str); img1 = fu_fdt_firmware_get_image_by_path(FU_FDT_FIRMWARE(firmware), "/configurations/conf-1", &error); g_assert_no_error(error); g_assert_nonnull(img1); ret = fu_fdt_image_get_attr_strlist(FU_FDT_IMAGE(img1), FU_FIT_FIRMWARE_ATTR_COMPATIBLE, &val, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_nonnull(val); g_assert_cmpstr(val[0], ==, "alice"); g_assert_cmpstr(val[1], ==, "bob"); g_assert_cmpstr(val[2], ==, "clara"); g_assert_cmpstr(val[3], ==, NULL); } static void fu_firmware_srec_tokenization_func(void) { FuSrecFirmwareRecord *rcd; GPtrArray *records; gboolean ret; g_autoptr(FuFirmware) firmware = fu_srec_firmware_new(); g_autoptr(GBytes) data_srec = NULL; g_autoptr(GError) error = NULL; const gchar *buf = "S3060000001400E5\r\n" "S31000000002281102000000007F0304002C\r\n" "S306000000145095\r\n" "S70500000000FA\r\n"; data_srec = g_bytes_new_static(buf, strlen(buf)); g_assert_no_error(error); g_assert_nonnull(data_srec); ret = fu_firmware_tokenize(firmware, data_srec, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); records = fu_srec_firmware_get_records(FU_SREC_FIRMWARE(firmware)); g_assert_nonnull(records); g_assert_cmpint(records->len, ==, 4); rcd = g_ptr_array_index(records, 2); g_assert_nonnull(rcd); g_assert_cmpint(rcd->ln, ==, 0x3); g_assert_cmpint(rcd->kind, ==, 3); g_assert_cmpint(rcd->addr, ==, 0x14); g_assert_cmpint(rcd->buf->len, ==, 0x1); g_assert_cmpint(rcd->buf->data[0], ==, 0x50); } static void fu_firmware_build_func(void) { gboolean ret; g_autofree gchar *str = NULL; g_autoptr(FuFirmware) firmware = fu_firmware_new(); g_autoptr(FuFirmware) img = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GBytes) blob2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *buf = "\n" "\n" " 1.2.3\n" " \n" " 4.5.6\n" " header\n" " 456\n" " 0x456\n" " aGVsbG8=\n" " \n" " \n" " 7.8.9\n" " header\n" " 789\n" " 0x789\n" " \n" "\n"; blob = g_bytes_new_static(buf, strlen(buf)); g_assert_no_error(error); g_assert_nonnull(blob); /* parse XML */ ret = xb_builder_source_load_bytes(source, blob, XB_BUILDER_SOURCE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); n = xb_silo_query_first(silo, "firmware", &error); g_assert_no_error(error); g_assert_nonnull(n); /* build object */ ret = fu_firmware_build(firmware, n, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpstr(fu_firmware_get_version(firmware), ==, "1.2.3"); /* verify image */ img = fu_firmware_get_image_by_id(firmware, "header", &error); g_assert_no_error(error); g_assert_nonnull(img); g_assert_cmpstr(fu_firmware_get_version(img), ==, "4.5.6"); g_assert_cmpint(fu_firmware_get_idx(img), ==, 456); g_assert_cmpint(fu_firmware_get_addr(img), ==, 0x456); blob2 = fu_firmware_write(img, &error); g_assert_no_error(error); g_assert_nonnull(blob2); g_assert_cmpint(g_bytes_get_size(blob2), ==, 5); str = g_strndup(g_bytes_get_data(blob2, NULL), g_bytes_get_size(blob2)); g_assert_cmpstr(str, ==, "hello"); } static gsize fu_firmware_dfuse_image_get_size(FuFirmware *self) { g_autoptr(GPtrArray) chunks = fu_firmware_get_chunks(self, NULL); gsize length = 0; for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); length += fu_chunk_get_data_sz(chk); } return length; } static gsize fu_firmware_dfuse_get_size(FuFirmware *firmware) { gsize length = 0; g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); for (guint i = 0; i < images->len; i++) { FuFirmware *image = g_ptr_array_index(images, i); length += fu_firmware_dfuse_image_get_size(image); } return length; } static void fu_firmware_dfuse_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuFirmware) firmware = fu_dfuse_firmware_new(); g_autoptr(GBytes) roundtrip_orig = NULL; g_autoptr(GBytes) roundtrip = NULL; g_autoptr(GError) error = NULL; /* load a DfuSe firmware */ filename = g_test_build_filename(G_TEST_DIST, "tests", "firmware.dfuse", NULL); g_assert_nonnull(filename); roundtrip_orig = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(roundtrip_orig); ret = fu_firmware_parse(firmware, roundtrip_orig, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_dfu_firmware_get_vid(FU_DFU_FIRMWARE(firmware)), ==, 0x1234); g_assert_cmpint(fu_dfu_firmware_get_pid(FU_DFU_FIRMWARE(firmware)), ==, 0x5678); g_assert_cmpint(fu_dfu_firmware_get_release(FU_DFU_FIRMWARE(firmware)), ==, 0x8642); g_assert_cmpint(fu_firmware_dfuse_get_size(firmware), ==, 0x21); /* can we roundtrip without losing data */ roundtrip = fu_firmware_write(firmware, &error); g_assert_no_error(error); g_assert_nonnull(roundtrip); ret = fu_bytes_compare(roundtrip, roundtrip_orig, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_firmware_fmap_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *img_str = NULL; g_autoptr(FuFirmware) firmware = fu_fmap_firmware_new(); g_autoptr(FuFirmware) img = NULL; g_autoptr(GBytes) img_blob = NULL; g_autoptr(GBytes) roundtrip = NULL; g_autoptr(GBytes) roundtrip_orig = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) images = NULL; #ifndef HAVE_MEMMEM g_test_skip("no memmem()"); return; #endif /* load firmware */ filename = g_test_build_filename(G_TEST_DIST, "tests", "fmap-offset.bin", NULL); g_assert_nonnull(filename); roundtrip_orig = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(roundtrip_orig); ret = fu_firmware_parse(firmware, roundtrip_orig, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); /* check image count */ images = fu_firmware_get_images(firmware); g_assert_cmpint(images->len, ==, 2); /* get a specific image */ img = fu_firmware_get_image_by_id(firmware, "FMAP", &error); g_assert_no_error(error); g_assert_nonnull(img); img_blob = fu_firmware_get_bytes(img, &error); g_assert_no_error(error); g_assert_nonnull(img_blob); g_assert_cmpint(g_bytes_get_size(img_blob), ==, 0xb); img_str = g_strndup(g_bytes_get_data(img_blob, NULL), g_bytes_get_size(img_blob)); g_assert_cmpstr(img_str, ==, "hello world"); /* can we roundtrip without losing data */ roundtrip = fu_firmware_write(firmware, &error); g_assert_no_error(error); g_assert_nonnull(roundtrip); ret = fu_bytes_compare(roundtrip, roundtrip_orig, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_firmware_new_from_gtypes_func(void) { g_autofree gchar *fn = NULL; g_autoptr(FuFirmware) firmware1 = NULL; g_autoptr(FuFirmware) firmware2 = NULL; g_autoptr(FuFirmware) firmware3 = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; fn = g_test_build_filename(G_TEST_DIST, "tests", "firmware.dfu", NULL); blob = fu_bytes_get_contents(fn, &error); g_assert_no_error(error); g_assert_nonnull(blob); /* dfu -> FuDfuFirmware */ firmware1 = fu_firmware_new_from_gtypes(blob, 0x0, FWUPD_INSTALL_FLAG_NONE, &error, FU_TYPE_SREC_FIRMWARE, FU_TYPE_DFUSE_FIRMWARE, FU_TYPE_DFU_FIRMWARE, G_TYPE_INVALID); g_assert_no_error(error); g_assert_nonnull(firmware1); g_assert_cmpstr(G_OBJECT_TYPE_NAME(firmware1), ==, "FuDfuFirmware"); /* dfu -> FuFirmware */ firmware2 = fu_firmware_new_from_gtypes(blob, 0x0, FWUPD_INSTALL_FLAG_NONE, &error, FU_TYPE_SREC_FIRMWARE, FU_TYPE_FIRMWARE, G_TYPE_INVALID); g_assert_no_error(error); g_assert_nonnull(firmware2); g_assert_cmpstr(G_OBJECT_TYPE_NAME(firmware2), ==, "FuFirmware"); /* dfu -> error */ firmware3 = fu_firmware_new_from_gtypes(blob, 0x0, FWUPD_INSTALL_FLAG_NONE, &error, FU_TYPE_SREC_FIRMWARE, G_TYPE_INVALID); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null(firmware3); } static void fu_firmware_csv_func(void) { FuCsvEntry *entry_tmp; gboolean ret; g_autofree gchar *str = NULL; g_autoptr(FuFirmware) firmware = fu_csv_firmware_new(); g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) imgs = NULL; const gchar *data = "sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md\n" "grub,1,Free Software Foundation,grub,2.04,https://www.gnu.org/software/grub/\n"; fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(firmware), "$id"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(firmware), "component_generation"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(firmware), "vendor_name"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(firmware), "vendor_package_name"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(firmware), "vendor_version"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(firmware), "vendor_url"); g_assert_cmpstr(fu_csv_firmware_get_column_id(FU_CSV_FIRMWARE(firmware), 0), ==, "$id"); g_assert_cmpstr(fu_csv_firmware_get_column_id(FU_CSV_FIRMWARE(firmware), 1), ==, "component_generation"); g_assert_cmpstr(fu_csv_firmware_get_column_id(FU_CSV_FIRMWARE(firmware), 5), ==, "vendor_url"); g_assert_cmpstr(fu_csv_firmware_get_column_id(FU_CSV_FIRMWARE(firmware), 6), ==, NULL); blob = g_bytes_new(data, strlen(data)); ret = fu_firmware_parse(firmware, blob, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); str = fu_firmware_to_string(firmware); g_debug("%s", str); imgs = fu_firmware_get_images(firmware); g_assert_cmpint(imgs->len, ==, 2); entry_tmp = g_ptr_array_index(imgs, 1); g_assert_cmpstr(fu_firmware_get_id(FU_FIRMWARE(entry_tmp)), ==, "grub"); g_assert_cmpstr(fu_csv_entry_get_value_by_idx(entry_tmp, 0), ==, NULL); g_assert_cmpstr(fu_csv_entry_get_value_by_idx(entry_tmp, 1), ==, "1"); g_assert_cmpstr(fu_csv_entry_get_value_by_column_id(entry_tmp, "vendor_version"), ==, "2.04"); } static void fu_firmware_archive_func(void) { gboolean ret; g_autofree gchar *fn = NULL; g_autoptr(FuFirmware) firmware = fu_archive_firmware_new(); g_autoptr(FuFirmware) img_asc = NULL; g_autoptr(FuFirmware) img_bin = NULL; g_autoptr(FuFirmware) img_both = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; #ifndef HAVE_LIBARCHIVE g_test_skip("no libarchive support"); return; #endif fn = g_test_build_filename(G_TEST_BUILT, "tests", "firmware.zip", NULL); file = g_file_new_for_path(fn); ret = fu_firmware_parse_file(firmware, file, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_archive_firmware_get_format(FU_ARCHIVE_FIRMWARE(firmware)), ==, FU_ARCHIVE_FORMAT_UNKNOWN); g_assert_cmpint(fu_archive_firmware_get_compression(FU_ARCHIVE_FIRMWARE(firmware)), ==, FU_ARCHIVE_COMPRESSION_UNKNOWN); img_bin = fu_archive_firmware_get_image_fnmatch(FU_ARCHIVE_FIRMWARE(firmware), "*.bin", &error); g_assert_no_error(error); g_assert_nonnull(img_bin); img_asc = fu_archive_firmware_get_image_fnmatch(FU_ARCHIVE_FIRMWARE(firmware), "*.bin.asc", &error); g_assert_no_error(error); g_assert_nonnull(img_asc); img_both = fu_archive_firmware_get_image_fnmatch(FU_ARCHIVE_FIRMWARE(firmware), "*.bin*", &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); g_assert_null(img_both); } static void fu_firmware_linear_func(void) { gboolean ret; g_autoptr(FuFirmware) firmware1 = fu_linear_firmware_new(FU_TYPE_OPROM_FIRMWARE); g_autoptr(FuFirmware) firmware2 = fu_linear_firmware_new(FU_TYPE_OPROM_FIRMWARE); g_autoptr(GBytes) blob1 = g_bytes_new_static("XXXX", 4); g_autoptr(GBytes) blob2 = g_bytes_new_static("HELO", 4); g_autoptr(GBytes) blob3 = NULL; g_autoptr(FuFirmware) img1 = fu_oprom_firmware_new(); g_autoptr(FuFirmware) img2 = fu_oprom_firmware_new(); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) imgs = NULL; g_autofree gchar *str = NULL; /* add images then parse */ fu_firmware_set_bytes(img1, blob1); fu_firmware_add_image(firmware1, img1); fu_firmware_set_bytes(img2, blob2); fu_firmware_add_image(firmware1, img2); blob3 = fu_firmware_write(firmware1, &error); g_assert_no_error(error); g_assert_nonnull(blob3); g_assert_cmpint(g_bytes_get_size(blob3), ==, 1024); /* parse them back */ ret = fu_firmware_parse(firmware2, blob3, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); str = fu_firmware_to_string(firmware2); g_debug("\n%s", str); /* verify we got both images */ imgs = fu_firmware_get_images(firmware2); g_assert_cmpint(imgs->len, ==, 2); } static void fu_firmware_dfu_func(void) { gboolean ret; g_autofree gchar *filename_dfu = NULL; g_autofree gchar *filename_ref = NULL; g_autoptr(FuFirmware) firmware = fu_dfu_firmware_new(); g_autoptr(GBytes) data_ref = NULL; g_autoptr(GBytes) data_dfu = NULL; g_autoptr(GBytes) data_bin = NULL; g_autoptr(GError) error = NULL; filename_dfu = g_test_build_filename(G_TEST_DIST, "tests", "firmware.dfu", NULL); data_dfu = fu_bytes_get_contents(filename_dfu, &error); g_assert_no_error(error); g_assert_nonnull(data_dfu); ret = fu_firmware_parse(firmware, data_dfu, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_dfu_firmware_get_vid(FU_DFU_FIRMWARE(firmware)), ==, 0x1234); g_assert_cmpint(fu_dfu_firmware_get_pid(FU_DFU_FIRMWARE(firmware)), ==, 0x4321); g_assert_cmpint(fu_dfu_firmware_get_release(FU_DFU_FIRMWARE(firmware)), ==, 0xdead); data_bin = fu_firmware_get_bytes(firmware, &error); g_assert_no_error(error); g_assert_nonnull(data_bin); g_assert_cmpint(g_bytes_get_size(data_bin), ==, 136); /* did we match the reference file? */ filename_ref = g_test_build_filename(G_TEST_DIST, "tests", "firmware.bin", NULL); data_ref = fu_bytes_get_contents(filename_ref, &error); g_assert_no_error(error); g_assert_nonnull(data_ref); ret = fu_bytes_compare(data_bin, data_ref, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_firmware_ifwi_cpd_func(void) { gboolean ret; g_autofree gchar *filename_ifwi_cpd = NULL; g_autoptr(FuFirmware) firmware = fu_ifwi_cpd_firmware_new(); g_autoptr(FuFirmware) img1 = NULL; g_autoptr(FuFirmware) img2 = NULL; g_autoptr(GBytes) data_bin = NULL; g_autoptr(GBytes) data_ifwi_cpd = NULL; g_autoptr(GError) error = NULL; filename_ifwi_cpd = g_test_build_filename(G_TEST_DIST, "tests", "ifwi-cpd.bin", NULL); data_ifwi_cpd = fu_bytes_get_contents(filename_ifwi_cpd, &error); g_assert_no_error(error); g_assert_nonnull(data_ifwi_cpd); ret = fu_firmware_parse(firmware, data_ifwi_cpd, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_firmware_get_idx(firmware), ==, 0x1234); data_bin = fu_firmware_write(firmware, &error); g_assert_no_error(error); g_assert_nonnull(data_bin); g_assert_cmpint(g_bytes_get_size(data_bin), ==, 90); img1 = fu_firmware_get_image_by_id(firmware, "one", &error); g_assert_no_error(error); g_assert_nonnull(img1); g_assert_cmpint(fu_firmware_get_offset(img1), ==, 68); g_assert_cmpint(fu_firmware_get_size(img1), ==, 11); img2 = fu_firmware_get_image_by_id(firmware, "two", &error); g_assert_no_error(error); g_assert_nonnull(img2); g_assert_cmpint(fu_firmware_get_offset(img2), ==, 79); g_assert_cmpint(fu_firmware_get_size(img2), ==, 11); } static void fu_firmware_ifwi_fpt_func(void) { gboolean ret; g_autofree gchar *filename_ifwi_fpt = NULL; g_autoptr(FuFirmware) firmware = fu_ifwi_fpt_firmware_new(); g_autoptr(FuFirmware) img1 = NULL; g_autoptr(FuFirmware) img2 = NULL; g_autoptr(GBytes) data_bin = NULL; g_autoptr(GBytes) data_ifwi_fpt = NULL; g_autoptr(GError) error = NULL; filename_ifwi_fpt = g_test_build_filename(G_TEST_DIST, "tests", "ifwi-fpt.bin", NULL); data_ifwi_fpt = fu_bytes_get_contents(filename_ifwi_fpt, &error); g_assert_no_error(error); g_assert_nonnull(data_ifwi_fpt); ret = fu_firmware_parse(firmware, data_ifwi_fpt, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); data_bin = fu_firmware_write(firmware, &error); g_assert_no_error(error); g_assert_nonnull(data_bin); g_assert_cmpint(g_bytes_get_size(data_bin), ==, 118); img1 = fu_firmware_get_image_by_idx(firmware, 0x4f464e49, &error); g_assert_no_error(error); g_assert_nonnull(img1); g_assert_cmpint(fu_firmware_get_offset(img1), ==, 96); g_assert_cmpint(fu_firmware_get_size(img1), ==, 11); img2 = fu_firmware_get_image_by_idx(firmware, 0x4d495746, &error); g_assert_no_error(error); g_assert_nonnull(img2); g_assert_cmpint(fu_firmware_get_offset(img2), ==, 107); g_assert_cmpint(fu_firmware_get_size(img2), ==, 11); } static void fu_firmware_oprom_func(void) { gboolean ret; g_autofree gchar *filename_oprom = NULL; g_autoptr(FuFirmware) firmware = fu_oprom_firmware_new(); g_autoptr(FuFirmware) img1 = NULL; g_autoptr(GBytes) data_bin = NULL; g_autoptr(GBytes) data_oprom = NULL; g_autoptr(GError) error = NULL; filename_oprom = g_test_build_filename(G_TEST_DIST, "tests", "oprom.bin", NULL); data_oprom = fu_bytes_get_contents(filename_oprom, &error); g_assert_no_error(error); g_assert_nonnull(data_oprom); ret = fu_firmware_parse(firmware, data_oprom, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_firmware_get_idx(firmware), ==, 0x1); data_bin = fu_firmware_write(firmware, &error); g_assert_no_error(error); g_assert_nonnull(data_bin); g_assert_cmpint(g_bytes_get_size(data_bin), ==, 1024); img1 = fu_firmware_get_image_by_id(firmware, "cpd", &error); g_assert_no_error(error); g_assert_nonnull(img1); g_assert_cmpint(fu_firmware_get_offset(img1), ==, 512); g_assert_cmpint(fu_firmware_get_size(img1), ==, 512); } static void fu_firmware_dfu_patch_func(void) { gboolean ret; g_autofree gchar *csum = NULL; g_autofree gchar *filename_dfu = NULL; g_autoptr(FuFirmware) firmware = fu_dfu_firmware_new(); g_autoptr(GBytes) data_dfu = NULL; g_autoptr(GBytes) data_new = NULL; g_autoptr(GBytes) data_patch0 = g_bytes_new_static("XXXX", 4); g_autoptr(GBytes) data_patch1 = g_bytes_new_static("HELO", 4); g_autoptr(GError) error = NULL; filename_dfu = g_test_build_filename(G_TEST_DIST, "tests", "firmware.dfu", NULL); data_dfu = fu_bytes_get_contents(filename_dfu, &error); g_assert_no_error(error); g_assert_nonnull(data_dfu); ret = fu_firmware_parse(firmware, data_dfu, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); /* add a couple of patches */ fu_firmware_add_patch(firmware, 0x0, data_patch0); fu_firmware_add_patch(firmware, 0x0, data_patch1); fu_firmware_add_patch(firmware, 136 - 4, data_patch1); data_new = fu_firmware_write(firmware, &error); g_assert_no_error(error); g_assert_nonnull(data_new); fu_dump_full(G_LOG_DOMAIN, "patch", g_bytes_get_data(data_new, NULL), g_bytes_get_size(data_new), 20, FU_DUMP_FLAGS_SHOW_ASCII | FU_DUMP_FLAGS_SHOW_ADDRESSES); csum = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, data_new); g_assert_cmpstr(csum, ==, "0722727426092ac564861d1a11697182017be83f"); } static void fu_hid_descriptor_container_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuFirmware) firmware = fu_hid_descriptor_new(); g_autoptr(FuFirmware) item_id = NULL; g_autoptr(FuHidReport) report = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error = NULL; filename = g_test_build_filename(G_TEST_DIST, "tests", "hid-descriptor2.bin", NULL); fw = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(fw); ret = fu_firmware_parse(firmware, fw, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); /* find report-id from usage */ report = fu_hid_descriptor_find_report(FU_HID_DESCRIPTOR(firmware), &error, "usage-page", 0xFF02, "usage", 0x01, "feature", 0x02, NULL); g_assert_no_error(error); g_assert_nonnull(report); item_id = fu_firmware_get_image_by_id(FU_FIRMWARE(report), "report-id", &error); g_assert_no_error(error); g_assert_nonnull(item_id); g_assert_cmpint(fu_hid_report_item_get_value(FU_HID_REPORT_ITEM(item_id)), ==, 0x09); } static void fu_hid_descriptor_func(void) { gboolean ret; g_autoptr(FuFirmware) firmware = fu_hid_descriptor_new(); g_autoptr(FuHidReport) report1 = NULL; g_autoptr(FuHidReport) report2 = NULL; g_autoptr(FuHidReport) report3 = NULL; g_autoptr(FuHidReport) report4 = NULL; g_autoptr(FuFirmware) item_usage = NULL; g_autoptr(FuFirmware) item_id = NULL; g_autoptr(GError) error = NULL; g_autofree gchar *filename = NULL; g_autoptr(GBytes) fw = NULL; filename = g_test_build_filename(G_TEST_DIST, "tests", "hid-descriptor.bin", NULL); fw = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(fw); ret = fu_firmware_parse(firmware, fw, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); /* find report-id from usage */ report4 = fu_hid_descriptor_find_report(FU_HID_DESCRIPTOR(firmware), &error, "usage", 0xC8, NULL); g_assert_no_error(error); g_assert_nonnull(report4); item_id = fu_firmware_get_image_by_id(FU_FIRMWARE(report4), "report-id", &error); g_assert_no_error(error); g_assert_nonnull(item_id); g_assert_cmpint(fu_hid_report_item_get_value(FU_HID_REPORT_ITEM(item_id)), ==, 0xF1); /* find usage from report-id */ report1 = fu_hid_descriptor_find_report(FU_HID_DESCRIPTOR(firmware), &error, "report-id", 0xF1, NULL); g_assert_no_error(error); g_assert_nonnull(report1); report2 = fu_hid_descriptor_find_report(FU_HID_DESCRIPTOR(firmware), &error, "usage-page", 0xFF0B, "report-id", 0xF1, NULL); g_assert_no_error(error); g_assert_nonnull(report2); item_usage = fu_firmware_get_image_by_id(FU_FIRMWARE(report2), "usage", &error); g_assert_no_error(error); g_assert_nonnull(item_usage); g_assert_cmpint(fu_hid_report_item_get_value(FU_HID_REPORT_ITEM(item_usage)), ==, 0xC8); /* not found */ report3 = fu_hid_descriptor_find_report(FU_HID_DESCRIPTOR(firmware), &error, "usage-page", 0x1234, "report-id", 0xF1, NULL); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_null(report3); } static void fu_firmware_func(void) { gboolean ret; g_autoptr(FuFirmware) firmware = fu_firmware_new(); g_autoptr(FuFirmware) img1 = fu_firmware_new(); g_autoptr(FuFirmware) img2 = fu_firmware_new(); g_autoptr(FuFirmware) img_id = NULL; g_autoptr(FuFirmware) img_idx = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) images = NULL; g_autofree gchar *str = NULL; fu_firmware_set_addr(img1, 0x200); fu_firmware_set_idx(img1, 13); fu_firmware_set_id(img1, "primary"); fu_firmware_set_filename(img1, "BIOS.bin"); fu_firmware_add_image(firmware, img1); fu_firmware_set_addr(img2, 0x400); fu_firmware_set_idx(img2, 23); fu_firmware_set_id(img2, "secondary"); fu_firmware_add_image(firmware, img2); img_id = fu_firmware_get_image_by_id(firmware, "NotGoingToExist", &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_null(img_id); g_clear_error(&error); img_id = fu_firmware_get_image_by_id(firmware, "primary", &error); g_assert_no_error(error); g_assert_nonnull(img_id); g_assert_cmpint(fu_firmware_get_addr(img_id), ==, 0x200); g_assert_cmpint(fu_firmware_get_idx(img_id), ==, 13); g_assert_cmpstr(fu_firmware_get_id(img_id), ==, "primary"); img_idx = fu_firmware_get_image_by_idx(firmware, 123456, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_null(img_idx); g_clear_error(&error); img_idx = fu_firmware_get_image_by_idx(firmware, 23, &error); g_assert_no_error(error); g_assert_nonnull(img_idx); g_assert_cmpint(fu_firmware_get_addr(img_idx), ==, 0x400); g_assert_cmpint(fu_firmware_get_idx(img_idx), ==, 23); g_assert_cmpstr(fu_firmware_get_id(img_idx), ==, "secondary"); str = fu_firmware_to_string(firmware); g_assert_cmpstr(str, ==, "\n" " \n" " primary\n" " 0xd\n" " 0x200\n" " BIOS.bin\n" " \n" " \n" " secondary\n" " 0x17\n" " 0x400\n" " \n" "\n"); ret = fu_firmware_remove_image_by_idx(firmware, 0xd, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_remove_image_by_id(firmware, "secondary", &error); g_assert_no_error(error); g_assert_true(ret); images = fu_firmware_get_images(firmware); g_assert_nonnull(images); g_assert_cmpint(images->len, ==, 0); ret = fu_firmware_remove_image_by_id(firmware, "NOTGOINGTOEXIST", &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_false(ret); } static void fu_firmware_common_func(void) { gboolean ret; guint8 value = 0; g_autoptr(GError) error = NULL; ret = fu_firmware_strparse_uint8_safe("ff00XX", 6, 0, &value, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(value, ==, 0xFF); ret = fu_firmware_strparse_uint8_safe("ff00XX", 6, 2, &value, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(value, ==, 0x00); ret = fu_firmware_strparse_uint8_safe("ff00XX", 6, 4, &value, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_false(ret); } static void fu_firmware_dedupe_func(void) { gboolean ret; g_autoptr(FuFirmware) firmware = fu_firmware_new(); g_autoptr(FuFirmware) img1 = fu_firmware_new(); g_autoptr(FuFirmware) img1_old = fu_firmware_new(); g_autoptr(FuFirmware) img2 = fu_firmware_new(); g_autoptr(FuFirmware) img2_old = fu_firmware_new(); g_autoptr(FuFirmware) img3 = fu_firmware_new(); g_autoptr(FuFirmware) img_id = NULL; g_autoptr(FuFirmware) img_idx = NULL; g_autoptr(GError) error = NULL; fu_firmware_add_flag(firmware, FU_FIRMWARE_FLAG_DEDUPE_ID); fu_firmware_add_flag(firmware, FU_FIRMWARE_FLAG_DEDUPE_IDX); fu_firmware_set_images_max(firmware, 2); fu_firmware_set_idx(img1_old, 13); fu_firmware_set_id(img1_old, "DAVE"); fu_firmware_add_image(firmware, img1_old); g_assert_true(fu_firmware_get_parent(img1_old) == firmware); fu_firmware_set_idx(img1, 13); fu_firmware_set_id(img1, "primary"); fu_firmware_add_image(firmware, img1); fu_firmware_set_idx(img2_old, 123456); fu_firmware_set_id(img2_old, "secondary"); fu_firmware_add_image(firmware, img2_old); fu_firmware_set_idx(img2, 23); fu_firmware_set_id(img2, "secondary"); fu_firmware_add_image(firmware, img2); img_id = fu_firmware_get_image_by_id(firmware, "primary", &error); g_assert_no_error(error); g_assert_nonnull(img_id); g_assert_cmpint(fu_firmware_get_idx(img_id), ==, 13); g_assert_cmpstr(fu_firmware_get_id(img_id), ==, "primary"); img_idx = fu_firmware_get_image_by_idx(firmware, 23, &error); g_assert_no_error(error); g_assert_nonnull(img_idx); g_assert_cmpint(fu_firmware_get_idx(img_idx), ==, 23); g_assert_cmpstr(fu_firmware_get_id(img_idx), ==, "secondary"); ret = fu_firmware_add_image_full(firmware, img3, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_false(ret); } static void fu_efivar_func(void) { gboolean ret; gsize sz = 0; guint32 attr = 0; guint64 total; g_autofree gchar *sysfsfwdir = NULL; g_autofree guint8 *data = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) names = NULL; #ifndef __linux__ g_test_skip("only works on Linux"); return; #endif /* these tests will write */ sysfsfwdir = g_test_build_filename(G_TEST_BUILT, "tests", NULL); (void)g_setenv("FWUPD_SYSFSFWDIR", sysfsfwdir, TRUE); /* check supported */ ret = fu_efivar_supported(&error); g_assert_no_error(error); g_assert_true(ret); /* check we can get the space used */ total = fu_efivar_space_used(&error); g_assert_no_error(error); g_assert_cmpint(total, >=, 0x2000); /* check existing keys */ g_assert_false(fu_efivar_exists(FU_EFIVAR_GUID_EFI_GLOBAL, "NotGoingToExist")); g_assert_true(fu_efivar_exists(FU_EFIVAR_GUID_EFI_GLOBAL, "SecureBoot")); /* list a few keys */ names = fu_efivar_get_names(FU_EFIVAR_GUID_EFI_GLOBAL, &error); g_assert_no_error(error); g_assert_nonnull(names); g_assert_cmpint(names->len, ==, 2); /* write and read a key */ ret = fu_efivar_set_data(FU_EFIVAR_GUID_EFI_GLOBAL, "Test", (guint8 *)"1", 1, FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_RUNTIME_ACCESS, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_efivar_get_data(FU_EFIVAR_GUID_EFI_GLOBAL, "Test", &data, &sz, &attr, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(sz, ==, 1); g_assert_cmpint(attr, ==, FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_RUNTIME_ACCESS); g_assert_cmpint(data[0], ==, '1'); /* delete single key */ ret = fu_efivar_delete(FU_EFIVAR_GUID_EFI_GLOBAL, "Test", &error); g_assert_no_error(error); g_assert_true(ret); g_assert_false(fu_efivar_exists(FU_EFIVAR_GUID_EFI_GLOBAL, "Test")); /* delete multiple keys */ ret = fu_efivar_set_data(FU_EFIVAR_GUID_EFI_GLOBAL, "Test1", (guint8 *)"1", 1, 0, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_efivar_set_data(FU_EFIVAR_GUID_EFI_GLOBAL, "Test2", (guint8 *)"1", 1, 0, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_efivar_delete_with_glob(FU_EFIVAR_GUID_EFI_GLOBAL, "Test*", &error); g_assert_no_error(error); g_assert_true(ret); g_assert_false(fu_efivar_exists(FU_EFIVAR_GUID_EFI_GLOBAL, "Test1")); g_assert_false(fu_efivar_exists(FU_EFIVAR_GUID_EFI_GLOBAL, "Test2")); /* read a key that doesn't exist */ ret = fu_efivar_get_data(FU_EFIVAR_GUID_EFI_GLOBAL, "NotGoingToExist", NULL, NULL, NULL, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); g_assert_false(ret); } typedef struct { guint cnt_success; guint cnt_failed; } FuDeviceRetryHelper; static gboolean fu_device_retry_success(FuDevice *device, gpointer user_data, GError **error) { FuDeviceRetryHelper *helper = (FuDeviceRetryHelper *)user_data; helper->cnt_success++; return TRUE; } static gboolean fu_device_retry_failed(FuDevice *device, gpointer user_data, GError **error) { FuDeviceRetryHelper *helper = (FuDeviceRetryHelper *)user_data; helper->cnt_failed++; g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed"); return FALSE; } static gboolean fu_device_retry_success_3rd_try(FuDevice *device, gpointer user_data, GError **error) { FuDeviceRetryHelper *helper = (FuDeviceRetryHelper *)user_data; if (helper->cnt_failed == 2) { helper->cnt_success++; return TRUE; } helper->cnt_failed++; g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed"); return FALSE; } static void fu_device_retry_success_func(void) { gboolean ret; g_autoptr(FuDevice) device = fu_device_new(NULL); g_autoptr(GError) error = NULL; FuDeviceRetryHelper helper = { .cnt_success = 0, .cnt_failed = 0, }; fu_device_retry_add_recovery(device, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, fu_device_retry_failed); ret = fu_device_retry(device, fu_device_retry_success, 3, &helper, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(helper.cnt_success, ==, 1); g_assert_cmpint(helper.cnt_failed, ==, 0); } static void fu_device_retry_failed_func(void) { gboolean ret; g_autoptr(FuDevice) device = fu_device_new(NULL); g_autoptr(GError) error = NULL; FuDeviceRetryHelper helper = { .cnt_success = 0, .cnt_failed = 0, }; fu_device_retry_add_recovery(device, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, fu_device_retry_success); ret = fu_device_retry(device, fu_device_retry_failed, 3, &helper, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL); g_assert_true(!ret); g_assert_cmpint(helper.cnt_success, ==, 2); /* do not reset for the last failure */ g_assert_cmpint(helper.cnt_failed, ==, 3); } static void fu_device_retry_hardware_func(void) { gboolean ret; g_autoptr(FuDevice) device = fu_device_new(NULL); g_autoptr(GError) error = NULL; FuDeviceRetryHelper helper = { .cnt_success = 0, .cnt_failed = 0, }; ret = fu_device_retry(device, fu_device_retry_success_3rd_try, 3, &helper, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(helper.cnt_success, ==, 1); g_assert_cmpint(helper.cnt_failed, ==, 2); } static void fu_bios_settings_load_func(void) { gboolean ret; gint integer; const gchar *tmp; GPtrArray *values; FwupdBiosSetting *setting; FwupdBiosSettingKind kind; g_autofree gchar *test_dir = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(GError) error = NULL; #ifdef FU_THINKLMI_COMPAT g_autoptr(FuBiosSettings) p14s_settings = NULL; g_autoptr(FuBiosSettings) p620_settings = NULL; g_autoptr(GPtrArray) p14s_items = NULL; g_autoptr(GPtrArray) p620_items = NULL; #endif g_autoptr(FuBiosSettings) p620_6_3_settings = NULL; g_autoptr(FuBiosSettings) xp29310_settings = NULL; g_autoptr(GPtrArray) p620_6_3_items = NULL; g_autoptr(GPtrArray) xps9310_items = NULL; #ifdef _WIN32 /* the "AlarmDate(MM\DD\YYYY)" setting really confuses wine for obvious reasons */ g_test_skip("BIOS settings not supported on Windows"); return; #endif /* load BIOS settings from a Lenovo P620 (with thinklmi driver problems) */ test_dir = g_test_build_filename(G_TEST_DIST, "tests", "bios-attrs", "lenovo-p620", NULL); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", test_dir, TRUE); ret = fu_context_reload_bios_settings(ctx, &error); #ifdef FU_THINKLMI_COMPAT g_assert_no_error(error); g_assert_true(ret); p620_settings = fu_context_get_bios_settings(ctx); p620_items = fu_bios_settings_get_all(p620_settings); g_assert_cmpint(p620_items->len, ==, 5); /* make sure nothing pending */ ret = fu_context_get_bios_setting_pending_reboot(ctx); g_assert_false(ret); /* check a BIOS setting reads from kernel as expected by fwupd today */ setting = fu_context_get_bios_setting(ctx, "com.thinklmi.AMDMemoryGuard"); g_assert_nonnull(setting); tmp = fwupd_bios_setting_get_name(setting); g_assert_cmpstr(tmp, ==, "AMDMemoryGuard"); tmp = fwupd_bios_setting_get_description(setting); g_assert_cmpstr(tmp, ==, "AMDMemoryGuard"); tmp = fwupd_bios_setting_get_current_value(setting); g_assert_cmpstr(tmp, ==, "Disable"); values = fwupd_bios_setting_get_possible_values(setting); for (guint i = 0; i < values->len; i++) { const gchar *possible = g_ptr_array_index(values, i); if (i == 0) g_assert_cmpstr(possible, ==, "Disable"); if (i == 1) g_assert_cmpstr(possible, ==, "Enable"); } /* try to read an BIOS setting known to have ][Status] to make sure we worked * around the thinklmi bug sufficiently */ setting = fu_context_get_bios_setting(ctx, "com.thinklmi.StartupSequence"); g_assert_nonnull(setting); tmp = fwupd_bios_setting_get_current_value(setting); g_assert_cmpstr(tmp, ==, "Primary"); values = fwupd_bios_setting_get_possible_values(setting); for (guint i = 0; i < values->len; i++) { const gchar *possible = g_ptr_array_index(values, i); if (i == 0) g_assert_cmpstr(possible, ==, "Primary"); if (i == 1) g_assert_cmpstr(possible, ==, "Automatic"); } /* check BIOS settings that should be read only */ for (guint i = 0; i < p620_items->len; i++) { const gchar *name; gboolean ro; setting = g_ptr_array_index(p620_items, i); ro = fwupd_bios_setting_get_read_only(setting); tmp = fwupd_bios_setting_get_current_value(setting); name = fwupd_bios_setting_get_name(setting); g_debug("%s: %s", name, tmp); if ((g_strcmp0(name, "pending_reboot") == 0) || (g_strrstr(tmp, "[Status") != NULL)) g_assert_true(ro); else g_assert_false(ro); } #else g_assert_error(error, G_FILE_ERROR, G_FILE_ERROR_NOENT); g_assert_false(ret); g_clear_error(&error); #endif g_free(test_dir); /* load BIOS settings from a Lenovo P620 running 6.3 */ test_dir = g_test_build_filename(G_TEST_DIST, "tests", "bios-attrs", "lenovo-p620-6.3", NULL); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", test_dir, TRUE); ret = fu_context_reload_bios_settings(ctx, &error); g_assert_no_error(error); g_assert_true(ret); p620_6_3_settings = fu_context_get_bios_settings(ctx); p620_6_3_items = fu_bios_settings_get_all(p620_6_3_settings); g_assert_cmpint(p620_6_3_items->len, ==, 5); /* make sure nothing pending */ ret = fu_context_get_bios_setting_pending_reboot(ctx); g_assert_false(ret); /* check a BIOS setting reads from kernel 6.3 as expected by fwupd */ setting = fu_context_get_bios_setting(ctx, "com.thinklmi.AMDMemoryGuard"); g_assert_nonnull(setting); tmp = fwupd_bios_setting_get_name(setting); g_assert_cmpstr(tmp, ==, "AMDMemoryGuard"); tmp = fwupd_bios_setting_get_description(setting); g_assert_cmpstr(tmp, ==, "AMDMemoryGuard"); tmp = fwupd_bios_setting_get_current_value(setting); g_assert_cmpstr(tmp, ==, "Disable"); values = fwupd_bios_setting_get_possible_values(setting); for (guint i = 0; i < values->len; i++) { const gchar *possible = g_ptr_array_index(values, i); if (i == 0) g_assert_cmpstr(possible, ==, "Disable"); if (i == 1) g_assert_cmpstr(possible, ==, "Enable"); } /* try to read an BIOS setting known to have ][Status] to make sure we worked * around the thinklmi bug sufficiently */ setting = fu_context_get_bios_setting(ctx, "com.thinklmi.StartupSequence"); g_assert_nonnull(setting); tmp = fwupd_bios_setting_get_current_value(setting); g_assert_cmpstr(tmp, ==, "Primary"); values = fwupd_bios_setting_get_possible_values(setting); for (guint i = 0; i < values->len; i++) { const gchar *possible = g_ptr_array_index(values, i); if (i == 0) g_assert_cmpstr(possible, ==, "Primary"); if (i == 1) g_assert_cmpstr(possible, ==, "Automatic"); } /* check BIOS settings that should be read only */ for (guint i = 0; i < p620_6_3_items->len; i++) { const gchar *name; gboolean ro; setting = g_ptr_array_index(p620_6_3_items, i); ro = fwupd_bios_setting_get_read_only(setting); tmp = fwupd_bios_setting_get_current_value(setting); name = fwupd_bios_setting_get_name(setting); g_debug("%s: %s", name, tmp); if ((g_strcmp0(name, "pending_reboot") == 0) || (g_strrstr(tmp, "[Status") != NULL)) g_assert_true(ro); else g_assert_false(ro); } g_free(test_dir); /* load BIOS settings from a Lenovo P14s Gen1 */ test_dir = g_test_build_filename(G_TEST_DIST, "tests", "bios-attrs", "lenovo-p14s-gen1", NULL); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", test_dir, TRUE); ret = fu_context_reload_bios_settings(ctx, &error); #ifdef FU_THINKLMI_COMPAT g_assert_no_error(error); g_assert_true(ret); p14s_settings = fu_context_get_bios_settings(ctx); p14s_items = fu_bios_settings_get_all(p14s_settings); g_assert_cmpint(p14s_items->len, ==, 3); /* reboot should be pending on this one */ ret = fu_context_get_bios_setting_pending_reboot(ctx); g_assert_true(ret); /* look for an enumeration BIOS setting with a space */ setting = fu_context_get_bios_setting(ctx, "com.thinklmi.SleepState"); g_assert_nonnull(setting); tmp = fwupd_bios_setting_get_name(setting); g_assert_cmpstr(tmp, ==, "SleepState"); tmp = fwupd_bios_setting_get_description(setting); g_assert_cmpstr(tmp, ==, "SleepState"); values = fwupd_bios_setting_get_possible_values(setting); for (guint i = 0; i < values->len; i++) { const gchar *possible = g_ptr_array_index(values, i); if (i == 0) g_assert_cmpstr(possible, ==, "Linux"); if (i == 1) g_assert_cmpstr(possible, ==, "Windows 10"); } /* make sure we defaulted UEFI Secure boot to read only if enabled */ setting = fu_context_get_bios_setting(ctx, "com.thinklmi.SecureBoot"); g_assert_nonnull(setting); ret = fwupd_bios_setting_get_read_only(setting); g_assert_true(ret); #else g_assert_error(error, G_FILE_ERROR, G_FILE_ERROR_NOENT); g_assert_false(ret); g_clear_error(&error); #endif g_free(test_dir); /* load BIOS settings from a Dell XPS 9310 */ test_dir = g_test_build_filename(G_TEST_DIST, "tests", "bios-attrs", "dell-xps13-9310", NULL); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", test_dir, TRUE); ret = fu_context_reload_bios_settings(ctx, &error); g_assert_no_error(error); g_assert_true(ret); xp29310_settings = fu_context_get_bios_settings(ctx); xps9310_items = fu_bios_settings_get_all(xp29310_settings); g_assert_cmpint(xps9310_items->len, ==, 109); /* make sure that we DIDN'T parse reset_bios setting */ setting = fu_context_get_bios_setting(ctx, FWUPD_BIOS_SETTING_RESET_BIOS); g_assert_null(setting); /* look at a integer BIOS setting */ setting = fu_context_get_bios_setting(ctx, "com.dell-wmi-sysman.CustomChargeStop"); g_assert_nonnull(setting); kind = fwupd_bios_setting_get_kind(setting); g_assert_cmpint(kind, ==, FWUPD_BIOS_SETTING_KIND_INTEGER); integer = fwupd_bios_setting_get_lower_bound(setting); g_assert_cmpint(integer, ==, 55); integer = fwupd_bios_setting_get_upper_bound(setting); g_assert_cmpint(integer, ==, 100); integer = fwupd_bios_setting_get_scalar_increment(setting); g_assert_cmpint(integer, ==, 1); /* look at a string BIOS setting */ setting = fu_context_get_bios_setting(ctx, "com.dell-wmi-sysman.Asset"); g_assert_nonnull(setting); integer = fwupd_bios_setting_get_lower_bound(setting); g_assert_cmpint(integer, ==, 1); integer = fwupd_bios_setting_get_upper_bound(setting); g_assert_cmpint(integer, ==, 64); tmp = fwupd_bios_setting_get_description(setting); g_assert_cmpstr(tmp, ==, "Asset Tag"); /* look at a enumeration BIOS setting */ setting = fu_context_get_bios_setting(ctx, "com.dell-wmi-sysman.BiosRcvrFrmHdd"); g_assert_nonnull(setting); kind = fwupd_bios_setting_get_kind(setting); g_assert_cmpint(kind, ==, FWUPD_BIOS_SETTING_KIND_ENUMERATION); values = fwupd_bios_setting_get_possible_values(setting); for (guint i = 0; i < values->len; i++) { const gchar *possible = g_ptr_array_index(values, i); if (i == 0) g_assert_cmpstr(possible, ==, "Disabled"); if (i == 1) g_assert_cmpstr(possible, ==, "Enabled"); } /* make sure we defaulted UEFI Secure boot to read only if enabled */ setting = fu_context_get_bios_setting(ctx, "com.dell-wmi-sysman.SecureBoot"); g_assert_nonnull(setting); ret = fwupd_bios_setting_get_read_only(setting); g_assert_true(ret); } static void fu_security_attrs_hsi_func(void) { g_autofree gchar *hsi1 = NULL; g_autofree gchar *hsi2 = NULL; g_autofree gchar *hsi3 = NULL; g_autofree gchar *hsi4 = NULL; g_autofree gchar *hsi5 = NULL; g_autofree gchar *hsi6 = NULL; g_autofree gchar *hsi7 = NULL; g_autofree gchar *hsi8 = NULL; g_autofree gchar *hsi9 = NULL; g_autofree gchar *expected_hsi9 = NULL; g_autoptr(FuSecurityAttrs) attrs = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; /* no attrs */ attrs = fu_security_attrs_new(); hsi1 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_NONE); g_assert_cmpstr(hsi1, ==, "HSI:0"); /* just success from HSI:1 */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_SPI_BIOSWE); fwupd_security_attr_set_plugin(attr, "test"); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_CRITICAL); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_url(attr, "http://test"); fu_security_attrs_append(attrs, attr); hsi2 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_NONE); g_assert_cmpstr(hsi2, ==, "HSI:1"); g_clear_object(&attr); /* add failed from HSI:2, so still HSI:1 */ attr = fwupd_security_attr_new("org.fwupd.hsi.PRX"); fwupd_security_attr_set_plugin(attr, "test"); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT); fwupd_security_attr_set_url(attr, "http://test"); fu_security_attrs_append(attrs, attr); hsi3 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_NONE); g_assert_cmpstr(hsi3, ==, "HSI:1"); g_clear_object(&attr); /* add an implicit obsolete via duplication */ attr = fwupd_security_attr_new("org.fwupd.hsi.PRX"); fwupd_security_attr_set_plugin(attr, "other-plugin"); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT); fwupd_security_attr_set_url(attr, "http://other-plugin"); fu_security_attrs_append(attrs, attr); fu_security_attrs_depsolve(attrs); hsi4 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_NONE); g_assert_cmpstr(hsi4, ==, "HSI:1"); g_assert_true(fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)); g_clear_object(&attr); /* add attr from HSI:3, obsoleting the failure */ attr = fwupd_security_attr_new("org.fwupd.hsi.BIOSGuard"); fwupd_security_attr_set_plugin(attr, "test"); fwupd_security_attr_set_level(attr, FWUPD_SECURITY_ATTR_LEVEL_THEORETICAL); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_add_obsolete(attr, "org.fwupd.hsi.PRX"); fwupd_security_attr_set_url(attr, "http://test"); fu_security_attrs_append(attrs, attr); fu_security_attrs_depsolve(attrs); hsi5 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_NONE); g_assert_cmpstr(hsi5, ==, "HSI:3"); g_clear_object(&attr); /* add taint that was fine */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_FWUPD_PLUGINS); fwupd_security_attr_set_plugin(attr, "test"); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fwupd_security_attr_set_url(attr, "http://test"); fu_security_attrs_append(attrs, attr); hsi6 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_NONE); g_assert_cmpstr(hsi6, ==, "HSI:3"); g_clear_object(&attr); /* add updates and attestation */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_FWUPD_UPDATES); fwupd_security_attr_set_plugin(attr, "test"); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_set_url(attr, "http://test"); fu_security_attrs_append(attrs, attr); hsi7 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_NONE); g_assert_cmpstr(hsi7, ==, "HSI:3"); g_clear_object(&attr); /* add issue that was uncool */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_KERNEL_SWAP); fwupd_security_attr_set_plugin(attr, "test"); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fwupd_security_attr_set_url(attr, "http://test"); fu_security_attrs_append(attrs, attr); hsi8 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_NONE); g_assert_cmpstr(hsi8, ==, "HSI:3!"); g_clear_object(&attr); /* show version in the attribute */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_KERNEL_SWAP); fwupd_security_attr_set_plugin(attr, "test"); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fwupd_security_attr_set_url(attr, "http://test"); fu_security_attrs_append(attrs, attr); hsi9 = fu_security_attrs_calculate_hsi(attrs, FU_SECURITY_ATTRS_FLAG_ADD_VERSION); expected_hsi9 = g_strdup_printf("HSI:3! (v%d.%d.%d)", FWUPD_MAJOR_VERSION, FWUPD_MINOR_VERSION, FWUPD_MICRO_VERSION); g_assert_cmpstr(hsi9, ==, expected_hsi9); g_clear_object(&attr); } static void fu_security_attrs_compare_func(void) { FwupdSecurityAttr *attr_tmp; g_autoptr(FuSecurityAttrs) attrs1 = fu_security_attrs_new(); g_autoptr(FuSecurityAttrs) attrs2 = fu_security_attrs_new(); g_autoptr(FwupdSecurityAttr) attr1 = fwupd_security_attr_new("org.fwupd.hsi.foo"); g_autoptr(FwupdSecurityAttr) attr2 = fwupd_security_attr_new("org.fwupd.hsi.bar"); g_autoptr(FwupdSecurityAttr) attr3 = fwupd_security_attr_new("org.fwupd.hsi.baz"); g_autoptr(FwupdSecurityAttr) attr4 = fwupd_security_attr_new("org.fwupd.hsi.baz"); g_autoptr(GPtrArray) results = NULL; /* attrs1 has foo and baz(enabled) */ fwupd_security_attr_set_plugin(attr1, "foo"); fwupd_security_attr_set_created(attr1, 0); fwupd_security_attr_set_result(attr1, FWUPD_SECURITY_ATTR_RESULT_ENCRYPTED); fu_security_attrs_append(attrs1, attr1); fwupd_security_attr_set_plugin(attr3, "baz"); fwupd_security_attr_set_created(attr3, 0); fwupd_security_attr_set_result(attr3, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs1, attr3); /* attrs2 has bar and baz(~enabled) */ fwupd_security_attr_set_plugin(attr2, "bar"); fwupd_security_attr_set_created(attr2, 0); fwupd_security_attr_set_result(attr2, FWUPD_SECURITY_ATTR_RESULT_LOCKED); fu_security_attrs_append(attrs2, attr2); fwupd_security_attr_set_plugin(attr4, "baz"); fwupd_security_attr_set_created(attr4, 0); fwupd_security_attr_set_result(attr4, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fu_security_attrs_append(attrs2, attr4); results = fu_security_attrs_compare(attrs1, attrs2); g_assert_cmpint(results->len, ==, 3); attr_tmp = g_ptr_array_index(results, 0); g_assert_cmpstr(fwupd_security_attr_get_appstream_id(attr_tmp), ==, "org.fwupd.hsi.bar"); g_assert_cmpint(fwupd_security_attr_get_result_fallback(attr_tmp), ==, FWUPD_SECURITY_ATTR_RESULT_UNKNOWN); g_assert_cmpint(fwupd_security_attr_get_result(attr_tmp), ==, FWUPD_SECURITY_ATTR_RESULT_LOCKED); attr_tmp = g_ptr_array_index(results, 1); g_assert_cmpstr(fwupd_security_attr_get_appstream_id(attr_tmp), ==, "org.fwupd.hsi.foo"); g_assert_cmpint(fwupd_security_attr_get_result_fallback(attr_tmp), ==, FWUPD_SECURITY_ATTR_RESULT_ENCRYPTED); g_assert_cmpint(fwupd_security_attr_get_result(attr_tmp), ==, FWUPD_SECURITY_ATTR_RESULT_UNKNOWN); attr_tmp = g_ptr_array_index(results, 2); g_assert_cmpstr(fwupd_security_attr_get_appstream_id(attr_tmp), ==, "org.fwupd.hsi.baz"); g_assert_cmpint(fwupd_security_attr_get_result_fallback(attr_tmp), ==, FWUPD_SECURITY_ATTR_RESULT_ENABLED); g_assert_cmpint(fwupd_security_attr_get_result(attr_tmp), ==, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); g_assert_true(fu_security_attrs_equal(attrs1, attrs1)); g_assert_false(fu_security_attrs_equal(attrs1, attrs2)); g_assert_false(fu_security_attrs_equal(attrs2, attrs1)); } static void fu_firmware_builder_round_trip_func(void) { struct { GType gtype; const gchar *xml_fn; const gchar *checksum; } map[] = { {FU_TYPE_CAB_FIRMWARE, "cab.builder.xml", "a708f47b1a46377f1ea420597641ffe9a40abd75"}, {FU_TYPE_CAB_FIRMWARE, "cab-compressed.builder.xml", NULL}, /* not byte-identical */ {FU_TYPE_DFUSE_FIRMWARE, "dfuse.builder.xml", "c1ff429f0e381c8fe8e1b2ee41a5a9a79e2f2ff7"}, {FU_TYPE_HID_REPORT_ITEM, "hid-report-item.builder.xml", "5b18c07399fc8968ce22127df38d8d923089ec92"}, {FU_TYPE_HID_DESCRIPTOR, "hid-descriptor.builder.xml", "6bb23f7c9fedc21f05528b3b63ad5837f4a16a92"}, {FU_TYPE_CSV_FIRMWARE, "csv.builder.xml", "986cbf8cde5bc7d8b49ee94cceae3f92efbd2eef"}, {FU_TYPE_FDT_FIRMWARE, "fdt.builder.xml", "40f7fbaff684a6bcf67c81b3079422c2529741e1"}, {FU_TYPE_FIT_FIRMWARE, "fit.builder.xml", "293ce07351bb7d76631c4e2ba47243db1e150f3c"}, {FU_TYPE_SREC_FIRMWARE, "srec.builder.xml", "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"}, {FU_TYPE_IHEX_FIRMWARE, "ihex.builder.xml", "a8d74f767f3fc992b413e5ba801cedc80a4cf013"}, {FU_TYPE_FMAP_FIRMWARE, "fmap.builder.xml", "a0b9ffc10a586d217edf9e9bae7c1fe7c564ea01"}, {FU_TYPE_EFI_LOAD_OPTION, "efi-load-option.builder.xml", "7ef696d22902ae97ef5f73ad9c85a28095ad56f1"}, {FU_TYPE_EDID, "edid.builder.xml", "64cef10b75ccce684a483d576dd4a4ce6bef8165"}, {FU_TYPE_EFI_FIRMWARE_SECTION, "efi-firmware-section.builder.xml", "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"}, {FU_TYPE_EFI_FIRMWARE_SECTION, "efi-firmware-section.builder.xml", "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"}, {FU_TYPE_EFI_FIRMWARE_FILE, "efi-firmware-file.builder.xml", "90374d97cf6bc70059d24c816c188c10bd250ed7"}, {FU_TYPE_EFI_FIRMWARE_FILESYSTEM, "efi-firmware-filesystem.builder.xml", "d6fbadc1c303a3b4eede9db7fb0ddb353efffc86"}, {FU_TYPE_EFI_FIRMWARE_VOLUME, "efi-firmware-volume.builder.xml", "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"}, {FU_TYPE_IFD_FIRMWARE, "ifd.builder.xml", "06ae066ea53cefe43fed2f1ca4fc7d8cccdbcf1e"}, {FU_TYPE_CFU_OFFER, "cfu-offer.builder.xml", "c10223887ff6cdf4475ad07c65b1f0f3a2d0d5ca"}, {FU_TYPE_CFU_PAYLOAD, "cfu-payload.builder.xml", "5da829f5fd15a28970aed98ebb26ebf2f88ed6f2"}, {FU_TYPE_IFWI_CPD_FIRMWARE, "ifwi-cpd.builder.xml", "91e348d17cb91ef7a528e85beb39d15a0532dca5"}, {FU_TYPE_IFWI_FPT_FIRMWARE, "ifwi-fpt.builder.xml", "d1f0fb2c2a7a99441bf4a825d060642315a94d91"}, {FU_TYPE_OPROM_FIRMWARE, "oprom.builder.xml", "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"}, {FU_TYPE_INTEL_THUNDERBOLT_NVM, "intel-thunderbolt.builder.xml", "e858000646fecb5223b41df57647c005b495749b"}, #ifdef HAVE_CBOR {FU_TYPE_USWID_FIRMWARE, "uswid.builder.xml", "b473fbdbe00f860c4da43f9499569394bac81f14"}, #endif {G_TYPE_INVALID, NULL, NULL}}; g_type_ensure(FU_TYPE_COSWID_FIRMWARE); for (guint i = 0; map[i].gtype != G_TYPE_INVALID; i++) { gboolean ret; g_autofree gchar *csum1 = NULL; g_autofree gchar *csum2 = NULL; g_autofree gchar *filename = NULL; g_autofree gchar *xml1 = NULL; g_autofree gchar *xml2 = NULL; g_autoptr(FuFirmware) firmware1 = g_object_new(map[i].gtype, NULL); g_autoptr(FuFirmware) firmware2 = g_object_new(map[i].gtype, NULL); g_autoptr(FuFirmware) firmware3 = g_object_new(map[i].gtype, NULL); g_autoptr(GError) error = NULL; g_autoptr(GBytes) blob = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", map[i].xml_fn, NULL); ret = g_file_get_contents(filename, &xml1, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_build_from_xml(firmware1, xml1, &error); g_assert_no_error(error); g_assert_true(ret); csum1 = fu_firmware_get_checksum(firmware1, G_CHECKSUM_SHA1, &error); g_assert_no_error(error); g_assert_nonnull(csum1); if (map[i].checksum != NULL) g_assert_cmpstr(csum1, ==, map[i].checksum); /* ensure we can write and then parse what we just wrote */ blob = fu_firmware_write(firmware1, &error); g_assert_no_error(error); g_assert_nonnull(blob); ret = fu_firmware_parse(firmware3, blob, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); if (!ret) g_prefix_error(&error, "%s: ", map[i].xml_fn); g_assert_no_error(error); g_assert_true(ret); /* ensure we can round-trip */ xml2 = fu_firmware_export_to_xml(firmware1, FU_FIRMWARE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); ret = fu_firmware_build_from_xml(firmware2, xml2, &error); g_assert_no_error(error); g_assert_true(ret); csum2 = fu_firmware_get_checksum(firmware2, G_CHECKSUM_SHA1, &error); g_assert_nonnull(csum2); g_assert_no_error(error); if (map[i].checksum != NULL) g_assert_cmpstr(csum2, ==, map[i].checksum); } } typedef struct { guint last_percentage; guint updates; } FuProgressHelper; static void fu_progress_percentage_changed_cb(FuProgress *progress, guint percentage, gpointer data) { FuProgressHelper *helper = (FuProgressHelper *)data; helper->last_percentage = percentage; helper->updates++; } static void fu_progress_func(void) { FuProgressHelper helper = {0}; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autofree gchar *str = NULL; g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_progress_percentage_changed_cb), &helper); g_assert_cmpfloat_with_epsilon(fu_progress_get_duration(progress), 0.f, 0.001); fu_progress_set_profile(progress, TRUE); fu_progress_set_steps(progress, 5); g_assert_cmpint(helper.last_percentage, ==, 0); g_usleep(20 * 1000); fu_progress_step_done(progress); g_assert_cmpint(helper.updates, ==, 2); g_assert_cmpint(helper.last_percentage, ==, 20); for (guint i = 0; i < 4; i++) { g_usleep(20 * 1000); fu_progress_step_done(progress); } g_assert_cmpint(helper.last_percentage, ==, 100); g_assert_cmpint(helper.updates, ==, 6); g_assert_cmpfloat_with_epsilon(fu_progress_get_duration(progress), 0.1f, 0.05); str = fu_progress_traceback(progress); g_debug("\n%s", str); } static void fu_progress_child_func(void) { FuProgressHelper helper = {0}; FuProgress *child; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* reset */ fu_progress_set_profile(progress, TRUE); fu_progress_set_steps(progress, 2); g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_progress_percentage_changed_cb), &helper); /* parent: |-----------------------|-----------------------| * step1: |-----------------------| * child: |-------------|---------| */ /* PARENT UPDATE */ g_debug("parent update #1"); fu_progress_step_done(progress); g_assert_cmpint(helper.updates, ==, 1); g_assert_cmpint(helper.last_percentage, ==, 50); /* now test with a child */ child = fu_progress_get_child(progress); fu_progress_set_id(child, G_STRLOC); fu_progress_set_steps(child, 2); g_debug("child update #1"); fu_progress_step_done(child); g_assert_cmpint(helper.updates, ==, 2); g_assert_cmpint(helper.last_percentage, ==, 75); /* child update */ g_debug("child update #2"); fu_progress_step_done(child); g_assert_cmpint(helper.updates, ==, 3); g_assert_cmpint(helper.last_percentage, ==, 100); /* parent update */ g_debug("parent update #2"); fu_progress_step_done(progress); /* ensure we ignored the duplicate */ g_assert_cmpint(helper.updates, ==, 3); g_assert_cmpint(helper.last_percentage, ==, 100); } static void fu_progress_scaling_func(void) { const guint insane_steps = 1000 * 1000; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); fu_progress_set_steps(progress, insane_steps); for (guint i = 0; i < insane_steps / 2; i++) fu_progress_step_done(progress); g_assert_cmpint(fu_progress_get_percentage(progress), ==, 50); for (guint i = 0; i < insane_steps / 2; i++) { FuProgress *progress_child = fu_progress_get_child(progress); fu_progress_set_percentage(progress_child, 0); fu_progress_set_percentage(progress_child, 100); fu_progress_step_done(progress); } g_assert_cmpint(fu_progress_get_percentage(progress), ==, 100); } static void fu_progress_parent_one_step_proxy_func(void) { FuProgressHelper helper = {0}; FuProgress *child; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* one step */ fu_progress_set_steps(progress, 1); g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_progress_percentage_changed_cb), &helper); /* now test with a child */ child = fu_progress_get_child(progress); fu_progress_set_id(child, G_STRLOC); fu_progress_set_steps(child, 2); /* child set value */ fu_progress_set_percentage(child, 33); /* ensure 1 updates for progress with one step and ensure using child value as parent */ g_assert_cmpint(helper.updates, ==, 1); g_assert_cmpint(helper.last_percentage, ==, 33); } static void fu_progress_non_equal_steps_func(void) { g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); FuProgress *child; FuProgress *grandchild; /* test non-equal steps */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 20, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 60, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 20, NULL); g_assert_cmpint(fu_progress_get_percentage(progress), ==, 0); g_assert_cmpint(fu_progress_get_status(progress), ==, FWUPD_STATUS_DEVICE_ERASE); /* child step should increment according to the custom steps */ child = fu_progress_get_child(progress); fu_progress_set_id(child, G_STRLOC); fu_progress_set_steps(child, 2); fu_progress_set_status(child, FWUPD_STATUS_DEVICE_BUSY); g_assert_cmpint(fu_progress_get_status(progress), ==, FWUPD_STATUS_DEVICE_BUSY); /* start child */ fu_progress_step_done(child); /* verify 10% */ g_assert_cmpint(fu_progress_get_percentage(progress), ==, 10); /* finish child */ fu_progress_step_done(child); /* ensure the parent is switched back to the status before the child took over */ g_assert_cmpint(fu_progress_get_status(progress), ==, FWUPD_STATUS_DEVICE_ERASE); fu_progress_step_done(progress); g_assert_cmpint(fu_progress_get_status(progress), ==, FWUPD_STATUS_DEVICE_WRITE); /* verify 20% */ g_assert_cmpint(fu_progress_get_percentage(progress), ==, 20); /* child step should increment according to the custom steps */ child = fu_progress_get_child(progress); fu_progress_set_id(child, G_STRLOC); fu_progress_set_id(child, G_STRLOC); fu_progress_add_step(child, FWUPD_STATUS_DEVICE_RESTART, 25, NULL); fu_progress_add_step(child, FWUPD_STATUS_DEVICE_WRITE, 75, NULL); g_assert_cmpint(fu_progress_get_status(progress), ==, FWUPD_STATUS_DEVICE_RESTART); /* start child */ fu_progress_step_done(child); g_assert_cmpint(fu_progress_get_status(progress), ==, FWUPD_STATUS_DEVICE_WRITE); /* verify bilinear interpolation is working */ g_assert_cmpint(fu_progress_get_percentage(progress), ==, 35); /* * 0 20 80 100 * |---------||----------------------------||---------| * | 35 | * |-------||-------------------| (25%) * | 75.5 | * |---------------||--| (90%) */ grandchild = fu_progress_get_child(child); fu_progress_set_id(grandchild, G_STRLOC); fu_progress_add_step(grandchild, FWUPD_STATUS_DEVICE_ERASE, 90, NULL); fu_progress_add_step(grandchild, FWUPD_STATUS_DEVICE_WRITE, 10, NULL); fu_progress_step_done(grandchild); /* verify bilinear interpolation (twice) is working for subpercentage */ g_assert_cmpint(fu_progress_get_percentage(progress), ==, 75); fu_progress_step_done(grandchild); /* finish child */ fu_progress_step_done(child); fu_progress_step_done(progress); g_assert_cmpint(fu_progress_get_status(progress), ==, FWUPD_STATUS_DEVICE_READ); /* verify 80% */ g_assert_cmpint(fu_progress_get_percentage(progress), ==, 80); fu_progress_step_done(progress); /* verify 100% */ g_assert_cmpint(fu_progress_get_percentage(progress), ==, 100); g_assert_cmpint(fu_progress_get_status(progress), ==, FWUPD_STATUS_UNKNOWN); } static void fu_progress_finish_func(void) { FuProgress *child; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* check straight finish */ fu_progress_set_steps(progress, 3); child = fu_progress_get_child(progress); fu_progress_set_id(child, G_STRLOC); fu_progress_set_steps(child, 3); fu_progress_finished(child); /* parent step done after child finish */ fu_progress_step_done(progress); } static void fu_progress_child_finished(void) { FuProgress *child; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* check straight finish */ fu_progress_set_steps(progress, 3); child = fu_progress_get_child(progress); fu_progress_set_id(child, G_STRLOC); fu_progress_set_steps(child, 3); /* some imaginary ignorable error */ /* parent step done after child finish */ fu_progress_add_flag(progress, FU_PROGRESS_FLAG_CHILD_FINISHED); fu_progress_step_done(progress); } static void fu_plugin_struct_func(void) { gboolean ret; g_autoptr(GByteArray) st = fu_struct_self_test_new(); g_autoptr(GByteArray) st2 = NULL; g_autoptr(GByteArray) st3 = NULL; g_autoptr(GError) error = NULL; g_autofree gchar *str1 = NULL; g_autofree gchar *str2 = NULL; g_autofree gchar *oem_table_id = NULL; /* size */ g_assert_cmpint(st->len, ==, 51); /* getters and setters */ fu_struct_self_test_set_revision(st, 0xFF); fu_struct_self_test_set_length(st, 0xDEAD); ret = fu_struct_self_test_set_oem_table_id(st, "X", &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_struct_self_test_get_revision(st), ==, 0xFF); g_assert_cmpint(fu_struct_self_test_get_length(st), ==, 0xDEAD); /* pack */ str1 = fu_byte_array_to_string(st); g_assert_cmpstr(str1, ==, "12345678adde0000ff000000000000000000000000000000004142434445465800000000" "00000000000000dfdfdfdf00000000"); /* parse */ st2 = fu_struct_self_test_parse(st->data, st->len, 0x0, &error); g_assert_no_error(error); g_assert_nonnull(st2); g_assert_cmpint(fu_struct_self_test_get_revision(st2), ==, 0xFF); g_assert_cmpint(fu_struct_self_test_get_length(st2), ==, 0xDEAD); oem_table_id = fu_struct_self_test_get_oem_table_id(st2); g_assert_cmpstr(oem_table_id, ==, "X"); /* to string */ str2 = fu_struct_self_test_to_string(st); g_assert_cmpstr(str2, ==, "SelfTest:\n" " length: 0xdead\n" " revision: 0xff [all]\n" " owner: 00000000-0000-0000-0000-000000000000\n" " oem_table_id: X\n" " oem_revision: 0x0\n" " asl_compiler_id: 0xDFDFDFDF\n" " asl_compiler_revision: 0x0"); /* parse failing signature */ st->data[0] = 0xFF; st3 = fu_struct_self_test_parse(st->data, st->len, 0x0, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_null(st3); g_clear_error(&error); ret = fu_struct_self_test_validate(st->data, st->len, 0x0, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_false(ret); } static void fu_plugin_struct_wrapped_func(void) { gboolean ret; g_autofree gchar *str1 = NULL; g_autofree gchar *str2 = NULL; g_autofree gchar *str4 = NULL; g_autoptr(GByteArray) st2 = NULL; g_autoptr(GByteArray) st3 = NULL; g_autoptr(GByteArray) st_base2 = NULL; g_autoptr(GByteArray) st_base = fu_struct_self_test_new(); g_autoptr(GByteArray) st = fu_struct_self_test_wrapped_new(); g_autoptr(GError) error = NULL; /* size */ g_assert_cmpint(st->len, ==, 53); /* getters and setters */ fu_struct_self_test_wrapped_set_less(st, 0x99); fu_struct_self_test_wrapped_set_more(st, 0x12); g_assert_cmpint(fu_struct_self_test_wrapped_get_more(st), ==, 0x12); str1 = fu_byte_array_to_string(st); g_assert_cmpstr(str1, ==, "991234567833000000000000000000000000000000000000000041424344454600000000" "0000000000000000dfdfdfdf0000000012"); /* modify the base */ fu_struct_self_test_set_revision(st_base, 0xFE); ret = fu_struct_self_test_wrapped_set_base(st, st_base, &error); g_assert_no_error(error); g_assert_true(ret); str4 = fu_byte_array_to_string(st); g_assert_cmpstr(str4, ==, "991234567833000000fe0000000000000000000000000000000041424344454600000000" "0000000000000000dfdfdfdf0000000012"); /* parse */ st2 = fu_struct_self_test_wrapped_parse(st->data, st->len, 0x0, &error); g_assert_no_error(error); g_assert_nonnull(st2); g_assert_cmpint(fu_struct_self_test_wrapped_get_more(st), ==, 0x12); st_base2 = fu_struct_self_test_wrapped_get_base(st); g_assert_cmpint(fu_struct_self_test_get_revision(st_base2), ==, 0xFE); /* to string */ str2 = fu_struct_self_test_wrapped_to_string(st); g_assert_cmpstr(str2, ==, "SelfTestWrapped:\n" " less: 0x99\n" " base: SelfTest:\n" " length: 0x33\n" " revision: 0xfe\n" " owner: 00000000-0000-0000-0000-000000000000\n" " oem_revision: 0x0\n" " asl_compiler_id: 0xDFDFDFDF\n" " asl_compiler_revision: 0x0\n" " more: 0x12"); /* parse failing signature */ st->data[FU_STRUCT_SELF_TEST_WRAPPED_OFFSET_BASE] = 0xFF; st3 = fu_struct_self_test_wrapped_parse(st->data, st->len, 0x0, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_null(st3); g_clear_error(&error); ret = fu_struct_self_test_wrapped_validate(st->data, st->len, 0x0, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_assert_false(ret); } static void fu_efi_load_option_func(void) { /* * 0000 = Linux-Firmware-Updater * 0001 = Fedora * 0002 = Windows Boot Manager */ for (guint16 i = 0; i < 3; i++) { g_autoptr(GError) error = NULL; g_autoptr(FuEfiLoadOption) load_option = fu_efi_load_option_new_esp_for_boot_entry(i, &error); g_autoptr(GBytes) fw = NULL; g_autofree gchar *str = NULL; if (load_option == NULL) { g_debug("failed: %s", error->message); continue; } str = fu_firmware_to_string(FU_FIRMWARE(load_option)); g_debug("%s", str); fw = fu_firmware_write(FU_FIRMWARE(load_option), &error); g_assert_no_error(error); g_assert_nonnull(fw); } } int main(int argc, char **argv) { g_autofree gchar *testdatadir = NULL; (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); g_type_ensure(FU_TYPE_IFD_BIOS); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); (void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); (void)g_setenv("FWUPD_DATADIR", testdatadir, TRUE); (void)g_setenv("FWUPD_LIBDIR_PKG", testdatadir, TRUE); (void)g_setenv("FWUPD_SYSCONFDIR", testdatadir, TRUE); (void)g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", testdatadir, TRUE); (void)g_setenv("FWUPD_SYSFSDMIDIR", testdatadir, TRUE); (void)g_setenv("FWUPD_LOCALSTATEDIR", testdatadir, TRUE); (void)g_setenv("FWUPD_OFFLINE_TRIGGER", "/tmp/fwupd-self-test/system-update", TRUE); (void)g_setenv("FWUPD_LOCALSTATEDIR", "/tmp/fwupd-self-test/var", TRUE); (void)g_setenv("FWUPD_PROFILE", "1", TRUE); g_test_add_func("/fwupd/struct", fu_plugin_struct_func); g_test_add_func("/fwupd/struct{wrapped}", fu_plugin_struct_wrapped_func); g_test_add_func("/fwupd/plugin{quirks-append}", fu_plugin_quirks_append_func); g_test_add_func("/fwupd/string{password-mask}", fu_strpassmask_func); g_test_add_func("/fwupd/common{strnsplit}", fu_strsplit_func); g_test_add_func("/fwupd/common{olson-timezone-id}", fu_common_olson_timezone_id_func); g_test_add_func("/fwupd/common{memmem}", fu_common_memmem_func); if (g_test_slow()) g_test_add_func("/fwupd/progress", fu_progress_func); g_test_add_func("/fwupd/progress{scaling}", fu_progress_scaling_func); g_test_add_func("/fwupd/progress{child}", fu_progress_child_func); g_test_add_func("/fwupd/progress{child-finished}", fu_progress_child_finished); g_test_add_func("/fwupd/progress{parent-1-step}", fu_progress_parent_one_step_proxy_func); g_test_add_func("/fwupd/progress{no-equal}", fu_progress_non_equal_steps_func); g_test_add_func("/fwupd/progress{finish}", fu_progress_finish_func); g_test_add_func("/fwupd/bios-attrs{load}", fu_bios_settings_load_func); g_test_add_func("/fwupd/security-attrs{hsi}", fu_security_attrs_hsi_func); g_test_add_func("/fwupd/security-attrs{compare}", fu_security_attrs_compare_func); g_test_add_func("/fwupd/config", fu_config_func); g_test_add_func("/fwupd/plugin{device-gtype}", fu_plugin_device_gtype_func); g_test_add_func("/fwupd/plugin{config}", fu_plugin_config_func); g_test_add_func("/fwupd/plugin{devices}", fu_plugin_devices_func); g_test_add_func("/fwupd/plugin{device-inhibit-children}", fu_plugin_device_inhibit_children_func); g_test_add_func("/fwupd/plugin{delay}", fu_plugin_delay_func); g_test_add_func("/fwupd/plugin{quirks}", fu_plugin_quirks_func); g_test_add_func("/fwupd/plugin{fdt}", fu_plugin_fdt_func); g_test_add_func("/fwupd/plugin{quirks-performance}", fu_plugin_quirks_performance_func); g_test_add_func("/fwupd/plugin{quirks-device}", fu_plugin_quirks_device_func); g_test_add_func("/fwupd/backend", fu_backend_func); g_test_add_func("/fwupd/chunk", fu_chunk_func); g_test_add_func("/fwupd/chunks", fu_chunk_array_func); g_test_add_func("/fwupd/common{align-up}", fu_common_align_up_func); g_test_add_func("/fwupd/volume{gpt-type}", fu_volume_gpt_type_func); g_test_add_func("/fwupd/common{byte-array}", fu_common_byte_array_func); g_test_add_func("/fwupd/common{crc}", fu_common_crc_func); g_test_add_func("/fwupd/common{string-append-kv}", fu_string_append_func); g_test_add_func("/fwupd/common{version-guess-format}", fu_version_guess_format_func); g_test_add_func("/fwupd/common{strtoull}", fu_strtoull_func); g_test_add_func("/fwupd/common{strtoll}", fu_strtoll_func); g_test_add_func("/fwupd/common{version}", fu_common_version_func); g_test_add_func("/fwupd/common{version-semver}", fu_version_semver_func); g_test_add_func("/fwupd/common{vercmp}", fu_common_vercmp_func); g_test_add_func("/fwupd/common{strstrip}", fu_strstrip_func); g_test_add_func("/fwupd/common{endian}", fu_common_endian_func); g_test_add_func("/fwupd/common{bytes-get-data}", fu_common_bytes_get_data_func); g_test_add_func("/fwupd/common{kernel-lockdown}", fu_common_kernel_lockdown_func); g_test_add_func("/fwupd/common{strsafe}", fu_strsafe_func); g_test_add_func("/fwupd/efi-load-option", fu_efi_load_option_func); g_test_add_func("/fwupd/efivar", fu_efivar_func); g_test_add_func("/fwupd/hwids", fu_hwids_func); g_test_add_func("/fwupd/context{flags}", fu_context_flags_func); g_test_add_func("/fwupd/context{hwids-dmi}", fu_context_hwids_dmi_func); g_test_add_func("/fwupd/string{utf16}", fu_string_utf16_func); g_test_add_func("/fwupd/smbios", fu_smbios_func); g_test_add_func("/fwupd/smbios3", fu_smbios3_func); g_test_add_func("/fwupd/kernel{cmdline}", fu_kernel_cmdline_func); g_test_add_func("/fwupd/kernel{config}", fu_kernel_config_func); g_test_add_func("/fwupd/hid{descriptor}", fu_hid_descriptor_func); g_test_add_func("/fwupd/hid{descriptor-container}", fu_hid_descriptor_container_func); g_test_add_func("/fwupd/firmware", fu_firmware_func); g_test_add_func("/fwupd/firmware{common}", fu_firmware_common_func); g_test_add_func("/fwupd/firmware{csv}", fu_firmware_csv_func); g_test_add_func("/fwupd/firmware{archive}", fu_firmware_archive_func); g_test_add_func("/fwupd/firmware{linear}", fu_firmware_linear_func); g_test_add_func("/fwupd/firmware{dedupe}", fu_firmware_dedupe_func); g_test_add_func("/fwupd/firmware{build}", fu_firmware_build_func); g_test_add_func("/fwupd/firmware{raw-aligned}", fu_firmware_raw_aligned_func); g_test_add_func("/fwupd/firmware{ihex}", fu_firmware_ihex_func); g_test_add_func("/fwupd/firmware{ihex-offset}", fu_firmware_ihex_offset_func); g_test_add_func("/fwupd/firmware{ihex-signed}", fu_firmware_ihex_signed_func); g_test_add_func("/fwupd/firmware{srec-tokenization}", fu_firmware_srec_tokenization_func); g_test_add_func("/fwupd/firmware{srec}", fu_firmware_srec_func); g_test_add_func("/fwupd/firmware{fdt}", fu_firmware_fdt_func); g_test_add_func("/fwupd/firmware{fit}", fu_firmware_fit_func); g_test_add_func("/fwupd/firmware{ifwi-cpd}", fu_firmware_ifwi_cpd_func); g_test_add_func("/fwupd/firmware{ifwi-fpt}", fu_firmware_ifwi_fpt_func); g_test_add_func("/fwupd/firmware{oprom}", fu_firmware_oprom_func); g_test_add_func("/fwupd/firmware{dfu}", fu_firmware_dfu_func); g_test_add_func("/fwupd/firmware{dfu-patch}", fu_firmware_dfu_patch_func); g_test_add_func("/fwupd/firmware{dfuse}", fu_firmware_dfuse_func); g_test_add_func("/fwupd/firmware{builder-round-trip}", fu_firmware_builder_round_trip_func); g_test_add_func("/fwupd/firmware{fmap}", fu_firmware_fmap_func); g_test_add_func("/fwupd/firmware{gtypes}", fu_firmware_new_from_gtypes_func); g_test_add_func("/fwupd/archive{invalid}", fu_archive_invalid_func); g_test_add_func("/fwupd/archive{cab}", fu_archive_cab_func); g_test_add_func("/fwupd/device", fu_device_func); g_test_add_func("/fwupd/device{instance-ids}", fu_device_instance_ids_func); g_test_add_func("/fwupd/device{composite-id}", fu_device_composite_id_func); g_test_add_func("/fwupd/device{flags}", fu_device_flags_func); g_test_add_func("/fwupd/device{private-flags}", fu_device_private_flags_func); g_test_add_func("/fwupd/device{inhibit}", fu_device_inhibit_func); g_test_add_func("/fwupd/device{inhibit-updateable}", fu_device_inhibit_updateable_func); g_test_add_func("/fwupd/device{parent}", fu_device_parent_func); g_test_add_func("/fwupd/device{children}", fu_device_children_func); g_test_add_func("/fwupd/device{incorporate}", fu_device_incorporate_func); if (g_test_slow()) g_test_add_func("/fwupd/device{poll}", fu_device_poll_func); g_test_add_func("/fwupd/device-locker{success}", fu_device_locker_func); g_test_add_func("/fwupd/device-locker{fail}", fu_device_locker_fail_func); g_test_add_func("/fwupd/device{name}", fu_device_name_func); g_test_add_func("/fwupd/device{metadata}", fu_device_metadata_func); g_test_add_func("/fwupd/device{open-refcount}", fu_device_open_refcount_func); g_test_add_func("/fwupd/device{version-format}", fu_device_version_format_func); g_test_add_func("/fwupd/device{retry-success}", fu_device_retry_success_func); g_test_add_func("/fwupd/device{retry-failed}", fu_device_retry_failed_func); g_test_add_func("/fwupd/device{retry-hardware}", fu_device_retry_hardware_func); g_test_add_func("/fwupd/device{cfi-device}", fu_device_cfi_device_func); g_test_add_func("/fwupd/device{progress}", fu_plugin_device_progress_func); return g_test_run(); } fwupd-1.9.16/libfwupdplugin/fu-self-test.rs000066400000000000000000000011771460375044200206670ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[repr(u8)] enum SelfTestRevision { None = 0x0, All = 0xF_F, } #[derive(New, Validate, Parse, ToString)] struct SelfTest { signature: u32be == 0x1234_5678, length: u32le = $struct_size, // bytes revision: SelfTestRevision, owner: Guid, oem_id: [char; 6] == "ABCDEF", oem_table_id: [char; 8], oem_revision: u32le, asl_compiler_id: [u8; 4] = 0xDF, asl_compiler_revision: u32le, } #[derive(New, Validate, Parse, ToString)] struct SelfTestWrapped { less: u8, base: SelfTest, more: u8, } fwupd-1.9.16/libfwupdplugin/fu-smbios-private.h000066400000000000000000000010371460375044200215230ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-smbios.h" gboolean fu_smbios_setup(FuSmbios *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_smbios_setup_from_path(FuSmbios *self, const gchar *path, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); gboolean fu_smbios_setup_from_file(FuSmbios *self, const gchar *filename, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-smbios.c000066400000000000000000000406431460375044200200540ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuSmbios" #include "config.h" #include #include #ifdef _WIN32 #include #include #endif #include "fwupd-error.h" #include "fu-byte-array.h" #include "fu-common.h" #include "fu-path.h" #include "fu-smbios-private.h" #include "fu-smbios-struct.h" #include "fu-string.h" /** * FuSmbios: * * Enumerate the SMBIOS data on the system. * * See also: [class@FuHwids] */ struct _FuSmbios { FuFirmware parent_instance; guint32 structure_table_len; GPtrArray *items; }; typedef struct { guint8 type; guint16 handle; GByteArray *buf; GPtrArray *strings; } FuSmbiosItem; G_DEFINE_TYPE(FuSmbios, fu_smbios, FU_TYPE_FIRMWARE) static FuSmbiosItem * fu_smbios_get_item_for_type(FuSmbios *self, guint8 type) { for (guint i = 0; i < self->items->len; i++) { FuSmbiosItem *item = g_ptr_array_index(self->items, i); if (item->type == type) return item; } return NULL; } static gboolean fu_smbios_setup_from_data(FuSmbios *self, const guint8 *buf, gsize bufsz, GError **error) { /* go through each structure */ for (gsize i = 0; i < bufsz; i++) { FuSmbiosItem *item; guint8 length; g_autoptr(GByteArray) st_str = NULL; /* sanity check */ st_str = fu_struct_smbios_structure_parse(buf, bufsz, i, error); if (st_str == NULL) return FALSE; length = fu_struct_smbios_structure_get_length(st_str); if (length < st_str->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "structure smaller than allowed @0x%x", (guint)i); return FALSE; } if (i + length >= bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "structure larger than available data @0x%x", (guint)i); return FALSE; } /* create a new result */ item = g_new0(FuSmbiosItem, 1); item->type = fu_struct_smbios_structure_get_type(st_str); item->handle = fu_struct_smbios_structure_get_handle(st_str); item->buf = g_byte_array_sized_new(length); item->strings = g_ptr_array_new_with_free_func(g_free); g_byte_array_append(item->buf, buf + i, length); g_ptr_array_add(self->items, item); /* jump to the end of the formatted area of the struct */ i += length; /* add strings from table */ while (i < bufsz) { GString *str; /* end of string section */ if (item->strings->len > 0 && buf[i] == 0x0) break; /* copy into string table */ str = fu_strdup((const gchar *)buf, bufsz, i); i += str->len + 1; g_ptr_array_add(item->strings, g_string_free(str, FALSE)); } } /* this has to exist */ if (fu_smbios_get_item_for_type(self, FU_SMBIOS_STRUCTURE_TYPE_SYSTEM) == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no structure with required type SYSTEM"); return FALSE; } /* success */ return TRUE; } /** * fu_smbios_setup_from_file: * @self: a #FuSmbios * @filename: a filename * @error: (nullable): optional return location for an error * * Reads all the SMBIOS values from a DMI blob. * * Returns: %TRUE for success * * Since: 1.0.0 **/ gboolean fu_smbios_setup_from_file(FuSmbios *self, const gchar *filename, GError **error) { gsize sz = 0; g_autofree gchar *buf = NULL; g_return_val_if_fail(FU_IS_SMBIOS(self), FALSE); g_return_val_if_fail(filename != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* DMI blob */ if (!g_file_get_contents(filename, &buf, &sz, error)) return FALSE; return fu_smbios_setup_from_data(self, (guint8 *)buf, sz, error); } static gboolean fu_smbios_parse_ep32(FuSmbios *self, const guint8 *buf, gsize bufsz, GError **error) { guint8 csum = 0; g_autofree gchar *version_str = NULL; g_autofree gchar *intermediate_anchor_str = NULL; g_autoptr(GByteArray) st_ep32 = NULL; /* verify checksum */ st_ep32 = fu_struct_smbios_ep32_parse(buf, bufsz, 0x0, error); if (st_ep32 == NULL) return FALSE; for (guint i = 0; i < bufsz; i++) csum += buf[i]; if (csum != 0x00) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "entry point checksum invalid"); return FALSE; } /* verify intermediate section */ intermediate_anchor_str = fu_struct_smbios_ep32_get_intermediate_anchor_str(st_ep32); if (g_strcmp0(intermediate_anchor_str, "_DMI_") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "intermediate anchor signature invalid, got %s", intermediate_anchor_str); return FALSE; } for (guint i = 10; i < bufsz; i++) csum += buf[i]; if (csum != 0x00) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "intermediate checksum invalid"); return FALSE; } self->structure_table_len = fu_struct_smbios_ep32_get_structure_table_len(st_ep32); version_str = g_strdup_printf("%u.%u", fu_struct_smbios_ep32_get_smbios_major_ver(st_ep32), fu_struct_smbios_ep32_get_smbios_minor_ver(st_ep32)); fu_firmware_set_version(FU_FIRMWARE(self), version_str); fu_firmware_set_version_raw( FU_FIRMWARE(self), (((guint16)fu_struct_smbios_ep32_get_smbios_major_ver(st_ep32)) << 8) + fu_struct_smbios_ep32_get_smbios_minor_ver(st_ep32)); return TRUE; } static gboolean fu_smbios_parse_ep64(FuSmbios *self, const guint8 *buf, gsize bufsz, GError **error) { guint8 csum = 0; g_autofree gchar *version_str = NULL; g_autoptr(GByteArray) st_ep64 = NULL; /* verify checksum */ st_ep64 = fu_struct_smbios_ep64_parse(buf, bufsz, 0x0, error); if (st_ep64 == NULL) return FALSE; for (guint i = 0; i < bufsz; i++) csum += buf[i]; if (csum != 0x00) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "entry point checksum invalid"); return FALSE; } self->structure_table_len = fu_struct_smbios_ep64_get_structure_table_len(st_ep64); version_str = g_strdup_printf("%u.%u", fu_struct_smbios_ep64_get_smbios_major_ver(st_ep64), fu_struct_smbios_ep64_get_smbios_minor_ver(st_ep64)); fu_firmware_set_version(FU_FIRMWARE(self), version_str); return TRUE; } /** * fu_smbios_setup_from_path: * @self: a #FuSmbios * @path: a path, e.g. `/sys/firmware/dmi/tables` * @error: (nullable): optional return location for an error * * Reads all the SMBIOS values from a specific path. * * Returns: %TRUE for success * * Since: 1.0.0 **/ gboolean fu_smbios_setup_from_path(FuSmbios *self, const gchar *path, GError **error) { gsize sz = 0; g_autofree gchar *dmi_fn = NULL; g_autofree gchar *dmi_raw = NULL; g_autofree gchar *ep_fn = NULL; g_autofree gchar *ep_raw = NULL; g_return_val_if_fail(FU_IS_SMBIOS(self), FALSE); g_return_val_if_fail(path != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* get the smbios entry point */ ep_fn = g_build_filename(path, "smbios_entry_point", NULL); if (!g_file_get_contents(ep_fn, &ep_raw, &sz, error)) return FALSE; /* check we got enough data to read the signature */ if (sz < 5) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid smbios entry point got 0x%x bytes, expected 0x%x or 0x%x", (guint)sz, (guint)FU_STRUCT_SMBIOS_EP32_SIZE, (guint)FU_STRUCT_SMBIOS_EP64_SIZE); return FALSE; } /* parse 32 bit structure */ if (memcmp(ep_raw, "_SM_", 4) == 0) { if (!fu_smbios_parse_ep32(self, (const guint8 *)ep_raw, sz, error)) return FALSE; } else if (memcmp(ep_raw, "_SM3_", 5) == 0) { if (!fu_smbios_parse_ep64(self, (const guint8 *)ep_raw, sz, error)) return FALSE; } else { g_autofree gchar *tmp = g_strndup(ep_raw, 4); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "SMBIOS signature invalid, got %s", tmp); return FALSE; } /* get the DMI data */ dmi_fn = g_build_filename(path, "DMI", NULL); if (!g_file_get_contents(dmi_fn, &dmi_raw, &sz, error)) return FALSE; if (sz > self->structure_table_len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid DMI data size, got %" G_GSIZE_FORMAT " bytes, expected %" G_GUINT32_FORMAT, sz, self->structure_table_len); return FALSE; } /* parse blob */ return fu_smbios_setup_from_data(self, (guint8 *)dmi_raw, sz, error); } static gboolean fu_smbios_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuSmbios *self = FU_SMBIOS(firmware); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); return fu_smbios_setup_from_data(self, buf, bufsz, error); } #ifdef _WIN32 #define FU_SMBIOS_FT_SIG_ACPI 0x41435049 #define FU_SMBIOS_FT_SIG_FIRM 0x4649524D #define FU_SMBIOS_FT_SIG_RSMB 0x52534D42 #define FU_SMBIOS_FT_RAW_OFFSET 0x08 #endif /** * fu_smbios_setup: * @self: a #FuSmbios * @error: (nullable): optional return location for an error * * Reads all the SMBIOS values from the hardware. * * Returns: %TRUE for success * * Since: 1.0.0 **/ gboolean fu_smbios_setup(FuSmbios *self, GError **error) { #ifdef _WIN32 gsize bufsz; guint rc; g_autofree guint8 *buf = NULL; rc = GetSystemFirmwareTable(FU_SMBIOS_FT_SIG_RSMB, 0, 0, 0); if (rc <= 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to access RSMB [%u]", (guint)GetLastError()); return FALSE; } if (rc < FU_SMBIOS_FT_RAW_OFFSET || rc > 0x1000000) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "RSMB impossible size"); return FALSE; } bufsz = rc; buf = g_malloc0(bufsz); rc = GetSystemFirmwareTable(FU_SMBIOS_FT_SIG_RSMB, 0, buf, (DWORD)bufsz); if (rc <= 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to read RSMB [%u]", (guint)GetLastError()); return FALSE; } return fu_smbios_setup_from_data(self, buf + FU_SMBIOS_FT_RAW_OFFSET, bufsz - FU_SMBIOS_FT_RAW_OFFSET, error); #else g_autofree gchar *path = NULL; g_autofree gchar *sysfsfwdir = NULL; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_SMBIOS(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* DMI */ sysfsfwdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_FW); path = g_build_filename(sysfsfwdir, "dmi", "tables", NULL); if (!g_file_test(path, G_FILE_TEST_EXISTS)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "SMBIOS tables not found at %s", path); return FALSE; } if (!fu_smbios_setup_from_path(self, path, &error_local)) { if (!g_error_matches(error_local, G_FILE_ERROR, G_FILE_ERROR_ACCES)) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } g_debug("ignoring %s", error_local->message); } /* success */ return TRUE; #endif } static void fu_smbios_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuSmbios *self = FU_SMBIOS(firmware); for (guint i = 0; i < self->items->len; i++) { FuSmbiosItem *item = g_ptr_array_index(self->items, i); g_autoptr(XbBuilderNode) bc = xb_builder_node_insert(bn, "item", NULL); fu_xmlb_builder_insert_kx(bc, "type", item->type); fu_xmlb_builder_insert_kx(bc, "length", item->buf->len); fu_xmlb_builder_insert_kx(bc, "handle", item->handle); for (guint j = 0; j < item->strings->len; j++) { const gchar *tmp = g_ptr_array_index(item->strings, j); g_autofree gchar *title = g_strdup_printf("%02u", j); g_autofree gchar *value = fu_strsafe(tmp, 20); xb_builder_node_insert_text(bc, "string", value, "idx", title, NULL); } } } /** * fu_smbios_get_data: * @self: a #FuSmbios * @type: a structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS * @error: (nullable): optional return location for an error * * Reads all the SMBIOS data blobs of a specified type. * * Returns: (transfer container) (element-type GBytes): a #GBytes, or %NULL if invalid or not found * * Since: 1.9.8 **/ GPtrArray * fu_smbios_get_data(FuSmbios *self, guint8 type, GError **error) { g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); g_return_val_if_fail(FU_IS_SMBIOS(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); for (guint i = 0; i < self->items->len; i++) { FuSmbiosItem *item = g_ptr_array_index(self->items, i); if (item->type == type && item->buf->len > 0) g_ptr_array_add(array, g_bytes_new(item->buf->data, item->buf->len)); } if (array->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no structures with type %02x", type); return NULL; } return g_steal_pointer(&array); } /** * fu_smbios_get_integer: * @self: a #FuSmbios * @type: a structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS * @offset: a structure offset * @error: (nullable): optional return location for an error * * Reads an integer value from the SMBIOS string table of a specific structure. * * The @type and @offset can be referenced from the DMTF SMBIOS specification: * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf * * Returns: an integer, or %G_MAXUINT if invalid or not found * * Since: 1.5.0 **/ guint fu_smbios_get_integer(FuSmbios *self, guint8 type, guint8 offset, GError **error) { FuSmbiosItem *item; g_return_val_if_fail(FU_IS_SMBIOS(self), 0); g_return_val_if_fail(error == NULL || *error == NULL, 0); /* get item */ item = fu_smbios_get_item_for_type(self, type); if (item == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no structure with type %02x", type); return G_MAXUINT; } /* check offset valid */ if (offset >= item->buf->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "offset bigger than size %u", item->buf->len); return G_MAXUINT; } /* success */ return item->buf->data[offset]; } /** * fu_smbios_get_string: * @self: a #FuSmbios * @type: a structure type, e.g. %FU_SMBIOS_STRUCTURE_TYPE_BIOS * @offset: a structure offset * @error: (nullable): optional return location for an error * * Reads a string from the SMBIOS string table of a specific structure. * * The @type and @offset can be referenced from the DMTF SMBIOS specification: * https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.1.1.pdf * * Returns: a string, or %NULL if invalid or not found * * Since: 1.0.0 **/ const gchar * fu_smbios_get_string(FuSmbios *self, guint8 type, guint8 offset, GError **error) { FuSmbiosItem *item; g_return_val_if_fail(FU_IS_SMBIOS(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* get item */ item = fu_smbios_get_item_for_type(self, type); if (item == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no structure with type %02x", type); return NULL; } /* check offset valid */ if (offset >= item->buf->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "offset bigger than size %u", item->buf->len); return NULL; } if (item->buf->data[offset] == 0x00) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no data available"); return NULL; } /* check string index valid */ if (item->buf->data[offset] > item->strings->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "index larger than string table %u", item->strings->len); return NULL; } return g_ptr_array_index(item->strings, item->buf->data[offset] - 1); } static void fu_smbios_item_free(FuSmbiosItem *item) { g_byte_array_unref(item->buf); g_ptr_array_unref(item->strings); g_free(item); } static void fu_smbios_finalize(GObject *object) { FuSmbios *self = FU_SMBIOS(object); g_ptr_array_unref(self->items); G_OBJECT_CLASS(fu_smbios_parent_class)->finalize(object); } static void fu_smbios_class_init(FuSmbiosClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_smbios_finalize; klass_firmware->parse = fu_smbios_parse; klass_firmware->export = fu_smbios_export; } static void fu_smbios_init(FuSmbios *self) { self->items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_smbios_item_free); } /** * fu_smbios_new: * * Creates a new object to parse SMBIOS data. * * Returns: a #FuSmbios * * Since: 1.0.0 **/ FuSmbios * fu_smbios_new(void) { FuSmbios *self; self = g_object_new(FU_TYPE_SMBIOS, NULL); return FU_SMBIOS(self); } fwupd-1.9.16/libfwupdplugin/fu-smbios.h000066400000000000000000000057771460375044200200720ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_SMBIOS (fu_smbios_get_type()) G_DECLARE_FINAL_TYPE(FuSmbios, fu_smbios, FU, SMBIOS, FuFirmware) FuSmbios * fu_smbios_new(void); /** * FU_SMBIOS_STRUCTURE_TYPE_BIOS: * * The SMBIOS structure type for the BIOS. * * Since: 1.5.5 **/ #define FU_SMBIOS_STRUCTURE_TYPE_BIOS 0x00 /** * FU_SMBIOS_STRUCTURE_TYPE_SYSTEM: * * The SMBIOS structure type for the system as a whole. * * Since: 1.5.5 **/ #define FU_SMBIOS_STRUCTURE_TYPE_SYSTEM 0x01 /** * FU_SMBIOS_STRUCTURE_TYPE_BASEBOARD: * * The SMBIOS structure type for the baseboard (motherboard). * * Since: 1.5.5 **/ #define FU_SMBIOS_STRUCTURE_TYPE_BASEBOARD 0x02 /** * FU_SMBIOS_STRUCTURE_TYPE_CHASSIS: * * The SMBIOS structure type for the chassis. * * Since: 1.5.5 **/ #define FU_SMBIOS_STRUCTURE_TYPE_CHASSIS 0x03 /** * FU_SMBIOS_STRUCTURE_TYPE_LAST: * * The last possible SMBIOS structure type. * * Since: 1.5.5 **/ #define FU_SMBIOS_STRUCTURE_TYPE_LAST 0x04 /** * FuSmbiosChassisKind: * * The system chassis kind. **/ typedef enum { FU_SMBIOS_CHASSIS_KIND_OTHER = 0x01, FU_SMBIOS_CHASSIS_KIND_UNKNOWN = 0x02, FU_SMBIOS_CHASSIS_KIND_DESKTOP = 0x03, FU_SMBIOS_CHASSIS_KIND_LOW_PROFILE_DESKTOP = 0x04, FU_SMBIOS_CHASSIS_KIND_PIZZA_BOX = 0x05, FU_SMBIOS_CHASSIS_KIND_MINI_TOWER = 0x06, FU_SMBIOS_CHASSIS_KIND_TOWER = 0x07, FU_SMBIOS_CHASSIS_KIND_PORTABLE = 0x08, FU_SMBIOS_CHASSIS_KIND_LAPTOP = 0x09, FU_SMBIOS_CHASSIS_KIND_NOTEBOOK = 0x0A, FU_SMBIOS_CHASSIS_KIND_HAND_HELD = 0x0B, FU_SMBIOS_CHASSIS_KIND_DOCKING_STATION = 0x0C, FU_SMBIOS_CHASSIS_KIND_ALL_IN_ONE = 0x0D, FU_SMBIOS_CHASSIS_KIND_SUB_NOTEBOOK = 0x0E, FU_SMBIOS_CHASSIS_KIND_SPACE_SAVING = 0x0F, FU_SMBIOS_CHASSIS_KIND_LUNCH_BOX = 0x10, FU_SMBIOS_CHASSIS_KIND_MAIN_SERVER = 0x11, FU_SMBIOS_CHASSIS_KIND_EXPANSION = 0x12, FU_SMBIOS_CHASSIS_KIND_SUBCHASSIS = 0x13, FU_SMBIOS_CHASSIS_KIND_BUS_EXPANSION = 0x14, FU_SMBIOS_CHASSIS_KIND_PERIPHERAL = 0x15, FU_SMBIOS_CHASSIS_KIND_RAID = 0x16, FU_SMBIOS_CHASSIS_KIND_RACK_MOUNT = 0x17, FU_SMBIOS_CHASSIS_KIND_SEALED_CASE_PC = 0x18, FU_SMBIOS_CHASSIS_KIND_MULTI_SYSTEM = 0x19, FU_SMBIOS_CHASSIS_KIND_COMPACT_PCI = 0x1A, FU_SMBIOS_CHASSIS_KIND_ADVANCED_TCA = 0x1B, FU_SMBIOS_CHASSIS_KIND_BLADE = 0x1C, FU_SMBIOS_CHASSIS_KIND_TABLET = 0x1E, FU_SMBIOS_CHASSIS_KIND_CONVERTIBLE = 0x1F, FU_SMBIOS_CHASSIS_KIND_DETACHABLE = 0x20, FU_SMBIOS_CHASSIS_KIND_IOT_GATEWAY = 0x21, FU_SMBIOS_CHASSIS_KIND_EMBEDDED_PC = 0x22, FU_SMBIOS_CHASSIS_KIND_MINI_PC = 0x23, FU_SMBIOS_CHASSIS_KIND_STICK_PC = 0x24, /*< private >*/ FU_SMBIOS_CHASSIS_KIND_LAST, } FuSmbiosChassisKind; const gchar * fu_smbios_get_string(FuSmbios *self, guint8 type, guint8 offset, GError **error) G_GNUC_NON_NULL(1); guint fu_smbios_get_integer(FuSmbios *self, guint8 type, guint8 offset, GError **error) G_GNUC_NON_NULL(1); GPtrArray * fu_smbios_get_data(FuSmbios *self, guint8 type, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-smbios.rs000066400000000000000000000016661460375044200202600ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, Parse)] struct SmbiosEp32 { anchor_str: [char; 4], entry_point_csum: u8, entry_point_len: u8, smbios_major_ver: u8, smbios_minor_ver: u8, max_structure_sz: u16le, entry_point_rev: u8, _formatted_area: [u8; 5], intermediate_anchor_str: [char; 5], intermediate_csum: u8, structure_table_len: u16le, structure_table_addr: u32le, number_smbios_structs: u16le, smbios_bcd_rev: u8, } #[derive(New, Parse)] struct SmbiosEp64 { anchor_str: [char; 5], entry_point_csum: u8, entry_point_len: u8, smbios_major_ver: u8, smbios_minor_ver: u8, smbios_docrev: u8, entry_point_rev: u8, reserved0: u8, structure_table_len: u32le, structure_table_addr: u64le, } #[derive(New, Parse)] struct SmbiosStructure { type: u8, length: u8, handle: u16le, } fwupd-1.9.16/libfwupdplugin/fu-srec-firmware.c000066400000000000000000000442731460375044200213310ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include #include "fu-byte-array.h" #include "fu-chunk-array.h" #include "fu-common.h" #include "fu-firmware-common.h" #include "fu-srec-firmware.h" #include "fu-string.h" /** * FuSrecFirmware: * * A SREC firmware image. * * See also: [class@FuFirmware] */ typedef struct { GPtrArray *records; guint32 addr_min; guint32 addr_max; } FuSrecFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuSrecFirmware, fu_srec_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_srec_firmware_get_instance_private(o)) #define FU_SREC_FIRMWARE_TOKENS_MAX 100000 /* lines */ /** * fu_srec_firmware_get_records: * @self: A #FuSrecFirmware * * Returns the raw records from SREC tokenization. * * This might be useful if the plugin is expecting the SREC file to be a list * of operations, rather than a simple linear image with filled holes. * * Returns: (transfer none) (element-type FuSrecFirmwareRecord): records * * Since: 1.3.2 **/ GPtrArray * fu_srec_firmware_get_records(FuSrecFirmware *self) { FuSrecFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_SREC_FIRMWARE(self), NULL); return priv->records; } /** * fu_srec_firmware_set_addr_min: * @self: A #FuSrecFirmware * @addr_min: address, or 0x0 to disable * * Sets the minimum address allowed. This may be useful to ignore a bootloader section. * * Since: 1.9.3 **/ void fu_srec_firmware_set_addr_min(FuSrecFirmware *self, guint32 addr_min) { FuSrecFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_SREC_FIRMWARE(self)); priv->addr_min = addr_min; } /** * fu_srec_firmware_set_addr_max: * @self: A #FuSrecFirmware * @addr_max: address, or 0x0 to disable * * Sets the maximum address allowed. This may be useful to ignore a signature. * * Since: 1.9.3 **/ void fu_srec_firmware_set_addr_max(FuSrecFirmware *self, guint32 addr_max) { FuSrecFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_SREC_FIRMWARE(self)); priv->addr_max = addr_max; } static void fu_srec_firmware_record_free(FuSrecFirmwareRecord *rcd) { g_byte_array_unref(rcd->buf); g_free(rcd); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuSrecFirmwareRecord, fu_srec_firmware_record_free); #pragma clang diagnostic pop /** * fu_srec_firmware_record_new: (skip): * @ln: unsigned integer * @kind: a record kind, e.g. #FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32 * @addr: unsigned integer * * Returns a single firmware record * * Returns: (transfer full): record * * Since: 1.3.2 **/ FuSrecFirmwareRecord * fu_srec_firmware_record_new(guint ln, FuFirmareSrecRecordKind kind, guint32 addr) { FuSrecFirmwareRecord *rcd = g_new0(FuSrecFirmwareRecord, 1); rcd->ln = ln; rcd->kind = kind; rcd->addr = addr; rcd->buf = g_byte_array_new(); return rcd; } static FuSrecFirmwareRecord * fu_srec_firmware_record_dup(const FuSrecFirmwareRecord *rcd) { FuSrecFirmwareRecord *dest; g_return_val_if_fail(rcd != NULL, NULL); dest = fu_srec_firmware_record_new(rcd->ln, rcd->kind, rcd->addr); dest->buf = g_byte_array_ref(rcd->buf); return dest; } /** * fu_srec_firmware_record_get_type: * * Gets a specific type. * * Return value: a #GType * * Since: 1.6.1 **/ GType fu_srec_firmware_record_get_type(void) { static GType type_id = 0; if (!type_id) { type_id = g_boxed_type_register_static("FuSrecFirmwareRecord", (GBoxedCopyFunc)fu_srec_firmware_record_dup, (GBoxedFreeFunc)fu_srec_firmware_record_free); } return type_id; } typedef struct { FuSrecFirmware *self; FwupdInstallFlags flags; gboolean got_eof; } FuSrecFirmwareTokenHelper; static gboolean fu_srec_firmware_tokenize_cb(GString *token, guint token_idx, gpointer user_data, GError **error) { FuSrecFirmwareTokenHelper *helper = (FuSrecFirmwareTokenHelper *)user_data; FuSrecFirmwarePrivate *priv = GET_PRIVATE(helper->self); g_autoptr(FuSrecFirmwareRecord) rcd = NULL; gboolean require_data = FALSE; guint32 rec_addr32; guint16 rec_addr16; guint8 addrsz = 0; /* bytes */ guint8 rec_count; /* words */ guint8 rec_kind; /* sanity check */ if (token_idx > FU_SREC_FIRMWARE_TOKENS_MAX) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "file has too many lines"); return FALSE; } /* remove WIN32 line endings */ g_strdelimit(token->str, "\r\x1a", '\0'); token->len = strlen(token->str); /* ignore blank lines */ if (token->len == 0) return TRUE; /* check starting token */ if (token->str[0] != 'S' || token->len < 3) { g_autofree gchar *strsafe = fu_strsafe(token->str, 3); if (strsafe != NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid starting token, got '%s' at line %u", strsafe, token_idx + 1); return FALSE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid starting token at line %u", token_idx + 1); return FALSE; } /* kind, count, address, (data), checksum, linefeed */ rec_kind = token->str[1] - '0'; if (!fu_firmware_strparse_uint8_safe(token->str, token->len, 2, &rec_count, error)) return FALSE; if (rec_count * 2 != token->len - 4) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "count incomplete at line %u, " "length %u, expected %u", token_idx + 1, (guint)token->len - 4, (guint)rec_count * 2); return FALSE; } /* checksum check */ if ((helper->flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint8 rec_csum = 0; guint8 rec_csum_expected; for (guint8 i = 0; i < rec_count; i++) { guint8 csum_tmp = 0; if (!fu_firmware_strparse_uint8_safe(token->str, token->len, (i * 2) + 2, &csum_tmp, error)) return FALSE; rec_csum += csum_tmp; } rec_csum ^= 0xff; if (!fu_firmware_strparse_uint8_safe(token->str, token->len, (rec_count * 2) + 2, &rec_csum_expected, error)) return FALSE; if (rec_csum != rec_csum_expected) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum incorrect line %u, " "expected %02x, got %02x", token_idx + 1, rec_csum_expected, rec_csum); return FALSE; } } /* set each command settings */ switch (rec_kind) { case FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER: case FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16: addrsz = 2; require_data = TRUE; break; case FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24: addrsz = 3; require_data = TRUE; break; case FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32: addrsz = 4; require_data = TRUE; break; case FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16: addrsz = 2; helper->got_eof = TRUE; break; case FU_FIRMWARE_SREC_RECORD_KIND_S6_COUNT_24: addrsz = 3; break; case FU_FIRMWARE_SREC_RECORD_KIND_S7_COUNT_32: addrsz = 4; helper->got_eof = TRUE; break; case FU_FIRMWARE_SREC_RECORD_KIND_S8_TERMINATION_24: addrsz = 3; helper->got_eof = TRUE; break; case FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16: addrsz = 2; helper->got_eof = TRUE; break; default: g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid srec record type S%c at line %u", token->str[1], token_idx + 1); return FALSE; } /* parse address */ switch (addrsz) { case 2: if (!fu_firmware_strparse_uint16_safe(token->str, token->len, 4, &rec_addr16, error)) return FALSE; rec_addr32 = rec_addr16; break; case 3: if (!fu_firmware_strparse_uint24_safe(token->str, token->len, 4, &rec_addr32, error)) return FALSE; break; case 4: if (!fu_firmware_strparse_uint32_safe(token->str, token->len, 4, &rec_addr32, error)) return FALSE; break; default: g_assert_not_reached(); } g_debug("line %03u S%u addr:0x%04x datalen:0x%02x", token_idx + 1, rec_kind, rec_addr32, (guint)rec_count - addrsz - 1); if (require_data && rec_count == addrsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "S%u required data but not provided", rec_kind); return FALSE; } /* data */ rcd = fu_srec_firmware_record_new(token_idx + 1, rec_kind, rec_addr32); if (rec_kind == 1 || rec_kind == 2 || rec_kind == 3) { for (gsize i = 4 + (addrsz * 2); i <= rec_count * 2; i += 2) { guint8 tmp = 0; if (!fu_firmware_strparse_uint8_safe(token->str, token->len, i, &tmp, error)) return FALSE; fu_byte_array_append_uint8(rcd->buf, tmp); } } g_ptr_array_add(priv->records, g_steal_pointer(&rcd)); return TRUE; } static gboolean fu_srec_firmware_tokenize(FuFirmware *firmware, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSrecFirmware *self = FU_SREC_FIRMWARE(firmware); FuSrecFirmwareTokenHelper helper = {.self = self, .flags = flags, .got_eof = FALSE}; /* parse records */ if (!fu_strsplit_full(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), "\n", fu_srec_firmware_tokenize_cb, &helper, error)) return FALSE; /* no EOF */ if (!helper.got_eof) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no EOF, perhaps truncated file"); return FALSE; } return TRUE; } static gboolean fu_srec_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuSrecFirmware *self = FU_SREC_FIRMWARE(firmware); FuSrecFirmwarePrivate *priv = GET_PRIVATE(self); gboolean got_hdr = FALSE; guint16 data_cnt = 0; guint32 addr32_last = 0; guint32 img_address = 0; g_autoptr(GBytes) img_bytes = NULL; g_autoptr(GByteArray) outbuf = g_byte_array_new(); /* parse records */ for (guint j = 0; j < priv->records->len; j++) { FuSrecFirmwareRecord *rcd = g_ptr_array_index(priv->records, j); /* header */ if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER) { g_autoptr(GString) modname = g_string_new(NULL); /* check for duplicate */ if (got_hdr) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "duplicate header record at line %u", rcd->ln); return FALSE; } /* could be anything, lets assume text */ for (guint i = 0; i < rcd->buf->len; i++) { gchar tmp = rcd->buf->data[i]; if (!g_ascii_isgraph(tmp)) break; g_string_append_c(modname, tmp); } if (modname->len != 0) fu_firmware_set_id(firmware, modname->str); got_hdr = TRUE; continue; } /* verify we got all records */ if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16) { if (rcd->addr != data_cnt) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "count record was not valid, got 0x%02x expected " "0x%02x at line %u", (guint)rcd->addr, (guint)data_cnt, rcd->ln); return FALSE; } continue; } /* data */ if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16 || rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24 || rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32) { /* invalid */ if (!got_hdr) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "missing header record at line %u", rcd->ln); return FALSE; } /* does not make sense */ if (rcd->addr < addr32_last) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid address 0x%x, last was 0x%x at line %u", (guint)rcd->addr, (guint)addr32_last, rcd->ln); return FALSE; } if (rcd->addr < priv->addr_min) { g_debug( "ignoring data at 0x%x as before start address 0x%x at line %u", (guint)rcd->addr, priv->addr_min, rcd->ln); } else if (priv->addr_max > 0 && rcd->addr < priv->addr_max) { g_debug( "ignoring data at 0x%x as after end address 0x%x at line %u", (guint)rcd->addr, priv->addr_max, rcd->ln); } else { guint32 len_hole = rcd->addr - addr32_last; /* fill any holes, but only up to 1Mb to avoid a DoS */ if (addr32_last > 0 && len_hole > 0x100000) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "hole of 0x%x bytes too large to fill at line %u", (guint)len_hole, rcd->ln); return FALSE; } if (addr32_last > 0x0 && len_hole > 1) { g_debug("filling address 0x%08x to 0x%08x at line %u", addr32_last + 1, addr32_last + len_hole - 1, rcd->ln); for (guint i = 0; i < len_hole; i++) fu_byte_array_append_uint8(outbuf, 0xff); } /* add data */ g_byte_array_append(outbuf, rcd->buf->data, rcd->buf->len); if (img_address == 0x0) img_address = rcd->addr; addr32_last = rcd->addr + rcd->buf->len; if (addr32_last < rcd->addr) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "overflow from address 0x%x at line %u", (guint)rcd->addr, rcd->ln); return FALSE; } } data_cnt++; } } /* add single image */ img_bytes = g_bytes_new(outbuf->data, outbuf->len); fu_firmware_set_bytes(firmware, img_bytes); fu_firmware_set_addr(firmware, img_address); return TRUE; } static void fu_srec_firmware_write_line(GString *str, FuFirmareSrecRecordKind kind, guint32 addr, const guint8 *buf, gsize bufsz) { guint8 csum = 0; g_autoptr(GByteArray) buf_addr = g_byte_array_new(); if (kind == FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER || kind == FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16 || kind == FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16 || kind == FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16) { fu_byte_array_append_uint16(buf_addr, addr, G_BIG_ENDIAN); } else if (kind == FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24 || kind == FU_FIRMWARE_SREC_RECORD_KIND_S6_COUNT_24 || kind == FU_FIRMWARE_SREC_RECORD_KIND_S8_TERMINATION_24) { fu_byte_array_append_uint32(buf_addr, addr, G_BIG_ENDIAN); g_byte_array_remove_index(buf_addr, 0); } else if (kind == FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32 || kind == FU_FIRMWARE_SREC_RECORD_KIND_S7_COUNT_32) { fu_byte_array_append_uint32(buf_addr, addr, G_BIG_ENDIAN); } /* bytecount + address + data */ csum = buf_addr->len + bufsz + 1; for (guint i = 0; i < buf_addr->len; i++) csum += buf_addr->data[i]; for (guint i = 0; i < bufsz; i++) csum += buf[i]; csum ^= 0xff; /* output record */ g_string_append_printf(str, "S%X", kind); g_string_append_printf(str, "%02X", (guint)(buf_addr->len + bufsz + 1)); for (guint i = 0; i < buf_addr->len; i++) g_string_append_printf(str, "%02X", buf_addr->data[i]); for (guint i = 0; i < bufsz; i++) g_string_append_printf(str, "%02X", buf[i]); g_string_append_printf(str, "%02X\n", csum); } static GByteArray * fu_srec_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GString) str = g_string_new(NULL); g_autoptr(GBytes) buf_blob = NULL; const gchar *id = fu_firmware_get_id(firmware); gsize id_strlen = id != NULL ? strlen(id) : 0; FuFirmareSrecRecordKind kind_data = FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16; FuFirmareSrecRecordKind kind_coun = FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16; FuFirmareSrecRecordKind kind_term = FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16; /* upgrade to longer addresses? */ if (fu_firmware_get_addr(firmware) >= (1ull << 24)) { kind_data = FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32; kind_term = FU_FIRMWARE_SREC_RECORD_KIND_S7_COUNT_32; /* intentional... */ } else if (fu_firmware_get_addr(firmware) >= (1ull << 16)) { kind_data = FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24; kind_term = FU_FIRMWARE_SREC_RECORD_KIND_S8_TERMINATION_24; } /* main blob */ buf_blob = fu_firmware_get_bytes_with_patches(firmware, error); if (buf_blob == NULL) return NULL; /* header */ fu_srec_firmware_write_line(str, FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER, 0x0, (const guint8 *)id, id_strlen); /* payload */ if (g_bytes_get_size(buf_blob) > 0) { g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(buf_blob, fu_firmware_get_addr(firmware), 64); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); fu_srec_firmware_write_line(str, kind_data, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); } /* upgrade to longer format */ if (fu_chunk_array_length(chunks) > G_MAXUINT16) kind_coun = FU_FIRMWARE_SREC_RECORD_KIND_S6_COUNT_24; fu_srec_firmware_write_line(str, kind_coun, fu_chunk_array_length(chunks), NULL, 0); } /* EOF */ fu_srec_firmware_write_line(str, kind_term, 0x0, NULL, 0); /* success */ g_byte_array_append(buf, (const guint8 *)str->str, str->len); return g_steal_pointer(&buf); } static void fu_srec_firmware_finalize(GObject *object) { FuSrecFirmware *self = FU_SREC_FIRMWARE(object); FuSrecFirmwarePrivate *priv = GET_PRIVATE(self); g_ptr_array_unref(priv->records); G_OBJECT_CLASS(fu_srec_firmware_parent_class)->finalize(object); } static void fu_srec_firmware_init(FuSrecFirmware *self) { FuSrecFirmwarePrivate *priv = GET_PRIVATE(self); priv->records = g_ptr_array_new_with_free_func((GFreeFunc)fu_srec_firmware_record_free); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); } static void fu_srec_firmware_class_init(FuSrecFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_srec_firmware_finalize; klass_firmware->parse = fu_srec_firmware_parse; klass_firmware->tokenize = fu_srec_firmware_tokenize; klass_firmware->write = fu_srec_firmware_write; } /** * fu_srec_firmware_new: * * Creates a new #FuFirmware of type SREC * * Since: 1.3.2 **/ FuFirmware * fu_srec_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_SREC_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-srec-firmware.h000066400000000000000000000043771460375044200213370ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_SREC_FIRMWARE (fu_srec_firmware_get_type()) #define FU_TYPE_SREC_FIRMWARE_RECORD (fu_srec_firmware_record_get_type()) G_DECLARE_DERIVABLE_TYPE(FuSrecFirmware, fu_srec_firmware, FU, SREC_FIRMWARE, FuFirmware) struct _FuSrecFirmwareClass { FuFirmwareClass parent_class; }; /** * FuFirmareSrecRecordKind: * @FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER: Header * @FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16: 16 bit data * @FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24: 24 bit data * @FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32: 32 bit data * @FU_FIRMWARE_SREC_RECORD_KIND_S4_RESERVED: Reserved value * @FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16: 16 bit count * @FU_FIRMWARE_SREC_RECORD_KIND_S6_COUNT_24: 24 bit count * @FU_FIRMWARE_SREC_RECORD_KIND_S7_COUNT_32: 32 bit count * @FU_FIRMWARE_SREC_RECORD_KIND_S8_TERMINATION_24: 24 bit termination * @FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16: 16 bit termination * * The kind of SREC record kind. **/ typedef enum { FU_FIRMWARE_SREC_RECORD_KIND_S0_HEADER, FU_FIRMWARE_SREC_RECORD_KIND_S1_DATA_16, FU_FIRMWARE_SREC_RECORD_KIND_S2_DATA_24, FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32, FU_FIRMWARE_SREC_RECORD_KIND_S4_RESERVED, FU_FIRMWARE_SREC_RECORD_KIND_S5_COUNT_16, FU_FIRMWARE_SREC_RECORD_KIND_S6_COUNT_24, FU_FIRMWARE_SREC_RECORD_KIND_S7_COUNT_32, FU_FIRMWARE_SREC_RECORD_KIND_S8_TERMINATION_24, FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16, /*< private >*/ FU_FIRMWARE_SREC_RECORD_KIND_LAST } FuFirmareSrecRecordKind; /** * FuSrecFirmwareRecord: * * A single SREC record. **/ typedef struct { guint ln; FuFirmareSrecRecordKind kind; guint32 addr; GByteArray *buf; } FuSrecFirmwareRecord; FuFirmware * fu_srec_firmware_new(void); void fu_srec_firmware_set_addr_min(FuSrecFirmware *self, guint32 addr_min) G_GNUC_NON_NULL(1); void fu_srec_firmware_set_addr_max(FuSrecFirmware *self, guint32 addr_max) G_GNUC_NON_NULL(1); GPtrArray * fu_srec_firmware_get_records(FuSrecFirmware *self) G_GNUC_NON_NULL(1); GType fu_srec_firmware_record_get_type(void); FuSrecFirmwareRecord * fu_srec_firmware_record_new(guint ln, FuFirmareSrecRecordKind kind, guint32 addr); fwupd-1.9.16/libfwupdplugin/fu-string.c000066400000000000000000000437641460375044200200750ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include "fu-byte-array.h" #include "fu-mem.h" #include "fu-string.h" /** * fu_strtoull: * @str: a string, e.g. `0x1234` * @value: (out) (nullable): parsed value * @min: minimum acceptable value, typically 0 * @max: maximum acceptable value, typically G_MAXUINT64 * @error: (nullable): optional return location for an error * * Converts a string value to an integer. Values are assumed base 10, unless * prefixed with "0x" where they are parsed as base 16. * * Returns: %TRUE if the value was parsed correctly, or %FALSE for error * * Since: 1.8.2 **/ gboolean fu_strtoull(const gchar *str, guint64 *value, guint64 min, guint64 max, GError **error) { gchar *endptr = NULL; guint64 value_tmp; guint base = 10; /* sanity check */ if (str == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse NULL"); return FALSE; } /* detect hex */ if (g_str_has_prefix(str, "0x")) { str += 2; base = 16; } /* convert */ value_tmp = g_ascii_strtoull(str, &endptr, base); if ((gsize)(endptr - str) != strlen(str) && *endptr != '\n') { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse %s", str); return FALSE; } /* overflow check */ if (value_tmp == G_MAXUINT64) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse %s as caused overflow", str); return FALSE; } /* range check */ if (value_tmp < min) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "value %" G_GUINT64_FORMAT " was below minimum %" G_GUINT64_FORMAT, value_tmp, min); return FALSE; } if (value_tmp > max) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "value %" G_GUINT64_FORMAT " was above maximum %" G_GUINT64_FORMAT, value_tmp, max); return FALSE; } /* success */ if (value != NULL) *value = value_tmp; return TRUE; } /** * fu_strtoll: * @str: a string, e.g. `0x1234`, `-12345` * @value: (out) (nullable): parsed value * @min: minimum acceptable value, typically 0 * @max: maximum acceptable value, typically G_MAXINT64 * @error: (nullable): optional return location for an error * * Converts a string value to an integer. Values are assumed base 10, unless * prefixed with "0x" where they are parsed as base 16. * * Returns: %TRUE if the value was parsed correctly, or %FALSE for error * * Since: 1.9.7 **/ gboolean fu_strtoll(const gchar *str, gint64 *value, gint64 min, gint64 max, GError **error) { gchar *endptr = NULL; gint64 value_tmp; guint base = 10; /* sanity check */ if (str == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse NULL"); return FALSE; } /* detect hex */ if (g_str_has_prefix(str, "0x")) { str += 2; base = 16; } /* convert */ value_tmp = g_ascii_strtoll(str, &endptr, base); if ((gsize)(endptr - str) != strlen(str) && *endptr != '\n') { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse %s", str); return FALSE; } /* overflow check */ if (value_tmp == G_MAXINT64) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse %s as caused overflow", str); return FALSE; } /* range check */ if (value_tmp < min) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "value %" G_GINT64_FORMAT " was below minimum %" G_GINT64_FORMAT, value_tmp, min); return FALSE; } if (value_tmp > max) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "value %" G_GINT64_FORMAT " was above maximum %" G_GINT64_FORMAT, value_tmp, max); return FALSE; } /* success */ if (value != NULL) *value = value_tmp; return TRUE; } /** * fu_strtobool: * @str: a string, e.g. `true` * @value: (out) (nullable): parsed value * @error: (nullable): optional return location for an error * * Converts a string value to a boolean. Only `true` and `false` are accepted values. * * Returns: %TRUE if the value was parsed correctly, or %FALSE for error * * Since: 1.8.2 **/ gboolean fu_strtobool(const gchar *str, gboolean *value, GError **error) { /* sanity check */ if (str == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse NULL"); return FALSE; } /* be super strict */ if (g_strcmp0(str, "true") == 0) { if (value != NULL) *value = TRUE; return TRUE; } if (g_strcmp0(str, "false") == 0) { if (value != NULL) *value = FALSE; return TRUE; } /* invalid */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse %s as boolean, expected true|false", str); return FALSE; } /** * fu_strstrip: * @str: a string, e.g. ` test ` * * Removes leading and trailing whitespace from a constant string. * * Returns: newly allocated string * * Since: 1.8.2 **/ gchar * fu_strstrip(const gchar *str) { guint head = G_MAXUINT; guint tail = 0; g_return_val_if_fail(str != NULL, NULL); /* find first non-space char */ for (guint i = 0; str[i] != '\0'; i++) { if (str[i] != ' ') { head = i; break; } } if (head == G_MAXUINT) return g_strdup(""); /* find last non-space char */ for (guint i = head; str[i] != '\0'; i++) { if (!g_ascii_isspace(str[i])) tail = i; } return g_strndup(str + head, tail - head + 1); } /** * fu_strdup: * @str: a string, e.g. ` test ` * @bufsz: the maximum size of @str * @offset: the offset to start copying from * * Copies a string from a buffer of a specified size up to (but not including) `NUL`. * * Returns: (transfer full): a #GString, possibly of zero size. * * Since: 1.8.11 **/ GString * fu_strdup(const gchar *str, gsize bufsz, gsize offset) { GString *substr; g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(offset < bufsz, NULL); substr = g_string_new(NULL); while (offset < bufsz) { if (str[offset] == '\0') break; g_string_append_c(substr, str[offset++]); } return substr; } /** * fu_strwidth: * @text: the string to operate on * * Returns the width of the string in displayed characters on the console. * * Returns: width of text * * Since: 1.8.2 **/ gsize fu_strwidth(const gchar *text) { const gchar *p = text; gsize width = 0; g_return_val_if_fail(text != NULL, 0); while (*p) { gunichar c = g_utf8_get_char(p); if (g_unichar_iswide(c)) width += 2; else if (!g_unichar_iszerowidth(c)) width += 1; p = g_utf8_next_char(p); } return width; } /** * fu_string_append: * @str: a #GString * @idt: the indent * @key: a string to append * @value: a string to append * * Appends a key and string value to a string * * Since: 1.8.2 */ void fu_string_append(GString *str, guint idt, const gchar *key, const gchar *value) { const guint align = 24; gsize keysz; g_return_if_fail(idt * 2 < align); /* ignore */ if (key == NULL) return; for (gsize i = 0; i < idt; i++) g_string_append(str, " "); if (key[0] != '\0') { g_string_append_printf(str, "%s:", key); keysz = (idt * 2) + fu_strwidth(key) + 1; } else { keysz = idt * 2; } if (value != NULL) { g_auto(GStrv) split = NULL; split = g_strsplit(value, "\n", -1); for (guint i = 0; split[i] != NULL; i++) { if (i == 0) { for (gsize j = keysz; j < align; j++) g_string_append(str, " "); } else { g_string_append(str, "\n"); for (gsize j = 0; j < idt; j++) g_string_append(str, " "); } g_string_append(str, split[i]); } } g_string_append(str, "\n"); } /** * fu_string_append_ku: * @str: a #GString * @idt: the indent * @key: a string to append * @value: guint64 * * Appends a key and unsigned integer to a string * * Since: 1.8.2 */ void fu_string_append_ku(GString *str, guint idt, const gchar *key, guint64 value) { g_autofree gchar *tmp = g_strdup_printf("%" G_GUINT64_FORMAT, value); fu_string_append(str, idt, key, tmp); } /** * fu_string_append_kx: * @str: a #GString * @idt: the indent * @key: a string to append * @value: guint64 * * Appends a key and hex integer to a string * * Since: 1.8.2 */ void fu_string_append_kx(GString *str, guint idt, const gchar *key, guint64 value) { g_autofree gchar *tmp = g_strdup_printf("0x%x", (guint)value); fu_string_append(str, idt, key, tmp); } /** * fu_string_append_kb: * @str: a #GString * @idt: the indent * @key: a string to append * @value: Boolean * * Appends a key and boolean value to a string * * Since: 1.8.2 */ void fu_string_append_kb(GString *str, guint idt, const gchar *key, gboolean value) { fu_string_append(str, idt, key, value ? "true" : "false"); } /** * fu_strsplit: * @str: (not nullable): a string to split * @sz: size of @str, which must be more than 0 * @delimiter: a string which specifies the places at which to split the string * @max_tokens: the maximum number of pieces to split @str into * * Splits a string into a maximum of @max_tokens pieces, using the given * delimiter. If @max_tokens is reached, the remainder of string is appended * to the last token. * * Returns: (transfer full): a newly-allocated NULL-terminated array of strings * * Since: 1.8.2 **/ gchar ** fu_strsplit(const gchar *str, gsize sz, const gchar *delimiter, gint max_tokens) { g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(sz > 0, NULL); if (str[sz - 1] != '\0') { g_autofree gchar *str2 = g_strndup(str, sz); return g_strsplit(str2, delimiter, max_tokens); } return g_strsplit(str, delimiter, max_tokens); } /** * fu_strsplit_full: * @str: a string to split * @sz: size of @str, or -1 for unknown * @delimiter: a string which specifies the places at which to split the string * @callback: (scope call) (closure user_data): a #FuStrsplitFunc. * @user_data: user data * @error: (nullable): optional return location for an error * * Splits the string, calling the given function for each * of the tokens found. If any @callback returns %FALSE scanning is aborted. * * Use this function in preference to fu_strsplit() when the input file is untrusted, * and you don't want to allocate a GStrv with billions of one byte items. * * Returns: %TRUE if no @callback returned FALSE * * Since: 1.8.2 */ gboolean fu_strsplit_full(const gchar *str, gssize sz, const gchar *delimiter, FuStrsplitFunc callback, gpointer user_data, GError **error) { gsize delimiter_sz; gsize str_sz; guint found_idx = 0; guint token_idx = 0; g_return_val_if_fail(str != NULL, FALSE); g_return_val_if_fail(delimiter != NULL && delimiter[0] != '\0', FALSE); g_return_val_if_fail(callback != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* make known */ str_sz = sz != -1 ? (gsize)sz : strlen(str); delimiter_sz = strlen(delimiter); /* cannot split */ if (delimiter_sz > str_sz) { g_autoptr(GString) token = g_string_new(str); return callback(token, token_idx, user_data, error); } /* start splittin' */ for (gsize i = 0; i < (str_sz - delimiter_sz) + 1;) { if (strncmp(str + i, delimiter, delimiter_sz) == 0) { g_autoptr(GString) token = g_string_new(NULL); g_string_append_len(token, str + found_idx, i - found_idx); if (!callback(token, token_idx++, user_data, error)) return FALSE; i += delimiter_sz; found_idx = i; } else { i++; } } /* any bits left over? */ if (found_idx != str_sz) { g_autoptr(GString) token = g_string_new(NULL); g_string_append_len(token, str + found_idx, str_sz - found_idx); if (!callback(token, token_idx, user_data, error)) return FALSE; } /* success */ return TRUE; } /** * fu_strsafe: * @str: (nullable): a string to make safe for printing * @maxsz: maximum size of returned string * * Converts a string into something that can be safely printed. * * Returns: (transfer full): safe string, or %NULL if there was nothing valid * * Since: 1.8.2 **/ gchar * fu_strsafe(const gchar *str, gsize maxsz) { gboolean valid = FALSE; g_autoptr(GString) tmp = NULL; /* sanity check */ if (str == NULL || maxsz == 0) return NULL; /* replace non-printable chars with '.' */ tmp = g_string_sized_new(maxsz); for (gsize i = 0; i < maxsz && str[i] != '\0'; i++) { if (!g_ascii_isprint(str[i])) { g_string_append_c(tmp, '.'); continue; } g_string_append_c(tmp, str[i]); if (!g_ascii_isspace(str[i])) valid = TRUE; } /* if just junk, don't return 'all dots' */ if (tmp->len == 0 || !valid) return NULL; return g_string_free(g_steal_pointer(&tmp), FALSE); } /** * fu_strjoin: * @separator: (nullable): string to insert between each of the strings * @array: (element-type utf8): a #GPtrArray * * Joins an array of strings together to form one long string, with the optional * separator inserted between each of them. * * If @array has no items, the return value will be an empty string. * If @array contains a single item, separator will not appear in the resulting * string. * * Returns: a string * * Since: 1.8.2 **/ gchar * fu_strjoin(const gchar *separator, GPtrArray *array) { g_autofree const gchar **strv = NULL; g_return_val_if_fail(array != NULL, NULL); strv = g_new0(const gchar *, array->len + 1); for (guint i = 0; i < array->len; i++) strv[i] = g_ptr_array_index(array, i); return g_strjoinv(separator, (gchar **)strv); } /** * fu_strpassmask: * @str: (nullable): a string to make safe for printing * * Hides password strings encoded in HTTP requests. * * Returns: a string * * Since: 1.9.10 **/ gchar * fu_strpassmask(const gchar *str) { g_autoptr(GString) tmp = g_string_new(str); if (tmp->str != NULL && g_strstr_len(tmp->str, -1, "@") != NULL && g_strstr_len(tmp->str, -1, ":") != NULL) { gboolean is_password = FALSE; gboolean is_url = FALSE; for (guint i = 0; i < tmp->len; i++) { const gchar *url_prefixes[] = {"http://", "https://", NULL}; for (guint j = 0; url_prefixes[j] != NULL; j++) { if (g_str_has_prefix(tmp->str + i, url_prefixes[j])) { is_url = TRUE; i += strlen(url_prefixes[j]); break; } } if (tmp->str[i] == ' ' || tmp->str[i] == '@' || tmp->str[i] == '/') { is_url = FALSE; is_password = FALSE; continue; } if (is_url && tmp->str[i] == ':') { is_password = TRUE; continue; } if (is_url && is_password) { if (tmp->str[i] == '@') { is_password = FALSE; continue; } tmp->str[i] = 'X'; } } } return g_string_free(g_steal_pointer(&tmp), FALSE); } /** * fu_utf16_to_utf8_byte_array: * @array: a #GByteArray * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @error: (nullable): optional return location for an error * * Converts a UTF-16 buffer to a UTF-8 string. * * Returns: (transfer full): a string, or %NULL on error * * Since: 1.9.3 **/ gchar * fu_utf16_to_utf8_byte_array(GByteArray *array, FuEndianType endian, GError **error) { g_autofree guint16 *buf16 = NULL; g_return_val_if_fail(array != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (array->len % 2 != 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid UTF-16 buffer length"); return NULL; } buf16 = g_new0(guint16, (array->len / sizeof(guint16)) + 1); for (guint i = 0; i < array->len / 2; i++) { guint16 data = fu_memread_uint16(array->data + (i * 2), endian); fu_memwrite_uint16((guint8 *)(buf16 + i), data, G_BYTE_ORDER); } return g_utf16_to_utf8(buf16, array->len / sizeof(guint16), NULL, NULL, error); } /** * fu_utf8_to_utf16_byte_array: * @str: a UTF-8 string * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @flags: a FuUtfConvertFlags, e.g. %FU_UTF_CONVERT_FLAG_APPEND_NUL * @error: (nullable): optional return location for an error * * Converts UTF-8 string to a buffer of UTF-16, optionially including the trailing NULw. * * Returns: (transfer full): a #GByteArray, or %NULL on error * * Since: 1.9.3 **/ GByteArray * fu_utf8_to_utf16_byte_array(const gchar *str, FuEndianType endian, FuUtfConvertFlags flags, GError **error) { glong buf_utf16sz = 0; g_autoptr(GByteArray) array = g_byte_array_new(); g_autofree gunichar2 *buf_utf16 = NULL; g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); buf_utf16 = g_utf8_to_utf16(str, (glong)-1, NULL, &buf_utf16sz, error); if (buf_utf16 == NULL) return NULL; if (flags & FU_UTF_CONVERT_FLAG_APPEND_NUL) buf_utf16sz += 1; for (glong i = 0; i < buf_utf16sz; i++) { guint16 data = fu_memread_uint16((guint8 *)(buf_utf16 + i), G_BYTE_ORDER); fu_byte_array_append_uint16(array, data, endian); } return g_steal_pointer(&array); } /** * fu_utf16_to_utf8_bytes: * @bytes: a #GBytes * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @error: (nullable): optional return location for an error * * Converts a UTF-16 buffer to a UTF-8 string. * * Returns: (transfer full): a string, or %NULL on error * * Since: 1.9.3 **/ gchar * fu_utf16_to_utf8_bytes(GBytes *bytes, FuEndianType endian, GError **error) { GByteArray array = {0x0}; g_return_val_if_fail(bytes != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); array.data = (guint8 *)g_bytes_get_data(bytes, NULL); array.len = g_bytes_get_size(bytes); return fu_utf16_to_utf8_byte_array(&array, endian, error); } /** * fu_utf8_to_utf16_bytes: * @str: a UTF-8 string * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * @error: (nullable): optional return location for an error * * Converts UTF-8 string to a buffer of UTF-16, optionally including the trailing NULw. * * Returns: (transfer full): a #GBytes, or %NULL on error * * Since: 1.9.3 **/ GBytes * fu_utf8_to_utf16_bytes(const gchar *str, FuEndianType endian, FuUtfConvertFlags flags, GError **error) { g_autoptr(GByteArray) buf = NULL; g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); buf = fu_utf8_to_utf16_byte_array(str, endian, flags, error); if (buf == NULL) return NULL; return g_bytes_new(buf->data, buf->len); } fwupd-1.9.16/libfwupdplugin/fu-string.h000066400000000000000000000052211460375044200200640ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-common.h" void fu_string_append(GString *str, guint idt, const gchar *key, const gchar *value) G_GNUC_NON_NULL(1); void fu_string_append_ku(GString *str, guint idt, const gchar *key, guint64 value) G_GNUC_NON_NULL(1); void fu_string_append_kx(GString *str, guint idt, const gchar *key, guint64 value) G_GNUC_NON_NULL(1); void fu_string_append_kb(GString *str, guint idt, const gchar *key, gboolean value) G_GNUC_NON_NULL(1); gchar * fu_strsafe(const gchar *str, gsize maxsz); gchar * fu_strpassmask(const gchar *str) G_GNUC_NON_NULL(1); gboolean fu_strtoull(const gchar *str, guint64 *value, guint64 min, guint64 max, GError **error); gboolean fu_strtoll(const gchar *str, gint64 *value, gint64 min, gint64 max, GError **error); gboolean fu_strtobool(const gchar *str, gboolean *value, GError **error); gchar * fu_strstrip(const gchar *str) G_GNUC_NON_NULL(1); gsize fu_strwidth(const gchar *text) G_GNUC_NON_NULL(1); gchar ** fu_strsplit(const gchar *str, gsize sz, const gchar *delimiter, gint max_tokens) G_GNUC_NON_NULL(1); gchar * fu_strjoin(const gchar *separator, GPtrArray *array) G_GNUC_NON_NULL(1, 2); GString * fu_strdup(const gchar *str, gsize bufsz, gsize offset) G_GNUC_NON_NULL(1); /** * FuStrsplitFunc: * @token: a #GString * @token_idx: the token number * @user_data: (closure): user data * @error: a #GError or NULL * * The fu_strsplit_full() iteration callback. */ typedef gboolean (*FuStrsplitFunc)(GString *token, guint token_idx, gpointer user_data, GError **error); gboolean fu_strsplit_full(const gchar *str, gssize sz, const gchar *delimiter, FuStrsplitFunc callback, gpointer user_data, GError **error) G_GNUC_NON_NULL(1, 3); /** * FuUtfConvertFlags: * @FU_UTF_CONVERT_FLAG_NONE: No flags set * @FU_UTF_CONVERT_FLAG_APPEND_NUL: Include the trailing `NUL` or `NULw` in the buffer * * The flags to use when converting to and from UTF-8. **/ typedef enum { FU_UTF_CONVERT_FLAG_NONE = 0, FU_UTF_CONVERT_FLAG_APPEND_NUL = 1 << 0, } FuUtfConvertFlags; gchar * fu_utf16_to_utf8_byte_array(GByteArray *array, FuEndianType endian, GError **error) G_GNUC_NON_NULL(1); GByteArray * fu_utf8_to_utf16_byte_array(const gchar *str, FuEndianType endian, FuUtfConvertFlags flags, GError **error) G_GNUC_NON_NULL(1); gchar * fu_utf16_to_utf8_bytes(GBytes *bytes, FuEndianType endian, GError **error) G_GNUC_NON_NULL(1); GBytes * fu_utf8_to_utf16_bytes(const gchar *str, FuEndianType endian, FuUtfConvertFlags flags, GError **error) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-sum.c000066400000000000000000000110271460375044200173560ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include "fu-mem.h" #include "fu-sum.h" /** * fu_sum8: * @buf: memory buffer * @bufsz: size of @buf * * Returns the arithmetic sum of all bytes in @buf. * * Returns: sum value * * Since: 1.8.2 **/ guint8 fu_sum8(const guint8 *buf, gsize bufsz) { guint8 checksum = 0; g_return_val_if_fail(buf != NULL, G_MAXUINT8); for (gsize i = 0; i < bufsz; i++) checksum += buf[i]; return checksum; } /** * fu_sum8_bytes: * @blob: a #GBytes * * Returns the arithmetic sum of all bytes in @blob. * * Returns: sum value * * Since: 1.8.2 **/ guint8 fu_sum8_bytes(GBytes *blob) { g_return_val_if_fail(blob != NULL, G_MAXUINT8); if (g_bytes_get_size(blob) == 0) return 0; return fu_sum8(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob)); } /** * fu_sum16: * @buf: memory buffer * @bufsz: size of @buf * * Returns the arithmetic sum of all bytes in @buf, adding them one byte at a time. * * Returns: sum value * * Since: 1.8.2 **/ guint16 fu_sum16(const guint8 *buf, gsize bufsz) { guint16 checksum = 0; g_return_val_if_fail(buf != NULL, G_MAXUINT16); for (gsize i = 0; i < bufsz; i++) checksum += buf[i]; return checksum; } /** * fu_sum16_bytes: * @blob: a #GBytes * * Returns the arithmetic sum of all bytes in @blob, adding them one byte at a time. * * Returns: sum value * * Since: 1.8.2 **/ guint16 fu_sum16_bytes(GBytes *blob) { g_return_val_if_fail(blob != NULL, G_MAXUINT16); return fu_sum16(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob)); } /** * fu_sum16w: * @buf: memory buffer * @bufsz: size of @buf * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * * Returns the arithmetic sum of all bytes in @buf, adding them one word at a time. * The caller must ensure that @bufsz is a multiple of 2. * * Returns: sum value * * Since: 1.8.2 **/ guint16 fu_sum16w(const guint8 *buf, gsize bufsz, FuEndianType endian) { guint16 checksum = 0; g_return_val_if_fail(buf != NULL, G_MAXUINT16); g_return_val_if_fail(bufsz % 2 == 0, G_MAXUINT16); for (gsize i = 0; i < bufsz; i += 2) checksum += fu_memread_uint16(&buf[i], endian); return checksum; } /** * fu_sum16w_bytes: * @blob: a #GBytes * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * * Returns the arithmetic sum of all bytes in @blob, adding them one word at a time. * The caller must ensure that the size of @blob is a multiple of 2. * * Returns: sum value * * Since: 1.8.2 **/ guint16 fu_sum16w_bytes(GBytes *blob, FuEndianType endian) { g_return_val_if_fail(blob != NULL, G_MAXUINT16); return fu_sum16w(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), endian); } /** * fu_sum32: * @buf: memory buffer * @bufsz: size of @buf * * Returns the arithmetic sum of all bytes in @buf, adding them one byte at a time. * * Returns: sum value * * Since: 1.8.2 **/ guint32 fu_sum32(const guint8 *buf, gsize bufsz) { guint32 checksum = 0; g_return_val_if_fail(buf != NULL, G_MAXUINT32); for (gsize i = 0; i < bufsz; i++) checksum += buf[i]; return checksum; } /** * fu_sum32_bytes: * @blob: a #GBytes * * Returns the arithmetic sum of all bytes in @blob, adding them one byte at a time. * * Returns: sum value * * Since: 1.8.2 **/ guint32 fu_sum32_bytes(GBytes *blob) { g_return_val_if_fail(blob != NULL, G_MAXUINT32); return fu_sum32(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob)); } /** * fu_sum32w: * @buf: memory buffer * @bufsz: size of @buf * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * * Returns the arithmetic sum of all bytes in @buf, adding them one dword at a time. * The caller must ensure that @bufsz is a multiple of 4. * * Returns: sum value * * Since: 1.8.2 **/ guint32 fu_sum32w(const guint8 *buf, gsize bufsz, FuEndianType endian) { guint32 checksum = 0; g_return_val_if_fail(buf != NULL, G_MAXUINT32); g_return_val_if_fail(bufsz % 4 == 0, G_MAXUINT32); for (gsize i = 0; i < bufsz; i += 4) checksum += fu_memread_uint32(&buf[i], endian); return checksum; } /** * fu_sum32w_bytes: * @blob: a #GBytes * @endian: an endian type, e.g. %G_LITTLE_ENDIAN * * Returns the arithmetic sum of all bytes in @blob, adding them one dword at a time. * The caller must ensure that the size of @blob is a multiple of 4. * * Returns: sum value * * Since: 1.8.2 **/ guint32 fu_sum32w_bytes(GBytes *blob, FuEndianType endian) { g_return_val_if_fail(blob != NULL, G_MAXUINT32); return fu_sum32w(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), endian); } fwupd-1.9.16/libfwupdplugin/fu-sum.h000066400000000000000000000012361460375044200173640ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-common.h" guint8 fu_sum8(const guint8 *buf, gsize bufsz); guint8 fu_sum8_bytes(GBytes *blob); guint16 fu_sum16(const guint8 *buf, gsize bufsz); guint16 fu_sum16_bytes(GBytes *blob); guint16 fu_sum16w(const guint8 *buf, gsize bufsz, FuEndianType endian); guint16 fu_sum16w_bytes(GBytes *blob, FuEndianType endian); guint32 fu_sum32(const guint8 *buf, gsize bufsz); guint32 fu_sum32_bytes(GBytes *blob); guint32 fu_sum32w(const guint8 *buf, gsize bufsz, FuEndianType endian); guint32 fu_sum32w_bytes(GBytes *blob, FuEndianType endian); fwupd-1.9.16/libfwupdplugin/fu-udev-device-private.h000066400000000000000000000005071460375044200224300ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-udev-device.h" void fu_udev_device_emit_changed(FuUdevDevice *self) G_GNUC_NON_NULL(1); void fu_udev_device_set_io_channel(FuUdevDevice *self, FuIOChannel *io_channel) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-udev-device.c000066400000000000000000002123331460375044200207550ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuUdevDevice" #include "config.h" #include #include #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_IOCTL_H #include #endif #include #include #include #include #include "fu-device-private.h" #include "fu-i2c-device.h" #include "fu-string.h" #include "fu-udev-device-private.h" /** * FuUdevDevice: * * A UDev device, typically only available on Linux. * * See also: [class@FuDevice] */ typedef struct { GUdevDevice *udev_device; gboolean udev_device_cleared; guint32 class; guint16 vendor; guint16 model; guint16 subsystem_vendor; guint16 subsystem_model; guint8 revision; gchar *subsystem; gchar *bind_id; gchar *driver; gchar *device_file; FuIOChannel *io_channel; FuUdevDeviceFlags flags; } FuUdevDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuUdevDevice, fu_udev_device, FU_TYPE_DEVICE) enum { PROP_0, PROP_UDEV_DEVICE, PROP_SUBSYSTEM, PROP_DRIVER, PROP_DEVICE_FILE, PROP_BIND_ID, PROP_LAST }; enum { SIGNAL_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; #define GET_PRIVATE(o) (fu_udev_device_get_instance_private(o)) /** * fu_udev_device_emit_changed: * @self: a #FuUdevDevice * * Emits the ::changed signal for the object. * * Since: 1.1.2 **/ void fu_udev_device_emit_changed(FuUdevDevice *self) { g_autoptr(GError) error = NULL; g_return_if_fail(FU_IS_UDEV_DEVICE(self)); g_debug("FuUdevDevice emit changed"); if (!fu_device_rescan(FU_DEVICE(self), &error)) g_debug("%s", error->message); g_signal_emit(self, signals[SIGNAL_CHANGED], 0); } #ifdef HAVE_GUDEV static guint32 fu_udev_device_get_sysfs_attr_as_uint32(GUdevDevice *udev_device, const gchar *name) { const gchar *tmp; guint64 tmp64 = 0; g_autoptr(GError) error_local = NULL; tmp = g_udev_device_get_sysfs_attr(udev_device, name); if (tmp == NULL) return 0x0; if (!fu_strtoull(tmp, &tmp64, 0, G_MAXUINT32, &error_local)) { g_warning("reading %s for %s was invalid: %s", name, tmp, error_local->message); return 0x0; } return tmp64; } static guint16 fu_udev_device_get_sysfs_attr_as_uint16(GUdevDevice *udev_device, const gchar *name) { const gchar *tmp; guint64 tmp64 = 0; g_autoptr(GError) error_local = NULL; tmp = g_udev_device_get_sysfs_attr(udev_device, name); if (tmp == NULL) return 0x0; if (!fu_strtoull(tmp, &tmp64, 0, G_MAXUINT16, &error_local)) { g_warning("reading %s for %s was invalid: %s", name, tmp, error_local->message); return 0x0; } return tmp64; } static guint8 fu_udev_device_get_sysfs_attr_as_uint8(GUdevDevice *udev_device, const gchar *name) { const gchar *tmp; guint64 tmp64 = 0; g_autoptr(GError) error_local = NULL; tmp = g_udev_device_get_sysfs_attr(udev_device, name); if (tmp == NULL) return 0x0; if (!fu_strtoull(tmp, &tmp64, 0, G_MAXUINT8, &error_local)) { g_warning("reading %s for %s was invalid: %s", name, g_udev_device_get_sysfs_path(udev_device), error_local->message); return 0x0; } return tmp64; } #endif static void fu_udev_device_to_string(FuDevice *device, guint idt, GString *str) { #ifdef HAVE_GUDEV FuUdevDevice *self = FU_UDEV_DEVICE(device); FuUdevDevicePrivate *priv = GET_PRIVATE(self); if (priv->vendor != 0x0) fu_string_append_kx(str, idt, "Vendor", priv->vendor); if (priv->model != 0x0) fu_string_append_kx(str, idt, "Model", priv->model); if (priv->subsystem_vendor != 0x0 || priv->subsystem_model != 0x0) { fu_string_append_kx(str, idt, "SubsystemVendor", priv->subsystem_vendor); fu_string_append_kx(str, idt, "SubsystemModel", priv->subsystem_model); } if (priv->class != 0x0) fu_string_append_kx(str, idt, "Class", priv->class); if (priv->revision != 0x0) fu_string_append_kx(str, idt, "Revision", priv->revision); if (priv->subsystem != NULL) fu_string_append(str, idt, "Subsystem", priv->subsystem); if (priv->driver != NULL) fu_string_append(str, idt, "Driver", priv->driver); if (priv->bind_id != NULL) fu_string_append(str, idt, "BindId", priv->bind_id); if (priv->device_file != NULL) fu_string_append(str, idt, "DeviceFile", priv->device_file); if (priv->udev_device != NULL) { fu_string_append(str, idt, "SysfsPath", g_udev_device_get_sysfs_path(priv->udev_device)); } #endif } static gboolean fu_udev_device_ensure_bind_id(FuUdevDevice *self, GError **error) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); /* sanity check */ if (priv->bind_id != NULL) return TRUE; #ifdef HAVE_GUDEV /* automatically set the bind ID from the subsystem */ if (g_strcmp0(priv->subsystem, "pci") == 0) { priv->bind_id = g_strdup(g_udev_device_get_property(priv->udev_device, "PCI_SLOT_NAME")); return TRUE; } if (g_strcmp0(priv->subsystem, "hid") == 0) { priv->bind_id = g_strdup(g_udev_device_get_property(priv->udev_device, "HID_PHYS")); return TRUE; } if (g_strcmp0(priv->subsystem, "usb") == 0) { priv->bind_id = g_path_get_basename(g_udev_device_get_sysfs_path(priv->udev_device)); return TRUE; } #endif /* nothing found automatically */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot derive bind-id from subsystem %s", priv->subsystem); return FALSE; } static void fu_udev_device_set_subsystem(FuUdevDevice *self, const gchar *subsystem) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->subsystem, subsystem) == 0) return; g_free(priv->subsystem); priv->subsystem = g_strdup(subsystem); g_object_notify(G_OBJECT(self), "subsystem"); } /** * fu_udev_device_set_bind_id: * @self: a #FuUdevDevice * @bind_id: a bind-id string, e.g. `pci:0:0:1` * * Sets the device ID used for binding the device, e.g. `pci:1:2:3` * * Since: 1.7.2 **/ void fu_udev_device_set_bind_id(FuUdevDevice *self, const gchar *bind_id) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->bind_id, bind_id) == 0) return; g_free(priv->bind_id); priv->bind_id = g_strdup(bind_id); g_object_notify(G_OBJECT(self), "bind-id"); } static void fu_udev_device_set_driver(FuUdevDevice *self, const gchar *driver) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->driver, driver) == 0) return; g_free(priv->driver); priv->driver = g_strdup(driver); g_object_notify(G_OBJECT(self), "driver"); } /** * fu_udev_device_set_device_file: * @self: a #FuUdevDevice * @device_file: (nullable): a device path * * Sets the device file to use for reading and writing. * * Since: 1.8.7 **/ void fu_udev_device_set_device_file(FuUdevDevice *self, const gchar *device_file) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); /* not changed */ if (g_strcmp0(priv->device_file, device_file) == 0) return; g_free(priv->device_file); priv->device_file = g_strdup(device_file); g_object_notify(G_OBJECT(self), "device-file"); } #ifdef HAVE_GUDEV static const gchar * fu_udev_device_get_vendor_fallback(GUdevDevice *udev_device) { const gchar *tmp; tmp = g_udev_device_get_property(udev_device, "ID_VENDOR_FROM_DATABASE"); if (tmp != NULL) return tmp; tmp = g_udev_device_get_property(udev_device, "ID_VENDOR"); if (tmp != NULL) return tmp; return NULL; } #endif #ifdef HAVE_GUDEV static gboolean fu_udev_device_probe_serio(FuUdevDevice *self, GError **error) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); const gchar *tmp; /* firmware ID */ tmp = g_udev_device_get_property(priv->udev_device, "SERIO_FIRMWARE_ID"); if (tmp != NULL) { /* this prefix is not useful */ if (g_str_has_prefix(tmp, "PNP: ")) tmp += 5; fu_device_add_instance_strsafe(FU_DEVICE(self), "FWID", tmp); if (!fu_device_build_instance_id_full(FU_DEVICE(self), FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS, error, "SERIO", "FWID", NULL)) return FALSE; } return TRUE; } static guint16 fu_udev_device_get_property_as_uint16(GUdevDevice *udev_device, const gchar *key) { const gchar *tmp = g_udev_device_get_property(udev_device, key); guint64 value = 0; g_autofree gchar *str = NULL; if (tmp == NULL) return 0x0; str = g_strdup_printf("0x%s", tmp); if (!fu_strtoull(str, &value, 0x0, G_MAXUINT16, NULL)) return 0x0; return (guint16)value; } static void fu_udev_device_set_vendor_from_udev_device(FuUdevDevice *self, GUdevDevice *udev_device) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); priv->vendor = fu_udev_device_get_sysfs_attr_as_uint16(udev_device, "vendor"); priv->model = fu_udev_device_get_sysfs_attr_as_uint16(udev_device, "device"); priv->revision = fu_udev_device_get_sysfs_attr_as_uint8(udev_device, "revision"); priv->class = fu_udev_device_get_sysfs_attr_as_uint32(udev_device, "class"); priv->subsystem_vendor = fu_udev_device_get_sysfs_attr_as_uint16(udev_device, "subsystem_vendor"); priv->subsystem_model = fu_udev_device_get_sysfs_attr_as_uint16(udev_device, "subsystem_device"); /* fallback to properties as udev might be using a subsystem-specific prober */ if (priv->vendor == 0x0) priv->vendor = fu_udev_device_get_property_as_uint16(udev_device, "ID_VENDOR_ID"); if (priv->model == 0x0) priv->model = fu_udev_device_get_property_as_uint16(udev_device, "ID_MODEL_ID"); if (priv->revision == 0x0) priv->revision = fu_udev_device_get_property_as_uint16(udev_device, "ID_REVISION"); } static void fu_udev_device_set_vendor_from_parent(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GUdevDevice) udev_device = g_object_ref(priv->udev_device); while (TRUE) { g_autoptr(GUdevDevice) parent = g_udev_device_get_parent(udev_device); if (parent == NULL) break; fu_udev_device_set_vendor_from_udev_device(self, parent); if (priv->vendor != 0x0 || priv->model != 0x0 || priv->revision != 0x0) break; g_set_object(&udev_device, parent); } } #endif static gboolean fu_udev_device_probe(FuDevice *device, GError **error) { FuUdevDevice *self = FU_UDEV_DEVICE(device); FuUdevDevicePrivate *priv = GET_PRIVATE(self); #ifdef HAVE_GUDEV const gchar *tmp; g_autofree gchar *subsystem = NULL; g_autoptr(GUdevDevice) udev_parent = NULL; g_autoptr(GUdevDevice) parent_i2c = NULL; #endif /* nothing to do */ if (priv->udev_device == NULL) return TRUE; #ifdef HAVE_GUDEV /* get IDs, but fallback to the parent, grandparent, great-grandparent, etc */ fu_udev_device_set_vendor_from_udev_device(self, priv->udev_device); udev_parent = g_udev_device_get_parent(priv->udev_device); if (udev_parent != NULL && priv->flags & FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT) fu_udev_device_set_vendor_from_parent(self); /* hidraw helpfully encodes the information in a different place */ if (udev_parent != NULL && priv->vendor == 0x0 && priv->model == 0x0 && priv->revision == 0x0 && g_strcmp0(priv->subsystem, "hidraw") == 0) { tmp = g_udev_device_get_property(udev_parent, "HID_ID"); if (tmp != NULL) { g_auto(GStrv) split = g_strsplit(tmp, ":", -1); if (g_strv_length(split) == 3) { guint64 val = g_ascii_strtoull(split[1], NULL, 16); if (val > G_MAXUINT16) { g_warning("reading %s for %s overflowed", split[1], g_udev_device_get_sysfs_path(priv->udev_device)); } else { priv->vendor = val; } val = g_ascii_strtoull(split[2], NULL, 16); if (val > G_MAXUINT32) { g_warning("reading %s for %s overflowed", split[2], g_udev_device_get_sysfs_path(priv->udev_device)); } else { priv->model = val; } } } tmp = g_udev_device_get_property(udev_parent, "HID_NAME"); if (tmp != NULL) { if (fu_device_get_name(device) == NULL) fu_device_set_name(device, tmp); } } /* if the device is a GPU try to fetch it from vbios_version */ if (g_strcmp0(priv->subsystem, "pci") == 0 && fu_udev_device_is_pci_base_cls(FU_UDEV_DEVICE(device), FU_PCI_BASE_CLS_DISPLAY) && fu_device_get_version(device) == NULL) { const gchar *version; version = g_udev_device_get_sysfs_attr(priv->udev_device, "vbios_version"); if (version != NULL) { fu_device_set_version(device, version); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PLAIN); fu_device_add_icon(FU_DEVICE(self), "video-display"); } } /* set the version if the revision has been set */ if (fu_device_get_version(device) == NULL && fu_device_get_version_format(device) == FWUPD_VERSION_FORMAT_UNKNOWN) { if (priv->revision != 0x00 && priv->revision != 0xFF) { g_autofree gchar *version = g_strdup_printf("%02x", priv->revision); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_version(device, version); } } /* set model */ if (fu_device_get_name(device) == NULL) { tmp = g_udev_device_get_property(priv->udev_device, "ID_MODEL_FROM_DATABASE"); if (tmp == NULL) tmp = g_udev_device_get_property(priv->udev_device, "ID_MODEL"); if (tmp == NULL) tmp = g_udev_device_get_property(priv->udev_device, "ID_PCI_CLASS_FROM_DATABASE"); if (tmp != NULL) fu_device_set_name(device, tmp); } /* set vendor */ if (fu_device_get_vendor(device) == NULL) { tmp = fu_udev_device_get_vendor_fallback(priv->udev_device); if (tmp != NULL) fu_device_set_vendor(device, tmp); } /* try harder to find a vendor name the user will recognize */ if (priv->flags & FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT && udev_parent != NULL && fu_device_get_vendor(device) == NULL) { g_autoptr(GUdevDevice) device_tmp = g_object_ref(udev_parent); for (guint i = 0; i < 0xff; i++) { g_autoptr(GUdevDevice) parent = NULL; tmp = fu_udev_device_get_vendor_fallback(device_tmp); if (tmp != NULL) { fu_device_set_vendor(device, tmp); break; } parent = g_udev_device_get_parent(device_tmp); if (parent == NULL) break; g_set_object(&device_tmp, parent); } } /* set revision */ if (fu_device_get_version(device) == NULL && fu_device_get_version_format(device) == FWUPD_VERSION_FORMAT_UNKNOWN) { tmp = g_udev_device_get_property(priv->udev_device, "ID_REVISION"); if (tmp != NULL) fu_device_set_version(device, tmp); } /* set vendor ID */ subsystem = g_ascii_strup(g_udev_device_get_subsystem(priv->udev_device), -1); if (subsystem != NULL && priv->vendor != 0x0000) { g_autofree gchar *vendor_id = NULL; vendor_id = g_strdup_printf("%s:0x%04X", subsystem, (guint)priv->vendor); fu_device_add_vendor_id(device, vendor_id); } /* add GUIDs in order of priority */ if (priv->vendor != 0x0000) fu_device_add_instance_u16(device, "VEN", priv->vendor); if (priv->model != 0x0000) fu_device_add_instance_u16(device, "DEV", priv->model); if (priv->subsystem_vendor != 0x0000 || priv->subsystem_model != 0x0000) { g_autofree gchar *subsys = g_strdup_printf("%04X%04X", priv->subsystem_vendor, priv->subsystem_model); fu_device_add_instance_str(device, "SUBSYS", subsys); } if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV) && priv->revision != 0xFF) { fu_device_add_instance_u8(device, "REV", priv->revision); } if (subsystem != NULL) { fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, subsystem, "VEN", NULL); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, subsystem, "VEN", "DEV", NULL); if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV)) { fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, subsystem, "VEN", "DEV", "REV", NULL); } fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, subsystem, "VEN", "DEV", "SUBSYS", NULL); if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV)) { fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, subsystem, "VEN", "DEV", "SUBSYS", "REV", NULL); } } /* set serial */ if (!fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER) && fu_device_get_serial(device) == NULL) { tmp = g_udev_device_get_property(priv->udev_device, "ID_SERIAL_SHORT"); if (tmp == NULL) tmp = g_udev_device_get_property(priv->udev_device, "ID_SERIAL"); if (tmp != NULL) fu_device_set_serial(device, tmp); } /* add device class */ if (subsystem != NULL) { tmp = g_udev_device_get_sysfs_attr(priv->udev_device, "class"); if (tmp != NULL && g_str_has_prefix(tmp, "0x")) tmp += 2; fu_device_add_instance_strup(device, "CLASS", tmp); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, subsystem, "VEN", "CLASS", NULL); /* add devtype */ fu_device_add_instance_strup(device, "TYPE", g_udev_device_get_devtype(priv->udev_device)); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, subsystem, "TYPE", NULL); /* add the driver */ fu_device_add_instance_str(device, "DRIVER", priv->driver); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, subsystem, "DRIVER", NULL); /* add the modalias */ fu_device_add_instance_strsafe( device, "MODALIAS", g_udev_device_get_property(priv->udev_device, "MODALIAS")); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, subsystem, "MODALIAS", NULL); } /* add firmware_id */ if (g_strcmp0(g_udev_device_get_subsystem(priv->udev_device), "serio") == 0) { if (!fu_udev_device_probe_serio(self, error)) return FALSE; } /* determine if we're wired internally */ parent_i2c = g_udev_device_get_parent_with_subsystem(priv->udev_device, "i2c", NULL); if (parent_i2c != NULL) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL); #endif /* success */ return TRUE; } #ifdef HAVE_GUDEV static gchar * fu_udev_device_get_miscdev0(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); const gchar *fn; g_autofree gchar *miscdir = NULL; g_autoptr(GDir) dir = NULL; miscdir = g_build_filename(g_udev_device_get_sysfs_path(priv->udev_device), "misc", NULL); dir = g_dir_open(miscdir, 0, NULL); if (dir == NULL) return NULL; fn = g_dir_read_name(dir); if (fn == NULL) return NULL; return g_strdup_printf("/dev/%s", fn); } static void fu_udev_device_set_dev_internal(FuUdevDevice *self, GUdevDevice *udev_device) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_UDEV_DEVICE(self)); if (g_set_object(&priv->udev_device, udev_device)) g_object_notify(G_OBJECT(self), "udev-device"); } #endif /** * fu_udev_device_set_dev: * @self: a #FuUdevDevice * @udev_device: a #GUdevDevice * * Sets the #GUdevDevice. This may need to be used to replace the actual device * used for reads and writes before the device is probed. * * Since: 1.6.2 **/ void fu_udev_device_set_dev(FuUdevDevice *self, GUdevDevice *udev_device) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); #ifdef HAVE_GUDEV const gchar *summary; #endif g_return_if_fail(FU_IS_UDEV_DEVICE(self)); #ifdef HAVE_GUDEV /* the net subsystem is not a real hardware class */ if (udev_device != NULL && g_strcmp0(g_udev_device_get_subsystem(udev_device), "net") == 0) { g_autoptr(GUdevDevice) udev_device_phys = NULL; udev_device_phys = g_udev_device_get_parent(udev_device); fu_udev_device_set_dev_internal(self, udev_device_phys); fu_device_set_metadata(FU_DEVICE(self), "ParentSubsystem", g_udev_device_get_subsystem(udev_device)); } else { fu_udev_device_set_dev_internal(self, udev_device); } #endif /* set new device */ if (priv->udev_device == NULL) return; #ifdef HAVE_GUDEV fu_udev_device_set_subsystem(self, g_udev_device_get_subsystem(priv->udev_device)); fu_udev_device_set_driver(self, g_udev_device_get_driver(priv->udev_device)); fu_udev_device_set_device_file(self, g_udev_device_get_device_file(priv->udev_device)); /* so we can display something sensible for unclaimed devices */ fu_device_set_backend_id(FU_DEVICE(self), g_udev_device_get_sysfs_path(priv->udev_device)); /* fall back to the first thing handled by misc drivers */ if (priv->device_file == NULL) { /* perhaps we should unconditionally fall back? or perhaps * require FU_UDEV_DEVICE_FLAG_FALLBACK_MISC... */ if (g_strcmp0(priv->subsystem, "serio") == 0) priv->device_file = fu_udev_device_get_miscdev0(self); if (priv->device_file != NULL) g_debug("falling back to misc %s", priv->device_file); } /* try to get one line summary */ summary = g_udev_device_get_sysfs_attr(priv->udev_device, "description"); if (summary == NULL) { g_autoptr(GUdevDevice) parent = NULL; parent = g_udev_device_get_parent(priv->udev_device); if (parent != NULL) summary = g_udev_device_get_sysfs_attr(parent, "description"); } if (summary != NULL) fu_device_set_summary(FU_DEVICE(self), summary); #endif } /** * fu_udev_device_get_slot_depth: * @self: a #FuUdevDevice * @subsystem: a subsystem * * Determine how far up a chain a given device is * * Returns: unsigned integer * * Since: 1.2.4 **/ guint fu_udev_device_get_slot_depth(FuUdevDevice *self, const gchar *subsystem) { #ifdef HAVE_GUDEV GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(self)); g_autoptr(GUdevDevice) device_tmp = NULL; device_tmp = g_udev_device_get_parent_with_subsystem(udev_device, subsystem, NULL); if (device_tmp == NULL) return 0; for (guint i = 0; i < 0xff; i++) { g_autoptr(GUdevDevice) parent = g_udev_device_get_parent(device_tmp); if (parent == NULL) return i; g_set_object(&device_tmp, parent); } #endif return 0; } static void fu_udev_device_probe_complete(FuDevice *device) { FuUdevDevice *self = FU_UDEV_DEVICE(device); FuUdevDevicePrivate *priv = GET_PRIVATE(self); /* this is where we would call g_clear_object(&priv->udev_device) in the future, but for * now just set the flag so we get the warning in non-supported builds */ priv->udev_device_cleared = TRUE; } static gboolean fu_udev_device_unbind_driver(FuDevice *device, GError **error) { #ifdef HAVE_GUDEV FuUdevDevice *self = FU_UDEV_DEVICE(device); FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_autofree gchar *fn = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GOutputStream) stream = NULL; /* is already unbound */ fn = g_build_filename(g_udev_device_get_sysfs_path(priv->udev_device), "driver", "unbind", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS)) return TRUE; /* write bus ID to file */ if (!fu_udev_device_ensure_bind_id(self, error)) return FALSE; file = g_file_new_for_path(fn); stream = G_OUTPUT_STREAM(g_file_replace(file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error)); if (stream == NULL) return FALSE; return g_output_stream_write_all(stream, priv->bind_id, strlen(priv->bind_id), NULL, NULL, error); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "driver unbinding not supported"); return FALSE; #endif } static gboolean fu_udev_device_bind_driver(FuDevice *device, const gchar *subsystem, const gchar *driver, GError **error) { #ifdef HAVE_GUDEV FuUdevDevice *self = FU_UDEV_DEVICE(device); FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_autofree gchar *driver_safe = g_strdup(driver); g_autofree gchar *fn = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GOutputStream) stream = NULL; /* copy the logic from modprobe */ g_strdelimit(driver_safe, "-", '_'); /* driver exists */ fn = g_strdup_printf("/sys/module/%s/drivers/%s:%s/bind", driver_safe, subsystem, driver_safe); if (!g_file_test(fn, G_FILE_TEST_EXISTS)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot bind with %s:%s", subsystem, driver); return FALSE; } /* write bus ID to file */ if (!fu_udev_device_ensure_bind_id(self, error)) return FALSE; if (priv->bind_id == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "bind-id not set for subsystem %s", priv->subsystem); return FALSE; } file = g_file_new_for_path(fn); stream = G_OUTPUT_STREAM(g_file_replace(file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error)); if (stream == NULL) return FALSE; return g_output_stream_write_all(stream, priv->bind_id, strlen(priv->bind_id), NULL, NULL, error); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "driver binding not supported on Windows"); return FALSE; #endif } static void fu_udev_device_incorporate(FuDevice *self, FuDevice *donor) { FuUdevDevice *uself = FU_UDEV_DEVICE(self); FuUdevDevice *udonor = FU_UDEV_DEVICE(donor); FuUdevDevicePrivate *priv = GET_PRIVATE(uself); FuUdevDevicePrivate *priv_donor = GET_PRIVATE(udonor); g_return_if_fail(FU_IS_UDEV_DEVICE(self)); g_return_if_fail(FU_IS_UDEV_DEVICE(donor)); fu_udev_device_set_dev(uself, fu_udev_device_get_dev(udonor)); if (priv->device_file == NULL) { fu_udev_device_set_subsystem(uself, fu_udev_device_get_subsystem(udonor)); fu_udev_device_set_bind_id(uself, fu_udev_device_get_bind_id(udonor)); fu_udev_device_set_device_file(uself, fu_udev_device_get_device_file(udonor)); fu_udev_device_set_driver(uself, fu_udev_device_get_driver(udonor)); } if (priv->vendor == 0x0 && priv_donor->vendor != 0x0) priv->vendor = priv_donor->vendor; if (priv->model == 0x0 && priv_donor->model != 0x0) priv->model = priv_donor->model; if (priv->subsystem_vendor == 0x0 && priv_donor->subsystem_vendor != 0x0) priv->subsystem_vendor = priv_donor->subsystem_vendor; if (priv->subsystem_model == 0x0 && priv_donor->subsystem_model != 0x0) priv->subsystem_model = priv_donor->subsystem_model; if (priv->revision == 0x0 && priv_donor->revision != 0x0) priv->revision = priv_donor->revision; if (priv->io_channel == NULL && priv_donor->io_channel != 0x0) priv->io_channel = g_object_ref(priv_donor->io_channel); } /** * fu_udev_device_get_dev: * @self: a #FuUdevDevice * * Gets the #GUdevDevice. * * NOTE: If a plugin calls this after the `->probe()` and `->setup()` phases then the * %FU_DEVICE_INTERNAL_FLAG_NO_PROBE_COMPLETE flag should be set on the device to avoid a warning. * * Returns: (transfer none): a #GUdevDevice, or %NULL * * Since: 1.1.2 **/ GUdevDevice * fu_udev_device_get_dev(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL); #ifndef SUPPORTED_BUILD if (priv->udev_device_cleared) { g_warning("soon the GUdevDevice will not be available post-probe, use " "FU_DEVICE_INTERNAL_FLAG_NO_PROBE_COMPLETE in %s plugin to opt-out %s", fu_device_get_plugin(FU_DEVICE(self)), fu_device_get_id(FU_DEVICE(self))); } #endif return priv->udev_device; } /** * fu_udev_device_get_subsystem: * @self: a #FuUdevDevice * * Gets the device subsystem, e.g. `pci` * * Returns: a subsystem, or NULL if unset or invalid * * Since: 1.1.2 **/ const gchar * fu_udev_device_get_subsystem(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL); return priv->subsystem; } /** * fu_udev_device_get_bind_id: * @self: a #FuUdevDevice * * Gets the device ID used for binding the device, e.g. `pci:1:2:3` * * Returns: a bind_id, or NULL if unset or invalid * * Since: 1.7.2 **/ const gchar * fu_udev_device_get_bind_id(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL); fu_udev_device_ensure_bind_id(self, NULL); return priv->bind_id; } /** * fu_udev_device_get_driver: * @self: a #FuUdevDevice * * Gets the device driver, e.g. `psmouse`. * * Returns: a subsystem, or NULL if unset or invalid * * Since: 1.5.3 **/ const gchar * fu_udev_device_get_driver(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL); return priv->driver; } /** * fu_udev_device_get_device_file: * @self: a #FuUdevDevice * * Gets the device node. * * Returns: a device file, or NULL if unset * * Since: 1.3.1 **/ const gchar * fu_udev_device_get_device_file(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL); return priv->device_file; } /** * fu_udev_device_get_sysfs_path: * @self: a #FuUdevDevice * * Gets the device sysfs path, e.g. `/sys/devices/pci0000:00/0000:00:14.0`. * * Returns: a local path, or NULL if unset or invalid * * Since: 1.1.2 **/ const gchar * fu_udev_device_get_sysfs_path(FuUdevDevice *self) { #ifdef HAVE_GUDEV FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL); if (priv->udev_device != NULL) return g_udev_device_get_sysfs_path(priv->udev_device); #endif return NULL; } /** * fu_udev_device_get_number: * @self: a #FuUdevDevice * * Gets the device number, if any. * * Returns: integer, 0 if the data is unavailable, or %G_MAXUINT64 if the * feature is not available * * Since: 1.5.0 **/ guint64 fu_udev_device_get_number(FuUdevDevice *self) { #ifdef HAVE_GUDEV FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0); if (priv->udev_device != NULL) { guint64 tmp = 0; g_autoptr(GError) error_local = NULL; if (!fu_strtoull(g_udev_device_get_number(priv->udev_device), &tmp, 0x0, G_MAXUINT64, &error_local)) { g_warning("failed to convert udev number: %s", error_local->message); return G_MAXUINT64; } return tmp; } #endif return G_MAXUINT64; } /** * fu_udev_device_is_pci_base_cls: * @self: a #FuUdevDevice * @cls: #FuPciBaseCls type * * Determines whether the device matches a given pci base class type * * Since: 1.8.11 **/ gboolean fu_udev_device_is_pci_base_cls(FuUdevDevice *self, FuPciBaseCls cls) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE); return (priv->class >> 16) == cls; } /** * fu_udev_device_get_cls: * @self: a #FuUdevDevice * * Gets the PCI class for a device. * * The class consists of a base class and subclass. * * Returns: a PCI class * * Since: 1.8.11 **/ guint32 fu_udev_device_get_cls(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0x0000); return priv->class; } /** * fu_udev_device_get_vendor: * @self: a #FuUdevDevice * * Gets the device vendor code. * * Returns: a vendor code, or 0 if unset or invalid * * Since: 1.1.2 **/ guint16 fu_udev_device_get_vendor(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0x0000); return priv->vendor; } /** * fu_udev_device_get_model: * @self: a #FuUdevDevice * * Gets the device model code. * * Returns: a vendor code, or 0 if unset or invalid * * Since: 1.1.2 **/ guint16 fu_udev_device_get_model(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0x0000); return priv->model; } /** * fu_udev_device_get_subsystem_vendor: * @self: a #FuUdevDevice * * Gets the device subsystem vendor code. * * Returns: a vendor code, or 0 if unset or invalid * * Since: 1.5.0 **/ guint16 fu_udev_device_get_subsystem_vendor(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0x0000); return priv->subsystem_vendor; } /** * fu_udev_device_get_subsystem_model: * @self: a #FuUdevDevice * * Gets the device subsystem model code. * * Returns: a vendor code, or 0 if unset or invalid * * Since: 1.5.0 **/ guint16 fu_udev_device_get_subsystem_model(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0x0000); return priv->subsystem_model; } /** * fu_udev_device_get_revision: * @self: a #FuUdevDevice * * Gets the device revision. * * Returns: a vendor code, or 0 if unset or invalid * * Since: 1.1.2 **/ guint8 fu_udev_device_get_revision(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), 0x00); return priv->revision; } #ifdef HAVE_GUDEV static gchar * fu_udev_device_get_parent_subsystems(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); GString *str = g_string_new(NULL); g_autoptr(GUdevDevice) udev_device = g_object_ref(priv->udev_device); /* find subsystems of self and all parent devices */ if (priv->subsystem != NULL) g_string_append_printf(str, "%s,", priv->subsystem); while (TRUE) { g_autoptr(GUdevDevice) parent = g_udev_device_get_parent(udev_device); if (parent == NULL) break; if (g_udev_device_get_subsystem(parent) != NULL) { g_string_append_printf(str, "%s,", g_udev_device_get_subsystem(parent)); } g_set_object(&udev_device, g_steal_pointer(&parent)); } if (str->len > 0) g_string_truncate(str, str->len - 1); return g_string_free(str, FALSE); } static gboolean fu_udev_device_match_subsystem_devtype(GUdevDevice *udev_device, const gchar *subsystem, const gchar *devtype) { if (subsystem != NULL) { if (g_strcmp0(g_udev_device_get_subsystem(udev_device), subsystem) != 0) return FALSE; } if (devtype != NULL) { if (g_strcmp0(g_udev_device_get_devtype(udev_device), devtype) != 0) return FALSE; } return TRUE; } static GUdevDevice * fu_udev_device_get_parent_with_subsystem_devtype(GUdevDevice *udev_device, const gchar *subsystem, const gchar *devtype) { g_autoptr(GUdevDevice) udev_device_tmp = g_object_ref(udev_device); while (udev_device_tmp != NULL) { g_autoptr(GUdevDevice) parent = NULL; if (fu_udev_device_match_subsystem_devtype(udev_device_tmp, subsystem, devtype)) return g_object_ref(udev_device_tmp); parent = g_udev_device_get_parent(udev_device_tmp); g_set_object(&udev_device_tmp, parent); } return NULL; } #endif /** * fu_udev_device_set_physical_id: * @self: a #FuUdevDevice * @subsystems: a subsystem string, e.g. `pci,usb,scsi:scsi_target` * @error: (nullable): optional return location for an error * * Sets the physical ID from the device subsystem. Plugins should choose the * subsystem that is "deepest" in the udev tree, for instance choosing `usb` * over `pci` for a mouse device. * * The devtype can also be specified for a specific device, which is useful when the * subsystem alone is not enough to identify the physical device. e.g. ignoring the * specific LUNs for a SCSI device. * * Returns: %TRUE if the physical device was set. * * Since: 1.1.2 **/ gboolean fu_udev_device_set_physical_id(FuUdevDevice *self, const gchar *subsystems, GError **error) { #ifdef HAVE_GUDEV FuUdevDevicePrivate *priv = GET_PRIVATE(self); const gchar *tmp; g_autofree gchar *physical_id = NULL; g_autofree gchar *subsystem = NULL; g_auto(GStrv) split = NULL; g_autoptr(GUdevDevice) udev_device = NULL; g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE); g_return_val_if_fail(subsystems != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* nothing to do */ if (priv->udev_device == NULL) return TRUE; /* look for each subsystem[:devtype] in turn */ split = g_strsplit(subsystems, ",", -1); for (guint i = 0; split[i] != NULL; i++) { g_auto(GStrv) subsys_devtype = g_strsplit(split[i], ":", 2); /* matching on devtype is optional */ udev_device = fu_udev_device_get_parent_with_subsystem_devtype(priv->udev_device, subsys_devtype[0], subsys_devtype[1]); if (udev_device != NULL) { subsystem = g_strdup(subsys_devtype[0]); break; } } if (udev_device == NULL) { g_autofree gchar *str = fu_udev_device_get_parent_subsystems(self); g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find device with subsystems %s, only got %s", subsystems, str); return FALSE; } if (g_strcmp0(subsystem, "pci") == 0) { tmp = g_udev_device_get_property(udev_device, "PCI_SLOT_NAME"); if (tmp == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find PCI_SLOT_NAME"); return FALSE; } physical_id = g_strdup_printf("PCI_SLOT_NAME=%s", tmp); } else if (g_strcmp0(subsystem, "usb") == 0 || g_strcmp0(subsystem, "mmc") == 0 || g_strcmp0(subsystem, "i2c") == 0 || g_strcmp0(subsystem, "platform") == 0 || g_strcmp0(subsystem, "scsi") == 0 || g_strcmp0(subsystem, "mtd") == 0 || g_strcmp0(subsystem, "block") == 0 || g_strcmp0(subsystem, "gpio") == 0 || g_strcmp0(subsystem, "video4linux") == 0) { tmp = g_udev_device_get_property(udev_device, "DEVPATH"); if (tmp == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find DEVPATH"); return FALSE; } physical_id = g_strdup_printf("DEVPATH=%s", tmp); } else if (g_strcmp0(subsystem, "hid") == 0) { tmp = g_udev_device_get_property(udev_device, "HID_PHYS"); if (tmp == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find HID_PHYS"); return FALSE; } physical_id = g_strdup_printf("HID_PHYS=%s", tmp); } else if (g_strcmp0(subsystem, "tpm") == 0 || g_strcmp0(subsystem, "drm_dp_aux_dev") == 0) { tmp = g_udev_device_get_property(udev_device, "DEVNAME"); if (tmp == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find DEVNAME"); return FALSE; } physical_id = g_strdup_printf("DEVNAME=%s", tmp); } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot handle subsystem %s", subsystem); return FALSE; } /* success */ fu_device_set_physical_id(FU_DEVICE(self), physical_id); return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); return FALSE; #endif } /** * fu_udev_device_set_logical_id: * @self: a #FuUdevDevice * @subsystem: a subsystem string, e.g. `pci,usb` * @error: (nullable): optional return location for an error * * Sets the logical ID from the device subsystem. Plugins should choose the * subsystem that most relevant in the udev tree, for instance choosing `hid` * over `usb` for a mouse device. * * Returns: %TRUE if the logical device was set. * * Since: 1.5.8 **/ gboolean fu_udev_device_set_logical_id(FuUdevDevice *self, const gchar *subsystem, GError **error) { #ifdef HAVE_GUDEV FuUdevDevicePrivate *priv = GET_PRIVATE(self); const gchar *tmp; g_autofree gchar *logical_id = NULL; g_autoptr(GUdevDevice) udev_device = NULL; g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE); g_return_val_if_fail(subsystem != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* nothing to do */ if (priv->udev_device == NULL) return TRUE; /* find correct device matching subsystem */ if (g_strcmp0(priv->subsystem, subsystem) == 0) { udev_device = g_object_ref(priv->udev_device); } else { udev_device = g_udev_device_get_parent_with_subsystem(priv->udev_device, subsystem, NULL); } if (udev_device == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find device with subsystem %s", subsystem); return FALSE; } /* query each subsystem */ if (g_strcmp0(subsystem, "hid") == 0) { tmp = g_udev_device_get_property(udev_device, "HID_UNIQ"); if (tmp == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find HID_UNIQ"); return FALSE; } logical_id = g_strdup_printf("HID_UNIQ=%s", tmp); } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot handle subsystem %s", subsystem); return FALSE; } /* success */ fu_device_set_logical_id(FU_DEVICE(self), logical_id); return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); return FALSE; #endif } /** * fu_udev_device_get_io_channel: * @self: a #FuUdevDevice * * Gets the IO channel. * * Returns: (transfer none): a #FuIOChannel, or %NULL if the device is not open * * Since: 1.9.8 **/ FuIOChannel * fu_udev_device_get_io_channel(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL); return priv->io_channel; } /** * fu_udev_device_set_io_channel: * @self: a #FuUdevDevice * @io_channel: a #FuIOChannel * * Replace the IO channel to use when the device has already been opened. * This object will automatically unref @io_channel when fu_device_close() is called. * * Since: 1.9.8 **/ void fu_udev_device_set_io_channel(FuUdevDevice *self, FuIOChannel *io_channel) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_UDEV_DEVICE(self)); g_return_if_fail(FU_IS_IO_CHANNEL(io_channel)); g_set_object(&priv->io_channel, io_channel); } /** * fu_udev_device_set_flags: * @self: a #FuUdevDevice * @flags: udev device flags, e.g. %FU_UDEV_DEVICE_FLAG_OPEN_READ * * Sets the parameters to use when opening the device. * * For example %FU_UDEV_DEVICE_FLAG_OPEN_READ means that fu_device_open() * would use `O_RDONLY` rather than `O_RDWR` which is the default. * * Since: 1.3.6 **/ void fu_udev_device_set_flags(FuUdevDevice *self, FuUdevDeviceFlags flags) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_UDEV_DEVICE(self)); priv->flags = flags; #ifdef HAVE_GUDEV /* overwrite */ if (flags & FU_UDEV_DEVICE_FLAG_USE_CONFIG) { g_free(priv->device_file); priv->device_file = g_build_filename(g_udev_device_get_sysfs_path(priv->udev_device), "config", NULL); } #endif } static gboolean fu_udev_device_open(FuDevice *device, GError **error) { FuUdevDevice *self = FU_UDEV_DEVICE(device); FuUdevDevicePrivate *priv = GET_PRIVATE(self); gint fd; /* open device */ if (priv->device_file != NULL && priv->flags != FU_UDEV_DEVICE_FLAG_NONE) { gint flags; g_autoptr(FuIOChannel) io_channel = NULL; if (priv->flags & FU_UDEV_DEVICE_FLAG_OPEN_READ && priv->flags & FU_UDEV_DEVICE_FLAG_OPEN_WRITE) { flags = O_RDWR; } else if (priv->flags & FU_UDEV_DEVICE_FLAG_OPEN_WRITE) { flags = O_WRONLY; } else { flags = O_RDONLY; } #ifdef O_NONBLOCK if (priv->flags & FU_UDEV_DEVICE_FLAG_OPEN_NONBLOCK) flags |= O_NONBLOCK; #endif #ifdef O_SYNC if (priv->flags & FU_UDEV_DEVICE_FLAG_OPEN_SYNC) flags |= O_SYNC; #endif fd = g_open(priv->device_file, flags, 0); if (fd < 0) { g_set_error(error, G_IO_ERROR, #ifdef HAVE_ERRNO_H g_io_error_from_errno(errno), #else G_IO_ERROR_FAILED, #endif "failed to open %s: %s", priv->device_file, g_strerror(errno)); return FALSE; } io_channel = fu_io_channel_unix_new(fd); g_set_object(&priv->io_channel, io_channel); } /* success */ return TRUE; } static gboolean fu_udev_device_rescan(FuDevice *device, GError **error) { #ifdef HAVE_GUDEV FuUdevDevice *self = FU_UDEV_DEVICE(device); FuUdevDevicePrivate *priv = GET_PRIVATE(self); const gchar *sysfs_path; g_autoptr(GUdevClient) udev_client = g_udev_client_new(NULL); g_autoptr(GUdevDevice) udev_device = NULL; /* never set */ if (priv->udev_device == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "rescan with no previous device"); return FALSE; } sysfs_path = g_udev_device_get_sysfs_path(priv->udev_device); udev_device = g_udev_client_query_by_sysfs_path(udev_client, sysfs_path); if (udev_device == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "rescan could not find device %s", sysfs_path); return FALSE; } fu_udev_device_set_dev(self, udev_device); fu_device_probe_invalidate(device); #endif return fu_device_probe(device, error); } static gboolean fu_udev_device_close(FuDevice *device, GError **error) { FuUdevDevice *self = FU_UDEV_DEVICE(device); FuUdevDevicePrivate *priv = GET_PRIVATE(self); if (priv->io_channel != NULL) { if (!fu_io_channel_shutdown(priv->io_channel, error)) return FALSE; } return TRUE; } /** * fu_udev_device_ioctl: * @self: a #FuUdevDevice * @request: request number * @buf: a buffer to use, which *must* be large enough for the request * @rc: (out) (nullable): the raw return value from the ioctl * @timeout: timeout in ms for the retry action, see %FU_UDEV_DEVICE_FLAG_IOCTL_RETRY * @error: (nullable): optional return location for an error * * Control a device using a low-level request. * * Returns: %TRUE for success * * Since: 1.8.2 **/ gboolean fu_udev_device_ioctl(FuUdevDevice *self, gulong request, guint8 *buf, gint *rc, guint timeout, GError **error) { #ifdef HAVE_IOCTL_H FuUdevDevicePrivate *priv = GET_PRIVATE(self); gint rc_tmp; g_autoptr(GTimer) timer = g_timer_new(); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE); g_return_val_if_fail(request != 0x0, FALSE); g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not open! */ if (priv->io_channel == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s [%s] has not been opened", fu_device_get_id(FU_DEVICE(self)), fu_device_get_name(FU_DEVICE(self))); return FALSE; } /* poll if required up to the timeout */ do { rc_tmp = ioctl(fu_io_channel_unix_get_fd(priv->io_channel), request, buf); if (rc_tmp >= 0) break; } while ((priv->flags & FU_UDEV_DEVICE_FLAG_IOCTL_RETRY) && (errno == EINTR || errno == EAGAIN) && g_timer_elapsed(timer, NULL) < timeout * 1000.f); if (rc != NULL) *rc = rc_tmp; if (rc_tmp < 0) { #ifdef HAVE_ERRNO_H if (errno == EPERM) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "permission denied"); return FALSE; } if (errno == ENOTTY) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "permission denied"); return FALSE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "ioctl error: %s [%i]", g_strerror(errno), errno); #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unspecified ioctl error"); #endif return FALSE; } return TRUE; #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as not found"); return FALSE; #endif } /** * fu_udev_device_pread: * @self: a #FuUdevDevice * @port: offset address * @buf: (in): data * @bufsz: size of @buf * @error: (nullable): optional return location for an error * * Read a buffer from a file descriptor at a given offset. * * Returns: %TRUE for success * * Since: 1.8.2 **/ gboolean fu_udev_device_pread(FuUdevDevice *self, goffset port, guint8 *buf, gsize bufsz, GError **error) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE); g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not open! */ if (priv->io_channel == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s [%s] has not been opened", fu_device_get_id(FU_DEVICE(self)), fu_device_get_name(FU_DEVICE(self))); return FALSE; } #ifdef HAVE_PWRITE if (pread(fu_io_channel_unix_get_fd(priv->io_channel), buf, bufsz, port) != (gssize)bufsz) { g_set_error(error, G_IO_ERROR, #ifdef HAVE_ERRNO_H g_io_error_from_errno(errno), #else G_IO_ERROR_FAILED, #endif "failed to read from port 0x%04x: %s", (guint)port, g_strerror(errno)); return FALSE; } return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as pread() is unavailable"); return FALSE; #endif } /** * fu_udev_device_seek: * @self: a #FuUdevDevice * @offset: offset address * @error: (nullable): optional return location for an error * * Seeks a file descriptor to a given offset. * * Returns: %TRUE for success * * Since: 1.7.2 **/ gboolean fu_udev_device_seek(FuUdevDevice *self, goffset offset, GError **error) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not open! */ if (priv->io_channel == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s [%s] has not been opened", fu_device_get_id(FU_DEVICE(self)), fu_device_get_name(FU_DEVICE(self))); return FALSE; } #ifdef HAVE_PWRITE if (lseek(fu_io_channel_unix_get_fd(priv->io_channel), offset, SEEK_SET) < 0) { g_set_error(error, G_IO_ERROR, #ifdef HAVE_ERRNO_H g_io_error_from_errno(errno), #else G_IO_ERROR_FAILED, #endif "failed to seek to 0x%04x: %s", (guint)offset, g_strerror(errno)); return FALSE; } return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as lseek() is unavailable"); return FALSE; #endif } /** * fu_udev_device_pwrite: * @self: a #FuUdevDevice * @port: offset address * @buf: (out): data * @bufsz: size of @data * @error: (nullable): optional return location for an error * * Write a buffer to a file descriptor at a given offset. * * Returns: %TRUE for success * * Since: 1.8.2 **/ gboolean fu_udev_device_pwrite(FuUdevDevice *self, goffset port, const guint8 *buf, gsize bufsz, GError **error) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not open! */ if (priv->io_channel == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s [%s] has not been opened", fu_device_get_id(FU_DEVICE(self)), fu_device_get_name(FU_DEVICE(self))); return FALSE; } #ifdef HAVE_PWRITE if (pwrite(fu_io_channel_unix_get_fd(priv->io_channel), buf, bufsz, port) != (gssize)bufsz) { g_set_error(error, G_IO_ERROR, #ifdef HAVE_ERRNO_H g_io_error_from_errno(errno), #else G_IO_ERROR_FAILED, #endif "failed to write to port %04x: %s", (guint)port, g_strerror(errno)); return FALSE; } return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as pwrite() is unavailable"); return FALSE; #endif } /** * fu_udev_device_get_parent_name * @self: a #FuUdevDevice * * Returns the name of the direct ancestor of this device * * Returns: string or NULL if unset or invalid * * Since: 1.4.5 **/ gchar * fu_udev_device_get_parent_name(FuUdevDevice *self) { #ifdef HAVE_GUDEV FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GUdevDevice) parent = NULL; g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL); parent = g_udev_device_get_parent(priv->udev_device); return parent == NULL ? NULL : g_strdup(g_udev_device_get_name(parent)); #else return NULL; #endif } /** * fu_udev_device_get_sysfs_attr: * @self: a #FuUdevDevice * @attr: name of attribute to get * @error: (nullable): optional return location for an error * * Reads an arbitrary sysfs attribute 'attr' associated with UDEV device * * Returns: string or NULL * * Since: 1.4.5 **/ const gchar * fu_udev_device_get_sysfs_attr(FuUdevDevice *self, const gchar *attr, GError **error) { #ifdef HAVE_GUDEV FuUdevDevicePrivate *priv = GET_PRIVATE(self); const gchar *result; g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL); g_return_val_if_fail(attr != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* nothing to do */ if (priv->udev_device == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "not yet initialized"); return NULL; } result = g_udev_device_get_sysfs_attr(priv->udev_device, attr); if (result == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "attribute %s returned no data", attr); return NULL; } return result; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "getting attributes is not supported as no GUdev support"); return NULL; #endif } /** * fu_udev_device_get_sysfs_attr_uint64: * @self: a #FuUdevDevice * @attr: name of attribute to get * @value: (out) (optional): value to return * @error: (nullable): optional return location for an error * * Reads an arbitrary sysfs attribute 'attr' associated with UDEV device as a uint64. * * Returns: %TRUE for success * * Since: 1.7.2 **/ gboolean fu_udev_device_get_sysfs_attr_uint64(FuUdevDevice *self, const gchar *attr, guint64 *value, GError **error) { const gchar *tmp; g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE); g_return_val_if_fail(attr != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); tmp = fu_udev_device_get_sysfs_attr(self, attr, error); if (tmp == NULL) return FALSE; return fu_strtoull(tmp, value, 0, G_MAXUINT64, error); } /** * fu_udev_device_write_sysfs: * @self: a #FuUdevDevice * @attribute: sysfs attribute name * @val: data to write into the attribute * @error: (nullable): optional return location for an error * * Writes data into a sysfs attribute * * Returns: %TRUE for success * * Since: 1.4.5 **/ gboolean fu_udev_device_write_sysfs(FuUdevDevice *self, const gchar *attribute, const gchar *val, GError **error) { #ifdef __linux__ ssize_t n; int r; int fd; g_autofree gchar *path = NULL; g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), FALSE); g_return_val_if_fail(attribute != NULL, FALSE); g_return_val_if_fail(val != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); path = g_build_filename(fu_udev_device_get_sysfs_path(self), attribute, NULL); fd = open(path, O_WRONLY | O_CLOEXEC); if (fd < 0) { g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errno), "could not open %s: %s", path, g_strerror(errno)); return FALSE; } do { n = write(fd, val, strlen(val)); if (n < 1 && errno != EINTR) { g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errno), "could not write to %s: %s", path, g_strerror(errno)); (void)close(fd); return FALSE; } } while (n < 1); r = close(fd); if (r < 0 && errno != EINTR) { g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errno), "could not close %s: %s", path, g_strerror(errno)); return FALSE; } return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "sysfs attributes not supported on Windows"); return FALSE; #endif } /** * fu_udev_device_get_devtype * @self: a #FuUdevDevice * * Returns the Udev device type * * Returns: device type specified in the uevent * * Since: 1.4.5 **/ const gchar * fu_udev_device_get_devtype(FuUdevDevice *self) { #ifdef HAVE_GUDEV FuUdevDevicePrivate *priv = GET_PRIVATE(self); return g_udev_device_get_devtype(priv->udev_device); #else return NULL; #endif } /** * fu_udev_device_get_siblings_with_subsystem * @self: a #FuUdevDevice * @subsystem: the name of a udev subsystem * * Get a list of devices that are siblings of self and have the * provided subsystem. * * Returns: (element-type FuUdevDevice) (transfer full): devices * * Since: 1.6.0 */ GPtrArray * fu_udev_device_get_siblings_with_subsystem(FuUdevDevice *self, const gchar *subsystem) { g_autoptr(GPtrArray) out = g_ptr_array_new_with_free_func(g_object_unref); #ifdef HAVE_GUDEV FuUdevDevicePrivate *priv = GET_PRIVATE(self); const gchar *udev_parent_path; g_autoptr(GUdevDevice) udev_parent = g_udev_device_get_parent(priv->udev_device); g_autoptr(GUdevClient) udev_client = g_udev_client_new(NULL); g_autolist(GUdevDevice) enumerated = g_udev_client_query_by_subsystem(udev_client, subsystem); /* we have no parent, and so no siblings are possible */ if (udev_parent == NULL) return g_steal_pointer(&out); udev_parent_path = g_udev_device_get_sysfs_path(udev_parent); for (GList *element = enumerated; element != NULL; element = element->next) { GUdevDevice *enumerated_device = G_UDEV_DEVICE(element->data); g_autoptr(GUdevDevice) enumerated_parent = NULL; const gchar *enumerated_parent_path; /* get parent, if it exists */ enumerated_parent = g_udev_device_get_parent(enumerated_device); if (enumerated_parent == NULL) break; enumerated_parent_path = g_udev_device_get_sysfs_path(enumerated_parent); /* if the sysfs path of self's parent is the same as that of the * located device's parent, they are siblings */ if (g_strcmp0(udev_parent_path, enumerated_parent_path) == 0) { g_ptr_array_add(out, fu_udev_device_new(fu_device_get_context(FU_DEVICE(self)), enumerated_device)); } } #endif return g_steal_pointer(&out); } /** * fu_udev_device_get_parent_with_subsystem * @self: a #FuUdevDevice * @subsystem: (nullable): the name of a udev subsystem * * Get the device that is a parent of self and has the provided subsystem. * * Returns: (transfer full): device, or %NULL * * Since: 1.7.6 */ FuUdevDevice * fu_udev_device_get_parent_with_subsystem(FuUdevDevice *self, const gchar *subsystem) { #ifdef HAVE_GUDEV FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GUdevDevice) device_tmp = NULL; if (subsystem == NULL) { device_tmp = g_udev_device_get_parent(priv->udev_device); } else { device_tmp = g_udev_device_get_parent_with_subsystem(priv->udev_device, subsystem, NULL); } if (device_tmp == NULL) return NULL; return fu_udev_device_new(fu_device_get_context(FU_DEVICE(self)), device_tmp); #else return NULL; #endif } /** * fu_udev_device_get_children_with_subsystem * @self: a #FuUdevDevice * @subsystem: the name of a udev subsystem * * Get a list of devices that are children of self and have the * provided subsystem. * * Returns: (element-type FuUdevDevice) (transfer full): devices * * Since: 1.6.2 */ GPtrArray * fu_udev_device_get_children_with_subsystem(FuUdevDevice *self, const gchar *const subsystem) { g_autoptr(GPtrArray) out = g_ptr_array_new_with_free_func(g_object_unref); #ifdef HAVE_GUDEV const gchar *self_path = fu_udev_device_get_sysfs_path(self); g_autoptr(GUdevClient) udev_client = g_udev_client_new(NULL); g_autoptr(GList) enumerated = g_udev_client_query_by_subsystem(udev_client, subsystem); for (GList *element = enumerated; element != NULL; element = element->next) { g_autoptr(GUdevDevice) enumerated_device = element->data; g_autoptr(GUdevDevice) enumerated_parent = NULL; const gchar *enumerated_parent_path; /* get parent, if it exists */ enumerated_parent = g_udev_device_get_parent(enumerated_device); if (enumerated_parent == NULL) break; enumerated_parent_path = g_udev_device_get_sysfs_path(enumerated_parent); /* enumerated device is a child of self if its parent is the * same as self */ if (g_strcmp0(self_path, enumerated_parent_path) == 0) { FuUdevDevice *dev = fu_udev_device_new(fu_device_get_context(FU_DEVICE(self)), g_steal_pointer(&enumerated_device)); g_ptr_array_add(out, dev); } } #endif return g_steal_pointer(&out); } /** * fu_udev_device_find_usb_device: * @self: a #FuUdevDevice * @error: (nullable): optional return location for an error * * Gets the matching #GUsbDevice for the #GUdevDevice. * * NOTE: This should never be stored in the device class as an instance variable, as the lifecycle * for `GUsbDevice` may be different to the `FuUdevDevice`. Every time the `GUsbDevice` is used * this function should be called. * * Returns: (transfer full): a #GUsbDevice, or NULL if unset or invalid * * Since: 1.8.7 **/ GUsbDevice * fu_udev_device_find_usb_device(FuUdevDevice *self, GError **error) { #if defined(HAVE_GUDEV) && defined(HAVE_GUSB) FuUdevDevicePrivate *priv = GET_PRIVATE(self); guint8 bus = 0; guint8 address = 0; g_autoptr(GUdevDevice) udev_device = g_object_ref(priv->udev_device); g_autoptr(GUsbContext) usb_ctx = NULL; g_autoptr(GUsbDevice) usb_device = NULL; g_return_val_if_fail(FU_IS_UDEV_DEVICE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* look at the current device and all the parent devices until we can find the USB data */ while (udev_device != NULL) { g_autoptr(GUdevDevice) udev_device_parent = NULL; bus = g_udev_device_get_sysfs_attr_as_int(udev_device, "busnum"); address = g_udev_device_get_sysfs_attr_as_int(udev_device, "devnum"); if (bus != 0 || address != 0) break; udev_device_parent = g_udev_device_get_parent(udev_device); g_set_object(&udev_device, udev_device_parent); } /* nothing found */ if (bus == 0x0 && address == 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No parent device with busnum and devnum"); return NULL; } /* match device */ usb_ctx = g_usb_context_new(error); if (usb_ctx == NULL) return NULL; usb_device = g_usb_context_find_by_bus_address(usb_ctx, bus, address, error); if (usb_device == NULL) return NULL; #if G_USB_CHECK_VERSION(0, 4, 1) g_usb_device_add_tag(usb_device, "is-transient"); #endif return g_steal_pointer(&usb_device); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as or is unavailable"); return NULL; #endif } static GBytes * fu_udev_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuUdevDevice *udev_device = FU_UDEV_DEVICE(device); guint number_reads = 0; g_autofree gchar *fn = NULL; g_autofree gchar *rom_fn = NULL; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GError) error_local = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GInputStream) stream = NULL; /* open the file */ rom_fn = g_build_filename(fu_udev_device_get_sysfs_path(udev_device), "rom", NULL); if (rom_fn == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Unable to read firmware from device"); return NULL; } /* open file */ file = g_file_new_for_path(rom_fn); stream = G_INPUT_STREAM(g_file_read(file, NULL, &error_local)); if (stream == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, error_local->message); return NULL; } /* we have to enable the read for devices */ fn = g_file_get_path(file); if (g_str_has_prefix(fn, "/sys")) { g_autoptr(GFileOutputStream) output_stream = NULL; output_stream = g_file_replace(file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error); if (output_stream == NULL) return NULL; if (g_output_stream_write(G_OUTPUT_STREAM(output_stream), "1", 1, NULL, error) < 0) return NULL; } /* ensure we got enough data to fill the buffer */ while (TRUE) { gssize sz; guint8 tmp[32 * 1024] = {0x0}; sz = g_input_stream_read(stream, tmp, sizeof(tmp), NULL, error); if (sz == 0) break; g_debug("ROM returned 0x%04x bytes", (guint)sz); if (sz < 0) return NULL; g_byte_array_append(buf, tmp, sz); /* check the firmware isn't serving us small chunks */ if (number_reads++ > 1024) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware not fulfilling requests"); return NULL; } } return g_bytes_new(buf->data, buf->len); } static void fu_udev_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuUdevDevice *self = FU_UDEV_DEVICE(object); FuUdevDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_UDEV_DEVICE: g_value_set_object(value, priv->udev_device); break; case PROP_SUBSYSTEM: g_value_set_string(value, priv->subsystem); break; case PROP_BIND_ID: g_value_set_string(value, priv->bind_id); break; case PROP_DRIVER: g_value_set_string(value, priv->driver); break; case PROP_DEVICE_FILE: g_value_set_string(value, priv->device_file); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_udev_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuUdevDevice *self = FU_UDEV_DEVICE(object); switch (prop_id) { case PROP_UDEV_DEVICE: fu_udev_device_set_dev(self, g_value_get_object(value)); break; case PROP_SUBSYSTEM: fu_udev_device_set_subsystem(self, g_value_get_string(value)); break; case PROP_BIND_ID: fu_udev_device_set_bind_id(self, g_value_get_string(value)); break; case PROP_DRIVER: fu_udev_device_set_driver(self, g_value_get_string(value)); break; case PROP_DEVICE_FILE: fu_udev_device_set_device_file(self, g_value_get_string(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_udev_device_finalize(GObject *object) { FuUdevDevice *self = FU_UDEV_DEVICE(object); FuUdevDevicePrivate *priv = GET_PRIVATE(self); g_free(priv->subsystem); g_free(priv->bind_id); g_free(priv->driver); g_free(priv->device_file); if (priv->udev_device != NULL) g_object_unref(priv->udev_device); if (priv->io_channel != NULL) g_object_unref(priv->io_channel); G_OBJECT_CLASS(fu_udev_device_parent_class)->finalize(object); } static void fu_udev_device_init(FuUdevDevice *self) { FuUdevDevicePrivate *priv = GET_PRIVATE(self); priv->flags = FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE; fu_device_set_acquiesce_delay(FU_DEVICE(self), 2500); } static void fu_udev_device_class_init(FuUdevDeviceClass *klass) { FuDeviceClass *device_class = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->finalize = fu_udev_device_finalize; object_class->get_property = fu_udev_device_get_property; object_class->set_property = fu_udev_device_set_property; device_class->probe = fu_udev_device_probe; device_class->rescan = fu_udev_device_rescan; device_class->incorporate = fu_udev_device_incorporate; device_class->open = fu_udev_device_open; device_class->close = fu_udev_device_close; device_class->to_string = fu_udev_device_to_string; device_class->bind_driver = fu_udev_device_bind_driver; device_class->unbind_driver = fu_udev_device_unbind_driver; device_class->probe_complete = fu_udev_device_probe_complete; device_class->dump_firmware = fu_udev_device_dump_firmware; /** * FuUdevDevice::changed: * @self: the #FuUdevDevice instance that emitted the signal * * The ::changed signal is emitted when the low-level GUdevDevice has changed. * * Since: 1.1.2 **/ signals[SIGNAL_CHANGED] = g_signal_new("changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * FuUdevDevice:udev-device: * * The low-level GUdevDevice. * * Since: 1.1.2 */ pspec = g_param_spec_object("udev-device", NULL, NULL, G_UDEV_TYPE_DEVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_UDEV_DEVICE, pspec); /** * FuUdevDevice:subsystem: * * The device subsystem. * * Since: 1.1.2 */ pspec = g_param_spec_string("subsystem", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_SUBSYSTEM, pspec); /** * FuUdevDevice:bind-id: * * The bind ID to use when binding a new driver. * * Since: 1.7.2 */ pspec = g_param_spec_string("bind-id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BIND_ID, pspec); /** * FuUdevDevice:driver: * * The driver being used for the device. * * Since: 1.5.3 */ pspec = g_param_spec_string("driver", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_DRIVER, pspec); /** * FuUdevDevice:device-file: * * The low level file to use for device access. * * Since: 1.3.1 */ pspec = g_param_spec_string("device-file", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_DEVICE_FILE, pspec); } /** * fu_udev_device_new: * @ctx: (nullable): a #FuContext * @udev_device: a #GUdevDevice * * Creates a new #FuUdevDevice. * * Returns: (transfer full): a #FuUdevDevice * * Since: 1.8.2 **/ FuUdevDevice * fu_udev_device_new(FuContext *ctx, GUdevDevice *udev_device) { return g_object_new(FU_TYPE_UDEV_DEVICE, "context", ctx, "udev-device", udev_device, NULL); } fwupd-1.9.16/libfwupdplugin/fu-udev-device.h000066400000000000000000000165061460375044200207660ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #ifdef HAVE_GUDEV #include #else #define G_UDEV_TYPE_DEVICE G_TYPE_OBJECT #define GUdevDevice GObject #endif #ifdef HAVE_GUSB #include #else #define GUsbContext GObject #define GUsbDevice GObject #endif #include "fu-io-channel.h" #include "fu-plugin.h" #define FU_TYPE_UDEV_DEVICE (fu_udev_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuUdevDevice, fu_udev_device, FU, UDEV_DEVICE, FuDevice) struct _FuUdevDeviceClass { FuDeviceClass parent_class; }; /** * FuUdevDeviceFlags: * @FU_UDEV_DEVICE_FLAG_NONE: No flags set * @FU_UDEV_DEVICE_FLAG_OPEN_READ: Open the device read-only * @FU_UDEV_DEVICE_FLAG_OPEN_WRITE: Open the device write-only * @FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT: Get the vendor ID from a parent or grandparent * @FU_UDEV_DEVICE_FLAG_USE_CONFIG: Read and write from the device config * @FU_UDEV_DEVICE_FLAG_OPEN_NONBLOCK: Open nonblocking, e.g. O_NONBLOCK * @FU_UDEV_DEVICE_FLAG_OPEN_SYNC: Open sync, e.g. O_SYNC * @FU_UDEV_DEVICE_FLAG_IOCTL_RETRY: Retry the ioctl() call when required * * Flags used when opening the device using fu_device_open(). **/ typedef enum { FU_UDEV_DEVICE_FLAG_NONE = 0, FU_UDEV_DEVICE_FLAG_OPEN_READ = 1 << 0, FU_UDEV_DEVICE_FLAG_OPEN_WRITE = 1 << 1, FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT = 1 << 2, FU_UDEV_DEVICE_FLAG_USE_CONFIG = 1 << 3, FU_UDEV_DEVICE_FLAG_OPEN_NONBLOCK = 1 << 4, FU_UDEV_DEVICE_FLAG_OPEN_SYNC = 1 << 5, FU_UDEV_DEVICE_FLAG_IOCTL_RETRY = 1 << 6, /*< private >*/ FU_UDEV_DEVICE_FLAG_LAST } FuUdevDeviceFlags; /** * FuPciBaseCls: * @FU_PCI_BASE_CLS_OLD: Device from before classes were defined * @FU_PCI_BASE_CLS_MASS_STORAGE: Mass Storage Controller * @FU_PCI_BASE_CLS_NETWORK: Network controller * @FU_PCI_BASE_CLS_DISPLAY: Display controller * @FU_PCI_BASE_CLS_MULTIMEDIA: Multimedia controller * @FU_PCI_BASE_CLS_MEMORY: Memory controller * @FU_PCI_BASE_CLS_BRIDGE: Bridge * @FU_PCI_BASE_CLS_SIMPLE_COMMUNICATION: Simple communications controller * @FU_PCI_BASE_CLS_BASE: Base system peripheral * @FU_PCI_BASE_CLS_INPUT: Input device * @FU_PCI_BASE_CLS_DOCKING: Docking station * @FU_PCI_BASE_CLS_PROCESSORS: Processor * @FU_PCI_BASE_CLS_SERIAL_BUS: Serial bus controller * @FU_PCI_BASE_CLS_WIRELESS: Wireless controller * @FU_PCI_BASE_CLS_INTELLIGENT_IO: Intelligent IO controller * @FU_PCI_BASE_CLS_SATELLITE: Satellite controller * @FU_PCI_BASE_CLS_ENCRYPTION: Encryption/Decryption controller * @FU_PCI_BASE_CLS_SIGNAL_PROCESSING: Data acquisition and signal processing controller * @FU_PCI_BASE_CLS_ACCELERATOR: Processing accelerator * @FU_PCI_BASE_CLS_NON_ESSENTIAL: Non-essential instrumentation * @FU_PCI_BASE_CLS_UNDEFINED: Device doesn't fit any defined class * * PCI base class types returned by fu_udev_device_get_cls(). **/ typedef enum { FU_PCI_BASE_CLS_OLD, FU_PCI_BASE_CLS_MASS_STORAGE, FU_PCI_BASE_CLS_NETWORK, FU_PCI_BASE_CLS_DISPLAY, FU_PCI_BASE_CLS_MULTIMEDIA, FU_PCI_BASE_CLS_MEMORY, FU_PCI_BASE_CLS_BRIDGE, FU_PCI_BASE_CLS_SIMPLE_COMMUNICATION, FU_PCI_BASE_CLS_BASE, FU_PCI_BASE_CLS_INPUT, FU_PCI_BASE_CLS_DOCKING, FU_PCI_BASE_CLS_PROCESSORS, FU_PCI_BASE_CLS_SERIAL_BUS, FU_PCI_BASE_CLS_WIRELESS, FU_PCI_BASE_CLS_INTELLIGENT_IO, FU_PCI_BASE_CLS_SATELLITE, FU_PCI_BASE_CLS_ENCRYPTION, FU_PCI_BASE_CLS_SIGNAL_PROCESSING, FU_PCI_BASE_CLS_ACCELERATOR, FU_PCI_BASE_CLS_NON_ESSENTIAL, FU_PCI_BASE_CLS_UNDEFINED = 0xff } FuPciBaseCls; FuUdevDevice * fu_udev_device_new(FuContext *ctx, GUdevDevice *udev_device) G_GNUC_NON_NULL(1, 2); GUdevDevice * fu_udev_device_get_dev(FuUdevDevice *self) G_GNUC_NON_NULL(1); void fu_udev_device_set_dev(FuUdevDevice *self, GUdevDevice *udev_device) G_GNUC_NON_NULL(1); const gchar * fu_udev_device_get_device_file(FuUdevDevice *self) G_GNUC_NON_NULL(1); void fu_udev_device_set_device_file(FuUdevDevice *self, const gchar *device_file) G_GNUC_NON_NULL(1); const gchar * fu_udev_device_get_sysfs_path(FuUdevDevice *self) G_GNUC_NON_NULL(1); const gchar * fu_udev_device_get_subsystem(FuUdevDevice *self) G_GNUC_NON_NULL(1); const gchar * fu_udev_device_get_bind_id(FuUdevDevice *self) G_GNUC_NON_NULL(1); void fu_udev_device_set_bind_id(FuUdevDevice *self, const gchar *bind_id) G_GNUC_NON_NULL(1); const gchar * fu_udev_device_get_driver(FuUdevDevice *self) G_GNUC_NON_NULL(1); gboolean fu_udev_device_is_pci_base_cls(FuUdevDevice *self, FuPciBaseCls cls) G_GNUC_NON_NULL(1); guint32 fu_udev_device_get_cls(FuUdevDevice *self) G_GNUC_NON_NULL(1); guint16 fu_udev_device_get_vendor(FuUdevDevice *self) G_GNUC_NON_NULL(1); guint16 fu_udev_device_get_model(FuUdevDevice *self) G_GNUC_NON_NULL(1); guint16 fu_udev_device_get_subsystem_vendor(FuUdevDevice *self) G_GNUC_NON_NULL(1); guint16 fu_udev_device_get_subsystem_model(FuUdevDevice *self) G_GNUC_NON_NULL(1); guint8 fu_udev_device_get_revision(FuUdevDevice *self) G_GNUC_NON_NULL(1); guint64 fu_udev_device_get_number(FuUdevDevice *self) G_GNUC_NON_NULL(1); guint fu_udev_device_get_slot_depth(FuUdevDevice *self, const gchar *subsystem) G_GNUC_NON_NULL(1); gboolean fu_udev_device_set_physical_id(FuUdevDevice *self, const gchar *subsystems, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_udev_device_set_logical_id(FuUdevDevice *self, const gchar *subsystem, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); void fu_udev_device_set_flags(FuUdevDevice *self, FuUdevDeviceFlags flags) G_GNUC_NON_NULL(1); FuIOChannel * fu_udev_device_get_io_channel(FuUdevDevice *self) G_GNUC_NON_NULL(1); gboolean fu_udev_device_ioctl(FuUdevDevice *self, gulong request, guint8 *buf, gint *rc, guint timeout, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_udev_device_pwrite(FuUdevDevice *self, goffset port, const guint8 *buf, gsize bufsz, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_udev_device_pread(FuUdevDevice *self, goffset port, guint8 *buf, gsize bufsz, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_udev_device_seek(FuUdevDevice *self, goffset offset, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); const gchar * fu_udev_device_get_sysfs_attr(FuUdevDevice *self, const gchar *attr, GError **error) G_GNUC_NON_NULL(1); gboolean fu_udev_device_get_sysfs_attr_uint64(FuUdevDevice *self, const gchar *attr, guint64 *value, GError **error) G_GNUC_NON_NULL(1); gchar * fu_udev_device_get_parent_name(FuUdevDevice *self) G_GNUC_NON_NULL(1); gboolean fu_udev_device_write_sysfs(FuUdevDevice *self, const gchar *attribute, const gchar *val, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1, 2, 3); const gchar * fu_udev_device_get_devtype(FuUdevDevice *self) G_GNUC_NON_NULL(1); GPtrArray * fu_udev_device_get_siblings_with_subsystem(FuUdevDevice *self, const gchar *subsystem) G_GNUC_NON_NULL(1, 2); GPtrArray * fu_udev_device_get_children_with_subsystem(FuUdevDevice *self, const gchar *subsystem) G_GNUC_NON_NULL(1, 2); FuUdevDevice * fu_udev_device_get_parent_with_subsystem(FuUdevDevice *self, const gchar *subsystem) G_GNUC_NON_NULL(1); GUsbDevice * fu_udev_device_find_usb_device(FuUdevDevice *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-usb-device-ds20.c000066400000000000000000000167071460375044200213600ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuUsbDeviceDs20" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-dump.h" #include "fu-usb-device-ds20-struct.h" #include "fu-usb-device-ds20.h" /** * FuUsbDeviceDs20: * * A USB DS20 BOS descriptor. * * See also: [class@FuUsbDevice] */ typedef struct { guint32 version_lowest; } FuUsbDeviceDs20Private; G_DEFINE_TYPE_WITH_PRIVATE(FuUsbDeviceDs20, fu_usb_device_ds20, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_usb_device_ds20_get_instance_private(o)) typedef struct { guint32 platform_ver; guint16 total_length; guint8 vendor_code; guint8 alt_code; } FuUsbDeviceDs20Item; /** * fu_usb_device_ds20_set_version_lowest: * @self: a #FuUsbDeviceDs20 * @version_lowest: version number * * Sets the lowest possible `platform_ver` for a DS20 descriptor. * * Since: 1.8.5 **/ void fu_usb_device_ds20_set_version_lowest(FuUsbDeviceDs20 *self, guint32 version_lowest) { FuUsbDeviceDs20Private *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_USB_DEVICE_DS20(self)); priv->version_lowest = version_lowest; } /** * fu_usb_device_ds20_apply_to_device: * @self: a #FuUsbDeviceDs20 * @device: a #FuUsbDevice * @error: (nullable): optional return location for an error * * Sets the DS20 descriptor onto @device. * * Returns: %TRUE for success * * Since: 1.8.5 **/ gboolean fu_usb_device_ds20_apply_to_device(FuUsbDeviceDs20 *self, FuUsbDevice *device, GError **error) { #ifdef HAVE_GUSB FuUsbDeviceDs20Class *klass = FU_USB_DEVICE_DS20_GET_CLASS(self); GUsbDevice *usb_device = fu_usb_device_get_dev(device); gsize actual_length = 0; gsize total_length = fu_firmware_get_size(FU_FIRMWARE(self)); guint8 vendor_code = fu_firmware_get_idx(FU_FIRMWARE(self)); g_autofree guint8 *buf = g_malloc0(total_length); g_autoptr(GBytes) blob = NULL; g_return_val_if_fail(FU_IS_USB_DEVICE_DS20(self), FALSE); g_return_val_if_fail(FU_IS_USB_DEVICE(device), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, vendor_code, /* bRequest */ 0x0, /* wValue */ 0x07, /* wIndex */ buf, total_length, &actual_length, 500, NULL, /* cancellable */ error)) { g_prefix_error(error, "requested vendor code 0x%02x: ", vendor_code); return FALSE; } if (total_length != actual_length) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "expected 0x%x bytes from vendor code 0x%02x, but got 0x%x", (guint)total_length, vendor_code, (guint)actual_length); return FALSE; } /* debug */ fu_dump_raw(G_LOG_DOMAIN, "PlatformCapabilityOs20", buf, actual_length); /* FuUsbDeviceDs20->parse */ blob = g_bytes_new_take(g_steal_pointer(&buf), actual_length); return klass->parse(self, blob, device, error); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "GUsb support is unavailable"); return FALSE; #endif } static gboolean fu_usb_device_ds20_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { g_autoptr(GByteArray) st = NULL; g_autofree gchar *guid_str = NULL; /* matches the correct UUID */ st = fu_struct_ds20_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; guid_str = fwupd_guid_to_string(fu_struct_ds20_get_guid(st), FWUPD_GUID_FLAG_MIXED_ENDIAN); if (g_strcmp0(guid_str, fu_firmware_get_id(firmware)) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid UUID for DS20, expected %s", fu_firmware_get_id(firmware)); return FALSE; } /* success */ return TRUE; } static gint fu_usb_device_ds20_sort_by_platform_ver_cb(gconstpointer a, gconstpointer b) { FuUsbDeviceDs20Item *ds1 = *((FuUsbDeviceDs20Item **)a); FuUsbDeviceDs20Item *ds2 = *((FuUsbDeviceDs20Item **)b); if (ds1->platform_ver < ds2->platform_ver) return -1; if (ds1->platform_ver > ds2->platform_ver) return 1; return 0; } static gboolean fu_usb_device_ds20_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuUsbDeviceDs20 *self = FU_USB_DEVICE_DS20(firmware); FuUsbDeviceDs20Private *priv = GET_PRIVATE(self); guint version_lowest = fu_firmware_get_version_raw(firmware); g_autoptr(GPtrArray) dsinfos = g_ptr_array_new_with_free_func(g_free); for (gsize off = 0; off < g_bytes_get_size(fw); off += FU_STRUCT_DS20_SIZE) { g_autofree FuUsbDeviceDs20Item *dsinfo = g_new0(FuUsbDeviceDs20Item, 1); g_autoptr(GByteArray) st = NULL; /* parse */ st = fu_struct_ds20_parse_bytes(fw, off, error); if (st == NULL) return FALSE; dsinfo->platform_ver = fu_struct_ds20_get_platform_ver(st); dsinfo->total_length = fu_struct_ds20_get_total_length(st); dsinfo->vendor_code = fu_struct_ds20_get_vendor_code(st); dsinfo->alt_code = fu_struct_ds20_get_alt_code(st); g_debug("PlatformVersion=0x%08x, TotalLength=0x%04x, VendorCode=0x%02x, " "AltCode=0x%02x", dsinfo->platform_ver, dsinfo->total_length, dsinfo->vendor_code, dsinfo->alt_code); g_ptr_array_add(dsinfos, g_steal_pointer(&dsinfo)); } /* sort by platform_ver, highest first */ g_ptr_array_sort(dsinfos, fu_usb_device_ds20_sort_by_platform_ver_cb); /* find the newest info that's not newer than the lowest version */ for (guint i = 0; i < dsinfos->len; i++) { FuUsbDeviceDs20Item *dsinfo = g_ptr_array_index(dsinfos, i); /* not valid */ if (dsinfo->platform_ver == 0x0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "invalid platform version 0x%08x", dsinfo->platform_ver); return FALSE; } if (dsinfo->platform_ver < priv->version_lowest) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "invalid platform version 0x%08x, expected >= 0x%08x", dsinfo->platform_ver, priv->version_lowest); return FALSE; } /* dwVersion is effectively the minimum version */ if (dsinfo->platform_ver <= version_lowest) { fu_firmware_set_size(firmware, dsinfo->total_length); fu_firmware_set_idx(firmware, dsinfo->vendor_code); return TRUE; } } /* failed */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no supported platform version"); return FALSE; } static GByteArray * fu_usb_device_ds20_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) st = fu_struct_ds20_new(); fwupd_guid_t guid = {0x0}; /* pack */ if (!fwupd_guid_from_string(fu_firmware_get_id(firmware), &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return NULL; fu_struct_ds20_set_guid(st, &guid); fu_struct_ds20_set_platform_ver(st, fu_firmware_get_version_raw(firmware)); fu_struct_ds20_set_total_length(st, fu_firmware_get_size(firmware)); fu_struct_ds20_set_vendor_code(st, fu_firmware_get_idx(firmware)); return g_steal_pointer(&st); } static void fu_usb_device_ds20_init(FuUsbDeviceDs20 *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE); } static void fu_usb_device_ds20_class_init(FuUsbDeviceDs20Class *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_usb_device_ds20_check_magic; klass_firmware->parse = fu_usb_device_ds20_parse; klass_firmware->write = fu_usb_device_ds20_write; } fwupd-1.9.16/libfwupdplugin/fu-usb-device-ds20.h000066400000000000000000000014031460375044200213500ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #include "fu-usb-device.h" #define FU_TYPE_USB_DEVICE_DS20 (fu_usb_device_ds20_get_type()) G_DECLARE_DERIVABLE_TYPE(FuUsbDeviceDs20, fu_usb_device_ds20, FU, USB_DEVICE_DS20, FuFirmware) struct _FuUsbDeviceDs20Class { FuFirmwareClass parent_class; gboolean (*parse)(FuUsbDeviceDs20 *self, GBytes *blob, FuUsbDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT; }; void fu_usb_device_ds20_set_version_lowest(FuUsbDeviceDs20 *self, guint32 version_lowest) G_GNUC_NON_NULL(1); gboolean fu_usb_device_ds20_apply_to_device(FuUsbDeviceDs20 *self, FuUsbDevice *device, GError **error) G_GNUC_NON_NULL(1, 2); fwupd-1.9.16/libfwupdplugin/fu-usb-device-ds20.rs000066400000000000000000000005301460375044200215450ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, ParseBytes)] struct Ds20 { _reserved: u8, guid: Guid, platform_ver: u32le, total_length: u16le, vendor_code: u8, alt_code: u8, } #[derive(New, ParseBytes)] struct MsDs20 { size: u16le, type: u16le, } fwupd-1.9.16/libfwupdplugin/fu-usb-device-fw-ds20.c000066400000000000000000000071751460375044200217710ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuUsbDeviceDs20" #include "config.h" #include "fu-device-private.h" #include "fu-string.h" #include "fu-usb-device-fw-ds20.h" #include "fu-version.h" struct _FuUsbDeviceFwDs20 { FuUsbDeviceDs20 parent_instance; }; G_DEFINE_TYPE(FuUsbDeviceFwDs20, fu_usb_device_fw_ds20, FU_TYPE_USB_DEVICE_DS20) #define DS20_VERSION_LOWEST ((1u << 16) | (8u << 8) | 5u) #define DS20_VERSION_CURRENT \ ((((guint32)FU_MAJOR_VERSION) << 16) | (((guint32)FU_MINOR_VERSION) << 8) | \ ((guint)FU_MICRO_VERSION)) static gboolean fu_usb_device_fw_ds20_parse(FuUsbDeviceDs20 *self, GBytes *blob, FuUsbDevice *device, GError **error) { gsize bufsz = 0; gsize bufsz_safe = 0; const guint8 *buf = g_bytes_get_data(blob, &bufsz); g_auto(GStrv) lines = NULL; /* only accept Linux line-endings */ if (g_strstr_len((const gchar *)buf, bufsz, "\r") != NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Windows line endings are not supported"); return FALSE; } /* find the first NUL, if one exists */ for (gsize i = 1; i < bufsz; i++) { if (buf[i] == '\0') { bufsz_safe = i - 1; break; } } /* no NUL is unexpected, but fine */ if (bufsz_safe == 0) bufsz_safe = bufsz; if (!g_utf8_validate((const gchar *)buf, (gssize)bufsz_safe, NULL)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "DS20 descriptor is not valid UTF-8"); return FALSE; } /* add payload for ->export() */ fu_firmware_set_bytes(FU_FIRMWARE(self), blob); /* split into lines */ lines = fu_strsplit((const gchar *)buf, bufsz_safe, "\n", -1); for (guint i = 0; lines[i] != NULL; i++) { g_autofree gchar *key = NULL; g_autofree gchar *value = NULL; g_auto(GStrv) kv = NULL; if (lines[i][0] == '\0') continue; if (g_str_has_prefix(lines[i], "[") && g_str_has_suffix(lines[i], "]")) { g_debug("ignoring DS-20 group header: %s", lines[i]); continue; } kv = g_strsplit(lines[i], "=", 2); if (g_strv_length(kv) < 2) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "expected key=value for '%s'", lines[i]); return FALSE; } key = fu_strstrip(kv[0]); if (g_strcmp0(key, kv[0]) != 0) g_debug("removing DS-20 whitespace '%s'", kv[0]); value = fu_strstrip(kv[1]); if (g_strcmp0(value, kv[1]) != 0) g_debug("removing DS-20 whitespace '%s'", kv[1]); /* it's fine to be strict here, as we checked the fwupd version was new enough in * FuUsbDeviceDs20Item */ g_debug("setting ds20 device quirk '%s'='%s'", key, value); if (!fu_device_set_quirk_kv(FU_DEVICE(device), key, value, error)) return FALSE; } /* success */ return TRUE; } static void fu_usb_device_fw_ds20_class_init(FuUsbDeviceFwDs20Class *klass) { FuUsbDeviceDs20Class *usb_device_ds20_klass = FU_USB_DEVICE_DS20_CLASS(klass); usb_device_ds20_klass->parse = fu_usb_device_fw_ds20_parse; } static void fu_usb_device_fw_ds20_init(FuUsbDeviceFwDs20 *self) { fu_firmware_set_version_raw(FU_FIRMWARE(self), DS20_VERSION_CURRENT); fu_usb_device_ds20_set_version_lowest(FU_USB_DEVICE_DS20(self), DS20_VERSION_LOWEST); fu_firmware_set_id(FU_FIRMWARE(self), "010aec63-f574-52cd-9dda-2852550d94f0"); } /** * fu_usb_device_fw_ds20_new: * * Creates a new #FuUsbDeviceFwDs20. * * Returns: (transfer full): a #FuFirmware * * Since: 1.8.5 **/ FuFirmware * fu_usb_device_fw_ds20_new(void) { return g_object_new(FU_TYPE_USB_DEVICE_FW_DS20, NULL); } fwupd-1.9.16/libfwupdplugin/fu-usb-device-fw-ds20.h000066400000000000000000000006241460375044200217660ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-usb-device-ds20.h" #define FU_TYPE_USB_DEVICE_FW_DS20 (fu_usb_device_fw_ds20_get_type()) G_DECLARE_FINAL_TYPE(FuUsbDeviceFwDs20, fu_usb_device_fw_ds20, FU, USB_DEVICE_FW_DS20, FuUsbDeviceDs20) FuFirmware * fu_usb_device_fw_ds20_new(void); fwupd-1.9.16/libfwupdplugin/fu-usb-device-ms-ds20.c000066400000000000000000000060041460375044200217620ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuUsbDeviceDs20" #include "config.h" #include "fu-usb-device-ds20-struct.h" #include "fu-usb-device-ms-ds20.h" struct _FuUsbDeviceMsDs20 { FuUsbDeviceDs20 parent_instance; }; G_DEFINE_TYPE(FuUsbDeviceMsDs20, fu_usb_device_ms_ds20, FU_TYPE_USB_DEVICE_DS20) #define USB_OS_20_SET_HEADER_DESCRIPTOR 0x00 #define USB_OS_20_SUBSET_HEADER_CONFIGURATION 0x01 #define USB_OS_20_SUBSET_HEADER_FUNCTION 0x02 #define USB_OS_20_FEATURE_COMPATIBLE_ID 0x03 #define USB_OS_20_FEATURE_REG_PROPERTY 0x04 #define USB_OS_20_FEATURE_MIN_RESUME_TIME 0x05 #define USB_OS_20_FEATURE_MODEL_ID 0x06 #define USB_OS_20_FEATURE_CCGP_DEVICE 0x07 #define USB_OS_20_FEATURE_VENDOR_REVISION 0x08 static const gchar * fu_usb_device_os20_type_to_string(guint16 type) { if (type == USB_OS_20_SET_HEADER_DESCRIPTOR) return "set-header-descriptor"; if (type == USB_OS_20_SUBSET_HEADER_CONFIGURATION) return "subset-header-configuration"; if (type == USB_OS_20_SUBSET_HEADER_FUNCTION) return "subset-header-function"; if (type == USB_OS_20_FEATURE_COMPATIBLE_ID) return "feature-compatible-id"; if (type == USB_OS_20_FEATURE_REG_PROPERTY) return "feature-reg-property"; if (type == USB_OS_20_FEATURE_MIN_RESUME_TIME) return "feature-min-resume-time"; if (type == USB_OS_20_FEATURE_MODEL_ID) return "feature-model-id"; if (type == USB_OS_20_FEATURE_CCGP_DEVICE) return "feature-ccgp-device"; if (type == USB_OS_20_FEATURE_VENDOR_REVISION) return "feature-vendor-revision"; return NULL; } static gboolean fu_usb_device_ms_ds20_parse(FuUsbDeviceDs20 *self, GBytes *blob, FuUsbDevice *device, GError **error) { /* get length and type only */ for (gsize offset = 0; offset < g_bytes_get_size(blob);) { guint16 desc_sz; guint16 desc_type; g_autoptr(GByteArray) st = NULL; st = fu_struct_ms_ds20_parse_bytes(blob, offset, error); if (st == NULL) return FALSE; desc_sz = fu_struct_ms_ds20_get_size(st); if (desc_sz == 0) break; desc_type = fu_struct_ms_ds20_get_type(st); g_debug("USB OS descriptor type 0x%04x [%s], length 0x%04x", desc_type, fu_usb_device_os20_type_to_string(desc_type), desc_sz); offset += desc_sz; } /* success */ return TRUE; } static void fu_usb_device_ms_ds20_class_init(FuUsbDeviceMsDs20Class *klass) { FuUsbDeviceDs20Class *usb_device_ds20_klass = FU_USB_DEVICE_DS20_CLASS(klass); usb_device_ds20_klass->parse = fu_usb_device_ms_ds20_parse; } static void fu_usb_device_ms_ds20_init(FuUsbDeviceMsDs20 *self) { fu_firmware_set_version_raw(FU_FIRMWARE(self), 0x06030000); /* Windows 8.1 */ fu_firmware_set_id(FU_FIRMWARE(self), "d8dd60df-4589-4cc7-9cd2-659d9e648a9f"); } /** * fu_usb_device_ms_ds20_new: * * Creates a new #FuUsbDeviceMsDs20. * * Returns: (transfer full): a #FuFirmware * * Since: 1.8.5 **/ FuFirmware * fu_usb_device_ms_ds20_new(void) { return g_object_new(FU_TYPE_USB_DEVICE_MS_DS20, NULL); } fwupd-1.9.16/libfwupdplugin/fu-usb-device-ms-ds20.h000066400000000000000000000006241460375044200217710ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-usb-device-ds20.h" #define FU_TYPE_USB_DEVICE_MS_DS20 (fu_usb_device_ms_ds20_get_type()) G_DECLARE_FINAL_TYPE(FuUsbDeviceMsDs20, fu_usb_device_ms_ds20, FU, USB_DEVICE_MS_DS20, FuUsbDeviceDs20) FuFirmware * fu_usb_device_ms_ds20_new(void); fwupd-1.9.16/libfwupdplugin/fu-usb-device-private.h000066400000000000000000000004631460375044200222570ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-usb-device.h" #define FU_USB_DEVICE_EMULATION_TAG "org.freedesktop.fwupd.emulation.v1" const gchar * fu_usb_device_get_platform_id(FuUsbDevice *self) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-usb-device.c000066400000000000000000001031301460375044200205750ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuUsbDevice" #include "config.h" #include "fu-device-private.h" #include "fu-dump.h" #include "fu-mem.h" #include "fu-string.h" #include "fu-usb-device-fw-ds20.h" #include "fu-usb-device-ms-ds20.h" #include "fu-usb-device-private.h" /** * FuUsbDevice: * * A USB device. * * See also: [class@FuDevice], [class@FuHidDevice] */ typedef struct { GUsbDevice *usb_device; gint configuration; GPtrArray *interfaces; /* nullable, element-type FuUsbDeviceInterface */ guint claim_retry_count; guint open_retry_count; FuDeviceLocker *usb_device_locker; } FuUsbDevicePrivate; typedef struct { guint8 number; gboolean claimed; } FuUsbDeviceInterface; G_DEFINE_TYPE_WITH_PRIVATE(FuUsbDevice, fu_usb_device, FU_TYPE_DEVICE) enum { PROP_0, PROP_USB_DEVICE, PROP_LAST }; #define GET_PRIVATE(o) (fu_usb_device_get_instance_private(o)) #define FU_USB_DEVICE_CLAIM_INTERFACE_DELAY 500 /* ms */ #define FU_USB_DEVICE_OPEN_DELAY 50 /* ms */ static void fu_usb_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuUsbDevice *device = FU_USB_DEVICE(object); FuUsbDevicePrivate *priv = GET_PRIVATE(device); switch (prop_id) { case PROP_USB_DEVICE: g_value_set_object(value, priv->usb_device); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_usb_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuUsbDevice *device = FU_USB_DEVICE(object); switch (prop_id) { case PROP_USB_DEVICE: fu_usb_device_set_dev(device, g_value_get_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_usb_device_finalize(GObject *object) { FuUsbDevice *device = FU_USB_DEVICE(object); FuUsbDevicePrivate *priv = GET_PRIVATE(device); if (priv->usb_device_locker != NULL) g_object_unref(priv->usb_device_locker); if (priv->usb_device != NULL) g_object_unref(priv->usb_device); if (priv->interfaces != NULL) g_ptr_array_unref(priv->interfaces); G_OBJECT_CLASS(fu_usb_device_parent_class)->finalize(object); } #if G_USB_CHECK_VERSION(0, 4, 5) static void fu_usb_device_flags_notify_cb(FuDevice *device, GParamSpec *pspec, gpointer user_data) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); if (usb_device == NULL) return; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG)) g_usb_device_add_tag(usb_device, FU_USB_DEVICE_EMULATION_TAG); } #endif static void fu_usb_device_init(FuUsbDevice *device) { FuUsbDevicePrivate *priv = GET_PRIVATE(device); priv->configuration = -1; fu_device_set_acquiesce_delay(FU_DEVICE(device), 2500); #ifdef HAVE_GUSB fu_device_retry_add_recovery(FU_DEVICE(device), G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE, NULL); fu_device_retry_add_recovery(FU_DEVICE(device), G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_PERMISSION_DENIED, NULL); #endif } static void fu_usb_device_constructed(GObject *obj) { #if G_USB_CHECK_VERSION(0, 4, 5) FuUsbDevice *self = FU_USB_DEVICE(obj); /* copy this to the GUsbDevice */ g_signal_connect(FU_DEVICE(self), "notify::flags", G_CALLBACK(fu_usb_device_flags_notify_cb), NULL); #endif } /** * fu_usb_device_set_claim_retry_count: * @self: a #FuUsbDevice * @claim_retry_count: integer * * Sets the number of tries we should attempt when claiming the device. * Applies to all interfaces associated with this device. * * Since: 1.9.10 **/ void fu_usb_device_set_claim_retry_count(FuUsbDevice *self, guint claim_retry_count) { FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_USB_DEVICE(self)); priv->claim_retry_count = claim_retry_count; } /** * fu_usb_device_get_claim_retry_count: * @self: a #FuUsbDevice * * Gets the number of tries we should attempt when claiming the device. * * Returns: integer, or `0` if no attempt should be made. * * Since: 1.9.10 **/ guint fu_usb_device_get_claim_retry_count(FuUsbDevice *self) { FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_USB_DEVICE(self), G_MAXUINT); return priv->claim_retry_count; } /** * fu_usb_device_set_open_retry_count: * @self: a #FuUsbDevice * @open_retry_count: integer * * Sets the number of tries we should attempt when opening the device. * * Since: 1.9.9 **/ void fu_usb_device_set_open_retry_count(FuUsbDevice *self, guint open_retry_count) { FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_USB_DEVICE(self)); priv->open_retry_count = open_retry_count; } /** * fu_usb_device_get_open_retry_count: * @self: a #FuUsbDevice * * Sets the number of tries we should attempt when opening the device. * * Returns: integer, or `0` if no attempt should be made. * * Since: 1.9.9 **/ guint fu_usb_device_get_open_retry_count(FuUsbDevice *self) { FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_USB_DEVICE(self), G_MAXUINT); return priv->open_retry_count; } /** * fu_usb_device_is_open: * @device: a #FuUsbDevice * * Finds out if a USB device is currently open. * * Returns: %TRUE if the device is open. * * Since: 1.0.3 **/ gboolean fu_usb_device_is_open(FuUsbDevice *device) { FuUsbDevicePrivate *priv = GET_PRIVATE(device); g_return_val_if_fail(FU_IS_USB_DEVICE(device), FALSE); return priv->usb_device_locker != NULL; } /** * fu_usb_device_set_configuration: * @device: a #FuUsbDevice * @configuration: the configuration value to set * * Set the active bConfigurationValue for the device. * * Since: 1.7.4 **/ void fu_usb_device_set_configuration(FuUsbDevice *device, gint configuration) { FuUsbDevicePrivate *priv = GET_PRIVATE(device); g_return_if_fail(FU_IS_USB_DEVICE(device)); priv->configuration = configuration; } /** * fu_usb_device_add_interface: * @device: a #FuUsbDevice * @number: bInterfaceNumber of the interface * * Adds an interface that will be claimed on `->open()` and released on `->close()`. * * Since: 1.7.4 **/ void fu_usb_device_add_interface(FuUsbDevice *device, guint8 number) { FuUsbDevicePrivate *priv = GET_PRIVATE(device); FuUsbDeviceInterface *iface; g_return_if_fail(FU_IS_USB_DEVICE(device)); if (priv->interfaces == NULL) priv->interfaces = g_ptr_array_new_with_free_func(g_free); /* check for existing */ for (guint i = 0; i < priv->interfaces->len; i++) { iface = g_ptr_array_index(priv->interfaces, i); if (iface->number == number) return; } /* add new */ iface = g_new0(FuUsbDeviceInterface, 1); iface->number = number; g_ptr_array_add(priv->interfaces, iface); } #ifdef HAVE_GUSB static gboolean fu_usb_device_query_hub(FuUsbDevice *self, GError **error) { FuUsbDevicePrivate *priv = GET_PRIVATE(self); gsize sz = 0; guint16 value = 0x29; guint8 data[0x0c] = {0x0}; g_autoptr(GString) hub = g_string_new(NULL); /* longer descriptor for SuperSpeed */ if (fu_usb_device_get_spec(self) >= 0x0300) value = 0x2a; if (!g_usb_device_control_transfer(priv->usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_DEVICE, 0x06, /* LIBUSB_REQUEST_GET_DESCRIPTOR */ value << 8, 0x00, data, sizeof(data), &sz, 1000, NULL, error)) { g_prefix_error(error, "failed to get USB descriptor: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "HUB_DT", data, sz); /* for USB 3: size is fixed as max ports is 15, * for USB 2: size is variable as max ports is 255 */ if (fu_usb_device_get_spec(self) >= 0x0300 && sz == 0x0C) { g_string_append_printf(hub, "%02X", data[0x0B]); g_string_append_printf(hub, "%02X", data[0x0A]); } else if (sz >= 9) { guint8 numbytes = fu_common_align_up(data[2] + 1, 0x03) / 8; for (guint i = 0; i < numbytes; i++) { guint8 tmp = 0x0; if (!fu_memread_uint8_safe(data, sz, 7 + i, &tmp, error)) return FALSE; g_string_append_printf(hub, "%02X", tmp); } } if (hub->len > 0) fu_device_add_instance_str(FU_DEVICE(self), "HUB", hub->str); return fu_device_build_instance_id_full(FU_DEVICE(self), FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS, error, "USB", "VID", "PID", "HUB", NULL); } static gboolean fu_usb_device_claim_interface_cb(FuDevice *device, gpointer user_data, GError **error) { FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); FuUsbDeviceInterface *iface = (FuUsbDeviceInterface *)user_data; return g_usb_device_claim_interface(priv->usb_device, iface->number, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error); } static gboolean fu_usb_device_open_internal_cb(FuDevice *device, gpointer user_data, GError **error) { FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); return g_usb_device_open(priv->usb_device, error); } static gboolean fu_usb_device_open_internal(FuUsbDevice *self, GError **error) { FuUsbDevicePrivate *priv = GET_PRIVATE(self); return fu_device_retry_full(FU_DEVICE(self), fu_usb_device_open_internal_cb, priv->open_retry_count, FU_USB_DEVICE_OPEN_DELAY, NULL, error); } static gboolean fu_usb_device_close_internal(FuUsbDevice *self, GError **error) { FuUsbDevicePrivate *priv = GET_PRIVATE(self); return g_usb_device_close(priv->usb_device, error); } #endif static gboolean fu_usb_device_open(FuDevice *device, GError **error) { #ifdef HAVE_GUSB FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(FuDeviceLocker) locker = NULL; g_return_val_if_fail(FU_IS_USB_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* already open */ if (priv->usb_device_locker != NULL) return TRUE; /* open */ if (priv->open_retry_count > 0) { locker = fu_device_locker_new_full(self, (FuDeviceLockerFunc)fu_usb_device_open_internal, (FuDeviceLockerFunc)fu_usb_device_close_internal, error); if (locker == NULL) { g_prefix_error(error, "failed to open device with retries: "); return FALSE; } } else { locker = fu_device_locker_new(priv->usb_device, error); if (locker == NULL) { g_prefix_error(error, "failed to open device: "); return FALSE; } } /* success */ priv->usb_device_locker = g_steal_pointer(&locker); /* if set */ if (priv->configuration >= 0) { if (!g_usb_device_set_configuration(priv->usb_device, priv->configuration, error)) { g_prefix_error(error, "failed to set configuration: "); return FALSE; } } /* claim interfaces */ for (guint i = 0; priv->interfaces != NULL && i < priv->interfaces->len; i++) { FuUsbDeviceInterface *iface = g_ptr_array_index(priv->interfaces, i); if (priv->claim_retry_count > 0) { if (!fu_device_retry_full(device, fu_usb_device_claim_interface_cb, priv->claim_retry_count, FU_USB_DEVICE_CLAIM_INTERFACE_DELAY, iface, error)) { g_prefix_error(error, "failed to claim interface 0x%02x with retries: ", iface->number); return FALSE; } } else { if (!g_usb_device_claim_interface( priv->usb_device, iface->number, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error(error, "failed to claim interface 0x%02x: ", iface->number); return FALSE; } } iface->claimed = TRUE; } #endif return TRUE; } static gboolean fu_usb_device_setup(FuDevice *device, GError **error) { #ifdef HAVE_GUSB FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); guint idx; g_return_val_if_fail(FU_IS_USB_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* get vendor */ if (fu_device_get_vendor(device) == NULL) { idx = g_usb_device_get_manufacturer_index(priv->usb_device); if (idx != 0x00) { g_autofree gchar *tmp = NULL; g_autoptr(GError) error_local = NULL; tmp = g_usb_device_get_string_descriptor(priv->usb_device, idx, &error_local); if (tmp != NULL) fu_device_set_vendor(device, g_strchomp(tmp)); else g_debug( "failed to load manufacturer string for usb device %u:%u: %s", g_usb_device_get_bus(priv->usb_device), g_usb_device_get_address(priv->usb_device), error_local->message); } } /* get product */ if (fu_device_get_name(device) == NULL) { idx = g_usb_device_get_product_index(priv->usb_device); if (idx != 0x00) { g_autofree gchar *tmp = NULL; g_autoptr(GError) error_local = NULL; tmp = g_usb_device_get_string_descriptor(priv->usb_device, idx, &error_local); if (tmp != NULL) fu_device_set_name(device, g_strchomp(tmp)); else g_debug("failed to load product string for usb device %u:%u: %s", g_usb_device_get_bus(priv->usb_device), g_usb_device_get_address(priv->usb_device), error_local->message); } } /* get serial number */ if (!fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER) && fu_device_get_serial(device) == NULL) { idx = g_usb_device_get_serial_number_index(priv->usb_device); if (idx != 0x00) { g_autofree gchar *tmp = NULL; g_autoptr(GError) error_local = NULL; tmp = g_usb_device_get_string_descriptor(priv->usb_device, idx, &error_local); if (tmp != NULL) fu_device_set_serial(device, g_strchomp(tmp)); else g_debug( "failed to load serial number string for usb device %u:%u: %s", g_usb_device_get_bus(priv->usb_device), g_usb_device_get_address(priv->usb_device), error_local->message); } } /* get the hub descriptor if this is a hub */ if (g_usb_device_get_device_class(priv->usb_device) == G_USB_DEVICE_CLASS_HUB) { if (!fu_usb_device_query_hub(self, error)) return FALSE; } #endif /* success */ return TRUE; } static gboolean fu_usb_device_ready(FuDevice *device, GError **error) { #ifdef HAVE_GUSB FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GPtrArray) intfs = NULL; /* get the interface GUIDs */ intfs = g_usb_device_get_interfaces(priv->usb_device, error); if (intfs == NULL) { g_prefix_error(error, "failed to get interfaces: "); return FALSE; } /* add fallback icon if there is nothing added already */ if (fu_device_get_icons(device)->len == 0) { for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index(intfs, i); /* Video: Video Control: i.e. a webcam */ if (g_usb_interface_get_class(intf) == G_USB_DEVICE_CLASS_VIDEO && g_usb_interface_get_subclass(intf) == 0x01) { fu_device_add_icon(device, "camera-web"); } /* Audio */ if (g_usb_interface_get_class(intf) == G_USB_DEVICE_CLASS_AUDIO) fu_device_add_icon(device, "audio-card"); /* Mass Storage */ if (g_usb_interface_get_class(intf) == G_USB_DEVICE_CLASS_MASS_STORAGE) fu_device_add_icon(device, "drive-harddisk"); /* Printer */ if (g_usb_interface_get_class(intf) == G_USB_DEVICE_CLASS_PRINTER) fu_device_add_icon(device, "printer"); } } #endif /* success */ return TRUE; } static gboolean fu_usb_device_close(FuDevice *device, GError **error) { FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_USB_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* already closed */ if (priv->usb_device_locker == NULL) return TRUE; #ifdef HAVE_GUSB /* release interfaces, ignoring errors */ for (guint i = 0; priv->interfaces != NULL && i < priv->interfaces->len; i++) { FuUsbDeviceInterface *iface = g_ptr_array_index(priv->interfaces, i); GUsbDeviceClaimInterfaceFlags claim_flags = G_USB_DEVICE_CLAIM_INTERFACE_NONE; g_autoptr(GError) error_local = NULL; if (!iface->claimed) continue; if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) { g_debug("re-binding kernel driver as not waiting for replug"); claim_flags |= G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER; } if (!g_usb_device_release_interface(priv->usb_device, iface->number, claim_flags, &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE) || g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_INTERNAL)) { g_debug("failed to release interface 0x%02x: %s", iface->number, error_local->message); } else { g_warning("failed to release interface 0x%02x: %s", iface->number, error_local->message); } } iface->claimed = FALSE; } #endif g_clear_object(&priv->usb_device_locker); return TRUE; } #if G_USB_CHECK_VERSION(0, 4, 0) static gboolean fu_usb_device_probe_bos_descriptor(FuUsbDevice *self, GUsbBosDescriptor *bos, GError **error) { GBytes *extra = g_usb_bos_descriptor_get_extra(bos); g_autofree gchar *str = NULL; g_autoptr(FuFirmware) ds20 = NULL; /* sanity check */ if (g_bytes_get_size(extra) == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "zero sized data"); return FALSE; } /* parse either type */ ds20 = fu_firmware_new_from_gtypes(extra, 0x0, FWUPD_INSTALL_FLAG_NONE, error, FU_TYPE_USB_DEVICE_FW_DS20, FU_TYPE_USB_DEVICE_MS_DS20, G_TYPE_INVALID); if (ds20 == NULL) { g_prefix_error(error, "failed to parse: "); return FALSE; } str = fu_firmware_to_string(ds20); g_debug("DS20: %s", str); /* set the quirks onto the device */ if (!fu_usb_device_ds20_apply_to_device(FU_USB_DEVICE_DS20(ds20), self, error)) { g_prefix_error(error, "failed to apply DS20 data: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_usb_device_probe_bos_descriptors(FuUsbDevice *self, GError **error) { FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(FuDeviceLocker) gusb_locker = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) bos_descriptors = NULL; /* already matched a quirk entry */ if (fu_device_has_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_PROBE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not probing"); return FALSE; } /* not supported, so there is no point opening */ if (g_usb_device_get_spec(priv->usb_device) <= 0x0200) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not available as bcdUSB 0x%04x <= 0x0200", g_usb_device_get_spec(priv->usb_device)); return FALSE; } gusb_locker = fu_device_locker_new(priv->usb_device, error); if (gusb_locker == NULL) return FALSE; bos_descriptors = g_usb_device_get_bos_descriptors(priv->usb_device, &error_local); if (bos_descriptors == NULL) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_IO)) { g_debug("ignoring missing BOS descriptor: %s", error_local->message); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } for (guint i = 0; i < bos_descriptors->len; i++) { GUsbBosDescriptor *bos = g_ptr_array_index(bos_descriptors, i); g_autoptr(GError) error_loop = NULL; if (g_usb_bos_descriptor_get_capability(bos) != 0x5) continue; if (!fu_usb_device_probe_bos_descriptor(self, bos, &error_loop)) { g_warning("failed to parse platform BOS descriptor: %s", error_loop->message); } } return TRUE; } #endif static gboolean fu_usb_device_probe(FuDevice *device, GError **error) { #ifdef HAVE_GUSB FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); guint16 release; g_autofree gchar *platform_id = NULL; g_autofree gchar *vendor_id = NULL; g_autoptr(GPtrArray) intfs = NULL; #if G_USB_CHECK_VERSION(0, 4, 0) g_autoptr(GError) error_bos = NULL; #endif /* set vendor ID */ vendor_id = g_strdup_printf("USB:0x%04X", g_usb_device_get_vid(priv->usb_device)); fu_device_add_vendor_id(device, vendor_id); /* set the version if the release has been set */ release = g_usb_device_get_release(priv->usb_device); if (release != 0x0 && fu_device_get_version_format(device) == FWUPD_VERSION_FORMAT_UNKNOWN) { fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_BCD); fu_device_set_version_u16(device, release); } /* add GUIDs in order of priority */ fu_device_add_instance_u16(device, "VID", g_usb_device_get_vid(priv->usb_device)); fu_device_add_instance_u16(device, "PID", g_usb_device_get_pid(priv->usb_device)); fu_device_add_instance_u16(device, "REV", release); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "USB", "VID", NULL); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "USB", "VID", "PID", NULL); if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV)) { fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_VISIBLE | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "USB", "VID", "PID", "REV", NULL); } /* add the interface GUIDs */ intfs = g_usb_device_get_interfaces(priv->usb_device, error); if (intfs == NULL) { g_prefix_error(error, "failed to get interfaces: "); return FALSE; } for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index(intfs, i); fu_device_add_instance_u8(device, "CLASS", g_usb_interface_get_class(intf)); fu_device_add_instance_u8(device, "SUBCLASS", g_usb_interface_get_subclass(intf)); fu_device_add_instance_u8(device, "PROT", g_usb_interface_get_protocol(intf)); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "USB", "CLASS", NULL); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "USB", "CLASS", "SUBCLASS", NULL); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_GENERIC | FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "USB", "CLASS", "SUBCLASS", "PROT", NULL); } /* add 2 levels of parent IDs */ platform_id = g_strdup(g_usb_device_get_platform_id(priv->usb_device)); for (guint i = 0; i < 2; i++) { gchar *tok = g_strrstr(platform_id, ":"); if (tok == NULL) break; *tok = '\0'; if (g_strcmp0(platform_id, "usb") == 0) break; fu_device_add_parent_physical_id(device, platform_id); } #if G_USB_CHECK_VERSION(0, 4, 0) /* parse the platform capability BOS descriptors for quirks */ if (!fu_usb_device_probe_bos_descriptors(self, &error_bos)) { if (g_error_matches(error_bos, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_debug("ignoring: %s", error_bos->message); } else { g_warning("failed to load BOS descriptor from USB device: %s", error_bos->message); } } #endif #endif /* success */ return TRUE; } /** * fu_usb_device_get_vid: * @self: a #FuUsbDevice * * Gets the device vendor code. * * Returns: integer, or 0x0 if unset or invalid * * Since: 1.1.2 **/ guint16 fu_usb_device_get_vid(FuUsbDevice *self) { #ifdef HAVE_GUSB FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_USB_DEVICE(self), 0x0000); if (priv->usb_device == NULL) return 0x0; return g_usb_device_get_vid(priv->usb_device); #else return 0x0; #endif } /** * fu_usb_device_get_pid: * @self: a #FuUsbDevice * * Gets the device product code. * * Returns: integer, or 0x0 if unset or invalid * * Since: 1.1.2 **/ guint16 fu_usb_device_get_pid(FuUsbDevice *self) { #ifdef HAVE_GUSB FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_USB_DEVICE(self), 0x0000); if (priv->usb_device == NULL) return 0x0; return g_usb_device_get_pid(priv->usb_device); #else return 0x0; #endif } /** * fu_usb_device_get_platform_id: * @self: a #FuUsbDevice * * Gets the device platform ID. * * Returns: string, or NULL if unset or invalid * * Since: 1.1.2 **/ const gchar * fu_usb_device_get_platform_id(FuUsbDevice *self) { #ifdef HAVE_GUSB FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_USB_DEVICE(self), NULL); if (priv->usb_device == NULL) return NULL; return g_usb_device_get_platform_id(priv->usb_device); #else return NULL; #endif } /** * fu_usb_device_get_spec: * @self: a #FuUsbDevice * * Gets the string USB revision for the device. * * Returns: a specification revision in BCD format, or 0x0 if not supported * * Since: 1.3.4 **/ guint16 fu_usb_device_get_spec(FuUsbDevice *self) { #ifdef HAVE_GUSB FuUsbDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_USB_DEVICE(self), 0x0); if (priv->usb_device == NULL) return 0x0; return g_usb_device_get_spec(priv->usb_device); #else return 0x0; #endif } /** * fu_usb_device_set_dev: * @device: a #FuUsbDevice * @usb_device: (nullable): optional #GUsbDevice * * Sets the #GUsbDevice to use. * * Since: 1.0.2 **/ void fu_usb_device_set_dev(FuUsbDevice *device, GUsbDevice *usb_device) { FuUsbDevicePrivate *priv = GET_PRIVATE(device); g_return_if_fail(FU_IS_USB_DEVICE(device)); /* need to re-probe hardware */ if (!fu_device_has_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_EMULATED)) fu_device_probe_invalidate(FU_DEVICE(device)); /* allow replacement */ g_set_object(&priv->usb_device, usb_device); if (usb_device == NULL) { g_clear_object(&priv->usb_device_locker); return; } #ifdef HAVE_GUSB #if G_USB_CHECK_VERSION(0, 4, 5) /* propagate emulated flag */ if (usb_device != NULL && g_usb_device_is_emulated(usb_device)) fu_device_add_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_EMULATED); #endif /* set device ID automatically */ fu_device_set_physical_id(FU_DEVICE(device), g_usb_device_get_platform_id(usb_device)); #endif } /** * fu_usb_device_find_udev_device: * @device: a #FuUsbDevice * @error: (nullable): optional return location for an error * * Gets the matching #GUdevDevice for the #GUsbDevice. * * Returns: (transfer full): a #GUdevDevice, or NULL if unset or invalid * * Since: 1.3.2 **/ GUdevDevice * fu_usb_device_find_udev_device(FuUsbDevice *device, GError **error) { #if defined(HAVE_GUDEV) && defined(HAVE_GUSB) FuUsbDevicePrivate *priv = GET_PRIVATE(device); g_autoptr(GList) devices = NULL; g_autoptr(GUdevClient) gudev_client = g_udev_client_new(NULL); g_return_val_if_fail(FU_IS_USB_DEVICE(device), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* find all tty devices */ devices = g_udev_client_query_by_subsystem(gudev_client, "usb"); for (GList *l = devices; l != NULL; l = l->next) { GUdevDevice *dev = G_UDEV_DEVICE(l->data); /* check correct device */ if (g_udev_device_get_sysfs_attr_as_int(dev, "busnum") != g_usb_device_get_bus(priv->usb_device)) continue; if (g_udev_device_get_sysfs_attr_as_int(dev, "devnum") != g_usb_device_get_address(priv->usb_device)) continue; /* success */ g_debug("USB device %u:%u is %s", g_usb_device_get_bus(priv->usb_device), g_usb_device_get_address(priv->usb_device), g_udev_device_get_sysfs_path(dev)); return g_object_ref(dev); } /* failure */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "could not find sysfs device for %u:%u", g_usb_device_get_bus(priv->usb_device), g_usb_device_get_address(priv->usb_device)); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); #endif return NULL; } /** * fu_usb_device_get_dev: * @device: a #FuUsbDevice * * Gets the #GUsbDevice. * * Returns: (transfer none): a USB device, or %NULL * * Since: 1.0.2 **/ GUsbDevice * fu_usb_device_get_dev(FuUsbDevice *device) { FuUsbDevicePrivate *priv = GET_PRIVATE(device); g_return_val_if_fail(FU_IS_USB_DEVICE(device), NULL); return priv->usb_device; } static void fu_usb_device_incorporate(FuDevice *self, FuDevice *donor) { g_return_if_fail(FU_IS_USB_DEVICE(self)); g_return_if_fail(FU_IS_USB_DEVICE(donor)); fu_usb_device_set_dev(FU_USB_DEVICE(self), fu_usb_device_get_dev(FU_USB_DEVICE(donor))); } static gboolean fu_udev_device_bind_driver(FuDevice *device, const gchar *subsystem, const gchar *driver, GError **error) { FuUsbDevice *self = FU_USB_DEVICE(device); g_autoptr(GUdevDevice) dev = NULL; g_autoptr(FuUdevDevice) udev_device = NULL; /* use udev for this */ dev = fu_usb_device_find_udev_device(self, error); if (dev == NULL) return FALSE; udev_device = fu_udev_device_new(fu_device_get_context(device), dev); return fu_device_bind_driver(FU_DEVICE(udev_device), subsystem, driver, error); } static gboolean fu_udev_device_unbind_driver(FuDevice *device, GError **error) { FuUsbDevice *self = FU_USB_DEVICE(device); g_autoptr(GUdevDevice) dev = NULL; g_autoptr(FuUdevDevice) udev_device = NULL; /* use udev for this */ dev = fu_usb_device_find_udev_device(self, error); if (dev == NULL) return FALSE; udev_device = fu_udev_device_new(fu_device_get_context(device), dev); return fu_device_unbind_driver(FU_DEVICE(udev_device), error); } /** * fu_usb_device_new: * @ctx: (nullable): a #FuContext * @usb_device: a USB device * * Creates a new #FuUsbDevice. * * Returns: (transfer full): a #FuUsbDevice * * Since: 1.8.2 **/ FuUsbDevice * fu_usb_device_new(FuContext *ctx, GUsbDevice *usb_device) { #if G_USB_CHECK_VERSION(0, 4, 3) if (usb_device != NULL && g_usb_device_has_tag(usb_device, "is-transient")) { g_critical("cannot use a device built using fu_udev_device_find_usb_device() as " "the GUsbContext is different"); return NULL; } #endif return g_object_new(FU_TYPE_USB_DEVICE, "context", ctx, "usb-device", usb_device, NULL); } #ifdef HAVE_GUSB static const gchar * fu_usb_device_class_code_to_string(GUsbDeviceClassCode code) { if (code == G_USB_DEVICE_CLASS_INTERFACE_DESC) return "interface-desc"; if (code == G_USB_DEVICE_CLASS_AUDIO) return "audio"; if (code == G_USB_DEVICE_CLASS_COMMUNICATIONS) return "communications"; if (code == G_USB_DEVICE_CLASS_HID) return "hid"; if (code == G_USB_DEVICE_CLASS_PHYSICAL) return "physical"; if (code == G_USB_DEVICE_CLASS_IMAGE) return "image"; if (code == G_USB_DEVICE_CLASS_PRINTER) return "printer"; if (code == G_USB_DEVICE_CLASS_MASS_STORAGE) return "mass-storage"; if (code == G_USB_DEVICE_CLASS_HUB) return "hub"; if (code == G_USB_DEVICE_CLASS_CDC_DATA) return "cdc-data"; if (code == G_USB_DEVICE_CLASS_SMART_CARD) return "smart-card"; if (code == G_USB_DEVICE_CLASS_CONTENT_SECURITY) return "content-security"; if (code == G_USB_DEVICE_CLASS_VIDEO) return "video"; if (code == G_USB_DEVICE_CLASS_PERSONAL_HEALTHCARE) return "personal-healthcare"; if (code == G_USB_DEVICE_CLASS_AUDIO_VIDEO) return "audio-video"; if (code == G_USB_DEVICE_CLASS_BILLBOARD) return "billboard"; if (code == G_USB_DEVICE_CLASS_DIAGNOSTIC) return "diagnostic"; if (code == G_USB_DEVICE_CLASS_WIRELESS_CONTROLLER) return "wireless-controller"; if (code == G_USB_DEVICE_CLASS_MISCELLANEOUS) return "miscellaneous"; if (code == G_USB_DEVICE_CLASS_APPLICATION_SPECIFIC) return "application-specific"; if (code == G_USB_DEVICE_CLASS_VENDOR_SPECIFIC) return "vendor-specific"; return NULL; } #endif static void fu_usb_device_to_string(FuDevice *device, guint idt, GString *str) { FuUsbDevice *self = FU_USB_DEVICE(device); FuUsbDevicePrivate *priv = GET_PRIVATE(self); if (priv->configuration > 0) fu_string_append_kx(str, idt, "Configuration", priv->configuration); if (priv->claim_retry_count > 0) fu_string_append_kx(str, idt, "ClaimRetryCount", priv->claim_retry_count); if (priv->open_retry_count > 0) fu_string_append_kx(str, idt, "OpenRetryCount", priv->open_retry_count); for (guint i = 0; priv->interfaces != NULL && i < priv->interfaces->len; i++) { FuUsbDeviceInterface *iface = g_ptr_array_index(priv->interfaces, i); g_autofree gchar *tmp = g_strdup_printf("InterfaceNumber#%02x", iface->number); fu_string_append(str, idt, tmp, iface->claimed ? "claimed" : "released"); } #ifdef HAVE_GUSB if (priv->usb_device != NULL) { GUsbDeviceClassCode code = g_usb_device_get_device_class(priv->usb_device); fu_string_append(str, idt, "UsbDeviceClass", fu_usb_device_class_code_to_string(code)); } #endif } static void fu_usb_device_class_init(FuUsbDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *device_class = FU_DEVICE_CLASS(klass); GParamSpec *pspec; object_class->finalize = fu_usb_device_finalize; object_class->get_property = fu_usb_device_get_property; object_class->set_property = fu_usb_device_set_property; object_class->constructed = fu_usb_device_constructed; device_class->open = fu_usb_device_open; device_class->setup = fu_usb_device_setup; device_class->ready = fu_usb_device_ready; device_class->close = fu_usb_device_close; device_class->probe = fu_usb_device_probe; device_class->to_string = fu_usb_device_to_string; device_class->incorporate = fu_usb_device_incorporate; device_class->bind_driver = fu_udev_device_bind_driver; device_class->unbind_driver = fu_udev_device_unbind_driver; /** * FuUsbDevice:usb-device: * * The low-level #GUsbDevice. * * Since: 1.0.2 */ pspec = g_param_spec_object("usb-device", NULL, NULL, #ifdef HAVE_GUSB G_USB_TYPE_DEVICE, #else G_TYPE_OBJECT, #endif G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_USB_DEVICE, pspec); } fwupd-1.9.16/libfwupdplugin/fu-usb-device.h000066400000000000000000000033521460375044200206070ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #ifdef HAVE_GUSB #include #else #define GUsbContext GObject #define GUsbDevice GObject #ifndef __GI_SCANNER__ #define G_USB_CHECK_VERSION(a, c, b) 0 #endif #endif #include "fu-plugin.h" #include "fu-udev-device.h" #define FU_TYPE_USB_DEVICE (fu_usb_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuUsbDevice, fu_usb_device, FU, USB_DEVICE, FuDevice) struct _FuUsbDeviceClass { FuDeviceClass parent_class; }; FuUsbDevice * fu_usb_device_new(FuContext *ctx, GUsbDevice *usb_device) G_GNUC_NON_NULL(1); guint16 fu_usb_device_get_vid(FuUsbDevice *self) G_GNUC_NON_NULL(1); guint16 fu_usb_device_get_pid(FuUsbDevice *self) G_GNUC_NON_NULL(1); guint16 fu_usb_device_get_spec(FuUsbDevice *self) G_GNUC_NON_NULL(1); GUsbDevice * fu_usb_device_get_dev(FuUsbDevice *device) G_GNUC_NON_NULL(1); void fu_usb_device_set_dev(FuUsbDevice *device, GUsbDevice *usb_device) G_GNUC_NON_NULL(1); gboolean fu_usb_device_is_open(FuUsbDevice *device) G_GNUC_NON_NULL(1); GUdevDevice * fu_usb_device_find_udev_device(FuUsbDevice *device, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); void fu_usb_device_set_configuration(FuUsbDevice *device, gint configuration) G_GNUC_NON_NULL(1); void fu_usb_device_add_interface(FuUsbDevice *device, guint8 number) G_GNUC_NON_NULL(1); void fu_usb_device_set_claim_retry_count(FuUsbDevice *self, guint claim_retry_count) G_GNUC_NON_NULL(1); guint fu_usb_device_get_claim_retry_count(FuUsbDevice *self) G_GNUC_NON_NULL(1); void fu_usb_device_set_open_retry_count(FuUsbDevice *self, guint open_retry_count) G_GNUC_NON_NULL(1); guint fu_usb_device_get_open_retry_count(FuUsbDevice *self) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-uswid-firmware.c000066400000000000000000000232511460375044200215210ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuFirmware" #include "config.h" #include "fu-byte-array.h" #include "fu-bytes.h" #include "fu-common.h" #include "fu-coswid-firmware.h" #include "fu-lzma-common.h" #include "fu-mem.h" #include "fu-string.h" #include "fu-uswid-firmware.h" #include "fu-uswid-struct.h" /** * FuUswidFirmware: * * A uSWID header with multiple optionally-compressed coSWID CBOR sections. * * See also: [class@FuCoswidFirmware] */ typedef struct { guint8 hdrver; FuUswidPayloadCompression compression; } FuUswidFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuUswidFirmware, fu_uswid_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_uswid_firmware_get_instance_private(o)) #define FU_USWID_FIRMARE_MINIMUM_HDRVER 1 static void fu_uswid_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuUswidFirmware *self = FU_USWID_FIRMWARE(firmware); FuUswidFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "hdrver", priv->hdrver); if (priv->compression != FU_USWID_PAYLOAD_COMPRESSION_NONE) { fu_xmlb_builder_insert_kv( bn, "compression", fu_uswid_payload_compression_to_string(priv->compression)); } } static gboolean fu_uswid_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_uswid_validate_bytes(fw, offset, error); } static gboolean fu_uswid_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuUswidFirmware *self = FU_USWID_FIRMWARE(firmware); FuUswidFirmwarePrivate *priv = GET_PRIVATE(self); guint16 hdrsz; guint32 payloadsz; g_autoptr(GByteArray) st = NULL; g_autoptr(GBytes) payload = NULL; /* unpack */ st = fu_struct_uswid_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; /* hdrver */ priv->hdrver = fu_struct_uswid_get_hdrver(st); if (priv->hdrver < FU_USWID_FIRMARE_MINIMUM_HDRVER) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "header version was unsupported"); return FALSE; } /* hdrsz+payloadsz */ hdrsz = fu_struct_uswid_get_hdrsz(st); payloadsz = fu_struct_uswid_get_payloadsz(st); if (payloadsz == 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "payload size is invalid"); return FALSE; } fu_firmware_set_size(firmware, hdrsz + payloadsz); /* flags */ if (priv->hdrver >= 0x03) { if (fu_struct_uswid_get_flags(st) & FU_USWID_HEADER_FLAG_COMPRESSED) { priv->compression = fu_struct_uswid_get_compression(st); } else { priv->compression = FU_USWID_PAYLOAD_COMPRESSION_NONE; } } else if (priv->hdrver >= 0x02) { priv->compression = fu_struct_uswid_get_flags(st) & FU_USWID_HEADER_FLAG_COMPRESSED ? FU_USWID_PAYLOAD_COMPRESSION_ZLIB : FU_USWID_PAYLOAD_COMPRESSION_NONE; } else { priv->compression = FU_USWID_PAYLOAD_COMPRESSION_NONE; } /* zlib stream */ if (priv->compression == FU_USWID_PAYLOAD_COMPRESSION_ZLIB) { g_autoptr(GBytes) payload_tmp = NULL; g_autoptr(GConverter) conv = NULL; g_autoptr(GInputStream) istream1 = NULL; g_autoptr(GInputStream) istream2 = NULL; payload_tmp = fu_bytes_new_offset(fw, offset + hdrsz, payloadsz, error); if (payload_tmp == NULL) return FALSE; istream1 = g_memory_input_stream_new_from_bytes(payload_tmp); conv = G_CONVERTER(g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_ZLIB)); istream2 = g_converter_input_stream_new(istream1, conv); payload = fu_bytes_get_contents_stream(istream2, G_MAXSIZE, error); if (payload == NULL) return FALSE; payloadsz = g_bytes_get_size(payload); } else if (priv->compression == FU_USWID_PAYLOAD_COMPRESSION_LZMA) { g_autoptr(GBytes) payload_tmp = NULL; payload_tmp = fu_bytes_new_offset(fw, offset + hdrsz, payloadsz, error); if (payload_tmp == NULL) return FALSE; payload = fu_lzma_decompress_bytes(payload_tmp, error); if (payload == NULL) return FALSE; payloadsz = g_bytes_get_size(payload); } else if (priv->compression == FU_USWID_PAYLOAD_COMPRESSION_NONE) { payload = fu_bytes_new_offset(fw, offset + hdrsz, payloadsz, error); if (payload == NULL) return FALSE; } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "compression format 0x%x is not supported", priv->compression); return FALSE; } /* payload */ for (gsize offset_tmp = 0; offset_tmp < payloadsz;) { g_autoptr(FuFirmware) firmware_coswid = fu_coswid_firmware_new(); g_autoptr(GBytes) fw2 = NULL; /* CBOR parse */ fw2 = fu_bytes_new_offset(payload, offset_tmp, payloadsz - offset_tmp, error); if (fw2 == NULL) return FALSE; if (!fu_firmware_parse(firmware_coswid, fw2, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return FALSE; if (!fu_firmware_add_image_full(firmware, firmware_coswid, error)) return FALSE; if (fu_firmware_get_size(firmware_coswid) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "coSWID read no bytes"); return FALSE; } offset_tmp += fu_firmware_get_size(firmware_coswid); } /* success */ return TRUE; } static GByteArray * fu_uswid_firmware_write(FuFirmware *firmware, GError **error) { FuUswidFirmware *self = FU_USWID_FIRMWARE(firmware); FuUswidFirmwarePrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) buf = fu_struct_uswid_new(); g_autoptr(GByteArray) payload = g_byte_array_new(); g_autoptr(GBytes) payload_blob = NULL; g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); /* generate early so we know the size */ for (guint i = 0; i < images->len; i++) { FuFirmware *firmware_coswid = g_ptr_array_index(images, i); g_autoptr(GBytes) fw = fu_firmware_write(firmware_coswid, error); if (fw == NULL) return NULL; fu_byte_array_append_bytes(payload, fw); } /* zlibify */ if (priv->compression == FU_USWID_PAYLOAD_COMPRESSION_ZLIB) { g_autoptr(GConverter) conv = NULL; g_autoptr(GInputStream) istream1 = NULL; g_autoptr(GInputStream) istream2 = NULL; conv = G_CONVERTER(g_zlib_compressor_new(G_ZLIB_COMPRESSOR_FORMAT_ZLIB, -1)); istream1 = g_memory_input_stream_new_from_data(payload->data, payload->len, NULL); istream2 = g_converter_input_stream_new(istream1, conv); payload_blob = fu_bytes_get_contents_stream(istream2, G_MAXSIZE, error); if (payload_blob == NULL) return NULL; } else if (priv->compression == FU_USWID_PAYLOAD_COMPRESSION_LZMA) { g_autoptr(GBytes) payload_tmp = g_bytes_new(payload->data, payload->len); payload_blob = fu_lzma_compress_bytes(payload_tmp, error); if (payload_blob == NULL) return NULL; } else { payload_blob = g_bytes_new(payload->data, payload->len); } /* pack */ fu_struct_uswid_set_hdrver(buf, priv->hdrver); fu_struct_uswid_set_payloadsz(buf, g_bytes_get_size(payload_blob)); if (priv->hdrver >= 3) { guint8 flags = 0; if (priv->compression != FU_USWID_PAYLOAD_COMPRESSION_NONE) flags |= FU_USWID_HEADER_FLAG_COMPRESSED; fu_struct_uswid_set_flags(buf, flags); fu_struct_uswid_set_compression(buf, priv->compression); } else if (priv->hdrver >= 2) { guint8 flags = 0; if (priv->compression != FU_USWID_PAYLOAD_COMPRESSION_NONE) { if (priv->compression != FU_USWID_PAYLOAD_COMPRESSION_ZLIB) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "hdrver 0x02 only supports zlib compression"); return NULL; } flags |= FU_USWID_HEADER_FLAG_COMPRESSED; } fu_struct_uswid_set_flags(buf, flags); g_byte_array_set_size(buf, buf->len - 1); fu_struct_uswid_set_hdrsz(buf, buf->len); } else { g_byte_array_set_size(buf, buf->len - 2); fu_struct_uswid_set_hdrsz(buf, buf->len); } fu_byte_array_append_bytes(buf, payload_blob); /* success */ return g_steal_pointer(&buf); } static gboolean fu_uswid_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuUswidFirmware *self = FU_USWID_FIRMWARE(firmware); FuUswidFirmwarePrivate *priv = GET_PRIVATE(self); const gchar *str; guint64 tmp; /* simple properties */ tmp = xb_node_query_text_as_uint(n, "hdrver", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) priv->hdrver = tmp; /* simple properties */ str = xb_node_query_text(n, "compression", NULL); if (str != NULL) { priv->compression = fu_uswid_payload_compression_from_string(str); if (priv->compression == FU_USWID_PAYLOAD_COMPRESSION_NONE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid compression type %s", str); return FALSE; } } else { priv->compression = FU_USWID_PAYLOAD_COMPRESSION_NONE; } /* success */ return TRUE; } static void fu_uswid_firmware_init(FuUswidFirmware *self) { FuUswidFirmwarePrivate *priv = GET_PRIVATE(self); priv->hdrver = FU_USWID_FIRMARE_MINIMUM_HDRVER; priv->compression = FU_USWID_PAYLOAD_COMPRESSION_NONE; fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_ALWAYS_SEARCH); fu_firmware_set_images_max(FU_FIRMWARE(self), 2000); } static void fu_uswid_firmware_class_init(FuUswidFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_uswid_firmware_check_magic; klass_firmware->parse = fu_uswid_firmware_parse; klass_firmware->write = fu_uswid_firmware_write; klass_firmware->build = fu_uswid_firmware_build; klass_firmware->export = fu_uswid_firmware_export; } /** * fu_uswid_firmware_new: * * Creates a new #FuFirmware of sub type uSWID * * Since: 1.8.0 **/ FuFirmware * fu_uswid_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_USWID_FIRMWARE, NULL)); } fwupd-1.9.16/libfwupdplugin/fu-uswid-firmware.h000066400000000000000000000006341460375044200215260ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-firmware.h" #define FU_TYPE_USWID_FIRMWARE (fu_uswid_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuUswidFirmware, fu_uswid_firmware, FU, USWID_FIRMWARE, FuFirmware) struct _FuUswidFirmwareClass { FuFirmwareClass parent_class; }; FuFirmware * fu_uswid_firmware_new(void); fwupd-1.9.16/libfwupdplugin/fu-uswid.rs000066400000000000000000000007641460375044200201150ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ enum UswidHeaderFlag { None = 0b0, Compressed = 0b1, } #[derive(ToString, FromString)] enum UswidPayloadCompression { None = 0x00, Zlib = 0x01, Lzma = 0x02, } #[derive(New, ValidateBytes, ParseBytes)] struct Uswid { magic: Guid == 0x53424F4DD6BA2EACA3E67A52AAEE3BAF, hdrver: u8, hdrsz: u16le = $struct_size, payloadsz: u32le, flags: u8, compression: u8, } fwupd-1.9.16/libfwupdplugin/fu-version-common.c000066400000000000000000000432401460375044200215270ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommon" #include "config.h" #include #include "fwupd-enums.h" #include "fwupd-error.h" #include "fu-version-common.h" #define FU_COMMON_VERSION_DECODE_BCD(val) ((((val) >> 4) & 0x0f) * 10 + ((val)&0x0f)) static gchar * fu_common_version_ensure_semver(const gchar *version); /** * fu_version_from_uint64: * @val: a raw version number * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_QUAD * * Returns a dotted decimal version string from a 64 bit number. * * Returns: a version number, e.g. `1.2.3.4`, or %NULL if not supported * * Since: 1.8.2 **/ gchar * fu_version_from_uint64(guint64 val, FwupdVersionFormat kind) { if (kind == FWUPD_VERSION_FORMAT_QUAD) { /* AABB.CCDD.EEFF.GGHH */ return g_strdup_printf("%" G_GUINT64_FORMAT "." "%" G_GUINT64_FORMAT "." "%" G_GUINT64_FORMAT "." "%" G_GUINT64_FORMAT "", (val >> 48) & 0xffff, (val >> 32) & 0xffff, (val >> 16) & 0xffff, val & 0xffff); } if (kind == FWUPD_VERSION_FORMAT_PAIR) { /* AABBCCDD.EEFFGGHH */ return g_strdup_printf("%" G_GUINT64_FORMAT ".%" G_GUINT64_FORMAT "", (val >> 32) & 0xffffffff, val & 0xffffffff); } if (kind == FWUPD_VERSION_FORMAT_NUMBER || kind == FWUPD_VERSION_FORMAT_PLAIN) { /* AABBCCDD */ return g_strdup_printf("%" G_GUINT64_FORMAT, val); } if (kind == FWUPD_VERSION_FORMAT_HEX) { /* 0xAABBCCDDEEFFGGHH */ return g_strdup_printf("0x%08x%08x", (guint32)(val >> 32), (guint32)(val & 0xffffffff)); } g_critical("failed to convert version format %s: %" G_GUINT64_FORMAT "", fwupd_version_format_to_string(kind), val); return NULL; } /** * fu_version_from_uint32: * @val: a uint32le version number * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET * * Returns a dotted decimal version string from a 32 bit number. * * Returns: a version number, e.g. `1.0.3`, or %NULL if not supported * * Since: 1.8.2 **/ gchar * fu_version_from_uint32(guint32 val, FwupdVersionFormat kind) { if (kind == FWUPD_VERSION_FORMAT_QUAD) { /* AA.BB.CC.DD */ return g_strdup_printf("%u.%u.%u.%u", (val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff); } if (kind == FWUPD_VERSION_FORMAT_TRIPLET) { /* AA.BB.CCDD */ return g_strdup_printf("%u.%u.%u", (val >> 24) & 0xff, (val >> 16) & 0xff, val & 0xffff); } if (kind == FWUPD_VERSION_FORMAT_PAIR) { /* AABB.CCDD */ return g_strdup_printf("%u.%u", (val >> 16) & 0xffff, val & 0xffff); } if (kind == FWUPD_VERSION_FORMAT_NUMBER || kind == FWUPD_VERSION_FORMAT_PLAIN) { /* AABBCCDD */ return g_strdup_printf("%" G_GUINT32_FORMAT, val); } if (kind == FWUPD_VERSION_FORMAT_BCD) { /* AA.BB.CC.DD, but BCD */ return g_strdup_printf("%u.%u.%u.%u", FU_COMMON_VERSION_DECODE_BCD(val >> 24), FU_COMMON_VERSION_DECODE_BCD(val >> 16), FU_COMMON_VERSION_DECODE_BCD(val >> 8), FU_COMMON_VERSION_DECODE_BCD(val)); } if (kind == FWUPD_VERSION_FORMAT_INTEL_ME) { /* aaa+11.bbbbb.cccccccc.dddddddddddddddd */ return g_strdup_printf("%u.%u.%u.%u", ((val >> 29) & 0x07) + 0x0b, (val >> 24) & 0x1f, (val >> 16) & 0xff, val & 0xffff); } if (kind == FWUPD_VERSION_FORMAT_INTEL_ME2) { /* A.B.CC.DDDD */ return g_strdup_printf("%u.%u.%u.%u", (val >> 28) & 0x0f, (val >> 24) & 0x0f, (val >> 16) & 0xff, val & 0xffff); } if (kind == FWUPD_VERSION_FORMAT_SURFACE_LEGACY) { /* 10b.12b.10b */ return g_strdup_printf("%u.%u.%u", (val >> 22) & 0x3ff, (val >> 10) & 0xfff, val & 0x3ff); } if (kind == FWUPD_VERSION_FORMAT_SURFACE) { /* 8b.16b.8b */ return g_strdup_printf("%u.%u.%u", (val >> 24) & 0xff, (val >> 8) & 0xffff, val & 0xff); } if (kind == FWUPD_VERSION_FORMAT_DELL_BIOS) { /* BB.CC.DD */ return g_strdup_printf("%u.%u.%u", (val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff); } if (kind == FWUPD_VERSION_FORMAT_HEX) { /* 0xAABBCCDD */ return g_strdup_printf("0x%08x", val); } g_critical("failed to convert version format %s: %u", fwupd_version_format_to_string(kind), val); return NULL; } /** * fu_version_from_uint24: * @val: a uint24le version number * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET * * Returns a dotted decimal version string from a 24 bit number. * * Returns: a version number, e.g. `1.0.3`, or %NULL if not supported * * Since: 1.8.9 **/ gchar * fu_version_from_uint24(guint32 val, FwupdVersionFormat kind) { if (kind == FWUPD_VERSION_FORMAT_TRIPLET) { /* BB.CC.DD */ return g_strdup_printf("%u.%u.%u", (val >> 24) & 0xff, (val >> 16) & 0xff, val & 0xffff); } if (kind == FWUPD_VERSION_FORMAT_PAIR) { /* BB.CCDD */ return g_strdup_printf("%u.%u", (val >> 16) & 0xff, val & 0xffff); } if (kind == FWUPD_VERSION_FORMAT_NUMBER || kind == FWUPD_VERSION_FORMAT_PLAIN) { /* BBCCDD */ return g_strdup_printf("%" G_GUINT32_FORMAT, val); } if (kind == FWUPD_VERSION_FORMAT_HEX) { /* 0xBBCCDD */ return g_strdup_printf("0x%06x", val); } g_critical("failed to convert version format %s: %u", fwupd_version_format_to_string(kind), val); return NULL; } /** * fu_version_from_uint16: * @val: a uint16le version number * @kind: version kind used for formatting, e.g. %FWUPD_VERSION_FORMAT_TRIPLET * * Returns a dotted decimal version string from a 16 bit number. * * Returns: a version number, e.g. `1.3`, or %NULL if not supported * * Since: 1.8.2 **/ gchar * fu_version_from_uint16(guint16 val, FwupdVersionFormat kind) { if (kind == FWUPD_VERSION_FORMAT_BCD) { return g_strdup_printf("%i.%i", FU_COMMON_VERSION_DECODE_BCD(val >> 8), FU_COMMON_VERSION_DECODE_BCD(val)); } if (kind == FWUPD_VERSION_FORMAT_PAIR) { return g_strdup_printf("%u.%u", (guint)(val >> 8) & 0xff, (guint)val & 0xff); } if (kind == FWUPD_VERSION_FORMAT_TRIPLET) { return g_strdup_printf("%u.%u.%u", (guint)(val >> 12) & 0xF, (guint)(val >> 8) & 0xF, (guint)val & 0xFF); } if (kind == FWUPD_VERSION_FORMAT_NUMBER || kind == FWUPD_VERSION_FORMAT_PLAIN) { return g_strdup_printf("%" G_GUINT16_FORMAT, val); } if (kind == FWUPD_VERSION_FORMAT_HEX) { /* 0xAABB */ return g_strdup_printf("0x%04x", val); } g_critical("failed to convert version format %s: %u", fwupd_version_format_to_string(kind), val); return NULL; } static gint fu_version_compare_char(gchar chr1, gchar chr2) { if (chr1 == chr2) return 0; if (chr1 == '~') return -1; if (chr2 == '~') return 1; return chr1 < chr2 ? -1 : 1; } static gint fu_version_compare_chunk(const gchar *str1, const gchar *str2) { guint i; /* trivial */ if (g_strcmp0(str1, str2) == 0) return 0; if (str1 == NULL) return 1; if (str2 == NULL) return -1; /* check each char of the chunk */ for (i = 0; str1[i] != '\0' && str2[i] != '\0'; i++) { gint rc = fu_version_compare_char(str1[i], str2[i]); if (rc != 0) return rc; } return fu_version_compare_char(str1[i], str2[i]); } static gboolean _g_ascii_is_digits(const gchar *str) { g_return_val_if_fail(str != NULL, FALSE); for (gsize i = 0; str[i] != '\0'; i++) { if (!g_ascii_isdigit(str[i])) return FALSE; } return TRUE; } static guint fu_version_format_number_sections(FwupdVersionFormat fmt) { if (fmt == FWUPD_VERSION_FORMAT_PLAIN || fmt == FWUPD_VERSION_FORMAT_NUMBER || fmt == FWUPD_VERSION_FORMAT_HEX) return 1; if (fmt == FWUPD_VERSION_FORMAT_PAIR || fmt == FWUPD_VERSION_FORMAT_BCD) return 2; if (fmt == FWUPD_VERSION_FORMAT_TRIPLET || fmt == FWUPD_VERSION_FORMAT_SURFACE_LEGACY || fmt == FWUPD_VERSION_FORMAT_SURFACE || fmt == FWUPD_VERSION_FORMAT_DELL_BIOS) return 3; if (fmt == FWUPD_VERSION_FORMAT_QUAD || fmt == FWUPD_VERSION_FORMAT_INTEL_ME || fmt == FWUPD_VERSION_FORMAT_INTEL_ME2) return 4; return 0; } /** * fu_version_ensure_semver: * @version: (nullable): a version number, e.g. ` V1.2.3 ` * @fmt: a version format, e.g. %FWUPD_VERSION_FORMAT_TRIPLET * * Builds a semver from the possibly crazy version number. Depending on the @semver value * the string will be split and a string in the correct format will be returned. * * Returns: a version number, e.g. `1.2.3`, or %NULL if the version was not valid * * Since: 1.8.2 */ gchar * fu_version_ensure_semver(const gchar *version, FwupdVersionFormat fmt) { guint sections_actual; guint sections_expected = fu_version_format_number_sections(fmt); g_autofree gchar *tmp = NULL; g_auto(GStrv) split = NULL; g_autoptr(GString) str = g_string_new(NULL); /* split into all sections */ tmp = fu_common_version_ensure_semver(version); if (tmp == NULL) return NULL; if (fmt == FWUPD_VERSION_FORMAT_UNKNOWN) return g_steal_pointer(&tmp); split = g_strsplit(tmp, ".", -1); sections_actual = g_strv_length(split); /* add zero sections as required */ if (sections_actual < sections_expected) { for (guint i = 0; i < sections_expected - sections_actual; i++) { if (str->len > 0) g_string_append(str, "."); g_string_append(str, "0"); } } /* only add enough sections for the format */ for (guint i = 0; i < sections_actual && i < sections_expected; i++) { if (str->len > 0) g_string_append(str, "."); g_string_append(str, split[i]); } /* success */ return g_string_free(g_steal_pointer(&str), FALSE); } /** * fu_common_version_ensure_semver: * @version: (nullable): a version number, e.g. ` V1.2.3 ` * * Builds a semver from the possibly crazy version number. * * Returns: a version number, e.g. `1.2.3`, or %NULL if the version was not valid */ static gchar * fu_common_version_ensure_semver(const gchar *version) { gboolean dot_valid = FALSE; guint digit_cnt = 0; g_autoptr(GString) version_safe = g_string_new(NULL); /* invalid */ if (version == NULL) return NULL; /* hex prefix */ if (g_str_has_prefix(version, "0x")) { return fu_version_parse_from_format(version, FWUPD_VERSION_FORMAT_TRIPLET); } /* make sane */ for (guint i = 0; version[i] != '\0'; i++) { if (g_ascii_isdigit(version[i])) { g_string_append_c(version_safe, version[i]); digit_cnt++; dot_valid = TRUE; continue; } if (version[i] == '-' || version[i] == '~') { g_string_append_c(version_safe, '.'); dot_valid = FALSE; continue; } if (version[i] == '.' && dot_valid && version[i + 1] != '\0') { g_string_append_c(version_safe, version[i]); dot_valid = FALSE; continue; } } /* remove any trailing dot */ if (version_safe->len > 0 && version_safe->str[version_safe->len - 1] == '.') g_string_truncate(version_safe, version_safe->len - 1); /* found no digits */ if (digit_cnt == 0) return NULL; return g_string_free(g_steal_pointer(&version_safe), FALSE); } /** * fu_version_parse_from_format * @version: (nullable): a version number * @fmt: a version format, e.g. %FWUPD_VERSION_FORMAT_TRIPLET * * Returns a dotted decimal version string from a version string using @fmt. * The supported formats are: * * - Dotted decimal, e.g. `1.2.3` * - Base 16, a hex number *with* a 0x prefix, e.g. `0x10203` * - Base 10, a string containing just [0-9], e.g. `66051` * - Date in YYYYMMDD format, e.g. `20150915` * * Anything with a `.` or that doesn't match `[0-9]` or `0x[a-f,0-9]` is considered * a string and returned without modification. * * Returns: a version number, e.g. `1.0.3`, or %NULL on error * * Since: 1.8.2 */ gchar * fu_version_parse_from_format(const gchar *version, FwupdVersionFormat fmt) { const gchar *version_noprefix = version; gchar *endptr = NULL; guint64 tmp; guint base; /* sanity check */ if (version == NULL) return NULL; /* already dotted decimal */ if (g_strstr_len(version, -1, ".") != NULL) return g_strdup(version); /* is a date */ if (g_str_has_prefix(version, "20") && strlen(version) == 8) return g_strdup(version); /* convert 0x prefixed strings to dotted decimal */ if (g_str_has_prefix(version, "0x")) { version_noprefix += 2; base = 16; } else { /* for non-numeric content, just return the string */ if (!_g_ascii_is_digits(version)) return g_strdup(version); base = 10; } /* convert */ tmp = g_ascii_strtoull(version_noprefix, &endptr, base); if (endptr != NULL && endptr[0] != '\0') return g_strdup(version); if (tmp == 0) return g_strdup(version); return fu_version_from_uint32((guint32)tmp, fmt); } /** * fu_version_guess_format: * @version: (nullable): a version number, e.g. `1.2.3` * * Guesses the version format from the version number. This is only a heuristic * and plugins and components should explicitly set the version format whenever * possible. * * If the version format cannot be guessed with any degree of accuracy, the * %FWUPD_VERSION_FORMAT_UNKNOWN constant is returned. * * Returns: a version format, e.g. %FWUPD_VERSION_FORMAT_QUAD * * Since: 1.8.2 */ FwupdVersionFormat fu_version_guess_format(const gchar *version) { guint sz; g_auto(GStrv) split = NULL; /* nothing to use */ if (version == NULL || version[0] == '\0') return FWUPD_VERSION_FORMAT_UNKNOWN; /* no dots, assume just text */ split = g_strsplit(version, ".", -1); sz = g_strv_length(split); if (sz == 1) { if (g_str_has_prefix(version, "0x") || _g_ascii_is_digits(version)) return FWUPD_VERSION_FORMAT_NUMBER; return FWUPD_VERSION_FORMAT_PLAIN; } /* check for only-digit semver version */ for (guint i = 0; split[i] != NULL; i++) { /* check sections are plain numbers */ if (!_g_ascii_is_digits(split[i])) return FWUPD_VERSION_FORMAT_PLAIN; } /* the most common formats */ if (sz == 2) return FWUPD_VERSION_FORMAT_PAIR; if (sz == 3) return FWUPD_VERSION_FORMAT_TRIPLET; if (sz == 4) return FWUPD_VERSION_FORMAT_QUAD; /* unknown! */ return FWUPD_VERSION_FORMAT_UNKNOWN; } static FwupdVersionFormat fu_version_format_convert_base(FwupdVersionFormat fmt) { if (fmt == FWUPD_VERSION_FORMAT_INTEL_ME || fmt == FWUPD_VERSION_FORMAT_INTEL_ME2) return FWUPD_VERSION_FORMAT_QUAD; if (fmt == FWUPD_VERSION_FORMAT_DELL_BIOS) return FWUPD_VERSION_FORMAT_TRIPLET; if (fmt == FWUPD_VERSION_FORMAT_BCD) return FWUPD_VERSION_FORMAT_PAIR; if (fmt == FWUPD_VERSION_FORMAT_HEX) return FWUPD_VERSION_FORMAT_NUMBER; return fmt; } /** * fu_version_verify_format: * @version: (not nullable): a string, e.g. `0x1234` * @fmt: a version format * @error: (nullable): optional return location for an error * * Verifies if a version matches the input format. * * Returns: TRUE or FALSE * * Since: 1.8.2 **/ gboolean fu_version_verify_format(const gchar *version, FwupdVersionFormat fmt, GError **error) { FwupdVersionFormat fmt_base = fu_version_format_convert_base(fmt); FwupdVersionFormat fmt_guess; g_return_val_if_fail(version != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* don't touch */ if (fmt == FWUPD_VERSION_FORMAT_PLAIN) return TRUE; /* nothing we can check for */ if (fmt == FWUPD_VERSION_FORMAT_UNKNOWN) return TRUE; /* check the base format */ fmt_guess = fu_version_guess_format(version); if (fmt_guess != fmt_base) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%s is not a valid %s (guessed %s)", version, fwupd_version_format_to_string(fmt), fwupd_version_format_to_string(fmt_guess)); return FALSE; } return TRUE; } static gint fu_version_compare_safe(const gchar *version_a, const gchar *version_b) { guint longest_split; g_auto(GStrv) split_a = NULL; g_auto(GStrv) split_b = NULL; /* sanity check */ if (version_a == NULL || version_b == NULL) return G_MAXINT; /* optimization */ if (g_strcmp0(version_a, version_b) == 0) return 0; /* split into sections, and try to parse */ split_a = g_strsplit(version_a, ".", -1); split_b = g_strsplit(version_b, ".", -1); longest_split = MAX(g_strv_length(split_a), g_strv_length(split_b)); for (guint i = 0; i < longest_split; i++) { gchar *endptr_a = NULL; gchar *endptr_b = NULL; gint64 ver_a; gint64 ver_b; /* we lost or gained a dot */ if (split_a[i] == NULL) return -1; if (split_b[i] == NULL) return 1; /* compare integers */ ver_a = g_ascii_strtoll(split_a[i], &endptr_a, 10); ver_b = g_ascii_strtoll(split_b[i], &endptr_b, 10); if (ver_a < ver_b) return -1; if (ver_a > ver_b) return 1; /* compare strings */ if ((endptr_a != NULL && endptr_a[0] != '\0') || (endptr_b != NULL && endptr_b[0] != '\0')) { gint rc = fu_version_compare_chunk(endptr_a, endptr_b); if (rc < 0) return -1; if (rc > 0) return 1; } } /* we really shouldn't get here */ return 0; } /** * fu_version_compare: * @version_a: (nullable): the semver release version, e.g. `1.2.3` * @version_b: (nullable): the semver release version, e.g. `1.2.3.1` * @fmt: a version format, e.g. %FWUPD_VERSION_FORMAT_PLAIN * * Compares version numbers for sorting taking into account the version format * if required. * * Returns: -1 if a < b, +1 if a > b, 0 if they are equal, and %G_MAXINT on error * * Since: 1.8.2 */ gint fu_version_compare(const gchar *version_a, const gchar *version_b, FwupdVersionFormat fmt) { if (fmt == FWUPD_VERSION_FORMAT_PLAIN) return g_strcmp0(version_a, version_b); if (fmt == FWUPD_VERSION_FORMAT_HEX) { g_autofree gchar *hex_a = NULL; g_autofree gchar *hex_b = NULL; hex_a = fu_version_parse_from_format(version_a, fmt); hex_b = fu_version_parse_from_format(version_b, fmt); return fu_version_compare_safe(hex_a, hex_b); } return fu_version_compare_safe(version_a, version_b); } fwupd-1.9.16/libfwupdplugin/fu-version-common.h000066400000000000000000000016531460375044200215360ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include gint fu_version_compare(const gchar *version_a, const gchar *version_b, FwupdVersionFormat fmt); gchar * fu_version_from_uint64(guint64 val, FwupdVersionFormat kind); gchar * fu_version_from_uint32(guint32 val, FwupdVersionFormat kind); gchar * fu_version_from_uint24(guint32 val, FwupdVersionFormat kind); gchar * fu_version_from_uint16(guint16 val, FwupdVersionFormat kind); gchar * fu_version_parse_from_format(const gchar *version, FwupdVersionFormat fmt); gchar * fu_version_ensure_semver(const gchar *version, FwupdVersionFormat fmt) G_GNUC_NON_NULL(1); FwupdVersionFormat fu_version_guess_format(const gchar *version); gboolean fu_version_verify_format(const gchar *version, FwupdVersionFormat fmt, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-version.c000066400000000000000000000010641460375044200202370ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-version.h" /** * fu_version_string: * * Gets the libfwupdplugin installed runtime version. * * This may be different to the *build-time* version if the daemon and library * objects somehow get out of sync. * * Returns: version string * * Since: 1.6.1 **/ const gchar * fu_version_string(void) { return G_STRINGIFY(FU_MAJOR_VERSION) "." G_STRINGIFY(FU_MINOR_VERSION) "." G_STRINGIFY( FU_MICRO_VERSION); } fwupd-1.9.16/libfwupdplugin/fu-version.h.in000066400000000000000000000023371460375044200206550ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /* clang-format off */ /** * FU_MAJOR_VERSION: * * The compile-time major version */ #define FU_MAJOR_VERSION @MAJOR_VERSION@ /** * FU_MINOR_VERSION: * * The compile-time minor version */ #define FU_MINOR_VERSION @MINOR_VERSION@ /** * FU_MICRO_VERSION: * * The compile-time micro version */ #define FU_MICRO_VERSION @MICRO_VERSION@ /* clang-format on */ /** * FU_CHECK_VERSION: * @major: Major version number * @minor: Minor version number * @micro: Micro version number * * Check whether a fwupd version equal to or greater than * major.minor.micro. * * These compile time macros allow the user to enable parts of client code * depending on the version of libfwupd installed. */ #define FU_CHECK_VERSION(major, minor, micro) \ (FU_MAJOR_VERSION > major || \ (FU_MAJOR_VERSION == major && FU_MINOR_VERSION > minor) || \ (FU_MAJOR_VERSION == major && FU_MINOR_VERSION == minor && \ FU_MICRO_VERSION >= micro)) const gchar * fu_version_string(void); fwupd-1.9.16/libfwupdplugin/fu-volume-private.h000066400000000000000000000005031460375044200215330ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-volume.h" FuVolume * fu_volume_new_from_mount_path(const gchar *mount_path) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fu-volume.c000066400000000000000000000637171460375044200200760ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuVolume" #include "config.h" #include #include #if defined(HAVE_IOCTL_H) && defined(HAVE_BLKSSZGET) #include #include #include #endif #include "fwupd-error.h" #include "fu-common-private.h" #include "fu-volume-private.h" /** * FuVolume: * * Volume abstraction that uses UDisks */ struct _FuVolume { GObject parent_instance; GDBusProxy *proxy_blk; GDBusProxy *proxy_fs; GDBusProxy *proxy_part; gchar *mount_path; /* only when mounted ourselves */ }; enum { PROP_0, PROP_MOUNT_PATH, PROP_PROXY_BLOCK, PROP_PROXY_FILESYSTEM, PROP_PROXY_PARTITION, PROP_LAST }; G_DEFINE_TYPE(FuVolume, fu_volume, G_TYPE_OBJECT) static void fu_volume_finalize(GObject *obj) { FuVolume *self = FU_VOLUME(obj); g_free(self->mount_path); if (self->proxy_blk != NULL) g_object_unref(self->proxy_blk); if (self->proxy_fs != NULL) g_object_unref(self->proxy_fs); if (self->proxy_part != NULL) g_object_unref(self->proxy_part); G_OBJECT_CLASS(fu_volume_parent_class)->finalize(obj); } static void fu_volume_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuVolume *self = FU_VOLUME(object); switch (prop_id) { case PROP_MOUNT_PATH: g_value_set_string(value, self->mount_path); break; case PROP_PROXY_BLOCK: g_value_set_object(value, self->proxy_blk); break; case PROP_PROXY_FILESYSTEM: g_value_set_object(value, self->proxy_fs); break; case PROP_PROXY_PARTITION: g_value_set_object(value, self->proxy_part); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_volume_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuVolume *self = FU_VOLUME(object); switch (prop_id) { case PROP_MOUNT_PATH: self->mount_path = g_value_dup_string(value); break; case PROP_PROXY_BLOCK: self->proxy_blk = g_value_dup_object(value); break; case PROP_PROXY_FILESYSTEM: self->proxy_fs = g_value_dup_object(value); break; case PROP_PROXY_PARTITION: self->proxy_part = g_value_dup_object(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_volume_class_init(FuVolumeClass *klass) { GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_volume_finalize; object_class->get_property = fu_volume_get_property; object_class->set_property = fu_volume_set_property; /** * FuVolume:proxy-block: * * The proxy for the block interface. * * Since: 1.4.6 */ pspec = g_param_spec_object("proxy-block", NULL, NULL, G_TYPE_DBUS_PROXY, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PROXY_BLOCK, pspec); /** * FuVolume:proxy-filesystem: * * The proxy for the filesystem interface. * * Since: 1.4.6 */ pspec = g_param_spec_object("proxy-filesystem", NULL, NULL, G_TYPE_DBUS_PROXY, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PROXY_FILESYSTEM, pspec); /** * FuVolume:mount-path: * * The UNIX mount path. * * Since: 1.4.6 */ pspec = g_param_spec_string("mount-path", NULL, NULL, NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_MOUNT_PATH, pspec); /** * FuVolume:proxy-partition: * * The proxy for the filesystem interface. * * Since: 1.9.3 */ pspec = g_param_spec_object("proxy-partition", NULL, NULL, G_TYPE_DBUS_PROXY, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_PROXY_PARTITION, pspec); } static void fu_volume_init(FuVolume *self) { } /** * fu_volume_get_id: * @self: a @FuVolume * * Gets the D-Bus path of the mount point. * * Returns: string ID, or %NULL * * Since: 1.4.6 **/ const gchar * fu_volume_get_id(FuVolume *self) { g_return_val_if_fail(FU_IS_VOLUME(self), NULL); if (self->proxy_fs != NULL) return g_dbus_proxy_get_object_path(self->proxy_fs); if (self->proxy_blk != NULL) return g_dbus_proxy_get_object_path(self->proxy_blk); if (self->proxy_part != NULL) return g_dbus_proxy_get_object_path(self->proxy_part); return NULL; } /** * fu_volume_get_size: * @self: a @FuVolume * * Gets the size of the block device pointed to by the volume. * * Returns: size in bytes, or 0 on error * * Since: 1.9.3 **/ guint64 fu_volume_get_size(FuVolume *self) { g_autoptr(GVariant) val = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), 0); if (self->proxy_blk == NULL) return 0; val = g_dbus_proxy_get_cached_property(self->proxy_blk, "Size"); if (val == NULL) return 0; return g_variant_get_uint64(val); } /** * fu_volume_get_partition_size: * @self: a @FuVolume * * Gets the size of the partition. * * Returns: size in bytes, or 0 on error * * Since: 1.9.3 **/ guint64 fu_volume_get_partition_size(FuVolume *self) { g_autoptr(GVariant) val = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), 0); if (self->proxy_part == NULL) return 0; val = g_dbus_proxy_get_cached_property(self->proxy_part, "Size"); if (val == NULL) return 0; return g_variant_get_uint64(val); } /** * fu_volume_get_partition_offset: * @self: a @FuVolume * * Gets the offset of the partition. * * Returns: offset in bytes, or 0 on error * * Since: 1.9.3 **/ guint64 fu_volume_get_partition_offset(FuVolume *self) { g_autoptr(GVariant) val = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), 0); if (self->proxy_part == NULL) return 0; val = g_dbus_proxy_get_cached_property(self->proxy_part, "Offset"); if (val == NULL) return 0; return g_variant_get_uint64(val); } /** * fu_volume_get_partition_number: * @self: a @FuVolume * * Gets the number of the partition. * * Returns: size in bytes, or 0 on error * * Since: 1.9.3 **/ guint32 fu_volume_get_partition_number(FuVolume *self) { g_autoptr(GVariant) val = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), 0); if (self->proxy_part == NULL) return 0; val = g_dbus_proxy_get_cached_property(self->proxy_part, "Number"); if (val == NULL) return 0; return g_variant_get_uint32(val); } /** * fu_volume_get_partition_uuid: * @self: a @FuVolume * * Gets the UUID of the partition. * * Returns: size in bytes, or 0 on error * * Since: 1.9.3 **/ gchar * fu_volume_get_partition_uuid(FuVolume *self) { g_autoptr(GVariant) val = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), NULL); if (self->proxy_part == NULL) return NULL; val = g_dbus_proxy_get_cached_property(self->proxy_part, "UUID"); if (val == NULL) return NULL; return g_variant_dup_string(val, NULL); } /** * fu_volume_get_partition_kind: * @self: a @FuVolume * * Gets the partition kind of the volume mount point. * * NOTE: If you want this to be converted to a GPT-style GUID then use * fu_volume_kind_convert_to_gpt() on the return value of this function. * * Returns: (transfer full): partition kind, e.g. `0x06`, `vfat` or a GUID like `FU_VOLUME_KIND_ESP` * * Since: 1.8.13 **/ gchar * fu_volume_get_partition_kind(FuVolume *self) { g_autoptr(GVariant) val = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), NULL); if (self->proxy_part == NULL) return NULL; val = g_dbus_proxy_get_cached_property(self->proxy_part, "Type"); if (val == NULL) return NULL; return g_variant_dup_string(val, NULL); } /** * fu_volume_get_partition_name: * @self: a @FuVolume * * Gets the partition name of the volume mount point. * * Returns: (transfer full): partition name, e.g 'Recovery Partition' * * Since: 1.9.10 **/ gchar * fu_volume_get_partition_name(FuVolume *self) { g_autoptr(GVariant) val = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), NULL); if (self->proxy_part == NULL) return NULL; val = g_dbus_proxy_get_cached_property(self->proxy_part, "Name"); if (val == NULL) return NULL; return g_variant_dup_string(val, NULL); } static guint32 fu_volume_get_block_size_from_device_name(const gchar *device_name, GError **error) { #if defined(HAVE_IOCTL_H) && defined(HAVE_BLKSSZGET) gint fd; gint rc; gint sector_size = 0; fd = g_open(device_name, O_RDONLY, 0); if (fd < 0) { g_set_error_literal(error, G_IO_ERROR, g_io_error_from_errno(errno), g_strerror(errno)); return 0; } rc = ioctl(fd, BLKSSZGET, §or_size); if (rc < 0) { g_set_error_literal(error, G_IO_ERROR, g_io_error_from_errno(errno), g_strerror(errno)); } else if (sector_size == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to get non-zero logical sector size"); } g_close(fd, NULL); return sector_size; #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as or BLKSSZGET not found"); return 0; #endif } /** * fu_volume_get_block_size: * @self: a @FuVolume * * Gets the logical block size of the volume mount point. * * Returns: block size in bytes or 0 on error * * Since: 1.9.4 **/ gsize fu_volume_get_block_size(FuVolume *self, GError **error) { g_autoptr(GVariant) val = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), 0); if (self->proxy_blk == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no udisks proxy"); return 0; } val = g_dbus_proxy_get_cached_property(self->proxy_blk, "Device"); if (val == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no device property"); return 0; } return fu_volume_get_block_size_from_device_name(g_variant_get_bytestring(val), error); } /** * fu_volume_get_mount_point: * @self: a @FuVolume * * Gets the location of the volume mount point. * * Returns: UNIX path, or %NULL * * Since: 1.4.6 **/ gchar * fu_volume_get_mount_point(FuVolume *self) { g_autofree const gchar **mountpoints = NULL; g_autoptr(GVariant) val = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), NULL); /* we mounted it */ if (self->mount_path != NULL) return g_strdup(self->mount_path); /* something else mounted it */ if (self->proxy_fs == NULL) return NULL; val = g_dbus_proxy_get_cached_property(self->proxy_fs, "MountPoints"); if (val == NULL) return NULL; mountpoints = g_variant_get_bytestring_array(val, NULL); return g_strdup(mountpoints[0]); } /** * fu_volume_check_free_space: * @self: a @FuVolume * @required: size in bytes * @error: (nullable): optional return location for an error * * Checks the volume for required space. * * Returns: %TRUE for success * * Since: 1.4.6 **/ gboolean fu_volume_check_free_space(FuVolume *self, guint64 required, GError **error) { guint64 fs_free; g_autofree gchar *path = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GFileInfo) info = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* skip the checks for unmounted disks */ path = fu_volume_get_mount_point(self); if (path == NULL) return TRUE; file = g_file_new_for_path(path); info = g_file_query_filesystem_info(file, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, NULL, error); if (info == NULL) return FALSE; fs_free = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); if (fs_free < required) { g_autofree gchar *str_free = g_format_size(fs_free); g_autofree gchar *str_reqd = g_format_size(required); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s does not have sufficient space, required %s, got %s", path, str_reqd, str_free); return FALSE; } return TRUE; } /** * fu_volume_is_mounted: * @self: a @FuVolume * * Checks if the VOLUME is already mounted. * * Returns: %TRUE for success * * Since: 1.4.6 **/ gboolean fu_volume_is_mounted(FuVolume *self) { g_autofree gchar *mount_point = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), FALSE); mount_point = fu_volume_get_mount_point(self); return mount_point != NULL; } /** * fu_volume_is_encrypted: * @self: a @FuVolume * * Checks if the VOLUME is currently encrypted. * * Returns: %TRUE for success * * Since: 1.5.1 **/ gboolean fu_volume_is_encrypted(FuVolume *self) { g_autoptr(GVariant) val = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), FALSE); if (self->proxy_blk == NULL) return FALSE; val = g_dbus_proxy_get_cached_property(self->proxy_blk, "CryptoBackingDevice"); if (val == NULL) return FALSE; if (g_strcmp0(g_variant_get_string(val, NULL), "/") == 0) return FALSE; return TRUE; } /** * fu_volume_mount: * @self: a @FuVolume * @error: (nullable): optional return location for an error * * Mounts the VOLUME ready for use. * * Returns: %TRUE for success * * Since: 1.4.6 **/ gboolean fu_volume_mount(FuVolume *self, GError **error) { GVariantBuilder builder; g_autoptr(GError) error_local = NULL; g_autoptr(GVariant) val = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* device from the self tests */ if (self->proxy_fs == NULL) return TRUE; g_debug("mounting %s", fu_volume_get_id(self)); g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); val = g_dbus_proxy_call_sync(self->proxy_fs, "Mount", g_variant_new("(a{sv})", &builder), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error_local); if (val == NULL) { if (g_error_matches(error_local, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_INTERFACE) || g_error_matches(error_local, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, error_local->message); return FALSE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } g_variant_get(val, "(s)", &self->mount_path); return TRUE; } /** * fu_volume_is_internal: * @self: a @FuVolume * * Guesses if the drive is internal to the system * * Returns: %TRUE for success * * Since: 1.5.2 **/ gboolean fu_volume_is_internal(FuVolume *self) { g_autoptr(GVariant) val_system = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), FALSE); val_system = g_dbus_proxy_get_cached_property(self->proxy_blk, "HintSystem"); if (val_system == NULL) return FALSE; return g_variant_get_boolean(val_system); } /** * fu_volume_get_id_type: * @self: a @FuVolume * * Return the IdType of the volume * * Returns: string for type or NULL * * Since: 1.5.2 **/ gchar * fu_volume_get_id_type(FuVolume *self) { g_autoptr(GVariant) val = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), NULL); val = g_dbus_proxy_get_cached_property(self->proxy_blk, "IdType"); if (val == NULL) return NULL; return g_strdup(g_variant_get_string(val, NULL)); } /** * fu_volume_unmount: * @self: a @FuVolume * @error: (nullable): optional return location for an error * * Unmounts the volume after use. * * Returns: %TRUE for success * * Since: 1.4.6 **/ gboolean fu_volume_unmount(FuVolume *self, GError **error) { GVariantBuilder builder; g_autoptr(GVariant) val = NULL; g_return_val_if_fail(FU_IS_VOLUME(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* device from the self tests */ if (self->proxy_fs == NULL) return TRUE; g_debug("unmounting %s", fu_volume_get_id(self)); g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); val = g_dbus_proxy_call_sync(self->proxy_fs, "Unmount", g_variant_new("(a{sv})", &builder), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (val == NULL) return FALSE; g_free(self->mount_path); self->mount_path = NULL; return TRUE; } /** * fu_volume_locker: * @self: a @FuVolume * @error: (nullable): optional return location for an error * * Locks the volume, mounting it and unmounting it as required. If the volume is * already mounted then it is is _not_ unmounted when the locker is closed. * * Returns: (transfer full): a device locker for success, or %NULL * * Since: 1.4.6 **/ FuDeviceLocker * fu_volume_locker(FuVolume *self, GError **error) { g_return_val_if_fail(FU_IS_VOLUME(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* already open, so NOP */ if (fu_volume_is_mounted(self)) return g_object_new(FU_TYPE_DEVICE_LOCKER, NULL); return fu_device_locker_new_full(self, (FuDeviceLockerFunc)fu_volume_mount, (FuDeviceLockerFunc)fu_volume_unmount, error); } /* private */ FuVolume * fu_volume_new_from_mount_path(const gchar *mount_path) { g_autoptr(FuVolume) self = g_object_new(FU_TYPE_VOLUME, NULL); g_return_val_if_fail(mount_path != NULL, NULL); self->mount_path = g_strdup(mount_path); return g_steal_pointer(&self); } /** * fu_volume_kind_convert_to_gpt: * @kind: UDisk reported type string, e.g. `efi` or `0xef` * * Converts a MBR type to a GPT type. * * Returns: the GPT type, usually a GUID. If not known @kind is returned. * * Since: 1.8.6 **/ const gchar * fu_volume_kind_convert_to_gpt(const gchar *kind) { struct { const gchar *gpt; const gchar *mbrs[6]; } typeguids[] = {{FU_VOLUME_KIND_ESP, { "0xef", "efi", NULL, }}, {FU_VOLUME_KIND_BDP, { "0x0b", "0x06", "vfat", "fat32", "fat32lba", NULL, }}, {NULL, {NULL}}}; for (guint i = 0; typeguids[i].gpt != NULL; i++) { for (guint j = 0; typeguids[i].mbrs[j] != NULL; j++) { if (g_strcmp0(kind, typeguids[i].mbrs[j]) == 0) return typeguids[i].gpt; } } return kind; } static gboolean fu_volume_check_block_device_symlinks(const gchar *const *symlinks, GError **error) { for (guint i = 0; symlinks[i] != NULL; i++) { if (g_str_has_prefix(symlinks[i], "/dev/zvol")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "detected zfs zvol"); return FALSE; } } /* success */ return TRUE; } /** * fu_volume_new_by_kind: * @kind: a volume kind, typically a GUID * @error: (nullable): optional return location for an error * * Finds all volumes of a specific partition type * * Returns: (transfer container) (element-type FuVolume): a #GPtrArray, or %NULL if the kind was not *found * * Since: 1.8.2 **/ GPtrArray * fu_volume_new_by_kind(const gchar *kind, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) volumes = NULL; g_return_val_if_fail(kind != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); devices = fu_common_get_block_devices(error); if (devices == NULL) return NULL; volumes = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < devices->len; i++) { GDBusProxy *proxy_blk = g_ptr_array_index(devices, i); const gchar *type_str; g_autofree gchar *id_type = NULL; g_autofree gchar *part_type = NULL; g_autoptr(FuVolume) vol = NULL; g_autoptr(GDBusProxy) proxy_part = NULL; g_autoptr(GDBusProxy) proxy_fs = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GVariant) symlinks = NULL; /* ignore anything in a zfs zvol */ symlinks = g_dbus_proxy_get_cached_property(proxy_blk, "Symlinks"); if (symlinks != NULL) { g_autofree const gchar **symlinks_strv = g_variant_get_bytestring_array(symlinks, NULL); if (!fu_volume_check_block_device_symlinks(symlinks_strv, &error_local)) { g_debug("ignoring due to symlink: %s", error_local->message); continue; } } proxy_part = g_dbus_proxy_new_sync(g_dbus_proxy_get_connection(proxy_blk), G_DBUS_PROXY_FLAGS_NONE, NULL, UDISKS_DBUS_SERVICE, g_dbus_proxy_get_object_path(proxy_blk), UDISKS_DBUS_INTERFACE_PARTITION, NULL, error); if (proxy_part == NULL) { g_prefix_error(error, "failed to initialize d-bus proxy %s: ", g_dbus_proxy_get_object_path(proxy_blk)); return NULL; } proxy_fs = g_dbus_proxy_new_sync(g_dbus_proxy_get_connection(proxy_blk), G_DBUS_PROXY_FLAGS_NONE, NULL, UDISKS_DBUS_SERVICE, g_dbus_proxy_get_object_path(proxy_blk), UDISKS_DBUS_INTERFACE_FILESYSTEM, NULL, &error_local); if (proxy_fs == NULL) { g_debug("failed to get filesystem for %s: %s", g_dbus_proxy_get_object_path(proxy_blk), error_local->message); continue; } vol = g_object_new(FU_TYPE_VOLUME, "proxy-block", proxy_blk, "proxy-filesystem", proxy_fs, "proxy-partition", proxy_part, NULL); /* convert reported type to GPT type */ part_type = fu_volume_get_partition_kind(vol); if (part_type == NULL) continue; type_str = fu_volume_kind_convert_to_gpt(part_type); id_type = fu_volume_get_id_type(vol); g_debug("device %s, type: %s, internal: %d, fs: %s", g_dbus_proxy_get_object_path(proxy_blk), type_str, fu_volume_is_internal(vol), id_type); if (g_strcmp0(type_str, kind) != 0) continue; g_ptr_array_add(volumes, g_steal_pointer(&vol)); } if (volumes->len == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no volumes of type %s", kind); return NULL; } return g_steal_pointer(&volumes); } /** * fu_volume_new_by_device: * @device: a device string, typically starting with `/dev/` * @error: (nullable): optional return location for an error * * Finds the first volume from the specified device. * * Returns: (transfer full): a volume, or %NULL if the device was not found * * Since: 1.8.2 **/ FuVolume * fu_volume_new_by_device(const gchar *device, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_return_val_if_fail(device != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* find matching block device */ devices = fu_common_get_block_devices(error); if (devices == NULL) return NULL; for (guint i = 0; i < devices->len; i++) { GDBusProxy *proxy_blk = g_ptr_array_index(devices, i); g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy_blk, "Device"); if (val == NULL) continue; if (g_strcmp0(g_variant_get_bytestring(val), device) == 0) { g_autoptr(GDBusProxy) proxy_fs = NULL; g_autoptr(GDBusProxy) proxy_part = NULL; g_autoptr(GError) error_local = NULL; proxy_fs = g_dbus_proxy_new_sync(g_dbus_proxy_get_connection(proxy_blk), G_DBUS_PROXY_FLAGS_NONE, NULL, UDISKS_DBUS_SERVICE, g_dbus_proxy_get_object_path(proxy_blk), UDISKS_DBUS_INTERFACE_FILESYSTEM, NULL, &error_local); if (proxy_fs == NULL) g_debug("ignoring: %s", error_local->message); proxy_part = g_dbus_proxy_new_sync(g_dbus_proxy_get_connection(proxy_blk), G_DBUS_PROXY_FLAGS_NONE, NULL, UDISKS_DBUS_SERVICE, g_dbus_proxy_get_object_path(proxy_blk), UDISKS_DBUS_INTERFACE_PARTITION, NULL, &error_local); if (proxy_part == NULL) g_debug("ignoring: %s", error_local->message); return g_object_new(FU_TYPE_VOLUME, "proxy-block", proxy_blk, "proxy-filesystem", proxy_fs, "proxy-partition", proxy_part, NULL); } } /* failed */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no volumes for device %s", device); return NULL; } /** * fu_volume_new_by_devnum: * @devnum: a device number * @error: (nullable): optional return location for an error * * Finds the first volume from the specified device. * * Returns: (transfer full): a volume, or %NULL if the device was not found * * Since: 1.8.2 **/ FuVolume * fu_volume_new_by_devnum(guint32 devnum, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* find matching block device */ devices = fu_common_get_block_devices(error); if (devices == NULL) return NULL; for (guint i = 0; i < devices->len; i++) { GDBusProxy *proxy_blk = g_ptr_array_index(devices, i); g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_get_cached_property(proxy_blk, "DeviceNumber"); if (val == NULL) continue; if (devnum == g_variant_get_uint64(val)) { return g_object_new(FU_TYPE_VOLUME, "proxy-block", proxy_blk, NULL); } } /* failed */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no volumes for devnum %u", devnum); return NULL; } /** * fu_volume_new_esp_for_path: * @esp_path: a path to the ESP * @error: (nullable): optional return location for an error * * Gets the platform ESP using a UNIX or UDisks path * * Returns: (transfer full): a #volume, or %NULL if the ESP was not found * * Since: 1.8.2 **/ FuVolume * fu_volume_new_esp_for_path(const gchar *esp_path, GError **error) { g_autofree gchar *basename = NULL; g_autoptr(GPtrArray) volumes = NULL; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(esp_path != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); volumes = fu_volume_new_by_kind(FU_VOLUME_KIND_ESP, &error_local); if (volumes == NULL) { /* check if it's a valid directory already */ if (g_file_test(esp_path, G_FILE_TEST_IS_DIR)) return fu_volume_new_from_mount_path(esp_path); g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "cannot fall back to %s as not a directory: ", esp_path); return NULL; } basename = g_path_get_basename(esp_path); for (guint i = 0; i < volumes->len; i++) { FuVolume *vol = g_ptr_array_index(volumes, i); g_autofree gchar *mount_point = fu_volume_get_mount_point(vol); g_autofree gchar *vol_basename = NULL; if (mount_point == NULL) continue; vol_basename = g_path_get_basename(mount_point); if (g_strcmp0(basename, vol_basename) == 0) return g_object_ref(vol); } if (g_file_test(esp_path, G_FILE_TEST_IS_DIR)) { g_info("using user requested path %s for ESP", esp_path); return fu_volume_new_from_mount_path(esp_path); } g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, "No ESP with path %s", esp_path); return NULL; } fwupd-1.9.16/libfwupdplugin/fu-volume.h000066400000000000000000000053171460375044200200730ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-device-locker.h" #define FU_TYPE_VOLUME (fu_volume_get_type()) G_DECLARE_FINAL_TYPE(FuVolume, fu_volume, FU, VOLUME, GObject) /** * FU_VOLUME_KIND_ESP: * * The GUID for the ESP, see: https://en.wikipedia.org/wiki/EFI_system_partition * * Since: 1.5.0 **/ #define FU_VOLUME_KIND_ESP "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" /** * FU_VOLUME_KIND_BDP: * * The GUID for the BDP, see: https://en.wikipedia.org/wiki/Microsoft_basic_data_partition * * Since: 1.5.3 **/ #define FU_VOLUME_KIND_BDP "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7" const gchar * fu_volume_get_id(FuVolume *self) G_GNUC_NON_NULL(1); gboolean fu_volume_check_free_space(FuVolume *self, guint64 required, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_volume_is_mounted(FuVolume *self) G_GNUC_NON_NULL(1); gboolean fu_volume_is_encrypted(FuVolume *self) G_GNUC_NON_NULL(1); guint64 fu_volume_get_size(FuVolume *self) G_GNUC_NON_NULL(1); gsize fu_volume_get_block_size(FuVolume *self, GError **error) G_GNUC_NON_NULL(1); gchar * fu_volume_get_partition_kind(FuVolume *self) G_GNUC_NON_NULL(1); gchar * fu_volume_get_partition_name(FuVolume *self) G_GNUC_NON_NULL(1); guint64 fu_volume_get_partition_size(FuVolume *self) G_GNUC_NON_NULL(1); guint64 fu_volume_get_partition_offset(FuVolume *self) G_GNUC_NON_NULL(1); guint32 fu_volume_get_partition_number(FuVolume *self) G_GNUC_NON_NULL(1); gchar * fu_volume_get_partition_uuid(FuVolume *self) G_GNUC_NON_NULL(1); gchar * fu_volume_get_mount_point(FuVolume *self) G_GNUC_NON_NULL(1); gboolean fu_volume_mount(FuVolume *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_volume_unmount(FuVolume *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); FuDeviceLocker * fu_volume_locker(FuVolume *self, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); gboolean fu_volume_is_internal(FuVolume *self) G_GNUC_NON_NULL(1); gchar * fu_volume_get_id_type(FuVolume *self) G_GNUC_NON_NULL(1); GPtrArray * fu_volume_new_by_kind(const gchar *kind, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); FuVolume * fu_volume_new_by_device(const gchar *device, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); FuVolume * fu_volume_new_by_devnum(guint32 devnum, GError **error) G_GNUC_WARN_UNUSED_RESULT; FuVolume * fu_volume_new_esp_for_path(const gchar *esp_path, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_NON_NULL(1); const gchar * fu_volume_kind_convert_to_gpt(const gchar *kind) G_GNUC_NON_NULL(1); fwupd-1.9.16/libfwupdplugin/fwupdplugin.h000066400000000000000000000075561460375044200205270ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #define __FWUPDPLUGIN_H_INSIDE__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef __FWUPDPLUGIN_H_INSIDE__ fwupd-1.9.16/libfwupdplugin/meson.build000066400000000000000000000260201460375044200201370ustar00rootroot00000000000000fwupdplugin_version_h = configure_file( input: 'fu-version.h.in', output: 'fu-version.h', configuration: conf ) fwupdplugin_structs = [ 'fu-acpi-table.rs', # fuzzing 'fu-archive.rs', # fuzzing 'fu-cab.rs', # fuzzing 'fu-cfu-firmware.rs', # fuzzing 'fu-coswid.rs', # fuzzing 'fu-dfu-firmware.rs', # fuzzing 'fu-dpaux.rs', # fuzzing 'fu-edid.rs', # fuzzing 'fu-efi.rs', # fuzzing 'fu-elf.rs', # fuzzing 'fu-fdt.rs', # fuzzing 'fu-fmap.rs', # fuzzing 'fu-hid.rs', # fuzzing 'fu-ifd.rs', # fuzzing 'fu-ifwi.rs', # fuzzing 'fu-intel-thunderbolt.rs', # fuzzing 'fu-oprom.rs', # fuzzing 'fu-pefile.rs', # fuzzing 'fu-progress.rs', # fuzzing 'fu-sbatlevel-section.rs', # fuzzing 'fu-smbios.rs', # fuzzing 'fu-usb-device-ds20.rs', # fuzzing 'fu-uswid.rs', # fuzzing ] # do not use structgen as these are referenced in headers too... fwupdplugin_rs_targets = [] foreach fwupdplugin_struct: fwupdplugin_structs fwupdplugin_rs_targets += custom_target('lib' + fwupdplugin_struct, input: fwupdplugin_struct, output: [ fwupdplugin_struct.replace('.rs', '-struct.c'), fwupdplugin_struct.replace('.rs', '-struct.h'), ], command: [ python3, join_paths(meson.project_source_root(), 'libfwupdplugin', 'rustgen.py'), '@INPUT@', '@OUTPUT0@', '@OUTPUT1@', ], ) endforeach fwupdplugin_src = [ 'fu-acpi-table.c', # fuzzing 'fu-archive.c', 'fu-archive-firmware.c', 'fu-backend.c', 'fu-bios-setting.c', # fuzzing 'fu-bios-settings.c', # fuzzing 'fu-bluez-device.c', 'fu-byte-array.c', # fuzzing 'fu-bytes.c', # fuzzing 'fu-cab-firmware.c', # fuzzing 'fu-cab-image.c', # fuzzing 'fu-cfi-device.c', 'fu-cfu-offer.c', # fuzzing 'fu-cfu-payload.c', # fuzzing 'fu-chunk.c', # fuzzing 'fu-chunk-array.c', # fuzzing 'fu-common.c', # fuzzing 'fu-common-guid.c', 'fu-config.c', # fuzzing 'fu-context.c', # fuzzing 'fu-coswid-firmware.c', # fuzzing 'fu-crc.c', # fuzzing 'fu-csv-entry.c', # fuzzing 'fu-csv-firmware.c', # fuzzing 'fu-device.c', # fuzzing 'fu-device-locker.c', # fuzzing 'fu-device-progress.c', 'fu-dfu-firmware.c', # fuzzing 'fu-dfuse-firmware.c', # fuzzing 'fu-dpaux-device.c', 'fu-drm-device.c', 'fu-dump.c', # fuzzing 'fu-edid.c', # fuzzing 'fu-efi-common.c', # fuzzing 'fu-efi-device-path.c', # fuzzing 'fu-efi-device-path-list.c', # fuzzing 'fu-efi-file-path-device-path.c', # fuzzing 'fu-efi-firmware-common.c', # fuzzing 'fu-efi-firmware-file.c', # fuzzing 'fu-efi-firmware-filesystem.c', # fuzzing 'fu-efi-firmware-section.c', # fuzzing 'fu-efi-firmware-volume.c', # fuzzing 'fu-efi-hard-drive-device-path.c', # fuzzing 'fu-efi-load-option.c', # fuzzing 'fu-efi-signature.c', 'fu-efi-signature-list.c', 'fu-efivar.c', # fuzzing 'fu-elf-firmware.c', # fuzzing 'fu-fdt-firmware.c', # fuzzing 'fu-fdt-image.c', # fuzzing 'fu-firmware.c', # fuzzing 'fu-firmware-common.c', # fuzzing 'fu-fit-firmware.c', # fuzzing 'fu-fmap-firmware.c', # fuzzing 'fu-hid-descriptor.c', # fuzzing 'fu-hid-device.c', 'fu-hid-report-item.c', # fuzzing 'fu-hid-report.c', # fuzzing 'fu-hwids.c', # fuzzing 'fu-hwids-config.c', # fuzzing 'fu-hwids-dmi.c', # fuzzing 'fu-hwids-fdt.c', # fuzzing 'fu-hwids-kenv.c', # fuzzing 'fu-hwids-darwin.c', # fuzzing 'fu-hwids-smbios.c', # fuzzing 'fu-i2c-device.c', 'fu-ifd-bios.c', # fuzzing 'fu-ifd-common.c', # fuzzing 'fu-ifd-firmware.c', # fuzzing 'fu-ifd-image.c', # fuzzing 'fu-ifwi-cpd-firmware.c', # fuzzing 'fu-ifwi-fpt-firmware.c', # fuzzing 'fu-ihex-firmware.c', # fuzzing 'fu-intel-thunderbolt-firmware.c', # fuzzing 'fu-intel-thunderbolt-nvm.c', # fuzzing 'fu-io-channel.c', # fuzzing 'fu-kenv.c', # fuzzing 'fu-kernel.c', # fuzzing 'fu-linear-firmware.c', 'fu-lzma-common.c', # fuzzing 'fu-mei-device.c', 'fu-mem.c', # fuzzing 'fu-oprom-firmware.c', # fuzzing 'fu-path.c', # fuzzing 'fu-pefile-firmware.c', # fuzzing 'fu-plugin.c', 'fu-progress.c', # fuzzing 'fu-quirks.c', # fuzzing 'fu-sbatlevel-section.c', # fuzzing 'fu-security-attr.c', # fuzzing 'fu-security-attrs.c', 'fu-smbios.c', # fuzzing 'fu-srec-firmware.c', # fuzzing 'fu-string.c', # fuzzing 'fu-sum.c', # fuzzing 'fu-udev-device.c', 'fu-usb-device.c', 'fu-usb-device-ds20.c', # fuzzing 'fu-usb-device-fw-ds20.c', # fuzzing 'fu-usb-device-ms-ds20.c', # fuzzing 'fu-uswid-firmware.c', # fuzzing 'fu-version.c', 'fu-version-common.c', # fuzzing 'fu-volume.c', # fuzzing ] if host_machine.system() == 'linux' fwupdplugin_src += 'fu-common-linux.c' # fuzzing fwupdplugin_src += 'fu-efivar-linux.c' elif host_machine.system() == 'freebsd' fwupdplugin_src += 'fu-common-freebsd.c' fwupdplugin_src += 'fu-efivar-freebsd.c' elif host_machine.system() == 'windows' fwupdplugin_src += 'fu-common-windows.c' fwupdplugin_src += 'fu-efivar-windows.c' elif host_machine.system() == 'darwin' fwupdplugin_src += 'fu-common-darwin.c' fwupdplugin_src += 'fu-efivar-darwin.c' else fwupdplugin_src += 'fu-efivar-impl.c' # fuzzing endif fwupdplugin_headers = [ 'fu-acpi-table.h', 'fu-archive-firmware.h', 'fu-archive.h', 'fu-backend.h', 'fu-backend-private.h', 'fu-bios-settings.h', 'fu-bios-settings-private.h', 'fu-bluez-device.h', 'fu-byte-array.h', 'fu-bytes.h', 'fu-cab-firmware.h', 'fu-cab-image.h', 'fu-cfi-device.h', 'fu-cfu-offer.h', 'fu-cfu-payload.h', 'fu-chunk.h', 'fu-chunk-array.h', 'fu-common-guid.h', 'fu-common.h', 'fu-config.h', 'fu-config-private.h', 'fu-context.h', 'fu-context-private.h', 'fu-coswid-firmware.h', 'fu-crc.h', 'fu-csv-entry.h', 'fu-csv-firmware.h', 'fu-device.h', 'fu-device-locker.h', 'fu-device-metadata.h', 'fu-device-private.h', 'fu-device-progress.h', 'fu-dfu-firmware.h', 'fu-dfuse-firmware.h', 'fu-dpaux-device.h', 'fu-drm-device.h', 'fu-dump.h', 'fu-edid.h', 'fu-efi-common.h', 'fu-efi-firmware-file.h', 'fu-efi-firmware-filesystem.h', 'fu-efi-firmware-section.h', 'fu-efi-firmware-volume.h', 'fu-efi-load-option.h', 'fu-efi-signature.h', 'fu-efi-signature-list.h', 'fu-efivar.h', 'fu-elf-firmware.h', 'fu-fdt-firmware.h', 'fu-fdt-image.h', 'fu-firmware-common.h', 'fu-firmware.h', 'fu-fit-firmware.h', 'fu-fmap-firmware.h', 'fu-hid-descriptor.h', 'fu-hid-device.h', 'fu-hid-report.h', 'fu-hwids.h', 'fu-i2c-device.h', 'fu-ifd-bios.h', 'fu-ifd-common.h', 'fu-ifd-firmware.h', 'fu-ifd-image.h', 'fu-ifwi-cpd-firmware.h', 'fu-ifwi-fpt-firmware.h', 'fu-ihex-firmware.h', 'fu-intel-thunderbolt-firmware.h', 'fu-intel-thunderbolt-nvm.h', 'fu-io-channel.h', 'fu-kenv.h', 'fu-kernel.h', 'fu-linear-firmware.h', 'fu-mei-device.h', 'fu-mem.h', 'fu-mem-private.h', 'fu-oprom-firmware.h', 'fu-path.h', 'fu-pefile-firmware.h', 'fu-plugin.h', 'fu-plugin-private.h', 'fu-progress.h', 'fu-quirks.h', 'fu-sbatlevel-section.h', 'fu-security-attr.h', 'fu-security-attrs.h', 'fu-security-attrs-private.h', 'fu-smbios.h', 'fu-smbios-private.h', 'fu-srec-firmware.h', 'fu-string.h', 'fu-sum.h', 'fu-udev-device.h', 'fu-udev-device-private.h', 'fu-usb-device-ds20.h', 'fu-usb-device-fw-ds20.h', 'fu-usb-device.h', 'fu-usb-device-ms-ds20.h', 'fu-usb-device-private.h', 'fu-uswid-firmware.h', 'fu-version-common.h', 'fu-volume.h', fwupdplugin_version_h, ] introspection_deps = [ libxmlb, giounix, gusb, gudev, ] pkgg_requires = [ 'gio-2.0', 'gmodule-2.0', 'gobject-2.0', 'fwupd', 'json-glib-1.0', 'libarchive', 'xmlb', 'gusb' ] library_deps = [ introspection_deps, gmodule, libjsonglib, zlib, valgrind, lzma, libarchive, cbor, platform_deps, ] fwupdplugin = library( 'fwupdplugin', fwupdplugin_rs_targets, sources: [ fwupdplugin_src, fwupdplugin_headers, ], include_directories: [ root_incdir, fwupd_incdir, ], dependencies: [ library_deps ], link_with: [ fwupd, ], install_dir: libdir_pkg, install: true ) # see https://mesonbuild.com/FAQ.html#how-do-i-tell-meson-that-my-sources-use-generated-headers fwupdplugin_rs_headers = [] foreach fwupdplugin_rs_target: fwupdplugin_rs_targets fwupdplugin_rs_headers += fwupdplugin_rs_target[1] endforeach fwupdplugin_rs_dep = declare_dependency(link_with: fwupdplugin, sources: fwupdplugin_rs_headers) if introspection.allowed() gir_dep = declare_dependency(sources: fwupd_gir) girtargets = [] extra_args = [] if gusb.found() if gusb.type_name() == 'internal' girtargets += subproject('gusb').get_variable('libgusb_girtarget')[0] else girtargets += 'GUsb-1.0' endif extra_args += '-DHAVE_GUSB' endif if libxmlb.type_name() == 'internal' girtargets += subproject('libxmlb').get_variable('gir')[0] elif libxmlb.version().version_compare ('>= 0.3.2') girtargets += 'Xmlb-2.0' endif fwupdplugin_gir = gnome.generate_gir(fwupd, sources: [ fwupdplugin_src, fwupdplugin_headers, fwupdplugin_rs_targets, ], nsversion: '1.0', namespace: 'FwupdPlugin', symbol_prefix: 'fu', identifier_prefix: 'Fu', export_packages: 'fwupdplugin', extra_args: extra_args, include_directories: [ fwupd_incdir, ], header: 'fwupdplugin.h', dependencies: [ gir_dep, introspection_deps ], link_with: [ fwupdplugin, ], includes: [ 'Gio-2.0', 'GObject-2.0', girtargets, fwupd_gir[0], ], install: false ) endif if get_option('tests') gcab = executable( 'gcab', sources: [ 'fu-gcab.c', ], include_directories: [ root_incdir, fwupd_incdir, ], dependencies: [ library_deps, fwupdplugin_rs_dep, ], link_with: [ fwupd, fwupdplugin ], ) subdir('tests') endif if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'fwupdplugin-self-test', installed_firmware_zip, rustgen.process('fu-self-test.rs'), sources: [ 'fu-self-test.c' ], include_directories: [ root_incdir, fwupd_incdir, ], dependencies: [ library_deps, fwupdplugin_rs_dep, ], link_with: [ fwupd, fwupdplugin ], c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('fwupdplugin-self-test', e, is_parallel: false, timeout: 180, env: env) install_data([ 'tests/chassis_type', 'tests/sys_vendor', ], install_dir: installed_test_datadir, ) install_data([ 'tests/dmi/tables/DMI', 'tests/dmi/tables/smbios_entry_point', ], install_dir: join_paths(installed_test_datadir, 'tests/dmi/tables'), ) install_data([ 'tests/dmi/tables64/DMI', 'tests/dmi/tables64/smbios_entry_point', ], install_dir: join_paths(installed_test_datadir, 'tests/dmi/tables64'), ) endif fwupdplugin_incdir = include_directories('.') fwupd-1.9.16/libfwupdplugin/rustgen.py000077500000000000000000000476601460375044200200560ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2023 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import os import sys import argparse from enum import Enum from typing import Optional, List, Tuple, Dict from jinja2 import Environment, FileSystemLoader, select_autoescape class Endian(Enum): NATIVE = "native" LITTLE = "le" BIG = "be" class Type(Enum): U8 = "u8" U16 = "u16" U24 = "u24" U32 = "u32" U64 = "u64" STRING = "char" GUID = "Guid" class Export(Enum): NONE = "none" PRIVATE = "static " PUBLIC = "" # convert a CamelCase name into snake_case def _camel_to_snake(name: str) -> str: # specified as all caps if name.upper() == name: return name.lower() name_snake: str = "" for char in name: if char.islower() or char.isnumeric(): name_snake += char continue if char == "_": name_snake += char continue if name_snake: name_snake += "_" name_snake += char.lower() return name_snake class EnumObj: def __init__(self, name: str) -> None: self.name: str = name self.repr_type: Optional[str] = None self.items: List[EnumItem] = [] self._exports: Dict[str, Export] = { "ToString": Export.NONE, "ToBitString": Export.NONE, "FromString": Export.NONE, } def c_method(self, suffix: str): return f"fu_{_camel_to_snake(self.name)}_{_camel_to_snake(suffix)}" @property def c_type(self): return f"Fu{self.name}" def item(self, name: str) -> Optional["EnumItem"]: for item in self.items: if item.name == name: return item return None def add_private_export(self, derive: str) -> None: if self._exports[derive] == Export.PUBLIC: return self._exports[derive] = Export.PRIVATE def add_public_export(self, derive: str) -> None: self.add_private_export(derive) self._exports[derive] = Export.PUBLIC def export(self, derive: str) -> Export: return self._exports[derive] def __str__(self) -> str: return f"EnumObj({self.name})" class EnumItem: def __init__(self, obj: EnumObj) -> None: self.obj: EnumObj = obj self.name: str = "" self.default: Optional[str] = None @property def c_define(self) -> str: name_snake = _camel_to_snake(self.obj.name) return f"FU_{name_snake.upper()}_{_camel_to_snake(self.name).replace('-', '_').upper()}" def parse_default(self, val: str) -> None: val = { "u64::MAX": "G_MAXUINT64", "u32::MAX": "G_MAXUINT32", "u16::MAX": "G_MAXUINT16", "u8::MAX": "G_MAXUINT8", }.get(val, val) if val.startswith("0x") or val.startswith("0b"): val = val.replace("_", "") if val.startswith("0b"): val = hex(int(val[2:], 2)) self.default = val @property def value(self) -> str: return _camel_to_snake(self.name).replace("_", "-") def __str__(self) -> str: return f"EnumItem({self.name}={self.default})" class StructObj: def __init__(self, name: str) -> None: self.name: str = name self.items: List[StructItem] = [] self._exports: Dict[str, Export] = { "Validate": Export.NONE, "ValidateBytes": Export.NONE, "Parse": Export.NONE, "ParseBytes": Export.NONE, "New": Export.NONE, "ToString": Export.NONE, } def c_method(self, suffix: str): return f"fu_struct_{_camel_to_snake(self.name)}_{_camel_to_snake(suffix)}" def c_define(self, suffix: str): return f"FU_STRUCT_{_camel_to_snake(self.name).upper()}_{suffix.upper()}" @property def size(self) -> int: size: int = 0 for item in self.items: size += item.size return size @property def has_constant(self) -> bool: for item in self.items: if item.constant: return True return False def add_private_export(self, derive: str) -> None: if self._exports[derive] == Export.PUBLIC: return self._exports[derive] = Export.PRIVATE if derive == "Validate": for item in self.items: if ( item.constant and item.type != Type.STRING and not (item.type == Type.U8 and item.multiplier) ): item.add_private_export("Getters") if item.struct_obj: item.struct_obj.add_private_export("Validate") elif derive == "ValidateBytes": self.add_private_export("Validate") elif derive == "ToString": for item in self.items: if item.enum_obj and not item.constant: item.enum_obj.add_private_export("ToString") elif derive == "ParseBytes": self.add_private_export("Parse") elif derive == "Parse": self.add_private_export("ToString") for item in self.items: if ( item.constant and item.type != Type.STRING and not (item.type == Type.U8 and item.multiplier) ): item.add_private_export("Getters") if item.struct_obj: item.struct_obj.add_private_export("Validate") elif derive == "New": for item in self.items: if item.constant and not (item.type == Type.U8 and item.multiplier): item.add_private_export("Setters") def add_public_export(self, derive: str) -> None: # Getters and Setters are special as we do not want public exports of const if derive in ["Getters", "Setters"]: for item in self.items: if not item.constant: item.add_public_export(derive) else: self.add_private_export(derive) self._exports[derive] = Export.PUBLIC # for convenience if derive in ["Parse", "ParseBytes"]: self.add_public_export("Getters") if derive == "New": self.add_public_export("Setters") def export(self, derive: str) -> Export: return self._exports[derive] def __str__(self) -> str: return f"StructObj({self.name})" class StructItem: def __init__(self, obj: StructObj) -> None: self.obj: StructObj = obj self.element_id: str = "" self.type: Type = Type.U8 self.enum_obj: Optional[EnumObj] = None self.struct_obj: Optional[StructObj] = None self.default: Optional[str] = None self.constant: Optional[str] = None self.padding: Optional[str] = None self.endian: Endian = Endian.NATIVE self.multiplier: int = 0 self.offset: int = 0 self._exports: Dict[str, Export] = { "Getters": Export.NONE, "Setters": Export.NONE, } def add_private_export(self, derive: str) -> None: if self._exports[derive] == Export.PUBLIC: return self._exports[derive] = Export.PRIVATE def add_public_export(self, derive: str) -> None: self.add_private_export(derive) self._exports[derive] = Export.PUBLIC def export(self, derive: str) -> Export: return self._exports[derive] @property def size(self) -> int: multiplier = self.multiplier if not multiplier: multiplier = 1 if self.type in [Type.U8, Type.STRING, Type.GUID]: return multiplier if self.type == Type.U16: return multiplier * 2 if self.type == Type.U24: return multiplier * 3 if self.type == Type.U32: return multiplier * 4 if self.type == Type.U64: return multiplier * 8 return 0 @property def enabled(self) -> bool: if self.element_id.startswith("_"): return False if self.element_id == "reserved": return False return True @property def endian_glib(self) -> str: if self.endian == Endian.LITTLE: return "G_LITTLE_ENDIAN" if self.endian == Endian.BIG: return "G_BIG_ENDIAN" return "G_BYTE_ORDER" def c_define(self, suffix: str): return self.obj.c_define(suffix.upper() + "_" + self.element_id.upper()) @property def c_getter(self): return self.obj.c_method("get_" + self.element_id) @property def c_setter(self): return self.obj.c_method("set_" + self.element_id) @property def type_glib(self) -> str: if self.enum_obj: return self.enum_obj.c_type if self.type == Type.U8: return "guint8" if self.type == Type.U16: return "guint16" if self.type == Type.U24: return "guint32" if self.type == Type.U32: return "guint32" if self.type == Type.U64: return "guint64" if self.type == Type.STRING: return "gchar" if self.type == Type.GUID: return "fwupd_guid_t" return "void" @property def type_mem(self) -> str: if self.type == Type.U16: return "uint16" if self.type == Type.U24: return "uint24" if self.type == Type.U32: return "uint32" if self.type == Type.U64: return "uint64" return "" def _parse_default(self, val: str) -> str: if self.enum_obj: enum_item = self.enum_obj.item(val) if not enum_item: msg: str = [item.name for item in self.enum_obj.items] raise ValueError(f"enum default unknown, got {val} expected: {msg}") return enum_item.c_define if self.type == Type.STRING: if val.startswith('"') and val.endswith('"'): return val[1:-1] raise ValueError(f"string default {val} needs double quotes") if self.type == Type.GUID or (self.type == Type.U8 and self.multiplier): if not val.startswith("0x"): raise ValueError(f"0x prefix for hex number expected, got: {val}") if len(val) != (self.size * 2) + 2: raise ValueError(f"data has to be {self.size} bytes exactly") val_hex = "" for idx in range(2, len(val), 2): val_hex += f"\\x{val[idx:idx+2]}" return val_hex if self.type in [ Type.U8, Type.U16, Type.U24, Type.U32, Type.U64, ]: if val.startswith("0x") or val.startswith("0b"): val = val.replace("_", "") return val.replace("$struct_offset", str(self.offset)) raise ValueError(f"do not know how to parse value for type: {self.type}") def parse_default(self, val: str) -> None: if ( self.type == Type.U8 and self.multiplier and val.startswith("0x") and len(val) == 4 ): self.padding = val return self.default = self._parse_default(val) def parse_constant(self, val: str) -> None: self.default = self._parse_default(val) self.constant = self.default def parse_type( self, val: str, enum_objs: Dict[str, EnumObj], struct_objs: Dict[str, StructObj] ) -> None: # is array if val.startswith("[") and val.endswith("]"): typestr, multiplier = val[1:-1].split(";", maxsplit=1) self.multiplier = int(multiplier) else: typestr = val # nested struct if typestr in struct_objs: self.struct_obj = struct_objs[typestr] self.multiplier = self.struct_obj.size self.type = Type.U8 return # find the type if typestr in enum_objs: self.enum_obj = enum_objs[typestr] typestr_maybe: Optional[str] = enum_objs[typestr].repr_type if not typestr_maybe: raise ValueError(f"no repr for: {typestr}") typestr = typestr_maybe try: if typestr.endswith("be"): self.endian = Endian.BIG typestr = typestr[:-2] elif typestr.endswith("le"): self.endian = Endian.LITTLE typestr = typestr[:-2] self.type = Type(typestr) if self.type == Type.GUID: self.multiplier = 16 except ValueError as e: raise ValueError(f"invalid type: {typestr}") from e def __str__(self) -> str: tmp = f"{self.element_id}: " if self.multiplier: tmp += str(self.multiplier) tmp += self.type.value if self.endian != Endian.NATIVE: tmp += self.endian.value if self.default: tmp += f" = {self.default}" elif self.constant: tmp += f" == {self.constant}" elif self.padding: tmp += f" = {self.padding}" return tmp class Generator: def __init__(self, basename) -> None: self.basename: str = basename self.struct_objs: Dict[str, StructObj] = {} self.enum_objs: Dict[str, EnumObj] = {} self._env = Environment( loader=FileSystemLoader(os.path.dirname(__file__)), autoescape=select_autoescape(), keep_trailing_newline=True, ) def _process_enums(self, enum_obj: EnumObj) -> Tuple[str, str]: # render subst = { "Type": Type, "Export": Export, "obj": enum_obj, } template_h = self._env.get_template(os.path.basename("fu-rustgen-enum.h.in")) template_c = self._env.get_template(os.path.basename("fu-rustgen-enum.c.in")) return template_c.render(subst), template_h.render(subst) def _process_structs(self, struct_obj: StructObj) -> Tuple[str, str]: # render subst = { "Type": Type, "Export": Export, "obj": struct_obj, } template_h = self._env.get_template(os.path.basename("fu-rustgen-struct.h.in")) template_c = self._env.get_template(os.path.basename("fu-rustgen-struct.c.in")) return template_c.render(subst), template_h.render(subst) def process_input(self, contents: str) -> Tuple[str, str]: name = None repr_type: Optional[str] = None derives: List[str] = [] offset: int = 0 struct_cur: Optional[StructObj] = None enum_cur: Optional[EnumObj] = None for line in contents.split("\n"): # replace all tabs with spaces line = line.replace("\t", " ") # remove comments and indent line = line.split("//")[0].strip() if not line: continue # start of structure if line.startswith("struct ") and line.endswith("{"): name = line[6:-1].strip() if name in self.struct_objs: raise ValueError(f"struct {name} already defined") struct_cur = StructObj(name) self.struct_objs[name] = struct_cur continue if line.startswith("enum ") and line.endswith("{"): name = line[4:-1].strip() if name in self.enum_objs: raise ValueError(f"enum {name} already defined") enum_cur = EnumObj(name) enum_cur.repr_type = repr_type self.enum_objs[name] = enum_cur continue # the enum type if line.startswith("#[repr(") and line.endswith(")]"): repr_type = line[7:-2] continue # what should we build if line.startswith("#[derive("): for derive in line[9:-2].replace(" ", "").split(","): derives.append(derive) continue # not in object if not struct_cur and not enum_cur: continue # end of structure if line.startswith("}"): if struct_cur: for derive in derives: struct_cur.add_public_export(derive) for item in struct_cur.items: if item.default == "$struct_size": item.default = str(offset) if item.constant == "$struct_size": item.constant = str(offset) if enum_cur: for derive in derives: enum_cur.add_public_export(derive) struct_cur = None enum_cur = None repr_type = None derives.clear() offset = 0 continue # check for trailing comma if not line.endswith(","): raise ValueError(f"invalid struct line: {line} -- needs trailing comma") line = line[:-1] # split enumeration into sections if enum_cur: enum_item = EnumItem(enum_cur) parts = line.replace(" ", "").split("=", maxsplit=2) enum_item.name = parts[0] if len(parts) > 1: enum_item.parse_default(parts[1]) enum_cur.items.append(enum_item) # split structure into sections if struct_cur: # parse "signature: u32be == 0x12345678" parts = line.replace(" ", "").split(":", maxsplit=2) if len(parts) == 1: raise ValueError(f"invalid struct line: {line}") # parse one element item = StructItem(struct_cur) item.offset = offset item.element_id = parts[0] type_parts = parts[1].split("=", maxsplit=3) item.parse_type( type_parts[0], enum_objs=self.enum_objs, struct_objs=self.struct_objs, ) if len(type_parts) == 3: item.parse_constant(type_parts[2]) elif len(type_parts) == 2: item.parse_default(type_parts[1]) offset += item.size struct_cur.items.append(item) # process the templates here subst = { "basename": self.basename, } template_h = self._env.get_template(os.path.basename("fu-rustgen.h.in")) template_c = self._env.get_template(os.path.basename("fu-rustgen.c.in")) dst_h = template_h.render(subst) dst_c = template_c.render(subst) for enum_obj in self.enum_objs.values(): str_c, str_h = self._process_enums(enum_obj) dst_c += str_c dst_h += str_h for struct_obj in self.struct_objs.values(): str_c, str_h = self._process_structs(struct_obj) dst_c += str_c dst_h += str_h # success return dst_c, dst_h if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("src", action="store", type=str, help="source") parser.add_argument("dst_c", action="store", type=str, help="destination .c") parser.add_argument("dst_h", action="store", type=str, help="destination .h") args = parser.parse_args() g = Generator(basename=os.path.basename(args.dst_h)) with open(args.src, "rb") as f: try: dst_c, dst_h = g.process_input(f.read().decode()) except ValueError as e: sys.exit(f"cannot process {args.src}: {str(e)}") with open(args.dst_c, "wb") as f: # type: ignore f.write(dst_c.encode()) with open(args.dst_h, "wb") as f: # type: ignore f.write(dst_h.encode()) fwupd-1.9.16/libfwupdplugin/tests/000077500000000000000000000000001460375044200171375ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/.gitattributes000066400000000000000000000000331460375044200220260ustar00rootroot00000000000000hid-descriptor*.bin binary fwupd-1.9.16/libfwupdplugin/tests/America/000077500000000000000000000000001460375044200205005ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/America/New_York000066400000000000000000000000001460375044200221460ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/archive.builder.xml000066400000000000000000000004601460375044200227270ustar00rootroot00000000000000 ustar gzip one.txt aGVsbG8gd29ybGQ= two.txt aGVsbG8gd29ybGQ= fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/000077500000000000000000000000001460375044200212265ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/000077500000000000000000000000001460375044200235145ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/000077500000000000000000000000001460375044200265365ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/000077500000000000000000000000001460375044200307245ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Absolute/000077500000000000000000000000001460375044200325025ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200413732../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Absolutedefault_value000077700000000000000000000000001460375044200413352../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Absolutedell_modifier000077700000000000000000000000001460375044200424272../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Absolutedell_value_modifier000077700000000000000000000000001460375044200436232../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Absolutedisplay_name000066400000000000000000000000111460375044200350030ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AbsoluteAbsolute display_name_language_code000077700000000000000000000000001460375044200464432../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Absolutepossible_values000066400000000000000000000000461460375044200355450ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AbsoluteEnabled;Disabled;PermanentlyDisabled; type000077700000000000000000000000001460375044200356012../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AbsoluteAdminSetupLockout/000077500000000000000000000000001460375044200342575ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200450752../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdminSetupLockoutdefault_value000077700000000000000000000000001460375044200450372../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdminSetupLockoutdell_modifier000077700000000000000000000000001460375044200442632../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdminSetupLockoutdell_value_modifier000077700000000000000000000000001460375044200454572../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdminSetupLockoutdisplay_name000066400000000000000000000000331460375044200366430ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdminSetupLockoutEnable Admin Setup Lockout display_name_language_code000077700000000000000000000000001460375044200502772../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdminSetupLockoutpossible_values000077700000000000000000000000001460375044200440712../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdminSetupLockouttype000077700000000000000000000000001460375044200374352../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdminSetupLockoutAdvBatteryChargeCfg/000077500000000000000000000000001460375044200344445ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200452622../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvBatteryChargeCfgdefault_value000077700000000000000000000000001460375044200452242../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvBatteryChargeCfgdell_modifier000077700000000000000000000000001460375044200444502../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvBatteryChargeCfgdell_value_modifier000077700000000000000000000000001460375044200456442../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvBatteryChargeCfgdisplay_name000066400000000000000000000000551460375044200370340ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvBatteryChargeCfgEnable Advanced Battery Charge Configuration display_name_language_code000077700000000000000000000000001460375044200504642../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvBatteryChargeCfgpossible_values000077700000000000000000000000001460375044200442562../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvBatteryChargeCfgtype000077700000000000000000000000001460375044200376222../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvBatteryChargeCfgAdvancedMode/000077500000000000000000000000001460375044200331575ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200421272../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvancedModedefault_value000077700000000000000000000000001460375044200420712../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvancedModedell_modifier000077700000000000000000000000001460375044200431632../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvancedModedell_value_modifier000077700000000000000000000000001460375044200443572../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvancedModedisplay_name000066400000000000000000000000311460375044200355410ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvancedModeBIOS Setup Advanced Mode display_name_language_code000077700000000000000000000000001460375044200471772../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvancedModepossible_values000077700000000000000000000000001460375044200427712../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvancedModetype000077700000000000000000000000001460375044200363352../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AdvancedModeAllowBiosDowngrade/000077500000000000000000000000001460375044200343735ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200433432../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AllowBiosDowngradedefault_value000077700000000000000000000000001460375044200433052../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AllowBiosDowngradedell_modifier000077700000000000000000000000001460375044200443772../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AllowBiosDowngradedell_value_modifier000077700000000000000000000000001460375044200455732../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AllowBiosDowngradedisplay_name000066400000000000000000000000251460375044200367600ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AllowBiosDowngradeAllow BIOS Downgrade display_name_language_code000077700000000000000000000000001460375044200504132../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AllowBiosDowngradepossible_values000077700000000000000000000000001460375044200442052../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AllowBiosDowngradetype000077700000000000000000000000001460375044200375512../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AllowBiosDowngradefwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Asset/000077500000000000000000000000001460375044200320035ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200420102../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Assetdefault_value000077700000000000000000000000001460375044200372072./display_nameustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Assetdell_modifier000077700000000000000000000000001460375044200417302../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Assetdisplay_name000066400000000000000000000000121460375044200343050ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AssetAsset Tag display_name_language_code000077700000000000000000000000001460375044200457442../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Assetmax_length000066400000000000000000000000031460375044200337660ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Asset64 min_length000066400000000000000000000000021460375044200337630ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Asset1 fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Asset/type000066400000000000000000000000071460375044200327040ustar00rootroot00000000000000string AutoOSRecoveryThreshold/000077500000000000000000000000001460375044200354135ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000066400000000000000000000000021460375044200402040ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOSRecoveryThreshold2 default_value000077700000000000000000000000001460375044200431072./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOSRecoveryThresholddell_modifier000077700000000000000000000000001460375044200454172../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOSRecoveryThresholddell_value_modifier000077700000000000000000000000001460375044200466132../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOSRecoveryThresholddisplay_name000066400000000000000000000000401460375044200377750ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOSRecoveryThresholdDell Auto OS Recovery Threshold display_name_language_code000077700000000000000000000000001460375044200514332../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOSRecoveryThresholdpossible_values000066400000000000000000000000131460375044200405270ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOSRecoveryThresholdOFF;1;2;3; type000077700000000000000000000000001460375044200405712../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOSRecoveryThresholdfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOn/000077500000000000000000000000001460375044200321315ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200426702../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOndefault_value000077700000000000000000000000001460375044200426322../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOndell_modifier000077700000000000000000000000001460375044200420562../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOndell_value_modifier000077700000000000000000000000001460375044200432522../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOndisplay_name000066400000000000000000000000151460375044200344360ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnAuto On Time display_name_language_code000077700000000000000000000000001460375044200460722../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnpossible_values000066400000000000000000000000471460375044200351750ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnDisabled;Everyday;Weekdays;SelectDays; fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOn/type000077700000000000000000000000001460375044200353072../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnFri/000077500000000000000000000000001460375044200325725ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200433312../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnFridefault_value000077700000000000000000000000001460375044200432732../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnFridell_modifier000077700000000000000000000000001460375044200420462../AutoOnTue/dell_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnFridell_value_modifier000077700000000000000000000000001460375044200437132../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnFridisplay_name000066400000000000000000000000071460375044200351000ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnFriFriday display_name_language_code000077700000000000000000000000001460375044200465332../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnFripossible_values000077700000000000000000000000001460375044200423252../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnFritype000077700000000000000000000000001460375044200356712../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnFrifwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnHr/000077500000000000000000000000001460375044200324235ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200371772./min_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnHrdefault_value000077700000000000000000000000001460375044200371412./min_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnHrdell_modifier000077700000000000000000000000001460375044200423502../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnHrdisplay_name000066400000000000000000000000131460375044200347260ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnHrHours (HH) display_name_language_code000077700000000000000000000000001460375044200463642../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnHrmax_value000066400000000000000000000000031460375044200342410ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnHr23 min_value000066400000000000000000000000021460375044200342360ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnHr0 scalar_increment000077700000000000000000000000001460375044200411342../Asset/min_lengthustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnHrtype000066400000000000000000000000101460375044200332370ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnHrinteger fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMn/000077500000000000000000000000001460375044200324245ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200407552../AutoOnHr/min_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMndefault_value000077700000000000000000000000001460375044200407172../AutoOnHr/min_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMndell_modifier000077700000000000000000000000001460375044200423512../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMndisplay_name000066400000000000000000000000151460375044200347310ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMnMinutes (MM) display_name_language_code000077700000000000000000000000001460375044200463652../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMnmax_value000066400000000000000000000000031460375044200342420ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMn59 min_value000077700000000000000000000000001460375044200400562../AutoOnHr/min_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMnscalar_increment000077700000000000000000000000001460375044200411352../Asset/min_lengthustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMntype000077700000000000000000000000001460375044200360622../AutoOnHr/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMnfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMon/000077500000000000000000000000001460375044200326035ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200433422../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMondefault_value000077700000000000000000000000001460375044200433042../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMondell_modifier000077700000000000000000000000001460375044200420572../AutoOnTue/dell_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMondell_value_modifier000077700000000000000000000000001460375044200437242../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMondisplay_name000066400000000000000000000000071460375044200351110ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMonMonday display_name_language_code000077700000000000000000000000001460375044200465442../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMonpossible_values000077700000000000000000000000001460375044200423362../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMontype000077700000000000000000000000001460375044200357022../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnMonfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSat/000077500000000000000000000000001460375044200326015ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200433402../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSatdefault_value000077700000000000000000000000001460375044200433022../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSatdell_modifier000077700000000000000000000000001460375044200420552../AutoOnTue/dell_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSatdell_value_modifier000077700000000000000000000000001460375044200437222../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSatdisplay_name000066400000000000000000000000111460375044200351020ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSatSaturday display_name_language_code000077700000000000000000000000001460375044200465422../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSatpossible_values000077700000000000000000000000001460375044200423342../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSattype000077700000000000000000000000001460375044200357002../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSatfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSun/000077500000000000000000000000001460375044200326175ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200433562../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSundefault_value000077700000000000000000000000001460375044200433202../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSundell_modifier000077700000000000000000000000001460375044200420732../AutoOnTue/dell_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSundell_value_modifier000077700000000000000000000000001460375044200437402../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSundisplay_name000066400000000000000000000000071460375044200351250ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSunSunday display_name_language_code000077700000000000000000000000001460375044200465602../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSunpossible_values000077700000000000000000000000001460375044200423522../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSuntype000077700000000000000000000000001460375044200357162../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnSunfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnThur/000077500000000000000000000000001460375044200327745ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200435332../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnThurdefault_value000077700000000000000000000000001460375044200434752../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnThurdell_modifier000077700000000000000000000000001460375044200422502../AutoOnTue/dell_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnThurdell_value_modifier000077700000000000000000000000001460375044200441152../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnThurdisplay_name000066400000000000000000000000111460375044200352750ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnThurThursday display_name_language_code000077700000000000000000000000001460375044200467352../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnThurpossible_values000077700000000000000000000000001460375044200425272../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnThurtype000077700000000000000000000000001460375044200360732../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnThurfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnTue/000077500000000000000000000000001460375044200326075ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200433462../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnTuedefault_value000077700000000000000000000000001460375044200433102../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnTuedell_modifier000066400000000000000000000000421460375044200352450ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnTue[SuppressIfNot:AutoOn=SelectDays] dell_value_modifier000077700000000000000000000000001460375044200437302../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnTuedisplay_name000066400000000000000000000000101460375044200351070ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnTueTuesday display_name_language_code000077700000000000000000000000001460375044200465502../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnTuepossible_values000077700000000000000000000000001460375044200423422../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnTuetype000077700000000000000000000000001460375044200357062../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnTuefwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnWed/000077500000000000000000000000001460375044200325715ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200433302../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnWeddefault_value000077700000000000000000000000001460375044200432722../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnWeddell_modifier000077700000000000000000000000001460375044200420452../AutoOnTue/dell_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnWeddell_value_modifier000077700000000000000000000000001460375044200437122../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnWeddisplay_name000066400000000000000000000000121460375044200350730ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnWedWednesday display_name_language_code000077700000000000000000000000001460375044200465322../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnWedpossible_values000077700000000000000000000000001460375044200423242../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnWedtype000077700000000000000000000000001460375044200356702../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnWedfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BIOSConnect/000077500000000000000000000000001460375044200327725ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200416632../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BIOSConnectdefault_value000077700000000000000000000000001460375044200416252../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BIOSConnectdell_modifier000077700000000000000000000000001460375044200427172../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BIOSConnectdell_value_modifier000077700000000000000000000000001460375044200441132../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BIOSConnectdisplay_name000066400000000000000000000000141460375044200352760ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BIOSConnectBIOSConnect display_name_language_code000077700000000000000000000000001460375044200467332../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BIOSConnectpossible_values000077700000000000000000000000001460375044200425252../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BIOSConnecttype000077700000000000000000000000001460375044200360712../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BIOSConnectBiosLogClear/000077500000000000000000000000001460375044200331525ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200437672../ThermalLogClear/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosLogCleardefault_value000077700000000000000000000000001460375044200437312../ThermalLogClear/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosLogCleardell_modifier000077700000000000000000000000001460375044200431562../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosLogCleardell_value_modifier000077700000000000000000000000001460375044200443522../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosLogCleardisplay_name000066400000000000000000000000251460375044200355370ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosLogClearClear Bios Event Log display_name_language_code000077700000000000000000000000001460375044200471722../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosLogClearpossible_values000077700000000000000000000000001460375044200446312../ThermalLogClear/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosLogCleartype000077700000000000000000000000001460375044200363302../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosLogClearBiosRcvrFrmHdd/000077500000000000000000000000001460375044200334635ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200424332../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosRcvrFrmHdddefault_value000077700000000000000000000000001460375044200423752../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosRcvrFrmHdddell_modifier000077700000000000000000000000001460375044200434672../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosRcvrFrmHdddell_value_modifier000077700000000000000000000000001460375044200446632../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosRcvrFrmHdddisplay_name000066400000000000000000000000361460375044200360520ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosRcvrFrmHddBIOS Recovery from Hard Drive display_name_language_code000077700000000000000000000000001460375044200475032../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosRcvrFrmHddpossible_values000077700000000000000000000000001460375044200432752../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosRcvrFrmHddtype000077700000000000000000000000001460375044200366412../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BiosRcvrFrmHddfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BlockSleep/000077500000000000000000000000001460375044200327475ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200435062../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BlockSleepdefault_value000077700000000000000000000000001460375044200434502../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BlockSleepdell_modifier000077700000000000000000000000001460375044200426742../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BlockSleepdell_value_modifier000077700000000000000000000000001460375044200440702../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BlockSleepdisplay_name000066400000000000000000000000141460375044200352530ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BlockSleepBlock Sleep display_name_language_code000077700000000000000000000000001460375044200467102../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BlockSleeppossible_values000077700000000000000000000000001460375044200425022../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BlockSleeptype000077700000000000000000000000001460375044200360462../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BlockSleepBluetoothDevice/000077500000000000000000000000001460375044200337325ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200427022../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BluetoothDevicedefault_value000077700000000000000000000000001460375044200426442../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BluetoothDevicedell_modifier000077700000000000000000000000001460375044200437362../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BluetoothDevicedell_value_modifier000077700000000000000000000000001460375044200451322../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BluetoothDevicedisplay_name000066400000000000000000000000121460375044200363130ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BluetoothDeviceBluetooth display_name_language_code000077700000000000000000000000001460375044200477522../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BluetoothDevicepossible_values000077700000000000000000000000001460375044200435442../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BluetoothDevicetype000077700000000000000000000000001460375044200371102../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/BluetoothDevicefwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CStatesCtrl/000077500000000000000000000000001460375044200331175ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200420102../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CStatesCtrldefault_value000077700000000000000000000000001460375044200417522../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CStatesCtrldell_modifier000077700000000000000000000000001460375044200430442../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CStatesCtrldell_value_modifier000077700000000000000000000000001460375044200442402../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CStatesCtrldisplay_name000066400000000000000000000000271460375044200354270ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CStatesCtrlEnable C-State Control display_name_language_code000077700000000000000000000000001460375044200470602../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CStatesCtrlpossible_values000077700000000000000000000000001460375044200426522../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CStatesCtrltype000077700000000000000000000000001460375044200362162../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CStatesCtrlfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Camera/000077500000000000000000000000001460375044200321145ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200410052../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Cameradefault_value000077700000000000000000000000001460375044200407472../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Cameradell_modifier000077700000000000000000000000001460375044200420412../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Cameradell_value_modifier000077700000000000000000000000001460375044200432352../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Cameradisplay_name000066400000000000000000000000161460375044200344220ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CameraEnable Camera display_name_language_code000077700000000000000000000000001460375044200460552../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Camerapossible_values000077700000000000000000000000001460375044200416472../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Camerafwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Camera/type000077700000000000000000000000001460375044200352722../SdCard/typeustar00rootroot00000000000000CapsuleFirmwareUpdate/000077500000000000000000000000001460375044200351015ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200440512../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CapsuleFirmwareUpdatedefault_value000077700000000000000000000000001460375044200440132../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CapsuleFirmwareUpdatedell_modifier000077700000000000000000000000001460375044200451052../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CapsuleFirmwareUpdatedell_value_modifier000077700000000000000000000000001460375044200463012../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CapsuleFirmwareUpdatedisplay_name000066400000000000000000000000451460375044200374700ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CapsuleFirmwareUpdateEnable UEFI Capsule Firmware Updates display_name_language_code000077700000000000000000000000001460375044200511212../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CapsuleFirmwareUpdatepossible_values000077700000000000000000000000001460375044200447132../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CapsuleFirmwareUpdatetype000077700000000000000000000000001460375044200402572../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CapsuleFirmwareUpdatefwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CpuCore/000077500000000000000000000000001460375044200322645ustar00rootroot00000000000000current_value000066400000000000000000000000111460375044200347760ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CpuCoreCoresAll default_value000077700000000000000000000000001460375044200377012./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CpuCoredell_modifier000077700000000000000000000000001460375044200422112../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CpuCoredell_value_modifier000077700000000000000000000000001460375044200434052../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CpuCoredisplay_name000066400000000000000000000000151460375044200345710ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CpuCoreActive Cores display_name_language_code000077700000000000000000000000001460375044200462252../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CpuCorepossible_values000066400000000000000000000000201460375044200353170ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CpuCoreCoresAll;1;2;3; fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CpuCore/type000077700000000000000000000000001460375044200354422../SdCard/typeustar00rootroot00000000000000CustomChargeStart/000077500000000000000000000000001460375044200342475ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200411022./min_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStartdefault_value000077700000000000000000000000001460375044200410442./min_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStartdell_modifier000077700000000000000000000000001460375044200442532../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStartdisplay_name000066400000000000000000000000241460375044200366330ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStartCustom Charge Start display_name_language_code000077700000000000000000000000001460375044200502672../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStartmax_value000066400000000000000000000000031460375044200361440ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStart95 min_value000066400000000000000000000000031460375044200361420ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStart50 scalar_increment000077700000000000000000000000001460375044200430372../Asset/min_lengthustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStarttype000077700000000000000000000000001460375044200377642../AutoOnHr/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStartCustomChargeStop/000077500000000000000000000000001460375044200340775ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000066400000000000000000000000031460375044200366710ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStop90 default_value000077700000000000000000000000001460375044200415732./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStopdell_modifier000077700000000000000000000000001460375044200441032../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStopdisplay_name000066400000000000000000000000231460375044200364620ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStopCustom Charge Stop display_name_language_code000077700000000000000000000000001460375044200501172../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStopmax_value000077700000000000000000000000001460375044200452032../PeakShiftBatteryThreshold/max_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStopmin_value000066400000000000000000000000031460375044200357720ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStop55 scalar_increment000077700000000000000000000000001460375044200426672../Asset/min_lengthustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStoptype000077700000000000000000000000001460375044200376142../AutoOnHr/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/CustomChargeStopDellCoreService/000077500000000000000000000000001460375044200336575ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200444752../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DellCoreServicedefault_value000077700000000000000000000000001460375044200444372../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DellCoreServicedell_modifier000077700000000000000000000000001460375044200436632../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DellCoreServicedell_value_modifier000077700000000000000000000000001460375044200450572../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DellCoreServicedisplay_name000066400000000000000000000000201460375044200362370ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DellCoreServiceDellCoreService display_name_language_code000077700000000000000000000000001460375044200476772../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DellCoreServicepossible_values000077700000000000000000000000001460375044200434712../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DellCoreServicetype000077700000000000000000000000001460375044200370352../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DellCoreServiceDockWarningsEnMsg/000077500000000000000000000000001460375044200341705ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200431402../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DockWarningsEnMsgdefault_value000077700000000000000000000000001460375044200431022../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DockWarningsEnMsgdell_modifier000077700000000000000000000000001460375044200441742../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DockWarningsEnMsgdell_value_modifier000077700000000000000000000000001460375044200453702../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DockWarningsEnMsgdisplay_name000066400000000000000000000000351460375044200365560ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DockWarningsEnMsgEnable Dock Warning Messages display_name_language_code000077700000000000000000000000001460375044200502102../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DockWarningsEnMsgpossible_values000077700000000000000000000000001460375044200440022../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DockWarningsEnMsgtype000077700000000000000000000000001460375044200373462../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DockWarningsEnMsgfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DynTunML/000077500000000000000000000000001460375044200323765ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200412672../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DynTunMLdefault_value000077700000000000000000000000001460375044200412312../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DynTunMLdell_modifier000077700000000000000000000000001460375044200423232../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DynTunMLdell_value_modifier000077700000000000000000000000001460375044200435172../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DynTunMLdisplay_name000066400000000000000000000000471460375044200347100ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DynTunMLEnable Dynamic Tuning:Machine Learning display_name_language_code000077700000000000000000000000001460375044200463372../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DynTunMLpossible_values000077700000000000000000000000001460375044200421312../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DynTunMLtype000077700000000000000000000000001460375044200354752../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/DynTunMLfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/EmbSataRaid/000077500000000000000000000000001460375044200330405ustar00rootroot00000000000000current_value000066400000000000000000000000051460375044200355550ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/EmbSataRaidRaid default_value000077700000000000000000000000001460375044200404552./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/EmbSataRaiddell_modifier000077700000000000000000000000001460375044200427652../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/EmbSataRaiddell_value_modifier000077700000000000000000000000001460375044200441612../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/EmbSataRaiddisplay_name000066400000000000000000000000241460375044200353450ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/EmbSataRaidSATA/NVMe Opeartion display_name_language_code000077700000000000000000000000001460375044200470012../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/EmbSataRaidpossible_values000066400000000000000000000000241460375044200360770ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/EmbSataRaidDisabled;Ahci;Raid; type000077700000000000000000000000001460375044200361372../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/EmbSataRaidfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ExtPostTime/000077500000000000000000000000001460375044200331515ustar00rootroot00000000000000current_value000066400000000000000000000000031460375044200356640ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ExtPostTime0s default_value000077700000000000000000000000001460375044200405662./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ExtPostTimedell_modifier000077700000000000000000000000001460375044200430762../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ExtPostTimedell_value_modifier000077700000000000000000000000001460375044200442722../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ExtPostTimedisplay_name000066400000000000000000000000261460375044200354600ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ExtPostTimeExtend BIOS POST Time display_name_language_code000077700000000000000000000000001460375044200471122../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ExtPostTimepossible_values000066400000000000000000000000131460375044200362060ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ExtPostTime0s;5s;10s; type000077700000000000000000000000001460375044200362502../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ExtPostTimefwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FOTA/000077500000000000000000000000001460375044200314555ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200403462../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FOTAdefault_value000077700000000000000000000000001460375044200403102../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FOTAdell_modifier000077700000000000000000000000001460375044200414022../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FOTAdell_value_modifier000077700000000000000000000000001460375044200425762../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FOTAdisplay_name000066400000000000000000000000051460375044200337610ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FOTAFOTA display_name_language_code000077700000000000000000000000001460375044200454162../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FOTApossible_values000077700000000000000000000000001460375044200412102../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FOTAfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FOTA/type000077700000000000000000000000001460375044200346332../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Fastboot/000077500000000000000000000000001460375044200325055ustar00rootroot00000000000000current_value000066400000000000000000000000101460375044200352160ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FastbootMinimal default_value000066400000000000000000000000111460375044200351610ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FastbootThorough dell_modifier000077700000000000000000000000001460375044200424322../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Fastbootdell_value_modifier000077700000000000000000000000001460375044200436262../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Fastbootdisplay_name000066400000000000000000000000111460375044200350060ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FastbootFastboot display_name_language_code000077700000000000000000000000001460375044200464462../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Fastbootpossible_values000066400000000000000000000000271460375044200355470ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FastbootMinimal;Thorough;Auto; type000077700000000000000000000000001460375044200356042../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FastbootFingerprintReader/000077500000000000000000000000001460375044200342575ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200432272../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderdefault_value000077700000000000000000000000001460375044200431712../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderdell_modifier000077700000000000000000000000001460375044200442632../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderdell_value_modifier000077700000000000000000000000001460375044200454572../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderdisplay_name000066400000000000000000000000411460375044200366420ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderEnable Fingerprint Reader Device display_name_language_code000077700000000000000000000000001460375044200502772../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderpossible_values000077700000000000000000000000001460375044200440712../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReadertype000077700000000000000000000000001460375044200374352../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderFingerprintReaderSingleSignOn/000077500000000000000000000000001460375044200365375ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200455072../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderSingleSignOndefault_value000077700000000000000000000000001460375044200454512../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderSingleSignOndell_modifier000077700000000000000000000000001460375044200465432../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderSingleSignOndell_value_modifier000077700000000000000000000000001460375044200477372../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderSingleSignOndisplay_name000066400000000000000000000000511460375044200411230ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderSingleSignOnEnable Fingerprint Reader Single Sign On display_name_language_code000077700000000000000000000000001460375044200525572../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderSingleSignOnpossible_values000077700000000000000000000000001460375044200463512../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderSingleSignOntype000077700000000000000000000000001460375044200417152../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FingerprintReaderSingleSignOnfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLock/000077500000000000000000000000001460375044200321005ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200407712../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockdefault_value000077700000000000000000000000001460375044200407332../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockdell_modifier000077700000000000000000000000001460375044200420252../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockdell_value_modifier000077700000000000000000000000001460375044200432212../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockdisplay_name000066400000000000000000000000201460375044200344010ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockFn Lock Options display_name_language_code000077700000000000000000000000001460375044200460412../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockpossible_values000077700000000000000000000000001460375044200416332../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLock/type000077700000000000000000000000001460375044200352562../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockMode/000077500000000000000000000000001460375044200327055ustar00rootroot00000000000000current_value000066400000000000000000000000201460375044200354170ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockModeDisableStandard default_value000066400000000000000000000000201460375044200353610ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockModeEnableSecondary dell_modifier000077700000000000000000000000001460375044200426322../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockModedell_value_modifier000077700000000000000000000000001460375044200440262../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockModedisplay_name000066400000000000000000000000121460375044200352070ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockModeLock Mode display_name_language_code000077700000000000000000000000001460375044200466462../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockModepossible_values000066400000000000000000000000411460375044200357430ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockModeDisableStandard;EnableSecondary; type000077700000000000000000000000001460375044200360042../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FnLockModeFullScreenLogo/000077500000000000000000000000001460375044200335305ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200443462../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FullScreenLogodefault_value000077700000000000000000000000001460375044200443102../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FullScreenLogodell_modifier000077700000000000000000000000001460375044200435342../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FullScreenLogodell_value_modifier000077700000000000000000000000001460375044200447302../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FullScreenLogodisplay_name000066400000000000000000000000211460375044200361110ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FullScreenLogoFull Screen Logo display_name_language_code000077700000000000000000000000001460375044200475502../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FullScreenLogopossible_values000077700000000000000000000000001460375044200433422../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FullScreenLogotype000077700000000000000000000000001460375044200367062../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/FullScreenLogoIntegratedAudio/000077500000000000000000000000001460375044200337155ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200426652../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntegratedAudiodefault_value000077700000000000000000000000001460375044200426272../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntegratedAudiodell_modifier000077700000000000000000000000001460375044200437212../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntegratedAudiodell_value_modifier000077700000000000000000000000001460375044200451152../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntegratedAudiodisplay_name000066400000000000000000000000151460375044200363010ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntegratedAudioEnable Audio display_name_language_code000077700000000000000000000000001460375044200477352../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntegratedAudiopossible_values000077700000000000000000000000001460375044200435272../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntegratedAudiotype000077700000000000000000000000001460375044200370732../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntegratedAudiofwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntelGna/000077500000000000000000000000001460375044200324255ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200431642../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntelGnadefault_value000077700000000000000000000000001460375044200412602../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntelGnadell_modifier000077700000000000000000000000001460375044200423522../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntelGnadell_value_modifier000077700000000000000000000000001460375044200435462../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntelGnadisplay_name000066400000000000000000000000271460375044200347350ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntelGnaIntel@ GNA Accelerator display_name_language_code000077700000000000000000000000001460375044200463662../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntelGnapossible_values000077700000000000000000000000001460375044200421602../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntelGnatype000077700000000000000000000000001460375044200355242../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/IntelGnaInternalSpeaker/000077500000000000000000000000001460375044200337345ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200427042../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/InternalSpeakerdefault_value000077700000000000000000000000001460375044200426462../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/InternalSpeakerdell_modifier000077700000000000000000000000001460375044200437402../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/InternalSpeakerdell_value_modifier000077700000000000000000000000001460375044200451342../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/InternalSpeakerdisplay_name000066400000000000000000000000301460375044200363150ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/InternalSpeakerEnable Internal Speaker display_name_language_code000077700000000000000000000000001460375044200477542../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/InternalSpeakerpossible_values000077700000000000000000000000001460375044200435462../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/InternalSpeakertype000077700000000000000000000000001460375044200371122../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/InternalSpeakerKbdBacklightTimeoutAc/000077500000000000000000000000001460375044200347715ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000066400000000000000000000000041460375044200375640ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutAc10s default_value000077700000000000000000000000001460375044200424652./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutAcdell_modifier000077700000000000000000000000001460375044200447752../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutAcdell_value_modifier000077700000000000000000000000001460375044200461712../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutAcdisplay_name000066400000000000000000000000411460375044200373540ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutAcKeyboard Backlight Timeout on AC display_name_language_code000077700000000000000000000000001460375044200510112../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutAcpossible_values000066400000000000000000000000401460375044200401050ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutAc5s;10s;15s;30s;1m;5m;15m;Never; type000077700000000000000000000000001460375044200401472../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutAcKbdBacklightTimeoutBatt/000077500000000000000000000000001460375044200353405ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200472742../KbdBacklightTimeoutAc/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutBattdefault_value000077700000000000000000000000001460375044200472362../KbdBacklightTimeoutAc/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutBattdell_modifier000077700000000000000000000000001460375044200453442../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutBattdell_value_modifier000077700000000000000000000000001460375044200465402../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutBattdisplay_name000066400000000000000000000000461460375044200377300ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutBattKeyboard Backlight Timeout on Battery display_name_language_code000077700000000000000000000000001460375044200513602../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutBattpossible_values000077700000000000000000000000001460375044200501362../KbdBacklightTimeoutAc/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutBatttype000077700000000000000000000000001460375044200405162../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KbdBacklightTimeoutBattKeyboardIllumination/000077500000000000000000000000001460375044200347725ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200456102../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KeyboardIlluminationdefault_value000066400000000000000000000000071460375044200375320ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KeyboardIlluminationBright dell_modifier000077700000000000000000000000001460375044200447762../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KeyboardIlluminationdell_value_modifier000077700000000000000000000000001460375044200461722../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KeyboardIlluminationdisplay_name000066400000000000000000000000261460375044200373600ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KeyboardIlluminationKeyboard Illumination display_name_language_code000077700000000000000000000000001460375044200510122../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KeyboardIlluminationpossible_values000066400000000000000000000000251460375044200401110ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KeyboardIlluminationDisabled;Dim;Bright; type000077700000000000000000000000001460375044200401502../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/KeyboardIlluminationfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LidSwitch/000077500000000000000000000000001460375044200326165ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200415072../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LidSwitchdefault_value000077700000000000000000000000001460375044200414512../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LidSwitchdell_modifier000077700000000000000000000000001460375044200425432../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LidSwitchdell_value_modifier000077700000000000000000000000001460375044200437372../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LidSwitchdisplay_name000066400000000000000000000000221460375044200351210ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LidSwitchEnable Lid Switch display_name_language_code000077700000000000000000000000001460375044200465572../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LidSwitchpossible_values000077700000000000000000000000001460375044200423512../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LidSwitchtype000077700000000000000000000000001460375044200357152../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LidSwitchfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LogicProc/000077500000000000000000000000001460375044200326055ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200414762../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LogicProcdefault_value000077700000000000000000000000001460375044200414402../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LogicProcdell_modifier000077700000000000000000000000001460375044200425322../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LogicProcdell_value_modifier000077700000000000000000000000001460375044200437262../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LogicProcdisplay_name000066400000000000000000000000501460375044200351110ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LogicProcEnable Intel Hyper-Threading Technology display_name_language_code000077700000000000000000000000001460375044200465462../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LogicProcpossible_values000077700000000000000000000000001460375044200423402../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LogicProctype000077700000000000000000000000001460375044200357042../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/LogicProcfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/M2PcieSsd0/000077500000000000000000000000001460375044200325355ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200414262../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/M2PcieSsd0default_value000077700000000000000000000000001460375044200413702../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/M2PcieSsd0dell_modifier000077700000000000000000000000001460375044200424622../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/M2PcieSsd0dell_value_modifier000077700000000000000000000000001460375044200436562../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/M2PcieSsd0display_name000066400000000000000000000000151460375044200350420ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/M2PcieSsd0M.2 PCIe SSD display_name_language_code000077700000000000000000000000001460375044200464762../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/M2PcieSsd0possible_values000077700000000000000000000000001460375044200422702../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/M2PcieSsd0type000077700000000000000000000000001460375044200356342../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/M2PcieSsd0MacAddrPassThru/000077500000000000000000000000001460375044200336325ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000066400000000000000000000000151460375044200364270ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MacAddrPassThruSystemUnique default_value000077700000000000000000000000001460375044200413262./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MacAddrPassThrudell_modifier000077700000000000000000000000001460375044200436362../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MacAddrPassThrudell_value_modifier000077700000000000000000000000001460375044200450322../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MacAddrPassThrudisplay_name000066400000000000000000000000311460375044200362140ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MacAddrPassThruMAC Address Pass-Through display_name_language_code000077700000000000000000000000001460375044200476522../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MacAddrPassThrupossible_values000066400000000000000000000000271460375044200367530ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MacAddrPassThruSystemUnique;Disabled; type000077700000000000000000000000001460375044200370102../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MacAddrPassThruMasterPasswordLockout/000077500000000000000000000000001460375044200351645ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200460022../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MasterPasswordLockoutdefault_value000077700000000000000000000000001460375044200457442../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MasterPasswordLockoutdell_modifier000077700000000000000000000000001460375044200451702../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MasterPasswordLockoutdell_value_modifier000077700000000000000000000000001460375044200463642../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MasterPasswordLockoutdisplay_name000066400000000000000000000000301460375044200375450ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MasterPasswordLockoutMaster Password Lockout display_name_language_code000077700000000000000000000000001460375044200512042../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MasterPasswordLockoutpossible_values000077700000000000000000000000001460375044200447762../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MasterPasswordLockouttype000077700000000000000000000000001460375044200403422../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MasterPasswordLockoutfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Microphone/000077500000000000000000000000001460375044200330275ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200417202../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Microphonedefault_value000077700000000000000000000000001460375044200416622../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Microphonedell_modifier000077700000000000000000000000001460375044200427542../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Microphonedell_value_modifier000077700000000000000000000000001460375044200441502../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Microphonedisplay_name000066400000000000000000000000221460375044200353320ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/MicrophoneEnable Microphone display_name_language_code000077700000000000000000000000001460375044200467702../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Microphonepossible_values000077700000000000000000000000001460375044200425622../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Microphonetype000077700000000000000000000000001460375044200361262../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Microphonefwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/NumLock/000077500000000000000000000000001460375044200322745ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200411652../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/NumLockdefault_value000077700000000000000000000000001460375044200411272../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/NumLockdell_modifier000077700000000000000000000000001460375044200422212../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/NumLockdell_value_modifier000077700000000000000000000000001460375044200434152../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/NumLockdisplay_name000066400000000000000000000000171460375044200346030ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/NumLockEnable Numlock display_name_language_code000077700000000000000000000000001460375044200462352../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/NumLockpossible_values000077700000000000000000000000001460375044200420272../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/NumLockfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/NumLock/type000077700000000000000000000000001460375044200354522../SdCard/typeustar00rootroot00000000000000PasswordBypass/000077500000000000000000000000001460375044200336315ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200444472../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordBypassdefault_value000077700000000000000000000000001460375044200444112../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordBypassdell_modifier000077700000000000000000000000001460375044200436352../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordBypassdell_value_modifier000077700000000000000000000000001460375044200450312../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordBypassdisplay_name000066400000000000000000000000201460375044200362110ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordBypassPassword Bypass display_name_language_code000077700000000000000000000000001460375044200476512../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordBypasspossible_values000066400000000000000000000000271460375044200367520ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordBypassDisabled;RebootBypass; type000077700000000000000000000000001460375044200370072../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordBypassPasswordLock/000077500000000000000000000000001460375044200332605ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200422302../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordLockdefault_value000077700000000000000000000000001460375044200421722../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordLockdell_modifier000077700000000000000000000000001460375044200432642../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordLockdell_value_modifier000077700000000000000000000000001460375044200444602../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordLockdisplay_name000066400000000000000000000000421460375044200356440ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordLockEnable Non-Admin Password Changes display_name_language_code000077700000000000000000000000001460375044200473002../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordLockpossible_values000077700000000000000000000000001460375044200430722../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordLocktype000077700000000000000000000000001460375044200364362../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PasswordLockPeakShiftBatteryThreshold/000077500000000000000000000000001460375044200357335ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200443432../AutoOnHr/min_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftBatteryThresholddefault_value000077700000000000000000000000001460375044200425302./min_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftBatteryThresholddell_modifier000066400000000000000000000000431460375044200404510ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftBatteryThreshold[SuppressIf:PeakShiftCfg=Disabled] display_name000066400000000000000000000000401460375044200403150ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftBatteryThresholdBattery Threshold [15% to 100%] display_name_language_code000077700000000000000000000000001460375044200517532../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftBatteryThresholdmax_value000066400000000000000000000000041460375044200376310ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftBatteryThreshold100 min_value000066400000000000000000000000031460375044200376260ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftBatteryThreshold15 scalar_increment000077700000000000000000000000001460375044200445232../Asset/min_lengthustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftBatteryThresholdtype000077700000000000000000000000001460375044200414502../AutoOnHr/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftBatteryThresholdPeakShiftCfg/000077500000000000000000000000001460375044200331435ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200437612../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftCfgdefault_value000077700000000000000000000000001460375044200437232../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftCfgdell_modifier000077700000000000000000000000001460375044200431472../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftCfgdell_value_modifier000077700000000000000000000000001460375044200443432../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftCfgdisplay_name000066400000000000000000000000221460375044200355250ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftCfgEnable Peak Shift display_name_language_code000077700000000000000000000000001460375044200471632../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftCfgpossible_values000077700000000000000000000000001460375044200427552../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftCfgtype000077700000000000000000000000001460375044200363212../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PeakShiftCfgfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PntDevice/000077500000000000000000000000001460375044200326055ustar00rootroot00000000000000current_value000066400000000000000000000000111460375044200353170ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PntDeviceTouchpad default_value000077700000000000000000000000001460375044200402222./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PntDevicedell_modifier000077700000000000000000000000001460375044200425322../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PntDevicedell_value_modifier000077700000000000000000000000001460375044200437262../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PntDevicedisplay_name000066400000000000000000000000171460375044200351140ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PntDeviceMouse/Touchpad display_name_language_code000077700000000000000000000000001460375044200465462../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PntDevicepossible_values000066400000000000000000000000371460375044200356500ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PntDeviceSerialMouse;Ps2Mouse;Touchpad; type000077700000000000000000000000001460375044200357042../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PntDevicePowerLogClear/000077500000000000000000000000001460375044200333525ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200441672../ThermalLogClear/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerLogCleardefault_value000077700000000000000000000000001460375044200441312../ThermalLogClear/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerLogCleardell_modifier000077700000000000000000000000001460375044200433562../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerLogCleardell_value_modifier000077700000000000000000000000001460375044200445522../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerLogCleardisplay_name000066400000000000000000000000261460375044200357400ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerLogClearClear POWER Event Log display_name_language_code000077700000000000000000000000001460375044200473722../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerLogClearpossible_values000077700000000000000000000000001460375044200450312../ThermalLogClear/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerLogCleartype000077700000000000000000000000001460375044200365302../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerLogClearPowerOnLidOpen/000077500000000000000000000000001460375044200335115ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200443272../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerOnLidOpendefault_value000077700000000000000000000000001460375044200424232../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerOnLidOpendell_modifier000077700000000000000000000000001460375044200435152../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerOnLidOpendell_value_modifier000077700000000000000000000000001460375044200447112../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerOnLidOpendisplay_name000066400000000000000000000000221460375044200360730ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerOnLidOpenPower On Lid Open display_name_language_code000077700000000000000000000000001460375044200475312../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerOnLidOpenpossible_values000077700000000000000000000000001460375044200433232../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerOnLidOpentype000077700000000000000000000000001460375044200366672../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerOnLidOpenfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerWarn/000077500000000000000000000000001460375044200326505ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200415412../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerWarndefault_value000077700000000000000000000000001460375044200415032../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerWarndell_modifier000077700000000000000000000000001460375044200425752../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerWarndell_value_modifier000077700000000000000000000000001460375044200437712../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerWarndisplay_name000066400000000000000000000000301460375044200351520ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerWarnEnable Adapter Warnings display_name_language_code000077700000000000000000000000001460375044200466112../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerWarnpossible_values000077700000000000000000000000001460375044200424032../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerWarntype000077700000000000000000000000001460375044200357472../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PowerWarnPrimaryBattChargeCfg/000077500000000000000000000000001460375044200346355ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000066400000000000000000000000111460375044200374260ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PrimaryBattChargeCfgAdaptive default_value000077700000000000000000000000001460375044200423312./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PrimaryBattChargeCfgdell_modifier000077700000000000000000000000001460375044200446412../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PrimaryBattChargeCfgdell_value_modifier000077700000000000000000000000001460375044200460352../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PrimaryBattChargeCfgdisplay_name000066400000000000000000000000261460375044200372230ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PrimaryBattChargeCfgBattery Configuration display_name_language_code000077700000000000000000000000001460375044200506552../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PrimaryBattChargeCfgpossible_values000066400000000000000000000000541460375044200377560ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PrimaryBattChargeCfgAdaptive;Standard;Express;PrimAcUse;Custom; type000077700000000000000000000000001460375044200400132../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PrimaryBattChargeCfgfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdDigitRqd/000077500000000000000000000000001460375044200331065ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200436452../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdDigitRqddefault_value000077700000000000000000000000001460375044200436072../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdDigitRqddell_modifier000077700000000000000000000000001460375044200430332../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdDigitRqddell_value_modifier000077700000000000000000000000001460375044200442272../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdDigitRqddisplay_name000066400000000000000000000000061460375044200354130ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdDigitRqdDigit display_name_language_code000077700000000000000000000000001460375044200470472../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdDigitRqdpossible_values000077700000000000000000000000001460375044200426412../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdDigitRqdtype000077700000000000000000000000001460375044200362052../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdDigitRqdPwdLowerCaseRqd/000077500000000000000000000000001460375044200336535ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000066400000000000000000000000111460375044200364440ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdLowerCaseRqdDisabled default_value000077700000000000000000000000001460375044200413472./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdLowerCaseRqddell_modifier000077700000000000000000000000001460375044200436572../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdLowerCaseRqddell_value_modifier000077700000000000000000000000001460375044200450532../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdLowerCaseRqddisplay_name000066400000000000000000000000221460375044200362350ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdLowerCaseRqdLower Case Letter display_name_language_code000077700000000000000000000000001460375044200476732../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdLowerCaseRqdpossible_values000077700000000000000000000000001460375044200434652../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdLowerCaseRqdtype000077700000000000000000000000001460375044200370312../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdLowerCaseRqdfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdMinLen/000077500000000000000000000000001460375044200325615ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200373352./min_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdMinLendefault_value000077700000000000000000000000001460375044200372772./min_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdMinLendell_modifier000077700000000000000000000000001460375044200425062../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdMinLendisplay_name000066400000000000000000000000231460375044200350650ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdMinLenMinimum Characters display_name_language_code000077700000000000000000000000001460375044200465222../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdMinLenmax_value000066400000000000000000000000031460375044200343770ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdMinLen32 min_value000066400000000000000000000000021460375044200343740ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdMinLen4 scalar_increment000077700000000000000000000000001460375044200412722../Asset/min_lengthustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdMinLentype000077700000000000000000000000001460375044200362172../AutoOnHr/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdMinLenPwdSpecialCharRqd/000077500000000000000000000000001460375044200341455ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200447632../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdSpecialCharRqddefault_value000077700000000000000000000000001460375044200447252../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdSpecialCharRqddell_modifier000077700000000000000000000000001460375044200441512../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdSpecialCharRqddell_value_modifier000077700000000000000000000000001460375044200453452../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdSpecialCharRqddisplay_name000066400000000000000000000000221460375044200365270ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdSpecialCharRqdSpecial Character display_name_language_code000077700000000000000000000000001460375044200501652../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdSpecialCharRqdpossible_values000077700000000000000000000000001460375044200437572../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdSpecialCharRqdtype000077700000000000000000000000001460375044200373232../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdSpecialCharRqdPwdUpperCaseRqd/000077500000000000000000000000001460375044200336565ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200444742../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdUpperCaseRqddefault_value000077700000000000000000000000001460375044200444362../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdUpperCaseRqddell_modifier000077700000000000000000000000001460375044200436622../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdUpperCaseRqddell_value_modifier000077700000000000000000000000001460375044200450562../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdUpperCaseRqddisplay_name000066400000000000000000000000221460375044200362400ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdUpperCaseRqdUpper Case Letter display_name_language_code000077700000000000000000000000001460375044200476762../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdUpperCaseRqdpossible_values000077700000000000000000000000001460375044200434702../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdUpperCaseRqdtype000077700000000000000000000000001460375044200370342../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/PwdUpperCaseRqdRemoteWipeInternalDrives/000077500000000000000000000000001460375044200355775ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000066400000000000000000000000101460375044200403670ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/RemoteWipeInternalDrivesUnarmed default_value000077700000000000000000000000001460375044200432732./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/RemoteWipeInternalDrivesdell_modifier000066400000000000000000000000251460375044200403150ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/RemoteWipeInternalDrives[ProgHideLocal:TRUE] dell_value_modifier000077700000000000000000000000001460375044200467772../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/RemoteWipeInternalDrivesdisplay_name000077700000000000000000000000001460375044200454522../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/RemoteWipeInternalDrivesdisplay_name_language_code000077700000000000000000000000001460375044200516172../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/RemoteWipeInternalDrivespossible_values000066400000000000000000000000361460375044200407200ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/RemoteWipeInternalDrivesUnarmed;Armed;Complete;Error; type000077700000000000000000000000001460375044200407552../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/RemoteWipeInternalDrivesfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SHA256/000077500000000000000000000000001460375044200315745ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200404652../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SHA256default_value000077700000000000000000000000001460375044200404272../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SHA256dell_modifier000077700000000000000000000000001460375044200415212../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SHA256dell_value_modifier000077700000000000000000000000001460375044200427152../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SHA256display_name000066400000000000000000000000101460375044200340740ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SHA256SHA-256 display_name_language_code000077700000000000000000000000001460375044200455352../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SHA256possible_values000077700000000000000000000000001460375044200413272../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SHA256fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SHA256/type000077700000000000000000000000001460375044200347522../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCard/000077500000000000000000000000001460375044200320645ustar00rootroot00000000000000current_value000066400000000000000000000000101460375044200345750ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardEnabled default_value000077700000000000000000000000001460375044200375012./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCarddell_modifier000077700000000000000000000000001460375044200405732./dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCarddell_value_modifier000066400000000000000000000000011460375044200357110ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCard display_name000066400000000000000000000000311460375044200343670ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardSecure Digital (SD) Card display_name_language_code000066400000000000000000000000061460375044200372260ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCarden-US possible_values000066400000000000000000000000221460375044200351210ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardDisabled;Enabled; fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCard/type000066400000000000000000000000141460375044200327630ustar00rootroot00000000000000enumeration fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardBoot/000077500000000000000000000000001460375044200327105ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200434472../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardBootdefault_value000077700000000000000000000000001460375044200434112../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardBootdell_modifier000077700000000000000000000000001460375044200426352../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardBootdell_value_modifier000077700000000000000000000000001460375044200440312../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardBootdisplay_name000066400000000000000000000000451460375044200352200ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardBootEnable Secure Digital (SD) Card Boot display_name_language_code000077700000000000000000000000001460375044200466512../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardBootpossible_values000077700000000000000000000000001460375044200424432../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardBoottype000077700000000000000000000000001460375044200360072../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardBootSdCardReadOnly/000077500000000000000000000000001460375044200334435ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200442612../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardReadOnlydefault_value000077700000000000000000000000001460375044200442232../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardReadOnlydell_modifier000077700000000000000000000000001460375044200434472../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardReadOnlydell_value_modifier000077700000000000000000000000001460375044200446432../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardReadOnlydisplay_name000066400000000000000000000000501460375044200360260ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardReadOnlySecure Digital (SD) Card Read-Only Mode display_name_language_code000077700000000000000000000000001460375044200474632../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardReadOnlypossible_values000077700000000000000000000000001460375044200432552../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardReadOnlytype000077700000000000000000000000001460375044200366212../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SdCardReadOnlyfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBoot/000077500000000000000000000000001460375044200327765ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200416672../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootdefault_value000077700000000000000000000000001460375044200434772../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootdell_modifier000077700000000000000000000000001460375044200427232../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootdell_value_modifier000077700000000000000000000000001460375044200441172../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootdisplay_name000066400000000000000000000000231460375044200353020ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootEnable Secure Boot display_name_language_code000077700000000000000000000000001460375044200467372../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootpossible_values000077700000000000000000000000001460375044200425312../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBoottype000077700000000000000000000000001460375044200360752../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootSecureBootMode/000077500000000000000000000000001460375044200335245ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000066400000000000000000000000151460375044200363210ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootModeDeployedMode default_value000077700000000000000000000000001460375044200412202./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootModedell_modifier000077700000000000000000000000001460375044200435302../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootModedell_value_modifier000077700000000000000000000000001460375044200447242../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootModedisplay_name000066400000000000000000000000211460375044200361050ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootModeSecure Boot Mode display_name_language_code000077700000000000000000000000001460375044200475442../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootModepossible_values000066400000000000000000000000301460375044200366370ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootModeDeployedMode;AuditMode; type000077700000000000000000000000001460375044200367022../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SecureBootModeSignOfLifeByDisplay/000077500000000000000000000000001460375044200344535ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200434232../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByDisplaydefault_value000077700000000000000000000000001460375044200433652../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByDisplaydell_modifier000077700000000000000000000000001460375044200444572../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByDisplaydell_value_modifier000077700000000000000000000000001460375044200456532../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByDisplaydisplay_name000066400000000000000000000000231460375044200370360ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByDisplayEarly Logo Display display_name_language_code000077700000000000000000000000001460375044200504732../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByDisplaypossible_values000077700000000000000000000000001460375044200442652../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByDisplaytype000077700000000000000000000000001460375044200376312../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByDisplaySignOfLifeByKbdBacklight/000077500000000000000000000000001460375044200353575ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200443272../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByKbdBacklightdefault_value000077700000000000000000000000001460375044200442712../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByKbdBacklightdell_modifier000077700000000000000000000000001460375044200453632../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByKbdBacklightdell_value_modifier000077700000000000000000000000001460375044200465572../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByKbdBacklightdisplay_name000066400000000000000000000000311460375044200377410ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByKbdBacklightEarly Keyboard Backlight display_name_language_code000077700000000000000000000000001460375044200513772../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByKbdBacklightpossible_values000077700000000000000000000000001460375044200451712../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByKbdBacklighttype000077700000000000000000000000001460375044200405352../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SignOfLifeByKbdBacklightfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmartErrors/000077500000000000000000000000001460375044200332075ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200437462../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmartErrorsdefault_value000077700000000000000000000000001460375044200437102../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmartErrorsdell_modifier000077700000000000000000000000001460375044200431342../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmartErrorsdell_value_modifier000077700000000000000000000000001460375044200443302../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmartErrorsdisplay_name000066400000000000000000000000271460375044200355170ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmartErrorsEnable SMART Reporting display_name_language_code000077700000000000000000000000001460375044200471502../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmartErrorspossible_values000077700000000000000000000000001460375044200427422../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmartErrorstype000077700000000000000000000000001460375044200363062../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmartErrorsSmmSecurityMitigation/000077500000000000000000000000001460375044200351565ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200441262../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmmSecurityMitigationdefault_value000077700000000000000000000000001460375044200457362../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmmSecurityMitigationdell_modifier000077700000000000000000000000001460375044200451622../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmmSecurityMitigationdell_value_modifier000077700000000000000000000000001460375044200463562../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmmSecurityMitigationdisplay_name000066400000000000000000000000301460375044200375370ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmmSecurityMitigationSMM Security Mitigation display_name_language_code000077700000000000000000000000001460375044200511762../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmmSecurityMitigationpossible_values000077700000000000000000000000001460375044200447702../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmmSecurityMitigationtype000077700000000000000000000000001460375044200403342../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SmmSecurityMitigationfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SpeedShift/000077500000000000000000000000001460375044200327625ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200416532../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SpeedShiftdefault_value000077700000000000000000000000001460375044200416152../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SpeedShiftdell_modifier000077700000000000000000000000001460375044200427072../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SpeedShiftdell_value_modifier000077700000000000000000000000001460375044200441032../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SpeedShiftdisplay_name000066400000000000000000000000351460375044200352710ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SpeedShiftIntel Speed Shift Technology display_name_language_code000077700000000000000000000000001460375044200467232../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SpeedShiftpossible_values000077700000000000000000000000001460375044200425152../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SpeedShifttype000077700000000000000000000000001460375044200360612../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SpeedShiftfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Speedstep/000077500000000000000000000000001460375044200326605ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200415512../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Speedstepdefault_value000077700000000000000000000000001460375044200415132../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Speedstepdell_modifier000077700000000000000000000000001460375044200426052../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Speedstepdell_value_modifier000077700000000000000000000000001460375044200440012../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Speedstepdisplay_name000066400000000000000000000000421460375044200351650ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SpeedstepEnable Intel SpeedStep Technology display_name_language_code000077700000000000000000000000001460375044200466212../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Speedsteppossible_values000077700000000000000000000000001460375044200424132../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Speedsteptype000077700000000000000000000000001460375044200357572../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SpeedstepStrongPassword/000077500000000000000000000000001460375044200336445ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200444622../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/StrongPassworddefault_value000077700000000000000000000000001460375044200444242../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/StrongPassworddell_modifier000077700000000000000000000000001460375044200436502../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/StrongPassworddell_value_modifier000077700000000000000000000000001460375044200450442../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/StrongPassworddisplay_name000066400000000000000000000000301460375044200362250ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/StrongPasswordEnable Strong Passwords display_name_language_code000077700000000000000000000000001460375044200476642../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/StrongPasswordpossible_values000077700000000000000000000000001460375044200434562../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/StrongPasswordtype000077700000000000000000000000001460375044200370222../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/StrongPasswordSupportAssistOSRecovery/000077500000000000000000000000001460375044200354715ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200444412../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SupportAssistOSRecoverydefault_value000077700000000000000000000000001460375044200444032../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SupportAssistOSRecoverydell_modifier000077700000000000000000000000001460375044200454752../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SupportAssistOSRecoverydell_value_modifier000077700000000000000000000000001460375044200466712../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SupportAssistOSRecoverydisplay_name000066400000000000000000000000321460375044200400540ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SupportAssistOSRecoverySupportAssist OS Recovery display_name_language_code000077700000000000000000000000001460375044200515112../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SupportAssistOSRecoverypossible_values000077700000000000000000000000001460375044200453032../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SupportAssistOSRecoverytype000077700000000000000000000000001460375044200406472../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SupportAssistOSRecoveryfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SvcTag/000077500000000000000000000000001460375044200321135ustar00rootroot00000000000000current_value000066400000000000000000000000101460375044200346240ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SvcTag8RQ19C3 default_value000077700000000000000000000000001460375044200373172./display_nameustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SvcTagdell_modifier000077700000000000000000000000001460375044200420402../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SvcTagdisplay_name000066400000000000000000000000141460375044200344170ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SvcTagService Tag display_name_language_code000077700000000000000000000000001460375044200460542../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SvcTagmax_length000077700000000000000000000000001460375044200363042./min_lengthustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SvcTagmin_length000066400000000000000000000000021460375044200340730ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SvcTag7 fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/SvcTag/type000077700000000000000000000000001460375044200352102../Asset/typeustar00rootroot00000000000000TelemetryAccessLvl/000077500000000000000000000000001460375044200344175ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000066400000000000000000000000051460375044200372130ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TelemetryAccessLvlFull default_value000077700000000000000000000000001460375044200421132./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TelemetryAccessLvldell_modifier000077700000000000000000000000001460375044200444232../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TelemetryAccessLvldell_value_modifier000077700000000000000000000000001460375044200456172../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TelemetryAccessLvldisplay_name000066400000000000000000000000271460375044200370060ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TelemetryAccessLvlTelemetry Access Level display_name_language_code000077700000000000000000000000001460375044200504372../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TelemetryAccessLvlpossible_values000066400000000000000000000000361460375044200375400ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TelemetryAccessLvlDisabled;Basic;Enhanced;Full; type000077700000000000000000000000001460375044200375752../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TelemetryAccessLvlThermalLogClear/000077500000000000000000000000001460375044200336525ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000066400000000000000000000000051460375044200364460ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalLogClearKeep default_value000077700000000000000000000000001460375044200413462./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalLogCleardell_modifier000077700000000000000000000000001460375044200436562../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalLogCleardell_value_modifier000077700000000000000000000000001460375044200450522../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalLogCleardisplay_name000066400000000000000000000000301460375044200362330ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalLogClearClear Thermal Event Log display_name_language_code000077700000000000000000000000001460375044200476722../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalLogClearpossible_values000066400000000000000000000000141460375044200367670ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalLogClearKeep;Clear; type000077700000000000000000000000001460375044200370302../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalLogClearThermalManagement/000077500000000000000000000000001460375044200342365ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000066400000000000000000000000121460375044200370300ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalManagementOptimized default_value000077700000000000000000000000001460375044200417322./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalManagementdell_modifier000077700000000000000000000000001460375044200442422../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalManagementdell_value_modifier000077700000000000000000000000001460375044200454362../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalManagementdisplay_name000066400000000000000000000000231460375044200366210ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalManagementThermal Management display_name_language_code000077700000000000000000000000001460375044200502562../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalManagementpossible_values000066400000000000000000000000471460375044200373610ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalManagementOptimized;Cool;Quiet;UltraPerformance; type000077700000000000000000000000001460375044200374142../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThermalManagementThunderboltBoot/000077500000000000000000000000001460375044200337635ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200446012../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltBootdefault_value000077700000000000000000000000001460375044200445432../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltBootdell_modifier000077700000000000000000000000001460375044200437672../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltBootdell_value_modifier000077700000000000000000000000001460375044200451632../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltBootdisplay_name000066400000000000000000000000411460375044200363460ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltBootEnable Thunderbolt" Boot Support display_name_language_code000077700000000000000000000000001460375044200500032../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltBootpossible_values000077700000000000000000000000001460375044200435752../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltBoottype000077700000000000000000000000001460375044200371412../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltBootThunderboltPreboot/000077500000000000000000000000001460375044200344725ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200453102../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltPrebootdefault_value000077700000000000000000000000001460375044200452522../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltPrebootdell_modifier000077700000000000000000000000001460375044200444762../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltPrebootdell_value_modifier000077700000000000000000000000001460375044200456722../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltPrebootdisplay_name000066400000000000000000000000731460375044200370620ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltPrebootEnable Thunderbolt" (and PCIe behind TBT) pre-boot modules display_name_language_code000077700000000000000000000000001460375044200505122../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltPrebootpossible_values000077700000000000000000000000001460375044200443042../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltPreboottype000077700000000000000000000000001460375044200376502../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/ThunderboltPrebootfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Touchscreen/000077500000000000000000000000001460375044200332065ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200420772../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Touchscreendefault_value000077700000000000000000000000001460375044200420412../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Touchscreendell_modifier000077700000000000000000000000001460375044200431332../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Touchscreendell_value_modifier000077700000000000000000000000001460375044200443272../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Touchscreendisplay_name000066400000000000000000000000141460375044200355120ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TouchscreenTouchscreen display_name_language_code000077700000000000000000000000001460375044200471472../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Touchscreenpossible_values000077700000000000000000000000001460375044200427412../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Touchscreentype000077700000000000000000000000001460375044200363052../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TouchscreenTpmActivation/000077500000000000000000000000001460375044200334275ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200423772../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmActivationdefault_value000077700000000000000000000000001460375044200423412../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmActivationdell_modifier000066400000000000000000000000421460375044200361440ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmActivation[SuppressIf:TpmSecurity=Disabled] dell_value_modifier000077700000000000000000000000001460375044200446272../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmActivationdisplay_name000066400000000000000000000000121460375044200360100ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmActivationTPM State display_name_language_code000077700000000000000000000000001460375044200474472../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmActivationpossible_values000077700000000000000000000000001460375044200432412../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmActivationtype000077700000000000000000000000001460375044200366052../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmActivationTpmPpiClearOverride/000077500000000000000000000000001460375044200345255ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200453432../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiClearOverridedefault_value000077700000000000000000000000001460375044200453052../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiClearOverridedell_modifier000077700000000000000000000000001460375044200447572../TpmActivation/dell_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiClearOverridedell_value_modifier000077700000000000000000000000001460375044200457252../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiClearOverridedisplay_name000066400000000000000000000000361460375044200371140ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiClearOverridePPI Bypass for Clear Commands display_name_language_code000077700000000000000000000000001460375044200505452../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiClearOverridepossible_values000077700000000000000000000000001460375044200443372../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiClearOverridetype000077700000000000000000000000001460375044200377032../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiClearOverridefwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiDpo/000077500000000000000000000000001460375044200326005ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200433372../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiDpodefault_value000077700000000000000000000000001460375044200433012../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiDpodell_modifier000077700000000000000000000000001460375044200427532../TpmActivation/dell_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiDpodell_value_modifier000077700000000000000000000000001460375044200437212../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiDpodisplay_name000066400000000000000000000000401460375044200351030ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiDpoPPI Bypass for Disable Commands display_name_language_code000077700000000000000000000000001460375044200465412../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiDpopossible_values000077700000000000000000000000001460375044200423332../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiDpotype000077700000000000000000000000001460375044200356772../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiDpofwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiPo/000077500000000000000000000000001460375044200324345ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200413252../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiPodefault_value000077700000000000000000000000001460375044200431352../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiPodell_modifier000077700000000000000000000000001460375044200426072../TpmActivation/dell_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiPodell_value_modifier000077700000000000000000000000001460375044200435552../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiPodisplay_name000066400000000000000000000000371460375044200347450ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiPoPPI Bypass for Enable Commands display_name_language_code000077700000000000000000000000001460375044200463752../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiPopossible_values000077700000000000000000000000001460375044200421672../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiPotype000077700000000000000000000000001460375044200355332../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmPpiPofwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmSecurity/000077500000000000000000000000001460375044200332145ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200421052../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmSecuritydefault_value000077700000000000000000000000001460375044200420472../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmSecuritydell_modifier000077700000000000000000000000001460375044200431412../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmSecuritydell_value_modifier000077700000000000000000000000001460375044200443352../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmSecuritydisplay_name000066400000000000000000000000241460375044200355210ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmSecurityTPM 2.0 Security On display_name_language_code000077700000000000000000000000001460375044200471552../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmSecuritypossible_values000077700000000000000000000000001460375044200427472../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmSecuritytype000077700000000000000000000000001460375044200363132../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TpmSecurityTrustExecution/000077500000000000000000000000001460375044200336525ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200444702../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TrustExecutiondefault_value000077700000000000000000000000001460375044200444322../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TrustExecutiondell_modifier000077700000000000000000000000001460375044200436562../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TrustExecutiondell_value_modifier000066400000000000000000000002061460375044200375650ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TrustExecutionDisabled[ForceIf:TpmSecurity=Disabled][ForceIf:Virtualization=Disabled][ForceIf:VtForDirectIo=Disabled][ForceIfNot:CpuCore=CoresAll]; display_name000066400000000000000000000000601460375044200362360ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TrustExecutionEnable Intel Trusted Execution Technology (TXT) display_name_language_code000077700000000000000000000000001460375044200476722../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TrustExecutionpossible_values000077700000000000000000000000001460375044200434642../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TrustExecutiontype000077700000000000000000000000001460375044200370302../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TrustExecutionfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TurboMode/000077500000000000000000000000001460375044200326245ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200415152../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TurboModedefault_value000077700000000000000000000000001460375044200414572../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TurboModedell_modifier000077700000000000000000000000001460375044200425512../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TurboModedell_value_modifier000077700000000000000000000000001460375044200437452../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TurboModedisplay_name000066400000000000000000000000441460375044200351330ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TurboModeEnable Intel Turbo Boost Technology display_name_language_code000077700000000000000000000000001460375044200465652../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TurboModepossible_values000077700000000000000000000000001460375044200423572../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TurboModetype000077700000000000000000000000001460375044200357232../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/TurboModeUefiBootPathSecurity/000077500000000000000000000000001460375044200347265ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000066400000000000000000000000301460375044200375200ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiBootPathSecurityAlwaysExceptInternalHdd default_value000077700000000000000000000000001460375044200424222./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiBootPathSecuritydell_modifier000077700000000000000000000000001460375044200447322../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiBootPathSecuritydell_value_modifier000077700000000000000000000000001460375044200461262../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiBootPathSecuritydisplay_name000066400000000000000000000000301460375044200373070ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiBootPathSecurityUEFI Boot Path Security display_name_language_code000077700000000000000000000000001460375044200507462../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiBootPathSecuritypossible_values000066400000000000000000000001011460375044200400400ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiBootPathSecurityNever;Always;AlwaysExceptInternalHdd;AlwaysExceptInternalHddPxe; type000077700000000000000000000000001460375044200401042../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiBootPathSecurityfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiNwStack/000077500000000000000000000000001460375044200331075ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200420002../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiNwStackdefault_value000077700000000000000000000000001460375044200417422../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiNwStackdell_modifier000077700000000000000000000000001460375044200430342../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiNwStackdell_value_modifier000077700000000000000000000000001460375044200442302../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiNwStackdisplay_name000066400000000000000000000000321460375044200354130ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiNwStackEnable UEFI Network Stack display_name_language_code000077700000000000000000000000001460375044200470502../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiNwStackpossible_values000077700000000000000000000000001460375044200426422../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiNwStacktype000077700000000000000000000000001460375044200362062../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UefiNwStackfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbEmu/000077500000000000000000000000001460375044200321245ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200410152../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbEmudefault_value000077700000000000000000000000001460375044200407572../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbEmudell_modifier000077700000000000000000000000001460375044200420512../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbEmudell_value_modifier000077700000000000000000000000001460375044200432452../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbEmudisplay_name000066400000000000000000000000301460375044200344260ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbEmuEnable USB Boot Support display_name_language_code000077700000000000000000000000001460375044200460652../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbEmupossible_values000077700000000000000000000000001460375044200416572../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbEmufwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbEmu/type000077700000000000000000000000001460375044200353022../SdCard/typeustar00rootroot00000000000000UsbPortsExternal/000077500000000000000000000000001460375044200341315ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200431012../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbPortsExternaldefault_value000077700000000000000000000000001460375044200430432../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbPortsExternaldell_modifier000077700000000000000000000000001460375044200441352../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbPortsExternaldell_value_modifier000077700000000000000000000000001460375044200453312../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbPortsExternaldisplay_name000066400000000000000000000000321460375044200365140ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbPortsExternalEnable External USB Ports display_name_language_code000077700000000000000000000000001460375044200501512../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbPortsExternalpossible_values000077700000000000000000000000001460375044200437432../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbPortsExternaltype000077700000000000000000000000001460375044200373072../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/UsbPortsExternalVirtualization/000077500000000000000000000000001460375044200336715ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200426412../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Virtualizationdefault_value000077700000000000000000000000001460375044200426032../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Virtualizationdell_modifier000077700000000000000000000000001460375044200436752../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Virtualizationdell_value_modifier000077700000000000000000000000001460375044200450712../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Virtualizationdisplay_name000066400000000000000000000000541460375044200362600ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/VirtualizationEnable Intel Virtualization Technology (VT) display_name_language_code000077700000000000000000000000001460375044200477112../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Virtualizationpossible_values000077700000000000000000000000001460375044200435032../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/Virtualizationtype000077700000000000000000000000001460375044200370472../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/VirtualizationVtForDirectIo/000077500000000000000000000000001460375044200333305ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200423002../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/VtForDirectIodefault_value000077700000000000000000000000001460375044200422422../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/VtForDirectIodell_modifier000077700000000000000000000000001460375044200433342../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/VtForDirectIodell_value_modifier000077700000000000000000000000001460375044200445302../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/VtForDirectIodisplay_name000066400000000000000000000000371460375044200357200ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/VtForDirectIoEnable Intel VT for Direct I/O display_name_language_code000077700000000000000000000000001460375044200473502../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/VtForDirectIopossible_values000077700000000000000000000000001460375044200431422../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/VtForDirectIotype000077700000000000000000000000001460375044200365062../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/VtForDirectIofwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnAc/000077500000000000000000000000001460375044200323545ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200431132../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnAcdefault_value000077700000000000000000000000001460375044200430552../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnAcdell_modifier000077700000000000000000000000001460375044200423012../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnAcdell_value_modifier000077700000000000000000000000001460375044200434752../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnAcdisplay_name000066400000000000000000000000131460375044200346570ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnAcWake on AC display_name_language_code000077700000000000000000000000001460375044200463152../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnAcpossible_values000077700000000000000000000000001460375044200421072../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnActype000077700000000000000000000000001460375044200354532../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnAcfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnDock/000077500000000000000000000000001460375044200327115ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200434502../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnDockdefault_value000077700000000000000000000000001460375044200415442../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnDockdell_modifier000077700000000000000000000000001460375044200426362../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnDockdell_value_modifier000077700000000000000000000000001460375044200440322../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnDockdisplay_name000066400000000000000000000000301460375044200352130ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnDockWake on Dell USB-C Dock display_name_language_code000077700000000000000000000000001460375044200466522../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnDockpossible_values000077700000000000000000000000001460375044200424442../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnDocktype000077700000000000000000000000001460375044200360102../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnDockfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnLan/000077500000000000000000000000001460375044200325435ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200433022../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnLandefault_value000077700000000000000000000000001460375044200432442../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnLandell_modifier000077700000000000000000000000001460375044200424702../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnLandell_value_modifier000077700000000000000000000000001460375044200436642../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnLandisplay_name000066400000000000000000000000141460375044200350470ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnLanWake on LAN display_name_language_code000077700000000000000000000000001460375044200465042../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnLanpossible_values000066400000000000000000000000411460375044200356010ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnLanDisabled;LanOnly;LanWithPxeBoot; type000077700000000000000000000000001460375044200356422../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WakeOnLanWarningsAndErr/000077500000000000000000000000001460375044200335315ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000066400000000000000000000000151460375044200363260ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WarningsAndErrPromptWrnErr default_value000077700000000000000000000000001460375044200412252./current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WarningsAndErrdell_modifier000077700000000000000000000000001460375044200435352../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WarningsAndErrdell_value_modifier000077700000000000000000000000001460375044200447312../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WarningsAndErrdisplay_name000066400000000000000000000000241460375044200361150ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WarningsAndErrWarnings and Errors display_name_language_code000077700000000000000000000000001460375044200475512../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WarningsAndErrpossible_values000066400000000000000000000000411460375044200366460ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WarningsAndErrPromptWrnErr;ContWrn;ContWrnErr; type000077700000000000000000000000001460375044200367072../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WarningsAndErrfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WirelessLan/000077500000000000000000000000001460375044200331545ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200420452../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WirelessLandefault_value000077700000000000000000000000001460375044200420072../SdCard/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WirelessLandell_modifier000077700000000000000000000000001460375044200431012../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WirelessLandell_value_modifier000077700000000000000000000000001460375044200442752../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WirelessLandisplay_name000066400000000000000000000000051460375044200354600ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WirelessLanWLAN display_name_language_code000077700000000000000000000000001460375044200471152../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WirelessLanpossible_values000077700000000000000000000000001460375044200427072../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WirelessLantype000077700000000000000000000000001460375044200362532../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WirelessLanWlanAutoSense/000077500000000000000000000000001460375044200333755ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributescurrent_value000077700000000000000000000000001460375044200442132../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WlanAutoSensedefault_value000077700000000000000000000000001460375044200441552../PwdLowerCaseRqd/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WlanAutoSensedell_modifier000077700000000000000000000000001460375044200434012../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WlanAutoSensedell_value_modifier000077700000000000000000000000001460375044200445752../SdCard/dell_value_modifierustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WlanAutoSensedisplay_name000066400000000000000000000000231460375044200357600ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WlanAutoSenseControl WLAN radio display_name_language_code000077700000000000000000000000001460375044200474152../SdCard/display_name_language_codeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WlanAutoSensepossible_values000077700000000000000000000000001460375044200432072../SdCard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WlanAutoSensetype000077700000000000000000000000001460375044200365532../SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/WlanAutoSensepending_reboot000077700000000000000000000000001460375044200371622AutoOnHr/min_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributesfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/dell-wmi-sysman/attributes/reset_bios000066400000000000000000000000521460375044200330020ustar00rootroot00000000000000builtinsafe lastknowngood factory custom fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/dell-xps13-9310/strings.txt000066400000000000000000000154111460375044200257500ustar00rootroot00000000000000#TRANSLATORS: Description of BIOS setting Service Tag #TRANSLATORS: Description of BIOS setting Keyboard Illumination #TRANSLATORS: Description of BIOS setting Intel Speed Shift Technology #TRANSLATORS: Description of BIOS setting BIOS Recovery from Hard Drive #TRANSLATORS: Description of BIOS setting Enable Thunderbolt (and PCIe behind TBT) pre-boot modules #TRANSLATORS: Description of BIOS setting BIOSConnect #TRANSLATORS: Description of BIOS setting Control WLAN radio #TRANSLATORS: Description of BIOS setting FOTA #TRANSLATORS: Description of BIOS setting TPM State #TRANSLATORS: Description of BIOS setting Battery Threshold [15% to 100%] #TRANSLATORS: Description of BIOS setting Special Character #TRANSLATORS: Description of BIOS setting Clear Thermal Event Log #TRANSLATORS: Description of BIOS setting Enable Numlock #TRANSLATORS: Description of BIOS setting Sunday #TRANSLATORS: Description of BIOS setting Enable Advanced Battery Charge Configuration #TRANSLATORS: Description of BIOS setting Secure Boot Mode #TRANSLATORS: Description of BIOS setting Enable External USB Ports #TRANSLATORS: Description of BIOS setting Master Password Lockout #TRANSLATORS: Description of BIOS setting Enable Non-Admin Password Changes #TRANSLATORS: Description of BIOS setting M.2 PCIe SSD #TRANSLATORS: Description of BIOS setting Battery Configuration #TRANSLATORS: Description of BIOS setting Enable Intel Hyper-Threading Technology #TRANSLATORS: Description of BIOS setting Lower Case Letter #TRANSLATORS: Description of BIOS setting Enable Intel Virtualization Technology (VT) #TRANSLATORS: Description of BIOS setting Thermal Management #TRANSLATORS: Description of BIOS setting DellCoreService #TRANSLATORS: Description of BIOS setting Keyboard Backlight Timeout on AC #TRANSLATORS: Description of BIOS setting Mouse/Touchpad #TRANSLATORS: Description of BIOS setting Enable Microphone #TRANSLATORS: Description of BIOS setting Enable UEFI Network Stack #TRANSLATORS: Description of BIOS setting SMM Security Mitigation #TRANSLATORS: Description of BIOS setting Enable USB Boot Support #TRANSLATORS: Description of BIOS setting Allow BIOS Downgrade #TRANSLATORS: Description of BIOS setting Lock Mode #TRANSLATORS: Description of BIOS setting Clear Bios Event Log #TRANSLATORS: Description of BIOS setting Wake on Dell USB-C Dock #TRANSLATORS: Description of BIOS setting Enable Secure Digital (SD) Card Boot #TRANSLATORS: Description of BIOS setting Enable Adapter Warnings #TRANSLATORS: Description of BIOS setting Saturday #TRANSLATORS: Description of BIOS setting Custom Charge Start #TRANSLATORS: Description of BIOS setting Enable Admin Setup Lockout #TRANSLATORS: Description of BIOS setting Wake on LAN #TRANSLATORS: Description of BIOS setting Fastboot #TRANSLATORS: Description of BIOS setting Enable SMART Reporting #TRANSLATORS: Description of BIOS setting Early Keyboard Backlight #TRANSLATORS: Description of BIOS setting Intel@ GNA Accelerator #TRANSLATORS: Description of BIOS setting Power On Lid Open #TRANSLATORS: Description of BIOS setting Touchscreen #TRANSLATORS: Description of BIOS setting Auto On Time #TRANSLATORS: Description of BIOS setting Asset Tag #TRANSLATORS: Description of BIOS setting Block Sleep #TRANSLATORS: Description of BIOS setting Thursday #TRANSLATORS: Description of BIOS setting MAC Address Pass-Through #TRANSLATORS: Description of BIOS setting Enable Intel SpeedStep Technology #TRANSLATORS: Description of BIOS setting Enable Intel Turbo Boost Technology #TRANSLATORS: Description of BIOS setting Enable Peak Shift #TRANSLATORS: Description of BIOS setting Wake on AC #TRANSLATORS: Description of BIOS setting Bluetooth #TRANSLATORS: Description of BIOS setting PPI Bypass for Clear Commands #TRANSLATORS: Description of BIOS setting Absolute #TRANSLATORS: Description of BIOS setting Clear POWER Event Log #TRANSLATORS: Description of BIOS setting SATA/NVMe Opeartion #TRANSLATORS: Description of BIOS setting Active Cores #TRANSLATORS: Description of BIOS setting UEFI Boot Path Security #TRANSLATORS: Description of BIOS setting Dell Auto OS Recovery Threshold #TRANSLATORS: Description of BIOS setting SHA-256 #TRANSLATORS: Description of BIOS setting PPI Bypass for Enable Commands #TRANSLATORS: Description of BIOS setting Friday #TRANSLATORS: Description of BIOS setting Enable Internal Speaker #TRANSLATORS: Description of BIOS setting Minimum Characters #TRANSLATORS: Description of BIOS setting Enable Intel VT for Direct I/O #TRANSLATORS: Description of BIOS setting Enable C-State Control #TRANSLATORS: Description of BIOS setting TPM 2.0 Security On #TRANSLATORS: Description of BIOS setting Enable Lid Switch #TRANSLATORS: Description of BIOS setting Enable Audio #TRANSLATORS: Description of BIOS setting Enable Thunderbolt Boot Support #TRANSLATORS: Description of BIOS setting Enable Fingerprint Reader Single Sign On #TRANSLATORS: Description of BIOS setting PPI Bypass for Disable Commands #TRANSLATORS: Description of BIOS setting Keyboard Backlight Timeout on Battery #TRANSLATORS: Description of BIOS setting Telemetry Access Level #TRANSLATORS: Description of BIOS setting Tuesday #TRANSLATORS: Description of BIOS setting Warnings and Errors #TRANSLATORS: Description of BIOS setting BIOS Setup Advanced Mode #TRANSLATORS: Description of BIOS setting Digit #TRANSLATORS: Description of BIOS setting Full Screen Logo #TRANSLATORS: Description of BIOS setting Upper Case Letter #TRANSLATORS: Description of BIOS setting Fn Lock Options #TRANSLATORS: Description of BIOS setting Enable Fingerprint Reader Device #TRANSLATORS: Description of BIOS setting SupportAssist OS Recovery #TRANSLATORS: Description of BIOS setting Enable UEFI Capsule Firmware Updates #TRANSLATORS: Description of BIOS setting Minutes (MM) #TRANSLATORS: Description of BIOS setting Hours (HH) #TRANSLATORS: Description of BIOS setting Enable Dock Warning Messages #TRANSLATORS: Description of BIOS setting Enable Strong Passwords #TRANSLATORS: Description of BIOS setting Enable Secure Boot #TRANSLATORS: Description of BIOS setting Extend BIOS POST Time #TRANSLATORS: Description of BIOS setting Password Bypass #TRANSLATORS: Description of BIOS setting Wednesday #TRANSLATORS: Description of BIOS setting WLAN #TRANSLATORS: Description of BIOS setting Secure Digital (SD) Card Read-Only Mode #TRANSLATORS: Description of BIOS setting Monday #TRANSLATORS: Description of BIOS setting Enable Camera #TRANSLATORS: Description of BIOS setting Secure Digital (SD) Card #TRANSLATORS: Description of BIOS setting Enable Dynamic Tuning:Machine Learning #TRANSLATORS: Description of BIOS setting Early Logo Display #TRANSLATORS: Description of BIOS setting Enable Intel Trusted Execution Technology (TXT) #TRANSLATORS: Description of BIOS setting Custom Charge Stop fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p14s-gen1/000077500000000000000000000000001460375044200241455ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p14s-gen1/thinklmi/000077500000000000000000000000001460375044200257645ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p14s-gen1/thinklmi/attributes/000077500000000000000000000000001460375044200301525ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p14s-gen1/thinklmi/attributes/SecureBoot/000077500000000000000000000000001460375044200322245ustar00rootroot00000000000000current_value000066400000000000000000000000071460375044200347430ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p14s-gen1/thinklmi/attributes/SecureBootEnable display_name000066400000000000000000000000131460375044200345270ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p14s-gen1/thinklmi/attributes/SecureBootSecureBoot possible_values000066400000000000000000000000171460375044200352650ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p14s-gen1/thinklmi/attributes/SecureBootDisable,Enable fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p14s-gen1/thinklmi/attributes/SleepState/000077500000000000000000000000001460375044200322235ustar00rootroot00000000000000current_value000066400000000000000000000000131460375044200347370ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p14s-gen1/thinklmi/attributes/SleepStateWindows 10 display_name000066400000000000000000000000131460375044200345260ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p14s-gen1/thinklmi/attributes/SleepStateSleepState possible_values000066400000000000000000000000211460375044200352570ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p14s-gen1/thinklmi/attributes/SleepStateLinux,Windows 10 fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p14s-gen1/thinklmi/attributes/pending_reboot000077700000000000000000000000001460375044200465532../../../dell-xps13-9310/dell-wmi-sysman/attributes/Asset/min_lengthustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/000077500000000000000000000000001460375044200235215ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/000077500000000000000000000000001460375044200253405ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/000077500000000000000000000000001460375044200275265ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/AMDMemoryGuard/000077500000000000000000000000001460375044200323035ustar00rootroot00000000000000current_value000066400000000000000000000000101460375044200350140ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/AMDMemoryGuardDisable display_name000077700000000000000000000000001460375044200510242../../../../lenovo-p620/thinklmi/attributes/AMDMemoryGuard/display_nameustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/AMDMemoryGuardpossible_values000066400000000000000000000000171460375044200353440ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/AMDMemoryGuardDisable;Enable fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/AMDMemoryGuard/type000077700000000000000000000000001460375044200510052../../../../dell-xps13-9310/dell-wmi-sysman/attributes/KeyboardIllumination/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/AlarmDate/000077500000000000000000000000001460375044200313605ustar00rootroot00000000000000current_value000077700000000000000000000000001460375044200514172../../../../lenovo-p620/thinklmi/attributes/AlarmDate(MM\DD\YYYY)/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/AlarmDatedisplay_name000066400000000000000000000000121460375044200336620ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/AlarmDateAlarmDate fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/AlarmDate/type000077700000000000000000000000001460375044200450142../../../../dell-xps13-9310/dell-wmi-sysman/attributes/Asset/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/StartupSequence/000077500000000000000000000000001460375044200326615ustar00rootroot00000000000000current_value000066400000000000000000000000101460375044200353720ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/StartupSequencePrimary display_name000077700000000000000000000000001460375044200517602../../../../lenovo-p620/thinklmi/attributes/StartupSequence/display_nameustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/StartupSequencepossible_values000066400000000000000000000000221460375044200357160ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/StartupSequencePrimary;Automatic type000077700000000000000000000000001460375044200513042../../../../dell-xps13-9310/dell-wmi-sysman/attributes/KeyboardIllumination/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/StartupSequenceWindowsUEFIFirmwareUpdate/000077500000000000000000000000001460375044200344125ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributescurrent_value000077700000000000000000000000001460375044200540612../../../../lenovo-p14s-gen1/thinklmi/attributes/SecureBoot/current_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/WindowsUEFIFirmwareUpdatedisplay_name000077700000000000000000000000001460375044200554002../../../../lenovo-p620/thinklmi/attributes/WindowsUEFIFirmwareUpdate/display_nameustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/WindowsUEFIFirmwareUpdatepossible_values000077700000000000000000000000001460375044200456412../AMDMemoryGuard/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/WindowsUEFIFirmwareUpdatetype000077700000000000000000000000001460375044200501272../../../../dell-xps13-9310/dell-wmi-sysman/attributes/SdCard/typeustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/WindowsUEFIFirmwareUpdatefwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620-6.3/thinklmi/attributes/pending_reboot000077700000000000000000000000001460375044200442202../../../lenovo-p620/thinklmi/attributes/pending_rebootustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/000077500000000000000000000000001460375044200232155ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/000077500000000000000000000000001460375044200250345ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/000077500000000000000000000000001460375044200272225ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/AMDMemoryGuard/000077500000000000000000000000001460375044200317775ustar00rootroot00000000000000current_value000066400000000000000000000000421460375044200345150ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/AMDMemoryGuardDisable;[Optional:Disable,Enable] display_name000066400000000000000000000000171460375044200343060ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/AMDMemoryGuardAMDMemoryGuard possible_values000077700000000000000000000000001460375044200440432../AlarmDate(MM\DD\YYYY)/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/AMDMemoryGuardfwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/AlarmDate(MM\DD\YYYY)/000077500000000000000000000000001460375044200326735ustar00rootroot00000000000000current_value000066400000000000000000000000361460375044200354140ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/AlarmDate(MM\DD\YYYY)[01/01/2019][Status:ShowOnly] display_name000066400000000000000000000000261460375044200352020ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/AlarmDate(MM\DD\YYYY)AlarmDate(MM\DD\YYYY) possible_values000066400000000000000000000000001460375044200357240ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/AlarmDate(MM\DD\YYYY)fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/StartupSequence/000077500000000000000000000000001460375044200323555ustar00rootroot00000000000000current_value000066400000000000000000000000661460375044200351010ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/StartupSequencePrimary;[Optional:Primary,Automatic][Status:ShowOnly] display_name000066400000000000000000000000201460375044200346560ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/StartupSequenceStartupSequence possible_values000077700000000000000000000000001460375044200444212../AlarmDate(MM\DD\YYYY)/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/StartupSequenceWindowsUEFIFirmwareUpdate/000077500000000000000000000000001460375044200341065ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributescurrent_value000066400000000000000000000000411460375044200367020ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/WindowsUEFIFirmwareUpdateEnable;[Optional:Disable,Enable] display_name000066400000000000000000000000321460375044200364710ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/WindowsUEFIFirmwareUpdateWindowsUEFIFirmwareUpdate possible_values000077700000000000000000000000001460375044200462312../AlarmDate(MM\DD\YYYY)/possible_valuesustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/WindowsUEFIFirmwareUpdatefwupd-1.9.16/libfwupdplugin/tests/bios-attrs/lenovo-p620/thinklmi/attributes/pending_reboot000077700000000000000000000000001460375044200460762../../../dell-xps13-9310/dell-wmi-sysman/attributes/AutoOnHr/min_valueustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/cab-compressed.bin000066400000000000000000000001731460375044200225210ustar00rootroot00000000000000MSCF{,b hello.txt goodbye.txt{CKHW(/I@0fwupd-1.9.16/libfwupdplugin/tests/cab-compressed.builder.xml000066400000000000000000000004351460375044200241770ustar00rootroot00000000000000 true hello.txt aGVsbG8gd29ybGQ= goodbye.txt aGVsbG8gd29ybGQ= fwupd-1.9.16/libfwupdplugin/tests/cab.bin000066400000000000000000000002001460375044200203460ustar00rootroot00000000000000MSCF,b hello.txt goodbye.txtB^jvhello worldhello worldfwupd-1.9.16/libfwupdplugin/tests/cab.builder.xml000066400000000000000000000004361460375044200220360ustar00rootroot00000000000000 false hello.txt aGVsbG8gd29ybGQ= goodbye.txt aGVsbG8gd29ybGQ= fwupd-1.9.16/libfwupdplugin/tests/cfu-offer.builder.xml000066400000000000000000000006731460375044200231700ustar00rootroot00000000000000 0x42 true true 0xAB 0xCD 0x4567 0xFACE 0xF 0x1 0x2 0xDEAD fwupd-1.9.16/libfwupdplugin/tests/cfu-payload.builder.xml000066400000000000000000000003671460375044200235200ustar00rootroot00000000000000 aGVsbG8gd29ybGQ= 0x8001234 aGVsbG8gd29ybGQ= 0x8005678 fwupd-1.9.16/libfwupdplugin/tests/chassis_type000066400000000000000000000000031460375044200215510ustar00rootroot0000000000000016 fwupd-1.9.16/libfwupdplugin/tests/colorhug/000077500000000000000000000000001460375044200207615ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/colorhug/README.md000066400000000000000000000004361460375044200222430ustar00rootroot00000000000000# Generating the p7b file manually certtool --p7-detached-sign --p7-time \ --load-privkey LVFS/pkcs7/secure-lvfs.rhcloud.com.key \ --load-certificate LVFS/pkcs7/secure-lvfs.rhcloud.com_signed.pem \ --infile firmware.bin \ --outfile firmware.bin.p7b fwupd-1.9.16/libfwupdplugin/tests/colorhug/firmware.bin000066400000000000000000000175001460375044200232720ustar00rootroot000000000000001 (~1 p~ 1(00'012!1 00@012!100@012!1@000001,!~ 1*! 02=!4W(4  9GHY(KJj(0G91H!!!3{(3  9GH}(ML(0G")! 0000@001!1# !Gw)0(1T!1(#!1!1)1'!1(#!18!1)1p!1!G")#!1!1)0 >000! ()00>00! ()0 >000! ()0!0000 >000! ))!^ !_ 1#1 W! V! U! T!0 >000! 5))$0!G)#!:N)!:N) 0!Gr)0?0@001x#1!GU)0?0000G1$1!H) )T:(:(:(:(:(:(:( :(:(:(/: )::) :@):(t)!3)3  9GH)ON)UG"!TG" 0!G@0H011#1!I0!G@0H011#!I B:*@98*! A:)! *! B@9:D9:D90[!2 : ,*D9>[D9>8*D9j>[D9>[_E*0[F*`N*`D9>S*`D9j>[A:t*`l*f*D9>i*D9>0`[0[`*0`[`00`[001$1* `0 [*`**D9>*D9>;0`[00`[001$1*;0 `[ D9>0=[\0]^[!*=00{0**! 2:+=+0 g!=+0 g=1!1=!+ 3+1%10 g!0 g=?+1!10 g=V+P+s0 0001$10 g=\+1!1=o+0 0001$1=0!2w+ =+ + go g 9h!0 g= o+!8>+!8>9 0g9g!+1%1+r0 0o0g001$10 g0i+=}+ 0 000 X0000Wc+Vb+Ua+T`,d + , X00=0=0=+Wc,Vb,Ua,T` ,00000000Wc6,Vb6,Ua6,T`b,d Y, U,00=0=0=hgfeU,0000 X00=0=0=(,1T!1 Xd >0>[ZYXhgfe1C"1t srqh_,g^,f],e\,0000hgfe ,! H!.:, ,!,! ,D9>,D9j>qr,r,0q,@9:,:,:,,!H00@0q44W44q4484f404644444444 44)4444444 444444444 4!44444"4444444@4444444@444444 444444)4@444&444u444@44444)4@444444H44u44g44h44s44k44i44 44L44t44d44.4444C44o44l44o44r44H44u44g44A44L44S4444444444?4'44444444444 44!=00u-st00=0qu-0=0{00v-v05>-q5 >0?0???0q0v-==-0q!-0q!0q0v!.vj>v>v>v>0q0v.(0q 0=@00q r00q! nA?vt!$L.s#R.#s0>v  @?sA?t=vu}.uH> s0s0se.0>.0| sH00|>s?t? n.0|s0|s0| s@00|>s?t?0|s! 1p% @9:DA:./!C:/#00 0.:0!0000q/ C!:.:.:// @ 9:@/0!0000q0q! Cq!0!0000q0q! Bq! A:U/: /:&/ :U/:/:4/U/!0 0]q/[\00=0{]a/ 0000 001!1= 0^/^>^>0{0^/0000001!1=(0{ 0{n{B{!/0{0 00{001$ 0{! x/R1u"1/ R1u" R>0=yzPQx1,1,1,1,1,1m,1v,1,1|,1y,1,1,1,  9 -)4d1 3)4q 44444444K)Q)!9$:I)Q)!  905 Z)9q 00 f) f)20b)  905 v) r 9qr9 q9qr69 q9q!t0 0001$=0{=0{! u0 0001$r9 q9qr69 q9qrq00;0v)0uwxs00=)=* n:*j:*0nq=0q m: *1<& !A*0| w@00|>w?x?0|w0 000qW*u v=w=x=05 X*06 _*tsrqL* SRQP 0rq05 }*v05>*s5r05>*t5uw*wv>*wvj>rww>=*01a!1018!101!101!11%1!0W*018!1_01a!1018!1_01a!10!V0W*d1"11= 1*! ! 0jb0b0jb0b0|b!1e$10 0001$11&q8+vj>:+v>tE+0r0w>t?u?sw t@0wt0wtqn+0vj>r+0v>tw@0r0q+0?0q0rvu+0?0s0tvu+006 +tx+sw+#wqxr=uvU00 00=+#0! m:+0| tH00|>t?u?0|t!B G+!A F+G! F!19&H00 j>t?u?0jtJ,1p!1 X!01!1018!!;1!0 X01'10 X@0Y011# Z!1& T:P,d,S:+,:G,:d,w:d,:&,:,d, @ 9:,!0 a!1#'1V'0!0@ 1$1! D>!0@ ! BD>a00001$ A 015>@0r0q,006 ,ty,sx-#rq00=xuv0x>0y=tw,sv-x>uv0U0000=,#0 j000t!2:3- B=.-0/-0t! m:j-H00j>t?u?19&1!=:U-0 jta-0 jt0 jt! !0q0q m:-j 0jqm:!0 n q@00n>q?r?0nq! 0| q@00|>q?r?0|q o9..o6?905>-c5 >dnc0cn 9 :+.0f.n>@?A?cdf@>en>0A1 0c0f-@00n>c?d?1"1"0 oc:1%0q0!B0AW.AqO. W.=:W.0q0>s  @?qA?r=s j qH0q!@..>?<q00=0q0y.>?<q00=0q0.=!..0v !.1#!0| v@00|>v?w?0|v0 jv . !.1#1! 1p%0v 0| v@00|>v?w?0|v! 1p% @:0q!/0!000 B5 0q0r=?!??>0qrr s?>0qrr s0 B/B5 0q0r=?!?>?qr!! C:./:8/:h//fwupd-1.9.16/libfwupdplugin/tests/colorhug/firmware.bin.asc000066400000000000000000000007311460375044200240350ustar00rootroot00000000000000-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJVLPMHAAoJEK2KUo/sRIgeAxAH/jPY7c2qrG4UEsZXgUFxMUQe QEufh3cK9cv8kA7SAzpSHy6M0rNanC2vCqcc/fTJI/yBRfBjPPZYEsQgwpB/8m9y wiTPRuQySwCKsH+ZXNh3j6x8Oaf3DTiO7bJI/M3sOb4fdvb0Csp910g67Nt+HtMw I5EUM0uvMquZTUygp9B6BBJv8xRKtCNgqvPhyoDZKxKrPzaFwvb7BY50Q03LymU6 hQUIkjHIvMcTljNocOZNvTBHvEGB2BiBb60QhAXYyNfDrS58pm2JHfw/pgOuQTzT 3Lw9qmedRXbWR95u/piUmyUsY5ey75lD08U/2aE9RLBZ9xR17u1mAgyLGoIMYEk= =ZdoH -----END PGP SIGNATURE----- fwupd-1.9.16/libfwupdplugin/tests/colorhug/firmware.bin.p7b000066400000000000000000000043251460375044200237620ustar00rootroot00000000000000-----BEGIN PKCS7----- MIIGYAYJKoZIhvcNAQcCoIIGUTCCBk0CAQExDTALBglghkgBZQMEAgEwCwYJKoZI hvcNAQcBoIIESDCCBEQwggKsoAMCAQICDFmdjlgcgXiV33RlVTANBgkqhkiG9w0B AQsFADA6MRAwDgYDVQQDEwdMVkZTIENBMSYwJAYDVQQKEx1MaW51eCBWZW5kb3Ig RmlybXdhcmUgUHJvamVjdDAeFw0xNzA4MDEwMDAwMDBaFw0xOTA4MDEwMDAwMDBa MBkxFzAVBgNVBAMTDlJpY2hhcmQgSHVnaGVzMIIBIjANBgkqhkiG9w0BAQEFAAOC AQ8AMIIBCgKCAQEA5XlsYGdD5isOAEim4tRR9usJa8C4Gs3TUPfe5EfXcIT44dJr plVcXpH2Wau/Pbcvc/2cY/bZmgcRMgw8O/4HoJyCCCKfjCfT6yN9BlLnxAgZVLSw QT2d2JW0m5bY/VgZNwdNZWb+fMnPDx7JMCjtdpUpwQ0R6hwrryRt+6zFyhDayCCL GOsxpmo7Fc9ix/nP5DEcPjU6Bofz0jFFMesod8babaQSWm2b/QN7aTgkrPjslC+p BkTLq7IrndgQzLKI9bXn++LFKE2Srm0nHZ6DapKCgsSE3UOqDGtKTUf86aT2IGnV 5JzTZ/HZk/sGqAyS2wb5m13rJfbzkKnf9c14qwIDAQABo4HqMIHnMAwGA1UdEwEB /wQCMAAwRQYDVR0RBD4wPIYlaHR0cHM6Ly9zZWN1cmUtbHZmcy5yaGNsb3VkLmNv bS9sdmZzL4ETcmljaGFyZEBodWdoc2llLmNvbTATBgNVHSUEDDAKBggrBgEFBQcD AzAPBgNVHQ8BAf8EBQMDB4AAMB0GA1UdDgQWBBSZpooSP4z6IVWsXkoUjpByAh5D fzAfBgNVHSMEGDAWgBSxjerkI6d+CY617jHgat2eNDdlrDAqBgNVHR8EIzAhMB+g HaAbhhlodHRwOi8vd3d3LmZ3dXBkLm9yZy9wa2kvMA0GCSqGSIb3DQEBCwUAA4IB gQBSXRGZB6YR8wTyuOdEelRcJj45Mz5tiuuCfei8ZOTyFzkGjnRno0Dl57tnmUmX ufN2Rb9yzXBGHmSTXT6j2uVg+U1xevPAVCWlIslhwxJcqncfpALxL7TwVL5PpJls /Ao7y/KkS5Bxd8u45A2/wIFkawxn/X0nRmwNh6jF9m3+NSwCv3QxYdgGcfhzD96p 6hG+DuXT97h0lJ3gJJDPbVkWTvuhoNo+iEz8fAfSmlk12HDQ+oQIGRgpFZYHREFr 2/A2HoBfAPFVdmRfYWNrxODrVg3tQEHmtxG7HIHocyRSVzqd31yJKgkwh4I9meUY rCOf0hhMjWmxiviPKJx4SEcNg7Ye8Ib2OtXxcQbZ71ax57dUyVZZXEcfR3KjBuFp vY6QnVF5D3NsyV5q3M1VV8XRh9ELRafruX+Ygx8NLkDPKqFGZh0xKDzr55gJF9q8 rfuHjQ/cd5tokRMI1qlGymbQ/bWgsLBO2MOWeZezITBO1ZVbz6QMJ4YnvHug8nsZ /SkxggHeMIIB2gIBATBKMDoxEDAOBgNVBAMTB0xWRlMgQ0ExJjAkBgNVBAoTHUxp bnV4IFZlbmRvciBGaXJtd2FyZSBQcm9qZWN0AgxZnY5YHIF4ld90ZVUwCwYJYIZI AWUDBAIBoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx DxcNMTcwODIzMTQzNTQ1WjAvBgkqhkiG9w0BCQQxIgQgoZZQTQmHHaT32DuHS1AP jubgYZq3mfB0gUsxbYj5b38wDQYJKoZIhvcNAQEBBQAEggEAWc7kxSri1v+c+N8h S8cerVmAPBm150DjB58F3gxSl91gs/z8d1uWOx88eX0DjOU4C7sQj7E9WiZSPcvb z2KvXqg7MJy+ev9wXPwDqqPtsVZdLKd665JqF7kfSXxpMFzutu/NxW7UUUrKot4v d93NlAEXmjjuQ8V6STtYapxzyuWGXThI/K89kXaMvzmqTYQ4S9+98sXG1PMX69zm z00PT+rL2QGMsZCSUcnE/u38s0q7uCEfBB9uoq5QIECYch65ezX3H2GqVcKPG4M3 6Ttko+W01+2IIPN02ZHPqXqEw8diTiMYS5HVRD7nVs5TTxNNB+rAIBR+mJJBkxin 7MLHjQ== -----END PKCS7----- fwupd-1.9.16/libfwupdplugin/tests/colorhug/firmware.metainfo.xml000066400000000000000000000023361460375044200251240ustar00rootroot00000000000000 com.hughski.ColorHugALS.firmware ColorHugALS Firmware Firmware for the ColorHugALS Ambient Light Sensor

    Updating the firmware on your ColorHugALS device improves performance and adds new features.

    84f40464-9272-4ef7-9399-cd95f12da696 12345678-1234-1234-1234-123456789012 http://www.hughski.com/ CC0-1.0 GPL-2.0+ richard_at_hughsie.com Hughski Limited

    This stable release fixes the following bugs:

    • Fix the return code from GetHardwareVersion
    • Scale the output of TakeReadingRaw by the datasheet values
    fwupd-1.9.16/libfwupdplugin/tests/colorhug/meson.build000066400000000000000000000004131460375044200231210ustar00rootroot00000000000000colorhug_test_firmware = custom_target('colorhug-test-firmware', input: [ 'firmware.bin', 'firmware.bin.asc', 'firmware.metainfo.xml', ], output: 'colorhug-als-3.0.2.cab', command: [ gcab, '--create', '--nopath', '@OUTPUT@', '@INPUT@', ], ) fwupd-1.9.16/libfwupdplugin/tests/coswid.builder.xml000066400000000000000000000013511460375044200225760ustar00rootroot00000000000000 fwupd-efi:fwupdx64 1.4 semver fwupdx64 EFI helpers to install system firmware 1.3-3-g1d0c69f license https://spdx.org/licenses/LGPL-2.0.html foo.bin 123 sha256 dc626520dcd53a22f727af3ee42c770e56c97a64fe3adb063799d8ab032fe551 Richard Hughes hughsie.com maintainer tag-creator fwupd-1.9.16/libfwupdplugin/tests/csv.bin000066400000000000000000000000631460375044200204230ustar00rootroot00000000000000#component_name,component_generation sbat,1 grub,1 fwupd-1.9.16/libfwupdplugin/tests/csv.builder.xml000066400000000000000000000006011460375044200220760ustar00rootroot00000000000000 sbat 1 grub 1 fwupd-1.9.16/libfwupdplugin/tests/dfuse.bin000066400000000000000000000011701460375044200207360ustar00rootroot00000000000000DfuSehTargetone&4 hello worldxV hello worldTargettwo hello worldBxV4UFD`pfwupd-1.9.16/libfwupdplugin/tests/dfuse.builder.xml000066400000000000000000000012151460375044200224130ustar00rootroot00000000000000 0x1234 0x5678 0x8642 0x1 one aGVsbG8gd29ybGQ= 0x8001234 aGVsbG8gd29ybGQ= 0x8005678 two 0x7 aGVsbG8gd29ybGQ= 0x8000000 fwupd-1.9.16/libfwupdplugin/tests/dmi/000077500000000000000000000000001460375044200177105ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/dmi/tables/000077500000000000000000000000001460375044200211625ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/dmi/tables/DMI000066400000000000000000000047331460375044200215250ustar00rootroot00000000000000*Qd44A!Intel(R) Core(TM) i7-4600U CPU @ 2.10GHzIntel(R) CorporationNoneCPU Socket - U3E1NoneNone @@L1-Cache @@L1-Cache@@L2-Cache@@L3-Cache"@@@@ChannelABANK 0ElpidaNoneNoneEDJ8416E6MB-GN-F " @@ChannelB-DIMM0BANK 2Samsung15AF7001NoneM471B1G73QH0-YK0  Intel_ASFIntel_ASF_001   ZS RNLENOVO20ARS19C0CThinkPad T440sPF01VVCALENOVO_MT_20AR_BU_Think_FM_ThinkPad T440sThinkPad T440s   LENOVO20ARS19C0CNot Defined1ZSUK45C1DZNot AvailableNot Available LENOVONot AvailablePF01VVCANo Asset InformationLENOVO_MT_20AR_BU_Think_FM_ThinkPad T440s Not AvailableUSB 1 Not AvailableUSB 2 Not AvailableUSB 3 Not AvailableUSB 4 Not AvailableUSB 5 Not AvailableUSB 6 Not AvailableUSB 7 Not AvailableUSB 8  Not AvailableEthernet Not AvailableExternal Monitor Not AvailableMini DisplayPort Not AvailableDisplayPort/DVI-D Not AvailableDisplayPort/HDMI Not AvailableHeadphone/Microphone Combo Jack1 Not AvailableHeadphone/Microphone Combo Jack2 Media Card Slot~SmartCard Slot  SimCard Slot !IBM Embedded Security hardware " #en-US$ \+;wD FrontSONY45N111103.01LiP%0*fD RearSANYO45N177703.01LION&'()TVT-Enablement*ZZ+STM TPM INFOSystem Reserved,$AMT@-5 C  Z&vPro.KHOIHGIUCCHHIIS/TPBAY I/O @0  LENOVOGJET75WW (2.25 )03/28/2014!1  C2LENOVO ca,tR)D/3LENOVO (uu#/ i"_8 ?4LENOVO 5LENOVO 6LENOVO MS 7LENOVO 8LENOVO 9":6;TP<LENOVO GJHT25WW11/07/2013+=LENOVO /fwupd-1.9.16/libfwupdplugin/tests/dmi/tables/smbios_entry_point000066400000000000000000000000371460375044200250330ustar00rootroot00000000000000_SM__DMI_ Ӽ>'fwupd-1.9.16/libfwupdplugin/tests/dmi/tables64/000077500000000000000000000000001460375044200213345ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/dmi/tables64/DMI000066400000000000000000000133051460375044200216720ustar00rootroot00000000000000ڲ@""##(())**++,,--..@@AABBCCPPUUWW\\]]eeffmmnn}}ڲ@  ++,,--..558899DDEEFFGGJJKKLLMMRRSSuuvv{{||ڲ@--..22335566JJKKLLddeeffgghhiillmmnnڲ@  %%&&))**++,,8899::;;AAڲ@BBCCFFGGHHIIJJMMNNOOPPWWXX[[\\]]^^__``aabbffggiijjkkllmmnnttuuvvwwxxyyڲ@11223366778899::;;@@ڲ@AABBCCDDEEFFGGPPQQRRaabb{{||}}~~J@J@K@K@L@L@ Yڲ@ ""0022@@BBPPRR?cDell Inc.99.01.2108/21/2017DELLR0QO2G2XPSDell Inc.XPS 13 9365077A2R0Q2G2 Dell Inc.0DVT6MA00/2R0Q2G2/CN1296374E0065/Dell Inc.2R0Q2G2Convertible0dl A5CPU 1Intel(R) CorporationIntel(R) Core(TM) i7-7Y75 CPU @ 1.30GHzTo Be Filled By O.E.M.To Be Filled By O.E.M.To Be Filled By O.E.M. L1 Cache L2 Cache L3 Cache  JKBTP1 - KeyboardNone J1A2BVideo J3A2HDMI JUSB1USB1 JUSB2USB2 JTypeCUSB3 JSD1Cardreader JHP1Audio Jack JeDP1-eDPNone JNGFF1 - WLAN/BT/Wigig CONNNone JNGFF2 - HDDNone JSPK1 - SpeakerNone JAPS1 - Automatic PowerNone JDEG1 - Debug PORTNone JRTC1 - RTCNone   PCI-Express 0  PCI-Express 4  PCI-Express 5  PCI-Express 8  "Intel HD Graphics"  Dell System1[077A]3[1.0]12[www.dell.com]14[1]15[0]  en-USIntel(R) Silicon View TechnologyFirmware Version Info$MEI(@@ @KKSystem Board Memory (UD2)0Micron000000000000000000MT52L1G32D4PG-107 (@@ @KKSystem Board Memory (UD2)0Micron000000000000000000MT52L1G32D4PG-107 nptald IG0 tald IGptali dCPU Thermal Sensora dOther Thermal Sensora dOther Thermal Sensord dHdd Thermal Sensorg dAmbient Thermal Sensorh dMemory Thermal Sensor 1 ) )Onboard IGD) )Onboard LAN) )Onboard SOUND) ) Onboard SATA CONTROLLERd #J Sys. Battery BaySMP01/03/20170CE4DELL HMPFH621.0LiPCN0HMPFHSLW00714I2DDA00 ~ Dell Inc.2R0Q2G2Docking Station$AMT@5 V &vPro T   ; A$BHP !"D QE0@CMEI1MEI2MEI3^Reference Code - CPUuCode VersionTXT ACM version   Reference Code - ME 11.0MEBx versionME Firmware VersionCorporate SKUK !! >4 > 4Reference Code - SKL PCHPCH-CRID StatusDisabledPCH-CRID Original ValuePCH-CRID New ValueOPROM - RST - RAIDSKL PCH H Bx Hsio VersionSKL PCH H Dx Hsio VersionKBL PCH H Ax Hsio VersionSKL PCH LP Bx Hsio VersionSKL PCH LP Cx Hsio Version6Reference Code - SA - System AgentReference Code - MRCSA - PCIe VersionSA-CRID StatusDisabledSA-CRID Original ValueSA-CRID New ValueOPROM - VBIOSg f Lan Phy VersionSensor Firmware VersionDebug Mode StatusDisabledPerformance Mode StatusDisabledDebug Use USB(Disabled:Serial)DisabledICC Overclocking VersionUNDI VersionEC FW VersionGOP VersionBIOS Guard VersionBase EC FW VersionEC-EC Protocol VersionRoyal Park VersionBP1.3.3.0_RP02Platform Version 0Memory Init CompleteEnd of DXE PhaseBIOS Boot Complete z2017041420170426 "Intel Corp.""2089" "02@BPR"Y_SIDARMy7y63nZgZ077Afwupd-1.9.16/libfwupdplugin/tests/dmi/tables64/smbios_entry_point000066400000000000000000000000301460375044200251760ustar00rootroot00000000000000_SM3_ jfwupd-1.9.16/libfwupdplugin/tests/edid.bin000066400000000000000000000002001460375044200205260ustar00rootroot000000000000004!Ce0x87654321Hughesfwupd-1.9.16/libfwupdplugin/tests/edid.builder.xml000066400000000000000000000002531460375044200222130ustar00rootroot00000000000000 0x1234 0x87654321 HUG Hughes fwupd-1.9.16/libfwupdplugin/tests/efi-firmware-file.builder.xml000066400000000000000000000003171460375044200246010ustar00rootroot00000000000000 ced4eac6-49f3-4c12-a597-fc8c33447691 0x0B aGVsbG8gd29ybGQ= fwupd-1.9.16/libfwupdplugin/tests/efi-firmware-filesystem.bin000066400000000000000000000002301460375044200243630ustar00rootroot00000000000000x匌=O5a-Ә_FVHHIL3Dv#hello worldIL3Dv#hello worldfwupd-1.9.16/libfwupdplugin/tests/efi-firmware-filesystem.builder.xml000066400000000000000000000010021460375044200260360ustar00rootroot00000000000000 8c8ce578-8a3d-4f1c-9935-896185c32dd3 0x3 ced4eac6-49f3-4c12-a597-fc8c33447691 aGVsbG8gd29ybGQ= 0x3 ced4eac6-49f3-4c12-a597-fc8c33447691 aGVsbG8gd29ybGQ= fwupd-1.9.16/libfwupdplugin/tests/efi-firmware-section.builder.xml000066400000000000000000000002301460375044200253200ustar00rootroot00000000000000 0x02 ced4eac6-49f3-4c12-a597-fc8c33447691 aGVsbG8gd29ybGQ= fwupd-1.9.16/libfwupdplugin/tests/efi-firmware-volume.bin000066400000000000000000000001301460375044200235050ustar00rootroot00000000000000+vL'G[OPX_FVHHXhello worldfwupd-1.9.16/libfwupdplugin/tests/efi-firmware-volume.builder.xml000066400000000000000000000002401460375044200251640ustar00rootroot00000000000000 0x3 fff12b8d-7696-4c8b-a985-2747075b4f50 aGVsbG8gd29ybGQ= fwupd-1.9.16/libfwupdplugin/tests/efi-load-option.bin000066400000000000000000000002111460375044200226110ustar00rootroot00000000000000JLinux-Firmware-Updater*ۘ0#J- *Hello Worldhello worldfwupd-1.9.16/libfwupdplugin/tests/efi-load-option.builder.xml000066400000000000000000000014451460375044200243000ustar00rootroot00000000000000 Linux-Firmware-Updater 0x1 aGVsbG8gd29ybGQ= 0x04 0x01 0x1 0x800 0x12c000 c7e198db-ec30-4a23-b0fd-8e2dbc092a01 guid-partition-table guid 0x04 0x04 Hello World fwupd-1.9.16/libfwupdplugin/tests/efi/000077500000000000000000000000001460375044200177025ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/efi/efivars/000077500000000000000000000000001460375044200213415ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/efi/efivars/Boot0000-8be4df61-93ca-11d2-aa0d-00e098032b8c000066400000000000000000000002661460375044200273660ustar00rootroot00000000000000bLinux-Firmware-Updater*ۘ0#J- *4\EFI\fedora\shimx64.efi\fwupdx64.efifwupd-1.9.16/libfwupdplugin/tests/efi/efivars/Boot0001-8be4df61-93ca-11d2-aa0d-00e098032b8c000066400000000000000000000001721460375044200273630ustar00rootroot00000000000000bFedora*ۘ0#J- *4\EFI\fedora\shimx64.efifwupd-1.9.16/libfwupdplugin/tests/efi/efivars/Boot0002-8be4df61-93ca-11d2-aa0d-00e098032b8c000066400000000000000000000004601460375044200273640ustar00rootroot00000000000000tWindows Boot Manager* "ȼaJ`/=[F\EFI\Microsoft\Boot\bootmgfw.efiWINDOWSxBCDOBJECT={9dea862c-5cdd-4e70-acc1-f32b344d4795}ofwupd-1.9.16/libfwupdplugin/tests/efi/efivars/BootNext-8be4df61-93ca-11d2-aa0d-00e098032b8c000066400000000000000000000000001460375044200277070ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c000066400000000000000000000000011460375044200302200ustar00rootroot000000000000001fwupd-ddc0ee61-e7f0-4e7d-acc5-c070a398838e-0-0abba7dc-e516-4167-bbf5-4d9d1c739416000066400000000000000000000003461460375044200344760ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/efi/efivars {iMi eY*@ZZ~I ʍ5Mm\EFI\fedora\fw\fwupd-697bd920-12cf-4da9-8385-996909bc6559.capfwupd-1.9.16/libfwupdplugin/tests/efi/efivars/meson.build000066400000000000000000000011121460375044200234760ustar00rootroot00000000000000configure_file(input: 'BootNext-8be4df61-93ca-11d2-aa0d-00e098032b8c', output: 'BootNext-8be4df61-93ca-11d2-aa0d-00e098032b8c', copy: true) configure_file(input: 'fwupd-ddc0ee61-e7f0-4e7d-acc5-c070a398838e-0-0abba7dc-e516-4167-bbf5-4d9d1c739416', output: 'fwupd-ddc0ee61-e7f0-4e7d-acc5-c070a398838e-0-0abba7dc-e516-4167-bbf5-4d9d1c739416', copy: true) configure_file(input: 'SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c', output: 'SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c', copy: true) fwupd-1.9.16/libfwupdplugin/tests/efi/meson.build000066400000000000000000000000221460375044200220360ustar00rootroot00000000000000subdir('efivars') fwupd-1.9.16/libfwupdplugin/tests/fdt.bin000066400000000000000000000004241460375044200204060ustar00rootroot00000000000000 8( hello worldimagesfirmware-1#hash-1 hello worconfigurationsconf-1conf-1 hello wor keyfwupd-1.9.16/libfwupdplugin/tests/fdt.builder.xml000066400000000000000000000015401460375044200220630ustar00rootroot00000000000000 0x11 0x5 hello world images firmware-1 0x123 hash-1 aGVsbG8gd29ybGQ configurations conf-1 conf-1 aGVsbG8gd29ybGQ fwupd-1.9.16/libfwupdplugin/tests/firmware.bin000066400000000000000000000002101460375044200214360ustar00rootroot00000000000000=   ? B = fwupd-1.9.16/libfwupdplugin/tests/firmware.dfu000066400000000000000000000002301460375044200214460ustar00rootroot00000000000000=   ? B = !C4UFD4Yfwupd-1.9.16/libfwupdplugin/tests/firmware.dfuse000066400000000000000000000011701460375044200220020ustar00rootroot00000000000000DfuSehTargetone&4 hello worldxV hello worldTargettwo hello worldBxV4UFDP]qfwupd-1.9.16/libfwupdplugin/tests/firmware.hex000066400000000000000000000006001460375044200214550ustar00rootroot00000000000000:044000003DEF20F080 :10400800FACF01F0FBCF02F0E9CF03F0EACF04F0DA :10401800E1CF05F0E2CF06F0D9CF07F0DACF08F00C :10402800F3CF09F0F4CF0AF0F6CF0BF0F7CF0CF08E :10403800F8CF0DF0F5CF0EF00EC0F5FF0DC0F8FF6C :104048000CC0F7FF0BC0F6FF0AC0F4FF09C0F3FF6E :1040580008C0DAFF07C0D9FF06C0E2FF05C0E1FFCC :1040680004C0EAFF03C0E9FF02C0FBFF01C0FAFF7A :1040780011003FEF20F0000142EF20F03DEF20F06B :00000001FF fwupd-1.9.16/libfwupdplugin/tests/firmware.shex000066400000000000000000000006441460375044200216500ustar00rootroot00000000000000:100000003DEF20F000000000FACF01F0FBCF02F03E :10001000E9CF03F0EACF04F0E1CF05F0E2CF06F03C :10002000D9CF07F0DACF08F0F3CF09F0F4CF0AF018 :10003000F6CF0BF0F7CF0CF0F8CF0DF0F5CF0EF0B8 :100040000EC0F5FF0DC0F8FF0CC0F7FF0BC0F6FFA8 :100050000AC0F4FF09C0F3FF08C0DAFF07C0D9FFE8 :1000600006C0E2FF05C0E1FF04C0EAFF03C0E9FFEC :1000700002C0FBFF01C0FAFF11003FEF20F00001BA :0800800042EF20F03DEF20F0FB :080000FD6465616462656566DB :00000001FF fwupd-1.9.16/libfwupdplugin/tests/firmware.srec000066400000000000000000000006331460375044200216330ustar00rootroot00000000000000S0220000687474703A2F2F737265636F72642E736F75726365666F7267652E6E65742F1D S12300003DEF20F000000000FACF01F0FBCF02F0E9CF03F0EACF04F0E1CF05F0E2CF06F086 S1230020D9CF07F0DACF08F0F3CF09F0F4CF0AF0F6CF0BF0F7CF0CF0F8CF0DF0F5CF0EF0FC S12300400EC0F5FF0DC0F8FF0CC0F7FF0BC0F6FF0AC0F4FF09C0F3FF08C0DAFF07C0D9FFDC S123006006C0E2FF05C0E1FF04C0EAFF03C0E9FF02C0FBFF01C0FAFF11003FEF20F0000112 S10B008042EF20F03DEF20F0F7 S5030005F7 fwupd-1.9.16/libfwupdplugin/tests/fit.bin000066400000000000000000000011301460375044200204060ustar00rootroot00000000000000 V8(nbJ FIT description FIT testimagesfirmware-1 v1.2.4none *firmware/arm6449u-boot 0x11 FIT test FIT description 0x629d4abd images firmware-1 u-boot YWJj AAABAA== arm64 firmware none v1.2.4 hash-1 crc32 0x352441C2 configurations conf-1 conf-1 alice:bob:clara firmware-1
    fwupd-1.9.16/libfwupdplugin/tests/fmap-offset.bin000066400000000000000000000002551460375044200220420ustar00rootroot00000000000000__FMAP__ FMAPTESThello worldWorld!fwupd-1.9.16/libfwupdplugin/tests/fmap-offset.builder.xml000066400000000000000000000003341460375044200235150ustar00rootroot00000000000000 0x10 FMAP aGVsbG8gd29ybGQ= TEST V29ybGQh fwupd-1.9.16/libfwupdplugin/tests/fmap.bin000066400000000000000000000002351460375044200205540ustar00rootroot00000000000000__FMAP__ FMAPTESThello worldWorld!fwupd-1.9.16/libfwupdplugin/tests/fmap.builder.xml000066400000000000000000000003041460375044200222260ustar00rootroot00000000000000 FMAP aGVsbG8gd29ybGQ= TEST V29ybGQh fwupd-1.9.16/libfwupdplugin/tests/hid-descriptor.bin000066400000000000000000000001021460375044200225420ustar00rootroot00000000000000 %u< Ǒ ́ ܑ ؁< ȱ ݑ ށfwupd-1.9.16/libfwupdplugin/tests/hid-descriptor.builder.xml000066400000000000000000000163521460375044200242350ustar00rootroot00000000000000 usage-page 0xff0b usage 0x101 collection 0x1 usage-page 0xff0b logical-minimum logical-maximum 0xff report-id 0xf4 report-size 0x8 report-count < 0x3c usage 0xc7 output 0x2 usage-page 0xff0b logical-minimum logical-maximum 0xff report-id 0xf5 report-size 0x8 report-count 0x10 usage 0xcc input 0x2 usage-page 0xff0b logical-minimum logical-maximum 0xff report-id 0xf2 report-size 0x8 report-count 0x10 usage 0xdc output 0x2 usage-page 0xff0b logical-minimum logical-maximum 0xff report-id 0xf3 report-size 0x8 report-count 0x10 usage 0xd8 input 0x2 usage-page 0xff0b logical-minimum logical-maximum 0xff report-id 0xf1 report-size 0x8 report-count < 0x3c usage 0xc8 feature 0x2 usage-page 0xff0b logical-minimum logical-maximum 0xff report-id 0xf9 report-size 0x8 report-count 0x18 usage 0xdd output 0x2 usage-page 0xff0b logical-minimum logical-maximum 0xff report-id 0xf8 report-size 0x8 report-count 0x14 usage 0xde input 0x2 usage-page 0xff0b logical-minimum logical-maximum 0xff report-id 0xf8 report-size 0x8 report-count 0x14 end-collection fwupd-1.9.16/libfwupdplugin/tests/hid-descriptor2.bin000066400000000000000000000000221460375044200226250ustar00rootroot00000000000000 u fwupd-1.9.16/libfwupdplugin/tests/hid-descriptor2.builder.xml000066400000000000000000000032471460375044200243160ustar00rootroot00000000000000 usage-page 0xff02 usage local 0x1 collection 0x1 usage-page 0xff02 report-size 0x8 report-count 0x14 report-id 0x9 usage local 0x1 feature 0x2 usage-page 0xff02 report-size 0x8 report-count 0x14 report-id 0x9 end-collection fwupd-1.9.16/libfwupdplugin/tests/hid-report-item.builder.xml000066400000000000000000000001261460375044200243160ustar00rootroot00000000000000 usage 0xDE fwupd-1.9.16/libfwupdplugin/tests/ifd-bios.bin000066400000000000000000000100001460375044200213140ustar00rootroot00000000000000+vL'G[OP_FVH H3hello world+vL'G[OP_FVH H3hello worldfwupd-1.9.16/libfwupdplugin/tests/ifd-bios.builder.xml000066400000000000000000000006641460375044200230100ustar00rootroot00000000000000 bios 0x1 0x1000 fff12b8d-7696-4c8b-a985-2747075b4f50 0xB aGVsbG8gd29ybGQ= fff12b8d-7696-4c8b-a985-2747075b4f50 0xB aGVsbG8gd29ybGQ= fwupd-1.9.16/libfwupdplugin/tests/ifd-no-bios.bin000066400000000000000000000400001460375044200217310ustar00rootroot00000000000000ZX01\2BWorld!World!fwupd-1.9.16/libfwupdplugin/tests/ifd-no-bios.builder.xml000066400000000000000000000010231460375044200234100ustar00rootroot00000000000000 0x40003 0x58100208 0x310330 0x325c00f5 0x42 gbe 0x3 0x3000 V29ybGQh me 0x2 0x2000 V29ybGQh fwupd-1.9.16/libfwupdplugin/tests/ifd.bin000066400000000000000000000300001460375044200203640ustar00rootroot00000000000000ZX01\2Bx匌=O5a-_FVH HIL3Dv{ L#IL3Dvhello worldhello worldIL3Dv#hello world+vL'G[OP_FVH H3hello worldWorld!fwupd-1.9.16/libfwupdplugin/tests/ifd.builder.xml000066400000000000000000000030251460375044200220500ustar00rootroot00000000000000 0x40003 0x58100208 0x310330 0x325c00f5 0x42 bios 0x1 0x1000 8c8ce578-8a3d-4f1c-9935-896185c32dd3 0xB 0x3 ced4eac6-49f3-4c12-a597-fc8c33447691 0x0B 0x02 ced4eac6-49f3-4c12-a597-fc8c33447691 aGVsbG8gd29ybGQ= aGVsbG8gd29ybGQ= ced4eac6-49f3-4c12-a597-fc8c33447691 aGVsbG8gd29ybGQ= fff12b8d-7696-4c8b-a985-2747075b4f50 0xB aGVsbG8gd29ybGQ= me 0x2 0x2000 V29ybGQh fwupd-1.9.16/libfwupdplugin/tests/ifwi-cpd.bin000066400000000000000000000001321460375044200213270ustar00rootroot00000000000000$CPD4oneD twoO hello worldhello worldfwupd-1.9.16/libfwupdplugin/tests/ifwi-cpd.builder.xml000066400000000000000000000005231460375044200230100ustar00rootroot00000000000000 0x1234 0x1 0x2 one aGVsbG8gd29ybGQ= two aGVsbG8gd29ybGQ= fwupd-1.9.16/libfwupdplugin/tests/ifwi-fpt.bin000066400000000000000000000001661460375044200213610ustar00rootroot00000000000000$FPT  INFO` FWIMk hello worldhello worldfwupd-1.9.16/libfwupdplugin/tests/ifwi-fpt.builder.xml000066400000000000000000000004051460375044200230320ustar00rootroot00000000000000 0x4f464e49 aGVsbG8gd29ybGQ= 0x4d495746 aGVsbG8gd29ybGQ= fwupd-1.9.16/libfwupdplugin/tests/ihex.bin000066400000000000000000000004401460375044200205640ustar00rootroot00000000000000:100000004E6571756520706F72726F2071756973BE :100010007175616D206573742071756920646F6CF2 :100020006F72656D20697073756D207175696120DF :10003000646F6C6F722073697420616D65742C201D :10004000636F6E73656374657475722C2061646987 :0C00500070697363692076656C69740A3E :040000FD646176655F :00000001FF fwupd-1.9.16/libfwupdplugin/tests/ihex.builder.xml000066400000000000000000000004061460375044200222430ustar00rootroot00000000000000 TmVxdWUgcG9ycm8gcXVpc3F1YW0gZXN0IHF1aSBkb2xvcmVtIGlwc3VtIHF1aWEgZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyLCBhZGlwaXNjaSB2ZWxpdAo= signature ZGF2ZQ== fwupd-1.9.16/libfwupdplugin/tests/intel-thunderbolt.bin000066400000000000000000000011601460375044200232720ustar00rootroot00000000000000`@ pfwupd-1.9.16/libfwupdplugin/tests/intel-thunderbolt.builder.xml000066400000000000000000000005021460375044200247460ustar00rootroot00000000000000 0x10 0xd4 0x15ef 0xb070 titan-ridge 0x3 false false 0x6000 fwupd-1.9.16/libfwupdplugin/tests/localtime000077700000000000000000000000001460375044200240472America/New_Yorkustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/lockdown/000077500000000000000000000000001460375044200207575ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/lockdown/locked/000077500000000000000000000000001460375044200222205ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/lockdown/locked/lockdown000066400000000000000000000000411460375044200237560ustar00rootroot00000000000000none integrity [confidentiality] fwupd-1.9.16/libfwupdplugin/tests/lockdown/none/000077500000000000000000000000001460375044200217165ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/lockdown/none/lockdown000066400000000000000000000000411460375044200234540ustar00rootroot00000000000000[none] integrity confidentiality fwupd-1.9.16/libfwupdplugin/tests/meson.build000066400000000000000000000005221460375044200213000ustar00rootroot00000000000000subdir('colorhug') subdir('efi') installed_firmware_zip = custom_target('installed-firmware-zip', input: [ 'colorhug/firmware.bin', 'colorhug/firmware.bin.asc', ], output: 'firmware.zip', command: [ python3, '-m', 'zipfile', '-c', '@OUTPUT@', '@INPUT@', ], install: true, install_dir: installed_test_datadir, ) fwupd-1.9.16/libfwupdplugin/tests/metadata.xml000077700000000000000000000000001460375044200263152../../src/tests/metadata.xmlustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/oprom.bin000066400000000000000000000020001460375044200207550ustar00rootroot00000000000000UPCIRhello worldfwupd-1.9.16/libfwupdplugin/tests/oprom.builder.xml000066400000000000000000000004571460375044200224500ustar00rootroot00000000000000 0x1 0x0 0x8086 0x1 aGVsbG8gd29ybGQ= cpd aGVsbG8gd29ybGQ= fwupd-1.9.16/libfwupdplugin/tests/quirks.d/000077500000000000000000000000001460375044200206775ustar00rootroot00000000000000fwupd-1.9.16/libfwupdplugin/tests/quirks.d/tests.quirk000066400000000000000000000010351460375044200231150ustar00rootroot00000000000000[USB\VID_0A5C&PID_6412] Flags = ignore-runtime [ACME Inc.=True] Name = awesome [CORP*] Name = town [USB\VID_0BDA&PID_1100] Flags = clever Name = Hub Children = FuDevice|USB\VID_0763&PID_2806&I2C_01 Flags = no-generic-guids [USB\VID_0763&PID_2806&I2C_01] Name = HDMI Flags = updatable,internal [CFI\FLASHID_3730] Name = A25Lxxx CfiDeviceCmdChipErase = 0xc7 CfiDeviceCmdSectorErase = 0x20 CfiDevicePageSize = 0x200 CfiDeviceSectorSize = 0x2000 CfiDeviceBlockSize = 0x8000 FirmwareSizeMax = 0x10000 [MEI] Plugin = one [MEI] Plugin = two fwupd-1.9.16/libfwupdplugin/tests/srec-addr32.bin000066400000000000000000000001201460375044200216330ustar00rootroot00000000000000S00600004844521B S3100100000068656C6C6F20776F726C6492 S5030001FB S70500000000FA fwupd-1.9.16/libfwupdplugin/tests/srec-addr32.builder.xml000066400000000000000000000001661460375044200233220ustar00rootroot00000000000000 0x1000000 HDR aGVsbG8gd29ybGQ= fwupd-1.9.16/libfwupdplugin/tests/srec.bin000066400000000000000000000001101460375044200205550ustar00rootroot00000000000000S00600004844521B S10E000068656C6C6F20776F726C6495 S5030001FB S9030000FC fwupd-1.9.16/libfwupdplugin/tests/srec.builder.xml000066400000000000000000000001351460375044200222410ustar00rootroot00000000000000 HDR aGVsbG8gd29ybGQ= fwupd-1.9.16/libfwupdplugin/tests/sys_vendor000066400000000000000000000000121460375044200212460ustar00rootroot00000000000000FwupdTest fwupd-1.9.16/libfwupdplugin/tests/uswid.bin000066400000000000000000000005151460375044200207650ustar00rootroot00000000000000SBOMֺ.zR;6een-USrfwupd-efi:fwupdx64hfwupdx64 c1.4@2efwupd7x&EFI helpers to install system firmware-n1.3-3-g1d0c69fnRichard Hughes khughsie.com!&x'https://spdx.org/licenses/LGPL-2.0.html(!gfoo.bin{X be :"'>,wVzd:7ث/Qeen-US c456@2efwupdfwupd-1.9.16/libfwupdplugin/tests/uswid.builder.xml000066400000000000000000000016761460375044200224530ustar00rootroot00000000000000 foo 1.2.3 0x1 fwupd-efi:fwupdx64 1.4 semver fwupdx64 EFI helpers to install system firmware 1.3-3-g1d0c69f license https://spdx.org/licenses/LGPL-2.0.html foo.bin 123 sha256 dc626520dcd53a22f727af3ee42c770e56c97a64fe3adb063799d8ab032fe551 Richard Hughes hughsie.com tag-creator 456 fwupd-1.9.16/meson.build000066400000000000000000000532251460375044200151130ustar00rootroot00000000000000project('fwupd', 'c', version: '1.9.16', license: 'LGPL-2.1-or-later', meson_version: '>=0.62.0', default_options: ['warning_level=2', 'c_std=c11'], ) fwupd_version = meson.project_version() varr = fwupd_version.split('.') fwupd_major_version = varr[0] fwupd_minor_version = varr[1] fwupd_micro_version = varr[2] conf = configuration_data() conf.set('MAJOR_VERSION', fwupd_major_version) conf.set('MINOR_VERSION', fwupd_minor_version) conf.set('MICRO_VERSION', fwupd_micro_version) conf.set_quoted('PACKAGE_VERSION', fwupd_version) # get source version, falling back to package version git = find_program('git', required: false) tag = false if git.found() source_version = run_command([git, 'describe'], check: false).stdout().strip() if source_version == '' source_version = fwupd_version endif tag = run_command([git, 'describe', '--exact-match'], check: false).returncode() == 0 else source_version = fwupd_version endif conf.set_quoted('SOURCE_VERSION', source_version) # libtool versioning - this applies to libfwupd # # See http://sources.redhat.com/autobook/autobook/autobook_91.html#SEC91 for details # # - If interfaces have been changed or added, but binary compatibility # has been preserved, change: # CURRENT += 1 # REVISION = 0 # AGE += 1 # - If binary compatibility has been broken (eg removed or changed # interfaces), change: # CURRENT += 1 # REVISION = 0 # AGE = 0 # - If the interface is the same as the previous version, but bugs are # fixed, change: # REVISION += 1 libfwupd_lt_current = '2' libfwupd_lt_revision = '0' libfwupd_lt_age = '0' libfwupd_lt_version = '@0@.@1@.@2@'.format(libfwupd_lt_current, libfwupd_lt_age, libfwupd_lt_revision) # get supported warning flags warning_flags = [ '-Wfatal-errors', '-Waggregate-return', '-Wunused', '-Warray-bounds', '-Wcast-align', '-Wclobbered', '-Wdeclaration-after-statement', '-Wdiscarded-qualifiers', '-Wduplicated-branches', '-Wduplicated-cond', '-Wempty-body', '-Wformat=2', '-Wformat-nonliteral', '-Wformat-security', '-Wformat-signedness', '-Wignored-qualifiers', '-Wimplicit-function-declaration', '-Wimplicit-int', '-Winit-self', '-Wint-conversion', '-Wlogical-op', '-Wmaybe-uninitialized', '-Wmissing-declarations', '-Wmissing-format-attribute', '-Wmissing-include-dirs', '-Wmissing-noreturn', '-Wmissing-parameter-type', '-Wmissing-prototypes', '-Wnested-externs', '-Wno-cast-function-type', '-Wno-address-of-packed-member', # incompatible with g_autoptr() '-Wno-unknown-pragmas', '-Wno-missing-field-initializers', '-Wno-strict-aliasing', '-Wno-suggest-attribute=format', '-Wno-typedef-redefinition', '-Wno-unknown-warning-option', '-Wno-unused-parameter', '-Wno-nonnull-compare', '-Wold-style-definition', '-Woverride-init', '-Wpointer-arith', '-Wredundant-decls', '-Wreturn-type', '-Wshadow', '-Wsign-compare', '-Wstrict-aliasing', '-Wstrict-prototypes', '-Wswitch-default', '-Wtype-limits', '-Wundef', '-Wuninitialized', '-Wunused-but-set-variable', '-Wunused-variable', '-Wvla', '-Wwrite-strings' ] static_analysis = get_option('static_analysis') and host_machine.system() != 'windows' if static_analysis warning_flags += ['-fanalyzer', '-Wno-analyzer-null-dereference'] endif cc = meson.get_compiler('c') add_project_arguments(cc.get_supported_arguments(warning_flags), language: 'c') if not meson.is_cross_build() add_project_arguments('-fstack-protector-strong', language: 'c') endif if cc.get_id() == 'msvc' error('MSVC is not supported as it does not support __attribute__((cleanup))') endif # enable full RELRO where possible # FIXME: until https://github.com/mesonbuild/meson/issues/1140 is fixed global_link_args = [] test_link_args = [ '-Wl,-z,relro', '-Wl,-z,defs', '-Wl,-z,now', '-Wl,-z,ibt,-z,shstk', ] foreach arg: test_link_args if cc.has_link_argument(arg) global_link_args += arg endif endforeach add_project_link_arguments( global_link_args, language: 'c' ) add_project_arguments('-DFWUPD_COMPILATION', language: 'c') # Needed for realpath(), syscall(), cfmakeraw(), etc. add_project_arguments('-D_DEFAULT_SOURCE', language: 'c') # needed for symlink() and BYTE_ORDER add_project_arguments('-D_BSD_SOURCE', language: 'c') add_project_arguments('-D__BSD_VISIBLE', language: 'c') add_project_arguments('-D_XOPEN_SOURCE=700', language: 'c') # needed for memfd_create() add_project_arguments('-D_GNU_SOURCE', language: 'c') # needed for memmem() add_project_arguments('-D_DARWIN_C_SOURCE=900000', language: 'c') # sanity check if get_option('build') == 'all' build_standalone = true build_daemon = true elif get_option('build') == 'standalone' build_standalone = true build_daemon = false elif get_option('build') == 'library' build_standalone = false build_daemon = false endif prefix = get_option('prefix') bindir = join_paths(prefix, get_option('bindir')) libdir = join_paths(prefix, get_option('libdir')) libexecdir = join_paths(prefix, get_option('libexecdir')) #this ends up in compiled code, ignore prefix if host_machine.system() == 'windows' sysconfdir = get_option('sysconfdir') localstatedir = get_option('localstatedir') datadir = get_option('datadir') installed_test_bindir = get_option('libexecdir') installed_test_datadir = get_option('datadir') daemon_dir = get_option('libexecdir') else datadir = join_paths(prefix, get_option('datadir')) sysconfdir = join_paths(prefix, get_option('sysconfdir')) localstatedir = join_paths(prefix, get_option('localstatedir')) installed_test_bindir = join_paths(libexecdir, 'installed-tests', meson.project_name()) installed_test_datadir = join_paths(datadir, 'installed-tests', meson.project_name()) daemon_dir = join_paths(libexecdir, 'fwupd') endif mandir = join_paths(prefix, get_option('mandir')) localedir = join_paths(prefix, get_option('localedir')) diffcmd = find_program('diff') gio = dependency('gio-2.0', version: '>= 2.68.0') giounix = dependency('gio-unix-2.0', version: '>= 2.68.0', required: false) if giounix.found() conf.set('HAVE_GIO_UNIX', '1') endif gmodule = dependency('gmodule-2.0') if build_standalone gudev = dependency('gudev-1.0', version: '>= 232', required: get_option('gudev').disable_auto_if(host_machine.system() != 'linux')) if gudev.found() conf.set('HAVE_GUDEV', '1') endif bluez = get_option('bluez').disable_auto_if(host_machine.system() != 'linux') if bluez.allowed() conf.set('HAVE_BLUEZ', '1') endif host_cpu = host_machine.cpu_family() hsi = get_option('hsi').disable_auto_if(host_machine.system() != 'linux').disable_auto_if(host_cpu != 'x86' and host_cpu != 'x86_64').allowed() if hsi conf.set('HAVE_HSI', '1') endif libxmlb = dependency('xmlb', version: '>= 0.3.6', fallback: ['libxmlb', 'libxmlb_dep']) gusb = dependency('gusb', version: '>= 0.3.8', fallback: ['gusb', 'gusb_dep'], required: get_option('gusb')) if libxmlb.version().version_compare('>= 0.3.17') if libxmlb.get_variable('zstd') == 'true' lvfs_metadata_format = 'zst' elif libxmlb.get_variable('lzma') == 'true' lvfs_metadata_format = 'xz' else lvfs_metadata_format = 'gz' endif else lvfs_metadata_format = 'xz' endif conf.set_quoted('FU_LVFS_METADATA_FORMAT', lvfs_metadata_format) if gusb.found() conf.set('HAVE_GUSB', '1') endif sqlite = dependency('sqlite3', required: get_option('sqlite')) if sqlite.found() conf.set('HAVE_SQLITE', '1') endif passim = dependency('passim', version: '>= 0.1.2', required: get_option('passim'), fallback: ['passim', 'passim_dep']) if passim.found() conf.set('HAVE_PASSIM', '1') endif libarchive = dependency('libarchive', required: get_option('libarchive')) if libarchive.found() conf.set('HAVE_LIBARCHIVE', '1') if cc.has_header_symbol('archive.h', 'archive_write_add_filter_zstd') conf.set('HAVE_LIBARCHIVE_WRITE_ADD_FILTER_ZSTD', '1') endif endif endif libjcat = dependency('jcat', version: '>= 0.1.4', fallback: ['libjcat', 'libjcat_dep']) libjsonglib = dependency('json-glib-1.0', version: '>= 1.6.0') valgrind = dependency('valgrind', required: false) libcurl = dependency('libcurl', version: '>= 7.62.0', required: get_option('curl')) if libcurl.found() conf.set('HAVE_LIBCURL', '1') endif polkit = dependency('polkit-gobject-1', version: '>= 0.103', required: get_option('polkit').disable_auto_if(host_machine.system() != 'linux')) if polkit.found() conf.set('HAVE_POLKIT', '1') if polkit.version().version_compare('>= 0.114') conf.set('HAVE_POLKIT_0_114', '1') endif conf.set_quoted ('POLKIT_ACTIONDIR', polkit.get_variable(pkgconfig: 'actiondir')) endif if build_daemon if not polkit.found() warning('Polkit is disabled, the daemon will allow ALL client actions') endif udevdir = get_option('udevdir') if udevdir == '' and host_machine.system() == 'linux' udev = dependency('udev') udevdir = udev.get_variable(pkgconfig: 'udevdir') endif endif libm = cc.find_library('m', required: false) zlib = dependency('zlib') bashcomp = dependency('bash-completion', required: false) python3path = get_option('python') if python3path == '' python3 = import('python').find_installation('python3') else python3 = find_program(python3path) endif gnutls = dependency('gnutls', version: '>= 3.6.0', required: get_option('gnutls')) if gnutls.found() conf.set('HAVE_GNUTLS', '1') endif lzma = dependency('liblzma', required: get_option('lzma')) if lzma.found() conf.set('HAVE_LZMA', '1') endif cbor = dependency('libcbor', version: '>= 0.7.0', required: get_option('cbor')) if cbor.found() conf.set('HAVE_CBOR', '1') endif platform_deps = [] if get_option('default_library') != 'static' if host_machine.system() == 'windows' platform_deps += cc.find_library('shlwapi') endif if host_machine.system() == 'freebsd' platform_deps += dependency('efivar') endif endif if valgrind.found() conf.set('HAVE_VALGRIND', '1') endif libsystemd = dependency('libsystemd', required: get_option('systemd').disable_auto_if(host_machine.system() != 'linux')) offline = get_option('offline').require(libsystemd.found(), error_message: '-Doffline=enabled requires systemd') if offline.allowed() conf.set('HAVE_FWUPDOFFLINE', '1') endif if cc.has_header('sys/utsname.h') conf.set('HAVE_UTSNAME_H', '1') endif if cc.has_header('sys/inotify.h') conf.set('HAVE_INOTIFY_H', '1') endif if cc.has_header('sys/ioctl.h') conf.set('HAVE_IOCTL_H', '1') endif if cc.has_header('errno.h') conf.set('HAVE_ERRNO_H', '1') endif if cc.has_header('sys/socket.h') conf.set('HAVE_SOCKET_H', '1') endif if cc.has_header('scsi/sg.h') conf.set('HAVE_SCSI_SG_H', '1') endif if cc.has_header('sys/select.h') conf.set('HAVE_SELECT_H', '1') endif if cc.has_header('sys/io.h') and cc.has_function('outb', prefix: '#include ') conf.set('HAVE_IO_H', '1') endif if cc.has_header('linux/ethtool.h') conf.set('HAVE_ETHTOOL_H', '1') endif if cc.has_header('linux/mei.h') conf.set('HAVE_MEI_H', '1') endif if cc.has_header('mtd/mtd-user.h') conf.set('HAVE_MTD_USER_H', '1') endif if cc.has_header('linux/hidraw.h') conf.set('HAVE_HIDRAW_H', '1') endif if cc.has_header('sys/mman.h') conf.set('HAVE_MMAN_H', '1') endif if cc.has_header('poll.h') conf.set('HAVE_POLL_H', '1') endif if cc.has_header('kenv.h') conf.set('HAVE_KENV_H', '1') endif if cc.has_header('malloc.h') conf.set('HAVE_MALLOC_H', '1') if cc.has_function('malloc_trim', prefix: '#include ') conf.set('HAVE_MALLOC_TRIM', '1') endif endif has_cpuid = cc.has_header_symbol('cpuid.h', '__get_cpuid_count', required: get_option('plugin_msr')) if has_cpuid conf.set('HAVE_CPUID_H', '1') endif if cc.has_function('getuid') conf.set('HAVE_GETUID', '1') endif if cc.has_function('realpath') conf.set('HAVE_REALPATH', '1') endif if cc.has_function('memmem') conf.set('HAVE_MEMMEM', '1') endif if cc.has_function('sigaction') conf.set('HAVE_SIGACTION', '1') endif if cc.has_function('memfd_create') conf.set('HAVE_MEMFD_CREATE', '1') endif if cc.has_header_symbol('locale.h', 'LC_MESSAGES') conf.set('HAVE_LC_MESSAGES', '1') endif if cc.has_header('linux/ipmi.h') have_linux_ipmi = true conf.set('HAVE_LINUX_IPMI_H', '1') else have_linux_ipmi = false endif if cc.has_header_symbol('fcntl.h', 'F_WRLCK') conf.set('HAVE_WRLCK', '1') endif if cc.has_header_symbol('fcntl.h', 'F_OFD_SETLK') conf.set('HAVE_OFD', '1') endif if cc.has_function('pwrite', args: '-D_XOPEN_SOURCE') conf.set('HAVE_PWRITE', '1') endif if cc.has_header_symbol('sys/mount.h', 'BLKSSZGET') conf.set('HAVE_BLKSSZGET', '1') endif if host_machine.system() == 'freebsd' if cc.has_type('struct efi_esrt_entry_v1', prefix: '#include \n#include ') conf.set('HAVE_FREEBSD_ESRT', '1') endif endif launchctl = find_program('launchctl', required: get_option('launchd').disable_auto_if(host_machine.system() != 'darwin').allowed(), ) # this is way less hassle than including TargetConditionals.h and looking for TARGET_OS_MAC=1 if host_machine.system() == 'darwin' conf.set('HOST_MACHINE_SYSTEM_DARWIN', '1') summary({ 'launchd': launchctl, 'launchd_agent_dir': get_option('launchd_agent_dir'), }, section: 'Darwin options') endif # EFI if build_standalone efi_app_location = join_paths(libexecdir, 'fwupd', 'efi') conf.set_quoted('EFI_APP_LOCATION', efi_app_location) if host_cpu == 'x86' EFI_MACHINE_TYPE_NAME = 'ia32' elif host_cpu == 'x86_64' EFI_MACHINE_TYPE_NAME = 'x64' elif host_cpu == 'arm' EFI_MACHINE_TYPE_NAME = 'arm' elif host_cpu == 'aarch64' EFI_MACHINE_TYPE_NAME = 'aa64' elif host_cpu == 'loongarch64' EFI_MACHINE_TYPE_NAME = 'loongarch64' elif host_cpu == 'riscv64' EFI_MACHINE_TYPE_NAME = 'riscv64' else EFI_MACHINE_TYPE_NAME = '' endif conf.set_quoted('EFI_MACHINE_TYPE_NAME', EFI_MACHINE_TYPE_NAME) endif umockdev = dependency('umockdev-1.0', required: false) if build_standalone libflashrom = dependency('flashrom', fallback: ['flashrom', 'flashrom_dep'], required: get_option('plugin_flashrom').disable_auto_if(host_machine.system() != 'linux')) endif if libsystemd.found() systemd = dependency('systemd', version: '>= 211', required: get_option('systemd')) conf.set('HAVE_SYSTEMD' , '1') conf.set('HAVE_LOGIND' , '1') systemd_root_prefix = get_option('systemd_root_prefix') if systemd_root_prefix == '' systemdunitdir = systemd.get_variable(pkgconfig: 'systemdsystemunitdir') systemd_shutdown_dir = systemd.get_variable(pkgconfig: 'systemdshutdowndir') systemd_modules_load_dir = systemd.get_variable(pkgconfig: 'modulesloaddir') systemd_sysusers_dir = systemd.get_variable(pkgconfig: 'sysusersdir') else systemdunitdir = systemd.get_variable(pkgconfig: 'systemdsystemunitdir', pkgconfig_define: ['rootprefix', systemd_root_prefix]) systemd_root_prefix_varname = 'root_prefix' if systemd.version().version_compare('< 246') systemd_root_prefix_varname = 'rootprefix' endif systemd_shutdown_dir = systemd.get_variable(pkgconfig: 'systemdshutdowndir', pkgconfig_define: [systemd_root_prefix_varname, systemd_root_prefix]) systemd_modules_load_dir = systemd.get_variable(pkgconfig: 'modulesloaddir', pkgconfig_define: [systemd_root_prefix_varname, systemd_root_prefix]) systemd_sysusers_dir = systemd.get_variable(pkgconfig: 'sysusersdir', pkgconfig_define: [systemd_root_prefix_varname, systemd_root_prefix]) endif endif elogind = dependency('systemd', 'libelogind', required: get_option('elogind')) if elogind.found() conf.set('HAVE_LOGIND' , '1') endif consolekit = get_option('consolekit').disable_auto_if(host_machine.system() != 'linux').allowed() if consolekit conf.set('HAVE_CONSOLEKIT' , '1') endif supported_build = get_option('supported_build').disable_auto_if(not tag).allowed() if supported_build conf.set('SUPPORTED_BUILD', '1') endif # compatibility with behavior in < kernel 6.3 if get_option('thinklmi_compat') conf.set('FU_THINKLMI_COMPAT', '1') endif gnome = import('gnome') i18n = import('i18n') conf.set_quoted('FWUPD_PREFIX', prefix) conf.set_quoted('FWUPD_BINDIR', bindir) conf.set_quoted('FWUPD_LIBDIR', libdir) conf.set_quoted('FWUPD_LIBEXECDIR', libexecdir) conf.set_quoted('FWUPD_DATADIR', datadir) conf.set_quoted('FWUPD_LOCALSTATEDIR', localstatedir) conf.set_quoted('FWUPD_SYSCONFDIR', sysconfdir) conf.set_quoted('FWUPD_LOCALEDIR', localedir) if build_standalone if host_machine.system() == 'windows' libdir_pkg = bindir else libdir_pkg = join_paths(libdir, 'fwupd-@0@'.format(fwupd_version)) endif conf.set_quoted('FWUPD_LIBDIR_PKG', libdir_pkg) endif # sanity check, otherwise there is not point building if build_standalone and host_machine.system() == 'windows' and not gusb.found() error('gusb feature is required for Windows build') endif conf.set_quoted('GETTEXT_PACKAGE', meson.project_name()) conf.set_quoted('PACKAGE_NAME', meson.project_name()) conf.set_quoted('VERSION', meson.project_version()) if get_option('dbus_socket_address') != '' conf.set_quoted('FWUPD_DBUS_SOCKET_ADDRESS', get_option('dbus_socket_address')) endif motd_file = '85-fwupd' motd_dir = 'motd.d' conf.set_quoted('MOTD_FILE', motd_file) conf.set_quoted('MOTD_DIR', motd_dir) conf.set_quoted('FU_DEFAULT_P2P_POLICY', get_option('p2p_policy')) configure_file( output: 'config.h', configuration: conf ) libdrm_amdgpu = dependency('libdrm_amdgpu', version: '>= 2.4.113', required: get_option('plugin_amdgpu')) protobufc = dependency('libprotobuf-c', required: get_option('plugin_logitech_bulkcontroller')) protoc = find_program('protoc', 'protoc-c', required: get_option('plugin_logitech_bulkcontroller')) root_incdir = include_directories('.') fwupd_gir = [] gir_dep = dependency('gobject-introspection-1.0', required: get_option('introspection')) introspection = get_option('introspection').disable_auto_if(host_machine.system() != 'linux').disable_auto_if(not gir_dep.found()) gidocgen_dep = dependency('gi-docgen', version: '>= 2021.1', native: true, fallback: ['gi-docgen', 'dummy_dep'], required: get_option('docs'), ) gidocgen_app = find_program('gi-docgen', required: gidocgen_dep.found()) build_docs = gidocgen_dep.found() and gidocgen_app.found() if build_docs and gidocgen_dep.version().version_compare('< 2022.2') markdown_version = run_command( [python3, '-c', 'import markdown; print(markdown.__version__)'], check: true, ).stdout().strip() build_docs = get_option('docs').require( markdown_version.version_compare('>=3.2'), error_message: 'docs=enabled requires at least markdown >= 3.2' ).allowed() endif jinja2 = run_command( [python3, '-c', 'import jinja2; print(jinja2.__version__)'], check: true, ) if jinja2.stderr().strip() != '' error('Python module jinja2 not found') endif # using "meson configure -Db_sanitize=address,undefined" is super useful in finding corruption, # but it does not work with our GMainContext-abuse tests... if get_option('b_sanitize') in ['address,undefined', 'address', 'undefined', 'leak'] run_sanitize_unsafe_tests = false else run_sanitize_unsafe_tests = true endif # take foo.rs and generate foo-struct.c and foo-struct.h files like protobuf_c rustgen = generator(python3, output : ['@BASENAME@-struct.c', '@BASENAME@-struct.h'], arguments : [ join_paths(meson.project_source_root(), 'libfwupdplugin', 'rustgen.py'), '@INPUT@', '@OUTPUT0@', '@OUTPUT1@', ], ) subdir('generate-build') subdir('libfwupd') if polkit.found() subdir('policy') endif if build_standalone man_md = [] md_targets = [] plugin_quirks = [] subdir('libfwupdplugin') subdir('data') subdir('po') subdir('contrib') # common to all plugins plugin_builtins = [] plugin_incdirs = [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ] plugin_libs = [ fwupd, fwupdplugin, ] subdir('plugins') subdir('src') subdir('docs') # append all the quirks into one big file and gzip it custom_target('builtin-quirk-gz', input: plugin_quirks, output: 'builtin.quirk.gz', command: [ generate_quirk_builtin, '@OUTPUT@', '@INPUT@', ], install: true, install_dir: join_paths(datadir, 'fwupd', 'quirks.d'), ) endif if build_daemon and libsystemd.found() and offline.allowed() install_symlink('fwupd-offline-update.service', install_dir: join_paths(systemdunitdir, 'system-update.target.wants'), pointing_to: join_paths('..', 'fwupd-offline-update.service') ) endif if libsystemd.found() summary({ 'systemd_unit_user': get_option('systemd_unit_user'), 'systemd unit dir': systemdunitdir, 'systemd shutdown dir': systemd_shutdown_dir, 'systemd modules dir': systemd_modules_load_dir, 'systemd sysusers dir': systemd_sysusers_dir, }, section: 'systemd options') endif summary({ 'fwupdtool': build_standalone, 'fwupd (daemon)': build_daemon }, section: 'build targets') summary({ 'cbor': cbor, 'consolekit': consolekit, 'dbus_socket_address': get_option('dbus_socket_address'), 'docs': build_docs, 'elogind': elogind, 'gnutls': gnutls, 'introspection': introspection.allowed(), 'libcurl': libcurl, 'lzma': lzma, 'polkit': polkit, 'python3': python3, 'supported_build': supported_build, 'static_analysis': static_analysis, 'tests': get_option('tests'), }, section: 'project features') if build_daemon summary({ 'bluez': bluez.allowed(), 'compat_cli': get_option('compat_cli'), 'gusb': gusb, 'gudev': gudev, 'hsi': hsi, 'lvfs_metadata_format': lvfs_metadata_format, 'libarchive': libarchive.found(), 'offline': offline.allowed(), 'passim': passim, 'sqlite': sqlite, 'thinklmi_compat': get_option('thinklmi_compat'), 'GPG support': supported_gpg, 'PKCS7 support': supported_pkcs7, }, section: 'daemon features') summary(plugins, section: 'plugins') endif fwupd-1.9.16/meson_options.txt000066400000000000000000000250401460375044200164000ustar00rootroot00000000000000option('build', type: 'combo', choices: [ 'all', 'standalone', 'library', ], value: 'all', description: 'build type', ) option('consolekit', type: 'feature', description: 'ConsoleKit support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('static_analysis', type: 'boolean', value: false, description: 'enable GCC static analysis support', ) option('dbus_socket_address', type: 'string', value: '', description: 'D-Bus socket address to use for p2p mode', ) option('firmware-packager', type: 'boolean', value: true, description: 'enable firmware-packager installation', ) option('docs', type: 'feature', description: 'Build developer documentation', deprecated: { 'docgen': 'enabled', 'none': 'disabled', }, ) option('introspection', type: 'feature', description: 'generate GObject Introspection data', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('lvfs', type: 'combo', choices: [ 'true', 'false', 'disabled', ], value: 'true', description: 'install LVFS remotes', ) option('man', type: 'boolean', value: true, description: 'enable man pages', ) option('libarchive', type: 'feature', description: 'libarchive support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('gudev', type: 'feature', description: 'GUdev support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('gusb', type: 'feature', description: 'GUsb support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('bluez', type: 'feature', description: 'BlueZ support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('polkit', type: 'feature', description: 'PolKit support in daemon', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('gnutls', type: 'feature', description: 'GnuTLS support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('sqlite', type: 'feature', description: 'sqlite support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('passim', type: 'feature', description: 'Passim support', ) option('p2p_policy', type: 'combo', choices: [ 'none', 'metadata', 'firmware', 'metadata,firmware', ], value: 'metadata', description: 'Default P2P sharing policy', ) option('lzma', type: 'feature', description: 'LZMA support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('cbor', type: 'feature', description: 'CBOR support for coSWID and uSWID', ) option('plugin_acpi_phat', type: 'feature', description: 'ACPI PHAT support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_amdgpu', type: 'feature', description: 'AMDGPU support', ) option('plugin_android_boot', type: 'feature', description: 'Android Boot support', ) option('plugin_bcm57xx', type: 'feature', description: 'BCM57xx support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_cfu', type: 'feature', description: 'CFU support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_cpu', type: 'feature', description: 'CPU support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_emmc', type: 'feature', description: 'eMMC support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_ep963x', type: 'feature', description: 'EP963x support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_fastboot', type: 'feature', description: 'Fastboot support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_gpio', type: 'feature', description: 'Linux GPIO support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_igsc', type: 'feature', description: 'Intel Graphics System Controller support', ) option('plugin_kinetic_dp', type: 'feature', description: 'Kinetic SST/MST DisplayPort converter support', ) option('plugin_logitech_bulkcontroller', type: 'feature', description: 'Logitech bulk controller support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_logitech_scribe', type: 'feature', description: 'Logitech Scribe support', ) option('plugin_logitech_tap', type: 'feature', description: 'Logitech Tap support', ) option('plugin_parade_lspcon', type: 'feature', description: 'Parade LSPCON support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_pixart_rf', type: 'feature', description: 'PixartRF support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_realtek_mst', type: 'feature', description: 'Realtek MST hub support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_synaptics_mst', type: 'feature', description: 'Synaptics MST hub support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_mediatek_scaler', type: 'feature', description: 'Mediatek scaler support', ) option('plugin_synaptics_rmi', type: 'feature', description: 'Synaptics RMI support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_scsi', type: 'feature', description: 'SCSI support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_tpm', type: 'feature', description: 'TPM support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_redfish', type: 'feature' , description: 'Redfish support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_uefi_capsule', type: 'feature', description: 'UEFI capsule support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_uefi_capsule_splash', type: 'boolean', value: true, description: 'enable UEFI capsule splash support', ) option('plugin_uefi_pk', type: 'feature', description: 'UEFI PK support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_nitrokey', type: 'feature', description: 'Nitrokey support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_nvme', type: 'feature', description: 'NVMe support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_modem_manager', type: 'feature', description: 'ModemManager support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_msr', type: 'feature', description: 'MSR support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_mtd', type: 'feature', description: 'MTD support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_flashrom', type: 'feature', description: 'flashrom support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_intel_me', type: 'feature', description: 'enable Intel ME support', ) option('plugin_intel_spi', type: 'boolean', value: false, description: 'enable Intel SPI support', ) option('plugin_vendor_example', type: 'boolean', value: false, description: 'enable vendor example support', ) option('plugin_uf2', type: 'feature', description: 'support for UF2', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_upower', type: 'feature', description: 'support for UPower', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('plugin_powerd', type: 'feature', description: 'support for powerd', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('qubes', type: 'boolean', value: false, description: 'build packages for Qubes OS', ) option('supported_build', type: 'feature', description: 'distribution package with upstream support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('systemd', type: 'feature', description: 'systemd support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('systemd_unit_user', type: 'string', value: 'fwupd-refresh', description: 'User account to use for fwupd-refresh.service (empty for DynamicUser)', ) option('systemd_root_prefix', type: 'string', value: '', description: 'Directory to base systemd’s installation directories on', ) option('launchd', type: 'feature', value: 'auto', description: 'Launchd auto-launch support (MacOS only)' ) option( 'launchd_agent_dir', type: 'string', value: '/Library/LaunchAgents', description: 'Directory to put the launchd agent' ) option('elogind', type: 'feature', description: 'elogind support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('tests', type: 'boolean', value: true, description: 'enable tests', ) option('curl', type: 'feature', description: 'libcurl support', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('udevdir', type: 'string', value: '', description: 'Directory for udev rules', ) option('vendor_metadata', type: 'boolean', value: false, description: 'install OS vendor provided metadata', ) option('efi_os_dir', type: 'string', description: 'the hardcoded name of OS directory in ESP, e.g. fedora', ) option('efi_binary', type: 'boolean', value: true, description: 'generate uefi binary if missing', ) option('metainfo', type: 'boolean', value: true, description: 'install the project metainfo.xml information', ) option('bash_completion', type: 'boolean', value: true, description: 'enable bash completion', ) option('fish_completion', type: 'boolean', value: true, description: 'enable fish completion', ) option('offline', type: 'feature', description: 'Allow installing firmware using a pre-boot systemd target', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('compat_cli', type: 'boolean', value: false, description: 'enable legacy commands: fwupdagent,dfu-tool,fwupdate', ) option('hsi', type: 'feature', description: ' Host Security Information', deprecated: { 'true': 'enabled', 'false': 'disabled', }, ) option('thinklmi_compat', type: 'boolean', description: 'Include workaround for thinklmi kernel bugs', ) option('python', type: 'string', description: 'the absolute path of the python3 binary', ) fwupd-1.9.16/plugins/000077500000000000000000000000001460375044200144235ustar00rootroot00000000000000fwupd-1.9.16/plugins/README.md000066400000000000000000000021611460375044200157020ustar00rootroot00000000000000# Adding a new plugin An extensible architecture allows for providing new plugin types (for reading and writing different firmware) as well as ways quirk their behavior. You can find more information about the architecture in the developers section of the [fwupd website](https://fwupd.org). You can use the [fwupd developer documentation](https://fwupd.github.io) to assist with APIs available to write the plugin. If you have a firmware specification and would like to see support in this project, please file an issue and share the spec. Patches are also welcome. We will not accept plugins that upgrade hardware using a proprietary Linux executable, proprietary UEFI executable, proprietary library, or DBus interface. ## Plugin interaction Some plugins may be able to influence the behavior of other plugins. This includes things like one plugin turning on a device, or providing missing metadata to another plugin. The ABI for these interactions is defined in: All interactions between plugins should have the interface defined in that file. fwupd-1.9.16/plugins/acpi-dmar/000077500000000000000000000000001460375044200162605ustar00rootroot00000000000000fwupd-1.9.16/plugins/acpi-dmar/README.md000066400000000000000000000006041460375044200175370ustar00rootroot00000000000000--- title: Plugin: ACPI DMAR — DMA Protection --- ## Introduction This plugin checks if DMA remapping for Thunderbolt devices is available. The result will be stored in an security attribute for HSI. ## External Interface Access This plugin requires read access to `/sys/firmware/acpi/tables`. ## Version Considerations This plugin has been available since fwupd version `1.5.0`. fwupd-1.9.16/plugins/acpi-dmar/fu-acpi-dmar-plugin.c000066400000000000000000000041641460375044200221720ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-dmar-plugin.h" #include "fu-acpi-dmar.h" struct _FuAcpiDmarPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuAcpiDmarPlugin, fu_acpi_dmar_plugin, FU_TYPE_PLUGIN) static void fu_acpi_dmar_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { g_autofree gchar *fn = NULL; g_autofree gchar *path = NULL; g_autoptr(FuAcpiDmar) dmar = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error_local = NULL; /* only Intel */ if (fu_cpu_get_vendor() != FU_CPU_VENDOR_INTEL) return; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_PREBOOT_DMA_PROTECTION); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); /* load DMAR table */ path = fu_path_from_kind(FU_PATH_KIND_ACPI_TABLES); fn = g_build_filename(path, "DMAR", NULL); blob = fu_bytes_get_contents(fn, &error_local); if (blob == NULL) { g_debug("failed to load %s: %s", fn, error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } dmar = fu_acpi_dmar_new(blob, &error_local); if (dmar == NULL) { g_warning("failed to parse %s: %s", fn, error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } if (!fu_acpi_dmar_get_opt_in(dmar)) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_acpi_dmar_plugin_init(FuAcpiDmarPlugin *self) { } static void fu_acpi_dmar_plugin_class_init(FuAcpiDmarPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->add_security_attrs = fu_acpi_dmar_plugin_add_security_attrs; } fwupd-1.9.16/plugins/acpi-dmar/fu-acpi-dmar-plugin.h000066400000000000000000000003601460375044200221710ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuAcpiDmarPlugin, fu_acpi_dmar_plugin, FU, ACPI_DMAR_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/acpi-dmar/fu-acpi-dmar.c000066400000000000000000000026401460375044200206730ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-dmar.h" struct _FuAcpiDmar { FuAcpiTable parent_instance; gboolean opt_in; }; G_DEFINE_TYPE(FuAcpiDmar, fu_acpi_dmar, FU_TYPE_ACPI_TABLE) #define DMAR_DMA_CTRL_PLATFORM_OPT_IN_FLAG 0x4 FuAcpiDmar * fu_acpi_dmar_new(GBytes *blob, GError **error) { FuAcpiDmar *self = g_object_new(FU_TYPE_ACPI_DMAR, NULL); gsize bufsz = 0; guint8 flags = 0; const guint8 *buf = g_bytes_get_data(blob, &bufsz); /* FuAcpiTable->parse */ if (!FU_FIRMWARE_CLASS(fu_acpi_dmar_parent_class) ->parse(FU_FIRMWARE(self), blob, 0x0, FWUPD_INSTALL_FLAG_NONE, error)) return NULL; /* check signature and read flags */ if (g_strcmp0(fu_firmware_get_id(FU_FIRMWARE(self)), "DMAR") != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Not a DMAR table, got %s", fu_firmware_get_id(FU_FIRMWARE(self))); return NULL; } if (!fu_memread_uint8_safe(buf, bufsz, 0x25, &flags, error)) return NULL; g_debug("Flags: 0x%02x", flags); self->opt_in = (flags & DMAR_DMA_CTRL_PLATFORM_OPT_IN_FLAG) > 0; return self; } gboolean fu_acpi_dmar_get_opt_in(FuAcpiDmar *self) { g_return_val_if_fail(FU_IS_ACPI_DMAR(self), FALSE); return self->opt_in; } static void fu_acpi_dmar_class_init(FuAcpiDmarClass *klass) { } static void fu_acpi_dmar_init(FuAcpiDmar *self) { } fwupd-1.9.16/plugins/acpi-dmar/fu-acpi-dmar.h000066400000000000000000000006051460375044200206770ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ACPI_DMAR (fu_acpi_dmar_get_type()) G_DECLARE_FINAL_TYPE(FuAcpiDmar, fu_acpi_dmar, FU, ACPI_DMAR, FuAcpiTable) FuAcpiDmar * fu_acpi_dmar_new(GBytes *blob, GError **error); gboolean fu_acpi_dmar_get_opt_in(FuAcpiDmar *self); fwupd-1.9.16/plugins/acpi-dmar/fu-self-test.c000066400000000000000000000035311460375044200207440ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-dmar.h" static void fu_acpi_dmar_opt_in_func(void) { const gchar *ci = g_getenv("CI_NETWORK"); g_autoptr(FuAcpiDmar) dmar = NULL; g_autoptr(GError) error = NULL; g_autoptr(GBytes) blob = NULL; g_autofree gchar *fn = NULL; fn = g_test_build_filename(G_TEST_DIST, "tests", "DMAR", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing DMAR"); return; } blob = fu_bytes_get_contents(fn, &error); g_assert_no_error(error); g_assert_nonnull(blob); dmar = fu_acpi_dmar_new(blob, &error); g_assert_no_error(error); g_assert_nonnull(dmar); g_assert_true(fu_acpi_dmar_get_opt_in(dmar)); } static void fu_acpi_dmar_opt_out_func(void) { const gchar *ci = g_getenv("CI_NETWORK"); g_autoptr(FuAcpiDmar) dmar = NULL; g_autoptr(GError) error = NULL; g_autoptr(GBytes) blob = NULL; g_autofree gchar *fn = NULL; fn = g_test_build_filename(G_TEST_DIST, "tests", "DMAR-OPTOUT", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing DMAR-OPTOUT"); return; } blob = fu_bytes_get_contents(fn, &error); g_assert_no_error(error); g_assert_nonnull(blob); dmar = fu_acpi_dmar_new(blob, &error); g_assert_no_error(error); g_assert_nonnull(dmar); g_assert_false(fu_acpi_dmar_get_opt_in(dmar)); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); (void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE); /* tests go here */ g_test_add_func("/acpi-dmar/opt-in", fu_acpi_dmar_opt_in_func); g_test_add_func("/acpi-dmar/opt-out", fu_acpi_dmar_opt_out_func); return g_test_run(); } fwupd-1.9.16/plugins/acpi-dmar/meson.build000066400000000000000000000021161460375044200204220ustar00rootroot00000000000000if hsi and (host_cpu == 'x86' or host_cpu == 'x86_64') plugins += {meson.current_source_dir().split('/')[-1]: true} cargs = ['-DG_LOG_DOMAIN="FuPluginAcpiDmar"'] plugin_builtin_acpi_dmar = static_library('fu_plugin_acpi_dmar', sources: [ 'fu-acpi-dmar-plugin.c', 'fu-acpi-dmar.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_acpi_dmar if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'acpi-dmar-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_acpi_dmar, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('acpi-dmar-self-test', e, env: env) # added to installed-tests endif endif fwupd-1.9.16/plugins/acpi-facp/000077500000000000000000000000001460375044200162465ustar00rootroot00000000000000fwupd-1.9.16/plugins/acpi-facp/README.md000066400000000000000000000005661460375044200175340ustar00rootroot00000000000000--- title: Plugin: ACPI FACP — Fixed ACPI Description Table --- ## Introduction This plugin checks if S2I sleep is available. The result will be stored in an security attribute for HSI. ## External Interface Access This plugin requires read access to `/sys/firmware/acpi/tables`. ## Version Considerations This plugin has been available since fwupd version `1.5.0`. fwupd-1.9.16/plugins/acpi-facp/fu-acpi-facp-plugin.c000066400000000000000000000042571460375044200221510ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-facp-plugin.h" #include "fu-acpi-facp.h" struct _FuAcpiFacpPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuAcpiFacpPlugin, fu_acpi_facp_plugin, FU_TYPE_PLUGIN) static void fu_acpi_facp_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { g_autofree gchar *fn = NULL; g_autofree gchar *path = NULL; g_autoptr(FuAcpiFacp) facp = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error_local = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_IDLE); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); /* load FACP table */ path = fu_path_from_kind(FU_PATH_KIND_ACPI_TABLES); fn = g_build_filename(path, "FACP", NULL); blob = fu_bytes_get_contents(fn, &error_local); if (blob == NULL) { g_debug("failed to load %s: %s", fn, error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } facp = fu_acpi_facp_new(blob, &error_local); if (facp == NULL) { g_warning("failed to parse %s: %s", fn, error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } /* options are usually "Linux" (S3) or "Windows" (s2idle) */ fu_security_attr_add_bios_target_value(attr, "com.thinklmi.SleepState", "windows"); if (!fu_acpi_facp_get_s2i(facp)) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_acpi_facp_plugin_init(FuAcpiFacpPlugin *self) { } static void fu_acpi_facp_plugin_class_init(FuAcpiFacpPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->add_security_attrs = fu_acpi_facp_plugin_add_security_attrs; } fwupd-1.9.16/plugins/acpi-facp/fu-acpi-facp-plugin.h000066400000000000000000000003601460375044200221450ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuAcpiFacpPlugin, fu_acpi_facp_plugin, FU, ACPI_FACP_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/acpi-facp/fu-acpi-facp.c000066400000000000000000000020401460375044200206410ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-acpi-facp.h" struct _FuAcpiFacp { GObject parent_instance; gboolean get_s2i; }; G_DEFINE_TYPE(FuAcpiFacp, fu_acpi_facp, G_TYPE_OBJECT) #define LOW_POWER_S0_IDLE_CAPABLE (1 << 21) FuAcpiFacp * fu_acpi_facp_new(GBytes *blob, GError **error) { FuAcpiFacp *self = g_object_new(FU_TYPE_ACPI_FACP, NULL); gsize bufsz = 0; guint32 flags = 0; const guint8 *buf = g_bytes_get_data(blob, &bufsz); /* parse table */ if (!fu_memread_uint32_safe(buf, bufsz, 0x70, &flags, G_LITTLE_ENDIAN, error)) return NULL; g_debug("Flags: 0x%04x", flags); self->get_s2i = (flags & LOW_POWER_S0_IDLE_CAPABLE) > 0; return self; } gboolean fu_acpi_facp_get_s2i(FuAcpiFacp *self) { g_return_val_if_fail(FU_IS_ACPI_FACP(self), FALSE); return self->get_s2i; } static void fu_acpi_facp_class_init(FuAcpiFacpClass *klass) { } static void fu_acpi_facp_init(FuAcpiFacp *self) { } fwupd-1.9.16/plugins/acpi-facp/fu-acpi-facp.h000066400000000000000000000005721460375044200206560ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ACPI_FACP (fu_acpi_facp_get_type()) G_DECLARE_FINAL_TYPE(FuAcpiFacp, fu_acpi_facp, FU, ACPI_FACP, GObject) FuAcpiFacp * fu_acpi_facp_new(GBytes *blob, GError **error); gboolean fu_acpi_facp_get_s2i(FuAcpiFacp *self); fwupd-1.9.16/plugins/acpi-facp/fu-self-test.c000066400000000000000000000036061460375044200207350ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-acpi-facp.h" static void fu_acpi_facp_s2i_disabled_func(void) { const gchar *ci = g_getenv("CI_NETWORK"); g_autofree gchar *fn = NULL; g_autoptr(FuAcpiFacp) facp = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; fn = g_test_build_filename(G_TEST_DIST, "tests", "FACP", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing FACP"); return; } blob = fu_bytes_get_contents(fn, &error); g_assert_no_error(error); g_assert_nonnull(blob); facp = fu_acpi_facp_new(blob, &error); g_assert_no_error(error); g_assert_nonnull(facp); g_assert_false(fu_acpi_facp_get_s2i(facp)); } static void fu_acpi_facp_s2i_enabled_func(void) { const gchar *ci = g_getenv("CI_NETWORK"); g_autofree gchar *fn = NULL; g_autoptr(FuAcpiFacp) facp = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; fn = g_test_build_filename(G_TEST_DIST, "tests", "FACP-S2I", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing FACP-S2I"); return; } blob = fu_bytes_get_contents(fn, &error); g_assert_no_error(error); g_assert_nonnull(blob); facp = fu_acpi_facp_new(blob, &error); g_assert_no_error(error); g_assert_nonnull(facp); g_assert_true(fu_acpi_facp_get_s2i(facp)); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); (void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE); /* tests go here */ g_test_add_func("/acpi-facp/s2i{disabled}", fu_acpi_facp_s2i_disabled_func); g_test_add_func("/acpi-facp/s2i{enabled}", fu_acpi_facp_s2i_enabled_func); return g_test_run(); } fwupd-1.9.16/plugins/acpi-facp/meson.build000066400000000000000000000021161460375044200204100ustar00rootroot00000000000000if hsi and (host_cpu == 'x86' or host_cpu == 'x86_64') cargs = ['-DG_LOG_DOMAIN="FuPluginAcpiFacp"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtin_acpi_facp = static_library('fu_plugin_acpi_facp', sources: [ 'fu-acpi-facp-plugin.c', 'fu-acpi-facp.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_acpi_facp if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'acpi-facp-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_acpi_facp, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('acpi-facp-self-test', e, env: env) # added to installed-tests endif endif fwupd-1.9.16/plugins/acpi-ivrs/000077500000000000000000000000001460375044200163205ustar00rootroot00000000000000fwupd-1.9.16/plugins/acpi-ivrs/README.md000066400000000000000000000007151460375044200176020ustar00rootroot00000000000000--- title: Plugin: ACPI IVRS — DMA Protection --- ## Introduction This plugin checks if Pre-boot DMA remapping is available and enabled from the [ACPI IVRS](http://support.amd.com/TechDocs/48882_IOMMU.pdf) table. The result will be stored in an security attribute for HSI. ## External Interface Access This plugin requires read access to `/sys/firmware/acpi/tables`. ## Version Considerations This plugin has been available since fwupd version `1.8.0`. fwupd-1.9.16/plugins/acpi-ivrs/fu-acpi-ivrs-plugin.c000066400000000000000000000042671460375044200222760ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * Copyright (C) 2022 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-ivrs-plugin.h" #include "fu-acpi-ivrs.h" struct _FuAcpiIvrsPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuAcpiIvrsPlugin, fu_acpi_ivrs_plugin, FU_TYPE_PLUGIN) static void fu_acpi_ivrs_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { g_autofree gchar *fn = NULL; g_autofree gchar *path = NULL; g_autoptr(FuAcpiIvrs) ivrs = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error_local = NULL; /* only AMD */ if (fu_cpu_get_vendor() != FU_CPU_VENDOR_AMD) return; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_PREBOOT_DMA_PROTECTION); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); /* load IVRS table */ path = fu_path_from_kind(FU_PATH_KIND_ACPI_TABLES); fn = g_build_filename(path, "IVRS", NULL); blob = fu_bytes_get_contents(fn, &error_local); if (blob == NULL) { g_debug("failed to load %s: %s", fn, error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } ivrs = fu_acpi_ivrs_new(blob, &error_local); if (ivrs == NULL) { g_warning("failed to parse %s: %s", fn, error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } if (!fu_acpi_ivrs_get_dma_remap(ivrs)) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_acpi_ivrs_plugin_init(FuAcpiIvrsPlugin *self) { } static void fu_acpi_ivrs_plugin_class_init(FuAcpiIvrsPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->add_security_attrs = fu_acpi_ivrs_plugin_add_security_attrs; } fwupd-1.9.16/plugins/acpi-ivrs/fu-acpi-ivrs-plugin.h000066400000000000000000000003601460375044200222710ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuAcpiIvrsPlugin, fu_acpi_ivrs_plugin, FU, ACPI_IVRS_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/acpi-ivrs/fu-acpi-ivrs.c000066400000000000000000000027771460375044200210060ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * Copyright (C) 2022 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-ivrs.h" struct _FuAcpiIvrs { FuAcpiTable parent_instance; gboolean remap_support; }; G_DEFINE_TYPE(FuAcpiIvrs, fu_acpi_ivrs, FU_TYPE_ACPI_TABLE) /* IVINfo field */ #define IVRS_DMA_REMAP_SUPPORT_FLAG 0x2 FuAcpiIvrs * fu_acpi_ivrs_new(GBytes *blob, GError **error) { FuAcpiIvrs *self = g_object_new(FU_TYPE_ACPI_IVRS, NULL); gsize bufsz = 0; guint8 ivinfo = 0; const guint8 *buf = g_bytes_get_data(blob, &bufsz); /* FuAcpiTable->parse */ if (!FU_FIRMWARE_CLASS(fu_acpi_ivrs_parent_class) ->parse(FU_FIRMWARE(self), blob, 0x0, FWUPD_INSTALL_FLAG_NONE, error)) return NULL; /* check signature and read flags */ if (g_strcmp0(fu_firmware_get_id(FU_FIRMWARE(self)), "IVRS") != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Not a IVRS table, got %s", fu_firmware_get_id(FU_FIRMWARE(self))); return NULL; } if (!fu_memread_uint8_safe(buf, bufsz, 0x24, &ivinfo, error)) return NULL; g_debug("Flags: 0x%02x", ivinfo); self->remap_support = ivinfo & IVRS_DMA_REMAP_SUPPORT_FLAG; return self; } gboolean fu_acpi_ivrs_get_dma_remap(FuAcpiIvrs *self) { g_return_val_if_fail(FU_IS_ACPI_IVRS(self), FALSE); return self->remap_support; } static void fu_acpi_ivrs_class_init(FuAcpiIvrsClass *klass) { } static void fu_acpi_ivrs_init(FuAcpiIvrs *self) { } fwupd-1.9.16/plugins/acpi-ivrs/fu-acpi-ivrs.h000066400000000000000000000007141460375044200210000ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * Copyright (C) 2022 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ACPI_IVRS (fu_acpi_ivrs_get_type()) G_DECLARE_FINAL_TYPE(FuAcpiIvrs, fu_acpi_ivrs, FU, ACPI_IVRS, FuAcpiTable) FuAcpiIvrs * fu_acpi_ivrs_new(GBytes *blob, GError **error); gboolean fu_acpi_ivrs_get_dma_remap(FuAcpiIvrs *self); fwupd-1.9.16/plugins/acpi-ivrs/fu-self-test.c000066400000000000000000000037311460375044200210060ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * Copyright (C) 2022 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-ivrs.h" static void fu_acpi_ivrs_dma_remap_func(void) { const gchar *ci = g_getenv("CI_NETWORK"); g_autoptr(FuAcpiIvrs) ivrs = NULL; g_autoptr(GError) error = NULL; g_autoptr(GBytes) blob = NULL; g_autofree gchar *fn = NULL; fn = g_test_build_filename(G_TEST_DIST, "tests", "IVRS-REMAP", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing IVRS-REMAP"); return; } blob = fu_bytes_get_contents(fn, &error); g_assert_no_error(error); g_assert_nonnull(blob); ivrs = fu_acpi_ivrs_new(blob, &error); g_assert_no_error(error); g_assert_nonnull(ivrs); g_assert_true(fu_acpi_ivrs_get_dma_remap(ivrs)); } static void fu_acpi_ivrs_no_dma_remap_func(void) { const gchar *ci = g_getenv("CI_NETWORK"); g_autoptr(FuAcpiIvrs) ivrs = NULL; g_autoptr(GError) error = NULL; g_autoptr(GBytes) blob = NULL; g_autofree gchar *fn = NULL; fn = g_test_build_filename(G_TEST_DIST, "tests", "IVRS-NOREMAP", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing IVRS-NOREMAP"); return; } blob = fu_bytes_get_contents(fn, &error); g_assert_no_error(error); g_assert_nonnull(blob); ivrs = fu_acpi_ivrs_new(blob, &error); g_assert_no_error(error); g_assert_nonnull(ivrs); g_assert_false(fu_acpi_ivrs_get_dma_remap(ivrs)); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); (void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE); /* tests go here */ g_test_add_func("/acpi-ivrs/dma-remap-support", fu_acpi_ivrs_dma_remap_func); g_test_add_func("/acpi-ivrs/no-dma-remap-support", fu_acpi_ivrs_no_dma_remap_func); return g_test_run(); } fwupd-1.9.16/plugins/acpi-ivrs/meson.build000066400000000000000000000021161460375044200204620ustar00rootroot00000000000000if hsi and (host_cpu == 'x86' or host_cpu == 'x86_64') cargs = ['-DG_LOG_DOMAIN="FuPluginAcpiIvrs"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtin_acpi_ivrs = static_library('fu_plugin_acpi_ivrs', sources: [ 'fu-acpi-ivrs-plugin.c', 'fu-acpi-ivrs.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_acpi_ivrs if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'acpi-ivrs-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_acpi_ivrs, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('acpi-ivrs-self-test', e, env: env) # added to installed-tests endif endif fwupd-1.9.16/plugins/acpi-phat/000077500000000000000000000000001460375044200162715ustar00rootroot00000000000000fwupd-1.9.16/plugins/acpi-phat/README.md000066400000000000000000000017731460375044200175600ustar00rootroot00000000000000--- title: Plugin: ACPI PHAT — Platform Health Assessment Table --- ## Introduction The PHAT is an ACPI table where a platform can expose health related telemetry that may be useful for software running within the constraints of an OS. These elements are typically going to encompass things that are likely otherwise not enumerable during the OS runtime phase of operations, such as version of pre-OS components. The daemon includes some of the PHAT data in the report data sent to the LVFS so that we can debug failures with the help of the IHV. This allows us to find the root cause of the problem, and so we know what other OEMs may be affected. See for more information. ## External Interface Access This plugin requires read access to `/sys/firmware/acpi/tables`. ## Version Considerations This plugin has been available since fwupd version `1.6.1`. fwupd-1.9.16/plugins/acpi-phat/fu-acpi-phat-health-record.c000066400000000000000000000134161460375044200234370ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-phat-health-record.h" #include "fu-acpi-phat-struct.h" #include "fu-acpi-phat.h" struct _FuAcpiPhatHealthRecord { FuFirmware parent_instance; guint8 am_healthy; gchar *guid; gchar *device_path; }; G_DEFINE_TYPE(FuAcpiPhatHealthRecord, fu_acpi_phat_health_record, FU_TYPE_FIRMWARE) static void fu_acpi_phat_health_record_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuAcpiPhatHealthRecord *self = FU_ACPI_PHAT_HEALTH_RECORD(firmware); if (self->guid != NULL) fu_xmlb_builder_insert_kv(bn, "guid", self->guid); if (self->device_path != NULL) fu_xmlb_builder_insert_kv(bn, "device_path", self->device_path); if (self->am_healthy != 0) fu_xmlb_builder_insert_kx(bn, "am_healthy", self->am_healthy); } static gboolean fu_acpi_phat_health_record_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuAcpiPhatHealthRecord *self = FU_ACPI_PHAT_HEALTH_RECORD(firmware); gsize bufsz = g_bytes_get_size(fw); guint16 rcdlen; guint32 dataoff; g_autoptr(GByteArray) st = NULL; /* sanity check record length */ st = fu_struct_acpi_phat_health_record_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; rcdlen = fu_struct_acpi_phat_health_record_get_rcdlen(st); if (rcdlen != bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "record length not valid: %" G_GUINT16_FORMAT, rcdlen); return FALSE; } self->am_healthy = fu_struct_acpi_phat_health_record_get_flags(st); self->guid = fwupd_guid_to_string(fu_struct_acpi_phat_health_record_get_device_signature(st), FWUPD_GUID_FLAG_MIXED_ENDIAN); /* device path */ dataoff = fu_struct_acpi_phat_health_record_get_device_specific_data(st); if (bufsz > 28) { gsize ubufsz; /* bytes */ g_autoptr(GBytes) ubuf = NULL; /* header -> devicepath -> data */ if (dataoff == 0x0) { ubufsz = bufsz - 28; } else { ubufsz = dataoff - 28; } if (ubufsz == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "device path not valid: %" G_GSIZE_FORMAT, ubufsz); return FALSE; } /* align and convert */ ubuf = fu_bytes_new_offset(fw, 28, ubufsz, error); if (ubuf == NULL) return FALSE; self->device_path = fu_utf16_to_utf8_bytes(ubuf, G_LITTLE_ENDIAN, error); if (self->device_path == NULL) return FALSE; } /* success */ return TRUE; } static GByteArray * fu_acpi_phat_health_record_write(FuFirmware *firmware, GError **error) { FuAcpiPhatHealthRecord *self = FU_ACPI_PHAT_HEALTH_RECORD(firmware); g_autoptr(GByteArray) st = fu_struct_acpi_phat_health_record_new(); /* convert device path ahead of time */ if (self->device_path != NULL) { g_autoptr(GByteArray) buf = fu_utf8_to_utf16_byte_array(self->device_path, G_LITTLE_ENDIAN, FU_UTF_CONVERT_FLAG_NONE, error); if (buf == NULL) return NULL; g_byte_array_append(st, buf->data, buf->len); } /* data record */ if (self->guid != NULL) { fwupd_guid_t guid = {0x0}; if (!fwupd_guid_from_string(self->guid, &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return NULL; fu_struct_acpi_phat_health_record_set_device_signature(st, &guid); } fu_struct_acpi_phat_health_record_set_rcdlen(st, st->len); fu_struct_acpi_phat_health_record_set_version(st, fu_firmware_get_version_raw(firmware)); fu_struct_acpi_phat_health_record_set_flags(st, self->am_healthy); /* success */ return g_steal_pointer(&st); } static void fu_acpi_phat_health_record_set_guid(FuAcpiPhatHealthRecord *self, const gchar *guid) { g_free(self->guid); self->guid = g_strdup(guid); } static void fu_acpi_phat_health_record_set_device_path(FuAcpiPhatHealthRecord *self, const gchar *device_path) { g_free(self->device_path); self->device_path = g_strdup(device_path); } static gboolean fu_acpi_phat_health_record_build(FuFirmware *firmware, XbNode *n, GError **error) { FuAcpiPhatHealthRecord *self = FU_ACPI_PHAT_HEALTH_RECORD(firmware); const gchar *tmp; guint64 tmp64; /* optional properties */ tmp = xb_node_query_text(n, "device_path", NULL); if (tmp != NULL) fu_acpi_phat_health_record_set_device_path(self, tmp); tmp = xb_node_query_text(n, "guid", NULL); if (tmp != NULL) fu_acpi_phat_health_record_set_guid(self, tmp); tmp64 = xb_node_query_text_as_uint(n, "am_healthy", NULL); if (tmp64 > G_MAXUINT8) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "am_healthy value invalid, got 0x%x", (guint)tmp64); return FALSE; } self->am_healthy = (guint8)tmp64; /* success */ return TRUE; } static void fu_acpi_phat_health_record_init(FuAcpiPhatHealthRecord *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 2000); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); } static void fu_acpi_phat_health_record_finalize(GObject *object) { FuAcpiPhatHealthRecord *self = FU_ACPI_PHAT_HEALTH_RECORD(object); g_free(self->guid); g_free(self->device_path); G_OBJECT_CLASS(fu_acpi_phat_health_record_parent_class)->finalize(object); } static void fu_acpi_phat_health_record_class_init(FuAcpiPhatHealthRecordClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_acpi_phat_health_record_finalize; klass_firmware->parse = fu_acpi_phat_health_record_parse; klass_firmware->write = fu_acpi_phat_health_record_write; klass_firmware->export = fu_acpi_phat_health_record_export; klass_firmware->build = fu_acpi_phat_health_record_build; } FuFirmware * fu_acpi_phat_health_record_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_ACPI_PHAT_HEALTH_RECORD, NULL)); } fwupd-1.9.16/plugins/acpi-phat/fu-acpi-phat-health-record.h000066400000000000000000000006461460375044200234450ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ACPI_PHAT_HEALTH_RECORD (fu_acpi_phat_health_record_get_type()) G_DECLARE_FINAL_TYPE(FuAcpiPhatHealthRecord, fu_acpi_phat_health_record, FU, ACPI_PHAT_HEALTH_RECORD, FuFirmware) FuFirmware * fu_acpi_phat_health_record_new(void); fwupd-1.9.16/plugins/acpi-phat/fu-acpi-phat-plugin.c000066400000000000000000000034431460375044200222130ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-phat-health-record.h" #include "fu-acpi-phat-plugin.h" #include "fu-acpi-phat-version-element.h" #include "fu-acpi-phat-version-record.h" #include "fu-acpi-phat.h" struct _FuAcpiPhatPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuAcpiPhatPlugin, fu_acpi_phat_plugin, FU_TYPE_PLUGIN) static gboolean fu_acpi_phat_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { g_autofree gchar *path = NULL; g_autofree gchar *fn = NULL; g_autofree gchar *str = NULL; g_autoptr(FuFirmware) phat = fu_acpi_phat_new(); g_autoptr(GBytes) blob = NULL; path = fu_path_from_kind(FU_PATH_KIND_ACPI_TABLES); fn = g_build_filename(path, "PHAT", NULL); blob = fu_bytes_get_contents(fn, error); if (blob == NULL) return FALSE; if (!fu_firmware_parse(phat, blob, FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return FALSE; str = fu_acpi_phat_to_report_string(FU_ACPI_PHAT(phat)); fu_plugin_add_report_metadata(plugin, "PHAT", str); return TRUE; } static void fu_acpi_phat_plugin_init(FuAcpiPhatPlugin *self) { } static void fu_acpi_phat_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_ACPI_PHAT); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_ACPI_PHAT_HEALTH_RECORD); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_ACPI_PHAT_VERSION_ELEMENT); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_ACPI_PHAT_VERSION_RECORD); } static void fu_acpi_phat_plugin_class_init(FuAcpiPhatPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_acpi_phat_plugin_constructed; plugin_class->coldplug = fu_acpi_phat_plugin_coldplug; } fwupd-1.9.16/plugins/acpi-phat/fu-acpi-phat-plugin.h000066400000000000000000000003601460375044200222130ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuAcpiPhatPlugin, fu_acpi_phat_plugin, FU, ACPI_PHAT_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/acpi-phat/fu-acpi-phat-version-element.c000066400000000000000000000103161460375044200240260ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-phat-struct.h" #include "fu-acpi-phat-version-element.h" struct _FuAcpiPhatVersionElement { FuFirmware parent_instance; gchar *guid; gchar *producer_id; }; G_DEFINE_TYPE(FuAcpiPhatVersionElement, fu_acpi_phat_version_element, FU_TYPE_FIRMWARE) static void fu_acpi_phat_version_element_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuAcpiPhatVersionElement *self = FU_ACPI_PHAT_VERSION_ELEMENT(firmware); if (self->guid != NULL) fu_xmlb_builder_insert_kv(bn, "guid", self->guid); if (self->producer_id != NULL) fu_xmlb_builder_insert_kv(bn, "producer_id", self->producer_id); } static gboolean fu_acpi_phat_version_element_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuAcpiPhatVersionElement *self = FU_ACPI_PHAT_VERSION_ELEMENT(firmware); g_autoptr(GByteArray) st = NULL; /* unpack */ st = fu_struct_acpi_phat_version_element_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; fu_firmware_set_size(firmware, st->len); self->guid = fwupd_guid_to_string(fu_struct_acpi_phat_version_element_get_component_id(st), FWUPD_GUID_FLAG_MIXED_ENDIAN); self->producer_id = fu_struct_acpi_phat_version_element_get_producer_id(st); fu_firmware_set_version_raw(firmware, fu_struct_acpi_phat_version_element_get_version_value(st)); return TRUE; } static GByteArray * fu_acpi_phat_version_element_write(FuFirmware *firmware, GError **error) { FuAcpiPhatVersionElement *self = FU_ACPI_PHAT_VERSION_ELEMENT(firmware); g_autoptr(GByteArray) st = fu_struct_acpi_phat_version_element_new(); /* pack */ if (self->guid != NULL) { fwupd_guid_t guid = {0x0}; if (!fwupd_guid_from_string(self->guid, &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return NULL; fu_struct_acpi_phat_version_element_set_component_id(st, &guid); } fu_struct_acpi_phat_version_element_set_version_value( st, fu_firmware_get_version_raw(firmware)); if (!fu_struct_acpi_phat_version_element_set_producer_id(st, self->producer_id, error)) return NULL; /* success */ return g_steal_pointer(&st); } static void fu_acpi_phat_version_element_set_guid(FuAcpiPhatVersionElement *self, const gchar *guid) { g_free(self->guid); self->guid = g_strdup(guid); } static void fu_acpi_phat_version_element_set_producer_id(FuAcpiPhatVersionElement *self, const gchar *producer_id) { g_free(self->producer_id); self->producer_id = g_strdup(producer_id); } static gboolean fu_acpi_phat_version_element_build(FuFirmware *firmware, XbNode *n, GError **error) { FuAcpiPhatVersionElement *self = FU_ACPI_PHAT_VERSION_ELEMENT(firmware); const gchar *tmp; /* optional properties */ tmp = xb_node_query_text(n, "producer_id", NULL); if (tmp != NULL) fu_acpi_phat_version_element_set_producer_id(self, tmp); tmp = xb_node_query_text(n, "guid", NULL); if (tmp != NULL) fu_acpi_phat_version_element_set_guid(self, tmp); /* success */ return TRUE; } static void fu_acpi_phat_version_element_init(FuAcpiPhatVersionElement *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 2000); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); } static void fu_acpi_phat_version_element_finalize(GObject *object) { FuAcpiPhatVersionElement *self = FU_ACPI_PHAT_VERSION_ELEMENT(object); g_free(self->guid); g_free(self->producer_id); G_OBJECT_CLASS(fu_acpi_phat_version_element_parent_class)->finalize(object); } static void fu_acpi_phat_version_element_class_init(FuAcpiPhatVersionElementClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_acpi_phat_version_element_finalize; klass_firmware->parse = fu_acpi_phat_version_element_parse; klass_firmware->write = fu_acpi_phat_version_element_write; klass_firmware->export = fu_acpi_phat_version_element_export; klass_firmware->build = fu_acpi_phat_version_element_build; } FuFirmware * fu_acpi_phat_version_element_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_ACPI_PHAT_VERSION_ELEMENT, NULL)); } fwupd-1.9.16/plugins/acpi-phat/fu-acpi-phat-version-element.h000066400000000000000000000006621460375044200240360ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ACPI_PHAT_VERSION_ELEMENT (fu_acpi_phat_version_element_get_type()) G_DECLARE_FINAL_TYPE(FuAcpiPhatVersionElement, fu_acpi_phat_version_element, FU, ACPI_PHAT_VERSION_ELEMENT, FuFirmware) FuFirmware * fu_acpi_phat_version_element_new(void); fwupd-1.9.16/plugins/acpi-phat/fu-acpi-phat-version-record.c000066400000000000000000000057531460375044200236640ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-phat-struct.h" #include "fu-acpi-phat-version-element.h" #include "fu-acpi-phat-version-record.h" #include "fu-acpi-phat.h" struct _FuAcpiPhatVersionRecord { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuAcpiPhatVersionRecord, fu_acpi_phat_version_record, FU_TYPE_FIRMWARE) static gboolean fu_acpi_phat_version_record_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { guint32 record_count = 0; g_autoptr(GByteArray) st = NULL; st = fu_struct_acpi_phat_version_record_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; record_count = fu_struct_acpi_phat_version_record_get_record_count(st); for (guint32 i = 0; i < record_count; i++) { g_autoptr(FuFirmware) firmware_tmp = fu_acpi_phat_version_element_new(); g_autoptr(GBytes) fw_tmp = NULL; fw_tmp = fu_bytes_new_offset(fw, offset + st->len, FU_STRUCT_ACPI_PHAT_VERSION_ELEMENT_SIZE, error); if (fw_tmp == NULL) return FALSE; fu_firmware_set_offset(firmware_tmp, offset + st->len); if (!fu_firmware_parse(firmware_tmp, fw_tmp, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return FALSE; if (!fu_firmware_add_image_full(firmware, firmware_tmp, error)) return FALSE; offset += fu_firmware_get_size(firmware_tmp); } return TRUE; } static GByteArray * fu_acpi_phat_version_record_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf2 = g_byte_array_new(); g_autoptr(GByteArray) st = fu_struct_acpi_phat_version_record_new(); g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); /* write each element so we get the image size */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) blob = fu_firmware_write(img, error); if (blob == NULL) return NULL; fu_byte_array_append_bytes(buf2, blob); } /* data record */ fu_struct_acpi_phat_version_record_set_rcdlen(st, st->len + buf2->len); fu_struct_acpi_phat_version_record_set_version(st, fu_firmware_get_version_raw(firmware)); fu_struct_acpi_phat_version_record_set_record_count(st, images->len); /* element data */ g_byte_array_append(st, buf2->data, buf2->len); return g_steal_pointer(&st); } static void fu_acpi_phat_version_record_init(FuAcpiPhatVersionRecord *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 2000); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); } static void fu_acpi_phat_version_record_class_init(FuAcpiPhatVersionRecordClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_acpi_phat_version_record_parse; klass_firmware->write = fu_acpi_phat_version_record_write; } FuFirmware * fu_acpi_phat_version_record_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_ACPI_PHAT_VERSION_RECORD, NULL)); } fwupd-1.9.16/plugins/acpi-phat/fu-acpi-phat-version-record.h000066400000000000000000000006541460375044200236640ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ACPI_PHAT_VERSION_RECORD (fu_acpi_phat_version_record_get_type()) G_DECLARE_FINAL_TYPE(FuAcpiPhatVersionRecord, fu_acpi_phat_version_record, FU, ACPI_PHAT_VERSION_RECORD, FuFirmware) FuFirmware * fu_acpi_phat_version_record_new(void); fwupd-1.9.16/plugins/acpi-phat/fu-acpi-phat.c000066400000000000000000000230211460375044200207110ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-acpi-phat-health-record.h" #include "fu-acpi-phat-version-record.h" #include "fu-acpi-phat.h" struct _FuAcpiPhat { FuFirmware parent_instance; gchar *oem_id; }; G_DEFINE_TYPE(FuAcpiPhat, fu_acpi_phat, FU_TYPE_FIRMWARE) static void fu_acpi_phat_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuAcpiPhat *self = FU_ACPI_PHAT(firmware); if (self->oem_id != NULL) fu_xmlb_builder_insert_kv(bn, "oem_id", self->oem_id); } static gboolean fu_acpi_phat_record_parse(FuFirmware *firmware, GBytes *fw, gsize *offset, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0; guint16 record_length = 0; guint16 record_type = 0; guint8 revision; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(FuFirmware) firmware_rcd = NULL; /* common header */ if (!fu_memread_uint16_safe(buf, bufsz, *offset, &record_type, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(buf, bufsz, *offset + 2, &record_length, G_LITTLE_ENDIAN, error)) return FALSE; if (record_length < 5) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "PHAT record length invalid, got 0x%x", record_length); return FALSE; } if (!fu_memread_uint8_safe(buf, bufsz, *offset + 4, &revision, error)) return FALSE; /* firmware version data record */ if (record_type == FU_ACPI_PHAT_RECORD_TYPE_VERSION) { firmware_rcd = fu_acpi_phat_version_record_new(); } else if (record_type == FU_ACPI_PHAT_RECORD_TYPE_HEALTH) { firmware_rcd = fu_acpi_phat_health_record_new(); } /* supported record type */ if (firmware_rcd != NULL) { g_autoptr(GBytes) fw_tmp = NULL; fw_tmp = fu_bytes_new_offset(fw, *offset, record_length, error); if (fw_tmp == NULL) return FALSE; fu_firmware_set_size(firmware_rcd, record_length); fu_firmware_set_offset(firmware_rcd, *offset); fu_firmware_set_version_raw(firmware_rcd, revision); if (!fu_firmware_parse(firmware_rcd, fw_tmp, flags, error)) return FALSE; if (!fu_firmware_add_image_full(firmware, firmware_rcd, error)) return FALSE; } *offset += record_length; return TRUE; } static void fu_acpi_phat_set_oem_id(FuAcpiPhat *self, const gchar *oem_id) { g_free(self->oem_id); self->oem_id = g_strdup(oem_id); } static gboolean fu_acpi_phat_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { const guint8 magic[4] = "PHAT"; return fu_memcmp_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset, magic, sizeof(magic), 0x0, sizeof(magic), error); } static gboolean fu_acpi_phat_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuAcpiPhat *self = FU_ACPI_PHAT(firmware); gchar oem_id[6] = {'\0'}; gchar oem_table_id[8] = {'\0'}; gsize bufsz = 0; guint32 length = 0; guint32 oem_revision = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autofree gchar *oem_id_safe = NULL; g_autofree gchar *oem_table_id_safe = NULL; /* parse table */ if (!fu_memread_uint32_safe(buf, bufsz, offset + 4, &length, G_LITTLE_ENDIAN, error)) return FALSE; if (bufsz < length) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "PHAT table invalid size, got 0x%x, expected 0x%x", (guint)bufsz, length); return FALSE; } /* spec revision */ if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { guint8 revision = 0; if (!fu_memread_uint8_safe(buf, bufsz, offset + 8, &revision, error)) return FALSE; if (revision != FU_ACPI_PHAT_REVISION) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "PHAT table revision invalid, got 0x%x, expected 0x%x", revision, (guint)FU_ACPI_PHAT_REVISION); return FALSE; } } /* verify checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint8 checksum = fu_sum8(buf, length); if (checksum != 0x00) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "PHAT table checksum invalid, got 0x%x", checksum); return FALSE; } } /* OEMID */ if (!fu_memcpy_safe((guint8 *)oem_id, sizeof(oem_id), 0x0, /* dst */ buf, bufsz, offset + 10, /* src */ sizeof(oem_id), error)) return FALSE; oem_id_safe = fu_strsafe((const gchar *)oem_id, sizeof(oem_id)); fu_acpi_phat_set_oem_id(self, oem_id_safe); /* OEM Table ID */ if (!fu_memcpy_safe((guint8 *)oem_table_id, sizeof(oem_table_id), 0x0, /* dst */ buf, bufsz, offset + 16, /* src */ sizeof(oem_table_id), error)) return FALSE; oem_table_id_safe = fu_strsafe((const gchar *)oem_table_id, sizeof(oem_table_id)); fu_firmware_set_id(firmware, oem_table_id_safe); if (!fu_memread_uint32_safe(buf, bufsz, offset + 24, &oem_revision, G_LITTLE_ENDIAN, error)) return FALSE; fu_firmware_set_version_raw(firmware, oem_revision); /* platform telemetry records */ for (gsize offset_tmp = offset + 36; offset_tmp < length;) { if (!fu_acpi_phat_record_parse(firmware, fw, &offset_tmp, flags, error)) return FALSE; } /* success */ return TRUE; } static GByteArray * fu_acpi_phat_write(FuFirmware *firmware, GError **error) { FuAcpiPhat *self = FU_ACPI_PHAT(firmware); const gchar *oem_table_id_str = fu_firmware_get_id(firmware); guint8 creator_id[] = {'F', 'W', 'U', 'P'}; guint8 creator_rev[] = {'0', '0', '0', '0'}; guint8 oem_id[6] = {'\0'}; guint8 oem_table_id[8] = {'\0'}; guint8 signature[] = {'P', 'H', 'A', 'T'}; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) buf2 = g_byte_array_new(); g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); /* write each image so we get the total size */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) blob = fu_firmware_write(img, error); if (blob == NULL) return NULL; fu_byte_array_append_bytes(buf2, blob); } /* header */ g_byte_array_append(buf, signature, sizeof(signature)); fu_byte_array_append_uint32(buf, buf2->len + 36, G_LITTLE_ENDIAN); fu_byte_array_append_uint8(buf, fu_firmware_get_version_raw(firmware)); fu_byte_array_append_uint8(buf, 0xFF); /* will fixup */ if (self->oem_id != NULL) { gsize oem_id_strlen = strlen(self->oem_id); if (!fu_memcpy_safe(oem_id, sizeof(oem_id), 0x0, /* dst */ (const guint8 *)self->oem_id, oem_id_strlen, 0x0, /* src */ oem_id_strlen, error)) return NULL; } g_byte_array_append(buf, oem_id, sizeof(oem_id)); if (oem_table_id_str != NULL) { gsize oem_table_id_strlen = strlen(oem_table_id_str); if (!fu_memcpy_safe(oem_table_id, sizeof(oem_table_id), 0x0, /* dst */ (const guint8 *)oem_table_id_str, oem_table_id_strlen, 0x0, /* src */ oem_table_id_strlen, error)) return NULL; } g_byte_array_append(buf, oem_table_id, sizeof(oem_table_id)); fu_byte_array_append_uint32(buf, fu_firmware_get_version_raw(firmware), G_LITTLE_ENDIAN); g_byte_array_append(buf, creator_id, sizeof(creator_id)); g_byte_array_append(buf, creator_rev, sizeof(creator_rev)); g_byte_array_append(buf, buf2->data, buf2->len); /* fixup checksum */ buf->data[9] = 0xFF - fu_sum8(buf->data, buf->len); /* success */ return g_steal_pointer(&buf); } static gboolean fu_acpi_phat_build(FuFirmware *firmware, XbNode *n, GError **error) { FuAcpiPhat *self = FU_ACPI_PHAT(firmware); const gchar *tmp; /* optional properties */ tmp = xb_node_query_text(n, "oem_id", NULL); if (tmp != NULL) fu_acpi_phat_set_oem_id(self, tmp); /* success */ return TRUE; } static gboolean fu_acpi_phat_to_report_string_cb(XbBuilderNode *bn, gpointer user_data) { if (g_strcmp0(xb_builder_node_get_element(bn), "offset") == 0 || g_strcmp0(xb_builder_node_get_element(bn), "flags") == 0 || g_strcmp0(xb_builder_node_get_element(bn), "size") == 0) xb_builder_node_add_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE); return FALSE; } gchar * fu_acpi_phat_to_report_string(FuAcpiPhat *self) { g_autoptr(XbBuilderNode) bn = xb_builder_node_new("firmware"); fu_firmware_export(FU_FIRMWARE(self), FU_FIRMWARE_EXPORT_FLAG_NONE, bn); xb_builder_node_traverse(bn, G_PRE_ORDER, G_TRAVERSE_ALL, 3, fu_acpi_phat_to_report_string_cb, NULL); return xb_builder_node_export(bn, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE | XB_NODE_EXPORT_FLAG_FORMAT_INDENT, NULL); } static void fu_acpi_phat_init(FuAcpiPhat *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 2000); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); } static void fu_acpi_phat_finalize(GObject *object) { FuAcpiPhat *self = FU_ACPI_PHAT(object); g_free(self->oem_id); G_OBJECT_CLASS(fu_acpi_phat_parent_class)->finalize(object); } static void fu_acpi_phat_class_init(FuAcpiPhatClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_acpi_phat_finalize; klass_firmware->check_magic = fu_acpi_phat_check_magic; klass_firmware->parse = fu_acpi_phat_parse; klass_firmware->write = fu_acpi_phat_write; klass_firmware->export = fu_acpi_phat_export; klass_firmware->build = fu_acpi_phat_build; } FuFirmware * fu_acpi_phat_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_ACPI_PHAT, NULL)); } fwupd-1.9.16/plugins/acpi-phat/fu-acpi-phat.h000066400000000000000000000007671460375044200207320ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ACPI_PHAT (fu_acpi_phat_get_type()) G_DECLARE_FINAL_TYPE(FuAcpiPhat, fu_acpi_phat, FU, ACPI_PHAT, FuFirmware) #define FU_ACPI_PHAT_RECORD_TYPE_VERSION 0x0000 #define FU_ACPI_PHAT_RECORD_TYPE_HEALTH 0x0001 #define FU_ACPI_PHAT_REVISION 0x01 FuFirmware * fu_acpi_phat_new(void); gchar * fu_acpi_phat_to_report_string(FuAcpiPhat *self); fwupd-1.9.16/plugins/acpi-phat/fu-acpi-phat.rs000066400000000000000000000011671460375044200211220ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, ParseBytes)] struct AcpiPhatHealthRecord { signature: u16le = 0x1, rcdlen: u16le, version: u8, reserved: [u8; 2], flags: u8, device_signature: Guid, device_specific_data: u32le, } #[derive(New, ParseBytes)] struct AcpiPhatVersionElement { component_id: Guid, version_value: u64le, producer_id: [char; 4], } #[derive(New, ParseBytes)] struct AcpiPhatVersionRecord { signature: u16le = 0x0, rcdlen: u16le, version: u8, reserved: [u8; 3], record_count: u32le, } fwupd-1.9.16/plugins/acpi-phat/fu-self-test.c000066400000000000000000000023761460375044200207630ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-phat.h" static void fu_acpi_phat_parse_func(void) { gboolean ret; g_autoptr(FuFirmware) phat = fu_acpi_phat_new(); g_autoptr(GError) error = NULL; g_autoptr(GBytes) blob = NULL; g_autofree gchar *str = NULL; g_autofree gchar *fn = NULL; fn = g_test_build_filename(G_TEST_DIST, "tests", "PHAT", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS)) { g_test_skip("missing PHAT"); return; } blob = fu_bytes_get_contents(fn, &error); g_assert_no_error(error); g_assert_nonnull(blob); ret = fu_firmware_parse(phat, blob, FWUPD_INSTALL_FLAG_FORCE | FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); str = fu_acpi_phat_to_report_string(FU_ACPI_PHAT(phat)); g_print("%s\n", str); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); (void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE); /* tests go here */ g_test_add_func("/acpi-phat/parse", fu_acpi_phat_parse_func); return g_test_run(); } fwupd-1.9.16/plugins/acpi-phat/meson.build000066400000000000000000000025431460375044200204370ustar00rootroot00000000000000if get_option('plugin_acpi_phat').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginAcpiPhat"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtin_acpi_phat = static_library('fu_plugin_acpi_phat', rustgen.process( 'fu-acpi-phat.rs', # fuzzing ), sources: [ 'fu-acpi-phat-plugin.c', 'fu-acpi-phat.c', # fuzzing 'fu-acpi-phat-health-record.c', # fuzzing 'fu-acpi-phat-version-element.c', # fuzzing 'fu-acpi-phat-version-record.c', # fuzzing ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_acpi_phat if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'acpi-phat-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_acpi_phat, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('acpi-phat-self-test', e, env: env) # added to installed-tests endif endif fwupd-1.9.16/plugins/acpi-phat/tests/000077500000000000000000000000001460375044200174335ustar00rootroot00000000000000fwupd-1.9.16/plugins/acpi-phat/tests/acpi-phat.bin000066400000000000000000000003101460375044200217650ustar00rootroot00000000000000PHATHUGHESSUDODAVEFWUP0000D3@fN2Ą#HUGHൂ dzG@Om$LENO(KuRF31%DELL ,KuRF31/dev/foofwupd-1.9.16/plugins/acpi-phat/tests/acpi-phat.builder.xml000066400000000000000000000022761460375044200234570ustar00rootroot00000000000000 SUDODAVE 0x1 HUGHES 0x1 0x123 40338ceb-b966-4eae-adae-9c32edfcc484 HUGH 0x124 2082b5e0-7a64-478a-b1b2-e3404fab6dad LENO 0x1 0x125 dfbaaded-754b-5214-a5f2-46aa3331e8ce DELL 0x1 0x1 0x1 dfbaaded-754b-5214-a5f2-46aa3331e8ce /dev/foo fwupd-1.9.16/plugins/algoltek-usb/000077500000000000000000000000001460375044200170145ustar00rootroot00000000000000fwupd-1.9.16/plugins/algoltek-usb/README.md000066400000000000000000000025421460375044200202760ustar00rootroot00000000000000--- title: Plugin: Algoltek USB --- ## Introduction This plugin supports the firmware upgrade of DisplayPort over USB-C to HDMI converter provided by Algoltek, Inc. These DisplayPort over USB-C to HDMI converters can be updated through multiple interfaces, but this plugin is only designed for the USB interface. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `tw.com.algoltek.usb` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_25A4&PID_9311` * `USB\VID_25A4&PID_9312` * `USB\VID_25A4&PID_9313` * `USB\VID_25A4&PID_9411` * `USB\VID_25A4&PID_9412` * `USB\VID_25A4&PID_9421` ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been programmed. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x25A4`. ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.9.11`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: Mason Lyu: @MasonLyu fwupd-1.9.16/plugins/algoltek-usb/algoltek-usb.quirk000066400000000000000000000005171460375044200224650ustar00rootroot00000000000000# AG9311 [USB\VID_25A4&PID_9311] Plugin = algoltek_usb # AG9312 [USB\VID_25A4&PID_9312] Plugin = algoltek_usb # AG9313 [USB\VID_25A4&PID_9313] Plugin = algoltek_usb # AG9411 [USB\VID_25A4&PID_9411] Plugin = algoltek_usb # AG9412 [USB\VID_25A4&PID_9412] Plugin = algoltek_usb # AG9421 [USB\VID_25A4&PID_9421] Plugin = algoltek_usb fwupd-1.9.16/plugins/algoltek-usb/fu-algoltek-usb-common.h000066400000000000000000000005371460375044200234610ustar00rootroot00000000000000/* * Copyright (C) 2024 Algoltek, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define ALGOLTEK_DEVICE_USB_TIMEOUT 3000 /* ms */ #define AG_ISP_ADDR 0x2000 #define AG_ISP_SIZE 0x1000 #define AG_FIRMWARE_SIZE 0x20000 #define AG_UPDATE_STATUS 0x860C #define AG_UPDATE_PASS 1 #define AG_UPDATE_FAIL 2 fwupd-1.9.16/plugins/algoltek-usb/fu-algoltek-usb-device.c000066400000000000000000000355311460375044200234250ustar00rootroot00000000000000/* * Copyright (C) 2024 Algoltek, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-algoltek-usb-common.h" #include "fu-algoltek-usb-device.h" #include "fu-algoltek-usb-firmware.h" #include "fu-algoltek-usb-struct.h" struct _FuAlgoltekUsbDevice { FuUsbDevice parent_instance; }; G_DEFINE_TYPE(FuAlgoltekUsbDevice, fu_algoltek_usb_device, FU_TYPE_USB_DEVICE) static gboolean fu_algoltek_usb_device_ctrl_transfer(FuAlgoltekUsbDevice *self, GUsbDeviceDirection direction, FuAlgoltekCmd algoltek_cmd, guint16 value, guint16 index, GByteArray *buf, guint8 len, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); if (!g_usb_device_control_transfer(usb_device, direction, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_INTERFACE, algoltek_cmd, value, index, buf->data, len, NULL, ALGOLTEK_DEVICE_USB_TIMEOUT, NULL, error)) return FALSE; return TRUE; } static GByteArray * fu_algoltek_usb_device_rdr(FuAlgoltekUsbDevice *self, int address, GError **error) { g_autoptr(GByteArray) st = fu_struct_algoltek_cmd_address_pkt_new(); fu_struct_algoltek_cmd_address_pkt_set_len(st, 5); fu_struct_algoltek_cmd_address_pkt_set_cmd(st, FU_ALGOLTEK_CMD_RDR); fu_struct_algoltek_cmd_address_pkt_set_address(st, address); fu_struct_algoltek_cmd_address_pkt_set_checksum(st, ~fu_sum8(st->data, st->len) + 1); if (!fu_algoltek_usb_device_ctrl_transfer(self, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, FU_ALGOLTEK_CMD_RDR, address, 0xFFFF, st, st->len, error)) return NULL; /* success */ return g_steal_pointer(&st); } static GByteArray * fu_algoltek_usb_device_rdv(FuAlgoltekUsbDevice *self, GError **error) { guint16 version_prefix; g_autoptr(GByteArray) st = fu_struct_algoltek_cmd_transfer_pkt_new(); g_autoptr(GByteArray) version_data = g_byte_array_new(); fu_struct_algoltek_cmd_transfer_pkt_set_len(st, 3); fu_struct_algoltek_cmd_transfer_pkt_set_cmd(st, FU_ALGOLTEK_CMD_RDV); fu_struct_algoltek_cmd_transfer_pkt_set_checksum(st, ~fu_sum8(st->data, st->len) + 1); if (!fu_algoltek_usb_device_ctrl_transfer(self, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, FU_ALGOLTEK_CMD_RDV, 0xFFFF, 0xFFFF, st, st->len, error)) return NULL; if (!fu_memread_uint16_safe(st->data, st->len, 2, &version_prefix, G_BIG_ENDIAN, error)) return NULL; if (version_prefix == 0x4147) { guint8 underscore_count = 0; /* remove len, cmd bytes and "AG" prefixes */ for (guint32 i = 4; i < st->len; i++) { if (st->data[i] == '_') { underscore_count++; if (underscore_count == 1) continue; } if (underscore_count > 2) break; if (underscore_count > 0) fu_byte_array_append_uint8(version_data, st->data[i]); } } else { /* remove len and cmd bytes */ for (guint32 i = 2; i < st->len; i++) { if (st->data[i] < 128) fu_byte_array_append_uint8(version_data, st->data[i]); } } /* success */ return g_steal_pointer(&version_data); } static gboolean fu_algoltek_usb_device_en(FuAlgoltekUsbDevice *self, GError **error) { g_autoptr(GByteArray) st = fu_struct_algoltek_cmd_address_pkt_new(); fu_struct_algoltek_cmd_address_pkt_set_len(st, 3); fu_struct_algoltek_cmd_address_pkt_set_cmd(st, FU_ALGOLTEK_CMD_EN); fu_struct_algoltek_cmd_address_pkt_set_checksum(st, ~fu_sum8(st->data, st->len) + 1); if (!fu_algoltek_usb_device_ctrl_transfer(self, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, FU_ALGOLTEK_CMD_EN, 0, 0, st, st->data[0], error)) { g_prefix_error(error, "system activation failure: "); return FALSE; } return TRUE; } static gboolean fu_algoltek_usb_device_rst(FuAlgoltekUsbDevice *self, guint16 address, GError **error) { g_autoptr(GByteArray) st = fu_struct_algoltek_cmd_address_pkt_new(); fu_struct_algoltek_cmd_address_pkt_set_len(st, 4); fu_struct_algoltek_cmd_address_pkt_set_cmd(st, FU_ALGOLTEK_CMD_RST); fu_struct_algoltek_cmd_address_pkt_set_address(st, address); fu_struct_algoltek_cmd_address_pkt_set_checksum(st, ~fu_sum8(st->data, st->len) + 1); if (!fu_algoltek_usb_device_ctrl_transfer(self, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, FU_ALGOLTEK_CMD_RST, 0, 0, st, st->data[0], error)) { g_prefix_error(error, "system reboot failure: "); return FALSE; } return TRUE; } static gboolean fu_algoltek_usb_device_wrr(FuAlgoltekUsbDevice *self, int address, int value, GError **error) { g_autoptr(GByteArray) st = fu_struct_algoltek_cmd_address_pkt_new(); fu_struct_algoltek_cmd_address_pkt_set_len(st, 7); fu_struct_algoltek_cmd_address_pkt_set_cmd(st, FU_ALGOLTEK_CMD_WRR); fu_struct_algoltek_cmd_address_pkt_set_address(st, address); fu_struct_algoltek_cmd_address_pkt_set_value(st, value); fu_struct_algoltek_cmd_address_pkt_set_checksum(st, ~fu_sum8(st->data, st->len) + 1); if (!fu_algoltek_usb_device_ctrl_transfer(self, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, FU_ALGOLTEK_CMD_WRR, 0, 0, st, st->data[0], error)) { g_prefix_error(error, "data write failure: "); return FALSE; } return TRUE; } static gboolean fu_algoltek_usb_device_isp(FuAlgoltekUsbDevice *self, GBytes *blob_isp, guint address, FuProgress *progress, GError **error) { const guint8 *isp_data = NULL; gsize isp_data_size = 0; guint8 basic_data_size = 5; g_autoptr(GPtrArray) chunks_isp = NULL; isp_data = g_bytes_get_data(blob_isp, &isp_data_size); chunks_isp = fu_chunk_array_new(isp_data, isp_data_size, address, 0x0, 64 - basic_data_size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks_isp->len); for (guint i = 0; i < chunks_isp->len; i++) { FuChunk *chk = g_ptr_array_index(chunks_isp, i); g_autoptr(GByteArray) st = fu_struct_algoltek_cmd_transfer_pkt_new(); fu_struct_algoltek_cmd_transfer_pkt_set_len(st, basic_data_size + fu_chunk_get_data_sz(chk)); fu_struct_algoltek_cmd_transfer_pkt_set_cmd(st, FU_ALGOLTEK_CMD_ISP); fu_struct_algoltek_cmd_transfer_pkt_set_address(st, fu_chunk_get_address(chk)); if (!fu_struct_algoltek_cmd_transfer_pkt_set_data(st, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "assign isp data failure: "); return FALSE; } fu_struct_algoltek_cmd_transfer_pkt_set_checksum(st, ~fu_sum8(st->data, st->len) + 1); if (!fu_algoltek_usb_device_ctrl_transfer(self, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, FU_ALGOLTEK_CMD_ISP, 0, 0, st, st->data[0], error)) { g_prefix_error(error, "isp failure: "); return FALSE; } fu_progress_step_done(progress); } return TRUE; } static gboolean fu_algoltek_usb_device_bot(FuAlgoltekUsbDevice *self, int address, GError **error) { g_autoptr(GByteArray) st = fu_struct_algoltek_cmd_address_pkt_new(); fu_struct_algoltek_cmd_address_pkt_set_len(st, 5); fu_struct_algoltek_cmd_address_pkt_set_cmd(st, FU_ALGOLTEK_CMD_BOT); fu_struct_algoltek_cmd_address_pkt_set_address(st, address); fu_struct_algoltek_cmd_address_pkt_set_checksum(st, ~fu_sum8(st->data, st->len) + 1); if (!fu_algoltek_usb_device_ctrl_transfer(self, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, FU_ALGOLTEK_CMD_BOT, 0, 0, st, st->data[0], error)) { g_prefix_error(error, "system boot failure: "); return FALSE; } return TRUE; } static gboolean fu_algoltek_usb_device_ers(FuAlgoltekUsbDevice *self, GError **error) { g_autoptr(GByteArray) st = fu_struct_algoltek_cmd_address_pkt_new(); fu_struct_algoltek_cmd_address_pkt_set_len(st, 3); fu_struct_algoltek_cmd_address_pkt_set_cmd(st, FU_ALGOLTEK_CMD_ERS); fu_struct_algoltek_cmd_address_pkt_set_checksum(st, ~fu_sum8(st->data, st->len) + 1); if (!fu_algoltek_usb_device_ctrl_transfer(self, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, FU_ALGOLTEK_CMD_ERS, 0, 0, st, st->len, error)) { g_prefix_error(error, "data clear failure: "); return FALSE; } return TRUE; } static gboolean fu_algoltek_usb_device_status_check_cb(FuDevice *self, gpointer user_data, GError **error) { guint8 update_status; g_autoptr(GByteArray) update_status_array = g_byte_array_new(); update_status_array = fu_algoltek_usb_device_rdr(FU_ALGOLTEK_USB_DEVICE(self), AG_UPDATE_STATUS, error); if (update_status_array == NULL) return FALSE; update_status = update_status_array->data[0]; switch (update_status) { case AG_UPDATE_PASS: break; case AG_UPDATE_FAIL: default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "update procedure is failed."); return FALSE; } return TRUE; } static gboolean fu_algoltek_usb_device_wrf(FuAlgoltekUsbDevice *self, GBytes *blob_payload, FuProgress *progress, GError **error) { const guint8 *fw_data = NULL; gsize fw_data_size = 0; guint16 value; guint16 index; g_autoptr(GByteArray) buf_parameter = g_byte_array_new(); g_autoptr(GPtrArray) chunks_payload = NULL; fw_data = g_bytes_get_data(blob_payload, &fw_data_size); chunks_payload = fu_chunk_array_new(fw_data, fw_data_size, 0x0, 0x0, 64); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks_payload->len); for (guint i = 0; i < chunks_payload->len; i++) { FuChunk *chk = g_ptr_array_index(chunks_payload, i); g_autoptr(GByteArray) buf = g_byte_array_new(); g_byte_array_append(buf, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); fu_byte_array_set_size(buf_parameter, 4, 0); if ((i + 1) % 4 == 0) buf_parameter->data[0] = 1; else buf_parameter->data[0] = 0; fu_memwrite_uint24(buf_parameter->data + 1, fu_chunk_get_address(chk), G_BIG_ENDIAN); value = fu_memread_uint16(buf_parameter->data, G_BIG_ENDIAN); index = fu_memread_uint16(buf_parameter->data + 2, G_BIG_ENDIAN); if (!fu_algoltek_usb_device_ctrl_transfer(self, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, FU_ALGOLTEK_CMD_WRF, value, index, buf, buf->len, error)) { g_prefix_error(error, "data write failure: "); return FALSE; } if ((i + 1) % 4 == 0 || (i + 1) == chunks_payload->len) { if (!fu_device_retry_full(FU_DEVICE(self), fu_algoltek_usb_device_status_check_cb, 10, 0, NULL, error)) return FALSE; } fu_progress_step_done(progress); } return TRUE; } static gboolean fu_algoltek_usb_device_setup(FuDevice *device, GError **error) { FuAlgoltekUsbDevice *self = FU_ALGOLTEK_USB_DEVICE(device); g_autofree gchar *version_str = NULL; g_autoptr(GByteArray) version_data = NULL; /* UsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_algoltek_usb_device_parent_class)->setup(device, error)) return FALSE; version_data = fu_algoltek_usb_device_rdv(self, error); version_str = g_strdup_printf("%s", version_data->data); fu_device_set_version(device, version_str); /* success */ return TRUE; } static gboolean fu_algoltek_usb_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuAlgoltekUsbDevice *self = FU_ALGOLTEK_USB_DEVICE(device); g_autoptr(GBytes) blob_isp = NULL; g_autoptr(GBytes) blob_payload = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 18, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 2, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80, NULL); if (!fu_algoltek_usb_device_en(self, error)) return FALSE; if (!fu_algoltek_usb_device_rst(self, 0x200, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), 900); if (!fu_algoltek_usb_device_wrr(self, 0x80AD, 0, error)) return FALSE; if (!fu_algoltek_usb_device_wrr(self, 0x80C0, 0, error)) return FALSE; if (!fu_algoltek_usb_device_wrr(self, 0x80C9, 0, error)) return FALSE; if (!fu_algoltek_usb_device_wrr(self, 0x80D1, 0, error)) return FALSE; if (!fu_algoltek_usb_device_wrr(self, 0x80D9, 0, error)) return FALSE; if (!fu_algoltek_usb_device_wrr(self, 0x80E1, 0, error)) return FALSE; if (!fu_algoltek_usb_device_wrr(self, 0x80E9, 0, error)) return FALSE; if (!fu_algoltek_usb_device_rst(self, 0, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), 500); /* get ISP image */ blob_isp = fu_firmware_get_image_by_id_bytes(firmware, "isp", error); if (blob_isp == NULL) return FALSE; if (!fu_algoltek_usb_device_isp(self, blob_isp, AG_ISP_ADDR, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); if (!fu_algoltek_usb_device_bot(self, AG_ISP_ADDR, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), 1000); if (!fu_algoltek_usb_device_ers(self, error)) return FALSE; fu_progress_step_done(progress); fu_device_sleep(FU_DEVICE(self), 500); /* get payload image */ blob_payload = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_PAYLOAD, error); if (blob_payload == NULL) return FALSE; if (!fu_algoltek_usb_device_wrf(self, blob_payload, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); if (!fu_algoltek_usb_device_rst(self, 0x100, error)) return FALSE; /* the device automatically reboots */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* success! */ return TRUE; } static void fu_algoltek_usb_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_algoltek_usb_device_init(FuAlgoltekUsbDevice *self) { fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_add_protocol(FU_DEVICE(self), "tw.com.algoltek.usb"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_ALGOLTEK_USB_FIRMWARE); fu_device_set_remove_delay(FU_DEVICE(self), 10000); } static void fu_algoltek_usb_device_class_init(FuAlgoltekUsbDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_algoltek_usb_device_setup; klass_device->write_firmware = fu_algoltek_usb_device_write_firmware; klass_device->set_progress = fu_algoltek_usb_device_set_progress; } fwupd-1.9.16/plugins/algoltek-usb/fu-algoltek-usb-device.h000066400000000000000000000005121460375044200234210ustar00rootroot00000000000000/* * Copyright (C) 2024 Algoltek, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ALGOLTEK_USB_DEVICE (fu_algoltek_usb_device_get_type()) G_DECLARE_FINAL_TYPE(FuAlgoltekUsbDevice, fu_algoltek_usb_device, FU, ALGOLTEK_USB_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/algoltek-usb/fu-algoltek-usb-firmware.c000066400000000000000000000056101460375044200237750ustar00rootroot00000000000000/* * Copyright (C) 2024 Algoltek, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-algoltek-usb-common.h" #include "fu-algoltek-usb-firmware.h" #include "fu-algoltek-usb-struct.h" struct _FuAlgoltekUsbFirmware { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuAlgoltekUsbFirmware, fu_algoltek_usb_firmware, FU_TYPE_FIRMWARE) static gboolean fu_algoltek_usb_firmware_validate(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_algoltek_product_identity_validate_bytes(fw, offset, error); } static gboolean fu_algoltek_usb_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { gchar *version; g_autoptr(FuFirmware) img_isp = fu_firmware_new(); g_autoptr(FuFirmware) img_payload = fu_firmware_new(); g_autoptr(GBytes) blob_isp = NULL; g_autoptr(GBytes) blob_payload = NULL; g_autoptr(GByteArray) header_array = g_byte_array_new(); g_autoptr(GBytes) blob_header = NULL; blob_header = fu_bytes_new_offset(fw, offset, FU_STRUCT_ALGOLTEK_PRODUCT_IDENTITY_SIZE, error); fu_byte_array_append_bytes(header_array, blob_header); version = fu_struct_algoltek_product_identity_get_version(header_array); offset += FU_STRUCT_ALGOLTEK_PRODUCT_IDENTITY_SIZE; blob_isp = fu_bytes_new_offset(fw, offset, AG_ISP_SIZE, error); if (blob_isp == NULL) return FALSE; fu_firmware_set_bytes(img_isp, blob_isp); fu_firmware_set_id(img_isp, "isp"); fu_firmware_add_image(firmware, img_isp); offset += g_bytes_get_size(blob_isp); blob_payload = fu_bytes_new_offset(fw, offset, AG_FIRMWARE_SIZE, error); if (blob_payload == NULL) return FALSE; fu_firmware_set_version(img_payload, version); fu_firmware_set_bytes(img_payload, blob_payload); fu_firmware_set_id(img_payload, FU_FIRMWARE_ID_PAYLOAD); fu_firmware_add_image(firmware, img_payload); return TRUE; } static GByteArray * fu_algoltek_usb_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) blob_isp = NULL; g_autoptr(GBytes) blob_payload = NULL; blob_isp = fu_firmware_get_image_by_id_bytes(firmware, "isp", error); if (blob_isp == NULL) return NULL; fu_byte_array_append_bytes(buf, blob_isp); blob_payload = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_PAYLOAD, error); if (blob_payload == NULL) return NULL; fu_byte_array_append_bytes(buf, blob_payload); return g_steal_pointer(&buf); } static void fu_algoltek_usb_firmware_init(FuAlgoltekUsbFirmware *self) { } static void fu_algoltek_usb_firmware_class_init(FuAlgoltekUsbFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_algoltek_usb_firmware_validate; klass_firmware->parse = fu_algoltek_usb_firmware_parse; klass_firmware->write = fu_algoltek_usb_firmware_write; } fwupd-1.9.16/plugins/algoltek-usb/fu-algoltek-usb-firmware.h000066400000000000000000000005231460375044200240000ustar00rootroot00000000000000/* * Copyright (C) 2024 Algoltek, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ALGOLTEK_USB_FIRMWARE (fu_algoltek_usb_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuAlgoltekUsbFirmware, fu_algoltek_usb_firmware, FU, ALGOLTEK_USB_FIRMWARE, FuFirmware) fwupd-1.9.16/plugins/algoltek-usb/fu-algoltek-usb-plugin.c000066400000000000000000000015521460375044200234600ustar00rootroot00000000000000/* * Copyright (C) 2024 Algoltek, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-algoltek-usb-device.h" #include "fu-algoltek-usb-firmware.h" #include "fu-algoltek-usb-plugin.h" struct _FuAlgoltekUsbPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuAlgoltekUsbPlugin, fu_algoltek_usb_plugin, FU_TYPE_PLUGIN) static void fu_algoltek_usb_plugin_init(FuAlgoltekUsbPlugin *self) { } static void fu_algoltek_usb_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_ALGOLTEK_USB_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_ALGOLTEK_USB_FIRMWARE); } static void fu_algoltek_usb_plugin_class_init(FuAlgoltekUsbPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_algoltek_usb_plugin_constructed; } fwupd-1.9.16/plugins/algoltek-usb/fu-algoltek-usb-plugin.h000066400000000000000000000003431460375044200234620ustar00rootroot00000000000000/* * Copyright (C) 2024 Algoltek, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuAlgoltekUsbPlugin, fu_algoltek_usb_plugin, FU, ALGOLTEK_USB_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/algoltek-usb/fu-algoltek-usb.rs000066400000000000000000000014061460375044200223640ustar00rootroot00000000000000// Copyright (C) 2024 Algoltek, Inc. // SPDX-License-Identifier: LGPL-2.1+ enum AlgoltekCmd { Rdr = 0x06, Wrr, Rdv, En, Wrf = 0x10, Isp = 0x13, Ers = 0x19, Bot = 0x1D, Rst = 0x20, } #[derive(Getters,ValidateBytes)] struct AlgoltekProductIdentity { header_len: u8, header: u64le == 0x4B45544C4F474C41, // 'A' 'L' 'G' 'O' 'L' 'T' 'E' 'K' product_name_len: u8, product_name: [char; 16], version_len: u8, version: [char; 48], } #[derive(New)] struct AlgoltekCmdAddressPkt { len: u8, cmd: u8, address: u16be, value: u16be, reserved: [u8; 4], checksum: u8, } #[derive(New)] struct AlgoltekCmdTransferPkt { len: u8, cmd: u8, address: u16be, data: [u8; 61], checksum: u8, } fwupd-1.9.16/plugins/algoltek-usb/meson.build000066400000000000000000000010261460375044200211550ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginAlgoltekUsb"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('algoltek-usb.quirk') plugin_builtins += static_library('fu_plugin_algoltek_usb', rustgen.process('fu-algoltek-usb.rs'), sources: [ 'fu-algoltek-usb-device.c', 'fu-algoltek-usb-firmware.c', 'fu-algoltek-usb-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/amd-gpu/000077500000000000000000000000001460375044200157555ustar00rootroot00000000000000fwupd-1.9.16/plugins/amd-gpu/README.md000066400000000000000000000037321460375044200172410ustar00rootroot00000000000000--- title: Plugin: AMDGPU --- ## Introduction This plugin reports the vbios version of APU devices supported by amdgpu and supports flashing the IFWI of some dGPU devices. ## External Interface Access This plugin requires R/W access to sysfs files located within `/sys/bus/pci/drivers/*/amdgpu`. This plugin requires ioctl access to `DRM_IOCTL_AMDGPU_INFO`. ## Firmware Format This plugin supports the following protocol ID: * `com.amd.pspvbflash` ## GUID Generation The plugin will use standard PCI GUIDs, but also generate an AMD GPU specific GUID with the part number: `AMD\$PART_NUMBER` ## Update behavior The dGPU will boot into the new firmware when the system is rebooted. The dGPU contains two partitions, and the update will be applied to the inactive partition. If the active partition becomes corrupted for any reason the dGPU may revert back to an older firmware present on the inactive partition. ## Version Considerations This plugin has been available since fwupd version `1.8.11`. Update functionality has been available since fwupd version `1.9.6`. ## Threat Model The plugin runs within the privileged fwupd process. The plugin doesn't directly interface with the hardware, but rather interfaces with the kernel driver which interfaces with the hardware. ```mermaid flowchart LR subgraph dGPU PSP SPI[(SPI)] end subgraph fwupd Process fwupdengine(FuEngine) plugin(AmdGpu\nPlugin) end subgraph Linux Kernel kernel(amdgpu driver) end PSP <-.->SPI kernel<--"mailbox"-->PSP plugin--"psp_vbflash\nsysfs"-->kernel plugin<--"psp_vbflash_status\nsysfs"-->kernel plugin<--"DRM_IOCTL_AMDGPU_INFO()"-->kernel fwupdengine -.- plugin PSP ~~~ kernel kernel ~~~ fwupdengine ``` ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Mario Limonciello: @superm1 fwupd-1.9.16/plugins/amd-gpu/amd-gpu.quirk000066400000000000000000000000451460375044200203630ustar00rootroot00000000000000[PCI\DRIVER_amdgpu] Plugin = amd_gpu fwupd-1.9.16/plugins/amd-gpu/fu-amd-gpu-atom-firmware.c000066400000000000000000000217451460375044200226440ustar00rootroot00000000000000/* * Copyright (C) 2023 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-amd-gpu-atom-firmware.h" #include "fu-amd-gpu-atom-struct.h" #define BIOS_VERSION_PREFIX "ATOMBIOSBK-AMD VER" #define BIOS_STRING_LENGTH 43 #define STRLEN_NORMAL 32 #define STRLEN_LONG 64 struct _FuAmdGpuAtomFirmware { FuFirmware parent_instance; gchar *part_number; gchar *asic; gchar *pci_type; gchar *memory_type; gchar *bios_date; gchar *model_name; gchar *config_filename; }; /** * FuAmdGpuAtomFirmware: * * Firmware for AMD dGPUs. * * This parser collects information from the "CSM" image also known as * the ATOM image. * * This image contains strings that describe the version and the hardware * the ATOM is intended to be used for. */ G_DEFINE_TYPE(FuAmdGpuAtomFirmware, fu_amd_gpu_atom_firmware, FU_TYPE_OPROM_FIRMWARE) static void fu_amd_gpu_atom_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuAmdGpuAtomFirmware *self = FU_AMD_GPU_ATOM_FIRMWARE(firmware); FU_FIRMWARE_CLASS(fu_amd_gpu_atom_firmware_parent_class)->export(firmware, flags, bn); fu_xmlb_builder_insert_kv(bn, "part_number", self->part_number); fu_xmlb_builder_insert_kv(bn, "asic", self->asic); fu_xmlb_builder_insert_kv(bn, "pci_type", self->pci_type); fu_xmlb_builder_insert_kv(bn, "memory_type", self->memory_type); fu_xmlb_builder_insert_kv(bn, "bios_date", self->bios_date); fu_xmlb_builder_insert_kv(bn, "model_name", self->model_name); fu_xmlb_builder_insert_kv(bn, "config_filename", self->config_filename); } static gboolean fu_amd_gpu_atom_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { g_autoptr(GByteArray) atom = NULL; atom = fu_struct_atom_image_parse_bytes(fw, offset, error); if (atom == NULL) return FALSE; return fu_struct_atom_rom21_header_validate_bytes(fw, fu_struct_atom_image_get_rom_loc(atom), error); } const gchar * fu_amd_gpu_atom_get_vbios_pn(FuFirmware *firmware) { FuAmdGpuAtomFirmware *self = FU_AMD_GPU_ATOM_FIRMWARE(firmware); return self->part_number; } static gboolean fu_amd_gpu_atom_parse_vbios_version(FuAmdGpuAtomFirmware *self, const guint8 *buf, gsize bufsz, GError **error) { gsize offset, base; g_autofree gchar *version = NULL; base = fu_firmware_get_addr(FU_FIRMWARE(self)); if (!fu_memmem_safe(buf + base, bufsz - base, (const guint8 *)BIOS_VERSION_PREFIX, sizeof(BIOS_VERSION_PREFIX) - 1, &offset, error)) { g_prefix_error(error, "failed to find anchor: "); return FALSE; } /* skip anchor */ offset += base + sizeof(BIOS_VERSION_PREFIX) - 1; version = fu_memstrsafe(buf, bufsz, offset, BIOS_STRING_LENGTH, error); if (version == NULL) return FALSE; fu_firmware_set_version(FU_FIRMWARE(self), version); return TRUE; } static gboolean fu_amd_gpu_atom_parse_vbios_date(FuAmdGpuAtomFirmware *self, GByteArray *atom_image, GError **error) { g_autoptr(GByteArray) st = fu_struct_atom_image_get_vbios_date(atom_image); if (st == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "ATOMBIOS date is invalid"); return FALSE; } /* same date format as atom_get_vbios_date() */ self->bios_date = g_strdup_printf("20%s/%s/%s %s:%s", fu_struct_vbios_date_get_year(st), fu_struct_vbios_date_get_month(st), fu_struct_vbios_date_get_day(st), fu_struct_vbios_date_get_hours(st), fu_struct_vbios_date_get_minutes(st)); return TRUE; } static gboolean fu_amd_gpu_atom_parse_vbios_pn(FuAmdGpuAtomFirmware *self, const guint8 *buf, gsize bufsz, GByteArray *atom_image, GError **error) { guint16 atombios_size; gint num_str, i; guint64 base; guint16 idx; g_autofree gchar *model = NULL; num_str = fu_struct_atom_image_get_num_strings(atom_image); if (num_str == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "ATOMBIOS number of strings is 0"); return FALSE; } idx = fu_struct_atom_image_get_str_loc(atom_image); if (idx == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "ATOMBIOS string location is invalid"); return FALSE; } /* make sure there is enough space for all the strings */ atombios_size = fu_firmware_get_size(FU_FIRMWARE(self)); if ((gsize)(idx + (num_str * (STRLEN_NORMAL - 1))) > atombios_size) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "bufsz is too small for all strings"); return FALSE; } base = fu_firmware_get_addr(FU_FIRMWARE(self)); /* parse atombios strings */ for (i = 0; i < num_str; i++) { g_autofree char *str = NULL; str = fu_memstrsafe(buf, bufsz, base + idx, STRLEN_NORMAL - 1, error); if (str == NULL) return FALSE; idx += strlen(str) + 1; switch (i) { case FU_ATOM_STRING_INDEX_PART_NUMBER: self->part_number = g_steal_pointer(&str); break; case FU_ATOM_STRING_INDEX_ASIC: self->asic = g_steal_pointer(&str); break; case FU_ATOM_STRING_INDEX_PCI_TYPE: self->pci_type = g_steal_pointer(&str); break; case FU_ATOM_STRING_INDEX_MEMORY_TYPE: self->memory_type = g_steal_pointer(&str); break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "unknown string index: %d", i); return FALSE; } } /* skip the following 2 chars: 0x0D 0x0A */ idx += 2; /* make sure there is enough space for name string */ if ((gsize)(idx + STRLEN_LONG - 1) > atombios_size) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "bufsz is too small for name string"); return FALSE; } model = fu_memstrsafe(buf, bufsz, base + idx, STRLEN_LONG - 1, error); if (model == NULL) return FALSE; self->model_name = fu_strstrip(model); return TRUE; } static gboolean fu_amd_gpu_atom_parse_config_filename(FuAmdGpuAtomFirmware *self, const guint8 *buf, gsize bufsz, GByteArray *atom_header, GError **error) { g_autofree gchar *config_filename = NULL; config_filename = fu_memstrsafe(buf, bufsz, fu_firmware_get_addr(FU_FIRMWARE(self)) + fu_struct_atom_rom21_header_get_config_filename_offset(atom_header), STRLEN_LONG - 1, error); if (config_filename == NULL) return FALSE; /* this function is called twice, but value only stored once */ if (self->config_filename == NULL) self->config_filename = fu_strstrip(config_filename); return TRUE; } static gboolean fu_amd_gpu_atom_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuAmdGpuAtomFirmware *self = FU_AMD_GPU_ATOM_FIRMWARE(firmware); gsize bufsz = 0; guint32 loc; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GByteArray) atom_image = NULL; g_autoptr(GByteArray) atom_rom = NULL; if (!FU_FIRMWARE_CLASS(fu_amd_gpu_atom_firmware_parent_class) ->parse(firmware, fw, offset, flags, error)) return FALSE; /* atom rom image */ atom_image = fu_struct_atom_image_parse_bytes(fw, offset, error); if (atom_image == NULL) return FALSE; /* unit is 512 bytes */ fu_firmware_set_size(firmware, fu_struct_atom_image_get_size(atom_image) * 512); /* atom rom header */ loc = fu_struct_atom_image_get_rom_loc(atom_image) + offset; atom_rom = fu_struct_atom_rom21_header_parse_bytes(fw, loc, error); if (atom_rom == NULL) return FALSE; if (!fu_amd_gpu_atom_parse_config_filename(self, buf, bufsz, atom_rom, error)) return FALSE; if (!fu_amd_gpu_atom_parse_vbios_date(self, atom_image, error)) return FALSE; if (!fu_amd_gpu_atom_parse_vbios_pn(self, buf, bufsz, atom_image, error)) return FALSE; if (!fu_amd_gpu_atom_parse_vbios_version(self, buf, bufsz, error)) return FALSE; return TRUE; } static void fu_amd_gpu_atom_firmware_init(FuAmdGpuAtomFirmware *self) { } static void fu_amd_gpu_atom_firmware_finalize(GObject *object) { FuAmdGpuAtomFirmware *self = FU_AMD_GPU_ATOM_FIRMWARE(object); g_free(self->part_number); g_free(self->asic); g_free(self->pci_type); g_free(self->memory_type); g_free(self->bios_date); g_free(self->model_name); g_free(self->config_filename); G_OBJECT_CLASS(fu_amd_gpu_atom_firmware_parent_class)->finalize(object); } static void fu_amd_gpu_atom_firmware_class_init(FuAmdGpuAtomFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_amd_gpu_atom_firmware_finalize; klass_firmware->parse = fu_amd_gpu_atom_firmware_parse; klass_firmware->export = fu_amd_gpu_atom_firmware_export; klass_firmware->check_magic = fu_amd_gpu_atom_firmware_check_magic; } FuFirmware * fu_amd_gpu_atom_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_AMD_GPU_ATOM_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/amd-gpu/fu-amd-gpu-atom-firmware.h000066400000000000000000000013171460375044200226420ustar00rootroot00000000000000/* * Copyright (C) 2023 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_AMD_GPU_ATOM_FIRMWARE (fu_amd_gpu_atom_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuAmdGpuAtomFirmware, fu_amd_gpu_atom_firmware, FU, AMD_GPU_ATOM_FIRMWARE, FuOpromFirmware) FuFirmware * fu_amd_gpu_atom_firmware_new(void); const gchar * fu_amd_gpu_atom_get_vbios_pn(FuFirmware *firmware); fwupd-1.9.16/plugins/amd-gpu/fu-amd-gpu-atom.rs000066400000000000000000000033051460375044200212240ustar00rootroot00000000000000// Copyright (C) 2023 Advanced Micro Devices Inc. // SPDX-License-Identifier: LGPL-2.1+ OR MIT #[derive(Getters, ToString)] struct VbiosDate { month: [char; 2], _separator: u8, day: [char; 2], _separator: u8, year: [char; 2], _separator: u8, hours: [char; 2], _separator: u8, minutes: [char; 2], _separator: u8, seconds: [char; 2], _nullchar: u8, } #[derive(ParseBytes)] struct AtomImage { signature: u16be = 0x55aa, size: u16le, reserved: [u64be; 2], reserved: u32le, pcir_loc: u16le, reserved: u32le, compat_sig: [char; 3] == "IBM", checksum: u8, reserved: [u32le; 3], reserved: u8, num_strings: u8, reserved: [u64le; 3], rom_loc: u16le, reserved: [u16le; 3], vbios_date: VbiosDate, oem: u16le, reserved: [u16le; 5], str_loc: u32le, } #[derive(Getters, ToString)] struct AtomHeaderCommon { size: u16le, format_rev: u8, content_rev: u8, } #[derive(ParseBytes, ValidateBytes)] struct AtomRom21Header { header: AtomHeaderCommon, signature: [char; 4] == "ATOM" , bios_runtime_seg_addr: u16le, protected_mode_info_offset: u16le, config_filename_offset: u16le, crc_block_offset: u16le, bios_bootup_message_offset: u16le, int10_offset: u16le, pci_bus_dev_init_code: u16le, io_base_addr: u16le, subsystem_vendor_id: u16le, subsystem_id: u16le, pci_info_offset: u16le, master_command_table_offset: u16le, master_data_table_offset: u16le, extended_function_code: u8, reserved: u8, psp_dir_table_offset: u32le, } #[repr(u8)] enum AtomStringIndex { PartNumber = 0x00, ASIC = 0x01, PciType = 0x02, MemoryType = 0x03, } fwupd-1.9.16/plugins/amd-gpu/fu-amd-gpu-device.c000066400000000000000000000215731460375044200213300ustar00rootroot00000000000000/* * Copyright (C) 2023 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include #include #include #include "fu-amd-gpu-atom-firmware.h" #include "fu-amd-gpu-device.h" #include "fu-amd-gpu-psp-firmware.h" struct _FuAmdGpuDevice { FuUdevDevice parent_instance; gchar *vbios_pn; }; #define PSPVBFLASH_MAX_POLL 1500 #define PSPVBFLASH_NOT_STARTED 0x0 #define PSPVBFLASH_IN_PROGRESS 0x1 #define PSPVBFLASH_SUCCESS 0x80000000 G_DEFINE_TYPE(FuAmdGpuDevice, fu_amd_gpu_device, FU_TYPE_UDEV_DEVICE) static gboolean fu_amd_gpu_set_device_file(FuDevice *device, const gchar *base, GError **error) { const gchar *f; g_autofree gchar *ddir = NULL; g_autoptr(GDir) dir = NULL; ddir = g_build_filename(base, "drm", NULL); dir = g_dir_open(ddir, 0, error); if (dir == NULL) return FALSE; while ((f = g_dir_read_name(dir))) { if (g_str_has_prefix(f, "card")) { g_autofree gchar *devbase = NULL; g_autofree gchar *device_file = NULL; devbase = fu_path_from_kind(FU_PATH_KIND_DEVFS); device_file = g_build_filename(devbase, "dri", f, NULL); fu_udev_device_set_device_file(FU_UDEV_DEVICE(device), device_file); return TRUE; } } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no DRM device file found"); return FALSE; } static gboolean fu_amd_gpu_device_probe(FuDevice *device, GError **error) { const gchar *base; g_autofree gchar *rom = NULL; g_autofree gchar *psp_vbflash = NULL; g_autofree gchar *psp_vbflash_status = NULL; base = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device)); if (!fu_amd_gpu_set_device_file(device, base, error)) return FALSE; if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "pci", error)) return FALSE; /* APUs don't have 'rom' sysfs file */ rom = g_build_filename(base, "rom", NULL); if (!g_file_test(rom, G_FILE_TEST_EXISTS)) { fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_HOST_CPU_CHILD); fu_device_set_name(device, "Graphics Processing Unit (GPU)"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL); } else { fu_device_set_logical_id(device, "rom"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_udev_device_set_flags(FU_UDEV_DEVICE(device), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT); } /* firmware upgrade support */ psp_vbflash = g_build_filename(base, "psp_vbflash", NULL); psp_vbflash_status = g_build_filename(base, "psp_vbflash_status", NULL); if (g_file_test(psp_vbflash, G_FILE_TEST_EXISTS) && g_file_test(psp_vbflash_status, G_FILE_TEST_EXISTS)) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_set_install_duration(device, 70); fu_device_add_protocol(device, "com.amd.pspvbflash"); } return TRUE; } static gboolean fu_amd_gpu_device_setup(FuDevice *device, GError **error) { FuAmdGpuDevice *self = FU_AMDGPU_DEVICE(device); struct drm_amdgpu_info_vbios vbios_info; struct drm_amdgpu_info request = { .query = AMDGPU_INFO_VBIOS, .return_pointer = GPOINTER_TO_SIZE(&vbios_info), .return_size = sizeof(struct drm_amdgpu_info_vbios), .vbios_info.type = AMDGPU_INFO_VBIOS_INFO, }; g_autofree gchar *part = NULL; g_autofree gchar *ver = NULL; g_autofree gchar *model = NULL; if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(device), DRM_IOCTL_AMDGPU_INFO, (void *)&request, NULL, 1000, error)) return FALSE; self->vbios_pn = fu_strsafe((const gchar *)vbios_info.vbios_pn, sizeof(vbios_info.vbios_pn)); part = g_strdup_printf("AMD\\%s", self->vbios_pn); fu_device_add_instance_id(device, part); fu_device_set_version_raw(device, vbios_info.version); ver = fu_strsafe((const gchar *)vbios_info.vbios_ver_str, sizeof(vbios_info.vbios_ver_str)); fu_device_set_version(device, ver); model = fu_strsafe((const gchar *)vbios_info.name, sizeof(vbios_info.name)); fu_device_set_summary(device, model); return TRUE; } static FuFirmware * fu_amd_gpu_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuAmdGpuDevice *self = FU_AMDGPU_DEVICE(device); g_autoptr(FuFirmware) firmware = fu_amd_gpu_psp_firmware_new(); g_autoptr(FuFirmware) ish_a = NULL; g_autoptr(FuFirmware) partition_a = NULL; g_autoptr(FuFirmware) csm = NULL; const gchar *fw_pn; if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* we will always flash the contents of partition A */ ish_a = fu_firmware_get_image_by_id(firmware, "ISH_A", error); if (ish_a == NULL) return NULL; partition_a = fu_firmware_get_image_by_id(ish_a, "PARTITION_A", error); if (partition_a == NULL) return NULL; csm = fu_firmware_get_image_by_id(partition_a, "ATOM_CSM_A", error); if (csm == NULL) return NULL; fw_pn = fu_amd_gpu_atom_get_vbios_pn(csm); if (g_strcmp0(fw_pn, self->vbios_pn) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware for %s does not match %s", fw_pn, self->vbios_pn); return NULL; } return g_steal_pointer(&firmware); } static gboolean fu_amd_gpu_device_wait_for_completion_cb(FuDevice *device, gpointer user_data, GError **error) { const gchar *base = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device)); gsize sz = 0; guint64 status = 0; g_autofree gchar *buf = NULL; g_autofree gchar *psp_vbflash_status = NULL; psp_vbflash_status = g_build_filename(base, "psp_vbflash_status", NULL); if (!g_file_get_contents(psp_vbflash_status, &buf, &sz, error)) return FALSE; if (!fu_strtoull(buf, &status, 0, G_MAXUINT64, error)) return FALSE; if (status != PSPVBFLASH_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "status was %" G_GUINT64_FORMAT, status); return FALSE; } /* success */ return TRUE; } static gboolean fu_amd_gpu_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { g_autofree gchar *psp_vbflash = NULL; g_autoptr(FuIOChannel) image_io = NULL; g_autoptr(GBytes) fw = NULL; const gchar *base; base = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device)); psp_vbflash = g_build_filename(base, "psp_vbflash", NULL); image_io = fu_io_channel_new_file(psp_vbflash, error); if (image_io == NULL) return FALSE; fu_progress_set_id(progress, G_STRLOC); /* stage the image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; if (!fu_io_channel_write_bytes(image_io, fw, 100, FU_IO_CHANNEL_FLAG_NONE, error)) return FALSE; /* trigger the update (this looks funny but amdgpu returns 0 bytes) */ if (!fu_io_channel_read_raw(image_io, NULL, 0, NULL, 100, FU_IO_CHANNEL_FLAG_NONE, NULL)) g_debug("triggered update"); /* poll for completion */ return fu_device_retry_full(device, fu_amd_gpu_device_wait_for_completion_cb, PSPVBFLASH_MAX_POLL, 100, /* ms */ NULL, error); } static void fu_amd_gpu_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, NULL); /* detach */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, NULL); /* write */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, NULL); /* attach */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, NULL); /* reload */ } static void fu_amd_gpu_device_init(FuAmdGpuDevice *self) { fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_AUTO_PARENT_CHILDREN); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_GENERIC_GUIDS); } static void fu_amd_gpu_device_finalize(GObject *object) { FuAmdGpuDevice *self = FU_AMDGPU_DEVICE(object); g_free(self->vbios_pn); G_OBJECT_CLASS(fu_amd_gpu_device_parent_class)->finalize(object); } static void fu_amd_gpu_device_class_init(FuAmdGpuDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_amd_gpu_device_finalize; klass_device->probe = fu_amd_gpu_device_probe; klass_device->setup = fu_amd_gpu_device_setup; klass_device->set_progress = fu_amd_gpu_device_set_progress; klass_device->write_firmware = fu_amd_gpu_device_write_firmware; klass_device->prepare_firmware = fu_amd_gpu_device_prepare_firmware; } fwupd-1.9.16/plugins/amd-gpu/fu-amd-gpu-device.h000066400000000000000000000010271460375044200213250ustar00rootroot00000000000000/* * Copyright (C) 2023 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_AMDGPU_DEVICE (fu_amd_gpu_device_get_type()) G_DECLARE_FINAL_TYPE(FuAmdGpuDevice, fu_amd_gpu_device, FU, AMDGPU_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/amd-gpu/fu-amd-gpu-plugin.c000066400000000000000000000025031460375044200213570ustar00rootroot00000000000000/* * Copyright (C) 2023 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-amd-gpu-atom-firmware.h" #include "fu-amd-gpu-device.h" #include "fu-amd-gpu-plugin.h" #include "fu-amd-gpu-psp-firmware.h" struct _FuAmdGpuPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuAmdGpuPlugin, fu_amd_gpu_plugin, FU_TYPE_PLUGIN) static void fu_amd_gpu_plugin_init(FuAmdGpuPlugin *self) { } static void fu_amd_gpu_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "pci"); fu_plugin_add_device_gtype(plugin, FU_TYPE_AMDGPU_DEVICE); /* Navi3x and later use PSP firmware container */ fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_AMD_GPU_PSP_FIRMWARE); /* Navi 2x and older have the ATOM firmware at start of image */ fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_AMD_GPU_ATOM_FIRMWARE); } static void fu_amd_gpu_plugin_class_init(FuAmdGpuPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_amd_gpu_plugin_constructed; } fwupd-1.9.16/plugins/amd-gpu/fu-amd-gpu-plugin.h000066400000000000000000000007261460375044200213710ustar00rootroot00000000000000/* * Copyright (C) 2023 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include G_DECLARE_FINAL_TYPE(FuAmdGpuPlugin, fu_amd_gpu_plugin, FU, AMDGPU_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/amd-gpu/fu-amd-gpu-psp-firmware.c000066400000000000000000000146141460375044200225030ustar00rootroot00000000000000/* * Copyright (C) 2023 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-amd-gpu-atom-firmware.h" #include "fu-amd-gpu-psp-firmware.h" #include "fu-amd-gpu-psp-struct.h" struct _FuAmdGpuPspFirmware { FuFirmware parent_instance; guint32 dir_location; }; /** * FuAmdGpuPspFirmware: * * An AMD PSP firmware image * * The firmware is structured in an Embedded Firmware Structure (EFS). * Within the EFS is a point to an "L1 PSP directory table". * * The L1 PSP directory table contains entries which point to * "Image Slot Headers" (ISH). * * The ISH headers contain entries that point to a given partition (A or B). * The partition contains an "L2 PSP directory table". * * The L2 directory table specifies a variety of IDs. Supported IDs will * be parsed by other firmware parsers. */ G_DEFINE_TYPE(FuAmdGpuPspFirmware, fu_amd_gpu_psp_firmware, FU_TYPE_FIRMWARE) static void fu_amd_gpu_psp_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuAmdGpuPspFirmware *self = FU_AMD_GPU_PSP_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "dir_location", self->dir_location); } static gboolean fu_amd_gpu_psp_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { g_autoptr(GByteArray) efs = NULL; efs = fu_struct_efs_parse_bytes(fw, 0, error); if (efs == NULL) return FALSE; return fu_struct_psp_dir_validate_bytes(fw, fu_struct_efs_get_psp_dir_loc(efs), error); } static gboolean fu_amd_gpu_psp_firmware_parse_l2(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { guint i; g_autoptr(GByteArray) l2 = NULL; l2 = fu_struct_psp_dir_table_parse_bytes(fw, offset, error); if (l2 == NULL) return FALSE; /* parse the L2 entries */ for (i = 0; i < fu_struct_psp_dir_get_total_entries(l2); i++) { g_autoptr(GByteArray) l2_entry = NULL; gint idx = FU_STRUCT_PSP_DIR_TABLE_SIZE + offset + i * FU_STRUCT_PSP_DIR_TABLE_SIZE; l2_entry = fu_struct_psp_dir_table_parse_bytes(fw, idx, error); if (l2_entry == NULL) return FALSE; } return TRUE; } static gboolean fu_amd_gpu_psp_firmware_parse_l1(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { g_autoptr(GByteArray) l1 = NULL; /* parse the L1 entries */ l1 = fu_struct_psp_dir_table_parse_bytes(fw, offset, error); if (l1 == NULL) return FALSE; for (guint i = 0; i < fu_struct_psp_dir_get_total_entries(l1); i++) { gint idx = FU_STRUCT_PSP_DIR_TABLE_SIZE + offset + i * FU_STRUCT_PSP_DIR_TABLE_SIZE; guint loc; guint sz; FuFirmwareClass *klass_firmware; g_autoptr(GByteArray) l1_entry = NULL; g_autoptr(GByteArray) ish = NULL; g_autoptr(FuFirmware) ish_img = fu_firmware_new(); g_autoptr(FuFirmware) csm_img = fu_amd_gpu_atom_firmware_new(); g_autoptr(FuFirmware) l2_img = fu_firmware_new(); g_autoptr(GBytes) l1_blob = NULL; g_autoptr(GBytes) l2_blob = NULL; l1_entry = fu_struct_psp_dir_table_parse_bytes(fw, idx, error); if (l1_entry == NULL) return FALSE; switch (fu_struct_psp_dir_table_get_fw_id(l1_entry)) { case FU_FWID_ISH_A: fu_firmware_set_id(ish_img, "ISH_A"); break; case FU_FWID_ISH_B: fu_firmware_set_id(ish_img, "ISH_B"); break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown ISH FWID: %x", fu_struct_psp_dir_table_get_fw_id(l1_entry)); return FALSE; } /* parse the image slot header */ loc = fu_struct_psp_dir_table_get_loc(l1_entry); ish = fu_struct_image_slot_header_parse_bytes(fw, loc, error); if (ish == NULL) return FALSE; l1_blob = fu_bytes_new_offset(fw, loc, FU_STRUCT_IMAGE_SLOT_HEADER_SIZE, error); if (l1_blob == NULL) return FALSE; fu_firmware_set_addr(ish_img, loc); fu_firmware_set_bytes(ish_img, l1_blob); fu_firmware_add_image(firmware, ish_img); /* parse the csm image */ loc = fu_struct_image_slot_header_get_loc_csm(ish); klass_firmware = FU_FIRMWARE_GET_CLASS(csm_img); fu_firmware_set_addr(csm_img, loc); if (!klass_firmware->parse(csm_img, fw, loc, FWUPD_INSTALL_FLAG_NONE, error)) return FALSE; loc = fu_struct_image_slot_header_get_loc(ish); sz = fu_struct_image_slot_header_get_slot_max_size(ish); switch (fu_struct_image_slot_header_get_fw_id(ish)) { case FU_FWID_PARTITION_A_L2: fu_firmware_set_id(l2_img, "PARTITION_A"); fu_firmware_set_id(csm_img, "ATOM_CSM_A"); break; case FU_FWID_PARTITION_B_L2: fu_firmware_set_id(l2_img, "PARTITION_B"); fu_firmware_set_id(csm_img, "ATOM_CSM_B"); break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown Partition FWID: %x", fu_struct_image_slot_header_get_fw_id(ish)); return FALSE; } fu_firmware_add_image(l2_img, csm_img); l2_blob = fu_bytes_new_offset(fw, loc, sz, error); if (l2_blob == NULL) return FALSE; fu_firmware_set_addr(l2_img, loc); fu_firmware_set_bytes(l2_img, l2_blob); fu_firmware_add_image(ish_img, l2_img); /* parse the partition */ if (!fu_amd_gpu_psp_firmware_parse_l2(l2_img, fw, loc, error)) return FALSE; } return TRUE; } static gboolean fu_amd_gpu_psp_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuAmdGpuPspFirmware *self = FU_AMD_GPU_PSP_FIRMWARE(firmware); g_autoptr(GByteArray) efs = NULL; efs = fu_struct_efs_parse_bytes(fw, 0, error); if (efs == NULL) return FALSE; self->dir_location = fu_struct_efs_get_psp_dir_loc(efs); return fu_amd_gpu_psp_firmware_parse_l1(firmware, fw, self->dir_location, error); } static void fu_amd_gpu_psp_firmware_init(FuAmdGpuPspFirmware *self) { } static void fu_amd_gpu_psp_firmware_class_init(FuAmdGpuPspFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_amd_gpu_psp_firmware_check_magic; klass_firmware->parse = fu_amd_gpu_psp_firmware_parse; klass_firmware->export = fu_amd_gpu_psp_firmware_export; } /** * fu_amd_gpu_psp_firmware_new * * Creates a new #FuFirmware of sub type amd-gpu-psp * * Since: 1.9.6 **/ FuFirmware * fu_amd_gpu_psp_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_AMD_GPU_PSP_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/amd-gpu/fu-amd-gpu-psp-firmware.h000066400000000000000000000012011460375044200224740ustar00rootroot00000000000000/* * Copyright (C) 2023 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_AMD_GPU_PSP_FIRMWARE (fu_amd_gpu_psp_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuAmdGpuPspFirmware, fu_amd_gpu_psp_firmware, FU, AMD_GPU_PSP_FIRMWARE, FuFirmware) FuFirmware * fu_amd_gpu_psp_firmware_new(void); fwupd-1.9.16/plugins/amd-gpu/fu-amd-gpu-psp.rs000066400000000000000000000020211460375044200210600ustar00rootroot00000000000000// Copyright (C) 2023 Advanced Micro Devices Inc. // SPDX-License-Identifier: LGPL-2.1+ OR MIT #[derive(ParseBytes)] struct Efs { signature: u32le = 0x55aa55aa, reserved: [u32le; 4], psp_dir_loc: u32le, reserved: [u32le; 5], _psp_dir_loc_back: u32le, reserved: [u32le; 6], _psp_dir_ind_loc: u32le, _rom_strap_a_loc: u32le, _rom_strap_b_loc: u32le, } #[derive(ValidateBytes, Getters)] struct PspDir { cookie: [char; 4] == "$PSP", checksum: u32le, total_entries: u32le, reserved: u32le, } #[derive(ParseBytes)] struct PspDirTable { fw_id: u32le, size: u32le, loc: u64le, } #[derive(ParseBytes)] struct ImageSlotHeader { checksum: u32le, boot_priority: u32le, update_retries: u32le, glitch_retries: u8, fw_id: u16le, reserved: u8, loc: u32le, psp_id: u32le, slot_max_size: u32le, loc_csm: u32le, } #[repr(u8)] enum Fwid { AtomCsm = 0x1, PartitionAL2 = 0x014D, PartitionBL2 = 0x014E, IshA = 0x013C, IshB = 0x013D, } fwupd-1.9.16/plugins/amd-gpu/meson.build000066400000000000000000000012221460375044200201140ustar00rootroot00000000000000if gudev.found() and libdrm_amdgpu.found() and get_option('plugin_amdgpu').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginAmdGpu"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('amd-gpu.quirk') plugin_builtins += static_library('fu_plugin_amd_gpu', rustgen.process( 'fu-amd-gpu-atom.rs', # fuzzing 'fu-amd-gpu-psp.rs', # fuzzing ), sources: [ 'fu-amd-gpu-plugin.c', 'fu-amd-gpu-device.c', 'fu-amd-gpu-psp-firmware.c', 'fu-amd-gpu-atom-firmware.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/amd-pmc/000077500000000000000000000000001460375044200157415ustar00rootroot00000000000000fwupd-1.9.16/plugins/amd-pmc/README.md000066400000000000000000000011751460375044200172240ustar00rootroot00000000000000--- title: Plugin: AMD PMC --- ## Introduction This plugin reports the firmware version of a microcontroller contained in AMD Zen CPU/APUs called the System Management Unit on kernels that support exporting this information. ## External Interface Access This plugin requires read only access to attributes located within `/sys/bus/platform/drivers/amd_pmc`. ## Version Considerations This plugin has been available since fwupd version `1.8.5`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Mario Limonciello: @superm1 fwupd-1.9.16/plugins/amd-pmc/amd-pmc.quirk000066400000000000000000000000531460375044200203320ustar00rootroot00000000000000[PLATFORM\DRIVER_amd_pmc] Plugin = amd_pmc fwupd-1.9.16/plugins/amd-pmc/fu-amd-pmc-device.c000066400000000000000000000043541460375044200212760ustar00rootroot00000000000000/* * Copyright (C) 2022 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-amd-pmc-device.h" struct _FuAmdPmcDevice { FuUdevDevice parent_instance; }; G_DEFINE_TYPE(FuAmdPmcDevice, fu_amd_pmc_device, FU_TYPE_UDEV_DEVICE) static gboolean fu_amd_pmc_device_probe(FuDevice *device, GError **error) { guint64 program; const gchar *version; g_autoptr(GError) error_local = NULL; g_autofree gchar *summary = NULL; version = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "smu_fw_version", &error_local); if (version == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported kernel version"); return FALSE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } if (!fu_udev_device_get_sysfs_attr_uint64(FU_UDEV_DEVICE(device), "smu_program", &program, error)) return FALSE; fu_device_set_version(device, version); summary = g_strdup_printf("Microcontroller used within CPU/APU program %" G_GUINT64_FORMAT, program); fu_device_set_summary(device, summary); fu_device_add_instance_id(device, fu_device_get_backend_id(device)); return TRUE; } static void fu_amd_pmc_device_init(FuAmdPmcDevice *self) { fu_device_set_name(FU_DEVICE(self), "System Management Unit (SMU)"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_icon(FU_DEVICE(self), "computer"); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_HOST_CPU_CHILD); fu_device_set_vendor(FU_DEVICE(self), "Advanced Micro Devices, Inc."); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_physical_id(FU_DEVICE(self), "amd-pmc"); } static void fu_amd_pmc_device_class_init(FuAmdPmcDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_amd_pmc_device_probe; } fwupd-1.9.16/plugins/amd-pmc/fu-amd-pmc-device.h000066400000000000000000000010311460375044200212700ustar00rootroot00000000000000/* * Copyright (C) 2022 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_AMD_PMC_DEVICE (fu_amd_pmc_device_get_type()) G_DECLARE_FINAL_TYPE(FuAmdPmcDevice, fu_amd_pmc_device, FU, AMD_PMC_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/amd-pmc/fu-amd-pmc-plugin.c000066400000000000000000000017621460375044200213350ustar00rootroot00000000000000/* * Copyright (C) 2022 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-amd-pmc-device.h" #include "fu-amd-pmc-plugin.h" struct _FuAmdPmcPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuAmdPmcPlugin, fu_amd_pmc_plugin, FU_TYPE_PLUGIN) static void fu_amd_pmc_plugin_init(FuAmdPmcPlugin *self) { } static void fu_amd_pmc_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "platform"); fu_plugin_add_device_gtype(plugin, FU_TYPE_AMD_PMC_DEVICE); } static void fu_amd_pmc_plugin_class_init(FuAmdPmcPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_amd_pmc_plugin_constructed; } fwupd-1.9.16/plugins/amd-pmc/fu-amd-pmc-plugin.h000066400000000000000000000010221460375044200213270ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2022 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include G_DECLARE_FINAL_TYPE(FuAmdPmcPlugin, fu_amd_pmc_plugin, FU, AMD_PMC_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/amd-pmc/meson.build000066400000000000000000000007111460375044200201020ustar00rootroot00000000000000if gudev.found() and (host_cpu == 'x86' or host_cpu == 'x86_64') cargs = ['-DG_LOG_DOMAIN="FuPluginAmdPmc"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('amd-pmc.quirk') plugin_builtins += static_library('fu_plugin_amd_pmc', sources: [ 'fu-amd-pmc-plugin.c', 'fu-amd-pmc-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/analogix/000077500000000000000000000000001460375044200162255ustar00rootroot00000000000000fwupd-1.9.16/plugins/analogix/README.md000066400000000000000000000024461460375044200175120ustar00rootroot00000000000000--- title: Plugin: Analogix --- ## Introduction This plugin can flash the firmware of some Analogix billboard devices. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a Intel Hex file format. The resulting binary image is either: * `OCM` section only * `CUSTOM` section only * Multiple sections excluded `CUSTOM` -- at fixed offsets and sizes This plugin supports the following protocol ID: * `com.analogix.bb` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_1F29&PID_7518` * `USB\VID_050D&PID_008B` * `USB\VID_047D&PID_80C8` * `USB\VID_0502&PID_04C4` * `USB\VID_14B0&PID_01D0` * `USB\VID_14B0&PID_01D1` ## Update Behavior The device is updated at runtime using USB control transfers. ## Vendor ID Security The vendor ID is set from the USB vendor. The list of USB VIDs used is: * `USB:0x1F29` * `USB:0x050D` * `USB:0x047D` * `USB:0x0502` * `USB:0x14B0` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.6.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Analogix: @xtcui fwupd-1.9.16/plugins/analogix/analogix.quirk000066400000000000000000000004701460375044200211050ustar00rootroot00000000000000# Phoenix-Lite [USB\VID_1F29&PID_7518] Plugin = analogix # Belkin [USB\VID_050D&PID_008B] Plugin = analogix # Kensington [USB\VID_047D&PID_80C8] Plugin = analogix # ACER [USB\VID_0502&PID_04C4] Plugin = analogix # Startech [USB\VID_14B0&PID_01D0] Plugin = analogix [USB\VID_14B0&PID_01D1] Plugin = analogix fwupd-1.9.16/plugins/analogix/fu-analogix-common.h000066400000000000000000000022351460375044200221000ustar00rootroot00000000000000/* * Copyright (C) 2021 Xiaotian Cui * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define ANX_BB_TRANSACTION_TIMEOUT 5000 /* ms */ #define BILLBOARD_CLASS 0x11 #define BILLBOARD_SUBCLASS 0x00 #define BILLBOARD_PROTOCOL 0x00 #define BILLBOARD_MAX_PACKET_SIZE 64 #define OCM_FLASH_SIZE 0x18000 #define SECURE_OCM_TX_SIZE 0x3000 #define SECURE_OCM_RX_SIZE 0x3000 #define CUSTOM_FLASH_SIZE 0x1000 #define FLASH_OCM_ADDR 0x1000 #define FLASH_TXFW_ADDR 0x31000 #define FLASH_RXFW_ADDR 0x34000 #define FLASH_CUSTOM_ADDR 0x38000 #define OCM_FW_VERSION_ADDR 0x14FF0 /* bRequest for Phoenix-Lite Billboard */ typedef enum { ANX_BB_RQT_SEND_UPDATE_DATA = 0x01, ANX_BB_RQT_READ_UPDATE_DATA = 0x02, ANX_BB_RQT_GET_UPDATE_STATUS = 0x10, ANX_BB_RQT_READ_FW_VER = 0x12, ANX_BB_RQT_READ_CUS_VER = 0x13, ANX_BB_RQT_READ_FW_RVER = 0x19, ANX_BB_RQT_READ_CUS_RVER = 0x1c, } AnxBbRqtCode; /* wValue low byte */ typedef enum { ANX_BB_WVAL_UPDATE_OCM = 0x06, ANX_BB_WVAL_UPDATE_CUSTOM_DEF = 0x07, ANX_BB_WVAL_UPDATE_SECURE_TX = 0x08, ANX_BB_WVAL_UPDATE_SECURE_RX = 0x09, } AnxwValCode; fwupd-1.9.16/plugins/analogix/fu-analogix-device.c000066400000000000000000000331701460375044200220440ustar00rootroot00000000000000/* * Copyright (C) 2021 Xiaotian Cui * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-analogix-common.h" #include "fu-analogix-device.h" #include "fu-analogix-firmware.h" #include "fu-analogix-struct.h" struct _FuAnalogixDevice { FuUsbDevice parent_instance; guint16 ocm_version; guint16 custom_version; }; G_DEFINE_TYPE(FuAnalogixDevice, fu_analogix_device, FU_TYPE_USB_DEVICE) static void fu_analogix_device_to_string(FuDevice *device, guint idt, GString *str) { FuAnalogixDevice *self = FU_ANALOGIX_DEVICE(device); fu_string_append_kx(str, idt, "OcmVersion", self->ocm_version); fu_string_append_kx(str, idt, "CustomVersion", self->custom_version); } static gboolean fu_analogix_device_send(FuAnalogixDevice *self, AnxBbRqtCode reqcode, guint16 val0code, guint16 index, const guint8 *buf, gsize bufsz, GError **error) { gsize actual_len = 0; g_autofree guint8 *buf_tmp = NULL; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(bufsz <= 64, FALSE); /* make mutable */ buf_tmp = fu_memdup_safe(buf, bufsz, error); if (buf_tmp == NULL) return FALSE; /* send data to device */ if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, reqcode, /* request */ val0code, /* value */ index, /* index */ buf_tmp, /* data */ bufsz, /* length */ &actual_len, /* actual length */ (guint)ANX_BB_TRANSACTION_TIMEOUT, NULL, error)) { g_prefix_error(error, "send data error: "); return FALSE; } if (actual_len != bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "send data length is incorrect"); return FALSE; } /* success */ return TRUE; } static gboolean fu_analogix_device_receive(FuAnalogixDevice *self, AnxBbRqtCode reqcode, guint16 val0code, guint16 index, guint8 *buf, gsize bufsz, GError **error) { gsize actual_len = 0; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(bufsz <= 64, FALSE); /* get data from device */ if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, reqcode, /* request */ val0code, /* value */ index, buf, /* data */ bufsz, /* length */ &actual_len, /* actual length */ (guint)ANX_BB_TRANSACTION_TIMEOUT, NULL, error)) { g_prefix_error(error, "receive data error: "); return FALSE; } if (actual_len != bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "receive data length is incorrect"); return FALSE; } /* success */ return TRUE; } static gboolean fu_analogix_device_get_update_status(FuAnalogixDevice *self, FuAnalogixUpdateStatus *status, GError **error) { for (guint i = 0; i < 3000; i++) { guint8 status_tmp = FU_ANALOGIX_UPDATE_STATUS_INVALID; if (!fu_analogix_device_receive(self, ANX_BB_RQT_GET_UPDATE_STATUS, 0, 0, &status_tmp, sizeof(status_tmp), error)) return FALSE; g_debug("status now: %s [0x%x]", fu_analogix_update_status_to_string(status_tmp), status_tmp); if ((status_tmp != FU_ANALOGIX_UPDATE_STATUS_ERROR) && (status_tmp != FU_ANALOGIX_UPDATE_STATUS_INVALID)) { if (status != NULL) *status = status_tmp; return TRUE; } fu_device_sleep(FU_DEVICE(self), 1); /* ms */ } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "timed out: status was invalid"); return FALSE; } static gboolean fu_analogix_device_setup(FuDevice *device, GError **error) { FuAnalogixDevice *self = FU_ANALOGIX_DEVICE(device); guint8 buf_fw[2] = {0x0}; guint8 buf_custom[2] = {0x0}; g_autofree gchar *version = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_analogix_device_parent_class)->setup(device, error)) return FALSE; /* get OCM version */ if (!fu_analogix_device_receive(self, ANX_BB_RQT_READ_FW_VER, 0, 0, &buf_fw[1], 1, error)) return FALSE; if (!fu_analogix_device_receive(self, ANX_BB_RQT_READ_FW_RVER, 0, 0, &buf_fw[0], 1, error)) return FALSE; self->ocm_version = fu_memread_uint16(buf_fw, G_LITTLE_ENDIAN); /* get custom version */ if (!fu_analogix_device_receive(self, ANX_BB_RQT_READ_CUS_VER, 0, 0, &buf_custom[1], 1, error)) return FALSE; if (!fu_analogix_device_receive(self, ANX_BB_RQT_READ_CUS_RVER, 0, 0, &buf_custom[0], 1, error)) return FALSE; self->custom_version = fu_memread_uint16(buf_custom, G_LITTLE_ENDIAN); /* device version is both versions as a pair */ version = g_strdup_printf("%04x.%04x", self->custom_version, self->ocm_version); fu_device_set_version(FU_DEVICE(device), version); return TRUE; } static gboolean fu_analogix_device_find_interface(FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(device); FuAnalogixDevice *self = FU_ANALOGIX_DEVICE(device); g_autoptr(GPtrArray) intfs = NULL; intfs = g_usb_device_get_interfaces(usb_device, error); if (intfs == NULL) return FALSE; for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index(intfs, i); if (g_usb_interface_get_class(intf) == BILLBOARD_CLASS && g_usb_interface_get_subclass(intf) == BILLBOARD_SUBCLASS && g_usb_interface_get_protocol(intf) == BILLBOARD_PROTOCOL) { fu_usb_device_add_interface(FU_USB_DEVICE(self), g_usb_interface_get_number(intf)); return TRUE; } } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no update interface found"); return FALSE; } static gboolean fu_analogix_device_probe(FuDevice *device, GError **error) { if (!fu_analogix_device_find_interface(FU_USB_DEVICE(device), error)) { g_prefix_error(error, "failed to find update interface: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_analogix_device_write_chunks(FuAnalogixDevice *self, FuChunkArray *chunks, guint16 req_val, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { FuAnalogixUpdateStatus status = FU_ANALOGIX_UPDATE_STATUS_INVALID; g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_analogix_device_send(self, ANX_BB_RQT_SEND_UPDATE_DATA, req_val, i + 1, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed send on chk %u: ", i); return FALSE; } if (!fu_analogix_device_get_update_status(self, &status, error)) { g_prefix_error(error, "failed status on chk %u: ", i); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_analogix_device_write_image(FuAnalogixDevice *self, FuFirmware *image, guint16 req_val, FuProgress *progress, GError **error) { FuAnalogixUpdateStatus status = FU_ANALOGIX_UPDATE_STATUS_INVALID; guint8 buf_init[4] = {0x0}; g_autoptr(GBytes) block_bytes = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "initialization"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, NULL); /* offset into firmware */ block_bytes = fu_firmware_get_bytes(image, error); if (block_bytes == NULL) return FALSE; /* initialization */ fu_memwrite_uint32(buf_init, g_bytes_get_size(block_bytes), G_LITTLE_ENDIAN); if (!fu_analogix_device_send(self, ANX_BB_RQT_SEND_UPDATE_DATA, req_val, 0, buf_init, 3, error)) { g_prefix_error(error, "program initialized failed: "); return FALSE; } if (!fu_analogix_device_get_update_status(self, &status, error)) return FALSE; fu_progress_step_done(progress); /* write data */ chunks = fu_chunk_array_new_from_bytes(block_bytes, 0x00, BILLBOARD_MAX_PACKET_SIZE); if (!fu_analogix_device_write_chunks(self, chunks, req_val, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_analogix_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuAnalogixDevice *self = FU_ANALOGIX_DEVICE(device); gsize totalsz = 0; g_autoptr(FuFirmware) fw_cus = NULL; g_autoptr(FuFirmware) fw_ocm = NULL; g_autoptr(FuFirmware) fw_srx = NULL; g_autoptr(FuFirmware) fw_stx = NULL; /* these are all optional */ fw_cus = fu_firmware_get_image_by_id(firmware, "custom", NULL); fw_stx = fu_firmware_get_image_by_id(firmware, "stx", NULL); fw_srx = fu_firmware_get_image_by_id(firmware, "srx", NULL); fw_ocm = fu_firmware_get_image_by_id(firmware, "ocm", NULL); /* progress */ fu_progress_set_id(progress, G_STRLOC); if (fw_cus != NULL) totalsz += fu_firmware_get_size(fw_cus); if (fw_stx != NULL) totalsz += fu_firmware_get_size(fw_stx); if (fw_srx != NULL) totalsz += fu_firmware_get_size(fw_srx); if (fw_ocm != NULL) totalsz += fu_firmware_get_size(fw_ocm); if (totalsz == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no firmware sections to update"); return FALSE; } if (fw_cus != NULL) { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, (100 * fu_firmware_get_size(fw_cus) / totalsz), "cus"); } if (fw_stx != NULL) { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, (100 * fu_firmware_get_size(fw_stx) / totalsz), "stx"); } if (fw_srx != NULL) { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, (100 * fu_firmware_get_size(fw_srx) / totalsz), "srx"); } if (fw_ocm != NULL) { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, (100 * fu_firmware_get_size(fw_ocm) / totalsz), "ocm"); } /* CUSTOM_DEF */ if (fw_cus != NULL) { if (!fu_analogix_device_write_image(self, fw_cus, ANX_BB_WVAL_UPDATE_CUSTOM_DEF, fu_progress_get_child(progress), error)) { g_prefix_error(error, "program custom define failed: "); return FALSE; } fu_progress_step_done(progress); } /* SECURE_TX */ if (fw_stx != NULL) { if (!fu_analogix_device_write_image(self, fw_stx, ANX_BB_WVAL_UPDATE_SECURE_TX, fu_progress_get_child(progress), error)) { g_prefix_error(error, "program secure TX failed: "); return FALSE; } fu_progress_step_done(progress); } /* SECURE_RX */ if (fw_srx != NULL) { if (!fu_analogix_device_write_image(self, fw_srx, ANX_BB_WVAL_UPDATE_SECURE_RX, fu_progress_get_child(progress), error)) { g_prefix_error(error, "program secure RX failed: "); return FALSE; } fu_progress_step_done(progress); } /* OCM */ if (fw_ocm != NULL) { if (!fu_analogix_device_write_image(self, fw_ocm, ANX_BB_WVAL_UPDATE_OCM, fu_progress_get_child(progress), error)) { g_prefix_error(error, "program OCM failed: "); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_analogix_device_attach(FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(FwupdRequest) request = fwupd_request_new(); /* the user has to do something */ fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); if (!fu_device_emit_request(device, request, progress, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* success */ return TRUE; } static void fu_analogix_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reload"); } static void fu_analogix_device_init(FuAnalogixDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.analogix.bb"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_request_flag(FU_DEVICE(self), FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_ANALOGIX_FIRMWARE); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_USER_REPLUG); /* 40 s */ } static void fu_analogix_device_class_init(FuAnalogixDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_analogix_device_to_string; klass_device->write_firmware = fu_analogix_device_write_firmware; klass_device->attach = fu_analogix_device_attach; klass_device->setup = fu_analogix_device_setup; klass_device->probe = fu_analogix_device_probe; klass_device->set_progress = fu_analogix_device_set_progress; } fwupd-1.9.16/plugins/analogix/fu-analogix-device.h000066400000000000000000000004611460375044200220460ustar00rootroot00000000000000/* * Copyright (C) 2021 Xiaotian Cui * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ANALOGIX_DEVICE (fu_analogix_device_get_type()) G_DECLARE_FINAL_TYPE(FuAnalogixDevice, fu_analogix_device, FU, ANALOGIX_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/analogix/fu-analogix-firmware.c000066400000000000000000000072711460375044200224240ustar00rootroot00000000000000/* * Copyright (C) 2021 Xiaotian Cui * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-analogix-common.h" #include "fu-analogix-firmware.h" struct _FuAnalogixFirmware { FuIhexFirmwareClass parent_instance; }; G_DEFINE_TYPE(FuAnalogixFirmware, fu_analogix_firmware, FU_TYPE_IHEX_FIRMWARE) static gboolean fu_analogix_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuFirmwareClass *klass = FU_FIRMWARE_CLASS(fu_analogix_firmware_parent_class); const guint8 *buf = NULL; gsize bufsz = 0; guint16 ocm_version; guint8 version_hi = 0; guint8 version_lo = 0; g_autofree gchar *version = NULL; g_autoptr(FuFirmware) fw_ocm = NULL; g_autoptr(GBytes) blob_cus = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GBytes) blob_ocm = NULL; g_autoptr(GBytes) blob_srx = NULL; g_autoptr(GBytes) blob_stx = NULL; /* convert to binary with FuIhexFirmware->parse */ if (!klass->parse(firmware, fw, offset, flags, error)) return FALSE; blob = fu_firmware_get_bytes_with_patches(firmware, error); if (blob == NULL) return FALSE; /* OCM section only, CUSTOM section only, or multiple sections excluded CUSTOM */ if (g_bytes_get_size(blob) == OCM_FLASH_SIZE) { blob_ocm = g_bytes_ref(blob); } else if (g_bytes_get_size(blob) == CUSTOM_FLASH_SIZE) { /* custom */ blob_cus = fu_bytes_new_offset(blob, 0, CUSTOM_FLASH_SIZE, error); } else { blob_ocm = fu_bytes_new_offset(blob, 0, OCM_FLASH_SIZE, error); if (blob_ocm == NULL) return FALSE; } if (blob_ocm != NULL) { fw_ocm = fu_firmware_new_from_bytes(blob_ocm); fu_firmware_set_id(fw_ocm, "ocm"); fu_firmware_set_addr(fw_ocm, FLASH_OCM_ADDR); fu_firmware_add_image(firmware, fw_ocm); /* get OCM version */ buf = g_bytes_get_data(blob_ocm, &bufsz); if (!fu_memread_uint8_safe(buf, bufsz, OCM_FW_VERSION_ADDR - FLASH_OCM_ADDR + 8, &version_hi, error)) return FALSE; if (!fu_memread_uint8_safe(buf, bufsz, OCM_FW_VERSION_ADDR - FLASH_OCM_ADDR + 12, &version_lo, error)) return FALSE; ocm_version = ((guint16)version_hi) << 8 | version_lo; fu_firmware_set_version_raw(fw_ocm, ocm_version); version = g_strdup_printf("%02x.%02x", version_hi, version_lo); fu_firmware_set_version(fw_ocm, version); } /* TXFW is optional */ blob_stx = fu_bytes_new_offset(blob, FLASH_TXFW_ADDR - FLASH_OCM_ADDR, SECURE_OCM_TX_SIZE, NULL); if (blob_stx != NULL && !fu_bytes_is_empty(blob_stx)) { g_autoptr(FuFirmware) fw2 = fu_firmware_new_from_bytes(blob_stx); fu_firmware_set_id(fw2, "stx"); fu_firmware_set_addr(fw2, FLASH_TXFW_ADDR); fu_firmware_add_image(firmware, fw2); } /* RXFW is optional */ blob_srx = fu_bytes_new_offset(blob, FLASH_RXFW_ADDR - FLASH_OCM_ADDR, SECURE_OCM_RX_SIZE, NULL); if (blob_srx != NULL && !fu_bytes_is_empty(blob_srx)) { g_autoptr(FuFirmware) fw2 = fu_firmware_new_from_bytes(blob_srx); fu_firmware_set_id(fw2, "srx"); fu_firmware_set_addr(fw2, FLASH_RXFW_ADDR); fu_firmware_add_image(firmware, fw2); } if (blob_cus != NULL && !fu_bytes_is_empty(blob_cus)) { g_autoptr(FuFirmware) fw2 = fu_firmware_new_from_bytes(blob_cus); fu_firmware_set_id(fw2, "custom"); fu_firmware_set_addr(fw2, FLASH_CUSTOM_ADDR); fu_firmware_add_image(firmware, fw2); } /* success */ return TRUE; } static void fu_analogix_firmware_init(FuAnalogixFirmware *self) { fu_ihex_firmware_set_padding_value(FU_IHEX_FIRMWARE(self), 0xFF); } static void fu_analogix_firmware_class_init(FuAnalogixFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_analogix_firmware_parse; } fwupd-1.9.16/plugins/analogix/fu-analogix-firmware.h000066400000000000000000000005321460375044200224220ustar00rootroot00000000000000/* * Copyright (C) 2021 Xiaotian Cui * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ANALOGIX_FIRMWARE (fu_analogix_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuAnalogixFirmware, fu_analogix_firmware, FU, ANALOGIX_FIRMWARE, FuIhexFirmware) fwupd-1.9.16/plugins/analogix/fu-analogix-plugin.c000066400000000000000000000015151460375044200221010ustar00rootroot00000000000000/* * Copyright (C) 2021 Xiaotian Cui * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-analogix-device.h" #include "fu-analogix-firmware.h" #include "fu-analogix-plugin.h" struct _FuAnalogixPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuAnalogixPlugin, fu_analogix_plugin, FU_TYPE_PLUGIN) static void fu_analogix_plugin_init(FuAnalogixPlugin *self) { } static void fu_analogix_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_ANALOGIX_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_ANALOGIX_FIRMWARE); } static void fu_analogix_plugin_class_init(FuAnalogixPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_analogix_plugin_constructed; } fwupd-1.9.16/plugins/analogix/fu-analogix-plugin.h000066400000000000000000000003561460375044200221100ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuAnalogixPlugin, fu_analogix_plugin, FU, ANALOGIX_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/analogix/fu-analogix.rs000066400000000000000000000003121460375044200210010ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum AnalogixUpdateStatus { Invalid, Start, Finish, Error = 0xFF, } fwupd-1.9.16/plugins/analogix/meson.build000066400000000000000000000007411460375044200203710ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginAnalogix"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('analogix.quirk') plugin_builtins += static_library('fu_plugin_analogix', rustgen.process('fu-analogix.rs'), sources: [ 'fu-analogix-plugin.c', 'fu-analogix-device.c', 'fu-analogix-firmware.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/android-boot/000077500000000000000000000000001460375044200170045ustar00rootroot00000000000000fwupd-1.9.16/plugins/android-boot/README.md000066400000000000000000000034121460375044200202630ustar00rootroot00000000000000--- title: Plugin: Android Boot --- ## Introduction This plugin is used to update hardware that use partitions to store their firmware on. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob as a Raw Disk File in the IMG format. The firmware blob will be flashed to the partition. Fastboot devices are similar but are flashed in fastboot mode using an external device. This plugin is similar but can be used to flash from the device itself rather than external device. This plugin supports the following protocol ID: * `com.google.android_boot` ## GUID Generation The GUID is generated by combining the partition UUID of the block device, its label and optionally boot slot when using an Android A/B partitioning scheme, e.g. * `DRIVE\UUID_c49183ed-aaec-9bf5-760a-66330fbcffc1&LABEL_label&SLOT_a` * `DRIVE\UUID_c49183ed-aaec-9bf5-760a-66330fbcffc1&LABEL_label` * `DRIVE\UUID_c49183ed-aaec-9bf5-760a-66330fbcffc1` ## Update Behavior The block device is erased in chunks, written and then read back to verify. ## Quirk Use This plugin uses the following plugin-specific quirk: ### AndroidBootVersionProperty Property to parse from `/proc/cmdline` to retrieve the bootloader version. Since: 1.8.5 ### AndroidBootPartitionMaxSize Maximum size the firmware may use of a partition. Since: 1.8.5 ## Vendor ID Security The vendor ID is set through the `android-boot.quirk` file. ## External Interface Access This plugin requires read/write access to `/dev/block`. ## Version Considerations This plugin has been available since fwupd version `1.8.5`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Dylan Van Assche: @DylanVanAssche fwupd-1.9.16/plugins/android-boot/android-boot.quirk000066400000000000000000000006371460375044200224500ustar00rootroot00000000000000# SHIFT6mq ABL A [DRIVE\UUID_c49183ed-aaec-9bf5-760a-66330fbcffc1&LABEL_abl-a] Flags = updatable,signed-payload Vendor = SHIFT GmbH VendorId = eco.shift AndroidBootVersionProperty = androidboot.abl.revision # SHIFT6mq ABL B [DRIVE\UUID_3d7b21e8-048b-db0b-0c18-d07a9bb32f2d&LABEL_abl-b] Flags = updatable,signed-payload Vendor = SHIFT GmbH VendorId = eco.shift AndroidBootVersionProperty = androidboot.abl.revision fwupd-1.9.16/plugins/android-boot/fu-android-boot-device.c000066400000000000000000000265151460375044200234070ustar00rootroot00000000000000/* * Copyright (C) 2022 Dylan Van Assche * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-android-boot-device.h" #define ANDROID_BOOT_UNKNOWN_VERSION "0.0.0" #define ANDROID_BOOT_SECTOR_SIZE 512 struct _FuAndroidBootDevice { FuUdevDevice parent_instance; gchar *label; gchar *uuid; gchar *boot_slot; guint64 max_size; }; G_DEFINE_TYPE(FuAndroidBootDevice, fu_android_boot_device, FU_TYPE_UDEV_DEVICE) static void fu_android_boot_device_to_string(FuDevice *device, guint idt, GString *str) { FuAndroidBootDevice *self = FU_ANDROID_BOOT_DEVICE(device); fu_string_append(str, idt, "BootSlot", self->boot_slot); fu_string_append(str, idt, "Label", self->label); fu_string_append(str, idt, "UUID", self->uuid); fu_string_append_kx(str, idt, "MaxSize", self->max_size); } static gboolean fu_android_boot_device_probe(FuDevice *device, GError **error) { FuAndroidBootDevice *self = FU_ANDROID_BOOT_DEVICE(device); GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); guint64 sectors = 0; guint64 size = 0; g_autoptr(GHashTable) cmdline = NULL; /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_android_boot_device_parent_class)->probe(device, error)) return FALSE; /* get kernel cmdline */ cmdline = fu_kernel_get_cmdline(error); if (cmdline == NULL) return FALSE; /* set the physical ID */ if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "block", error)) return FALSE; /* extract boot slot if available */ self->boot_slot = g_strdup(g_hash_table_lookup(cmdline, "androidboot.slot_suffix")); /* extract label and check if it matches boot slot*/ if (g_udev_device_has_property(udev_device, "ID_PART_ENTRY_NAME")) { self->label = g_strdup(g_udev_device_get_property(udev_device, "ID_PART_ENTRY_NAME")); /* Use label as device name */ fu_device_set_name(device, self->label); /* If the device has A/B partitioning, compare boot slot to only expose partitions * in-use */ if (self->boot_slot != NULL && !g_str_has_suffix(self->label, self->boot_slot)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device is on a different bootslot"); return FALSE; } } /* set max firmware size, required to avoid writing firmware bigger than partition */ if (!g_udev_device_has_property(udev_device, "ID_PART_ENTRY_SIZE")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device does not expose its size"); return FALSE; } sectors = g_udev_device_get_property_as_uint64(udev_device, "ID_PART_ENTRY_SIZE"); size = sectors * ANDROID_BOOT_SECTOR_SIZE; self->max_size = size; /* extract partition UUID and require it for supporting a device */ if (!g_udev_device_has_property(udev_device, "ID_PART_ENTRY_UUID")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device does not have a UUID"); return FALSE; } self->uuid = g_strdup(g_udev_device_get_property(udev_device, "ID_PART_ENTRY_UUID")); /* extract serial number and set it */ fu_device_set_serial(device, g_hash_table_lookup(cmdline, "androidboot.serialno")); /* * Some devices don't have unique TYPE UUIDs, add the partition label to make them truly * unique Devices have a fixed partition scheme anyway because they originally have Android * which has such requirements. */ fu_device_add_instance_strsafe(device, "UUID", self->uuid); fu_device_add_instance_strsafe(device, "LABEL", self->label); fu_device_add_instance_strsafe(device, "SLOT", self->boot_slot); /* GUID based on UUID / UUID, label / UUID, label, slot */ fu_device_build_instance_id(device, NULL, "DRIVE", "UUID", NULL); fu_device_build_instance_id(device, NULL, "DRIVE", "UUID", "LABEL", NULL); fu_device_build_instance_id(device, NULL, "DRIVE", "UUID", "LABEL", "SLOT", NULL); /* quirks will have matched now */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device is not updatable"); return FALSE; } /* set the firmware maximum size based on partition size or from quirk */ fu_device_set_firmware_size_max(device, self->max_size); return TRUE; } static gboolean fu_android_boot_device_open(FuDevice *device, GError **error) { g_autoptr(GError) error_local = NULL; /* FuUdevDevice->open */ if (!FU_DEVICE_CLASS(fu_android_boot_device_parent_class)->open(device, &error_local)) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, error_local->message); return FALSE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } return TRUE; } static gboolean fu_android_boot_device_write(FuAndroidBootDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); /* rewind */ if (!fu_udev_device_seek(FU_UDEV_DEVICE(self), 0x0, error)) { g_prefix_error(error, "failed to rewind: "); return FALSE; } /* write each chunk */ for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_udev_device_pwrite(FU_UDEV_DEVICE(self), fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to write @0x%x: ", (guint)fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); } return TRUE; } static gboolean fu_android_boot_device_erase(FuAndroidBootDevice *self, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = NULL; gsize bufsz = fu_device_get_firmware_size_max(FU_DEVICE(self)); g_autofree guint8 *buf = g_malloc0(bufsz); g_autoptr(GBytes) fw = g_bytes_new_take(g_steal_pointer(&buf), bufsz); chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 10 * 1024); return fu_android_boot_device_write(self, chunks, progress, error); } static gboolean fu_android_boot_device_verify(FuAndroidBootDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); /* verify each chunk */ for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autofree guint8 *buf = g_malloc0(fu_chunk_get_data_sz(chk)); g_autoptr(GBytes) blob1 = fu_chunk_get_bytes(chk); g_autoptr(GBytes) blob2 = NULL; if (!fu_udev_device_pread(FU_UDEV_DEVICE(self), fu_chunk_get_address(chk), buf, fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to read @0x%x: ", (guint)fu_chunk_get_address(chk)); return FALSE; } blob2 = g_bytes_new_static(buf, fu_chunk_get_data_sz(chk)); if (!fu_bytes_compare(blob1, blob2, error)) { g_prefix_error(error, "failed to verify @0x%x: ", (guint)fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); } return TRUE; } static gboolean fu_android_boot_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuAndroidBootDevice *self = FU_ANDROID_BOOT_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* get data to write */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; fu_dump_bytes(G_LOG_DOMAIN, "write", fw); chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 10 * 1024); fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 72, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 20, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 7, NULL); /* erase, write, verify */ if (!fu_android_boot_device_erase(self, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); if (!fu_android_boot_device_write(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); if (!fu_android_boot_device_verify(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); return TRUE; } static gboolean fu_android_boot_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuAndroidBootDevice *self = FU_ANDROID_BOOT_DEVICE(device); /* load from quirks */ if (g_strcmp0(key, "AndroidBootVersionProperty") == 0) { g_autoptr(GHashTable) cmdline = NULL; const gchar *version = NULL; cmdline = fu_kernel_get_cmdline(error); if (cmdline == NULL) return FALSE; version = g_hash_table_lookup(cmdline, value); if (version != NULL) fu_device_set_version(device, version); return TRUE; } if (g_strcmp0(key, "AndroidBootPartitionMaxSize") == 0) { guint64 size = 0; if (!fu_strtoull(value, &size, 0, G_MAXUINT32, error)) return FALSE; self->max_size = size; return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_android_boot_device_finalize(GObject *obj) { FuAndroidBootDevice *self = FU_ANDROID_BOOT_DEVICE(obj); G_OBJECT_CLASS(fu_android_boot_device_parent_class)->finalize(obj); g_free(self->boot_slot); g_free(self->label); g_free(self->uuid); } static void fu_android_boot_device_init(FuAndroidBootDevice *self) { fu_device_set_summary(FU_DEVICE(self), "Android Bootloader"); fu_device_add_protocol(FU_DEVICE(self), "com.google.android_boot"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | FU_UDEV_DEVICE_FLAG_OPEN_SYNC); fu_device_add_icon(FU_DEVICE(self), "computer"); /* * Fallback for ABL without version reporting, fwupd will always provide an upgrade in this * case. Once upgraded, the version reporting will be available and the update notification * will disappear. If version reporting is available, the reported version is set. */ fu_device_set_version(FU_DEVICE(self), ANDROID_BOOT_UNKNOWN_VERSION); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); } static void fu_android_boot_device_class_init(FuAndroidBootDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *klass_object = G_OBJECT_CLASS(klass); klass_object->finalize = fu_android_boot_device_finalize; klass_device->probe = fu_android_boot_device_probe; klass_device->open = fu_android_boot_device_open; klass_device->write_firmware = fu_android_boot_device_write_firmware; klass_device->to_string = fu_android_boot_device_to_string; klass_device->set_quirk_kv = fu_android_boot_device_set_quirk_kv; } fwupd-1.9.16/plugins/android-boot/fu-android-boot-device.h000066400000000000000000000005451460375044200234070ustar00rootroot00000000000000/* * Copyright (C) 2022 Dylan Van Assche * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ANDROID_BOOT_DEVICE (fu_android_boot_device_get_type()) G_DECLARE_FINAL_TYPE(FuAndroidBootDevice, fu_android_boot_device, FU, ANDROID_BOOT_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/android-boot/fu-android-boot-plugin.c000066400000000000000000000015101460375044200234320ustar00rootroot00000000000000/* * Copyright (C) 2022 Dylan Van Assche * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-android-boot-device.h" #include "fu-android-boot-plugin.h" struct _FuAndroidBootPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuAndroidBootPlugin, fu_android_boot_plugin, FU_TYPE_PLUGIN) static void fu_android_boot_plugin_init(FuAndroidBootPlugin *self) { } static void fu_android_boot_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_ANDROID_BOOT_DEVICE); fu_plugin_add_device_udev_subsystem(plugin, "block"); } static void fu_android_boot_plugin_class_init(FuAndroidBootPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_android_boot_plugin_constructed; } fwupd-1.9.16/plugins/android-boot/fu-android-boot-plugin.h000066400000000000000000000003711460375044200234430ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuAndroidBootPlugin, fu_android_boot_plugin, FU, ANDROID_BOOT_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/android-boot/meson.build000066400000000000000000000010451460375044200211460ustar00rootroot00000000000000if get_option('plugin_android_boot').require(gudev.found(), error_message: 'gudev is needed for plugin_android_boot').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginAndroidBoot"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('android-boot.quirk') plugin_builtins += static_library('fu_plugin_android_boot', sources: [ 'fu-android-boot-plugin.c', 'fu-android-boot-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/ata/000077500000000000000000000000001460375044200151705ustar00rootroot00000000000000fwupd-1.9.16/plugins/ata/README.md000066400000000000000000000031741460375044200164540ustar00rootroot00000000000000--- title: Plugin: ATA --- ## Introduction This plugin allows updating ATA/ATAPI storage hardware. Devices are enumerated from the block devices and if ID_ATA_DOWNLOAD_MICROCODE is supported they can be updated with appropriate firmware file. Updating ATA devices is more dangerous than other hardware such as DFU or NVMe and should be tested carefully with the help of the drive vendor. The device GUID is read from the trimmed model string. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * `org.t13.ata` ## GUID Generation These device use the Microsoft DeviceInstanceId values, e.g. * `IDE\VENDOR[40]REVISION[8]` * `IDE\0VENDOR[40]` See for more details. ## Update Behavior The firmware is deployed when the device is in normal runtime mode, but it is only activated when the system is in the final shutdown stages. This is done to minimize the chance of data loss if the switch to the new firmware is not done correctly. ## Vendor ID Security No vendor ID is set as there is no vendor field in the IDENTIFY response. ## Quirk Use This plugin uses the following plugin-specific quirks: ### AtaTransferBlocks Blocks to transfer, or `0xffff` for max Since: 1.2.4 ### AtaTransferMode The transfer mode, `0x3`, `0x7` or `0xe` Since: 1.2.4 ## External Interface Access This plugin requires the `SG_IO` ioctl interface. ## Version Considerations This plugin has been available since fwupd version `1.2.4`. fwupd-1.9.16/plugins/ata/ata.quirk000066400000000000000000000024331460375044200170140ustar00rootroot00000000000000[ThinkSystem M.2 VD] Flags = ~updatable [OUI\000039] Vendor = Toshiba VendorId = ATA:0x1179 [OUI\0000f0] Vendor = Samsung VendorId = ATA:0x144D [OUI\000120] Vendor = Corsair VendorId = ATA:0x1987 [OUI\0004cf] Vendor = Seagate VendorId = ATA:0x1BB1 [OUI\00080d] Vendor = Toshiba VendorId = ATA:0x1179 [OUI\000c50] Vendor = Seagate VendorId = ATA:0x1BB1 [OUI\000cca] Vendor = Western Digital VendorId = ATA:0x101C [OUI\0014ee] Vendor = Western Digital VendorId = ATA:0x101C [OUI\001517] Vendor = Intel VendorId = ATA:0x8086 [OUI\001b44] Vendor = Western Digital VendorId = ATA:0x101C [OUI\002303] Vendor = LITE-ON VendorId = ATA:0x14A4 [OUI\0024e9] Vendor = Samsung VendorId = ATA:0x144D [OUI\002538] Vendor = Samsung VendorId = ATA:0x144D [OUI\0026b7] Vendor = Kingston VendorId = ATA:0x2646 Flags = needs-shutdown [OUI\00a075] Vendor = Micron VendorId = ATA:0x1344 [OUI\030302] Vendor = SK hynix VendorId = ATA:0x1C5C [OUI\5cd2e4] Vendor = Intel VendorId = ATA:0x8086 [OUI\707c18] Vendor = ADATA VendorId = ATA:0x1CC1 [OUI\7c3548] Vendor = Transcend VendorId = ATA:0x8564 [OUI\001b44] Vendor = SanDisk VendorId = ATA:0x15B7 [OUI\96a060] Vendor = Western Digital VendorId = ATA:0x101C [OUI\f8db4c] Vendor = PNY VendorId = ATA:0x196E [OUI\e83a97] Vendor = Toshiba VendorId = ATA:0x1179 fwupd-1.9.16/plugins/ata/fu-ata-device.c000066400000000000000000000643371460375044200177630ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ata-device.h" #define FU_ATA_IDENTIFY_SIZE 512 /* bytes */ #define FU_ATA_BLOCK_SIZE 512 /* bytes */ struct ata_tf { guint8 dev; guint8 command; guint8 error; guint8 status; guint8 feat; guint8 nsect; guint8 lbal; guint8 lbam; guint8 lbah; }; #define ATA_USING_LBA (1 << 6) #define ATA_STAT_DRQ (1 << 3) #define ATA_STAT_ERR (1 << 0) #define ATA_OP_IDENTIFY 0xec #define ATA_OP_FLUSH_CACHE 0xe7 #define ATA_OP_DOWNLOAD_MICROCODE 0x92 #define ATA_OP_STANDBY_IMMEDIATE 0xe0 #define ATA_SUBCMD_MICROCODE_OBSOLETE 0x01 #define ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS_ACTIVATE 0x03 #define ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNK 0x07 #define ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS 0x0e #define ATA_SUBCMD_MICROCODE_ACTIVATE 0x0f #define SG_CHECK_CONDITION 0x02 #define SG_DRIVER_SENSE 0x08 #define SG_ATA_12 0xa1 #define SG_ATA_12_LEN 12 #define SG_ATA_PROTO_NON_DATA (3 << 1) #define SG_ATA_PROTO_PIO_IN (4 << 1) #define SG_ATA_PROTO_PIO_OUT (5 << 1) #define FU_ATA_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ enum { SG_CDB2_TLEN_NODATA = 0 << 0, SG_CDB2_TLEN_FEAT = 1 << 0, SG_CDB2_TLEN_NSECT = 2 << 0, SG_CDB2_TLEN_BYTES = 0 << 2, SG_CDB2_TLEN_SECTORS = 1 << 2, SG_CDB2_TDIR_TO_DEV = 0 << 3, SG_CDB2_TDIR_FROM_DEV = 1 << 3, SG_CDB2_CHECK_COND = 1 << 5, }; struct _FuAtaDevice { FuUdevDevice parent_instance; guint pci_depth; guint usb_depth; guint16 transfer_blocks; guint8 transfer_mode; guint32 oui; }; G_DEFINE_TYPE(FuAtaDevice, fu_ata_device, FU_TYPE_UDEV_DEVICE) guint8 fu_ata_device_get_transfer_mode(FuAtaDevice *self) { return self->transfer_mode; } guint16 fu_ata_device_get_transfer_blocks(FuAtaDevice *self) { return self->transfer_blocks; } static gchar * fu_ata_device_get_string(const guint16 *buf, guint start, guint end) { g_autoptr(GString) str = g_string_new(NULL); for (guint i = start; i <= end; i++) { g_string_append_c(str, (gchar)(buf[i] >> 8)); g_string_append_c(str, (gchar)(buf[i] & 0xff)); } /* remove whitespace before returning */ if (str->len > 0) { g_strstrip(str->str); if (str->str[0] == '\0') return NULL; } return g_string_free(g_steal_pointer(&str), FALSE); } static void fu_ata_device_to_string(FuDevice *device, guint idt, GString *str) { FuAtaDevice *self = FU_ATA_DEVICE(device); FU_DEVICE_CLASS(fu_ata_device_parent_class)->to_string(device, idt, str); fu_string_append_kx(str, idt, "TransferMode", self->transfer_mode); fu_string_append_kx(str, idt, "TransferBlocks", self->transfer_blocks); if (self->oui != 0x0) fu_string_append_kx(str, idt, "OUI", self->oui); fu_string_append_ku(str, idt, "PciDepth", self->pci_depth); fu_string_append_ku(str, idt, "UsbDepth", self->usb_depth); } /* https://docs.microsoft.com/en-us/windows-hardware/drivers/install/identifiers-for-ide-devices */ static gchar * fu_ata_device_pad_string_for_id(const gchar *name) { GString *str = g_string_new(name); g_string_replace(str, " ", "_", 0); for (guint i = str->len; i < 40; i++) g_string_append_c(str, '_'); return g_string_free(str, FALSE); } static gchar * fu_ata_device_get_guid_safe(const guint16 *buf, guint16 addr_start) { if (!fu_common_guid_is_plausible((((guint8 *)buf) + addr_start))) return NULL; return fwupd_guid_to_string((const fwupd_guid_t *)(((guint8 *)buf) + addr_start), FWUPD_GUID_FLAG_MIXED_ENDIAN); } static void fu_ata_device_parse_id_maybe_dell(FuAtaDevice *self, const guint16 *buf) { g_autofree gchar *component_id = NULL; g_autofree gchar *guid_efi = NULL; g_autofree gchar *guid_id = NULL; g_autofree gchar *guid = NULL; /* add extra component ID if set */ component_id = fu_ata_device_get_string(buf, 137, 140); if (component_id == NULL || !g_str_is_ascii(component_id) || strlen(component_id) < 6) { g_debug("invalid component ID, skipping"); return; } /* do not add the FuUdevDevice instance IDs as generic firmware * should not be used on these OEM-specific devices */ fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_AUTO_INSTANCE_IDS); /* add instance ID *and* GUID as using no-auto-instance-ids */ guid_id = g_strdup_printf("STORAGE-DELL-%s", component_id); fu_device_add_instance_id(FU_DEVICE(self), guid_id); guid = fwupd_guid_hash_string(guid_id); fu_device_add_guid(FU_DEVICE(self), guid); /* also add the EFI GUID */ guid_efi = fu_ata_device_get_guid_safe(buf, 129); if (guid_efi != NULL) fu_device_add_guid(FU_DEVICE(self), guid_efi); /* owned by Dell */ fu_device_set_vendor(FU_DEVICE(self), "Dell"); fu_device_add_vendor_id(FU_DEVICE(self), "ATA:0x1028"); } static void fu_ata_device_parse_vendor_name(FuAtaDevice *self, const gchar *name) { struct { const gchar *prefix; /* in CAPS */ guint16 vid; const gchar *name; } map_name[] = {/* vendor matches */ {"ADATA*", 0x1cc1, "ADATA"}, {"APACER*", 0x0000, "Apacer"}, /* not in pci.ids */ {"APPLE*", 0x106b, "Apple"}, {"CORSAIR*", 0x1987, "Corsair"}, /* identifies as Phison */ {"CRUCIAL*", 0xc0a9, "Crucial"}, {"FUJITSU*", 0x10cf, "Fujitsu"}, {"GIGABYTE*", 0x1458, "Gigabyte"}, {"HGST*", 0x101c, "Western Digital"}, {"HITACHI*", 0x101c, "Western Digital"}, /* was acquired by WD */ {"HITACHI*", 0x1054, "Hitachi"}, {"HP SSD*", 0x103c, "HP"}, {"INTEL*", 0x8086, "Intel"}, {"KINGSPEC*", 0x0000, "KingSpec"}, /* not in pci.ids */ {"KINGSTON*", 0x2646, "Kingston"}, {"LITEON*", 0x14a4, "LITE-ON"}, {"MAXTOR*", 0x115f, "Maxtor"}, {"MICRON*", 0x1344, "Micron"}, {"OCZ*", 0x1179, "Toshiba"}, {"PNY*", 0x196e, "PNY"}, {"QEMU*", 0x1b36, "QEMU"}, /* identifies as Red Hat! */ {"SAMSUNG*", 0x144d, "Samsung"}, {"SANDISK*", 0x15b7, "SanDisk"}, {"SEAGATE*", 0x1bb1, "Seagate"}, { "SK HYNIX*", 0x1c5c, "SK hynix", }, {"SUPERMICRO*", 0x15d9, "SuperMicro"}, {"TOSHIBA*", 0x1179, "Toshiba"}, {"WDC*", 0x101c, "Western Digital"}, {NULL, 0x0000, NULL}}; struct { const gchar *prefix; /* in CAPS */ guint16 vid; const gchar *name; } map_fuzzy[] = {/* fuzzy name matches -- also see legacy list at: * https://github.com/linuxhw/hw-probe/blob/master/hw-probe.pl#L647 */ {"001-*", 0x1bb1, "Seagate"}, {"726060*", 0x101c, "Western Digital"}, {"CT*", 0xc0a9, "Crucial"}, {"DT0*", 0x1179, "Toshiba"}, {"EK0*", 0x1590, "HPE"}, {"EZEX*", 0x101c, "Western Digital"}, {"GB0*", 0x1590, "HPE"}, {"GOODRAM*", 0x1987, "Phison"}, {"H??54*", 0x101c, "Western Digital"}, {"H??72?0*", 0x101c, "Western Digital"}, {"HDWG*", 0x1179, "Toshiba"}, {"M?0??CA*", 0x1179, "Toshiba"}, /* enterprise */ {"M4-CT*", 0xc0a9, "Crucial"}, { "MA*", 0x10cf, "Fujitsu", }, { "MB*", 0x10cf, "Fujitsu", }, {"MK0*", 0x1590, "HPE"}, {"MTFDDAK*", 0x1344, "Micron"}, { "NIM*", 0x0000, "Nimbus", }, /* no PCI ID */ { "SATADOM*", 0x0000, "Innodisk", }, /* no PCI ID */ {"SSD 860*", 0x144d, "Samsung"}, {"SSDPR*", 0x1987, "Phison"}, {"SSDSC?K*", 0x8086, "Intel"}, { "ST*", 0x1bb1, "Seagate", }, {"TEAM*", 0x0000, "Team Group"}, /* not in pci.ids */ {"TS*", 0x8564, "Transcend"}, {"VK0*", 0x1590, "HPE"}, {"WD*", 0x101c, "Western Digital"}, {NULL, 0x0000, NULL}}; struct { const gchar *prefix; /* in CAPS */ guint16 vid; const gchar *name; } map_version[] = {/* fuzzy version matches */ {"CS2111*", 0x196e, "PNY"}, {"S?FM*", 0x1987, "Phison"}, {NULL, 0x0000, NULL}}; g_autofree gchar *name_up = g_ascii_strup(name, -1); g_autofree gchar *vendor_id = NULL; /* find match */ for (guint i = 0; map_name[i].prefix != NULL; i++) { if (g_pattern_match_simple(map_name[i].prefix, name_up)) { name += strlen(map_name[i].prefix) - 1; fu_device_set_vendor(FU_DEVICE(self), map_name[i].name); vendor_id = g_strdup_printf("ATA:0x%X", map_name[i].vid); break; } } /* fall back to fuzzy match */ if (vendor_id == NULL) { for (guint i = 0; map_fuzzy[i].prefix != NULL; i++) { if (g_pattern_match_simple(map_fuzzy[i].prefix, name_up)) { fu_device_set_vendor(FU_DEVICE(self), map_fuzzy[i].name); vendor_id = g_strdup_printf("ATA:0x%X", map_fuzzy[i].vid); break; } } } /* fall back to version */ if (vendor_id == NULL) { g_autofree gchar *version_up = g_ascii_strup(fu_device_get_version(FU_DEVICE(self)), -1); for (guint i = 0; map_version[i].prefix != NULL; i++) { if (g_pattern_match_simple(map_version[i].prefix, version_up)) { fu_device_set_vendor(FU_DEVICE(self), map_version[i].name); vendor_id = g_strdup_printf("ATA:0x%X", map_version[i].vid); break; } } } /* devices without a vendor ID will not be UPGRADABLE */ if (vendor_id != NULL) fu_device_add_vendor_id(FU_DEVICE(self), vendor_id); /* remove leading junk */ while (name[0] == ' ' || name[0] == '_' || name[0] == '-') name += 1; /* if changed */ if (g_strcmp0(fu_device_get_name(FU_DEVICE(self)), name) != 0) fu_device_set_name(FU_DEVICE(self), name); } static gboolean fu_ata_device_parse_id(FuAtaDevice *self, const guint8 *buf, gsize sz, GError **error) { FuDevice *device = FU_DEVICE(self); gboolean has_oui_quirk = FALSE; guint16 xfer_min = 1; guint16 xfer_max = 0xffff; guint16 id[FU_ATA_IDENTIFY_SIZE / 2]; g_autofree gchar *name = NULL; g_autofree gchar *sku = NULL; /* check size */ if (sz != FU_ATA_IDENTIFY_SIZE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "ID incorrect size, got 0x%02x", (guint)sz); return FALSE; } /* read LE buffer */ for (guint i = 0; i < sz / 2; i++) id[i] = fu_memread_uint16(buf + (i * 2), G_LITTLE_ENDIAN); /* verify drive correctly supports DOWNLOAD_MICROCODE */ if (!(id[83] & 1 && id[86] & 1)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "DOWNLOAD_MICROCODE not supported by device"); return FALSE; } fu_ata_device_parse_id_maybe_dell(self, id); /* firmware will be applied when the device restarts */ if (self->transfer_mode == ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); /* the newer, segmented transfer mode */ if (self->transfer_mode == ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS_ACTIVATE || self->transfer_mode == ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS) { xfer_min = id[234]; if (xfer_min == 0x0 || xfer_min == 0xffff) xfer_min = 1; xfer_max = id[235]; if (xfer_max == 0x0 || xfer_max == 0xffff) xfer_max = xfer_min; } /* fall back to a sane block size */ if (self->transfer_blocks == 0x0) self->transfer_blocks = xfer_min; else if (self->transfer_blocks == 0xffff) self->transfer_blocks = xfer_max; /* get values in case the kernel didn't */ if (fu_device_get_serial(device) == NULL) { g_autofree gchar *tmp = NULL; tmp = fu_ata_device_get_string(id, 10, 19); if (tmp != NULL) fu_device_set_serial(device, tmp); } if (fu_device_get_version(device) == NULL) { g_autofree gchar *tmp = NULL; tmp = fu_ata_device_get_string(id, 23, 26); if (tmp != NULL) fu_device_set_version(device, tmp); } /* get OUI if set */ self->oui = ((guint32)(id[108] & 0x0fff)) << 12 | ((guint32)(id[109] & 0xfff0)) >> 4; if (self->oui > 0x0) { g_autofree gchar *tmp = NULL; tmp = g_strdup_printf("OUI\\%06x", self->oui); fu_device_add_instance_id_full(device, tmp, FU_DEVICE_INSTANCE_FLAG_QUIRKS); has_oui_quirk = fu_device_get_vendor(FU_DEVICE(self)) != NULL; } if (self->oui > 0x0) { g_autofree gchar *vendor_id = NULL; vendor_id = g_strdup_printf("OUI:%06x", self->oui); fu_device_add_vendor_id(device, vendor_id); } /* if not already set using the vendor block or a OUI quirk */ name = fu_ata_device_get_string(id, 27, 46); if (name != NULL) { /* use the name as-is */ if (has_oui_quirk) { fu_device_set_name(FU_DEVICE(self), name); } else { fu_ata_device_parse_vendor_name(self, name); } } /* 8 byte additional product identifier == SKU? */ sku = fu_ata_device_get_string(id, 170, 173); if (sku != NULL) g_debug("SKU=%s", sku); /* add extra GUIDs if none detected from identify block */ if (name != NULL && fu_device_get_guids(device)->len == 0) { g_autofree gchar *name_pad = fu_ata_device_pad_string_for_id(name); if (name_pad != NULL && fu_device_get_version(device) != NULL) { g_autofree gchar *tmp = NULL; tmp = g_strdup_printf("IDE\\%s%s", name_pad, fu_device_get_version(device)); fu_device_add_instance_id(device, tmp); } if (name_pad != NULL) { g_autofree gchar *tmp = NULL; tmp = g_strdup_printf("IDE\\0%s", name_pad); fu_device_add_instance_id(device, tmp); } /* add the name fallback */ fu_device_add_instance_id(device, name); } /* for Phison this is per-chipset -- which is specified in the version prefix */ if (g_strcmp0(fu_device_get_vendor(device), "Phison") == 0 && fu_device_get_version(device) != NULL) { if (g_str_has_prefix(fu_device_get_version(device), "SB")) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); } else if (g_str_has_prefix(fu_device_get_version(device), "SC") || g_str_has_prefix(fu_device_get_version(device), "SH")) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); } } return TRUE; } static gboolean fu_ata_device_probe(FuDevice *device, GError **error) { FuAtaDevice *self = FU_ATA_DEVICE(device); GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); /* check is valid */ if (g_strcmp0(g_udev_device_get_devtype(udev_device), "disk") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct devtype=%s, expected disk", g_udev_device_get_devtype(udev_device)); return FALSE; } if (!g_udev_device_get_property_as_boolean(udev_device, "ID_ATA_SATA") || !g_udev_device_get_property_as_boolean(udev_device, "ID_ATA_DOWNLOAD_MICROCODE")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "has no ID_ATA_DOWNLOAD_MICROCODE"); return FALSE; } /* set the physical ID */ if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "scsi", error)) return FALSE; /* look at the PCI and USB depth to work out if in an external enclosure */ self->pci_depth = fu_udev_device_get_slot_depth(FU_UDEV_DEVICE(device), "pci"); self->usb_depth = fu_udev_device_get_slot_depth(FU_UDEV_DEVICE(device), "usb"); if (self->pci_depth <= 2 && self->usb_depth <= 2) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); } return TRUE; } static guint64 fu_ata_device_tf_to_pack_id(struct ata_tf *tf) { guint32 lba24 = (tf->lbah << 16) | (tf->lbam << 8) | (tf->lbal); guint32 lbah = tf->dev & 0x0f; return (((guint64)lbah) << 24) | (guint64)lba24; } static gboolean fu_ata_device_command(FuAtaDevice *self, struct ata_tf *tf, gint dxfer_direction, guint timeout_ms, guint8 *dxferp, gsize dxfer_len, GError **error) { guint8 cdb[SG_ATA_12_LEN] = {0x0}; guint8 sb[32] = {0x0}; sg_io_hdr_t io_hdr = {0x0}; /* map _TO_DEV to PIO mode */ if (dxfer_direction == SG_DXFER_TO_DEV) cdb[1] = SG_ATA_PROTO_PIO_OUT; else if (dxfer_direction == SG_DXFER_FROM_DEV) cdb[1] = SG_ATA_PROTO_PIO_IN; else cdb[1] = SG_ATA_PROTO_NON_DATA; /* libata workaround: don't demand sense data for IDENTIFY */ if (dxfer_len > 0) { cdb[2] |= SG_CDB2_TLEN_NSECT | SG_CDB2_TLEN_SECTORS; cdb[2] |= dxfer_direction == SG_DXFER_TO_DEV ? SG_CDB2_TDIR_TO_DEV : SG_CDB2_TDIR_FROM_DEV; } else { cdb[2] = SG_CDB2_CHECK_COND; } /* populate non-LBA48 CDB */ cdb[0] = SG_ATA_12; cdb[3] = tf->feat; cdb[4] = tf->nsect; cdb[5] = tf->lbal; cdb[6] = tf->lbam; cdb[7] = tf->lbah; cdb[8] = tf->dev; cdb[9] = tf->command; fu_dump_raw(G_LOG_DOMAIN, "CDB", cdb, sizeof(cdb)); if (dxfer_direction == SG_DXFER_TO_DEV && dxferp != NULL) fu_dump_raw(G_LOG_DOMAIN, "outgoing_data", dxferp, dxfer_len); /* hit hardware */ io_hdr.interface_id = 'S'; io_hdr.mx_sb_len = sizeof(sb); io_hdr.dxfer_direction = dxfer_direction; io_hdr.dxfer_len = dxfer_len; io_hdr.dxferp = dxferp; io_hdr.cmdp = cdb; io_hdr.cmd_len = SG_ATA_12_LEN; io_hdr.sbp = sb; io_hdr.pack_id = fu_ata_device_tf_to_pack_id(tf); io_hdr.timeout = timeout_ms; if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), SG_IO, (guint8 *)&io_hdr, NULL, FU_ATA_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; g_debug("ATA_%u status=0x%x, host_status=0x%x, driver_status=0x%x", io_hdr.cmd_len, io_hdr.status, io_hdr.host_status, io_hdr.driver_status); fu_dump_raw(G_LOG_DOMAIN, "SB", sb, sizeof(sb)); /* error check */ if (io_hdr.status && io_hdr.status != SG_CHECK_CONDITION) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "bad status: 0x%x", io_hdr.status); return FALSE; } if (io_hdr.host_status) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "bad host status: 0x%x", io_hdr.host_status); return FALSE; } if (io_hdr.driver_status && (io_hdr.driver_status != SG_DRIVER_SENSE)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "bad driver status: 0x%x", io_hdr.driver_status); return FALSE; } /* repopulate ata_tf */ tf->error = sb[8 + 3]; tf->nsect = sb[8 + 5]; tf->lbal = sb[8 + 7]; tf->lbam = sb[8 + 9]; tf->lbah = sb[8 + 11]; tf->dev = sb[8 + 12]; tf->status = sb[8 + 13]; g_debug("ATA_%u stat=%02x err=%02x nsect=%02x lbal=%02x " "lbam=%02x lbah=%02x dev=%02x", io_hdr.cmd_len, tf->status, tf->error, tf->nsect, tf->lbal, tf->lbam, tf->lbah, tf->dev); /* io error */ if (tf->status & (ATA_STAT_ERR | ATA_STAT_DRQ)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "I/O error, ata_op=0x%02x ata_status=0x%02x ata_error=0x%02x", tf->command, tf->status, tf->error); return FALSE; } /* success */ return TRUE; } static gboolean fu_ata_device_setup(FuDevice *device, GError **error) { FuAtaDevice *self = FU_ATA_DEVICE(device); struct ata_tf tf = {0x0}; guint8 id[FU_ATA_IDENTIFY_SIZE]; /* get ID block */ tf.dev = ATA_USING_LBA; tf.command = ATA_OP_IDENTIFY; tf.nsect = 1; /* 512 bytes */ if (!fu_ata_device_command(self, &tf, SG_DXFER_FROM_DEV, 1000, id, sizeof(id), error)) { g_prefix_error(error, "failed to IDENTIFY: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "IDENTIFY", id, sizeof(id)); if (!fu_ata_device_parse_id(self, id, sizeof(id), error)) return FALSE; /* success */ return TRUE; } static gboolean fu_ata_device_activate(FuDevice *device, FuProgress *progress, GError **error) { FuAtaDevice *self = FU_ATA_DEVICE(device); struct ata_tf tf = {0x0}; /* flush cache and put drive in standby to prepare to activate */ tf.dev = ATA_USING_LBA; tf.command = ATA_OP_FLUSH_CACHE; if (!fu_ata_device_command(self, &tf, SG_DXFER_NONE, 120 * 1000, /* a long time! */ NULL, 0, error)) { g_prefix_error(error, "failed to flush cache immediate: "); return FALSE; } tf.command = ATA_OP_STANDBY_IMMEDIATE; if (!fu_ata_device_command(self, &tf, SG_DXFER_NONE, 120 * 1000, /* a long time! */ NULL, 0, error)) { g_prefix_error(error, "failed to standby immediate: "); return FALSE; } /* load the new firmware */ tf.dev = 0xa0 | ATA_USING_LBA; tf.command = ATA_OP_DOWNLOAD_MICROCODE; tf.feat = ATA_SUBCMD_MICROCODE_ACTIVATE; if (!fu_ata_device_command(self, &tf, SG_DXFER_NONE, 120 * 1000, /* a long time! */ NULL, 0, error)) { g_prefix_error(error, "failed to activate firmware: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_ata_device_fw_download(FuAtaDevice *self, guint32 idx, guint32 addr, const guint8 *data, guint32 data_sz, GError **error) { struct ata_tf tf = {0x0}; guint32 block_count = data_sz / FU_ATA_BLOCK_SIZE; guint32 buffer_offset = addr / FU_ATA_BLOCK_SIZE; /* write block */ tf.dev = 0xa0 | ATA_USING_LBA; tf.command = ATA_OP_DOWNLOAD_MICROCODE; tf.feat = self->transfer_mode; tf.nsect = block_count & 0xff; tf.lbal = block_count >> 8; tf.lbam = buffer_offset & 0xff; tf.lbah = buffer_offset >> 8; if (!fu_ata_device_command(self, &tf, SG_DXFER_TO_DEV, 120 * 1000, /* a long time! */ (guint8 *)data, data_sz, error)) { g_prefix_error(error, "failed to write firmware @0x%0x: ", (guint)addr); return FALSE; } /* check drive status */ if (tf.nsect == 0x0) return TRUE; /* drive wants more data, or thinks it is all done */ if (tf.nsect == 0x1 || tf.nsect == 0x2) return TRUE; /* the offset was set up incorrectly */ if (tf.nsect == 0x4) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "alignment error"); return FALSE; } /* other error */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unknown return code 0x%02x", tf.nsect); return FALSE; } static gboolean fu_ata_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuAtaDevice *self = FU_ATA_DEVICE(device); guint32 chunksz = (guint32)self->transfer_blocks * FU_ATA_BLOCK_SIZE; guint max_size = 0xffff * FU_ATA_BLOCK_SIZE; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* only one block allowed */ if (self->transfer_mode == ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNK) max_size = 0xffff; /* check is valid */ if (g_bytes_get_size(fw) > max_size) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware is too large, maximum size is %u", max_size); return FALSE; } if (g_bytes_get_size(fw) % FU_ATA_BLOCK_SIZE != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware is not multiple of block size %i", FU_ATA_BLOCK_SIZE); return FALSE; } /* write each block */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); chunks = fu_chunk_array_new_from_bytes(fw, 0x00, chunksz); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_ata_device_fw_download(self, fu_chunk_get_idx(chk), fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to write chunk %u: ", i); return FALSE; } fu_progress_step_done(progress); } /* success! */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); return TRUE; } static gboolean fu_ata_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuAtaDevice *self = FU_ATA_DEVICE(device); guint64 tmp = 0; if (g_strcmp0(key, "AtaTransferMode") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; if (tmp != ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS_ACTIVATE && tmp != ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS && tmp != ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNK) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "AtaTransferMode only supports " "values 0x3, 0x7 or 0xe"); return FALSE; } self->transfer_mode = (guint8)tmp; return TRUE; } if (g_strcmp0(key, "AtaTransferBlocks") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->transfer_blocks = (guint16)tmp; return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_ata_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_ata_device_init(FuAtaDevice *self) { /* we chose this default as _DOWNLOAD_CHUNKS_ACTIVATE applies the * firmware straight away and the kernel might not like the unexpected * ATA restart and panic */ self->transfer_mode = ATA_SUBCMD_MICROCODE_DOWNLOAD_CHUNKS; fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_INHERIT_ACTIVATION); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS); fu_device_set_summary(FU_DEVICE(self), "ATA drive"); fu_device_add_icon(FU_DEVICE(self), "drive-harddisk"); fu_device_add_protocol(FU_DEVICE(self), "org.t13.ata"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ); } static void fu_ata_device_finalize(GObject *object) { G_OBJECT_CLASS(fu_ata_device_parent_class)->finalize(object); } static void fu_ata_device_class_init(FuAtaDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_ata_device_finalize; klass_device->to_string = fu_ata_device_to_string; klass_device->set_quirk_kv = fu_ata_device_set_quirk_kv; klass_device->setup = fu_ata_device_setup; klass_device->activate = fu_ata_device_activate; klass_device->write_firmware = fu_ata_device_write_firmware; klass_device->probe = fu_ata_device_probe; klass_device->set_progress = fu_ata_device_set_progress; } FuAtaDevice * fu_ata_device_new_from_blob(FuContext *ctx, const guint8 *buf, gsize sz, GError **error) { g_autoptr(FuAtaDevice) self = NULL; self = g_object_new(FU_TYPE_ATA_DEVICE, "context", ctx, NULL); if (!fu_ata_device_parse_id(self, buf, sz, error)) return NULL; return g_steal_pointer(&self); } fwupd-1.9.16/plugins/ata/fu-ata-device.h000066400000000000000000000010211460375044200177450ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ATA_DEVICE (fu_ata_device_get_type()) G_DECLARE_FINAL_TYPE(FuAtaDevice, fu_ata_device, FU, ATA_DEVICE, FuUdevDevice) FuAtaDevice * fu_ata_device_new_from_blob(FuContext *ctx, const guint8 *buf, gsize sz, GError **error); /* for self tests */ guint8 fu_ata_device_get_transfer_mode(FuAtaDevice *self); guint16 fu_ata_device_get_transfer_blocks(FuAtaDevice *self); fwupd-1.9.16/plugins/ata/fu-ata-plugin.c000066400000000000000000000013351460375044200200070ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ata-device.h" #include "fu-ata-plugin.h" struct _FuAtaPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuAtaPlugin, fu_ata_plugin, FU_TYPE_PLUGIN) static void fu_ata_plugin_init(FuAtaPlugin *self) { } static void fu_ata_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_udev_subsystem(plugin, "block"); fu_plugin_add_device_gtype(plugin, FU_TYPE_ATA_DEVICE); } static void fu_ata_plugin_class_init(FuAtaPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_ata_plugin_constructed; } fwupd-1.9.16/plugins/ata/fu-ata-plugin.h000066400000000000000000000003371460375044200200150ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuAtaPlugin, fu_ata_plugin, FU, ATA_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/ata/fu-self-test.c000066400000000000000000000064641460375044200176640ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ata-device.h" #include "fu-context-private.h" #include "fu-device-private.h" static void fu_ata_id_func(void) { gboolean ret; gsize sz; const gchar *ci = g_getenv("CI_NETWORK"); g_autofree gchar *data = NULL; g_autofree gchar *path = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuAtaDevice) dev = NULL; g_autoptr(GError) error = NULL; ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); path = g_test_build_filename(G_TEST_DIST, "tests", "StarDrive-SBFM61.2.bin", NULL); if (!g_file_test(path, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing StarDrive-SBFM61.2.bin"); return; } ret = g_file_get_contents(path, &data, &sz, &error); g_assert_no_error(error); g_assert_true(ret); dev = fu_ata_device_new_from_blob(ctx, (guint8 *)data, sz, &error); g_assert_no_error(error); g_assert_nonnull(dev); g_assert_cmpint(fu_ata_device_get_transfer_mode(dev), ==, 0xe); g_assert_cmpint(fu_ata_device_get_transfer_blocks(dev), ==, 0x1); g_assert_cmpstr(fu_device_get_serial(FU_DEVICE(dev)), ==, "A45A078A198600476509"); g_assert_cmpstr(fu_device_get_name(FU_DEVICE(dev)), ==, "SATA SSD"); g_assert_cmpstr(fu_device_get_version(FU_DEVICE(dev)), ==, "SBFM61.2"); } static void fu_ata_oui_func(void) { gboolean ret; gsize sz; const gchar *ci = g_getenv("CI_NETWORK"); g_autofree gchar *data = NULL; g_autofree gchar *path = NULL; g_autofree gchar *str = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuAtaDevice) dev = NULL; g_autoptr(GError) error = NULL; ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); path = g_test_build_filename(G_TEST_DIST, "tests", "Samsung SSD 860 EVO 500GB.bin", NULL); if (!g_file_test(path, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing Samsung SSD 860 EVO 500GB.bin"); return; } ret = g_file_get_contents(path, &data, &sz, &error); g_assert_no_error(error); g_assert_true(ret); dev = fu_ata_device_new_from_blob(ctx, (guint8 *)data, sz, &error); g_assert_no_error(error); g_assert_nonnull(dev); fu_device_convert_instance_ids(FU_DEVICE(dev)); str = fu_device_to_string(FU_DEVICE(dev)); g_debug("%s", str); g_assert_cmpint(fu_ata_device_get_transfer_mode(dev), ==, 0xe); g_assert_cmpint(fu_ata_device_get_transfer_blocks(dev), ==, 0x1); g_assert_cmpstr(fu_device_get_serial(FU_DEVICE(dev)), ==, "S3Z1NB0K862928X"); g_assert_cmpstr(fu_device_get_name(FU_DEVICE(dev)), ==, "SSD 860 EVO 500GB"); g_assert_cmpstr(fu_device_get_version(FU_DEVICE(dev)), ==, "RVT01B6Q"); } int main(int argc, char **argv) { g_autofree gchar *testdatadir = NULL; (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); (void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", testdatadir, TRUE); /* tests go here */ g_test_add_func("/fwupd/ata/id", fu_ata_id_func); g_test_add_func("/fwupd/ata/oui", fu_ata_oui_func); return g_test_run(); } fwupd-1.9.16/plugins/ata/meson.build000066400000000000000000000022261460375044200173340ustar00rootroot00000000000000if gudev.found() cargs = ['-DG_LOG_DOMAIN="FuPluginAta"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('ata.quirk') plugin_builtin_ata = static_library('fu_plugin_ata', sources: [ 'fu-ata-plugin.c', 'fu-ata-device.c', ], include_directories: plugin_incdirs, c_args: [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], link_with: plugin_libs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_ata if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) env.set('FWUPD_DATADIR_QUIRKS', meson.current_source_dir()) e = executable( 'ata-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_ata, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('ata-self-test', e, env: env) # added to installed-tests endif endif fwupd-1.9.16/plugins/audio-s5gen2/000077500000000000000000000000001460375044200166255ustar00rootroot00000000000000fwupd-1.9.16/plugins/audio-s5gen2/README.md000066400000000000000000000026501460375044200201070ustar00rootroot00000000000000--- title: Plugin: audio-s5gen2 --- ## Introduction Firmware Update Plug-in for Qualcomm Voice & Music Series 5 Gen 1 and Gen 2, and Series 3 Gen 1, Gen 2 and Gen 3. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. The DFU file format is covered in documentation from Qualcomm, referenced by 80-CH281-1. This plugin supports the following protocol ID: * `com.qualcomm.s5gen2` ## GUID Generation These devices use the standard DeviceInstanceId values, e.g. * `USB\VID_0A12&PID_4007` ## Update Behavior The device is updated in runtime mode and rebooted with a new version. The new firmware is saved after commit command, otherwise the device is rebooted with a previous version. The upgrade protocol and update behivior are specified in documentation from Qualcomm, referenced by 80-CH281-1 and 80-CU043-1. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x0A12` ## Quirk Use This plugin uses the following plugin-specific quirks: * no specific quirks ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.9.16`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Denis Pynkin: @d4s fwupd-1.9.16/plugins/audio-s5gen2/audio-s5gen2.quirk000066400000000000000000000001701460375044200221020ustar00rootroot00000000000000# 5171 devboard [USB\VID_0A12&PID_4007] Plugin = audio_s5gen2 Flags = enforce-requires ProxyGType = FuQcS5gen2HidDevice fwupd-1.9.16/plugins/audio-s5gen2/fu-audio-s5gen2-device.c000066400000000000000000000505401460375044200230440ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-audio-s5gen2-device.h" #include "fu-audio-s5gen2-firmware.h" #include "fu-audio-s5gen2-impl.h" #include "fu-audio-s5gen2-struct.h" #define FU_QC_S5GEN2_DEVICE_DATA_REQ_SLEEP 1000 /* ms */ #define FU_QC_S5GEN2_DEVICE_SEND_DELAY 2 /* ms */ /* 100ms delay requested by device as a rule */ #define FU_QC_S5GEN2_DEVICE_VALIDATION_RETRIES (60000 / 100) struct _FuQcS5gen2Device { FuDevice parent_instance; guint32 file_id; guint8 file_version; guint16 battery_raw; }; G_DEFINE_TYPE(FuQcS5gen2Device, fu_qc_s5gen2_device, FU_TYPE_DEVICE) static void fu_qc_s5gen2_device_to_string(FuDevice *device, guint idt, GString *str) { FuQcS5gen2Device *self = FU_QC_S5GEN2_DEVICE(device); fu_string_append_kx(str, idt, "FileId", self->file_id); fu_string_append_kx(str, idt, "FileVersion", self->file_version); fu_string_append_kx(str, idt, "BatteryRaw", self->battery_raw); } static gboolean fu_qc_s5gen2_device_msg_out(FuQcS5gen2Device *self, guint8 *data, gsize data_len, GError **error) { FuDevice *proxy = fu_device_get_proxy(FU_DEVICE(self)); if (proxy == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no proxy"); return FALSE; } return fu_qc_s5gen2_impl_msg_out(FU_QC_S5GEN2_IMPL(proxy), data, data_len, error); } static gboolean fu_qc_s5gen2_device_msg_in(FuQcS5gen2Device *self, guint8 *data_in, gsize data_len, GError **error) { FuDevice *proxy = fu_device_get_proxy(FU_DEVICE(self)); if (proxy == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no proxy"); return FALSE; } return fu_qc_s5gen2_impl_msg_in(FU_QC_S5GEN2_IMPL(proxy), data_in, data_len, error); } static gboolean fu_qc_s5gen2_device_msg_cmd(FuQcS5gen2Device *self, guint8 *data, gsize data_len, GError **error) { FuDevice *proxy = fu_device_get_proxy(FU_DEVICE(self)); if (proxy == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no proxy"); return FALSE; } return fu_qc_s5gen2_impl_msg_cmd(FU_QC_S5GEN2_IMPL(proxy), data, data_len, error); } static gboolean fu_qc_s5gen2_device_cmd_req_disconnect(FuQcS5gen2Device *self, GError **error) { g_autoptr(GByteArray) req = fu_struct_qc_disconnect_req_new(); return fu_qc_s5gen2_device_msg_cmd(self, req->data, req->len, error); } static gboolean fu_qc_s5gen2_device_cmd_req_connect(FuQcS5gen2Device *self, GError **error) { guint8 data_in[FU_STRUCT_QC_UPDATE_STATUS_SIZE] = {0x0}; FuQcStatus update_status; g_autoptr(GByteArray) req = fu_struct_qc_connect_req_new(); g_autoptr(GByteArray) st = NULL; if (!fu_qc_s5gen2_device_msg_cmd(self, req->data, req->len, error)) return FALSE; if (!fu_qc_s5gen2_device_msg_in(self, data_in, sizeof(data_in), error)) return FALSE; st = fu_struct_qc_update_status_parse(data_in, sizeof(data_in), 0, error); if (st == NULL) return FALSE; update_status = fu_struct_qc_update_status_get_status(st); switch (update_status) { case FU_QC_STATUS_SUCCESS: break; case FU_QC_STATUS_ALREADY_CONNECTED_WARNING: g_info("device is already connected"); /* FIXME: continue the previous update for wireless * atm fail for USB */ g_set_error(error, FWUPD_ERROR, G_IO_ERROR_INVALID_DATA, "device is already connected"); return FALSE; default: g_set_error(error, FWUPD_ERROR, G_IO_ERROR_INVALID_DATA, "invalid update status (%s)", fu_qc_status_to_string(update_status)); return FALSE; } return TRUE; } static gboolean fu_qc_s5gen2_device_cmd_abort(FuQcS5gen2Device *self, GError **error) { guint8 data[FU_STRUCT_QC_ABORT_SIZE] = {0}; g_autoptr(GByteArray) req = fu_struct_qc_abort_req_new(); g_autoptr(GByteArray) reply = NULL; if (!fu_qc_s5gen2_device_msg_out(self, req->data, req->len, error)) return FALSE; if (!fu_qc_s5gen2_device_msg_in(self, data, sizeof(data), error)) return FALSE; reply = fu_struct_qc_abort_parse(data, sizeof(data), 0, error); if (reply == NULL) return FALSE; return TRUE; } static gboolean fu_qc_s5gen2_device_cmd_sync(FuQcS5gen2Device *self, GError **error) { guint8 data[FU_STRUCT_QC_SYNC_SIZE] = {0}; FuQcResumePoint rp; g_autoptr(GByteArray) req = fu_struct_qc_sync_req_new(); g_autoptr(GByteArray) reply = NULL; fu_struct_qc_sync_req_set_file_id(req, self->file_id); if (!fu_qc_s5gen2_device_msg_out(self, req->data, req->len, error)) return FALSE; if (!fu_qc_s5gen2_device_msg_in(self, data, sizeof(data), error)) return FALSE; /* FIXME: correct error handling -- move to msg_in()? */ if (data[0] == 0x11) { g_set_error(error, FWUPD_ERROR, G_IO_ERROR_INVALID_DATA, "unexpected error (0x%.02X)", data[0]); return FALSE; } reply = fu_struct_qc_sync_parse(data, sizeof(data), 0, error); if (reply == NULL) return FALSE; if (self->file_version != fu_struct_qc_sync_get_protocol_version(reply)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unsupported firmware protocol version on device %u, expected %u", fu_struct_qc_sync_get_protocol_version(reply), self->file_version); return FALSE; } rp = fu_struct_qc_sync_get_resume_point(reply); switch (rp) { case FU_QC_RESUME_POINT_START: case FU_QC_RESUME_POINT_POST_REBOOT: break; default: g_set_error(error, FWUPD_ERROR, G_IO_ERROR_INVALID_DATA, "unexpected resume point (%s)", fu_qc_resume_point_to_string(rp)); return FALSE; } if (self->file_id != fu_struct_qc_sync_get_file_id(reply)) { g_set_error(error, FWUPD_ERROR, G_IO_ERROR_INVALID_DATA, "unexpected file ID from the device (%u), expected (%u)", fu_struct_qc_sync_get_file_id(reply), self->file_id); return FALSE; } return TRUE; } static gboolean fu_qc_s5gen2_device_cmd_start(FuQcS5gen2Device *self, GError **error) { guint8 data[FU_STRUCT_QC_START_SIZE] = {0}; FuQcStartStatus status; g_autoptr(GByteArray) req = fu_struct_qc_start_req_new(); g_autoptr(GByteArray) reply = NULL; if (!fu_qc_s5gen2_device_msg_out(self, req->data, req->len, error)) return FALSE; if (!fu_qc_s5gen2_device_msg_in(self, data, sizeof(data), error)) return FALSE; reply = fu_struct_qc_start_parse(data, sizeof(data), 0, error); if (reply == NULL) return FALSE; status = fu_struct_qc_start_get_status(reply); if (status != FU_QC_START_STATUS_SUCCESS) { g_set_error(error, FWUPD_ERROR, G_IO_ERROR_INVALID_DATA, "status failure in upgrade (%s)", fu_qc_start_status_to_string(status)); return FALSE; } /* check battery */ self->battery_raw = fu_struct_qc_start_get_battery_level(reply); /* FIXME: calculate and set real percentage here. * For now just pass the threshold. */ fu_device_set_battery_level(FU_DEVICE(self), 100); return TRUE; } static gboolean fu_qc_s5gen2_device_cmd_start_data(FuQcS5gen2Device *self, GError **error) { g_autoptr(GByteArray) req = fu_struct_qc_start_data_req_new(); if (!fu_qc_s5gen2_device_msg_out(self, req->data, req->len, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), FU_QC_S5GEN2_DEVICE_DATA_REQ_SLEEP); return TRUE; } static gboolean fu_qc_s5gen2_device_cmd_validation(FuQcS5gen2Device *self, GError **error) { FuQcOpcode opcode; guint16 delay_ms; guint8 data[FU_STRUCT_QC_VALIDATION_SIZE] = {0}; g_autoptr(GByteArray) req = fu_struct_qc_validation_req_new(); g_autoptr(GByteArray) reply = NULL; if (!fu_qc_s5gen2_device_msg_out(self, req->data, req->len, error)) return FALSE; if (!fu_qc_s5gen2_device_msg_in(self, data, sizeof(data), error)) return FALSE; /* do not care about FU_QC_OPCODE_TRANSFER_COMPLETE_IND format */ reply = fu_struct_qc_validation_parse(data, sizeof(data), 0, error); if (reply == NULL) return FALSE; opcode = fu_struct_qc_validation_get_opcode(reply); switch (opcode) { case FU_QC_OPCODE_TRANSFER_COMPLETE_IND: break; case FU_QC_OPCODE_IS_VALIDATION_DONE_CFM: delay_ms = fu_struct_qc_validation_get_delay(reply); g_set_error(error, FWUPD_ERROR, G_IO_ERROR_INVALID_DATA, "validation of the image is not complete, waiting (%u) ms", delay_ms); fu_device_sleep(FU_DEVICE(self), delay_ms); return FALSE; default: fu_device_sleep(FU_DEVICE(self), FU_QC_S5GEN2_DEVICE_SEND_DELAY); g_set_error(error, FWUPD_ERROR, G_IO_ERROR_INVALID_DATA, "unexpected opcode (%s)", fu_qc_opcode_to_string(opcode)); return FALSE; } return TRUE; } static gboolean fu_qc_s5gen2_device_validation_cb(FuDevice *device, gpointer user_data, GError **error) { return fu_qc_s5gen2_device_cmd_validation(FU_QC_S5GEN2_DEVICE(device), error); } static gboolean fu_qc_s5gen2_device_cmd_transfer_complete(FuQcS5gen2Device *self, GError **error) { g_autoptr(GByteArray) req = fu_struct_qc_transfer_complete_new(); fu_struct_qc_transfer_complete_set_action(req, FU_QC_ACTION_PROCEED); return fu_qc_s5gen2_device_msg_out(self, req->data, req->len, error); } static gboolean fu_qc_s5gen2_device_cmd_proceed_to_commit(FuQcS5gen2Device *self, GError **error) { guint8 data[FU_STRUCT_QC_COMMIT_REQ_SIZE] = {0}; g_autoptr(GByteArray) req = fu_struct_qc_proceed_to_commit_new(); g_autoptr(GByteArray) reply = NULL; fu_struct_qc_proceed_to_commit_set_action(req, FU_QC_ACTION_PROCEED); if (!fu_qc_s5gen2_device_msg_out(self, req->data, req->len, error)) return FALSE; if (!fu_qc_s5gen2_device_msg_in(self, data, sizeof(data), error)) return FALSE; reply = fu_struct_qc_commit_req_parse(data, sizeof(data), 0, error); if (reply == NULL) return FALSE; return TRUE; } static gboolean fu_qc_s5gen2_device_cmd_commit(FuQcS5gen2Device *self, GError **error) { guint8 data[FU_STRUCT_QC_COMPLETE_SIZE] = {0}; g_autoptr(GByteArray) req = fu_struct_qc_commit_cfm_new(); g_autoptr(GByteArray) reply = NULL; fu_struct_qc_commit_cfm_set_action(req, FU_QC_COMMIT_ACTION_UPGRADE); if (!fu_qc_s5gen2_device_msg_out(self, req->data, req->len, error)) return FALSE; if (!fu_qc_s5gen2_device_msg_in(self, data, sizeof(data), error)) return FALSE; reply = fu_struct_qc_complete_parse(data, sizeof(data), 0, error); if (reply == NULL) return FALSE; return TRUE; } static gboolean fu_qc_s5gen2_device_ensure_version(FuQcS5gen2Device *self, GError **error) { guint8 ver_raw[FU_STRUCT_QC_VERSION_SIZE] = {0}; g_autofree gchar *ver_str = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GByteArray) version = NULL; g_autoptr(GByteArray) version_req = fu_struct_qc_version_req_new(); locker = fu_device_locker_new_full(FU_DEVICE(self), (FuDeviceLockerFunc)fu_qc_s5gen2_device_cmd_req_connect, (FuDeviceLockerFunc)fu_qc_s5gen2_device_cmd_req_disconnect, error); if (locker == NULL) return FALSE; if (!fu_qc_s5gen2_device_msg_out(self, version_req->data, version_req->len, error)) return FALSE; if (!fu_qc_s5gen2_device_msg_in(self, ver_raw, sizeof(ver_raw), error)) return FALSE; version = fu_struct_qc_version_parse(ver_raw, sizeof(ver_raw), 0, error); if (version == NULL) return FALSE; ver_str = g_strdup_printf("%u.%u.%u", fu_struct_qc_version_get_major(version), fu_struct_qc_version_get_minor(version), fu_struct_qc_version_get_config(version)); fu_device_set_version(FU_DEVICE(self), ver_str); return TRUE; } static gboolean fu_qc_s5gen2_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuQcS5gen2Device *self = FU_QC_S5GEN2_DEVICE(device); g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_qc_s5gen2_device_cmd_req_connect, (FuDeviceLockerFunc)fu_qc_s5gen2_device_cmd_req_disconnect, error); if (locker == NULL) { g_prefix_error(error, "failed to connect: "); return FALSE; } if (!fu_qc_s5gen2_device_cmd_sync(self, error)) { g_prefix_error(error, "failed to cmd-sync: "); return FALSE; } if (!fu_qc_s5gen2_device_cmd_start(self, error)) { g_prefix_error(error, "failed to cmd-start: "); return FALSE; } if (!fu_qc_s5gen2_device_cmd_proceed_to_commit(self, error)) { g_prefix_error(error, "failed to cmd-proceed-to-commit: "); return FALSE; } if (!fu_qc_s5gen2_device_cmd_commit(self, error)) { g_prefix_error(error, "failed to cmd-commit: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_qc_s5gen2_device_reload(FuDevice *device, GError **error) { FuQcS5gen2Device *self = FU_QC_S5GEN2_DEVICE(device); return fu_qc_s5gen2_device_ensure_version(self, error); } static gboolean fu_qc_s5gen2_device_setup(FuDevice *device, GError **error) { FuQcS5gen2Device *self = FU_QC_S5GEN2_DEVICE(device); return fu_qc_s5gen2_device_ensure_version(self, error); } static gboolean fu_qc_s5gen2_device_prepare(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuQcS5gen2Device *self = FU_QC_S5GEN2_DEVICE(device); g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_qc_s5gen2_device_cmd_req_connect, (FuDeviceLockerFunc)fu_qc_s5gen2_device_cmd_req_disconnect, error); if (locker == NULL) { g_prefix_error(error, "failed to connect: "); return FALSE; } /* FIXME: do abort of any stalled upgrade for USB only * rework that part to continue update for wireless/USB */ if (!fu_qc_s5gen2_device_cmd_abort(self, error)) return FALSE; return TRUE; } static gboolean fu_qc_s5gen2_device_write_bucket(FuQcS5gen2Device *self, GBytes *data, FuQcMoreData last, GError **error) { g_autoptr(FuChunkArray) chunks = NULL; chunks = fu_chunk_array_new_from_bytes(data, 0, FU_STRUCT_QC_DATA_SIZE_DATA); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(GByteArray) pkt = fu_struct_qc_data_new(); g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); fu_struct_qc_data_set_data_len(pkt, fu_chunk_get_data_sz(chk) + 1); /* only the last block of the last bucket should have flag LAST */ if ((i + 1) == fu_chunk_array_length(chunks)) fu_struct_qc_data_set_last_packet(pkt, last); else fu_struct_qc_data_set_last_packet(pkt, FU_QC_MORE_DATA_MORE); if (!fu_struct_qc_data_set_data(pkt, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; if (!fu_qc_s5gen2_device_msg_out(self, pkt->data, pkt->len, error)) return FALSE; /* wait between packets sending */ fu_device_sleep(FU_DEVICE(self), FU_QC_S5GEN2_DEVICE_SEND_DELAY); } return TRUE; } static gboolean fu_qc_s5gen2_device_write_blocks(FuQcS5gen2Device *self, GBytes *bytes, FuProgress *progress, GError **error) { const gsize blobsz = g_bytes_get_size(bytes); guint32 cur_offset = 0; FuQcMoreData more_data = FU_QC_MORE_DATA_MORE; /* progress */ fu_progress_set_id(progress, G_STRLOC); /* device is requesting data from the host */ do { guint8 buf_in[FU_STRUCT_QC_DATA_REQ_SIZE] = {0}; guint32 data_sz; guint32 data_offset; g_autoptr(GByteArray) data_req = NULL; g_autoptr(GBytes) data_out = NULL; if (!fu_qc_s5gen2_device_msg_in(self, buf_in, sizeof(buf_in), error)) return FALSE; data_req = fu_struct_qc_data_req_parse(buf_in, sizeof(buf_in), 0, error); if (data_req == NULL) return FALSE; /* requested data */ data_sz = fu_struct_qc_data_req_get_fw_data_len(data_req); data_offset = fu_struct_qc_data_req_get_fw_data_offset(data_req); cur_offset += data_offset; /* requested data might be larger than the single packet payload */ /* FIXME: checking the data is less or equal the firmware size? */ if (blobsz < (cur_offset + data_sz)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unexpected firmware data requested: offset=%u, size=%u", cur_offset, data_sz); return FALSE; } more_data = (blobsz <= (cur_offset + data_sz)) ? FU_QC_MORE_DATA_LAST : FU_QC_MORE_DATA_MORE; data_out = g_bytes_new_from_bytes(bytes, cur_offset, data_sz); if (!fu_qc_s5gen2_device_write_bucket(self, data_out, more_data, error)) return FALSE; /* update progress */ fu_progress_set_percentage_full(progress, data_sz + cur_offset, blobsz); cur_offset += data_sz; /* FIXME: petentially infinite loop if device requesting wrong data? some counter or timeout? */ } while (more_data != FU_QC_MORE_DATA_LAST); /* success */ return TRUE; } static FuFirmware * fu_qc_s5gen2_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuQcS5gen2Device *self = FU_QC_S5GEN2_DEVICE(device); g_autoptr(FuFirmware) firmware = fu_qc_s5gen2_firmware_new(); if (!fu_firmware_parse_full(firmware, fw, 0, flags, error)) return NULL; self->file_version = fu_qc_s5gen2_firmware_get_protocol_version(FU_QC_S5GEN2_FIRMWARE(firmware)); self->file_id = fu_qc_s5gen2_firmware_get_id(FU_QC_S5GEN2_FIRMWARE(firmware)); return g_steal_pointer(&firmware); } static gboolean fu_qc_s5gen2_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuQcS5gen2Device *self = FU_QC_S5GEN2_DEVICE(device); g_autoptr(GBytes) fw = NULL; if (!fu_qc_s5gen2_device_cmd_req_connect(self, error)) return FALSE; if (!fu_qc_s5gen2_device_cmd_sync(self, error)) return FALSE; if (!fu_qc_s5gen2_device_cmd_start(self, error)) return FALSE; if (!fu_qc_s5gen2_device_cmd_start_data(self, error)) return FALSE; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 83, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 17, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; if (!fu_qc_s5gen2_device_write_blocks(self, fw, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* send validation request */ /* get the FU_QC_OPCODE_TRANSFER_COMPLETE_IND during 60000ms or fail */ if (!fu_device_retry_full(device, fu_qc_s5gen2_device_validation_cb, FU_QC_S5GEN2_DEVICE_VALIDATION_RETRIES, 0, /* custom delay based on value in response */ NULL, error)) return FALSE; fu_progress_step_done(progress); /* complete & reboot the device */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return fu_qc_s5gen2_device_cmd_transfer_complete(self, error); } static void fu_qc_s5gen2_hid_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reload"); } static void fu_qc_s5gen2_hid_device_replace(FuDevice *device, FuDevice *donor) { FuQcS5gen2Device *self = FU_QC_S5GEN2_DEVICE(device); FuQcS5gen2Device *self_donor = FU_QC_S5GEN2_DEVICE(donor); self->file_id = self_donor->file_id; self->file_version = self_donor->file_version; self->battery_raw = self_donor->battery_raw; } static void fu_qc_s5gen2_device_init(FuQcS5gen2Device *self) { fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_remove_delay(FU_DEVICE(self), FU_QC_S5GEN2_DEVICE_REMOVE_DELAY); fu_device_add_protocol(FU_DEVICE(self), "com.qualcomm.s5gen2"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FOR_OPEN); } static void fu_qc_s5gen2_device_class_init(FuQcS5gen2DeviceClass *klass) { FuDeviceClass *device_class = FU_DEVICE_CLASS(klass); device_class->to_string = fu_qc_s5gen2_device_to_string; device_class->setup = fu_qc_s5gen2_device_setup; device_class->reload = fu_qc_s5gen2_device_reload; device_class->prepare = fu_qc_s5gen2_device_prepare; device_class->attach = fu_qc_s5gen2_device_attach; device_class->prepare_firmware = fu_qc_s5gen2_device_prepare_firmware; device_class->write_firmware = fu_qc_s5gen2_device_write_firmware; device_class->set_progress = fu_qc_s5gen2_hid_device_set_progress; device_class->replace = fu_qc_s5gen2_hid_device_replace; } fwupd-1.9.16/plugins/audio-s5gen2/fu-audio-s5gen2-device.h000066400000000000000000000005601460375044200230460ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_QC_S5GEN2_DEVICE_REMOVE_DELAY 90000 /* ms */ #define FU_TYPE_QC_S5GEN2_DEVICE (fu_qc_s5gen2_device_get_type()) G_DECLARE_FINAL_TYPE(FuQcS5gen2Device, fu_qc_s5gen2_device, FU, QC_S5GEN2_DEVICE, FuDevice) fwupd-1.9.16/plugins/audio-s5gen2/fu-audio-s5gen2-firmware.c000066400000000000000000000100771460375044200234220ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-audio-s5gen2-firmware.h" #include "fu-audio-s5gen2-fw-struct.h" struct _FuQcS5gen2Firmware { FuFirmware parent_instance; guint32 file_id; guint8 protocol_ver; gchar *device_variant; }; G_DEFINE_TYPE(FuQcS5gen2Firmware, fu_qc_s5gen2_firmware, FU_TYPE_FIRMWARE) guint8 fu_qc_s5gen2_firmware_get_protocol_version(FuQcS5gen2Firmware *self) { return self->protocol_ver; } /* generated ID unique for the firmware */ guint32 fu_qc_s5gen2_firmware_get_id(FuQcS5gen2Firmware *self) { return self->file_id; } static void fu_qc_s5gen2_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuQcS5gen2Firmware *self = FU_QC_S5GEN2_FIRMWARE(firmware); fu_xmlb_builder_insert_kv(bn, "device_variant", self->device_variant); fu_xmlb_builder_insert_kx(bn, "protocol_version", self->protocol_ver); fu_xmlb_builder_insert_kx(bn, "generated_file_id", self->file_id); } static gboolean fu_qc_s5gen2_firmware_validate(FuFirmware *firmware, GBytes *bytes, gsize offset, GError **error) { return fu_struct_qc_fw_update_hdr_validate_bytes(bytes, offset, error); } static gboolean fu_qc_s5gen2_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuQcS5gen2Firmware *self = FU_QC_S5GEN2_FIRMWARE(firmware); const guint8 *device_variant; gsize config_offset = 26; guint16 config_ver; g_autofree gchar *ver_str = NULL; g_autoptr(GByteArray) hdr = NULL; /* FIXME: deal with encrypted? */ hdr = fu_struct_qc_fw_update_hdr_parse_bytes(fw, offset, error); if (hdr == NULL) return FALSE; /* protocol version */ self->protocol_ver = fu_struct_qc_fw_update_hdr_get_protocol(hdr) - '0'; device_variant = fu_struct_qc_fw_update_hdr_get_dev_variant(hdr, NULL); self->device_variant = fu_strsafe((const gchar *)device_variant, 8); config_offset += fu_struct_qc_fw_update_hdr_get_upgrades(hdr) * 4; if (!fu_memread_uint16_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), config_offset, &config_ver, G_BIG_ENDIAN, error)) return FALSE; ver_str = g_strdup_printf("%u.%u.%u", fu_struct_qc_fw_update_hdr_get_major(hdr), fu_struct_qc_fw_update_hdr_get_minor(hdr), config_ver); fu_firmware_set_version(firmware, ver_str); fu_firmware_set_bytes(firmware, fw); self->file_id = fu_crc32_full(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), 0x0, 0xEDB88320); /* success */ return TRUE; } static GByteArray * fu_qc_s5gen2_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) fw = NULL; /* data first */ fw = fu_firmware_get_bytes_with_patches(firmware, error); if (fw == NULL) return NULL; fu_byte_array_append_bytes(buf, fw); /* success */ return g_steal_pointer(&buf); } static void fu_qc_s5gen2_firmware_init(FuQcS5gen2Firmware *self) { self->device_variant = NULL; fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); } static void fu_qc_s5gen2_firmware_finalize(GObject *object) { FuQcS5gen2Firmware *self = FU_QC_S5GEN2_FIRMWARE(object); g_free(self->device_variant); G_OBJECT_CLASS(fu_qc_s5gen2_firmware_parent_class)->finalize(object); } static void fu_qc_s5gen2_firmware_class_init(FuQcS5gen2FirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_qc_s5gen2_firmware_finalize; klass_firmware->check_magic = fu_qc_s5gen2_firmware_validate; klass_firmware->parse = fu_qc_s5gen2_firmware_parse; klass_firmware->write = fu_qc_s5gen2_firmware_write; klass_firmware->export = fu_qc_s5gen2_firmware_export; } FuFirmware * fu_qc_s5gen2_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_QC_S5GEN2_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/audio-s5gen2/fu-audio-s5gen2-firmware.h000066400000000000000000000010011460375044200234120ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_QC_S5GEN2_FIRMWARE (fu_qc_s5gen2_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuQcS5gen2Firmware, fu_qc_s5gen2_firmware, FU, QC_S5GEN2_FIRMWARE, FuFirmware) FuFirmware * fu_qc_s5gen2_firmware_new(void); guint8 fu_qc_s5gen2_firmware_get_protocol_version(FuQcS5gen2Firmware *self); guint32 fu_qc_s5gen2_firmware_get_id(FuQcS5gen2Firmware *self); fwupd-1.9.16/plugins/audio-s5gen2/fu-audio-s5gen2-fw.rs000066400000000000000000000005631460375044200224230ustar00rootroot00000000000000// Copyright (C) 2023 Denis Pynkin // SPDX-License-Identifier: LGPL-2.1+ #[derive(ParseBytes, ValidateBytes)] struct QcFwUpdateHdr { magic1: u32be == 0x41505055, magic2: u16be == 0x4844, magic3: u8 == 0x52, protocol: u8, length: u32be, dev_variant: [u8; 8], major: u16be, minor: u16be, upgrades: u16be, } fwupd-1.9.16/plugins/audio-s5gen2/fu-audio-s5gen2-hid-device.c000066400000000000000000000113061460375044200236030ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * Copyright (C) 2024 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-audio-s5gen2-device.h" #include "fu-audio-s5gen2-hid-device.h" #include "fu-audio-s5gen2-hid-struct.h" #include "fu-audio-s5gen2-impl.h" #define HID_IFACE 0x01 #define HID_EP_IN 0x82 #define HID_EP_OUT 0x01 /* FIXME: value :-| */ #define FU_QC_S5GEN2_HID_DEVICE_TIMEOUT 0 /* ms */ struct _FuQcS5gen2HidDevice { FuHidDevice parent_instance; }; static void fu_qc_s5gen2_hid_device_impl_iface_init(FuQcS5gen2ImplInterface *iface); G_DEFINE_TYPE_WITH_CODE(FuQcS5gen2HidDevice, fu_qc_s5gen2_hid_device, FU_TYPE_HID_DEVICE, G_IMPLEMENT_INTERFACE(FU_TYPE_QC_S5GEN2_IMPL, fu_qc_s5gen2_hid_device_impl_iface_init)) static gboolean fu_qc_s5gen2_hid_device_msg_out(FuQcS5gen2Impl *impl, guint8 *data, gsize data_len, GError **error) { FuQcS5gen2HidDevice *self = FU_QC_S5GEN2_HID_DEVICE(impl); g_autoptr(GByteArray) msg = fu_struct_qc_hid_data_transfer_new(); fu_struct_qc_hid_data_transfer_set_payload_len(msg, data_len); if (!fu_struct_qc_hid_data_transfer_set_payload(msg, data, data_len, error)) return FALSE; return fu_hid_device_set_report(FU_HID_DEVICE(self), 0x00, msg->data, FU_STRUCT_QC_HID_DATA_TRANSFER_SIZE, FU_QC_S5GEN2_HID_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error); } static gboolean fu_qc_s5gen2_hid_device_msg_in(FuQcS5gen2Impl *impl, guint8 *data, gsize data_len, GError **error) { FuQcS5gen2HidDevice *self = FU_QC_S5GEN2_HID_DEVICE(impl); guint8 buf[FU_STRUCT_QC_HID_RESPONSE_SIZE] = {0x0}; g_autoptr(GByteArray) msg = NULL; if (!fu_hid_device_get_report(FU_HID_DEVICE(self), 0x00, buf, FU_STRUCT_QC_HID_RESPONSE_SIZE, FU_QC_S5GEN2_HID_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) return FALSE; msg = fu_struct_qc_hid_response_parse(buf, FU_STRUCT_QC_HID_RESPONSE_SIZE, 0, error); if (msg == NULL) return FALSE; if (!fu_memcpy_safe(data, data_len, 0, msg->data, msg->len, FU_STRUCT_QC_HID_RESPONSE_OFFSET_PAYLOAD, fu_struct_qc_hid_response_get_payload_len(msg), error)) return FALSE; return TRUE; } static gboolean fu_qc_s5gen2_hid_device_msg_cmd(FuQcS5gen2Impl *impl, guint8 *data, gsize data_len, GError **error) { FuQcS5gen2HidDevice *self = FU_QC_S5GEN2_HID_DEVICE(impl); g_autoptr(GByteArray) msg = fu_struct_qc_hid_command_new(); fu_struct_qc_hid_command_set_payload_len(msg, data_len); if (!fu_struct_qc_hid_command_set_payload(msg, data, data_len, error)) return FALSE; return fu_hid_device_set_report(FU_HID_DEVICE(self), 0x03, msg->data, FU_STRUCT_QC_HID_COMMAND_SIZE, 0, FU_HID_DEVICE_FLAG_IS_FEATURE, error); } static gboolean fu_qc_s5gen2_hid_device_probe(FuDevice *device, GError **error) { FuHidDevice *hid_device = FU_HID_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); GUsbInterface *iface = NULL; g_autoptr(GPtrArray) ifaces = NULL; ifaces = g_usb_device_get_interfaces(usb_device, error); if (ifaces == NULL) return FALSE; /* need the second HID interface */ if (ifaces->len <= HID_IFACE) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "transitional device detected"); return FALSE; } iface = g_ptr_array_index(ifaces, HID_IFACE); if (g_usb_interface_get_class(iface) != G_USB_DEVICE_CLASS_HID) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "target interface is not HID"); return FALSE; } fu_hid_device_set_interface(hid_device, HID_IFACE); fu_hid_device_set_ep_addr_in(hid_device, HID_EP_IN); fu_hid_device_set_ep_addr_out(hid_device, HID_EP_OUT); /* FuHidDevice->probe */ if (!FU_DEVICE_CLASS(fu_qc_s5gen2_hid_device_parent_class)->probe(device, error)) return FALSE; /* success */ return TRUE; } static void fu_qc_s5gen2_hid_device_init(FuQcS5gen2HidDevice *self) { fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_RETRY_FAILURE); fu_device_set_remove_delay(FU_DEVICE(self), FU_QC_S5GEN2_DEVICE_REMOVE_DELAY); fu_device_set_battery_threshold(FU_DEVICE(self), 0); } static void fu_qc_s5gen2_hid_device_impl_iface_init(FuQcS5gen2ImplInterface *iface) { iface->msg_in = fu_qc_s5gen2_hid_device_msg_in; iface->msg_out = fu_qc_s5gen2_hid_device_msg_out; iface->msg_cmd = fu_qc_s5gen2_hid_device_msg_cmd; } static void fu_qc_s5gen2_hid_device_class_init(FuQcS5gen2HidDeviceClass *klass) { FuDeviceClass *device_class = FU_DEVICE_CLASS(klass); device_class->probe = fu_qc_s5gen2_hid_device_probe; } fwupd-1.9.16/plugins/audio-s5gen2/fu-audio-s5gen2-hid-device.h000066400000000000000000000005511460375044200236100ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_QC_S5GEN2_HID_DEVICE (fu_qc_s5gen2_hid_device_get_type()) G_DECLARE_FINAL_TYPE(FuQcS5gen2HidDevice, fu_qc_s5gen2_hid_device, FU, QC_S5GEN2_HID_DEVICE, FuHidDevice) fwupd-1.9.16/plugins/audio-s5gen2/fu-audio-s5gen2-hid.rs000066400000000000000000000010751460375044200225520ustar00rootroot00000000000000// Copyright (C) 2023 Denis Pynkin // SPDX-License-Identifier: LGPL-2.1+ #[repr(u8)] enum QcReportId { Command = 3, DataTransfer = 5, Response = 6, } #[derive(New)] struct QcHidCommand { report_id: QcReportId == Command, payload_len: u8, payload: [u8; 61], } #[derive(Parse)] struct QcHidResponse { report_id: QcReportId == Response, payload_len: u8, payload: [u8; 11], } #[derive(New)] struct QcHidDataTransfer { report_id: QcReportId == DataTransfer, payload_len: u8, payload: [u8; 253], } fwupd-1.9.16/plugins/audio-s5gen2/fu-audio-s5gen2-impl.c000066400000000000000000000033031460375044200225410ustar00rootroot00000000000000/* * Copyright (C) 2024 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-audio-s5gen2-impl.h" G_DEFINE_INTERFACE(FuQcS5gen2Impl, fu_qc_s5gen2_impl, G_TYPE_OBJECT) static void fu_qc_s5gen2_impl_default_init(FuQcS5gen2ImplInterface *iface) { } gboolean fu_qc_s5gen2_impl_msg_in(FuQcS5gen2Impl *self, guint8 *data, gsize data_len, GError **error) { FuQcS5gen2ImplInterface *iface; g_return_val_if_fail(FU_IS_QC_S5GEN2_IMPL(self), FALSE); iface = FU_QC_S5GEN2_IMPL_GET_IFACE(self); if (iface->msg_in == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "iface->msg_in not implemented"); return FALSE; } return (*iface->msg_in)(self, data, data_len, error); } gboolean fu_qc_s5gen2_impl_msg_out(FuQcS5gen2Impl *self, guint8 *data, gsize data_len, GError **error) { FuQcS5gen2ImplInterface *iface; g_return_val_if_fail(FU_IS_QC_S5GEN2_IMPL(self), FALSE); iface = FU_QC_S5GEN2_IMPL_GET_IFACE(self); if (iface->msg_out == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "iface->msg_out not implemented"); return FALSE; } return (*iface->msg_out)(self, data, data_len, error); } gboolean fu_qc_s5gen2_impl_msg_cmd(FuQcS5gen2Impl *self, guint8 *data, gsize data_len, GError **error) { FuQcS5gen2ImplInterface *iface; g_return_val_if_fail(FU_IS_QC_S5GEN2_IMPL(self), FALSE); iface = FU_QC_S5GEN2_IMPL_GET_IFACE(self); if (iface->msg_cmd == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "iface->msg_cmd not implemented"); return FALSE; } return (*iface->msg_cmd)(self, data, data_len, error); } fwupd-1.9.16/plugins/audio-s5gen2/fu-audio-s5gen2-impl.h000066400000000000000000000016521460375044200225530ustar00rootroot00000000000000/* * Copyright (C) 2024 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_QC_S5GEN2_IMPL (fu_qc_s5gen2_impl_get_type()) G_DECLARE_INTERFACE(FuQcS5gen2Impl, fu_qc_s5gen2_impl, FU, QC_S5GEN2_IMPL, GObject) struct _FuQcS5gen2ImplInterface { GTypeInterface g_iface; gboolean (*msg_in)(FuQcS5gen2Impl *self, guint8 *data, gsize data_len, GError **error); gboolean (*msg_out)(FuQcS5gen2Impl *self, guint8 *data, gsize data_len, GError **error); gboolean (*msg_cmd)(FuQcS5gen2Impl *self, guint8 *data, gsize data_len, GError **error); }; gboolean fu_qc_s5gen2_impl_msg_in(FuQcS5gen2Impl *self, guint8 *data, gsize data_len, GError **error); gboolean fu_qc_s5gen2_impl_msg_out(FuQcS5gen2Impl *self, guint8 *data, gsize data_len, GError **error); gboolean fu_qc_s5gen2_impl_msg_cmd(FuQcS5gen2Impl *self, guint8 *data, gsize data_len, GError **error); fwupd-1.9.16/plugins/audio-s5gen2/fu-audio-s5gen2-plugin.c000066400000000000000000000017621460375044200231050ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-audio-s5gen2-device.h" #include "fu-audio-s5gen2-firmware.h" #include "fu-audio-s5gen2-hid-device.h" #include "fu-audio-s5gen2-plugin.h" struct _FuAudioS5gen2Plugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuAudioS5gen2Plugin, fu_audio_s5gen2_plugin, FU_TYPE_PLUGIN) static void fu_audio_s5gen2_plugin_init(FuAudioS5gen2Plugin *self) { } static void fu_audio_s5gen2_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_QC_S5GEN2_HID_DEVICE); fu_plugin_set_device_gtype_default(plugin, FU_TYPE_QC_S5GEN2_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_QC_S5GEN2_FIRMWARE); } static void fu_audio_s5gen2_plugin_class_init(FuAudioS5gen2PluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_audio_s5gen2_plugin_constructed; } fwupd-1.9.16/plugins/audio-s5gen2/fu-audio-s5gen2-plugin.h000066400000000000000000000003761460375044200231120ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuAudioS5gen2Plugin, fu_audio_s5gen2_plugin, FU, AUDIO_S5GEN2_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/audio-s5gen2/fu-audio-s5gen2.rs000066400000000000000000000077631460375044200220220ustar00rootroot00000000000000// Copyright (C) 2023 Denis Pynkin // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] #[repr(u8)] // Upgrade protocol OpCode enum QcOpcode { StartReq = 0x01, StartCfm = 0x02, DataBytesReq = 0x03, Data = 0x04, AbortReq = 0x07, AbortCfm = 0x08, TransferCompleteInd = 0x0B, TransferCompleteRes = 0x0C, ProceedToCommit = 0x0E, CommitReq = 0x0F, CommitCfm = 0x10, ErrorInd = 0x11, CompleteInd = 0x12, SyncReq = 0x13, SyncCfm = 0x14, StartDataReq = 0x15, IsValidationDoneReq = 0x16, IsValidationDoneCfm = 0x17, HostVersionReq = 0x19, HostVersionCfm = 0x1A, ErrorRes = 0x1F, } #[repr(u8)] enum QcAction { Proceed = 0, NotProceed = 1, } #[repr(u8)] enum QcReq { Connect = 0x02, Disconnect = 0x07, } #[derive(New)] struct QcConnectReq { req: QcReq == Connect, } #[derive(New)] struct QcDisconnectReq { req: QcReq == Disconnect, } #[derive(ToString)] #[repr(u8)] enum QcStatus { Success = 0, // Operation succeeded UnexpectedError, // Operation failed AlreadyConnectedWarning, // Already connected InProgress, // Requested operation failed, an upgrade is in progress Busy, // UNUSED InvalidPowerState, // Invalid power management state } #[derive(Parse)] struct QcUpdateStatus { status: QcStatus, } #[derive(New)] struct QcVersionReq { opcode: QcOpcode == HostVersionReq, data_len: u16be == 0x00, } #[derive(Parse)] struct QcVersion { status: QcOpcode == HostVersionCfm, data_len: u16be == 0x0006, major: u16be, minor: u16be, config: u16be, } #[derive(New)] struct QcAbortReq { opcode: QcOpcode == AbortReq, data_len: u16be = 0x00, } #[derive(Parse)] struct QcAbort { opcode: QcOpcode == AbortCfm, data_len: u16be = 0x00, } #[derive(ToString)] #[repr(u8)] enum QcResumePoint { Start = 0, PreValidate, PreReboot, PostReboot, PostCommit, } #[derive(New)] struct QcSyncReq { opcode: QcOpcode == SyncReq, data_len: u16be = 0x04, fileId: u32be, } #[derive(Parse)] struct QcSync { opcode: QcOpcode == SyncCfm, data_len: u16be = 0x06, resume_point: QcResumePoint, file_id: u32be, protocolVersion: u8, } #[derive(ToString)] #[repr(u8)] enum QcStartStatus { Success = 0, Failure = 1, } #[derive(New)] struct QcStartReq { opcode: QcOpcode == StartReq, data_len: u16be = 0x00, } #[derive(Parse)] struct QcStart { opcode: QcOpcode == StartCfm, data_len: u16be = 0x0003, status: QcStartStatus, battery_level: u16be, } #[derive(New)] struct QcStartDataReq { opcode: QcOpcode == StartDataReq, data_len: u16be = 0x00, data: [u8; 250], } #[repr(u8)] enum QcMoreData { More = 0, Last = 1, } #[derive(Parse)] struct QcDataReq { opcode: QcOpcode == DataBytesReq, data_len: u16be = 0x0008, fw_data_len: u32be, fw_data_offset: u32be, } #[derive(New)] struct QcData { opcode: QcOpcode == Data, data_len: u16be, last_packet: QcMoreData, data: [u8; 249], } #[derive(New)] struct QcValidationReq { opcode: QcOpcode == IsValidationDoneReq, data_len: u16be = 0x00, } #[derive(Parse)] struct QcValidation { opcode: QcOpcode, // Could be TransferCompleteInd or IsValidationDoneCfm data_len: u16be, delay: u16be, } #[derive(New)] struct QcTransferComplete { opcode: QcOpcode == TransferCompleteRes, data_len: u16be = 0x01, action: QcAction, } #[derive(New)] struct QcProceedToCommit { opcode: QcOpcode == ProceedToCommit, data_len: u16be = 0x01, action: QcAction, } #[derive(Parse)] struct QcCommitReq { opcode: QcOpcode == CommitReq, data_len: u16be = 0x00, } #[repr(u8)] enum QcCommitAction { Upgrade = 0, Rollback = 1, } #[derive(New)] struct QcCommitCfm { opcode: QcOpcode == CommitCfm, data_len: u16be = 0x01, action: QcCommitAction, } #[derive(Parse)] struct QcComplete { opcode: QcOpcode == CompleteInd, data_len: u16be = 0x00, } fwupd-1.9.16/plugins/audio-s5gen2/meson.build000066400000000000000000000012241460375044200207660ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginQcS5gen2"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('audio-s5gen2.quirk') plugin_builtins += static_library('fu_plugin_audio_s5gen2', rustgen.process('fu-audio-s5gen2.rs'), rustgen.process('fu-audio-s5gen2-hid.rs'), rustgen.process('fu-audio-s5gen2-fw.rs'), sources: [ 'fu-audio-s5gen2-device.c', 'fu-audio-s5gen2-hid-device.c', 'fu-audio-s5gen2-firmware.c', 'fu-audio-s5gen2-plugin.c', 'fu-audio-s5gen2-impl.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/aver-hid/000077500000000000000000000000001460375044200161225ustar00rootroot00000000000000fwupd-1.9.16/plugins/aver-hid/README.md000066400000000000000000000034441460375044200174060ustar00rootroot00000000000000--- title: Plugin: Aver HID-ISP --- ## Introduction The AVer HID In-System-Programming plugin is used for various products that can be updated using a proprietary HID protocol. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `com.aver.hid` ## GUID Generation These devices use the standard DeviceInstanceId values, e.g. * `USB\VID_2574&PID_09F0` ## Update Behavior The device is updated using a HID request/response with a fixed size payload. Once ready, the plugin can start the update with the `UVCX_UCAM_ISP_FILE_START` header. After the device sends back the `UVCX_UCAM_ISP_FILE_START` packet, the PC process can send the firmware file in chunks using `UVCX_UCAM_ISP_FILE_DNLOAD`. After the last chunk, the plugin sends `UVCX_UCAM_ISP_FILE_END` packet and the device will check whether the firmware is valid. If the firmware file is correct, the device will send `UVCX_UCAM_ISP_START` to PC, and the plugin can continuously send `UVCX_UCAM_ISP_STATUS` to get the ISP progress percentage. If the firmware file is incorrect, the device sends `UVCX_UCAM_ISP_STOP` back to the plugin, and the ISP progress should be terminated. The PC process should go back to `UVCX_UCAM_ISP_STATUS` and restart the process if needed. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x2574` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.9.2`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Pierce Wang: @PierceWangAVer fwupd-1.9.16/plugins/aver-hid/aver-hid.quirk000066400000000000000000000005431460375044200207000ustar00rootroot00000000000000# AVer Fone540 [USB\VID_2574&PID_09F0] Plugin = aver_hid GType = FuAverHidDevice # AVer CAM520Pro3 [USB\VID_2574&PID_0B20] Plugin = aver_hid GType = FuAverHidDevice # AVer VB342Pro [USB\VID_2574&PID_0AB1] Plugin = aver_hid Flags = dual-isp GType = FuAverHidDevice # AVer CAM340Plus [USB\VID_2574&PID_0980] Plugin = aver_hid GType = FuAverSafeispDevice fwupd-1.9.16/plugins/aver-hid/fu-aver-hid-device.c000066400000000000000000000416551460375044200216450ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-aver-hid-device.h" #include "fu-aver-hid-firmware.h" #include "fu-aver-hid-struct.h" struct _FuAverHidDevice { FuHidDevice parent_instance; }; G_DEFINE_TYPE(FuAverHidDevice, fu_aver_hid_device, FU_TYPE_HID_DEVICE) #define FU_AVER_HID_DEVICE_TIMEOUT 200 /* ms */ #define FU_AVER_HID_DEVICE_GET_STATUS_POLL_INTERVAL 1000 /* ms */ #define FU_AVER_HID_DEVICE_POLL_INTERVAL 5000 /* ms */ #define FU_AVER_HID_DEVICE_ISP_RETRY_COUNT 300 #define FU_AVER_HID_DEVICE_ISP_UNTAR_WAIT_COUNT 600 #define FU_AVER_HID_FLAG_DUAL_ISP (1 << 0) static gboolean fu_aver_hid_device_transfer(FuAverHidDevice *self, GByteArray *req, GByteArray *res, GError **error) { if (req != NULL) { if (!fu_hid_device_set_report(FU_HID_DEVICE(self), req->data[0], req->data, req->len, FU_AVER_HID_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) { g_prefix_error(error, "failed to send packet: "); return FALSE; } } if (res != NULL) { if (!fu_hid_device_get_report(FU_HID_DEVICE(self), res->data[0], res->data, res->len, FU_AVER_HID_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) { g_prefix_error(error, "failed to receive packet: "); return FALSE; } g_debug("custom-isp-cmd: %s [0x%x]", fu_aver_hid_custom_isp_cmd_to_string( fu_struct_aver_hid_res_isp_get_custom_isp_cmd(res)), fu_struct_aver_hid_res_isp_get_custom_isp_cmd(res)); } return TRUE; } static gboolean fu_aver_hid_device_ensure_status(FuAverHidDevice *self, GError **error) { g_autoptr(GByteArray) req = fu_struct_aver_hid_req_isp_new(); g_autoptr(GByteArray) res = fu_struct_aver_hid_res_isp_status_new(); fu_struct_aver_hid_req_isp_set_custom_isp_cmd(req, FU_AVER_HID_CUSTOM_ISP_CMD_STATUS); if (!fu_aver_hid_device_transfer(self, req, res, error)) return FALSE; if (!fu_struct_aver_hid_res_isp_status_validate(res->data, res->len, 0x0, error)) return FALSE; if (fu_struct_aver_hid_res_isp_status_get_status(res) == FU_AVER_HID_STATUS_BUSY) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "device has status %s", fu_aver_hid_status_to_string( fu_struct_aver_hid_res_isp_status_get_status(res))); return FALSE; } return TRUE; } static gboolean fu_aver_hid_device_ensure_version(FuAverHidDevice *self, GError **error) { g_autofree gchar *ver = NULL; g_autoptr(GByteArray) req = fu_struct_aver_hid_req_device_version_new(); g_autoptr(GByteArray) res = fu_struct_aver_hid_res_device_version_new(); g_autoptr(GError) error_local = NULL; if (!fu_aver_hid_device_transfer(self, req, res, &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT)) { g_debug("ignoring %s", error_local->message); fu_device_set_version(FU_DEVICE(self), "0.0.0000.00"); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } if (!fu_struct_aver_hid_res_device_version_validate(res->data, res->len, 0x0, error)) return FALSE; ver = fu_strsafe((const gchar *)fu_struct_aver_hid_res_device_version_get_ver(res, NULL), FU_STRUCT_AVER_HID_RES_DEVICE_VERSION_SIZE_VER); fu_device_set_version(FU_DEVICE(self), ver); return TRUE; } static gboolean fu_aver_hid_device_setup(FuDevice *device, GError **error) { FuAverHidDevice *self = FU_AVER_HID_DEVICE(device); /* HidDevice->setup */ if (!FU_DEVICE_CLASS(fu_aver_hid_device_parent_class)->setup(device, error)) return FALSE; /* ensure that the device status is updateable */ if (!fu_aver_hid_device_ensure_status(self, error)) return FALSE; /* get the version from the hardware while open */ if (!fu_aver_hid_device_ensure_version(self, error)) return FALSE; /* success */ return TRUE; } static FuFirmware * fu_aver_hid_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_aver_hid_firmware_new(); if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; return g_steal_pointer(&firmware); } static gboolean fu_aver_hid_device_isp_file_dnload(FuAverHidDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GByteArray) req = fu_struct_aver_hid_req_isp_file_dnload_new(); g_autoptr(GByteArray) res = fu_struct_aver_hid_res_isp_status_new(); /* copy in payload */ if (fu_device_has_private_flag(FU_DEVICE(self), FU_AVER_HID_FLAG_DUAL_ISP)) { fu_struct_aver_hid_req_isp_file_dnload_set_custom_isp_cmd( req, FU_AVER_HID_CUSTOM_ISP_CMD_ALL_FILE_DNLOAD); } else { fu_struct_aver_hid_req_isp_file_dnload_set_custom_isp_cmd( req, FU_AVER_HID_CUSTOM_ISP_CMD_FILE_DNLOAD); } if (!fu_memcpy_safe(req->data, req->len, FU_STRUCT_AVER_HID_REQ_ISP_FILE_DNLOAD_OFFSET_DATA, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; /* resize the last packet */ if ((i == (fu_chunk_array_length(chunks) - 1)) && (fu_chunk_get_data_sz(chk) < FU_STRUCT_AVER_HID_REQ_ISP_FILE_DNLOAD_SIZE_DATA)) fu_byte_array_set_size(req, 3 + fu_chunk_get_data_sz(chk), 0x0); if (!fu_aver_hid_device_transfer(self, req, res, error)) return FALSE; if (!fu_struct_aver_hid_res_isp_status_validate(res->data, res->len, 0x0, error)) return FALSE; /* invalid chunk */ if (fu_struct_aver_hid_res_isp_status_get_status(res) == FU_AVER_HID_STATUS_FILEERR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "device has status %s", fu_aver_hid_status_to_string( fu_struct_aver_hid_res_isp_status_get_status(res))); return FALSE; } /* update progress */ fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_aver_hid_device_wait_for_ready_cb(FuDevice *device, gpointer user_data, GError **error) { FuAverHidDevice *self = FU_AVER_HID_DEVICE(device); g_autoptr(GByteArray) req = fu_struct_aver_hid_req_isp_new(); g_autoptr(GByteArray) res = fu_struct_aver_hid_res_isp_status_new(); fu_struct_aver_hid_req_isp_set_custom_isp_cmd(req, FU_AVER_HID_CUSTOM_ISP_CMD_STATUS); if (!fu_aver_hid_device_transfer(self, req, res, error)) return FALSE; if (!fu_struct_aver_hid_res_isp_status_validate(res->data, res->len, 0x0, error)) return FALSE; if (fu_struct_aver_hid_res_isp_status_get_status(res) != FU_AVER_HID_STATUS_READY) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "device has status %s", fu_aver_hid_status_to_string( fu_struct_aver_hid_res_isp_status_get_status(res))); return FALSE; } return TRUE; } static gboolean fu_aver_hid_device_isp_file_start(FuAverHidDevice *self, gsize sz, const gchar *name, GError **error) { g_autoptr(GByteArray) req = fu_struct_aver_hid_req_isp_file_start_new(); g_autoptr(GByteArray) res = fu_struct_aver_hid_res_isp_status_new(); if (fu_device_has_private_flag(FU_DEVICE(self), FU_AVER_HID_FLAG_DUAL_ISP)) { fu_struct_aver_hid_req_isp_file_start_set_custom_isp_cmd( req, FU_AVER_HID_CUSTOM_ISP_CMD_ALL_FILE_START); } else { fu_struct_aver_hid_req_isp_file_start_set_custom_isp_cmd( req, FU_AVER_HID_CUSTOM_ISP_CMD_FILE_START); } if (!fu_struct_aver_hid_req_isp_file_start_set_file_name(req, name, error)) return FALSE; fu_struct_aver_hid_req_isp_file_start_set_file_size(req, sz); if (!fu_aver_hid_device_transfer(self, req, res, error)) return FALSE; if (!fu_struct_aver_hid_res_isp_status_validate(res->data, res->len, 0x0, error)) return FALSE; return TRUE; } static gboolean fu_aver_hid_device_isp_file_end(FuAverHidDevice *self, gsize sz, const gchar *name, GError **error) { g_autoptr(GByteArray) req = fu_struct_aver_hid_req_isp_file_end_new(); g_autoptr(GByteArray) res = fu_struct_aver_hid_res_isp_status_new(); if (fu_device_has_private_flag(FU_DEVICE(self), FU_AVER_HID_FLAG_DUAL_ISP)) { fu_struct_aver_hid_req_isp_file_end_set_custom_isp_cmd( req, FU_AVER_HID_CUSTOM_ISP_CMD_ALL_FILE_END); } else { fu_struct_aver_hid_req_isp_file_end_set_custom_isp_cmd( req, FU_AVER_HID_CUSTOM_ISP_CMD_FILE_END); } if (!fu_struct_aver_hid_req_isp_file_end_set_file_name(req, name, error)) return FALSE; fu_struct_aver_hid_req_isp_file_end_set_end_flag(req, 1); fu_struct_aver_hid_req_isp_file_end_set_file_size(req, sz); if (!fu_aver_hid_device_transfer(self, req, res, error)) return FALSE; if (!fu_struct_aver_hid_res_isp_status_validate(res->data, res->len, 0x0, error)) return FALSE; return TRUE; } static gboolean fu_aver_hid_device_wait_for_untar_cb(FuDevice *device, gpointer user_data, GError **error) { FuAverHidDevice *self = FU_AVER_HID_DEVICE(device); g_autoptr(GByteArray) req = fu_struct_aver_hid_req_isp_file_end_new(); g_autoptr(GByteArray) res = fu_struct_aver_hid_res_isp_status_new(); fu_struct_aver_hid_req_isp_set_custom_isp_cmd(req, FU_AVER_HID_CUSTOM_ISP_CMD_STATUS); if (!fu_aver_hid_device_transfer(self, req, res, error)) return FALSE; g_info("isp status: %s", fu_aver_hid_status_to_string(fu_struct_aver_hid_res_isp_status_get_status(res))); if (fu_struct_aver_hid_res_isp_status_get_status(res) != FU_AVER_HID_STATUS_WAITUSR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "device has status %s", fu_aver_hid_status_to_string( fu_struct_aver_hid_res_isp_status_get_status(res))); return FALSE; } return TRUE; } static gboolean fu_aver_hid_device_isp_start(FuAverHidDevice *self, GError **error) { g_autoptr(GByteArray) req = fu_struct_aver_hid_req_isp_new(); g_autoptr(GByteArray) res = fu_struct_aver_hid_res_isp_status_new(); if (fu_device_has_private_flag(FU_DEVICE(self), FU_AVER_HID_FLAG_DUAL_ISP)) { fu_struct_aver_hid_req_isp_start_set_custom_isp_cmd( req, FU_AVER_HID_CUSTOM_ISP_CMD_ALL_START); } else { fu_struct_aver_hid_req_isp_start_set_custom_isp_cmd( req, FU_AVER_HID_CUSTOM_ISP_CMD_START); } if (!fu_aver_hid_device_transfer(self, req, res, error)) return FALSE; if (!fu_struct_aver_hid_res_isp_status_validate(res->data, res->len, 0x0, error)) return FALSE; return TRUE; } static gboolean fu_aver_hid_device_isp_reboot(FuAverHidDevice *self, GError **error) { g_autoptr(GByteArray) req = fu_struct_aver_hid_req_isp_new(); fu_struct_aver_hid_req_isp_set_custom_isp_cmd(req, FU_AVER_HID_CUSTOM_ISP_CMD_ISP_REBOOT); return fu_aver_hid_device_transfer(self, req, NULL, error); } static gboolean fu_aver_hid_device_wait_for_reboot_cb(FuDevice *device, gpointer user_data, GError **error) { FuAverHidDevice *self = FU_AVER_HID_DEVICE(device); FuProgress *progress = FU_PROGRESS(user_data); g_autoptr(GByteArray) req = fu_struct_aver_hid_req_isp_new(); g_autoptr(GByteArray) res = fu_struct_aver_hid_res_isp_status_new(); fu_struct_aver_hid_req_isp_set_custom_isp_cmd(req, FU_AVER_HID_CUSTOM_ISP_CMD_STATUS); if (!fu_aver_hid_device_transfer(self, req, res, error)) return FALSE; if (fu_struct_aver_hid_res_isp_status_get_status(res) == FU_AVER_HID_STATUS_ISPING) { guint8 percentage = fu_struct_aver_hid_res_isp_status_get_progress(res); if (percentage < 100) fu_progress_set_percentage(progress, percentage); g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "device has status %s", fu_aver_hid_status_to_string( fu_struct_aver_hid_res_isp_status_get_status(res))); return FALSE; } if (fu_struct_aver_hid_res_isp_status_get_status(res) != FU_AVER_HID_STATUS_REBOOT) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "device has status %s", fu_aver_hid_status_to_string( fu_struct_aver_hid_res_isp_status_get_status(res))); return FALSE; } return TRUE; } static gboolean fu_aver_hid_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuAverHidDevice *self = FU_AVER_HID_DEVICE(device); const gchar *aver_fw_name = NULL; gsize fw_size; g_autoptr(FuArchive) archive = NULL; g_autoptr(GBytes) aver_fw = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 15, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* decompress */ archive = fu_archive_new(fw, FU_ARCHIVE_FLAG_NONE, error); if (archive == NULL) return FALSE; aver_fw_name = fu_firmware_get_filename(firmware); aver_fw = fu_archive_lookup_by_fn(archive, aver_fw_name, error); if (aver_fw == NULL) return FALSE; fw_size = g_bytes_get_size(aver_fw); /* wait for ST_READY */ if (!fu_device_retry_full(device, fu_aver_hid_device_wait_for_ready_cb, 5, FU_AVER_HID_DEVICE_GET_STATUS_POLL_INTERVAL, NULL, error)) return FALSE; fu_progress_step_done(progress); /* ISP_FILE_START */ if (!fu_aver_hid_device_isp_file_start(self, fw_size, aver_fw_name, error)) return FALSE; fu_progress_step_done(progress); /* ISP_FILE_DNLOAD */ chunks = fu_chunk_array_new_from_bytes(aver_fw, 0x00, FU_STRUCT_AVER_HID_REQ_ISP_FILE_DNLOAD_SIZE_DATA); if (!fu_aver_hid_device_isp_file_dnload(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* ISP_FILE_END */ if (!fu_aver_hid_device_isp_file_end(self, fw_size, aver_fw_name, error)) return FALSE; /* poll for the file untar progress */ if (!fu_device_retry_full(device, fu_aver_hid_device_wait_for_untar_cb, FU_AVER_HID_DEVICE_ISP_UNTAR_WAIT_COUNT, FU_AVER_HID_DEVICE_GET_STATUS_POLL_INTERVAL, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* ISP_START */ if (!fu_aver_hid_device_isp_start(self, error)) return FALSE; fu_progress_step_done(progress); /* poll for the actual write progress */ if (!fu_device_retry_full(device, fu_aver_hid_device_wait_for_reboot_cb, FU_AVER_HID_DEVICE_ISP_RETRY_COUNT, FU_AVER_HID_DEVICE_GET_STATUS_POLL_INTERVAL, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* send ISP_REBOOT, no response expected */ if (!fu_aver_hid_device_isp_reboot(self, error)) return FALSE; fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* success! */ return TRUE; } static void fu_aver_hid_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 74, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 25, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reload"); } static void fu_aver_hid_device_init(FuAverHidDevice *self) { fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_add_protocol(FU_DEVICE(self), "com.aver.hid"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_AUTO_PAUSE_POLLING); fu_device_set_poll_interval(FU_DEVICE(self), FU_AVER_HID_DEVICE_POLL_INTERVAL); fu_device_set_remove_delay(FU_DEVICE(self), 150000); fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_RETRY_FAILURE); fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_AUTODETECT_EPS); fu_device_register_private_flag(FU_DEVICE(self), FU_AVER_HID_FLAG_DUAL_ISP, "dual-isp"); } static void fu_aver_hid_device_class_init(FuAverHidDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_aver_hid_device_setup; klass_device->prepare_firmware = fu_aver_hid_device_prepare_firmware; klass_device->write_firmware = fu_aver_hid_device_write_firmware; klass_device->set_progress = fu_aver_hid_device_set_progress; } fwupd-1.9.16/plugins/aver-hid/fu-aver-hid-device.h000066400000000000000000000004601460375044200216370ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_AVER_HID_DEVICE (fu_aver_hid_device_get_type()) G_DECLARE_FINAL_TYPE(FuAverHidDevice, fu_aver_hid_device, FU, AVER_HID_DEVICE, FuHidDevice) fwupd-1.9.16/plugins/aver-hid/fu-aver-hid-firmware.c000066400000000000000000000030761460375044200222150ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-aver-hid-firmware.h" struct _FuAverHidFirmware { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuAverHidFirmware, fu_aver_hid_firmware, FU_TYPE_FIRMWARE) static gboolean fu_aver_hid_firmware_parse_archive_cb(FuArchive *self, const gchar *filename, GBytes *bytes, gpointer user_data, GError **error) { FuFirmware *firmware = FU_FIRMWARE(user_data); if (g_str_has_suffix(filename, ".dat")) { g_autofree gchar *version = g_strndup(filename, strlen(filename) - 4); fu_firmware_set_version(firmware, version); fu_firmware_set_filename(firmware, filename); } return TRUE; } static gboolean fu_aver_hid_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { g_autoptr(FuArchive) archive = NULL; archive = fu_archive_new(fw, FU_ARCHIVE_FLAG_NONE, error); if (archive == NULL) return FALSE; if (!fu_archive_iterate(archive, fu_aver_hid_firmware_parse_archive_cb, firmware, error)) return FALSE; fu_firmware_set_bytes(firmware, fw); return TRUE; } static void fu_aver_hid_firmware_init(FuAverHidFirmware *self) { } static void fu_aver_hid_firmware_class_init(FuAverHidFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_aver_hid_firmware_parse; } FuFirmware * fu_aver_hid_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_AVER_HID_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/aver-hid/fu-aver-hid-firmware.h000066400000000000000000000005471460375044200222220ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_AVER_HID_FIRMWARE (fu_aver_hid_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuAverHidFirmware, fu_aver_hid_firmware, FU, AVER_HID_FIRMWARE, FuFirmware) FuFirmware * fu_aver_hid_firmware_new(void); fwupd-1.9.16/plugins/aver-hid/fu-aver-hid-plugin.c000066400000000000000000000016561460375044200217010ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-aver-hid-device.h" #include "fu-aver-hid-firmware.h" #include "fu-aver-hid-plugin.h" #include "fu-aver-safeisp-device.h" struct _FuAverHidPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuAverHidPlugin, fu_aver_hid_plugin, FU_TYPE_PLUGIN) static void fu_aver_hid_plugin_init(FuAverHidPlugin *self) { } static void fu_aver_hid_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_AVER_HID_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_AVER_SAFEISP_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_AVER_HID_FIRMWARE); } static void fu_aver_hid_plugin_class_init(FuAverHidPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_aver_hid_plugin_constructed; } fwupd-1.9.16/plugins/aver-hid/fu-aver-hid-plugin.h000066400000000000000000000003551460375044200217010ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuAverHidPlugin, fu_aver_hid_plugin, FU, AVER_HID_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/aver-hid/fu-aver-hid.rs000066400000000000000000000074261460375044200206100ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum AverHidStatus { Ready, Busy, Dnload, Waitisp, Isping, Reboot, Fileerr, Powerisp, Version, Waitusr, Stop, } #[derive(ToString)] enum AverHidCustomIspCmd { Status = 0x01, FileStart, FileDnload, FileEnd, Start, Stop, Reserve, LogStart, LogUpload, IspReboot, LogEnd, AllFileStart = 0x11, AllFileDnload, AllFileEnd, AllStart, } #[derive(ToString)] enum AverSafeispCustomCmd { GetVersion = 0x14, Support = 0x29, EraseTemp, UploadPrepare, UploadCompareChecksum, UploadToCx3, UploadToM12mo, UploadToM051, UploadToTmpm342, UploadToTmpm342Boot, UpdateStart, } enum AverSafeispAckStatus { Idle = 0x00, Success, Checksum, ParamErr, PrepareFail, UploadFail, DataRead, DataReadFail, DataWrite, DataWriteFail, VerifyFail, CompareSame, CompareDiff, Support, } #[derive(ToString, Getters, New)] struct AverHidReqIsp { report_id_custom_command: u8 == 0x08, custom_cmd_isp: u8 == 0x10, custom_isp_cmd: u8, data: [u8; 508] = 0xFF, end: u8 == 0x00, } #[derive(Setters, Getters, New)] struct AverHidReqIspFileStart { report_id_custom_command: u8 == 0x08, custom_cmd_isp: u8 == 0x10, custom_isp_cmd: u8, file_name: [char; 52], file_size: u32le, free_space: u32le, _reserved: [u8; 448] = 0xFF, end: u8 == 0x00, } #[derive(Setters, Getters, New)] struct AverHidReqIspFileDnload { report_id_custom_command: u8 == 0x08, custom_cmd_isp: u8 == 0x10, custom_isp_cmd: u8, data: [u8; 508] = 0xFF, } #[derive(Setters, Getters, New)] struct AverHidReqIspFileEnd { report_id_custom_command: u8 == 0x08, custom_cmd_isp: u8 == 0x10, custom_isp_cmd: u8, file_name: [char; 51], end_flag: u8, file_size: u32le, free_space: u32le, _reserved: [u8; 448] = 0xFF, end: u8 == 0x00, } #[derive(Getters, Setters)] struct AverHidReqIspStart { report_id_custom_command: u8 == 0x08, custom_cmd_isp: u8 == 0x10, custom_isp_cmd: u8, isp_cmd: [u8; 60], _reserved: [u8; 448] = 0xFF, end: u8 == 0x00, } #[derive(Getters, New)] struct AverHidReqDeviceVersion { report_id_custom_command: u8 == 0x08, custom_cmd_isp: u8 == 0x25, ver: [u8; 11], _reserved: [u8; 498] = 0xFF, end: u8 == 0x00, } #[derive(New, Getters, Validate)] struct AverHidResIspStatus { report_id_custom_command: u8 == 0x09, custom_cmd_isp: u8 == 0x10, custom_isp_cmd: u8, status: u8, status_string: [char; 58], progress: u8, _reserved: [u8; 448] = 0xFF, end: u8 == 0x00, } #[derive(Getters, Setters)] struct AverHidResIsp { report_id_custom_command: u8 == 0x09, custom_cmd_isp: u8 == 0x10, custom_isp_cmd: u8, _reserved: [u8; 508] = 0xFF, end: u8 == 0x00, } #[derive(Getters, New, Validate)] struct AverHidResDeviceVersion { report_id_custom_command: u8 == 0x09, custom_cmd_isp: u8 == 0x25, ver: [u8; 11], _reserved: [u8; 498] = 0xFF, end: u8 == 0x00, } #[derive(Setters, Getters, New)] struct AverSafeispReq { report_id_custom_command: u8 == 0x08, custom_cmd: u8, custom_res: u16, custom_parm0: u32 = 0x00, custom_parm1: u32 = 0x00, data: [u8; 1012] = 0x00, }; #[derive(New, Getters, Validate)] struct AverSafeispRes { report_id_custom_command: u8 == 0x09, custom_cmd: u8, custom_res: u16, custom_parm0: u32, custom_parm1: u32, data: [u8; 4] = 0x00, }; #[derive(Getters, Validate)] struct AverSafeispResDeviceVersion { report_id_custom_command: u8 == 0x09, custom_cmd: u8 == 0x14, ver: [u8; 11], _reserved: [u8; 3] = 0x00, } fwupd-1.9.16/plugins/aver-hid/fu-aver-safeisp-device.c000066400000000000000000000350501460375044200225230ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-aver-hid-firmware.h" #include "fu-aver-hid-struct.h" #include "fu-aver-safeisp-device.h" struct _FuAverSafeispDevice { FuHidDevice parent_instance; }; G_DEFINE_TYPE(FuAverSafeispDevice, fu_aver_safeisp_device, FU_TYPE_HID_DEVICE) #define FU_AVER_SAFEISP_DEVICE_TIMEOUT 100000 /* ms */ #define FU_AVER_SAFEISP_DEVICE_POLL_INTERVAL 5000 /* ms */ typedef enum { ISP_CX3, ISP_M12 } FuAverSafeIspPartition; static gboolean fu_aver_safeisp_device_transfer(FuAverSafeispDevice *self, GByteArray *req, GByteArray *res, GError **error) { if (req != NULL) { if (!fu_hid_device_set_report(FU_HID_DEVICE(self), req->data[0], req->data, req->len, FU_AVER_SAFEISP_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) { g_prefix_error(error, "failed to send packet: "); return FALSE; } } if (res != NULL) { if (!fu_hid_device_get_report(FU_HID_DEVICE(self), res->data[0], res->data, res->len, FU_AVER_SAFEISP_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) { g_prefix_error(error, "failed to receive packet: "); return FALSE; } g_debug("custom-isp-cmd: %s [0x%x]", fu_aver_safeisp_custom_cmd_to_string( fu_struct_aver_safeisp_res_get_custom_cmd(res)), fu_struct_aver_safeisp_res_get_custom_cmd(res)); } return TRUE; } static gboolean fu_aver_safeisp_device_poll(FuDevice *device, GError **error) { FuAverSafeispDevice *self = FU_AVER_SAFEISP_DEVICE(device); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GByteArray) req = fu_struct_aver_safeisp_req_new(); g_autoptr(GByteArray) res = fu_struct_aver_safeisp_res_new(); locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; fu_struct_aver_safeisp_req_set_custom_cmd(req, FU_AVER_SAFEISP_CUSTOM_CMD_GET_VERSION); if (!fu_aver_safeisp_device_transfer(self, req, res, error)) return FALSE; return TRUE; } static gboolean fu_aver_safeisp_device_ensure_version(FuAverSafeispDevice *self, GError **error) { g_autofree gchar *ver = NULL; g_autoptr(GByteArray) req = fu_struct_aver_safeisp_req_new(); g_autoptr(GByteArray) res = fu_struct_aver_safeisp_res_new(); fu_struct_aver_safeisp_req_set_custom_cmd(req, FU_AVER_SAFEISP_CUSTOM_CMD_GET_VERSION); if (!fu_aver_safeisp_device_transfer(self, req, res, error)) return FALSE; if (!fu_struct_aver_safeisp_res_device_version_validate(res->data, res->len, 0x0, error)) return FALSE; ver = fu_strsafe((const gchar *)fu_struct_aver_safeisp_res_device_version_get_ver(res, NULL), FU_STRUCT_AVER_SAFEISP_RES_DEVICE_VERSION_SIZE_VER); fu_device_set_version(FU_DEVICE(self), ver); return TRUE; } static gboolean fu_aver_safeisp_device_setup(FuDevice *device, GError **error) { FuAverSafeispDevice *self = FU_AVER_SAFEISP_DEVICE(device); /* HidDevice->setup */ if (!FU_DEVICE_CLASS(fu_aver_safeisp_device_parent_class)->setup(device, error)) return FALSE; /* using isp status requests as polling device requests */ if (!fu_aver_safeisp_device_poll(device, error)) return FALSE; /* get the version from the hardware while open */ if (!fu_aver_safeisp_device_ensure_version(self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_aver_safeisp_device_support(FuAverSafeispDevice *self, GError **error) { g_autoptr(GByteArray) req = fu_struct_aver_safeisp_req_new(); g_autoptr(GByteArray) res = fu_struct_aver_safeisp_res_new(); fu_struct_aver_safeisp_req_set_custom_cmd(req, FU_AVER_SAFEISP_CUSTOM_CMD_SUPPORT); if (!fu_aver_safeisp_device_transfer(self, req, res, error)) return FALSE; if (!fu_struct_aver_safeisp_res_validate(res->data, res->len, 0x0, error)) return FALSE; if (fu_struct_aver_safeisp_res_get_custom_cmd(res) != FU_AVER_SAFEISP_ACK_STATUS_SUPPORT) return FALSE; return TRUE; } static gboolean fu_aver_safeisp_device_upload_prepare(FuAverSafeispDevice *self, FuAverSafeIspPartition partition, gsize size, GError **error) { g_autoptr(GByteArray) req = fu_struct_aver_safeisp_req_new(); g_autoptr(GByteArray) res = fu_struct_aver_safeisp_res_new(); fu_struct_aver_safeisp_req_set_custom_cmd(req, FU_AVER_SAFEISP_CUSTOM_CMD_UPLOAD_PREPARE); fu_struct_aver_safeisp_req_set_custom_parm0(req, partition); fu_struct_aver_safeisp_req_set_custom_parm1(req, size); if (!fu_aver_safeisp_device_transfer(self, req, res, error)) return FALSE; if (!fu_struct_aver_safeisp_res_validate(res->data, res->len, 0x0, error)) return FALSE; return TRUE; } static gboolean fu_aver_safeisp_device_erase_flash(FuAverSafeispDevice *self, gsize param0, gsize param1, GError **error) { g_autoptr(GByteArray) req = fu_struct_aver_safeisp_req_new(); g_autoptr(GByteArray) res = fu_struct_aver_safeisp_res_new(); fu_struct_aver_safeisp_req_set_custom_cmd(req, FU_AVER_SAFEISP_CUSTOM_CMD_ERASE_TEMP); fu_struct_aver_safeisp_req_set_custom_parm0(req, param0); fu_struct_aver_safeisp_req_set_custom_parm1(req, param1); if (!fu_aver_safeisp_device_transfer(self, req, res, error)) return FALSE; if (!fu_struct_aver_safeisp_res_validate(res->data, res->len, 0x0, error)) return FALSE; return TRUE; } static gboolean fu_aver_safeisp_device_upload(FuAverSafeispDevice *self, FuChunkArray *chunks, FuProgress *progress, FuAverSafeIspPartition partition, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = NULL; g_autoptr(GByteArray) req = fu_struct_aver_safeisp_req_new(); g_autoptr(GByteArray) res = fu_struct_aver_safeisp_res_new(); /* prepare chunk */ chk = fu_chunk_array_index(chunks, i); if (chk == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "invalid chunk %u for argument %u", i, partition); return FALSE; } /* copy in payload */ if (partition == ISP_CX3) { fu_struct_aver_safeisp_req_set_custom_cmd( req, FU_AVER_SAFEISP_CUSTOM_CMD_UPLOAD_TO_CX3); } else if (partition == ISP_M12) { fu_struct_aver_safeisp_req_set_custom_cmd( req, FU_AVER_SAFEISP_CUSTOM_CMD_UPLOAD_TO_M12MO); } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "invalid argument %u", partition); return FALSE; } fu_struct_aver_safeisp_req_set_custom_parm0(req, fu_chunk_get_address(chk)); fu_struct_aver_safeisp_req_set_custom_parm1(req, fu_chunk_get_data_sz(chk)); if (!fu_memcpy_safe(req->data, req->len, FU_STRUCT_AVER_SAFEISP_REQ_OFFSET_DATA, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; /* resize the last packet */ if ((i == (fu_chunk_array_length(chunks) - 1)) && (fu_chunk_get_data_sz(chk) < 512)) { fu_byte_array_set_size(req, 12 + fu_chunk_get_data_sz(chk), 0x0); fu_struct_aver_safeisp_req_set_custom_parm1(req, fu_chunk_get_data_sz(chk)); } if (!fu_aver_safeisp_device_transfer(self, req, res, error)) return FALSE; if (!fu_struct_aver_safeisp_res_validate(res->data, res->len, 0x0, error)) return FALSE; /* update progress */ fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_aver_safeisp_device_upload_checksum(FuAverSafeispDevice *self, gsize param0, gsize param1, GError **error) { g_autoptr(GByteArray) req = fu_struct_aver_safeisp_req_new(); g_autoptr(GByteArray) res = fu_struct_aver_safeisp_res_new(); fu_struct_aver_safeisp_req_set_custom_cmd( req, FU_AVER_SAFEISP_CUSTOM_CMD_UPLOAD_COMPARE_CHECKSUM); fu_struct_aver_safeisp_req_set_custom_parm0(req, param0); fu_struct_aver_safeisp_req_set_custom_parm1(req, param1); if (!fu_aver_safeisp_device_transfer(self, req, res, error)) return FALSE; if (!fu_struct_aver_safeisp_res_validate(res->data, res->len, 0x0, error)) return FALSE; if (fu_struct_aver_safeisp_req_get_custom_cmd(res) != FU_AVER_SAFEISP_ACK_STATUS_SUCCESS) return FALSE; return TRUE; } static gboolean fu_aver_safeisp_device_update(FuAverSafeispDevice *self, gsize param0, gsize param1, GError **error) { g_autoptr(GByteArray) req = fu_struct_aver_safeisp_req_new(); fu_struct_aver_safeisp_req_set_custom_cmd(req, FU_AVER_SAFEISP_CUSTOM_CMD_UPDATE_START); fu_struct_aver_safeisp_req_set_custom_parm0(req, param0); fu_struct_aver_safeisp_req_set_custom_parm1(req, param1); if (!fu_aver_safeisp_device_transfer(self, req, NULL, error)) return FALSE; return TRUE; } static gboolean fu_aver_safeisp_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuAverSafeispDevice *self = FU_AVER_SAFEISP_DEVICE(device); gsize cx3_fw_size; gsize m12_fw_size; const guint8 *cx3_fw_buf; const guint8 *m12_fw_buf; guint32 cx3_checksum = 0; guint32 m12_checksum = 0; g_autoptr(FuArchive) archive = NULL; g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GBytes) cx3_fw = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) m12_fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 5, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 58, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 34, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* decompress */ archive = fu_archive_new(fw, FU_ARCHIVE_FLAG_NONE, error); if (archive == NULL) return FALSE; cx3_fw = fu_archive_lookup_by_fn(archive, "update/cx3uvc.img", error); if (cx3_fw == NULL) return FALSE; m12_fw = fu_archive_lookup_by_fn(archive, "update/RS_M12MO.bin", error); if (m12_fw == NULL) return FALSE; /* CX3 fw file size should be less than 256KB */ cx3_fw_buf = g_bytes_get_data(cx3_fw, &cx3_fw_size); if (cx3_fw_size > 256 * 1024) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cx3 file size is invalid: 0x%x", (guint)cx3_fw_size); return FALSE; } /* calculate CX3 firmware checksum */ cx3_checksum = fu_sum32(cx3_fw_buf, cx3_fw_size); /* M12 fw file size should be less than 3MB */ m12_fw_buf = g_bytes_get_data(m12_fw, &m12_fw_size); if (m12_fw_size > 3 * 1024 * 1024) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "m12 file size is invalid: 0x%x", (guint)m12_fw_size); return FALSE; } /* calculate M12 firmware checksum */ m12_checksum = fu_sum32(m12_fw_buf, m12_fw_size); /* check if the device supports safeisp */ if (!fu_aver_safeisp_device_support(self, error)) return FALSE; /* CX3 safeisp prepare */ if (!fu_aver_safeisp_device_upload_prepare(self, ISP_CX3, cx3_fw_size, error)) return FALSE; fu_progress_step_done(progress); /* CX3 safeisp erase flash */ if (!fu_aver_safeisp_device_erase_flash(self, ISP_CX3, 0x0, error)) return FALSE; /* CX3 safeisp firmware upload */ chunks = fu_chunk_array_new_from_bytes(cx3_fw, 0x00, 512); if (!fu_aver_safeisp_device_upload(self, chunks, fu_progress_get_child(progress), ISP_CX3, error)) return FALSE; fu_progress_step_done(progress); /* CX3 safeisp checksum */ if (!fu_aver_safeisp_device_upload_checksum(self, ISP_CX3, cx3_checksum, error)) return FALSE; fu_progress_step_done(progress); /* M12 safeisp prepare */ if (!fu_aver_safeisp_device_upload_prepare(self, ISP_M12, m12_fw_size, error)) return FALSE; fu_progress_step_done(progress); /* M12 safeisp erase flash */ if (!fu_aver_safeisp_device_erase_flash(self, ISP_M12, 0x0, error)) return FALSE; /* M12 safeisp firmware upload */ chunks = fu_chunk_array_new_from_bytes(m12_fw, 0x00, 512); if (!fu_aver_safeisp_device_upload(self, chunks, fu_progress_get_child(progress), ISP_M12, error)) return FALSE; fu_progress_step_done(progress); /* M12 safeisp checksum */ if (!fu_aver_safeisp_device_upload_checksum(self, ISP_M12, m12_checksum, error)) return FALSE; fu_progress_step_done(progress); /* update device */ if (!fu_aver_safeisp_device_update(self, ((1 << ISP_CX3) | (1 << ISP_M12)), 0x0, error)) return FALSE; fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* success! */ return TRUE; } static void fu_aver_safeisp_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 68, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 31, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reload"); } static void fu_aver_safeisp_device_init(FuAverSafeispDevice *self) { fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_add_protocol(FU_DEVICE(self), "com.aver.safeisp"); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_AVER_HID_FIRMWARE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_AUTO_PAUSE_POLLING); fu_device_set_poll_interval(FU_DEVICE(self), FU_AVER_SAFEISP_DEVICE_POLL_INTERVAL); fu_device_set_remove_delay(FU_DEVICE(self), 150000); fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_RETRY_FAILURE); fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_AUTODETECT_EPS); } static void fu_aver_safeisp_device_class_init(FuAverSafeispDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->poll = fu_aver_safeisp_device_poll; klass_device->setup = fu_aver_safeisp_device_setup; klass_device->write_firmware = fu_aver_safeisp_device_write_firmware; klass_device->set_progress = fu_aver_safeisp_device_set_progress; } fwupd-1.9.16/plugins/aver-hid/fu-aver-safeisp-device.h000066400000000000000000000005361460375044200225310ustar00rootroot00000000000000/* * Copyright (C) 2024 Pierce Wang * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_AVER_SAFEISP_DEVICE (fu_aver_safeisp_device_get_type()) G_DECLARE_FINAL_TYPE(FuAverSafeispDevice, fu_aver_safeisp_device, FU, AVER_SAFEISP_DEVICE, FuHidDevice) fwupd-1.9.16/plugins/aver-hid/meson.build000066400000000000000000000010001460375044200202530ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginAverHid"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('aver-hid.quirk') plugin_builtins += static_library('fu_plugin_aver_hid', rustgen.process('fu-aver-hid.rs'), sources: [ 'fu-aver-hid-device.c', 'fu-aver-safeisp-device.c', 'fu-aver-hid-firmware.c', 'fu-aver-hid-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/aver-hid/test/000077500000000000000000000000001460375044200171015ustar00rootroot00000000000000fwupd-1.9.16/plugins/aver-hid/test/firmware.metainfo.xml000066400000000000000000000024331460375044200232420ustar00rootroot00000000000000 com.aver.FONE540.firmware FONE540 System firmware for AVer FONE540

    AVer Fone540 can be updated using fwupd with plugin aver-hid.

    audio-card a5dcbf10-6530-11d2-901f-00c04fb951ed https://www.aver.com/ CC0-1.0 LicenseRef-proprietary AVer Information Inc. X-Device

    This release updates firmware to 0.0.7004.02

    org.freedesktop.fwupd plain com.aver.hid
    fwupd-1.9.16/plugins/bcm57xx/000077500000000000000000000000001460375044200157205ustar00rootroot00000000000000fwupd-1.9.16/plugins/bcm57xx/README.md000066400000000000000000000026701460375044200172040ustar00rootroot00000000000000--- title: Plugin: BCM57xx --- ## Introduction This plugin updates BCM57xx wired network adaptors from Broadcom using a reverse-engineered flashing protocol. It is designed to be used with the clean-room reimplementation of the BCM5719 firmware found here: ## Protocol BCM57xx devices support a custom `com.broadcom.bcm57xx` protocol which is implemented as ioctls like ethtool does. ## GUID Generation These devices use the standard PCI instance IDs, for example: * `PCI\VEN_14E4&DEV_1657` * `PCI\VEN_14E4&DEV_1657&SUBSYS_17AA222E` ## Update Behavior The device usually presents in runtime mode, and the firmware is written to the device without disconnecting the working kernel driver. Once complete the APE is reset which may cause a brief link reconnection. On flash failure the device is nonfunctional, but is recoverable using direct BAR writes, which is typically much slower than updating the device using the kernel driver and the ethtool API. ## Vendor ID Security The vendor ID is set from the PCI vendor, in this instance set to `PCI:0x14E4` ## External Interface Access This plugin requires the `SIOCETHTOOL` ioctl interface. ## Version Considerations This plugin has been available since fwupd version `1.5.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Evan Lojewski: @meklort fwupd-1.9.16/plugins/bcm57xx/bcm57xx.quirk000066400000000000000000000004121460375044200202670ustar00rootroot00000000000000# Broadcom BCM5719 [PCI\VEN_14E4&DEV_1657] Plugin = bcm57xx # OEM PCI cards [PCI\VEN_14E4&DEV_1657&SUBSYS_14E41904] FirmwareSize = 0x80000 [PCI\VEN_14E4&DEV_1657&SUBSYS_94E41904] FirmwareSize = 0x80000 [PCI\VEN_14E4&DEV_1657&SUBSYS_17AA402D] FirmwareSize = 0x80000 fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-common.c000066400000000000000000000051401460375044200212570ustar00rootroot00000000000000/* * Copyright (C) 2018 Evan Lojewski * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-bcm57xx-common.h" guint32 fu_bcm57xx_nvram_crc(const guint8 *buf, gsize bufsz) { return fu_crc32(buf, bufsz); } gboolean fu_bcm57xx_verify_crc(GBytes *fw, GError **error) { guint32 crc_actual; guint32 crc_file = 0; gsize bufsz = 0x0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* expected */ if (!fu_memread_uint32_safe(buf, bufsz, bufsz - sizeof(guint32), &crc_file, G_LITTLE_ENDIAN, error)) return FALSE; /* reality */ crc_actual = fu_bcm57xx_nvram_crc(buf, bufsz - sizeof(guint32)); if (crc_actual != crc_file) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid CRC, expected 0x%08x got: 0x%08x", (guint)crc_file, (guint)crc_actual); return FALSE; } /* success */ return TRUE; } gboolean fu_bcm57xx_verify_magic(GBytes *fw, gsize offset, GError **error) { guint32 magic = 0; gsize bufsz = 0x0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* hardcoded value */ if (!fu_memread_uint32_safe(buf, bufsz, offset, &magic, G_BIG_ENDIAN, error)) return FALSE; if (magic != BCM_NVRAM_MAGIC) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid magic, got: 0x%x", (guint)magic); return FALSE; } /* success */ return TRUE; } void fu_bcm57xx_veritem_free(Bcm57xxVeritem *veritem) { g_free(veritem->branch); g_free(veritem->version); g_free(veritem); } Bcm57xxVeritem * fu_bcm57xx_veritem_new(const guint8 *buf, gsize bufsz) { g_autofree gchar *tmp = NULL; g_autoptr(Bcm57xxVeritem) veritem = g_new0(Bcm57xxVeritem, 1); struct { const gchar *prefix; const gchar *branch; FwupdVersionFormat verfmt; } data[] = {{"5719-v", BCM_FW_BRANCH_UNKNOWN, FWUPD_VERSION_FORMAT_PAIR}, {"stage1-", BCM_FW_BRANCH_OSS_FIRMWARE, FWUPD_VERSION_FORMAT_TRIPLET}, {NULL, NULL, 0}}; /* do not assume this is NUL terminated */ tmp = g_strndup((const gchar *)buf, bufsz); if (tmp == NULL || tmp[0] == '\0') return NULL; /* use prefix to define object */ for (guint i = 0; data[i].prefix != NULL; i++) { if (g_str_has_prefix(tmp, data[i].prefix)) { veritem->version = g_strdup(tmp + strlen(data[i].prefix)); veritem->branch = g_strdup(data[i].branch); veritem->verfmt = data[i].verfmt; return g_steal_pointer(&veritem); } } veritem->verfmt = FWUPD_VERSION_FORMAT_UNKNOWN; veritem->version = g_strdup(tmp); return g_steal_pointer(&veritem); } fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-common.h000066400000000000000000000035061460375044200212700ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define BCM_VENDOR_BROADCOM 0x14E4 #define BCM_FW_BRANCH_UNKNOWN NULL #define BCM_FW_BRANCH_OSS_FIRMWARE "oss-firmware" #define BCM_FIRMWARE_SIZE 0x40000 /* x2 for Dell */ #define BCM_PHYS_ADDR_DEFAULT 0x08003800 #define BCM_NVRAM_MAGIC 0x669955AA /* offsets into NVMRAM */ #define BCM_NVRAM_HEADER_BASE 0x00 #define BCM_NVRAM_DIRECTORY_BASE 0x14 #define BCM_NVRAM_INFO_BASE 0x74 #define BCM_NVRAM_VPD_BASE 0x100 #define BCM_NVRAM_INFO2_BASE 0x200 #define BCM_NVRAM_STAGE1_BASE 0x28c #define BCM_NVRAM_HEADER_MAGIC 0x00 #define BCM_NVRAM_HEADER_PHYS_ADDR 0x04 #define BCM_NVRAM_HEADER_SIZE_WRDS 0x08 #define BCM_NVRAM_HEADER_OFFSET 0x0C #define BCM_NVRAM_HEADER_CRC 0x10 #define BCM_NVRAM_HEADER_SZ 0x14 #define BCM_NVRAM_INFO_MAC_ADDR0 0x00 #define BCM_NVRAM_INFO_VENDOR 0x2E #define BCM_NVRAM_INFO_DEVICE 0x2C #define BCM_NVRAM_INFO_SZ 0x8C #define BCM_NVRAM_DIRECTORY_ADDR 0x00 #define BCM_NVRAM_DIRECTORY_SIZE_WRDS 0x04 #define BCM_NVRAM_DIRECTORY_OFFSET 0x08 #define BCM_NVRAM_DIRECTORY_SZ 0x0c #define BCM_NVRAM_VPD_SZ 0x100 #define BCM_NVRAM_INFO2_SZ 0x8c #define BCM_NVRAM_STAGE1_VERADDR 0x08 #define BCM_NVRAM_STAGE1_VERSION 0x0C typedef struct { gchar *branch; gchar *version; FwupdVersionFormat verfmt; } Bcm57xxVeritem; guint32 fu_bcm57xx_nvram_crc(const guint8 *buf, gsize bufsz); gboolean fu_bcm57xx_verify_crc(GBytes *fw, GError **error); gboolean fu_bcm57xx_verify_magic(GBytes *fw, gsize offset, GError **error); /* parses stage1 version */ void fu_bcm57xx_veritem_free(Bcm57xxVeritem *veritem); Bcm57xxVeritem * fu_bcm57xx_veritem_new(const guint8 *buf, gsize bufsz); G_DEFINE_AUTOPTR_CLEANUP_FUNC(Bcm57xxVeritem, fu_bcm57xx_veritem_free) fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-device.c000066400000000000000000000513501460375044200212320ustar00rootroot00000000000000/* * Copyright (C) 2018 Evan Lojewski * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_ERRNO_H #include #endif #include #include #ifdef HAVE_ETHTOOL_H #include #include #include #endif #ifdef HAVE_IOCTL_H #include #endif #ifdef HAVE_SOCKET_H #include #endif #include "fu-bcm57xx-common.h" #include "fu-bcm57xx-device.h" #include "fu-bcm57xx-dict-image.h" #include "fu-bcm57xx-firmware.h" #include "fu-bcm57xx-recovery-device.h" #define FU_BCM57XX_BLOCK_SZ 0x4000 /* 16kb */ struct _FuBcm57xxDevice { FuUdevDevice parent_instance; FuBcm57xxRecoveryDevice *recovery; gchar *ethtool_iface; int ethtool_fd; }; G_DEFINE_TYPE(FuBcm57xxDevice, fu_bcm57xx_device, FU_TYPE_UDEV_DEVICE) static void fu_bcm57xx_device_to_string(FuDevice *device, guint idt, GString *str) { FuBcm57xxDevice *self = FU_BCM57XX_DEVICE(device); FU_DEVICE_CLASS(fu_bcm57xx_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "EthtoolIface", self->ethtool_iface); } static gboolean fu_bcm57xx_device_probe(FuDevice *device, GError **error) { FuBcm57xxDevice *self = FU_BCM57XX_DEVICE(device); g_autofree gchar *fn = NULL; g_autoptr(GPtrArray) ifaces = NULL; /* only enumerate number 0 */ if (fu_udev_device_get_number(FU_UDEV_DEVICE(device)) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "only device 0 supported on multi-device card"); return FALSE; } /* we need this even for non-recovery to reset APE */ fu_device_set_context(FU_DEVICE(self->recovery), fu_device_get_context(FU_DEVICE(self))); fu_device_incorporate(FU_DEVICE(self->recovery), FU_DEVICE(self)); if (!fu_device_probe(FU_DEVICE(self->recovery), error)) return FALSE; /* only if has an interface */ fn = g_build_filename(fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device)), "net", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS)) { g_debug("waiting for net devices to appear"); fu_device_sleep(device, 50); /* ms */ } ifaces = fu_path_glob(fn, "en*", NULL); if (ifaces == NULL || ifaces->len == 0) { fu_device_add_child(FU_DEVICE(self), FU_DEVICE(self->recovery)); } else { self->ethtool_iface = g_path_get_basename(g_ptr_array_index(ifaces, 0)); } /* success */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "pci", error); } static gboolean fu_bcm57xx_device_nvram_write(FuBcm57xxDevice *self, guint32 address, const guint8 *buf, gsize bufsz, GError **error) { #ifdef HAVE_ETHTOOL_H gsize eepromsz; gint rc = -1; struct ifreq ifr = {0}; g_autofree struct ethtool_eeprom *eeprom = NULL; /* failed to load tg3 */ if (self->ethtool_iface == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as ethtool interface disabled"); return FALSE; } /* sanity check */ if (address + bufsz > fu_device_get_firmware_size_max(FU_DEVICE(self))) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "tried to read outside of EEPROM size [0x%x]", (guint)fu_device_get_firmware_size_max(FU_DEVICE(self))); return FALSE; } /* write EEPROM (NVRAM) data */ eepromsz = sizeof(struct ethtool_eeprom) + bufsz; eeprom = (struct ethtool_eeprom *)g_malloc0(eepromsz); eeprom->cmd = ETHTOOL_SEEPROM; eeprom->magic = BCM_NVRAM_MAGIC; eeprom->len = bufsz; eeprom->offset = address; memcpy(eeprom->data, buf, eeprom->len); strncpy(ifr.ifr_name, self->ethtool_iface, IFNAMSIZ - 1); ifr.ifr_data = (char *)eeprom; #ifdef HAVE_IOCTL_H rc = ioctl(self->ethtool_fd, SIOCETHTOOL, &ifr); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as not found"); return FALSE; #endif if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot write eeprom [%i]", rc); return FALSE; } /* success */ return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as not found"); return FALSE; #endif } static gboolean fu_bcm57xx_device_nvram_read(FuBcm57xxDevice *self, guint32 address, guint8 *buf, gsize bufsz, GError **error) { #ifdef HAVE_ETHTOOL_H gsize eepromsz; gint rc = -1; struct ifreq ifr = {0}; g_autofree struct ethtool_eeprom *eeprom = NULL; /* failed to load tg3 */ if (self->ethtool_iface == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as ethtool interface disabled"); return FALSE; } /* sanity check */ if (address + bufsz > fu_device_get_firmware_size_max(FU_DEVICE(self))) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "tried to read outside of EEPROM size [0x%x]", (guint)fu_device_get_firmware_size_max(FU_DEVICE(self))); return FALSE; } /* read EEPROM (NVRAM) data */ eepromsz = sizeof(struct ethtool_eeprom) + bufsz; eeprom = (struct ethtool_eeprom *)g_malloc0(eepromsz); eeprom->cmd = ETHTOOL_GEEPROM; eeprom->len = bufsz; eeprom->offset = address; strncpy(ifr.ifr_name, self->ethtool_iface, IFNAMSIZ - 1); ifr.ifr_data = (char *)eeprom; #ifdef HAVE_IOCTL_H rc = ioctl(self->ethtool_fd, SIOCETHTOOL, &ifr); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as not found"); return FALSE; #endif if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot read eeprom [%i]", rc); return FALSE; } /* copy back data */ if (!fu_memcpy_safe(buf, bufsz, 0x0, /* dst */ (guint8 *)eeprom, eepromsz, /* src */ G_STRUCT_OFFSET(struct ethtool_eeprom, data), bufsz, error)) return FALSE; /* success */ return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as not found"); return FALSE; #endif } static gboolean fu_bcm57xx_device_nvram_check(FuBcm57xxDevice *self, GError **error) { #ifdef HAVE_ETHTOOL_H gint rc = -1; struct ethtool_drvinfo drvinfo = {0}; struct ifreq ifr = {0}; /* failed to load tg3 */ if (self->ethtool_iface == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as ethtool interface disabled"); return FALSE; } /* get driver info */ drvinfo.cmd = ETHTOOL_GDRVINFO; strncpy(ifr.ifr_name, self->ethtool_iface, IFNAMSIZ - 1); ifr.ifr_data = (char *)&drvinfo; #ifdef HAVE_IOCTL_H rc = ioctl(self->ethtool_fd, SIOCETHTOOL, &ifr); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as not found"); return FALSE; #endif if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot get driver information [%i]", rc); return FALSE; } g_debug("FW version %s", drvinfo.fw_version); /* detect more OEM cards */ if (drvinfo.eedump_len == fu_device_get_firmware_size_max(FU_DEVICE(self)) * 2) { g_autofree gchar *subsys = g_strdup_printf("%04X%04X", fu_udev_device_get_subsystem_vendor(FU_UDEV_DEVICE(self)), fu_udev_device_get_subsystem_model(FU_UDEV_DEVICE(self))); g_debug("auto-sizing expected EEPROM size for OEM SUBSYS %s", subsys); fu_device_set_firmware_size(FU_DEVICE(self), drvinfo.eedump_len); } else if (drvinfo.eedump_len != fu_device_get_firmware_size_max(FU_DEVICE(self))) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "EEPROM size invalid, got 0x%x, expected 0x%x", drvinfo.eedump_len, (guint)fu_device_get_firmware_size_max(FU_DEVICE(self))); return FALSE; } /* success */ return TRUE; #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as not found"); return FALSE; #endif } static gboolean fu_bcm57xx_device_activate(FuDevice *device, FuProgress *progress, GError **error) { FuBcm57xxDevice *self = FU_BCM57XX_DEVICE(device); g_autoptr(FuDeviceLocker) locker1 = NULL; g_autoptr(FuDeviceLocker) locker2 = NULL; /* the only way to do this is using the mmap method */ locker2 = fu_device_locker_new_full(FU_DEVICE(self->recovery), (FuDeviceLockerFunc)fu_device_detach, (FuDeviceLockerFunc)fu_device_attach, error); if (locker2 == NULL) return FALSE; /* open */ locker1 = fu_device_locker_new(FU_DEVICE(self->recovery), error); if (locker1 == NULL) return FALSE; /* activate, causing APE reset, then close, then attach */ if (!fu_device_activate(FU_DEVICE(self->recovery), progress, error)) return FALSE; /* ensure we attach before we close */ if (!fu_device_locker_close(locker2, error)) return FALSE; /* wait for the device to restart before calling reload() */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_BUSY); fu_device_sleep_full(device, 5000, progress); /* ms */ return TRUE; } static GBytes * fu_bcm57xx_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuBcm57xxDevice *self = FU_BCM57XX_DEVICE(device); const gsize bufsz = fu_device_get_firmware_size_max(FU_DEVICE(self)); g_autofree guint8 *buf = g_malloc0(bufsz); g_autoptr(GPtrArray) chunks = NULL; chunks = fu_chunk_array_mutable_new(buf, bufsz, 0x0, 0x0, FU_BCM57XX_BLOCK_SZ); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_bcm57xx_device_nvram_read(self, fu_chunk_get_address(chk), fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), error)) return NULL; fu_progress_step_done(progress); } /* read from hardware */ return g_bytes_new_take(g_steal_pointer(&buf), bufsz); } static FuFirmware * fu_bcm57xx_device_read_firmware(FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(FuFirmware) firmware = fu_bcm57xx_firmware_new(); g_autoptr(GBytes) fw = NULL; /* read from hardware */ fw = fu_bcm57xx_device_dump_firmware(device, progress, error); if (fw == NULL) return NULL; if (!fu_firmware_parse(firmware, fw, FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return NULL; /* remove images that will contain user-data */ if (!fu_firmware_remove_image_by_id(firmware, "info", error)) return NULL; if (!fu_firmware_remove_image_by_id(firmware, "info2", error)) return NULL; if (!fu_firmware_remove_image_by_id(firmware, "vpd", error)) return NULL; return g_steal_pointer(&firmware); } static FuFirmware * fu_bcm57xx_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { guint dict_cnt = 0; g_autofree gchar *str_existing = NULL; g_autofree gchar *str_proposed = NULL; g_autoptr(GBytes) fw_old = NULL; g_autoptr(FuFirmware) firmware = fu_bcm57xx_firmware_new(); g_autoptr(FuFirmware) firmware_tmp = fu_bcm57xx_firmware_new(); g_autoptr(FuFirmware) img_ape = NULL; g_autoptr(FuFirmware) img_stage1 = NULL; g_autoptr(FuFirmware) img_stage2 = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GPtrArray) images = NULL; /* try to parse NVRAM, stage1 or APE */ if (!fu_firmware_parse(firmware_tmp, fw, flags, error)) { g_prefix_error(error, "failed to parse new firmware: "); return NULL; } /* for full NVRAM image, verify if correct device */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) == 0) { guint16 vid = fu_bcm57xx_firmware_get_vendor(FU_BCM57XX_FIRMWARE(firmware_tmp)); guint16 did = fu_bcm57xx_firmware_get_model(FU_BCM57XX_FIRMWARE(firmware_tmp)); if (vid != 0x0 && did != 0x0 && (fu_udev_device_get_vendor(FU_UDEV_DEVICE(device)) != vid || fu_udev_device_get_model(FU_UDEV_DEVICE(device)) != did)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "PCI vendor or model incorrect, " "got: %04X:%04X expected %04X:%04X", vid, did, fu_udev_device_get_vendor(FU_UDEV_DEVICE(device)), fu_udev_device_get_model(FU_UDEV_DEVICE(device))); return NULL; } } /* get the existing firmware from the device */ fw_old = fu_bcm57xx_device_dump_firmware(device, progress, error); if (fw_old == NULL) return NULL; if (!fu_firmware_parse(firmware, fw_old, FWUPD_INSTALL_FLAG_NO_SEARCH, error)) { g_prefix_error(error, "failed to parse existing firmware: "); return NULL; } str_existing = fu_firmware_to_string(firmware); g_info("existing device firmware: %s", str_existing); /* merge in all the provided images into the existing firmware */ img_stage1 = fu_firmware_get_image_by_id(firmware_tmp, "stage1", NULL); if (img_stage1 != NULL) fu_firmware_add_image(firmware, img_stage1); img_stage2 = fu_firmware_get_image_by_id(firmware_tmp, "stage2", NULL); if (img_stage2 != NULL) fu_firmware_add_image(firmware, img_stage2); img_ape = fu_firmware_get_image_by_id(firmware_tmp, "ape", NULL); if (img_ape != NULL) fu_firmware_add_image(firmware, img_ape); /* the src and dst dictionaries may be in different order */ images = fu_firmware_get_images(firmware); for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); if (FU_IS_BCM57XX_DICT_IMAGE(img)) { fu_firmware_set_idx(img, 0x80 + dict_cnt); dict_cnt++; } } str_proposed = fu_firmware_to_string(firmware); g_info("proposed device firmware: %s", str_proposed); /* success */ return g_steal_pointer(&firmware); } static gboolean fu_bcm57xx_device_write_chunks(FuBcm57xxDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_bcm57xx_device_nvram_write(self, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; fu_progress_step_done(progress); } return TRUE; } static gboolean fu_bcm57xx_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuBcm57xxDevice *self = FU_BCM57XX_DEVICE(device); g_autoptr(GBytes) blob = NULL; g_autoptr(GBytes) blob_verify = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "build-img"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80, "write-chunks"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 19, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, NULL); /* build the images into one linear blob of the correct size */ blob = fu_firmware_write(firmware, error); if (blob == NULL) return FALSE; fu_progress_step_done(progress); /* hit hardware */ chunks = fu_chunk_array_new_from_bytes(blob, 0x0, FU_BCM57XX_BLOCK_SZ); if (!fu_bcm57xx_device_write_chunks(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* verify */ blob_verify = fu_bcm57xx_device_dump_firmware(device, fu_progress_get_child(progress), error); if (blob_verify == NULL) return FALSE; if (!fu_bytes_compare(blob, blob_verify, error)) return FALSE; fu_progress_step_done(progress); /* reset APE */ if (!fu_device_activate(device, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_bcm57xx_device_setup(FuDevice *device, GError **error) { FuBcm57xxDevice *self = FU_BCM57XX_DEVICE(device); guint32 fwversion = 0; /* device is in recovery mode */ if (self->ethtool_iface == NULL) { g_autoptr(FuDeviceLocker) locker = NULL; g_info("device in recovery mode, use alternate device"); locker = fu_device_locker_new(FU_DEVICE(self->recovery), error); if (locker == NULL) return FALSE; return fu_device_setup(FU_DEVICE(self->recovery), error); } /* check the EEPROM size */ if (!fu_bcm57xx_device_nvram_check(self, error)) return FALSE; /* get NVRAM version */ if (!fu_bcm57xx_device_nvram_read(self, BCM_NVRAM_STAGE1_BASE + BCM_NVRAM_STAGE1_VERSION, (guint8 *)&fwversion, sizeof(guint32), error)) return FALSE; if (fwversion != 0x0) { /* this is only set on the OSS firmware */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version_u32(device, GUINT32_FROM_BE(fwversion)); fu_device_set_branch(device, BCM_FW_BRANCH_OSS_FIRMWARE); } else { guint8 bufver[16] = {0x0}; guint32 veraddr = 0; g_autoptr(Bcm57xxVeritem) veritem = NULL; /* fall back to the string, e.g. '5719-v1.43' */ if (!fu_bcm57xx_device_nvram_read(self, BCM_NVRAM_STAGE1_BASE + BCM_NVRAM_STAGE1_VERADDR, (guint8 *)&veraddr, sizeof(guint32), error)) return FALSE; veraddr = GUINT32_FROM_BE(veraddr); if (veraddr > BCM_PHYS_ADDR_DEFAULT) veraddr -= BCM_PHYS_ADDR_DEFAULT; if (!fu_bcm57xx_device_nvram_read(self, BCM_NVRAM_STAGE1_BASE + veraddr, bufver, sizeof(bufver), error)) return FALSE; veritem = fu_bcm57xx_veritem_new(bufver, sizeof(bufver)); if (veritem != NULL) { fu_device_set_version_format(device, veritem->verfmt); fu_device_set_version(device, veritem->version); fu_device_set_branch(device, veritem->branch); } } /* success */ fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_BACKUP_BEFORE_INSTALL); return TRUE; } static gboolean fu_bcm57xx_device_open(FuDevice *device, GError **error) { #ifdef HAVE_SOCKET_H FuBcm57xxDevice *self = FU_BCM57XX_DEVICE(device); self->ethtool_fd = socket(AF_INET, SOCK_DGRAM, 0); if (self->ethtool_fd < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to open socket: %s", #ifdef HAVE_ERRNO_H g_strerror(errno)); #else "unspecified error"); #endif return FALSE; } return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "socket() not supported as sys/socket.h not available"); return FALSE; #endif } static gboolean fu_bcm57xx_device_close(FuDevice *device, GError **error) { FuBcm57xxDevice *self = FU_BCM57XX_DEVICE(device); return g_close(self->ethtool_fd, error); } static void fu_bcm57xx_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_bcm57xx_device_init(FuBcm57xxDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_protocol(FU_DEVICE(self), "com.broadcom.bcm57xx"); fu_device_add_icon(FU_DEVICE(self), "network-wired"); /* other values are set from a quirk */ fu_device_set_firmware_size(FU_DEVICE(self), BCM_FIRMWARE_SIZE); /* used for recovery in case of ethtool failure and for APE reset */ self->recovery = fu_bcm57xx_recovery_device_new(); } static void fu_bcm57xx_device_finalize(GObject *object) { FuBcm57xxDevice *self = FU_BCM57XX_DEVICE(object); g_free(self->ethtool_iface); G_OBJECT_CLASS(fu_bcm57xx_device_parent_class)->finalize(object); } static void fu_bcm57xx_device_class_init(FuBcm57xxDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_bcm57xx_device_finalize; klass_device->prepare_firmware = fu_bcm57xx_device_prepare_firmware; klass_device->setup = fu_bcm57xx_device_setup; klass_device->reload = fu_bcm57xx_device_setup; klass_device->open = fu_bcm57xx_device_open; klass_device->close = fu_bcm57xx_device_close; klass_device->activate = fu_bcm57xx_device_activate; klass_device->write_firmware = fu_bcm57xx_device_write_firmware; klass_device->read_firmware = fu_bcm57xx_device_read_firmware; klass_device->dump_firmware = fu_bcm57xx_device_dump_firmware; klass_device->probe = fu_bcm57xx_device_probe; klass_device->to_string = fu_bcm57xx_device_to_string; klass_device->set_progress = fu_bcm57xx_device_set_progress; } fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-device.h000066400000000000000000000004551460375044200212370ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_BCM57XX_DEVICE (fu_bcm57xx_device_get_type()) G_DECLARE_FINAL_TYPE(FuBcm57xxDevice, fu_bcm57xx_device, FU, BCM57XX_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-dict-image.c000066400000000000000000000104131460375044200217710ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-bcm57xx-common.h" #include "fu-bcm57xx-dict-image.h" struct _FuBcm57xxDictImage { FuFirmware parent_instance; guint8 target; guint8 kind; }; G_DEFINE_TYPE(FuBcm57xxDictImage, fu_bcm57xx_dict_image, FU_TYPE_FIRMWARE) static void fu_bcm57xx_dict_image_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuBcm57xxDictImage *self = FU_BCM57XX_DICT_IMAGE(firmware); if (self->target != 0xff) fu_xmlb_builder_insert_kx(bn, "target", self->target); if (self->kind != 0xff) fu_xmlb_builder_insert_kx(bn, "kind", self->kind); } static gboolean fu_bcm57xx_dict_image_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { g_autoptr(GBytes) fw_nocrc = NULL; if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { if (!fu_bcm57xx_verify_crc(fw, error)) return FALSE; } fw_nocrc = fu_bytes_new_offset(fw, 0x0, g_bytes_get_size(fw) - sizeof(guint32), error); if (fw_nocrc == NULL) return FALSE; fu_firmware_set_bytes(firmware, fw_nocrc); return TRUE; } static GByteArray * fu_bcm57xx_dict_image_write(FuFirmware *firmware, GError **error) { const guint8 *buf; gsize bufsz = 0; guint32 crc; g_autoptr(GByteArray) blob = NULL; g_autoptr(GBytes) fw_nocrc = NULL; /* get the CRC-less data */ fw_nocrc = fu_firmware_get_bytes(firmware, error); if (fw_nocrc == NULL) return NULL; /* add to a mutable buffer */ buf = g_bytes_get_data(fw_nocrc, &bufsz); blob = g_byte_array_sized_new(bufsz + sizeof(guint32)); fu_byte_array_append_bytes(blob, fw_nocrc); /* add CRC */ crc = fu_bcm57xx_nvram_crc(buf, bufsz); fu_byte_array_append_uint32(blob, crc, G_LITTLE_ENDIAN); return g_steal_pointer(&blob); } static gboolean fu_bcm57xx_dict_image_build(FuFirmware *firmware, XbNode *n, GError **error) { FuBcm57xxDictImage *self = FU_BCM57XX_DICT_IMAGE(firmware); guint64 tmp; /* two simple properties */ tmp = xb_node_query_text_as_uint(n, "kind", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) fu_bcm57xx_dict_image_set_kind(self, tmp); tmp = xb_node_query_text_as_uint(n, "target", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT8) fu_bcm57xx_dict_image_set_target(self, tmp); /* success */ return TRUE; } static void fu_bcm57xx_dict_image_ensure_id(FuBcm57xxDictImage *self) { g_autofree gchar *id = NULL; struct { guint8 target; guint8 kind; const gchar *id; } ids[] = {{0x00, 0x00, "pxe"}, {0x0D, 0x00, "ape"}, {0x09, 0x00, "iscsi1"}, {0x05, 0x00, "iscsi2"}, {0x0b, 0x00, "iscsi3"}, {0x00, 0x01, "cfg1000"}, {0x04, 0x01, "vpd2"}, {0xff, 0xff, NULL}}; if (self->target == 0xff || self->kind == 0xff) return; for (guint i = 0; ids[i].id != NULL; i++) { if (self->target == ids[i].target && self->kind == ids[i].kind) { g_debug("using %s for %02x:%02x", ids[i].id, self->target, self->kind); fu_firmware_set_id(FU_FIRMWARE(self), ids[i].id); return; } } id = g_strdup_printf("dict-%02x-%02x", self->target, self->kind); if (g_getenv("FWUPD_FUZZER_RUNNING") == NULL) g_warning("falling back to %s, please report", id); fu_firmware_set_id(FU_FIRMWARE(self), id); } void fu_bcm57xx_dict_image_set_target(FuBcm57xxDictImage *self, guint8 target) { self->target = target; fu_bcm57xx_dict_image_ensure_id(self); } guint8 fu_bcm57xx_dict_image_get_target(FuBcm57xxDictImage *self) { return self->target; } void fu_bcm57xx_dict_image_set_kind(FuBcm57xxDictImage *self, guint8 kind) { self->kind = kind; fu_bcm57xx_dict_image_ensure_id(self); } guint8 fu_bcm57xx_dict_image_get_kind(FuBcm57xxDictImage *self) { return self->kind; } static void fu_bcm57xx_dict_image_init(FuBcm57xxDictImage *self) { self->target = 0xff; self->kind = 0xff; } static void fu_bcm57xx_dict_image_class_init(FuBcm57xxDictImageClass *klass) { FuFirmwareClass *klass_image = FU_FIRMWARE_CLASS(klass); klass_image->parse = fu_bcm57xx_dict_image_parse; klass_image->write = fu_bcm57xx_dict_image_write; klass_image->build = fu_bcm57xx_dict_image_build; klass_image->export = fu_bcm57xx_dict_image_export; } FuFirmware * fu_bcm57xx_dict_image_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_BCM57XX_DICT_IMAGE, NULL)); } fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-dict-image.h000066400000000000000000000012151460375044200217760ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_BCM57XX_DICT_IMAGE (fu_bcm57xx_dict_image_get_type()) G_DECLARE_FINAL_TYPE(FuBcm57xxDictImage, fu_bcm57xx_dict_image, FU, BCM57XX_DICT_IMAGE, FuFirmware) FuFirmware * fu_bcm57xx_dict_image_new(void); void fu_bcm57xx_dict_image_set_kind(FuBcm57xxDictImage *self, guint8 kind); guint8 fu_bcm57xx_dict_image_get_kind(FuBcm57xxDictImage *self); void fu_bcm57xx_dict_image_set_target(FuBcm57xxDictImage *self, guint8 target); guint8 fu_bcm57xx_dict_image_get_target(FuBcm57xxDictImage *self); fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-firmware.c000066400000000000000000000451731460375044200216150ustar00rootroot00000000000000/* * Copyright (C) 2018 Evan Lojewski * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-bcm57xx-common.h" #include "fu-bcm57xx-dict-image.h" #include "fu-bcm57xx-firmware.h" #include "fu-bcm57xx-stage1-image.h" #include "fu-bcm57xx-stage2-image.h" struct _FuBcm57xxFirmware { FuFirmware parent_instance; guint16 vendor; guint16 model; gboolean is_backup; guint32 phys_addr; gsize source_size; guint8 source_padchar; }; G_DEFINE_TYPE(FuBcm57xxFirmware, fu_bcm57xx_firmware, FU_TYPE_FIRMWARE) #define BCM_STAGE1_HEADER_MAGIC_BROADCOM 0x0E000E03 #define BCM_STAGE1_HEADER_MAGIC_MEKLORT 0x3C1D0800 #define BCM_APE_HEADER_MAGIC 0x1A4D4342 #define BCM_CODE_DIRECTORY_ADDR_APE 0x07 static void fu_bcm57xx_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuBcm57xxFirmware *self = FU_BCM57XX_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "vendor", self->vendor); fu_xmlb_builder_insert_kx(bn, "model", self->model); if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) { fu_xmlb_builder_insert_kb(bn, "is_backup", self->is_backup); fu_xmlb_builder_insert_kx(bn, "phys_addr", self->phys_addr); } } static gboolean fu_bcm57xx_firmware_parse_header(FuBcm57xxFirmware *self, GBytes *fw, GError **error) { gsize bufsz = 0x0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* verify magic and CRC */ if (!fu_bcm57xx_verify_magic(fw, 0x0, error)) return FALSE; if (!fu_bcm57xx_verify_crc(fw, error)) return FALSE; /* get address */ return fu_memread_uint32_safe(buf, bufsz, BCM_NVRAM_HEADER_PHYS_ADDR, &self->phys_addr, G_BIG_ENDIAN, error); } static FuFirmware * fu_bcm57xx_firmware_parse_info(FuBcm57xxFirmware *self, GBytes *fw, GError **error) { gsize bufsz = 0x0; guint32 mac_addr0 = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(FuFirmware) img = fu_firmware_new_from_bytes(fw); /* if the MAC is set non-zero this is an actual backup rather than a container */ if (!fu_memread_uint32_safe(buf, bufsz, BCM_NVRAM_INFO_MAC_ADDR0, &mac_addr0, G_BIG_ENDIAN, error)) return NULL; self->is_backup = mac_addr0 != 0x0 && mac_addr0 != 0xffffffff; /* read vendor + model */ if (!fu_memread_uint16_safe(buf, bufsz, BCM_NVRAM_INFO_VENDOR, &self->vendor, G_BIG_ENDIAN, error)) return NULL; if (!fu_memread_uint16_safe(buf, bufsz, BCM_NVRAM_INFO_DEVICE, &self->model, G_BIG_ENDIAN, error)) return NULL; /* success */ fu_firmware_set_id(img, "info"); return g_steal_pointer(&img); } static FuFirmware * fu_bcm57xx_firmware_parse_stage1(FuBcm57xxFirmware *self, GBytes *fw, guint32 *out_stage1_sz, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0x0; guint32 stage1_wrds = 0; guint32 stage1_sz; guint32 stage1_off = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(FuFirmware) img = fu_bcm57xx_stage1_image_new(); g_autoptr(GBytes) blob = NULL; if (!fu_memread_uint32_safe(buf, bufsz, BCM_NVRAM_HEADER_BASE + BCM_NVRAM_HEADER_SIZE_WRDS, &stage1_wrds, G_BIG_ENDIAN, error)) return NULL; if (!fu_memread_uint32_safe(buf, bufsz, BCM_NVRAM_HEADER_BASE + BCM_NVRAM_HEADER_OFFSET, &stage1_off, G_BIG_ENDIAN, error)) return NULL; stage1_sz = (stage1_wrds * sizeof(guint32)); if (stage1_off != BCM_NVRAM_STAGE1_BASE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "stage1 offset invalid, got: 0x%x, expected 0x%x", (guint)stage1_sz, (guint)BCM_NVRAM_STAGE1_BASE); return NULL; } if (stage1_off + stage1_sz > bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "bigger than firmware, got: 0x%x @ 0x%x", (guint)stage1_sz, (guint)stage1_off); return NULL; } /* verify CRC */ blob = fu_bytes_new_offset(fw, stage1_off, stage1_sz, error); if (blob == NULL) return NULL; if (!fu_firmware_parse(img, blob, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return NULL; /* needed for stage2 */ if (out_stage1_sz != NULL) *out_stage1_sz = stage1_sz; /* success */ fu_firmware_set_id(img, "stage1"); fu_firmware_set_offset(img, stage1_off); return g_steal_pointer(&img); } static FuFirmware * fu_bcm57xx_firmware_parse_stage2(FuBcm57xxFirmware *self, GBytes *fw, guint32 stage1_sz, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0x0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); guint32 stage2_off = 0; guint32 stage2_sz = 0; g_autoptr(FuFirmware) img = fu_bcm57xx_stage2_image_new(); g_autoptr(GBytes) blob = NULL; stage2_off = BCM_NVRAM_STAGE1_BASE + stage1_sz; if (!fu_bcm57xx_verify_magic(fw, stage2_off, error)) return NULL; if (!fu_memread_uint32_safe(buf, bufsz, stage2_off + sizeof(guint32), &stage2_sz, G_BIG_ENDIAN, error)) return NULL; if (stage2_off + stage2_sz > bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "bigger than firmware, got: 0x%x @ 0x%x", (guint)stage2_sz, (guint)stage2_off); return NULL; } /* verify CRC */ blob = fu_bytes_new_offset(fw, stage2_off + 0x8, stage2_sz, error); if (blob == NULL) return NULL; if (!fu_firmware_parse(img, blob, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return NULL; /* success */ fu_firmware_set_id(img, "stage2"); fu_firmware_set_offset(img, stage2_off); return g_steal_pointer(&img); } static gboolean fu_bcm57xx_firmware_parse_dict(FuBcm57xxFirmware *self, GBytes *fw, guint idx, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0x0; guint32 dict_addr = 0x0; guint32 dict_info = 0x0; guint32 dict_off = 0x0; guint32 dict_sz; guint32 base = BCM_NVRAM_DIRECTORY_BASE + (idx * BCM_NVRAM_DIRECTORY_SZ); const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(FuFirmware) img = fu_bcm57xx_dict_image_new(); g_autoptr(GBytes) blob = NULL; /* header */ if (!fu_memread_uint32_safe(buf, bufsz, base + BCM_NVRAM_DIRECTORY_ADDR, &dict_addr, G_BIG_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, base + BCM_NVRAM_DIRECTORY_SIZE_WRDS, &dict_info, G_BIG_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, base + BCM_NVRAM_DIRECTORY_OFFSET, &dict_off, G_BIG_ENDIAN, error)) return FALSE; /* no dict stored */ if (dict_addr == 0 && dict_info == 0 && dict_off == 0) return TRUE; dict_sz = (dict_info & 0x00FFFFFF) * sizeof(guint32); /* implies that maximum size is 16 MB */ fu_bcm57xx_dict_image_set_target(FU_BCM57XX_DICT_IMAGE(img), (dict_info & 0x0F000000) >> 24); fu_bcm57xx_dict_image_set_kind(FU_BCM57XX_DICT_IMAGE(img), (dict_info & 0xF0000000) >> 28); fu_firmware_set_addr(img, dict_addr); fu_firmware_set_offset(img, dict_off); fu_firmware_set_idx(img, 0x80 + idx); /* empty */ if (dict_sz == 0) { blob = g_bytes_new(NULL, 0); fu_firmware_set_bytes(img, blob); fu_firmware_add_image(FU_FIRMWARE(self), img); return TRUE; } /* check against image size */ if (dict_off + dict_sz > bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "bigger than firmware, got: 0x%x @ 0x%x", (guint)dict_sz, (guint)dict_off); return FALSE; } blob = fu_bytes_new_offset(fw, dict_off, dict_sz, error); if (blob == NULL) return FALSE; if (!fu_firmware_parse(img, blob, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return FALSE; /* success */ fu_firmware_add_image(FU_FIRMWARE(self), img); return TRUE; } static gboolean fu_bcm57xx_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { guint32 magic = 0; if (!fu_memread_uint32_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), 0x0, &magic, G_BIG_ENDIAN, error)) { g_prefix_error(error, "failed to read magic: "); return FALSE; } if (magic != BCM_APE_HEADER_MAGIC && magic != BCM_STAGE1_HEADER_MAGIC_BROADCOM && magic != BCM_STAGE1_HEADER_MAGIC_MEKLORT && magic != BCM_NVRAM_MAGIC) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "file not supported, got: 0x%08X", magic); return FALSE; } /* success */ return TRUE; } static gboolean fu_bcm57xx_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuBcm57xxFirmware *self = FU_BCM57XX_FIRMWARE(firmware); gsize bufsz = 0x0; guint32 magic = 0; guint32 stage1_sz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(FuFirmware) img_info2 = NULL; g_autoptr(FuFirmware) img_info = NULL; g_autoptr(FuFirmware) img_stage1 = NULL; g_autoptr(FuFirmware) img_stage2 = NULL; g_autoptr(FuFirmware) img_vpd = NULL; g_autoptr(GBytes) blob_header = NULL; g_autoptr(GBytes) blob_info2 = NULL; g_autoptr(GBytes) blob_info = NULL; g_autoptr(GBytes) blob_vpd = NULL; /* try to autodetect the file type */ if (!fu_memread_uint32_safe(buf, bufsz, 0x0, &magic, G_BIG_ENDIAN, error)) return FALSE; /* standalone APE */ if (magic == BCM_APE_HEADER_MAGIC) { g_autoptr(FuFirmware) img = fu_bcm57xx_dict_image_new(); fu_bcm57xx_dict_image_set_target(FU_BCM57XX_DICT_IMAGE(img), 0xD); fu_bcm57xx_dict_image_set_kind(FU_BCM57XX_DICT_IMAGE(img), 0x0); fu_firmware_set_addr(img, BCM_CODE_DIRECTORY_ADDR_APE); fu_firmware_set_id(img, "ape"); fu_firmware_add_image(firmware, img); return TRUE; } /* standalone stage1 */ if (magic == BCM_STAGE1_HEADER_MAGIC_BROADCOM || magic == BCM_STAGE1_HEADER_MAGIC_MEKLORT) { img_stage1 = fu_firmware_new_from_bytes(fw); fu_firmware_set_id(img_stage1, "stage1"); fu_firmware_add_image(firmware, img_stage1); return TRUE; } /* not full NVRAM image */ if (magic != BCM_NVRAM_MAGIC) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "file not supported, got: 0x%08X", magic); return FALSE; } /* save the size so we can export the padding for a perfect roundtrip */ self->source_size = bufsz; self->source_padchar = buf[bufsz - 1]; /* NVRAM header */ blob_header = fu_bytes_new_offset(fw, BCM_NVRAM_HEADER_BASE, BCM_NVRAM_HEADER_SZ, error); if (blob_header == NULL) return FALSE; if (!fu_bcm57xx_firmware_parse_header(self, blob_header, error)) { g_prefix_error(error, "failed to parse header: "); return FALSE; } /* info */ blob_info = fu_bytes_new_offset(fw, BCM_NVRAM_INFO_BASE, BCM_NVRAM_INFO_SZ, error); if (blob_info == NULL) return FALSE; img_info = fu_bcm57xx_firmware_parse_info(self, blob_info, error); if (img_info == NULL) { g_prefix_error(error, "failed to parse info: "); return FALSE; } fu_firmware_set_offset(img_info, BCM_NVRAM_INFO_BASE); fu_firmware_add_image(firmware, img_info); /* VPD */ blob_vpd = fu_bytes_new_offset(fw, BCM_NVRAM_VPD_BASE, BCM_NVRAM_VPD_SZ, error); if (blob_vpd == NULL) return FALSE; img_vpd = fu_firmware_new_from_bytes(blob_vpd); fu_firmware_set_id(img_vpd, "vpd"); fu_firmware_set_offset(img_vpd, BCM_NVRAM_VPD_BASE); fu_firmware_add_image(firmware, img_vpd); /* info2 */ blob_info2 = fu_bytes_new_offset(fw, BCM_NVRAM_INFO2_BASE, BCM_NVRAM_INFO2_SZ, error); if (blob_info2 == NULL) return FALSE; img_info2 = fu_firmware_new_from_bytes(blob_info2); fu_firmware_set_id(img_info2, "info2"); fu_firmware_set_offset(img_info2, BCM_NVRAM_INFO2_BASE); fu_firmware_add_image(firmware, img_info2); /* stage1 */ img_stage1 = fu_bcm57xx_firmware_parse_stage1(self, fw, &stage1_sz, flags, error); if (img_stage1 == NULL) { g_prefix_error(error, "failed to parse stage1: "); return FALSE; } fu_firmware_add_image(firmware, img_stage1); /* stage2 */ img_stage2 = fu_bcm57xx_firmware_parse_stage2(self, fw, stage1_sz, flags, error); if (img_stage2 == NULL) { g_prefix_error(error, "failed to parse stage2: "); return FALSE; } fu_firmware_add_image(firmware, img_stage2); /* dictionaries, e.g. APE */ for (guint i = 0; i < 8; i++) { if (!fu_bcm57xx_firmware_parse_dict(self, fw, i, flags, error)) { g_prefix_error(error, "failed to parse dict 0x%x: ", i); return FALSE; } } /* success */ return TRUE; } static GBytes * _g_bytes_new_sized(gsize sz) { g_autoptr(GByteArray) tmp = g_byte_array_sized_new(sz); for (gsize i = 0; i < sz; i++) fu_byte_array_append_uint8(tmp, 0x0); return g_bytes_new(tmp->data, tmp->len); } static gboolean fu_bcm57xx_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuBcm57xxFirmware *self = FU_BCM57XX_FIRMWARE(firmware); guint64 tmp; /* two simple properties */ tmp = xb_node_query_text_as_uint(n, "vendor", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) self->vendor = tmp; tmp = xb_node_query_text_as_uint(n, "model", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) self->model = tmp; /* success */ return TRUE; } static GByteArray * fu_bcm57xx_firmware_write(FuFirmware *firmware, GError **error) { gsize off = BCM_NVRAM_STAGE1_BASE; FuBcm57xxFirmware *self = FU_BCM57XX_FIRMWARE(firmware); g_autoptr(GByteArray) buf = g_byte_array_sized_new(self->source_size); g_autoptr(FuFirmware) img_info2 = NULL; g_autoptr(FuFirmware) img_info = NULL; g_autoptr(FuFirmware) img_stage1 = NULL; g_autoptr(FuFirmware) img_stage2 = NULL; g_autoptr(FuFirmware) img_vpd = NULL; g_autoptr(GBytes) blob_info2 = NULL; g_autoptr(GBytes) blob_info = NULL; g_autoptr(GBytes) blob_stage1 = NULL; g_autoptr(GBytes) blob_stage2 = NULL; g_autoptr(GBytes) blob_vpd = NULL; g_autoptr(GPtrArray) blob_dicts = NULL; /* write out the things we need to pre-compute */ img_stage1 = fu_firmware_get_image_by_id(firmware, "stage1", error); if (img_stage1 == NULL) return NULL; blob_stage1 = fu_firmware_write(img_stage1, error); if (blob_stage1 == NULL) return NULL; off += g_bytes_get_size(blob_stage1); img_stage2 = fu_firmware_get_image_by_id(firmware, "stage2", error); if (img_stage2 == NULL) return NULL; blob_stage2 = fu_firmware_write(img_stage2, error); if (blob_stage2 == NULL) return NULL; off += g_bytes_get_size(blob_stage2); /* add header */ fu_byte_array_append_uint32(buf, BCM_NVRAM_MAGIC, G_BIG_ENDIAN); fu_byte_array_append_uint32(buf, self->phys_addr, G_BIG_ENDIAN); fu_byte_array_append_uint32(buf, g_bytes_get_size(blob_stage1) / sizeof(guint32), G_BIG_ENDIAN); fu_byte_array_append_uint32(buf, BCM_NVRAM_STAGE1_BASE, G_BIG_ENDIAN); fu_byte_array_append_uint32(buf, fu_bcm57xx_nvram_crc(buf->data, buf->len), G_LITTLE_ENDIAN); /* add directory entries */ blob_dicts = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); for (guint i = 0; i < 8; i++) { g_autoptr(FuFirmware) img = NULL; g_autoptr(GBytes) blob = NULL; img = fu_firmware_get_image_by_idx(firmware, 0x80 + i, NULL); if (img != NULL) { blob = fu_firmware_write(img, error); if (blob == NULL) return NULL; } if (blob != NULL) { fu_byte_array_append_uint32(buf, fu_firmware_get_addr(img), G_BIG_ENDIAN); fu_byte_array_append_uint32( buf, (g_bytes_get_size(blob) / sizeof(guint32)) | (guint32)fu_bcm57xx_dict_image_get_target( FU_BCM57XX_DICT_IMAGE(img)) << 24 | (guint32)fu_bcm57xx_dict_image_get_kind(FU_BCM57XX_DICT_IMAGE(img)) << 28, G_BIG_ENDIAN); if (g_bytes_get_size(blob) > 0) { fu_byte_array_append_uint32(buf, off, G_BIG_ENDIAN); off += g_bytes_get_size(blob); } else { fu_byte_array_append_uint32(buf, 0x0, G_BIG_ENDIAN); } } else { blob = g_bytes_new(NULL, 0); for (guint32 j = 0; j < sizeof(guint32) * 3; j++) fu_byte_array_append_uint8(buf, 0x0); } g_ptr_array_add(blob_dicts, g_steal_pointer(&blob)); } /* add info */ img_info = fu_firmware_get_image_by_id(firmware, "info", NULL); if (img_info != NULL) { blob_info = fu_firmware_write(img_info, error); if (blob_info == NULL) return NULL; } else { g_autoptr(GByteArray) tmp = g_byte_array_sized_new(BCM_NVRAM_INFO_SZ); for (gsize i = 0; i < BCM_NVRAM_INFO_SZ; i++) fu_byte_array_append_uint8(tmp, 0x0); fu_memwrite_uint16(tmp->data + BCM_NVRAM_INFO_VENDOR, self->vendor, G_BIG_ENDIAN); fu_memwrite_uint16(tmp->data + BCM_NVRAM_INFO_DEVICE, self->model, G_BIG_ENDIAN); blob_info = g_bytes_new(tmp->data, tmp->len); } fu_byte_array_append_bytes(buf, blob_info); /* add vpd */ img_vpd = fu_firmware_get_image_by_id(firmware, "vpd", NULL); if (img_vpd != NULL) { blob_vpd = fu_firmware_write(img_vpd, error); if (blob_vpd == NULL) return NULL; } else { blob_vpd = _g_bytes_new_sized(BCM_NVRAM_VPD_SZ); } fu_byte_array_append_bytes(buf, blob_vpd); /* add info2 */ img_info2 = fu_firmware_get_image_by_id(firmware, "info2", NULL); if (img_info2 != NULL) { blob_info2 = fu_firmware_write(img_info2, error); if (blob_info2 == NULL) return NULL; } else { blob_info2 = _g_bytes_new_sized(BCM_NVRAM_INFO2_SZ); } fu_byte_array_append_bytes(buf, blob_info2); /* add stage1+2 */ fu_byte_array_append_bytes(buf, blob_stage1); fu_byte_array_append_bytes(buf, blob_stage2); /* add dictionaries, e.g. APE */ for (guint i = 0; i < blob_dicts->len; i++) { GBytes *blob = g_ptr_array_index(blob_dicts, i); fu_byte_array_append_bytes(buf, blob); } /* pad until full */ for (guint32 i = buf->len; i < self->source_size; i++) fu_byte_array_append_uint8(buf, self->source_padchar); /* add EOF */ return g_steal_pointer(&buf); } guint16 fu_bcm57xx_firmware_get_vendor(FuBcm57xxFirmware *self) { return self->vendor; } guint16 fu_bcm57xx_firmware_get_model(FuBcm57xxFirmware *self) { return self->model; } gboolean fu_bcm57xx_firmware_is_backup(FuBcm57xxFirmware *self) { return self->is_backup; } static void fu_bcm57xx_firmware_init(FuBcm57xxFirmware *self) { self->phys_addr = BCM_PHYS_ADDR_DEFAULT; self->source_size = BCM_FIRMWARE_SIZE; self->source_padchar = 0xff; fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_DEDUPE_ID); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); } static void fu_bcm57xx_firmware_class_init(FuBcm57xxFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_bcm57xx_firmware_check_magic; klass_firmware->parse = fu_bcm57xx_firmware_parse; klass_firmware->export = fu_bcm57xx_firmware_export; klass_firmware->write = fu_bcm57xx_firmware_write; klass_firmware->build = fu_bcm57xx_firmware_build; } FuFirmware * fu_bcm57xx_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_BCM57XX_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-firmware.h000066400000000000000000000010441460375044200216070ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_BCM57XX_FIRMWARE (fu_bcm57xx_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuBcm57xxFirmware, fu_bcm57xx_firmware, FU, BCM57XX_FIRMWARE, FuFirmware) FuFirmware * fu_bcm57xx_firmware_new(void); guint16 fu_bcm57xx_firmware_get_vendor(FuBcm57xxFirmware *self); guint16 fu_bcm57xx_firmware_get_model(FuBcm57xxFirmware *self); gboolean fu_bcm57xx_firmware_is_backup(FuBcm57xxFirmware *self); fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-plugin.c000066400000000000000000000030061460375044200212640ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-bcm57xx-device.h" #include "fu-bcm57xx-dict-image.h" #include "fu-bcm57xx-firmware.h" #include "fu-bcm57xx-plugin.h" #include "fu-bcm57xx-stage1-image.h" #include "fu-bcm57xx-stage2-image.h" struct _FuBcm57XxPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuBcm57XxPlugin, fu_bcm57xx_plugin, FU_TYPE_PLUGIN) static void fu_bcm57xx_plugin_init(FuBcm57XxPlugin *self) { } static void fu_bcm57xx_plugin_object_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_set_name(plugin, "bcm57xx"); } static void fu_bcm57xx_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "pci"); fu_plugin_add_device_gtype(plugin, FU_TYPE_BCM57XX_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_BCM57XX_FIRMWARE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_BCM57XX_DICT_IMAGE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_BCM57XX_STAGE1_IMAGE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_BCM57XX_STAGE2_IMAGE); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_BETTER_THAN, "optionrom"); } static void fu_bcm57xx_plugin_class_init(FuBcm57XxPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->constructed = fu_bcm57xx_plugin_object_constructed; plugin_class->constructed = fu_bcm57xx_plugin_constructed; } fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-plugin.h000066400000000000000000000003531460375044200212730ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuBcm57XxPlugin, fu_bcm57xx_plugin, FU, BCM57XX_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-recovery-device.c000066400000000000000000000603701460375044200230700ustar00rootroot00000000000000/* * Copyright (C) 2018 Evan Lojewski * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #ifdef HAVE_MMAN_H #include #endif #ifdef HAVE_VALGRIND #include #endif /* HAVE_VALGRIND */ #include "fu-bcm57xx-common.h" #include "fu-bcm57xx-firmware.h" #include "fu-bcm57xx-recovery-device.h" /* offsets into BAR[0] */ #define REG_DEVICE_PCI_VENDOR_DEVICE_ID 0x6434 #define REG_NVM_SOFTWARE_ARBITRATION 0x7020 #define REG_NVM_ACCESS 0x7024 #define REG_NVM_COMMAND 0x7000 #define REG_NVM_ADDR 0x700c #define REG_NVM_READ 0x7010 #define REG_NVM_WRITE 0x7008 /* offsets into BAR[2] */ #define REG_APE_MODE 0x0 typedef struct { guint8 *buf; gsize bufsz; } FuBcm57xxMmap; #define FU_BCM57XX_BAR_DEVICE 0 #define FU_BCM57XX_BAR_APE 1 #define FU_BCM57XX_BAR_MAX 3 struct _FuBcm57xxRecoveryDevice { FuUdevDevice parent_instance; FuBcm57xxMmap bar[FU_BCM57XX_BAR_MAX]; }; typedef union { guint32 r32; struct { guint32 reserved_0_0 : 1; guint32 Reset : 1; guint32 reserved_2_2 : 1; guint32 Done : 1; guint32 Doit : 1; guint32 Wr : 1; guint32 Erase : 1; guint32 First : 1; guint32 Last : 1; guint32 reserved_15_9 : 7; guint32 WriteEnableCommand : 1; guint32 WriteDisableCommand : 1; guint32 reserved_31_18 : 14; } __attribute__((packed)) bits; } BcmRegNVMCommand; typedef union { guint32 r32; struct { guint32 ReqSet0 : 1; guint32 ReqSet1 : 1; guint32 ReqSet2 : 1; guint32 ReqSet3 : 1; guint32 ReqClr0 : 1; guint32 ReqClr1 : 1; guint32 ReqClr2 : 1; guint32 ReqClr3 : 1; guint32 ArbWon0 : 1; guint32 ArbWon1 : 1; guint32 ArbWon2 : 1; guint32 ArbWon3 : 1; guint32 Req0 : 1; guint32 Req1 : 1; guint32 Req2 : 1; guint32 Req3 : 1; guint32 reserved_31_16 : 16; } __attribute__((packed)) bits; } BcmRegNVMSoftwareArbitration; typedef union { guint32 r32; struct { guint32 Enable : 1; guint32 WriteEnable : 1; guint32 reserved_31_2 : 30; } __attribute__((packed)) bits; } BcmRegNVMAccess; typedef union { guint32 r32; struct { guint32 Reset : 1; guint32 Halt : 1; guint32 FastBoot : 1; guint32 HostDiag : 1; guint32 reserved_4_4 : 1; guint32 Event1 : 1; guint32 Event2 : 1; guint32 GRCint : 1; guint32 reserved_8_8 : 1; guint32 SwapATBdword : 1; guint32 reserved_10_10 : 1; guint32 SwapARBdword : 1; guint32 reserved_13_12 : 2; guint32 Channel0Enable : 1; guint32 Channel2Enable : 1; guint32 reserved_17_16 : 2; guint32 MemoryECC : 1; guint32 ICodePIPRdDisable : 1; guint32 reserved_29_20 : 10; guint32 Channel1Enable : 1; guint32 Channel3Enable : 1; } __attribute__((packed)) bits; } BcmRegAPEMode; G_DEFINE_TYPE(FuBcm57xxRecoveryDevice, fu_bcm57xx_recovery_device, FU_TYPE_UDEV_DEVICE) #ifdef __ppc64__ #define BARRIER() __asm__ volatile("sync 0\neieio\n" : : : "memory") #else #define BARRIER() __asm__ volatile("" : : : "memory"); #endif static gboolean fu_bcm57xx_recovery_device_bar_read(FuBcm57xxRecoveryDevice *self, guint bar, gsize offset, guint32 *val, GError **error) { /* this should never happen */ if (self->bar[bar].buf == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "BAR[%u] is not mapped!", bar); return FALSE; } BARRIER(); return fu_memcpy_safe((guint8 *)val, sizeof(*val), 0x0, /* dst */ self->bar[bar].buf, self->bar[bar].bufsz, offset, sizeof(*val), error); } static gboolean fu_bcm57xx_recovery_device_bar_write(FuBcm57xxRecoveryDevice *self, guint bar, gsize offset, guint32 val, GError **error) { /* this should never happen */ if (self->bar[bar].buf == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "BAR[%u] is not mapped!", bar); return FALSE; } BARRIER(); if (!fu_memcpy_safe(self->bar[bar].buf, self->bar[bar].bufsz, offset, /* dst */ (const guint8 *)&val, sizeof(val), 0x0, /* src */ sizeof(val), error)) return FALSE; BARRIER(); return TRUE; } static gboolean fu_bcm57xx_recovery_device_nvram_disable(FuBcm57xxRecoveryDevice *self, GError **error) { BcmRegNVMAccess tmp; if (!fu_bcm57xx_recovery_device_bar_read(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_ACCESS, &tmp.r32, error)) return FALSE; tmp.bits.Enable = FALSE; tmp.bits.WriteEnable = FALSE; return fu_bcm57xx_recovery_device_bar_write(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_ACCESS, tmp.r32, error); } static gboolean fu_bcm57xx_recovery_device_nvram_enable(FuBcm57xxRecoveryDevice *self, GError **error) { BcmRegNVMAccess tmp; if (!fu_bcm57xx_recovery_device_bar_read(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_ACCESS, &tmp.r32, error)) return FALSE; tmp.bits.Enable = TRUE; tmp.bits.WriteEnable = FALSE; return fu_bcm57xx_recovery_device_bar_write(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_ACCESS, tmp.r32, error); } static gboolean fu_bcm57xx_recovery_device_nvram_enable_write(FuBcm57xxRecoveryDevice *self, GError **error) { BcmRegNVMAccess tmp; if (!fu_bcm57xx_recovery_device_bar_read(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_ACCESS, &tmp.r32, error)) return FALSE; tmp.bits.Enable = TRUE; tmp.bits.WriteEnable = TRUE; return fu_bcm57xx_recovery_device_bar_write(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_ACCESS, tmp.r32, error); } static gboolean fu_bcm57xx_recovery_device_nvram_acquire_lock(FuBcm57xxRecoveryDevice *self, GError **error) { BcmRegNVMSoftwareArbitration tmp = {0}; g_autoptr(GTimer) timer = g_timer_new(); tmp.bits.ReqSet1 = 1; if (!fu_bcm57xx_recovery_device_bar_write(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_SOFTWARE_ARBITRATION, tmp.r32, error)) return FALSE; do { if (!fu_bcm57xx_recovery_device_bar_read(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_SOFTWARE_ARBITRATION, &tmp.r32, error)) return FALSE; if (tmp.bits.ArbWon1) return TRUE; if (g_timer_elapsed(timer, NULL) > 0.2) break; } while (TRUE); /* timed out */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "timed out trying to acquire lock #1"); return FALSE; } static gboolean fu_bcm57xx_recovery_device_nvram_release_lock(FuBcm57xxRecoveryDevice *self, GError **error) { BcmRegNVMSoftwareArbitration tmp = {0}; tmp.r32 = 0; tmp.bits.ReqClr1 = 1; return fu_bcm57xx_recovery_device_bar_write(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_SOFTWARE_ARBITRATION, tmp.r32, error); } static gboolean fu_bcm57xx_recovery_device_nvram_wait_done(FuBcm57xxRecoveryDevice *self, GError **error) { BcmRegNVMCommand tmp = {0}; g_autoptr(GTimer) timer = g_timer_new(); do { if (!fu_bcm57xx_recovery_device_bar_read(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_COMMAND, &tmp.r32, error)) return FALSE; if (tmp.bits.Done) return TRUE; if (g_timer_elapsed(timer, NULL) > 0.2) break; } while (TRUE); /* timed out */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "timed out"); return FALSE; } static gboolean fu_bcm57xx_recovery_device_nvram_clear_done(FuBcm57xxRecoveryDevice *self, GError **error) { BcmRegNVMCommand tmp = {0}; tmp.bits.Done = 1; return fu_bcm57xx_recovery_device_bar_write(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_COMMAND, tmp.r32, error); } static gboolean fu_bcm57xx_recovery_device_nvram_read(FuBcm57xxRecoveryDevice *self, guint32 address, guint32 *buf, gsize bufsz, FuProgress *progress, GError **error) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, bufsz); for (guint i = 0; i < bufsz; i++) { BcmRegNVMCommand tmp = {0}; guint32 val32 = 0; if (!fu_bcm57xx_recovery_device_nvram_clear_done(self, error)) return FALSE; if (!fu_bcm57xx_recovery_device_bar_write(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_ADDR, address, error)) return FALSE; tmp.bits.Doit = 1; tmp.bits.First = i == 0; tmp.bits.Last = i == bufsz - 1; if (!fu_bcm57xx_recovery_device_bar_write(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_COMMAND, tmp.r32, error)) return FALSE; if (!fu_bcm57xx_recovery_device_nvram_wait_done(self, error)) { g_prefix_error(error, "failed to read @0x%x: ", address); return FALSE; } if (!fu_bcm57xx_recovery_device_bar_read(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_READ, &val32, error)) return FALSE; buf[i] = GUINT32_FROM_BE(val32); address += sizeof(guint32); fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_bcm57xx_recovery_device_nvram_write(FuBcm57xxRecoveryDevice *self, guint32 address, const guint32 *buf, gsize bufsz_dwrds, FuProgress *progress, GError **error) { const guint32 page_size_dwrds = 64; /* can only write in pages of 64 dwords */ if (bufsz_dwrds % page_size_dwrds != 0 || (address * sizeof(guint32)) % page_size_dwrds != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "can only write aligned with page size 0x%x", page_size_dwrds); return FALSE; } fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, bufsz_dwrds); for (guint i = 0; i < bufsz_dwrds; i++) { BcmRegNVMCommand tmp = {0}; if (!fu_bcm57xx_recovery_device_nvram_clear_done(self, error)) return FALSE; if (!fu_bcm57xx_recovery_device_bar_write(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_WRITE, GUINT32_TO_BE(buf[i]), error)) return FALSE; if (!fu_bcm57xx_recovery_device_bar_write(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_ADDR, address, error)) return FALSE; tmp.bits.Wr = TRUE; tmp.bits.Doit = TRUE; tmp.bits.First = i % page_size_dwrds == 0; tmp.bits.Last = (i + 1) % page_size_dwrds == 0; if (!fu_bcm57xx_recovery_device_bar_write(self, FU_BCM57XX_BAR_DEVICE, REG_NVM_COMMAND, tmp.r32, error)) return FALSE; if (!fu_bcm57xx_recovery_device_nvram_wait_done(self, error)) { g_prefix_error(error, "failed to write @0x%x: ", address); return FALSE; } address += sizeof(guint32); fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_bcm57xx_recovery_device_detach(FuDevice *device, FuProgress *progress, GError **error) { /* unbind tg3 */ return fu_device_unbind_driver(device, error); } static gboolean fu_bcm57xx_recovery_device_attach(FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(GError) error_local = NULL; /* bind tg3, which might fail if the module is not compiled */ if (!fu_device_bind_driver(device, "pci", "tg3", &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_warning("failed to bind tg3: %s", error_local->message); } else { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to bind tg3: "); return FALSE; } } /* success */ return TRUE; } static gboolean fu_bcm57xx_recovery_device_activate(FuDevice *device, FuProgress *progress, GError **error) { BcmRegAPEMode mode = {0}; FuBcm57xxRecoveryDevice *self = FU_BCM57XX_RECOVERY_DEVICE(device); /* halt */ mode.bits.Halt = 1; mode.bits.FastBoot = 0; if (!fu_bcm57xx_recovery_device_bar_write(self, FU_BCM57XX_BAR_APE, REG_APE_MODE, mode.r32, error)) return FALSE; /* boot */ mode.bits.Halt = 0; mode.bits.FastBoot = 0; mode.bits.Reset = 1; return fu_bcm57xx_recovery_device_bar_write(self, FU_BCM57XX_BAR_APE, REG_APE_MODE, mode.r32, error); } static GBytes * fu_bcm57xx_recovery_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuBcm57xxRecoveryDevice *self = FU_BCM57XX_RECOVERY_DEVICE(device); gsize bufsz_dwrds = fu_device_get_firmware_size_max(FU_DEVICE(self)) / sizeof(guint32); g_autofree guint32 *buf_dwrds = g_new0(guint32, bufsz_dwrds); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuDeviceLocker) locker2 = NULL; /* read from hardware */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); locker = fu_device_locker_new_full( self, (FuDeviceLockerFunc)fu_bcm57xx_recovery_device_nvram_acquire_lock, (FuDeviceLockerFunc)fu_bcm57xx_recovery_device_nvram_release_lock, error); if (locker == NULL) return NULL; locker2 = fu_device_locker_new_full(self, (FuDeviceLockerFunc)fu_bcm57xx_recovery_device_nvram_enable, (FuDeviceLockerFunc)fu_bcm57xx_recovery_device_nvram_disable, error); if (locker2 == NULL) return NULL; if (!fu_bcm57xx_recovery_device_nvram_read(self, 0x0, buf_dwrds, bufsz_dwrds, progress, error)) return NULL; if (!fu_device_locker_close(locker2, error)) return NULL; return g_bytes_new(buf_dwrds, bufsz_dwrds * sizeof(guint32)); } static FuFirmware * fu_bcm57xx_recovery_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware_bin = fu_firmware_new(); g_autoptr(FuFirmware) firmware_tmp = fu_bcm57xx_firmware_new(); /* check is a NVRAM backup */ if (!fu_firmware_parse(firmware_tmp, fw, flags, error)) { g_prefix_error(error, "failed to parse new firmware: "); return NULL; } if (!fu_bcm57xx_firmware_is_backup(FU_BCM57XX_FIRMWARE(firmware_tmp))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "can only recover with backup firmware"); return NULL; } if (!fu_firmware_parse(firmware_bin, fw, flags, error)) return NULL; return g_steal_pointer(&firmware_bin); } static gboolean fu_bcm57xx_recovery_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuBcm57xxRecoveryDevice *self = FU_BCM57XX_RECOVERY_DEVICE(device); const guint8 *buf; gsize bufsz = 0; gsize bufsz_dwrds; g_autofree guint32 *buf_dwrds = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuDeviceLocker) locker2 = NULL; g_autoptr(GBytes) blob = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DECOMPRESSING, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 95, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, NULL); /* build the images into one linear blob of the correct size */ blob = fu_firmware_write(firmware, error); if (blob == NULL) return FALSE; fu_progress_step_done(progress); /* align into uint32_t buffer */ buf = g_bytes_get_data(blob, &bufsz); bufsz_dwrds = bufsz / sizeof(guint32); buf_dwrds = g_new0(guint32, bufsz_dwrds); if (!fu_memcpy_safe((guint8 *)buf_dwrds, bufsz_dwrds * sizeof(guint32), 0x0, /* dst */ buf, bufsz, 0x0, /* src */ bufsz, error)) return FALSE; /* hit hardware */ locker = fu_device_locker_new_full( self, (FuDeviceLockerFunc)fu_bcm57xx_recovery_device_nvram_acquire_lock, (FuDeviceLockerFunc)fu_bcm57xx_recovery_device_nvram_release_lock, error); if (locker == NULL) return FALSE; locker2 = fu_device_locker_new_full( self, (FuDeviceLockerFunc)fu_bcm57xx_recovery_device_nvram_enable_write, (FuDeviceLockerFunc)fu_bcm57xx_recovery_device_nvram_disable, error); if (locker2 == NULL) return FALSE; if (!fu_bcm57xx_recovery_device_nvram_write(self, 0x0, buf_dwrds, bufsz_dwrds, fu_progress_get_child(progress), error)) return FALSE; if (!fu_device_locker_close(locker2, error)) return FALSE; if (!fu_device_locker_close(locker, error)) return FALSE; fu_progress_step_done(progress); /* reset APE */ if (!fu_device_activate(device, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_bcm57xx_recovery_device_setup(FuDevice *device, GError **error) { FuBcm57xxRecoveryDevice *self = FU_BCM57XX_RECOVERY_DEVICE(device); guint32 fwversion = 0; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuDeviceLocker) locker2 = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, "enable"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 80, "nvram"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, "veraddr"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, "version"); locker = fu_device_locker_new_full( self, (FuDeviceLockerFunc)fu_bcm57xx_recovery_device_nvram_acquire_lock, (FuDeviceLockerFunc)fu_bcm57xx_recovery_device_nvram_release_lock, error); if (locker == NULL) return FALSE; locker2 = fu_device_locker_new_full(self, (FuDeviceLockerFunc)fu_bcm57xx_recovery_device_nvram_enable, (FuDeviceLockerFunc)fu_bcm57xx_recovery_device_nvram_disable, error); if (locker2 == NULL) return FALSE; fu_progress_step_done(progress); /* get NVRAM version */ if (!fu_bcm57xx_recovery_device_nvram_read(self, BCM_NVRAM_STAGE1_BASE + BCM_NVRAM_STAGE1_VERSION, &fwversion, 1, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); if (fwversion != 0x0) { /* this is only set on the OSS firmware */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version_u32(device, GUINT32_FROM_BE(fwversion)); fu_device_set_branch(device, BCM_FW_BRANCH_OSS_FIRMWARE); fu_progress_step_done(progress); fu_progress_step_done(progress); } else { guint32 bufver[4] = {0x0}; guint32 veraddr = 0; g_autoptr(Bcm57xxVeritem) veritem = NULL; /* fall back to the string, e.g. '5719-v1.43' */ if (!fu_bcm57xx_recovery_device_nvram_read(self, BCM_NVRAM_STAGE1_BASE + BCM_NVRAM_STAGE1_VERADDR, &veraddr, 1, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); veraddr = GUINT32_FROM_BE(veraddr); if (veraddr > BCM_PHYS_ADDR_DEFAULT) veraddr -= BCM_PHYS_ADDR_DEFAULT; if (!fu_bcm57xx_recovery_device_nvram_read(self, BCM_NVRAM_STAGE1_BASE + veraddr, bufver, 4, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); veritem = fu_bcm57xx_veritem_new((guint8 *)bufver, sizeof(bufver)); if (veritem != NULL) { fu_device_set_version(device, veritem->version); fu_device_set_branch(device, veritem->branch); fu_device_set_version_format(device, veritem->verfmt); } } return TRUE; } static gboolean fu_bcm57xx_recovery_device_open(FuDevice *device, GError **error) { #ifdef HAVE_MMAN_H FuBcm57xxRecoveryDevice *self = FU_BCM57XX_RECOVERY_DEVICE(device); FuUdevDevice *udev_device = FU_UDEV_DEVICE(device); const gchar *sysfs_path = fu_udev_device_get_sysfs_path(udev_device); #endif #ifdef RUNNING_ON_VALGRIND /* this can't work */ if (RUNNING_ON_VALGRIND) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot mmap'ing BARs when using valgrind"); return FALSE; } #endif #ifdef HAVE_MMAN_H /* map BARs */ for (guint i = 0; i < FU_BCM57XX_BAR_MAX; i++) { int memfd; struct stat st; g_autofree gchar *fn = NULL; g_autofree gchar *resfn = NULL; /* open 64 bit resource */ resfn = g_strdup_printf("resource%u", i * 2); fn = g_build_filename(sysfs_path, resfn, NULL); memfd = open(fn, O_RDWR | O_SYNC); if (memfd < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "error opening %s", fn); return FALSE; } if (fstat(memfd, &st) < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "could not stat %s", fn); close(memfd); return FALSE; } /* mmap */ g_debug("mapping BAR[%u] %s for 0x%x bytes", i, fn, (guint)st.st_size); self->bar[i].buf = (guint8 *)mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, memfd, 0); self->bar[i].bufsz = st.st_size; close(memfd); if (self->bar[i].buf == MAP_FAILED) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "could not mmap %s: %s", fn, g_strerror(errno)); return FALSE; } } /* success */ return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "mmap() not supported as sys/mman.h not available"); return FALSE; #endif } static gboolean fu_bcm57xx_recovery_device_close(FuDevice *device, GError **error) { #ifdef HAVE_MMAN_H FuBcm57xxRecoveryDevice *self = FU_BCM57XX_RECOVERY_DEVICE(device); /* unmap BARs */ for (guint i = 0; i < FU_BCM57XX_BAR_MAX; i++) { if (self->bar[i].buf == NULL) continue; g_debug("unmapping BAR[%u]", i); munmap(self->bar[i].buf, self->bar[i].bufsz); self->bar[i].buf = NULL; self->bar[i].bufsz = 0; } /* success */ return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "munmap() not supported as sys/mman.h not available"); return FALSE; #endif } static void fu_bcm57xx_recovery_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_bcm57xx_recovery_device_init(FuBcm57xxRecoveryDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_BACKUP_BEFORE_INSTALL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IGNORE_VALIDATION); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_protocol(FU_DEVICE(self), "com.broadcom.bcm57xx"); fu_device_add_icon(FU_DEVICE(self), "network-wired"); fu_device_set_logical_id(FU_DEVICE(self), "recovery"); /* other values are set from a quirk */ fu_device_set_firmware_size(FU_DEVICE(self), BCM_FIRMWARE_SIZE); /* no BARs mapped */ for (guint i = 0; i < FU_BCM57XX_BAR_MAX; i++) { self->bar[i].buf = NULL; self->bar[i].bufsz = 0; } } static gboolean fu_bcm57xx_recovery_device_probe(FuDevice *device, GError **error) { return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "pci", error); } static void fu_bcm57xx_recovery_device_class_init(FuBcm57xxRecoveryDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->activate = fu_bcm57xx_recovery_device_activate; klass_device->prepare_firmware = fu_bcm57xx_recovery_device_prepare_firmware; klass_device->setup = fu_bcm57xx_recovery_device_setup; klass_device->reload = fu_bcm57xx_recovery_device_setup; klass_device->open = fu_bcm57xx_recovery_device_open; klass_device->close = fu_bcm57xx_recovery_device_close; klass_device->write_firmware = fu_bcm57xx_recovery_device_write_firmware; klass_device->dump_firmware = fu_bcm57xx_recovery_device_dump_firmware; klass_device->attach = fu_bcm57xx_recovery_device_attach; klass_device->detach = fu_bcm57xx_recovery_device_detach; klass_device->probe = fu_bcm57xx_recovery_device_probe; klass_device->set_progress = fu_bcm57xx_recovery_device_set_progress; } FuBcm57xxRecoveryDevice * fu_bcm57xx_recovery_device_new(void) { FuUdevDevice *self = g_object_new(FU_TYPE_BCM57XX_RECOVERY_DEVICE, NULL); return FU_BCM57XX_RECOVERY_DEVICE(self); } fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-recovery-device.h000066400000000000000000000006661460375044200230770ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_BCM57XX_RECOVERY_DEVICE (fu_bcm57xx_recovery_device_get_type()) G_DECLARE_FINAL_TYPE(FuBcm57xxRecoveryDevice, fu_bcm57xx_recovery_device, FU, BCM57XX_RECOVERY_DEVICE, FuUdevDevice) FuBcm57xxRecoveryDevice * fu_bcm57xx_recovery_device_new(void); fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-stage1-image.c000066400000000000000000000102571460375044200222400ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-bcm57xx-common.h" #include "fu-bcm57xx-stage1-image.h" struct _FuBcm57xxStage1Image { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuBcm57xxStage1Image, fu_bcm57xx_stage1_image, FU_TYPE_FIRMWARE) static gboolean fu_bcm57xx_stage1_image_parse(FuFirmware *image, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0x0; guint32 fwversion = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GBytes) fw_nocrc = NULL; /* verify CRC */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { if (!fu_bcm57xx_verify_crc(fw, error)) return FALSE; } /* get version number */ if (!fu_memread_uint32_safe(buf, bufsz, BCM_NVRAM_STAGE1_VERSION, &fwversion, G_BIG_ENDIAN, error)) return FALSE; if (fwversion != 0x0) { g_autofree gchar *tmp = NULL; tmp = fu_version_from_uint32(fwversion, FWUPD_VERSION_FORMAT_TRIPLET); fu_firmware_set_version(image, tmp); fu_firmware_set_version_raw(image, fwversion); } else { guint32 veraddr = 0x0; /* fall back to the optional string, e.g. '5719-v1.43' */ if (!fu_memread_uint32_safe(buf, bufsz, BCM_NVRAM_STAGE1_VERADDR, &veraddr, G_BIG_ENDIAN, error)) return FALSE; if (veraddr != 0x0) { guint32 bufver[4] = {'\0'}; g_autoptr(Bcm57xxVeritem) veritem = NULL; if (veraddr < BCM_PHYS_ADDR_DEFAULT + sizeof(bufver)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "version address 0x%x less than physical 0x%x", veraddr, (guint)BCM_PHYS_ADDR_DEFAULT); return FALSE; } if (!fu_memcpy_safe((guint8 *)bufver, sizeof(bufver), 0x0, /* dst */ buf, bufsz, veraddr - BCM_PHYS_ADDR_DEFAULT, /* src */ sizeof(bufver), error)) return FALSE; veritem = fu_bcm57xx_veritem_new((guint8 *)bufver, sizeof(bufver)); if (veritem != NULL) fu_firmware_set_version(image, veritem->version); } } fw_nocrc = fu_bytes_new_offset(fw, 0x0, g_bytes_get_size(fw) - sizeof(guint32), error); if (fw_nocrc == NULL) return FALSE; fu_firmware_set_bytes(image, fw_nocrc); return TRUE; } static GByteArray * fu_bcm57xx_stage1_image_write(FuFirmware *firmware, GError **error) { guint32 crc; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) fw_nocrc = NULL; /* sanity check */ if (fu_firmware_get_alignment(firmware) > FU_FIRMWARE_ALIGNMENT_1M) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "alignment invalid, got 0x%02x", fu_firmware_get_alignment(firmware)); return NULL; } /* the CRC-less payload */ fw_nocrc = fu_firmware_get_bytes(firmware, error); if (fw_nocrc == NULL) return NULL; /* fuzzing, so write a header with the version */ if (g_bytes_get_size(fw_nocrc) < BCM_NVRAM_STAGE1_VERSION) fu_byte_array_set_size(buf, BCM_NVRAM_STAGE1_VERSION + sizeof(guint32), 0x00); /* payload */ fu_byte_array_append_bytes(buf, fw_nocrc); /* update version */ if (!fu_memwrite_uint32_safe(buf->data, buf->len, BCM_NVRAM_STAGE1_VERSION, fu_firmware_get_version_raw(firmware), G_BIG_ENDIAN, error)) return NULL; /* align */ fu_byte_array_set_size( buf, fu_common_align_up(g_bytes_get_size(fw_nocrc), fu_firmware_get_alignment(firmware)), 0x00); /* add CRC */ crc = fu_bcm57xx_nvram_crc(buf->data, buf->len); fu_byte_array_append_uint32(buf, crc, G_LITTLE_ENDIAN); return g_steal_pointer(&buf); } static void fu_bcm57xx_stage1_image_init(FuBcm57xxStage1Image *self) { fu_firmware_set_alignment(FU_FIRMWARE(self), FU_FIRMWARE_ALIGNMENT_4); } static void fu_bcm57xx_stage1_image_class_init(FuBcm57xxStage1ImageClass *klass) { FuFirmwareClass *klass_image = FU_FIRMWARE_CLASS(klass); klass_image->parse = fu_bcm57xx_stage1_image_parse; klass_image->write = fu_bcm57xx_stage1_image_write; } FuFirmware * fu_bcm57xx_stage1_image_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_BCM57XX_STAGE1_IMAGE, NULL)); } fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-stage1-image.h000066400000000000000000000006251460375044200222430ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_BCM57XX_STAGE1_IMAGE (fu_bcm57xx_stage1_image_get_type()) G_DECLARE_FINAL_TYPE(FuBcm57xxStage1Image, fu_bcm57xx_stage1_image, FU, BCM57XX_STAGE1_IMAGE, FuFirmware) FuFirmware * fu_bcm57xx_stage1_image_new(void); fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-stage2-image.c000066400000000000000000000041011460375044200222300ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-bcm57xx-common.h" #include "fu-bcm57xx-stage2-image.h" struct _FuBcm57xxStage2Image { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuBcm57xxStage2Image, fu_bcm57xx_stage2_image, FU_TYPE_FIRMWARE) static gboolean fu_bcm57xx_stage2_image_parse(FuFirmware *image, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { g_autoptr(GBytes) fw_nocrc = NULL; if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { if (!fu_bcm57xx_verify_crc(fw, error)) return FALSE; } fw_nocrc = fu_bytes_new_offset(fw, 0x0, g_bytes_get_size(fw) - sizeof(guint32), error); if (fw_nocrc == NULL) return FALSE; fu_firmware_set_bytes(image, fw_nocrc); return TRUE; } static GByteArray * fu_bcm57xx_stage2_image_write(FuFirmware *image, GError **error) { const guint8 *buf; gsize bufsz = 0; g_autoptr(GByteArray) blob = NULL; g_autoptr(GBytes) fw_nocrc = NULL; /* get the CRC-less data */ fw_nocrc = fu_firmware_get_bytes(image, error); if (fw_nocrc == NULL) return NULL; /* add to a mutable buffer */ buf = g_bytes_get_data(fw_nocrc, &bufsz); blob = g_byte_array_sized_new(bufsz + (sizeof(guint32) * 3)); fu_byte_array_append_uint32(blob, BCM_NVRAM_MAGIC, G_BIG_ENDIAN); fu_byte_array_append_uint32(blob, g_bytes_get_size(fw_nocrc) + sizeof(guint32), G_BIG_ENDIAN); fu_byte_array_append_bytes(blob, fw_nocrc); /* add CRC */ fu_byte_array_append_uint32(blob, fu_bcm57xx_nvram_crc(buf, bufsz), G_LITTLE_ENDIAN); return g_steal_pointer(&blob); } static void fu_bcm57xx_stage2_image_init(FuBcm57xxStage2Image *self) { } static void fu_bcm57xx_stage2_image_class_init(FuBcm57xxStage2ImageClass *klass) { FuFirmwareClass *klass_image = FU_FIRMWARE_CLASS(klass); klass_image->parse = fu_bcm57xx_stage2_image_parse; klass_image->write = fu_bcm57xx_stage2_image_write; } FuFirmware * fu_bcm57xx_stage2_image_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_BCM57XX_STAGE2_IMAGE, NULL)); } fwupd-1.9.16/plugins/bcm57xx/fu-bcm57xx-stage2-image.h000066400000000000000000000006251460375044200222440ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_BCM57XX_STAGE2_IMAGE (fu_bcm57xx_stage2_image_get_type()) G_DECLARE_FINAL_TYPE(FuBcm57xxStage2Image, fu_bcm57xx_stage2_image, FU, BCM57XX_STAGE2_IMAGE, FuFirmware) FuFirmware * fu_bcm57xx_stage2_image_new(void); fwupd-1.9.16/plugins/bcm57xx/fu-self-test.c000066400000000000000000000112461460375044200204060ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-bcm57xx-common.h" #include "fu-bcm57xx-dict-image.h" #include "fu-bcm57xx-firmware.h" #include "fu-bcm57xx-stage1-image.h" #include "fu-bcm57xx-stage2-image.h" static void fu_bcm57xx_create_verbuf(guint8 *bufver, const gchar *version) { memcpy(bufver, version, strlen(version) + 1); } static void fu_bcm57xx_common_veritem_func(void) { g_autoptr(Bcm57xxVeritem) veritem1 = NULL; g_autoptr(Bcm57xxVeritem) veritem2 = NULL; g_autoptr(Bcm57xxVeritem) veritem3 = NULL; guint8 bufver[16] = {0x0}; fu_bcm57xx_create_verbuf(bufver, "5719-v1.43"); veritem1 = fu_bcm57xx_veritem_new(bufver, sizeof(bufver)); g_assert_nonnull(veritem1); g_assert_cmpstr(veritem1->version, ==, "1.43"); g_assert_cmpstr(veritem1->branch, ==, BCM_FW_BRANCH_UNKNOWN); g_assert_cmpint(veritem1->verfmt, ==, FWUPD_VERSION_FORMAT_PAIR); fu_bcm57xx_create_verbuf(bufver, "stage1-0.4.391"); veritem2 = fu_bcm57xx_veritem_new(bufver, sizeof(bufver)); g_assert_nonnull(veritem2); g_assert_cmpstr(veritem2->version, ==, "0.4.391"); g_assert_cmpstr(veritem2->branch, ==, BCM_FW_BRANCH_OSS_FIRMWARE); g_assert_cmpint(veritem2->verfmt, ==, FWUPD_VERSION_FORMAT_TRIPLET); fu_bcm57xx_create_verbuf(bufver, "RANDOM-7"); veritem3 = fu_bcm57xx_veritem_new(bufver, sizeof(bufver)); g_assert_nonnull(veritem3); g_assert_cmpstr(veritem3->version, ==, "RANDOM-7"); g_assert_cmpstr(veritem3->branch, ==, BCM_FW_BRANCH_UNKNOWN); g_assert_cmpint(veritem3->verfmt, ==, FWUPD_VERSION_FORMAT_UNKNOWN); } static void fu_bcm57xx_firmware_talos_func(void) { gboolean ret; g_autofree gchar *fn = NULL; g_autofree gchar *fn_out = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GBytes) blob_out = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) images = NULL; g_autoptr(FuFirmware) firmware = fu_bcm57xx_firmware_new(); /* load file */ fn = g_test_build_filename(G_TEST_DIST, "tests", "Bcm5719_talos.bin", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS)) { g_test_skip("missing file"); return; } blob = fu_bytes_get_contents(fn, &error); g_assert_no_error(error); g_assert_nonnull(blob); ret = fu_firmware_parse(firmware, blob, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); images = fu_firmware_get_images(firmware); g_assert_cmpint(images->len, ==, 6); blob_out = fu_firmware_write(firmware, &error); g_assert_no_error(error); g_assert_nonnull(blob_out); fn_out = g_test_build_filename(G_TEST_BUILT, "tests", "Bcm5719_talos.bin", NULL); ret = fu_bytes_set_contents(fn_out, blob_out, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_bytes_compare(blob, blob_out, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_bcm57xx_firmware_xml_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *csum1 = NULL; g_autofree gchar *csum2 = NULL; g_autofree gchar *xml_out = NULL; g_autofree gchar *xml_src = NULL; g_autoptr(FuFirmware) firmware1 = fu_bcm57xx_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_bcm57xx_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "bcm57xx.builder.xml", NULL); ret = g_file_get_contents(filename, &xml_src, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_build_from_xml(firmware1, xml_src, &error); g_assert_no_error(error); g_assert_true(ret); csum1 = fu_firmware_get_checksum(firmware1, G_CHECKSUM_SHA1, &error); g_assert_no_error(error); g_assert_cmpstr(csum1, ==, "a3ac108905c37857cf48612b707c1c72c582f914"); /* ensure we can round-trip */ xml_out = fu_firmware_export_to_xml(firmware1, FU_FIRMWARE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); ret = fu_firmware_build_from_xml(firmware2, xml_out, &error); g_assert_no_error(error); g_assert_true(ret); csum2 = fu_firmware_get_checksum(firmware2, G_CHECKSUM_SHA1, &error); g_assert_cmpstr(csum1, ==, csum2); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); g_type_ensure(FU_TYPE_BCM57XX_STAGE1_IMAGE); g_type_ensure(FU_TYPE_BCM57XX_STAGE2_IMAGE); g_type_ensure(FU_TYPE_BCM57XX_DICT_IMAGE); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func("/fwupd/bcm57xx/firmware{xml}", fu_bcm57xx_firmware_xml_func); g_test_add_func("/fwupd/bcm57xx/firmware{talos}", fu_bcm57xx_firmware_talos_func); g_test_add_func("/fwupd/bcm57xx/common{veritem}", fu_bcm57xx_common_veritem_func); return g_test_run(); } fwupd-1.9.16/plugins/bcm57xx/meson.build000066400000000000000000000030421460375044200200610ustar00rootroot00000000000000if get_option('plugin_bcm57xx').require(gudev.found(), error_message: 'gudev is needed for plugin_bcm57xx').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginBcm57xx"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('bcm57xx.quirk') plugin_builtin_bcm57xx = static_library('fu_plugin_bcm57xx', sources: [ 'fu-bcm57xx-plugin.c', 'fu-bcm57xx-common.c', # fuzzing 'fu-bcm57xx-device.c', 'fu-bcm57xx-dict-image.c', # fuzzing 'fu-bcm57xx-firmware.c', # fuzzing 'fu-bcm57xx-recovery-device.c', 'fu-bcm57xx-stage1-image.c', # fuzzing 'fu-bcm57xx-stage2-image.c', # fuzzing ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: [ plugin_deps, valgrind, ], ) plugin_builtins += plugin_builtin_bcm57xx if get_option('tests') install_data(['tests/bcm57xx.builder.xml'], install_dir: join_paths(installed_test_datadir, 'tests')) env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'bcm57xx-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_bcm57xx, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('bcm57xx-self-test', e, env: env) endif endif fwupd-1.9.16/plugins/bcm57xx/tests/000077500000000000000000000000001460375044200170625ustar00rootroot00000000000000fwupd-1.9.16/plugins/bcm57xx/tests/bcm57xx.bin000066400000000000000000010000001460375044200210400ustar00rootroot00000000000000fU8ŏ.o{fUfwupd-1.9.16/plugins/bcm57xx/tests/bcm57xx.builder.xml000066400000000000000000000007541460375044200225340ustar00rootroot00000000000000 1.2.3 0x123456 stage1 0x01 aGVsbG8gd29ybGQ= stage2 ape 0x7 aGVsbG8gd29ybGQ= fwupd-1.9.16/plugins/bios/000077500000000000000000000000001460375044200153575ustar00rootroot00000000000000fwupd-1.9.16/plugins/bios/README.md000066400000000000000000000005341460375044200166400ustar00rootroot00000000000000--- title: Plugin: BIOS --- ## Introduction This plugin checks UEFI capsules are available, and if missing a HSI failure is reported. ## External Interface Access This plugin requires read only access to attributes located within `/sys/firmware/efi/esrt`. ## Version Considerations This plugin has been available since fwupd version `1.5.0`. fwupd-1.9.16/plugins/bios/fu-bios-plugin.c000066400000000000000000000052231460375044200203650ustar00rootroot00000000000000/* * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-bios-plugin.h" struct _FuBiosPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuBiosPlugin, fu_bios_plugin, FU_TYPE_PLUGIN) static gboolean fu_bios_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); const gchar *vendor; vendor = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_BIOS_VENDOR); if (g_strcmp0(vendor, "coreboot") == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "system uses coreboot"); return FALSE; } return TRUE; } static gboolean fu_bios_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { g_autofree gchar *sysfsfwdir = NULL; g_autofree gchar *esrt_path = NULL; /* are the EFI dirs set up so we can update each device */ #if defined(__x86_64__) || defined(__i386__) g_autoptr(GError) error_local = NULL; if (!fu_efivar_supported(&error_local)) { fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_LEGACY_BIOS); fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_USER_WARNING); return TRUE; } #endif /* get the directory of ESRT entries */ sysfsfwdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_FW); esrt_path = g_build_filename(sysfsfwdir, "efi", "esrt", NULL); if (!g_file_test(esrt_path, G_FILE_TEST_IS_DIR)) { fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_CAPSULES_UNSUPPORTED); fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_USER_WARNING); return TRUE; } /* we appear to have UEFI capsule updates */ fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED); return TRUE; } static void fu_bios_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; if (!fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_LEGACY_BIOS)) return; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_UEFI_SECUREBOOT); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); } static void fu_bios_plugin_init(FuBiosPlugin *self) { } static void fu_bios_plugin_class_init(FuBiosPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->startup = fu_bios_plugin_startup; plugin_class->coldplug = fu_bios_plugin_coldplug; plugin_class->add_security_attrs = fu_bios_plugin_add_security_attrs; } fwupd-1.9.16/plugins/bios/fu-bios-plugin.h000066400000000000000000000003421460375044200203670ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuBiosPlugin, fu_bios_plugin, FU, BIOS_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/bios/meson.build000066400000000000000000000006361460375044200175260ustar00rootroot00000000000000if get_option('plugin_uefi_capsule').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginBios"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtins += static_library('fu_plugin_bios', sources: [ 'fu-bios-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/ccgx-dmc/000077500000000000000000000000001460375044200161105ustar00rootroot00000000000000fwupd-1.9.16/plugins/ccgx-dmc/README.md000066400000000000000000000063361460375044200173770ustar00rootroot00000000000000--- title: Plugin: CCGX DMC --- ## Introduction This plugin can flash firmware on Infineon (previously Cypress) CCGx DMC devices used in docks. ## Supported Protocols This plugin supports the following protocol IDs: * `com.cypress.ccgx.dmc` (deprecated) * `com.infineon.ccgx.dmc` ### DMC Factory Mode Dock Management Controller devices have a *composite version* that is used to describe the dock hardware as a whole, rather than enumerating and updating each sub-component separately. When OEMs have not followed the IHV-approved factory assembly process, the composite number is unset and fwupd would display `0.0.0.0` in the GUI and on the command line. In fwupd >= 1.8.11 we detect if the device is in *factory mode* and set the version number to `0.0.0.1`. When the device is in factory mode any valid upgrade will be allowed, which means the user might be prompted to “update to” the same current version installed on the dock. For millions of devices this is both a waste of time, resources, and also would inconvenience the user with an additional process for no reason. For devices that have been shipped in factory mode, but would like to avoid the update from `0.0.0.1` to the original version on the LVFS, can add a quirk entry which matches the `devx` subcomponent *base version*. In this example we match the parent VID, PID, the ComponentID and the `devx` base firmware version, setting the parent composite version to `0.0.0.15`. [USB\VID_2188&PID_0035&CID_05&VER_3.3.1.69] CcgxDmcCompositeVersion = 15 All the `devx` subcomponent versions can be shown on the console using: sudo fwupdtool –plugins ccgx get-devices –show-all –verbose ## Firmware Format In composite firmware topology, a single firmware image contains metadata and firmware images of multiple devices including DMC itself in a dock system. The daemon will decompress the cabinet archive and extract several firmware blobs in a combined image file format. See 4.4.1 Single Composite (Combined) Dock Image at for more details. ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_1234&PID_5678` ## Update Behavior The device usually presents in runtime HID mode, but on detach re-enumerates with with a DMC or HPI interface. On attach the device again re-enumerates back to the runtime HID mode. ## Vendor ID Security The vendor ID is set from the USB vendor, for example set to `USB:0x04B4` ## Quirk Use This plugin uses the following plugin-specific quirks: ### CcgxDmcTriggerCode DMC devices need a specified trigger code to request the device to update the firmware and the trigger code depends on the devices. 0x0: Do not update 0x1: Update immediately 0x2: Update after port disconnected Since: 1.8.0 ### CcgxDmcCompositeVersion Set the parent composite version, as a 32 bit integer. Since: 1.8.11 ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.5.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Ryan Lee: @chlee75 fwupd-1.9.16/plugins/ccgx-dmc/ccgx-dmc-noinst.quirk000066400000000000000000000003071460375044200221620ustar00rootroot00000000000000# Any EVB board that uses CY7C65219 [USB\VID_04B4&PID_5220] Plugin = ccgx_dmc RemoveDelay = 732000 # Any EVB board that uses CYUSB4357 [USB\VID_04B4&PID_521B] Plugin = ccgx_dmc RemoveDelay = 732000 fwupd-1.9.16/plugins/ccgx-dmc/ccgx-dmc.quirk000066400000000000000000000040061460375044200206520ustar00rootroot00000000000000# Lenovo ThinkPad Universal USB-C Dock [USB\VID_17EF&PID_30A9] Plugin = ccgx_dmc Summary = Dock Management Controller Device ParentGuid = USB\VID_17EF&PID_30AF Name = ThinkPad Universal USB-C Dock Flags = has-manual-replug CcgxDmcTriggerCode = 0x02 InstallDuration = 60 [USB\VID_17EF&PID_3105] Plugin = ccgx_dmc Summary = Dock Management Controller Device ParentGuid = USB\VID_17EF&PID_30AF Name = ThinkPad Universal USB-C Dock Flags = has-manual-replug CcgxDmcTriggerCode = 0x02 InstallDuration = 60 # HP USB-C Dock G5 [USB\VID_03F0&PID_046B] Plugin = ccgx_dmc Summary = Dock Management Controller Device ParentGuid = USB\VID_03F0&PID_0363 Vendor = HP Name = USB-C Dock G5 InstallDuration = 233 RemoveDelay = 203000 # HP USB-C/A Universal Dock G2 [USB\VID_03F0&PID_0A6B] Plugin = ccgx_dmc Summary = Dock Management Controller Device ParentGuid = USB\VID_03F0&PID_096B Vendor = HP Name = USB-C/A Universal Dock G2 InstallDuration = 180 RemoveDelay = 162000 # HP Thunderbolt Dock G4 Root Hub [USB\VID_1D5C&PID_5801] Summary = USB Hub Name = Thunderbolt Dock G4 top most USB Hub # HP Thunderbolt Dock G4 Hub [USB\VID_03F0&PID_2488] Summary = USB Hub ParentGuid = USB\VID_1D5C&PID_5801 Name = Thunderbolt Dock G4 USB Hub # HP Thunderbolt Dock G4 [USB\VID_03F0&PID_0488] Plugin = ccgx_dmc Summary = Dock Management Controller Device ParentGuid = USB\VID_03F0&PID_2488 Vendor = HP Name = Thunderbolt Dock G4 InstallDuration = 898 RemoveDelay = 732000 # Quanta Storage Inc. QSI Thunderbolt4 Godzilla Hub [USB\VID_2BEF&PID_9065] Plugin = ccgx_dmc Summary = Dock Management Controller Device RemoveDelay = 732000 # Anker Thunderbolt4 Mini Dock [USB\VID_291A&PID_8398] Plugin = ccgx_dmc Summary = Dock Management Controller Device RemoveDelay = 732000 # Caldigit ElementHub [USB\VID_2188&PID_0035] Plugin = ccgx_dmc [USB\VID_2188&PID_0035&CID_05&VER_3.3.1.69] CcgxDmcCompositeVersion = 15 # Belkin Thunderbolt 4 Core Hub dock [USB\VID_050D&PID_006E] Plugin = ccgx_dmc # CE-LINK TB4-Dock01 [USB\VID_2095&PID_4D01] Plugin = ccgx_dmc RemoveDelay = 732000 fwupd-1.9.16/plugins/ccgx-dmc/fu-ccgx-dmc-device.c000066400000000000000000000635511460375044200216200ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ccgx-dmc-device.h" #include "fu-ccgx-dmc-devx-device.h" #include "fu-ccgx-dmc-firmware.h" #include "fu-ccgx-dmc-struct.h" #define DMC_FW_WRITE_STATUS_RETRY_COUNT 3 #define DMC_FW_WRITE_STATUS_RETRY_DELAY_MS 30 #define DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT 5000 /* control in/out pipe policy in ms */ #define DMC_BULK_OUT_PIPE_TIMEOUT 2000 /* bulk out pipe policy in ms */ #define DMC_GET_REQUEST_TIMEOUT 20000 /* bulk out pipe policy in ms */ #define DMC_INTERRUPT_PIPE_ID 0x82 /* interrupt ep for DMC Dock */ #define DMC_BULK_PIPE_ID 1 /* USB bulk end point for DMC Dock */ /* maximum number of programmable devices expected to be connected in dock */ #define DMC_DOCK_MAX_DEV_COUNT 16 struct _FuCcgxDmcDevice { FuUsbDevice parent_instance; FuCcgxDmcDeviceStatus device_status; guint8 ep_intr_in; guint8 ep_bulk_out; FuCcgxDmcUpdateModel update_model; guint16 trigger_code; /* trigger code for update */ guint8 custom_meta_flag; }; /** * FU_CCGX_DMC_DEVICE_FLAG_HAS_MANUAL_REPLUG: * * Needs a manual replug from the end-user. */ #define FU_CCGX_DMC_DEVICE_FLAG_HAS_MANUAL_REPLUG (1 << 0) G_DEFINE_TYPE(FuCcgxDmcDevice, fu_ccgx_dmc_device, FU_TYPE_USB_DEVICE) static gboolean fu_ccgx_dmc_device_ensure_dock_id(FuCcgxDmcDevice *self, GError **error) { g_autoptr(GByteArray) st_id = fu_struct_ccgx_dmc_dock_identity_new(); if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_CCGX_DMC_RQT_CODE_DOCK_IDENTITY, /* request */ 0, /* value */ 0, /* index */ st_id->data, st_id->len, NULL, /* actual length */ DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT, NULL, error)) { g_prefix_error(error, "get_dock_id error: "); return FALSE; } self->custom_meta_flag = fu_struct_ccgx_dmc_dock_identity_get_custom_meta_data_flag(st_id); return TRUE; } static gboolean fu_ccgx_dmc_device_ensure_status(FuCcgxDmcDevice *self, GError **error) { guint remove_delay = 20 * 1000; /* guard band */ gsize bufsz; gsize offset = FU_STRUCT_CCGX_DMC_DOCK_STATUS_SIZE; g_autofree guint8 *buf = NULL; g_autoptr(GByteArray) st = fu_struct_ccgx_dmc_dock_status_new(); /* read minimum status length */ fu_byte_array_set_size(st, 32, 0x0); if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_CCGX_DMC_RQT_CODE_DOCK_STATUS, /* request */ 0, /* value */ 0, /* index */ st->data, st->len, NULL, /* actual length */ DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT, NULL, error)) { g_prefix_error(error, "get_dock_status min size error: "); return FALSE; } /* read full status length */ bufsz = FU_STRUCT_CCGX_DMC_DOCK_STATUS_SIZE + (DMC_DOCK_MAX_DEV_COUNT * FU_STRUCT_CCGX_DMC_DEVX_STATUS_SIZE); buf = g_malloc0(bufsz); if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_EMULATED)) { /* copying the old buffer preserves compatibility with old emulation files */ if (!fu_memcpy_safe(buf, bufsz, 0x0, st->data, st->len, 0x0, st->len, error)) return FALSE; } if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_CCGX_DMC_RQT_CODE_DOCK_STATUS, /* request */ 0, /* value */ 0, /* index */ buf, bufsz, NULL, /* actual length */ DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT, NULL, error)) { g_prefix_error(error, "get_dock_status actual size error: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "DmcDockStatus", buf, bufsz); /* add devx children */ for (guint i = 0; i < fu_struct_ccgx_dmc_dock_status_get_device_count(st); i++) { g_autoptr(FuCcgxDmcDevxDevice) devx = fu_ccgx_dmc_devx_device_new(FU_DEVICE(self), buf, bufsz, offset, error); if (devx == NULL) return FALSE; remove_delay += fu_ccgx_dmc_devx_device_get_remove_delay(devx); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(devx)); } /* ensure the remove delay is set */ if (fu_device_get_remove_delay(FU_DEVICE(self)) == 0) { g_debug("autosetting remove delay to %ums using DMC devx components", remove_delay); fu_device_set_remove_delay(FU_DEVICE(self), remove_delay); } /* success */ self->device_status = fu_struct_ccgx_dmc_dock_status_get_device_status(st); fu_device_set_version_u32(FU_DEVICE(self), fu_struct_ccgx_dmc_dock_status_get_composite_version(st)); return TRUE; } static gboolean fu_ccgx_dmc_device_send_reset_state_machine(FuCcgxDmcDevice *self, GError **error) { if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_CCGX_DMC_RQT_CODE_RESET_STATE_MACHINE, /* request */ 0, /* value */ 0, /* index */ 0, /* data */ 0, /* length */ NULL, /* actual length */ DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT, NULL, error)) { g_prefix_error(error, "send reset state machine error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_dmc_device_send_soft_reset(FuCcgxDmcDevice *self, gboolean reset_later, GError **error) { if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_CCGX_DMC_RQT_CODE_SOFT_RESET, /* request */ reset_later, /* value */ 0, /* index */ 0, /* data */ 0, /* length */ NULL, /* actual length */ DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT, NULL, error)) { g_prefix_error(error, "send reset error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_dmc_device_send_start_upgrade(FuCcgxDmcDevice *self, GBytes *fw, GError **error) { gsize bufsz = 0; const guint8 *buf = NULL; g_autofree guint8 *buf_mut = NULL; if (fw != NULL) buf = g_bytes_get_data(fw, &bufsz); if (bufsz > 0) { buf_mut = fu_memdup_safe(buf, bufsz, error); if (buf_mut == NULL) return FALSE; } if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_CCGX_DMC_RQT_CODE_UPGRADE_START, /* request */ bufsz > 0 ? 1 : 0, /* value */ 1, /* index, forced update */ buf_mut, /* data */ bufsz, /* length */ NULL, /* actual length */ DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT, NULL, error)) { g_prefix_error(error, "send reset error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_dmc_device_send_download_trigger(FuCcgxDmcDevice *self, guint16 trigger, GError **error) { if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_CCGX_DMC_RQT_CODE_TRIGGER, /* request */ trigger, /* value */ 0, /* index */ 0, /* data */ 0, /* length */ NULL, /* actual length */ DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT, NULL, error)) { g_prefix_error(error, "send download trigger error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_dmc_device_send_fwct(FuCcgxDmcDevice *self, const guint8 *buf, guint16 bufsz, GError **error) { g_autofree guint8 *buf_mut = NULL; g_return_val_if_fail(buf != NULL, FALSE); buf_mut = fu_memdup_safe(buf, bufsz, error); if (buf_mut == NULL) return FALSE; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_CCGX_DMC_RQT_CODE_FWCT_WRITE, /* request */ 0, /* value */ 0, /* index */ buf_mut, /* data */ bufsz, /* length */ NULL, /* actual length */ DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT, NULL, error)) { g_prefix_error(error, "send fwct error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_dmc_device_read_intr_req(FuCcgxDmcDevice *self, GByteArray *intr_rqt, GError **error) { guint8 rqt_opcode; g_autofree gchar *title = NULL; g_return_val_if_fail(intr_rqt != NULL, FALSE); if (!g_usb_device_interrupt_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), self->ep_intr_in, intr_rqt->data, intr_rqt->len, NULL, DMC_GET_REQUEST_TIMEOUT, NULL, error)) { g_prefix_error(error, "read intr rqt error: "); return FALSE; } /* success */ rqt_opcode = fu_struct_ccgx_dmc_int_rqt_get_opcode(intr_rqt); title = g_strdup_printf("DmcIntRqt-opcode=0x%02x[%s]", rqt_opcode, fu_ccgx_dmc_int_opcode_to_string(rqt_opcode)); fu_dump_raw(G_LOG_DOMAIN, title, fu_struct_ccgx_dmc_int_rqt_get_data(intr_rqt, NULL), MIN(fu_struct_ccgx_dmc_int_rqt_get_length(intr_rqt), FU_STRUCT_CCGX_DMC_INT_RQT_SIZE_DATA)); return TRUE; } static gboolean fu_ccgx_dmc_device_send_write_command(FuCcgxDmcDevice *self, guint16 start_row, guint16 num_of_row, GError **error) { if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_CCGX_DMC_RQT_CODE_IMG_WRITE, /* request */ start_row, /* value */ num_of_row, /* index */ 0, /* data */ 0, /* length */ NULL, /* actual length */ DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT, NULL, error)) { g_prefix_error(error, "send fwct error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_dmc_device_send_row_data(FuCcgxDmcDevice *self, const guint8 *row_buffer, guint16 row_size, GError **error) { g_return_val_if_fail(row_buffer != NULL, FALSE); if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), self->ep_bulk_out, (guint8 *)row_buffer, row_size, NULL, DMC_BULK_OUT_PIPE_TIMEOUT, NULL, error)) { g_prefix_error(error, "write row data error: "); return FALSE; } return TRUE; } static void fu_ccgx_dmc_device_to_string(FuDevice *device, guint idt, GString *str) { FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE(device); fu_string_append(str, idt, "UpdateModel", fu_ccgx_dmc_update_model_to_string(self->update_model)); fu_string_append_kx(str, idt, "EpBulkOut", self->ep_bulk_out); fu_string_append_kx(str, idt, "EpIntrIn", self->ep_intr_in); fu_string_append_kx(str, idt, "TriggerCode", self->trigger_code); fu_string_append(str, idt, "DeviceStatus", fu_ccgx_dmc_device_status_to_string(self->device_status)); } static gboolean fu_ccgx_dmc_get_image_write_status_cb(FuDevice *device, gpointer user_data, GError **error) { FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE(device); const guint8 *req_data; guint8 req_opcode; g_autoptr(GByteArray) dmc_int_req = fu_struct_ccgx_dmc_int_rqt_new(); /* get interrupt request */ if (!fu_ccgx_dmc_device_read_intr_req(self, dmc_int_req, error)) { g_prefix_error(error, "failed to read intr req in image write status: "); return FALSE; } /* check opcode for fw write */ req_opcode = fu_struct_ccgx_dmc_int_rqt_get_opcode(dmc_int_req); if (req_opcode != FU_CCGX_DMC_INT_OPCODE_IMG_WRITE_STATUS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid intr req opcode in image write status: %u [%s]", req_opcode, fu_ccgx_dmc_int_opcode_to_string(req_opcode)); return FALSE; } /* retry if data[0] is 1 otherwise error */ req_data = fu_struct_ccgx_dmc_int_rqt_get_data(dmc_int_req, NULL); if (req_data[0] != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid intr req data in image write status = %u", req_data[0]); fu_device_sleep(device, DMC_FW_WRITE_STATUS_RETRY_DELAY_MS); return FALSE; } return TRUE; } static gboolean fu_ccgx_dmc_write_firmware_record(FuCcgxDmcDevice *self, FuCcgxDmcFirmwareSegmentRecord *seg_rcd, gsize *fw_data_written, FuProgress *progress, GError **error) { GPtrArray *data_records = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99, NULL); /* write start row and number of rows to a device */ if (!fu_ccgx_dmc_device_send_write_command(self, seg_rcd->start_row, seg_rcd->num_rows, error)) return FALSE; fu_progress_step_done(progress); /* send data records */ data_records = seg_rcd->data_records; for (guint32 data_index = 0; data_index < data_records->len; data_index++) { GBytes *data_rcd = g_ptr_array_index(data_records, data_index); const guint8 *row_buffer = NULL; gsize row_size = 0; /* write row data */ row_buffer = g_bytes_get_data(data_rcd, &row_size); if (!fu_ccgx_dmc_device_send_row_data(self, row_buffer, (guint16)row_size, error)) return FALSE; /* increase fw written size */ *fw_data_written += row_size; /* get status */ if (!fu_device_retry(FU_DEVICE(self), fu_ccgx_dmc_get_image_write_status_cb, DMC_FW_WRITE_STATUS_RETRY_COUNT, NULL, error)) return FALSE; /* done */ fu_progress_set_percentage_full(fu_progress_get_child(progress), data_index + 1, data_records->len); } fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_ccgx_dmc_write_firmware_image(FuDevice *device, FuCcgxDmcFirmwareRecord *img_rcd, gsize *fw_data_written, const gsize fw_data_size, FuProgress *progress, GError **error) { FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE(device); GPtrArray *seg_records; g_return_val_if_fail(img_rcd != NULL, FALSE); g_return_val_if_fail(fw_data_written != NULL, FALSE); /* get segment records */ seg_records = img_rcd->seg_records; fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, seg_records->len); for (guint32 seg_index = 0; seg_index < seg_records->len; seg_index++) { FuCcgxDmcFirmwareSegmentRecord *seg_rcd = g_ptr_array_index(seg_records, seg_index); if (!fu_ccgx_dmc_write_firmware_record(self, seg_rcd, fw_data_written, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } return TRUE; } static gboolean fu_ccgx_dmc_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE(device); FuCcgxDmcFirmwareRecord *img_rcd = NULL; GBytes *custom_meta_blob; GBytes *fwct_blob; GPtrArray *image_records; const guint8 *fwct_buf = NULL; const guint8 *rqt_data = NULL; gsize fwct_sz = 0; gsize fw_data_size = 0; gsize fw_data_written = 0; guint8 img_index = 0; guint8 rqt_opcode; g_autoptr(GByteArray) dmc_int_rqt = fu_struct_ccgx_dmc_int_rqt_new(); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "fwct"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "img"); /* this is used in FuDevice->attach */ self->update_model = FU_CCGX_DMC_UPDATE_MODEL_NONE; /* get fwct record */ fwct_blob = fu_ccgx_dmc_firmware_get_fwct_record(FU_CCGX_DMC_FIRMWARE(firmware)); fwct_buf = g_bytes_get_data(fwct_blob, &fwct_sz); if (fwct_buf == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid fwct data"); return FALSE; } /* reset */ if (!fu_ccgx_dmc_device_send_reset_state_machine(self, error)) return FALSE; fu_progress_step_done(progress); /* start fw upgrade with custom metadata */ custom_meta_blob = fu_ccgx_dmc_firmware_get_custom_meta_record(FU_CCGX_DMC_FIRMWARE(firmware)); if (!fu_ccgx_dmc_device_send_start_upgrade(self, custom_meta_blob, error)) return FALSE; /* send fwct data */ if (!fu_ccgx_dmc_device_send_fwct(self, fwct_buf, fwct_sz, error)) return FALSE; fu_progress_step_done(progress); /* get total fw size */ image_records = fu_ccgx_dmc_firmware_get_image_records(FU_CCGX_DMC_FIRMWARE(firmware)); fw_data_size = fu_ccgx_dmc_firmware_get_fw_data_size(FU_CCGX_DMC_FIRMWARE(firmware)); while (1) { /* get interrupt request */ if (!fu_ccgx_dmc_device_read_intr_req(self, dmc_int_rqt, error)) return FALSE; rqt_data = fu_struct_ccgx_dmc_int_rqt_get_data(dmc_int_rqt, NULL); /* fw upgrade request */ rqt_opcode = fu_struct_ccgx_dmc_int_rqt_get_opcode(dmc_int_rqt); if (rqt_opcode != FU_CCGX_DMC_INT_OPCODE_FW_UPGRADE_RQT) break; img_index = rqt_data[0]; if (img_index >= image_records->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid image index %d, expected less than %u", img_index, image_records->len); return FALSE; } /* write image */ g_debug("writing image index %u/%u", img_index, image_records->len - 1); img_rcd = g_ptr_array_index(image_records, img_index); if (!fu_ccgx_dmc_write_firmware_image(device, img_rcd, &fw_data_written, fw_data_size, fu_progress_get_child(progress), error)) return FALSE; } if (rqt_opcode != FU_CCGX_DMC_INT_OPCODE_FW_UPGRADE_STATUS) { if (rqt_opcode == FU_CCGX_DMC_INT_OPCODE_FWCT_ANALYSIS_STATUS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid fwct analysis failed with status 0x%02x[%s]", rqt_data[0], fu_ccgx_dmc_fwct_analysis_status_to_string(rqt_data[0])); return FALSE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid dmc intr req opcode 0x%02x[%s] with status 0x%02x", rqt_opcode, fu_ccgx_dmc_int_opcode_to_string(rqt_opcode), rqt_data[0]); return FALSE; } if (rqt_data[0] == FU_CCGX_DMC_DEVICE_STATUS_UPDATE_PHASE1_COMPLETE) { self->update_model = FU_CCGX_DMC_UPDATE_MODEL_DOWNLOAD_TRIGGER; } else if (rqt_data[0] == FU_CCGX_DMC_DEVICE_STATUS_FW_DOWNLOADED_UPDATE_PEND) { self->update_model = FU_CCGX_DMC_UPDATE_MODEL_PENDING_RESET; } else if (rqt_data[0] >= FU_CCGX_DMC_DEVICE_STATUS_PHASE2_UPDATE_FAIL_INVALID_FWCT) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid status code = %u", rqt_data[0]); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static FuFirmware * fu_ccgx_dmc_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_ccgx_dmc_firmware_new(); FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE(device); GBytes *custom_meta_blob = NULL; gboolean custom_meta_exist = FALSE; /* parse all images */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* get custom meta record */ custom_meta_blob = fu_ccgx_dmc_firmware_get_custom_meta_record(FU_CCGX_DMC_FIRMWARE(firmware)); if (custom_meta_blob) if (g_bytes_get_size(custom_meta_blob) > 0) custom_meta_exist = TRUE; /* check custom meta flag */ if (self->custom_meta_flag != custom_meta_exist) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "custom metadata mismatch"); return NULL; } return g_steal_pointer(&firmware); } static gboolean fu_ccgx_dmc_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE(device); gboolean manual_replug; manual_replug = fu_device_has_private_flag(device, FU_CCGX_DMC_DEVICE_FLAG_HAS_MANUAL_REPLUG); /* device action required */ if (self->update_model == FU_CCGX_DMC_UPDATE_MODEL_DOWNLOAD_TRIGGER) { if (self->trigger_code > 0) { if (!fu_ccgx_dmc_device_send_download_trigger(self, self->trigger_code, error)) { g_prefix_error(error, "download trigger error: "); return FALSE; } } } else if (self->update_model == FU_CCGX_DMC_UPDATE_MODEL_PENDING_RESET) { if (!fu_ccgx_dmc_device_send_soft_reset(self, manual_replug, error)) { g_prefix_error(error, "soft reset error: "); return FALSE; } } /* the user has to do something */ if (manual_replug) { g_autoptr(FwupdRequest) request = fwupd_request_new(); fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); if (!fu_device_emit_request(device, request, progress, error)) return FALSE; } /* success */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_ccgx_dmc_device_ensure_factory_version(FuCcgxDmcDevice *self) { GPtrArray *children = fu_device_get_children(FU_DEVICE(self)); for (guint i = 0; i < children->len; i++) { FuCcgxDmcDevxDevice *child = g_ptr_array_index(children, i); const guint8 *fw_version = fu_ccgx_dmc_devx_device_get_fw_version(child); FuCcgxDmcDevxDeviceType device_type = fu_ccgx_dmc_devx_device_get_device_type(child); guint64 fwver_img1 = fu_memread_uint64(fw_version + 0x08, G_LITTLE_ENDIAN); guint64 fwver_img2 = fu_memread_uint64(fw_version + 0x10, G_LITTLE_ENDIAN); if (device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_DMC && fwver_img1 == fwver_img2 && fwver_img1 != 0) { g_info("overriding version as device is in factory mode"); fu_device_set_version_u32(FU_DEVICE(self), 0x1); return; } } } static gboolean fu_ccgx_dmc_device_setup(FuDevice *device, GError **error) { FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE(device); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_ccgx_dmc_device_parent_class)->setup(device, error)) return FALSE; /* get dock identity */ if (!fu_ccgx_dmc_device_ensure_dock_id(self, error)) return FALSE; if (!fu_ccgx_dmc_device_ensure_status(self, error)) return FALSE; /* use composite version, but also try to detect "factory mode" where the SPI has been * imaged but has not been updated manually to the initial version */ if (fu_device_get_version_raw(device) == 0) fu_ccgx_dmc_device_ensure_factory_version(self); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); if (self->custom_meta_flag > 0) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); else fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); if (fu_device_has_private_flag(device, FU_CCGX_DMC_DEVICE_FLAG_HAS_MANUAL_REPLUG)) { fu_device_add_request_flag(FU_DEVICE(self), FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); } return TRUE; } static gboolean fu_ccgx_dmc_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE(device); if (g_strcmp0(key, "CcgxDmcTriggerCode") == 0) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->trigger_code = tmp; return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no supported"); return FALSE; } static void fu_ccgx_dmc_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); /* actually 0, 20, 0, 80! */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 75, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 25, "reload"); } static void fu_ccgx_dmc_device_init(FuCcgxDmcDevice *self) { self->ep_intr_in = DMC_INTERRUPT_PIPE_ID; self->ep_bulk_out = DMC_BULK_PIPE_ID; self->trigger_code = 0x1; fu_device_add_protocol(FU_DEVICE(self), "com.cypress.ccgx.dmc"); fu_device_add_protocol(FU_DEVICE(self), "com.infineon.ccgx.dmc"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG); fu_usb_device_add_interface(FU_USB_DEVICE(self), 0x01); fu_device_register_private_flag(FU_DEVICE(self), FU_CCGX_DMC_DEVICE_FLAG_HAS_MANUAL_REPLUG, "has-manual-replug"); } static void fu_ccgx_dmc_device_class_init(FuCcgxDmcDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_ccgx_dmc_device_to_string; klass_device->write_firmware = fu_ccgx_dmc_write_firmware; klass_device->prepare_firmware = fu_ccgx_dmc_device_prepare_firmware; klass_device->attach = fu_ccgx_dmc_device_attach; klass_device->setup = fu_ccgx_dmc_device_setup; klass_device->set_quirk_kv = fu_ccgx_dmc_device_set_quirk_kv; klass_device->set_progress = fu_ccgx_dmc_device_set_progress; } fwupd-1.9.16/plugins/ccgx-dmc/fu-ccgx-dmc-device.h000066400000000000000000000005511460375044200216140ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CCGX_DMC_DEVICE (fu_ccgx_dmc_device_get_type()) G_DECLARE_FINAL_TYPE(FuCcgxDmcDevice, fu_ccgx_dmc_device, FU, CCGX_DMC_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/ccgx-dmc/fu-ccgx-dmc-devx-device.c000066400000000000000000000270541460375044200225620ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * Copyright (C) 2020 Cypress Semiconductor Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ccgx-dmc-devx-device.h" #define DMC_FW_WRITE_STATUS_RETRY_COUNT 3 #define DMC_FW_WRITE_STATUS_RETRY_DELAY_MS 30 struct _FuCcgxDmcDevxDevice { FuDevice parent_instance; GByteArray *status; /* DmcDevxStatus */ }; G_DEFINE_TYPE(FuCcgxDmcDevxDevice, fu_ccgx_dmc_devx_device, FU_TYPE_DEVICE) const guint8 * fu_ccgx_dmc_devx_device_get_fw_version(FuCcgxDmcDevxDevice *self) { return fu_struct_ccgx_dmc_devx_status_get_fw_version(self->status, NULL); } FuCcgxDmcDevxDeviceType fu_ccgx_dmc_devx_device_get_device_type(FuCcgxDmcDevxDevice *self) { return fu_struct_ccgx_dmc_devx_status_get_device_type(self->status); } static gchar * fu_ccgx_dmc_devx_device_version_dmc_bfw(FuCcgxDmcDevxDevice *self, gsize offset) { const guint8 *fw_version = fu_ccgx_dmc_devx_device_get_fw_version(self); return g_strdup_printf("%u.%u.%u.%u", fw_version[offset + 3] >> 4, fw_version[offset + 3] & 0xFu, fw_version[offset + 2], fu_memread_uint16(fw_version + offset, G_LITTLE_ENDIAN)); } static gchar * fu_ccgx_dmc_devx_device_version_dmc_app(FuCcgxDmcDevxDevice *self, gsize offset) { const guint8 *fw_version = fu_ccgx_dmc_devx_device_get_fw_version(self); return g_strdup_printf("%u.%u.%u", fw_version[offset + 4 + 3] >> 4, fw_version[offset + 4 + 3] & 0xFu, fw_version[offset + 4 + 2]); } static gchar * fu_ccgx_dmc_devx_device_version_hx3(FuCcgxDmcDevxDevice *self, gsize offset) { const guint8 *fw_version = fu_ccgx_dmc_devx_device_get_fw_version(self); return g_strdup_printf("%u.%u.%u", fw_version[offset + 4 + 3], fw_version[offset + 4 + 2], fw_version[offset + 4 + 1]); } static void fu_ccgx_dmc_devx_device_hexver_to_string(FuCcgxDmcDevxDevice *self, const gchar *kind, gsize offset, guint idt, GString *str) { const guint8 *fw_version = fu_ccgx_dmc_devx_device_get_fw_version(self); g_autofree gchar *key = g_strdup_printf("FwVersion[%s]", kind); g_autofree gchar *val = fu_version_from_uint64(fu_memread_uint64(fw_version + offset, G_LITTLE_ENDIAN), FWUPD_VERSION_FORMAT_HEX); fu_string_append(str, idt, key, val); } static void fu_ccgx_dmc_devx_device_hx3ver_to_string(FuCcgxDmcDevxDevice *self, const gchar *kind, gsize offset, guint idt, GString *str) { g_autofree gchar *key = g_strdup_printf("FwVersion[%s]", kind); g_autofree gchar *val = fu_ccgx_dmc_devx_device_version_hx3(self, offset); fu_string_append(str, idt, key, val); } static void fu_ccgx_dmc_devx_device_dmcver_to_string(FuCcgxDmcDevxDevice *self, const gchar *kind, gsize offset, guint idt, GString *str) { g_autofree gchar *key = g_strdup_printf("FwVersion[%s]", kind); g_autofree gchar *bfw_val = fu_ccgx_dmc_devx_device_version_dmc_bfw(self, offset); g_autofree gchar *app_val = fu_ccgx_dmc_devx_device_version_dmc_app(self, offset); g_autofree gchar *tmp = g_strdup_printf("base:%s\tapp:%s", bfw_val, app_val); fu_string_append(str, idt, key, tmp); } static FuCcgxDmcDevxDeviceType fu_ccgx_dmc_devx_device_version_type(FuCcgxDmcDevxDevice *self) { guint8 device_type = fu_struct_ccgx_dmc_devx_status_get_device_type(self->status); if (device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_DMC || device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_CCG3 || device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_CCG4 || device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_CCG5 || device_type == 0x0B) return FU_CCGX_DMC_DEVX_DEVICE_TYPE_DMC; if (device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_HX3) return FU_CCGX_DMC_DEVX_DEVICE_TYPE_HX3; return FU_CCGX_DMC_DEVX_DEVICE_TYPE_INVALID; } static void fu_ccgx_dmc_devx_device_to_string(FuDevice *device, guint idt, GString *str) { FuCcgxDmcDevxDevice *self = FU_CCGX_DMC_DEVX_DEVICE(device); FuCcgxDmcDevxDeviceType device_version_type = fu_ccgx_dmc_devx_device_version_type(self); guint8 device_type = fu_struct_ccgx_dmc_devx_status_get_device_type(self->status); guint8 image_mode = fu_struct_ccgx_dmc_devx_status_get_image_mode(self->status); guint8 img_status = fu_struct_ccgx_dmc_devx_status_get_img_status(self->status); const gchar *device_type_str = fu_ccgx_dmc_devx_device_type_to_string(device_type); if (device_type_str != NULL) { g_autofree gchar *tmp = g_strdup_printf("0x%x [%s]", device_type, device_type_str); fu_string_append(str, idt, "DeviceType", tmp); } else { fu_string_append_kx(str, idt, "DeviceType", device_type); } if (image_mode < FU_CCGX_DMC_IMG_MODE_LAST) { g_autofree gchar *tmp = g_strdup_printf("0x%x [%s]", image_mode, fu_ccgx_dmc_img_mode_to_string(image_mode)); fu_string_append(str, idt, "ImageMode", tmp); } else { fu_string_append_kx(str, idt, "ImageMode", image_mode); } fu_string_append_kx(str, idt, "CurrentImage", fu_struct_ccgx_dmc_devx_status_get_current_image(self->status)); fu_string_append(str, idt, "ImgStatus1", fu_ccgx_dmc_img_status_to_string(img_status & 0x0F)); fu_string_append(str, idt, "ImgStatus2", fu_ccgx_dmc_img_status_to_string((img_status >> 4) & 0x0F)); /* versions */ if (device_version_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_DMC) { fu_ccgx_dmc_devx_device_dmcver_to_string(self, "boot", 0x00, idt, str); fu_ccgx_dmc_devx_device_dmcver_to_string(self, "img1", 0x08, idt, str); if (image_mode != FU_CCGX_DMC_IMG_MODE_SINGLE_IMG) fu_ccgx_dmc_devx_device_dmcver_to_string(self, "img2", 0x10, idt, str); } else if (device_version_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_HX3) { fu_ccgx_dmc_devx_device_hx3ver_to_string(self, "boot", 0x00, idt, str); fu_ccgx_dmc_devx_device_hx3ver_to_string(self, "img1", 0x08, idt, str); if (image_mode != FU_CCGX_DMC_IMG_MODE_SINGLE_IMG) fu_ccgx_dmc_devx_device_hx3ver_to_string(self, "img2", 0x10, idt, str); } else { fu_ccgx_dmc_devx_device_hexver_to_string(self, "boot", 0x00, idt, str); fu_ccgx_dmc_devx_device_hexver_to_string(self, "img1", 0x08, idt, str); if (image_mode != FU_CCGX_DMC_IMG_MODE_SINGLE_IMG) fu_ccgx_dmc_devx_device_hexver_to_string(self, "img2", 0x10, idt, str); } } static gboolean fu_ccgx_dmc_devx_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { if (g_strcmp0(key, "CcgxDmcCompositeVersion") == 0) { guint64 tmp = 0; FuDevice *proxy = fu_device_get_proxy(device); if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; if (fu_device_get_version_raw(proxy) != tmp) { g_debug("overriding composite version from %u to %u from %s", (guint)fu_device_get_version_raw(proxy), (guint)tmp, fu_device_get_id(device)); fu_device_set_version_u32(proxy, tmp); } return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no supported"); return FALSE; } static const gchar * fu_ccgx_dmc_devx_device_type_to_name(FuCcgxDmcDevxDeviceType device_type) { if (device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_CCG3) return "CCG3"; if (device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_DMC) return "DMC"; if (device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_CCG4) return "CCG4"; if (device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_CCG5) return "CCG5"; if (device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_HX3) return "HX3"; if (device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_HX3_PD) return "HX3 PD"; if (device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_DMC_PD) return "DMC PD"; if (device_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_SPI) return "SPI"; return "Unknown"; } guint fu_ccgx_dmc_devx_device_get_remove_delay(FuCcgxDmcDevxDevice *self) { guint remove_delay = 0; g_return_val_if_fail(FU_IS_CCGX_DMC_DEVX_DEVICE(self), G_MAXUINT); switch (fu_struct_ccgx_dmc_devx_status_get_device_type(self->status)) { case FU_CCGX_DMC_DEVX_DEVICE_TYPE_DMC: remove_delay = 40 * 1000; break; default: remove_delay = 30 * 1000; break; } return remove_delay; } static gboolean fu_ccgx_dmc_devx_device_probe(FuDevice *device, GError **error) { FuCcgxDmcDevxDevice *self = FU_CCGX_DMC_DEVX_DEVICE(device); FuDevice *proxy = fu_device_get_proxy(device); FuCcgxDmcDevxDeviceType device_version_type = fu_ccgx_dmc_devx_device_version_type(self); gsize offset = 0; guint8 device_type = fu_struct_ccgx_dmc_devx_status_get_device_type(self->status); g_autofree gchar *logical_id = g_strdup_printf("0x%02x", fu_struct_ccgx_dmc_devx_status_get_component_id(self->status)); g_autofree gchar *version = NULL; fu_device_set_name(device, fu_ccgx_dmc_devx_device_type_to_name(device_type)); fu_device_set_logical_id(device, logical_id); /* for the version number */ if (fu_struct_ccgx_dmc_devx_status_get_current_image(self->status) == 0x01) offset = 4; else if (fu_struct_ccgx_dmc_devx_status_get_current_image(self->status) == 0x02) offset = 8; /* version, if possible */ if (device_version_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_DMC) { version = fu_ccgx_dmc_devx_device_version_dmc_bfw(self, offset); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); } else if (device_version_type == FU_CCGX_DMC_DEVX_DEVICE_TYPE_HX3) { version = fu_ccgx_dmc_devx_device_version_hx3(self, offset); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, version); } if (version != NULL) { fu_device_set_version(device, version); fu_device_add_instance_strsafe(device, "VER", version); } /* add GUIDs */ fu_device_add_instance_strup(device, "TYPE", fu_ccgx_dmc_devx_device_type_to_string(device_type)); fu_device_add_instance_u8(device, "CID", fu_struct_ccgx_dmc_devx_status_get_component_id(self->status)); fu_device_add_instance_u16(device, "VID", fu_usb_device_get_vid(FU_USB_DEVICE(proxy))); fu_device_add_instance_u16(device, "PID", fu_usb_device_get_pid(FU_USB_DEVICE(proxy))); fu_device_build_instance_id(device, NULL, "USB", "VID", "PID", "CID", NULL); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "USB", "VID", "PID", "CID", "TYPE", NULL); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "USB", "VID", "PID", "CID", "VER", NULL); /* success */ return TRUE; } static void fu_ccgx_dmc_devx_device_init(FuCcgxDmcDevxDevice *self) { } static void fu_ccgx_dmc_devx_device_finalize(GObject *object) { FuCcgxDmcDevxDevice *self = FU_CCGX_DMC_DEVX_DEVICE(object); if (self->status != NULL) g_byte_array_unref(self->status); G_OBJECT_CLASS(fu_ccgx_dmc_devx_device_parent_class)->finalize(object); } static void fu_ccgx_dmc_devx_device_class_init(FuCcgxDmcDevxDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_ccgx_dmc_devx_device_finalize; klass_device->probe = fu_ccgx_dmc_devx_device_probe; klass_device->to_string = fu_ccgx_dmc_devx_device_to_string; klass_device->set_quirk_kv = fu_ccgx_dmc_devx_device_set_quirk_kv; } FuCcgxDmcDevxDevice * fu_ccgx_dmc_devx_device_new(FuDevice *proxy, const guint8 *buf, gsize bufsz, gsize offset, GError **error) { g_autoptr(FuCcgxDmcDevxDevice) self = g_object_new(FU_TYPE_CCGX_DMC_DEVX_DEVICE, "context", fu_device_get_context(proxy), "proxy", proxy, NULL); self->status = fu_struct_ccgx_dmc_devx_status_parse(buf, bufsz, offset, error); if (self->status == NULL) return NULL; return g_steal_pointer(&self); } fwupd-1.9.16/plugins/ccgx-dmc/fu-ccgx-dmc-devx-device.h000066400000000000000000000015231460375044200225600ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * Copyright (C) 2020 Cypress Semiconductor Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-ccgx-dmc-struct.h" #define FU_TYPE_CCGX_DMC_DEVX_DEVICE (fu_ccgx_dmc_devx_device_get_type()) G_DECLARE_FINAL_TYPE(FuCcgxDmcDevxDevice, fu_ccgx_dmc_devx_device, FU, CCGX_DMC_DEVX_DEVICE, FuDevice) FuCcgxDmcDevxDevice * fu_ccgx_dmc_devx_device_new(FuDevice *proxy, const guint8 *buf, gsize bufsz, gsize offset, GError **error); guint fu_ccgx_dmc_devx_device_get_remove_delay(FuCcgxDmcDevxDevice *self); const guint8 * fu_ccgx_dmc_devx_device_get_fw_version(FuCcgxDmcDevxDevice *self); FuCcgxDmcDevxDeviceType fu_ccgx_dmc_devx_device_get_device_type(FuCcgxDmcDevxDevice *self); fwupd-1.9.16/plugins/ccgx-dmc/fu-ccgx-dmc-firmware.c000066400000000000000000000347051460375044200221740ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ccgx-dmc-firmware.h" #include "fu-ccgx-dmc-struct.h" struct _FuCcgxDmcFirmware { FuFirmwareClass parent_instance; GPtrArray *image_records; GBytes *fwct_blob; GBytes *custom_meta_blob; guint32 row_data_offset_start; guint32 fw_data_size; }; G_DEFINE_TYPE(FuCcgxDmcFirmware, fu_ccgx_dmc_firmware, FU_TYPE_FIRMWARE) #define DMC_FWCT_MAX_SIZE 2048 #define DMC_HASH_SIZE 32 #define DMC_CUSTOM_META_LENGTH_FIELD_SIZE 2 static void fu_ccgx_dmc_firmware_record_free(FuCcgxDmcFirmwareRecord *rcd) { if (rcd->seg_records != NULL) g_ptr_array_unref(rcd->seg_records); g_free(rcd); } static void fu_ccgx_dmc_firmware_segment_record_free(FuCcgxDmcFirmwareSegmentRecord *rcd) { if (rcd->data_records != NULL) g_ptr_array_unref(rcd->data_records); g_free(rcd); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCcgxDmcFirmwareRecord, fu_ccgx_dmc_firmware_record_free) G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCcgxDmcFirmwareSegmentRecord, fu_ccgx_dmc_firmware_segment_record_free) GPtrArray * fu_ccgx_dmc_firmware_get_image_records(FuCcgxDmcFirmware *self) { g_return_val_if_fail(FU_IS_CCGX_DMC_FIRMWARE(self), NULL); return self->image_records; } GBytes * fu_ccgx_dmc_firmware_get_fwct_record(FuCcgxDmcFirmware *self) { g_return_val_if_fail(FU_IS_CCGX_DMC_FIRMWARE(self), NULL); return self->fwct_blob; } GBytes * fu_ccgx_dmc_firmware_get_custom_meta_record(FuCcgxDmcFirmware *self) { g_return_val_if_fail(FU_IS_CCGX_DMC_FIRMWARE(self), NULL); return self->custom_meta_blob; } guint32 fu_ccgx_dmc_firmware_get_fw_data_size(FuCcgxDmcFirmware *self) { g_return_val_if_fail(FU_IS_CCGX_DMC_FIRMWARE(self), 0); return self->fw_data_size; } static void fu_ccgx_dmc_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(firmware); if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) { fu_xmlb_builder_insert_kx(bn, "fw_data_size", self->fw_data_size); fu_xmlb_builder_insert_kx(bn, "image_records", self->image_records->len); } } static gboolean fu_ccgx_dmc_firmware_parse_segment(FuFirmware *firmware, const guint8 *buf, gsize bufsz, FuCcgxDmcFirmwareRecord *img_rcd, gsize *seg_off, FwupdInstallFlags flags, GError **error) { FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(firmware); gsize row_off; g_autoptr(GChecksum) csum = g_checksum_new(G_CHECKSUM_SHA256); /* set row data offset in current image */ row_off = self->row_data_offset_start + img_rcd->img_offset; /* parse segment in image */ img_rcd->seg_records = g_ptr_array_new_with_free_func((GFreeFunc)fu_ccgx_dmc_firmware_segment_record_free); for (guint32 i = 0; i < img_rcd->num_img_segments; i++) { guint16 row_size_bytes = 0; g_autofree guint8 *row_buf = NULL; g_autoptr(FuCcgxDmcFirmwareSegmentRecord) seg_rcd = NULL; g_autoptr(GByteArray) st_info = NULL; /* read segment info */ seg_rcd = g_new0(FuCcgxDmcFirmwareSegmentRecord, 1); st_info = fu_struct_ccgx_dmc_fwct_segmentation_info_parse(buf, bufsz, *seg_off, error); if (st_info == NULL) return FALSE; seg_rcd->start_row = fu_struct_ccgx_dmc_fwct_segmentation_info_get_start_row(st_info); seg_rcd->num_rows = fu_struct_ccgx_dmc_fwct_segmentation_info_get_num_rows(st_info); /* calculate actual row size */ row_size_bytes = img_rcd->row_size * 64; /* create data record array in segment record */ seg_rcd->data_records = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); /* read row data in segment */ row_buf = g_malloc0(row_size_bytes); for (int row = 0; row < seg_rcd->num_rows; row++) { g_autoptr(GBytes) data_rcd = NULL; /* read row data */ if (!fu_memcpy_safe(row_buf, row_size_bytes, 0x0, /* dst */ buf, bufsz, row_off, /* src */ row_size_bytes, error)) { g_prefix_error(error, "failed to read row data: "); return FALSE; } /* update hash */ g_checksum_update(csum, (guchar *)row_buf, row_size_bytes); /* add row data to data record */ data_rcd = g_bytes_new(row_buf, row_size_bytes); g_ptr_array_add(seg_rcd->data_records, g_steal_pointer(&data_rcd)); /* increment row data offset */ row_off += row_size_bytes; } /* add segment record to segment array */ g_ptr_array_add(img_rcd->seg_records, g_steal_pointer(&seg_rcd)); /* increment segment info offset */ *seg_off += st_info->len; } /* check checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint8 csumbuf[DMC_HASH_SIZE] = {0x0}; gsize csumbufsz = sizeof(csumbuf); g_checksum_get_digest(csum, csumbuf, &csumbufsz); if (memcmp(csumbuf, img_rcd->img_digest, DMC_HASH_SIZE) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid hash"); return FALSE; } } /* success */ return TRUE; } static gboolean fu_ccgx_dmc_firmware_parse_image(FuFirmware *firmware, guint8 image_count, const guint8 *buf, gsize bufsz, FwupdInstallFlags flags, GError **error) { FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(firmware); gsize img_off = FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE; gsize seg_off = FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE + image_count * FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE; /* set initial segment info offset */ for (guint32 i = 0; i < image_count; i++) { gsize img_digestsz = 0; const guint8 *img_digest; g_autoptr(FuCcgxDmcFirmwareRecord) img_rcd = NULL; g_autoptr(GByteArray) st_img = NULL; /* read image info */ img_rcd = g_new0(FuCcgxDmcFirmwareRecord, 1); st_img = fu_struct_ccgx_dmc_fwct_image_info_parse(buf, bufsz, img_off, error); if (st_img == NULL) return FALSE; img_rcd->row_size = fu_struct_ccgx_dmc_fwct_image_info_get_row_size(st_img); if (img_rcd->row_size == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid row size 0x%x", img_rcd->row_size); return FALSE; } img_rcd->img_offset = fu_struct_ccgx_dmc_fwct_image_info_get_img_offset(st_img); img_rcd->num_img_segments = fu_struct_ccgx_dmc_fwct_image_info_get_num_img_segments(st_img); if (img_rcd->num_img_segments == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid segment number = %d", img_rcd->num_img_segments); return FALSE; } img_digest = fu_struct_ccgx_dmc_fwct_image_info_get_img_digest(st_img, &img_digestsz); if (!fu_memcpy_safe((guint8 *)&img_rcd->img_digest, sizeof(img_rcd->img_digest), 0x0, /* dst */ img_digest, img_digestsz, 0, /* src */ img_digestsz, error)) return FALSE; /* parse segment */ if (!fu_ccgx_dmc_firmware_parse_segment(firmware, buf, bufsz, img_rcd, &seg_off, flags, error)) return FALSE; /* add image record to image record array */ g_ptr_array_add(self->image_records, g_steal_pointer(&img_rcd)); /* increment image offset */ img_off += FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE; } return TRUE; } static gboolean fu_ccgx_dmc_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_ccgx_dmc_fwct_info_validate_bytes(fw, offset, error); } static gboolean fu_ccgx_dmc_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(firmware); gsize bufsz = 0; guint16 hdr_size = 0; guint16 mdbufsz = 0; guint32 hdr_composite_version = 0; guint8 hdr_image_count = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(FuFirmware) img = fu_firmware_new_from_bytes(fw); g_autoptr(GByteArray) st_hdr = NULL; /* parse */ st_hdr = fu_struct_ccgx_dmc_fwct_info_parse(buf, bufsz, offset, error); if (st_hdr == NULL) return FALSE; /* check fwct size */ hdr_size = fu_struct_ccgx_dmc_fwct_info_get_size(st_hdr); if (hdr_size > DMC_FWCT_MAX_SIZE || hdr_size == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid dmc fwct size, expected <= 0x%x, got 0x%x", (guint)DMC_FWCT_MAX_SIZE, (guint)hdr_size); return FALSE; } /* set version */ hdr_composite_version = fu_struct_ccgx_dmc_fwct_info_get_composite_version(st_hdr); if (hdr_composite_version != 0) { g_autofree gchar *ver = NULL; ver = fu_version_from_uint32(hdr_composite_version, FWUPD_VERSION_FORMAT_QUAD); fu_firmware_set_version(firmware, ver); fu_firmware_set_version_raw(firmware, hdr_composite_version); } /* read fwct data */ self->fwct_blob = fu_bytes_new_offset(fw, offset, hdr_size, error); if (self->fwct_blob == NULL) return FALSE; /* create custom meta binary */ if (!fu_memread_uint16_safe(buf, bufsz, offset + hdr_size, &mdbufsz, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read metadata size: "); return FALSE; } if (mdbufsz > 0) { self->custom_meta_blob = fu_bytes_new_offset(fw, offset + hdr_size + 2, mdbufsz, error); if (self->custom_meta_blob == NULL) return FALSE; } /* set row data start offset */ self->row_data_offset_start = hdr_size + DMC_CUSTOM_META_LENGTH_FIELD_SIZE + mdbufsz; self->fw_data_size = bufsz - self->row_data_offset_start; /* parse image */ hdr_image_count = fu_struct_ccgx_dmc_fwct_info_get_image_count(st_hdr); if (!fu_ccgx_dmc_firmware_parse_image(firmware, hdr_image_count, buf, bufsz, flags, error)) return FALSE; /* add something, although we'll use the records for the update */ fu_firmware_set_addr(img, 0x0); fu_firmware_add_image(firmware, img); return TRUE; } static GByteArray * fu_ccgx_dmc_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) st_hdr = fu_struct_ccgx_dmc_fwct_info_new(); g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); /* add header */ fu_struct_ccgx_dmc_fwct_info_set_size( st_hdr, FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE + (images->len * (FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE + FU_STRUCT_CCGX_DMC_FWCT_SEGMENTATION_INFO_SIZE))); fu_struct_ccgx_dmc_fwct_info_set_version(st_hdr, 0x2); fu_struct_ccgx_dmc_fwct_info_set_custom_meta_type(st_hdr, 0x3); fu_struct_ccgx_dmc_fwct_info_set_cdtt_version(st_hdr, 0x1); fu_struct_ccgx_dmc_fwct_info_set_device_id(st_hdr, 0x1); fu_struct_ccgx_dmc_fwct_info_set_composite_version(st_hdr, fu_firmware_get_version_raw(firmware)); fu_struct_ccgx_dmc_fwct_info_set_image_count(st_hdr, images->len); g_byte_array_append(buf, st_hdr->data, st_hdr->len); /* add image headers */ for (guint i = 0; i < images->len; i++) { g_autoptr(GByteArray) st_img = fu_struct_ccgx_dmc_fwct_image_info_new(); fu_struct_ccgx_dmc_fwct_image_info_set_device_type(st_img, 0x2); fu_struct_ccgx_dmc_fwct_image_info_set_img_type(st_img, 0x1); fu_struct_ccgx_dmc_fwct_image_info_set_row_size(st_img, 0x1); fu_struct_ccgx_dmc_fwct_image_info_set_fw_version(st_img, 0x330006d2); fu_struct_ccgx_dmc_fwct_image_info_set_app_version(st_img, 0x14136161); fu_struct_ccgx_dmc_fwct_image_info_set_num_img_segments(st_img, 0x1); g_byte_array_append(buf, st_img->data, st_img->len); } /* add segments */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GByteArray) st_info = fu_struct_ccgx_dmc_fwct_segmentation_info_new(); g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GBytes) img_bytes = fu_firmware_get_bytes(img, error); if (img_bytes == NULL) return NULL; chunks = fu_chunk_array_new_from_bytes(img_bytes, 0x0, 64); fu_struct_ccgx_dmc_fwct_segmentation_info_set_num_rows( st_info, MAX(fu_chunk_array_length(chunks), 1)); g_byte_array_append(buf, st_info->data, st_info->len); } /* metadata */ fu_byte_array_append_uint16(buf, 0x1, G_LITTLE_ENDIAN); fu_byte_array_append_uint8(buf, 0xff); /* add image headers */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); gsize csumbufsz = DMC_HASH_SIZE; gsize img_offset = FU_STRUCT_CCGX_DMC_FWCT_INFO_SIZE + (i * FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_SIZE); guint8 csumbuf[DMC_HASH_SIZE] = {0x0}; g_autoptr(GChecksum) csum = g_checksum_new(G_CHECKSUM_SHA256); g_autoptr(GBytes) img_bytes = NULL; g_autoptr(GBytes) img_padded = NULL; g_autoptr(FuChunkArray) chunks = NULL; img_bytes = fu_firmware_get_bytes(img, error); if (img_bytes == NULL) return NULL; chunks = fu_chunk_array_new_from_bytes(img_bytes, 0x0, 64); img_padded = fu_bytes_pad(img_bytes, MAX(fu_chunk_array_length(chunks), 1) * 64); fu_byte_array_append_bytes(buf, img_padded); g_checksum_update(csum, (const guchar *)g_bytes_get_data(img_padded, NULL), g_bytes_get_size(img_padded)); g_checksum_get_digest(csum, csumbuf, &csumbufsz); /* update checksum */ if (!fu_memcpy_safe(buf->data, buf->len, /* dst */ img_offset + FU_STRUCT_CCGX_DMC_FWCT_IMAGE_INFO_OFFSET_IMG_DIGEST, csumbuf, sizeof(csumbuf), 0x0, /* src */ sizeof(csumbuf), error)) return NULL; } return g_steal_pointer(&buf); } static void fu_ccgx_dmc_firmware_init(FuCcgxDmcFirmware *self) { self->image_records = g_ptr_array_new_with_free_func((GFreeFunc)fu_ccgx_dmc_firmware_record_free); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); } static void fu_ccgx_dmc_firmware_finalize(GObject *object) { FuCcgxDmcFirmware *self = FU_CCGX_DMC_FIRMWARE(object); if (self->fwct_blob != NULL) g_bytes_unref(self->fwct_blob); if (self->custom_meta_blob != NULL) g_bytes_unref(self->custom_meta_blob); if (self->image_records != NULL) g_ptr_array_unref(self->image_records); G_OBJECT_CLASS(fu_ccgx_dmc_firmware_parent_class)->finalize(object); } static void fu_ccgx_dmc_firmware_class_init(FuCcgxDmcFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_ccgx_dmc_firmware_finalize; klass_firmware->check_magic = fu_ccgx_dmc_firmware_check_magic; klass_firmware->parse = fu_ccgx_dmc_firmware_parse; klass_firmware->write = fu_ccgx_dmc_firmware_write; klass_firmware->export = fu_ccgx_dmc_firmware_export; } FuFirmware * fu_ccgx_dmc_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_CCGX_DMC_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/ccgx-dmc/fu-ccgx-dmc-firmware.h000066400000000000000000000017401460375044200221720ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CCGX_DMC_FIRMWARE (fu_ccgx_dmc_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuCcgxDmcFirmware, fu_ccgx_dmc_firmware, FU, CCGX_DMC_FIRMWARE, FuFirmware) typedef struct { guint16 start_row; guint16 num_rows; GPtrArray *data_records; } FuCcgxDmcFirmwareSegmentRecord; typedef struct { guint8 row_size; guint32 img_offset; guint8 img_digest[32]; guint8 num_img_segments; GPtrArray *seg_records; } FuCcgxDmcFirmwareRecord; FuFirmware * fu_ccgx_dmc_firmware_new(void); GPtrArray * fu_ccgx_dmc_firmware_get_image_records(FuCcgxDmcFirmware *self); GBytes * fu_ccgx_dmc_firmware_get_fwct_record(FuCcgxDmcFirmware *self); GBytes * fu_ccgx_dmc_firmware_get_custom_meta_record(FuCcgxDmcFirmware *self); guint32 fu_ccgx_dmc_firmware_get_fw_data_size(FuCcgxDmcFirmware *self); fwupd-1.9.16/plugins/ccgx-dmc/fu-ccgx-dmc-plugin.c000066400000000000000000000017501460375044200216500ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ccgx-dmc-device.h" #include "fu-ccgx-dmc-firmware.h" #include "fu-ccgx-dmc-plugin.h" struct _FuCcgxDmcPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuCcgxDmcPlugin, fu_ccgx_dmc_plugin, FU_TYPE_PLUGIN) static void fu_ccgx_dmc_plugin_init(FuCcgxDmcPlugin *self) { } static void fu_ccgx_dmc_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "CcgxDmcTriggerCode"); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_CCGX_DMC_FIRMWARE); fu_plugin_add_device_gtype(plugin, FU_TYPE_CCGX_DMC_DEVICE); } static void fu_ccgx_dmc_plugin_class_init(FuCcgxDmcPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_ccgx_dmc_plugin_constructed; } fwupd-1.9.16/plugins/ccgx-dmc/fu-ccgx-dmc-plugin.h000066400000000000000000000003551460375044200216550ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuCcgxDmcPlugin, fu_ccgx_dmc_plugin, FU, CCGX_DMC_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/ccgx-dmc/fu-ccgx-dmc.rs000066400000000000000000000143171460375044200205610ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ enum CcgxDmcImgType { Invalid = 0, Image0, Image1, } #[derive(ToString)] enum CcgxDmcImgStatus { Unknown = 0, Valid, Invalid, Recovery, RecoveredFromSecondary, NotSupported = 0x0F, } // flash architecture #[derive(ToString)] #[repr(u8)] enum CcgxDmcImgMode { // indicates that the device has a single image SingleImg = 0, // the device supports symmetric boot. In symmetric mode the bootloader // boots the image with higher version, when they are valid DualImgSym, // the device supports Asymmetric boot. Image-1 & 2 can be different or // same. in this method Bootloader is hard coded to boot the primary // image. Secondary acts as recovery DualImgAsym, SingleImgWithRamImg, Last, } // dock status #[derive(ToString)] #[repr(u8)] enum CcgxDmcDeviceStatus { // status code indicating DOCK IDLE state. SUCCESS: no malfunctioning // no outstanding request or event Idle = 0, // status code indicating dock FW update in progress UpdateInProgress, // status code indicating dock FW update is partially complete UpdatePartial, // status code indicating dock FW update SUCCESS - all m_images of all // devices are valid UpdateCompleteFull, // status code indicating dock FW update SUCCESS - not all m_images of all // devices are valid UpdateCompletePartial, // fw download status UpdatePhase1Complete, FwDownloadedUpdatePend, FwDownloadedPartialUpdatePend, Phase2UpdateInProgress = 0x81, Phase2UpdatePartial, Phase2UpdateFactoryBackup, Phase2UpdateCompletePartial, Phase2UpdateCompleteFull, Phase2UpdateFailInvalidFwct, Phase2UpdateFailInvalidDockIdentity, Phase2UpdateFailInvalidCompositeVer, Phase2UpdateFailAuthenticationFailed, Phase2UpdateFailInvalidAlgorithm, Phase2UpdateFailSpiReadFailed, Phase2UpdateFailNoValidKey, Phase2UpdateFailNoValidSpiPackage, Phase2UpdateFailRamInitFailed, Phase2UpdateFailFactoryBackupFailed, Phase2UpdateFailNoValidFactoryPackage, // status code indicating dock FW update FAILED UpdateFail = 0xff, } #[derive(ToString)] #[repr(u8)] enum CcgxDmcDevxDeviceType { Invalid = 0x00, Ccg3 = 0x01, Dmc = 0x02, Ccg4 = 0x03, Ccg5 = 0x04, Hx3 = 0x05, Hx3Pd = 0x0A, DmcPd = 0x0B, Spi = 0xFF, } // request codes for vendor interface enum CcgxDmcRqtCode { UpgradeStart = 0xD0, Reserv0, FwctWrite, ImgWrite, Reserv1, Reserv2, DockStatus, DockIdentity, ResetStateMachine, // command to reset dmc state machine of DMC SoftReset = 0xDC, // command to reset for online enhanced mode (no reset during update) Trigger = 0xDA, // Update Trigger command for offline mode } // opcode of interrupt read #[derive(ToString)] #[repr(u8)] enum CcgxDmcIntOpcode { FwUpgradeRqt = 1, FwUpgradeStatus = 0x80, ImgWriteStatus, Reenum, FwctAnalysisStatus, } // fwct analysis status #[derive(ToString)] enum CcgxDmcFwctAnalysisStatus { InvalidFwct = 0, InvalidDockIdentity, InvalidCompositeVersion, AuthenticationFailed, InvalidAlgorithm, } #[derive(ToString)] enum CcgxDmcUpdateModel { None = 0, DownloadTrigger, // need to trigger after updating FW PendingReset, // need to set soft reset after updating FW } // fields of data returned when reading dock_identity for new firmware #[derive(New, Getters)] struct CcgxDmcDockIdentity { // this field indicates both validity and structure version // 0 : invalid // 1 : old structure // 2 : new structure structure_version: u8, cdtt_version: u8, vid: u16le, pid: u16le, device_id: u16le, vendor_string: [char; 32], product_string: [char; 32], custom_meta_data_flag: u8, // model field indicates the type of the firmware upgrade status // 0 - online/offline // 1 - Online model // 2 - ADICORA/Offline model // 3 - No reset // 4 - 0xFF - Reserved model: u8, } // fields of status of a specific device #[derive(Parse)] struct CcgxDmcDevxStatus { // device ID of the device device_type: CcgxDmcDevxDeviceType, // component ID of the device component_id: u8, // image mode of the device - single image/ dual symmetric/ dual // asymmetric image > image_mode: CcgxDmcImgMode, // current running image current_image: u8, // image status // b7:b4 => Image 2 status // b3:b0 => Image 1 status // 0 = Unknown // 1 = Valid // 2 = Invalid // 3-0xF = Reserved img_status: u8, // padding _reserved0: [u8; 3], // complete fw version 8 bytes for bootload, image1 and image2. 8 byte // for fw version and application version fw_version: [u8; 24], } // fields of data returned when reading dock_status #[derive(New, Getters)] struct CcgxDmcDockStatus { device_status: CcgxDmcDeviceStatus, device_count: u8, status_length: u16le, // including dock_status, devx_status for each device composite_version: u32le, // dock composite version m_fwct_info // CcgxDmcDevxStatus devx_status[DMC_DOCK_MAX_DEV_COUNT], } // fields of data returned when reading an interrupt from DMC #[derive(New, Getters)] struct CcgxDmcIntRqt { opcode: CcgxDmcIntOpcode, length: u8, data: [u8; 8], } // header structure of FWCT #[derive(New, Parse, ValidateBytes)] struct CcgxDmcFwctInfo { signature: u32le == 0x54435746, // 'F' 'W' 'C' 'T' size: u16le, checksum: u8, version: u8, custom_meta_type: u8, cdtt_version: u8, vid: u16le, pid: u16le, device_id: u16le, _reserv0: [u8; 16], composite_version: u32le, image_count: u8, _reserv1: [u8; 3], } #[derive(New, Parse)] struct CcgxDmcFwctImageInfo { device_type: u8, img_type: u8, comp_id: u8, row_size: u8, _reserv0: [u8; 4], fw_version: u32le, app_version: u32le, img_offset: u32le, img_size: u32le, img_digest: [u8; 32], num_img_segments: u8, _reserv1: [u8; 3], } #[derive(New, Parse)] struct CcgxDmcFwctSegmentationInfo { img_id: u8, type: u8, start_row: u16le, num_rows: u16le, // size _reserv0: [u8; 2], } fwupd-1.9.16/plugins/ccgx-dmc/fu-self-test.c000066400000000000000000000033671460375044200206030ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ccgx-dmc-firmware.h" static void fu_ccgx_dmc_firmware_xml_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *csum1 = NULL; g_autofree gchar *csum2 = NULL; g_autofree gchar *xml_out = NULL; g_autofree gchar *xml_src = NULL; g_autoptr(FuFirmware) firmware1 = fu_ccgx_dmc_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_ccgx_dmc_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "ccgx-dmc.builder.xml", NULL); ret = g_file_get_contents(filename, &xml_src, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_build_from_xml(firmware1, xml_src, &error); g_assert_no_error(error); g_assert_true(ret); csum1 = fu_firmware_get_checksum(firmware1, G_CHECKSUM_SHA1, &error); g_assert_no_error(error); g_assert_cmpstr(csum1, ==, "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"); /* ensure we can round-trip */ xml_out = fu_firmware_export_to_xml(firmware1, FU_FIRMWARE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); ret = fu_firmware_build_from_xml(firmware2, xml_out, &error); g_assert_no_error(error); g_assert_true(ret); csum2 = fu_firmware_get_checksum(firmware2, G_CHECKSUM_SHA1, &error); g_assert_cmpstr(csum1, ==, csum2); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func("/ccgx-dmc/firmware{xml}", fu_ccgx_dmc_firmware_xml_func); return g_test_run(); } fwupd-1.9.16/plugins/ccgx-dmc/meson.build000066400000000000000000000025471460375044200202620ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginCcgxDmc"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files(['ccgx-dmc.quirk']) plugin_builtin_ccgx_dmc = static_library('fu_plugin_ccgx_dmc', rustgen.process( 'fu-ccgx-dmc.rs', # fuzzing ), sources: [ 'fu-ccgx-dmc-plugin.c', 'fu-ccgx-dmc-device.c', 'fu-ccgx-dmc-devx-device.c', 'fu-ccgx-dmc-firmware.c', # fuzzing ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: [ plugin_deps, gudev, ], ) plugin_builtins += plugin_builtin_ccgx_dmc if get_option('tests') install_data(['tests/ccgx-dmc.builder.xml'], install_dir: join_paths(installed_test_datadir, 'tests')) env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'ccgx-dmc-self-test', rustgen.process('fu-ccgx-dmc.rs'), sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_ccgx_dmc, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('ccgx-dmc-self-test', e, env: env) endif endif fwupd-1.9.16/plugins/ccgx-dmc/tests/000077500000000000000000000000001460375044200172525ustar00rootroot00000000000000fwupd-1.9.16/plugins/ccgx-dmc/tests/ccgx-dmc.bin000066400000000000000000000000531460375044200214270ustar00rootroot00000000000000FWCT(fwupd-1.9.16/plugins/ccgx-dmc/tests/ccgx-dmc.builder.xml000066400000000000000000000001701460375044200231040ustar00rootroot00000000000000 0x1000800 aGVsbG8gd29ybGQ= fwupd-1.9.16/plugins/ccgx/000077500000000000000000000000001460375044200153475ustar00rootroot00000000000000fwupd-1.9.16/plugins/ccgx/README.md000066400000000000000000000064361460375044200166370ustar00rootroot00000000000000--- title: Plugin: CCGX --- ## Introduction This plugin can flash firmware on Infineon (previously Cypress) CCGx USB-C controller family of devices used in docks. ## Supported Protocols This plugin supports the following protocol IDs: * `com.cypress.ccgx` (deprecated) * `com.infineon.ccgx` ## Device Flash There are four kinds of flash layout. Single image firmware is not currently supported in this plugin. ### Symmetric Firmware In symmetric firmware topology, FW1 and FW2 are both primary (main) firmware with identical sizes and functionality. We can only update FW1 from FW2 or FW2 from FW1. This does mean we need to update just one time as booting from either firmware slot gives a fully functional device. After updating the "other" firmware we can just use `CY_PD_DEVICE_RESET_CMD_SIG` to reboot into the new firmware, and no further action is required. ### Asymmetric Firmware In asymmetric firmware topology, FW1 is backup and FW2 is primary (main) firmware with different firmware sizes. The backup firmware may not support all dock functionality. To update primary, we thus need to update twice: Case 1: FW2 is running * Update FW1 -> Jump to backup FW `CY_PD_JUMP_TO_ALT_FW_CMD_SIG` -> reboot * Update FW2 -> Reset device `CY_PD_DEVICE_RESET_CMD_SIG` -> reboot -> FW2 Case 2: FW1 is running (recovery case) * Update FW2 -> Reset device `CY_PD_DEVICE_RESET_CMD_SIG` -> reboot -> FW2 The `CY_PD_JUMP_TO_ALT_FW_CMD_SIG` command is allowed only in asymmetric FW, but `CY_PD_DEVICE_RESET_CMD_SIG` is allowed in both asymmetric FW and symmetric FW. ## Firmware Format ### Cyacd firmware format The daemon will decompress the cabinet archive and extract several firmware blobs in cyacd file format. See for more details. ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_1234&PID_5678` Devices also have additional instance IDs which corresponds to the silicon ID, application ID and device mode, e.g. * `USB\VID_1234&PID_5678&SID_9ABC` * `USB\VID_1234&PID_5678&SID_9ABC&APP_DEF1` * `USB\VID_1234&PID_5678&SID_9ABC&APP_DEF1&MODE_FW2` ## Update Behavior The device usually presents in runtime HID mode, but on detach re-enumerates with with the HPI interface. On attach the device again re-enumerates back to the runtime HID mode. For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the HPI and runtime modes are treated as the same device. ## Vendor ID Security The vendor ID is set from the USB vendor, for example set to `USB:0x04B4` ## Quirk Use This plugin uses the following plugin-specific quirks: ### CcgxFlashRowSize Set the size of the flash row in bytes, as a 32 bit integer. Since: 1.4.0 ### CcgxFlashSize Set the maximum flash size, as a 32 bit integer. Since: 1.4.0 ### CcgxImageKind Set the image kind from one of: * `unknown` * `single` * `dual-symmetric` * `dual-asymmetric` * `dual-asymmetric-variable` Since: 1.4.0 ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.4.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Ryan Lee: @chlee75 fwupd-1.9.16/plugins/ccgx/ccgx-ids.quirk000066400000000000000000000132651460375044200201340ustar00rootroot00000000000000# CCG2 - CYPD2103-20FNXI [CCGX\SID_1400] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x8000 # CCG2 - CYPD2104-20FNXI [CCGX\SID_1401] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x8000 # CCG2 - CYPD2105-20FNXI [CCGX\SID_1402] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x8000 # CCG2 - CYPD2103-14LHXI [CCGX\SID_1403] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x8000 # CCG2 - CYPD2122-24LQXI [CCGX\SID_1404] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x8000 # CCG2 - CYPD2134-24LQXI [CCGX\SID_1405] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x8000 # CCG2 - CYPD2122-20FNXI [CCGX\SID_1406] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x8000 # CCG2 - CYPD2123-24LQXI [CCGX\SID_1407] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x8000 # CCG2 - CYPD2124-24LQXI [CCGX\SID_1408] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x8000 # CCG2 - CYPD2119-24LQXI [CCGX\SID_1409] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x8000 # CCG2 - CYPD2121-24LQXI [CCGX\SID_1410] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x8000 # CCG2 - CYPD2125-24LQXI [CCGX\SID_1411] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x8000 # CCG2 - CYPD2120-24LQXI [CCGX\SID_1412] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x8000 # CCG3 - CYPD3120-40LQXI [CCGX\SID_1D00] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x20000 # CCG3 - CYPD3105-42FNXI [CCGX\SID_1D01] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x20000 # CCG3 - CYPD3121-40LQXI [CCGX\SID_1D02] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x20000 # CCG3 - CYPD3122-40LQXI [CCGX\SID_1D03] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x20000 # CCG3 - CYPD3125-40LQXI [CCGX\SID_1D04] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x20000 # CCG3 - CYPD3135-40LQXI [CCGX\SID_1D05] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x20000 # CCG3 - CYPD3135-16SXQ' [CCGX\SID_1D06] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x20000 # CCG3 - CYPD3126-42FNXI [CCGX\SID_1D07] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x20000 # CCG3 - CYPD3123-40LQXI [CCGX\SID_1D09] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x20000 # CCG4 - CYPD4225-40LQXI [CCGX\SID_1800] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG4 - CYPD4125-40LQXI [CCGX\SID_1801] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG4 - CYPD4235-40LQXI [CCGX\SID_1802] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG4 - CYPD4135-40LQXI [CCGX\SID_1803] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG4 - CYPD4225A0-33FNXIT [CCGX\SID_1810] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG4 - CYPD4226-40LQXI [CCGX\SID_1F00] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG4 - CYPD4126-40LQXI [CCGX\SID_1F01] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG4 - CYPD4126-24LQXI [CCGX\SID_1F04] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG4 - CYPD4236-40LQXI [CCGX\SID_1F02] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG4 - CYPD4136-40LQXI [CCGX\SID_1F03] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG4 - CYPD4136-24LQXI [CCGX\SID_1F05] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG3PA - CYPD3174-24LQXQ [CCGX\SID_2000] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x10000 # CCG3PA - CYPD3174-16SXQ [CCGX\SID_2001] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x10000 # CCG3PA - CYPD3175-24LQXQ [CCGX\SID_2002] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x10000 # CCG3PA - CYPD3171-24LQXQ [CCGX\SID_2003] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x10000 # CCG3PA - CYPD3195-24LDXS [CCGX\SID_2005] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x10000 # CCG3PA - CYPD3196-24LDXS [CCGX\SID_2006] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x10000 # CCG3PA - CYPD3197-24LDXS [CCGX\SID_2007] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x10000 # CCG3PA2 - CYPDC1185-32LQXQ [CCGX\SID_2400] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x20000 # CCG3PA2 - CYPDC1186-30FNXI [CCGX\SID_2401] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x20000 # CCG3PA2 - CYPDC1186B2-30FNXI [CCGX\SID_2402] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x20000 # CCG5 - CYPD5225-96BZXI [CCGX\SID_2100] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG5 - CYPD5125-40LQXI [CCGX\SID_2101] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG5 - CYPD5235-96BZXI [CCGX\SID_2102] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG5 - CYPD5236-96BZXI [CCGX\SID_2103] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG5 - CYPD5237-96BZXI [CCGX\SID_2104] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG5 - CYPD5227-96BZXI [CCGX\SID_2105] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG5 - CYPD5135-40LQXI [CCGX\SID_2106] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG6 - CYPD6125-40LQXI [CCGX\SID_2A00] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG6 - CYPD6126-96BZXI [CCGX\SID_2A10] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG6 - CYPD5126-40LQXI [CCGX\SID_2A01] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG6 - CYPD5137-40LQXI [CCGX\SID_2A02] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # CCG6 - CYPD6137-40LQXI [CCGX\SID_2A03] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # PAG1S - CYPAS111-24LQXQ [CCGX\SID_2B01] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x10000 # PAG1S - CYPD3184-24LQXQ [CCGX\SID_2B00] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x10000 # HX3PD - CYUSB4347-BZXC_PD [CCGX\SID_1F82] CcgxFlashRowSize = 0x100 CcgxFlashSize = 0x20000 # ACG1F - CYAC1126-24LQXI [CCGX\SID_2F00] CcgxFlashRowSize = 0x40 CcgxFlashSize = 0x4000 # ACG1F - CYAC1126-40LQXI [CCGX\SID_2F01] CcgxFlashRowSize = 0x40 CcgxFlashSize = 0x4000 # CCG6DF - CYPD6227-96BZXI [CCGX\SID_3000] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x10000 # CCG6DF - CYPD6127-96BZXI [CCGX\SID_3001] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x10000 # CCG6SF - CYPD6128-96BZXI [CCGX\SID_3300] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x10000 # CCG6SF - CYPD6127-48LQXI [CCGX\SID_3301] CcgxFlashRowSize = 0x80 CcgxFlashSize = 0x10000 fwupd-1.9.16/plugins/ccgx/ccgx.quirk000066400000000000000000000025401460375044200173510ustar00rootroot00000000000000# Lenovo ThinkPad USB-C Dock Gen2 [USB\VID_17EF&PID_A38F] Plugin = ccgx GType = FuCcgxHidDevice ParentGuid = USB\VID_17EF&PID_A391 [USB\VID_04B4&PID_521A] Plugin = ccgx GType = FuCcgxHpiDevice [USB\VID_04B4&PID_521A&SID_1F00&APP_6D64] CcgxImageKind = dual-asymmetric Name = ThinkPad USB-C Dock Gen2 PD Controller ParentGuid = USB\VID_17EF&PID_A391 InstallDuration = 120 RemoveDelay = 60000 [USB\VID_04B4&PID_521A&SID_1F00&APP_6D64&MODE_FW1] Summary = CCGx Power Delivery Device (Bootloader) Flags = is-bootloader [USB\VID_04B4&PID_521A&SID_1F00&APP_6D64&MODE_FW2] Summary = CCGx Power Delivery Device CounterpartGuid = USB\VID_04B4&PID_521A&SID_1F00&APP_6D64&MODE_FW1 # Lenovo ThinkPad USB-C Dock Hybrid [USB\VID_17EF&PID_A354] Plugin = ccgx GType = FuCcgxHidDevice ParentGuid = USB\VID_17EF&PID_1028 [USB\VID_17EF&PID_A35F] Plugin = ccgx GType = FuCcgxHidDevice ParentGuid = USB\VID_17EF&PID_1028 [USB\VID_04B4&PID_5218] Plugin = ccgx GType = FuCcgxHpiDevice [USB\VID_04B4&PID_5218&SID_1F00&APP_6432] CcgxImageKind = dual-symmetric Name = ThinkPad USB-C Dock Hybrid PD Controller ParentGuid = USB\VID_17EF&PID_1028 InstallDuration = 120 RemoveDelay = 60000 [USB\VID_04B4&PID_5218&SID_1F00&APP_6432&MODE_FW1] Summary = CCGx Power Delivery Device (Symmetric FW1) [USB\VID_04B4&PID_5218&SID_1F00&APP_6432&MODE_FW2] Summary = CCGx Power Delivery Device (Symmetric FW2) fwupd-1.9.16/plugins/ccgx/fu-ccgx-common.c000066400000000000000000000012531460375044200203360ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ccgx-common.h" gchar * fu_ccgx_version_to_string(guint32 val) { /* 16 bits: application type [LSB] * 8 bits: build number * 4 bits: minor version * 4 bits: major version [MSB] */ return g_strdup_printf("%u.%u.%u", (val >> 28) & 0x0f, (val >> 24) & 0x0f, (val >> 16) & 0xff); } FuCcgxFwMode fu_ccgx_fw_mode_get_alternate(FuCcgxFwMode val) { if (val == FU_CCGX_FW_MODE_FW1) return FU_CCGX_FW_MODE_FW2; if (val == FU_CCGX_FW_MODE_FW2) return FU_CCGX_FW_MODE_FW1; return FU_CCGX_FW_MODE_BOOT; } fwupd-1.9.16/plugins/ccgx/fu-ccgx-common.h000066400000000000000000000004351460375044200203440ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-ccgx-struct.h" gchar * fu_ccgx_version_to_string(guint32 val); FuCcgxFwMode fu_ccgx_fw_mode_get_alternate(FuCcgxFwMode val); fwupd-1.9.16/plugins/ccgx/fu-ccgx-firmware.c000066400000000000000000000346121460375044200206670ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ccgx-common.h" #include "fu-ccgx-firmware.h" struct _FuCcgxFirmware { FuFirmwareClass parent_instance; GPtrArray *records; guint16 app_type; guint16 silicon_id; FuCcgxFwMode fw_mode; }; G_DEFINE_TYPE(FuCcgxFirmware, fu_ccgx_firmware, FU_TYPE_FIRMWARE) /* offset stored application version for CCGx */ #define CCGX_APP_VERSION_OFFSET 228 /* 128+64+32+4 */ #define FU_CCGX_FIRMWARE_TOKENS_MAX 100000 /* lines */ GPtrArray * fu_ccgx_firmware_get_records(FuCcgxFirmware *self) { g_return_val_if_fail(FU_IS_CCGX_FIRMWARE(self), NULL); return self->records; } guint16 fu_ccgx_firmware_get_app_type(FuCcgxFirmware *self) { g_return_val_if_fail(FU_IS_CCGX_FIRMWARE(self), 0); return self->app_type; } guint16 fu_ccgx_firmware_get_silicon_id(FuCcgxFirmware *self) { g_return_val_if_fail(FU_IS_CCGX_FIRMWARE(self), 0); return self->silicon_id; } FuCcgxFwMode fu_ccgx_firmware_get_fw_mode(FuCcgxFirmware *self) { g_return_val_if_fail(FU_IS_CCGX_FIRMWARE(self), 0); return self->fw_mode; } static void fu_ccgx_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuCcgxFirmware *self = FU_CCGX_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "silicon_id", self->silicon_id); if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) { fu_xmlb_builder_insert_kx(bn, "app_type", self->app_type); fu_xmlb_builder_insert_kx(bn, "records", self->records->len); fu_xmlb_builder_insert_kv(bn, "fw_mode", fu_ccgx_fw_mode_to_string(self->fw_mode)); } } static void fu_ccgx_firmware_record_free(FuCcgxFirmwareRecord *rcd) { if (rcd->data != NULL) g_bytes_unref(rcd->data); g_free(rcd); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCcgxFirmwareRecord, fu_ccgx_firmware_record_free) static gboolean fu_ccgx_firmware_add_record(FuCcgxFirmware *self, GString *token, FwupdInstallFlags flags, GError **error) { guint16 buflen; guint8 checksum_calc = 0; g_autoptr(FuCcgxFirmwareRecord) rcd = NULL; g_autoptr(GByteArray) data = g_byte_array_new(); /* this is not in the specification, but exists in reality */ if (token->str[0] == ':') g_string_erase(token, 0, 1); /* parse according to https://community.cypress.com/docs/DOC-10562 */ rcd = g_new0(FuCcgxFirmwareRecord, 1); if (!fu_firmware_strparse_uint8_safe(token->str, token->len, 0, &rcd->array_id, error)) return FALSE; if (!fu_firmware_strparse_uint16_safe(token->str, token->len, 2, &rcd->row_number, error)) return FALSE; if (!fu_firmware_strparse_uint16_safe(token->str, token->len, 6, &buflen, error)) return FALSE; if (token->len != ((gsize)buflen * 2) + 12) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid record, expected %u chars, got %u", (guint)(buflen * 2) + 12, (guint)token->len); return FALSE; } /* parse payload, adding checksum */ for (guint i = 0; i < buflen; i++) { guint8 tmp = 0; if (!fu_firmware_strparse_uint8_safe(token->str, token->len, 10 + (i * 2), &tmp, error)) return FALSE; fu_byte_array_append_uint8(data, tmp); checksum_calc += tmp; } rcd->data = g_bytes_new(data->data, data->len); /* verify 2s complement checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint8 checksum_file; if (!fu_firmware_strparse_uint8_safe(token->str, token->len, (buflen * 2) + 10, &checksum_file, error)) return FALSE; for (guint i = 0; i < 5; i++) { guint8 tmp = 0; if (!fu_firmware_strparse_uint8_safe(token->str, token->len, i * 2, &tmp, error)) return FALSE; checksum_calc += tmp; } checksum_calc = 1 + ~checksum_calc; if (checksum_file != checksum_calc) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum invalid, got %02x, expected %02x", checksum_calc, checksum_file); return FALSE; } } /* success */ g_ptr_array_add(self->records, g_steal_pointer(&rcd)); return TRUE; } static gboolean fu_ccgx_firmware_parse_md_block(FuCcgxFirmware *self, FwupdInstallFlags flags, GError **error) { FuCcgxFirmwareRecord *rcd; gsize bufsz = 0; gsize md_offset = 0; guint32 fw_size = 0; guint32 rcd_version_idx = 0; guint32 version = 0; guint8 checksum_calc = 0; g_autoptr(GByteArray) st_metadata = NULL; /* sanity check */ if (self->records->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no records added to image"); return FALSE; } /* read metadata from correct offset */ rcd = g_ptr_array_index(self->records, self->records->len - 1); bufsz = g_bytes_get_size(rcd->data); if (bufsz == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid buffer size"); return FALSE; } switch (bufsz) { case 0x80: md_offset = 0x40; break; case 0x100: md_offset = 0xC0; break; default: break; } /* parse */ st_metadata = fu_struct_ccgx_metadata_hdr_parse_bytes(rcd->data, md_offset, error); if (st_metadata == NULL) return FALSE; if (fu_struct_ccgx_metadata_hdr_get_metadata_valid(st_metadata) != FU_STRUCT_CCGX_METADATA_HDR_DEFAULT_METADATA_VALID) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid metadata 0x@%x, expected 0x%04x, got 0x%04x", (guint)md_offset, (guint)FU_STRUCT_CCGX_METADATA_HDR_DEFAULT_METADATA_VALID, (guint)fu_struct_ccgx_metadata_hdr_get_metadata_valid(st_metadata)); return FALSE; } for (guint i = 0; i < self->records->len - 1; i++) { rcd = g_ptr_array_index(self->records, i); checksum_calc += fu_sum8_bytes(rcd->data); fw_size += g_bytes_get_size(rcd->data); } if (fw_size != fu_struct_ccgx_metadata_hdr_get_fw_size(st_metadata)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware size invalid, got %02x, expected %02x", fw_size, fu_struct_ccgx_metadata_hdr_get_fw_size(st_metadata)); return FALSE; } checksum_calc = 1 + ~checksum_calc; if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { if (fu_struct_ccgx_metadata_hdr_get_fw_checksum(st_metadata) != checksum_calc) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum invalid, got %02x, expected %02x", checksum_calc, fu_struct_ccgx_metadata_hdr_get_fw_checksum(st_metadata)); return FALSE; } } /* get version if enough data */ rcd_version_idx = CCGX_APP_VERSION_OFFSET / bufsz; if (rcd_version_idx < self->records->len) { g_autofree gchar *version_str = NULL; const guint8 *buf; rcd = g_ptr_array_index(self->records, rcd_version_idx); buf = g_bytes_get_data(rcd->data, &bufsz); if (bufsz == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "metadata record had zero size"); return FALSE; } if (!fu_memread_uint32_safe(buf, bufsz, CCGX_APP_VERSION_OFFSET % bufsz, &version, G_LITTLE_ENDIAN, error)) return FALSE; self->app_type = version & 0xffff; version_str = fu_ccgx_version_to_string(version); fu_firmware_set_version(FU_FIRMWARE(self), version_str); fu_firmware_set_version_raw(FU_FIRMWARE(self), version); } /* work out the FuCcgxFwMode */ if (self->records->len > 0) { rcd = g_ptr_array_index(self->records, self->records->len - 1); if ((rcd->row_number & 0xFF) == 0xFF) /* last row */ self->fw_mode = FU_CCGX_FW_MODE_FW1; if ((rcd->row_number & 0xFF) == 0xFE) /* penultimate row */ self->fw_mode = FU_CCGX_FW_MODE_FW2; } return TRUE; } typedef struct { FuCcgxFirmware *self; FwupdInstallFlags flags; } FuCcgxFirmwareTokenHelper; static gboolean fu_ccgx_firmware_tokenize_cb(GString *token, guint token_idx, gpointer user_data, GError **error) { FuCcgxFirmwareTokenHelper *helper = (FuCcgxFirmwareTokenHelper *)user_data; FuCcgxFirmware *self = FU_CCGX_FIRMWARE(helper->self); /* sanity check */ if (token_idx > FU_CCGX_FIRMWARE_TOKENS_MAX) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "file has too many lines"); return FALSE; } /* remove WIN32 line endings */ g_strdelimit(token->str, "\r\x1a", '\0'); token->len = strlen(token->str); /* header */ if (token_idx == 0) { guint32 device_id = 0; if (token->len != 12) { g_autofree gchar *strsafe = fu_strsafe(token->str, 12); if (strsafe != NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid header, expected == 12 chars -- got %s", strsafe); return FALSE; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid header, expected == 12 chars"); return FALSE; } if (!fu_firmware_strparse_uint32_safe(token->str, token->len, 0, &device_id, error)) return FALSE; self->silicon_id = device_id >> 16; return TRUE; } /* ignore blank lines */ if (token->len == 0) return TRUE; /* parse record */ if (!fu_ccgx_firmware_add_record(self, token, helper->flags, error)) { g_prefix_error(error, "error on line %u: ", token_idx + 1); return FALSE; } /* success */ return TRUE; } static gboolean fu_ccgx_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuCcgxFirmware *self = FU_CCGX_FIRMWARE(firmware); FuCcgxFirmwareTokenHelper helper = {.self = self, .flags = flags}; /* tokenize */ if (!fu_strsplit_full(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), "\n", fu_ccgx_firmware_tokenize_cb, &helper, error)) return FALSE; /* address is first data entry */ if (self->records->len > 0) { FuCcgxFirmwareRecord *rcd = g_ptr_array_index(self->records, 0); fu_firmware_set_addr(firmware, rcd->row_number); } /* parse metadata block */ if (!fu_ccgx_firmware_parse_md_block(self, flags, error)) { g_prefix_error(error, "failed to parse metadata: "); return FALSE; } /* success */ return TRUE; } static void fu_ccgx_firmware_write_record(GString *str, guint8 array_id, guint8 row_number, const guint8 *buf, guint16 bufsz) { guint8 checksum_calc = 0xff; g_autoptr(GString) datastr = g_string_new(NULL); /* offset for bootloader perhaps? */ row_number += 0xE; checksum_calc += array_id; checksum_calc += row_number; checksum_calc += bufsz & 0xff; checksum_calc += (bufsz >> 8) & 0xff; for (guint j = 0; j < bufsz; j++) { g_string_append_printf(datastr, "%02X", buf[j]); checksum_calc += buf[j]; } g_string_append_printf(str, ":%02X%04X%04X%s%02X\n", array_id, row_number, bufsz, datastr->str, (guint)((guint8)~checksum_calc)); } static GByteArray * fu_ccgx_firmware_write(FuFirmware *firmware, GError **error) { FuCcgxFirmware *self = FU_CCGX_FIRMWARE(firmware); gsize fwbufsz = 0; guint8 checksum_img = 0xff; const guint8 *fwbuf; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) mdbuf = g_byte_array_new(); g_autoptr(GByteArray) st_metadata = fu_struct_ccgx_metadata_hdr_new(); g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GString) str = g_string_new(NULL); /* header record */ g_string_append_printf(str, "%04X%04X%02X%02X\n", self->silicon_id, (guint)0x11AF, /* SiliconID */ (guint)0x0, /* SiliconRev */ (guint)0x0); /* Checksum, or 0x0 */ /* add image in chunks */ fw = fu_firmware_get_bytes_with_patches(firmware, error); if (fw == NULL) return NULL; chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x100); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); fu_ccgx_firmware_write_record(str, 0x0, i, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); } /* add metadata */ fwbuf = g_bytes_get_data(fw, &fwbufsz); for (guint j = 0; j < fwbufsz; j++) checksum_img += fwbuf[j]; /* copy into place */ fu_byte_array_set_size(mdbuf, 0x80, 0x00); fu_struct_ccgx_metadata_hdr_set_fw_checksum(st_metadata, ~checksum_img); fu_struct_ccgx_metadata_hdr_set_fw_entry(st_metadata, 0x0); /* unknown */ fu_struct_ccgx_metadata_hdr_set_last_boot_row(st_metadata, 0x13); fu_struct_ccgx_metadata_hdr_set_fw_size(st_metadata, fwbufsz); fu_struct_ccgx_metadata_hdr_set_boot_seq(st_metadata, 0x0); /* unknown */ if (!fu_memcpy_safe(mdbuf->data, mdbuf->len, 0x40, /* dst */ st_metadata->data, st_metadata->len, 0x0, /* src */ st_metadata->len, error)) return NULL; fu_ccgx_firmware_write_record(str, 0x0, 0xFE, /* FW2: penultimate row */ mdbuf->data, mdbuf->len); /* success */ g_byte_array_append(buf, (const guint8 *)str->str, str->len); return g_steal_pointer(&buf); } static gboolean fu_ccgx_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuCcgxFirmware *self = FU_CCGX_FIRMWARE(firmware); guint64 tmp; /* optional properties */ tmp = xb_node_query_text_as_uint(n, "silicon_id", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) self->silicon_id = tmp; /* success */ return TRUE; } static void fu_ccgx_firmware_init(FuCcgxFirmware *self) { self->records = g_ptr_array_new_with_free_func((GFreeFunc)fu_ccgx_firmware_record_free); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); } static void fu_ccgx_firmware_finalize(GObject *object) { FuCcgxFirmware *self = FU_CCGX_FIRMWARE(object); g_ptr_array_unref(self->records); G_OBJECT_CLASS(fu_ccgx_firmware_parent_class)->finalize(object); } static void fu_ccgx_firmware_class_init(FuCcgxFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_ccgx_firmware_finalize; klass_firmware->parse = fu_ccgx_firmware_parse; klass_firmware->write = fu_ccgx_firmware_write; klass_firmware->build = fu_ccgx_firmware_build; klass_firmware->export = fu_ccgx_firmware_export; } FuFirmware * fu_ccgx_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_CCGX_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/ccgx/fu-ccgx-firmware.h000066400000000000000000000014031460375044200206640ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-ccgx-common.h" #define FU_TYPE_CCGX_FIRMWARE (fu_ccgx_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuCcgxFirmware, fu_ccgx_firmware, FU, CCGX_FIRMWARE, FuFirmware) typedef struct { guint8 array_id; guint16 row_number; GBytes *data; } FuCcgxFirmwareRecord; FuFirmware * fu_ccgx_firmware_new(void); GPtrArray * fu_ccgx_firmware_get_records(FuCcgxFirmware *self); guint16 fu_ccgx_firmware_get_app_type(FuCcgxFirmware *self); guint16 fu_ccgx_firmware_get_silicon_id(FuCcgxFirmware *self); FuCcgxFwMode fu_ccgx_firmware_get_fw_mode(FuCcgxFirmware *self); fwupd-1.9.16/plugins/ccgx/fu-ccgx-hid-device.c000066400000000000000000000065671460375044200210640ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ccgx-hid-device.h" struct _FuCcgxHidDevice { FuHidDevice parent_instance; }; G_DEFINE_TYPE(FuCcgxHidDevice, fu_ccgx_hid_device, FU_TYPE_HID_DEVICE) #define FU_CCGX_HID_DEVICE_TIMEOUT 5000 /* ms */ #define FU_CCGX_HID_DEVICE_RETRY_DELAY 30 /* ms */ #define FU_CCGX_HID_DEVICE_RETRY_CNT 5 static gboolean fu_ccgx_hid_device_enable_hpi_mode_cb(FuDevice *device, gpointer user_data, GError **error) { guint8 buf[5] = {0xEE, 0xBC, 0xA6, 0xB9, 0xA8}; if (!fu_hid_device_set_report(FU_HID_DEVICE(device), buf[0], buf, sizeof(buf), FU_CCGX_HID_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "switch to HPI mode error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_hid_device_detach(FuDevice *device, FuProgress *progress, GError **error) { if (!fu_device_retry(device, fu_ccgx_hid_device_enable_hpi_mode_cb, FU_CCGX_HID_DEVICE_RETRY_CNT, NULL, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_ccgx_hid_device_setup(FuDevice *device, GError **error) { /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_ccgx_hid_device_parent_class)->setup(device, error)) return FALSE; /* This seems insane... but we need to switch the device from HID * mode to HPI mode at startup. The device continues to function * exactly as before and no user-visible effects are noted */ if (!fu_device_retry(device, fu_ccgx_hid_device_enable_hpi_mode_cb, FU_CCGX_HID_DEVICE_RETRY_CNT, NULL, error)) return FALSE; /* never add this device, the daemon does not expect the device to * disconnect before it is added */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device is replugging into HPI mode"); return FALSE; } static void fu_ccgx_hid_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_ccgx_hid_device_init(FuCcgxHidDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.cypress.ccgx"); fu_device_add_protocol(FU_DEVICE(self), "com.infineon.ccgx"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_WILL_DISAPPEAR); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_retry_set_delay(FU_DEVICE(self), FU_CCGX_HID_DEVICE_RETRY_DELAY); } static void fu_ccgx_hid_device_class_init(FuCcgxHidDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->detach = fu_ccgx_hid_device_detach; klass_device->setup = fu_ccgx_hid_device_setup; klass_device->set_progress = fu_ccgx_hid_device_set_progress; } fwupd-1.9.16/plugins/ccgx/fu-ccgx-hid-device.h000066400000000000000000000005511460375044200210540ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CCGX_HID_DEVICE (fu_ccgx_hid_device_get_type()) G_DECLARE_FINAL_TYPE(FuCcgxHidDevice, fu_ccgx_hid_device, FU, CCGX_HID_DEVICE, FuHidDevice) fwupd-1.9.16/plugins/ccgx/fu-ccgx-hpi-common.h000066400000000000000000000303121460375044200211170ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define I2C_READ_WRITE_DELAY_MS 10 /* ms */ #define CY_SCB_INDEX_POS 15 #define CY_I2C_WRITE_COMMAND_POS 3 #define CY_I2C_WRITE_COMMAND_LEN_POS 4 #define CY_I2C_GET_STATUS_LEN 3 #define CY_I2C_MODE_WRITE 1 #define CY_I2C_MODE_READ 0 #define CY_I2C_ERROR_BIT 1 #define CY_I2C_ARBITRATION_ERROR_BIT (1 << 1) #define CY_I2C_NAK_ERROR_BIT (1 << 2) #define CY_I2C_BUS_ERROR_BIT (1 << 3) #define CY_I2C_STOP_BIT_ERROR (1 << 4) #define CY_I2C_BUS_BUSY_ERROR (1 << 5) #define CY_I2C_ENABLE_PRECISE_TIMING 1 #define CY_I2C_EVENT_NOTIFICATION_LEN 3 #define PD_I2C_TARGET_ADDRESS 0x08 /* timeout (ms) for USB I2C communication */ #define FU_CCGX_HPI_WAIT_TIMEOUT 5000 /* max i2c frequency */ #define FU_CCGX_HPI_FREQ 400000 typedef enum { CY_GET_VERSION_CMD = 0xB0, /* get the version of the boot-loader * value = 0, index = 0, length = 4; * data_in = 32 bit version */ CY_GET_SIGNATURE_CMD = 0xBD, /* get the signature of the firmware * It is suppose to be 'CYUS' for normal firmware * and 'CYBL' for Bootloader */ CY_UART_GET_CONFIG_CMD = 0xC0, /* retrieve the 16 byte UART configuration information * MS bit of value indicates the SCB index * length = 16, data_in = 16 byte configuration */ CY_UART_SET_CONFIG_CMD, /* update the 16 byte UART configuration information * MS bit of value indicates the SCB index. * length = 16, data_out = 16 byte configuration information */ CY_SPI_GET_CONFIG_CMD, /* retrieve the 16 byte SPI configuration information * MS bit of value indicates the SCB index * length = 16, data_in = 16 byte configuration */ CY_SPI_SET_CONFIG_CMD, /* update the 16 byte SPI configuration information * MS bit of value indicates the SCB index * length = 16, data_out = 16 byte configuration information */ CY_I2C_GET_CONFIG_CMD, /* retrieve the 16 byte I2C configuration information * MS bit of value indicates the SCB index * length = 16, data_in = 16 byte configuration */ CY_I2C_SET_CONFIG_CMD = 0xC5, /* update the 16 byte I2C configuration information * MS bit of value indicates the SCB index * length = 16, data_out = 16 byte configuration information */ CY_I2C_WRITE_CMD, /* perform I2C write operation * value = bit0 - start, bit1 - stop, bit3 - start on idle, * bits[14:8] - target address, bit15 - scbIndex. length = 0 the * data is provided over the bulk endpoints */ CY_I2C_READ_CMD, /* perform I2C read operation. * value = bit0 - start, bit1 - stop, bit2 - Nak last byte, * bit3 - start on idle, bits[14:8] - target address, bit15 - scbIndex, * length = 0. The data is provided over the bulk endpoints */ CY_I2C_GET_STATUS_CMD, /* retrieve the I2C bus status. * value = bit0 - 0: TX 1: RX, bit15 - scbIndex, length = 3, * data_in = byte0: bit0 - flag, bit1 - bus_state, bit2 - SDA state, * bit3 - TX underflow, bit4 - arbitration error, bit5 - NAK * bit6 - bus error, * byte[2:1] Data count remaining */ CY_I2C_RESET_CMD, /* the command cleans up the I2C state machine and frees the bus * value = bit0 - 0: TX path, 1: RX path; bit15 - scbIndex, * length = 0 */ CY_SPI_READ_WRITE_CMD = 0xCA, /* the command starts a read / write operation at SPI * value = bit 0 - RX enable, bit 1 - TX enable, bit 15 - * scbIndex; index = length of transfer */ CY_SPI_RESET_CMD, /* the command resets the SPI pipes and allows it to receive new * request * value = bit 15 - scbIndex */ CY_SPI_GET_STATUS_CMD, /* the command returns the current transfer status * the count will match the TX pipe status at SPI end * for completion of read, read all data * at the USB end signifies the end of transfer * value = bit 15 - scbIndex */ CY_JTAG_ENABLE_CMD = 0xD0, /* enable JTAG module */ CY_JTAG_DISABLE_CMD, /* disable JTAG module */ CY_JTAG_READ_CMD, /* jtag read vendor command */ CY_JTAG_WRITE_CMD, /* jtag write vendor command */ CY_GPIO_GET_CONFIG_CMD = 0xD8, /* get the GPIO configuration */ CY_GPIO_SET_CONFIG_CMD, /* set the GPIO configuration */ CY_GPIO_GET_VALUE_CMD, /* get GPIO value */ CY_GPIO_SET_VALUE_CMD, /* set the GPIO value */ CY_PROG_USER_FLASH_CMD = 0xE0, /* program user flash area. The total space available is 512 * bytes this can be accessed by the user from USB. The flash * area address offset is from 0x0000 to 0x00200 and can be * written to page wise (128 byte) */ CY_READ_USER_FLASH_CMD, /* read user flash area. The total space available is 512 bytes * this can be accessed by the user from USB. The flash area * address offset is from 0x0000 to 0x00200 and can be written to * page wise (128 byte) */ CY_DEVICE_RESET_CMD = 0xE3, /* performs a device reset from firmware */ } CyVendorCommand; typedef struct __attribute__((packed)) { guint32 frequency; /* frequency of operation. Only valid values are 100KHz and 400KHz */ guint8 target_address; /* target address to be used when in target mode */ guint8 is_msb_first; /* whether to transmit most significant bit first */ guint8 is_initiator; /* whether to block is to be configured as a initiator */ guint8 s_ignore; /* ignore general call in target mode */ guint8 is_clock_stretch; /* whether to stretch clock in case of no FIFO availability */ guint8 is_loop_back; /* whether to loop back TX data to RX. Valid only for debug purposes */ guint8 reserved[6]; } CyI2CConfig; typedef enum { CY_I2C_DATA_CONFIG_NONE = 0, CY_I2C_DATA_CONFIG_STOP = 1 << 0, CY_I2C_DATA_CONFIG_NAK = 1 << 1, /* only for read */ } CyI2CDataConfigBits; typedef enum { HPI_DEV_REG_DEVICE_MODE = 0, HPI_DEV_REG_BOOT_MODE_REASON, HPI_DEV_REG_SI_ID, HPI_DEV_REG_SI_ID_LSB, HPI_DEV_REG_BL_LAST_ROW, HPI_DEV_REG_BL_LAST_ROW_LSB, HPI_DEV_REG_INTR_ADDR, HPI_DEV_REG_JUMP_TO_BOOT, HPI_DEV_REG_RESET_ADDR, HPI_DEV_REG_RESET_CMD, HPI_DEV_REG_ENTER_FLASH_MODE, HPI_DEV_REG_VALIDATE_FW_ADDR, HPI_DEV_REG_FLASH_READ_WRITE, HPI_DEV_REG_FLASH_READ_WRITE_CMD, HPI_DEV_REG_FLASH_ROW, HPI_DEV_REG_FLASH_ROW_LSB, HPI_DEV_REG_ALL_VERSION, HPI_DEV_REG_ALL_VERSION_BYTE_1, HPI_DEV_REG_ALL_VERSION_BYTE_2, HPI_DEV_REG_ALL_VERSION_BYTE_3, HPI_DEV_REG_ALL_VERSION_BYTE_4, HPI_DEV_REG_ALL_VERSION_BYTE_5, HPI_DEV_REG_ALL_VERSION_BYTE_6, HPI_DEV_REG_ALL_VERSION_BYTE_7, HPI_DEV_REG_ALL_VERSION_BYTE_8, HPI_DEV_REG_ALL_VERSION_BYTE_9, HPI_DEV_REG_ALL_VERSION_BYTE_10, HPI_DEV_REG_ALL_VERSION_BYTE_11, HPI_DEV_REG_ALL_VERSION_BYTE_12, HPI_DEV_REG_ALL_VERSION_BYTE_13, HPI_DEV_REG_ALL_VERSION_BYTE_14, HPI_DEV_REG_ALL_VERSION_BYTE_15, HPI_DEV_REG_FW_2_VERSION, HPI_DEV_REG_FW_2_VERSION_BYTE_1, HPI_DEV_REG_FW_2_VERSION_BYTE_2, HPI_DEV_REG_FW_2_VERSION_BYTE_3, HPI_DEV_REG_FW_2_VERSION_BYTE_4, HPI_DEV_REG_FW_2_VERSION_BYTE_5, HPI_DEV_REG_FW_2_VERSION_BYTE_6, HPI_DEV_REG_FW_2_VERSION_BYTE_7, HPI_DEV_REG_FW_BIN_LOC, HPI_DEV_REG_FW_1_BIN_LOC_LSB, HPI_DEV_REG_FW_2_BIN_LOC_MSB, HPI_DEV_REG_FW_2_BIN_LOC_LSB, HPI_DEV_REG_PORT_ENABLE, HPI_DEV_SPACE_REG_LEN, HPI_DEV_REG_RESPONSE = 0x007E, HPI_DEV_REG_FLASH_MEM = 0x0200 } HPIDevReg; typedef enum { HPI_REG_SECTION_DEV = 0, /* device information */ HPI_REG_SECTION_PORT_0, /* USB-PD Port 0 related */ HPI_REG_SECTION_PORT_1, /* USB-PD Port 1 related */ HPI_REG_SECTION_ALL /* select all registers */ } HPIRegSection; typedef struct __attribute__((packed)) { guint16 event_code; guint16 event_length; guint8 event_data[128]; } HPIEvent; typedef enum { HPI_REG_PART_REG = 0, /* register region */ HPI_REG_PART_DATA = 1, /* data memory */ HPI_REG_PART_FLASH = 2, /* flash memory */ HPI_REG_PART_PDDATA_READ = 4, /* read data memory */ HPI_REG_PART_PDDATA_WRITE = 8, /* write data memory */ } HPIRegPart; typedef enum { FU_CCGX_PD_RESP_REG_DEVICE_MODE_ADDR, FU_CCGX_PD_RESP_BOOT_MODE_REASON, FU_CCGX_PD_RESP_SILICON_ID, FU_CCGX_PD_RESP_BL_LAST_ROW = 0x04, FU_CCGX_PD_RESP_REG_INTR_REG_ADDR = 0x06, FU_CCGX_PD_RESP_JUMP_TO_BOOT_REG_ADDR, FU_CCGX_PD_RESP_REG_RESET_ADDR, FU_CCGX_PD_RESP_REG_ENTER_FLASH_MODE_ADDR = 0x0A, FU_CCGX_PD_RESP_REG_VALIDATE_FW_ADDR, FU_CCGX_PD_RESP_REG_FLASH_READ_WRITE_ADDR, FU_CCGX_PD_RESP_GET_VERSION = 0x10, FU_CCGX_PD_RESP_REG_DBG_PD_INIT = 0x12, FU_CCGX_PD_RESP_REG_U_VDM_CTRL_ADDR = 0x20, FU_CCGX_PD_RESP_REG_READ_PD_PROFILE = 0x22, FU_CCGX_PD_RESP_REG_EFFECTIVE_SOURCE_PDO_MASK = 0x24, FU_CCGX_PD_RESP_REG_EFFECTIVE_SINK_PDO_MASK, FU_CCGX_PD_RESP_REG_SELECT_SOURCE_PDO, FU_CCGX_PD_RESP_REG_SELECT_SINK_PDO, FU_CCGX_PD_RESP_REG_PD_CONTROL, FU_CCGX_PD_RESP_REG_PD_STATUS = 0x2C, FU_CCGX_PD_RESP_REG_TYPE_C_STATUS = 0x30, FU_CCGX_PD_RESP_REG_CURRENT_PDO = 0x34, FU_CCGX_PD_RESP_REG_CURRENT_RDO = 0x38, FU_CCGX_PD_RESP_REG_CURRENT_CABLE_VDO = 0x3C, FU_CCGX_PD_RESP_REG_DISPLAY_PORT_STATUS = 0x40, FU_CCGX_PD_RESP_REG_DISPLAY_PORT_CONFIG = 0x44, FU_CCGX_PD_RESP_REG_ALTERNATE_MODE_MUX_SELECTION = 0X45, FU_CCGX_PD_RESP_REG_EVENT_MASK = 0x48, FU_CCGX_PD_RESP_REG_RESPONSE_ADDR = 0x7E, FU_CCGX_PD_RESP_REG_BOOTDATA_MEMORY_ADDR = 0x80, FU_CCGX_PD_RESP_REG_FWDATA_MEMORY_ADDR = 0xC0, } CyPDReg; #define FU_CCGX_PD_RESP_GET_SILICON_ID_CMD_SIG 0x53 #define FU_CCGX_PD_RESP_REG_INTR_REG_CLEAR_RQT 0x01 #define FU_CCGX_PD_RESP_JUMP_TO_BOOT_CMD_SIG 0x4A #define FU_CCGX_PD_RESP_JUMP_TO_ALT_FW_CMD_SIG 0x41 #define FU_CCGX_PD_RESP_DEVICE_RESET_CMD_SIG 0x52 #define FU_CCGX_PD_RESP_REG_RESET_DEVICE_CMD 0x01 #define FU_CCGX_PD_RESP_ENTER_FLASHING_MODE_CMD_SIG 0x50 #define FU_CCGX_PD_RESP_FLASH_READ_WRITE_CMD_SIG 0x46 #define FU_CCGX_PD_RESP_REG_FLASH_ROW_READ_CMD 0x00 #define FU_CCGX_PD_RESP_REG_FLASH_ROW_WRITE_CMD 0x01 #define FU_CCGX_PD_RESP_REG_FLASH_READ_WRITE_ROW_LSB 0x02 #define FU_CCGX_PD_RESP_REG_FLASH_READ_WRITE_ROW_MSB 0x03 #define FU_CCGX_PD_RESP_U_VDM_TYPE 0x00 #define HPI_GET_SILICON_ID_CMD_SIG 0x53 #define HPI_REG_INTR_REG_CLEAR_RQT 0x01 #define HPI_JUMP_TO_BOOT_CMD_SIG 0x4A #define HPI_DEVICE_RESET_CMD_SIG 0x52 #define HPI_REG_RESET_DEVICE_CMD 0x01 #define HPI_ENTER_FLASHING_MODE_CMD_SIG 0x50 #define HPI_FLASH_READ_WRITE_CMD_SIG 0x46 #define HPI_REG_FLASH_ROW_READ_CMD 0x00 #define HPI_REG_FLASH_ROW_WRITE_CMD 0x01 #define HPI_REG_FLASH_READ_WRITE_ROW_LSB 0x02 #define HPI_REG_FLASH_READ_WRITE_ROW_MSB 0x03 #define HPI_PORT_DISABLE_CMD 0x11 #define HPI_DEVICE_VERSION_SIZE_HPIV1 16 #define HPI_DEVICE_VERSION_SIZE_HPIV2 24 #define HPI_META_DATA_OFFSET_ROW_128 64 #define HPI_META_DATA_OFFSET_ROW_256 (64 + 128) #define PD_I2C_USB_EP_BULK_OUT 0x01 #define PD_I2C_USB_EP_BULK_IN 0x82 #define PD_I2C_USB_EP_INTR_IN 0x83 #define PD_I2CM_USB_EP_BULK_OUT 0x02 #define PD_I2CM_USB_EP_BULK_IN 0x83 #define PD_I2CM_USB_EP_INTR_IN 0x84 typedef enum { HPI_RESPONSE_NO_RESPONSE, HPI_RESPONSE_SUCCESS = 0x02, HPI_RESPONSE_FLASH_DATA_AVAILABLE, HPI_RESPONSE_INVALID_COMMAND = 0x05, HPI_RESPONSE_FLASH_UPDATE_FAILED = 0x07, HPI_RESPONSE_INVALID_FW, HPI_RESPONSE_INVALID_ARGUMENT, HPI_RESPONSE_NOT_SUPPORTED, HPI_RESPONSE_PD_TRANSACTION_FAILED = 0x0C, HPI_RESPONSE_PD_COMMAND_FAILED, HPI_RESPONSE_UNDEFINED_ERROR = 0x0F, HPI_EVENT_RESET_COMPLETE = 0x80, HPI_EVENT_MSG_OVERFLOW, HPI_EVENT_OC_DETECT, HPI_EVENT_OV_DETECT, HPI_EVENT_CONNECT_DETECT, HPI_EVENT_DISCONNECT_DETECT, HPI_EVENT_NEGOTIATION_COMPLETE, HPI_EVENT_SWAP_COMPLETE, HPI_EVENT_PS_RDY_RECEIVED = 0x8A, HPI_EVENT_GOTO_MIN_RECEIVED, HPI_EVENT_ACCEPT_RECEIVED, HPI_EVENT_REJECT_RECEIVED, HPI_EVENT_WAIT_RECEIVED, HPI_EVENT_HARD_RESET_RECEIVED, HPI_EVENT_VDM_RECEIVED = 0x90, HPI_EVENT_SOURCE_CAP_RECEIVED, HPI_EVENT_SINK_CAP_RECEIVED, HPI_EVENT_DP_MODE_ENTERED, HPI_EVENT_DP_STATUS_UPDATE, HPI_EVENT_DP_SID_NOT_FOUND = 0x96, HPI_EVENT_DP_MANY_SID_FOUND, HPI_EVENT_DP_NO_CABLE_SUPPORT, HPI_EVENT_DP_NO_UFP_SUPPORT, HPI_EVENT_HARD_RESET_SENT, HPI_EVENT_SOFT_RESET_SENT, HPI_EVENT_CABLE_RESET_SENT, HPI_EVENT_SOURCE_DISABLED, HPI_EVENT_SENDER_TIMEOUT, HPI_EVENT_VDM_NO_RESPONSE, HPI_EVENT_UNEXPECTED_VOLTAGE, HPI_EVENT_ERROR_RECOVERY, HPI_EVENT_EMCA_DETECT = 0xA6, HPI_EVENT_RP_CHANGE_DETECT = 0xAA, HPI_EVENT_TB_ENTERED = 0xB0, HPI_EVENT_TB_EXITED } HPIResp; fwupd-1.9.16/plugins/ccgx/fu-ccgx-hpi-device.c000066400000000000000000001332721460375044200210720ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ccgx-common.h" #include "fu-ccgx-firmware.h" #include "fu-ccgx-hpi-common.h" #include "fu-ccgx-hpi-device.h" #include "fu-ccgx-struct.h" struct _FuCcgxHpiDevice { FuUsbDevice parent_instance; guint8 inf_num; /* USB interface number */ guint8 scb_index; guint16 silicon_id; guint16 fw_app_type; guint8 hpi_addrsz; /* hpiv1: 1 byte, hpiv2: 2 byte */ guint8 num_ports; /* max number of ports */ FuCcgxFwMode fw_mode; FuCcgxImageType fw_image_type; guint8 target_address; guint8 ep_bulk_in; guint8 ep_bulk_out; guint8 ep_intr_in; guint32 flash_row_size; guint32 flash_size; }; /** * FU_CCGX_HPI_DEVICE_IS_IN_RESTART: * * Device is in restart and should not be closed manually. * * Since: 1.7.0 */ #define FU_CCGX_HPI_DEVICE_IS_IN_RESTART (1 << 0) G_DEFINE_TYPE(FuCcgxHpiDevice, fu_ccgx_hpi_device, FU_TYPE_USB_DEVICE) #define HPI_CMD_REG_READ_WRITE_DELAY_MS 10 #define HPI_CMD_ENTER_FLASH_MODE_DELAY_MS 20 #define HPI_CMD_SETUP_EVENT_WAIT_TIME_MS 200 #define HPI_CMD_SETUP_EVENT_CLEAR_TIME_MS 150 #define HPI_CMD_COMMAND_RESPONSE_TIME_MS 500 #define HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS 30 #define HPI_CMD_RESET_COMPLETE_DELAY_MS 150 #define HPI_CMD_RETRY_DELAY 30 /* ms */ #define HPI_CMD_RESET_RETRY_CNT 3 #define HPI_CMD_ENTER_LEAVE_FLASH_MODE_RETRY_CNT 3 #define HPI_CMD_FLASH_WRITE_RETRY_CNT 3 #define HPI_CMD_FLASH_READ_RETRY_CNT 3 #define HPI_CMD_VALIDATE_FW_RETRY_CNT 3 static void fu_ccgx_hpi_device_to_string(FuDevice *device, guint idt, GString *str) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); fu_string_append_kx(str, idt, "ScbIndex", self->scb_index); fu_string_append_kx(str, idt, "SiliconId", self->silicon_id); fu_string_append_kx(str, idt, "FwAppType", self->fw_app_type); fu_string_append_kx(str, idt, "HpiAddrsz", self->hpi_addrsz); fu_string_append_kx(str, idt, "NumPorts", self->num_ports); fu_string_append(str, idt, "FuCcgxFwMode", fu_ccgx_fw_mode_to_string(self->fw_mode)); fu_string_append(str, idt, "FwImageType", fu_ccgx_image_type_to_string(self->fw_image_type)); fu_string_append_kx(str, idt, "EpBulkIn", self->ep_bulk_in); fu_string_append_kx(str, idt, "EpBulkOut", self->ep_bulk_out); fu_string_append_kx(str, idt, "EpIntrIn", self->ep_intr_in); if (self->flash_row_size > 0) fu_string_append_kx(str, idt, "CcgxFlashRowSize", self->flash_row_size); if (self->flash_size > 0) fu_string_append_kx(str, idt, "CcgxFlashSize", self->flash_size); } typedef struct { guint8 mode; guint16 addr; guint8 *buf; gsize bufsz; } FuCcgxHpiDeviceRetryHelper; typedef struct { guint16 addr; const guint8 *buf; gsize bufsz; } FuCcgxHpiFlashWriteRetryHelper; typedef struct { guint16 addr; guint8 *buf; gsize bufsz; } FuCcgxHpiFlashReadRetryHelper; static gboolean fu_ccgx_hpi_device_i2c_reset_cb(FuDevice *device, gpointer user_data, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); FuCcgxHpiDeviceRetryHelper *helper = (FuCcgxHpiDeviceRetryHelper *)user_data; g_autoptr(GError) error_local = NULL; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_RESET_CMD, (self->scb_index << CY_SCB_INDEX_POS) | helper->mode, 0x0, NULL, 0x0, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to reset i2c: %s", error_local->message); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_check_i2c_status(FuCcgxHpiDevice *self, guint8 mode, GError **error) { guint8 buf[CY_I2C_GET_STATUS_LEN] = {0x0}; g_autoptr(GError) error_local = NULL; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_GET_STATUS_CMD, (((guint16)self->scb_index) << CY_SCB_INDEX_POS) | mode, 0x0, buf, sizeof(buf), NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get i2c status: %s", error_local->message); return FALSE; } if (buf[0] & CY_I2C_ERROR_BIT) { if (buf[0] & 0x80) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "i2c status write error: 0x%x", buf[0]); return FALSE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "i2c status read error: 0x%x", buf[0]); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_get_i2c_config(FuCcgxHpiDevice *self, CyI2CConfig *i2c_config, GError **error) { g_autoptr(GError) error_local = NULL; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_GET_CONFIG_CMD, ((guint16)self->scb_index) << CY_SCB_INDEX_POS, 0x0, (guint8 *)i2c_config, sizeof(*i2c_config), NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "i2c get config error: control xfer: %s", error_local->message); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_set_i2c_config(FuCcgxHpiDevice *self, CyI2CConfig *i2c_config, GError **error) { g_autoptr(GError) error_local = NULL; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_SET_CONFIG_CMD, ((guint16)self->scb_index) << CY_SCB_INDEX_POS, 0x0, (guint8 *)i2c_config, sizeof(*i2c_config), NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "i2c set config error: control xfer: %s", error_local->message); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_wait_for_notify(FuCcgxHpiDevice *self, guint16 *bytes_pending, GError **error) { guint8 buf[CY_I2C_EVENT_NOTIFICATION_LEN] = {0x0}; g_autoptr(GError) error_local = NULL; if (!g_usb_device_interrupt_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), self->ep_intr_in, buf, sizeof(buf), NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get i2c event: %s", error_local->message); return FALSE; } /* @bytes_pending available on failure */ if (buf[0] & CY_I2C_ERROR_BIT) { if (bytes_pending != NULL) { if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x01, bytes_pending, G_LITTLE_ENDIAN, error)) return FALSE; } if (buf[0] & 0x80) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "i2c status write error: 0x%x", buf[0]); return FALSE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "i2c status read error: 0x%x", buf[0]); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_i2c_read(FuCcgxHpiDevice *self, guint8 *buf, gsize bufsz, CyI2CDataConfigBits cfg_bits, GError **error) { guint8 target_address = 0; if (!fu_ccgx_hpi_device_check_i2c_status(self, CY_I2C_MODE_READ, error)) { g_prefix_error(error, "i2c read error: "); return FALSE; } target_address = (self->target_address & 0x7F) | (self->scb_index << 7); if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_READ_CMD, (((guint16)target_address) << 8) | cfg_bits, bufsz, NULL, 0x0, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, error)) { g_prefix_error(error, "i2c read error: control xfer: "); return FALSE; } if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), self->ep_bulk_in, buf, bufsz, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, error)) { g_prefix_error(error, "i2c read error: bulk xfer: "); return FALSE; } /* 10 msec delay */ fu_device_sleep(FU_DEVICE(self), I2C_READ_WRITE_DELAY_MS); if (!fu_ccgx_hpi_device_wait_for_notify(self, NULL, error)) { g_prefix_error(error, "i2c read error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_i2c_write(FuCcgxHpiDevice *self, guint8 *buf, gsize bufsz, CyI2CDataConfigBits cfg_bits, GError **error) { guint8 target_address; if (!fu_ccgx_hpi_device_check_i2c_status(self, CY_I2C_MODE_WRITE, error)) { g_prefix_error(error, "i2c get status error: "); return FALSE; } target_address = (self->target_address & 0x7F) | (self->scb_index << 7); if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_WRITE_CMD, ((guint16)target_address << 8) | (cfg_bits & CY_I2C_DATA_CONFIG_STOP), bufsz, /* idx */ NULL, 0x0, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, error)) { g_prefix_error(error, "i2c write error: control xfer: "); return FALSE; } if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), self->ep_bulk_out, buf, bufsz, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, error)) { g_prefix_error(error, "i2c write error: bulk xfer: "); return FALSE; } /* 10 msec delay */ fu_device_sleep(FU_DEVICE(self), I2C_READ_WRITE_DELAY_MS); if (!fu_ccgx_hpi_device_wait_for_notify(self, NULL, error)) { g_prefix_error(error, "i2c wait for notification error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_device_i2c_write_no_resp(FuCcgxHpiDevice *self, guint8 *buf, gsize bufsz, CyI2CDataConfigBits cfg_bits, GError **error) { guint8 target_address = 0; g_autoptr(GError) error_local = NULL; if (!fu_ccgx_hpi_device_check_i2c_status(self, CY_I2C_MODE_WRITE, error)) { g_prefix_error(error, "i2c write error: "); return FALSE; } target_address = (self->target_address & 0x7F) | (self->scb_index << 7); if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, CY_I2C_WRITE_CMD, ((guint16)target_address << 8) | (cfg_bits & CY_I2C_DATA_CONFIG_STOP), bufsz, NULL, 0x0, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, error)) { g_prefix_error(error, "i2c write error: control xfer: "); return FALSE; } /* device will reboot after this, so txfer will fail */ if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), self->ep_bulk_out, buf, bufsz, NULL, FU_CCGX_HPI_WAIT_TIMEOUT, NULL, &error_local)) { g_debug("ignoring i2c write error: bulk xfer: %s", error_local->message); } return TRUE; } static gboolean fu_ccgx_hpi_device_reg_read_cb(FuDevice *device, gpointer user_data, GError **error) { FuCcgxHpiDeviceRetryHelper *helper = (FuCcgxHpiDeviceRetryHelper *)user_data; FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); g_autofree guint8 *bufhw = g_malloc0(self->hpi_addrsz); for (guint32 i = 0; i < self->hpi_addrsz; i++) bufhw[i] = (guint8)(helper->addr >> (8 * i)); if (!fu_ccgx_hpi_device_i2c_write(self, bufhw, self->hpi_addrsz, CY_I2C_DATA_CONFIG_NAK, error)) { g_prefix_error(error, "write error: "); return FALSE; } if (!fu_ccgx_hpi_device_i2c_read(self, helper->buf, helper->bufsz, CY_I2C_DATA_CONFIG_STOP | CY_I2C_DATA_CONFIG_NAK, error)) { g_prefix_error(error, "read error: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), HPI_CMD_REG_READ_WRITE_DELAY_MS); return TRUE; } static gboolean fu_ccgx_hpi_device_reg_read(FuCcgxHpiDevice *self, guint16 addr, guint8 *buf, gsize bufsz, GError **error) { FuCcgxHpiDeviceRetryHelper helper = { .addr = addr, .mode = CY_I2C_MODE_READ, .buf = buf, .bufsz = bufsz, }; return fu_device_retry(FU_DEVICE(self), fu_ccgx_hpi_device_reg_read_cb, HPI_CMD_RESET_RETRY_CNT, &helper, error); } static gboolean fu_ccgx_hpi_device_reg_write_cb(FuDevice *device, gpointer user_data, GError **error) { FuCcgxHpiDeviceRetryHelper *helper = (FuCcgxHpiDeviceRetryHelper *)user_data; FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); g_autofree guint8 *bufhw = g_malloc0(helper->bufsz + self->hpi_addrsz); for (guint32 i = 0; i < self->hpi_addrsz; i++) bufhw[i] = (guint8)(helper->addr >> (8 * i)); memcpy(&bufhw[self->hpi_addrsz], helper->buf, helper->bufsz); if (!fu_ccgx_hpi_device_i2c_write(self, bufhw, helper->bufsz + self->hpi_addrsz, CY_I2C_DATA_CONFIG_STOP | CY_I2C_DATA_CONFIG_NAK, error)) { g_prefix_error(error, "reg write error: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), HPI_CMD_REG_READ_WRITE_DELAY_MS); return TRUE; } static gboolean fu_ccgx_hpi_device_reg_write(FuCcgxHpiDevice *self, guint16 addr, const guint8 *buf, gsize bufsz, GError **error) { FuCcgxHpiDeviceRetryHelper helper = { .addr = addr, .mode = CY_I2C_MODE_WRITE, .buf = (guint8 *)buf, .bufsz = bufsz, }; return fu_device_retry(FU_DEVICE(self), fu_ccgx_hpi_device_reg_write_cb, HPI_CMD_RESET_RETRY_CNT, &helper, error); } static gboolean fu_ccgx_hpi_device_reg_write_no_resp(FuCcgxHpiDevice *self, guint16 addr, guint8 *buf, guint16 bufsz, GError **error) { g_autofree guint8 *bufhw = g_malloc0(bufsz + self->hpi_addrsz); for (guint32 i = 0; i < self->hpi_addrsz; i++) bufhw[i] = (guint8)(addr >> (8 * i)); memcpy(&bufhw[self->hpi_addrsz], buf, bufsz); if (!fu_ccgx_hpi_device_i2c_write_no_resp(self, bufhw, bufsz + self->hpi_addrsz, CY_I2C_DATA_CONFIG_STOP | CY_I2C_DATA_CONFIG_NAK, error)) { g_prefix_error(error, "reg write no-resp error: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), HPI_CMD_REG_READ_WRITE_DELAY_MS); return TRUE; } static gboolean fu_ccgx_hpi_device_clear_intr(FuCcgxHpiDevice *self, HPIRegSection section, GError **error) { guint8 intr = 0; for (guint8 i = 0; i <= self->num_ports; i++) { if (i == section || section == HPI_REG_SECTION_ALL) intr |= 1 << i; } if (!fu_ccgx_hpi_device_reg_write(self, HPI_DEV_REG_INTR_ADDR, &intr, sizeof(intr), error)) { g_prefix_error(error, "failed to clear interrupt: "); return FALSE; } return TRUE; } static guint16 fu_ccgx_hpi_device_reg_addr_gen(guint8 section, guint8 part, guint8 addr) { return (((guint16)section) << 12) | (((guint16)part) << 8) | addr; } static gboolean fu_ccgx_hpi_device_read_event_reg(FuCcgxHpiDevice *self, HPIRegSection section, HPIEvent *event, GError **error) { if (section != HPI_REG_SECTION_DEV) { guint16 reg_addr; guint8 buf[4] = {0x0}; /* first read the response register */ reg_addr = fu_ccgx_hpi_device_reg_addr_gen(section, HPI_REG_PART_PDDATA_READ, 0); if (!fu_ccgx_hpi_device_reg_read(self, reg_addr, buf, sizeof(buf), error)) { g_prefix_error(error, "read response reg error: "); return FALSE; } /* byte 1 is reserved and should read as zero */ buf[1] = 0; memcpy((guint8 *)event, buf, sizeof(buf)); if (event->event_length != 0) { reg_addr = fu_ccgx_hpi_device_reg_addr_gen(section, HPI_REG_PART_PDDATA_READ, sizeof(buf)); if (!fu_ccgx_hpi_device_reg_read(self, reg_addr, event->event_data, event->event_length, error)) { g_prefix_error(error, "read event data error: "); return FALSE; } } } else { guint8 buf[2] = {0x0}; if (!fu_ccgx_hpi_device_reg_read(self, FU_CCGX_PD_RESP_REG_RESPONSE_ADDR, buf, sizeof(buf), error)) { g_prefix_error(error, "read response reg error: "); return FALSE; } event->event_code = buf[0]; event->event_length = buf[1]; if (event->event_length != 0) { /* read the data memory */ if (!fu_ccgx_hpi_device_reg_read(self, FU_CCGX_PD_RESP_REG_BOOTDATA_MEMORY_ADDR, event->event_data, event->event_length, error)) { g_prefix_error(error, "read event data error: "); return FALSE; } } } /* success */ return fu_ccgx_hpi_device_clear_intr(self, section, error); } static gboolean fu_ccgx_hpi_device_app_read_intr_reg(FuCcgxHpiDevice *self, HPIRegSection section, HPIEvent *event_array, guint8 *event_count, GError **error) { guint16 reg_addr; guint8 event_count_tmp = 0; guint8 intr_reg = 0; reg_addr = fu_ccgx_hpi_device_reg_addr_gen(HPI_REG_SECTION_DEV, HPI_REG_PART_REG, HPI_DEV_REG_INTR_ADDR); if (!fu_ccgx_hpi_device_reg_read(self, reg_addr, &intr_reg, sizeof(intr_reg), error)) { g_prefix_error(error, "read intr reg error: "); return FALSE; } /* device section will not come here */ for (guint8 i = 0; i <= self->num_ports; i++) { /* check if this section is needed */ if (section == i || section == HPI_REG_SECTION_ALL) { /* check whether this section has any event/response */ if ((1 << i) & intr_reg) { if (!fu_ccgx_hpi_device_read_event_reg(self, section, &event_array[i], error)) { g_prefix_error(error, "read event error: "); return FALSE; } event_count_tmp++; } } } if (event_count != NULL) *event_count = event_count_tmp; return TRUE; } static gboolean fu_ccgx_hpi_device_wait_for_event(FuCcgxHpiDevice *self, HPIRegSection section, HPIEvent *event_array, guint32 timeout_ms, GError **error) { guint8 event_count = 0; g_autoptr(GTimer) start_time = g_timer_new(); do { if (!fu_ccgx_hpi_device_app_read_intr_reg(self, section, event_array, &event_count, error)) return FALSE; if (event_count > 0) return TRUE; } while (g_timer_elapsed(start_time, NULL) * 1000.f <= timeout_ms); /* timed out */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "failed to wait for event in %ums", timeout_ms); return FALSE; } static gboolean fu_ccgx_hpi_device_get_event(FuCcgxHpiDevice *self, HPIRegSection reg_section, FuCcgxPdResp *event, guint32 io_timeout, GError **error) { HPIEvent event_array[HPI_REG_SECTION_ALL + 1] = {0x0}; if (!fu_ccgx_hpi_device_wait_for_event(self, reg_section, event_array, io_timeout, error)) { g_prefix_error(error, "failed to get event: "); return FALSE; } *event = event_array[reg_section].event_code; return TRUE; } static gboolean fu_ccgx_hpi_device_clear_all_events(FuCcgxHpiDevice *self, guint32 io_timeout, GError **error) { HPIEvent event_array[HPI_REG_SECTION_ALL + 1] = {0x0}; if (io_timeout == 0) { return fu_ccgx_hpi_device_app_read_intr_reg(self, HPI_REG_SECTION_ALL, event_array, NULL, error); } for (guint8 i = 0; i < self->num_ports; i++) { g_autoptr(GError) error_local = NULL; if (!fu_ccgx_hpi_device_wait_for_event(self, i, event_array, io_timeout, &error_local)) { if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to clear events: "); return FALSE; } } } return TRUE; } static gboolean fu_ccgx_hpi_validate_fw_cb(FuDevice *device, gpointer user_data, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); guint8 *fw_index = (guint8 *)user_data; FuCcgxPdResp hpi_event = 0; g_return_val_if_fail(fw_index != NULL, FALSE); if (!fu_ccgx_hpi_device_clear_all_events(self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; if (!fu_ccgx_hpi_device_reg_write(self, FU_CCGX_PD_RESP_REG_VALIDATE_FW_ADDR, fw_index, 1, error)) { g_prefix_error(error, "validate fw error: "); return FALSE; } if (!fu_ccgx_hpi_device_get_event(self, HPI_REG_SECTION_DEV, &hpi_event, HPI_CMD_COMMAND_RESPONSE_TIME_MS, error)) { g_prefix_error(error, "validate fw resp error: "); return FALSE; } if (hpi_event != FU_CCGX_PD_RESP_SUCCESS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "validate failed: %s [0x%x]", fu_ccgx_pd_resp_to_string(hpi_event), hpi_event); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_validate_fw(FuCcgxHpiDevice *self, guint8 fw_index, GError **error) { return fu_device_retry(FU_DEVICE(self), fu_ccgx_hpi_validate_fw_cb, HPI_CMD_VALIDATE_FW_RETRY_CNT, &fw_index, error); } static gboolean fu_ccgx_hpi_enter_flash_mode_cb(FuDevice *device, gpointer user_data, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); FuCcgxPdResp hpi_event = 0; guint8 buf[] = {FU_CCGX_PD_RESP_ENTER_FLASHING_MODE_CMD_SIG}; if (!fu_ccgx_hpi_device_clear_all_events(self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; if (!fu_ccgx_hpi_device_reg_write(self, FU_CCGX_PD_RESP_REG_ENTER_FLASH_MODE_ADDR, buf, sizeof(buf), error)) { g_prefix_error(error, "enter flash mode error: "); return FALSE; } if (!fu_ccgx_hpi_device_get_event(self, HPI_REG_SECTION_DEV, &hpi_event, HPI_CMD_COMMAND_RESPONSE_TIME_MS, error)) { g_prefix_error(error, "enter flash mode resp error: "); return FALSE; } if (hpi_event != FU_CCGX_PD_RESP_SUCCESS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "enter flash failed: %s [0x%x]", fu_ccgx_pd_resp_to_string(hpi_event), hpi_event); return FALSE; } /* wait 10 msec */ fu_device_sleep(FU_DEVICE(self), HPI_CMD_ENTER_FLASH_MODE_DELAY_MS); return TRUE; } static gboolean fu_ccgx_hpi_enter_flash_mode(FuCcgxHpiDevice *self, GError **error) { return fu_device_retry(FU_DEVICE(self), fu_ccgx_hpi_enter_flash_mode_cb, HPI_CMD_ENTER_LEAVE_FLASH_MODE_RETRY_CNT, NULL, error); } static gboolean fu_ccgx_hpi_leave_flash_mode_cb(FuDevice *device, gpointer user_data, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); FuCcgxPdResp hpi_event = 0; guint8 buf = {0x0}; if (!fu_ccgx_hpi_device_clear_all_events(self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; if (!fu_ccgx_hpi_device_reg_write(self, FU_CCGX_PD_RESP_REG_ENTER_FLASH_MODE_ADDR, &buf, sizeof(buf), error)) { g_prefix_error(error, "leave flash mode error: "); return FALSE; } if (!fu_ccgx_hpi_device_get_event(self, HPI_REG_SECTION_DEV, &hpi_event, HPI_CMD_COMMAND_RESPONSE_TIME_MS, error)) { g_prefix_error(error, "leave flash mode resp error: "); return FALSE; } if (hpi_event != FU_CCGX_PD_RESP_SUCCESS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "leave flash mode failed: %s [0x%x]", fu_ccgx_pd_resp_to_string(hpi_event), hpi_event); return FALSE; } /* wait 10 msec */ fu_device_sleep(FU_DEVICE(self), HPI_CMD_ENTER_FLASH_MODE_DELAY_MS); return TRUE; } static gboolean fu_ccgx_hpi_leave_flash_mode(FuCcgxHpiDevice *self, GError **error) { return fu_device_retry(FU_DEVICE(self), fu_ccgx_hpi_leave_flash_mode_cb, HPI_CMD_ENTER_LEAVE_FLASH_MODE_RETRY_CNT, NULL, error); } static gboolean fu_ccgx_hpi_write_flash_cb(FuDevice *device, gpointer user_data, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); FuCcgxHpiFlashWriteRetryHelper *helper = (FuCcgxHpiFlashWriteRetryHelper *)user_data; FuCcgxPdResp hpi_event = 0; guint16 addr_tmp = 0; guint8 bufhw[] = { FU_CCGX_PD_RESP_FLASH_READ_WRITE_CMD_SIG, FU_CCGX_PD_RESP_REG_FLASH_ROW_WRITE_CMD, helper->addr & 0xFF, helper->addr >> 8, }; if (!fu_ccgx_hpi_device_clear_all_events(self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; /* write data to memory */ addr_tmp = self->hpi_addrsz > 1 ? HPI_DEV_REG_FLASH_MEM : FU_CCGX_PD_RESP_REG_BOOTDATA_MEMORY_ADDR; if (!fu_ccgx_hpi_device_reg_write(self, addr_tmp, helper->buf, helper->bufsz, error)) { g_prefix_error(error, "write buf to memory error: "); return FALSE; } if (!fu_ccgx_hpi_device_reg_write(self, FU_CCGX_PD_RESP_REG_FLASH_READ_WRITE_ADDR, bufhw, sizeof(bufhw), error)) { g_prefix_error(error, "write flash error: "); return FALSE; } /* wait until flash is written */ if (!fu_ccgx_hpi_device_get_event(self, HPI_REG_SECTION_DEV, &hpi_event, HPI_CMD_COMMAND_RESPONSE_TIME_MS, error)) { g_prefix_error(error, "write flash resp error: "); return FALSE; } if (hpi_event != FU_CCGX_PD_RESP_SUCCESS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "write flash failed: %s [0x%x]", fu_ccgx_pd_resp_to_string(hpi_event), hpi_event); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_write_flash(FuCcgxHpiDevice *self, guint16 addr, const guint8 *buf, guint16 bufsz, GError **error) { FuCcgxHpiFlashWriteRetryHelper helper = { .addr = addr, .buf = buf, .bufsz = bufsz, }; return fu_device_retry(FU_DEVICE(self), fu_ccgx_hpi_write_flash_cb, HPI_CMD_FLASH_WRITE_RETRY_CNT, &helper, error); } static gboolean fu_ccgx_hpi_read_flash_cb(FuDevice *device, gpointer user_data, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); FuCcgxHpiFlashReadRetryHelper *helper = (FuCcgxHpiFlashReadRetryHelper *)user_data; FuCcgxPdResp hpi_event = 0; guint16 addr_tmp; guint8 bufhw[] = { FU_CCGX_PD_RESP_FLASH_READ_WRITE_CMD_SIG, FU_CCGX_PD_RESP_REG_FLASH_ROW_READ_CMD, helper->addr & 0xFF, helper->addr >> 8, }; /* set address */ if (!fu_ccgx_hpi_device_clear_all_events(self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; if (!fu_ccgx_hpi_device_reg_write(self, FU_CCGX_PD_RESP_REG_FLASH_READ_WRITE_ADDR, bufhw, sizeof(bufhw), error)) { g_prefix_error(error, "read flash error: "); return FALSE; } /* wait until flash is read */ if (!fu_ccgx_hpi_device_get_event(self, HPI_REG_SECTION_DEV, &hpi_event, HPI_CMD_COMMAND_RESPONSE_TIME_MS, error)) { g_prefix_error(error, "read flash resp error: "); return FALSE; } if (hpi_event != FU_CCGX_PD_RESP_FLASH_DATA_AVAILABLE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "read flash failed: %s [0x%x]", fu_ccgx_pd_resp_to_string(hpi_event), hpi_event); return FALSE; } addr_tmp = self->hpi_addrsz > 1 ? HPI_DEV_REG_FLASH_MEM : FU_CCGX_PD_RESP_REG_BOOTDATA_MEMORY_ADDR; if (!fu_ccgx_hpi_device_reg_read(self, addr_tmp, helper->buf, helper->bufsz, error)) { g_prefix_error(error, "read data from memory error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_read_flash(FuCcgxHpiDevice *self, guint16 addr, guint8 *buf, guint16 bufsz, GError **error) { FuCcgxHpiFlashReadRetryHelper helper = { .addr = addr, .buf = buf, .bufsz = bufsz, }; return fu_device_retry(FU_DEVICE(self), fu_ccgx_hpi_read_flash_cb, HPI_CMD_FLASH_READ_RETRY_CNT, &helper, error); } static gboolean fu_ccgx_hpi_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); guint8 buf[] = { FU_CCGX_PD_RESP_JUMP_TO_ALT_FW_CMD_SIG, }; /* not required */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER) || self->fw_image_type == FU_CCGX_IMAGE_TYPE_DUAL_SYMMETRIC) return TRUE; /* jump to Alt FW */ if (!fu_ccgx_hpi_device_clear_all_events(self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; if (!fu_ccgx_hpi_device_reg_write(self, FU_CCGX_PD_RESP_JUMP_TO_BOOT_REG_ADDR, buf, sizeof(buf), error)) { g_prefix_error(error, "jump to alt mode error: "); return FALSE; } /* sym not required */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); fu_device_add_private_flag(device, FU_CCGX_HPI_DEVICE_IS_IN_RESTART); /* success */ return TRUE; } static gboolean fu_ccgx_hpi_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); guint8 buf[] = { FU_CCGX_PD_RESP_DEVICE_RESET_CMD_SIG, FU_CCGX_PD_RESP_REG_RESET_DEVICE_CMD, }; if (!fu_ccgx_hpi_device_clear_all_events(self, HPI_CMD_COMMAND_CLEAR_EVENT_TIME_MS, error)) return FALSE; if (!fu_ccgx_hpi_device_reg_write_no_resp(self, FU_CCGX_PD_RESP_REG_RESET_ADDR, buf, sizeof(buf), error)) { g_prefix_error(error, "reset device error: "); return FALSE; } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); fu_device_add_private_flag(device, FU_CCGX_HPI_DEVICE_IS_IN_RESTART); return TRUE; } static FuFirmware * fu_ccgx_hpi_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); FuCcgxFwMode fw_mode; guint16 fw_app_type; guint16 fw_silicon_id; g_autoptr(FuFirmware) firmware = fu_ccgx_firmware_new(); /* parse all images */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* check the silicon ID */ fw_silicon_id = fu_ccgx_firmware_get_silicon_id(FU_CCGX_FIRMWARE(firmware)); if (fw_silicon_id != self->silicon_id) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "silicon id mismatch, expected 0x%x, got 0x%x", self->silicon_id, fw_silicon_id); return NULL; } if ((flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) == 0) { fw_app_type = fu_ccgx_firmware_get_app_type(FU_CCGX_FIRMWARE(firmware)); if (fw_app_type != self->fw_app_type) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "app type mismatch, expected 0x%x, got 0x%x", self->fw_app_type, fw_app_type); return NULL; } } fw_mode = fu_ccgx_firmware_get_fw_mode(FU_CCGX_FIRMWARE(firmware)); if (fw_mode != fu_ccgx_fw_mode_get_alternate(self->fw_mode)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "FuCcgxFwMode mismatch, expected %s, got %s", fu_ccgx_fw_mode_to_string(fu_ccgx_fw_mode_get_alternate(self->fw_mode)), fu_ccgx_fw_mode_to_string(fw_mode)); return NULL; } return g_steal_pointer(&firmware); } static gboolean fu_ccgx_hpi_get_metadata_offset(FuCcgxHpiDevice *self, FuCcgxFwMode fw_mode, guint32 *addr, guint32 *offset, GError **error) { guint32 addr_max; /* sanity check */ if (self->flash_row_size == 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unset support row size"); return FALSE; } /* get the row offset for the flash size */ addr_max = self->flash_size / self->flash_row_size; if (self->flash_row_size == 128) { *offset = HPI_META_DATA_OFFSET_ROW_128; } else if (self->flash_row_size == 256) { *offset = HPI_META_DATA_OFFSET_ROW_256; } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported support row size: 0x%x", self->flash_row_size); return FALSE; } /* get the row offset in the flash */ switch (fw_mode) { case FU_CCGX_FW_MODE_FW1: *addr = addr_max - 1; break; case FU_CCGX_FW_MODE_FW2: *addr = addr_max - 2; break; default: g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "boot recovery not supported"); return FALSE; } return TRUE; } /* this will only work after fu_ccgx_hpi_enter_flash_mode() has been used */ static gboolean fu_ccgx_hpi_load_metadata(FuCcgxHpiDevice *self, FuCcgxFwMode fw_mode, GByteArray *st_metadata, GError **error) { guint32 addr = 0x0; guint32 md_offset = 0x0; g_autofree guint8 *buf = NULL; /* read flash at correct address */ if (!fu_ccgx_hpi_get_metadata_offset(self, fw_mode, &addr, &md_offset, error)) return FALSE; buf = g_malloc0(self->flash_row_size); if (!fu_ccgx_hpi_read_flash(self, addr, buf, self->flash_row_size, error)) { g_prefix_error(error, "fw metadata read error: "); return FALSE; } return fu_memcpy_safe(st_metadata->data, st_metadata->len, 0x0, buf, self->flash_row_size, md_offset, st_metadata->len, error); } /* this will only work after fu_ccgx_hpi_enter_flash_mode() has been used */ static gboolean fu_ccgx_hpi_save_metadata(FuCcgxHpiDevice *self, FuCcgxFwMode fw_mode, GByteArray *st_metadata, GError **error) { guint32 addr = 0x0; guint32 md_offset = 0x0; g_autofree guint8 *buf = NULL; /* read entire row of flash at correct address */ if (!fu_ccgx_hpi_get_metadata_offset(self, fw_mode, &addr, &md_offset, error)) return FALSE; buf = g_malloc0(self->flash_row_size); if (!fu_ccgx_hpi_read_flash(self, addr, buf, self->flash_row_size, error)) { g_prefix_error(error, "fw metadata read existing error: "); return FALSE; } if (!fu_memcpy_safe(buf, self->flash_row_size, md_offset, st_metadata->data, st_metadata->len, 0x0, st_metadata->len, error)) return FALSE; if (!fu_ccgx_hpi_write_flash(self, addr, buf, self->flash_row_size, error)) { g_prefix_error(error, "fw metadata write error: "); return FALSE; } return TRUE; } static gboolean fu_ccgx_hpi_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); GPtrArray *records = fu_ccgx_firmware_get_records(FU_CCGX_FIRMWARE(firmware)); FuCcgxFwMode fw_mode_alt = fu_ccgx_fw_mode_get_alternate(self->fw_mode); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GByteArray) st_metadata = fu_struct_ccgx_metadata_hdr_new(); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "invalidate-metadata"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "leave-flash"); /* enter flash mode */ locker = fu_device_locker_new_full(self, (FuDeviceLockerFunc)fu_ccgx_hpi_enter_flash_mode, (FuDeviceLockerFunc)fu_ccgx_hpi_leave_flash_mode, error); if (locker == NULL) return FALSE; /* invalidate metadata for alternate image */ if (!fu_ccgx_hpi_load_metadata(self, fw_mode_alt, st_metadata, error)) return FALSE; fu_struct_ccgx_metadata_hdr_set_metadata_valid(st_metadata, 0x0); if (!fu_ccgx_hpi_save_metadata(self, fw_mode_alt, st_metadata, error)) return FALSE; fu_progress_step_done(progress); /* write new image */ for (guint i = 0; i < records->len; i++) { FuCcgxFirmwareRecord *rcd = g_ptr_array_index(records, i); /* write chunk */ if (!fu_ccgx_hpi_write_flash(self, rcd->row_number, g_bytes_get_data(rcd->data, NULL), g_bytes_get_size(rcd->data), error)) { g_prefix_error(error, "fw write error @0x%x: ", rcd->row_number); return FALSE; } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)records->len); } fu_progress_step_done(progress); /* validate fw */ if (!fu_ccgx_hpi_validate_fw(self, fw_mode_alt, error)) { g_prefix_error(error, "fw validate error: "); return FALSE; } fu_progress_step_done(progress); /* this is a good time to leave the flash mode *before* rebooting */ if (!fu_device_locker_close(locker, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_ccgx_hpi_device_ensure_silicon_id(FuCcgxHpiDevice *self, GError **error) { guint8 buf[2] = {0x0}; if (!fu_ccgx_hpi_device_reg_read(self, FU_CCGX_PD_RESP_SILICON_ID, buf, sizeof(buf), error)) { g_prefix_error(error, "get silicon id error: "); return FALSE; } if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x0, &self->silicon_id, G_LITTLE_ENDIAN, error)) return FALSE; /* add quirks */ if (self->silicon_id != 0x0) fu_device_add_instance_u16(FU_DEVICE(self), "SID", self->silicon_id); fu_device_build_instance_id_full(FU_DEVICE(self), FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "CCGX", "SID", NULL); g_debug("got silicon ID: 0x%04x", self->silicon_id); /* sanity check */ if (self->flash_row_size == 0x0 || self->flash_size == 0x0 || self->flash_size % self->flash_row_size != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid row size for: 0x%x/0x%x", self->flash_row_size, self->flash_size); return FALSE; } /* success */ return TRUE; } static gchar * fu_ccgx_hpi_device_convert_version(FuDevice *device, guint64 version_raw) { return fu_ccgx_version_to_string(version_raw); } static gboolean fu_ccgx_hpi_device_setup(FuDevice *device, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); CyI2CConfig i2c_config = {0x0}; guint32 hpi_event = 0; guint8 mode = 0; g_autoptr(GError) error_local = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_ccgx_hpi_device_parent_class)->setup(device, error)) return FALSE; /* set the new config */ if (!fu_ccgx_hpi_device_get_i2c_config(self, &i2c_config, error)) { g_prefix_error(error, "get config error: "); return FALSE; } i2c_config.frequency = FU_CCGX_HPI_FREQ; i2c_config.is_initiator = TRUE; i2c_config.is_msb_first = TRUE; if (!fu_ccgx_hpi_device_set_i2c_config(self, &i2c_config, error)) { g_prefix_error(error, "set config error: "); return FALSE; } if (!fu_ccgx_hpi_device_reg_read(self, FU_CCGX_PD_RESP_REG_DEVICE_MODE_ADDR, &mode, 1, error)) { g_prefix_error(error, "get device mode error: "); return FALSE; } self->hpi_addrsz = mode & 0x80 ? 2 : 1; self->num_ports = (mode >> 2) & 0x03 ? 2 : 1; self->fw_mode = (FuCcgxFwMode)(mode & 0x03); fu_device_set_logical_id(device, fu_ccgx_fw_mode_to_string(self->fw_mode)); fu_device_add_instance_str(device, "MODE", fu_device_get_logical_id(device)); /* get silicon ID */ if (!fu_ccgx_hpi_device_ensure_silicon_id(self, error)) return FALSE; /* get correct version if not in boot mode */ if (self->fw_mode != FU_CCGX_FW_MODE_BOOT) { guint16 bufsz; guint32 versions[FU_CCGX_FW_MODE_LAST] = {0x0}; guint8 bufver[HPI_DEVICE_VERSION_SIZE_HPIV2] = {0x0}; bufsz = self->hpi_addrsz == 1 ? HPI_DEVICE_VERSION_SIZE_HPIV1 : HPI_DEVICE_VERSION_SIZE_HPIV2; if (!fu_ccgx_hpi_device_reg_read(self, FU_CCGX_PD_RESP_GET_VERSION, bufver, bufsz, error)) { g_prefix_error(error, "get version error: "); return FALSE; } /* fw1 */ if (!fu_memread_uint32_safe(bufver, sizeof(bufver), 0x0c, &versions[FU_CCGX_FW_MODE_FW1], G_LITTLE_ENDIAN, error)) return FALSE; /* fw2 */ if (!fu_memread_uint32_safe(bufver, sizeof(bufver), 0x14, &versions[FU_CCGX_FW_MODE_FW2], G_LITTLE_ENDIAN, error)) return FALSE; /* add GUIDs that are specific to the firmware app type */ self->fw_app_type = versions[self->fw_mode] & 0xffff; if (self->fw_app_type != 0x0) fu_device_add_instance_u16(device, "APP", self->fw_app_type); /* if running in bootloader force an upgrade to any version */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { fu_device_set_version_raw(device, 0x0); } else { fu_device_set_version_raw(device, versions[self->fw_mode]); } } /* not supported in boot mode */ if (self->fw_mode == FU_CCGX_FW_MODE_BOOT) { fu_device_inhibit(device, "device-in-boot-mode", "Not supported in BOOT mode"); } else { fu_device_uninhibit(device, "device-in-boot-mode"); } /* add extra instance IDs */ fu_device_build_instance_id(device, NULL, "USB", "VID", "PID", "SID", "APP", NULL); fu_device_build_instance_id(device, NULL, "USB", "VID", "PID", "SID", "APP", "MODE", NULL); /* if we are coming back from reset, wait for hardware to settle */ if (!fu_ccgx_hpi_device_get_event(self, HPI_REG_SECTION_DEV, &hpi_event, HPI_CMD_SETUP_EVENT_WAIT_TIME_MS, &error_local)) { if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } else { if (hpi_event == FU_CCGX_PD_RESP_RESET_COMPLETE) fu_device_sleep(FU_DEVICE(self), HPI_CMD_RESET_COMPLETE_DELAY_MS); } /* start with no events in the queue */ return fu_ccgx_hpi_device_clear_all_events(self, HPI_CMD_SETUP_EVENT_CLEAR_TIME_MS, error); } static gboolean fu_ccgx_hpi_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuCcgxHpiDevice *self = FU_CCGX_HPI_DEVICE(device); guint64 tmp = 0; if (g_strcmp0(key, "SiliconId") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->silicon_id = tmp; return TRUE; } if (g_strcmp0(key, "CcgxFlashRowSize") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->flash_row_size = tmp; return TRUE; } if (g_strcmp0(key, "CcgxFlashSize") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->flash_size = tmp; return TRUE; } if (g_strcmp0(key, "CcgxImageKind") == 0) { self->fw_image_type = fu_ccgx_image_type_from_string(value); if (self->fw_image_type != FU_CCGX_IMAGE_TYPE_UNKNOWN) return TRUE; g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid CcgxImageKind"); return FALSE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no supported"); return FALSE; } static gboolean fu_ccgx_hpi_device_close(FuDevice *device, GError **error) { /* do not close handle when device restarts */ if (fu_device_has_private_flag(device, FU_CCGX_HPI_DEVICE_IS_IN_RESTART)) return TRUE; /* FuUsbDevice->close */ return FU_DEVICE_CLASS(fu_ccgx_hpi_device_parent_class)->close(device, error); } static void fu_ccgx_hpi_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_ccgx_hpi_device_init(FuCcgxHpiDevice *self) { self->inf_num = 0x0; self->hpi_addrsz = 1; self->num_ports = 1; self->target_address = PD_I2C_TARGET_ADDRESS; self->ep_bulk_out = PD_I2C_USB_EP_BULK_OUT; self->ep_bulk_in = PD_I2C_USB_EP_BULK_IN; self->ep_intr_in = PD_I2C_USB_EP_INTR_IN; fu_device_add_protocol(FU_DEVICE(self), "com.cypress.ccgx"); fu_device_add_protocol(FU_DEVICE(self), "com.infineon.ccgx"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_retry_set_delay(FU_DEVICE(self), HPI_CMD_RETRY_DELAY); fu_device_register_private_flag(FU_DEVICE(self), FU_CCGX_HPI_DEVICE_IS_IN_RESTART, "device-is-in-restart"); /* we can recover the I²C link using reset */ fu_device_retry_add_recovery(FU_DEVICE(self), FWUPD_ERROR, FWUPD_ERROR_READ, fu_ccgx_hpi_device_i2c_reset_cb); fu_device_retry_add_recovery(FU_DEVICE(self), FWUPD_ERROR, FWUPD_ERROR_WRITE, fu_ccgx_hpi_device_i2c_reset_cb); /* this might not be true for future hardware */ if (self->inf_num > 0) self->scb_index = 1; fu_usb_device_add_interface(FU_USB_DEVICE(self), self->inf_num); } static void fu_ccgx_hpi_device_class_init(FuCcgxHpiDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_ccgx_hpi_device_to_string; klass_device->write_firmware = fu_ccgx_hpi_write_firmware; klass_device->prepare_firmware = fu_ccgx_hpi_device_prepare_firmware; klass_device->detach = fu_ccgx_hpi_device_detach; klass_device->attach = fu_ccgx_hpi_device_attach; klass_device->setup = fu_ccgx_hpi_device_setup; klass_device->set_quirk_kv = fu_ccgx_hpi_device_set_quirk_kv; klass_device->close = fu_ccgx_hpi_device_close; klass_device->set_progress = fu_ccgx_hpi_device_set_progress; klass_device->convert_version = fu_ccgx_hpi_device_convert_version; } fwupd-1.9.16/plugins/ccgx/fu-ccgx-hpi-device.h000066400000000000000000000005511460375044200210700ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CCGX_HPI_DEVICE (fu_ccgx_hpi_device_get_type()) G_DECLARE_FINAL_TYPE(FuCcgxHpiDevice, fu_ccgx_hpi_device, FU, CCGX_HPI_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/ccgx/fu-ccgx-plugin.c000066400000000000000000000020771460375044200203510ustar00rootroot00000000000000/* * Copyright (C) 2020 Cypress Semiconductor Corporation. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ccgx-firmware.h" #include "fu-ccgx-hid-device.h" #include "fu-ccgx-hpi-device.h" #include "fu-ccgx-plugin.h" struct _FuCcgxPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuCcgxPlugin, fu_ccgx_plugin, FU_TYPE_PLUGIN) static void fu_ccgx_plugin_init(FuCcgxPlugin *self) { } static void fu_ccgx_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "CcgxFlashRowSize"); fu_context_add_quirk_key(ctx, "CcgxFlashSize"); fu_context_add_quirk_key(ctx, "CcgxImageKind"); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_CCGX_FIRMWARE); fu_plugin_add_device_gtype(plugin, FU_TYPE_CCGX_HID_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_CCGX_HPI_DEVICE); } static void fu_ccgx_plugin_class_init(FuCcgxPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_ccgx_plugin_constructed; } fwupd-1.9.16/plugins/ccgx/fu-ccgx-plugin.h000066400000000000000000000003421460375044200203470ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuCcgxPlugin, fu_ccgx_plugin, FU, CCGX_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/ccgx/fu-ccgx.rs000066400000000000000000000036101460375044200172510ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, ParseBytes)] struct CcgxMetadataHdr { fw_checksum: u8, fw_entry: u32le, last_boot_row: u16le, // last flash row of bootloader or previous firmware _reserved1: [u8; 2], fw_size: u32le, _reserved2: [u8; 9], metadata_valid: u16le = 0x4359, // "CY" _reserved3: [u8; 4], boot_seq: u32le, } #[derive(ToString, FromString)] enum CcgxImageType { Unknown, Single, DualSymmetric, // A/B runtime DualAsymmetric, // A=bootloader (fixed) B=runtime DualAsymmetricVariable, // A=bootloader (variable) B=runtime } #[derive(ToString)] enum CcgxFwMode { Boot, Fw1, Fw2, Last, } #[derive(ToString)] enum CcgxPdResp { // responses NoResponse, Success = 0x02, FlashDataAvailable, InvalidCommand = 0x05, CollisionDetected, FlashUpdateFailed, InvalidFw, InvalidArguments, NotSupported, TransactionFailed = 0x0C, PdCommandFailed, Undefined, RaDetect = 0x10, RaRemoved, // device specific events ResetComplete = 0x80, MessageQueueOverflow, // type-c specific events OverCurrentDetected, OverVoltageDetected, TypeCConnected, TypeCDisconnected, // pd specific events and asynchronous messages PdContractEstablished, DrSwap, PrSwap, VconSwap, PsRdy, Gotomin, AcceptMessage, RejectMessage, WaitMessage, HardReset, VdmReceived, SrcCapRcvd, SinkCapRcvd, DpAlternateMode, DpDeviceNonnected, DpDeviceNotConnected, DpSidNotFound, MultipleSvidDiscovered, DpFunctionNotSupported, DpPortConfigNotSupported, // not a response? HardResetSent, SoftResetSent, CableResetSent, SourceDisabledStateEntered, SenderResponseTimerTimeout, NoVdmResponseReceived, } fwupd-1.9.16/plugins/ccgx/fu-self-test.c000066400000000000000000000033331460375044200200330ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ccgx-firmware.h" static void fu_ccgx_firmware_xml_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *csum1 = NULL; g_autofree gchar *csum2 = NULL; g_autofree gchar *xml_out = NULL; g_autofree gchar *xml_src = NULL; g_autoptr(FuFirmware) firmware1 = fu_ccgx_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_ccgx_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "ccgx.builder.xml", NULL); ret = g_file_get_contents(filename, &xml_src, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_build_from_xml(firmware1, xml_src, &error); g_assert_no_error(error); g_assert_true(ret); csum1 = fu_firmware_get_checksum(firmware1, G_CHECKSUM_SHA1, &error); g_assert_no_error(error); g_assert_cmpstr(csum1, ==, "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"); /* ensure we can round-trip */ xml_out = fu_firmware_export_to_xml(firmware1, FU_FIRMWARE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); ret = fu_firmware_build_from_xml(firmware2, xml_out, &error); g_assert_no_error(error); g_assert_true(ret); csum2 = fu_firmware_get_checksum(firmware2, G_CHECKSUM_SHA1, &error); g_assert_cmpstr(csum1, ==, csum2); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func("/ccgx/firmware{xml}", fu_ccgx_firmware_xml_func); return g_test_run(); } fwupd-1.9.16/plugins/ccgx/meson.build000066400000000000000000000025741460375044200175210ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginCcgx"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files([ 'ccgx-ids.quirk', 'ccgx.quirk', ]) plugin_builtin_ccgx = static_library('fu_plugin_ccgx', rustgen.process( 'fu-ccgx.rs', # fuzzing ), sources: [ 'fu-ccgx-plugin.c', 'fu-ccgx-common.c', # fuzzing 'fu-ccgx-firmware.c', # fuzzing 'fu-ccgx-hid-device.c', 'fu-ccgx-hpi-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: [ plugin_deps, gudev, ], ) plugin_builtins += plugin_builtin_ccgx if get_option('tests') install_data(['tests/ccgx.builder.xml'], install_dir: join_paths(installed_test_datadir, 'tests')) env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'ccgx-self-test', rustgen.process('fu-ccgx.rs'), sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_ccgx, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('ccgx-self-test', e, env: env) endif endif fwupd-1.9.16/plugins/ccgx/tests/000077500000000000000000000000001460375044200165115ustar00rootroot00000000000000fwupd-1.9.16/plugins/ccgx/tests/ccgx.bin000066400000000000000000000004771460375044200201370ustar00rootroot000000000000001F0011AF0000 :00000E000B68656C6C6F20776F726C648B :00000C008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000A400000000130000000B00000000000000000000000059430000000000000000000000000000000000000000000000000000000000000000000000000000000016 fwupd-1.9.16/plugins/ccgx/tests/ccgx.builder.xml000066400000000000000000000001601460375044200216010ustar00rootroot00000000000000 0x1F00 aGVsbG8gd29ybGQ= fwupd-1.9.16/plugins/cfu/000077500000000000000000000000001460375044200152005ustar00rootroot00000000000000fwupd-1.9.16/plugins/cfu/README.md000066400000000000000000000130501460375044200164560ustar00rootroot00000000000000--- title: Plugin: CFU - Component Firmware Update --- ## Introduction CFU is a protocol from Microsoft to make it easy to install firmware on HID devices. See for more details. This plugin supports the following protocol ID: * `com.microsoft.cfu` ## Implementation Notes CFU has a pre-download phase that is used to send the firmware *offer* to the microcontroller, so the device can check if the firmware is required and compatible. CFU also requires devices to be able to transfer the entire new transfer mode in runtime mode. The pre-download “offer” allows the device to check any sub-components attached (e.g. other devices attached to the SoC) and forces it to do dependency resolution in case sub-components have to be updated in a specific order. Pushing the dependency resolution down to the device means the low-power device has to do all the version comparisons and also know all the logic with regard to protocol incompatibilities. The end-user could be in a position where the device firmware needs to be updated so that it “knows” about the new protocol restrictions, which are needed to update the device and the things attached in the right order in a subsequent update. If the user always updates the device to the latest version, the factory-default running version *might yet know* about the new restrictions. It is therefore imperative that all previous versions are tested being updated *from*. Something that we support in fwupd is being able to restrict the peripheral device firmware to a specific SMBIOS CHID or a system firmware vendor, which lets vendors solve the *same hardware in different chassis, with custom firmware* problem. Using CFU in Microsoft Windows also means that the peripheral is unaware of the other devices in the system, so for instance couldn’t only install a new firmware version for only new builds of Windows for example. A few other consideration for vendors is the doubling of flash storage required to do an runtime transfer, the extra power budget of being woken up to process the *offer* and providing enough bulk power to stay alive if *unplugged* during a A/B swap. On most existing hardware the easiest way to implement CFU is an additional ARM micro-controller to act as a CFU “bridge” for legacy silicon. The CFU “bridge” could also do signing and encryption. CFU does not define any standard way to encrypt and sign firmware, or to detect if devices have any firmware verification capabilities and so this too will need to be set per-device either in the metadata or in the quirk file. CFU also downloads in the runtime mode in the background, at a maximum of 52 bytes per HID request and response. This means even small updates will take a long time to complete due to the huge number of USB control transfers required. The specification also doesn't specify the HID reports to use, so it all needs to be hardcoded per-device unless the exact same defaults are used as in `CFU/Tools/ComponentFirmwareUpdateStandAloneToolSample/protocolCfgExample.cfg`. In fwupd these can be set as quirks in `cfu.quirk`. The included `https://github.com/fwupd/fwupd/blob/main/contrib/cfu-inf-to-quirk.py` script may be useful to convert an existing `.inf` file to fwupd `.quirk` format. ## Firmware Format Due to the one-shot way fwupd deploys firmware, the daemon only deals with one “payload” per update. The offer and payload currently have to be combined in an archive where they are transferred to the device one after the other. The files in the firmware archive therefore should have the extensions `.offer.bin` and `.payload.bin`. ## GUID Generation These devices use standard USB DeviceInstanceId values, as well as two extra for the component ID and the bank, e.g. * `HIDRAW\VEN_17EF&DEV_7226&CID_01&BANK_1` * `HIDRAW\VEN_17EF&DEV_7226&CID_01` * `HIDRAW\VEN_17EF&DEV_7226` ## Quirk Use This plugin uses the following plugin-specific quirks: ### CfuVersionGetReport The HID report usage to use when parsing the response of `GET_FIRMWARE_VERSION`. This usually corresponds to the `VersionsFeatureValueCapabilityUsageRangeMinimum` value set in the `.inf` file. Since: 1.9.1 ### CfuOfferSetReport The HID report usage to use when sending the request for `FIRMWARE_UPDATE_OFFER`. This usually corresponds to the `OfferOutputValueCapabilityUsageRangeMinimum` value set in the `.inf` file. Since: 1.9.1 ### CfuOfferGetReport The HID report usage to use when parsing the response of `FIRMWARE_UPDATE_OFFER`. This usually corresponds to the `OfferInputValueCapabilityUsageRangeMinimum` value set in the `.inf` file. Since: 1.9.1 ### CfuContentSetReport The HID report usage to use when sending the request for `FIRMWARE_UPDATE_CONTENT`. This usually corresponds to the `PayloadOutputValueCapabilityUsageRangeMinimum` value set in the `.inf` file. Since: 1.9.1 ### CfuContentGetReport The HID report usage to use when parsing the response of `FIRMWARE_UPDATE_CONTENT`. This usually corresponds to the `PayloadInputValueCapabilityUsageRangeMinimum` value set in the `.inf` file. Since: 1.9.1 ## Update Behavior The device has to support runtime updates and does not have a detach-into-bootloader mode -- but after the install has completed the device still has to reboot into the new firmware. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `HIDRAW:0x17EF` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.7.1`. fwupd-1.9.16/plugins/cfu/cfu.quirk000066400000000000000000000003761460375044200170400ustar00rootroot00000000000000[USB\VID_273F&PID_100A] Plugin = cfu # Microsoft USB-C Travel Hub [USB\VID_045E&PID_09BC] Plugin = cfu Vendor = Microsoft CfuVersionGetReport = 0xC8 CfuOfferSetReport = 0xDC CfuOfferGetReport = 0xD8 CfuContentSetReport = 0xC7 CfuContentGetReport = 0xCC fwupd-1.9.16/plugins/cfu/fu-cfu-device.c000066400000000000000000000426231460375044200177750ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-cfu-device.h" #include "fu-cfu-module.h" #include "fu-cfu-struct.h" typedef struct { guint8 op; guint8 id; guint8 ct; } FuCfuDeviceMap; struct _FuCfuDevice { FuHidDevice parent_instance; guint8 protocol_version; FuCfuDeviceMap version_get_report; FuCfuDeviceMap offer_set_report; FuCfuDeviceMap offer_get_report; FuCfuDeviceMap content_set_report; FuCfuDeviceMap content_get_report; }; G_DEFINE_TYPE(FuCfuDevice, fu_cfu_device, FU_TYPE_HID_DEVICE) #define FU_CFU_DEVICE_TIMEOUT 5000 /* ms */ #define FU_CFU_DEVICE_FLAG_SEND_OFFER_INFO (1 << 0) static void fu_cfu_device_map_to_string(GString *str, guint idt, FuCfuDeviceMap *map, const gchar *title) { g_autofree gchar *title_op = g_strdup_printf("%sOp", title); g_autofree gchar *title_id = g_strdup_printf("%sId", title); g_autofree gchar *title_ct = g_strdup_printf("%sCt", title); fu_string_append_kx(str, idt, title_op, map->op); fu_string_append_kx(str, idt, title_id, map->id); fu_string_append_kx(str, idt, title_ct, map->ct); } static void fu_cfu_device_to_string(FuDevice *device, guint idt, GString *str) { FuCfuDevice *self = FU_CFU_DEVICE(device); /* FuUdevDevice->to_string */ FU_DEVICE_CLASS(fu_cfu_device_parent_class)->to_string(device, idt, str); fu_string_append_kx(str, idt, "ProtocolVersion", self->protocol_version); fu_cfu_device_map_to_string(str, idt, &self->version_get_report, "VersionGetReport"); fu_cfu_device_map_to_string(str, idt, &self->offer_set_report, "OfferSetReport"); fu_cfu_device_map_to_string(str, idt, &self->offer_get_report, "OfferGetReport"); fu_cfu_device_map_to_string(str, idt, &self->content_set_report, "ContentSetReport"); fu_cfu_device_map_to_string(str, idt, &self->content_get_report, "ContentGetReport"); } static gboolean fu_cfu_device_send_offer_info(FuCfuDevice *self, FuCfuOfferInfoCode info_code, GError **error) { g_autoptr(GByteArray) buf_in = g_byte_array_new(); g_autoptr(GByteArray) buf_out = g_byte_array_new(); g_autoptr(GByteArray) st_req = fu_struct_cfu_offer_info_req_new(); g_autoptr(GByteArray) st_res = NULL; /* not all devices handle this */ if (!fu_device_has_private_flag(FU_DEVICE(self), FU_CFU_DEVICE_FLAG_SEND_OFFER_INFO)) return TRUE; /* SetReport */ fu_struct_cfu_offer_info_req_set_code(st_req, info_code); fu_byte_array_append_uint8(buf_out, self->offer_set_report.id); g_byte_array_append(buf_out, st_req->data, st_req->len); fu_byte_array_set_size(buf_out, self->offer_set_report.ct, 0x0); if (!fu_hid_device_set_report(FU_HID_DEVICE(self), self->offer_set_report.id, buf_out->data, buf_out->len, FU_CFU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to send offer info: "); return FALSE; } /* GetReport */ fu_byte_array_append_uint8(buf_in, self->offer_get_report.id); fu_byte_array_set_size(buf_in, self->offer_get_report.ct + 0x1, 0x0); if (!fu_hid_device_get_report(FU_HID_DEVICE(self), self->offer_get_report.id, buf_in->data, buf_in->len, FU_CFU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) { g_prefix_error(error, "failed to send offer info: "); return FALSE; } st_res = fu_struct_cfu_offer_rsp_parse(buf_in->data, buf_in->len, 0x1, error); if (st_res == NULL) return FALSE; if (fu_struct_cfu_offer_rsp_get_token(st_res) != FU_STRUCT_CFU_OFFER_INFO_REQ_DEFAULT_TOKEN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "token invalid: got 0x%x and expected 0x%x", fu_struct_cfu_offer_rsp_get_token(st_res), (guint)FU_STRUCT_CFU_OFFER_INFO_REQ_DEFAULT_TOKEN); return FALSE; } if (fu_struct_cfu_offer_rsp_get_status(st_res) != FU_CFU_OFFER_STATUS_ACCEPT) { g_set_error( error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "offer info %s not supported: %s", fu_cfu_offer_info_code_to_string(info_code), fu_cfu_offer_status_to_string(fu_struct_cfu_offer_rsp_get_status(st_res))); return FALSE; } /* success */ return TRUE; } static gboolean fu_cfu_device_send_offer(FuCfuDevice *self, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { g_autoptr(GByteArray) buf_in = g_byte_array_new(); g_autoptr(GByteArray) buf_out = g_byte_array_new(); g_autoptr(GByteArray) st = NULL; g_autoptr(GBytes) blob = NULL; /* generate a offer blob */ if (flags & FWUPD_INSTALL_FLAG_FORCE) fu_cfu_offer_set_force_ignore_version(FU_CFU_OFFER(firmware), TRUE); blob = fu_firmware_write(firmware, error); if (blob == NULL) return FALSE; /* SetReport */ fu_byte_array_append_uint8(buf_out, self->offer_set_report.id); fu_byte_array_append_bytes(buf_out, blob); fu_byte_array_set_size(buf_out, self->offer_set_report.ct, 0x0); if (!fu_hid_device_set_report(FU_HID_DEVICE(self), self->offer_set_report.id, buf_out->data, buf_out->len, FU_CFU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to send offer: "); return FALSE; } /* GetReport */ fu_byte_array_append_uint8(buf_in, self->offer_get_report.id); fu_byte_array_set_size(buf_in, self->offer_get_report.ct + 0x1, 0x0); if (!fu_hid_device_get_report(FU_HID_DEVICE(self), self->offer_get_report.id, buf_in->data, buf_in->len, FU_CFU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) { g_prefix_error(error, "failed to get offer response: "); return FALSE; } st = fu_struct_cfu_offer_rsp_parse(buf_in->data, buf_in->len, 0x1, error); if (st == NULL) return FALSE; if (fu_struct_cfu_offer_rsp_get_token(st) != fu_cfu_offer_get_token(FU_CFU_OFFER(firmware))) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "offer token invalid: got %02x but expected %02x", fu_struct_cfu_offer_rsp_get_token(st), fu_cfu_offer_get_token(FU_CFU_OFFER(firmware))); return FALSE; } if (fu_struct_cfu_offer_rsp_get_status(st) != FU_CFU_OFFER_STATUS_ACCEPT) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "offer not supported: %s: %s", fu_cfu_offer_status_to_string(fu_struct_cfu_offer_rsp_get_status(st)), fu_cfu_rr_code_to_string(fu_struct_cfu_offer_rsp_get_rr_code(st))); return FALSE; } /* success */ return TRUE; } static gboolean fu_cfu_device_send_payload(FuCfuDevice *self, FuFirmware *firmware, FuProgress *progress, GError **error) { g_autoptr(GPtrArray) chunks = NULL; /* write each chunk */ chunks = fu_firmware_get_chunks(firmware, error); if (chunks == NULL) return FALSE; fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); g_autoptr(GByteArray) buf_in = g_byte_array_new(); g_autoptr(GByteArray) buf_out = g_byte_array_new(); g_autoptr(GByteArray) st_req = fu_struct_cfu_content_req_new(); g_autoptr(GByteArray) st_rsp = NULL; /* build */ if (i == 0) { fu_struct_cfu_content_req_set_flags(st_req, FU_CFU_CONTENT_FLAG_FIRST_BLOCK); } else if (i == chunks->len - 1) { fu_struct_cfu_content_req_set_flags(st_req, FU_CFU_CONTENT_FLAG_LAST_BLOCK); } fu_struct_cfu_content_req_set_data_length(st_req, fu_chunk_get_data_sz(chk)); fu_struct_cfu_content_req_set_seq_number(st_req, i); fu_struct_cfu_content_req_set_address(st_req, fu_chunk_get_address(chk)); fu_byte_array_append_uint8(buf_out, self->content_set_report.id); g_byte_array_append(buf_out, st_req->data, st_req->len); g_byte_array_append(buf_out, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); fu_byte_array_set_size(buf_out, self->content_set_report.ct + 1, 0x0); /* SetReport */ if (!fu_hid_device_set_report(FU_HID_DEVICE(self), self->content_set_report.id, buf_out->data, buf_out->len, FU_CFU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to send payload: "); return FALSE; } /* GetReport */ fu_byte_array_append_uint8(buf_in, self->content_get_report.id); fu_byte_array_set_size(buf_in, self->content_get_report.ct + 1, 0x0); if (!fu_hid_device_get_report(FU_HID_DEVICE(self), self->content_get_report.id, buf_in->data, buf_in->len, FU_CFU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) { g_prefix_error(error, "failed to get payload response: "); return FALSE; } st_rsp = fu_struct_cfu_content_rsp_parse(buf_in->data, buf_in->len, 0x1, error); if (st_rsp == NULL) return FALSE; /* verify */ if (fu_struct_cfu_content_rsp_get_seq_number(st_rsp) != fu_struct_cfu_content_req_get_seq_number(st_req)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "sequence number invalid 0x%x: expected 0x%x", fu_struct_cfu_content_rsp_get_seq_number(st_rsp), fu_struct_cfu_content_req_get_seq_number(st_req)); return FALSE; } if (fu_struct_cfu_content_rsp_get_status(st_rsp) != FU_CFU_CONTENT_STATUS_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to send chunk %u: %s", i + 1, fu_cfu_content_status_to_string( fu_struct_cfu_content_rsp_get_status(st_rsp))); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_cfu_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuCfuDevice *self = FU_CFU_DEVICE(device); g_autoptr(FuFirmware) fw_offer = NULL; g_autoptr(FuFirmware) fw_payload = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "start-entire"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "start-offer"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "offer"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "payload"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "end-offer"); /* get both images */ fw_offer = fu_firmware_get_image_by_id(firmware, FU_FIRMWARE_ID_HEADER, error); if (fw_offer == NULL) return FALSE; fw_payload = fu_firmware_get_image_by_id(firmware, FU_FIRMWARE_ID_PAYLOAD, error); if (fw_payload == NULL) return FALSE; /* host is now initialized */ if (!fu_cfu_device_send_offer_info(self, FU_CFU_OFFER_INFO_CODE_START_ENTIRE_TRANSACTION, error)) return FALSE; fu_progress_step_done(progress); /* send offer */ if (!fu_cfu_device_send_offer_info(self, FU_CFU_OFFER_INFO_CODE_START_OFFER_LIST, error)) return FALSE; fu_progress_step_done(progress); if (!fu_cfu_device_send_offer(self, fw_offer, fu_progress_get_child(progress), flags, error)) return FALSE; fu_progress_step_done(progress); /* send payload */ if (!fu_cfu_device_send_payload(self, fw_payload, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* all done */ if (!fu_cfu_device_send_offer_info(self, FU_CFU_OFFER_INFO_CODE_END_OFFER_LIST, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } /* find report properties from usage */ static gboolean fu_cfu_device_ensure_map_item(FuHidDescriptor *descriptor, FuCfuDeviceMap *map, GError **error) { g_autoptr(FuFirmware) item_ct = NULL; g_autoptr(FuFirmware) item_id = NULL; g_autoptr(FuHidReport) report = NULL; report = fu_hid_descriptor_find_report(FU_HID_DESCRIPTOR(descriptor), error, "usage", map->op, NULL); if (report == NULL) return FALSE; item_id = fu_firmware_get_image_by_id(FU_FIRMWARE(report), "report-id", error); if (item_id == NULL) return FALSE; map->id = fu_hid_report_item_get_value(FU_HID_REPORT_ITEM(item_id)); item_ct = fu_firmware_get_image_by_id(FU_FIRMWARE(report), "report-count", error); if (item_ct == NULL) return FALSE; map->ct = fu_hid_report_item_get_value(FU_HID_REPORT_ITEM(item_ct)); return TRUE; } static gboolean fu_cfu_device_setup(FuDevice *device, GError **error) { FuCfuDevice *self = FU_CFU_DEVICE(device); guint8 component_cnt = 0; gsize offset = 0; g_autoptr(GHashTable) modules_by_cid = NULL; g_autoptr(GByteArray) st = NULL; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(FuHidDescriptor) descriptor = NULL; /* FuHidDevice->setup */ if (!FU_DEVICE_CLASS(fu_cfu_device_parent_class)->setup(device, error)) return FALSE; /* weirdly, use the in EP if out is missing */ if (fu_hid_device_get_ep_addr_out(FU_HID_DEVICE(device)) == 0x0) { fu_hid_device_set_ep_addr_out(FU_HID_DEVICE(device), fu_hid_device_get_ep_addr_in(FU_HID_DEVICE(device))); } descriptor = fu_hid_device_parse_descriptor(FU_HID_DEVICE(device), error); if (descriptor == NULL) return FALSE; if (!fu_cfu_device_ensure_map_item(descriptor, &self->version_get_report, error)) return FALSE; if (!fu_cfu_device_ensure_map_item(descriptor, &self->offer_set_report, error)) return FALSE; if (!fu_cfu_device_ensure_map_item(descriptor, &self->offer_get_report, error)) return FALSE; if (!fu_cfu_device_ensure_map_item(descriptor, &self->content_set_report, error)) return FALSE; if (!fu_cfu_device_ensure_map_item(descriptor, &self->content_get_report, error)) return FALSE; /* get version */ fu_byte_array_append_uint8(buf, self->version_get_report.id); fu_byte_array_set_size(buf, self->version_get_report.ct + 0x1, 0x0); if (!fu_hid_device_get_report(FU_HID_DEVICE(device), self->version_get_report.id, buf->data, buf->len, FU_CFU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; st = fu_struct_cfu_get_version_rsp_parse(buf->data, buf->len, 0x1, error); if (st == NULL) return FALSE; self->protocol_version = fu_struct_cfu_get_version_rsp_get_flags(st) & 0b1111; /* keep track of all modules so we can work out which are dual bank */ modules_by_cid = g_hash_table_new(g_direct_hash, g_direct_equal); /* read each component module version */ offset += 0x1 + st->len; component_cnt = fu_struct_cfu_get_version_rsp_get_component_cnt(st); for (guint i = 0; i < component_cnt; i++) { g_autoptr(FuCfuModule) module = fu_cfu_module_new(device); FuCfuModule *module_tmp; if (!fu_cfu_module_setup(module, buf->data, buf->len, offset, error)) return FALSE; fu_device_add_child(device, FU_DEVICE(module)); /* same module already exists, so mark both as being dual bank */ module_tmp = g_hash_table_lookup(modules_by_cid, GINT_TO_POINTER(fu_cfu_module_get_component_id(module))); if (module_tmp != NULL) { fu_device_add_flag(FU_DEVICE(module), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(module_tmp), FWUPD_DEVICE_FLAG_DUAL_IMAGE); } else { g_hash_table_insert(modules_by_cid, GINT_TO_POINTER(fu_cfu_module_get_component_id(module)), module); } /* done */ offset += FU_STRUCT_CFU_GET_VERSION_RSP_COMPONENT_SIZE; } /* success */ return TRUE; } static gboolean fu_cfu_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuCfuDevice *self = FU_CFU_DEVICE(device); /* load from quirks */ if (g_strcmp0(key, "CfuVersionGetReport") == 0) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0x0, G_MAXUINT8, error)) return FALSE; self->version_get_report.op = tmp; return TRUE; } if (g_strcmp0(key, "CfuOfferSetReport") == 0) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0x0, G_MAXUINT8, error)) return FALSE; self->offer_set_report.op = tmp; return TRUE; } if (g_strcmp0(key, "CfuOfferGetReport") == 0) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0x0, G_MAXUINT8, error)) return FALSE; self->offer_get_report.op = tmp; return TRUE; } if (g_strcmp0(key, "CfuContentSetReport") == 0) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0x0, G_MAXUINT8, error)) return FALSE; self->content_set_report.op = tmp; return TRUE; } if (g_strcmp0(key, "CfuContentGetReport") == 0) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0x0, G_MAXUINT8, error)) return FALSE; self->content_get_report.op = tmp; return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_cfu_device_init(FuCfuDevice *self) { /* values taken from CFU/Tools/ComponentFirmwareUpdateStandAloneToolSample/README.md */ self->version_get_report.op = 0x62; self->offer_set_report.op = 0x8A; self->offer_get_report.op = 0x8E; self->content_set_report.op = 0x61; self->content_get_report.op = 0x66; fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_AUTODETECT_EPS); fu_device_register_private_flag(FU_DEVICE(self), FU_CFU_DEVICE_FLAG_SEND_OFFER_INFO, "send-offer-info"); } static void fu_cfu_device_class_init(FuCfuDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_cfu_device_setup; klass_device->to_string = fu_cfu_device_to_string; klass_device->write_firmware = fu_cfu_device_write_firmware; klass_device->set_quirk_kv = fu_cfu_device_set_quirk_kv; } fwupd-1.9.16/plugins/cfu/fu-cfu-device.h000066400000000000000000000004301460375044200177700ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CFU_DEVICE (fu_cfu_device_get_type()) G_DECLARE_FINAL_TYPE(FuCfuDevice, fu_cfu_device, FU, CFU_DEVICE, FuHidDevice) fwupd-1.9.16/plugins/cfu/fu-cfu-module.c000066400000000000000000000137461460375044200200270ustar00rootroot00000000000000/*# * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-cfu-module.h" #include "fu-cfu-struct.h" struct _FuCfuModule { FuDevice parent_instance; guint8 component_id; guint8 bank; }; G_DEFINE_TYPE(FuCfuModule, fu_cfu_module, FU_TYPE_DEVICE) static void fu_cfu_module_to_string(FuDevice *device, guint idt, GString *str) { FuCfuModule *self = FU_CFU_MODULE(device); fu_string_append_kx(str, idt, "ComponentId", self->component_id); fu_string_append_kx(str, idt, "Bank", self->bank); } guint8 fu_cfu_module_get_component_id(FuCfuModule *self) { return self->component_id; } gboolean fu_cfu_module_setup(FuCfuModule *self, const guint8 *buf, gsize bufsz, gsize offset, GError **error) { FuDevice *device = FU_DEVICE(self); FuDevice *parent = fu_device_get_proxy(device); g_autofree gchar *logical_id = NULL; g_autoptr(GByteArray) st = NULL; /* parse */ st = fu_struct_cfu_get_version_rsp_component_parse(buf, bufsz, offset, error); if (st == NULL) return FALSE; /* these GUIDs may cause the name or version-format to be overwritten */ self->component_id = fu_struct_cfu_get_version_rsp_component_get_component_id(st); fu_device_add_instance_u8(device, "CID", self->component_id); if (!fu_device_build_instance_id(device, error, "USB", "VID", "PID", NULL)) return FALSE; if (!fu_device_build_instance_id(device, error, "USB", "VID", "PID", "CID", NULL)) return FALSE; /* bank */ self->bank = fu_struct_cfu_get_version_rsp_component_get_flags(st) & 0b11; fu_device_add_instance_u4(device, "BANK", self->bank); if (!fu_device_build_instance_id(device, error, "USB", "VID", "PID", "CID", "BANK", NULL)) return FALSE; /* set name, if not already set using a quirk */ if (fu_device_get_name(device) == NULL) { g_autofree gchar *name = NULL; name = g_strdup_printf("%s (0x%02X:0x%02x)", fu_device_get_name(parent), self->component_id, self->bank); fu_device_set_name(device, name); } /* version */ fu_device_set_version_u32(device, fu_struct_cfu_get_version_rsp_component_get_fw_version(st)); /* logical ID */ logical_id = g_strdup_printf("CID:0x%02x,BANK:0x%02x", self->component_id, self->bank); fu_device_set_logical_id(device, logical_id); /* success */ return TRUE; } static FuFirmware * fu_cfu_module_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_firmware_new(); g_autoptr(FuFirmware) firmware_archive = fu_archive_firmware_new(); g_autoptr(FuFirmware) fw_offer = NULL; g_autoptr(FuFirmware) fw_payload = NULL; g_autoptr(FuFirmware) offer = fu_cfu_offer_new(); g_autoptr(FuFirmware) payload = fu_cfu_payload_new(); g_autoptr(GBytes) blob_offer = NULL; g_autoptr(GBytes) blob_payload = NULL; /* parse archive */ if (!fu_firmware_parse(firmware_archive, fw, flags, error)) return NULL; /* offer */ fw_offer = fu_archive_firmware_get_image_fnmatch(FU_ARCHIVE_FIRMWARE(firmware_archive), "*.offer.bin", error); if (fw_offer == NULL) return NULL; blob_offer = fu_firmware_get_bytes(fw_offer, NULL); if (blob_offer == NULL) return NULL; if (!fu_firmware_parse(offer, blob_offer, flags, error)) return NULL; fu_firmware_set_id(offer, FU_FIRMWARE_ID_HEADER); fu_firmware_add_image(firmware, offer); /* payload */ fw_payload = fu_archive_firmware_get_image_fnmatch(FU_ARCHIVE_FIRMWARE(firmware_archive), "*.payload.bin", error); if (fw_payload == NULL) return NULL; blob_payload = fu_firmware_get_bytes(fw_payload, NULL); if (blob_payload == NULL) return NULL; if (!fu_firmware_parse(payload, blob_payload, flags, error)) return NULL; fu_firmware_set_id(payload, FU_FIRMWARE_ID_PAYLOAD); fu_firmware_add_image(firmware, payload); /* success */ return g_steal_pointer(&firmware); } static gboolean fu_cfu_module_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDevice *proxy; FuDeviceClass *klass_proxy; /* process by the parent */ proxy = fu_device_get_proxy(device); if (proxy == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no proxy device assigned"); return FALSE; } klass_proxy = FU_DEVICE_GET_CLASS(proxy); return klass_proxy->write_firmware(proxy, firmware, progress, flags, error); } static void fu_cfu_module_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 96, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_cfu_module_init(FuCfuModule *self) { fu_device_add_protocol(FU_DEVICE(self), "com.microsoft.cfu"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_SURFACE); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_ARCHIVE_FIRMWARE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); } static void fu_cfu_module_class_init(FuCfuModuleClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_cfu_module_to_string; klass_device->prepare_firmware = fu_cfu_module_prepare_firmware; klass_device->write_firmware = fu_cfu_module_write_firmware; klass_device->set_progress = fu_cfu_module_set_progress; } FuCfuModule * fu_cfu_module_new(FuDevice *parent) { FuCfuModule *self; self = g_object_new(FU_TYPE_CFU_MODULE, "proxy", parent, "parent", parent, NULL); return self; } fwupd-1.9.16/plugins/cfu/fu-cfu-module.h000066400000000000000000000010121460375044200200130ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CFU_MODULE (fu_cfu_module_get_type()) G_DECLARE_FINAL_TYPE(FuCfuModule, fu_cfu_module, FU, CFU_MODULE, FuDevice) guint8 fu_cfu_module_get_component_id(FuCfuModule *self); gboolean fu_cfu_module_setup(FuCfuModule *self, const guint8 *buf, gsize bufsz, gsize offset, GError **error); FuCfuModule * fu_cfu_module_new(FuDevice *parent); fwupd-1.9.16/plugins/cfu/fu-cfu-plugin.c000066400000000000000000000017461460375044200200350ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-cfu-device.h" #include "fu-cfu-plugin.h" struct _FuCfuPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuCfuPlugin, fu_cfu_plugin, FU_TYPE_PLUGIN) static void fu_cfu_plugin_init(FuCfuPlugin *self) { } static void fu_cfu_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "CfuVersionGetReport"); fu_context_add_quirk_key(ctx, "CfuOfferSetReport"); fu_context_add_quirk_key(ctx, "CfuOfferGetReport"); fu_context_add_quirk_key(ctx, "CfuContentSetReport"); fu_context_add_quirk_key(ctx, "CfuContentGetReport"); fu_plugin_add_device_gtype(plugin, FU_TYPE_CFU_DEVICE); } static void fu_cfu_plugin_class_init(FuCfuPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_cfu_plugin_constructed; } fwupd-1.9.16/plugins/cfu/fu-cfu-plugin.h000066400000000000000000000003371460375044200200350ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuCfuPlugin, fu_cfu_plugin, FU, CFU_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/cfu/fu-cfu.rs000066400000000000000000000043021460375044200167320ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // Copyright (C) 2021 Michael Cheng // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, Parse)] struct CfuGetVersionRsp { component_cnt: u8, _reserved: u16le, flags: u8, } #[derive(New, Parse)] struct CfuGetVersionRspComponent { fw_version: u32le, flags: u8, component_id: u8, _vendor_specific: u16le, } #[derive(ToString)] #[repr(u8)] enum CfuOfferInfoCode { StartEntireTransaction = 0x00, StartOfferList = 0x01, EndOfferList = 0x02, } #[derive(ToString)] #[repr(u8)] enum CfuRrCode { OfferRejectOldFirmware = 0x00, OfferRejectInvComponent = 0x01, UpdateOfferSwapPending = 0x02, WrongBank = 0x04, SignRule = 0xE0, VerReleaseDebug = 0xE1, DebugSameVersion = 0xE2, None = 0xFF, } #[derive(ToString)] #[repr(u8)] enum CfuOfferStatus { Skip = 0x00, Accept = 0x01, Reject = 0x02, Busy = 0x03, Command = 0x04, CmdNotSupported = 0xFF, } #[derive(New)] struct CfuOfferInfoReq { code: CfuOfferInfoCode, _reserved1: u8, component_id: u8 == 0xFF, token: u8 == 0xDE, // chosen by dice roll _reserved2: [u8; 12], } #[derive(Parse)] struct CfuOfferRsp { _reserved1: [u8; 3], token: u8, _reserved2: [u8; 4], rr_code: CfuRrCode, _reserved3: [u8; 3], status: CfuOfferStatus, _reserved3: [u8; 3], } #[repr(u8)] enum CfuContentFlag { Verify = 0x08, TestReplaceFilesystem = 0x20, LastBlock = 0x40, FirstBlock = 0x80, } #[derive(ToString)] #[repr(u8)] enum CfuContentStatus { Success = 0x00, ErrorPrepare = 0x01, ErrorWrite = 0x02, ErrorComplete = 0x03, ErrorVerify = 0x04, ErrorCrc = 0x05, ErrorSignature = 0x06, ErrorVersion = 0x07, SwapPending = 0x08, ErrorInvalidAddr 0x09, ErrorNoOffer = 0x0A, ErrorInvalid = 0x0B, } #[derive(New, Getters)] struct CfuContentReq { flags: CfuContentFlag, data_length: u8, seq_number: u16le, address: u32le, } #[derive(Parse)] struct CfuContentRsp { seq_number: u16le, _reserved1: u16le, status: CfuContentStatus, _reserved2: [u8; 3], _reserved3: [u8; 4], _reserved4: [u8; 4], } fwupd-1.9.16/plugins/cfu/meson.build000066400000000000000000000015111460375044200173400ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginCfu"'] plugins += {meson.current_source_dir().split('/')[-1]: true} # do not use structgen as these files are used in the elanfp plugin too... cfu_rs = custom_target('fu-cfu-rs', input: 'fu-cfu.rs', output: ['fu-cfu-struct.c', 'fu-cfu-struct.h'], command: [ python3, join_paths(meson.project_source_root(), 'libfwupdplugin', 'rustgen.py'), '@INPUT@', '@OUTPUT0@', '@OUTPUT1@', ], ) plugin_quirks += files('cfu.quirk') plugin_builtin_cfu = static_library('fu_plugin_cfu', cfu_rs, sources: [ 'fu-cfu-device.c', 'fu-cfu-module.c', 'fu-cfu-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_cfu plugincfu_incdir = include_directories('.') endif fwupd-1.9.16/plugins/ch341a/000077500000000000000000000000001460375044200154065ustar00rootroot00000000000000fwupd-1.9.16/plugins/ch341a/README.md000066400000000000000000000033601460375044200166670ustar00rootroot00000000000000--- title: Plugin: CH341A --- ## Introduction The CH341A is an affordable SPI programmer. The assumed map between UIO command bits, pins on CH341A chip and pins on SPI chip: UIO CH341A SPI CH341A 0 D0/15 CS/1 CS0 1 D1/16 unused CS1 2 D2/17 unused CS2 3 D3/18 SCK/6 DCK 4 D4/19 unused DOUT2 5 D5/20 SI/5 DOUBT 6 D6/21 unused DIN2 7 D7/22 SO/2 DIN IMPORTANT NOTE: You must perform the 3.3V signal output modification if you are using the CH341A with 3.3V SPI chips. The CH341A has a design flaw that outputs 5V on the MISO and MOSI pins even when VCC is 3V which will almost certainly be out-of-specification for the device you are trying to program. ![CH341A Signal Output Modification](ch341a-vmod.png) See [this guide](https://www.chucknemeth.com/usb-devices/ch341a/3v-ch341a-mod) for more details. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob of unspecified format. This plugin supports the following protocol ID: - `org.jedec.cfi` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. - `USB\VID_1A86&PID_5512` ## Update Behavior The device programs devices in raw mode, and can best be used with `fwupdtool`. To write an image, use `sudo fwupdtool --plugins ch341a install-blob firmware.bin` and to backup the contents of a SPI device use `sudo fwupdtool --plugins ch341a firmware-dump backup.bin` ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x1A86` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.8.0`. fwupd-1.9.16/plugins/ch341a/ch341a-vmod.png000066400000000000000000012003041460375044200200420ustar00rootroot00000000000000PNG  IHDRjT pHYs.#.#x?v IDATxLYdu2jh H'I_hG(l?!Pؒ"#"{8>g=ykSJqr.c P՘)@}q̚HU٬>}Rm;wUh}ÏcםW+AeϾ|߻<2QLՀHU@a0TB LL7W.>!ȯ/cy"j'}ы?~hh"Lf6U1 8-!`CMneΞvɠik%D#-Zy(f"vNL"ˆ!|UUlŘJΩdD&I ֫=xq~J.}!;_{/6]7Ϯig}JvξUEլ]|[E8{0by" 9I}\=={z<l>o'?|* %OɌ[wg`!c_ sj47ܚ-<Ed!"8Hu Twy{j`Rr.XXugg˓[s0|ٮr{@BªMZU !K)fX,w~U jD͔Rq ٍ//ET$zb1vm۶evj{|UU P5h5ռmS1%v$#bǘE:Dtw"Q5zTJ1v)*D ˧kBryr~8p)&@4ST5D4vqHUu伊0Gg;~F@xgO={z\]iuz[.^\ړ\{wnu}.'g/{Ɲbnyd:Tn쟞OP3QmqTC=~N iOY\"Lp|t c`Q`y l4톮W_jg?l٧o>xw?5"4M0"R1JMa,%#p붝/|=JM옙L PŬHwyGw9"|U[mf#hTޫv׵VDcץ>޾}zơ"s."{BCGFֶպ۲sS~Ϟ~Tf㠆>p1˫nؓ 2!̦MdfQDnٍWa]O `pf;/VWMݠA)"EsfuӴM `denڦr>zS))NNOf^Uã!D`wi bL!9Rb·;ˣvX_ܺukoiZ\ qĵwTҮ[?~r|8sh^]/wwF82"HO?zY6\&R4bU αc9QAqiCs))G "<)瘒H)%{s̜sȻ,"%hnYu]7Q*TvSS5 320BʇRKN0s'yJUnv쇫K5!$3"pyt0_֪2Tv{¬m`Y?{l>{-RȹIy]?q1!""b)B``fČRN"sNET1ߺyxyo},djܸ}ު89e#hg򵯽!q<佟Wg7;9Ŷi$ |3{w^6^>~rZ9_{[kjln\\wh?֫u蠪jsuuy0kѬzG! GrifŔ(b61>zJLN4mc __?|ѐ o p߿iDZۺbNfʌ``*pa///R,PH79Ïw"/]ٹ`c o:KǍ=d$bl|uy+UqPUUNyE9>r;Y;C:oI{r 3\7ON~ōw׿OD28D#N|fY  ̹|>{,"B-1_j;K3E&,NDC]M]5sCN9XTb8XU}O(KPjs%Zn|evZrqBr伯ۓw|kkfsMd`i7 Tj^ q(stbWJ.{w@ýд1;NPsIbv&@EQq̬f|1[,S !@.WQU'o:ƘR$"3;fs9iiƘH"FRpuDRI)EfSDd0DS!YP9sR9_jn6 c21t<(L6R4B(emߝfm}zt,rU5ãC $B C^_#b6QA|X{ml7fw>cvǔLAĈLS*"uj"0%5S&1Nwn 0p9DU~]{ռW3\GP1˅|=]~䜔 Im3d_ږ=GhV 8s$73Fݯb?'>{!z~._7!4ܺ?o|Q +#ϠM7ں=9<;Ip1:h("sTfU5"*IF#{T,ӝêbDA1s΅P8G?ܺucٳ#U}8yx­1&Y9 dR(ys5mXrNSBEm&R`جHXM&^mvE2) yT=3sdhyb'tԁ1#'QJ"~7Y(|߫;O!RUUS~O;׸/>*"S;""B3PU UDv>gs\R9}l6;=>~ŧCjzvgzMbh.C=0gtM={^ !0q<:lZا"ݐkCҨ\W~ykCLcL!z2qYg_~l 9tiRHK9dfj۶i2 8uZU󁃯0KTpӍEG<ᵪLqR  R@ DYMB|fH("ۺQUaT&IjR@ ~Řn"rtqD"Ŝ6,blo@’r)f]ơOh`;DT`,eĜ6U;9:.888Uj21*PJwpzzRUUcW1,RLFCY2t̄9JJCbw1zxyS~}Ţ%t{99P$C,]/2kf*þK݀H>4Q~= Eo>/"Fg??f}~nlٳvUfED *r,NjZ[c~8t(g>2gտ_ێԹ`V6l6s֓_ @)iLs>;>sD4V{ً͌כ!fN{|'jD*z{>Bg??:&x45RɁv!1YVB)RbNU̇YSTi70SU@&f9٧YG D_GLB@DDFL5>ik\VM]Qxl.6ɟl>/脈lL4v~`v.7"0mҏl?j3&D ]R{^x?s4 ~߾xqN=5u]R鷫M{6:B{|x0q~f1)'DΡdEz3U5B"vm3!BA۶g&}%10 h6L8b?ӭgRB4rv,=::ڮsg<3 e3USDrlhH<{11զ"D$"k0$R$猈uUyPH^X\4U])cPDhr}),Z-%:0 /ESJ1O/M20@Q[v~jfPQbF@3RS)C1: kg??Q RE >MM}x.]SMTu[f[P^|O~+Y{tVpvgm`RJ14ψ+N L bcIvSa?.~6q81C~ ,LJJɌjtU=;f h9rΕ1NRɹ(:̦BPՔu]v)rHΡ"""B!`f#BcsrΒڦffm`9T!oEJ)U1" %K]UUJ.f(zbDEU1 }&;fRƮY8t]/񷪂("3ͥuh;6zR'GDSʓ5UHS6 H!9R1M~X)eٔRdY,j[v6#ܭ ƨdjW'S!\@䪮8D8ZΝ8dzu۸n, wCv|wo>rMp s M I= C!-΅8pOvڑiO}Ift۶._<\[oX]˶ P5ރbO%YFm_~˔"N*MjdjݪHH:>nxDeLP jHBQ!tVY @r .kc"Bt\јЂwj3 P9"@Ts / Gywf}@40LJQUlj`haH h'eIUU9sj}<! Ad,eRރM*䚦9D<ֈ`bvPFrP4øۮwm=`B 2ptcgO2 Kp<}Le"舧tRTJ)9vDdONf4 )e99<<]_޼f% fq J{&9ɀ n[#w`hj} h[BcZ!b7.Yɥ$"6"4Ryrޏ)8|6a^{.EbL)ipy2)vU2 }?#!-P HF@3ADc&R3$p> \TIlSW8szy0Yw VXJQuKJoؤTbτuMXooLbKE`ʔ4Q#z0#f̤!dS뺮ݻwn) 0dM+M_%}v,RT˧ ƘjWoO޳gc׫]p)u]n8!o߼VϮW;j0 HU6!Kqf0 SN'p<O|$Ϯg7Ƹ%z4tn{KNf=#U IDATTEsu]+YYSwE;zadO9'!aXm6,w<~7K_<}ẽ:&GHVPWJqB4)M !#'D.%~e3n6!e-yU%I}xgV\૮;8o;UU|iL3xoC]9vnbY@A9uۭ`)e"jnjǔ|.^jZL2TRJ d!r)s HDq11|> &SB\o-L~rNǔ/ЩMAv֕)3Z~b)Iyuӏzf-fXU "@II%K)U'O>y@@H)qĚ 8lzgPf&ck BUьTCt HIn4lyrKC5fv9)+)=f'L`9~ם*O_>ofY9\bC9wCڹs͔"c,y]XH ġ)Hg4D3 ARI2/6b~+z!C.,Bef6 DH1gr$I6U#{W3OƿKԒWNGid;N 8 ]lOYK@s.""l/EDd<ַɧuZ 6@g/SQdRȹi).Co}oտsJ̉$|f  B۶)ESx_DqA`CBۯgǢjO>am֢"*@frTrvY3q]c;::nq)䶩ҰÐj{-83-UuU7G9a]_ $C50E0$aP)~G0_zYuþC4ٗ3K0l "Ij)8g1%~BV!Ҥ*wX:ǦHSm#Yeud-9纩c. }rf&b"C3r5ܰ**{ؑcjj\OKK"eZedDu0F-}01 ;rn:x"03cŅ>Q)`@L,YO?luacRU82s./R!p!r8o$mZUUU΢4:FE<JQPAs䜆j80\IԊ  "?jhesċݽ{ttx:mc"%B2D $޹} M^so{}PkޛϏ>sEyܯ Ќ|5vg7E>;;a*LJn^ )v]~GW/7Lˀ,x_ziک(E&ALǔbN8mnVW|AŻ|O;{d$rbK&}NOv߭EtOc.GGǎ98DLĆaC|qvLeMl֛vI Myvtt|p}yQr!=tw`z<4Ȑ]_ PDRDJy_g{jg/bLUUSS0 KliQ YS_Jfbilۋ2$O- 9BBP0S5I[qH{^Zb-r`yD7)Cp<})~^TBS ;7oI)9'ԝ'1&f'cSDǸ9ǎ*x)Jw/|1F5UtU-f&jh*"4)%0Wu( /oRJUUL41@$Ev8Dge4LȈMӌn6RbIcӄ*e~q;br~뇔Rfeحr_n8v9Vu= @f''" }v%@4Sɔ r {GZig9qfM{~_/"1Ɖ#RU)e/Dyfy22jR,RDr.|U8>9l6^Jiꪪڶ- 0yeǩ[h'7jbSWD('屯iy0quubQ5m;9 ]i)C)q, ͠mfcvP5M=309ﶲjW!2!upΥ\lJND)IGJ4wO~ơK.Z5m}7`@vu!9{cNc]O*61˗ PP%}욺6M&CRCh*vXwnz W7NNbqFQ q̒כGG*ч] 5?;w_姟?~UɣXAv@f)sD4|qq9Qzsrrc\v1M;㪹;?'͹~y||w|\4F#]TUUt/sNK9}II$(O) 4#TrQ'aX6 j=!̫Mȿӈ0h?) c40EL&68N|>0"\*azr)yme(bfCN˃u3Λ=ˇ竚V$D솴OOOaηÏ?s2mSI\\ǜc.856hmg]i`ɹ|hH%vzUDLIpd=}s{&]^\fW^>0h6y«UJ mT)6^vuyNK|մ}qJ:(XnmTiUzӟɲ/s]fUU39$AH%J0`C D EQttWWWn{pFB}(@n<'"  tj01yv FD 7Ϲ\a?d<fɬb%RJ˧DL״mhUm;S)>~cQ)^^\v{$ZKdaUT2ɓ?>Z~ǘݿ\j.95MӅ!X@vDTNXSХSbW3`4К~j蓟;WO'â1M^WgRjyZ-7:0pDE cZ.SW<~Jq!{$)ӌHTl\_"a""αaw_18;v`E__^]=SQ6>NUJ)L9oN49F"9/qׯn_yTDnPb.}g &NۻbZbLS-E 0;+".ꜩ朸9Φqc,âGrm[35RJ.YTp)!1o.3ZJ  y>ibD,E y 3Wj"sNb7wz7;v""mcΥ'~^_Ӵlۮ1chZs>O}J1K)\w9}Ӷ՗9f:[3gUr|8 K)NAGn1;@SB!xqb&ى$"c*꽯" )w}7q|_}^J) 6ϓzǫx<b<_y3N{G@mn޼~Y o߽z؆E'_m>3.aZ>%M믟mCVJlm˦ %].}8NWo7!_]^޼͟<')Na]4M,|}<"lb9ojpuy}Ir ۅ@j6O_~˯>c ?|oჇoƤ"vvy}?'Rd`W_z58죸]7\AN31)TBh*oJhRP䬘*cœēlHQj#2ߌ?XE4]\/ &-)SCZR.%@ggg_|֚M`b(F5jXhVRCj\z,'?>_n8Tu!(HMI>0K@0qJxDp;NRjQkٹs$c4լ) 䐸ძw~,+FX-7Tж|phC/?04舽s"SJ.)Y$M(ET;|r{e0@D |ty7 q  @ 5T-R1; R<(ZJ)9g駔2r8VZt H1,8??;VUj}  ]tj*buJmߥԌۮcvvn\x\B CPyf߁w޻PpۮZ ?+) VU:P,na? \qa>óoU d̹Au<M!TQ6L믏ݟ_(\]]Wys#XoiAy xs2qsVUM9rqo6Q#"yI'8{")Ω`~?S1[8LxSQ cG?S\tii<;9x]LGݢruiZOJ̒N;b!RvU-x; ^U*# ,RE9rzwѝz\S5jRAb{n{8t:WJ GaSJ&xg-o&gϦ<_^u뾡aȵs9DЇsuy~8E Mu} 3) }s1R3Q3T{O@>3壡M̉YByrvZ7nXy 8Kþ:{ }Sw3a;?@yH h"xb\tmWgj ErJhFfh`UJe)3M7 >4x^)ّov&2@JAŢZUUt|KdT/VggLn1E3YU5mBi߫0s"3sgg>~/"!0(%Wf,yD2obZ]6pwOa4]/TeSsٛR.9b TfP3X >s9TS@S@GͶĆ֦7qo~˗Ex]9hY.]@awŃeAYU.鸽b]JlzJ)ut*3UT}YT03{m;~ܯV~ $޽{Ewlcsǟ>h۶kZՏjx8c:u;smؚ!"0;BcM~BS./.}c3cBpر#cb)S2Qs{w[`^:Gq#i:+"9KQ"% !鼽ǽb|nTq jeYEr)c9Dh@'.)%WːwYXbbbإLUhD\rFNo*131rꑬ6f>W+\>sbgH2NSB6 "0h9is4L[,0,~y_m$mXNZΎ@-dyǔoNyS΀Zڮ9?\,YJ%8{)rQUL9DB Byl6V:q|nA _D0njEM$ZQfSc5M$ǨTԘE12#*X[wd 4*D5ƉġiJcRʎ+Cѳk]!8nv81#;DږuK)~?[S*KAI]̘k#%4,Cӝ_Ms[Ka YRDaӴۤ47m`Y\޻sNeѷ̎fS*Yhr؉c 3+EUiNQɿZ:KutOw5-1ɤ=1Zι;1h)O49s)CÔ1Mq>D_\$+& )o^<{TMCeO)\8@RDBXݓF5!(;Wuu][qM@yk4̜R};7@B5r[/b.bX7PTi;n7on%Ҭ$4󮞹 x8/./˗ (U\w45}0;3.P˞yx&P"` &ֶp$'˹U5K޹Po/.})@LfBH9wx˿wi&hNZJIsviS׶qQ׫j};M@KF!x?5!0ܜ~KݎQ1v {gD>?΋'?3I9Bhah6,b8i+FbD}ӶΩy9&ǔsvb&f,„ .bም)tv~ɾmTV4o8Y~2vBb`D94Doy g=!*3sɚRL\.KI9EŇ` HBcXy.9Ā1X..aۻv+"VԎ{oVaðhQRz50~﫯6 ݽyC MErJ_a[!4rD?'UhaL>??}5O94"ZRӧ_(4ΙH,R4TSjEjK%qL鎯;LEKdiOEڮjL1Z* DL TMNLMV k"*1* 1!#( #b&5GZvu甚ISʎѳ[C5O?~wH4,Zww"RbEM ʺla%Ј伷zP8 `)LM׺iwCgH%4TBpm#"Ss"@*愱kv|U<)DxǺq9,ϸ s= 9/˾KN=3iZB B)Mq#4M#UccA1vߋr9fw0ÔbgiqbMx9n6"LӸ;ߴzyy}].S!sA0 #iJpGDr\TH4P"4m o5>JD=Os~{>I)WOsa)9ԵݳU]Df=}C4@020y>9~c5B"3*"{ODMv7jﯯ_kcP)3?IIir"YDVv"0i_ XAW?yx_?| 3k V 5*"u8P1PNWժn6ةעySr 15H*jZfPu* `HŔ8177 VTr;U2ӟڋhlU :`S3D L@H4%y1 0TTt5 }=8=#808wvvYJD%ArbB*f*w-3-{I潫3F8L5,$"T@ 2>yL CE2R8J!,y \*ۦkD1Ti'ÅSJ̴ZMӎqݩSʉ=qR ػ*КSԔ<99]ӥ$v}r 9AsDB0~T͚V4!E(E/..ݰS!R  N{_ZN"*] !x$.Y*}pu Ḫ.>4M$`j|hGkIEpF{":8ͦ0MAMw.4|tðhv;s?'9;@S#"@IqDžٱs)3bVe$nv>6 qNUA-|_\߽-R!UTFf @T\f":??瑈bLsf<39&9o J.ه0Nq)eu9'SP *Ōy@$W1Al$UD`63)9Uz1@05+ Funԭ@JV訒7d8a d:Pi s̈$䜚S᰿l7)'އ?goܘRJ-3]HToPm "UfRE 4`"}ߕ {c"T%5vlGK.fYk#K*XDn޼IqfV RprGח޻O~Yy²p5,cLR PkG4OR2E38;{lG?sLؔ&aDQD 2D ةB,29N2DŪmHa9{\Cu-1xk$bjڶnۡuF?SN!8fZV~̹y# }9Dkڶ<h8s9̈2P nŜ=NK\gaޛ0RHfb8 Є01{݈YD̀ q쬲K"k[>vV]cUex<PJ~_sɓǵbQC../EE_ [M.eW$Y t;b$ƨjd`R51YJ.vtJf` 3-p.93KI%u fh؄izYANKfӄ4\pрЌ"#"xǿŋg([#y9'3E8z e|xo0 Ff:_^~/938`nf oZ8HRLmCbLooHyq;T.9{=;yfhb>|+aD3T%ۻ:~ꙩ-Σ+yviDxn@c2=l1g;R$MO0h!e7aL 1ϥrA4B)DSqf]>R2 -WFLvK1֡ tZ e@9ͦ*mx{=vmϧ%a<7],*_Y7BT QEQUHƬ9iIrbº ;OG%'P& OӤ}߫JH Ml].ooc輟zg)ru=\;mwV9)dB繆ƽ&N*fǾ_~9K]+Zv4Wb(.4՝.$$ۻ_,CSoJ*2cT@],zj/*msys3*j;expgiCߤiwe Ӽ=ЬmEG/^>Yۺi<]߼m[#/Ь 0"8s?}対xR]`b}T02[G?qj8fw$3jRӷB C|B{wePS]iѸEpyc@?ś9͋b*@6sXanh{q޵a 6mWEyۍyD3ocY,;20% r=zi[5Ŝ~OYMto~vwF!tjlc*EQ~Sf~x*sUzw>Ej!pq #OHS97o\U1oo*3cXv- }SDƩ8)EPJ!#V*%͕iNc9-9oXRif꒿Rn#rwmJdc-=Έ6't8Ea?sUrp8TL.BCĵl7 sMӌb5ThVjq,4SR1b33#srL_>~\ eZzڶ4#V ;ώ~cSHp H s!R%v@$jXDTL\?~o˫g_148惇ߏ1xf"r*8O]aG~LR)ﮑim|vMRBg1]^:bx~l%߄?Ÿ[4cE?4رoC0t]8_s28jŃPwyT 4ooW_Oǣ'Z-rۜcW֗;E}/Vԝֶ/^D;>+H&3'6Fr#t\ ԏ~#{`jmwss 'Ũ>:df>IU5)TD`"o޼N)B3H0kmf C:tDHLq%eN 1%jӵbyqq4ӑ9x9;nѭ.GC4Wxv1:瑨kك橤Y?◷jjۦa" *1#:v~<)&.(HE44Tp@E:DRÜ-T$Yچ{ybO"UĚJ 0r+3')α-rɪV/ݓ`tTDI^uZ9@"@vlDTJU ;G8<XJ%E"%2[f_ݭHa@ss^}  KIJ8R>09%c .%WYDM mzZ1fKZNۦaqb/{"p{wcqH0!!x/¨ZȤ1VhaQn)R`cZcK IDAT>Ĕ5q΄4LhM4/ZTdL_}iO}Ȯr6 ~ԭ SW.$n8iMPZjRZifDm E8[lX!Rx[k# m$e45E(!|v>@PZRP!BFQ4)@iKH[PFk`#//_hמ2!]ִm{o__nnG?C۴궮RLǏ/W/59l6?\,~IefPRh̼lUr"mh>?Ҧu|1?9?uKQzSUFo|>kW- N:tz۶Md:K!8n7D8#nF99ެ6x|vcCIY-"~uL?┝+ϘDY,&gVn14, & `Ά ^qef !$ά4[} _:_k4SND(h9d%r|xG| NSM^,֚nC~ҌZD@۲K^'uP2$5Bw9g-A[m!Lm'yPkgX dZkuiv:wizxۤrdfW○跿wۘngq3b PПYP#BuRQ+V̒, (IgD5L B 5j2s0TuR9p v2M1(4,2ꥰ-'AU`]B ZkU9,]׽*Qf1D.xrJ)g&)c4R"Ƙ(;S2ݮyhSW0 M]C)y %ڶiWFi ڳpCL4Rښ(,95<+c9kdrR^| ewR1F 8]2'k\o_<{v\ -F3ˌ0![ (e>$]l6NƇH˜Y1 J} 1 m+h5QYX״ӃqTE%Gh&39֎#fGq')(㳩1?_Z;.4N%s _yJKIkjijl;777X" M(1Y)YGEv aW?=:aX9t~qyxrzϾ싯&DC}ݻ <..ZNOgc?ES;@jj,!5:㰿fS^6ӥVO&C3Uw"K?+P7KD1%SF#!Ke9u8~4vbڶk[g%p|hCQ Ĕ&SE@%J앨j6 }9 # EAprrr}H1v{kuNi2n~ DFۦwcgD9+bYYc*[B.DNDB5x{"ݶ1Y)غbgt̉XUnXլnT"}0%Q„j/:p&*yYC:>ƴ^6s>XJ MSY#zˤ?8:mM3䨔uF/MS+>FiR(u*Gг0"JrUU0P >e~b\_,s9P$ůMSt}d) 5R֤aھ98<4lw[k*ޮYn2TcP*WLd}$V&cMG?+ BR"hܻBRl&mTn*$`qvvvoFCVGo U:<ʽ~ƻ/a߭wVWZk MMjm\}xx\Wu/O"Bd@1eV q@3픈FgO&跾MuO~Z߼λ7Vզ\\zgQ穼}v~% JE*Q k(+̠P!"ޗ,@ y]̺"aOZGp˺GD,XHch48CDJas!JrSq(黾[P@sg.ApFn޻׶Ӌ˫snՔr 1]]_Gc4^MuQ۪ZCJDD?= ֠2Z+  2ZHTn.#٬5hDd={ 1+bq^[s V zL5?8QСtLHRN,bc$[[E@$0kڶfNvbCYnެBLu;={蕣;'6Y`GfÀ G71b&BT8Ĥ)\Z2)뫫}/EӢ]ʹܶoBB KfCDUsӆP"9+Zk9u}quXk8$Ƙ|DYWW]/-RHF8O'S%Jkc]yW913!"J7U_WTSr_x/gg'q/ϗ7[mP˿rf}kD1v]Wfݾ/#!`bj*goxS_=!P DpV)E_Z~Hml>sdaRc~dΚ4}CT"c?B7߽{յ5CËSN eߦ6mpҾvjw+@chmFD ~i%*c$z7+]p% @B + 4q>VqU=!-"*ɋŝևhJ39tnmiDjh?*;9z|rȊB$X=*10zcy[v[ĜG?H>yxVݣusPs*ED뵱u;10OU Ҹ$fcbꖤ611`L se qE1nP̜ b6"@ 9K~krV511('!!SL#b;|%$tUUU.t}s@i\]dJɝScm9欍m' s 㸘bLy6zWY b H MFsTC?cȡ}ED\UMکRʏC)\GAIUt;Q9gFҜ[Gя^)([D0z@<88@,@|ɄHLT4KޏO{{ů~>{^>&IW⚶ѿw>8:}u8ME}?=9n*RrvjQLJMxez|z󭜕ݶ6_.vY>x'_mhu?byY]JZysGc׹2"f-,\Fơ~|{'suGoʃ/zrvr8i{mw8n^V_mC<:l\e1sj8ɝM뛋dTk!y?s+76mkWd2=\LS!S!֥Ql%P)(!M")F1Қ #AXqz("@9@N)@(ace"Nn;Y1l> 8ĐE(}ʷ" D8EtqpM])*DDUkɒcL!SnZ՜$s 3ǘE| KZ(M3DN:E@_\,2Ŷ֕Kc* IW)bbt^7N[@D2P(Ӥ¡<ҒN纪Cbv) ї.? ߩ=yz}ֵ5 (9>q|LxKA$ĔXi]]W_vs.*}#̜S*,. j 'g)%0g1Al6c,[x8LX#PWAa6KݻP)ŔbVYS#DMvĩ98X,X2u} !@IVvnv:lwv;D-nX糖B9?'"k\e2B`6V sC ]SVPҢ|ˊQDcf9E)gVHjxM&*!(q'_?y͔1ʨYWt~i]4{w. r\v0Wo~x:&N[a9=;|q&Mǟ_lU7כ!v>jf{Iڶ[Ʉ4!Y' #|>Nھ˚@ d a9 oǧ8vj\|o}պ?!.qLO9rQ I/ՓBM!FX12XU8xQ14mZf3"aJ~rVs91<8籈TS1'2ZDK"iRJ /&c# !Q¸`j>W(D_j&3\(֕ `o~]uZ}4uݘ iYYmXXX !ϿN) d.9ƭ5V0Ɣbq뗛-!+ECR> hB" ,BK(dS1&Pj#ASUuoW뛣IH"*\@1gMTtzL8:}fRw8Q sf@Q*Xu+9G%iV|ziRFӳϮ.'ɶk*8<Ql[ӉvʃUiZd[K3k˿n>'TZ[4CO9>Bk8|>Ūyx_)VFka!DheN1P|>[kgӺje|{{U 8+%0>ʹZsʜ/`B,~Hphs]ZSUti>g&m>(m[@PMNW~7ϟ=Heu6ۦjPZm,ۆ0O)\>= b11\+]ThSJdRCZk4u`鶡>V))۝yH h]M]W>Fh\+8 iQ[:=;[VϞ=4US*# J!$"ζo?٧ܶg~tدׂOkWԻM;IIL)C5k\5zSZf67vUdb+Wgp]׳n[}M&Ι}y?4JPAN)xp%ƠD&j~1Vu5̬qe[cc~Ǎ*X[eFgM:k3rF/> ],#f&$R F9ϟ?#21do'dq)w>~Y wJw/F48R)>cJ!n>e?A)ȉ1Zր1Hәp aT,)n'"Wt>~ods\3 U>bYû8_ѐBDO$sN9%Φ$ (g u602ic-~m7դmONSǘEU#z %<, K)mYrN)XS%85sdT5骪ۻ jooٔd3}@$M:CLZkY :k$1)m;,W2[H)!t:%*qD& }o-^ K:cTJU֊b?a1DABmm4ΏarjjWmbt>*&MO>Gϙ Q>xp0m+d-MgM?KѿxEJYJE =[gic*#`buQ9sJ|>ݾ{cёsb"[m]ʹݰKD)C))EFR(bݪkx4Qzp1of~8O)|]l IYD1jX.w[c_BNA+mӈ8 ?O>hLF)t7˫KPd?_=+Unݣaoۮ댱rBDwm)!(Le룃Wޝ/Gǯݷ6g/b5uvzݬD~JYrdzWQ,dC?z}?N&S >'NI4uY۪&ֆ[ M; Ŝ:CAM=)onBv'Gm)&ّӈDYUfVeZ% 1u",ֹW}2seP)f"%W~ !2uSBJ 9-y>z1 C?tbq'gz|~rh\}٧dS>c}?c77Ar5jS$mR&l6"84i2!$:Oc+F1R"MCfj]5bDJeQUwNN (4Tv#6 ~mM߯qubu<)rY0~b94IekH)~}THhr1ҳp"KB31f|޼<̙{.5lZ2.(t D5B1(@k]U71 SƆd)D-mLI=N&m!眬ֹǾ= \e'])cJ)D*fUzR!Zb?FXm ’9$)v%T .M$dօ8Xe)ZDp䯩dYfΙ^),irwǾ:ET^TNZktLAbue R>dšf?.nv9MP<٩,쓧[!?bij6=-s2/^nj"2qHa8n [3'''>~Fcf~Oi݂R?_w80/ϧދgO>}>"V~w/|6$ IDATf2NZԓg Q#sc. 1zf}C<<9wqvx #B%Xח0%UdLc5sv\z "M 6uYmV 1%!dfWղLrt[AfX&BsNctf3~3 H[DBJE@ QWMub ~cv2]mn}vxݲj/ϟ1oOc}Jҹ؈RQeH]X(%M,9eaL$/!au(I9^bb!(̜qn+#UbqtJ?#8*ƺgR{4 (e 8G80F2ڇa68 d56~ؙF&2`ُf}Ef<)@Fu'8瘙Hb&lۺ1'$[YGXRJ0R?6Z DzWe_L=jT_>wd߷,u{?z6駟t}oCG/<|j?~٭~}nܹwwպɐJ,?LEiE7~)L"V&!DkVP?jA5Hʞ(Th2]wkBa|#XJE ٍBH9Dad!k,&`>9i[> kL:"N"a2N7Sk )/>`ݭDZo֢n1-zǠ]>1sVjf PKA0h"n JR,Jf&t2KbsfA_\̚i &r$"U 93B[y<:8 Me~ !#Z;! 9ŔڛrT(Ѻ춻&#5QքTY2")TQ!Ĺ 11J cdZ%VLXRV9۶|-33+뵱&LF7M]׵R@̹{=BETJFS龖qDY |XެC}iL9ARD/25P-)[ƹtv c^"m7faNY5B֩kX' l6Y,); 5eΜccŘP^c%E Ji҅ĄD#-"!#B~U*AoJ"M~_  DDk sʄ0v]9vuǐcILΠTbo7_oۡkN9A`+6J"hmIߞHAju?4cBm37_\_pV)G?T9h]/B? fKcgmS?~{w6G^?>|~N0xWVŹU'9KJ*DӦi6d8+)$<>8$[":@e:5XWm[ Y ̧mw;D,_~Jx4 F몲xp0_LFg|W`:z~xU&1 T#2H|cN48* Դ4!S ả5Uo s6.plZcg0*(Pw|˫2產&SG$rVZ@38'N9#)HEX18|NbF&ɜB1Ppda^~oj~)(~?QIM&ՊsV,)auk+CfmwMUc)FI%*frAjnM;?vkݰ^ ^8kkE@++ ΐH`5Nb(JL(nrIFɊASEYTΥ*E1VsfNR1ƫ36Ċ5 jf2~}BPlK֛9W"U@+ژ}^8k tqE QN9DtK-j6`6NO=~R!H|AYLcCKfgt6ɜB!lcsHY3"dN9sTJ&u]9gmoo?T(RY맶fƜ1d`|-WޞX!MC).$E(h9RQ5u4 Xk%8FMDn4Ѡ^Dr2 zO@cnx''ǻ z4դZuJ8vP9x49WBXfFݰ\mͮ߆C; 믾Wݽ9/ Rfϭ׿*=/T$"ǜj?o"jFW{WWW?iM֚EXvyL,\UﲫD"Pg;wζ۽ֺiRt*s"iK\̶ns05P @9g%JΦWg)%D5iӲvf4|z|\Y\YUR ʁJ!t~ha@4I@Iytj5=ia/N?OV>e{}#W&M/.w~f@ETӶƒ*;]Jc}?tjy|qIm]O&vs ڦ1%%/,lu]}c,rșAJJUʖ92gLJw//bI__=]ӺrU e%wK?uK d8V¹|4wF}^ 뙝Iu@ 6$8>c>,Q8'9%ΒRꪜ3gQ* 7"rj9{6Zd}O&pq Bk9GRf%k2H}1pqߵM3]DrL0vbG,Yf6FⓈgI<ðSk6i>n0 caz0V-@QQԍ(QM到m~*ҝ,Tw²[YcLa}IkS^oH^9(s6Τ˒V"b9s VUaTދmӤ9fFDb&Ds*KAQnsgrR1hbmq nl✴1VFPr]*ǖ+gIjjۛÅbΙ+B r8?_W(DYܻpss(LmaG?*_x0?՘Ү[?}dcN?|G˫Ym0|[wָ;Sʏ^)m\ksՃ7^s($8 c 1 \)܅U2QΚB+%iSr!&P*ĈON09%d >IއR 6X,x~"Gof ~``@8Uɼ;R@vRW>>Zy.|k$n})B.a?43)daAclvL7&*EBYɴ$G˩d&) nnzׇb O7=9^iN Pk)rgmJy\749wyXrzNP69IQ!Jaa,0  Y"Q鮮5rόv?ܬR7_2"o{9bLtjUa" Z!qbAэs|Zk,3TFvpr|J1<$}()wчD6/0% *"qF)s<R$4sJ9fa2Hu].|9%C!1a$+w8 5fԈ-Jӏ}Ƒbp̛!f:RӡoEC-laˊs1JZjaww_{ٳ@kl}L)%"i0a?6q՝,km0 pf:(V༰wfa!ʹ-}ȩYr0~@A{ 2JiE*׶vʆծ[w}׍h*Ѝ!ֈ0Z+14)>OЇ=R0l,KĴ1{&9Ő1iSL"`̘R׎MUyj^{[}`j>]JV1šofb$ ۽~}5I]-.nqG?3;wWeAM)UQk̀lro]VC1zmFg!4aa*777`\3HQ؍ͩ 9?jL&$֊R@8zʈBf"$ x2Ojt'R J+SVPe>Ėf 3cQjh;vaF@ uU#C9;9$jշ0YfN~:|paFEJ1qBV@0 ,@Hζ6e҈(եd8Em۶<N*bN)!VF+C_ KE&1 bDĪ6泼1sdLST@fj<|Է릩n଱y/xGR+#s{X-}A{\.GqrMUq>heZ'NŹ,at#[mkLV)DJvXmmm@u E,RYbLْ9 󵻶˒Pk&uZ^q]sEY*( 9e^ǦgO|9Yû5͍ 7&Y VQ^%)aĐ7(|ͬ)J1z oe{zzU=fڞ5dz+ׯ?~t{k~jy}IBeY?>8w}lӣCTU5Q04m&cߧĀuQ3_CX~_q?i_ЭI{udqd-<۽vE+ Fٝ~7 C0fG s2,(R"b!DPnTj-rɨ4"iQ9D3?cL9R EWwnևORJ0?yȔ5[*wqջ_6E1齏ýO &oL4ꪶf2&Njk_W`{q" )._/0`D<_Χ6˲vI'aSDHI^UG@`L^sjPؾvS!!b|L ZBpc7W_nܻ]c=V)HEw|@TX¨M`I^˜&bhY O1%r6+,)RB0 $JiMha bIiL&P|(E"1ƨlk!70*St6>(X{Cu̶lz=vv6BE8lmͰZ"9%f9SEy!0 5'py~"^stqyqR! ECФ9jyHZ|=[7~p'r\O.GYNy9ے9%{km, 1 9KNaxg}!tcTOnIL*2T[+Wͭӣ](pɗ՘r(=(o,/z Øx<:99>>kꚴ8=nڜOJ{lʢmL9?ٻ,fg?қo g+$:=9Ã'nr{?lzzu@L҇4@m?Wr, (ۿGgI|͢g&m.˧~|VǾ[޻*!3SYˢ./sy~r A@yP񜥝bhk O !1'd{k7$=gZY-I9.ع~xookoKeYN۟o㇕[57n]LɢɣN-ia n^3=0&2ue29BF)t6wuFg{^u,+c(gϞ,1X jte5QU:;=m_y|sg}qdu }`4Χq2!>;:yf p;|F׆B@I10~,-KDчw] t cBUyr))TLgZ)##"0^Dy/Ҥ`3?EdΩ sl ɶ LWe0 8x1hz}kHa=<Cd}aMQls{E}QXb PX[Vd6gkǾXZ昲}?HŻ게ѥ8\XX2d#mkژϭ1m}/"!- fcJO~w{Ds!f"cmS SP~U͒{nH=KeseeǗCe7Ύcd#ݘ##q Dʁ>aΗnm~yqv[&ͬ?] E IDATs<9?̶77w~E~nэ16;@ȨrQDf2m/zsvr @ɣ{-֩|0آ.*&3\J)f:+YCoԓ( ($"ptr~wMLD|;\/-MUh]ߵe5Ynl]q.<|h>DLJ!!P!VAX|/F7bL:a6% Z!tǁSp, 2J))&7:@1 [tɏgf|HQ(J5A&_J麪z.Às+HJ)d]7'i& }SJ̆01Gm, *Jݾ2~k: 1zջ>pJEQh{R([ PRVeF2 a>ߚnu+IRz{G(RU@P)%愄PM8!LEJ)z9NLH!(MM3ͪajNxLȳVMi틀oi!}3BDBjf5 ) ;cjsg)#?% Zi31ڞ/X4~:?\HcLoEF5'0F,W?ynfEmF[ݼJYF#X$oߺytrtv~1NhP'罇Ig'''g+;|:~!a^et{ӳSSNˢޘoE\.ݸq#4Z[Q3&T]>8~-)nHu}c۶ru. tL*nݺBϫƬơK˒k |TRs~q^k"XI^_ "&q?eQd4qqBnX_sfǟ|0ε)?ԆJJrtQV"jR5c0DdF0b+4d T @9I;>:ܭF^G$,2 9()Jœ"d(h aݶZyJkm$F}4N+wM3UFi=ѓbcMnraJ8zy+_v7~8T+mYR(BH1`+Bi-`B$SD7RV("$)j0,RVUL<@`D"QJIAVq|;;[{{ڕ^Uu)F?0jp}'Cz!QH!v]?zyERLKXQk4+=3HS1EssA11 Q {k֚/'1F+99,>de,a>OWea2i6,އ!돏NDX=l@"n&Ck8HdN",! 0p-]GNF6trXwy'}ƍO?ɣ?O1\ٹz'wYjgO͍2. ~_dX.nh9~njZ?scL4YZUٙ!s/=.Eu}Mo}xxɽ{ݫ;OE>y?]~g3]GO Tm f j2)sM\_ӟos_E~de}h/~_|D7nݢ:@JB)-[_OaS7^ʓd_}kˢI 'BhQ"qZɐ@射sZVDVˢ{Y %c *PQ("~ԽԱ2ض]>$VZ){9~v>-Aʍw_7 /c, uC:zonL SejADTM&uUc׻D>G@B >@*x0*9y sjSѳ\2HbH:aJ!%˘ceE@SUbafcK6)RJ>SZcHrNAf'*""j 0Bu{>loi5didb2ί_k7ru<;,cH19γ& C`N,(f(X6LPդi&M=z%f\"hPjl)8tdl,,P)4^2=Bb HL7)/JLqCdR{Ç1VSCΞ4Zq%nezK&0y?x45) F,UYuѣǓz+ww_G'?_ۖbH7n\O1CoͿJ^ӟ$ICcʲͷ[o;www6zkkƍOՓ8(|7fծypvv1$NEYU7H zyq )TJ?yȺowծ-1̀|5{Ծ/WؗK5Ee[kJw{p/BHrVaU8FRJQ Q66#*4Ň|ut9tnPXCG\,$E5So޳_AZE(!`'4Ƙ82 @R9^# ȉ5 Dg0t618v ɴ(*Ac׾zunKd;oּSw~~~ߘ͊**4pzwv)FBZ;I]5qtPeY\Sa|pq1qָA$H IxEJ+ $(<PŔ 12i[sL@) E "BV$"R*B !r"!"c,)tn)bYM'MT*wla,2ǔrӹi^{r]u 1uuw>k{O+vvm.Iw%̄uUjEQśo9 }wiUV@C',Mp i&VkȘ"8 \&f=dB#hP 1~r{C1Z1)R.e?ز{/D B0jZ]1yZF?ѓU$Ȭy8%)T)!)҉S&UVd)*f[[C/[|r|ɽ7yѣ'Ϟgj;v[|rG?߈ѣ^&Ӧ!FO< REQGeU#akHdVUqS6 SU8>:ܴ*V?siȁ@!?%7'TMU8./j"q<ڿMɽ{G2*8 O=4Mj E9s5rE/~ѳ|".I4D@tR|X+H&(.!h֊GN@B02_JY͘(}[n?>r+bdݲ!֦̫I! 30lQc3o7rфCw cMLR.;{+( 7^87ΚwƫR#R9 1E@V1b3j2ix6ݜM7c LJ*f&57h!Dar~BP""Ta 4mlLTBD)C46ƔƔ])sΧNcs.%Iɋ5)=)%" mׅ-f󭢪{RZikb{[ҹS:=;of|>w΅˲ CC&d2]W{O%HG1X8$xfV?*2uU[v) )6*ܜiT1DzbLB-aR,RZT夞JB$n[DLާSbaꚙyt#Ji{Sʲ,ɭbNR[",)(|1K 1E̼C4KEYi%(ι{@bʊV$¹'"HJӭ훨u1yomwW^Y>CPƖEUom_}=b~~xw NQee/}嗾 ݿ:eY@/W]w/\1Ƅ~_{Rm[E_;D@k(JiDRD)g-&l6to]} EQ͍ͫ7n~=7:1Xڒ0ƀd[1‹^M^d]ŵxh @aOASZ)FqR &JƛdrvN7n][;-[N霔ԖSߟ^D 3mm_#Әq>%ލ@*h%(F$CQmlUG5dvnb4e]M 1V+?F`j"AZi^/.EHaų.?RcG;LlYw}߻Z+]݉TUV$P*JD N9Vd򰞅1R-;< ֪EQTlfJ$$kʘNʘ UN]blUeq.#QQVEan֚(˪jfsLv ĉi1k@DEc~x0 ?X$9fk>?})8bacs>M7uQ1x߶\VU4|2iagӲ듮rjmnZ]_U-C(SHFG5jc{jZ-[PZǨMXks09d:uY(oS :MaqtcJ4t6EճtXw1a:ju]W>z >Rb!Ek PdXUbbI q"tQ0,sDf}ƽ^}ѓZA:?[\rR_7֓lqztrPVŵ+W$4BBf}W}歧?R jmQhw/.?8 TV0Q)=b4 ~~/sZ"p P6J¦0 s^E)Һ(,3QUMwm?7rIxVY =e~/hwfE)"ρɗsS`IRU)I^!hl5iPM&}ߕu4z ~LuQتv! Ue 9fd9I!3+ED)ŐbhmAV!҈@*Su3iB8_C8 ) ˲tQeYUϞ_,b֧C@8*"RL9QQs.b&WO1FD &̈́r<]!Eoι,A5Z kYF-Z–D2D"(QX2T^H[chRB)Rhb7"02zwV,DͤYIX)BFoONNP!"))ЦЇH]Mt6O~)!xd 3:罿rمsBfqΕ֢nnmqJntggݿ=)knMgrYآ,kb X LR466 >1sP޹^۾ZzRJEQ ޻")RDy)DAnpNQ#9wb)&JA&B7g[STUFk}J1&2V{O/.%b4 ;Hdݶ=]r]"w1%ܩsN۲TH#8E}8 ~qgN\mzG`֭g!u7wE7o"Rx*{b HdʊLDWO?todOqzp!PTlc^ l mo*&& "e"!N/jMٲ. lc qvelc9\/n?yuc:mvwv~Cv ׫lc~Nko>'jL~ޞ4e20ɤ8=R+9n7R@%i1җ|G%s T6oJ*Sj8X[@Ur{bZ(9f19.lHZ+%:oіrr˙6 \!K"B*,iRIcY=kYS_ݾzg"P),KS g k(JaAiS"NKKCڍ*;RPy aVJmNс .O"jBBO@8:b̹ Hp,osd'Dֹ!)~ .Ύ5 h8rJO*[z1XчW%",n(s@zpY"YG+blӓc9YCHC?BlT_><&`fCBpSXJkMS\-n>ĐD 7"_VwP͞&b(7l7y׵m% IDAT'PlW΍|'+MD·GflO~ZdV8*1 cJ~HR9D-"H$JcH,D:Gf)Q_1qm]5MC1Fхi&s(MZ̋_,m.w@.lII"#pZ `I6U ~EIkqKo#I)PE9ZABN)ƘbBD1$+u\PIiV8mWMlLI"(!3g-zD̢3l)OXHjVʪjR;~(H #ଜ[S8 !GG}Rd__V `(QVg(WuYEYu]!c0ZkJ}HNN?zxO>^ GclQئft8 aٺrʤnbźeBH"a?d"93kw˲~*yQm۝%ˏRJGGģ=quqYQ` !,eD@ZJi>;F3=A]Cgj"G~ծa ]m*}\hEV7^{?!ǔb"w+oʿw#|Z/WiERQ6 }]ӺZלXr(#KuG*nmݼ}烏O婋v*Sãi6]7+np n'Mqv~ "Oji]qd*~(ƶ+ l`JIbG"$O!X?%=3#II0P3%j Dr韝Π7QJ!@10aksRS[ʨB,4)ԛjey9ko]FFfdEjXEAS@`?'l ,@$HYU}FdF{ۯ=n`bp{qs^}!_O|Hab,G9l)H)gfUڭL'S"((I15Ɣ7x_cIA5T!&M )))[hd)//g/G^>1IetSF)8"h1ߔNs&YUD`B[,( DRPDwh*8%;6" p̈8b!uHƘz:qum!v aC51&*"";f*Bg&*uy"BI3ݮ8OBHXD꺽LUlQDR9eB a79@uOdRUU4LdEu8"q9 M0(8kUEl)DUUwFeIEf['G_5uX,nV P\i{b&(1W5#;9ٔzy֫k98}^H!۶mOOR/..ƾ?߭8B)r/O~YRV@1~ ްXzrh«~)!UaG1lwL*_?|]ԓ?ӗ{_Y[Fun5]Ru(sjY"YcCUP|rGD5efDXiH^(Y˶\TAwjkdUS֞ %r-#>"bua+/ ɲfJa&`XP!<CLFU]Օ&~?~79bzc5rܵ~|EHޔɒȐolR"UՔL uȠλbV֚5'n&KX5u;⫗1C)C)ʳ1Z$C$+/> b3b47Cs}_/-}awylcO w_/y[:Ջt6or}oIH{@&mە"J}v5 `L)dIdf]}]aU FP=:Zn7vq:05hA^cI\]N}]nU8ވtѧ5ԅ v* ;]۵}7 v6 ) DU%cˬXDfLX3?O;crI'|wϿ-!j2؃ 9>'T-3FB&@d2@a ?y .B JP'1hG@>^TUSc&"]UUF<&woߚ>>?o ]u||:G2LĈĆm,*"Ij23O"aH/H1":_Omb]"]ݼL9+jʮVmADцϊ1e&cؔijuR3)*[B)gg9V33`l6+cs)"msRqۇf?>bg}gC !Ģm"B"V TS5fNbReYKN0 Rt^/Ie cJֺdZO&a4/Cc*9qu1}cpޕh?Q0e"O 47-[Q f.ɊƤ2ӣ$PKbCtzssS."HROY4术oBD蝫j"M9JroO|$HBZSB8D,Xc75nb& gIt4ٵ[pC?OЄ*~ݧ_(8c6ёa3 "G?{/N:虝JfgjJTooi?|1[//_u]:9;?)9t Bhܽ 2$)绷nJJ#"2dW1:KC?樤PspLb蚪B0 m7us S؋$C&WWWD*Z5f6'e0vߎ!2) jȑnkD]}O\@WWHr0]4v1wLM3;i,7< Sonӓe3j}C`vƻ|~ -fþ}2jʒ(RIsVބN&S2BDdöe.Rb6*[׾ QHl %dle (9:[Uh_84Y[GHRJ*윭n~///52ca1hBT^$EФd|-Eء_=~4tvz d~ue;=9;:>!c~fuEl<1!qlXY:CL9Y= S ccli&{vIEJgPVz޳(g1(ck d߷9GTNjyUZqȥ@t:vk{}Ǻg޾g?*z9fCM"1'c΢1v*ODT89x02đ}U C(Lʜf6cڶ-c" `rU;+ĄR,b!Ϳ?lAd3p5GyJFCjf>v)4d*k'wݶĒqo?xӛs@kyhs{n/m'H8~y5?|wwxu泳wjo=xnpƫP()cq?gϟ\^$a7 TL&׻Ĝ>vU5^rVcC ax׿\Uyn6c@cCԬʶ΂9 ay{ *cf8HT*h*#Wח)6c1tc i\DЖl$9'1.0_1!A!МaY% 1"B0Jd: 1Twݱ _|ܵ~__.p3H]?޴O?\wTe2xy}cO^=1Qb3Z[m/ηۍ3䜫*( r){7 !r2LDH}[f&clbٔHR:L)S`ލP"24;nwnW7W/WZkY=ݗ7v1[ RUUwR )')_chYyg [m۝_l6 "nc@a09)PePsV!TNںnNNNjff9D+]+okDI1 *)KdfӥdlU|*<]q}o~^VbNQeȚI=sn.\Փ:^̚qLE_^_~Iũ˱]S-G}6 ʻѝ۷V;;;9=17ï?6Yb /mG7߻uӛ}=yx>fAf9߹;_kf6kCPtȐRT_y&T%vx@4aK7hnVWwƚ/~okbД 4"e+BPq9mr"4AU4"R!"N1`B YZgTc@9aK/6*xd;E%Iyݲo7Og6n߶';1zmvzs=c`wG'G i^v1m޿O% CogC/n׻ɝ;FE3YRAVBf-!֚]Z׵qBHYEdchYw0>?XUBA뜅R@QSs=듇q||İY+?zYId^]מ5vwwY2kzmv:m׵n_<˟ؤ |լ{%gKL3B?UGd)9k7ԍ` QDPrFR3ZkDPȩA.Rco 6!XCUϚ% @Mȡ"Ua DT]9,] m%FJ%9P&7}EJйjo_RUb3Mgt6+ 9OK>CZ%!\ n0}w1UV!nFrECTNESdfB c,Q} JN^l۞u$[WHb^q#8ky7Yi-˓c"{Loq{/|߼|fbSO!;[j&aܸ9ӛRw|Ox1aYz&d|#\EC!P٬iB 1f@٤;_3Xr 2lc!)fێc C/51! ?JU23IL);bM!βs4ZRsJX$7XE@I%l rs&椮3@\Bu|[V7a[b#O~%0ެ/v㾙.m7ƐE1CǙ7٭>zۧq0/e=}_#j3[o/_^.'wn;=}0@yĄYQDfcqAb tI3Q"VƳ!F@LT=R,.&ARquD MUɑ.}:?vͤN)[}5 1IպOQˉ'l)鬑4>zk9tDn{on=H OnfR9$s4[[O&ƚ6EhUְaAc!`9\C}l 3[k PɢUu]P@تj!T5Ncf];)ͺ{un;Ut:+Ĕv[1`s:-~H9c~sN4EJsBCgms@L)ED!T!!y2)JF@@L  |1m =N 8jb~43!ª۷n/OơAc??>#fb2 ղ|lo޽GͯdgSf Lmھz~u}=NNϬsְ1定Bl kA@AȠY2s\9)9kw}NZ[Lh,WY۶CIYsMӜc,oX=4av݇-/$11MCrNȮȄ7m͒m׎oy(>cC<[,?e?UCT&5 13f1Z IDATB "82LQb 1#3bPwa_e8LHYTTqv[֧{k8 88ȗ_>-srW2&rzsLbUSH)ά7kdQtū+DcFD"Dc]UUEEݓaX,Zǘd @r1:ݷ{q"DQE %J)DC)׼eD`yc=xynCe9"1Ed.5q-3Um NtD/P j_M1abu9Lɦw<0_\ے:[Hr J|q3"ڡ߿Z8cDb )|hr֝_?^lnglnR&M4row//΋-2Ǿ=9=yeISHDbFp?::1?4 gO~q{}}=!4e.|"#Y1Ѿϐ@ED$8C1t]RΦ77m۞}FD, !KuָS; )l6Gc*!wPdcA@r*XCڝa2ƠeN!8_1*kzXw.Obt^Hw0$fN!;;}A;t)6mlV~W*bΰ$bցj7zUSI)Dd(!T 8JVAҒc,bҶ~((Nw^bݾ3k cI9몓[_nݾUS!E몮B)ĔS8nUI'1Zvׯ6Ÿ;~{q!*X&2C妓qID 5J[?DJuXWVe㪪L&d Lj k9qd~M3)U][cBc"<0~PB$T9Xޑ*24qb,q4˛,1$C180>CyZgLJ TU$@DMu-"ݾqP6>%AT묱H>]fI1םû l#m۵޲=Lx@CH1,V)z?@0@U){~ŗ_Ru}5Wu2(e!j:c+е}X[wޚwQx"4k/c xthTzyn7rWWBU-"jNqd)?|~[կcCaoffEƸf+{d*I@̀$*γmHj)AaGdeȲwlXoVWUQٱ ΡvCL9YMkZ3`"@NP3HIrc(6tV5 c zΝ|Q^];g c[[|5's~߶1LXucl>710S hר2 Trr2@uФ\ =Oq;^+5M?ǧNyݤnH{4611)Rƺ7O6q4Jd)!&cfZa֔BFG0vqL_vc5]a t2nnrjnV'}rquc04ɒRr޷[dGg`P[t! U<~8+Ww~1,46R| Yq~f۷z~r=kɾCr9c֔Raz̆D*BF Ԭe!9!"aBW Pc&CB۬x}IjG/s9ΒjLzq>!Bs>_]?]:|J!&Fì93&fa Q5M9[+|P9PV0\]5l>56 EL)q+R)Yk0#Ve<İoY 1M9Qvփ7fH㰾 2G"qVS4 f&3"bur 3})Knm۶1!8$ZEV$%,1VAPSڬ4cG ,E9kUɆf!bLjLļZo}{s}rfھlaB`B')SJ8:c b*Z;mv}ĈF$x) g.KZrc:ɚ5UI0Em$B@)(u9 Zm lJ@q˅@Ds1bz`8k)ZC1fFE V "HDÜ- a,UD躾zfvjj.t9T\V%DHbߩdM4֤! $hV1tqQSvL~Jnvv5t/>nY4lP;G191'iꆉbJWogfa2-uNݮB"fQ"TAl ["MYtJ Ͼ/m!//~{:rQbzkCێLf"I*^9,ujݛy]f! A'BRz>S41D\Ut2n?w|z *fL'l\=aIZSUښ|>՜:#" VS^uDxl+!``ƹJhS7hՋgѭy~ً?8+3<>0'ǪST%_NQѣ/x!v.+k~G_T6 Y%(H+٭rX,q@RD($UM؈!W;`4VSc2usvmyYQsp!8h}]MU-˯[-w]T0&INi^7Nݎ{G%ɒri%a߯4ߑ1)Y$qbǾ]&,XƹBdBcqXv]N ۜr3>E?1EgZԕwV%fDZJx H|5e*H\䬩ks!q0dA&:HTnJ@cJDƐs{)K:sZx.nR}\,TÏ>ry^!b4R(R}-ēHM Ĕ<TrJ轷~T\ ba1HP53-&~*E՜c&V͠4qb :c_O+ŋ1ffBϯ+gс4$,Cbh*GÐB'S4 h޷ &#_U`|͌!EF%Y'dqtlQ&HAܪ.?ܷjgfM|{dN|6ݷz[nXﶳ99:2l7Ȓ'q:-O'GqOۮγgϐvmCuS%}cX(.On=| ={E}ЭӬ jf?b>5ꇀA԰ |[g7_ucD* 3 0 W @!My?ck+l~G9~q7W/ IN]+{+djxkIꦓ) ^D^n NS јb$En췻nn0P!d#yb 6gڪn*biY-SLI& a˜fG9qDZĤĘBNn}uaXnjuu @7n0 c?]W/۶ǮrQiWa8hN9(@v]]ݞBly\ͦAi u=9Z޶a:;=9}d8nj C-_JWM۶v>[N2yyO0ST@ gel]ULkfk[$ be`SaJ͕ f:BɆ PHi*b11R.I!/"\sb{d*zHM #Tk-ZrML鼡8͎_'MM@B!q4!cL)ΖBIɔ}Vr\F8kqvsdq>svC (1hW^b9P<)qn7|:v~cx?hm4MuqHծ̖NjŢ߯j?Z[o= ƃJd^9AM<ֻ_ѓ;y74$zֹ0w` NNBL QIQ&մ/b^zUvXPE_^3ZP19 |UUTݘ`2;RTa6]Lfi ˓ǟ^^2F!C @X7MX#n.qǃ@@SX%T#ǘFaΝGoyn۵C[Ϟ>zl2z'˲o1ץ)_ 0$B3H1yVL(B@#8ҐpMuWK{q۬o"+f9{/}㟼ovݶ/9о;WlY'ӏo,wg?:&ӶjnM 3",T*;d֔SU[cMq֊hUWH3o\UHQ8ǐtfmq^.+2έgVGWV㕫<) ](hГo+I`QpXWUޠEۦ~=qv}9{穡0vS5n;iov'VRPI7am6e#ȥFF$21H*cdUgma!_$0 23uPy_NQC3UD0CM]!D#iޗu0gqԍj]/rsmZ|fe~ǜV+n7 n!4M45hp)Fb-_*r|4'7Xz+8?ϙ W f*hΉSR!0u6:KJ!sP_"dQad%$ͦGC[-n![k@!GGUDF|am sNQSje0w+kmUqJa4mƎS$Wgd `4w뗷o>{6.ugiv^✜(ke8:gCSQDWGz׍VM궵~yjܿ_Ǐ?Q5;~ϽZu!|q"dH@u<c4q !jSْAPɢWJh ˪W3#_aaGyäHdvu2]w)eQTfvd,(s Avd9j:X+   KTlduA8KwzCb~TldW>͝FRRy;T)jyuv.#G;>x||rcu}ѳLu]4bà,#S|Kt|6XD"B[Y@\=UES7Me qqdi|UU8#($WWM45TIXu>=x^_KN3돎n߹/~œn]7IlH\^]rne۶(C,s~IC@Ys2VRS0)gg1S]'19vwakguc~}b,M0t;@jgU]5&.x) }b9?bJiִm003'u]pI>;Tziݧ4JٜcSW''|9x_uݘR,JDZYr4cu][ks㜛)Oa95_.DDqn##94MԾ,8IqHSVU*8Wբдb?݃UaVQ㍵3uU31~2gYcma"sd 8EhDUA Wi\+c,,U!c[# {윛fp. rdx?3H)) g=@rLP9{4_W:bTքM1u8 ~? ̹H,y 6*q܎LW*j-rz y/=wjqrr™_ MRZT%8 3!l[%1®lz ZXuKDym97!c)%@Ustr\8g̾ۧA~?Mi}n뺮:?@ѲiۇwHL(YkCEjruݯ` IDAT~89Bh<1?pC:Wmٮm/bL@g(;[9 {_]I$GS.[\E €H9s 7YftֶeJڴ-HȈYB,\)9窙s9e\Q7)"amS]v֮VfF@A3! "8k]90hKjB8ٝۧ1<կ?~=kIQay|?A\+=BbI109x+_j)0423aC``2تUS!b,*D4'v ;8>!Owͧ2o͆uU5_;?r6NaW)̖pXIUsrN!L";cl1u]U(9ǰg΄ CjѺnٙɢ0ZKy]:!1+" vrQ70 :i}ee1k4N1daںr͛nK!9c8gHh5"QY*Zc> 2m=YYW0^ oekT1ƔxTb6_.OϞr?U99=ʙ.~ Sǀ`Yˡ]qy1rw>*T_,d=X"%|~7C=sά|h^pl0Đ*Cog-Ә!EK9wHX-]~rfT뺮k$aqVD]u݇~4*ijcGYc+PV6U8-V_*_}1hC<r6UMHsFYSoחq~Y1U=NӸ7o#~>3^]̎OӫOοwOO<!$$adl6#\w}Dde)Cg- )a}vQ:g1lL `)IJɬ70 % :kL\]^OӴ>d<)BUHHZlzW|CسiRz}yhBŊs1"AI"CmXc- -qB(t1e C"R{FXoݺџ[?0^TTњ Tf7 ЩHUWM6$[WxS$cL;k1&# CJnOO`֞U'~?ଭ'dϟT.Ϟy|/o}j<:}zSﯿ䳏6֕U0P{GUU;[;sݬ99owݫ}X$M[_.80 \J̅5DIJsfL5z5h@S r"֬;)l3rf@ UEE&fR$cO$ "h6K X Q;De#E@K(X3U}% $nU3% M! 3N8gqTxE< Tq:US_]fN>jbܿT25Օ=Y?%[ok_'b&@Yq(gil׈h 9Z<83 E`Q2n«>~9KTΑEJoq9?{]58iOOajNiɯfcIS}q&V&F$(6i&JErYi,T % ȜbL \Y_){b;s񿩯evStz1FT;BRq,WB)@Uu,x"Y,Mk:1i81FaȂ֠ppAL69%6TniL$"Z*Pd l|r朴EUn߹s8 LIJ"g1PUl-L(6彇C ˩m%ɼt$3X`)L*3rkeX.b~7g|e3gTB4)Z68D@&>򙷆Ƹu3mjGC8{$OQBB,D}@?yxo?ۖaPPảBָѭucIyh/~/籝tqǷgcp 61)15UEHVBDLNjNAKAil8PT9;_~/_\Oi""(@BI *ƌZF~t_V7sK)w"Dr**,&e8 aª@ Xr.2ZW9VǎpS |hTa0QcA0XY~)k$44N;6-œ~{4tzͳ ֕jO_~PYS {o%D8c (isr84[~v]4vfJsY$}]jF$Jc4c^ <=>sz-{K~98k ΢l]Go-ճ<kȘb A(+2sFT2λzgQBD2!YVzxpְ]-zX 9泹vf1k7M9_Vmw42oM)Ơµ!6El* º-b !'I5p3##@I5泔RAjq:C@)DX`oߎ!qH ()E,zm*JVEnGMUo֛'bDPBG[Uc,C82 4-{޹}i?LJ6X9*< 0Hx0 !g)yM?nn%"t]!Aju5 cgW^Ca+@ŋ1>luU { 7p^L驭tq6Sӓӿs1}~4GMb̥:)heMFfECD@09g~'! }s,j|,m׏e'G޺u띷ɧLr;. ʹ1A"emyt).)pS=XԄ3rD"$fuγ0+^/RоEhr޻ Ƹ\.NK_/.I9 MvY1w-`DZ,oR7E*f}z +MSU bR JdF DH 1MCQL8;;o1;j׻).ɧ~Q|k1Sz1s$4z٬k4~D!Csw435Yӽ;{k_|z7)8t|wYou* b"wq0|vۨi] ᠣUVd04R<\sΉ@L@` Bysf-&,cV Ѡ\___oqq Zcl+D9-)FT:]@4I}UU[ʔh P6XξR̾?cxD:U rW2E,D\-ֺaE5Đ9;UbLa c?8Z"k-"sw Qi_ơ0w9#"`}SHe,R}UU|a@xÄ7G@fZȹ}&* ֭zE¡1)//魓r bJٹ*4 Cɠl3!JQHrJ.WVw6%&1L!E+f&iۮwc??[-W?x$N|a6uSߺuwz⋶i|v~ Ӕecۡ^کq%1rt:=9:{u d(~[B?-&[R:dKN6%*n; }Oc9B!)t} g@unr/լYIęQš5P8 Ȁ(-16{Q};N֗ /׻nA5\nbQCPTh Pp aa1̆qSsHDֻJC9Xa.5֐4䌫QUJ Cºiff./_OmhqԸjHl $[cYV@J*At ϜǮFi^TYĶ:Da41PacMI&D Zov/ rV|nͬL4|8<:<۬ˣf-jx@fTS. D qB ť|]HVB)ѳleu9',"\V%㜯PE/_]5UUUeiT֛891R2䬥nbSv9;kd_&5JatIi%{;gXo̬PnUTՒc*C&WKJΖI=OS_9琳(2EifKQJLd)mm 10K&v+CHAD}ĊfCC n (4 uvZO|O($" "Zg|JGˣn4ކA,:z{9",D5"ck(mm6Ŵ^oɢaCח?7nN/~wuWI9$caYhNǜW//ϛm6!L!LG7?'ڐ᷿/޻g?uPq:_eᜣE)R1p.ck(3Q$%!cPU"{bcJ*; Byd1A:U2Җ"*DBTPǿUZכSݹs/^<!4 ,;arxU@xQU"+BdVn:Xcr#di+1COM;+'(TʶX.)]?l=˙ )gq,yT eUɓ,8S'8嵷*lnZWv+Bs6)n\e zԨz'ɽ{mꪩz:)CLa5],l\cG'[- ٺ٘ 1nwqmFav}78.A9%aq)D_W'e%9)P3B;)Ma,D(*b3 =ONNvMC"朽dq xB*P 5`@ً8PYm)YA|]+@f(9*f>+;aJ RqXDRy\.RJq!3K? }?RNd-@(b@sds1<`)'As!xi0]\$h,8rM}޽0]g*_S"1)9/AX3v]u-NNNE$o4MX*4efz9K>:>>nGǏK<Ʈy/Rfh9Z)pUA?owgW|:>981ĿO5N5MI 4M9qfM{_?Ȫ(޹0(pUrAr۶MS@hcq ZRʔי6¢E\ڶ2p`?S.)cւK.h?*"s7 "60SS%~ {DumLfK|HI@śB$?@K"q쐬EJia*D-:_7́OZOV>(اY$Y'CSH]O!RH1ƐӔRS7d\ZFh鳧O>|ރ{?ُ.v]J:9/j]c)MPz}롯V_җܽ|էaJO]9b>o*礪hdz^7ukǘv]Qs;@4۹u.*cʐz-3(Z eʒqu] CsN!w}틋+\VA14Ų<,=o ⸁",]7 }9[5B˜Rs9lSl7ԉUBCdY<0gD0Ƅכ]?NY3_ۺFTCr)S68SN!A,RT]v\ kM[{qb☸ 쩈@DS 9Rsqfݬi:O8߻g;묫Dx6+ͲXC:I&D/ɓhe#k$Ga5L !z}lnҨ")egm%Ch-Qx쭷0={;7urzh٫ Md  +/f׵_+}kח{w-4N:TP24^m{k'z໶)fj=99I)@Q%9=5fZ\\1(G$2Du7rN[g}&Exs4COƸzn8繡v~7sMQy(T2aΕ{  L @8KY[#@Ή@r';CY%&"3ֹF,ca*j@%"ottqyEY*JVՔYEigUUe1{wr5%f)rjNma@*mfYE1̻ !,s2*1%5)L*`Mܽ{;W/~?g?~8-۶n,8ˇn}0k4gLHdusރ~ooo|qr3޻?ۡۥiT!\f@0ox&gi& YXQМs $8g)0L9D E8afoEkf!mNq @4!)Xyg nU.:JnNَ-LtE0 3*8k o-6u}rrɧv8TbL*\Ν*C*!%CT[r1zye{׶m]WUUqM7˜~W\4t{ЂPm4zf^UDQDT8 ;cqr+/g$5M}o'ʙH0:{UEeǏ IDAT{dsU]58a/ZUHiZ+93WBa7g|h!9껯O{=裏U`G鿹>2b޻i?b$)(+V}!G?ڨ CS+ H )'RE"@v[T$9q8GȜW#!4eY˺n(%$hn:H%uDPdbݳ/xr82VE4Drۡ~7ER#s(hlA4u8 L/ AQo]L913kc* P Ec!+v0Ę5_>{7էK;"œ4?WEܮHd5L_6k*Y PUYL9 ,1"R.//DERv)TX g8`n}˟x&DEB0@Ň,"iW˟}hJWk֠srWf.PCBU9ES,JIlLQ@9%AS023KIx@<DKߙYrhY.BLYf}" Zgf=ʦPEp*_0E?D+]6SP<]? X./vjksD91R#$,ZBY 3gE!ŐR ئꦮ:w}JLIDErs3Z%4Q)yf1Xs1ų380M`Qc SM=SE?ٌY)87}l -45gY,ǐRLδ\# deW?۾jw]*_///i2:ԟ]dðw2M[Mq*[WyУcCNoY$ItGk8ec'ҔTLE UʢuU!" umUe_|f+x*>YFj,ZZHΙ^"DX4>#u¢x fb4Ba"1$cѣӧ̂%Ӎ( dW-gY_"GȠt9t1"kAݻwl>7 @ 9KLHTUM9hci"5Y]uSzyjg~?1sl7Mf6hrǑsjZEP aDPk+?U*k)* ,Zq zvrhDZQ4x#%DU1LTb ¬h "UU=o.XaA1K,8pWzj5E79 $ŶcˊU"8pBQ=A4SL73$"؜z} x9q_)rR!P/`36u}zɚ䍌 ! *"X@2raBhɬV+>9Dz4VM=MSb) c&BCTdd 1(TUR.<"*DTv98DvjSf}ɃUՔr%h;jAUc)*-d-5qs>_7Ī2Usaاntk ƔB"ETIU3M6h  ]o6o=lLדeIr"ejQ#K횭i|!h$,f03;==]Zpw>>VYes"\|{śt6rՍ`- 'Di|YUU;a1,Ae>~=JnW,)WYKmPw.LI{z~sePWɐNfrarbѬ\ʜ"⾠!0NCfAUPQYm\B!xcHD$$!kA)PȒ'Y%M[H&-keD0 {q +^(}s)E,s6@,Kuiˋ 9G$,*>Ե8 eʺvEn @7L4Yݘ޽'nݾ}zg\-XdY5ƒ9Ys7>Xn({bӴz- B5m69g2h+ qc"iJeCu]UyrۯKl3dU].wXmU"o1Řд9OHjq$;vrr,wS9sΥy)ݨx˒>sF"C% 4dꦱޓ#k 5T>_"(Y] VWRJbUpޫzqw[嬨{Y:0 b(3̆HkC~J("TQqMTlM1F) o1!T7yVrUU1糓˫fh8}wRo%3= ,9!9v9| $=s.g"*K!*?4P`6*wۭ"ʍjXe}=Y}8sUS2c%k׿zE;lnW7[^@J/,)c82KDŇPUU? DV֛_@dw}2f# 'DX;}߭v+묪i͠Z.W*أcJGGm;yE-lj"ّ1?ϯwdLSͼƇwrDt-8gCO9}Yv/e)@gmbD@P(Sn2HVvsFCcHXyn0ĪUVU3ġ*#QJ YQp2=|199l>o6ŴX,W~ܻw Xzu(du@sD hJ)\`ɱAb T5匆{1 c2.Ukmݴ|~r|4iK8n;B*Esv51n6ȒqjOyݍL;'LD)De،8 duT8gf9359vPbyC3ڇʊ= r()f$"Q͜Lit>i&yODλT9 Nrܗ1iP2?aI'q0h6DYk۶%m^@o^2y}LU $*PU]o'ѣ/U)jR5 euhEL7 B| \51vALU0V9y-3W9~GSm1SvnՅLX3A IYTg)q?*يBiR1&k! Bas2Clr©0QjYq*j ,Q^1`be:w0?{K)9Or}RcL"aΑ)'s/.'caEYPVf+E"CeI1TuuTcLrsʹ̜3\"}x =Brė<:k8S )H(Ϫֹqqlym_GĶmꠀyƗ/_NS"QwAY4&93{ Nm7&EdD jRLxxa۞yz}}v ֯a}}ma;tk!9YFDX\%ֆR~5xb[O wW_拻Gd5dPD̤mr.jD`攳 T߻}w6/bnE8wz crh1ҏ~O>Yrfu֛&T+,~4/E5H$w߿}j|uv^lSxꦎ16m{=f$#DavDƠqU}ч83݀R6^!C)ecy΋/?{ꌝL&׋rAkYXbk'޺I[o}{Hd]%Y H9Hcۺ sv䵔9%+KnGņ ,F 5&Gn9KᏪ l e̜TUaGU\"kA pRٽ5&ؕUὔ䉛}(.,>?zbynbLܝ[wrNW"2v)`ÒLg!TBΉ`|=OY2`$d=UgC=1,DN.xqvz|ヨ4() v*DD(9Z ZD:;iPڮWw}?z:_]-Ȼ<'gU~?Z]]o ȳ$sz4 0d?)I!Ls47ydrW֐qlߊ(4y_W)qlPڶ><:nV1qL1}zݺuz||zyu9jpM?x3|1vYg prNU3N}9{](;O>}Z~ջMH׋Sݻw7bz>Edb,,>쳗궝L̤m9D0fq>:|?\Bԍ)*ICd }b B\Jd8c>"f0R!,}QwϞ>h8 e2bL1zォoUVlc6;kT$݋׻؁ͮ$ÖAcٟrqo^]]g_]dz:LGd1"S܁P+-fzoZ;" wC \Ĩc.8Tleu;U ΅`sHSNȜBF ΙYU`),!%iz٬T:BY_NL]YU()kEB8=9: %K$Y2@KD šbr[EDbwvefCw%"-:ɍ}Ι.RJozZ7XWoNXׯ^׳ڑ)ʹP7U̹*5;޻|YAq6)",]jj:˫$YHdX Pί,'PrԴr?? 2y# vAE4%E09q׋?_9M]8u,(6qF̅Y^Y릳zqxM;?8}cBcXEUwn7'|bꫯPYNo?~>\CdrUe ,bU$圊ji֛MI()y~1TUu#Kj21n^Y^(Z{`*JxӀzNOvΥ85 9}{ Uc0nncWoxs[ 113+eO W݋ꥄ}'n%g_Wd1[kkB"SGb)7^n%E+(j qDb47TLOf>W8KIUW?2=YAaZ^l=dmfDz}a)d!+|_ !~vy魻uz{T!mնa'Od^={_:1`+ -`")h Ylfǔ!qL4MiU|M]Us)~/JD)qʙ@RU4z:??w6 VsPCs >n[P YԙI,"<)}ۮЪBQdYceP㭳 qtO33:,̆E-G5V2 XffnF,{1~HJ U.e^uUY21ơb"97*t] TvzWyqYdLF~LndD2lv4?[5n3=g!馏9tr|ɧ؟'2hEq3bnB;;m?]/֗oXDrd<Ɯ ?/cw7>Qvv w>u|tǿ 瀞x.7U%CDf> f!ȯjZ>WwM61o4)3+_t=S.f vo)כ 1 b,"1 !zpZrsyȉYC)ɩ<}:8 @Ӷuzkqq^/6]7 M*$mY""I kAߎ^0Kf1zGmWeN㽓9yM0 s9⋗߼ymB:CV%wUwP3uݒ y\vZ#QĄ@_zsF kCSu]'!%[c8d1-/6 ɽ>xB3 j ;T.Uz^nkK~CUm7goANvƆvv0?x&7]SOnQo>S!'v}q*ŜǔS읨Zˢ "e#r""DRT($0r IDATݻsr|o~M*ʠ)%PLp^]_w]Mg(܎CN)8r΍pJqZ "n7ͦw͙@l`n皺Vb׋aײ[UUCWFIispX5/ZE7Zh \ʏJ8a9 v<B{W<%@q\ sH ~w\g9cy솮g(ԡ|:?Pm`0v=+zrrC*D#G8_:>mǟ~S}GݛzPCfwvAU7o,,^{wJdFR1 Zk3 тEPmJ<c*OGބB&s(Q1fNb.[~wCSU1#4HYu tm[tx>k)}zO)RJU=tI;1tݏ˭0Y)v;0)ر۩1{ rL9ҡd|몪Sq:@*ks 9gɌ%3j\.ɘ̒Q B#[pTr.//U`~x8lG2:(d-R?ƶ^WvlrcmpcX$TbTEEa89:4W^<3Yt߹sO?>9>S^w <ƱfUp諯맿wܾ3fHCw}X\-1L'FI{3`ZKV,<1. H7M ||ޗzb#i 9xM n!??/yB%s''˗yO>տA!s&[^|Ylo?mΜ'ُ~^zkf0 w}4`)Juczuqvxp782^Kv~~RΩﻱ&vb~zL\К} ,ZXhNZ{"VU dx4q~\7R17v!raIBZ{0?8::Z,W1dB[$24{p|pyR_</9Gc A49| ,ǧ9e4QU:goPR_Us\.Wpg*\JbkMSqخu4V+CH E!TĪUU}ߺ.!=c@c tJzj=zJ.V>':mZZ/g/>~l:TE 3甅3Ǜ!s^כTe[X*4MKDzGQ& @HXͬUʖrcQ0+w1N&M}^_-, 'xN_RE ~UUD{ r CVC"u*0 쳖mG1o o\{j "rR#֘TU8hNnrUYؠ6mBH̼^)5 YɋFѡ:|??h:vC ENn.xbQz# jX`b8_:ƝIA5ix8#ѫM\;;b9DYyr=?~[Eq8=9=9msm!1$_]OS~˗gouܴhVP%yU4rC1>ʔ=uΕQ0{)&kNn U]i04!sc޹}?o?}2 &Pʢ-Q.ngfaTt~RNKD$c&ZS,uU0 *<-ZZW,,!SUU!,ʘb7\UujjmHc|1Տ;w/~))jќ?_/vi["WRdj2Tu h:˙ڶ*xyD{o1"j[g04C,~o3~ٍ0q {wNk&vYCd)~Y\yvڸlRj۪58gѬV[dԍ:筫*'&WLE$y)$kXZ-I1$BSUUUyN9qDZt]mS xkw'[kf6fфeΙnЋ,cDTl*7c7uN燻~R2%@̹0lVUUw?#0'$u`nIϨ<3eBDVCUyo+K]fVK1vKe+ERbD,3l響\ %gKRe"@}iۻi +e2R~yp[Ǝ(` Ъ*ȢiHnGѝ*q~d\Ыx<Ip3l^mź[VD⽳֒q9#Ho=ΠTBqT*#TqZoZUj}xl6٬ ūt^1RUUMӰm[U8duֹB qw:C1gŃfɳf),b"%$.:s|||)}OчP|cAU'z4sRIX${R?1%>:<)̌DݎGq'*YDn02go+Uӗx>O~ORmm1n7[ cqHTenbJ, !DEC)I?ľ>7k"M!C^2z]Hҧ,`q1̑y(Φ4\_]u]|2Eif>waTEwNU1IJ9F38E%RTIbR}q0ֲ*Ũʦ` @5p)iEe!j&m[W@se-6`Uf>ݘR2 )s~6kfmv> CիfsH2 ]CyݐJv c[X`5D!{J܈ʇ $nZc]? 5vbqMdfٮsJ81*2n)"y_[U,*HDmS;a̪n2Wm ̓ڮྲn+trY϶}z)?xߝ_n"wkSh[uZvj'~}]Oprt链[N3Gz뛺lˋ[2՛z۱eekއP5%k7{BRZЏcL9ǿv>Vt6{l6n1s;osgoܾ3ޜ*B4uc:cȐ1y笍)1Vr]Q Dŋ)傺zvvrz|młY9tf BL윽{Au1%?6唌]Vܴ0oٟɸY}}$z\]>_yk՛y[ޒX_s3X7a{CqPU`v#ceلPSq9gc]?{>z֛'OPb̜YίϞQ Y$rSUglP3Kۜbm10KH1vۋ A﹤3_+rv]ܞCmw0C_URfDfdw1ekݗ_9?0LZklqܾ{/_hlɥޛ3qUE3WUu䒳g d/1 "EOVj@΃iqC.OdGLDFmחfyqckNݰE(0<;ݭ(nX]]vm昢U{tp|M X BBkTsΥ4& 9#Yf3Tv0bR} ;8n.QYnJ9ZECK2GBcl"`Y%o P8r*1O"ʠ27m&d !s0cnٺn LV'''nw qƱ뺜svL;Ĕ4%('PU `GyǸZ6M:g Sb-'!ZcU1{} hHYtd9sa6Of5T~X*9¬bz[U6e5d,)yLfw)n|ֱߩE&37==XyĻbf@UBtAEdqtWUS,Wf}4/~VMz-iE*3~F6)$LgAw:u%QaYP D([a*IEQq5_q8=:<99}u6j'u̹4sֆP {R/~wX^^-! *ι|F}߶ͦ&fRM)yg!}`7b魹 TĐ jIx<9jC~p""PDTb\^?8* kߞy>No=\NVwB89H0);>gͯB֙cJn-4 _|O?Y,VWEĦ1TG!ź]]^siԗ]in9$y>(]~J9W7n1#dEPFst|:kכ5&ir RDĨ)C?'GnhٶqYn1c&$z佾bHJyuOk*ǽ@RR&,9i譫f^21Pc1&6Pv>[ z~y矅il39}g_79vM]MdiơGU?0M^۝8 c8n{%9 mW4H$iaa,;ƒv1[%8! xT U%uZ {41,"!x@,1FkL!`Im= jU8:gO $Ffb%[kLT[jyYj]%J*kfmspplr1dےǫ۸7VU뚦qUU.@YT 1VUvIOr&6sFv)g˔R1%DXRJn$"kYqmH2s]7ֺf6a,Ia& U`17lӜrwfhdH*h9ST!0^_;p~t x@ )uzA{3n(q3>T!ʖvVuo"o_,^^*'.D"A7_|jkG{vMYqNY?f{px$YC1jcTE-Iˆ$ q'ݮV~?ǀd8^ع'|o$GE1g3e#h :oΘYk_zaPOPz)T aTdTв IDAT pVȢ]QZ4eh_PMYEI<덊]°עR 31*cC>$fqug/l|s )6Jpχ0~z4q}Ten^˱[){`~Ui-L)a11lȇhgmr1୵ Dl=8>><9mʙ?a]]UUS^ M nw7gG'46Ml A nu4BNYBEyߥ8VQ@S?îۥA:t4 a򨂢qv3}!;8Wfpm~MmWQR]D{ﶨ(@H/ !c B><(@l۶T(hGm4MM;f짔s `YE&?$5((cɲ\.٘a1zuZk)Ү"62!6R!` ((SH"0]v>C2$IǔŲr4K0֏뮮@Sd&$CHI #z%}uDU{/:4Yc1!@gʚ7_?ÐRnfnf'G *bSxnчelFD&gUznc1(d уm4njѲsaEurl_=뼉!1uTF?v.l^j}Zm7Yb:kTtnb1Ĥ//a|xr?E)\_"伽HmKժ igSlLUcw1/22ׯ$EAekunv/|'Fݮ+i*2VSr**>%-V>g if"ͦnT9A3$ٚ+m9ׇ-EU#6f8$&D)2$"6 {m׷UeM[Ѝr>~i=q 0͗2OO~ _9<<M|* bC "3a*$MS70i}HV48^BJ$~Hø~1c%@rJD"ڦmIߥ<)"23K B,\(i!68@iKq`Ëb9nvK1QewU[c$$ųxGn[UBNɱzs;k8٬4D2HΨ[ lI)E]7Uu]q`kۛr94Mo_旿E۵mcbX׆YkmUr:.-)7h\o}E;$rJj><9wtͭ(m;ͦ,W h~|D*gRo~qܾiwØ' U80szz5Κ9Ͼ[9X)ǣ.65<֛ZgWDDo޼?y䃋mۍÖ Yc>|\,⋜[æ\>$7[=3ȳîjZdRֶv ֎ HwdGu;cz}}sL121J|Ų"~ؘm~e,v몪bi4igTPzZƘc,!R@.s3-/AT0qVb"\`AEb"9d,E<Ͷ "{]ݴ!RU7MM?:8/޼ 1N><,5gJ!aFɓn#\{vƞ-ʽ~yGcSo=DlXE NE49qU $C!E69KeOb"QQbD!EДR;d?vk޿`i1UׯutCbkIsX*iWn 8׶\T~ )GI^$"jBpS`sE&$)` uL*l ,abRPD"6@,*wRQ?.]VTq֚"ʚÃ^9!TMSs# ZS@,(SiZUu]dm]T1, TsNޏE*Xۦ9==U}1sV"L)EJLb%8Iλ ve/ZT1}-hJgX@<^lWπ ;)"՗W$E !(#1rgٷΟ__|6~>|軡m.EW.c Ӱ[#'q1Ec.%v"Ҵu]D짩``6B bhizXTfRR%(8)  ( 1Dp T]S!B(! Xl5&ơ"ˍ)*åݩ"gV*AҘgc f/d2u0 My%ݮBnB[zzq>nַ>xc,lo790X#m;$:ߓ|̀@ѣSRy} g˃v6?Ǧ|n}`R!?xXN=|߻<^uv׿?׫jb檪|78TU")Ʊ"j4GDDӨ)c>/?կSf)14Ǥu}/ijX<g\gZI%ƄE~veAk)vڻ%>xq 墴JsG"\ej}9Q"Q@,UM;K1Ei|f[ɐhYWR %YsκY[;ơme8SgQՔAuSQS70NյxMDYDcm[7n!?>~*Zm|6 c!MUH)f۶O)o61bl7އlVMY)umsUU֘ȔA3'r;ęR.3BEDS7{HkU BLQ [k )xcDQ >=eD.Nlgk Xg R`iR MS|xX.f!|}`C<08k˓~;"kI)K`VBM9j0NWP)L98M989'ATͩdc.`0!BU9&"@ɹx;ыwuU0la)ba&LUY˘l,R=CD^S$rz+䲪8bY+?,Wu-j@D7>v[,8H)Y3S4!iX"lr9O!6MrBPUjoâRfֺQB:it<0'70s@ʩTEKXsN]κ8N)ujD/EI(.1l9kV$y6\;)'f" cNs" 1FRPH1:نo Ř,)9cC&ٮWul֜_\OSSZv3T* {ۦ<|8|757uS(+l۶>8BT2#fJQݶyM|sys}_FP+:vjjGk Qa}ƺ2|檮cKŒ u]T%1}w}c dIy,1WΩBcirVk|{{/u6ݯonO2#՛ X眵o޼/>u]I|ED K"0z{y1mNqͯr 4|S,uAPEnnoΊs{^R@!R +B鵊}UUǧ'NO|ǟ^>8}{DI!1Oaoq$"լP 2+T) Ђw)$ ~;s`)[n>r9!L}8 _?+sj8Wn[g˗/@>z'W/_iL!(:k~764jb,Y%M=ơs1Y!9^%*IAPĪ"ɰ-x-4%]AE!&c-<] VUa$ `dY4 m?S"yퟻB^1"C0BJ n$U9MM$)zA%k8N~9 U /(6nv~,@X""HhakL WT*`8HNUg~/?~xGHU+oTkfuU[o(8>"rjWv00 >|~l9onoEi*><ttmlif Be5<ؚ./vMɟs@z1*N>LA+$'$Nw^\ )BTgꃣ?/!mt}t|R.ca]Ns=_E0=*f&m)XgcJ)g{-WK._ƪT|YPF**ZJ~@rJUU݋{' 8 CL }cV6_E^U\(Ė\")'0֪jVe95%UmXbJx%]qΎO1KC]ՠ0oZ~uj=ܤ߮[R1P뫪w?n9<aJ1eRSp|t*"mS\lVkkLJ!QES)X:o-֖ :g80*UݴeEĈ\-b4lY}6Ѝ4+~,!X"1Zc*z k^(B0DT4!rZ)Nq=8X27o>f'GO}̈>dtw\bjalq)Jvhnj"n68P^YE@k\U9fܜ_\a)BLoaR8NUi0auYpa( \P@+GNXƳd0$:{!ݷOqڙa5RLEJ o޼Xnj cHF`7*觟??6qe} IDAT?W_3l~{{=M=aꦎ)?y_֏u;1*\.bغ11Ct|xvux}}~pt:H^o/ocNhUEN_jm 12EY9n]qƔ$5ְ͐%Ƃk^v] ?0]1#!.hfv% U*Q1`vUbJV%Le16XڀkM9< "0k)X@I`+ƘYWW B+R )R&UuR0kpyGϞuKLu;_,ٷߍu],!F|a@U9e?Ib9Ǐ_^^8OL/ oV$0u}kglUU&`Q[w;#YR)xφ)}X9>Y!UQPA!(?G<4y)Һ٘c,p"CT *S%ƒ~B$DefX(bݮH!6ְ1\e'_eUqJ TX }uU&@%x;ع dc0#3Ae/1D*\3-IECw- *,w--;4(@9e*gܝ^몮 dZ,ަ Դ`T(֪hW䔂j6ZrY{DZi1\;+{cn|!KADuӧCmvnqU6s.i0ŸZl1XOK6*988[vfJ bR,YcjWN%Rƀ*h',\%PKUsHy&r4M ;k$젦k(-lRYFUQ!kFE(Φi{ۮnUH6ƶsJ)!eN)P"w jJ ;}I^1l Jʚ *Ƙ%U&C]Ć{xſ>1Xզ~zR><@*Y4u61&Ă/ .w6h5!>ÈuUdf̹*T`Y~78H)kPDD)5r޶=9>)C޹leRfa镵4U4M]OϞrLS6%UE9fkLH6f*8kpR34vlׯ߼]iȓwϞ9Ά0YmQV )| 1\8* YED1^_.sU;[] 7e'tqsm뜳uaFmiLyZKjy/_{),Ǐ?/o|Ys p#)g,r&\aD j*]׭V|KVk\GE2*C[k˼JU4*s1T5’+K1aO.B T!dKub9'LIJfÌbcfalH\.E[uLNUu-9ǘ1~^~?^__#yJ)KƏ411Y[*isN!zTM=8 R*&?|>Sb1_(kɢn*;̌*c$RHPssr Զճ_~g%^DiTS1ncme~)#87c`1}OHO{rˋP×a6UU FPQ0]$ : 2#qQhV1l.1!R5p]Utו/>ga/D()gIUn:vgz1o=P^QRj199>Z<]]_bGH>b]lg3kz8\VRU}kw/ .Tf8NPq/4K!E$cDaƺr~Vp")%ivTe`HH>O|%gkuׯ'|p{:1i 84} Cw||!UU]Uuyi ¶mgmEq̒5PU~fyptcnncNYǟ|>jbfk!T)!?d j^r*%6|||b{u9S6r)YκI3oC)Ŕ=~nӴ]7c|[]4K}+)%Ys@+Br>s*͔dRb Y.9?M9=|'_ Xbl7!r!\>L%2Mi Jfzzv݋׿ym7ۋ"x??3r8eg6+w)!JN)FVF檪`! :3?7mS;_6! AMU5Ջ̨&fx}}1f3}zgg,MHu5n}rJ 0 c2 B /lU!r96 ZX5D?S 1Ɛl m3?82~B):gʟ,I Yuw7PUOM;'ef=\6LHpGzۮ+=o1n<$UJ]pl2яEr!I&1 lUNI*JD<=Ou]s̑?wn֚k}c$N&;Ӯ(5(x2ެ׫劙2a2v35!Q_d sni$n0󽽃ŲJb ZeIY~HLo<|WMQd8kq1OK˕DWR(R9FܭM. jc"Ic9$i DI0apPJoNiiB Gf`gٓjyw}ڮͦn9BVRҤ=:vϞFM]um>|u4{_H L10u.%FDh麡ۮ׋sDHo}71޼_>;8" h>q Rw"l"u~; =虓U*QX^1~CHʠIIw>Crpzq+/s="1sCE9$Hk n4 )LsQQքqc+9r1F+~?ѿn]oFUmۦ1|gD[YNU*sԸ└ғt6OGo:}_|h4 H0~xO9ɻQ C;|6R 0TzHf41"Ϟ!%'Ai-"!ʉ~J"*DZGwW]7HݴZkjC'Av"lgg)pDڬI`2[RYJ]1ĩkjILImj\\ [T)#]7DCrMzwQ:?lb2)wvarJL)w0u#)RCԚn8 !-׫mAEroRb$yX)LD)SC6v?;[ոARR~=Ιlzqq 00|2J[m%'bE*?9%kMJI@Uc ! >$hDs} IaVWUG"&" JL.[}N$֨)@"!?}c18{gWS`8@$rb8*#"BCd$":Ġ^۾Atm~2 )&TƜܸb?/+Jzޛge⨵޽sqvvjtMtCf0Z߼uk>pC+'&%ICg$=}Tk--/wok6z"(`{g}rR1 as{0JZ7u1Q a!%[%52'1,@`ďu]>pVx RbF+Ȱ]#ks>a*٢9;@"﻾o+q 1h$!vJJcLuZj !%M d¢(FEI"t/x_~!zh<ҨQt';gV|$ >Ng{Ϟ>~o|l>9>x1*kE(Q!m]{ rdF|g6Lg3W/_p6M5}1B:xbrVJ$!9Z)$d:)pdc-XX xbCuMS<(8y4C2)&x37D1uˎ HW[DĄ?bd)n^8c|"^/V5Lg$ai؇c̵"pbetJZ[8UYoZ/ZkA( H@C4aֹsV(Mm kaה96TVck0~9 ZIx2D8F=rJg %>'KNJR3;I*qHjZu]NQBJUQHӶ`+KtXUr5SHuf!'NDV:\,-ikmFsqvOܜb|c֚92ۛMnݪ˗MjcuEl` 3iqDB"j4%c81$f5rˢ E!B& Rd4yM#$kPj@`H$)(7)p>* C_op4&w>))]%CIRFWUYrzC ) $tNEsWn2(@[mw]q@ղ֣j6 Xٴ^#*Q㨪Zlb)Mb! k970 xкB@P(oŔX83.~v_LQ%IBY >ċb5xOZEDSLql?<<0ԛɟ)3wo@lv柍FlŲΉR@.%D+vc w{{ӽ?8?~26-RCw|rspv\tjXG¸׎km}rqrpz @{*֟_6tj4Z-NO_)eƓyK/1G1?2Te!ϦO;I$u]c@e7\s'%ܿﻡkZU K#1p;Lv_~d<O[wn~}͎]]g$,F{X|޸F1z2VaD(\Jk[GQ!zt۲tqI՛ONntm{|x\U/cq#0GLNNITѝEY-ۺO8_$d}e2U)}CVfňX;F>M@(e )ُD2_-Oa3e/@ ,vRq9.Lɱ⼮ksǦ,b9# *>nv9򮒣-ۖ1|ua I@Q;&;j2tv~f\~=*œm[(UElC>*|`{&ˏM!(Dy ( 9~+RJ}?h (":DЇ^oݡscl!-ǘS2 r^۾ZQ2 %MckQU? `uU߼dm"%Wyw8qP@b_^^zB)!9E4 C!cTZZe8 =n vYG iAEw߼!D@0ΙY9gJ0EJZU$NQi "ZY/~,h!V2G}˟4t !EvϞ_;xTXmoi*םw7f ۺ^>|D֒#7ZRnק˳>|{g*I1I*K")"2 h2g\ܹs[n\>mL>\޸y^|7{%12GI2:6!$qimlw\>x*owm>(RCf:gn߸v8M;cH|T&pz_) o6h[$H!>::$jZ.~MF0"Մo\яe~+Yl/_te1b B9@LDGGHdVdvvAWb0XkٳM.?zF3 2f*DN$景161(DKBk UlZ Y׷bhqqк5h4$$QW˧_I56\])9K|Y8? Ze2Kk$I Z4BaM7xsi=}vyvPTQ"Ƅ bJqB#\'=&{RP@ IDAT!44Rޚ:\8O'pK _sm|QN, θq9咙9hqi)f ezaRJ>V9;e;A81BZkBl7dTe$)jY6]Ikb|dEJPi Ԅqe5uEdTm)r6&dɜjk- C))cë_|(J<(0DHC)HdSZCN Oݾug_}^`H7+k(`b`zVO B  R@؇4& "\8LVY0Q=[fZVջ>~|o8)Dk5d:$o6[Nq^j<>~Λoɏ\*@Z+Gp ;U:eBMӧg_'Ã2t &];OV/}!.Ϟ>xPU1[_U,D ã㓓oC} f߿{xt_vZot<۫mC>#*B_75"(M޽7}hOOM׹qrD8׫AktQ>bbd4ymGmqJmߗ uǜ@3&xv~icܛL.@ou}ױXkH) x\0֤vL<ț?$H(*!r"m Rf2Vd28Dֹx4쯗e𓏾C//T51vTYBNg !<)]mltP]dݫvq8X7ݿ1݃CALG7YqSH^?~E_og{㣓ׯo!_5ƨ fL-彫ldM!.ڜMӤ$Z$Us^8ZIוuA* q!z]/I%#͘H)휛f>zc BhGI E5jn|uscLJB'QI +?2Ɛ'솨eyR@+j՜\{򓪨bJ$@d &׎=v '7nuQӡ^=}X={8G5b 1{Y5dlM1F痫yl׏<|OIiMͭ.R9롲TжEC؇˪ {^۵?Hc4MR*˾woR*2k*k&HPf:W+C۲(gXN*A\&p!(2UQ ٞ/әuŶ^m1j6k[g/W= .Wf'n*jKZM*Wggu޸~v>8v>Dۺ>؛9'8;}}rSLl~p2?8X8gעwa9}rq~ fclYv](12a9݊( kM1F01 8!n[$U !V@%_&!Ggt~QB.*8~hmp"QJheH)$Rk#M;F)zhcAQnsSi9]/1Z\ 10FRpno/ἶ`h\L&5 mr)bl2GG?|/^<]{ed6QjvGQi,tj6;9:ڳ7>fo6-YΕ@$H]].9;ooǡQiXEB@`JѣG CH棪b~hׯ|0QY ($)Z,Q4Y7:QGeڶ~W삆@@k3c eQ|{j^7?zM۴]O tm`4}OFCyyQXIX!0(IEQCE\&fE(z'|ۺRY z4r%n|_z<;;jvcr֜ɏR[^~9߻ӳj2?$00G"4dZuCu;IJ.U6aMJq Fq8q1d\ߞzeUy N" ]kFx2Yo닋"4/ΐ u)CHF*чo"B\,*bv=+!휵ڍ&juۺ2t0-ǣj:rgO>LJ ^puXnj2 n9KJYDjcmb[^e# *#YD,-U"sƹUI;9Fޅ*|*ˁMCF$aS~=s.RHiMDAVcF#V*Đs!?%#\AD$Dڮ> >4DJ"9Y{OsCLZ3#@ c-Έ1FB"RJPʩϦirwȻ]@gb֪(˾m3.?t=sÃm[QBT!y~t1tvzqߛOFUUM~{{!=}v:rxk'h~XNz_<[r[wF/_ƇhD`v]kƶm)'md?ln]{[_My_%q5I(PAWxvA`FEJﻯ~2*B 䛤V9'wzk}lqӿu4͛7ŋWa?z 1|`jg;9Z}}yڬ3sO]\cDR5Pr)}sJC8͎)(JA6+ `weYr upr4L'vziYmuU@m\el!d{Ν;(E^o7Qe\Ӆk-dCHߣcZIqtp8qn6F7˱-ӗm3h ""4PD‰S²Lطè*X )L@뜐# BL=sVW!%望O@6{gw'9yjOD%>dPgj sJCn5vto7ݛ17#Dۮ"o7RXxvɐxjR!j5Ľ7s{b1&߄@BQU0A2Pnة "#p"CJbΞ^Z:+25Zܻ"&?5Wl?"[w /~6#)%mh2J]'[gbsN+=ǘ757^I,uJ)# !$1ߙoTUUg6jZl-))c۴>0:u۶(${0 2[Z!a ժHZd&(2`Sejsj퇺n);rU|vQTƊl/'N{Pj#bB*/_V;E&aD!Ij5*v(/?|M %1hmRcQP ),>;} tFѴ]QP($A@`B B@N  1JV!rnqfܣGV*/3*˘?~^o|ևggZ۷o9WBonD )L) *uڢh2VJ1("$J#aAk UJ:8.K=Skmu/_<{}q~˟[mΚ̈1f0vzp@®MwI+Cm0y7r}yo[7v};&֫e..r[M6V Yg+|VX3+!4!DaND)Ѭ(D`#kXk1[CyصK|>DJ)RHփ(=a~0vbY-#+t*"w:!R( c<2'PDXBZ')2 -c\9Y3$O)zf(U"sY7M!ra%%BJ)GVTHa;+Rx<^yA"(,s" AGGj2)SG޺}|8ՕIUA}}q_]4OtMt1ky4r4 S8QH5?skH|R_GfLA@2,Ib~DBD{I6^H9 g] ~א+H|;CMӼ|D٬WTG^W~g}@b ?rGMw""dq}}~bQeYV:޿x嫗N_׎ORp'M6M_8ԵCYn#K9SQ9%zmH1@қoy޽æiL&?P}mzo>DMcv~h,rTDeQ|`cm@%械o}Om4uC&ږ"WSic9':dgˡL( (q"B $ j@DuaMצjEJY8{]+7Tb2OUC?t~{]eY1X[=)0uC[_._Xtvs!ҾOCﻡo6C 9FBcVYgc%V ]wu"KEKW )U*"d0!M>"ڦh!ߋ1&Q~DQ0.i)DB a"#_H\;ݮ꙽V:㎕|My w#$\)fxCja?13+Fʳ$œc8Xu }!\ pio=f)Ǩ6Z.I1&fNu]U;{!(Gc!@JC )q:4 ]|Zgs" a뺶n$8ǮmכQYHYM},HR}ۡ FΙѨrRCa5a>/BDք*Mȫ) ȜIDVm̲;dQ^91){0|W= \-"RN ]zA.(Đ,TʧZGDm7\Gɂs9RJr^8ّWsH,m !!)"PXSJާ\SJe)y@@й;ʛ<Ʉ$iۭR:w;vH+cZiD8ưv3 ˜Ė FfG.(ڽE@+SBL Iw#s:Y¦ I='vybZn2fV@ H7O/g[7'&]^-_qNUma:6h]y"NʋָlvX~hdvRBq2D@!BٵBT1[o߾Z-e["ƍ[{{y!cčrq>ٟ7=[o)Jk IDAT:(J)x22ƕE]oUF@O|?_WzhҮ.~u,t8"VhTBSJJUYh.7VWQstTُu>7Đj.*JIKm[6а~1 OalK6jlYDq*ɪ[ӽu3o8'Χ7 k[W_=c!C띊XC™1ӯ7ZQY4cfLM-!067λ_Ï8Nf~ooJҥ~NVe)9k c1ŔRHc:Jq_CN)13f1EEDBF2aPփ{=<}kgK،}LNAbƮ3dC UGnrCmV+11GԈj aЋ t~؍c . "B6U֍5FTAB1VU)&T ڷXkTA)Ff}Y]'Ye~ê^=*cE.:R#{P̙Z'c/m~ ~_|;* *ƒwtLJ XБ46O<,U%_bQł3"BQ)^i"r:dΪttx}5u"09|~hXxǑsa-=9dҦ;Sƫjxc,ʓQS`TPY#Au"cQhJEiaǑ9 gaN1ǐзu]q%t,c68M@54'nw۫ʻ%gX93m'GC`Ox*9&j:O*tzս@:֎)a$293D׭qUU {زdlkֻ|'Y=i-+݄ #]fό1u]!@]Wd>/?ⱟ~7^w0 /^xuWk*TДޝ3AUi:Ip6;{XΈTHٳڦjz2\1f|۶vTNjgW/}oV-K7Eڪ95}fb%"qyXLjs6@AM&{D"cui8קǜsZW7! C/9οuU/=NC2N&d$61-n\c_:֬uʜ|f DK )"Ean78j=̊"@BJn#xqqæ`al%cJ,"1ra%sʒz[,&Ic6)@usPYwX,}e֒, 8hl*c0ywÀYDXRޕ=aO&%P5ւg s.{C_{ 8)Z[JD@Uv!zafF4%"dz""IRNr2`H ,PvGDc-"ꪲ}?iۇ0z*﻾O9-rŢv;cmնtW4l6A}UZޓȐ))Iv΅1!KFOIn! Ygސj  < h&C]R Ї~l⮭Er3Xy|:\{,?%Mt˛{'O>-'9ͦG ]s?bcaq]LuމP !e&gͤ{8m۬]LYno?>ꪔM'3cݶ?9mŜ^AD`(t*r+y|?oޓ/w]o*8c }{7?|:icN!sz_O<}"ov Ρly{3 3/fۃősA53_^\,n۞ܻOXVÇ_<{\.n&W70,λn{{,}mOӺm>]_/c :qY!K(jTr&㣓hMk$Ӵb1Wz^%qBF)."s}0Q,Bb٪8:X5|}φϟJHɈ Kzcuc`fnXqHUb10d#mT\CSTI"QRq wz-כ0n2Mw;T%"[/vsy:?)Juբ3(Hf\6D2 W_ },!P4c! (+"q "b1ƦTMoVə (ݴ`HDPhh+6T GTNg2;oQ-S(r|eGkhK 9+eXd1##"Zcf75X5 1D̩tCu}強j*&TY_99)0CLH0nwh RZ_ؕZ&pX`xŐx'cz-um?>'GXcsNd Umw^5Bʜx0ƾzWO eL{-R)ם5nCd!%#djOU$b?zWוe+oŬzus/H@_L?ÂOښ~E$"sxl Mi%춻o׿}𫯾@B<=>b/ϻ<9owϿ\.32ԏArz{Bqx|\]8td7g'??~66u7IÀ^}/ b>08ѧџ8{ytzqڶ^ΒB\x~8 @֘o?TpW77~# @PUW kjrs[zmIݶ+;[5EN"2e#23@H(fA"cMrʠPi %ų_2@RZ q8tbqyvα`cs6W7q+$3ǾK}%*?yDĢeC]U/s;imYkl5)@,RqI:rεm-"!F8 R21-.;A_ :K&i-rCn.讜3Q./C;f)nEUAUsf}]U8ʖU8Ƹ/2.}wnӴ7ͮS0t}7N{U88XvaDۛ\} "u]Y6eM0d󃪪v-3{iUU7= r, "9{xTYo\|vjysu8*Ge[1dBL7ql4;P% }D&׍/WU5>d2mmڡ,O>-j^>/k ps:;|o }qqRZkUМ!H  S8q??y8>f7W6f~~v_wG)ogWOk/ooPQX\Ea !DDaEbfcl>o˿OKqǜY_Y3zzzmW}'_?h&G>g?k*+'o}ݓÃ, _|o/oV|[C}ǿywN_ϟ$"H)?;>9>>>BObU:gYZ;bŰ0wɟWsz^>].󃐗cSN*j n\;}LRFY1 j+D'21erY^MVT.D8Bp2}ͯ_DB*vNr,)~>{EVǩav>i81T9,''131f6i43T۶ߜ]-/s}Ez2;kckcM$1MC9=:4:kwՕo]f1D4VE8*YzDH9@G7{W7f5om=[0tNOgoϮf޽]Qrƻn,*u]sN @aǏ?t}ss{rdc ?C0Օƞ_^J'9<_~~Yo1M[Wq—="֐2؍˗U3z]y])˫KkĔ.a0 O?ѣ3&%zW֚ҴӣϾoNd~a?;!7iଷ9"CoTEt_Y8"ǜDT2s4뺪rJ R.XXt0O??q&c ʒRT~˧_{]?|Φm]UNr1ތ}U/uۛʒ#30bΛ՚9pUM=![ 7덤dxg8EĔ#9 gg#g +Nl]HL w@=PeSQPd -1)sN1191&Ӊ1&$YJ ]AK?xqɐXk{K|ϰH{XbsJ2(=u}JbC!Çr$”ͯ?g}iلʛ ZFqCxeKwgkuJ5CPtKH:SJ)BF2$bAAq02wcH1kwpppsv~(Jv͒,f1luO|i?ۦvik WחWLNq7 w vd_9춻.zysCX>GNu]18˫7r{ JgcfW@ { QIU]U!qVyl'Xwvbmu˟?/qח/)pL1T9?Lsl9&Ÿ**[w{sqs{eȎ!)!Kbʉyh1yFk)3ꔣHB'G`Ϟq8w6iP* y~pPy٬FT0m|eM#F@ XV!iJlS1XrY)WUQTX8l[v_vxp{ɓ'Ó궴nJt7dA;9"ZSH"5ͤTT/6bʌ0A,1 !S7jlJQUUU} q{ 1O>ک>fCxbTU M]x!k p]DXTccGreSN=H̚vSʪ`Qc$0$"/^`VU9ǾLU LUycR@c,gL6u]SGlZ2a4Xk뺩zۅq,ChK9ѽӮ\];|"}> 3o֘|c)޻x?}tDΥ,LoKbB48:ͮC8G}/?ev:Vk溮qv,鳧g/h]ya ϴ\wdڶ)aؒ3|2i~[=x}r}V[Ba,ۦi^{O1޻nT$|:$*HgsQv}oJs@uֺvde =y4붪jB_|v}s[t)2X9"*,9zGG!o㿏!D└Y8p0* QJԵ*K uq߻+Uغ9%lZ[cd T-=TR$JD5,^4O&326EGlccԕ/_QO<ŋWjE¹w3;DD…C|9hcWU69sa̹[E*@|42 }#GBdaRI" j#H68a 㖗R9?qIl6% 4! CYYMedvLyf`@5om|0 "}z"bN1Fݷ1&b'Cֺbۏ4EYeٷ :ŕHUU圻+]LYidžL.n^*nW˫*ڳ\i+c/MћJ [‡SU#P1Tv "J9X1X*c \w|>߯eR:?Ͼ|AU%NrUUyK` $SD81HC7B03su8. rֳת!MZ_jg+o[k8]㠜]g=>!t}_bixy5$5vbNvk,7j9?~ ~XM;mrJZDBR !r"J"Ɛ<" 1Ȅ!HSdLMX1Bw[5uUJe+TCrPk)g.lAQn6vw}S"ekRLm8ΏDՊNȨrNArBqT. 8ƜʐhTrnZgm!B6MݶP\V,RU(11(!E P( (YDc,s..a2dZG! TfŜoª $sT*Q{ig-=yl^_DW+~YI2K4D3((VDU*QW3 pD0\=P=Y?%.K)8{ZlYG)){-3~\SAc 1f#6uBdL+6RryU%Ƹ^PTRK4&Cm*cc1E Q1 ~%&0XU1& 鎵VbZKY{zt:mPz2iqηm'?˫'Oۆ$LtuyIh8ǿh 8̹ͤ.AGPǡLGPYKp d^3%* pcNH@ !CD!c8k "9(.NNܿw}}}yy6-9jbLW~8xx|\v߅V7sg/!fg/q#!2۫[\WrP06zc4RDXo*mw6'tVZ}[oÓP&]aLa± "a}匱!!Jof2dm&tngBE{'>ϟlZ$z7X6\!+7X"TU5">ĕ[H !s.o(8qЬ]n2ceӐ>E-UhTMIk?9!3~>?zww0m7 Ɣb 8*qt]Q4]'rRPk9cI)cWUN,ૺ4\^X'@QRyl+>$Uy?q΅\+o9k3%CoJ"<4d̒UP,^sàImVq1)O^/_]!\W;J}+Ib_r*/.e"糿sw yc]e "WۺUUz,YTv0]qPn󦩽fM]9kw cT3]bsv֠B'P@X4f9 8ղ$y1CUP)lȤV%mֹf[_<&0˙T'kr9<x8Gܯfv~0C\.WCK/ONkr91+f:Ci1s>|:T;P Ko÷uB}kD^o~?GV8s DUUdvF,C,ƒ)sYGpgI$0CUqLc70ƘH]w;/^{{7[V~'M7^)-7YDIZBfrFSY8Hdu9JgL& \]ÂU+,]SU2gld!=p URTUxwX~ZBo|Xye,F9,|$wrhWHzw++ADކsNiy0 >N%|W*kLS{k+D@~SU4稊(9jjPvCN$3gWGCqQm[B!3[K)AD@%ApuYDU)f1 !4m}rUlSW m*CTyWvt+v1A`.2}*i VS6μ28}e2.q"0EA-iONNWnuibAyXZrjuЋã.-bus&*c k\;Oַw]90H"a n5x}ˏ/.fˋb1zmrff !(^_ӜR'੖l:K{w1 IE̚bRޛR?[^#*0"眯*jt} (V HDl ɘD"d2}mh+?L|uzlI80n^>&,ACX$jyg*owB ka{> dZg몝NSYTWlT-/m!l=Z,7kDP6}o cxqwx_u#vzzbn,[ݰ;;v}ss19Tv]51ϟØr"bQeQ% ,WWJ#Xk˳z݌9y|d"7oO-\Ltdd/iðC$w;{$t齧サ]? K撝n圙"X=!a:;Ue?<&J)8~ETرa"R\p Z8]ӄba52E{7rqWwm.y߫l6~9Xu<":}sjňՂCG \3$u܍ɐjʹaTF-rΓb&{nA4DLޓGisC4cUiFr.YRMT*"0X"h᧻jFvɃ5@/.~u=;!ƬjX,n;68bYCTsJqD%xRbIP0F?j_}r%޾`X6MخWTLRh溘|EabdsB:Uɷw7? .I!ĚaY-D_Y羽fm Z5Tij{.bnِ~gO@ Urv~~&7WWr5;5ib&&t]OhUd-,>(RpNӾo}tmon{Wm:5 3)9RJJv|sZ9GO{՛/4Tff3r>4 Ri*).lCIDq\m6C" M H"&]W_ҶMcU8G޻ Zr3yL%$G.}_j9^u>4"`jSӄlRulX'f>N**FlP"MgG?ghـ_qPyBI)hqsۿ8MEMBe{S$lڶi|J TTDo=BINMľ@Si3!n"TqkBӶMm朝i˩!哓&0X6΍a8MWuBų%f8L# t]WZ"5ϖ8;5bZiʲ8&RD_e%G11,Z\ܭqn,8v`5;EU u]??Y\>|>qc`*q,EqYKaEr TM ﮯ@i8MauRJ8oXa U*J*F\}jQ!k 4M!8Um7W)vsvv;"\koqH9J)MK.*ʠM8bw`P}3 %Tw fP"vw6x|g|8y7;/oϒfE @IηͬXٮ7m:+{b D4xuu%R_hzg4S# 6Nۘ/r1 ! ެ~B-E07VfpAݛveX|h ټ27nu'jB1P5@jH 5sCrd!f[), ]E-ӇP+C)!(9G3XeyBD<3fe!x(eLMJi :du 408"  x?#[;wߎ Ef+"4 %jʉ0b@]vnt WQ%Ś3T8RJ*ZJ99bRN~U6=,H,F.k[?d)''o~zs~q>6mvɓbCJI`gUH1|TKR$惵ʱ =1s|i" ryQEKlŸf"jLh) Ɛ^\vuWquD@9'4Mצ[]__~3HJx?ʺ;Uepmw]=֩L\Ts*^2Arb@%pt#CCDartK.}1"EbΣ5fv.4f5^  UwHU/DGy{8(O"vr3R+pQ&ڶ1.7VgR}8圧jCbv2lbq6&7 `fqJ9џ׿7a@?[?su FhMpA4uOg]}~m9mfRMvþP aBevSK!L.kE|hԪs1]Η}.Bα󩨩6 8~|t8RI{Ҝq70Yɥ 㨢ʊm-}TJU,RrÈ 7:j)ǔOruTabJ鈇IjYe 04R|У⛜c;iєSQ ]3Tjϖ̞z]H,G~m}94;A|Z chVU%gS#>x$#؇ \J1%{_;; IDATnwGOaUTECv>-^Hv>8O1}9~۬T 9nB< 3O))V5H rr>L ڶ-]hqK2ZG{]D nMopQrͲ\pRI #Uo1Ʃk;fRHT\|maG`"5sE,Hi kZTWWdi|މjƄ`hEkјc1KޣAUJQ\p&J1;'R|hjKDRD*BI! +;Sͦ  *bdU],OA뛛,ڙ8=q!<<97wH@&tM3"KJY'O<|舿}Պ1&"}W_}*]p E)g72gI,3 Hjo4G 3y3 41q/g7"o'CWtƾb(>{zo:gssvRͭQ3vuhfmۅ,=?GWJ~?}/M'Mp\kMgQP"j!}wM*途U`$t Ar0"E;BR>,P9u(Gsi\=>ՅDHUՒ"C˓oӓY^pYy:%iȡn zMkQST9osΕ"5M.ybY=|`߶)uz۩ҦZ5MCƻ┯/wS^<}zu[o؀)}]TCXL$mӴ)#Fb4z\L?x7o&$\!2IԌ:?[G藏=J; j"r$Ц%'Ȥ.X=~ۀ{現 +Kt߼{Cha:.qju0McB!yxV\=y9Wxww3 CQQ()M4Q> ̢*`6ũu.}U.'ljXzZDE~PJ)E,oh`f%cjZʥi[D9nS8z#jFՆuəRT4DQs΃aQ50B63d"`Q$0`b B60VU@ I)R#\^ݬ?zϱ ާ)Sp^ONmJb`\CRۛ+0l|{u0&ffum%N٩iJD.Ƽq$v1l'BnBjAL!x$}H pu}]?k!jڦ_,\uM{gӟ櫗/Рm>;=c>r滗|nDTuIĹ$=Xr.UTRmϝcTƩ{1L@vqw=; @!V-ZHkcO9) 1 vu15)DcYlƱ%Tf  UU*2#i.ub.E" QB˜.nO;Rnww74>篾͛ï)uRQ1 ` !2c)bP"1֭/cR4CZ:fb$O,cL!@XTsLfjĬDܶ]pع ~ֶ˗_Poyݷ*O9bbi<+uM9ȹ!INqUaKW܃fqJ[QC&p~y2O_x{~2_.( ];w|,Ť9ruw^n]4}˱o U@oOj6h/fv&?`88;$%?مտ/?GrH@5hϩsgJTD*56R<ܝ]n {}eIqph##9R EL&uMf圦 !fAR]8A Ua2jP;l{YTcDwE &%*>^KIb͹T6aUE 5)IG&45x$$$'iԭ!SДZ b"ߵ@tCLۻ+\uVɚy((E|8N? >/"2_\<r0K2L|?4M׷>BCO1Ocj|ww[M鏞zSOGE,MŶcl7nN㘅bQ]BkO_]gq栶 4iD `)ls]֎77w!ԇ97CVJ?Z) jvMۺz8wxȐAEùv~\2ӣǏRLnWrɩT9 "R<;惐 q'D D{HΩiUVUTkDo[PQsXSRp΅UT<^yp)um[4oC049$x|qX@8Z9v"}Hםyw"]?f̘>^@Zʫ1iI}Ȁ> u]ݮB2V )9zYMӞƗ"gϟ_]]U ޕ,A8`9д|/~krݞcxӟg?ӟ7 JJfĮq,1)ن-InY/?,%BB]ߺ/yݛׯ^B p3%왈EAs{Iqm׫8W\ 1c*JDI)MSVǁ2BGv\Gr*ɪM #-s6m۶eaчP ]v۔&bU^[WDp`Yr^ <Bĵ-@b{*"|D !AGzmۄbRUix(3c)vU_g[_wFU3@(1c|8Y>zMcbxki4M9eKXO.NbB?eLʚfg}_d7w8=`HFI _&bBn59 ɩ1;qgr:m?f?ٟ}ś7$vUX70 x_R0vH~(hI}#HӮk˞1;23x'wS*/>|{uqpNbzuIA듟F8L(`̆p:ζRJM M4>*ќs}[իjdjF 3y*xiPT +`dvs9^.|/a_C?ɏ9ʙ"<є ͋_#TйH@TYTDal?OϞ=bYeNEY8G9n;w4ŮC航 B8m׏lq4C(0kRn ~loN@Pm{{Bn?>ڮO:$'qqrXy9 9E%1I% yn[ѳ󇊎HYQRLt{&!ƹF:BORS㹜R "!BJ98GNj>bH9A9T+GJ98ON^g&]\%8 czՐ߬dbRUgDŽ j0Q(vv5K%Y4އ{ڦ5g1W J\̚E,4 _IdVŘ"#ZQ8F Z1#!sT$31v~Oôla*ǒe7rTd5Lԑ :nkv:wڷx]{tq*HM;&%{wmn$X3%l%j@$RvdBA?+Bv.Nv0sG0)><`9Ǩ w.jޕ"fdyhONOOD>uH:P)k{TR`=Gww3#9mgݐ kbEr^CuϞ=n[.D40CGubNC;_*xmꏊ8 )Q8CwΧW8 )%Ukb?}PM]aƔSJű#bbn6-b3@E"ono)?{W=;Bn7[cϖ7HBߛڰiD_z[}^S%S e;e⒥@ugDo߽}1WhW`A!bٻ|q~vps{yC?-1gIy6 I5wPp)4Ɣ0 q}w`:g?mRׇnfXJeuIT3!s# ǜM gR {DĤ(E Ձӽ{3xO)GTTGV""EڶIE|]V3TUm"T_J9K`3MsQq*%"А\LHfF隮k'ww[5mbb|u窺eSRbB]a&Zx7.9=!Pbk[&jRaf@t9x)GZe .E-sjDj&8i9N1ZIh'漌R}ӳy(*͊pOoY A7_ /d9gAUBrڠ]pV;CrELqq̑ג*f'"i٘Z)\d"v>L8cb€}?)LDSL߾xX.6h>kzP Ր: #fGO?x0zRyn=(krn[,u.W}jkuk `P @@mx~{b9]^_d6TEl p.NOoWcyJ>U#0q_cV;REH*rvq<)H̔KT\ ;47xfB1Z)朆!MӰ7i}J)4BK 2\:c<> 6ce}w}o6NciY)r %Ǿ YL=!dvͯ~8Ŷq..ATKIw{qDőZr,HhZ\.ݻwnVED%qMq}?8M%NC]z\,昳̫;04PѣY4qA"Vu"u;Z`%㞙#$(iGhz&sUͩ䜫) cQ3@dH%%"3=fV?ǝdeR) .O!o_c;6.486)Q5AHT'u)Q5੺+jqS5Ԧ8:vm:*UE/ IDAT+ !I`h%x杓 m;cds "f:1x{JN&R3OJ)ðwu]7_;Rպm0wy6nȩ7[?I1W)E}|>'BlF:PIW߽ۿxwΗ">p~w\nلL\!f,`JѾ?C68NjwMϪL43><|O?ۗ1AILxXۅ*l7VhHUdkdz^L/~SNZ\s)&fW3+55z____0PQ"4sѽ͂_+Soj0i*ږV7wݰvƘsIʘKRIa#k2š$oiIUQhWUsl0$@S!dXJ)HE=VR?j119`TnL5S2tj&V*,2fDL:4 LF9G?1ӷS]#pEdc~s> jU7X1ܳk ԊxôؘA}5i3Hu%QwmF~jUa'3RFTUA9BUФxۨ%{$.E4/` .4#0+ђ'y`WP4 )7MP2զ}TTJqLm7[|&jچiyr*R<'|wN,އV݋J$NW)L9Cι|n*V2իB`!f*Y^x Dxv~u]\mp1?Q~Ij^t\fl8'PiwR!i =GżsUEgvn]wMufOCgڶa]8 00 Ϟ>].T]&>]J1ö UPYAwDx@`ݻ㈕LЄ5KAB2bjC\9FDD4Ejd__]f@v佟ujH)"y],2gSQhֳiJ%4Mh'EdЕSJN3@1P0PY?ݴLYD"&3R5NhjBS+%n\28д22e4Y2ŘYp6CIn6bZ]wR2j=̾熬Rј9Zb HdȀc,%$!0FrɄ{!~]HvzVs\*y@G= Tr8"q{f!xc!6덂5M{rv{~aJpU|WB50$9m׿W $@f5Z LsIh&=~`lՃ+$tܴBefI,HfoM˰ۊ"OPq=PMٓk򄼟R)݌Z%L"girq+錭#Q7ki y)o׷/j@|]],O/RRTsZ,flrq'gxuf۟ D5)1LُdWifg,5Jݍh$HbFzth@Hn⒙=p\oLDD=ǖ}X_4!FOUC :un-*J4C]ׄ 9'JfiP#3zC~^۶/_~{y : o*zBZX [$LG1w{RI_\5eo], LHXz:3d087]u02@d8| 5R΢ޫ0 utY<]=H`/Ooon9Y] ý땈XL C!Lp.*R9 U20傜K%dOOW9\J+oΊ?RtNpV!}F$ k)NY,VU iUͅ߼}E84p:i®M LYK48H ͱF4iL1}?(r}ΝpBT0Kj 2ƈ0$}նԵ|nLz:u4 R5 `i/99ټyi^šJ)hQRjz);Ruy4wy"j)ns%[iS\I P8gfQc>.e'5U)F=zͫ-Q a'QT'E>4,o$Rw~@8k^:! cA)*.1vչwc.1]UҌ7M xp} RbJӇmYGîy'??\&1R8Œs엵bi)xD gڮ{Yͻw`?ٜ+lLSUfwmܵ퇏Gj Dλ뫿q(Zk !TmYTƱf#Bui̷ۃZ\+Vf&DY8 ij?{z~rf/VEXnwHt~~c/^^T)sI+5Y.% ۪B[,Vcu m{۾yE*53 58(*o/.q_vr0$1d0к!K!M34mS$Bd:[J.,SI"Cc)2-GD ')gnSjBsv>Y-q_^\T_#U)!Q9*!4NB(uZuP;}a-DB1-ΤwaWY|Nh-`Dtb߾PͥjMΩjVRCйQ9~މt4_Q5D?jQ?պ>WW aCQRbJbLdD1AU{"2OjXE2[YkŦ m 4·PȦy"`iJ 0dBۆ$eo5ztg|]bd@5,%N n:Y.7S=}=ŗo֫e٧?Z0YTÖCBdd(v^@sqdfnlܬmM1* dT$%gm]YXҔ]\X~C>]gMEo/>50WbH!Bz, tyu}" ,}5 ZchٴM_fHPi򀠘R%v]x<^_=8<"LӤ A5̓:).C.9sY{7_}ۏ{o](zR!7<~٧p>a1~xo6Ms 4c`kfeed?8U co]:kEV+kV7r)YU<JIEUɚ5o(V{jb6B[Dnɽn-7oM48R-Z+q,ˋ8;noG¶bF=.)d)U9k3(h6b>v֫uIywsE7 Zk,*+!0K_dX @Jwnw~hpZr&.EfޥW9vwAGowUEPZD6Ǐ~<ϣ瀻;\wﺇ;kyڔRrw !}uI)٦4r)C=u75%Uah` Y㜳8r5pĊP`I 6Ͼ}*ǸX,WMgJIY}&vnu`vu(ۛ.\g'F1k}w% \)%?ѓ KXYv[8}j9Mxq97Muv-(@k9DŽ޺¥̥il^,hyX9<sIEAPMIrQxXg<vk,Cc-HN u xYkg]%Īv;Q fY-wۛ0 7~7MEYw!PL1Mv}հ-[l㍷ƹ2Up\?ιv8`4C>LZATN|m s~_xzfsЧtQZP9}J1DXJ)(C`VGheZUp5:k{TŊeBQ%uh f*ugj;ߵO~Ϟs~9oWϿ/1>3eAE \2fmЏi[ˇ/w7ƬKtss3iI ,qmd-( ) hP &HSM3Q%#?WW~ߓCIyr{~ꋯ|;wntAƉJ"Ϻv1_x%:|WO&a*ʼXzŦ !=y޳gφ!x>ߏvvmL)541fcCDaǦiBH4}{m¢ p]lwMǔN T ʪ5 1>;;ofML)Ɛ.4NU_~+Z1uٵˮˮ+sb"}S 1& @MBayn{7W c-/)}|_2cwT}4Ս#鉙j64mɚYN|1)V*2!4hRy5ZT?1-9!{gZŘ D۫/^<}u?ywuT\Xp=LuŻ75 Z=owEs% YR M%)E@%(X[*q6**R T:H!DRfcM.:T.|`NVe;Pq%>fXMxu^ǝdCRH@q6 "q@4ֺ)3YkZ wai@%k T" CyqX.r ݜ3.޼S\.din{q}nf8|?~] %C_-4aАwh ]۬ӡ}8CIQ ޫ`Az;Tfcu3bejO"}GuB"Ks̀")gs&By|*\00 ɩ2JݫsRrG  (CԸ ?I؉w\R3иE'$ =3"}DeU%@(f4dЂb *9g/r.ݢCGVfˡ K:rE a.//rS%SJdS\9r_lL rg!442g\ۄwO.#3Zb/q<|vv/>Ƌ3:EYӟ|W_=7\gwGD9kGnFƶmkHsU_ߣL)_?|qfu6XD >qPK*B^|{?.4ơ`9(ιsin^b)<ŴS:6O>9Y.>NR%HO}w;emgeywo^ SSQ*aJ?=;s)(v64C/.r*iGIL>W~6B B$*؄`/DTxLPr<~0/evK"V[+*X\Tϭ}ػVqr*`i3g<ŒjukK)m{ >V:~SZpGC{&]UY0n_۸X(t^NqC݇~{&cUd`*~ߧ$/3)ME ׶YhPi6׈f6rK KdQ 'fZVB"*1qe"1^R S&"8kXslӊh 2C(xmAk$pISsgT$^@|T[ӣo7ܭXf8M 1 ϨzAW*XǜHnA̢5pfkcY_7DއJV=?ΖڐU~+ )gk9 i2@77o2֐B@)% *"Au)xHE9d9 ,1M|5>DY眅c V__m5u)B(bTs,) IDAT٣go./_]^HP )˫,'M^yT) rIɛopֽy`s)8n.w&+CEf13䔊p7A fS)zڪje_ʋoI[.o]I0R2!zg U-HΜrb j6OQọ'OӋ϶ۛ{{6MO6˵!wׯ^ > Y޾}ͷhrq+\ckc~i*b-X ϧ ޮWKD0K?Fв?|U\Ӭh)g9qLbVEBΑ\Și[yc,).4M)!kArq2quu"UTJ E TBc318N@Ak(())F.LqBYe&GHV5tƬ DdE(qOc.IPCׯ†l9;;7MXTX sqe)9փ̅ cm۶98ŔbeKR!DUA Zkl5+YQ(9k z.+魩f33ywjԖ* ;g$dЂ!!cIDs, Dt8sxY??AMؙ.smTIn&3KcHuJ*YUfAU "Uc ȅs)Z3u@m1>ݜ^]m 43pa2q4UEcU]/ɉ;s$47vSq)Y4.д\mp,x [c)Ӱ/󟚮5* oc8֓K;['D8# 缪ABNi"C%ƒs.Bc,%m{{WBM9ظK^Eg./40,26<:?U\R"ZNhhDh{{xٽsclJzh5:g~*3+Ds..Um:B) a_9XҨbNr*47,C6AJBDDZ ™ na3{62U$#H4lBeao8R m] 9Ux"r!55q6!B.ty8w8yg9R"h X,Bh-+_nR d E,E.0WO>w}vRyؚ= Π箵n#gi*zrn'P 4s;=qc~ORѯ*).ѣwDg?0n t}qcFm8 }࣏N"X,w8!bۄb%q%9 i6?fO8U!4]\rTJb!csRL}?,׷" *"[k *U g),)%[K\qiJYnnvд{S\ι]XTY=zQ*TcqmS;qRuXH7_a6u"C*>/}[noS#_|C8"[9#N,ʐPB4΁ 9> ,kG?aןI)2좙`IEɢE"8,:+_h\NDۜG\4dfĐHI)DTqP;Dcs ϡd͝u" ՚WEWe]/W1R1:Jaa1RJ-Tij"ܶ&4ss\1ƂQ%Cs Wj,U"#h XDYEXĘY?3 +>X1UX&$RmhܛwRa,xg_}u}}ť5ٔ4M$kmJ*0T߀wiښ{I.%NEYDyNYQ ReU4ul],DѣGi7|^lN[AMU0UEC D YaX⠒Q]BLS͐,`)5Š`k D,,1Fan^udf¶kX ֢ 4vbq[sx6g.|շrY,WKUDj1L?|bN_~;r26]Wں*3ˢ1Ԅqv^]50W4ǘBSh뫡pqxw}'ͽUe:ܖ<(K4 SaP?Ni 2*P(P)9r隆Ȩ0ۛ]a-EPCpmkr\A򌕭xap軕xA4XR\mGs )]w^ɹi엟R23#ь…3fT.Ks{Cۦ["aBٳ}ҸpyuONO7p7z#Xt~tUBg(#3޽ aIydQ~c.ǜO6mpv7(^^,ng`d )yȘyGD42cLZ֣ض 8ss\~u[W ywd qI9s1֪xSE1:!e!׵!x CM!ʀPW:ZKbqqTթcl= D^ uR&DKsBqdASӶ޹b}޹~;ê^t]vETS/3{P8ڦiP>K@'ąYx,aX}s */n?VUKN{crΉ33z3IS2E @UGc֫eh T}-ɘ$a.%gg _]]ֺJ#*9S 3M6u ZWΙk:;9YJi wf}(YgܩVi03+*hU !BB}iy[P 8S q@5RIAA| ZL.7)NSР:o!,\8^`p,%rlP6Gr*ǽ)"a+ ^5@^zUÛJ!ϹG2f*HE2֑qhu?WD\D(a΅E\ `N4Tmmm)pXK`, @I< ˶Äh>#~楈}ι%f$4d,'fN)TEUH18IIr{-s>#'K3ow\u5sֹlPn[@FrEbjC&(e uWg X.}*TǴnQ%$BKwƘKcE2 1e4$"E"trV)k@,Jsf^sTsf5X \pȑ  xYBS))8NŔ{,d0) *,A\T&|ػX4͏e˷#c=df=35TVZ[%ĉ~x-c-?L͡2qПMyR!rΕp]>훦-GSΙ3u"FJ]p֙*1#+լ]onBUwZn)%R|훛mATaa9 R۶zED )Y)t  Š *`JxHOM1m-Qp>NǏÇixE9QqƘR$C1ƾyU$cT5ž\6Of1ґ~oo!mf2n._:s|X桪n֛wTYw8 Cw)*К /TRu+cPEq'qޚ2_:g!~9ΪJ0~?uVj4Nf)^ArN!Y;+r^)-Ȫt,~1yK30o]F?_.Ke~S >|0 ZBމp7 d8 POiZe&ž9ʇemI #*XcȘrʴ d- /2 㣶m0Xc%tH]-UEu3?>yxr_"󸛆8o+ 0qTv ֐ue]6 IDAT*c$4Hh]CsNHub:2|^%,a~Ǯ4А۹Aq/#kLfDVLK%sb)s6lBZLX<^H"BE Y*_@B6Y2:Ĕ9K%(5W:ŪK5)Tue)iI9a֔}JɀђQfPDqҞ539e`!kRJ *A";yeR8tt;$gIqһm_TpvlyGcJSql˯>u T_^\dHIvvð'gmUHPd:+@/j Q!8 Tu|lyj*ޘd Ii4=}D9 TiFiqϬ5lڣ11cΉuАlSx/ Hw"૜#GrSA )%Hś7qޅb›+|>vzYWu=9 vwg''G'~y('#sr9(!>ni~|V76l9뛗/7BƱR("[ Y :SR1%)["J3+cJwiqζm㜷8lgE%@ጤ˭[s1㬤%Y| _.BUZ@ƒuU%)l)BK! Bdwઓqz;?\PIXUTSFo*ճ/bܻ!X\(""s8F@CIb}}}ZFQn{>?QCȘv64b)NM"!80VDETBR UAg)iTDD9%dcйYUաsquXJ~(`VC;"|0dRba)s YXSU!8M% \DZ\…)B;ԑABADk4NH\q9ݚ|U=z&{n'$"y9X.-2VDKSA0MͼgN"JhR-ա7k}b!sVvqtrvf"&|>fEwsӳ {Gm)Ӕ X }m&# KR\Pjj65?'Qc23hA,"_z{sZD%csoN" 3Ic?1}|1 HzJ0v73w">d HVQ;+ciz9꺞u_[TW?x~>yO?~/t32vx ͦ^.W\_zE !A kpl1ֈ9!O.߼'T~-(yA4!tzv ]dvr?T;f)Ҝ""CX˙cL%NS"]>HL85%`}׉HSՄ9s9=nj֚M"LDUU9g0U%FSNޅ|SJ9RyNl.[eN!=$r*#QƖlXS)~LX֘b91zf0Cdh˔ą@]7 'д@!lqZVGggQ3}G:w]2n/<~ˋkk8ĔnU-E4ڔX7~~ UXAY2O||zyUSrrb,sR t@bAȚ6Ki?:;~s) 弙877HڶB(R +3;떋6um-AI*Y1fJyz-h$\,ᣇ ͬ-WC}ojի/WG/]Ϸw˫Wuq_*)gDt M9ǜUɎD9}!smjcq)SY +RjLn/VHh`F: B9)Q$+sSggg0{[04XNIj*jX]@!c199g^,.84f>m7>-+7e * (Tnl]78v]'cלapKI*3CH9s >,ڔsFƲD(2Ҏ#)WuQ,aoPMSoDrU+f(adA!s>DV $ejC_2SY>SL:Gh`RqX<ĪI0R,$2aiv4lXBeeB) \z(Bm-4ź uS]`Ƌ~L`_,/:c}`r۶nVq$p1a7˿7XKiM=|zKb)LSRrA ܭobw͋/> awq_ewB J} qQ) "Qњ-BGeՂf8n}WԬՋ/ƾ0]]Ttζm}=ܽ{7~eۚѥ4&YEA/eJn95uzjֹ'mȜu0&Xy8412+D}~[ۗ__\2Ef({"N(qͪu4M''M]o֛vcsh85_-Иi1t[DhX C  C0hYDQ$8',_zgWWb{UC@59)EQ;cl>ۉ!$Z(T Ɯ; ΅3I|5J)kvM9byXC*s&fvCb6_V("dZ.SQEvRUl>>PbmsU]߁ÊVqIfSS9W) N @I;G/* A }? /uPLiu|:kEk$|wi憐~Z{4?nP8sZ4=ݧ￷::}[)&x.1%QENCI+m-jYoLT8Xsf?l2A0p.{u%C==>]7+Qf͙TYĮSzuE*)Y-ς/?g~[YUQXli (WaZFUB竔Mئv SArΘ4T9C̿z;/=|t[*D%@ jr1S9/faʨa8?(dղn5 ]'6mOΖ̒8e-H^=V9?}ȭmێ>8wιHMS䜈PX5, * .ɫveV&qe2E4!#S$B JUha'̒Ӄ{o hDgӰ)&K@]CGOP|W5LJ `)9@Xl;{OrݖSw" vSWWƘ3hQxQWɻvw=8R5 "3gj"ac ^_]1sݴ, O4QvΜ dBT)X@X7fN)KxIDGʧSny;RVSJ,lham^ K:nݪJOXX59D cbJt(R~',a$Pc8њ u*c)nѐTr2blLɲ7qC)NӔ9wݞ%1Rg" lefZ[|{\^N<}ף+[O>?է'|QSni~4{ ;jX=h1nq#gw]!83M $`q:o2m?~_]^]|y*e&D'a]_}ŋ_ 'c @EH^["k 3ǔa|34 )qo*8Kϒ]-WkB몮+g[kpJ QCڮtR@ knǘ3K[-9$yom܌9ja͛jmP}譗7:N= If9#QsՑi",LUU!)2lwpu'xo=|ŋ?/aȒ9wbJ)庪s~02NӔ*XTaFd-`]ק1"{Z9 Zh2CEktCXTօP`-D%C&2R~6ki ~C'3_-Y^_2Yj9{PdPt·` g6(j9ֶX,**߱^^Xod\4M9)AL‡Tz:jMhj9k`>c-񡜓xキ"9ǂ {뜨o= (ԊHyDTbK)0xLU PzhJQK5I,*ќb9fADAD2xl# 0)64h >]GnU {BV kcLSZ4z.ʴ/r9@ca2AwqrGĔc m@uʘYȘӧ|k7iLϜ1ޅzM;>9rZ:=M識٫Df=qӧrś~SS:3<G!QdH{oã| ADY1|[?H@~λneL544/bP[%r:;Z,VM]sz]˶Yo^_[̗ՋWn` 21!̬JA텥{XȠUNZ?}y]1"؜bN1Srcꇁ~' _>Uvgsw;R*-BO4mSWŬ";Gy\-}u;C7WϟH6;؂Մo.KD毞:؜You9g9xO~/_ן^_d(Ӂjr.cJR#,ZD4B9>:%C?5H4=o>vNqR9eCȏE!8gC卷i;BAѐUe4㔘%g&c' :n_6nZF0Ģ2Tr8 in7lΛ֖&E3TNĀ 1%8f:CV.rWwMiag[MT2*vUU9u`Zs~&PǨ!B LJ[S "FnQ׀Mq1㛞9 l|wDžOVqcLIMgc,acL߀ks#B·\f݉mw2[\0œXXIUaDڙi4(Bi;;Øa{ue;?Z[skauVuqcz;L1FH 0ʤi*DZsI58|G?Hd4<{աGeIq;{?tTDO?~kG{7?j5[w4mHM]O1\_UYU۶n1֍)#~뫫\**؃Ȁ"8M/um 1d OGۼ9Y|GɰP̄qjf{}ꭇOn;v}o|E!,On}}U62Cח)bC=-U0vl $ҼaHY:Ƒ..8Mq.)x_Օ1&T!TUJSӧOo^N.<&A/]^??&L,`"}f.Tqd\fdU!l{WGGQXEA o}[.vo^~-QK!!Z-Gǫy[44B&̌3 J |w~9˿]$EM99gJ3y(&3TU4D@!T!T,SRc3S:td92֖2;A]tvM7 +( ҂ g둖9iιʨ/WW8"lжu>m:UbϞ?n7.?q(I :.@_e"co6z;(u]/zmsZy\ѸbdEkl<:9O7n?{1o8P5~|&ed\-W)vKjfi{Krnœæ_߿8::>=^58I|=٬]LpF@SN-_m@ IDAT0'#,ITSsN=n..|?[+"L*$cs6d,܇WL1BPHLEXX|{"s 8BRZV'^L3Kj_7* nvcqF"hfNHqK R81>@""Z⎀ XOκߢ4-h8g-2jDk6nggRvY$*՛r>M) fv/g"L1sk*1IQQ4ƒR>ˉչo,sat-(Yxo7dsƒ&=8;uu])y E)U⒖M]*lirNؒ(%BkR-  "㡪"*)pzQ<%߄8޾sBu=NC :\@ RΑ1šR,O ȒcILUݸ>Oi,ȩv`M7},8G&اq},V3sJ@8"8kIDs!b)9d)+YC-; BᾩH[7ZUMkvλn~_瘆aj%Y\%眬CfTU'}s|SO?>B*^">`B‹)f;=]rZBTsL{Ucs퟿z#?x[oNONa|xO߬W7nWUqEjlIqP81ƪ G2{thiۦ鄻nYo櫳fmgW3)6u3Ͽ^]^|jPc-)(M.WʚiahP޻r77+**UqOsD# HxMZ_#!Y@2m1~]OØB* <n+Q cI`B7sScǾn뻘ujbE]ڧ>Ǭ*:޿w,rsŲR^yC4())YP;g3>xkKQȨYo&43tQC`!+" "0)y-4!Qð[UsNbf!A9DqZu[U5 w1Fi8 p/1CEs:`/l 4_!19g|*@(B+Uwʹdrܬg笝ͭu"2MCJOUL?\ACRAǻ;-@Wł//X]/˛5ё)Sc @Q 2ewXr8k<0A3#ZC4oi$JiH;Q1AxJ)ncC]˷!Cc4W/0D.-uو1}-p-#E(H9~w6'[ k|ޚsP~__\x[Ukfut}Jӫu]w7✙T-gm'O/n{FE rّ!4sZo%0kUyNj-Ǿo`N:a,}׷fS׋|>7n}~uJYҔ}GxgƘ9{o l۶> 0qZYUmv|~su6}~Ulr$8K]7wn:rqZ5aopH`H,4cPӇF+ K"Ʊ ȁ;fuXKyUߍgaд#d*+͍^T FVMCtsOyHyH9NCHMzciLTSTlƆBo< !rE9ggH@v'NLXkY hi1iz՗?kc*s1tݕ< )!mGisgML8 #øܼJ$$.󶢯T7w~(1+e'R,o6XPl)ǔR*!iC81 4ONN}]ǡ0;N^NHs-=uJDd-LTڦӴíX2(zWMӎh*w!Z.*az )恄ǘ"!(˸' &$瘄Ǿ[~^YtnQd}gE۶4uF-}W!m֒Eɰ9ȁ̆;Ģ_ݻwc޻aͶ[Ez&־a8]|:cl1Vs du2lgϞ?|x {"̚GlrP MΊ ڔ$b yo'Z"4g_m۪q"gU[@?սl5VUS]Jp0MqRLdPW"P5US]㄀,իfX.h1$S>8'ݎ3)כZ)uM*9&B'|GǏAQT)8F%kˑԏq !n3ED VDrJERєsZWv" ޸:P]U~7Gǩ31ؿ|'?E=GYP5i&뻮1}ew7U*1NpҔdxeq{ڶvGN4AUnSX" !T'')Ŝ!isN1u<(l *"@E2V E?+2|A꒱&`U3 \h* uqMS U5_,]R!Ad%@Ӕ),7w[ߝC,_dVJ1Ƭ-Oއժmgfw2{PsQ8b!=Ww$Òt[r^Uyј79DXmYkHV@ ak5GIM!#֡6,p]b2 y,sȑ 2aUz)"8N'Z!Dofy}DpmwJ9ɥr{2w?~ng?OBCc֬}77usn:>:xs~qyr:VRA|qS1}/Pկ_wի޹˿MRDba ?Ȉj? 9U@Pk7ޡKSlihj!<% pX|_~untpuq9~flTZWgb1Ŧo=z1%$#4~0Lq<ŊXrL1c4=RF2w] UrXňw5k!e%Tvm;;h"U+sijCv}nvIS]Z 0MS⦞7U'_X@1UnvdcH}iTuʓ 1cJ3,Gyq1gRʻuv-xL^Ra~sc)I8vj ~/{/TZPPEQ|p[wy%HEEЇ$Ōy"!qz.K^.c_}Y֫uArKJNVBpXɒLfw 1t977o-5;̕O)Mɽ?,j\iҁ3ΗWd'D۳xA_(u5bdjQKK@]wuqlNƾW侯(t}}} *dUNObdn&e]Buu?/5(veu2~ҶDze*j=:ko*7L!Er]i4LW}l?zOSN)%W9fm'L瘺J0V c鐑Q@lV>|+S 5;? Y(e,lU,,Sm~pkw=8朸sc g|`Q[O?_=mw9` hmZRJi@1 9n" *=Z0,T!舺'mM*^SfNP(I3_/N61 c9~1 g =q8OOsD}j7Vn~pخs[XkT%Th2\\\0 3;Tr]yU"~ kfZoU3y|ُnܷ9E2#c,,a/O;ɣvCwd鬋c4DsTe- 0N>w׏/\K]1X6V+$|ɽ{嫯ogO{xr* 瘒)jf2{a{ϞYka1,{QޏxPm|=fuyu~כK `Njv飩!}>'O/nnޝ_ 1օEEPk#|HSSA+fUW[ DvNSEHkc}X3~7Ϫ%k !8C%6s] 5@!< HO$Gm>jɇOazCHC0F'̀J ybR0mS0f؍þq>9#24#XcL&րݭ}罭8~ $K^UBc?لz[YzA;ݮ2ڿK/{s0vݾi|@2$f.A&$0)OM4V!4>b (aTMS޳_yC~+by 圑5H};iONNөð٬ a"-;졔C,!CB,#ͪ,O|;7$,)ecL /'Я:l˛3\>)D ,4;4mwf؏ZL't<˘ՍU+iY_^Xe^LlRնcnlR{蓏?A?Sr|*LD*3)N0sd-f͙W7N?~_~Ťcdͅpcgi:iwuƱ|0 zD5[n:վ~W֫0aijb* rsFDPvW?E~g",hـl8&[b煮Ǻn7O* <6)^ǏW/}?߼e:{/GMb(&Ӕ^ϫ=}wwCʹvt2KIWuj.Ot64XjmmW l q> r˜T sR@k$8|<Ƹ& U<~ۘ d昢`V@r8޽ o (кrB[Y?{r}N'Oo_6)fE~Yo(XUSOOV߾8 nH8AXYMEڦnƘ3KLAXܚƐTTD*e2V!uo YAmγE]6)mlDf2dڦLY/ߝ9;WJ6搁= Ԑ)HsK>Ɣr"f ADDJgxxYcV3SHxP7h2c0\__18(D<켫o(MӔqIm;M'1G$%"t*YkA[=LZ婦>6U%*E٫WYU1(vLK@YxUM!'fenqgAck򵳤חW8mru lJCUR$l+^yMBշ?ԧI!fZ+7Ϟ=?ϊ<6`1Ɩx,Ĕwӧ,&s&(˜T1 WOޟ-&Iw3^_^’;9><=}X*o<'JU<Z􊀦X5qR~syUns=ڶr]_]^?:ZyZW&Z;innnnfiP>h9sT$eVP+DhC2zl'\LU =q`кꜫE fP)KvMĔRi|ѦɭsVYPVD-N 0^_nn=)\Tl.|>DHT~⭱4NsB,3&®) Y"`f)rTL)zkU8Ǩ! 1easZf%shzt7KgK9qU R9@ K@Us@!j:\5u=q]uѲY%HD-NY0Xk_X2I1;'KzV~DB_Ka @'tNl xuyuqvX1EΎ'0}DY-B4o82T'߿{{+ȕw/O_}eu]OmUU%YY8g]7UM9a/y6: 0j`@Ysqzj-(-`11YKN#(:A M]Ɛ1W5 j?Yۿ;o/u뛯?+Vޏg9's캒c ^n1˲0 |2Aī1$T'ޏl{?{z/aM۲߾y,#ƫwOfSzq}uoCSʷE\0/D$}woWyRD"A'm=iG9g<f.޽\lf~`Bd|lޏ!{ y۔_Wo=+?z`2i{r9,ݎ'(X7~IKNqӦUDUbqw8zK"3amߝw޴usYRSJ1I,0F%"c}UUy_r2v6w]J;rԕ8fs;jo8_}z5u@sT)4߾z^G? ]O_xv}Va=*wcJ)2,7oԜ3l,V"ZD"Kc ݾ/{_䪝|| NAtG@<+!ChqX,Ue"X$$kUUWMLlXvŴu1:"wCbd߹4Μ)"ڃr?~?r1F)JPAXrzT`0ƢVAL9ILymqƟ,C[ ra nw;klN*Y!8JL)|edaDz QCGBQ-lFFC6bD@4ę ӶH@*PQ 6 ʒFsVec :A3itsjb+b3ǔԻ KTgD0*Zץt>Q۶HXeu$`bNd G|>fޮwv$/=n՛ӟ'C7pJ E^`+"̌EA&uJtudw;~3+,2Ƥm|hmMMkb9CC`V!Va0c )3Z棊zޫ!^'t:_.1f%#fWW`iAg5Y2p 2C|ƐEC c9>9aFl3wv64uqw|usvf:99yͻӳW2PUo5Uۧo~/Z^YTj є-Pjb=\ {&FU5<|޾;RG\Ս~_| f˟r8>B%3G|rvVA&mTוѣGB ay4n;r%Kda{ TzpdX.KCrا!t~k?vrX FD,7+f2dLzgQ8YgQ<1r0-fz|4cX9,R gvd8PW(sVV*lӴM9! 1xc1Bd1vf==08@)N) ,-qlA#:ks*Ri9[jCT-+/ ̙"A|I!@d4||nSr8 X޻Br!*hUU~ 2tqTY1P!,E@<3Dr!*U˂~)l (w+DD14imۺ~ަ1!un*_vY4DeI l}e$֖$R@jJANT^㽿3]ɸBE` Rl6-]yY4̦3*)%2Xg(0q8\ a*:g_gǏ6ͤ MT3ʬYE1Eo^4FBe"R0c?8~:D}eΨhN~g=y:qYrQqb9$)5ՍW۵nv;_U'7/Q4,ɋᣇ% >zŋa2k뼖ToUlu;_DP"5 aOgGo^{ .$gnHd2Ml>mf~1/I[Wn1}ȐsZKpN :WUμy2'AP-Cu=r r椢i:a`XfscfAhsJ8BLΌ1Z;=y6[_>~KI آ+n,@=jI· !)Kw{cm;ϿiIrN>aGC3"6M4Dg̤m)joDDk?OrNP6KKHD+kH5h V RGKcd|18G]nV+@h9 Z4n3Ɛ ɘQDd6Uc]7Hj@dHclUr~ߕQ]P_))[!kX(P!9C֔GG/jZv{RD펳ޡĀ@ [Yc(}[cn-93_E/rxb a sU (,Ld'y3>wӶqخ>}ӧfss DiꊐH:SDTxKXfbv4_N&up-#ceNUdXr`NF/G4, ~rt6u^xq}ug̐r@|QAy̐r\7M;^^^"ГGԛ~Q7* ?|s@Ys9s㓓'O~+$1>y^x=!1`sVD1/҂k%_uLˣv4wgU?)TOg=z;~{Ջ^pɢmlߨ٪JKs|U;!w]U,E+ga1Ư'O?w?+Ohtf2ulq,),3p'm;UfΈVXk ǖI.x}:8w-N 2@T96u:]qJ*ps\6N0 n*]'A@SN1ƶfR(XD "B֨0c)/5J:19"¡'@5Tȹ+̹GL @9WLKae0eUSJ@VYk@L593sG&)j,|n\_]_p-ZkE9/W5cL*E~v| +Ŝ'LH{_!#"1E*k, ;ܮ0X1{ bH8·h1h\̼ZoޝsZ,4cwyUUwUAucS梨C"@.crȀp+nE44cT7-/tAȘϟ!h ԔacʍwUNwחW|ل1B)F1mR , *`A@%}Xc u}l]js55 TnBD$)~nZqY2Mݖi>vޥ83iڪ#j_kw׎hYXͦG!Diw}yS ]ok_-bY$"n[f㴮b hTCiI;/؍㻋sTlQjG\_;]\~Ջ?zxfu髛Nj:G7~]*|S7kM 9WbUUUb攢vG `)<{9?ia_<Zog50"jLUMC $dZ4Ɩ~b!"^]ߌcLZU{-ZKF0"+p0jussDc*C&,9sN:֭\ۇaDD4$@"c=z9 5nn9q|]BwP$rցfCߏhKDTTGb"3D0s96jSDD ʜ˽Ao* lUj( a4朝ƚnOY,O=v]'f[}'w7?[x9crʙ,1D*ob!䪪L9Ux6rxBJ j+"glFN(T[|~w+o*9N4*ZYyKߖʷoq\g⬣1*sV5.{'⋟ 0ơLJQTֻj rN GJ)!6'㠕vm|XcSJ9&tNUEK 8#dDz,E9˅DP pr6Wzoث|vyS OWU,"Hf"eP*@t2-?=قfnv1)iaTPX8s22z9/te|8I?D8ήfn:53 @UU1믾f !圇ql1q8kh:[kC? ah<:;7ë1}ۓ޻DS]\_v|N'vm֫~N1VWּ'yYon6BcC@dN'9B)YHDc2X#*d̀TɳgۿlC1dr6*bEMӴfRmY$^bK RfaCF$ *2>}h7UْQΚ,BRʻn Qia4[|enC4D(@CC scfNEep<4 RL@! 1 l19 Q]UmkՋ7o31XU18WYD )%9T͜r&B PaPM&bGy'1kضJ1m~?O..  $aacs6X-VDfv6N* c K0gU9T5wTTJftcg26mںmv>އ7obi\sfg(DҭwƳӃ9\NvS&o/faU\QbBP@9[:+L<* ^nl-cU9eV53BbU,r`Bp#1*b %}? us*0U3X3sQ@[*3_\G1K샵"u"uDԏ}ݶ>d+o8 :hj2͜ct6*XW{nuYN21Vu-YUdo!gtBx_Y be2|Gϟ)j]Wm>&[*M'mۜ_^|} Wox|w~/O߾92g?{ Zf$].ߋ!4*@1Lnv6NZ\D4]UC! C1& JHH"9! 1kINw!DlU7zvv/tƱ׫ms'GջoU*ʴmw^qhy>{[,0oO~֒əm'm;t]'"UvJ)1R9U5[BZVa)JT! !h- .BK ;9C;c9&~՟_^?*UDS]SX]_MRЇ!_9_f'/ЈaK!5伯q?V|"HE%\$9 Sr C䫪njBJqYDH)VM l6stʒcXT"XgDG՛<[vey9v>dTX$KXD<eM?C=vRX$%G5hgv{zOD1stA?bz7-`fc4123ՕlHiV Vsew0V[e[Rnzl;L ""!"9ܨZcD-qJsMydu8תBU0x|))!5.Ċ=غE}9ݻ2hjE !X2 !u΋j5Q31! r3}|8겄ɻwn`5RwVR\wM&QHTbYzA|^TRE$gs)%nk۶gD!?Q"|.&v\a8Df5;==iYr}?s|RRwmܹ7g^)ӛWy닷UHѦ=}5fu)AߏWWWݿ{~ϟ._:iZERjY?WWD\D BUԜ pkBb&c7_> y$ϵ["Eއ/YUd$Ki"bDv&Ʀ#E}uvWzw% !FfNinRRaN4yj&R EI㐧~rfjEa9-G ח9&C"Z.@J\TOb9!"-9]pE2xg6e)ð4[fb\o#B.Rv4A䈈w\a#Ru:CS֑wLRnbl&)\s\_|)SlʴROoWK!"d(5>6`R~ӌXDq:GvZŦ}=u)-* ԣ1ssgfD !&4!!"QJ.fQ+#б+R*˾mc۶T"r4N>|*V͟!N14uY07(}vII{U5a!x Y|sttm4 1$RKP/dHaEb3#r3ǝ<1#47W.!Ҳ)A"@0L* R|F{UqHǏ=wkJw}0;N4)xTiM3[,W04qmY4C3p6$rlf@|33`4%0"1" )gLWD'v /3f>ϼu|Iav&p~[|J{>|OTJbxsvVLZ0_~nכݽۏM?*vhڵjuԵ~6sEuqة)+A Fشq71:z3}zr4Dɣ(s)Eot΅F{5+e7ndޛ\(y2>VD t"6%mZS],jjV 1R "VrSMp-X%xxj4M6$Mb{&Ft5b҄9۷NWbssfɥAK8ADmQuSqu,AD h4ٖG BFilb8x-=;}lbM:Z_\E#fӔswM 4<0Nz"Gw!M% j'o5"Vl6˹li9X'oI\D9*7\/Ts''Ǧ:CGaҔJ6ogLsa&"ƂȾa:ss4\eR+jb@FS"J>4#P0%cĐ pl[eHY޾s_ۗ+?_~Yg޻Y۩6zf6 zW7! TOH!19MxovJlBlY.pCr..i5_2cr4];;b ~O TsH0}<0f0C#3nwHM$Bn)/\x g_߻sGwtz}SѬWxY|W[wq^|>_,oߞUc3pZ>BǢ"8:j0>?8.~;VJ4}t*IUwD2$1#&Fct<7ia4t]7GߣcHE5R6q~vWg>ū7ϗ^!!1X>ӓ{m3[ohZ0ILT 91G?'~a>VNȰ:{Ih(6YS.E-Ө/DԸ~Pr;뷣\$Ih@. Qq0rN׻)'"B)e9qբ?3U  .[&sM:R,T\.J)\ӵD!8&̹VK_Xw\JU=;N"DTAw9_07]>>>9\JFPs6DS΢C4BDHRpJe݉YzUMdy 4N/6c `RJUKQ}iKUE23;blTu)82vl69>Sjuzrt|œϙ;+ [ߺuzuuCTYCCn,d.^g;{Y4㎨)\͠=h rj㧯Th~|խ7NOO>c?ߤU UG` DD@nֺT{E\){63"ѝ&FfAEIɪr(n/) Y;#1LyRʂhF}l8|>n޵v< Ǐmv]mZX,p.~Gy7[-GyAx1@$<{%w-i?b?/M^5R > cwv0`h"9{ .xcL)k)g$M\oǷa m; Q3dU@S.{ѣaL`ufGDfB4DTDjxo>ʊ"4 ÐsahDuPf|$9/?y_^o@ ĴiV+<~O>~}}w˥f)v=0ʃw}|>_7g *Oo޼r>4]gM m4ȥzv7Ϯ6)464;vDڄfPEJ)MS6S1=i|tU+l1l68NUSoR10\ MZD8v>xhPJSiXjPRN,jiJ<DXiq wD0Mq?F\J^)ưہHqJhs.ٌJ. &ZXDԔٻz q5M FRdƔrAT V%pkKI)#[xy_5x u8">`r׻sֽ۫jޝ<8zN;{խzϵL8ZaWfkvz `g岢jޯgDdJYD@$ _߻cϤbEg/c.7I}ώn C{~B~bgowOaN1ټS٫7}4@坐Ϟb dٹRhU3S >}xaRRΥ:g4'&5-#dQ)0vY7;Wp(k)Yهb(@pʧO_ m׬n/GM舜2 ݰ˘ַo~>կ#.OY*TNy󵯼iqr1E~z{~ox|gO?UqtXUbaB(R9"~SX*"䐨2tmnz9O0c1aJ 9JTAIA3 LSfvmG+Pb>+ jSM4N9cZנnֵ)%Q睪T8Hb%9R77REjg8Yoggvϗ}vCGwdˋ˧_k'F;i7̖޽ޭr{7X7?}g-T8B.4eZ?r[`eܛBM?b㯼W~lOwW_\"f%;Sj)ZcLrVR 5M7M?;Md )d;[?zD.\f~hB ~d]sr|~;߿~(8"k@qbbӆbykZfh) `vLyh7Ǐߝ,W`}q$^~GE\e-zc9o~k7}u*1acJy>?_~qWCk6e۝:|wGt>s];!駟d3*DMtMI5rXcWS5DlX .TH(%suȂsNLWJ3͵(]͙R(:Є0k"T0c-Oֽ!rLZ\}l-4lV~կ};ha@F 8BAI"(ᢛqv7޻?޽u:wޭ޽}|''w߹mE\Dtżɲ^_%YIP"f`ԴmΩfZ7!Q׳2apNU%f&w}U392"T:G0ZEfpU0Cq'ͬv7UQ.``*jXUF<g2ƇG/">rֵ10bNi{D)i9{{7z8p$$"WQDbG {>)/:]_k|~{t[wyo#+fB7_ݣ#hTq9(&E v^_>8}@ .񉕤SȽ7O^ah'՛(U֧}6",wa/~uFATJF]#ʪZMӞ.=[])1Zx n}fKZvw/^zONKj4;Gfش*03O<}QtjLFXiz`BZ.Ͷv}ۣ׋[w6Φ m@tnsu}yq˿~HHURʛݎBۥn]C?;̮VsJWgI R|~rrmZ51|tr]ENK-=z_a8Z唼 Rj=~{k߸w47$uO}߽|Gd՝@M ޻qbffv&3iND$\2Tanqz.Ȯ!psvfM4䔑(M%\^\={ua mGx5b;vb4X&D#::|ǿH /$y,?9Yfno7MDkJTyu,[@]cY-}zBi{uy9SWp9N4n{NT{oFJQ3"*%]\짔q"|fO9wW.Cdժ~u3"T"r#!U?{.o|[U 4 w,ezut;iE7?l 9vmv?oȭc" !Rʨ%\-M2hŋe%V1M{P]12R@j\@drȓHNEIL ,6{SIELJ)HE{ X1~= KVQ R1`Fb@rsM]h'v+A Ȫ(\OHTyE$gS8xf JDnHĎh1oOo3$$< Ôpum^՜>Fl,޹_v{9W 5Z\JP ЧSqHvc8WmE @E Z[\m-D-6m!]ifW/^8:}:oMg޶wnAFs;O.?n;& 7%U8P=AC=o6iS>m> YEBUT؈;+Sw7b}}̵"[.Edʙ{gHRɛST\xf6c a?8JƶiǜIJ?g>9զ HEш\l"`* E,@)D?^|gG$ :篮/\~;}Ox6 =SFbC8ݻwޱ}j&*!Ι(i\iGGn4fQӿPONo۔D blvmϒ]냏î'g+r B&\Ryzo>,BDvy-( !-b(E4Iɪ-B)R{u L...Ҙ~*߫i2D`D[:9Z- a:ڮ/rغ7z\_^6LI))ӸSQL{H^}15ۧ bL1&~6uNVF޳gVSB1̽j&TRTݬs*j]D fgd^oK) EZOn5m< q)s)0N]>IQM/+"÷CKJ6޹BDߑDf&&61h]-~ ADܱGJ(:+dD|hЕ4zDLWWm悶Y_ПDwMRmpVdxϟ}jY_ntpāmn0#X<ZWK)kJ_~ۿ1{zsdQU@r7'bv޹IRE]p71"U #dpV.V0NӤy6[4Wl/&{ nbEs8( :vG EDvW{Vpg' J!-LS}/?4QA|Di>ys64Mboi̠F0DإiT@ mh\8:>q8j":"*\njnfc*uhV{λWh ımeGIn)M ZPgV`bM 0 "cZHS.!QRM7&G"*M_<}qX\JѦm5:Hد:(Scb LtBs% jHyؔ~̆iӪc٬/ro_<]qEo/^K , ``u j*T< @6103I)io*޹qR_ LTR9VU7U'{-b"s>VB\* \ՔH}Ճ[eۍ;BY  朓ZAmݽs7oކ˥{&B@1gIci~w]c E=Vf!^.M 4"NDi0rNRJUfM)qO34s,4t*x!DDID4z:yPOMfRW*d@HY&FB}@ozRc? R^MM9_z3L>,)Aʈ\7N4l#f'#Rh ߜq\~jiɿ3!( 0R DB@i:RL "7$ǪS2TQrNZ.1qvb 0>zٳg_ǿ׿ب9]RHRn܎$P@t fo} .$ |>wϞ?2~_}ۿ?xi튇¼ivXj\32j"K<Xrq\*hw?=m*FD*Hc?9Kff6ww~?Kk!wA"LY6o݅X͏ON|.x$1M}S C,25sIV}sB'  U h@޼9]]\(Ħa %8Wo^ɳ/N{fGM"ԉ@ΫHc)9C*R 2;AJ;o8͋7O>@Woߔܺs)OvʹX)JNChc;ER"8J4-10[&fB ժ]Uvxs6qՀDdᩇLԪ#>vNug(Rf$v&;hcf$Dd19Ni9!9OTJMdG`xutcU Jz=M4C?J)UYR14M`aip)`ݎ")Z-GsιPJQPՃWjW1Se$ /ܴm!6GKa09!4<뺋+Q9^8]iy0 q!!uD.<8O_Cѷ]c.fq$&rI%71?F>4]ivwNo_j)Urrs!D-%:Er:UFtn):Z'q4b"9e5UT>sO_r}- L0LxqJ"9SvYC14UtauzǎfV˳O>6RƔ/g>sĢZo߾S#ا]H RJ9g>K5w%""gTJ04N)L\qB@qy"5I5aw9dCLRg$&E18~ݻwvv6J=UZqprt2M27_WKO75ѐPd]7J>>5f]+R 4PfX I~ϧq9$MiQM2L=Ƀ9t0^V/gB#ɧ~H]LKJ4%eتY9prhj\ TWHDL;NFq>d,yMhﶠڳa>1W9XmD|}|@@$GD`Z]E):_?6uk35E+i^)TwPguvRJN fyPh1kumK̤SdS.wvs7pAwWHH޻ 8RJ*冣[5]E01Թ|PG.9hA ~KVjp1#_Sc 2mOX5ㄊ1>4)heϞaM4qK (>4\e.&̹A,vnvk0eDf{AUwbq}qBh)iClww_]iZBC14nG?QZI˹ؿ=56trzͦ1oBRJLR (ݺ0A@FQWWW9eپM f1Zm{B`rUS@$P;  Pvۭg>S8U~WDB0퇋˳7&~6DESJƵR0[{{NV'W4bcJ%aq uӴܥ<dD 33̎ jzA*]fȀ57~Z_$f5+a!rl-ILbHQoufR*LMAc6&yN_oMm7~~(YsWz."E k;RJWLZ֫OWwɈX0X}XY9D.]_ZSΦ%0P\J.sZ8:en{ iǀc*HrR*yL i;Ǡԛdǰ~"EG0Akn3loc͞-;35dLwU"Q$!lGGa7ᶺ-w[(R SnUL{̵r ­s9;o})壃ʻq蟿|I̞CDY|z~uY_}g:tdݝ5w_}Y*JY\UCCvN)ևG(|b4Mw}5wRJݰ)}G~y9+xCY\PaS"D5B"dSdtU $XI5c n>{ױ;9>qu6,f;wWa CD3kfl6[2b\(VSbU39HfAY PV|~xaM QU58Z1+~gPlNe}'p} A%f0WɌHa@Tɨڷ]U,8ly{,`9_\]7kDZ:nL.ș8t˗_ó XyϬcFmuŶ}l6 d6󇋅/,H>)I#&Ȃ+W;fg0mvBL' -3R]Wu]5`NYRN W)eH6Yh;\ ZͿbŽ@,%fSTLrH,=U:9Yf|TUpm |Ð$C6 cYj{d2*<^|'0 3}3Z1|rrX^|Qij9pXcfYs͈l;fWg\U9dg9@:@dDص MU|& U5 1fdISNyIi|~EH$'G2.lmwunKNo|;1ffgf++C?0;\ݻz8ܿw>?<<yk_淁]Cs^yppYc\ QO/ɼ_fMFPC3&$m(ҵ2TCD-]О*B7") }gO'^< )닧?~qzIǷn?}yzC=g$96uS>iVlˉJrF !>yCb *z8ks.<Ĕʽt4ADUlo $Ƙ*631 Gv@`@BHĮn "IR.DIԬ0`9oT\qص49H%O BUuT12*t~9MwUU!a0s(gDr}x'ucM|p'BSS"TsXWγ {B5s7aVq\uYՅUadb&N1vTǡ'^_C+Ռ1朇,R7Mg0+fiZR H%M;YAl/TEhMƲ*Nkڊ3 q1*嬪#HD)gvӫ˫$gUî Q+8| ڜK6ڣ29AsLl*9 m۔4rPsrVJzP~ryVKmBFcDB5"LqbJ윙1q*̹`fiGߋF`&4zƄThH《}$}ߝ1̎6\U7zgpvy :>wr@z@ElUV!͚:]Nrf'OO?buksL__^f?ݖݢj>~Ŭ>鋫?}ˋ};X,V}u?=w1>yo9_!vhdF1~`S#`I ]ʝOOV[x\yv>G2jvxXy řz~frc.AnϜK}MLԂ\U/.٬ sZ n6yqޕ!I.M@) @bꐰ 2Sǔs;纮O){ 8NJU p\ZBt<42D8N`0aw71e4"$6PDdv>'v!0`aҩ>}r>op_]ߎ]qZf@)g3s.pU0$%Ly9F09uZqdJ˴6ƈH׉T9YSNn4>{pPUbZ:~|~r}yٵ{I ׊\'?|)QPnE5*apߴ`߸ڳ$Ie) 9_S  K33)I, 2DV%gUq)eДƱk糃f3,)v[_Cm D! \nqǶ{5rH9чZﮎ@>|Z-V!x%d$<( h%xf;<޻oÇ}\_>^>ӡ_rE!wHn/}H)TǾb^9۷~4~vW?*}}=hS`fWǷ=~f㈚͚1WWmkU5{[GE| 4Bs"Dnv˫v1/SLPu3*9v w vCĢ&"2;0b9CSʱf34KPSbJXcR\S㩗զBtIRB!k{c&BUzTfvyvMS{p_M9n~qm۶K)_HfWHBeskr4~j[1QR)(U]$8 ~6+5O:Պ1Ƥ*XU嫌ףM4t3̲Cqyy)5ZXXnVf0tqr?=0jJ&w޾}V̎Nܻ;ã˳k#GT{ofL鋧O^3]6u56u vCjWz]΀\0dP,<)%$ 1D0:$rSPICOR-&8l`f6[>_#AaO:8^Lː96㜥K5יR/wL4qǢ s\NfPSXt:3Wng0]2EBBR1b^FDu#1u3ElTD0;*כ)@fRDR)RG=ՀXE3\׍wODmbg1lw,YSަ8%@04H4>~D\ut/zUi|o[d˚>1f6/˗YM!l>cǎyF9ٹJ4~'UOW:nϝ31uo~k_C'O 8 ìG]ׅP-Ó[!_s zŸsݥo%Uуŋ3Lk(7<|'qVK_Q_{Ua`Nl ̺A=:dSI~}l9zY.V8կ;|{:_O^^v0F\Ղp>81RRUTcwLU]A*X)Hy*)n"w Z̛m0nrUM)zRg4gEK95,sSv ./.;9XBjlm}G0d PJc$G3p.'=UUBP+UMrd#.nVM9<QsيLBU `o%[^clFe&T՘g?|oDZlf^h%'5@EU3X?ɏV}pqoֶǏ>GU^d:{ PpЫI1(wG}m_wN$=veyvÇ"? Y޻cO]ÐIw j=}pAOm;|ଦPU Ogϟmm~ؽ|ɰh=R㣓_'/xgiܭY2X?t9秱* ðo~ fEsfez9'9(Qm XЁfPV>r̴\bcLmۭK$)!߽{>?%l9`tΉ6U(tqUǑl:0"[>rNT|pm6@Tb콨xJQ1"{}pqA]'"@$@GNGGo~Ge@qY9iع8Lﻮq?iLjΚ3[*XDO.j#D jػ,g@" ]I2f) *\{WKӗEi DbYhi`rô@Y(L^rJlj@E@2 S9c}S1HL1g!&OÀM+.C睁! z"r 31!P{*_&Vt5{HH͚@E^ǧ~9K)gMYз˕f! myUW-f$"EW7"1/Nw3ϋլ1f q6=]q<;{xr[Lh9w,Ξo89`.fO?{?8k?UEołMbPAS.q_[<;7lJ 4kp:x𕺮>y?'~񇿧<`eyUpP5u. QbD({륪ȴ"j3-|2)umbJPQࢱ"b^KLbv}߅``/?H8 m*Wz[{???F4 !1%ttr_؁1uیlVrghbz= Sq~wrqL)@];kVsJ8 pY/K% jϟ?{ڽj..Ϟ=\\J{ ^"3vj@w?}78_h6ouޭJ<ÝWw^]:yoeb^!@9W{wm&!7n217D>8V)Ώ11bEfUPרyf& ,_(7f9gVX we,/ ਹ HNize'}k5F,A#@I (vhb2r  {nBXYRN/3bVmʬIRT6_wx;g?7O0u /vn7U9[{q٩iUj} z-YYqqBoH)Ɣr͚9c,W2b ȘLγ7R1Y7TLD&T9H bL9w8 0b!dQp^{??u[ufӹ9)%d"UTiũYy. ٕT>7bb,N8"6QTծǘDA^`9T@^r<}:xG勧?#"5EDbbYDdW|*F2T#\T;G`ōc;;i>p-&yJR,n9VU,Wۉx)%|!\\\UMYf1Uqr6E @!@> J~QQ·ã~stΗ&^2)? Aia3\ },jYSJCOu:`JA]TEŪ$ow;0H[zu:{ITRBD0buvݮݱC@*r˜Cy>9]:\phXE+s_ycכOMJ8Xo|~xp X&t|H5 9h*irЂyBB$C*Xy (9]kL\?*r{\Sel:!VmZc&4Y8)guq|8Tgn&%0rE"$*]kפ0#LZ;GΫj߶jBR,$!Fna r4 P6v/۫ []ĔhUMGs ]+L(f.h\:gBfHc&Dr욪*:j&ԕE"GD3L)F0_x/'JбgXKE)K 8g)!L& UUY4GQU"Cb$5Yo&+#IA4S`vUUk8F扦_HN9BJr*ԣã#3(QǘrC?ĜLmc̉SIծ1@UD|X,>x-wh PT\NTi9f"$2€,J۶BBU}0AEAhnT,lrdSJ9`+&tAT}NNNOT x2fhZf6ʛ`)C]wfɒ mʱ9ݻ}3۶98 Wӧ__Z"8̺n<YZognOf몺e<lڥ>4^EBc9k7WvjI]2V}~Z@tPwX}r1fQI(@g?~]K՞m8[M圫ߓ{.[1}pyuXol68$!DA10_D4q)3!j9pUK!:v)BdC+W 3+L("(8!!~5 p@&ՄĦmwuSM]ʃJnxã㋳)", 솎2TӔMLT,哆+yr@SqʝצYs5+ ~wY4xZV޹8l/>~ S PM{$c!d ` -x/Z0%\(dvo~_ɻ=k[?Y3[,\3^|ʐ,QsV_x*F&cCsvnj ujEgW/H]INCt)TýWzmۍr{9HBY}p|ڶ1ռ>uk>]^0s9hOmm/U\.6.c,p\UP4MRUjY @ Jhm`h_`&RF̾c1eva>_ 1uT"j:9:y뭷m&JȔT-Wd3B/'#%^#IQ*&C5%)a98͸iDuLhιf9;X3Aibc%)tׂ_Y4g ʤ>ǘJUcNj j`̀ IDAT;X"K8wz ʇ Y GqB}pG'w:{"#M1´O J ͼ%c`9L133)99,E1P.(QtX(y$BVe2r 0K"·B4u]9OgGUPcWH4AHr65bȢ`"UH9ømR1DB9 b!V+~^+XB98F""4f*^_Eo4M)v}RDSn6Ħ-"PK)L%pTk0,)тLɐ̈|xt?G=~yL#w-0z7{oӶ=!T܀}U`B5{~o^@㘝|2+;*l/ͷΏɏE4qQqw]V,rĬ6.9Զ`y cBPe 3r爘28Ӡ[oz|1>~Yj޼xe }f30[_^t;rΕ6]C9.9v `Ħ4T2 ;SnV`.D_$3c"MpbZA$",ŗ]R@~_<Q]WbjcBx~_ꦆxgӀ*LM0Sc_8Yiqzf4e VrcZ.V~6ߨilx5zr|K4ׇ/$1)'_@o$*OΤcqJf">g˃(K1Y?!t̎{?#會LkSN1ԍH6B`)p&D $^DSL2tp%riVQ@}i/*25Z\83WՍOqqlѐ`bf_gU9T5oכ$3΅j~[~%֏>~M}U=t^wxDQMloh//ׇw_'/a_y;|,h9j0v6lƞ@DX*<$J;}cOgBg/NS?vj::Dt!,f|q bU4aT7:v%o 0a\98ܳD`BQFfCrkR)OrɉW :0?$`s*=y^?WQb\$r+3% 7|Z'r@S',trressT<{SbAD811shS7~ÍKC42~5%9^ `*SA\#]252Ҁ1)f; CVf+BpLLT`ӄL0)1Irq)79|]W 6[BEɼ+"v=J %TJ1ybR[.r:r}FhdCiUU^r.yע$="pO$3u\vj888U-"s)G,&\ݨQEMdݑĶX4 !̮v۷55>j Iyݦ~H +(>dhfw]#8|LhGhH)L}De RRUavBAUJW ]PW7ZDAN4u4ƪFԜ%ҁ%(W!j6kD9d\۔MlDհXZg/p}u]gã/#2k:4Q,c\ġQ<#`0~nfL|\]bvhOOr?73ְ~3ށk$K,Y=6nH$_HjӁ۱ݲlKbUp{wZϥ|& ,{=(L[._m'1j<'86~c:Y;坣Ã=DE:L `]Šdᩯ&6[;4Y A}[,iƧ~yr1d2T㫯?y݄0JODЏ9W׵s"7VEP$1epEe >裊D0&]Nɕ }mi*cB#1z=gGJɹºz?% t 1 qfв`UZ%]j(oRŲ,c')ś4#cRLuP6VwXy?gm͈&`e$|"^)@iIAr*ZHUU͈%Zصɐ E P5@٬vwa>ͺwvh2A5BRV.kVA0`Xu||C:=Cz c K 4m[on___xeeM *'MO^'_&^ab-9oٹruu*6ETXȱiv`o6oX!u04#"k>wN^/Ͽ2"@L7I "al(c#xJ@h<Uݴ2`ׇr6Ɣ,4 S>j ,M~HTEUCD$a [k A)CDI1Eg1'`ySz1@Ugj-Hj&$du>N1vc)Ldrm`6@$$L, c}? dPP$ GQyZW,jX5⽈A$ 21c:ǔRL99 909b&@iUIp%bJ)"2(ڲp[軱ۦUK ~h ưq*If:?v #k(HJoHs5̻hT1lLUڛw(q}!i^&*$+8F﻾ӉK>[g9Ť4C9Ri7">$S3FR4&ς$ 팒mD3X.ZCWV1)헥C 1f I),+ic`J)yODp8+6JǛ#H6@uHK7'>w ifg7 7%5HB;N\&]*Ʋ٬k]րJsVqF$Bf(+d>e#Tٳ$3&Ũ%E/U9?~|˶k( yw|tO?H^_󧮬mֽtt՜]0<དྷ?ɧ_@Zc˪7d5vFAV D>ʘckDB˲lFXð}1\_]"\_|ɓ/^.X *b4pmۦ(% KRy;}_߻s -6CT~o/?춧*l lc6O2k:wSǑTclQf %FJ1Ycؕ&8p;ER$Bu6h߀7nMo`9?͆x 3VUյO1Fٕ(afCDg,1C olLA$ll.X&c\L!13*HAEJQO~Gp铟_$ZU5bPPCsŮ"* 55ښMaАQg,7[}?I M]Mә+nb0a7偵nuuuy]8z"SuYLbJJ "&ǔc"0M~>aԮ@pq"QJil D6dLu(n0*ycd]@v{!g1scmc9LIIV'ރ5q:'"0SLaW`"dQ)5ٮcTMXVM)ZcȰGA4 {I0C)1Cެ)yu.^VyyylM|Z bێP8WEJId14T¹L :bDEw<ߠ*5lɴo- 9ZҤ>s>j!DABbb3&C㨩=.wM=}pGD.խ[z/[\Q!byMN.°Y]6MR ~yl2W(as~~ѷaԝߴ(TXlE!`F8X/Y:80r;0ޞ+jQXotcCc|B(~`owd\><%@L#$ !a6 0txadя#"/1pWx3-}!1ۼ:=V/)o9j뢰퐒 qaab&I/..zO\׶WW!DS8f UEwCU[Ėe1YgRLj;YVb>uQbӴ97RM2tN&UiVbDB໵Hڼ:y-o>zϟ hڢZ`3;v6,\Y6z(3j5?rZG !òVpGELs<}ڷݺ&Ds"osh[ H@Ft!Đ皥pJC("Gk]bh%#"k%#M d؄81} ZUb70S|(N泹`amY90i=Im۴M RB& T6L&;n 9y\I8WiTU̫Z5u]eٶak'v$a$f`STc&®Wfzۮ)YU/1d\Uﷁ&A-GHŋ ٓ/_\l{!peupQjRЏ@^_ MZq\]BH?!0m7mQ>Z$Ipu\]O'/weQUvQUBtvY_lf$ gA.90SJQo*ERQL$)_[R&7[]0Twp$㪺3fgK2C5DD{^!-3Z2|SJ1zI(CR,˪(U|Re$UI˴d#{lZ;Wܾ}\jQ&Պ޻wclM'yЄu18㬵("EJgTib,f4Zg3 Dw$Q@P)͢I*:ơjJ)ĞE/ۺ>zfk]WU5sGD̮0aʢ*88q \YM73WɯϞ A$Db*PДEBC @Ay HRTDT )^wx&0474v~vDD!ƤJDl4%ֹb1?O3V!\M* Gp+ 0u1^o9{tk"Rddیi ĵUQUq!RY iVC"MuZٲ,lܼLJ''m&ՇxXWNظ{{,$-.>\*9yutj@)3wq g]N1sN8Tι#FcJ`o6s>>|Zsa 1 Wؘ莳}wmkf}SLs;fD#(BT)1yb-ru`ޏc_Uu7"dqh֬yGC.TRglދT DI5ox(sl|b[!rQMxNohqjeD;_|kVFۮ;=[_\G}eΣr~Ķv}UZs3GA/g` jIR D}&Hskl=n߽'~\.PJ痧iUHͶiM'v0D*csf1$G$İ[ڴH rfvjb:WO?nI_e%T|O Co׫fb13r_UQ"ǟ>}իחmɜ@1٨uq !EQ:c1ŤdXU!f|;}~L tCHIY8c a$xU>j+}ߧzh7mko8}7} Z\.1cաI]:% lR !q !cI82*{$X"%h* Y%@D= 6 c~w>.J># $ h !8kWI!b>{Yu(OHA-W IDAT.!Af[*;2`ؓ#]{ʼ4l۲|:/m+J>yAdRUw&eS%,)BrSXSU ^ paWkLd@z k9F}d7Qrb8K!5mUB.I6*BU;1ŌeWH28oAch-\Q }YBM0 FV knaM()l1H֚lR%!ju&>N1ﶝ½ $AcƘaڶ͹.USd%?"⊒뺊鷞- ĝ!]=w~٤:h1NO4uj8<|W˶.A`pfdp:-<~J}g/7/"ުY*~qiH ח9ZfRuL !y LENGT|gO͊Ul>~L" Q ,4F624<*!D!xφ,s촲0no.^g.V[M _c6ATɩ3\Z*JS@..,yq9i>n[&7]_Mߟ]7#(rT/.NMެM䝷_^^^__KBL1%"b9%a! S3Ge ~W 16IRJCŘbpiUl̨pC"qUU$$RZWTenֹ~M}'OzʾOw1Lſd/>??\3o((o42\0Zcì6M)J[MIabTeUW v,lR It}a;鎂(7'HČ>_7T7)%\L+(\0+"W9F 9&2u=!~v<DkzLh05L@>Ymqvozu}}ćoܟ~<˯DL/>ogݷnO^\={qqy Iea1Q"$B$`3;rQ6@JGwn}ryQ;CH>v;J|ы$wɿ/_[|]?hcB$K<ƔO ?. Uۡ+]Qu:˄s2EᜈUI!s焅a+"4Y,aȯ6,9sA.jUy޽˓W&9X`5acmo_tqYsQ %c:~;ߛWôk$1<~CBS|uZg#@UՆ,e*f9gJ!iTO84D)z';DQ/kr,*Ȕ(r6<|$Wy!B ";!?]qŔ<C :tX]{2U ozfciatr2- dc 1׍bd2JJƊ*9;'qi&01oꦒ"1dU aZODRt3ERIi##WUe(H]DTE)E, s`&: 9];2Q !t] ιYg($HqH).UA˲2wsyyi̦( B!ĘT4 Lx5 ͆Zy<2Ë>x%cyUTSJ}Z ]fֺ*QFQQU(*Cu^O&%PIwUUlڱBb?+yEƨUr;oچ%C0XXEP$>}c z2̗5jSQ~1Ύ!:Y\=8xt% B;ǷL&''>,q~48 10).Bsf'a.¡ Ƙ|v&M;Wb e<\N'?=ru]Um/fWg2&2PGbL&-=99%Иr}dFi -[ߚ|~BS@h>%EıCYVfE8TFʪDfM ȔUY cHI[O~ˋׯl( OE0{3/?zE&"1OZriӳcKaq" O!ľ)B6؆WӒ*\9[*cnכյ^%qJUac C( GdI9.H&dzW!%FڕqxwlL~1ƜfݒJZňȘa Q Iv9oD )kKwwՐ^s'HEeܝޕTrq,+2H!.Ttëy#EL x)8SJ(!|-Ad69"si8ezoHuK{)v"UI>J9dWc)"ݔѹtQU Bcm!n*d1c !cIbBd ""KL5vo^l:+.勯QCX o$KF!^wKe,Cve-!eJ /c8cj㗟LRɴ}vɩxZE۳B ˡrƌcd*w-z{-*!3NB }D(˺*, 6(8 Mӄ@13&8_޻uۏ^\>|Ta(l5/Lj9Km/9gDѷ~g>OS}]Uv{Z'ACŐu` W_:{ueع:3ڮkvooo:S''o./.n߾cbWz`?PUdRn׮*Vy֖V)C!+J62E( )XT?&Uf0 0 u9?OCXV{aIHLĮ(,۪,kR`g-ZkK*uQ-A!Hb(\IcKٓW%P1khʲRդ4,<|G!؉&H1 } 11Xg9a{AE}Ðb'0ipw?Zf Q6ͶR(5;ᬿz4J"$BTP"j#jFa(x9ƈ;DnTcY "u6Lv2.%1@ ƤKy^AIC ՎD}(唳!#P ' w"$,JI*KbP']"ĆDY#SڵUAݿy9[%cM'|6!xc+ bd2?ܔUfidW16Θ"d$5F2h?zE&1 1 q!nf3o}^ 0۲ojJە(lƦm_|uplCUYM($LI5M7zvn.|&HjΛkhlڭU?1ٌ!92lږ("j1@ʚ],& [-DZ()0 |v0ѯ~~juy}l>i| ^4 S Y!"믿* VDtb|j_z%8]Yl6 …j8qemɴ@L)ܿb>pxpd|Cϟ?"Ö Pz}ڬȒpy~W??wKcRJ*^_\@ڶj.ËeQ~C:<]K*ѻ8}<p}Md "KH;͍BR`f B&SdGZ&E0tJ$4DƑ.@f"!B#Ǘ'R0TNCTg#@2h5FF`yc #D?.&$FyU޻`\Ø(C~04z^RlA42,Jku"&KDwQ$)2 q؊8ٙP {dΛ Y!ȏ&re?C hKPƢwGtIbIU@PElJ*HaG'jTښ%ݨ D+&E% V"ZVUL B*`B1*uYCћ 1LY)f@ si[1D%Usr(@%rpy11h^I!ipX&R!$0UE%tas}J h"ĘbH[ !&c#g569ncsrwٮ.WELY[liVOkUl-//0IU% %)4}߾1zTBJqErXt]wc?Vdƚ$ E7d̓>>:!PW|>ٶmÈ 3}MAҬV+ g~{opuy:fMC kL !*/// w_$F%ȫW_c[.>zd ^_[FZ*2 q8^vJ~|R lĬˋwz#ʦ9l?|٧|7>T8A>կ^eqRv_BMC;w"ZR;wtݟ Tue~(ZexS 11͌ #>De㐍ODŽ0 Y 1 f}_]m\JA@4'l=F@]غtL%1!qL1WܹLm"J"B2ƧLŚDXˢL榨:?_rT3GUκIU tVOlܨDư-!ݳ&6B F7=~PljŬɶEt~$VteaHs'( ~ڃt#QFZvS9 STaL {󿚓'7b)Dfޏ C7 jF0e>nֺ7xhg@dR?GGْcl]7(3eJD?xLA.JEHduWNCskYV.3($dZ&~`YxJW%M4%0hF000drUuT3ٮ} 'gvҰ4ֽ>yi{zi6z[Sw{! ߆f{/''w*ݾB']eq2u5x*=G///./O_H 2Mݽu7m5=~tz1'={EYo6W4|af7(ȶ2%s^ejk1ǀC)d$k@()-0FAP!O-&h(/`.HMY}[jɷN׫W_ֵ[VMNѺb'O|4)Jo@m6~0IC ק/.O[S5/OHB$MӚ|a]ճ԰i~^76 ِUDPѺVMRJBYeij J Y}J&fL2{W@4P@^={}x:Ч82#h% (HL!10N5 RYLIalϷs3[XQ0 \EtdtXWYAzGjݴ;Y%y7lRw @?4Y䝝F;t|6/e]s 1?uA5ȆUXf621=\f`1$w0 32x !1!(fL|CL̔F#p@L;፹0/BTDAw U!$1nZ^ !U=C clK .Q"a=׫,犘4!444)jlдMJiooYZc Bd2-ȕCrJF˺OBܮ* >H߅-PۍzTUEYv/-*r0wTdoV$ (t->P,H6ӲzٓO>qQ;3v}.!~;˒nO7'r;{T[is}g` (v-j,!l6˪6CI/Wocّ/眻Dč=LnEըꖪ'=gQГ hS.umd5{,nfzsD'&Ȍٷsxm jR9}2X',AY( 6L![:_o;TJlmaW޻?.L))z\"ІՓgggOoz9WɣǧΈ'g(G3^^jlEը g'&CUZk˲m@X5mP'u];@8ף+ik{ O^eG& qI$dVD9ET\bd8a4l lMgϞ{ﭩKlu; 5FK&UΗM'Xnj "ǐBumq\j6?[w  Q]/L`0.[5í-tٮ;"wG_z|]/Ble5b)g1q_/!O(1\*,kE8ksԸZTdKcr(}Uŕ W/ \uɩX)ftV,Ƀ>sr\%<*hێs53ID2ֲ4%B &F9; kQ1b-"GcU - !\]a5EQ( #IIFNUʢ,UXE>;99~uB0UY% O/;SbG[rݜ]Jdjݴm^j4Fz/*T9u7=99᧷ C;ǿ*McȎwpb蚮GQn,iz5fM|w2Eٻb{krݢz9^8D n|1|筷O?c -xqz3'(g|S" HGŊ';e {o_ξz+pP)*s/U0d?s0KVt6US"mn(!$, Hgs(Ȝ8E -2 YKκ(K-`Ϭ# Q:Q v IDATHd=A.JY7vw~[WŨ?|m=w)OFi%ϙQ4(Te;ܚ ڷz?)O$ݽ= ,Dԥx9_# Y&:׵ 0"p`Rd-$d,|,\QDcsb&4mT^<vMvb]Sw:kM-BSl#t\u&I>e]4a<җg󓳋Eݰb |<; DbHno]۶i )'k\&(cR 18qrkŘ+{jJj'Unќ}a@hɨ& w( &Ekm]6<' 2oGx_>|`hQg*Q:td{ߥ u\,6p$;˳G[RRTZe3_~g#ӧCLQ_q2n8ީb~9τt(N;o٬ي1֛EvAy+¨ sG)nfP1,[nod4u޸V֖7޺uvluA$vb`j,,њ-h Ͽ.@iX~#7U K I!\Z)Bd3k2>%A)mmYfm!3)Y J_rٶ2\BkOn8[)# AD*7dTy,,K?y4|8ryQETڮYjD@Rh].חlcrq8;o>y2,}ޚYCv-. L)7*ie1*a2^ۦulMvGjqV1BHۺi%0XS)e5TCDy*)PDSJ9B4`P @X6@}]n@5>Azؽϗ߸LmrČDmL11cqց*YHSp.7y6)t̻+/3{@yOOE4y?*_{YrX?s~)m֫t_|C``\Uk];G@aqwg\N8mwƣ7˟?ūo}82Dpz|i.'_?}JBXV'GǃAIƣdo_z"Y,|_}w\t4w4~⛯ z; kk&gcfZ4kVt o&G|19ꈄt۹9 @SZk-*DQYV}hԡ!J 0A_+=T]EbVn9ʉ%#v#J)2szis3ZL4oQg>>3s9'RFNtgk։H] "]. j*TW*b9OƼ1D'VϞ=unz9ǣr¯׫t~z|IϦl 9c71n\bXAwxRn6_ӏg/>ğ|ISJ=\&f@8, f>&" )V:#$v(h:]E)m c"3dVϧ&eG(my`gnݽ9zT"hnY3 *ʮkC.PJv1B۶ܕ6kCE鋽kݸq{w]ۻqVFѵ`2\Ll#)3~G/_rtjBG~uJ?gl!m^cbXV:fK{@1w%֔^.uը_zf Ng.YUۆ,쬃 Ly\7(;9R2d fN/bL"T9%a~۾8_fQۻ60 h0}-$szr~"1+ c4*5Ep"DKsp;,qbs,]Y4Y(,%#81c8wh_yv&ӳdroHS l!D'3" "Mۭ&h\m,* $mW{#\Fg&6^=."{Af2sUT3s." ^PD"+7;C Pۀ(1iLuZ71H~C|p{`< \AJi^5͚9D$ARcxDc`0[V$dIغEnrww9vyq~~=}9ȊvǤ)9ՠv'1g*w~߿hk<ϗŪol<ض;[O|9ђj4ޚ-}dwUxhsx7'\~c4=:_:dNÊnctQq{?/E9/?+̔%krd{wLAzk ֹ:m[s0** h~5䈌JZ.޽3==~ts]/v˶)$nc@n5DJ9>@4WAU9BU6UўQJi>ht۷o?yŋio!ee994i$b/׫ղFg.WƀR )&a((!H+tLuXBB _$$,ERWcDEEUw ru]]0htݵu  oHT$r(pbT@նi۶[YD_<{ڴ6] wzūgj=-_8yxqrA~Sm9֭;hITUw⧿{D)`Xv#֛o_W/|tƯ-F[1ݽs?[7v^^gu,W]g':e)/_dhvE7In W+Fŏޛ]./g(u~b ,gs_cQ; Ʒ|OX\a{oh9'*b94LhPcOQ+r0ͦ+s(buFƠ1Oʻw~a2FUN繯( "jۖE)%IJH9d<,,Y } >/W9vygϞ̦qyytymP KRNY7TB"J/' S2In##1"a9p_%cg [ Tr> ZZW>cYG#f)1CW_Bhf#lQ"? %K\p%M/.ٗQFK$":c9e CdKr.l2m\f{b) SV۶5m;"S4/:݁ ]PD, ua])19R-qZu]ivunݍ{[=aZEDomo3^\̧EQfXmS 8p<Y#,L(yzE2BX!2;2TU2*wz:;/pvy'.VSA\.kׯe!|~yyE[<>mMs䐼Rv:[4]39"l֍A'cYKA*1I*(m'Oyo] 1u!0е97 rLE^IC!MMHa|11\ *BdDeIRotU$UQ4c訪JoqkPD;[&(*h,jCm um% )Z%$aI1Qr QE s$Ҧ+YY@Es38@>H P@>WQ#(mLI䋢뺦iR B5=;wp@gD;20Mt(w㪠EN31e[E-BӮsFEt/y̏!8e)RLD&DCD}>4Wd- SL':|s.so6Ɣټ;$a\z,QAb!F$rMv~, h (1%jXלh*^H11DʶY Urߚ/}GrT"5z:;?8qPb0:Ջ$4}Z/bWMWnt]u)D뷮(ʢ,E2SBlUFz\\\&IHĢ)3WTۓɫT6er;8~R GUYlm]~<=.ӟJ?pu!<}m:Wu~?r=<||I *uȬu6MNJ@b>xvEuUD*b:_j0 fu uhvGxd U8 $*9PYs?xkPu#::;g w3o߿{xGkvvǣNJRF9Qt~~*it";z$01o2&h{R"_DSUlUQM( qΰB+[lk4ڛ9c(lH&1302u1mZcRc]lzucp?wZ]kJMd4G*)%,IYRU4e#B`@cr36FMm/K./*ʐ,dM?*Y]URÍrC|!*ү}$aϾ^3o}sx?ri>6l&ec*"UQ j֐5)ِBFaQ M3y (mSܔ)sn8un\!",(t]eQN|ibNPnXU5۟n >M-342@ GA5ޞ侭aˋ `U妮_~\-T#5"'׫5"~ÿ{k|r9 ]c@g7YxsejPhCry9emXk]51E1v][,͛޵k8jB DڵkBO$p2'i;;r8EQۓmklTɫ[D{?M6ݾ{{?5rr>8}x䚮&[ rYGL҆!骶?Wy(MY)Ht ,.Bsaՙ X^4΍;{*qE(`gE^y.( ;~?ͷտۿhX|?d@^}w;?t(#vZs*(ۜ|"1& !"ARf&+5[rsޗ0;W.xŝB_UVJ/C!F!%95uI̮d B$ID ,!Ʈ `J9J )"fSC!)1sCvsƘF3sDX,D5@@cri9Fޗ:!%0R+D?!;3%=O}b(.+K8J=f(Y4AYcMJQU3*BHڬG+Wޘ^@CYc1db.T3Paʘ|ī!T5rpV)ưZs.Co59-?+UE{okPTzAdlUEY[C["&+T$9@DNNZS'WƸm/.UHT*Sd$2ƫnjKBx[7>ѓ'bhf X$Őp@)4ʺnuHhLD|Ap )$Ls!tjez-Y^LX'ǫu- :dFN0-9Ih[ak+ _- -d q0Χ_|5`AQ_-k];ܹv{??_45/5NEQB $4o3kUak40O_v|X IDATV'z>^ۿvqrjTp^U21 X$FEEt85$" n/ճW=xw{Ѽ~^&7K5U`{h- /^)lݧ"IHdq!1do8jupp'W: @hxxo5m_=yi3{ͷ>ESʉ%sqۮ+,kfT$beP?ER$DC(I1XccJv11Ā@d ClU," %d CPMh4mf +aQwko#A2htP ,վ`5dIJF>(/"D) ٞ|Q)elwĈ(<M08|^-!DK.9}&eY5c&E"eQNDD !"N1aB Hjڻr;I~*pgZB1?3lJIϦ ]D1Zf10Y=ܸ~KNܐivvy~jp'唌ujpD[@UU!AJ C !Ÿb^B pJQ˲RIŭۇ_=;B7]-XR9G O#EY\vm^|y{D ǔD8 ƀ(d(Y)$M"Ȗ)p^׫!pdsگ1x8VTi.Nv/vv8 x,rvo:ŀH I>=>Of2Míw~ŢBBѲ(}@"cu4n9;ܹ߄r,!xX˒o޼6̇GAWJ~p+Bd ={>.hbEC- .Ƴ }t=*Hzx߷ ݺy @2_H%"AJI5%D򺊄9){2&:@D4*[[;ի#8 6DhoΧkDtZ^=^\~|8F}n\4#smc &D#Y`5'3PDV=(#TTXlP!CP?Zc$ (f 92'셉""M"ωH %3j,چlP,sd !lA@D ,E9Y',n*颪bHd\E$!\D%'KFicf Hb_Ȝ4 ;lrYXtf8Um3I^ 3DMKioX;H9@@%#ÌBy>Y#}^eJZUκhB/{c_XanaY~Blu]D"(!*qfr ̡SU1F(!Ւe&t>_=Oضs˹׋sgE1Y ZM$r!:o7qcQSb0, |Az?j€/|׶Kdr""m#1 ZvJdK_97ϛqW;v=C WpB/SԢ’*֔wNb0U\Χ zy?.r*.9W^9)ZAawGnqԳx =?{{F&n\G[7<97?3NmہT;>?hGCv <Ͼz%˰OO{{?IA8v“b[ղiVjٗzUooM,+ y%omvxﷶ99!+X̛v;?;KWUkHsEťjo߽UIw^ٝO>:?Fk(Es5q>NM'\HιFz Rv2lcdaf@D((0ؚlMI]~4,yDf:\:IDbfFHAmBHxW1`1Āc*eNٷ[gM2%Ro>f$Rpō:;w&"cK8q~!T9? y(TŬB"VQĻ#+";q:d@Q$4/$Ws_{޳gO\q݀n~ͷ/h E)" BN$mbU1(h~cGrMHL:W QVU7nUUÇ \5s& /KaħG}/lJf X)EAQ26C1 BJ\Ȅ./9'C"!m 4!U@1W;?D4js^SA FMMLM% 7^?X@v_[:0LFfi+# Jn?yqSlz5(MD|wΦחˋAg &2\;8uFhve8ƳRޅ[ޚ*ho7/ϗ `9 c>kޗ$Rrt6W J2B]^.m\m'/4O\5I5iœx*1 8ob;+l_19bXMM1Ee9 ,Pb+٦]wGqZ/Aʪ, O_/ [ |ɿ|'g*E$YA,Je9pmZ"R,}5Zr j  *QI1umԵD$KC( CPQ v?˿ˇ_>}¢"҉HׅXUg$ 4&0s \EƦkf5Q%fh7vS+8@YM<՟ŨhaG n6 B(=flY[)c2ִ-E Y @v0a֞={"m_@umL'u kF!"63C|2sYŢ:N)BJ d׏ymLͧ`J̨z%c ,cL}JYc  o5${0 @2H$InUAFθϧy@I@TE!FVDŹ!ڝGt] )pJJP 립8+241TUp(M  TT%!Yw* YSy+GE$1I 1)Ff6*H. V/^\}A\(@Y;K\6X5 [錈 v*뜳~4hSGx7( $qJBP3ϾHXklbeN7 f~QR $nNjYBr:B׭> / $B1AH5uA@b0\'xyjQL }k[[ÃwVQ]cp0!,r57?˓$Zbgkںb a7X)u@B>;aVI-txRVuDYSU.F U(rP &b'?Oݟy}zr>_WmۅbOs58gcLȏF E( (F-$FEX2֕ihsPE" PʿӋz4 (!ÜtE 6v/TuKX ˺J򕅈z&AJJ뺶i6QO꾌E WT8crsIZgB1ZgmJ!70cC\wǜ ]@TD4r<@hDģHC]?~+"fD _Vx:ݻ{kޖ!E,@@< _x![D$ E D#hi6w]|O{=z]W~37"  b)jn[ˮr.?~GlVwɖD@^|N_ZaN?nxB>[a1~ŕZS4TAH~ܺz<.m6WH秧m;e%Ff1%ºKH ཷ!ufoBz>>Q[UԔ+DӍ𓽬*irkYS`=Dxj?pY&Ea3ڶ5ɬ5.7xgLhlֽ*$2W^ye0h B [^6m߽tLYWU5NnRz/clcg]״ej)&'Ožz)db^+Ta^x5v?EyLC1%dYgН?ѝ5nfs֭ax~twsIhYˆS>#f0 lFLCv`vΩ@>@^1`YиvYA((Pn2)f\^YK.jLbw^>zv~1\Wݠ ET8UY֕.ί14]n6^lH)B̆ QaE?4O><;}}Rdc @XM^"NK 1@6egbDED\J)7SU1-!Ȱ=TjJۯjW9i[NY"""c >8B$!D1V5y琂#!}i_d޴٭H 74B3~^rJ3-$i?2dl fw1 ŔeT܇v\V?1}z\U%&W Ɣk- .C!jYIFP2Vչ:zgr}qq EXk1hX9'aX5m~2?Z7eQ)t o rƚ5Zڧ@!%&)ڕ0FYi~p/8" dn&JhQ> (*1(d<A@$(Q?e>I''hylV56CѪ\^TUQ0M>JLtÐ.$DSUッ;]i^?y6Yc%FLfy*lPu:HL]Pxu~Y\+E1XSD'gg^cwu/wCT`Cn0O?dqu30b a\,c Qv(FSPts싏Ç_߇ag9šmV"zLkNm/I|"jǦ+{dM.Cl g,&cQC,"),XP}ۮU5$I٨HIGMECUHQ͸麎=u *qŅ7j~?E"E=MըVzp}X/.I9 VM&ekX|ߵM~c@̖AH){c8ƄH)K^}<'fݗ!f;̮#w~o[-d9V d|L)\sC 7<<cXS/8I˜d6_RJ,fT}"kh;)qJnZw*TT4˟_Ls,/dj偌]$SֺSJI4)yZ!aF>r/Q-' O~ٓ'\m||{A0cعcҳzƣ^rvPUmV˱5b6M7w]_꽷vC75M11>!@֯`lYAq2r> C!,o[m=~H51kQڤʯ$R!D@dHQs;. ĠMz߹v:f*Uʪ~,/|vqqC|0 O؍Ef󽢬Xj6KW8^>{l8e6ٙAH,Rх0+|a !Fh>**hlVz r~Y\-e6QQUO&j>W?ØB |ƈٛ}?8}yaJ8lբ1D ) !xdڢBmN%h:DϞ<- `FH)v]A1,\857DpE!*Mðs7god7sg*MuU$Q> v];7Q PReA; IU'?rֶ7ɧϛ׿w}$^גdZ-~sQ]LE-zco )]i((ٰ+)3[ڬꛥ-|)sVr WYH.Qx ֓NI @kvה'YnZ }V%BUv7Yݖob9Sb 3ge6W1Q޿aBr(ݗq+e~6R3ht숨(hc>wvHD+;BJ)]B,\y"$O"!Ob^U㽓o~U9Ш$dlzT%cѭ{[%i:7O zd,/g≮_\]^ +'edtp|L71=b~@z *T5>//_pLla1 ;1$GGGws֘u^1X9=;#{)Ĉ+49vb !Ĉ)IQP'{|4d6o}Ӛyo_|5\{ߛkk"#{f0F^41>r0eGq13&T=>>0zÇRab[M 4mp 57]j~g7_{K*,!>|Mθ0$ 4 ;n&rcjH!M͚A61ҹIc`MA* ޳a&!;ѳfCD̙ Ib !@:"ʶ6 EIזuʑH 9Fe&騘}kmY\mTb6]ŋǿ}g_}rP}, MƷoݙ1% {o]\M9{e{Tcl5UUeYZssO{IaH!n.UH)hgf$TeS.K&gv \4[wձT9 b[k n@( )D$Ԥ1|VI󶧢/CE**1Ř g9ni  @w)E3v4o0/Hxcwzaa~x{}/b=֯ƛvۻf{Ǜ>x{2Ll^gwY۷O7]7LQ1}wC6iUp Q72˦Moݕ*1E7=7_C"$1lG{|?zY7dz$`۷ڛ[0-uvϛӧO?{/Eq IDAT?D**q\Ύ.rVscw}yBrUM&?Mn6Đ4)$֯N?|zg$<֎н8}ڷݓǏ}׫E4M@ ۺ_h5HX8kMt:jiڦsEA KHq\eYfC֩|FyEٔ`F>Y&4lU Tc"Fg~18D,z^8eҽ*9WZ3 } CH! !e(86oLvتg]屈A̧\!MV !4M$a0 ePɰ+B&eQ˖HI54Q{" g/BhN ):pi_NƠ>yz}#1<: F/H1_6̮.by懧>?{y^m|d&{lR߻}w2lQ II?` '}L98"Jy 9jL>,x3V輾8Z#o#K!(d',/e't rQ@E$IsBo9b\xԄr|H999:9.fT\+^:Wh1zn,c2,;C',SJ,DRBfkh󝏐7)Ef|o,^^ E غW wQl}ͨ4_-L ! έQaGeQ8bJqZEB& vm-²Yo6m/b4 "!b=k1֘RnrƘ|Jd?gw_-.0wGohfǟgO_,mQvAx6*ST'AJ XW1UY1M6zN1Cu1%7t2>fMj ~w?3:8_>~ Kz ЪHBlY\-~Ѩ|GM>&Ac|MߟzSXu$9NJD[s[vSkI$爃ThVe'&B2ݻw9 #jJqZv]R50*:fRee9&0zv"?ogMK"4GE5*:.(:Bq~U/blY\eca)D釈 kd2`ĕ4TDx4j&"jPU|FĢ(ꮧIIg͖XQ3[kW'8ߌ_l-ʂ"bSHB!SISJ)#r ,lK̜`5SHUAm<;;ܰ$#]8 Pٺ0U,Kt07MVt_.m9m!GM &Q|oKIDBJBڬjyM[Ո-Ҵ~7&6QUoGUiAS{n w'pÇ_M:,zVnXv)PVa|_ ) *F}\֮COV˅d0Zg ❐Pq<&ӧO]WE]񝻯kEI)5뢏")ưYa`,1DVq=ʲEQV̶( drtt||Q!a'c˲QkdFBL!tΫ'Nng}os_ /JW9}S:}JAL4q&1S81}S"T~ULfWԖV00tzc,ƣrkcՆ)ܭ8"´7dy2YA @*x+LMط6mJ Üzw_}ν{e5)+ɭ!5Te (,4)\uɐ=ū___Ԍogz:OR* R1OoGUO).(2Bl!P$ܖL)!brΰkLv tU9mZg9 KGo/}tC#M9Ē/1yNlر|S㽢U]Xk C?49h >Ĝ~pD0l[1$kʗ+H2PJkL ްͷC54*9ܿ7Jpqd:΅0XV6*r&`|5u~/-qnt55bbՄ | a}뤙afBa`2E1**g,KATB -+RjS5p=rs܄DnYRCTD]Fغs'oc۰eum%,)L_ sZ.(VnTb쁻eG{gx7=a¾!fdvftV+JRJy[gHF1EDBBap9bqljlLG^s'ⷸ7CTZnjl) ALj)\Q[8PRj4ucXbЋٳfJi<*ZcXl'/m/1 ߬*/+U#ʼZjmm|,:*ۦ(ֹaH|$"֠mCZ6M|0l!Iv1fBVvbQoףph ȲiUH$:7*+܂BBTb臡u;}fܹwtthv쳇{8w~r} ah;Ip!raݛ_N QI?W(Hn (AOq̆D%&^AِZ$dHd 笭bTtahΟǔDZL"83[ĐQQ9#uaM߅"Ms\mOE5Scpn:KBJdO~ܺ-*A&iLrv~C;C${}Զf8?^o>~}.F$&Stx}tu`b>IAqsH#S!"I"ةӺ PhL.!^ݻo`H/y4Mf|OiT$Gs6lʪfWٸhι"]ksq{7}+Lɞ999Άi۶g2)liUOh!c+T$C (stl k9/O† Y|Y Nu1zvyZyS@9 5[F1kξDJ)5biC^4c6A1m<$F򓙻}tptvyr5Pfd:vD)*0a*]a $b8@)i3<ܜQES[ %ky)pJc79}6*_yw3>D*ITCQ5z nmxD \!o;@xJq"ֲt7PMef'O?}fl֮(O܏Qf_׿ϒclLA&\$I14$KI㓠tلDIL4YDH..3G)a6Bh6%2sU6M,s Q UYY{;\Q٢lCW^9^<}(ͦ!l IUoauFsuL&rZ4ח\ۂE ž0ժ4-HTQJ4LR=^ w|:''u7]et6Ӥi@?٬/>`uk4-]ED|26M!DU!y5ptT;/[|V !NDbw{t%dKdgf)%f$PGor TdhyRLIfFAv2A4O 3AoVcMi7!ƺ82rC QوϻELi > }D@ulAєbn,ZĔӼ_[j HQ}vӬN_wP;G achl0!  "ORU6èectkCJ(Ft]ILEWfŵe:{}g _]_m3 lcҌk,U!(!d&cPDQBID}չGeQ/އM\Z~ΕU9FҮяHEDr] k5M__|aR?<i={`OI>WM=F>{qdo.ssnsȘB*c@0:$Hj4 hRѲsyq!$ h1sOwl:m2Pj+c·_[-4C'ˋ_Ͼ4C ƹ5ͯ~ެ~Xp2y^\k(1l1!\yٴ;}u=0E f([/X9Ls.g1ƘM FL)cƣQm8I1Jt}7^#ϪCQi(MuwƳ!8ɟ{Y>t).Cߵ0*2%_UYbL)}3fhTL$M|վW,/a_83J4F(Em1J\Q5ه2, #seJnݶa%!K2kM/upߤ[w- [΍=gon6bsM2P7Hv Qdjh0uGbr(dULq@APFUU}KS7bPc2ƺ2ސaW}a[DD0\HƐzl'b=R$DöJ64qZf2I ~hM9<=7Mg].IcL?G'>QMZbOF161exX1 BCSzn' IDAT1v(ݬWaolL ;[12GD+B$ѕ՘2*+QjSrIQ׵cӡ[陷kkf3Kd[+;d+SalZ[a"! 7᳏~sxrWO~{<Ά{7$ ٬7,RLJ??Gտ?ߴw+v8$J_M \P=1> @9nETE]ת EDs5x<8?ڶmb`$q( ?''{1WWx|uw<*c7Wo-w=m.\ k?H?$KZXd\ϦmY1"D(M8fwѝ[C쮆VOB9tN1X3ǡm6LXkE%eGl ۦZvÔ37+Kc V.GWC~X}knH tT"fcff[T{#fu4I!9eg>ϒl&0!DPQf zB>zU W"P![4Xkc, F☄I*B֡}?vmb( Ejukbc:A((@ٮ&lT WIZn>Iq2 7?,$QnC@™l✙JF*~|*ʪ(!!de6ڪ@LC3HߴW2J犲F NJ\^85M* &DWHYޯMm6]]TȜC9}EKQ~ۅLj}{;::Q; <)(!voӉ)rdPuR2bpW4 # )3FcNC՚hcqv&EBMU!DI(R}nډ]kg/L㶒ҙr6AC9$#"rVv>d ^Oo~ZMӬVחW%c—HL>~~udAT!&. W.W|]eX h66HBKY& H(Mgʶg[!遍*h볳$v9DMW PPޯnJ&C`STO>G6f<=-\Id±)غjݞ? !Y{:g-J%4 ~,ˮ359w9TeU2XU$J(jBKnY'Ov//?0%[-$R-EV5ΰ_ d""9{~ޤV},*ԇbEuu`UxaH̘ro|?fFADF"TrI$*tSVJp"ۢ*b)v63%BfjG+" Z"bnfيwԪnAJMAUCp#VR C-YTLMg9\Լ H)#B>FۦW/3996ȩj_|'YKJ䛦i[v\t2Mӄw}iJRi"ċeéw׻WElޞVŋŢ[tO|jSLjEh̸tJϾIg]7Trv Ii`//W~iHi؏w|~zv]Rrդml.34퇡f kcRqy.KqD@[fH&T5e'?זDurSOI"bZ9Sè97a+1I*9BP2Rr))ܵs)ضǯ>0k]IĈRsι1ǒS9d䦀,s;^w"jZN⽟G?Q14~ϋ>I%\|6>t٠jU䂳,>}24dR)%93/77ϝ s)jM0*z:E5hm@4)nj*C }źuCfvØTg~-O _|Ǹ\xe}T{Me`1zΑSFٯv5̙N|i>ܮK^Sª&RnKttyw>[,ڦ Z*Xß jYZ(upkZM 6{ݘVd-+^mbB+Kz87c" _r)H@Xb%D\C@ViMEC3JGl^&bU927Msvvv۶)&8"&6)cfb`Hu<(r7m6nHf"wV>D&vDH^4Y.ݟW:gql[wGh >wrO~#:bbQcʃ3˧E8WDhιSߏ$Rq)$lZԬbѵ]۵jr#uʻjhhds.E4xj,[U1TCR΄hL?on>77 !Irxo~ɇDQE @0e .dEq<>lu!uAŽ'ݟ޽ijsv/Y}ɳ~?ZpC\~/f 胮bbȂ8(ߊ2YܬeRʒ0MHLX2LSp~FnߏCjSO)'H4욦kۡk;-nHΗVDE ;vlє2}f⸽CUzҸOs̎ͷt*P3\Š1n'R:_  @~w)MBlBQ.5xՒ0noyg779;M)"8 tsu똝soMr/25m}+~yiszgMXcoS-Bjǡ`on֛kGƳ91Qtn/b̌9 1 Lgf1=8WW~=6Ms.%a?kR9mUɣѣ{a]`>_C8v\\KJrllPDT;?732ovYWYTs<#cD .%gC=~7Hi8;w-doqg >(3F3@vowÔ ctUu+AdBTiY"XDRحH<~7C/@(FTm Duj\t*TzKl"!07YOVg˗vATwAWFڶuTa);O2 j[s z64*%A!;$&ʱ,xЯAT9D~({Db%AMɅ9wT֓w6DZ_v^KIk"o^.fswb}q_}ߏ3c7g]Bg푂 pRSf\\bVT"'RpL nssۿIUBA:v9,6}vwO+)Sb֯{0P_뫗*i7~|oݶ:Y>w/lvqg]HYr}7|=Q)9OOΈl6_NOw0mʊRɲmۘe臮k{\Br{`nn6qS8ok?9~?朇q2$3\kDg'd h]7-;OD@l_ Z*ܱ͟執m"!tmR|=z%#gOUQQ]ƘM"# Z}PETa؏~o}/q<2o[=`yv/)EudDHj!TK%92V 3z"%tGXs)5ŕKe޹r#U"4⽦ !!ڂb<8q כyX̛!y7-v{9[1[&? 㸟DxU()EHF݌0Leה5x`0fR2 :KĩDhq AE\,43Qs/VV6qQT:tɨ!C8ơ:9*)3,du׾ΛՏ/B맔J{H|vuzψxzzuyմLon迆ّ9fHeʹ-%}[g/ZtٷMSL jl89 Md r.\U$OO0 jx<"u;>{WK30-9Jaۭ{].*məKopY;o?| 5]w~~<ǑIɢ9G\Jއ^qӴ1i9M1"4ًOOӴ:9ܬkDfz~v|k0}ɧ({A6s1 qi"v)gBv0_F*I˯]]11̓y ||[W?1M) q/Y#רPJA0rkCugP {'ͽ!5Gn*@M~fvԝ0,>TDJm[$1j*bT Ā% R;"bjzK΀hZD 03;W5y;\T&ξNWmhn7~fO>`sBd EJAbxb㴽m?{ߧ_}%Y)bHԶ!fLvsXJN9S Xp~ѵyC@)= 0f7lBDL޿O|g,u)C#p\9X?I&;o;1t]fH%/_`&L׵s6lrcErŸʁuAlݝհzpƚ SD5wPן0۩alUU(PaȹD\ P U\1<n:_"h*fENEL*6bΉ]8zw]DhM(`޹::*$ qoowI`&-(j)(Vi:8z4W*O=^BH9;;0NS9%G`"95C1o&9n3lqUl8 70#bC_0x&4iH?Co#mH), t6qJ)9qL)M~^i9B2q5Mf͌v?wXÞvȏE;?\.:C!jL(eֵt>ʙϖoOO4C([N薏tˇ^ua8IYMλv?|mc ~|w=_<~{TOʈ$xD/=M~1U`ŀةRڧ;f NϻK%שk?Mj h9|K)5̨-`&\.f6MQUU4[odۨ/wvvVufַ $׷}Rh/6#BPl6sՄ'P۶-brILĈ3L}UT8ˠ0N PN͒w8fdBY?Qd~r#4y&xWyOVr|O]\\\\]_yǵy7n&xz}}o0<α n?_\RL&T15|cAIT]DNU^Wq &"E8vd6ْu[_\Pt6\}J΁Z) O1qޜ͈ sVҲՔ:S\"IX)Y4#jҨ#R9R%x x]+ L$؄Ʊcjr۴1G"DP-TDA䃟86v`0Mn-9 ;} STJpdV=Mx{{3NcRmז4x.*w;麶m^j엟^^>{ŗCvrɈb4 _>>૯.'o~fjb DsW]ךoq)t:l̨j>ʚ kiAYD!J& 8w|?PS) wݼq]@bkOo?8?3Q|W^OGOrֹ#0L_g3 )ZV<J"MB9׶AaB|F3P\ V @m۶u8k[5t.+АEΙXbrY6t}rCӵqRr!X.{/%ǘr.P-P./_W_~r{n @I *T1t-amV')Z"MSJYSIRJ6:`γcVLP5!reg^yM~s>矤(30%dUpWl>_.=s/f' MҢǧx,~}}GuޑCB0(ZBgMT4nU/RΥ T&·'DZ#51֡0\EVٹ;ιZsq95jt1kA*O)iwj4 AF`VJaOh(L.Ϻ&)g3A $2qa]CH?9gA#Ԙ>Hqos.qlo4򳏯.wuٔ4|ZjB cv@v% IDAT'7%LpsC5,iCq+nw{-UDtTԠVqww#7- ޛ%oGþG)1£K כmJ98 :C8993$˷BmWUoon7.od6/]nljpxs5w쐻;FH;v )T$rFBr2Ƈ٬+ms.21K1!R۶m HRJ|&rIB!RӀ1Rl=~8Ms L%$a7ͺ=W򾦒y O1!$V6mI NUg]ۅW^n'3e$c7N{f-*XZ kI1vxk4ny6 U$h-|s9rD4w,,4S~f?YrRO-BvSJs1N)MsVƖwlz)1)M)MT:_W<GrXj\3VhU1,mRJUJ-}A-zI)|яuWo +,V١ 3;7lyPHդ~jrK{b2x߱JAԄ)P wF8 { @ɐ;]ln sl>@b>1U3Aucߩ|,ن>ijqQi[$Bqw}o41-ٙl.fMͻ5&"ˏ۾KLyXZNDtZy|󱔓ndc/Rs'CRs;ܞHQU",*\!rS00SD'j.Ef6Hsj0 Ęr"dFm6fgg!~RI8^on1~Zig)ItyfMd}uӳ)FMC\ҐwM3(@ 9ǘJќ՘ ucϬۏj'"ԄжjT  $Iзj_S$vκ(0!3! CCU!&0i1"s novm"\^n*æ 9E !j9<fG|X퓩!Bu b9J\ y`g*".3{/^uiSr􉠫gS%D}7#>T*o"E*bJ)g)!+YF}p6lVTj\gm;͇]ߴɼy%UG?"Wގ((*9R5b,E2@cK>o;1g>|_JG/?Z,:$@ӳ3x9cziBrJUgy$2-I>rkC,}@dD9NEaAY-,')~ HD1&׆bv*Yru}*jt8wEqO;U0 i1("1NP>ۇI"BeˆBUM/sXJ25Ɖ|c (T lwkkZ^s&iϝTdY],%`>~=zڟٟ^~U +ʹ:rRfBs~noUpmN伟ڮkEMN䜬`-V' ɹT yS(&9sy;PD$4KjFrMh;zE712LcLӬQ#}'BvۛsvΣ)W})xrd\,b|y>&a/V4#f0uLcOt{)֮<[?a̛@ Ĝs9N0RvMYDm>g_~%"婰_VLbFPPU"dvf$yg"H XWfJKUZ_sjZJ7}TwL뀄=k"JJt+ӪWM1i!m/s˃> j~㳳'OҺ$A")@T3(ÁNf6OV'8hGRLEP_R Ռ3)Y])RJJ56/P`SEew)fB2x0[<(!H-S8֥:ŭDZ>'$߶Ĩz(FfFTL|Ŕq:S|h.r1ei 4~0OD ydQŲH0e7|wHv0oOOomQBBe#]Аx\yF&pP[H&`Pl蕇Ų8f{?駟}e1!C8} Qxc pQӴjycsOWn>dzO 1/Z=| hjV䍟vxPժ" UUtCM"T80cYRLv:+M19R93жm]*̤mm)9WNWYtWW<",uu8dx`;k=V$wMӘ₼,5031P N^}[RV9m&f2v2f8BYɪU<ח\4MbJ)DGH%Mi}Yrʭwb[\r.VG"?$dgOMWHѦ>sӑ}|rTԌȊf*&!r~G2Xuz')cīEXQ0<94MmDPEuZvuoV|>BPvJbUjA"!jLT r:<.pLmcRf)"%JTl?MPQi/U^W!TR1D TC"K)WFKC |6Gi N1Ʃ("HBU7|Ņi$;zƣhՈ :{suF1FԀ200S]UրB653euTuǎSŏoj/-KY4:6iTuֶ`a|771^.Nfe/W;?{iA{n)9@8N~׆3&Q@VKghprq;׮N@\WϾ09%f+鳧\lE9rND^}ރG77EХ 2K)m"Akѫi0fnD`iJUƱ}hsמ \oX2dEA1;ߠy?cv1_o·"?wƓ7zD4fԗV]p +^]]Mף ۶E$RPq)djHV}>sMA:|VǴBfF"ɥ9U@k |wp8Y)nr!&03G 13\.S6brȒ Q۪Y jdU> cKfEPh51,1fáwDt*yXDXzĨ1prr4!4HγgC c&44|JIg/jPEJꫯ\_>bL4r`c}BL K؏j-|{l K&dDf'|Y,%Z57ww78朜ۏ_~}Jo޿>kΧԦ)i]5@Q UŗZ49Otot@`.x*jy,LSE0BrYgzT! }/*vl\,ϊjɩ&C;_vS>XIyB1 %;x*]׮N9WɬClQH6m*8I1َKРd/'u,:<[M)LPI)&b"}?I$k`uT! x/EJ)ƨEZrҔ @RSSUt$SD5*"!xc^^\!"#͈L4=K޳YItJb63MA#գ%pX/nЬ=X"k2돜"bɚ;wL4 J)"cج9eH:jb)e|XYtj˒85M@vCrNPέ])1"8]c4#?{Ǜ)21w\3c| $4ErȤ`X{&.P@$9vټX Z\dEG*b*9@W@V,{߰iK7{?NT j:4=4TpdE:*T.!'[?8?52cQ wO>d۪j1I&fnr4|㗫Uby}f/aIqYv= 8};lsN9"hZU%8Lٳ''0mwv~r˯ahvիba!y7%?4\ A%Et)n˛3Ӣ)&siS)dm>~ۯ_ H$ Ap)V5$hVu_ D@D{(B.&{v}YosvΌ7mlJDt ~>~"}n 9 &jVJ9bg'àYn^<shOu:f&6u'^彥4G) *䤈V(%AQ04'|yfY b>>9v"K|7 4Ynj^ wf9F\Wهl$U0Sx AjfR}7uwE:ejcfb)0cƱhKJR42"Ei82\Jdff!KfjdLEUlpGa qh}Q h0'̫ӒgBQ{oHP 1fͥd3썁EKΒ|$CP br)E3FN?ٲ+Kkˡ4 &PiB/ԃ4J/Ґ6a7۠h)]6ea{GDETឳʝC $5^]mH%2e"K&,\1։j)h AaŐPW#ѝ!&dbǘq5iYHI;8@YsVDN|Vu*9L4]T@oK/ LASΨ\56+P96'z*LX,j CɳݶYuh<~r8=`f;8\L8uUնQ?|n6_޺VՏ/n[UX>7ӟ^<HT~$>x/?7^ɢuX/򟺋'@:"ۺF6UUm+*D@49+K)#r)+)@mL*t񣯫ml6Tm@8IV V&F|2ZqL)fYtRo}/ϟΟn#3Wb9:s{茙bէ40[! 0FA3u[UӪ>Ϗ?Al&;0 RNHSf) " 1ϚLGJ{ A3n2QkxT޼"YvR r)1(";^3!2vnqb ؚ Z FXf^rd1d 4saF˫ +9腑wԌ1-Մfh}rNls6'|铔} mG!3CAEEkڮ^w_|uzoӋ=٬GǷ8lmݴumUɟ_?;:?oFȦu/|Ry7m ?_OU{ɾ?qL~%ew/^>ʤ,\S[5hѭn3D\pp{z1ƹ U%P$gT6:zOXѕ5saTL[|I]TcuV>OLO>`:_gI:w rDDJXM5o߽묋1?^]9|e Ǝaj:՛U./MT)7[Bzݵ)osJ]}-S&RfcL-7R4]w?)k$p\I0ָCt: *_]ʹ qq@I6NSu.z T$liUi|WA\RH|iƘb**&nӫM,Rv6(Vn}kkࣛO,lB;=8_L+oګx?^t6L&ESFج֟w1$%?"[H' e\ETULE ÷+Cx;wn'Ͼr)+U뺜YsqyilCG lFfR5jj2ov8oC1Zgqգo[TUqLc >X'Td{ɹ5# jm[@U@BÒST.r})W>|٧%=6zt-G:>?zCP7m|q,np{l\-/D/4$荩jݑq 891}G*;m;y珿z1cw~ _OX+DhgOڶ)|$$!P%R9)uwiWnkS4/MmC8]feZǪ@`KTvEEvm!IЃ f+I|=HCD(rp}bBζjb}Jf*w)"Ys|t촠Y6m > IMTԒ䜩J awyH٨Ȃ޽v'O826UHX^6/6_͢i3ۏa5ēF/$w_ɝ)'skf '533]o| #ZۘP{poT.[%G) ޛoo߸1,&q޹KL1Q sdTIfb~k T O}|zV&ʹ֧|͵Mmw7mk]%m;]v5{{GLM㻓GC@-l؜wm+bLY&ǁl6OaL1(Q̼mcJBq)QzkZF$K(N_z2bbf̆@nv1Vۍ%礪*eM &f4L57uI װLΒTwR')%U+4\VTv;D2l39mc9"MLzjL儘/NiZTz;n4ejk@ Io lvqvrR59sojmgx~to1Y̧S0lBM'u3YT\+ZEaM8Q`4Usl64H5͟ɟ2*g8PU parUL糪i4eII+b0UڶgIT֐JؘRCΉK@L9$m >(5V{a ,@ EU"6gbv|o6[XWͦjJFLDR,P~mK |Bj L ! $˳_/D*!%r!m{_~3+SW^zg?7wTǓv S4Y}f #j9oKlJs.71l]@ p@%i6ݤ+l>oaLJU1ޏA+ztotųS38>~{ɣ'/>;ێU51Pߞ|EJ|oA>wV5qĺ \.~|_~^vuspt~CUAU@集8G)'9e80^LgϟQ3*Dc)eQdf(}121Wz^]@U2H2lk E@TH[?7>2Z߆n眉p] \Bvq99ĘΚlsKC &D"$$YۮOg{dzHLef].-br Q"(rUO^{;w1^\\Zw7_/߫+n趖g~bGփ_ VjMir:A!Kc"곾姏{Q%Q%hjvZlj$3w>/>Yd/~9&ID8U "x/%QJ MktoRSYg_,]d dPf.a]1yw?m٠FT[,ꪪ1ʹA*WW>R;$$D֔@]x:,߻{ۮc"ifvxx'{h>mm]YflƘJdj&M;wW׿0iLLd,[pO]uSt ~,f~<6 dF4HEO>eϟ>%J&nV\OOOju\%>Yx6f$gNZgA@GC؅%SJ)\.CfWU[P19c-JLF͝[Qݦfhj IZnCb )pOA ^b,hzXo~ow.fW:.ٓ?8犇i&<:{VˤȮiRͦzw쩱{gج/T("?^N%ɚS"ƐI$ut|Յ I@$Ǭ;[7z&6K.}M^6R {\W7-D$4BA#4=d!-K[]a#팽Đ1ehj@趽d{]c6MVK 0 "kst7ɇ#l 10 C!;D09G?:BCq>f>sLVÞs2ĂL\D9rΙ=cx{Y]?$bV{mfǮ۪>9=UCәEcC̭p奮]Mh(/ܺ@vy BU]{:V?0wz|4hFϾz8> yEMR7SG1ގQe]z- "20\X^T$ŐcαbSɖ!1\$mBn7-㛺-UQIΛD; !AyJ1Kkd:;8ݶӇvۥ{fe&Po%Ȼ%Y*2X\Lrruiɒw-,P,{Č\}̣Q8}?)eWUDrTfش{/t=999Y7)IݶbƇ `e8SU C"%edt sPW6 VqIJĘ֫UyҊ3B]?1 $7qRQJ^r?ܬ.כێCOqԜ% !^|r@d-W#e˕$%TέllT` (($?G_5ŞkyUD$CNlST4jQKr&K*2Adzb* )eQSm;<=# :cdHeڮ~yȶxmI ZCwj  %]E  C8yO~Sj/>Opt0{U=&)zQ(>R70^wpp{?kZas:L^1JG_H 2;k]%.w Dȁ*/˽}:urb̅c\7ڰs^k&U"*圅]1[DE#;}/> 7LBo/e&I$1tolZ^{ mL .#SfVqR \n,JըǁQ^||絳ӓgϞ~)&@{/0tϞ<81IS8 UXkv]Q!%ZQ@Æ9I@H@d*Ő$zBEPK Fо%v2˫>QBc10~*ES0n%$"`S?⬝N[>kLZkdf1Li?lGwsNĜ姟}k"F18k652 b]m60"軔rUե3brqL&9b/>B2H2&c)#Ħm|"ς9x$w*i,;t;\9G_?}GGx~wdZm/g?4 vZٟtӚ#)cڶIf;W?=yLLf{ָ>gCf@sLV]7q<7[?ӿ8{;y~y>N!\k! 3)" #D/1YL@T?Nv ~E fy]W*vSu}N)Mgs[qz}`|Uue+WUUVaF!>3T @,jD"AmS7M'7fmHkȪ!\^tǓiܤo~Ǐ00WyTa1✣a21{٤mk6IJm*RTp(˱;˩)Ҏ꽷+)Fd&me{tt_G'm)FForh,s)uwj+,e: QqV@SLї[G{CCzCL IL9\2}.12,(rŇ3*)JJp*"7lXv4,0s o~{?=_\ur|eܴgԔr#E\UUU%sru=cPEDS&9[9&;Y3 *)xdq`1hmBe!H$IΠ`Sdc)l1[M)y>n}rL6uP|?t"2_Tcm;Qճնp2Se& )O  Ty곇 Ia1-iTW"a7tH8LR)egѫ2NfG`jjZmnF?Tu̼^X_od1ƚ( *hx:fJ2Tm>5 }0j֤UUAT+U"<!%MysֱI2g_?zx9mw=;Z-W~ָUbҜ%A$tf^J{c2? 8 ;9}f[xC(*w_xwg{/Lr볳qs!ÕAX(E? !bFf h!}oY B*YR%葵x0n MӴDQA {D%ªr;P2FE2 Sa5v26m=c1)}JaE$s NrB|ݸjj S }kpn1Gdۇ>,f{r+f+sh2Y$ 9h,ID5v+%l *zõ@JRo6q  ۤ* vs,SNN X?LxSVDa#1f3 WgziֵIryUPSU-2-8]>f*YBHI2 Rcdkyͪ USwJi\U l}ru>mZeRz?n6+h'EG_@7}#RR bv5U0ذ1 X|q b/d1Y)g򃕈"сR\]4 *Hs'66~j.gEXV)%l&KFE@PC/~> ރ5 )fψ>)idQ=Kd1Vh.-ίFr#TZG9ɤfE|2kI_ d%M91Ri$1Db3)rCQDo?ɣO';d[۷_zӋɳa cR 1e ɰqwRa:cm)8~4LZʪzz߮S%eQ@ۯۦ_'xF_XY?}>y,tԜ "dBDcZr" jQ΂kOΌ#1Rӈ/1W nؘ~^@s,%+@؂vjjUκ gzUR$ro2E i̕GH 4wί ~ͲD圗W)TSX?^,8fno9͠HTQ}cUU11+3k q4|S'Y/c.R$5n[/O/+dU")ĆXu)"RQj|Rטo@;9uغVUSGz)c*sYp粱D ,I9KQn' RNY2 H+Hf<9p !@:98bbq1=4+ !$>=9k4ܰc82sJhsJq\]χ|/s/kta&B2 WW! 9fbS!D 9daRNXnZ>y$,)E?l.)j.7=3 a\o7Wzd4Hu(b!SMɹJTQ ye*Ümw{L9+uEr1pow IDAT\(%"ꌳlJp7V%8m]6v "⤭YrtѭYe")w@InҕVUdHV<}H7l5UͫKCwb*T$]t}\oz@b &"ɚs oޫS-+Gg[Gj'{OO/>ż|puRk4DAr XdT%* ӗezψ໲*uSO*ũjDPv4UcCY&iW0tY4wrPmflB#:c.dn5)š0c")&YRc]9SAXk1V2EPTr պlSf몺n<3g "JٕݛAL)rM~|3tݭ_ィfI>0M1sT^r!ir )7m)S,DƔ&`7)Ly.$@50t]B: s1Џ~򲒂/;[fC{_g̈(>,BL5Y`#7G.> ΙmZ"ɚE !a*jΊP`cDRM92ɍ$ιv"яa׫ )Uqar;k1$/*2Y[m.ECzoW7J)k)YcʁOB,"U&CfI)3Y!R(*dJKD@UPv{(ZMd !^ؤSQC%)BJvi;0 ag7%)|>xEۚ6ȺYjM68P0DcKQwLڶ,lrJ8\Ž[[e֋/_gOSߙ$n91ۜRxYR 1$T5D[sV۴822ڦ]ϐE/ 32KyJ8'CsFTv*dFe6jlhdC|(1Yg0E}/M[FdX3FCuF{16v>:9\KyoVOsm0<~?,[EacB1DIyv۳!~iDu6ۮ-s&rcr-OGNT6GLq:TޏL77vD!)oCTns7-r U800%YF2QE@wHlU | E$bPL9 I),w"H r*^R  h1F̈,GIsA@T4 "HڦE,& q!6QDe1$%Eh\[ @Z82  `\3U\ ~jf2d`˦>E YQBa$A"rY{ދ~Z.WWcJ: Yk +2Md E20Cwv AdÆSNY 8Mg3Tͩ3XPT6 ].> zᯑJJ EU 4N^+ qѰr[d*AN*`+caZw۵uF3TtP̮E@el28. "z4"fcd6¼Q S% Ę+0%bBd6mD!jI JcL8Dǁ]ڮWJqs洣4"2va[-B$LȀ5"t\^!'Gȳvkoom߯H-!(4ƹ|/bɤD.ˊtzpp;~DZHqx沑PXokfcV3V "EPfLwn󳋘la(=ɦ޼L'U.Oo/f:궲uqu}ڜB2!buh 4Ssƻ ":WdZHH2ۦYSՖ2W HX%y@ ࿖IYhL;L QQ VZQ˙Nb `Y.Dæ8tBͪ9ݼ.X;Ll-9YUUgmWՐa=߅5Ĕr4%>)Z)-ig3\qH@*k4al9[U8'@`wxbPVUi3@uUoJI$ 0Ɛb)!O]ץ䭣q(„vv܎!Hʚ@6'OwחW}ǏdHa u#d**Bi../pw)ecufYWM+W/>D7i/l>1߹uvÇ;Ev \| KTE-Loa2jQߡBaoB)+ITf=W7ıΗ(sڏv^\lueknNmb.omUokqCUt;r&%K$eIzrm Æ ^9@$NnKVjJ8]PP\9I.ϭUT39ޞٌC߶gf**1p}1%pA5 ,*`H7[d$Ƴy?R$ 7",!mcTy~_jgOpht٣3)93nX ͣ Y ajH g]DCƃ3B016wgݹ}G !T#ڑAaKYIvvs]WH``hm6OsS!FB0]"ĝQBYB$s9'e$R]vz},f'exVًǟ_Wl\4OOOpSVg&%`f?(X۶Ӑ $^QHȷ/#jj"fû'՗qJ.F^ZpG%UGr Qjr\n=W/_hՌcxn+"!ݯ/?2-i{|//.NMšM8'm|!amj?9Wn|Wŀqmų)R@kݺݠu'w_1 Hm gRf}~bhŷ'|~1fdŃdUt~-=~6r~r ~z;rLmvqfryRAe:f}/w OOwO)oF2oC>~!HFۆG 4YV1RmZ]v|1ᰟu󳳋q2SfI85t|>?=9wۛN) #AӤmMT+-ƘBhj*%gS؋$0eIdN'07C~{\Ncncs"6/tX.Md/mwso79zy~CKBZ s&51 1A gD31w3E5C~?U|y9)`.b 6*hACD pOf@PSLE RL11 BMOz@Ų\A}35R5r!4Uk*4*\FE$Wq3$U37)YD74'"L`*GT0F"fc.Chի 1HFB09qu v)YClLKqMHD#3Vs ZBmfP7Gf?j5_Z0p^O=1:nt~~WWWMEq>K'_>kə~FϞ<Q _?803a? ڻϞGyJb4)z~ψFTL&p`q\k4m?rf-6 _T[щׂCrwJ뽄M؈/y%8o_|ɷݷZZ,? a8S 8-><mX!gOarb6HT% fv~ӟ ?Ku{#N|~o_;=xm?> ${^{?|9w)iqvvzrV./<"ٹj|Q`Cay1]n6SK ~Dqʥ/LSJHgsͺyMEC)cFѶkٝTͰdd՝5%Oha䦕NcT0J2MnC@"sPpS22j ̌4@` q m7Sm9&L벎vaCWk[C(R6 A EMT ""2" Sr(8ӬH~y?22YtJ))ꗿ7Oy:*<LD91;Q<\ԥi$7?-O>/77Mq4F uX`*5;S+2֏hwzh8N?O=[ {\)=ǁ]Zf@.9瀦Pߋ'm%98|~A3Ҭ !nD+-ʋgF( Ga>{z]du5_Y.O6勗ՙ"٬K |-5 ԴdQ@ E̼LQYC޾x;M|q_~O6!P@W_{|]=̓}痛#7Oqd^1r<99I)^>ʌTM5E G("Ac8 b3 4^^]514kkm;l9kit1 o;~9 1:h1CWMV_%Qv?rlnn.)%5+wBTtIL2uڣiDwcGKɪ\R2@ED$Dx?{Bc$fU !rfjb*Ťbuc38*/8~"H!3zI{9hΒ'܉kVa̙a sqOSecԶRM"" 8  iu6/ޚVe\m ,kX[v)xۈfT7G-&plic~9GJYjRv큐k!!Bj?iMݖ/}?䜫H]TL"#96gvw&"6WT7&B+z}Y_q`675 H@DwĪ.WO:$1s̈bn...`{HXEͰ1H莣Ԇi6yp2aԜ~ӳsāDJ@Ow~swyddRλLSui/ƩnMl9_0lٙ#3j PUa񰽑a闏w?W|ퟜ{oŝa,nwgfy쳏㬹ƛEB?.аM*RklrhM-:J:93Sqyc ~㈦5x  xP7~l5곟aƑ)Ej׻Az:6 !bsS)pؗLܶ*5TSC@Fyr=~kqɯ~*e|rr6Yp(yZ?}q#jØ} xarߏZ}0)"f|8n6[]fw/^&sCcR4ErWp10"44ܔib)cU\cCðbbzപv XDaYa`jp@@ЋhsI9AlR&Wŭ"T92SDd#}\_J)UCGD)!ցR"BnBJRcR?ͪs.t(9-\ FPwl^h-r`2 p m;w>~0j tp1tCwDW)rD,*PTЎ^Gq; z+RᕞPOX]c'̰\rJd\zJ͂Lz˺0* )W }0nUl۳ _^\>xﭷdI~[8i)cai9s1WGȂ6pca2rֶ# :SY_^\o~_w/YӜ?NEr{}yz1ǟ~oZko~;0Nfwl77ǿ ŝn?7%6<MJ݅ )&BnoDͻicV iiLļjW "׻1p1ǀLSJ"2<}| QTXȨ)&10>Lcb{OALf>C?N4bC`UR25I!gwuݼCm/wĤо}?<۝;_l~~!Oz:KFbEU+C7T!Cw'fs*,Y%V IH)VhoND*ujGTߞqE$RԴ뺶m$8T> !Bbh> eKDε)DnˮMjE'ɒG(2YB]M`)`_RDt|L#K1< IDATMRg@fs.H.2I*uS?3Q!2*vbuQR08\o )Ĕ!RI8ckE0@f.jHC:RV#]꧵2Ǧ9YͰe716q_̓6Zq{X'??ܬm9AZgW@WzR!C<̘tW/p:཯~'Oݹ6Mywj9 yMb<Cl;23^|T@HC 96픇ak_/9kbOLՋ0y/>h^]9+E?}J㋗_~ɧXz:L;/me2&@WL7W/vm5!p}~Zm'?[G9l@k/|6kYJS29(UwZD 3s፵n</n@5f \ۥ MʁT4sJ x154s Q8JX];(2jdۊ9fN7!Vߡj NpSrU~7L@T V7@Ed^XZ5?ǽl 80_fMLaEjLC8zEUnfVurjdLCis@lHZL0nږBH0e@1p)Auxj%mZ;B! 阸݌8r3@ر 8r:IywZcSͦ) ]M"6i 9ir})#r>rU_07\7P}%!8&6 ~ѓO^opmDGov*2rc/Ńڶcr7!&X4+Ŧ+D$0X۶L:)S*?ON!gWa?NWL;{Ir)tih[NQJRuǏdؖ!8U+`m<"mZn/3zŀj㡷[uQyIi/?wڄHkU:᱉$BHmAE% f)&[:x\okAx]?{'fm2D)RO7z#q*RBjOO/C0X[,C22Lx84p"2kjiLU}܌8 ﴌw@Bѕ{ 5S Sn."RB+E%Ruy8Ƹ3bpBDD[Wb S1Sl##x:9!~)%3[Dmh6QTLcXR^;H# Zu4a0+3[ 8U#: 1E;#08XI-KiRSzrsZP1):JatReTX8Z3Đbds??;cϟ= 1uMB& DSڶ~gZ#@uWëV,j*\\0ڶ35)b{rh1$S|'V3xg";nVkEDNϒYqP"({owOg'6W?K 7]_/Ww!GFε?8uݼ \U=aW?1?^>ţ$u 7{x/_fd*qRT 5)cZVb1U3"8]ۈW6y Y_ص7WOx8#4m ИK .#Xa27_},b*YD@RUK2cL͝cZW)5 tZQTE)R CmqLY5O1 *X,Rie,\0݈𘙓b,ЄL1|1?;'b\.wۛ6_<:\nIK1Cb1*ź7lf-89{ݛN_\*:eؤ$ ˗W᰹Z-H-kR2a)D2唸mK5 )6l`herBD݋a_חWkD0"xLjPgȄ*x?Z43C8ŮfQDlBgm^\EȞc־> ^tTx3qau̹ 8&G' PG8J)u.XSLT厱 D8WB5w"6G3#s+Q H,\^n=80oqo`cm\!u aBvjdVDU츮`pm5MۦEcr/?cBbqm/z_uY^oGBBAMUi>A?LYZ+wԴzB@Xy?9wZCsͷ޺s6m{2fmo}ճeY;?|/'?D~z1HH_j7O?E77_}K-b"O_ǿ]<{ޤf:PrNĽ!?, 䈎4fkG__oy=p__$슗ME[ +ĻĽ RB 7pQ-R:c*r-Տ3~eR͘s-nhNnckk _q:pP9}rv㨋YLHB16)iRzXk6]7[.atvqEQqm uMḻ ۃuܹsO޾c)-@AU372G8ƱNZy!y\E LJd@ #3)bu"h, Fu0:m1s35m1qRJL c,Hjv+s`,sN15ؤhf5,FHpkMu]L8|?Rc8 (vS"\TUkl'0U3`E R.+>Hlp>C}W7Ua&d@HO0,OwNVw/NSEJ dD|D1;ǿ|K3w @\U׷:V333 q*cݘ/gjbh_zn8~'͚.$ØE!gIͬNBpKR\.7[ $5]uR3@ 0wwYbC1ۮmfwsn40謯-^ ps ~+SV"m7nM۞fM !p ۣQHliyrBm~kR8T0"2呻Ue"V\f_oyLQܻOrOr.'/)Զ\C,g秫 fr~z%~7|ud]:0i3 GυC37{]28Z.a jG "Y`4qȢTJ9"" )<:s''sWWUMP2-SL5஦~8J%O"ȁ0@`zPR^Q-q#+RJfMlfkߺۏLUFƮuufbDbyJqVmKn5L^F P&fnI$:h[l84[bBDu'W|F %QbĄLĜR{}gDJ0ԏ~Ϟ>81NTff&~)P@SCfDGGA7w(3%2!5mT!c.jG`#c CRӵqmͺ[N'W'6j-2hF"8lzKRwyw/EjOe xm@:f9*K\ٽR9HyWՍ&D)̗ͤ0NHe)!@ s LD5{XB2).o/>]]2WIq/W+-SFVHCeځ>h<S!"6[\,nͮ^>?춛MCtG@P@bnŽ|nF)%'K-h6hHnw)O@,EK5Spu)MYNN!ěԴ'勗ϟ?ƌDͼ99]!R-VmON痗>?fT6_h|pL&ů?G~ݣ>[_|?^?Wi9r (e @T$W赙Q`sWE"!a)6NSX朑HE!r mzyR 眉IN|E"ss=H)ZnՏZ9忡8B[8C?qEe:Mϻ4/tàfue`&GI8s "uLJD}XEzROTrzEg33dGZ楚KYn֗ě>܊ъ)2"d$"!qu(޹swur2kc ့Jsss/O/D :9LG"LT䁐 Y E$bD!ܥ!;vwD)FB@,"BjxT`X6Sr\fvs@42b"Cݔ,*o|7_{}]翺z~HXDez5; fV z=SFRRC ̨ܼ9,+ nw%PyPdw#{OH`]$lpӼxt,Jʓ 1W܀\eZnU= [ԗ qVVaTW/88}S^ܽCxAtݘc$"JŽ/Ws] L!&Ff65ElEEQlRUsaMe?NVp6vvqrMEcjvl6# zllgms0p7T@\[lT5`i̬il4DA2R9sԘ9RU@G=VI=KQ3k3A~yz N Bc&՝4#prĺpGB Tgny.Џǔ(@<Ai<vTu87!buS۵ƩP3khSJͪ$].ZBRci)l6E7;ʙ隆ơ?;=aI|g5?BL,Rp\_ݘi6"7כյNܽwg?WWE qFPYe^#O`t d8fx0ttTRKM i1aۭ80C}RMf*G*jn4fj?:4 %޽sqvzW_2yKT*ڏ?dɩ'$#RCMuۡ#Ǧmb2+4: b;_^??[,0kUéH)bD\ `ZNϖ'2!8˿AMEmo^MJuONN9rǜ!3swU%|^gՁ31B S꽻}sΈ4 34M9w#P8WS?ݼRSuN2IiREn>o[+L%B۶ݬ#m48cs&z [ 8)$$ 1N8匈0 ~rSBFdJ1YΙB uTCmrlp4McJsumĉl6 !yva=&0眷!ߺC(SZ!p 1.4ԙky{rzJ! |!"\M(aѼumS cR%@H]\ ҵRsΓ9lHѮL|uqfdYК6s'ẅȡ.-^J*! _ jD@]ٕc w<^kۍT>dlﵾq]嗷\ػ]IekyܺﻯfmU==/Oïxy~*nnO./?t4fy ?Y2=2d>ĈB`1Hc `ZaBǶo//u%x\2p[m׎6M W\jq5#RK˲Yxa{|-RYeN'f:J)MLZ1 Ě1<^kMhݷm֚I.Rk͔};4y)H=-WotC\Z۹쌤bȧRnnߔܾg|~V߸\΅f9\L,? ԴH9/3d*0QZ UJ-S1i*)Z#kqrw/OW\=@-ۮz ^~m۶3x\M]n; ̋͛K^/?޿} IDAT›{/Ͽ/}1l€q@t!<3{&#{_i?1ؔh*"jF)pH =: Yn~/~a%;i9Yz`O{ +ٷ=_ytBLJ_C#0{[o =X.=|-K`[z`i>yoHjtuE_|;B(R,T:MT^0(L:0#A)wReD-cPiWwS9UfP6Z;EYwWݷ÷wo7_/ϗChr{!Aj32Mȑ<50G[i^[pٷ痗moY>[(LUwo<~߭뺮kMGG0s?rY]]݇i؞~xbۛy0ֹiۚ2Ͻ%{O[oӲxc${p:,PҾ";#Q1f:D*L;zhlLpg] yוN ޼vTS͏>;op)w Ku_ϥD;- LOn?|*|Tky>oo绫*R@R y.Tu-HiLm   8_2]/ӂ@py??!EΛ!@8W}wFbmwUͮT5moM7`II R| S㼓;@C(33DY0Bu_u`zy+3? ~|\l-u,¸fO)N_-fj+13Y岆;#2AbC#x Cυ 0km!mH,3A@o{3Af^j3@.m{~"yA$ wp߶S׈/KXhT*n?=?>^\Ow'l L;EYm?dBpSnt^/ﵠmߴi]EfO'Ɏ]dY1BD~ٺ+W,P&J:M0RKZi׾M!qBT}[b@Z8]=bo;D aav3fvYwx~|\3򲭽0"YԷwpUue (|8T9i#f$ĬB"\bں7px~Puu7hm/U1moZF  BWS܄|\׬{8Dx<@< C5M7~< ![!oflmW}"(Utusٷ7_׵msV͒?t6Yͼ0GАd(Kw?o~xzWw7R_֭ZaK#f!3Bb&12q|z*dwH ! {uZ^Ã{kkM1Xڌs:"q$HDPp4L"B'ZK|7-^4#`hz'w')k[_wʥҶ79ǽ͉?<].m@0kFL݃i[몑)r*="TcTZ9zõw7nB`$ w &Ȅ#eʘ{fuWp$Y.C۶;x`HpnDva,H%af~ۯ[2Q(6@f2sHGqT{DG??|+d^^^ZPXᱫ+ >C*l8!F o~=S%XD0@z|NB9$~И6҄"0aDLa=-x^fd4??o ͙OOl[9g4+ A\*__Mf Sgܷ-׎7o"ww?_~eYUTw"ݧ軏{i9mlZjA]]lx@rHdIP2ɂWU<ڔi"81Y>L=y1,cg6!HA!%ъ{KȠu9]zF#:|_gG[-1Nd(KDt}QUu0:3*$Dd涭 TS,R"A )۶Rdga1- yU%S- iӺwcF=U: "#X$ÊoI"}{yhZV//e)SOTkOL tSlK?>aG#Oem 42&z,w `"pYbX}j̅L,{sSHm-<̀0bu##!<{WӞ. /=auw1a"BКnmkw/ 'TWDF>87ሶHˮjDrR}ݒT%Q: <}/}VLJ*L8Po=O Xn{kavL򦉃N'M7vU P~w^3ѻVJE5wwEiD#L=K&9!jZN #2i vwn?zŏxܟ?^^ִuZRtNZ ^{͏+mOeR\TraO>t7?wȪݑ!"ceɢ`8_.Yԝؑ`ӽ#Z{4LT 銑lNӑ4|4\޺1{#;Rpu.%f5ӅhgoC-#D|8Y,gtjWDr^5HL>~?7,[ش3sUT0}ki*-{[Nj),L <\V))%zTu,zGyZiZU-7~Rh^xYTH&^=dqWA733d0T&AqP AJ"+ l;D }Rj1!*Rdw{r~7gaw߷v3C&;D2!{^}(Ytm184Jo #ȲR;dIim_v9ח޿\У2CQ͇!%#QDc9!BB]8!uW >u11 4^oS5MQ=Pn)D C{&Bd rO2"tYa@̲c6K| )fBfrEium]X¶sdYVzڑQ_?m0U HCF&DG9TrPoZlcGxwPsBJc{xR@ΫwA m/܆Ǹq.fH4FFyS+3@8!X8H"T"DVaѮS9+"4{sSjoZsEL "")ٮO䵔M 劙cD!w$F6iBVV4Rf7CD7?( bLddIgiU@8::|"dZ*1IA,,hJP] D( f ɱ"dڡ}O":Qk;f zoQ0}̿;f$p`ty#)fQMՕ—i^!^@)s)CLbgJAŐUEdQ%aU?ݕ)@D8ff)5߬޽w@hMy`W4fGL(yYמ2CD&LwF&a#6b# ch;937#e ˳8Zk}$BAE< :#w$C3W<?Qt:_><DQk!e)GP 1sE#ynG #"SH˲ CY!~Ir^lGm򄄄`Ms.p59G4qOb_xNDby>XZk "D8|,3gs4kv q7KX8Z)3?!(ADx=?~WO&o4`BOh'K TyXOmbefwػwZצfGudPDΗUe(H1\+":hF{$@#C;M7HCD!HDD@L,l„#2T֋=>=e_`~"{̥|vkXD%OLRKPD`XA$& x=_F_0!初b:g?%F ~bLl=(3̜>#PF( cnCS&HQk /rV9Ӵ2!yczc/>giMam5 }Dz -%bl xus nzYW]u; ԎjD! `FCGZqw{'tyfhqL/fa-r^Ia@x7/8I{Z8Of$$7ge+rz>??=Թx+ 0L}NOZ8@ZADBWe3\)__1MS-5$!Fob.8|) rL6hD&u |t`2]4H2U<"@xiepRX$ ,"w i7wjwD 3ˎqc^fĞp <> !d"Jk3L#A~pBd`zބa :0#apg xzp<@5/,0QM,C"19=T r6>(cF0# s!k>0S{ 3;;vm.E2<>>4퉆#@*" ZʊgFNmONt;Y=r(D`.^E_w"r~\D !2ꡱޕ$tdxW v<"J'C-,Dk @ an!Bd \=~ĥ<<"H`2u7@=QH"ĂEƓ&3!g7$.f  pHw>kD)'cDZuWeCS@}>a9ls?G₈s|8* (0=|m: \ Z(£?hs*z:36( 'pt9(ji: f-R\ aqbTrJ4+sv!i}/8aq 9Y [<#`[IcY8`r Y22XfbdObĽb4o<Ț$w8=0LHDL<7` wflJ@H-HES=" RSkIG A@-B\F0Ʈ-Ѵ!b8)nrۻ. 3D(ۢ} oo`$0wOȌEpg gH d> p 2ƀH ʔU008g9fCFa CaDb͇,2h pd@H^B0 K 4P#w czTQ3+,L - ƥCk]RLw0@7t}X愈"%142R+ @/P"u m{z_|w>MKwݭ:"X%2сh\-n#8]no+bpՎusAӆ/,R/[z~ٿ>b4Pu)DB}Ƀ2m`B BT JfDPۮ()mBbw'Rfv3@b$Rn~}sb޵mppIɉpdkm1n??G\1f:9`f[Rl ,>try1HzbEƖ1 Lyq)Y8 YNNB";BQ Bde2sHd|`90F1ѪZݣZ14&p 0 Z0S5<}p+emRKt{s{sso7uDy4LL\JTޚg?џ׿>M][kT;!D K$lxszYwZC$;)"mR؆df7b!dVsZJ}?'nӟTGW?,W?2}߼<~:Nr S;bv]/ yCMz(j55>OWo޾|l ͬTZyCU]H|9<8'&3f )1T;ABM3;vvmS,yOQ .KdՎZĐ[Ǐ"7˂]{rA@˽p&ml!=D.Yǃx&CB@ȹEJ82#!/$${(xp&(|=ZN'7t?ّs$'WF:) RE"R-uU ̜ylPv̄hm/~0:BLEejMK"y?yYjADO0p  Bw`f#@G^zݠu^^] <[,,];fpB%2fA*AfitHL%ZSE{2ƘF Ep)¥pԁ,RfR#kFnJ8b)+M̝0@ԃ&L&!?0&f#p$@H$,8d|ͦbygSM,\v[ćr"bj XU[y 0D$LdKd kYd @qOe;99ܩ K*2-0A퉀 7" wlYR֝2Zw)들7z=.DjX zQxKz{y;;:&Nݸ?؇L?B6ֶ2!j._ L}F.gZ0"H!vSMd0y#1xʻiF $=o:ZV7j:0@1SaMwbLb'h@:;0pmbS ̛̣IA2|:@ٝɺ#0=f(9 f ;(q}cGxDLQZ%HPP$19G[!c`#G6Lp"znPIrTdgVۦn%ݤ-^_f@nQ,L葖aO WM~DE?P>ޖ g6|Dm]ںNӼyGvyDD/WXXm bE=0'E|lf˲ISF`JF'|bߕ2HHH % 0ɩ!$=_"1LD PkE so=ܣ="@9qfyCʗQ/ zRu.XRl餀!"T=_Y7i!DYUU"ԞSx40,8Ex8C8lfZ׈%fk[\O;[[cPOE@ #  E <@^?ǖ@[4 1SbSoV Axo', i1\1ȅd5 Y PV,{xAr*h95.Dte5DirՁzmevBg~AĵV д%z$n}h$SQϼG"]Zn-/uV;"s@pWwbnT/)zHᾞOF"N Hu]|M"s@0) 菔^6` )|͛7ǻgðN@(gܵa"rd E/xI CsPUU1\8{S'&tonމ~_k]j<|:2g?޽$ZwH"9W=#:!+ Fe6xBB` `jziU"DpzwIj9c$L ;|: z`e\V8~^Dz9M|pBR(ZUgB&jZt~@g , IDATAY) =e0~N3oxϿt`!FkFx~fp8 HZZs <ϝȒzJ;+N{B7`4ZCbz]3T{I}uGye;G t- yox{T!mDlfū%ik)[>naP?̏ǣLevqOYnn[ٌ*x]})꺦J#<[9oF$ /#qj%1|rZU};}Vyj{'xtf-[."jBZ35+u];a?7K]5HÿRH1ΔcjY/˹y,Dķ9q !$ky2걻|C8"o{[Dvu'_ڙMa3ÍQ<3]\yZ牙T[)X\[k*2=yݛw&cQ렺a:x;t6lYXJqоgؗW߼=Ìg曕x-B*$x/be_|)JpJz,Oց¥7Pvk~Yt_?uao}Go^H`Q2/+{#k B)5ӄD;;(k)c8۷XVc,s4c35Zm ~q?#]9'aD1SeeLW]l~ nY =f~v飰jd!2c@4q|/w\q(@F {iiWa'޳w\>uK]7w4<mIGؽb\-!6>x?]~w9SΣv;Me\Td0QxЈ B?-e9/˚TA~B*i]Jܚfu~ ,E)5Z )t037c*CsW~Me~ 7hf!,}a"` !st:][w9:,/g_}64g/qKwx:GSq HG"3ޱ`mYNnA tDx#@v-QdҶF8e#O:!餦UoqY Yj0b$KaSPĈͷH-K~@1Fn(ju㭹D d̠Q4F ݞ32q9=cɖ =0x![j+C|NLyv[f"3sHn]aݥYN,9hyn%lKrej8b.@O_<7dv7׷nokˈMt|nNOumuMK`/QRj][ke _D$JT&H6l="㚈\\枞Slw pGW_}dGnow%j#F,,-S9u)ss`Cww3bʔ$a"Փ,.TmŬ;I|̙!Mˢ77@s!&#DZ'@""@-< "QJlE>\!E+ݹVWF4ʠUm < ep;H)<\dZ\b4dld0v=_~[1svإc*FN~{gT v n^Ax 4[,]v(){Ep5{kU[V4K 82Rl#M5 :4]wS}'ӴMk]{5~בUaD01&ѳ\b^,ͬM'ADgnB|#j ,6e{>.3{+!"*v[铀MoC1r挐 ɧ9Uʁy9C౪׵G0 S2cdM G5y+N )`p]YzQjKxQ.@$HV}X<(ȠJ\_?w?9w>{g嗟ݼz7a1S)=j3T ."Ġ0aXׅrM5{-Ps]oo$ H+ZYȈΏ 6}O^J k?\0Bw=-~^.@Dx)8M7Xr8~DČI Or0* A7A*0!1_T^kfiYx, n#>ByWyu*oYGdIHjݚ.޽<#q>Χ.v05H)b)4c}{s|JZu]43ayɳlkDx~89dR1}m;fj_/zצG{ܭo޾zJMM瀐jzNDՖ1OT+L0uz7~H,mD$uS/H/3Wps3ݐõ麴R O10<Pvf_MD^_ɧQkHV-B8Mj$y/@$ 3uYjJfY"Bn&ڬUfv}O4 9f^'ތ G(1I #i3Q=}5Um"ҌŪ"51^yU2sڈ<"և\ToK)4"o mZ0aAb*BkLtͣP϶%5 e9..̬)88<[}|yn={omQIU-[|>vmHDLL%4cfM5I WO=o_x?/#BS"<̤)PUm&(`mpev1vH}ͽL+3˺Z;PMkr?ۂzV{' d#%0/"Y>&iGvb&JjmǫM)3e5yv6mt>뺤 ce g< |zD}Zd::<jU&@i3m1 Y&>I=ykzu_dUz'|ȳ_2 Qs0Sx0>D<ӤCc2gDѷmVLVjN׉k]E ;"c>0\h X޼y3ϻ ŸU;I&-ٚ5W d &ܒ(~f:} Epfi <jL F2Yz4jNy2LGo"R@nT-9Y8 !'TKMJPyb8uUGGJMRŜru]=Ck.b|-KDbSmcHyf̅D0[mU-T3O:@&bYߖ.0LP v{?׻7j.u8ΪSƅjD>i5F[HߍG?_|q>v쌟O&܅(PvE~G--AGﱫ"bFRFAcry/\zL^5gn7fzԠ=6UD= YJZ՛WoEPL=~tu")p46ݦ$t߄QJ٦Nw7Sy~ .p6KeG1~󼶷;.ܞ<bU}{sӒvߓLgfm/_~U..wWLwSvZO^<;C7AfID̶EӼ_)aW!9QV?W6[׾qCGKNwLSlG$Ēk bRf"J]xJ;O˺vZr\{O}CJf:>df"P(!pYNK=jԲzpXoW`!ym9}K_j)d!ܳFQ3@kZcEwrAqxO$L'=Ya|]})J$bKG"aUZyp{nۺa.HtonO޿7k8Me]צ)/RCdٶ)G9feiw! 0fȜ2vp5>ҘbfS)iܐ"!G\67\\ge `C‡UY$yM˯޼;!^ʺأǏwNM8i1Z>_|źѹj=gq&Ru)/LU/_0ޜ.铹HC*K瀛[ K2~ן~z 2}u] zb;%* ^nLR"%"饊q`޶õ@`eY ڟy5.tn>)u$akR8秾i4sZi7/oc:y߸8"w߾Y΋iipgD)'\WGɕiGeooeVs=0grΟ8$yr!bߜuW꺾}x|Ժ. j[jWJ̘fzMC0'u]ff9۰Lx_}&'I&z5a'&VD2P֌x3X^fv^ T)j܌n:`.pm0K$80aa C, -czAa{ O=,=HP g۳z}{/ IDATs6ikm,#ZpkC0H! 943f+Rdͫ# ,//vG,|{℻8( -Gn9a3iyvk;B,U;`f"6y.Ku3,@6qy~{{&Tb&*Fu~pOEuo}ׯ߼LU,9ۤj8M _|7nLt{~-'r"6-efID:$7ZDY!4)%2E -@ hK 3Ոx<òŎ5RL; {1 O"`F@AOR#΍E=i3Karq=•b-#i>y @",Dpb8~?KLuF7=FL,@嫓lj~vx{|_GO>[kU3@JH|lMs_%džBND1&B8~ٝV=isiؐ24Y%y*I,tLv|zJnU5*EWzldX. |T-mMӍ /+RDW,A.I374Lo!FIEA.퉐X8kRZU^\IS~G.@fHv^*ryuEӾ C {՚1q'bU!32[N/"O׏5m YriJDy.{ x2x_\^ssA$jPDtD.% %p)MuLCAkхd0Ms0>;޿o/߱7"xKe.ץ/BfmEÞYڗ_~Z?_?;޽7)Ax[ASJawd ZE3"(utynP:Wb;KUiԪv$HHa?Op9S,գGGOd≈$av:zv/~/ۿ{y~7?K)~Ӕ(,< |^y/Č1"jyFb%iI2MDdY!Ydf#02p,1֕#؃<<;b&pkL Pv;g!f@y]02>-Kj^'D9101;鰟XHB ݂I\i#n",tyqwK]?#˥NpV /He;S)pZ@!\>H7,1gj<)ryZg si.R)](Ih{uQ ̿S@)Qѵ>FHcsѣ̐Pr^ԙ<]f @Ii!@y oSc 2AkgYB9G=_0G@w `Ddi m-'ɴ~/ dfd.wZ%4> !<Ԍla*%"Zk+ԸGsr~Nrt"v4U |ZY(!{{?|m3dsiYiCL z_|ݻ7o./D6U0(P 7n}~~O_zz*l-j_2w5|^ks5a+1"nǼ?ݼ;>e9 keۣO)Snnon??~ zfLo5_96I$-"%T()JQUj??Gx`e%JH2mN׬t s0ߵ d; !(ˍ[.]UW1Dǣ Ø1n?zT2>(pëB>"l ,&fbdQ/@A+LU1gWy5bL>Oxò[r tʢ؂KK"K?aΦSvqJ9~g϶̄̐޿̟}Bvn\ 2jپyЃc#eCWk'g1sL9O<9ko)ٷ#D^t/n{|$C?>ИbKKw  !pJiwyG/_wpifɼpCI?kwlDk-O\p*J! ØWwg߃/ÐGտ/ZNGn0y.]Np'IaefMELť1/y|veo|{~{35&&GyK5QQFRQ"P[m:\cJӼ+rjHrg?|>7ᧇqIDKOd4ڒRSC̓Jn Z֊'rsX)w>|{ǟ7<jv{uʣ!BXWD4MZ4"(ѭW38~O_O~x^x~<<@M1̄XOnL;Bzt8_ۧ`Rx?/.7[qƸT~ҼU$"G!~N:x jƩA˯eyzd,>~`yEE߲!Q(sQdftGq2V.0& VVeLCNTkծĄ<O!Ѱޤ} >ˋ|7tUn}],,U5uORND>K8'rz=^{o_PL~~YOu [ 0qIͭnƁ]b@HNݸMGbTSUX?xo羿-U69xӧnB" B]-z345f Z3c^!l;gw~i8;ØSϿz2w/_~)FЖotvypl7S|'}o3:mq/}}6!d=W0qAn磈o~B~ߴZirۗyrAtT[#fT{ss^X@ &od&?͇՟Gl{ hGmD̈) 5 b\ 㠂(b*`jLƁjUA%4F0"<|v珿|ٯ&~82X hb@h, ,!pk 1@-j@HfBSCde[" 8nW`-!O@5pӖ4CvyEʼn>?WOynٶ11z mӾ--SS}p#,Qe Z sO1o}'}Ԛ^cJ^QqqId '1&^9NAv܅> EZ)HkJz-uiᒣ1і&΢7UĻq{P'01@<7ym͙'W/Mb1Yhjq!߾O)f7%Ff#N:UeLػ\]]KBy޼~n7׵;(@P0iWD  yE R kԦֻ̤5f1p8~_4M AL[DvuZ굂Zo"ԊWT `Vk͹oz?OeWO~^="ju}ۢi2.ڥCzox`eqqyxnm.jR \8KMC_Lh(ګHst FNS)9zz]d PkM,OF\e`5U|Jx7lvf&u~ =$q#X$ZpZ_1Ê@>o=!m&.vSJ!xscH_Bv~RKvL7]xJ q>ILTzmE[DK҇EDnokd|ݼ;MNw/?b&C)wV[+hF]޵wM3j{ح&=AR).p7"d+,j}vkjtl"YɊ MBzB8I隁t@1bS"45ᘙ RU;ԆLEҤ!j}Pf2`t ѽ{bLu}xq\!Jv #0!` NG }>_FL8 HGpuz׮"o䐬'OR+X)s-8IJA~PlZشm Q ORnz5"`o] ٽaޥ֢**noTEݻx02jjcf5ZU_ Z{p $@aVƌY[:*n]8hQ[ uk"8O bD!`#D8DĘD#tl]NH$ޥCw<м~ZRL! 8Gxz9Ơ*܏cM- @br{NXLϝ?ۼ@ue5 2947&t`-}]CgBo.o"6]Zks47PA);n!DHUm*f()0V7b 1Ðr .](0!R2͓]C "<ZXLG_LGKEԌZ& j10STS44fo6!F!.)8QOp.}>z^Y~ǐTKҼCH$&K-z-k<}'0ڲ;Vd1\}Q"";JdT,c-J1. P"ZSxn]w?QӟDedPS54.wѺztLDnrBb8;/郈4uo0 S(K1UCpXȭJEzj$Ԁ, :q)&FnN3[^N֙My FMi&"`b 4sA]9j‘㡽]f f&1ðB^+=:;?e~s3 Zk*.69)f>Lښlb%&CC0WOZ0bK4P . 1pl 3{)~*ڇDM,췄&lIUm4 R /-.u_F]DcJ)Z3޻tQ 1'_5a}~4bSّw;ܯS{մae-d f `&tUCKn7rH1J/~q8nׯ2Yf4$0pO1zS@wBdDSڥ1HQjƈ3 .3wO7)Fv]G~^  L>Ĭj.Ra`B%/"PP50+HJ|}"8s?QUS? K΅ JIDAT'Rڂk+e# Jy1No we&< jz7wZ썯F9'8a Hz9'a-*;>^yn/ TA0RHK|f'Jш L@9IenTD9!PZqsSk!D"v_Z-_5*V߹yۏ?>_꭬q{ownCṙ9%)ώ(a17&M)0J`[tTUxp6'![ "84D:&1@l'%TJ&"[LR齉 PdbqT^B @A8&1z_TN)i67Ů%B=MjHIJfMs TD%J~;+驞P]C \*qjXv6#!>O PW)=*ZW5@R,C65U (F,UOy Lv 0|񤵲]V 5{<81ŜnOuFĜ1Pk g"1&BL,ZtU,He X906ٴs1]]ʙdHj`BuA5' 2TW$Ea<$(XRq0\__Za[ b``AQ @dnu$i Sjq!OV" kr*Lo֛t9N'p(SHuR Jf'1\u^P)ZѹG "":ʾ"Kփ\o_v&ip9ǼybG$ϒ<(ROy|"q@"wꪝK'D0ښ61R ]b|r  n CRRAӢغ2GZID1fSXf1[hB" 18Z_ץְYUh54Uq.\mK%b kUTœ|xD5z?~!"R  n@ǹM1$jI1 :7^%1$v+)P̪}ϟ|ɷvixUEG\s-D`<.j5eۅݿ?<øÙn7_a M}d@5S[M Czwovq 2;yZwm]̀f((DUI@K1x˛ {:eAҭ`!}7 X_ D([m 2dWmZsJLko'*KԤ(Z*sҐ< ?GRf&Ą`{<&dptu4mݿ]gv"9G"Zﵵ6ϓŜhV!pbd6 Һǜ"RJ= ~́lA$0MZT6zc]v{8$w{LDr!ĔN$9 Z_=z8V!{/O_dMj-U(n,,i߿1>7}zo~s{no_qqRቹz}}S_ןwa\mZZ׈W7w[knxnz qBׯ֚/]^HiL)2E!Eq7g;si PVJ˼y <|rh5D[M*sfk :VĊ8\Se:NHUlføF.Ϡ!$%Dx /&RP':uYw3 ܷ gu}Do=3֛&<;[#!RK#j13{J&ILRA[07uT;+kE"uWI\4eYt⸴>JoRr{x" 7oѩbhEjv?kCZjomHmQnv'Eڥ~9CaҒ0UEb& &pzk!jR ;$d0"qbZr y0)n40c A'Z`٘n7g9N\ե/HS5ďqߙ" ziHԈD|Z+ur8a5rUn=kvk&1| !TD DT2ܤZ͚H!|BDL84^o}}Moca:k-`9,N CRN ـt3og?WΥb8N)m.'?zf;/O_~OOb>ݥZZWǹow[0^^<{^[&tfr D#*.RO9Q)3cNTuy#_#CUі6'#2[((f@z^z~zvv';8QMHN,8xDDSoT 9$JoF.xjobv̤RNHYx#C1Q$l 1Dij-73c@BSh"-`] k3.j!ȆUbRFb=栋 " Y@ֻO""4 Hƣ;&zcZ]NY \|8j !j= n{ÄRN]ZbTP4t )WgD.ef1 aޕuRr"Pas-sbgWĘCDK) yͳr`BXZ=4?xnF)DE@0cb 9b2O51:k{VcdܮnW77_>ذ,/^\9S兡v[(?|^ ZNͷ`F!'&Ds03@DSN%TF:Գ8Yd5)ۛWt!jvtU|90~ @$c ƼS60/gZ+!v5U .G E-0TTE%BT̶za6׭vDe&I2;@̻u)"w, |aIJFۦflV1,urESqv L9SSqpH3Tе̪Jb3TSb̞7UN`9%$b?5A`8i],!l#B&&Q"ŘG!l]RZg3q.jkfs&"e.|ʴJa +∄Ri{@4MVUx<P[n!g"PL)-: "!`!p 4 g-e݆)ޫAEt: B9z2*MEk>Rc.|hn 6oᶵz&SC aȈs$Ø!t'iÐjZ]\H-?ijR .KB4zLRP[Cʷ7nwo;3qVJ9w[7c2 ־uU `SJdBB><wqv^ɱ|B tp #ci23~<bJ1b"Rv!Ra\M8!EF41!Gm6jJ[R)vts%]H1q\ TBiX< Ū(qh#ZoT^P$0c¦yCm&ȩ 9!*G (BTDbK!g Cň8,&Lb!:?OJ9 èB/ Wc^k E!%̙[3[4P8BL)EU(4X]rȣ&& 2|aO' Z̙9qtW"źvZYEjE!ZK?sb vxEջwGVf7~l+s40Jq ))6ЛbT4l[U5C#@AZ!ƘK"t5p լC)DM31Se&&@vXo4fZ;&q\:u:@5sri0a!Mn{#jE <x5.enl\@1w&i! qbLf}cyXMksF6*'/q#֚:.|1,["FR8O`jn@ a9s b"JHBJ Pyk^jOﭔ90lJC$cd1* 5Ejc.Hb ZK!&S}>AA Cڠq C.7]H8L1Zk^[ebo8h> r)ϝݣs= Ik*ڻ(hy}ľ)M[]v2age8xVJK&sqKOi#Ҟ504E5D Sw{Q5a`,`|it!JjBT4  * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ch341a-cfi-device.h" #include "fu-ch341a-device.h" struct _FuCh341aCfiDevice { FuCfiDevice parent_instance; }; G_DEFINE_TYPE(FuCh341aCfiDevice, fu_ch341a_cfi_device, FU_TYPE_CFI_DEVICE) #define CH341A_PAYLOAD_SIZE 0x1A static gboolean fu_ch341a_cfi_device_chip_select(FuCfiDevice *self, gboolean value, GError **error) { FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); return fu_ch341a_device_chip_select(proxy, value, error); } typedef struct { guint8 mask; guint8 value; } FuCh341aCfiDeviceHelper; static gboolean fu_ch341a_cfi_device_wait_for_status_cb(FuDevice *device, gpointer user_data, GError **error) { FuCh341aCfiDeviceHelper *helper = (FuCh341aCfiDeviceHelper *)user_data; FuCh341aCfiDevice *self = FU_CH341A_CFI_DEVICE(device); FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(device)); guint8 buf[2] = {0x0}; g_autoptr(FuDeviceLocker) cslocker = NULL; /* enable chip */ cslocker = fu_cfi_device_chip_select_locker_new(FU_CFI_DEVICE(self), error); if (cslocker == NULL) return FALSE; if (!fu_cfi_device_get_cmd(FU_CFI_DEVICE(self), FU_CFI_DEVICE_CMD_READ_STATUS, &buf[0], error)) return FALSE; if (!fu_ch341a_device_spi_transfer(proxy, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to want to status: "); return FALSE; } if ((buf[0x1] & helper->mask) != helper->value) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "wanted 0x%x, got 0x%x", helper->value, buf[0x1] & helper->mask); return FALSE; } /* success */ return TRUE; } static gboolean fu_ch341a_cfi_device_wait_for_status(FuCh341aCfiDevice *self, guint8 mask, guint8 value, guint count, guint delay, GError **error) { FuCh341aCfiDeviceHelper helper = {.mask = mask, .value = value}; return fu_device_retry_full(FU_DEVICE(self), fu_ch341a_cfi_device_wait_for_status_cb, count, delay, &helper, error); } static gboolean fu_ch341a_cfi_device_read_jedec(FuCh341aCfiDevice *self, GError **error) { FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); guint8 buf[CH341A_PAYLOAD_SIZE] = {0x9F}; g_autoptr(FuDeviceLocker) cslocker = NULL; g_autoptr(GString) flash_id = g_string_new(NULL); /* enable chip */ cslocker = fu_cfi_device_chip_select_locker_new(FU_CFI_DEVICE(self), error); if (cslocker == NULL) return FALSE; /* read JEDEC ID */ if (!fu_ch341a_device_spi_transfer(proxy, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to request JEDEC ID: "); return FALSE; } if (buf[1] == 0x0 && buf[2] == 0x0 && buf[3] == 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flash ID non-valid, got 0x000000"); return FALSE; } if (buf[1] == 0xFF && buf[2] == 0xFF && buf[3] == 0xFF) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device not detected, flash ID 0x%02X%02X%02X", buf[1], buf[2], buf[3]); return FALSE; } g_string_append_printf(flash_id, "%02X", buf[1]); g_string_append_printf(flash_id, "%02X", buf[2]); g_string_append_printf(flash_id, "%02X", buf[3]); fu_cfi_device_set_flash_id(FU_CFI_DEVICE(self), flash_id->str); /* success */ return TRUE; } static gboolean fu_ch341a_cfi_device_setup(FuDevice *device, GError **error) { FuCh341aCfiDevice *self = FU_CH341A_CFI_DEVICE(device); /* setup SPI chip */ if (!fu_ch341a_cfi_device_read_jedec(self, error)) return FALSE; /* this is a generic SPI chip */ fu_device_add_instance_id(device, "SPI"); fu_device_add_vendor_id(device, "SPI:*"); /* FuCfiDevice->setup */ return FU_DEVICE_CLASS(fu_ch341a_cfi_device_parent_class)->setup(device, error); } static gboolean fu_ch341a_cfi_device_write_enable(FuCh341aCfiDevice *self, GError **error) { FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); guint8 buf[1] = {0x0}; g_autoptr(FuDeviceLocker) cslocker = NULL; /* write enable */ if (!fu_cfi_device_get_cmd(FU_CFI_DEVICE(self), FU_CFI_DEVICE_CMD_WRITE_EN, &buf[0], error)) return FALSE; cslocker = fu_cfi_device_chip_select_locker_new(FU_CFI_DEVICE(self), error); if (cslocker == NULL) return FALSE; if (!fu_ch341a_device_spi_transfer(proxy, buf, sizeof(buf), error)) return FALSE; if (!fu_device_locker_close(cslocker, error)) return FALSE; /* check that WEL is now set */ return fu_ch341a_cfi_device_wait_for_status(self, 0b10, 0b10, 10, 5, error); } static gboolean fu_ch341a_cfi_device_chip_erase(FuCh341aCfiDevice *self, GError **error) { FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); guint8 buf[] = {0x0}; g_autoptr(FuDeviceLocker) cslocker = NULL; /* enable chip */ cslocker = fu_cfi_device_chip_select_locker_new(FU_CFI_DEVICE(self), error); if (cslocker == NULL) return FALSE; /* erase */ if (!fu_cfi_device_get_cmd(FU_CFI_DEVICE(self), FU_CFI_DEVICE_CMD_CHIP_ERASE, &buf[0], error)) return FALSE; if (!fu_ch341a_device_spi_transfer(proxy, buf, sizeof(buf), error)) return FALSE; if (!fu_device_locker_close(cslocker, error)) return FALSE; /* poll Read Status register BUSY */ return fu_ch341a_cfi_device_wait_for_status(self, 0b1, 0b0, 100, 500, error); } static gboolean fu_ch341a_cfi_device_write_page(FuCh341aCfiDevice *self, FuChunk *page, GError **error) { FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); guint8 buf[4] = {0x0}; g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(FuDeviceLocker) cslocker = NULL; g_autoptr(GBytes) page_blob = fu_chunk_get_bytes(page); if (!fu_ch341a_cfi_device_write_enable(self, error)) return FALSE; cslocker = fu_cfi_device_chip_select_locker_new(FU_CFI_DEVICE(self), error); if (cslocker == NULL) return FALSE; /* cmd, then 24 bit starting address */ fu_memwrite_uint32(buf, fu_chunk_get_address(page), G_BIG_ENDIAN); if (!fu_cfi_device_get_cmd(FU_CFI_DEVICE(self), FU_CFI_DEVICE_CMD_PAGE_PROG, &buf[0], error)) return FALSE; if (!fu_ch341a_device_spi_transfer(proxy, buf, sizeof(buf), error)) return FALSE; /* send data */ chunks = fu_chunk_array_new_from_bytes(page_blob, 0x0, CH341A_PAYLOAD_SIZE); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); guint8 buf2[CH341A_PAYLOAD_SIZE] = {0x0}; if (!fu_memcpy_safe(buf2, sizeof(buf2), 0x0, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; if (!fu_ch341a_device_spi_transfer(proxy, buf2, fu_chunk_get_data_sz(chk), error)) return FALSE; } if (!fu_device_locker_close(cslocker, error)) return FALSE; /* poll Read Status register BUSY */ return fu_ch341a_cfi_device_wait_for_status(self, 0b1, 0b0, 100, 50, error); } static gboolean fu_ch341a_cfi_device_write_pages(FuCh341aCfiDevice *self, FuChunkArray *pages, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(pages)); for (guint i = 0; i < fu_chunk_array_length(pages); i++) { g_autoptr(FuChunk) page = fu_chunk_array_index(pages, i); if (!fu_ch341a_cfi_device_write_page(self, page, error)) return FALSE; fu_progress_step_done(progress); } /* success */ return TRUE; } static GBytes * fu_ch341a_cfi_device_read_firmware(FuCh341aCfiDevice *self, gsize bufsz, FuProgress *progress, GError **error) { FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); guint8 buf[CH341A_PAYLOAD_SIZE] = {0x0}; g_autoptr(FuDeviceLocker) cslocker = NULL; g_autoptr(GByteArray) blob = g_byte_array_new(); g_autoptr(GPtrArray) chunks = NULL; /* enable chip */ cslocker = fu_cfi_device_chip_select_locker_new(FU_CFI_DEVICE(self), error); if (cslocker == NULL) return NULL; /* read each block */ chunks = fu_chunk_array_new(NULL, bufsz + 0x4, 0x0, 0x0, CH341A_PAYLOAD_SIZE); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); /* cmd, then 24 bit starting address */ fu_memwrite_uint32(buf, 0x0, G_BIG_ENDIAN); if (!fu_cfi_device_get_cmd(FU_CFI_DEVICE(self), FU_CFI_DEVICE_CMD_READ_DATA, &buf[0], error)) return NULL; for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); /* the first package has cmd and address info */ if (!fu_ch341a_device_spi_transfer(proxy, buf, sizeof(buf), error)) return NULL; if (i == 0) { g_byte_array_append(blob, buf + 0x4, fu_chunk_get_data_sz(chk) - 0x4); } else { g_byte_array_append(blob, buf + 0x0, fu_chunk_get_data_sz(chk)); } /* done */ fu_progress_step_done(progress); } /* success */ return g_bytes_new(blob->data, blob->len); } static gboolean fu_ch341a_cfi_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuCh341aCfiDevice *self = FU_CH341A_CFI_DEVICE(device); FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) fw_verify = NULL; g_autoptr(FuChunkArray) pages = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* open programmer */ locker = fu_device_locker_new(proxy, error); if (locker == NULL) return FALSE; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 33, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 44, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 35, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* erase */ if (!fu_ch341a_cfi_device_write_enable(self, error)) { g_prefix_error(error, "failed to enable writes: "); return FALSE; } if (!fu_ch341a_cfi_device_chip_erase(self, error)) { g_prefix_error(error, "failed to erase: "); return FALSE; } fu_progress_step_done(progress); /* write each block */ pages = fu_chunk_array_new_from_bytes(fw, 0x0, fu_cfi_device_get_page_size(FU_CFI_DEVICE(self))); if (!fu_ch341a_cfi_device_write_pages(self, pages, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write pages: "); return FALSE; } fu_progress_step_done(progress); /* verify each block */ fw_verify = fu_ch341a_cfi_device_read_firmware(self, g_bytes_get_size(fw), fu_progress_get_child(progress), error); if (fw_verify == NULL) { g_prefix_error(error, "failed to verify blocks: "); return FALSE; } if (!fu_bytes_compare(fw, fw_verify, error)) return FALSE; fu_progress_step_done(progress); /* success! */ return TRUE; } static GBytes * fu_ch341a_cfi_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuCh341aCfiDevice *self = FU_CH341A_CFI_DEVICE(device); FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); gsize bufsz = fu_device_get_firmware_size_max(device); g_autoptr(FuDeviceLocker) locker = NULL; /* open programmer */ locker = fu_device_locker_new(proxy, error); if (locker == NULL) return NULL; /* sanity check */ if (bufsz == 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "device firmware size not set"); return NULL; } return fu_ch341a_cfi_device_read_firmware(self, bufsz, progress, error); } static void fu_ch341a_cfi_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_ch341a_cfi_device_init(FuCh341aCfiDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "org.jedec.cfi"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); } static void fu_ch341a_cfi_device_class_init(FuCh341aCfiDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); FuCfiDeviceClass *klass_cfi = FU_CFI_DEVICE_CLASS(klass); klass_cfi->chip_select = fu_ch341a_cfi_device_chip_select; klass_device->setup = fu_ch341a_cfi_device_setup; klass_device->write_firmware = fu_ch341a_cfi_device_write_firmware; klass_device->dump_firmware = fu_ch341a_cfi_device_dump_firmware; klass_device->set_progress = fu_ch341a_cfi_device_set_progress; } fwupd-1.9.16/plugins/ch341a/fu-ch341a-cfi-device.h000066400000000000000000000004721460375044200211510ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CH341A_CFI_DEVICE (fu_ch341a_cfi_device_get_type()) G_DECLARE_FINAL_TYPE(FuCh341aCfiDevice, fu_ch341a_cfi_device, FU, CH341A_CFI_DEVICE, FuCfiDevice) fwupd-1.9.16/plugins/ch341a/fu-ch341a-device.c000066400000000000000000000163741460375044200204150ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ch341a-cfi-device.h" #include "fu-ch341a-device.h" struct _FuCh341aDevice { FuUsbDevice parent_instance; guint8 speed; }; G_DEFINE_TYPE(FuCh341aDevice, fu_ch341a_device, FU_TYPE_USB_DEVICE) #define CH341A_USB_TIMEOUT 1000 #define CH341A_EP_OUT 0x02 /* host to device (write) */ #define CH341A_EP_IN 0x82 /* device to host (read) */ #define CH341A_EP_SIZE 0x20 #define CH341A_CMD_SET_OUTPUT 0xA1 #define CH341A_CMD_IO_ADDR 0xA2 #define CH341A_CMD_PRINT_OUT 0xA3 #define CH341A_CMD_SPI_STREAM 0xA8 #define CH341A_CMD_SIO_STREAM 0xA9 #define CH341A_CMD_I2C_STREAM 0xAA #define CH341A_CMD_UIO_STREAM 0xAB #define CH341A_CMD_I2C_STM_START 0x74 #define CH341A_CMD_I2C_STM_STOP 0x75 #define CH341A_CMD_I2C_STM_OUT 0x80 #define CH341A_CMD_I2C_STM_IN 0xC0 #define CH341A_CMD_I2C_STM_SET 0x60 #define CH341A_CMD_I2C_STM_US 0x40 #define CH341A_CMD_I2C_STM_MS 0x50 #define CH341A_CMD_I2C_STM_DLY 0x0F #define CH341A_CMD_I2C_STM_END 0x00 #define CH341A_CMD_UIO_STM_IN 0x00 #define CH341A_CMD_UIO_STM_DIR 0x40 #define CH341A_CMD_UIO_STM_OUT 0x80 #define CH341A_CMD_UIO_STM_US 0xC0 #define CH341A_CMD_UIO_STM_END 0x20 #define CH341A_STM_I2C_SPEED_LOW 0x00 #define CH341A_STM_I2C_SPEED_STANDARD 0x01 #define CH341A_STM_I2C_SPEED_FAST 0x02 #define CH341A_STM_I2C_SPEED_HIGH 0x03 #define CH341A_STM_SPI_MODUS_STANDARD 0x00 #define CH341A_STM_SPI_MODUS_DOUBLE 0x04 #define CH341A_STM_SPI_ENDIAN_BIG 0x0 #define CH341A_STM_SPI_ENDIAN_LITTLE 0x80 static const gchar * fu_ch341a_device_speed_to_string(guint8 speed) { if (speed == CH341A_STM_I2C_SPEED_LOW) return "20kHz"; if (speed == CH341A_STM_I2C_SPEED_STANDARD) return "100kHz"; if (speed == CH341A_STM_I2C_SPEED_FAST) return "400kHz"; if (speed == CH341A_STM_I2C_SPEED_HIGH) return "750kHz"; if (speed == (CH341A_STM_I2C_SPEED_LOW | CH341A_STM_SPI_MODUS_DOUBLE)) return "2*20kHz"; if (speed == (CH341A_STM_I2C_SPEED_STANDARD | CH341A_STM_SPI_MODUS_DOUBLE)) return "2*100kHz"; if (speed == (CH341A_STM_I2C_SPEED_FAST | CH341A_STM_SPI_MODUS_DOUBLE)) return "2*400kHz"; if (speed == (CH341A_STM_I2C_SPEED_HIGH | CH341A_STM_SPI_MODUS_DOUBLE)) return "2*750kHz"; return NULL; } static void fu_ch341a_device_to_string(FuDevice *device, guint idt, GString *str) { FuCh341aDevice *self = FU_CH341A_DEVICE(device); /* FuUsbDevice->to_string */ FU_DEVICE_CLASS(fu_ch341a_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "Speed", fu_ch341a_device_speed_to_string(self->speed)); } static gboolean fu_ch341a_device_write(FuCh341aDevice *self, guint8 *buf, gsize bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length = 0; /* debug */ fu_dump_raw(G_LOG_DOMAIN, "write", buf, bufsz); if (!g_usb_device_bulk_transfer(usb_device, CH341A_EP_OUT, buf, bufsz, &actual_length, CH341A_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to write 0x%x bytes: ", (guint)bufsz); return FALSE; } if (bufsz != actual_length) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "only wrote 0x%x of 0x%x", (guint)actual_length, (guint)bufsz); return FALSE; } /* success */ return TRUE; } static gboolean fu_ch341a_device_read(FuCh341aDevice *self, guint8 *buf, gsize bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length = 0; if (!g_usb_device_bulk_transfer(usb_device, CH341A_EP_IN, buf, bufsz, &actual_length, CH341A_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to read 0x%x bytes: ", (guint)bufsz); return FALSE; } if (bufsz != actual_length) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "only read 0x%x of 0x%x", (guint)actual_length, (guint)bufsz); return FALSE; } /* debug */ fu_dump_raw(G_LOG_DOMAIN, "read", buf, bufsz); /* success */ return TRUE; } /** * fu_ch341a_reverse_uint8: * @value: integer * * Calculates the reverse bit order for a single byte. * * Returns: the @value, reversed **/ static guint8 fu_ch341a_reverse_uint8(guint8 value) { guint8 tmp = 0; if (value & 0x01) tmp = 0x80; if (value & 0x02) tmp |= 0x40; if (value & 0x04) tmp |= 0x20; if (value & 0x08) tmp |= 0x10; if (value & 0x10) tmp |= 0x08; if (value & 0x20) tmp |= 0x04; if (value & 0x40) tmp |= 0x02; if (value & 0x80) tmp |= 0x01; return tmp; } gboolean fu_ch341a_device_spi_transfer(FuCh341aDevice *self, guint8 *buf, gsize bufsz, GError **error) { gsize buf2sz = bufsz + 1; g_autofree guint8 *buf2 = g_malloc0(buf2sz); /* requires LSB first */ buf2[0] = CH341A_CMD_SPI_STREAM; for (gsize i = 0; i < bufsz; i++) buf2[i + 1] = fu_ch341a_reverse_uint8(buf[i]); /* debug */ fu_dump_raw(G_LOG_DOMAIN, "SPIwrite", buf, bufsz); if (!fu_ch341a_device_write(self, buf2, buf2sz, error)) return FALSE; if (!fu_ch341a_device_read(self, buf, bufsz, error)) return FALSE; /* requires LSB first */ for (gsize i = 0; i < bufsz; i++) buf[i] = fu_ch341a_reverse_uint8(buf[i]); /* debug */ fu_dump_raw(G_LOG_DOMAIN, "SPIread", buf, bufsz); /* success */ return TRUE; } static gboolean fu_ch341a_device_configure_stream(FuCh341aDevice *self, GError **error) { guint8 buf[] = {CH341A_CMD_I2C_STREAM, CH341A_CMD_I2C_STM_SET | self->speed, CH341A_CMD_I2C_STM_END}; if (!fu_ch341a_device_write(self, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to configure stream: "); return FALSE; } /* success */ return TRUE; } gboolean fu_ch341a_device_chip_select(FuCh341aDevice *self, gboolean val, GError **error) { guint8 buf[] = { CH341A_CMD_UIO_STREAM, CH341A_CMD_UIO_STM_OUT | (val ? 0x36 : 0x37), /* CS* high, SCK=0, DOUBT*=1 */ CH341A_CMD_UIO_STM_DIR | (val ? 0x3F : 0x00), /* pin direction */ CH341A_CMD_UIO_STM_END, }; return fu_ch341a_device_write(self, buf, sizeof(buf), error); } static gboolean fu_ch341a_device_setup(FuDevice *device, GError **error) { FuCh341aDevice *self = FU_CH341A_DEVICE(device); g_autoptr(FuCh341aCfiDevice) cfi_device = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_ch341a_device_parent_class)->setup(device, error)) return FALSE; /* set speed */ if (!fu_ch341a_device_configure_stream(self, error)) return FALSE; /* setup SPI chip */ cfi_device = g_object_new(FU_TYPE_CH341A_CFI_DEVICE, "context", fu_device_get_context(FU_DEVICE(self)), "proxy", FU_DEVICE(self), "logical-id", "SPI", NULL); if (!fu_device_setup(FU_DEVICE(cfi_device), error)) return FALSE; fu_device_add_child(device, FU_DEVICE(cfi_device)); /* success */ return TRUE; } static void fu_ch341a_device_init(FuCh341aDevice *self) { self->speed = CH341A_STM_I2C_SPEED_STANDARD; fu_usb_device_add_interface(FU_USB_DEVICE(self), 0x0); fu_device_set_name(FU_DEVICE(self), "CH341A"); fu_device_set_vendor(FU_DEVICE(self), "WinChipHead"); } static void fu_ch341a_device_class_init(FuCh341aDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_ch341a_device_setup; klass_device->to_string = fu_ch341a_device_to_string; } fwupd-1.9.16/plugins/ch341a/fu-ch341a-device.h000066400000000000000000000007531460375044200204140ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CH341A_DEVICE (fu_ch341a_device_get_type()) G_DECLARE_FINAL_TYPE(FuCh341aDevice, fu_ch341a_device, FU, CH341A_DEVICE, FuUsbDevice) gboolean fu_ch341a_device_chip_select(FuCh341aDevice *self, gboolean val, GError **error); gboolean fu_ch341a_device_spi_transfer(FuCh341aDevice *self, guint8 *buf, gsize bufsz, GError **error); fwupd-1.9.16/plugins/ch341a/fu-ch341a-plugin.c000066400000000000000000000017171460375044200204470ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ch341a-device.h" #include "fu-ch341a-plugin.h" struct _FuCh341APlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuCh341APlugin, fu_ch341a_plugin, FU_TYPE_PLUGIN) static void fu_ch341a_plugin_init(FuCh341APlugin *self) { } static void fu_ch341a_plugin_object_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_set_name(plugin, "ch341a"); } static void fu_ch341a_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_CH341A_DEVICE); } static void fu_ch341a_plugin_class_init(FuCh341APluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->constructed = fu_ch341a_plugin_object_constructed; plugin_class->constructed = fu_ch341a_plugin_constructed; } fwupd-1.9.16/plugins/ch341a/fu-ch341a-plugin.h000066400000000000000000000003501460375044200204440ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuCh341APlugin, fu_ch341a_plugin, FU, CH341A_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/ch341a/lsusb.txt000066400000000000000000000045311460375044200173020ustar00rootroot00000000000000Bus 001 Device 124: ID 1a86:5512 QinHeng Electronics CH341 in EPP/MEM/I2C mode, EPP/I2C adapter Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 1.10 bDeviceClass 255 Vendor Specific Class bDeviceSubClass 0 bDeviceProtocol 2 bMaxPacketSize0 8 idVendor 0x1a86 QinHeng Electronics idProduct 0x5512 CH341 in EPP/MEM/I2C mode, EPP/I2C adapter bcdDevice 3.04 iManufacturer 0 iProduct 0 iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0027 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 96mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 3 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 1 bInterfaceProtocol 2 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 1 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/ch341a/meson.build000066400000000000000000000006621460375044200175540ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginCh341a"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('ch341a.quirk') plugin_builtins += static_library('fu_plugin_ch341a', sources: [ 'fu-ch341a-cfi-device.c', 'fu-ch341a-device.c', 'fu-ch341a-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/ch347/000077500000000000000000000000001460375044200152535ustar00rootroot00000000000000fwupd-1.9.16/plugins/ch347/README.md000066400000000000000000000017001460375044200165300ustar00rootroot00000000000000--- title: Plugin: CH347 --- ## Introduction The CH347 is an affordable SPI programmer. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob of unspecified format. This plugin supports the following protocol ID: - `org.jedec.cfi` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. - `USB\VID_1A86&PID_55DB` ## Update Behavior The device programs devices in raw mode, and can best be used with `fwupdtool`. To write an image, use `sudo fwupdtool --plugins ch347 install-blob firmware.bin` and to backup the contents of a SPI device use `sudo fwupdtool --plugins ch347 firmware-dump backup.bin` ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x1A86` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.8.14`. fwupd-1.9.16/plugins/ch347/ch347.quirk000066400000000000000000000000471460375044200171610ustar00rootroot00000000000000[USB\VID_1A86&PID_55DB] Plugin = ch347 fwupd-1.9.16/plugins/ch347/fu-ch347-cfi-device.c000066400000000000000000000023561460375044200206610ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ch347-cfi-device.h" #include "fu-ch347-device.h" struct _FuCh347CfiDevice { FuCfiDevice parent_instance; }; G_DEFINE_TYPE(FuCh347CfiDevice, fu_ch347_cfi_device, FU_TYPE_CFI_DEVICE) static gboolean fu_ch347_cfi_device_chip_select(FuCfiDevice *self, gboolean value, GError **error) { FuCh347Device *proxy = FU_CH347_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); return fu_ch347_device_chip_select(proxy, value, error); } static gboolean fu_ch347_cfi_device_send_command(FuCfiDevice *self, const guint8 *wbuf, gsize wbufsz, guint8 *rbuf, gsize rbufsz, FuProgress *progress, GError **error) { FuCh347Device *proxy = FU_CH347_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); return fu_ch347_device_send_command(proxy, wbuf, wbufsz, rbuf, rbufsz, progress, error); } static void fu_ch347_cfi_device_init(FuCh347CfiDevice *self) { } static void fu_ch347_cfi_device_class_init(FuCh347CfiDeviceClass *klass) { FuCfiDeviceClass *klass_cfi = FU_CFI_DEVICE_CLASS(klass); klass_cfi->chip_select = fu_ch347_cfi_device_chip_select; klass_cfi->send_command = fu_ch347_cfi_device_send_command; } fwupd-1.9.16/plugins/ch347/fu-ch347-cfi-device.h000066400000000000000000000004651460375044200206650ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CH347_CFI_DEVICE (fu_ch347_cfi_device_get_type()) G_DECLARE_FINAL_TYPE(FuCh347CfiDevice, fu_ch347_cfi_device, FU, CH347_CFI_DEVICE, FuCfiDevice) fwupd-1.9.16/plugins/ch347/fu-ch347-device.c000066400000000000000000000177411460375044200201260ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ch347-cfi-device.h" #include "fu-ch347-device.h" struct _FuCh347Device { FuUsbDevice parent_instance; guint8 divisor; }; G_DEFINE_TYPE(FuCh347Device, fu_ch347_device, FU_TYPE_USB_DEVICE) #define FU_CH347_USB_TIMEOUT 1000 #define FU_CH347_CMD_SPI_SET_CFG 0xC0 #define FU_CH347_CMD_SPI_CS_CTRL 0xC1 #define FU_CH347_CMD_SPI_OUT_IN 0xC2 #define FU_CH347_CMD_SPI_IN 0xC3 #define FU_CH347_CMD_SPI_OUT 0xC4 #define FU_CH347_CMD_SPI_GET_CFG 0xCA #define FU_CH347_CS_ASSERT 0x00 #define FU_CH347_CS_DEASSERT 0x40 #define FU_CH347_CS_CHANGE 0x80 #define FU_CH347_CS_IGNORE 0x00 #define FU_CH347_EP_OUT 0x06 #define FU_CH347_EP_IN 0x86 #define FU_CH347_MODE1_IFACE 0x2 #define FU_CH347_MODE2_IFACE 0x1 #define FU_CH347_PACKET_SIZE 510 #define FU_CH347_PAYLOAD_SIZE (FU_CH347_PACKET_SIZE - 3) static void fu_ch347_device_to_string(FuDevice *device, guint idt, GString *str) { FuCh347Device *self = FU_CH347_DEVICE(device); /* FuUsbDevice->to_string */ FU_DEVICE_CLASS(fu_ch347_device_parent_class)->to_string(device, idt, str); fu_string_append_kx(str, idt, "Divisor", self->divisor); } static gboolean fu_ch347_device_write(FuCh347Device *self, guint8 cmd, const guint8 *buf, gsize bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length = 0; g_autoptr(GByteArray) cmdbuf = g_byte_array_new(); /* pack */ fu_byte_array_append_uint8(cmdbuf, cmd); fu_byte_array_append_uint16(cmdbuf, bufsz, G_LITTLE_ENDIAN); if (bufsz > 0) g_byte_array_append(cmdbuf, buf, bufsz); /* debug */ fu_dump_raw(G_LOG_DOMAIN, "write", cmdbuf->data, cmdbuf->len); if (!g_usb_device_bulk_transfer(usb_device, FU_CH347_EP_OUT, cmdbuf->data, cmdbuf->len, &actual_length, FU_CH347_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to write 0x%x bytes: ", (guint)bufsz); return FALSE; } if (cmdbuf->len != actual_length) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "only wrote 0x%x of 0x%x", (guint)actual_length, (guint)cmdbuf->len); return FALSE; } /* success */ return TRUE; } static gboolean fu_ch347_device_read(FuCh347Device *self, guint8 cmd, guint8 *buf, gsize bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length = 0; guint8 cmd_rsp; guint16 size_rsp; g_autoptr(GByteArray) cmdbuf = g_byte_array_new(); /* pack */ fu_byte_array_append_uint8(cmdbuf, cmd); fu_byte_array_append_uint16(cmdbuf, sizeof(guint32), G_LITTLE_ENDIAN); fu_byte_array_append_uint32(cmdbuf, bufsz, G_LITTLE_ENDIAN); fu_byte_array_set_size(cmdbuf, FU_CH347_PACKET_SIZE, 0x0); if (!g_usb_device_bulk_transfer(usb_device, FU_CH347_EP_IN, cmdbuf->data, cmdbuf->len, &actual_length, FU_CH347_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to read 0x%x bytes: ", (guint)bufsz); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "read", cmdbuf->data, actual_length); if (actual_length == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "returned 0 bytes"); return FALSE; } /* debug */ cmd_rsp = cmdbuf->data[0]; if (cmd_rsp != cmd) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid cmd, got 0x%02x, expected 0x%02x", cmd_rsp, cmd); return FALSE; } size_rsp = fu_memread_uint16(cmdbuf->data + 0x1, G_LITTLE_ENDIAN); if (size_rsp != bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "size invalid, got 0x%04x, expected 0x04%x", size_rsp, (guint)bufsz); return FALSE; } /* success */ memcpy(buf, cmdbuf->data + 0x3, size_rsp); return TRUE; } gboolean fu_ch347_device_send_command(FuCh347Device *self, const guint8 *wbuf, gsize wbufsz, guint8 *rbuf, gsize rbufsz, FuProgress *progress, GError **error) { /* write */ if (wbufsz > 0) { g_autoptr(GBytes) wblob = g_bytes_new_static(wbuf, wbufsz); g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(wblob, 0x0, FU_CH347_PAYLOAD_SIZE); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { guint8 buf[1] = {0x0}; g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_ch347_device_write(self, FU_CH347_CMD_SPI_OUT, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; if (!fu_ch347_device_read(self, FU_CH347_CMD_SPI_OUT, buf, sizeof(buf), error)) return FALSE; } } /* read */ if (rbufsz > 0) { g_autoptr(GPtrArray) chunks = fu_chunk_array_mutable_new(rbuf, rbufsz, 0x0, 0x0, FU_CH347_PAYLOAD_SIZE); g_autoptr(GByteArray) cmdbuf = g_byte_array_new(); fu_byte_array_append_uint32(cmdbuf, rbufsz, G_LITTLE_ENDIAN); if (!fu_ch347_device_write(self, FU_CH347_CMD_SPI_IN, cmdbuf->data, cmdbuf->len, error)) return FALSE; fu_progress_set_id(progress, G_STRLOC); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_ch347_device_read(self, FU_CH347_CMD_SPI_IN, fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; fu_progress_step_done(progress); } } /* success */ return TRUE; } static gboolean fu_ch347_device_configure_stream(FuCh347Device *self, GError **error) { guint8 data[26] = {[2] = 4, /* ?? */ [3] = 1, /* ?? */ [6] = 0, /* clock polarity: bit 1 */ [8] = 0, /* clock phase: bit 0 */ [11] = 2, /* ?? */ [12] = (self->divisor & 0x7) << 3, /* clock divisor: bits 5:3 */ [14] = 0, /* bit order: bit 7, 0=MSB */ [16] = 7, /* ?? */ [21] = 0}; /* CS polarity: bit 7 CS2, bit 6 CS1. 0 = active low */ if (!fu_ch347_device_write(self, FU_CH347_CMD_SPI_SET_CFG, data, sizeof(data), error)) { g_prefix_error(error, "failed to configure stream: "); return FALSE; } if (!fu_ch347_device_read(self, FU_CH347_CMD_SPI_SET_CFG, data, 1, error)) { g_prefix_error(error, "failed to confirm configure stream: "); return FALSE; } /* success */ return TRUE; } gboolean fu_ch347_device_chip_select(FuCh347Device *self, gboolean val, GError **error) { guint8 buf[10] = { [0] = val ? FU_CH347_CS_ASSERT | FU_CH347_CS_CHANGE : FU_CH347_CS_DEASSERT | FU_CH347_CS_CHANGE, [5] = FU_CH347_CS_IGNORE /* CS2 */ }; return fu_ch347_device_write(self, FU_CH347_CMD_SPI_CS_CTRL, buf, sizeof(buf), error); } static gboolean fu_ch347_device_setup(FuDevice *device, GError **error) { FuCh347Device *self = FU_CH347_DEVICE(device); g_autoptr(FuCh347CfiDevice) cfi_device = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_ch347_device_parent_class)->setup(device, error)) return FALSE; /* set divisor */ if (!fu_ch347_device_configure_stream(self, error)) return FALSE; /* setup SPI chip */ cfi_device = g_object_new(FU_TYPE_CH347_CFI_DEVICE, "context", fu_device_get_context(device), "proxy", device, "parent", device, "logical-id", "SPI", NULL); if (!fu_device_setup(FU_DEVICE(cfi_device), error)) return FALSE; fu_device_add_child(device, FU_DEVICE(cfi_device)); /* success */ return TRUE; } static void fu_ch347_device_init(FuCh347Device *self) { self->divisor = 0b10; fu_usb_device_add_interface(FU_USB_DEVICE(self), FU_CH347_MODE1_IFACE); fu_device_set_name(FU_DEVICE(self), "CH347"); fu_device_set_vendor(FU_DEVICE(self), "WinChipHead"); } static void fu_ch347_device_class_init(FuCh347DeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_ch347_device_setup; klass_device->to_string = fu_ch347_device_to_string; } fwupd-1.9.16/plugins/ch347/fu-ch347-device.h000066400000000000000000000011141460375044200201160ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CH347_DEVICE (fu_ch347_device_get_type()) G_DECLARE_FINAL_TYPE(FuCh347Device, fu_ch347_device, FU, CH347_DEVICE, FuUsbDevice) gboolean fu_ch347_device_chip_select(FuCh347Device *self, gboolean val, GError **error); gboolean fu_ch347_device_send_command(FuCh347Device *self, const guint8 *wbuf, gsize wbufsz, guint8 *rbuf, gsize rbufsz, FuProgress *progress, GError **error); fwupd-1.9.16/plugins/ch347/fu-ch347-plugin.c000066400000000000000000000012761460375044200201610ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ch347-device.h" #include "fu-ch347-plugin.h" struct _FuCh347Plugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuCh347Plugin, fu_ch347_plugin, FU_TYPE_PLUGIN) static void fu_ch347_plugin_init(FuCh347Plugin *self) { } static void fu_ch347_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_CH347_DEVICE); } static void fu_ch347_plugin_class_init(FuCh347PluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_ch347_plugin_constructed; } fwupd-1.9.16/plugins/ch347/fu-ch347-plugin.h000066400000000000000000000003451460375044200201620ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuCh347Plugin, fu_ch347_plugin, FU, CH347_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/ch347/meson.build000066400000000000000000000006541460375044200174220ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginCh347"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('ch347.quirk') plugin_builtins += static_library('fu_plugin_ch347', sources: [ 'fu-ch347-cfi-device.c', 'fu-ch347-device.c', 'fu-ch347-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/colorhug/000077500000000000000000000000001460375044200162455ustar00rootroot00000000000000fwupd-1.9.16/plugins/colorhug/README.md000066400000000000000000000027011460375044200175240ustar00rootroot00000000000000--- title: Plugin: ColorHug --- ## Introduction The ColorHug is an affordable open source display colorimeter built by Hughski Limited. The USB device allows you to calibrate your screen for accurate color matching. ColorHug versions 1 and 2 support a custom HID-based flashing protocol, but version 3 (ColorHug+) has now switched to DFU. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `com.hughski.colorhug` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_273F&PID_1001` ## Update Behavior The device usually presents in runtime mode, but on detach re-enumerates with a different USB PID in a bootloader mode. On attach the device again re-enumerates back to the runtime mode. For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x273F` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `0.8.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Richard Hughes: @hughsie fwupd-1.9.16/plugins/colorhug/colorhug.quirk000066400000000000000000000027751460375044200211570ustar00rootroot00000000000000# ColorHug1 [USB\VID_273F&PID_1000] Plugin = colorhug Flags = is-bootloader,self-recovery Guid = 40338ceb-b966-4eae-adae-9c32edfcc484 FirmwareSizeMin = 0x2000 FirmwareSizeMax = 0x8000 CounterpartGuid = USB\VID_273F&PID_1001 InstallDuration = 8 # ColorHug1: first batch! [USB\VID_04D8&PID_F8DA] Guid = USB\VID_273F&PID_1000 [USB\VID_273F&PID_1001] Plugin = colorhug Flags = self-recovery Summary = An open source display colorimeter Icon = colorimeter-colorhug Guid = 40338ceb-b966-4eae-adae-9c32edfcc484 CounterpartGuid = USB\VID_273F&PID_1000 InstallDuration = 8 # ColorHug2 [USB\VID_273F&PID_1004] Plugin = colorhug Flags = self-recovery Summary = An open source display colorimeter Icon = colorimeter-colorhug Guid = 2082b5e0-7a64-478a-b1b2-e3404fab6dad FirmwareSizeMin = 0x2000 FirmwareSizeMax = 0x8000 CounterpartGuid = USB\VID_273F&PID_1005 InstallDuration = 8 [USB\VID_273F&PID_1005] Plugin = colorhug Flags = is-bootloader,self-recovery Guid = 2082b5e0-7a64-478a-b1b2-e3404fab6dad CounterpartGuid = USB\VID_273F&PID_1004 InstallDuration = 8 # ColorHugALS [USB\VID_273F&PID_1007] Plugin = colorhug Flags = halfsize,self-recovery Summary = An open source ambient light sensor Guid = 84f40464-9272-4ef7-9399-cd95f12da696 FirmwareSizeMin = 0x1000 FirmwareSizeMax = 0x4000 CounterpartGuid = USB\VID_273F&PID_1006 InstallDuration = 5 [USB\VID_273F&PID_1006] Plugin = colorhug Flags = halfsize,is-bootloader,self-recovery Guid = 84f40464-9272-4ef7-9399-cd95f12da696 CounterpartGuid = USB\VID_273F&PID_1007 InstallDuration = 5 fwupd-1.9.16/plugins/colorhug/fu-colorhug-common.c000066400000000000000000000057411460375044200221400ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-colorhug-common.h" const gchar * ch_strerror(ChError error_enum) { if (error_enum == CH_ERROR_NONE) return "Success"; if (error_enum == CH_ERROR_UNKNOWN_CMD) return "Unknown command"; if (error_enum == CH_ERROR_WRONG_UNLOCK_CODE) return "Wrong unlock code"; if (error_enum == CH_ERROR_NOT_IMPLEMENTED) return "Not implemented"; if (error_enum == CH_ERROR_UNDERFLOW_SENSOR) return "Underflow of sensor"; if (error_enum == CH_ERROR_NO_SERIAL) return "No serial"; if (error_enum == CH_ERROR_WATCHDOG) return "Watchdog"; if (error_enum == CH_ERROR_INVALID_ADDRESS) return "Invalid address"; if (error_enum == CH_ERROR_INVALID_LENGTH) return "Invalid length"; if (error_enum == CH_ERROR_INVALID_CHECKSUM) return "Invalid checksum"; if (error_enum == CH_ERROR_INVALID_VALUE) return "Invalid value"; if (error_enum == CH_ERROR_UNKNOWN_CMD_FOR_BOOTLOADER) return "Unknown command for bootloader"; if (error_enum == CH_ERROR_OVERFLOW_MULTIPLY) return "Overflow of multiply"; if (error_enum == CH_ERROR_OVERFLOW_ADDITION) return "Overflow of addition"; if (error_enum == CH_ERROR_OVERFLOW_SENSOR) return "Overflow of sensor"; if (error_enum == CH_ERROR_OVERFLOW_STACK) return "Overflow of stack"; if (error_enum == CH_ERROR_NO_CALIBRATION) return "No calibration"; if (error_enum == CH_ERROR_DEVICE_DEACTIVATED) return "Device deactivated"; if (error_enum == CH_ERROR_INCOMPLETE_REQUEST) return "Incomplete previous request"; if (error_enum == CH_ERROR_SELF_TEST_SENSOR) return "Self test failed: Sensor"; if (error_enum == CH_ERROR_SELF_TEST_RED) return "Self test failed: Red"; if (error_enum == CH_ERROR_SELF_TEST_GREEN) return "Self test failed: Green"; if (error_enum == CH_ERROR_SELF_TEST_BLUE) return "Self test failed: Blue"; if (error_enum == CH_ERROR_SELF_TEST_MULTIPLIER) return "Self test failed: Multiplier"; if (error_enum == CH_ERROR_SELF_TEST_COLOR_SELECT) return "Self test failed: Color Select"; if (error_enum == CH_ERROR_SELF_TEST_TEMPERATURE) return "Self test failed: Temperature"; if (error_enum == CH_ERROR_INVALID_CALIBRATION) return "Invalid calibration"; if (error_enum == CH_ERROR_SRAM_FAILED) return "SRAM failed"; if (error_enum == CH_ERROR_OUT_OF_MEMORY) return "Out of memory"; if (error_enum == CH_ERROR_SELF_TEST_I2C) return "Self test failed: I2C"; if (error_enum == CH_ERROR_SELF_TEST_ADC_VDD) return "Self test failed: ADC Vdd"; if (error_enum == CH_ERROR_SELF_TEST_ADC_VSS) return "Self test failed: ADC Vss"; if (error_enum == CH_ERROR_SELF_TEST_ADC_VREF) return "Self test failed: ADC Vref"; if (error_enum == CH_ERROR_I2C_TARGET_ADDRESS) return "I2C set target address failed"; if (error_enum == CH_ERROR_I2C_TARGET_CONFIG) return "I2C set target config failed"; if (error_enum == CH_ERROR_SELF_TEST_EEPROM) return "Self test failed: EEPROM"; return NULL; } fwupd-1.9.16/plugins/colorhug/fu-colorhug-common.h000066400000000000000000000023121460375044200221340ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef enum { CH_ERROR_NONE, CH_ERROR_UNKNOWN_CMD, CH_ERROR_WRONG_UNLOCK_CODE, CH_ERROR_NOT_IMPLEMENTED, CH_ERROR_UNDERFLOW_SENSOR, CH_ERROR_NO_SERIAL, CH_ERROR_WATCHDOG, CH_ERROR_INVALID_ADDRESS, CH_ERROR_INVALID_LENGTH, CH_ERROR_INVALID_CHECKSUM, CH_ERROR_INVALID_VALUE, CH_ERROR_UNKNOWN_CMD_FOR_BOOTLOADER, CH_ERROR_NO_CALIBRATION, CH_ERROR_OVERFLOW_MULTIPLY, CH_ERROR_OVERFLOW_ADDITION, CH_ERROR_OVERFLOW_SENSOR, CH_ERROR_OVERFLOW_STACK, CH_ERROR_DEVICE_DEACTIVATED, CH_ERROR_INCOMPLETE_REQUEST, CH_ERROR_SELF_TEST_SENSOR, CH_ERROR_SELF_TEST_RED, CH_ERROR_SELF_TEST_GREEN, CH_ERROR_SELF_TEST_BLUE, CH_ERROR_SELF_TEST_COLOR_SELECT, CH_ERROR_SELF_TEST_MULTIPLIER, CH_ERROR_INVALID_CALIBRATION, CH_ERROR_SRAM_FAILED, CH_ERROR_OUT_OF_MEMORY, CH_ERROR_SELF_TEST_TEMPERATURE, CH_ERROR_SELF_TEST_I2C, CH_ERROR_SELF_TEST_ADC_VDD, CH_ERROR_SELF_TEST_ADC_VSS, CH_ERROR_SELF_TEST_ADC_VREF, CH_ERROR_I2C_TARGET_ADDRESS, CH_ERROR_I2C_TARGET_CONFIG, CH_ERROR_SELF_TEST_EEPROM, CH_ERROR_LAST } ChError; const gchar * ch_strerror(ChError error_enum); fwupd-1.9.16/plugins/colorhug/fu-colorhug-device.c000066400000000000000000000407271460375044200221120ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-colorhug-common.h" #include "fu-colorhug-device.h" /** * FU_COLORHUG_DEVICE_FLAG_HALFSIZE: * * Some devices have a compact memory layout and the application code starts * earlier. * * Since: 1.0.3 */ #define FU_COLORHUG_DEVICE_FLAG_HALFSIZE (1 << 0) struct _FuColorhugDevice { FuUsbDevice parent_instance; guint16 start_addr; }; G_DEFINE_TYPE(FuColorhugDevice, fu_colorhug_device, FU_TYPE_USB_DEVICE) #define CH_CMD_GET_FIRMWARE_VERSION 0x07 #define CH_CMD_RESET 0x24 #define CH_CMD_READ_FLASH 0x25 #define CH_CMD_WRITE_FLASH 0x26 #define CH_CMD_BOOT_FLASH 0x27 #define CH_CMD_SET_FLASH_SUCCESS 0x28 #define CH_CMD_ERASE_FLASH 0x29 #define CH_USB_HID_EP 0x0001 #define CH_USB_HID_EP_IN (CH_USB_HID_EP | 0x80) #define CH_USB_HID_EP_OUT (CH_USB_HID_EP | 0x00) #define CH_USB_HID_EP_SIZE 64 #define CH_USB_CONFIG 0x0001 #define CH_USB_INTERFACE 0x0000 #define CH_EEPROM_ADDR_RUNCODE 0x4000 #define CH_EEPROM_ADDR_RUNCODE_ALS 0x2000 #define CH_DEVICE_USB_TIMEOUT 5000 /* ms */ #define CH_FLASH_TRANSFER_BLOCK_SIZE 0x020 /* 32 */ static gboolean fu_colorhug_device_msg(FuColorhugDevice *self, guint8 cmd, guint8 *ibuf, gsize ibufsz, guint8 *obuf, gsize obufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); guint8 buf[] = {[0] = cmd, [1 ... CH_USB_HID_EP_SIZE - 1] = 0x00}; gsize actual_length = 0; g_autoptr(GError) error_local = NULL; /* check size */ if (ibufsz > sizeof(buf) - 1) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot process chunk of size %" G_GSIZE_FORMAT, ibufsz); return FALSE; } if (obufsz > sizeof(buf) - 2) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot process chunk of size %" G_GSIZE_FORMAT, ibufsz); return FALSE; } /* optionally copy in data */ if (ibuf != NULL) { if (!fu_memcpy_safe(buf, sizeof(buf), 0x1, /* dst */ ibuf, ibufsz, 0x0, /* src */ ibufsz, error)) return FALSE; } /* request */ fu_dump_raw(G_LOG_DOMAIN, "REQ", buf, ibufsz + 1); if (!g_usb_device_interrupt_transfer(usb_device, CH_USB_HID_EP_OUT, buf, sizeof(buf), &actual_length, CH_DEVICE_USB_TIMEOUT, NULL, /* cancellable */ &error_local)) { if (cmd == CH_CMD_RESET && g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE)) { g_debug("ignoring '%s' on reset", error_local->message); return TRUE; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to send request: "); return FALSE; } if (actual_length != CH_USB_HID_EP_SIZE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "request not all sent, got %" G_GSIZE_FORMAT, actual_length); return FALSE; } /* read reply */ if (!g_usb_device_interrupt_transfer(usb_device, CH_USB_HID_EP_IN, buf, sizeof(buf), &actual_length, CH_DEVICE_USB_TIMEOUT, NULL, /* cancellable */ &error_local)) { if (cmd == CH_CMD_RESET && g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE)) { g_debug("ignoring '%s' on reset", error_local->message); return TRUE; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to get reply: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "RES", buf, actual_length); /* old bootloaders do not return the full block */ if (actual_length != CH_USB_HID_EP_SIZE && actual_length != 2 && actual_length != obufsz + 2) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "request not all received, got %" G_GSIZE_FORMAT, actual_length); return FALSE; } /* check error code */ if (buf[0] != CH_ERROR_NONE) { const gchar *msg = ch_strerror(buf[0]); if (msg == NULL) msg = "unknown error"; g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, msg); return FALSE; } /* check cmd matches */ if (buf[1] != cmd) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cmd incorrect, expected %u, got %u", cmd, buf[1]); return FALSE; } /* copy back optional buf */ if (obuf != NULL) { if (!fu_memcpy_safe(obuf, obufsz, 0x0, /* dst */ buf, sizeof(buf), 0x2, /* src */ obufsz, error)) return FALSE; } return TRUE; } static gboolean fu_colorhug_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE(device); g_autoptr(GError) error_local = NULL; /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in bootloader mode, skipping"); return TRUE; } if (!fu_colorhug_device_msg(self, CH_CMD_RESET, NULL, 0, /* in */ NULL, 0, /* out */ &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to reset device: %s", error_local->message); return FALSE; } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_colorhug_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE(device); g_autoptr(GError) error_local = NULL; /* sanity check */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } if (!fu_colorhug_device_msg(self, CH_CMD_BOOT_FLASH, NULL, 0, /* in */ NULL, 0, /* out */ &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to boot to runtime: %s", error_local->message); return FALSE; } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_colorhug_device_set_flash_success(FuColorhugDevice *self, gboolean val, GError **error) { guint8 buf[] = {[0] = val ? 0x01 : 0x00}; g_autoptr(GError) error_local = NULL; g_debug("setting flash success %s", val ? "true" : "false"); if (!fu_colorhug_device_msg(self, CH_CMD_SET_FLASH_SUCCESS, buf, sizeof(buf), /* in */ NULL, 0, /* out */ &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to set flash success: %s", error_local->message); return FALSE; } return TRUE; } static gboolean fu_colorhug_device_reload(FuDevice *device, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE(device); return fu_colorhug_device_set_flash_success(self, TRUE, error); } static gboolean fu_colorhug_device_erase(FuColorhugDevice *self, guint16 addr, gsize sz, GError **error) { guint8 buf[4]; g_autoptr(GError) error_local = NULL; fu_memwrite_uint16(buf + 0, addr, G_LITTLE_ENDIAN); fu_memwrite_uint16(buf + 2, sz, G_LITTLE_ENDIAN); if (!fu_colorhug_device_msg(self, CH_CMD_ERASE_FLASH, buf, sizeof(buf), /* in */ NULL, 0, /* out */ &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to erase device: %s", error_local->message); return FALSE; } return TRUE; } static gchar * fu_colorhug_device_get_version(FuColorhugDevice *self, GError **error) { guint8 buf[6]; if (!fu_colorhug_device_msg(self, CH_CMD_GET_FIRMWARE_VERSION, NULL, 0, /* in */ buf, sizeof(buf), /* out */ error)) { return NULL; } return g_strdup_printf("%i.%i.%i", fu_memread_uint16(buf + 0, G_LITTLE_ENDIAN), fu_memread_uint16(buf + 2, G_LITTLE_ENDIAN), fu_memread_uint16(buf + 4, G_LITTLE_ENDIAN)); } static gboolean fu_colorhug_device_probe(FuDevice *device, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE(device); /* compact memory layout */ if (fu_device_has_private_flag(device, FU_COLORHUG_DEVICE_FLAG_HALFSIZE)) self->start_addr = CH_EEPROM_ADDR_RUNCODE_ALS; /* add hardcoded bits */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); /* success */ return TRUE; } static gboolean fu_colorhug_device_setup(FuDevice *device, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); guint idx; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_colorhug_device_parent_class)->setup(device, error)) return FALSE; /* get version number, falling back to the USB device release */ idx = g_usb_device_get_custom_index(usb_device, G_USB_DEVICE_CLASS_VENDOR_SPECIFIC, 'F', 'W', NULL); if (idx != 0x00) { g_autofree gchar *tmp = NULL; tmp = g_usb_device_get_string_descriptor(usb_device, idx, NULL); /* although guessing is a route to insanity, if the device has * provided the extra data it's because the BCD type was not * suitable -- and INTEL_ME is not relevant here */ if (tmp != NULL) { fu_device_set_version_format(device, fu_version_guess_format(tmp)); fu_device_set_version(device, tmp); } } /* get GUID from the descriptor if set */ idx = g_usb_device_get_custom_index(usb_device, G_USB_DEVICE_CLASS_VENDOR_SPECIFIC, 'G', 'U', NULL); if (idx != 0x00) { g_autofree gchar *tmp = NULL; tmp = g_usb_device_get_string_descriptor(usb_device, idx, NULL); fu_device_add_guid(device, tmp); } /* using the USB descriptor and old firmware */ if (fu_device_get_version_format(device) == FWUPD_VERSION_FORMAT_BCD) { g_autofree gchar *version = NULL; g_autoptr(GError) error_local = NULL; version = fu_colorhug_device_get_version(self, &error_local); if (version != NULL) { g_debug("obtained fwver using API '%s'", version); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, version); } else { g_warning("failed to get firmware version: %s", error_local->message); } } /* success */ return TRUE; } static guint8 ch_colorhug_device_calculate_checksum(const guint8 *data, guint32 len) { guint8 checksum = 0xff; for (guint32 i = 0; i < len; i++) checksum ^= data[i]; return checksum; } static gboolean fu_colorhug_device_write_blocks(FuColorhugDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); guint8 buf[CH_FLASH_TRANSFER_BLOCK_SIZE + 4]; g_autoptr(GError) error_local = NULL; /* set address, length, checksum, data */ fu_memwrite_uint16(buf + 0, fu_chunk_get_address(chk), G_LITTLE_ENDIAN); buf[2] = fu_chunk_get_data_sz(chk); buf[3] = ch_colorhug_device_calculate_checksum(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); if (!fu_memcpy_safe(buf, sizeof(buf), 0x4, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; if (!fu_colorhug_device_msg(self, CH_CMD_WRITE_FLASH, buf, sizeof(buf), /* in */ NULL, 0, /* out */ &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to write: %s", error_local->message); return FALSE; } /* update progress */ fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_colorhug_device_verify_blocks(FuColorhugDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); guint8 buf[3]; guint8 buf_out[CH_FLASH_TRANSFER_BLOCK_SIZE + 1]; g_autoptr(GError) error_local = NULL; /* set address */ fu_memwrite_uint16(buf + 0, fu_chunk_get_address(chk), G_LITTLE_ENDIAN); buf[2] = fu_chunk_get_data_sz(chk); if (!fu_colorhug_device_msg(self, CH_CMD_READ_FLASH, buf, sizeof(buf), /* in */ buf_out, sizeof(buf_out), /* out */ &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read: %s", error_local->message); return FALSE; } /* verify */ if (memcmp(buf_out + 1, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to verify firmware for chunk %u, " "address 0x%0x, length 0x%0x", i, (guint)fu_chunk_get_address(chk), fu_chunk_get_data_sz(chk)); return FALSE; } /* update progress */ fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_colorhug_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuColorhugDevice *self = FU_COLORHUG_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 19, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 44, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 35, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* don't auto-boot firmware */ if (!fu_colorhug_device_set_flash_success(self, FALSE, error)) return FALSE; fu_progress_step_done(progress); /* erase flash */ if (!fu_colorhug_device_erase(self, self->start_addr, g_bytes_get_size(fw), error)) return FALSE; fu_progress_step_done(progress); /* write each block */ chunks = fu_chunk_array_new_from_bytes(fw, self->start_addr, CH_FLASH_TRANSFER_BLOCK_SIZE); if (!fu_colorhug_device_write_blocks(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* verify each block */ if (!fu_colorhug_device_verify_blocks(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success! */ return TRUE; } static void fu_colorhug_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 57, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 43, "reload"); } static void fu_colorhug_device_init(FuColorhugDevice *self) { /* this is the application code */ self->start_addr = CH_EEPROM_ADDR_RUNCODE; fu_device_add_protocol(FU_DEVICE(self), "com.hughski.colorhug"); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG); fu_device_register_private_flag(FU_DEVICE(self), FU_COLORHUG_DEVICE_FLAG_HALFSIZE, "halfsize"); fu_usb_device_set_configuration(FU_USB_DEVICE(self), CH_USB_CONFIG); fu_usb_device_add_interface(FU_USB_DEVICE(self), CH_USB_INTERFACE); } static void fu_colorhug_device_class_init(FuColorhugDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_colorhug_device_write_firmware; klass_device->attach = fu_colorhug_device_attach; klass_device->detach = fu_colorhug_device_detach; klass_device->reload = fu_colorhug_device_reload; klass_device->setup = fu_colorhug_device_setup; klass_device->probe = fu_colorhug_device_probe; klass_device->set_progress = fu_colorhug_device_set_progress; } fwupd-1.9.16/plugins/colorhug/fu-colorhug-device.h000066400000000000000000000004611460375044200221060ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_COLORHUG_DEVICE (fu_colorhug_device_get_type()) G_DECLARE_FINAL_TYPE(FuColorhugDevice, fu_colorhug_device, FU, COLORHUG_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/colorhug/fu-colorhug-plugin.c000066400000000000000000000013421460375044200221370ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-colorhug-device.h" #include "fu-colorhug-plugin.h" struct _FuColorhugPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuColorhugPlugin, fu_colorhug_plugin, FU_TYPE_PLUGIN) static void fu_colorhug_plugin_init(FuColorhugPlugin *self) { } static void fu_colorhug_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_COLORHUG_DEVICE); } static void fu_colorhug_plugin_class_init(FuColorhugPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_colorhug_plugin_constructed; } fwupd-1.9.16/plugins/colorhug/fu-colorhug-plugin.h000066400000000000000000000003561460375044200221500ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuColorhugPlugin, fu_colorhug_plugin, FU, COLORHUG_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/colorhug/meson.build000066400000000000000000000006721460375044200204140ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginColorHug"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('colorhug.quirk') plugin_builtins += static_library('fu_plugin_colorhug', sources: [ 'fu-colorhug-common.c', 'fu-colorhug-device.c', 'fu-colorhug-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/corsair/000077500000000000000000000000001460375044200160655ustar00rootroot00000000000000fwupd-1.9.16/plugins/corsair/README.md000066400000000000000000000040751460375044200173520ustar00rootroot00000000000000--- title: Plugin: Corsair --- ## Introduction This plugin allows to update firmware on Corsair mice and receivers: * SABRE RGB PRO WIRELESS * SLIPSTREAM WIRELESS USB Receiver * KATAR PRO WIRELESS * KATAR PRO XT Gaming Mouse * SABRE PRO Gaming Mouse ## Code structure All devices handled by one object (FuCorsairDevice). Receivers with wireless-only devices will be shown as two entities: parent device as a receiver and wireless device as a child. Difference in behavior is handled by private flags. FuCorsairBp contains low-level protocol related routines. Device objects should call correct versions of these routines in order to update firmware. Correct routines chosen by device quirks and private flags. ## Wired mice update behavior Mice and/or it's wireless adapter must be connected to host via USB cable to apply an update. The device is switched to bootloader mode to flash updates, and is reset automatically to new firmware after flashing. ## Wireless mice update behavior The receiver should be connected to host and the mouse should be turned on and not sleeping. ## Quirk Use This plugin uses the following plugin-specific quirks: ### CorsairVendorInterfaceId Some devices have non-standard USB interface for protocol communication. This quirk should be set if protocol interface is not 1. Since: 1.8.0 ### CorsairSubdeviceId Specifies ID of any wireless child device which can be updated. Polling will be turned on if a subdevice is not connected when parent is being probed. ### Flags:legacy-attach This flag is used if legacy attach command should be used ### Flags:no-version-in-bl This flag handles cases if device reports incorrect firmware version in bootloader mode. ### Flags:is-subdevice This flag tells device that it is a child device. All subdevice behavior tweaks will be applied. ## Version Considerations This plugin has been available since fwupd version `1.8.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Andrii Dushko: @dushko-devx fwupd-1.9.16/plugins/corsair/corsair.quirk000066400000000000000000000007511460375044200206070ustar00rootroot00000000000000[USB\VID_1B1C&PID_1BAC] Plugin = corsair GType = FuCorsairDevice Name = KATAR PRO XT Gaming Mouse CorsairDeviceKind = mouse Flags = legacy-attach,no-version-in-bl [USB\VID_1B1C&PID_1B7A] Plugin = corsair GType = FuCorsairDevice Name = SABRE PRO Gaming Mouse CorsairDeviceKind = mouse Flags = legacy-attach,no-version-in-bl [USB\VID_1B1C&PID_1B79] Plugin = corsair GType = FuCorsairDevice Name = SABRE RGB PRO Gaming Mouse CorsairDeviceKind = mouse Flags = legacy-attach,no-version-in-bl fwupd-1.9.16/plugins/corsair/fu-corsair-bp.c000066400000000000000000000316061460375044200207100ustar00rootroot00000000000000/* * Copyright (C) 2022 Andrii Dushko * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-corsair-bp.h" #include "fu-corsair-common.h" #define CORSAIR_DEFAULT_VENDOR_INTERFACE_ID 1 #define CORSAIR_ACTIVATION_TIMEOUT 30000 #define CORSAIR_MODE_BOOTLOADER 3 #define CORSAIR_FIRST_CHUNK_HEADER_SIZE 7 #define CORSAIR_NEXT_CHUNKS_HEADER_SIZE 3 #define CORSAIR_TRANSACTION_TIMEOUT 10000 #define CORSAIR_DEFAULT_CMD_SIZE 64 #define CORSAIR_OFFSET_CMD_PROPERTY_ID 0x02 #define CORSAIR_OFFSET_CMD_PROPERTY_VALUE 0x03 #define CORSAIR_OFFSET_CMD_VERSION 0x03 #define CORSAIR_OFFSET_CMD_CRC 0x08 #define CORSAIR_OFFSET_CMD_MODE 0x03 #define CORSAIR_OFFSET_CMD_STATUS 0x02 #define CORSAIR_OFFSET_CMD_FIRMWARE_SIZE 0x03 #define CORSAIR_OFFSET_CMD_SET_MODE 0x04 #define CORSAIR_OFFSET_CMD_DESTINATION 0x00 #define CORSAIR_INPUT_FLUSH_TIMEOUT 10 #define CORSAIR_INPUT_FLUSH_ITERATIONS 3 typedef enum { FU_CORSAIR_BP_DESTINATION_SELF = 0x08, FU_CORSAIR_BP_DESTINATION_SUBDEVICE = 0x09 } FuCorsairBpDestination; struct _FuCorsairBp { FuUsbDevice parent_instance; guint8 destination; guint8 epin; guint8 epout; guint16 cmd_write_size; guint16 cmd_read_size; gboolean is_legacy_attach; }; G_DEFINE_TYPE(FuCorsairBp, fu_corsair_bp, FU_TYPE_USB_DEVICE) static gboolean fu_corsair_bp_command(FuCorsairBp *self, guint8 *data, guint timeout, gboolean need_reply, GError **error) { gsize actual_len = 0; gboolean ret; GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); data[CORSAIR_OFFSET_CMD_DESTINATION] = self->destination; fu_dump_raw("FuPluginCorsair", "command", data, self->cmd_write_size); ret = g_usb_device_interrupt_transfer(usb_device, self->epout, data, self->cmd_write_size, &actual_len, timeout, NULL, error); if (!ret) { g_prefix_error(error, "failed to write command: "); return FALSE; } if (actual_len != self->cmd_write_size) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "wrong size written: %" G_GSIZE_FORMAT, actual_len); return FALSE; } if (!need_reply) return TRUE; memset(data, 0, FU_CORSAIR_MAX_CMD_SIZE); ret = g_usb_device_interrupt_transfer(usb_device, self->epin, data, self->cmd_read_size, &actual_len, timeout, NULL, error); if (!ret) { g_prefix_error(error, "failed to get command response: "); return FALSE; } if (actual_len != self->cmd_read_size) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "wrong size read: %" G_GSIZE_FORMAT, actual_len); return FALSE; } fu_dump_raw("FuPluginCorsair", "response", data, self->cmd_write_size); if (data[CORSAIR_OFFSET_CMD_STATUS] != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "device replied with error: 0x%02x", data[CORSAIR_OFFSET_CMD_STATUS]); return FALSE; } return TRUE; } /** * @brief Flush all input reports if there are any. * @self: a #FuCorsairBp * * This function clears any dangling IN reports that * the device may have sent after the enumeration. */ void fu_corsair_bp_flush_input_reports(FuCorsairBp *self) { gsize actual_len; g_autofree guint8 *buf = g_malloc0(self->cmd_read_size); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); for (guint i = 0; i < CORSAIR_INPUT_FLUSH_ITERATIONS; i++) { g_autoptr(GError) error_local = NULL; if (!g_usb_device_interrupt_transfer(usb_device, self->epin, buf, self->cmd_read_size, &actual_len, CORSAIR_INPUT_FLUSH_TIMEOUT, NULL, &error_local)) g_debug("flushing status: %s", error_local->message); } } static gboolean fu_corsair_bp_write_first_chunk(FuCorsairBp *self, FuChunk *chunk, guint32 firmware_size, GError **error) { guint8 init_cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x0d, 0x00, 0x03}; guint8 write_cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x06, 0x00}; if (!fu_corsair_bp_command(self, init_cmd, CORSAIR_TRANSACTION_TIMEOUT, TRUE, error)) { g_prefix_error(error, "firmware init fail: "); return FALSE; } if (!fu_memwrite_uint32_safe(write_cmd, sizeof(write_cmd), CORSAIR_OFFSET_CMD_FIRMWARE_SIZE, firmware_size, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "cannot serialize firmware size: "); return FALSE; } if (!fu_memcpy_safe(write_cmd, sizeof(write_cmd), CORSAIR_FIRST_CHUNK_HEADER_SIZE, fu_chunk_get_data(chunk), fu_chunk_get_data_sz(chunk), 0, fu_chunk_get_data_sz(chunk), error)) { g_prefix_error(error, "cannot set data: "); return FALSE; } if (!fu_corsair_bp_command(self, write_cmd, CORSAIR_TRANSACTION_TIMEOUT, TRUE, error)) { g_prefix_error(error, "write command fail: "); return FALSE; } return TRUE; } static gboolean fu_corsair_bp_write_chunk(FuCorsairBp *self, FuChunk *chunk, GError **error) { guint8 cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x07}; if (!fu_memcpy_safe(cmd, sizeof(cmd), CORSAIR_NEXT_CHUNKS_HEADER_SIZE, fu_chunk_get_data(chunk), fu_chunk_get_data_sz(chunk), 0, fu_chunk_get_data_sz(chunk), error)) { g_prefix_error(error, "cannot set data: "); return FALSE; } if (!fu_corsair_bp_command(self, cmd, CORSAIR_TRANSACTION_TIMEOUT, TRUE, error)) { g_prefix_error(error, "write command fail: "); return FALSE; } return TRUE; } static void fu_corsair_bp_incorporate(FuDevice *self, FuDevice *donor) { FuCorsairBp *bp_self = FU_CORSAIR_BP(self); FuCorsairBp *bp_donor = FU_CORSAIR_BP(donor); g_return_if_fail(FU_IS_CORSAIR_BP(self)); g_return_if_fail(FU_IS_CORSAIR_BP(donor)); /* FuUsbDevice */ FU_DEVICE_CLASS(fu_corsair_bp_parent_class)->incorporate(self, donor); bp_self->epin = bp_donor->epin; bp_self->epout = bp_donor->epout; bp_self->cmd_write_size = bp_donor->cmd_write_size; bp_self->cmd_read_size = bp_donor->cmd_read_size; } static void fu_corsair_bp_init(FuCorsairBp *self) { self->cmd_read_size = CORSAIR_DEFAULT_CMD_SIZE; self->cmd_write_size = CORSAIR_DEFAULT_CMD_SIZE; self->destination = FU_CORSAIR_BP_DESTINATION_SELF; } gboolean fu_corsair_bp_get_property(FuCorsairBp *self, FuCorsairBpProperty property, guint32 *value, GError **error) { guint8 data[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x02}; fu_memwrite_uint16(&data[CORSAIR_OFFSET_CMD_PROPERTY_ID], (guint16)property, G_LITTLE_ENDIAN); if (!fu_corsair_bp_command(self, data, CORSAIR_TRANSACTION_TIMEOUT, TRUE, error)) return FALSE; *value = fu_memread_uint32(&data[CORSAIR_OFFSET_CMD_PROPERTY_VALUE], G_LITTLE_ENDIAN); return TRUE; } static gboolean fu_corsair_bp_set_mode(FuCorsairBp *self, FuCorsairDeviceMode mode, GError **error) { guint8 cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x01, 0x03}; cmd[CORSAIR_OFFSET_CMD_SET_MODE] = mode; if (!fu_corsair_bp_command(self, cmd, CORSAIR_TRANSACTION_TIMEOUT, TRUE, error)) { g_prefix_error(error, "set mode command fail: "); return FALSE; } return TRUE; } static gboolean fu_corsair_bp_write_firmware_chunks(FuCorsairBp *self, FuChunk *first_chunk, FuChunkArray *chunks, FuProgress *progress, guint32 firmware_size, GError **error) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks) + 1); if (!fu_corsair_bp_write_first_chunk(self, first_chunk, firmware_size, error)) { g_prefix_error(error, "cannot write first chunk: "); return FALSE; } fu_progress_step_done(progress); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_corsair_bp_write_chunk(self, chk, error)) { g_prefix_error(error, "cannot write chunk %u: ", i); return FALSE; } fu_progress_step_done(progress); } return TRUE; } static gboolean fu_corsair_bp_commit_firmware(FuCorsairBp *self, GError **error) { guint8 commit_cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x05, 0x01, 0x00}; if (!fu_corsair_bp_command(self, commit_cmd, CORSAIR_TRANSACTION_TIMEOUT, TRUE, error)) { g_prefix_error(error, "firmware commit fail: "); return FALSE; } return TRUE; } static gboolean fu_corsair_bp_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { const guint8 *firmware_raw; gsize firmware_size; g_autoptr(GBytes) blob = NULL; g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(FuChunk) firstChunk = NULL; g_autoptr(GBytes) rest_of_firmware = NULL; FuCorsairBp *self = FU_CORSAIR_BP(device); guint32 first_chunk_size = self->cmd_write_size - CORSAIR_FIRST_CHUNK_HEADER_SIZE; blob = fu_firmware_get_bytes(firmware, error); if (blob == NULL) { g_prefix_error(error, "cannot get firmware data: "); return FALSE; } firmware_raw = fu_bytes_get_data_safe(blob, &firmware_size, error); if (firmware_raw == NULL) { g_prefix_error(error, "cannot get firmware data: "); return FALSE; } /* the firmware size should be greater than 1 chunk */ if (firmware_size <= first_chunk_size) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "update file should be bigger"); return FALSE; } firstChunk = fu_chunk_new(0, 0, 0, g_bytes_get_data(blob, NULL), first_chunk_size); rest_of_firmware = fu_bytes_new_offset(blob, first_chunk_size, firmware_size - first_chunk_size, error); if (rest_of_firmware == NULL) { g_prefix_error(error, "cannot get firmware past first chunk: "); return FALSE; } chunks = fu_chunk_array_new_from_bytes(rest_of_firmware, first_chunk_size, self->cmd_write_size - CORSAIR_NEXT_CHUNKS_HEADER_SIZE); if (!fu_corsair_bp_write_firmware_chunks(self, firstChunk, chunks, progress, g_bytes_get_size(blob), error)) return FALSE; if (!fu_corsair_bp_commit_firmware(self, error)) return FALSE; return TRUE; } gboolean fu_corsair_bp_activate_firmware(FuCorsairBp *self, FuFirmware *firmware, GError **error) { guint32 crc; gsize firmware_size; const guint8 *firmware_raw; g_autoptr(GBytes) blob = NULL; guint8 cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x16, 0x00, 0x01, 0x03, 0x00, 0x01, 0x01}; blob = fu_firmware_get_bytes(firmware, error); if (blob == NULL) { g_prefix_error(error, "cannot get firmware bytes: "); return FALSE; } firmware_raw = fu_bytes_get_data_safe(blob, &firmware_size, error); if (firmware_raw == NULL) { g_prefix_error(error, "cannot get firmware data: "); return FALSE; } crc = fu_corsair_calculate_crc(firmware_raw, firmware_size); fu_memwrite_uint32(&cmd[CORSAIR_OFFSET_CMD_CRC], crc, G_LITTLE_ENDIAN); return fu_corsair_bp_command(self, cmd, CORSAIR_ACTIVATION_TIMEOUT, TRUE, error); } static gboolean fu_corsair_bp_attach(FuDevice *device, FuProgress *progress, GError **error) { FuCorsairBp *self = FU_CORSAIR_BP(device); if (self->is_legacy_attach) { guint8 cmd[FU_CORSAIR_MAX_CMD_SIZE] = {0x08, 0x10, 0x01, 0x00, 0x03, 0x00, 0x01}; return fu_corsair_bp_command(self, cmd, CORSAIR_TRANSACTION_TIMEOUT, FALSE, error); } return fu_corsair_bp_set_mode(self, FU_CORSAIR_DEVICE_MODE_APPLICATION, error); } static gboolean fu_corsair_bp_detach(FuDevice *device, FuProgress *progress, GError **error) { FuCorsairBp *self = FU_CORSAIR_BP(device); return fu_corsair_bp_set_mode(self, FU_CORSAIR_DEVICE_MODE_BOOTLOADER, error); } static void fu_corsair_bp_to_string(FuDevice *device, guint idt, GString *str) { FuCorsairBp *self = FU_CORSAIR_BP(device); FU_DEVICE_CLASS(fu_corsair_bp_parent_class)->to_string(device, idt, str); fu_string_append_kx(str, idt, "InEndpoint", self->epin); fu_string_append_kx(str, idt, "OutEndpoint", self->epout); } static void fu_corsair_bp_class_init(FuCorsairBpClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->incorporate = fu_corsair_bp_incorporate; klass_device->write_firmware = fu_corsair_bp_write_firmware; klass_device->attach = fu_corsair_bp_attach; klass_device->detach = fu_corsair_bp_detach; klass_device->to_string = fu_corsair_bp_to_string; } FuCorsairBp * fu_corsair_bp_new(GUsbDevice *usb_device, gboolean is_subdevice) { FuCorsairBp *self = g_object_new(FU_TYPE_CORSAIR_BP, "usb_device", usb_device, NULL); if (is_subdevice) { self->destination = FU_CORSAIR_BP_DESTINATION_SUBDEVICE; } else { self->destination = FU_CORSAIR_BP_DESTINATION_SELF; } return self; } void fu_corsair_bp_set_cmd_size(FuCorsairBp *self, guint16 write_size, guint16 read_size) { self->cmd_write_size = write_size; self->cmd_read_size = read_size; } void fu_corsair_bp_set_endpoints(FuCorsairBp *self, guint8 epin, guint8 epout) { self->epin = epin; self->epout = epout; } void fu_corsair_bp_set_legacy_attach(FuCorsairBp *self, gboolean is_legacy_attach) { self->is_legacy_attach = is_legacy_attach; } fwupd-1.9.16/plugins/corsair/fu-corsair-bp.h000066400000000000000000000017701460375044200207140ustar00rootroot00000000000000/* * Copyright (C) 2021 Andrii Dushko * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-corsair-common.h" #define FU_TYPE_CORSAIR_BP (fu_corsair_bp_get_type()) G_DECLARE_FINAL_TYPE(FuCorsairBp, fu_corsair_bp, FU, CORSAIR_BP, FuUsbDevice) struct _FuCorsairBpClass { FuUsbDeviceClass parent_class; }; void fu_corsair_bp_flush_input_reports(FuCorsairBp *self); gboolean fu_corsair_bp_get_property(FuCorsairBp *self, FuCorsairBpProperty property, guint32 *value, GError **error); gboolean fu_corsair_bp_activate_firmware(FuCorsairBp *self, FuFirmware *firmware, GError **error); void fu_corsair_bp_set_cmd_size(FuCorsairBp *self, guint16 write_size, guint16 read_size); void fu_corsair_bp_set_endpoints(FuCorsairBp *self, guint8 epin, guint8 epout); void fu_corsair_bp_set_legacy_attach(FuCorsairBp *self, gboolean is_legacy_attach); FuCorsairBp * fu_corsair_bp_new(GUsbDevice *usb_device, gboolean is_subdevice); fwupd-1.9.16/plugins/corsair/fu-corsair-common.c000066400000000000000000000017341460375044200215760ustar00rootroot00000000000000/* * Copyright (C) 2022 Andrii Dushko * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-corsair-common.h" guint32 fu_corsair_calculate_crc(const guint8 *data, guint32 data_len) { gboolean bit; guint8 c; guint32 crc = 0xffffffff; while (data_len--) { c = *data++; for (guint i = 0x80; i > 0; i >>= 1) { bit = crc & 0x80000000; if (c & i) { bit = !bit; } crc <<= 1; if (bit) { crc ^= 0x04c11db7; } } } return crc; } /** * fu_corsair_version_from_uint32: * @val: version in corsair device format * * fu_version_from_uint32(... %FWUPD_VERSION_FORMAT_TRIPLET) * cannot be used because bytes in the version are in non-standard * order: 0xCCDD.BB.AA. * * Returns: a version number, e.g. `1.0.3`. **/ gchar * fu_corsair_version_from_uint32(guint32 value) { return g_strdup_printf("%u.%u.%u", value & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xffff); } fwupd-1.9.16/plugins/corsair/fu-corsair-common.h000066400000000000000000000015531460375044200216020ustar00rootroot00000000000000/* * Copyright (C) 2022 Andrii Dushko * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_CORSAIR_DEVICE_FLAG_LEGACY_ATTACH (1 << 0) #define FU_CORSAIR_DEVICE_FLAG_IS_SUBDEVICE (1 << 1) #define FU_CORSAIR_DEVICE_FLAG_NO_VERSION_IN_BOOTLOADER (1 << 2) #define FU_CORSAIR_MAX_CMD_SIZE 1024 typedef enum { FU_CORSAIR_BP_PROPERTY_MODE = 0x03, FU_CORSAIR_BP_PROPERTY_BATTERY_LEVEL = 0x0F, FU_CORSAIR_BP_PROPERTY_VERSION = 0x13, FU_CORSAIR_BP_PROPERTY_BOOTLOADER_VERSION = 0x14, FU_CORSAIR_BP_PROPERTY_SUBDEVICES = 0x36, } FuCorsairBpProperty; typedef enum { FU_CORSAIR_DEVICE_MODE_APPLICATION = 0x01, FU_CORSAIR_DEVICE_MODE_BOOTLOADER = 0x03 } FuCorsairDeviceMode; guint32 fu_corsair_calculate_crc(const guint8 *data, guint32 data_len); gchar * fu_corsair_version_from_uint32(guint32 val); fwupd-1.9.16/plugins/corsair/fu-corsair-device.c000066400000000000000000000416671460375044200215560ustar00rootroot00000000000000/* * Copyright (C) 2022 Andrii Dushko * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-corsair-bp.h" #include "fu-corsair-common.h" #include "fu-corsair-device.h" #include "fu-corsair-struct.h" #define CORSAIR_DEFAULT_VENDOR_INTERFACE_ID 1 #define CORSAIR_TRANSACTION_TIMEOUT 4000 #define CORSAIR_SUBDEVICE_POLL_PERIOD 30000 #define CORSAIR_SUBDEVICE_REBOOT_DELAY 4000 /* ms */ #define CORSAIR_SUBDEVICE_RECONNECT_RETRIES 30 #define CORSAIR_SUBDEVICE_RECONNECT_PERIOD 1000 #define CORSAIR_SUBDEVICE_FIRST_POLL_DELAY 2000 /* ms */ struct _FuCorsairDevice { FuUsbDevice parent_instance; FuCorsairDeviceKind device_kind; guint8 vendor_interface; gchar *subdevice_id; FuCorsairBp *bp; }; G_DEFINE_TYPE(FuCorsairDevice, fu_corsair_device, FU_TYPE_USB_DEVICE) static FuCorsairDevice * fu_corsair_device_new(FuCorsairDevice *parent, FuCorsairBp *bp); static gboolean fu_corsair_device_probe(FuDevice *device, GError **error) { FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); GUsbInterface *iface = NULL; GUsbEndpoint *ep1 = NULL; GUsbEndpoint *ep2 = NULL; g_autoptr(GPtrArray) ifaces = NULL; g_autoptr(GPtrArray) endpoints = NULL; guint16 cmd_write_size; guint16 cmd_read_size; guint8 epin; guint8 epout; /* probing are skipped for subdevices */ if (fu_device_has_private_flag(device, FU_CORSAIR_DEVICE_FLAG_IS_SUBDEVICE)) return TRUE; if (!FU_DEVICE_CLASS(fu_corsair_device_parent_class)->probe(device, error)) return FALSE; ifaces = g_usb_device_get_interfaces(usb_device, error); if (ifaces == NULL || (ifaces->len < (self->vendor_interface + 1u))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "update interface not found"); return FALSE; } iface = g_ptr_array_index(ifaces, self->vendor_interface); endpoints = g_usb_interface_get_endpoints(iface); /* expecting to have two endpoints for communication */ if (endpoints == NULL || endpoints->len != 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "update interface endpoints not found"); return FALSE; } ep1 = g_ptr_array_index(endpoints, 0); ep2 = g_ptr_array_index(endpoints, 1); if (g_usb_endpoint_get_direction(ep1) == G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST) { epin = g_usb_endpoint_get_address(ep1); epout = g_usb_endpoint_get_address(ep2); cmd_read_size = g_usb_endpoint_get_maximum_packet_size(ep1); cmd_write_size = g_usb_endpoint_get_maximum_packet_size(ep2); } else { epin = g_usb_endpoint_get_address(ep2); epout = g_usb_endpoint_get_address(ep1); cmd_read_size = g_usb_endpoint_get_maximum_packet_size(ep2); cmd_write_size = g_usb_endpoint_get_maximum_packet_size(ep1); } if (cmd_write_size > FU_CORSAIR_MAX_CMD_SIZE || cmd_read_size > FU_CORSAIR_MAX_CMD_SIZE) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "endpoint size is bigger than allowed command size"); return FALSE; } fu_usb_device_add_interface(FU_USB_DEVICE(self), self->vendor_interface); self->bp = fu_corsair_bp_new(usb_device, FALSE); fu_corsair_bp_set_cmd_size(self->bp, cmd_write_size, cmd_read_size); fu_corsair_bp_set_endpoints(self->bp, epin, epout); return TRUE; } static gboolean fu_corsair_poll_subdevice(FuDevice *device, gboolean *subdevice_added, GError **error) { FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); guint32 subdevices; g_autoptr(FuCorsairDevice) child = NULL; g_autoptr(FuCorsairBp) child_bp = NULL; if (!fu_corsair_bp_get_property(self->bp, FU_CORSAIR_BP_PROPERTY_SUBDEVICES, &subdevices, error)) { g_prefix_error(error, "cannot get subdevices: "); return FALSE; } if (subdevices == 0) { *subdevice_added = FALSE; return TRUE; } child_bp = fu_corsair_bp_new(usb_device, TRUE); fu_device_incorporate(FU_DEVICE(child_bp), FU_DEVICE(self->bp)); child = fu_corsair_device_new(self, child_bp); fu_device_add_instance_id(FU_DEVICE(child), self->subdevice_id); fu_device_set_logical_id(FU_DEVICE(child), "subdevice"); fu_device_add_internal_flag(FU_DEVICE(child), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); if (!fu_device_probe(FU_DEVICE(child), error)) return FALSE; if (!fu_device_setup(FU_DEVICE(child), error)) return FALSE; fu_device_add_child(device, FU_DEVICE(child)); *subdevice_added = TRUE; return TRUE; } static gchar * fu_corsair_device_get_version(FuDevice *device, GError **error) { FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); guint32 version_raw; if (!fu_corsair_bp_get_property(self->bp, FU_CORSAIR_BP_PROPERTY_VERSION, &version_raw, error)) return NULL; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { gboolean broken_by_flag = fu_device_has_private_flag(device, FU_CORSAIR_DEVICE_FLAG_NO_VERSION_IN_BOOTLOADER); /* Version 0xffffffff means that previous update was interrupted. Set version to 0.0.0 in both broken and interrupted cases to make sure that new firmware will not be rejected because of older version. It is safe to always pass firmware because setup in bootloader mode can only happen during emergency update */ if (broken_by_flag || version_raw == G_MAXUINT32) { version_raw = 0; } } return fu_corsair_version_from_uint32(version_raw); } static gchar * fu_corsair_device_get_bootloader_version(FuCorsairBp *self, GError **error) { guint32 version_raw; if (!fu_corsair_bp_get_property(self, FU_CORSAIR_BP_PROPERTY_BOOTLOADER_VERSION, &version_raw, error)) return NULL; return fu_corsair_version_from_uint32(version_raw); } static gboolean fu_corsair_device_setup(FuDevice *device, GError **error) { guint32 mode; guint32 battery_level; g_autofree gchar *bootloader_version = NULL; g_autofree gchar *version = NULL; FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); fu_corsair_bp_flush_input_reports(self->bp); if (!fu_corsair_bp_get_property(self->bp, FU_CORSAIR_BP_PROPERTY_MODE, &mode, error)) return FALSE; if (mode == FU_CORSAIR_DEVICE_MODE_BOOTLOADER) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); version = fu_corsair_device_get_version(device, error); if (version == NULL) { g_prefix_error(error, "cannot get version: "); return FALSE; } fu_device_set_version(device, version); bootloader_version = fu_corsair_device_get_bootloader_version(self->bp, error); if (bootloader_version == NULL) { g_prefix_error(error, "cannot get bootloader version: "); return FALSE; } fu_device_set_version_bootloader(device, bootloader_version); if (fu_device_has_private_flag(device, FU_CORSAIR_DEVICE_FLAG_IS_SUBDEVICE) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { if (!fu_corsair_bp_get_property(self->bp, FU_CORSAIR_BP_PROPERTY_BATTERY_LEVEL, &battery_level, error)) { g_prefix_error(error, "cannot get battery level: "); return FALSE; } fu_device_set_battery_level(device, battery_level / 10); } fu_corsair_bp_set_legacy_attach( self->bp, fu_device_has_private_flag(device, FU_CORSAIR_DEVICE_FLAG_LEGACY_ATTACH)); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); /* check for a subdevice */ if (self->subdevice_id != NULL && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { gboolean subdevice_added = FALSE; g_autoptr(GError) local_error = NULL; /* Give some time to a subdevice to get connected to the receiver. * Without this delay a subdevice may be not present even if it is * turned on. */ fu_device_sleep(device, CORSAIR_SUBDEVICE_FIRST_POLL_DELAY); if (!fu_corsair_poll_subdevice(device, &subdevice_added, &local_error)) { g_warning("error polling subdevice: %s", local_error->message); } else { /* start polling if a subdevice was not added */ if (!subdevice_added) fu_device_set_poll_interval(device, CORSAIR_SUBDEVICE_POLL_PERIOD); } } return TRUE; } static gboolean fu_corsair_device_reload(FuDevice *device, GError **error) { if (fu_device_has_private_flag(device, FU_CORSAIR_DEVICE_FLAG_IS_SUBDEVICE)) { return fu_corsair_device_setup(device, error); } /* USB devices will be reloaded by FWUPD after reenumeration */ return TRUE; } static gboolean fu_corsair_is_subdevice_connected_cb(FuDevice *device, gpointer user_data, GError **error) { guint32 subdevices = 0; FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); if (!fu_corsair_bp_get_property(self->bp, FU_CORSAIR_BP_PROPERTY_SUBDEVICES, &subdevices, error)) { g_prefix_error(error, "cannot get subdevices: "); return FALSE; } if (subdevices == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "subdevice is not connected"); return FALSE; } return TRUE; } static gboolean fu_corsair_reconnect_subdevice(FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent(device); if (parent == NULL) { g_prefix_error(error, "cannot get parent: "); return FALSE; } /* Wait some time to make sure that a subdevice was disconnected. */ fu_device_sleep(device, CORSAIR_SUBDEVICE_REBOOT_DELAY); if (!fu_device_retry_full(parent, fu_corsair_is_subdevice_connected_cb, CORSAIR_SUBDEVICE_RECONNECT_RETRIES, CORSAIR_SUBDEVICE_RECONNECT_PERIOD, NULL, error)) { g_prefix_error(error, "a subdevice did not reconnect after attach: "); return FALSE; } return TRUE; } static gboolean fu_corsair_ensure_mode(FuDevice *device, FuCorsairDeviceMode mode, GError **error) { FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); FuCorsairDeviceMode current_mode; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { current_mode = FU_CORSAIR_DEVICE_MODE_BOOTLOADER; } else { current_mode = FU_CORSAIR_DEVICE_MODE_APPLICATION; } if (mode == current_mode) return TRUE; if (mode == FU_CORSAIR_DEVICE_MODE_APPLICATION) { if (!fu_device_attach(FU_DEVICE(self->bp), error)) { g_prefix_error(error, "attach failed: "); return FALSE; } } else { if (!fu_device_detach(FU_DEVICE(self->bp), error)) { g_prefix_error(error, "detach failed: "); return FALSE; } } if (fu_device_has_private_flag(device, FU_CORSAIR_DEVICE_FLAG_IS_SUBDEVICE)) { if (!fu_corsair_reconnect_subdevice(device, error)) { g_prefix_error(error, "subdevice did not reconnect: "); return FALSE; } if (mode == FU_CORSAIR_DEVICE_MODE_BOOTLOADER) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else { fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } } else { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } return TRUE; } static gboolean fu_corsair_device_attach(FuDevice *device, FuProgress *progress, GError **error) { return fu_corsair_ensure_mode(device, FU_CORSAIR_DEVICE_MODE_APPLICATION, error); } static gboolean fu_corsair_device_detach(FuDevice *device, FuProgress *progress, GError **error) { return fu_corsair_ensure_mode(device, FU_CORSAIR_DEVICE_MODE_BOOTLOADER, error); } static gboolean fu_corsair_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); g_autoptr(GBytes) firmware_bytes = fu_firmware_get_bytes(firmware, error); if (firmware_bytes == NULL) { g_prefix_error(error, "cannot get firmware data: "); return FALSE; } fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 95, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, NULL); if (!fu_device_write_firmware(FU_DEVICE(self->bp), firmware_bytes, fu_progress_get_child(progress), flags, error)) { g_prefix_error(error, "cannot write firmware: "); return FALSE; } fu_progress_step_done(progress); if (!fu_device_has_private_flag(device, FU_CORSAIR_DEVICE_FLAG_LEGACY_ATTACH)) { if (!fu_corsair_bp_activate_firmware(self->bp, firmware, error)) { g_prefix_error(error, "firmware activation fail: "); return FALSE; } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } fu_progress_step_done(progress); return TRUE; } static void fu_corsair_device_to_string(FuDevice *device, guint idt, GString *str) { FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); FU_DEVICE_CLASS(fu_corsair_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "DeviceKind", fu_corsair_device_kind_to_string(self->device_kind)); fu_device_add_string(FU_DEVICE(self->bp), idt, str); } static void fu_corsair_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 4, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 92, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 4, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reload"); } static gboolean fu_corsair_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); guint64 vendor_interface; if (g_strcmp0(key, "CorsairDeviceKind") == 0) { self->device_kind = fu_corsair_device_kind_from_string(value); if (self->device_kind != FU_CORSAIR_DEVICE_KIND_UNKNOWN) return TRUE; g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unsupported device in quirk"); return FALSE; } if (g_strcmp0(key, "CorsairVendorInterfaceId") == 0) { /* clapped to uint8 because bNumInterfaces is 8 bits long */ if (!fu_strtoull(value, &vendor_interface, 0, 255, error)) { g_prefix_error(error, "cannot parse CorsairVendorInterface: "); return FALSE; } self->vendor_interface = vendor_interface; return TRUE; } if (g_strcmp0(key, "CorsairSubdeviceId") == 0) { self->subdevice_id = g_strdup(value); return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_corsair_device_poll(FuDevice *device, GError **error) { gboolean subdevice_added = FALSE; g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new(device, error); if (locker == NULL) { g_prefix_error(error, "cannot open device: "); return FALSE; } if (!fu_corsair_poll_subdevice(device, &subdevice_added, error)) { return FALSE; } /* stop polling if a subdevice was added */ if (subdevice_added) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "subdevice added successfully"); return FALSE; } return TRUE; } static void fu_corsair_device_finalize(GObject *object) { FuCorsairDevice *self = FU_CORSAIR_DEVICE(object); g_free(self->subdevice_id); g_object_unref(self->bp); G_OBJECT_CLASS(fu_corsair_device_parent_class)->finalize(object); } static void fu_corsair_device_class_init(FuCorsairDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); klass_device->poll = fu_corsair_device_poll; klass_device->probe = fu_corsair_device_probe; klass_device->set_quirk_kv = fu_corsair_set_quirk_kv; klass_device->setup = fu_corsair_device_setup; klass_device->reload = fu_corsair_device_reload; klass_device->attach = fu_corsair_device_attach; klass_device->detach = fu_corsair_device_detach; klass_device->write_firmware = fu_corsair_device_write_firmware; klass_device->to_string = fu_corsair_device_to_string; klass_device->set_progress = fu_corsair_device_set_progress; object_class->finalize = fu_corsair_device_finalize; } static void fu_corsair_device_init(FuCorsairDevice *device) { FuCorsairDevice *self = FU_CORSAIR_DEVICE(device); self->device_kind = FU_CORSAIR_DEVICE_KIND_MOUSE; self->vendor_interface = CORSAIR_DEFAULT_VENDOR_INTERFACE_ID; fu_device_register_private_flag(FU_DEVICE(device), FU_CORSAIR_DEVICE_FLAG_IS_SUBDEVICE, "is-subdevice"); fu_device_register_private_flag(FU_DEVICE(device), FU_CORSAIR_DEVICE_FLAG_LEGACY_ATTACH, "legacy-attach"); fu_device_register_private_flag(FU_DEVICE(device), FU_CORSAIR_DEVICE_FLAG_NO_VERSION_IN_BOOTLOADER, "no-version-in-bl"); fu_device_set_remove_delay(FU_DEVICE(device), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_set_version_format(FU_DEVICE(device), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(device), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_protocol(FU_DEVICE(device), "com.corsair.bp"); } static FuCorsairDevice * fu_corsair_device_new(FuCorsairDevice *parent, FuCorsairBp *bp) { FuCorsairDevice *self = NULL; FuDevice *device = FU_DEVICE(parent); self = g_object_new(FU_TYPE_CORSAIR_DEVICE, "context", fu_device_get_context(device), "usb_device", fu_usb_device_get_dev(FU_USB_DEVICE(device)), NULL); self->bp = g_object_ref(bp); return self; } fwupd-1.9.16/plugins/corsair/fu-corsair-device.h000066400000000000000000000006611460375044200215500ustar00rootroot00000000000000/* * Copyright (C) 2021 Andrii Dushko * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-corsair-bp.h" #include "fu-corsair-common.h" #define FU_TYPE_CORSAIR_DEVICE (fu_corsair_device_get_type()) G_DECLARE_FINAL_TYPE(FuCorsairDevice, fu_corsair_device, FU, CORSAIR_DEVICE, FuUsbDevice) struct _FuCorsairDeviceClass { FuUsbDeviceClass parent_class; }; fwupd-1.9.16/plugins/corsair/fu-corsair-plugin.c000066400000000000000000000017601460375044200216030ustar00rootroot00000000000000/* * Copyright (C) 2022 Andrii Dushko * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-corsair-device.h" #include "fu-corsair-plugin.h" struct _FuCorsairPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuCorsairPlugin, fu_corsair_plugin, FU_TYPE_PLUGIN) static void fu_corsair_plugin_init(FuCorsairPlugin *self) { } static void fu_corsair_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "CorsairDeviceKind"); fu_context_add_quirk_key(ctx, "CorsairVendorInterfaceId"); fu_context_add_quirk_key(ctx, "CorsairSubdeviceId"); fu_plugin_add_device_gtype(plugin, FU_TYPE_CORSAIR_DEVICE); } static void fu_corsair_plugin_class_init(FuCorsairPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_corsair_plugin_constructed; } fwupd-1.9.16/plugins/corsair/fu-corsair-plugin.h000066400000000000000000000003531460375044200216050ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuCorsairPlugin, fu_corsair_plugin, FU, CORSAIR_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/corsair/fu-corsair.rs000066400000000000000000000003031460375044200205010ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString, FromString)] enum CorsairDeviceKind { Unknown, Mouse, Receiver, } fwupd-1.9.16/plugins/corsair/meson.build000066400000000000000000000010201460375044200202200ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginCorsair"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += join_paths(meson.current_source_dir(), 'corsair.quirk') plugin_builtins += static_library('fu_plugin_corsair', rustgen.process('fu-corsair.rs'), sources: [ 'fu-corsair-plugin.c', 'fu-corsair-common.c', 'fu-corsair-device.c', 'fu-corsair-bp.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/cpu/000077500000000000000000000000001460375044200152125ustar00rootroot00000000000000fwupd-1.9.16/plugins/cpu/README.md000066400000000000000000000013711460375044200164730ustar00rootroot00000000000000--- title: Plugin: CPU Microcode --- ## Introduction This plugin reads the sysfs attributes associated with CPU microcode. It displays a read-only value of the CPU microcode version loaded onto the physical CPU at fwupd startup. ## GUID Generation These devices add extra instance IDs from the CPUID values, e.g. * `CPUID\PRO_0&FAM_06` (only-quirk) * `CPUID\PRO_0&FAM_06&MOD_0E` * `CPUID\PRO_0&FAM_06&MOD_0E&STP_3` ## Quirk Use This plugin uses the following plugin-specific quirks: ### CpuMitigationsRequired Mitigations required for this specific CPU. Valid values are: * `gds` Since: 1.9.4 ## External Interface Access This plugin requires no extra access. ## Version Considerations This plugin has been available since fwupd version `1.4.0`. fwupd-1.9.16/plugins/cpu/cpu.quirk000066400000000000000000000042221460375044200170560ustar00rootroot00000000000000# Intel Atom Bay Trail [Silvermont] [CPUID\PRO_0&FAM_06&MOD_37] PciBcrAddr = 0x0 # affected by Gather Data Sampling bug [CPUID\PRO_0&FAM_06&MOD_55&STP_3] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_55&STP_4] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_55&STP_6] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_55&STP_7] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_55&STP_A] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_55&STP_B] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_6A&STP_4] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_6A&STP_5] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_6A&STP_6] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_6C&STP_1] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_7E&STP_5] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_8C&STP_0] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_8C&STP_1] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_8C&STP_2] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_8D&STP_0] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_8D&STP_1] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_8E&STP_9] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_8E&STP_A] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_8E&STP_B] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_8E&STP_C] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_9E&STP_9] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_9E&STP_A] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_9E&STP_B] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_9E&STP_C] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_9E&STP_D] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_A5&STP_0] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_A5&STP_1] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_A5&STP_2] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_A5&STP_3] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_A5&STP_5] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_A6&STP_0] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_A6&STP_1] CpuMitigationsRequired = gds [CPUID\PRO_0&FAM_06&MOD_A7] CpuMitigationsRequired = gds fwupd-1.9.16/plugins/cpu/fu-cpu-device.c000066400000000000000000000327161460375044200200230ustar00rootroot00000000000000/* * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_UTSNAME_H #include #endif #include "fu-cpu-device.h" typedef enum { FU_CPU_DEVICE_FLAG_NONE = 0, FU_CPU_DEVICE_FLAG_SHSTK = 1 << 0, FU_CPU_DEVICE_FLAG_IBT = 1 << 1, FU_CPU_DEVICE_FLAG_TME = 1 << 2, FU_CPU_DEVICE_FLAG_SMAP = 1 << 3, } FuCpuDeviceFlag; struct _FuCpuDevice { FuDevice parent_instance; FuCpuDeviceFlag flags; }; G_DEFINE_TYPE(FuCpuDevice, fu_cpu_device, FU_TYPE_DEVICE) static gboolean fu_cpu_device_has_flag(FuCpuDevice *self, FuCpuDeviceFlag flag) { return (self->flags & flag) > 0; } static void fu_cpu_device_to_string(FuDevice *device, guint idt, GString *str) { FuCpuDevice *self = FU_CPU_DEVICE(device); fu_string_append_kb(str, idt, "HasSHSTK", fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_SHSTK)); fu_string_append_kb(str, idt, "HasIBT", fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_IBT)); fu_string_append_kb(str, idt, "HasTME", fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_TME)); fu_string_append_kb(str, idt, "HasSMAP", fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_SMAP)); } static const gchar * fu_cpu_device_convert_vendor(const gchar *vendor) { if (g_strcmp0(vendor, "GenuineIntel") == 0) return "Intel"; if (g_strcmp0(vendor, "AuthenticAMD") == 0 || g_strcmp0(vendor, "AMDisbetter!") == 0) return "Advanced Micro Devices, Inc."; if (g_strcmp0(vendor, "CentaurHauls") == 0) return "IDT"; if (g_strcmp0(vendor, "CyrixInstead") == 0) return "Cyrix"; if (g_strcmp0(vendor, "TransmetaCPU") == 0 || g_strcmp0(vendor, "GenuineTMx86") == 0) return "Transmeta"; if (g_strcmp0(vendor, "Geode by NSC") == 0) return "National Semiconductor"; if (g_strcmp0(vendor, "NexGenDriven") == 0) return "NexGen"; if (g_strcmp0(vendor, "RiseRiseRise") == 0) return "Rise"; if (g_strcmp0(vendor, "SiS SiS SiS ") == 0) return "SiS"; if (g_strcmp0(vendor, "UMC UMC UMC ") == 0) return "UMC"; if (g_strcmp0(vendor, "VIA VIA VIA ") == 0) return "VIA"; if (g_strcmp0(vendor, "Vortex86 SoC") == 0) return "Vortex"; if (g_strcmp0(vendor, " Shanghai ") == 0) return "Zhaoxin"; if (g_strcmp0(vendor, "HygonGenuine") == 0) return "Hygon"; if (g_strcmp0(vendor, "E2K MACHINE") == 0) return "MCST"; if (g_strcmp0(vendor, "bhyve bhyve ") == 0) return "bhyve"; if (g_strcmp0(vendor, " KVMKVMKVM ") == 0) return "KVM"; if (g_strcmp0(vendor, "TCGTCGTCGTCG") == 0) return "QEMU"; if (g_strcmp0(vendor, "Microsoft Hv") == 0) return "Microsoft"; if (g_strcmp0(vendor, " lrpepyh vr") == 0) return "Parallels"; if (g_strcmp0(vendor, "VMwareVMware") == 0) return "VMware"; if (g_strcmp0(vendor, "XenVMMXenVMM") == 0) return "Xen"; if (g_strcmp0(vendor, "ACRNACRNACRN") == 0) return "ACRN"; if (g_strcmp0(vendor, " QNXQVMBSQG ") == 0) return "QNX"; if (g_strcmp0(vendor, "VirtualApple") == 0) return "Apple"; return vendor; } static void fu_cpu_device_init(FuCpuDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_HOST_CPU); fu_device_add_icon(FU_DEVICE(self), "computer"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_HEX); fu_device_set_physical_id(FU_DEVICE(self), "cpu:0"); } static gboolean fu_cpu_device_add_instance_ids(FuDevice *device, GError **error) { guint32 eax = 0; guint32 family_id; guint32 family_id_ext; guint32 model_id; guint32 model_id_ext; guint32 processor_id; guint32 stepping_id; /* decode according to https://en.wikipedia.org/wiki/CPUID */ if (!fu_cpuid(0x1, &eax, NULL, NULL, NULL, error)) return FALSE; processor_id = (eax >> 12) & 0x3; model_id = (eax >> 4) & 0xf; family_id = (eax >> 8) & 0xf; model_id_ext = (eax >> 16) & 0xf; family_id_ext = (eax >> 20) & 0xff; stepping_id = eax & 0xf; /* use extended IDs where required */ if (family_id == 6 || family_id == 15) model_id |= model_id_ext << 4; if (family_id == 15) family_id += family_id_ext; /* add GUIDs */ fu_device_add_instance_u4(device, "PRO", processor_id); fu_device_add_instance_u8(device, "FAM", family_id); fu_device_add_instance_u8(device, "MOD", model_id); fu_device_add_instance_u4(device, "STP", stepping_id); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "CPUID", "PRO", "FAM", NULL); fu_device_build_instance_id(device, NULL, "CPUID", "PRO", "FAM", "MOD", NULL); fu_device_build_instance_id(device, NULL, "CPUID", "PRO", "FAM", "MOD", "STP", NULL); /* success */ return TRUE; } static gboolean fu_cpu_device_probe_manufacturer_id(FuDevice *device, GError **error) { guint32 ebx = 0; guint32 ecx = 0; guint32 edx = 0; gchar str[13] = {'\0'}; if (!fu_cpuid(0x0, NULL, &ebx, &ecx, &edx, error)) return FALSE; if (!fu_memcpy_safe((guint8 *)str, sizeof(str), 0x0, /* dst */ (const guint8 *)&ebx, sizeof(ebx), 0x0, /* src */ sizeof(guint32), error)) return FALSE; if (!fu_memcpy_safe((guint8 *)str, sizeof(str), 0x4, /* dst */ (const guint8 *)&edx, sizeof(edx), 0x0, /* src */ sizeof(guint32), error)) return FALSE; if (!fu_memcpy_safe((guint8 *)str, sizeof(str), 0x8, /* dst */ (const guint8 *)&ecx, sizeof(ecx), 0x0, /* src */ sizeof(guint32), error)) return FALSE; fu_device_set_vendor(device, fu_cpu_device_convert_vendor(str)); return TRUE; } static gboolean fu_cpu_device_probe_model(FuDevice *device, GError **error) { guint32 eax = 0; guint32 ebx = 0; guint32 ecx = 0; guint32 edx = 0; gchar str[49] = {'\0'}; for (guint32 i = 0; i < 3; i++) { if (!fu_cpuid(0x80000002 + i, &eax, &ebx, &ecx, &edx, error)) return FALSE; if (!fu_memcpy_safe((guint8 *)str, sizeof(str), (16 * i) + 0x0, /* dst */ (const guint8 *)&eax, sizeof(eax), 0x0, /* src */ sizeof(guint32), error)) return FALSE; if (!fu_memcpy_safe((guint8 *)str, sizeof(str), (16 * i) + 0x4, /* dst */ (const guint8 *)&ebx, sizeof(ebx), 0x0, /* src */ sizeof(guint32), error)) return FALSE; if (!fu_memcpy_safe((guint8 *)str, sizeof(str), (16 * i) + 0x8, /* dst */ (const guint8 *)&ecx, sizeof(ecx), 0x0, /* src */ sizeof(guint32), error)) return FALSE; if (!fu_memcpy_safe((guint8 *)str, sizeof(str), (16 * i) + 0xc, /* dst */ (const guint8 *)&edx, sizeof(edx), 0x0, /* src */ sizeof(guint32), error)) return FALSE; } fu_device_set_name(device, str); return TRUE; } static gboolean fu_cpu_device_probe_extended_features(FuDevice *device, GError **error) { FuCpuDevice *self = FU_CPU_DEVICE(device); guint32 ebx = 0; guint32 ecx = 0; guint32 edx = 0; if (!fu_cpuid(0x7, NULL, &ebx, &ecx, &edx, error)) return FALSE; if ((ebx >> 20) & 0x1) self->flags |= FU_CPU_DEVICE_FLAG_SMAP; if ((ecx >> 7) & 0x1) self->flags |= FU_CPU_DEVICE_FLAG_SHSTK; if (fu_cpu_get_vendor() == FU_CPU_VENDOR_INTEL) { if ((ecx >> 13) & 0x1) self->flags |= FU_CPU_DEVICE_FLAG_TME; if ((edx >> 20) & 0x1) self->flags |= FU_CPU_DEVICE_FLAG_IBT; } return TRUE; } static gboolean fu_cpu_device_probe(FuDevice *device, GError **error) { if (!fu_cpu_device_probe_manufacturer_id(device, error)) return FALSE; if (!fu_cpu_device_probe_model(device, error)) return FALSE; if (!fu_cpu_device_probe_extended_features(device, error)) return FALSE; if (!fu_cpu_device_add_instance_ids(device, error)) return FALSE; return TRUE; } static gboolean fu_cpu_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { if (g_strcmp0(key, "PciBcrAddr") == 0) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; fu_device_set_metadata_integer(device, "PciBcrAddr", tmp); return TRUE; } if (g_strcmp0(key, "CpuMitigationsRequired") == 0) { fu_device_set_metadata(device, "CpuMitigationsRequired", value); return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no supported"); return FALSE; } static void fu_cpu_device_add_security_attrs_cet_enabled(FuCpuDevice *self, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_device_security_attr_new(FU_DEVICE(self), FWUPD_SECURITY_ATTR_ID_INTEL_CET_ENABLED); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_SUPPORTED); fu_security_attrs_append(attrs, attr); switch (fu_cpu_get_vendor()) { case FU_CPU_VENDOR_INTEL: if (fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_SHSTK) && fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_IBT)) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); return; } break; case FU_CPU_VENDOR_AMD: if (fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_SHSTK)) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); return; } break; default: break; } fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); } static void fu_cpu_device_add_security_attrs_cet_active(FuCpuDevice *self, FuSecurityAttrs *attrs) { gint exit_status = 0xff; g_autofree gchar *toolfn = NULL; g_autofree gchar *dir = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(FwupdSecurityAttr) cet_plat_attr = NULL; g_autoptr(GError) error_local = NULL; /* check for CET */ cet_plat_attr = fu_security_attrs_get_by_appstream_id(attrs, FWUPD_SECURITY_ATTR_ID_INTEL_CET_ENABLED, NULL); if (cet_plat_attr == NULL) return; if (!fwupd_security_attr_has_flag(cet_plat_attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) return; /* create attr */ attr = fu_device_security_attr_new(FU_DEVICE(self), FWUPD_SECURITY_ATTR_ID_INTEL_CET_ACTIVE); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_SUPPORTED); fu_security_attrs_append(attrs, attr); /* check that userspace has been compiled for CET support */ dir = fu_path_from_kind(FU_PATH_KIND_LIBEXECDIR_PKG); toolfn = g_build_filename(dir, "fwupd-detect-cet", NULL); if (!g_spawn_command_line_sync(toolfn, NULL, NULL, &exit_status, &error_local)) { g_warning("failed to test CET: %s", error_local->message); return; } #if GLIB_CHECK_VERSION(2, 69, 2) if (!g_spawn_check_wait_status(exit_status, &error_local)) { #else if (!g_spawn_check_exit_status(exit_status, &error_local)) { #endif g_debug("CET does not function, not supported: %s", error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_cpu_device_add_security_attrs_intel_tme(FuCpuDevice *self, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_device_security_attr_new(FU_DEVICE(self), FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); /* check for TME */ if (!fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_TME)) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_cpu_device_add_security_attrs_smap(FuCpuDevice *self, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_device_security_attr_new(FU_DEVICE(self), FWUPD_SECURITY_ATTR_ID_INTEL_SMAP); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); /* check for SMEP and SMAP */ if (!fu_cpu_device_has_flag(self, FU_CPU_DEVICE_FLAG_SMAP)) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_cpu_device_add_x86_64_security_attrs(FuDevice *device, FuSecurityAttrs *attrs) { FuCpuDevice *self = FU_CPU_DEVICE(device); /* only Intel */ if (fu_cpu_get_vendor() == FU_CPU_VENDOR_INTEL) fu_cpu_device_add_security_attrs_intel_tme(self, attrs); fu_cpu_device_add_security_attrs_cet_enabled(self, attrs); fu_cpu_device_add_security_attrs_cet_active(self, attrs); fu_cpu_device_add_security_attrs_smap(self, attrs); } static void fu_cpu_device_add_security_attrs(FuDevice *device, FuSecurityAttrs *attrs) { #ifdef HAVE_UTSNAME_H struct utsname name_tmp; memset(&name_tmp, 0, sizeof(struct utsname)); if (uname(&name_tmp) < 0) { g_warning("failed to read CPU architecture"); return; } if (g_strcmp0(name_tmp.machine, "x86_64") == 0) fu_cpu_device_add_x86_64_security_attrs(device, attrs); #endif } static void fu_cpu_device_class_init(FuCpuDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_cpu_device_to_string; klass_device->probe = fu_cpu_device_probe; klass_device->set_quirk_kv = fu_cpu_device_set_quirk_kv; klass_device->add_security_attrs = fu_cpu_device_add_security_attrs; } FuCpuDevice * fu_cpu_device_new(FuContext *ctx) { FuCpuDevice *device = NULL; device = g_object_new(FU_TYPE_CPU_DEVICE, "context", ctx, NULL); return device; } fwupd-1.9.16/plugins/cpu/fu-cpu-device.h000066400000000000000000000005211460375044200200150ustar00rootroot00000000000000/* * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CPU_DEVICE (fu_cpu_device_get_type()) G_DECLARE_FINAL_TYPE(FuCpuDevice, fu_cpu_device, FU, CPU_DEVICE, FuDevice) FuCpuDevice * fu_cpu_device_new(FuContext *ctx); fwupd-1.9.16/plugins/cpu/fu-cpu-helper-cet-common.c000066400000000000000000000010251460375044200220670ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * Copyright (C) 2020 H.J. Lu * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-cpu-helper-cet-common.h" static void fu_cpu_helper_cet_testfn_fptr(void) { } static void __attribute__((noinline, noclone)) fu_cpu_helper_cet_testfn_call_fptr(void (*func)(void)) { func(); } void __attribute__((noinline, noclone)) fu_cpu_helper_cet_testfn1(void) { fu_cpu_helper_cet_testfn_call_fptr(fu_cpu_helper_cet_testfn_fptr); } fwupd-1.9.16/plugins/cpu/fu-cpu-helper-cet-common.h000066400000000000000000000003241460375044200220750ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * Copyright (C) 2020 H.J. Lu * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once void fu_cpu_helper_cet_testfn1(void); fwupd-1.9.16/plugins/cpu/fu-cpu-helper-cet.c000066400000000000000000000013641460375044200206070ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * Copyright (C) 2020 H.J. Lu * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-cpu-helper-cet-common.h" #ifdef HAVE_SIGACTION static __attribute__((noreturn)) void segfault_sigaction(int signal, siginfo_t *si, void *arg) { /* CET did exactly as it should to protect the system */ exit(0); } #endif int main(int argc, char *argv[]) { #ifdef HAVE_SIGACTION struct sigaction sa = {0}; sigemptyset(&sa.sa_mask); sa.sa_sigaction = segfault_sigaction; sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); #endif fu_cpu_helper_cet_testfn1(); /* this means CET did not work */ return 1; } fwupd-1.9.16/plugins/cpu/fu-cpu-plugin.c000066400000000000000000000030111460375044200200440ustar00rootroot00000000000000/* * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-cpu-device.h" #include "fu-cpu-plugin.h" struct _FuCpuPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuCpuPlugin, fu_cpu_plugin, FU_TYPE_PLUGIN) static gboolean fu_cpu_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(FuCpuDevice) dev = fu_cpu_device_new(ctx); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 99, "probe"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "setup"); if (!fu_device_probe(FU_DEVICE(dev), error)) return FALSE; fu_progress_step_done(progress); if (!fu_device_setup(FU_DEVICE(dev), error)) return FALSE; fu_progress_step_done(progress); fu_plugin_cache_add(plugin, "cpu", dev); fu_plugin_device_add(plugin, FU_DEVICE(dev)); return TRUE; } static void fu_cpu_plugin_init(FuCpuPlugin *self) { } static void fu_cpu_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "CpuMitigationsRequired"); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_RUN_BEFORE, "msr"); } static void fu_cpu_plugin_class_init(FuCpuPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_cpu_plugin_constructed; plugin_class->coldplug = fu_cpu_plugin_coldplug; } fwupd-1.9.16/plugins/cpu/fu-cpu-plugin.h000066400000000000000000000003371460375044200200610ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuCpuPlugin, fu_cpu_plugin, FU, CPU_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/cpu/meson.build000066400000000000000000000026571460375044200173660ustar00rootroot00000000000000if get_option('plugin_cpu').disable_auto_if(host_machine.system() != 'linux').require(hsi, error_message: 'plugin_cpu needs hsi to be set').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginCpu"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('cpu.quirk') plugin_builtins += static_library('fu_plugin_cpu', sources: [ 'fu-cpu-plugin.c', 'fu-cpu-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) code = ''' #if !__has_attribute (__noclone__) #error symver attribute not supported #endif static void __attribute__((noinline,noclone)) f(void) {} ''' # verify the compiler knows what to do if cc.has_argument('-fcf-protection') build_fwupdcethelper = cc.compiles(code, name: '__attribute__((noinline,noclone))', ) else build_fwupdcethelper = false endif if build_fwupdcethelper libfwupdcethelper = static_library('fwupdcethelper', sources: [ 'fu-cpu-helper-cet-common.c', ], include_directories: [ root_incdir, ], c_args: ['-fcf-protection=none'], install: false, ) executable( 'fwupd-detect-cet', sources: [ 'fu-cpu-helper-cet.c', ], include_directories: [ root_incdir, ], link_with: [ libfwupdcethelper, ], c_args: ['-fcf-protection=full'], install: true, install_dir: join_paths(libexecdir, 'fwupd') ) endif endif fwupd-1.9.16/plugins/cros-ec/000077500000000000000000000000001460375044200157565ustar00rootroot00000000000000fwupd-1.9.16/plugins/cros-ec/README.md000066400000000000000000000036051460375044200172410ustar00rootroot00000000000000--- title: Plugin: Chrome OS EC --- ## Introduction This plugin provides support for the firmware updates for Chrome OS EC project based devices. Initially, it supports the USB endpoint updater, but lays the groundwork for future updaters which use other update methods other than the USB endpoint. This is based on the chromeos ec project's [usb_updater2 application](https://chromium.googlesource.com/chromiumos/platform/ec/+/master/extra/usb_updater/usb_updater2.c). Information about the USB update protocol is [available here](https://chromium.googlesource.com/chromiumos/platform/ec/+/master/docs/usb_updater.md). ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in the Google [fmap file format](https://www.chromium.org/chromium-os/firmware-porting-guide/fmap). This plugin supports the following protocol ID: * `com.google.usb.crosec` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_18D1&PID_501A` ## Update Behavior The device usually presents in runtime mode, but on detach re-enumerates with the same USB PID in an unlocked mode. On attach the device again re-enumerates back to the runtime locked mode. For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. ## Vendor ID Security The vendor ID is set from the USB vendor, which is set to various different values depending on the model and device mode. The list of USB VIDs used is: * `USB:0x18D1` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.5.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Benson Leung: @bleungatchromium fwupd-1.9.16/plugins/cros-ec/cros-ec.quirk000066400000000000000000000010421460375044200203630ustar00rootroot00000000000000# Servo Micro [USB\VID_18D1&PID_501A] Plugin = cros_ec Summary = Servo Micro (aka "uServo") Debug Board # Quiche [USB\VID_18D1&PID_5048] Plugin = cros_ec Summary = Quiche Reference Board # Baklava (D501) [USB\VID_0502&PID_1195] Plugin = cros_ec Summary = D501 Device (Google Quiche derivative) # Gingerbread [USB\VID_18D1&PID_5049] Plugin = cros_ec Summary = Gingerbread Reference Board # Belkin [USB\VID_050D&PID_003F] Plugin = cros_ec Summary = Belkin Reference Board # Prism [USB\VID_18D1&PID_5022] Plugin = cros_ec Summary = Prism Board fwupd-1.9.16/plugins/cros-ec/data/000077500000000000000000000000001460375044200166675ustar00rootroot00000000000000fwupd-1.9.16/plugins/cros-ec/data/lsusb-servo-micro.txt000066400000000000000000000205531460375044200230300ustar00rootroot00000000000000 Bus 003 Device 006: ID 18d1:501a Google Inc. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x18d1 Google Inc. idProduct 0x501a bcdDevice 1.00 iManufacturer 1 Google Inc. iProduct 2 Servo Micro iSerial 3 CMO653-00166-040491U00771 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 170 bNumInterfaces 7 bConfigurationValue 1 iConfiguration 4 servo_micro_v2.4.17-df61092c3 bmAttributes 0x80 (Bus Powered) MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 80 bInterfaceProtocol 1 iInterface 6 UART3 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 10 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 83 bInterfaceProtocol 255 iInterface 10 Firmware update Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 10 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 81 bInterfaceProtocol 1 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 10 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x03 EP 3 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 3 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 80 bInterfaceProtocol 1 iInterface 7 Servo Shell Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 10 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x04 EP 4 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 4 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 82 bInterfaceProtocol 1 iInterface 5 I2C Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x85 EP 5 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 10 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x05 EP 5 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 5 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 80 bInterfaceProtocol 1 iInterface 8 CPU Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x86 EP 6 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 10 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x06 EP 6 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 6 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 80 bInterfaceProtocol 1 iInterface 9 EC Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x87 EP 7 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 10 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x07 EP 7 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 0 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/cros-ec/fu-cros-ec-common.c000066400000000000000000000034221460375044200213540ustar00rootroot00000000000000/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-cros-ec-common.h" gboolean fu_cros_ec_parse_version(const gchar *version_raw, struct cros_ec_version *version, GError **error) { gchar *ver = NULL; g_autofree gchar *board = g_strdup(version_raw); g_auto(GStrv) marker_split = NULL; g_auto(GStrv) triplet_split = NULL; if (NULL == version_raw || 0 == strlen(version_raw)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no version string to parse"); return FALSE; } /* sample version string: cheese_v1.1.1755-4da9520 */ ver = g_strrstr(board, "_v"); if (ver == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "version marker not found"); return FALSE; } *ver = '\0'; ver += 2; marker_split = g_strsplit_set(ver, "-+", 2); if (g_strv_length(marker_split) < 2) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "hash marker not found: %s", ver); return FALSE; } triplet_split = g_strsplit_set(marker_split[0], ".", 3); if (g_strv_length(triplet_split) < 3) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "improper version triplet: %s", marker_split[0]); return FALSE; } (void)g_strlcpy(version->triplet, marker_split[0], 32); if (g_strlcpy(version->boardname, board, 32) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "empty board name"); return FALSE; } if (g_strlcpy(version->sha1, marker_split[1], 32) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "empty SHA"); return FALSE; } version->dirty = (g_strrstr(ver, "+") != NULL); return TRUE; } fwupd-1.9.16/plugins/cros-ec/fu-cros-ec-common.h000066400000000000000000000101521460375044200213570ustar00rootroot00000000000000/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define UPDATE_PROTOCOL_VERSION 6 #define FU_CROS_EC_STRLEN 32 /* * This is the format of the update PDU header. * * block digest: the first four bytes of the sha1 digest of the rest of the * structure (can be 0 on boards where digest is ignored). * block_base: offset of this PDU into the flash SPI. */ typedef struct __attribute__((packed)) { guint32 block_digest; guint32 block_base; /* The actual payload goes here. */ } update_command; /* * This is the frame format the host uses when sending update PDUs over USB. * * The PDUs are up to 1K bytes in size, they are fragmented into USB chunks of * 64 bytes each and reassembled on the receive side before being passed to * the flash update function. * * The flash update function receives the unframed PDU body (starting at the * cmd field below), and puts its reply into the same buffer the PDU was in. */ struct update_frame_header { guint32 block_size; /* Total frame size, including this field. */ update_command cmd; }; /* * A convenience structure which allows to group together various revision * fields of the header created by the signer (cr50-specific). * * These fields are compared when deciding if versions of two images are the * same or when deciding which one of the available images to run. */ struct signed_header_version { guint32 minor; guint32 major; guint32 epoch; }; /* * Response to the connection establishment request. * * When responding to the very first packet of the update sequence, the * original USB update implementation was responding with a four byte value, * just as to any other block of the transfer sequence. * * It became clear that there is a need to be able to enhance the update * protocol, while staying backwards compatible. * * All newer protocol versions (starting with version 2) respond to the very * first packet with an 8 byte or larger response, where the first 4 bytes are * a version specific data, and the second 4 bytes - the protocol version * number. * * This way the host receiving of a four byte value in response to the first * packet is considered an indication of the target running the 'legacy' * protocol, version 1. Receiving of an 8 byte or longer response would * communicates the protocol version in the second 4 bytes. */ struct first_response_pdu { guint32 return_value; /* The below fields are present in versions 2 and up. */ /* Type of header following (one of first_response_pdu_header_type) */ guint16 header_type; /* Must be UPDATE_PROTOCOL_VERSION */ guint16 protocol_version; /* In version 6 and up, a board-specific header follows. */ union { /* cr50 (header_type = UPDATE_HEADER_TYPE_CR50) */ struct { /* The below fields are present in versions 3 and up. */ guint32 backup_ro_offset; guint32 backup_rw_offset; /* The below fields are present in versions 4 and up. */ /* * Versions of the currently active RO and RW sections. */ struct signed_header_version shv[2]; /* The below fields are present in versions 5 and up */ /* keyids of the currently active RO and RW sections. */ guint32 keyid[2]; } cr50; /* Common code (header_type = UPDATE_HEADER_TYPE_COMMON) */ struct { /* Maximum PDU size */ guint32 maximum_pdu_size; /* Flash protection status */ guint32 flash_protection; /* Offset of the other region */ guint32 offset; /* Version string of the other region */ gchar version[FU_CROS_EC_STRLEN]; /* Minimum rollback version that RO will accept */ gint32 min_rollback; /* RO public key version */ guint32 key_version; } common; }; }; enum first_response_pdu_header_type { UPDATE_HEADER_TYPE_CR50 = 0, /* Must be 0 for backwards compatibility */ UPDATE_HEADER_TYPE_COMMON = 1, }; struct cros_ec_version { gchar boardname[FU_CROS_EC_STRLEN]; gchar triplet[FU_CROS_EC_STRLEN]; gchar sha1[FU_CROS_EC_STRLEN]; gboolean dirty; }; gboolean fu_cros_ec_parse_version(const gchar *version_raw, struct cros_ec_version *version, GError **error); fwupd-1.9.16/plugins/cros-ec/fu-cros-ec-firmware.c000066400000000000000000000125061460375044200217030ustar00rootroot00000000000000/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-cros-ec-common.h" #include "fu-cros-ec-firmware.h" #define MAXSECTIONS 2 struct _FuCrosEcFirmware { FuFmapFirmware parent_instance; struct cros_ec_version version; GPtrArray *sections; }; G_DEFINE_TYPE(FuCrosEcFirmware, fu_cros_ec_firmware, FU_TYPE_FMAP_FIRMWARE) gboolean fu_cros_ec_firmware_pick_sections(FuCrosEcFirmware *self, guint32 writeable_offset, GError **error) { gboolean found = FALSE; for (gsize i = 0; i < self->sections->len; i++) { FuCrosEcFirmwareSection *section = g_ptr_array_index(self->sections, i); guint32 offset = section->offset; if (offset != writeable_offset) continue; section->ustatus = FU_CROS_EC_FW_NEEDED; found = TRUE; } if (!found) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no writeable section found with offset: 0x%x", writeable_offset); return FALSE; } /* success */ return TRUE; } GPtrArray * fu_cros_ec_firmware_get_needed_sections(FuCrosEcFirmware *self, GError **error) { g_autoptr(GPtrArray) needed_sections = g_ptr_array_new(); for (guint i = 0; i < self->sections->len; i++) { FuCrosEcFirmwareSection *section = g_ptr_array_index(self->sections, i); if (section->ustatus != FU_CROS_EC_FW_NEEDED) continue; g_ptr_array_add(needed_sections, section); } if (needed_sections->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no needed sections"); return NULL; } /* success */ return g_steal_pointer(&needed_sections); } static gboolean fu_cros_ec_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuCrosEcFirmware *self = FU_CROS_EC_FIRMWARE(firmware); FuFirmware *fmap_firmware = FU_FIRMWARE(firmware); for (gsize i = 0; i < self->sections->len; i++) { gboolean rw = FALSE; FuCrosEcFirmwareSection *section = g_ptr_array_index(self->sections, i); const gchar *fmap_name; const gchar *fmap_fwid_name; g_autoptr(FuFirmware) img = NULL; g_autoptr(FuFirmware) fwid_img = NULL; g_autoptr(GBytes) payload_bytes = NULL; g_autoptr(GBytes) fwid_bytes = NULL; if (g_strcmp0(section->name, "RO") == 0) { fmap_name = "EC_RO"; fmap_fwid_name = "RO_FRID"; } else if (g_strcmp0(section->name, "RW") == 0) { rw = TRUE; fmap_name = "EC_RW"; fmap_fwid_name = "RW_FWID"; } else { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "incorrect section name"); return FALSE; } img = fu_firmware_get_image_by_id(fmap_firmware, fmap_name, error); if (img == NULL) { g_prefix_error(error, "%s image not found: ", fmap_name); return FALSE; } fwid_img = fu_firmware_get_image_by_id(fmap_firmware, fmap_fwid_name, error); if (fwid_img == NULL) { g_prefix_error(error, "%s image not found: ", fmap_fwid_name); return FALSE; } fwid_bytes = fu_firmware_write(fwid_img, error); if (fwid_bytes == NULL) { g_prefix_error(error, "unable to get bytes from %s: ", fmap_fwid_name); return FALSE; } if (!fu_memcpy_safe((guint8 *)section->raw_version, FU_FMAP_FIRMWARE_STRLEN, 0x0, g_bytes_get_data(fwid_bytes, NULL), g_bytes_get_size(fwid_bytes), 0x0, g_bytes_get_size(fwid_bytes), error)) return FALSE; payload_bytes = fu_firmware_write(img, error); if (payload_bytes == NULL) { g_prefix_error(error, "unable to get bytes from %s: ", fmap_name); return FALSE; } section->offset = fu_firmware_get_addr(img); section->size = g_bytes_get_size(payload_bytes); fu_firmware_set_version(img, section->raw_version); section->image_idx = fu_firmware_get_idx(img); if (!fu_cros_ec_parse_version(section->raw_version, §ion->version, error)) { g_prefix_error(error, "failed parsing firmware's version: %32s: ", section->raw_version); return FALSE; } if (rw) { if (!fu_cros_ec_parse_version(section->raw_version, &self->version, error)) { g_prefix_error(error, "failed parsing firmware's version: %32s: ", section->raw_version); return FALSE; } fu_firmware_set_version(firmware, self->version.triplet); } } /* success */ return TRUE; } static void fu_cros_ec_firmware_init(FuCrosEcFirmware *self) { FuCrosEcFirmwareSection *section; self->sections = g_ptr_array_new_with_free_func(g_free); section = g_new0(FuCrosEcFirmwareSection, 1); section->name = "RO"; g_ptr_array_add(self->sections, section); section = g_new0(FuCrosEcFirmwareSection, 1); section->name = "RW"; g_ptr_array_add(self->sections, section); } static void fu_cros_ec_firmware_finalize(GObject *object) { FuCrosEcFirmware *self = FU_CROS_EC_FIRMWARE(object); g_ptr_array_free(self->sections, TRUE); G_OBJECT_CLASS(fu_cros_ec_firmware_parent_class)->finalize(object); } static void fu_cros_ec_firmware_class_init(FuCrosEcFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFmapFirmwareClass *klass_firmware = FU_FMAP_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_cros_ec_firmware_parse; object_class->finalize = fu_cros_ec_firmware_finalize; } FuFirmware * fu_cros_ec_firmware_new(void) { return g_object_new(FU_TYPE_CROS_EC_FIRMWARE, NULL); } fwupd-1.9.16/plugins/cros-ec/fu-cros-ec-firmware.h000066400000000000000000000025051460375044200217060ustar00rootroot00000000000000/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-cros-ec-common.h" #define FU_TYPE_CROS_EC_FIRMWARE (fu_cros_ec_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuCrosEcFirmware, fu_cros_ec_firmware, FU, CROS_EC_FIRMWARE, FuFmapFirmware) /* * Each RO or RW section of the new image can be in one of the following * states. */ typedef enum { FU_CROS_EC_FW_NOT_NEEDED = 0, /* Version below or equal that on the target. */ FU_CROS_EC_FW_NOT_POSSIBLE, /* * RO is newer, but can't be transferred due to * target RW shortcomings. */ FU_CROS_EC_FW_NEEDED /* * This section needs to be transferred to the * target. */ } FuCrosEcFirmwareUpgradeStatus; typedef struct { const gchar *name; guint32 offset; gsize size; FuCrosEcFirmwareUpgradeStatus ustatus; gchar raw_version[FU_FMAP_FIRMWARE_STRLEN]; struct cros_ec_version version; gint32 rollback; guint32 key_version; guint64 image_idx; } FuCrosEcFirmwareSection; gboolean fu_cros_ec_firmware_pick_sections(FuCrosEcFirmware *self, guint32 writeable_offset, GError **error); GPtrArray * fu_cros_ec_firmware_get_needed_sections(FuCrosEcFirmware *self, GError **error); FuFirmware * fu_cros_ec_firmware_new(void); fwupd-1.9.16/plugins/cros-ec/fu-cros-ec-plugin.c000066400000000000000000000015001460375044200213550ustar00rootroot00000000000000/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-cros-ec-firmware.h" #include "fu-cros-ec-plugin.h" #include "fu-cros-ec-usb-device.h" struct _FuCrosEcPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuCrosEcPlugin, fu_cros_ec_plugin, FU_TYPE_PLUGIN) static void fu_cros_ec_plugin_init(FuCrosEcPlugin *self) { } static void fu_cros_ec_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_CROS_EC_USB_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_CROS_EC_FIRMWARE); } static void fu_cros_ec_plugin_class_init(FuCrosEcPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_cros_ec_plugin_constructed; } fwupd-1.9.16/plugins/cros-ec/fu-cros-ec-plugin.h000066400000000000000000000003521460375044200213660ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuCrosEcPlugin, fu_cros_ec_plugin, FU, CROS_EC_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/cros-ec/fu-cros-ec-usb-device.c000066400000000000000000000761041460375044200221210ustar00rootroot00000000000000/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-cros-ec-common.h" #include "fu-cros-ec-firmware.h" #include "fu-cros-ec-usb-device.h" #define USB_SUBCLASS_GOOGLE_UPDATE 0x53 #define USB_PROTOCOL_GOOGLE_UPDATE 0xff #define SETUP_RETRY_CNT 5 #define MAX_BLOCK_XFER_RETRIES 10 #define FLUSH_TIMEOUT_MS 10 #define BULK_SEND_TIMEOUT_MS 2000 #define BULK_RECV_TIMEOUT_MS 5000 #define CROS_EC_REMOVE_DELAY_RE_ENUMERATE 20000 #define UPDATE_DONE 0xB007AB1E #define UPDATE_EXTRA_CMD 0xB007AB1F enum update_extra_command { UPDATE_EXTRA_CMD_IMMEDIATE_RESET = 0, UPDATE_EXTRA_CMD_JUMP_TO_RW = 1, UPDATE_EXTRA_CMD_STAY_IN_RO = 2, UPDATE_EXTRA_CMD_UNLOCK_RW = 3, UPDATE_EXTRA_CMD_UNLOCK_ROLLBACK = 4, UPDATE_EXTRA_CMD_INJECT_ENTROPY = 5, UPDATE_EXTRA_CMD_PAIR_CHALLENGE = 6, UPDATE_EXTRA_CMD_TOUCHPAD_INFO = 7, UPDATE_EXTRA_CMD_TOUCHPAD_DEBUG = 8, UPDATE_EXTRA_CMD_CONSOLE_READ_INIT = 9, UPDATE_EXTRA_CMD_CONSOLE_READ_NEXT = 10, }; struct _FuCrosEcUsbDevice { FuUsbDevice parent_instance; guint8 iface_idx; /* bInterfaceNumber */ guint8 ep_num; /* bEndpointAddress */ guint16 chunk_len; /* wMaxPacketSize */ struct first_response_pdu targ; guint32 writeable_offset; guint16 protocol_version; guint16 header_type; struct cros_ec_version version; /* version of other region */ struct cros_ec_version active_version; /* version of active region */ gchar configuration[FU_CROS_EC_STRLEN]; gboolean in_bootloader; }; G_DEFINE_TYPE(FuCrosEcUsbDevice, fu_cros_ec_usb_device, FU_TYPE_USB_DEVICE) typedef union _START_RESP { struct first_response_pdu rpdu; guint32 legacy_resp; } START_RESP; typedef struct { FuChunk *block; FuProgress *progress; } FuCrosEcUsbBlockHelper; #define FU_CROS_EC_USB_DEVICE_FLAG_RO_WRITTEN (1 << 0) #define FU_CROS_EC_USB_DEVICE_FLAG_RW_WRITTEN (1 << 1) #define FU_CROS_EC_USB_DEVICE_FLAG_REBOOTING_TO_RO (1 << 2) #define FU_CROS_EC_USB_DEVICE_FLAG_SPECIAL (1 << 3) static gboolean fu_cros_ec_usb_device_get_configuration(FuCrosEcUsbDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); guint8 index; g_autofree gchar *configuration = NULL; index = g_usb_device_get_configuration_index(usb_device); configuration = g_usb_device_get_string_descriptor(usb_device, index, error); if (configuration == NULL) return FALSE; if (g_strlcpy(self->configuration, configuration, FU_CROS_EC_STRLEN) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "empty iConfiguration"); return FALSE; } /* success */ return TRUE; } static gboolean fu_cros_ec_usb_device_find_interface(FuUsbDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(device); FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); g_autoptr(GPtrArray) intfs = NULL; /* based on usb_updater2's find_interfacei() and find_endpoint() */ intfs = g_usb_device_get_interfaces(usb_device, error); if (intfs == NULL) return FALSE; for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index(intfs, i); if (g_usb_interface_get_class(intf) == 255 && g_usb_interface_get_subclass(intf) == USB_SUBCLASS_GOOGLE_UPDATE && g_usb_interface_get_protocol(intf) == USB_PROTOCOL_GOOGLE_UPDATE) { GUsbEndpoint *ep; g_autoptr(GPtrArray) endpoints = g_usb_interface_get_endpoints(intf); if (NULL == endpoints || 0 == endpoints->len) continue; ep = g_ptr_array_index(endpoints, 0); self->iface_idx = g_usb_interface_get_number(intf); self->ep_num = g_usb_endpoint_get_address(ep) & 0x7f; self->chunk_len = g_usb_endpoint_get_maximum_packet_size(ep); return TRUE; } } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no update interface found"); return FALSE; } static gboolean fu_cros_ec_usb_device_probe(FuDevice *device, GError **error) { FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); /* very much like usb_updater2's usb_findit() */ if (!fu_cros_ec_usb_device_find_interface(FU_USB_DEVICE(device), error)) { g_prefix_error(error, "failed to find update interface: "); return FALSE; } fu_usb_device_add_interface(FU_USB_DEVICE(self), self->iface_idx); if (self->chunk_len == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "wMaxPacketSize isn't valid: %" G_GUINT16_FORMAT, self->chunk_len); return FALSE; } /* success */ return TRUE; } static gboolean fu_cros_ec_usb_device_do_xfer(FuCrosEcUsbDevice *self, const guint8 *outbuf, gsize outlen, guint8 *inbuf, gsize inlen, gboolean allow_less, gsize *rxed_count, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual = 0; /* send data out */ if (outbuf != NULL && outlen > 0) { g_autofree guint8 *outbuf_tmp = NULL; /* make mutable */ outbuf_tmp = fu_memdup_safe(outbuf, outlen, error); if (outbuf_tmp == NULL) return FALSE; if (!g_usb_device_bulk_transfer(usb_device, self->ep_num, outbuf_tmp, outlen, &actual, BULK_SEND_TIMEOUT_MS, NULL, error)) { return FALSE; } if (actual != outlen) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, "only sent %" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT " bytes", actual, outlen); return FALSE; } } /* read reply back */ if (inbuf != NULL && inlen > 0) { actual = 0; if (!g_usb_device_bulk_transfer(usb_device, self->ep_num | 0x80, inbuf, inlen, &actual, BULK_RECV_TIMEOUT_MS, NULL, error)) { return FALSE; } if (actual != inlen && !allow_less) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, "only received %" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT " bytes", actual, inlen); return FALSE; } } if (rxed_count != NULL) *rxed_count = actual; return TRUE; } static gboolean fu_cros_ec_usb_device_flush(FuDevice *device, gpointer user_data, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); gsize actual = 0; g_autofree guint8 *inbuf = g_malloc0(self->chunk_len); /* bulk transfer expected to fail normally (ie, no stale data) * but if bulk transfer succeeds, indicates stale bytes on the device * so this will retry until they're emptied */ if (g_usb_device_bulk_transfer(usb_device, self->ep_num | 0x80, inbuf, self->chunk_len, &actual, FLUSH_TIMEOUT_MS, NULL, NULL)) { g_debug("flushing %" G_GSIZE_FORMAT " bytes", actual); g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "flushing %" G_GSIZE_FORMAT " bytes", actual); return FALSE; } /* success */ return TRUE; } static gboolean fu_cros_ec_usb_device_recovery(FuDevice *device, GError **error) { /* flush all data from endpoint to recover in case of error */ if (!fu_device_retry(device, fu_cros_ec_usb_device_flush, SETUP_RETRY_CNT, NULL, error)) { g_prefix_error(error, "failed to flush device to idle state: "); return FALSE; } /* success */ return TRUE; } /* * Channel TPM extension/vendor command over USB. The payload of the USB frame * in this case consists of the 2 byte subcommand code concatenated with the * command body. The caller needs to indicate if a response is expected, and * if it is - of what maximum size. */ static gboolean fu_cros_ec_usb_ext_cmd(FuDevice *device, guint16 subcommand, gpointer cmd_body, gsize body_size, gpointer resp, gsize *resp_size, gboolean allow_less, GError **error) { FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); guint16 *frame_ptr; gsize usb_msg_size = sizeof(struct update_frame_header) + sizeof(subcommand) + body_size; g_autofree struct update_frame_header *ufh = g_malloc0(usb_msg_size); ufh->block_size = GUINT32_TO_BE(usb_msg_size); ufh->cmd.block_digest = 0; ufh->cmd.block_base = GUINT32_TO_BE(UPDATE_EXTRA_CMD); frame_ptr = (guint16 *)(ufh + 1); *frame_ptr = GUINT16_TO_BE(subcommand); if (body_size != 0) { gsize offset = sizeof(struct update_frame_header) + sizeof(subcommand); if (!fu_memcpy_safe((guint8 *)ufh, usb_msg_size, offset, (const guint8 *)cmd_body, body_size, 0x0, body_size, error)) return FALSE; } return fu_cros_ec_usb_device_do_xfer(self, (const guint8 *)ufh, usb_msg_size, (guint8 *)resp, resp_size != NULL ? *resp_size : 0, TRUE, NULL, error); } static gboolean fu_cros_ec_usb_device_start_request(FuDevice *device, gpointer user_data, GError **error) { FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); guint8 *start_resp = (guint8 *)user_data; struct update_frame_header ufh = {0x0}; gsize rxed_size = 0; ufh.block_size = GUINT32_TO_BE(sizeof(ufh)); if (!fu_cros_ec_usb_device_do_xfer(self, (const guint8 *)&ufh, sizeof(ufh), start_resp, sizeof(START_RESP), TRUE, &rxed_size, error)) return FALSE; /* we got something, so check for errors in response */ if (rxed_size < 8) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, "unexpected response size %" G_GSIZE_FORMAT, rxed_size); return FALSE; } /* success */ return TRUE; } static gboolean fu_cros_ec_usb_device_setup(FuDevice *device, GError **error) { FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); guint32 error_code; START_RESP start_resp = {0x0}; g_auto(GStrv) config_split = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_cros_ec_usb_device_parent_class)->setup(device, error)) return FALSE; if (!fu_cros_ec_usb_device_recovery(device, error)) return FALSE; /* send start request */ if (!fu_device_retry(device, fu_cros_ec_usb_device_start_request, SETUP_RETRY_CNT, &start_resp, error)) { g_prefix_error(error, "failed to send start request: "); return FALSE; } self->protocol_version = GUINT16_FROM_BE(start_resp.rpdu.protocol_version); if (self->protocol_version < 5 || self->protocol_version > 6) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unsupported protocol version %d", self->protocol_version); return FALSE; } self->header_type = GUINT16_FROM_BE(start_resp.rpdu.header_type); error_code = GUINT32_FROM_BE(start_resp.rpdu.return_value); if (error_code != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "target reporting error %u", error_code); return FALSE; } self->writeable_offset = GUINT32_FROM_BE(start_resp.rpdu.common.offset); if (!fu_memcpy_safe((guint8 *)self->targ.common.version, FU_CROS_EC_STRLEN, 0x0, (const guint8 *)start_resp.rpdu.common.version, sizeof(start_resp.rpdu.common.version), 0x0, sizeof(start_resp.rpdu.common.version), error)) return FALSE; self->targ.common.maximum_pdu_size = GUINT32_FROM_BE(start_resp.rpdu.common.maximum_pdu_size); self->targ.common.flash_protection = GUINT32_FROM_BE(start_resp.rpdu.common.flash_protection); self->targ.common.min_rollback = GINT32_FROM_BE(start_resp.rpdu.common.min_rollback); self->targ.common.key_version = GUINT32_FROM_BE(start_resp.rpdu.common.key_version); /* get active version string and running region from iConfiguration */ if (!fu_cros_ec_usb_device_get_configuration(self, error)) return FALSE; config_split = g_strsplit(self->configuration, ":", 2); if (g_strv_length(config_split) < 2) { /* no prefix found so fall back to offset */ self->in_bootloader = self->writeable_offset != 0x0; if (!fu_cros_ec_parse_version(self->configuration, &self->active_version, error)) { g_prefix_error(error, "failed parsing device's version: %32s: ", self->configuration); return FALSE; } } else { self->in_bootloader = g_strcmp0("RO", config_split[0]) == 0; if (!fu_cros_ec_parse_version(config_split[1], &self->active_version, error)) { g_prefix_error(error, "failed parsing device's version: %32s: ", config_split[1]); return FALSE; } } /* get the other region's version string from targ */ if (!fu_cros_ec_parse_version(self->targ.common.version, &self->version, error)) { g_prefix_error(error, "failed parsing device's version: %32s: ", self->targ.common.version); return FALSE; } if (self->in_bootloader) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_set_version(FU_DEVICE(device), self->version.triplet); fu_device_set_version_bootloader(FU_DEVICE(device), self->active_version.triplet); } else { fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_set_version(FU_DEVICE(device), self->active_version.triplet); fu_device_set_version_bootloader(FU_DEVICE(device), self->version.triplet); } fu_device_add_instance_id(FU_DEVICE(device), self->version.boardname); /* success */ return TRUE; } static gboolean fu_cros_ec_usb_device_reload(FuDevice *device, GError **error) { fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); return fu_cros_ec_usb_device_setup(device, error); } static gboolean fu_cros_ec_usb_device_transfer_block(FuDevice *device, gpointer user_data, GError **error) { FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); FuCrosEcUsbBlockHelper *helper = (FuCrosEcUsbBlockHelper *)user_data; gsize transfer_size = 0; guint32 reply = 0; g_autoptr(GPtrArray) chunks = NULL; struct update_frame_header ufh = { .block_size = GUINT32_TO_BE(fu_chunk_get_data_sz(helper->block) + sizeof(struct update_frame_header)), .cmd.block_base = GUINT32_TO_BE(fu_chunk_get_address(helper->block)), .cmd.block_digest = 0, }; /* first send the header */ if (!fu_cros_ec_usb_device_do_xfer(self, (const guint8 *)&ufh, sizeof(struct update_frame_header), NULL, 0, FALSE, NULL, error)) { g_autoptr(GError) error_flush = NULL; /* flush all data from endpoint to recover in case of error */ if (!fu_cros_ec_usb_device_recovery(device, &error_flush)) { g_debug("failed to flush to idle: %s", error_flush->message); } g_prefix_error(error, "failed at sending header: "); return FALSE; } /* send the block, chunk by chunk */ chunks = fu_chunk_array_new(fu_chunk_get_data(helper->block), fu_chunk_get_data_sz(helper->block), 0x00, 0x00, self->chunk_len); fu_progress_set_id(helper->progress, G_STRLOC); fu_progress_set_steps(helper->progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_cros_ec_usb_device_do_xfer(self, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), NULL, 0, FALSE, NULL, error)) { g_autoptr(GError) error_flush = NULL; g_prefix_error(error, "failed sending chunk 0x%x: ", i); /* flush all data from endpoint to recover in case of error */ if (!fu_cros_ec_usb_device_recovery(device, &error_flush)) { g_debug("failed to flush to idle: %s", error_flush->message); } return FALSE; } fu_progress_step_done(helper->progress); } /* get the reply */ if (!fu_cros_ec_usb_device_do_xfer(self, NULL, 0, (guint8 *)&reply, sizeof(reply), TRUE, &transfer_size, error)) { g_autoptr(GError) error_flush = NULL; g_prefix_error(error, "failed at reply: "); /* flush all data from endpoint to recover in case of error */ if (!fu_cros_ec_usb_device_recovery(device, &error_flush)) { g_debug("failed to flush to idle: %s", error_flush->message); } return FALSE; } if (transfer_size == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "zero bytes received for block reply"); return FALSE; } if (reply != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "error: status 0x%#x", reply); return FALSE; } /* success */ return TRUE; } static gboolean fu_cros_ec_usb_device_transfer_section(FuDevice *device, FuFirmware *firmware, FuCrosEcFirmwareSection *section, FuProgress *progress, GError **error) { FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); const guint8 *data_ptr = NULL; gsize data_len = 0; g_autoptr(GBytes) img_bytes = NULL; g_autoptr(GPtrArray) blocks = NULL; g_return_val_if_fail(section != NULL, FALSE); img_bytes = fu_firmware_get_image_by_idx_bytes(firmware, section->image_idx, error); if (img_bytes == NULL) { g_prefix_error(error, "failed to find section image: "); return FALSE; } data_ptr = (const guint8 *)g_bytes_get_data(img_bytes, &data_len); if (data_ptr == NULL || data_len != section->size) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "image and section sizes do not match: image = %" G_GSIZE_FORMAT " bytes vs section size = %" G_GSIZE_FORMAT " bytes", data_len, section->size); return FALSE; } /* smart update: trim trailing bytes */ while (data_len != 0 && (data_ptr[data_len - 1] == 0xff)) data_len--; g_debug("trimmed %" G_GSIZE_FORMAT " trailing bytes", section->size - data_len); g_debug("sending 0x%x bytes to 0x%x", (guint)data_len, section->offset); /* send in chunks of PDU size */ blocks = fu_chunk_array_new(data_ptr, data_len, section->offset, 0x0, self->targ.common.maximum_pdu_size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, blocks->len); for (guint i = 0; i < blocks->len; i++) { FuCrosEcUsbBlockHelper helper = { .block = g_ptr_array_index(blocks, i), .progress = fu_progress_get_child(progress), }; if (!fu_device_retry(device, fu_cros_ec_usb_device_transfer_block, MAX_BLOCK_XFER_RETRIES, &helper, error)) { g_prefix_error(error, "failed to transfer block 0x%x: ", i); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static void fu_cros_ec_usb_device_send_done(FuDevice *device) { guint32 out = GUINT32_TO_BE(UPDATE_DONE); g_autoptr(GError) error_local = NULL; /* send stop request, ignoring reply */ if (!fu_cros_ec_usb_device_do_xfer(FU_CROS_EC_USB_DEVICE(device), (const guint8 *)&out, sizeof(out), (guint8 *)&out, 1, FALSE, NULL, &error_local)) { g_debug("error on transfer of done: %s", error_local->message); } } static gboolean fu_cros_ec_usb_device_send_subcommand(FuDevice *device, guint16 subcommand, gpointer cmd_body, gsize body_size, gpointer resp, gsize *resp_size, gboolean allow_less, GError **error) { fu_cros_ec_usb_device_send_done(device); if (!fu_cros_ec_usb_ext_cmd(device, subcommand, cmd_body, body_size, resp, resp_size, FALSE, error)) { g_prefix_error(error, "failed to send subcommand %" G_GUINT16_FORMAT ": ", subcommand); return FALSE; } /* success */ return TRUE; } static gboolean fu_cros_ec_usb_device_reset_to_ro(FuDevice *device, GError **error) { guint8 response = 0x0; guint16 subcommand = UPDATE_EXTRA_CMD_IMMEDIATE_RESET; guint8 command_body[2] = {0x0}; /* max command body size */ gsize command_body_size = 0; gsize response_size = 1; g_autoptr(GError) error_local = NULL; fu_device_add_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_REBOOTING_TO_RO); if (!fu_cros_ec_usb_device_send_subcommand(device, subcommand, command_body, command_body_size, &response, &response_size, FALSE, &error_local)) { /* failure here is ok */ g_debug("ignoring failure: %s", error_local->message); } /* success */ return TRUE; } static gboolean fu_cros_ec_usb_device_jump_to_rw(FuDevice *device) { guint8 response = 0x0; guint16 subcommand = UPDATE_EXTRA_CMD_JUMP_TO_RW; guint8 command_body[2] = {0x0}; /* max command body size */ gsize command_body_size = 0; gsize response_size = 1; if (!fu_cros_ec_usb_device_send_subcommand(device, subcommand, command_body, command_body_size, &response, &response_size, FALSE, NULL)) { /* bail out early here if subcommand failed, which is normal */ return TRUE; } /* Jump to rw may not work, so if we've reached here, initiate a * full reset using immediate reset */ subcommand = UPDATE_EXTRA_CMD_IMMEDIATE_RESET; fu_cros_ec_usb_device_send_subcommand(device, subcommand, command_body, command_body_size, &response, &response_size, FALSE, NULL); /* success */ return TRUE; } static gboolean fu_cros_ec_usb_device_stay_in_ro(FuDevice *device, GError **error) { gsize response_size = 1; guint8 response = 0x0; guint16 subcommand = UPDATE_EXTRA_CMD_STAY_IN_RO; guint8 command_body[2] = {0x0}; /* max command body size */ gsize command_body_size = 0; if (!fu_cros_ec_usb_device_send_subcommand(device, subcommand, command_body, command_body_size, &response, &response_size, FALSE, error)) { return FALSE; } return TRUE; } static gboolean fu_cros_ec_usb_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); g_autoptr(GPtrArray) sections = NULL; FuCrosEcFirmware *cros_ec_firmware = FU_CROS_EC_FIRMWARE(firmware); fu_device_remove_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_SPECIAL); if (fu_device_has_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_REBOOTING_TO_RO)) { START_RESP start_resp = {0x0}; fu_device_remove_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_REBOOTING_TO_RO); if (!fu_cros_ec_usb_device_stay_in_ro(device, error)) { g_prefix_error(error, "failed to send stay-in-ro subcommand: "); return FALSE; } /* flush all data from endpoint to recover in case of error */ if (!fu_cros_ec_usb_device_recovery(device, error)) { g_prefix_error(error, "failed to flush device to idle state: "); return FALSE; } /* send start request */ if (!fu_device_retry(device, fu_cros_ec_usb_device_start_request, SETUP_RETRY_CNT, &start_resp, error)) { g_prefix_error(error, "failed to send start request: "); return FALSE; } } if (fu_device_has_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_RW_WRITTEN) && self->in_bootloader) { /* * We had previously written to the rw region (while we were * booted from ro region), but somehow landed in ro again after * a reboot. Since we wrote rw already, we wanted to jump * to the new rw so we could evaluate ro. * * This is a transitory state due to the fact that we have to * boot through RO to get to RW. Set another write required to * allow the RO region to auto-jump to RW. * * Special flow: write phase skips actual write -> attach skips * send of reset command, just sets wait for replug, and * device restart status. */ fu_device_add_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_SPECIAL); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); return TRUE; } sections = fu_cros_ec_firmware_get_needed_sections(cros_ec_firmware, error); if (sections == NULL) return FALSE; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, sections->len); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < sections->len; i++) { FuCrosEcFirmwareSection *section = g_ptr_array_index(sections, i); g_autoptr(GError) error_local = NULL; if (!fu_cros_ec_usb_device_transfer_section(device, firmware, section, fu_progress_get_child(progress), &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NOT_SUPPORTED)) { g_debug("failed to transfer section, trying another write, " "ignoring error: %s", error_local->message); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); fu_progress_finished(progress); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } if (self->in_bootloader) { fu_device_set_version(FU_DEVICE(device), section->version.triplet); } else { fu_device_set_version_bootloader(FU_DEVICE(device), section->version.triplet); } fu_progress_step_done(progress); } /* send done */ fu_cros_ec_usb_device_send_done(device); if (self->in_bootloader) fu_device_add_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_RW_WRITTEN); else fu_device_add_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_RO_WRITTEN); /* logical XOR */ if (fu_device_has_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_RW_WRITTEN) != fu_device_has_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_RO_WRITTEN)) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); /* success */ return TRUE; } static FuFirmware * fu_cros_ec_usb_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); FuCrosEcFirmware *cros_ec_firmware = NULL; g_autoptr(FuFirmware) firmware = fu_cros_ec_firmware_new(); if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; cros_ec_firmware = FU_CROS_EC_FIRMWARE(firmware); /* pick sections */ if (!fu_cros_ec_firmware_pick_sections(cros_ec_firmware, self->writeable_offset, error)) { g_prefix_error(error, "failed to pick sections: "); return NULL; } return g_steal_pointer(&firmware); } static gboolean fu_cros_ec_usb_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); fu_device_set_remove_delay(device, CROS_EC_REMOVE_DELAY_RE_ENUMERATE); if (self->in_bootloader && fu_device_has_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_SPECIAL)) { /* * attach after the SPECIAL flag was set. The EC will auto-jump * from ro -> rw, so we do not need to send an explicit * reset_to_ro. We just need to set for another wait for replug * as a detach + reenumeration is expected as we jump from * ro -> rw. */ fu_device_remove_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_SPECIAL); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } if (fu_device_has_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_RO_WRITTEN) && !fu_device_has_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_RW_WRITTEN)) { if (!fu_cros_ec_usb_device_reset_to_ro(device, error)) { return FALSE; } } else { fu_cros_ec_usb_device_jump_to_rw(device); } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* success */ return TRUE; } static gboolean fu_cros_ec_usb_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); if (fu_device_has_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_RW_WRITTEN) && !fu_device_has_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_RO_WRITTEN)) return TRUE; if (self->in_bootloader) { /* If EC just rebooted - prevent jumping to RW during the update */ fu_device_add_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_REBOOTING_TO_RO); g_debug("skipping immediate reboot in case of already in bootloader"); /* in RO so skip reboot */ return TRUE; } if (self->targ.common.flash_protection != 0x0) { /* in RW, and RO region is write protected, so jump to RO */ fu_device_add_private_flag(device, FU_CROS_EC_USB_DEVICE_FLAG_RO_WRITTEN); fu_device_set_remove_delay(device, CROS_EC_REMOVE_DELAY_RE_ENUMERATE); if (!fu_cros_ec_usb_device_reset_to_ro(device, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } /* success */ return TRUE; } static void fu_cros_ec_usb_device_init(FuCrosEcUsbDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.google.usb.crosec"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_register_private_flag(FU_DEVICE(self), FU_CROS_EC_USB_DEVICE_FLAG_RO_WRITTEN, "ro-written"); fu_device_register_private_flag(FU_DEVICE(self), FU_CROS_EC_USB_DEVICE_FLAG_RW_WRITTEN, "rw-written"); fu_device_register_private_flag(FU_DEVICE(self), FU_CROS_EC_USB_DEVICE_FLAG_REBOOTING_TO_RO, "rebooting-to-ro"); fu_device_register_private_flag(FU_DEVICE(self), FU_CROS_EC_USB_DEVICE_FLAG_SPECIAL, "special"); } static void fu_cros_ec_usb_device_to_string(FuDevice *device, guint idt, GString *str) { FuCrosEcUsbDevice *self = FU_CROS_EC_USB_DEVICE(device); g_autofree gchar *min_rollback = NULL; fu_string_append(str, idt, "GitHash", self->version.sha1); fu_string_append_kb(str, idt, "Dirty", self->version.dirty); fu_string_append_ku(str, idt, "ProtocolVersion", self->protocol_version); fu_string_append_ku(str, idt, "HeaderType", self->header_type); fu_string_append_ku(str, idt, "MaxPDUSize", self->targ.common.maximum_pdu_size); fu_string_append_kx(str, idt, "FlashProtectionStatus", self->targ.common.flash_protection); fu_string_append(str, idt, "RawVersion", self->targ.common.version); fu_string_append_ku(str, idt, "KeyVersion", self->targ.common.key_version); min_rollback = g_strdup_printf("%" G_GINT32_FORMAT, self->targ.common.min_rollback); fu_string_append(str, idt, "MinRollback", min_rollback); fu_string_append_kx(str, idt, "WriteableOffset", self->writeable_offset); } static void fu_cros_ec_usb_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 76, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 22, "reload"); } static void fu_cros_ec_usb_device_class_init(FuCrosEcUsbDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->attach = fu_cros_ec_usb_device_attach; klass_device->detach = fu_cros_ec_usb_device_detach; klass_device->prepare_firmware = fu_cros_ec_usb_device_prepare_firmware; klass_device->setup = fu_cros_ec_usb_device_setup; klass_device->to_string = fu_cros_ec_usb_device_to_string; klass_device->write_firmware = fu_cros_ec_usb_device_write_firmware; klass_device->probe = fu_cros_ec_usb_device_probe; klass_device->set_progress = fu_cros_ec_usb_device_set_progress; klass_device->reload = fu_cros_ec_usb_device_reload; } fwupd-1.9.16/plugins/cros-ec/fu-cros-ec-usb-device.h000066400000000000000000000004741460375044200221230ustar00rootroot00000000000000/* * Copyright (C) 2020 Benson Leung * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CROS_EC_USB_DEVICE (fu_cros_ec_usb_device_get_type()) G_DECLARE_FINAL_TYPE(FuCrosEcUsbDevice, fu_cros_ec_usb_device, FU, CROS_EC_USB_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/cros-ec/meson.build000066400000000000000000000007601460375044200201230ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginCrosEc"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('cros-ec.quirk') plugin_builtins += static_library('fu_plugin_cros_ec', sources: [ 'fu-cros-ec-plugin.c', 'fu-cros-ec-usb-device.c', 'fu-cros-ec-common.c', # fuzzing 'fu-cros-ec-firmware.c', # fuzzing ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/cros-ec/tests/000077500000000000000000000000001460375044200171205ustar00rootroot00000000000000fwupd-1.9.16/plugins/cros-ec/tests/cros-ec.bin000066400000000000000000000005401460375044200211440ustar00rootroot00000000000000__FMAP__0` RO_FRID RW_FWID  EC_RO@ EC_RWcheese_v1.1.1755-4da9520 cheese_v1.1.1755-4da9520 cheese_v1.1.1755-4da9520 cheese_v1.1.1755-4da9520 fwupd-1.9.16/plugins/cros-ec/tests/cros-ec.builder.xml000066400000000000000000000007721460375044200226300ustar00rootroot00000000000000 0x3000 RO_FRID Y2hlZXNlX3YxLjEuMTc1NS00ZGE5NTIwICAgICAgICA= RW_FWID Y2hlZXNlX3YxLjEuMTc1NS00ZGE5NTIwICAgICAgICA= EC_RO Y2hlZXNlX3YxLjEuMTc1NS00ZGE5NTIwICAgICAgICA= EC_RW Y2hlZXNlX3YxLjEuMTc1NS00ZGE5NTIwICAgICAgICA= fwupd-1.9.16/plugins/dell-dock/000077500000000000000000000000001460375044200162615ustar00rootroot00000000000000fwupd-1.9.16/plugins/dell-dock/README.md000066400000000000000000000125321460375044200175430ustar00rootroot00000000000000--- title: Plugin: Dell USB-C Dock --- ## Dell System Unlike previous Dell USB-C devices, a Dell system is not needed for updating. ## Components The device contains components the following directly updatable components: * USB hubs * MST controller * Thunderbolt controller * Embedded controller This plugin is used to perform the update on the USB hubs as well as the Dell Embedded controller. The USB hubs are updated directly over a USB HID endpoint while the embedded controller is updated using an I2C over HID interface. The fwupd thunderbolt plugin is used for updating the Titan Ridge controller. The MST controller is updated through either the DP Aux interface (SynapticsMST plugin) or I2C over HID interface provided by this plugin. ## Device topology When this plugin is used, devices present in other plugins may be shown in the topology of this dock. This is intentional as this plugin works together with those plugins to manage the flashing of all components. ## Firmware Format The daemon will decompress the cabinet archive and extract several firmware blobs with an unspecified binary file format. This plugin supports the following protocol ID: * `com.dell.dock` * `com.synaptics.mst` ## GUID Generation These devices use several different generation schemes, e.g. * USB Hub1: `USB\VID_413C&PID_B06F&hub` * USB Hub2: `USB\VID_413C&PID_B06E&hub` * Embedded Controller: `USB\VID_413C&PID_B06E&hub&embedded` * Update Level: `USB\VID_413C&PID_B06E&hub&status` * MST Hub: `MST-panamera-vmm5331-259` * Thunderbolt Controller: `TBT-00d4b070` * USB4 Controller: `TBT-00d4b071` ## Update Behavior All devices will be updated the next time the usb-c plug from the dock is unplugged from the host. ### USB4 Controller This device will be probed by `dell-dock` plugin over the USB interface, additionally will be probed by `thunderbolt` plugin if thunderbolt hardware is enabled at the host. The primary plugin has been chosen to `dell-dock` for broader supoprt, the device introduced by `thunderbolt` plugin will be default inhibited, before fwupd version 1.8.2 (include), `Update Error` will be seen which is expected. ```shell USB4 controller in Dell dock: Device ID: ce501f4b2e03e819c525bb9354aa88c03db4f11e Summary: USB4 controller Current version: 36.00 Vendor: Dell Inc. (THUNDERBOLT:0x00D4, TBT:0x00D4) Install Duration: 46 seconds Update Error: firmware update inhibited by [dell_dock] plugin GUIDs: 4fb9d92e-2b96-51a7-9ed5-3db156dfcf12 ← THUNDERBOLT\VEN_00D4&DEV_B071 bd79ce60-525b-5f39-a3f6-c98c495039ff ← TBT-00d4b071 03f008d5-d06a-5d2e-89ca-61f12a8dbf73 ← TBT-00d4b071-controller0-3 Device Flags: • System requires external power source • Device stages updates • Updatable • Signed Payload ``` After version 1.8.2, the device introduced by `thunderbolt` plugin will be default hidden, so only the primary plugin will be seen on the device view. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x413C` ## Quirk Use This plugin uses the following plugin-specific quirks: ### DellDockUnlockTarget The EC argument needed for unlocking certain device usage. Since: 1.1.3 ### DellDockBlobMajorOffset The offset of the major version number in a payload. Since: 1.1.3 ### DellDockBlobMinorOffset The offset of the minor version number in a payload Since: 1.1.3 ### DellDockBlobBuildOffset The offset of the build version number in a payload Since: 1.1.3 ### DellDockBlobVersionOffset The offset of the ASCII representation of a version string in a payload. Since: 1.1.3 ### DellDockBoardMin The minimum board revision required to safely operate the plugin. Since: 1.1.3 ### DellDockVersionLowest The minimum component version required to safely operate the plugin. Since: 1.1.3 ### DellDockBoard* The board description of a board revision. Since: 1.1.3 ### DellDockInstallDurationI2C The duration of time required to install a payload via I2C. Since: 1.1.3 ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.2.0`. ## Data flow ```mermaid flowchart TD subgraph Dell Dock I2C([I2C Bus]) EC EC_SPI[(EC\nStorage)] USB1 USB1_SPI[(SPI)] USB2 MST_SPI[(SPI)] USB2_SPI[(SPI)] MST TBT(TBT/USB4) TBT_SPI[(SPI)] end subgraph fwupd Process fwupdengine(FuEngine) dell_plugin(DellDock\nPlugin) tbt_plugin(Thunderbolt\nPlugin) usb4_plugin(USB4\nPlugin) end dell_plugin<--hid proxy-->I2C tbt_plugin<--thunderbolt-->TBT usb4_plugin<--hid-->TBT fwupdengine --- dell_plugin fwupdengine --- tbt_plugin USB1<-->I2C USB2<-.->I2C EC<-.->I2C TBT<-.->I2C MST<-.->I2C EC<-.->EC_SPI USB1---USB1_SPI USB2---USB2_SPI MST---MST_SPI TBT---TBT_SPI I2C~~~EC I2C~~~MST I2C~~~USB1 I2C~~~USB2 ``` ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Crag Wang: @CragW * Mario Limonciello: @superm1 fwupd-1.9.16/plugins/dell-dock/dell-dock.quirk000066400000000000000000000135221460375044200211770ustar00rootroot00000000000000# # Copyright (C) 2018 Dell Inc. # All rights reserved. # # This software and associated documentation (if any) is furnished # under a license and may only be used or copied in accordance # with the terms of the license. # # This file is provided under a dual MIT/LGPLv2 license. When using or # redistributing this file, you may do so under either license. # Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. # # SPDX-License-Identifier: LGPL-2.1+ OR MIT # # Used to make plugin probe the devices [USB\VID_413C&PID_B06F] Name = Unprobed Dell accessory endpoint Plugin = dell_dock [USB\VID_413C&PID_B06E] Name = Unprobed Dell accessory endpoint Plugin = dell_dock Flags = has-bridge # USB hub1 [USB\VID_413C&PID_B06F&hub] Name = RTS5413 in Dell dock Summary = USB 3.1 Generation 1 Hub ParentGuid = USB\VID_413C&PID_B06E&hub&embedded Plugin = dell_dock Vendor = Dell Inc. Icon = dock-usb FirmwareSize = 0x10000 Flags = updatable,dual-image,usable-during-update DellDockUnlockTarget = 8 DellDockBlobMajorOffset = 0x7F6E DellDockBlobMinorOffset = 0x7F6F InstallDuration = 14 # USB hub2 [USB\VID_413C&PID_B06E&hub] Name = RTS5487 in Dell dock Summary = USB 3.1 Generation 2 Hub ParentGuid = USB\VID_413C&PID_B06E&hub&embedded Vendor = Dell Inc. Plugin = dell_dock Icon = dock-usb FirmwareSize = 0x10000 Flags = updatable,has-bridge,dual-image,usable-during-update DellDockUnlockTarget = 7 DellDockBlobMajorOffset = 0x7F52 DellDockBlobMinorOffset = 0x7F53 InstallDuration = 3 # Atomic USB hub1 [USB\VID_413C&PID_B06F&atomic_hub] Name = RTS5413 in Dell dock Summary = USB 3.1 Generation 1 Hub ParentGuid = USB\VID_413C&PID_B06E&hub&atomic_embedded Plugin = dell_dock Vendor = Dell Inc. Icon = dock-usb FirmwareSize = 0x10000 Flags = updatable,dual-image,usable-during-update DellDockUnlockTarget = 8 DellDockBlobMajorOffset = 0x7F6E DellDockBlobMinorOffset = 0x7F6F InstallDuration = 14 # Atomic USB hub2 [USB\VID_413C&PID_B06E&atomic_hub] Name = RTS5487 in Dell dock Summary = USB 3.1 Generation 2 Hub ParentGuid = USB\VID_413C&PID_B06E&hub&atomic_embedded Vendor = Dell Inc. Plugin = dell_dock Icon = dock-usb FirmwareSize = 0x10000 Flags = updatable,has-bridge,dual-image,usable-during-update DellDockUnlockTarget = 7 DellDockBlobMajorOffset = 0x7F52 DellDockBlobMinorOffset = 0x7F53 InstallDuration = 3 # Embedded Controller # Name is intentionally not set (it's queried by dock) [USB\VID_413C&PID_B06E&hub&embedded] Name = Dell dock Summary = High performance dock Plugin = dell_dock Vendor = Dell Inc. VendorId = USB:0x413C Icon = dock-usb FirmwareSizeMin = 0x1FFC0 FirmwareSizeMax = 0x20000 Flags = dual-image,self-recovery,usable-during-update DellDockUnlockTarget = 1 DellDockBoardMin = 6 DellDockVersionLowest = 01.01.00.01 DellDockBlobVersionOffset = 0x1AFC0 InstallDuration = 60 #Atomic Embedded Controller # Name is intentionally not set (it's queried by dock) [USB\VID_413C&PID_B06E&hub&atomic_embedded] Name = Dell dock Summary = High performance dock Plugin = dell_dock Vendor = Dell Inc. VendorId = USB:0x413C Icon = dock-usb FirmwareSizeMin = 0x1FFC0 FirmwareSizeMax = 0x20000 Flags = dual-image,self-recovery,usable-during-update DellDockUnlockTarget = 1 DellDockBlobVersionOffset = 0x1AFC0 InstallDuration = 60 # Representation of overall dock update [USB\VID_413C&PID_B06E&hub&status] Name = Package level of Dell dock Summary = A representation of dock update status Plugin = dell_dock Vendor = Dell Inc. Flags = self-recovery,usable-during-update FirmwareSize = 24 InstallDuration = 5 DellDockBlobVersionOffset = 0x14 # Representation of overall dock update [USB\VID_413C&PID_B06E&hub&salomon_mlk_status] Name = Package level of Dell dock Summary = A representation of dock update status Plugin = dell_dock Vendor = Dell Inc. Flags = self-recovery,usable-during-update FirmwareSize = 24 InstallDuration = 5 DellDockBlobVersionOffset = 0x14 # Representation of overall dock update [USB\VID_413C&PID_B06E&hub&atomic_status] Name = Package level of Dell dock Summary = A representation of dock update status Plugin = dell_dock Vendor = Dell Inc. Flags = self-recovery,usable-during-update FirmwareSize = 24 InstallDuration = 5 DellDockBlobVersionOffset = 0x14 # MST Hub [MST-panamera-vmm5331-259] Name = VMM5331 in Dell dock Summary = Multi Stream Transport controller Vendor = Dell Inc. Plugin = dell_dock ParentGuid = USB\VID_413C&PID_B06E&hub&embedded Flags = skips-restart,dual-image,usable-during-update FirmwareSize = 524288 DellDockUnlockTarget = 9 InstallDuration = 95 DellDockInstallDurationI2C = 360 DellDockBlobMajorOffset = 0x18400 DellDockBlobMinorOffset = 0x18401 DellDockBlobBuildOffset = 0x18402 Icon = video-display #Atomic MST Hub [MST-cayenne-vmm6210-257] Name = VMM6210 in Dell dock Summary = Multi Stream Transport controller Vendor = Dell Inc. Plugin = dell_dock ParentGuid = USB\VID_413C&PID_B06E&hub&atomic_embedded Flags = skips-restart,dual-image,usable-during-update FirmwareSize = 1048576 DellDockUnlockTarget = 9 InstallDuration = 95 DellDockInstallDurationI2C = 360 DellDockBlobMajorOffset = 0x4000 DellDockBlobMinorOffset = 0x4001 DellDockBlobBuildOffset = 0x4002 Icon = video-display # Thunderbolt controller [TBT-00d4b070] Name = Thunderbolt controller in Dell dock Summary = Thunderbolt controller Vendor = Dell Inc. VendorId = TBT:0x00D4 ParentGuid = USB\VID_413C&PID_B06E&hub&embedded FirmwareSizeMin = 0x40000 FirmwareSizeMax = 0x80000 Flags = dual-image Icon = thunderbolt InstallDuration = 22 DellDockInstallDurationI2C = 181 DellDockUnlockTarget = 10 DellDockHubVersionLowest = 1.31 DellDockBlobMajorOffset = 0x400a DellDockBlobMinorOffset = 0x4009 # USB4 controller [TBT-00d4b071] Name = USB4 controller in Dell dock Summary = USB4 controller Vendor = Dell Inc. VendorId = TBT:0x00D4 ParentGuid = USB\VID_413C&PID_B06E&hub&embedded FirmwareSizeMin = 0x40000 FirmwareSizeMax = 0x80000 Flags = skips-restart,dual-image,usable-during-update Icon = thunderbolt InstallDuration = 46 fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-common.c000066400000000000000000000023021460375044200221560ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-dell-dock-common.h" #include "fu-dell-dock-i2c-ec.h" gboolean fu_dell_dock_set_power(FuDevice *device, guint8 target, gboolean enabled, GError **error) { FuDevice *parent; g_autoptr(FuDeviceLocker) locker = NULL; g_return_val_if_fail(device != NULL, FALSE); parent = FU_IS_DELL_DOCK_EC(device) ? device : fu_device_get_parent(device); if (parent == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Couldn't find parent for %s", fu_device_get_name(device)); return FALSE; } locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; return fu_dell_dock_ec_modify_lock(parent, target, enabled, error); } fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-common.h000066400000000000000000000033471460375044200221750ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #include "fu-dell-dock-hid.h" #include "fu-dell-dock-hub.h" #include "fu-dell-dock-i2c-ec.h" #include "fu-dell-dock-i2c-mst.h" #include "fu-dell-dock-i2c-tbt.h" #include "fu-dell-dock-status.h" #define DELL_DOCK_DOCK1_INSTANCE_ID "USB\\VID_413C&PID_B06E&hub&status" #define DELL_DOCK_DOCK2_INSTANCE_ID "USB\\VID_413C&PID_B06E&hub&salomon_mlk_status" #define DELL_DOCK_EC_INSTANCE_ID "USB\\VID_413C&PID_B06E&hub&embedded" #define DELL_DOCK_TBT_INSTANCE_ID "TBT-00d4b070" #define DELL_DOCK_USB4_INSTANCE_ID "TBT-00d4b071" #define DELL_DOCK_VM5331_INSTANCE_ID "MST-panamera-vmm5331-259" #define GR_USB_VID 0x8087 #define GR_USB_PID 0x0B40 #define DELL_DOCK_ATOMIC_STATUS_INSTANCE_ID "USB\\VID_413C&PID_B06E&hub&atomic_status" #define DELL_DOCK_ATOMIC_EC_INSTANCE_ID "USB\\VID_413C&PID_B06E&hub&atomic_embedded" #define DELL_DOCK_VMM6210_INSTANCE_ID "MST-cayenne-vmm6210-257" #define ATOMIC_HUB2_PID 0x548A #define ATOMIC_HUB1_PID 0x541A #define DELL_VID 0x413C #define DOCK_BASE_TYPE_UNKNOWN 0x0 #define DOCK_BASE_TYPE_SALOMON 0x04 #define DOCK_BASE_TYPE_ATOMIC 0x05 gboolean fu_dell_dock_set_power(FuDevice *device, guint8 target, gboolean enabled, GError **error); fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-hid.c000066400000000000000000000303771460375044200214470ustar00rootroot00000000000000/* * Copyright (C) 2018 Realtek Semiconductor Corporation * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include #include #include "fu-dell-dock-hid.h" #define HIDI2C_MAX_REGISTER 4 #define HID_MAX_RETRIES 5 #define TBT_MAX_RETRIES 2 #define HIDI2C_TRANSACTION_TIMEOUT 2000 #define HUB_CMD_READ_DATA 0xC0 #define HUB_CMD_WRITE_DATA 0x40 #define HUB_EXT_READ_STATUS 0x09 #define HUB_EXT_MCUMODIFYCLOCK 0x06 #define HUB_EXT_I2C_WRITE 0xC6 #define HUB_EXT_WRITEFLASH 0xC8 #define HUB_EXT_I2C_READ 0xD6 #define HUB_EXT_VERIFYUPDATE 0xD9 #define HUB_EXT_ERASEBANK 0xE8 #define HUB_EXT_WRITE_TBT_FLASH 0xFF #define TBT_COMMAND_WAKEUP 0x00000000 #define TBT_COMMAND_AUTHENTICATE 0xFFFFFFFF #define TBT_COMMAND_AUTHENTICATE_STATUS 0xFFFFFFFE typedef struct __attribute__((packed)) { guint8 cmd; guint8 ext; union { guint32 dwregaddr; struct { guint8 cmd_data0; guint8 cmd_data1; guint8 cmd_data2; guint8 cmd_data3; }; }; guint16 bufferlen; FuHIDI2CParameters parameters; guint8 extended_cmdarea[53]; guint8 data[192]; } FuHIDCmdBuffer; typedef struct __attribute__((packed)) { guint8 cmd; guint8 ext; guint8 i2ctargetaddr; guint8 i2cspeed; union { guint32 startaddress; guint32 tbt_command; }; guint8 bufferlen; guint8 extended_cmdarea[55]; guint8 data[192]; } FuTbtCmdBuffer; static gboolean fu_dell_dock_hid_set_report_cb(FuDevice *self, gpointer user_data, GError **error) { guint8 *outbuffer = (guint8 *)user_data; return fu_hid_device_set_report(FU_HID_DEVICE(self), 0x0, outbuffer, 192, HIDI2C_TRANSACTION_TIMEOUT, FU_HID_DEVICE_FLAG_NONE, error); } static gboolean fu_dell_dock_hid_set_report(FuDevice *self, guint8 *outbuffer, GError **error) { return fu_device_retry(self, fu_dell_dock_hid_set_report_cb, HID_MAX_RETRIES, outbuffer, error); } static gboolean fu_dell_dock_hid_get_report_cb(FuDevice *self, gpointer user_data, GError **error) { guint8 *inbuffer = (guint8 *)user_data; return fu_hid_device_get_report(FU_HID_DEVICE(self), 0x0, inbuffer, 192, HIDI2C_TRANSACTION_TIMEOUT, FU_HID_DEVICE_FLAG_NONE, error); } static gboolean fu_dell_dock_hid_get_report(FuDevice *self, guint8 *inbuffer, GError **error) { return fu_device_retry(self, fu_dell_dock_hid_get_report_cb, HID_MAX_RETRIES, inbuffer, error); } gboolean fu_dell_dock_hid_get_hub_version(FuDevice *self, GError **error) { g_autofree gchar *version = NULL; FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_READ_DATA, .ext = HUB_EXT_READ_STATUS, .cmd_data0 = 0, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = GUINT16_TO_LE(12), .parameters = {.i2ctargetaddr = 0, .regaddrlen = 0, .i2cspeed = 0}, .extended_cmdarea[0 ... 52] = 0, }; if (!fu_dell_dock_hid_set_report(self, (guint8 *)&cmd_buffer, error)) { g_prefix_error(error, "failed to query hub version: "); return FALSE; } if (!fu_dell_dock_hid_get_report(self, cmd_buffer.data, error)) { g_prefix_error(error, "failed to query hub version: "); return FALSE; } version = g_strdup_printf("%02x.%02x", cmd_buffer.data[10], cmd_buffer.data[11]); fu_device_set_version_format(self, FWUPD_VERSION_FORMAT_PAIR); fu_device_set_version(self, version); return TRUE; } gboolean fu_dell_dock_hid_raise_mcu_clock(FuDevice *self, gboolean enable, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_MCUMODIFYCLOCK, .cmd_data0 = (guint8)enable, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = 0, .parameters = {.i2ctargetaddr = 0, .regaddrlen = 0, .i2cspeed = 0}, .extended_cmdarea[0 ... 52] = 0, }; if (!fu_dell_dock_hid_set_report(self, (guint8 *)&cmd_buffer, error)) { g_prefix_error(error, "failed to set mcu clock to %d: ", enable); return FALSE; } return TRUE; } gboolean fu_dell_dock_hid_erase_bank(FuDevice *self, guint8 idx, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_ERASEBANK, .cmd_data0 = 0, .cmd_data1 = idx, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = 0, .parameters = {.i2ctargetaddr = 0, .regaddrlen = 0, .i2cspeed = 0}, .extended_cmdarea[0 ... 52] = 0, }; if (!fu_dell_dock_hid_set_report(self, (guint8 *)&cmd_buffer, error)) { g_prefix_error(error, "failed to erase bank: "); return FALSE; } return TRUE; } gboolean fu_dell_dock_hid_write_flash(FuDevice *self, guint32 dwAddr, const guint8 *input, gsize write_size, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_WRITEFLASH, .dwregaddr = GUINT32_TO_LE(dwAddr), .bufferlen = GUINT16_TO_LE(write_size), .parameters = {.i2ctargetaddr = 0, .regaddrlen = 0, .i2cspeed = 0}, .extended_cmdarea[0 ... 52] = 0, }; g_return_val_if_fail(write_size <= HIDI2C_MAX_WRITE, FALSE); memcpy(cmd_buffer.data, input, write_size); if (!fu_dell_dock_hid_set_report(self, (guint8 *)&cmd_buffer, error)) { g_prefix_error(error, "failed to write %" G_GSIZE_FORMAT " flash to %x: ", write_size, dwAddr); return FALSE; } return TRUE; } gboolean fu_dell_dock_hid_verify_update(FuDevice *self, gboolean *result, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_VERIFYUPDATE, .cmd_data0 = 1, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = GUINT16_TO_LE(1), .parameters = {.i2ctargetaddr = 0, .regaddrlen = 0, .i2cspeed = 0}, .extended_cmdarea[0 ... 52] = 0, }; if (!fu_dell_dock_hid_set_report(self, (guint8 *)&cmd_buffer, error)) { g_prefix_error(error, "failed to verify update: "); return FALSE; } if (!fu_dell_dock_hid_get_report(self, cmd_buffer.data, error)) { g_prefix_error(error, "failed to verify update: "); return FALSE; } *result = cmd_buffer.data[0]; return TRUE; } gboolean fu_dell_dock_hid_i2c_write(FuDevice *self, const guint8 *input, gsize write_size, const FuHIDI2CParameters *parameters, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_I2C_WRITE, .dwregaddr = 0, .bufferlen = GUINT16_TO_LE(write_size), .parameters = {.i2ctargetaddr = parameters->i2ctargetaddr, .regaddrlen = 0, .i2cspeed = parameters->i2cspeed | 0x80}, .extended_cmdarea[0 ... 52] = 0, }; g_return_val_if_fail(write_size <= HIDI2C_MAX_WRITE, FALSE); memcpy(cmd_buffer.data, input, write_size); return fu_dell_dock_hid_set_report(self, (guint8 *)&cmd_buffer, error); } gboolean fu_dell_dock_hid_i2c_read(FuDevice *self, guint32 cmd, gsize read_size, GBytes **bytes, const FuHIDI2CParameters *parameters, GError **error) { FuHIDCmdBuffer cmd_buffer = { .cmd = HUB_CMD_WRITE_DATA, .ext = HUB_EXT_I2C_READ, .dwregaddr = GUINT32_TO_LE(cmd), .bufferlen = GUINT16_TO_LE(read_size), .parameters = {.i2ctargetaddr = parameters->i2ctargetaddr, .regaddrlen = parameters->regaddrlen, .i2cspeed = parameters->i2cspeed | 0x80}, .extended_cmdarea[0 ... 52] = 0, .data[0 ... 191] = 0, }; g_return_val_if_fail(read_size <= HIDI2C_MAX_READ, FALSE); g_return_val_if_fail(bytes != NULL, FALSE); g_return_val_if_fail(parameters->regaddrlen < HIDI2C_MAX_REGISTER, FALSE); if (!fu_dell_dock_hid_set_report(self, (guint8 *)&cmd_buffer, error)) return FALSE; if (!fu_dell_dock_hid_get_report(self, cmd_buffer.data, error)) return FALSE; *bytes = g_bytes_new(cmd_buffer.data, read_size); return TRUE; } gboolean fu_dell_dock_hid_tbt_wake(FuDevice *self, const FuHIDI2CParameters *parameters, GError **error) { FuTbtCmdBuffer cmd_buffer = { .cmd = HUB_CMD_READ_DATA, /* special write command that reads status result */ .ext = HUB_EXT_WRITE_TBT_FLASH, .i2ctargetaddr = parameters->i2ctargetaddr, .i2cspeed = parameters->i2cspeed, /* unlike other commands doesn't need | 0x80 */ .tbt_command = TBT_COMMAND_WAKEUP, .bufferlen = 0, .extended_cmdarea[0 ... 53] = 0, .data[0 ... 191] = 0, }; if (!fu_dell_dock_hid_set_report(self, (guint8 *)&cmd_buffer, error)) { g_prefix_error(error, "failed to set wake thunderbolt: "); return FALSE; } if (!fu_dell_dock_hid_get_report(self, cmd_buffer.data, error)) { g_prefix_error(error, "failed to get wake thunderbolt status: "); return FALSE; } g_debug("thunderbolt wake result: 0x%x", cmd_buffer.data[1]); return TRUE; } static const gchar * fu_dell_dock_hid_tbt_map_error(guint32 code) { if (code == 1) return g_strerror(EINVAL); if (code == 2) return g_strerror(EPERM); return g_strerror(EIO); } gboolean fu_dell_dock_hid_tbt_write(FuDevice *self, guint32 start_addr, const guint8 *input, gsize write_size, const FuHIDI2CParameters *parameters, GError **error) { FuTbtCmdBuffer cmd_buffer = { .cmd = HUB_CMD_READ_DATA, /* It's a special write command that reads status result */ .ext = HUB_EXT_WRITE_TBT_FLASH, .i2ctargetaddr = parameters->i2ctargetaddr, .i2cspeed = parameters->i2cspeed, /* unlike other commands doesn't need | 0x80 */ .startaddress = GUINT32_TO_LE(start_addr), .bufferlen = write_size, .extended_cmdarea[0 ... 53] = 0, }; guint8 result; g_return_val_if_fail(input != NULL, FALSE); g_return_val_if_fail(write_size <= HIDI2C_MAX_WRITE, FALSE); memcpy(cmd_buffer.data, input, write_size); for (gint i = 1; i <= TBT_MAX_RETRIES; i++) { if (!fu_dell_dock_hid_set_report(self, (guint8 *)&cmd_buffer, error)) { g_prefix_error(error, "failed to run TBT update: "); return FALSE; } if (!fu_dell_dock_hid_get_report(self, cmd_buffer.data, error)) { g_prefix_error(error, "failed to get TBT flash status: "); return FALSE; } result = cmd_buffer.data[1] & 0xf; if (result == 0) break; g_debug("attempt %d/%d: Thunderbolt write failed: %x", i, TBT_MAX_RETRIES, result); } if (result != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Writing address 0x%04x failed: %s", start_addr, fu_dell_dock_hid_tbt_map_error(result)); return FALSE; } return TRUE; } gboolean fu_dell_dock_hid_tbt_authenticate(FuDevice *self, const FuHIDI2CParameters *parameters, GError **error) { FuTbtCmdBuffer cmd_buffer = { .cmd = HUB_CMD_READ_DATA, /* It's a special write command that reads status result */ .ext = HUB_EXT_WRITE_TBT_FLASH, .i2ctargetaddr = parameters->i2ctargetaddr, .i2cspeed = parameters->i2cspeed, /* unlike other commands doesn't need | 0x80 */ .tbt_command = GUINT32_TO_LE(TBT_COMMAND_AUTHENTICATE), .bufferlen = 0, .extended_cmdarea[0 ... 53] = 0, }; guint8 result; if (!fu_dell_dock_hid_set_report(self, (guint8 *)&cmd_buffer, error)) { g_prefix_error(error, "failed to send authentication: "); return FALSE; } cmd_buffer.tbt_command = GUINT32_TO_LE(TBT_COMMAND_AUTHENTICATE_STATUS); /* needs at least 2 seconds */ fu_device_sleep(self, 2000); for (gint i = 1; i <= TBT_MAX_RETRIES; i++) { if (!fu_dell_dock_hid_set_report(self, (guint8 *)&cmd_buffer, error)) { g_prefix_error(error, "failed to set check authentication: "); return FALSE; } if (!fu_dell_dock_hid_get_report(self, cmd_buffer.data, error)) { g_prefix_error(error, "failed to get check authentication: "); return FALSE; } result = cmd_buffer.data[1] & 0xf; if (result == 0) break; g_debug("attempt %d/%d: Thunderbolt authenticate failed: %x", i, TBT_MAX_RETRIES, result); fu_device_sleep(self, 500); /* ms */ } if (result != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Thunderbolt authentication failed: %s", fu_dell_dock_hid_tbt_map_error(result)); return FALSE; } return TRUE; } fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-hid.h000066400000000000000000000042001460375044200214360ustar00rootroot00000000000000/* * Copyright (C) 2018 Realtek Semiconductor Corporation * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include typedef struct __attribute__((packed)) { guint8 i2ctargetaddr; guint8 regaddrlen; guint8 i2cspeed; } FuHIDI2CParameters; typedef enum { I2C_SPEED_250K, I2C_SPEED_400K, I2C_SPEED_800K, /* */ I2C_SPEED_LAST, } BridgedI2CSpeed; #define HIDI2C_MAX_READ 192 #define HIDI2C_MAX_WRITE 128 gboolean fu_dell_dock_hid_i2c_write(FuDevice *self, const guint8 *input, gsize write_size, const FuHIDI2CParameters *parameters, GError **error); gboolean fu_dell_dock_hid_i2c_read(FuDevice *self, guint32 cmd, gsize read_size, GBytes **bytes, const FuHIDI2CParameters *parameters, GError **error); gboolean fu_dell_dock_hid_get_hub_version(FuDevice *self, GError **error); gboolean fu_dell_dock_hid_raise_mcu_clock(FuDevice *self, gboolean enable, GError **error); gboolean fu_dell_dock_hid_erase_bank(FuDevice *self, guint8 idx, GError **error); gboolean fu_dell_dock_hid_write_flash(FuDevice *self, guint32 addr, const guint8 *input, gsize write_size, GError **error); gboolean fu_dell_dock_hid_verify_update(FuDevice *self, gboolean *result, GError **error); gboolean fu_dell_dock_hid_tbt_wake(FuDevice *self, const FuHIDI2CParameters *parameters, GError **error); gboolean fu_dell_dock_hid_tbt_write(FuDevice *self, guint32 start_addr, const guint8 *input, gsize write_size, const FuHIDI2CParameters *parameters, GError **error); gboolean fu_dell_dock_hid_tbt_authenticate(FuDevice *self, const FuHIDI2CParameters *parameters, GError **error); fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-hub.c000066400000000000000000000147661460375044200214650ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-dell-dock-common.h" struct _FuDellDockHub { FuHidDevice parent_instance; guint8 unlock_target; guint64 blob_major_offset; guint64 blob_minor_offset; }; G_DEFINE_TYPE(FuDellDockHub, fu_dell_dock_hub, FU_TYPE_HID_DEVICE) void fu_dell_dock_hub_add_instance(FuDevice *device, guint8 dock_type) { g_autofree gchar *devid = NULL; if (dock_type == DOCK_BASE_TYPE_ATOMIC) { devid = g_strdup_printf("USB\\VID_%04X&PID_%04X&atomic_hub", (guint)fu_usb_device_get_vid(FU_USB_DEVICE(device)), (guint)fu_usb_device_get_pid(FU_USB_DEVICE(device))); } else { devid = g_strdup_printf("USB\\VID_%04X&PID_%04X&hub", (guint)fu_usb_device_get_vid(FU_USB_DEVICE(device)), (guint)fu_usb_device_get_pid(FU_USB_DEVICE(device))); } fu_device_add_instance_id(device, devid); } static gboolean fu_dell_dock_hub_probe(FuDevice *device, GError **error) { fu_device_set_logical_id(device, "hub"); fu_device_add_protocol(device, "com.dell.dock"); return TRUE; } static gboolean fu_dell_dock_hub_write_fw(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDellDockHub *self = FU_DELL_DOCK_HUB(device); gsize fw_size = 0; const guint8 *data; gsize write_size; gsize nwritten = 0; guint32 address = 0; gboolean result = FALSE; g_autofree gchar *dynamic_version = NULL; g_autoptr(GBytes) fw = NULL; g_return_val_if_fail(device != NULL, FALSE); g_return_val_if_fail(FU_IS_FIRMWARE(firmware), FALSE); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 49, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 50, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; data = g_bytes_get_data(fw, &fw_size); write_size = (fw_size / HIDI2C_MAX_WRITE) >= 1 ? HIDI2C_MAX_WRITE : fw_size; dynamic_version = g_strdup_printf("%02x.%02x", data[self->blob_major_offset], data[self->blob_minor_offset]); g_info("writing hub firmware version %s", dynamic_version); if (!fu_dell_dock_set_power(device, self->unlock_target, TRUE, error)) return FALSE; if (!fu_dell_dock_hid_raise_mcu_clock(device, TRUE, error)) return FALSE; /* erase */ if (!fu_dell_dock_hid_erase_bank(device, 1, error)) return FALSE; fu_progress_step_done(progress); /* write */ do { /* last packet */ if (fw_size - nwritten < write_size) write_size = fw_size - nwritten; if (!fu_dell_dock_hid_write_flash(device, address, data, write_size, error)) return FALSE; nwritten += write_size; data += write_size; address += write_size; fu_progress_set_percentage_full(fu_progress_get_child(progress), nwritten, fw_size); } while (nwritten < fw_size); fu_progress_step_done(progress); /* verify */ if (!fu_dell_dock_hid_verify_update(device, &result, error)) return FALSE; if (!result) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to verify the update"); return FALSE; } fu_progress_step_done(progress); /* dock will reboot to re-read; this is to appease the daemon */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PAIR); fu_device_set_version(device, dynamic_version); return TRUE; } static gboolean fu_dell_dock_hub_setup(FuDevice *device, GError **error) { /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_dell_dock_hub_parent_class)->setup(device, error)) return FALSE; return fu_dell_dock_hid_get_hub_version(device, error); } static gboolean fu_dell_dock_hub_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuDellDockHub *self = FU_DELL_DOCK_HUB(device); guint64 tmp = 0; if (g_strcmp0(key, "DellDockUnlockTarget") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->unlock_target = tmp; return TRUE; } if (g_strcmp0(key, "DellDockBlobMajorOffset") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_major_offset = tmp; return TRUE; } if (g_strcmp0(key, "DellDockBlobMinorOffset") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_minor_offset = tmp; return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_dell_dock_hub_finalize(GObject *object) { G_OBJECT_CLASS(fu_dell_dock_hub_parent_class)->finalize(object); } static void fu_dell_dock_hub_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_dell_dock_hub_init(FuDellDockHub *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_retry_set_delay(FU_DEVICE(self), 1000); fu_device_register_private_flag(FU_DEVICE(self), FU_DELL_DOCK_HUB_FLAG_HAS_BRIDGE, "has-bridge"); } static void fu_dell_dock_hub_class_init(FuDellDockHubClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_dell_dock_hub_finalize; klass_device->setup = fu_dell_dock_hub_setup; klass_device->probe = fu_dell_dock_hub_probe; klass_device->write_firmware = fu_dell_dock_hub_write_fw; klass_device->set_quirk_kv = fu_dell_dock_hub_set_quirk_kv; klass_device->set_progress = fu_dell_dock_hub_set_progress; } FuDellDockHub * fu_dell_dock_hub_new(FuUsbDevice *device) { FuDellDockHub *self = g_object_new(FU_TYPE_DELL_DOCK_HUB, NULL); fu_device_incorporate(FU_DEVICE(self), FU_DEVICE(device)); return self; } fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-hub.h000066400000000000000000000017121460375044200214550ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_DELL_DOCK_HUB (fu_dell_dock_hub_get_type()) G_DECLARE_FINAL_TYPE(FuDellDockHub, fu_dell_dock_hub, FU, DELL_DOCK_HUB, FuHidDevice) /** * FU_DELL_DOCK_HUB_FLAG_HAS_BRIDGE: * * A bridge is present, possibly with extended devices. */ #define FU_DELL_DOCK_HUB_FLAG_HAS_BRIDGE (1 << 0) FuDellDockHub * fu_dell_dock_hub_new(FuUsbDevice *device); void fu_dell_dock_hub_add_instance(FuDevice *device, guint8 dock_type); fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-i2c-ec.c000066400000000000000000000740001460375044200217340ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include #include "fu-dell-dock-common.h" #define I2C_EC_ADDRESS 0xec #define EC_CMD_SET_DOCK_PKG 0x01 #define EC_CMD_GET_DOCK_INFO 0x02 #define EC_CMD_GET_DOCK_DATA 0x03 #define EC_CMD_GET_DOCK_TYPE 0x05 #define EC_CMD_MODIFY_LOCK 0x0a #define EC_CMD_RESET 0x0b #define EC_CMD_PASSIVE 0x0d #define EC_GET_FW_UPDATE_STATUS 0x0f #define EXPECTED_DOCK_INFO_SIZE 0xb7 #define TBT_MODE_MASK 0x01 #define BIT_SET(x, y) (x |= (1 << y)) #define BIT_CLEAR(x, y) (x &= (~(1 << y))) #define PASSIVE_RESET_MASK 0x01 #define PASSIVE_REBOOT_MASK 0x02 #define PASSIVE_TBT_MASK 0x04 typedef enum { FW_UPDATE_IN_PROGRESS, FW_UPDATE_COMPLETE, FW_UPDATE_AUTHENTICATION_FAILED, } FuDellDockECFWUpdateStatus; const FuHIDI2CParameters ec_base_settings = { .i2ctargetaddr = I2C_EC_ADDRESS, .regaddrlen = 1, .i2cspeed = I2C_SPEED_250K, }; typedef enum { LOCATION_BASE, LOCATION_MODULE, } FuDellDockLocationEnum; typedef enum { FU_DELL_DOCK_DEVICETYPE_MAIN_EC = 0, FU_DELL_DOCK_DEVICETYPE_PD = 1, FU_DELL_DOCK_DEVICETYPE_HUB = 3, FU_DELL_DOCK_DEVICETYPE_MST = 4, FU_DELL_DOCK_DEVICETYPE_TBT = 5, } FuDellDockDeviceTypeEnum; typedef enum { SUBTYPE_GEN2, SUBTYPE_GEN1, } FuDellDockHubSubTypeEnum; typedef struct __attribute__((packed)) { guint8 total_devices; guint8 first_index; guint8 last_index; } FuDellDockDockInfoHeader; typedef struct __attribute__((packed)) { guint8 location; guint8 device_type; guint8 sub_type; guint8 arg; guint8 instance; } FuDellDockEcAddrMap; typedef struct __attribute__((packed)) { FuDellDockEcAddrMap ec_addr_map; union { guint32 version_32; guint8 version_8[4]; } version; } FuDellDockEcQueryEntry; typedef enum { MODULE_TYPE_45_TBT = 1, MODULE_TYPE_45, MODULE_TYPE_130_TBT, MODULE_TYPE_130_DP, MODULE_TYPE_130_UNIVERSAL, MODULE_TYPE_240_TRIN, MODULE_TYPE_210_DUAL, MODULE_TYPE_130_USB4, } FuDellDockDockModule; typedef struct __attribute__((packed)) { guint8 dock_configuration; guint8 dock_type; guint16 power_supply_wattage; guint16 module_type; guint16 board_id; guint16 port0_dock_status; guint16 port1_dock_status; guint32 dock_firmware_pkg_ver; guint64 module_serial; guint64 original_module_serial; gchar service_tag[7]; gchar marketing_name[64]; } FuDellDockDockDataStructure; typedef struct __attribute__((packed)) { guint32 ec_version; guint32 mst_version; guint32 hub1_version; guint32 hub2_version; guint32 tbt_version; guint32 pkg_version; } FuDellDockDockPackageFWVersion; struct _FuDellDockEc { FuDevice parent_instance; FuDellDockDockDataStructure *data; FuDellDockDockPackageFWVersion *raw_versions; guint8 base_type; gchar *ec_version; gchar *mst_version; gchar *tbt_version; guint8 unlock_target; guint8 board_min; gchar *ec_minimum_version; guint64 blob_version_offset; guint8 passive_flow; guint32 dock_unlock_status; }; static gboolean fu_dell_dock_get_ec_status(FuDevice *device, FuDellDockECFWUpdateStatus *status_out, GError **error); G_DEFINE_TYPE(FuDellDockEc, fu_dell_dock_ec, FU_TYPE_DEVICE) /* Used to root out I2C communication problems */ static gboolean fu_dell_dock_test_valid_byte(const guint8 *str, gint index) { if (str[index] == 0x00 || str[index] == 0xff) return FALSE; return TRUE; } static void fu_dell_dock_ec_set_board(FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); const gchar *summary = NULL; g_autofree gchar *board_type_str = NULL; board_type_str = g_strdup_printf("DellDockBoard%u", self->data->board_id); summary = fu_device_get_metadata(device, board_type_str); if (summary != NULL) fu_device_set_summary(device, summary); } gboolean fu_dell_dock_module_is_usb4(FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); return self->data->module_type == MODULE_TYPE_130_USB4; } guint8 fu_dell_dock_get_dock_type(FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); return self->base_type; } const gchar * fu_dell_dock_ec_get_module_type(FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); switch (self->data->module_type) { case MODULE_TYPE_45_TBT: return "45 (TBT)"; case MODULE_TYPE_45: return "45"; case MODULE_TYPE_130_TBT: return "130 (TBT)"; case MODULE_TYPE_130_DP: return "130 (DP)"; case MODULE_TYPE_130_UNIVERSAL: return "130 (Universal)"; case MODULE_TYPE_240_TRIN: return "240 (Trinity)"; case MODULE_TYPE_210_DUAL: return "210 (Dual)"; case MODULE_TYPE_130_USB4: return "130 (TBT4)"; default: return "unknown"; } } gboolean fu_dell_dock_ec_needs_tbt(FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); gboolean port0_tbt_mode = self->data->port0_dock_status & TBT_MODE_MASK; /* check for TBT module type */ if (self->data->module_type != MODULE_TYPE_130_TBT && self->data->module_type != MODULE_TYPE_45_TBT) return FALSE; g_info("found thunderbolt dock, port mode: %d", port0_tbt_mode); return !port0_tbt_mode; } gboolean fu_dell_dock_ec_tbt_passive(FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); if (self->passive_flow > 0) { self->passive_flow |= PASSIVE_TBT_MASK; return TRUE; } return FALSE; } static const gchar * fu_dell_dock_devicetype_to_str(guint device_type, guint sub_type) { switch (device_type) { case FU_DELL_DOCK_DEVICETYPE_MAIN_EC: return "EC"; case FU_DELL_DOCK_DEVICETYPE_MST: return "MST"; case FU_DELL_DOCK_DEVICETYPE_TBT: return "Thunderbolt"; case FU_DELL_DOCK_DEVICETYPE_HUB: if (sub_type == SUBTYPE_GEN2) return "USB 3.1 Gen2"; else if (sub_type == SUBTYPE_GEN1) return "USB 3.1 Gen1"; return NULL; case FU_DELL_DOCK_DEVICETYPE_PD: return "PD"; default: return NULL; } } static gboolean fu_dell_dock_ec_read(FuDevice *device, guint32 cmd, gsize length, GBytes **bytes, GError **error) { /* The first byte of result data will be the size of the return, hide this from callers */ guint8 result_length = length + 1; g_autoptr(GBytes) bytes_local = NULL; const guint8 *result; g_return_val_if_fail(device != NULL, FALSE); g_return_val_if_fail(fu_device_get_proxy(device) != NULL, FALSE); g_return_val_if_fail(bytes != NULL, FALSE); if (!fu_dell_dock_hid_i2c_read(fu_device_get_proxy(device), cmd, result_length, &bytes_local, &ec_base_settings, error)) { g_prefix_error(error, "read over HID-I2C failed: "); return FALSE; } result = g_bytes_get_data(bytes_local, NULL); /* first byte of result should be size of our data */ if (result[0] != length) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid result data: %d expected %" G_GSIZE_FORMAT, result[0], length); return FALSE; } *bytes = g_bytes_new(result + 1, length); return TRUE; } static gboolean fu_dell_dock_ec_write(FuDevice *device, gsize length, guint8 *data, GError **error) { g_return_val_if_fail(device != NULL, FALSE); g_return_val_if_fail(fu_device_get_proxy(device) != NULL, FALSE); g_return_val_if_fail(length > 1, FALSE); if (!fu_dell_dock_hid_i2c_write(fu_device_get_proxy(device), data, length, &ec_base_settings, error)) { g_prefix_error(error, "write over HID-I2C failed: "); return FALSE; } return TRUE; } static gboolean fu_dell_dock_is_valid_dock(FuDevice *device, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); const guint8 *result = NULL; gsize sz = 0; g_autoptr(GBytes) data = NULL; g_return_val_if_fail(device != NULL, FALSE); if (!fu_dell_dock_ec_read(device, EC_CMD_GET_DOCK_TYPE, 1, &data, error)) { g_prefix_error(error, "Failed to query dock type: "); return FALSE; } result = g_bytes_get_data(data, &sz); if (sz != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No valid dock was found"); return FALSE; } self->base_type = result[0]; /* this will trigger setting up all the quirks */ if (self->base_type == DOCK_BASE_TYPE_SALOMON) { fu_device_add_instance_id(device, DELL_DOCK_EC_INSTANCE_ID); return TRUE; } if (self->base_type == DOCK_BASE_TYPE_ATOMIC) { fu_device_add_instance_id(device, DELL_DOCK_ATOMIC_EC_INSTANCE_ID); return TRUE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid dock type: %x", self->base_type); return FALSE; } static gboolean fu_dell_dock_ec_get_dock_info(FuDevice *device, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); const FuDellDockDockInfoHeader *header = NULL; const FuDellDockEcQueryEntry *device_entry = NULL; const FuDellDockEcAddrMap *map = NULL; const gchar *hub_version; guint32 oldest_base_pd = 0; g_autoptr(GBytes) data = NULL; g_return_val_if_fail(device != NULL, FALSE); if (!fu_dell_dock_ec_read(device, EC_CMD_GET_DOCK_INFO, EXPECTED_DOCK_INFO_SIZE, &data, error)) { g_prefix_error(error, "Failed to query dock info: "); return FALSE; } if (!g_bytes_get_data(data, NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Failed to read dock info"); return FALSE; } header = (FuDellDockDockInfoHeader *)g_bytes_get_data(data, NULL); if (!header) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Failed to parse dock info"); return FALSE; } /* guard against EC not yet ready and fail init */ if (header->total_devices == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "No bridge devices detected, dock may be booting up"); return FALSE; } g_info("%u devices [%u->%u]", header->total_devices, header->first_index, header->last_index); device_entry = (FuDellDockEcQueryEntry *)((guint8 *)header + sizeof(FuDellDockDockInfoHeader)); for (guint i = 0; i < header->total_devices; i++) { const gchar *type_str; map = &(device_entry[i].ec_addr_map); type_str = fu_dell_dock_devicetype_to_str(map->device_type, map->sub_type); if (type_str == NULL) continue; g_debug("#%u: %s in %s (A: %u I: %u)", i, type_str, (map->location == LOCATION_BASE) ? "Base" : "Module", map->arg, map->instance); g_debug("\tVersion32: %08x\tVersion8: %x %x %x %x", device_entry[i].version.version_32, device_entry[i].version.version_8[0], device_entry[i].version.version_8[1], device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); /* BCD but guint32 */ if (map->device_type == FU_DELL_DOCK_DEVICETYPE_MAIN_EC) { self->raw_versions->ec_version = device_entry[i].version.version_32; self->ec_version = g_strdup_printf("%02x.%02x.%02x.%02x", device_entry[i].version.version_8[0], device_entry[i].version.version_8[1], device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); g_debug("\tParsed version %s", self->ec_version); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version(FU_DEVICE(self), self->ec_version); } else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_MST) { self->raw_versions->mst_version = device_entry[i].version.version_32; /* guard against invalid MST version read from EC */ if (!fu_dell_dock_test_valid_byte(device_entry[i].version.version_8, 1)) { g_warning("[EC Bug] EC read invalid MST version %08x", device_entry[i].version.version_32); continue; } self->mst_version = g_strdup_printf("%02x.%02x.%02x", device_entry[i].version.version_8[1], device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); g_debug("\tParsed version %s", self->mst_version); } else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_TBT && (self->data->module_type == MODULE_TYPE_130_TBT || self->data->module_type == MODULE_TYPE_45_TBT || self->data->module_type == MODULE_TYPE_130_USB4)) { /* guard against invalid Thunderbolt version read from EC */ if (!fu_dell_dock_test_valid_byte(device_entry[i].version.version_8, 2)) { g_warning("[EC bug] EC read invalid Thunderbolt version %08x", device_entry[i].version.version_32); continue; } self->raw_versions->tbt_version = device_entry[i].version.version_32; self->tbt_version = g_strdup_printf("%02x.%02x", device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); g_debug("\tParsed version %s", self->tbt_version); } else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_HUB) { g_debug("\thub subtype: %u", map->sub_type); if (map->sub_type == SUBTYPE_GEN2) self->raw_versions->hub2_version = device_entry[i].version.version_32; else if (map->sub_type == SUBTYPE_GEN1) self->raw_versions->hub1_version = device_entry[i].version.version_32; } else if (map->device_type == FU_DELL_DOCK_DEVICETYPE_PD && map->location == LOCATION_BASE && map->sub_type == 0) { if (oldest_base_pd == 0 || device_entry[i].version.version_32 < oldest_base_pd) oldest_base_pd = GUINT32_TO_BE(device_entry[i].version.version_32); g_debug("\tParsed version: %02x.%02x.%02x.%02x", device_entry[i].version.version_8[0], device_entry[i].version.version_8[1], device_entry[i].version.version_8[2], device_entry[i].version.version_8[3]); } } /* Thunderbolt SKU takes a little longer */ if (self->data->module_type == MODULE_TYPE_130_TBT || self->data->module_type == MODULE_TYPE_45_TBT || self->data->module_type == MODULE_TYPE_130_USB4) { guint64 tmp = fu_device_get_install_duration(device); fu_device_set_install_duration(device, tmp + 20); } /* Determine if the passive flow should be used when flashing */ hub_version = fu_device_get_version(fu_device_get_proxy(device)); if (fu_version_compare(hub_version, "1.42", FWUPD_VERSION_FORMAT_PAIR) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "dock containing hub2 version %s is not supported", hub_version); return FALSE; } self->passive_flow = PASSIVE_REBOOT_MASK; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SKIPS_RESTART); return TRUE; } static gboolean fu_dell_dock_ec_get_dock_data(FuDevice *device, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); g_autoptr(GBytes) data = NULL; g_autoptr(GString) name = NULL; gchar service_tag[8] = {0x00}; const guint8 *result; gsize length = sizeof(FuDellDockDockDataStructure); g_autofree gchar *bundled_serial = NULL; FuDellDockECFWUpdateStatus status; g_return_val_if_fail(device != NULL, FALSE); if (!fu_dell_dock_ec_read(device, EC_CMD_GET_DOCK_DATA, length, &data, error)) { g_prefix_error(error, "Failed to query dock info: "); return FALSE; } result = g_bytes_get_data(data, NULL); if (result == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Failed to read dock data"); return FALSE; } if (g_bytes_get_size(data) != length) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Unexpected dock data size %" G_GSIZE_FORMAT, g_bytes_get_size(data)); return FALSE; } memcpy(self->data, result, length); /* guard against EC not yet ready and fail init */ name = g_string_new(self->data->marketing_name); if (name->len > 0) fu_device_set_name(device, name->str); else g_warning("[EC bug] Invalid dock name detected"); if (self->data->module_type >= 0xfe) g_warning("[EC bug] Invalid module type 0x%02x", self->data->module_type); /* set serial number */ memcpy(service_tag, self->data->service_tag, 7); bundled_serial = g_strdup_printf("%s/%08" G_GUINT64_FORMAT, service_tag, self->data->module_serial); fu_device_set_serial(device, bundled_serial); /* copy this for being able to send in next commit transaction */ self->raw_versions->pkg_version = self->data->dock_firmware_pkg_ver; /* read if passive update pending */ if (!fu_dell_dock_get_ec_status(device, &status, error)) return FALSE; /* make sure this hardware spin matches our expectations */ if (self->data->board_id >= self->board_min) { if (status != FW_UPDATE_IN_PROGRESS) { fu_dell_dock_ec_set_board(device); fu_device_uninhibit(device, "update-pending"); } else { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_UPDATE_PENDING); } } else { fu_device_inhibit(device, "not-supported", "Utility does not support this board"); } return TRUE; } static void fu_dell_dock_ec_to_string(FuDevice *device, guint idt, GString *str) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); gchar service_tag[8] = {0x00}; fu_string_append_ku(str, idt, "BaseType", self->base_type); fu_string_append_ku(str, idt, "BoardId", self->data->board_id); fu_string_append_ku(str, idt, "PowerSupply", self->data->power_supply_wattage); fu_string_append_kx(str, idt, "StatusPort0", self->data->port0_dock_status); fu_string_append_kx(str, idt, "StatusPort1", self->data->port1_dock_status); memcpy(service_tag, self->data->service_tag, 7); fu_string_append(str, idt, "ServiceTag", service_tag); fu_string_append_ku(str, idt, "Configuration", self->data->dock_configuration); fu_string_append_kx(str, idt, "PackageFirmwareVersion", self->data->dock_firmware_pkg_ver); fu_string_append_ku(str, idt, "ModuleSerial", self->data->module_serial); fu_string_append_ku(str, idt, "OriginalModuleSerial", self->data->original_module_serial); fu_string_append_ku(str, idt, "Type", self->data->dock_type); fu_string_append_kx(str, idt, "ModuleType", self->data->module_type); fu_string_append(str, idt, "MinimumEc", self->ec_minimum_version); fu_string_append_ku(str, idt, "PassiveFlow", self->passive_flow); } gboolean fu_dell_dock_ec_modify_lock(FuDevice *device, guint8 target, gboolean unlocked, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); guint32 cmd; g_return_val_if_fail(device != NULL, FALSE); g_return_val_if_fail(target != 0, FALSE); cmd = EC_CMD_MODIFY_LOCK | /* cmd */ 2 << 8 | /* length of data arguments */ target << 16 | /* device to operate on */ unlocked << 24; /* unlock/lock */ if (!fu_dell_dock_ec_write(device, 4, (guint8 *)&cmd, error)) { g_prefix_error(error, "Failed to unlock device %d: ", target); return FALSE; } g_debug("Modified lock for %d to %d through %s (%s)", target, unlocked, fu_device_get_name(device), fu_device_get_id(device)); if (unlocked) BIT_SET(self->dock_unlock_status, target); else BIT_CLEAR(self->dock_unlock_status, target); g_debug("current overall unlock status: 0x%08x", self->dock_unlock_status); return TRUE; } static gboolean fu_dell_dock_ec_reset(FuDevice *device, GError **error) { guint16 cmd = EC_CMD_RESET; g_return_val_if_fail(device != NULL, FALSE); return fu_dell_dock_ec_write(device, 2, (guint8 *)&cmd, error); } static gboolean fu_dell_dock_ec_activate(FuDevice *device, FuProgress *progress, GError **error) { FuDellDockECFWUpdateStatus status; /* read if passive update pending */ if (!fu_dell_dock_get_ec_status(device, &status, error)) return FALSE; if (status != FW_UPDATE_IN_PROGRESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No firmware update pending for %s", fu_device_get_name(device)); return FALSE; } return fu_dell_dock_ec_reset(device, error); } gboolean fu_dell_dock_ec_reboot_dock(FuDevice *device, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); guint32 cmd = EC_CMD_PASSIVE | /* cmd */ 1 << 8 | /* length of data arguments */ self->passive_flow << 16; g_return_val_if_fail(device != NULL, FALSE); g_info("activating passive flow (%x) for %s", self->passive_flow, fu_device_get_name(device)); return fu_dell_dock_ec_write(device, 3, (guint8 *)&cmd, error); } static gboolean fu_dell_dock_get_ec_status(FuDevice *device, FuDellDockECFWUpdateStatus *status_out, GError **error) { g_autoptr(GBytes) data = NULL; const guint8 *result = NULL; g_return_val_if_fail(device != NULL, FALSE); g_return_val_if_fail(status_out != NULL, FALSE); if (!fu_dell_dock_ec_read(device, EC_GET_FW_UPDATE_STATUS, 1, &data, error)) { g_prefix_error(error, "Failed to read FW update status: "); return FALSE; } result = g_bytes_get_data(data, NULL); if (!result) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Failed to read FW update status"); return FALSE; } *status_out = *result; return TRUE; } const gchar * fu_dell_dock_ec_get_tbt_version(FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); return self->tbt_version; } const gchar * fu_dell_dock_ec_get_mst_version(FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); return self->mst_version; } guint32 fu_dell_dock_ec_get_status_version(FuDevice *device) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); return self->raw_versions->pkg_version; } gboolean fu_dell_dock_ec_commit_package(FuDevice *device, GBytes *blob_fw, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); gsize length = 0; const guint8 *data = g_bytes_get_data(blob_fw, &length); g_autofree guint8 *payload = g_malloc0(length + 2); g_return_val_if_fail(device != NULL, FALSE); g_return_val_if_fail(blob_fw != NULL, FALSE); if (length != sizeof(FuDellDockDockPackageFWVersion)) { g_set_error(error, G_IO_ERR, G_IO_ERROR_INVALID_DATA, "Invalid package size %" G_GSIZE_FORMAT, length); return FALSE; } memcpy(self->raw_versions, data, length); g_debug("Committing (%zu) bytes ", sizeof(FuDellDockDockPackageFWVersion)); g_debug("\tec_version: %x", self->raw_versions->ec_version); g_debug("\tmst_version: %x", self->raw_versions->mst_version); g_debug("\thub1_version: %x", self->raw_versions->hub1_version); g_debug("\thub2_version: %x", self->raw_versions->hub2_version); g_debug("\ttbt_version: %x", self->raw_versions->tbt_version); g_debug("\tpkg_version: %x", self->raw_versions->pkg_version); payload[0] = EC_CMD_SET_DOCK_PKG; payload[1] = length; memcpy(payload + 2, data, length); if (!fu_dell_dock_ec_write(device, length + 2, payload, error)) { g_prefix_error(error, "Failed to query dock info: "); return FALSE; } return TRUE; } static gboolean fu_dell_dock_ec_write_fw(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); gsize fw_size = 0; const guint8 *data; gsize write_size = 0; gsize nwritten = 0; guint32 address = 0 | 0xff << 24; g_autofree gchar *dynamic_version = NULL; g_autoptr(GBytes) fw = NULL; g_return_val_if_fail(device != NULL, FALSE); g_return_val_if_fail(FU_IS_FIRMWARE(firmware), FALSE); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 15, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 85, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; data = g_bytes_get_data(fw, &fw_size); write_size = (fw_size / HIDI2C_MAX_WRITE) >= 1 ? HIDI2C_MAX_WRITE : fw_size; dynamic_version = g_strndup((gchar *)data + self->blob_version_offset, 11); g_info("writing EC firmware version %s", dynamic_version); /* meet the minimum EC version */ if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && (fu_version_compare(dynamic_version, self->ec_minimum_version, FWUPD_VERSION_FORMAT_QUAD) < 0)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "New EC version %s is less than minimum required %s", dynamic_version, self->ec_minimum_version); return FALSE; } g_info("writing EC firmware version %s", dynamic_version); if (!fu_dell_dock_ec_modify_lock(device, self->unlock_target, TRUE, error)) return FALSE; if (!fu_dell_dock_hid_raise_mcu_clock(fu_device_get_proxy(device), TRUE, error)) return FALSE; /* erase */ if (!fu_dell_dock_hid_erase_bank(fu_device_get_proxy(device), 0xff, error)) return FALSE; fu_progress_step_done(progress); /* write */ do { /* last packet */ if (fw_size - nwritten < write_size) write_size = fw_size - nwritten; if (!fu_dell_dock_hid_write_flash(fu_device_get_proxy(device), address, data, write_size, error)) { g_prefix_error(error, "write over HID failed: "); return FALSE; } fu_progress_set_percentage_full(fu_progress_get_child(progress), nwritten, fw_size); nwritten += write_size; data += write_size; address += write_size; } while (nwritten < fw_size); fu_progress_step_done(progress); if (!fu_dell_dock_hid_raise_mcu_clock(fu_device_get_proxy(device), FALSE, error)) return FALSE; /* dock will reboot to re-read; this is to appease the daemon */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version(device, dynamic_version); /* activate passive behavior */ self->passive_flow |= PASSIVE_RESET_MASK; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); return TRUE; } static gboolean fu_dell_dock_ec_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); guint64 tmp = 0; if (g_strcmp0(key, "DellDockUnlockTarget") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->unlock_target = tmp; return TRUE; } if (g_strcmp0(key, "DellDockBoardMin") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->board_min = tmp; return TRUE; } if (g_strcmp0(key, "DellDockVersionLowest") == 0) { self->ec_minimum_version = g_strdup(value); return TRUE; } if (g_str_has_prefix(key, "DellDockBoard")) { fu_device_set_metadata(device, key, value); return TRUE; } if (g_strcmp0(key, "DellDockBlobVersionOffset") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_version_offset = tmp; return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_dell_dock_ec_query(FuDevice *device, GError **error) { if (!fu_dell_dock_ec_get_dock_data(device, error)) return FALSE; return fu_dell_dock_ec_get_dock_info(device, error); } static gboolean fu_dell_dock_ec_setup(FuDevice *device, GError **error) { g_autoptr(GError) error_local = NULL; GPtrArray *children; /* if query looks bad, wait a few seconds and retry */ if (!fu_dell_dock_ec_query(device, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID)) { g_warning("%s", error_local->message); fu_device_sleep(device, 2000); /* ms */ if (!fu_dell_dock_ec_query(device, error)) return FALSE; } else { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } /* call setup on all the children we produced */ children = fu_device_get_children(device); for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index(children, i); g_autoptr(FuDeviceLocker) locker = NULL; g_debug("setup %s", fu_device_get_name(child)); locker = fu_device_locker_new(child, error); if (locker == NULL) return FALSE; } return TRUE; } static gboolean fu_dell_dock_ec_open(FuDevice *device, GError **error) { FuDellDockEc *self = FU_DELL_DOCK_EC(device); if (!fu_device_open(fu_device_get_proxy(device), error)) return FALSE; if (!self->data->dock_type) return fu_dell_dock_is_valid_dock(device, error); return TRUE; } static gboolean fu_dell_dock_ec_close(FuDevice *device, GError **error) { return fu_device_close(fu_device_get_proxy(device), error); } static void fu_dell_dock_ec_finalize(GObject *object) { FuDellDockEc *self = FU_DELL_DOCK_EC(object); g_free(self->ec_version); g_free(self->mst_version); g_free(self->tbt_version); g_free(self->data); g_free(self->raw_versions); g_free(self->ec_minimum_version); G_OBJECT_CLASS(fu_dell_dock_ec_parent_class)->finalize(object); } static void fu_dell_dock_ec_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_dell_dock_ec_init(FuDellDockEc *self) { self->data = g_new0(FuDellDockDockDataStructure, 1); self->raw_versions = g_new0(FuDellDockDockPackageFWVersion, 1); fu_device_add_protocol(FU_DEVICE(self), "com.dell.dock"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN); } static void fu_dell_dock_ec_class_init(FuDellDockEcClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_dell_dock_ec_finalize; klass_device->activate = fu_dell_dock_ec_activate; klass_device->to_string = fu_dell_dock_ec_to_string; klass_device->setup = fu_dell_dock_ec_setup; klass_device->open = fu_dell_dock_ec_open; klass_device->close = fu_dell_dock_ec_close; klass_device->write_firmware = fu_dell_dock_ec_write_fw; klass_device->set_quirk_kv = fu_dell_dock_ec_set_quirk_kv; klass_device->set_progress = fu_dell_dock_ec_set_progress; } FuDellDockEc * fu_dell_dock_ec_new(FuDevice *proxy) { FuDellDockEc *self = NULL; FuContext *ctx = fu_device_get_context(proxy); self = g_object_new(FU_TYPE_DELL_DOCK_EC, "context", ctx, NULL); fu_device_set_proxy(FU_DEVICE(self), proxy); fu_device_set_physical_id(FU_DEVICE(self), fu_device_get_physical_id(proxy)); fu_device_set_logical_id(FU_DEVICE(self), "ec"); return self; } fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-i2c-ec.h000066400000000000000000000027061460375044200217450ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_DELL_DOCK_EC (fu_dell_dock_ec_get_type()) G_DECLARE_FINAL_TYPE(FuDellDockEc, fu_dell_dock_ec, FU, DELL_DOCK_EC, FuDevice) FuDellDockEc * fu_dell_dock_ec_new(FuDevice *proxy); const gchar * fu_dell_dock_ec_get_module_type(FuDevice *device); gboolean fu_dell_dock_ec_needs_tbt(FuDevice *device); gboolean fu_dell_dock_ec_tbt_passive(FuDevice *device); gboolean fu_dell_dock_ec_modify_lock(FuDevice *device, guint8 target, gboolean unlocked, GError **error); gboolean fu_dell_dock_ec_reboot_dock(FuDevice *device, GError **error); const gchar * fu_dell_dock_ec_get_mst_version(FuDevice *device); const gchar * fu_dell_dock_ec_get_tbt_version(FuDevice *device); guint32 fu_dell_dock_ec_get_status_version(FuDevice *device); gboolean fu_dell_dock_ec_commit_package(FuDevice *device, GBytes *blob_fw, GError **error); gboolean fu_dell_dock_module_is_usb4(FuDevice *device); guint8 fu_dell_dock_get_dock_type(FuDevice *device); fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-i2c-mst.c000066400000000000000000001075301460375044200221550ustar00rootroot00000000000000/* * Copyright (C) 2018 Synaptics * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include #include "fu-dell-dock-common.h" #define I2C_MST_ADDRESS 0x72 /* Panamera MST registers */ #define PANAMERA_MST_RC_TRIGGER_ADDR 0x2000fc #define PANAMERA_MST_CORE_MCU_BOOTLOADER_STS 0x20010c #define PANAMERA_MST_RC_COMMAND_ADDR 0x200110 #define PANAMERA_MST_RC_OFFSET_ADDR 0x200114 #define PANAMERA_MST_RC_LENGTH_ADDR 0x200118 #define PANAMERA_MST_RC_DATA_ADDR 0x200120 #define PANAMERA_MST_CORE_MCU_FW_VERSION 0x200160 #define PANAMERA_MST_REG_QUAD_DISABLE 0x200fc0 #define PANAMERA_MST_REG_HDCP22_DISABLE 0x200f90 /* Cayenne MST registers */ #define CAYENNE_MST_RC_TRIGGER_ADDR 0x2020021C #define CAYENNE_MST_CORE_MCU_BOOTLOADER_STS 0x2020022C #define CAYENNE_MST_RC_COMMAND_ADDR 0x20200280 #define CAYENNE_MST_RC_OFFSET_ADDR 0x20200284 #define CAYENNE_MST_RC_LENGTH_ADDR 0x20200288 #define CAYENNE_MST_RC_DATA_ADDR 0x20200290 /* MST remote control commands */ #define MST_CMD_ENABLE_REMOTE_CONTROL 0x1 #define MST_CMD_DISABLE_REMOTE_CONTROL 0x2 #define MST_CMD_CHECKSUM 0x11 #define MST_CMD_ERASE_FLASH 0x14 #define MST_CMD_WRITE_FLASH 0x20 #define MST_CMD_READ_FLASH 0x30 #define MST_CMD_WRITE_MEMORY 0x21 #define MST_CMD_READ_MEMORY 0x31 /* Cayenne specific remote control commands */ #define MST_CMD_CRC16_CHECKSUM 0x17 #define MST_CMD_ACTIVATE_FW 0x18 /* Arguments related to flashing */ #define FLASH_SECTOR_ERASE_4K 0x1000 #define FLASH_SECTOR_ERASE_32K 0x2000 #define FLASH_SECTOR_ERASE_64K 0x3000 #define EEPROM_TAG_OFFSET 0x1fff0 #define EEPROM_BANK_OFFSET 0x20000 #define EEPROM_ESM_OFFSET 0x40000 /* Flash offsets */ #define MST_BOARDID_OFFSET 0x10e /* Remote control offsets */ #define MST_CHIPID_OFFSET 0x1500 /* magic triggers */ #define MST_TRIGGER_WRITE 0xf2 #define MST_TRIGGER_REBOOT 0xf5 /* IDs used in DELL_DOCK */ #define EXPECTED_CHIPID 0x5331 /* firmware file offsets */ #define MST_BLOB_VERSION_OFFSET 0x06F0 typedef enum { Panamera_mst, Cayenne_mst, Unknown, } MSTType; typedef enum { Bank0, Bank1, ESM, Cayenne, } MSTBank; typedef struct { guint start; guint length; guint checksum_cmd; } MSTBankAttributes; const MSTBankAttributes bank0_attributes = { .start = 0, .length = EEPROM_BANK_OFFSET, .checksum_cmd = MST_CMD_CHECKSUM, }; const MSTBankAttributes bank1_attributes = { .start = EEPROM_BANK_OFFSET, .length = EEPROM_BANK_OFFSET, .checksum_cmd = MST_CMD_CHECKSUM, }; const MSTBankAttributes esm_attributes = { .start = EEPROM_ESM_OFFSET, .length = 0x3ffff, .checksum_cmd = MST_CMD_CHECKSUM, }; const MSTBankAttributes cayenne_attributes = { .start = 0, .length = 0x50000, .checksum_cmd = MST_CMD_CRC16_CHECKSUM, }; FuHIDI2CParameters mst_base_settings = { .i2ctargetaddr = I2C_MST_ADDRESS, .regaddrlen = 0, .i2cspeed = I2C_SPEED_400K, }; struct _FuDellDockMst { FuDevice parent_instance; guint8 unlock_target; guint64 blob_major_offset; guint64 blob_minor_offset; guint64 blob_build_offset; guint32 mst_rc_trigger_addr; guint32 mst_rc_command_addr; guint32 mst_rc_data_addr; guint32 mst_core_mcu_bootloader_addr; }; G_DEFINE_TYPE(FuDellDockMst, fu_dell_dock_mst, FU_TYPE_DEVICE) /** * fu_dell_dock_mst_get_bank_attribs: * @bank: the MSTBank * @out (out): the MSTBankAttributes attribute that matches * @error: (nullable): optional return location for an error * * Returns a structure that corresponds to the attributes for a bank * * Returns: %TRUE for success **/ static gboolean fu_dell_dock_mst_get_bank_attribs(MSTBank bank, const MSTBankAttributes **out, GError **error) { switch (bank) { case Bank0: *out = &bank0_attributes; break; case Bank1: *out = &bank1_attributes; break; case ESM: *out = &esm_attributes; break; case Cayenne: *out = &cayenne_attributes; break; default: g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid bank specified %u", bank); return FALSE; } return TRUE; } static gboolean fu_dell_dock_mst_rc_command(FuDevice *device, guint8 cmd, guint32 length, guint32 offset, const guint8 *data, GError **error); static gboolean fu_dell_dock_mst_read_register(FuDevice *proxy, guint32 address, gsize length, GBytes **bytes, GError **error) { g_return_val_if_fail(proxy != NULL, FALSE); g_return_val_if_fail(bytes != NULL, FALSE); g_return_val_if_fail(length <= 32, FALSE); /* write the offset we're querying */ if (!fu_dell_dock_hid_i2c_write(proxy, (guint8 *)&address, 4, &mst_base_settings, error)) return FALSE; /* read data for the result */ if (!fu_dell_dock_hid_i2c_read(proxy, 0, length, bytes, &mst_base_settings, error)) return FALSE; return TRUE; } static gboolean fu_dell_dock_mst_write_register(FuDevice *proxy, guint32 address, guint8 *data, gsize length, GError **error) { g_autofree guint8 *buffer = g_malloc0(length + 4); g_return_val_if_fail(proxy != NULL, FALSE); g_return_val_if_fail(data != NULL, FALSE); memcpy(buffer, &address, 4); memcpy(buffer + 4, data, length); /* write the offset we're querying */ return fu_dell_dock_hid_i2c_write(proxy, buffer, length + 4, &mst_base_settings, error); } static gboolean fu_dell_dock_mst_query_active_bank(FuDevice *proxy, MSTBank *active, GError **error) { g_autoptr(GBytes) bytes = NULL; const guint32 *data = NULL; gsize length = 4; if (!fu_dell_dock_mst_read_register(proxy, PANAMERA_MST_CORE_MCU_BOOTLOADER_STS, length, &bytes, error)) { g_prefix_error(error, "Failed to query active bank: "); return FALSE; } data = g_bytes_get_data(bytes, &length); if ((data[0] & (1 << 7)) || (data[0] & (1 << 30))) *active = Bank1; else *active = Bank0; g_debug("MST: active bank is: %u", *active); return TRUE; } static gboolean fu_dell_dock_mst_disable_remote_control(FuDevice *device, GError **error) { g_debug("MST: Disabling remote control"); return fu_dell_dock_mst_rc_command(device, MST_CMD_DISABLE_REMOTE_CONTROL, 0, 0, NULL, error); } static gboolean fu_dell_dock_mst_enable_remote_control(FuDevice *device, GError **error) { g_autoptr(GError) error_local = NULL; const gchar *data = "PRIUS"; g_debug("MST: Enabling remote control"); if (!fu_dell_dock_mst_rc_command(device, MST_CMD_ENABLE_REMOTE_CONTROL, 5, 0, (guint8 *)data, &error_local)) { g_debug("Failed to enable remote control: %s", error_local->message); /* try to disable / re-enable */ if (!fu_dell_dock_mst_disable_remote_control(device, error)) return FALSE; return fu_dell_dock_mst_enable_remote_control(device, error); } return TRUE; } static gboolean fu_dell_dock_trigger_rc_command(FuDevice *device, GError **error) { const guint8 *result = NULL; FuDevice *proxy = fu_device_get_proxy(device); FuDellDockMst *self = FU_DELL_DOCK_MST(device); guint32 tmp; /* Trigger the write */ tmp = MST_TRIGGER_WRITE; if (!fu_dell_dock_mst_write_register(proxy, self->mst_rc_trigger_addr, (guint8 *)&tmp, sizeof(guint32), error)) { g_prefix_error(error, "Failed to write MST_RC_TRIGGER_ADDR: "); return FALSE; } /* poll for completion */ tmp = 0xffff; for (guint i = 0; i < 1000; i++) { g_autoptr(GBytes) bytes = NULL; if (!fu_dell_dock_mst_read_register(proxy, self->mst_rc_command_addr, sizeof(guint32), &bytes, error)) { g_prefix_error(error, "Failed to poll MST_RC_COMMAND_ADDR: "); return FALSE; } result = g_bytes_get_data(bytes, NULL); /* complete */ if ((result[2] & 0x80) == 0) { tmp = result[3]; break; } fu_device_sleep(FU_DEVICE(self), 2); /* ms */ } switch (tmp) { /* need to enable remote control */ case 4: return fu_dell_dock_mst_enable_remote_control(device, error); /* error scenarios */ case 3: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown error"); return FALSE; case 2: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unsupported command"); return FALSE; case 1: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid argument"); return FALSE; /* success scenario */ case 0: return TRUE; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Command timed out or unknown failure: %x", tmp); return FALSE; } } static gboolean fu_dell_dock_mst_rc_command(FuDevice *device, guint8 cmd, guint32 length, guint32 offset, const guint8 *data, GError **error) { /* 4 for cmd, 4 for offset, 4 for length, 4 for garbage */ FuDellDockMst *self = FU_DELL_DOCK_MST(device); FuDevice *proxy = fu_device_get_proxy(device); gint buffer_len = (data == NULL) ? 12 : length + 16; g_autofree guint8 *buffer = g_malloc0(buffer_len); guint32 tmp; g_return_val_if_fail(proxy != NULL, FALSE); /* command */ tmp = (cmd | 0x80) << 16; memcpy(buffer, &tmp, 4); /* offset */ memcpy(buffer + 4, &offset, 4); /* length */ memcpy(buffer + 8, &length, 4); /* data */ if (data != NULL) memcpy(buffer + 16, data, length); /* write the combined register stream */ if (!fu_dell_dock_mst_write_register(proxy, self->mst_rc_command_addr, buffer, buffer_len, error)) return FALSE; return fu_dell_dock_trigger_rc_command(device, error); } static MSTType fu_dell_dock_mst_check_type(FuDevice *device) { GPtrArray *instance_ids; const gchar *tmp = NULL; instance_ids = fu_device_get_instance_ids(device); for (guint i = 0; i < instance_ids->len; i++) { tmp = g_ptr_array_index(instance_ids, i); if (g_strcmp0(tmp, DELL_DOCK_VMM6210_INSTANCE_ID) == 0) return Cayenne_mst; if (g_strcmp0(tmp, DELL_DOCK_VM5331_INSTANCE_ID) == 0) return Panamera_mst; } return Unknown; } static gboolean fu_dell_dock_mst_check_offset(guint8 byte, guint8 offset) { if ((byte & offset) != 0) return TRUE; return FALSE; } static gboolean fu_d19_mst_check_fw(FuDevice *device, GError **error) { FuDellDockMst *self = FU_DELL_DOCK_MST(device); g_autoptr(GBytes) bytes = NULL; const guint8 *data; gsize length = 4; if (!fu_dell_dock_mst_read_register(fu_device_get_proxy(device), self->mst_core_mcu_bootloader_addr, length, &bytes, error)) return FALSE; data = g_bytes_get_data(bytes, &length); g_debug("MST: firmware check: %d", fu_dell_dock_mst_check_offset(data[0], 0x01)); g_debug("MST: HDCP key check: %d", fu_dell_dock_mst_check_offset(data[0], 0x02)); g_debug("MST: Config0 check: %d", fu_dell_dock_mst_check_offset(data[0], 0x04)); g_debug("MST: Config1 check: %d", fu_dell_dock_mst_check_offset(data[0], 0x08)); if (fu_dell_dock_mst_check_offset(data[0], 0xF0)) g_debug("MST: running in bootloader"); else g_debug("MST: running in firmware"); g_debug("MST: Error code: %x", data[1]); g_debug("MST: GPIO boot strap record: %d", data[2]); g_debug("MST: Bootloader version number %x", data[3]); return TRUE; } static guint16 fu_dell_dock_mst_get_crc(guint8 type, guint32 length, const guint8 *payload_data) { static const guint16 CRC16_table[] = { 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, 0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, 0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, 0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, 0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, 0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, 0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, 0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, 0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, 0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, 0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, 0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, 0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, 0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202}; static const guint16 CRC8_table[] = { 0x00, 0xd5, 0x7f, 0xaa, 0xfe, 0x2b, 0x81, 0x54, 0x29, 0xfc, 0x56, 0x83, 0xd7, 0x02, 0xa8, 0x7d, 0x52, 0x87, 0x2d, 0xf8, 0xac, 0x79, 0xd3, 0x06, 0x7b, 0xae, 0x04, 0xd1, 0x85, 0x50, 0xfa, 0x2f, 0xa4, 0x71, 0xdb, 0x0e, 0x5a, 0x8f, 0x25, 0xf0, 0x8d, 0x58, 0xf2, 0x27, 0x73, 0xa6, 0x0c, 0xd9, 0xf6, 0x23, 0x89, 0x5c, 0x08, 0xdd, 0x77, 0xa2, 0xdf, 0x0a, 0xa0, 0x75, 0x21, 0xf4, 0x5e, 0x8b, 0x9d, 0x48, 0xe2, 0x37, 0x63, 0xb6, 0x1c, 0xc9, 0xb4, 0x61, 0xcb, 0x1e, 0x4a, 0x9f, 0x35, 0xe0, 0xcf, 0x1a, 0xb0, 0x65, 0x31, 0xe4, 0x4e, 0x9b, 0xe6, 0x33, 0x99, 0x4c, 0x18, 0xcd, 0x67, 0xb2, 0x39, 0xec, 0x46, 0x93, 0xc7, 0x12, 0xb8, 0x6d, 0x10, 0xc5, 0x6f, 0xba, 0xee, 0x3b, 0x91, 0x44, 0x6b, 0xbe, 0x14, 0xc1, 0x95, 0x40, 0xea, 0x3f, 0x42, 0x97, 0x3d, 0xe8, 0xbc, 0x69, 0xc3, 0x16, 0xef, 0x3a, 0x90, 0x45, 0x11, 0xc4, 0x6e, 0xbb, 0xc6, 0x13, 0xb9, 0x6c, 0x38, 0xed, 0x47, 0x92, 0xbd, 0x68, 0xc2, 0x17, 0x43, 0x96, 0x3c, 0xe9, 0x94, 0x41, 0xeb, 0x3e, 0x6a, 0xbf, 0x15, 0xc0, 0x4b, 0x9e, 0x34, 0xe1, 0xb5, 0x60, 0xca, 0x1f, 0x62, 0xb7, 0x1d, 0xc8, 0x9c, 0x49, 0xe3, 0x36, 0x19, 0xcc, 0x66, 0xb3, 0xe7, 0x32, 0x98, 0x4d, 0x30, 0xe5, 0x4f, 0x9a, 0xce, 0x1b, 0xb1, 0x64, 0x72, 0xa7, 0x0d, 0xd8, 0x8c, 0x59, 0xf3, 0x26, 0x5b, 0x8e, 0x24, 0xf1, 0xa5, 0x70, 0xda, 0x0f, 0x20, 0xf5, 0x5f, 0x8a, 0xde, 0x0b, 0xa1, 0x74, 0x09, 0xdc, 0x76, 0xa3, 0xf7, 0x22, 0x88, 0x5d, 0xd6, 0x03, 0xa9, 0x7c, 0x28, 0xfd, 0x57, 0x82, 0xff, 0x2a, 0x80, 0x55, 0x01, 0xd4, 0x7e, 0xab, 0x84, 0x51, 0xfb, 0x2e, 0x7a, 0xaf, 0x05, 0xd0, 0xad, 0x78, 0xd2, 0x07, 0x53, 0x86, 0x2c, 0xf9}; guint8 val; guint16 crc = 0; const guint8 *message = payload_data; if (type == 8) { for (guint32 byte = 0; byte < length; ++byte) { val = (guint8)(message[byte] ^ crc); crc = CRC8_table[val]; } } else { for (guint32 byte = 0; byte < length; ++byte) { val = (guint8)(message[byte] ^ (crc >> 8)); crc = CRC16_table[val] ^ (crc << 8); } } return crc; } static gboolean fu_dell_dock_mst_checksum_bank(FuDevice *device, GBytes *blob_fw, MSTBank bank, gboolean *checksum, GError **error) { FuDellDockMst *self = FU_DELL_DOCK_MST(device); g_autoptr(GBytes) csum_bytes = NULL; const MSTBankAttributes *attribs = NULL; gsize length = 0; const guint8 *data = g_bytes_get_data(blob_fw, &length); guint32 payload_sum = 0; guint32 bank_sum = 0; g_return_val_if_fail(blob_fw != NULL, FALSE); g_return_val_if_fail(checksum != NULL, FALSE); if (!fu_dell_dock_mst_get_bank_attribs(bank, &attribs, error)) return FALSE; /* bank is specified outside of payload */ if (attribs->start + attribs->length > length) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Payload %u is bigger than bank %u", attribs->start + attribs->length, bank); return FALSE; } /* checksum the file */ if (attribs->checksum_cmd == MST_CMD_CRC16_CHECKSUM) payload_sum = fu_dell_dock_mst_get_crc(16, (attribs->length + attribs->start), data); else { for (guint i = attribs->start; i < attribs->length + attribs->start; i++) { payload_sum += data[i]; } } g_debug("MST: Payload checksum: 0x%x", payload_sum); /* checksum the bank */ if (!fu_dell_dock_mst_rc_command(device, attribs->checksum_cmd, attribs->length, attribs->start, NULL, error)) { g_prefix_error(error, "Failed to checksum bank %u: ", bank); return FALSE; } /* read result from data register */ if (!fu_dell_dock_mst_read_register(fu_device_get_proxy(device), self->mst_rc_data_addr, 4, &csum_bytes, error)) return FALSE; data = g_bytes_get_data(csum_bytes, NULL); bank_sum = GUINT32_FROM_LE(data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24); g_debug("MST: Bank %u checksum: 0x%x", bank, bank_sum); *checksum = (bank_sum == payload_sum); return TRUE; } static gboolean fu_dell_dock_mst_erase_panamera_bank(FuDevice *device, MSTBank bank, GError **error) { const MSTBankAttributes *attribs = NULL; guint32 sector; if (!fu_dell_dock_mst_get_bank_attribs(bank, &attribs, error)) return FALSE; for (guint32 i = attribs->start; i < attribs->start + attribs->length; i += 0x10000) { sector = FLASH_SECTOR_ERASE_64K | (i / 0x10000); g_debug("MST: Erasing sector 0x%x", sector); if (!fu_dell_dock_mst_rc_command(device, MST_CMD_ERASE_FLASH, 4, 0, (guint8 *)§or, error)) { g_prefix_error(error, "Failed to erase sector 0x%x: ", sector); return FALSE; } } g_debug("MST: Waiting for flash clear to settle"); fu_device_sleep(device, 5000); /* ms */ return TRUE; } static gboolean fu_dell_dock_mst_erase_cayenne(FuDevice *device, GError **error) { guint8 data[4] = {0, 0x30, 0, 0}; for (guint8 i = 0; i < 5; i++) { data[0] = i; if (!fu_dell_dock_mst_rc_command(device, MST_CMD_ERASE_FLASH, 4, 0, (guint8 *)&data, error)) { g_prefix_error(error, "Failed to erase sector: %d", i); return FALSE; } } g_debug("MST: Waiting for flash clear to settle"); fu_device_sleep(device, 5000); return TRUE; } static gboolean fu_dell_dock_write_flash_bank(FuDevice *device, GBytes *blob_fw, MSTBank bank, FuProgress *progress, GError **error) { const MSTBankAttributes *attribs = NULL; gsize write_size = 32; guint end; const guint8 *data = g_bytes_get_data(blob_fw, NULL); g_return_val_if_fail(blob_fw != NULL, FALSE); if (!fu_dell_dock_mst_get_bank_attribs(bank, &attribs, error)) return FALSE; end = attribs->start + attribs->length; g_debug("MST: Writing payload to bank %u", bank); for (guint i = attribs->start; i < end; i += write_size) { if (!fu_dell_dock_mst_rc_command(device, MST_CMD_WRITE_FLASH, write_size, i, data + i, error)) { g_prefix_error(error, "Failed to write bank %u payload offset 0x%x: ", bank, i); return FALSE; } fu_progress_set_percentage_full(progress, i - attribs->start, end - attribs->start); } return TRUE; } static gboolean fu_dell_dock_mst_stop_esm(FuDevice *device, GError **error) { g_autoptr(GBytes) quad_bytes = NULL; g_autoptr(GBytes) hdcp_bytes = NULL; guint32 payload = 0x21; gsize length = sizeof(guint32); const guint8 *data; guint8 data_out[sizeof(guint32)]; /* disable ESM first */ if (!fu_dell_dock_mst_rc_command(device, MST_CMD_WRITE_MEMORY, length, PANAMERA_MST_RC_TRIGGER_ADDR, (guint8 *)&payload, error)) return FALSE; /* waiting for ESM exit */ fu_device_sleep(device, 1); /* disable QUAD mode */ if (!fu_dell_dock_mst_rc_command(device, MST_CMD_READ_MEMORY, length, PANAMERA_MST_REG_QUAD_DISABLE, NULL, error)) return FALSE; if (!fu_dell_dock_mst_read_register(fu_device_get_proxy(device), PANAMERA_MST_RC_DATA_ADDR, length, &quad_bytes, error)) return FALSE; data = g_bytes_get_data(quad_bytes, &length); memcpy(data_out, data, length); data_out[0] = 0x00; if (!fu_dell_dock_mst_rc_command(device, MST_CMD_WRITE_MEMORY, length, PANAMERA_MST_REG_QUAD_DISABLE, data_out, error)) return FALSE; /* disable HDCP2.2 */ if (!fu_dell_dock_mst_rc_command(device, MST_CMD_READ_MEMORY, length, PANAMERA_MST_REG_HDCP22_DISABLE, NULL, error)) return FALSE; if (!fu_dell_dock_mst_read_register(fu_device_get_proxy(device), PANAMERA_MST_RC_DATA_ADDR, length, &hdcp_bytes, error)) return FALSE; data = g_bytes_get_data(hdcp_bytes, &length); memcpy(data_out, data, length); data_out[0] = data[0] & (1 << 2); if (!fu_dell_dock_mst_rc_command(device, MST_CMD_WRITE_MEMORY, length, PANAMERA_MST_REG_HDCP22_DISABLE, data_out, error)) return FALSE; return TRUE; } static gboolean fu_dell_dock_mst_invalidate_bank(FuDevice *device, MSTBank bank_in_use, GError **error) { const MSTBankAttributes *attribs; g_autoptr(GBytes) bytes = NULL; const guint8 *crc_tag; const guint8 *new_tag; guint32 crc_offset; guint retries = 2; if (!fu_dell_dock_mst_get_bank_attribs(bank_in_use, &attribs, error)) { g_prefix_error(error, "unable to invalidate bank: "); return FALSE; } /* we need to write 4 byte increments over I2C so this differs from DP aux */ crc_offset = attribs->start + EEPROM_TAG_OFFSET + 12; /* Read CRC byte to flip */ if (!fu_dell_dock_mst_rc_command(device, MST_CMD_READ_FLASH, 4, crc_offset, NULL, error)) { g_prefix_error(error, "failed to read tag from flash: "); return FALSE; } if (!fu_dell_dock_mst_read_register(fu_device_get_proxy(device), PANAMERA_MST_RC_DATA_ADDR, 1, &bytes, error)) { return FALSE; } crc_tag = g_bytes_get_data(bytes, NULL); g_debug("CRC byte is currently 0x%x", crc_tag[3]); for (guint32 retries_cnt = 0;; retries_cnt++) { g_autoptr(GBytes) bytes_new = NULL; /* CRC8 is not 0xff, erase last 4k of bank# */ if (crc_tag[3] != 0xff) { guint32 sector = FLASH_SECTOR_ERASE_4K + (attribs->start + attribs->length - 0x1000) / 0x1000; g_debug("Erasing 4k from sector 0x%x invalidate bank %u", sector, bank_in_use); /* offset for last 4k of bank# */ if (!fu_dell_dock_mst_rc_command(device, MST_CMD_ERASE_FLASH, 4, 0, (guint8 *)§or, error)) { g_prefix_error(error, "failed to erase sector 0x%x: ", sector); return FALSE; } /* CRC8 is 0xff, set it to 0x00 */ } else { guint32 write = 0x00; g_debug("Writing 0x00 byte to 0x%x to invalidate bank %u", crc_offset, bank_in_use); if (!fu_dell_dock_mst_rc_command(device, MST_CMD_WRITE_FLASH, 4, crc_offset, (guint8 *)&write, error)) { g_prefix_error(error, "failed to clear CRC byte: "); return FALSE; } } /* re-read for comparison */ if (!fu_dell_dock_mst_rc_command(device, MST_CMD_READ_FLASH, 4, crc_offset, NULL, error)) { g_prefix_error(error, "failed to read tag from flash: "); return FALSE; } if (!fu_dell_dock_mst_read_register(fu_device_get_proxy(device), PANAMERA_MST_RC_DATA_ADDR, 4, &bytes_new, error)) { return FALSE; } new_tag = g_bytes_get_data(bytes_new, NULL); g_debug("CRC byte is currently 0x%x", new_tag[3]); /* tag successfully cleared */ if ((new_tag[3] == 0xff && crc_tag[3] != 0xff) || (new_tag[3] == 0x00 && crc_tag[3] == 0xff)) { break; } if (retries_cnt > retries) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "set tag invalid fail (new 0x%x; old 0x%x)", new_tag[3], crc_tag[3]); return FALSE; } } return TRUE; } static gboolean fu_dell_dock_mst_write_bank(FuDevice *device, GBytes *fw, guint8 bank, FuProgress *progress, GError **error) { const guint retries = 2; for (guint i = 0; i < retries; i++) { gboolean checksum = FALSE; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 15, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 84, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1, NULL); if (!fu_dell_dock_mst_erase_panamera_bank(device, bank, error)) return FALSE; fu_progress_step_done(progress); if (!fu_dell_dock_write_flash_bank(device, fw, bank, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); if (!fu_dell_dock_mst_checksum_bank(device, fw, bank, &checksum, error)) return FALSE; if (!checksum) { g_debug("MST: Failed to verify checksum on bank %u", bank); fu_progress_reset(progress); continue; } fu_progress_step_done(progress); g_debug("MST: Bank %u successfully flashed", bank); return TRUE; } /* failed after all our retries */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to write to bank %u", bank); return FALSE; } static FuProgress * fu_dell_dock_mst_set_local_progress(FuProgress *progress, guint steps) { FuProgress *progress_local; progress_local = fu_progress_get_child(progress); fu_progress_set_id(progress_local, G_STRLOC); fu_progress_set_steps(progress_local, steps); return progress_local; } static gboolean fu_dell_dock_mst_write_panamera(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, FuProgress *progress, GError **error) { gboolean checksum = FALSE; MSTBank bank_in_use = 0; guint8 order[2] = {ESM, Bank0}; FuProgress *progress_local; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "stop-esm"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99, NULL); /* determine the flash order */ if (!fu_dell_dock_mst_query_active_bank(fu_device_get_proxy(device), &bank_in_use, error)) return FALSE; if (bank_in_use == Bank0) order[1] = Bank1; /* ESM needs special handling during flash process*/ if (!fu_dell_dock_mst_stop_esm(device, error)) return FALSE; fu_progress_step_done(progress); progress_local = fu_dell_dock_mst_set_local_progress(progress, 2); /* Write each bank in order */ for (guint phase = 0; phase < 2; phase++) { g_debug("MST: Checking bank %u", order[phase]); if (!fu_dell_dock_mst_checksum_bank(device, fw, order[phase], &checksum, error)) return FALSE; if (checksum) { g_debug("MST: bank %u is already up to date", order[phase]); fu_progress_step_done(progress_local); continue; } g_debug("MST: bank %u needs to be updated", order[phase]); if (!fu_dell_dock_mst_write_bank(device, fw, order[phase], fu_progress_get_child(progress_local), error)) return FALSE; fu_progress_step_done(progress_local); } /* invalidate the previous bank */ if (!fu_dell_dock_mst_invalidate_bank(device, bank_in_use, error)) { g_prefix_error(error, "failed to invalidate bank %u: ", bank_in_use); return FALSE; } fu_progress_step_done(progress); return TRUE; } static gboolean fu_dell_dock_mst_write_cayenne(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, FuProgress *progress, GError **error) { gboolean checksum = FALSE; guint retries = 2; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 3, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 97, NULL); for (guint i = 0; i < retries; i++) { if (!fu_dell_dock_mst_erase_cayenne(device, error)) return FALSE; fu_progress_step_done(progress); if (!fu_dell_dock_write_flash_bank(device, fw, Cayenne, fu_progress_get_child(progress), error)) return FALSE; if (!fu_dell_dock_mst_checksum_bank(device, fw, Cayenne, &checksum, error)) return FALSE; fu_progress_step_done(progress); if (!checksum) { g_debug("MST: Failed to verify checksum"); fu_progress_reset(progress); continue; } break; } /* failed after all our retries */ if (!checksum) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to write to bank"); return FALSE; } /* activate the FW */ if (!fu_dell_dock_mst_rc_command(device, MST_CMD_ACTIVATE_FW, g_bytes_get_size(fw), 0x0, NULL, error)) { g_prefix_error(error, "Failed to activate FW: "); return FALSE; } return TRUE; } static gboolean fu_dell_dock_mst_write_fw(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDellDockMst *self = FU_DELL_DOCK_MST(device); const guint8 *data; g_autofree gchar *dynamic_version = NULL; g_autoptr(GBytes) fw = NULL; MSTType type; g_return_val_if_fail(device != NULL, FALSE); g_return_val_if_fail(FU_IS_FIRMWARE(firmware), FALSE); g_return_val_if_fail(fu_device_get_proxy(device) != NULL, FALSE); /* open the hub*/ if (!fu_device_open(fu_device_get_proxy(device), error)) return FALSE; /* open up access to controller bus */ if (!fu_dell_dock_set_power(device, self->unlock_target, TRUE, error)) return FALSE; /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; data = g_bytes_get_data(fw, NULL); dynamic_version = g_strdup_printf("%02x.%02x.%02x", data[self->blob_major_offset], data[self->blob_minor_offset], data[self->blob_build_offset]); g_info("writing MST firmware version %s", dynamic_version); /* enable remote control */ if (!fu_dell_dock_mst_enable_remote_control(device, error)) return FALSE; type = fu_dell_dock_mst_check_type(device); if (type == Panamera_mst) { if (!fu_dell_dock_mst_write_panamera(device, fw, flags, progress, error)) return FALSE; } else if (type == Cayenne_mst) { if (!fu_dell_dock_mst_write_cayenne(device, fw, flags, progress, error)) return FALSE; } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unknown mst found"); return FALSE; } /* dock will reboot to re-read; this is to appease the daemon */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, dynamic_version); /* disable remote control now */ return fu_dell_dock_mst_disable_remote_control(device, error); } static gboolean fu_dell_dock_mst_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuDellDockMst *self = FU_DELL_DOCK_MST(device); guint64 tmp = 0; if (g_strcmp0(key, "DellDockUnlockTarget") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->unlock_target = tmp; return TRUE; } if (g_strcmp0(key, "DellDockBlobMajorOffset") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_major_offset = tmp; return TRUE; } if (g_strcmp0(key, "DellDockBlobMinorOffset") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_minor_offset = tmp; return TRUE; } if (g_strcmp0(key, "DellDockBlobBuildOffset") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_build_offset = tmp; return TRUE; } if (g_strcmp0(key, "DellDockInstallDurationI2C") == 0) { if (!fu_strtoull(value, &tmp, 0, 60 * 60 * 24, error)) return FALSE; fu_device_set_install_duration(device, tmp); return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_dell_dock_mst_setup(FuDevice *device, GError **error) { FuDevice *parent; const gchar *version; /* sanity check that we can talk to MST */ if (!fu_d19_mst_check_fw(device, error)) return FALSE; /* set version from EC if we know it */ parent = fu_device_get_parent(device); version = fu_dell_dock_ec_get_mst_version(parent); if (version != NULL) { fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, version); } return TRUE; } static gboolean fu_dell_dock_mst_probe(FuDevice *device, GError **error) { MSTType type; FuDellDockMst *self = FU_DELL_DOCK_MST(device); fu_device_set_logical_id(FU_DEVICE(device), "mst"); /* confige mst register via instance id*/ type = fu_dell_dock_mst_check_type(device); switch (type) { case Cayenne_mst: self->mst_rc_trigger_addr = CAYENNE_MST_RC_TRIGGER_ADDR; self->mst_rc_command_addr = CAYENNE_MST_RC_COMMAND_ADDR; self->mst_rc_data_addr = CAYENNE_MST_RC_DATA_ADDR; self->mst_core_mcu_bootloader_addr = CAYENNE_MST_CORE_MCU_BOOTLOADER_STS; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); return TRUE; case Panamera_mst: self->mst_rc_trigger_addr = PANAMERA_MST_RC_TRIGGER_ADDR; self->mst_rc_command_addr = PANAMERA_MST_RC_COMMAND_ADDR; self->mst_rc_data_addr = PANAMERA_MST_RC_DATA_ADDR; self->mst_core_mcu_bootloader_addr = PANAMERA_MST_CORE_MCU_BOOTLOADER_STS; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); return TRUE; case Unknown: default: g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unknown mst found"); return FALSE; } return TRUE; } static gboolean fu_dell_dock_mst_open(FuDevice *device, GError **error) { FuDellDockMst *self = FU_DELL_DOCK_MST(device); FuDevice *parent = fu_device_get_parent(device); g_return_val_if_fail(self->unlock_target != 0, FALSE); g_return_val_if_fail(parent != NULL, FALSE); if (fu_device_get_proxy(device) == NULL) fu_device_set_proxy(device, fu_device_get_proxy(parent)); if (!fu_device_open(fu_device_get_proxy(device), error)) return FALSE; /* open up access to controller bus */ if (!fu_dell_dock_set_power(device, self->unlock_target, TRUE, error)) return FALSE; return TRUE; } static gboolean fu_dell_dock_mst_close(FuDevice *device, GError **error) { FuDellDockMst *self = FU_DELL_DOCK_MST(device); /* close access to controller bus */ if (!fu_dell_dock_set_power(device, self->unlock_target, FALSE, error)) return FALSE; return fu_device_close(fu_device_get_proxy(device), error); } static void fu_dell_dock_mst_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_dell_dock_mst_init(FuDellDockMst *self) { fu_device_add_protocol(FU_DEVICE(self), "com.synaptics.mst"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); } static void fu_dell_dock_mst_class_init(FuDellDockMstClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_dell_dock_mst_probe; klass_device->open = fu_dell_dock_mst_open; klass_device->close = fu_dell_dock_mst_close; klass_device->setup = fu_dell_dock_mst_setup; klass_device->write_firmware = fu_dell_dock_mst_write_fw; klass_device->set_quirk_kv = fu_dell_dock_mst_set_quirk_kv; klass_device->set_progress = fu_dell_dock_mst_set_progress; } FuDellDockMst * fu_dell_dock_mst_new(FuContext *ctx) { FuDellDockMst *device = NULL; device = g_object_new(FU_TYPE_DELL_DOCK_MST, "context", ctx, NULL); return device; } fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-i2c-mst.h000066400000000000000000000013371460375044200221600ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_DELL_DOCK_MST (fu_dell_dock_mst_get_type()) G_DECLARE_FINAL_TYPE(FuDellDockMst, fu_dell_dock_mst, FU, DELL_DOCK_MST, FuDevice) FuDellDockMst * fu_dell_dock_mst_new(FuContext *ctx); fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-i2c-tbt.c000066400000000000000000000211261460375044200221370ustar00rootroot00000000000000/* * Copyright (C) 2019 Intel Corporation. * Copyright (C) 2019 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include #include "fu-dell-dock-common.h" #define I2C_TBT_ADDRESS 0xa2 const FuHIDI2CParameters tbt_base_settings = { .i2ctargetaddr = I2C_TBT_ADDRESS, .regaddrlen = 1, .i2cspeed = I2C_SPEED_400K, }; /* TR Device ID */ #define PID_OFFSET 0x05 #define INTEL_PID 0x15ef /* earlier versions have bugs */ #define MIN_NVM "36.01" struct _FuDellDockTbt { FuDevice parent_instance; guint8 unlock_target; guint64 blob_major_offset; guint64 blob_minor_offset; gchar *hub_minimum_version; }; G_DEFINE_TYPE(FuDellDockTbt, fu_dell_dock_tbt, FU_TYPE_DEVICE) static gboolean fu_dell_dock_tbt_write_fw(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDellDockTbt *self = FU_DELL_DOCK_TBT(device); guint32 start_offset = 0; gsize image_size = 0; const guint8 *buffer; guint16 target_system = 0; g_autoptr(GTimer) timer = g_timer_new(); g_autofree gchar *dynamic_version = NULL; g_autoptr(GBytes) fw = NULL; g_return_val_if_fail(device != NULL, FALSE); g_return_val_if_fail(FU_IS_FIRMWARE(firmware), FALSE); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; buffer = g_bytes_get_data(fw, &image_size); dynamic_version = g_strdup_printf("%02x.%02x", buffer[self->blob_major_offset], buffer[self->blob_minor_offset]); g_info("writing Thunderbolt firmware version %s", dynamic_version); g_debug("Total Image size: %" G_GSIZE_FORMAT, image_size); memcpy(&start_offset, buffer, sizeof(guint32)); g_debug("Header size 0x%x", start_offset); if (start_offset > image_size) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Image header is too big (0x%x)", start_offset); return FALSE; } memcpy(&target_system, buffer + start_offset + PID_OFFSET, sizeof(guint16)); if (target_system != INTEL_PID) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Image is not intended for this system (0x%x)", target_system); return FALSE; } buffer += start_offset; image_size -= start_offset; g_debug("waking Thunderbolt controller"); if (!fu_dell_dock_hid_tbt_wake(fu_device_get_proxy(device), &tbt_base_settings, error)) return FALSE; fu_device_sleep(device, 2000); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < image_size; i += HIDI2C_MAX_WRITE, buffer += HIDI2C_MAX_WRITE) { guint8 write_size = (image_size - i) > HIDI2C_MAX_WRITE ? HIDI2C_MAX_WRITE : (image_size - i); if (!fu_dell_dock_hid_tbt_write(fu_device_get_proxy(device), i, buffer, write_size, &tbt_base_settings, error)) return FALSE; fu_progress_set_percentage_full(progress, i, image_size); } g_debug("writing took %f seconds", g_timer_elapsed(timer, NULL)); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_BUSY); if (fu_dell_dock_ec_tbt_passive(fu_device_get_parent(device))) { g_info("using passive flow for Thunderbolt"); } else if (!fu_dell_dock_hid_tbt_authenticate(fu_device_get_proxy(device), &tbt_base_settings, error)) { g_prefix_error(error, "failed to authenticate: "); return FALSE; } /* dock will reboot to re-read; this is to appease the daemon */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PAIR); fu_device_set_version(device, dynamic_version); return TRUE; } static gboolean fu_dell_dock_tbt_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuDellDockTbt *self = FU_DELL_DOCK_TBT(device); guint64 tmp = 0; if (g_strcmp0(key, "DellDockUnlockTarget") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->unlock_target = tmp; return TRUE; } if (g_strcmp0(key, "DellDockInstallDurationI2C") == 0) { if (!fu_strtoull(value, &tmp, 0, 60 * 60 * 24, error)) return FALSE; fu_device_set_install_duration(device, tmp); return TRUE; } if (g_strcmp0(key, "DellDockHubVersionLowest") == 0) { self->hub_minimum_version = g_strdup(value); return TRUE; } if (g_strcmp0(key, "DellDockBlobMajorOffset") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_major_offset = tmp; return TRUE; } if (g_strcmp0(key, "DellDockBlobMinorOffset") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_minor_offset = tmp; return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_dell_dock_tbt_setup(FuDevice *device, GError **error) { FuDellDockTbt *self = FU_DELL_DOCK_TBT(device); FuDevice *parent; const gchar *version; const gchar *hub_version; /* set version from EC if we know it */ parent = fu_device_get_parent(device); version = fu_dell_dock_ec_get_tbt_version(parent); if (version != NULL) { fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PAIR); fu_device_set_version(device, version); } /* minimum version of NVM that supports this feature */ if (version == NULL || fu_version_compare(version, MIN_NVM, FWUPD_VERSION_FORMAT_PAIR) < 0) { fu_device_set_update_error( device, "Updates over I2C are disabled due to insufficient NVM version"); return TRUE; } /* minimum Hub2 version that supports this feature */ hub_version = fu_device_get_version(fu_device_get_proxy(device)); if (fu_version_compare(hub_version, self->hub_minimum_version, FWUPD_VERSION_FORMAT_PAIR) < 0) { fu_device_set_update_error( device, "Updates over I2C are disabled due to insufficient USB 3.1 G2 hub version"); return TRUE; } return TRUE; } static gboolean fu_dell_dock_tbt_probe(FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent(device); fu_device_set_physical_id(device, fu_device_get_physical_id(parent)); fu_device_set_logical_id(FU_DEVICE(device), "tbt"); fu_device_add_instance_id(device, DELL_DOCK_TBT_INSTANCE_ID); /* this is true only when connected to non-thunderbolt port */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); return TRUE; } static gboolean fu_dell_dock_tbt_open(FuDevice *device, GError **error) { FuDellDockTbt *self = FU_DELL_DOCK_TBT(device); g_return_val_if_fail(self->unlock_target != 0, FALSE); if (!fu_device_open(fu_device_get_proxy(device), error)) return FALSE; /* adjust to access controller */ if (!fu_dell_dock_set_power(device, self->unlock_target, TRUE, error)) return FALSE; return TRUE; } static gboolean fu_dell_dock_tbt_close(FuDevice *device, GError **error) { FuDellDockTbt *self = FU_DELL_DOCK_TBT(device); /* adjust to access controller */ if (!fu_dell_dock_set_power(device, self->unlock_target, FALSE, error)) return FALSE; return fu_device_close(fu_device_get_proxy(device), error); } static void fu_dell_dock_tbt_finalize(GObject *object) { FuDellDockTbt *self = FU_DELL_DOCK_TBT(object); g_free(self->hub_minimum_version); G_OBJECT_CLASS(fu_dell_dock_tbt_parent_class)->finalize(object); } static void fu_dell_dock_tbt_init(FuDellDockTbt *self) { fu_device_add_protocol(FU_DEVICE(self), "com.intel.thunderbolt"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); } static void fu_dell_dock_tbt_class_init(FuDellDockTbtClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_dell_dock_tbt_finalize; klass_device->probe = fu_dell_dock_tbt_probe; klass_device->setup = fu_dell_dock_tbt_setup; klass_device->open = fu_dell_dock_tbt_open; klass_device->close = fu_dell_dock_tbt_close; klass_device->write_firmware = fu_dell_dock_tbt_write_fw; klass_device->set_quirk_kv = fu_dell_dock_tbt_set_quirk_kv; } FuDellDockTbt * fu_dell_dock_tbt_new(FuDevice *proxy) { FuContext *ctx = fu_device_get_context(proxy); FuDellDockTbt *self = g_object_new(FU_TYPE_DELL_DOCK_TBT, "context", ctx, NULL); fu_device_set_proxy(FU_DEVICE(self), proxy); return self; } fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-i2c-tbt.h000066400000000000000000000014111460375044200221370ustar00rootroot00000000000000/* * Copyright (C) 2019 Intel Corporation. * Copyright (C) 2019 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_DELL_DOCK_TBT (fu_dell_dock_tbt_get_type()) G_DECLARE_FINAL_TYPE(FuDellDockTbt, fu_dell_dock_tbt, FU, DELL_DOCK_TBT, FuDevice) FuDellDockTbt * fu_dell_dock_tbt_new(FuDevice *proxy); fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-plugin.c000066400000000000000000000272511460375044200221760ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-dell-dock-common.h" #include "fu-dell-dock-plugin.h" struct _FuDellDockPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuDellDockPlugin, fu_dell_dock_plugin, FU_TYPE_PLUGIN) static gboolean fu_dell_dock_plugin_create_node(FuPlugin *plugin, FuDevice *device, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; fu_plugin_device_add(plugin, device); return TRUE; } static gboolean fu_dell_dock_plugin_probe(FuPlugin *plugin, FuDevice *proxy, GError **error) { const gchar *instance_id_mst; const gchar *instance_id_status; g_autofree const gchar *instance_guid_mst = NULL; g_autofree const gchar *instance_guid_status = NULL; g_autoptr(FuDellDockEc) ec_device = NULL; g_autoptr(FuDellDockMst) mst_device = NULL; g_autoptr(FuDellDockStatus) status_device = NULL; FuContext *ctx = fu_plugin_get_context(plugin); /* create ec endpoint */ ec_device = fu_dell_dock_ec_new(proxy); if (!fu_dell_dock_plugin_create_node(plugin, FU_DEVICE(ec_device), error)) return FALSE; /* create mst endpoint */ mst_device = fu_dell_dock_mst_new(ctx); if (fu_dell_dock_get_dock_type(FU_DEVICE(ec_device)) == DOCK_BASE_TYPE_ATOMIC) instance_id_mst = DELL_DOCK_VMM6210_INSTANCE_ID; else instance_id_mst = DELL_DOCK_VM5331_INSTANCE_ID; fu_device_add_instance_id(FU_DEVICE(mst_device), instance_id_mst); instance_guid_mst = fwupd_guid_hash_string(instance_id_mst); fu_device_add_guid(FU_DEVICE(mst_device), instance_guid_mst); if (!fu_device_probe(FU_DEVICE(mst_device), error)) return FALSE; fu_device_add_child(FU_DEVICE(ec_device), FU_DEVICE(mst_device)); if (!fu_dell_dock_plugin_create_node(plugin, FU_DEVICE(mst_device), error)) return FALSE; /* create package version endpoint */ status_device = fu_dell_dock_status_new(ctx); if (fu_dell_dock_get_dock_type(FU_DEVICE(ec_device)) == DOCK_BASE_TYPE_ATOMIC) instance_id_status = DELL_DOCK_ATOMIC_STATUS_INSTANCE_ID; else if (fu_dell_dock_module_is_usb4(FU_DEVICE(ec_device))) instance_id_status = DELL_DOCK_DOCK2_INSTANCE_ID; else instance_id_status = DELL_DOCK_DOCK1_INSTANCE_ID; instance_guid_status = fwupd_guid_hash_string(instance_id_status); fu_device_add_guid(FU_DEVICE(status_device), fwupd_guid_hash_string(instance_guid_status)); fu_device_add_child(FU_DEVICE(ec_device), FU_DEVICE(status_device)); fu_device_add_instance_id(FU_DEVICE(status_device), instance_id_status); if (!fu_dell_dock_plugin_create_node(plugin, FU_DEVICE(status_device), error)) return FALSE; /* create TBT endpoint if Thunderbolt SKU and Thunderbolt link inactive */ if (fu_dell_dock_ec_needs_tbt(FU_DEVICE(ec_device))) { g_autoptr(FuDellDockTbt) tbt_device = fu_dell_dock_tbt_new(proxy); g_autofree const gchar *instance_guid_tbt = fwupd_guid_hash_string(DELL_DOCK_TBT_INSTANCE_ID); fu_device_add_guid(FU_DEVICE(tbt_device), instance_guid_tbt); fu_device_add_child(FU_DEVICE(ec_device), FU_DEVICE(tbt_device)); if (!fu_dell_dock_plugin_create_node(plugin, FU_DEVICE(tbt_device), error)) return FALSE; } return TRUE; } /* prefer to use EC if in the transaction and parent if it is not */ static FuDevice * fu_dell_dock_plugin_get_ec(GPtrArray *devices) { FuDevice *ec_parent = NULL; for (gint i = devices->len - 1; i >= 0; i--) { FuDevice *dev = g_ptr_array_index(devices, i); FuDevice *parent; if (FU_IS_DELL_DOCK_EC(dev)) return dev; parent = fu_device_get_parent(dev); if (parent != NULL && FU_IS_DELL_DOCK_EC(parent)) ec_parent = parent; } return ec_parent; } static gboolean fu_dell_dock_plugin_backend_device_added(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuDellDockHub) hub = NULL; const gchar *hub_cache_key = "hub-usb3-gen1"; GPtrArray *devices; FuDevice *ec_device; FuDevice *hub_dev; guint8 dock_type; /* not interesting */ if (!FU_IS_USB_DEVICE(device)) return TRUE; hub = fu_dell_dock_hub_new(FU_USB_DEVICE(device)); locker = fu_device_locker_new(FU_DEVICE(hub), error); if (locker == NULL) return FALSE; /* probe extend devices under Usb3.1 Gen 2 Hub */ if (fu_device_has_private_flag(FU_DEVICE(hub), FU_DELL_DOCK_HUB_FLAG_HAS_BRIDGE)) { if (!fu_dell_dock_plugin_probe(plugin, FU_DEVICE(hub), error)) return FALSE; } /* process hub devices if ec device is added */ devices = fu_plugin_get_devices(plugin); ec_device = fu_dell_dock_plugin_get_ec(devices); if (ec_device == NULL) { fu_plugin_cache_add(plugin, hub_cache_key, FU_DEVICE(hub)); return TRUE; } /* determine dock type by ec */ dock_type = fu_dell_dock_get_dock_type(ec_device); if (dock_type == DOCK_BASE_TYPE_UNKNOWN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "can't read base dock type from EC"); return FALSE; } fu_dell_dock_hub_add_instance(FU_DEVICE(hub), dock_type); fu_plugin_device_add(plugin, FU_DEVICE(hub)); /* add hub instance id for the cached device */ hub_dev = fu_plugin_cache_lookup(plugin, hub_cache_key); if (hub_dev != NULL) { fu_dell_dock_hub_add_instance(FU_DEVICE(hub_dev), dock_type); fu_plugin_device_add(plugin, FU_DEVICE(hub_dev)); fu_plugin_cache_remove(plugin, hub_cache_key); } return TRUE; } static void fu_dell_dock_plugin_separate_activation(FuPlugin *plugin) { FuDevice *device_ec = fu_plugin_cache_lookup(plugin, "ec"); FuDevice *device_usb4 = fu_plugin_cache_lookup(plugin, "usb4"); /* both usb4 and ec device are found */ if (device_usb4 && device_ec) { if (fu_device_has_flag(device_usb4, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION) && fu_device_has_flag(device_ec, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { fu_device_remove_flag(FU_DEVICE(device_ec), FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); g_info("activate for %s is inhibited by %s", fu_device_get_name(device_ec), fu_device_get_name(device_usb4)); } } } static void fu_dell_dock_plugin_device_registered(FuPlugin *plugin, FuDevice *device) { /* dell dock delays the activation so skips device restart */ if (fu_device_has_guid(device, DELL_DOCK_TBT_INSTANCE_ID)) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SKIPS_RESTART); fu_plugin_cache_add(plugin, "tbt", device); } if (fu_device_has_guid(device, DELL_DOCK_USB4_INSTANCE_ID)) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SKIPS_RESTART); fu_plugin_cache_add(plugin, "usb4", device); } if (FU_IS_DELL_DOCK_EC(device)) fu_plugin_cache_add(plugin, "ec", device); /* usb4 device from thunderbolt plugin */ if (g_strcmp0(fu_device_get_plugin(device), "thunderbolt") == 0 && fu_device_has_guid(device, DELL_DOCK_USB4_INSTANCE_ID)) { g_autofree gchar *msg = NULL; msg = g_strdup_printf("firmware update inhibited by [%s] plugin", fu_plugin_get_name(plugin)); fu_device_inhibit(device, "hidden", msg); return; } /* online activation is mutually exclusive between usb4 and ec */ fu_dell_dock_plugin_separate_activation(plugin); } static gboolean fu_dell_dock_plugin_backend_device_removed(FuPlugin *plugin, FuDevice *device, GError **error) { const gchar *device_key = fu_device_get_id(device); FuDevice *dev; FuDevice *parent; /* only the device with bridge will be in cache */ dev = fu_plugin_cache_lookup(plugin, device_key); if (dev == NULL) return TRUE; fu_plugin_cache_remove(plugin, device_key); /* find the parent and ask daemon to remove whole chain */ parent = fu_device_get_parent(dev); if (parent != NULL && FU_IS_DELL_DOCK_EC(parent)) { g_debug("Removing %s (%s)", fu_device_get_name(parent), fu_device_get_id(parent)); fu_plugin_device_remove(plugin, parent); } return TRUE; } static gboolean fu_dell_dock_plugin_composite_prepare(FuPlugin *plugin, GPtrArray *devices, GError **error) { FuDevice *parent = fu_dell_dock_plugin_get_ec(devices); const gchar *sku; if (parent == NULL) return TRUE; sku = fu_dell_dock_ec_get_module_type(parent); if (sku != NULL) fu_plugin_add_report_metadata(plugin, "DellDockSKU", sku); return TRUE; } static gboolean fu_dell_dock_plugin_composite_cleanup(FuPlugin *plugin, GPtrArray *devices, GError **error) { FuDevice *parent = fu_dell_dock_plugin_get_ec(devices); FuDevice *dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; gboolean needs_activation = FALSE; if (parent == NULL) return TRUE; /* if thunderbolt is in the transaction it needs to be activated separately */ for (guint i = 0; i < devices->len; i++) { dev = g_ptr_array_index(devices, i); if ((g_strcmp0(fu_device_get_plugin(dev), "thunderbolt") == 0 || g_strcmp0(fu_device_get_plugin(dev), "intel_usb4") == 0 || g_strcmp0(fu_device_get_plugin(dev), "dell_dock") == 0) && fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { /* the kernel and/or thunderbolt plugin have been configured to let HW * finish the update */ if (fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE)) { fu_dell_dock_ec_tbt_passive(parent); /* run the update immediately - no kernel support */ } else { needs_activation = TRUE; break; } } } /* separate activation flag between usb4 and ec device */ fu_dell_dock_plugin_separate_activation(plugin); locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; if (!fu_dell_dock_ec_reboot_dock(parent, error)) return FALSE; /* close this first so we don't have an error from the thunderbolt activation */ if (!fu_device_locker_close(locker, error)) return FALSE; if (needs_activation && dev != NULL) { g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); if (!fu_device_activate(dev, progress, error)) return FALSE; } return TRUE; } static void fu_dell_dock_plugin_init(FuDellDockPlugin *self) { } static void fu_dell_dock_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "DellDockBlobBuildOffset"); fu_context_add_quirk_key(ctx, "DellDockBlobMajorOffset"); fu_context_add_quirk_key(ctx, "DellDockBlobMinorOffset"); fu_context_add_quirk_key(ctx, "DellDockBlobVersionOffset"); fu_context_add_quirk_key(ctx, "DellDockBoardMin"); fu_context_add_quirk_key(ctx, "DellDockHubVersionLowest"); fu_context_add_quirk_key(ctx, "DellDockInstallDurationI2C"); fu_context_add_quirk_key(ctx, "DellDockUnlockTarget"); fu_context_add_quirk_key(ctx, "DellDockVersionLowest"); /* allow these to be built by quirks */ fu_plugin_add_device_gtype(plugin, FU_TYPE_DELL_DOCK_STATUS); fu_plugin_add_device_gtype(plugin, FU_TYPE_DELL_DOCK_MST); #ifndef _WIN32 /* currently slower performance, but more reliable in corner cases */ fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_BETTER_THAN, "synaptics_mst"); #endif } static void fu_dell_dock_plugin_class_init(FuDellDockPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_dell_dock_plugin_constructed; plugin_class->device_registered = fu_dell_dock_plugin_device_registered; plugin_class->backend_device_added = fu_dell_dock_plugin_backend_device_added; plugin_class->backend_device_removed = fu_dell_dock_plugin_backend_device_removed; plugin_class->composite_cleanup = fu_dell_dock_plugin_composite_cleanup; plugin_class->composite_prepare = fu_dell_dock_plugin_composite_prepare; } fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-plugin.h000066400000000000000000000003601460375044200221730ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuDellDockPlugin, fu_dell_dock_plugin, FU, DELL_DOCK_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-status.c000066400000000000000000000122761460375044200222240ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include #include "fu-dell-dock-common.h" struct _FuDellDockStatus { FuDevice parent_instance; guint64 blob_version_offset; }; G_DEFINE_TYPE(FuDellDockStatus, fu_dell_dock_status, FU_TYPE_DEVICE) static gchar * fu_dell_dock_status_ver_string(guint32 status_version) { /* guint32 BCD */ return g_strdup_printf("%02x.%02x.%02x.%02x", status_version & 0xff, (status_version >> 8) & 0xff, (status_version >> 16) & 0xff, (status_version >> 24) & 0xff); } static gboolean fu_dell_dock_status_setup(FuDevice *device, GError **error) { FuDevice *parent; guint32 status_version; g_autofree gchar *dynamic_version = NULL; parent = fu_device_get_parent(device); status_version = fu_dell_dock_ec_get_status_version(parent); dynamic_version = fu_dell_dock_status_ver_string(status_version); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version(device, dynamic_version); fu_device_set_logical_id(FU_DEVICE(device), "status"); return TRUE; } static gboolean fu_dell_dock_status_write(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDellDockStatus *self = FU_DELL_DOCK_STATUS(device); gsize length = 0; guint32 status_version = 0; const guint8 *data; g_autofree gchar *dynamic_version = NULL; g_autoptr(GBytes) fw = NULL; g_return_val_if_fail(device != NULL, FALSE); g_return_val_if_fail(FU_IS_FIRMWARE(firmware), FALSE); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; data = g_bytes_get_data(fw, &length); if (!fu_memcpy_safe((guint8 *)&status_version, sizeof(status_version), 0x0, /* dst */ data, length, self->blob_version_offset, /* src */ sizeof(status_version), error)) return FALSE; dynamic_version = fu_dell_dock_status_ver_string(status_version); g_info("writing status firmware version %s", dynamic_version); if (!fu_dell_dock_ec_commit_package(fu_device_get_proxy(device), fw, error)) return FALSE; /* dock will reboot to re-read; this is to appease the daemon */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version(device, dynamic_version); return TRUE; } static gboolean fu_dell_dock_status_open(FuDevice *device, GError **error) { if (fu_device_get_proxy(device) == NULL) fu_device_set_proxy(device, fu_device_get_parent(device)); return fu_device_open(fu_device_get_proxy(device), error); } static gboolean fu_dell_dock_status_close(FuDevice *device, GError **error) { return fu_device_close(fu_device_get_proxy(device), error); } static gboolean fu_dell_dock_status_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuDellDockStatus *self = FU_DELL_DOCK_STATUS(device); if (g_strcmp0(key, "DellDockBlobVersionOffset") == 0) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->blob_version_offset = tmp; return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_dell_dock_status_finalize(GObject *object) { G_OBJECT_CLASS(fu_dell_dock_status_parent_class)->finalize(object); } static void fu_dell_dock_status_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 13, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 72, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 9, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 7, "reload"); } static void fu_dell_dock_status_init(FuDellDockStatus *self) { fu_device_add_protocol(FU_DEVICE(self), "com.dell.dock"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); } static void fu_dell_dock_status_class_init(FuDellDockStatusClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_dell_dock_status_finalize; klass_device->write_firmware = fu_dell_dock_status_write; klass_device->setup = fu_dell_dock_status_setup; klass_device->open = fu_dell_dock_status_open; klass_device->close = fu_dell_dock_status_close; klass_device->set_quirk_kv = fu_dell_dock_status_set_quirk_kv; klass_device->set_progress = fu_dell_dock_status_set_progress; } FuDellDockStatus * fu_dell_dock_status_new(FuContext *ctx) { FuDellDockStatus *self = NULL; self = g_object_new(FU_TYPE_DELL_DOCK_STATUS, "context", ctx, NULL); return self; } fwupd-1.9.16/plugins/dell-dock/fu-dell-dock-status.h000066400000000000000000000013641460375044200222250ustar00rootroot00000000000000/* * Copyright (C) 2018 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_DELL_DOCK_STATUS (fu_dell_dock_status_get_type()) G_DECLARE_FINAL_TYPE(FuDellDockStatus, fu_dell_dock_status, FU, DELL_DOCK_STATUS, FuDevice) FuDellDockStatus * fu_dell_dock_status_new(FuContext *ctx); fwupd-1.9.16/plugins/dell-dock/meson.build000066400000000000000000000011421460375044200204210ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginDellDock"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('dell-dock.quirk') plugin_builtins += static_library('fu_plugin_dell_dock', sources: [ 'fu-dell-dock-plugin.c', 'fu-dell-dock-common.c', 'fu-dell-dock-hid.c', 'fu-dell-dock-status.c', 'fu-dell-dock-i2c-ec.c', 'fu-dell-dock-hub.c', 'fu-dell-dock-i2c-tbt.c', 'fu-dell-dock-i2c-mst.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: [ plugin_deps, gudev, ], ) endif fwupd-1.9.16/plugins/dell/000077500000000000000000000000001460375044200153435ustar00rootroot00000000000000fwupd-1.9.16/plugins/dell/README.md000066400000000000000000000041761460375044200166320ustar00rootroot00000000000000--- title: Plugin: Dell --- ## Introduction This allows installing Dell capsules that are not part of the ESRT table. ## GUID Generation These devices uses custom GUIDs for Dell-specific hardware. * Thunderbolt devices: `TBT-0x00d4u$(system-id)` * TPM devices `$(system-id)-$(mode)`, where `mode` is either `2.0` or `1.2` In both cases the `system-id` is derived from the SMBIOS Product SKU property. TPM GUIDs are also built using the TSS properties `TPM2_PT_FAMILY_INDICATOR`, `TPM2_PT_MANUFACTURER`, and `TPM2_PT_VENDOR_STRING_*` These are built hierarchically with more parts for each GUID: * `DELL-TPM-$FAMILY-$MANUFACTURER-$VENDOR_STRING_1` * `DELL-TPM-$FAMILY-$MANUFACTURER-$VENDOR_STRING_1$VENDOR_STRING_2` * `DELL-TPM-$FAMILY-$MANUFACTURER-$VENDOR_STRING_1$VENDOR_STRING_2$VENDOR_STRING_3` * `DELL-TPM-$FAMILY-$MANUFACTURER-$VENDOR_STRING_1$VENDOR_STRING_2$VENDOR_STRING_3$VENDOR_STRING_4` If there are non-ASCII values in any vendor string or any vendor is missing that octet will be skipped. Example resultant GUIDs from a real system containing a TPM from Nuvoton: ```text Guid: 7d65b10b-bb24-552d-ade5-590b3b278188 <- DELL-TPM-2.0-NTC-NPCT Guid: 6f5ddd3a-8339-5b2a-b9a6-cf3b92f6c86d <- DELL-TPM-2.0-NTC-NPCT75x Guid: fe462d4a-e48f-5069-9172-47330fc5e838 <- DELL-TPM-2.0-NTC-NPCT75xrls ``` ## Devices powered by the Dell Plugin The Dell plugin creates device nodes for PC's with upgradable TPMs. These device nodes can be flashed using UEFI capsule but don't use the ESRT table to communicate device status or version information. This is intentional behavior because more complicated decisions need to be made on the OS side to determine if the devices should be offered to flash. ## External Interface Access This plugin requires read/write access to `/dev/wmi/dell-smbios` and `/sys/bus/platform/devices/dcdbas`. ## Version Considerations This plugin has been available since fwupd version `0.8.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Mario Limonciello: @superm1 fwupd-1.9.16/plugins/dell/dell.quirk000066400000000000000000000013571460375044200173460ustar00rootroot00000000000000# Dell TB16/TB18 cable [TBT-00d4b051] Plugin = thunderbolt ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 # Dell TB16/TB18 dock [TBT-00d4b054] Plugin = thunderbolt ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 # Dell WD15 dock [MST-wd15-vmm3332-274] Plugin = synaptics_mst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 # Dell TB16 dock [MST-tb16-vmm3320-274] Plugin = synaptics_mst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 [MST-tb16-vmm3330-274] Plugin = synaptics_mst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 #Dell TB18 dock [MST-tb18-vmm3320-274] Plugin = synaptics_mst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 [MST-tb18-vmm3330-274] Plugin = synaptics_mst ParentGuid = e7ca1f36-bf73-4574-afe6-a4ccacabf479 fwupd-1.9.16/plugins/dell/fu-dell-plugin.c000066400000000000000000000164451460375044200203450ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * Copyright (C) 2016 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-dell-plugin.h" #define DACI_FLASH_INTERFACE_CLASS 7 #define DACI_FLASH_INTERFACE_SELECT 3 #define BIOS_SETTING_BIOS_DOWNGRADE "com.dell-wmi-sysman.AllowBiosDowngrade" struct _FuDellPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuDellPlugin, fu_dell_plugin, FU_TYPE_PLUGIN) struct da_structure { guint8 type; guint8 length; guint16 handle; guint16 cmd_address; guint8 cmd_code; guint32 supported_cmds; guint8 *tokens; } __attribute__((packed)); /** * Dell device types to run */ static guint8 enclosure_allowlist[] = {0x03, /* desktop */ 0x04, /* low profile desktop */ 0x06, /* mini tower */ 0x07, /* tower */ 0x08, /* portable */ 0x09, /* laptop */ 0x0A, /* notebook */ 0x0D, /* AIO */ 0x1E, /* tablet */ 0x1F, /* convertible */ 0x21, /* IoT gateway */ 0x22, /* embedded PC */}; static guint16 fu_dell_get_system_id(FuPlugin *plugin) { FuContext *ctx = fu_plugin_get_context(plugin); const gchar *system_id_str = NULL; guint16 system_id = 0; gchar *endptr = NULL; system_id_str = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_PRODUCT_SKU); if (system_id_str != NULL) system_id = g_ascii_strtoull(system_id_str, &endptr, 16); return system_id; } static gboolean fu_dell_supported(FuPlugin *plugin, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); FuSmbiosChassisKind chassis_kind = fu_context_get_chassis_kind(ctx); GBytes *de_blob = NULL; GBytes *da_blob = NULL; g_autoptr(GPtrArray) de_tables = NULL; g_autoptr(GPtrArray) da_tables = NULL; guint8 value = 0; struct da_structure da_values = {0x0}; /* make sure that Dell SMBIOS methods are available */ de_tables = fu_context_get_smbios_data(ctx, 0xDE, error); if (de_tables == NULL) return FALSE; de_blob = g_ptr_array_index(de_tables, 0); if (!fu_memread_uint8_safe(g_bytes_get_data(de_blob, NULL), g_bytes_get_size(de_blob), 0x0, &value, error)) { g_prefix_error(error, "invalid DE data: "); return FALSE; } if (value != 0xDE) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid DE data"); return FALSE; } da_tables = fu_context_get_smbios_data(ctx, 0xDA, error); if (da_tables == NULL) return FALSE; da_blob = g_ptr_array_index(da_tables, 0); if (!fu_memcpy_safe((guint8 *)&da_values, sizeof(da_values), 0x0, /* dst */ g_bytes_get_data(da_blob, NULL), g_bytes_get_size(da_blob), 0x0, /* src */ sizeof(da_values), error)) { g_prefix_error(error, "unable to access flash interface: "); return FALSE; } if (!(da_values.supported_cmds & (1 << DACI_FLASH_INTERFACE_CLASS))) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unable to access flash interface. supported commands: 0x%x", da_values.supported_cmds); return FALSE; } /* only run on intended Dell hw types */ for (guint i = 0; i < G_N_ELEMENTS(enclosure_allowlist); i++) { if (enclosure_allowlist[i] == chassis_kind) return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "chassis invalid"); return FALSE; } static void fu_dell_plugin_device_registered(FuPlugin *plugin, FuDevice *device) { /* thunderbolt plugin */ if (g_strcmp0(fu_device_get_plugin(device), "thunderbolt") == 0 && fu_device_has_flag(device, FWUPD_DEVICE_FLAG_INTERNAL)) { /* fix VID/DID of safe mode devices */ if (fu_device_get_metadata_boolean(device, FU_DEVICE_METADATA_TBT_IS_SAFE_MODE)) { g_autofree gchar *vendor_id = NULL; g_autofree gchar *device_id = NULL; guint16 system_id = 0; vendor_id = g_strdup("TBT:0x00D4"); system_id = fu_dell_get_system_id(plugin); if (system_id == 0) return; /* the kernel returns lowercase in sysfs, need to match it */ device_id = g_strdup_printf("TBT-%04x%04x", 0x00d4u, (unsigned)system_id); fu_device_add_vendor_id(device, vendor_id); fu_device_add_instance_id(device, device_id); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); } } /* tpm plugin */ if (g_strcmp0(fu_device_get_plugin(device), "tpm") == 0) { guint16 system_id = fu_dell_get_system_id(plugin); g_autofree gchar *tpm_guid_raw = NULL; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_set_metadata(device, FU_DEVICE_METADATA_UEFI_DEVICE_KIND, "dell-tpm-firmware"); tpm_guid_raw = g_strdup_printf("%04x-2.0", system_id); fu_device_add_instance_id(device, tpm_guid_raw); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_QUAD); } } static gboolean fu_dell_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { g_autofree gchar *sysfsfwdir = NULL; g_autofree gchar *esrtdir = NULL; if (!fu_dell_supported(plugin, error)) { g_prefix_error(error, "firmware updating not supported: "); return FALSE; } /* If ESRT is not turned on, fwupd will have already created an * unlock device. * * Once unlocked, that will enable flashing capsules here too. */ sysfsfwdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_FW); esrtdir = g_build_filename(sysfsfwdir, "efi", "esrt", NULL); if (!g_file_test(esrtdir, G_FILE_TEST_EXISTS)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "capsule support disabled in BIOS"); return FALSE; } return TRUE; } static void fu_dell_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { FwupdBiosSetting *bios_attr; FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; bios_attr = fu_context_get_bios_setting(ctx, BIOS_SETTING_BIOS_DOWNGRADE); if (bios_attr == NULL) { g_debug("failed to find %s in cache", BIOS_SETTING_BIOS_DOWNGRADE); return; } attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_BIOS_ROLLBACK_PROTECTION); fu_security_attr_add_bios_target_value(attr, BIOS_SETTING_BIOS_DOWNGRADE, "Disabled"); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); if (g_strcmp0(fwupd_bios_setting_get_current_value(bios_attr), "Enabled") == 0) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_dell_plugin_init(FuDellPlugin *self) { } static void fu_dell_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); /* make sure that UEFI plugin is ready to receive devices */ fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_RUN_AFTER, "uefi_capsule"); } static void fu_dell_plugin_class_init(FuDellPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_dell_plugin_constructed; plugin_class->startup = fu_dell_plugin_startup; plugin_class->device_registered = fu_dell_plugin_device_registered; plugin_class->add_security_attrs = fu_dell_plugin_add_security_attrs; } fwupd-1.9.16/plugins/dell/fu-dell-plugin.h000066400000000000000000000003421460375044200203370ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuDellPlugin, fu_dell_plugin, FU, DELL_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/dell/meson.build000066400000000000000000000010061460375044200175020ustar00rootroot00000000000000if get_option('plugin_uefi_capsule').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginDell"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('dell.quirk') plugin_builtin_dell = static_library('fu_plugin_dell', sources: [ 'fu-dell-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: [ cargs, ], dependencies: [ plugin_deps, ], ) plugin_builtins += plugin_builtin_dell endif fwupd-1.9.16/plugins/dell/tests000077700000000000000000000000001460375044200223642../uefi-capsule/tests/ustar00rootroot00000000000000fwupd-1.9.16/plugins/dfu-csr/000077500000000000000000000000001460375044200157665ustar00rootroot00000000000000fwupd-1.9.16/plugins/dfu-csr/README.md000066400000000000000000000034011460375044200172430ustar00rootroot00000000000000--- title: Plugin: DFU CSR — Cambridge Silicon Radio --- ## Introduction CSR is often called “driverless DFU” and is used only by BlueCore chips from Cambridge Silicon Radio (now owned by Qualcomm). The driverless just means that it's DFU like, and is routed over HID. CSR is a ODM that makes most of the Bluetooth audio chips in vendor hardware. The hardware vendor can enable or disable features on the CSR microcontroller depending on licensing options (for instance echo cancellation), and there’s even a little virtual machine to do simple vendor-specific things. All the CSR chips are updatable in-field, and most vendors issue updates to fix sound quality issues or to add support for new protocols or devices. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in DFU file format. This plugin supports the following protocol ID: * `com.qualcomm.dfu` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_0A12&PID_1337&REV_2520` * `USB\VID_0A12&PID_1337` ## Update Behavior A DFU device usually presents in runtime mode (or with no interfaces defined), but if the user puts the device into bootloader mode using a physical button it then enumerates with a HID descriptor. On attach the device returns to runtime mode which *may* mean the device "goes away". For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x0A12` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.5.7`. fwupd-1.9.16/plugins/dfu-csr/data/000077500000000000000000000000001460375044200166775ustar00rootroot00000000000000fwupd-1.9.16/plugins/dfu-csr/data/lsusb.txt000066400000000000000000000036411460375044200205740ustar00rootroot00000000000000Bus 001 Device 040: ID 0a12:1337 Cambridge Silicon Radio, Ltd Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x0a12 Cambridge Silicon Radio, Ltd idProduct 0x1337 bcdDevice 25.20 iManufacturer 0 iProduct 2 AIAIAI H05 in iSerial 3 ABCDEF0123456789 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 34 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.00 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 40 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/dfu-csr/data/upgrade.tdc.gz000066400000000000000000000163611460375044200214500ustar00rootroot00000000000000*Zupgrade.tdcZiXҮ=#8h<" .=\#q`.0Ѡ1ո-JF%^DKP$;Ow?3y[VԡOzd! _7,0 WAG><H>c&*S|"x ?RfǼn9 5ǡq`I` I4@5,& ~?pMJD LDD*o!F11`n‚@!eT$.PE|&/؅HvnQtP\x4C.)Ƀ@*\<쐖vHBĔO+**xqPEe$@.$ Bm?cHqJJCF|=( -%7tKE'Xӣ%%|IQ5(R'#K||X 'p7JJ57Md> zaCk-,\8GҎTD]bh⧑' qބRWXk 1:oQ'uv#2wKY{k 6JƎeV(2x? +!V6A5OϼԒq) N ^_ƷAR?OqU1_GW4KoJ5MaT#KRrC/H ?GNo7n}cjUH:8 wG"/ON!l^zR^/S倯}g2^ċ67S:9Ay^/l\qD=.KsNܐ@{Cl9׽{Cߏ18Q(pGl9{x-nH .y#GG 'Lq~Lk iO pk$ )t色rs@GΓjetq+V Fњ]Tkurn#Z9ߙVB[ 1 -)\xJ) ㍪I@BM7YψF\z036Eˑ[lAG㗔D[Aevf9DUb{㍋'ôwo'(ZUfFzt !_U7i(uƋRыlБ4tp'ء1Ia$VTB@JvjMIF31.#o qSRB^o@[9e.R=ϖYDTeG ,J}ĺ64&S 1n$)4艳h@*3^TB:3qgw~ %Q~F]oZɿP5 jn,G?3|A[{M"% )”Qv)n] lif0ss}7 +[ : w'-pBfҿc=0BˊmpvFg4J/2$*וJ_Ԯ 1QSedUuLqnxI0|w3=zG9+2Q,( M5,nY`AjZxGrr4}Ie9IxG/g}<%r?u5ӫfPZRÓՉVra2]3{tY\w-{yhmm..2"dQ8Rtw$6u4nQ7/`l޳dxMFSY *%Ӳ(8ʑ'VmE1o8Dtܚi_UAa`S.CIhKӵLŤhb1/. P mZ_I`#|i f+dPDF\2*}aʃKp a a.n#L:( y 7Xp`V8>ާ).BQwnW9Rwf8p[o*84Z+{\y)j"Qс-q=R.Qh@%6Gl |KUX5Jef+& u>b Gʁ0nqg0lDX1“Tr&B36`)whI+\k"b[|\6UDO 8A2v쾚rP㬏^3N\ƅB(, d k| xf;7 6׫a`9WLdp r.P4%p5v؉-k{l3I8I\>qU*px{Rrhl"`#Z%f9$ToqQIF#l7 :K/^QiqbLSwr焕2dp NQG7t()C*2ob3ܗ5-bPH}6sN9^uG<x ¹`фpXl,8D:DTq+\GMGǪ>6oN[)3mp. pq78; qH EEUNo{+>$R$Qa2?L+=D4q}3 *nw'PPS5Crܼpkyؤgl~y܉ 5.,ݽ u˿ٷnӖ _rzO@&۬кa3pMJUmy'q":I茺jeQ^=9hа7U+'kZXg[;X \M߿q{JX9 gir+7LnAt}C|hèRV 9ϐC@N97H+=tՊS^E%tTHqQcy52fЌ2J掃úqVSI[&dnvIV|Cd$?W 4L[{606;Lq5`d1aJu|MXRx|] %Gn7fsn$OQUǮn~*+Y6Ƞ?f]`&^U;H#v~~F ܘ~SҖL+#.-ϴ٧"nykVPcp˪E?+đ5GgW2yrtۤ5 "QM*XIx-2-d_FgߒUM"hUNdp"L j}s*o#GO"JA2 QC4}EFdL@lWWmtٮnn c5[IL_H@bTq DrbLj\)6zJ/U6E8>,x5tZj ;J \-T:ST,lu40z*@zJ򂪁3GL^ۦf%qVHTf7RR&kQ;BVƻJ޴w$9膟ALZ ?g[f^?t-ӿ$Mr]3Wrj١OQ ٭+ tm- GѿE'C/vxX9xHHTXgdN^}/޸9A.Na=q'@ }gD8L[}Zc #dD֜cxѽ(}AW=@#W.+/±k}F>M|%{8& СT=x0@+?`luE{.%64XZgU'?C [|nɤr|ն^qqt+\g6Z18 UHJCh`d&1.B>f~>\ZB*Qmf0 ' l~Cd!D߂!kؒ3D#V=~UFوdp֏PeV/TDKv B-r@ar|c?{rp7'T )#?xPJe629C.9.D𓵪ƺiC85H]'#oyCa4^5TGMƣ up5Cя5*ܵ%YQ4a ץ5fXc)- Pq.cưfXA4 g0ߛXX9J#sdمh!?G t5{Z:FK-xu#KQVr"3VJt W$o~gZofJ.7}|8";)%vWljmVKXAT[ܡ<[5<vbys)ym &UlZW%,Ux_7]OUr1r T}[s~ˇ]bv`P1dtx V4QQ:jbyݻZP]3MV_XGKٟr%V Xz7nLLy^6)lx'ZjgӛUw2jz&t3u~oSLt&"jm63~r#fj'*1Wz R #(S PJr3OU:ne]Ng\cB *P|]$3/i"sEIg>U|دk~#t-3y"ޗ+^S/u7S/MںH]& [k}&*KuL^s&VȠȰ@n^ʃ~X͙V#bMI`80#XVF!e{ߢz2akW9 Yd^1A*QADBN$hvDvŧ :&B ABWmv*ZHvtH:$/*ST*YW1)GwLٝ<J &}GRj3}-TQ, ')Ew]7OAۛ3+Ty}a* _z, oU";Yvw7ResZ)ȋ#k+ꈮp ˎ깑gdp`ݱv2> pVp"DEKHLG}:kH "=PWnbډÆv0&bq_I!]-F*F$ f 20/ntI-fwupd-1.9.16/plugins/dfu-csr/dfu-csr.quirk000066400000000000000000000004461460375044200204120ustar00rootroot00000000000000[USB\VID_0A12&PID_1337] Plugin = dfu_csr Name = H05 Summary = Bluetooth Headphones Icon = audio-headphones Vendor = AIAIAI [USB\VID_0A12&PID_1337&REV_2520] Version = 1.2 [USB\VID_0A12&PID_4004] Plugin = dfu_csr Name = H60 Summary = Bluetooth Headphones Icon = audio-headphones Vendor = AIAIAI fwupd-1.9.16/plugins/dfu-csr/fu-dfu-csr-device.c000066400000000000000000000260001460375044200213400ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-dfu-common.h" #include "fu-dfu-csr-device.h" #include "fu-dfu-csr-firmware.h" #include "fu-dfu-csr-struct.h" #include "fu-dfu-struct.h" /** * FU_DFU_CSR_DEVICE_FLAG_REQUIRE_DELAY: * * Respect the write timeout value when performing actions. This is sometimes * set to a huge amount of time, and so is not used by default. * * Since: 1.0.3 */ #define FU_DFU_CSR_DEVICE_FLAG_REQUIRE_DELAY (1 << 0) struct _FuDfuCsrDevice { FuHidDevice parent_instance; FuDfuState dfu_state; guint32 dnload_timeout; }; G_DEFINE_TYPE(FuDfuCsrDevice, fu_dfu_csr_device, FU_TYPE_HID_DEVICE) #define FU_DFU_CSR_CONTROL_CLEAR_STATUS 0x04 #define FU_DFU_CSR_CONTROL_RESET 0xff /* maximum firmware packet, including the command header */ #define FU_DFU_CSR_PACKET_DATA_SIZE 1023 /* bytes */ #define FU_DFU_CSR_DEVICE_TIMEOUT 5000 /* ms */ static void fu_dfu_csr_device_to_string(FuDevice *device, guint idt, GString *str) { FuDfuCsrDevice *self = FU_DFU_CSR_DEVICE(device); fu_string_append(str, idt, "State", fu_dfu_state_to_string(self->dfu_state)); fu_string_append_ku(str, idt, "DownloadTimeout", self->dnload_timeout); } static gboolean fu_dfu_csr_device_attach(FuDevice *device, FuProgress *progress, GError **error) { guint8 buf[] = {FU_DFU_CSR_REPORT_ID_CONTROL, FU_DFU_CSR_CONTROL_RESET}; if (!fu_hid_device_set_report(FU_HID_DEVICE(device), FU_DFU_CSR_REPORT_ID_CONTROL, buf, sizeof(buf), FU_DFU_CSR_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { g_prefix_error(error, "failed to attach: "); return FALSE; } return TRUE; } static gboolean fu_dfu_csr_device_get_status(FuDfuCsrDevice *self, GError **error) { guint8 buf[64] = {0}; /* hit hardware */ if (!fu_hid_device_get_report(FU_HID_DEVICE(self), FU_DFU_CSR_REPORT_ID_STATUS, buf, sizeof(buf), FU_DFU_CSR_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_ALLOW_TRUNC | FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { g_prefix_error(error, "failed to GetStatus: "); return FALSE; } /* check packet */ if (buf[0] != FU_DFU_CSR_REPORT_ID_STATUS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "GetStatus packet-id was %i expected %i", buf[0], FU_DFU_CSR_REPORT_ID_STATUS); return FALSE; } self->dfu_state = buf[5]; self->dnload_timeout = fu_memread_uint24(&buf[2], G_LITTLE_ENDIAN); g_debug("timeout=%" G_GUINT32_FORMAT, self->dnload_timeout); g_debug("state=%s", fu_dfu_state_to_string(self->dfu_state)); g_debug("status=%s", fu_dfu_status_to_string(buf[6])); return TRUE; } static gboolean fu_dfu_csr_device_clear_status(FuDfuCsrDevice *self, GError **error) { guint8 buf[] = {FU_DFU_CSR_REPORT_ID_CONTROL, FU_DFU_CSR_CONTROL_CLEAR_STATUS}; /* only clear the status if the state is error */ if (!fu_dfu_csr_device_get_status(self, error)) return FALSE; if (self->dfu_state != FU_DFU_STATE_DFU_ERROR) return TRUE; /* hit hardware */ if (!fu_hid_device_set_report(FU_HID_DEVICE(self), FU_DFU_CSR_REPORT_ID_CONTROL, buf, sizeof(buf), FU_DFU_CSR_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { g_prefix_error(error, "failed to ClearStatus: "); return FALSE; } /* check the hardware again */ return fu_dfu_csr_device_get_status(self, error); } static GBytes * fu_dfu_csr_device_upload_chunk(FuDfuCsrDevice *self, GError **error) { guint16 data_sz; guint8 buf[64] = {0}; /* hit hardware */ if (!fu_hid_device_get_report(FU_HID_DEVICE(self), FU_DFU_CSR_REPORT_ID_COMMAND, buf, sizeof(buf), FU_DFU_CSR_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_ALLOW_TRUNC | FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { g_prefix_error(error, "failed to ReadFirmware: "); return NULL; } /* check command byte */ if (buf[0] != FU_DFU_CSR_REPORT_ID_COMMAND) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "wrong report ID %u", buf[0]); return NULL; } /* check the length */ data_sz = fu_memread_uint16(&buf[1], G_LITTLE_ENDIAN); if (data_sz + FU_STRUCT_DFU_CSR_COMMAND_HEADER_SIZE != (guint16)sizeof(buf)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "wrong data length %" G_GUINT16_FORMAT, data_sz); return NULL; } /* return as bytes */ return g_bytes_new(buf + FU_STRUCT_DFU_CSR_COMMAND_HEADER_SIZE, sizeof(buf) - FU_STRUCT_DFU_CSR_COMMAND_HEADER_SIZE); } static GBytes * fu_dfu_csr_device_upload(FuDevice *device, FuProgress *progress, GError **error) { FuDfuCsrDevice *self = FU_DFU_CSR_DEVICE(device); g_autoptr(GPtrArray) chunks = NULL; guint32 total_sz = 0; gsize done_sz = 0; /* notify UI */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); chunks = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); for (guint32 i = 0; i < 0x3ffffff; i++) { g_autoptr(GBytes) chunk = NULL; gsize chunk_sz; /* hit hardware */ chunk = fu_dfu_csr_device_upload_chunk(self, error); if (chunk == NULL) return NULL; chunk_sz = g_bytes_get_size(chunk); /* get the total size using the CSR header */ if (i == 0) { g_autoptr(FuFirmware) firmware = fu_dfu_csr_firmware_new(); if (!fu_firmware_parse(firmware, chunk, FWUPD_INSTALL_FLAG_NONE, error)) return NULL; total_sz = fu_dfu_csr_firmware_get_total_sz(FU_DFU_CSR_FIRMWARE(firmware)); } /* add to chunk array */ done_sz += chunk_sz; g_ptr_array_add(chunks, g_steal_pointer(&chunk)); fu_progress_set_percentage_full(progress, done_sz, (gsize)total_sz); /* we're done */ if (chunk_sz < 64 - FU_STRUCT_DFU_CSR_COMMAND_HEADER_SIZE) break; } /* notify UI */ return fu_dfu_utils_bytes_join_array(chunks); } static gboolean fu_dfu_csr_device_download_chunk(FuDfuCsrDevice *self, guint16 idx, GBytes *chunk, GError **error) { g_autoptr(GByteArray) buf = fu_struct_dfu_csr_command_header_new(); /* create packet */ fu_struct_dfu_csr_command_header_set_report_id(buf, FU_DFU_CSR_REPORT_ID_COMMAND); fu_struct_dfu_csr_command_header_set_command(buf, FU_DFU_CSR_COMMAND_UPGRADE); fu_struct_dfu_csr_command_header_set_idx(buf, idx); fu_struct_dfu_csr_command_header_set_chunk_sz(buf, g_bytes_get_size(chunk)); fu_byte_array_append_bytes(buf, chunk); fu_byte_array_set_size(buf, FU_DFU_CSR_PACKET_DATA_SIZE, 0x0); /* hit hardware */ g_debug("writing %" G_GSIZE_FORMAT " bytes of data", g_bytes_get_size(chunk)); if (!fu_hid_device_set_report(FU_HID_DEVICE(self), FU_DFU_CSR_REPORT_ID_COMMAND, buf->data, buf->len, FU_DFU_CSR_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { g_prefix_error(error, "failed to Upgrade: "); return FALSE; } /* wait for hardware */ if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_CSR_DEVICE_FLAG_REQUIRE_DELAY)) { g_debug("sleeping for %ums", self->dnload_timeout); fu_device_sleep(FU_DEVICE(self), self->dnload_timeout); } /* get status */ if (!fu_dfu_csr_device_get_status(self, error)) return FALSE; /* is still busy */ if (self->dfu_state == FU_DFU_STATE_DFU_DNBUSY) { g_debug("busy, so sleeping a bit longer"); fu_device_sleep(FU_DEVICE(self), 1000); if (!fu_dfu_csr_device_get_status(self, error)) return FALSE; } /* not correct */ if (self->dfu_state != FU_DFU_STATE_DFU_DNLOAD_IDLE && self->dfu_state != FU_DFU_STATE_DFU_IDLE) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "device did not return to IDLE"); return FALSE; } /* success */ return TRUE; } static gboolean fu_dfu_csr_device_download(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDfuCsrDevice *self = FU_DFU_CSR_DEVICE(device); guint idx; g_autoptr(GBytes) blob_empty = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* get default image */ blob = fu_firmware_get_bytes(firmware, error); if (blob == NULL) return FALSE; /* notify UI */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); /* create chunks */ chunks = fu_chunk_array_new_from_bytes(blob, 0x0, FU_DFU_CSR_PACKET_DATA_SIZE - FU_STRUCT_DFU_CSR_COMMAND_HEADER_SIZE); if (fu_chunk_array_length(chunks) > G_MAXUINT16) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "too many chunks for hardware: 0x%x", fu_chunk_array_length(chunks)); return FALSE; } /* send to hardware */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (idx = 0; idx < fu_chunk_array_length(chunks); idx++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, idx); g_autoptr(GBytes) blob_tmp = fu_chunk_get_bytes(chk); /* send packet */ if (!fu_dfu_csr_device_download_chunk(self, idx, blob_tmp, error)) return FALSE; /* update progress */ fu_progress_step_done(progress); } /* all done */ blob_empty = g_bytes_new(NULL, 0); return fu_dfu_csr_device_download_chunk(self, idx, blob_empty, error); } static gboolean fu_dfu_csr_device_setup(FuDevice *device, GError **error) { FuDfuCsrDevice *self = FU_DFU_CSR_DEVICE(device); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_dfu_csr_device_parent_class)->setup(device, error)) return FALSE; if (!fu_dfu_csr_device_clear_status(self, error)) return FALSE; /* success */ return TRUE; } static void fu_dfu_csr_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_dfu_csr_device_init(FuDfuCsrDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.qualcomm.dfu"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_DFU_FIRMWARE); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_CSR_DEVICE_FLAG_REQUIRE_DELAY, "require-delay"); } static void fu_dfu_csr_device_class_init(FuDfuCsrDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_dfu_csr_device_to_string; klass_device->write_firmware = fu_dfu_csr_device_download; klass_device->dump_firmware = fu_dfu_csr_device_upload; klass_device->attach = fu_dfu_csr_device_attach; klass_device->setup = fu_dfu_csr_device_setup; klass_device->set_progress = fu_dfu_csr_device_set_progress; } fwupd-1.9.16/plugins/dfu-csr/fu-dfu-csr-device.h000066400000000000000000000004531460375044200213510ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_DFU_CSR_DEVICE (fu_dfu_csr_device_get_type()) G_DECLARE_FINAL_TYPE(FuDfuCsrDevice, fu_dfu_csr_device, FU, DFU_CSR_DEVICE, FuHidDevice) fwupd-1.9.16/plugins/dfu-csr/fu-dfu-csr-firmware.c000066400000000000000000000037241460375044200217250ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-dfu-csr-firmware.h" #include "fu-dfu-csr-struct.h" struct _FuDfuCsrFirmware { FuFirmware parent_instance; guint32 total_sz; }; G_DEFINE_TYPE(FuDfuCsrFirmware, fu_dfu_csr_firmware, FU_TYPE_FIRMWARE) static void fu_dfu_csr_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuDfuCsrFirmware *self = FU_DFU_CSR_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "total_sz", self->total_sz); } static gboolean fu_dfu_csr_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { return fu_struct_dfu_csr_file_validate_bytes(fw, offset, error); } static gboolean fu_dfu_csr_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuDfuCsrFirmware *self = FU_DFU_CSR_FIRMWARE(firmware); g_autoptr(GByteArray) st_hdr = NULL; /* parse file header */ st_hdr = fu_struct_dfu_csr_file_parse_bytes(fw, offset, error); if (st_hdr == NULL) return FALSE; self->total_sz = fu_struct_dfu_csr_file_get_file_len(st_hdr); fu_firmware_set_bytes(firmware, fw); return TRUE; } guint32 fu_dfu_csr_firmware_get_total_sz(FuDfuCsrFirmware *self) { g_return_val_if_fail(FU_IS_DFU_CSR_FIRMWARE(self), G_MAXUINT16); return self->total_sz; } static void fu_dfu_csr_firmware_init(FuDfuCsrFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE); } static void fu_dfu_csr_firmware_class_init(FuDfuCsrFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_dfu_csr_firmware_check_magic; klass_firmware->parse = fu_dfu_csr_firmware_parse; klass_firmware->export = fu_dfu_csr_firmware_export; } FuFirmware * fu_dfu_csr_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_DFU_CSR_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/dfu-csr/fu-dfu-csr-firmware.h000066400000000000000000000006431460375044200217270ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_DFU_CSR_FIRMWARE (fu_dfu_csr_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuDfuCsrFirmware, fu_dfu_csr_firmware, FU, DFU_CSR_FIRMWARE, FuFirmware) FuFirmware * fu_dfu_csr_firmware_new(void); guint32 fu_dfu_csr_firmware_get_total_sz(FuDfuCsrFirmware *self); fwupd-1.9.16/plugins/dfu-csr/fu-dfu-csr-plugin.c000066400000000000000000000014721460375044200214050ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-dfu-csr-device.h" #include "fu-dfu-csr-firmware.h" #include "fu-dfu-csr-plugin.h" struct _FuDfuCsrPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuDfuCsrPlugin, fu_dfu_csr_plugin, FU_TYPE_PLUGIN) static void fu_dfu_csr_plugin_init(FuDfuCsrPlugin *self) { } static void fu_dfu_csr_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_DFU_CSR_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_DFU_CSR_FIRMWARE); } static void fu_dfu_csr_plugin_class_init(FuDfuCsrPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_dfu_csr_plugin_constructed; } fwupd-1.9.16/plugins/dfu-csr/fu-dfu-csr-plugin.h000066400000000000000000000003521460375044200214060ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuDfuCsrPlugin, fu_dfu_csr_plugin, FU, DFU_CSR_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/dfu-csr/fu-dfu-csr.rs000066400000000000000000000011411460375044200203040ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ValidateBytes, ParseBytes)] struct DfuCsrFile { file_id: [char; 8] == "CSR-dfu2", file_version: u16le == 0x02, file_len: u32le, file_hdr_len: u16le, //file_desc: [char; 64], -- useless } #[repr(u8)] enum DfuCsrReportId { Command = 0x01, Status = 0x02, Control = 0x03, } #[repr(u8)] enum DfuCsrCommand { Upgrade = 0x01, } #[derive(New)] struct DfuCsrCommandHeader { report_id: DfuCsrReportId, command: DfuCsrCommand, idx: u16le, chunk_sz: u16le, } fwupd-1.9.16/plugins/dfu-csr/meson.build000066400000000000000000000011341460375044200201270ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginDfuCsr"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('dfu-csr.quirk') plugin_builtins += static_library('fu_plugin_dfu_csr', rustgen.process('fu-dfu-csr.rs'), sources: [ dfu_rs[1], # header 'fu-dfu-csr-device.c', 'fu-dfu-csr-firmware.c', 'fu-dfu-csr-plugin.c', ], include_directories: [ root_incdir, fwupd_incdir, fwupdplugin_incdir, plugindfu_incdir, ], c_args: cargs, dependencies: plugin_deps, link_with: [ fwupdplugin, plugin_builtin_dfu, ], ) endif fwupd-1.9.16/plugins/dfu/000077500000000000000000000000001460375044200152015ustar00rootroot00000000000000fwupd-1.9.16/plugins/dfu/README.md000066400000000000000000000076601460375044200164710ustar00rootroot00000000000000--- title: Plugin: DFU --- ## Introduction Device Firmware Update is a standard that allows USB devices to be easily and safely updated by any operating system. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in DFU or DfuSe file format. This plugin supports the following protocol IDs: * `org.usb.dfu` * `com.st.dfuse` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_273F&PID_1003&REV_0001` * `USB\VID_273F&PID_1003` ## Update Behavior A DFU device usually presents in runtime mode (with optional DFU runtime), but on detach re-enumerates with an additional required DFU descriptor. On attach the device again re-enumerates back to the runtime mode. For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. ## Implementation Notes The runtime mode is just as important as the DFU mode from the point of view of fwupd and should be included if firmware updates are to "just work". Without a DFU runtime interface we can match the device with `Flags = no-dfu-runtime` but will need a suitably new fwupd version before the device is recognized. The USB interface revision (`REV`) is used as the BCD version number, as DFU has no way of representing a firmware version number. A new firmware version should always increment the USB REV of the *runtime* interface as fwupd will **not** switch the device into *DFU mode* during enumeration to read the version number. The version number of the DFU mode should represent the *bootloader version* and this should not change as the firmware is updated. The runtime USB interface should have a unique vendor ID and product ID for the specific firmware stream. A different version of software should have a unique VID/PID USB descriptor pair. The microcontroller example VID/PID should **never** be used in the runtime mode otherwise fwupd would not know what firmware to match. Ideally, the bootloader should also have a unique USB vendor ID and product ID. This allows fwupd to more easily recognize the runtime interface *going away* and the DFU interface *coming back*. If the VID/PID is the same in runtime and DFU modes then the quirk `Flags = no-pid-change` is required. If the bootloader VID/PID is not customized (as might be the default for the supplied MCU) then fwupd can match the runtime VID/PID to the bootloader VID/PID. Where this fails is when the device is *stuck* in the DFU mode, perhaps because the user removed the USB cable before the device had completed updating. With a unique VID/PID fwupd can *recover* the device stuck in DFU mode, reflashing the device with the latest matching firmware and then attaching it back into runtime mode. Using a *generic* VID/PID for the bootloader means fwupd does not know how to recover the device back into runtime mode as the client does not know what firmware to choose and the user is forced to either RMA the device, or to download the correct file manually from the vendor vebsite and use low-level commands like `sudo fwupdtool install-blob`. ## Vendor ID Security The vendor ID is set from the USB vendor, for example `USB:0x0A12` ## Quirk Use This plugin uses the following plugin-specific quirks: ### DfuFlags Optional quirks for a DFU device which doesn't follow the DFU 1.0 or 1.1 specification. Since: 1.0.1 ### DfuForceVersion Forces a specific DFU version for the hardware device. This is required if the device does not set, or sets incorrectly, items in the DFU functional descriptor. If set to 0000 then the DFU functionality is disabled. Since: 1.0.1 ### DfuForceTimeout Forces a specific device timeout, in ms. Since: 1.4.0 ### DfuForceTransferSize Forces a target transfer size, in bytes. Since: 1.5.6 ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `0.8.0`. fwupd-1.9.16/plugins/dfu/contrib/000077500000000000000000000000001460375044200166415ustar00rootroot00000000000000fwupd-1.9.16/plugins/dfu/contrib/parse-avrdude-conf.py000077500000000000000000000113571460375044200227120ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ """ This parses avrdude.conf and generates quirks for fwupd """ # pylint: disable=wrong-import-position,pointless-string-statement import sys from difflib import SequenceMatcher # finds a part using the ID def _find_part_by_id(parts, part_id): for part in parts: if "id" not in part: continue if part["id"] == part_id: return part return None # finds a memory layout for a part, climbing up the tree to the parent if reqd. def _find_mem_layout(parts, part): if "memory-application" in part: memory_flash = part["memory-application"] if memory_flash: return memory_flash # look at the parent if "parent" in part: parent = _find_part_by_id(parts, part["parent"]) if parent: return _find_mem_layout(parts, parent) print("no parent ", part["parent"], "found for", part["id"]) return None # parses the weird syntax of avrdude.conf and makes lots of nested dictionaries def _parse_parts(fn_source): print("reading", fn_source) part = None memory_id = None parts = [] for line in open(fn_source).readlines(): # try to clean up crazy syntax line = line.replace("\n", "") if line.endswith(";"): line = line[:-1] # ignore blank lines line = line.rstrip() if not line: continue # count how many spaces deep this is lvl = 0 for char in line: if char != " ": break lvl = lvl + 1 # ignore comments line = line.strip() if line[0] == "#": continue # level 0 of hell if lvl == 0: if line.startswith("part"): memory_id = None part = {} parts.append(part) if line.startswith("part parent "): part["parent"] = line[13:].replace('"', "") continue # level 4 of hell if lvl == 4: if line.startswith("memory"): memory_id = "memory-" + line[7:].replace('"', "") part[memory_id] = {} continue split = line.split("=") if len(split) != 2: print("ignoring", line) continue part[split[0].strip()] = split[1].strip().replace('"', "") continue # level 8 of hell if lvl == 8: if memory_id: split = line.split("=") if len(split) != 2: continue memory = part[memory_id] memory[split[0].strip()] = split[1].strip() continue return parts def _get_longest_substring(s1, s2): match = SequenceMatcher(None, s1, s2).find_longest_match(0, len(s1), 0, len(s2)) return s2[match.b : match.b + match.size] # writes important data to the quirks file def _write_quirks(parts, fn_destination): outp = [] results = {} for part in parts: # ignore meta parts with deprecated names if "desc" not in part: continue if "signature" not in part: continue # find the layout mem_part = _find_mem_layout(parts, part) if not mem_part: print("no memory layout for", part["desc"]) continue if "size" not in mem_part: print("no memory size for", part["desc"]) continue if mem_part["size"].startswith("0x"): size = int(mem_part["size"], 16) else: size = int(mem_part["size"], 10) # output the line for the quirk chip_id = "0x" + part["signature"].replace("0x", "").replace(" ", "") mem_layout = "@Flash/0x0/1*%.0iKg" % int(size / 1024) # merge duplicate quirks if chip_id in results: result = results[chip_id] result["desc"] = _get_longest_substring(result["desc"], part["desc"]) else: result = {} result["desc"] = part["desc"] result["size"] = size result["mem_layout"] = mem_layout results[chip_id] = result for chip_id in results: result = results[chip_id] outp.append( "# " + result["desc"] + f"\t[USER]\t\tUSER=0x{result['size']:x}" + "\n" ) outp.append(chip_id + "=" + result["mem_layout"] + "\n\n") # write file print("writing", fn_destination) open(fn_destination, "w").writelines(outp) if __name__ == "__main__": if len(sys.argv) != 3: print(f"USAGE: {sys.argv[0]} avrdude.conf tmp.quirk") sys.exit(1) all_parts = _parse_parts(sys.argv[1]) _write_quirks(all_parts, sys.argv[2]) fwupd-1.9.16/plugins/dfu/dfu-tool.md000066400000000000000000000022171460375044200172560ustar00rootroot00000000000000% dfu-tool(1) {{PACKAGE_VERSION}} | dfu-tool man page NAME ==== **dfu-tool** — write firmware to DFU devices SYNOPSIS ======== | **dfu-tool** [CMD] DESCRIPTION =========== This manual page documents briefly the **dfu-tool** command. **dfu-tool** allows a user to write various kinds of firmware onto devices supporting the USB Device Firmware Upgrade protocol. This tool can be used to switch the device from the normal runtime mode to DFU mode which allows the user to read and write firmware. Either the whole device can be written in one operation, or individual targets can be specified with the alternative name or number. All synchronous actions can be safely cancelled and on failure will return errors with both a type and a full textual description. libdfu supports DFU 1.0, DFU 1.1 and the ST DfuSe vendor extension, and handles many device quirks necessary for the real-world implementations of DFU. OPTIONS ======= The dfu-tool command takes various options depending on the action. Run **dfu-tool --help** for the full list. BUGS ==== See GitHub Issues: SEE ALSO ======== fwupdtool(1), fwupdmgr(1) fwupd-1.9.16/plugins/dfu/dfu.quirk000066400000000000000000000302661460375044200170430ustar00rootroot00000000000000# All DFU devices [USB\CLASS_FE&SUBCLASS_01] Plugin = dfu # GD32VF103 Rev1 [USB\VID_28E9&PID_0189] Flags = gd32,force-dfu-mode,will-disappear Name = GD32VF103 Vendor = GDMicroelectronics # Realtek USB camera [USB\VID_0BDA&PID_5850] Flags = enforce-requires CounterpartGuid = USB\VID_0BDA&PID_5800 [USB\VID_0BDA&PID_5855] Flags = enforce-requires CounterpartGuid = USB\VID_0BDA&PID_5800 [USB\VID_0BDA&PID_58FE] Flags = enforce-requires CounterpartGuid = USB\VID_0BDA&PID_5800 [USB\VID_0BDA&PID_5800] Flags = detach-for-attach,enforce-requires # Openmoko Freerunner / GTA02 [USB\VID_1D50&PID_5119] Plugin = dfu Flags = ignore-polltimeout,no-pid-change,no-dfu-runtime,needs-bootloader,no-get-status-upload # OpenPCD Reader [USB\VID_16C0&PID_076B] Plugin = dfu Flags = ignore-polltimeout # SIMtrace [USB\VID_16C0&PID_0762] Plugin = dfu Flags = ignore-polltimeout # OpenPICC [USB\VID_16C0&PID_076C] Plugin = dfu Flags = ignore-polltimeout # Siemens AG, PXM 40 & PXM 50 [USB\VID_0908&PID_02C4] Plugin = dfu [USB\VID_0908&PID_02C5] Plugin = dfu [USB\VID_0908&PID_02C4&REV_0000] Flags = ignore-polltimeout [USB\VID_0908&PID_02C5&REV_0000] Flags = ignore-polltimeout # Midiman M-Audio Transit [USB\VID_0763&PID_2806] Plugin = dfu Flags = ignore-polltimeout # LPC DFU bootloader [USB\VID_1FC9&PID_000C] Plugin = dfu Flags = force-dfu-mode # m-stack DFU [USB\VID_273F&PID_1003] Flags = attach-upload-download [USB\VID_273F&PID_100A] Flags = attach-upload-download [USB\VID_273F&PID_1008] Flags = attach-upload-download # HydraBus [USB\VID_1D50&PID_60A7] Plugin = dfu Flags = no-dfu-runtime,needs-bootloader # Hughski AT90USBKEY Mouse+DFU Demo [USB\VID_273F&PID_2000] Flags = unsigned-payload # Jabra 410 [appIDLE & dfuIDLE] [USB\VID_0B0E&PID_0411] Plugin = dfu Flags = no-pid-change,ignore-upload,attach-extra-reset # Jabra 510 [appIDLE & dfuIDLE] [USB\VID_0B0E&PID_0421] Plugin = dfu Flags = no-pid-change,ignore-upload,attach-extra-reset # Jabra 710 [appIDLE & dfuIDLE] [USB\VID_0B0E&PID_0982] Plugin = dfu Flags = no-pid-change,ignore-upload,attach-extra-reset # Jabra 810 [appIDLE & dfuIDLE] [USB\VID_0B0E&PID_0971] Plugin = dfu Flags = no-pid-change,ignore-upload,attach-extra-reset # Atmel AT90USB Bootloader [USB\VID_03EB&PID_2FF7] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode DfuForceVersion = 0xff01 [USB\VID_03EB&PID_2FF9] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode DfuForceVersion = 0xff01 [USB\VID_03EB&PID_2FFA] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode DfuForceVersion = 0xff01 [USB\VID_03EB&PID_2FFB] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode DfuForceVersion = 0xff01 # Atmel ATMEGA Bootloader [USB\VID_03EB&PID_2FEE] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [USB\VID_03EB&PID_2FEF] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [USB\VID_03EB&PID_2FF0] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [USB\VID_03EB&PID_2FF2] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [USB\VID_03EB&PID_2FF3] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode [USB\VID_03EB&PID_2FF4] Plugin = dfu Flags = use-any-interface,legacy-protocol,force-dfu-mode # Atmel XMEGA Bootloader [USB\VID_03EB&PID_2FE2] Plugin = dfu Flags = use-any-interface,force-dfu-mode DfuForceVersion = 0xff01 # Leaflabs Maple3 [USB\VID_1EAF&PID_0003&REV_0200] Plugin = dfu DfuForceVersion = 0x0110 # AT32UC3B1256 [BLDR][USER] USER@0x2000, BLDR+USER=0x40000 [DFU_AVR\CID_0x58200203] DfuAltName = @Flash/0x2000/1*248Kg # AT32UC3A3256 [BLDR][USER] USER@0x2000, BLDR+USER=0x40000 [DFU_AVR\CID_0x58200204] DfuAltName = @Flash/0x2000/1*248Kg # AT90USB1287 [USER][BLDR] BLDR@0x1e000, BLDR+USER=0x20000 [DFU_AVR\CID_0x581e9782] DfuAltName = @Flash/0x0/1*120Kg # AT90USB647 [USER][BLDR] BLDR@0x0e000, BLDR+USER=0x10000 # AT90USB646 [USER][BLDR] BLDR@0x0e000, BLDR+USER=0x10000 [DFU_AVR\CID_0x581e9682] DfuAltName = @Flash/0x0/1*56Kg # ATmega32U4 [USER][BLDR] BLDR@0x07000, BLDR+USER=0x08000 [DFU_AVR\CID_0x581e9587] DfuAltName = @Flash/0x0/1*28Kg # ATmega16U4 [USER][BLDR] BLDR@0x03000, BLDR+USER=0x04000 [DFU_AVR\CID_0x581e9488] DfuAltName = @Flash/0x0/1*12Kg # ATmega32U2 [USER][BLDR] BLDR@0x07000, BLDR+USER=0x08000 [DFU_AVR\CID_0x581e958a] DfuAltName = @Flash/0x0/1*28Kg # ATmega16U2 [USER][BLDR] BLDR@0x03000, BLDR+USER=0x04000 [DFU_AVR\CID_0x581e9489] DfuAltName = @Flash/0x0/1*12Kg # AT90USB162 [USER][BLDR] BLDR@0x03000, BLDR+USER=0x04000 [DFU_AVR\CID_0x581e9482] DfuAltName = @Flash/0x0/1*12Kg # ATmega8U2 [USER][BLDR] BLDR@0x01000, BLDR+USER=0x02000 [DFU_AVR\CID_0x581e9389] DfuAltName = @Flash/0x0/1*4Kg # AT90USB82 [USER][BLDR] BLDR@0x01000, BLDR+USER=0x02000 [DFU_AVR\CID_0x581e9382] DfuAltName = @Flash/0x0/1*4Kg # ATxmega16A4 [USER] USER=0x4000 [DFU_AVR\CID_0x1e9441] DfuAltName = @Flash/0x0/1*16Kg # ATxmega16C4 [USER] USER=0x4000 [DFU_AVR\CID_0x1e9544] DfuAltName = @Flash/0x0/1*16Kg # ATxmega16D4 [USER] USER=0x4000 [DFU_AVR\CID_0x1e9442] DfuAltName = @Flash/0x0/1*16Kg # ATxmega32A4 [USER] USER=0x8000 [DFU_AVR\CID_0x1e9541] DfuAltName = @Flash/0x0/1*32Kg # ATxmega32C4 [USER] USER=0x8000 [DFU_AVR\CID_0x1e9443] DfuAltName = @Flash/0x0/1*32Kg # ATxmega32D4 [USER] USER=0x8000 [DFU_AVR\CID_0x1e9542] DfuAltName = @Flash/0x0/1*32Kg # ATxmega64A4 [USER] USER=0x10000 [DFU_AVR\CID_0x1e9646] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64C3 [USER] USER=0x10000 [DFU_AVR\CID_0x1e9649] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64D3 [USER] USER=0x10000 [DFU_AVR\CID_0x1e964a] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64D4 [USER] USER=0x10000 [DFU_AVR\CID_0x1e9647] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64A1 [USER] USER=0x10000 [DFU_AVR\CID_0x1e964e] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64A3 [USER] USER=0x10000 [DFU_AVR\CID_0x1e9642] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64B1 [USER] USER=0x10000 [DFU_AVR\CID_0x1e9652] DfuAltName = @Flash/0x0/1*64Kg # ATxmega64B3 [USER] USER=0x10000 [DFU_AVR\CID_0x1e9651] DfuAltName = @Flash/0x0/1*64Kg # ATxmega128C3 [USER] USER=0x20000 [DFU_AVR\CID_0x1e9752] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128D3 [USER] USER=0x20000 [DFU_AVR\CID_0x1e9748] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128D4 [USER] USER=0x20000 [DFU_AVR\CID_0x1e9747] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128A1 [USER] USER=0x20000 [DFU_AVR\CID_0x1e974c] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128A1D [USER] USER=0x20000 [DFU_AVR\CID_0x1e9741] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128A3 [USER] USER=0x20000 [DFU_AVR\CID_0x1e9742] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128A4 [USER] USER=0x20000 [DFU_AVR\CID_0x1e9746] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128B1 [USER] USER=0x20000 [DFU_AVR\CID_0x1e974d] DfuAltName = @Flash/0x0/1*128Kg # ATxmega128B3 [USER] USER=0x20000 [DFU_AVR\CID_0x1e974b] DfuAltName = @Flash/0x0/1*128Kg # ATxmega192C3 [USER] USER=0x30000 [DFU_AVR\CID_0x1e9751] DfuAltName = @Flash/0x0/1*192Kg # ATxmega192D3 [USER] USER=0x30000 [DFU_AVR\CID_0x1e9749] DfuAltName = @Flash/0x0/1*192Kg # ATxmega192A1 [USER] USER=0x30000 [DFU_AVR\CID_0x1e974e] DfuAltName = @Flash/0x0/1*192Kg # ATxmega192A3 [USER] USER=0x30000 [DFU_AVR\CID_0x1e9744] DfuAltName = @Flash/0x0/1*192Kg # ATxmega256 [USER] USER=0x40000 [DFU_AVR\CID_0x1e9846] DfuAltName = @Flash/0x0/1*256Kg # ATxmega256D3 [USER] USER=0x40000 [DFU_AVR\CID_0x1e9844] DfuAltName = @Flash/0x0/1*256Kg # ATxmega256A3 [USER] USER=0x40000 [DFU_AVR\CID_0x1e9842] DfuAltName = @Flash/0x0/1*256Kg # ATxmega256A3B [USER] USER=0x40000 [DFU_AVR\CID_0x1e9843] DfuAltName = @Flash/0x0/1*256Kg # ATxmega384C3 [USER] USER=0x60000 [DFU_AVR\CID_0x1e9845] DfuAltName = @Flash/0x0/1*384Kg # ATxmega384D3 [USER] USER=0x60000 [DFU_AVR\CID_0x1e9847] DfuAltName = @Flash/0x0/1*384Kg # ATxmega8E5 [USER] USER=0x2000 [DFU_AVR\CID_0x1e9341] DfuAltName = @Flash/0x0/1*8Kg # ATxmega16E5 [USER] USER=0x4000 [DFU_AVR\CID_0x1e9445] DfuAltName = @Flash/0x0/1*16Kg # ATxmega32E5 [USER] USER=0x8000 [DFU_AVR\CID_0x1e954c] DfuAltName = @Flash/0x0/1*32Kg # STM32F745 dfuse bootloader [USB\VID_0483&PID_DF11] Flags = absent-sector-size,will-disappear Plugin = dfu DfuForceVersion = 0x011a DfuForceTimeout = 5000 # Poly Studio USB [USB\VID_095D&PID_9217] Plugin = dfu Flags = manifest-poll,no-bus-reset-attach,allow-zero-polltimeout,signed-payload,index-force-detach RemoveDelay = 60000 [USB\VID_095D&PID_9218] Plugin = dfu Flags = manifest-poll,no-bus-reset-attach,allow-zero-polltimeout,signed-payload RemoveDelay = 60000 # Poly Eagle Eye Cube [USB\VID_095D&PID_9212] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,unsigned-payload RemoveDelay = 30000 [USB\VID_095D&PID_9213] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,unsigned-payload RemoveDelay = 30000 # Poly Studio P15 [USB\VID_095D&PID_9290] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,signed-payload,index-force-detach RemoveDelay = 60000 [USB\VID_095D&PID_9291] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,signed-payload RemoveDelay = 60000 # Poly ULCC [USB\VID_095D&PID_9160] Plugin = dfu Flags = manifest-poll,no-bus-reset-attach,allow-zero-polltimeout,signed-payload,index-force-detach RemoveDelay = 60000 [USB\VID_095D&PID_927B] Plugin = dfu Flags = manifest-poll,no-bus-reset-attach,allow-zero-polltimeout,signed-payload RemoveDelay = 60000 # Poly Eagle Eye Mini [USB\VID_095D&PID_3001] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,unsigned-payload RemoveDelay = 9000 [USB\VID_095D&PID_3002] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,unsigned-payload RemoveDelay = 9000 # Poly Studio R30 [USB\VID_095D&PID_92B2] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,signed-payload,index-force-detach RemoveDelay = 60000 [USB\VID_095D&PID_92B3] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,signed-payload RemoveDelay = 60000 [USB\VID_095D&PID_92B4] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,signed-payload,index-force-detach RemoveDelay = 60000 # Poly Studio P5 [USB\VID_095D&PID_9296] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,unsigned-payload RemoveDelay = 9000 [USB\VID_095D&PID_9297] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,unsigned-payload RemoveDelay = 9000 # Poly Studio E70 [USB\VID_095D&PID_92A1] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,signed-payload,index-force-detach RemoveDelay = 90000 [USB\VID_095D&PID_92A2] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,signed-payload RemoveDelay = 90000 # Poly Studio P21 [USB\VID_095D&PID_9298] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,unsigned-payload RemoveDelay = 9000 [USB\VID_095D&PID_9299] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,unsigned-payload RemoveDelay = 9000 # Poly Studio V52 [USB\VID_095D&PID_92C6] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,signed-payload,index-force-detach RemoveDelay = 180000 [USB\VID_095D&PID_92C7] Plugin = dfu Flags = manifest-poll,allow-zero-polltimeout,signed-payload RemoveDelay = 180000 # AVer ATLAS CAM [USB\VID_34AD&PID_0006] Plugin = dfu Flags = detach-for-attach RemoveDelay = 60000 # AVer CAM520 Pro2 [USB\VID_2574&PID_0A30] Plugin = dfu Flags = detach-for-attach RemoveDelay = 180000 # FlatFrog DFU [USB\VID_25B5&PID_0004] Plugin = dfu Flags = manifest-poll,detach-for-attach,ignore-upload # SunplusIT USB cameras [USB\VID_1BCF&PID_0B1D] Plugin = dfu Flags = detach-for-attach,ignore-upload,ignore-polltimeout [USB\VID_1BCF&PID_0B1E] Plugin = dfu Flags = detach-for-attach,ignore-upload # Sonix USB cameras [USB\VID_0C45&PID_636D] Plugin = dfu Flags = detach-for-attach,ignore-upload,enforce-requires [USB\VID_0C45&PID_636C] Plugin = dfu Flags = detach-for-attach,ignore-upload,enforce-requires [USB\VID_0C45&PID_636E] Plugin = dfu Flags = detach-for-attach,ignore-upload,enforce-requires [USB\VID_0C45&PID_636F] Plugin = dfu Flags = detach-for-attach,ignore-upload,enforce-requires [USB\VID_0C45&PID_6373] Plugin = dfu Flags = detach-for-attach,ignore-upload,enforce-requires [USB\VID_0C45&PID_6374] Plugin = dfu Flags = detach-for-attach,ignore-upload,enforce-requires # FPC Fingerprint Reader [USB\VID_10A5&PID_FFE0] DfuForceVersion = 0x0 Flags = enforce-requires [USB\VID_10A5&PID_FFE1] DfuForceVersion = 0x0 Flags = enforce-requires [USB\VID_10A5&PID_9800] DfuForceVersion = 0x0 Flags = enforce-requires fwupd-1.9.16/plugins/dfu/fu-dfu-common.c000066400000000000000000000021261460375044200200220ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-dfu-common.h" /** * fu_dfu_utils_bytes_join_array: * @chunks: (element-type GBytes): bytes * * Creates a monolithic block of memory from an array of #GBytes. * * Returns: (transfer full): a new GBytes **/ GBytes * fu_dfu_utils_bytes_join_array(GPtrArray *chunks) { gsize total_size = 0; guint32 offset = 0; guint8 *buffer; /* get the size of all the chunks */ for (guint i = 0; i < chunks->len; i++) { GBytes *chunk_tmp = g_ptr_array_index(chunks, i); total_size += g_bytes_get_size(chunk_tmp); } /* copy them into a buffer */ buffer = g_malloc0(total_size); for (guint i = 0; i < chunks->len; i++) { const guint8 *chunk_data; gsize chunk_size = 0; GBytes *chunk_tmp = g_ptr_array_index(chunks, i); chunk_data = g_bytes_get_data(chunk_tmp, &chunk_size); if (chunk_size == 0) continue; memcpy(buffer + offset, chunk_data, chunk_size); offset += chunk_size; } return g_bytes_new_take(buffer, total_size); } fwupd-1.9.16/plugins/dfu/fu-dfu-common.h000066400000000000000000000074201460375044200200310ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /** * FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD: * * Can download from host->device. */ #define FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD (1ull << 0) /** * FU_DFU_DEVICE_FLAG_CAN_UPLOAD: * * Can upload from device->host. */ #define FU_DFU_DEVICE_FLAG_CAN_UPLOAD (1ull << 1) /** * FU_DFU_DEVICE_FLAG_MANIFEST_TOL: * * Can answer GetStatus in manifest. */ #define FU_DFU_DEVICE_FLAG_MANIFEST_TOL (1ull << 2) /** * FU_DFU_DEVICE_FLAG_WILL_DETACH: * * Will self-detach. */ #define FU_DFU_DEVICE_FLAG_WILL_DETACH (1ull << 3) /** * FU_DFU_DEVICE_FLAG_CAN_ACCELERATE: * * Use a larger transfer size for speed. */ #define FU_DFU_DEVICE_FLAG_CAN_ACCELERATE (1ull << 7) /** * FU_DFU_DEVICE_FLAG_ATTACH_UPLOAD_DOWNLOAD: * * An upload or download is required for attach. */ #define FU_DFU_DEVICE_FLAG_ATTACH_UPLOAD_DOWNLOAD (1ull << (8 + 1)) /** * FU_DFU_DEVICE_FLAG_FORCE_DFU_MODE: * * Force DFU mode. */ #define FU_DFU_DEVICE_FLAG_FORCE_DFU_MODE (1ull << (8 + 2)) /** * FU_DFU_DEVICE_FLAG_IGNORE_POLLTIMEOUT: * * Ignore the device download timeout. */ #define FU_DFU_DEVICE_FLAG_IGNORE_POLLTIMEOUT (1ull << (8 + 3)) /** * FU_DFU_DEVICE_FLAG_IGNORE_RUNTIME: * * Device has broken DFU runtime support. */ #define FU_DFU_DEVICE_FLAG_IGNORE_RUNTIME (1ull << (8 + 4)) /** * FU_DFU_DEVICE_FLAG_IGNORE_UPLOAD: * * Uploading from the device is broken. */ #define FU_DFU_DEVICE_FLAG_IGNORE_UPLOAD (1ull << (8 + 5)) /** * FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME: * * No DFU runtime interface is provided. */ #define FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME (1ull << (8 + 6)) /** * FU_DFU_DEVICE_FLAG_NO_GET_STATUS_UPLOAD: * * Do not do GetStatus when uploading. */ #define FU_DFU_DEVICE_FLAG_NO_GET_STATUS_UPLOAD (1ull << (8 + 7)) /** * FU_DFU_DEVICE_FLAG_NO_PID_CHANGE: * * Accept the same VID:PID when changing modes. */ #define FU_DFU_DEVICE_FLAG_NO_PID_CHANGE (1ull << (8 + 8)) /** * FU_DFU_DEVICE_FLAG_USE_ANY_INTERFACE: * * Use any interface for DFU. */ #define FU_DFU_DEVICE_FLAG_USE_ANY_INTERFACE (1ull << (8 + 9)) /** * FU_DFU_DEVICE_FLAG_USE_ATMEL_AVR: * * Device uses the ATMEL bootloader. */ #define FU_DFU_DEVICE_FLAG_USE_ATMEL_AVR (1ull << (8 + 10)) /** * FU_DFU_DEVICE_FLAG_USE_PROTOCOL_ZERO: * * Fix up the protocol number. */ #define FU_DFU_DEVICE_FLAG_USE_PROTOCOL_ZERO (1ull << (8 + 11)) /** * FU_DFU_DEVICE_FLAG_LEGACY_PROTOCOL: * * Use a legacy protocol version. */ #define FU_DFU_DEVICE_FLAG_LEGACY_PROTOCOL (1ull << (8 + 12)) /** * FU_DFU_DEVICE_FLAG_DETACH_FOR_ATTACH: * * Requires a FU_DFU_REQUEST_DETACH to attach. */ #define FU_DFU_DEVICE_FLAG_DETACH_FOR_ATTACH (1ull << (8 + 13)) /** * FU_DFU_DEVICE_FLAG_ABSENT_SECTOR_SIZE: * * In absence of sector size, assume byte. */ #define FU_DFU_DEVICE_FLAG_ABSENT_SECTOR_SIZE (1ull << (8 + 14)) /** * FU_DFU_DEVICE_FLAG_MANIFEST_POLL: * * Requires polling via GetStatus in dfuManifest state. */ #define FU_DFU_DEVICE_FLAG_MANIFEST_POLL (1ull << (8 + 15)) /** * FU_DFU_DEVICE_FLAG_NO_BUS_RESET_ATTACH: * * Do not require a bus reset to attach to normal. */ #define FU_DFU_DEVICE_FLAG_NO_BUS_RESET_ATTACH (1ull << (8 + 16)) /** * FU_DFU_DEVICE_FLAG_GD32: * * Uses the slightly weird GD32 variant of DFU. */ #define FU_DFU_DEVICE_FLAG_GD32 (1ull << (8 + 17)) /** * FU_DFU_DEVICE_FLAG_ALLOW_ZERO_POLLTIMEOUT: * * Allows the zero bwPollTimeout from GetStatus in dfuDNLOAD-SYNC state. */ #define FU_DFU_DEVICE_FLAG_ALLOW_ZERO_POLLTIMEOUT (1ull << (8 + 18)) /** * FU_DFU_DEVICE_FLAG_INDEX_FORCE_DETACH: * * Requires Force Detach in wIndex to bypass status checking. */ #define FU_DFU_DEVICE_FLAG_INDEX_FORCE_DETACH (1ull << (8 + 19)) /* helpers */ GBytes * fu_dfu_utils_bytes_join_array(GPtrArray *chunks); fwupd-1.9.16/plugins/dfu/fu-dfu-device.c000066400000000000000000001474501460375044200200030ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * FuDfuDevice: * * This object allows two things: * * - Downloading from the host to the device, optionally with * verification using a DFU or DfuSe firmware file. * * - Uploading from the device to the host to a DFU or DfuSe firmware * file. The file format is chosen automatically, with DfuSe being * chosen if the device contains more than one target. * * See also: [class@FuDfuTarget], [class@FuDfuseFirmware] */ /** * FU_QUIRKS_DFU_FORCE_VERSION: * @key: the USB device ID, e.g. `USB\VID_0763&PID_2806` * @value: the uint16_t DFU version, encoded in base 16, e.g. `0110` * * Forces a specific DFU version for the hardware device. This is required * if the device does not set, or sets incorrectly, items in the DFU functional * descriptor. If zero, then DFU functionality is disabled. * * Since: 1.0.1 */ #define FU_QUIRKS_DFU_FORCE_VERSION "DfuForceVersion" #define DFU_DEVICE_DNLOAD_TIMEOUT_DEFAULT 5 /* ms */ #include "config.h" #include "fu-dfu-common.h" #include "fu-dfu-device.h" #include "fu-dfu-target-avr.h" #include "fu-dfu-target-private.h" /* waive-pre-commit */ #include "fu-dfu-target-stm.h" static gboolean fu_dfu_device_clear_status(FuDfuDevice *self, GError **error); static void fu_dfu_device_finalize(GObject *object); typedef struct { FuDfuState state; FuDfuStatus status; GPtrArray *targets; gboolean done_upload_or_download; gboolean claimed_interface; gchar *chip_id; guint16 version; guint16 force_version; guint16 force_transfer_size; guint16 runtime_pid; guint16 runtime_vid; guint16 runtime_release; guint16 transfer_size; guint8 iface_number; guint dnload_timeout; guint timeout_ms; } FuDfuDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuDfuDevice, fu_dfu_device, FU_TYPE_USB_DEVICE) #define GET_PRIVATE(o) (fu_dfu_device_get_instance_private(o)) static void fu_dfu_device_set_state(FuDfuDevice *self, FuDfuState state); static void fu_dfu_device_to_string(FuDevice *device, guint idt, GString *str) { FuDfuDevice *self = FU_DFU_DEVICE(device); FuDfuDevicePrivate *priv = GET_PRIVATE(self); fu_string_append(str, idt, "State", fu_dfu_state_to_string(priv->state)); fu_string_append(str, idt, "Status", fu_dfu_status_to_string(priv->status)); fu_string_append_kb(str, idt, "DoneUploadOrDownload", priv->done_upload_or_download); fu_string_append_kb(str, idt, "ClaimedInterface", priv->claimed_interface); if (priv->chip_id != NULL) fu_string_append(str, idt, "ChipId", priv->chip_id); fu_string_append_kx(str, idt, "Version", priv->version); if (priv->force_version != G_MAXUINT16) fu_string_append_kx(str, idt, "ForceVersion", priv->force_version); if (priv->force_transfer_size != 0x0) { fu_string_append_kx(str, idt, "ForceTransferSize", priv->force_transfer_size); } fu_string_append_kx(str, idt, "RuntimePid", priv->runtime_pid); fu_string_append_kx(str, idt, "RuntimeVid", priv->runtime_vid); fu_string_append_kx(str, idt, "RuntimeRelease", priv->runtime_release); fu_string_append_kx(str, idt, "TransferSize", priv->transfer_size); fu_string_append_kx(str, idt, "IfaceNumber", priv->iface_number); fu_string_append_kx(str, idt, "DnloadTimeout", priv->dnload_timeout); fu_string_append_kx(str, idt, "TimeoutMs", priv->timeout_ms); for (guint i = 0; i < priv->targets->len; i++) { FuDfuTarget *target = g_ptr_array_index(priv->targets, i); fu_device_add_string(FU_DEVICE(target), idt + 1, str); } } /** * fu_dfu_device_get_transfer_size: * @device: a USB device * * Gets the transfer size in bytes. * * Returns: packet size, or 0 for unknown **/ guint16 fu_dfu_device_get_transfer_size(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0xffff); return priv->transfer_size; } /** * fu_dfu_device_get_version: * @self: a #FuDfuDevice * * Gets the DFU specification version supported by the device. * * Returns: integer, or 0 for unknown, e.g. %FU_DFU_FIRMARE_VERSION_DFU_1_1 **/ guint16 fu_dfu_device_get_version(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0xffff); return priv->version; } /** * fu_dfu_device_get_download_timeout: * @self: a #FuDfuDevice * * Gets the download timeout in ms. * * Returns: delay, or 0 for unknown **/ guint fu_dfu_device_get_download_timeout(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0); return priv->dnload_timeout; } static void fu_dfu_device_set_download_timeout(FuDfuDevice *self, guint dnload_timeout) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DFU_DEVICE(self)); /* quirked */ if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_IGNORE_POLLTIMEOUT)) { g_debug("ignoring dnload-timeout, using default of %ums", priv->dnload_timeout); return; } if (dnload_timeout == 0 && !fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_ALLOW_ZERO_POLLTIMEOUT)) { g_debug("no dnload-timeout, using default of %ums", priv->dnload_timeout); return; } /* use what the device says */ priv->dnload_timeout = dnload_timeout; } /** * fu_dfu_device_set_transfer_size: * @self: a #FuDfuDevice * @transfer_size: maximum packet size * * Sets the transfer size in bytes. **/ void fu_dfu_device_set_transfer_size(FuDfuDevice *self, guint16 transfer_size) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DFU_DEVICE(self)); priv->transfer_size = transfer_size; } typedef struct __attribute__((packed)) { guint8 bLength; guint8 bDescriptorType; guint8 bmAttributes; guint16 wDetachTimeOut; guint16 wTransferSize; guint16 bcdDFUVersion; } DfuFuncDescriptor; static gboolean fu_dfu_device_parse_iface_data(FuDfuDevice *self, GBytes *iface_data, GError **error) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); DfuFuncDescriptor desc = {0x0}; const guint8 *buf; gsize sz; /* parse the functional descriptor */ buf = g_bytes_get_data(iface_data, &sz); if (sz == sizeof(DfuFuncDescriptor)) { memcpy(&desc, buf, sz); } else if (sz > sizeof(DfuFuncDescriptor)) { g_debug("DFU interface with %" G_GSIZE_FORMAT " bytes vendor data", sz - sizeof(DfuFuncDescriptor)); memcpy(&desc, buf, sizeof(DfuFuncDescriptor)); } else if (sz == sizeof(DfuFuncDescriptor) - 2) { g_warning("truncated DFU interface data, no bcdDFUVersion"); memcpy(&desc, buf, sz); desc.bcdDFUVersion = FU_DFU_FIRMARE_VERSION_DFU_1_1; } else { g_autoptr(GString) bufstr = g_string_new(NULL); for (gsize i = 0; i < sz; i++) g_string_append_printf(bufstr, "%02x ", buf[i]); if (bufstr->len > 0) g_string_truncate(bufstr, bufstr->len - 1); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "interface found, but not the correct length for " "functional data: %" G_GSIZE_FORMAT " bytes: %s", sz, bufstr->str); return FALSE; } /* get transfer size and version */ priv->transfer_size = GUINT16_FROM_LE(desc.wTransferSize); priv->version = GUINT16_FROM_LE(desc.bcdDFUVersion); /* ST-specific */ if (priv->version == FU_DFU_FIRMARE_VERSION_DFUSE && desc.bmAttributes & FU_DFU_DEVICE_FLAG_CAN_ACCELERATE) priv->transfer_size = 0x1000; /* get attributes about the DFU operation */ if (desc.bmAttributes & FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD) fu_device_add_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD); if (desc.bmAttributes & FU_DFU_DEVICE_FLAG_CAN_UPLOAD) fu_device_add_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_CAN_UPLOAD); if (desc.bmAttributes & FU_DFU_DEVICE_FLAG_MANIFEST_TOL) fu_device_add_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_MANIFEST_TOL); if (desc.bmAttributes & FU_DFU_DEVICE_FLAG_WILL_DETACH) fu_device_add_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_WILL_DETACH); if (desc.bmAttributes & FU_DFU_DEVICE_FLAG_CAN_ACCELERATE) fu_device_add_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_CAN_ACCELERATE); return TRUE; } static void fu_dfu_device_guess_state_from_iface(FuDfuDevice *self, GUsbInterface *iface) { /* some devices use the wrong interface */ if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_FORCE_DFU_MODE)) { g_debug("quirking device into DFU mode"); fu_dfu_device_set_state(self, FU_DFU_STATE_DFU_IDLE); return; } /* runtime */ if (g_usb_interface_get_protocol(iface) == 0x01) { fu_dfu_device_set_state(self, FU_DFU_STATE_APP_IDLE); return; } /* DFU */ if (g_usb_interface_get_protocol(iface) == 0x02) { fu_dfu_device_set_state(self, FU_DFU_STATE_DFU_IDLE); return; } g_warning("unable to guess initial device state from interface %u", g_usb_interface_get_protocol(iface)); } static gboolean fu_dfu_device_add_targets(FuDfuDevice *self, GError **error) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); g_autoptr(GPtrArray) ifaces = NULL; /* disabled using quirk */ if (priv->force_version == 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ignoring device as DFU version set to 0x0"); return FALSE; } /* add all DFU-capable targets */ ifaces = g_usb_device_get_interfaces(usb_device, error); if (ifaces == NULL) return FALSE; g_ptr_array_set_size(priv->targets, 0); for (guint i = 0; i < ifaces->len; i++) { GBytes *iface_data = NULL; FuDfuTarget *target; g_autoptr(GError) error_local = NULL; GUsbInterface *iface = g_ptr_array_index(ifaces, i); /* some devices don't use the right class and subclass */ if (!fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_USE_ANY_INTERFACE)) { if (g_usb_interface_get_class(iface) != G_USB_DEVICE_CLASS_APPLICATION_SPECIFIC) continue; if (g_usb_interface_get_subclass(iface) != 0x01) continue; } /* parse any interface data */ iface_data = g_usb_interface_get_extra(iface); if (iface_data != NULL && g_bytes_get_size(iface_data) > 0) { if (!fu_dfu_device_parse_iface_data(self, iface_data, &error_local)) { g_warning("failed to parse interface data for %04x:%04x: %s", g_usb_device_get_vid(usb_device), g_usb_device_get_pid(usb_device), error_local->message); continue; } } else { fu_device_add_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_CAN_UPLOAD); fu_device_add_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD); } /* fix up the version */ if (priv->force_version != G_MAXUINT16) priv->version = priv->force_version; if (priv->version == FU_DFU_FIRMARE_VERSION_DFU_1_0 || priv->version == FU_DFU_FIRMARE_VERSION_DFU_1_1) { g_info("DFU v1.1"); } else if (priv->version == FU_DFU_FIRMARE_VERSION_ATMEL_AVR) { g_info("AVR-DFU support"); priv->version = FU_DFU_FIRMARE_VERSION_ATMEL_AVR; } else if (priv->version == FU_DFU_FIRMARE_VERSION_DFUSE) { g_info("STM-DFU support"); } else if (priv->version == 0x0101) { g_info("DFU v1.1 assumed"); priv->version = FU_DFU_FIRMARE_VERSION_DFU_1_1; } else { g_warning("DFU version 0x%04x invalid, v1.1 assumed", priv->version); priv->version = FU_DFU_FIRMARE_VERSION_DFU_1_1; } /* set expected protocol */ if (priv->version == FU_DFU_FIRMARE_VERSION_DFUSE) { fu_device_add_protocol(FU_DEVICE(self), "com.st.dfuse"); } else { fu_device_add_protocol(FU_DEVICE(self), "org.usb.dfu"); } /* fix up the transfer size */ if (priv->force_transfer_size != 0x0) { priv->transfer_size = priv->force_transfer_size; g_debug("forcing DFU transfer size 0x%04x bytes", priv->transfer_size); } else if (priv->transfer_size == 0xffff) { priv->transfer_size = 0x0400; g_debug("DFU transfer size unspecified, guessing"); } else if (priv->transfer_size == 0x0) { g_warning("DFU transfer size invalid, using default"); priv->transfer_size = 64; } else { g_debug("using DFU transfer size 0x%04x bytes", priv->transfer_size); } /* create a target of the required type */ switch (priv->version) { case FU_DFU_FIRMARE_VERSION_DFUSE: target = fu_dfu_target_stm_new(); break; case FU_DFU_FIRMARE_VERSION_ATMEL_AVR: target = fu_dfu_target_avr_new(); break; default: target = fu_dfu_target_new(); break; } fu_device_set_proxy(FU_DEVICE(target), FU_DEVICE(self)); fu_dfu_target_set_alt_idx(target, g_usb_interface_get_index(iface)); fu_dfu_target_set_alt_setting(target, g_usb_interface_get_alternate(iface)); /* add target */ priv->iface_number = g_usb_interface_get_number(iface); g_ptr_array_add(priv->targets, target); fu_dfu_device_guess_state_from_iface(self, iface); } /* save for reset */ if (priv->state == FU_DFU_STATE_APP_IDLE || fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_PID_CHANGE)) { priv->runtime_vid = g_usb_device_get_vid(usb_device); priv->runtime_pid = g_usb_device_get_pid(usb_device); priv->runtime_release = g_usb_device_get_release(usb_device); } /* the device has no DFU runtime, so cheat */ if (priv->targets->len == 0 && fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME)) { g_debug("no DFU runtime, so faking device"); fu_dfu_device_set_state(self, FU_DFU_STATE_APP_IDLE); priv->iface_number = 0xff; priv->runtime_vid = g_usb_device_get_vid(usb_device); priv->runtime_pid = g_usb_device_get_pid(usb_device); priv->runtime_release = g_usb_device_get_release(usb_device); fu_device_add_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD | FU_DFU_DEVICE_FLAG_CAN_UPLOAD); return TRUE; } /* no targets */ if (priv->targets->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no DFU interfaces"); return FALSE; } /* the device upload is broken */ if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_IGNORE_UPLOAD)) fu_device_remove_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_CAN_UPLOAD); return TRUE; } /** * fu_dfu_device_get_timeout: * @device: a #FuDfuDevice * * Gets the device timeout. * * Returns: enumerated timeout in ms **/ guint fu_dfu_device_get_timeout(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0); return priv->timeout_ms; } /** * fu_dfu_device_get_state: * @device: a #FuDfuDevice * * Gets the device state. * * Returns: enumerated state, e.g. %FU_DFU_STATE_DFU_UPLOAD_IDLE **/ FuDfuState fu_dfu_device_get_state(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0); return priv->state; } /** * fu_dfu_device_get_status: * @device: a USB device * * Gets the device status. * * Returns: enumerated status, e.g. %FU_DFU_STATUS_ERR_ADDRESS **/ FuDfuStatus fu_dfu_device_get_status(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0); return priv->status; } /** * fu_dfu_device_new: * * Creates a new DFU device object. * * Returns: a new #FuDfuDevice **/ FuDfuDevice * fu_dfu_device_new(FuContext *ctx, GUsbDevice *usb_device) { FuDfuDevice *self; self = g_object_new(FU_TYPE_DFU_DEVICE, "usb-device", usb_device, "context", ctx, NULL); return self; } /** * fu_dfu_device_get_target_by_alt_setting: * @self: a #FuDfuDevice * @alt_setting: the setting used to find * @error: (nullable): optional return location for an error * * Gets a target with a specific alternative setting. * * Returns: (transfer full): a #FuDfuTarget, or %NULL **/ FuDfuTarget * fu_dfu_device_get_target_by_alt_setting(FuDfuDevice *self, guint8 alt_setting, GError **error) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* find by ID */ for (guint i = 0; i < priv->targets->len; i++) { FuDfuTarget *target = g_ptr_array_index(priv->targets, i); if (fu_dfu_target_get_alt_setting(target) == alt_setting) return g_object_ref(target); } /* failed */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No target with alt-setting %i", alt_setting); return NULL; } /** * fu_dfu_device_get_target_by_alt_name: * @self: a #FuDfuDevice * @alt_name: the name used to find * @error: (nullable): optional return location for an error * * Gets a target with a specific alternative name. * * Returns: (transfer full): a #FuDfuTarget, or %NULL **/ FuDfuTarget * fu_dfu_device_get_target_by_alt_name(FuDfuDevice *self, const gchar *alt_name, GError **error) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* find by ID */ for (guint i = 0; i < priv->targets->len; i++) { FuDfuTarget *target = g_ptr_array_index(priv->targets, i); if (g_strcmp0(fu_device_get_logical_id(FU_DEVICE(target)), alt_name) == 0) return g_object_ref(target); } /* failed */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No target with alt-name %s", alt_name); return NULL; } /** * fu_dfu_device_get_runtime_vid: * @self: a #FuDfuDevice * * Gets the runtime vendor ID. * * Returns: vendor ID, or 0xffff for unknown **/ guint16 fu_dfu_device_get_runtime_vid(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0xffff); return priv->runtime_vid; } /** * fu_dfu_device_get_runtime_pid: * @self: a #FuDfuDevice * * Gets the runtime product ID. * * Returns: product ID, or 0xffff for unknown **/ guint16 fu_dfu_device_get_runtime_pid(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0xffff); return priv->runtime_pid; } const gchar * fu_dfu_device_get_chip_id(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), NULL); return priv->chip_id; } static void fu_dfu_device_set_chip_id(FuDfuDevice *self, const gchar *chip_id) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_DFU_DEVICE(self)); g_debug("chip ID set to: %s", chip_id); priv->chip_id = g_strdup(chip_id); } static void fu_dfu_device_set_state(FuDfuDevice *self, FuDfuState state) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); if (priv->state == state) return; priv->state = state; /* set bootloader status */ if (state == FU_DFU_STATE_APP_IDLE || state == FU_DFU_STATE_APP_DETACH) { fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } } static void fu_dfu_device_set_status(FuDfuDevice *self, FuDfuStatus status) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); if (priv->status == status) return; priv->status = status; } gboolean fu_dfu_device_ensure_interface(FuDfuDevice *self, GError **error) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); g_autoptr(GError) error_local = NULL; /* already done */ if (priv->claimed_interface) return TRUE; /* nothing set */ if (priv->iface_number == 0xff) return TRUE; /* claim, without detaching kernel driver */ if (!g_usb_device_claim_interface(usb_device, (gint)priv->iface_number, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot claim interface %i: %s", priv->iface_number, error_local->message); return FALSE; } /* success */ priv->claimed_interface = TRUE; return TRUE; } /** * fu_dfu_device_refresh_and_clear: * @self: a #FuDfuDevice * @error: (nullable): optional return location for an error * * Refreshes the cached properties on the DFU device. If there are any transfers * in progress they are cancelled, and if there are any pending errors they are * cancelled. * * Returns: %TRUE for success **/ gboolean fu_dfu_device_refresh_and_clear(FuDfuDevice *self, GError **error) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); if (!fu_dfu_device_refresh(self, 0, error)) return FALSE; switch (priv->state) { case FU_DFU_STATE_DFU_UPLOAD_IDLE: case FU_DFU_STATE_DFU_DNLOAD_IDLE: case FU_DFU_STATE_DFU_DNLOAD_SYNC: g_debug("aborting transfer %s", fu_dfu_status_to_string(priv->status)); if (!fu_dfu_device_abort(self, error)) return FALSE; break; case FU_DFU_STATE_DFU_ERROR: g_debug("clearing error %s", fu_dfu_status_to_string(priv->status)); if (!fu_dfu_device_clear_status(self, error)) return FALSE; break; default: break; } return TRUE; } /** * fu_dfu_device_refresh: * @self: a #FuDfuDevice * @timeout_ms: a timeout, or 0 to use the default device timeout * @error: (nullable): optional return location for an error * * Refreshes the cached properties on the DFU device. * * Returns: %TRUE for success **/ gboolean fu_dfu_device_refresh(FuDfuDevice *self, guint timeout_ms, GError **error) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length = 0; guint8 buf[6] = {0x0}; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_DFU_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* fall back to default */ if (timeout_ms == 0) timeout_ms = priv->timeout_ms; /* the device has no DFU runtime, so cheat */ if (priv->state == FU_DFU_STATE_APP_IDLE && fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME)) return TRUE; /* ensure interface is claimed */ if (!fu_dfu_device_ensure_interface(self, error)) return FALSE; /* Device that cannot communicate via the USB after the * Manifestation phase indicated this limitation to the * host by clearing bmAttributes bit bitManifestationTolerant. * so we assume the operation was successful */ if (priv->state == FU_DFU_STATE_DFU_MANIFEST && !fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_MANIFEST_TOL)) return TRUE; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, FU_DFU_REQUEST_GETSTATUS, 0, priv->iface_number, buf, sizeof(buf), &actual_length, timeout_ms, NULL, /* cancellable */ &error_local)) { /* got STALL */ if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NOT_SUPPORTED)) { g_info("GetStatus not implemented, assuming appIDLE"); fu_dfu_device_set_status(self, FU_DFU_STATUS_OK); fu_dfu_device_set_state(self, FU_DFU_STATE_APP_IDLE); return TRUE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot get device state: %s", error_local->message); return FALSE; } if (actual_length != 6) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot get device status, invalid size: %04x", (guint)actual_length); return FALSE; } /* some devices use the wrong state value */ if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_FORCE_DFU_MODE) && fu_dfu_device_get_state(self) != FU_DFU_STATE_DFU_IDLE) { g_info("quirking device into DFU mode"); fu_dfu_device_set_state(self, FU_DFU_STATE_DFU_IDLE); } else { fu_dfu_device_set_state(self, buf[4]); } /* status or state changed */ fu_dfu_device_set_status(self, buf[0]); fu_dfu_device_set_download_timeout(self, fu_memread_uint24(&buf[1], G_LITTLE_ENDIAN)); g_debug("refreshed status=%s and state=%s (dnload=%u)", fu_dfu_status_to_string(priv->status), fu_dfu_state_to_string(priv->state), priv->dnload_timeout); return TRUE; } static gboolean fu_dfu_device_request_detach(FuDfuDevice *self, FuProgress *progress, GError **error) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); const guint16 timeout_reset_ms = 1000; guint16 ctrl_setup_index = priv->iface_number; g_autoptr(GError) error_local = NULL; if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_INDEX_FORCE_DETACH)) ctrl_setup_index |= 0x01u << 8; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, FU_DFU_REQUEST_DETACH, timeout_reset_ms, ctrl_setup_index, NULL, 0, NULL, priv->timeout_ms, NULL, /* cancellable */ &error_local)) { /* some devices just reboot and stall the endpoint :/ */ if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NOT_SUPPORTED) || g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED)) { g_debug("ignoring while detaching: %s", error_local->message); } else { /* refresh the error code */ fu_dfu_device_error_fixup(self, &error_local); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot detach device: %s", error_local->message); return FALSE; } } return TRUE; } static gboolean fu_dfu_device_reload(FuDevice *device, GError **error) { FuDfuDevice *self = FU_DFU_DEVICE(device); return fu_dfu_device_refresh_and_clear(self, error); } static gboolean fu_dfu_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuDfuDevice *self = FU_DFU_DEVICE(device); FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* already in DFU mode */ if (!fu_dfu_device_refresh_and_clear(self, error)) return FALSE; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; /* the device has no DFU runtime, so cheat */ if (priv->state == FU_DFU_STATE_APP_IDLE && fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME)) return TRUE; /* ensure interface is claimed */ if (!fu_dfu_device_ensure_interface(self, error)) return FALSE; /* inform UI there's going to be a detach:attach */ if (!fu_dfu_device_request_detach(self, progress, error)) return FALSE; /* do a host reset */ if (!fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_WILL_DETACH)) { g_info("doing device reset as host will not self-reset"); if (!fu_dfu_device_reset(self, progress, error)) return FALSE; } /* success */ priv->force_version = G_MAXUINT16; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } /** * fu_dfu_device_abort: * @self: a #FuDfuDevice * @error: (nullable): optional return location for an error * * Aborts any upload or download in progress. * * Returns: %TRUE for success **/ gboolean fu_dfu_device_abort(FuDfuDevice *self, GError **error) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_DFU_DEVICE(self), FALSE); g_return_val_if_fail(G_USB_IS_DEVICE(usb_device), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* the device has no DFU runtime, so cheat */ if (priv->state == FU_DFU_STATE_APP_IDLE && fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported as no DFU runtime"); return FALSE; } /* ensure interface is claimed */ if (!fu_dfu_device_ensure_interface(self, error)) return FALSE; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, FU_DFU_REQUEST_ABORT, 0, priv->iface_number, NULL, 0, NULL, priv->timeout_ms, NULL, /* cancellable */ &error_local)) { /* refresh the error code */ fu_dfu_device_error_fixup(self, &error_local); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot abort device: %s", error_local->message); return FALSE; } return TRUE; } static gboolean fu_dfu_device_clear_status(FuDfuDevice *self, GError **error) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GError) error_local = NULL; /* the device has no DFU runtime, so cheat */ if (priv->state == FU_DFU_STATE_APP_IDLE && fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported as no DFU runtime"); return FALSE; } /* ensure interface is claimed */ if (!fu_dfu_device_ensure_interface(self, error)) return FALSE; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, FU_DFU_REQUEST_CLRSTATUS, 0, priv->iface_number, NULL, 0, NULL, priv->timeout_ms, NULL, /* cancellable */ &error_local)) { /* refresh the error code */ fu_dfu_device_error_fixup(self, &error_local); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot clear status on the device: %s", error_local->message); return FALSE; } return TRUE; } /** * fu_dfu_device_get_interface: * @self: a #FuDfuDevice * * Gets the interface number. **/ guint8 fu_dfu_device_get_interface(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), 0xff); return priv->iface_number; } /** * fu_dfu_device_open: * @self: a #FuDfuDevice * @error: (nullable): optional return location for an error * * Opens a DFU-capable device. * * Returns: %TRUE for success **/ static gboolean fu_dfu_device_open(FuDevice *device, GError **error) { FuDfuDevice *self = FU_DFU_DEVICE(device); FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_DEVICE(device), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* FuUsbDevice->open */ if (!FU_DEVICE_CLASS(fu_dfu_device_parent_class)->open(device, error)) return FALSE; /* the device has no DFU runtime, so cheat */ if (priv->state == FU_DFU_STATE_APP_IDLE && fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME)) { fu_dfu_device_set_state(self, FU_DFU_STATE_APP_IDLE); priv->status = FU_DFU_STATUS_OK; } /* GD32VF103 encodes the serial number in UTF-8 (rather than UTF-16) * and also uses the first two bytes as the model identifier */ if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_GD32)) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); const guint8 *buf; gsize bufsz = 0; guint16 langid = G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES; guint8 idx = g_usb_device_get_serial_number_index(usb_device); g_autofree gchar *chip_id = NULL; g_autofree gchar *serial_str = NULL; g_autoptr(GBytes) serial_blob = NULL; serial_blob = g_usb_device_get_string_descriptor_bytes(usb_device, idx, langid, error); if (serial_blob == NULL) return FALSE; fu_dump_bytes(G_LOG_DOMAIN, "GD32 serial", serial_blob); buf = g_bytes_get_data(serial_blob, &bufsz); if (bufsz < 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "GD32 serial number invalid"); return FALSE; } /* ID is first two bytes */ chip_id = g_strdup_printf("%02x%02x", buf[0], buf[1]); fu_dfu_device_set_chip_id(self, chip_id); /* serial number follows */ serial_str = g_strndup((const gchar *)buf + 2, bufsz - 2); fu_device_set_serial(FU_DEVICE(device), serial_str); } /* set up target ready for use */ for (guint j = 0; j < priv->targets->len; j++) { FuDfuTarget *target = g_ptr_array_index(priv->targets, j); if (!fu_dfu_target_setup(target, error)) return FALSE; } /* success */ return TRUE; } /** * fu_dfu_device_close: * @self: a #FuDfuDevice * @error: (nullable): optional return location for an error * * Closes a DFU device. * * Returns: %TRUE for success **/ static gboolean fu_dfu_device_close(FuDevice *device, GError **error) { FuDfuDevice *self = FU_DFU_DEVICE(device); FuDfuDevicePrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); /* release interface */ if (priv->claimed_interface) { g_autoptr(GError) error_local = NULL; if (!g_usb_device_release_interface(usb_device, (gint)priv->iface_number, 0, &error_local)) { if (!g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE)) { g_warning("failed to release interface: %s", error_local->message); } } priv->claimed_interface = FALSE; } /* FuUsbDevice->close */ return FU_DEVICE_CLASS(fu_dfu_device_parent_class)->close(device, error); } static gboolean fu_dfu_device_probe(FuDevice *device, GError **error) { FuDfuDevice *self = FU_DFU_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); /* add all the targets */ if (!fu_dfu_device_add_targets(self, error)) { g_prefix_error(error, "%04x:%04x is not supported: ", g_usb_device_get_vid(usb_device), g_usb_device_get_pid(usb_device)); return FALSE; } /* check capabilities */ if (!fu_device_has_private_flag(device, FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD)) { g_info("%04x:%04x is missing download capability", g_usb_device_get_vid(usb_device), g_usb_device_get_pid(usb_device)); } /* hardware from Jabra literally reboots if you try to retry a failed * write -- there's no way to avoid blocking the daemon like this... */ if (fu_device_has_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ATTACH_EXTRA_RESET)) { g_debug("blocking wait to work around Jabra hardware..."); fu_device_sleep(device, 10000); } /* success */ return TRUE; } gboolean fu_dfu_device_reset(FuDfuDevice *self, FuProgress *progress, GError **error) { g_autoptr(GError) error_local = NULL; g_autoptr(GTimer) timer = g_timer_new(); g_return_val_if_fail(FU_IS_DFU_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!g_usb_device_reset(fu_usb_device_get_dev(FU_USB_DEVICE(self)), &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot reset USB device: %s [%i]", error_local->message, error_local->code); return FALSE; } g_debug("reset took %.2lfms", g_timer_elapsed(timer, NULL) * 1000); return TRUE; } static gboolean fu_dfu_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuDfuDevice *self = FU_DFU_DEVICE(device); FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(FuDfuTarget) target = NULL; g_return_val_if_fail(FU_IS_DFU_DEVICE(device), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* already in runtime mode */ if (!fu_dfu_device_refresh_and_clear(self, error)) return FALSE; if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; /* handle weirdness */ if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_DETACH_FOR_ATTACH)) { if (!fu_dfu_device_request_detach(self, progress, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } /* handle m-stack DFU bootloaders */ if (!priv->done_upload_or_download && fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_ATTACH_UPLOAD_DOWNLOAD)) { g_autoptr(GBytes) chunk = NULL; g_autoptr(FuDfuTarget) target_zero = NULL; g_debug("doing dummy upload to work around m-stack quirk"); target_zero = fu_dfu_device_get_target_by_alt_setting(self, 0, error); if (target_zero == NULL) return FALSE; chunk = fu_dfu_target_upload_chunk(target_zero, 0, 0, progress, error); if (chunk == NULL) return FALSE; } /* get default target */ target = fu_dfu_device_get_target_by_alt_setting(self, 0, error); if (target == NULL) return FALSE; /* normal DFU mode just needs a bus reset */ if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_BUS_RESET_ATTACH) && fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_WILL_DETACH)) { g_info("bus reset is not required; device will reboot to normal"); } else if (!fu_dfu_target_attach(target, progress, error)) { g_prefix_error(error, "failed to attach target: "); return FALSE; } /* there is no USB runtime whatsoever */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) return TRUE; /* success */ priv->force_version = 0x0; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } /** * fu_dfu_device_upload: * @self: a #FuDfuDevice * @flags: DFU target flags, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY * @error: (nullable): optional return location for an error * * Uploads firmware from the target to the host. * * Returns: (transfer full): the uploaded firmware, or %NULL for error **/ FuFirmware * fu_dfu_device_upload(FuDfuDevice *self, FuProgress *progress, FuDfuTargetTransferFlags flags, GError **error) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); gboolean use_dfuse = FALSE; g_autoptr(FuFirmware) firmware = NULL; /* ensure interface is claimed */ if (!fu_dfu_device_ensure_interface(self, error)) return NULL; /* choose the most appropriate type */ for (guint i = 0; i < priv->targets->len; i++) { FuDfuTarget *target = g_ptr_array_index(priv->targets, i); if (fu_device_get_logical_id(FU_DEVICE(target)) != NULL || i > 0) { use_dfuse = TRUE; break; } } if (use_dfuse) { firmware = fu_dfuse_firmware_new(); g_debug("switching to DefuSe automatically"); } else { firmware = fu_dfu_firmware_new(); } fu_dfu_firmware_set_vid(FU_DFU_FIRMWARE(firmware), priv->runtime_vid); fu_dfu_firmware_set_pid(FU_DFU_FIRMWARE(firmware), priv->runtime_pid); fu_dfu_firmware_set_release(FU_DFU_FIRMWARE(firmware), 0xffff); /* upload from each target */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, priv->targets->len); for (guint i = 0; i < priv->targets->len; i++) { FuDfuTarget *target; /* upload to target and proxy signals */ target = g_ptr_array_index(priv->targets, i); /* ignore some target types */ if (g_strcmp0(fu_device_get_name(FU_DEVICE(target)), "Option Bytes") == 0) { g_debug("ignoring target %s", fu_device_get_name(FU_DEVICE(target))); continue; } if (!fu_dfu_target_upload(target, firmware, fu_progress_get_child(progress), DFU_TARGET_TRANSFER_FLAG_NONE, error)) return NULL; fu_progress_step_done(progress); } /* do not do the dummy upload for quirked devices */ priv->done_upload_or_download = TRUE; /* success */ return g_object_ref(firmware); } static gboolean fu_dfu_device_id_compatible(guint16 id_file, guint16 id_runtime, guint16 id_dev) { /* file doesn't specify */ if (id_file == 0xffff) return TRUE; /* runtime matches */ if (id_runtime != 0xffff && id_file == id_runtime) return TRUE; /* bootloader matches */ if (id_dev != 0xffff && id_file == id_dev) return TRUE; /* nothing */ return FALSE; } static gsize fu_dfu_device_calculate_chunks_size(GPtrArray *chunks) { gsize total = 0; for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); total += fu_chunk_get_data_sz(chk); } return total; } static gboolean fu_dfu_device_download(FuDfuDevice *self, FuFirmware *firmware, FuProgress *progress, FuDfuTargetTransferFlags flags, GError **error) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); gboolean ret; g_autoptr(GPtrArray) images = NULL; guint16 firmware_pid = 0xffff; guint16 firmware_vid = 0xffff; /* ensure interface is claimed */ if (!fu_dfu_device_ensure_interface(self, error)) return FALSE; /* firmware supports footer? */ if (FU_IS_DFU_FIRMWARE(firmware)) { firmware_vid = fu_dfu_firmware_get_vid(FU_DFU_FIRMWARE(firmware)); firmware_pid = fu_dfu_firmware_get_pid(FU_DFU_FIRMWARE(firmware)); } else { flags |= DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID; flags |= DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID; } /* do we allow wildcard VID:PID matches */ if ((flags & DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID) == 0) { if (firmware_vid == 0xffff) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware vendor ID not specified"); return FALSE; } } if ((flags & DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID) == 0) { if (firmware_pid == 0xffff) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware product ID not specified"); return FALSE; } } /* check vendor matches */ if (priv->runtime_vid != 0xffff) { if (!fu_dfu_device_id_compatible(firmware_vid, priv->runtime_vid, fu_usb_device_get_vid(FU_USB_DEVICE(self)))) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "vendor ID incorrect, expected 0x%04x " "got 0x%04x and 0x%04x\n", firmware_vid, priv->runtime_vid, fu_usb_device_get_vid(FU_USB_DEVICE(self))); return FALSE; } } /* check product matches */ if (priv->runtime_pid != 0xffff) { if (!fu_dfu_device_id_compatible(firmware_pid, priv->runtime_pid, fu_usb_device_get_pid(FU_USB_DEVICE(self)))) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "product ID incorrect, expected 0x%04x " "got 0x%04x and 0x%04x", firmware_pid, priv->runtime_pid, fu_usb_device_get_pid(FU_USB_DEVICE(self))); return FALSE; } } /* download each target */ images = fu_firmware_get_images(firmware); if (images->len == 0) g_ptr_array_add(images, g_object_ref(firmware)); fu_progress_set_id(progress, G_STRLOC); for (guint i = 0; i < images->len; i++) { FuFirmware *image = g_ptr_array_index(images, i); g_autoptr(GPtrArray) chunks = fu_firmware_get_chunks(image, error); if (chunks == NULL) return FALSE; fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, fu_dfu_device_calculate_chunks_size(chunks), NULL); } for (guint i = 0; i < images->len; i++) { FuFirmware *image = g_ptr_array_index(images, i); FuDfuTargetTransferFlags flags_local = DFU_TARGET_TRANSFER_FLAG_NONE; guint8 alt; g_autoptr(FuDfuTarget) target_tmp = NULL; alt = fu_firmware_get_idx(image); target_tmp = fu_dfu_device_get_target_by_alt_setting(self, alt, error); if (target_tmp == NULL) return FALSE; if (!fu_dfu_target_setup(target_tmp, error)) return FALSE; g_debug("downloading to target: %s", fu_device_get_logical_id(FU_DEVICE(target_tmp))); /* download onto target */ if (flags & DFU_TARGET_TRANSFER_FLAG_VERIFY) flags_local = DFU_TARGET_TRANSFER_FLAG_VERIFY; if (!FU_IS_DFU_FIRMWARE(firmware) || fu_dfu_firmware_get_version(FU_DFU_FIRMWARE(firmware)) == 0x0) flags_local |= DFU_TARGET_TRANSFER_FLAG_ADDR_HEURISTIC; ret = fu_dfu_target_download(target_tmp, image, fu_progress_get_child(progress), flags_local, error); if (!ret) return FALSE; fu_progress_step_done(progress); } /* do not do the dummy upload for quirked devices */ priv->done_upload_or_download = TRUE; /* success */ return TRUE; } void fu_dfu_device_error_fixup(FuDfuDevice *self, GError **error) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); /* sad panda */ if (error == NULL) return; /* not the right error to query */ if (!g_error_matches(*error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NOT_SUPPORTED)) return; /* get the status */ if (!fu_dfu_device_refresh(self, 0, NULL)) return; /* not in an error state */ if (priv->state != FU_DFU_STATE_DFU_ERROR) return; /* prefix the error */ switch (priv->status) { case FU_DFU_STATUS_OK: /* ignore */ break; case FU_DFU_STATUS_ERR_VENDOR: g_prefix_error(error, "read protection is active: "); break; default: g_prefix_error(error, "[%s,%s]: ", fu_dfu_state_to_string(priv->state), fu_dfu_status_to_string(priv->status)); break; } } static GBytes * fu_dfu_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuDfuDevice *self = FU_DFU_DEVICE(device); g_autoptr(FuFirmware) firmware = NULL; /* get data from hardware */ g_debug("uploading from device->host"); if (!fu_dfu_device_refresh_and_clear(self, error)) return NULL; firmware = fu_dfu_device_upload(self, progress, DFU_TARGET_TRANSFER_FLAG_NONE, error); if (firmware == NULL) return NULL; /* get the checksum */ return fu_firmware_write(firmware, error); } static FuFirmware * fu_dfu_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { return fu_firmware_new_from_gtypes(fw, 0x0, flags, error, FU_TYPE_IHEX_FIRMWARE, FU_TYPE_DFUSE_FIRMWARE, FU_TYPE_DFU_FIRMWARE, FU_TYPE_FIRMWARE, G_TYPE_INVALID); } static gboolean fu_dfu_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDfuDevice *self = FU_DFU_DEVICE(device); FuDfuTargetTransferFlags transfer_flags = DFU_TARGET_TRANSFER_FLAG_VERIFY; /* open it */ if (!fu_dfu_device_refresh_and_clear(self, error)) return FALSE; if (flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) { transfer_flags |= DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID; transfer_flags |= DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID; } /* hit hardware */ return fu_dfu_device_download(self, firmware, progress, transfer_flags, error); } static gboolean fu_dfu_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuDfuDevice *self = FU_DFU_DEVICE(device); FuDfuDevicePrivate *priv = GET_PRIVATE(self); guint64 tmp = 0; if (g_strcmp0(key, FU_QUIRKS_DFU_FORCE_VERSION) == 0) { if (!fu_strtoull(value, &tmp, 0x0, G_MAXUINT16, error)) return FALSE; priv->force_version = tmp; return TRUE; } if (g_strcmp0(key, "DfuForceTimeout") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT, error)) return FALSE; priv->timeout_ms = tmp; return TRUE; } if (g_strcmp0(key, "DfuForceTransferSize") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; priv->force_transfer_size = tmp; return TRUE; } if (g_strcmp0(key, "DfuAltName") == 0) { fu_dfu_device_set_chip_id(self, value); return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_dfu_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 88, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, "reload"); } static void fu_dfu_device_finalize(GObject *object) { FuDfuDevice *self = FU_DFU_DEVICE(object); FuDfuDevicePrivate *priv = GET_PRIVATE(self); g_free(priv->chip_id); g_ptr_array_unref(priv->targets); G_OBJECT_CLASS(fu_dfu_device_parent_class)->finalize(object); } static void fu_dfu_device_class_init(FuDfuDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->set_quirk_kv = fu_dfu_device_set_quirk_kv; klass_device->to_string = fu_dfu_device_to_string; klass_device->dump_firmware = fu_dfu_device_dump_firmware; klass_device->write_firmware = fu_dfu_device_write_firmware; klass_device->prepare_firmware = fu_dfu_device_prepare_firmware; klass_device->attach = fu_dfu_device_attach; klass_device->detach = fu_dfu_device_detach; klass_device->reload = fu_dfu_device_reload; klass_device->open = fu_dfu_device_open; klass_device->close = fu_dfu_device_close; klass_device->probe = fu_dfu_device_probe; klass_device->set_progress = fu_dfu_device_set_progress; object_class->finalize = fu_dfu_device_finalize; } static void fu_dfu_device_init(FuDfuDevice *self) { FuDfuDevicePrivate *priv = GET_PRIVATE(self); priv->iface_number = 0xff; priv->runtime_pid = 0xffff; priv->runtime_vid = 0xffff; priv->runtime_release = 0xffff; priv->state = FU_DFU_STATE_APP_IDLE; priv->status = FU_DFU_STATUS_OK; priv->targets = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); priv->timeout_ms = 1500; priv->transfer_size = 64; priv->force_version = G_MAXUINT16; priv->dnload_timeout = DFU_DEVICE_DNLOAD_TIMEOUT_DEFAULT; fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD, "can-download"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_CAN_UPLOAD, "can-upload"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_MANIFEST_TOL, "manifest-tol"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_WILL_DETACH, "will-detach"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_CAN_ACCELERATE, "can-accelerate"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_ATTACH_UPLOAD_DOWNLOAD, "attach-upload-download"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_FORCE_DFU_MODE, "force-dfu-mode"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_IGNORE_POLLTIMEOUT, "ignore-polltimeout"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_IGNORE_RUNTIME, "ignore-runtime"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_IGNORE_UPLOAD, "ignore-upload"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_DFU_RUNTIME, "no-dfu-runtime"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_GET_STATUS_UPLOAD, "no-get-status-upload"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_PID_CHANGE, "no-pid-change"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_USE_ANY_INTERFACE, "use-any-interface"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_USE_ATMEL_AVR, "use-atmel-avr"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_USE_PROTOCOL_ZERO, "use-protocol-zero"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_LEGACY_PROTOCOL, "legacy-protocol"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_DETACH_FOR_ATTACH, "detach-for-attach"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_ABSENT_SECTOR_SIZE, "absent-sector-size"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_MANIFEST_POLL, "manifest-poll"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_NO_BUS_RESET_ATTACH, "no-bus-reset-attach"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_GD32, "gd32"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_ALLOW_ZERO_POLLTIMEOUT, "allow-zero-polltimeout"); fu_device_register_private_flag(FU_DEVICE(self), FU_DFU_DEVICE_FLAG_INDEX_FORCE_DETACH, "index-force-detach"); } fwupd-1.9.16/plugins/dfu/fu-dfu-device.h000066400000000000000000000037141460375044200200020ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-dfu-common.h" #include "fu-dfu-struct.h" #include "fu-dfu-target.h" #define FU_TYPE_DFU_DEVICE (fu_dfu_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuDfuDevice, fu_dfu_device, FU, DFU_DEVICE, FuUsbDevice) struct _FuDfuDeviceClass { FuUsbDeviceClass parent_class; }; FuDfuDevice * fu_dfu_device_new(FuContext *ctx, GUsbDevice *usb_device); FuDfuTarget * fu_dfu_device_get_target_by_alt_setting(FuDfuDevice *self, guint8 alt_setting, GError **error); FuDfuTarget * fu_dfu_device_get_target_by_alt_name(FuDfuDevice *self, const gchar *alt_name, GError **error); const gchar * fu_dfu_device_get_chip_id(FuDfuDevice *self); guint16 fu_dfu_device_get_runtime_vid(FuDfuDevice *self); guint16 fu_dfu_device_get_runtime_pid(FuDfuDevice *self); gboolean fu_dfu_device_reset(FuDfuDevice *self, FuProgress *progress, GError **error); FuFirmware * fu_dfu_device_upload(FuDfuDevice *self, FuProgress *progress, FuDfuTargetTransferFlags flags, GError **error); gboolean fu_dfu_device_refresh(FuDfuDevice *self, guint timeout_ms, GError **error); gboolean fu_dfu_device_refresh_and_clear(FuDfuDevice *self, GError **error); gboolean fu_dfu_device_abort(FuDfuDevice *self, GError **error); guint8 fu_dfu_device_get_interface(FuDfuDevice *self); FuDfuState fu_dfu_device_get_state(FuDfuDevice *self); FuDfuStatus fu_dfu_device_get_status(FuDfuDevice *self); guint16 fu_dfu_device_get_transfer_size(FuDfuDevice *self); guint16 fu_dfu_device_get_version(FuDfuDevice *self); guint fu_dfu_device_get_timeout(FuDfuDevice *self); void fu_dfu_device_set_transfer_size(FuDfuDevice *self, guint16 transfer_size); void fu_dfu_device_error_fixup(FuDfuDevice *self, GError **error); guint fu_dfu_device_get_download_timeout(FuDfuDevice *self); gboolean fu_dfu_device_ensure_interface(FuDfuDevice *self, GError **error); fwupd-1.9.16/plugins/dfu/fu-dfu-plugin.c000066400000000000000000000015531460375044200200330ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-dfu-device.h" #include "fu-dfu-plugin.h" struct _FuDfuPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuDfuPlugin, fu_dfu_plugin, FU_TYPE_PLUGIN) static void fu_dfu_plugin_init(FuDfuPlugin *self) { } static void fu_dfu_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "DfuAltName"); fu_context_add_quirk_key(ctx, "DfuForceTimeout"); fu_context_add_quirk_key(ctx, "DfuForceVersion"); fu_plugin_add_device_gtype(plugin, FU_TYPE_DFU_DEVICE); } static void fu_dfu_plugin_class_init(FuDfuPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_dfu_plugin_constructed; } fwupd-1.9.16/plugins/dfu/fu-dfu-plugin.h000066400000000000000000000003371460375044200200370ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuDfuPlugin, fu_dfu_plugin, FU, DFU_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/dfu/fu-dfu-sector.c000066400000000000000000000056761460375044200200460ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * This object represents an sector of memory at a specific address on the * device itself. * * This allows relocatable data segments to be stored in different * locations on the device itself. * * You can think of these objects as flash segments on devices, where a * complete block can be erased and then written to. */ #include "config.h" #include #include #include "fu-dfu-common.h" #include "fu-dfu-sector.h" typedef struct { guint32 address; guint32 size; guint32 size_left; guint16 zone; guint16 number; FuDfuSectorCap cap; } FuDfuSectorPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuDfuSector, fu_dfu_sector, G_TYPE_OBJECT) #define GET_PRIVATE(o) (fu_dfu_sector_get_instance_private(o)) static void fu_dfu_sector_class_init(FuDfuSectorClass *klass) { } static void fu_dfu_sector_init(FuDfuSector *self) { } FuDfuSector * fu_dfu_sector_new(guint32 address, guint32 size, guint32 size_left, guint16 zone, guint16 number, FuDfuSectorCap cap) { FuDfuSectorPrivate *priv; FuDfuSector *self; self = g_object_new(FU_TYPE_DFU_SECTOR, NULL); priv = GET_PRIVATE(self); priv->address = address; priv->size = size; priv->size_left = size_left; priv->zone = zone; priv->number = number; priv->cap = cap; return self; } guint32 fu_dfu_sector_get_address(FuDfuSector *self) { FuDfuSectorPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_SECTOR(self), 0x00); return priv->address; } guint32 fu_dfu_sector_get_size(FuDfuSector *self) { FuDfuSectorPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_SECTOR(self), 0x00); return priv->size; } guint16 fu_dfu_sector_get_zone(FuDfuSector *self) { FuDfuSectorPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_SECTOR(self), 0x00); return priv->zone; } /* use this number to check if the segment is the 'same' as the last * written or read sector */ guint32 fu_dfu_sector_get_id(FuDfuSector *self) { FuDfuSectorPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_SECTOR(self), 0x00); return (((guint32)priv->zone) << 16) | priv->number; } gboolean fu_dfu_sector_has_cap(FuDfuSector *self, FuDfuSectorCap cap) { FuDfuSectorPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_SECTOR(self), FALSE); return (priv->cap & cap) > 0; } gchar * fu_dfu_sector_to_string(FuDfuSector *self) { FuDfuSectorPrivate *priv = GET_PRIVATE(self); GString *str; g_autofree gchar *caps_str = NULL; g_return_val_if_fail(FU_IS_DFU_SECTOR(self), NULL); str = g_string_new(""); caps_str = fu_dfu_sector_cap_to_string(priv->cap); g_string_append_printf(str, "Zone:%i, Sec#:%i, Addr:0x%08x, " "Size:0x%04x, Caps:0x%01x [%s]", priv->zone, priv->number, priv->address, priv->size, priv->cap, caps_str); return g_string_free(str, FALSE); } fwupd-1.9.16/plugins/dfu/fu-dfu-sector.h000066400000000000000000000016051460375044200200370ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-dfu-struct.h" #define FU_TYPE_DFU_SECTOR (fu_dfu_sector_get_type()) G_DECLARE_DERIVABLE_TYPE(FuDfuSector, fu_dfu_sector, FU, DFU_SECTOR, GObject) struct _FuDfuSectorClass { GObjectClass parent_class; }; FuDfuSector * fu_dfu_sector_new(guint32 address, guint32 size, guint32 size_left, guint16 zone, guint16 number, FuDfuSectorCap cap); guint32 fu_dfu_sector_get_id(FuDfuSector *self); guint32 fu_dfu_sector_get_address(FuDfuSector *self); guint32 fu_dfu_sector_get_size(FuDfuSector *self); guint32 fu_dfu_sector_get_size_left(FuDfuSector *self); guint16 fu_dfu_sector_get_zone(FuDfuSector *self); gboolean fu_dfu_sector_has_cap(FuDfuSector *self, FuDfuSectorCap cap); gchar * fu_dfu_sector_to_string(FuDfuSector *self); fwupd-1.9.16/plugins/dfu/fu-dfu-self-test.c000066400000000000000000000125061460375044200204430ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-context-private.h" #include "fu-dfu-device.h" #include "fu-dfu-sector.h" #include "fu-dfu-target-private.h" static gboolean fu_test_compare_lines(const gchar *txt1, const gchar *txt2, GError **error) { g_autofree gchar *output = NULL; if (g_strcmp0(txt1, txt2) == 0) return TRUE; if (g_pattern_match_simple(txt2, txt1)) return TRUE; if (!g_file_set_contents("/tmp/a", txt1, -1, error)) return FALSE; if (!g_file_set_contents("/tmp/b", txt2, -1, error)) return FALSE; if (!g_spawn_command_line_sync("diff -urNp /tmp/b /tmp/a", &output, NULL, NULL, error)) return FALSE; g_set_error_literal(error, 1, 0, output); return FALSE; } static gchar * fu_dfu_target_sectors_to_string(FuDfuTarget *target) { GPtrArray *sectors; GString *str; str = g_string_new(""); sectors = fu_dfu_target_get_sectors(target); for (guint i = 0; i < sectors->len; i++) { FuDfuSector *sector = g_ptr_array_index(sectors, i); g_autofree gchar *tmp = fu_dfu_sector_to_string(sector); g_string_append_printf(str, "%s\n", tmp); } if (str->len > 0) g_string_truncate(str, str->len - 1); return g_string_free(str, FALSE); } static void fu_dfu_target_dfuse_func(void) { gboolean ret; gchar *tmp; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuDfuDevice) device = fu_dfu_device_new(ctx, NULL); g_autoptr(FuDfuTarget) target = NULL; g_autoptr(GError) error = NULL; /* NULL */ target = g_object_new(FU_TYPE_DFU_TARGET, NULL); fu_device_set_proxy(FU_DEVICE(target), FU_DEVICE(device)); ret = fu_dfu_target_parse_sectors(target, NULL, &error); g_assert_no_error(error); g_assert_true(ret); tmp = fu_dfu_target_sectors_to_string(target); g_assert_cmpstr(tmp, ==, ""); g_free(tmp); /* no addresses */ ret = fu_dfu_target_parse_sectors(target, "@Flash3", &error); g_assert_no_error(error); g_assert_true(ret); tmp = fu_dfu_target_sectors_to_string(target); g_assert_cmpstr(tmp, ==, ""); g_free(tmp); /* one sector, no space */ ret = fu_dfu_target_parse_sectors(target, "@Internal Flash /0x08000000/2*001Ka", &error); g_assert_no_error(error); g_assert_true(ret); tmp = fu_dfu_target_sectors_to_string(target); ret = fu_test_compare_lines( tmp, "Zone:0, Sec#:0, Addr:0x08000000, Size:0x0400, Caps:0x1 [readable]\n" "Zone:0, Sec#:0, Addr:0x08000400, Size:0x0400, Caps:0x1 [readable]", &error); g_assert_no_error(error); g_assert_true(ret); g_free(tmp); /* multiple sectors */ ret = fu_dfu_target_parse_sectors(target, "@Flash1 /0x08000000/2*001Ka,4*001Kg", &error); g_assert_no_error(error); g_assert_true(ret); tmp = fu_dfu_target_sectors_to_string(target); ret = fu_test_compare_lines( tmp, "Zone:0, Sec#:0, Addr:0x08000000, Size:0x0400, Caps:0x1 [readable]\n" "Zone:0, Sec#:0, Addr:0x08000400, Size:0x0400, Caps:0x1 [readable]\n" "Zone:0, Sec#:1, Addr:0x08000800, Size:0x0400, Caps:0x7 [readable,writeable,erasable]\n" "Zone:0, Sec#:1, Addr:0x08000c00, Size:0x0400, Caps:0x7 [readable,writeable,erasable]\n" "Zone:0, Sec#:1, Addr:0x08001000, Size:0x0400, Caps:0x7 [readable,writeable,erasable]\n" "Zone:0, Sec#:1, Addr:0x08001400, Size:0x0400, Caps:0x7 [readable,writeable,erasable]", &error); g_assert_no_error(error); g_assert_true(ret); g_free(tmp); /* non-contiguous */ ret = fu_dfu_target_parse_sectors(target, "@Flash2 /0xF000/4*100Ba/0xE000/3*8Kg/0x80000/2*24Kg", &error); g_assert_no_error(error); g_assert_true(ret); tmp = fu_dfu_target_sectors_to_string(target); ret = fu_test_compare_lines( tmp, "Zone:0, Sec#:0, Addr:0x0000f000, Size:0x0064, Caps:0x1 [readable]\n" "Zone:0, Sec#:0, Addr:0x0000f064, Size:0x0064, Caps:0x1 [readable]\n" "Zone:0, Sec#:0, Addr:0x0000f0c8, Size:0x0064, Caps:0x1 [readable]\n" "Zone:0, Sec#:0, Addr:0x0000f12c, Size:0x0064, Caps:0x1 [readable]\n" "Zone:1, Sec#:0, Addr:0x0000e000, Size:0x2000, Caps:0x7 [readable,writeable,erasable]\n" "Zone:1, Sec#:0, Addr:0x00010000, Size:0x2000, Caps:0x7 [readable,writeable,erasable]\n" "Zone:1, Sec#:0, Addr:0x00012000, Size:0x2000, Caps:0x7 [readable,writeable,erasable]\n" "Zone:2, Sec#:0, Addr:0x00080000, Size:0x6000, Caps:0x7 [readable,writeable,erasable]\n" "Zone:2, Sec#:0, Addr:0x00086000, Size:0x6000, Caps:0x7 [readable,writeable,erasable]", &error); g_assert_no_error(error); g_assert_true(ret); g_free(tmp); /* invalid */ ret = fu_dfu_target_parse_sectors(target, "Flash", NULL); g_assert_true(ret); ret = fu_dfu_target_parse_sectors(target, "@Internal Flash /0x08000000", NULL); g_assert_false(ret); ret = fu_dfu_target_parse_sectors(target, "@Internal Flash /0x08000000/12*001a", NULL); g_assert_false(ret); } int main(int argc, char **argv) { g_autofree gchar *testdatadir = NULL; (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); (void)g_setenv("G_MESSAGES_DEBUG", "all", FALSE); /* environment */ testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", testdatadir, TRUE); /* tests go here */ g_test_add_func("/dfu/target(DfuSe}", fu_dfu_target_dfuse_func); return g_test_run(); } fwupd-1.9.16/plugins/dfu/fu-dfu-target-avr.c000066400000000000000000000672701460375044200206210ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-dfu-common.h" #include "fu-dfu-device.h" #include "fu-dfu-sector.h" #include "fu-dfu-target-avr.h" #include "fu-dfu-target-private.h" /* waive-pre-commit */ /** * FU_QUIRKS_DFU_AVR_ALT_NAME: * @key: the AVR chip ID, e.g. `0x58200204` * @value: the UM0424 sector description, e.g. `@Flash/0x2000/1*248Kg` * * Assigns a sector description for the chip ID. This is required so fwupd can * program the user firmware avoiding the bootloader and for checking the total * chunk size. * * The chip ID can be found from a datasheet or using `dfu-tool list` when the * hardware is connected and in bootloader mode. * * Since: 1.0.1 */ #define FU_QUIRKS_DFU_AVR_ALT_NAME "DfuAltName" typedef struct { guint32 device_id; } FuDfuTargetAvrPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuDfuTargetAvr, fu_dfu_target_avr, FU_TYPE_DFU_TARGET) #define GET_PRIVATE(o) (fu_dfu_target_avr_get_instance_private(o)) /* ATMEL AVR version of DFU: * http://www.atmel.com/Images/doc7618.pdf */ #define DFU_AVR_CMD_PROG_START 0x01 #define DFU_AVR_CMD_DISPLAY_DATA 0x03 #define DFU_AVR_CMD_WRITE_COMMAND 0x04 #define DFU_AVR_CMD_READ_COMMAND 0x05 #define DFU_AVR_CMD_CHANGE_BASE_ADDR 0x06 /* Atmel AVR32 version of DFU: * http://www.atmel.com/images/doc32131.pdf */ #define DFU_AVR32_GROUP_SELECT 0x06 /** SELECT */ #define DFU_AVR32_CMD_SELECT_MEMORY 0x03 #define DFU_AVR32_MEMORY_UNIT 0x00 #define DFU_AVR32_MEMORY_PAGE 0x01 #define DFU_AVR32_MEMORY_UNIT_FLASH 0x00 #define DFU_AVR32_MEMORY_UNIT_EEPROM 0x01 #define DFU_AVR32_MEMORY_UNIT_SECURITY 0x02 #define DFU_AVR32_MEMORY_UNIT_CONFIGURATION 0x03 #define DFU_AVR32_MEMORY_UNIT_BOOTLOADER 0x04 #define DFU_AVR32_MEMORY_UNIT_SIGNATURE 0x05 #define DFU_AVR32_MEMORY_UNIT_USER 0x06 #define DFU_AVR32_GROUP_DOWNLOAD 0x01 /** DOWNLOAD */ #define DFU_AVR32_CMD_PROGRAM_START 0x00 #define DFU_AVR32_GROUP_UPLOAD 0x03 /** UPLOAD */ #define DFU_AVR32_CMD_READ_MEMORY 0x00 #define DFU_AVR32_CMD_BLANK_CHECK 0x01 #define DFU_AVR32_GROUP_EXEC 0x04 /** EXEC */ #define DFU_AVR32_CMD_ERASE 0x00 #define DFU_AVR32_ERASE_EVERYTHING 0xff #define DFU_AVR32_CMD_START_APPLI 0x03 #define DFU_AVR32_START_APPLI_RESET 0x00 #define DFU_AVR32_START_APPLI_NO_RESET 0x01 #define ATMEL_64KB_PAGE 0x10000 #define ATMEL_MAX_TRANSFER_SIZE 0x0400 #define ATMEL_AVR_CONTROL_BLOCK_SIZE 32 #define ATMEL_AVR32_CONTROL_BLOCK_SIZE 64 #define ATMEL_MANUFACTURER_CODE1 0x58 #define ATMEL_MANUFACTURER_CODE2 0x1e static gboolean fu_dfu_target_avr_mass_erase(FuDfuTarget *target, FuProgress *progress, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); /* format buffer */ fu_byte_array_append_uint8(buf, DFU_AVR32_GROUP_EXEC); fu_byte_array_append_uint8(buf, DFU_AVR32_CMD_ERASE); fu_byte_array_append_uint8(buf, 0xFF); if (!fu_dfu_target_download_chunk(target, 0, buf, 5000, progress, error)) { g_prefix_error(error, "cannot mass-erase: "); return FALSE; } return TRUE; } static gboolean fu_dfu_target_avr_attach(FuDfuTarget *target, FuProgress *progress, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) buf_empty = g_byte_array_new(); g_autoptr(GError) error_local = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 50, "download-chunk"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 50, "download-zero"); /* format buffer */ fu_byte_array_append_uint8(buf, DFU_AVR32_GROUP_EXEC); fu_byte_array_append_uint8(buf, DFU_AVR32_CMD_START_APPLI); fu_byte_array_append_uint8(buf, DFU_AVR32_START_APPLI_RESET); if (!fu_dfu_target_download_chunk(target, 0, buf, 0, /* timeout default */ fu_progress_get_child(progress), &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_debug("ignoring as device rebooting: %s", error_local->message); fu_progress_finished(progress); return TRUE; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "cannot start application reset attach: "); return FALSE; } fu_progress_step_done(progress); /* do zero-sized download to initiate the reset */ if (!fu_dfu_target_download_chunk(target, 0, buf_empty, 0, /* timeout default */ fu_progress_get_child(progress), &error_local)) { if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "cannot initiate reset for attach: "); return FALSE; } g_debug("ignoring as device rebooting: %s", error_local->message); } fu_progress_step_done(progress); /* success */ return TRUE; } /** * fu_dfu_target_avr_select_memory_unit: * @target: a #FuDfuTarget * @memory_unit: a unit, e.g. %DFU_AVR32_MEMORY_UNIT_FLASH * @error: (nullable): optional return location for an error * * Selects the memory unit for the device. * * Returns: %TRUE for success **/ static gboolean fu_dfu_target_avr_select_memory_unit(FuDfuTarget *target, guint8 memory_unit, FuProgress *progress, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); /* check legacy protocol quirk */ if (fu_device_has_private_flag(fu_device_get_proxy(FU_DEVICE(target)), FU_DFU_DEVICE_FLAG_LEGACY_PROTOCOL)) { g_debug("ignoring select memory unit as legacy protocol"); return TRUE; } /* format buffer */ fu_byte_array_append_uint8(buf, DFU_AVR32_GROUP_SELECT); fu_byte_array_append_uint8(buf, DFU_AVR32_CMD_SELECT_MEMORY); fu_byte_array_append_uint8(buf, DFU_AVR32_MEMORY_UNIT); fu_byte_array_append_uint8(buf, memory_unit); g_debug("selecting memory unit 0x%02x", (guint)memory_unit); if (!fu_dfu_target_download_chunk(target, 0, buf, 0, progress, error)) { g_prefix_error(error, "cannot select memory unit: "); return FALSE; } return TRUE; } /** * fu_dfu_target_avr_select_memory_page: * @target: a #FuDfuTarget * @memory_page: an address * @error: (nullable): optional return location for an error * * Selects the memory page for the AVR device. * * Returns: %TRUE for success **/ static gboolean fu_dfu_target_avr_select_memory_page(FuDfuTarget *target, guint16 memory_page, FuProgress *progress, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); /* check page not too large for protocol */ if (memory_page > 0xff) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot select memory page:0x%02x " "with FLIP protocol version 1", memory_page); return FALSE; } /* format buffer */ fu_byte_array_append_uint8(buf, DFU_AVR_CMD_CHANGE_BASE_ADDR); fu_byte_array_append_uint8(buf, 0x03); fu_byte_array_append_uint8(buf, 0x00); fu_byte_array_append_uint8(buf, memory_page & 0xFF); g_debug("selecting memory page 0x%01x", (guint)memory_page); if (!fu_dfu_target_download_chunk(target, 0, buf, 0, progress, error)) { g_prefix_error(error, "cannot select memory page: "); return FALSE; } return TRUE; } /** * fu_dfu_target_avr32_select_memory_page: * @target: a #FuDfuTarget * @memory_page: an address * @error: (nullable): optional return location for an error * * Selects the memory page for the AVR32 device. * * Returns: %TRUE for success **/ static gboolean fu_dfu_target_avr32_select_memory_page(FuDfuTarget *target, guint16 memory_page, FuProgress *progress, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); /* format buffer */ fu_byte_array_append_uint8(buf, DFU_AVR32_GROUP_SELECT); fu_byte_array_append_uint8(buf, DFU_AVR32_CMD_SELECT_MEMORY); fu_byte_array_append_uint8(buf, DFU_AVR32_MEMORY_PAGE); fu_byte_array_append_uint16(buf, memory_page, G_BIG_ENDIAN); g_debug("selecting memory page 0x%02x", (guint)memory_page); if (!fu_dfu_target_download_chunk(target, 0, buf, 0, progress, error)) { g_prefix_error(error, "cannot select memory page: "); return FALSE; } return TRUE; } /** * fu_dfu_target_avr_read_memory * @target: a #FuDfuTarget * @addr_start: an address * @addr_end: an address * @error: (nullable): optional return location for an error * * Reads flash data from the device. * * Returns: %TRUE for success **/ static gboolean fu_dfu_target_avr_read_memory(FuDfuTarget *target, guint16 addr_start, guint16 addr_end, FuProgress *progress, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); /* format buffer */ fu_byte_array_append_uint8(buf, DFU_AVR32_GROUP_UPLOAD); fu_byte_array_append_uint8(buf, DFU_AVR32_CMD_READ_MEMORY); fu_byte_array_append_uint16(buf, addr_start, G_BIG_ENDIAN); fu_byte_array_append_uint16(buf, addr_end, G_BIG_ENDIAN); g_debug("reading memory from 0x%04x to 0x%04x", (guint)addr_start, (guint)addr_end); if (!fu_dfu_target_download_chunk(target, 0, buf, 0, progress, error)) { g_prefix_error(error, "cannot read memory 0x%04x to 0x%04x: ", (guint)addr_start, (guint)addr_end); return FALSE; } return TRUE; } /** * fu_dfu_target_avr_read_command: * @target: a #FuDfuTarget * @memory_unit: a unit, e.g. %DFU_AVR32_MEMORY_UNIT_FLASH * @error: (nullable): optional return location for an error * * Performs a read operation on the device. * * Returns: %TRUE for success **/ static gboolean fu_dfu_target_avr_read_command(FuDfuTarget *target, guint8 page, guint8 addr, FuProgress *progress, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); /* format buffer */ fu_byte_array_append_uint8(buf, DFU_AVR_CMD_READ_COMMAND); fu_byte_array_append_uint8(buf, page); fu_byte_array_append_uint8(buf, addr); g_debug("read command page:0x%02x addr:0x%02x", (guint)page, (guint)addr); if (!fu_dfu_target_download_chunk(target, 0, buf, 0, progress, error)) { g_prefix_error(error, "cannot read command page: "); return FALSE; } return TRUE; } /** * fu_dfu_target_avr32_get_chip_signature: * @target: a #FuDfuTarget * @error: (nullable): optional return location for an error * * Gets the chip signature for the AVR32 device. * * Returns: a 4-byte %GBytes object for success, else %NULL **/ static GBytes * fu_dfu_target_avr32_get_chip_signature(FuDfuTarget *target, FuProgress *progress, GError **error) { g_autoptr(GBytes) buf = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 25, "mem-unit"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 25, "mem-page"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 25, "read-memory"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 25, "upload"); /* select unit, and request 4 bytes */ if (!fu_dfu_target_avr_select_memory_unit(target, DFU_AVR32_MEMORY_UNIT_SIGNATURE, fu_progress_get_child(progress), error)) return NULL; fu_progress_step_done(progress); if (!fu_dfu_target_avr32_select_memory_page(target, 0x00, fu_progress_get_child(progress), error)) return NULL; fu_progress_step_done(progress); if (!fu_dfu_target_avr_read_memory(target, 0x00, 0x03, fu_progress_get_child(progress), error)) return NULL; fu_progress_step_done(progress); /* get data back */ buf = fu_dfu_target_upload_chunk(target, 0x00, 0, fu_progress_get_child(progress), error); if (buf == NULL) return NULL; fu_progress_step_done(progress); /* success */ return g_steal_pointer(&buf); } static GBytes * fu_dfu_target_avr_get_chip_signature_for_addr(FuDfuTarget *target, guint8 page, guint addr, FuProgress *progress, GError **error) { g_autoptr(GBytes) buf = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 10, "req"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 90, "res"); /* request a single byte */ if (!fu_dfu_target_avr_read_command(target, page, addr, fu_progress_get_child(progress), error)) return NULL; fu_progress_step_done(progress); /* get data back */ buf = fu_dfu_target_upload_chunk(target, 0x00, 0x01, progress, error); if (buf == NULL) return NULL; if (g_bytes_get_size(buf) != 1) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot read signature memory page:0x%02x " "addr:0x%02x, got 0x%02x bytes", (guint)page, (guint)addr, (guint)g_bytes_get_size(buf)); return NULL; } fu_progress_step_done(progress); /* success */ return g_steal_pointer(&buf); } /** * fu_dfu_target_avr_get_chip_signature: * @target: a #FuDfuTarget * @error: (nullable): optional return location for an error * * Gets the chip signature for the AVR device. * * Returns: a 4-byte %GBytes object for success, else %NULL **/ static GBytes * fu_dfu_target_avr_get_chip_signature(FuDfuTarget *target, FuProgress *progress, GError **error) { struct { guint8 page; guint addr; } signature_locations[] = {{0x01, 0x30}, {0x01, 0x31}, {0x01, 0x60}, {0x01, 0x61}, {0xff, 0xff}}; g_autoptr(GPtrArray) chunks = NULL; /* we have to request this one byte at a time */ chunks = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, G_N_ELEMENTS(signature_locations)); for (guint i = 0; signature_locations[i].page != 0xff; i++) { g_autoptr(GBytes) buf = NULL; buf = fu_dfu_target_avr_get_chip_signature_for_addr(target, signature_locations[i].page, signature_locations[i].addr, fu_progress_get_child(progress), error); if (buf == NULL) return NULL; g_ptr_array_add(chunks, g_steal_pointer(&buf)); fu_progress_step_done(progress); } return fu_dfu_utils_bytes_join_array(chunks); } static gboolean fu_dfu_target_avr_setup(FuDfuTarget *target, GError **error) { FuDfuDevice *device; FuDfuTargetAvr *self = FU_DFU_TARGET_AVR(target); FuDfuTargetAvrPrivate *priv = GET_PRIVATE(self); const gchar *chip_id; const guint8 *buf; gsize sz; guint32 device_id_be; g_autofree gchar *chip_id_guid = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GBytes) chunk_sig = NULL; /* already done */ if (priv->device_id > 0x0) return TRUE; /* different methods for AVR vs. AVR32 */ if (fu_device_has_private_flag(fu_device_get_proxy(FU_DEVICE(target)), FU_DFU_DEVICE_FLAG_LEGACY_PROTOCOL)) { chunk_sig = fu_dfu_target_avr_get_chip_signature(target, progress, error); if (chunk_sig == NULL) return FALSE; } else { chunk_sig = fu_dfu_target_avr32_get_chip_signature(target, progress, error); if (chunk_sig == NULL) { g_prefix_error(error, "failed to get chip signature: "); return FALSE; } } /* get data back */ buf = g_bytes_get_data(chunk_sig, &sz); fu_dump_bytes(G_LOG_DOMAIN, "AVR:CID", chunk_sig); if (sz != 4) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot read config memory, got 0x%02x bytes", (guint)sz); return FALSE; } memcpy(&device_id_be, buf, 4); priv->device_id = GINT32_FROM_BE(device_id_be); if (buf[0] == ATMEL_MANUFACTURER_CODE1) { chip_id_guid = g_strdup_printf("0x%08x", (guint)priv->device_id); } else if (buf[0] == ATMEL_MANUFACTURER_CODE2) { chip_id_guid = g_strdup_printf("0x%06x", (guint)priv->device_id >> 8); } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot read config vendor, got 0x%08x, " "expected 0x%02x or 0x%02x", (guint)priv->device_id, (guint)ATMEL_MANUFACTURER_CODE1, (guint)ATMEL_MANUFACTURER_CODE2); return FALSE; } /* set the alt-name using the chip ID via a quirk */ device = FU_DFU_DEVICE(fu_device_get_proxy(FU_DEVICE(target))); fu_device_add_instance_str(FU_DEVICE(device), "CID", chip_id_guid); if (!fu_device_build_instance_id(FU_DEVICE(device), error, "DFU_AVR", "CID", NULL)) return FALSE; chip_id = fu_dfu_device_get_chip_id(device); if (chip_id == NULL) { fu_device_remove_private_flag(fu_device_get_proxy(FU_DEVICE(target)), FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD); fu_device_remove_private_flag(fu_device_get_proxy(FU_DEVICE(target)), FU_DFU_DEVICE_FLAG_CAN_UPLOAD); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ChipID GUID %s is not supported", chip_id_guid); return FALSE; } fu_device_set_logical_id(FU_DEVICE(target), chip_id); return TRUE; } static gboolean fu_dfu_target_avr_download_element_chunks(FuDfuTarget *target, GPtrArray *chunks, guint16 *page_last, gsize header_sz, FuProgress *progress, GError **error) { const guint8 footer[] = {0x00, 0x00, 0x00, 0x00, /* CRC */ 16, /* len */ 'D', 'F', 'U', /* signature */ 0x01, 0x10, /* version */ 0xff, 0xff, /* vendor ID */ 0xff, 0xff, /* product ID */ 0xff, 0xff}; /* release */ /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) chunk_tmp = fu_chunk_get_bytes(chk); /* select page if required */ if (fu_chunk_get_page(chk) != *page_last) { g_autoptr(FuProgress) progress_tmp = fu_progress_new(G_STRLOC); if (fu_device_has_private_flag(fu_device_get_proxy(FU_DEVICE(target)), FU_DFU_DEVICE_FLAG_LEGACY_PROTOCOL)) { if (!fu_dfu_target_avr_select_memory_page(target, fu_chunk_get_page(chk), progress_tmp, error)) return FALSE; } else { if (!fu_dfu_target_avr32_select_memory_page(target, fu_chunk_get_page(chk), progress_tmp, error)) return FALSE; } *page_last = fu_chunk_get_page(chk); } /* create chunk with header and footer */ fu_byte_array_append_uint8(buf, DFU_AVR32_GROUP_DOWNLOAD); fu_byte_array_append_uint8(buf, DFU_AVR32_CMD_PROGRAM_START); fu_byte_array_append_uint16(buf, fu_chunk_get_address(chk), G_BIG_ENDIAN); fu_byte_array_append_uint16(buf, fu_chunk_get_address(chk) + fu_chunk_get_data_sz(chk) - 1, G_BIG_ENDIAN); fu_byte_array_set_size(buf, header_sz, 0x0); fu_byte_array_append_bytes(buf, chunk_tmp); g_byte_array_append(buf, footer, sizeof(footer)); /* download data */ g_debug("sending 0x%x bytes to the hardware", buf->len); if (!fu_dfu_target_download_chunk(target, i, buf, 0, /* timeout default */ fu_progress_get_child(progress), error)) return FALSE; /* update UI */ fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_dfu_target_avr_download_element(FuDfuTarget *target, FuChunk *chk, FuProgress *progress, FuDfuTargetTransferFlags flags, GError **error) { FuDfuSector *sector; const guint8 *data; gsize header_sz = ATMEL_AVR32_CONTROL_BLOCK_SIZE; guint16 page_last = G_MAXUINT16; guint32 address; guint32 address_offset = 0x0; g_autoptr(GBytes) blob = NULL; g_autoptr(GPtrArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); /* select a memory and erase everything */ if (!fu_dfu_target_avr_select_memory_unit(target, fu_dfu_target_get_alt_setting(target), progress, error)) return FALSE; if (!fu_dfu_target_avr_mass_erase(target, progress, error)) return FALSE; fu_progress_step_done(progress); /* verify the element isn't larger than the target size */ blob = fu_chunk_get_bytes(chk); sector = fu_dfu_target_get_sector_default(target); if (sector == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sector defined for target"); return FALSE; } address = fu_chunk_get_address(chk) & ~0x80000000; if (address < fu_dfu_sector_get_address(sector)) { address_offset = fu_dfu_sector_get_address(sector) - address; g_warning("firmware element starts at 0x%x but sector " "starts at 0x%x, so offsetting by 0x%x (bootloader?)", (guint)address, (guint)fu_dfu_sector_get_address(sector), (guint)address_offset); } if (g_bytes_get_size(blob) + address_offset > fu_dfu_sector_get_size(sector)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "element was larger than sector size: 0x%x", (guint)fu_dfu_sector_get_size(sector)); return FALSE; } /* the original AVR protocol uses a half-size control block */ if (fu_device_has_private_flag(fu_device_get_proxy(FU_DEVICE(target)), FU_DFU_DEVICE_FLAG_LEGACY_PROTOCOL)) { header_sz = ATMEL_AVR_CONTROL_BLOCK_SIZE; } /* chunk up the memory space into pages */ data = g_bytes_get_data(blob, NULL); chunks = fu_chunk_array_new(data + address_offset, g_bytes_get_size(blob) - address_offset, fu_dfu_sector_get_address(sector), ATMEL_64KB_PAGE, ATMEL_MAX_TRANSFER_SIZE); if (!fu_dfu_target_avr_download_element_chunks(target, chunks, &page_last, header_sz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* done */ return TRUE; } static GBytes * fu_dfu_target_avr_upload_element_chunk(FuDfuTarget *target, FuChunk *chk, FuProgress *progress, GError **error) { g_autoptr(GBytes) blob = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 70, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 30, NULL); /* prepare to read */ if (!fu_dfu_target_avr_read_memory(target, fu_chunk_get_address(chk), fu_chunk_get_address(chk) + fu_chunk_get_data_sz(chk) - 1, fu_progress_get_child(progress), error)) return NULL; fu_progress_step_done(progress); /* upload data */ g_debug("requesting %i bytes from the hardware for chunk 0x%x", ATMEL_MAX_TRANSFER_SIZE, fu_chunk_get_idx(chk)); blob = fu_dfu_target_upload_chunk(target, fu_chunk_get_idx(chk), ATMEL_MAX_TRANSFER_SIZE, fu_progress_get_child(progress), error); if (blob == NULL) return NULL; fu_progress_step_done(progress); /* success */ return g_steal_pointer(&blob); } static FuChunk * fu_dfu_target_avr_upload_element_chunks(FuDfuTarget *target, guint32 address, gsize expected_size, GPtrArray *chunks, FuProgress *progress, GError **error) { guint16 page_last = G_MAXUINT16; guint chunk_valid = G_MAXUINT; g_autoptr(FuChunk) chk2 = NULL; g_autoptr(GBytes) contents = NULL; g_autoptr(GBytes) contents_truncated = NULL; g_autoptr(GPtrArray) blobs = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); /* process each chunk */ blobs = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); for (guint i = 0; i < chunks->len; i++) { GBytes *blob_tmp = NULL; FuChunk *chk = g_ptr_array_index(chunks, i); /* select page if required */ if (fu_chunk_get_page(chk) != page_last) { g_autoptr(FuProgress) progress_tmp = fu_progress_new(G_STRLOC); if (fu_device_has_private_flag(fu_device_get_proxy(FU_DEVICE(target)), FU_DFU_DEVICE_FLAG_LEGACY_PROTOCOL)) { if (!fu_dfu_target_avr_select_memory_page(target, fu_chunk_get_page(chk), progress_tmp, error)) return NULL; } else { if (!fu_dfu_target_avr32_select_memory_page(target, fu_chunk_get_page(chk), progress_tmp, error)) return NULL; } page_last = fu_chunk_get_page(chk); } blob_tmp = fu_dfu_target_avr_upload_element_chunk(target, chk, fu_progress_get_child(progress), error); if (blob_tmp == NULL) return NULL; g_ptr_array_add(blobs, blob_tmp); /* this page has valid data */ if (!fu_bytes_is_empty(blob_tmp)) { g_debug("chunk %u has data (page %" G_GUINT32_FORMAT ")", i, fu_chunk_get_page(chk)); chunk_valid = i; } else { g_debug("chunk %u is empty", i); } /* update UI */ fu_progress_step_done(progress); } /* truncate the image if any sectors are empty, i.e. all 0xff */ if (chunk_valid == G_MAXUINT) { g_debug("all %u chunks are empty", blobs->len); g_ptr_array_set_size(chunks, 0); } else if (blobs->len != chunk_valid + 1) { g_debug("truncating chunks from %u to %u", blobs->len, chunk_valid + 1); g_ptr_array_set_size(blobs, chunk_valid + 1); } /* create element of required size */ contents = fu_dfu_utils_bytes_join_array(blobs); if (expected_size > 0 && g_bytes_get_size(contents) > expected_size) { contents_truncated = g_bytes_new_from_bytes(contents, 0x0, expected_size); } else { contents_truncated = g_bytes_ref(contents); } chk2 = fu_chunk_bytes_new(contents_truncated); fu_chunk_set_address(chk2, address | 0x80000000); /* flash */ return g_steal_pointer(&chk2); } static FuChunk * fu_dfu_target_avr_upload_element(FuDfuTarget *target, guint32 address, gsize expected_size, gsize maximum_size, FuProgress *progress, GError **error) { FuDfuSector *sector; g_autoptr(FuChunk) chk2 = NULL; g_autoptr(GPtrArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 95, NULL); /* select unit */ if (!fu_dfu_target_avr_select_memory_unit(target, fu_dfu_target_get_alt_setting(target), fu_progress_get_child(progress), error)) return NULL; fu_progress_step_done(progress); /* verify the element isn't lower than the flash area */ sector = fu_dfu_target_get_sector_default(target); if (sector == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sector defined for target"); return NULL; } if (address < fu_dfu_sector_get_address(sector)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "cannot read from below sector start"); return NULL; } /* the flash starts at 0x80000000, but is indexed from zero */ address &= ~0x80000000; /* chunk up the memory space into pages */ chunks = fu_chunk_array_new(NULL, maximum_size, address, ATMEL_64KB_PAGE, ATMEL_MAX_TRANSFER_SIZE); chk2 = fu_dfu_target_avr_upload_element_chunks(target, address, expected_size, chunks, fu_progress_get_child(progress), error); if (chk2 == NULL) return NULL; fu_progress_step_done(progress); /* success */ return g_steal_pointer(&chk2); } static void fu_dfu_target_avr_init(FuDfuTargetAvr *self) { } static void fu_dfu_target_avr_class_init(FuDfuTargetAvrClass *klass) { FuDfuTargetClass *klass_target = FU_DFU_TARGET_CLASS(klass); klass_target->setup = fu_dfu_target_avr_setup; klass_target->attach = fu_dfu_target_avr_attach; klass_target->mass_erase = fu_dfu_target_avr_mass_erase; klass_target->upload_element = fu_dfu_target_avr_upload_element; klass_target->download_element = fu_dfu_target_avr_download_element; } FuDfuTarget * fu_dfu_target_avr_new(void) { FuDfuTarget *target; target = g_object_new(FU_TYPE_DFU_TARGET_AVR, NULL); return target; } fwupd-1.9.16/plugins/dfu/fu-dfu-target-avr.h000066400000000000000000000006371460375044200206200ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-dfu-target.h" #define FU_TYPE_DFU_TARGET_AVR (fu_dfu_target_avr_get_type()) G_DECLARE_DERIVABLE_TYPE(FuDfuTargetAvr, fu_dfu_target_avr, FU, DFU_TARGET_AVR, FuDfuTarget) struct _FuDfuTargetAvrClass { FuDfuTargetClass parent_class; }; FuDfuTarget * fu_dfu_target_avr_new(void); fwupd-1.9.16/plugins/dfu/fu-dfu-target-private.h000066400000000000000000000023171460375044200214770ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-dfu-device.h" #include "fu-dfu-sector.h" #include "fu-dfu-target.h" FuDfuTarget * fu_dfu_target_new(void); GBytes * fu_dfu_target_upload_chunk(FuDfuTarget *self, guint16 index, gsize buf_sz, FuProgress *progress, GError **error); gboolean fu_dfu_target_download_chunk(FuDfuTarget *self, guint16 index, GByteArray *buf, guint timeout_ms, FuProgress *progress, GError **error); gboolean fu_dfu_target_attach(FuDfuTarget *self, FuProgress *progress, GError **error); void fu_dfu_target_set_alt_idx(FuDfuTarget *self, guint8 alt_idx); void fu_dfu_target_set_alt_setting(FuDfuTarget *self, guint8 alt_setting); /* for the other implementations */ void fu_dfu_target_set_alt_name(FuDfuTarget *self, const gchar *alt_name); gboolean fu_dfu_target_check_status(FuDfuTarget *self, GError **error); FuDfuSector * fu_dfu_target_get_sector_for_addr(FuDfuTarget *self, guint32 addr); /* export this just for the self tests */ gboolean fu_dfu_target_parse_sectors(FuDfuTarget *self, const gchar *alt_name, GError **error); fwupd-1.9.16/plugins/dfu/fu-dfu-target-stm.c000066400000000000000000000336721460375044200206330ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-dfu-common.h" #include "fu-dfu-sector.h" #include "fu-dfu-target-private.h" /* waive-pre-commit */ #include "fu-dfu-target-stm.h" G_DEFINE_TYPE(FuDfuTargetStm, fu_dfu_target_stm, FU_TYPE_DFU_TARGET) /* STMicroelectronics STM32 version of DFU: * www.st.com/resource/en/application_note/cd00264379.pdf */ #define DFU_STM_CMD_GET_COMMAND 0x00 #define DFU_STM_CMD_SET_ADDRESS_POINTER 0x21 #define DFU_STM_CMD_ERASE 0x41 #define DFU_STM_CMD_READ_UNPROTECT 0x92 static gboolean fu_dfu_target_stm_attach(FuDfuTarget *target, FuProgress *progress, GError **error) { /* downloading empty payload will cause a dfu to leave, * the returned status will be dfuMANIFEST and expect the device to disconnect */ g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GError) error_local = NULL; if (!fu_dfu_target_download_chunk(target, 2, buf, 0, progress, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_debug("ignoring: %s", error_local->message); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } return TRUE; } static gboolean fu_dfu_target_stm_mass_erase(FuDfuTarget *target, FuProgress *progress, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); /* format buffer */ fu_byte_array_append_uint8(buf, DFU_STM_CMD_ERASE); if (!fu_dfu_target_download_chunk(target, 0, buf, 35000, progress, error)) { g_prefix_error(error, "cannot mass-erase: "); return FALSE; } /* 2nd check required to get error code */ return fu_dfu_target_check_status(target, error); } /* sets the address used for the next download or upload request */ static gboolean fu_dfu_target_stm_set_address(FuDfuTarget *target, guint32 address, FuProgress *progress, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); /* format buffer */ fu_byte_array_append_uint8(buf, DFU_STM_CMD_SET_ADDRESS_POINTER); fu_byte_array_append_uint32(buf, address, G_LITTLE_ENDIAN); if (!fu_dfu_target_download_chunk(target, 0, buf, 0, progress, error)) { g_prefix_error(error, "cannot set address 0x%x: ", address); return FALSE; } /* 2nd check required to get error code */ g_debug("doing actual check status"); return fu_dfu_target_check_status(target, error); } static FuChunk * fu_dfu_target_stm_upload_element(FuDfuTarget *target, guint32 address, gsize expected_size, gsize maximum_size, FuProgress *progress, GError **error) { FuDfuDevice *device = FU_DFU_DEVICE(fu_device_get_proxy(FU_DEVICE(target))); FuDfuSector *sector; FuChunk *chk = NULL; GBytes *chunk_tmp; guint32 offset = address; guint percentage_size = expected_size > 0 ? expected_size : maximum_size; gsize total_size = 0; guint16 transfer_size = fu_dfu_device_get_transfer_size(device); g_autoptr(GBytes) contents = NULL; g_autoptr(GBytes) contents_truncated = NULL; g_autoptr(GPtrArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 40, "set-addr"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "abort"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 58, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); /* for DfuSe devices we need to handle the address manually */ sector = fu_dfu_target_get_sector_for_addr(target, offset); if (sector == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no memory sector at 0x%04x", (guint)offset); return NULL; } g_debug("using sector %u for read of %x", fu_dfu_sector_get_id(sector), offset); if (!fu_dfu_sector_has_cap(sector, FU_DFU_SECTOR_CAP_READABLE)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "memory sector at 0x%04x is not readable", (guint)offset); return NULL; } /* manually set the sector address */ g_debug("setting DfuSe address to 0x%04x", (guint)offset); if (!fu_dfu_target_stm_set_address(target, offset, fu_progress_get_child(progress), error)) return NULL; fu_progress_step_done(progress); /* abort back to IDLE */ if (!fu_dfu_device_abort(device, error)) return NULL; fu_progress_step_done(progress); /* get all the chunks from the hardware */ chunks = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); for (guint16 idx = 0; idx < G_MAXUINT16; idx++) { guint32 chunk_size; g_autoptr(FuProgress) progress_tmp = fu_progress_new(G_STRLOC); /* read chunk of data -- ST uses wBlockNum=0 for DfuSe commands * and wBlockNum=1 is reserved */ chunk_tmp = fu_dfu_target_upload_chunk(target, idx + 2, 0, /* device transfer size */ progress_tmp, /* we don't know! */ error); if (chunk_tmp == NULL) return NULL; /* add to array */ chunk_size = (guint32)g_bytes_get_size(chunk_tmp); g_debug("got #%04x chunk @0x%x of size %" G_GUINT32_FORMAT, idx, offset, chunk_size); g_ptr_array_add(chunks, chunk_tmp); total_size += chunk_size; offset += chunk_size; /* update UI */ if (chunk_size > 0) { fu_progress_set_percentage_full(fu_progress_get_child(progress), MIN(total_size, percentage_size), percentage_size); } /* detect short write as EOF */ if (chunk_size < transfer_size) break; /* more data than we needed */ if (maximum_size > 0 && total_size > maximum_size) break; } fu_progress_step_done(progress); /* abort back to IDLE */ if (!fu_dfu_device_abort(device, error)) return NULL; fu_progress_step_done(progress); /* check final size */ if (expected_size > 0) { if (total_size < expected_size) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid size, got %" G_GSIZE_FORMAT ", " "expected %" G_GSIZE_FORMAT, total_size, expected_size); return NULL; } } /* create new image */ contents = fu_dfu_utils_bytes_join_array(chunks); if (expected_size > 0) { contents_truncated = fu_bytes_new_offset(contents, 0, expected_size, error); if (contents_truncated == NULL) return NULL; } else { contents_truncated = g_bytes_ref(contents); } chk = fu_chunk_bytes_new(contents_truncated); fu_chunk_set_address(chk, address); return chk; } /** * fu_dfu_target_stm_erase_address: * @target: a #FuDfuTarget * @address: memory address * @error: (nullable): optional return location for an error * * Erases a memory sector at a given address. * * Returns: %TRUE for success **/ static gboolean fu_dfu_target_stm_erase_address(FuDfuTarget *target, guint32 address, FuProgress *progress, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); /* format buffer */ fu_byte_array_append_uint8(buf, DFU_STM_CMD_ERASE); fu_byte_array_append_uint32(buf, address, G_LITTLE_ENDIAN); if (!fu_dfu_target_download_chunk(target, 0, buf, 0, progress, error)) { g_prefix_error(error, "cannot erase address 0x%x: ", address); return FALSE; } /* 2nd check required to get error code */ g_debug("doing actual check status"); return fu_dfu_target_check_status(target, error); } static gboolean fu_dfu_target_stm_download_element1(FuDfuTarget *target, FuChunkArray *chunks, GPtrArray *sectors_array, FuProgress *progress, GError **error) { g_autoptr(GHashTable) sectors_hash = g_hash_table_new(g_direct_hash, g_direct_equal); guint32 address = 0; guint32 transfer_size = 0; /* start offset */ if (fu_chunk_array_length(chunks) > 0) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, 0); address = fu_chunk_get_address(chk); transfer_size = fu_chunk_get_data_sz(chk); } /* no progress */ for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { guint32 offset_dev = i * transfer_size; /* for DfuSe devices we need to handle the erase and setting * the sector address manually */ while (offset_dev < (i + 1) * transfer_size) { FuDfuSector *sector = fu_dfu_target_get_sector_for_addr(target, address + offset_dev); if (sector == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no memory sector at 0x%04x", (guint)address + offset_dev); return FALSE; } if (!fu_dfu_sector_has_cap(sector, FU_DFU_SECTOR_CAP_WRITEABLE)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "memory sector at 0x%04x is not writable", (guint)address + offset_dev); return FALSE; } /* if it's erasable and not yet blanked */ if (fu_dfu_sector_has_cap(sector, FU_DFU_SECTOR_CAP_ERASABLE) && g_hash_table_lookup(sectors_hash, sector) == NULL) { g_hash_table_insert(sectors_hash, sector, GINT_TO_POINTER(1)); g_ptr_array_add(sectors_array, sector); g_debug("marking sector 0x%04x-%04x to be erased", fu_dfu_sector_get_address(sector), fu_dfu_sector_get_address(sector) + fu_dfu_sector_get_size(sector)); } offset_dev += fu_dfu_sector_get_size(sector); } } /* success */ return TRUE; } static gboolean fu_dfu_target_stm_download_element2(FuDfuTarget *target, GPtrArray *sectors_array, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, sectors_array->len); for (guint i = 0; i < sectors_array->len; i++) { FuDfuSector *sector = g_ptr_array_index(sectors_array, i); g_debug("erasing sector at 0x%04x", fu_dfu_sector_get_address(sector)); if (!fu_dfu_target_stm_erase_address(target, fu_dfu_sector_get_address(sector), fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_dfu_target_stm_download_element3(FuDfuTarget *target, FuChunkArray *chunks, GPtrArray *sectors_array, FuProgress *progress, GError **error) { guint zone_last = G_MAXUINT; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk_tmp = fu_chunk_array_index(chunks, i); FuDfuSector *sector; guint32 offset_dev = fu_chunk_get_address(chk_tmp); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) bytes_tmp = NULL; /* for DfuSe devices we need to set the address manually */ sector = fu_dfu_target_get_sector_for_addr(target, offset_dev); if (sector == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no valid sector for %x", offset_dev); return FALSE; } /* manually set the sector address */ if (fu_dfu_sector_get_zone(sector) != zone_last) { g_autoptr(FuProgress) progress_tmp = fu_progress_new(G_STRLOC); g_debug("setting address to 0x%04x", (guint)offset_dev); if (!fu_dfu_target_stm_set_address(target, (guint32)offset_dev, progress_tmp, error)) return FALSE; zone_last = fu_dfu_sector_get_zone(sector); } /* we have to write one final zero-sized chunk for EOF */ bytes_tmp = fu_chunk_get_bytes(chk_tmp); g_debug("writing sector at 0x%04x (0x%" G_GSIZE_FORMAT ")", offset_dev, g_bytes_get_size(bytes_tmp)); /* ST uses wBlockNum=0 for DfuSe commands and wBlockNum=1 is reserved */ fu_byte_array_append_bytes(buf, bytes_tmp); if (!fu_dfu_target_download_chunk(target, (i + 2), buf, 0, /* timeout default */ fu_progress_get_child(progress), error)) return FALSE; /* getting the status moves the state machine to DNLOAD-IDLE */ if (!fu_dfu_target_check_status(target, error)) return FALSE; /* update UI */ fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_dfu_target_stm_download_element(FuDfuTarget *target, FuChunk *chk, FuProgress *progress, FuDfuTargetTransferFlags flags, GError **error) { FuDfuDevice *device = FU_DFU_DEVICE(fu_device_get_proxy(FU_DEVICE(target))); g_autoptr(GBytes) bytes = NULL; g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GPtrArray) sectors_array = g_ptr_array_new(); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 49, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, NULL); /* 1st pass: work out which sectors need erasing */ bytes = fu_chunk_get_bytes(chk); chunks = fu_chunk_array_new_from_bytes(bytes, fu_chunk_get_address(chk), fu_dfu_device_get_transfer_size(device)); if (!fu_dfu_target_stm_download_element1(target, chunks, sectors_array, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* 2nd pass: actually erase sectors */ if (!fu_dfu_target_stm_download_element2(target, sectors_array, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* 3rd pass: write data */ if (!fu_dfu_target_stm_download_element3(target, chunks, sectors_array, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_dfu_target_stm_init(FuDfuTargetStm *self) { } static void fu_dfu_target_stm_class_init(FuDfuTargetStmClass *klass) { FuDfuTargetClass *klass_target = FU_DFU_TARGET_CLASS(klass); klass_target->attach = fu_dfu_target_stm_attach; klass_target->mass_erase = fu_dfu_target_stm_mass_erase; klass_target->upload_element = fu_dfu_target_stm_upload_element; klass_target->download_element = fu_dfu_target_stm_download_element; } FuDfuTarget * fu_dfu_target_stm_new(void) { FuDfuTarget *target; target = g_object_new(FU_TYPE_DFU_TARGET_STM, NULL); return target; } fwupd-1.9.16/plugins/dfu/fu-dfu-target-stm.h000066400000000000000000000006371460375044200206330ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-dfu-target.h" #define FU_TYPE_DFU_TARGET_STM (fu_dfu_target_stm_get_type()) G_DECLARE_DERIVABLE_TYPE(FuDfuTargetStm, fu_dfu_target_stm, FU, DFU_TARGET_STM, FuDfuTarget) struct _FuDfuTargetStmClass { FuDfuTargetClass parent_class; }; FuDfuTarget * fu_dfu_target_stm_new(void); fwupd-1.9.16/plugins/dfu/fu-dfu-target.c000066400000000000000000001051261460375044200200240ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * FuDfuTarget: * * This object allows uploading and downloading an image onto a * specific DFU-capable target. * * You only need to use this in preference to #FuDfuDevice if you only * want to update one target on the device. Most users will want to * update all the targets on the device at the same time. * * See also: [class@FuDfuDevice], [class@FuFirmware] */ #include "config.h" #include #include #include "fu-dfu-common.h" #include "fu-dfu-device.h" #include "fu-dfu-sector.h" #include "fu-dfu-struct.h" #include "fu-dfu-target-private.h" /* waive-pre-commit */ #define DFU_TARGET_MANIFEST_MAX_POLLING_TRIES 200 typedef struct { gboolean done_setup; guint8 alt_setting; guint8 alt_idx; GPtrArray *sectors; /* of FuDfuSector */ } FuDfuTargetPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuDfuTarget, fu_dfu_target, FU_TYPE_DEVICE) #define GET_PRIVATE(o) (fu_dfu_target_get_instance_private(o)) static void fu_dfu_target_init(FuDfuTarget *self) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); priv->sectors = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); } static void fu_dfu_target_finalize(GObject *object) { FuDfuTarget *self = FU_DFU_TARGET(object); FuDfuTargetPrivate *priv = GET_PRIVATE(self); g_ptr_array_unref(priv->sectors); G_OBJECT_CLASS(fu_dfu_target_parent_class)->finalize(object); } static void fu_dfu_target_to_string(FuDevice *device, guint idt, GString *str) { FuDfuTarget *self = FU_DFU_TARGET(device); FuDfuTargetPrivate *priv = GET_PRIVATE(self); fu_string_append_kx(str, idt, "AltSetting", priv->alt_setting); fu_string_append_kx(str, idt, "AltIdx", priv->alt_idx); for (guint i = 0; i < priv->sectors->len; i++) { FuDfuSector *sector = g_ptr_array_index(priv->sectors, i); g_autofree gchar *tmp1 = g_strdup_printf("Idx%02x", i); g_autofree gchar *tmp2 = fu_dfu_sector_to_string(sector); fu_string_append(str, idt + 1, tmp1, tmp2); } } FuDfuSector * fu_dfu_target_get_sector_for_addr(FuDfuTarget *self, guint32 addr) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); for (guint i = 0; i < priv->sectors->len; i++) { FuDfuSector *sector = g_ptr_array_index(priv->sectors, i); if (addr < fu_dfu_sector_get_address(sector)) continue; if (addr > fu_dfu_sector_get_address(sector) + fu_dfu_sector_get_size(sector)) continue; return sector; } return NULL; } static gboolean fu_dfu_target_parse_sector(FuDfuTarget *self, const gchar *dfuse_sector_id, guint32 *addr, guint16 zone, guint16 number, GError **error) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); FuDfuSectorCap cap = FU_DFU_SECTOR_CAP_NONE; gchar *tmp; guint32 addr_offset = 0; guint64 nr_sectors; guint64 sector_size; /* parse # of sectors */ nr_sectors = g_ascii_strtoull(dfuse_sector_id, &tmp, 10); if (nr_sectors > 999) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid number of sectors: %s", dfuse_sector_id); return FALSE; } /* check this is the delimiter */ if (tmp[0] != '*') { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid sector ID: %s", dfuse_sector_id); return FALSE; } /* parse sector size */ sector_size = g_ascii_strtoull(tmp + 1, &tmp, 10); if (sector_size > 999) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid sector size: %s", dfuse_sector_id); return FALSE; } /* handle weirdness */ if (fu_device_has_private_flag(fu_device_get_proxy(FU_DEVICE(self)), FU_DFU_DEVICE_FLAG_ABSENT_SECTOR_SIZE)) { if (tmp[1] == '\0') { tmp[1] = tmp[0]; tmp[0] = 'B'; } } /* get multiplier */ switch (tmp[0]) { case 'B': /* byte */ case ' ': /* byte, ST reference bootloader :/ */ break; case 'K': /* Kilo */ sector_size *= 0x400; break; case 'M': /* Mega */ sector_size *= 0x100000; break; default: g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid sector multiplier: %s", tmp); return FALSE; } /* get sector type */ switch (tmp[1]) { case 'a': cap = FU_DFU_SECTOR_CAP_READABLE; break; case 'b': cap = FU_DFU_SECTOR_CAP_ERASABLE; break; case 'c': cap = FU_DFU_SECTOR_CAP_READABLE | FU_DFU_SECTOR_CAP_ERASABLE; break; case 'd': cap = FU_DFU_SECTOR_CAP_WRITEABLE; break; case 'e': cap = FU_DFU_SECTOR_CAP_READABLE | FU_DFU_SECTOR_CAP_WRITEABLE; break; case 'f': cap = FU_DFU_SECTOR_CAP_ERASABLE | FU_DFU_SECTOR_CAP_WRITEABLE; break; case 'g': cap = FU_DFU_SECTOR_CAP_READABLE | FU_DFU_SECTOR_CAP_ERASABLE | FU_DFU_SECTOR_CAP_WRITEABLE; break; default: g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Invalid sector type: %s", tmp); return FALSE; } /* add all the sectors */ for (guint i = 0; i < nr_sectors; i++) { FuDfuSector *sector; sector = fu_dfu_sector_new(*addr + addr_offset, (guint32)sector_size, (guint32)((nr_sectors * sector_size) - addr_offset), zone, number, cap); g_ptr_array_add(priv->sectors, sector); addr_offset += fu_dfu_sector_get_size(sector); } /* update for next sector */ *addr += addr_offset; return TRUE; } gboolean fu_dfu_target_parse_sectors(FuDfuTarget *self, const gchar *alt_name, GError **error) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); g_auto(GStrv) zones = NULL; /* not set */ if (alt_name == NULL) return TRUE; /* From the Neo Freerunner */ if (g_str_has_prefix(alt_name, "RAM 0x")) { FuDfuSector *sector; guint64 addr_tmp; addr_tmp = g_ascii_strtoull(alt_name + 6, NULL, 16); if (addr_tmp == 0 || addr_tmp > G_MAXUINT32) return FALSE; g_debug("RAM description, so parsing"); sector = fu_dfu_sector_new((guint32)addr_tmp, 0x0, /* size */ 0x0, /* size_left */ 0x0, /* zone */ 0x0, /* number */ FU_DFU_SECTOR_CAP_ERASABLE | FU_DFU_SECTOR_CAP_READABLE | FU_DFU_SECTOR_CAP_WRITEABLE); g_ptr_array_add(priv->sectors, sector); } /* not a DfuSe alternative name */ if (alt_name[0] != '@') return TRUE; /* clear any existing zones */ g_ptr_array_set_size(priv->sectors, 0); /* parse zones */ zones = g_strsplit(alt_name, "/", -1); fu_device_set_name(FU_DEVICE(self), g_strchomp(zones[0] + 1)); for (guint i = 1; zones[i] != NULL; i += 2) { guint32 addr; guint64 addr_tmp; g_auto(GStrv) sectors = NULL; /* parse address */ if (!g_str_has_prefix(zones[i], "0x")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No sector address"); return FALSE; } addr_tmp = g_ascii_strtoull(zones[i] + 2, NULL, 16); if (addr_tmp > G_MAXUINT32) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Sector address too large"); return FALSE; } addr = (guint32)addr_tmp; /* no sectors?! */ if (zones[i + 1] == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No sector section"); return FALSE; } /* parse sectors */ sectors = g_strsplit(zones[i + 1], ",", -1); for (guint16 j = 0; sectors[j] != NULL; j++) { if (!fu_dfu_target_parse_sector(self, sectors[j], &addr, (i - 1) / 2, j, error)) { g_prefix_error(error, "Failed to parse: '%s': ", sectors[j]); return FALSE; } } } /* success */ return TRUE; } /** * fu_dfu_target_new: (skip) * * Creates a new DFU target, which represents an alt-setting on a * DFU-capable device. * * Returns: a #FuDfuTarget **/ FuDfuTarget * fu_dfu_target_new(void) { FuDfuTarget *self; self = g_object_new(FU_TYPE_DFU_TARGET, NULL); return self; } /** * fu_dfu_target_get_sectors: * @self: a #FuDfuTarget * * Gets the sectors exported by the target. * * Returns: (transfer none) (element-type FuDfuSector): sectors **/ GPtrArray * fu_dfu_target_get_sectors(FuDfuTarget *self) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_TARGET(self), NULL); return priv->sectors; } /** * fu_dfu_target_get_sector_default: * @self: a #FuDfuTarget * * Gets the default (first) sector exported by the target. * * Returns: (transfer none): a #FuDfuSector, or %NULL **/ FuDfuSector * fu_dfu_target_get_sector_default(FuDfuTarget *self) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_TARGET(self), NULL); if (priv->sectors->len == 0) return NULL; return FU_DFU_SECTOR(g_ptr_array_index(priv->sectors, 0)); } /** * fu_dfu_target_status_to_error_msg: * @status: a #FuDfuStatus, e.g. %FU_DFU_STATUS_ERR_ERASE * * Converts an enumerated value to an error description. * * Returns: a string **/ static const gchar * fu_dfu_target_status_to_error_msg(FuDfuStatus status) { if (status == FU_DFU_STATUS_OK) return "No error condition is present"; if (status == FU_DFU_STATUS_ERR_TARGET) return "Firmware is not for designed this device"; if (status == FU_DFU_STATUS_ERR_FILE) return "Firmware is for this device but fails verification"; if (status == FU_DFU_STATUS_ERR_WRITE) return "Device is unable to write memory"; if (status == FU_DFU_STATUS_ERR_ERASE) return "Memory erase function failed"; if (status == FU_DFU_STATUS_ERR_CHECK_ERASED) return "Memory erase check failed"; if (status == FU_DFU_STATUS_ERR_PROG) return "Program memory function failed"; if (status == FU_DFU_STATUS_ERR_VERIFY) return "Programmed memory failed verification"; if (status == FU_DFU_STATUS_ERR_ADDRESS) return "Cannot program memory due to address out of range"; if (status == FU_DFU_STATUS_ERR_NOTDONE) return "Received zero-length download but data is incomplete"; if (status == FU_DFU_STATUS_ERR_FIRMWARE) return "Device firmware is corrupt"; if (status == FU_DFU_STATUS_ERR_VENDOR) return "Vendor-specific error"; if (status == FU_DFU_STATUS_ERR_USBR) return "Device detected unexpected USB reset signaling"; if (status == FU_DFU_STATUS_ERR_POR) return "Device detected unexpected power on reset"; if (status == FU_DFU_STATUS_ERR_UNKNOWN) return "Something unexpected went wrong"; if (status == FU_DFU_STATUS_ERR_STALLDPKT) return "Device stalled an unexpected request"; return NULL; } static gboolean fu_dfu_target_manifest_wait(FuDfuTarget *self, GError **error) { FuDfuDevice *device = FU_DFU_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); guint polling_count = 0; /* get the status */ if (!fu_dfu_device_refresh(device, 0, error)) return FALSE; /* wait for FU_DFU_STATE_DFU_MANIFEST to not be set */ while (fu_dfu_device_get_state(device) == FU_DFU_STATE_DFU_MANIFEST_SYNC || fu_dfu_device_get_state(device) == FU_DFU_STATE_DFU_MANIFEST) { g_debug("waiting for FU_DFU_STATE_DFU_MANIFEST to clear"); if (polling_count++ > DFU_TARGET_MANIFEST_MAX_POLLING_TRIES) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "reach to max polling tries"); return FALSE; } fu_device_sleep(FU_DEVICE(device), fu_dfu_device_get_download_timeout(device) + 1000); if (!fu_dfu_device_refresh(device, 0, error)) return FALSE; } /* in an error state */ if (fu_dfu_device_get_state(device) == FU_DFU_STATE_DFU_ERROR) { g_set_error_literal( error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, fu_dfu_target_status_to_error_msg(fu_dfu_device_get_status(device))); return FALSE; } return TRUE; } gboolean fu_dfu_target_check_status(FuDfuTarget *self, GError **error) { FuDfuDevice *device = FU_DFU_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); FuDfuStatus status; g_autoptr(GTimer) timer = g_timer_new(); /* get the status */ if (!fu_dfu_device_refresh(device, 0, error)) return FALSE; /* wait for dfuDNBUSY to not be set */ while (fu_dfu_device_get_state(device) == FU_DFU_STATE_DFU_DNBUSY) { g_debug("waiting for FU_DFU_STATE_DFU_DNBUSY to clear"); fu_device_sleep(FU_DEVICE(device), fu_dfu_device_get_download_timeout(device)); if (!fu_dfu_device_refresh(device, 0, error)) return FALSE; /* this is a really long time to save fwupd in case * the device has got wedged */ if (g_timer_elapsed(timer, NULL) > 120.f) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Stuck in DFU_DNBUSY"); return FALSE; } } /* not in an error state */ if (fu_dfu_device_get_state(device) != FU_DFU_STATE_DFU_ERROR) return TRUE; /* STM32-specific long errors */ status = fu_dfu_device_get_status(device); if (fu_dfu_device_get_version(device) == FU_DFU_FIRMARE_VERSION_DFUSE) { if (status == FU_DFU_STATUS_ERR_VENDOR) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Read protection is active"); return FALSE; } if (status == FU_DFU_STATUS_ERR_TARGET) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Address is wrong or unsupported"); return FALSE; } } /* use a proper error description */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, fu_dfu_target_status_to_error_msg(status)); return FALSE; } /** * fu_dfu_target_use_alt_setting: * @self: a #FuDfuTarget * @error: (nullable): optional return location for an error * * Opens a DFU-capable target. * * Returns: %TRUE for success **/ static gboolean fu_dfu_target_use_alt_setting(FuDfuTarget *self, GError **error) { FuDfuDevice *device = FU_DFU_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); FuDfuTargetPrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_DFU_TARGET(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* ensure interface is claimed */ if (!fu_dfu_device_ensure_interface(device, error)) return FALSE; /* use the correct setting */ if (fu_device_has_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { if (!g_usb_device_set_interface_alt(usb_device, (gint)fu_dfu_device_get_interface(device), (gint)priv->alt_setting, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot set alternate setting 0x%02x on interface %i: %s", priv->alt_setting, fu_dfu_device_get_interface(device), error_local->message); return FALSE; } } return TRUE; } /** * fu_dfu_target_setup: * @self: a #FuDfuTarget * @error: (nullable): optional return location for an error * * Opens a DFU-capable target. * * Returns: %TRUE for success **/ gboolean fu_dfu_target_setup(FuDfuTarget *self, GError **error) { FuDfuTargetClass *klass = FU_DFU_TARGET_GET_CLASS(self); FuDfuTargetPrivate *priv = GET_PRIVATE(self); FuDevice *device = fu_device_get_proxy(FU_DEVICE(self)); g_return_val_if_fail(FU_IS_DFU_TARGET(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* already done */ if (priv->done_setup) return TRUE; /* superclassed */ if (klass->setup != NULL) { if (!klass->setup(self, error)) return FALSE; } /* GD32VF103 devices features and peripheral list */ if (priv->alt_setting == 0x0 && fu_device_has_private_flag(device, FU_DFU_DEVICE_FLAG_GD32)) { /* RB R8 R6 R4 VB V8 * Flash (KB) 128 64 32 16 128 64 * TB T8 T6 T4 CB C8 C6 C4 * Flash (KB) 128 64 32 16 128 64 32 16 */ const gchar *serial = fu_device_get_serial(device); if (serial == NULL || strlen(serial) < 4 || serial[3] != 'J') { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "GD32 serial number %s invalid", serial); return FALSE; } if (serial[2] == '2') { fu_device_set_logical_id(FU_DEVICE(self), "@Internal Flash /0x8000000/8*1Kg"); } else if (serial[2] == '4') { fu_device_set_logical_id(FU_DEVICE(self), "@Internal Flash /0x8000000/16*1Kg"); } else if (serial[2] == '6') { fu_device_set_logical_id(FU_DEVICE(self), "@Internal Flash /0x8000000/32*1Kg"); } else if (serial[2] == '8') { fu_device_set_logical_id(FU_DEVICE(self), "@Internal Flash /0x8000000/64*1Kg"); } else if (serial[2] == 'B') { fu_device_set_logical_id(FU_DEVICE(self), "@Internal Flash /0x8000000/128*1Kg"); } else if (serial[2] == 'D') { fu_device_set_logical_id(FU_DEVICE(self), "@Internal Flash /0x8000000/256*1Kg"); } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unknown GD32 sector size: %c", serial[2]); return FALSE; } } /* get string */ if (priv->alt_idx != 0x00 && fu_device_get_logical_id(FU_DEVICE(self)) == NULL) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); g_autofree gchar *alt_name = NULL; alt_name = g_usb_device_get_string_descriptor(usb_device, priv->alt_idx, NULL); fu_device_set_logical_id(FU_DEVICE(self), alt_name); } /* parse the DfuSe format according to UM0424 */ if (priv->sectors->len == 0) { if (!fu_dfu_target_parse_sectors(self, fu_device_get_logical_id(FU_DEVICE(self)), error)) return FALSE; } /* add a dummy entry */ if (priv->sectors->len == 0) { FuDfuSector *sector; sector = fu_dfu_sector_new(0x0, /* addr */ 0x0, /* size */ 0x0, /* size_left */ 0x0, /* zone */ 0x0, /* number */ FU_DFU_SECTOR_CAP_READABLE | FU_DFU_SECTOR_CAP_WRITEABLE); g_debug("no UM0424 sector description in %s", fu_device_get_logical_id(FU_DEVICE(self))); g_ptr_array_add(priv->sectors, sector); } priv->done_setup = TRUE; return TRUE; } gboolean fu_dfu_target_download_chunk(FuDfuTarget *self, guint16 index, GByteArray *buf, guint timeout_ms, FuProgress *progress, GError **error) { FuDfuDevice *device = FU_DFU_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); g_autoptr(GError) error_local = NULL; gsize actual_length; /* fall back to default */ if (timeout_ms == 0) timeout_ms = fu_dfu_device_get_timeout(device); /* low level packet debugging */ fu_dump_raw(G_LOG_DOMAIN, "Message", buf->data, buf->len); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, FU_DFU_REQUEST_DNLOAD, index, fu_dfu_device_get_interface(device), buf->data, buf->len, &actual_length, timeout_ms, NULL, &error_local)) { /* refresh the error code */ fu_dfu_device_error_fixup(device, &error_local); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot download data: %s", error_local->message); return FALSE; } /* for STM32 devices, the action only occurs when we do GetStatus -- * and it can take a long time to complete! */ if (fu_dfu_device_get_version(device) == FU_DFU_FIRMARE_VERSION_DFUSE) { if (!fu_dfu_device_refresh(device, 35000, error)) return FALSE; } /* wait for the device to write contents to the EEPROM */ if (buf->len == 0 && fu_dfu_device_get_download_timeout(device) > 0) fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_BUSY); if (fu_dfu_device_get_download_timeout(device) > 0) { g_debug("sleeping for %ums…", fu_dfu_device_get_download_timeout(device)); fu_device_sleep(FU_DEVICE(device), fu_dfu_device_get_download_timeout(device)); } /* find out if the write was successful, waiting for BUSY to clear */ if (!fu_dfu_target_check_status(self, error)) { g_prefix_error(error, "cannot wait for busy: "); return FALSE; } g_assert_cmpint(actual_length, ==, buf->len); return TRUE; } GBytes * fu_dfu_target_upload_chunk(FuDfuTarget *self, guint16 index, gsize buf_sz, FuProgress *progress, GError **error) { FuDfuDevice *device = FU_DFU_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); g_autoptr(GError) error_local = NULL; guint8 *buf; gsize actual_length; /* unset */ if (buf_sz == 0) buf_sz = (gsize)fu_dfu_device_get_transfer_size(device); buf = g_new0(guint8, buf_sz); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, FU_DFU_REQUEST_UPLOAD, index, fu_dfu_device_get_interface(device), buf, buf_sz, &actual_length, fu_dfu_device_get_timeout(device), NULL, &error_local)) { /* refresh the error code */ fu_dfu_device_error_fixup(device, &error_local); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot upload data: %s", error_local->message); return NULL; } /* low level packet debugging */ fu_dump_raw(G_LOG_DOMAIN, "Message", buf, actual_length); return g_bytes_new_take(buf, actual_length); } void fu_dfu_target_set_alt_idx(FuDfuTarget *self, guint8 alt_idx) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); priv->alt_idx = alt_idx; } void fu_dfu_target_set_alt_setting(FuDfuTarget *self, guint8 alt_setting) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); priv->alt_setting = alt_setting; } gboolean fu_dfu_target_attach(FuDfuTarget *self, FuProgress *progress, GError **error) { FuDfuDevice *device = FU_DFU_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); FuDfuTargetClass *klass = FU_DFU_TARGET_GET_CLASS(self); /* ensure populated */ if (!fu_dfu_target_setup(self, error)) return FALSE; /* implemented as part of a superclass */ if (klass->attach != NULL) return klass->attach(self, progress, error); /* normal DFU mode just needs a bus reset */ return fu_dfu_device_reset(device, progress, error); } static FuChunk * fu_dfu_target_upload_element_dfu(FuDfuTarget *self, guint32 address, gsize expected_size, gsize maximum_size, FuProgress *progress, GError **error) { FuDfuDevice *device = FU_DFU_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); GBytes *chunk_tmp; guint percentage_size = expected_size > 0 ? expected_size : maximum_size; gsize total_size = 0; guint16 transfer_size = fu_dfu_device_get_transfer_size(device); g_autoptr(GBytes) contents = NULL; g_autoptr(GPtrArray) chunks = NULL; /* update UI */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); /* get all the chunks from the hardware */ chunks = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref); for (guint16 idx = 0; idx < G_MAXUINT16; idx++) { guint32 chunk_size; /* read chunk of data */ chunk_tmp = fu_dfu_target_upload_chunk(self, idx, 0, /* device transfer size */ progress, error); if (chunk_tmp == NULL) return NULL; /* keep a sum of all the chunks */ chunk_size = (guint32)g_bytes_get_size(chunk_tmp); total_size += chunk_size; /* add to array */ g_debug("got #%04x chunk of size %" G_GUINT32_FORMAT, idx, chunk_size); g_ptr_array_add(chunks, chunk_tmp); /* update UI */ if (chunk_size > 0) fu_progress_set_percentage_full(progress, total_size, percentage_size); /* detect short write as EOF */ if (chunk_size < transfer_size) break; } /* check final size */ if (expected_size > 0) { if (total_size != expected_size) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid size, got %" G_GSIZE_FORMAT ", " "expected %" G_GSIZE_FORMAT, total_size, expected_size); return NULL; } } /* done */ fu_progress_set_percentage(progress, 100); /* create new image */ contents = fu_dfu_utils_bytes_join_array(chunks); return fu_chunk_bytes_new(contents); } static FuChunk * fu_dfu_target_upload_element(FuDfuTarget *self, guint32 address, gsize expected_size, gsize maximum_size, FuProgress *progress, GError **error) { FuDfuTargetClass *klass = FU_DFU_TARGET_GET_CLASS(self); /* implemented as part of a superclass */ if (klass->upload_element != NULL) { return klass ->upload_element(self, address, expected_size, maximum_size, progress, error); } return fu_dfu_target_upload_element_dfu(self, address, expected_size, maximum_size, progress, error); } static guint32 fu_dfu_target_get_size_of_zone(FuDfuTarget *self, guint16 zone) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); guint32 len = 0; for (guint i = 0; i < priv->sectors->len; i++) { FuDfuSector *sector = g_ptr_array_index(priv->sectors, i); if (fu_dfu_sector_get_zone(sector) != zone) continue; len += fu_dfu_sector_get_size(sector); } return len; } /* private */ gboolean fu_dfu_target_upload(FuDfuTarget *self, FuFirmware *firmware, FuProgress *progress, FuDfuTargetTransferFlags flags, GError **error) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); FuDfuSector *sector; guint16 zone_cur; guint32 zone_size = 0; guint32 zone_last = G_MAXUINT; g_autoptr(FuFirmware) image = NULL; g_return_val_if_fail(FU_IS_DFU_TARGET(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* ensure populated */ if (!fu_dfu_target_setup(self, error)) return FALSE; /* can the target do this? */ if (!fu_device_has_private_flag(fu_device_get_proxy(FU_DEVICE(self)), FU_DFU_DEVICE_FLAG_CAN_UPLOAD)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "target cannot do uploading"); return FALSE; } /* use correct alt */ if (!fu_dfu_target_use_alt_setting(self, error)) return FALSE; /* no open?! */ if (priv->sectors->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sectors defined for target"); return FALSE; } /* create a new image */ image = fu_firmware_new(); fu_firmware_set_id(image, fu_device_get_logical_id(FU_DEVICE(self))); fu_firmware_set_idx(image, priv->alt_setting); /* get all the sectors for the device */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, priv->sectors->len); for (guint i = 0; i < priv->sectors->len; i++) { g_autoptr(FuChunk) chk = NULL; /* only upload to the start of any zone:sector */ sector = g_ptr_array_index(priv->sectors, i); zone_cur = fu_dfu_sector_get_zone(sector); if (zone_cur == zone_last) continue; /* get the size of the entire continuous zone */ zone_size = fu_dfu_target_get_size_of_zone(self, zone_cur); zone_last = zone_cur; /* get the first element from the hardware */ g_debug("starting upload from 0x%08x (0x%04x)", fu_dfu_sector_get_address(sector), zone_size); chk = fu_dfu_target_upload_element(self, fu_dfu_sector_get_address(sector), 0, /* expected */ zone_size, /* maximum */ fu_progress_get_child(progress), error); if (chk == NULL) return FALSE; /* this chunk was uploaded okay */ fu_firmware_add_chunk(image, chk); fu_progress_step_done(progress); } /* success */ fu_firmware_add_image(firmware, image); return TRUE; } static gchar * _g_bytes_compare_verbose(GBytes *bytes1, GBytes *bytes2) { const guint8 *data1; const guint8 *data2; gsize length1; gsize length2; data1 = g_bytes_get_data(bytes1, &length1); data2 = g_bytes_get_data(bytes2, &length2); /* not the same length */ if (length1 != length2) { return g_strdup_printf("got %" G_GSIZE_FORMAT " bytes, " "expected %" G_GSIZE_FORMAT, length1, length2); } /* return 00 01 02 03 */ for (guint i = 0; i < length1; i++) { if (data1[i] != data2[i]) { return g_strdup_printf("got 0x%02x, expected 0x%02x @ 0x%04x", data1[i], data2[i], i); } } return NULL; } static gboolean fu_dfu_target_download_element_dfu(FuDfuTarget *self, FuChunk *chk, FuProgress *progress, FuDfuTargetTransferFlags flags, GError **error) { FuDfuDevice *device = FU_DFU_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); guint32 nr_chunks; guint16 transfer_size = fu_dfu_device_get_transfer_size(device); g_autoptr(GBytes) bytes = NULL; /* round up as we have to transfer incomplete blocks */ bytes = fu_chunk_get_bytes(chk); nr_chunks = (guint)ceil((gdouble)g_bytes_get_size(bytes) / (gdouble)transfer_size); if (nr_chunks == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "zero-length firmware"); return FALSE; } fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); for (guint32 i = 0; i < nr_chunks + 1; i++) { gsize length; guint32 offset; g_autoptr(GByteArray) buf = g_byte_array_new(); /* calculate the offset into the chunk data */ offset = i * transfer_size; /* we have to write one final zero-sized chunk for EOF */ if (i < nr_chunks) { g_autoptr(GBytes) bytes_tmp = NULL; length = g_bytes_get_size(bytes) - offset; if (length > transfer_size) length = transfer_size; bytes_tmp = fu_bytes_new_offset(bytes, offset, length, error); if (bytes_tmp == NULL) return FALSE; fu_byte_array_append_bytes(buf, bytes_tmp); } g_debug("writing #%04x chunk of size 0x%x", i, buf->len); if (!fu_dfu_target_download_chunk(self, i, buf, 0, progress, error)) return FALSE; /* update UI */ fu_progress_set_percentage_full(progress, i + 1, nr_chunks + 1); } /* success */ return TRUE; } static gboolean fu_dfu_target_download_element(FuDfuTarget *self, FuChunk *chk, FuProgress *progress, FuDfuTargetTransferFlags flags, GError **error) { FuDevice *device = fu_device_get_proxy(FU_DEVICE(self)); FuDfuTargetClass *klass = FU_DFU_TARGET_GET_CLASS(self); /* progress */ if (flags & DFU_TARGET_TRANSFER_FLAG_VERIFY && fu_device_has_private_flag(device, FU_DFU_DEVICE_FLAG_CAN_UPLOAD)) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 96, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 4, NULL); } else { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, 1); } /* implemented as part of a superclass */ if (klass->download_element != NULL) { if (!klass->download_element(self, chk, fu_progress_get_child(progress), flags, error)) return FALSE; } else { if (!fu_dfu_target_download_element_dfu(self, chk, fu_progress_get_child(progress), flags, error)) return FALSE; } fu_progress_step_done(progress); /* verify */ if (flags & DFU_TARGET_TRANSFER_FLAG_VERIFY && fu_device_has_private_flag(device, FU_DFU_DEVICE_FLAG_CAN_UPLOAD)) { g_autoptr(GBytes) bytes = NULL; g_autoptr(GBytes) bytes_tmp = NULL; g_autoptr(FuChunk) chunk_tmp = NULL; bytes = fu_chunk_get_bytes(chk); chunk_tmp = fu_dfu_target_upload_element(self, fu_chunk_get_address(chk), g_bytes_get_size(bytes), g_bytes_get_size(bytes), fu_progress_get_child(progress), error); if (chunk_tmp == NULL) return FALSE; bytes_tmp = fu_chunk_get_bytes(chunk_tmp); if (g_bytes_compare(bytes_tmp, bytes) != 0) { g_autofree gchar *bytes_cmp_str = NULL; bytes_cmp_str = _g_bytes_compare_verbose(bytes_tmp, bytes); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "verify failed: %s", bytes_cmp_str); return FALSE; } fu_progress_step_done(progress); } return TRUE; } /** * fu_dfu_target_download: * @self: a #FuDfuTarget * @image: a #FuFirmware * @flags: DFU target flags, e.g. %DFU_TARGET_TRANSFER_FLAG_VERIFY * @error: (nullable): optional return location for an error * * Downloads firmware from the host to the target, optionally verifying * the transfer. * * Returns: %TRUE for success **/ gboolean fu_dfu_target_download(FuDfuTarget *self, FuFirmware *image, FuProgress *progress, FuDfuTargetTransferFlags flags, GError **error) { FuDevice *device = fu_device_get_proxy(FU_DEVICE(self)); FuDfuTargetPrivate *priv = GET_PRIVATE(self); g_autoptr(GPtrArray) chunks = NULL; g_return_val_if_fail(FU_IS_DFU_TARGET(self), FALSE); g_return_val_if_fail(FU_IS_FIRMWARE(image), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* ensure populated */ if (!fu_dfu_target_setup(self, error)) return FALSE; /* can the target do this? */ if (!fu_device_has_private_flag(device, FU_DFU_DEVICE_FLAG_CAN_DOWNLOAD)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "target cannot do downloading"); return FALSE; } /* use correct alt */ if (!fu_dfu_target_use_alt_setting(self, error)) return FALSE; /* download all chunks in the image to the device */ chunks = fu_firmware_get_chunks(image, error); if (chunks == NULL) return FALSE; if (chunks->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no image chunks"); return FALSE; } fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); g_debug("downloading chunk at 0x%04x", fu_chunk_get_address(chk)); /* auto-detect missing firmware address -- this assumes * that the first target is the main program memory and that * there is only one element in the firmware file */ if (flags & DFU_TARGET_TRANSFER_FLAG_ADDR_HEURISTIC && fu_chunk_get_address(chk) == 0x0 && chunks->len == 1 && priv->sectors->len > 0) { FuDfuSector *sector = g_ptr_array_index(priv->sectors, 0); g_debug("fixing up firmware address from 0x0 to 0x%x", fu_dfu_sector_get_address(sector)); fu_chunk_set_address(chk, fu_dfu_sector_get_address(sector)); } /* download to device */ if (!fu_dfu_target_download_element(self, chk, fu_progress_get_child(progress), flags, error)) return FALSE; fu_progress_step_done(progress); } if (fu_device_has_private_flag(device, FU_DFU_DEVICE_FLAG_MANIFEST_POLL) && fu_device_has_private_flag(device, FU_DFU_DEVICE_FLAG_MANIFEST_TOL)) if (!fu_dfu_target_manifest_wait(self, error)) return FALSE; /* success */ return TRUE; } /** * fu_dfu_target_get_alt_setting: * @self: a #FuDfuTarget * * Gets the alternate setting to use for this interface. * * Returns: the alternative setting, typically zero **/ guint8 fu_dfu_target_get_alt_setting(FuDfuTarget *self) { FuDfuTargetPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_DFU_TARGET(self), 0xff); return priv->alt_setting; } static void fu_dfu_target_class_init(FuDfuTargetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_dfu_target_to_string; object_class->finalize = fu_dfu_target_finalize; } fwupd-1.9.16/plugins/dfu/fu-dfu-target.h000066400000000000000000000046041460375044200200300ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-dfu-common.h" #include "fu-dfu-sector.h" #define FU_TYPE_DFU_TARGET (fu_dfu_target_get_type()) G_DECLARE_DERIVABLE_TYPE(FuDfuTarget, fu_dfu_target, FU, DFU_TARGET, FuDevice) /** * FuDfuTargetTransferFlags: * @DFU_TARGET_TRANSFER_FLAG_NONE: No flags set * @DFU_TARGET_TRANSFER_FLAG_VERIFY: Verify the download once complete * @DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID: Allow downloading images with wildcard VIDs * @DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID: Allow downloading images with wildcard PIDs * @DFU_TARGET_TRANSFER_FLAG_ADDR_HEURISTIC: Automatically detect the address to use * * The optional flags used for transferring firmware. **/ typedef enum { DFU_TARGET_TRANSFER_FLAG_NONE = 0, DFU_TARGET_TRANSFER_FLAG_VERIFY = (1 << 0), DFU_TARGET_TRANSFER_FLAG_WILDCARD_VID = (1 << 4), DFU_TARGET_TRANSFER_FLAG_WILDCARD_PID = (1 << 5), DFU_TARGET_TRANSFER_FLAG_ADDR_HEURISTIC = (1 << 7), /*< private >*/ DFU_TARGET_TRANSFER_FLAG_LAST } FuDfuTargetTransferFlags; struct _FuDfuTargetClass { FuDeviceClass parent_class; gboolean (*setup)(FuDfuTarget *self, GError **error); gboolean (*attach)(FuDfuTarget *self, FuProgress *progress, GError **error); gboolean (*detach)(FuDfuTarget *self, FuProgress *progress, GError **error); gboolean (*mass_erase)(FuDfuTarget *self, FuProgress *progress, GError **error); FuChunk *(*upload_element)(FuDfuTarget *self, guint32 address, gsize expected_size, gsize maximum_size, FuProgress *progress, GError **error); gboolean (*download_element)(FuDfuTarget *self, FuChunk *chk, FuProgress *progress, FuDfuTargetTransferFlags flags, GError **error); }; GPtrArray * fu_dfu_target_get_sectors(FuDfuTarget *self); FuDfuSector * fu_dfu_target_get_sector_default(FuDfuTarget *self); guint8 fu_dfu_target_get_alt_setting(FuDfuTarget *self); gboolean fu_dfu_target_upload(FuDfuTarget *self, FuFirmware *firmware, FuProgress *progress, FuDfuTargetTransferFlags flags, GError **error); gboolean fu_dfu_target_setup(FuDfuTarget *self, GError **error); gboolean fu_dfu_target_download(FuDfuTarget *self, FuFirmware *image, FuProgress *progress, FuDfuTargetTransferFlags flags, GError **error); fwupd-1.9.16/plugins/dfu/fu-dfu-tool.c000066400000000000000000000564101460375044200175140ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #ifdef HAVE_GIO_UNIX #include #endif #include "fu-context-private.h" #include "fu-dfu-device.h" #include "fu-dfu-sector.h" typedef struct { GCancellable *cancellable; GPtrArray *cmd_array; gboolean force; gchar *device_vid_pid; guint16 transfer_size; FuContext *ctx; GUsbContext *usb_context; } FuDfuTool; static void fu_dfu_tool_free(FuDfuTool *self) { if (self == NULL) return; g_free(self->device_vid_pid); if (self->cancellable != NULL) g_object_unref(self->cancellable); if (self->usb_context != NULL) g_object_unref(self->usb_context); g_object_unref(self->ctx); if (self->cmd_array != NULL) g_ptr_array_unref(self->cmd_array); g_free(self); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuDfuTool, fu_dfu_tool_free) #pragma clang diagnostic pop typedef gboolean (*FuUtilPrivateCb)(FuDfuTool *util, gchar **values, GError **error); typedef struct { gchar *name; gchar *arguments; gchar *description; FuUtilPrivateCb callback; } FuUtilItem; static void fu_dfu_tool_item_free(FuUtilItem *item) { g_free(item->name); g_free(item->arguments); g_free(item->description); g_free(item); } static gint fu_dfu_tool_sort_command_name_cb(FuUtilItem **item1, FuUtilItem **item2) { return g_strcmp0((*item1)->name, (*item2)->name); } static void fu_dfu_tool_add(GPtrArray *array, const gchar *name, const gchar *arguments, const gchar *description, FuUtilPrivateCb callback) { g_auto(GStrv) names = NULL; g_return_if_fail(name != NULL); g_return_if_fail(description != NULL); g_return_if_fail(callback != NULL); /* add each one */ names = g_strsplit(name, ",", -1); for (guint i = 0; names[i] != NULL; i++) { FuUtilItem *item = g_new0(FuUtilItem, 1); item->name = g_strdup(names[i]); if (i == 0) { item->description = g_strdup(description); } else { /* TRANSLATORS: this is a command alias, e.g. 'get-devices' */ item->description = g_strdup_printf(_("Alias to %s"), names[0]); } item->arguments = g_strdup(arguments); item->callback = callback; g_ptr_array_add(array, item); } } static gchar * fu_dfu_tool_get_descriptions(GPtrArray *array) { gsize len; const gsize max_len = 31; FuUtilItem *item; GString *string; /* print each command */ string = g_string_new(""); for (guint i = 0; i < array->len; i++) { item = g_ptr_array_index(array, i); g_string_append(string, " "); g_string_append(string, item->name); len = strlen(item->name) + 2; if (item->arguments != NULL) { g_string_append(string, " "); g_string_append(string, item->arguments); len += strlen(item->arguments) + 1; } if (len < max_len) { for (guint j = len; j < max_len + 1; j++) g_string_append_c(string, ' '); g_string_append(string, item->description); g_string_append_c(string, '\n'); } else { g_string_append_c(string, '\n'); for (guint j = 0; j < max_len + 1; j++) g_string_append_c(string, ' '); g_string_append(string, item->description); g_string_append_c(string, '\n'); } } /* remove trailing newline */ if (string->len > 0) g_string_set_size(string, string->len - 1); return g_string_free(string, FALSE); } static gboolean fu_dfu_tool_run(FuDfuTool *self, const gchar *command, gchar **values, GError **error) { /* find command */ for (guint i = 0; i < self->cmd_array->len; i++) { FuUtilItem *item = g_ptr_array_index(self->cmd_array, i); if (g_strcmp0(item->name, command) == 0) return item->callback(self, values, error); } /* not found */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, /* TRANSLATORS: error message */ _("Command not found")); return FALSE; } static FuDfuDevice * fu_dfu_tool_get_default_device(FuDfuTool *self, GError **error) { g_autoptr(GPtrArray) devices = NULL; /* get all the DFU devices */ self->usb_context = g_usb_context_new(error); if (self->usb_context == NULL) return NULL; g_usb_context_enumerate(self->usb_context); /* we specified it manually */ if (self->device_vid_pid != NULL) { gchar *tmp; guint64 pid; guint64 vid; g_autoptr(GUsbDevice) usb_device = NULL; /* parse */ vid = g_ascii_strtoull(self->device_vid_pid, &tmp, 16); if (vid == 0 || vid > G_MAXUINT16) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid format of VID:PID"); return NULL; } if (tmp[0] != ':') { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid format of VID:PID"); return NULL; } pid = g_ascii_strtoull(tmp + 1, NULL, 16); if (pid == 0 || pid > G_MAXUINT16) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid format of VID:PID"); return NULL; } /* find device */ usb_device = g_usb_context_find_by_vid_pid(self->usb_context, (guint16)vid, (guint16)pid, error); if (usb_device == NULL) { g_prefix_error(error, "no device matches for %04x:%04x: ", (guint)vid, (guint)pid); return NULL; } return fu_dfu_device_new(self->ctx, usb_device); } /* auto-detect first device */ devices = g_usb_context_get_devices(self->usb_context); for (guint i = 0; i < devices->len; i++) { GUsbDevice *usb_device = g_ptr_array_index(devices, i); g_autoptr(FuDfuDevice) device = fu_dfu_device_new(self->ctx, usb_device); if (fu_device_probe(FU_DEVICE(device), NULL)) return g_steal_pointer(&device); } /* failed */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no DFU devices found"); return NULL; } static gboolean fu_dfu_device_wait_for_replug(FuDfuTool *self, FuDfuDevice *device, guint timeout, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); g_autoptr(GUsbDevice) usb_device2 = NULL; g_autoptr(GError) error_local = NULL; /* close */ if (!fu_device_close(FU_DEVICE(device), &error_local)) g_debug("failed to close: %s", error_local->message); /* watch the device disappear and re-appear */ usb_device2 = g_usb_context_wait_for_replug(self->usb_context, usb_device, timeout, error); if (usb_device2 == NULL) return FALSE; /* re-open with new device set */ fu_usb_device_set_dev(FU_USB_DEVICE(device), usb_device2); if (!fu_device_open(FU_DEVICE(device), error)) return FALSE; if (!fu_dfu_device_refresh_and_clear(device, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_dfu_tool_parse_firmware_from_file(FuFirmware *firmware, GFile *file, FwupdInstallFlags flags, GError **error) { gchar *contents = NULL; gsize length = 0; g_autoptr(GBytes) bytes = NULL; if (!g_file_load_contents(file, NULL, &contents, &length, NULL, error)) return FALSE; bytes = g_bytes_new_take(contents, length); return fu_firmware_parse(firmware, bytes, flags, error); } static gboolean fu_dfu_tool_write_firmware_to_file(FuFirmware *firmware, GFile *file, GError **error) { const guint8 *data; gsize length = 0; g_autoptr(GBytes) bytes = fu_firmware_write(firmware, error); if (bytes == NULL) return FALSE; data = g_bytes_get_data(bytes, &length); return g_file_replace_contents(file, (const gchar *)data, length, NULL, FALSE, G_FILE_CREATE_NONE, NULL, NULL, /* cancellable */ error); } static gboolean fu_dfu_tool_replace_data(FuDfuTool *self, gchar **values, GError **error) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Available as `fwupdtool firmware-patch`"); return FALSE; } static void fu_tool_action_changed_cb(FuProgress *progress, gpointer dummy, FuDfuTool *self) { g_print("%s:\t%u%%\n", fwupd_status_to_string(fu_progress_get_status(progress)), fu_progress_get_percentage(progress)); } static gboolean fu_dfu_tool_read_alt(FuDfuTool *self, gchar **values, GError **error) { FuDfuTargetTransferFlags flags = DFU_TARGET_TRANSFER_FLAG_NONE; g_autofree gchar *str_debug = NULL; g_autoptr(FuDfuDevice) device = NULL; g_autoptr(FuFirmware) firmware = NULL; g_autoptr(FuDfuTarget) target = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GFile) file = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* check args */ if (g_strv_length(values) < 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected " "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID"); return FALSE; } /* open correct device */ device = fu_dfu_tool_get_default_device(self, error); if (device == NULL) return FALSE; if (self->transfer_size > 0) fu_dfu_device_set_transfer_size(device, self->transfer_size); locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; if (!fu_dfu_device_refresh(device, 0, error)) return FALSE; /* set up progress */ g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(fu_tool_action_changed_cb), self); g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_tool_action_changed_cb), self); /* APP -> DFU */ if (!fu_device_has_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("detaching"); if (!fu_device_detach_full(FU_DEVICE(device), progress, error)) return FALSE; if (!fu_dfu_device_wait_for_replug(self, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; } /* transfer */ target = fu_dfu_device_get_target_by_alt_name(device, values[1], NULL); if (target == NULL) { gchar *endptr; guint64 tmp = g_ascii_strtoull(values[1], &endptr, 10); if (tmp > 0xff || endptr[0] != '\0') { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse alt-setting '%s'", values[1]); return FALSE; } target = fu_dfu_device_get_target_by_alt_setting(device, (guint8)tmp, error); if (target == NULL) return FALSE; } /* do transfer */ firmware = fu_dfuse_firmware_new(); fu_dfu_firmware_set_vid(FU_DFU_FIRMWARE(firmware), fu_dfu_device_get_runtime_vid(device)); fu_dfu_firmware_set_pid(FU_DFU_FIRMWARE(firmware), fu_dfu_device_get_runtime_pid(device)); if (!fu_dfu_target_upload(target, firmware, progress, flags, error)) return FALSE; /* do host reset */ if (!fu_device_attach_full(FU_DEVICE(device), progress, error)) return FALSE; if (!fu_dfu_device_wait_for_replug(self, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; /* save file */ file = g_file_new_for_path(values[0]); if (!fu_dfu_tool_write_firmware_to_file(firmware, file, error)) return FALSE; /* print the new object */ str_debug = fu_firmware_to_string(firmware); g_debug("DFU: %s", str_debug); /* success */ g_print("Successfully uploaded from device\n"); return TRUE; } static gboolean fu_dfu_tool_read(FuDfuTool *self, gchar **values, GError **error) { FuDfuTargetTransferFlags flags = DFU_TARGET_TRANSFER_FLAG_NONE; g_autofree gchar *str_debug = NULL; g_autoptr(FuDfuDevice) device = NULL; g_autoptr(FuFirmware) firmware = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GFile) file = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* check args */ if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILENAME"); return FALSE; } /* open correct device */ device = fu_dfu_tool_get_default_device(self, error); if (device == NULL) return FALSE; locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; if (!fu_dfu_device_refresh(device, 0, error)) return FALSE; /* APP -> DFU */ if (!fu_device_has_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { if (!fu_device_detach_full(FU_DEVICE(device), progress, error)) return FALSE; if (!fu_dfu_device_wait_for_replug(self, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) { return FALSE; } } /* transfer */ g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(fu_tool_action_changed_cb), self); g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_tool_action_changed_cb), self); firmware = fu_dfu_device_upload(device, progress, flags, error); if (firmware == NULL) return FALSE; /* do host reset */ if (!fu_device_attach_full(FU_DEVICE(device), progress, error)) return FALSE; if (!fu_dfu_device_wait_for_replug(self, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; /* save file */ file = g_file_new_for_path(values[0]); if (!fu_dfu_tool_write_firmware_to_file(firmware, file, error)) return FALSE; /* print the new object */ str_debug = fu_firmware_to_string(firmware); g_debug("DFU: %s", str_debug); /* success */ g_print("successfully uploaded from device\n"); return TRUE; } static gboolean fu_dfu_tool_write_alt(FuDfuTool *self, gchar **values, GError **error) { FuDfuTargetTransferFlags flags = DFU_TARGET_TRANSFER_FLAG_VERIFY; g_autofree gchar *str_debug = NULL; g_autoptr(FuDfuDevice) device = NULL; g_autoptr(FuFirmware) firmware = NULL; g_autoptr(FuFirmware) image = NULL; g_autoptr(FuDfuTarget) target = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GFile) file = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* check args */ if (g_strv_length(values) < 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected " "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID " "[IMAGE-ALT-NAME|IMAGE-ALT-ID]"); return FALSE; } /* open file */ firmware = fu_dfuse_firmware_new(); file = g_file_new_for_path(values[0]); if (!fu_dfu_tool_parse_firmware_from_file(firmware, file, FWUPD_INSTALL_FLAG_NONE, error)) return FALSE; /* open correct device */ device = fu_dfu_tool_get_default_device(self, error); if (device == NULL) return FALSE; if (self->transfer_size > 0) fu_dfu_device_set_transfer_size(device, self->transfer_size); locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; if (!fu_dfu_device_refresh(device, 0, error)) return FALSE; /* set up progress */ g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(fu_tool_action_changed_cb), self); g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_tool_action_changed_cb), self); /* APP -> DFU */ if (!fu_device_has_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("detaching"); if (!fu_device_detach_full(FU_DEVICE(device), progress, error)) return FALSE; if (!fu_dfu_device_wait_for_replug(self, device, 5000, error)) return FALSE; } /* print the new object */ str_debug = fu_firmware_to_string(firmware); g_debug("DFU: %s", str_debug); /* get correct target on device */ target = fu_dfu_device_get_target_by_alt_name(device, values[1], NULL); if (target == NULL) { gchar *endptr; guint64 tmp = g_ascii_strtoull(values[1], &endptr, 10); if (tmp > 0xff || endptr[0] != '\0') { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse alt-setting '%s'", values[1]); return FALSE; } target = fu_dfu_device_get_target_by_alt_setting(device, (guint8)tmp, error); if (target == NULL) return FALSE; } /* allow overriding the firmware alt-setting */ if (g_strv_length(values) > 2) { image = fu_firmware_get_image_by_id(firmware, values[2], NULL); if (image == NULL) { gchar *endptr; guint64 tmp = g_ascii_strtoull(values[2], &endptr, 10); if (tmp > 0xff || endptr[0] != '\0') { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to parse image alt-setting '%s'", values[2]); return FALSE; } image = fu_firmware_get_image_by_idx(firmware, tmp, error); if (image == NULL) return FALSE; } } else { g_print("WARNING: Using default firmware image\n"); image = fu_firmware_get_image_by_id(firmware, NULL, error); if (image == NULL) return FALSE; } /* transfer */ if (!fu_dfu_target_download(target, image, progress, flags, error)) return FALSE; /* do host reset */ if (!fu_device_attach_full(FU_DEVICE(device), progress, error)) return FALSE; if (!fu_dfu_device_wait_for_replug(self, device, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, error)) return FALSE; /* success */ g_print("Successfully downloaded to device\n"); return TRUE; } static gboolean fu_dfu_tool_write(FuDfuTool *self, gchar **values, GError **error) { FwupdInstallFlags flags = FWUPD_INSTALL_FLAG_NO_SEARCH; g_autoptr(FuDfuDevice) device = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* check args */ if (g_strv_length(values) < 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid arguments, expected FILENAME"); return FALSE; } /* open file */ fw = fu_bytes_get_contents(values[0], error); if (fw == NULL) return FALSE; /* open correct device */ device = fu_dfu_tool_get_default_device(self, error); if (device == NULL) return FALSE; locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; if (!fu_dfu_device_refresh(device, 0, error)) return FALSE; /* APP -> DFU */ if (!fu_device_has_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { if (!fu_device_detach_full(FU_DEVICE(device), progress, error)) return FALSE; if (!fu_dfu_device_wait_for_replug(self, device, fu_device_get_remove_delay(FU_DEVICE(device)), error)) return FALSE; } /* allow wildcards */ if (self->force) { flags |= FWUPD_INSTALL_FLAG_IGNORE_VID_PID; flags |= FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM; } /* transfer */ g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(fu_tool_action_changed_cb), self); g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_tool_action_changed_cb), self); if (!fu_device_write_firmware(FU_DEVICE(device), fw, progress, flags, error)) return FALSE; /* do host reset */ if (!fu_device_attach_full(FU_DEVICE(device), progress, error)) return FALSE; if (fu_device_has_private_flag(FU_DEVICE(device), FU_DFU_DEVICE_FLAG_MANIFEST_TOL)) { if (!fu_dfu_device_wait_for_replug(self, device, fu_device_get_remove_delay(FU_DEVICE(device)), error)) return FALSE; } /* success */ g_print("%u bytes successfully downloaded to device\n", (guint)g_bytes_get_size(fw)); return TRUE; } #ifdef HAVE_GIO_UNIX static gboolean fu_dfu_tool_sigint_cb(gpointer user_data) { FuDfuTool *self = (FuDfuTool *)user_data; g_debug("Handling SIGINT"); g_cancellable_cancel(self->cancellable); return FALSE; } #endif int main(int argc, char *argv[]) { gboolean ret; gboolean verbose = FALSE; gboolean version = FALSE; g_autofree gchar *cmd_descriptions = NULL; g_autoptr(FuDfuTool) self = g_new0(FuDfuTool, 1); g_autoptr(GError) error = NULL; g_autoptr(GOptionContext) context = NULL; const GOptionEntry options[] = { {"version", '\0', 0, G_OPTION_ARG_NONE, &version, _("Print the version number"), NULL}, {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, N_("Print verbose debug statements"), NULL}, {"device", 'd', 0, G_OPTION_ARG_STRING, &self->device_vid_pid, N_("Specify Vendor/Product ID(s) of DFU device"), N_("VID:PID")}, {"transfer-size", 't', 0, G_OPTION_ARG_STRING, &self->transfer_size, N_("Specify the number of bytes per USB transfer"), N_("BYTES")}, {"force", '\0', 0, G_OPTION_ARG_NONE, &self->force, N_("Force the action ignoring all warnings"), NULL}, {NULL}}; setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); #ifndef SUPPORTED_BUILD /* make critical warnings fatal */ (void)g_setenv("G_DEBUG", "fatal-criticals", FALSE); #endif /* add commands */ self->cmd_array = g_ptr_array_new_with_free_func((GDestroyNotify)fu_dfu_tool_item_free); fu_dfu_tool_add(self->cmd_array, "read", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME"), /* TRANSLATORS: command description */ _("Read firmware from device into a file"), fu_dfu_tool_read); fu_dfu_tool_add(self->cmd_array, "read-alt", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID"), /* TRANSLATORS: command description */ _("Read firmware from one partition into a file"), fu_dfu_tool_read_alt); fu_dfu_tool_add(self->cmd_array, "write", NULL, /* TRANSLATORS: command description */ _("Write firmware from file into device"), fu_dfu_tool_write); fu_dfu_tool_add(self->cmd_array, "write-alt", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]"), /* TRANSLATORS: command description */ _("Write firmware from file into one partition"), fu_dfu_tool_write_alt); fu_dfu_tool_add(self->cmd_array, "replace-data", NULL, /* TRANSLATORS: command description */ _("Replace data in an existing firmware file"), fu_dfu_tool_replace_data); /* use quirks */ self->ctx = fu_context_new(); if (!fu_context_load_quirks(self->ctx, FU_QUIRKS_LOAD_FLAG_NONE, &error)) { /* TRANSLATORS: quirks are device-specific workarounds */ g_print("%s: %s\n", _("Failed to load quirks"), error->message); return EXIT_FAILURE; } /* do stuff on ctrl+c */ self->cancellable = g_cancellable_new(); #ifdef HAVE_GIO_UNIX g_unix_signal_add_full(G_PRIORITY_DEFAULT, SIGINT, fu_dfu_tool_sigint_cb, self, NULL); #endif /* sort by command name */ g_ptr_array_sort(self->cmd_array, (GCompareFunc)fu_dfu_tool_sort_command_name_cb); /* get a list of the commands */ context = g_option_context_new(NULL); cmd_descriptions = fu_dfu_tool_get_descriptions(self->cmd_array); g_option_context_set_summary(context, cmd_descriptions); /* TRANSLATORS: DFU stands for device firmware update */ g_set_application_name(_("DFU Utility")); g_option_context_add_main_entries(context, options, NULL); ret = g_option_context_parse(context, &argc, &argv, &error); if (!ret) { /* TRANSLATORS: the user didn't read the man page */ g_print("%s: %s\n", _("Failed to parse arguments"), error->message); return EXIT_FAILURE; } /* set verbose? */ if (verbose) (void)g_setenv("G_MESSAGES_DEBUG", "all", FALSE); /* version */ if (version) { g_print("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION); return EXIT_SUCCESS; } /* run the specified command */ ret = fu_dfu_tool_run(self, argv[1], (gchar **)&argv[2], &error); if (!ret) { if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL)) { g_autofree gchar *tmp = NULL; tmp = g_option_context_get_help(context, TRUE, NULL); g_print("%s\n\n%s", error->message, tmp); } else { g_print("%s\n", error->message); } return EXIT_FAILURE; } /* success/ */ return EXIT_SUCCESS; } fwupd-1.9.16/plugins/dfu/fu-dfu.rs000066400000000000000000000015461460375044200167430ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ enum DfuRequest { Detach, Dnload, Upload, Getstatus, Clrstatus, Getstate, Abort, } #[derive(ToString)] enum DfuStatus { Ok, ErrTarget, ErrFile, ErrWrite, ErrErase, ErrCheckErased, ErrProg, ErrVerify, ErrAddress, ErrNotdone, ErrFirmware, ErrVendor, ErrUsbr, ErrPor, ErrUnknown, ErrStalldpkt, } #[derive(ToString)] enum DfuState { AppIdle, AppDetach, DfuIdle, DfuDnloadSync, DfuDnbusy, DfuDnloadIdle, DfuManifestSync, DfuManifest, DfuManifestWaitReset, DfuUploadIdle, DfuError, } #[derive(ToBitString)] enum DfuSectorCap { None = 0, // No operations possible Readable = 1 << 0, Writeable = 1 << 1, Erasable = 1 << 2, } fwupd-1.9.16/plugins/dfu/meson.build000066400000000000000000000044031460375044200173440ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginDfu"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('dfu.quirk') # do not use structgen as these files are used in the dfu-csr plugin too... dfu_rs = custom_target('fu-dfu-rs', input: 'fu-dfu.rs', output: ['fu-dfu-struct.c', 'fu-dfu-struct.h'], command: [ python3, join_paths(meson.project_source_root(), 'libfwupdplugin', 'rustgen.py'), '@INPUT@', '@OUTPUT0@', '@OUTPUT1@', ], ) plugin_builtin_dfu = static_library('fu_plugin_dfu', dfu_rs, sources: [ 'fu-dfu-plugin.c', 'fu-dfu-common.c', 'fu-dfu-device.c', 'fu-dfu-sector.c', 'fu-dfu-target.c', 'fu-dfu-target-stm.c', 'fu-dfu-target-avr.c', ], include_directories: plugin_incdirs, c_args: cargs, dependencies: [ plugin_deps, libm, ], link_with: plugin_libs, ) plugin_builtins += plugin_builtin_dfu if get_option('compat_cli') fu_dfu_tool = executable( 'dfu-tool', dfu_rs, sources: [ 'fu-dfu-tool.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_dfu, ], c_args: cargs, install: true, install_rpath: libdir_pkg, install_dir: bindir ) endif if get_option('compat_cli') and get_option('man') custom_target('dfu-tool.1', input: 'dfu-tool.md', output: 'dfu-tool.1', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, ], install: true, install_dir: join_paths(mandir, 'man1'), ) endif if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'fu-dfu-self-test', dfu_rs, sources: [ 'fu-dfu-self-test.c' ], include_directories: plugin_incdirs, dependencies: [ plugin_deps, libm, ], link_with: [ plugin_libs, plugin_builtin_dfu, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('fu-dfu-self-test', e, env: env) # added to installed-tests endif plugindfu_incdir = include_directories('.') endif fwupd-1.9.16/plugins/ebitdo/000077500000000000000000000000001460375044200156715ustar00rootroot00000000000000fwupd-1.9.16/plugins/ebitdo/README.md000066400000000000000000000031071460375044200171510ustar00rootroot00000000000000--- title: Plugin: 8BitDo --- ## Introduction This plugin can flash the firmware on the 8BitDo game pads. Ebitdo support is supported directly by this project with the embedded libebitdo library and is possible thanks to the vendor open sourcing the flashing tool. The 8BitDo devices share legacy USB VID/PIDs with other projects and so we have to be a bit careful to not claim other devices as our own. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. The binary file has a vendor-specific header that is used when flashing the image. This plugin supports the following protocol ID: * `com.8bitdo` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_2DC8&PID_AB11` ## Update Behavior The device usually presents in runtime mode, but on detach re-enumerates with a different USB VID and PID in a bootloader mode. On attach the device again re-enumerates back to the runtime mode. For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. ## Vendor ID Security The vendor ID is set from the USB vendor, which is set to various different values depending on the model and device mode. The list of USB VIDs used is: * `USB:0x2DC8` * `USB:0x0483` * `USB:0x1002` * `USB:0x1235` * `USB:0x2002` * `USB:0x8000` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `0.8.0`. fwupd-1.9.16/plugins/ebitdo/data/000077500000000000000000000000001460375044200166025ustar00rootroot00000000000000fwupd-1.9.16/plugins/ebitdo/data/m30.txt000066400000000000000000000175211460375044200177500ustar00rootroot00000000000000Bus 002 Device 008: ID 2dc8:5006 8BitDo M30 gamepad Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x2dc8 8BitDo idProduct 0x5006 M30 gamepad bcdDevice 0.01 iManufacturer 1 8Bitdo iProduct 2 8BitDo M30 gamepad iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0029 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 480mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 123 Report Descriptor: (length is 123) Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x05 ] 5 Gamepad Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0x01 ] 1 Item(Global): Physical Minimum, data= [ 0x00 ] 0 Item(Global): Physical Maximum, data= [ 0x01 ] 1 Item(Global): Report Size, data= [ 0x01 ] 1 Item(Global): Report Count, data= [ 0x0f ] 15 Item(Global): Usage Page, data= [ 0x09 ] 9 Buttons Item(Local ): Usage Minimum, data= [ 0x01 ] 1 Button 1 (Primary) Item(Local ): Usage Maximum, data= [ 0x0f ] 15 (null) Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x01 ] 1 Constant Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Global): Logical Maximum, data= [ 0x07 ] 7 Item(Global): Physical Maximum, data= [ 0x3b 0x01 ] 315 Item(Global): Report Size, data= [ 0x04 ] 4 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Global): Unit, data= [ 0x14 ] 20 System: English Rotation, Unit: Degrees Item(Local ): Usage, data= [ 0x39 ] 57 Hat Switch Item(Main ): Input, data= [ 0x42 ] 66 Data Variable Absolute No_Wrap Linear Preferred_State Null_State Non_Volatile Bitfield Item(Global): Unit, data= [ 0x00 ] 0 System: None, Unit: (None) Item(Global): Report Count, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x01 ] 1 Constant Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x30 ] 48 Direction-X Item(Local ): Usage, data= [ 0x31 ] 49 Direction-Y Item(Local ): Usage, data= [ 0x32 ] 50 Direction-Z Item(Local ): Usage, data= [ 0x35 ] 53 Rotate-Z Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x04 ] 4 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x02 ] 2 Simulation Controls Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0xc4 ] 196 Accelerator Item(Local ): Usage, data= [ 0xc5 ] 197 Brake Item(Global): Report Count, data= [ 0x02 ] 2 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x08 ] 8 LEDs Item(Local ): Usage, data= [ 0x43 ] 67 Slow Blink On Time Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Physical Minimum, data= [ 0x00 ] 0 Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x02 ] 2 Item(Main ): Output, data= [ 0x82 ] 130 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Volatile Bitfield Item(Local ): Usage, data= [ 0x44 ] 68 Slow Blink Off Time Item(Main ): Output, data= [ 0x82 ] 130 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Volatile Bitfield Item(Local ): Usage, data= [ 0x45 ] 69 Fast Blink On Time Item(Main ): Output, data= [ 0x82 ] 130 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Volatile Bitfield Item(Local ): Usage, data= [ 0x46 ] 70 Fast Blink Off Time Item(Main ): Output, data= [ 0x82 ] 130 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 5 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 16 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/ebitdo/data/nes30pro.txt000066400000000000000000000107671460375044200210270ustar00rootroot00000000000000Bus 003 Device 017: ID 2002:9000 DAP Technologies Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x2002 DAP Technologies idProduct 0x9000 bcdDevice 0.01 iManufacturer 1 8Bitdo NES30 Pro iProduct 2 8Bitdo NES30 Pro iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 480mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 123 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 32 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 16 Device Status: 0x0000 (Bus Powered) Bus 003 Device 019: ID 0483:5750 STMicroelectronics Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x0483 STMicroelectronics idProduct 0x5750 bcdDevice 2.00 iManufacturer 1 8BitdoJoy iProduct 2 8Bitdo iSerial 3 BootMod bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 480mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 33 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 32 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 16 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/ebitdo/data/scf30.txt000066400000000000000000000106001460375044200202560ustar00rootroot00000000000000 Bus 002 Device 053: ID 1235:ab21 Focusrite-Novation Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x1235 Focusrite-Novation idProduct 0xab21 bcdDevice 0.01 iManufacturer 1 iProduct 2 iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 480mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 99 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 32 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 16 Bus 002 Device 055: ID 0483:5750 STMicroelectronics Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x0483 STMicroelectronics idProduct 0x5750 bcdDevice 2.00 iManufacturer 1 iProduct 2 iSerial 3 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 480mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 33 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 32 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 16 fwupd-1.9.16/plugins/ebitdo/data/sf30pro.txt000066400000000000000000000303401460375044200206370ustar00rootroot00000000000000Start + Y --------- Bus 001 Device 008: ID 057e:2009 Nintendo Co., Ltd Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x057e Nintendo Co., Ltd idProduct 0x2009 bcdDevice 2.00 iManufacturer 1 Nintendo Co., Ltd. iProduct 2 Pro Controller iSerial 3 000000000001 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 No Subclass bInterfaceProtocol 0 None iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 203 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 8 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 8 Device Status: 0x0000 (Bus Powered) Start + B ------------ Bus 001 Device 009: ID 2dc8:6000 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x2dc8 idProduct 0x6000 bcdDevice 0.01 iManufacturer 1 8Bitdo SF30 Pro iProduct 2 8Bitdo SF30 Pro iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 480mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 No Subclass bInterfaceProtocol 0 None iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 123 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 5 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 16 Device Status: 0x0000 (Bus Powered) Start + A --------- Bus 001 Device 012: ID 054c:05c4 Sony Corp. DualShock 4 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x054c Sony Corp. idProduct 0x05c4 DualShock 4 bcdDevice 1.00 iManufacturer 1 Sony Computer Entertainment iProduct 2 Wireless Controller iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 No Subclass bInterfaceProtocol 0 None iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 499 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 5 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x03 EP 3 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 5 Device Status: 0x0000 (Bus Powered) Start + X --------- Bus 001 Device 013: ID 045e:028e Microsoft Corp. Xbox360 Controller Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 255 Vendor Specific Class bDeviceSubClass 255 Vendor Specific Subclass bDeviceProtocol 255 Vendor Specific Protocol bMaxPacketSize0 8 idVendor 0x045e Microsoft Corp. idProduct 0x028e Xbox360 Controller bcdDevice 1.14 iManufacturer 1 8Bitdo SF30 Pro iProduct 2 Controller iSerial 3 (error) bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 153 bNumInterfaces 4 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 93 bInterfaceProtocol 1 iInterface 0 ** UNRECOGNIZED: 11 21 00 01 01 25 81 14 00 00 00 00 13 01 08 00 00 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 4 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 8 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 4 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 93 bInterfaceProtocol 3 iInterface 0 ** UNRECOGNIZED: 1b 21 00 01 01 01 82 40 01 02 20 16 83 00 00 00 00 00 00 16 03 00 00 00 00 00 00 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 2 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 4 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 64 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x03 EP 3 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 16 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 93 bInterfaceProtocol 2 iInterface 0 ** UNRECOGNIZED: 09 21 00 01 01 22 84 07 00 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 16 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 3 bAlternateSetting 0 bNumEndpoints 0 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 253 bInterfaceProtocol 19 iInterface 4 µ∡H釰俸샴`翰⣸贠øĀ贤Ǹɀ败˸赐ϸ桀F㏰៸贠ø贀Ǹ赀˸赐ϸ桀F⟰ ** UNRECOGNIZED: 06 41 00 01 01 03 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/ebitdo/data/update.tdc000066400000000000000000017532571460375044200206040ustar00rootroot00000000000000TPDC?cQW u  .% 1cQW 7  l @`.   `  9!- -`-  `8 @8`@ 8 @@q@@8m]`@ 8q8x8q`\8J qA$@q8q>`68̏8ֵq ' YxAa@vH@H@ `   @@3K@ @F@@`6@@`@` J À@;@ݔCB1@7JEJ J `J@ A-FU@BpB@B i @' `AOBAR `CEa¨R J 8yyaR @@[=G RU )@@ `@# /@ Kc.@PW S$A\AT>T `T\i\B=@BA~`8@B i\b1@AA\XE( 5E\@z{a\ sya"d@x@ !\"@"@BKBBJ >@JBp@U `  Jb@AAJ#`E53@@`;J J 8||E @=D=`=$8&`v` 8CZ`qi8aE@=|=`v!E@谀@g%BEJTJ  a ` B@x@A B@B E?k>`VM@aR @=m=#MFL@{l@ U!@B! B J B" "@B@Ua@ i@x@AAJFE&?aJPEJ@7aJ$ @p=#U`=$WE@@3b% @ @!@@@`@LHAR&J]J J `J @D-GH'`B$ @x@A AR( A@܀@R@aR) @=8= H*@T݀@H@ `@!H+ qT `T\i\, B@BHB@x@AA\- @EVbq \a\K^=#/ |@˘@~!\"0B7BBJ1 B@Bo@a@x@AAJ2`CE!SWaJaJ3 9@ptX`=J! 4@@a)"A5JHJ `J6 B@Bt dR@x@A AR7 .@E/ʀ aR8 ;R7р=l.n@nˀ@M"$;: /@CK )". .E !"!@  As@; = ; >@BN @x@A-As< @EYbZas= ;CZ`=$.@lB@!s ?BAB@BZ@x@AAJ/`EaJB Bm B@B@x@AAJn`CEJaJo 9 f$J#p <@i@!.# _4AqJյJ qr +@B@x@A ARs`CEpgaaRt 9q= cAu@T@ Z ,v B@B@JK[@x@Aw @A;,ha$APax 2k`=#`bVy@@!V |NzJ J  V{ +@ Bq! BS@x@A AR| .@EՀVaR} ; ՠ=ڀ= V(&R&Q~ < 8@׀@6N PT|րT!/UBQ@x@AA\Eclbq\a\ =+=&Q@@ QBBQ B@JB9@x@AAJ`CELmaJQaJ@JX n`=J&QC@ @ d @x@A AS @E}obh@@aS @=E=#@@A~S& C +B B@B@x@AAJ`CE;paJaJ 9W=J#@@$)AJ/J  +@RB@x@A AR`CEqaRbr aR 9R!=R#,"@Y@M@k  B@sB,-@x@A-As @Emrasdmsas \ =do=s#s$ <@n@ {%e @ B<B B@B@x@AAJE)saJTj:{ = =J$@@ @{'%A @J Z +@B@x@A AR`CE,taR @  AR 9  =" <@h@ "$R B J B@JB )@x@AAJ @E[uJJ" ; {^="@\@! =  A SJ  +@ B@x@A AR`CE9vaR Y!R 9j= R@ ~Z0 B@B@B@x@A @AҀP! ; =gwaA /%?$ <@Ϗ@  "6J;J$V B@B5@x@A AR`CEIxaVt!R 9O=R$R7@ K@ J$ 7K  ;G &u@  fC +iJcwkBSS<@x@A%AkETyakAk =€=k&`@ @ s!k @ `Bw B B@JB@x@AAJ`CEa|zaJv@ } #AJ 9Jp`=1@@!~R$ @^@!*ϰ@G :H ! XO +@Bo  @x@ADA .@E2aLk-lE@-b1A YS  A! .@FQ@ " @K@;0>A @@FP$ B@Bp@x@ADAaZ E TaLA( aL21@@=@: =Va&h=" R|@ & V^`{ @6N@ @  @*rA :@@M &) SiCS B@`BBA': `#TS@x@ADA @`E_aL3@$@d?Z!Q R@ B@x@ADRAKuV "B@l@dCk@m D B@x@ADREKaR"KF@@dG$RH BR@x@ADRIKBaR"J@`@dRK#L BR@x@ADRMKKa"RN@DC@dOB#RP BR@x@ADRQKn'RR@ي@dSE$RT B@x@ADRUKۺaR"KV@nҀ@dRWѠ#X B@x@ADRYK"aR"Z@@d[p#R\ BR@x@ADR]K1j'R^@a@d_ #R` BR@x@ADRaKñ'Rb@/@dc d BR@x@ADReKYaR"Kf@@dg0$Rh BR@x@ADRiK@aR"Kj@Y8@dRk7#l BR@x@ADRmKt'Rn@@do[#Rp BR@x@ADRqKhaR"r@ǀ@dsƀ$Rt BR@x@ADRuK]'Rv@@dRw#x BR@x@ADRyKC_QaR"Kz@V@d{#R| BR@x@ADR}KئE'R~@D@d#R BR@x@ADRKn9'R@@dF  BR@x@ADRK6.aR"@o-@d,$R BR@x@ADRK}"aR"K@u@dRpt# BR@x@ADRK.'R@@d#R BR@x@ADRK 'R@/@d$R B/@x@ADRKYTUA"K@K@dR0@ B@x@ADRKaR"K@Z@dƒ#R BR@x@ADRK'R@ڀ@d[#R BR@x@ADRK+'R@"@d!  BR@x@ADRKraR"K@j@di$R BR@x@ADRKCaR"K@@dR# BR@x@ADRK'R@E`@d#R BR@x@ADRKoIa"@@@dF$R B@x@ADRKaR"K@o@dRۇ# B@x@ADRKؕaR"@Ѐ@dqϠ#R BR@x@ADRK. 'R@@d#R BR@x@ADRKg~'R@4_@d^ BR@x@ADRKZraR"K@Ŧ@d1$R BR@x@ADRKfaR"K@Z@dR# BR@x@ADRK>['R@5@d\#R BR@x@ADRKO'R@}@d|$R B@x@ADRKCaR"K@ŀ@dRĠ# B@x@ADRKD8aR"K@ @d#R BR@x@ADRK\,'R@ET@dS#R BR@x@ADRKo 'R@ۛ@dG  BR@x@ADRK aR"K@t@d $RBR@x@ADRK3 aR"K@+@dRq*# BR@x@ADRK/{Z"R@r@d#R BR@x@ADRK'R@0@d$R B@x@ADRKZ aR"K@@dR1# B@x@ADRKQaR"@[I@dH#R BR@x@ADRK'R@@d\#RBR@x@ADRK'R@؀@dנ!(  BR@x` =R !&{(aR"K@ @d$R BR@x@ADR KDpaR"K @g@dR "( BR@x@ADR Kڷ'R@F@d!Q R BR@x@ADRKp'R@@dG$R B@x@ADRKGaR"K@p>@dR="  B@x@ADRK|aR"K@@dr!Q R BR@x@ADRK/p'R@̀@d$R BR@x@ADR!KeaR""@1@d# /$ B M/t@x@ADR%K[eY'R&@\@d'2$R( B@x@ADR)KMaR"K*@[@dR+ǣ#, BR@x@ADR-KA'R.@@d/]#R0 BR@x@ADR1K<6'R2@3@d32$R4 B@x@ADR5K*aR"6@{@dR7z#8 B@x@ADR9KEaR"K:@€@d;#R< BR@x@ADR=K'R>@F @d? $R@ BR@x@ADRAKpZ'RB@Q@dCH D BR@x@ADREKC"KF@q@dGݘ$RH BR@x@ADRIKaR"KJ@@dRKrࠂ#L BR@x@ADRMK01'RN@(@dO#RP BR@x@ADRQKx'RR@1p@dSo$RT B@x@ADRUK[aR"KV@Ʒ@dRW2#X B@x@ADRYKaR"KZ@\`@d[#R\ BR@x@ADR]KOa"R^@F@d_]$R` BR@x@ADRaK'Rb@@dc򍀂$d BK@x@ADReKޝaR"Kf@ր@dgՀ$h B@x@ADRiKE&aR"j@@dRk  @LAl BR@x@ADRmKm'Rn@Ge@dod!J Rp BR@x@ADRqKpzaR"r@ܬ@dsH$Rt B{@x@ADRuKn'Rv@q@dRw" x B@x@ADRyKDcaR"Kz@<@d{s;!Q R| BR@x@ADR}K0W'R~@@d$R BR@x@ADRKK'R@2ˀ@dʀ$ B@x@ADRK\@aR"@@d3$ B@x@ADRKb4aR"K@\Z@dRY  BR@x@ADRK('R@@d^#R BR@x@ADRK'R@@d耂$R BR@x@ADRK9aR"K@1@dR0# BR@x@ADRKFaR"K@x@d#R BR@x@ADRK`"R@G@d$R BR@x@ADRKq'R@@dRH  B@x@ADRKXaR"K@rO@dRN$ B@x@ADRKaR"@@dRs# BR@x@ADRK1'R@ހ@d #R BR@x@ADRK.'R@2&@d%$R BR@x@ADRK\vaR"K@m@dR3# BR@x@ADRKaR"K@]@dɴ#R BR@x@ADRK'R@`@d^$R BR@x@ADRKMa"@D@dRC  B@x@ADRKaR"K@@d#R B@x@ADRKFxaR"@Ӏ@d#R BR@x@ADRK#m'R@H@d#R BR@x@ADRKqka'R@b@dI$R BR@x@ADRKUaR"K@r@dRީ# BR@x@ADRKIaR"K@@dt#R BR@x@ADRK1B>'R@9@d #R BR@x@ADR K@ p,w4 @AC=DEGBHIKGLMOLPQSQTUWVXZ[[\^ _` | x t p l h d ` \ X T P L H D @ < 8 4 0 , ( $     !'                                  | x t p l h d ` \ X T P L H D @ < 8 4 0 , ( $   A @ avm k@Y@ `@JyX F B@aB b {@x@AD>EowaL@@==AC.)`A =^`{ @ V@ @a<.whU@Tp `mi B@B |!@x@ADAESaLAxA'AA =@`= S@@ SK=y !7 `S B@B @x@ADAEˁ kBQA =ҍa"@ɀ@'# }(:J0%Lg%IC%w0s*CZ B@Bt @x@ADAEDaLR'`c =Ϙ`="@ƀ@K=>  B@B !@x@ADAE(aL) a/a =="@X@ 1_C  B@Bu @x@ADAEA觠A[c @ B@B@x@ADAEbaL 7?D =l="@d@ !$1=oc a B@B@x@ADAEZad = %`=!I @n@ ' @ADj$5LgI65:J6 xD! " B@Bx@x@ADA#Eր7c$ ="$_%@^@ ! @=& c @' B@B@x@ADA(EӀ@Y ?a) =y݀="*@Ԁ@ /C+H$, B@By@x@ADA-E/bod. =ߕ`="/@G@ #"2R{yI2 cZA5 `$6 B@B@x@ADA7E}DaL@Yc8 =EN="9@E@A/XC:+; B@Bx@x@ADA<Eaa= =X@!I>@`@ +Q2.8wrm͸ @ 8A? @ B@BA@x@ADAAEoL z=B =aI$_C@ `@E @>ADh E B@B 5$@x@ADAFESL A8!!G = = H@@%A/CI `+J B@B@x@ADAKEpb !L =w`="M@n@&{$5pow 0y:ae6~вN!XANY$+O B@B@x@ADAPED)aL8 AQ =t%`=+$_R@k@ @>AS=A[T B@B@x@ADAUE(&&aL!(!(AV =/="W@X'@ *$҃/tCX Y B@B@x@ADAZE "1A[ =g1a!I\@߀@ +AYHL~x cҼN8kA[]. ^ B@B P@x@ADA_E2aL2(@c` =W<`=$_a@܀@ ! @>Ab c B@B@x@ADAdE=aLA0A8Ce =Ҡ="f@-@#V/2DgAh B@B@x@ADAiER>aB!)Aj =0YI`="k@P@&޺[b g/q0ɷ[E O$R4a& ]Al$m B@B@x@ADAnE JaLR0`co =,VT`="p@M@ '>AqL Ar B@B@x@ADAsEUaL) aaCt =="u@ @&/Cvnw B@B@x@ADAxEYÀbqAy =`a%z@m@%AB޴62\VpB Hqɐ&dY treA{ A| B@B|+@x@ADA}E{aaLrc~ =k`=$_@3]@]@d A B@BA@x@ADAExlaL C =AVx="@y@ &+#V(-`C A/I@GD B@B{@x@ADAE.4maA =:x`=!I@B2@ +,W @ $fxL>lAyO#" ^Ի ma& r1$+ B@B@x@ADAE쀈AA =7a @2/@dA.A[ B@B@x@ADAE}逈 0A =E="@@ /r B@BA@x@ADAEbAA =`=$_@@ &Ɗwtא1'oD:a& j A B@B@x@ADAEn]aLc =`= @@dAg  B@B@x@ADAERZaL j =d="@[@&$/)  B@B:@x@ADAEaD =`="@@&mhX5;ˌhiҖYl[V/a& AY B@B@x@ADAEC΁ c =a"@@dA=  B@B@x@ADAE'ˡ @Y  C =Ԁ="@Ẁ@&/XC  B@B@x@ADAEbA =_`= A@„@ AA!s.&'xzv33Xa& *+A. A B@B@x@ADAE?aL A =Z`= A@@dAA[ B@B@x@ADAE;aL 1A =E= @-=@ /C< B@ B@x@ADAEl a =@a"@@ ?ا{+2B:W U߬xA- cs B@x@ADA @EaLA1 a = ?=,`=+ @@dA Œ!]a B@B@x@ADAEҬ4 Ax!!D =@="@@ /Cn B@B@x@ADAEYhaA"&,A =o`= +@mf@&g] '=7pU 3=*P?a/7#N/a& =Ae$ B@B@x@ADAE aL2@c =l`= @]c@dAb$ B@B@x@ADAEaL A9AC =p'="@@&$/CC+ B@B:@x@ADAE.ف BQA =7"@B׀@%Awm8j.vs:Ԉυx9mBUAր$ B@Bg@x@ADAEaLR)`!A =`="@2Ԁ@dAӀ A B@B@x@ADAE|aL) aac =M="@@ }A/yC B@B}A@x@ADAEJab)qc =P`= A@H@ +th;"w܀:Fq{AI3a& S?AG + B@BA@x@ADAEnaL r1a =M'`= A@E@dAgDA[m@ B@BA@x@ADAER @sd =3 @츀@}妠V~&jwm&H@ U@ gCX$ B@B@x@ADAECs4aL1c>`= @ܵ@d<  B@B`x@9 A @E'p?aLm =b2K`= @)@Ъ1C. + B@DB@x@ADA E J =V/V  @&@d+ $ B@B@x@ADAE @Y+Axc =@="@,@+=ဂ B@B@x@ADAEWb )C =3b`=A @@ :L ;3jRUtiO(ũQD$ B@B:@x@ADAETcaLd =+m`= @@d疀 m B@Bm@x@ADAEQnaL)  c =[="@S@ gm/|C mR"e! B@B@x@ADA"EX oau# = z`= $@l @ +"SѻNV]#_|ŭU*a& A% & B@B+@x$ 9ADA' @EŀmF+( =a )@`@dA*Ac @+ B@DB@x@ADA,E A- = ẁ=".@À@ W/EC/C 0 B@BW@x@ADA1E-~b*A2 =ڄ`= 3@A|@&+? B@B$@x@ADA@E A"1"DA =a B@@ @ h+td^F挼nL[s,[."'FdAC젂@4D B@B sd  @'@x@ADAEEmaL2@dF =`= G@@ @=9~Hf适$I B@AbB@x@ADAJEQaL AAcK =="L@@+ @/LHCM N B@B4@x@ADAOE_aB QDP =f`="+Q@]@& ja`!-lI)QTƘ` ARXS B@B+@x@ADATEBaLR`dU =c`=$_V@Z@ @=W< X B@B@x@ADAYE&aL) a acZ = ="[@V@&A/rC\ ] B@BW@x@ADA^EЁ b:q D_ =^a%`@΀@ ~ ,Lо:6() aSnX>]b[z@Aa- Ab B@B@x@ADAcEaLrdd =U`=$_e@ˀ@dAf g B@BA@x@ADAhEaL :ci =ȏ="j@,@ :/Ckl B@Bw@x@ADAmEAa*tn =CH`= o@?@% 4_H͇pȒqe 'ؒdp-q B@B@x@ADArE Ads =/Ea t@<@dAu;A[v B@B$@x@ADAwE *cx =a"y@~ /fdzm{ B@B@x@ADA|EXLAj} =Z@ ~@l@ +A'>oݩ3VxY:0 9@ DF+د$ B@B+@x@ADAEjaLd =`= @\@dA$ B@BA@x@ADAEgaL c =!sq= @h@ W/CB B@BW@x@ADAE-#a:c =)`="+@A!@!0<&k^0jxq ;F3٣۲ h&a& /A @0 B@B@x@ADAEۀa =&)a @1@ # @>  B@B@x@ADAE{؀) :a =@="@ـ@$/ٌCA[ B@Bc'@x@ADAE*b2}P = 5`=G d@@ AIBQJ;:RzJ_ W&@Z A  B@B$@x@ADAEmL6aLT =@`=$_@ @+ @=$f  B@B@x@ADAEQIAaL 3+A =S="@J@ :&+/XC A B@B@x@ADAEBa A = M`=!I@@&­c`&.i:ʫXa W$+ B@B@x@ADAEB A d =Xa$_@W`@ @>A;A[ B@B@x@ADAE&L!!c = À="@V@&/   B@B@x@ADAEuYbA"1D =]|d`= @s@&:p2A)4^Tĝ?^s,u_W"-a& F+- B@B@x@ADAE.eaL2@d =Uyo`=$_@p@dA$ B@B@x@ADAE*paL AAAc =4= @+,@&/RC+ B@B@x@ADAE怈B#QD =6{a"+@@ v̠-t.ɸӟ>/^H&Ia& اA$ B@B@x@ADAE|aLR`d =*`= @@dA  B@B@x@ADAEЛaL) a#ac =="@@ /.Cl B@B"Y@x@ADAEWWab+qj =!A ^`= +@kU@ {{mԟLP| z"BS(*y "AT  B@B@x@ADAEaLrd =Z`= @\R@dAQA[ B@BA@x@ADAE aL +c =w="@ @ .CB B@B@x@ADAE,ȁ #f+ =Ϊa @@ƀ@ q,gebjQvG51'L|'䫼a& 1AŠ@0`)  ` B@Bq@x@ADAEaLAd = ˵`= @4À@ # @J  A B@B$@x@ADAE{}aL #c =?="@~@#V%o/C B@B@x@ADAE9aA;3M =?`=!I@7@ +T7p@pR3ơW=|0\ a& A6$ B@B+@x@ADAEld =<$_@4@dAe3$ B@BA@x@ADAEP r =="@@&.@C  B@B@x@ADAEשbc = P`=H  @맀@&c' wjVQ"uK깓xRW5A AW+ B@Bc'@x@ADAEAbaLc =`= @ۤ@ @=g ; A B@B@x@ADA E%_aL4c =\!`="@@ *''W dr_H:Ƶ'C]N0EL2@d1 =*aI$_2@[@dA3$4 B@B@x@ADA5E+aL A B@B@x@ADA?E%8aLR`d@ =pB`= A@0h@ ! @JABg AC B@B@x@ADADEz"CaL a$acE =O,="F@#@/|9CGH B@Bt @x@ADAIEށ b qjJ =Na";@܀@$҃5ȋMΣ S./g6v?d @`a& ALۀ +M B@B$@x@ADANElOaLAYirdO =Y`=$_`= @3P@ـ@dAQeؠ@mR B@BA@x@ADASEPZaLE cT =AV =+ AU@@Ġ/nCV W B@Bw@x@ADAXEN[a$IAY = Uf`= Z@L@ +' @Ġ LYC )c:#T,jr#%A[V$+\ B@B+@x@ADA]EAgaLd^ =Rq`= _@I@dA`: a B@B}@x@ADAbE%raL Acc = ="d@U@ /] Ce +f B@BW@x@ADAgE A$ˆH)d̴1X5A@4A B@BM@x@ADAEk;aL2?A =`="@~@ #$>1e} `au-j B@B @x@ADAEO8aL A%AA =B= @9@ @/.C + B@B @x@ADAE BQf+ =a"@@&uEFo/x}JطQL)d/a& gAV+ B@B@x@ADAE@aLR:$ =~`=$_@@ &+ @>J: ` B@B@x@ADAE$aL aac =="@T@&/KC  B@B@x@ADAEdab%q%C = gk \ #%@b@zLg ~MV*xRpgD_lp |A+ + B@BzL@x@ADAE aLr5$ =Sh`=$_@_@ @>AA[ B@B@x@ADAEaL %%A =#=+)@*@ * @A/PC B@B Y@x@ADAEՀ=A = 5!a!I@Ӏ@&('y0~eGP# * xlA- B@B@x@ADAE"aLg/$ = {-,`=$_@Ѐ@dAϠ B@B@x@ADAEϊ-aL =c =="@@ /@Ck B@B@x@ADAEVF.aA5C =M9`="@jD@ ,W$+A-əir}ndBYd;D}&bAC$ B@Bt+@x@ADAE+J$ =IDa"@ZA@dA@ B@B 'M@x@ADAE c =mE"+@~&/C@ +`m0< B@B@x@ADAE+L=C = Pa"@?@&Z{gdFEHЬKii:ut%DK(A  B@BA@x@ADAEoQaLA$$ = ׺[`="@3@dA  B@B@x@ADAEyl\aL c =Jv="@m@&A/YCA B@B@x@ADAE(]a5%C = 5.h`="@&@&l A؄9;$K:3݁=]8ƭJA%  B@B@x@ADAEk%A = +s @#@ ,W @>d"  B@B@x@ADAEO 6&A ==+"A @ހ@ @҃/C   B@B@x@ADA E՘tbp !  @A =`="@햀@ CX5ܝUzAT fB vndR@ AY$+ B@BM@x@ADAE@QaL d = ~`=$_@ٓ@ ! @>9 Œ B@B@x@ADAE$NaL)Ax!!c =@W="@TO@&/~C  B@B@x@ADAE aA">1>D = _`="@@ :#+zG mܡ /$ oW_4ћ.A+ B@B@x@ADA$h`E 2@d! =S a""@@ PA# $ B@DB@x@ADA%E  A6AiA& =Ȁ="+'@)@&/C( +) B@B@x@ADA*EzbBQc+ = 0`=",@x@&-Av Bq;cL)Z;4A- A-. B@Bx B w6@x@ADA/E2aLR6`c0 = {(~`=)1@u@ &+ @JA2t 3 B@B@x@ADA4E/aLaqG5 =`="6@i@A}:Q&lۙjgmRS俕8G>7zL7耂 8 B@B@x@ADA9EaLrd: = `=";@Y@ P<倂 = B@BW@x@ADA>EaL c? =p= )@ԡ@+/zLA@+B B@Bw+@x@ADACE*\a}.DD =b`= E@>Z@&j.="<6%bbf GaGFY$AG B@BM@x@ADAHEaLdI =_`= J@2W@ P1?VA[L B@B}@x@ADAMEyaL .cN =E="O@@ !#VAĠ/MCP+`B@x@ADARÉ WcS = 5a"T@ˀ@ 'AT? CA R<%A#6| z#AUʀ$V B@B@x@ADAWEjaLaX = `="Y@Ȁ@ !$>:Zdǀ [ B@BW@x@ADA\EN]@T a] ==G@Bgb^@~@ /C_ +W` B@B@x@ADAaE=a6IAb =D `="c@;@&ݾm.a/\'d bxV[ !;a& AdU#bXe B@Bg@x@ADAfE? cg =}Aa$_h@8@ # @(&_a,Ai9 j B@@B@x@ADAkE#  6cl =="m@W@&/)Aná$o B@B@x@ADApEb>cq =^#`="r@@ 'AT]S#eCW E9@V~|Kp9:a&  As* t B@B+@x@ADAuEg$aLޗv =R.`=$_w@@ ! @>Ax y B@B@x@ADAzEc/aL A?a{ =m=+"|@)e@ &+ @/ C}d~ B@B@x@ADAE0aIA5 (&;`=!I@@ @ m;ߠθk }q 2!/0= IA@2i7 A B@BM@x@ADAE׀  c =#Fa$_@@ `@>A  B@B@x@ADAEԀ?!!C =ހ="@ր@҃/CnՀ$ B@B`@x@ADAEUGbA"'1'A =R`="@i@ 'g=x"sT1 R]hB!d"  ֧AՍ$ B@B@x@ADA?j`EHSaL2@d =]`="@X@ !$>A  B@DB@x@ADAEE^aL) AA'Ac =lO="+@F@&/ li? B@B@x@ADAE*_aB?Qa =j`="@>i`@ '# *;C"EnB"D;  B@B(@x@ADAELR`d =uaI$_@.t`@ `@>A  B@B@x@ADAExL a?ac =M="@@ (!$A/C B@Bg@x@ADAEqvbbqIA =x`="@p@ 'iuc?T#J *T(a& Ao  B@B@x@ADA=`Ej*aLrd =u`= @m@ ! @>Agl  B@DB@x@ADAEN'aL C ="1=+"+@~(@ /C' B@B(@x@ADAE ?/A =a @@ # @A dn}Wmx?Urˡ-a& \AT$+ B@B@x@ADAE?aLd>`=$_@݀@ # @>A$8  B@B@x@ADAE#aL ?c =="@S@ !A/SpC  B@B@x@ADAESaAj =ZZ`="@Q@ 4Zt~^t& rRc΃y{ ea& )A* B@B@x@ADAE aLd =RW`="@N@ !# a,g A B@@B@x@ADAEaL$c ==$_+@, @&/C +W B@B@x@ADAEĀoCc =0a"@€@&$zN<ܭ<Yff9Bk'4"Aa& CA@0 B@B@x@ADAE|aL(a =+`=$_@@ ' @JA羀  B@B@x@ADAEyaLa =="@{@&/oCmz$ B@BW@x@ADAET5a7M =<`="@l3@ '&lӠz%EvX-'X%1-`a& A2  B@B+@x@ADAE퀈 z% =8$_@X0@ ! @=A/  B@B@x@ADAE 0A =k=+"@@ `@/kC?  B@Bs$@x@ADAE)bA =֬`="@=@ ##!#f9tƬPü'F-FA$+ B@ B(@x@ADAE^aL E B@x@ADAEx[aL !!c =@e="@\@ !/C B@BW@x@ADAEa"1 C = `="@@%t%e 1~⌥ܬ撓(ٹ'ւ bA $ B@BDK@x@ADA Eiπ2@A =a$_ @@ &+ @>Ac  B@B ; @x@ADAEM AAc =&ր=$_@}̀@Gag/NC +A B@B@x@ =A @EԇbB Qa =%`="@腀@&$ڶ-,Ӓ+Jg:&[ec` zT@0 @ J B@DB"Y@x@ADAE>@&aLR.- =|0`=$_@؂@ ' @>A8  B@B@x@ADAE"=1aL a ac =F="!@R>@ !$Ġ/D" # B@B@x@ADA$E bq% =]A;cA[< B@B@x@ADA=E`aL 0r> =(=+"?@@ ! @A/C@iWA B@B@x@ADABETځ A C =ka"D@h؀@&=Et4c?n?֊tM7&K\ E׀$F B@B 6h@x@ADAGElaL0cH =v`="I@WՀ@ &+>JԀ K B@B@x@ADALEwaL6hgM =Q`=$_N@=I@ A-)}Gom\we[h^{UDDOH WP B@B@x@ADAQEaL$R =N`="S@-F@ ! @+=TE U B@B@x@ADAVEwaL? cW =D ="+X@@ $#VǶ/bCYZ B@B@x@ADA[E 8C\ =ša"]@@ #_oЙμ1/ {xrb,a& A^~ _ B@B@x@ADA`EitaL Aa =`=$_b@@ # @=cb d B@BW@x@ADAeEMqaL 9Af ="{=+"g@}r@ ! @Ġ/tCh i B@B@x@ADAjE,a))Ak =3`="l@*@ +P}O&'hJٍ@) X2Lj(wAmS$+n B@BW@x@ADAoE>  dp =|0$_q@'@ ! @>Ar7A[s B@B@x@ADAtE" !)!cu ==+$_v@R@ /Cw x B@Bm@x@ADAyEbA"1Dz =]`="{@@$[zJ|XRq=r 7m>{<a& TA|)} B@B$@x@ADA~EVaL2@d =U`=+$_@@ # @>A A B@B 2^@x@ADAERaL) AAAc =\= @'T@PA/xCS B@B@x@ADAE~aBQj =&`="@ @%AH\zGnY᣶SQl E.a& > @0 B@Bo @x@ADAEƀR`d =$_@ @ `@>A  B@B@x@ADAEÀ aa9G =̀="@Ā@ &+$/fDhA[ B@B4@x@ADAESbb qj =`="@g}@JA4{Ϡ&ʬi} a&  |  B@BJ@x@ADAE7aLrd =&H@Qd_@Wz@$=y  B@B@x@ADAE4aL  c =k>="@5@ /A&>A B@B@x@ADAE( j =a"@<@&Jv,M5JEGPnlea& mf+퀂$+ B@B@x@ADAEaLAd =`=$_@-@ PAꠂA[d B@B@x@ADAEwaL c =@=+)@@ /lW B@B@x@ =A @E`aA c = ?=g'`="@_@&gtkvPY]wsL T VJ~^$ B@B@x@ADAEh(aLa =d2`="@\@ #$JAb[  B@B@x@ADAEL3aL  M = = @|@&g/zC +W B@B@x@ADAEс 9 A =>a"@π@&$!`V}[8m-}x77;TKeCS+ B@BE@x@ADAE=?aLc ={I`=$_@̀@ PA7  B@B@x@ADAE!JaL@Y 9c =="@Q@&$/C  B@BM@x@ADAEBKa9!D =TIV`="@@@ #&lMMQ[Xy}Wˀ_`Aa& cA( + B@B@x@ADAE  =PFa @=@ PA  B@B@x@ADAE  :"C =ba+"+@'~&/C B@B@x@ADAE}L*A = &m'@@ YĪ6>bԥl1`DKrA - B@Bu@x@ADAEknaLA d =x`= @@ @+=$᭠A[ B@B@x@ADAEhyaL !*!c =r="@i@ &+$/Y^Ch B@B@x@ADAES$zaA"1 =+`="@g"@&Ӧ` (Z` sa& BA!$ B@Bm@x@ADAE܀2@d ='a"@Z@>A[ B@B @x@ADAEـ AAc =n=$_A@ڀ@&/\=  B@B@x@ADA  E(bB:Q2IA =`="@<@ A O{χExL @ yD%zc B@DBE@x` =A @EMaLR`d = ?=И`=$_@,@ ! @>A A B@B@x@ADA EvJaL a:ac =KT=" @K@ A/CUC  B@B @x@ADAEab"qG = `="@@ #&lk(-fU/SDGya& ~A}  B@Bv@x@ADAEhrd = $_@@ # @>Aa  B@B@x@ADAEL "c =ŀ=+"@|@ :! @J/RC  B@BJ@x@ADAEvb IA =}`=" @t@ $X4^=U7>xc:a396^+U*A!R$+" B@B@x@ADA#E=/aL d$ ={z`=$_%@q@ ! @>A&6 j@' B@B@x@ADA(E!,aL) c) =5="*@Q-@ /+ , B@B@x@ADA-E Aom. =\a"/@@ +#+A_BFAв9Y?69fa& ?0(1 B@ B+@x@ADA2EaLd3 =P`="4@@ #=$5 !U 6 B@B@x@ADA7EaL c8 =Ʀ="9@&@&g/G?: ; B@B@x@ADA<E}Xax= =5_`=$_>@V@&k\O־"[u^S(/ua& &QIA?U@ B@B@x@ADAAEaLdB =%\`@$_C@S@ &+ @JADR AE B@B@x@ADAFE aL cG =="H@@ &+#V/}CIgJ B@B@x@ADAKERɁ :L =  a"M@fǀ@ #AƭFQii~ӭN˺_$ $ANƀ +O B@B@x@ADAPEaLAQ =`=$_R@VĀ@ # @=ASÀ T B@BA@x@ADAUE~aL ;#AV =u="W@@ ! @҃/nCX=Y B@B@x@ADAZE':a A[ =@)`=!I\@;8@ m25!vJYMaXғ! A]7$+^ B@Bm@x@ADA_E d` ==4$_a@,5@ ! @>b4 c B@B@x@ADAdEv !;!e =B="f@@ /Cgh B@B@x@ADAiE5bA"1cj =@`="k@@ +#$!Nee?@Rik9JLEa& Al}$m B@B+@x@ADA&`EgcAaL2;@co =K`=+$_p@@ # @>Aq`Ar B@DB@x@ADAsEK`LaL; AQ3*t = "X`= u@@ ! @H~{8rPmM\9V=6C CvR$w B@B{  @x@ADAxE<ԁ R`dy =zc"z@@ $ @>{6 W| B@B@x@ADA}E ѡ @Y aac~ = +ڀ="+@PҀ@ !B/nC  B@B@x@ADAEdbb#q+D =Wo`="@@AĠzꬷXoQ`c!jxeD' yA'  B@BA@x@ADA. EEpaLrdDIOz`= @@@VԒ&+ @> & DBW@x@@A EA{aL #cK="@&C@`ڥ/CBc DB@x@ADA @E|D =%a"I+@`@B'#+,$5MP0 zk L AF@2 @ ` !@DB@x@ADAE絁LAdM%aIG@`@d_AW@`@ #>A!  B@B@x@ADAE˲L c = =+"@@ @+A/kCgN B@B@x@ADAERnbAom =u`="@fl@EE$*8?fhT~\:T;kFS L ZAk$ B@BA@x@ADAE&aLd =q`="@Ui@ !>AhA[d B@B@x@ADAE#aL c = l-="@$@h&+/vC<  B@B@x@ADAE'߁ { =a!I+@;݀@&_Vi`cėh~YLa& A܀+ B@BwN[P@x@ADAEaLd =`=$_@+ڀ@ # @=ـ  B@B@x@ADAEuaL c =F="@@KqO!#V/C B@B@x@ADAEOaq om =V`="@N@ +'SW]1ı _Ki--|B#2L@!~#;YAM + B@B+@x@ADAEgaLOm =S`=$_@K@ ! @>A`J  B@BA@x@ADAEKaL ,A = ="@{@ /uC  B@B@x@ADAE $c =a @往@ +# @$+SV+ɰ|-Zd+hC%} L ۖA lQ$+ B@B@x@ADAEA @ B@B}@x@ADAE  i AxAAc =@="@%@|Z/瀂 W B@B@x@ADAE|bB,Q J =$`=%@@p# @A\[co -QApa& t\@0 B@BM@x@ADAEZaLR`d =`=$_@@ # @>A A B@B@x@ADAEWaL a,ac = a="@X@A/ʈf  B@BM@x@ADAEQ abq4D =+`="@e@ +"^+.iipM2_5XE.a& rM  B@Bu$@x@AD>"Eˀrd m6$_@U@ ! @= `iF B@B`x@9 A @EȀc = ?=lҀ="@ɀ@&/3^C@  ! BA@x@ADA E&7bc = B`=% @:@C# @҃P[E̴벀Ž/2&NLV 6A $+ B@B`@x@ADAE/5 A0 B@B@x@ADA1E}aL d2 =$="3@O@#V/IA4 A5 B@B@x@ADA6Eց  c7 =[݈a"8@Ԁ@ 'AK}d" - dmN0r\D41a& f=9& : B@B@x@ADA;EaL< =Nړ`=$_=@р@ ! @>A> ? B@B 'A@x@ADA@EaL F+A =ŕ="B@%@ W&+ @/liCD B@B|@  @'@x@ADAE@/ @{GalcF =,N`=!IG@E@ # @`[qV ɥ^o-@ zF+HD-I B@DBg@x@ADAJEA CK =$K$_L@B@ # @>AMAA[ ` $N B@B@x@ADAOE !!dP = a"Q@~ !/CRfS B@B@x@ADATEQLA" 1CU ='V@e@ +ۢ~*l*=401oJ a& 'AWѵ$X B@B@X+@x@ADAY@/ EpaL2 @f+Z =`=+$_[@X@ ! @>A\AG `@  ] B@DB@x@ADA^EmaL AAd_ ={w="`@n@7/C Ca;b B@B@x@ADAcE&)aB Qcd =/`=%e@:'@&  ly’'!0̅*D}f&$g B@B+@x@ADAhEဈR`ai =,$_j@*$@ # @>Ak# l B@B@x@ADAmEtހ) aadn =A="o@߀@&A/U@rm B@x@ADAr! @Ebb=qas =`="t@@ '$F;t'B|Nb QNrLgF+u{ v B@DBAjA@x@ADA] EfRaLraE`=$_B9@@ @=z!G_ 0& DB@x@ADAq( EJOaL =aT|Y="~zP@ :&+ @/C-C g4 DB@x@ADA? E a=%M`="A@@&$*ox+퐭&FpԿ@ AP$+ B@DBb@x@ADAE;Á Ac =y b@$_K{@@ # @=4@G B@B@x@ADAiq E 6h=%C@mI^a" @y@ !pc õkDWXD'[hDy@ bC& DBd@x@ADAo E4aLAN `="@v@ )A> AG B@DBAbB!:@x@ADAE0!aL d =:="@$2@ !B/C1 B@BF @x@ADAE{쀈Wc =,,a!I+@@ iзƒuDF!"LKMZ$*`~n! jA适$ B@BJ@x@ADAE-aL5a ='7`=$_@@ ! @J怂  B@Bg#@x@ADAEɡ8aL) d =="@@ /OFCeW B@BM@x@ADAEP]9a5c =cD`="@d[@ #$AjEcB)rli^7'W|C2v! nw$Z `XF B@BP@x@ADAEEaL-.IA =`O`=$_@TX@ # @>AW  B@BA@x@ADAEPaL A =g="@@ ! @/}P; B@BS@x@ADAE%΁ p}.c =[a!I@=̀@ +l=;}NN>Ju%fx! F+ˀ$+ B@BA@x@ADAE\aL6  =f`=$_@*ɀ@ @>Ȁ  B@B@x@ADAEtgaL !!d =@="@@ .>C B@B@x@ADAE>haA"61c = Es`="@=@&-[!D% QHr38yYL-' A{<$ B@B@x@ADAEe2@a =B~a"@:@dA^9A[ B@BsB,X@x@ADAEI AAd =="@y@&/TC  B@B@x@ADAEЯbBQc =y`= A@䭀@ @h$Y}Q4be f;/A 'p(raEAPA[ B@Bp@B J@x@ADA@/ E:haLR`c =x`=)A@Ԫ@ @=:4AG B@DB@x@ADAEeaL) aaa =n="@Nf@+/iC  B@B@x@ADAE abqa =R'`="@@&T>]\)d?$*.]ݥJİAm,a& iA%  B@B@x@ADAEف r&a =M$$_@@ @>  B@B@#A@x@ADA@/ EՁ  a =߀="@$׀@&/Cր B@DB@x@ADAEzb}&a =/`=%@@ }p1=lkckQ̼63n A- B@B}@x@ADAEIaLA>U =#`=$_@@dAދ  B@B@x@ADAEFaL c =P=" @G@&/CeA B@Bx!+A@x@ADAVEPaAc `="@d@ +5o}yodHuK6꘧Ng5ֳ'ĸ9m- A`$ B@B+@x@ADAE &D =`" @S`@dA A[ B@B@x@ADA EL d =j="@θ@&/C:W B@B@x@ADAE%sbc =y`= A@9q@&$"+<O0[N113І]ܠN' = 0 `= (@ހ@dA)^ A* B@BA@x@ADA+EI aL J, == -@y@ Wg/EqC. A/ B@BW@x@ADA0ET a?c1 = [`="+2@R@ +dF FDsaUΛFT}L>&̉h  43O$+4 B@B+@x@ADA5E: aLA c6 = xX"`= 7@O@dA83 9 B@B@x@ADA:E #aL?!!a; =="<@R @ /v`=> B@B@x@ADA?EŁ A"1a@ =M.a"A@À@&l:g+ނ(|<O/F+B%@4C B@Bp@x@ADADE~/aL2@'GE =Q9`="F@@dAG H B@B@x@ADAIEz:aL AAAcJ =="K@#|@g/(CL{M B@B@x@ADANEz6;akJBQcO =3=F`=!I:P@4@%-"yܵN)?s, زa& AQ3$R B@B-@x@ADASER`DT =":Q AU@~1@+ @+=V0 +W B@B@x@ADAXE뀈) aadY =="Z@@ A/C[d\ B@B@x@ADA]EORbbqc^ =]`="+_@c@ d ƾ*5o>Z.Aj{[2M" sA`Ϥ a B@B@x@ADAbE_^aLIderf+c =h`=$_`= @3d@W@ @>e 5`B@x@ADAgE\iaLADdh =@jf= i@]@ :/DKj:Ak B@B@x@ADAlE$jacm =u`="+n@8@')Cؑ 1)寋cb(Ja& VCo$+p B@B@x@ADAqEЀcr =$_s@)@dAt u B@B@x@ADAvEs̀ aw =?׀="x@΀@ .>yz B@B@x@ADA{EbAa| =`="}@@&lA^ WvlObaLg,(t`aL$c =H="@|?@/tC$ B@B@x@ADAE oc =a!I@`@&H%$~ovLL&UlVm7AS$ B@Bm@x@ADAE9L*'c ={aI @@+ @+=7  B@B@x@ADAEaLa == @Q@ `A/C + B@B`@x@ADAEjaG =\q`="+@h@ ^)c٪4vy?0;@C9?!78a& Ar(  B@B@x@ADAE#aL zA =Ln`=$_+@e@ @> A B@BA@x@ADAEaL A =)= @#!@ M/r  B@B@x@ADAEyۀ0A =.a"+@ـ@&:ѺL[U#ˀ #O< +BoЕ F+؀$+ B@B@x@ADAEaL A ="`=$_@~ր@dAՀ  B@B@x@ADAEȐaL9o# 8!01A =R`="@cJ@ :fl/RChɾ;8v?;ȷ .I  B@B @x@ADAEaL28@f+ =O`="@VG@dFA[+ B@B}@x@ADAEaLAxAC)A =@i ="@@ 1/49 B@B@x@ADAE$ B8Qc =d@ A@8@Ġ欫tna'ivsk_?g6_9Q M0giS$ B@B@x@ADAEuaLR `C = `= @(@dA[ B@B@x@ADAErr aL) aaA =?|= @s@&/o B@B@x@ADAE-ab qc =4`="+@ ,@  Ġo[cEY9JN∂Y2Ga& py+ + B@Bt @x@ADAEd怈r0 =1$ @(@dA] A B@BW@x@ADAEH  D =="@x@ WA/p  B@B@x@ADAEΞ%b0c =0`="+@✀@ +t~ }\lR(zzfLy!haPN$+ B@B+@x@ADAE9W1aLA(C =w;`= @ә@dA2  B@B@x@ADAETE8 R)`!LW? =vG @@>@dAA2 B B@B@x@ADACE ) aadD = E@L~ W/oCF G B@B@x@ADAHELb)qcI =Ka"+J@@!+z;lfWiHfd8qH!a& tAK# AL B@B-@x@ADAMEmaLr jN =`= O@@ @=P ` zLQ B@B@x@ADAREiaL dS =s="T@"k@+ @A/CUjA[+V B@B@x@ADAWEx%a cX =-,`="AY@#@ A&|x[ |Q<<~t'iMAG"L?a& .OAZ"-A[ B@B@x@ADA\E݀1f+] =!)$_^@} @ @=_ ` B@B@x@ADAaEڀ Jb =="c@ۀ@ A/cCdce B@B{@x@ADAfEMb1cg = e@"+h@a@oJ l|Y`Z?aHpM))@L@ Ai͓ +j B@B@x@g =Ak @ENaL!)Cl =`=$_m@Q@dAnAo B@DB@x@ADApEKaL Aq =tU="r@L@ /ޗs8t B@B@x@ADAuE#a!cv = `=*w@7@ AQXJ7+ =,, 9Ǫ饈wa& Dx@0y B@B@x@ADAzE1t{ = & |@'@ # @JA}A[~ B@B@x@ADAEq) d =6ƀ="@@ +!#V-/C  B@B@x@ADAEw'b1c =~2`="+@ v@ TоoB\Ztj-fmA (*Axu  B@B@x@ADAEc03aLG ={=`=$_+@r@ ! @>\  B@BA@x@ADAEG->aL a = 7=$_@w.@&g/wC A B@B@x@ADAE a =~Ia"+@@ #!00s=774;u6bta& GAM$+ B@B@x@ADAE8JaL*  =uT`=$_@@dA1  B@B@x@ADAEUaL!!c =駀="@L@ A/YkC  B@B@x@ADAEYVa"*1c =S`a`=$_@W@&aיbz{mgkxLZSԀ a& J" A B@Bym@x@ADAE baL2@a =O]l`= @T@dAA[W B@B@x@ADAEmaLAAd =="@!@ /D B@B@x@ADAExʀB2Qz =$xa"@Ȁ@ A8h0fOڏ(jm Ĕ5Aǀ$ B@B@x@ADAEyaLR`c = ΃`="@|ŀ@dAĠA[ B@B@x@ADAEaL) a2ac = = @@ /lCb B@B@x@ADAEM;abqc =A`="@a9@ L rWm\Y܇4\|5A8 A B@B@x@ADAEra => A@Q6@dA5 A B@B@x@ADAEDå)a =ʲ @6@ U.Nu8ڼpCv E e3:A *UC@2 B@@iB$@x@ADAEdaLf+ =`= @'@dA[ B@B@x@ADAEqaaLAxc =@5k="@b@ +:/yC + B@Bom@x@ADAEac =#`= @ @ [ڀ-*Fo66j* |spta& gAw$ B@B}@x@ADAEbՀW:A = a @@ +# @$=[  B@B@x@ADAEFҁ  d =܀="@vӀ@ !#VĠ/ KC  B@B@x@ADAE͍bWc =`="@ዀ@ [=%DL/a&  AM B@B@x@ADAE7FaLf+ =u`="@ш@ !> 1A[ B@B@x@ADAECaL) d =L=$_@KD@ /4!C  B@B@x@ADAE c =Oa"@`@ #!+4X3o&hȻ{ a& D?"  B@BC@x@ADAE L +IA =JaI$_@`@dA   BA@x@ADAEL  =="@!@A/rD B@B@x@ADAEwob c =#vf@"@m@&Զo?Ckh<43bZX o G l- B@B@x@ADA E'aL c =s`= @|j@dAiA[A B@B@x@ADAE$aL !!a =.="A@%@&/ b B@B(@x@ADAEL "1a =a +@`ހ@&$r˹ >{{nY2e@a& =F+݀$ B@BA@x@ADAEaL2#@#IA = '`= @Tۀ@dAڠ  B@B}@x@ADAE(aL AAc =d="!@˖@ /˔C"7# B@B@x@ADA$E"Q)aB#Qc% =W4`="&@6O@ 4N3M 8je `f5r}oA'N$( B@B@x& 9ADA) @E 5aLR+`;D* =T?`="+@%L@ /m$m=$,K - B@DB@x@ADA.Ep@aL) aad/ === 0@@&$/C1 2 B@B@x@ADA3E b+qc4 = Ka"5@ @ r#!ĠJA #DN][7ctbDQ#q A6w 7 B@Bc'@x@ADA8EbzLaLrom9 =V`=$_:@@ ' @>;[ < B@B@x@ADA=EFwWaL d> =="?@vx@ #! @҃/"Y@ A B@B&@x@ADABE2XacC =|9c`="D@0@ + 8#;83{ZEnJI XSa& 9WDEL$+F B@B+@x@ADAGE7 IAH = t6n$_I@-@ ! @>AJ0K B@B@x@ADALE  dM =="AN@K@ W/{CO P B@B@x@ADAQEobcR =Rz`="S@@&bجX5q6AX e ZL>a& xAT!$U B@B@x@ADAVE \{aLA "  db3omW =J`=$_`= @3zLX@@ # @>AY@Z B@B@x@ADA[EXaLd\ =AVb="]@ Z@ !A/C^Y_ B@B@x@ADA`EwaA3ca =`="b@@:tVp'jk{7$jVնc /XAc@2d B@BJ@x@ADAeÈ;jf =a"g@z@ PAh i B@B@x@ADAjEɀ)+Axdk =@Ӏ="+l@ʀ@ /tCma n B@B@x@ADAoELbq ;cp =`="q@d@&,y x8hH)Y=ЈW0X)a& (rЂ s B@B@x@ADAtE=aLu =`=)v@P@ @JAw x B@BA@x@ADAyE:aL Omz =kD= {@;@ 3/D|7A} B@B@x@ADA~E! Ac =a"@5@ P'A^ `ڔ;Klc߸.hznE:Q6=A$ B@B@x@ADAEaL4 c =`=$_@%@ ! @>  B@B@x@ADAEpaL !!a =<="A@@&//C  B@BwJ@x@ADAEfa"41a = m`=$_@ e@ +# @߸0f_ #z1e] qvd$ B@B}@x@ADAEaaL2@a =j`=$_@a@ ' @>ZA[9 B@B@x@ADAEEaL AAa =&="@u@ !/q  B@BA@x@ADAEׁ ABQa =ta"@Հ@&XK}܀\jrR ,LBa& "F+L@0 B@Bm@x@ADAE6aLR<`j = t`="@Ҁ@ &+>A0  B@B@x@ADAEaL) aac =ߖ=$_+@J@ /FC  B@B@x@ADAEHabAڳ  B@B@x@ADAEn*a  Ad =x="A@o@&/fCa B@B@x@ADAEK*+a<$C =06`=$_@_(@&$ӁOƀ$(K\ɫ3+? b 4T  XF B@Bm@x@ADAESNaLxa& v  B@B+@x@ADAEaĀ< C =p)@@ @>AZ  B@B@x@ADAED ; -5A =|"@z@.'#)AD-u >o_pKs3?zK  B@B.@x@ =A @E65}aL A =s`="@w@ $ @>/  B@DB@x@ADAE2aL !-!c =;="@J3@ !#VW/  B@B A  @'@x@ADA%`@ "51a = @=Qa!I@@ AĠ4o' aH'*2nME2 IA $A B@B@x` =A @E aLW2@d = ?=M`=$_@@ ! @>  B@B}@x@ADA EaLA5Ac ==" @@ /;C  B@B@x@ADAEv^aWB=Qj =&e`="@\@$Ġ=^zQ-뜃i.)<禐a& GA[$ B@B@x@ADAEaLR`d = b`="@yY@ #>AX  B@B '@x@ADAEaL) Aa=ac == @@&/NkC` B@B@x@ADAEKρ bq-LW = a" @_̀@ ĠWyK{dOԌx3cCPN7*M!̀ bN " B@B @x@ADA#EaLrd$ =!`=$_A%@Oʀ@ PA&ɀ ' B@B@x@ADA(EaL c) =j="*@ʅ@ &+$/S+5, B@B@x@ADA-E @aD. = F`="/@4>@&Fh~iX}_C% cJƶߊ#F+0= b 1 B@B@x@ADA2Ed3 = C 4@$;@ # @=5: 6 B@B@x@ADA7Eo C8 =P=+$_+9@@&/?C: ` ; B@B @x@ADA<Ebom= =`=">@ @ :'!Q`ABAczUmi,\>2h,t]B?U + ̬@G?u$+@ B@B}@x> 9ADAA @E`iaLdB = ?=`=$_C@@ ! @=DY E B@B #@x@ADAFEDfaL cG = p="H@tg@ &+A/:CI AJ B@B@x@ADAKE!aAF+L = {( "M@@&*VkpnCQYmhzğ,b ANKO B@Bm@x@ADAPE5ځ d4 {%a"R@@ #>AS3 AT B@B@x@ADAUEס @Y cV ==$_+W@I؀@%/ $CX Y B@B@x@ADAZEb-t[ = \ `="\@@ '!҃Awwm qoD_/Z/"Aq A] ` ^ B@B@x@ADA_E K!aLF+` =H+`=$_5@@ ! @=Ab c B@B@x@ADAdEG,aL.e =Q=H bQf@I@ &+A/yCgHh B@B}b@x@ADAiEu-ao(vL+@s&zj =) 8`="k@@ #^ 'j=n:Ds. Go1 eX@ 6hl m B@BA@x@ADAnEເ do = C$_p@yB`@ # @K=q r B@B@x@ADAsEĸLAx!&!ct =@€=+"u@@ M! @/-6hv`w B@BA@x@ADAxEJtDb">1Gy = zO`="z@^r@&Ww ^@'07U.u|`* {q$+| B@B@x@ADA}E,PaL2@d~ =wZ`=$_@Oo@ &+ @>An - B@B@x@ADAE)[aL A>Ac =e3="@*@ /"IA5 B@B@x@ADAE B>Qc = fa"@4@&@S^AX0GS%t0z=S A $ B@B@x@ADAEgaLR`a = q`="@#@ >A߀  B@B@x@ADAEnraL) a>aa =;=)+@@&/xC J B@B@x@ADAEUsab6qj =\~`="@ T@ '!҃y@ZWo2~7-Z+Daca& AuS + B@BXZ@x@ADAE_aLrc =Y`=$_@P@ ! @=Y  B@B@x@ADAEC aL 6c = ="@s @&$/C  B@B@x@ADAEƁ a =͕a"@Ā@&=,xِ4\4EOY-MAJ e# $ B@B@x@ADAE5aLd =rʠ`=$_@@ ,W @>A.  B@B@x@ADAE|aL c ==+'u@I}@ @҃/C  B@B@x@ADAE7a&a =L>`="@5@ +'AIҚ&-8D.x𘦇Ƀ8W)a& 6A- B@B@x@ADAE Ad =H;$_@2@ ! @>AA[ B@B@x@ =A @E &c ==+"@@ &+ @/\C퀂!]a B@DBA@x@ADAEubA6a =)`="@@ #55څD4/qb<\+=dhC!7W [0A$ B@B<@x@ADAE`aLd =!`=+$_@|@ # @=٢ A B@B@x@ADAE]aL6h6c =g="@^@ ! @A/Cc + B@B@x@ADAEJa&c =`="@^@&FMsĪؕD(Nz%*P%>a& "Y  B@ BA@x@ADAEр]P =$_@N@ &+ @>A  B@B@x@ADAE΀ 'a =a؀="@π@&A.\(4A B@BA@x@ADAEb7C =ː`="@3@ #+A2lFox!´ 6^YSa& 0iF+  B@B@x@ADAEBaLJ c =Ǎ`=$_@#@ ' @>A  B@B@x@ADAEn?aLz!!C =;I="@@@ A/EC  B@B@x@ADAE "1c = i@"@ `@ P _xf3OGRQj@ At$+ B@BP@x@ADAE_LA2@C =aI$_@@ ! @>AXA[p@ B@B@x@ADAECaL AA'Ag ==+'u@s@  `@/B>C  B@B@x@ADAEkaABQd vr"`="@i@ +#!A%P j/0:9(0_n AJ B@B+`x@9 A @E4$#aLR'`c =ro-`="@f@ #>A.  B@DB@x@ADA E!.aL6haqF+ =L9`=" @ڀ@  `@9~vnӀCo & ̒=B*2_>pC   B@B@x@ADAE :aLrd =KD`="@׀@ $ @>  B@B@x@ADAEEaL? C =="+@@mՙ/\C+ B@B@x@ADAEtMFa'f+ = TQ`="@K@ MXZ04Y4nI8EIa&  ;AJ  B@B@x@ADAERaLd = Q\`=$_@xH@ ! @> G ! B@BW@x@ADA"E]aL 'c# = =+ $@@Ġ/C%_& B@BW@x@ADA'EI ?F+( =ha")@]@ &֥cdD{:"e"! `*ɻ$++ B@B@x@ADA,EviaLWd- =s`=$_.@N@ PA/A[0 B@B@x@ADA1EstaL?c2 =h}=+ 3@t@ ! @+/m`48$5 B@B@x@ADA6E/uaC?a7 =5`="8@7-@ + *;/ Xò~;]Kh0 FI =Wa& R]F+9,$: B@B@x@ADA;E瀈d< =2a"=@**@ !$>A>) A? B@B}@x@ADA@Em䀈}?GA =>=$_+B@@&/qCC +AD B@B@x@ADAEEi@T/AF =`="G@ @JAa SRKl#0qQdS"@ OAHx I B@B@x@ADAJE^XaLA zA$# =`=$_L@@ ' @>MX N B@B@x@ADAOEBUaL P =_="Q@rV@ !$/rR S B@B; @x@ADATEa(CU =}`="V@@ }YE:xA=kaȳwdrnt߁w. *6a& DWI X B@B@x@ADAYE4Ɂ  AZ =q$_[@ @ ! @>A\- ] B@B@x@ADA^EƁ  !(!c_ =π=+$_`@Hǀ@ /Ca b B@B@x@ADAcEb"1ad =O`="e@@ #!Abq N /lAf-g B@B@x@ADAhE :aLA2@Di =G`=$_j@|@ # @>AkA[l B@B@x@ADAmE6aLAAcn =@="o@8@ !A/SAz쀂 A{ B@B@x@ADA|E§aL) Aa8ac} =="~@@ /QC^ B@B@x@ADAEIcab(q =i`=%@]a@ # @AWBe_RA\6"֩z^a& vA`@0 B@Bc@x@ADAEaLrD =fr$_@M^@ # @>A]  B@B@x@ADAEaL (c =\"="@@҃/xZC3A[ B@Bq B Ƕ@x@ADAEԁ (a = a"@2Ҁ@&1 'A?z"\i5w`@R5! SAр  B@Bm@x@ADAE aLD =`=$_@"π@ &+ @>A΀  B@B #@x@ADAEmaL (c =B=+$_@@ `@/1 A B@BE@x@ADAEDaC =K$`="@C@!A{SHAKEb l  #]ڞ*DsB@4` -& B@Bp+@x@ADAE^AA =H/$_@?@ `@>AW  B@B@x@ADAEB/aL A =0`=+"@r/`@ @A/1C  B@B@x@ADAEɵLA0}P =u;aI"@ݳ@&}ќuޅK$b.WK+C5ʨ0a& .I B@BJ@x@ADAE3nב  B@B@x@ADAELvaL !1!c =V="@M@ /C^3 !k B@B@x@ADAEHwa" 1c =`="@\@ +#ʴ ?X:P%scsa& GiA$+ B@B$@x@ADAEA2@a = $_@M@ # @>A c @ B@B@x@ADAE A Aa =hǀ=+"@Ǿ@ ! @A/-&3 B@BA@x@ADAEybABQz =`="@2w@ A26xuU%yN4 lɜIW3a& ov$ B@Bm@x@ADAE1aLR`c =|`=+$_@!t@ `@>AsA[ B@B@x@ADAEl.aL aac =48="@/@&/&GG B@B@x@ADAE b9qOm =a$_@@ # @J.j[-6mbFPGάUr?As瀂$ B@B{@x@ADAE]aLrA =`= !@@ ' @>AW A B@B@x@ADA¨EAaL) 9c I="@q@&A/C  B@DBA@x@ADAEZa)om =ta`=" @X@ +"M)ŕ}!f8k&j_L Oa& A H  B@B+@x@ADA E3aL#B =p^`=$_ .`@3y @ U@ ! @>A,  B@BA@x@ADAEaL )c =I='u .`@3a @ G@ W&+ @/WC  B@B@x@ADAEˁ c =IFa!I .`@3a @ ɀ@ @AR'#1$Y<"qEqc1c 2$#%7{s &@/W B@B@x@ADAEaLA)D =IF`=$_ .`@3a @ ƀ@ # @>A@ B@B@x@ADA!EaL m" =I="#@@/D$% B@B@x@ADA&Es B@B+@x@ADA?Ea8@ =$&`="A@1@ }#LC HHJh=ieGO!AB C B@Bg@x@ADADEր dE =!1$_ .`@3f+F @ !@ @+>G H B@BW@x@ADAIEl !!cJ =I8݀="K@Ԁ@ }/ L M B@B@x@ADANE2b"*1*GO ==`=$_ .`@3cP @ @&aH9PEWu,JkBA wDQr$+R B@B@x@ADASE]G>aLA2@dT =IH`=$_ .`@3aU @ @ &+ @>VV@W B@B@x@ADAXEADIaL A*AcY =I N="Z@qE@ /C[ \ B@B@x@ADA]E AB:QD^ =Ua"_@T`@-D~ t4,Q9I1|W3a& IA`G ed a B@B W @x@ADAbE2LR`dc = p`aI"d@_`@ #>Ae+ f B@B@x@ADAgELa:ach =="i@F@&~/Cj k B@B@x@ADAlEpabb qcm =Iwl`= An@n@Ġk-N-9-3S= :'4|\'~;a& Aop B@Bs@x@ADAqE)maLrar =Etw`=$_ .`@3iAs @ k@ PAt Au B@B@x@ADAvE%xaL) A aw =I/="x@'@ /L/Cy&z B@BM@x@ADA{Erဈ*om| =a"}@߀@ #$WĠB[v^[/y>}CjZHa& BA~ހ A B@Bg@x@ADAEݙaLc =`= @v܀@ PAۀ  B@BA@x@ADAEaL *c ==" .`@3f+ @ @ ! @/GnC] B@B@x@ADAEGRaj =IX`=!I .`@3a @ [P@ mX< .ѩ{a;p֠α%z*as yIAO$+ B@Bm@x@ADAE aLAd =IU`= @LM@ ! @>LA[ @ `  B@B@x@ADAEaL c =b="@@&$/eC2 B@B@x@ADAEÁ R =ɲa"@0@ #$Aٜʬ\j#?0[hA{7a& [  B@B@x@ADAE{aLgd =ƽ`="@$@ 'K`.C@Td  B@B m4@x@ADAEkxaL c =?="@y@ !A/4 B@B1@x@ADAE3a D =:`=!I .`@3iA @ 2@ #ZB'?P1bjR7na:i/"Ӏs iAr1$ B@B@x@ADAE\쀈A =I7$_ .`@3a @ .@ ! @+=V A B@B@x@ADAE@ @Y)   A = + ="@p@ .iA  B@B@x@ADAEǤbA =w`="@ۢ@&|r*wxw kM*ZQ ofa& GG + B@Bx  @x@ADAE2]aL d =o`=$_ .`@3d @ ˟@ # @>A+  B@B@x@ADAEZaL !!c =Ic= @F[@&/14C  B@B@x@ADAEa"13D =]`="A@@ :!}ApOIQ@x/ QD]A- B@B@x@ADAE΁ 2@d =Dl@$_ .`@3d @ @ PA@ B@B@x@ADAEʁ AAc = {Ԁ=" .`@3a @ ̀@ U&+ @/ Cˀ B@B@x@ADAEqbB#Q#D =I&`="@@ ҃R%CZTbSa& A񃀂  B@B @x@ADAE>aLR`d =`=+ @u@ # @=ՀA[ B@B@x@ADAE;aL i  a#ac =@E="@<@&/C\ B@B@x@ADAEG b+qc ='a% .`@3f+ @ [@%A X?s4`Y|ժoS_EHa`E}A$ B@B+@x@ADAE(aLra =I2`=$_ .`@3a @ K@ PA A B@B@x@ADAE3aL) +a =Ij="@ŭ@&/R.1 B@B:@x@ADAEh4a3a =n?`="@0f@ #$Az); FO>WzB w6VgDe A B@B@x@ADAE @aLa =kJ`= @ c@ PAb  B@BA@x@ADAEkKaL 3a = 3'=" .`@3f+ @ @@ :! @/KC* B@B@x@ADAE؁ ;;MJVa!I!x`@3a ~@ ׀@ ʿB6Riz5aWs Aqր$+ B@B@x@ADAE\WaLc =Ia`= @Ӏ@"Y @> UA[ B@B@x@ADA E@baL ;c = = @p@ W/NC  B@B@x@ADAEIca#a ={Pn`="@G@%(9_i0Y~[5x?66ّ[AF$ B@B}@x@ =A @E1oaLd =sMy`=+$_+@D@dA*A B@DB@x@ADAE #c =za"@E@ /+C  B@B|@x@ADA E r;t! =La +"@@ Ap аC-2<\P˒)%A# $ B@B@x@ADA%EsaL"& = D`= '@@ ! @JA( ) B@B@x@ADA*EoaL) A4a+ = {y=",@q@ /l C-p. B@B@x@ADA/Eq+aa0 =2`="1@)@ #$҃s;(W!a E7% 8 B@B@x@ADA9E !4!$P: = =";@@ !/+ <\ = B@B@x@ADA>EFb"1c? =`=!I@@Z@ c‘CyJ2 ?ud%Aƙ$+B B@Bm@x@ADACETaL24@cD =`=$_E@K@ ! @>F G B@B@x@ADAHEQaL6hAPA[Q B@B@x@ADAREj€ a/CUWV B@B @x@ADAWE}aLbqaX =`=!IY@|@AĠz]lQq#6=o60`AZq{$[ B@B@x@ADA\E[6aLrd] =`=$_^@x@d:_UA[` B@B@x@ADAaE?3aL/cb = =="c@s4@m(/dߡ +c 0<e B@B@x@ADAfEa%9`u%A>$ B@B+@x@ADAEOm =DL @J<@dA;A[ B@B@x@ADAE) = A =]M @~ W/F0 B@BW@x@ADAEL5A =ϸXa"+@/@&뻲dw9*-t3$ A$ ?a& 3D + B@Bm@x@ADAEjYaL A =õc`= @@dA A B@B@x@ADAEjgdaL !!c =>q= @h@ `/C B@B@x@ADAE"ea"1f+ =)p`="A@!@& -|V9xa8 &?.'h +~`Ap $ B@B@x@ADAE[ۀ2@d =&{a @@dATA[A B@B@x@ADAE?ء @Y AAc = ="@oـ@ -&/LC  B@Bw:@x@ADAEœ|bBQG =z`= +@ّ@&S+qX#'&~VA":yHouAE$ B@B@x@ =A @E0LaLR`d =n`= @Ɏ@dA)A `@  iF B@DB@x@ADAEIaLaaC =R="@DJ@ /cC A B@B@x@ADAEaAb5qj =K `= @@ +QwMѽp§AP(aux@a& 7A B@B+@x@ADAE rd =C @`@dAA[ B@B@x@ADAE鹁L) A5c =À="@@&$/vC B@B$@x@ADAEpub=a =|`=H@Q @s@$ g{Y o߱}>cׂچX#DTg;mr + B@Bp@x@ADAE-aLd =y`= @tp@ @}aL) A == @n~@&/SC  B@B@x@ADAE8 a-&a =y?`="+@6@ e&7(=eC@N#{za& AI  B@B@x@ADAE/  d =m<a !@3@dA") # B@B@x@ADA$E ) !!c% =="&@C@ d-/h2C' a ( B@B`@x@ADA)E n@TA"1.F+* = 0G+`= ++@@ +֕ Ui{G46% I[>@=' xA, - B@B+@x@ADA.Eb,aL2@d/ =B6`= 0@@dA1A[c @2 B@B@x@ADA3E^7n@T AAc4 =h="5@`@ W/}TC6_7 B@B@x@ADA8Eo8aBQD9 =!C`= :@@&jnݞ‹SEIL(M@ I&l yA;-< B@B@x@ADA=EҀAR`d> =Nn@ ?@s@dA@ A B@B@x@ADABEπ aaiAC =ـ="U6@Ѐ@ / CEZF B@B@x@ADAGEEObAbqcH =IZ`= I@Y@ +AُMƐRŋ7ps;4')KgV5S WmAJň$K B@B@x@ADALEC[aLrcM =e`= N@I@dAOA[P B@B@x@ADAQE@faL) cR =\J= S@A@ /ACT/U B@BA@x@ADAVE >&IAW =rn@"+X@.q`@&sK63)ӈExM+*nވ>"@ lUAY AZ B@B@x@ADA[EL.c\ =|aI ]@@ &+ @>^~ _ B@B@x@ADA`Eh}aL aa =5="b@@&/yCcd B@B@x@ADAeEl~a.af =s`= +g@k@ +# @.WݭvfKAJ,e8лP2a& ©Ahoj i B@B+@x@ADAjEZ%aLf+k =p`=$_`3l@g@dAmS An B@B@x@ADAoE>"aL `.cp u`="q@ۀ@liҡbga֟P|}"eկYi8Ma& |*CrD$s B@B@x@ADAtE/aLcu =q`= v@؀@dw(A[Wx B@B}@x@ADAyEaL@YAx>>Jz =@㜀="{@G@  +6h/C| +} B@B@x@ADA~ENad =JU`= @L@ W Ġ["F%0sGab$=M;8A$ B@B @x@ADAEaL>?C =BR`= @I@dH mW B@BW@x@ADAEn@T) c' = = @@ /? B@B| o@x@ADAEoa =a"+@@&#0ME%p͢ߥQ&sXψ:ǮD＀  B@B+@x@ADAEwaL a =`= @s@dAӹ  B@B@x@ADAEtaL?!!/F+ =~="@u@ XZ/1C]$ B@BW@x@ADAED0a"1c =6`="@X.@ Aߛ|*{Vą\(Kf>C9v a& A-  B@B@x@ADAE耈 2@c =3 @I+@dA*A[A B@B@x@ADAE AAAc =[=+ @@P/yC/  B@BP@x@ADAEb}Qa =§`="@-@ y-mhL%c 4r@pa& A@0 B@Bq@x@ADAEYaLAR`a = o@ @@ ' @$=C}@A B@B@x@ADAEhV aL a7aIA =,`="@W@$Ġ/tCm B@B}@x@ADAE aAbqc =`="@@&OB՟W=Dl UC[0-a& To$ B@B@x@ADAEYʀr7c =!a"@ @dAW  B@B 5@ADA @E=ǡ @ '7D = р=$_:@mȀ@&/T١ + B@DB@x@ADAEĂ"bd =l-`="@؀@!+$ȨvRz6ÑK` bUgb+a& L;F+D@0%9`  B@B+@x@ADAE.;.aLA'c =8`=)@}@ @>A( A B@B@x@ADAE89aL 'c =A="@B9@A/C  B@BW@x@ADAE a =QDa"@@ #"F jEi?xm(a& *8A  B@B@x@ADAEEaL'a =AO`=$_@@+ @>^ f!@+ B@B@x@ADAEPaLg/t ==+$_@@%$.C  B@B}b@x@ADAEndQap +@sc = +k\`= @b@&Gp 4*J(:lȋ݀ XZa- B@B(@x@ADAE]aL/c =hg`=$_@z_@ @>A^A[ B@B@x@ =A @EhaL?M =#="@@ 4#V҃/D]$ B@DB@x@ADAEDՁ #d =sa!I@\Ӏ@ +A3.^5D3@SUU AҀ$ B@B+@x@ADAEtaL?C =!~`=$_@HЀ@dAπ$ B@BA@x@ADA  EaL 8A =g`= @‹@&/!?. B@DB@x` =A @EFaL"A =L`="+@-D@!+xЂ똳MF'ˡ$]& ` DC@0A B@DB@x@ADA E c  Ia @A@dA }@  B@B@x@ADAEg) !(!,a"@~%/CA[ B@B@x@ADA`DEL"1D ='@@&S<PZV{[W[9 An  B@B@x@ADAEYoaL2(@c `= @@+ @+=gR  `'%` H B@BA@x@ADAE=laL AAj = v=+'u @mm@ /C! $sd" B@B@x@ADA#E'a")A$ =p.`= %@%@&O9SZ{nU s 1cE{A&C$+' B@Bu$@x@ADA(E. AR`c)l+a$_*@"@ @"`C >+' ` , B@B@x@ADA-E݁ aaC. =="/@Bހ@ /T0 1 B@B@x@ADA2EbAbqA3 =H`="4@@ +x-jψԩd1>FP`Ya& f=56 B@B+@x@ADA7EQaLr8&+8 A`="9@@dA:$; B@B@x@ADA<EMaL AA= =W= >@O@&/li?N@ B@B@x@ADAAEn a :B =`="C@@&4x9>1PwExI6˯1AJ!a& 8GD$E B@B@x@ADAFEcG a)AH@r@dAI J B@B@x@ADAKE@Yl CL =Ȁ="M@쿀@& /CNXO B@B@x@ADAPECzbAQ =p@"R@Wx@ A]/U,T[P,Cz@ kASw mT B@B@x@ADAUE2aLcV } `= W@Hu@dAXt Y B@BA@x@ADAZE/ aL A a[ =Z9=+ A\@0@&/C].^ B@B@x@ADA_E D` =a a@,@ 8v0eO'$T1\MZC#MNAb蠂@4-c B@B@x@ADAdEaL Ae "`= f@ @ 0 @Jg|堂 h B@B@x@ADAiEg#aL Aj = +="k@@#V/1(Cl cm B@B@x@ADAnE[$aAco =b/`="p@Z@ P >l#%J 7XJ/AqmY r B@Bm@x@ADAsEX0aLCt _:`="u@V@dAvQ w B@BA@x@ADAxE<;aL  Ay = =$_mz@l@&/{ $s| B@B@x@ADA}É c~ =wFa"@ʀ@&QܖP5[C)zrgsRa& DC B@B@x@ADAE-GaL 9C kQ`=)@ǀ@ @=g'  B@B@x@ADAERaL6h!1d =HD^`="@;@10=nM`q9WU'LF\3M a& 8~C  B@B@x@ADAE 2@c @Ai"@8@ )A @>}7  B@B@x@ADAE A9Ac = = @@ ! @W/RC  B@B@x@ADAEmjbBQa =u`="@@ (zE(twѫHeva& A  B@B}@x@ADAEfvaLR9`G`=$_@q@ ! @>ѨA[j@ B@B@x@ADAEcaL aaa =m="@d@&/7CX B@B@x@ADAEBalbqa =%`="@V@&K^,qſ\Ω\D אW'A  B@B@x@ADAE׀ra "a"@F@ '>A B@B@x@ADAEԀ !F+ =Zހ=)+@Հ@ ! @+A/ C- B@B@x@ADAEbA =Ж`="@,@ 'AYM3{r*NH==xMˏa& A$ B@B@x@ADAEHaLc`=$_@@ ! @>A| A B@B@x@ADAEfEaL) t = ;O="@F@ /3C B@B"Y@x@ADAEad =`="@`@ +#Z^|5G@j.i8ބm*N0B-m A B@B+@x@ADAEXLc aI$_@`@ # @>AQ  B@BA@x@ADAEA&A[ B@B}@x@ADAE'aL91D =0= @A(@P$/AM  B@B@x@ADAEaL) !"!*C =="@ @&/bCW B@By"Y@x@ADAEBā "1d@a"@V€@ #p6M5oJ\RKjLѺ7{ =A A B@B`x@9 A @E|aL2"@c =$`=$_A@F@ ' @>  B@DBA@x@ADA Ey%aL A*Aa =]=+" @z@ ! @/C - B@Bw@x@ADAE5&aBQd =;1`=!I@+3@&p"chF I{)ҝa& eA2$+ B@B@x@ADAE퀈R*`c =8<$_@0@ &+ @>A{/  B@B@x@ADAEf a a IA =6="@@&/C  B@B@x@ADAE=bbqd =H`="@@ #$$96ofhWPyT?l}*A l$! B@B+@x@ADA"EW^IaLr c# =!AS`=$_$@@ ' @>A%P & B@B@x@ADA'E;[TaL t( =e=")@k\@ ! @A/3C* A+ B@B@x@ADA,EUad- =j``=".@@ Ig(;kvDFx9a& >mA/B@0:+0 B@B-@x@ADA1E,ρ c2 =ka$_3@@ ! @>A4& A5 B@B@x@ADA6E̡ @Y 2a7 =Հ="8@@̀@ /AC9 : B@B"Y@x@ADA;Elbd< = Gw`="=@@&h5;: {-cHi8 A> ? B@B@x@ADA@E@xaL2cA =?`=$_B@@ @>AC D B@BA@x@ADAEE@&/CH=I B@B@x@ADAJEldK =%a L@@ 4' @:N%n?|?Ќ_a& AM-N B@B 4@x@AD>OEװaL:cP = `=$_Q@t@ ! @Aac Yb B@BA@x@ADAcEaL =Ad =](= e@@ ! @҃/Cf,Wg B@B@x@ADAhEځ ANi = a"j@+؀@ 'K$p5.Ecȯ(i*0f݅2; [JAk׀$l B@B@x@ADAmEaL7l#n =`= o@Հ@ ! @>Ap{Ԁ q B@B@x@ADArEeaL) !! As =.="t@@ /fCu"Yv B@B#@x@ADAwEJa"1dx =Q`="y@I@ +#~fC5ԧ H5{4# AzlH { B@B+@x@ADA|EWaL2@3C} =!AN`=$_ +@@3~@E@ # @>AP  B@BA@x@ADAE:aL AAc =AV =+"@k@ ! @A/ס  B@B{@x@ADAE BQc =ja"@չ@ -@}!X 6^D3$ƵDA$ B@B@x@ADAE,taLR`c =i`="@Ŷ@>A%  B@B@x@ADAEqaL a#a+F+ =z=$_@@r@ /S4 A B@B@x@ADAE,ab3q#A = B3r #@*@ u'8C|N:ߘƏnlfyѪT66 xD- B@B @x@ADAE rd =?0a+$_@'@ # @=$&A[ B@B@x@ADAEဈ3c == @@ ! @A/C  B@B@x@ADAElbA;c =`="@@ P_w$OhXɆ@p0 v)] {?cA욀$ B@BP@x@ADAEUaLa = &`=$_@p@ ! @>AЗ A B@B@x@ADAER'aL; ;a =3`="A@U @ ` ՙj$ID@ a꺳qH^C  B@BJ@x@ADAEƀW IA =>a"@D @ !>  B@B@x@ADAEÀ? Wc =à='u@Ā@ $ @Ƕ/FrC+ B@Bv@x@ADAE?bc = ˅J`="@*}@&p_VpܯCjFc=`f Nͥ3_v VTA|  B@B@x@ADAE7KaL ~ =‚U`=H@d_@z@ #>zy  B@B}@x@ADAEe4VaL d =5>=$_@5@&/xC B@B@x@ADAE p;  c =aa"@@A)</m`&ufOie$\a& VAo퀂$+ B@B@x@ADAEVbaLW v; =l`=+$_@@ PAOA[d B@B@x@ADAE:maL 8 == @j@  `@ A/0zC  a B@B@x@ADAE`naAc =ygy`="@^@ Ġu flTqzp?jڿa& AA B@B @x@ADAE+zaL $D =id`= @[@ # @=%  B@B@x@ADAEaL) !!d =="@?@/HC  B@B@x@ADAEс "1c =Jؐa"@π@ '&l &B/싋nn8fuusa& ǸA + B@B@x@ADAEaLA ?i2@,D = 5>՛`="@̀@ !>* ˀ  B@B@x@ADAE䆜aLAAd =="a @3@@ &+ @/1C B@B@x@ADAEkBaBQc = I`="@@@ # n!*˫AjĜg'!0o! Rom?  B@B@x@ADAER<`a =Fa+$_@@o=@ # @Q 2@Bq !@<  B@@B@x@2ADA@> @ +@aad 6@  " 9@~A/uV  B@@B@x@ADA@> @@L}  ' 9@T@  -Zu:ztB@ZbK]J4f OC] $+ B@@B @x@ADA @> @kaLArIA 6 >  `=+$_ 9@D@ ! @>A CǶ0z`@B@x@ADA@> @haL/d 6I\r= @i@ &+ @/5/$ B@@B@x@ADA@> @$aAc 6*`="@*"@ +#ADʜ笂D2'V5Qf\C@@ ܲD!$ B@@B@x@ADA@> @܀<G 6 >  '$_ 9@@ # @=z  B@@B@x@ADA!@> @dـ) Ad" 6I-="#@ڀ@&/*C$$% B@@B@x@ADA&@> @b @UMaLA.O / B@@B@x@ADA0@> @9JaL d1 6T= 2@iK@&/C3  4 B@@B@x@ADA5@> @ac6 6 >  q v"7 9@@&, ,>^@LI`;|IA8@ 9 B@@B@x@ADA:@> @+   l a+$_< 9@@ PA=$ > B@@B@x@ADA?@> @aL a@ 6IĀ= A@?@ :! @C/ޗB C B@@B:@x@ADAD@> @vac(>&@ cTG@1f!@+H B@@B@x@ADAI@> @!T/aLA,IAJ 6 5=>z(`=+ K@q@ @>ALp M B@@B@x@ADAN@> @+)aLAO 65="P@-@ /DQ,R B@@B@x@ADAS@> @k瀈A-cT 6 >  4a$_U 9@@&DF"Nt4 @՟5aL CY 6I?`=$_Z@o@ @>[᠂A+\ B@@B@x@ADA]@> @@aL A!!d^ 6="_@靀@&/dC`U c a B@@B@x@ADAb@> @@XAa" 1cc 6 >  ^L`="d 9@TV@ As4Hz?w;5MboO!ꅜ=$3Kdmo1eU+f B@@B@x@ADAg@> @MaL2@ah 6 >  [W`="i 9@DS@ !>AjR k B@@B@x@ADAl@> @ XaL AAdm 6I[= n@@ A/)o*p B@@B@x@ADAq@> @Ɂ BQcr 6ca"s@)ǀ@ +#!,ɓ"DYΣ-mfF[C@ F+tƀ +u B@@B$@x@ADAv@> @daLR`aw 6 >  n`=+$_Ax 9@Ā@ # @>AyyÀ z B@@BA@x@ADA{@> @d~oaL aad| 6I,="}@@ W! @/C~ B@@Bt-&@x@ADA@> @9pabqc 6 {=@{`="@7@&[)zR0, \۠&f0t ɆAj$+ B@@B@x@ADA@> @U Ar55M 6=a+$_@4@ &+ @`.f+N@T B@@B@x@ADA@> @9  d 6=$_@i@&A/?C  B@@BA@x@ADA@> @bA5c 6 >  t`=" 9@Ԩ@ #!$:%PFα+tzs FH ZA@ B@@B3R@x@ADA@> @*caLf+4 5=h`=$_@ĥ@ ' @>A# B@@B@x@ADA@> @`aL F+ 6i="@>a@&/vC : B@@B`@x@ADA@> @a-A 6="`="@@3RĠ^Ft+\ʨ8j A@4 B@@B@x@ADA@> @Ӂ c 6a"@@ &+>A ` B@@B@x@ADA@> @Ѐ) -C 6ڀ="@Ҁ@ ; &+/Cр B@@B$@x@ADA@> @jb a 6/`=!I@~@ # @_֤zKW ^?e>~W@ Aꉀ  B@@B  @x@ADA@> @DaL-d 6`=$_@v@ # @>A҆ ` B@@B@x@ADA@> @AaL  D 6K="@B@ ! @҃/ =CU B@@B[p@x@ADA@> @? =A 6a"@S`@ @pӗ2&x^bjB?-@ BA$+ B@@B@x@ADA@> @LA 6aI+$_@C`@ ! @>AA ` B@@B +&,+@x@ADA@> @L 6taI$_@,l@ '# X1ZxiS*XYpT޺wkRUCk@4 B@@BW@x@ADA@> @&aL D 6q`="@i@ @>xhA B@@B@x@ADA@> @c#aL !!d 6(-="+@$@ $#V>/jC $M !$ B@@Bq:@x@ADA@> @ށ W"1c 6t@K"@܀@ -&?)HKຝrT֢ٓvG)q1Aj B@@B-&@x@ADA@> @TaL2>@a 6`="@ـ@>N  B@@B@x@ADA@> @8aL) AAd 6 ="@h@ -&/ǹC  B@@B@x@ADA@> @OaB>Qc 6pV`=%@M@$AhR|DWG66)hς:fkB'? `̿ B@@B@x@ADA@> @* aL}.`IA 6gS*`=$_@J@ &+ @>A#  B@@B g@W@x@ADA@> @+aL aad 6= @>@P/ѯD G B@@B| @x@ADA@> @ b.qc 6@6a"@@$ĠIDp O P)lƴvgmKsΧ#`@ 5A- B@@B}@x@ADA@> @x7aLrc 6 >  A ] B@@B@x@ADA@> @uBaLa 6I= @w@ ! @Ġ/Cv B@ ,B@x@ADAEi1Caa ="8N`="A@}/@ 'vܿ芹9mHV^}%9"*jykÊۥA.  B@B@x@ADAE逈:>6IA =5Y$_@34 @n,@ ! @>A +  B@B@x@ADA E怈 Ac =AV="@@ /-CT B@B; @x@ADAE?Zb>c =e`="@S@&=CpMZ+! VяXهi?a& "$ B@B@x@ADAEZfaL>a =p`=+$_@C@ # @=  `m} B@B@x@ADAEWqaL) d =aa="@X@&/X;D)A B@B@x@ADA Era>c! =}`=)"@(@g'㶱k !'W|H8o`B?;08FmA# A$ B@Bm@x@ADA%Eˀt& =a$_`3iA'@@ PA(x ) B@B}@x@ADA*EcȀ@Y d+ = +?Ҁ= ,@ɀ@ &+ @+/C- . B@B@x@ADA/E郉bc0 =`="1@@&$Zt-ƢKd7'RVT05M+K^Ha& [[2i$+3 B@B$@x@ADA4ET'M5 =`= 6@~@ # @=A7M 8 B@B@x@ADA9E89aL : =C=$_+;@h:@"YĠ/< $se`#= B@B@x@ADA>E ?c? =ga"A@@@ '!A}J(a 94DeY#/ԀF+A>@4a B B@B@x@ADACE)aL7 DD =`=$_E@@ @=F" G B@B@x@ADAHE aLA!!dI =ֳ="J@A@ &+/"KL B@B@x@ADAMEeakV"71cN =@l`="O@c@ <>,GS~0t!&y0^` FEDP$Q B@B@x@ADARE`2@S = @i`="T@`@>AU_ V B@B m> @x@ADAWEaL) AAAdX = `$="Y@@ /iCZ~[ B@Bu  J@x@ADA\EiրBQc] =a%^@}Ԁ@&'8 >9Mf0S DU<*a& b_Ӏ ` B@B@x@ADAaEԎaLR/`ab =`=$_c@mр@ &+ @JAdЀ e B@BA@x@ADAfEaL aadg == h@茀@%/ iT j B@B@x@ADAkE>Gab/qcl =M`="m@RE@ +#!$iTFn2%V,<Pkc juda2)a& DfF+nD$o B@B+@x@ADApEr7Mq =J$_r@FB@ ' @>AsA t B@B@x@ADAuE dv =U"w@~ W! @҃/x) y B@BW@x@ADAzELc{ =̾ u@"A|@'@ PBi.;˿$ i:@ w  @  B@B@x@ADAEbmaL d =7w="@n@ /p  B@B@x@ADAE(aA'c =/!`="@&@&JǷ m7_VTaJUt?>= x˦6hMDi B@B@x@ADAES 7c =,,a"@#@ #>AM  B@B@x@ADAE7ށ @Y) a = ="@k߀@"YA/Cס + B@B}@x@ADAE-bo$7a =o8`=*@֗@&.Z_-e+|9AQFB{a& ]*B  B@B@x@ADAE)R9aL7/M =jC`=$_@Ɣ@ PA& + B@B@x@ADAE ODaLc =X="@AP@&/0<$ B@B@x@ADAE Ea7c =CP`="@@&N,LC4 N…iJ%oq8ƾa&   B@B@x@ADAE $0D =;[ @@dA  B@B@x@ADAE AAA =ɀ="@@ +*'mA/~ + B@B}@x@ADAEh{\b08 =%g`=!I@|y@&J؟3#%!G)OLs}D4ga& dmJx$+ B@B 4@x@ADAE3haL C =r`= @mv@ PAu  B@̠B@x@ADAE0saL !0!c =:="@1@ /S B@B@x@ADAE> "1a =~a"@R@ P#$+KǘLZ =j( F+ϓB@[Wa& /ZD适$ B@BP@x@ADAEaL2@D =`="@E@dA怂  B@B @x@ =A @EaL) AAc =X="@@ }/?( B@DB@x@ADAE]aB$)* =c`=$_A@'[@&M%Wvs\ƌtw(LPa& iSZ + B@B@x@ADAE}aLR`A =``= A@X@ &+ @"`^=JwW  B@B@x@ADAEaaL aaA =.="@@&/wiS  B@BDK@x@ADAÉ bqf+ =ԭa"@ˀ@ #$AK+_yk=R{dqS#g riSh  B@B@x@ADAESaLrC =Ѹ`=$_+@Ȁ@ ' @>L  B@B@x@ADAE7aL; c =zE`=+"@<@ ! @/Y= B@B@x@ADAE(  a =fB"@9@d!  B@B@x@ADAE @Y D =="@<@  +=  B@B@x@ADAEb c =;`=+$_@@  ǶZ{y,3@C s@{skдG = K@1  B$@x@ADAEgaL8C =`= @@d  B@BW@x@ADAEdaL) A =n="@f@ /oC }eA[ B@B@x@ADA Eh ac ='`= @|@ 3"p) j/_yh Ha& koA W B@B| @x@ADAE؀8C =$ @p@ +# @>A[ B@B@x@ADAEՀ A =߀="@ր@ !#VA/CCR B@B@x@ADAE=v@T8c = `="+@Q@ASꏮŊl~KjK43ͱ,K@  hA  B@Bx@x@ADAEI aL( C =`=$_!@A@ &+ @>" # B@B@x@ADA$EFaL F}A% =\P=$_&@G@ /DC'(( B@B@x@ADA)Ea})c* =#`="++@&@ +#!Ġ-{pV =vE`= ?@m@dA@K A B@B6h@x@ADABE6(FaL AAdC =2="D@f)@ W/wpCEҡ WF B@B@x@ADAGE B QcH =rQa +I@@ yyqE^cLɧ##7iͲa& gJ=K B@B+@x@ADALE'RaLR!`GM =e\`= N@ހ@dAO!A[P B@B$@x@ADAQE ]aL aaAR =ܢ= S@;@&A/)T U B@B@x@ADAVET^ab!qcW =B[i`="+X@R@ +Y_'.JDOd@h "ha& 7F+Y +Z B@B+@x@ADA[E jaLr9!C\ =:Xt`= ]@O@dA^N _ B@B@x@ADA`E uaL Aa == b@ @ WA/4Cc} d B@B@x@ADAeEgŀ9cf = ̀a"+g@{À@%o҃~U0_^v{1^jQ2hOZAh€-i B@B#@x@ADAjE}aL#9Ck =ɋ`= l@l@dAm˿ n B@B@x@ADAoEzaL dp == q@{@&A/CrRs B@Bt @x@ADAtE=6aAcu =<`="+v@Q4@ Ġ_*8 DRb=$P\3rn9Aw3$x B@BC@x@ADAyEDz =9a {@A1@dA|0A[} B@B@x@ADA~E뀈 !8 = S="@@"Yg/C' B@B@x@ADAEbc =`= +@&@&75:Fzb7.zsncā>R2A$ B@B@x@ADAE|_aLc =`= @@dAvA[ B@B@x@ADAE`\aL) a = )f= @]@&/C  B@B@x@ADAEaa =`="+@@&m : z%6پSݏ|_a& Ag  B@B@x@ADAERЁ G = @@dAK  B@BA@x@ADAE6́  a = ׀="@f΀@ /gC  B@B@x@ADAEba =m`="+@І@&$ĥBV%YbfVb bQΘ.<- B@B@x@ADAE'AaL D =e`= @@dA  B@B@x@ =A @E >aL!!c = ?=G= A@;?@ /Y  B@B@x@ADAE A" 1c =:a"+@`@! ƹA#l^o2P9.K"Pf"{mSF+@4WA B@BqJ@x@ADAEL2@ =>w@ @@ @=#@ B@B@x@ADAEaLAAd = ="@@&/C| B@B@x@ADAEgjaBQc =q `=$_ 4@{h@ g"*F"H O: Iw4a%rg@4 B@B@x@ADAE"aLR2`c =n`=$_@ke@ ' @>d  B@B@x@ADAEaL) aaa =z)= @ @ +! @$/}rQA[ B@B@x@ADAE<ہ b2qa =$a"+@Pـ@ b zhҺDW*x ^L疧tn|,)F+؀  B@B@x@ =A @E%aLrf+ =/`=$_@@ր@ ! @>AՀ  B@DB@x@ADAE0aL c = W=$_@@&/C'A B@B@x@ADAEL1a2a =R<`="+@%J@ &#!gʿ&~ٰQCl w(a& AI$+ B@B@x@ADAE|=aLa =OG`=$_@G@dAuF  B@B@x@ADAE`HaL 2a = 8 ="@@ iSA/SC  B@B@x@ADAE漁 2"U =Sa$_@@ ̖85>Q^ L"0{@9HA]|a& yf A B@BJw@x@ADAEQuTaL$c =^`= @귀@dAJA[W B@B@x@ADAE5r_aL2c =|="@es@ W/3y ? B@BA@x@ADAE-`a :D =e4k`= @+@ /c$zDNHtasAC͞F+ A B@DB ?@x` =A @E 6h c = ?=9a"@@ +!#VW#%Eʢ/߱3ehN>'\j C  B@B@x@ADA EVaLW2D =`="A @@ $ @>   B@B@x@ADAESaL W8 = ]="@U@ ! @ՙ/C|T  B@B@x@ADAEfaa =`="@z @Ġ=Re}1C52n%$}NI6Cda& w$  B@B@x@ADAEǀ a =$_@k @ &+ @> A[ B@B@x@ADAE !!a =}΀=" @ŀ@Ġ/>}P!Q " B@B@x@ADA#E;b"1a$ =䆱`="%@O~@ #+^'( N`kp(ɗ1\BE]BF+&}@4' B@B}@x@ADA(E8aLW2@a) =胼`=$_*@C{@ ' @>A+zA[A, B@B}@x@ADA-E5aL AAa. =N?="/@6@/nC0&1 B@BA@x@ADA2E k @sBQa3 =a"4@%@ARaM40ccj5In@ \A5$6 B@B@x@ADA7E{aLR`a8 =`="9@@dA:u렂A[; B@B@x@ADA<E_aL)Axaaa= =@,=$_+>@@ &+ @+A/C? @ B@B@x@ADAAEaabqaB =h`="C@_@ :Ar<SbC[Q*įzkCC'͟3a& M[ADf AE B@B:@x@ADAFEQaLraG =e`=)H@\@ @= IJ AJ B@B@x@ADAKE4aLaL =!=$_M@e@0<Ġ/wCNѡ O B@B-&@x@ADAPEҁ aQ =ga"R@Ѐ@& _:e j <09na&  AS;$T B@B@x@ADAUE&aLaV =cx@$_W@̀@dAX@AY B@B@x@ADAZE aL Aa[ = ґ="A\@:@&/_ C] r^ B@B@x@ADA_ECaa` ==J`= +a@A@&|+R#n6hX'f Ab`4c B@BiS@x@ADAdE ae =9Ga f@>@dAg= Ah B@B@x@ADAiEaj =x@"k@~ /Cl{m B@B@x@ADAnEfLAao =&&'p@z@y҃z*2()ւ~ɭxaq!Aq汀$r B@By@x@ADAsEl'aLat =1`="u@j@dAvʮA[w B@B@x@ADAxEi2aL) Aay =s= z@j@ #/ C{P| B@B  @x@ADA}E;%3aq a~ =+>`="+@S#@  aN9D'xkxj?d W7na& A" A B@B@x@ADAE݀A =(Ia A@? @dA A B@B@x@ADAEڀ a =V="@ۀ@ :/XC% B@Bc'@x@ADAEJbAa =̜U`="@$@ "yX ۍ~R:VU"a& A  B@B@x@ =A @E{NVaL a =``= @@dAtAA B@DB@x@ADAE_KaaL !!a =7U=+ A@L@ /(C  B@B@x@ADAEba"1a = m`= @@ msZ>鮾H6*5ݸAxa& Ae$+ B@Bu@x@ADAEP 2@a = xa @@dAI  B@B@x@ADAE4 AAa =ŀ="@d@ /:C  B@Bt@x@ADAEwybABQa =c~`="@u@$ U!FtsoOag#ܣ^Ş/zA;A[A B@B@x@ADAE%0aLR`a ={`="@r@$=gA[ B@B@x@ADAE -aL) Aaaa =6="P@9.@+ @g/C  B@B@x@ADAE bqa =Ha"@@ ARqkA7*Z#ϯ Ara& ~A怂  B@B7@x@ADAEaLCra =8`=$_@@ @=   B@B@x@ADAEޝaL ) a =="@@ A/3RC~ B@B@x@ADAEeYaa =``="@yW@&$Ǧ@ +_ fsx/"Һ I_"a&  oAV  B@B$@x@ADAEaLa = ]`=$_`3@iT@ &+ @>AS  B@B@x@ADAEaL a ==+)`3a@@& /CCP B@B @x@ADAE:ʁ a =a @NȀ@&Z,sl_Łҫ/@M߹%#k[pAǀ$+ B@B@x@ADAEaLAa =`=$_@>ŀ@ ' @>AĠA[W B@B@x@ADAEaL a =U="@@ !#VJ/C% B@B@x@ADAE;aAa =A`="@$9@g:軓Jx ( у ,ja& A8@0 @  4 B@Bm@x@ADAEza =>a"@6@ PAt5  B@B@x@ADAE^ a =#="+@@ />C + B@B@x@ADAEba =`="@@ #!+ncFAXVŷC͆b pAe`4 B@B@x@ADAEOdaLA =y@)@馀@ @>AI  B@B@x@ADAE3aaL aj="@cb@&/m\C  B@B`x@9 A @Eaa =j#`="@@ VPSS¬{Άа_$` : + B@DB@x@ADA E%Ձ $ a =b $_ @@ ! @>   B@B@x@ADAE ҁ  !!a =ۀ=+'u@9Ӏ@& /   B@Bwg@x@ADAEb"1a =<(`="@@ +#!CJiO;n#a& -+ B@Bu@x@ADAEE)aLA2@a =3`=$_@@ ,W @>A󇠂A[ B@B@x@ADAEB4aLAAa =L="@D@ !/)5IA zC! B@B@x@ADA"EeABQa# = @a"$@y?`@&` a(^,Kib1ca& A%@0A& B@Bm@x@ADA'E϶LR`a( =KaI")@hJ`@ &+>A*A[+ B@BA@x@ADA,EL; aqa- =uWaI".@Nm@ /`C/l 0 B@B@x@ADA1E'XaLra2 =rb`="3@>j@ !>4i 5 B@B@x@ADA6E$caL Wa7 =M.="8@%@ $P59$A[: B@B4@x@ADA;E a< =na"=@#ހ@ #',3 B)B<1\uN^OV>Ns>D>݀ ? B@B@x@ADA@EzoaLaA =y`=$_WB@ۀ@ # @W>Csڀ D B@B@x@ADAEE^zaL aF ="=+"G@@ ! @/CH mI B@Bom@x@ADAJEP{aaK =W`="L@N@ ĠK>M0>y0&_$-jlbVa& #AMd$+N B@B@x@ADAOEO aLAaP =T`=$_Q@K@ &+ @>ARHA[S B@B@x@ADATE3aLaU ==+$_V@c@ /^CW X B@B@x@ADAYE AaZ =jȝa"[@ο@$ĠM@<}?x;pX2$8"Ċ?3a& T}A\9 A] B@B@x@ADA^E$zaL:a_ =fŨ`="`@@ #>Aa! b B@B@x@ADAcEwaLad =Հ= e@8x@PA/QCf g B@BP@x@ADAhE2ak?ai =?9`="j@0@&Y҉:j/fJ )Lpa& -k+l B@B@x@AD>mE An =76$_o@-@ PAp, Aq B@B@x@ADArE瀈) as =="t@ @&A/Duy耂v B@B}@x@ADAwEdb-&ax =`="y@|@ +#&lWA˺Derw?D |Abr|?a& vAz蠀 A{ B@B+@x@ADA|E[aL a} = `= ~@h@ PAȝ `au" iF B@BA@x@AD&+EXaL !!a =b="@Y@ !A/CO B@B@x@ADAE9a"1a =`="@M@&TpE2R!eJsVZKCca& A$+ B@B@x@ADAÈ2@a = a"@A@ &+$+=C B@B}@x@ADAEɀ AAa =TӀ=$_@ʀ@&/5,C$ B@B@x@ADAEbBQa =`="@"@&^\X;}Yyg C&Za& A A B@B@x@ADAEy=aLR`a = z +$_@@ ' @>r B@BA@x@ADAE]:aL aaa =*D="@;@ !$҃/C  B@B@x@ADAE bqa =a"@@ ?'NJytN\ j#{!S[uAd B@Bm@x@ADAENaLra =`=$_@@ ! @>AH  B@B@x@ADAE2aL) a =="@b@ ?/YC  B@B@x@ADAEfaa =im*`="@d@ +#]Tz`+.+z?xÔ naa& $A9 A B@B+@x@ADAE$+aLa =aj5`=$_@a@ # @>A  B@BA@x@ADAE6aL a =%=+"@8@&/д  B@B@x@ADAEׁ a =GAa"@Հ@ 1+"^8Cx%+<ѕa& ռD- B@B1@x@ADAEBaLa =6L`="@Ҁ@ !=р B@B@x@ADAE݌MaL a == @ @&/#Cy B@B@x@ADAEcHNaa =OY`="@wF@M#!1ư׹dlрL,o-EQeˎAE$ B@B@x@ADAEZaLa = Ld`=+$_@gC@ PABA[  B@B@x@ADAEp= a =e"@~ ! @/=CR$ B@Bt@x@ADAE9LAa =p'@M@%QVMNGg ,г̹ANA@4P B@B@x@ADAEqqaL ={`="@=@ &+$>A  B@B@x@ADAEn|aL) a =Lx="@o@ /C#A[ B@B@x@ADAE*}aa =0`="@"(@ +#+A:Cy֖Y*DFdzva& _JA'  B@B+@x@ADAEy  a =-$_@%@ @>r$  B@BA@x@ADAE\߀?!!a =*=+"@@0AG  B@B@x@ADAE2PaL AAAa =Y= @bQ@ WA.uL  W B@B@x@ADA E aBQa =h`="@ @&v|kѬx*9ߍrvYa& *MC8$ B@B@x@ADAE#ā R`a =aa+$_@@ # @>AA[W B@BA@x@ADAE aaa =ʀ= @7€@ ! @Q`BA/4@G B@B@x@ADAE|bAbqa =F`="@z@ AX@3@OxyH_K^ኜ_~Ua& ңA$ B@B@x@ADA!E4aL$ra" =:`=$_#@w@ ! @Q`AB?A$v % B@B@x@ADA&E1aL) Aa' =;="(@ 3@ /$A)x2* B@B@x@ADA+Ec퀈a, =a"-@w@&C6 /zs4Z iyO]fbe׏a& ZA.ꀂ m/ B@Bm@x@ADA0EΥaLa1 = `="2@g@ #>A3瀂A[A4 B@B@x@ADA5EaL a6 =z=)+7@ᣀ@ ! @+/s8M9 B@B@x@ADA:E8^aa; =d`="<@L\@ '$ T&'um"XWY1P!2 5{=[ > B@B@x@ADA?EaLa@ =a{ +$_A@BX AC B@B@x@ADADE aLaE =`="F@%̀@&g eElSF4RcI:f!%rYG̀$H B@Bg@x@ADAIExaLWaJ =`="`3SK@ʀ@ &+ @>Luɠ M B@B@x@ADANE\ aL}aO =5= P@@ W$ @>/JQ +R B@B@x@ADASE?!ao(aT =F,`="U@=@ iS#B:kNJK;5;\OpIAVg$W B@BiS@x@ADAXEM  zAY =C7$_Z@:@ # @= [G \ B@BW@x@ADA]E1 @Y WA!^ = ="_@a@ !/teC` Wa B@Bs@x@ADAbE8bWAc =dC`="d@̮@g(t!p'k遾:KfU8ZyB&a& nAe8 +f B@BW@x@ADAgE"iDaL Ah =`N`=+$_i@@ &+ @>Aj k B@B@x@ADAlEfOaL!!Am = o=$_n@6g@ :/BCo p B@B:@x@ADAqE!Pa"1Ar =>([`="s@@ #!ĠفbvVeb@^Ⱥ!}AHt u B@B@x@ADAvEف 2@Aw =9%fa+$_x@@ # @>AyA[z B@B}@x@ADA{E AAA)A| =="}@ ؀@ ! @ |1iO~x׀  B@B@x@ADAEbgb}!! =! r`="@v@ Nֲ)>!.d hگ$ՃD GYս 'F+⏀- B@BA@x@ADAEJsaLAR`A = }`=+$_@f@ ! @"`C >Aƌ ` B@B@x@ADAEG~aL aaA =zQ=$_@H@ /2 AM B@B@x@ADAE8aAbqA = `="@L@ +#!AIHgj uf M51t8a& {A$ B@B @x@ADAErA =$_@<`@ # @>AA[ B@B@x@ADAEL A = S€="@@ W!A/C" B@B0<@x@ADAE tbA =z`="@!r@ ҸR-Bo5Tk {c3AN\2 f+q$ B@B@x@ADAEw,aLA =w`=+$_@o@ ! @>Aqn  B@B@x@ADAE[)aL) A =/3="@*@&/WlW  B@B@x@ADAE A =a%@@ # @A5wx料 r e-5t<yF+b  B@B@x@ADAEMaLA =`=$_@߀@ ' @>AF  B@BA@x@ADAE1aL A =="@a@ :! @҃/3C  B@B@x@ADAEUaA =g\`="@S@ y.8%~G \Gq  ja& A7- B@B@x@ADAE"aLgA =`Y`=+$_@P@ ! @>AA[ B@B@x@ADAE aLA = ="@6 @ /=C  B@B@x@ADAEƁ AA =Ea%@Ā@&m~حI6,R$fG 91 SU @3A B@B@x@ADAE~aL A =5`=$_@@ # @>A A B@B@x@ADAE{aL A =="@ }@&/Dw| B@B@x@ADAEb7aa =>`="@v5@&?ӗwA9; I sJO9&A4$ B@B@x@ADAE a = ; | "@f2@ PA1  B@B@x@ADAE쀈) !!a =="@@ #&+'+/CL B@BR.@x@ADAE7 b"1F+ = `=!I@K@ # @ n1({Qxq0  A A B@B@x@ADAE`aL2@a =!`=)@?@ # @>A  B@B@x@ADAE]"aL AAa =Vg="@^@%/C"J B@B`@x@ADAE #aBQa =.`="@ @ m al̎֓ؑS[; @Zۑia& rA$+ B@B; @x@ADAEwрR`a = 9a+$_@@ ! @`z.Ap   B@B@x@ADAE[΀ aaa =#؀="@π@&$/=^A  B@B@x@ADAE:bbqIA =E`= + @@& 㘁gEVg @"CXa& A a A B@B@x@ADA ELBFaL$rA =P`=$_@愀@ PAEA[ B@B@x@ADAE0?QaLa =H="@`@@ !#V+/C  B@B@x@ADAE a = c]a"@\`@ +'A^9v_!|۝%:OxS`Ni1A  A7+ B@B@x@ADAE!LD =_gaI"@@ !$>A  B@B@x@ADA EhaL) Aa! =ι=""@5@&/,# $ B@B@x@ADA%EkiaC& =9rt`=$_'@i@ # @qbgj:_?{ya& %o( A) B@B@x@ADA*E#uaLa+ = 4o`=$_,@f@ ' @>-e . B@BA@x@ADA/E aL a0 =*="1@ "@ ! @$/3%o2w!3 B@B@x@ADA4Ea܀D5 = a"6@uڀ@ m ">R H|=Q a& xIA7ـ-8 B@B@x@ADA9E̔aLA: = `=$_;@e׀@ ! @>A<ր = B@B @x@ADA>EaL a? =x=$_@@@&/CALB B@B@x@ADACE6MaaD =S`="AE@JK@& zo&AKGA[L B@B@x@ADAMEaL aN =R ="O@@ !$A/ԁCP!Q B@By + +P 5@ADAR @E aS =ĺa"T@ @ P'Ba71V-'XoG%l]1` qAU$V B@DBP@x@ADAWEvvaL aX = `="Y@@ !>AZp A[ B@B@x@ADA\EZsaL) !!a] =+}="^@t@&/#]C_ ` B@B@x@ADAaE.a"1ab =5`=$_c@,@&~[?lS: ʵ-uz. CN. Ada e B@B@x@ADAfEL 2@ag =2a$_h@)@ `@=giE j B@B@x@ADAkE0  AAal == m@`@ ! @A/I/Cn o B@B@x@ADApEbBQaq = f`="r@ʝ@ 'A)jP!{4'DR ;As6t B@B@x@ADAuE!XaLR`av =^`= w@@ ! @>Ax y B@B@x@ADAzEUaL aaa{ =^="+|@5V@&/C} ~ B@B@x@ADAEabqa =8} "A@@Hu#!\-r(d@>Bʭ?u%xL3 q@ A   B@B@x@ADAEŀa =π="@ ǀ@ !A/`Cvƀ8\ !kW B@Bg@x@ADAEa bAa =`="@u@%M`KyOx=)մa& A~$ B@B@x@ADAE9aLa = #`=+$_@h|@ &+ @>A{  B@B 5@x@ADAE6$aL) Aa =@="@7@ /CK B@B@x@ADAE6 a =/a%@J@ +# @AELnw"r0 A @ XF B@BA@x@ADAE0aLa =:`=$_@:@ # @>A쀂  B@B$@x@AD>E;aL a =Q="@@ !A.9C  B@B@x@ADAE cAo]  B@B@x@ADAEZSaL a =&"=$_@@ ; /sC N B@B@x@ADAEӁ a = ^aG   @р@ P# @{ @ou|.ky^W‚P%bA`$+ B@B@x@ADAEK_aL)A =i`=$_@΀@ # @>AD N B@B@x@ADAE/jaLa =="@_@ !A/MC  B@B} m@x@ADAEDkaAa =!fKv`="@B@ +ac&IOYȮ&þʩ7[ A6 B@B+@x@ADAE  a = ^Ha"@?@ !>A  B@B@x@ADAE @Y A!!a =a"@4~ /qC  B@B@x@ADAEL"1a = 4a%@@ # @A,hy MBQ ZsfČm8s A @2 B@Bo@x@ADAEmaL2@a =3`=$_@@ # @>Aﯠ  B@B@x@ADAEjaLAAa =t="@ l@ +! @/PCuk ` B@B@x@ADAE`&aBQa = -`="+@t$@ +B6+O28܎(,+Mg#  B@B+@x@ADAEހR`a = *$_@d!@ ! @>A  B@B@x@ADAE Aaaa ={="@܀@ W/NK  B@B@x@ADAE5bbqa =ޝ`="+@I@+Ae7&C}Njj֒h TW^a& g+@0+ B@B+@x@ADAEOaLAra = ޚ`=$_@:@dA   B@x@ADAELaL a =HV="@M@ /1 W B@B/@x@ADAE aAa = `="+@@&<nΏ"2.\{={/龜1kx ~J $ B@B@x@ADA Eua = a @@ PAn + B@B 5@x@ADAEY a =*ǀ="@@&$/ A B@B$@x@ADAExba = `= +@v@ # @Wr_Qϔ*F[3t!P2 zD`@2 B@BA@x@ADAEJ1aLa = |`= @s@ ' @>D  B@B@x@ADAE..aL a =7="!@^/@ +!/VC" # B@B[@x@ADA$E a% =a~ "&@@& e,9o+~ 7{I2ηv)@ A'5 ( B@B @x@ADA)E aLa* = ] `=$_W+@@ &+ @>, - B@B@x@ADA.EaL a/ =̨=$_0@4@&*/\C1 2 B@B@x@ADA3EZapt a4 = ;a`= 5@X@&8gA=Ǹ$ 'lC}In6LrF@ rt6-7 B@B@x@ADA8EaLAA9 =3^%`=$_:@U@dA;T < B@B@x@ADA=E&aLa> == ?@ @ A/z:@uA B@Bs  *@x@ADABE`ˀAaC = p1a"+D@tɀ@ '!z~[Z)hpFW4"!f%oEȀ AF B@By@x@ADAGEʃ2aL aH =<`= I@cƀ@dAJŠA[K B@B@x@ADALE=aL A!!aM =z="N@ށ@ /"/IAOJP B@B@x@ADAQE5<>~@T"1aR =BI`=$_S@I:@ "YIP| -.M{}@XmD' peAT9$U B@BJ@x@ADAVE2@aW =?T X@97@dAY6A[Z B@B@x@ADA[E) AAa\ =T="]@@&$/x^_ B@B@x@ADA`E UbBQaa =``="+b@@ uo ~]:֫{P[O< R-YJ.CZKDc Ad B@B"Y@x@ADAeEueaaLR`af =k`= g@@dAhn Ai B@BA@x@ADAjEYblaL aaak =!l= l@c@&/gCm n B@B@x@ADAoEmaqap =$x`="+q@@! iWn6 x#,.Y+^V )-Ar_@1s B@B@x@ADAtEJց rau =!a v@@dAwC x B@B@x@u =Ay @E.Ӂ Eaz =܀="{@bԀ@/!6|ΡA} B@DB@x@ADA~Eba =i`=!I@Ȍ@ A=*a+ƺ_BM KX晌'wA4  B@B@x@ADAEGaLa =a`= @@+ @+=7 + B@B@x@ADAEDaL Aa =3`= @`@ՙT—̀n˅Ql{@5ZM{L68a& T{C @0 B@B@x@ADAELa =2aI"@`@ @>}A[ B@B@x@ADAEشLAxa =@= @@ @K/WCt  B@B@x@ADAE_pba = w`="@sn@ $ĠJI3J(,Ǽ YB(JEp%]{0a& 4Am  B@B@x@ADAE(aLWa =t`=$_@ck@ @=j  B@BW@x@ADAE%aL a =z/=$_@&@PĠ/CJW B@B:@x@ADAE4 a =a"+@H߀@3RĠlȚ~NsxuD_8WI%`u$W#W7ހ$+ B@B`@x@ADAEaLA =`=$_@9܀@dA۠ A B@B@x@ADAEaL a =W="@@vĠ/LD B@B]}@x@ADAE Raa =X`= +@P@gĠG`rTqGw3!}wv a& AO$ B@B}@x@ADAEt aL-& a =U`= @ M@dAmLA[A B@B@x@ADAEXaL !!a =$="@@  Ġ/oC  B@BA@x@ADAE A"1a = "@@ lٶ-?cY4^,!56@ VA_ B@B@x@ADAEI{aL2@a =`="@㽀@dACA[ B@B@x@ADAE-xaL) AAa == @]y@&/C A B@B@x@ADAE3aBQa =i:`="@1@ :.v㿄/=sELҠ + a& 4 bNJ? B@B:@x@ADAE R`a =\7' A@.@dA A B@BA@x@ADAE @Y) aaa =="@3@ 4A/D  B@B@x@ADAE(bbqa =53`="@@&$6=ދV}ÜZQB|X!$-5#C(  B@Btm@x@ADAE\4aLra =1>`= @@dA힠A[ B@B@x@ADAEY?aL a =c="A@[@ /*CtZ B@B@x@ADAE^@aa = K`= @r@ AG{9zGmW:ru lnڑ A- B@B@x@ADAÈa = Va @f@dAA[ B@B}@x@ADAEʀ a =uԀ="@ˀ@ /eI B@B@x@ADA  E4WbAa =b`="@H@ +4wI:zHBk\ʵ*w5jM@ oXZ$ B@DB@x` => @E>caL$a = ?=܉m`="@8@dA B@B@x@ADA E;naL) a =KE= @<@&/^  B@B@x@ADAE a =ya"@@&xiPV):&i<im,̢q7a& IA  B@B@x@ADAEszaLa =`= A@@dAq  B@B@x@ADAEWaL a = ="@@&/yu  B@B@x@ADAEgaa = n`=" @e@&RQ ~G"f=YѐvFߨD!^ " B@Bx@x@ADA#EI aLA$ =k`= %@b@ * @ >J&B ' B@B@x@ADA(E-aL a) =&=+ A*@]@ /AC+ , B@B7@x@ADA-E؁ p-&a. =lߨa /@ր@&A?9mȋ ƒ`Gha& `A07-1 B@B@x@ADA2EaL a3 =\ܳ`=$_ 4@Ӏ@ # @>J5A[6 B@B@x@ADA7EaL!!a8 =җ="9@2@ !#V/: ; B@B#@x@ADA<EIaA"1a= =5P@">@G@&Ons,ρpAzG/gO(@ iA? @ B@Bm@x@ADAAEaL2@aB =1M`="C@D@dDCA[E B@B@x@ADAFE) AAAaG =$_+H@@&/iAIs J B@B@x@ADAKE^ BQaL =  a"M@r@&k ;kź3HJ IAN޷ O B@Bm@x@ADAPEraLR`aQ =`="R@b@dAS´ T B@B@x@ADAUEoaLaaaV =qy="W@p@$Q`%c A/ @GXH `+Y B@B?@x@ADAZE3+abqa[ =1`="\@G)@&i WDZW85lrh¯:+a& J]( ^ B@B@x@ADA_E〈ra` =. a@7&@ Pb% c B@B@x@ADAdE Aae =N=+$_Af@@&/5Jg h B@B@x@ADAiEbaj =."k@@&} QW@ g<9,mLP*XYa& [F+l$+m B@B@x@ADAnEsTaLAao =`= p@@ 0 @= qlA[r B@B@x@ADAsEWQaL at =#[="u@R@ !$҃.1wCv w B@B@x@ADAxE aAay =`="z@ @ +''# az~bW^\;k^ʅ}5< A״{3;XA{^| B@B+@x@ADA}EHŁ a~ =)a"@@ !>A  B@BA@x@ADAE,  a =ˀ="A@\À@&/RtC  B@B@x@ADAE}*ba =c5`="@{@&5cg_w,&]u'ia& ^A3+ B@B@x@ADAE66aLa =[@`=$_@x@ ' @=A  B@B V@x@ADAE3AaL 4) a =<="@54@&/C + B@BW@x@ADAE a =4La"@@ '&lAos#e&IMTqc%=N\[>OHa& "Y  B@B@x@ADAEMaL9A =0W`=$_@@ ! @=耂  B@BA@x@ADAEףXaL a ==+"@@li/s(s B@B@x@ADAE]_Yaa =fd`= @q]@ @Ġ/EDa+s腳\hVK*#4a& _F+\@0W B@B@x@ADAEeaLA a =co`=$_@aZ@ PAYA[ B@B@x@ADAEpaL?!!a =t="@@ !/CLA[ B@B7@x@ADAE2Ё "1a = {a"@F΀@&A Δ!(1D0M b#À  B@Bg@x@ADAE|aL2@a =ӆ`="@6ˀ@ PAʠA[ B@B@x@ADAEaL AAAa =J=$_+@@ /CA B@BW@x@ADAEAaBQa =G`="@?@ &#! A/w%/W-y0 =R7>$ B@BJ@x@ADAErR`a =Da A@ <@ `@>l;  B@B@x@ADAEV) aaa =#a"@~&/ C  B@B@x@ADAEݱLbqa ='@@ +hj22otU6Tqw'6`z k PccMF+] A B@B+@x@ADAEHjaLA  gxra =`= @嬀@ ! @>E  B@BA@x@ADAE,gaLAxa =@p=+'ua @3@\h@&/C  B@B@x@ADAE"aa =AVg)`= @ @ v# @҃gس;WߣEX uXUp7@ d2- B@BA@x@ADAEہ $a =@逃^&$_@@ PA B@B@x@ADAE؁  a =="@1ـ@ W!/d  B@B@x@ADAEba =?`="@@&Ep-~C ѣ4M~~ӂ!l0na& 6F+$ B@BE@x@ADAEKaLAa =0`= @@ &+ @+=덠 C B@BA@x@ADAEHaL$a =R=$_+@ J@ /-CvI B@B@x@ADAE]ao a  `="@u@ +#!A48S- !l<vHBM&Oůy ͽA$ B@B{ XGa`x@9 A @EǼa = $_@e`@ # @>  B@DB@x@ADA ELa =À=" @ߺ@ W!A/;C K + B@BW@x@ADAE2uba = { #@Js@ 7ɆR:<F1/ÜY Ar  B@B @x@ADAE- aL zA { =x`=$_@6p@ ! @>Ao  B@BA@x@ADAE*aL A! = Q4="@+@ /A%k & B@B@x@ADA'EV+aL !!A( ==")@@% /d* + B@BpW@x@ADA,EV,a"1A- =]7`=".@T@ĠrxT75chY' t]n7a& "Y/\-0 B@B@x@ADA1EG8aL2@A2 =ZB`=$_3@Q@ PA4@A[*5 B@B@x@ADA6E+ CaLAA)A7 = = 8@[ @ &+ @+J/(9 : B@B@x@ADA;Eǁ AB!!< =fNa"=@ŀ@ #l0?bsk_XH2? B@B@x@ADA@EOaLR`AA =ZY`= B@€@ # @"`(=$C D B@B@x@ADAEE}ZaL) Aaa BF = 5ц="G@0~@&/AH I B@B@x@ADAJE8[abqAK =3?f`="L@6@ c;]l~3^H.%oa& AM mN B@BtA@x@ADAOE r45#P =/R2 S B@B@x@ADATE퀈!s!U =='uV@@ &+ @+A/[CWqX B@Bu@x@ADAYE\r!cAZ = }`="[@p@ #A',;\}MraV9hc:SvK V\ܦ ] B@B@x@ADA^Ea~aLA_ =`=+$_`@d@ # @=Aa b B@B =Q@x@ADAcE^aL AAd =sh="e@_@ ! @Ġ/DfGg B@B@x@ADAhE1aAi = `="j@E@&WZ$={$J~p*j- m052a& k$+l B@B+@x@ADAmEҀAAn =a+$_o@5@ &+ @>ApA[q B@B@x@ADArEπ As = Iـ=$_t@Ѐ@ />uv B@B@x@ADAwEbAx =`="y@@ +#!'xIX9]E Nӈ0gEuR/MYa& ͤF+z${ B@B@x@ADA|EqCaLA} =`=$_~@ @ # @>Ak A B@B@x@ADAEU@aL A =J="@A@&/;C +W B@B@x@ADAE A =a"@`@ P:`E"!rGxqf$[a& A\@0 B@BP@x@ADAEFL A =aI"@@ !>A@  B@B@x@ADAE*aL  A = ﺀ='u+@Z@ &+ @+/r4C A B@B@x@ADAElaa =^s`="@j@ +#ATd`D+ry0  1  B@B@x@ADAE%aLg a =Yp`=+$_@g@ @=  B@B@x@ADAE"aL !!a = 5+="@0#@ 6h/!   B@B @x@ADAE݁ "1a =;a"@ۀ@ PX`>_G%Wz {KI~+8jF+- B@BP@x@ADAEaLA2@a =/`=+$_@؀@ ! @>AנA[ B@B@x@ADAEՒaLAAa == @@  `@A/$FCq B@B-@x@ADAE\NaABQa = U @"@pL@ +#!A!sE^,^;sY @ xK$ B@BA5@x@ADAE aLR`a =R`=$_@`I@ # @>AHA[ B@B@x@ADAEaL Aaaa = w ="@@%J/P~F B@B*@x@ADAE1 bqa =!a"@E@&|Z7]>Xd&)fΫbN% ݴ 8Jwy`F+A[ B@Bm@x@ADAEw"aLra =,`="@5@ &+>A A B@B@x@ADAEt-aLa =69`='u+@.@A:Mv{_S[Ŝ5?LrO|a& ҧC-  B@BA@x@ADAEq耈a =3Da"@+@ Pj*  B@B}@x@ADAEU a =%= @@.$C  B@B@x@ADAE۠Eba =P`="@@AĠ&H9B`]Vt~ ̿9Va& #[  B@B%o@x@ADAEFYQaLa =[`=+ @ߛ@ P? A B@BW@x@ADAE*V\aL) a =_="@ZW@ [!$/Q#  B@B@x@ADAE]aa =]h`=!I@@%Q5U$KmYJbV^va2I NF+0  B@B:@x@ADAEʁ :a =Ys @ @ PAA[ B@B@x@ADAEƁ a =Ѐ="@/Ȁ@ /{ǀ B@B}@x@ADAEtba =.`="@@ #$+Aŵ}̤3*RnF nAa& 3D@0 B@B@x@ADAE:aLA' =2`=+ @}@ # @>| A B@B@x@ADAE7aL) Aa =A="@9@ @/ŎCp8!&  B@B@x@ADAE[a = a" @o@$mK絊c<>n!R"#a& _A   B@Bx   @'@x@ADA EƫaL a =`=+$_@_@ &+ @>:퀂  B@AbBA@x@ADAEaL !!a =v=$_@ک@PĠ/CF"dg B@B@x@ADAE0da"1a =j`="@Db@ #!Ġ LS]ﱥҫD=\vpk02a& Moma$+ B@B@x@ADAEaL2@a =g`=$_@4_@ PA^  B@B@x@ADA!EaL i AxAAa" =@G#="#@@vĠ/u$% B@B@x@ADA&EՁ BQa' =aG@e]@g (@Ӏ@&Ag%sd7qQq0^!BA ZF+)Ҁ$* B@B+@x@ADA+EpaLR`a, =`= -@ Ѐ@ &+ @+= .iϠA[IN/ B@B@x@ADA0ETaL aaa1 =!="2@@ &+/}vC3A4 B@B}@x@ADA5EEaAbqa6 =L`="7@C@ #A_>سf!"@yPl/s fra& A8[$9 B@B@x@ADA:EE ra; =Ia"<@@@ #>=? > B@B}@x@ADA?E) @Y) a@ =a"A@Y~ }!/SCB zC B@Bg@x@ADADELaE =]a!IF@Ĵ@&MjklW\DlmUdvʛ8qR,TAG0 H B@B@x@ADAIEoaLaJ =X`=$_K@@ &+ @JAL M B@BA@x@ADANEk@TaO =u= P@/m@&/bxCQl R B@B@x@ADASE'aaT =9. `="U@%@ +#!҃\ijZiHw!>ZYa& "AV$W B@B+@x@ADAXE߁ aY =-+$_Z@"@ ' @>A[! \ B@B@x@ADA]E Aa^ =="_@ހ@& /ԄC`p݀ a B@B@x@ADAbEZbac =#`="Ad@n@&f~z2`'!N6z%? Feڕ-f B@B@x@ADAgEP$aLah =.`=$_i@_@ &+ @>jA[Y ` k B@B@x@ADAlEM/aL am =yW="n@N@ &+/sWDoEp B@B@x@ADAqE0 0aAar =;`="s@D@&1xq(rz47W~(_Lk,} yAt$u B@B@x@ADAvEAw = Fa"x@7@ #>Ay z B@B@x@ADA{E~) a| =NȀ="}@@&/r~ B@B|}@x@ADAEzGba =R`=%@x@%J[2q<|(+ >+}Dw@4g@` + B@B@x@ADAEo2SaL a =}]`=$_@ u@ PAit  B@B$@x@ADAES/^aL !!a =9="@0@ &+#VA/QC  B@Bq@x@ADAE "1a =ia"@@ +#'OC'  B@B@x@ADAE)uaL AAa =="+@Y@&/aC  B@BA@x@ADAE[vaBQa =`b`= @Y@ ]|t{կVR.(`8$*  b _YA  !G/-+ B@B@x@ADAEaLAR`a =X_`=$_@V@ ! @> j@ B@B@x@ADAEaLaaa =="@.@ WA/uC B@B@x@ADAÈAbqa =9Әa"@ʀ@ #$Q $Ø ??$Bzڠz W%oA$ B@B+@x@ADAEaLra =-У`="@ǀ@ #>Aƀ  B@B@x@ADAEӁaL) Aa =="@@&/Co f5 B@B@x@ADAEZ=aa = D`=$_@n;@ vp_.O]Y+t6kO˥a& *A:  B@B@x@ADAEa A$_@^8@ @+=7A[c @ B@DB M@x@ADAE a =u="@@&/ܱCD B@BJ@x@ADAE/ba =۴`="@C@& UeS jKXC=gylh+na& A  B@B@x@ADAEfaLa =ױ`=$_@3@dA  B@B@x@ADAE~caL a =Fm= @d@ W* @+$/nC+ B@B@x@ADAEaa =%`="A@@&.[?q? ]sM?g)mh-8gDd S jA$+ B@Bg@x@ADAEo׀*a =" @ @ * @>Ah A B@B@x@ADAESԀ6ha =a"@@ } &rSq~oqYx& yL@?C^$ B@B@x@ADAEDHaLZ: = "@ኀ@ ! @>=@ B@B6h@x@ADAE(EaL a =N="@XF@+ |11C  B@B@x@ADAEaWa =! ``=)@ `@ # @Ġr qKsL˱!q #A/A B@B@x@ADAEL a = WaI$_@`@ P W B@B@x@ADAEL) !!a =ʿ="@-@ !/XC B@B@x@ADAEqb"1a =9x%`="@o@ }8d"ɥѼK\\Ȕ\T5,_a& q A  B@B}@x@ADAE)&aL2@a =,u0`= @l@ ! @+= k  B@BW@x@ADA E&1aL AAa =0=$_+@(@PA/!Co' j B@BP@x@ADAEY BQa =E4xaa? =;`="+@@2@ '!Ġ1`dFi{V-A9 S_f|e^gAY AB B@BA@x@ADACED aD =8 E@/@dAF= AG B@BA@x@ADAHE(  aI =="J@X@+/gK L B@B@x@ADAMEbaN =[`="+O@£@&vj|{ 5̗%8I64 GJP.-Q B@B+@x@ADARE^aLaS =V`= T@@ @+>}UA[AV B@B@x@ADAWEZaL aX =d="Y@-\@ W/CZ[[ B@Bt @x@ADA\Eaa] =4`=%W^@@ PA5ڹ53.yhqDڼG,zeCQsa& ۘA_$` B@BP@x@ADAaE΁ Ab =0a$_c@@dAdA[e B@B@x@ADAfEˀ ag =Հ="h@̀@ /-Ciǹ"aWj B@B}@x@ADAkEYbal = `= m@m@&Ce$߇mZ T5T +Ka& L%Anل$o B@B@x@ADApE?aL aq =`= r@]@dAsA[c @t B@B@x@ADAuE<  B@B@x@ADAE'aLa =="@W@ A/A  B@BA@x@ADAEJaAa =^Q'`="@H@ xiz 5UW"=:Oia& ̫A. B@B@x@ADAE(aLa =VN2`="@E@>A[ B@B@x@ADAE ) Aa = 3 @,@&/:C B@B@x@ADAEa =0>a"@@%&U|kO:&GCJ:Baa& \ m B@Bu@x 9ADA @Es?aLa =+I`="@@dA絀  B@DB@x@ADAEpJaL a =z="@r@ A/qc'mq"f5 B@B@x@ADAEX,Kaa =3V`="@l*@ +A.k373fE),Fβ~a& F+)  B@B+@x@ADAE䀈 a =0a)m@a'@dA&A[c @ B@B@x@ADAE a = p=+ A@@ / CC  B@Bt@x@ADAE-bba =m`="@A@ &ְE+hZ0I񀞣xyha& A$+ B@B@x@ADAEUnaLA =ڠx`= @5@dAA[ B@B@x@ADAE|RyaL a =P\="@S@ /uC+ B@B*@x@ADAEzaa =`="@ @&(еf ۝6 vwZA $ B@B@x@ADAEmƀ a =a"@ @dAgA[ B@B@x@ADAEQÀ) !!a =̀= A@Ā@ W/jC $-a B@BW@x@ADAE~b"1a =`="@|@&3R:awfTDJ(~vx_@a& X  B@B+@x@ADAEB7aL2@a =`= A@y@dA<  B@B@x@ADAE&4aLAAa = =="@V5@&/]D  `BW@x@ADAE BQa =aa"@@ e@^c=a?!4=}VA] )j!v"` 2d-  B@B+@x@ADAE``a =U`= @@dA A[  B@B@x@ADA EaLqaqa =3g`= A @^@A ڹi'ٔ["Je?1D' 6DØ B@B}@x@ADAEaLra =+d`="@[@dZA[ B@B@x@ADAEaL Wa =="@@  +1/Cm B@BW@x@ADAEXрa =a"@lπ@ Ġ0f~ =`= ?@@ ! @`> @f A B@BA@x@ADABEQhaL6haC =!r=+ AD@i@ W/?CE F B@BW@x@ADAGE#a}aH =*)`= I@!@$A*H}u!{ !JW$+K B@B$@x@ADALEB܁ AAM ='4a$_N@@ # @>O;A[P B@B}@x@ADAQE&ف  AaR =="S@Vڀ@ }!#V҃/ICT AU B@BP@x@ADAVE5bAaW =d@`="X@@ 'tI$*[ w*wȈ)AY, +Z B@B@x@ADA[EMAaL} a\ =UK`="]@@ !>A^ _ B@BA@x@ADA`EILaLA!!aa =S="+b@/K@ /ߎCcJd B@B@x@ADAeEMa"1af =6 X`="g@@%$G\=R|ҷ?:3߶ c#& =.Ah$i B@B@x@ADAjE콁 2@ak =* ca$_l@@ # @=DKmb` `au iFn B@B@x@ADAoEк @Y AAAap =Ā="q@@A/Crls B@BP@x@ADAtEWvdbBQau =!A}o`="v@kt@&**ۢqƔXElܟg~ iAws Ax B@B"@x@ADAyE.paLR`az =yz`=$_{@[q@ PA|p } B@BA@x@ADA~E+{aL aaa =5=+ A@,@ &+ @+Ġ/!ECB B@ B@x@ADAE, l + bqa = a!I@@@ P# @@^| !O[1?"֒Psg ?䀂$+ B@B@x@ADAEaLAra = `= @4@ # @>Aဂ  B@B@x@ADAE{aL a =G="@@&$/XrD B@Bg@x@ADAEXaa =^`="@V@&Ibhyz$Yrnd_&vK-a& 3AU$ B@B@x@ =A @ElaLa =[`=$_@S@ &+ @>eRAY B@DB@x@ADAEP aL a == @@ &+ @A/C W B@B@x@ADAEȁ a = ϵ #@ƀ@&Z@kbZ ?[_"àD*YS AW B@B@x@ADAEAaLa =`=$_@À@ # @=;  B@B@x@ADAE%~aLa =="@Y@&/&Cš + B@B@x@ADAE9aCa =h@`="@7@ '+Q&5h=Z1[~1S|a& A0  B@B@x@ADAE a =X=$_@4@ ! @>A  B@BA@x@ADAE a ==+"@/@ &+ @A/ -C  B@B@x@ADAEba =.`="@@ZЕ)%`ő5c79[|FA$ B@B[@x@ADAEbaL$A =-`=$_@@ # @=A夀  B@B@x@ADAE_aL A! =i="@a@ v! @$/Cl`A[m B@BB@x@ADAEVa=! =!A"`="@j@&$ƶ $ƒ]@kSjT` ; A$ B@B@x@ADAEӀ$ 6! bLG@Qd_@Z@ PA  B@BA@x@ADAEЀaQL !!A =rڀ=$_@р@ /CA" B@B@x@ADAE,`"1A =ܒ`="@@@ #!+A=trC=dNK2YCa& A`Ȣ B@B@x@ADAED`2@A = ԏ`=)@0@ # @>A` B@B@x@ADAEzA`) AA&+ =GK="@B@&.U"C B@By m@x@ADAE B"! =+a"@*`@ -&w íL?Xa+=Xߑ`! $A`Ge#*;` XF B@B@x@ADAEkLR ! =6`"@5`@ !>e` B@B@x@ADAEOL aaA =$='u+@@ &+ @#`C /@G  B@B@x@ADAEm7bbqA =tB`="@k@A5} M>q>p'Hi AV` B@Bm@x@ADA, @EA&C`rA =~qM`=+$_@h@ # @=:` B@DB@x` =A @E%#N`` A =,="@U$@&/+C  B@DB@x@ADA Eށ A =[Ya" @܀@ '!_)wx65Z%sק;K[a& nyA +- B@B@x@ADAEZaLA1 Td`=+$_@ـ@ ! @=  B@B@x@ADAEeaL`A =ǝ="@*@ &+ @A/UC B@B@x@ADAEOf`AA ==Vq`="@M@ #ojn ~Ga& ?`Ȣ B@Bm@x@ADAEr`A =)S|`=$_ @J@ # @=!I`" B@B@x@ADA#E}` AoA$ =Lj`="%@j@ !^1r5uA9"=Ϭ@ mD&ֽ`' B@B@x@ADA(Ex`A) = Ó`="*@Z@ $>+`, B@B@x@ADA-Eu`?AxA. =@q="/@v@ ! @^/C0@+1 B@B@x@ADA2E+1aA3 = 7`="4@?/@ R3O8I7x? #qfg-skC %[5.`6 B@B@x@ADA7E逈 A8 =4`+$_9@3,@ ! @>:+`; B@B}@x@ADA<Ez YA= =F=$_>@@ /#D? @ B@B@x@ADAAE`aB = 5`="C@@$'# Z],ib'+Wc6Vf/C+AD$+E B@B$@x@ADAFEkZaL aG =`=+$_H@@ # @>AId J B@B@x@ADAKEOWaL`) !!aL =$a= M@X@ ! @/CN AO B@B@x@ADAPEaA"1aQ =`="R@@ +'i!} dmd4 K;YxzqASV`ȢT B@B3R@x@ADAUE@ˁ 2@aV = ~`$_W@ @ ! @>AX9 AY B@B@x@ADAZE$ȁ aQ AAa[ =р="\@Tɀ@ W /_C] ^ B@BA@x@ADA_EaBQa` =[`="a@@&ʭ+io]7E|hRtc Ab+`Ȣc B@B@x@ADAdE<`R`ae =W`="f@~@ #>Ag`h B@B $M@x@ADAiE8`) aaaj =B='u+k@):@&/&Cl9m B@B@x@ADAnEbqao = 1a"p@@ '!a;u?+$W*[f," 1 ;J? RAq`mr B@B@x@ADAsE`rat = {(bL+$_u@@ ! @=:vaw B@BA@x@ADAxEϩ aL ay =="z@@ &+ @A/Z~C{k| B@B@x@ADA}EUe aa~ = l`="@ic@ Ġ?#c#tۉW8XDwO`( a& +tAb` B@B @x@ADAE`a = h `=$_@Y`@ # @=_  B@B@x@ADAE!` a =t$=$_@@&/XC@ B@B@x@ADAE*ց a = ,a"A@>Ԁ@&? BẢjq@ۦdTeHz= ݞAӀ` B@B@x@ADAE-`a =7`=$_@/р@ PAРA[ B@B@x@ADAEy8` a =I="@@ &+$+$/+C B@B@x@ADAEG9aa = 5MD`="@E@ S#A S`wG۴y~@Ӣ" L ?AD$ B@B@x@ADAEja =JOa"@B@ #$=AdA  B@B@x@ADAENaQ a =P`8"+@~~&/ɊC "#a W B@B@x@ADAEշLa =~['@鵀@ ;`1Ţ a6bq V`<*@a& "U  B@B@x@ADAE@p\aL" = }f`=+$_@ٲ@ @>9  B@BA@x@ADAE$mgaL a =v= @Tn@YA/N  B@B6h@x@ADAE(haa = j/s`="@&@%A"d5 E ~6e8 ͞* B@B :  @'@x@ADAE  a =R,~$_@#@ PA  B@AbB@x@ADAE݁  !!a =="@)߀@ !+/iIAހ B@B @x@ADAEb"1a =,`="@@<$4,U#HYiGFձ\c Zih a& `j@ B@B+@x@ADAEQ`2@a =(`= @@ * @JA㓠A[ B@B #+@x@ADAEN` AAa =X="@O@ /FEj!7CZW B@B@x@ADAEU akHBQa = `="@i@ #+>pucA3hͭ@xEE^kyRa& ,F+$ B@Bm@x@ADAE€R`a =@"@\@ #>  B@B@x@ADAEaQc aaa =sɀ="@@&/ C? B@B@x@ADAE*{`bqa =ׁ`=$_W@>y@ +-68z,m zԔ0\-a& x + B@B+@x@ADAE3aLra =~`=$_@.v@ ! @JAu  B@B mA@x@ADAEx0aL`a =A:=#= @1@ &+#V/k B@BiS@x@ADAE a =`"@@AX 7WݺC5wqlƩUa& yF+适  B@B$@x@ADAEjaLa =`=$_@@ # @=c怂  B@B@x@ADAENaL a>7="@~@%/C A B@B`x@9 A @E\aa =}c`= @Z@ ' @6UHI̓t09#wV; ʞ` uAT@0- B@DB@x@ADA E?`a =``=$_ @W@ @= 8A[ B@B@x@ADAE#`a ==H@A@S@ &+K/(C  B@B@x@ADAÉ Aa =ja"@ˀ@ +b}¨P1?uˣ|@L u/A* B@B@x@ADAE@Ta =R `="@Ȁ@K=  B@B@x@ADAE aLbܤb Aa =Ԍ="@(@BȠ~’/ W! B@B m@x@ADA"E> aa# =0E`=%$@<@ uO)%So6HYWCtH{a& D%; & B@B@x@ADA'E( ='B"$_)@9@ ! @JA*8 + B@B@x@ADA,E a- ==".@@&$/C/iA0 B@B@x@ADA1ET#ba2 = .`="3@h@ #$g XOrq,ӡÔ\cA9NKa& 4A4Ԭ 5 B@B@x@ADA6Eg/aL a7 =9`=$_8@X@ ' @>A9 : B@B@x@ADA;Ed:aL !!a< =sn="=@e@ ! @/qC>?? B@B@x@ADA@E) ;a"1aA =&F`="AB@=@&"_ q?׿ԥFb Al C^͞DKC$+D B@B@x@ADAEE؀A2@aF =#Q$_G@.@ &+ @>HA[I B@B@xG 9ADAJ @ExՀ; AQaK =]a"L@@ W Ƕ:{psM0lPhM,!T|a(DM$N B@DB@x@ADAOEiI^aLR`aP =h`="Q@@ ! @>RbA[S B@B6h@x@ADATEMFiaL aaaU =P="V@}G@ $Ƕ/zW X B@B@x@ADAYEjaWbqaZ =u`=!I[@t`@&uK+{wF4˝&-7r(A˫| \D\T] B@B@x@ADA^E>Lra_ =|aI `@`@ # @>a8 b B@B@x@ADAcE"L) ad =="e@R@&/JpCf g B@B}@x@ADAhErbai =Vy`="j@p@ '$AeLl`7H\V|&~)ngb>Ak) l B@B@x@ADAmE+aLan =Qv`=$_+o@m@ ! @=:p q B@BW@x@ADArE'aL as =1="t@()@ &+ @A/HCu(v B@B@x@ADAwE~〈ax = /a!Iy@@ 1# @~[aӗ^4[[sքf 2/uAz-{ B@B:@x@ADA|E雤aLa} ='`=$_~@ހ@ # @=݀  B@B@x@ADAE͘aL a =="@@ ! @/YCiow B@B>@x@ADAESTaa =[`="+@gR@ !IH:ZOs*mj Kׁa& AQ  B@B`@x@ADAE aLa =W`=$_@WO@dANA[c @ B@B@x@ADAE aL a =n="@ @ /iC> B@B@x@ADAE)Ł q a =a%@AÀ@ }0ċ8 ⪋51֜/`Ra& A€$ B@B@x@ADAE}aLA =`= @-@dAA[ B@B@x@ADAEwzaL) a =D="@{@&/oC B@B@x@ADAE5aa =<`="@4@%Ż,"u5m{l_഍CA~3 + B@B@x@ADAEi*(a =9 @1@dAb0 A B@B 4#@x@ADAEM !!a =)="@}@/oC  B@B @x@ADAEӦbk ƴ@s"1a =@ A@礀@ } `@mJ5Dݡ*N6Be@ \S$+ B@B}@x@ADAE>_aL2@a ={ `= @ء@dA7A[A B@B@x@ADAE"\ aLAxAAa =@e="@R]@ /6D  B@B{ +`@x@ADAEaBQa =Y`= @@ `@ =e~[0']N2/MA($ B@B@x@ADAEЁ R`a =U$a @@dA A[ B@B@x@ADAÉ aaa =ր="@'΀@ /C̀ B@B@x@ADAE~%bAbqa =.0`= @@&<`ۃp*S:~d1$˧-)0a& <A$ B@B@x@ADAE@1aLra =&;`= @@dA₠A[ B@B@x@ADAE= + B@B@x@ADAESa =!AHa"+@gG`@ A;NIi΂Z#*U *d B V A B@B@x@ADAE!aL) a = = @Q@&/5C A B@B@x@ADAE a =Xåa"+@@&$RڡZ-0n"Y .mIa& sA#(  B@B@x@ADA!EuaL a" =P`= #@@dA$ % B@B@x@ADA&EqaL!!a' ={="(@&s@ A/_C)rm* B@B@x@ADA+E}-a"1a, =94`="-@+@ 3AXmc`XTs:[3/?A ~ L %A.* / B@Bc@x@ADA0E倈2@a1 =%1 2@(@dA3'A[ 4 B@B@x@ADA5E AAAa6 ==+ A7@@^ /UD8h 9 B@B@x@ADA:ERbBQa; = `="<@f@ +4TΆDcLeͧ< f|u %D=қ$+> B@B+@x@ADA?EVaLAR`a@ =`= A@Z@dAB C B@B@x@ADADESaL aaaE =m]="F@T@ A/ӖCG=#aH B@BW@x@ADAIE(aAbqaJ =`="K@< @&'hb{Aˌ1smĦv/sa& ?AL $M B@B{+@x@J =AN @EǀraO =a"P@, @dAQ $R B@DBA@x@ADASEvĀ aT =C΀= AU@ŀ@&/ɣCVW B@B@x@ADAXEbaY =h"Z@~@ PADvV^]w4D{oV3\a& A[}}$\ B@Bt@x@ADA]Eg8aLa^ =`=G@f@ A_@{@dA`az a B@B@x@ADAbEK5aL6hac =`= d@@$6h]Oi=Y>Łrְ鞹MCeR `au jf B@B$@x@ADAgE=aLah =z%`= Wi@@dj6A[k B@B@x@ADAlE!&aL am =="n@Q@/@aCo dp B@B@x@ADAqEa'aar =Th2`= s@_@  Ġ2_@kj{U !$4^v*At' u B@B @x@ADAvE3aL/aw =e=`= x@\@dyA[z B@B@x@ADA{E>aLa| = ="}@&@  Ġ/[C~ B@B@x@ADAE}ҀWa =,Ia"@Ѐ@ĠOo$IcŬw^ 9 @dA=  B@B@x@ADAE) !!a = yma"@~A/ZPC< B@B+@x@ADAE'LA"1a =Ϻx'@;@ ;ݷͩɶBVmZ KY5,;<A A B@B[p@x@ADAElyaL2@a =`= @,@dA  B@BA@x@ADAEviaL AAa =:s=+ A@j@ /tCA[ B@Bo@x@ADAE$aBQa =+`= @#@&$6wȋﶺ̨7qn4[ )0@Ra& xA|"$+ B@B@x@ADAEg݀R`a =( @ @ +# @*=?` A B@B}@x@ADAEK aaa =  ="@{ۀ@&$/C A B@B$@x@ADAEѕbl1bqa =`="@哀@ AeۙNF>v]J_Ea& Dt m B@B@x@ADAE/aLa ={`= @r@ &+ @+=Aq  B@B@x@ADAE,aL a =6=+$_+@-@ /7Cg  B@BsA@x@ADAEQ a =a @e@ @A"Q>2R!xZa& d倂$ B@Bq@x@ADAEaLa =`=$_@U@ # @>   B@B@x@ADAEaL`Aa = h="@Ԟ@&$/lj@  B@B:@x@ADAE&Yaa = _8,"@:W@&} J™ ]'! nìcQZy&O F+V- B@Bx@x@ADAEaLA =\`=$_@*T@ PA$S  B@B@x@ADAEuaL Aa =B= @@  `@+g/!C B@B@x@ADAEɁ Aa =a" @Ȁ@ (#!A6onF&jvF;y$cTsyw$ |ǀ$ B@B@x@ADA EfaL a ='`= @ŀ@ # @>A`Ā  B@B@x@ADAEJ(aL`A!!a = ="@~@&/w$ +c  B@B@x@ADAE:)a"1a =A4`="@8@&1IZB`żizϕ X[4բa& F+Q  B@B@x@ADAE; 2@a =y>?a"@5@ &+>5  B@B@x@ADA $`E @AAa! =='u"@O@&/)C# A$ B@DB@x@ADA%E@bBQa& =OK`="'@@!Ab^!g=޴9{bX#1AAa& (& ) B@B@x@ADA*EdLaLR`a+ =NV`=$_,@@ PA- . B@B@x@ADA/E`WaL aaa0 =j="1@%b@&/%dC2aA[3 B@B@x@ADA4E{Xabqa5 =##c`="6@@ '! 1JD]&PEFj60jHa"ha& 7A7@08 B@B@x@ADA9EԀgra: =( na ;@@ @=<A[= B@B@x@: =A> @Eр@ a? =ۀ="@@Ҁ@ &+ @/Af$B B@DB@x@ADACEQo AaD =z`="E@e@ PR2#*=3@*A4S'O*~Lk@ DFъ$G B@BP@x@ADAHEE{aLaI =`=$_J@U@ @=KA[L B@B@x@ADAMEBaL aN =gL="O@C@ }/WCP;WQ B@B}@x@ADARE& aS =a"T@:`@&% RJd=\К OYhR)a& &:AU$V B@Bm@x@ADAWELaX =aI"Y@-`@ &+>AZ [ B@B@x@ADA\EtLa) a] =E=)+^@@&/C_ +` B@B@x@ADAaEna ab =u`="c@m@ 4#!ޡ&rNio*Ȃ~ʏa& bAdl e B@BJ@x@ADAfEf'aLA bag =r`=+$_`= @3h@j@ ' @>Aici j B@BA@x@ADAkEJ$aL@Yal =AV.="m@~%@ `! @g/Cn o B@B@x@ADApE߁ paq =a"r@݀@&D]Z7);͈H\ ma& AsT$+t B@B@x@ADAuE;aLA zAv =y`=+$_w@ڀ@ &+ @>Ax4 y B@B m@x@ADAzEaL A A{ = =$_|@O@&/sC} ~ B@B@x@ADAEPaA =RW`="@N@ m#!J&Daí;'o3>6QO<}9HA%@4- B@Bm@x@ADAE aL A =NT`=$_@K@ ' @>A K  B@B@x@ADAEaL; !1A =7`="@@ -&!K?&|oN`2YJ]G=}Dra& J/ B@BC@x@ADAEyaL2@A =#`="@~@ $>߻  B@B}@x@ADAEvaL AA)A =="@w@"YW/F+$eA B@B$ADA @EP2aB%! =9@L"@d0@ WK#@y&R5J$P59FR@ ./  B@DBA@x@ADAEꀈ}`A =5a+$_@T-@ ! @"`C >,  B@B@x@ADAE aaA =o= @@ W&+ @/+9!;  B@ B@x@ADAE%b}qA =ک`="@9@&2S{%j IM)!e],! A$+ B@B} m F@x$aDA @E[aLrA =ͦ)`=$_@)@ # @=  B@DB@x@ADAEtX*aL A =Eb="@Y@&/A B@B@x@ =A @E+aA = ?=6`="@@ '+ @d * &T5V*"5 Dz@3m+ B@B@x@ADAEèA =A$_@@ @>A^A[ B@B@x@ADAEIɁ  A = Ӏ=+"@3{@yʀ@ &+ @A/C  B@B@x@ADAEЄBbA =AVM`="@䂀@ }w/&Gyg&ߞ F!9Ma& *AP B@Bg@x@ADAE:=NaLA = |X`=+$_@@ @=A4  B@B *@x@ADAE:YaL) A =C=$_@N;@ }/ܔC  B@B@x@ADAE #2 =Rda"@@ : н Hdh- jU{a& T% A B@B:@x@ADAEeaLA =Mo`=+$_@@ ! @>A  B@BA@x@ADAEpaLA == @$@$ @^` /D ` @ XF B@B@x@ADAEzfqaA =/m|`="@d@&}Յs4[b{8V/TEAc$ B@B@x@ADAE}aLA&+ ="j`=$_@~a@ # @Q`AB?A`  B@B@x@ADAEaL A A$%="@@&/Ae# B@B@x@ADAEOׁ a =ޓa"@cՀ@ '&l k,ϙ蠎S -~8Ѕa& hAԀ- B@B@x@ADAEaL a =!ڞ`=G u^@TҀ@ !>AѠ  B@B@x@ADAEaL !!a =j=H@h@!"+@΍@ &+ @/DC:%  B@x@ADAE%HaA"1a =N`="@9F@ #!v%Ȯ@nh\.<FJA% AE$ B@B@x@ =A @EaL2@a =K`="@(C@ #> B  B@DB@x@ADA Es) AAa =7a" @~%/ CA[e B@B$@x@ADAELBQa =a$_+@@& jII7☌z`'-Xa& BAz  B@B@x@ADAEdqaLR`a =`=$_@@  `@>A^  B@B@x@ADAEHnaL aaa =x="@xo@%/C  B@B  }@x@ADA%`DE)abqa =|0`="!@'@&$q;F&,IZoicf>RA"O # B@Bu@x@ADA$E: ra% =w- &@$@ PA'3 ( B@B@x@ADA)E߁  a* =="+@N@ W!'+/nC, - B@BW@x@ADA.Eba/ =Y`=!I0@@&$*4Awduҧa.p q]a& wA1$-2 B@B@x@ADA3ESaLCa4 =M`= 5@@ PA6A[W7 B@B@x@ADA8EOaLa9 =Y=":@#Q@&/SC;P< B@B@x@ADA=Ez aAa> =* @"?@ @&ze{1tOHyI_˘@ r@$A B@B@x@ADABEÀaC =&a"D@@ '$=E F B@B@x@ADAGE A[H =ʀ="I@@&/*rJd +WK B@B@x@ADALEO|baM = `= AN@cz@ m' @$k7MtL>@~p[cpdW%+"\M&~ba& 3/Oy P B@BA@x@ADAQE4!aLaR =+`=$_S@Sw@ ! @=Tv U B@B@x@ADAVE1,aL aW =j;="X@2@&/œIAY9AZ B@B@x@ADA[E$ a\ =7a"]@8@ #4G gKLN 9!lea& ^ꀂ _ B@Bj@x@ADA`E8aL"a =B`=$_b@(@ PAc瀂 d B@B@x@ADAeEsCaL af =C="g@@ ! @+/ hi B@B@x@ADAjE]Daak =dO`=!Il@ \@&(h~-?4*1)Àk=6a& F+my[$+n B@B@x@ADAoEdPaLA ap =aZ`= q@X@ &+ @>Ar]A[s B@B@x@ADAtEH[aL !!au =="v@x@ /Cw x B@B@x@ADAyE΁ A"1az =fa"{@̀@&+_wGh;;,pi' Ť~70a& A|N A} B@B@x@ADA~E9gaL2@a =wq`="@ɀ@ #>2  B@B@x@ADAEraLAAa =퍀="@M@&/C A B@B@x@ADAE?saBQa =TF~`= A@=@&.Tߥ5d(6n~wZ{ĉlSa& y$ B@Bwg@x@ADAE R`a =LC$_@:@ `@JA `auiF B@B@x@ADAE @Y) Aaaa =="@"@ }cV/ 2D B@B}@x@ADAEybbqX =%`="@@ #$$w%t!Vj^lh4ݏ>a& {A ` B@B@x@ADAEhaLra =!`= @}@ # @>Aݪ  B@BA@x@ADAEeaL6ha ='`="@b@$6hFLQƹhh_|>X|ڲUJdk INC`4 B@Bx@x@ADAEـa =$a"@S@ $>$ B@B@x@ADAE a =n=%A@׀@ ! @B/bC9 + B@B@x@ADAE#ba =И`="@7@ &+ =6$5 #dT-otxO@a& 'A$ B@BA@x@ADAEJaLa =`=+$_@+@ ! @>A[+ `AW B@Bq @x@ADAErGaL a =:Q="@H@ /уC B@B@x@ADAEaWa = `=$_@ @ [p# @A*I탭o(EDz +}`Ay@4+ B@B@x@ADAEca =$_@`@ # @>A]  B@B@x@ADAEGL) a = €="@w@ +!A/-C  B@B@x@ADAEsba =~z`="+@q@& (s^@~ PD =%M5U#a& BN@0 B@B@x@ADAE9,aL4A =vw`=$_@n@ &+ @>A2  B@B@x@ADAE)aLa =2=$_@M*@DK/*D  B@B@x@ADAE a =\ T"+@@-ABtoKf-%Zǽ;ab [#$ B@B@x@ADAE aL a =K`=`gd_@߀@dA  B@B@x@ADAEaL A!!a =£="@"@&/DA B@B@x@ADAExUa"1a =%\"`= +@S@ m' @Ġ t CX;Mg!#$aR\0Dza& BAR- B@B@x@ADAE #aL2@a =!Y-`=)@|P@dAOA[ B@B@x@ADAE .aL AAa =="@ @ /Cc B@B@x@ADAENƁ ABQa =9a$_@bĀ@ +j?&% \;Pd2xu}a& ʟAÀ$ B@B+@x@ADA&  E~:aLR`a =D`= @R@dAA B@DB@x` =A @E{EaL) aaa =i= @|@ /!\ " B@B@x@ADA#EG]taL'  a$ =@g="%@w^@ -&!#V$/ \C& A' B@B@x@ADA(Euaa) =~`=!I*@@&qI)e{.@lM50K tYYpa& Jw+M$+, B@B@x@ADA-E8с Aa. =za$_/@@ &+ @>01A[1 B@B 4M@x@ADA2E΁ a3 =׀="4@Lπ@ /DD5 6 B@B@x@ADA7EbAa8 =S`= 9@@ P# @$P>U Tz*ODJzka& :#; B@B@x@ADA<E BaLa= =K`=$_>@@dA?A[@ B@B@x@ADAAE>aL AaB =H="C@!@@&/TDD? +WE B@B@x@ADAFExuaG =0a"+H@`@&$>;<n7e08#|VKNa& Z)AIJ B@B-@x@ADAKEⲁLAL = aI M@|@dAN O B@B@x@ADAPEƯaL aQ = ="R@@0<>.CSbT B@B@x@ADAUEMkaAaV =q`= +W@ai@ @ A{On첧tldd_u0F^a&Խa& lAXh +Y B@B$@x@ADAZE#aL a[ =n`= \@Rf@dA]eA[A^ B@B@x@ADA_E aL !!a` =`*="a@!@/r@Cb8 c B@Bo mDK@x@ADAdE"܁ lzL"1ae =a!If@6ڀ@ +s]b(qht&r3 gـ$+h B@B+@x@ADAiEaLA2@aj =`= k@&׀@+ @+=6hl֠A[Am B@B@x@ADAnEqaL AAao =A="p@@ /$q Ar B@BW@x@ADAsELaABQat =S`="u@ K@&=-p`^BN5\->1b]iR a& \vwJ +w B@Bm@x@t =Ax @EbaLR`ay =P@L"z@G@>{[ | B@DB@x@y =A} @EFaL aaa~ = ?= = @v@&/7IA  B@B@x@ADAEͽ bqa =} a"@Ề@&|{4'@b|zc^7a& xM B@B@x@ADAE7v aLra =u`=$_@Ѹ@dA1  B@B@x@ADAEsaL@Y a =|="@Ot@&/V + B@B@x@ADAE.aa =J5$`="@,@&lWtoeEZwE,@f֙u)HpBa& F+"  B@B@x@ADAE a =2/ @)@dAA[+ B@BA@x@ADAE @Y a ==+"+@!@&//C䀂 B@B@x@ADAEw0ba =(;`="@@ +c!x 2w{8"nR1)'! `A$+ B@B+@x@ADAEW} A B@B@x@ADAEaLaaa =ɒ= @ @ \- @A/cC B@Bt @x@ADAEwDaAbqa = +K`="@B@ +#KA/T74vK`BOb A$ B@B+@x@ADAEra =Ha`6h@{?@ #>> A B@B@x@ADAE) Aa'a"@~ !/)a B@B`x@9 A @ELLa ='@`@gĠkW@'Z!9fqVY._V([7` F+̲ A B@DBt@x@ADA EmaLa = `=" @O@ &+>A   B@B@x@ADAEjaL a =_t=$_@k@ /Y3C6A[ B@B:@x@ADAE!&aa =,`="+@5$@ #!1P]g Қ^Xt5y&0a"Zva& A#  B@Bq@x@ =A @Eހa =)@L$_@%!@ # @>A  B@DB@x@ADAEp a =8="@܀@ ! @$/^C  ! B@B@x@ADA"Eba# = `="$@ @ ^M8oOK e΋Ӝ nA%v$+& B@B@x@ADA'EaOaLa( =`=$_)@@ ! @>A*^A[+ B@B@x@ADA,EELaL a- =V=$_.@uM@ /C/ 0 B@B@x@ADA1EaAa2 =x&`="3@@ +#!kẸ@PLB{2;I`6a& A4L5 B@B+@x@ADA6E6 A7 = t 1a$_8@@ # @>A9/ : B@B@x@ADA;E  a< =ƀ="=@J@ DK!A/> A? B@B@x@ADA@Ex2b-&aA =Q=`="B@v@&H6-XĘCgoWe 5xv*t#É?C%D B@B@x@ADAEE 1>aL aF =M|H`="G@s@ &+>AH AI B@B~@x@ADAJE-IaL !!aK =7=$_+L@/@ /kM.A[N B@B@x@ADAOEv逈A"1aP ='Ta"Q@@&  J6uׁ98^>~[vV]eDra& IAR怂 S B@B@x@ADATEUaLA2@aU =_`=$_V@z@ @JAW。 X B@BA@x@ADAYEŞ`aL AAaZ == [@@҃/xC\a] B@B @x@ADA^EKZaaBQa_ =al`="`@_X@ m'!%c# )~?AtK&̼걏ܬAaW$+b B@B-&@x@ADAcEmaLR`ad =!]w`=$_e@OU@ ! @=`fT  `iq g B@B@x@ADAhExaL aaai =k="j@@ A/4Ck6l B@B@x@ADAmE!ˁ Abqan =уa"o@5ɀ@%JZ\Nx|"mia:GQU7Ohaa& hApȀ Aq B@B @x@ADArEaLras =Ύ`=$_t@%ƀ@ # @>uŠ v B@B@x@ADAwEoaL ax =C="y@@ !$A/#DCz ${ B@B@x@ADA|E;aa} =B`="~@ :@&  `Hz5oHX0}ܿAv9$ B@B+@x@ADAE`Aa = ?a"@6@ PAZ A B@B H@x@ADAED 4) a ==$_+@t@ /*C  B@B@x@ADAEˬba = x`="@ߪ@ #!+F?([Q;0P0 #JQ:-9(Z׭ (AK A B@B$@x@ADAE6eaLa =s`=+)@ϧ@ # @>A/  B@BA@x@ADAEbaL a =k="@Jc@ :! @A/.C  B@B@x@ADAEaa = U$`="@@ ms5G#i@(oN=ߕd A - B@B@x@ADAE ց a =H!$_@@ ! @> B@B@x@ADAEҁ f a =܀="@#Ԁ@&$/CӀ  B@BA@x@ADAEuba = "`="@@&An-;u3_ K*<]ZfJʆ sFA$ B@B+@x@ADAEFaLA = `=$_@z@ ' @>Aو  B@B@x@ADAECaL a =M="@D@ !A/jC` B@Bli@x@ADAEK a = a"@_`@ 'Mf'NI-O_b%gr7 {A$ B@B@x@ADAEL a =@"@R`@ !>A  B@B@x@ADAEL?!!a =f="+@͵@&/C9 + B@B@x@ADAE pb"1a = v`="@4n@!s#i@&!İxCnJhSv>7Am  B@BO@x@ADAE(aL2@a = s`=+$_@$k@ `@>Aj  B@BA@x@ADAEn%aL AAAa =3/="@&@ 0AY  B@B@x@ADAED3aL6haqa =X?`="@O@ } GWU'{bnEOGlCJ$ B@Bg@x@AD>E5 @aLra =wUJ`="+@L@ ! @>2  B@B@x@ADAEKaL a ==%@I@Ƕ/C + B@B@x@ADAE a = PVa"@@$Ġ6on',C `RjhhN1p'7]6ڊS IRA  B@B$@x@ADAE {WaLa = Ha`="@@ )A>  B@B}@x@ADAEwbaL) a =="@y@ !$+/~Cx+ B@B@x@ADAEu3caa =!:n`=!I+(@1@ ' @S)8ZKqTzay f c=A0  B@BsW@x@ADAE뀈a =7y$_@y.@ ! @=`-  B@B@x@ADAE耈a ==" @@ /C c$ B@B:@x@ =A @EJzbCa =`="@b@ AvMg^=.? d[` XAΡ  B@DBA@x@ADAE\aLa =`=$_@R@ # @>A  B@B@x@ADAEYaL@Yk Aa =ic='u@Z@ W! @/tC9  B@B@x@ADAEapga =`="A@7@& f# iu) }&ua& A#A$+ B@B@x@ADA!ÈA zA" =$_#@$@ PA$ W% B@B@x@ADA&Enʀ  A' = :Ԁ="(@ˀ@ /C) * B@B@x@ADA+EbkA, =`="-@ @A dv 4{}ɋ 8Js F,A7A.uA[-/ B@B:@x- 9ADA0 @E_>aL A1 =`=+ 2@@ # @=3XA4 B@DBM@x@ADA5EC;aL !!A6 =E="7@s<@A/H~8 9 B@B@x@ADA:E "1A; =va!I<@@& ƹjYݝ9/޽ jgmUjC#=J> B@B@x@ADA?E4̒@T2@A@ =r`=$_A@@ PAB. C B@B@x@ADADEaL) AAAE =ᵀ=H g F@H@&/TGG H B@B@x@ADAIEgaB'?!J =Kn`="K(@e`@ #$ v`DtA=MVܻNU'nAQqߛWo=Z2 5@GL( M B@B @x@ADANE a }`AO =Gk`= P@b@ PAQ R B@B@x@ADASEaL aaAT =&="+U(@@ ! @/CV!]enW B@B@x@ADAXEt؀bqAY =!a!IZ@ր@&{ÒBI%Q;2pݔAB3Lc a& ʭA[Հ-\ B@B@x@ADA]EߐaLrA^ =W _@yӀ@ &+ @>`Ҡ c @a B@B@x@ADAbEÍaL Ac == d@@&/oC!FGp B@B@x@ADAqE Ar =ha"s@~ !A.kCt4u B@B @x@ADAvELAw =)a!Ix@3@&$} uШN:>.-yʃ2a& Ay@0]`8` XFz B@B@x@ADA{Er*aL*!/m| =4`=$_}@#@ &+ @>A~ `6h B@B@x@ADAEmo5aL) A =2y="@p@ /GC  B@B@x@ADAE*6aA =1A`="@)@ 4#$A}8cLe,hs$%{-e*[Ma& #2At( ` B@B 4@x@ADAE_〈A =.L$_@%@ @>AX  B@BA@x@ADAEC  A =="@s@9~/5  B@B@x@ADAEɛMbA =vX`=$_@ݙ@&kܤ}æs0^xGFDmBrAI B@B@x@ADAE4TYaL@)A =qc`=$_@Ζ@ `@>A-  B@B@x@ADAEQdaL OA =Z= @HR@&/"9C  B@B@x@ADAE ealA =Gp`="@ @!A5"Lĥ/Q|BKWkFA @4 B@B@x@ADAE ŀJ a =K{a+ @@ ,W @>AA[ B@B@x@ADAE !!a =ˀ="@À@A/4C€ B@B@x@ADAEt}|bA"1a =!`=!I@{@ +' @@F=d.G!$' } |t* a& -Az$ B@BA@x@ADAE5aL2@F+ =`=$_@xx@ @= wA[ B@B@x@ADAE2t) AAAa =<="@3@ W /^C^ B@BA@x@ADAEI BQa =c"@]@ vm`ZHj*#^R"ڋ֥VԂ a& 9"A뀂  B@B@x@ADAEaLR`a =`="@M@>A耂 A B@B@x@ADAEaL aaa =d`="@Ǥ@&m.C3 B@BUD@x@ADAE_aLbqa =e`=)@2]@ +pNÓ/:OHMlcٲ&bHA\  B@BA@x@ADAEaLra =b`=$_@&Z@dAYA[A B@B@x@ADAEmaL a =5="@@ WA/xC  B@B`@x@ADAEρ a =a @΀@&H0%E79 ~O!2t)=e(As̀$+ B@BIXUG@x@ADAE^aLa =`= @ʀ@dAW GA B@B$@x@ADAEBaL a =="@r@ /1C  B@B@x@ADAE@aAa =G`= @>@ PA)??/Rz̀,Se&1#(PAI B@B@x@ADAE3 R =qD @;@ 4 @J-A[ B@B@x@ADAE @Y a =="@G@&/|C +W B@B@x@ADAEba =J`="@@ dzXCĘS\:60SK}⫀77a) B@B@x@ADAEjaLa =FT+$_@@dA `  B@B@x@ADAEfaL; a =$)`= @ @;qN Ӝ4I.њaߑҾbO33a& ԸD   B@B@x@ADA Eڀ!G =&a @w@dA[-Ġ B@BW@x@ADAE a =="@؀@/"C^  B@BF@x@ADAEH bl}a =+`= @\@&[r- 3 B"z0/&I< b l^A Ȑ$A B@B$@x@ADAEK,aL a =6`= @L@dA[ B@B@x@ADA EH7aL !!a! =cR=""@I@  Ġ/rC#3$ B@B@x@ADA%E8aW"1a& = C`= '@1@ A2ɼd&~rBR&:ya& 7nA( +) B@B@x@ADA*E2@a+ =N ,@"M`@dA- A. B@B@x@ADA/ElL AAa0 =9À="1@@ 2.BC23 B@B@x@ADA4EtObBQa5 ={Z`="+6@s@ sw;!>&ߍT6na& )A7sr$8 B@B@x@5 =A9 @E]-[aLR`a: =xe`= ;@o@dA<W = B@DB@x@ADA>EA*faL) aaa? = 4="@@q+@&/CA AB B@B@x@ADACE bqaD =uqa AE@@ b9^ u2`=H@ r@\6@&*ױ?+'EYz+n!@ =NAs5$t B@Bm@x@ADAuEav =;a w@L3@+ @+`.Ax2 y B@B J@x@ADAzE퀈) a{ =b="|@@&/bA}2~ B@B@x@ADA߄Eba ɯ`="@1@ +a͌Т "HnNx`|H7<Xd A B@DB+@x@ADAEaaL" =Ŭ`="@!@dA ] B@B@x@ADAEk^aL a =4h= A@_@&/d B@B@x@ADAEaa = `="@@7gX 2c9),ꄐ̇' /30Qa& Or  B@B}  @x@ADAE]Ҁ a =)W@@dAV  B@B@x@ADAEAρ  !!a =ـ="@qЀ@ /YUD  B@B; @x@ADAENJb"1a =w`="@ۈ@ A^@ 1&~m@(^5Cca& JG$+ B@B@x@ADAE2CaL2@a =p @ @̅@dA+  B@B@x@ADAE@ aLAAa =I= A@FA@ /$UA[ B@B@x@ADAE@taL a =  ~="@pu@&/C  B@Bx@x@ADAE/aw6a =6`="@-@ #+gŽtj]{2421)f-a& /AK B@B@x@ADAE1 A =o3a"@*@ '>A+ ]^B$@x@ADAE ^) a =="+@E@ ! @+A/ZC  B@B@x@ADAEbAa =P`="@@ md,M? , Z,a& A  m B@B@x@ADA EYaL a =D`=$_ @@ ! @>A  B@BA@x@ADAEUaL !!a = {_=$_@W@ / CV B@BA@x@ADAEqa"1a =`="@@&J3ϻhtxCe^cG\Nj2z\A- B@B@x@ADAEɀ2@a =$_@u @ # @>A  B@B@x@ADAEƀ6hAQa = !@Z@ ! @ xeǍ ֟sE*yW՘`ۦ%+t )_C"@3m# B@Bu@x@ADA$E:aLWR`a% =`="&@K}@ P'| ( B@B@x@ADA)E7aL aaa* =^A="++@8@ !P/sC,1+- B@B@x@ADA.E Wbqa/ =a"0@0@AĠ_:ya& 9NA1$2 B@Bx@x@ADA3EaLra4 =`="5@#@ &+$+=:6퀂 7 B@B}@x@ADA8EjaLa9 = ;=$_ +@@3:@@ /; +< B@B@x@ADA=Ecaa> =AVj@"?@b@ #!Ġ >aR Ű[1>;T#mޖ7aU '3D@qa A B@B@x@ADABE\aLWaC =g `=$_D@^@ # @>EU F B@BA@x@ADAGE? aLbH AaH =#="I@p@ Jw! @/CJܡ K B@B@x@ADALEԁ aM =va"N@Ҁ@ +j`r5 ՙy r*h7=+XǨJg?OF$P B@B+@x@ADAQE1aLaR =n"`=$_S@π@ ! @>AT* U B@*B@x@ADAVE#aL aW =Ⓚ="AX@E@ W/j?Y Z B@B@x@ADA[EE$aa\ =TL/`="]@C@ M#+Ag8Z6;t1#fGO{ PF+^-_ B@B+@x@ADA`E #aa =DI:$_b@@@ # @>Ac? d B@B@x@ADAeEaf = ;a+"g@~ ! @A/cChAi B@B@x@ADAjEqLAak =-F'l@@\FᇣQmwRMad\ L lm񳀂$n B@B@x@ADAoEnGaL8p =Q`=+$_q@t@ ! @>Arհ s B@B}@x@ADAtEkRaL Aau =u=$_v@l@&/ w[ +x B@B@x@ADAyEF'Saaz =-^`="{@Z%@ m#!A8>$y0h`6G@u8G$5:f+|$+} B@BY@x@ADA~E߀ a =*i$_@J"@ ' @>A!  B@B@x@ADAE܀ !!a =i="A@݀@&/DlW0 B@B@x@ADAEjb"1a =Ǟu`="@/@ P+̈́jh˥3 $Mo1+:#a& J + B@B}@x@ADAEPvaL2@a =Û`=$_@@ ! @>A  B@B@x@ADAEjMaL AAa =3W="@N@v/AC B@B@x@ADAEaBQa =`="@@ +#A{^a4߹@Dge(G~+] pAp$+ B@B@x@ADAE[AR`a = $_@@ PAT  B@B@x@ADAE?  aaa =Ȁ=+"@o@ ! @+/  B@B@x@ADAEybAbqa =v`="@w@ %.4f֞+ށU}?NsMDF B@B@x@ADAE02aLra =r}`=+ @t@ ! @>A)A[ B@BM@x@ADAE/aL a =8=$_@D0@ /<;C  B@B@x@ADAE a =Ha"@@ #!҃ IyUĴAQAT}"a& A B@Btm@x@ADAEaLa =C`=$_@@ # @>䀂  B@B@x@ADAEaL) a =="@@ !A/5C B@BJ@x@ADAEp[aa =(b`="@Y@%*}k#볉krPX$AX  B@B@x@ADAEaLa =_`=$_@tV@ &+ @>AU  B@B@x@ADAEaL a ==+$_@@ :/bC[ B@B:@x@ADAEÉ a =a"@Yʀ@ #!A:%<DM2kPo4ova& Aɀ$+ B@B@x@ADAEaLa =`="@Qǀ@ #>Aƀ  B@B@x@ADAEaL a =\="@Ă@ ! @A/IC0 B@BW@x@ADAE=aa =C@"@.;@ NoGpKpJ2pWa H#CA:$ B@B@x@ADAEA =@ a+$_@8@ ! @>A~7A[ B@BA@x@ADAEi a =1="@@ /C B@B@x@ADAEb-&a =`=%@@ +# @AwyY:6a'l^Q$IMDa& ,zAt$ B@B@x@ADAEZfaL a =$`=$_@@ # @>AT A B@B@x@ADAE>c%aL) !!a =m="@nd@'.VC  B@B@x@ADA+  E&a"1a =u%1`="@@&K}uP#6ywt @ UAE  B@DB@x` =A @E0ׁ 2@a =m"<$_`k3C@@ &+ @>A)  B@DBA@x@ADA Eԡ @Y AAa =݀=" @DՀ@ &+/AyC   B@B{@x@ADAE=bBQa =KH`=!I@@&Nk9 ;`Fhe䚍I a& A$ B@B@x@ADAEHIaLR`a =BS`=$_@@ # @>A  B@B@x@ADAEDTaL aaa =N= @F@&N.@"CEA B@B@x@ADAEoUabqa = ``=" @_`@&gz_,m%-_eNAq5L:З)A!-m" B@Byg@x@ADA#EڸLra$ =kaI+$_%@sj`@ PA& ȸ' B@B@x@ADA(EL a) =="*@@ &+$+J/aC+ZA, B@B@x@ADA-EEqlbAa. =ww`=!I/@Yo@ ?# @҃-!hQL-\/ 8"_$a& QA0n$1 B@B*@x@ADA2E)xaLa3 =t`= 4@Il@ # @>A5k 6 B@B@x@ADA7E&aL) a8 =d0="9@'@ -&!/0}C:/; B@BP@x@ADA<E a= =a">@.@ Ԛ,}Hvyt:3Mo};fEa& A?߀ +@ B@B+@x@ADAAEaLaB =`="C@݀@ !>D~܀ E B@B@x@ADAFEhaL @saG =Y`=$_H@Q@ Ƕxx;g*ؾ@#00 dV# flCIoP J B@ BpW@x@ADAKEZ aL^L =V`="M@M@ @JNS O B@B}@x@ADAPE>aLޗaQ == R@r @[p @Ga/@CSޡ T B@B@x@ADAUEÁ aV =yʽa"W@@AĠA)JM0ݖHb´ .AQGa& (CXD$AY B@BA@x@ADAZE/|aL"[ =m`=+$_\@Ⱦ@ P](A[&` ^ B@B@x@ADA_EyaLAxa` =@む="a@Cz@ !/Cb c B@Bm@x@ADAdE4aWae =R;`=!If@2@JĠ2ߏAmGe3-kD ڗAgh B@BJ@x@ADAiE  aj =B8 k@/@ `@>l.A[d@m B@B@x@ADAnE逈A!!ao =="p@@vՙ/,Cqꀂ$r B@B(@x@ADAsEob"1at =`="u@@ #$Ġ iF)2!H&%4m71z[KjY^:  Av@3+w B@B@x@ADAxE]aL 2@ay =`=+ z@s@ ' @>{ӟ A| B@B@x@ADA}EZaL) AAAa~ =d="@[@ !/CY+ B@B 4@x@ADAEDaBQa =:u!IA@X@ V-ǯl#tTSqzERa& %A  B@B@x@ADAE΀R`a =a$_@H@ ! @>A  B@BA@x@ADAE aaa =cՀ= @̀@PA/ӘC/  B@B@x@ADAEbbqa =`="@-@!A4kgQM.vyqȒb\m<8iNa& VA@0A B@ BA@x@ADAE?aLra =Ŋ&`=$_@!@ ' @>A!U B@B@x@ADAEh<'aL a =,F="@=@ W! @Ġ/C  B@B@x@ADAE a =2a"A@@&A*Ħi6:Af8s6kAn + B@B$@x@ADAEY3aLa ==`=$_@@ PAR  B@B@x@ADAE=>aL a =="@m@ /3C  B@B@x@ADAEh?aa =toJ`="@f@ }#+AM²|We1M09a& AD B@B}@x@ADAE.!KaLa =llU`="@c@dA(  B@B@x@ADAEVaLVa ='="@F@&/UC + B@B@x@ADAEف Ca =Jaa$_@׀@  :o^s3>}'Į=}"[A  B@B@x@ADAEbaLa =Il`= A@Ԁ@dA W B@B@x@ADAEmaLa == @@"Y/hwC  B@B@x@ADAEnJnaa =.Qy`="@H@ #18ڿTQV#5 G'Q/|:AG$ B@BM@x@ADAEzaL$ zA =N`= @rE@dAD  B@B@x@ADAE  i Ax%A =   @@PĠ/̒CY  B@B@x@ADAEC A =a"A@W@%(J/頂 `+0 B@B@x@ADA1EaL AA2 =="3@쥀@ 3/94X5 B@B@x@ADA6EC`aA7 =f`= +8@W^@ XdqqBꎁi\-.R#2NT #]9]A[ : B@B+@x@ADA;EaLA< =c(`=$_=@K[@ @>>ZA[? B@Bx@x@ADA@E)aL) AA =Z= B@@ /CC-D B@B@x@ADAEEс ;7$F =4a"+G@,π@ ` ]um63w!jլ!o4ūiuEv#AH΀ I B@BE@x@AD>JE5aL AK =?`=$_L@̀@  @>AM|ˀ N B@BA@x@ADAOEg@aL  AP = /= Q@@ @/CR f5S B@B@x@ADATEAAaaU =HL`="V@@@&gm0g]?:=YNxAA' &0Y];a& nAWm?$+X B@B@x@ADAYEXA CZ =EWa$_[@<@dA\Q ] B@B@x@ADA^E< 6h!1a_ =kca"`@ְ@+C铭Çc8:&&hYAiՃ"Cl+B9WCaB@4d b B@Bq6h@x@ADAcE-kdaL2@ad =n`="e@ǭ@#V+/{Ck l B@Bm@x@ADAmE#paWBQ!8& =H*{`="o@!@AĠ+om؈i^qpElC%.K_a&  'Apq B@B@x@ADArE܁ R`as =@'a"t@@>u v B@B}@x@ADAwE؀) aaax = ='uAy@ڀ@ / Czـ{ B@B@x@ADA|Embbqa} =`="~@@  Ġl8Ox!dr D.wmm%

    Kv> x0U! *F+s$ B@B:@x@ADAE.aLa =y`=$_@q@ &+>{p  B@B@x@ADAEf+aL a =.5="@,@ /C B@B@x@ADAE Aa =a"@@ m#+.KW?G1$sra& Jwm䀂$ B@B@x@ADAEWaLF+ =`="@@ #>AQ  B@B@x@ADAE;aL) a =="+@k@ ! @QC /4P  B@B:@x@ADAEWaa =v^`="@U@  (3Yej(Şnba& AB m B@B|@x@ADAE,aLD =j[`=$_@R@ ! @=&  B@B@x@ADAE aLa =="@@@%/C  B@BH@x@ADAEȁ a = ? #@ƀ@+A)1 hmʩF6 N]A  B@B@x@ADAEaL a =`=$_@À@ '>A€  B@B@x 9ADA @E}aL A!!a ==+"@@&$/vC~Am B@DBo@x@ADAEl9a"1a =!@`= @7@&A9Rt3-_7lEg7-ja& A6@0 B@ B<@x@ADAEA b2@a = =*a @t4@ PA3  B@B6h@x@ADAEAxAAa =@="@@ &++ |1CWA B@BW@x@ADAEB+bABQa =! 6`="@V@ #AWs@UU<]" aHF($iȼ 7§$ B@B@x@ADAEb7aLR`a =A`="@E@ #$>AA[+ B@BA@x@ADAE_BaL aaa =Ui="fi @3@`@ @Ġ/[D,@A B@BW@x@ADAECabqa = !N`="@+@ t9F`YWCRSB6) %A@2 B@B@x@ADAEӀra =Y$_@@ ! @>{  B@B@x@ADAEeЀ a =*ڀ="-@р@ &+/^ C  B@Bq@x@ADAEZba =e`="@@ P#AjcO#:PXWS5xdAl + B@BA@x@ADAEWDfaLa =!Ap`=$_ @@ @>A P  B@BA@x@ADA E;AqaL a =K="@kB@ `/[C  B@B~@x@ADAE a =v}a"@|`@ mU}_5أꬶͷoZ A%A[ B@B@x@ADAELa =什=+'u@@@&/M  B@B(@x@ADA!Emba" =Ct`="#@k@&A]z1qրJwFd6-K:SF D$ A% B@B @x@ADA&E&aLa' =?q`="(-@h@ '>A)gA[* B@B@x@ADA+-`DE"aLa, =,= -@$@ ! @+/7C.#C/ B@BW@x@ADA0Elހa1 =a"2@܀@&$FCd B/wh`5x[pEmA3ۀ$4 B@B@x@ADA5E֖aL"6 = `=$_7@pـ@ PA8؀ A9 B@B @x@ADA:EaL) a; =="<@ꔀ@&/AC=V> B@B@x@ADA?EAOaa@ =U`="A@UM@3RJ κtta0^iR{vbϴ3IQs ABL AC B@B @x@ADADEaL aE =R`= F@EJ@ ' @= GI H B@B@x@ADAIEaL !!aJ =\=+)+K@@&/CL,M B@B@xK 9ADAN @E "1aO = ?=a"P@*@&V?Ze΋UIf>r9< AQ@2WR B@B@x@ADASExaL2@aT =`="U@@ PAVz W B@B@x@ADAXEeuaL AAaY =1= Z@v@&.@C[\ B@B@x@ADA]E0aBQa^ =7`="_@.@&^|- 'V]uo AMA`k$a B@kBq@x@ADAbEV R`ac =4a+)d@+@ PAeOA[+f B@BA@x@ADAgE:  aaah == i@j@ ! @/3Cj k B@B@x@ADAlEbAbqam =n @"n@՟@&\ GQzPQ\tI&@ 6AoAp B@B@x@ADAqE+Z aLrar =i`= s@Ŝ@ PAt% u B@B@x@ADAvEWaL) aw =`=H@gD_+x@?X@&/|Cy z B@B@x@ADA{Eaa| =>!`="}@@+҃ȗV#1^=qZw"Hvx|@ 0Oa& 3A~  B@B 4@x@ADAEˁ a =,a"@ @ '$=  B@B@x@ADAEǀ; a =8"@@ @A/ŖC뀀  B@B@x@ADAE;9aLa =C`="@o~@ P}  B@BW@x@ADAE8DaL a =~B="@9@=VA[z B@B@x@ADAE@ a = Oa"@T@ &+!Ƕճ}>ՠQ+|c(hrW D@2 B@B@x@ADAEPaLWa = Z`="@D@$=  B@B@x@ADAE[aL a =S="@@ &+/C+W B@B@x@ADAEe\ao a =kg`=!I+@.c@ e-=6ߤuK|2x<5YykAb$ B@B @x@ADAEhaLA = hr`=$_@`@ @> z_  B@BW@x@ADAEdsaL) a =5$="@@A/GC B@B"Y@x@ADAEՁ ka = {~a"@Ӏ@%>HTXo!n͊lo)u7 %Ak  B@B$@x@ADAEUaL a =ى`="@Ѐ@ &+>AO  B@B@x@ADAE9aL !!a =="@i@ :A/ӪC  B@B@x@ADAEFa"1a = qM`= A@D@ # @̤(d:8 VBKm{NҖRbW A@  B@B@x@ADAE+ `2@a = lJa$_A@A@ # @>A$  B@B}@x@ADAE  AAa ="@?~ W! @A/C  B@B|   @' 5@ADA @@LlABQa =I'@@ <[E0W2_T:Dג.f` Ade B@DB|@x@ADAEpaLAR`a = 5>`=+$_@@ ! @>A  B@B@x@ADAElaLaaa =v=+$_@n@ /Vam B@B@x@ADAEk(aAbqa =/`= @&@%r{Agt.gf[W$ p(D%  B@B@x@ADAEra =,$_@o#@ # @>A" + B@B@x@ADAE݀F Aa =="@ހ@&/CY$ B@B}@x@ADAE@ba = `="@T@&[T#sw%7t ѧYtj[ "#A$ B@Bx@x@ADAEQaLa =  +$_@D@ PA`  B@B@x@ADAEN`) a =_X="@O@&/C*A B@B@x@ADAE aa =`="A@)@&*Vr٠eu }$f=Ձ H-a& BA. A B@B@x@ADAE€a =  a @@ PAy  B@BA@x@ADAEd a =,ɀ= W @@ W! @҃/nC   B@Bt@x@ADA Ezla = { `="@x@&IL+g56h&%DXB1G:^Oc Aj$+ B@B@x@ADAEU3 aL$a =~`= @u@ PAN  B@B@x@ADAE90aL a =:=$_@i1@ W/tC A B@B@x@ADAE a =p"a"A@@&5² |á z[ݦa& \?$ B@B@x@ADA E*#aLA! =h-`= "@@ # @= ## ` $ B@B@x@ADA%E.aLa& =ת="'@>@ !$A/ ʈ( A) B@BuA@x@ADA*E\/aa+ =Ac:`=",@Z@ gJY4kJZv&KV;Mya& OF+-. B@B@x@ADA/E;aL a0 ==`E`="1@W@ !K=2V 3 B@B@x@ADA4EFaL) A!!a5 =="6@@&/lcC78 B@B@x@ADA9Ej̀"1a: =Qa%;@~ˀ@ # @+¶fcf?9}@< q@D+ha& CA<ʀ += B@B@x@ADA>EՅRaL2@a? =\`=$_@@rȀ@ @>AAǠ i7 AB B@B@x@ADACE]aL AAaD =="E@郀@ A/FTG B@B@x@ADAHE?>^aBQaI =Di`=$_J@S<@&|i/0RS۝Mí߾a& DK; L B@B@x@ADAMER`aN =At$_O@C9@dAP8 AQ B@B@x@ADARE aaaS =Z="T@@&/@CU* V B@B@x@ADAWEubbqaX =ŵ`="+Y@(@&~O(Hϴ,w< p= Ga& AZ$+[ B@B@x@ADA\EgaLra] =`= ^@@dA_x ` B@B@x@ADAaEcdaL ab =0n="c@e@ A/Cd e B@By@x@ADAfEaAag =&`="h@@ iW v+[U7 rNN}[J/A@y~a& Crijj B@B@x@ADAkET؁ al =#a"m@@dAnN o B@B@x@ADApE8ա @Yk aq = ߀="r@hր@%g/rs t B@BW@x@ADAuEbav =l`= w@ӎ@&$*y]ySnj7Q0DM h|8F+x? +y B@Bt+@x@ADAzE)IaLa{ =g`= A|@Ë@dA}#A[~ B@B@x@ADAE FaLa =O= @=G@&$/1C  B@B@x@ADAEaa =<`="+@`@! ARk2u(ArkNp`$b A  B@ Bp@x@ADAELA&rEa =@aI @`@ @=  B@B@x@ADAE㶁L Aa = ="a @3@@+ @g/^C@+ B@B@x@ADAEirba =AVy`="+@}p@ v.⩗h7>`s%MR׹ͯroAo-+ B@B@x@ADAE*aLA =v`=$_@nm@ @=l  B@B@x@ADAE'aLqa =`="@W@18k6chv4 VCM#YcC$ B@B@x@ADAEaL a = "@Bހ@dݠ@+ B@B@x@ADAEaL !!a =a="@@  +/)+ B@B@x@ADAETaW"1a =Z `=)+@(R@+ @+ĠG;v;fxgE9m-ZDQ A B@BA@x@ADAE~ aLW2@a =W`='u@O@ @=xNA[-]  B@B@x@ADAEb aL) AAa ='="@ @%/V4C  B@B@x@ADAEā BQa =$a"+@€@ aĠR_X0So~~+pa& uAi  B@BA@x@ADAET}%aL}`a =/`=$_@@ @>M  B@B Y@x@ADAE8z0aL aaa == @h{@ A/YC  B@B -&@x@ADAE51abqa =o<<`="+@3@gĠGqw&.%geNϏ}Ra& A>@0 B@B}@x@ADAE) ra =f9Ga$_@0@dA"  B@B@x@ADAE @Y a =="@=@ /GC  B@Bw@x@ADAEHba =@S`= @@Ġ_Ng٪;‰*Rv (BXȢZ;a& .^  B@B@x@ADAE^TaLa = @^`= @@dA  B@B`@x@ADAE[_aLa =e="@]@ /؊C~\ B@B@x@ADAEi`aa =k`= @}@ +SIqc$]3M}bI 5 Ea& 2A$ B@BA@x@ADAEπa =v @m@dAA[ B@B 5@ADA @È) Aa =ր="@̀@ W/CS B@DB@x@ADAE>wba =`="+@R@ mH-CjzQqHV v\ua& dZA + B@B@x@ADAE@aLa =拍`= @B@dA A/  B@x@ADAE=aL8a =]G= @>@ /vC-  B@B@x@ADAE a =a"@'@ b\u&lyS L6N@ "* $ B@B @x@ADA E~aL" =`= @@dAwA[A B@B!B5B@x@ADAEbaL ޗa =*="A@@g/z A B@Bs $@x@ADAEiaa =p`= +@g@ T.0c+!Km1Jwb Σ$a& Dh$ B@B@x@ADAES"aL a =m`= @d@dALA[ B@B@x@ADAE7aL@Y !!a =)="!@k @ WA/ C"# B@BA@x@ADA$EڀA"1a% =va"&@؀@& .}Pv' o<<|co'>$( B@B@x@ADA)E(aL2@a* =f`="+@Հ@ &+$g>," - B@B@x@ADA.E aL) AAa/ =ᙀ= 0@<@& /1 2 B@B@x@ADA3EKaBQa4 =;R`="5@I@W!(_6P݊OҾ㾃2vE젾x6@4-7 B@B@x@ADA8EaLA )R`a9 =O`=$_`= @3v:@F@ ' @>;E < B@B@x@ADA=EaLaaa> =AV ="?@@/~@} A B@Bq@x@ADABEhbqaC =a"D@|@ 'sSiC:-VhAJж K B@B@x@ADALEqaL+AxaM =@{=+"N@r@&$/OS3';P B@B@x@ADAQE=-aaR =3`= S@Q+@ @A],E'[\"U cLj!lQOmT*A[a@U B@B+@x@ADAVE倈AaW =0a$_X@E(@ PAY'A[Z B@B@x@ADA[E  a\ =P="]@@A/6h^(_ B@BW@x@ADA`EbAaa =&`="b@'@ '.UqwGgžb9t.2<TVc$d B@B@x@ADAeE}V'aLaf =1`="g@@ !$+=hv i B@BA@x@ADAjEaS2aL$ak =.]="+l@T@ &+ @/Vm$n B@B@x@ADAoE3ao)  +ap =>`="q@ @ 1#wʂ~#mlc^1 Yg\׏@ rl $s B@B@x@ADAtERǀau =!AIa$_v@ @ # @=wP Ax B@B@x@ADAyE6ā az = ΀="{@jŀ@ !/`LW|֡ +} B@B@x@ADA~EJba =uU`="@}@ ]rs`pZ鱙WΐU [WhMB>A  B@B(@x@ADAE(8VaLA =e``=$_@z@ ! @>A!  B@BA@x@ADAE 5aaLAx@! =@>=+$_@<6@&/lD  B@B@x@ADAE lAA =;la"@@!҃rZYˀTی ^s܄DgA>a& @0C B@Bm@x@ADAEmaL A =:w`="@@ '>Aꀂ B@B@x@ADAExaL !!A =="@@ ! @A/}A[ B@B@x@ADAEgaya"1A =h`="@{_@&/YsMxUЮ Nm@k{>ޔ&p– a& s^$ B@B@x@ADAEaL2@A =e`=+$_@k\@ PA[A[ B@B@x@ADAEaL AA)A = =$_@@ /R: B@B@x@ADAE=ҁ $B%! =؛a"@QЀ@&䈣H&suaZV|/~aU Jπ`4 B@B@x@ADAEaLR`A =զ`= @À@ # @"`C >À  B@B@x@ADAEaL) aaA =\="@@ !$/;A' B@B@x@ADAECabqA =I`="@&A@ 'e }@ebrΌ))%fa& xA@  B@B@x@ADAE}rA =F$_@>@ @>v=  B@BA@x@ADAE`+0 8A = a+"@@ /3Cg  B@B@x@ADAERlaLA =`="@ﮀ@ @>K  B@B}@x@ADAE6iaLAxA =@s= @fj@ +^l&0 s|zݐ>B,X4G  B@BW@x@ADAE$aA =h+`="@"@gՙLcGփ"& DFsrLas[a& /D<$A B@Bg@x@ADAE'݁ WA =e(a+$_ @3@@ # @> A[- B@B@x@ADAE ځ A = = @;ۀ@ ! @W/٩C  B@B@x@ADAEbWA =F`="@@ AĠJy-ޅ'wt]ڸ=wє/>a& + B@B{@x@ADAEMaLA =:@$_@@ ! @>A  B@B@x@ADAEJaL) AA = T=H gj@L@ /݆D|K B@B@x@ADAEgaA = `="@{@ #+ QA [92ȶيA-S` M~a& %/A + B@B@x@ADAEѾ A = a"@k@ #>AA[A B@B@x@ADAE  A =ŀ="+@开@ :! @+A/CQ B@B@x@ADA0  EAu⠂Ad B@DB@x@ADAE`KaL AAa = 1="@@ ! @Ġ/0C  B@B@x@ADAEXLaABQa =_W`=" @V@&Ncbju Ҥ Dk4g: Ip!f A" B@B@x@ADA#EQXaLR`a$ =\b`=$_%@S@ `@>A&J ' B@B@x@ADA(E5caLaaa) =="*@e@&/U+ , B@Bli@x@ADA-Ecabqa. =hn`="/@ǀ@ #+A].{a߾~Hk f'," a& F+0<1 B@B@x@ADA2E&oaLra3 =dy`="4@Ā@ '$>A5 A6 B@B@x@ADA7E zaL) Aa8 = ӈ="9@:@ !A/SC: ; B@BH@x@ADA<E:{aa= =>A`=!I>@8@ q_dWL c̅tmia& m? A@ B@B@x@ADAAE aB ==>a$_C@5@ ! @>AD4 E B@B@x@ADAFE aG = = H@@ /mI| J B@B@x@ADAKEfbaL =`="M@z@7'ps@4>6?z W|%њ1Aa& _gN樀-+O B@B7@x@ADAPEcaLaQ =`=$_R@j@ # @>ASʥ T B@B@x@ADAUE`aL aV =j= W@a@4҃/CIAXQY B@B@x@ADAZE;aa[ ="`="A\@O@&AnhQv133Pݒ`8&IA]$^ B@B+@x@ADA_EԀ(a` =$_a@@@ PAb c B@B@x@ADAdEр ae =Sۀ="f@Ҁ@ &+$Q l@C /d"@Gg&h B@B@x@ADAiEbaj =œ`="k@%@& "!mISYt. \y;Aـ  B@B@x@ADAEߔaLaaa =="@@ /lKC{ B@B@x@ADAEfPaAbqa =W*`="@zN@ +UyQŝPDRZ>va&  AM$ B@B@x@ADAE+aLra =T5`="@iK@dAJ  B@B@x@ADAE6aL) Aa =="@@&/CP B@B 5@ADA @E; a = ?=Aa A@O@&$fS;COx[5\ @ ?g A  B@B@x@ADAEyBaLa =L`=)@?@dAA[ B@B@x@ADAEvMaL a =Z= @w@ A/_C% B@B@x@ADAE2Naa =8Y`="+@$0@ A(yyBʖDQÖ~:Xo쏻!A/  B@B"Y@x@ADAE{ꀈa =5d @-@dAt,  B@B@x@ADAE_ a =/="@@ /=C  B@B@x@ADAEeba =p`="+@@ w OKi̅Pt=IG줟}a& aAe$+ B@B@x@ADAEP[qaLAa ={`= @Ꝁ@dAI  B@B@x@ADAE4X|aL6ha =o`="@@ A~",ptV M % h!9(o@:Fa& 5C:  B@B@x@ADAE%́ 6| =ga"@@dA[W B@B`@x@ADAE Ɂ ? a =Ҁ="@9ʀ@  + /WC  B@B@x@ADAE@TWa =E`= @@AĠhC{<[2bl_.?HĦ>:5A B@B@x@ADAECz:A[ B@B@x 9ADA @Ee"1a = a"+@y@!PKvdPSjw8,gMw$9*` A  B@DB?@x@ADAEЭaL2@a1  `= @i@ @= W B@BW`x@9 A @EaL AAa =x="$@䫀@+ @A/1PA B@DB@x@ADA E:faBQa =l`="+ @Nd@ )m l?1m4l*`L*ftya& $qD c$+ B@B@x@ADAEaLR`a =i`=$_@?a@ @=`A[ B@B@x@ADAEaL aaa =U%="@@/]C%W B@Bw @x@ADAEׁ bqa =a%@#Հ@ CH{n:Z;۬ }a` f=a& `AԀ$ B@BJ@x@ADAEzaLra =`= @Ҁ@dA sѠ ! B@B@x@ADA"E^aL a# =2="$@@ /C% & B@BC@x@ADA'EGaa( =N`= )@E@%o҃G/PZ;CeTy7`*9e=:TkSeA*e+ B@BC@x@ADA,EOaLa- =K@ .@B@dA/I@0 B@B@x@ADA1E3 ) a2 =  3@c~vA/C4 5 B@BtA@x@ADA6ELa7 =wa"+8@ζ@&/[k2p/{ 25ψa& {9: : B@B@x@ADA;E%qaLa< =b`="=@@dA> ? B@B@x@ADA@En aLaA =w="B@8o@ A/DC D B@B@x@ADAEE)!aaF =@0,`= +G@'@&>4D/y-e>0r;OP(;Ip$H/PAH I B@B@x@ADAJE aK = 7-7a AL@$@dAM# AN B@B@x@ADAOE AaP =="Q@@&/|CRz߀ S B@B@x@ADATEd8bpliaU =%C`= V@|@ +Ͳ{:5T2.F@a& "xAW藀-X B@B+@x@ADAYERDaLAZ =N`= [@l@dA\ȔA[] B@B@x@ADA^EOOaL a_ =Y="`@P@ A/TCaOb B@B7@x@ADAcE: PaAad =[`="e@N @&Pé`Ut857v#\Af$g B@B@x@ADAhEÀ ai =fa"j@>@dAk$l B@BA@x@ADAmE !!an =Mʀ= o@@ +/SCp$A[+Aq B@Bg@x@ADArE|gb"1as =Âr`="t@#z@& Ēqbf6r24~o1֞Yxg:Auy+v B@B|@x@ADAwEy4saL2@ax =}`= Ay@w@ * @g>zsv { B@B@xy 9ADA| @E]1~aL AAa} =.;="~@2@& />C  B@DB`@x@ADAE BQa =a"@@&AvzQ};7V6?Tq13*M"]NAd + B@B@x@ADAEOaLR`a =`=$_@@ ' @>H  B@BA@x@ADAE3aL aaa ==+ A@c@&A.VC  B@B@x@ADAE]aqa =nd`= @[@ +' @gFQa*yy2yGza& ֚A9- B@B+@x@ADAE$aLra =ba`=$_@X@ ! @= A[W B@B@x@ADAEaL@Y a =="@<@ W&+g/ C B@BW@x@ADAE΁ a =Bոa"@̀@&@Gcz9؀D5GG"57R'A A B@B@x@ADAEaLa =7`="@ɀ@ #>AȠ  B@B@x@ADAE݃aL a ==$_+@ @ ! @+/Cy B@B@x@ADAEd?aa =F`="@x=@&$%2 r]ox Qk0Na& eA<$ B@B@x@ADAEa = Ca$_`3r@h:@ PA9  B@B@x@ADAE) a ={="@@& //ICN B@B>@x@ADAE9ba =`="@M@ #+uñ cir ^gP,,a& s A + B@B@x@ADAEhaLAa =`= @=@ ' @=  B@BA@x@ADAEeaL a =Qo=+"+@f@ ! @J/!C$ B@B@x@ADAE!aa ='`="@"@ vI<C#A1,"f'kmta& L*$ B@Bv@x@ADAEyـA) =$ x-"@@ !>r  B@B P @'@x@ADAE] a =1= @׀@ H/*  B@AbB@x@ADAE ba =`="@@XZbl(ck _@ݏ4O p8`F+c$ B@cB Jw@x@ADAENJaLA a =!`=+$_@猀@ # @=AG  B@BA@x@ADAE2G"aL) !!a =Q= @bH@ ! @A/LC % !kW B@B @x@ADAE#aA"1a =q .`="@@ +'a10[v19h(xD!OY-a& ?A9 B@B@x@ADAE# 2@a =a9$_@8`@ ! @>A A B@B@x@ADAEL) AAa =="@7@ /cC  B@B@x@ADAEs:bBQa =:zE`="@q@ m#5)C5Oaeeq2ث0OPF+a& :d + B@Bm@x@ADAE+FaLR`a =6wP`=+$_2@n@ # @>Am  B@B@x@ADAE(QaLaaa =2="@ *@ @A/dx)!& B@BJ@x@ADAEc䀈bqa = \a" @w@ CbF[ì Ǔ@= va& WG ဂ  B@B@x@ADA EΜ]aLra = g`=+$_@g߀@ @>Aހ  B@B W,X@x@ADAEhaL Aa =v=$_@⚀@v @/IAN!6  B@B@x@ADAE8Uiaa =[t`="@LS@2iQ+*ŷ:b3%ga& AR@0oc@m B@B@x@ADAE uaLAa = X`=+$_@A.qA[/ B@B@x@ADA0E\{aL a1 = ="2@|@ &+/~3 4 B@B@x@ADA5E6aa6 = =`="7@4@ #AkP[F${nϊ}n#coLF+8c9 B@B"Y@x@ADA:EM A9*gxa; =:a"<@1@>=K > B@B@x@ADA?E1 @Y Axa@ =@ ="a @3 4A@a@ /CB C B@B@x@ADADEbaE =AVm`="F@̥@ {~p gTdpͦ޶(AG8 H B@B@x@ADAIE#`aLJ =``=H@SQK@@ !>AL M B@BA@x@ADANE]aL+@saO =@f= P@7^@%/Q NR B@B@x@ADASEa.aT =F`="U@@&P\>DHj;c_Jka& jhDV-W B@BP@x@ADAXEЁ  aY =5$_Z@@ ' @>A[\ B@B@x@ADA]E !!a^ =׀="_@ π@&/DC`x΀ a B@B@x@ADAbEbb$"1ac =`="Ad@v@ +'&lE'دh'kpQ6AJ^7v,)a& ~&Aeↀ$f B@B 4@x@ADAgEAaL2@ah = `=$_i@g@ ! @= jƃ k B@B@x@ADAlE>aL AAam =H="n@?@ &+/ CoM+p B@B@x@ADAqE8 BQar =@"s@L`@ +#LsfH8J4i ݾ@~x]^aOAt$u B@B@x@ADAvELR`aw = aI"x@?@ #>Ay z B@B g@x@ADA{E aL) aaa| =V="}@@&/ 6C~" B@B@x} 9ADA @E k abqa =q`=$_@!i@ C^oƈxH%g<ݳd?"L` sAh + B@DB@x@ADAEx#aLra =n#`=$_@f@ ! @JAqe  B@B$@x@ADAE[ $aL a =,*="@!@ W&+#VA/C  B@B@x@ADAEہ a =/a"@ـ@&  lBjU飅NH]=a& mQAb  B@B"Y@x@ADAEM0aLa =:`=$_@ր@ # @=F  B@B@x@ADAE1;aL a ==$_@a@ `! @J/}C  B@Bt @x@ADAELA񷀂 A B@B@x@ADAErjaL@Y) Aa =|="@t@ W!A/FC{s +W B@B@x@ADAEb.kaa = 5v`=!I@v,@&JHj1`7h vt#(|a& `A+  B@B 4@x@ADAE怈A = 2$_@f)@ &+ @>(  B@B@x@ADAE〈 a =u="@@ /CLA[ B@B@x@ADAE7ba = 쥍`="@K@ #$*+k Gv*.0{÷#QAD$ + B@B@x@ADAEWaL a =ߢ`=$_@;@ @>A  B@B@x@ADAETaLA5!!a =Z^="@U@&$/| &  B@B@x@ADAE alXZ"1a =`="A@ @&N@t{~T| 5J{o~簔Ss*GF+ $+ B@B@x@ADAEwȀ@a =$_@ @ @>Ap  B@B@x@ADAE[ŀ AAAa =/π="@ƀ@ A/C W B@B@x@ADAE‱bABQa =`="@~@ PAwޯh)Hv.<&[:fAa + B@B\@x@ADAEL9aLR`a =`=+$_@{@dAEA[W B@B :%o@x@ADAE06aLaaa =@="@`7@&/LJC  B@B@x@ADAE bqa = ta @@&{"͘{4=%i95VsԗA7 B@B@x@ADAE!aL-ra =c`= @@dA3   B@B@x@ADAEaL) Aa =ް="@5@&/"C  B@B@x@ADAEbaa =Ai`=" @`@&δ6LtGWc oZ2`^Bja& Y  A B@B@x@ADA EaLAa = 4f`= @]@dA\ A B@B@x@ADAEaL a =!= @ @&/ w B@B@x@ADAEaӀa =@ @uр@ P@1IBjN! N@ DF+Ѐ- B@BP@x@ADAE̋aLa = `= @f΀@dA͠A[ B@B@x@ADA EaLda! =J`= "@NB@ :ڇv%qu4*h}8 oJ ̋KqC#A$$ B@B@x@ADA%Ea& =G%a '@>?@d(>A[) B@B@x@ADA*E:a+ =Y&a",@~  + /-% . B@B@x@ADA/E Lo:a0 =1a 1@$@ &l| d`ٱgi?6)8YC- a& D2$3 B@Bm@x@ADA4Evm2aL zA5 =<`= 6@@d7p 8 B@B@x@ADA9EZj=aL) WA!: =#t= ;@k@Ġ.-C< = B@B@x@ADA>E%>aWA? =,I`="+@@#@A[O ypqIu!Ad%31`AAa +B B@B@x@ADACEKށ A AD =)Ta E@ @dAFE G B@B@x@ADAHE/ۡ @Y !!AI =="J@_܀@&A/MK L B@BW@x@ADAMEUb"1AN =!o``= +O@ʔ@&AbG҇-u}DE.< DP6 Q B@B@x@ADARE!OaaL2@AS =^k`= T@@dAU V B@B@x@ADAWELlaL AA)AX =U="Y@5M@A/ CZ [ B@Bw g@xY 9ADA\ @EmaBQA] =<x`= ^@@ Ԃ)rs%dcs5_IPY$A_ -` B@DB @x@ADAaE AR`Ab =8 a c@@dAdA[e B@B}@x@ADAfEڼaaAg =ƀ="h@ @ /r$Civj B@Bu@x@ADAkEaxbAbqAl =`= m@uv@ V}9fCWeH.&=. `}D Anu$o B@B+@x@ADApE0aLrAq = |`= r@es@dAsr$t B@BA@x@ADAuE-aL AAv =7= w@.@ W/LCxKy B@BW@x@ADAzE6aA{ =`="+|@J@&$߂zY٥8 ֭7FO4A}怂$~ B@B@x@ADAEaLAA =`= @:@dA。  B@B@x@ADAEaL) A =M="@@&$/C  B@B+@x@ADAE ZaA = ``="@X@ A f:Qa="bifjĕy6 AW m B@B@x@ADAEvaLA =]`= @U@dAoTA[ B@BA@x@ADAEZaL A =#=G@ A@@ A/]C  B@B@x@ADAEʁ A = a @Ȁ@ @2`C {Λv"8@+PE萜 + `@2N B@B @x@ADAEKaLA = `= @ŀ@ @;ہD A B@B@x@ADAE/") A =="@_@+ @$/C]  B@Bp@x@ADAE;aA =]B`="@9@lYk 2!$}k74ڍB4<\+[a& {A5@0 B@B@x@ADAE  A = ^?a$_@6@ @=  B@B@x@ADAE @Y AZA =="@4@ WA/WC  B@BA@x@ADAEba = ; #@@ P 5sH1gjvAq1h aί A  B@B@x@ADAEdaL a =3`=$_@@+ @>A呂  B@B@x@ADAEaaL) !!a =k="@ c@&/XCub B@B@x@ADAE`a"1a = $`="@t@ XDTSj¬I %:7bC A A B@B|@x@ADAEՀ2@a = !'$_@c@ ' @>A  B@BA@x@ADAEҀ [AAa ={܀=+ A@Ӏ@ ! @/ `K  B@B@x@ADAE5(bBQa =3`="@I@&e`L*7䗂jpa& :o$ B@B}@x@ADAEF4J&A      R`A3  ݑ> H =`G `= @3@9@ $`6@*@ `@Q @C K`z.@G@T `@  iF B@B B @'@x@AD>EC?aLADaaa =@@[XM= @D@ `@ | e`L*7䗂jp?A  `XA B@B  @x@ADAE l#Cbqa = Ka"@J`@ #!K'#!ж$fj~&EDJCl&Փ~6A@4 B@B@x@ADAEuLra =VaIG @U`@ #$=n  B@B@x@ADAEYLAxa =@.="+@@ ! @Ġ/C  B@B@x@ADAEoWbka =vb`="@m@ +S(x#5NYg WeX #A` B@B@x@ADAEJ(caLa =sm`=$_@j@ ! @>AD  B@B 'A@x@ADAE.%naL) a =/="@^&@&/ՎC 4  B@x@ADAE a =eya"@ހ@%A>RQ1KDQŝYgnI-IA5  B@Bx @x@ADAEzaLa =a`="@ۀ@ '>A   B@B !@x@ADA EaLOm =П=)+ @3@ ! @OmA/@G !k B@B@x@ADAEQaa =7X`="@O@ ' ꒐x|eT j#lmPa& %A  B@Bu @x@ADAE aLa =2U`=$_@L@ ! @K=K  B@B@x@ADAEaL Aa =="@ @ /Cu B@Bt@x@ADAE_€F+ =ɨa"!@s@ #!҃V¥tgʯ|9E췍tA'ü g@ᇶ( B@B@x@ADA)EwaL a* =="+@x@ ! @҃/C,J- B@B@x@ADA.E53aAa/ =9`="0@I1@&XxIgTL5 (? T`j4.A10 2 B@Bx+@x@ADA3E뀈 a4 =6$_5@9.@ &+ @>A6-A[7 B@B@x@ADA8E耈 !!a9 =O=":@@%A/hC;< B@B@x@ADA=E b"1a> =`="?@@ #+Ay eRERYjdůRa& A@$A B@B|@x@ADABEt\aL2@aC =`=+$_D@ @ ' @>AEn AF B@B$@x@ADAGEXYaL) AAaH =-c="I@Z@&A/CJ K B@BA@x@ADALEaBQaM =`="N@@ >hńIvi] ; kQ浞eAO_ AP B@B@x@ADAQEJ́ R`aR =a+$_S@@ ! @>ATC U B@BA@x@ADAVE.ʁ  aaaW =Ԁ= X@^ˀ@ &+ @/CY AZ B@B@x@ADA[Ebbqa\ =d "]@ȃ@ #AlBŷC8%c2u.]ˊa0@ iA^4-_ B@B@x@ADA`E>aLraa =\`=$_b@@ # @Qz:=$cd B@B@x@ADAeE;aL 4Axaf =@D="g@3<@ !/Ah !owi B@B@x@ADAjE ak =>a"l@@%0#xB^9mݍ./V_a& Frm $n B@B@x@ADAoEaLap =2(`=$_q@@ &+ @>ArA[c @s B@B@x@ADAtEث)aLau ==+$_v@@ /Dwtx B@B@x@ADAyE_g*aaz =n5`="{@se@ +#!X:hg; (+hPRdk? &entba& A|d$} B@B+@x@ADA~E6aLa = k@`="@fb@ #>Aa  B@B@x@ADAEAaL) Aa =z&="@@ W! @A/QCI+ B@B@x@ADAE4؁ a =La"@Hր@&<֤Y\' 4Z{x IN a& (/AՀ  B@B@x@ADAEMaLa =W`=$_@8Ӏ@ &+ @>AҀ  B@B$@x@ADAEXaL a =KX`="@@ /C B@B@x@ =A @E IYaLa =Od`="@G@&$T4ҧŮx9R= ,7` ~nAF  B@DB@x@ADAEteaLA =Lo`=$_@ D@ # @>AmC  B@B@x@ADAEX a =(p)@~ ! @/BC  B@B@x@ADAE޹La ={a!I@@ +' @%>Bi7Kv1^l3^$+ B@BA@x@ADAEIr|aL a =`=$_@㴀@ ! @=$BA[ B@B@x@ADAE-oaL) !!a =x=+"@]p@&/   B@B@x@ADAE*aA"1a =l1`="@(@ P#!҃_FP]-?1Ժ;a& F+4 B@BA@x@ADAE 2@a =\.a"@%@ '>A  B@B@x@ADAE  AAa =="@2@ W! @A/FC W B@B@x@ADAEbBQa =5`="@@ v'TRgRM\{3'BJB$6A  B@Bs @x@ADAESaLAR`a =1`=$_@@ ! @>A핵`  B@B@x@ADAEP` aaa =Z="@R@ W/VCsQ B@B@x@ADAE^ abqa = `="@r @+Ala盱vT\6LINN1)h .A + B@Bp@x@ADAEĀra =$_@b@ # @>A  B@BA@x@ADAE a =qˀ="@€@%/}I  B@B@x@ADAE3}ba =٦@$_@G{@ ' @>EOxc**3KO_xXo@ aDz$+ B@B@x@ADAE5aLa =܀`=$_@8x@ ! @=w  B@B@x@ADAE2aL a =V<="@3@ &+/CA B@B@x@ADAE a =a"@@ #-H&ǩ.v1N+cy%M1A` + B@ByA@x@AD>Es`a =`=G@3R@@ #>Al蠂  B@B *@x@ADAEWaL a =#="@@ !/YC $ B@B$@x@ADA5  E^aa =e@K!I@\@&[aԲ~1U|bUH࠷sǻ@ цA^ B@DB@x` =A @EH aLa =b`=$_@Y@ &+ @JAB  B@DB@x@ADA E,aL) a ==" @\@&/'C   B@B@x@ADAEρ o7a =ca"@̀@ #$2`bQ>/vܙD8>ˏia& jA7 A B@Bt .@x@ADAE aLA =[*`=$_@ʀ@ ' @>A  B@B@x@ADAE+aL a =ʎ="@2@ ! @/!FC A B@B@x@ADAE@,aAa =9G7`=!I @>@ 3iԆ9,-.2ВԢ*\-= sA!" B@Bx@x@ADA#E  a$ =0DBa$_%@;@ ! @>&: ' B@B@x@ADA(E !!a) =="*@@ /C_C+s , B@Bw@x@ADA-E]Cb"1a. = N`= /@q@&AqM>'.94ot?VFC"0y@a& B0ݮ+1 B@B@x@ADA2EiOaL2@a3 =Y`=$_4@a@ # @>A5A[G6 B@B@x@ADA7EfZaL AAa8 =tp=H@4 9@g@ ! @A/!tC:H; B@BA@x@ADA<E3"[aABQa= =(f`=!I>@G @ ' @`Yߪ7f"BNKB_Ex")a& d?$@ B@B{+@x@ADAAEڀR`aB =%q$_C@7@ ! @=DA[E B@B@x@ADAFE׀) aaaG =V="H@؀@ /?dIJ B@B@x@ADAKErbbqaL =}`="M@@&4%N' 6Fh7EJPi&gzF+N O B@Bv@x@ADAPErK~aLraQ =`=+$_R@ @dASl T B@B@x@ADAUEVHaL@Y) aV =+R="W@I@&%o/VCX$Y B@B}@x@ADAZEaa[ = `=)\@@ ' @$jX8!8=5z^ua& "oA]] ^ B@B@x@ADA_EHaLa` =`= a@`@dAbA Ƕc B@B@x@ADAdE,L WXae =c{aI"f@r@ A)b PFo ǜc)\k@Cg2$h B@B}@x@ADAiE-aLaj =[x`= k@o@ ! @>lA[m B@B@x@ADAnE*aLAxao =@3="p@1+@  +/7lCq r B@B@x@ADAsEakat =8`= u@@JĠH71Ik|0G ;AbGx[n,a& ccAv w B@B@x@ADAxEaLay =0`=$_+z@@d{߀ | B@B@x@ADA}E֚aLa~ == @@&/OCr B@B@x@ADAE]Vaa =]`="+@qT@ '!Ġ>W#LvHɁc* $ƸCa+ga& ~AS$ B@B@x@ADAEaLA =Z`= @aQ@dAP  B@B@x@ADAE aL) Aa =w="@ @&/]RCG B@B@x@ADAE2ǁ a =a$_@Fŀ@ -`j0aJbɮUg2o@a&  ZAĀ A B@B@x 9ADA @EaL a =`= @:€@dAA B@DB`@x@ADAE|aL !!a =Q="@}@ /vCa B@BP@x@ADAE8a"1a =>  @6@ F`@^\ݺ TW ,^0_D 2ɾҦ E qA5$+ B@B}@x@ADAEr2@a =; @ 3@dAk2 A B@B@x@ADAEV AAa =="@@ /C Ø B@B}@x@ADAEܨbBQa =!`="@@&>gn'?־+ HVa& [A\$ B@B 5@ADA @EGa"aLR`a =,`= @ᣀ@dA@ `a/ ir B@DB@x@ADAE+^-aL) aaa = g= @[_@  `@$>.C  B@Bt@x@ADAE.aAbqa =n 9`="@@%Agsw1o){H~^z}'&tذ"`2 B@B@x@ADAEҁ ra =ZDa @@dA ` B@B@x@ADAEϡ @Y a = 5؀="@0Ѐ@ /M`  B@B@x@ADAEEba =3P`="@@ +B/.Ұ\t=1-0:AQa& F+  B@B+@x@ADAEBQaLa =/[`="@@dA넠A[ B@B@x@ADAE?\aLa =I= A@A@ /?/Cq@ B@B@x@ADAE\a = ha"@pg`@ gejP`v-6?N1ZF5Uj a& A  B@B@x@ADAEdzLa =raI A@`@dA  B@B@x@ADAEsaL Aa = w= @۱@ }/n:CG B@B@x@ADAE1ltaa =r`="@Ej@&Aa8 FVʺ-i\9bHQP [pi$+ B@B@x@ADAE$aL`a =o`= @:g@dAf  B@B@x@ADAE!aL a = M+= @"@ /~D B@B@x@ADAE݁ Aa =a"@ۀ@ mA1)8qo/7?/a&  Aڀ$ B@B@x@ADAEqaLA ~6`= @ ؀@dAjנ  B@B`x@9 A @EUaL a =&="@@ /.'C  B@DB@x@ADA EMa-&a =T`=" @K@ U -0' e&7Z y,ra& WA ` B@B+@x@ADAEFaL a = Q`="@H@ # :=C@ A B@B x@x@ADAE*aL) !!a = ` = A@Z@&A/C  B@B@x@ADAE A"1a =ja"@ż@ J*0Z;zHKʰ& [tJa& \A1  B@B @x@ADAEwaL2@a =Y`=$_ 4@@ &+ @>  ! B@BA@x@ADA"EtaL AAa# = 5}= $@0u@ ?&+ @g/C% & B@B +4@x@ADA'E/aBQa( =66`=")@-@ +#=䬥d Ar83kA*-+ B@B+@x@ADA,E R`a- =.3$_.@*@ # @=A/)A[06@-B@x@ADA1E䀈aaa2 =="A3@@ W!/C4q倂5 B@B@x@ADA6E[bbqa7 =`="8@o@&$[[Kb;hŸQ !(r*!sQdlA 9۝ A: B@@iB$@x@ADA;EXaL$ra< =`=$_=@`@ &+ @>A>A[? B@B@x@ADA@EU* AaA =s_="B@V@ /DCFD B@B@x@ADAEE1aaF = `="G@E@&$OAVd,?´vaGM[+AAH$I B@B$@x@ADAJEɀaK =a"L@4 @ #>AM N B@B@x@ADAOEƀ) aP =PЀ=)+Q@ǀ@&$/_CRS B@B@x@ADATEbaU =#`="V@@&!XחY.*'mPb8a& AW AX B@Bm@x@ADAYEq:$aLaZ =.`=$_[@ }@ PA\j| ] B@B@x@ADA^ET7/aL a_ =)A="`@8@&/5,Ca b B@B@x@ADAcE ad =:a"e@@ #&lW$m K3jVn_TSCqAf[ g B@B@x@ADAhEF;aLai =E`= j@@ PAk? l B@B@x@ADAmE*FaL@Yk an ==+"+o@^@ ! @/Cpʨ q B@B@x@ADArEcGaas =ajR`="t@a@&$xZ;r&V]C61:ݲB3a& vu0-Pv B@B@x@ADAwESaLA m =Yg]`= y@^@ &+ @>zA[{ B@B@x@ADA|E^aL6ha} =6i`=+$_~@Ҁ@$Ƕ%BTQ[1 W/R~)2‹UDLD$ B@B@x@ADAEjaL a =2t`="@π@ &+>΀  B@B}@x@ADAEԉuaL>!!a == @@+/Ct  B@B@x@ADAE[EvaW"1a = L`="@oC@&;2„( 1h3.xT-xza& hAB  B@BM@x@ADAE2@a =I$_+@_@@ P?  B@B@x@ADAE WAAa =va"@~&/CE"diK B@B@x@ADAE0LBQa6 ܼ'@D@A.廒=Zy}b6ɑ4ۥ\ nA  B@BC@x@ADAEnaLR`a =ع`= @4@ PA  B@BW@x@ADAEkaL aaa =Hu="@l@ -&&+)A/~ B@B@x@ADAE'abqa =-`="@%@&@ȶ.IsjV d~Rx91a& $$+ B@B@x@ADAEp߀ra =* @"@ # @=m!A[g@ᇶ B@B $`@x@ADAET܀ a ==+$_@݀@&/'G  B@B}@x@ADAEۗbAa =`="@@ '!AD©Ue*s"wa& ;}AZ  B@B@x@ADAEEPaLa =`=+$_@ޒ@ ! @=>  B@BA@x@ADAE)MaLa =V="@YN@ &+A/QC + B@B`@x@ADAEaa =d`=!I@@ # @V3.At]ll1A0 B@B<@x@ADAE a =X $_@@ # @>A A B@B@x@ADAE $a =ǀ="@2@ !/C + B@BC@x@ADAEybo ƴ̟a =5`="@w@&M3h-;"` AJ6لA  B@B@x@ADAE1aLa =1}@$_@t@ &+ @>As ` 6h B@BA@x@ADAE.aL}a =8=$_@0@&//Ct/  B@Bt @x@ADAEZꀈa = a"A@r@&v>Qu?3^2΢oa& JA瀂$ B@B@x@ADAEŢaL$ zA =`=$_@^@ ' @>A䀂  B@B@x@ADAEaLAx@! =@q= @٠@&/>fCEW B@B@x@ADAE/[a$&! =a%`="@CY@ '!Q.Q%eI |lQ[Lna& AX$ B@B@x@ADAE&aL A =^0`=+$_@3V@ ! @=UA[ B@B@x@ADAE~1aL !!A =R="7@@ &+/ C B@B  @x@ADAÉ "1A =A iƀ A B@B@x@ADA ESHaL) AA' = ="@@&/wC  B@B@x@ADAEA>  B@B@x@ADAE( @Y5 aaA =="@X@/>C  B@B@x@ADA!E`bbqA" =!dk`=$_#@ë@&@~Inu?/i6mKljh4iT[p\A$/ % B@B|@x@ADA&EflaLrA' =[v`=$_(@@ PA) * B@B@x@ADA+EbwaL A, =l= -@.d@ ! @/xpC.cA/ B@B@x@ADA0ExaA1 ==%`="2@@ m'AF`3Ev`sikSKP"a& A3-4 B@B@x@ADA5Eց A6 =-"a+ 7@@ ! @>A8A[9 B@B@x@ADA:EӀ A; =݀="<@Հ@ /eC=oԀ a> B@B@x@ADA?EZbkgA@ =`=$_A@n@ +# @ak|K[0~ADM%â? dBڌ$C B@B+@x@ADADEGaL FE =`=$_F@^@ @>G$H B@B@x@ADAIEDaL AJ =yN="K@E@ W/(LDM B@B:@x@ADANE/aAO =`="P@C`@&ujG.ӧ8ujaˏ_}0a& +F+Q$R B@B@x@ADASELAT =aI"U@3`@dAV AW B@B@xU 9ADAX @E}L@h5 AY =M="Z@@&/ݰC[A\ B@DB@x@ADA]EqbA^ =w`=)_@o@ AZN#AEvC A`n a B@B@x@ADAbEo)aL!)Ac =t`=)d@l@dAehkA[f B@BA@x@ADAgES&aL A_-Ah =0="i@'@ #V$/>X:j k B@B@x@ADAlE am =a!In@߀@ @ƃ'Us zqNRa& qAoY@0p B@B@x@ADAqEDaL Cr =`= s@܀@ @=t=u B@B@x@ADAvE(aL !!aw =저="x@X@+ @/D]Cy z B@B@x@ADA{ERa"1a| =_Y`="+}@P@ AJ5'KyB@Hg_wa& A~.$ B@B+@x@ADAE aL2@a =WV $_@M@ @=  B@B@x@ADAEaLAAa ==$_@- @ /M)CA B@B@x@ADAEÀBQa =4a"+@@ aAL؜^]Ap 10;Veaa& FA$ B@B@x@ADAE{aLR`a =,`=$_@@dA轀  B@B@x@ADAExaL) Aaaa =="@z@&/* ny B@B@x@ADAEY4abqa = ;'`= +@m2@  nQUF+3b 6/괌/a& D1  B@B@x@ADAE쀈ra =82@ @a/@ @+=A. m B@B7@x@ADAE逈; Om =>a"7@B@g̥ͥ~/7ZP:a9l/C  B@Bg@x@ADAE]?aLa =֨I`="@2@d  B@BW@x@ADAE}ZJaL a =Qd= +@[@ +ՙ/,~ B@B@x@ADAEKaa =V`="@@ Ġr4eߡOdZU^צZHWf`4<@ *D$A B@B@x@ADAEn΀a =@逃aa"@@dg  B@B@x@ADAERˀ a =Հ= @̀@ /Jw  B@B@x@ADAEنbbWa =m`="@턀@ Ġȭwɷ DvdDS+^5)a& xDY B@B @x@ADAEC?naLa =x`= A@݁@dA}b  B@BA@x@ADAEaL !!a ='= @@PA/2Cn"f5 B@B@x@ADAEXـ"1a =a"@l׀@ +<8 4Ew8u=QjXT"a& Aր$+ B@Bt@x@ADAEÑaL2@a =ݾ`=$_`3@]Ԁ@dAӀ  B@B@x@ADAEaL AAa =p= @׏@ W/Rt C B@B@x@ADAE-JaBQa =P`="@AH@&A>|uf5}g  B@B@x@ADAEQpaL) a =z= A@q@ W/]~ + B@B@x@ADAE+aa =2`="+@)@ +#!'# u;ŊM _, 5/6NrX  B@B+@x@ADA EC a! =/ $_"@&@ # @>#< $ B@B@x@ADA%E& @Y a& = +="'@W@&/x( ) B@BW@x@ADA*Eba+ =U`=",@@ \TX*㈍3jsQ̗z7%ӭja&  J-- . B@B@x@ADA/EUaLa0 =`=$_1@@ @>A2 3 B@B@x@ADA4EQaL a5 =[=+'u6@,S@ W&+ @$/C7R8 B@BA@x@ADA9E aa: =3)`=!I;@ @&A de^^ (lԁ2PgJkL!a& r<-= B@B+@x@ADA>E)aLa? =+4`=$_@@@ # @=`A `@  B B@B@x@? =AC @E€ aD =̀="E@Ā@ !#V/xVDFmÀG B@DBW@x@ADAHEX~5bAaI =@`="J@l|@ +A{RBs=p9b na& AK{$L B@B+@x@ADAME6AaL#AN =K`="O@[y@ !>APx Q B@B@x@ADARE3LaL aS =!o==$_+T@4@  @ /[CUB +V B@B@x@ADAWE- aX =Wa"Y@A@&0) Am3jsUA|NWa& bAZ쀂+[ B@Bug@x@ADA\EXaL a] =b`=$_^@1@ ' @=A_适 ` B@B@x@ADAaE{caL !!ab =D="c@@&/Cde B@B@x@ADAfE`dak U@s"1ag =fo`="h@^@&-)3 䗷psDcUuQ63@ dbAi] +j B@B@x@ADAN6EmpaL2@al cz`=$_m@[@ PAnfZ o B@DB@x@AD>pEQ{aLAxAAaq =@%=+)r@@&-/NCs t B@B@x@ADAuEЁ BQav =׆a"w@΀@ #!uԁw)esS۟΀{b?a& 7xW$+y B@BJw@x@ADAzEBaL-R`a{ =ԑ`= |@ˀ@ PA};A[~ B@B@x@ADAE&aLaaa ==+"@V@ ! @/  B@B@x@ADAEAabqa =aH`="@?@ ; ~q*1AYz|oK'?F+, A B@B}+@x@ADAE ra = UEa"@<@ !$> B@B@x@ADAE a =$_`3@+~ /VC B@B|@x@ADAELa =2'@@ #!W#}{M]HN,~ml]H\a& bA$ B@B<@x@ADAEjaLa =*`="@@ #>欀  B@B@x@ADAEgaL) a =q="@i@&/Clh B@B@x@ADAEW#aa =!A *`="@k!@ +W]o^> o, 7~3 YA e#XF B@B+@x@ADAEۀa =&$_@[@ ! @+=$  B@BA@x@ADAE a =w=+'u@ـ@ &+ @/SuCB  B@B@x@ADAE,ba =ٚ`="@@@ #Az:Je[KGxt/pz?،$"Hռa& ȅA$ B@BP@x@ADAELaLa = ؗ`=+$_@4@ # @=  B@B W@x@ADAE{IaL kfa = `="@@ @W%[qאx+RQ&P3WCb Qwbt! MC@2 B@B @x@ADAElA =r"@@ $ @QC >e`  B@B@x@ADAEP Axa =@Ā="@@ @/ W B@B@x@ADAEuba = |`="@s@ "Y&+ `HHy#݈3#^+ l}C]W@2 B@Bo + m@x@ADAEA.aL a =y`=$_@p@ ! @>;  B@B@x@ADAE%+aL) !!a =4="@U,@ &+/QC  B@Bq@x@ADAE "1a =\*a"@@ #aFIٮ8<D Pe&A, A B@BJ@x@ADAE+aL2@a = T5`="@@ m#>A  B@B@x@ADAE6aLAAa =ʥ="@*@A/C B@B@x@ADAEW7aBQa =.^B`=$_@U@ 'I9&hG]gzc7,pJ}(+M+՘sa& A  B@B@x@ADAECaLR`a =)[M`=$_@R@ ! @JAQ  B@B@x@ADAE NaL Aaaa == @@Ġ/Cl  B@B@x@ADAEVȀ}qa =!A Ya"@jƀ@ +#!҃Joaڵ͍ dŀ$+ B@B+@x@ADAEZaLAra =d`=+$_@ZÀ@ PA A[m9  B@x@ADAE}eaL/a =y="@~@ !/DE$ B@B@x@ADAE,9faAa =?q`=!I@@7@ +w쵙" ?NNL@>`lsiqa& #A 6$ B@B@x@ADA Ea = <| @04@ ! @+=:3  B@B@x@ADAEz Aa =C="@@ W/SC( B@B@x@ADAE}ba =`="@@&GjyH>0ob A$ B@B@x@ADAEkbaLa =`=+$_+@@ `@>e A B@B@x@ADAEO_aLbH a =i="!@`@ !'/BC" # B@B@x@ADA$Eaa% =!`=!I&@@ ' @/ёőG-hΟa^_"'V ( B@B@x@ADA)EAӁ a* =a +@@ ! @=A,: - B@B}@x@ADA.E%Ё  a/ =ـ="0@Uр@ / D1 2 B@B@x@ADA3Eba4 =g`="5@@҃F%öyt8E32//=1wa& z6+-7 B@B@x@ADA8EDaLA9 =S`=$_:@@ # @>A;< B@B@x@ADA=E@aL a> =J= ?@*B@ C! @A//D@AA B@B"Y@x? 9ADAB @EaC = ?=)a"AD@`@ 'fJMe| "GD N iAE@2F B@B@x@ADAGE봁L aH = {)aI$_I@`@ g! @>AJ K B@B@x@ADALEϱL !!aM =="N@@ /)OkP B@B@x@ADAQEVmb"1aR =t`="+S@jk@&$z"`BDC=(F;I Ea& LDTj$U B@BP@x@ADAVE%aL2@aW = p`=$_X@Zh@ @>AYg Z B@B@x@ADA[E"aL) AAa\ =m,="]@#@&$/`C^@_ B@B$@x@ADA`E+ށ BQaa =a +b@?܀@ ' @Am)VPZu'ض=SEy=tx7O }ہcۀ d B@B@x@ADAeEaLR`af =@$_g@/ـ@dAhؠ@i B@B @x@ADAjEy aL aaak =F="l@@ A/Dmn B@B@xl 9ADAo @EO abqap = 5=U`="+q@M@ +Qz"$k:_ $ArL s B@DBt @x@ADAtEkaLrau = R `= v@J@dAwdI x B@B@x@ADAyEO!aL az = = {@@ W//| } B@B@x@ADA~Eտ a =,a"+@齀@&&W%ȃlO1+ ~]+DU$+ B@B@x@ADAE@x-aLa =~7`= @ں@c9 A B@B@x@ADAE$u8aLa =~= @Tv@ /<~  B@B@x@ADAE09aAa = W7D`="+@.@ +A+5#sZ)K8ش&F؊S VO+ B@B+@x@ADAE a =S4Oa @+@dAA[ B@B@x@ADAE  Aa =="@)@ W/G怂 B@B@x@ADAEPba = -[`= +@@&]>5Tr SN8l1.Vg[$"A$ B@B@x@ADAEY\aL%oa = {,f`= @@dA蛠 B@B@x@ADAEVgaL) a =`= @W@&/Cj B@B@x@ADAEUhaa =s`="+@i@ A#&M8lFJ+ R + j*DA  B@B@x@ADAEʀ" = ~ @Y @dA  B@BA@x@ADAE a =lр="@Ȁ@ A/C@  B@B@x@ADAE*bpHa = ډ`="@B@&ƨ[ԑvtLϋI_}v A$+ B@B@x@ADAE;aL a =҆`= @/~@dA}  B@B@x@ADAEy8aL !!a =AB="A@9@&/wCA B@Bs:@x@ADAE $"1a =a @@&|˽Bj!XJ>׆97(vL2 a& A + B@Bm@x@ADAEjaL2@a =`= @@dAcA[ B@B@x@ADAENaL AAa =="@~@ A/C - B@B@x@ADAEdaBQa =k`="@b@ +Gk=~u4"^U"Ʋ֑uBKrY{a& vU B@B@x@ADAE?aLR`a =}h`="@_@dA9  B@B@x@ADAE#aLqaqa =Z`="@Ӏ@i]&7aFT -= ^a&  D*  B@B@x@ADAEaLWra =R`="@Ѐ@d W B@B@x@ADAEaL? a =ɔ="@)@  + |1C B@BO@x@ADAEFaa =! /M`="@D@&?}v{1] g:} ^AC  B@B@x@ADAEa ='J W@A@d@A[ B@B@x@ADAE a =a+ m@~&Ġ/JCj  B@BO@x@ADA:  ETLa = a"@h@A-^VY"$:#O+um3ԯZ@ _cAԴ$+ B@DB+@x` =A @Eo@Wa = `= @X@dAA B@DB@x@ADA El aL a =sv=" @m@ A/OC ?A B@B@x@ADAE*( aAa =.`="@>&@ Ġa9C$w_nxW& a& A%$ B@B @x@ADAEa =+"a"@-#@ #$=(" A B@B@x@ADAEx݀6ha =Q= A@ހ@%/9li +A B@B@x@ADAE#ba =.`=" @@ $ĠS81(;k`3_(ȯg xWxD! " B@B@x@ADA#EiQ/aLA$ =9`=$_%@@ ! @>C&c ' B@B@x@ADA(EMN:aL Aa) =X="*@}O@ :&+$/7C+ A, B@Bu@x@ADA-E ;aa. =F`="/@@ #+)B۠cl= Mdht}?0T 1 B@B@x@ADA2E?  a3 =| Q$_4@@ # @=A58 6 B@B C@x@ADA7E# A!!a8 =Ȁ=+"9@W@&$/?:á ; B@B{ @x@ADA<EzRb"1a= =Z]`=">@x@&$ոgw?7raO mu; DiIa& Zr?)-+@ B@By@x@ADAAE3^aL2@aB =V~h`=$_C@u@ &+ @>AD A[E B@B}@x@ADAFE/iaL AAAaG =9="H@(1@%/rI0J B@BW@x@ADAKE~뀈BQaL =*ta"M@@ #&lia\G%/paE#a& ; N耂 +O B@B}@x@ADAPEuaLR`aQ ='`="R@@ PAS堂A[T B@B +'XZ@x@ADAUE͠aL aaaV =="+W@@ ! @K/:XiY B@B@x@ADAZET\abqa[ =c`="\@hZ@ qK^Ӥ&$jʑ&U#hFa& A5]Y$^ B@BJ@x@ADA_EaLra` =!``=)a@\W@ ! @>AbV `ݟ/c B@B@x@ADAdEaL) ae =!o="f@@ W/Gag>h B@BW@x@ADAiE)́ aj =ӣa"k@=ˀ@ +#+AQ% L6J5CJҙe]$:,((Tlʀ Am B@B+@x@ADAnEaLao =Ю`=$_p@-Ȁ@ # @>qǀ r B@BA@x@ADAsEwaL at =A=+"u@@ ! @ /Cv w B@B@x@ADAxE=aay =D`="z@<@&$v55?IAb8  B@B@x@ADAEM$a =!=$_@@&$/9C  B@B@x@ADAEӮbCa =`="@묀@&A yJ0ڦ\kj!mid$XGe"a& zLW-0 @ B@B+@x@ADAE>gaLa =`=+$_@۩@ ' @=;A[a@၊ B@B@x@ADAE"daLa = m= @Ve@ ! @A/(zL¡$ B@B@x@ADAEao#a =U&`="@@ `'lIp8©a l3]{v]a& Mд-@0 B@Bm@x@ADAE؁ $ zA =Q#$_@@ ! @>A  B@B@x@ADAEԁ  A! =ހ="@'ր@ /jCՠ  B@Bq g@x@ADAE~b4! =*@"@@ m#z6PrEpA\&ͨ#Gf@ AǶ B@B+@x@ADAEHaL A =& `=+$_@@ @>A⊀  B@B@x@ADAEE aL !!A =O=G@b@F@&A/P9Ch B@B @x@ADAESa"1A =`="@g`@ LD{D]/G8tjoS%V VWa& Lm  B@B@x@ADAEL2@A =!$aI+$_@[#`@ ! @>A  B@B@x@ADAEL AA' =j='u@ҷ@ A/m> B@B@x@ADAE(r%b"! =x0`="@Aaݠ  B@B@x@ADAELSaL A =!="@|@ /  B@B@x@ADAESTaA =Z_`="@Q@ #7YOL Ik%xg ga& F+S B@B@x@ADAE= `aLA =Wj`="@N@ #>7  B@B8@x@ADAE! kaL) A =="@Q @ !A/C  B@B@x@ADAEā A =Uva!I@€@  #D_D/NZf=jb{z;Ψ(a zYA( A B@B@x@ADAE}waLA =Pȁ`=$_@@ ! @JA  B@BA@x@ADAEyaLqA =9<`= @3@&li&!?׭0/xmt'a& C2  B@Bli@x@ADAE퀈A;%9"@0@ &+ @>/  B@B`x@9 A @E A =="@@ $#Vli/Ch  B@DB@x@ADA ERbA =`=" @f`@&.(GղW`MI R%a& %A ңCA B@B@x@ADAE^a W-5A =`=$_@W@ # @=A[ B@B@x@ADAE[aL  A =ue="@\`~ !. C= B@B@x@ADAE(a Wa =`="@<@ '# ,Vx% EPD VwèľCK 3H{oA$ B@B$@x@ADAEπ a =a"@+@ PA  A! B@B}@x@ADA"Ev̀) !!a# =Bր="$@̀@ :/RCC%̀& B@B@x@ADA'Eb"1a( =`=%)@@&uk̀-NSv<,L<Va& PXA*} + B@B@x@ADA,Eg@aL2@a- =`=).@@J @JA/aA[A0 B@B@x@ADA1EK=aL AAIA2 =G="3@{>@&/tC4 5 B@BC@x@ADA6E BQa7 =a"8@@ dAEWfxC#J׿.ݲ2+

    6 A? B@B@x@ADA@E!aL aaDA == B@Q@ `@E.CC D B@B@x@ADAEEi@TbqaF =Tp:Q G@g@ :2>$}jtk[[тxw gH'-I B@B:@x@ADAJE"aLAraK =Pm`="AL@d@dAM N B@B@x@ADAOEaLaP =(="Q@& @ /8RS B@B@x@ADATE}ڀAaU =)a"V@؀@ }@|5b߻ 'u֮ W׀ AX B@Bg@x@ADAYEaLaZ =%`="[@Հ@dA\ԠA[] B@B 1@x@ADA^Eˏ&aL Aa_ = `="`@@ W/?agb B@BA@x@ADAcERK'aad =R2`= Ae@fI@%o$oUL9d'ZDeTMWIJfH$g B@B%o@x@ADAhE3aLai =N=`= j@VF@dAkEA[l B@B@x@ADAmE>aL) an = u = o@@&/Cp<q B@B@x@ADArE' as =Ia"+t@;@ -&A 201$#e>l9eAu Av B@BR.@x@ADAwEtJaLax =ϿT`= y@+@dAz { B@B@x@ADA|EvqUaL a} =F{= ~@r@ A/PIC B@B@x@ADAE,Val  Ua =3a`="+@+@!Ahh KäbʆfRXEp0:W@ PA|*@1 B@B[@x@ADAEg倈" =0la @(@dA`'A[ B@B@x@ADAEK @sa =@="@{@$/C  B@B+@x@ADAEѝmba =x`=!I:@囀@ mAfW'ÛSYpa,W{da& 8AQ$ B@BP@x@ADAEJ  B@B@x@ADAEÁ ) AAa =̀="@%ŀ@&/CĀ B@B@x@ADAE|bBQa =1`="+@}@&^0B3Z@a`J a& g|  B@B@x@ADAE7aLR`a =$`=$_@z@dAy  B@B@x@ADAE4aL aaa = >="@5@ A/gf B@B@x@ADAEQ bqa = a"@e@ #O=<>*g&gA+Sa& F+퀂  B@B@x@ADAEaLra =`= @V@dAꠂ A B@B@x@ADAEaL a = t=+ W@Ц@g-&/C< B@B@x@ADAE&aaa =g`= @:_@ m^4Kv<f5u_g (]!(a& EA^$+ B@B@x@ADAEaLa =d`= @.\@dA[A[ B@B@x@ADAEuaL a =A ="@@ A/iC B@B@x@ADAEс Aa =a"@Ѐ@ P~1q`Vx? m j(ta% q(|π$ B@Ba@x@ADAEfaLa =`="@̀@dA_̀$ B@BA@x@ADAEJaL a =!A= @z@&/uD  B@Bg@x@ADAEBaa =I@"@@@ v'A!<>7pzKMNh@ ]Q B@B+@x@ADAE; a =yFa @=@dA5  B@B@x@ADAE @Y a =a"@O~&A/  B@BW@x@ADAELa =N'@@&l:H`y, y,JtjSx^a& #nF+& @J  B@Bp W c'@x@ADAElaL A#1 ='`= <@@ ,W @>  B@BA@x@ADAEh(aL a =r=+"@%j@ @҃/BCi!&Ţ B@B@x@ADAE{$)aa =(+4`=!I @"@ PAddDSwy-2P2a& iA !- B@B@x@ADA E܀ a =#(?$_@@ ! @>  B@B@x@ADAE !!a =="@ڀ@ }&+#V/iAf  B@Bg@x@ADAEP@b"1a =K`="@d@Aӂr "aD}4؉bQÊ>В@.Y B@BA@x@ADAEMLaL2@a =V`=$_@3H@T@ # @= A[ B@B@x@ADA!EJWaLqAQa" =AV c`="#@:@ !ZZr5YGp%kF6f B[ć!a& IA$$% B@ B@x@ADA&ER`a' = na"(@*@ P) * B@B@x@ADA+Et aaa, =Aŀ="-@@ !B//C.+/ B@B@x@ADA0Evobbqa1 =}z`="2@u@ War(ľ{f'V{a-Ra& dA3{t 4 B@BA@x@ADA5Ef/{aLWra6 =z`="7@q@ !M8_ 9 B@B@x@ADA:EI,aL a; =6=$_A<@y-@ /7HC= > B@Bm@x@ADA?E a@ =a"A@@ +#!QC A'ڑBfwz+eR0N03 + x@GBP C B@BA@x@ADADE;aLaE =x`=+$_F@@ # @>G4 H B@BW@x@ADAIEaL aJ =릀="K@O@ W! @ՙ/fTCL M B@B@x@ADANEXaaO =M_`="P@V@   DU22Wf{$  ֐^xa& '`Q%@0mR B@B@x@ADASEaLaT =N\`=+$_U@S@ @>V A[W B@B@x@ADAXE aLaY ==$_Z@$@ /`[\ B@B@x@ADA]E{ɀAa^ ='a"_@ǀ@$Ats}!9QLdfA]ޟ4Af ްȣa& OF+`ƀ$a B@B@x@ADAbEaLac =#`=$_d@Ā@ @>AeàA[f B@B g1@x@ADAgE~aL Aah =="i@@&/Cjek B@B@x@ADAlEP:aq am = A`="n@h8@ĠMJ@h'&z;R?va& Ao7$p B@B@x@ADAqEAr ==a"s@W5@ *>At4 Au B@B@x@ADAvE) aw =s= Ax@@.0Cy:z B@B@x@ADA{E%bAa| =ұ`="}@9@ +#!K˛IWc>W>B)e A~  B@BA@x@ADAEcaL a =ͮ`=+$_A@)@ ,W @>A  B@BA@x@ADAEt`aL !!a =A^A[ B@B@x@ADAEIс  AAa =ۀ="@yҀ@ / C  B@B@x@ADAEόbBQa =`="@㊀@ #+AQ07e@߱)ު0 p5 L AAO A B@B@x@ADAE:EaLCR`a =x)`=$_@ԇ@ # @>A3  A B@B@x@ADAEB*aLaaa =K="@NC@ !A/\mC  B@B@x@ADAE bqa =M6a"@5`@ +iKsicӮ=BcKP`e"a& q3R%@0 B@B+@x@ADAELra =AaI"@@`@ !>A  B@B@x@ADAEL@Y Aa ==$_+@'@ /9~ B@B@x@ADAEznBba ='uM`="@l@&nLU\QF#qLb#V ymBa& F+k  B@Bm@x@ADAE&NaLAa ="rX`=$_@~i@ @JAh  B@B@x@ADAE#YaL a =-="@$@&/OCd B@B@x@ADAEO߁ a =da"@c݀@&SyrQj.Gٝ>'k Ѵ.a& om܀  B@B@x@ADAEeaLa =o`=$_@Sڀ@ PAـ  B@B@x@ADAEpaL a =j= A@Ε@& /u: B@B @x@ADAE$Pqaa =V|`= @8N@&x f*9.w>ooyAa& aM$+ B@BA@x@ADAE}aLa =S`= @)K@ ,W @JAJA[Y B@B@x@ADAEsaL a =7="@@#V/g B@B@x@ADAE Aa =Ǔa"@@ +'$ΨX#\A>F'd$X$+P/]A[ B@B6h@x@ADAEHvaL a =="@xw@ &+/ʳC W B@B@x@ADAE1aa =|8`=!IW@/@ m# @+{ك@c` 1uvAa& /AO+ B@BA@x@ADAE9  a =w5$_@,@ # @>3 A B@B@x@ADAE @Y) !!a =="@M@ W!/ٜC=  B@B@x@ADAEb"1a =P`="@@ הiǃ~8L6d4شv]܅5Ba& !A$ + B@B@x@ADAE[aL2@a =L`=$_ @@ ! @>A   B@B@x@ADA EWaL AAa =a=$_@#Y@ /l CX B@B@x@ADAEyaBQa =.`="A@@ #!лTdjϢ$|駪vVA  B@B@x@ADAEȀ aaa =Ҁ="@ɀ@B҃/d B@BM@x@ADA ENbbqa! =`=""@b@!=_ C=n8 ͞JzF+#΁@4$ B@B@x@ADA%EnI/D?HS@ w`A2$3 B@B[p@x@ADA4E aLa5 =`=$_6@(@dA7A[8 B@B@x@ADA9EraL6ha: =l `=";@ d@ WnIJjg3 Եdy[IGд^C<yc = B@Bt  B@x@ADA>Ed!aLWa? =i+`= @@`@ +$ @JA] B B@B@x@ADACEG,aL? aD =%= +E@x@+0/CF G B@B@x@ADAHEց aI =7a+ J@Ԁ@ ' X]U~tz)x a& xAKN L B@B @x@ADAME98aL aN =zB`=$_O@р@dP6 Q B@B@x@ADARECaL i AxaS =@="T@M@&Ġ/CU V B@B@x@ADAWEGDaaX =\NO`= Y@E@&h(_[![pC8:Joa& =BZ#-[ B@B@x@ADA\EPaL"] =PKZ`= ^@B@dA_A[` B@B}@x@ADAaE ab =[a"c@"~ *#VĠ/˴Dde B@B@x@ADAfEyL-&ag =)fa!Ih@@ Qgbm4} ڊ c](ϝ1Ai$j B@B@x@ADAkEpgaL al =!q`= m@}@dAnݲA[o B@B@x@ADApEmraL) A!!aq =w= r@n@ /ǬCsct B@B@x@ADAuEN)sa"1av =/~`="+w@b'@&X,V-K8)I62ibm1Ya& Ax& y B@B@x@ADAzEဈ2@a{ =,a |@R$@dA}# ~ B@B@x@ADAEހ AAa =i="@߀@ /3C8 B@BH@x@ADAE#bBQa =ܠ`= +@7@ ACݘ`O2~g0[A  B@B@x@ADAERaL$R`a =ϝ`= @+@dA A B@B@x@ADAErOaL aaa =FY="@P@&/wC B@B@x@ADAE abqa =`= @ @ >2 FR"(*\2M|Y8ia& x$+ B@Bu X@x@ADAEcÀra =a @@dA\!FGA B@B$@x@ADAEG  a =ʀ="@w@҃/  B@B@x@ADAE{ba =`= @y@ >=e<4 61½1 ;>a& F+M A B@B @x@ADAE84aLa =v`= @v@dA1  B@B@x@ADAE1aLa =:= @L2@ /\C  B@B@x@ADAE a =Oa"+@@&q>UX=,>9hhRy3a& A# B@B@x@ADAE aLa =K`= @@dA  B@B@x@ADAEaL) Aa =="@!@&/k B@BA@x@ADAEx]aa =0d`="@[@&˘(o \y#4ʺDZ A B@B@x@ADAEaLa = a`= @|X@dAW * B@BA@x@ADAEaL6ha = =+ A@@>/~Cg  B@B@x@ADAEM΁ a = @ @à@&(ťSңɸb">s @3Zo's@ Aˀ$ B@B@x@ADAE aLA =`= @Qɀ@dAȀ  B@B@x@ADAEaL Aa =l= @̄@ /C8A B@B@x@ =A @E"?aa =E"`="@6=@&$ؕ7z!VF^CN}SYd"soA<$ B@DB$@x@ADAE$ a =B-a @&:@ 4 @>C9 A B@B@x@ADAEq@Y !!a =>= @@ /$ B@B@x@ADAE.bA"1a =9`="@ @ #!vQԭE~cJY-n[a& 9Dx$ B@Bm@x@ADAEbh:aL2@a =D`=$_ 4@@ # @>C\ A B@B@x@ADAEFeEaL) AAa = o="@vf@&/ύC A B@B@x@ADAE FaBQa =}'Q`="@@&y%q-/DP~}]&Ênͨzh@ iAM m(B@x@ADAE7ف R`a =@逃y$\a"@@ &+>A1  B@B[p@x@ADAE֡ @YAaaa =߀='u+@K׀@&/   B@B@x@ADA E]bbqa =Kh`=" @@!A0VёuYw3ӳQ{q&( m #ja& "  B@B(@x@ADA@@E JiaL-ra Ns`=$_@@ PA  B@DB@x@ADAEFtaL Aa =P="@!H@ ! @$/}GA[ B@B(@x@ADAEwuaa =4 `="@@&?/d7BQPds9/*A%5a& #IA`- B@B@x@ADAE⺁ Aa = ` !@{`@ PA" # B@B@x@ADA$EƷL a% ==$_&@@&/ըC'b( B@B@x@ADA)EMsbAa* =z`="+@`q@ #!+K҃Lr'Qv\gԶF3a& A,p A- B@B3R@x@ADA.E+aLa/ = v`= 0@Qn@ ' @>1m 2 B@B@x@ADA3E(aL@Y) a4 =o2="5@)@ !A/m6;$7 B@B@x@ADA8E"  a9 =a":@:@& 'މ2Y/sRr;w>a#f!ް0];D;ဂ$< B@B@x@ADA=EaLa> =`="?@)߀@ &+>@ހ AA B@B$@x@ADABEpaLaC ===$_AD@@&/nCE +F B@B@x@ADAGETaaH =[`="I@S@ #!A)fx;|Q*({h&}c\$Ua& AJ{R K B@B@x@ADALEb aL zAM =X`=+$_N@O@ ' @>AO[ P B@BA@x@ADAQEF aL A,CAR = ="S@v @ ! @ /YCT U B@B@x@ADAVEŁ AW =|a"X@À@ ʠ!MpOl}F-K?Nja& AYLZ B@B@x@ADA[E7~aL A\ = t`=$_]@@ ! @>A^0 _ B@B@x@ADA`E{aL; !1Aa =R=`="b@4@  ਻X'P?TQt$U_!9Y?͞c!$Ad B@B@x@ADAeE 2@Af =J:@"g@1@ ! @>h@ȰoĠi B@B@x@ADAjE  AA)Ak =='ul@ @ $ @K; /QF+m쀂n B@B@x@ADAoEwbB!!p = + `="q@@JĠ4zBnvd0L]^y Iu NAr$s B@B@x@ADAtE_ aLR`Au =#`="v@~@ #>wۡ x B@B}@x@ADAyE\aL) aaAz =f`="{@]@ !/C|af5} B@B@x@ADA~ELaLbqA =$`=!I+@`@Ġ-9DAi4@r֤89FA  B@B6h@x@ADAEЀrA =/$_@P@ PA  B@B@x@ADAÈ A =c׀="@΀@A/pjC6 B@Bv@x@ADAE!0bA =ُ;`="@5@ #$)`C B{>"I.g65Rrv + g  B@BC@x@ADAEAA  B@B@x@ADAEp>GaL A>DHG`="+>@?G`@ 1! @Ġ/5>  B@B@x@ADAEGa A> S`="A@ R`@ 'g+%hQJEvV5TUe -Dv@2j@ B@B @x@ADAEaLA =]aI$_@@ @>ZA[ B@B@x@ADAEE^aL A =="@u@&A'.ۿC  B@BA@x@ADAEj_aAA =xqj`="@h@&CQbCv@>֐fNr|a& K A B@B@x@ADAE6#kaLA = tnu`="@e@ '>A/ B@B@x@ADAE vaLA =)="@J!@&/  B@B@x@ADAEہ A =Na*`3@ـ@& ]c׷:i bz/ҁ:3. 7F+! B@B@x@ADAE aL@8 =Iߌ`=$_@ր@ * @JA A B@B@x@ADAEaL) A A =="@@&/6C B@B@x@ADAEvLaa =&S`="@J@ P#$$6VVՓ3=Zn[*R 'AI A B@BP@x@ADAEaL C =P`=$_@zG@ PAF  B@BA@x@ADAEaL !!a = ="@@"Y҃/: Ca B@B@x@ADAEK "1C =İa @_@3RĠ-m5 G,V4V.ӛ_4g;0 A˺$ B@B@x@ADAEuaL2@a =`= @P@ &+ @+=g  B@B@x@ADAEraL AAa =v|= @s@  `@/^C6 B@B@x@ADAE .a$BQa = 4`="@4,@ #!+hBNx5ݨz̧yP \A+$ B@B@x@ADAE怈R`a = 1a+$_@()@ # @>( B@B @x@ADAEo〈 aaa =7="@@ !/ pC  B@B@x@ADAEbAbqa =`=!I@ @&&p7Mfu V a& VqAv$ B@B@x@ADAE`WaLra = `=$_@@& @>AZ B@B@x@ADA?  EDTaL) a = ^= @tU@&/]C  B@DB@x` =A @Eaa = ?=w`="+@ @&S]Jg_!)g_KDN =# lAK  B@B@x@ADA E5ȁ a =s@$_ @ @dA /  B@B@x@ADAEš @Y a =΀="@Iƀ@&/q@C  B@B@x@ADAEbR = U`= +@~@ `@҃F f՟$HAMmsSg,݅ A  B@B@x@ADAE 9aLa = L`= @{@dA  B@B@x@ADAE5aL a =?=" @7@ /sC!6" B@B@x@ADA#Eua$ =*%a %@@&瑘dzK ـ9Ӷ"V)|: S)A&-' B@B-&@x@ADA(E&aLAa) = 0`= *@y@dA+렂 , B@B$@x@ADA-EĦ1aL a. =="/@@ /C0`1 B@B@x@ADA2EKb2aAa3 = h=`= 4@_`@&x+мGݡ"g؈78e y ΃A5_ +6 B@B$@x@ADA7E>aLJ8? eH`= 9@O]@ :* @=:\ A; B@B@x@ADA@@&/8C?5@ B@B@x@ADAAE Ӂ aB = Ta"+C@4р@&[5fΝ.{Lv8:"O;D4 UADЀ@1oE B@B@x@ADAFEUaL aG = _`=G d_H@$΀@dAÌ ` @ iFJ B@B@x@ADAKEn`aL) !!aL =6="M@@&/CN O B@B@x@ADAPECaa"1aQ =Jl`= +R@ B@ ' @҃Қ$E;֊d-6$MX*SuA AT B@B@x@ADAUE`2@aV = Gwa)W@>@dAXY dY B@BA@x@ADAZED @Y AAa[ = xa"\@t~ /QD] ^ B@B#@x@ADA_EʴLBQa` ={a$_a@޲@&Jh.g!5?X & 'cRFK|N@ jAbJc B@Bt @x@ADAdE5maLR`ae =r`= f@ί@dAg. h B@B@x@ADAiEjaL aaaj =s= k@Ik@&J/j(l m B@BJ@x@ADAnE%a$bqao =P,`="+p@#@&5zDlK W(`OПga& Dq$r B@B@x@ADAsE ށ rat =H) u@ @dAv w B@B@x@ADAxEځ 6hay =a"z@@ AWkS6) nsB^sfz#y2Q̋$C{@.C!]` W| B@B#@x@ADA}ENaLa~ =`= @y@dِ  B@B@x@ADAEKaL a =U="@L@ m/ҢC_A[c @ B@B@x@ADAEJaWa = `="@^@&@rlsqLJ¾!ԹS'ܻIa& A@0 B@B@x@ADAEa = a"@R@ #$C >: ` B@B6h@x@ADAE a =]ƀ= W@Ƚ@&/>8A4A[ B@B@x@ADAExba =~`="@3v@ Ae>3vs^e[ -͑  Na& +|Au A B@B@x@ADAE0aLa ={`=$_`3%o@#s@ @>r  B@BW@x@ADAEn-aL a =27= @.@+ @A/PC  B@B@x@ADAE a =a"@@k5-T~=ORJehTT0ȷ#aU Ct怂$+ B@B}  T@x@ADAE_aLAA =@$_@@ PAXA[ B@B@x@ADAECaLbH a ==$_@s@9~/sC GāB6h@x@ADA @EYaAa =``="@W@7Ġ/*lSVY1g-MV#oNk` eF+I A B@DB@x@ADAE4aL6h a =r]`= @T@ * @>A-A[ B@B@x@ADAEaL!!a =="@H@&/C  B@B@x@ADAEʁ "1a =K'a"@Ȁ@gĠmv&v4GF UקU8SԊA B@B}@x@ADAE (aL2@a =G2`="@ŀ@ PA  B@B$@x@ADAE3aL) AAAa == A@@A/"C!]f5 B@B@x@ADAEt;4aBQa =$B?`="@9@ '!ĠcÄz($-\͊BIJP_ ia& A8  B@B@x@ADAER`a =?J)W@x6@ ! @=`5  B@BA@x@ADAE aaa =="@@ }&+/V_  B@B@x@ADAEIKbbqa =V`="@]@ +#(nDr"(ٌ M2 a&  nDɩ$ B@B+@x@ADAEdWaLra =a`=$_@M@ # @>  B@B@x@ADAEabaL a =ak="A@b@ W!/BC4A B@B@x@ADAEca$a =#n`="@2@ 6a۠>Fg*ȶ}#/rv$źa& fA@0j@ B@B@x@ADAEՀa = y$_@#@ @>AA[ B@B@x@ADAEmҀ a =1܀=+$_@Ӏ@ /uJC  B@B@x@ADAEzbAa@@`="@@ PX+`GbܷXÛQmR"#^p iAt@2 B@B`x@9 A @E^FaLa =`=+$_@@ @>AX  B@DB@x@ADA EBCaL) a = M= @rD@ @҃/1iA   B@B@x@ADAE a =ya"@`@ m3O}v5v{` (hq;x\a& IF+I  B@Bw@x@ADAE3La =qaI$_@`@ ! @>A-  B@B$@x@ADAELa =轀="@G@ }A.C  B@B}@x@ADAEobo  +@a =bv`="@m@ &#+ |?9rǣ3 V2A " `"YXF! B@B@x@ADA"E (aLA# =Fs`=$_$@j@ # @>A% & B@B@x@ADA'E$aLAxa( =@.=+")@&@ ! @A/C*%+ B@BGa@x@ADA,Esa- =a".@ހ@ :ARxf+mK̄T@Ra& 6h/ݠ@00 B@Bp@x@ADA1EޘaLA a2 =`=$_3@xۀ@ @Q`B?A4ڠ `5 B@B@x@ADA6E•aL !!a7 ==+$_8@@ /B6h9^: B@B@x@ADA;EIQa"1a< =W`="=@]O@ Wuw4T*ƦUfv;`a& #>N A? B@Bu@x@ADA@E aL2@aA =T`="B@LL@>ACK D B@B@x@ADAEEaL AAaF =`= G@@ /#H3I B@Bt@x@ADAJE BQaK = a"L@2@&B+ ȖO=h&ahV H*CM$N B@B@x@ADAOEzaLR`aP =j8$_Q@"@ &+ @JAR AS B@B@x@ADA+x`ElwaL) aaaU =9="V@x@ /CWX B@DBy@x@ADAYE2abqaZ =9`="[@1@ #&l %zm!@@B؊3r!MJz}x5a& SA\s0 A] B@B|@x@ADA^E^뀈ra_ =6$_`@-@ # @>AaW b B@BA@x@ADAcEB 8 ad =="e@v@&/Cf g B@B@x@ADAhEȣbai =y)`=$_j@ܡ@ +Ϊ=)FfeO!g2EGΨv)$kH$+l@aBx@x@ADAm@ B 3\*aLan =p4`=$_o@̞@ ! @>p, q B@DB@x@ADArEY5aL as =b= t@GZ@ W&+ @$/-Du v B@B@x@ADAwE6alax =NA`="y@@&|<ĺf9x#צ;oa& kAz${ B@B@x@ADA|É a} =FLa+$_~@@ # @=A[ B@B@x@ADAEɁ a =Ӏ="@ˀ@ !/"CʀA B@B@x@ADAEsMbka =#X`=!I@@ +' @|Ɩh d.Kꍌkư3Fa& A󂀂$ B@B@x@ADAE=YaLa =c`=$_`3*@w@ ! @=  B@B@x@ADAE:daL) Aa =D="@;@&$|-.C]A B@B:@x 9ADA @EH a =oa"@\@ #r6\ %xݻ0_,wg + B@DB@x@ADAEpaLA =z`="@L@ '>A  B@B@x@ADAE{aL a =m`="@3f+@1e`@Aՙob}O큍SzVbu<l,h[ɠtEDd  B@BA@x@ADAEa  a =AVj`="@%b@ $ @Ja  B@B g@x@ADAElaLAx!!a =@8&= @@/C B@B@x@ADAEׁ "1a =ޞa"@ր@*ĠnYЌa!G<_YkUa& XArՀ$A B@B-&@x@ADAE]aL(@a =۩`=+$_@Ҁ@ PVA[ B@B@x@ADAEAaL AAa =="@q@ &+$+Ġ/C  B@B-&@x@ADAEHaWBQa =uO`=!I@F@ W# @T+ A B@B@x@ADAE ) aaa ="@F~ /;C B@Bt 9~@x@ADAELbqa = Ua"+@@ 6hFҥFI5 D$3 }$ B@B+@x@ADAEraLra =E`="@@dA  B@B@x@ADAEnaL) a =x="@p@ }b/|Do B@B@x@ADAEr*aa =1`= +@(@ +^Z`惗O젤Ҩ1ʢa& A' A B@B+@x@ADAE Aa =.a)@v%@dA$  B@BA@x@ADAE a =="@@ W/LC]  B@BsA@x@ADAEGba =`= @[@ @W| ˡѝphԽb 5Z'N]Xka& Aǘ$ B@B@x@ADAESaLa = @K@ @=  B@B ,XE@x@ADAEPaL a =ZZ="+@Q@+ @/LdDZzڋC>vD zLDK $ B@B`@x@ADAEĀAa =$__ @%@ @=?  B@B@x@ADAEk a =<ˀ="@€@ A.DK B@B@x@ADAE| bAa =+`="+ @{@ gCd}UR(5pQXa& F+ rz$ B@B@x@ADA E\5,aLC[ =6`=$_@w@dAV  B@BA@x@ADAE@27aL) a =  <="@p3@ }/[  B@Bg@x@ADAE o a =Ba"@@ {xs3|F~GZB(@Da& DK + B@B+@x@ADAE1CaL a =sM`="@@dA+A[/ B@B@x@ADA!ENaL!!a" =⬀= +#@E@&/$ % B@B@x@ADA&E^OaA"1a' =YeZ`="(@\@ d0KF-n\gc6SCa& RD) * B@B P@x@ADA+E[aL2@a, =Dbe`= -@Y@dA."O` / B@B@x@ADA0Ef AAAa1 = {= 2@@&/l~C3 4 B@B@x@ADA5EqπBQa6 =1qa"7@̀@ 6hG$RL*X1g&U_ B@B@x@ADA?E}aL aaa@ == A@@ /`CB\C B@B@x@ADADEG@~aAbqaE =F`="F@Z>@ q“= t̩cDި{C {F AG= +H B@By@x@ADAIEraJ =Ca K@K;@dAL:A[M B@B@x@ADANE aO =f="P@@&$/!CQ1R B@B?@x@ADASEbaT =Է`="U@0@ CbLݴ]™", 4_ysN^5V$W B@B@x@ADAXEiaLaY =Ĵ`="Z@ @dA[ \ B@B$@x@ADA]EjfaL) a^ = ;p= A_@g@&/ `a B@B@x@ADAbE!aac =(`="d@ @&A֊8;pN\# *R$D~?Ca& {MF+eq Af B@B@x@ADAgE\ڀah =% Ai@@dAjU k B@BA@x@ADAlE@ׁ  am =  ="n@p؀@&-/aCo p B@B@x@ADAqEƒbar =r`="s@ڐ@&JQt#yeubxo7槾({f` AtFu B@Bg@x@ADAvE1KaLaw =n`=`} x@ʍ@ 0$Jy* z B@B@x@ADA{EHaL@YL a| =Q="A}@II@ }!)-/j:C~  B@B@x@ADAEa$a =L `=!I}@@&AT.FR]IPe[H\l"o[n$ B@B@x@ADAE " =Da$_@`@ `@>JA[+ B@B@x@ADAE긁L a =€="@@ /ioD B@BA@x@ADAEqtbAa =){`="@r@&E⇩b} J=^Ka& NAq$ B@B@x@ADAE,aL a =x "@to@ #$>Jn  B@B@x@ADAE) aL!!a =3= @*@&%o/2bC_ + B@B@x@ADAEF "1a =a"@Z@& vJIF 6!F OmA   B@B{$@x@ADAEaL@a = `=$_A@J@ PA߀  B@B@x@ADAE!aL AAAa =e="@ě@&$/C0A B@B@x@ADAEV"aBQa =\-`="@/T@&bAQ|l[>OTb]*5a& .S  B@Bm@x@ADAE.aLR`a =Y8`= @Q@ PAP  B@B@x@ADAEj 9aL6haqa =D`=+ A@ŀ@ @1vSpRr޺&ZKS o%]Ѱa& DpĠ@0 B@B-@x@ADAE[EaLra =O`= @@ PXA[ B@B AC@x@ADAE?|PaL a =="A@o}@ g! @Ga/`C  a B@B@x@ADAE7Qaa =z>\`="@5@AĠ꽡 ⪳YXSVN[ͿB wlAE  B@BA@x@ADAE0 Wa =n;ga"@2@ P)A[c @ B@B@x@ADAE a ==$_@D@ /  B@B}@x@ADAEhba =Ps`="@@ #!+^5S쎗}[2Tsi+Va& &D B@B@x@ADAEataLa =C~`= A@@ # @>:  B@BA@x@ADAE]aLa =g="@_@&/6C^ + B@B@x@ADAEpaCa =$ `="@@A(n`=!'[ia&*2D4XUD  B@B@x@ADAEрa =$_@x@ &+ @>:  B@BA@x@ADAE΀a =؀="@π@ &+/D_  B@B@x@ADAEEba =`=!I@]@ # @ĠhN2 ݮdb^,ݽ>\bT L uAɇ$ B@B@x@ADAEBaL z 9 퍭`=$_@I@ # @>A  B@B@x@ADAE?aL A 9 \I="@@@ @/CB0m B@B@x@ADAE A =a"@.`@&vJj̈=gd^|7_6h]A$ B@BDK@x@ADAELA A =aI+$_ @@ &+ @K=1 ~A[ B@B@x@ADA EiaL !!A =1="@@ &+/UqC B@B@x@ADAEka"1A =r`=!I@j@ +# @A#36R+{Y6Q*Qko3%8a& Api$ B@BY@x@ADAEZ$aL2@A = o`=$_@f@ # @>AT  B@B@x@ADAE>!aL) AA( = +="@n"@ !A/vF  B@B@x@ADA E܁ B"!! =ua""@ڀ@&s\WxJF#q|lIBOK}P#E $ B@B@x@ADA%E/aLR`A& =m`="'@׀@ &+>A() ) B@B@x@ADA*EaLaaA+ =ۛ=",@C@&A/ }- A. B@BA@x@ADA/EMabqA0 =ST@%1@K@ +# @%`C KA*PJ:A7G 8 B@B@x@ADA9E aL AA: = =";@@ ! @҃/YcC<= B@B@x@ADA>EoA? =a"@@@ Ad5NJaM@ #ַG2 jAAﻀ-+B B@BA@x@ADACEvaLAD ="`=$_E@s@ ! @>AFӸ G B@B@x@ADAHEs#aL AI =}=$_J@t@ W/VCKZL B@B@x@ADAMED/$aAN =5/`="AO@X-@ +#!AëҟnT2C!KkRa& v[AP, AQ B@B@x@ADARE瀈AAS =2:$_T@I*@ # @=$U) AV B@B@x@ADAWE䀈 AX =d="Y@@ !A.wCZ/[ B@B@x@ADA\E;bA] =¦F`="^@.@ k T&gS]<1aӬM2M7a& iS_@0` B@B@x@ADAaEXGaLAb =Q`=+$_c@@dAd~ e B@B@x@ADAfEhURaL) Ag =,_="h@V@ /WiSiA[j B@B@x@ADAkESaAl =^`=%m@@ 1%9- 4e`yxz(pDF+no o B@B@x@ADApEZɀ Aq =i r@ @ +# @JAsSA[t B@B@x@ADAuE=Ɓ   Av =Ѐ="w@nǀ@&/hCx #ay B@B@x@ADAzEājba{ =xu`="+|@@ $,Sϟ*:4w7\Iwa&  A}D ~ B@B@x@ADAE/:vaL a =l`=$_+@|@ ! @>(  B@B@x@ADAE7aL !!a =@= @C8@ &+ @/C  B@B@x@ADAE "1a =Fa"+@@&A`Lt jʿ]r ˇ=ˇ;ia& *\A- B@B+@x@ADAEaL2@a =B`=$_@@dA쀂  B@B@x@ADAE觘aLAAa ==$_@@ / B@B@x@ADAEocaABQa ='j`="+@a@ P a+$v(4B/ .>ó0$g{c~a& `$ B@B Lc'@x@ADAEaLR`a =g`= @s^@dA] lM@W B@B@x@ADAEaL Aaaa ="="@@ }/ Y W B@B@x@ADAEDԁ bqa =ڻa +@XҀ@ mк>xEt_.-kuJpa |5IAр B@B+@x@ADAEaLra =`= @Lπ@dAΠA[ B@B[p@x@ADAEaL a =[= @Š@&A/C. B@B@x@ADAEEaa =K`="+@-C@&X(HIef|.m6Ŏ=/a& oB  B@Bm@x@ADAEa =H @@@dA}? A B@BA@x@ADAEh W$ =8 @~&/ D  B@B@x@ADAELa =aG@ @@& WOGpNc"oHSa={An$+ B@B@x@ADAEYnaLAa =`=+ @@dAR  B@B@x@ADAE=kaL@Y a = u="@ml@& /Z A B@B@x@ADAE&aa =|-@ +@$@ m `@  qe\Fs Ao1B_0WyJgx@ DC + B@Bm@x@ADAE.߁ a =p* a @!@dA'!A[ B@B@x@ADAE܀; a =R @@ g8/vR&>[O2ԷL`d7?XC@4 B@B@x@ADAEPaL(J =A$`="@@d  B@B}@x@ADAEL%aL a =V= @N@ +/uCM B@B@x@ADAEn&aa = 1`="@@ĠSX'oHxj&`["0p޵"6 ѲA  B@Bu@x@ADAEW a = ލ/ǒc s<; < B@B@x@ADA=E a> =@@$_?@7@ ! @=@6 A B@BA@x@ADABE aC = =bD@@Ġ/BE F B@B@x@ADAGEmbaH =`="I@@&BGJQpV F4 b~J$ma& F+J-+K B@B@x@ADALEeaLaM =`=$_N@u@ `@>AOѧ P B@B}@x@ADAQEbaL aR =l='uS@c@&/aTXU B@B@x@ADAVEBaaW =$`="X@V@ `'!AFE+Q@¼8;Oea& TDY$Z B@BJ@x@ADA[Eր"\ =!a ]@F@ ! @=A^A[z_ B@BA@x@ADA`EӀ aa =b݀="b@Ԁ@ &+ @A/Cc-d B@B@x@ADAeEbaf =ȕ`="g@,@&Nq!5'ٚU=Fi{Dފ~a&  Ah$i B@BJ@x@ADAjEGaL ak =`=$_l@@ # @=m| n B@B@x@ADAoEfDaL) !!ap = 3N="q@E@&/Q%Crs B@B@x@ADAtE "1au =@"v@`@ '+A'^?w@kt{]V\}Ss@ 1Awm mx B@B@x@ADAyEWL2@az =aI"{@`@ !>A|Q } B@B@x@ADA~E;L AAa =="+@k@ &+ @+҃/C  B@Bu@x@ADAEpbBQa =sw`="@n@ +#ֺ+"FC @_Puç)͞B  B@B@x@ADAE-)aLR`a =jt&`=+$_@k@ # @=A&  B@B@x@ADAE&'aL aaa =/="@A'@ W! @Ġ/sD  B@B@x@ADAE bqa =S2a"@߀@ ?^ ?_ҬRב?@F 66A- B@Bv@x@ADAE3aLAra =@=`=+$_@܀@ ! @>A۠A[  B@B@x@ADAE>aLa = ="@@ /li B@B@x@ADAEmR?aAa =YJ`=%@P@ +# @ҏ:fF䖿ѰP9vO֬GDa& 5O  B@B+@x@ADAE KaLa =VU`=$_@qM@ # @>ALA[ B@B@x@ADAEVaL Aa =="@@ W!A/W B@B@x@ADAEBÁ a =aa"@V@*Ġ\ ~3f>N;ck#_W }IA$ B@B@x@ADAE{baLa =l`="@E@ &+>A A B@B@x@ADAExmaL) a =b=$_+@y@&/OQC, B@BM@x@ADAE4naa =:y`="@+2@ +#!hQ8Ƶ[/<&ZtR{Մ2+E`FA1 A B@BA@x@ADAE쀈a =7a+$_@/@ ' @>A{.  B@BA@x@ADAEf a =2="@@ W! @҃/ jC  B@B@x@ADAE줅bo2U@sa =`="@@&lp20}^BcPܘqޠ1K.Z[i,Ap$ B@B@x@ADAEW]aLA = `=$_@3@@ &+ @>AP  B@B@x@ADAE;ZaLADa =@d=$_@k[@&/EC A B@B@x@ADAEala =r`="A@@&Y;L^ұyr;fa& UAA$ B@B@x@ADAE,΁  a =j$_@@ ' @>A%A[- B@B@x@ADAEˁ !!a =Ԁ="@@̀@ !$A/C A B@B@x@ADAEb"1a = C`="@@&T,-ƝeRԮ6_N$s AC B@B@x@ADAE?aL2@a =`=+$_@@ PA  B@BDK@x@ADAE;aL6hAQa =!`=$_@@ ՙ mW!%O;Ƌ={>a& kC  B@B@x@ADAE֯aLR`a = `= @p@ ! @+=}  B@B@x@ADAnEaL? Waaa =="+@ꭀ@ $#VUD/SGCV+ B@DB@x` =A @EAhabqa =n`="@Uf@ Ġ:YKR\ع{Լ^Q-%d.cGfv` ߽Ae  B@DB @x@ADA E aLra =k`=$_+ @Ec@ # @= b  B@BW@x@ADAEaL@Y a =\'=$_@@W/)C0  B@B@x@ADAEف a =@ @*׀@ĠY&Z'[HVt*~(rI@ lր$+ B@B@x@ADAEaLAa =`=$_@Ԁ@4 @>AzӀ  B@B@x@ADAEeaL a =1=  @@ &+ @/;D!A" B@B@x@ADA#EIaAa$ =P`="+%@G@ ,W{'fo(Da""bstY+?J&k +' B@B@x@ADA(EVaLa) =M(`=$_*@D@dA+OA[, B@B@x@ADA-E:  a. = )a"/@j@ /v0 1 B@B@x@ADA2E a3 =r4a$_4@ո@ADFCb9S>o8Cַ ? FF+5A6 B@B@x@ADA7E+s5aLa8 =i?`= 9@ŵ@dA:%A[; B@B@x@ADA<Ep@aL) a= =y= >@?q@&/+C? @ B@B@x@ADAAE+AaaB =F2L`="+C@)@ A_%9`4 EƘm3@a& 8AD +E B@B@x@ADAFE AG =>/W H@&@dAI% AJ B@BA@x@ADAKE aL =="M@@ :A/ΟCNဂ O B@B|C@x@ADAPEkXbaQ =c`="+R@@&5;"9w9ˮ_0a& >AS뙀$T B@B@x@ADAUETdaL aV =n`= W@p@dAXϖA[AY B@B@x@ADAZEQoaL !!a[ =[=G@6h\@R@ W/ȚC]VA^ B@B@x@ADA_E@ pal-L "1a` ={`= a@T @&Ū%nGC+'vTH֨hy  B@B@x@ADAEc؀ !!a =-= A@ـ@&/dC  B@B@x@ADAE+bA"1a = {6`="@@ +#!'# 12uaeT᧜mIԱ(qAj$ B@B+@x@ADAEUL7aL2@a =A`=$_n @3@@ ,W @> N  B@B 5@ADA @E9IBaL AAa =AV= S="@iJ@ W! @ /` + B@BW@x@ADAECalABQa =t N`="@@%gM![Q˓b8 Sra& OD?- B@B@x@ADAE* R`a =hYa$_@X`@ @>A#A[u B@B@x@ADAELaaa =À=$_@>@ :/)cC  B@B@x@ADAEuZbAbqa =I|e`="@s@ +A㞦Nrt^ ~uI Tu1y>a& ,A B@B)@x@ADAE-faLra ==yp`=$_@p@ # @>AoA[ B@B@x@ADAE*qaL AaE4="@,@ !$A/C+ ` @ XF B@BA`x@9 A @Ej怈a =|a"@~@&J#*DIY&]pyru'|l]:S ` A。+ B@DB+@x@ADA EԞ}aLa =`=" @q@ &+>A  A B@B6h@x@ADAEaL a ==$_+@蜀@&/?CT B@B@x@ADAE?Waa =]`="@SU@ #!+MCHTc`-ٜta& 'AT + B@B@x@ADAEaLa =Z`=+$_@CR@ ' @>AQ  B@BA@x@ADAE aL; a =Ϋ`="@(ƀ@&/ C ŀ$! B@B@x@ADA"EaLa# = ˶`="$@À@ $ @>%x€$& B@B@x@ADA'Ec}aL6ha( =8="+)@~@mW5* ++ B@B@x@ADA,E8aa- =?`=".@6@ &+$:J/HHł}C+{͒ #_hb$c -/i$0 B@B@x@ADA1ET WA2 =<$_3@3@ ! @=; 4MA[5 B@BN[B@x@ADA6E8  Wa7 ==b#8@h@ &+/o"Y9 $s`: B@Bu   @'@x@ADA;EbWa< =k`="=@ӧ@gĠf0G'0]P b/lcmL$aUnf zIA>?? B@AbB@x@ADA@E)baL aA =g`=+$_B@¤@ # @>AC# WD B@B}@x@ADAEE _aL!!aF =h="G@A`@&/yVCH +I B@B@x@ADAJEa"1aK =M!`=%L@@ P' @nsZi玟>mAO_1.m/Ϸژ b AM  N B@B@x@ADAOEҁ 2@aP =<$_Q@@ ! @=AR S B@B@x@ADATEπ AAAaU =ـ="V@р@ &+A/gCW~ЀAX B@BA@x@ADAYEibBQaZ =! @"[@}@ #oĠ>G[}|4=:|!s| ]$q ~A\鈀 ] B@B@x@ADA^EC aLR`a_ =`=$_`@m@ # @>Aaͅ b B@B@x@ADAcE@aL aaad =J="e@A@ ! @҃/CfTg B@B@x@ADAhE> bqai =!a"Aj@R `@ nQ#u_ESrH`U?Kq Ak$+l B@B@x@ADAmELran =+aI$_o@G@ ! @>ApA[p@` q B@B@x@ADArE,aL as =Y="t@@PĠ/u)v B@BP@x@ADAwEm-aAax =s8`="y@(k@ +#+App![5̿uc}VOA  3Dzj${ B@@iBr@x@ADA|E~%9aLCa} =pC`="~@h@ '>AwgA[d@ B@B6h@x@ADAEb"DaL a = >,="@#@ !/C  B@B@x@ADAE݁ a =Oa!I@ۀ@ 4 ҦQoW#/ ,hfAi B@B<@x@ADAESPaLa =Z`=$_@؀@ ! @JAM A B@B@x@ADAE7[aL$a =="@k@&A/Cס + B@B+@x@ADAEN\aCa =nUg`="@L@&rO+c8,BX5Me_0xd%a& CB  B@ B@x@ADAE)haLAa =jRr`=$_@I@ ' @>A&  B@B@x@ADAE saL~ Aa = = @A@ ! @/C  B@B@x@ADAE pa =@~a!I@@ ' @ghe%Yx[ms2-4ՈHɎoǧ8dA A B@B"@x@ADAEwaL$ zA = ;É`=$_@@ -&! @=  B@B@x@ADAEtaL A! =~="@v@ .BC~uA[ B@B@x@ADAEh0a$A =7`=$_@|.@&A^cbq]]BnK!^a& m-$ B@B@x@ADAE耈 A =4a$_@l+@ +# @>A*A[ B@B@x@ADAE倈 !!A =="@@ !#VA/#t S B@BA@x@ADAE>b"1A =`=!I@R@&'pҠGQ߆ TXO Z9\;a& Y$ B@B@x@ADAEYaL2@A =椸`=$_@B@ PAA[ B@B@x@ADAEVaL) AA)A =Y`= @W@%$/( B@Bx$@x@ADAEaB%! =`="+@'@ m#! v`2;-*ɽoV0XrSU^r + *@G m B@B+@x@ADAE}ʀR`A =a @ @dAw A B@B@x@ADAEaǀ aaA =*р="@Ȁ@&A/C  B@BW@x@ADAEbbqA =`=$_@@ 1y mLT|ǒ^|ifW8@ CAh  B@B@x@ADAES;aLrA =@逃`= @}@dALA[A B@B@x@ADAE78aL A =B="@g9@ W/oC  a B@BW@x@ADAE A =ra @@ yHmQ{#~ST* a& TA=- B@B @x@ADAE(aLAA =j`= @@dA!A[c @A B@B@x@ADAE aLA =ܲ="@<@ /C  B@B @x@ADAEd@TAA =?k `="F@b@&styՅDNJ9MYxNWy  A  B@B@x@ADAE aLA =;h`="@_@dA^  B@BA@x@ADAEaL$0 =#= @@ W/4 C } B@BW@x@ADA EhՀA ="a"@|Ӏ@ &A;q$Nw-/煩}^b77 AҀ$ B@B+@x@ADAEҍ#aLA =-`= A@lЀ@dAπ  B@B@x@ADAE.aL) A =="@拀@&A/CR B@B@x@ADAE=F/aA =L:`="@QD@ +fl $?oN&ۓuʴG:luSAMaASAC A B@B+@x@ADA!E!'" =IE #@BA@dA$@A[% B@B :*@x@ADA&E  A' =XFa+ A(@~&/eC)( * B@B@x@ADA+ELa, =ǽQa"-@&@ +#Jw,[Kw^R/ LlIa& HA.$/ B@B@x@ADA0E}oRaL a1 =\`= 2@@dA3v A4 B@B@x@ADA5Eal]aL6h!1D6 =.i`= :7@%@g  :'kOH\ƭd^QCI# q7a& :nC8g$A9 B@Bg@x@ADA:ER W2@a; =+ta+ <@"@ $ @A= =KA[> B@B@x@ADA?E6݁  AAa@ == A@fހ@ ! @/BCB  aC B@B@x@ADADEubBQaE =e`="F@і@ xhNm!"]Qw}w+ cC|AG=@4a@H B@Bp Jz3R@x@ADAIE'QaLR`aJ =`= +K@@ ! @> L! M B@B@x@ADANE NaL) aaGO =W="P@;O@ /CQ R B@B@x@ADASE abqaT =R`="U@@DS#+"t)Ա~G6ݭ4e ɠM*a& '\AV +W B@B@x@ADAXE raY =: a"Z@@>A[ \ B@B@x@ADA]Eྀa^ = Ȁ="_@@ #/pC`|a B@B@x@ADAbEgzbac =`="d@{x@  ;z0$! <D@dEkf3xa& hgew f B@B@x@ADAgE2aLah =~`=$_i@ku@ ! @JAjt k B@B@x@ADAlE/aL Aam =~9= n@0@/goRp B@B@x@ADAqE< ar =a"s@P@%Afȩ TM'iD/ #A^@qT:;E|a& 6F+t耂$+u B@Bm@x@ADAvEaLAaw =`=$_x@@@ ' @>Ay堂A[Oz B@B@x@ADA{EaL a| = X= }@@ :! @Ġ/~' B@B@x@ADAE\aa =b`="@%Z@&-1]Ùf-H*`̮2VDY` A B@B@x@ADAE|`a =_`=$_@W@ PAuVA[ B@B@x@ADAE`aL a = (="@@ W/C  B@BA@x@ADAÉ a =a"@ʀ@ +#+>N17LD`71{a& Wlig B@B@x@ADAEQaLU =@"@ǀ@ #$=K A B@B 8@x@ =A @E5aL) a =="+@e@ ! @A/dD  B@DB@x@ADAE=aa =mD `="@;@ rGd5#<}W%6{A< A B@B@x@ADAE'  a =dAa+$_@8@ ! @>  B@BA@x@ADAE @Y !!a = +=$_@;@ /C  B@B@x@ADAEb"1a =I$`="@@ H#!a(w/T"p~xjwoua& }P$ B@BE@x@ADAEf%aL2@a =9/`=$_@@ # @>A  B@B@x@ADAEc0aL AAa = m="@e@ H!A.a}|dA B@B@x@ADAEf1aBQa =&<`="@z@ mLgt^TG+>` *0| rq 0\ua- B@Bm@x@ADAE׀; R`a =#G$_@k@ ! @>AA[ B@B@x@ADAEԀ aaa =}ހ=+$_@Հ@ /gQA B@B@x@ADAEAu  B@B @x@ADAE_L) a =0="@@Ġ/MC  B@B} `@x@ADAEqwba =x`="@o@ #8*tr,̗IԸ$a& RRAf A B@B@x@ADAEQ*aLa =u`=$_@l@ PAJ  B@BA@x@ADAE5'aL a = 1="@e(@&/~C  B@B@x@ADAE a =da @@ -XSeHgXH0>OT c[gA;@4W B@B@x@ADAE&aLa =c`= @݀@ @JAB9 Gab|B@x@ADAE aL a =Ρ= @:@ &+ @A/hKC  B@B@x@ADAESaa =EZ`=" @Q@&AfbqW}"4_I@SxkA $ B@B@x@ADA E aL" =9W`= ! @d_ +@N`@ #>M  B@B@x@ADAEa a =I="@ @ !/C{ N B@BA@x@ADAEfĀa =a !:!I +@z€@ AҰš1^)c^lLC hA$ B@B*@x@ADAE|aL a =I`=  $_ +@j@ ! @>ʾ  B@B@x@ADA EyaL) A!!a! =I=""@z@ :/$C#P$ B@B[p@x@ADA%E;5a"1a& =;`="'@O3@ #$7 QQ/ۺ\cA-/ . B@B@x, 9ADA/ @Eꀈ AAa0 =IU="1@@ !/MC2%3 B@DB@x@ADA4EbBQa5 =`=  !I6 +@$@ [Exm 0ƈqezvEP2Il  ?A7 8 B@B@x@ADA9E{^aLAR`a: =I@H@ $_;@@ ! @>A<t = B@B ::;@x@ADA>E_[aL aaa? =+e=  @ +@\@ W/A B B@BW@x@ADACEabqaD =I`= E@@ m# @ANKչr?J8@;N0¹oFe$+G B@B P@x@ADAHEPρ raI =a J +@@dAKI L B@B@x@ADAME4́ aN =Iր="O@d̀@ / GP Q B@B@x@ADAREbAaS =p&`= !:T +@υ@&׫qlڻ~] :/3soF3sAU;V B@B@x@ADAWE%@'aLaX =Ic1`=  )AY +@@dAZ$[ B@B@x@ADA\E =2aL; a] =IH=`="^@@&g/_C_$` B@B$@x@ADAaE>aLab =8H`= c +@@dd e B@B@x@ADAfEޭIaLag =IpU`= +h +@yg@&3k;%V0_DU&k'R@6 if mj B@B@x@ADAkE!VaLmal =I m``= +m@id@d+nc zo B@B+@x@ADApEaaL maq =(= >-r +@@&+/`sPt B@B@x@ADAuE:ځ t av =Ila w@R؀@&^C߆ NYO#~Ea& 9Gx׀ Ay B@B:@x@ADAzEmaLޗ{ =w`= |@>Հ@d}Ԁ ~ B@B@x@ADAExaL a =U= @@&/uC% B@B$@x@ADAEKyaa =Q`= + +@#I@ buϻ/_ݥ7L|  H$ B@B+@x@ADAEzaL a =IN`=  +@F@dAsE  B@B@x@ADAE^aL !!a =I; =  +@@&>/O A B@B>@x@ADAE廁 "1a =I›a"+@@ +!je^Afz5 @5'a& Ve B@B+@x@ADAEOtaL2@a =`=   +@鶀@dAI  B@B@x@ADAE3qaL) AAa =Iz="@cr@ W/-  B@B@x@ADAE,aBQa =f3`="@*@ ?-+vt.l^`('d۠H aJ:  B@ B#@x@ADAE$ R`a =0a  +@'@dA  B@B4@x@ADAE aaa =I= W +@8@&&/C  B@B@x@ADAEbbqa =I?`="@@ +fkd?8!\[vj$Oyӝ]bYa& =-A  B@ B+@x@ADAEUaLra =7`= @@dA󗀂  B@B@x@ADAERaLa =\= @T@ WA/C~S  B@B@x@ADAEdaa =`="@x @&Cwqq=JOԃM6A - B@B@x@ADAEƀa = a }' +@h @ &+ @IC a> 4 ` B@B@x@ADAEÀ a =Ì= ! +@Ā@ /AO B@B@x@ADAE:bAa =I`="@M}@&AdBȐ6( :Vy quVԫDA| + B@B@x@ADAE7aLa =^ "$_ +@>z@ # @>vy  B@B@x@ADAE4aL@YL a =IT>="@5@ !$0v^'.\|`۪V3NuA퀂$ B@B@x@ADAEyaLa =`= S'$_ +@@ ! @>Asꀂ  B@B$@x@ADAE]aL) a =I*=  " +@@&/1CN B@B@x@ADAE`aa =Ig(`="@^@ `#!v82?.%g@AH  B@BA@x@ADAE24aL a = ="@c@ ! @A/bCϡ  B@ϠB|Z2@x@ADAEс a =i?a"@π@ +eu3hdۜFX_%b(/9π$B+@x@ADAa7E$@aL a =aJ`= A +@̀@ !>A  B@B@x@ADAEKaL !!a =IՐ="f dm$_@8@ W/'D  ]` @ ) B@B@x@ADA EBLa$"1a =CIW`=" @@@&A5N}?BWB):tt0`n[9At,Da& ]A- B@B|+@x@ADAE 2@a =7Fb$_@=@ # @`.A<@T B@B@x@ADAEAAa =ca &+  +@ ~ ! @A/5Cy B@BW@x@ADAEdLABQa =In'@x@ ''΅ܐq1g/ljep{A IA䰀$ B@@iB@x@ADAEkoaLR`a =y`=+$_!@k@ ! @>A"ǭ # B@B&@x@ADA$EhzaL Aaaa% =r="&@i@ `.eC'N ( B@B@x@ADA)E9${abqa* =*`="+@M"@ #!҃4I{01M)Rcyru 2;kADXA,!A- B@B(@x@ADA.E܀ra/ ='$_0@=@ # @>A1 A2 B@B$@x@ADA3Eـ a4 =T= gA5 +@ڀ@ !A/tC6#7 B@B@x@ADA8Eba9 =I`= : +@"@ ܛ?dӷp(J 4#]Kۣ A; +< B@Bm@x@ADA=EyMaLa> =I`=  $_? +@@ ! @>A@r A B@B@ 5@ADAB @E]JaL aC =I)T="D@K@ /OE F B@DB@x@ADAGEaZ:H = `="I@@&! )L[[^B +b%8/h#_R *4DJc$+K B@B@x@ADALEN aM = a  +$_N +@@ # @>AOGP B@B@x@ADAQE2  aR =IĀ=  )S +@b@&/CT U B@B@x@ADAVEvbaW =Ii}`= X +@t@&HVÁY_U@ot!Zjy%os ̳AY8$Z B@B@x@ADA[E#/aL a\ =Iez`=G@,W$_]@q@ `@>A^ `:iF_ B@BA@x@ADA`E,aLaa =5=  b +@7-@ `@/:Cc +`d B@B@x@ADAeE af =ICa g@@&$1ꋃ4:I?\D5A`8&ha& Ahi B@B@x@ADAjEaLJk =6`= l@@ # @=mဂ n B@B@x@ADAoEܜaL )Acap =_`= =Q q +@{V@$ՙ|Ts׷/3]&Џ"Ts舔Þ 8(rU s B@B@x@ADAtEaLW au =I \@"v@gS@ PwR x B@B@x@ADAyE aL?Ax!!az =@}= { +@@ !&l/(|M} B@B@x@ADA~E8Ɂ "1a =Ia!I@Lǀ@&MF3w2sّ̍̎YJtrƀ  B@B@x@ADAEaL2@a =`= i}&+'u + -<Ā@ PÀ  B@BW@x@ADAE~aL AAa =IW= !:  +@@}/͞#N B@B@x@ADAE :aBQa =I@*`=  +@!8@%oAWE'Kr 4]'CTY *7b LW7$+ B@BE@x@ADAExWR`a =I=5a+ @5@ ' @=Wq4A[ B@B@x@ADAE\ aaa =$= &+ +@@ ! @Ġ/C  B@BtY(`@x@ADAE6bAbqa =IA`=!I@@ +' @7pA}«Pf+%/9 dS^&(a& Bc B@B+@x@ADAEMcBaLra =L`=$_@祀@ ! @=FA[ `po   B@B@x@ADAE1`MaL a =i=  +@aa@&/sD  B@B@x@ADAENaa =Id"Y`=  +@@ 1#%ɱދwD gJVƨrQ'+ 9E8 B@B+@x@ADAE"ԁ a =I`da"@@ '>A  B@B@x@ADAEѡ @Y) a = +ڀ= =Q +@6Ҁ@A/E  B@Bf=@x@ADAEeba =IFp`=!I@@*U0A?$'GDuhz>̝a& F+  B@B*@x@ADAEDqaLa =9{`=  $_ +@@ 6h&+ @JA񆠂  B@B}@x@ADAEA|aL a =IK=  +@ C@ :&+#V/FCxB B@B@x@ADAEba =Ia  +@v`@ #V9 %$em_<Ip4# A- B@B@x@ADAE͵La =I aI  $_ +@f`@ @<  B@B@x@ADAEL6ha =I=  " +@峀@ /{CQ  B@B@x@ADAE7nb$a =It`="A@Kl@&A ,'G!Sf)%La& Ak$ B@BA@x@ADAE&aLA =q`=$_@ 6]n}b}c A܀$ B@B@x@ADAEwaL a =I`= @ڀ@dAqـ  B@B@x@ADAE[aLQ!!a =(= = +@@&/C +A B@B@x@ADAEOa"1a =IV`= @M@ {Eb~)MhSsBC a& Ab  B@B@x@ADAELaL2@a =S`= @J@dAFA[ ` B@B@x@ADAI  E0aL AAAa == @`@&/i3C  B@DB@x` =A @E BQa = ?=oab#Q+@˾@&yZ#31fTBZjLa* uA57  B@B@x@ADA E"yaLR`a =_`= @@dA   B@B@x@ADAEvaL aaa == @6w@ ` `@/Ga  B@B@x@ADAE1abqa =48`="@/@ Aҳw[\/An(zF+ @4d B@BP@x@ADAE ra = 95  @,@dA+  B@B@x@ADAzE怈a ="A @ @ /VC!w瀂" B@DB@x@ADA#Eb bAa$ =`="%@u@&A 9H' 2? EҘ \&ៀ ' B@B  !@x@ADA(EZaL$a) = `=G * *@e@ @=+Ŝ ,I , B@x@ADA-EW aL Aa. =a=H@! A/@X@&$/0L1 B@B6h@x@ADA2E7!aa3 = ,`="4@K@&,yVm;%aj]]qL&,fas IF+5$6 B@B@x@ADA7Eˀa8 =7a"9@;@dA: A[; B@B@x@ADA<EȀ$a= =ZҀ= >@ɀ@&/C?% +@ B@B@x@ADAAE 8bmaB =C`="C@$@&ag~bv}t[%hPa& AD E B@B@x@ADAFEwXE Y B@B@x@ADAZE0faL A![ =="A\@`@ }/"A] ^ B@Bs@x@ADA_Eega$=!` =clr`= +a@c@&Hk))7+=c[T_-Ld 5Ab6dW+c B@B@x@ADAdE!saL Ae =ci}`=$_Af@`@ # @K<Jg h B@B@x@e =Ai @E~aL!!Aj =$="k@5@ !#V/îCl cm B@DB@x@ADAnEց "1Ao =D݉a"p@Ԁ@ P'?J0OHlV~<2,a& Aq r B@ BPq 5@ADAs @ aL2@At = 9=4ڔ`="u@р@ !>AvР w B@B cNA$X@x@ADAxEڋaL AAA'y ==" @@30z@ @ :/*C{vG| B@B@x@ADA}EaGaB%!~ =AV N`="@uE@! v`lWĠ, I w2Ipl,N!T%X2 @GD  B@Bo  @x@ADAER`A =K$_@eB@ # @>AA A B@B$@x@ADAE) aaA =ta"@~ !A/CK  B@Bq@x@ADAE6LbqA =澸'@J@&$Onq[`sFUI_PJrgt۾@a& O+  B@B@x@ADAEpaLr &+ =޻`=$_@:@ PA  B@BA@x@ADAEmaL; A =/`="@'@ /1&$ B@B@x@ADAEvဈ(# =,a"@$@ !#VJo#$ B@B@x@ADAEZ A =&='u@߀@ :$ @B5 W B@B@x@ADAE@TA = 0`="@@W}nIp-3Ҍ(g$N"cDL74  B@B@x@ADAE @Yy A =ɀ="@4@ W/z5C  B@By@x@ADAE{ bA =;`="@y@&>;*UZum*.9î J  B@B@x@ADAE3aL A =3!`=+$_@v@ # @>Au  B@B@x@ADAE0"aLWvA =:='u@ 2@ ! @A/;vu1 B@B`@x@ADAE`쀈a = -a"@t@ +'AP|EVDQg8XyjB#"j F+适  B@B@x@ADAEˤ.aL a =  8`=+$_@h@ ! @>A怂  B@B}@x@ADAE9aL A!!a ={="@ߢ@ /3J=K B@B@x@ADAE5]:a"1a =cE`="@I[@&WsN:e5Ya3,o<@-a& EAZ$+ B@B@x@ADAEFaLA2@a = `P`=+$_@9X@ # @>AW Y B@B@x@ADAEQaL@YAAa =P="@@ !$A/iabqaJ@Et`="@<@ #$s׷\mRC֫~q454 ` B@B+`x@9 A @EJ ra = ?=Ba"@9@>AD  B@B + @x@ADA E. 4) a ==" @^@&A/gD  A B@B@x@ =A @Eba =j`=$_@ɭ@ _AOWeD|_/d\]:jj b 3A5`4 B@DB @x@ADAE haLa = ]`=$_@@ ! @+=A  B@BA@x@ADAEeaL a =n= @4f@UD/C  B@B@x@ADAE aa = ?'`="@@&n#^+iބJ|Wչ= uA  ! B@B@x@ADA"E؁ a# =2$$_$@@ ,W @>A% & B@B@x@ADA'E a( =߀=")@ ׀@&/1YC*uր A+ B@B@x@ADA,E_b$a- = `="A.@s@ '&l 6]o`$? B@B-@x@ADA@E AA =`"B@8`@dAC D B@B@x@ADAEEL) aF =W="G@@ C/CHI B@B@x@ADAJE sbaK =y`=%L@q@ gU.R}cm/!AMp N B@B  C@x@ADAOEt+aL aP =v`=)Q@n@dARnmA[S B@B@x@ADATEX(aL !!aU =92= V@)@ /CW X B@B@x@ADAYE "1aZ =@"+[@@ ּ[sIώvU ʞv(Mu/cpS@ 9A\_ ] B@B :@x@ADA^EJaL2@a_ = `= `@ހ@dAaC b B@B !(@x@ADAcE. aL AAad == e@^@ /Cf Ag B@B`@x@ADAhET aBQai =e[`="+j@R@ PH'^²%I_3Jhaߋn3 %a& k4-l B@BP@x@ADAmE aLR`an =]X#`= o@O@dAp q B@B@x@ADArE $aLaaas == t@3 @ 6h/Du !jv B@Bs@x@ADAwEŁ bqax =:/a"+y@À@&:8Lb,ԡ&8y%+a& :Az { B@B@x@ADA|E}0aLra} =6:`= ~@@dAA[c @ B@B@x@ADAEz;aLa =="@|@ / Ct{ B@B@x@ADAE_6,J?u.Ҁ,ȷXa& A3$ B@B@x@ADAEa =:R @c1@dA0A[ B@B@x@ADAE뀈) a =="@@ /|"CI+ B@B@x@ADAE4Sba =^`="+@H@ P1T9[qlǬq6u@}( =a& [oA  B@Bu@x@ADAE__aLa =ܪi`= @8@dA A B@B@x@ADAE\jaL a =Kf="@]@&/CC B@B@x@ADAE kaa =v`="@@ ?yPC~k{&|S,M ĝ(A  B@B@x@ADAEtЀa = @@dAmA[A B@B@x@ADAEX̀6ha = W@@A9Egw?<~BCW+a& `c'^$ B@B:@x@ADAEIAaLWA =`="@⃀@dBA[ B@B@x@ADAE->aL a =H="+@]?@  +`/c'  B@B  @x@ADAE a =ga"@`@JĠW:PXH8qmŷ;:! <5G4 B@B{@x@ADAEL a =\aI"@@ #'J A[W B@B@x@ADAEaL !!a =Ӹ= @2@vĠ/C  B@B`@x@ADAEja"1a =5q`="@h@Ġnm~*z ՕVQt!aW4ԃa& kA  B@BJ@x@ADAE"@T2@a =1n`=$_@e@ 4 @>d  B@B@x@ADAEaL) AAa =)="@!@ }&+$/Cs  B@B@x@ADAE^ۀBQa = a"@rـ@;*Ȫ++cml=5  A؀  B@B(@x@ADAEɓaLR`a =`=$_@bր@ # @=Հ  B@BA@x@ADAEaL aaa =q=+"@ݑ@ @A/ CIA[ B@BO@x@ADAE3Labqa =R`=!I@GJ@ ' @A ʭ*6~c=b`0|}AI$+ B@BC@x@ADAEaLra =O`=$_@;G@ ! @AB  B@B@x@ADAE, @Y a =="@\@&/C  B@B@x@ADAE&ba =c1`="@ǜ@ 6h#xn 3+8gjvQT4fmca& , A3 m B@BJ@x@ADAEW2aLa =[<`="@@ '$>A  B@B@x@ADA!ET=aLa" =]="+#@1U@ `! @A/&C$  % B@B@x@ADA&E>aq a' =AI`="(@ @&`ymM^1h>CP1Ka& aA) * B@BA@x@ADA+Eǁ A%m = 0Ta+$_-@ @ &+ @>A. / B@B@x@ADA0E Aa1 =΀=$_2@ƀ@%/(MC3sŀ 4 B@B@x@ADA5E]Uba6 =``="7@q~@!҃`m(FBh6j9E縍ua& 6A8}@0+9 B@B@x@ADA:E8aaLA a; =k`=+$_<@a{@ ' @>A=zA[> B@B@x@ADA?E5laL !!a@ =p?="A@6@A/"CBHC B@B@x@ADADE3 "1aE =wa!IF@F@&Aݸ߉0P)-4ħ&(.HL\ Ia& GAV_$W B@B @x@ADAXEraLR`aY =e`="Z@ ]@ #$=[l\ A\ B@B@x@ADA]EVaL) aaa^ =!="_@@& /kC` a B@B@x@ADAbEҁ bqac =٦a%d@Ѐ@ +' @^>\=#B__t0p` kVCYBa& Ae] `J XFf B@B+@x@ADAgEHaLrah =ֱ`=$_i@̀@ ! @=jA k B@BA@x@ADAlE+aL@Y am =="n@`@>A/Co̡ p B@Bg@x@ADAqECaar =_J`="s@A@ +#!҃ڞSNdKF z@/5jӾ, pzAt2$u B@B@x@ADAvE aw =ZG$_x@>@ PAy z B@B@x@ADA{E  a| ="}@1~ W! @/VC~ A B@BA@x@ADAEL$a =4a"A@@&/b<}1x2 Vmr][:lA B@B@x@ADAElaLA ba =0`= @@ &+ @>A뮠A[ B@B@x@ADAEiaLa =s="@k@ /M@CrjA B@B@x@ADAE]%aAa = ,`="@q#@ +#+Ag e>){@JqUH9a& "$ B@B@x@ADAE݀a =)a"@d @ #>A[ B@B@x@ADAEڀ Aa = s="@ۀ@&/bG B@B@x@ADAE2ba =ߜ@$_g @3@F@&**?x-;'-*SW3{(kY}';5}F+$ B@B@x@ADAENaLA =AVڙ`=$_@6@ `@JA A B@B$@x@ADAEKaL) a = IU="@L@&/GQC B@BA@x@ADAEaa = `="@@ #$҃H>}ZeZq?F8)a& *MC  B@B@x@ADAEr a = ' @ @ PAk  B@B@x@ADAEV !!a =ƀ="@@ !/{gC  B@B@x@ADAEw(b"1a =~3`=!I@u@ vzB>u)Ą"(vɨea& IA\$+ B@B@x@ADAEG04aL2@a ={>`= @r@ #! @+=@  B@B@x@ADAE+-?aL AAa = 7= @[.@%J/C  B@B9~@x@ADAE BQa =fJa"@@&g%toi[JdnIʗꊃ>$aa& ~A1$ B@B%o@x@ADAEKaL$R`a =ZU`=+$_+@@dAA[ B@B@x@ADAEVaLaaa = 5̧="@0@&/URC  B@B@x@ADAEYWak:@sbqa =?`b`= +@W@ ' @aa6T$j؟OcfcZ 2@ bjA B@B 9~@x@ADAEcaLra =/]m`= @T@dAS  B@B@x@ADAEnaL) CAxa =@="+@@ W/FCq B@BW@x@ADAE\ʀa =ya"+@pȀ@ M׋ZDg~4ȝjQ*~~&.{F!` Aǀ  B@B>@x@ADAEƂz`Aa =΄`= @`ŀ@dAĀ  B@B@x@ADAEaL a = ="@ڀ@&A5/h0CF B@B@x@ADAE1;aa =A`= +@E9@ +8]Jք·0T38fZ` LA8  B@B+@x@ADAEa =>a @66@dAL5  A B@B@x@ADAE a = T=b @@ WA/C  B@BW@x@ADAEba =`= @@& qfO"lbƚ3_6-ڛa& ;F; $+ B@B@x@ADA EqdaLAa =`= @@dAnA[A B@B@x@ADAEUaaL A =%k="@b@ /F{ A B@B@x@ADAEaAa =#`= @@ A/N߳%YN]ƍqCq}vVD[ + B@B@x 9ADA @EFՁ  =  @@dA?  B@DBA@x@ADA E*ҡ ) a! = +ۀ=""@ZӀ@ /# $ B@B@x@ADA%Eb.a& =e`="+'@ɋ@ P2wD] ]ƷT|a& D(5) B@B@x@ADA*EFaL a+ =Y`= ,@@dA- . B@B@x@ADA/EBaL) !!a0 =L="1@/D@&A//C2C3 B@BA@x@ADA4EA"1a5 =6a"6@`@ +H엱gsGgtFz;?1A< fA7 A8 B@B@x@ADA9EL2@a: =2aI ;@`@ ! @P=< = B@B =QV@x@ADA>EԳL AAa? ==+ W@@@ A/CAq B B@B@x@ADACE[obBQaD = v  E@om@&Mz8yH=;Y(sn^@ ytFl$G B@Bm@x@ADAHE'aLR`aI =s`=$_J@_j@ # @>Ki L B@B@x@ADAME$aL aaaN =v.= O@%@&/z:PF"dnQ B@B@x@ADARE0 $bqaS =a"T@Dހ@&a/c|'&| E 3Ia& F+U݀-V B@B@x@ADAWEaLraX = (`=$_Y@4ۀ@ PAZڠ [ B@B@x@ADA\E)aL a] =L= ^@@ &+ @+*/C_` B@B@x@ADAaEQ*aAab =W5`="c@O@ ># -11u`Qj0Sa& AdN$e B@Bm@x@ADAfEp 6aLag =T@`= h@ L@ # @=iiKA[g@ᇶj B@B@x@ADAkETAaL al =$="m@@&/jCn o B@B@x@ADApE aq =La"r@￀@& }y /fJspYa& :As[t B@B@x@ADAuEEzMaLav =W`="w@޼@ &+>x? y B@B$@x@ADAzE)wXaL a{ =='u|@Yx@&/C} +~ B@B@x@ADAE2Yaa =e9d`="@0@ #!A Oa ]vZ:.Ī 6A0  B@B@x@ADAE a =X6o$_@-@ PA  B@BA@x@ADAE @Y a =="@3@&/3C耂  B@B@x@ADAEpba =2{`="@@ ٢ ŨC4y(H}Voc/0(62j5O L ^A- B@B%o@x@ADAE[|aL =-`= @@ ! @+=靀  B@B@x@ADAEXaL a =b="A@Z@&%o/CpY B@Bt P@x@ADAEZaa = `="@n@ #A#;p$XGf aI &/Y`|B LxTOiA$ B@B+@x@ADAÈ a =$_A@_@ PAA[ B@B@x@ADAEɀA!!a =yӀ=+"@ʀ@ ! @+$/PECI$ B@B@x@ADAE0b"1a =`="@D@ hgVXZ>6oMZp<{a& =)A$ B@B@x@ADAE=aL2@a = ܈`=+ @7@ ! @>A  B@Bc@x@ADAE~:aL) AAAa =KD=$_@;@&/C'f5W B@B@x@ADAE BQa =a"@@&$ aO%i  B@B$@x@ADAESaL aaa =$="@@ !$ |1'_C  B@B@x@ADAEfabqa =! m`="@d@&6'z.q5۴(, BZ  B@Bm@x@ADAEEaLra =j`=$_@a@ PAB  B@B@x@ADAE)aL a =%=+$_@Y@&/^lD  B@ B 5@ADA @ ׁ a =da"@Հ@ +#!+.^ )e#M`UAD|&=_ A/-+ B@DBP@x@ADAEaLAa =X`= @Ҁ@ ' @>A lM W B@B@x@ADAEaLa =ϖ=+"@.@ ! @A/C B@B}W@x@ADAEHaAa =5O@"@F@&APesU<M0Q«`@ VmA  B@B@x@ADAE aLa =-L`="@C@ &+=BA[ B@B@x@ADAEda =$_@r@  Ƕj Fll{qFٶɥ!Iԋ A kC޶$ B@@iB@x@ADAEq aL(a =*`="@b@ !>³  B@B@x@ADAEn+aL[pa =ux="+@o@mǶ/CH +M  B@x@ADAE/*,ao )@ra =07`="@G(@AĠୗ7"wGJ ^kzZ@ Q '  B@B|A@x@ADAE } zA =-B$_+@3%@ P $  B@B@x@ADA E~ Ax@! =@N=" @@Ġ/   B@B@x@ADAECbWA =N`= A@@Ġ3YX,Aml](et>5*a& vqF+$ B@B@x@ADAEoSOaL8%A =Y`= @ @ -&PAhA[W B@B@x@ADAESPZaL !!A =Z= @Q@ W&+ @/]C  B@B@x@ADAE [a"1A =f`="!@ @&Aj+Xf,ιTAfҼ:L5q\$  UA"Y$# B@B@x@ADA$EDā 2@A% =qa+ &@@dA'= ( B@B@x@ADA)E( AA)A* =ʀ="+@X€@ /yC, - B@BA@x@ADA.E|rbAB%!/ =W}`=%W0@z@ @ v`C A{Y+pôOt[G2 fĒ + 1/@2+2 B@B@x@ADA3E5~aLR`A4 =`= 5@w@ PA6 7 B@B@x@ADA8E1aL) AaaA9 =;=":@-3@ /L C];2A[< B@Bq@x@ADA=E퀈bqA> =4a"?@@ # n=t/ :kdս,; a& {A@ A B@B@x@ADABEaLr C =,`="D@@$+>:E瀂 F B@B@x@ADAGEҢaL AH =="I@@ /iWCJnK B@B@x@ADALEY^aAM =e`=$_`3N@m\@ Hi`1dυH'.?7ϯ; p ',AO[ P B@BH@x@ADAQEaLAR =b`=$_S@aY@ @>TX U B@B6h@x@ADAVEaL AW =|="X@@ H/CYDZ B@BH@x@ADA[E.ρ A\ =a ]@B̀@ PKǷɔLh|a& _A^̀$+_ B@BP@x@ADA`EaLAa =`=H d_b@2ʀ@dAcɠ d B@B@x@ADAeE}aL Af =E=H@ g@@ /Chi B@Bt@x@ADAjE@aEg,Wk =F`="+l@>@ "DTD;!Vvh{#el G}mD@ S. A B@B@x@ADAEځ  a =V% @@dAA[ B@B@x@ADAEց !!a =="@,؀@ /xY׀ B@B@x@ADAEb"1a =,!`="+@@ \\Ma,s+5Ny)J`.ygga& R.  B@BP@x@ADAEJ"aL2@a =+,`= @@ # @$>猀  B@B@x@ADAEG-aL AAAa =Q= @I@ W! @J/R.nH"f5 B@B@x@ADAEX.aBQa = 9`="@l@   QtGq}DEKO@͠>iZa& J@0a@ B@B@x@ADAEûR`a =!Da$_@]C`@dA  B@B@x@ADAEL aaM =l€=$_@׹@ /CC B@B@x@ADAE.tEbAbqa =zP`="@Br@ ) ⫹ʬ4a^oR Aq$ B@B@x@ADAE,QaLra =w[`= @2o@ @=CnA[  B@B@x@ADAE|)\aL a =M3="@*@&$/  B@B:@x@ADAE a =ga"@@&lAݶW!Ӽ|cD%]uep:q`Jҕx⠂@4 B@B+@x@ADAEmhaLa =r`="@ @ &+>g߀  B@B6h@x@ADAEQsaL) a =="A@@ A/~  B@B@x@ADAEUtaJ =\`="@S@& 7Sv,TrD"ft%IAX  B@B @x@ADAECaLga =Y`=$_@P@ @+>A<  B@BA@x@ADAE' aL a = = @W @& / C  B@B@x@ADAEƁ a =]͖a"@Ā@  aA#0].Rj@RȐ#i7׭=?tA-- B@Bx@x@ADAEaLF+ =Uʡ`=$_@@ ! @= B@B@x@ADAE{aL a =Ņ="A@,}@&$/XC| B@B$@x@ADAE7a$a =7>`= +@5@ m# @%oWe~FM"J$ǨvL3]OݻZ A$ B@Bm@x@ADAE A pD =+;a$_@2@ ,W @>A1A[m B@B@x@ADAE쀈 a =="@@ !/WCm퀂 B@B@x@ADAN  EXbAa =`="@l@%$ɴ‹t"F= +13k0o;jU~~~$Aإ$ B@DB@x` =A @E`aL a =`="@[@ &+>A  B@DB@x@ADA E]aL ". `_@!1a =!`=$_+ @A@ C ǶuI'܆D/"Ζ'H3ְ T`_C  A B@B6h@x@ADAEр2@a ="@1@ ! @J  B@B@x@ADAE{΀?AxAAa =@H؀="+@π@m}/Jw B@B<@x@ADAEbBQa =`="@@ Ġ挆Y}'Ȩōѧm2Wj6zja& V}  B@B@x@ADAEmBaLR`a =`=$_ @@ P!f " B@BW@x@ADA#EQ?  aaa$ =I="%@@@ !'+/}& ' B@Bs@x@ADA(E bqa) = a"*@ `@&fn[u\U+4,9R;?x4a& YIA+W$+, B@B@x@ADA-EBLAra. =aI /@@ PA0;A[1 B@B@x@ADA2E&aLa3 = ﹀=+$_+4@V@ /'uC5 6 B@B"Y@x@ADA7EkaAa8 =er#`="9@i@ #!+AרVQܞ$i" p9`iߔa& A:, ; B@Bk@x@ADA<E$$aLa= = Uo.`=">@f@ #$>? @ B@B@x@ADAAE /aLaB =*="C@+"@ !A/[CD!E B@B@x@ADAFE܀aG =*:a!I+H@ڀ@ ,jVA] twpDx` AI@1J B@B6h@x@ADAKE;`aL =E`=$_M@׀@ ! @>:Nր AO B@B@x@ADAPEБFaL) aQ =="R@@ /EaSlA[T B@B@x@ADAUEWMGaaV =!ATR`="W@kK@ #$҃|FgLf\i4̡25NYdXJ Y B@B@x@ADAZESaLa[ =P]`=$_\@[H@ @>A]G ^ B@BA@x@ADA_E^aL a` =i ="a@@ /dbB c B@B@x@ADAdE, ae =ia f@@@ *T8 ξ9QY 7IAg$h B@B@x@ADAiEvjaLAj = t`=$_k@4@ ! @>Al m B@B6h@x@ADAnE{suaL ao =K}= p@t@/w/q(dr B@B@x@ADAsE/va$at =5`="u@-@&<|#.BڄJGCIP1 a?a& SDv,-w B@B@x@ADAxEl瀈 ay =2a+$_z@*@ ' @K= {e)A[c @| B@BA@x@ADA}EP䀈 !!a~ = ="@@ !$Ġ/-C  B@B@x@ADAEןbA"1a = `=!I@띀@ P' @Pt1(d- F={EA+v kAW B@B@x@ADAEAXaL2@a =`=$_@ۚ@ W! @=:$ B@B@x@ADAE%UaL AAa =^="@UV@&/ޗ  B@B@x@ADAEaBQa =\`="@@& 2ʟȁƅܺ3ghN2{b:J^a& @^D, B@B@x@ADAEɁ R`a = Ta"@ @dA A B@B$@x@ADAEš @Y aaa =π="@*ǀ@&/rCƀ B@B@x@ADAEbbqa =2`=)@@ `@$}vep!9)cuƱk.Q + B@B@x@ADAE9aLra =)`=)@|@dA{A[ B@BA@x@ADAE6aL a =@= @8@ /l7 B@B@x@ADAEVlda =!A a"@j@ mr+Db6ei~y>(; W vF+$+ B@B@x@ADAEaLa =`= @Z@ # @+>쀂  B@B@x@ADAEaL a =u= @ը@&$/LZCA B@B@x@ADAE+caa =i`="A@?a@&AOG]gp 5z,՜eA` + B@B+@x@ADAEaLa =f $_A@0^@dA]  B@B@x@ADAEzaL a =G"= @@ &+ @+$/IC B@B@x@ADAEԁ a = a"+@Ҁ@ A9̇krl?% ^;c iJa& &Aр$ B@B@x@ADAEkaLa =`= @π@dAe΀  B@B@x@ADAEOaL) a =="@@&/( C  B@B@x@ADAEDaoa =K%`=%@B@ @+m TRRkOrLt<2$^mZA[A B@Bp@x@ADAE@ A = H0 @?@ @=:  B@B @x@ADAE$ a =1"@T~ m @A/m W B@B@x@ADAELAa = WXRcPgSI +\ВON}%%<t Ք A B@B@x@ADAEOlaLR`a = v`= @Z@ @JA + B@B@x@ADAELwaL aaa =pV="@M@&/ @ B@B@x@ADAE+xabqa = `="@?@&RZ9Y[]9|$jž CF+$ B@B@x@ADAEra = a"@.@ *>  ! B@B$@x@ADA"Eya# =Jǀ= A$@@&/foC% +& B@B@x@ADA'Eyba( = 5`=")@w@ #!$]{HV:ԗon cF|~ bA*v + B@B * 5@ADA, @Ek1aLa- = ?=|`=$_.@t@ ,W @K=/ds `:  iF0 B@BA@x. 9ADA1 @EO.aL; a2 =`="3@@ ! @ g陴8id:q}ݰV ` f#C4U DB@x@ADA6 @E@aLa7 = ?=}`="8@@ $ @>99 : B@B@x@ADA;E$aL Wa< =="=@T@ !B/C> W? B@B@x@ADA@EZaaA =[a`=!IB@X@AĠQ]IvztG;~Nahͻ0HMAC*$AD B@B@x@ADAEEaLWaF =S^`=$_G@U@ &+ @K=HA[$I B@B@x@ADAJEaLaK =="L@)@ /hCMN B@BW@x@ADAOEˀWaP = 8a"Q@ɀ@ S#$Ġ_S#Ot A&$9O1^"/ L  LAR$S B@B3R@x@ADATEaLAU = {(`="V@ƀ@ #=Wŀ X B@B@x@ADAYE΀aL AaZ =="+[@@ 6h! @A/`C\j +W] B@B@x@ADA^EU@&DnxZ3@W&AHDa& Ap +q B@B@x@ADArEeaL2@as =Ұ`=$_t@.@ ' @>Au v B@B@x@ADAwEybaL AAax =El=+)y@c@ ! @/ Cz{ B@B@x@ADA|EaBQa} =$'`="~@@ 'N| V.EM)T&ZA 8A$+ B@@iB @x@ADAEjրR`a =!2a"@@ !>Ac  B@B6h@x@ADAEN aaa =݀="@~Ԁ@ /fC A B@Bt   @'@x@ADAEԎ3bbqa =}>`="@茀@!+l; \fZ+ zbt` 5T@1-]  B@AbBA5@x@ADAE?G?aLra =I`=+$_@؉@ # @>A8A[ B@BA@x@ADAE#DJaL) a =M="@SE@ @A/lD  B@B @x@ADAE a =ZVa"@U`@ 'I]gbG6I)D6= 5a& DA* B@B@x@ADAEVa a =Ra`=$_@``@ ! @>A  B@B@x@ADAEL) a =ž=g'gb@(@&/C B@B@x@ADAEpbba =3wm`="@n@&1)h(5Jr>)A"?Hmda& Am  B@B@x@ADAE(naLa ='tx`="@k@ PAj  B@B@x@ADAE%yaL a =/="@&@ !A/ 2CiA B@B0<@x@ADAET a =a!I+@h߀@&ex_΁HKq'E)V-@ժgAހ  B@Bm@x@ADAEaLra =`=)@\܀@ PAۀ  B@B@x@ADAEaL a =!k= @ӗ@&/C? B@B@x@ADAE)Raa =X`="@=P@&ӾO-j }Z M7Xl6y٠Ya& -1O$+ B@B@x@ADAE aLA =U`=+ @-M@ ' @=LA[ B@B@x@ADAExaL a =@="@@ !$҃/1 B@B@x@ADAE o-&a = ɳa!I@@&=>l9EN0wۦ՜c) F+ `L XF B@B 4@x@ADAEi{aL a =ƾ`=$_@@ PAbA[ B@B@x@ADAEMxaL !!a =="@}y@&/}C "#a B@B@x@ADAE3a"1a =:`="@1@%J}~py@E3W[x|b2](/a& QCT B@BP@x@ADAE>aL2@a =|7`="@.@ '$=A8 A B@B@x@ADAE" @Yc AAa =="@R@A/#  B@B@x@ADAEbBQa =b`= A@@ ' @Hۤ\&!Պ%Y]~%#+ D.) + B@B@x@ADAE]aL$R`a =U`=$_A@@ -&! @=  B@B@x@ADAEYaL Aaaa =c="P@([@ &+ @/4Z B@B@x@ADAE~abqa =*`="@@ m##L'M>՞))\@Z; v  F+$ B@Bm@x@ADAÈra =&@$_ @@dA   B@B@x@ADA E a =Ԁ="@ˀ@&$/Ci  B@B@x@ADAESbla =`="A@g@ PGMX"Tq \k^9wW5e?#a& _AӃ+ B@Bg@x@ADAE>aLA &F  ba =`= @X@dA  B@B@x@ADAE;aLAxa =@nE= @<@&/kx> B@B@x@ADA!E) Aa" =(a"+#@=@&$9rQYnFBgYtE4 O>? D$$% B@B$@x@ADA&E)aLa' =3`= (@-@dA) * B@B@x@ADA+Ew4aL) a, =C="-@@&/C.$/ B@B@x@ADA0Eg5aa1 =n@`= +2@f@&;Y gG&7[`R>la& pA3~e 4 B@B@x@ADA5Eh AaLa6 =kK`= 7@c@dA8bbA[C9 B@B@x@ADA:ELLaL6ha; ='= <@@&/X\C=$> B@B@x@ADA?E؁ a@ =Wa"+A@ր@&1zl_7mgtFRW¢x)4`+0 v7BS C B@B 4@x@ADADE>XaLAE ={b`= F@Ӏ@dAG7 H B@B@x@ADAIE"caLqaJ =PPo`= K@G@ @$'# F`8|O-;`jcNG8Ve L( +M B@B@x@ADANEpaL aO =Mz`= P@D@dQ R B@B@x@ADASE @Y W!!aT ={a"U@+@P/V W B@ B@x@ADAXE} "1aY = .a!I +@@3+Z@@AĠ+2}o%A[p B@B@x@ADAqEWaqar =բa"s@<@&b:,kE(i9!lߝca& kCt u B@Bu 4@x@ADAvETaLraw =ԟ`="x@0@dy Wz B@B@x@ADA{EvQaL? a| =?[="}@R@  +Ƕ/C~'d B@B@x@ADAE aa =`="@ @AǶK_P2c2hIWu`m'A}  B@B@x@ADAEhŀa ='u@@ * @W>a  B@BW@x@ADAEL a =̀=+ m@|À@&Ga/gC  B@BGa@x@ADAE}ba =`="@{@&3N(zr?y)c4oW7D> I+Oa& $AR$+ B@B@x@ADAE=6aLWa =`=$_@x@ ' @>6A[g@ᇶ B@B@x@ADAE!3aLa =<="@U4@ !$A/C + B@B@x@ADAE a =\a"@@ 'AZcSc&qyor#821+`6#A,$ B@B@x@ADAEaL 4a =T]"@@ !>A B@BA@x@ADAEaLa =˭="+@*@ g/P$ B@BJ@x@ADAE}_aa =.f`="@]@ `#!+qLE&| x(<Oqa& (D$ B@Bm@x@ADAEaLAJ A =%c`=$_@Z@ # @>AY A B@B@x@ADAEaL) A! =="@@&/CgA B@B@x@ADAERЁ 1! =*a"@f΀@ 2/e;GG"^p=7/"ɯa& 9À  B@BA@x@ADAE+aL A =5`=$_@Vˀ@ ! @>Aʀ  B@BA@x@ADAE6aL !!A =m=+'u@ц@ W&+ @/4C= B@Bw:@x@ADAE'A7a"1A =GB`="@;?@ +#AaGjm0NOaa& >$ B@B@x@ADAE2@A =DMa"@+<@ #>A;  B@B@x@ADAEv AA' =>N"@~ W! @ /   B@B@x@ADAEL$B!! =Y'@@&A`3aVL(߭@ks{a& QxF+|$ B@BA@x@ADAEgjZaLR3c! =d`=+$_@@ &+ @"`C >A` ` B@B@x@ADAEKgeaL a' ! =q=$_@{h@ /TA A B@B@x@ADAE"faAb1! =~)q`="@ @ +#!g quWw7)[n":oa& iAR B@Bs@x@ADAE<ہ rA =z&|$_@@ # @>A6 A B@B@x@ADAE ء @YN A = +="@Pـ@ W! @/iA  B@B@x@ADAE}bA = S`="@@';6!◻uW9t"<5 miA'  B@B@x@ADAELaLA =O`=+$_@@ &+ @>AK B@x@ADAEHaLAAR=" ~@%J@'.iAI B@B@x@ADAE|aA =I1 `=% @@ P# @ .`lWA 뮢"hqCK:F}Dn"D€2 ` S  B@BP@x@ADA E缀A =(a$_@`@ ' @ .`A>A S  B@B@x@ADAE˹L AA = À="@@& /s4Ag B@B@x@ADAEQubA =|`="@es@%>9BJ0qF^A So  B@B@x@ADA E*aL A! =t4= "@+@&9~/G#<$ B@B9~@x@ADA%E& A& =a"A'@:@ #! .`cAϹqH?F*:2+h#ÆO@+ ( S。$) B@B@x@ADA*EaL@4+ =`=$_,@+@ PA-ࠂA[. B@B@x@ADA/EuaL  A0 ==="1@@ !/@C]2W3 B@B@x@ADA4EVaa5 =]`="6@U@ +GDe Kg[cɕN#'^F.ot0<TMA7|T$8 B@B+@x@ADA9EfaL a: =Z`=";@R@ !$ .`E>A< S`Q = B@B(@x@ADA>EJ aL) !!a? = ="@@z @ W/aAA "#f5B B@B@x@ADACEǁ "1aD =~a%E@ŀ@ +# @ .`cAཥYe 1}ihHN.VNDSpQC[2 i@GF SQ G B@B+@x@ADAHEK S5 L B@B$@x@ADAME} aLAAaN =膀="O@O~@ !A/EAP Q B@B$@x@ADARE8 aBQaS =V?`="T@6@  KĠԈmܕ&s͚g}۰WO$Na& AU&6 V B@Bxg@x@ADAWER`aX =N< $_Y@3@ ! @ .`D>AZ S [ B@B@x@ADA\E  Aaaa] =="^@%@&J/0A_` B@B@x@ADAaE{!bbqOmb =(,`=%c@@ +# @ .`cĠ?BTa*jUȗ1dWGHn2 Mh@Gd S-e B@BA@x@ADAfEa-aL-rag =$7`=$_h@@ -&' @ .`A>Ai S UGߣ j B@B@x@ADAkE^8aL-&al = D`="m@e@ }!0<m̜nص].F5fY{= VSAn o B@BA@x@ADApEҀaq =Oa"r@T@dsA[oc@mt B@B@x@ADAuEπ av =kـ="w@Ѐ@/Cx;y B@B@x@ADAzE&PbWa{ =ߑ[`=$_+|@:@ 䑢l#O*Bt2)(vT0Wzha& )A}$~ B@B@x@ADAEC\aLa =Ύf`='u@*@d  B@B@x@ADAEt@gaL) a =IJ= @A@ Ġ/C B@B@x@ADAE a =sa"+@r`@ -c>AJ4jXqVwT#kA{ + B@B|@x@ =A @EfLa =}aI @@dA_ A B@DBA@x@ADAEJ~aL a == @z@ / 6C  B@B@x@ADAElaa =s`="+@j@&T9&M)"~B>m҈q1P B@B@x@AD>E;%aLR =xp`= @g@dA4 A B@B@x@ADAE"aL a =+="@O#@&/!2  B@B@x@ADAE݁ a =Va +@ۀ@ mAnlѿ:e0^qI]1Ys6^7a& A%$ B@B6h@x@ADAEaL a =R`= @؀@dA A[ B@B`@x@ADAEaL!!a =="@$@  cV .`$X/@G S NG B@B@x@ADAE{NaA"1a =+U`="@L@:9st*1.7-ĕb,7g0Gƨa& t K$ B@B@x@ADAEaL2@a =#R`="@I@'O&+$ .`C>: SH@g B@B@x@ADAEaL AAAa = = @@&/pt e + B@B@x@ADAEP BQa =a"@d@! .`cĠ#AaTd089M7\Z9g2  Sм@/ B@BR.@x@ADAEwaLR`a =`=$_$@T@dA  B@B@x@ADAEtaL aaa =c~=H@Qb@u@$/-:  B@B@x@ADAE%0abqa =6`="@9.@&Murmu4E$na& 1D-  B@Bm@x@ADAE耈ra =3 @*+@+ @ .`f+=UD S*@g B@B@x@ADAEt a =E=+$_@@&/!A  B@B@x@ADAEba =`="@@ ,W! .`cA,-B ?76r2 @G Sz$+ B@B@x@ADAEeY@Ta = `=$_@@ @ .`A> S^  B@B@x@ADAEIV aL a =`="@yW@ /YA  B@B 4@x@ADAE aa =`="@@ ! .`C-mCA '(1 @G SO$ B@Bg@x@ADAE:ʁ a =x"a$_@ @ &+ @ .`a=A S3@gA B@B@x@ADAEǁ a =Ѐ="@NȀ@ /MA R  B@x@ADAE#ba =].`="@@& y Up37]bԸ?G.=A% zA% B@B@x@ADAE;/aLa =M9`=$_@}@ # @"`D>A  A B@B@x@ADA E7:aL) Aa =A=" @#9@& /A8 B@B@x@ADAEza =*Ea"@@%AC q5bS5`[# a& t m B@B@x@ADAEFaLޗ ="P`="@~@ PA퀂  B@B@x@ADAEȨQaL a == A@@&/Tz:d B@B@x@ADAEOdRaa =k]`="!@cb@ #!gn'7;4z- %ub?[F+"a # B@B@x@ADA$E^aL a% =!gh`=+)A&@W_@ PA'^ ( B@B@x@ADA)EiaL !!a* =n#="+@@&$/?/C,:A- B@B$@x@ADA.E$Ձ "1a/ =ta"0@8Ӏ@&h%i!^~öVk[#XmnLa& 4A1Ҁ$+2 B@B@x@ADA3EuaLA2@a4 =`=+ 5@(Ѐ@ &+ @+=6ϠA[7 B@B@x@ADA8EsaL AAa9 =@= :@@ &+ @g/†C;< B@B@x@ADA=EEaABQa> = L`="?@D@&033PΘi8yk?+?JA@yC AA B@B@x@ADABEdR`aC =I$_D@@@ # @=E] F B@B@x@ADAGEH  aaaH =a"I@x~&/CJ K B@B@x@ADALE϶LbqaM ='N@㴀@% c>Јѱc$aC0@(?ib |AOO+P B@B@x@AD>QE9oaLraR ={`=+$_S@ֱ@ @>AT3 U B@B6h@x@ADAVElaLbH) aW =u=)X@Mm@ &+ @A/+PCY Z B@B@x@ADA[E'aa\ =M.`="]@%@EK=3ae J.ɳEa& A^$ A_ B@BJ@x@ADA`E aa =L+a$_b@"@ # @=Ac d B@BA@x@ADAeEܡ @Yaf =="g@#ހ@ 4! @%o/ Chݠ i B@B@x@ADAjEybak =!`="l@@ 0(|D!|* =CA9pam$n B@B@x@ADAoEPaLap =`=$_q@}@ @>Arݒ s B@B@x@ADAtEMaL Aau =W="v@N@&/gwdA[x B@B@x@ADAyEN a$az =`="A{@b@&f UE OxzbZ[ >q9SF+|- aA} B@B@x@ADA~Ea = $_@S@ ' @>A a ၊ B@B@x@ADAENa =؀ "@tA[ B@B@x@ADAEr/ aL a =>9="@0@ :!B/C + B@B@x@ADAE Wa =a!I@ @ Njm٘yxGS8JQca& fGy耂+ B@B@x@ADAEcaL a =#`=$_@@ ! @>]  B@B@x@ADAEG$aL !!a = ="@w@ /0JC A B@B @x@ADAE[%a"1a = {b0`="@Y@ #$AofU̗ɃVSA&A3[mR# 5AN  B@B@x@ADAE91aL2@a =v_;`=$_@V@ # @>A2  B@BA@x@ADAE6߭t,D#- B@B@x@ADAEHaLR`a =KR`=$_@3@ǀ@ @>AA[ B@B@x@ADAESaL aaa =AV= @"@ /C B@B@x@ADAEx=Tabqa =)D_`="@;@&bx 0W#Kt>ʗ/); yA:$ B@B6h@x@ADAEra =%Aja+$_@8@dA7  B@B`@x@ADAE/a = ="@@ /Cg$ B@B@x@ADAENkba =v`= +@b@ 1@U"7i?=caW.a& DKΫ$ B@B@x@ADAEfwaLa =`= @R@dAA[ B@B@x@ADAEcaL) Aa =mm= @d@ /^DK8A B@BP@x@ADAE#aa =%`="+@7@!WĠlԘUw1MeQ}ەdșa& "F+  B@Bg@x@ADAE׀a ="a @'@dA  B@B@x@ADAEqԀ a =6ހ="@Հ@/JC ՠA[ B@Bq$@x@ADAEba =`=!IW@ @&$Nm>+Q ݃"s@pUOa& x  B@B@x@ADAEcHaLa =`= @@+ @+= \A[ B@B@x@ADAEGEaL a =  O="@wF@ A/  A B@Bo@x@ADAEaa =`= @`@ $Z珺'Asm|Q-t6Q2a& ʛF+M$+ B@B@x@ADAS  E8LAA! =vaI$_+@`@ @>1A B@DB@x` =A @ELa =쿀="@L@ /A A B@B@x@ADAE&aL!!a =0= @!(@ /NR.' B@B@x@ADAEx "1a =$a"@@ #!|C J@l944 ?5 2^Ia& }F+߀$ B@B@x@ADAEaL2@a = `=G@gd_A @|݀@>A!܀ " B@B@x@ADA#EƗaL) AAa$ =="%@@ /JQC&b' B@B@x@ADA(EMSaBQa) =Y@"*@aQ@ K$*hoS_!lj -,&"EdBw@ q +P A, B@B@x@ADA-E aLR`a. =V`=$_/@PN@ ! @+=0M 1 B@BA@x@ADA2EaL aaa3 =h=+'u4@ @&/D58 6 B@Bw@x@ADA7E"ā bqa8 =a 9@6€@ m# @ ̓|~Nؼ_kRLa& 7:$; B@B@x@ADA<E|aLra= =%`=$_>@*@ ' @>A? @ B@B@x@ADAAEqy&aL i AxaB =@E="C@z@0<J/>D AE B@B-&@x@ADAFE4'a$aG =;2`="H@ 3@ m񡙪—U"ԬTBg*:]KF+Iw2@4+J B@Bm@x@ADAKEb퀈aL =8=a$_M@/@ @>AN[A[O B@BA@x@ADAPEF  aQ =  = R@v@ &+ @/ gCS T B@B@x@ADAUEͥ>bAaV =}I`="W@ᣀ@ ZH&"C[NjXb"M2AXMY B@B@x@ADAZE7^JaLa[ =uT`=$_\@Ѡ@ @Q`AB?A]0 ` ^ B@B@x@ADA_EaL a` =d="a@K\@&J/Ab c B@BJ@x@ADAdEVaae =Ra`="f@@&Z(_O_2q'v3Op42d` Ag"h B@B@x@ADAiE ρ 6haj =l`"k@@ &+>Al Am B@B6h@x@ADAnEˡ @Y ao =Հ=)+p@ ̀@&/Cq̀r B@B@x@ADAsEwmbat =  x`="u@@!O {ta"ڧ$rɗn> 3Av w B@B@x@ADAxE?yaLޗy = `=+$_z@{@ ,W @>{ہ | B@BA@x@ADA}EA  B@B@x@ADAEaL !!a =t="@ˮ@ -&/vC7 B@B-&@x@ADAE!ia$"1a =o`="@5g@ #)6q )6^I˄G.[a& Af$ B@B@x@ADAE!aL2@a =l`=$_@&d@ # @>AcA[ B@B@x@ADAEpaL AAa =<(=+"@@ ! @A/kC  B@B@x@ADAEف BQa =a"@ ؀@%A~o~M >#֙_|"I?a;p Aw׀$ B@Ba@x@ADAEaaLR`a =`="@Ԁ@ &+>A[  B@B @x@ADAEEaLqaqa =Q`=$_@H@ # 'y&%`\?>jt)a& "CL  B@B{W@x@ADAE6aLra = tN`="@E@ ! @+=0  B@B@x@ADAEaL? a = ="+@J@Ƕ/  B@B@x@ADAE a =Ua"@@ #$ĠWM#` CK9܎$͵Zja& cCD!  B@B@x@ADAE taLa =I`=$_@@ P  B@BW@x@ADAEpaL a =z="@ r@ ! @/oCq B@B@x@AD>Ev,aa = '3=!I@*@ĠO* ]$oޔ’Cí5 "C)- B@B@x@ADAE䀈Aa =0 @{'@ &+ @>A&A[ B@B@x@ADAEဈ a =="@@&/4Ca B@BA@x@ADAEKba =`="@_@&癖8D|& Fʂ~o(^&9a& ʯA˚ A B@B6h@x@ADAEUaLa ='`=+$_+@S@ ' @>A[ B@B m @'@x@ADAER(aL6ha =f\="@S@/AZ  B@B@x@ADAED4WaL@Y !!aT>="@x5@ !/UC$ B@B`x@9 A @E "1a =tba!I@@ ' @Au D !A62WܽiM]3)` lAK  B@DB @x@ADA E6caL2@a =sm`=$_ @@ 4 @=A /A[Ƕ B@B@x@ADAEnaL AAa =ޮ="@J@ /-@C  B@B6h@x@ADAE`oaBQa = Qgz`=$_@^@ b(ȞBcur| z]77;?Dt A $+ B@B@x@ADAE {aLR`a = Id`=$_@[@ +# @>A  B@B@x@ADAEaLaaa =="@@ !#V҃/[;C ! B@B:@x@ADA"EvрAbqa# ="ؑa!I$@π@ G ~wBHv^Wm0Mf*9%a& A%΀$& B@Bޗ@x@ADA'EaLra( = "՜`=G 1)@~̀@ !>A*ˀ$+ B@B@x@ADA,EĆaL Aa- == .@@&$/tC/`0 B@B$@x@ADA1EKBaa2 =  I`="+3@_@@ #!+(K%re%; A4?$5 B@B @x@ADA6EAa7 =Ea$_+8@O=@dA9< : B@B@x@ADA;E) a< =ua"=@~ A/XC>5? B@B@x@ADA@E LaA =͹a$_B@4@ +E*Y+` k r|0Fnj \YAC` mD B@B+@x@ADAEEk`aF = ȶ`= G@$@dAH I B@BA@x@ADAJEohaLgaK =;r="L@i@ W/uCM N B@BW@x@ADAOE#aaP =*`= Q@ "@ m(pvAOY{u>%#I٬Sda& ARy!$S B@B@x@ADATE`܀(aU = ' V@@dAW] X B@B@xV 9ADAY @EDف aZ =="[@xڀ@ }/BC\ ] B@DB@x@ADA^Eʔb#a_ =~`="`@⒀@&aى%l1B~4$$^ da*gt n?AaN$b B@B@x@ADAcE5MaLAd =s`= e@Ϗ@dAf. g B@B@x@ADAhEJaL A!i =S= j@IK@ /Ck pl B@B@x@ADAmEaAn = X  #o@@<S(9E[=šֆ^?La/c Ap q B@B<@x@ADArE  As =H a t@@dAu v B@B@x@ADAwE  !!Ax =Ā="y@@%/Cz +{ B@B@x@ADA|Euvb"1A} =)}`="~@t@ vU-4syBx*9ۓ7٪ga& &As B@B@x@ADAE.aL2@A =!z)`="@|q@ !$=p  B@B6h@x@ADAE+*aL AA)A =5= A@,@%/ C_A B@B@x@ADAEJ B"! =5a"@^@ #! v`C A󮏧g-)Ս?<Z9@0P + H@G䀂 + B@B@x@ADAE6aL$R`A =@`=$_@N@ ,W @>ဂ  B@B@x@ADAEAaL aaA =m="@ɝ@ W! @/kdC5 B@B@x@ADAEX3"  bqA =^M`="@3V@ mQXU.7Oƛݻ@d`*/MJ&f AU$+ B@Bm@x@ADAENaLrA =[X`=$_@#S@ ! @>AR  B@B@x@ADAEn YaL A =;=bq@d_@@&$/LC  B@B@x@ADAEȁ A =da"@ǀ@&:4f*DB^/S/RxRa& tƀ$ B@B@x@ADAE_eaLA =o`=$_@À@ ' @>AXA[ B@B@x@ADAEC~paL A ==+)@s@ ! @A/ d B@B@x@ADAE9qaA =v@|`="@7@ 'ݪahTj|QjUY,wn2\a& F+J B@B@x@ADAE4 A =r=a"@4@ !>A.  B@B$@x@ADAE qA =T"@@ : uk58$ -7t"e)ȵ9a& EB  B@B@x@ADAE caLA =G`="@@ !>  B@B@x@ADAE_aL? A =i="+@a@ :$#V>/vF+`!]iK B@B} @x@ADAEtaA =$"`="@@AĠpOot9u3Nva& a  B@BA@x@ADAEӀ A =$_+@x@ # @=:  B@BW@x@ADAE  A =ڀ=+$_@р@ ! @W/g_  B@B@x@ADAEIba =`="@]@&v ڞSWdxe_R6և8 A  B@B@x@ADA!E !@Ta" =JS+`=)#@J@ ! @+=A$ % B@BDK@x@ADA&E,aL Aa' == (@@$/]C)* B@B@x@ADA+Esa, =(7@"-@@ P#!A;?]vGZG a

    @ KA=.$> B@B+@x@ADA?E逈a@ =4Z  A@M,@ ! @+=AB+@C B@B@x@ADADE怈 aE =`="F@@ W/AgCG3H B@Bu + M@x@ADAIE[baJ = ֨f`="K@2@A8h-yY^؜`dpM }AL$M B@B@x@ADANEZgaL"O =ƥq`="P@"@ #>Q R B@B@x@ADASElWraL) aT =@a="U@X@ !'+A/CVW B@B@x@ADAXEs aY = 0~`=!IAZ@@ ' @JW$d17sqp3b,9v A[s \ B@B@x@ADA]E^ˀ a^ =@$__@ @ -&! @=A`W Aa B@BA@x@ADAbEBȡ @Y !!Xc =҉a"d@rɀ@ #/Ce f B@B@x@ADAgEȃaL"1Ah =u`=$_i@܁@ # @A4spYS*h?fr%OR .,"0a& ajHk B@B@x@ADAlE3<@T2@am =p`=$_n@~@dAo, Ap B@B@x@ADAqE9aL AAar =B="s@G:@ / gt u B@B@x@ADAvE $BQaw =F@"+x@@ M&v$pt,+ϐ6HU>ͼ@ F+y -z B@B@x@ADA{EaL$R`a| =`= }@@dA~  B@B 0`@x@ADAE쩸aLaaa == @@ /DK B@B@x@ADAEse@TAbqa ='l`="+@c@&-}4b/ {ϱp?wJD,@ Db$ B@B@x@ADAEaLra =i`= @w`@ @>_  B@B@x@ADAEaL Aa =$="@@&/e(C] +Edau XF#^ B@B} +$@x@ADAEHց a =@"@\Ԁ@&$]yUta,.MhWR~T@ VӀ+ B@B@x@ADAEaLa =!`="@Pр@dAР  B@B #A@x@ADAEaL a =g= A@ƌ@&/;D2 B@B@x@ADAEG a =M`="@1E@&/SM"탊,>ոGc9AD + B@B 4@x@ADAEa =J)@!B@dAA  B@B@x@ADAEl a =@ @~ / C  B@B@x@ADAELa = #@@ +tfXP_*gCY9A49U #Ar$+ B@B+@x@ADAE]p aLa =`=+ @@ ! @>V  B@B@x@ADAEAmaL a =w="A@qn@ :/zC A B@B@x@ADAE(aa =t/"`= +@&@&a>հQbu 4XQۊho4a& AG$ B@B@x@ADAE2 /A =,-a$_A@#@ # @>+A[ B@B@x@ADAEށ a =="@F߀@ }!#V/C  B@B}@x@ADAE.ba =U9`="@@ 'g~~_+5 BFrLU x1a& A B@B@x@ADAER:aL a =ED`="@@ !>A  B@B$@x@ADAENEaL)7!!a = X="+@P@ S/CO B@B@x@ADAEr F "1a =&Q`="@@ #!+*.gU^M QɫuȞ~)ne ,C m B@B@x@ADAE€g2@a =\a$_@v@ # @>A  B@B@x@ADAE; AQa = ha"@[y@ W!>yfPEepg}^J KICx  B@B@x@ADAE3iaLR`a =~s`="@Kv@ $ @>u  B@B@x@ADAE0taL aaa =^:="@1t`~ ! @W/eC2 B@Bs @x@ADAEta bqa =`="@0@& ǝÞ 26dAL mv*Uka& n`A适$A B@B@x@ADAEaLra =`=$_@ @ &+ @>V栂 O B@B@x@ADAEkaL a = 3=+$_@@ /LC B@B@x@ADAE\aWa =c`=" @[@`Bg94G+ [ p DjAyx/a& } qZ A B@B.@x@ADA E\aLa =``="@W@ #>AUA[ B@B@x@ADAE@aL a =  = @p@ ! @+KA/'}  B@B@x@ADAÉ a =tԮa"@ˀ@ 'u:tZ]hH_Q2$^F+G@4 B@B+@x@ADAE1aLa = oѹ`=$_@Ȁ@ ! @>A+  B@B@x@ADA EaL) a! =ތ=""@E@&A/C# $ B@ BP@x@ADA%E>aa& =LE`="'@<@ +#玢d8 %M.YĔނ[_:! }1( ) B@B+@x@ADA*E a+ =DBa",@9@ '>A- . B@B@x@ADA/E @Y a0 = {="+1@@ C! @+A/123 B@B@x@ADA4Eqbua5 =*`="6@@ 57N==AU%HI쳼a& ϿF+7 8 B@B:@x@ADA9EgaL A:V `=+$_;@y@ ! @>A<թ = B@B}@x@ADA>EdaL a? = n=$_@@e@&/9CA\B B@B@x@ADACEF a$aD =&`="E@Z@&ApMBes҂ ^r'g|AF$+G B@B@x@ADAHE؀ aI =#G@Qd_J@J@ '>AK L B@B@x@ADAMEՀ !!aN =a߀="O@ր@ !$+A/CP1րNQ B@BP 5@ADAR @Eb"1aS =ȗ `=!IT@0@&s%]yhr֩t|/Hp|g` AU$V B@DBP@x@ADAWEI aL2@aX =Ĕ`=$_Y@ @ `@>AZA[d[ B@B@x@ADA\EjFaL AAa] = 7P="^@G@&/C_` B@B@x@ADAaEaBQab =$`="c@@ #M=NqIWkyd@J,wAdq#`$e B@B%o@x@ADAfE[ R`ag =/`"h@.`@ '$>AiU j B@B@x@ADAkE?L) aaal =="m@o@&$/Cn o B@B$@x@ADApEr0bbqaq =wy;`= r@p@ +Gbk4MIp۞MЛ|x?9AsFA[t B@B@x@ADAuE1+Ax*A[y B@BA@x@ADAzE(GaL@Y a{ =1= |@I)@&/nC} ~ B@B@x@ADAE a =SRa"@@A䟬tZ3k/[ AIIz?S~7`7|A$ B@B@x@ADAESaLa =C]`=$_@ހ@dA݀  B@B@x@ADAE^aL a == @@ * @+/C !k B@B@x@ADAEpT_a$a =[j`="A@R@ AqO#HӃ}"[UyPuA%/Q@4a@ B@B@x@ADAE kaLAa =Xu`= @uO@dAN  B@B@x@ADAE vaL a =="@ @ /LD[ B@Bc'@x@ADAEFŁ Aa =ˁa"@ZÀ@ Br(\CnAm3䗐@ X&a& ߨA€$ B@Bg@x@ADAE}aLa =Ȍ`="@I@$=DK  B@B@x@ADAEzaL a =d="@{@ }/)C0 +A B@B@x@ADAE6aa =<`=*W@/4@&mh}dS=wq|−(dƄ5ta& OA3+ B@BJ@x@ADAE8[A =9$_@1@dA0A[ B@B@x@ADAEi뀈 a =.="@@+#V+/cCA[ B@BW@x@ADAEba =`="+@@ UEnY#>yS淵ZG^荢fB3DK<US͞a& AE$+ B@B@x@ADAE0Ё 2@a =ma$_@@dA)  B@B@x@ADAE͡ @Y AAa =ր="@D΀@ /OC  B@B{%o@x@ADAEbBQa =G`= +@@&AR:_P a/wWCE%~C UQ8& ykA$ B@BA@x@ADAEAaL$R`a =`= @@dA  B@B@x@ADAE=aLaaa =G="@?@ /GC>W B@Bt@x@ADAEpbqa =,a"@`@ PYu?#!t ? ~Հ  B@B@x@ADA Ei1aL a =1= @@ ! @Ƕ/zE B@B@x@ADAEK2aa =R=`=!I@J@gĠqy:~RKFހCa& GoI$A B@BV@x@ADAEZ>aLa =OH`=$_@F@ &+ @K=:WA[d B@B@x@ADAE>IaL a = ="@n@ / C #a B@B@x@ADAEż Wa =tTa"!@غ@ #$Ġ>3EPwϪ]j{4V=k 5#nA"D # B@B@x@ADA$E/uUaLW"% =m_`="&@ȷ@ #>A'( c @( B@B@x@ADA)Er`aLa* ={="++@Cs@ ! @A/BC, - B@B@x@ADA.E-aa-&a/ = ^4l`="0@+@ AﲴV5[|Lr &-12 B@B@x@ADA3E  a4 =B1wa$_5@(@ ! @Q`B?A6' 7 B@B@x@ADA8E ) A!!a9 ==":@@ /"Y;。< B@B@x@ADA=EoxbA"1a> =`="?@@ +#+A?\zr1$߭O2+QYLa& C]@ +A B@B+@x@ADABEVaL2@aC =`="D@s@ #>AEӘ F B@B@x@ADAGESaL AAaH =]="+I@T@҃/^CJYAK B@B@x@ADALEDaBQaM =`="N@X @ +DsM|mO`qhsk]ypa& {O P B@BJ@x@ADAQEǀR`aR =a+$_S@L @ @JAT U B@B}@x@ADAVE aaaW =W΀= X@ŀ@g @/Y/A[mZ B@B@x@ADA[Eb$bqa\ =ֆ`="]@-~@ +mGD@~c+AkNa& rF+^}$+_ B@B@x@ADA`E8aLraa =ƒ`=+$_b@{@ PAc}z d B@B@x@ADAeEh5aL af =A?=$_g@6@ ! @ /^ChAi B@B}@x@ADAjE Aak =a"l@@ ҎbAB:4N> a& dmo$n B@Bx@x@ADAoEYaLap =`= q@@ ! @>rRA[s B@B@x@ADAtE=aL au = ="v@m@ />dw #aAx B@B6h@x@ADAyEaaaz =ph`="{@_@ #+"^0úF,dQpHrɈa& <|D} B@BJ@x@ADA~E.aLa =e`="@\@ #>( A B@Bu@x@ADAEaL) a = ="@B@ `!/HIA  B@Bv@x@ADAEҁ a =Fa!I@Ѐ@ $I Аrl"X$w=h*a& }A + B@B@x@ADAEaLa =A@$_@̀@ ! @JÀ  B@BA@x@ADAEaL@Yh5 a == @@ /C  B@B@x@ADAEnCaa =J`="@A@ #!'# sf3eo8^,dkK>Y%A@$ B@B@x@ADAEA =G$_@r>@ # @>A=  B@B@x@ADAE Aa ="@~ ! @/CY  B@B@x@ADAECL$a ='a"A@W@&^y8E2[' g txC5a&  %Añm B@B@x@ADAEl(aL a = 2`=$_@H@ &+ @> ȸ B@B@x@ADAEi3aLA!!a =fs="@j@ `/ C2$ B@B1@x@ADAE%4aA"1a =+?`="@-#@ +#+AvC.fڑ#2A}  B@B@x@ADAEgڀ) uAAa =3="@ۀ@ !A/r nA B@B@x@ADAEKbBQa =V`=!I@@*ĤHR\FRY,^tKUSh4E/F+n  B@B+@x@ADAEXNWaLR`a =a`=$_@@ }&+ @JARA[c @ B@B@x@ADAES ` B@B@x@ADA EaL@Y) Aa =b=  @@&/ 1 +A B@B@x@ADAEʁ a =a"+@0Ȁ@ #!I)9M,K WFحN֙tZрyda& dC]ǀ  B@B@x@ADAEaLA zA =`=$_@ŀ@dA|Ā  B@B@x@ADAEfaL A! = /="@@ A/+C B@Bu@x@ADAE:a$A =A`=" @9@ +|+ֺeڔA¤w|(uu A!m8 " B@B+@x@ADA#EX+A$ => %@5@dA&Q ' B@B@x@ADA(E< @Y !!A) ==+'u+*@l@ }  =AW #xC+ , B@B@x@ADA-E«b"1A. =s`="/@֩@1A0B$+1 B@B@x@ADA2E-daLA2@A3 =k@ 4@Ʀ@dA5&@Ȥ6 B@B@x@ADA7EaaL NAj`=8=`=G%  A B99=$`=`3{ `9@8:80V# 8H`8;88 7`8<8j q=8m68q>88>?8t,`8ֵq ' d2@ =@U`=\ F`rKA@@@S`6@@`@AÀ@|@{BJcJ J `J@  -FC B@Bo B  @' @x@A ARDEIa$ J 8DE =@@[i=: `!RRUF@@ `@# P@ K@(@5!+JF GT `T\i\H B@BA~`@ \@x@AA\IEXa\ 5E\@a\JX@\`=3K@@ d!\""@BLBBBJM B@JBo@١ `  J@x@AAJNEπEUO=Tހ=``yP8#a` 8Q88 R =! 3,O`v!S@M@@{%BTJJ  a U B@B@x@R =BV @EacMW =@ր= =#MX@#@ #M$`! !@BYBJZ B@B@a@ @x@AAJ[EmÀ> < :H \ =@paJ$W! ]@@@S~@- @ HAR^JJ J `J-GH_ B@BpE$ H@x@A AR`E5Ya =@Y=!R"\R'Hb@u@ 2$ @HcTՠ+ T\i\d B@BC\%@x@AA\eExbqHAJcf =@z=#g@y@~!\""@BhBXB0+BJi B@Bp J@x@f =AJj @EB4aJaJk =@p==J! l@@@S "ARmJqJ n B@Bt d@x@A ,oEPaR aRp =@P=lq@@ "#: @, K )".  C !+L "c  rAsrk s B@B  @x@A-AstEfasZasu = `%$`=$v@#@!s#@BwB"Bx B@B@x@AAJyE݀aJz =JJ$J! {@@J "dAR|JJ$ } B@RB@x@A AR~ETa aR =RQW= '@-V@@Sb"$)`]K  xANFUF B@NB `@x@AANExaNaN =@̀="@.@ V!N B̀ B B@B@x@AAJEJaJ =JE`=J#@PD@ "BJCJ  B@RB@x@A ARE aR =R2R#a@~H' ;KSF!M305-Joyst3ck0`.` B@hBL @x@A"AhE*)1 =hcwh#h'@v@ pB<B B@JBN@x@AAJE&1a Jb =J="@@ *, _4AJ]JA# B@RB@x@A ARE4aR aR =Rc= RA@ ~R B B@BDJK @x@AAcasA! Q =5c  1&]@}3@,WJ2J NV B@NBV@x@A ARE퀈V  aR = = V!R]2W@@6WT\T\ : B@BB%E@x@AA\EGdbq N  a\ =@ =\&Q@w@~QB BQ B@BQ@x@AAJEdeaJQ  aJ =JM#f`=,n"J#@!@!, AJ$J  B@RB@x@A AREۀ aR =Rހ=RL@݀@@S" @` WuKw܀K¦ B@SB!@x@A ASEagbh SaS =@&=S#@@~:!S @ IBB B@BJ@x@AAJERhaJx =J3i`=J#@@!C"8)AJ J  B@RBI@x@A AREɀ b =RЀ=R#@3VI@2ˀ@ " @3Ikʀk0 B@sBJS@x@A-AsE|jb sas =A?A=s#s$@@~!s @ BB B@B@x@AAJEAkaJaJ =J^=J$@@ @{'%AJ5J Z B@RB@x@A ARElaRVaR =@ր=R#@Q@ @ B B B@B 5@x@AAJEsmaJaJ =J1n`=J#J"@M0@! BJ/J  B@RB@x@A AREꀈ ;aR =R= Rb\Rp@@@~; ZB B@B B@x@AA+obw 1: 8a = )$ q`= @@ "BVJJ  B@NB@x@A ARE RA@aR =@=R$V@ـ@' @Q }@'K u&5F 0 1 2 5u%F;e 9Beu %E ) ^a@G[ 1! B@B 4@x@ADAEErbj a ==f@@ 6(!{& !]A] a!! Ame *m B@mB :m@x@A'AmEOsaml =mc t`=m"@ @ u" @ B;B B@JB@x@AAJEƀQ> !.=-`@<Li@k-|axTk,,? L8=5D k u7`=u - A#@@ @ @ @`@(_' @# `@ @   )   !@   / @8Bitd  @B5!! P@8@@ A ` " `g XG@  a>ABAE"}% @HZ`i `n@8!`$`M a@I@;@c1`M }Р"I@s`@ @E$ 1 @5i&A]fwupd-1.9.16/plugins/ebitdo/ebitdo-legacy.quirk000066400000000000000000000005571460375044200214650ustar00rootroot00000000000000# This is the ID assigned to STMicroelectronics, and also seems to be used by other vendors who # did not change the default from the devkit. Install this quirk file if your 8bitdo controller # uses the legacy bootloader from 2018. # # See https://github.com/fwupd/fwupd/issues/4180 for more information [USB\VID_0483&PID_5750] Plugin = ebitdo Flags = is-bootloader fwupd-1.9.16/plugins/ebitdo/ebitdo.quirk000066400000000000000000000040721460375044200202170ustar00rootroot00000000000000# bootloader [USB\VID_2DC8&PID_5750] Plugin = ebitdo Flags = is-bootloader InstallDuration = 120 [USB\VID_0483&PID_5760] Plugin = ebitdo Flags = is-bootloader,will-disappear InstallDuration = 120 # FC30 [USB\VID_1235&PID_AB11] Plugin = ebitdo Flags = ~is-bootloader [USB\VID_2DC8&PID_AB11] Plugin = ebitdo Flags = ~is-bootloader InstallDuration = 120 # NES30 [USB\VID_1235&PID_AB12] Plugin = ebitdo Flags = ~is-bootloader [USB\VID_2DC8&PID_AB12] Plugin = ebitdo Flags = ~is-bootloader InstallDuration = 120 # SFC30 [USB\VID_1235&PID_AB21] Plugin = ebitdo Flags = ~is-bootloader [USB\VID_2DC8&PID_AB21] Plugin = ebitdo Flags = ~is-bootloader InstallDuration = 120 # SNES30 [USB\VID_1235&PID_AB20] Plugin = ebitdo Flags = ~is-bootloader [USB\VID_2DC8&PID_AB20] Plugin = ebitdo Flags = ~is-bootloader InstallDuration = 120 # FC30PRO [USB\VID_1002&PID_9000] Plugin = ebitdo Flags = ~is-bootloader [USB\VID_2DC8&PID_9000] Plugin = ebitdo Flags = ~is-bootloader InstallDuration = 120 # NES30PRO [USB\VID_2002&PID_9000] Plugin = ebitdo Flags = will-disappear [USB\VID_2DC8&PID_9001] Plugin = ebitdo Flags = will-disappear InstallDuration = 120 # FC30_ARCADE [USB\VID_8000&PID_1002] Plugin = ebitdo Flags = ~is-bootloader [USB\VID_2DC8&PID_1002] Plugin = ebitdo Flags = ~is-bootloader InstallDuration = 120 # SF30 PRO/SN30 PRO ## Dinput mode (Start + B) [USB\VID_2DC8&PID_6000] Plugin = ebitdo Flags = will-disappear [USB\VID_2DC8&PID_6001] Plugin = ebitdo Flags = will-disappear InstallDuration = 120 # SN30 PRO+ ## Dinput mode (Start + B) [USB\VID_2DC8&PID_6002] Plugin = ebitdo Flags = will-disappear InstallDuration = 120 # M30 [USB\VID_2DC8&PID_5006] Plugin = ebitdo Flags = ~is-bootloader InstallDuration = 120 # SN30v2 [USB\VID_2DC8&PID_9012] Plugin = ebitdo Flags = ~is-bootloader InstallDuration = 120 # SN30 Pro for Android [USB\VID_2DC8&PID_2100] Plugin = ebitdo Flags = ~is-bootloader InstallDuration = 120 [USB\VID_2DC8&PID_2101] Plugin = ebitdo Flags = ~is-bootloader InstallDuration = 120 # N30 Pro 2 [USB\VID_2DC8&PID_9015] Plugin = ebitdo Flags = ~is-bootloader InstallDuration = 120 fwupd-1.9.16/plugins/ebitdo/fu-ebitdo-device.c000066400000000000000000000505111460375044200211520ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ebitdo-device.h" #include "fu-ebitdo-firmware.h" #include "fu-ebitdo-struct.h" struct _FuEbitdoDevice { FuUsbDevice parent_instance; guint32 serial[9]; }; G_DEFINE_TYPE(FuEbitdoDevice, fu_ebitdo_device, FU_TYPE_USB_DEVICE) #define FU_EBITDO_USB_TIMEOUT 5000 /* ms */ #define FU_EBITDO_USB_BOOTLOADER_EP_IN 0x82 #define FU_EBITDO_USB_BOOTLOADER_EP_OUT 0x01 #define FU_EBITDO_USB_RUNTIME_EP_IN 0x81 #define FU_EBITDO_USB_RUNTIME_EP_OUT 0x02 #define FU_EBITDO_USB_EP_SIZE 64 /* bytes */ static gboolean fu_ebitdo_device_send(FuEbitdoDevice *self, FuEbitdoPktType type, FuEbitdoPktCmd subtype, FuEbitdoPktCmd cmd, const guint8 *in, gsize in_len, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length; guint8 ep_out = FU_EBITDO_USB_RUNTIME_EP_OUT; g_autoptr(GByteArray) st_hdr = fu_struct_ebitdo_pkt_new(); g_autoptr(GError) error_local = NULL; fu_byte_array_set_size(st_hdr, FU_EBITDO_USB_EP_SIZE, 0x0); /* different */ if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) ep_out = FU_EBITDO_USB_BOOTLOADER_EP_OUT; /* check size */ if (in_len > 64 - 8) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "input buffer too large"); return FALSE; } /* packet[0] is the total length of the packet */ fu_struct_ebitdo_pkt_set_type(st_hdr, type); fu_struct_ebitdo_pkt_set_subtype(st_hdr, subtype); /* do we have a payload */ if (in_len > 0) { fu_struct_ebitdo_pkt_set_cmd_len(st_hdr, in_len + 3); fu_struct_ebitdo_pkt_set_cmd(st_hdr, cmd); fu_struct_ebitdo_pkt_set_payload_len(st_hdr, in_len); if (!fu_memcpy_safe(st_hdr->data, st_hdr->len, FU_STRUCT_EBITDO_PKT_SIZE, /* dst */ in, in_len, 0x0, /* src */ in_len, error)) return FALSE; fu_struct_ebitdo_pkt_set_pkt_len(st_hdr, in_len + 7); } else { fu_struct_ebitdo_pkt_set_cmd_len(st_hdr, in_len + 1); fu_struct_ebitdo_pkt_set_cmd(st_hdr, cmd); fu_struct_ebitdo_pkt_set_pkt_len(st_hdr, 5); } fu_dump_raw(G_LOG_DOMAIN, "->DEVICE", st_hdr->data, st_hdr->len); /* get data from device */ if (!g_usb_device_interrupt_transfer(usb_device, ep_out, st_hdr->data, st_hdr->len, &actual_length, FU_EBITDO_USB_TIMEOUT, NULL, /* cancellable */ &error_local)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to send to device on ep 0x%02x: %s", (guint)FU_EBITDO_USB_BOOTLOADER_EP_OUT, error_local->message); return FALSE; } return TRUE; } static gboolean fu_ebitdo_device_receive(FuEbitdoDevice *self, guint8 *out, gsize out_len, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); guint8 packet[FU_EBITDO_USB_EP_SIZE] = {0}; gsize actual_length; guint8 ep_in = FU_EBITDO_USB_RUNTIME_EP_IN; g_autoptr(GByteArray) st_hdr = NULL; g_autoptr(GError) error_local = NULL; /* different */ if (fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) ep_in = FU_EBITDO_USB_BOOTLOADER_EP_IN; /* get data from device */ if (!g_usb_device_interrupt_transfer(usb_device, ep_in, packet, sizeof(packet), &actual_length, FU_EBITDO_USB_TIMEOUT, NULL, /* cancellable */ &error_local)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to retrieve from device on ep 0x%02x: %s", (guint)ep_in, error_local->message); return FALSE; } /* debug */ fu_dump_raw(G_LOG_DOMAIN, "<-DEVICE", packet, actual_length); st_hdr = fu_struct_ebitdo_pkt_parse(packet, sizeof(packet), 0x0, error); if (st_hdr == NULL) return FALSE; /* get-version (bootloader) */ if (fu_struct_ebitdo_pkt_get_type(st_hdr) == FU_EBITDO_PKT_TYPE_USER_CMD && fu_struct_ebitdo_pkt_get_subtype(st_hdr) == FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA && fu_struct_ebitdo_pkt_get_cmd(st_hdr) == FU_EBITDO_PKT_CMD_FW_GET_VERSION) { if (out != NULL) { if (fu_struct_ebitdo_pkt_get_payload_len(st_hdr) < out_len) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "payload too small, expected %" G_GSIZE_FORMAT " got %u", out_len, fu_struct_ebitdo_pkt_get_payload_len(st_hdr)); return FALSE; } if (!fu_memcpy_safe(out, out_len, 0x0, /* dst */ packet, sizeof(packet), FU_STRUCT_EBITDO_PKT_SIZE, /* src */ out_len, error)) return FALSE; } return TRUE; } /* get-version (firmware) -- not a packet, just raw data! */ if (fu_struct_ebitdo_pkt_get_pkt_len(st_hdr) == FU_EBITDO_PKT_CMD_GET_VERSION_RESPONSE) { if (out != NULL) { if (out_len != 4) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "outbuf size wrong, expected 4 got %" G_GSIZE_FORMAT, out_len); return FALSE; } if (!fu_memcpy_safe(out, out_len, 0x0, /* dst */ packet, sizeof(packet), 0x1, /* src */ 4, error)) return FALSE; } return TRUE; } /* verification-id response */ if (fu_struct_ebitdo_pkt_get_type(st_hdr) == FU_EBITDO_PKT_TYPE_USER_CMD && fu_struct_ebitdo_pkt_get_subtype(st_hdr) == FU_EBITDO_PKT_CMD_VERIFICATION_ID) { if (out != NULL) { if (fu_struct_ebitdo_pkt_get_cmd_len(st_hdr) != out_len) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "outbuf size wrong, expected %" G_GSIZE_FORMAT " got %i", out_len, fu_struct_ebitdo_pkt_get_cmd_len(st_hdr)); return FALSE; } if (!fu_memcpy_safe(out, out_len, 0x0, /* dst */ packet, sizeof(packet), FU_STRUCT_EBITDO_PKT_SIZE - 3, /* src */ fu_struct_ebitdo_pkt_get_cmd_len(st_hdr), error)) return FALSE; } return TRUE; } /* update-firmware-data */ if (fu_struct_ebitdo_pkt_get_type(st_hdr) == FU_EBITDO_PKT_TYPE_USER_CMD && fu_struct_ebitdo_pkt_get_subtype(st_hdr) == FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA && fu_struct_ebitdo_pkt_get_payload_len(st_hdr) == 0x00) { if (fu_struct_ebitdo_pkt_get_cmd(st_hdr) != FU_EBITDO_PKT_CMD_ACK) { g_set_error( error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "write failed, got %s", fu_ebitdo_pkt_cmd_to_string(fu_struct_ebitdo_pkt_get_cmd(st_hdr))); return FALSE; } return TRUE; } /* unhandled */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unexpected device response"); return FALSE; } static gchar * fu_ebitdo_device_convert_version(FuDevice *device, guint64 version_raw) { return g_strdup_printf("%u.%02u", (guint)version_raw / 100, (guint)version_raw % 100); } static gboolean fu_ebitdo_device_validate(FuEbitdoDevice *self, GError **error) { const gchar *ven; const gchar *allowlist[] = {"8Bitdo", "8BitDo", "SFC30", NULL}; /* this is a new, always valid, VID */ if (fu_usb_device_get_vid(FU_USB_DEVICE(self)) == 0x2dc8) return TRUE; /* verify the vendor prefix against a allowlist */ ven = fu_device_get_vendor(FU_DEVICE(self)); if (ven == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "could not check vendor descriptor: "); return FALSE; } for (guint i = 0; allowlist[i] != NULL; i++) { if (g_str_has_prefix(ven, allowlist[i])) return TRUE; } g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "vendor '%s' did not match allowlist, " "probably not a 8BitDo device…", ven); return FALSE; } static gboolean fu_ebitdo_device_open(FuDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); FuEbitdoDevice *self = FU_EBITDO_DEVICE(device); /* FuUsbDevice->open */ if (!FU_DEVICE_CLASS(fu_ebitdo_device_parent_class)->open(device, error)) return FALSE; /* open, then ensure this is actually 8BitDo hardware */ if (!fu_ebitdo_device_validate(self, error)) return FALSE; if (!g_usb_device_claim_interface(usb_device, 0, /* 0 = idx? */ G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { return FALSE; } /* success */ return TRUE; } static gboolean fu_ebitdo_device_setup(FuDevice *device, GError **error) { FuEbitdoDevice *self = FU_EBITDO_DEVICE(device); guint32 version_tmp = 0; guint32 serial_tmp[9] = {0}; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_ebitdo_device_parent_class)->setup(device, error)) return FALSE; /* in firmware mode */ if (!fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { if (!fu_ebitdo_device_send(self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_GET_VERSION, 0, NULL, 0, /* in */ error)) { return FALSE; } if (!fu_ebitdo_device_receive(self, (guint8 *)&version_tmp, sizeof(version_tmp), error)) { return FALSE; } fu_device_set_version_raw(FU_DEVICE(self), GUINT32_FROM_LE(version_tmp)); return TRUE; } /* get version */ if (!fu_ebitdo_device_send(self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA, FU_EBITDO_PKT_CMD_FW_GET_VERSION, NULL, 0, /* in */ error)) { return FALSE; } if (!fu_ebitdo_device_receive(self, (guint8 *)&version_tmp, sizeof(version_tmp), error)) { return FALSE; } fu_device_set_version_raw(device, GUINT32_FROM_LE(version_tmp)); /* get verification ID */ if (!fu_ebitdo_device_send(self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_GET_VERIFICATION_ID, 0x00, /* cmd */ NULL, 0, error)) { return FALSE; } if (!fu_ebitdo_device_receive(self, (guint8 *)&serial_tmp, sizeof(serial_tmp), error)) { return FALSE; } for (guint i = 0; i < 9; i++) self->serial[i] = GUINT32_FROM_LE(serial_tmp[i]); /* success */ return TRUE; } static gboolean fu_ebitdo_device_detach(FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(FwupdRequest) request = fwupd_request_new(); /* not required */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; /* generate a message if not already set from the metadata */ if (fu_device_get_update_message(device) == NULL) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); g_autoptr(GString) msg = g_string_new(NULL); g_string_append(msg, "Not in bootloader mode: Disconnect the controller, "); switch (g_usb_device_get_pid(usb_device)) { case 0xab11: /* FC30 */ case 0xab12: /* NES30 */ case 0xab21: /* SFC30 */ case 0xab20: /* SNES30 */ case 0x9012: /* SN30v2 */ g_string_append(msg, "hold down L+R+START for 3 seconds until " "both LED lights flashing, "); break; case 0x9000: /* FC30PRO */ case 0x9001: /* NES30PRO */ g_string_append(msg, "hold down RETURN+POWER for 3 seconds until " "both LED lights flashing, "); break; case 0x1002: /* FC30-ARCADE */ g_string_append(msg, "hold down L1+R1+HOME for 3 seconds until " "both blue LED and green LED blink, "); break; case 0x6000: /* SF30 pro: Dinput mode */ case 0x6001: /* SN30 pro: Dinput mode */ case 0x6002: /* SN30 pro+: Dinput mode */ case 0x028e: /* SF30/SN30 pro: Xinput mode */ case 0x5006: /* M30 */ g_string_append(msg, "press and hold L1+R1+START for 3 seconds " "until the LED on top blinks red, "); break; case 0x2100: /* SN30 for Android */ case 0x2101: /* SN30 for Android */ g_string_append(msg, "press and hold LB+RB+Xbox buttons " "both white LED and green LED blink, "); break; case 0x9015: /* N30 Pro 2 */ g_string_append(msg, "press and hold L1+R1+START buttons " "until the yellow LED blinks, "); break; default: g_string_append(msg, "do what it says in the manual, "); break; } g_string_append(msg, "then re-connect controller"); fu_device_set_update_message(device, msg->str); } /* wait */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* emit request */ fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG); fwupd_request_set_message(request, fu_device_get_update_message(device)); fwupd_request_set_image(request, fu_device_get_update_image(device)); return fu_device_emit_request(device, request, progress, error); } static gboolean fu_ebitdo_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuEbitdoDevice *self = FU_EBITDO_DEVICE(device); const guint8 *buf; gsize bufsz = 0; guint32 serial_new[3]; g_autoptr(GBytes) fw_hdr = NULL; g_autoptr(GBytes) fw_payload = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(FuChunkArray) chunks = NULL; const guint32 app_key_index[16] = {0x186976e5, 0xcac67acd, 0x38f27fee, 0x0a4948f1, 0xb75b7753, 0x1f8ffa5c, 0xbff8cf43, 0xc4936167, 0x92bd03f0, 0x5573c6ed, 0x57d8845b, 0x827197ac, 0xb91901c9, 0x3917edfe, 0xbcd6344f, 0xcf9e23b5}; /* not in bootloader mode */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NEEDS_USER_ACTION, "Not in bootloader mode"); return FALSE; } /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "header"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 97, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 2, NULL); /* get header and payload */ fw_hdr = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_HEADER, error); if (fw_hdr == NULL) return FALSE; fw_payload = fu_firmware_get_bytes(firmware, error); if (fw_payload == NULL) return FALSE; /* set up the firmware header */ buf = g_bytes_get_data(fw_hdr, &bufsz); if (!fu_ebitdo_device_send(self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA, FU_EBITDO_PKT_CMD_FW_UPDATE_HEADER, buf, bufsz, error)) { g_prefix_error(error, "failed to set up firmware header: "); return FALSE; } if (!fu_ebitdo_device_receive(self, NULL, 0, error)) { g_prefix_error(error, "failed to get ACK for fw update header: "); return FALSE; } fu_progress_step_done(progress); /* flash the firmware in 32 byte blocks */ chunks = fu_chunk_array_new_from_bytes(fw_payload, 0x0, 32); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_debug("writing %u bytes to 0x%04x of 0x%04x", fu_chunk_get_data_sz(chk), fu_chunk_get_address(chk), fu_chunk_get_data_sz(chk)); if (!fu_ebitdo_device_send(self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA, FU_EBITDO_PKT_CMD_FW_UPDATE_DATA, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to write firmware @0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } if (!fu_ebitdo_device_receive(self, NULL, 0, error)) { g_prefix_error(error, "failed to get ACK for write firmware @0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } fu_progress_set_percentage_full(fu_progress_get_child(progress), i + 1, fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* set the "encode id" which is likely a checksum, bluetooth pairing * or maybe just security-through-obscurity -- also note: * SET_ENCODE_ID enforces no read for success?! */ serial_new[0] = self->serial[0] ^ app_key_index[self->serial[0] & 0x0f]; serial_new[1] = self->serial[1] ^ app_key_index[self->serial[1] & 0x0f]; serial_new[2] = self->serial[2] ^ app_key_index[self->serial[2] & 0x0f]; if (!fu_ebitdo_device_send(self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA, FU_EBITDO_PKT_CMD_FW_SET_ENCODE_ID, (guint8 *)serial_new, sizeof(serial_new), error)) { g_prefix_error(error, "failed to set encoding ID: "); return FALSE; } /* mark flash as successful */ if (!fu_ebitdo_device_send(self, FU_EBITDO_PKT_TYPE_USER_CMD, FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA, FU_EBITDO_PKT_CMD_FW_UPDATE_OK, NULL, 0, error)) { g_prefix_error(error, "failed to mark firmware as successful: "); return FALSE; } if (!fu_ebitdo_device_receive(self, NULL, 0, &error_local)) { g_prefix_error(&error_local, "failed to get ACK for mark firmware as successful: "); if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) { fu_device_set_remove_delay(device, 0); g_debug("%s", error_local->message); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } fu_progress_step_done(progress); /* success! */ return TRUE; } static gboolean fu_ebitdo_device_attach(FuDevice *device, FuProgress *progress, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); g_autoptr(GError) error_local = NULL; /* when doing a soft-reboot the device does not re-enumerate properly * so manually reboot the GUsbDevice */ if (!g_usb_device_reset(usb_device, &error_local)) { g_prefix_error(&error_local, "failed to force-reset device: "); if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) { fu_device_set_remove_delay(device, 0); g_debug("%s", error_local->message); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* not all 8bito devices come back in the right mode */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) fu_device_set_remove_delay(device, 0); else fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* success! */ return TRUE; } static gboolean fu_ebitdo_device_probe(FuDevice *device, GError **error) { /* allowed, but requires manual bootloader step */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_remove_delay(device, FU_DEVICE_REMOVE_DELAY_USER_REPLUG); /* set name and vendor */ fu_device_set_summary(device, "A redesigned classic game controller"); fu_device_set_vendor(device, "8BitDo"); /* add a hardcoded icon name */ fu_device_add_icon(device, "input-gaming"); /* only the bootloader can do the update */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { fu_device_add_counterpart_guid(device, "USB\\VID_0483&PID_5750"); fu_device_add_counterpart_guid(device, "USB\\VID_2DC8&PID_5750"); } /* success */ return TRUE; } static void fu_ebitdo_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 97, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_ebitdo_device_init(FuEbitdoDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.8bitdo"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_request_flag(FU_DEVICE(self), FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_EBITDO_FIRMWARE); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); } static void fu_ebitdo_device_class_init(FuEbitdoDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_ebitdo_device_write_firmware; klass_device->setup = fu_ebitdo_device_setup; klass_device->detach = fu_ebitdo_device_detach; klass_device->attach = fu_ebitdo_device_attach; klass_device->open = fu_ebitdo_device_open; klass_device->probe = fu_ebitdo_device_probe; klass_device->set_progress = fu_ebitdo_set_progress; klass_device->convert_version = fu_ebitdo_device_convert_version; } fwupd-1.9.16/plugins/ebitdo/fu-ebitdo-device.h000066400000000000000000000004471460375044200211620ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_EBITDO_DEVICE (fu_ebitdo_device_get_type()) G_DECLARE_FINAL_TYPE(FuEbitdoDevice, fu_ebitdo_device, FU, EBITDO_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/ebitdo/fu-ebitdo-firmware.c000066400000000000000000000055231460375044200215320ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ebitdo-firmware.h" #include "fu-ebitdo-struct.h" struct _FuEbitdoFirmware { FuFirmwareClass parent_instance; }; G_DEFINE_TYPE(FuEbitdoFirmware, fu_ebitdo_firmware, FU_TYPE_FIRMWARE) static gboolean fu_ebitdo_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { guint32 payload_len; guint32 version; g_autofree gchar *version_str = NULL; g_autoptr(FuFirmware) img_hdr = fu_firmware_new(); g_autoptr(GByteArray) st = NULL; g_autoptr(GBytes) fw_hdr = NULL; g_autoptr(GBytes) fw_payload = NULL; /* check the file size */ st = fu_struct_ebitdo_hdr_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; payload_len = (guint32)(g_bytes_get_size(fw) - st->len); if (payload_len != fu_struct_ebitdo_hdr_get_destination_len(st)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "file size incorrect, expected 0x%04x got 0x%04x", (guint)fu_struct_ebitdo_hdr_get_destination_len(st), (guint)payload_len); return FALSE; } /* parse version */ version = fu_struct_ebitdo_hdr_get_version(st); version_str = g_strdup_printf("%.2f", version / 100.f); fu_firmware_set_version(firmware, version_str); fu_firmware_set_version_raw(firmware, version); /* add header */ fw_hdr = fu_bytes_new_offset(fw, 0x0, st->len, error); if (fw_hdr == NULL) return FALSE; fu_firmware_set_id(img_hdr, FU_FIRMWARE_ID_HEADER); fu_firmware_set_bytes(img_hdr, fw_hdr); fu_firmware_add_image(firmware, img_hdr); /* add payload */ fw_payload = fu_bytes_new_offset(fw, st->len, payload_len, error); if (fw_payload == NULL) return FALSE; fu_firmware_set_id(firmware, FU_FIRMWARE_ID_PAYLOAD); fu_firmware_set_addr(firmware, fu_struct_ebitdo_hdr_get_destination_addr(st)); fu_firmware_set_bytes(firmware, fw_payload); return TRUE; } static GByteArray * fu_ebitdo_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) st = fu_struct_ebitdo_hdr_new(); g_autoptr(GBytes) blob = NULL; /* header then payload */ blob = fu_firmware_get_bytes_with_patches(firmware, error); if (blob == NULL) return NULL; fu_struct_ebitdo_hdr_set_version(st, fu_firmware_get_version_raw(firmware)); fu_struct_ebitdo_hdr_set_destination_addr(st, fu_firmware_get_addr(firmware)); fu_struct_ebitdo_hdr_set_destination_len(st, g_bytes_get_size(blob)); fu_byte_array_append_bytes(st, blob); return g_steal_pointer(&st); } static void fu_ebitdo_firmware_init(FuEbitdoFirmware *self) { } static void fu_ebitdo_firmware_class_init(FuEbitdoFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_ebitdo_firmware_parse; klass_firmware->write = fu_ebitdo_firmware_write; } fwupd-1.9.16/plugins/ebitdo/fu-ebitdo-firmware.h000066400000000000000000000004601460375044200215320ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_EBITDO_FIRMWARE (fu_ebitdo_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuEbitdoFirmware, fu_ebitdo_firmware, FU, EBITDO_FIRMWARE, FuFirmware) fwupd-1.9.16/plugins/ebitdo/fu-ebitdo-plugin.c000066400000000000000000000014601460375044200212100ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ebitdo-device.h" #include "fu-ebitdo-firmware.h" #include "fu-ebitdo-plugin.h" struct _FuEbitdoPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuEbitdoPlugin, fu_ebitdo_plugin, FU_TYPE_PLUGIN) static void fu_ebitdo_plugin_init(FuEbitdoPlugin *self) { } static void fu_ebitdo_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_EBITDO_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_EBITDO_FIRMWARE); } static void fu_ebitdo_plugin_class_init(FuEbitdoPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_ebitdo_plugin_constructed; } fwupd-1.9.16/plugins/ebitdo/fu-ebitdo-plugin.h000066400000000000000000000003501460375044200212120ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuEbitdoPlugin, fu_ebitdo_plugin, FU, EBITDO_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/ebitdo/fu-ebitdo.rs000066400000000000000000000030771460375044200201240ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, ParseBytes)] struct EbitdoHdr { version: u32le, destination_addr: u32le, destination_len: u32le, reserved: [u32le; 4], } #[repr(u8)] enum EbitdoPktType { UserCmd = 0x00, UserData = 0x01, MidCmd = 0x02, } #[derive(ToString)] #[repr(u8)] enum EbitdoPktCmd { FwUpdateData = 0x00, // update firmware data FwUpdateHeader = 0x01, // update firmware header FwUpdateOk = 0x02, // mark update as successful FwUpdateError = 0x03, // update firmware error FwGetVersion = 0x04, // get cur firmware vision FwSetVersion = 0x05, // set firmware version FwSetEncodeId = 0x06, // set app firmware encode ID Ack = 0x14, // acknowledge Nak = 0x15, // negative acknowledge UpdateFirmwareData = 0x16, // update firmware data TransferAbort = 0x18, // aborts transfer VerificationId = 0x19, // verification id (only BT?) GetVerificationId = 0x1a, // verification id (only BT) VerifyError = 0x1b, // verification error VerifyOk = 0x1c, // verification successful TransferTimeout = 0x1d, // send or receive data timeout GetVersion = 0x21, // get fw ver joystick mode GetVersionResponse = 0x22, // get fw version response } #[derive(New, Parse)] struct EbitdoPkt { pkt_len: u8, type: EbitdoPktType, subtype: u8, cmd_len: u16le, cmd: EbitdoPktCmd, payload_len: u16le, // optional } fwupd-1.9.16/plugins/ebitdo/meson.build000066400000000000000000000007741460375044200200430ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginEbitdo"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('ebitdo.quirk') plugin_builtins += static_library('fu_plugin_ebitdo', rustgen.process( 'fu-ebitdo.rs', # fuzzing ), sources: [ 'fu-ebitdo-plugin.c', 'fu-ebitdo-device.c', 'fu-ebitdo-firmware.c', # fuzzing ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/ebitdo/tests/000077500000000000000000000000001460375044200170335ustar00rootroot00000000000000fwupd-1.9.16/plugins/ebitdo/tests/ebitdo.bin000066400000000000000000000000471460375044200207740ustar00rootroot000000000000000 hello worldfwupd-1.9.16/plugins/ebitdo/tests/ebitdo.builder.xml000066400000000000000000000002131460375044200224440ustar00rootroot00000000000000 0x10002 0x3000 aGVsbG8gd29ybGQ= fwupd-1.9.16/plugins/elanfp/000077500000000000000000000000001460375044200156705ustar00rootroot00000000000000fwupd-1.9.16/plugins/elanfp/README.md000066400000000000000000000014761460375044200171570ustar00rootroot00000000000000--- title: Plugin: Elan Fingerprint Sensor --- ## Introduction The plugin used for update firmware for fingerprint sensors from Elan. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `tw.com.emc.elanfp` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_04F3&PID_0C7E` ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x04F3` ## Version Considerations This plugin has been available since fwupd version `1.7.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Michael Cheng: @MichaelCheng04 fwupd-1.9.16/plugins/elanfp/elanfp.quirk000066400000000000000000000003251460375044200202120ustar00rootroot00000000000000[USB\VID_04F3&PID_0C7E] Plugin = elanfp Flags = enforce-requires [USB\VID_04F3&PID_0C82] Plugin = elanfp Flags = enforce-requires [USB\VID_04F3&PID_0C88] Plugin = elanfp Flags = usb-bulk-transfer,enforce-requires fwupd-1.9.16/plugins/elanfp/fu-elanfp-device.c000066400000000000000000000300141460375044200211440ustar00rootroot00000000000000/* * Copyright (C) 2021 Michael Cheng * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-cfu-struct.h" #include "fu-elanfp-device.h" #include "fu-elanfp-firmware.h" #define ELAN_EP_CMD_OUT (0x01 | 0x00) #define ELAN_EP_CMD_IN (0x02 | 0x80) #define ELAN_EP_MOC_CMD_IN (0x04 | 0x80) #define ELAN_EP_IMG_IN (0x03 | 0x80) #define ELANFP_USB_INTERFACE 0 #define CTRL_SEND_TIMEOUT_MS 3000 #define BULK_SEND_TIMEOUT_MS 3000 #define BULK_RECV_TIMEOUT_MS 3000 #define REPORT_ID_FW_VERSION_FEATURE 0x20 #define REPORT_ID_OFFER_COMMAND 0x25 #define REPORT_ID_OFFER_RESPONSE 0x25 #define REPORT_ID_PAYLOAD_COMMAND 0x20 #define REPORT_ID_PAYLOAD_RESPONSE 0x22 #define REQTYPE_GET_VERSION 0xC1 #define REQTYPE_COMMAND 0x41 #define TAG_SEND_COMMAND 0xFA /** * FU_ELAN_FP_DEVICE_FLAG_USB_BULK_TRANSFER: * * Use usb bulk transfer. */ #define FU_ELAN_FP_DEVICE_FLAG_USB_BULK_TRANSFER (1 << 0) struct _FuElanfpDevice { FuUsbDevice parent_instance; }; G_DEFINE_TYPE(FuElanfpDevice, fu_elanfp_device, FU_TYPE_USB_DEVICE) static gboolean fu_elanfp_iap_send_command(FuElanfpDevice *self, guint8 request_type, guint8 request, const guint8 *buf, gsize bufsz, gsize rspsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual = 0; gsize sendsz = bufsz + 1; guint8 start_index = 0x01; guint8 buftmp[64] = {request, 0}; if (buf != NULL) { if (fu_device_has_private_flag(FU_DEVICE(self), FU_ELAN_FP_DEVICE_FLAG_USB_BULK_TRANSFER)) { buftmp[0] = TAG_SEND_COMMAND; buftmp[1] = rspsz; buftmp[2] = bufsz + 1; buftmp[3] = request; start_index = 0x04; sendsz = bufsz + 1 + 3; } if (!fu_memcpy_safe(buftmp, sizeof(buftmp), start_index, /* dst */ buf, bufsz, 0x0, /* src */ bufsz, error)) return FALSE; } if (fu_device_has_private_flag(FU_DEVICE(self), FU_ELAN_FP_DEVICE_FLAG_USB_BULK_TRANSFER)) { if (!g_usb_device_bulk_transfer(usb_device, ELAN_EP_CMD_OUT, buftmp, sendsz, &actual, BULK_SEND_TIMEOUT_MS, NULL, error)) { g_prefix_error(error, "failed to send command (bulk): "); return FALSE; } } else { if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_INTERFACE, request, /* request */ 0x00, /* value */ 0x00, /* index */ buftmp, sendsz, &actual, CTRL_SEND_TIMEOUT_MS, NULL, error)) { g_prefix_error(error, "failed to send command (ctrl transfer): "); return FALSE; } } if (actual != sendsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "send length (%u) is not match with the request (%u)", (guint)actual, (guint)bufsz + 1); return FALSE; } return TRUE; } static gboolean fu_elanfp_iap_recv_status(FuElanfpDevice *self, guint8 *buf, gsize bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); guint8 endpoint = ELAN_EP_CMD_IN; gsize actual = 0; if (fu_device_has_private_flag(FU_DEVICE(self), FU_ELAN_FP_DEVICE_FLAG_USB_BULK_TRANSFER)) { endpoint = ELAN_EP_IMG_IN; } if (!g_usb_device_bulk_transfer(usb_device, endpoint, buf, bufsz, &actual, BULK_RECV_TIMEOUT_MS, NULL, error)) { g_prefix_error(error, "failed to receive status: "); return FALSE; } if (actual != bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "received length (%u) is not match with the request (%u)", (guint)actual, (guint)bufsz); return FALSE; } return TRUE; } static gboolean fu_elanfp_device_do_xfer(FuElanfpDevice *self, guint8 *outbuf, gsize outlen, guint8 *inbuf, gsize inlen, gboolean allow_less, gsize *rxed_count, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual = 0; /* send data out */ if (outbuf != NULL && outlen > 0) { if (!g_usb_device_bulk_transfer(usb_device, ELAN_EP_CMD_OUT, outbuf, outlen, &actual, BULK_SEND_TIMEOUT_MS, NULL, error)) { return FALSE; } if (actual != outlen) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, "only sent %" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT " bytes", actual, outlen); return FALSE; } } /* read reply back */ if (inbuf != NULL && inlen > 0) { actual = 0; if (!g_usb_device_bulk_transfer(usb_device, ELAN_EP_IMG_IN, inbuf, inlen, &actual, BULK_RECV_TIMEOUT_MS, NULL, error)) { return FALSE; } if (actual != inlen && !allow_less) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, "only received %" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT " bytes", actual, outlen); return FALSE; } } if (rxed_count != NULL) *rxed_count = actual; return TRUE; } static gboolean fu_elanfp_device_setup(FuDevice *device, GError **error) { FuElanfpDevice *self = FU_ELANFP_DEVICE(device); guint16 fw_ver; guint8 usb_buf[2] = {0x40, 0x19}; g_autofree gchar *fw_ver_str = NULL; /* get version */ if (!fu_elanfp_device_do_xfer(self, (guint8 *)&usb_buf, sizeof(usb_buf), usb_buf, sizeof(usb_buf), TRUE, NULL, error)) { g_prefix_error(error, "failed to device setup: "); return FALSE; } fw_ver = fu_memread_uint16(usb_buf, G_BIG_ENDIAN); fw_ver_str = g_strdup_printf("%04x", fw_ver); fu_device_set_version(device, fw_ver_str); /* success */ return TRUE; } static gboolean fu_elanfp_device_write_payload(FuElanfpDevice *self, FuFirmware *payload, FuProgress *progress, GError **error) { g_autoptr(GPtrArray) chunks = NULL; /* write each chunk */ chunks = fu_firmware_get_chunks(payload, error); if (chunks == NULL) return FALSE; fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint8 databuf[60] = {0}; guint8 recvbuf[17] = {0}; /* flags */ if (i == 0) databuf[0] = FU_CFU_CONTENT_FLAG_FIRST_BLOCK; else if (i == chunks->len - 1) databuf[0] = FU_CFU_CONTENT_FLAG_LAST_BLOCK; /* length */ databuf[1] = fu_chunk_get_data_sz(chk); /* sequence number */ if (!fu_memwrite_uint16_safe(databuf, sizeof(databuf), 0x2, i + 1, G_LITTLE_ENDIAN, error)) return FALSE; /* address */ if (!fu_memwrite_uint32_safe(databuf, sizeof(databuf), 0x4, fu_chunk_get_address(chk), G_LITTLE_ENDIAN, error)) return FALSE; /* data */ if (!fu_memcpy_safe(databuf, sizeof(databuf), 0x8, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "memory copy for payload fail: "); return FALSE; } if (!fu_elanfp_iap_send_command(self, REQTYPE_COMMAND, REPORT_ID_PAYLOAD_COMMAND, databuf, sizeof(databuf), sizeof(recvbuf), error)) { g_prefix_error(error, "send payload command fail: "); return FALSE; } if (!fu_elanfp_iap_recv_status(self, recvbuf, sizeof(recvbuf), error)) { g_prefix_error(error, "received payload status fail: "); return FALSE; } if (recvbuf[5] != FU_CFU_CONTENT_STATUS_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to send chunk %u: %s", i + 1, fu_cfu_content_status_to_string(recvbuf[5])); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_elanfp_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuElanfpDevice *self = FU_ELANFP_DEVICE(device); guint i; struct { const gchar *tag; guint8 offer_idx; guint8 payload_idx; } items[] = { {"A", FU_ELANTP_FIRMWARE_IDX_CFU_OFFER_A, FU_ELANTP_FIRMWARE_IDX_CFU_PAYLOAD_A}, {"B", FU_ELANTP_FIRMWARE_IDX_CFU_OFFER_B, FU_ELANTP_FIRMWARE_IDX_CFU_PAYLOAD_B}, {NULL, FU_ELANTP_FIRMWARE_IDX_END, FU_ELANTP_FIRMWARE_IDX_END}}; g_autoptr(FuFirmware) payload = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "offer"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "payload"); /* send offers */ for (i = 0; items[i].tag != NULL; i++) { g_autoptr(GBytes) offer = NULL; guint8 recvbuf[17] = {0}; offer = fu_firmware_get_image_by_idx_bytes(firmware, items[i].offer_idx, error); if (offer == NULL) return FALSE; if (!fu_elanfp_iap_send_command(self, REQTYPE_COMMAND, REPORT_ID_OFFER_COMMAND, g_bytes_get_data(offer, NULL), g_bytes_get_size(offer), g_bytes_get_size(offer) + 1, error)) { g_prefix_error(error, "send offer command fail: "); return FALSE; } if (!fu_elanfp_iap_recv_status(self, recvbuf, sizeof(recvbuf), error)) { g_prefix_error(error, "received offer status fail: "); return FALSE; } g_debug("offer-%s status:%s reject:%s", items[i].tag, fu_cfu_offer_status_to_string(recvbuf[13]), fu_cfu_rr_code_to_string(recvbuf[9])); if (recvbuf[13] == FU_CFU_OFFER_STATUS_ACCEPT) break; } if (items[i].tag == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no CFU offer was accepted"); return FALSE; } fu_progress_step_done(progress); /* send payload */ payload = fu_firmware_get_image_by_idx(firmware, items[i].payload_idx, error); if (payload == NULL) return FALSE; if (!fu_elanfp_device_write_payload(self, payload, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_elanfp_device_init(FuElanfpDevice *device) { FuElanfpDevice *self = FU_ELANFP_DEVICE(device); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_remove_delay(FU_DEVICE(self), 5000); fu_device_add_protocol(FU_DEVICE(self), "tw.com.emc.elanfp"); fu_device_set_name(FU_DEVICE(self), "Fingerprint Sensor"); fu_device_set_summary(FU_DEVICE(self), "Match-On-Chip Fingerprint Sensor"); fu_device_set_vendor(FU_DEVICE(self), "Elan"); fu_device_set_install_duration(FU_DEVICE(self), 10); fu_device_set_firmware_size_min(FU_DEVICE(self), 0x20000); fu_device_set_firmware_size_max(FU_DEVICE(self), 0x90000); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_ELANFP_FIRMWARE); fu_usb_device_add_interface(FU_USB_DEVICE(self), ELANFP_USB_INTERFACE); fu_device_register_private_flag(FU_DEVICE(self), FU_ELAN_FP_DEVICE_FLAG_USB_BULK_TRANSFER, "usb-bulk-transfer"); } static void fu_elanfp_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_elanfp_device_class_init(FuElanfpDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_elanfp_device_setup; klass_device->write_firmware = fu_elanfp_device_write_firmware; klass_device->set_progress = fu_elanfp_device_set_progress; } fwupd-1.9.16/plugins/elanfp/fu-elanfp-device.h000066400000000000000000000004531460375044200211550ustar00rootroot00000000000000/* * Copyright (C) 2021 Michael Cheng * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ELANFP_DEVICE (fu_elanfp_device_get_type()) G_DECLARE_FINAL_TYPE(FuElanfpDevice, fu_elanfp_device, FU, ELANFP_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/elanfp/fu-elanfp-firmware.c000066400000000000000000000143021460375044200215230ustar00rootroot00000000000000/* * Copyright (C) 2021 Michael Cheng * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-elanfp-firmware.h" struct _FuElanfpFirmware { FuFirmwareClass parent_instance; guint32 format_version; }; G_DEFINE_TYPE(FuElanfpFirmware, fu_elanfp_firmware, FU_TYPE_FIRMWARE) #define FU_ELANFP_FIRMWARE_HEADER_MAGIC 0x46325354 static void fu_elanfp_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuElanfpFirmware *self = FU_ELANFP_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "format_version", self->format_version); } static gboolean fu_elanfp_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuElanfpFirmware *self = FU_ELANFP_FIRMWARE(firmware); guint64 tmp; /* optional properties */ tmp = xb_node_query_text_as_uint(n, "format_version", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) self->format_version = tmp; /* success */ return TRUE; } static gboolean fu_elanfp_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { guint32 magic = 0; if (!fu_memread_uint32_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset, &magic, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read magic: "); return FALSE; } if (magic != FU_ELANFP_FIRMWARE_HEADER_MAGIC) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid magic, expected 0x%04X got 0x%04X", (guint32)FU_ELANFP_FIRMWARE_HEADER_MAGIC, magic); return FALSE; } /* success */ return TRUE; } static gboolean fu_elanfp_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuElanfpFirmware *self = FU_ELANFP_FIRMWARE(firmware); const guint8 *buf; gsize bufsz; /* file format version */ buf = g_bytes_get_data(fw, &bufsz); if (!fu_memread_uint32_safe(buf, bufsz, offset + 0x4, &self->format_version, G_LITTLE_ENDIAN, error)) return FALSE; /* read indexes */ offset += 0x10; while (1) { guint32 start_addr = 0; guint32 length = 0; guint32 fwtype = 0; g_autoptr(GBytes) blob = NULL; g_autoptr(FuFirmware) img = NULL; /* type, reserved, start-addr, len */ if (!fu_memread_uint32_safe(buf, bufsz, offset + 0x0, &fwtype, G_LITTLE_ENDIAN, error)) return FALSE; /* check not already added */ img = fu_firmware_get_image_by_idx(firmware, fwtype, NULL); if (img != NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "already parsed image with fwtype 0x%x", fwtype); return FALSE; } /* done */ if (fwtype == FU_ELANTP_FIRMWARE_IDX_END) break; switch (fwtype) { case FU_ELANTP_FIRMWARE_IDX_CFU_OFFER_A: case FU_ELANTP_FIRMWARE_IDX_CFU_OFFER_B: img = fu_cfu_offer_new(); break; case FU_ELANTP_FIRMWARE_IDX_CFU_PAYLOAD_A: case FU_ELANTP_FIRMWARE_IDX_CFU_PAYLOAD_B: img = fu_cfu_payload_new(); break; default: img = fu_firmware_new(); break; } fu_firmware_set_idx(img, fwtype); if (!fu_memread_uint32_safe(buf, bufsz, offset + 0x8, &start_addr, G_LITTLE_ENDIAN, error)) return FALSE; fu_firmware_set_addr(img, start_addr); if (!fu_memread_uint32_safe(buf, bufsz, offset + 0xC, &length, G_LITTLE_ENDIAN, error)) return FALSE; if (length == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "zero size fwtype 0x%x not supported", fwtype); return FALSE; } blob = fu_bytes_new_offset(fw, start_addr, length, error); if (blob == NULL) return FALSE; if (!fu_firmware_parse(img, blob, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return FALSE; if (!fu_firmware_add_image_full(firmware, img, error)) return FALSE; offset += 0x10; } /* success */ return TRUE; } static GByteArray * fu_elanfp_firmware_write(FuFirmware *firmware, GError **error) { FuElanfpFirmware *self = FU_ELANFP_FIRMWARE(firmware); gsize offset = 0; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); /* S2F_HEADER */ fu_byte_array_append_uint32(buf, 0x46325354, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, self->format_version, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* ICID, assumed */ fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* reserved */ /* S2F_INDEX */ offset += 0x10 + ((imgs->len + 1) * 0x10); for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); g_autoptr(GBytes) blob = fu_firmware_write(img, error); if (blob == NULL) return NULL; fu_byte_array_append_uint32(buf, fu_firmware_get_idx(img), G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* reserved */ fu_byte_array_append_uint32(buf, offset, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, g_bytes_get_size(blob), G_LITTLE_ENDIAN); offset += g_bytes_get_size(blob); } /* end of index */ fu_byte_array_append_uint32(buf, FU_ELANTP_FIRMWARE_IDX_END, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* reserved */ fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* assumed */ fu_byte_array_append_uint32(buf, 0x0, G_LITTLE_ENDIAN); /* assumed */ /* data */ for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); g_autoptr(GBytes) blob = fu_firmware_write(img, error); if (blob == NULL) return NULL; fu_byte_array_append_bytes(buf, blob); } /* success */ return g_steal_pointer(&buf); } static void fu_elanfp_firmware_init(FuElanfpFirmware *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 256); } static void fu_elanfp_firmware_class_init(FuElanfpFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_elanfp_firmware_check_magic; klass_firmware->parse = fu_elanfp_firmware_parse; klass_firmware->write = fu_elanfp_firmware_write; klass_firmware->export = fu_elanfp_firmware_export; klass_firmware->build = fu_elanfp_firmware_build; } fwupd-1.9.16/plugins/elanfp/fu-elanfp-firmware.h000066400000000000000000000011501460375044200215250ustar00rootroot00000000000000/* * Copyright (C) 2021 Michael Cheng * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ELANFP_FIRMWARE (fu_elanfp_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuElanfpFirmware, fu_elanfp_firmware, FU, ELANFP_FIRMWARE, FuFirmware) #define FU_ELANTP_FIRMWARE_IDX_FIRMWAREVERSION 0x00 #define FU_ELANTP_FIRMWARE_IDX_CFU_OFFER_A 0x72 #define FU_ELANTP_FIRMWARE_IDX_CFU_OFFER_B 0x73 #define FU_ELANTP_FIRMWARE_IDX_CFU_PAYLOAD_A 0x74 #define FU_ELANTP_FIRMWARE_IDX_CFU_PAYLOAD_B 0x75 #define FU_ELANTP_FIRMWARE_IDX_END 0xFF fwupd-1.9.16/plugins/elanfp/fu-elanfp-plugin.c000066400000000000000000000014131460375044200212040ustar00rootroot00000000000000/* * Copyright (C) 2021 * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-elanfp-device.h" #include "fu-elanfp-firmware.h" #include "fu-elanfp-plugin.h" struct _FuElanfpPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuElanfpPlugin, fu_elanfp_plugin, FU_TYPE_PLUGIN) static void fu_elanfp_plugin_init(FuElanfpPlugin *self) { } static void fu_elanfp_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_ELANFP_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_ELANFP_FIRMWARE); } static void fu_elanfp_plugin_class_init(FuElanfpPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_elanfp_plugin_constructed; } fwupd-1.9.16/plugins/elanfp/fu-elanfp-plugin.h000066400000000000000000000003501460375044200212100ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuElanfpPlugin, fu_elanfp_plugin, FU, ELANFP_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/elanfp/meson.build000066400000000000000000000010261460375044200200310ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginElanfp"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('elanfp.quirk') plugin_builtins += static_library('fu_plugin_elanfp', sources: [ cfu_rs[1], # header 'fu-elanfp-plugin.c', 'fu-elanfp-device.c', 'fu-elanfp-firmware.c' # fuzzing ], include_directories: [ plugin_incdirs, plugincfu_incdir, ], link_with: [ plugin_libs, plugin_builtin_cfu, ], c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/elanfp/tests/000077500000000000000000000000001460375044200170325ustar00rootroot00000000000000fwupd-1.9.16/plugins/elanfp/tests/elanfp.bin000066400000000000000000000002401460375044200207650ustar00rootroot00000000000000TS2F#r`sptu4xV4xV4 hello world4 hello worldfwupd-1.9.16/plugins/elanfp/tests/elanfp.builder.xml000066400000000000000000000014351460375044200224510ustar00rootroot00000000000000 0x123 0x72 0x1234 0x5678 0x1 0x73 0x1234 0x5678 0x2 0x74 aGVsbG8gd29ybGQ= 0x8001234 0x75 aGVsbG8gd29ybGQ= 0x8001234 fwupd-1.9.16/plugins/elantp/000077500000000000000000000000001460375044200157065ustar00rootroot00000000000000fwupd-1.9.16/plugins/elantp/README.md000066400000000000000000000046421460375044200171730ustar00rootroot00000000000000--- title: Plugin: Elan TouchPad --- ## Introduction This plugin allows updating Touchpad devices from Elan. Devices are enumerated using HID and raw I²C nodes. The I²C mode is used for ABS devices and firmware recovery of HID devices. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * `tw.com.emc.elantp` ## GUID Generation These device uses the standard DeviceInstanceId values, e.g. * `HIDRAW\VEN_04F3&DEV_3010` Additionally another instance ID is added which corresponds to the module ID: * `HIDRAW\VEN_04F3&DEV_3010&MOD_1234` These devices also use custom GUID values for the IC configuration, e.g. * `ELANTP\ICTYPE_09` (only-quirk) Additionally another instance ID is added which corresponds to the IC type & module ID: * `ELANTP\ICTYPE_09&MOD_1234` Additionally another instance ID is added which corresponds to the IC Type & module ID and Driver in order to distinguish HID/ABS devices: * `ELANTP\ICTYPE_09&MOD_1234&DRIVER_HID` -> HID Device * `ELANTP\ICTYPE_09&MOD_1234&DRIVER_ELAN_I2C` -> ABS Device ## Update Behavior The device usually presents in HID/ABS mode, and the firmware is written to the device by switching to a IAP mode where the touchpad is nonfunctional. Once complete the device is reset to get out of IAP mode and to load the new firmware version. For HID devices, on flash failure the device is nonfunctional, but is recoverable by writing to the i2c device. This is typically much slower than updating the device using HID and also requires a model-specific HWID quirk to match. For ABS devices, on flash failure the device is nonfunctional, but it could be recovered by the same i2c device. ## Vendor ID Security The vendor ID is set from the HID vendor, for example set to `HIDRAW:0x17EF` ## Quirk Use This plugin uses the following plugin-specific quirks: ### ElantpIcPageCount The IC page count. Since: 1.4.6 ### ElantpIapPassword The IAP password. Since: 1.4.6 ## External Interface Access This plugin requires ioctl access to `HIDIOCSFEATURE` and `HIDIOCGFEATURE`. ## Version Considerations This plugin has been available since fwupd version `1.5.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Jingle Wu: @jinglewu * Josh Chen: @josh-chen-elan fwupd-1.9.16/plugins/elantp/elantp.quirk000066400000000000000000000043021460375044200202450ustar00rootroot00000000000000[HIDRAW\VEN_04F3] Plugin = elantp GType = FuElantpHidDevice # ThinkPad X1 Carbon Gen 9 [6c87726f-b545-549e-840a-189422ea21d0] Flags = elantp-recovery # Lenovo X1 Nano Gen1 [4c20262a-aee0-5d6e-ba72-6d29b23fe350] Flags = elantp-recovery # Lenovo ThinkPad X13 Yoga Gen 2 [34874ca5-54f0-5a4f-9161-e03910d14b75] Flags = elantp-recovery # Lenovo ThinkPad X13 Gen 2 - 20XHFVT007 [e5319542-ca16-5d93-bd0f-2d25f547a846] Flags = elantp-recovery # Lenovo ThinkPad X13 Gen 2 - 20XHFVT003 [ea5ea62f-ec8d-5f5d-8231-0816c89d75d6] Flags = elantp-recovery # Acer Aspire V3-372T [513cde3d-d939-59bd-a634-5c1645ebb93b] Flags = elantp-recovery # absolute report device on ACPI [I2C\NAME_ELAN0000:00] Plugin = elantp GType = FuElantpI2cDevice ElantpI2cTargetAddress = 0x15 Flags = elantp-absolute # absolute report device on Device Tree [I2C\NAME_ekth3000] Plugin = elantp GType = FuElantpI2cDevice ElantpI2cTargetAddress = 0x15 Flags = elantp-absolute # recovery device [I2C\NAME_Synopsys-DesignWare-I2C-adapter] Plugin = elantp GType = FuElantpI2cDevice ElantpI2cTargetAddress = 0x15 [ELANTP\ICTYPE_00] ElantpIcPageCount = 0x200 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_03] ElantpIcPageCount = 0x300 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_06] ElantpIcPageCount = 0x200 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_07] ElantpIcPageCount = 0x300 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_08] ElantpIcPageCount = 0x200 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_0A] ElantpIcPageCount = 0x300 ElantpIapPassword = 0xE15A [ELANTP\ICTYPE_0B] ElantpIcPageCount = 0x300 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_0C] ElantpIcPageCount = 0x300 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_0E] ElantpIcPageCount = 0x280 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_09] ElantpIcPageCount = 0x300 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_0D] ElantpIcPageCount = 0x380 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_10] ElantpIcPageCount = 0x400 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_11] ElantpIcPageCount = 0x500 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_13] ElantpIcPageCount = 0x800 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_14] ElantpIcPageCount = 0x400 ElantpIapPassword = 0x1EA5 [ELANTP\ICTYPE_15] ElantpIcPageCount = 0x400 ElantpIapPassword = 0x1EA5 fwupd-1.9.16/plugins/elantp/fu-elantp-common.h000066400000000000000000000045521460375044200212460ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define ETP_CMD_GET_HID_DESCRIPTOR 0x0001 #define ETP_CMD_GET_HARDWARE_ID 0x0100 #define ETP_CMD_GET_MODULE_ID 0x0101 #define ETP_CMD_I2C_FW_CHECKSUM 0x030F #define ETP_CMD_I2C_FW_VERSION 0x0102 #define ETP_CMD_I2C_IAP 0x0311 #define ETP_CMD_I2C_IAP_CHECKSUM 0x0315 #define ETP_CMD_I2C_IAP_CTRL 0x0310 #define ETP_CMD_I2C_IAP_ICBODY 0x0110 #define ETP_CMD_I2C_IAP_RESET 0x0314 #define ETP_CMD_I2C_IAP_VERSION 0x0111 #define ETP_CMD_I2C_IAP_VERSION_2 0x0110 #define ETP_CMD_I2C_OSM_VERSION 0x0103 #define ETP_CMD_I2C_GET_HID_ID 0x0100 #define ETP_CMD_I2C_IAP_TYPE 0x0304 #define ETP_CMD_I2C_FW_PW 0x030E #define ETP_CMD_FORCE_ADDR 0x03AD #define ETP_CMD_I2C_FORCE_TYPE_ENABLE 0x0104 #define ETP_CMD_I2C_SET_EEPROM_CTRL 0x0321 #define ETP_CMD_I2C_GET_EEPROM_FW_VERSION 0x0710 #define ETP_CMD_I2C_GET_EEPROM_IAP_VERSION 0x0711 #define ETP_CMD_I2C_SET_EEPROM_ENTER_IAP 0x0607 #define ETP_CMD_I2C_SET_EEPROM_LEAVE_IAP 0x0606 #define ETP_CMD_I2C_SET_EEPROM_DATATYPE 0x0702 #define ETP_CMD_I2C_CALC_EEPROM_CHECKSUM 0x060F #define ETP_CMD_I2C_READ_EEPROM_CHECKSUM 0x070A #define ETP_CMD_I2C_HAPTIC_RESTART 0x0601 #define ETP_CMD_I2C_EEPROM_SETTING 0x0322 #define ETP_CMD_I2C_EEPROM_LONG_TRANS_ENABLE 0x4607 #define ETP_CMD_I2C_EEPROM_SETTING_INITIAL 0x0000 #define ETP_CMD_I2C_EEPROM_WRITE_INFORMATION 0x4600 #define ETP_CMD_I2C_EEPROM_WRITE_CHECKSUM 0x048B #define ETP_I2C_IC13_IAPV5_PW 0x37CA #define ETP_FW_FORCE_TYPE_ENABLE_BIT 0x1 #define ETP_FW_EEPROM_ENABLE_BIT 0x2 #define ETP_I2C_IAP_TYPE_REG 0x0040 #define ETP_I2C_ENABLE_REPORT 0x0800 #define ETP_I2C_IAP_RESET 0xF0F0 #define ETP_I2C_MAIN_MODE_ON (1 << 9) #define ETP_I2C_MAIN_MODE_ON2 (1 << 12) #define ETP_I2C_IAP_REG_L 0x01 #define ETP_I2C_IAP_REG_H 0x06 #define ETP_FW_IAP_INTF_ERR (1 << 4) #define ETP_FW_IAP_PAGE_ERR (1 << 5) #define ETP_FW_IAP_CHECK_PW (1 << 7) #define ETP_FW_IAP_LAST_FIT (1 << 9) #define ELANTP_DELAY_COMPLETE 1200 /* ms */ #define ELANTP_DELAY_RESET 30 /* ms */ #define ELANTP_EEPROM_READ_DELAY 100 /* ms */ #define ELANTP_DELAY_UNLOCK 100 /* ms */ #define ELANTP_DELAY_WRITE_BLOCK 35 /* ms */ #define ELANTP_DELAY_WRITE_BLOCK_512 50 /* ms */ fwupd-1.9.16/plugins/elantp/fu-elantp-firmware.c000066400000000000000000000175511460375044200215700ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-elantp-common.h" #include "fu-elantp-firmware.h" struct _FuElantpFirmware { FuFirmwareClass parent_instance; guint16 module_id; guint16 ic_type; guint16 iap_addr; guint16 iap_ver; gboolean force_table_support; guint32 force_table_addr; }; G_DEFINE_TYPE(FuElantpFirmware, fu_elantp_firmware, FU_TYPE_FIRMWARE) /* firmware block update */ #define ETP_IC_TYPE_ADDR_WRDS 0x0080 #define ETP_IAP_VER_ADDR_WRDS 0x0082 #define ETP_IAP_START_ADDR_WRDS 0x0083 #define ETP_IAP_FORCETABLE_ADDR_V5 0x0085 const guint8 elantp_signature[] = {0xAA, 0x55, 0xCC, 0x33, 0xFF, 0xFF}; guint16 fu_elantp_firmware_get_module_id(FuElantpFirmware *self) { g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), 0); return self->module_id; } guint16 fu_elantp_firmware_get_ic_type(FuElantpFirmware *self) { g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), 0); return self->ic_type; } guint16 fu_elantp_firmware_get_iap_addr(FuElantpFirmware *self) { g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), 0); return self->iap_addr; } gboolean fu_elantp_firmware_get_forcetable_support(FuElantpFirmware *self) { g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), FALSE); return self->force_table_support; } guint32 fu_elantp_firmware_get_forcetable_addr(FuElantpFirmware *self) { g_return_val_if_fail(FU_IS_ELANTP_FIRMWARE(self), 0); return self->force_table_addr; } static void fu_elantp_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "iap_addr", self->iap_addr); fu_xmlb_builder_insert_kx(bn, "module_id", self->module_id); } static gboolean fu_elantp_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); gsize bufsz = g_bytes_get_size(fw); const guint8 *buf = g_bytes_get_data(fw, NULL); for (gsize i = 0; i < sizeof(elantp_signature); i++) { guint8 tmp = 0x0; if (!fu_memread_uint8_safe(buf, bufsz, bufsz - sizeof(elantp_signature) + i, &tmp, error)) return FALSE; if (tmp != elantp_signature[i]) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "signature[%u] invalid: got 0x%2x, expected 0x%02x", (guint)i, tmp, elantp_signature[i]); return FALSE; } } if (self->force_table_addr != 0) { for (gsize i = 0; i < sizeof(elantp_signature); i++) { guint8 tmp = 0x0; if (!fu_memread_uint8_safe(buf, bufsz, self->force_table_addr - 1 - sizeof(elantp_signature) + i, &tmp, error)) return FALSE; if (tmp != elantp_signature[i]) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "signature[%u] invalid: got 0x%2x, expected 0x%02x", (guint)i, tmp, elantp_signature[i]); return FALSE; } } } /* success */ return TRUE; } static gboolean fu_elantp_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); gsize bufsz = 0; guint16 iap_addr_wrds; guint16 force_table_addr_wrds; guint16 module_id_wrds; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GError) error_local = NULL; /* presumably in words */ if (!fu_memread_uint16_safe(buf, bufsz, offset + ETP_IAP_START_ADDR_WRDS * 2, &iap_addr_wrds, G_LITTLE_ENDIAN, error)) return FALSE; if (iap_addr_wrds < ETP_IAP_START_ADDR_WRDS || iap_addr_wrds > 0x7FFF) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "IAP address invalid: 0x%x", iap_addr_wrds); return FALSE; } self->iap_addr = iap_addr_wrds * 2; /* read module ID */ if (!fu_memread_uint16_safe(buf, bufsz, offset + self->iap_addr, &module_id_wrds, G_LITTLE_ENDIAN, error)) return FALSE; if (module_id_wrds > 0x7FFF) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "module ID address invalid: 0x%x", module_id_wrds); return FALSE; } if (!fu_memread_uint16_safe(buf, bufsz, offset + module_id_wrds * 2, &self->module_id, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(buf, bufsz, offset + ETP_IC_TYPE_ADDR_WRDS * 2, &self->ic_type, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(buf, bufsz, offset + ETP_IAP_VER_ADDR_WRDS * 2, &self->iap_ver, G_LITTLE_ENDIAN, error)) return FALSE; if (self->ic_type != 0x12 && self->ic_type != 0x13) return TRUE; if (self->iap_ver <= 4) { if (!fu_memread_uint16_safe(buf, bufsz, offset + (self->iap_addr + 6), &force_table_addr_wrds, G_LITTLE_ENDIAN, &error_local)) { g_debug("forcetable address wrong: %s", error_local->message); return TRUE; } } else { if (!fu_memread_uint16_safe(buf, bufsz, offset + ETP_IAP_FORCETABLE_ADDR_V5 * 2, &force_table_addr_wrds, G_LITTLE_ENDIAN, &error_local)) { g_debug("forcetable address wrong: %s", error_local->message); return TRUE; } } if (force_table_addr_wrds % 32 == 0) { self->force_table_addr = force_table_addr_wrds * 2; self->force_table_support = TRUE; } /* success */ return TRUE; } static gboolean fu_elantp_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); guint64 tmp; /* two simple properties */ tmp = xb_node_query_text_as_uint(n, "module_id", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) self->module_id = tmp; tmp = xb_node_query_text_as_uint(n, "iap_addr", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) self->iap_addr = tmp; /* success */ return TRUE; } static GByteArray * fu_elantp_firmware_write(FuFirmware *firmware, GError **error) { FuElantpFirmware *self = FU_ELANTP_FIRMWARE(firmware); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) blob = NULL; /* only one image supported */ blob = fu_firmware_get_bytes_with_patches(firmware, error); if (blob == NULL) return NULL; /* lets build a simple firmware like this: * ------ 0x0 * HEADER (containing IAP offset and module ID) * ------ ~0x10a * DATA * ------ * SIGNATURE * ------ */ fu_byte_array_set_size(buf, self->iap_addr + 0x2 + 0x2, 0x00); if (!fu_memwrite_uint16_safe(buf->data, buf->len, ETP_IAP_START_ADDR_WRDS * 2, self->iap_addr / 2, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memwrite_uint16_safe(buf->data, buf->len, self->iap_addr, (self->iap_addr + 2) / 2, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memwrite_uint16_safe(buf->data, buf->len, self->iap_addr + 0x2, self->module_id, G_LITTLE_ENDIAN, error)) return NULL; fu_byte_array_append_bytes(buf, blob); g_byte_array_append(buf, elantp_signature, sizeof(elantp_signature)); return g_steal_pointer(&buf); } static void fu_elantp_firmware_init(FuElantpFirmware *self) { } static void fu_elantp_firmware_class_init(FuElantpFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_elantp_firmware_check_magic; klass_firmware->parse = fu_elantp_firmware_parse; klass_firmware->build = fu_elantp_firmware_build; klass_firmware->write = fu_elantp_firmware_write; klass_firmware->export = fu_elantp_firmware_export; } FuFirmware * fu_elantp_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_ELANTP_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/elantp/fu-elantp-firmware.h000066400000000000000000000012631460375044200215660ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ELANTP_FIRMWARE (fu_elantp_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuElantpFirmware, fu_elantp_firmware, FU, ELANTP_FIRMWARE, FuFirmware) FuFirmware * fu_elantp_firmware_new(void); guint16 fu_elantp_firmware_get_module_id(FuElantpFirmware *self); guint16 fu_elantp_firmware_get_ic_type(FuElantpFirmware *self); guint16 fu_elantp_firmware_get_iap_addr(FuElantpFirmware *self); gboolean fu_elantp_firmware_get_forcetable_support(FuElantpFirmware *self); guint32 fu_elantp_firmware_get_forcetable_addr(FuElantpFirmware *self); fwupd-1.9.16/plugins/elantp/fu-elantp-haptic-firmware.c000066400000000000000000000063761460375044200230410ustar00rootroot00000000000000/* * Copyright (C) 2022 Jingle Wu * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-elantp-common.h" #include "fu-elantp-haptic-firmware.h" struct _FuElantpHapticFirmware { FuFirmwareClass parent_instance; guint16 driver_ic; }; G_DEFINE_TYPE(FuElantpHapticFirmware, fu_elantp_haptic_firmware, FU_TYPE_FIRMWARE) const guint8 elantp_haptic_signature_ictype02[] = {0xFF, 0x40, 0xA2, 0x5B}; guint16 fu_elantp_haptic_firmware_get_driver_ic(FuElantpHapticFirmware *self) { g_return_val_if_fail(FU_IS_ELANTP_HAPTIC_FIRMWARE(self), 0); return self->driver_ic; } static void fu_elantp_haptic_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuElantpHapticFirmware *self = FU_ELANTP_HAPTIC_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "driver_ic", self->driver_ic); } static gboolean fu_elantp_haptic_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { gsize bufsz = g_bytes_get_size(fw); const guint8 *buf = g_bytes_get_data(fw, NULL); for (gsize i = 0; i < sizeof(elantp_haptic_signature_ictype02); i++) { guint8 tmp = 0x0; if (!fu_memread_uint8_safe(buf, bufsz, i + offset, &tmp, error)) return FALSE; if (tmp != elantp_haptic_signature_ictype02[i]) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "signature[%u] invalid: got 0x%2x, expected 0x%02x", (guint)i, tmp, elantp_haptic_signature_ictype02[i]); return FALSE; } } return TRUE; } static gboolean fu_elantp_haptic_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuElantpHapticFirmware *self = FU_ELANTP_HAPTIC_FIRMWARE(firmware); gsize bufsz = 0; guint8 v_s = 0; guint8 v_d = 0; guint8 v_m = 0; guint8 v_y = 0; guint8 tmp = 0; g_autofree gchar *version_str = NULL; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_return_val_if_fail(fw != NULL, FALSE); if (!fu_memread_uint8_safe(buf, bufsz, offset + 0x4, &tmp, error)) return FALSE; v_m = tmp & 0xF; v_s = (tmp & 0xF0) >> 4; if (!fu_memread_uint8_safe(buf, bufsz, offset + 0x5, &v_d, error)) return FALSE; if (!fu_memread_uint8_safe(buf, bufsz, offset + 0x6, &v_y, error)) return FALSE; if (v_y == 0xFF || v_d == 0xFF || v_m == 0xF) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "bad firmware version %02d%02d%02d%02d", v_y, v_m, v_d, v_s); return FALSE; } version_str = g_strdup_printf("%02d%02d%02d%02d", v_y, v_m, v_d, v_s); fu_firmware_set_version(FU_FIRMWARE(self), version_str); /* success */ self->driver_ic = 0x2; return TRUE; } static void fu_elantp_haptic_firmware_init(FuElantpHapticFirmware *self) { } static void fu_elantp_haptic_firmware_class_init(FuElantpHapticFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_elantp_haptic_firmware_check_magic; klass_firmware->parse = fu_elantp_haptic_firmware_parse; klass_firmware->export = fu_elantp_haptic_firmware_export; } FuFirmware * fu_elantp_haptic_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_ELANTP_HAPTIC_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/elantp/fu-elantp-haptic-firmware.h000066400000000000000000000007561460375044200230420ustar00rootroot00000000000000/* * Copyright (C) 2022 Jingle Wu * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ELANTP_HAPTIC_FIRMWARE (fu_elantp_haptic_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuElantpHapticFirmware, fu_elantp_haptic_firmware, FU, ELANTP_HAPTIC_FIRMWARE, FuFirmware) FuFirmware * fu_elantp_haptic_firmware_new(void); guint16 fu_elantp_haptic_firmware_get_driver_ic(FuElantpHapticFirmware *self); fwupd-1.9.16/plugins/elantp/fu-elantp-hid-device.c000066400000000000000000000672201460375044200217530ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-elantp-common.h" #include "fu-elantp-firmware.h" #include "fu-elantp-hid-device.h" #include "fu-elantp-hid-haptic-device.h" struct _FuElantpHidDevice { FuUdevDevice parent_instance; guint16 ic_page_count; guint16 ic_type; guint16 iap_type; guint16 iap_ctrl; guint16 iap_password; guint16 iap_ver; guint16 module_id; guint16 fw_page_size; gboolean force_table_support; guint32 force_table_addr; guint8 pattern; }; G_DEFINE_TYPE(FuElantpHidDevice, fu_elantp_hid_device, FU_TYPE_UDEV_DEVICE) #define FU_ELANTP_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ static gboolean fu_elantp_hid_device_detach(FuDevice *device, FuProgress *progress, GError **error); static void fu_elantp_hid_device_to_string(FuDevice *device, guint idt, GString *str) { FuElantpHidDevice *self = FU_ELANTP_HID_DEVICE(device); fu_string_append_kx(str, idt, "ModuleId", self->module_id); fu_string_append_kx(str, idt, "Pattern", self->pattern); fu_string_append_kx(str, idt, "FwPageSize", self->fw_page_size); fu_string_append_kx(str, idt, "IcPageCount", self->ic_page_count); fu_string_append_kx(str, idt, "IapType", self->iap_type); fu_string_append_kx(str, idt, "IapCtrl", self->iap_ctrl); } static gboolean fu_elantp_hid_device_probe(FuDevice *device, GError **error) { guint16 device_id = fu_udev_device_get_model(FU_UDEV_DEVICE(device)); /* check is valid */ if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "hidraw") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct subsystem=%s, expected hidraw", fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device))); return FALSE; } /* i2c-hid */ if (device_id != 0x400 && (device_id < 0x3000 || device_id >= 0x4000)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not i2c-hid touchpad"); return FALSE; } /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); } static gboolean fu_elantp_hid_device_send_cmd(FuElantpHidDevice *self, guint8 *tx, gsize txsz, guint8 *rx, gsize rxsz, GError **error) { g_autofree guint8 *buf = NULL; gsize bufsz = rxsz + 3; fu_dump_raw(G_LOG_DOMAIN, "SetReport", tx, txsz); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(txsz), tx, NULL, FU_ELANTP_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; if (rxsz == 0) return TRUE; /* GetFeature */ buf = g_malloc0(bufsz); buf[0] = tx[0]; /* report number */ if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGFEATURE(bufsz), buf, NULL, FU_ELANTP_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "GetReport", buf, bufsz); /* success */ return fu_memcpy_safe(rx, rxsz, 0x0, /* dst */ buf, bufsz, 0x3, /* src */ rxsz, error); } static gboolean fu_elantp_hid_device_read_cmd(FuElantpHidDevice *self, guint16 reg, guint8 *rx, gsize rxsz, GError **error) { guint8 buf[5] = {0x0d, 0x05, 0x03}; fu_memwrite_uint16(buf + 0x3, reg, G_LITTLE_ENDIAN); return fu_elantp_hid_device_send_cmd(self, buf, sizeof(buf), rx, rxsz, error); } static gint fu_elantp_hid_device_write_cmd(FuElantpHidDevice *self, guint16 reg, guint16 cmd, GError **error) { guint8 buf[5] = {0x0d}; fu_memwrite_uint16(buf + 0x1, reg, G_LITTLE_ENDIAN); fu_memwrite_uint16(buf + 0x3, cmd, G_LITTLE_ENDIAN); return fu_elantp_hid_device_send_cmd(self, buf, sizeof(buf), NULL, 0, error); } static gboolean fu_elantp_hid_device_ensure_iap_ctrl(FuElantpHidDevice *self, GError **error) { guint8 buf[2] = {0x0}; if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_IAP_CTRL, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read IAPControl: "); return FALSE; } self->iap_ctrl = fu_memread_uint16(buf, G_LITTLE_ENDIAN); /* in bootloader mode? */ if (self->force_table_support && self->iap_ver <= 5) { if ((self->iap_ctrl & ETP_I2C_MAIN_MODE_ON2) == 0) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); else fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } if ((self->iap_ctrl & ETP_I2C_MAIN_MODE_ON) == 0) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); else fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_elantp_hid_device_read_force_table_enable(FuElantpHidDevice *self, GError **error) { guint8 buf[2] = {0x0}; guint16 value; if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_FORCE_TYPE_ENABLE, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read force type cmd: "); return FALSE; } value = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if (value == 0xFFFF || value == ETP_CMD_I2C_FORCE_TYPE_ENABLE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "forcetype cmd not supported"); return FALSE; } if ((buf[0] & ETP_FW_FORCE_TYPE_ENABLE_BIT) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "force type table not supported"); return FALSE; } /* success */ return TRUE; } static gboolean fu_elantp_hid_device_read_haptic_enable(FuElantpHidDevice *self, GError **error) { guint8 buf[2] = {0x0}; guint16 value; if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_FORCE_TYPE_ENABLE, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read haptic enable cmd: "); return FALSE; } value = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if (value == 0xFFFF || value == ETP_CMD_I2C_FORCE_TYPE_ENABLE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not hapticpad"); return FALSE; } if ((buf[0] & ETP_FW_FORCE_TYPE_ENABLE_BIT) == 0 || (buf[0] & ETP_FW_EEPROM_ENABLE_BIT) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "the haptic eeprom not supported"); return FALSE; } /* success */ return TRUE; } static gboolean fu_elantp_hid_device_get_forcetable_address(FuElantpHidDevice *self, GError **error) { guint8 buf[2] = {0x0}; guint16 addr_wrds; if (self->iap_ver == 0x3) { self->force_table_addr = 0xFF40 * 2; return TRUE; } if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_FORCE_ADDR, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read force table address cmd: "); return FALSE; } addr_wrds = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if (addr_wrds % 32 != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "illegal force table address (%x)", addr_wrds); return FALSE; } self->force_table_addr = addr_wrds * 2; /* success */ return TRUE; } static gboolean fu_elantp_hid_device_write_fw_password(FuElantpHidDevice *self, guint16 ic_type, guint16 iap_ver, GError **error) { guint8 buf[2] = {0x0}; guint16 pw = ETP_I2C_IC13_IAPV5_PW; guint16 value; if (iap_ver < 0x5 || ic_type != 0x13) return TRUE; if (!fu_elantp_hid_device_write_cmd(self, ETP_CMD_I2C_FW_PW, pw, error)) { g_prefix_error(error, "failed to write fw password cmd: "); return FALSE; } if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_FW_PW, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read fw password cmd: "); return FALSE; } value = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if (value != pw) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "can't set fw password got:%x", value); return FALSE; } /* success */ return TRUE; } static gboolean fu_elantp_hid_device_setup(FuDevice *device, GError **error) { FuElantpHidDevice *self = FU_ELANTP_HID_DEVICE(device); FuUdevDevice *udev_device = FU_UDEV_DEVICE(device); guint16 fwver; guint16 tmp; guint8 buf[2] = {0x0}; g_autofree gchar *version_bl = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GError) error_forcetable = NULL; /* get pattern */ if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_GET_HID_ID, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read HID ID: "); return FALSE; } tmp = fu_memread_uint16(buf, G_LITTLE_ENDIAN); self->pattern = tmp != 0xFFFF ? (tmp & 0xFF00) >> 8 : 0; /* get current firmware version */ if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_FW_VERSION, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read fw version: "); return FALSE; } fwver = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if (fwver == 0xFFFF || fwver == ETP_CMD_I2C_FW_VERSION) fwver = 0; fu_device_set_version_u16(device, fwver); /* get IAP firmware version */ if (!fu_elantp_hid_device_read_cmd(self, self->pattern == 0 ? ETP_CMD_I2C_IAP_VERSION : ETP_CMD_I2C_IAP_VERSION_2, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read bootloader version: "); return FALSE; } if (self->pattern >= 1) { self->iap_ver = buf[1]; } else { self->iap_ver = fu_memread_uint16(buf, G_LITTLE_ENDIAN); } version_bl = fu_version_from_uint16(self->iap_ver, FWUPD_VERSION_FORMAT_HEX); fu_device_set_version_bootloader(device, version_bl); /* get module ID */ if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_GET_MODULE_ID, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read module ID: "); return FALSE; } self->module_id = fu_memread_uint16(buf, G_LITTLE_ENDIAN); /* define the extra instance IDs */ fu_device_add_instance_u16(device, "VEN", fu_udev_device_get_vendor(udev_device)); fu_device_add_instance_u16(device, "DEV", fu_udev_device_get_model(udev_device)); fu_device_add_instance_u16(device, "MOD", self->module_id); if (!fu_device_build_instance_id(device, error, "HIDRAW", "VEN", "DEV", "MOD", NULL)) return FALSE; /* get OSM version */ if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_OSM_VERSION, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read OSM version: "); return FALSE; } tmp = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if (tmp == ETP_CMD_I2C_OSM_VERSION || tmp == 0xFFFF) { if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_IAP_ICBODY, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read IC body: "); return FALSE; } self->ic_type = fu_memread_uint16(buf, G_LITTLE_ENDIAN) & 0xFF; } else { self->ic_type = (tmp >> 8) & 0xFF; } /* define the extra instance IDs (ic_type + module_id + driver) */ fu_device_add_instance_u8(device, "ICTYPE", self->ic_type); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "ELANTP", "ICTYPE", NULL); fu_device_build_instance_id(device, NULL, "ELANTP", "ICTYPE", "MOD", NULL); fu_device_add_instance_str(device, "DRIVER", "HID"); fu_device_build_instance_id(device, NULL, "ELANTP", "ICTYPE", "MOD", "DRIVER", NULL); /* no quirk entry */ if (self->ic_page_count == 0x0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no page count for ELANTP\\ICTYPE_%02X", self->ic_type); return FALSE; } /* The ic_page_count is based on 64 bytes/page. */ fu_device_set_firmware_size(device, (guint64)self->ic_page_count * (guint64)64); /* is in bootloader mode */ if (!fu_elantp_hid_device_ensure_iap_ctrl(self, error)) return FALSE; if (self->ic_type != 0x12 && self->ic_type != 0x13) return TRUE; if (!fu_elantp_hid_device_read_force_table_enable(self, &error_forcetable)) { g_debug("no forcetable detected: %s", error_forcetable->message); } else { if (!fu_elantp_hid_device_get_forcetable_address(self, error)) { g_prefix_error(error, "get forcetable address fail: "); return FALSE; } self->force_table_support = TRUE; /* is in bootloader mode */ if (!fu_elantp_hid_device_ensure_iap_ctrl(self, error)) return FALSE; } if (!fu_elantp_hid_device_read_haptic_enable(self, &error_local)) { g_debug("no haptic device detected: %s", error_local->message); } else { g_autoptr(FuElantpHidHapticDevice) cfg = fu_elantp_haptic_device_new(device); fu_device_add_child(FU_DEVICE(device), FU_DEVICE(cfg)); } /* fix an unsuitable i²c name, e.g. `VEN 04F3:00 04F3:3XXX` */ if (g_str_has_prefix(fu_device_get_name(device), "VEN 04F3:00 04F3:3")) fu_device_set_name(device, "Touchpad"); /* success */ return TRUE; } static FuFirmware * fu_elantp_hid_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuElantpHidDevice *self = FU_ELANTP_HID_DEVICE(device); guint16 module_id; guint16 ic_type; gboolean force_table_support; g_autoptr(FuFirmware) firmware = fu_elantp_firmware_new(); /* check is compatible with hardware */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; module_id = fu_elantp_firmware_get_module_id(FU_ELANTP_FIRMWARE(firmware)); if (self->module_id != module_id) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware incompatible, got 0x%04x, expected 0x%04x", module_id, self->module_id); return NULL; } ic_type = fu_elantp_firmware_get_ic_type(FU_ELANTP_FIRMWARE(firmware)); if (self->ic_type != ic_type) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware ic type incompatible, got 0x%04x, expected 0x%04x", ic_type, self->ic_type); return NULL; } force_table_support = fu_elantp_firmware_get_forcetable_support(FU_ELANTP_FIRMWARE(firmware)); if (self->force_table_support != force_table_support) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware incompatible, forcetable incorrect."); return NULL; } if (self->force_table_support) { guint32 force_table_addr; guint32 diff_size; force_table_addr = fu_elantp_firmware_get_forcetable_addr(FU_ELANTP_FIRMWARE(firmware)); if (self->force_table_addr < force_table_addr) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware forcetable address incompatible, got 0x%04x, expected 0x%04x", force_table_addr / 2, self->force_table_addr / 2); return NULL; } diff_size = self->force_table_addr - force_table_addr; if (diff_size % 64 != 0) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware forcetable address incompatible, got 0x%04x, expected 0x%04x", force_table_addr / 2, self->force_table_addr / 2); return NULL; } } /* success */ return g_steal_pointer(&firmware); } static gboolean fu_elantp_hid_device_filling_forcetable_firmware(FuDevice *device, guint8 *fw_data, gsize fw_size, guint32 force_table_addr, GError **error) { FuElantpHidDevice *self = FU_ELANTP_HID_DEVICE(device); const guint8 fillature[] = {0x77, 0x33, 0x44, 0xaa}; const guint8 signature[] = {0xAA, 0x55, 0xCC, 0x33, 0xFF, 0xFF}; guint8 buf[64] = {[0 ... 63] = 0xFF}; guint16 block_checksum; guint16 filling_value; if (self->force_table_addr == force_table_addr) return TRUE; if (self->force_table_addr < force_table_addr) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "forcetable address wrong (%x,%x): ", force_table_addr, self->force_table_addr); return FALSE; } if (!fu_memcpy_safe(buf, sizeof(buf), 0, /* dst */ fillature, sizeof(fillature), 0x0, /* src */ sizeof(fillature), error)) return FALSE; fu_memwrite_uint16(buf + 0x4, self->force_table_addr / 2, G_LITTLE_ENDIAN); if (!fu_memcpy_safe(buf, sizeof(buf), sizeof(buf) - 6, /* dst */ signature, sizeof(signature), 0x0, /* src */ sizeof(signature), error)) return FALSE; block_checksum = fu_sum16w(buf, sizeof(buf), G_LITTLE_ENDIAN) - 0xFFFF; filling_value = 0x10000 - (block_checksum & 0xFFFF); fu_memwrite_uint16(buf + 0x6, filling_value, G_LITTLE_ENDIAN); for (guint i = force_table_addr; i < self->force_table_addr; i += 64) { if (!fu_memcpy_safe(fw_data, fw_size, i, /* dst */ buf, sizeof(buf), 0x0, /* src */ sizeof(buf), error)) return FALSE; } return TRUE; } static gboolean fu_elantp_hid_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuElantpHidDevice *self = FU_ELANTP_HID_DEVICE(device); FuElantpFirmware *firmware_elantp = FU_ELANTP_FIRMWARE(firmware); gsize bufsz = 0; guint16 checksum = 0; guint16 checksum_device = 0; guint16 iap_addr; const guint8 *buf; guint8 csum_buf[2] = {0x0}; g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; guint total_pages; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 30, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 10, "reset"); /* simple image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* detach */ if (!fu_elantp_hid_device_detach(device, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* write each block */ buf = g_bytes_get_data(fw, &bufsz); iap_addr = fu_elantp_firmware_get_iap_addr(firmware_elantp); if (self->force_table_support && self->force_table_addr >= fu_elantp_firmware_get_forcetable_addr(FU_ELANTP_FIRMWARE(firmware))) { g_autofree guint8 *buf2 = g_malloc0(bufsz); if (!fu_memcpy_safe(buf2, bufsz, 0x0, /* dst */ buf, bufsz, 0x0, /* src */ bufsz, error)) return FALSE; if (!fu_elantp_hid_device_filling_forcetable_firmware( device, buf2, bufsz, fu_elantp_firmware_get_forcetable_addr(FU_ELANTP_FIRMWARE(firmware)), error)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "filling forcetable failed"); return FALSE; } chunks = fu_chunk_array_new(buf2 + iap_addr, bufsz - iap_addr, 0x0, 0x0, self->fw_page_size); total_pages = (self->force_table_addr - iap_addr - 1) / self->fw_page_size + 1; if (total_pages > chunks->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "total pages wrong (%u)", total_pages); return FALSE; } } else { chunks = fu_chunk_array_new(buf + iap_addr, bufsz - iap_addr, 0x0, 0x0, self->fw_page_size); total_pages = chunks->len; } for (guint i = 0; i < total_pages; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint16 csum_tmp = fu_sum16w(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), G_LITTLE_ENDIAN); gsize blksz = self->fw_page_size + 3; g_autofree guint8 *blk = g_malloc0(blksz); /* write block */ blk[0] = 0x0B; /* report ID */ if (!fu_memcpy_safe(blk, blksz, 0x1, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; fu_memwrite_uint16(blk + fu_chunk_get_data_sz(chk) + 1, csum_tmp, G_LITTLE_ENDIAN); if (!fu_elantp_hid_device_send_cmd(self, blk, blksz, NULL, 0, error)) return FALSE; fu_device_sleep(device, self->fw_page_size == 512 ? ELANTP_DELAY_WRITE_BLOCK_512 : ELANTP_DELAY_WRITE_BLOCK); if (!fu_elantp_hid_device_ensure_iap_ctrl(self, error)) return FALSE; if (self->iap_ctrl & (ETP_FW_IAP_PAGE_ERR | ETP_FW_IAP_INTF_ERR)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "bootloader reports failed write: 0x%x", self->iap_ctrl); return FALSE; } /* update progress */ checksum += csum_tmp; fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)chunks->len); } fu_progress_step_done(progress); /* verify the written checksum */ if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_IAP_CHECKSUM, csum_buf, sizeof(csum_buf), error)) return FALSE; if (!fu_memread_uint16_safe(csum_buf, sizeof(csum_buf), 0x0, &checksum_device, G_LITTLE_ENDIAN, error)) return FALSE; if (checksum != checksum_device) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "checksum failed 0x%04x != 0x%04x", checksum, checksum_device); return FALSE; } fu_progress_step_done(progress); /* wait for a reset */ fu_device_sleep_full(device, ELANTP_DELAY_COMPLETE, fu_progress_get_child(progress)); /* ms */ fu_progress_step_done(progress); return TRUE; } static gboolean fu_elantp_hid_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuElantpHidDevice *self = FU_ELANTP_HID_DEVICE(device); guint16 iap_ver; guint16 ic_type; guint8 buf[2] = {0x0}; guint16 tmp; /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_info("in bootloader mode, reset IC"); if (!fu_elantp_hid_device_write_cmd(self, ETP_CMD_I2C_IAP_RESET, ETP_I2C_IAP_RESET, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), ELANTP_DELAY_RESET); } /* get OSM version */ if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_OSM_VERSION, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read OSM version: "); return FALSE; } tmp = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if (tmp == ETP_CMD_I2C_OSM_VERSION || tmp == 0xFFFF) { if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_IAP_ICBODY, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read IC body: "); return FALSE; } ic_type = fu_memread_uint16(buf, G_LITTLE_ENDIAN) & 0xFF; } else { ic_type = (tmp >> 8) & 0xFF; } /* get IAP firmware version */ if (!fu_elantp_hid_device_read_cmd(self, self->pattern == 0 ? ETP_CMD_I2C_IAP_VERSION : ETP_CMD_I2C_IAP_VERSION_2, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read bootloader version: "); return FALSE; } if (self->pattern >= 1) { iap_ver = buf[1]; } else { iap_ver = fu_memread_uint16(buf, G_LITTLE_ENDIAN); } /* set the page size */ self->fw_page_size = 64; if (ic_type >= 0x10) { if (iap_ver >= 1) { /* set the IAP type, presumably some kind of ABI */ if (iap_ver >= 2 && (ic_type == 0x14 || ic_type == 0x15)) { self->fw_page_size = 512; } else { self->fw_page_size = 128; } if (!fu_elantp_hid_device_write_cmd(self, ETP_CMD_I2C_IAP_TYPE, self->fw_page_size / 2, error)) return FALSE; if (!fu_elantp_hid_device_read_cmd(self, ETP_CMD_I2C_IAP_TYPE, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read IAP type: "); return FALSE; } self->iap_type = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if (self->iap_type != self->fw_page_size / 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to set IAP type"); return FALSE; } } } if (!fu_elantp_hid_device_write_fw_password(self, ic_type, iap_ver, error)) return FALSE; if (!fu_elantp_hid_device_write_cmd(self, ETP_CMD_I2C_IAP, self->iap_password, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), ELANTP_DELAY_UNLOCK); if (!fu_elantp_hid_device_ensure_iap_ctrl(self, error)) return FALSE; if ((self->iap_ctrl & ETP_FW_IAP_CHECK_PW) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "unexpected bootloader password"); return FALSE; } /* success */ return TRUE; } static gboolean fu_elantp_hid_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuElantpHidDevice *self = FU_ELANTP_HID_DEVICE(device); /* sanity check */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } /* reset back to runtime */ if (!fu_elantp_hid_device_write_cmd(self, ETP_CMD_I2C_IAP_RESET, ETP_I2C_IAP_RESET, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), ELANTP_DELAY_RESET); if (!fu_elantp_hid_device_write_cmd(self, ETP_CMD_I2C_IAP_RESET, ETP_I2C_ENABLE_REPORT, error)) { g_prefix_error(error, "cannot enable TP report: "); return FALSE; } if (!fu_elantp_hid_device_write_cmd(self, 0x0306, 0x003, error)) { g_prefix_error(error, "cannot switch to TP PTP mode: "); return FALSE; } if (!fu_elantp_hid_device_ensure_iap_ctrl(self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_elantp_hid_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuElantpHidDevice *self = FU_ELANTP_HID_DEVICE(device); guint64 tmp = 0; if (g_strcmp0(key, "ElantpIcPageCount") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->ic_page_count = (guint16)tmp; return TRUE; } if (g_strcmp0(key, "ElantpIapPassword") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->iap_password = (guint16)tmp; return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_elantp_hid_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_elantp_hid_device_init(FuElantpHidDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_summary(FU_DEVICE(self), "Touchpad"); fu_device_add_icon(FU_DEVICE(self), "input-touchpad"); fu_device_add_protocol(FU_DEVICE(self), "tw.com.emc.elantp"); fu_device_set_vendor(FU_DEVICE(self), "ELAN Microelectronics"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_HEX); fu_device_set_priority(FU_DEVICE(self), 1); /* better than i2c */ fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | FU_UDEV_DEVICE_FLAG_OPEN_NONBLOCK); } static void fu_elantp_hid_device_finalize(GObject *object) { G_OBJECT_CLASS(fu_elantp_hid_device_parent_class)->finalize(object); } static void fu_elantp_hid_device_class_init(FuElantpHidDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_elantp_hid_device_finalize; klass_device->to_string = fu_elantp_hid_device_to_string; klass_device->attach = fu_elantp_hid_device_attach; klass_device->set_quirk_kv = fu_elantp_hid_device_set_quirk_kv; klass_device->setup = fu_elantp_hid_device_setup; klass_device->reload = fu_elantp_hid_device_setup; klass_device->write_firmware = fu_elantp_hid_device_write_firmware; klass_device->prepare_firmware = fu_elantp_hid_device_prepare_firmware; klass_device->probe = fu_elantp_hid_device_probe; klass_device->set_progress = fu_elantp_hid_device_set_progress; } fwupd-1.9.16/plugins/elantp/fu-elantp-hid-device.h000066400000000000000000000004731460375044200217550ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ELANTP_HID_DEVICE (fu_elantp_hid_device_get_type()) G_DECLARE_FINAL_TYPE(FuElantpHidDevice, fu_elantp_hid_device, FU, ELANTP_HID_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/elantp/fu-elantp-hid-haptic-device.c000066400000000000000000001001011460375044200232030ustar00rootroot00000000000000/* * Copyright (C) 2022 Jingle Wu * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-elantp-common.h" #include "fu-elantp-haptic-firmware.h" #include "fu-elantp-hid-haptic-device.h" struct _FuElantpHidHapticDevice { FuUdevDevice parent_instance; guint16 ic_page_count; guint16 iap_type; guint16 tp_iap_ctrl; guint16 tp_iap_ver; guint16 tp_ic_type; guint16 iap_ctrl; guint16 iap_password; guint16 module_id; guint16 fw_page_size; guint8 pattern; gint16 driver_ic; guint8 iap_ver; }; G_DEFINE_TYPE(FuElantpHidHapticDevice, fu_elantp_hid_haptic_device, FU_TYPE_UDEV_DEVICE) #define FU_ELANTP_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ static FuElantpHidDevice * fu_elantp_haptic_device_get_parent(FuDevice *self, GError **error) { FuDevice *parent = fu_device_get_parent(FU_DEVICE(self)); if (parent == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no parent set"); return NULL; } return FU_ELANTP_HID_DEVICE(FU_UDEV_DEVICE(parent)); } static gboolean fu_elantp_hid_haptic_device_detach(FuDevice *device, FuProgress *progress, GError **error); static void fu_elantp_hid_haptic_device_to_string(FuDevice *device, guint idt, GString *str) { FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); fu_string_append_kx(str, idt, "ModuleId", self->module_id); fu_string_append_kx(str, idt, "Pattern", self->pattern); fu_string_append_kx(str, idt, "FwPageSize", self->fw_page_size); fu_string_append_kx(str, idt, "IcPageCount", self->ic_page_count); fu_string_append_kx(str, idt, "IapType", self->iap_type); fu_string_append_kx(str, idt, "TpIapCtrl", self->tp_iap_ctrl); fu_string_append_kx(str, idt, "IapCtrl", self->iap_ctrl); fu_string_append_kx(str, idt, "DriverIC", self->driver_ic); fu_string_append_kx(str, idt, "IAPVersion", self->iap_ver); } static gboolean fu_elantp_hid_haptic_device_probe(FuDevice *device, GError **error) { /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); } static gboolean fu_elantp_hid_haptic_device_send_cmd(FuDevice *self, const guint8 *tx, gsize txsz, guint8 *rx, gsize rxsz, GError **error) { g_autofree guint8 *buf = NULL; gsize bufsz = rxsz + 3; fu_dump_raw(G_LOG_DOMAIN, "SetReport", tx, txsz); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(txsz), (guint8 *)tx, NULL, FU_ELANTP_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; if (rxsz == 0) return TRUE; /* GetFeature */ buf = g_malloc0(bufsz); buf[0] = tx[0]; /* report number */ if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGFEATURE(bufsz), buf, NULL, FU_ELANTP_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "GetReport", buf, bufsz); /* success */ return fu_memcpy_safe(rx, rxsz, 0x0, /* dst */ buf, bufsz, 0x3, /* src */ rxsz, error); } static gboolean fu_elantp_hid_haptic_device_read_cmd(FuDevice *self, guint16 reg, guint8 *buf, gsize bufz, GError **error) { guint8 tmp[5] = {0x0D, 0x05, 0x03}; fu_memwrite_uint16(tmp + 0x3, reg, G_LITTLE_ENDIAN); return fu_elantp_hid_haptic_device_send_cmd(self, tmp, sizeof(tmp), buf, bufz, error); } static gint fu_elantp_hid_haptic_device_write_cmd(FuDevice *self, guint16 reg, guint16 cmd, GError **error) { guint8 buf[5] = {0x0D}; fu_memwrite_uint16(buf + 0x1, reg, G_LITTLE_ENDIAN); fu_memwrite_uint16(buf + 0x3, cmd, G_LITTLE_ENDIAN); return fu_elantp_hid_haptic_device_send_cmd(self, buf, sizeof(buf), NULL, 0, error); } static gboolean fu_elantp_hid_haptic_device_ensure_iap_ctrl(FuDevice *parent, FuElantpHidHapticDevice *self, GError **error) { guint8 buf[2] = {0x0}; if (!fu_elantp_hid_haptic_device_read_cmd(parent, ETP_CMD_I2C_IAP_CTRL, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read IAPControl: "); return FALSE; } self->tp_iap_ctrl = fu_memread_uint16(buf, G_LITTLE_ENDIAN); /* in bootloader mode? */ if (self->tp_iap_ver <= 5) { if ((self->tp_iap_ctrl & ETP_I2C_MAIN_MODE_ON2) == 0) fu_device_add_flag(FU_DEVICE(parent), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); else fu_device_remove_flag(FU_DEVICE(parent), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } if ((self->tp_iap_ctrl & ETP_I2C_MAIN_MODE_ON) == 0) fu_device_add_flag(FU_DEVICE(parent), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); else fu_device_remove_flag(FU_DEVICE(parent), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_elantp_hid_haptic_device_ensure_eeprom_iap_ctrl(FuDevice *parent, FuElantpHidHapticDevice *self, GError **error) { guint8 buf[2] = {0x0}; if (!fu_elantp_hid_haptic_device_read_cmd(parent, ETP_CMD_I2C_SET_EEPROM_CTRL, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read IAPControl: "); return FALSE; } self->iap_ctrl = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if ((self->iap_ctrl & 0x800) != 0x800) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, "bit11 fail"); return FALSE; } if ((self->iap_ctrl & 0x1000) == 0x1000) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "bit12 fail, resend"); return FALSE; } return TRUE; } static gboolean fu_elantp_hid_haptic_device_get_haptic_driver_ic(FuDevice *parent, FuElantpHidHapticDevice *self, GError **error) { guint8 buf[2] = {0x0}; guint16 value; if (!fu_elantp_hid_haptic_device_read_cmd(parent, ETP_CMD_I2C_FORCE_TYPE_ENABLE, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read haptic enable cmd: "); return FALSE; } value = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if (value == 0xFFFF || value == ETP_CMD_I2C_FORCE_TYPE_ENABLE) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to read haptic enable cmd"); return FALSE; } if ((buf[0] & ETP_FW_FORCE_TYPE_ENABLE_BIT) == 0 || (buf[0] & ETP_FW_EEPROM_ENABLE_BIT) == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "eeprom enable bit unset"); return FALSE; } /* success */ self->driver_ic = (buf[0] >> 4) & 0xF; return TRUE; } static gboolean fu_elantp_hid_haptic_device_get_version(FuDevice *parent, FuElantpHidHapticDevice *self, GError **error) { guint16 v_s = 0; guint16 v_d = 0; guint16 v_m = 0; guint16 v_y = 0; guint8 buf[2] = {0x0}; if (!fu_elantp_hid_haptic_device_write_cmd(parent, ETP_CMD_I2C_SET_EEPROM_CTRL, ETP_CMD_I2C_GET_EEPROM_FW_VERSION, error)) { g_prefix_error(error, "failed to write haptic version cmd: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), ELANTP_DELAY_RESET); if (!fu_elantp_hid_haptic_device_read_cmd(parent, 0x0321, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read haptic version cmd: "); return FALSE; } v_d = buf[0]; v_m = buf[1] & 0xF; v_s = (buf[1] & 0xF0) >> 4; if (!fu_elantp_hid_haptic_device_write_cmd(parent, ETP_CMD_I2C_SET_EEPROM_CTRL, ETP_CMD_I2C_GET_EEPROM_IAP_VERSION, error)) { g_prefix_error(error, "failed to write haptic iap version cmd: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), ELANTP_DELAY_RESET); if (!fu_elantp_hid_haptic_device_read_cmd(parent, 0x0321, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read haptic iap version cmd: "); return FALSE; } v_y = buf[0]; self->iap_ver = buf[1]; if (v_y == 0xFF && v_d == 0xFF && v_m == 0xF) { fu_device_set_version(FU_DEVICE(self), "0"); } else { g_autofree gchar *str = g_strdup_printf("%02d%02d%02d%02d", v_y, v_m, v_d, v_s); fu_device_set_version(FU_DEVICE(self), str); } return TRUE; } static gboolean fu_elantp_hid_haptic_device_write_fw_password(FuDevice *parent, guint16 tp_ic_type, guint16 tp_iap_ver, GError **error) { guint8 buf[2] = {0x0}; guint16 pw = ETP_I2C_IC13_IAPV5_PW; guint16 value; if (tp_iap_ver < 0x5 || tp_ic_type != 0x13) return TRUE; if (!fu_elantp_hid_haptic_device_write_cmd(parent, ETP_CMD_I2C_FW_PW, pw, error)) { g_prefix_error(error, "failed to write fw password cmd: "); return FALSE; } if (!fu_elantp_hid_haptic_device_read_cmd(parent, ETP_CMD_I2C_FW_PW, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read fw password cmd: "); return FALSE; } value = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if (value != pw) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "can't set fw password got:%x", value); return FALSE; } /* success */ return TRUE; } typedef struct { guint16 checksum; guint16 iap_password; guint16 tp_iap_ver; guint16 tp_ic_type; } FuElantpHaptictpWaitFlashEEPROMChecksumHelper; static gboolean fu_elantp_hid_haptic_device_write_checksum_cb(FuDevice *parent, gpointer user_data, GError **error) { guint8 buf[2] = {0x0}; guint16 value; FuElantpHaptictpWaitFlashEEPROMChecksumHelper *helper = user_data; if (!fu_elantp_hid_haptic_device_write_cmd(parent, ETP_CMD_I2C_EEPROM_SETTING, ETP_CMD_I2C_EEPROM_WRITE_INFORMATION, error)) { g_prefix_error(error, "failed to write haptic info: "); return FALSE; } if (!fu_elantp_hid_haptic_device_read_cmd(parent, ETP_CMD_I2C_EEPROM_SETTING, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read haptic info: "); return FALSE; } value = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if ((value & 0xFFFF) != ETP_CMD_I2C_EEPROM_WRITE_INFORMATION) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to set haptic info (0x%04x): ", value); return FALSE; } if (!fu_elantp_hid_haptic_device_write_fw_password(parent, helper->tp_ic_type, helper->tp_iap_ver, error)) return FALSE; if (!fu_elantp_hid_haptic_device_write_cmd(parent, ETP_CMD_I2C_IAP, helper->iap_password, error)) { g_prefix_error(error, "failed to write iap password: "); return FALSE; } if (!fu_elantp_hid_haptic_device_write_cmd(parent, ETP_CMD_I2C_EEPROM_WRITE_CHECKSUM, helper->checksum, error)) { g_prefix_error(error, "failed to write eeprom checksum: "); return FALSE; } if (!fu_elantp_hid_haptic_device_write_cmd(parent, ETP_CMD_I2C_EEPROM_SETTING, ETP_CMD_I2C_EEPROM_SETTING_INITIAL, error)) { g_prefix_error(error, "failed to set haptic initial setting: "); return FALSE; } if (!fu_elantp_hid_haptic_device_read_cmd(parent, ETP_CMD_I2C_EEPROM_WRITE_CHECKSUM, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read haptic checksum: "); return FALSE; } value = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if ((value & 0xFFFF) != helper->checksum) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "eeprom checksum failed 0x%04x != 0x%04x : ", value, helper->checksum); return FALSE; } /* success */ return TRUE; } static gboolean fu_elantp_hid_haptic_device_wait_calc_checksum_cb(FuDevice *parent, gpointer user_data, GError **error) { guint16 ctrl; guint8 buf[2] = {0x0}; if (!fu_elantp_hid_haptic_device_write_cmd(parent, ETP_CMD_I2C_SET_EEPROM_CTRL, ETP_CMD_I2C_SET_EEPROM_DATATYPE, error)) { g_prefix_error(error, "failed to write eeprom datatype: "); return FALSE; } if (!fu_elantp_hid_haptic_device_read_cmd(parent, ETP_CMD_I2C_SET_EEPROM_CTRL, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read calc haptic cmd: "); return FALSE; } ctrl = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if ((ctrl & 0x20) == 0x20) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "ctrl failed 0x%04x", ctrl); return FALSE; } return TRUE; } static gboolean fu_elantp_hid_haptic_device_get_checksum(FuDevice *parent, guint16 *checksum, GError **error) { guint8 buf[2] = {0x0}; g_autoptr(GError) error_local = NULL; if (!fu_elantp_hid_haptic_device_write_cmd(parent, ETP_CMD_I2C_SET_EEPROM_CTRL, ETP_CMD_I2C_CALC_EEPROM_CHECKSUM, error)) return FALSE; if (!fu_device_retry_full(parent, fu_elantp_hid_haptic_device_wait_calc_checksum_cb, 100, ELANTP_EEPROM_READ_DELAY, NULL, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to wait calc eeprom checksum (%s)", error_local->message); return FALSE; } if (!fu_elantp_hid_haptic_device_write_cmd(parent, ETP_CMD_I2C_SET_EEPROM_CTRL, ETP_CMD_I2C_READ_EEPROM_CHECKSUM, error)) return FALSE; if (!fu_elantp_hid_haptic_device_read_cmd(parent, ETP_CMD_I2C_SET_EEPROM_CTRL, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read haptic checksum cmd: "); return FALSE; } *checksum = fu_memread_uint16(buf, G_LITTLE_ENDIAN); return TRUE; } static gboolean fu_elantp_hid_haptic_device_setup(FuDevice *device, GError **error) { FuElantpHidDevice *parent; FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); FuUdevDevice *udev_parent; guint8 ic_type; guint16 tmp; guint8 buf[2] = {0x0}; g_autofree gchar *version_bl = NULL; parent = fu_elantp_haptic_device_get_parent(device, error); if (parent == NULL) return FALSE; if (!fu_elantp_hid_haptic_device_get_haptic_driver_ic(FU_DEVICE(parent), self, error)) { g_prefix_error(error, "this module is not support haptic EEPROM: "); return FALSE; } /* get pattern */ if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), ETP_CMD_I2C_GET_HID_ID, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read HID ID: "); return FALSE; } tmp = fu_memread_uint16(buf, G_LITTLE_ENDIAN); self->pattern = tmp != 0xFFFF ? (tmp & 0xFF00) >> 8 : 0; if (!fu_elantp_hid_haptic_device_get_version(FU_DEVICE(parent), self, error)) return FALSE; version_bl = fu_version_from_uint16(self->iap_ver, FWUPD_VERSION_FORMAT_HEX); fu_device_set_version_bootloader(device, version_bl); /* get module ID */ if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), ETP_CMD_GET_MODULE_ID, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read module ID: "); return FALSE; } self->module_id = fu_memread_uint16(buf, G_LITTLE_ENDIAN); /* define the extra instance IDs */ udev_parent = FU_UDEV_DEVICE(parent); fu_device_add_instance_u16(device, "VEN", fu_udev_device_get_vendor(udev_parent)); fu_device_add_instance_u16(device, "DEV", fu_udev_device_get_model(udev_parent)); fu_device_add_instance_u16(device, "DRIVERIC", self->driver_ic); fu_device_add_instance_u16(device, "MOD", self->module_id); if (!fu_device_build_instance_id(device, error, "HIDRAW", "VEN", "DEV", "DRIVERIC", "MOD", NULL)) return FALSE; /* get OSM version */ if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), ETP_CMD_I2C_OSM_VERSION, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read OSM version: "); return FALSE; } tmp = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if (tmp == ETP_CMD_I2C_OSM_VERSION || tmp == 0xFFFF) { if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), ETP_CMD_I2C_IAP_ICBODY, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read IC body: "); return FALSE; } ic_type = fu_memread_uint16(buf, G_LITTLE_ENDIAN) & 0xFF; } else ic_type = (tmp >> 8) & 0xFF; /* define the extra instance IDs (ic_type + module_id + driver) */ fu_device_add_instance_u8(device, "ICTYPE", ic_type); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "ELANTP", "ICTYPE", NULL); fu_device_build_instance_id(device, NULL, "ELANTP", "ICTYPE", "DRIVERIC", "MOD", NULL); fu_device_add_instance_str(device, "DRIVER", "HID"); fu_device_build_instance_id(device, NULL, "ELANTP", "ICTYPE", "DRIVERIC", "MOD", "DRIVER", NULL); /* no quirk entry */ if (self->ic_page_count == 0x0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no page count for ELANTP\\ICTYPE_%02X", ic_type); return FALSE; } fu_device_set_firmware_size(device, 32768); /* find out if in bootloader mode */ if (!fu_elantp_hid_haptic_device_ensure_iap_ctrl(FU_DEVICE(parent), self, error)) return FALSE; /* success */ return TRUE; } static FuFirmware * fu_elantp_hid_haptic_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); guint16 driver_ic; g_autoptr(FuFirmware) firmware = fu_elantp_haptic_firmware_new(); /* check is compatible with hardware */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; driver_ic = fu_elantp_haptic_firmware_get_driver_ic(FU_ELANTP_HAPTIC_FIRMWARE(firmware)); if (driver_ic != self->driver_ic) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "driver IC 0x%x != 0x%x", (guint)driver_ic, (guint)self->driver_ic); return NULL; } /* success */ return g_steal_pointer(&firmware); } typedef struct { guint16 checksum; guint idx_page_start; GBytes *fw; /* noref */ FuProgress *progress; /* noref */ } FuElantpHaptictpWriteHelper; static gboolean fu_elantp_hid_haptic_device_write_chunks_cb(FuDevice *device, gpointer user_data, GError **error) { FuElantpHaptictpWriteHelper *helper = (FuElantpHaptictpWriteHelper *)user_data; FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); FuElantpHidDevice *parent; const guint16 eeprom_fw_page_size = 32; g_autoptr(FuChunkArray) chunks = NULL; /* use parent */ parent = fu_elantp_haptic_device_get_parent(device, error); if (parent == NULL) return FALSE; /* progress */ chunks = fu_chunk_array_new_from_bytes(helper->fw, 0x0, eeprom_fw_page_size); fu_progress_set_id(helper->progress, G_STRLOC); fu_progress_set_steps(helper->progress, fu_chunk_array_length(chunks) - helper->idx_page_start + 1); for (guint i = helper->idx_page_start; i <= fu_chunk_array_length(chunks); i++) { guint16 csum_tmp; gsize blksz = self->fw_page_size + 3; g_autofree guint8 *blk = g_malloc0(blksz); g_autoptr(FuChunk) chk = NULL; g_autoptr(GError) error_iapctrl = NULL; if (i == fu_chunk_array_length(chunks)) chk = fu_chunk_array_index(chunks, 0); else chk = fu_chunk_array_index(chunks, i); /* write block */ blk[0] = 0x0B; /* report ID */ blk[1] = eeprom_fw_page_size + 5; blk[2] = 0xA2; fu_memwrite_uint16(blk + 0x3, i * eeprom_fw_page_size, G_BIG_ENDIAN); if (i == 0) { guint8 first_page[32] = {0x0}; memset(&first_page[0], 0xFF, sizeof(first_page)); csum_tmp = fu_sum16(first_page, eeprom_fw_page_size); if (!fu_memcpy_safe(blk, blksz, 0x5, /* dst */ first_page, eeprom_fw_page_size, 0x0, /* src */ eeprom_fw_page_size, error)) return FALSE; fu_memwrite_uint16(blk + eeprom_fw_page_size + 5, csum_tmp, G_BIG_ENDIAN); csum_tmp = 0; } else { csum_tmp = fu_sum16(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); if (!fu_memcpy_safe(blk, blksz, 0x5, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; fu_memwrite_uint16(blk + fu_chunk_get_data_sz(chk) + 5, csum_tmp, G_BIG_ENDIAN); } if (!fu_elantp_hid_haptic_device_send_cmd(FU_DEVICE(parent), blk, blksz, NULL, 0, error)) return FALSE; fu_device_sleep(device, self->fw_page_size == 512 ? ELANTP_DELAY_WRITE_BLOCK_512 : ELANTP_DELAY_WRITE_BLOCK); if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), ETP_CMD_I2C_SET_EEPROM_CTRL, ETP_CMD_I2C_SET_EEPROM_DATATYPE, error)) return FALSE; if (!fu_elantp_hid_haptic_device_ensure_eeprom_iap_ctrl(FU_DEVICE(parent), self, &error_iapctrl)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "bootloader reports failed write: 0x%x (%s)", self->iap_ctrl, error_iapctrl->message); return FALSE; } /* update progress */ helper->checksum += csum_tmp; helper->idx_page_start = i + 1; fu_progress_step_done(helper->progress); } /* success */ return TRUE; } static gboolean fu_elantp_hid_haptic_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuElantpHidDevice *parent; FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); guint16 checksum_device = 0; const gchar *fw_ver; const gchar *fw_ver_device; g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error_local = NULL; FuElantpHaptictpWaitFlashEEPROMChecksumHelper helper = {0x0}; FuElantpHaptictpWriteHelper helper_write = {0x0}; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, NULL); /* simple image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* use parent */ parent = fu_elantp_haptic_device_get_parent(device, error); if (parent == NULL) return FALSE; /* detach */ if (!fu_elantp_hid_haptic_device_detach(device, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* write each block */ helper_write.fw = fw; helper_write.progress = fu_progress_get_child(progress); if (!fu_device_retry_full(device, fu_elantp_hid_haptic_device_write_chunks_cb, 3, 100, &helper_write, error)) return FALSE; fu_progress_step_done(progress); if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), ETP_CMD_I2C_EEPROM_SETTING, ETP_CMD_I2C_EEPROM_SETTING_INITIAL, error)) { g_prefix_error(error, "cannot disable EEPROM Long Transmission mode: "); return FALSE; } if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), ETP_CMD_I2C_SET_EEPROM_CTRL, ETP_CMD_I2C_SET_EEPROM_LEAVE_IAP, error)) { g_prefix_error(error, "cannot leave EEPROM IAP: "); return FALSE; } fu_device_sleep(device, ELANTP_DELAY_RESET); if (!fu_elantp_hid_haptic_device_get_checksum(FU_DEVICE(parent), &checksum_device, error)) { g_prefix_error(error, "read device checksum fail: "); return FALSE; } if (helper_write.checksum != checksum_device) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "checksum failed 0x%04x != 0x%04x", helper_write.checksum, checksum_device); return FALSE; } helper.checksum = checksum_device; helper.iap_password = self->iap_password; helper.tp_ic_type = self->tp_ic_type; helper.tp_iap_ver = self->tp_iap_ver; if (!fu_device_retry_full(FU_DEVICE(parent), fu_elantp_hid_haptic_device_write_checksum_cb, 3, ELANTP_DELAY_WRITE_BLOCK, &helper, &error_local)) { g_prefix_error(error, "write device checksum fail (%s): ", error_local->message); return FALSE; } if (!fu_elantp_hid_haptic_device_get_version(FU_DEVICE(parent), self, error)) return FALSE; fw_ver_device = fu_device_get_version(device); fw_ver = fu_firmware_get_version(firmware); if (g_strcmp0(fw_ver_device, fw_ver) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "firmware version failed %s != %s", fw_ver, fw_ver_device); return FALSE; } fu_progress_step_done(progress); if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), ETP_CMD_I2C_SET_EEPROM_CTRL, ETP_CMD_I2C_HAPTIC_RESTART, error)) { g_prefix_error(error, "cannot restart haptic DriverIC: "); return FALSE; } fu_progress_step_done(progress); return TRUE; } static gboolean fu_elantp_hid_haptic_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuElantpHidDevice *parent; FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); guint8 buf[2] = {0x0}; guint16 ctrl; guint16 tmp; /* haptic EEPROM IAP process runs in the TP main code */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "in touchpad bootloader mode"); return FALSE; } if (self->driver_ic != 0x2 || self->iap_ver != 0x1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no support for EEPROM IAP 0x%x 0x%x: ", (guint)self->driver_ic, (guint)self->iap_ver); return FALSE; } parent = fu_elantp_haptic_device_get_parent(device, error); if (parent == NULL) return FALSE; /* get OSM version */ if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), ETP_CMD_I2C_OSM_VERSION, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read OSM version: "); return FALSE; } tmp = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if (tmp == ETP_CMD_I2C_OSM_VERSION || tmp == 0xFFFF) { if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), ETP_CMD_I2C_IAP_ICBODY, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read IC body: "); return FALSE; } self->tp_ic_type = fu_memread_uint16(buf, G_LITTLE_ENDIAN) & 0xFF; } else self->tp_ic_type = (tmp >> 8) & 0xFF; /* get IAP firmware version */ if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), self->pattern == 0 ? ETP_CMD_I2C_IAP_VERSION : ETP_CMD_I2C_IAP_VERSION_2, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read bootloader version: "); return FALSE; } if (self->pattern >= 1) self->tp_iap_ver = buf[1]; else self->tp_iap_ver = fu_memread_uint16(buf, G_LITTLE_ENDIAN); /* set the page size */ self->fw_page_size = 64; if (self->tp_ic_type >= 0x10) { if (self->tp_iap_ver >= 1) { /* set the IAP type, presumably some kind of ABI */ if (self->tp_iap_ver >= 2 && (self->tp_ic_type == 0x14 || self->tp_ic_type == 0x15)) { self->fw_page_size = 512; } else { self->fw_page_size = 128; } if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), ETP_CMD_I2C_IAP_TYPE, self->fw_page_size / 2, error)) return FALSE; if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), ETP_CMD_I2C_IAP_TYPE, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read IAP type: "); return FALSE; } self->iap_type = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if (self->iap_type != self->fw_page_size / 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to set IAP type"); return FALSE; } } } if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), ETP_CMD_I2C_EEPROM_SETTING, ETP_CMD_I2C_EEPROM_LONG_TRANS_ENABLE, error)) { g_prefix_error(error, "cannot enable EEPROM Long Transmission mode: "); return FALSE; } if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), ETP_CMD_I2C_SET_EEPROM_CTRL, ETP_CMD_I2C_SET_EEPROM_ENTER_IAP, error)) { g_prefix_error(error, "cannot enter EEPROM IAP: "); return FALSE; } if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent), ETP_CMD_I2C_SET_EEPROM_CTRL, buf, sizeof(buf), error)) return FALSE; ctrl = fu_memread_uint16(buf, G_LITTLE_ENDIAN); if ((ctrl & 0x800) == 0x800) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "unexpected EEPROM bootloader control %x", ctrl); return FALSE; } /* success */ return TRUE; } static gboolean fu_elantp_hid_haptic_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuElantpHidDevice *parent; FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); parent = fu_elantp_haptic_device_get_parent(device, error); if (parent == NULL) return FALSE; /* reset back to runtime */ if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), ETP_CMD_I2C_IAP_RESET, ETP_I2C_IAP_RESET, error)) { g_prefix_error(error, "cannot reset TP: "); return FALSE; } fu_device_sleep(device, ELANTP_DELAY_RESET); if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), ETP_CMD_I2C_IAP_RESET, ETP_I2C_ENABLE_REPORT, error)) { g_prefix_error(error, "cannot enable TP report: "); return FALSE; } if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), 0x0306, 0x003, error)) { g_prefix_error(error, "cannot switch to TP PTP mode: "); return FALSE; } if (!fu_elantp_hid_haptic_device_ensure_iap_ctrl(FU_DEVICE(parent), self, error)) return FALSE; /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "in bootloader mode"); return FALSE; } /* success */ return TRUE; } static gboolean fu_elantp_hid_haptic_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device); guint64 tmp = 0; if (g_strcmp0(key, "ElantpIcPageCount") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->ic_page_count = (guint16)tmp; return TRUE; } if (g_strcmp0(key, "ElantpIapPassword") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->iap_password = (guint16)tmp; return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_elantp_hid_haptic_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 85, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 8, "reload"); } static void fu_elantp_hid_haptic_device_init(FuElantpHidHapticDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); fu_device_add_icon(FU_DEVICE(self), "input-touchpad"); fu_device_add_protocol(FU_DEVICE(self), "tw.com.emc.elantp.haptic"); fu_device_set_name(FU_DEVICE(self), "HapticPad EEPROM"); fu_device_set_logical_id(FU_DEVICE(self), "eeprom"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_priority(FU_DEVICE(self), 1); /* better than i2c */ } static void fu_elantp_hid_haptic_device_class_init(FuElantpHidHapticDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_elantp_hid_haptic_device_to_string; klass_device->attach = fu_elantp_hid_haptic_device_attach; klass_device->set_quirk_kv = fu_elantp_hid_haptic_device_set_quirk_kv; klass_device->setup = fu_elantp_hid_haptic_device_setup; klass_device->reload = fu_elantp_hid_haptic_device_setup; klass_device->write_firmware = fu_elantp_hid_haptic_device_write_firmware; klass_device->prepare_firmware = fu_elantp_hid_haptic_device_prepare_firmware; klass_device->probe = fu_elantp_hid_haptic_device_probe; klass_device->set_progress = fu_elantp_hid_haptic_device_set_progress; } FuElantpHidHapticDevice * fu_elantp_haptic_device_new(FuDevice *device) { FuElantpHidHapticDevice *self; self = g_object_new(FU_TYPE_ELANTP_HID_HAPTIC_DEVICE, NULL); return FU_ELANTP_HID_HAPTIC_DEVICE(self); } fwupd-1.9.16/plugins/elantp/fu-elantp-hid-haptic-device.h000066400000000000000000000007431460375044200232230ustar00rootroot00000000000000/* * Copyright (C) 2022 Jingle Wu * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-elantp-hid-device.h" #define FU_TYPE_ELANTP_HID_HAPTIC_DEVICE (fu_elantp_hid_haptic_device_get_type()) G_DECLARE_FINAL_TYPE(FuElantpHidHapticDevice, fu_elantp_hid_haptic_device, FU, ELANTP_HID_HAPTIC_DEVICE, FuUdevDevice) FuElantpHidHapticDevice * fu_elantp_haptic_device_new(FuDevice *device); fwupd-1.9.16/plugins/elantp/fu-elantp-i2c-device.c000066400000000000000000000606031460375044200216620ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-elantp-common.h" #include "fu-elantp-firmware.h" #include "fu-elantp-i2c-device.h" struct _FuElantpI2cDevice { FuI2cDevice parent_instance; guint16 i2c_addr; guint16 ic_page_count; guint16 iap_type; guint16 iap_ctrl; guint16 iap_password; guint16 module_id; guint16 fw_page_size; guint8 pattern; gchar *bind_path; gchar *bind_id; }; G_DEFINE_TYPE(FuElantpI2cDevice, fu_elantp_i2c_device, FU_TYPE_I2C_DEVICE) #define FU_ELANTP_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ static gboolean fu_elantp_i2c_device_detach(FuDevice *device, FuProgress *progress, GError **error); static void fu_elantp_i2c_device_to_string(FuDevice *device, guint idt, GString *str) { FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE(device); fu_string_append_kx(str, idt, "I2cAddr", self->i2c_addr); fu_string_append_kx(str, idt, "ModuleId", self->module_id); fu_string_append_kx(str, idt, "Pattern", self->pattern); fu_string_append_kx(str, idt, "FwPageSize", self->fw_page_size); fu_string_append_kx(str, idt, "IcPageCount", self->ic_page_count); fu_string_append_kx(str, idt, "IapType", self->iap_type); fu_string_append_kx(str, idt, "IapCtrl", self->iap_ctrl); fu_string_append(str, idt, "BindPath", self->bind_path); fu_string_append(str, idt, "BindId", self->bind_id); } static gboolean fu_elantp_i2c_device_writeln(const gchar *fn, const gchar *buf, GError **error) { int fd; g_autoptr(FuIOChannel) io = NULL; if (!g_file_test(fn, G_FILE_TEST_EXISTS)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "%s does not exist", fn); return FALSE; } fd = open(fn, O_WRONLY); if (fd < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "could not open %s", fn); return FALSE; } io = fu_io_channel_unix_new(fd); return fu_io_channel_write_raw(io, (const guint8 *)buf, strlen(buf), 1000, FU_IO_CHANNEL_FLAG_NONE, error); } static gboolean fu_elantp_i2c_device_rebind_driver(FuElantpI2cDevice *self, GError **error) { if (self->bind_path == NULL || self->bind_id == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no Path or ID for rebind driver"); return FALSE; } if (!fu_elantp_i2c_device_writeln(g_build_filename(self->bind_path, "unbind", NULL), self->bind_id, error)) return FALSE; if (!fu_elantp_i2c_device_writeln(g_build_filename(self->bind_path, "bind", NULL), self->bind_id, error)) return FALSE; g_debug("rebind driver of %s", self->bind_id); return TRUE; } static gboolean fu_elantp_i2c_device_probe(FuDevice *device, GError **error) { FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE(device); /* check is valid */ if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "i2c") == 0) { g_autoptr(GPtrArray) i2c_buses = NULL; FuUdevDevice *i2c_device = fu_udev_device_get_parent_with_subsystem(FU_UDEV_DEVICE(device), "i2c"); if (i2c_device == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "did not find the i2c parent for device"); return FALSE; } i2c_buses = fu_udev_device_get_children_with_subsystem(i2c_device, "i2c-dev"); if (i2c_buses->len == 1) { FuUdevDevice *bus_device = g_object_ref(g_ptr_array_index(i2c_buses, 0)); if (bus_device == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "did not find the i2c-dev children for device"); return FALSE; } g_debug("found I2C bus at %s, using this device", fu_udev_device_get_sysfs_path(bus_device)); self->bind_path = g_build_filename("/sys/bus/i2c/drivers", fu_udev_device_get_driver(FU_UDEV_DEVICE(device)), NULL); self->bind_id = g_path_get_basename( fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device))); fu_udev_device_set_dev(FU_UDEV_DEVICE(device), fu_udev_device_get_dev(bus_device)); } } if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "i2c-dev") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct subsystem=%s, expected i2c-dev", fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device))); return FALSE; } if (fu_udev_device_get_device_file(FU_UDEV_DEVICE(device)) == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no device file"); return FALSE; } /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "i2c", error); } static gboolean fu_elantp_i2c_device_send_cmd(FuElantpI2cDevice *self, guint8 *tx, gssize txsz, guint8 *rx, gssize rxsz, GError **error) { fu_dump_raw(G_LOG_DOMAIN, "Write", tx, txsz); if (!fu_udev_device_pwrite(FU_UDEV_DEVICE(self), 0, tx, txsz, error)) return FALSE; if (rxsz == 0) return TRUE; if (!fu_udev_device_pread(FU_UDEV_DEVICE(self), 0, rx, rxsz, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "Read", rx, rxsz); return TRUE; } static gboolean fu_elantp_i2c_device_write_cmd(FuElantpI2cDevice *self, guint16 reg, guint16 cmd, GError **error) { guint8 buf[4]; fu_memwrite_uint16(buf + 0x0, reg, G_LITTLE_ENDIAN); fu_memwrite_uint16(buf + 0x2, cmd, G_LITTLE_ENDIAN); return fu_elantp_i2c_device_send_cmd(self, buf, sizeof(buf), NULL, 0, error); } static gboolean fu_elantp_i2c_device_read_cmd(FuElantpI2cDevice *self, guint16 reg, guint8 *rx, gsize rxsz, GError **error) { guint8 buf[2]; fu_memwrite_uint16(buf + 0x0, reg, G_LITTLE_ENDIAN); return fu_elantp_i2c_device_send_cmd(self, buf, sizeof(buf), rx, rxsz, error); } static gboolean fu_elantp_i2c_device_ensure_iap_ctrl(FuElantpI2cDevice *self, GError **error) { guint8 buf[2] = {0x0}; if (!fu_elantp_i2c_device_read_cmd(self, ETP_CMD_I2C_IAP_CTRL, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read IAPControl: "); return FALSE; } if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x0, &self->iap_ctrl, G_LITTLE_ENDIAN, error)) return FALSE; /* in bootloader mode? */ if ((self->iap_ctrl & ETP_I2C_MAIN_MODE_ON) == 0) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); else fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_elantp_i2c_device_setup(FuDevice *device, GError **error) { FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE(device); guint16 fwver; guint16 iap_ver; guint16 tmp; guint16 pid; guint16 vid; guint8 buf[30] = {0x0}; guint8 ic_type; g_autofree gchar *version_bl = NULL; /* read the I2C descriptor */ if (!fu_elantp_i2c_device_read_cmd(self, ETP_CMD_GET_HID_DESCRIPTOR, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to get HID descriptor: "); return FALSE; } if (!fu_memread_uint16_safe(buf, sizeof(buf), 20, &vid, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(buf, sizeof(buf), 22, &pid, G_LITTLE_ENDIAN, error)) return FALSE; /* set the vendor ID */ if (vid != 0x0000) { g_autofree gchar *vendor_id = NULL; vendor_id = g_strdup_printf("HIDRAW:0x%04X", vid); fu_device_add_vendor_id(device, vendor_id); } /* add GUIDs in order of priority */ fu_device_add_instance_u16(device, "VID", vid); fu_device_add_instance_u16(device, "PID", pid); if (!fu_device_build_instance_id(device, error, "HIDRAW", "VID", "PID", NULL)) return FALSE; /* get pattern */ if (!fu_elantp_i2c_device_read_cmd(self, ETP_CMD_I2C_GET_HID_ID, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read I2C ID: "); return FALSE; } if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x0, &tmp, G_LITTLE_ENDIAN, error)) return FALSE; self->pattern = tmp != 0xffff ? (tmp & 0xff00) >> 8 : 0; /* get current firmware version */ if (!fu_elantp_i2c_device_read_cmd(self, ETP_CMD_I2C_FW_VERSION, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read fw version: "); return FALSE; } if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x0, &fwver, G_LITTLE_ENDIAN, error)) return FALSE; if (fwver == 0xFFFF || fwver == ETP_CMD_I2C_FW_VERSION) fwver = 0; fu_device_set_version_u16(device, fwver); /* get IAP firmware version */ if (!fu_elantp_i2c_device_read_cmd(self, self->pattern == 0 ? ETP_CMD_I2C_IAP_VERSION : ETP_CMD_I2C_IAP_VERSION_2, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read bootloader version: "); return FALSE; } if (self->pattern >= 1) { iap_ver = buf[1]; } else { if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x0, &iap_ver, G_LITTLE_ENDIAN, error)) return FALSE; } version_bl = fu_version_from_uint16(iap_ver, FWUPD_VERSION_FORMAT_HEX); fu_device_set_version_bootloader(device, version_bl); /* get module ID */ if (!fu_elantp_i2c_device_read_cmd(self, ETP_CMD_GET_MODULE_ID, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read module ID: "); return FALSE; } if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x0, &self->module_id, G_LITTLE_ENDIAN, error)) return FALSE; /* define the extra instance IDs */ fu_device_add_instance_u16(device, "VEN", vid); fu_device_add_instance_u16(device, "DEV", pid); fu_device_add_instance_u16(device, "MOD", self->module_id); if (!fu_device_build_instance_id(device, error, "HIDRAW", "VEN", "DEV", "MOD", NULL)) return FALSE; /* get OSM version */ if (!fu_elantp_i2c_device_read_cmd(self, ETP_CMD_I2C_OSM_VERSION, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read OSM version: "); return FALSE; } if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x0, &tmp, G_LITTLE_ENDIAN, error)) return FALSE; if (tmp == ETP_CMD_I2C_OSM_VERSION || tmp == 0xFFFF) { if (!fu_elantp_i2c_device_read_cmd(self, ETP_CMD_I2C_IAP_ICBODY, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read IC body: "); return FALSE; } if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x0, &tmp, G_LITTLE_ENDIAN, error)) return FALSE; ic_type = tmp & 0xFF; } else { ic_type = (tmp >> 8) & 0xFF; } /* define the extra instance IDs (ic_type + module_id + driver) */ fu_device_add_instance_u8(device, "ICTYPE", ic_type); fu_device_build_instance_id(device, NULL, "ELANTP", "ICTYPE", NULL); fu_device_build_instance_id(device, NULL, "ELANTP", "ICTYPE", "MOD", NULL); if (fu_device_has_private_flag(device, FU_ELANTP_I2C_DEVICE_ABSOLUTE)) { fu_device_add_instance_str(device, "DRIVER", "ELAN_I2C"); } else { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_instance_str(device, "DRIVER", "HID"); } fu_device_build_instance_id(device, NULL, "ELANTP", "ICTYPE", "MOD", "DRIVER", NULL); /* no quirk entry */ if (self->ic_page_count == 0x0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no page count for ELANTP\\ICTYPE_%02X", ic_type); return FALSE; } fu_device_set_firmware_size(device, (guint64)self->ic_page_count * (guint64)64); /* is in bootloader mode */ if (!fu_elantp_i2c_device_ensure_iap_ctrl(self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_elantp_i2c_device_open(FuDevice *device, GError **error) { FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE(device); gint addr = self->i2c_addr; guint8 tx_buf[] = {0x02, 0x01}; /* FuUdevDevice->open */ if (!FU_DEVICE_CLASS(fu_elantp_i2c_device_parent_class)->open(device, error)) return FALSE; /* set target address */ if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(device), I2C_SLAVE, GINT_TO_POINTER(addr), NULL, FU_ELANTP_DEVICE_IOCTL_TIMEOUT, NULL)) { if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(device), I2C_SLAVE_FORCE, GINT_TO_POINTER(addr), NULL, FU_ELANTP_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error(error, "failed to set target address to 0x%x: ", self->i2c_addr); return FALSE; } } /* read i2c device */ return fu_udev_device_pwrite(FU_UDEV_DEVICE(device), 0x0, tx_buf, sizeof(tx_buf), error); } static FuFirmware * fu_elantp_i2c_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE(device); guint16 module_id; g_autoptr(FuFirmware) firmware = fu_elantp_firmware_new(); /* check is compatible with hardware */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; module_id = fu_elantp_firmware_get_module_id(FU_ELANTP_FIRMWARE(firmware)); if (self->module_id != module_id) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware incompatible, got 0x%04x, expected 0x%04x", module_id, self->module_id); return NULL; } /* success */ return g_steal_pointer(&firmware); } static gboolean fu_elantp_i2c_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE(device); FuElantpFirmware *firmware_elantp = FU_ELANTP_FIRMWARE(firmware); guint16 checksum = 0; guint16 checksum_device = 0; guint16 iap_addr; guint8 csum_buf[2] = {0x0}; g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) fw2 = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, NULL); /* simple image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* detach */ if (!fu_elantp_i2c_device_detach(device, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* write each block */ iap_addr = fu_elantp_firmware_get_iap_addr(firmware_elantp); fw2 = fu_bytes_new_offset(fw, iap_addr, g_bytes_get_size(fw) - iap_addr, error); if (fw2 == NULL) return FALSE; chunks = fu_chunk_array_new_from_bytes(fw2, 0x0, self->fw_page_size); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); guint16 csum_tmp = fu_sum16w(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), G_LITTLE_ENDIAN); gsize blksz = self->fw_page_size + 4; g_autofree guint8 *blk = g_malloc0(blksz); /* write block */ blk[0] = ETP_I2C_IAP_REG_L; blk[1] = ETP_I2C_IAP_REG_H; if (!fu_memcpy_safe(blk, blksz, 0x2, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; fu_memwrite_uint16(blk + fu_chunk_get_data_sz(chk) + 2, csum_tmp, G_LITTLE_ENDIAN); if (!fu_elantp_i2c_device_send_cmd(self, blk, blksz, NULL, 0, error)) return FALSE; fu_device_sleep(device, self->fw_page_size == 512 ? ELANTP_DELAY_WRITE_BLOCK_512 : ELANTP_DELAY_WRITE_BLOCK); if (!fu_elantp_i2c_device_ensure_iap_ctrl(self, error)) return FALSE; if (self->iap_ctrl & (ETP_FW_IAP_PAGE_ERR | ETP_FW_IAP_INTF_ERR)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "bootloader reports failed write: 0x%x", self->iap_ctrl); return FALSE; } /* update progress */ checksum += csum_tmp; fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* verify the written checksum */ if (!fu_elantp_i2c_device_read_cmd(self, ETP_CMD_I2C_IAP_CHECKSUM, csum_buf, sizeof(csum_buf), error)) return FALSE; if (!fu_memread_uint16_safe(csum_buf, sizeof(csum_buf), 0x0, &checksum_device, G_LITTLE_ENDIAN, error)) return FALSE; if (checksum != checksum_device) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "checksum failed 0x%04x != 0x%04x", checksum, checksum_device); return FALSE; } fu_progress_step_done(progress); /* wait for a reset */ fu_device_sleep_full(device, ELANTP_DELAY_COMPLETE, fu_progress_get_child(progress)); /* ms */ fu_progress_step_done(progress); return TRUE; } static gboolean fu_elantp_i2c_device_detach(FuDevice *device, FuProgress *progress, GError **error) { guint16 iap_ver; guint16 ic_type; guint8 buf[2] = {0x0}; guint16 tmp; FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE(device); /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_info("in bootloader mode, reset IC"); if (!fu_elantp_i2c_device_write_cmd(self, ETP_CMD_I2C_IAP_RESET, ETP_I2C_IAP_RESET, error)) return FALSE; fu_device_sleep(device, ELANTP_DELAY_RESET); } /* get OSM version */ if (!fu_elantp_i2c_device_read_cmd(self, ETP_CMD_I2C_OSM_VERSION, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read OSM version: "); return FALSE; } if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x0, &tmp, G_LITTLE_ENDIAN, error)) return FALSE; if (tmp == ETP_CMD_I2C_OSM_VERSION || tmp == 0xFFFF) { if (!fu_elantp_i2c_device_read_cmd(self, ETP_CMD_I2C_IAP_ICBODY, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read IC body: "); return FALSE; } if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x0, &ic_type, G_LITTLE_ENDIAN, error)) return FALSE; } else { ic_type = (tmp >> 8) & 0xFF; } /* get IAP firmware version */ if (!fu_elantp_i2c_device_read_cmd(self, self->pattern == 0 ? ETP_CMD_I2C_IAP_VERSION : ETP_CMD_I2C_IAP_VERSION_2, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read bootloader version: "); return FALSE; } if (self->pattern >= 1) { iap_ver = buf[1]; } else { if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x0, &iap_ver, G_LITTLE_ENDIAN, error)) return FALSE; } /* set the page size */ self->fw_page_size = 64; if (ic_type >= 0x10) { if (iap_ver >= 1) { if (iap_ver >= 2 && (ic_type == 0x14 || ic_type == 0x15)) { self->fw_page_size = 512; } else { self->fw_page_size = 128; } /* set the IAP type, presumably some kind of ABI */ if (!fu_elantp_i2c_device_write_cmd(self, ETP_CMD_I2C_IAP_TYPE, self->fw_page_size / 2, error)) return FALSE; if (!fu_elantp_i2c_device_read_cmd(self, ETP_CMD_I2C_IAP_TYPE, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read IAP type: "); return FALSE; } if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x0, &self->iap_type, G_LITTLE_ENDIAN, error)) return FALSE; if (self->iap_type != self->fw_page_size / 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to set IAP type"); return FALSE; } } } if (!fu_elantp_i2c_device_write_cmd(self, ETP_CMD_I2C_IAP, self->iap_password, error)) return FALSE; fu_device_sleep(device, ELANTP_DELAY_UNLOCK); if (!fu_elantp_i2c_device_ensure_iap_ctrl(self, error)) return FALSE; if ((self->iap_ctrl & ETP_FW_IAP_CHECK_PW) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "unexpected bootloader password"); return FALSE; } /* success */ return TRUE; } static gboolean fu_elantp_i2c_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE(device); /* sanity check */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } /* reset back to runtime */ if (!fu_elantp_i2c_device_write_cmd(self, ETP_CMD_I2C_IAP_RESET, ETP_I2C_IAP_RESET, error)) return FALSE; fu_device_sleep(device, ELANTP_DELAY_RESET); if (!fu_elantp_i2c_device_write_cmd(self, ETP_CMD_I2C_IAP_RESET, ETP_I2C_ENABLE_REPORT, error)) { g_prefix_error(error, "cannot enable TP report: "); return FALSE; } if (!fu_elantp_i2c_device_ensure_iap_ctrl(self, error)) return FALSE; if (fu_device_has_private_flag(device, FU_ELANTP_I2C_DEVICE_ABSOLUTE)) { g_autoptr(GError) error_local = NULL; if (!fu_elantp_i2c_device_write_cmd(self, 0x0300, 0x001, error)) { g_prefix_error(error, "cannot switch to TP ABS mode: "); return FALSE; } if (!fu_elantp_i2c_device_rebind_driver(self, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED)) { g_debug("%s", error_local->message); } else { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } } else { if (!fu_elantp_i2c_device_write_cmd(self, 0x0306, 0x003, error)) { g_prefix_error(error, "cannot switch to TP PTP mode: "); return FALSE; } } /* success */ return TRUE; } static gboolean fu_elantp_i2c_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE(device); guint64 tmp = 0; if (g_strcmp0(key, "ElantpIcPageCount") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->ic_page_count = (guint16)tmp; return TRUE; } if (g_strcmp0(key, "ElantpIapPassword") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->iap_password = (guint16)tmp; return TRUE; } if (g_strcmp0(key, "ElantpI2cTargetAddress") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->i2c_addr = (guint16)tmp; return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_elantp_i2c_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_elantp_i2c_device_init(FuElantpI2cDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_name(FU_DEVICE(self), "Touchpad"); fu_device_add_icon(FU_DEVICE(self), "input-touchpad"); fu_device_add_protocol(FU_DEVICE(self), "tw.com.emc.elantp"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_HEX); fu_device_set_vendor(FU_DEVICE(self), "ELAN Microelectronics"); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE); fu_device_register_private_flag(FU_DEVICE(self), FU_ELANTP_I2C_DEVICE_ABSOLUTE, "elantp-absolute"); } static void fu_elantp_i2c_device_finalize(GObject *object) { FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE(object); g_free(self->bind_path); g_free(self->bind_id); G_OBJECT_CLASS(fu_elantp_i2c_device_parent_class)->finalize(object); } static void fu_elantp_i2c_device_class_init(FuElantpI2cDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_elantp_i2c_device_finalize; klass_device->to_string = fu_elantp_i2c_device_to_string; klass_device->attach = fu_elantp_i2c_device_attach; klass_device->set_quirk_kv = fu_elantp_i2c_device_set_quirk_kv; klass_device->setup = fu_elantp_i2c_device_setup; klass_device->reload = fu_elantp_i2c_device_setup; klass_device->write_firmware = fu_elantp_i2c_device_write_firmware; klass_device->prepare_firmware = fu_elantp_i2c_device_prepare_firmware; klass_device->probe = fu_elantp_i2c_device_probe; klass_device->open = fu_elantp_i2c_device_open; klass_device->set_progress = fu_elantp_i2c_device_set_progress; } fwupd-1.9.16/plugins/elantp/fu-elantp-i2c-device.h000066400000000000000000000005521460375044200216640ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_ELANTP_I2C_DEVICE_ABSOLUTE (1 << 0) #define FU_TYPE_ELANTP_I2C_DEVICE (fu_elantp_i2c_device_get_type()) G_DECLARE_FINAL_TYPE(FuElantpI2cDevice, fu_elantp_i2c_device, FU, ELANTP_I2C_DEVICE, FuI2cDevice) fwupd-1.9.16/plugins/elantp/fu-elantp-plugin.c000066400000000000000000000034041460375044200212420ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-elantp-firmware.h" #include "fu-elantp-hid-device.h" #include "fu-elantp-i2c-device.h" #include "fu-elantp-plugin.h" struct _FuElantpPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuElantpPlugin, fu_elantp_plugin, FU_TYPE_PLUGIN) static gboolean fu_elantp_plugin_device_created(FuPlugin *plugin, FuDevice *dev, GError **error) { if (fu_device_get_specialized_gtype(dev) == FU_TYPE_ELANTP_I2C_DEVICE && !fu_context_has_hwid_flag(fu_plugin_get_context(plugin), "elantp-recovery") && !fu_device_has_private_flag(dev, FU_ELANTP_I2C_DEVICE_ABSOLUTE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not required"); return FALSE; } return TRUE; } static void fu_elantp_plugin_init(FuElantpPlugin *self) { } static void fu_elantp_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "ElantpI2cTargetAddress"); fu_context_add_quirk_key(ctx, "ElantpIapPassword"); fu_context_add_quirk_key(ctx, "ElantpIcPageCount"); fu_plugin_add_udev_subsystem(plugin, "i2c"); fu_plugin_add_udev_subsystem(plugin, "i2c-dev"); fu_plugin_add_udev_subsystem(plugin, "hidraw"); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_ELANTP_FIRMWARE); fu_plugin_add_device_gtype(plugin, FU_TYPE_ELANTP_I2C_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_ELANTP_HID_DEVICE); } static void fu_elantp_plugin_class_init(FuElantpPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_elantp_plugin_constructed; plugin_class->device_created = fu_elantp_plugin_device_created; } fwupd-1.9.16/plugins/elantp/fu-elantp-plugin.h000066400000000000000000000003501460375044200212440ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuElantpPlugin, fu_elantp_plugin, FU, ELANTP_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/elantp/fu-self-test.c000066400000000000000000000033511460375044200203720ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-elantp-firmware.h" static void fu_elantp_firmware_xml_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *csum1 = NULL; g_autofree gchar *csum2 = NULL; g_autofree gchar *xml_out = NULL; g_autofree gchar *xml_src = NULL; g_autoptr(FuFirmware) firmware1 = fu_elantp_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_elantp_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "elantp.builder.xml", NULL); ret = g_file_get_contents(filename, &xml_src, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_build_from_xml(firmware1, xml_src, &error); g_assert_no_error(error); g_assert_true(ret); csum1 = fu_firmware_get_checksum(firmware1, G_CHECKSUM_SHA1, &error); g_assert_no_error(error); g_assert_cmpstr(csum1, ==, "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"); /* ensure we can round-trip */ xml_out = fu_firmware_export_to_xml(firmware1, FU_FIRMWARE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); ret = fu_firmware_build_from_xml(firmware2, xml_out, &error); g_assert_no_error(error); g_assert_true(ret); csum2 = fu_firmware_get_checksum(firmware2, G_CHECKSUM_SHA1, &error); g_assert_cmpstr(csum1, ==, csum2); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func("/elantp/firmware{xml}", fu_elantp_firmware_xml_func); return g_test_run(); } fwupd-1.9.16/plugins/elantp/meson.build000066400000000000000000000025271460375044200200560ustar00rootroot00000000000000if gudev.found() cargs = ['-DG_LOG_DOMAIN="FuPluginElantp"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('elantp.quirk') plugin_builtin_elantp = static_library('fu_plugin_elantp', sources: [ 'fu-elantp-plugin.c', 'fu-elantp-firmware.c', # fuzzing 'fu-elantp-haptic-firmware.c', 'fu-elantp-hid-device.c', 'fu-elantp-i2c-device.c', 'fu-elantp-hid-haptic-device.c', ], include_directories: plugin_incdirs, c_args: [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], link_with: plugin_libs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_elantp if get_option('tests') install_data(['tests/elantp.builder.xml'], install_dir: join_paths(installed_test_datadir, 'tests')) env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'elantp-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_elantp, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('elantp-self-test', e, env: env) endif endif fwupd-1.9.16/plugins/elantp/tests/000077500000000000000000000000001460375044200170505ustar00rootroot00000000000000fwupd-1.9.16/plugins/elantp/tests/elantp.bin000066400000000000000000000070251460375044200210310ustar00rootroot00000000000000hello worldU3fwupd-1.9.16/plugins/elantp/tests/elantp.builder.xml000066400000000000000000000002321460375044200224770ustar00rootroot00000000000000 0xe00 0x2 aGVsbG8gd29ybGQ= fwupd-1.9.16/plugins/emmc/000077500000000000000000000000001460375044200153445ustar00rootroot00000000000000fwupd-1.9.16/plugins/emmc/README.md000066400000000000000000000023241460375044200166240ustar00rootroot00000000000000--- title: Plugin: eMMC --- ## Introduction This plugin reads the sysfs attributes corresponding to eMMC devices. It uses the kernel MMC API for flashing devices. ## Protocol eMMC devices support the `org.jedec.mmc` protocol. ## GUID Generation These devices use the following instance values: * `EMMC\NAME_%name%` * `EMMC\NAME_%name%&REV_%rev%` * `EMMC\MAN_%manfid%&OEM_%oemid%` (only-quirk) * `EMMC\MAN_%manfid%&OEM_%oemid%&NAME_%name%` * `EMMC\MAN_%manfid%&NAME_%name%&REV_%rev%` * `EMMC\MAN_%manfid%&OEM_%oemid%&NAME_%name%&REV_%rev%` One deprecated instance ID is also added; new firmware should not use this. * `EMMC\%manfid%&%oemid%&%name%` ## Update Behavior The firmware is deployed when the device is in normal runtime mode, but it is only activated when the device is rebooted. ## Quirk Use This plugin uses the following plugin-specific quirks: ### EmmcBlockSize The block size used for Emmc writes Since: 1.9.7 ## Vendor ID Security The vendor ID is set from the EMMC vendor, for example set to `EMMC:{$manfid}` ## External Interface Access This plugin requires ioctl `MMC_IOC_CMD` and `MMC_IOC_MULTI_CMD` access. ## Version Considerations This plugin has been available since fwupd version `1.3.3`. fwupd-1.9.16/plugins/emmc/emmc.quirk000066400000000000000000000002711460375044200173420ustar00rootroot00000000000000#Western Digital [EMMC\NAME_DI4064] EmmcBlockSize = 0x1000 [EMMC\NAME_DI4128] EmmcBlockSize = 0x1000 [EMMC\NAME_DJ4008] EmmcBlockSize = 0x1000 [EMMC\NAME_DJ4016] EmmcBlockSize = 0x1000 fwupd-1.9.16/plugins/emmc/fu-emmc-device.c000066400000000000000000000444111460375044200203020ustar00rootroot00000000000000/* * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-emmc-device.h" /* From kernel linux/major.h */ #define MMC_BLOCK_MAJOR 179 /* From kernel linux/mmc/mmc.h */ #define MMC_SWITCH 6 /* ac [31:0] See below R1b */ #define MMC_SEND_EXT_CSD 8 /* adtc R1 */ #define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */ #define MMC_WRITE_BLOCK 24 /* adtc [31:0] data addr R1 */ #define MMC_SET_BLOCK_COUNT 23 /* adtc [31:0] data addr R1 */ #define MMC_WRITE_MULTIPLE_BLOCK 25 /* adtc [31:0] data addr R1 */ /* From kernel linux/mmc/core.h */ #define MMC_RSP_PRESENT (1 << 0) #define MMC_RSP_CRC (1 << 2) /* expect valid crc */ #define MMC_RSP_BUSY (1 << 3) /* card may send busy */ #define MMC_RSP_OPCODE (1 << 4) /* response contains opcode */ #define MMC_RSP_SPI_S1 (1 << 7) /* one status byte */ #define MMC_CMD_AC (0 << 5) #define MMC_CMD_ADTC (1 << 5) #define MMC_RSP_SPI_BUSY (1 << 10) /* card may send busy */ #define MMC_RSP_SPI_R1 (MMC_RSP_SPI_S1) #define MMC_RSP_SPI_R1B (MMC_RSP_SPI_S1 | MMC_RSP_SPI_BUSY) #define MMC_RSP_R1 (MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE) #define MMC_RSP_R1B (MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE | MMC_RSP_BUSY) /* EXT_CSD fields */ #define EXT_CSD_SUPPORTED_MODES 493 /* RO */ #define EXT_CSD_FFU_FEATURES 492 /* RO */ #define EXT_CSD_FFU_ARG_3 490 /* RO */ #define EXT_CSD_FFU_ARG_2 489 /* RO */ #define EXT_CSD_FFU_ARG_1 488 /* RO */ #define EXT_CSD_FFU_ARG_0 487 /* RO */ #define EXT_CSD_NUM_OF_FW_SEC_PROG_3 305 /* RO */ #define EXT_CSD_NUM_OF_FW_SEC_PROG_2 304 /* RO */ #define EXT_CSD_NUM_OF_FW_SEC_PROG_1 303 /* RO */ #define EXT_CSD_NUM_OF_FW_SEC_PROG_0 302 /* RO */ #define EXT_CSD_REV 192 #define EXT_CSD_FW_CONFIG 169 /* R/W */ #define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */ #define EXT_CSD_MODE_CONFIG 30 #define EXT_CSD_MODE_OPERATION_CODES 29 /* W */ #define EXT_CSD_FFU_STATUS 26 /* R */ #define EXT_CSD_REV_V5_1 8 #define EXT_CSD_REV_V5_0 7 /* EXT_CSD field definitions */ #define EXT_CSD_NORMAL_MODE (0x00) #define EXT_CSD_FFU_MODE (0x01) #define EXT_CSD_FFU_INSTALL (0x01) #define EXT_CSD_FFU (1 << 0) #define EXT_CSD_UPDATE_DISABLE (1 << 0) #define EXT_CSD_CMD_SET_NORMAL (1 << 0) #define FU_EMMC_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ struct _FuEmmcDevice { FuUdevDevice parent_instance; guint32 sect_size; guint32 write_block_size; }; G_DEFINE_TYPE(FuEmmcDevice, fu_emmc_device, FU_TYPE_UDEV_DEVICE) static void fu_emmc_device_to_string(FuDevice *device, guint idt, GString *str) { FuEmmcDevice *self = FU_EMMC_DEVICE(device); FU_DEVICE_CLASS(fu_emmc_device_parent_class)->to_string(device, idt, str); fu_string_append_ku(str, idt, "SectorSize", self->sect_size); } static const gchar * fu_emmc_device_get_manufacturer(guint64 mmc_id) { switch (mmc_id) { case 0x00: case 0x44: return "SanDisk"; case 0x02: return "Kingston/Sandisk"; case 0x03: case 0x11: return "Toshiba"; case 0x13: return "Micron"; case 0x15: return "Samsung/Sandisk/LG"; case 0x37: return "Kingmax"; case 0x70: case 0x2c: return "Kingston"; default: return NULL; } return NULL; } static gboolean fu_emmc_device_get_sysattr_guint64(GUdevDevice *device, const gchar *name, guint64 *val_out, GError **error) { const gchar *sysfs; sysfs = g_udev_device_get_sysfs_attr(device, name); if (sysfs == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed get %s", name); return FALSE; } *val_out = g_ascii_strtoull(sysfs, NULL, 16); return TRUE; } static gboolean fu_emmc_device_probe(FuDevice *device, GError **error) { GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); guint64 flag; guint64 oemid = 0; guint64 manfid = 0; const gchar *tmp; g_autoptr(GUdevDevice) udev_parent = NULL; g_autofree gchar *man_oem_name = NULL; g_autofree gchar *vendor_id = NULL; g_autoptr(GRegex) dev_regex = NULL; udev_parent = g_udev_device_get_parent_with_subsystem(udev_device, "mmc", NULL); if (udev_parent == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no MMC parent"); return FALSE; } /* look for only the parent node */ if (g_strcmp0(g_udev_device_get_devtype(udev_device), "disk") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct devtype=%s, expected disk", g_udev_device_get_devtype(udev_device)); return FALSE; } /* ignore *rpmb and *boot* mmc block devices */ dev_regex = g_regex_new("mmcblk\\d$", 0, 0, NULL); tmp = g_udev_device_get_name(udev_device); if (tmp == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device has no name"); return FALSE; } if (!g_regex_match(dev_regex, tmp, 0, NULL)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not raw mmc block device, devname=%s", g_udev_device_get_name(udev_device)); return FALSE; } /* doesn't support FFU */ if (!fu_emmc_device_get_sysattr_guint64(udev_parent, "ffu_capable", &flag, error)) return FALSE; if (flag == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s does not support field firmware updates", fu_device_get_name(device)); return FALSE; } /* name */ tmp = g_udev_device_get_sysfs_attr(udev_parent, "name"); if (tmp == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s does not have 'name' sysattr", fu_device_get_name(device)); return FALSE; } fu_device_add_instance_strsafe(device, "NAME", tmp); fu_device_build_instance_id(device, NULL, "EMMC", "NAME", NULL); fu_device_set_name(device, tmp); /* firmware version */ tmp = g_udev_device_get_sysfs_attr(udev_parent, "fwrev"); if (tmp != NULL) { fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_NUMBER); fu_device_set_version(device, tmp); } fu_device_add_instance_strsafe(device, "REV", tmp); if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV)) fu_device_build_instance_id(device, NULL, "EMMC", "NAME", "REV", NULL); /* manfid + oemid, manfid + oemid + name */ if (!fu_emmc_device_get_sysattr_guint64(udev_parent, "manfid", &manfid, error)) return FALSE; if (!fu_emmc_device_get_sysattr_guint64(udev_parent, "oemid", &oemid, error)) return FALSE; fu_device_add_instance_u16(device, "MAN", manfid); fu_device_add_instance_u16(device, "OEM", oemid); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "EMMC", "MAN", "OEM", NULL); fu_device_build_instance_id(device, NULL, "EMMC", "MAN", "OEM", "NAME", NULL); fu_device_build_instance_id(device, NULL, "EMMC", "MAN", "NAME", "REV", NULL); fu_device_build_instance_id(device, NULL, "EMMC", "MAN", "OEM", "NAME", "REV", NULL); /* this is a (invalid!) instance ID added for legacy compatibility */ man_oem_name = g_strdup_printf("EMMC\\%04" G_GUINT64_FORMAT "&%04" G_GUINT64_FORMAT "&%s", manfid, oemid, fu_device_get_name(device)); fu_device_add_instance_id(device, man_oem_name); /* set the vendor */ tmp = g_udev_device_get_sysfs_attr(udev_parent, "manfid"); vendor_id = g_strdup_printf("EMMC:%s", tmp); fu_device_add_vendor_id(device, vendor_id); fu_device_set_vendor(device, fu_emmc_device_get_manufacturer(manfid)); /* set the physical ID */ if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "mmc", error)) return FALSE; /* internal */ if (!fu_emmc_device_get_sysattr_guint64(udev_device, "removable", &flag, error)) return FALSE; if (flag == 0) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL); return TRUE; } static gboolean fu_emmc_read_extcsd(FuEmmcDevice *self, guint8 *ext_csd, gsize ext_csd_sz, GError **error) { struct mmc_ioc_cmd idata = { .write_flag = 0, .opcode = MMC_SEND_EXT_CSD, .arg = 0, .flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC, .blksz = 512, .blocks = 1, }; mmc_ioc_cmd_set_data(idata, ext_csd); return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), MMC_IOC_CMD, (guint8 *)&idata, NULL, FU_EMMC_DEVICE_IOCTL_TIMEOUT, error); } static gboolean fu_emmc_validate_extcsd(FuDevice *device, GError **error) { FuEmmcDevice *self = FU_EMMC_DEVICE(device); guint8 ext_csd[512] = {0x0}; if (!fu_emmc_read_extcsd(FU_EMMC_DEVICE(device), ext_csd, sizeof(ext_csd), error)) return FALSE; if (ext_csd[EXT_CSD_REV] < EXT_CSD_REV_V5_0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "FFU is only available on devices >= " "MMC 5.0, not supported in %s", fu_device_get_name(device)); return FALSE; } if ((ext_csd[EXT_CSD_SUPPORTED_MODES] & EXT_CSD_FFU) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "FFU is not supported in %s", fu_device_get_name(device)); return FALSE; } if (ext_csd[EXT_CSD_FW_CONFIG] & EXT_CSD_UPDATE_DISABLE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware update was disabled in %s", fu_device_get_name(device)); return FALSE; } self->sect_size = (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] == 0) ? 512 : 4096; return TRUE; } static gboolean fu_emmc_device_setup(FuDevice *device, GError **error) { g_autoptr(GError) error_validate = NULL; if (!fu_emmc_validate_extcsd(device, &error_validate)) g_debug("%s", error_validate->message); else fu_device_add_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_UPDATABLE); return TRUE; } static FuFirmware * fu_emmc_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuEmmcDevice *self = FU_EMMC_DEVICE(device); gsize fw_size = g_bytes_get_size(fw); /* check alignment */ if ((fw_size % self->sect_size) > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware data size (%" G_GSIZE_FORMAT ") is not aligned", fw_size); return NULL; } return fu_firmware_new_from_bytes(fw); } static gboolean fu_emmc_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuEmmcDevice *self = FU_EMMC_DEVICE(device); gsize fw_size = 0; guint32 arg; guint32 sect_done = 0; guint32 sector_size; gboolean check_sect_done = FALSE; guint8 ext_csd[512]; guint failure_cnt = 0; g_autofree struct mmc_ioc_multi_cmd *multi_cmd = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "ffu"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 45, NULL); if (!fu_emmc_read_extcsd(FU_EMMC_DEVICE(device), ext_csd, sizeof(ext_csd), error)) return FALSE; fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; fw_size = g_bytes_get_size(fw); sector_size = self->write_block_size ?: self->sect_size; /* mode operation codes are supported */ check_sect_done = (ext_csd[EXT_CSD_FFU_FEATURES] & 1) > 0; /* set CMD ARG */ arg = ext_csd[EXT_CSD_FFU_ARG_0] | ext_csd[EXT_CSD_FFU_ARG_1] << 8 | ext_csd[EXT_CSD_FFU_ARG_2] << 16 | ext_csd[EXT_CSD_FFU_ARG_3] << 24; /* prepare multi_cmd to be sent */ multi_cmd = g_malloc0(sizeof(struct mmc_ioc_multi_cmd) + 4 * sizeof(struct mmc_ioc_cmd)); multi_cmd->num_of_cmds = 4; /* put device into ffu mode */ multi_cmd->cmds[0].opcode = MMC_SWITCH; multi_cmd->cmds[0].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | (EXT_CSD_MODE_CONFIG << 16) | (EXT_CSD_FFU_MODE << 8) | EXT_CSD_CMD_SET_NORMAL; multi_cmd->cmds[0].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; multi_cmd->cmds[0].write_flag = 1; /* send block count */ multi_cmd->cmds[1].opcode = MMC_SET_BLOCK_COUNT; multi_cmd->cmds[1].arg = sector_size / 512; multi_cmd->cmds[1].flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; /* send image chunk */ multi_cmd->cmds[2].opcode = MMC_WRITE_MULTIPLE_BLOCK; multi_cmd->cmds[2].blksz = 512; multi_cmd->cmds[2].blocks = sector_size / 512; multi_cmd->cmds[2].arg = arg; multi_cmd->cmds[2].flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; multi_cmd->cmds[2].write_flag = 1; /* return device into normal mode */ multi_cmd->cmds[3].opcode = MMC_SWITCH; multi_cmd->cmds[3].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | (EXT_CSD_MODE_CONFIG << 16) | (EXT_CSD_NORMAL_MODE << 8) | EXT_CSD_CMD_SET_NORMAL; multi_cmd->cmds[3].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; multi_cmd->cmds[3].write_flag = 1; fu_progress_step_done(progress); /* build packets */ chunks = fu_chunk_array_new_from_bytes(fw, 0x00, sector_size); while (failure_cnt < 3) { for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); mmc_ioc_cmd_set_data(multi_cmd->cmds[2], fu_chunk_get_data(chk)); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), MMC_IOC_MULTI_CMD, (guint8 *)multi_cmd, NULL, FU_EMMC_DEVICE_IOCTL_TIMEOUT, error)) { g_autoptr(GError) error_local = NULL; g_prefix_error(error, "multi-cmd failed: "); /* multi-cmd ioctl failed before exiting from ffu mode */ if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), MMC_IOC_CMD, (guint8 *)&multi_cmd->cmds[3], NULL, FU_EMMC_DEVICE_IOCTL_TIMEOUT, &error_local)) { g_prefix_error(error, "%s: ", error_local->message); } return FALSE; } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } if (!check_sect_done) break; if (!fu_emmc_read_extcsd(self, ext_csd, sizeof(ext_csd), error)) return FALSE; sect_done = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_0] | ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_1] << 8 | ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_2] << 16 | ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG_3] << 24; if (sect_done != 0) break; failure_cnt++; g_debug("programming failed: retrying (%u)", failure_cnt); fu_progress_step_done(progress); } fu_progress_step_done(progress); /* sanity check */ if (check_sect_done) { gsize total_done = (gsize)sect_done * (gsize)self->sect_size; if (total_done != fw_size) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "firmware size and number of sectors written " "mismatch (%" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT "):", total_done, fw_size); return FALSE; } } /* check mode operation for ffu install*/ if (!check_sect_done) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); } else { /* re-enter ffu mode and install the firmware */ multi_cmd->num_of_cmds = 2; /* set ext_csd to install mode */ multi_cmd->cmds[1].opcode = MMC_SWITCH; multi_cmd->cmds[1].blksz = 0; multi_cmd->cmds[1].blocks = 0; multi_cmd->cmds[1].arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | (EXT_CSD_MODE_OPERATION_CODES << 16) | (EXT_CSD_FFU_INSTALL << 8) | EXT_CSD_CMD_SET_NORMAL; multi_cmd->cmds[1].flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; multi_cmd->cmds[1].write_flag = 1; /* send ioctl with multi-cmd */ if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), MMC_IOC_MULTI_CMD, (guint8 *)multi_cmd, NULL, FU_EMMC_DEVICE_IOCTL_TIMEOUT, error)) { g_autoptr(GError) error_local = NULL; /* In case multi-cmd ioctl failed before exiting from ffu mode */ g_prefix_error(error, "multi-cmd failed setting install mode: "); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), MMC_IOC_CMD, (guint8 *)&multi_cmd->cmds[2], NULL, FU_EMMC_DEVICE_IOCTL_TIMEOUT, &error_local)) { g_prefix_error(error, "%s: ", error_local->message); } return FALSE; } /* return status */ if (!fu_emmc_read_extcsd(self, ext_csd, sizeof(ext_csd), error)) return FALSE; if (ext_csd[EXT_CSD_FFU_STATUS] != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "FFU install failed: %d", ext_csd[EXT_CSD_FFU_STATUS]); return FALSE; } } fu_progress_step_done(progress); return TRUE; } static gboolean fu_emmc_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuEmmcDevice *self = FU_EMMC_DEVICE(device); if (g_strcmp0(key, "EmmcBlockSize") == 0) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->write_block_size = tmp; return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_emmc_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_emmc_device_init(FuEmmcDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "org.jedec.mmc"); fu_device_add_icon(FU_DEVICE(self), "media-memory"); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED); } static void fu_emmc_device_finalize(GObject *object) { G_OBJECT_CLASS(fu_emmc_device_parent_class)->finalize(object); } static void fu_emmc_device_class_init(FuEmmcDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_emmc_device_finalize; klass_device->set_quirk_kv = fu_emmc_device_set_quirk_kv; klass_device->setup = fu_emmc_device_setup; klass_device->to_string = fu_emmc_device_to_string; klass_device->prepare_firmware = fu_emmc_device_prepare_firmware; klass_device->probe = fu_emmc_device_probe; klass_device->write_firmware = fu_emmc_device_write_firmware; klass_device->set_progress = fu_emmc_device_set_progress; } fwupd-1.9.16/plugins/emmc/fu-emmc-device.h000066400000000000000000000004501460375044200203020ustar00rootroot00000000000000/* * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_EMMC_DEVICE (fu_emmc_device_get_type()) G_DECLARE_FINAL_TYPE(FuEmmcDevice, fu_emmc_device, FU, EMMC_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/emmc/fu-emmc-plugin.c000066400000000000000000000015261460375044200203410ustar00rootroot00000000000000/* * Copyright (C) 2019 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-emmc-device.h" #include "fu-emmc-plugin.h" struct _FuEmmcPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuEmmcPlugin, fu_emmc_plugin, FU_TYPE_PLUGIN) static void fu_emmc_plugin_init(FuEmmcPlugin *self) { } static void fu_emmc_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "EmmcBlockSize"); fu_plugin_add_device_udev_subsystem(plugin, "block"); fu_plugin_add_device_gtype(plugin, FU_TYPE_EMMC_DEVICE); } static void fu_emmc_plugin_class_init(FuEmmcPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_emmc_plugin_constructed; } fwupd-1.9.16/plugins/emmc/fu-emmc-plugin.h000066400000000000000000000003421460375044200203410ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuEmmcPlugin, fu_emmc_plugin, FU, EMMC_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/emmc/meson.build000066400000000000000000000007571460375044200175170ustar00rootroot00000000000000if get_option('plugin_emmc').require(gudev.found(), error_message: 'gudev is needed for plugin_emmc').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginEmmc"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('emmc.quirk') plugin_builtins += static_library('fu_plugin_emmc', sources: [ 'fu-emmc-plugin.c', 'fu-emmc-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/ep963x/000077500000000000000000000000001460375044200154615ustar00rootroot00000000000000fwupd-1.9.16/plugins/ep963x/README.md000066400000000000000000000016311460375044200167410ustar00rootroot00000000000000--- title: Plugin: EP963x --- ## Introduction The EP963x is a generic MCU used in many different products. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `tw.com.exploretech.ep963x` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_17EF&PID_7226` ## Update Behavior The device usually presents in runtime mode, but on detach re-enumerates with the same USB PID in an unlocked mode. On attach the device again re-enumerates back to the runtime locked mode. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x17EF` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.4.0`. fwupd-1.9.16/plugins/ep963x/ep963x.quirk000066400000000000000000000000501460375044200175670ustar00rootroot00000000000000[USB\VID_17EF&PID_7226] Plugin = ep963x fwupd-1.9.16/plugins/ep963x/fu-ep963x-common.h000066400000000000000000000034531460375044200205730ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_EP963_FIRMWARE_SIZE 0x1f000 #define FU_EP963_TRANSFER_BLOCK_SIZE 0x200 /* 512 */ #define FU_EP963_TRANSFER_CHUNK_SIZE 0x04 #define FU_EP963_FEATURE_ID1_SIZE 0x08 #define FU_EP963_USB_CONTROL_ID 0x01 #define FU_EP963_ICP_ENTER 0x40 #define FU_EP963_ICP_EXIT 0x82 #define FU_EP963_ICP_BANK 0x83 #define FU_EP963_ICP_ADDRESS 0x84 #define FU_EP963_ICP_READBLOCK 0x85 #define FU_EP963_ICP_WRITEBLOCK 0x86 #define FU_EP963_ICP_MCUID 0x87 #define FU_EP963_ICP_DONE 0x5A #define FU_EP963_OPCODE_SMBUS_READ 0x01 #define FU_EP963_OPCODE_ERASE_SPI 0x02 #define FU_EP963_OPCODE_RESET_BLOCK_INDEX 0x03 #define FU_EP963_OPCODE_WRITE_BLOCK_DATA 0x04 #define FU_EP963_OPCODE_PROGRAM_SPI_BLOCK 0x05 #define FU_EP963_OPCODE_PROGRAM_SPI_FINISH 0x06 #define FU_EP963_OPCODE_GET_SPI_CHECKSUM 0x07 #define FU_EP963_OPCODE_PROGRAM_EP_FLASH 0x08 #define FU_EP963_OPCODE_GET_EP_CHECKSUM 0x09 #define FU_EP963_OPCODE_START_THROW_PAGE 0x0B #define FU_EP963_OPCODE_GET_EP_SITE_TYPE 0x0C #define FU_EP963_OPCODE_COMMAND_VERSION 0x10 #define FU_EP963_OPCODE_COMMAND_STATUS 0x20 #define FU_EP963_OPCODE_SUBMCU_ENTER_ICP 0x30 #define FU_EP963_OPCODE_SUBMCU_RESET_BLOCK_IDX 0x31 #define FU_EP963_OPCODE_SUBMCU_WRITE_BLOCK_DATA 0x32 #define FU_EP963_OPCODE_SUBMCU_PROGRAM_BLOCK 0x33 #define FU_EP963_OPCODE_SUBMCU_PROGRAM_FINISHED 0x34 #define FU_EP963_UF_CMD_VERSION 0x00 #define FU_EP963_UF_CMD_ENTERISP 0x01 #define FU_EP963_UF_CMD_PROGRAM 0x02 #define FU_EP963_UF_CMD_READ 0x03 #define FU_EP963_UF_CMD_MODE 0x04 /* byte 0x02 */ #define FU_EP963_USB_STATE_READY 0x00 #define FU_EP963_USB_STATE_BUSY 0x01 #define FU_EP963_USB_STATE_FAIL 0x02 #define FU_EP963_USB_STATE_UNKNOWN 0xff fwupd-1.9.16/plugins/ep963x/fu-ep963x-device.c000066400000000000000000000240161460375044200205330ustar00rootroot00000000000000/*# * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ep963x-common.h" #include "fu-ep963x-device.h" #include "fu-ep963x-firmware.h" #include "fu-ep963x-struct.h" struct _FuEp963xDevice { FuHidDevice parent_instance; }; G_DEFINE_TYPE(FuEp963xDevice, fu_ep963x_device, FU_TYPE_HID_DEVICE) #define FU_EP963_DEVICE_TIMEOUT 5000 /* ms */ static gboolean fu_ep963x_device_write(FuEp963xDevice *self, guint8 ctrl_id, guint8 cmd, const guint8 *buf, gsize bufsz, GError **error) { guint8 bufhw[FU_EP963_FEATURE_ID1_SIZE] = { ctrl_id, cmd, 0x0, }; if (buf != NULL) { if (!fu_memcpy_safe(bufhw, sizeof(bufhw), 0x02, /* dst */ buf, bufsz, 0x0, /* src */ bufsz, error)) return FALSE; } if (!fu_hid_device_set_report(FU_HID_DEVICE(self), 0x00, bufhw, sizeof(bufhw), FU_EP963_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; /* wait for hardware */ fu_device_sleep(FU_DEVICE(self), 100); return TRUE; } static gboolean fu_ep963x_device_write_icp(FuEp963xDevice *self, guint8 cmd, const guint8 *buf, gsize bufsz, guint8 *bufout, gsize bufoutsz, GError **error) { /* wait for hardware */ for (guint i = 0; i < 5; i++) { guint8 bufhw[FU_EP963_FEATURE_ID1_SIZE] = { FU_EP963_USB_CONTROL_ID, cmd, }; if (!fu_ep963x_device_write(self, FU_EP963_USB_CONTROL_ID, cmd, buf, bufsz, error)) return FALSE; if (!fu_hid_device_get_report(FU_HID_DEVICE(self), 0x00, bufhw, sizeof(bufhw), FU_EP963_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { return FALSE; } if (bufhw[2] == FU_EP963_USB_STATE_READY) { /* optional data */ if (bufout != NULL) { if (!fu_memcpy_safe(bufout, bufoutsz, 0x0, bufhw, sizeof(bufhw), 0x02, bufoutsz, error)) return FALSE; } return TRUE; } g_debug("SMBUS: %s [0x%x]", fu_ep963x_smbus_error_to_string(bufhw[7]), bufhw[7]); fu_device_sleep(FU_DEVICE(self), 100); } /* failed */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to wait for icp-done"); return FALSE; } static gboolean fu_ep963x_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuEp963xDevice *self = FU_EP963X_DEVICE(device); const guint8 buf[] = {'E', 'P', '9', '6', '3'}; g_autoptr(GError) error_local = NULL; /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in bootloader mode, skipping"); return TRUE; } if (!fu_ep963x_device_write_icp(self, FU_EP963_ICP_ENTER, buf, sizeof(buf), /* in */ NULL, 0x0, /* out */ &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to detach: %s", error_local->message); return FALSE; } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_ep963x_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuEp963xDevice *self = FU_EP963X_DEVICE(device); g_autoptr(GError) error_local = NULL; /* sanity check */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } if (!fu_ep963x_device_write(self, FU_EP963_USB_CONTROL_ID, FU_EP963_OPCODE_SUBMCU_PROGRAM_FINISHED, NULL, 0, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to boot to runtime: %s", error_local->message); return FALSE; } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_ep963x_device_setup(FuDevice *device, GError **error) { FuEp963xDevice *self = FU_EP963X_DEVICE(device); guint8 buf[] = {0x0}; g_autofree gchar *version = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_ep963x_device_parent_class)->setup(device, error)) return FALSE; /* get version */ if (!fu_ep963x_device_write_icp(self, FU_EP963_UF_CMD_VERSION, NULL, 0, /* in */ buf, sizeof(buf), /* out */ error)) { return FALSE; } version = g_strdup_printf("%i", buf[0]); fu_device_set_version(device, version); /* the VID and PID are unchanged between bootloader modes */ if (buf[0] == 0x00) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else { fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } /* success */ return TRUE; } static gboolean fu_ep963x_device_wait_cb(FuDevice *device, gpointer user_data, GError **error) { guint8 bufhw[FU_EP963_FEATURE_ID1_SIZE] = { FU_EP963_USB_CONTROL_ID, FU_EP963_OPCODE_SUBMCU_PROGRAM_BLOCK, 0xFF, }; if (!fu_hid_device_get_report(FU_HID_DEVICE(device), 0x00, bufhw, sizeof(bufhw), FU_EP963_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { return FALSE; } if (bufhw[2] != FU_EP963_USB_STATE_READY) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "hardware is not ready"); return FALSE; } return TRUE; } static gboolean fu_ep963x_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuEp963xDevice *self = FU_EP963X_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(FuChunkArray) blocks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "icp"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 95, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* reset the block index */ if (!fu_ep963x_device_write(self, FU_EP963_USB_CONTROL_ID, FU_EP963_OPCODE_SUBMCU_ENTER_ICP, NULL, 0, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to reset block index: %s", error_local->message); return FALSE; } fu_progress_step_done(progress); /* write each block */ blocks = fu_chunk_array_new_from_bytes(fw, 0x00, FU_EP963_TRANSFER_BLOCK_SIZE); for (guint i = 0; i < fu_chunk_array_length(blocks); i++) { g_autoptr(FuChunk) chk2 = fu_chunk_array_index(blocks, i); guint8 buf[] = {i}; g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GBytes) chk_blob = fu_chunk_get_bytes(chk2); /* set the block index */ if (!fu_ep963x_device_write(self, FU_EP963_USB_CONTROL_ID, FU_EP963_OPCODE_SUBMCU_RESET_BLOCK_IDX, buf, sizeof(buf), &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to reset block index: %s", error_local->message); return FALSE; } /* 4 byte chunks */ chunks = fu_chunk_array_new_from_bytes(chk_blob, fu_chunk_get_address(chk2), FU_EP963_TRANSFER_CHUNK_SIZE); for (guint j = 0; j < fu_chunk_array_length(chunks); j++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, j); g_autoptr(GError) error_loop = NULL; /* copy data and write */ if (!fu_ep963x_device_write(self, FU_EP963_USB_CONTROL_ID, FU_EP963_OPCODE_SUBMCU_WRITE_BLOCK_DATA, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), &error_loop)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to write 0x%x: %s", (guint)fu_chunk_get_address(chk), error_loop->message); return FALSE; } } /* program block */ if (!fu_ep963x_device_write(self, FU_EP963_USB_CONTROL_ID, FU_EP963_OPCODE_SUBMCU_PROGRAM_BLOCK, buf, sizeof(buf), &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to write 0x%x: %s", (guint)fu_chunk_get_address(chk2), error_local->message); return FALSE; } /* wait for program finished */ if (!fu_device_retry(device, fu_ep963x_device_wait_cb, 5, NULL, error)) return FALSE; /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* success! */ return TRUE; } static void fu_ep963x_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_ep963x_device_init(FuEp963xDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_protocol(FU_DEVICE(self), "tw.com.exploretech.ep963x"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_NUMBER); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_set_firmware_size(FU_DEVICE(self), FU_EP963_FIRMWARE_SIZE); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_EP963X_FIRMWARE); fu_device_retry_set_delay(FU_DEVICE(self), 100); } static void fu_ep963x_device_class_init(FuEp963xDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_ep963x_device_write_firmware; klass_device->attach = fu_ep963x_device_attach; klass_device->detach = fu_ep963x_device_detach; klass_device->setup = fu_ep963x_device_setup; klass_device->set_progress = fu_ep963x_device_set_progress; } fwupd-1.9.16/plugins/ep963x/fu-ep963x-device.h000066400000000000000000000004471460375044200205420ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_EP963X_DEVICE (fu_ep963x_device_get_type()) G_DECLARE_FINAL_TYPE(FuEp963xDevice, fu_ep963x_device, FU, EP963X_DEVICE, FuHidDevice) fwupd-1.9.16/plugins/ep963x/fu-ep963x-firmware.c000066400000000000000000000027031460375044200211070ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-ep963x-common.h" #include "fu-ep963x-firmware.h" struct _FuEp963xFirmware { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuEp963xFirmware, fu_ep963x_firmware, FU_TYPE_FIRMWARE) static gboolean fu_ep963x_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { guint8 magic[5] = "EP963"; return fu_memcmp_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset + 16, magic, sizeof(magic), 0x0, sizeof(magic), error); } static gboolean fu_ep963x_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { gsize len = g_bytes_get_size(fw); /* check size */ if (len != FU_EP963_FIRMWARE_SIZE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware size expected 0x%x, got 0x%x", (guint)FU_EP963_FIRMWARE_SIZE, (guint)len); return FALSE; } /* success */ return TRUE; } static void fu_ep963x_firmware_init(FuEp963xFirmware *self) { } static void fu_ep963x_firmware_class_init(FuEp963xFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_ep963x_firmware_check_magic; klass_firmware->parse = fu_ep963x_firmware_parse; } fwupd-1.9.16/plugins/ep963x/fu-ep963x-firmware.h000066400000000000000000000004601460375044200211120ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_EP963X_FIRMWARE (fu_ep963x_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuEp963xFirmware, fu_ep963x_firmware, FU, EP963X_FIRMWARE, FuFirmware) fwupd-1.9.16/plugins/ep963x/fu-ep963x-plugin.c000066400000000000000000000020651460375044200205720ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ep963x-device.h" #include "fu-ep963x-firmware.h" #include "fu-ep963x-plugin.h" struct _FuEp963XPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuEp963XPlugin, fu_ep963x_plugin, FU_TYPE_PLUGIN) static void fu_ep963x_plugin_init(FuEp963XPlugin *self) { } static void fu_ep963x_plugin_object_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_set_name(plugin, "ep963x"); } static void fu_ep963x_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_EP963X_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_EP963X_FIRMWARE); } static void fu_ep963x_plugin_class_init(FuEp963XPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->constructed = fu_ep963x_plugin_object_constructed; plugin_class->constructed = fu_ep963x_plugin_constructed; } fwupd-1.9.16/plugins/ep963x/fu-ep963x-plugin.h000066400000000000000000000003501460375044200205720ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuEp963XPlugin, fu_ep963x_plugin, FU, EP963X_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/ep963x/fu-ep963x.rs000066400000000000000000000004471460375044200175020ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ // byte = 0x07 #[derive(ToString)] enum Ep963xSmbusError { None = 0x00, Address = 0x01, NoAck = 0x02, Arbitration = 0x04, Command = 0x08, Timeout = 0x10, Busy = 0x20, } fwupd-1.9.16/plugins/ep963x/meson.build000066400000000000000000000010361460375044200176230ustar00rootroot00000000000000if get_option('plugin_ep963x').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginEp963x"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('ep963x.quirk') plugin_builtins += static_library('fu_plugin_ep963x', rustgen.process('fu-ep963x.rs'), sources: [ 'fu-ep963x-device.c', 'fu-ep963x-firmware.c', 'fu-ep963x-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/fastboot/000077500000000000000000000000001460375044200162445ustar00rootroot00000000000000fwupd-1.9.16/plugins/fastboot/README.md000066400000000000000000000032621460375044200175260ustar00rootroot00000000000000--- title: Plugin: Fastboot --- ## Introduction This plugin is used to update hardware that uses the fastboot protocol. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in ZIP file format. Inside the zip file must be all the firmware images for each partition and a manifest file. The partition images can be in any format, but the manifest must be either an Android `flashfile.xml` format file, or a QFIL `partition_nand.xml` format file. For both types, all partitions with a defined image found in the zip file will be updated. This plugin supports the following protocol ID: * `com.google.fastboot` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_18D1&PID_4EE0` ## Update Behavior A fastboot device usually presents in runtime mode (or with no interface), but if the user puts the device into fastboot mode using a physical button it then enumerates with a USB descriptor. On attach the device reboots to runtime mode which *may* mean the device "goes away". For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. ## Quirk Use This plugin uses the following plugin-specific quirk: ### FastbootBlockSize Block size to use for transfers. Since: 1.2.2 ### FastbootOperationDelay Time in ms to delay after a read or write operation. Since: 1.7.4 ## Vendor ID Security The vendor ID is set from the USB vendor, for example `USB:0x18D1` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.2.2`. fwupd-1.9.16/plugins/fastboot/data/000077500000000000000000000000001460375044200171555ustar00rootroot00000000000000fwupd-1.9.16/plugins/fastboot/data/android/000077500000000000000000000000001460375044200205755ustar00rootroot00000000000000fwupd-1.9.16/plugins/fastboot/data/android/flashfile.xml000066400000000000000000000003701460375044200232540ustar00rootroot00000000000000 fwupd-1.9.16/plugins/fastboot/data/lsusb.txt000066400000000000000000000036661460375044200210610ustar00rootroot00000000000000Bus 001 Device 025: ID 18d1:4ee0 Google Inc. Nexus 4 (bootloader) Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x18d1 Google Inc. idProduct 0x4ee0 Nexus 4 (bootloader) bcdDevice 1.00 iManufacturer 1 Google iProduct 2 Android iSerial 3 034412a082919b5c bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0020 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 66 bInterfaceProtocol 3 iInterface 4 fastboot Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 1 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/fastboot/data/qfil/000077500000000000000000000000001460375044200201105ustar00rootroot00000000000000fwupd-1.9.16/plugins/fastboot/data/qfil/partition_nand.xml000066400000000000000000000011751460375044200236470ustar00rootroot00000000000000 0xAA7D1B9A 0x1F7D48BC 0x4 0:SBL 0x8 0x2 0 0xFF 0x01 0x00 0xFE sbl1.mbn fwupd-1.9.16/plugins/fastboot/fastboot.quirk000066400000000000000000000020331460375044200211400ustar00rootroot00000000000000# Quectel EG25-G modem [USB\VID_18D1&PID_D00D] Plugin = fastboot FastbootBlockSize = 16384 FastbootOperationDelay = 250 Summary = Quectel EG25-G modem (fastboot) CounterpartGuid = USB\VID_2C7C&PID_0125 Flags = detach-at-fastboot-has-no-response ModemManagerBranchAtCommand = AT+GETFWBRANCH # Fibocom FM101 [USB\VID_2CB7&PID_D00D] Plugin = fastboot FastbootBlockSize = 16384 FastbootOperationDelay = 0 Summary = Fibocom FM101 Modem (fastboot) CounterpartGuid = USB\VID_2CB7&PID_01A2 # DW5821e [USB\VID_413C&PID_81D6] Plugin = fastboot Summary = Dell DW5821e LTE (fastboot) CounterpartGuid = USB\VID_413C&PID_81D7 # DW5821e/eSIM [USB\VID_413C&PID_81E1] Plugin = fastboot Summary = Dell DW5821e/eSIM LTE modem (fastboot) CounterpartGuid = USB\VID_413C&PID_81E0 # T77W968 [USB\VID_0489&PID_E0B7] Plugin = fastboot Summary = Foxconn T77w968 LTE modem (fastboot) CounterpartGuid = USB\VID_0489&PID_E0B4 # T77W968/eSIM [USB\VID_0489&PID_E0B8] Plugin = fastboot Summary = Foxconn T77w968/eSIM LTE modem (fastboot) CounterpartGuid = USB\VID_0489&PID_E0B5 fwupd-1.9.16/plugins/fastboot/fu-fastboot-device.c000066400000000000000000000501501460375044200220770ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-fastboot-device.h" #define FASTBOOT_REMOVE_DELAY_RE_ENUMERATE 60000 /* ms */ #define FASTBOOT_TRANSACTION_TIMEOUT 1000 /* ms */ #define FASTBOOT_TRANSACTION_RETRY_MAX 600 #define FASTBOOT_EP_IN 0x81 #define FASTBOOT_EP_OUT 0x01 #define FASTBOOT_CMD_BUFSZ 64 /* bytes */ struct _FuFastbootDevice { FuUsbDevice parent_instance; gboolean secure; guint blocksz; guint operation_delay; }; G_DEFINE_TYPE(FuFastbootDevice, fu_fastboot_device, FU_TYPE_USB_DEVICE) static void fu_fastboot_device_to_string(FuDevice *device, guint idt, GString *str) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE(device); fu_string_append_kx(str, idt, "BlockSize", self->blocksz); fu_string_append_kb(str, idt, "Secure", self->secure); } static gboolean fu_fastboot_device_probe(FuDevice *device, GError **error) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); g_autoptr(GUsbInterface) intf = NULL; /* find the correct fastboot interface */ intf = g_usb_device_get_interface(usb_device, 0xff, 0x42, 0x03, error); if (intf == NULL) return FALSE; fu_usb_device_add_interface(FU_USB_DEVICE(self), g_usb_interface_get_number(intf)); return TRUE; } static gboolean fu_fastboot_device_write(FuDevice *device, const guint8 *buf, gsize buflen, GError **error) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); gboolean ret; gsize actual_len = 0; g_autofree guint8 *buf2 = NULL; /* make mutable */ fu_dump_raw(G_LOG_DOMAIN, "writing", buf, buflen); buf2 = fu_memdup_safe(buf, buflen, error); if (buf2 == NULL) return FALSE; ret = g_usb_device_bulk_transfer(usb_device, FASTBOOT_EP_OUT, buf2, buflen, &actual_len, FASTBOOT_TRANSACTION_TIMEOUT, NULL, error); /* give device some time to handle action */ fu_device_sleep(device, self->operation_delay); if (!ret) { g_prefix_error(error, "failed to do bulk transfer: "); return FALSE; } if (actual_len != buflen) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only wrote %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } return TRUE; } static gboolean fu_fastboot_device_writestr(FuDevice *device, const gchar *str, GError **error) { gsize buflen = strlen(str); if (buflen > FASTBOOT_CMD_BUFSZ - 4) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "fastboot limits writes to %i bytes", FASTBOOT_CMD_BUFSZ - 4); return FALSE; } return fu_fastboot_device_write(device, (const guint8 *)str, buflen, error); } typedef enum { FU_FASTBOOT_DEVICE_READ_FLAG_NONE, FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL, } FuFastbootDeviceReadFlags; static gboolean fu_fastboot_device_read(FuDevice *device, gchar **str, FuProgress *progress, FuFastbootDeviceReadFlags flags, GError **error) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); guint retries = 1; /* these commands may return INFO or take some time to complete */ if (flags & FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL) retries = FASTBOOT_TRANSACTION_RETRY_MAX; for (guint i = 0; i < retries; i++) { gboolean ret; gsize actual_len = 0; guint8 buf[FASTBOOT_CMD_BUFSZ] = {0x00}; g_autofree gchar *tmp = NULL; g_autoptr(GError) error_local = NULL; ret = g_usb_device_bulk_transfer(usb_device, FASTBOOT_EP_IN, buf, sizeof(buf), &actual_len, FASTBOOT_TRANSACTION_TIMEOUT, NULL, &error_local); /* give device some time to handle action */ fu_device_sleep(device, self->operation_delay); if (!ret) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT)) { g_debug("ignoring %s", error_local->message); continue; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to do bulk transfer: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "read", buf, actual_len); if (actual_len < 4) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only read %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } /* info */ tmp = g_strndup((const gchar *)buf + 4, self->blocksz - 4); if (memcmp(buf, "INFO", 4) == 0) { if (g_strcmp0(tmp, "erasing flash") == 0) fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_ERASE); else if (g_strcmp0(tmp, "writing flash") == 0) fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); else g_debug("INFO returned unknown: %s", tmp); continue; } /* success */ if (memcmp(buf, "OKAY", 4) == 0 || memcmp(buf, "DATA", 4) == 0) { if (str != NULL) *str = g_steal_pointer(&tmp); return TRUE; } /* failure */ if (memcmp(buf, "FAIL", 4) == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to read response: %s", tmp); return FALSE; } /* unknown failure */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to read response"); return FALSE; } /* we timed out a *lot* */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "no response to read"); return FALSE; } static gboolean fu_fastboot_device_getvar(FuDevice *device, const gchar *key, gchar **str, GError **error) { g_autofree gchar *tmp = g_strdup_printf("getvar:%s", key); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); if (!fu_fastboot_device_writestr(device, tmp, error)) return FALSE; if (!fu_fastboot_device_read(device, str, progress, FU_FASTBOOT_DEVICE_READ_FLAG_NONE, error)) { g_prefix_error(error, "failed to getvar %s: ", key); return FALSE; } return TRUE; } static gboolean fu_fastboot_device_cmd(FuDevice *device, const gchar *cmd, FuProgress *progress, FuFastbootDeviceReadFlags flags, GError **error) { if (!fu_fastboot_device_writestr(device, cmd, error)) return FALSE; if (!fu_fastboot_device_read(device, NULL, progress, flags, error)) return FALSE; return TRUE; } static gboolean fu_fastboot_device_flash(FuDevice *device, const gchar *partition, FuProgress *progress, GError **error) { g_autofree gchar *tmp = g_strdup_printf("flash:%s", partition); return fu_fastboot_device_cmd(device, tmp, progress, FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL, error); } static gboolean fu_fastboot_device_download(FuDevice *device, GBytes *fw, FuProgress *progress, GError **error) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE(device); gsize sz = g_bytes_get_size(fw); g_autofree gchar *tmp = g_strdup_printf("download:%08x", (guint)sz); g_autoptr(FuChunkArray) chunks = NULL; /* tell the client the size of data to expect */ if (!fu_fastboot_device_cmd(device, tmp, progress, FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL, error)) return FALSE; /* send the data in chunks */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); chunks = fu_chunk_array_new_from_bytes(fw, 0x00, self->blocksz); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_fastboot_device_write(device, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; fu_progress_step_done(progress); } if (!fu_fastboot_device_read(device, NULL, progress, FU_FASTBOOT_DEVICE_READ_FLAG_STATUS_POLL, error)) return FALSE; return TRUE; } static gboolean fu_fastboot_device_setup(FuDevice *device, GError **error) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE(device); g_autofree gchar *product = NULL; g_autofree gchar *serialno = NULL; g_autofree gchar *version = NULL; g_autofree gchar *secure = NULL; g_autofree gchar *version_bootloader = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_fastboot_device_parent_class)->setup(device, error)) return FALSE; /* product */ if (!fu_fastboot_device_getvar(device, "product", &product, error)) return FALSE; if (product != NULL && product[0] != '\0') { g_autofree gchar *tmp = g_strdup_printf("Fastboot %s", product); fu_device_set_name(device, tmp); } /* fastboot API version */ if (!fu_fastboot_device_getvar(device, "version", &version, error)) return FALSE; if (version != NULL && version[0] != '\0') g_info("fastboot version %s", version); /* bootloader version */ if (!fu_fastboot_device_getvar(device, "version-bootloader", &version_bootloader, error)) return FALSE; if (version_bootloader != NULL && version_bootloader[0] != '\0') { fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PAIR); fu_device_set_version_bootloader(device, version_bootloader); } /* serialno */ if (!fu_fastboot_device_getvar(device, "serialno", &serialno, error)) return FALSE; if (serialno != NULL && serialno[0] != '\0') fu_device_set_serial(device, serialno); /* secure */ if (!fu_fastboot_device_getvar(device, "secure", &secure, error)) return FALSE; if (secure != NULL && secure[0] != '\0') self->secure = TRUE; /* success */ return TRUE; } static gboolean fu_fastboot_device_write_qfil_part(FuDevice *device, FuFirmware *firmware, XbNode *part, FuProgress *progress, GError **error) { GBytes *data; const gchar *fn; const gchar *partition; /* not all partitions have images */ fn = xb_node_query_text(part, "img_name", NULL); if (fn == NULL) return TRUE; /* find filename */ data = fu_firmware_get_image_by_id_bytes(firmware, fn, error); if (data == NULL) return FALSE; /* get the partition name */ partition = xb_node_query_text(part, "name", error); if (partition == NULL) return FALSE; if (g_str_has_prefix(partition, "0:")) partition += 2; /* flash the partition */ if (!fu_fastboot_device_download(device, data, progress, error)) return FALSE; return fu_fastboot_device_flash(device, partition, progress, error); } static gboolean fu_fastboot_device_write_motorola_part(FuDevice *device, FuFirmware *firmware, XbNode *part, FuProgress *progress, GError **error) { const gchar *op = xb_node_get_attr(part, "operation"); /* oem */ if (g_strcmp0(op, "oem") == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "OEM commands are not supported"); return FALSE; } /* getvar */ if (g_strcmp0(op, "getvar") == 0) { const gchar *var = xb_node_get_attr(part, "var"); g_autofree gchar *tmp = NULL; /* check required args */ if (var == NULL) { tmp = xb_node_export(part, XB_NODE_EXPORT_FLAG_NONE, NULL); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "required var for part: %s", tmp); return FALSE; } /* just has to be non-empty */ if (!fu_fastboot_device_getvar(device, var, &tmp, error)) return FALSE; if (tmp == NULL || tmp[0] == '\0') { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to getvar %s", var); return FALSE; } return TRUE; } /* erase */ if (g_strcmp0(op, "erase") == 0) { const gchar *partition = xb_node_get_attr(part, "partition"); g_autofree gchar *cmd = g_strdup_printf("erase:%s", partition); /* check required args */ if (partition == NULL) { g_autofree gchar *tmp = NULL; tmp = xb_node_export(part, XB_NODE_EXPORT_FLAG_NONE, NULL); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "required partition for part: %s", tmp); return FALSE; } /* erase the partition */ return fu_fastboot_device_cmd(device, cmd, progress, FU_FASTBOOT_DEVICE_READ_FLAG_NONE, error); } /* flash */ if (g_strcmp0(op, "flash") == 0) { GBytes *data; const gchar *filename = xb_node_get_attr(part, "filename"); const gchar *partition = xb_node_get_attr(part, "partition"); struct { GChecksumType kind; const gchar *str; } csum_kinds[] = {{G_CHECKSUM_MD5, "MD5"}, {G_CHECKSUM_SHA1, "SHA1"}, {G_CHECKSUM_SHA256, "SHA256"}, {0, NULL}}; /* check required args */ if (partition == NULL || filename == NULL) { g_autofree gchar *tmp = NULL; tmp = xb_node_export(part, XB_NODE_EXPORT_FLAG_NONE, NULL); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "required partition and filename: %s", tmp); return FALSE; } /* find filename */ data = fu_firmware_get_image_by_id_bytes(firmware, filename, error); if (data == NULL) return FALSE; /* checksum is optional */ for (guint i = 0; csum_kinds[i].str != NULL; i++) { const gchar *csum; g_autofree gchar *csum_actual = NULL; /* not provided */ csum = xb_node_get_attr(part, csum_kinds[i].str); if (csum == NULL) continue; /* check is valid */ csum_actual = g_compute_checksum_for_bytes(csum_kinds[i].kind, data); if (g_strcmp0(csum, csum_actual) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%s invalid, expected %s, got %s", filename, csum, csum_actual); return FALSE; } } /* flash the partition */ if (!fu_fastboot_device_download(device, data, progress, error)) return FALSE; return fu_fastboot_device_flash(device, partition, progress, error); } /* dumb operation that doesn't expect a response */ if (g_strcmp0(op, "boot") == 0 || g_strcmp0(op, "continue") == 0 || g_strcmp0(op, "reboot") == 0 || g_strcmp0(op, "reboot-bootloader") == 0 || g_strcmp0(op, "powerdown") == 0) { return fu_fastboot_device_cmd(device, op, progress, FU_FASTBOOT_DEVICE_READ_FLAG_NONE, error); } /* unknown */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unknown operation %s", op); return FALSE; } static gboolean fu_fastboot_device_write_motorola(FuDevice *device, FuFirmware *firmware, FuProgress *progress, GError **error) { GBytes *data; g_autoptr(GPtrArray) parts = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; /* load the manifest of operations */ data = fu_firmware_get_image_by_id_bytes(firmware, "flashfile.xml", error); if (data == NULL) return FALSE; if (!xb_builder_source_load_bytes(source, data, XB_BUILDER_SOURCE_FLAG_NONE, error)) return FALSE; xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; /* get all the operation parts */ parts = xb_silo_query(silo, "parts/part", 0, error); if (parts == NULL) return FALSE; fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, parts->len); for (guint i = 0; i < parts->len; i++) { XbNode *part = g_ptr_array_index(parts, i); if (!fu_fastboot_device_write_motorola_part(device, firmware, part, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_fastboot_device_write_qfil(FuDevice *device, FuFirmware *firmware, FuProgress *progress, GError **error) { GBytes *data; g_autoptr(GPtrArray) parts = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; /* load the manifest of operations */ data = fu_firmware_get_image_by_id_bytes(firmware, "partition_nand.xml", error); if (data == NULL) return FALSE; if (!xb_builder_source_load_bytes(source, data, XB_BUILDER_SOURCE_FLAG_NONE, error)) return FALSE; xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; /* get all the operation parts */ parts = xb_silo_query(silo, "nandboot/partitions/partition", 0, error); if (parts == NULL) return FALSE; fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, parts->len); for (guint i = 0; i < parts->len; i++) { XbNode *part = g_ptr_array_index(parts, i); if (!fu_fastboot_device_write_qfil_part(device, firmware, part, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_fastboot_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) manifest = NULL; /* load the manifest of operations */ manifest = fu_firmware_get_image_by_id(firmware, "partition_nand.xml", NULL); if (manifest != NULL) return fu_fastboot_device_write_qfil(device, firmware, progress, error); manifest = fu_firmware_get_image_by_id(firmware, "flashfile.xml", NULL); if (manifest != NULL) return fu_fastboot_device_write_motorola(device, firmware, progress, error); /* not supported */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "manifest not supported"); return FALSE; } static gboolean fu_fastboot_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuFastbootDevice *self = FU_FASTBOOT_DEVICE(device); guint64 tmp = 0; /* load from quirks */ if (g_strcmp0(key, "FastbootBlockSize") == 0) { if (!fu_strtoull(value, &tmp, 0x40, 0x100000, error)) return FALSE; self->blocksz = tmp; return TRUE; } if (g_strcmp0(key, "FastbootOperationDelay") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXSIZE, error)) return FALSE; self->operation_delay = tmp; return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_fastboot_device_attach(FuDevice *device, FuProgress *progress, GError **error) { if (!fu_fastboot_device_cmd(device, "reboot", progress, FU_FASTBOOT_DEVICE_READ_FLAG_NONE, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_fastboot_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_fastboot_device_init(FuFastbootDevice *self) { /* this is a safe default, even using USBv1 */ self->blocksz = 512; /* no delay is applied by default after a read or write operation */ self->operation_delay = 0; fu_device_add_protocol(FU_DEVICE(self), "com.google.fastboot"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_set_remove_delay(FU_DEVICE(self), FASTBOOT_REMOVE_DELAY_RE_ENUMERATE); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_ARCHIVE_FIRMWARE); } static void fu_fastboot_device_class_init(FuFastbootDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_fastboot_device_probe; klass_device->setup = fu_fastboot_device_setup; klass_device->write_firmware = fu_fastboot_device_write_firmware; klass_device->attach = fu_fastboot_device_attach; klass_device->to_string = fu_fastboot_device_to_string; klass_device->set_quirk_kv = fu_fastboot_device_set_quirk_kv; klass_device->set_progress = fu_fastboot_device_set_progress; } fwupd-1.9.16/plugins/fastboot/fu-fastboot-device.h000066400000000000000000000004611460375044200221040ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_FASTBOOT_DEVICE (fu_fastboot_device_get_type()) G_DECLARE_FINAL_TYPE(FuFastbootDevice, fu_fastboot_device, FU, FASTBOOT_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/fastboot/fu-fastboot-plugin.c000066400000000000000000000016021460375044200221340ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-fastboot-device.h" #include "fu-fastboot-plugin.h" struct _FuFastbootPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuFastbootPlugin, fu_fastboot_plugin, FU_TYPE_PLUGIN) static void fu_fastboot_plugin_init(FuFastbootPlugin *self) { } static void fu_fastboot_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "FastbootBlockSize"); fu_context_add_quirk_key(ctx, "FastbootOperationDelay"); fu_plugin_add_device_gtype(plugin, FU_TYPE_FASTBOOT_DEVICE); } static void fu_fastboot_plugin_class_init(FuFastbootPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_fastboot_plugin_constructed; } fwupd-1.9.16/plugins/fastboot/fu-fastboot-plugin.h000066400000000000000000000003561460375044200221460ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuFastbootPlugin, fu_fastboot_plugin, FU, FASTBOOT_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/fastboot/meson.build000066400000000000000000000010101460375044200203760ustar00rootroot00000000000000if get_option('plugin_fastboot').require(gusb.found(), error_message: 'gusb is needed for plugin_fastboot').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginFastboot"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('fastboot.quirk') plugin_builtins += static_library('fu_plugin_fastboot', sources: [ 'fu-fastboot-plugin.c', 'fu-fastboot-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/flashrom/000077500000000000000000000000001460375044200162365ustar00rootroot00000000000000fwupd-1.9.16/plugins/flashrom/README.md000066400000000000000000000045301460375044200175170ustar00rootroot00000000000000--- title: Plugin: Flashrom --- ## Introduction This plugin uses `libflashrom` to update the system firmware. It can be used to update BIOS or ME regions of the flash. Device for ME region is created only if "Intel SPI" plugin indicates that such a region exists, which makes "Intel SPI" a dependency of this plugin for doing ME updates. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format, which is typically the raw input for an EEPROM programmer. This plugin supports the following protocol ID: * `org.flashrom` ## Coreboot Version String The coreboot version string can have an optional prefix (see below). After the optional prefix the *major*, *minor* string follows and finally the *build string*, containing the exact commit and repository state, follows. For example `4.10-989-gc8a4e4b9c5-dirty` ### Exception on Lenovo devices The thinkpad_acpi kernel module requires a specific pattern in the DMI version string. To satisfy those requirements coreboot adds the CBETxxxx prefix to the DMI version string on all Lenovo devices. For example `CBET4000 4.10-989-gc8a4e4b9c5-dirty` The coreboot DMI version string always starts with `CBET`. ## GUID Generation Internal device uses hardware ID values which are derived from SMBIOS. * HardwareID-3 * HardwareID-4 * HardwareID-5 * HardwareID-6 * HardwareID-10 They should match the values provided by `fwupdtool hwids` or the `ComputerHardwareIds.exe` Windows utility. One more GUID has the following form: * `FLASHROM\VENDOR_{manufacturer}&PRODUCT_{product}®ION_{ifd_region_name}` Its purpose is to target specific regions of the flash as defined by IFD (Intel SPI Flash Descriptor), examples: * `FLASHROM\VENDOR_Notebook&PRODUCT_NS50MU®ION_BIOS` * `FLASHROM\VENDOR_Notebook&PRODUCT_NS50MU®ION_ME` ## Update Behavior The firmware is deployed to the SPI chip when the machine is in normal runtime mode, but it is only used when the device is rebooted. ## Vendor ID Security The vendor ID is set from the BIOS vendor, for example `DMI:Google` ## External Interface Access This plugin requires access to all interfaces that `libflashrom` has been compiled for. This typically is `/sys/bus/spi` but there may be other interfaces as well. ## Version Considerations This plugin has been available since fwupd version `1.1.2`. fwupd-1.9.16/plugins/flashrom/flashrom.quirk000066400000000000000000000075341460375044200211370ustar00rootroot00000000000000# Purism [a0ce5085-2dea-5086-ae72-45810a186ad0] Plugin = flashrom # Libretrend [52b68c34-6b31-5ecc-8a5c-de37e666ccd5] Plugin = flashrom VersionFormat = quad # StarLite Mk II (HwId - AMI) [013b60e5-1023-5bee-8ae5-14cae21377b7] Plugin = flashrom # StarLite Mk II (HwId - coreboot) [0130a1a1-f888-5977-820a-6214bf1d6ab2] Plugin = flashrom # StarLite Mk III (HwId) [d5521faa-c50b-5d64-971d-8fd400030c51] Plugin = flashrom # StarLite Mk IV (HwId) [0fc25c8c-ffa8-54ad-a216-d13cfe75bee4] Plugin = flashrom # StarLabTop Mk III (HwId - AMI) [013b60e5-1023-5bee-8ae5-14cae21377b7] Plugin = flashrom # StarLabTop Mk III (HwId - coreboot) [8f8ca82b-30e1-5907-bc9d-4257a49898d4] Plugin = flashrom # StarLabTop Mk IV (HwId) [baf1d04e-fd16-5e6a-93cc-1c23d171f879] Plugin = flashrom # StarBookMk V (HwId) [85aba599-addd-5985-a2e8-eddb41c61ba3] Plugin = flashrom # StarBook Mk VI - Intel (HwId) [5c917039-d938-5c9a-b22a-9c392b1534f3] Plugin = flashrom # StarBook Mk VI - AMD (HwId) [b39593ad-7522-52a5-a4ab-1b2ca2153956] Plugin = flashrom # StarBook Mk VIr2 - Intel (HwId) [12122d1c-c383-5583-9cb7-3ba8d220913d] Plugin = flashrom # Byte Mk I - AMD (HwId) [79a64046-5643-59c4-91d0-e68b33db5829] Plugin = flashrom # StarLabTop Mk III (coreboot GUID) [d33219e2-b84c-53a8-a624-27af9752dba6] Branch = coreboot Flags = reset-cmos PciBcrAddr = 0x0 # StarLite Mk II (coreboot GUID) [2993474c-b4c4-5b7c-b2e1-a471d8d328a5] Branch = coreboot Flags = reset-cmos PciBcrAddr = 0x0 # StarLite Mk II (AMI GUID) [0676539d-150f-5f07-89b9-fe0afd98c44e] FirmwareSizeMax = 0x800000 # StarLite Mk III (coreboot GUID) [3d2f164a-8818-58fd-a082-6c60a67e21a6] Branch = coreboot Flags = reset-cmos PciBcrAddr = 0x0 # StarLite Mk III (AMI GUID) [ec375a72-9ed9-5a21-b1da-5e7f00dcada1] FirmwareSizeMax = 0x800000 # StarLite Mk IV (coreboot GUID) [5dc1dd5b-761e-5146-8ac2-1fdd8445f2ff] Branch = coreboot Flags = reset-cmos PciBcrAddr = 0x0 # StarLite Mk IV (AMI GUID) [32edd806-13a0-5b0f-a8e9-656a0e147369] FirmwareSizeMax = 0x800000 # StarLabTop Mk IV (coreboot GUID) [0ee5867c-93f0-5fb4-adf1-9d686ea1183a] Branch = coreboot Flags = reset-cmos PciBcrAddr = 0x0 # StarBook Mk V (coreboot GUID) [54c96fef-31e7-5011-a3ff-ea8e855d9acd] Branch = coreboot Flags = reset-cmos PciBcrAddr = 0x0 # StarBook Mk VI - Intel (AMI GUID) [1292e166-a66f-5e11-b2bb-53265a8f53d9] FirmwareSizeMax = 0x2000000 # StarBook Mk VI - Intel (coreboot GUID) [8c994a92-7ef8-5d68-80b5-99ead7cf4686] Branch = coreboot Flags = reset-cmos PciBcrAddr = 0x0 FirmwareSizeMax = 0x2000000 # StarBook Mk VIr2 - Intel (AMI GUID) [ce35649d-b89d-5188-b961-d992ce7e0f88] FirmwareSizeMax = 0x2000000 # StarBook Mk VIr2 - Intel (coreboot GUID) [595c5861-a105-509b-8dd6-f77070345286] Branch = coreboot Flags = reset-cmos PciBcrAddr = 0x0 FirmwareSizeMax = 0x2000000 # NovaCustom NV4x (HwId) [25b6ea34-8f52-598e-a27a-31e03014dbe3] Plugin = flashrom [59caeff5-6a3c-595b-aab1-56500d0fecbc] Plugin = flashrom # NovaCustom NV4x (Dasharo GUID) [9a8c30e2-1b7e-5b28-9632-fc2fbf8cd0ba] Branch = dasharo VersionFormat = triplet [41a8fb3d-213a-5a8a-b0f4-e2a7c55aaf80] Branch = dasharo VersionFormat = triplet # NovaCustom NS5x (HwId) [bab4f96c-34e4-5b92-a785-4e4489b1c395] Plugin = flashrom # NovaCustom NS5x (Dasharo GUID) [43455705-4c7d-5440-b285-8362b67a5743] Branch = dasharo VersionFormat = triplet # TUXEDO InfinityBook S 14 Gen6 (HwId) [9366407e-433e-51e9-bd79-a517e3f31536] Plugin = flashrom [FLASHROM\GUID_9366407e-433e-51e9-bd79-a517e3f31536] Flags = fn-m-me-unlock FirmwareSizeMax = 0x1000000 # TUXEDO InfinityBook S 15 Gen6 (HwId) [16cabee1-8d79-5164-9400-346dddbfe2c5] Plugin = flashrom [FLASHROM\GUID_16cabee1-8d79-5164-9400-346dddbfe2c5] Flags = fn-m-me-unlock FirmwareSizeMax = 0x1000000 # TUXEDO InfinityBook Pro 13 v3 (HwId) [caaa8a77-aca3-595b-92fa-4e6fb7bccc82] Plugin = flashrom [FLASHROM\GUID_caaa8a77-aca3-595b-92fa-4e6fb7bccc82] Flags = fn-m-me-unlock FirmwareSizeMax = 0x800000 fwupd-1.9.16/plugins/flashrom/fu-flashrom-cmos.c000066400000000000000000000025761460375044200215760ustar00rootroot00000000000000/* * Copyright (C) 2021 Sean Rhodes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_IO_H #include #endif #include "fu-flashrom-cmos.h" #ifdef HAVE_IO_H static gboolean fu_flashrom_cmos_write(guint8 addr, guint8 val) { guint8 tmp; /* Reject addresses in the second bank */ if (addr >= 128) return FALSE; /* Write the value to CMOS */ outb(addr, RTC_BASE_PORT); outb(val, RTC_BASE_PORT + 1); /* Read the value back from CMOS */ outb(addr, RTC_BASE_PORT); tmp = inb(RTC_BASE_PORT + 1); /* Check the read value against the written */ return (tmp == val); } #endif gboolean fu_flashrom_cmos_reset(GError **error) { #ifdef HAVE_IO_H /* Call ioperm() to grant us access to ports 0x70 and 0x71 */ if (ioperm(RTC_BASE_PORT, 2, TRUE) < 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to gain access to ports 0x70 and 0x71"); return FALSE; } /* Write a default value to the CMOS checksum */ if ((!fu_flashrom_cmos_write(CMOS_CHECKSUM_OFFSET, 0xff)) || (!fu_flashrom_cmos_write(CMOS_CHECKSUM_OFFSET + 1, 0xff))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to reset CMOS"); return FALSE; } /* success */ return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no support"); return FALSE; #endif } fwupd-1.9.16/plugins/flashrom/fu-flashrom-cmos.h000066400000000000000000000007301460375044200215710ustar00rootroot00000000000000/* * Copyright (C) 2021 Sean Rhodes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /* From coreboot's src/include/pc80/mc146818rtc.h file */ #define RTC_BASE_PORT 0x70 /* * This is the offset of the first of the two checksum bytes * we may want to figure out how we can determine this dynamically * during execution. */ #define CMOS_CHECKSUM_OFFSET 123 gboolean fu_flashrom_cmos_reset(GError **error); fwupd-1.9.16/plugins/flashrom/fu-flashrom-device.c000066400000000000000000000300651460375044200220660ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * Copyright (C) 2021 Daniel Campello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-flashrom-cmos.h" #include "fu-flashrom-device.h" /* * Flag to determine if the CMOS checksum should be reset after the flash * is reprogrammed. This will force the CMOS defaults to be reloaded on * the next boot. */ #define FU_FLASHROM_DEVICE_FLAG_RESET_CMOS (1 << 0) /* * Flag to determine if manual ME unlocking by pressing Fn + M is supported. */ #define FU_FLASHROM_DEVICE_FLAG_FN_M_ME_UNLOCK (1 << 1) struct _FuFlashromDevice { FuUdevDevice parent_instance; FuIfdRegion region; struct flashrom_flashctx *flashctx; struct flashrom_layout *layout; }; G_DEFINE_TYPE(FuFlashromDevice, fu_flashrom_device, FU_TYPE_UDEV_DEVICE) enum { PROP_0, PROP_FLASHCTX, PROP_REGION, PROP_LAST }; static gboolean fu_flashrom_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { if (g_strcmp0(key, "PciBcrAddr") == 0) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; fu_device_set_metadata_integer(device, "PciBcrAddr", tmp); return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no supported"); return FALSE; } static gboolean fu_flashrom_device_probe(FuDevice *device, GError **error) { const gchar *dev_name = NULL; const gchar *sysfs_path = NULL; sysfs_path = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device)); if (sysfs_path != NULL) { g_autofree gchar *physical_id = NULL; physical_id = g_strdup_printf("DEVNAME=%s", sysfs_path); fu_device_set_physical_id(device, physical_id); } dev_name = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "name", NULL); if (dev_name != NULL) { fu_device_add_instance_id_full(device, dev_name, FU_DEVICE_INSTANCE_FLAG_QUIRKS); } return TRUE; } static gboolean fu_flashrom_device_open(FuDevice *device, GError **error) { FuFlashromDevice *self = FU_FLASHROM_DEVICE(device); /* get the flash size from the device if not already been quirked */ if (fu_device_get_firmware_size_max(device) == 0) { gsize flash_size = flashrom_flash_getsize(self->flashctx); if (flash_size == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flash size zero"); return FALSE; } fu_device_set_firmware_size_max(device, flash_size); } /* update only one specific region of the flash and do not touch others */ if (fu_cpu_get_vendor() == FU_CPU_VENDOR_INTEL) { struct flashrom_layout *layout = NULL; if (flashrom_layout_read_from_ifd(&layout, self->flashctx, NULL, 0)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read layout from Intel ICH descriptor"); return FALSE; } if (flashrom_layout_include_region(layout, fu_ifd_region_to_string(self->region))) { flashrom_layout_release(layout); g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid region name"); return FALSE; } /* does not transfer ownership, so we must manage the lifetime of layout */ self->layout = layout; flashrom_layout_set(self->flashctx, self->layout); } return TRUE; } static gboolean fu_flashrom_device_close(FuDevice *device, GError **error) { FuFlashromDevice *self = FU_FLASHROM_DEVICE(device); if (self->layout != NULL) { flashrom_layout_release(self->layout); self->layout = NULL; flashrom_layout_set(self->flashctx, NULL); } return TRUE; } static GBytes * fu_flashrom_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuFlashromDevice *self = FU_FLASHROM_DEVICE(device); gint rc; gsize bufsz = fu_device_get_firmware_size_max(device); g_autofree guint8 *buf = g_malloc0(bufsz); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); rc = flashrom_image_read(self->flashctx, buf, bufsz); if (rc != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "failed to read flash [%i]", rc); return NULL; } return g_bytes_new_take(g_steal_pointer(&buf), bufsz); } static gboolean fu_flashrom_device_prepare(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { g_autofree gchar *firmware_orig = NULL; g_autofree gchar *localstatedir = NULL; g_autofree gchar *basename = NULL; /* if the original firmware doesn't exist, grab it now */ basename = g_strdup_printf("flashrom-%s.bin", fu_device_get_id(device)); localstatedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); firmware_orig = g_build_filename(localstatedir, "builder", basename, NULL); if (!fu_path_mkdir_parent(firmware_orig, error)) return FALSE; if (!g_file_test(firmware_orig, G_FILE_TEST_EXISTS)) { g_autoptr(GBytes) buf = NULL; buf = fu_flashrom_device_dump_firmware(device, progress, error); if (buf == NULL) { g_prefix_error(error, "failed to back up original firmware: "); return FALSE; } if (!fu_bytes_set_contents(firmware_orig, buf, error)) return FALSE; } return TRUE; } static gboolean fu_flashrom_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuFlashromDevice *self = FU_FLASHROM_DEVICE(device); gsize sz = 0; gint rc; const guint8 *buf; g_autoptr(GBytes) blob_fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 10, NULL); /* read early */ blob_fw = fu_firmware_get_bytes(firmware, error); if (blob_fw == NULL) return FALSE; buf = g_bytes_get_data(blob_fw, &sz); /* write region */ if (sz != fu_device_get_firmware_size_max(device)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid image size 0x%x, expected 0x%x", (guint)sz, (guint)fu_device_get_firmware_size_max(device)); return FALSE; } rc = flashrom_image_write(self->flashctx, (void *)buf, sz, NULL /* refbuffer */); if (rc != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "image write failed, err=%i", rc); return FALSE; } fu_progress_step_done(progress); if (flashrom_image_verify(self->flashctx, (void *)buf, sz)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "image verify failed"); return FALSE; } fu_progress_step_done(progress); /* Check if CMOS needs a reset */ if (fu_device_has_private_flag(device, FU_FLASHROM_DEVICE_FLAG_RESET_CMOS)) { g_debug("attempting CMOS reset"); if (!fu_flashrom_cmos_reset(error)) { g_prefix_error(error, "failed CMOS reset: "); return FALSE; } } /* success */ return TRUE; } static void fu_flashrom_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_flashrom_device_init(FuFlashromDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_protocol(FU_DEVICE(self), "org.flashrom"); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE); fu_device_set_physical_id(FU_DEVICE(self), "flashrom"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_add_icon(FU_DEVICE(self), "computer"); fu_device_register_private_flag(FU_DEVICE(self), FU_FLASHROM_DEVICE_FLAG_RESET_CMOS, "reset-cmos"); fu_device_register_private_flag(FU_DEVICE(self), FU_FLASHROM_DEVICE_FLAG_FN_M_ME_UNLOCK, "fn-m-me-unlock"); } static void fu_flashrom_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuFlashromDevice *self = FU_FLASHROM_DEVICE(object); switch (prop_id) { case PROP_FLASHCTX: g_value_set_pointer(value, self->flashctx); break; case PROP_REGION: g_value_set_uint(value, self->region); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_flashrom_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuFlashromDevice *self = FU_FLASHROM_DEVICE(object); switch (prop_id) { case PROP_FLASHCTX: self->flashctx = g_value_get_pointer(value); break; case PROP_REGION: self->region = g_value_get_uint(value); fu_device_set_logical_id(FU_DEVICE(self), fu_ifd_region_to_string(self->region)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_flashrom_device_finalize(GObject *object) { FuFlashromDevice *self = FU_FLASHROM_DEVICE(object); if (self->layout != NULL) flashrom_layout_release(self->layout); G_OBJECT_CLASS(fu_flashrom_device_parent_class)->finalize(object); } static void fu_flashrom_device_class_init(FuFlashromDeviceClass *klass) { GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->get_property = fu_flashrom_device_get_property; object_class->set_property = fu_flashrom_device_set_property; /** * FuFlashromDevice:region: * * The IFD region that's being managed. */ pspec = g_param_spec_uint("region", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_REGION, pspec); /** * FuFlashromDevice:flashctx: * * The JSON root member for the device. */ pspec = g_param_spec_pointer("flashctx", NULL, NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FLASHCTX, pspec); object_class->finalize = fu_flashrom_device_finalize; klass_device->set_quirk_kv = fu_flashrom_device_set_quirk_kv; klass_device->probe = fu_flashrom_device_probe; klass_device->open = fu_flashrom_device_open; klass_device->close = fu_flashrom_device_close; klass_device->set_progress = fu_flashrom_device_set_progress; klass_device->prepare = fu_flashrom_device_prepare; klass_device->dump_firmware = fu_flashrom_device_dump_firmware; klass_device->write_firmware = fu_flashrom_device_write_firmware; } FuDevice * fu_flashrom_device_new(FuContext *ctx, struct flashrom_flashctx *flashctx, FuIfdRegion region) { return FU_DEVICE(g_object_new(FU_TYPE_FLASHROM_DEVICE, "context", ctx, "flashctx", flashctx, "region", region, NULL)); } gboolean fu_flashrom_device_unlock(FuFlashromDevice *self, GError **error) { if (self->region == FU_IFD_REGION_ME && fu_device_has_private_flag(FU_DEVICE(self), FU_FLASHROM_DEVICE_FLAG_FN_M_ME_UNLOCK)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "\n" "ME region should be unlocked manually the following way:\n" " 1. Power off your device\n" " 2. Press and keep holding Fn + M during the next step\n" " 3. Press power on button"); return FALSE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unlocking of device %s is not supported", fu_device_get_name(FU_DEVICE(self))); return FALSE; } fwupd-1.9.16/plugins/flashrom/fu-flashrom-device.h000066400000000000000000000011041460375044200220630ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * Copyright (C) 2021 Daniel Campello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_FLASHROM_DEVICE (fu_flashrom_device_get_type()) G_DECLARE_FINAL_TYPE(FuFlashromDevice, fu_flashrom_device, FU, FLASHROM_DEVICE, FuUdevDevice) struct flashrom_flashctx; FuDevice * fu_flashrom_device_new(FuContext *ctx, struct flashrom_flashctx *flashctx, FuIfdRegion region); gboolean fu_flashrom_device_unlock(FuFlashromDevice *self, GError **error); fwupd-1.9.16/plugins/flashrom/fu-flashrom-plugin.c000066400000000000000000000261511460375044200221260ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * Copyright (C) 2019 9elements Agency GmbH * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-flashrom-device.h" #define SELFCHECK_TRUE 1 struct FuPluginData { struct flashrom_flashctx *flashctx; struct flashrom_programmer *flashprog; gchar *guid; /* GUID from quirks that activated this plugin */ }; typedef FuPluginData FuFlashromPlugin; #define FU_FLASHROM_PLUGIN(o) fu_plugin_get_data(FU_PLUGIN(o)) static void fu_flashrom_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuFlashromPlugin *self = FU_FLASHROM_PLUGIN(plugin); if (self->guid != NULL) fu_string_append(str, idt, "Guid", self->guid); } static int fu_flashrom_plugin_debug_cb(enum flashrom_log_level lvl, const char *fmt, va_list args) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" g_autofree gchar *tmp = g_strdup_vprintf(fmt, args); #pragma clang diagnostic pop g_autofree gchar *str = fu_strstrip(tmp); if (g_strcmp0(str, "OK.") == 0 || g_strcmp0(str, ".") == 0) return 0; switch (lvl) { case FLASHROM_MSG_ERROR: case FLASHROM_MSG_WARN: g_warning("%s", str); break; case FLASHROM_MSG_INFO: g_info("%s", str); break; case FLASHROM_MSG_DEBUG: case FLASHROM_MSG_DEBUG2: g_debug("%s", str); break; case FLASHROM_MSG_SPEW: default: break; } return 0; } static void fu_flashrom_plugin_device_set_version(FuPlugin *plugin, FuDevice *device) { FuContext *ctx = fu_plugin_get_context(plugin); const gchar *version; const gchar *version_major; const gchar *version_minor; /* as-is */ version = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_BIOS_VERSION); if (version != NULL) { /* some Lenovo hardware requires a specific prefix for the EC, * so strip it before we use ensure-semver */ if (strlen(version) > 9 && g_str_has_prefix(version, "CBET")) version += 9; /* this may not "stick" if there are no numeric chars */ fu_device_set_version(device, version); if (fu_device_get_version(device) != NULL) return; } /* component parts only */ version_major = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_BIOS_MAJOR_RELEASE); version_minor = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_BIOS_MINOR_RELEASE); if (version_major != NULL && version_minor != NULL) { g_autofree gchar *tmp = g_strdup_printf("%s.%s.0", version_major, version_minor); fu_device_set_version(device, tmp); return; } } static gboolean fu_flashrom_plugin_device_set_bios_info(FuPlugin *plugin, FuDevice *device, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); GBytes *bios_blob; const guint8 *buf; gsize bufsz; guint32 bios_char = 0x0; g_autoptr(GPtrArray) bios_tables = NULL; /* get SMBIOS info */ bios_tables = fu_context_get_smbios_data(ctx, FU_SMBIOS_STRUCTURE_TYPE_BIOS, error); if (bios_tables == NULL) return FALSE; /* ROM size if not already been quirked */ bios_blob = g_ptr_array_index(bios_tables, 0); buf = g_bytes_get_data(bios_blob, &bufsz); if (fu_device_get_firmware_size_max(device) == 0) { guint8 bios_sz = 0x0; if (fu_memread_uint8_safe(buf, bufsz, 0x9, &bios_sz, NULL)) { guint64 firmware_size = (bios_sz + 1) * 64 * 1024; fu_device_set_firmware_size_max(device, firmware_size); } } /* BIOS characteristics */ if (fu_memread_uint32_safe(buf, bufsz, 0xa, &bios_char, G_LITTLE_ENDIAN, NULL)) { if ((bios_char & (1 << 11)) == 0) { fu_device_inhibit(device, "bios-characteristics", "Not supported from SMBIOS"); } } return TRUE; } static void fu_flashrom_plugin_device_set_hwids(FuPlugin *plugin, FuDevice *device) { FuContext *ctx = fu_plugin_get_context(plugin); static const gchar *hwids[] = { "HardwareID-3", "HardwareID-4", "HardwareID-5", "HardwareID-6", "HardwareID-10", /* a more useful one for coreboot branch detection */ FU_HWIDS_KEY_MANUFACTURER "&" FU_HWIDS_KEY_FAMILY "&" FU_HWIDS_KEY_PRODUCT_NAME "&" FU_HWIDS_KEY_PRODUCT_SKU "&" FU_HWIDS_KEY_BIOS_VENDOR, }; /* don't include FU_HWIDS_KEY_BIOS_VERSION */ for (guint i = 0; i < G_N_ELEMENTS(hwids); i++) { g_autofree gchar *str = NULL; str = fu_context_get_hwid_replace_value(ctx, hwids[i], NULL); if (str != NULL) fu_device_add_instance_id(device, str); } } static FuDevice * fu_flashrom_plugin_add_device(FuPlugin *plugin, const gchar *guid, FuIfdRegion region, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); FuFlashromPlugin *self = FU_FLASHROM_PLUGIN(plugin); const gchar *dmi_vendor; const gchar *product = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_PRODUCT_NAME); const gchar *vendor = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_MANUFACTURER); const gchar *region_str = fu_ifd_region_to_string(region); g_autofree gchar *name = g_strdup_printf("%s (%s)", product, region_str); g_autoptr(FuDevice) device = fu_flashrom_device_new(ctx, self->flashctx, region); g_autoptr(GError) error_local = NULL; fu_device_set_name(device, name); fu_device_set_vendor(device, vendor); fu_device_add_instance_str(device, "VENDOR", vendor); fu_device_add_instance_str(device, "PRODUCT", product); fu_device_add_instance_strup(device, "REGION", region_str); if (!fu_device_build_instance_id(device, error, "FLASHROM", "VENDOR", "PRODUCT", "REGION", NULL)) return NULL; /* add this so we can attach board-specific quirks */ fu_device_add_instance_str(FU_DEVICE(device), "GUID", guid); if (!fu_device_build_instance_id(FU_DEVICE(device), error, "FLASHROM", "GUID", NULL)) return NULL; /* use same VendorID logic as with UEFI */ dmi_vendor = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_BIOS_VENDOR); if (dmi_vendor != NULL) { g_autofree gchar *vendor_id = g_strdup_printf("DMI:%s", dmi_vendor); fu_device_add_vendor_id(FU_DEVICE(device), vendor_id); } fu_flashrom_plugin_device_set_version(plugin, device); fu_flashrom_plugin_device_set_hwids(plugin, device); if (!fu_flashrom_plugin_device_set_bios_info(plugin, device, &error_local)) g_warning("failed to set bios info: %s", error_local->message); if (!fu_device_setup(device, error)) return NULL; /* success */ fu_plugin_device_add(plugin, device); return g_steal_pointer(&device); } static void fu_flashrom_plugin_device_registered(FuPlugin *plugin, FuDevice *device) { g_autoptr(FuDevice) me_device = NULL; FuFlashromPlugin *self = FU_FLASHROM_PLUGIN(plugin); const gchar *me_region_str = fu_ifd_region_to_string(FU_IFD_REGION_ME); /* we're only interested in a device from intel-spi plugin that corresponds to ME * region of IFD */ if (g_strcmp0(fu_device_get_plugin(device), "intel_spi") != 0) return; if (g_strcmp0(fu_device_get_logical_id(device), me_region_str) != 0) return; me_device = fu_flashrom_plugin_add_device(plugin, self->guid, FU_IFD_REGION_ME, NULL); if (me_device == NULL) return; /* unlock operation requires device to be locked */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_LOCKED)) fu_device_add_flag(me_device, FWUPD_DEVICE_FLAG_LOCKED); } static gboolean fu_flashrom_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { FuFlashromPlugin *self = FU_FLASHROM_PLUGIN(plugin); g_autoptr(FuDevice) device = fu_flashrom_plugin_add_device(plugin, self->guid, FU_IFD_REGION_BIOS, error); return (device != NULL); } /* finds GUID that activated this plugin */ static const gchar * fu_flashrom_plugin_find_guid(FuPlugin *plugin, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); GPtrArray *hwids = fu_context_get_hwid_guids(ctx); for (guint i = 0; i < hwids->len; i++) { const gchar *guid = g_ptr_array_index(hwids, i); const gchar *plugin_name = fu_context_lookup_quirk_by_id(ctx, guid, FU_QUIRKS_PLUGIN); if (g_strcmp0(plugin_name, "flashrom") == 0) return guid; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no HwIDs found"); return NULL; } static gboolean fu_flashrom_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { gint rc; const gchar *guid; FuFlashromPlugin *self = FU_FLASHROM_PLUGIN(plugin); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 5, "find-guid"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 90, "init"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 5, "probe"); guid = fu_flashrom_plugin_find_guid(plugin, error); if (guid == NULL) return FALSE; fu_progress_step_done(progress); /* if changed */ if (g_strcmp0(self->guid, guid) != 0) { g_free(self->guid); self->guid = g_strdup(guid); } if (flashrom_init(SELFCHECK_TRUE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flashrom initialization error"); return FALSE; } flashrom_set_log_callback(fu_flashrom_plugin_debug_cb); fu_progress_step_done(progress); if (flashrom_programmer_init(&self->flashprog, "internal", NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "programmer initialization failed"); return FALSE; } rc = flashrom_flash_probe(&self->flashctx, self->flashprog, NULL); if (rc == 3) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flash probe failed: multiple chips were found"); return FALSE; } if (rc == 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flash probe failed: no chip was found"); return FALSE; } if (rc != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flash probe failed: unknown error"); return FALSE; } fu_progress_step_done(progress); return TRUE; } static gboolean fu_flashrom_plugin_unlock(FuPlugin *self, FuDevice *device, GError **error) { return fu_flashrom_device_unlock(FU_FLASHROM_DEVICE(device), error); } static void fu_flashrom_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); (void)fu_plugin_alloc_data(plugin, sizeof(FuFlashromPlugin)); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "linux_lockdown"); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_CONFLICTS, "coreboot"); /* obsoleted */ fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_REQUIRE_HWID); fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY); } static void fu_flashrom_plugin_finalize(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuFlashromPlugin *self = FU_FLASHROM_PLUGIN(plugin); if (self->flashctx != NULL) flashrom_flash_release(self->flashctx); if (self->flashprog != NULL) flashrom_programmer_shutdown(self->flashprog); g_free(self->guid); /* G_OBJECT_CLASS(fu_flashrom_plugin_parent_class)->finalize() not required as modular */ } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->constructed = fu_flashrom_plugin_constructed; vfuncs->finalize = fu_flashrom_plugin_finalize; vfuncs->to_string = fu_flashrom_plugin_to_string; vfuncs->device_registered = fu_flashrom_plugin_device_registered; vfuncs->startup = fu_flashrom_plugin_startup; vfuncs->coldplug = fu_flashrom_plugin_coldplug; vfuncs->unlock = fu_flashrom_plugin_unlock; } fwupd-1.9.16/plugins/flashrom/meson.build000066400000000000000000000012461460375044200204030ustar00rootroot00000000000000if get_option('plugin_flashrom').enabled() or \ (get_option('plugin_flashrom').auto() and libflashrom.found()) cargs = ['-DG_LOG_DOMAIN="FuPluginFlashrom"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('flashrom.quirk') shared_module('fu_plugin_flashrom', sources: [ 'fu-flashrom-device.c', 'fu-flashrom-plugin.c', 'fu-flashrom-cmos.c', ], include_directories: plugin_incdirs, install: true, install_rpath: libdir_pkg, install_dir: libdir_pkg, link_with: plugin_libs, c_args: [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], dependencies: [ plugin_deps, libflashrom, ], ) endif fwupd-1.9.16/plugins/focalfp/000077500000000000000000000000001460375044200160355ustar00rootroot00000000000000fwupd-1.9.16/plugins/focalfp/README.md000066400000000000000000000030061460375044200173130ustar00rootroot00000000000000--- title: Plugin: Focal TouchPad --- ## Introduction This plugin allows updating Touchpad devices from Focal. Devices are enumerated using HID . The I²C mode is used for firmware recovery. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * `tw.com.focalfp` ## GUID Generation These device uses the standard DeviceInstanceId values, e.g. * `HIDRAW\VEN_2808&DEV_0106` ## Update Behavior The device usually presents in HID mode, and the firmware is written to the device by switching to a IAP mode where the touchpad is nonfunctional. Once complete the device is reset to get out of IAP mode and to load the new firmware version. On flash failure the device is nonfunctional, but is recoverable by writing to the i2c device. This is typically much slower than updating the device using HID and also requires a model-specific HWID quirk to match. ## Vendor ID Security The vendor ID is set from the HID vendor, for example set to `HIDRAW:0x17EF` ## Quirk Use This plugin uses the following plugin-specific quirks: ## External Interface Access This plugin requires ioctl access to `HIDIOCSFEATURE` and `HIDIOCGFEATURE`. ## Version Considerations This plugin has been available since fwupd version `1.8.6`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Wayne Huang: @waynehuang2022 fwupd-1.9.16/plugins/focalfp/focalfp.quirk000066400000000000000000000001401460375044200205170ustar00rootroot00000000000000[HIDRAW\VEN_2808&DEV_0106] Plugin = focalfp GType = FuFocalfpHidDevice Flags = enforce-requires fwupd-1.9.16/plugins/focalfp/fu-focalfp-firmware.c000066400000000000000000000042171460375044200220410ustar00rootroot00000000000000/* * Copyright (C) 2022 Shihwei Huang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-focalfp-firmware.h" struct _FuFocalfpFirmware { FuFirmwareClass parent_instance; guint16 start_address; guint32 checksum; }; G_DEFINE_TYPE(FuFocalfpFirmware, fu_focalfp_firmware, FU_TYPE_FIRMWARE) /* firmware block update */ #define FOCAL_NAME_START_ADDR_WRDS 0x011E const guint8 focalfp_signature[] = {0xFF}; guint32 fu_focalfp_firmware_get_checksum(FuFocalfpFirmware *self) { g_return_val_if_fail(FU_IS_FOCALFP_FIRMWARE(self), 0); return self->checksum; } static void fu_focalfp_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuFocalfpFirmware *self = FU_FOCALFP_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "start_address", self->start_address); fu_xmlb_builder_insert_kx(bn, "checksum", self->checksum); } static gboolean fu_focalfp_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuFocalfpFirmware *self = FU_FOCALFP_FIRMWARE(firmware); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* start address */ if (!fu_memread_uint16_safe(buf, bufsz, FOCAL_NAME_START_ADDR_WRDS, &self->start_address, G_BIG_ENDIAN, error)) { return FALSE; } if (self->start_address != 0x582e) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "force pad address invalid: 0x%x", self->start_address); return FALSE; } /* calculate checksum */ for (guint32 i = 0; i < bufsz; i += 4) { guint32 value = 0; if (!fu_memread_uint32_safe(buf, bufsz, i, &value, G_LITTLE_ENDIAN, error)) return FALSE; self->checksum ^= value; } self->checksum += 1; /* success */ return TRUE; } static void fu_focalfp_firmware_init(FuFocalfpFirmware *self) { } static void fu_focalfp_firmware_class_init(FuFocalfpFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_focalfp_firmware_parse; klass_firmware->export = fu_focalfp_firmware_export; } fwupd-1.9.16/plugins/focalfp/fu-focalfp-firmware.h000066400000000000000000000006141460375044200220430ustar00rootroot00000000000000/* * Copyright (C) 2022 Shihwei Huang * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_FOCALFP_FIRMWARE (fu_focalfp_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuFocalfpFirmware, fu_focalfp_firmware, FU, FOCALFP_FIRMWARE, FuFirmware) guint32 fu_focalfp_firmware_get_checksum(FuFocalfpFirmware *self); fwupd-1.9.16/plugins/focalfp/fu-focalfp-hid-device.c000066400000000000000000000444231460375044200222310ustar00rootroot00000000000000/* * Copyright (C) 2022 Shihwei Huang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-focalfp-firmware.h" #include "fu-focalfp-hid-device.h" struct _FuFocalfpHidDevice { FuUdevDevice parent_instance; }; G_DEFINE_TYPE(FuFocalfpHidDevice, fu_focalfp_hid_device, FU_TYPE_UDEV_DEVICE) #define FU_FOCALFP_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ #define CMD_ENTER_UPGRADE_MODE 0x40 #define CMD_CHECK_CURRENT_STATE 0x41 #define CMD_READY_FOR_UPGRADE 0x42 #define CMD_SEND_DATA 0x43 #define CMD_UPGRADE_CHECKSUM 0x44 #define CMD_EXIT_UPGRADE_MODE 0x45 #define CMD_USB_READ_UPGRADE_ID 0x46 #define CMD_USB_ERASE_FLASH 0x47 #define CMD_USB_BOOT_READ 0x48 #define CMD_USB_BOOT_BOOTLOADERVERSION 0x49 #define CMD_READ_REGISTER 0x50 #define CMD_WRITE_REGISTER 0x51 #define CMD_ACK 0xf0 #define CMD_NACK 0xff #define FIRST_PACKET 0x00 #define MID_PACKET 0x01 #define END_PACKET 0x02 #define REPORT_SIZE 64 #define MAX_USB_PACKET_SIZE 56 static gboolean fu_focalfp_hid_device_detach(FuDevice *device, FuProgress *progress, GError **error); static gboolean fu_focalfp_hid_device_probe(FuDevice *device, GError **error) { /* check is valid */ if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "hidraw") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct subsystem=%s, expected hidraw", fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device))); return FALSE; } /* i2c-hid */ if (fu_udev_device_get_model(FU_UDEV_DEVICE(device)) != 0x0106) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not i2c-hid touchpad"); return FALSE; } /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); } static guint8 fu_focaltp_buffer_generate_checksum(const guint8 *buf, gsize bufsz) { guint8 checksum = 0; for (gsize i = 0; i < bufsz; i++) checksum ^= buf[i]; checksum++; return checksum; } static gboolean fu_focalfp_hid_device_io(FuFocalfpHidDevice *self, guint8 *wbuf, gsize wbufsz, guint8 *rbuf, gsize rbufsz, GError **error) { /* SetReport */ if (wbuf != NULL && wbufsz > 0) { guint8 buf[64] = {0x06, 0xff, 0xff}; guint8 cmdlen = 4 + wbufsz; buf[3] = cmdlen; if (!fu_memcpy_safe(buf, sizeof(buf), 0x04, wbuf, wbufsz, 0x00, wbufsz, error)) return FALSE; buf[cmdlen] = fu_focaltp_buffer_generate_checksum(&buf[1], cmdlen - 1); fu_dump_raw(G_LOG_DOMAIN, "SetReport", buf, sizeof(buf)); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(cmdlen + 1), buf, NULL, FU_FOCALFP_DEVICE_IOCTL_TIMEOUT, error)) { return FALSE; } } /* GetReport */ if (rbuf != NULL && rbufsz > 0) { guint8 buf[64] = {0x06}; if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGFEATURE(sizeof(buf)), buf, NULL, FU_FOCALFP_DEVICE_IOCTL_TIMEOUT, error)) { return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "GetReport", buf, sizeof(buf)); if (!fu_memcpy_safe(rbuf, rbufsz, 0x0, buf, sizeof(buf), 0x00, rbufsz, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_focalfp_buffer_check_cmd_crc(const guint8 *buf, gsize bufsz, guint8 cmd, GError **error) { guint8 csum = 0; guint8 csum_actual; /* check was correct response */ if (buf[4] != cmd) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got cmd 0x%02x, expected 0x%02x", buf[4], cmd); return FALSE; } /* check crc */ if (!fu_memread_uint8_safe(buf, bufsz, buf[3], &csum, error)) return FALSE; csum_actual = fu_focaltp_buffer_generate_checksum(buf + 1, buf[3] - 1); if (csum != csum_actual) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got checksum 0x%02x, expected 0x%02x", csum, csum_actual); return FALSE; } /* success */ return TRUE; } static gboolean fu_focalfp_hid_device_read_reg_cb(FuDevice *device, gpointer user_data, GError **error) { FuFocalfpHidDevice *self = FU_FOCALFP_HID_DEVICE(device); guint8 buf[64] = {0x0}; guint8 *val = (guint8 *)user_data; if (!fu_focalfp_hid_device_io(self, NULL, 0, buf, 8, error)) return FALSE; /* check was correct response */ if (!fu_focalfp_buffer_check_cmd_crc(buf, sizeof(buf), CMD_READ_REGISTER, error)) return FALSE; /* success */ *val = buf[6]; return TRUE; } static gboolean fu_focalfp_hid_device_read_reg(FuFocalfpHidDevice *self, guint8 reg_address, guint8 *val, /* out */ GError **error) { guint8 buf[64] = {CMD_READ_REGISTER, reg_address}; /* write */ if (!fu_focalfp_hid_device_io(self, buf, 2, NULL, 0, error)) return FALSE; /* read */ return fu_device_retry_full(FU_DEVICE(self), fu_focalfp_hid_device_read_reg_cb, 5, 1 /* ms */, val, error); } /* enter upgrade mode */ static gboolean fu_focalfp_hid_device_enter_upgrade_mode(FuFocalfpHidDevice *self, GError **error) { guint8 wbuf[64] = {CMD_ENTER_UPGRADE_MODE}; guint8 rbuf[64] = {0x0}; if (!fu_focalfp_hid_device_io(self, wbuf, 1, rbuf, 6, error)) { g_prefix_error(error, "failed to CMD_ENTER_UPGRADE_MODE: "); return FALSE; } /* check was correct response */ return fu_focalfp_buffer_check_cmd_crc(rbuf, sizeof(rbuf), CMD_ACK, error); } /* get bootloader current state */ static gboolean fu_focalfp_hid_device_check_current_state(FuFocalfpHidDevice *self, guint8 *val, GError **error) { guint8 wbuf[64] = {CMD_CHECK_CURRENT_STATE}; guint8 rbuf[64] = {0x0}; if (!fu_focalfp_hid_device_io(self, wbuf, 1, rbuf, 7, error)) return FALSE; /* check was correct response */ if (!fu_focalfp_buffer_check_cmd_crc(rbuf, sizeof(rbuf), CMD_CHECK_CURRENT_STATE, error)) return FALSE; /* success */ *val = rbuf[5]; return TRUE; } static gboolean fu_focalfp_hid_device_wait_for_upgrade_ready_cb(FuDevice *device, gpointer user_data, GError **error) { FuFocalfpHidDevice *self = FU_FOCALFP_HID_DEVICE(device); guint8 wbuf[64] = {CMD_READY_FOR_UPGRADE}; guint8 rbuf[64] = {0x0}; if (!fu_focalfp_hid_device_io(self, wbuf, 1, rbuf, 7, error)) return FALSE; /* check was correct response */ return fu_focalfp_buffer_check_cmd_crc(rbuf, sizeof(rbuf), CMD_READY_FOR_UPGRADE, error); } /* wait for ready */ static gboolean fu_focalfp_hid_device_wait_for_upgrade_ready(FuFocalfpHidDevice *self, guint retries, GError **error) { return fu_device_retry_full(FU_DEVICE(self), fu_focalfp_hid_device_wait_for_upgrade_ready_cb, retries, 500, NULL, error); } static gboolean fu_focalfp_hid_device_read_update_id_cb(FuDevice *device, gpointer user_data, GError **error) { FuFocalfpHidDevice *self = FU_FOCALFP_HID_DEVICE(device); guint16 *us_ic_id = (guint16 *)user_data; guint8 wbuf[64] = {CMD_USB_READ_UPGRADE_ID}; guint8 rbuf[64] = {0x0}; if (!fu_focalfp_hid_device_io(self, wbuf, 1, rbuf, 8, error)) return FALSE; /* check was correct response */ if (!fu_focalfp_buffer_check_cmd_crc(rbuf, sizeof(rbuf), CMD_USB_READ_UPGRADE_ID, error)) return FALSE; /* success */ *us_ic_id = fu_memread_uint16(rbuf + 5, G_BIG_ENDIAN); return TRUE; } /* get bootload id */ static gboolean fu_focalfp_hid_device_read_update_id(FuFocalfpHidDevice *self, guint16 *us_ic_id, GError **error) { return fu_device_retry_full(FU_DEVICE(self), fu_focalfp_hid_device_read_update_id_cb, 10, 1 /* ms */, us_ic_id, error); } /* erase flash */ static gboolean fu_focalfp_hid_device_erase_flash(FuFocalfpHidDevice *self, GError **error) { guint8 wbuf[64] = {CMD_USB_ERASE_FLASH}; guint8 rbuf[64] = {0x0}; if (!fu_focalfp_hid_device_io(self, wbuf, 1, rbuf, 6, error)) return FALSE; /* check was correct response */ return fu_focalfp_buffer_check_cmd_crc(rbuf, sizeof(rbuf), CMD_ACK, error); } static gboolean fu_focalfp_hid_device_send_data_cb(FuDevice *device, gpointer user_data, GError **error) { FuFocalfpHidDevice *self = FU_FOCALFP_HID_DEVICE(device); guint8 rbuf[64] = {0x0}; if (!fu_focalfp_hid_device_io(self, NULL, 0, rbuf, 7, error)) return FALSE; /* check was correct response */ return fu_focalfp_buffer_check_cmd_crc(rbuf, sizeof(rbuf), CMD_ACK, error); } /* send write data */ static gboolean fu_focalfp_hid_device_send_data(FuFocalfpHidDevice *self, guint8 packet_type, const guint8 *buf, guint8 bufsz, GError **error) { guint8 wbuf[64] = {CMD_SEND_DATA, packet_type}; /* sanity check */ if (bufsz > REPORT_SIZE - 8) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "data length 0x%x invalid", bufsz); return FALSE; } if (!fu_memcpy_safe(wbuf, sizeof(wbuf), 0x02, buf, bufsz, 0x00, bufsz, error)) return FALSE; if (!fu_focalfp_hid_device_io(self, wbuf, bufsz + 2, NULL, 0, error)) return FALSE; return fu_device_retry_full(FU_DEVICE(self), fu_focalfp_hid_device_send_data_cb, 4, 1 /* ms */, NULL, error); } /* get checksum for write done */ static gboolean fu_focalfp_hid_device_checksum_upgrade(FuFocalfpHidDevice *self, guint32 *val, GError **error) { guint8 wbuf[64] = {CMD_UPGRADE_CHECKSUM}; guint8 rbuf[64] = {0x0}; if (!fu_focalfp_hid_device_io(self, wbuf, 1, rbuf, 7 + 3, error)) return FALSE; /* check was correct response */ if (!fu_focalfp_buffer_check_cmd_crc(rbuf, sizeof(rbuf), CMD_UPGRADE_CHECKSUM, error)) return FALSE; /* success */ return fu_memread_uint32_safe(rbuf, sizeof(rbuf), 0x05, val, G_LITTLE_ENDIAN, error); } static gboolean fu_focalfp_hid_device_setup(FuDevice *device, GError **error) { FuFocalfpHidDevice *self = FU_FOCALFP_HID_DEVICE(device); guint8 buf[2] = {0x0}; /* get current firmware version */ if (!fu_focalfp_hid_device_read_reg(self, 0xA6, buf, error)) { g_prefix_error(error, "failed to read version1: "); return FALSE; } if (!fu_focalfp_hid_device_read_reg(self, 0xAD, buf + 1, error)) { g_prefix_error(error, "failed to read version2: "); return FALSE; } fu_device_set_version_u16(device, fu_memread_uint16(buf, G_BIG_ENDIAN)); /* success */ return TRUE; } static gboolean fu_focalfp_hid_device_write_chunks(FuFocalfpHidDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); guint8 uc_packet_type = MID_PACKET; if (i == 0) uc_packet_type = FIRST_PACKET; else if (i == fu_chunk_array_length(chunks) - 1) uc_packet_type = END_PACKET; if (!fu_focalfp_hid_device_send_data(self, uc_packet_type, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to write chunk %u: ", i); return FALSE; } if (!fu_focalfp_hid_device_wait_for_upgrade_ready(self, 100, error)) { g_prefix_error(error, "failed to wait for chunk %u: ", i); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_focalfp_hid_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuFocalfpHidDevice *self = FU_FOCALFP_HID_DEVICE(device); const guint32 UPGRADE_ID = 0x582E; guint16 us_ic_id = 0; guint32 checksum = 0; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 89, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 89, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "reset"); /* simple image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* check chip id and erase flash */ if (!fu_focalfp_hid_device_wait_for_upgrade_ready(self, 6, error)) return FALSE; if (!fu_focalfp_hid_device_read_update_id(self, &us_ic_id, error)) return FALSE; if (us_ic_id != UPGRADE_ID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got us_ic_id 0x%02x, expected 0x%02x", us_ic_id, (guint)UPGRADE_ID); return FALSE; } if (!fu_focalfp_hid_device_erase_flash(self, error)) return FALSE; fu_device_sleep(device, 1000); if (!fu_focalfp_hid_device_wait_for_upgrade_ready(self, 20, error)) return FALSE; fu_progress_step_done(progress); /* send packet data */ chunks = fu_chunk_array_new_from_bytes(fw, 0x0, MAX_USB_PACKET_SIZE); if (!fu_focalfp_hid_device_write_chunks(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* write flash end and check ready (fw calculate checksum) */ fu_device_sleep(device, 50); if (!fu_focalfp_hid_device_wait_for_upgrade_ready(self, 5, error)) return FALSE; fu_progress_step_done(progress); /* verify checksum */ if (!fu_focalfp_hid_device_checksum_upgrade(self, &checksum, error)) return FALSE; if (checksum != fu_focalfp_firmware_get_checksum(FU_FOCALFP_FIRMWARE(firmware))) { fu_device_sleep(device, 500); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "device checksum invalid, got 0x%02x, expected 0x%02x", checksum, fu_focalfp_firmware_get_checksum(FU_FOCALFP_FIRMWARE(firmware))); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } /* called after attach, but only when the firmware has been updated */ static gboolean fu_focalfp_hid_device_reload(FuDevice *device, GError **error) { FuFocalfpHidDevice *self = FU_FOCALFP_HID_DEVICE(device); guint8 idbuf[2] = {0x0}; fu_device_sleep(device, 500); if (!fu_focalfp_hid_device_read_reg(self, 0x9F, &idbuf[0], error)) return FALSE; if (!fu_focalfp_hid_device_read_reg(self, 0xA3, &idbuf[1], error)) return FALSE; g_debug("id1=%x, id2=%x", idbuf[1], idbuf[0]); if (idbuf[1] != 0x58 && idbuf[0] != 0x22) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware id invalid, got 0x%02x:0x%02x, expected 0x%02x:0x%02x", idbuf[1], idbuf[0], (guint)0x58, (guint)0x22); return FALSE; } return fu_focalfp_hid_device_setup(device, error); } static gboolean fu_focalfp_hid_device_detach_cb(FuDevice *device, gpointer user_data, GError **error) { FuFocalfpHidDevice *self = FU_FOCALFP_HID_DEVICE(device); guint8 uc_mode = 0; if (!fu_focalfp_hid_device_enter_upgrade_mode(self, error)) { g_prefix_error(error, "failed to enter upgrade mode: "); return FALSE; } /* get current state */ if (!fu_focalfp_hid_device_check_current_state(self, &uc_mode, error)) return FALSE; /* 1: upgrade mode; 2: fw mode */ if (uc_mode != 1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got uc_mode 0x%02x, expected 0x%02x", uc_mode, (guint)1); return FALSE; } /* success */ return TRUE; } /* enter upgrade mode */ static gboolean fu_focalfp_hid_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuFocalfpHidDevice *self = FU_FOCALFP_HID_DEVICE(device); guint8 wbuf[64] = {CMD_ENTER_UPGRADE_MODE}; guint8 rbuf[64] = {0x0}; /* command to go from APP --> Bootloader -- but we do not check crc */ if (!fu_focalfp_hid_device_io(self, wbuf, 1, rbuf, 6, error)) { g_prefix_error(error, "failed to CMD_ENTER_UPGRADE_MODE: "); return FALSE; } fu_device_sleep(device, 200); /* second command : bootloader normal mode --> bootloader upgrade mode */ if (!fu_device_retry_full(device, fu_focalfp_hid_device_detach_cb, 3, 200 /* ms */, progress, error)) return FALSE; /* success */ fu_device_sleep(device, 200); return TRUE; } /* exit upgrade mode */ static gboolean fu_focalfp_hid_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuFocalfpHidDevice *self = FU_FOCALFP_HID_DEVICE(device); guint8 wbuf[64] = {CMD_EXIT_UPGRADE_MODE}; guint8 rbuf[64] = {0x0}; if (!fu_focalfp_hid_device_io(self, wbuf, 1, rbuf, 6, error)) return FALSE; /* check was correct response */ if (!fu_focalfp_buffer_check_cmd_crc(rbuf, sizeof(rbuf), CMD_ACK, error)) return FALSE; /* success */ fu_device_sleep(device, 500); return TRUE; } static void fu_focalfp_hid_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_focalfp_hid_device_init(FuFocalfpHidDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_firmware_size(FU_DEVICE(self), 0x1E000); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_FOCALFP_FIRMWARE); fu_device_set_summary(FU_DEVICE(self), "Forcepad"); fu_device_add_icon(FU_DEVICE(self), "input-touchpad"); fu_device_add_protocol(FU_DEVICE(self), "tw.com.focalfp"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_HEX); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | FU_UDEV_DEVICE_FLAG_OPEN_NONBLOCK); } static void fu_focalfp_hid_device_class_init(FuFocalfpHidDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->attach = fu_focalfp_hid_device_attach; klass_device->detach = fu_focalfp_hid_device_detach; klass_device->setup = fu_focalfp_hid_device_setup; klass_device->reload = fu_focalfp_hid_device_reload; klass_device->write_firmware = fu_focalfp_hid_device_write_firmware; klass_device->probe = fu_focalfp_hid_device_probe; klass_device->set_progress = fu_focalfp_hid_device_set_progress; } fwupd-1.9.16/plugins/focalfp/fu-focalfp-hid-device.h000066400000000000000000000005571460375044200222360ustar00rootroot00000000000000/* * Copyright (C) 2022 Shihwei Huang * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_FOCALFP_HID_DEVICE (fu_focalfp_hid_device_get_type()) G_DECLARE_FINAL_TYPE(FuFocalfpHidDevice, fu_focalfp_hid_device, FU, FOCALFP_HID_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/focalfp/fu-focalfp-plugin.c000066400000000000000000000016121460375044200215170ustar00rootroot00000000000000/* * Copyright (C) 2022 Shihwei Huang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-focalfp-firmware.h" #include "fu-focalfp-hid-device.h" #include "fu-focalfp-plugin.h" struct _FuFocalfpPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuFocalfpPlugin, fu_focalfp_plugin, FU_TYPE_PLUGIN) static void fu_focalfp_plugin_init(FuFocalfpPlugin *self) { } static void fu_focalfp_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "hidraw"); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_FOCALFP_FIRMWARE); fu_plugin_add_device_gtype(plugin, FU_TYPE_FOCALFP_HID_DEVICE); } static void fu_focalfp_plugin_class_init(FuFocalfpPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_focalfp_plugin_constructed; } fwupd-1.9.16/plugins/focalfp/fu-focalfp-plugin.h000066400000000000000000000003531460375044200215250ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuFocalfpPlugin, fu_focalfp_plugin, FU, FOCALFP_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/focalfp/meson.build000066400000000000000000000007061460375044200202020ustar00rootroot00000000000000if gudev.found() cargs = ['-DG_LOG_DOMAIN="FuPluginFocalfp"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('focalfp.quirk') plugin_builtins += static_library('fu_plugin_focalfp', sources: [ 'fu-focalfp-plugin.c', 'fu-focalfp-firmware.c', 'fu-focalfp-hid-device.c', ], include_directories: plugin_incdirs, c_args: [ cargs, ], link_with: plugin_libs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/fpc/000077500000000000000000000000001460375044200151735ustar00rootroot00000000000000fwupd-1.9.16/plugins/fpc/README.md000066400000000000000000000020541460375044200164530ustar00rootroot00000000000000--- title: Plugin: FPC Fingerprint Sensor --- ## Introduction The plugin used for update firmware for fingerprint sensors from FPC. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `com.fingerprints.dfupc` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_10A5&PID_FFE0` ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x10A5` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.8.6`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Jim Zhang: @jimzhang2 fwupd-1.9.16/plugins/fpc/fpc.quirk000066400000000000000000000003671460375044200170260ustar00rootroot00000000000000# FPC Fingerprint sensor [USB\VID_10A5&PID_FFE0] Plugin = fpc Flags = enforce-requires [USB\VID_10A5&PID_FFE1] Plugin = fpc Flags = moh-device,rts,enforce-requires [USB\VID_10A5&PID_9800] Plugin = fpc Flags = moh-device,rts,lenfy,enforce-requires fwupd-1.9.16/plugins/fpc/fu-fpc-device.c000066400000000000000000000365331460375044200177660ustar00rootroot00000000000000/* * Copyright (C) 2022 Haowei Lo * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-fpc-device.h" #include "fu-fpc-struct.h" #define FPC_USB_INTERFACE 0 #define FPC_USB_TRANSFER_TIMEOUT 1500 /* ms */ #define FPC_FLASH_BLOCK_SIZE_DEFAULT 2048 /* 2048 */ #define FPC_FLASH_BLOCK_SIZE_4096 4096 /* 4096 */ #define FPC_CMD_DFU_DETACH 0x00 #define FPC_CMD_DFU_DNLOAD 0x01 #define FPC_CMD_DFU_GETSTATUS 0x03 #define FPC_CMD_DFU_CLRSTATUS 0x04 #define FPC_CMD_DFU_GET_FW_STATUS 0x09 #define FPC_CMD_BOOT0 0x04 #define FPC_CMD_GET_STATE 0x0B #define FPC_CMD_GET_STATE_LENFY 0x50 #define FPC_DEVICE_MOC_STATE_LEN 68 #define FPC_DEVICE_MOH_STATE_LEN 72 #define FPC_DEVICE_DFU_FW_STATUS_LEN 8 #define FPC_DFU_MAX_ATTEMPTS 50 #define FPC_DEVICE_DFU_MODE_CLASS 0xFE #define FPC_DEVICE_DFU_MODE_PORT 0x02 #define FPC_DEVICE_NORMAL_MODE_CLASS 0xFF #define FPC_DEVICE_NORMAL_MODE_PORT 0xFF /** * FU_FPC_DEVICE_FLAG_MOH_DEVICE: * * Device is a moh device */ #define FU_FPC_DEVICE_FLAG_MOH_DEVICE (1 << 0) /** * FU_FPC_DEVICE_FLAG_LEGACY_DFU: * * Device supports legacy dfu mode */ #define FU_FPC_DEVICE_FLAG_LEGACY_DFU (1 << 1) /** * FU_FPC_DEVICE_FLAG_RTS_DEVICE: * * Device is a RTS device */ #define FU_FPC_DEVICE_FLAG_RTS_DEVICE (1 << 2) /** * FU_FPC_DEVICE_FLAG_LENFY_DEVICE: * * Device is a LENFY MOH device */ #define FU_FPC_DEVICE_FLAG_LENFY_DEVICE (1 << 3) struct _FuFpcDevice { FuUsbDevice parent_instance; guint32 max_block_size; }; G_DEFINE_TYPE(FuFpcDevice, fu_fpc_device, FU_TYPE_USB_DEVICE) static gboolean fu_fpc_device_dfu_cmd(FuFpcDevice *self, guint8 request, guint16 value, guint8 *data, gsize length, gboolean device2host, gboolean type_vendor, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_len = 0; if (data == NULL && length > 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid input data"); return FALSE; } if (!g_usb_device_control_transfer(usb_device, device2host ? G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST : G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, type_vendor ? G_USB_DEVICE_REQUEST_TYPE_VENDOR : G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, request, value, 0x0000, data, length, length ? &actual_len : NULL, FPC_USB_TRANSFER_TIMEOUT, NULL, error)) return FALSE; if (actual_len != length) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only sent 0x%04x of 0x%04x", (guint)actual_len, (guint)length); return FALSE; } return TRUE; } static gboolean fu_fpc_device_fw_cmd(FuFpcDevice *self, guint8 request, guint8 *data, gsize length, gboolean device2host, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_len = 0; if (data == NULL && length > 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid input data"); return FALSE; } if (!g_usb_device_control_transfer(usb_device, device2host ? G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST : G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, request, 0x0000, 0x0000, data, length, length ? &actual_len : NULL, FPC_USB_TRANSFER_TIMEOUT, NULL, error)) return FALSE; if (actual_len != length) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only sent 0x%04x of 0x%04x", (guint)actual_len, (guint)length); return FALSE; } return TRUE; } static gboolean fu_fpc_device_setup_mode(FuFpcDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); g_autoptr(GPtrArray) intfs = NULL; intfs = g_usb_device_get_interfaces(usb_device, error); if (intfs == NULL) return FALSE; for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index(intfs, i); if (g_usb_interface_get_class(intf) == FPC_DEVICE_DFU_MODE_CLASS && g_usb_interface_get_protocol(intf) == FPC_DEVICE_DFU_MODE_PORT) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } if (g_usb_interface_get_class(intf) == FPC_DEVICE_NORMAL_MODE_CLASS && g_usb_interface_get_protocol(intf) == FPC_DEVICE_NORMAL_MODE_PORT) { fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no update interface found"); return FALSE; } static gboolean fu_fpc_device_setup_version(FuFpcDevice *self, GError **error) { guint32 version = 0; gsize data_len = 0; FuEndianType endian_type = G_LITTLE_ENDIAN; g_autofree guint8 *data = NULL; guint32 cmd_id = FPC_CMD_GET_STATE; if (fu_device_has_private_flag(FU_DEVICE(self), FU_FPC_DEVICE_FLAG_RTS_DEVICE)) endian_type = G_BIG_ENDIAN; if (!fu_device_has_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { if (fu_device_has_private_flag(FU_DEVICE(self), FU_FPC_DEVICE_FLAG_MOH_DEVICE)) { data_len = FPC_DEVICE_MOH_STATE_LEN; } else { data_len = FPC_DEVICE_MOC_STATE_LEN; } data = g_malloc0(data_len); if (fu_device_has_private_flag(FU_DEVICE(self), FU_FPC_DEVICE_FLAG_LENFY_DEVICE)) cmd_id = FPC_CMD_GET_STATE_LENFY; if (!fu_fpc_device_fw_cmd(self, cmd_id, data, data_len, TRUE, error)) return FALSE; if (!fu_memread_uint32_safe(data, data_len, 0, &version, endian_type, error)) return FALSE; } else { if (!fu_device_has_private_flag(FU_DEVICE(self), FU_FPC_DEVICE_FLAG_LEGACY_DFU)) { if (!fu_fpc_device_dfu_cmd(self, FPC_CMD_DFU_CLRSTATUS, 0x0000, NULL, 0, FALSE, FALSE, error)) { g_prefix_error(error, "fail to clear status in setup version"); return FALSE; } } data = g_malloc0(FPC_DEVICE_DFU_FW_STATUS_LEN); if (!fu_fpc_device_dfu_cmd(self, FPC_CMD_DFU_GET_FW_STATUS, 0x0000, data, FPC_DEVICE_DFU_FW_STATUS_LEN, TRUE, TRUE, error)) { g_prefix_error(error, "fail to get fw status in setup version"); return FALSE; } if (!fu_memread_uint32_safe(data, FPC_DEVICE_DFU_FW_STATUS_LEN, 4, &version, endian_type, error)) return FALSE; } /* set display version */ fu_device_set_version_u32(FU_DEVICE(self), version); return TRUE; } static gboolean fu_fpc_device_check_dfu_status_cb(FuDevice *device, gpointer user_data, GError **error) { FuFpcDevice *self = FU_FPC_DEVICE(device); g_autoptr(GByteArray) dfu_status = fu_struct_fpc_dfu_new(); if (!fu_fpc_device_dfu_cmd(self, FPC_CMD_DFU_GETSTATUS, 0x0000, dfu_status->data, dfu_status->len, TRUE, FALSE, error)) { g_prefix_error(error, "failed to get status: "); return FALSE; } if (fu_struct_fpc_dfu_get_status(dfu_status) != 0 || fu_struct_fpc_dfu_get_state(dfu_status) == FU_FPC_DFU_STATE_DNBUSY) { /* device is not in correct status/state */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "dfu status error [0x%x, 0x%x]", fu_struct_fpc_dfu_get_status(dfu_status), fu_struct_fpc_dfu_get_state(dfu_status)); return FALSE; } if (fu_struct_fpc_dfu_get_max_payload_size(dfu_status) > 0 || fu_device_has_private_flag(FU_DEVICE(self), FU_FPC_DEVICE_FLAG_RTS_DEVICE)) self->max_block_size = FPC_FLASH_BLOCK_SIZE_4096; else self->max_block_size = FPC_FLASH_BLOCK_SIZE_DEFAULT; return TRUE; } static gboolean fu_fpc_device_update_init(FuFpcDevice *self, GError **error) { if (!fu_device_has_private_flag(FU_DEVICE(self), FU_FPC_DEVICE_FLAG_LEGACY_DFU)) { if (!fu_fpc_device_dfu_cmd(self, FPC_CMD_DFU_CLRSTATUS, 0x0000, NULL, 0, FALSE, FALSE, error)) { g_prefix_error(error, "failed to clear status: "); return FALSE; } } return fu_device_retry_full(FU_DEVICE(self), fu_fpc_device_check_dfu_status_cb, FPC_DFU_MAX_ATTEMPTS, 20, NULL, error); } static void fu_fpc_device_to_string(FuDevice *device, guint idt, GString *str) { FuFpcDevice *self = FU_FPC_DEVICE(device); fu_string_append_kx(str, idt, "MaxBlockSize", self->max_block_size); fu_string_append_kb(str, idt, "LegacyDfu", fu_device_has_private_flag(device, FU_FPC_DEVICE_FLAG_LEGACY_DFU)); fu_string_append_kb(str, idt, "MocDevice", !fu_device_has_private_flag(device, FU_FPC_DEVICE_FLAG_MOH_DEVICE)); if (fu_device_has_private_flag(device, FU_FPC_DEVICE_FLAG_MOH_DEVICE)) { fu_string_append_kb( str, idt, "RtsDevice", fu_device_has_private_flag(device, FU_FPC_DEVICE_FLAG_RTS_DEVICE)); } } static gboolean fu_fpc_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuFpcDevice *self = FU_FPC_DEVICE(device); /* sanity check */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } if (!fu_fpc_device_dfu_cmd(self, FPC_CMD_DFU_DETACH, 0x0000, NULL, 0, FALSE, FALSE, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_fpc_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuFpcDevice *self = FU_FPC_DEVICE(device); /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in bootloader mode, skipping"); return TRUE; } if (!fu_fpc_device_fw_cmd(self, FPC_CMD_BOOT0, NULL, 0, FALSE, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_fpc_device_setup(FuDevice *device, GError **error) { FuFpcDevice *self = FU_FPC_DEVICE(device); g_autofree gchar *name = NULL; /* setup */ if (!FU_DEVICE_CLASS(fu_fpc_device_parent_class)->setup(device, error)) return FALSE; /* remove the ' L:0001 FW:27.26.23.18' suffix */ name = g_strdup(fu_device_get_name(device)); if (name != NULL) { gchar *tmp = g_strstr_len(name, -1, " L:00"); if (tmp != NULL) *tmp = '\0'; fu_device_set_name(device, name); } if (!fu_fpc_device_setup_mode(self, error)) { g_prefix_error(error, "failed to get device mode: "); return FALSE; } /* ensure version */ if (!fu_fpc_device_setup_version(self, error)) { g_prefix_error(error, "failed to get firmware version: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_fpc_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuFpcDevice *self = FU_FPC_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "init"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 95, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "check"); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* don't auto-boot firmware */ if (!fu_fpc_device_update_init(self, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to initial update: %s", error_local->message); return FALSE; } fu_progress_step_done(progress); /* build packets */ chunks = fu_chunk_array_new_from_bytes(fw, 0x00, self->max_block_size); /* write each block */ for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GByteArray) req = g_byte_array_new(); g_byte_array_append(req, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); if (!fu_fpc_device_dfu_cmd(self, FPC_CMD_DFU_DNLOAD, (guint16)i, req->data, (gsize)req->len, FALSE, FALSE, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to write: %s", error_local->message); return FALSE; } if (!fu_device_retry_full(device, fu_fpc_device_check_dfu_status_cb, FPC_DFU_MAX_ATTEMPTS, 20, NULL, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to write: %s", error_local->message); return FALSE; } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } if (!fu_device_has_private_flag(FU_DEVICE(self), FU_FPC_DEVICE_FLAG_LEGACY_DFU)) { /* exit fw download loop. send null package */ if (!fu_fpc_device_dfu_cmd(self, FPC_CMD_DFU_DNLOAD, 0, NULL, 0, FALSE, FALSE, error)) { g_prefix_error(error, "fail to exit dnload loop: "); return FALSE; } } fu_progress_step_done(progress); if (!fu_device_retry_full(device, fu_fpc_device_check_dfu_status_cb, FPC_DFU_MAX_ATTEMPTS, 20, NULL, error)) return FALSE; fu_progress_step_done(progress); /* success! */ return TRUE; } static void fu_fpc_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_fpc_device_init(FuFpcDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_set_remove_delay(FU_DEVICE(self), 10000); fu_device_add_protocol(FU_DEVICE(self), "com.fingerprints.dfupc"); fu_device_set_summary(FU_DEVICE(self), "FPC fingerprint sensor"); fu_device_set_install_duration(FU_DEVICE(self), 15); fu_device_set_firmware_size_min(FU_DEVICE(self), 0x10000); fu_device_set_firmware_size_max(FU_DEVICE(self), 0x64000); fu_usb_device_add_interface(FU_USB_DEVICE(self), FPC_USB_INTERFACE); fu_device_register_private_flag(FU_DEVICE(self), FU_FPC_DEVICE_FLAG_MOH_DEVICE, "moh-device"); fu_device_register_private_flag(FU_DEVICE(self), FU_FPC_DEVICE_FLAG_RTS_DEVICE, "rts"); fu_device_register_private_flag(FU_DEVICE(self), FU_FPC_DEVICE_FLAG_LEGACY_DFU, "legacy-dfu"); fu_device_register_private_flag(FU_DEVICE(self), FU_FPC_DEVICE_FLAG_LENFY_DEVICE, "lenfy"); } static void fu_fpc_device_class_init(FuFpcDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_fpc_device_to_string; klass_device->write_firmware = fu_fpc_device_write_firmware; klass_device->setup = fu_fpc_device_setup; klass_device->reload = fu_fpc_device_setup; klass_device->attach = fu_fpc_device_attach; klass_device->detach = fu_fpc_device_detach; klass_device->set_progress = fu_fpc_device_set_progress; } fwupd-1.9.16/plugins/fpc/fu-fpc-device.h000066400000000000000000000004321460375044200177600ustar00rootroot00000000000000/* * Copyright (C) 2022 Haowei Lo * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_FPC_DEVICE (fu_fpc_device_get_type()) G_DECLARE_FINAL_TYPE(FuFpcDevice, fu_fpc_device, FU, FPC_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/fpc/fu-fpc-plugin.c000066400000000000000000000012321460375044200200110ustar00rootroot00000000000000/* * Copyright (C) 2022 Haowei Lo * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-fpc-device.h" #include "fu-fpc-plugin.h" struct _FuFpcPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuFpcPlugin, fu_fpc_plugin, FU_TYPE_PLUGIN) static void fu_fpc_plugin_init(FuFpcPlugin *self) { } static void fu_fpc_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_FPC_DEVICE); } static void fu_fpc_plugin_class_init(FuFpcPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_fpc_constructed; } fwupd-1.9.16/plugins/fpc/fu-fpc-plugin.h000066400000000000000000000003371460375044200200230ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuFpcPlugin, fu_fpc_plugin, FU, FPC_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/fpc/fu-fpc.rs000066400000000000000000000004571460375044200167270ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[repr(u8)] enum FpcDfuState { Dnbusy = 0x04, } #[derive(New, Getters)] struct FpcDfu { status: u8, max_payload_size: u8, _reserved: [u8; 2], state: FpcDfuState, _reserved2: u8, } fwupd-1.9.16/plugins/fpc/meson.build000066400000000000000000000007061460375044200173400ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginFpc"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += join_paths(meson.current_source_dir(), 'fpc.quirk') plugin_builtins += static_library('fu_plugin_fpc', rustgen.process('fu-fpc.rs'), sources: [ 'fu-fpc-device.c', 'fu-fpc-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/fresco-pd/000077500000000000000000000000001460375044200163055ustar00rootroot00000000000000fwupd-1.9.16/plugins/fresco-pd/README.md000066400000000000000000000016641460375044200175730ustar00rootroot00000000000000--- title: Plugin: Fresco PD --- ## Introduction This plugin is used to update Power Delivery devices by Fresco. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary format. This plugin supports the following protocol ID: * `com.frescologic.pd` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_1D5C&PID_7102` These devices also use custom GUID values, e.g. * `USB\VID_1D5C&PID_7102&CID_01` ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x1D5C` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.3.8`. fwupd-1.9.16/plugins/fresco-pd/fresco-pd.quirk000066400000000000000000000001511460375044200212410ustar00rootroot00000000000000# FL7102 [USB\VID_1D5C&PID_7102] Plugin = fresco_pd # FL7112 [USB\VID_1D5C&PID_7112] Plugin = fresco_pd fwupd-1.9.16/plugins/fresco-pd/fu-fresco-pd-common.c000066400000000000000000000006671460375044200222420ustar00rootroot00000000000000/* * Copyright (C) 2020 Fresco Logic * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-fresco-pd-common.h" gchar * fu_fresco_pd_version_from_buf(const guint8 ver[4]) { if (ver[3] == 1 || ver[3] == 2) return g_strdup_printf("%u.%u.%u.%u", ver[0], ver[1], ver[2], ver[3]); return g_strdup_printf("%u.%u.%u.%u", ver[3], ver[1], ver[2], ver[0]); } fwupd-1.9.16/plugins/fresco-pd/fu-fresco-pd-common.h000066400000000000000000000003631460375044200222400ustar00rootroot00000000000000/* * Copyright (C) 2020 Fresco Logic * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gchar * fu_fresco_pd_version_from_buf(const guint8 ver[4]); fwupd-1.9.16/plugins/fresco-pd/fu-fresco-pd-device.c000066400000000000000000000320331460375044200222010ustar00rootroot00000000000000/* * Copyright (C) 2020 Fresco Logic * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-fresco-pd-common.h" #include "fu-fresco-pd-device.h" #include "fu-fresco-pd-firmware.h" struct _FuFrescoPdDevice { FuUsbDevice parent_instance; guint8 customer_id; }; G_DEFINE_TYPE(FuFrescoPdDevice, fu_fresco_pd_device, FU_TYPE_USB_DEVICE) static void fu_fresco_pd_device_to_string(FuDevice *device, guint idt, GString *str) { FuFrescoPdDevice *self = FU_FRESCO_PD_DEVICE(device); fu_string_append_ku(str, idt, "CustomerID", self->customer_id); } static gboolean fu_fresco_pd_device_transfer_read(FuFrescoPdDevice *self, guint16 offset, guint8 *buf, guint16 bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length = 0; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(bufsz != 0, FALSE); /* to device */ fu_dump_raw(G_LOG_DOMAIN, "read", buf, bufsz); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0x40, 0x0, offset, buf, bufsz, &actual_length, 5000, NULL, error)) { g_prefix_error(error, "failed to read from offset 0x%x: ", offset); return FALSE; } if (bufsz != actual_length) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "read 0x%x bytes of 0x%x", (guint)actual_length, bufsz); return FALSE; } /* success */ return TRUE; } static gboolean fu_fresco_pd_device_transfer_write(FuFrescoPdDevice *self, guint16 offset, guint8 *buf, guint16 bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length = 0; g_return_val_if_fail(buf != NULL, FALSE); g_return_val_if_fail(bufsz != 0, FALSE); /* to device */ fu_dump_raw(G_LOG_DOMAIN, "write", buf, bufsz); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0x41, 0x0, offset, buf, bufsz, &actual_length, 5000, NULL, error)) { g_prefix_error(error, "failed to write offset 0x%x: ", offset); return FALSE; } if (bufsz != actual_length) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "wrote 0x%x bytes of 0x%x", (guint)actual_length, bufsz); return FALSE; } /* success */ return TRUE; } static gboolean fu_fresco_pd_device_read_byte(FuFrescoPdDevice *self, guint16 offset, guint8 *buf, GError **error) { return fu_fresco_pd_device_transfer_read(self, offset, buf, 1, error); } static gboolean fu_fresco_pd_device_write_byte(FuFrescoPdDevice *self, guint16 offset, guint8 buf, GError **error) { return fu_fresco_pd_device_transfer_write(self, offset, &buf, 1, error); } static gboolean fu_fresco_pd_device_set_byte(FuFrescoPdDevice *self, guint16 offset, guint8 val, GError **error) { guint8 buf = 0x0; if (!fu_fresco_pd_device_read_byte(self, offset, &buf, error)) return FALSE; if (buf == val) return TRUE; return fu_fresco_pd_device_write_byte(self, offset, val, error); } static gboolean fu_fresco_pd_device_and_byte(FuFrescoPdDevice *self, guint16 offset, guint8 val, GError **error) { guint8 buf = 0xff; if (!fu_fresco_pd_device_read_byte(self, offset, &buf, error)) return FALSE; buf &= val; return fu_fresco_pd_device_write_byte(self, offset, buf, error); } static gboolean fu_fresco_pd_device_or_byte(FuFrescoPdDevice *self, guint16 offset, guint8 val, GError **error) { guint8 buf; if (!fu_fresco_pd_device_read_byte(self, offset, &buf, error)) return FALSE; buf |= val; return fu_fresco_pd_device_write_byte(self, offset, buf, error); } static gboolean fu_fresco_pd_device_setup(FuDevice *device, GError **error) { FuFrescoPdDevice *self = FU_FRESCO_PD_DEVICE(device); guint8 ver[4] = {0x0}; g_autofree gchar *version = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_fresco_pd_device_parent_class)->setup(device, error)) return FALSE; /* read existing device version */ for (guint i = 0; i < 4; i++) { if (!fu_fresco_pd_device_transfer_read(self, 0x3000 + i, &ver[i], 1, error)) { g_prefix_error(error, "failed to read device version [%u]: ", i); return FALSE; } } version = fu_fresco_pd_version_from_buf(ver); fu_device_set_version(FU_DEVICE(self), version); /* get customer ID */ self->customer_id = ver[1]; /* add extra instance ID */ fu_device_add_instance_u8(device, "CID", self->customer_id); return fu_device_build_instance_id(device, error, "USB", "VID", "PID", "CID", NULL); } static FuFirmware * fu_fresco_pd_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuFrescoPdDevice *self = FU_FRESCO_PD_DEVICE(device); guint8 customer_id; g_autoptr(FuFirmware) firmware = fu_fresco_pd_firmware_new(); /* check firmware is suitable */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; customer_id = fu_fresco_pd_firmware_get_customer_id(FU_FRESCO_PD_FIRMWARE(firmware)); if (customer_id != self->customer_id) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "device is incompatible with firmware x.%u.x.x", customer_id); return NULL; } return g_steal_pointer(&firmware); } static gboolean fu_fresco_pd_device_panther_reset_device(FuFrescoPdDevice *self, GError **error) { g_autoptr(GError) error_local = NULL; g_debug("resetting target device"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* ignore when the device reset before completing the transaction */ if (!fu_fresco_pd_device_or_byte(self, 0xA003, 1 << 3, &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED)) { g_debug("ignoring %s", error_local->message); return TRUE; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to reset device: "); return FALSE; } return TRUE; } static gboolean fu_fresco_pd_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuFrescoPdDevice *self = FU_FRESCO_PD_DEVICE(device); const guint8 *buf; gsize bufsz = 0x0; guint16 begin_addr = 0x6420; guint8 config[3] = {0x0}; guint8 start_symbols[2] = {0x0}; g_autoptr(GBytes) fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "enable-mtp-write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, "copy-mmio"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 46, "customize"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "boot"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, NULL); /* get default blob, which we know is already bigger than FirmwareMin */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; buf = g_bytes_get_data(fw, &bufsz); /* get start symbols, and be slightly paranoid */ if (!fu_memcpy_safe(start_symbols, sizeof(start_symbols), 0x0, /* dst */ buf, bufsz, 0x4000, /* src */ sizeof(start_symbols), error)) return FALSE; /* 0xA001 = b'0 * 0x6C00 = b'0 * 0x6C04 = 0x08 */ g_debug("disable MCU, and enable mtp write"); if (!fu_fresco_pd_device_and_byte(self, 0xa001, ~(1 << 2), error)) { g_prefix_error(error, "failed to disable MCU bit 2: "); return FALSE; } if (!fu_fresco_pd_device_and_byte(self, 0x6c00, ~(1 << 1), error)) { g_prefix_error(error, "failed to disable MCU bit 1: "); return FALSE; } if (!fu_fresco_pd_device_write_byte(self, 0x6c04, 0x08, error)) { g_prefix_error(error, "failed to disable MCU: "); return FALSE; } /* fill safe code in the boot code */ for (guint16 i = 0; i < 0x400; i += 3) { for (guint j = 0; j < 3; j++) { if (!fu_fresco_pd_device_read_byte(self, begin_addr + i + j, &config[j], error)) { g_prefix_error(error, "failed to read config byte %u: ", j); return FALSE; } } if (config[0] == start_symbols[0] && config[1] == start_symbols[1]) { begin_addr = 0x6420 + i; break; } if (config[0] == 0 && config[1] == 0 && config[2] == 0) break; } g_debug("begin_addr: 0x%04x", begin_addr); for (guint i = begin_addr + 3; i < (guint)begin_addr + 0x400; i += 3) { for (guint j = 0; j < 3; j++) { if (!fu_fresco_pd_device_read_byte(self, i + j, &config[j], error)) { g_prefix_error(error, "failed to read config byte %u: ", j); return FALSE; } } if (config[0] == 0x74 && config[1] == 0x06 && config[2] != 0x22) { if (!fu_fresco_pd_device_write_byte(self, i + 2, 0x22, error)) return FALSE; } else if (config[0] == 0x6c && config[1] == 0x00 && config[2] != 0x01) { if (!fu_fresco_pd_device_write_byte(self, i + 2, 0x01, error)) return FALSE; } else if (config[0] == 0x00 && config[1] == 0x00 && config[2] != 0x00) break; } fu_progress_step_done(progress); /* copy buf offset [0 - 0x3FFFF] to mmio address [0x2000 - 0x5FFF] */ g_debug("fill firmware body"); for (guint16 byte_index = 0; byte_index < 0x4000; byte_index++) { if (!fu_fresco_pd_device_set_byte(self, byte_index + 0x2000, buf[byte_index], error)) return FALSE; fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)byte_index + 1, 0x4000); } fu_progress_step_done(progress); /* write file buf 0x4200 ~ 0x4205, 6 bytes to internal address 0x6600 ~ 0x6605 * write file buf 0x4210 ~ 0x4215, 6 bytes to internal address 0x6610 ~ 0x6615 * write file buf 0x4220 ~ 0x4225, 6 bytes to internal address 0x6620 ~ 0x6625 * write file buf 0x4230, 1 byte, to internal address 0x6630 */ g_debug("update customize data"); for (guint16 byte_index = 0; byte_index < 6; byte_index++) { if (!fu_fresco_pd_device_set_byte(self, 0x6600 + byte_index, buf[0x4200 + byte_index], error)) return FALSE; if (!fu_fresco_pd_device_set_byte(self, 0x6610 + byte_index, buf[0x4210 + byte_index], error)) return FALSE; if (!fu_fresco_pd_device_set_byte(self, 0x6620 + byte_index, buf[0x4220 + byte_index], error)) return FALSE; } if (!fu_fresco_pd_device_set_byte(self, 0x6630, buf[0x4230], error)) return FALSE; fu_progress_step_done(progress); /* overwrite firmware file's boot code area (0x4020 ~ 0x41ff) to the area on the device * marked by begin_addr example: if the begin_addr = 0x6420, then copy file buf [0x4020 ~ * 0x41ff] to device offset[0x6420 ~ 0x65ff] */ g_debug("write boot configuration area"); for (guint16 byte_index = 0; byte_index < 0x1e0; byte_index += 3) { if (!fu_fresco_pd_device_set_byte(self, begin_addr + byte_index + 0, buf[0x4020 + byte_index], error)) return FALSE; if (!fu_fresco_pd_device_set_byte(self, begin_addr + byte_index + 1, buf[0x4021 + byte_index], error)) return FALSE; if (!fu_fresco_pd_device_set_byte(self, begin_addr + byte_index + 2, buf[0x4022 + byte_index], error)) return FALSE; } fu_progress_step_done(progress); /* reset the device */ if (!fu_fresco_pd_device_panther_reset_device(self, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_fresco_pd_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_fresco_pd_device_init(FuFrescoPdDevice *self) { fu_device_add_icon(FU_DEVICE(self), "usb-hub"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_protocol(FU_DEVICE(self), "com.frescologic.pd"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_set_install_duration(FU_DEVICE(self), 15); fu_device_set_remove_delay(FU_DEVICE(self), 20000); fu_device_set_firmware_size(FU_DEVICE(self), 0x4400); } static void fu_fresco_pd_device_class_init(FuFrescoPdDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_fresco_pd_device_to_string; klass_device->setup = fu_fresco_pd_device_setup; klass_device->write_firmware = fu_fresco_pd_device_write_firmware; klass_device->prepare_firmware = fu_fresco_pd_device_prepare_firmware; klass_device->set_progress = fu_fresco_pd_device_set_progress; } fwupd-1.9.16/plugins/fresco-pd/fu-fresco-pd-device.h000066400000000000000000000004651460375044200222120ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_FRESCO_PD_DEVICE (fu_fresco_pd_device_get_type()) G_DECLARE_FINAL_TYPE(FuFrescoPdDevice, fu_fresco_pd_device, FU, FRESCO_PD_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/fresco-pd/fu-fresco-pd-firmware.c000066400000000000000000000037431460375044200225640ustar00rootroot00000000000000/* * Copyright (C) 2020 Fresco Logic * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-fresco-pd-common.h" #include "fu-fresco-pd-firmware.h" struct _FuFrescoPdFirmware { FuFirmwareClass parent_instance; guint8 customer_id; }; G_DEFINE_TYPE(FuFrescoPdFirmware, fu_fresco_pd_firmware, FU_TYPE_FIRMWARE) guint8 fu_fresco_pd_firmware_get_customer_id(FuFrescoPdFirmware *self) { return self->customer_id; } static void fu_fresco_pd_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuFrescoPdFirmware *self = FU_FRESCO_PD_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "customer_id", self->customer_id); } static gboolean fu_fresco_pd_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuFrescoPdFirmware *self = FU_FRESCO_PD_FIRMWARE(firmware); guint8 ver[4] = {0x0}; gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autofree gchar *version = NULL; /* read version block */ if (!fu_memcpy_safe(ver, sizeof(ver), 0x0, /* dst */ buf, bufsz, 0x1000, /* src */ sizeof(ver), error)) return FALSE; /* customer ID is always the 2nd byte */ self->customer_id = ver[1]; /* set version number */ version = fu_fresco_pd_version_from_buf(ver); fu_firmware_set_version(firmware, version); return TRUE; } static void fu_fresco_pd_firmware_init(FuFrescoPdFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); } static void fu_fresco_pd_firmware_class_init(FuFrescoPdFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_fresco_pd_firmware_parse; klass_firmware->export = fu_fresco_pd_firmware_export; } FuFirmware * fu_fresco_pd_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_FRESCO_PD_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/fresco-pd/fu-fresco-pd-firmware.h000066400000000000000000000007301460375044200225620ustar00rootroot00000000000000/* * Copyright (C) 2020 Fresco Logic * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_FRESCO_PD_FIRMWARE (fu_fresco_pd_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuFrescoPdFirmware, fu_fresco_pd_firmware, FU, FRESCO_PD_FIRMWARE, FuFirmware) FuFirmware * fu_fresco_pd_firmware_new(void); guint8 fu_fresco_pd_firmware_get_customer_id(FuFrescoPdFirmware *self); fwupd-1.9.16/plugins/fresco-pd/fu-fresco-pd-plugin.c000066400000000000000000000015261460375044200222430ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-fresco-pd-device.h" #include "fu-fresco-pd-firmware.h" #include "fu-fresco-pd-plugin.h" struct _FuFrescoPdPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuFrescoPdPlugin, fu_fresco_pd_plugin, FU_TYPE_PLUGIN) static void fu_fresco_pd_plugin_init(FuFrescoPdPlugin *self) { } static void fu_fresco_pd_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_FRESCO_PD_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_FRESCO_PD_FIRMWARE); } static void fu_fresco_pd_plugin_class_init(FuFrescoPdPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_fresco_pd_plugin_constructed; } fwupd-1.9.16/plugins/fresco-pd/fu-fresco-pd-plugin.h000066400000000000000000000003601460375044200222430ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuFrescoPdPlugin, fu_fresco_pd_plugin, FU, FRESCO_PD_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/fresco-pd/lsusb.txt000066400000000000000000000047141460375044200202040ustar00rootroot00000000000000Bus 003 Device 002: ID 1d5c:7102 Fresco Logic Generic Billboard Device Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.01 bDeviceClass 17 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x1d5c Fresco Logic idProduct 0x7102 bcdDevice 1.00 iManufacturer 1 Fresco Logic, Inc iProduct 2 Generic Billboard Device iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0012 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 0mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 0 bInterfaceClass 17 bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 Binary Object Store Descriptor: bLength 5 bDescriptorType 15 wTotalLength 0x0050 bNumDeviceCaps 3 USB 2.0 Extension Device Capability: bLength 7 bDescriptorType 16 bDevCapabilityType 2 bmAttributes 0x00000006 BESL Link Power Management (LPM) Supported Container ID Device Capability: bLength 20 bDescriptorType 16 bDevCapabilityType 4 bReserved 0 ContainerID {00000000-0000-0000-0000-000000000000} Billboard Capability: bLength 48 bDescriptorType 16 bDevCapabilityType 13 iAdditionalInfoURL 4 www.frescologic.com bNumberOfAlternateModes 1 bPreferredAlternateMode 0 VCONN Power 0 1W bmConfigured 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 bcdVersion 1.10 bAdditionalFailureInfo 0 bReserved 0 Alternate Modes supported by Device Container: Alternate Mode 0 : Alternate Mode configuration successful wSVID[0] 0x0000 bAlternateMode[0] 0 iAlternateModeString[0] 0 Device Status: 0x0001 Self Powered fwupd-1.9.16/plugins/fresco-pd/meson.build000066400000000000000000000007361460375044200204550ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginFrescoPd"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('fresco-pd.quirk') plugin_builtins += static_library('fu_plugin_fresco_pd', sources: [ 'fu-fresco-pd-plugin.c', 'fu-fresco-pd-common.c', 'fu-fresco-pd-device.c', 'fu-fresco-pd-firmware.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/genesys-gl32xx/000077500000000000000000000000001460375044200172255ustar00rootroot00000000000000fwupd-1.9.16/plugins/genesys-gl32xx/README.md000066400000000000000000000022451460375044200205070ustar00rootroot00000000000000--- title: Plugin: Genesys GL322x/GL323x --- ## Introduction The GL3224/GL3232 families are USB3 card reader products. ## Firmware Format This plugin supports the following protocol ID: * `com.genesys.gl32xx` ## GUID Generation These devices use the standard UDEV DeviceInstanceId values, e.g. * `BLOCK\VEN_05E3&DEV_XXXX` (quirk-only) These devices also use custom GUID values, e.g. * `BLOCK\VEN_05E3&DEV_XXXX&VER_YY` (quirk-only) Additional GUID value based on firmware version stream and customer ID read from the device, e.g. * `BLOCK\VEN_05E3&DEV_XXXX&VER_YY&CID_ZZZZZZZZ` ## Update Behavior The device is switched to ROM mode for the update and the device must be reset the firmware update/dump to return back to normal mode. For 323x family the expected firmware size is `0x01C000`, and `0x010000` for 3224. ## Vendor ID Security The vendor ID is set from the udev vendor, in this instance set to `BLOCK:0x05E3` ## External Interface Access This plugin requires read/write access to `/dev/sd*` block devices and requires using a `sg_io ioctl` for interaction with the device. ## Version Considerations This plugin has been available since fwupd version `1.9.3`. fwupd-1.9.16/plugins/genesys-gl32xx/fu-genesys-gl32xx-device.c000066400000000000000000000660121460375044200240450ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_SCSI_SG_H #include #endif #include "fu-genesys-gl32xx-device.h" #include "fu-genesys-gl32xx-firmware.h" #define FU_GENESYS_GL32XX_FW_START_ADDR 0x0 #define FU_GENESYS_GL32XX_FW_SIZE 0x00010000 #define FU_GENESYS_GL32XX_CLEAR_WP_SLEEP_MS 800 struct _FuGenesysGl32xxDevice { FuUdevDevice parent_instance; gchar *chip_name; guint32 packetsz; guint32 customer_id; }; G_DEFINE_TYPE(FuGenesysGl32xxDevice, fu_genesys_gl32xx_device, FU_TYPE_UDEV_DEVICE) #define FU_GENESYS_GL32XX_BUFFER_LEN 32 #define FU_GENESYS_GL32XX_IOCTL_TIMEOUT_MS 20000 static gboolean fu_genesys_gl32xx_device_cmd_none(FuGenesysGl32xxDevice *self, const guint8 *cdb, guint8 cdbsz, GError **error) { #ifdef HAVE_SCSI_SG_H guint8 sense_buffer[FU_GENESYS_GL32XX_BUFFER_LEN] = {0}; struct sg_io_hdr io_hdr = { .interface_id = 'S', .cmd_len = cdbsz, .mx_sb_len = sizeof(sense_buffer), .dxfer_direction = SG_DXFER_NONE, .cmdp = (guint8 *)cdb, .sbp = sense_buffer, .timeout = FU_GENESYS_GL32XX_IOCTL_TIMEOUT_MS, .flags = SG_FLAG_DIRECT_IO, }; gint rc = 0; fu_dump_raw(G_LOG_DOMAIN, "cmd", cdb, cdbsz); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), SG_IO, (guint8 *)&io_hdr, &rc, 5 * FU_GENESYS_GL32XX_IOCTL_TIMEOUT_MS, error)) return FALSE; if (io_hdr.status) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Command fail with status %x, senseKey 0x%02x, asc 0x%02x, ascq 0x%02x", io_hdr.status, sense_buffer[2], sense_buffer[12], sense_buffer[13]); return FALSE; } /* success */ return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported as scsi/sg.h not found"); return FALSE; #endif } static gboolean fu_genesys_gl32xx_device_cmd_in(FuGenesysGl32xxDevice *self, const guint8 *cdb, guint8 cdbsz, guint8 *buf, guint32 bufsz, GError **error) { #ifdef HAVE_SCSI_SG_H guint8 sense_buffer[FU_GENESYS_GL32XX_BUFFER_LEN] = {0}; struct sg_io_hdr io_hdr = { .interface_id = 'S', .cmd_len = cdbsz, .mx_sb_len = sizeof(sense_buffer), .dxfer_direction = SG_DXFER_FROM_DEV, .dxfer_len = bufsz, .dxferp = buf, .cmdp = (guint8 *)cdb, .sbp = sense_buffer, .timeout = FU_GENESYS_GL32XX_IOCTL_TIMEOUT_MS, .flags = SG_FLAG_DIRECT_IO, }; gint rc = 0; fu_dump_raw(G_LOG_DOMAIN, "cmd", cdb, cdbsz); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), SG_IO, (guint8 *)&io_hdr, &rc, 5 * FU_GENESYS_GL32XX_IOCTL_TIMEOUT_MS, error)) return FALSE; if (io_hdr.status) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Command fail with status %x, senseKey 0x%02x, asc 0x%02x, ascq 0x%02x", io_hdr.status, sense_buffer[2], sense_buffer[12], sense_buffer[13]); return FALSE; } if (bufsz > 0) fu_dump_raw(G_LOG_DOMAIN, "cmd data", buf, bufsz); /* success */ return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported as scsi/sg.h not found"); return FALSE; #endif } static gboolean fu_genesys_gl32xx_device_cmd_out(FuGenesysGl32xxDevice *self, const guint8 *cdb, guint8 cdbsz, const guint8 *buf, guint32 bufsz, GError **error) { #ifdef HAVE_SCSI_SG_H guint8 sense_buffer[FU_GENESYS_GL32XX_BUFFER_LEN] = {0}; struct sg_io_hdr io_hdr = { .interface_id = 'S', .cmd_len = cdbsz, .mx_sb_len = sizeof(sense_buffer), .dxfer_direction = SG_DXFER_TO_DEV, .dxfer_len = bufsz, .dxferp = (guint8 *)buf, .cmdp = (guint8 *)cdb, .sbp = sense_buffer, .timeout = FU_GENESYS_GL32XX_IOCTL_TIMEOUT_MS, .flags = SG_FLAG_DIRECT_IO, }; gint rc = 0; fu_dump_raw(G_LOG_DOMAIN, "cmd", cdb, cdbsz); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), SG_IO, (guint8 *)&io_hdr, &rc, 5 * FU_GENESYS_GL32XX_IOCTL_TIMEOUT_MS, error)) return FALSE; if (io_hdr.status) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Command fail with status %x, senseKey 0x%02x, asc 0x%02x, ascq 0x%02x", io_hdr.status, sense_buffer[2], sense_buffer[12], sense_buffer[13]); return FALSE; } if (bufsz > 0) fu_dump_raw(G_LOG_DOMAIN, "cmd data", buf, bufsz); /* success */ return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported as scsi/sg.h not found"); return FALSE; #endif } static void fu_genesys_gl32xx_device_set_chip_name(FuGenesysGl32xxDevice *self, const gchar *chip_name) { g_return_if_fail(chip_name != NULL); g_free(self->chip_name); self->chip_name = g_strdup(chip_name); } static GByteArray * fu_genesys_gl32xx_device_cmd_get_version(FuGenesysGl32xxDevice *self, GError **error) { const guint8 cmd[] = {0x12, 0x00, 0x00, 0x00, 0x2e, 0x00}; g_autoptr(GByteArray) buf = g_byte_array_new(); fu_byte_array_set_size(buf, 0x2E, 0x0); if (!fu_genesys_gl32xx_device_cmd_in(self, cmd, sizeof(cmd), buf->data, buf->len, error)) return NULL; return g_steal_pointer(&buf); } static gboolean fu_genesys_gl32xx_device_cmd_switch_to_rom_mode(FuGenesysGl32xxDevice *self, GError **error) { const guint8 cmd[] = {0xF3, 0x06, 0x00, 0x00, 0x00, 0x00}; if (!fu_genesys_gl32xx_device_cmd_none(self, cmd, sizeof(cmd), error)) { g_prefix_error(error, "failed to switch into ROM mode: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_gl32xx_device_cmd_reset_usb(FuGenesysGl32xxDevice *self, GError **error) { const guint8 cmd[] = {0xE6, 0x00, 0x00, 0x00, 0x00, 0x00}; if (!fu_genesys_gl32xx_device_cmd_none(self, cmd, sizeof(cmd), error)) { g_prefix_error(error, "failed to reset USB: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_gl32xx_device_cmd_write_sr(FuGenesysGl32xxDevice *self, GError **error) { const guint8 cmd[] = {0xF3, 0x01, 0x00, 0x00, 0x01, 0x00}; return fu_genesys_gl32xx_device_cmd_none(self, cmd, sizeof(cmd), error); } static gboolean fu_genesys_gl32xx_device_cmd_write_enable(FuGenesysGl32xxDevice *self, GError **error) { const guint8 cmd[] = {0xF3, 0x00, 0x00, 0x00, 0x06, 0x00}; return fu_genesys_gl32xx_device_cmd_none(self, cmd, sizeof(cmd), error); } static gboolean fu_genesys_gl32xx_device_cmd_write_disable(FuGenesysGl32xxDevice *self, GError **error) { const guint8 cmd[] = {0xF3, 0x00, 0x00, 0x00, 0x04, 0x00}; return fu_genesys_gl32xx_device_cmd_none(self, cmd, sizeof(cmd), error); } static gboolean fu_genesys_gl32xx_device_cmd_clear_wp(FuGenesysGl32xxDevice *self, GError **error) { const guint8 cmd[] = {0xF3, 0x02, 0x00, 0x02, 0x00, 0x00}; const guint8 data[] = {0x01, 0x00}; return fu_genesys_gl32xx_device_cmd_out(self, cmd, sizeof(cmd), data, sizeof(data), error); } static gboolean fu_genesys_gl32xx_device_cmd_chip_erase(FuGenesysGl32xxDevice *self, GError **error) { const guint8 cmd[] = {0xF3, 0x00, 0x00, 0x00, 0xC7, 0x00}; return fu_genesys_gl32xx_device_cmd_none(self, cmd, sizeof(cmd), error); } static gboolean fu_genesys_gl32xx_device_cmd_wait_wip(FuGenesysGl32xxDevice *self, GError **error) { const guint8 cmd[] = {0xF3, 0x03, 0x01, 0x00, 0x05, 0x00}; return fu_genesys_gl32xx_device_cmd_none(self, cmd, sizeof(cmd), error); } static gboolean fu_genesys_gl32xx_device_cmd_read_flash(FuGenesysGl32xxDevice *self, guint32 addr, guint8 *data, guint32 datasz, GError **error) { guint8 cmd[] = {0xE4, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; g_return_val_if_fail(data != NULL && datasz != 0, FALSE); /* start address */ if (!fu_memwrite_uint32_safe(cmd, sizeof(cmd), 2, addr, G_BIG_ENDIAN, error)) return FALSE; /* block size */ if (!fu_memwrite_uint16_safe(cmd, sizeof(cmd), 6, (guint16)datasz, G_BIG_ENDIAN, error)) return FALSE; return fu_genesys_gl32xx_device_cmd_in(self, cmd, sizeof(cmd), data, datasz, error); } static gboolean fu_genesys_gl32xx_device_ensure_version(FuGenesysGl32xxDevice *self, GError **error) { g_autofree gchar *version = NULL; g_autofree gchar *version_prefix = NULL; g_autoptr(GByteArray) buf = NULL; buf = fu_genesys_gl32xx_device_cmd_get_version(self, error); if (buf == NULL) { g_prefix_error(error, "failed to read version: "); return FALSE; } if (buf->len < 0x24) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to read version"); return FALSE; } version = fu_memstrsafe(buf->data, buf->len, 0x20, 4, error); if (version == NULL) return FALSE; fu_device_set_version(FU_DEVICE(self), version); /* this is used to differentiate standard firmware versions */ version_prefix = fu_memstrsafe(buf->data, buf->len, 0x20, 2, error); if (version_prefix == NULL) return FALSE; fu_device_add_instance_str(FU_DEVICE(self), "VER", version_prefix); return fu_device_build_instance_id_full(FU_DEVICE(self), FU_DEVICE_INSTANCE_FLAG_QUIRKS, error, "BLOCK", "VEN", "DEV", "VER", NULL); } static gboolean fu_genesys_gl32xx_device_check_rom_mode(FuGenesysGl32xxDevice *self, guint8 *cmd, gsize cmdsz, GError **error) { const guint8 ext_rom_mode[] = {0x58, 0x52, 0x4F, 0x4D}; /* "XROM" */ const guint8 int_rom_mode[] = {0x49, 0x4E, 0x54, 0x2D}; /* "INT-" */ guint8 data[4] = {0}; if (!fu_genesys_gl32xx_device_cmd_in(self, cmd, cmdsz, data, sizeof(data), error)) return FALSE; if (fu_memcmp_safe(int_rom_mode, sizeof(int_rom_mode), 0, data, sizeof(data), 0, sizeof(data), NULL)) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } if (fu_memcmp_safe(ext_rom_mode, sizeof(ext_rom_mode), 0, data, sizeof(data), 0, sizeof(data), NULL)) { fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no supported devices detected"); return FALSE; } /* safe to call in any mode */ static gboolean fu_genesys_gl32xx_device_ensure_rom_mode(FuGenesysGl32xxDevice *self, GError **error) { guint8 cmd_gl323x[] = {0xE4, 0x01, 0x00, 0xDC, 0x04, 0x00}; guint8 cmd_gl3224[] = {0xE4, 0x01, 0x00, 0xFC, 0x04, 0x00}; g_autoptr(GError) error_local = NULL; /* check for 3230, 3231, 3232, 3230S, 3231S, 3232S first */ /* ignore error here */ if (!fu_genesys_gl32xx_device_check_rom_mode(self, cmd_gl323x, sizeof(cmd_gl323x), &error_local)) { g_debug("ignoring: %s", error_local->message); } else { fu_genesys_gl32xx_device_set_chip_name(self, "GL323x"); return TRUE; } /* check the 3224 */ if (!fu_genesys_gl32xx_device_check_rom_mode(self, cmd_gl3224, sizeof(cmd_gl3224), error)) return FALSE; fu_genesys_gl32xx_device_set_chip_name(self, "GL3224"); return TRUE; } static gboolean fu_genesys_gl32xx_device_verify_chip_id(FuGenesysGl32xxDevice *self, GError **error) { const guint8 cmd_req[] = {0xF3, 0x02, 0x00, 0x01, 0x00, 0x03}; const guint8 data_req[] = {0x9F}; const guint8 cmd_get[] = {0xF3, 0x04, 0x00, 0x00, 0x00, 0x03}; guint8 buf[3] = {0}; g_autofree gchar *flash_id = NULL; g_autoptr(FuCfiDevice) cfi_device = NULL; if (!fu_genesys_gl32xx_device_cmd_out(self, cmd_req, sizeof(cmd_req), data_req, sizeof(data_req), error)) return FALSE; if (!fu_genesys_gl32xx_device_cmd_in(self, cmd_get, sizeof(cmd_get), buf, sizeof(buf), error)) return FALSE; flash_id = g_strdup_printf("%02X%02X%02X", buf[0], buf[1], buf[2]); cfi_device = fu_cfi_device_new(fu_device_get_context(FU_DEVICE(self)), flash_id); if (!fu_device_setup(FU_DEVICE(cfi_device), error)) return FALSE; if (fu_device_get_name(cfi_device) == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "not supported flash type"); return FALSE; } g_debug("flash type detected: %s", fu_device_get_name(cfi_device)); /* success */ return TRUE; } static void fu_genesys_gl32xx_device_ensure_enforce_requires(FuGenesysGl32xxDevice *self) { const gchar *version = fu_device_get_version(FU_DEVICE(self)); const guint16 model = fu_udev_device_get_model(FU_UDEV_DEVICE(self)); /* GL3224 */ if (model == 0x0749 && self->customer_id == 0xFFFFFFFF && g_str_has_prefix(version, "15")) { fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ENFORCE_REQUIRES); return; } /* GL323X */ if (model == 0x0764 && self->customer_id == 0x22FFFFFF && g_str_has_prefix(version, "29")) { fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ENFORCE_REQUIRES); return; } } static gboolean fu_genesys_gl32xx_device_ensure_cid(FuGenesysGl32xxDevice *self, GError **error) { const guint8 cmd_gl3224_cid[] = {0xE4, 0x01, 0xBF, 0x80, 0x04, 0x00}; const guint8 cmd_gl323x_cid[] = {0xE4, 0x01, 0x35, 0x00, 0x04, 0x00}; const guint8 *cmd = NULL; const guint16 model = fu_udev_device_get_model(FU_UDEV_DEVICE(self)); guint8 data[4] = {0}; switch (model) { case 0x0749: cmd = cmd_gl3224_cid; break; case 0x0764: cmd = cmd_gl323x_cid; break; default: g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "unsupported model [0x%04X]", model); return FALSE; } if (!fu_genesys_gl32xx_device_cmd_in(self, cmd, sizeof(cmd_gl323x_cid), data, sizeof(data), error)) return FALSE; self->customer_id = fu_memread_uint32(data, G_BIG_ENDIAN); fu_device_add_instance_u32(FU_DEVICE(self), "CID", self->customer_id); /* valid GUID with the pair of FW version stream and customer ID */ return fu_device_build_instance_id(FU_DEVICE(self), error, "BLOCK", "VEN", "DEV", "VER", "CID", NULL); } static gboolean fu_genesys_gl32xx_device_get_usb_mode(FuGenesysGl32xxDevice *self, GError **error) { guint8 mode = 0; const guint8 cmd[] = {0xF2, 0xFF, 0x00, 0x00, 0x00, 0x00}; if (!fu_genesys_gl32xx_device_cmd_in(self, cmd, sizeof(cmd), &mode, sizeof(mode), error)) { g_prefix_error(error, "failed to read USB mode: "); return FALSE; } switch (mode) { case 1: self->packetsz = 64; break; case 2: self->packetsz = 512; break; case 3: self->packetsz = 1024; break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unknown USB mode 0x%02x read from device", mode); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_gl32xx_device_erase(FuGenesysGl32xxDevice *self, GError **error) { /* write enable */ if (!fu_genesys_gl32xx_device_cmd_write_enable(self, error)) { g_prefix_error(error, "failed to write enable: "); return FALSE; } /* clear write protect */ if (!fu_genesys_gl32xx_device_cmd_clear_wp(self, error)) { g_prefix_error(error, "failed to clear WP: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), FU_GENESYS_GL32XX_CLEAR_WP_SLEEP_MS); /* write enable */ if (!fu_genesys_gl32xx_device_cmd_write_enable(self, error)) { g_prefix_error(error, "failed to write enable: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), FU_GENESYS_GL32XX_CLEAR_WP_SLEEP_MS); /* chip erase */ if (!fu_genesys_gl32xx_device_cmd_chip_erase(self, error)) { g_prefix_error(error, "failed to erase chip: "); return FALSE; } /* wait WIP to reset back to 0 */ if (!fu_genesys_gl32xx_device_cmd_wait_wip(self, error)) { g_prefix_error(error, "failed to wait WIP: "); return FALSE; } /* success */ return TRUE; } static void fu_genesys_gl32xx_device_to_string(FuDevice *device, guint idt, GString *str) { FuGenesysGl32xxDevice *self = FU_GENESYS_GL32XX_DEVICE(device); FU_DEVICE_CLASS(fu_genesys_gl32xx_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "ChipName", self->chip_name); fu_string_append_kx(str, idt, "BlockTransferSize", self->packetsz); fu_string_append_kx(str, idt, "CustomerId", self->customer_id); } static gboolean fu_genesys_gl32xx_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuGenesysGl32xxDevice *self = FU_GENESYS_GL32XX_DEVICE(device); /* switch to internal, request and check chip ID */ if (!fu_genesys_gl32xx_device_cmd_switch_to_rom_mode(self, error)) return FALSE; /* get USB mode */ if (!fu_genesys_gl32xx_device_get_usb_mode(self, error)) return FALSE; if (!fu_genesys_gl32xx_device_verify_chip_id(self, error)) return FALSE; /* clear SR */ if (!fu_genesys_gl32xx_device_cmd_write_sr(self, error)) { g_prefix_error(error, "failed to clear SR: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_gl32xx_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuGenesysGl32xxDevice *self = FU_GENESYS_GL32XX_DEVICE(device); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return fu_genesys_gl32xx_device_cmd_reset_usb(self, error); } static gboolean fu_genesys_gl32xx_device_probe(FuDevice *device, GError **error) { FuUdevDevice *udev_device = FU_UDEV_DEVICE(device); /* UdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_genesys_gl32xx_device_parent_class)->probe(device, error)) return FALSE; if (g_strcmp0(fu_udev_device_get_devtype(udev_device), "disk") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct devtype=%s, expected disk", fu_udev_device_get_devtype(udev_device)); return FALSE; } /* success */ return fu_udev_device_set_physical_id(udev_device, "usb", error); } static gboolean fu_genesys_gl32xx_device_setup(FuDevice *device, GError **error) { FuGenesysGl32xxDevice *self = FU_GENESYS_GL32XX_DEVICE(device); g_autofree gchar *name = NULL; if (!fu_genesys_gl32xx_device_ensure_version(self, error)) return FALSE; if (!fu_genesys_gl32xx_device_ensure_rom_mode(self, error)) { g_prefix_error(error, "failed to check ROM mode: "); return FALSE; } /* if not detected above */ if (self->chip_name == NULL) fu_genesys_gl32xx_device_set_chip_name(self, "GL32xx"); name = g_strdup_printf("%s SD reader [0x%04X]", self->chip_name, fu_udev_device_get_model(FU_UDEV_DEVICE(device))); fu_device_set_name(device, name); if (!fu_genesys_gl32xx_device_ensure_cid(self, error)) return FALSE; fu_genesys_gl32xx_device_ensure_enforce_requires(self); /* success */ return TRUE; } static GBytes * fu_genesys_gl32xx_device_dump_bytes(FuDevice *device, FuProgress *progress, GError **error) { FuGenesysGl32xxDevice *self = FU_GENESYS_GL32XX_DEVICE(device); const gsize fwsz = fu_device_get_firmware_size_max(device); g_autoptr(GPtrArray) chunks = NULL; g_autofree guint8 *buf = g_malloc0(fwsz); chunks = fu_chunk_array_mutable_new(buf, fwsz, 0x0, 0x0, self->packetsz); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_genesys_gl32xx_device_cmd_read_flash(self, fu_chunk_get_address(chk), fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to read flash data on chunk 0x%x: ", i); return NULL; } fu_progress_step_done(progress); } return g_bytes_new_take(g_steal_pointer(&buf), fwsz); } static GBytes * fu_genesys_gl32xx_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuGenesysGl32xxDevice *self = FU_GENESYS_GL32XX_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(FuDeviceLocker) locker = fu_device_locker_new_full(FU_DEVICE(self), (FuDeviceLockerFunc)fu_genesys_gl32xx_device_detach, (FuDeviceLockerFunc)fu_genesys_gl32xx_device_attach, error); if (locker == NULL) return NULL; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); fw = fu_genesys_gl32xx_device_dump_bytes(device, progress, error); if (fw == NULL) return NULL; /* success */ return g_steal_pointer(&fw); } static FuFirmware * fu_genesys_gl32xx_device_read_firmware(FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(FuFirmware) firmware = fu_genesys_gl32xx_firmware_new(); g_autoptr(GBytes) fw = NULL; fw = fu_genesys_gl32xx_device_dump_firmware(device, progress, error); if (fw == NULL) return NULL; if (!fu_firmware_parse(firmware, fw, FWUPD_INSTALL_FLAG_NONE, error)) return NULL; /* success */ return g_steal_pointer(&firmware); } static FuFirmware * fu_genesys_gl32xx_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_genesys_gl32xx_firmware_new(); if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* check size */ if (fu_firmware_get_size(firmware) != fu_device_get_firmware_size_max(device)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware size is [%" G_GSIZE_FORMAT "] bytes while expecting [%" G_GUINT64_FORMAT "] bytes", fu_firmware_get_size(firmware), fu_device_get_firmware_size_max(device)); return NULL; } /* TODO: validate compatibility? */ /* success */ return g_steal_pointer(&firmware); } static gboolean fu_genesys_gl32xx_device_write_block(FuGenesysGl32xxDevice *self, FuChunk *chunk, GError **error) { guint32 addr = fu_chunk_get_address(chunk); guint16 datasz = fu_chunk_get_data_sz(chunk); const guint8 *data = fu_chunk_get_data(chunk); guint8 cmd[] = {0xE5, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}; /* build command */ if (!fu_memwrite_uint32_safe(cmd, sizeof(cmd), 2, addr, G_BIG_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint16_safe(cmd, sizeof(cmd), 6, (guint16)datasz, G_BIG_ENDIAN, error)) return FALSE; if (!fu_genesys_gl32xx_device_cmd_out(self, cmd, sizeof(cmd), data, datasz, error)) { g_prefix_error(error, "failed to write flash data: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_gl32xx_device_write_blocks(FuGenesysGl32xxDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_genesys_gl32xx_device_write_block(self, chk, error)) { g_prefix_error(error, "failed on block 0x%x: ", i); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_genesys_gl32xx_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuGenesysGl32xxDevice *self = FU_GENESYS_GL32XX_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) fw_read = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 80, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 9, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* erase */ if (!fu_genesys_gl32xx_device_erase(self, error)) return FALSE; fu_progress_step_done(progress); /* write each block */ chunks = fu_chunk_array_new_from_bytes(fw, FU_GENESYS_GL32XX_FW_START_ADDR, self->packetsz); if (!fu_genesys_gl32xx_device_write_blocks(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* verify written data */ fw_read = fu_genesys_gl32xx_device_dump_bytes(device, progress, error); if (fw_read == NULL) return FALSE; fu_progress_step_done(progress); if (g_bytes_compare(fw, fw_read) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "unable to verify written firmware"); return FALSE; } fu_progress_step_done(progress); /* write disable */ if (!fu_genesys_gl32xx_device_cmd_write_disable(self, error)) { g_prefix_error(error, "failed to write disable: "); return FALSE; } /* success */ return TRUE; } static void fu_genesys_gl32xx_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 55, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 45, "reload"); } static void fu_genesys_gl32xx_device_init(FuGenesysGl32xxDevice *self) { self->packetsz = 64; fu_device_set_vendor(FU_DEVICE(self), "Genesys"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_set_firmware_size(FU_DEVICE(self), FU_GENESYS_GL32XX_FW_SIZE); /* defaults to 64K */ fu_device_add_protocol(FU_DEVICE(self), "com.genesys.gl32xx"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | FU_UDEV_DEVICE_FLAG_OPEN_NONBLOCK | FU_UDEV_DEVICE_FLAG_IOCTL_RETRY); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_GENERIC_GUIDS); } static void fu_genesys_gl32xx_device_finalize(GObject *object) { FuGenesysGl32xxDevice *self = FU_GENESYS_GL32XX_DEVICE(object); g_free(self->chip_name); G_OBJECT_CLASS(fu_genesys_gl32xx_device_parent_class)->finalize(object); } static void fu_genesys_gl32xx_device_class_init(FuGenesysGl32xxDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_genesys_gl32xx_device_finalize; klass_device->to_string = fu_genesys_gl32xx_device_to_string; klass_device->probe = fu_genesys_gl32xx_device_probe; klass_device->setup = fu_genesys_gl32xx_device_setup; klass_device->detach = fu_genesys_gl32xx_device_detach; klass_device->attach = fu_genesys_gl32xx_device_attach; klass_device->dump_firmware = fu_genesys_gl32xx_device_dump_firmware; klass_device->write_firmware = fu_genesys_gl32xx_device_write_firmware; klass_device->read_firmware = fu_genesys_gl32xx_device_read_firmware; klass_device->prepare_firmware = fu_genesys_gl32xx_device_prepare_firmware; klass_device->set_progress = fu_genesys_gl32xx_device_set_progress; } fwupd-1.9.16/plugins/genesys-gl32xx/fu-genesys-gl32xx-device.h000066400000000000000000000005611460375044200240470ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_GENESYS_GL32XX_DEVICE (fu_genesys_gl32xx_device_get_type()) G_DECLARE_FINAL_TYPE(FuGenesysGl32xxDevice, fu_genesys_gl32xx_device, FU, GENESYS_GL32XX_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/genesys-gl32xx/fu-genesys-gl32xx-firmware.c000066400000000000000000000042201460375044200244130ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * Copyright (C) 2023 Richard Hughes * Copyright (C) 2023 Ben Chuang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-genesys-gl32xx-firmware.h" #define FU_GENESYS_GL32XX_VERSION_ADDR 0x00D4 #define FU_GENESYS_GL32XX_CHECKSUM_MAGIC 0x0055 struct _FuGenesysGl32xxFirmware { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuGenesysGl32xxFirmware, fu_genesys_gl32xx_firmware, FU_TYPE_FIRMWARE) static gboolean fu_genesys_gl32xx_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0; guint8 ver[4] = {0}; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autofree gchar *version = NULL; /* version */ if (!fu_memcpy_safe(ver, sizeof(ver), 0x0, buf, g_bytes_get_size(fw), FU_GENESYS_GL32XX_VERSION_ADDR, sizeof(ver), error)) return FALSE; version = g_strdup_printf("%c%c%c%c", ver[0x0], ver[0x1], ver[0x2], ver[0x3]); fu_firmware_set_version(firmware, version); /* verify checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint8 chksum_expected = buf[bufsz - 1]; guint8 chksum_actual = FU_GENESYS_GL32XX_CHECKSUM_MAGIC - fu_sum8(buf, bufsz - 2); if (chksum_actual != chksum_expected) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "checksum mismatch, got 0x%02x, expected 0x%02x", chksum_actual, chksum_expected); return FALSE; } } /* payload is entire blob */ fu_firmware_set_bytes(firmware, fw); return TRUE; } static void fu_genesys_gl32xx_firmware_init(FuGenesysGl32xxFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); } static void fu_genesys_gl32xx_firmware_class_init(FuGenesysGl32xxFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_genesys_gl32xx_firmware_parse; } FuFirmware * fu_genesys_gl32xx_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_GENESYS_GL32XX_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/genesys-gl32xx/fu-genesys-gl32xx-firmware.h000066400000000000000000000006541460375044200244270ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_GENESYS_GL32XX_FIRMWARE (fu_genesys_gl32xx_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuGenesysGl32xxFirmware, fu_genesys_gl32xx_firmware, FU, GENESYS_GL32XX_FIRMWARE, FuFirmware) FuFirmware * fu_genesys_gl32xx_firmware_new(void); fwupd-1.9.16/plugins/genesys-gl32xx/fu-genesys-gl32xx-plugin.c000066400000000000000000000017211460375044200241000ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-genesys-gl32xx-device.h" #include "fu-genesys-gl32xx-firmware.h" #include "fu-genesys-gl32xx-plugin.h" struct _FuGenesysGl32xxPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuGenesysGl32xxPlugin, fu_genesys_gl32xx_plugin, FU_TYPE_PLUGIN) static void fu_genesys_gl32xx_plugin_init(FuGenesysGl32xxPlugin *self) { } static void fu_genesys_gl32xx_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "block"); fu_plugin_add_device_gtype(plugin, FU_TYPE_GENESYS_GL32XX_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_GENESYS_GL32XX_FIRMWARE); } static void fu_genesys_gl32xx_plugin_class_init(FuGenesysGl32xxPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_genesys_gl32xx_plugin_constructed; } fwupd-1.9.16/plugins/genesys-gl32xx/fu-genesys-gl32xx-plugin.h000066400000000000000000000004401460375044200241020ustar00rootroot00000000000000/* * Copyright (C) 2023 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuGenesysGl32xxPlugin, fu_genesys_gl32xx_plugin, FU, GENESYS_GL32XX_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/genesys-gl32xx/genesys-gl32xx.quirk000066400000000000000000000002231460375044200231010ustar00rootroot00000000000000[BLOCK\VEN_05E3&DEV_0749] Plugin = genesys_gl32xx FirmwareSize = 0x10000 [BLOCK\VEN_05E3&DEV_0764] Plugin = genesys_gl32xx FirmwareSize = 0x1C000 fwupd-1.9.16/plugins/genesys-gl32xx/meson.build000066400000000000000000000007401460375044200213700ustar00rootroot00000000000000if gudev.found() cargs = ['-DG_LOG_DOMAIN="FuPluginGenesysGl32xx"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('genesys-gl32xx.quirk') plugin_builtins += static_library('fu_plugin_genesys_gl32xx', sources: [ 'fu-genesys-gl32xx-device.c', 'fu-genesys-gl32xx-firmware.c', 'fu-genesys-gl32xx-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/genesys/000077500000000000000000000000001460375044200161005ustar00rootroot00000000000000fwupd-1.9.16/plugins/genesys/README.md000066400000000000000000000073011460375044200173600ustar00rootroot00000000000000--- title: Plugin: Genesys Logic --- ## Introduction This plugin allows updating the Genesys Logic USB Hub devices. * GL3521 * GL3523 * GL3525 * GL3590 Additionally, this plugin allows updating the MStar Semiconductor Scaler connected via an I²C bus. * TSUM G ## Firmware Format The daemon will decompress the cabinet archives and extract the firmware blob in an unspecified binary file format. This plugin supports the following protocol IDs: * `com.genesys.usbhub` * `com.mstarsemi.scaler` ## GUID Generation These devices use the standard USB DeviceInstanceId values for the USB Hub, e.g. * `USB\VID_05E3&PID_0610` (quirk-only) These devices use the standard USB DeviceInstanceId values for the HID under USB hub, e.g. * `USB\VID_05E3&PID_0102` (quirk-only) Additionally, some customized instance IDs are added. e.g. * `USB\VID_03F0&PID_0610&IC_352330&BONDING_0F` * `USB\VID_03F0&PID_0610&VENDOR_GENESYSLOGIC&IC_352330&BONDING_0F&PORTNUM_23&VENDORSUP_C09B5DD3-1A23-51D2-995A-F7366AAB3CA4` * `USB\VID_05E3&PID_0630&PROJECT_1885D34D-0418-5EF8-8E69-4CEF77B6B6E8` * `USB\VID_03F0&PID_0610&PUBKEY_AB859399-95B8-5817-B521-9AD8CC7F5BD6` These devices also use custom GUID values for the Scaler, e.g. * `GENESYS_SCALER\MSTAR_TSUM_G&PUBKEY_B335BDCE-7073-5D0E-9BD3-9B69C1A6899F&PANELREV_RIM101` The Public Key is product-specific and is required to identify the product. ## Quirk Use This plugin uses the following plugin-specific quirks: ### has-mstar-scaler USB Hub has a MStar Semiconductor Scaler attached via I²C. Since 1.7.6. ### has-public-key Device has a public-key appended to firmware. Since 1.8.0 ### GenesysUsbhubSwitchRequest USB Hub Switch Request value. * HP Mxfd FHD Monitors: `0xA1` Since 1.7.6. ### GenesysUsbhubReadRequest USB Hub Read Request value. * HP Mxfd FHD Monitors: `0xA2` Since 1.7.6. ### GenesysUsbhubWriteRequest USB Hub Write Request value. * HP Mxfd FHD Monitors: `0xA3` Since 1.7.6. ### use-i2c-ch0 Scalar uses I²C channel 0. Since 1.7.6. ### pause-r2-cpu Scalar pause R2 CPU. Since 1.7.6. ### GenesysScalerDeviceTransferSize Scaler Block size to use for transfers. * MStar Semiconductor TSUM G: `0x40` Since 1.7.6. ### GenesysScalerGpioOutputRegister Scaler GPIO Output Register value. * MStar Semiconductor TSUM G: `0x0426` Since 1.7.6. ### GenesysScalerGpioEnableRegister Scaler GPIO Enable Register value. * MStar Semiconductor TSUM G: `0x0428` Since 1.7.6. ### GenesysScalerGpioValue Scaler GPIO value. * MStar Semiconductor TSUM G: `0x01` Since 1.7.6. ### GenesysScalerCfiFlashId CFI Flash Id. * HP M24fd USB-C Monitor: `0xC22016` * HP M27fd USB-C Monitor: `0xC84016` Since 1.8.2. ## Runtime Requirement The USB Hub devices and its attached Scaler require libgusb version [0.3.8][1] or later to be detected. ## Update Behavior The devices are independently updated at runtime using USB control transfers. The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. The HP Mxfd FHD Monitors must be connected to host via the USB-C cable to apply an update. The devices remain functional during the update; the Scaler update is ~10 minute long. ## Vendor ID Security The vendor ID is set from the USB vendor, for example set to `USB:0x03F0` for HP. ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. [1]: https://github.com/hughsie/libgusb/commit/4e118c154dde70e196c4381bd97790a9413c3552 ## Version Considerations This plugin has been available since fwupd version `1.7.6`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Adam Chen: @adamgene fwupd-1.9.16/plugins/genesys/fu-genesys-common.h000066400000000000000000000032051460375044200216240ustar00rootroot00000000000000/* * Copyright (C) 2023 Adam.Chen * Copyright (C) 2021 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef struct { guint8 N[0x206]; guint8 E[0x00c]; } FuGenesysPublicKey; typedef struct { guint8 reg; guint8 expected_val; } FuGenesysWaitFlashRegisterHelper; typedef enum { ISP_MODEL_UNKNOWN, /* hub */ ISP_MODEL_HUB_GL3521, /* EOL */ ISP_MODEL_HUB_GL3523, ISP_MODEL_HUB_GL3510, ISP_MODEL_HUB_GL3590, ISP_MODEL_HUB_GL7000, ISP_MODEL_HUB_GL3525, /* pd */ ISP_MODEL_PD_GL9510, } FuGenesysModel; typedef struct { FuGenesysModel model; gint32 revision; } FuGenesysChip; #define GENESYS_USBHUB_FW_SIG_OFFSET 0xFC #define GENESYS_USBHUB_FW_SIG_LEN 4 #define GENESYS_USBHUB_FW_SIG_TEXT_HUB "XROM" #define GENESYS_USBHUB_FW_SIG_TEXT_DEV_BRIDGE "HOST" #define GENESYS_USBHUB_FW_SIG_TEXT_PD "PRDY" #define GENESYS_USBHUB_FW_CONFIGURATION_OFFSET 0x100 #define GENESYS_USBHUB_FW_CONFIGURATION_WITHOUT_SERIAL 0x55 #define GENESYS_USBHUB_FW_CONFIGURATION_WITH_SERIAL 0xAA #define GENESYS_USBHUB_FW_CONFIGURATION_NEW_FORMAT 0xA5 #define GENESYS_USBHUB_FW_CONFIGURATION_NEW_FORMAT_V2 0xA6 #define GENESYS_USBHUB_CODE_SIZE_OFFSET 0xFB #define GENESYS_USBHUB_VERSION_OFFSET 0x10E #define GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3521 0x221 #define GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3523 0x221 #define GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3590 0x241 #define GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3525 0x251 #define GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3525_V2 0x1E1 fwupd-1.9.16/plugins/genesys/fu-genesys-hubhid-device.c000066400000000000000000000244541460375044200230400ustar00rootroot00000000000000/* * Copyright (C) 2023 Adam.Chen * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-genesys-common.h" #include "fu-genesys-hubhid-device.h" #define GENESYS_HUBHID_REPORT_ID 0 #define GENESYS_HUBHID_REPORT_BYTE_LENGTH 0x40 #define GENESYS_HUBHID_REPORT_TIMEOUT 100 /* ms */ #define GENESYS_HUBHID_REPORT_FLAGS FU_HID_DEVICE_FLAG_ALLOW_TRUNC | FU_HID_DEVICE_FLAG_IS_FEATURE typedef union { struct { guint8 recipient : 2; /* GUsbDeviceRecipient */ guint8 reserved : 3; guint8 type : 2; /* GUsbDeviceRequestType */ guint8 dir : 1; /* GUsbDeviceDirection */ }; guint8 bm; } FuGenesysUsbRequestType; typedef struct { FuGenesysUsbRequestType req_type; guint8 request; guint16 value; guint16 index; guint16 length; } FuGenesysUsbSetup; struct _FuGenesysHubhidDevice { FuHidDevice parent_instance; gboolean support_report_pack; guint16 report_length; guint16 max_report_pack_data_length; }; G_DEFINE_TYPE(FuGenesysHubhidDevice, fu_genesys_hubhid_device, FU_TYPE_HID_DEVICE) static gboolean fu_genesys_hubhid_device_command_read(FuGenesysHubhidDevice *self, FuGenesysUsbSetup *setup, guint8 *data, gsize datasz, FuProgress *progress, GError **error) { FuHidDevice *hid_device = FU_HID_DEVICE(self); g_autoptr(GPtrArray) chunks = NULL; g_autoptr(GByteArray) buf_report = g_byte_array_new(); g_return_val_if_fail(datasz == setup->length, FALSE); fu_byte_array_set_size(buf_report, self->report_length, 0); /* set request data */ if (!fu_memcpy_safe(buf_report->data, buf_report->len, 0, /* dst */ (const guint8 *)setup, sizeof(FuGenesysUsbSetup), 0x0, /* src */ sizeof(FuGenesysUsbSetup), error)) return FALSE; /* send request report */ if (!fu_hid_device_set_report(hid_device, GENESYS_HUBHID_REPORT_ID, buf_report->data, buf_report->len, GENESYS_HUBHID_REPORT_TIMEOUT, GENESYS_HUBHID_REPORT_FLAGS, error)) return FALSE; if (setup->length == 0) { g_warning("read zero-length hid report"); return TRUE; } /* receive report */ chunks = fu_chunk_array_mutable_new(data, setup->length, 0, 0x0, buf_report->len); if (progress != NULL) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); } for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); memset(buf_report->data, 0, buf_report->len); if (!fu_hid_device_get_report(hid_device, GENESYS_HUBHID_REPORT_ID, buf_report->data, buf_report->len, GENESYS_HUBHID_REPORT_TIMEOUT, GENESYS_HUBHID_REPORT_FLAGS | FU_HID_DEVICE_FLAG_RETRY_FAILURE, error)) { g_prefix_error(error, "error getting report at 0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } if (!fu_memcpy_safe(fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), 0, /* dst */ buf_report->data, buf_report->len, 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "error getting report data at 0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } if (progress != NULL) fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_genesys_hubhid_device_can_pack_report(FuGenesysHubhidDevice *self, guint16 data_length) { if (!self->support_report_pack) return FALSE; if (data_length > self->max_report_pack_data_length) return FALSE; return TRUE; } static gboolean fu_genesys_hubhid_device_command_write(FuGenesysHubhidDevice *self, FuGenesysUsbSetup *setup, const guint8 *data, gsize datasz, FuProgress *progress, GError **error) { FuHidDevice *hid_device = FU_HID_DEVICE(self); gboolean pack_report = FALSE; g_autoptr(GByteArray) buf_report = g_byte_array_new(); g_return_val_if_fail(datasz == setup->length, FALSE); fu_byte_array_set_size(buf_report, self->report_length, 0); /* set request data */ if (!fu_memcpy_safe(buf_report->data, buf_report->len, 0, /* dst */ (const guint8 *)setup, sizeof(FuGenesysUsbSetup), 0x0, /* src */ sizeof(FuGenesysUsbSetup), error)) return FALSE; /* pack report if it can */ pack_report = fu_genesys_hubhid_device_can_pack_report(self, setup->length); if (pack_report) { if (setup->length > 0 && !fu_memcpy_safe(buf_report->data, buf_report->len, sizeof(FuGenesysUsbSetup), /* dst */ data, datasz, 0x0, /* src */ setup->length, error)) { g_prefix_error(error, "error packing request data: "); return FALSE; } } /* send request report */ if (!fu_hid_device_set_report(hid_device, GENESYS_HUBHID_REPORT_ID, buf_report->data, buf_report->len, GENESYS_HUBHID_REPORT_TIMEOUT, GENESYS_HUBHID_REPORT_FLAGS, error)) return FALSE; /* command completed after packed report sent */ if (pack_report) return TRUE; /* send report */ if (setup->length > 0) { g_autoptr(GPtrArray) chunks = fu_chunk_array_new(data, setup->length, 0, 0, buf_report->len); if (progress != NULL) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); } for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); memset(buf_report->data, 0, buf_report->len); if (!fu_memcpy_safe(buf_report->data, buf_report->len, 0, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "error setting report data at 0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } if (!fu_hid_device_set_report(hid_device, GENESYS_HUBHID_REPORT_ID, buf_report->data, buf_report->len, GENESYS_HUBHID_REPORT_TIMEOUT, GENESYS_HUBHID_REPORT_FLAGS | FU_HID_DEVICE_FLAG_RETRY_FAILURE, error)) { g_prefix_error(error, "error setting report at 0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } if (progress != NULL) fu_progress_step_done(progress); } } /* finish report */ if (!fu_hid_device_get_report(hid_device, GENESYS_HUBHID_REPORT_ID, buf_report->data, buf_report->len, GENESYS_HUBHID_REPORT_TIMEOUT, GENESYS_HUBHID_REPORT_FLAGS, error)) { g_prefix_error(error, "error finishing report: "); return FALSE; } /* success */ return TRUE; } gboolean fu_genesys_hubhid_device_send_report(FuGenesysHubhidDevice *self, FuProgress *progress, GUsbDeviceDirection direction, GUsbDeviceRequestType request_type, GUsbDeviceRecipient recipient, guint8 request, guint16 value, guint16 idx, guint8 *data, gsize datasz, GError **error) { g_autofree FuGenesysUsbSetup *setup = g_new0(FuGenesysUsbSetup, 1); setup->req_type.dir = (direction == G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST) ? 1 : 0; /* revert g_usb in/out dir to usb spec */ setup->req_type.type = request_type; setup->req_type.recipient = recipient; setup->request = request; setup->value = value; setup->index = idx; setup->length = datasz; if (direction == G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST) { return fu_genesys_hubhid_device_command_read(self, setup, data, datasz, progress, error); } else { return fu_genesys_hubhid_device_command_write(self, setup, data, datasz, progress, error); } } static gboolean fu_genesys_hubhid_device_validate_token(FuGenesysHubhidDevice *self, GError **error) { g_autoptr(GByteArray) buf_hid_token = NULL; g_autoptr(GByteArray) buf_data = g_byte_array_new(); g_autofree FuGenesysUsbSetup *setup = g_new0(FuGenesysUsbSetup, 1); buf_hid_token = fu_utf8_to_utf16_byte_array("GLI HID", G_LITTLE_ENDIAN, FU_UTF_CONVERT_FLAG_NONE, error); if (buf_hid_token == NULL) return FALSE; /* get 0x80 string descriptor */ setup->req_type.bm = 0x80; setup->request = 0x06; setup->value = (0x03 << 8) | 0x80; setup->index = 0; setup->length = 0x40; fu_byte_array_set_size(buf_data, setup->length, 0); if (!fu_genesys_hubhid_device_command_read(self, setup, buf_data->data, buf_data->len, NULL, error)) return FALSE; if (!fu_memcmp_safe(buf_data->data, buf_data->len, 0x2, buf_hid_token->data, buf_hid_token->len, 0, buf_hid_token->len, error)) { g_prefix_error(error, "wrong HID token string: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_hubhid_device_probe(FuDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); if (g_usb_device_get_device_class(usb_device) != G_USB_DEVICE_CLASS_INTERFACE_DESC) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not a hub hid"); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_hubhid_device_setup(FuDevice *device, GError **error) { FuGenesysHubhidDevice *self = FU_GENESYS_HUBHID_DEVICE(device); /* validate by string token */ if (!fu_genesys_hubhid_device_validate_token(self, error)) return FALSE; /* FuHidDevice->setup */ if (!FU_DEVICE_CLASS(fu_genesys_hubhid_device_parent_class)->setup(device, error)) { g_prefix_error(error, "error setting up device: "); return FALSE; } /* success */ return TRUE; } static void fu_genesys_hubhid_device_init(FuGenesysHubhidDevice *self) { self->support_report_pack = TRUE; self->report_length = GENESYS_HUBHID_REPORT_BYTE_LENGTH; self->max_report_pack_data_length = self->report_length - sizeof(FuGenesysUsbSetup); } static void fu_genesys_hubhid_device_class_init(FuGenesysHubhidDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_genesys_hubhid_device_probe; klass_device->setup = fu_genesys_hubhid_device_setup; } fwupd-1.9.16/plugins/genesys/fu-genesys-hubhid-device.h000066400000000000000000000013461460375044200230400ustar00rootroot00000000000000/* * Copyright (C) 2023 Adam.Chen * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_GENESYS_HUBHID_DEVICE (fu_genesys_hubhid_device_get_type()) G_DECLARE_FINAL_TYPE(FuGenesysHubhidDevice, fu_genesys_hubhid_device, FU, GENESYS_HUBHID_DEVICE, FuHidDevice) gboolean fu_genesys_hubhid_device_send_report(FuGenesysHubhidDevice *self, FuProgress *progress, GUsbDeviceDirection direction, GUsbDeviceRequestType request_type, GUsbDeviceRecipient recipient, guint8 request, guint16 value, guint16 idx, guint8 *data, gsize datasz, GError **error); fwupd-1.9.16/plugins/genesys/fu-genesys-plugin.c000066400000000000000000000052721460375044200216330ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-genesys-hubhid-device.h" #include "fu-genesys-plugin.h" #include "fu-genesys-scaler-firmware.h" #include "fu-genesys-usbhub-device.h" #include "fu-genesys-usbhub-firmware.h" struct _FuGenesysPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuGenesysPlugin, fu_genesys_plugin, FU_TYPE_PLUGIN) static void fu_genesys_plugin_init(FuGenesysPlugin *self) { } static void fu_genesys_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "GenesysScalerCfiFlashId"); fu_context_add_quirk_key(ctx, "GenesysScalerGpioOutputRegister"); fu_context_add_quirk_key(ctx, "GenesysScalerGpioEnableRegister"); fu_context_add_quirk_key(ctx, "GenesysScalerGpioValue"); fu_context_add_quirk_key(ctx, "GenesysUsbhubReadRequest"); fu_context_add_quirk_key(ctx, "GenesysUsbhubSwitchRequest"); fu_context_add_quirk_key(ctx, "GenesysUsbhubWriteRequest"); fu_plugin_add_device_gtype(plugin, FU_TYPE_GENESYS_USBHUB_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_GENESYS_HUBHID_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_GENESYS_USBHUB_FIRMWARE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_GENESYS_SCALER_FIRMWARE); } static FuDevice * fu_genesys_plugin_get_device_by_gusb_device(FuPlugin *self, GUsbDevice *gusb_device) { GPtrArray *devices = fu_plugin_get_devices(self); for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index(devices, i); if (!FU_IS_GENESYS_USBHUB_DEVICE(dev)) continue; if (fu_usb_device_get_dev(FU_USB_DEVICE(dev)) == gusb_device) return dev; } return NULL; } static void fu_genesys_plugin_device_added(FuPlugin *self, FuDevice *device) { GUsbDevice *gusb_parent = NULL; FuDevice *parent = NULL; /* link hid to parent hub */ if (!FU_IS_GENESYS_HUBHID_DEVICE(device)) return; gusb_parent = g_usb_device_get_parent(fu_usb_device_get_dev(FU_USB_DEVICE(device))); g_return_if_fail(gusb_parent); parent = fu_genesys_plugin_get_device_by_gusb_device(self, gusb_parent); if (parent == NULL) { g_warning("hubhid cannot find parent, platform_id(%s)", g_usb_device_get_platform_id(gusb_parent)); fu_plugin_device_remove(self, device); } else { fu_genesys_usbhub_device_set_hid_channel(parent, device); fu_device_add_child(parent, device); } } static void fu_genesys_plugin_class_init(FuGenesysPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_genesys_plugin_constructed; plugin_class->device_added = fu_genesys_plugin_device_added; } fwupd-1.9.16/plugins/genesys/fu-genesys-plugin.h000066400000000000000000000003531460375044200216330ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuGenesysPlugin, fu_genesys_plugin, FU, GENESYS_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/genesys/fu-genesys-scaler-device.c000066400000000000000000001573551460375044200230550ustar00rootroot00000000000000/* * Copyright (C) 2021 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-genesys-common.h" #include "fu-genesys-scaler-device.h" #include "fu-genesys-scaler-firmware.h" #define GENESYS_SCALER_BANK_SIZE 0x200000U #define GENESYS_SCALER_MSTAR_READ 0x7a #define GENESYS_SCALER_MSTAR_WRITE 0x7b #define GENESYS_SCALER_MSTAR_DATA_OUT 0x7c #define GENESYS_SCALER_MSTAR_DATA_IN 0x7f #define GENESYS_SCALER_CMD_DDCCI_FIRMWARE_PACKET_VERSION 0x06 #define GENESYS_SCALER_CMD_DATA_WRITE 0x10 #define GENESYS_SCALER_CMD_DATA_READ 0x11 #define GENESYS_SCALER_CMD_DATA_END 0x12 #define GENESYS_SCALER_INFO 0xA4 #define GENESYS_SCALER_USB_TIMEOUT 5000 /* 5s */ /** * FU_SCALER_FLAG_PAUSE_R2_CPU: * * Pause R2 CPU. * * Since 1.7.6 */ #define FU_SCALER_FLAG_PAUSE_R2_CPU (1 << 1) /** * FU_SCALER_FLAG_USE_I2C_CH0: * * Use I2C ch0. * * Since 1.7.6 */ #define FU_SCALER_FLAG_USE_I2C_CH0 (1 << 0) typedef struct { guint8 req_read; guint8 req_write; } FuGenesysVendorCommand; typedef struct { guint8 stage; guint8 model; guint8 major; guint8 minor; } FuGenesysScalerFirmwarePacketVersion; struct _FuGenesysScalerDevice { FuDevice parent_instance; guint8 level; FuGenesysPublicKey public_key; guint32 cfi_flash_id; FuCfiDevice *cfi_device; FuGenesysVendorCommand vc; guint32 sector_size; guint32 page_size; guint32 transfer_size; guint16 gpio_out_reg; guint16 gpio_en_reg; guint8 gpio_val; }; G_DEFINE_TYPE(FuGenesysScalerDevice, fu_genesys_scaler_device, FU_TYPE_DEVICE) static gboolean fu_genesys_scaler_device_enter_serial_debug_mode(FuGenesysScalerDevice *self, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data[] = {0x53, 0x45, 0x52, 0x44, 0x42}; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0001, /* value */ 0x0000, /* idx */ data, /* data */ sizeof(data), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error entering Serial Debug Mode: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 1); /* ms */ /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_exit_serial_debug_mode(FuGenesysScalerDevice *self, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data[] = {0x45}; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0001, /* value */ 0x0000, /* idx */ data, /* data */ sizeof(data), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error exiting Serial Debug Mode: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_enter_single_step_mode(FuGenesysScalerDevice *self, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data1[] = {0x10, 0xc0, 0xc1, 0x53}; guint8 data2[] = {0x10, 0x1f, 0xc1, 0x53}; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0001, /* value */ 0x0000, /* idx */ data1, /* data */ sizeof(data1), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error entering Single Step Mode: "); return FALSE; } if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0001, /* value */ 0x0000, /* idx */ data2, /* data */ sizeof(data2), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error entering Single Step Mode: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_exit_single_step_mode(FuGenesysScalerDevice *self, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data[] = {0x10, 0xc0, 0xc1, 0xff}; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0001, /* value */ 0x0000, /* idx */ data, /* data */ sizeof(data), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error exiting Single Step Mode: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_enter_debug_mode(FuGenesysScalerDevice *self, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data[] = {0x10, 0x00, 0x00, 0x00}; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0001, /* value */ 0x0000, /* idx */ data, /* data */ sizeof(data), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error entering Debug Mode: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_mst_i2c_bus_ctrl(FuGenesysScalerDevice *self, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data[] = {0x35, 0x71}; for (guint i = 0; i < sizeof(data); i++) { if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0001, /* value */ 0x0000, /* idx */ &data[i], /* data */ sizeof(data[i]), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error sending i2c bus ctrl 0x%02x: ", data[i]); return FALSE; } } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_mst_i2c_bus_switch_to_ch0(FuGenesysScalerDevice *self, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data[] = {0x80, 0x82, 0x84, 0x51, 0x7f, 0x37, 0x61}; for (guint i = 0; i < sizeof(data); i++) { if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0001, /* value */ 0x0000, /* idx */ &data[i], /* data */ sizeof(data[i]), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error sending i2c bus ch0 0x%02x: ", data[i]); return FALSE; } } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_mst_i2c_bus_switch_to_ch4(FuGenesysScalerDevice *self, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data[] = {0x80, 0x82, 0x85, 0x53, 0x7f}; for (guint i = 0; i < sizeof(data); i++) { if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0001, /* value */ 0x0000, /* idx */ &data[i], /* data */ sizeof(data[i]), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error sending i2c bus ch4 0x%02x: ", data[i]); return FALSE; } } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_disable_wp(FuGenesysScalerDevice *self, gboolean disable, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data_out[] = {0x10, 0x00 /* gpio_out_reg_h */, 0x00 /* gpio_out_reg_l */, 0x00 /* gpio_out_val */}; guint8 data_en[] = {0x10, 0x00 /* gpio_en_reg_h */, 0x00 /* gpio_en_reg_l */, 0x00 /* gpio_en_val */}; /* disable write protect [output] */ data_out[1] = (self->gpio_out_reg & 0xff00) >> 8; data_out[2] = self->gpio_out_reg & 0x00ff; /* read gpio-out register */ if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0003, /* value */ 0x0000, /* idx */ data_out, /* data */ sizeof(data_out) - 1, /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading GPIO-Out Register 0x%02x%02x: ", data_out[1], data_out[2]); return FALSE; } if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_read, 0x0003, /* value */ 0x0000, /* idx */ &data_out[3], /* data */ sizeof(data_out[3]), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading GPIO-Out Register 0x%02x%02x: ", data_out[1], data_out[2]); return FALSE; } if (data_out[3] == 0xff) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "error reading GPIO-Out Register 0x%02x%02x: ", data_out[1], data_out[2]); return FALSE; } if (disable) data_out[3] |= self->gpio_val; /* pull high */ else data_out[3] &= ~self->gpio_val; /* pull low */ /* write gpio-out register */ if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0001, /* value */ 0x0000, /* idx */ data_out, /* data */ sizeof(data_out), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error writing GPIO-Out Register 0x%02x%02x=0x%02x: ", data_out[1], data_out[2], data_out[3]); return FALSE; } /* disable write protect [enable] */ data_en[1] = (self->gpio_en_reg & 0xff00) >> 8; data_en[2] = self->gpio_en_reg & 0x00ff; /* read gpio-enable register */ if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0003, /* value */ 0x0000, /* idx */ data_en, /* data */ sizeof(data_en) - 1, /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error writing GPIO-Enable Register 0x%02x%02x: ", data_en[1], data_en[2]); return FALSE; } if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_read, 0x0003, /* value */ 0x0000, /* idx */ &data_en[3], /* data */ sizeof(data_en[3]), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading GPIO-Out Register 0x%02x%02x: ", data_en[1], data_en[2]); return FALSE; } if (data_en[3] == 0xff) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "error reading GPIO-Enable Register 0x%02x%02x: ", data_en[1], data_en[2]); return FALSE; } data_en[3] &= ~self->gpio_val; /* write gpio-enable register */ if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0001, /* value */ 0x0000, /* idx */ data_en, /* data */ sizeof(data_en), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error writing GPIO-Enable Register 0x%02x%02x=0x%02x: ", data_en[1], data_en[2], data_en[3]); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_pause_r2_cpu(FuGenesysScalerDevice *self, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data[] = {0x10, 0x00, 0x10, 0x0F, 0xD7, 0x00}; /* * MST9U only! * * Pause R2 CPU for preventing Scaler entering Power Saving Mode also * need for Disable SPI Flash Write Protect Mode. */ if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0003, /* value */ 0x0000, /* idx */ data, /* data */ sizeof(data) - 1, /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading register 0x%02x%02x%02x%02x%02x: ", data[0], data[1], data[2], data[3], data[4]); return FALSE; } if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_read, 0x0003, /* value */ 0x0000, /* idx */ &data[5], /* data */ sizeof(data[5]), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading register 0x%02x%02x%02x%02x%02x: ", data[0], data[1], data[2], data[3], data[4]); return FALSE; } if (data[5] == 0xff) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "error reading register 0x%02x%02x%02x%02x%02x: ", data[0], data[1], data[2], data[3], data[4]); return FALSE; } data[5] |= 0x80; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0003, /* value */ 0x0000, /* idx */ data, /* data */ sizeof(data), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error writing register 0x%02x%02x%02x%02x%02x: ", data[0], data[1], data[2], data[3], data[4]); return FALSE; } fu_device_sleep(FU_DEVICE(self), 200); /* ms */ /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_set_isp_mode(FuDevice *device, gpointer user_data, GError **error) { FuGenesysScalerDevice *self = user_data; FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data[] = {0x4d, 0x53, 0x54, 0x41, 0x52}; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0000, /* value */ 0x0000, /* idx */ data, /* data */ sizeof(data), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { return FALSE; } fu_device_sleep(device, 1); /* ms */ /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_enter_isp_mode(FuGenesysScalerDevice *self, GError **error) { /* * Enter ISP mode: * * Note: MStar application note say execute twice to avoid race * condition */ if (!fu_device_retry_full(FU_DEVICE(self), fu_genesys_scaler_device_set_isp_mode, 2, 1000 /* 1ms */, self, error)) { g_prefix_error(error, "error entering ISP mode: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_exit_isp_mode(FuGenesysScalerDevice *self, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data[] = {0x24}; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0000, /* value */ 0x0000, /* idx */ data, /* data */ sizeof(data), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error exiting ISP mode: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); /* * Important: do not change the order below; otherwise, unexpected * condition occurs. */ if (!fu_genesys_scaler_device_enter_serial_debug_mode(self, error)) return FALSE; if (!fu_genesys_scaler_device_enter_single_step_mode(self, error)) return FALSE; if (fu_device_has_private_flag(device, FU_SCALER_FLAG_USE_I2C_CH0)) if (!fu_genesys_scaler_device_mst_i2c_bus_switch_to_ch0(self, error)) return FALSE; if (!fu_genesys_scaler_device_enter_debug_mode(self, error)) return FALSE; if (!fu_genesys_scaler_device_mst_i2c_bus_ctrl(self, error)) return FALSE; if (!fu_genesys_scaler_device_disable_wp(self, TRUE, error)) return FALSE; if (fu_device_has_private_flag(device, FU_SCALER_FLAG_PAUSE_R2_CPU)) { if (!fu_genesys_scaler_device_mst_i2c_bus_switch_to_ch4(self, error)) return FALSE; if (!fu_genesys_scaler_device_mst_i2c_bus_ctrl(self, error)) return FALSE; if (!fu_genesys_scaler_device_pause_r2_cpu(self, error)) return FALSE; } if (!fu_genesys_scaler_device_enter_isp_mode(self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); if (!fu_genesys_scaler_device_exit_single_step_mode(self, error)) return FALSE; if (!fu_genesys_scaler_device_exit_serial_debug_mode(self, error)) return FALSE; if (!fu_genesys_scaler_device_exit_isp_mode(self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_get_level(FuGenesysScalerDevice *self, guint8 *level, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_SCALER_INFO, 0x0004, /* value */ 0x0000, /* idx */ level, /* data */ 1, /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error getting level: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 100); /* ms */ /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_get_version(FuGenesysScalerDevice *self, guint8 *buf, guint bufsz, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_SCALER_INFO, 0x0005, /* value */ 0x0000, /* idx */ buf, /* data */ bufsz, /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error getting version: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 100); /* ms */ /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_get_public_key(FuGenesysScalerDevice *self, guint8 *buf, guint bufsz, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); const gsize data_size = 0x20; g_autoptr(GPtrArray) chunks = NULL; chunks = fu_chunk_array_mutable_new(buf, bufsz, 0, 0, data_size); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_SCALER_INFO, 0x0006, /* value */ fu_chunk_get_address(chk), /* idx */ fu_chunk_get_data_out(chk), /* data */ fu_chunk_get_data_sz(chk), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error getting public key: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 100); /* ms */ } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_read_flash(FuGenesysScalerDevice *self, guint addr, guint8 *buf, guint bufsz, FuProgress *progress, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data1[] = { GENESYS_SCALER_CMD_DATA_WRITE, 0x00, /* Read Data command */ (addr & 0x00ff0000) >> 16, (addr & 0x0000ff00) >> 8, (addr & 0x000000ff), }; guint8 data2[] = { GENESYS_SCALER_CMD_DATA_READ, }; guint8 data3[] = { GENESYS_SCALER_CMD_DATA_END, }; g_autoptr(GPtrArray) chunks = NULL; if (!fu_cfi_device_get_cmd(self->cfi_device, FU_CFI_DEVICE_CMD_READ_DATA, &data1[1], error)) return FALSE; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0000, /* value */ 0x0000, /* idx */ data1, /* data */ sizeof(data1), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading flash at 0x%06x: ", addr); return FALSE; } if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0000, /* value */ 0x0000, /* idx */ data2, /* data */ sizeof(data2), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading flash at 0x%06x: ", addr); return FALSE; } chunks = fu_chunk_array_mutable_new(buf, bufsz, addr, 0, self->transfer_size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_read, 0x0000, /* value */ 0x0000, /* idx */ fu_chunk_get_data_out(chk), /* data */ fu_chunk_get_data_sz(chk), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading flash at 0x%06x: ", fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); } if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0000, /* value */ 0x0000, /* idx */ data3, /* data */ sizeof(data3), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading flash at 0x%06x: ", addr); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_wait_flash_control_register_cb(FuDevice *dev, gpointer user_data, GError **error) { FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(dev); FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); FuGenesysWaitFlashRegisterHelper *helper = user_data; guint8 status = 0; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_read, helper->reg << 8 | 0x04, /* value */ 0x0000, /* idx */ &status, /* data */ sizeof(status), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading flash control register: "); return FALSE; } if ((status & 0x81) != helper->expected_val) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "wrong value in flash control register"); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_flash_control_write_enable(FuGenesysScalerDevice *self, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data1[] = { GENESYS_SCALER_CMD_DATA_WRITE, 0x00, /* Write Enable command */ }; guint8 data2[] = { GENESYS_SCALER_CMD_DATA_END, }; if (!fu_cfi_device_get_cmd(self->cfi_device, FU_CFI_DEVICE_CMD_WRITE_EN, &data1[1], error)) return FALSE; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0000, /* value */ 0x0000, /* idx */ data1, /* data */ sizeof(data1), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error sending flash control write enable: "); return FALSE; } if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0000, /* value */ 0x0000, /* idx */ data2, /* data */ sizeof(data2), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error sending flash control write enable: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_flash_control_write_status(FuGenesysScalerDevice *self, guint8 status, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data1[] = { GENESYS_SCALER_CMD_DATA_WRITE, 0x00, /* Write Status command */ status, }; guint8 data2[] = { GENESYS_SCALER_CMD_DATA_END, }; if (!fu_cfi_device_get_cmd(self->cfi_device, FU_CFI_DEVICE_CMD_WRITE_STATUS, &data1[1], error)) return FALSE; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0000, /* value */ 0x0000, /* idx */ data1, /* data */ sizeof(data1), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error sending flash control write status 0x%02x: ", status); return FALSE; } if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0000, /* value */ 0x0000, /* idx */ data2, /* data */ sizeof(data2), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error sending flash control write status 0x%02x: ", status); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_flash_control_sector_erase(FuGenesysScalerDevice *self, guint addr, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); FuGenesysWaitFlashRegisterHelper helper = { .reg = 0x00, /* Read Status command */ .expected_val = 0, }; guint8 data1[] = { GENESYS_SCALER_CMD_DATA_WRITE, 0x00, /* Sector Erase command */ (addr & 0x00ff0000) >> 16, (addr & 0x0000ff00) >> 8, (addr & 0x000000ff), }; guint8 data2[] = { GENESYS_SCALER_CMD_DATA_END, }; if (!fu_cfi_device_get_cmd(self->cfi_device, FU_CFI_DEVICE_CMD_READ_STATUS, &helper.reg, error)) return FALSE; if (!fu_cfi_device_get_cmd(self->cfi_device, FU_CFI_DEVICE_CMD_SECTOR_ERASE, &data1[1], error)) return FALSE; if (!fu_genesys_scaler_device_flash_control_write_enable(self, error)) return FALSE; if (!fu_genesys_scaler_device_flash_control_write_status(self, 0x00, error)) return FALSE; /* 5s */ if (!fu_device_retry_full(FU_DEVICE(self), fu_genesys_scaler_device_wait_flash_control_register_cb, 100, 50, /* 50ms */ &helper, error)) { g_prefix_error(error, "error waiting for flash control read status register: "); return FALSE; } if (!fu_genesys_scaler_device_flash_control_write_enable(self, error)) return FALSE; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0000, /* value */ 0x0000, /* idx */ data1, /* data */ sizeof(data1), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error sending flash control erase at address 0x%06x: ", addr); return FALSE; } if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, 0x0000, /* value */ 0x0000, /* idx */ data2, /* data */ sizeof(data2), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error sending flash control erase at address 0x%06x: ", addr); return FALSE; } /* 5s */ if (!fu_device_retry_full(FU_DEVICE(self), fu_genesys_scaler_device_wait_flash_control_register_cb, 100, 50, /* 50ms */ &helper, error)) { g_prefix_error(error, "error waiting for flash control read status register: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_erase_flash(FuGenesysScalerDevice *self, guint addr, guint bufsz, FuProgress *progress, GError **error) { g_autoptr(GPtrArray) chunks = NULL; chunks = fu_chunk_array_new(NULL, bufsz, addr, 0, self->sector_size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_genesys_scaler_device_flash_control_sector_erase(self, fu_chunk_get_address(chk), error)) { g_prefix_error(error, "error erasing flash at address 0x%06x: ", fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_flash_control_page_program(FuGenesysScalerDevice *self, guint addr, const guint8 *buf, guint bufsz, FuProgress *progress, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); FuGenesysWaitFlashRegisterHelper helper = { .reg = 0x00, /* Read Status command */ .expected_val = 0, }; guint8 data1[] = { GENESYS_SCALER_CMD_DATA_WRITE, 0x00, /* Page Program command */ (addr & 0x00ff0000) >> 16, (addr & 0x0000ff00) >> 8, (addr & 0x000000ff), }; gsize datasz = 0; g_autoptr(GPtrArray) chunks = NULL; g_autofree guint8 *data = NULL; if (!fu_cfi_device_get_cmd(self->cfi_device, FU_CFI_DEVICE_CMD_READ_STATUS, &helper.reg, error)) return FALSE; if (!fu_cfi_device_get_cmd(self->cfi_device, FU_CFI_DEVICE_CMD_PAGE_PROG, &data1[1], error)) return FALSE; datasz = sizeof(data1) + bufsz; data = g_malloc0(datasz); if (!fu_memcpy_safe(data, datasz, 0, /* dst */ data1, sizeof(data1), 0, /* src */ sizeof(data1), error)) return FALSE; if (!fu_memcpy_safe(data, datasz, sizeof(data1), /* dst */ buf, bufsz, 0, /* src */ bufsz, error)) return FALSE; chunks = fu_chunk_array_mutable_new(data, datasz, addr + sizeof(data1), 0, self->transfer_size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint16 index = 0x0010 * (i + 1); /* last chunk */ if ((i + 1) == chunks->len) index |= 0x0080; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vc.req_write, index, /* value */ 0x0000, /* idx */ fu_chunk_get_data_out(chk), /* data */ fu_chunk_get_data_sz(chk), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error( error, "error sending flash control page program at address 0x%06x: ", fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); } /* 200ms */ if (!fu_device_retry(FU_DEVICE(self), fu_genesys_scaler_device_wait_flash_control_register_cb, 20, &helper, error)) { g_prefix_error(error, "error waiting for flash control read status register: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_write_sector(FuGenesysScalerDevice *self, guint addr, const guint8 *buf, guint bufsz, FuProgress *progress, GError **error) { g_autoptr(GPtrArray) chunks = NULL; chunks = fu_chunk_array_new(buf, bufsz, addr, 0, self->page_size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_genesys_scaler_device_flash_control_page_program( self, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_write_flash(FuGenesysScalerDevice *self, guint addr, const guint8 *buf, guint bufsz, FuProgress *progress, GError **error) { g_autoptr(GPtrArray) chunks = NULL; chunks = fu_chunk_array_new(buf, bufsz, addr, 0, self->sector_size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_genesys_scaler_device_write_sector(self, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } /* success */ return TRUE; } static guint8 fu_genesys_scaler_device_calculate_checksum(const guint8 *buf, gsize bufsz) { guint8 checksum = 0x00; for (gsize i = 0; i < bufsz; i++) checksum ^= buf[i]; return checksum; } static gboolean fu_genesys_scaler_device_get_ddcci_data(FuGenesysScalerDevice *self, guint8 cmd, guint8 *buf, guint bufsz, GError **error) { FuDevice *parent_device = fu_device_get_parent(FU_DEVICE(self)); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent_device)); guint8 data[] = {0x6e, 0x51, 0x83, 0xcd, 0x01, 0x00 /* command */, 0x00 /* checksum */}; data[5] = cmd; data[6] = fu_genesys_scaler_device_calculate_checksum(data, 6); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_SCALER_MSTAR_DATA_OUT, 0x0000, /* value */ 0x0000, /* idx */ data, /* data */ sizeof(data), /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error setting dddci data: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 100); /* 1ms */ if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_SCALER_MSTAR_DATA_IN, 0x0001, /* value */ 0x0000, /* idx */ buf, /* data */ bufsz, /* data length */ NULL, /* actual length */ GENESYS_SCALER_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error getting dddci data: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 100); /* ms */ /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_get_firmware_packet_version(FuGenesysScalerDevice *self, FuGenesysScalerFirmwarePacketVersion *ver, GError **error) { guint8 buf[0x40]; guint8 offset = 4; if (!fu_genesys_scaler_device_get_ddcci_data( self, GENESYS_SCALER_CMD_DDCCI_FIRMWARE_PACKET_VERSION, buf, sizeof(buf), error)) return FALSE; if (buf[0] == 0x6f && buf[1] == 0x6e) { gsize len = buf[2] ^ 0x80; guint8 checksum; guint8 checksum_tmp = 0x0; if (len > sizeof(buf) - 3) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "error dddci length too large, got 0x%x, expected <= 0x%zx: ", (guint)len, sizeof(buf)); return FALSE; } buf[0] = 0x50; /* drifted value */ checksum = fu_genesys_scaler_device_calculate_checksum(buf, len + 3); if (!fu_memread_uint8_safe(buf, sizeof(buf), len + 3, &checksum_tmp, error)) return FALSE; if (checksum_tmp != checksum) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "error dddci checksum mismatch, got 0x%02x, expected 0x%02x", checksum_tmp, checksum); return FALSE; } offset = 7; } ver->stage = buf[offset]; ver->model = buf[offset + 1]; ver->major = buf[offset + 2]; ver->minor = buf[offset + 3]; /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_probe(FuDevice *device, GError **error) { FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); FuGenesysScalerFirmwarePacketVersion ver; guint8 buf[7 + 1] = {0}; g_autofree gchar *guid = NULL; g_autofree gchar *version = NULL; g_autofree gchar *panelrev = NULL; if (!fu_genesys_scaler_device_get_level(self, &self->level, error)) return FALSE; if (!fu_genesys_scaler_device_get_public_key(self, (guint8 *)&self->public_key, sizeof(self->public_key), error)) return FALSE; if (memcmp(self->public_key.N, "N = ", 4) != 0 || memcmp(self->public_key.E, "E = ", 4) != 0) { fu_dump_raw(G_LOG_DOMAIN, "PublicKey", (const guint8 *)&self->public_key, sizeof(self->public_key)); g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "invalid public-key"); return FALSE; } guid = fwupd_guid_hash_data((const guint8 *)&self->public_key, sizeof(self->public_key), FWUPD_GUID_FLAG_NONE); if (!fu_genesys_scaler_device_get_version(self, buf, sizeof(buf), error)) return FALSE; /* ?xIM123; where ? is 0x06 (length?) */ panelrev = fu_memstrsafe(buf, sizeof(buf), 0x1, 6, error); if (panelrev == NULL) return FALSE; if (!fu_genesys_scaler_device_get_firmware_packet_version(self, &ver, error)) return FALSE; version = g_strdup_printf("%d.%d.%d.%d", ver.stage, ver.model, ver.major, ver.minor); fu_device_set_version(device, version); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_logical_id(device, "scaler"); /* add instance ID */ fu_device_add_instance_str(device, "MSTAR", "TSUM_G"); fu_device_add_instance_strup(device, "PUBKEY", guid); fu_device_add_instance_strup(device, "PANELREV", panelrev); if (!fu_device_build_instance_id(device, error, "GENESYS_SCALER", "MSTAR", "PUBKEY", NULL)) return FALSE; if (!fu_device_build_instance_id(device, error, "GENESYS_SCALER", "MSTAR", "PUBKEY", "PANELREV", NULL)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); self->vc.req_read = GENESYS_SCALER_MSTAR_READ; self->vc.req_write = GENESYS_SCALER_MSTAR_WRITE; if (self->level != 1) { self->vc.req_read += 3; self->vc.req_write += 3; } /* success */ return TRUE; } static gboolean fu_genesys_scaler_device_setup(FuDevice *device, GError **error) { FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); guint64 size_min = fu_device_get_firmware_size_max(device); guint64 size; guint32 sector_size; guint32 page_size; g_autofree gchar *flash_id = NULL; flash_id = g_strdup_printf("%06X", self->cfi_flash_id); self->cfi_device = fu_cfi_device_new(fu_device_get_context(FU_DEVICE(self)), flash_id); if (!fu_device_setup(FU_DEVICE(self->cfi_device), error)) return FALSE; sector_size = fu_cfi_device_get_sector_size(self->cfi_device); if (sector_size != 0) self->sector_size = sector_size; page_size = fu_cfi_device_get_page_size(self->cfi_device); if (page_size != 0) self->page_size = page_size; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE)) size_min *= 2; size = fu_device_get_firmware_size_max(FU_DEVICE(self->cfi_device)); if (size != 0 && size < size_min) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "CFI device too small, got 0x%x, expected >= 0x%x", (guint)size, (guint)size_min); return FALSE; } /* success */ return TRUE; } static GBytes * fu_genesys_scaler_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); gsize size = fu_cfi_device_get_size(self->cfi_device); g_autofree guint8 *buf = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 99, NULL); /* require detach -> attach */ locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_device_detach, (FuDeviceLockerFunc)fu_device_attach, error); if (locker == NULL) return NULL; fu_progress_step_done(progress); buf = g_malloc0(size); if (!fu_genesys_scaler_device_read_flash(self, 0, buf, size, fu_progress_get_child(progress), error)) return NULL; fu_progress_step_done(progress); /* success */ return g_bytes_new_take(g_steal_pointer(&buf), size); } static FuFirmware * fu_genesys_scaler_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); g_autoptr(FuFirmware) firmware = fu_genesys_scaler_firmware_new(); g_autoptr(GBytes) blob_payload = NULL; g_autoptr(GBytes) blob_public_key = NULL; /* parse firmware */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* check public-key */ blob_public_key = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_SIGNATURE, error); if (blob_public_key == NULL) return NULL; fu_dump_raw(G_LOG_DOMAIN, "PublicKey", g_bytes_get_data(blob_public_key, NULL), g_bytes_get_size(blob_public_key)); if (memcmp(g_bytes_get_data(blob_public_key, NULL), &self->public_key, sizeof(self->public_key)) != 0 && (flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "mismatch public-key"); return NULL; } /* check size */ blob_payload = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_PAYLOAD, error); if (blob_payload == NULL) return NULL; if (g_bytes_get_size(blob_payload) > fu_device_get_firmware_size_max(device)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware too large, got 0x%x, expected <= 0x%x", (guint)g_bytes_get_size(blob_payload), (guint)fu_device_get_firmware_size_max(device)); return NULL; } /* success */ return g_steal_pointer(&firmware); } static gboolean fu_genesys_scaler_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); guint addr = 0; gsize size; const guint8 *data; g_autofree guint8 *buf = NULL; g_autoptr(FuFirmware) payload = NULL; g_autoptr(GBytes) fw_payload = NULL; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 4, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 54, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 42, NULL); if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE)) addr = GENESYS_SCALER_BANK_SIZE; payload = fu_firmware_get_image_by_id(firmware, FU_FIRMWARE_ID_PAYLOAD, error); if (payload == NULL) return FALSE; fw_payload = fu_firmware_get_bytes(payload, error); if (fw_payload == NULL) return FALSE; data = g_bytes_get_data(fw_payload, &size); if (data == NULL) return FALSE; if (!fu_genesys_scaler_device_erase_flash(self, addr, size, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); if (!fu_genesys_scaler_device_write_flash(self, addr, data, size, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); buf = g_malloc0(size); if (!fu_genesys_scaler_device_read_flash(self, addr, buf, size, fu_progress_get_child(progress), error)) return FALSE; if (!fu_memcmp_safe(buf, size, 0x0, data, size, 0x0, size, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_genesys_scaler_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_genesys_scaler_device_to_string(FuDevice *device, guint idt, GString *str) { FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); gchar public_key_e[6 + 1] = {0}; gchar public_key_n[0x200 + 1] = {0}; g_autoptr(GError) error_local_e = NULL; g_autoptr(GError) error_local_n = NULL; fu_string_append_kx(str, idt, "Level", self->level); if (fu_memcpy_safe((guint8 *)public_key_e, sizeof(public_key_e), 0, /* dst */ (const guint8 *)&self->public_key, sizeof(self->public_key), sizeof(self->public_key) - 2 - (sizeof(public_key_e) - 1), /* src */ sizeof(public_key_e) - 1, &error_local_e)) { fu_string_append(str, idt, "PublicKeyE", public_key_e); } else { g_debug("ignoring public-key parameter E: %s", error_local_e->message); } if (fu_memcpy_safe((guint8 *)public_key_n, sizeof(public_key_n), 0, /* dst */ (const guint8 *)&self->public_key, sizeof(self->public_key), 4, /* src */ sizeof(public_key_n) - 1, &error_local_n)) { fu_string_append(str, idt, "PublicKeyN", public_key_n); } else { g_debug("ignoring public-key parameter N: %s", error_local_n->message); } fu_string_append_kx(str, idt, "ReadRequestRead", self->vc.req_read); fu_string_append_kx(str, idt, "WriteRequest", self->vc.req_write); fu_string_append_kx(str, idt, "SectorSize", self->sector_size); fu_string_append_kx(str, idt, "PageSize", self->page_size); fu_string_append_kx(str, idt, "TransferSize", self->transfer_size); fu_string_append_kx(str, idt, "GpioOutputRegister", self->gpio_out_reg); fu_string_append_kx(str, idt, "GpioEnableRegister", self->gpio_en_reg); fu_string_append_kx(str, idt, "GpioValue", self->gpio_val); fu_string_append_kx(str, idt, "CfiFlashId", self->cfi_flash_id); } static gboolean fu_genesys_scaler_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(device); guint64 tmp; if (g_strcmp0(key, "GenesysScalerDeviceTransferSize") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->transfer_size = tmp; /* success */ return TRUE; } if (g_strcmp0(key, "GenesysScalerGpioOutputRegister") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->gpio_out_reg = tmp; /* success */ return TRUE; } if (g_strcmp0(key, "GenesysScalerGpioEnableRegister") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->gpio_en_reg = tmp; /* success */ return TRUE; } if (g_strcmp0(key, "GenesysScalerGpioValue") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->gpio_val = tmp; /* success */ return TRUE; } if (g_strcmp0(key, "GenesysScalerCfiFlashId") == 0) { if (!fu_strtoull(value, &tmp, 0, 0x00ffffffU, error)) return FALSE; self->cfi_flash_id = tmp; /* success */ return TRUE; } /* failure */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_genesys_scaler_device_init(FuGenesysScalerDevice *self) { fu_device_set_vendor(FU_DEVICE(self), "MStar Semiconductor"); fu_device_set_name(FU_DEVICE(self), "TSUMG"); fu_device_add_protocol(FU_DEVICE(self), "com.mstarsemi.scaler"); fu_device_retry_set_delay(FU_DEVICE(self), 10); /* 10ms */ fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); fu_device_register_private_flag(FU_DEVICE(self), FU_SCALER_FLAG_PAUSE_R2_CPU, "pause-r2-cpu"); fu_device_register_private_flag(FU_DEVICE(self), FU_SCALER_FLAG_USE_I2C_CH0, "use-i2c-ch0"); fu_device_set_install_duration(FU_DEVICE(self), 730); /* 12min 10s */ self->sector_size = 0x1000; /* 4KB */ self->page_size = 0x100; /* 256B */ self->transfer_size = 0x40; /* 64B */ fu_device_set_firmware_size(FU_DEVICE(self), GENESYS_SCALER_BANK_SIZE); /* 2MB */ } static void fu_genesys_scaler_device_finalize(GObject *object) { FuGenesysScalerDevice *self = FU_GENESYS_SCALER_DEVICE(object); if (self->cfi_device != NULL) g_object_unref(self->cfi_device); G_OBJECT_CLASS(fu_genesys_scaler_device_parent_class)->finalize(object); } static void fu_genesys_scaler_device_class_init(FuGenesysScalerDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_genesys_scaler_device_finalize; klass_device->probe = fu_genesys_scaler_device_probe; klass_device->setup = fu_genesys_scaler_device_setup; klass_device->dump_firmware = fu_genesys_scaler_device_dump_firmware; klass_device->prepare_firmware = fu_genesys_scaler_device_prepare_firmware; klass_device->write_firmware = fu_genesys_scaler_device_write_firmware; klass_device->set_progress = fu_genesys_scaler_device_set_progress; klass_device->detach = fu_genesys_scaler_device_detach; klass_device->attach = fu_genesys_scaler_device_attach; klass_device->to_string = fu_genesys_scaler_device_to_string; klass_device->set_quirk_kv = fu_genesys_scaler_device_set_quirk_kv; } FuGenesysScalerDevice * fu_genesys_scaler_device_new(FuContext *ctx) { FuGenesysScalerDevice *device = NULL; device = g_object_new(FU_TYPE_GENESYS_SCALER_DEVICE, "context", ctx, NULL); return device; } fwupd-1.9.16/plugins/genesys/fu-genesys-scaler-device.h000066400000000000000000000006621460375044200230460ustar00rootroot00000000000000/* * Copyright (C) 2021 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_GENESYS_SCALER_DEVICE (fu_genesys_scaler_device_get_type()) G_DECLARE_FINAL_TYPE(FuGenesysScalerDevice, fu_genesys_scaler_device, FU, GENESYS_SCALER_DEVICE, FuDevice) FuGenesysScalerDevice * fu_genesys_scaler_device_new(FuContext *ctx); fwupd-1.9.16/plugins/genesys/fu-genesys-scaler-firmware.c000066400000000000000000000106041460375044200234130ustar00rootroot00000000000000/* * Copyright (C) 2021 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-genesys-common.h" #include "fu-genesys-scaler-firmware.h" struct _FuGenesysScalerFirmware { FuFirmwareClass parent_instance; FuGenesysPublicKey public_key; }; G_DEFINE_TYPE(FuGenesysScalerFirmware, fu_genesys_scaler_firmware, FU_TYPE_FIRMWARE) static gboolean fu_genesys_scaler_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuGenesysScalerFirmware *self = FU_GENESYS_SCALER_FIRMWARE(firmware); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(FuFirmware) firmware_payload = NULL; g_autoptr(FuFirmware) firmware_public_key = NULL; g_autoptr(GBytes) blob_payload = NULL; g_autoptr(GBytes) blob_public_key = NULL; if (!fu_memcpy_safe((guint8 *)&self->public_key, sizeof(self->public_key), 0, /* dst */ buf, bufsz, bufsz - sizeof(self->public_key), /* src */ sizeof(self->public_key), error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "PublicKey", (const guint8 *)&self->public_key, sizeof(self->public_key)); if (memcmp(self->public_key.N, "N = ", 4) != 0 || memcmp(self->public_key.E, "E = ", 4) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid public-key"); return FALSE; } /* set payload */ blob_payload = g_bytes_new(buf, bufsz - sizeof(self->public_key)); firmware_payload = fu_firmware_new_from_bytes(blob_payload); fu_firmware_set_id(firmware_payload, FU_FIRMWARE_ID_PAYLOAD); fu_firmware_add_image(firmware, firmware_payload); /* set public-key */ blob_public_key = g_bytes_new(&self->public_key, sizeof(self->public_key)); firmware_public_key = fu_firmware_new_from_bytes(blob_public_key); fu_firmware_set_id(firmware_public_key, FU_FIRMWARE_ID_SIGNATURE); fu_firmware_add_image(firmware, firmware_public_key); /* success */ return TRUE; } static void fu_genesys_scaler_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuGenesysScalerFirmware *self = FU_GENESYS_SCALER_FIRMWARE(firmware); gchar N[0x200 + 1] = {'\0'}; gchar E[0x006 + 1] = {'\0'}; memcpy(N, self->public_key.N + 4, sizeof(N) - 1); fu_xmlb_builder_insert_kv(bn, "N", N); memcpy(E, self->public_key.E + 4, sizeof(E) - 1); fu_xmlb_builder_insert_kv(bn, "E", E); } static gboolean fu_genesys_scaler_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuGenesysScalerFirmware *self = FU_GENESYS_SCALER_FIRMWARE(firmware); const gchar *tmp; /* optional properties */ tmp = xb_node_query_text(n, "N", NULL); if (tmp != NULL) { if (!fu_memcpy_safe((guint8 *)&self->public_key.N, sizeof(self->public_key.N), 0x0, /* dst */ (const guint8 *)tmp, strlen(tmp), 0x0, /* src */ strlen(tmp), error)) return FALSE; } tmp = xb_node_query_text(n, "E", NULL); if (tmp != NULL) { if (!fu_memcpy_safe((guint8 *)&self->public_key.E, sizeof(self->public_key.E), 0x0, /* dst */ (const guint8 *)tmp, strlen(tmp), 0x0, /* src */ strlen(tmp), error)) return FALSE; } /* success */ return TRUE; } static GByteArray * fu_genesys_scaler_firmware_write(FuFirmware *firmware, GError **error) { FuGenesysScalerFirmware *self = FU_GENESYS_SCALER_FIRMWARE(firmware); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) blob = NULL; /* payload */ blob = fu_firmware_get_bytes(firmware, error); if (blob == NULL) return NULL; fu_byte_array_append_bytes(buf, blob); /* public-key */ g_byte_array_append(buf, (const guint8 *)&self->public_key, sizeof(self->public_key)); /* success */ return g_steal_pointer(&buf); } static void fu_genesys_scaler_firmware_init(FuGenesysScalerFirmware *self) { } static void fu_genesys_scaler_firmware_class_init(FuGenesysScalerFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_genesys_scaler_firmware_parse; klass_firmware->export = fu_genesys_scaler_firmware_export; klass_firmware->build = fu_genesys_scaler_firmware_build; klass_firmware->write = fu_genesys_scaler_firmware_write; } FuFirmware * fu_genesys_scaler_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_GENESYS_SCALER_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/genesys/fu-genesys-scaler-firmware.h000066400000000000000000000007271460375044200234250ustar00rootroot00000000000000/* * Copyright (C) 2021 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_GENESYS_SCALER_FIRMWARE (fu_genesys_scaler_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuGenesysScalerFirmware, fu_genesys_scaler_firmware, FU, GENESYS_SCALER_FIRMWARE, FuFirmware) #define GENESYS_SCALER_BANK_SIZE 0x200000U FuFirmware * fu_genesys_scaler_firmware_new(void); fwupd-1.9.16/plugins/genesys/fu-genesys-usbhub-codesign-firmware.c000066400000000000000000000063241460375044200252270ustar00rootroot00000000000000/* * Copyright (C) 2023 Adam.Chen * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-genesys-common.h" #include "fu-genesys-usbhub-codesign-firmware.h" #include "fu-genesys-usbhub-struct.h" struct _FuGenesysUsbhubCodesignFirmware { FuFirmwareClass parent_instance; FuGenesysFwCodesign codesign; }; G_DEFINE_TYPE(FuGenesysUsbhubCodesignFirmware, fu_genesys_usbhub_codesign_firmware, FU_TYPE_FIRMWARE) gint fu_genesys_usbhub_codesign_firmware_get_codesign(FuGenesysUsbhubCodesignFirmware *self) { g_return_val_if_fail(FU_IS_GENESYS_USBHUB_CODESIGN_FIRMWARE(self), 0); return self->codesign; } static gboolean fu_genesys_usbhub_codesign_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { gsize code_size = g_bytes_get_size(fw) - offset; if (code_size != FU_STRUCT_GENESYS_FW_CODESIGN_INFO_RSA_SIZE && code_size != FU_STRUCT_GENESYS_FW_CODESIGN_INFO_ECDSA_SIZE) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unknown codesign format"); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_codesign_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuGenesysUsbhubCodesignFirmware *self = FU_GENESYS_USBHUB_CODESIGN_FIRMWARE(firmware); gsize bufsz = g_bytes_get_size(fw); gsize code_size = bufsz - offset; if (code_size == FU_STRUCT_GENESYS_FW_CODESIGN_INFO_RSA_SIZE) { if (!fu_struct_genesys_fw_codesign_info_rsa_validate_bytes(fw, offset, error)) { g_prefix_error(error, "not valid for codesign: "); return FALSE; } self->codesign = FU_GENESYS_FW_CODESIGN_RSA; } else if (code_size == FU_STRUCT_GENESYS_FW_CODESIGN_INFO_ECDSA_SIZE) { if (!fu_struct_genesys_fw_codesign_info_ecdsa_validate_bytes(fw, offset, error)) { g_prefix_error(error, "not valid for codesign: "); return FALSE; } self->codesign = FU_GENESYS_FW_CODESIGN_ECDSA; } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unknown file format at 0x%x:0x%x", (guint)offset, (guint)bufsz); return FALSE; } fu_firmware_set_id(firmware, fu_genesys_fw_type_to_string(FU_GENESYS_FW_TYPE_CODESIGN)); fu_firmware_set_idx(firmware, FU_GENESYS_FW_TYPE_CODESIGN); fu_firmware_set_size(firmware, code_size); return TRUE; } static void fu_genesys_usbhub_codesign_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuGenesysUsbhubCodesignFirmware *self = FU_GENESYS_USBHUB_CODESIGN_FIRMWARE(firmware); fu_xmlb_builder_insert_kv(bn, "codesign", fu_genesys_fw_codesign_to_string(self->codesign)); } static void fu_genesys_usbhub_codesign_firmware_init(FuGenesysUsbhubCodesignFirmware *self) { } static void fu_genesys_usbhub_codesign_firmware_class_init(FuGenesysUsbhubCodesignFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_genesys_usbhub_codesign_firmware_check_magic; klass_firmware->parse = fu_genesys_usbhub_codesign_firmware_parse; klass_firmware->export = fu_genesys_usbhub_codesign_firmware_export; } fwupd-1.9.16/plugins/genesys/fu-genesys-usbhub-codesign-firmware.h000066400000000000000000000010031460375044200252210ustar00rootroot00000000000000/* * Copyright (C) 2023 Adam.Chen * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_GENESYS_USBHUB_CODESIGN_FIRMWARE (fu_genesys_usbhub_codesign_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuGenesysUsbhubCodesignFirmware, fu_genesys_usbhub_codesign_firmware, FU, GENESYS_USBHUB_CODESIGN_FIRMWARE, FuFirmware) gint fu_genesys_usbhub_codesign_firmware_get_codesign(FuGenesysUsbhubCodesignFirmware *self); fwupd-1.9.16/plugins/genesys/fu-genesys-usbhub-dev-firmware.c000066400000000000000000000047771460375044200242240ustar00rootroot00000000000000/* * Copyright (C) 2023 Adam.Chen * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-genesys-common.h" #include "fu-genesys-usbhub-dev-firmware.h" #include "fu-genesys-usbhub-firmware.h" #include "fu-genesys-usbhub-struct.h" struct _FuGenesysUsbhubDevFirmware { FuFirmwareClass parent_instance; }; G_DEFINE_TYPE(FuGenesysUsbhubDevFirmware, fu_genesys_usbhub_dev_firmware, FU_TYPE_FIRMWARE) static gboolean fu_genesys_usbhub_dev_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { guint8 magic[4] = GENESYS_USBHUB_FW_SIG_TEXT_DEV_BRIDGE; return fu_memcmp_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset + GENESYS_USBHUB_FW_SIG_OFFSET, magic, sizeof(magic), 0x0, sizeof(magic), error); } static gboolean fu_genesys_usbhub_dev_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { gsize code_size = 0; g_autoptr(GBytes) fw_trunc = NULL; fu_firmware_set_id(firmware, fu_genesys_fw_type_to_string(FU_GENESYS_FW_TYPE_DEV_BRIDGE)); fu_firmware_set_idx(firmware, FU_GENESYS_FW_TYPE_DEV_BRIDGE); fu_firmware_set_alignment(firmware, FU_FIRMWARE_ALIGNMENT_1K); /* truncate to correct size */ if (!fu_genesys_usbhub_firmware_calculate_size(fw, offset, &code_size, error)) { g_prefix_error(error, "not valid for dev: "); return FALSE; } fw_trunc = fu_bytes_new_offset(fw, offset, code_size, error); if (fw_trunc == NULL) return FALSE; fu_firmware_set_bytes(firmware, fw_trunc); /* calculate checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { if (!fu_genesys_usbhub_firmware_verify_checksum(fw_trunc, error)) { g_prefix_error(error, "not valid for dev: "); return FALSE; } } /* get firmware version */ if (!fu_genesys_usbhub_firmware_ensure_version(firmware, error)) { g_prefix_error(error, "not valid for dev: "); return FALSE; } /* success */ return TRUE; } static void fu_genesys_usbhub_dev_firmware_init(FuGenesysUsbhubDevFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); } static void fu_genesys_usbhub_dev_firmware_class_init(FuGenesysUsbhubDevFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_genesys_usbhub_dev_firmware_check_magic; klass_firmware->parse = fu_genesys_usbhub_dev_firmware_parse; } fwupd-1.9.16/plugins/genesys/fu-genesys-usbhub-dev-firmware.h000066400000000000000000000006131460375044200242120ustar00rootroot00000000000000/* * Copyright (C) 2023 Adam.Chen * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_GENESYS_USBHUB_DEV_FIRMWARE (fu_genesys_usbhub_dev_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuGenesysUsbhubDevFirmware, fu_genesys_usbhub_dev_firmware, FU, GENESYS_USBHUB_DEV_FIRMWARE, FuFirmware) fwupd-1.9.16/plugins/genesys/fu-genesys-usbhub-device.c000066400000000000000000002707411460375044200230670ustar00rootroot00000000000000/* * Copyright (C) 2023 Adam.Chen * Copyright (C) 2022 Gaël PORTAY * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-genesys-common.h" #include "fu-genesys-hubhid-device.h" #include "fu-genesys-scaler-device.h" #include "fu-genesys-usbhub-codesign-firmware.h" #include "fu-genesys-usbhub-device.h" #include "fu-genesys-usbhub-firmware.h" #include "fu-genesys-usbhub-struct.h" /** * FU_GENESYS_USBHUB_FLAG_HAS_MSTAR_SCALER: * * Device has a MStar scaler attached via I2C. * * Since 1.7.6 */ #define FU_GENESYS_USBHUB_FLAG_HAS_MSTAR_SCALER (1 << 0) /** * FU_GENESYS_USBHUB_FLAG_HAS_PUBLIC_KEY: * * Device has a public-key appended to firmware. * * Since 1.8.0 */ #define FU_GENESYS_USBHUB_FLAG_HAS_PUBLIC_KEY (1 << 1) #define GENESYS_USBHUB_STATIC_TOOL_DESC_IDX_USB_3_0 0x84 #define GENESYS_USBHUB_DYNAMIC_TOOL_DESC_IDX_USB_3_0 0x85 #define GENESYS_USBHUB_STATIC_TOOL_DESC_IDX_USB_2_0 0x81 #define GENESYS_USBHUB_DYNAMIC_TOOL_DESC_IDX_USB_2_0 0x82 #define GENESYS_USBHUB_FW_INFO_DESC_IDX 0x83 #define GENESYS_USBHUB_VENDOR_SUPPORT_DESC_IDX 0x86 #define GENESYS_USBHUB_BRAND_PROJECT_DESC_IDX 0x8A #define GENESYS_USBHUB_GL_HUB_VERIFY 0x71 #define GENESYS_USBHUB_GL_HUB_SWITCH 0x81 #define GENESYS_USBHUB_GL_HUB_READ 0x82 #define GENESYS_USBHUB_GL_HUB_WRITE 0x83 #define GENESYS_USBHUB_GL_HUB_HW_SECURITY 0xAC #define GENESYS_USBHUB_ENCRYPT_REGION_START 0x01 #define GENESYS_USBHUB_ENCRYPT_REGION_END 0x15 #define GENESYS_USBHUB_USB_TIMEOUT 5000 /* ms */ #define GENESYS_USBHUB_FLASH_WRITE_TIMEOUT 500 /* ms */ #define GL3523_BONDING_VALID_BIT 0x0F #define GL3590_BONDING_VALID_BIT 0x7F #define GL3523_BONDING_FLASH_DUMP_LOCATION_BIT 1 << 4 #define GL3590_BONDING_FLASH_DUMP_LOCATION_BIT 1 << 7 typedef enum { ISP_EXIT, ISP_ENTER, } FuGenesysIspMode; typedef struct { guint8 req_switch; guint8 req_read; guint8 req_write; } FuGenesysVendorCommandSetting; typedef struct { guint16 cmd; guint16 len; } FuGenesysQueryRdidFormat; typedef enum { FW_BANK_1, FW_BANK_2, FW_BANK_COUNT } FuGenesysFwBank; typedef struct { FuGenesysChip chip; gboolean support_dual_bank; gboolean support_code_size; guint32 fw_bank_addr[FW_BANK_COUNT][FU_GENESYS_FW_TYPE_INSIDE_HUB_COUNT]; guint32 fw_bank_capacity[FU_GENESYS_FW_TYPE_INSIDE_HUB_COUNT]; guint32 fw_data_max_count; } FuGenesysModelSpec; struct _FuGenesysUsbhubDevice { FuUsbDevice parent_instance; GByteArray *st_static_ts; GByteArray *st_dynamic_ts; GByteArray *st_fwinfo_ts; GByteArray *st_vendor_ts; GByteArray *st_project_ts; FuGenesysVendorCommandSetting vcs; FuGenesysModelSpec spec; FuGenesysTsVersion tool_string_version; FuGenesysFwStatus running_bank; guint8 bonding; gboolean is_gl352350; /* model with unique codesign mechanism */ FuCfiDevice *cfi_device; guint32 flash_erase_delay; guint32 flash_write_delay; guint32 flash_block_size; guint32 flash_sector_size; guint32 flash_rw_size; guint16 fw_bank_code_sizes[FW_BANK_COUNT][FU_GENESYS_FW_TYPE_INSIDE_HUB_COUNT]; guint16 fw_bank_vers[FW_BANK_COUNT][FU_GENESYS_FW_TYPE_INSIDE_HUB_COUNT]; FuGenesysFwBank update_fw_banks[FU_GENESYS_FW_TYPE_INSIDE_HUB_COUNT]; /** * GL3523 hub default boot up on fw bank1 - even bank2's version is higher. * It only boots up on bank2 when bank1 is broken and bank2 is right. * * Therefore, we want bank1 always be last updated firmware. * Also, to fulfill dual bank mechanism, we shall keep at last one fw bank is right. * * For this purpose, we usually backup bank1 (right fw) to bank2, and update fw to bank1. * In rare case - bootup on bank2, we can update fw to bank1 and skip backup. */ gboolean backup_hub_fw_bank1; GBytes *hub_fw_bank1_data; /* restore hub bank1 fw for backup */ /* codesign info */ gboolean has_codesign; gboolean has_hw_codesign; FuGenesysVsCodesignCheck codesign_check; FuGenesysFwCodesign codesign; GByteArray *st_codesign; /* codesign info, may need to backup for GL352350 */ GByteArray *st_public_key; /* hid channel */ FuGenesysHubhidDevice *hid_channel; }; G_DEFINE_TYPE(FuGenesysUsbhubDevice, fu_genesys_usbhub_device, FU_TYPE_USB_DEVICE) void fu_genesys_usbhub_device_set_hid_channel(FuDevice *device, FuDevice *channel) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); g_return_if_fail(self); g_return_if_fail(FU_IS_GENESYS_HUBHID_DEVICE(channel)); if (self->hid_channel != NULL) { g_warning("already set hid_channel, physical_id(%s)", fu_device_get_physical_id(FU_DEVICE(self->hid_channel))); } else { self->hid_channel = FU_GENESYS_HUBHID_DEVICE(channel); /* align usb max length(0xffff) to usb2 packet size(0x40) */ self->flash_rw_size = 0xffc0; } } static gboolean fu_genesys_usbhub_device_ctrl_transfer(FuGenesysUsbhubDevice *self, FuProgress *progress, GUsbDeviceDirection direction, GUsbDeviceRequestType request_type, GUsbDeviceRecipient recipient, guint8 request, guint16 value, guint16 idx, guint8 *data, gsize length, gsize *actual_length, guint timeout, GCancellable *cancellable, GError **error) { if (self->hid_channel != NULL) { return fu_genesys_hubhid_device_send_report(self->hid_channel, progress, direction, request_type, recipient, request, value, idx, data, length, error); } else { return g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), direction, request_type, recipient, request, value, idx, data, length, actual_length, timeout, cancellable, error); } } static gboolean fu_genesys_usbhub_device_mstar_scaler_setup(FuGenesysUsbhubDevice *self, GError **error) { FuContext *ctx = fu_device_get_context(FU_DEVICE(self)); g_autoptr(FuGenesysScalerDevice) scaler_device = fu_genesys_scaler_device_new(ctx); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(scaler_device)); /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_read_flash(FuGenesysUsbhubDevice *self, guint start_addr, guint8 *buf, guint bufsz, FuProgress *progress, GError **error) { g_autoptr(GPtrArray) chunks = NULL; chunks = fu_chunk_array_mutable_new(buf, bufsz, start_addr, self->flash_block_size, self->flash_rw_size); if (progress != NULL) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); } for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); FuProgress *progress_child = NULL; if (progress != NULL) progress_child = fu_progress_get_child(progress); if (!fu_genesys_usbhub_device_ctrl_transfer( self, progress_child, /* progress */ G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vcs.req_read, (fu_chunk_get_page(chk) & 0x000f) << 12, /* value */ fu_chunk_get_address(chk) & 0xffff, /* idx */ fu_chunk_get_data_out(chk), /* data */ fu_chunk_get_data_sz(chk), /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading flash at 0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } if (progress != NULL) fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_compare_flash_blank(FuGenesysUsbhubDevice *self, guint start_addr, guint code_size, FuProgress *progress, GError **error) { guint read_addr = 0; guint read_size = 0; g_autoptr(GPtrArray) chunks = NULL; g_autoptr(GByteArray) read_buf = g_byte_array_new(); g_autoptr(GByteArray) blank_buf = g_byte_array_new(); if (code_size < 0x400) { /* for small data, compare entire size */ read_addr = start_addr; read_size = code_size; } else { /* for large data, compare last 1024 bytes */ read_addr = start_addr + code_size - 0x400; read_size = 0x400; } fu_byte_array_set_size(read_buf, self->flash_rw_size, 0xFF); fu_byte_array_set_size(blank_buf, self->flash_rw_size, 0xFF); chunks = fu_chunk_array_new(NULL, read_size, read_addr, self->flash_block_size, self->flash_rw_size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_genesys_usbhub_device_ctrl_transfer( self, fu_progress_get_child(progress), /* progress */ G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vcs.req_read, (fu_chunk_get_page(chk) & 0x000f) << 12, /* value */ fu_chunk_get_address(chk) & 0xffff, /* idx */ read_buf->data, /* data */ fu_chunk_get_data_sz(chk), /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading flash at 0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } if (!fu_memcmp_safe(read_buf->data, read_buf->len, 0x0, blank_buf->data, blank_buf->len, 0x0, fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "compare flash blank at 0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_compare_flash_data(FuGenesysUsbhubDevice *self, guint start_addr, const guint8 *buf, guint bufsz, FuProgress *progress, GError **error) { g_autoptr(GPtrArray) chunks = NULL; g_autoptr(GByteArray) read_buf = g_byte_array_new(); fu_byte_array_set_size(read_buf, self->flash_rw_size, 0xFF); chunks = fu_chunk_array_new(buf, bufsz, start_addr, self->flash_block_size, self->flash_rw_size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_genesys_usbhub_device_ctrl_transfer( self, fu_progress_get_child(progress), /* progress */ G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vcs.req_read, (fu_chunk_get_page(chk) & 0x000f) << 12, /* value */ fu_chunk_get_address(chk) & 0xffff, /* idx */ read_buf->data, /* data */ fu_chunk_get_data_sz(chk), /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading flash at 0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } if (!fu_memcmp_safe(read_buf->data, read_buf->len, 0x0, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "compare flash data failed at 0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_reset(FuGenesysUsbhubDevice *self, GError **error) { if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vcs.req_switch, 0x0003, /* value */ 0, /* idx */ NULL, /* data */ 0, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error resetting device: "); return FALSE; } /* success */ return TRUE; } static FuCfiDevice * fu_genesys_usbhub_device_cfi_setup(FuGenesysUsbhubDevice *self, GError **error) { const FuGenesysQueryRdidFormat rdid[] = { {.cmd = 0x1D02, .len = 0x02}, {.cmd = 0x4B01, .len = 0x02}, {.cmd = 0x9001, .len = 0x02}, {.cmd = 0x9f02, .len = 0x03}, {.cmd = 0xAB01, .len = 0x02}, }; for (guint8 i = 0; i < G_N_ELEMENTS(rdid); i++) { guint8 buf[3] = {0}; g_autoptr(GError) error_local = NULL; g_autoptr(FuCfiDevice) cfi_device = NULL; g_autofree gchar *flash_id = NULL; if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vcs.req_read, rdid[i].cmd, /* value */ 0, /* idx */ buf, /* data */ rdid[i].len, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error reading flash chip: "); return NULL; } if (rdid[i].len == 2) { flash_id = g_strdup_printf("%02X%02X", buf[0], buf[1]); } else if (rdid[i].len == 3) { flash_id = g_strdup_printf("%02X%02X%02X", buf[0], buf[1], buf[2]); } cfi_device = fu_cfi_device_new(fu_device_get_context(FU_DEVICE(self)), flash_id); if (cfi_device == NULL) continue; if (!fu_device_setup(FU_DEVICE(cfi_device), &error_local)) { g_debug("ignoring %s: %s", flash_id, error_local->message); continue; } if (fu_device_get_name(FU_DEVICE(cfi_device)) == NULL) { g_debug("read %#x: %s", rdid[i].cmd, flash_id); continue; } fu_dump_raw(G_LOG_DOMAIN, "Flash ID", buf, rdid[i].len); g_debug("CFI: %s", fu_device_get_name(FU_DEVICE(cfi_device))); return g_steal_pointer(&cfi_device); } /* failure */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no CFI device found"); return NULL; } static gboolean fu_genesys_usbhub_device_wait_flash_status_register_cb(FuDevice *device, gpointer user_data, GError **error) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); guint8 status = 0; FuGenesysWaitFlashRegisterHelper *helper = user_data; if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vcs.req_read, helper->reg << 8 | 0x02, /* value */ 0, /* idx */ &status, /* data */ 1, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error getting flash status register (0x%02x): ", helper->reg); return FALSE; } if (status != helper->expected_val) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "wrong value in flash status register"); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_set_isp_mode(FuGenesysUsbhubDevice *self, FuGenesysIspMode mode, GError **error) { if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vcs.req_switch, mode, /* value */ 0, /* idx */ NULL, /* data */ 0, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error setting isp mode - " "control transfer error (reg 0x%02x) ", self->vcs.req_switch); return FALSE; } if (mode == ISP_ENTER) { FuGenesysWaitFlashRegisterHelper helper = {.reg = 5, .expected_val = 0}; /* 150ms */ if (!fu_device_retry(FU_DEVICE(self), fu_genesys_usbhub_device_wait_flash_status_register_cb, 5, &helper, error)) { g_prefix_error(error, "error setting isp mode: "); return FALSE; } } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_authentication_request(FuGenesysUsbhubDevice *self, guint8 offset_start, guint8 offset_end, guint8 data_check, GError **error) { guint8 buf = 0; if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_USBHUB_GL_HUB_VERIFY, (offset_end << 8) | offset_start, /* value */ 0, /* idx */ &buf, /* data */ 1, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "control transfer error (req: 0x%0x): ", (guint)GENESYS_USBHUB_GL_HUB_VERIFY); return FALSE; } if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_USBHUB_GL_HUB_VERIFY, (offset_end << 8) | offset_start, /* value */ 1 | (data_check << 8), /* idx */ &buf, /* data */ 1, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "control transfer error (req: 0x%0x): ", (guint)GENESYS_USBHUB_GL_HUB_VERIFY); return FALSE; } if (buf != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "device authentication failed"); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_authenticate(FuGenesysUsbhubDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); guint8 low_byte; guint8 high_byte; guint8 temp_byte; guint8 offset_start; guint8 offset_end; if (self->vcs.req_switch == GENESYS_USBHUB_GL_HUB_SWITCH) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device authentication not supported"); return FALSE; } low_byte = g_usb_device_get_release(usb_device) & 0xff; high_byte = (g_usb_device_get_release(usb_device) & 0xff00) >> 8; temp_byte = low_byte ^ high_byte; offset_start = g_random_int_range(GENESYS_USBHUB_ENCRYPT_REGION_START, GENESYS_USBHUB_ENCRYPT_REGION_END - 1); offset_end = g_random_int_range(offset_start + 1, GENESYS_USBHUB_ENCRYPT_REGION_END); for (guint8 i = offset_start; i <= offset_end; i++) { temp_byte ^= self->st_fwinfo_ts->data[i]; } if (!fu_genesys_usbhub_device_authentication_request(self, offset_start, offset_end, temp_byte, error)) { g_prefix_error(error, "error authenticating device: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_enter_isp_mode(FuGenesysUsbhubDevice *self, GError **error) { if (self->has_codesign) { if (!fu_genesys_usbhub_device_authenticate(self, error)) return FALSE; } if (!fu_genesys_usbhub_device_set_isp_mode(self, ISP_ENTER, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_get_descriptor_data(GBytes *desc_bytes, guint8 *dst, guint dst_size, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(desc_bytes, &bufsz); if (bufsz <= 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "data is too small"); return FALSE; } memset(dst, 0, dst_size); /* discard first 2 bytes (desc. length and type) */ buf += 2; bufsz -= 2; for (gsize i = 0, j = 0; i < bufsz && j < dst_size; i += 2, j++) dst[j] = buf[i]; /* legacy hub replies "USB2.0 Hub" or "USB3.0 Hub" */ if (memcmp(dst, "USB", 3) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "tool string unsupported"); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_check_fw_signature(FuGenesysUsbhubDevice *self, FuGenesysFwType fw_type, int bank_num, GError **error) { guint8 sig[GENESYS_USBHUB_FW_SIG_LEN] = {0}; const gchar *sig_txt = NULL; g_return_val_if_fail(fw_type < FU_GENESYS_FW_TYPE_INSIDE_HUB_COUNT, FALSE); g_return_val_if_fail(bank_num < FW_BANK_COUNT, FALSE); /* get firmware signature from device */ if (!fu_genesys_usbhub_device_read_flash(self, self->spec.fw_bank_addr[bank_num][fw_type] + GENESYS_USBHUB_FW_SIG_OFFSET, sig, sizeof(sig), NULL, error)) { g_prefix_error(error, "error getting fw signature (bank %d) from device: ", bank_num); return FALSE; } /* select firmware signature text and compare */ switch (fw_type) { case FU_GENESYS_FW_TYPE_HUB: sig_txt = GENESYS_USBHUB_FW_SIG_TEXT_HUB; break; case FU_GENESYS_FW_TYPE_DEV_BRIDGE: sig_txt = GENESYS_USBHUB_FW_SIG_TEXT_DEV_BRIDGE; break; case FU_GENESYS_FW_TYPE_PD: sig_txt = GENESYS_USBHUB_FW_SIG_TEXT_PD; break; default: g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported firmware signature"); return FALSE; } if (memcmp(sig, sig_txt, sizeof(sig)) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "wrong firmware signature"); return FALSE; } /* success */ return TRUE; } /* read the code size from the firmware stored in the device */ static gboolean fu_genesys_usbhub_device_get_fw_bank_code_size(FuGenesysUsbhubDevice *self, FuGenesysFwType fw_type, int bank_num, GError **error) { guint8 kbs = 0; g_return_val_if_fail(fw_type < FU_GENESYS_FW_TYPE_INSIDE_HUB_COUNT, FALSE); g_return_val_if_fail(bank_num < FW_BANK_COUNT, FALSE); /* check firmware type available */ if (self->spec.fw_bank_capacity[fw_type] == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported firmware type %s", fu_genesys_fw_type_to_string(fw_type)); return FALSE; } /* check bank 2 available */ if (!self->spec.support_dual_bank && bank_num == FW_BANK_2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported dual bank"); return FALSE; } /* check firmware signature from device */ if (!fu_genesys_usbhub_device_check_fw_signature(self, fw_type, bank_num, error)) return FALSE; /* bank firmware use fixed code size */ if (!self->spec.support_code_size) { self->fw_bank_code_sizes[bank_num][fw_type] = self->spec.fw_bank_capacity[fw_type]; return TRUE; } /* get code size from device */ if (!fu_genesys_usbhub_device_read_flash(self, self->spec.fw_bank_addr[bank_num][fw_type] + GENESYS_USBHUB_CODE_SIZE_OFFSET, &kbs, 1, NULL, error)) { g_prefix_error(error, "error getting fw size from device: "); return FALSE; } self->fw_bank_code_sizes[bank_num][fw_type] = 1024 * kbs; /* success */ return TRUE; } /* read the version from the firmware stored in the device */ static gboolean fu_genesys_usbhub_device_get_fw_bank_version(FuGenesysUsbhubDevice *self, FuGenesysFwType fw_type, int bank_num, FuProgress *progress, GError **error) { gsize bufsz = 0; g_autoptr(GError) error_local = NULL; g_autoptr(GBytes) blob = NULL; g_autofree guint8 *buf = NULL; g_return_val_if_fail(fw_type < FU_GENESYS_FW_TYPE_INSIDE_HUB_COUNT, FALSE); g_return_val_if_fail(bank_num < FW_BANK_COUNT, FALSE); /* check firmware type available */ if (self->spec.fw_bank_capacity[fw_type] == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported firmware type %s", fu_genesys_fw_type_to_string(fw_type)); return FALSE; } /* get bank firmware code size */ if (!fu_genesys_usbhub_device_get_fw_bank_code_size(self, fw_type, bank_num, &error_local)) { g_debug("ignoring firmware %s bank%d: %s", fu_genesys_fw_type_to_string(fw_type), bank_num + 1, error_local->message); self->fw_bank_vers[bank_num][fw_type] = 0; return TRUE; } /* get bank firmware from device */ bufsz = self->fw_bank_code_sizes[bank_num][fw_type]; buf = g_malloc0(bufsz); if (!fu_genesys_usbhub_device_read_flash(self, self->spec.fw_bank_addr[bank_num][fw_type], buf, bufsz, progress, error)) return FALSE; /* verify bank firmware integrity */ blob = g_bytes_new_take(g_steal_pointer(&buf), bufsz); if (!fu_genesys_usbhub_firmware_verify_checksum(blob, &error_local)) { g_debug("ignoring firmware %s bank%d: %s", fu_genesys_fw_type_to_string(fw_type), bank_num + 1, error_local->message); self->fw_bank_vers[bank_num][fw_type] = 0; return TRUE; } /* get firmware version from bank firmware */ if (!fu_memread_uint16_safe(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), GENESYS_USBHUB_VERSION_OFFSET, &self->fw_bank_vers[bank_num][fw_type], G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to get version: "); return FALSE; } /* keep hub bank 1 fw that may needs backup to bank2 later */ if (self->spec.chip.model == ISP_MODEL_HUB_GL3523 && bank_num == FW_BANK_1) self->hub_fw_bank1_data = g_steal_pointer(&blob); /* success */ return TRUE; } /* read the public-key from the firmware stored in the device */ static gboolean fu_genesys_usbhub_device_get_public_key(FuGenesysUsbhubDevice *self, int bank_num, GError **error) { gsize bufsz = self->spec.fw_bank_capacity[FU_GENESYS_FW_TYPE_CODESIGN]; g_autofree guint8 *buf = NULL; g_autofree gchar *guid = NULL; g_return_val_if_fail(bank_num < FW_BANK_COUNT, FALSE); /* check public-key available */ if (bufsz == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported public-key"); return FALSE; } /* get public-key from device */ buf = g_malloc0(bufsz); if (!fu_genesys_usbhub_device_read_flash( self, self->spec.fw_bank_addr[bank_num][FU_GENESYS_FW_TYPE_CODESIGN], buf, bufsz, NULL, error)) { g_prefix_error(error, "error getting public-key from device: "); return FALSE; } /* validate and parse public-key */ if (self->has_hw_codesign) { /* master device has ECDSA key and signature only */ if (fu_struct_genesys_fw_ecdsa_public_key_validate(buf, bufsz, 0, NULL)) { self->codesign = FU_GENESYS_FW_CODESIGN_ECDSA; self->st_codesign = fu_struct_genesys_fw_ecdsa_public_key_parse(buf, bufsz, 0, error); self->st_public_key = g_byte_array_ref(self->st_codesign); } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device does not have public-key"); return FALSE; } } else { if (fu_struct_genesys_fw_rsa_public_key_text_validate(buf, bufsz, 0, NULL)) { self->codesign = FU_GENESYS_FW_CODESIGN_RSA; self->st_codesign = fu_struct_genesys_fw_rsa_public_key_text_parse(buf, bufsz, 0, error); self->st_public_key = g_byte_array_ref(self->st_codesign); } else if (fu_struct_genesys_fw_codesign_info_ecdsa_validate(buf, bufsz, 0, NULL)) { /* slave device has completely ECDSA codesign info */ gsize keysz = 0; const guint8 *key = NULL; self->codesign = FU_GENESYS_FW_CODESIGN_ECDSA; self->st_codesign = fu_struct_genesys_fw_codesign_info_ecdsa_parse(buf, bufsz, 0, error); key = fu_struct_genesys_fw_codesign_info_ecdsa_get_key(self->st_codesign, &keysz); self->st_public_key = g_byte_array_new(); g_byte_array_append(self->st_public_key, key, keysz); } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device does not have public-key"); return FALSE; } } /* add PUBKEY product instance */ guid = fwupd_guid_hash_data(self->st_public_key->data, self->st_public_key->len, FWUPD_GUID_FLAG_NONE); fu_device_add_instance_strup(FU_DEVICE(self), "PUBKEY", guid); /* success */ return TRUE; } static gint fu_genesys_tsdigit_value(gchar c) { if (c >= 'A' && c <= 'Z') return c - 'A' + 10; if (c >= 'a' && c <= 'z') return c - 'a' + 10; return g_ascii_digit_value(c); } static gboolean fu_genesys_usbhub_device_validate_token(FuGenesysUsbhubDevice *self, GError **error) { const gchar *valid_tokens[] = {"GL 3.0 Hub", "GL 3.1 Hub", NULL}; g_autofree gchar *token = NULL; g_autoptr(GBytes) token_blob = NULL; g_autoptr(GBytes) token_blob_trunc = NULL; g_autoptr(GError) error_local = NULL; /* get 0x80 string descriptor */ token_blob = g_usb_device_get_string_descriptor_bytes_full( fu_usb_device_get_dev(FU_USB_DEVICE(self)), 0x80, G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES, 64, &error_local); if (token_blob == NULL) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_IO)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot get 0x80 string descriptor: %s", error_local->message); return FALSE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } token_blob_trunc = fu_bytes_new_offset(token_blob, 0x2, g_bytes_get_size(token_blob) - 0x2, error); if (token_blob_trunc == NULL) return FALSE; token = fu_utf16_to_utf8_bytes(token_blob_trunc, G_LITTLE_ENDIAN, error); if (token == NULL) return FALSE; /* supported strings */ if (g_strv_contains(valid_tokens, token)) return TRUE; /* failed */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not a valid hub: %s", token); return FALSE; } static gboolean fu_genesys_usbhub_device_get_info_from_static_ts(FuGenesysUsbhubDevice *self, const guint8 *buf, gsize bufsz, GError **error) { g_autofree gchar *project_ic_type = NULL; self->st_static_ts = fu_struct_genesys_ts_static_parse(buf, bufsz, 0, error); if (self->st_static_ts == NULL) { g_prefix_error(error, "failed to parse static tool info: "); return FALSE; } project_ic_type = fu_struct_genesys_ts_static_get_mask_project_ic_type(self->st_static_ts); /* verify chip model and revision */ if (memcmp(project_ic_type, "3521", 4) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ic type %s already EOL and not supported", project_ic_type); return FALSE; } else if (memcmp(project_ic_type, "3523", 4) == 0) { self->spec.chip.model = ISP_MODEL_HUB_GL3523; } else if (memcmp(project_ic_type, "3590", 4) == 0) { self->spec.chip.model = ISP_MODEL_HUB_GL3590; } else if (memcmp(project_ic_type, "3525", 4) == 0) { self->spec.chip.model = ISP_MODEL_HUB_GL3525; } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported ic type %s", project_ic_type); return FALSE; } self->spec.chip.revision = 10 * (project_ic_type[4] - '0') + (project_ic_type[5] - '0'); /* convert tool string version */ self->tool_string_version = fu_struct_genesys_ts_static_get_tool_string_version(self->st_static_ts); /* setup firmware parameters */ switch (self->spec.chip.model) { case ISP_MODEL_HUB_GL3521: self->spec.support_dual_bank = FALSE; self->spec.support_code_size = FALSE; self->spec.fw_bank_addr[FW_BANK_1][FU_GENESYS_FW_TYPE_HUB] = 0x0000; self->spec.fw_bank_capacity[FU_GENESYS_FW_TYPE_HUB] = 0x5000; self->spec.fw_data_max_count = 0x5000; break; case ISP_MODEL_HUB_GL3523: self->spec.support_dual_bank = TRUE; self->spec.fw_bank_addr[FW_BANK_1][FU_GENESYS_FW_TYPE_HUB] = 0x0000; self->spec.fw_bank_addr[FW_BANK_2][FU_GENESYS_FW_TYPE_HUB] = 0x8000; if (self->spec.chip.revision == 50) { self->is_gl352350 = TRUE; self->spec.support_code_size = TRUE; self->spec.fw_bank_addr[FW_BANK_1][FU_GENESYS_FW_TYPE_CODESIGN] = 0x7C00; self->spec.fw_bank_addr[FW_BANK_2][FU_GENESYS_FW_TYPE_CODESIGN] = 0xFC00; self->spec.fw_bank_capacity[FU_GENESYS_FW_TYPE_HUB] = 0x8000; } else { self->spec.support_code_size = FALSE; self->spec.fw_bank_addr[FW_BANK_1][FU_GENESYS_FW_TYPE_CODESIGN] = 0x6000; self->spec.fw_bank_addr[FW_BANK_2][FU_GENESYS_FW_TYPE_CODESIGN] = 0xE000; self->spec.fw_bank_capacity[FU_GENESYS_FW_TYPE_HUB] = 0x6000; } self->spec.fw_bank_capacity[FU_GENESYS_FW_TYPE_CODESIGN] = 0x400; self->spec.fw_data_max_count = 0x10000; break; case ISP_MODEL_HUB_GL3590: self->spec.support_dual_bank = TRUE; self->spec.support_code_size = TRUE; self->spec.fw_bank_addr[FW_BANK_1][FU_GENESYS_FW_TYPE_HUB] = 0x0000; self->spec.fw_bank_addr[FW_BANK_2][FU_GENESYS_FW_TYPE_HUB] = 0x10000; self->spec.fw_bank_addr[FW_BANK_1][FU_GENESYS_FW_TYPE_DEV_BRIDGE] = 0x20000; self->spec.fw_bank_addr[FW_BANK_2][FU_GENESYS_FW_TYPE_DEV_BRIDGE] = 0x30000; self->spec.fw_bank_addr[FW_BANK_1][FU_GENESYS_FW_TYPE_CODESIGN] = 0xFF00; self->spec.fw_bank_addr[FW_BANK_2][FU_GENESYS_FW_TYPE_CODESIGN] = 0x1FF00; self->spec.fw_bank_capacity[FU_GENESYS_FW_TYPE_HUB] = 0x10000; self->spec.fw_bank_capacity[FU_GENESYS_FW_TYPE_DEV_BRIDGE] = 0x10000; self->spec.fw_bank_capacity[FU_GENESYS_FW_TYPE_CODESIGN] = 0x100; self->spec.fw_data_max_count = 0x40000; break; case ISP_MODEL_HUB_GL3525: self->spec.support_dual_bank = TRUE; self->spec.support_code_size = TRUE; self->spec.fw_bank_addr[FW_BANK_1][FU_GENESYS_FW_TYPE_HUB] = 0x0000; self->spec.fw_bank_addr[FW_BANK_2][FU_GENESYS_FW_TYPE_HUB] = 0xB000; self->spec.fw_bank_addr[FW_BANK_1][FU_GENESYS_FW_TYPE_PD] = 0x16000; self->spec.fw_bank_addr[FW_BANK_2][FU_GENESYS_FW_TYPE_PD] = 0x23000; self->spec.fw_bank_addr[FW_BANK_1][FU_GENESYS_FW_TYPE_DEV_BRIDGE] = 0x30000; self->spec.fw_bank_addr[FW_BANK_2][FU_GENESYS_FW_TYPE_DEV_BRIDGE] = 0x38000; self->spec.fw_bank_addr[FW_BANK_1][FU_GENESYS_FW_TYPE_CODESIGN] = 0x16000; self->spec.fw_bank_addr[FW_BANK_2][FU_GENESYS_FW_TYPE_CODESIGN] = 0x17000; self->spec.fw_bank_capacity[FU_GENESYS_FW_TYPE_HUB] = 0xB000; self->spec.fw_bank_capacity[FU_GENESYS_FW_TYPE_PD] = 0xD000; self->spec.fw_bank_capacity[FU_GENESYS_FW_TYPE_DEV_BRIDGE] = 0x8000; self->spec.fw_bank_capacity[FU_GENESYS_FW_TYPE_CODESIGN] = 0x1000; self->spec.fw_data_max_count = 0x40000; break; default: break; } /* add IC product instance */ fu_device_add_instance_str(FU_DEVICE(self), "IC", project_ic_type); /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_get_info_from_dynamic_ts(FuGenesysUsbhubDevice *self, const guint8 *buf, gsize bufsz, GError **error) { gint ss_port_number = 0; gint hs_port_number = 0; gchar running_mode = 0; guint8 bonding = 0; guint8 portnum = 0; gboolean flash_dump_location_bit = FALSE; g_autofree gchar *rm_st = NULL; g_autofree gchar *ss_st = NULL; g_autofree gchar *hs_st = NULL; g_autofree gchar *bonding_st = NULL; /* bonding is not supported */ if (self->tool_string_version < FU_GENESYS_TS_VERSION_BONDING) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "legacy model 0x%02x not supported", self->spec.chip.model); return FALSE; } /* get running mode, portnum, bonding and flash dump location bit */ switch (self->spec.chip.model) { case ISP_MODEL_HUB_GL3523: self->st_dynamic_ts = fu_struct_genesys_ts_dynamic_gl3523_parse(buf, bufsz, 0, error); if (self->st_dynamic_ts == NULL) { g_prefix_error(error, "failed to parse dynamic tool info: "); return FALSE; } rm_st = fu_struct_genesys_ts_dynamic_gl3523_get_running_mode(self->st_dynamic_ts); ss_st = fu_struct_genesys_ts_dynamic_gl3523_get_ss_port_number(self->st_dynamic_ts); hs_st = fu_struct_genesys_ts_dynamic_gl3523_get_hs_port_number(self->st_dynamic_ts); bonding_st = fu_struct_genesys_ts_dynamic_gl3523_get_bonding(self->st_dynamic_ts); bonding = fu_genesys_tsdigit_value(bonding_st[0]); if (self->tool_string_version < FU_GENESYS_TS_VERSION_BONDING_QC) bonding <<= 1; self->bonding = bonding & GL3523_BONDING_VALID_BIT; flash_dump_location_bit = (bonding & GL3523_BONDING_FLASH_DUMP_LOCATION_BIT) > 0; break; case ISP_MODEL_HUB_GL3590: if (self->spec.chip.revision == 30) { self->st_dynamic_ts = fu_struct_genesys_ts_dynamic_gl359030_parse(buf, bufsz, 0, error); if (self->st_dynamic_ts == NULL) { g_prefix_error(error, "failed to parse dynamic tool info: "); return FALSE; } rm_st = fu_struct_genesys_ts_dynamic_gl359030_get_running_mode( self->st_dynamic_ts); ss_st = fu_struct_genesys_ts_dynamic_gl359030_get_ss_port_number( self->st_dynamic_ts); hs_st = fu_struct_genesys_ts_dynamic_gl359030_get_hs_port_number( self->st_dynamic_ts); self->bonding = fu_struct_genesys_ts_dynamic_gl359030_get_bonding(self->st_dynamic_ts); flash_dump_location_bit = fu_struct_genesys_ts_dynamic_gl359030_get_hub_fw_status( self->st_dynamic_ts) == FU_GENESYS_FW_STATUS_BANK2; } else { self->st_dynamic_ts = fu_struct_genesys_ts_dynamic_gl3590_parse(buf, bufsz, 0, error); if (self->st_dynamic_ts == NULL) { g_prefix_error(error, "failed to parse dynamic tool info: "); return FALSE; } rm_st = fu_struct_genesys_ts_dynamic_gl3590_get_running_mode( self->st_dynamic_ts); ss_st = fu_struct_genesys_ts_dynamic_gl3590_get_ss_port_number( self->st_dynamic_ts); hs_st = fu_struct_genesys_ts_dynamic_gl3590_get_hs_port_number( self->st_dynamic_ts); bonding = fu_struct_genesys_ts_dynamic_gl3590_get_bonding(self->st_dynamic_ts); self->bonding = bonding & GL3590_BONDING_VALID_BIT; flash_dump_location_bit = (bonding & GL3590_BONDING_FLASH_DUMP_LOCATION_BIT) > 0; } break; case ISP_MODEL_HUB_GL3525: self->st_dynamic_ts = fu_struct_genesys_ts_dynamic_gl3525_parse(buf, bufsz, 0, error); if (self->st_dynamic_ts == NULL) { g_prefix_error(error, "failed to parse dynamic tool info: "); return FALSE; } rm_st = fu_struct_genesys_ts_dynamic_gl3525_get_running_mode(self->st_dynamic_ts); ss_st = fu_struct_genesys_ts_dynamic_gl3525_get_ss_port_number(self->st_dynamic_ts); hs_st = fu_struct_genesys_ts_dynamic_gl3525_get_hs_port_number(self->st_dynamic_ts); self->bonding = fu_struct_genesys_ts_dynamic_gl3525_get_bonding(self->st_dynamic_ts); flash_dump_location_bit = fu_struct_genesys_ts_dynamic_gl3525_get_hub_fw_status( self->st_dynamic_ts) == FU_GENESYS_FW_STATUS_BANK2; break; default: g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported model 0x%02x", self->spec.chip.model); return FALSE; } running_mode = rm_st[0]; ss_port_number = fu_genesys_tsdigit_value(ss_st[0]); hs_port_number = fu_genesys_tsdigit_value(hs_st[0]); if (running_mode == 'M') { self->running_bank = FU_GENESYS_FW_STATUS_MASK; } else if (flash_dump_location_bit) { self->running_bank = FU_GENESYS_FW_STATUS_BANK2; } else { self->running_bank = FU_GENESYS_FW_STATUS_BANK1; } portnum = ss_port_number << 4 | hs_port_number; /* add specific product info */ fu_device_add_instance_u8(FU_DEVICE(self), "PORTNUM", portnum); fu_device_add_instance_u8(FU_DEVICE(self), "BONDING", self->bonding); /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_get_info_from_vendor_ts(FuGenesysUsbhubDevice *self, const guint8 *buf, gsize bufsz, GError **error) { self->st_vendor_ts = fu_struct_genesys_ts_vendor_support_parse(buf, bufsz, 0, error); if (self->st_vendor_ts == NULL) { g_prefix_error(error, "failed to parse vendor support tool info: "); return FALSE; } self->codesign_check = fu_struct_genesys_ts_vendor_support_get_codesign_check(self->st_vendor_ts); if (self->codesign_check > FU_GENESYS_VS_CODESIGN_CHECK_UNSUPPORTED) { self->vcs.req_switch = 0xA1; self->vcs.req_read = 0xA2; self->vcs.req_write = 0xA3; self->has_codesign = TRUE; self->has_hw_codesign = self->codesign_check == FU_GENESYS_VS_CODESIGN_CHECK_HW; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_get_info_from_project_ts(FuGenesysUsbhubDevice *self, const guint8 *buf, gsize bufsz, GError **error) { g_autofree gchar *guid = NULL; self->st_project_ts = fu_struct_genesys_ts_brand_project_parse(buf, bufsz, 0, error); if (self->st_project_ts == NULL) { g_prefix_error(error, "failed to parse brand project tool info: "); return FALSE; } /* add specific product info */ guid = fwupd_guid_hash_data(self->st_project_ts->data, self->st_project_ts->len, FWUPD_GUID_FLAG_NONE); fu_device_add_instance_strup(FU_DEVICE(self), "PROJECT", guid); /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_detach(FuDevice *device, FuProgress *progress, GError **error) { return fu_genesys_usbhub_device_enter_isp_mode(FU_GENESYS_USBHUB_DEVICE(device), error); } static gboolean fu_genesys_usbhub_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); if (!fu_genesys_usbhub_device_reset(self, error)) return FALSE; if (self->hid_channel != NULL) { fu_device_add_flag(FU_DEVICE(self->hid_channel), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } /* success */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static GBytes * fu_genesys_usbhub_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); gsize size = fu_cfi_device_get_size(self->cfi_device); g_autoptr(FuDeviceLocker) locker = NULL; g_autofree guint8 *buf = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 99, NULL); /* require detach -> attach */ locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_device_detach, (FuDeviceLockerFunc)fu_device_attach, error); if (locker == NULL) return NULL; fu_progress_step_done(progress); buf = g_malloc0(size); if (!fu_genesys_usbhub_device_read_flash(self, 0, buf, size, fu_progress_get_child(progress), error)) return NULL; fu_progress_step_done(progress); /* success */ return g_bytes_new_take(g_steal_pointer(&buf), size); } static gboolean fu_genesys_usbhub_device_probe(FuDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); if (g_usb_device_get_device_class(usb_device) != G_USB_DEVICE_CLASS_HUB) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not a usb hub"); return FALSE; } if (g_usb_device_get_spec(usb_device) < 0x210) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported USB2 hub"); return FALSE; } if (g_usb_device_get_spec(usb_device) >= 0x300) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported USB3 hub"); return FALSE; } return TRUE; } static gboolean fu_genesys_usbhub_device_open(FuDevice *device, GError **error) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); /* FuUsbDevice->open */ if (!FU_DEVICE_CLASS(fu_genesys_usbhub_device_parent_class)->open(device, error)) return FALSE; /* FuGenesysHubhidDevice->open */ if (self->hid_channel != NULL) { if (!fu_device_open(FU_DEVICE(self->hid_channel), error)) return FALSE; } return TRUE; } static gboolean fu_genesys_usbhub_device_close(FuDevice *device, GError **error) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); /* FuUsbDevice->close */ if (!FU_DEVICE_CLASS(fu_genesys_usbhub_device_parent_class)->close(device, error)) return FALSE; /* FuGenesysHubhidDevice->close */ if (self->hid_channel != NULL) { if (!fu_device_close(FU_DEVICE(self->hid_channel), error)) return FALSE; } return TRUE; } static gboolean fu_genesys_usbhub_device_setup(FuDevice *device, GError **error) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); guint32 block_size; guint32 sector_size; guint8 static_idx = 0; guint8 dynamic_idx = 0; const gsize bufsz = 0x20; g_autoptr(GBytes) static_buf = NULL; g_autoptr(GBytes) dynamic_buf = NULL; g_autoptr(GBytes) fw_buf = NULL; g_autofree guint8 *buf = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_genesys_usbhub_device_parent_class)->setup(device, error)) { g_prefix_error(error, "error setting up device: "); return FALSE; } /* validate by string token */ if (!fu_genesys_usbhub_device_validate_token(self, error)) return FALSE; /* [DEBUG] - additional info from device: * release version: g_usb_device_get_release(usb_device) */ /* read standard string descriptors */ if (g_usb_device_get_spec(usb_device) >= 0x300) { static_idx = GENESYS_USBHUB_STATIC_TOOL_DESC_IDX_USB_3_0; dynamic_idx = GENESYS_USBHUB_DYNAMIC_TOOL_DESC_IDX_USB_3_0; } else { static_idx = GENESYS_USBHUB_STATIC_TOOL_DESC_IDX_USB_2_0; dynamic_idx = GENESYS_USBHUB_DYNAMIC_TOOL_DESC_IDX_USB_2_0; } /* * Read/parse vendor-specific string descriptors and use that * data to setup device attributes. */ buf = g_malloc0(bufsz); /* parse static tool string */ static_buf = g_usb_device_get_string_descriptor_bytes_full(usb_device, static_idx, G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES, 64, error); if (static_buf == NULL) { g_prefix_error(error, "failed to get static tool info from device (idx=0x%02x): ", static_idx); return FALSE; } if (!fu_genesys_usbhub_device_get_descriptor_data(static_buf, buf, bufsz, error)) { g_prefix_error(error, "failed to get static tool info from device: "); return FALSE; } if (!fu_genesys_usbhub_device_get_info_from_static_ts(self, buf, bufsz, error)) return FALSE; /* parse dynamic tool string */ dynamic_buf = g_usb_device_get_string_descriptor_bytes_full(usb_device, dynamic_idx, G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES, 64, error); if (dynamic_buf == NULL) { g_prefix_error(error, "failed to get dynamic tool info from device (idx=0x%02x): ", dynamic_idx); return FALSE; } if (!fu_genesys_usbhub_device_get_descriptor_data(dynamic_buf, buf, bufsz, error)) { g_prefix_error(error, "failed to get dynamic tool info from device: "); return FALSE; } if (!fu_genesys_usbhub_device_get_info_from_dynamic_ts(self, buf, bufsz, error)) return FALSE; /* parse firmware info tool string */ fw_buf = g_usb_device_get_string_descriptor_bytes_full(usb_device, GENESYS_USBHUB_FW_INFO_DESC_IDX, G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES, 64, error); if (fw_buf == NULL) { g_prefix_error(error, "failed to get firmware tool info from device: "); return FALSE; } if (!fu_genesys_usbhub_device_get_descriptor_data(fw_buf, buf, bufsz, error)) { g_prefix_error(error, "failed to get firmware tool info from device: "); return FALSE; } self->st_fwinfo_ts = fu_struct_genesys_ts_firmware_info_parse(buf, bufsz, 0, error); if (self->st_fwinfo_ts == NULL) { g_prefix_error(error, "failed to parse firmware tool info: "); return FALSE; } /* parse vendor support tool string */ if (self->tool_string_version >= FU_GENESYS_TS_VERSION_VENDOR_SUPPORT) { g_autoptr(GBytes) vendor_buf = g_usb_device_get_string_descriptor_bytes_full( usb_device, GENESYS_USBHUB_VENDOR_SUPPORT_DESC_IDX, G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES, 64, error); if (vendor_buf == NULL) { g_prefix_error(error, "failed to get vendor support tool info from device: "); return FALSE; } if (!fu_genesys_usbhub_device_get_descriptor_data(vendor_buf, buf, bufsz, error)) { g_prefix_error(error, "failed to get vendor support tool info from device: "); return FALSE; } if (!fu_genesys_usbhub_device_get_info_from_vendor_ts(self, buf, bufsz, error)) return FALSE; } else { self->st_vendor_ts = fu_struct_genesys_ts_vendor_support_new(); } /* parse brand project tool string */ if (self->tool_string_version >= FU_GENESYS_TS_VERSION_BRAND_PROJECT) { g_autoptr(GBytes) project_buf = g_usb_device_get_string_descriptor_bytes_full( usb_device, GENESYS_USBHUB_BRAND_PROJECT_DESC_IDX, G_USB_DEVICE_LANGID_ENGLISH_UNITED_STATES, 64, error); if (project_buf == NULL) { g_prefix_error(error, "failed to get brand project tool info from device: "); return FALSE; } if (!fu_genesys_usbhub_device_get_descriptor_data(project_buf, buf, bufsz, error)) { g_prefix_error(error, "failed to get brand project tool info from device: "); return FALSE; } if (!fu_genesys_usbhub_device_get_info_from_project_ts(self, buf, bufsz, error)) return FALSE; } if (fu_device_has_private_flag(device, FU_GENESYS_USBHUB_FLAG_HAS_PUBLIC_KEY)) self->has_codesign = TRUE; /* enter isp mode */ if (!fu_genesys_usbhub_device_enter_isp_mode(self, error)) return FALSE; /* setup cfi device */ self->cfi_device = fu_genesys_usbhub_device_cfi_setup(self, error); if (self->cfi_device == NULL) return FALSE; block_size = fu_cfi_device_get_block_size(self->cfi_device); if (block_size != 0) self->flash_block_size = block_size; sector_size = fu_cfi_device_get_sector_size(self->cfi_device); if (sector_size != 0) self->flash_sector_size = sector_size; /* setup firmware parameters */ fu_device_set_firmware_size_max( device, MIN(self->spec.fw_data_max_count, fu_cfi_device_get_size(self->cfi_device))); /* has codesign */ if (self->has_codesign) { FuGenesysFwBank bank = FW_BANK_1; switch (self->running_bank) { case FU_GENESYS_FW_STATUS_BANK1: bank = FW_BANK_1; break; case FU_GENESYS_FW_STATUS_BANK2: bank = FW_BANK_2; break; default: g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "wrong setting in .quirk, " "mask code does not support codesign"); return FALSE; } if (!fu_genesys_usbhub_device_get_public_key(self, bank, error)) return FALSE; } /* add specific product info */ if (self->running_bank != FU_GENESYS_FW_STATUS_MASK) { const gchar *vendor = fwupd_device_get_vendor(FWUPD_DEVICE(device)); g_autofree gchar *guid = NULL; guid = fwupd_guid_hash_data(self->st_vendor_ts->data, self->st_vendor_ts->len, FWUPD_GUID_FLAG_NONE); fu_device_add_instance_strup(device, "VENDOR", vendor); fu_device_add_instance_strup(device, "VENDORSUP", guid); } if (!fu_device_build_instance_id(device, error, "USB", "VID", "PID", "IC", NULL)) return FALSE; if (!fu_device_build_instance_id(device, error, "USB", "VID", "PID", "IC", "BONDING", NULL)) return FALSE; fu_device_build_instance_id(device, NULL, "USB", "VID", "PID", "VENDOR", "IC", "BONDING", "PORTNUM", "VENDORSUP", NULL); fu_device_build_instance_id(device, NULL, "USB", "VID", "PID", "PUBKEY", NULL); fu_device_build_instance_id(device, NULL, "USB", "VID", "PID", "PROJECT", NULL); /* have MStar scaler */ if (fu_device_has_private_flag(device, FU_GENESYS_USBHUB_FLAG_HAS_MSTAR_SCALER)) if (!fu_genesys_usbhub_device_mstar_scaler_setup(self, error)) return FALSE; /* success */ return TRUE; } static void fu_genesys_usbhub_device_codesign_to_string(FuDevice *device, guint idt, GString *str) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); guint64 fw_max_size = fu_device_get_firmware_size_max(device); guint32 bank_addr1 = self->spec.fw_bank_addr[FW_BANK_1][FU_GENESYS_FW_TYPE_CODESIGN]; guint32 bank_addr2 = self->spec.fw_bank_addr[FW_BANK_2][FU_GENESYS_FW_TYPE_CODESIGN]; guint idt_detail = idt + 1; fu_string_append(str, idt, "Codesign", fu_genesys_fw_codesign_to_string(self->codesign)); fu_string_append(str, idt_detail, "CodesignCheck", fu_genesys_vs_codesign_check_to_string(self->codesign_check)); if (self->spec.support_dual_bank) { fu_string_append_kx(str, idt_detail, "Bank1Addr", bank_addr1); if (fw_max_size <= bank_addr2) /* capacity too small */ return; fu_string_append_kx(str, idt_detail, "Bank2Addr", bank_addr2); } } static void fu_genesys_usbhub_device_to_string(FuDevice *device, guint idt, GString *str) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); guint64 fw_max_size = fu_device_get_firmware_size_max(device); guint idt_detail = idt + 1; guint idt_bank_detail = idt_detail + 1; fu_string_append(str, idt, "CFI", fu_device_get_name(FU_DEVICE(self->cfi_device))); fu_string_append_ku(str, idt_detail, "FlashEraseDelay", self->flash_erase_delay); fu_string_append_ku(str, idt_detail, "FlashWriteDelay", self->flash_write_delay); fu_string_append_kx(str, idt_detail, "FlashBlockSize", self->flash_block_size); fu_string_append_kx(str, idt_detail, "FlashSectorSize", self->flash_sector_size); fu_string_append_kx(str, idt_detail, "FlashRwSize", self->flash_rw_size); fu_string_append(str, idt, "RunningBank", fu_genesys_fw_status_to_string(self->running_bank)); fu_string_append_kb(str, idt, "SupportDualBank", self->spec.support_dual_bank); fu_string_append_kb(str, idt, "SupportCodeSize", self->spec.support_code_size); for (gint i = 0; i < FU_GENESYS_FW_TYPE_INSIDE_HUB_COUNT; i++) { if (self->spec.fw_bank_capacity[i] == 0 || /* unsupported fw type */ fw_max_size <= self->spec.fw_bank_addr[FW_BANK_1][i]) /* capacity too small */ continue; if (i == FU_GENESYS_FW_TYPE_CODESIGN) { if (self->has_codesign) fu_genesys_usbhub_device_codesign_to_string(device, idt + 1, str); continue; } fu_string_append(str, idt_detail, "FwBank", fu_genesys_fw_type_to_string(i)); fu_string_append_kx(str, idt_bank_detail, "DataTotalCount", self->spec.fw_bank_capacity[i]); fu_string_append_ku(str, idt_bank_detail, "UpdateBank", self->update_fw_banks[i]); if (self->spec.chip.model == ISP_MODEL_HUB_GL3523 && i == FU_GENESYS_FW_TYPE_HUB) fu_string_append_kb(str, idt_bank_detail, "BackupHubFwBank1", self->backup_hub_fw_bank1); if (self->spec.support_dual_bank) { fu_string_append_kx(str, idt_bank_detail, "Bank1Addr", self->spec.fw_bank_addr[FW_BANK_1][i]); fu_string_append_kx(str, idt_bank_detail, "Bank1Ver", self->fw_bank_vers[FW_BANK_1][i]); fu_string_append_kx(str, idt_bank_detail, "Bank1CodeSize", self->fw_bank_code_sizes[FW_BANK_1][i]); if (fw_max_size <= self->spec.fw_bank_addr[FW_BANK_2][i]) /* capacity too small */ continue; fu_string_append_kx(str, idt_bank_detail, "Bank2Addr", self->spec.fw_bank_addr[FW_BANK_2][i]); fu_string_append_kx(str, idt_bank_detail, "Bank2Ver", self->fw_bank_vers[FW_BANK_2][i]); fu_string_append_kx(str, idt_bank_detail, "Bank2CodeSize", self->fw_bank_code_sizes[FW_BANK_2][i]); } } } /** * Prepare update * * Collect dual bank's version. Then select lower version bank to update. * When device is running mask code, should not compare and select bank1. * */ static gboolean fu_genesys_usbhub_device_prepare(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); guint64 fw_max_size = fu_device_get_firmware_size_max(device); g_autoptr(GArray) fw_types = g_array_new(FALSE, FALSE, sizeof(FuGenesysFwType)); for (gint i = 0; i < FU_GENESYS_FW_TYPE_INSIDE_HUB_COUNT; i++) { if (self->spec.fw_bank_capacity[i] == 0 || /* unsupported fw type */ fw_max_size <= self->spec.fw_bank_addr[FW_BANK_1][i]) /* capacity is smaller */ continue; if (self->running_bank == FU_GENESYS_FW_STATUS_MASK || /* both banks are invalid */ !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE)) { self->update_fw_banks[i] = FW_BANK_1; continue; } /* hub & codesign info must at the same bank */ if (i == FU_GENESYS_FW_TYPE_CODESIGN) continue; g_array_append_val(fw_types, i); } if (fw_types->len == 0) return TRUE; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_SCHEDULING, 5, "detach"); for (guint i = 0; i < fw_types->len; i++) { fu_progress_add_step(progress, FWUPD_STATUS_SCHEDULING, 100, NULL); fu_progress_add_step(progress, FWUPD_STATUS_SCHEDULING, 100, NULL); } /* enter isp mode */ if (!fu_genesys_usbhub_device_enter_isp_mode(self, error)) return FALSE; fu_progress_step_done(progress); /* query each fw bank version of fw type */ for (guint i = 0; i < fw_types->len; i++) { FuGenesysFwType fw_type = g_array_index(fw_types, FuGenesysFwType, i); /* hub & codesign info must at the same bank */ if (fw_type == FU_GENESYS_FW_TYPE_CODESIGN) { self->update_fw_banks[fw_type] = self->update_fw_banks[FU_GENESYS_FW_TYPE_HUB]; continue; } if (!fu_genesys_usbhub_device_get_fw_bank_version(self, fw_type, FW_BANK_1, fu_progress_get_child(progress), error)) { g_prefix_error(error, "error getting %s bank1 version: ", fu_genesys_fw_type_to_string(fw_type)); return FALSE; } fu_progress_step_done(progress); if (!fu_genesys_usbhub_device_get_fw_bank_version(self, fw_type, FW_BANK_2, fu_progress_get_child(progress), error)) { g_prefix_error(error, "error getting %s bank2 version: ", fu_genesys_fw_type_to_string(fw_type)); return FALSE; } fu_progress_step_done(progress); if (self->fw_bank_vers[FW_BANK_1][fw_type] > self->fw_bank_vers[FW_BANK_2][fw_type]) { /* bank1 is more recent than bank2: write fw on bank2 */ if (self->spec.chip.model == ISP_MODEL_HUB_GL3523) { /* GL3523 unique dual bank mechanism */ self->backup_hub_fw_bank1 = TRUE; self->update_fw_banks[fw_type] = FW_BANK_1; } else { self->update_fw_banks[fw_type] = FW_BANK_2; } } else { /* bank2 is more recent than bank1: write fw on bank1 */ self->update_fw_banks[fw_type] = FW_BANK_1; } } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_compare_fw_public_key(FuGenesysUsbhubDevice *self, FuFirmware *firmware, GError **error) { FuGenesysFwCodesign codesign_type = FU_GENESYS_FW_CODESIGN_NONE; g_autoptr(GBytes) blob = NULL; g_autoptr(GByteArray) st_codesign = NULL; g_return_val_if_fail(FU_IS_GENESYS_USBHUB_CODESIGN_FIRMWARE(firmware), FALSE); /* compare dev and fw codesign type */ codesign_type = fu_genesys_usbhub_codesign_firmware_get_codesign( FU_GENESYS_USBHUB_CODESIGN_FIRMWARE(firmware)); if (codesign_type != self->codesign) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "mismatch codesign type %s", fu_genesys_fw_codesign_to_string(codesign_type)); return FALSE; } /* get fw codesign info */ blob = fu_firmware_get_bytes(firmware, error); if (blob == NULL) { g_prefix_error(error, "firmware does not have %s bytes: ", fu_genesys_fw_type_to_string(FU_GENESYS_FW_TYPE_CODESIGN)); return FALSE; } /* compare dev and fw public-key */ switch (self->codesign) { case FU_GENESYS_FW_CODESIGN_RSA: { g_autofree gchar *fw_n = NULL; g_autofree gchar *fw_e = NULL; g_autofree gchar *dev_n = NULL; g_autofree gchar *dev_e = NULL; /* parse and validate */ st_codesign = fu_struct_genesys_fw_codesign_info_rsa_parse_bytes(blob, 0x0, error); if (st_codesign == NULL) { g_prefix_error(error, "failed to parse %s codesgin: ", fu_genesys_fw_codesign_to_string(codesign_type)); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "PublicKey", st_codesign->data, FU_STRUCT_GENESYS_FW_CODESIGN_INFO_RSA_SIZE); fw_n = fu_struct_genesys_fw_codesign_info_rsa_get_text_n(st_codesign); dev_n = fu_struct_genesys_fw_rsa_public_key_text_get_text_n(self->st_public_key); if (!fu_memcmp_safe((const guint8 *)fw_n, FU_STRUCT_GENESYS_FW_CODESIGN_INFO_RSA_SIZE_TEXT_N, 0, (const guint8 *)dev_n, FU_STRUCT_GENESYS_FW_RSA_PUBLIC_KEY_TEXT_SIZE_TEXT_N, 0, FU_STRUCT_GENESYS_FW_RSA_PUBLIC_KEY_TEXT_SIZE_TEXT_N, error)) { g_prefix_error(error, "mismatch public-keyN: "); return FALSE; } fw_e = fu_struct_genesys_fw_codesign_info_rsa_get_text_e(st_codesign); dev_e = fu_struct_genesys_fw_rsa_public_key_text_get_text_e(self->st_public_key); if (!fu_memcmp_safe((const guint8 *)fw_e, FU_STRUCT_GENESYS_FW_CODESIGN_INFO_RSA_SIZE_TEXT_E, 0, (const guint8 *)dev_e, FU_STRUCT_GENESYS_FW_RSA_PUBLIC_KEY_TEXT_SIZE_TEXT_E, 0, FU_STRUCT_GENESYS_FW_RSA_PUBLIC_KEY_TEXT_SIZE_TEXT_E, error)) { g_prefix_error(error, "mismatch public-keyE: "); return FALSE; } break; } case FU_GENESYS_FW_CODESIGN_ECDSA: { gsize fw_keysz = 0; const guint8 *fw_key = NULL; /* parse and validate */ st_codesign = fu_struct_genesys_fw_codesign_info_ecdsa_parse_bytes(blob, 0x0, error); if (st_codesign == NULL) { g_prefix_error(error, "failed to parse %s codesgin: ", fu_genesys_fw_codesign_to_string(codesign_type)); return FALSE; } fw_key = fu_struct_genesys_fw_codesign_info_ecdsa_get_key(st_codesign, &fw_keysz); if (fw_keysz != FU_STRUCT_GENESYS_FW_ECDSA_PUBLIC_KEY_SIZE) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "mismatch public-key size"); return FALSE; } if (!fu_memcmp_safe(fw_key, fw_keysz, 0, self->st_public_key->data, self->st_public_key->len, 0, FU_STRUCT_GENESYS_FW_ECDSA_PUBLIC_KEY_SIZE, error)) { g_prefix_error(error, "mismatch public-key: "); fu_dump_raw(G_LOG_DOMAIN, "PublicKey", fw_key, fw_keysz); return FALSE; } break; } default: break; } /* does not exist */ if (st_codesign == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "unsupported codesign type %s", fu_genesys_fw_codesign_to_string(codesign_type)); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_adjust_fw_addr(FuGenesysUsbhubDevice *self, FuFirmware *firmware, GError **error) { FuGenesysFwType fw_type = fu_firmware_get_idx(firmware); FuGenesysFwBank bank_num; guint32 code_size = 0; guint32 bank_size = 0; g_autoptr(GPtrArray) imgs = NULL; /* check supported fw type */ if (fw_type >= FU_GENESYS_FW_TYPE_INSIDE_HUB_COUNT) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unknown firmware type %s", fu_firmware_get_id(firmware)); return FALSE; } /* check bank capacity */ bank_size = self->spec.fw_bank_capacity[fw_type]; if (bank_size == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device not supported firmware type %s", fu_firmware_get_id(firmware)); return FALSE; } code_size = fu_firmware_get_size(firmware); if (code_size > bank_size) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "firmware %s too large, got %#x, expected <= %#x", fu_firmware_get_id(firmware), code_size, bank_size); return FALSE; } /* set update address */ bank_num = self->update_fw_banks[fw_type]; if (bank_num >= FW_BANK_COUNT) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unknown bank num 0x%x", bank_num); return FALSE; } fu_firmware_set_addr(firmware, self->spec.fw_bank_addr[bank_num][fw_type]); /* set child firmware */ imgs = fu_firmware_get_images(firmware); for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); if (!fu_genesys_usbhub_device_adjust_fw_addr(self, img, error)) return FALSE; } /* success */ return TRUE; } static FuFirmware * fu_genesys_usbhub_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); g_autoptr(FuFirmware) firmware = fu_genesys_usbhub_firmware_new(); /* parse firmware */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* has codesign */ if (self->has_codesign && (flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_autoptr(FuFirmware) img = NULL; if (self->st_public_key == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device does not have public-key"); return NULL; } img = fu_firmware_get_image_by_idx(firmware, FU_GENESYS_FW_TYPE_CODESIGN, error); if (img == NULL) { g_prefix_error(error, "firmware does not have %s: ", fu_genesys_fw_type_to_string(FU_GENESYS_FW_TYPE_CODESIGN)); return NULL; } if (!fu_genesys_usbhub_device_compare_fw_public_key(self, img, error)) return NULL; } /* set write address into each firmware address */ if (!fu_genesys_usbhub_device_adjust_fw_addr(self, firmware, error)) return NULL; /* success */ return g_steal_pointer(&firmware); } static gboolean fu_genesys_usbhub_device_erase_flash(FuGenesysUsbhubDevice *self, guint start_addr, guint len, FuProgress *progress, GError **error) { FuGenesysWaitFlashRegisterHelper helper = {.reg = 5, .expected_val = 0}; g_autoptr(GPtrArray) chunks = NULL; chunks = fu_chunk_array_new(NULL, len, start_addr, self->flash_block_size, self->flash_sector_size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint16 sectornum = fu_chunk_get_address(chk) / self->flash_sector_size; guint16 blocknum = fu_chunk_get_page(chk); guint16 index = (0x01 << 8) | (sectornum << 4) | blocknum; if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vcs.req_write, 0x2001, /* value */ index, /* idx */ NULL, /* data */ 0, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error erasing flash at sector 0x%02x in block 0x%02x", sectornum, blocknum); return FALSE; } /* 8s */ if (!fu_device_retry(FU_DEVICE(self), fu_genesys_usbhub_device_wait_flash_status_register_cb, self->flash_erase_delay / 30, &helper, error)) { g_prefix_error(error, "error erasing flash: "); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_write_flash(FuGenesysUsbhubDevice *self, guint start_addr, const guint8 *buf, guint bufsz, FuProgress *progress, GError **error) { FuGenesysWaitFlashRegisterHelper helper = {.reg = 5, .expected_val = 0}; g_autoptr(GPtrArray) chunks = NULL; chunks = fu_chunk_array_new(buf, bufsz, start_addr, self->flash_block_size, self->flash_rw_size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); g_autoptr(GByteArray) buf_write = g_byte_array_new(); g_byte_array_append(buf_write, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); if (!fu_genesys_usbhub_device_ctrl_transfer( self, fu_progress_get_child(progress), /* progress */ G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, self->vcs.req_write, (fu_chunk_get_page(chk) & 0x000f) << 12, /* value */ fu_chunk_get_address(chk) & 0xffff, /* idx */ buf_write->data, /* data */ buf_write->len, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error writing flash at 0x%02x%04x: ", fu_chunk_get_page(chk), fu_chunk_get_address(chk)); return FALSE; } /* 5s */ if (!fu_device_retry(FU_DEVICE(self), fu_genesys_usbhub_device_wait_flash_status_register_cb, self->flash_write_delay / 30, &helper, error)) { g_prefix_error(error, "error writing flash: "); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } /** * GL3523 hub default boot up on fw bank1 - even bank2's version is higher. It only boots up * on bank2 when bank1 is broken and bank2 is right. Therefore, we want bank1 always be last * updated firmware. Also, to fulfill dual bank mechanism, we shall keep at last one fw bank * is right. For this purpose, we usually backup bank1 (right fw) to bank2, then update new * fw to bank1. * * GL352350's fw bank stores codesign info and code in the same sector and the sector will * be erased. Therefore, backup must include codesign info. */ static gboolean fu_genesys_usbhub_device_backup_hub_fw_bank1_to_bank2(FuGenesysUsbhubDevice *self, FuProgress *progress, GError **error) { gsize bufsz = 0; const guint start_addr = self->spec.fw_bank_addr[FW_BANK_2][FU_GENESYS_FW_TYPE_HUB]; g_autofree guint8 *buf = NULL; /* reuse fw on bank1 for GL3523 */ if (self->hub_fw_bank1_data == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "backup bank does not exist"); return FALSE; } if (self->is_gl352350 && self->has_codesign) { /* merge hub fw and codesign info for GL352350 */ gsize code_size = 0; const guint8 *code_data = g_bytes_get_data(self->hub_fw_bank1_data, &code_size); guint32 codesign_offset = self->spec.fw_bank_addr[FW_BANK_1][FU_GENESYS_FW_TYPE_CODESIGN]; if (codesign_offset < code_size) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "hub firmware too large, got 0x%x, expected <= 0x%x", (guint)code_size, codesign_offset); return FALSE; } bufsz = codesign_offset + self->st_codesign->len; buf = g_malloc0(bufsz); /* copy fw bank data */ if (!fu_memcpy_safe(buf, bufsz, 0, /* dst */ code_data, code_size, 0x0, /* src */ code_size, error)) return FALSE; /* copy codesign info */ if (!fu_memcpy_safe(buf, bufsz, codesign_offset, /* dst */ self->st_codesign->data, self->st_codesign->len, 0x0, /* src */ self->st_codesign->len, error)) return FALSE; } else { bufsz = g_bytes_get_size(self->hub_fw_bank1_data); buf = fu_memdup_safe(g_bytes_get_data(self->hub_fw_bank1_data, NULL), bufsz, error); if (buf == NULL) return FALSE; } /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 30, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 5, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 15, NULL); /* erase */ if (!fu_genesys_usbhub_device_erase_flash(self, start_addr, bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* verify blank */ if (!fu_genesys_usbhub_device_compare_flash_blank(self, start_addr, bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* write */ if (!fu_genesys_usbhub_device_write_flash(self, start_addr, buf, bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* verify */ if (!fu_genesys_usbhub_device_compare_flash_data(self, start_addr, buf, bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_update_firmware(FuGenesysUsbhubDevice *self, FuFirmware *firmware, FuProgress *progress, GError **error) { gboolean skip_erase = FALSE; gsize bufsz = 0; const guint8 *buf = NULL; const guint start_addr = fu_firmware_get_addr(firmware); g_autoptr(GBytes) blob = NULL; /* get raw data */ blob = fu_firmware_get_bytes(firmware, error); if (blob == NULL) return FALSE; buf = g_bytes_get_data(blob, &bufsz); if (fu_firmware_get_idx(firmware) == FU_GENESYS_FW_TYPE_CODESIGN) { /* already erase at FU_GENESYS_FW_TYPE_HUB before */ if (self->is_gl352350) skip_erase = TRUE; /* jump ECDSA hash on HW codesign */ if (self->has_hw_codesign && self->codesign == FU_GENESYS_FW_CODESIGN_ECDSA) { if (bufsz != FU_STRUCT_GENESYS_FW_CODESIGN_INFO_ECDSA_SIZE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "mismatch codesign length, got 0x%x, expected = 0x%x", (guint)bufsz, (guint)FU_STRUCT_GENESYS_FW_CODESIGN_INFO_ECDSA_SIZE); return FALSE; } buf += FU_STRUCT_GENESYS_FW_CODESIGN_INFO_ECDSA_SIZE_HASH; bufsz -= FU_STRUCT_GENESYS_FW_CODESIGN_INFO_ECDSA_SIZE_HASH; } } /* progress */ fu_progress_set_id(progress, fu_firmware_get_id(firmware)); if (skip_erase) { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 5, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 79, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 15, NULL); } else { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 30, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 5, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 15, NULL); } /* erase */ if (!skip_erase && !fu_genesys_usbhub_device_erase_flash(self, start_addr, bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* verify blank */ if (!fu_genesys_usbhub_device_compare_flash_blank(self, start_addr, bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* write */ if (!fu_genesys_usbhub_device_write_flash(self, start_addr, buf, bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* verify */ if (!self->has_hw_codesign) { /* skip specific codesign verification */ if (!fu_genesys_usbhub_device_compare_flash_data(self, start_addr, buf, bufsz, fu_progress_get_child(progress), error)) return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_setup_hw_module(FuGenesysUsbhubDevice *self, GError **error) { if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_USBHUB_GL_HUB_HW_SECURITY, 0, /* value */ 0, /* idx */ NULL, /* data */ 0, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error setting up HW module: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_send_hash_data_length(FuGenesysUsbhubDevice *self, gsize size_to_hash, GError **error) { guint16 length_by_4k = size_to_hash / 4096; if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_USBHUB_GL_HUB_HW_SECURITY, 0x040b, /* value */ length_by_4k, /* idx */ NULL, /* data */ 0, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error sending hash data length by 4k(0x%x): ", length_by_4k); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_send_hash_digest(FuGenesysUsbhubDevice *self, GByteArray *st_codesign, GError **error) { gsize hash_bufsz = 0; const guint8 *hash_buf = NULL; g_autofree guint8 *buf_mut = NULL; hash_buf = fu_struct_genesys_fw_codesign_info_ecdsa_get_hash(st_codesign, &hash_bufsz); if (hash_buf == NULL) { g_prefix_error(error, "failed to get hash digest: "); return FALSE; } buf_mut = fu_memdup_safe(hash_buf, hash_bufsz, error); if (buf_mut == NULL) { g_prefix_error(error, "failed to dup hash digest: "); return FALSE; } if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_USBHUB_GL_HUB_HW_SECURITY, 0x000b, /* value */ 0, /* idx */ buf_mut, /* data */ hash_bufsz, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error sending hash digest: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_check_hash_digest_verification(FuGenesysUsbhubDevice *self, GError **error) { guint8 verification = 0; if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_USBHUB_GL_HUB_HW_SECURITY, 0x000b, /* value */ 0, /* idx */ &verification, /* data */ 1, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error getting hash digest verification: "); return FALSE; } if (verification != 1) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "failed to verify hash digest, got 0x%x, expected 0x01", verification); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_toggle_hw_read_key(FuGenesysUsbhubDevice *self, guint16 key_addr, GError **error) { if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_USBHUB_GL_HUB_HW_SECURITY, 0x020c, /* value */ key_addr, /* idx */ NULL, /* data */ 0, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error sending key addr 0x%x: ", key_addr); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_send_signature(FuGenesysUsbhubDevice *self, GByteArray *st_codesign, GError **error) { gsize sig_bufsz = 0; const guint8 *sig_buf = NULL; g_autofree guint8 *buf_mut = NULL; sig_buf = fu_struct_genesys_fw_codesign_info_ecdsa_get_signature(st_codesign, &sig_bufsz); if (sig_buf == NULL) { g_prefix_error(error, "failed to get signature: "); return FALSE; } buf_mut = fu_memdup_safe(sig_buf, sig_bufsz, error); if (buf_mut == NULL) { g_prefix_error(error, "failed to dup signature: "); return FALSE; } if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_USBHUB_GL_HUB_HW_SECURITY, 0x010c, /* value */ 0, /* idx */ buf_mut, /* data */ sig_bufsz, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error sending signature: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_check_signature_verification(FuGenesysUsbhubDevice *self, GError **error) { guint8 verification = 0; if (!fu_genesys_usbhub_device_ctrl_transfer(self, NULL, /* progress */ G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, GENESYS_USBHUB_GL_HUB_HW_SECURITY, 0x000c, /* value */ 0, /* idx */ &verification, /* data */ 1, /* data length */ NULL, /* actual length */ GENESYS_USBHUB_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "error getting signature verification: "); return FALSE; } if (verification != 1) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "failed to verify signature, got 0x%x, expected 0x01", verification); return FALSE; } /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_compare_firmware_flash_data(FuGenesysUsbhubDevice *self, FuFirmware *firmware, FuProgress *progress, GError **error) { gsize bufsz = 0; const guint8 *buf = NULL; const guint start_addr = fu_firmware_get_addr(firmware); g_autoptr(GBytes) blob = NULL; /* get raw data */ blob = fu_firmware_get_bytes(firmware, error); if (blob == NULL) return FALSE; buf = g_bytes_get_data(blob, &bufsz); if (!fu_genesys_usbhub_device_compare_flash_data(self, start_addr, buf, bufsz, progress, error)) return FALSE; /* success */ return TRUE; } /** * GL3590 hub has HW module to examine ECDSA codesign. */ static gboolean fu_genesys_usbhub_device_examine_fw_codesign_hw(FuGenesysUsbhubDevice *self, FuFirmware *firmware, FuProgress *progress, GError **error) { gsize codesize_to_hash = 0; g_autoptr(FuFirmware) codesign_img = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GByteArray) st_codesign = NULL; g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); /* get fw codesign info */ codesign_img = fu_firmware_get_image_by_idx(firmware, FU_GENESYS_FW_TYPE_CODESIGN, error); if (codesign_img == NULL) return FALSE; blob = fu_firmware_get_bytes(codesign_img, error); if (blob == NULL) return FALSE; st_codesign = fu_struct_genesys_fw_codesign_info_ecdsa_parse_bytes(blob, 0x0, error); if (st_codesign == NULL) return FALSE; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 5, "setup HW codesign engine"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 100, fu_firmware_get_id(firmware)); for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); if (fu_firmware_get_idx(img) != FU_GENESYS_FW_TYPE_CODESIGN) fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 100, fu_firmware_get_id(img)); } fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 5, "verify hash"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 5, "verify signature"); /* setup HW codesign module */ if (!fu_genesys_usbhub_device_setup_hw_module(self, error)) return FALSE; fu_progress_step_done(progress); /* compare firmware data */ if (!fu_genesys_usbhub_device_compare_firmware_flash_data(self, firmware, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); codesize_to_hash += fu_firmware_get_size(firmware); for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); if (fu_firmware_get_idx(img) != FU_GENESYS_FW_TYPE_CODESIGN) { if (!fu_genesys_usbhub_device_compare_firmware_flash_data( self, img, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); codesize_to_hash += fu_firmware_get_size(img); } } /* compute and verify hash */ if (!fu_genesys_usbhub_device_send_hash_data_length(self, codesize_to_hash, error)) return FALSE; if (!fu_genesys_usbhub_device_send_hash_digest(self, st_codesign, error)) return FALSE; if (!fu_genesys_usbhub_device_check_hash_digest_verification(self, error)) return FALSE; fu_progress_step_done(progress); /* toggle hw read key */ if (!fu_genesys_usbhub_device_toggle_hw_read_key(self, fu_firmware_get_addr(codesign_img), error)) return FALSE; /* send and verify signature */ if (!fu_genesys_usbhub_device_send_signature(self, st_codesign, error)) return FALSE; if (!fu_genesys_usbhub_device_check_signature_verification(self, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_genesys_usbhub_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); /* enter isp mode */ if (!fu_genesys_usbhub_device_enter_isp_mode(self, error)) return FALSE; /* progress */ fu_progress_set_id(progress, G_STRLOC); if (self->backup_hub_fw_bank1) { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "backup hub firmware to bank2"); } fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, fu_firmware_get_id(firmware)); for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, fu_firmware_get_id(img)); } if (self->has_hw_codesign) { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 100, "verify firmware codesign"); } /* backup fw to bank2 first */ if (self->backup_hub_fw_bank1) { if (!fu_genesys_usbhub_device_backup_hub_fw_bank1_to_bank2( self, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } /* write main firmware */ if (!fu_genesys_usbhub_device_update_firmware(self, firmware, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* write child firmware */ for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); if (!fu_genesys_usbhub_device_update_firmware(self, img, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } /* examine codesign */ if (self->has_hw_codesign) { if (!fu_genesys_usbhub_device_examine_fw_codesign_hw( self, firmware, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to verify HW codesign: "); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static void fu_genesys_usbhub_device_set_progress(FuDevice *device, FuProgress *progress) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); fu_progress_set_id(progress, G_STRLOC); if (self->backup_hub_fw_bank1) { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 30, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 70, "reload"); } else { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 15, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 85, "reload"); } } static gboolean fu_genesys_usbhub_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(device); guint64 tmp; if (g_strcmp0(key, "GenesysUsbhubDeviceTransferSize") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->flash_rw_size = tmp; /* success */ return TRUE; } if (g_strcmp0(key, "GenesysUsbhubSwitchRequest") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->vcs.req_switch = tmp; /* success */ return TRUE; } if (g_strcmp0(key, "GenesysUsbhubReadRequest") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->vcs.req_read = tmp; /* success */ return TRUE; } if (g_strcmp0(key, "GenesysUsbhubWriteRequest") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->vcs.req_write = tmp; /* success */ return TRUE; } /* failure */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_genesys_usbhub_device_init(FuGenesysUsbhubDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_GENERIC_GUIDS); fu_device_add_protocol(FU_DEVICE(self), "com.genesys.usbhub"); fu_device_retry_set_delay(FU_DEVICE(self), 30); /* 30ms */ fu_device_set_remove_delay(FU_DEVICE(self), 5000); /* 5s */ fu_device_register_private_flag(FU_DEVICE(self), FU_GENESYS_USBHUB_FLAG_HAS_MSTAR_SCALER, "has-mstar-scaler"); fu_device_register_private_flag(FU_DEVICE(self), FU_GENESYS_USBHUB_FLAG_HAS_PUBLIC_KEY, "has-public-key"); fu_device_set_install_duration(FU_DEVICE(self), 9); /* 9 s */ self->vcs.req_switch = GENESYS_USBHUB_GL_HUB_SWITCH; self->vcs.req_read = GENESYS_USBHUB_GL_HUB_READ; self->vcs.req_write = GENESYS_USBHUB_GL_HUB_WRITE; self->flash_erase_delay = 8000; /* 8s */ self->flash_write_delay = 500; /* 500ms */ self->flash_block_size = 0x10000; /* 64KB */ self->flash_sector_size = 0x1000; /* 4KB */ self->flash_rw_size = 0x40; /* 64B */ self->is_gl352350 = FALSE; self->has_codesign = FALSE; self->has_hw_codesign = FALSE; self->codesign_check = FU_GENESYS_VS_CODESIGN_CHECK_UNSUPPORTED; self->codesign = FU_GENESYS_FW_CODESIGN_NONE; } static void fu_genesys_usbhub_device_finalize(GObject *object) { FuGenesysUsbhubDevice *self = FU_GENESYS_USBHUB_DEVICE(object); if (self->st_static_ts != NULL) g_byte_array_unref(self->st_static_ts); if (self->st_dynamic_ts != NULL) g_byte_array_unref(self->st_dynamic_ts); if (self->st_fwinfo_ts != NULL) g_byte_array_unref(self->st_fwinfo_ts); if (self->st_vendor_ts != NULL) g_byte_array_unref(self->st_vendor_ts); if (self->st_project_ts != NULL) g_byte_array_unref(self->st_project_ts); if (self->hub_fw_bank1_data != NULL) g_bytes_unref(self->hub_fw_bank1_data); if (self->st_codesign != NULL) g_byte_array_unref(self->st_codesign); if (self->st_public_key != NULL) g_byte_array_unref(self->st_public_key); if (self->cfi_device != NULL) g_object_unref(self->cfi_device); G_OBJECT_CLASS(fu_genesys_usbhub_device_parent_class)->finalize(object); } static void fu_genesys_usbhub_device_class_init(FuGenesysUsbhubDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_genesys_usbhub_device_finalize; klass_device->probe = fu_genesys_usbhub_device_probe; klass_device->open = fu_genesys_usbhub_device_open; klass_device->close = fu_genesys_usbhub_device_close; klass_device->setup = fu_genesys_usbhub_device_setup; klass_device->dump_firmware = fu_genesys_usbhub_device_dump_firmware; klass_device->prepare = fu_genesys_usbhub_device_prepare; klass_device->prepare_firmware = fu_genesys_usbhub_device_prepare_firmware; klass_device->write_firmware = fu_genesys_usbhub_device_write_firmware; klass_device->set_progress = fu_genesys_usbhub_device_set_progress; klass_device->detach = fu_genesys_usbhub_device_detach; klass_device->attach = fu_genesys_usbhub_device_attach; klass_device->to_string = fu_genesys_usbhub_device_to_string; klass_device->set_quirk_kv = fu_genesys_usbhub_device_set_quirk_kv; } fwupd-1.9.16/plugins/genesys/fu-genesys-usbhub-device.h000066400000000000000000000007131460375044200230620ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_GENESYS_USBHUB_DEVICE (fu_genesys_usbhub_device_get_type()) G_DECLARE_FINAL_TYPE(FuGenesysUsbhubDevice, fu_genesys_usbhub_device, FU, GENESYS_USBHUB_DEVICE, FuUsbDevice) void fu_genesys_usbhub_device_set_hid_channel(FuDevice *device, FuDevice *channel); fwupd-1.9.16/plugins/genesys/fu-genesys-usbhub-firmware.c000066400000000000000000000370441460375044200234410ustar00rootroot00000000000000/* * Copyright (C) 2023 Adam.Chen * Copyright (C) 2021 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-genesys-common.h" #include "fu-genesys-usbhub-codesign-firmware.h" #include "fu-genesys-usbhub-dev-firmware.h" #include "fu-genesys-usbhub-firmware.h" #include "fu-genesys-usbhub-pd-firmware.h" #include "fu-genesys-usbhub-struct.h" struct _FuGenesysUsbhubFirmware { FuFirmwareClass parent_instance; GByteArray *st_static_ts; FuGenesysChip chip; }; G_DEFINE_TYPE(FuGenesysUsbhubFirmware, fu_genesys_usbhub_firmware, FU_TYPE_FIRMWARE) static gboolean fu_genesys_usbhub_firmware_get_chip(FuGenesysUsbhubFirmware *self, const guint8 *buf, gsize bufsz, gsize offset, GError **error) { guint8 project_ic_type[6]; /* recognize GL3523 code base product */ if (!fu_memcpy_safe(project_ic_type, sizeof(project_ic_type), 0, /* dst */ buf, bufsz, offset + GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3523 + FU_STRUCT_GENESYS_TS_STATIC_OFFSET_MASK_PROJECT_IC_TYPE, /* src */ sizeof(project_ic_type), error)) return FALSE; if (memcmp(project_ic_type, "3521", 4) == 0) { self->chip.model = ISP_MODEL_HUB_GL3521; self->chip.revision = 10 * (project_ic_type[4] - '0') + (project_ic_type[5] - '0'); return TRUE; } if (memcmp(project_ic_type, "3523", 4) == 0) { self->chip.model = ISP_MODEL_HUB_GL3523; self->chip.revision = 10 * (project_ic_type[4] - '0') + (project_ic_type[5] - '0'); return TRUE; } /* recognize GL3590 */ if (!fu_memcpy_safe(project_ic_type, sizeof(project_ic_type), 0, /* dst */ buf, bufsz, offset + GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3590 + FU_STRUCT_GENESYS_TS_STATIC_OFFSET_MASK_PROJECT_IC_TYPE, /* src */ sizeof(project_ic_type), error)) return FALSE; if (memcmp(project_ic_type, "3590", 4) == 0) { self->chip.model = ISP_MODEL_HUB_GL3590; self->chip.revision = 10 * (project_ic_type[4] - '0') + (project_ic_type[5] - '0'); return TRUE; } /* recognize GL3525 first edition */ if (!fu_memcpy_safe(project_ic_type, sizeof(project_ic_type), 0, /* dst */ buf, bufsz, offset + GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3525 + FU_STRUCT_GENESYS_TS_STATIC_OFFSET_MASK_PROJECT_IC_TYPE, /* src */ sizeof(project_ic_type), error)) return FALSE; if (memcmp(project_ic_type, "3525", 4) == 0) { self->chip.model = ISP_MODEL_HUB_GL3525; self->chip.revision = 10 * (project_ic_type[4] - '0') + (project_ic_type[5] - '0'); return TRUE; } /* recognize GL3525 second edition */ if (!fu_memcpy_safe(project_ic_type, sizeof(project_ic_type), 0, /* dst */ buf, bufsz, offset + GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3525_V2 + FU_STRUCT_GENESYS_TS_STATIC_OFFSET_MASK_PROJECT_IC_TYPE, /* src */ sizeof(project_ic_type), error)) return FALSE; if (memcmp(project_ic_type, "3525", 4) == 0) { self->chip.model = ISP_MODEL_HUB_GL3525; self->chip.revision = 10 * (project_ic_type[4] - '0') + (project_ic_type[5] - '0'); return TRUE; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported IC"); return FALSE; } gboolean fu_genesys_usbhub_firmware_verify_checksum(GBytes *fw, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); guint16 fw_checksum = 0; guint16 checksum; /* get checksum */ if (!fu_memread_uint16_safe(buf, bufsz, bufsz - sizeof(checksum), &fw_checksum, G_BIG_ENDIAN, error)) { g_prefix_error(error, "failed to get checksum: "); return FALSE; } /* calculate checksum */ checksum = fu_sum16(buf, bufsz - sizeof(checksum)); if (checksum != fw_checksum) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "checksum mismatch, got 0x%04x, expected 0x%04x", checksum, fw_checksum); return FALSE; } return TRUE; } gboolean fu_genesys_usbhub_firmware_calculate_size(GBytes *fw, gsize offset, gsize *size, GError **error) { guint8 kbs = 0; if (!fu_memread_uint8_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset + GENESYS_USBHUB_CODE_SIZE_OFFSET, &kbs, error)) { g_prefix_error(error, "failed to get codesize: "); return FALSE; } if (kbs == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid codesize"); return FALSE; } if (size != NULL) *size = 1024 * kbs; return TRUE; } gboolean fu_genesys_usbhub_firmware_ensure_version(FuFirmware *firmware, GError **error) { guint16 version_raw = 0; g_autofree gchar *version = NULL; g_autoptr(GBytes) fw = NULL; fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; if (!fu_memread_uint16_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), GENESYS_USBHUB_VERSION_OFFSET, &version_raw, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to get version: "); return FALSE; } fu_firmware_set_version_raw(firmware, version_raw); version = g_strdup_printf("%02x.%02x", (version_raw & 0xFF00U) >> 8, (version_raw & 0x00FFU)); fu_firmware_set_version(firmware, version); /* success */ return TRUE; } static gboolean fu_genesys_usbhub_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { guint8 magic[4] = GENESYS_USBHUB_FW_SIG_TEXT_HUB; return fu_memcmp_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset + GENESYS_USBHUB_FW_SIG_OFFSET, magic, sizeof(magic), 0x0, sizeof(magic), error); } static gboolean fu_genesys_usbhub_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuGenesysUsbhubFirmware *self = FU_GENESYS_USBHUB_FIRMWARE(firmware); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); gsize code_size = 0; guint32 static_ts_offset = 0; g_autoptr(GBytes) fw_trunc = NULL; /* get chip */ if (!fu_genesys_usbhub_firmware_get_chip(self, buf, bufsz, offset, error)) { g_prefix_error(error, "failed to get chip: "); return FALSE; } fu_firmware_set_id(firmware, fu_genesys_fw_type_to_string(FU_GENESYS_FW_TYPE_HUB)); fu_firmware_set_idx(firmware, FU_GENESYS_FW_TYPE_HUB); fu_firmware_set_alignment(firmware, FU_FIRMWARE_ALIGNMENT_1K); /* get static tool string */ switch (self->chip.model) { case ISP_MODEL_HUB_GL3521: static_ts_offset = GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3521; break; case ISP_MODEL_HUB_GL3523: static_ts_offset = GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3523; break; case ISP_MODEL_HUB_GL3590: static_ts_offset = GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3590; break; case ISP_MODEL_HUB_GL3525: { guint8 configuration = 0; if (!fu_memread_uint8_safe(buf, bufsz, GENESYS_USBHUB_FW_CONFIGURATION_OFFSET, &configuration, error)) return FALSE; if (configuration == GENESYS_USBHUB_FW_CONFIGURATION_NEW_FORMAT || configuration == GENESYS_USBHUB_FW_CONFIGURATION_NEW_FORMAT_V2) static_ts_offset = GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3525_V2; else static_ts_offset = GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3525; break; } default: break; } self->st_static_ts = fu_struct_genesys_ts_static_parse_bytes(fw, static_ts_offset, error); /* deduce code size */ switch (self->chip.model) { case ISP_MODEL_HUB_GL3521: code_size = 0x5000; break; case ISP_MODEL_HUB_GL3523: { if (self->chip.revision == 50) { if (!fu_genesys_usbhub_firmware_calculate_size(fw, offset, &code_size, error)) return FALSE; } else { code_size = 0x6000; } break; } case ISP_MODEL_HUB_GL3590: case ISP_MODEL_HUB_GL3525: { if (!fu_genesys_usbhub_firmware_calculate_size(fw, offset, &code_size, error)) return FALSE; break; } default: break; } /* truncate to correct size */ fw_trunc = fu_bytes_new_offset(fw, offset, code_size, error); if (fw_trunc == NULL) return FALSE; fu_firmware_set_bytes(firmware, fw_trunc); /* calculate checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { if (!fu_genesys_usbhub_firmware_verify_checksum(fw_trunc, error)) return FALSE; } /* get firmware version */ if (!fu_genesys_usbhub_firmware_ensure_version(firmware, error)) return FALSE; /* parse remaining firmware bytes */ offset += code_size; while (offset < bufsz) { g_autoptr(FuFirmware) firmware_sub = NULL; firmware_sub = fu_firmware_new_from_gtypes(fw, offset, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error, FU_TYPE_GENESYS_USBHUB_DEV_FIRMWARE, FU_TYPE_GENESYS_USBHUB_PD_FIRMWARE, FU_TYPE_GENESYS_USBHUB_CODESIGN_FIRMWARE, G_TYPE_INVALID); if (firmware_sub == NULL) { g_prefix_error(error, "fw bytes have dual hub firmware: "); return FALSE; } fu_firmware_set_offset(firmware_sub, offset); fu_firmware_add_image(firmware, firmware_sub); offset += fu_firmware_get_size(firmware_sub); } /* success */ return TRUE; } static GByteArray * fu_genesys_usbhub_firmware_write(FuFirmware *firmware, GError **error) { FuGenesysUsbhubFirmware *self = FU_GENESYS_USBHUB_FIRMWARE(firmware); g_autoptr(GByteArray) buf = g_byte_array_new(); guint16 code_size = 0x6000; guint16 checksum; /* fixed size */ fu_byte_array_set_size(buf, code_size, 0x00); /* signature */ if (!fu_memcpy_safe(buf->data, buf->len, GENESYS_USBHUB_FW_SIG_OFFSET, /* dst */ (const guint8 *)GENESYS_USBHUB_FW_SIG_TEXT_HUB, GENESYS_USBHUB_FW_SIG_LEN, 0x0, /* src */ GENESYS_USBHUB_FW_SIG_LEN, error)) return NULL; /* static tool string */ if (self->st_static_ts != NULL) { if (!fu_memcpy_safe(buf->data, buf->len, GENESYS_USBHUB_STATIC_TOOL_STRING_OFFSET_GL3523, /* dst */ self->st_static_ts->data, self->st_static_ts->len, 0x0, /* src */ self->st_static_ts->len, error)) return NULL; } /* version */ if (!fu_memwrite_uint16_safe(buf->data, buf->len, GENESYS_USBHUB_VERSION_OFFSET, 0x1234, // TODO: parse from firmware version string G_BIG_ENDIAN, error)) return NULL; /* checksum */ checksum = fu_sum16(buf->data, code_size - sizeof(checksum)); if (!fu_memwrite_uint16_safe(buf->data, buf->len, code_size - sizeof(guint16), checksum, G_BIG_ENDIAN, error)) return NULL; /* success */ return g_steal_pointer(&buf); } static void fu_genesys_usbhub_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuGenesysUsbhubFirmware *self = FU_GENESYS_USBHUB_FIRMWARE(firmware); if (self->st_static_ts != NULL) { FuGenesysTsVersion tool_string_version = fu_struct_genesys_ts_static_get_tool_string_version(self->st_static_ts); g_autofree gchar *mask_project_code = fu_struct_genesys_ts_static_get_mask_project_code(self->st_static_ts); g_autofree gchar *mask_project_hardware = fu_struct_genesys_ts_static_get_mask_project_hardware(self->st_static_ts); g_autofree gchar *mask_project_firmware = fu_struct_genesys_ts_static_get_mask_project_firmware(self->st_static_ts); g_autofree gchar *mask_project_ic_type = fu_struct_genesys_ts_static_get_mask_project_ic_type(self->st_static_ts); g_autofree gchar *running_project_code = fu_struct_genesys_ts_static_get_mask_project_code(self->st_static_ts); g_autofree gchar *running_project_hardware = fu_struct_genesys_ts_static_get_running_project_hardware(self->st_static_ts); g_autofree gchar *running_project_firmware = fu_struct_genesys_ts_static_get_running_project_firmware(self->st_static_ts); g_autofree gchar *running_project_ic_type = fu_struct_genesys_ts_static_get_running_project_ic_type(self->st_static_ts); fu_xmlb_builder_insert_kv(bn, "tool_string_version", fu_genesys_ts_version_to_string(tool_string_version)); fu_xmlb_builder_insert_kv(bn, "mask_project_code", mask_project_code); if (mask_project_hardware != NULL) mask_project_hardware[0] += 0x11; /* '0' -> 'A'... */ fu_xmlb_builder_insert_kv(bn, "mask_project_hardware", mask_project_hardware); fu_xmlb_builder_insert_kv(bn, "mask_project_firmware", mask_project_firmware); fu_xmlb_builder_insert_kv(bn, "mask_project_ic_type", mask_project_ic_type); fu_xmlb_builder_insert_kv(bn, "running_project_code", running_project_code); if (running_project_hardware != NULL) running_project_hardware[0] += 0x11; /* '0' -> 'A'... */ fu_xmlb_builder_insert_kv(bn, "running_project_hardware", running_project_hardware); fu_xmlb_builder_insert_kv(bn, "running_project_firmware", running_project_firmware); fu_xmlb_builder_insert_kv(bn, "running_project_ic_type", running_project_ic_type); } } static gboolean fu_genesys_usbhub_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuGenesysUsbhubFirmware *self = FU_GENESYS_USBHUB_FIRMWARE(firmware); const gchar *tmp; /* optional properties */ self->st_static_ts = fu_struct_genesys_ts_static_new(); tmp = xb_node_query_text(n, "tool_string_version", NULL); if (tmp == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid tool_string_version"); return FALSE; } else { fu_struct_genesys_ts_static_set_tool_string_version(self->st_static_ts, tmp[0]); } /* mask_project_code */ tmp = xb_node_query_text(n, "mask_project_code", NULL); if (tmp != NULL) { gsize len = strlen(tmp); if (len != 4) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid mask_project_code %s, got 0x%x length", tmp, (guint)len); return FALSE; } if (!fu_struct_genesys_ts_static_set_mask_project_code(self->st_static_ts, tmp, error)) return FALSE; } /* mask_project_ic_type */ tmp = xb_node_query_text(n, "mask_project_ic_type", NULL); if (tmp != NULL) { gsize len = strlen(tmp); if (len != 6) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid mask_project_ic_type %s, got 0x%x length", tmp, (guint)len); return FALSE; } if (!fu_struct_genesys_ts_static_set_mask_project_ic_type(self->st_static_ts, tmp, error)) return FALSE; } /* success */ return TRUE; } static void fu_genesys_usbhub_firmware_init(FuGenesysUsbhubFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); } static void fu_genesys_usbhub_firmware_finalize(GObject *object) { FuGenesysUsbhubFirmware *self = FU_GENESYS_USBHUB_FIRMWARE(object); if (self->st_static_ts != NULL) g_byte_array_unref(self->st_static_ts); G_OBJECT_CLASS(fu_genesys_usbhub_firmware_parent_class)->finalize(object); } static void fu_genesys_usbhub_firmware_class_init(FuGenesysUsbhubFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_genesys_usbhub_firmware_finalize; klass_firmware->check_magic = fu_genesys_usbhub_firmware_check_magic; klass_firmware->parse = fu_genesys_usbhub_firmware_parse; klass_firmware->export = fu_genesys_usbhub_firmware_export; klass_firmware->build = fu_genesys_usbhub_firmware_build; klass_firmware->write = fu_genesys_usbhub_firmware_write; } FuFirmware * fu_genesys_usbhub_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_GENESYS_USBHUB_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/genesys/fu-genesys-usbhub-firmware.h000066400000000000000000000013011460375044200234310ustar00rootroot00000000000000/* * Copyright (C) 2021 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_GENESYS_USBHUB_FIRMWARE (fu_genesys_usbhub_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuGenesysUsbhubFirmware, fu_genesys_usbhub_firmware, FU, GENESYS_USBHUB_FIRMWARE, FuFirmware) FuFirmware * fu_genesys_usbhub_firmware_new(void); gboolean fu_genesys_usbhub_firmware_verify_checksum(GBytes *fw, GError **error); gboolean fu_genesys_usbhub_firmware_calculate_size(GBytes *fw, gsize offset, gsize *size, GError **error); gboolean fu_genesys_usbhub_firmware_ensure_version(FuFirmware *firmware, GError **error); fwupd-1.9.16/plugins/genesys/fu-genesys-usbhub-pd-firmware.c000066400000000000000000000047211460375044200240360ustar00rootroot00000000000000/* * Copyright (C) 2023 Adam.Chen * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-genesys-common.h" #include "fu-genesys-usbhub-firmware.h" #include "fu-genesys-usbhub-pd-firmware.h" #include "fu-genesys-usbhub-struct.h" struct _FuGenesysUsbhubPdFirmware { FuFirmwareClass parent_instance; }; G_DEFINE_TYPE(FuGenesysUsbhubPdFirmware, fu_genesys_usbhub_pd_firmware, FU_TYPE_FIRMWARE) static gboolean fu_genesys_usbhub_pd_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { guint8 magic[4] = GENESYS_USBHUB_FW_SIG_TEXT_PD; return fu_memcmp_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset + GENESYS_USBHUB_FW_SIG_OFFSET, magic, sizeof(magic), 0x0, sizeof(magic), error); } static gboolean fu_genesys_usbhub_pd_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { gsize code_size = 0; g_autoptr(GBytes) fw_trunc = NULL; fu_firmware_set_id(firmware, fu_genesys_fw_type_to_string(FU_GENESYS_FW_TYPE_PD)); fu_firmware_set_idx(firmware, FU_GENESYS_FW_TYPE_PD); fu_firmware_set_alignment(firmware, FU_FIRMWARE_ALIGNMENT_1K); /* truncate to correct size */ if (!fu_genesys_usbhub_firmware_calculate_size(fw, offset, &code_size, error)) { g_prefix_error(error, "not valid for pd: "); return FALSE; } fw_trunc = fu_bytes_new_offset(fw, offset, code_size, error); if (fw_trunc == NULL) return FALSE; fu_firmware_set_bytes(firmware, fw_trunc); /* calculate checksum */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { if (!fu_genesys_usbhub_firmware_verify_checksum(fw_trunc, error)) { g_prefix_error(error, "not valid for pd: "); return FALSE; } } /* get firmware version */ if (!fu_genesys_usbhub_firmware_ensure_version(firmware, error)) { g_prefix_error(error, "not valid for pd: "); return FALSE; } /* success */ return TRUE; } static void fu_genesys_usbhub_pd_firmware_init(FuGenesysUsbhubPdFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); } static void fu_genesys_usbhub_pd_firmware_class_init(FuGenesysUsbhubPdFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_genesys_usbhub_pd_firmware_check_magic; klass_firmware->parse = fu_genesys_usbhub_pd_firmware_parse; } fwupd-1.9.16/plugins/genesys/fu-genesys-usbhub-pd-firmware.h000066400000000000000000000006061460375044200240410ustar00rootroot00000000000000/* * Copyright (C) 2023 Adam.Chen * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_GENESYS_USBHUB_PD_FIRMWARE (fu_genesys_usbhub_pd_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuGenesysUsbhubPdFirmware, fu_genesys_usbhub_pd_firmware, FU, GENESYS_USBHUB_PD_FIRMWARE, FuFirmware) fwupd-1.9.16/plugins/genesys/fu-genesys-usbhub.rs000066400000000000000000000137211460375044200220250ustar00rootroot00000000000000// Copyright (C) 2023 Adam.Chen // SPDX-License-Identifier: LGPL-2.1+ // Tool String Descriptor #[repr(u8)] #[derive(ToString)] enum GenesysTsVersion { Dynamic_9Byte = 0x30, Bonding, BondingQc, VendorSupport, MultiToken, Dynamic_2nd, Reserved, Dynamic_13Byte, BrandProject, } #[derive(ToString, Parse, ParseBytes, New)] struct GenesysTsStatic { tool_string_version: GenesysTsVersion, mask_project_code: [char; 4], mask_project_hardware: char, // 0=a, 1=b... mask_project_firmware: [char; 2], // 01,02,03... mask_project_ic_type: [char; 6], // 352310=GL3523-10 (ASCII string) running_project_code: [char; 4], running_project_hardware: char, running_project_firmware: [char; 2], running_project_ic_type: [char; 6], firmware_version: [char; 4], // MMmm=MM.mm (ASCII string) } #[derive(ToString, Parse)] struct GenesysTsDynamicGl3523 { running_mode: char, // 'M' for mask code, the others for bank code ss_port_number: char, // super-speed port number hs_port_number: char, // high-speed port number ss_connection_status: char, // bit field. ON = DFP is a super-speed device hs_connection_status: char, // bit field. ON = DFP is a high-speed device fs_connection_status: char, // bit field. ON = DFP is a full-speed device ls_connection_status: char, // bit field. ON = DFP is a low-speed device charging: char, // bit field. ON = DFP is a charging port non_removable_port_status: char, // bit field. ON = DFP is a non-removable port // Bonding reports Hardware register status for GL3523: (ASCII) // 2 / 4 ports : 1 means 4 ports, 0 means 2 ports // MTT / STT : 1 means Multi Token Transfer, 0 means Single TT // Type - C : 1 means disable, 0 means enable // QC : 1 means disable, 0 means enable // Flash dump location : 1 means 32KB offset bank 1, 0 means 0 offset bank 0. // Tool string Version 1: // Bit3 : Flash dump location // BIT2 : Type - C // BIT1 : MTT / STT // BIT0 : 2 / 4 ports // Tool string Version 2 or newer : // Bit4 : Flash dump location // BIT3 : Type - C // BIT2 : MTT / STT // BIT1 : 2 / 4 ports // BIT0 : QC // Default use '0'~'F', plus Bit4 may over value, should extract that. bonding: char, } #[derive(ToString, Parse)] struct GenesysTsDynamicGl3590 { running_mode: char, ss_port_number: char, hs_port_number: char, ss_connection_status: char, hs_connection_status: char, fs_connection_status: char, ls_connection_status: char, charging: char, non_removable_port_status: char, // Bonding for GL3590-10/20: // Bit7 : Flash dump location, 0 means bank 0, 1 means bank 1. bonding: u8, } #[derive(ToString)] #[repr(u8)] enum GenesysFwStatus { Mask = 0x30, Bank1, Bank2, } #[derive(ToString, Parse)] struct GenesysTsDynamicGl359030 { running_mode: char, ss_port_number: char, hs_port_number: char, ss_connection_status: char, hs_connection_status: char, fs_connection_status: char, ls_connection_status: char, charging: char, non_removable_port_status: char, bonding: u8, hub_fw_status: GenesysFwStatus, dev_fw_status: GenesysFwStatus, dev_fw_version: u16le, } #[derive(ToString, Parse)] struct GenesysTsDynamicGl3525 { running_mode: char, ss_port_number: char, hs_port_number: char, ss_connection_status: char, hs_connection_status: char, fs_connection_status: char, ls_connection_status: char, charging: char, non_removable_port_status: char, bonding: u8, hub_fw_status: GenesysFwStatus, pd_fw_status: GenesysFwStatus, pd_fw_version: u16le, dev_fw_status: GenesysFwStatus, dev_fw_version: u16le, } #[derive(ToString, Parse)] struct GenesysTsFirmwareInfo { tool_version: [u8; 6], // ISP tool defined by itself address_mode: u8, // 3 or 4: support 3 or 4-bytes address, others are no meaning. build_fw_time: [char; 12], // YYYYMMDDhhmm update_fw_time: [char; 12], // YYYYMMDDhhmm } #[derive(ToString)] #[repr(u8)] enum GenesysVsCodesignCheck { Unsupported = 0x30, Scaler, Fw, Master, // there is a master hub has Scaler or Hw, and this hub verified by the master. Reserved, Hw, } #[repr(u8)] enum GenesysVsHidIsp { Unsupported = 0x30, Support, CodesignNReset, // Support Codesign ISP Bank2 FW without reset. } #[derive(ToString, New, Parse)] struct GenesysTsVendorSupport { version: [char; 2], reserved1: [char; 8], codesign_check: GenesysVsCodesignCheck, // offset: 0x0a reserved2: [char; 4], hid_isp: GenesysVsHidIsp, // offset: 0x0f reserved3: [char; 15], } #[derive(ToString, Parse)] struct GenesysTsBrandProject { project: [char; 15], } // Firmware info #[derive(ToString)] enum GenesysFwCodesign { None, Rsa, Ecdsa, } #[derive(ParseBytes, ValidateBytes)] struct GenesysFwCodesignInfoRsa { tag_n: u32be == 0x4E203D20, // 'N = ' text_n: [char; 512], end_n: u16be == 0x0D0A, tag_e: u32be == 0x45203D20, // 'E = ' text_e: [char; 6], end_e: u16be == 0x0D0A, signature: [u8; 256], } #[derive(Parse, Validate)] struct GenesysFwRsaPublicKeyText { tag_n: u32be == 0x4E203D20, // 'N = ' text_n: [char; 512], end_n: u16be == 0x0D0A, tag_e: u32be == 0x45203D20, // 'E = ' text_e: [char; 6], end_e: u16be == 0x0D0A, } #[derive(Parse, ParseBytes, Validate, ValidateBytes)] struct GenesysFwCodesignInfoEcdsa { hash: [u8; 32], key: [u8; 64], signature: [u8; 64], } #[derive(Parse, Validate)] struct GenesysFwEcdsaPublicKey { key: [u8; 64], } #[derive(ToString)] enum GenesysFwType { Hub, // inside hub start DevBridge, Pd, Codesign, // inside hub end InsideHubCount, Scaler, // vendor support start Unknown = 0xff, } fwupd-1.9.16/plugins/genesys/genesys.quirk000066400000000000000000000043271460375044200206400ustar00rootroot00000000000000# Genesys Logic USB Hubs [USB\VID_05E3&PID_0610] Plugin = genesys GType = FuGenesysUsbhubDevice [USB\VID_05E3&PID_0630] Plugin = genesys GType = FuGenesysUsbhubDevice # Genesys Logic USB Hubs' HID [USB\VID_05E3&PID_0102] Plugin = genesys GType = FuGenesysHubhidDevice [USB\VID_05E3&PID_0155] Plugin = genesys GType = FuGenesysHubhidDevice # Brand Products # Google Servo Dock [USB\VID_05E3&PID_0610&VENDOR_GOOGLE&IC_359010&BONDING_6A&PORTNUM_25&VENDORSUP_E8A9A9E4-6C17-5F9C-B7BD-CDA49FE66D75] Name = Servo Dock # Google EVB [USB\VID_05E3&PID_0630&PROJECT_DD5E70E4-0F1A-57EB-A0F9-44555ECC56EE] Name = GL3525S USB Hub EVB Flags = dual-image # HP M2xfd # usbhub [USB\VID_03F0&PID_0610] Plugin = genesys GType = FuGenesysUsbhubDevice Name = HP USB-C Controller Flags = dual-image,has-public-key GenesysUsbhubSwitchRequest = 0xA1 GenesysUsbhubReadRequest = 0xA2 GenesysUsbhubWriteRequest = 0xA3 [USB\VID_03F0&PID_0610&PUBKEY_AB859399-95B8-5817-B521-9AD8CC7F5BD6] Name = HP M24fd USB-C Hub Flags = dual-image,has-public-key,has-mstar-scaler GenesysUsbhubSwitchRequest = 0xA1 GenesysUsbhubReadRequest = 0xA2 GenesysUsbhubWriteRequest = 0xA3 [USB\VID_03F0&PID_0610&PUBKEY_6BE97D77-C2BA-5AA2-B7DF-B9B318BEC2B5] Name = HP M27fd USB-C Hub Flags = dual-image,has-public-key,has-mstar-scaler GenesysUsbhubSwitchRequest = 0xA1 GenesysUsbhubReadRequest = 0xA2 GenesysUsbhubWriteRequest = 0xA3 # scaler [GENESYS_SCALER\MSTAR_TSUM_G&PUBKEY_B335BDCE-7073-5D0E-9BD3-9B69C1A6899F&PANELREV_RIM101] Name = HP M24fd USB-C Monitor Flags = use-i2c-ch0 GenesysScalerGpioOutputRegister = 0x0426 GenesysScalerGpioEnableRegister = 0x0428 GenesysScalerGpioValue = 0x01 GenesysScalerCfiFlashId = 0xC22016 [GENESYS_SCALER\MSTAR_TSUM_G&PUBKEY_847A3650-8648-586B-83C8-8B53714F37E3&PANELREV_AIM101] Name = HP M27fd USB-C Monitor Flags = use-i2c-ch0 GenesysScalerGpioOutputRegister = 0x0426 GenesysScalerGpioEnableRegister = 0x0428 GenesysScalerGpioValue = 0x01 GenesysScalerCfiFlashId = 0xC84016 [GENESYS_SCALER\MSTAR_TSUM_G&PUBKEY_847A3650-8648-586B-83C8-8B53714F37E3&PANELREV_RIM101] Name = HP M27fd USB-C Monitor Flags = use-i2c-ch0 GenesysScalerGpioOutputRegister = 0x0426 GenesysScalerGpioEnableRegister = 0x0428 GenesysScalerGpioValue = 0x01 GenesysScalerCfiFlashId = 0xC84016 fwupd-1.9.16/plugins/genesys/meson.build000066400000000000000000000014341460375044200202440ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginGenesys"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('genesys.quirk') plugin_builtins += static_library('fu_plugin_genesys', rustgen.process( 'fu-genesys-usbhub.rs', # fuzzing ), sources: [ 'fu-genesys-scaler-firmware.c', # fuzzing 'fu-genesys-usbhub-firmware.c', # fuzzing 'fu-genesys-usbhub-dev-firmware.c', # fuzzing 'fu-genesys-usbhub-pd-firmware.c', # fuzzing 'fu-genesys-usbhub-codesign-firmware.c', # fuzzing 'fu-genesys-scaler-device.c', 'fu-genesys-usbhub-device.c', 'fu-genesys-hubhid-device.c', 'fu-genesys-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/genesys/tests/000077500000000000000000000000001460375044200172425ustar00rootroot00000000000000fwupd-1.9.16/plugins/genesys/tests/genesys-scaler.bin000066400000000000000000000010351460375044200226570ustar00rootroot00000000000000hello worldfwupd-1.9.16/plugins/genesys/tests/genesys-scaler.builder.xml000066400000000000000000000002041460375044200243310ustar00rootroot00000000000000 aGVsbG8gd29ybGQ= A fwupd-1.9.16/plugins/genesys/tests/genesys-usbhub.bin000066400000000000000000000600001460375044200226730ustar00rootroot00000000000000XROM4352310fwupd-1.9.16/plugins/genesys/tests/genesys-usbhub.builder.xml000066400000000000000000000003141460375044200243520ustar00rootroot00000000000000 aGVsbG8gd29ybGQ= 1 352310 fwupd-1.9.16/plugins/goodix-moc/000077500000000000000000000000001460375044200164705ustar00rootroot00000000000000fwupd-1.9.16/plugins/goodix-moc/README.md000066400000000000000000000020601460375044200177450ustar00rootroot00000000000000--- title: Plugin: Goodix Fingerprint Sensor --- ## Introduction The plugin used for update firmware for fingerprint sensors from Goodix. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `com.goodix.goodixmoc` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_27C6&PID_6001` ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x27C6` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.5.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Boger Wang: @boger047 fwupd-1.9.16/plugins/goodix-moc/fu-goodixmoc-common.h000066400000000000000000000023321460375044200225270ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * Copyright (C) 2020 boger wang * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /* protocol */ #define GX_CMD_ACK 0xAA #define GX_CMD_VERSION 0xD0 #define GX_CMD_RESET 0xB4 #define GX_CMD_UPGRADE 0x80 #define GX_CMD_UPGRADE_INIT 0x00 #define GX_CMD_UPGRADE_DATA 0x01 #define GX_CMD1_DEFAULT 0x00 #define GX_SIZE_CRC32 4 /* type covert */ #define MAKE_CMD_EX(cmd0, cmd1) ((guint16)(((cmd0) << 8) | (cmd1))) typedef struct { guint8 format[2]; guint8 fwtype[8]; guint8 fwversion[8]; guint8 customer[8]; guint8 mcu[8]; guint8 sensor[8]; guint8 algversion[8]; guint8 interface[8]; guint8 protocol[8]; guint8 flashVersion[8]; guint8 reserved[62]; } GxfpVersionInfo; typedef struct { guint8 cmd; gboolean configured; } GxfpAckMsg; typedef struct { guint8 result; union { GxfpAckMsg ack_msg; GxfpVersionInfo version_info; }; } GxfpCmdResp; typedef enum { GX_PKG_TYPE_NORMAL = 0x80, GX_PKG_TYPE_EOP = 0, } GxPkgType; typedef struct __attribute__((__packed__)) { guint8 cmd0; guint8 cmd1; guint8 pkg_flag; guint8 reserved; guint16 len; guint8 crc8; guint8 rev_crc8; } GxfpPkgHeader; fwupd-1.9.16/plugins/goodix-moc/fu-goodixmoc-device.c000066400000000000000000000303251460375044200224740ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * Copyright (C) 2020 boger wang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-goodixmoc-common.h" #include "fu-goodixmoc-device.h" struct _FuGoodixMocDevice { FuUsbDevice parent_instance; guint8 dummy_seq; }; G_DEFINE_TYPE(FuGoodixMocDevice, fu_goodixmoc_device, FU_TYPE_USB_DEVICE) #define GX_USB_BULK_EP_IN (3 | 0x80) #define GX_USB_BULK_EP_OUT (1 | 0x00) #define GX_USB_INTERFACE 0 #define GX_USB_DATAIN_TIMEOUT 2000 /* ms */ #define GX_USB_DATAOUT_TIMEOUT 200 /* ms */ #define GX_FLASH_TRANSFER_BLOCK_SIZE 1000 /* 1000 */ static gboolean goodixmoc_device_cmd_send(FuGoodixMocDevice *self, guint8 cmd0, guint8 cmd1, GxPkgType type, GByteArray *req, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); guint32 crc_all = 0; guint32 crc_hdr = 0; gsize actual_len = 0; g_autoptr(GByteArray) buf = g_byte_array_new(); /* build header */ fu_byte_array_append_uint8(buf, cmd0); fu_byte_array_append_uint8(buf, cmd1); fu_byte_array_append_uint8(buf, type); /* pkg_flag */ fu_byte_array_append_uint8(buf, self->dummy_seq++); /* reserved */ fu_byte_array_append_uint16(buf, req->len + GX_SIZE_CRC32, G_LITTLE_ENDIAN); crc_hdr = fu_crc8(buf->data, buf->len); fu_byte_array_append_uint8(buf, crc_hdr); fu_byte_array_append_uint8(buf, ~crc_hdr); g_byte_array_append(buf, req->data, req->len); crc_all = fu_crc32(buf->data, buf->len); fu_byte_array_append_uint32(buf, crc_all, G_LITTLE_ENDIAN); /* send zero length package */ if (!g_usb_device_bulk_transfer(usb_device, GX_USB_BULK_EP_OUT, NULL, 0, NULL, GX_USB_DATAOUT_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to req: "); return FALSE; } fu_dump_full(G_LOG_DOMAIN, "REQST", buf->data, buf->len, 16, FU_DUMP_FLAGS_SHOW_ADDRESSES); /* send data */ if (!g_usb_device_bulk_transfer(usb_device, GX_USB_BULK_EP_OUT, buf->data, buf->len, &actual_len, GX_USB_DATAOUT_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to req: "); return FALSE; } if (actual_len != buf->len) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid length"); return FALSE; } /* success */ return TRUE; } static gboolean goodixmoc_device_cmd_recv(FuGoodixMocDevice *self, GxfpCmdResp *presponse, gboolean data_reply, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); guint32 crc_actual = 0; guint32 crc_calculated = 0; gsize actual_len = 0; gsize offset = 0; g_return_val_if_fail(presponse != NULL, FALSE); /* * package format * | zlp | ack | zlp | data | */ while (1) { guint16 header_len = 0x0; guint8 header_cmd0 = 0x0; g_autoptr(GByteArray) reply = g_byte_array_new(); fu_byte_array_set_size(reply, GX_FLASH_TRANSFER_BLOCK_SIZE, 0x00); if (!g_usb_device_bulk_transfer(usb_device, GX_USB_BULK_EP_IN, reply->data, reply->len, &actual_len, /* allowed to return short read */ GX_USB_DATAIN_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to reply: "); return FALSE; } /* receive zero length package */ if (actual_len == 0) continue; fu_dump_full(G_LOG_DOMAIN, "REPLY", reply->data, actual_len, 16, FU_DUMP_FLAGS_SHOW_ADDRESSES); /* parse package header */ if (!fu_memread_uint8_safe(reply->data, reply->len, 0x0, &header_cmd0, error)) return FALSE; if (!fu_memread_uint16_safe(reply->data, reply->len, 0x4, &header_len, G_LITTLE_ENDIAN, error)) return FALSE; offset = sizeof(GxfpPkgHeader) + header_len - GX_SIZE_CRC32; crc_actual = fu_crc32(reply->data, offset); if (!fu_memread_uint32_safe(reply->data, reply->len, offset, &crc_calculated, G_LITTLE_ENDIAN, error)) return FALSE; if (crc_actual != crc_calculated) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid checksum, got 0x%x, expected 0x%x", crc_calculated, crc_actual); return FALSE; } /* parse package data */ if (!fu_memread_uint8_safe(reply->data, reply->len, sizeof(GxfpPkgHeader) + 0x00, &presponse->result, error)) return FALSE; if (header_cmd0 == GX_CMD_ACK) { if (header_len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid bufsz"); return FALSE; } if (!fu_memread_uint8_safe(reply->data, reply->len, sizeof(GxfpPkgHeader) + 0x01, &presponse->ack_msg.cmd, error)) return FALSE; } else if (header_cmd0 == GX_CMD_VERSION) { if (!fu_memcpy_safe((guint8 *)&presponse->version_info, sizeof(presponse->version_info), 0x0, /* dst */ reply->data, reply->len, sizeof(GxfpPkgHeader) + 0x01, /* src */ sizeof(GxfpVersionInfo), error)) return FALSE; } /* continue after ack received */ if (header_cmd0 == GX_CMD_ACK && data_reply) continue; break; } /* success */ return TRUE; } static gboolean fu_goodixmoc_device_cmd_xfer(FuGoodixMocDevice *device, guint8 cmd0, guint8 cmd1, GxPkgType type, GByteArray *req, GxfpCmdResp *presponse, gboolean data_reply, GError **error) { FuGoodixMocDevice *self = FU_GOODIXMOC_DEVICE(device); if (!goodixmoc_device_cmd_send(self, cmd0, cmd1, type, req, error)) return FALSE; return goodixmoc_device_cmd_recv(self, presponse, data_reply, error); } static gboolean fu_goodixmoc_device_setup_version(FuGoodixMocDevice *self, GError **error) { GxfpCmdResp rsp = {0}; g_autofree gchar *version = NULL; g_autoptr(GByteArray) req = g_byte_array_new(); fu_byte_array_append_uint8(req, 0); /* dummy */ if (!fu_goodixmoc_device_cmd_xfer(self, GX_CMD_VERSION, GX_CMD1_DEFAULT, GX_PKG_TYPE_EOP, req, &rsp, TRUE, error)) return FALSE; version = g_strndup((const gchar *)rsp.version_info.fwversion, sizeof(rsp.version_info.fwversion)); fu_device_set_version(FU_DEVICE(self), version); return TRUE; } static gboolean fu_goodixmoc_device_update_init(FuGoodixMocDevice *self, GError **error) { GxfpCmdResp rsp = {0}; g_autoptr(GByteArray) req = g_byte_array_new(); /* update initial */ if (!fu_goodixmoc_device_cmd_xfer(self, GX_CMD_UPGRADE, GX_CMD_UPGRADE_INIT, GX_PKG_TYPE_EOP, req, &rsp, TRUE, error)) { g_prefix_error(error, "failed to send initial update: "); return FALSE; } /* check result */ if (rsp.result != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "initial update failed [0x%x]", rsp.result); return FALSE; } return TRUE; } static gboolean fu_goodixmoc_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuGoodixMocDevice *self = FU_GOODIXMOC_DEVICE(device); GxfpCmdResp rsp = {0}; g_autoptr(GByteArray) req = g_byte_array_new(); /* reset device */ if (!fu_goodixmoc_device_cmd_xfer(self, GX_CMD_RESET, 0x03, GX_PKG_TYPE_EOP, req, &rsp, FALSE, error)) { g_prefix_error(error, "failed to send reset device: "); return FALSE; } /* check result */ if (rsp.result != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to reset device [0x%x]", rsp.result); return FALSE; } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_goodixmoc_device_setup(FuDevice *device, GError **error) { FuGoodixMocDevice *self = FU_GOODIXMOC_DEVICE(device); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_goodixmoc_device_parent_class)->setup(device, error)) return FALSE; /* ensure version */ if (!fu_goodixmoc_device_setup_version(self, error)) { g_prefix_error(error, "failed to get firmware version: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_goodixmoc_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuGoodixMocDevice *self = FU_GOODIXMOC_DEVICE(device); GxPkgType pkg_eop = GX_PKG_TYPE_NORMAL; GxfpCmdResp rsp = {0}; gboolean wait_data_reply = FALSE; g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "init"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* build packets */ chunks = fu_chunk_array_new_from_bytes(fw, 0x00, GX_FLASH_TRANSFER_BLOCK_SIZE); /* don't auto-boot firmware */ if (!fu_goodixmoc_device_update_init(self, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to initial update: %s", error_local->message); return FALSE; } fu_progress_step_done(progress); /* write each block */ for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GByteArray) req = g_byte_array_new(); g_autoptr(GError) error_block = NULL; g_byte_array_append(req, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); /* the last chunk */ if (i == fu_chunk_array_length(chunks) - 1) { wait_data_reply = TRUE; pkg_eop = GX_PKG_TYPE_EOP; } if (!fu_goodixmoc_device_cmd_xfer(self, GX_CMD_UPGRADE, GX_CMD_UPGRADE_DATA, pkg_eop, req, &rsp, wait_data_reply, &error_block)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to write: %s", error_block->message); return FALSE; } /* check update status */ if (wait_data_reply && rsp.result != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to verify firmware [0x%x]", rsp.result); return FALSE; } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* success! */ return TRUE; } static void fu_goodixmoc_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_goodixmoc_device_init(FuGoodixMocDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_remove_delay(FU_DEVICE(self), 5000); fu_device_add_protocol(FU_DEVICE(self), "com.goodix.goodixmoc"); fu_device_set_name(FU_DEVICE(self), "Fingerprint Sensor"); fu_device_set_summary(FU_DEVICE(self), "Match-On-Chip fingerprint sensor"); fu_device_set_vendor(FU_DEVICE(self), "Goodix"); fu_device_set_install_duration(FU_DEVICE(self), 10); fu_device_set_firmware_size_min(FU_DEVICE(self), 0x20000); fu_device_set_firmware_size_max(FU_DEVICE(self), 0x30000); fu_usb_device_add_interface(FU_USB_DEVICE(self), GX_USB_INTERFACE); } static void fu_goodixmoc_device_class_init(FuGoodixMocDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_goodixmoc_device_write_firmware; klass_device->setup = fu_goodixmoc_device_setup; klass_device->attach = fu_goodixmoc_device_attach; klass_device->set_progress = fu_goodixmoc_device_set_progress; } fwupd-1.9.16/plugins/goodix-moc/fu-goodixmoc-device.h000066400000000000000000000005521460375044200225000ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * Copyright (C) 2020 boger wang * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_GOODIXMOC_DEVICE (fu_goodixmoc_device_get_type()) G_DECLARE_FINAL_TYPE(FuGoodixMocDevice, fu_goodixmoc_device, FU, GOODIXMOC_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/goodix-moc/fu-goodixmoc-plugin.c000066400000000000000000000020601460375044200225260ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * Copyright (C) 2020 boger wang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-goodixmoc-device.h" #include "fu-goodixmoc-plugin.h" struct _FuGoodixMocPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuGoodixMocPlugin, fu_goodixmoc_plugin, FU_TYPE_PLUGIN) static void fu_goodixmoc_plugin_init(FuGoodixMocPlugin *self) { } static void fu_goodixmoc_plugin_object_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_set_name(plugin, "goodixmoc"); } static void fu_goodixmoc_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_GOODIXMOC_DEVICE); } static void fu_goodixmoc_plugin_class_init(FuGoodixMocPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->constructed = fu_goodixmoc_plugin_object_constructed; plugin_class->constructed = fu_goodixmoc_plugin_constructed; } fwupd-1.9.16/plugins/goodix-moc/fu-goodixmoc-plugin.h000066400000000000000000000003611460375044200225350ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuGoodixMocPlugin, fu_goodixmoc_plugin, FU, GOODIXMOC_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/goodix-moc/goodixmoc.quirk000066400000000000000000000011331460375044200215330ustar00rootroot00000000000000# Goodix Fingerprint sensor [USB\VID_27C6&PID_60A2] Plugin = goodixmoc Flags = enforce-requires [USB\VID_27C6&PID_6384] Plugin = goodixmoc Flags = enforce-requires [USB\VID_27C6&PID_639C] Plugin = goodixmoc Flags = enforce-requires [USB\VID_27C6&PID_63AC] Plugin = goodixmoc Flags = enforce-requires [USB\VID_27C6&PID_6594] Plugin = goodixmoc Flags = enforce-requires [USB\VID_27C6&PID_6496] Plugin = goodixmoc Flags = enforce-requires [USB\VID_27C6&PID_659A] Plugin = goodixmoc Flags = enforce-requires [USB\VID_27C6&PID_609C] Plugin = goodixmoc Flags = enforce-requires,dual-image RemoveDelay = 10000 fwupd-1.9.16/plugins/goodix-moc/meson.build000066400000000000000000000006431460375044200206350ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginGoodixMoc"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('goodixmoc.quirk') plugin_builtins += static_library('fu_plugin_goodixmoc', sources: [ 'fu-goodixmoc-device.c', 'fu-goodixmoc-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/goodix-tp/000077500000000000000000000000001460375044200163355ustar00rootroot00000000000000fwupd-1.9.16/plugins/goodix-tp/README.md000066400000000000000000000022671460375044200176230ustar00rootroot00000000000000--- title: Plugin: Goodix Touch Controller Sensor --- ## Introduction This plugin allows updating Touchpad devices from Goodix. Devices are enumerated using HID and raw I²C nodes. The I²C mode is used for ABS devices and firmware recovery of HID devices. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * `com.goodix.goodixtp` ## GUID Generation These device uses the standard DeviceInstanceId values, e.g. * `HIDRAW\VEN_27C6&DEV_1234` ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Vendor ID Security The vendor ID is set from the HID vendor, for example set to `HIDRAW:0x01E0` ## External Interface Access This plugin requires ioctl access to `HIDIOCSFEATURE` and `HIDIOCGFEATURE`. ## Version Considerations This plugin has been available since fwupd version `1.9.2`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Goodix: @xulinkun fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-brlb-device.c000066400000000000000000000342171460375044200231310ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-goodixtp-brlb-device.h" #include "fu-goodixtp-brlb-firmware.h" #include "fu-goodixtp-common.h" struct _FuGoodixtpBrlbDevice { FuGoodixtpHidDevice parent_instance; }; G_DEFINE_TYPE(FuGoodixtpBrlbDevice, fu_goodixtp_brlb_device, FU_TYPE_GOODIXTP_HID_DEVICE) static gboolean fu_goodixtp_brlb_device_read_pkg(FuGoodixtpBrlbDevice *self, guint32 addr, guint8 *buf, guint32 bufsz, GError **error) { guint8 hidbuf[PACKAGE_LEN] = {0}; hidbuf[0] = REPORT_ID; hidbuf[1] = I2C_DIRECT_RW; hidbuf[2] = 0; hidbuf[3] = 0; hidbuf[4] = 7; hidbuf[5] = I2C_READ_FLAG; fu_memwrite_uint32(hidbuf + 6, addr, G_BIG_ENDIAN); fu_memwrite_uint16(hidbuf + 10, bufsz, G_BIG_ENDIAN); if (!fu_goodixtp_hid_device_set_report(FU_GOODIXTP_HID_DEVICE(self), hidbuf, 12, error)) return FALSE; if (!fu_goodixtp_hid_device_get_report(FU_GOODIXTP_HID_DEVICE(self), hidbuf, sizeof(hidbuf), error)) return FALSE; if (hidbuf[3] != 0 || hidbuf[4] != bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "Failed to read_pkg, hidbuf[3]:%d hidbuf[4]:%d", hidbuf[3], hidbuf[4]); return FALSE; } if (!fu_memcpy_safe(buf, bufsz, 0, hidbuf, sizeof(hidbuf), 5, hidbuf[4], error)) return FALSE; return TRUE; } static gboolean fu_goodixtp_brlb_device_hid_read(FuGoodixtpBrlbDevice *self, guint32 addr, guint8 *buf, guint32 bufsz, GError **error) { g_autoptr(GPtrArray) chunks = fu_chunk_array_mutable_new(buf, bufsz, addr, 0, PACKAGE_LEN - 12); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_goodixtp_brlb_device_read_pkg(self, fu_chunk_get_address(chk), fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; } return TRUE; } static gboolean fu_goodixtp_brlb_device_hid_write(FuGoodixtpBrlbDevice *self, guint32 addr, guint8 *buf, guint32 bufsz, GError **error) { g_autoptr(GPtrArray) chunks = fu_chunk_array_new(buf, bufsz, addr, 0, PACKAGE_LEN - 12); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint8 hidbuf[PACKAGE_LEN] = {0}; hidbuf[0] = REPORT_ID; hidbuf[1] = I2C_DIRECT_RW; if (i == chunks->len - 1) hidbuf[2] = 0x00; else hidbuf[2] = 0x01; hidbuf[3] = i; hidbuf[4] = fu_chunk_get_data_sz(chk) + 7; hidbuf[5] = I2C_WRITE_FLAG; fu_memwrite_uint32(hidbuf + 6, fu_chunk_get_address(chk), G_BIG_ENDIAN); fu_memwrite_uint16(hidbuf + 10, fu_chunk_get_data_sz(chk), G_BIG_ENDIAN); if (!fu_memcpy_safe(hidbuf, sizeof(hidbuf), 12, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0, fu_chunk_get_data_sz(chk), error)) return FALSE; if (!fu_goodixtp_hid_device_set_report(FU_GOODIXTP_HID_DEVICE(self), hidbuf, fu_chunk_get_data_sz(chk) + 12, error)) { g_prefix_error(error, "failed write data to addr=0x%x, len=%d: ", fu_chunk_get_address(chk), (gint)fu_chunk_get_data_sz(chk)); return FALSE; } } return TRUE; } static gboolean fu_goodixtp_brlb_device_send_cmd(FuGoodixtpBrlbDevice *self, guint8 cmd, guint8 *buf, gsize bufsz, GError **error) { guint8 hidbuf[PACKAGE_LEN] = {0}; hidbuf[0] = REPORT_ID; hidbuf[1] = cmd; hidbuf[2] = 0x00; hidbuf[3] = 0x00; hidbuf[4] = (guint8)bufsz; if (!fu_memcpy_safe(hidbuf, sizeof(hidbuf), 5, buf, bufsz, 0, bufsz, error)) return FALSE; if (!fu_goodixtp_hid_device_set_report(FU_GOODIXTP_HID_DEVICE(self), hidbuf, bufsz + 5, error)) { g_prefix_error(error, "send cmd[%x] failed: ", cmd); return FALSE; } return TRUE; } static gboolean fu_goodixtp_brlb_device_ensure_version(FuGoodixtpBrlbDevice *self, GError **error) { guint8 hidbuf[14] = {0}; guint8 vice_ver; guint8 inter_ver; guint8 cfg_ver; guint32 patch_vid_raw; g_autofree gchar *patch_pid = NULL; if (!fu_goodixtp_brlb_device_hid_read(self, 0x1001E, hidbuf, sizeof(hidbuf), error)) { g_prefix_error(error, "failed read PID/VID: "); return FALSE; } vice_ver = hidbuf[10]; inter_ver = hidbuf[11]; patch_pid = fu_memstrsafe(hidbuf, sizeof(hidbuf), 0x0, 8, NULL); if (patch_pid != NULL) fu_goodixtp_hid_device_set_patch_pid(FU_GOODIXTP_HID_DEVICE(self), patch_pid); patch_vid_raw = fu_memread_uint32(hidbuf + 8, G_BIG_ENDIAN); if (patch_vid_raw != 0) { g_autofree gchar *patch_vid = g_strdup_printf("%04X", patch_vid_raw); fu_goodixtp_hid_device_set_patch_vid(FU_GOODIXTP_HID_DEVICE(self), patch_vid); } fu_goodixtp_hid_device_set_sensor_id(FU_GOODIXTP_HID_DEVICE(self), hidbuf[13]); if (!fu_goodixtp_brlb_device_hid_read(self, 0x10076, hidbuf, 5, error)) { g_prefix_error(error, "Failed read config id/version: "); return FALSE; } cfg_ver = hidbuf[4]; fu_goodixtp_hid_device_set_config_ver(FU_GOODIXTP_HID_DEVICE(self), cfg_ver); fu_device_set_version_u32(FU_DEVICE(self), (vice_ver << 16) | (inter_ver << 8) | cfg_ver); return TRUE; } static gboolean fu_goodixtp_brlb_device_wait_mini_cb(FuDevice *device, gpointer user_data, GError **error) { FuGoodixtpBrlbDevice *self = FU_GOODIXTP_BRLB_DEVICE(device); guint8 hidbuf[1] = {0x0}; if (!fu_goodixtp_brlb_device_hid_read(self, 0x10010, hidbuf, sizeof(hidbuf), error)) return FALSE; if (hidbuf[0] != 0xDD) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "ack=0x%02x", hidbuf[0]); return FALSE; } return TRUE; } static gboolean fu_goodixtp_brlb_device_wait_erase_cb(FuDevice *device, gpointer user_data, GError **error) { FuGoodixtpBrlbDevice *self = FU_GOODIXTP_BRLB_DEVICE(device); guint8 hidbuf[5]; guint8 recvBuf[5] = {0x0}; memset(hidbuf, 0x55, sizeof(hidbuf)); if (!fu_goodixtp_brlb_device_hid_write(self, 0x14000, hidbuf, 5, error)) return FALSE; if (!fu_goodixtp_brlb_device_hid_read(self, 0x14000, recvBuf, 5, error)) return FALSE; if (memcmp(hidbuf, recvBuf, 5)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "sram not ready"); return FALSE; } return TRUE; } static gboolean fu_goodixtp_brlb_device_update_prepare(FuGoodixtpBrlbDevice *self, GError **error) { guint8 cmdbuf[1]; /* step 1. switch mini system */ cmdbuf[0] = 0x01; if (!fu_goodixtp_brlb_device_send_cmd(self, 0x10, cmdbuf, sizeof(cmdbuf), error)) { g_prefix_error(error, "failed send minisystem cmd: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 100); if (!fu_device_retry_full(FU_DEVICE(self), fu_goodixtp_brlb_device_wait_mini_cb, 5, 30, NULL, error)) { g_prefix_error(error, "wait brlb minisystem status failed: "); return FALSE; } g_debug("switch mini system successfully"); /* step 2. erase flash */ cmdbuf[0] = 0x01; if (!fu_goodixtp_brlb_device_send_cmd(self, 0x11, cmdbuf, sizeof(cmdbuf), error)) { g_prefix_error(error, "Failed send erase flash cmd: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 50); if (!fu_device_retry_full(FU_DEVICE(self), fu_goodixtp_brlb_device_wait_erase_cb, 5, 20, NULL, error)) { g_prefix_error(error, "wait brlb erase status failed: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_goodixtp_brlb_device_wait_flash_cb(FuDevice *device, gpointer user_data, GError **error) { FuGoodixtpBrlbDevice *self = FU_GOODIXTP_BRLB_DEVICE(device); guint8 hidbuf[1] = {0}; if (!fu_goodixtp_brlb_device_hid_read(self, 0x10011, hidbuf, 1, error)) { g_prefix_error(error, "Failed to read 0x10011"); return FALSE; } if (hidbuf[0] != 0xAA) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "ack=0x%02x", hidbuf[0]); return FALSE; } return TRUE; } static gboolean fu_goodixtp_brlb_device_load_sub_firmware_cb(FuDevice *device, gpointer user_data, GError **error) { FuGoodixtpBrlbDevice *self = FU_GOODIXTP_BRLB_DEVICE(device); FuChunk *chk = (FuChunk *)user_data; guint32 checksum = 0; guint8 buf_align4k[RAM_BUFFER_SIZE] = {0}; guint8 cmdbuf[10] = {0}; if (!fu_memcpy_safe(buf_align4k, sizeof(buf_align4k), 0, (guint8 *)fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0, fu_chunk_get_data_sz(chk), error)) return FALSE; /* send fw data to dram */ if (!fu_goodixtp_brlb_device_hid_write(self, 0x14000, buf_align4k, sizeof(buf_align4k), error)) { g_prefix_error(error, "write fw data failed: "); return FALSE; } /* send checksum */ for (guint i = 0; i < sizeof(buf_align4k); i += 2) { guint16 tmp_val; if (!fu_memread_uint16_safe(buf_align4k, sizeof(buf_align4k), i, &tmp_val, G_LITTLE_ENDIAN, error)) return FALSE; checksum += tmp_val; } fu_memwrite_uint16(cmdbuf, sizeof(buf_align4k), G_BIG_ENDIAN); fu_memwrite_uint32(cmdbuf + 2, fu_chunk_get_address(chk), G_BIG_ENDIAN); fu_memwrite_uint32(cmdbuf + 6, checksum, G_BIG_ENDIAN); if (!fu_goodixtp_brlb_device_send_cmd(self, 0x12, cmdbuf, sizeof(cmdbuf), error)) { g_prefix_error(error, "failed send start update cmd: "); return FALSE; } fu_device_sleep(device, 80); /* wait update finish */ if (!fu_device_retry_full(device, fu_goodixtp_brlb_device_wait_flash_cb, 10, 20, NULL, error)) { g_prefix_error(error, "wait flash status failed: "); return FALSE; } return TRUE; } static gboolean fu_goodixtp_brlb_device_update_process(FuGoodixtpBrlbDevice *self, FuChunk *chk, GError **error) { if (!fu_device_retry_full(FU_DEVICE(self), fu_goodixtp_brlb_device_load_sub_firmware_cb, 3, 10, chk, error)) { g_prefix_error(error, "load sub firmware failed, addr:0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } return TRUE; } static gboolean fu_goodixtp_brlb_device_update_finish(FuGoodixtpBrlbDevice *self, GError **error) { guint8 cmdbuf[1] = {0x01}; /* reset IC */ if (!fu_goodixtp_brlb_device_send_cmd(self, 0x13, cmdbuf, sizeof(cmdbuf), error)) { g_prefix_error(error, "failed reset IC: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 100); return TRUE; } static gboolean fu_goodixtp_brlb_device_setup(FuDevice *device, GError **error) { FuGoodixtpBrlbDevice *self = FU_GOODIXTP_BRLB_DEVICE(device); if (!fu_goodixtp_brlb_device_ensure_version(self, error)) { g_prefix_error(error, "brlb read version failed: "); return FALSE; } return TRUE; } static FuFirmware * fu_goodixtp_brlb_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuGoodixtpBrlbDevice *self = FU_GOODIXTP_BRLB_DEVICE(device); g_autoptr(FuFirmware) firmware = fu_goodixtp_brlb_firmware_new(); if (!fu_goodixtp_brlb_firmware_parse( FU_GOODIXTP_FIRMWARE(firmware), fw, fu_goodixtp_hid_device_get_sensor_id(FU_GOODIXTP_HID_DEVICE(self)), error)) return NULL; return g_steal_pointer(&firmware); } static gboolean fu_goodixtp_brlb_device_write_image(FuGoodixtpBrlbDevice *self, FuFirmware *img, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GBytes) blob = NULL; blob = fu_firmware_get_bytes(img, error); if (blob == NULL) return FALSE; chunks = fu_chunk_array_new_from_bytes(blob, fu_firmware_get_addr(img), RAM_BUFFER_SIZE); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_goodixtp_brlb_device_update_process(self, chk, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), 20); fu_progress_step_done(progress); } return TRUE; } static gboolean fu_goodixtp_brlb_device_write_images(FuGoodixtpBrlbDevice *self, GPtrArray *imgs, FuProgress *progress, GError **error) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, imgs->len); for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); if (!fu_goodixtp_brlb_device_write_image(self, img, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } return TRUE; } static gboolean fu_goodixtp_brlb_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuGoodixtpBrlbDevice *self = FU_GOODIXTP_BRLB_DEVICE(device); guint32 fw_ver = fu_goodixtp_firmware_get_version(FU_GOODIXTP_FIRMWARE(firmware)); g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 10, "prepare"); fu_progress_add_step(progress, FWUPD_STATUS_DOWNLOADING, 85, "download"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "reload"); if (!fu_goodixtp_brlb_device_update_prepare(self, error)) return FALSE; fu_progress_step_done(progress); if (!fu_goodixtp_brlb_device_write_images(self, imgs, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); if (!fu_goodixtp_brlb_device_update_finish(self, error)) return FALSE; if (!fu_goodixtp_brlb_device_ensure_version(self, error)) return FALSE; fu_progress_step_done(progress); if (fu_device_get_version_raw(device) != fw_ver) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "update failed chip_ver:%x != bin_ver:%x", (guint)fu_device_get_version_raw(device), (guint)fw_ver); return FALSE; } return TRUE; } static void fu_goodixtp_brlb_device_init(FuGoodixtpBrlbDevice *self) { } static void fu_goodixtp_brlb_device_class_init(FuGoodixtpBrlbDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_goodixtp_brlb_device_setup; klass_device->reload = fu_goodixtp_brlb_device_setup; klass_device->prepare_firmware = fu_goodixtp_brlb_device_prepare_firmware; klass_device->write_firmware = fu_goodixtp_brlb_device_write_firmware; } fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-brlb-device.h000066400000000000000000000005651460375044200231350ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-goodixtp-hid-device.h" #define FU_TYPE_GOODIXTP_BRLB_DEVICE (fu_goodixtp_brlb_device_get_type()) G_DECLARE_FINAL_TYPE(FuGoodixtpBrlbDevice, fu_goodixtp_brlb_device, FU, GOODIXTP_BRLB_DEVICE, FuGoodixtpHidDevice) fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-brlb-firmware.c000066400000000000000000000073011460375044200235000ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-goodixtp-brlb-firmware.h" #include "fu-goodixtp-common.h" #include "fu-goodixtp-struct.h" struct _FuGoodixtpBrlbFirmware { FuGoodixtpFirmware parent_instance; }; G_DEFINE_TYPE(FuGoodixtpBrlbFirmware, fu_goodixtp_brlb_firmware, FU_TYPE_GOODIXTP_FIRMWARE) #define FW_HEADER_SIZE 512 gboolean fu_goodixtp_brlb_firmware_parse(FuGoodixtpFirmware *self, GBytes *fw, guint8 sensor_id, GError **error) { guint32 version; gsize offset_hdr; gsize offset_payload = FW_HEADER_SIZE; guint32 checksum = 0; guint32 firmware_size; guint8 subsys_num; guint8 cfg_ver = 0; gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GByteArray) st = NULL; st = fu_struct_goodix_brlb_hdr_parse_bytes(fw, 0x0, error); if (st == NULL) return FALSE; firmware_size = fu_struct_goodix_brlb_hdr_get_firmware_size(st); firmware_size += 8; /* [payload][64 bytes padding?][config] */ if ((gsize)firmware_size < bufsz) { g_autoptr(GBytes) fw_img = NULL; g_autoptr(FuFirmware) img = fu_firmware_new(); fu_firmware_set_idx(img, 4); fu_firmware_set_addr(img, 0x40000); fw_img = fu_bytes_new_offset(fw, firmware_size + 64, bufsz - (gsize)firmware_size - 64, error); if (fw_img == NULL) return FALSE; fu_firmware_set_bytes(img, fw_img); fu_firmware_add_image(FU_FIRMWARE(self), img); if (!fu_memread_uint8_safe(buf, bufsz, firmware_size + 64 + 34, &cfg_ver, error)) return FALSE; g_debug("config size:0x%x, config ver:0x%02x", (guint)fu_firmware_get_size(img), cfg_ver); } /* verify checksum */ for (guint i = 8; i < firmware_size; i += 2) { guint16 tmp_val; if (!fu_memread_uint16_safe(buf, bufsz, i, &tmp_val, G_LITTLE_ENDIAN, error)) return FALSE; checksum += tmp_val; } if (checksum != fu_struct_goodix_brlb_hdr_get_checksum(st)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid checksum"); return FALSE; } /* parse each image */ subsys_num = fu_struct_goodix_brlb_hdr_get_subsys_num(st); if (subsys_num == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid subsys_num"); return FALSE; } offset_hdr = st->len; for (guint i = 0; i < subsys_num; i++) { guint32 img_size; g_autoptr(GByteArray) st_img = NULL; st_img = fu_struct_goodix_brlb_img_parse_bytes(fw, offset_hdr, error); if (st_img == NULL) return FALSE; img_size = fu_struct_goodix_brlb_img_get_size(st_img); if (fu_struct_goodix_brlb_img_get_kind(st_img) != 0x0B && fu_struct_goodix_brlb_img_get_kind(st_img) != 0x01) { g_autoptr(GBytes) fw_img = NULL; g_autoptr(FuFirmware) img = fu_firmware_new(); fu_firmware_set_idx(img, fu_struct_goodix_brlb_img_get_kind(st_img)); fu_firmware_set_addr(img, fu_struct_goodix_brlb_img_get_addr(st_img)); fw_img = fu_bytes_new_offset(fw, offset_payload, img_size, error); if (fw_img == NULL) return FALSE; fu_firmware_set_bytes(img, fw_img); if (!fu_firmware_add_image_full(FU_FIRMWARE(self), img, error)) return FALSE; } offset_hdr += st_img->len; offset_payload += img_size; } version = (fu_struct_goodix_brlb_hdr_get_vid(st) << 8) | cfg_ver; fu_goodixtp_firmware_set_version(self, version); return TRUE; } static void fu_goodixtp_brlb_firmware_init(FuGoodixtpBrlbFirmware *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); } static void fu_goodixtp_brlb_firmware_class_init(FuGoodixtpBrlbFirmwareClass *klass) { } FuFirmware * fu_goodixtp_brlb_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_GOODIXTP_BRLB_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-brlb-firmware.h000066400000000000000000000010551460375044200235050ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-goodixtp-firmware.h" #define FU_TYPE_GOODIXTP_BRLB_FIRMWARE (fu_goodixtp_brlb_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuGoodixtpBrlbFirmware, fu_goodixtp_brlb_firmware, FU, GOODIXTP_BRLB_FIRMWARE, FuGoodixtpFirmware) gboolean fu_goodixtp_brlb_firmware_parse(FuGoodixtpFirmware *self, GBytes *fw, guint8 sensor_id, GError **error); FuFirmware * fu_goodixtp_brlb_firmware_new(void); fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-common.h000066400000000000000000000005141460375044200222410ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define PACKAGE_LEN 65 #define REPORT_ID 0x0E #define I2C_DIRECT_RW 0x20 #define I2C_READ_FLAG 1 #define I2C_WRITE_FLAG 0 #define RAM_BUFFER_SIZE 4096 #define CFG_MAX_SIZE 4096 fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-firmware.c000066400000000000000000000016771460375044200225730ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-goodixtp-firmware.h" typedef struct { FuFirmwareClass parent_instance; guint32 version; } FuGoodixtpFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuGoodixtpFirmware, fu_goodixtp_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_goodixtp_firmware_get_instance_private(o)) guint32 fu_goodixtp_firmware_get_version(FuGoodixtpFirmware *self) { FuGoodixtpFirmwarePrivate *priv = GET_PRIVATE(self); return priv->version; } void fu_goodixtp_firmware_set_version(FuGoodixtpFirmware *self, guint32 version) { FuGoodixtpFirmwarePrivate *priv = GET_PRIVATE(self); priv->version = version; } static void fu_goodixtp_firmware_init(FuGoodixtpFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); } static void fu_goodixtp_firmware_class_init(FuGoodixtpFirmwareClass *klass) { } fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-firmware.h000066400000000000000000000010461460375044200225660ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_GOODIXTP_FIRMWARE (fu_goodixtp_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuGoodixtpFirmware, fu_goodixtp_firmware, FU, GOODIXTP_FIRMWARE, FuFirmware) struct _FuGoodixtpFirmwareClass { FuFirmwareClass parent_class; }; guint32 fu_goodixtp_firmware_get_version(FuGoodixtpFirmware *self); void fu_goodixtp_firmware_set_version(FuGoodixtpFirmware *self, guint32 version); fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-gtx8-device.c000066400000000000000000000370751460375044200231070ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-goodixtp-common.h" #include "fu-goodixtp-gtx8-device.h" #include "fu-goodixtp-gtx8-firmware.h" struct _FuGoodixtpGtx8Device { FuGoodixtpHidDevice parent_instance; }; G_DEFINE_TYPE(FuGoodixtpGtx8Device, fu_goodixtp_gtx8_device, FU_TYPE_GOODIXTP_HID_DEVICE) #define CMD_ADDR 0x60CC #define BL_STATE_ADDR 0x5095 #define FLASH_RESULT_ADDR 0x5096 #define FLASH_BUFFER_ADDR 0xC000 static gboolean fu_goodixtp_gtx8_device_read_pkg(FuGoodixtpGtx8Device *self, guint32 addr, guint8 *buf, guint32 bufsz, GError **error) { guint8 hidbuf[PACKAGE_LEN] = {0}; hidbuf[0] = REPORT_ID; hidbuf[1] = I2C_DIRECT_RW; hidbuf[2] = 0; hidbuf[3] = 0; hidbuf[4] = 5; hidbuf[5] = I2C_READ_FLAG; fu_memwrite_uint16(hidbuf + 6, addr, G_BIG_ENDIAN); fu_memwrite_uint16(hidbuf + 8, bufsz, G_BIG_ENDIAN); if (!fu_goodixtp_hid_device_set_report(FU_GOODIXTP_HID_DEVICE(self), hidbuf, 10, error)) return FALSE; if (!fu_goodixtp_hid_device_get_report(FU_GOODIXTP_HID_DEVICE(self), hidbuf, sizeof(hidbuf), error)) return FALSE; if (hidbuf[3] != 0 || hidbuf[4] != bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "Failed to read_pkg, hidbuf[3]:%d hidbuf[4]:%d", hidbuf[3], hidbuf[4]); return FALSE; } if (!fu_memcpy_safe(buf, bufsz, 0, hidbuf, sizeof(hidbuf), 5, hidbuf[4], error)) return FALSE; return TRUE; } static gboolean fu_goodixtp_gtx8_device_hid_read(FuGoodixtpGtx8Device *self, guint32 addr, guint8 *buf, guint32 bufsz, GError **error) { g_autoptr(GPtrArray) chunks = fu_chunk_array_mutable_new(buf, bufsz, addr, 0, PACKAGE_LEN - 10); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_goodixtp_gtx8_device_read_pkg(self, fu_chunk_get_address(chk), fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; } return TRUE; } static gboolean fu_goodixtp_gtx8_device_hid_write(FuGoodixtpGtx8Device *self, guint32 addr, guint8 *buf, guint32 bufsz, GError **error) { g_autoptr(GPtrArray) chunks = fu_chunk_array_new(buf, bufsz, addr, 0, PACKAGE_LEN - 10); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint8 hidbuf[PACKAGE_LEN] = {0}; hidbuf[0] = REPORT_ID; hidbuf[1] = I2C_DIRECT_RW; if (i == chunks->len - 1) hidbuf[2] = 0x00; else hidbuf[2] = 0x01; hidbuf[3] = i; hidbuf[4] = fu_chunk_get_data_sz(chk) + 5; hidbuf[5] = I2C_WRITE_FLAG; fu_memwrite_uint16(hidbuf + 6, fu_chunk_get_address(chk), G_BIG_ENDIAN); fu_memwrite_uint16(hidbuf + 8, fu_chunk_get_data_sz(chk), G_BIG_ENDIAN); if (!fu_memcpy_safe(hidbuf, sizeof(hidbuf), 10, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0, fu_chunk_get_data_sz(chk), error)) return FALSE; if (!fu_goodixtp_hid_device_set_report(FU_GOODIXTP_HID_DEVICE(self), hidbuf, fu_chunk_get_data_sz(chk) + 10, error)) { g_prefix_error(error, "failed write data to addr=0x%x, len=%d: ", fu_chunk_get_address(chk), (gint)fu_chunk_get_data_sz(chk)); return FALSE; } } return TRUE; } static gboolean fu_goodixtp_gtx8_device_send_cmd(FuGoodixtpGtx8Device *self, guint8 *buf, guint32 bufsz, GError **error) { guint8 hidbuf[PACKAGE_LEN] = {0}; if (!fu_memcpy_safe(hidbuf, sizeof(hidbuf), 0, buf, bufsz, 0, bufsz, error)) return FALSE; hidbuf[0] = REPORT_ID; if (!fu_goodixtp_hid_device_set_report(FU_GOODIXTP_HID_DEVICE(self), hidbuf, bufsz, error)) { g_prefix_error(error, "failed to send cmd: "); return FALSE; } return TRUE; } static gboolean fu_goodixtp_gtx8_device_ensure_version(FuGoodixtpGtx8Device *self, GError **error) { guint8 fw_info[72] = {0}; guint8 vice_ver; guint8 inter_ver; guint8 cfg_ver = 0x0; guint8 chksum; guint32 patch_vid_raw; g_autofree gchar *patch_pid = NULL; if (!fu_goodixtp_gtx8_device_hid_read(self, 0x60DC, &cfg_ver, 1, error)) { g_prefix_error(error, "failed to read cfg version: "); return FALSE; } if (!fu_goodixtp_gtx8_device_hid_read(self, 0x452C, fw_info, sizeof(fw_info), error)) { g_prefix_error(error, "failed to read firmware version: "); return FALSE; } /*check fw version*/ chksum = fu_sum8(fw_info, sizeof(fw_info)); if (chksum != 0x0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "fw version check sum error: %d", chksum); return FALSE; } patch_pid = fu_memstrsafe(fw_info, sizeof(fw_info), 0x9, 5, NULL); if (patch_pid != NULL) fu_goodixtp_hid_device_set_patch_pid(FU_GOODIXTP_HID_DEVICE(self), patch_pid); patch_vid_raw = fu_memread_uint32(fw_info + 17, G_BIG_ENDIAN); if (patch_vid_raw != 0) { g_autofree gchar *patch_vid = g_strdup_printf("%04X", patch_vid_raw); fu_goodixtp_hid_device_set_patch_vid(FU_GOODIXTP_HID_DEVICE(self), patch_vid); } fu_goodixtp_hid_device_set_sensor_id(FU_GOODIXTP_HID_DEVICE(self), fw_info[21] & 0x0F); fu_goodixtp_hid_device_set_config_ver(FU_GOODIXTP_HID_DEVICE(self), cfg_ver); vice_ver = fw_info[19]; inter_ver = fw_info[20]; fu_device_set_version_u32(FU_DEVICE(self), (vice_ver << 16) | (inter_ver << 8) | cfg_ver); return TRUE; } static gboolean fu_goodixtp_gtx8_device_disable_report(FuGoodixtpGtx8Device *self, GError **error) { guint8 buf_disable[] = {0x33, 0x00, 0xCD}; guint8 buf_confirm[] = {0x35, 0x00, 0xCB}; guint8 buf[3] = {0}; for (gint i = 0; i < 3; i++) { if (!fu_goodixtp_gtx8_device_hid_write(self, CMD_ADDR, buf_disable, sizeof(buf_disable), error)) { g_prefix_error(error, "send close report cmd failed: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 10); } if (!fu_goodixtp_gtx8_device_hid_write(self, CMD_ADDR, buf_confirm, sizeof(buf_confirm), error)) { g_prefix_error(error, "send confirm cmd failed: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 30); if (!fu_goodixtp_gtx8_device_hid_read(self, CMD_ADDR, buf, sizeof(buf), error)) { g_prefix_error(error, "read confirm flag failed: "); return FALSE; } if (buf[1] != 1) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "close report failed, flag[0x%02X]", buf[1]); return FALSE; } /* success */ return TRUE; } static gboolean fu_goodixtp_gtx8_device_wait_bl_cb(FuDevice *device, gpointer user_data, GError **error) { FuGoodixtpGtx8Device *self = FU_GOODIXTP_GTX8_DEVICE(device); guint8 hidbuf[1] = {0}; if (!fu_goodixtp_gtx8_device_hid_read(self, BL_STATE_ADDR, hidbuf, 1, error)) return FALSE; if (hidbuf[0] != 0xDD) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "ack=0x%02x", hidbuf[0]); return FALSE; } return TRUE; } static gboolean fu_goodixtp_gtx8_device_update_prepare(FuGoodixtpGtx8Device *self, GError **error) { guint8 cmd_switch_to_patch[] = {0x00, 0x10, 0x00, 0x00, 0x01, 0x01}; guint8 cmd_start_update[] = {0x00, 0x11, 0x00, 0x00, 0x01, 0x01}; /* close report */ if (!fu_goodixtp_gtx8_device_disable_report(self, error)) { g_prefix_error(error, "disable report failed: "); return FALSE; } if (!fu_goodixtp_gtx8_device_send_cmd(self, cmd_switch_to_patch, sizeof(cmd_switch_to_patch), error)) { g_prefix_error(error, "failed switch to patch: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 100); if (!fu_device_retry_full(FU_DEVICE(self), fu_goodixtp_gtx8_device_wait_bl_cb, 5, 30, NULL, error)) { g_prefix_error(error, "wait gtx8 BL status failed: "); return FALSE; } if (!fu_goodixtp_gtx8_device_disable_report(self, error)) { g_prefix_error(error, "disable report failed: "); return FALSE; } /* start update */ if (!fu_goodixtp_gtx8_device_send_cmd(self, cmd_start_update, sizeof(cmd_start_update), error)) { g_prefix_error(error, "failed to start update: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 100); return TRUE; } static gboolean fu_goodixtp_gtx8_device_soft_reset_ic(FuGoodixtpGtx8Device *self, GError **error) { guint8 cmd_reset[] = {0x0E, 0x13, 0x00, 0x00, 0x01, 0x01}; guint8 cmd_switch_ptp_mode[] = {0x03, 0x03, 0x00, 0x00, 0x01, 0x01}; if (!fu_goodixtp_gtx8_device_send_cmd(self, cmd_reset, sizeof(cmd_reset), error)) { g_prefix_error(error, "failed write reset command: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 100); if (!fu_goodixtp_gtx8_device_send_cmd(self, cmd_switch_ptp_mode, sizeof(cmd_switch_ptp_mode), error)) { g_prefix_error(error, "failed switch to ptp mode: "); return FALSE; } return TRUE; } static gboolean fu_goodixtp_gtx8_device_wait_flash_cb(FuDevice *device, gpointer user_data, GError **error) { FuGoodixtpGtx8Device *self = FU_GOODIXTP_GTX8_DEVICE(device); guint8 hidbuf[1] = {0}; if (!fu_goodixtp_gtx8_device_hid_read(self, FLASH_RESULT_ADDR, hidbuf, 1, error)) return FALSE; if (hidbuf[0] != 0xAA) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "ack=0x%02x", hidbuf[0]); return FALSE; } return TRUE; } static gboolean fu_goodixtp_gtx8_device_load_sub_firmware_cb(FuDevice *device, gpointer user_data, GError **error) { FuGoodixtpGtx8Device *self = FU_GOODIXTP_GTX8_DEVICE(device); guint16 check_sum; guint8 buf_align4k[RAM_BUFFER_SIZE] = {0}; guint8 buf_load_flash[15] = {0x0e, 0x12, 0x00, 0x00, 0x06}; guint8 dummy = 0; FuChunk *chk = (FuChunk *)user_data; if (!fu_memcpy_safe(buf_align4k, sizeof(buf_align4k), 0, (guint8 *)fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0, fu_chunk_get_data_sz(chk), error)) return FALSE; if (!fu_goodixtp_gtx8_device_hid_write(self, FLASH_BUFFER_ADDR, buf_align4k, sizeof(buf_align4k), error)) { g_prefix_error(error, "Failed to load fw bufsz=0x%x, addr=0x%x", (guint)sizeof(buf_align4k), fu_chunk_get_address(chk)); return FALSE; } /* inform IC to load 4K data to flash */ check_sum = fu_sum16w(buf_align4k, sizeof(buf_align4k), G_BIG_ENDIAN); fu_memwrite_uint16(buf_load_flash + 5, sizeof(buf_align4k), G_BIG_ENDIAN); fu_memwrite_uint16(buf_load_flash + 7, fu_chunk_get_address(chk) >> 8, G_BIG_ENDIAN); fu_memwrite_uint16(buf_load_flash + 9, check_sum, G_BIG_ENDIAN); if (!fu_goodixtp_gtx8_device_send_cmd(self, buf_load_flash, 11, error)) { g_prefix_error(error, "failed write load flash command: "); return FALSE; } fu_device_sleep(device, 80); if (!fu_device_retry_full(device, fu_goodixtp_gtx8_device_wait_flash_cb, 10, 20, NULL, error)) { g_prefix_error(error, "wait flash status failed: "); return FALSE; } if (!fu_goodixtp_gtx8_device_hid_write(self, FLASH_RESULT_ADDR, &dummy, sizeof(dummy), error)) return FALSE; fu_device_sleep(device, 5); return TRUE; } static gboolean fu_goodixtp_gtx8_device_update_process(FuGoodixtpGtx8Device *self, FuChunk *chk, GError **error) { if (!fu_device_retry_full(FU_DEVICE(self), fu_goodixtp_gtx8_device_load_sub_firmware_cb, 3, 10, chk, error)) { g_prefix_error(error, "load sub firmware failed, addr=0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } return TRUE; } static gboolean fu_goodixtp_gtx8_device_setup(FuDevice *device, GError **error) { FuGoodixtpGtx8Device *self = FU_GOODIXTP_GTX8_DEVICE(device); if (!fu_goodixtp_gtx8_device_ensure_version(self, error)) { g_prefix_error(error, "gtx8 read version failed: "); return FALSE; } return TRUE; } static FuFirmware * fu_goodixtp_gtx8_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuGoodixtpGtx8Device *self = FU_GOODIXTP_GTX8_DEVICE(device); g_autoptr(FuFirmware) firmware = fu_goodixtp_gtx8_firmware_new(); if (!fu_goodixtp_gtx8_firmware_parse( FU_GOODIXTP_FIRMWARE(firmware), fw, fu_goodixtp_hid_device_get_sensor_id(FU_GOODIXTP_HID_DEVICE(self)), error)) return NULL; return g_steal_pointer(&firmware); } static gboolean fu_goodixtp_gtx8_device_write_image(FuGoodixtpGtx8Device *self, FuFirmware *img, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GBytes) blob = NULL; blob = fu_firmware_get_bytes(img, error); if (blob == NULL) return FALSE; chunks = fu_chunk_array_new_from_bytes(blob, fu_firmware_get_addr(img), RAM_BUFFER_SIZE); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_goodixtp_gtx8_device_update_process(self, chk, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), 20); fu_progress_step_done(progress); } return TRUE; } static gboolean fu_goodixtp_gtx8_device_write_images(FuGoodixtpGtx8Device *self, GPtrArray *imgs, FuProgress *progress, GError **error) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, imgs->len); for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); if (!fu_goodixtp_gtx8_device_write_image(self, img, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } return TRUE; } static gboolean fu_goodixtp_gtx8_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuGoodixtpGtx8Device *self = FU_GOODIXTP_GTX8_DEVICE(device); FuGoodixtpFirmware *firmware_goodixtp = FU_GOODIXTP_FIRMWARE(firmware); guint32 fw_ver = fu_goodixtp_firmware_get_version(firmware_goodixtp); g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 10, "prepare"); fu_progress_add_step(progress, FWUPD_STATUS_DOWNLOADING, 85, "download"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "reload"); if (!fu_goodixtp_gtx8_device_update_prepare(self, error)) return FALSE; fu_progress_step_done(progress); if (!fu_goodixtp_gtx8_device_write_images(self, imgs, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* reset IC */ if (!fu_goodixtp_gtx8_device_soft_reset_ic(self, error)) return FALSE; if (!fu_goodixtp_gtx8_device_ensure_version(self, error)) return FALSE; fu_progress_step_done(progress); if (fu_device_get_version_raw(device) != fw_ver) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "update failed chip_ver:%x != bin_ver:%x", (guint)fu_device_get_version_raw(device), (guint)fw_ver); return FALSE; } return TRUE; } static void fu_goodixtp_gtx8_device_init(FuGoodixtpGtx8Device *self) { } static void fu_goodixtp_gtx8_device_class_init(FuGoodixtpGtx8DeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_goodixtp_gtx8_device_setup; klass_device->reload = fu_goodixtp_gtx8_device_setup; klass_device->prepare_firmware = fu_goodixtp_gtx8_device_prepare_firmware; klass_device->write_firmware = fu_goodixtp_gtx8_device_write_firmware; } fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-gtx8-device.h000066400000000000000000000005651460375044200231060ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-goodixtp-hid-device.h" #define FU_TYPE_GOODIXTP_GTX8_DEVICE (fu_goodixtp_gtx8_device_get_type()) G_DECLARE_FINAL_TYPE(FuGoodixtpGtx8Device, fu_goodixtp_gtx8_device, FU, GOODIXTP_GTX8_DEVICE, FuGoodixtpHidDevice) fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-gtx8-firmware.c000066400000000000000000000134041460375044200234520ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-goodixtp-common.h" #include "fu-goodixtp-gtx8-firmware.h" #include "fu-goodixtp-struct.h" struct _FuGoodixtpGtx8Firmware { FuGoodixtpFirmware parent_instance; }; G_DEFINE_TYPE(FuGoodixtpGtx8Firmware, fu_goodixtp_gtx8_firmware, FU_TYPE_GOODIXTP_FIRMWARE) #define GTX8_FW_DATA_OFFSET 256 gboolean fu_goodixtp_gtx8_firmware_parse(FuGoodixtpFirmware *self, GBytes *fw, guint8 sensor_id, GError **error) { gboolean has_config = FALSE; gsize bufsz = 0; guint16 checksum = 0; guint32 firmware_size = 0; guint32 version; guint8 cfg_ver = 0; guint8 subsys_num; guint sub_cfg_info_pos; guint32 offset_hdr; guint32 offset_payload = GTX8_FW_DATA_OFFSET; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GByteArray) st = NULL; st = fu_struct_goodix_gtx8_hdr_parse_bytes(fw, 0x0, error); if (st == NULL) return FALSE; firmware_size = fu_struct_goodix_gtx8_hdr_get_firmware_size(st); if (firmware_size < 6 || firmware_size > G_MAXUINT32 - GTX8_FW_DATA_OFFSET) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid firmware size"); return FALSE; } if ((gsize)firmware_size + 6 != bufsz) { g_debug("check file len unequal 0x%x != 0x%x, this bin may contain config", (guint)firmware_size + 6, (guint)bufsz); has_config = TRUE; } /* verify checksum */ for (guint i = 6; i < (guint)firmware_size + 6; i++) { guint8 tmp_val = 0; if (!fu_memread_uint8_safe(buf, bufsz, i, &tmp_val, error)) return FALSE; checksum += tmp_val; } if (checksum != fu_struct_goodix_gtx8_hdr_get_checksum(st)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum invalid"); return FALSE; } if (has_config) { guint16 cfg_packlen = 0; guint cfg_offset; guint8 sub_cfg_num; guint16 read_cksum; if (!fu_memread_uint16_safe(buf, bufsz, firmware_size + 6, &cfg_packlen, G_BIG_ENDIAN, error)) return FALSE; if ((gint)(bufsz - firmware_size - 6) != (gint)cfg_packlen + 6) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "config pack len error"); return FALSE; } checksum = 0; for (guint i = firmware_size + 12; i < (guint)bufsz; i++) { guint8 tmp_val; if (!fu_memread_uint8_safe(buf, bufsz, i, &tmp_val, error)) return FALSE; checksum += tmp_val; } if (!fu_memread_uint16_safe(buf, bufsz, firmware_size + 10, &read_cksum, G_BIG_ENDIAN, error)) return FALSE; if (checksum != read_cksum) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "config pack checksum error"); return FALSE; } if (!fu_memread_uint8_safe(buf, bufsz, firmware_size + 9, &sub_cfg_num, error)) return FALSE; if (sub_cfg_num == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "sub_cfg_num is 0"); return FALSE; } sub_cfg_info_pos = firmware_size + 12; cfg_offset = firmware_size + 6 + 64; for (guint i = 0; i < sub_cfg_num; i++) { guint8 sub_cfg_id = 0; guint16 sub_cfg_len = 0; if (!fu_memread_uint8_safe(buf, bufsz, sub_cfg_info_pos, &sub_cfg_id, error)) return FALSE; if (!fu_memread_uint16_safe(buf, bufsz, sub_cfg_info_pos + 1, &sub_cfg_len, G_BIG_ENDIAN, error)) return FALSE; if (sensor_id == sub_cfg_id) { g_autoptr(GBytes) fw_img = NULL; g_autoptr(FuFirmware) img = fu_firmware_new(); fu_firmware_set_idx(img, 3); fu_firmware_set_addr(img, 0x1E000); fw_img = fu_bytes_new_offset(fw, cfg_offset, sub_cfg_len, error); if (fw_img == NULL) return FALSE; fu_firmware_set_bytes(img, fw_img); if (!fu_firmware_add_image_full(FU_FIRMWARE(self), img, error)) return FALSE; if (!fu_memread_uint8_safe(buf, bufsz, cfg_offset, &cfg_ver, error)) return FALSE; g_debug("Find a cfg match sensorID:ID=%d, cfg version=%d", sensor_id, cfg_ver); break; } cfg_offset += sub_cfg_len; sub_cfg_info_pos += 3; } } /* parse each image */ subsys_num = fu_struct_goodix_gtx8_hdr_get_subsys_num(st); if (subsys_num == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "subsys_num is 0, exit"); return FALSE; } offset_hdr = st->len; for (guint i = 0; i < subsys_num; i++) { guint32 img_size; g_autoptr(GByteArray) st_img = NULL; st_img = fu_struct_goodix_gtx8_img_parse_bytes(fw, offset_hdr, error); if (st_img == NULL) return FALSE; img_size = fu_struct_goodix_gtx8_img_get_size(st_img); if (fu_struct_goodix_gtx8_img_get_kind(st_img) != 0x01) { g_autoptr(GBytes) fw_img = NULL; g_autoptr(FuFirmware) img = fu_firmware_new(); fu_firmware_set_idx(img, fu_struct_goodix_gtx8_img_get_kind(st_img)); fu_firmware_set_addr(img, fu_struct_goodix_gtx8_img_get_addr(st_img) << 8); fw_img = fu_bytes_new_offset(fw, offset_payload, img_size, error); if (fw_img == NULL) return FALSE; fu_firmware_set_bytes(img, fw_img); if (!fu_firmware_add_image_full(FU_FIRMWARE(self), img, error)) return FALSE; } offset_hdr += st_img->len; offset_payload += img_size; } version = (fu_struct_goodix_gtx8_hdr_get_vid(st) << 8) | cfg_ver; fu_goodixtp_firmware_set_version(self, version); return TRUE; } static void fu_goodixtp_gtx8_firmware_init(FuGoodixtpGtx8Firmware *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); } static void fu_goodixtp_gtx8_firmware_class_init(FuGoodixtpGtx8FirmwareClass *klass) { } FuFirmware * fu_goodixtp_gtx8_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_GOODIXTP_GTX8_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-gtx8-firmware.h000066400000000000000000000010551460375044200234560ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-goodixtp-firmware.h" #define FU_TYPE_GOODIXTP_GTX8_FIRMWARE (fu_goodixtp_gtx8_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuGoodixtpGtx8Firmware, fu_goodixtp_gtx8_firmware, FU, GOODIXTP_GTX8_FIRMWARE, FuGoodixtpFirmware) gboolean fu_goodixtp_gtx8_firmware_parse(FuGoodixtpFirmware *self, GBytes *fw, guint8 sensor_id, GError **error); FuFirmware * fu_goodixtp_gtx8_firmware_new(void); fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-hid-device.c000066400000000000000000000136151460375044200227530ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_HIDRAW_H #include #include #endif #include "fu-goodixtp-common.h" #include "fu-goodixtp-firmware.h" #include "fu-goodixtp-hid-device.h" typedef struct { gchar *patch_pid; gchar *patch_vid; guint8 sensor_id; guint8 cfg_ver; } FuGoodixtpHidDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuGoodixtpHidDevice, fu_goodixtp_hid_device, FU_TYPE_UDEV_DEVICE) #define GET_PRIVATE(o) (fu_goodixtp_hid_device_get_instance_private(o)) #define GOODIX_DEVICE_IOCTL_TIMEOUT 5000 void fu_goodixtp_hid_device_set_patch_pid(FuGoodixtpHidDevice *self, const gchar *patch_pid) { FuGoodixtpHidDevicePrivate *priv = GET_PRIVATE(self); priv->patch_pid = g_strdup_printf("GT%s", patch_pid); } void fu_goodixtp_hid_device_set_patch_vid(FuGoodixtpHidDevice *self, const gchar *patch_vid) { FuGoodixtpHidDevicePrivate *priv = GET_PRIVATE(self); priv->patch_vid = g_strdup(patch_vid); } void fu_goodixtp_hid_device_set_sensor_id(FuGoodixtpHidDevice *self, guint8 sensor_id) { FuGoodixtpHidDevicePrivate *priv = GET_PRIVATE(self); priv->sensor_id = sensor_id; } void fu_goodixtp_hid_device_set_config_ver(FuGoodixtpHidDevice *self, guint8 ver) { FuGoodixtpHidDevicePrivate *priv = GET_PRIVATE(self); priv->cfg_ver = ver; } guint8 fu_goodixtp_hid_device_get_sensor_id(FuGoodixtpHidDevice *self) { FuGoodixtpHidDevicePrivate *priv = GET_PRIVATE(self); return priv->sensor_id; } static void fu_goodixtp_hid_device_to_string(FuDevice *device, guint idt, GString *str) { FuGoodixtpHidDevice *self = FU_GOODIXTP_HID_DEVICE(device); FuGoodixtpHidDevicePrivate *priv = GET_PRIVATE(self); fu_string_append(str, idt, "patch_pid", priv->patch_pid); fu_string_append(str, idt, "patch_vid", priv->patch_vid); fu_string_append_kx(str, idt, "sensor_id", priv->sensor_id); fu_string_append_kx(str, idt, "cfg_ver", priv->cfg_ver); } gboolean fu_goodixtp_hid_device_get_report(FuGoodixtpHidDevice *self, guint8 *buf, guint32 bufsz, GError **error) { #ifdef HAVE_HIDRAW_H guint8 rcv_buf[PACKAGE_LEN + 1] = {0}; rcv_buf[0] = REPORT_ID; if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGFEATURE(PACKAGE_LEN), rcv_buf, NULL, GOODIX_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error(error, "failed get report: "); return FALSE; } if (rcv_buf[0] != REPORT_ID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "rcv_buf[0]:%02x != 0x0E", rcv_buf[0]); return FALSE; } if (!fu_memcpy_safe(buf, bufsz, 0, rcv_buf, sizeof(rcv_buf), 0, PACKAGE_LEN, error)) return FALSE; return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } gboolean fu_goodixtp_hid_device_set_report(FuGoodixtpHidDevice *self, guint8 *buf, guint32 len, GError **error) { #ifdef HAVE_HIDRAW_H if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(len), buf, NULL, GOODIX_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error(error, "failed set report: "); return FALSE; } return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_goodixtp_hid_device_probe(FuDevice *device, GError **error) { /* check is valid */ if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "hidraw") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct subsystem=%s, expected hidraw", fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device))); return FALSE; } /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); } static void fu_goodixtp_hid_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_goodixtp_hid_device_init(FuGoodixtpHidDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_summary(FU_DEVICE(self), "Touchpad"); fu_device_add_icon(FU_DEVICE(self), "input-touchpad"); fu_device_add_protocol(FU_DEVICE(self), "com.goodix.goodixtp"); fu_device_set_name(FU_DEVICE(self), "Touch Controller Sensor"); fu_device_set_vendor(FU_DEVICE(self), "Goodix inc."); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_HEX); fu_device_set_priority(FU_DEVICE(self), 1); /* better than i2c */ fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | FU_UDEV_DEVICE_FLAG_OPEN_NONBLOCK); } static void fu_goodixtp_hid_device_finalize(GObject *object) { FuGoodixtpHidDevice *self = FU_GOODIXTP_HID_DEVICE(object); FuGoodixtpHidDevicePrivate *priv = GET_PRIVATE(self); g_free(priv->patch_pid); g_free(priv->patch_vid); G_OBJECT_CLASS(fu_goodixtp_hid_device_parent_class)->finalize(object); } static void fu_goodixtp_hid_device_class_init(FuGoodixtpHidDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_goodixtp_hid_device_finalize; klass_device->to_string = fu_goodixtp_hid_device_to_string; klass_device->probe = fu_goodixtp_hid_device_probe; klass_device->set_progress = fu_goodixtp_hid_device_set_progress; } fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-hid-device.h000066400000000000000000000021661460375044200227570ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-goodixtp-common.h" #define FU_TYPE_GOODIXTP_HID_DEVICE (fu_goodixtp_hid_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuGoodixtpHidDevice, fu_goodixtp_hid_device, FU, GOODIXTP_HID_DEVICE, FuUdevDevice) struct _FuGoodixtpHidDeviceClass { FuUdevDeviceClass parent_class; }; gboolean fu_goodixtp_hid_device_get_report(FuGoodixtpHidDevice *self, guint8 *buf, guint32 bufsz, GError **error); gboolean fu_goodixtp_hid_device_set_report(FuGoodixtpHidDevice *self, guint8 *buf, guint32 len, GError **error); void fu_goodixtp_hid_device_set_patch_pid(FuGoodixtpHidDevice *self, const gchar *patch_pid); void fu_goodixtp_hid_device_set_patch_vid(FuGoodixtpHidDevice *self, const gchar *patch_vid); void fu_goodixtp_hid_device_set_sensor_id(FuGoodixtpHidDevice *self, guint8 sensor_id); void fu_goodixtp_hid_device_set_config_ver(FuGoodixtpHidDevice *self, guint8 ver); guint8 fu_goodixtp_hid_device_get_sensor_id(FuGoodixtpHidDevice *self); fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-plugin.c000066400000000000000000000057751460375044200222600ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-goodixtp-brlb-device.h" #include "fu-goodixtp-common.h" #include "fu-goodixtp-firmware.h" #include "fu-goodixtp-gtx8-device.h" #include "fu-goodixtp-plugin.h" #include "fu-goodixtp-struct.h" struct _FuGoodixtpPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuGoodixtpPlugin, fu_goodixtp_plugin, FU_TYPE_PLUGIN) static void fu_goodixtp_plugin_init(FuGoodixtpPlugin *self) { } static void fu_goodixtp_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "hidraw"); fu_plugin_add_device_gtype(plugin, FU_TYPE_GOODIXTP_HID_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_GOODIXTP_FIRMWARE); } static FuGoodixtpIcType fu_goodixtp_plugin_ic_type_from_pid(guint16 pid) { if ((pid >= 0x01E0 && pid <= 0x01E7) || (pid >= 0x0D00 && pid <= 0x0D7F)) return FU_GOODIXTP_IC_TYPE_NORMANDYL; if ((pid >= 0x0EB0 && pid <= 0x0EBF) || (pid >= 0x0EC0 && pid <= 0x0ECF) || (pid >= 0x0EA5 && pid <= 0x0EAA) || (pid >= 0x0C00 && pid <= 0x0CFF)) return FU_GOODIXTP_IC_TYPE_BERLINB; return FU_GOODIXTP_IC_TYPE_NONE; } static gboolean fu_goodixtp_plugin_backend_device_added(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { guint16 hid_pid; FuGoodixtpIcType ic_type; g_autoptr(FuDeviceLocker) locker = NULL; if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "hidraw") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct subsystem=%s, expected hidraw", fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device))); return FALSE; } hid_pid = fu_udev_device_get_model(FU_UDEV_DEVICE(device)); ic_type = fu_goodixtp_plugin_ic_type_from_pid(hid_pid); if (ic_type == FU_GOODIXTP_IC_TYPE_NORMANDYL) { g_autoptr(FuDevice) dev = g_object_new(FU_TYPE_GOODIXTP_GTX8_DEVICE, "context", fu_plugin_get_context(plugin), NULL); fu_device_incorporate(dev, device); locker = fu_device_locker_new(dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add(plugin, dev); return TRUE; } if (ic_type == FU_GOODIXTP_IC_TYPE_BERLINB) { g_autoptr(FuDevice) dev = g_object_new(FU_TYPE_GOODIXTP_BRLB_DEVICE, "context", fu_plugin_get_context(plugin), NULL); fu_device_incorporate(dev, device); locker = fu_device_locker_new(dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add(plugin, dev); return TRUE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "can't find valid ic_type, pid is %x", hid_pid); return FALSE; } static void fu_goodixtp_plugin_class_init(FuGoodixtpPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_goodixtp_plugin_constructed; plugin_class->backend_device_added = fu_goodixtp_plugin_backend_device_added; } fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp-plugin.h000066400000000000000000000003521460375044200222470ustar00rootroot00000000000000/* * Copyright (C) 2023 Goodix.inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuGoodixtpPlugin, fu_goodixtp_plugin, FU, GOODIXTP_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/goodix-tp/fu-goodixtp.rs000066400000000000000000000014541460375044200211540ustar00rootroot00000000000000// Copyright (C) 2023 Goodix.inc // SPDX-License-Identifier: LGPL-2.1+ enum GoodixtpIcType { None, Phoenix, TypeNanjing, Mousepad, Normandyl, Berlinb, Yellowstone, } #[derive(ParseBytes)] struct GoodixBrlbHdr { firmware_size: u32le, checksum: u32le, _unknown: [u8; 19], vid: u16be, subsys_num: u8, _unknown: [u8; 12], } #[derive(ParseBytes)] struct GoodixBrlbImg { kind: u8, size: u32le, addr: u32le, _unknown: [u8; 1], } #[derive(ParseBytes)] struct GoodixGtx8Hdr { firmware_size: u32be, checksum: u16be, _unknown: [u8; 19], vid: u16be, subsys_num: u8, _unknown: [u8; 4], } #[derive(ParseBytes)] struct GoodixGtx8Img { kind: u8, size: u32be, addr: u16be, _unknown: [u8; 1], } fwupd-1.9.16/plugins/goodix-tp/goodixtp.quirk000066400000000000000000000000751460375044200212510ustar00rootroot00000000000000[HIDRAW\VEN_27C6] Plugin = goodixtp Flags = enforce-requires fwupd-1.9.16/plugins/goodix-tp/meson.build000066400000000000000000000011551460375044200205010ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginGoodixtp"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('goodixtp.quirk') plugin_builtins += static_library('fu_plugin_goodixtp', rustgen.process('fu-goodixtp.rs'), sources: [ 'fu-goodixtp-brlb-device.c', 'fu-goodixtp-brlb-firmware.c', 'fu-goodixtp-firmware.c', 'fu-goodixtp-gtx8-device.c', 'fu-goodixtp-gtx8-firmware.c', 'fu-goodixtp-hid-device.c', 'fu-goodixtp-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/gpio/000077500000000000000000000000001460375044200153615ustar00rootroot00000000000000fwupd-1.9.16/plugins/gpio/README.md000066400000000000000000000016461460375044200166470ustar00rootroot00000000000000--- title: Plugin: GPIO --- ## Introduction This plugin sets GPIO outputs either high or low before and/or after an update has been deployed. ## GUID Generation These device use GPIO `gpiochip_info.label` values, e.g. * `GPIO\ID_INT3450:00` ## Quirk Use This plugin uses the following plugin-specific quirks: ### GpioForUpdate The GPIO bit to set before the update is deployed e.g. `INT3450:00,SPI_MUX,high`. After the update has finished, the bits are returned to the default state. For example, to set GPIO pin 2 low for the duration of the ColorHug device update this could be added to the quirk file: [USB\VID_273F&PID_1001] GpioForUpdate=fake-gpio-chip,2,low Since: 1.7.6 ## External Interface Access This plugin requires ioctl `GPIO_GET_CHIPINFO_IOCTL` and `GPIO_V2_GET_LINE_IOCTL` access on `/dev/gpiochip*` devices. ## Version Considerations This plugin has been available since fwupd version `1.7.6`. fwupd-1.9.16/plugins/gpio/fu-gpio-device.c000066400000000000000000000132571460375044200203400ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-gpio-device.h" struct _FuGpioDevice { FuUdevDevice parent_instance; guint num_lines; gint fd; /* valid when the GPIO bit is assigned */ }; G_DEFINE_TYPE(FuGpioDevice, fu_gpio_device, FU_TYPE_UDEV_DEVICE) #define FU_GPIO_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ static void fu_gpio_device_to_string(FuDevice *device, guint idt, GString *str) { FuGpioDevice *self = FU_GPIO_DEVICE(device); FU_DEVICE_CLASS(fu_gpio_device_parent_class)->to_string(device, idt, str); fu_string_append_ku(str, idt, "NumLines", self->num_lines); fu_string_append_kb(str, idt, "FdOpen", self->fd > 0); } static gboolean fu_gpio_device_probe(FuDevice *device, GError **error) { /* no device file */ if (fu_udev_device_get_device_file(FU_UDEV_DEVICE(device)) == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no device file"); return FALSE; } /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "gpio", error); } static gboolean fu_gpio_device_setup(FuDevice *device, GError **error) { FuGpioDevice *self = FU_GPIO_DEVICE(device); struct gpiochip_info info = {0x0}; /* get info */ if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), GPIO_GET_CHIPINFO_IOCTL, (guint8 *)&info, NULL, FU_GPIO_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error(error, "failed to get chipinfo: "); return FALSE; } /* sanity check */ self->num_lines = info.lines; if (self->num_lines == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "0 lines is not supported"); return FALSE; } /* label is optional, but name is always set */ if (info.label[0] != '\0') { g_autofree gchar *logical_id = fu_strsafe(info.label, sizeof(info.label)); fu_device_set_logical_id(device, logical_id); /* add instance ID */ fu_device_add_instance_strsafe(device, "ID", logical_id); if (!fu_device_build_instance_id(device, error, "GPIO", "ID", NULL)) return FALSE; } /* success */ return TRUE; } gboolean fu_gpio_device_unassign(FuGpioDevice *self, GError **error) { if (self->fd < 0) return TRUE; g_info("unsetting %s", fu_device_get_logical_id(FU_DEVICE(self))); if (!g_close(self->fd, error)) return FALSE; self->fd = -1; return TRUE; } static gboolean fu_gpio_device_assign_full(FuGpioDevice *self, guint64 line, gboolean value, GError **error) { const gchar consumer[] = "fwupd"; struct gpio_v2_line_request req = { .num_lines = 1, req.offsets[0] = line, .config.flags = GPIO_V2_LINE_FLAG_OUTPUT, .config.num_attrs = 1, .config.attrs[0].attr.values = value ? 0x1 : 0x0, .config.attrs[0].mask = 0x1, }; /* this is useful if we have contention with other tools */ if (!fu_memcpy_safe((guint8 *)req.consumer, sizeof(req.consumer), 0x0, /* dst */ (const guint8 *)consumer, sizeof(consumer), 0x0, /* src */ sizeof(consumer), error)) return FALSE; /* slightly weird API, but roll with it */ g_info("setting %s:0x%02x → %i", fu_device_get_logical_id(FU_DEVICE(self)), (guint)line, value); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), GPIO_V2_GET_LINE_IOCTL, (guint8 *)&req, NULL, FU_GPIO_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error(error, "failed to assign: "); return FALSE; } /* success */ self->fd = req.fd; return TRUE; } gboolean fu_gpio_device_assign(FuGpioDevice *self, const gchar *id, gboolean value, GError **error) { guint64 line = G_MAXUINT64; /* sanity check */ if (self->fd > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "GPIO %s already in use", id); return FALSE; } /* specified as a number, or look for @id as named pin */ if (fu_strtoull(id, &line, 0, self->num_lines - 1, NULL)) { struct gpio_v2_line_info info = {.offset = line}; if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), GPIO_V2_GET_LINEINFO_IOCTL, (guint8 *)&info, NULL, FU_GPIO_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error(error, "failed to get lineinfo: "); return FALSE; } } else { for (guint i = 0; i < self->num_lines; i++) { struct gpio_v2_line_info info = {.offset = i}; g_autofree gchar *name = NULL; if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), GPIO_V2_GET_LINEINFO_IOCTL, (guint8 *)&info, NULL, FU_GPIO_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error(error, "failed to get lineinfo: "); return FALSE; } name = fu_strsafe(info.name, sizeof(info.name)); if (g_strcmp0(name, id) == 0) { line = i; break; } } } if (line == G_MAXUINT64) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to find %s", id); return FALSE; } return fu_gpio_device_assign_full(self, line, value, error); } static void fu_gpio_device_init(FuGpioDevice *self) { fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ); } static void fu_gpio_device_finalize(GObject *object) { FuGpioDevice *self = FU_GPIO_DEVICE(object); if (self->fd > 0) g_close(self->fd, NULL); G_OBJECT_CLASS(fu_gpio_device_parent_class)->finalize(object); } static void fu_gpio_device_class_init(FuGpioDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_gpio_device_finalize; klass_device->to_string = fu_gpio_device_to_string; klass_device->setup = fu_gpio_device_setup; klass_device->probe = fu_gpio_device_probe; } fwupd-1.9.16/plugins/gpio/fu-gpio-device.h000066400000000000000000000007121460375044200203350ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_GPIO_DEVICE (fu_gpio_device_get_type()) G_DECLARE_FINAL_TYPE(FuGpioDevice, fu_gpio_device, FU, GPIO_DEVICE, FuUdevDevice) gboolean fu_gpio_device_assign(FuGpioDevice *self, const gchar *id, gboolean value, GError **error); gboolean fu_gpio_device_unassign(FuGpioDevice *self, GError **error); fwupd-1.9.16/plugins/gpio/fu-gpio-plugin.c000066400000000000000000000123211460375044200203660ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-gpio-device.h" #include "fu-gpio-plugin.h" struct _FuGpioPlugin { FuPlugin parent_instance; GPtrArray *current_logical_ids; /* element-type: utf-8 */ }; G_DEFINE_TYPE(FuGpioPlugin, fu_gpio_plugin, FU_TYPE_PLUGIN) static void fu_gpio_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuGpioPlugin *self = FU_GPIO_PLUGIN(plugin); for (guint i = 0; i < self->current_logical_ids->len; i++) { const gchar *current_logical_id = g_ptr_array_index(self->current_logical_ids, i); g_autofree gchar *title = g_strdup_printf("CurrentLogicalId[0x%02x]", i); fu_string_append(str, idt, title, current_logical_id); } } static gboolean fu_gpio_plugin_parse_level(const gchar *str, gboolean *ret, GError **error) { if (g_strcmp0(str, "high") == 0) { *ret = TRUE; return TRUE; } if (g_strcmp0(str, "low") == 0) { *ret = FALSE; return TRUE; } g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot parse level, got %s and expected high|low", str); return FALSE; } static gboolean fu_gpio_plugin_process_quirk(FuPlugin *plugin, const gchar *str, GError **error) { FuGpioPlugin *self = FU_GPIO_PLUGIN(plugin); FuDevice *device_tmp; gboolean value = FALSE; g_auto(GStrv) split = g_strsplit(str, ",", -1); g_autoptr(FuDeviceLocker) locker = NULL; /* sanity check */ if (g_strv_length(split) != 3) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid format, CHIP_NAME,PIN_NAME,LEVEL, got '%s'", str); return FALSE; } if (!fu_gpio_plugin_parse_level(split[2], &value, error)) return FALSE; device_tmp = fu_plugin_cache_lookup(plugin, split[0]); if (device_tmp == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "GPIO device %s not found", split[0]); return FALSE; } locker = fu_device_locker_new(device_tmp, error); if (locker == NULL) return FALSE; if (!fu_gpio_device_assign(FU_GPIO_DEVICE(device_tmp), split[1], value, error)) { g_prefix_error(error, "failed to assign %s: ", split[0]); return FALSE; } /* success */ g_ptr_array_add(self->current_logical_ids, g_strdup(fu_device_get_logical_id(device_tmp))); return TRUE; } static gboolean fu_gpio_plugin_prepare(FuPlugin *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { GPtrArray *guids = fu_device_get_guids(device); for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index(guids, i); const gchar *str; str = fu_context_lookup_quirk_by_id(fu_plugin_get_context(self), guid, "GpioForUpdate"); if (str == NULL) continue; if (!fu_gpio_plugin_process_quirk(self, str, error)) return FALSE; } return TRUE; } static gboolean fu_gpio_plugin_cleanup(FuPlugin *plugin, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuGpioPlugin *self = FU_GPIO_PLUGIN(plugin); g_autoptr(GPtrArray) current_logical_ids = NULL; /* deep copy to local to clear transaction array */ current_logical_ids = g_ptr_array_copy(self->current_logical_ids, (GCopyFunc)g_strdup, NULL); g_ptr_array_set_size(self->current_logical_ids, 0); /* close the fds we opened during ->prepare */ for (guint i = 0; i < current_logical_ids->len; i++) { FuDevice *device_tmp; const gchar *current_logical_id = g_ptr_array_index(current_logical_ids, i); device_tmp = fu_plugin_cache_lookup(plugin, current_logical_id); if (device_tmp == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "GPIO device %s no longer found", current_logical_id); return FALSE; } if (!fu_gpio_device_unassign(FU_GPIO_DEVICE(device_tmp), error)) { g_prefix_error(error, "failed to unassign %s: ", current_logical_id); return FALSE; } } /* success */ return TRUE; } static void fu_gpio_plugin_device_added(FuPlugin *self, FuDevice *device) { fu_plugin_cache_add(self, fu_device_get_logical_id(device), device); } static void fu_gpio_plugin_init(FuGpioPlugin *self) { self->current_logical_ids = g_ptr_array_new_with_free_func(g_free); } static void fu_gpio_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "GpioForUpdate"); fu_plugin_add_device_udev_subsystem(plugin, "gpio"); fu_plugin_add_device_gtype(plugin, FU_TYPE_GPIO_DEVICE); } static void fu_gpio_finalize(GObject *obj) { FuGpioPlugin *self = FU_GPIO_PLUGIN(obj); g_ptr_array_unref(self->current_logical_ids); G_OBJECT_CLASS(fu_gpio_plugin_parent_class)->finalize(obj); } static void fu_gpio_plugin_class_init(FuGpioPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_gpio_finalize; plugin_class->constructed = fu_gpio_plugin_constructed; plugin_class->to_string = fu_gpio_plugin_to_string; plugin_class->prepare = fu_gpio_plugin_prepare; plugin_class->cleanup = fu_gpio_plugin_cleanup; plugin_class->device_added = fu_gpio_plugin_device_added; } fwupd-1.9.16/plugins/gpio/fu-gpio-plugin.h000066400000000000000000000003421460375044200203730ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuGpioPlugin, fu_gpio_plugin, FU, GPIO_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/gpio/gpio.quirk000066400000000000000000000001751460375044200173770ustar00rootroot00000000000000[GPIO\ID_AMDI0030:00] Name = GPIO controller ParentGuid = cpu [GPIO\ID_AMDI0031:00] Name = GPIO controller ParentGuid = cpu fwupd-1.9.16/plugins/gpio/meson.build000066400000000000000000000011121460375044200175160ustar00rootroot00000000000000gpio_header = cc.has_header_symbol('linux/gpio.h', 'GPIO_V2_LINE_FLAG_OUTPUT', required: get_option('plugin_gpio')) if gpio_header and gudev.found() cargs = ['-DG_LOG_DOMAIN="FuPluginGpio"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('gpio.quirk') plugin_builtins += static_library('fu_plugin_gpio', sources: [ 'fu-gpio-plugin.c', 'fu-gpio-device.c', ], include_directories: plugin_incdirs, c_args: [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], link_with: plugin_libs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/hailuck/000077500000000000000000000000001460375044200160435ustar00rootroot00000000000000fwupd-1.9.16/plugins/hailuck/README.md000066400000000000000000000023651460375044200173300ustar00rootroot00000000000000--- title: Plugin: Hailuck --- ## Introduction Hailuck produce the firmware used on the keyboard and trackpad used in the Pinebook Pro laptops. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `com.hailuck.kbd` * `com.hailuck.tp` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_0603&PID_1020` ## Update Behavior The keyboard device usually presents in runtime mode, but on detach it re-enumerates with a different USB VID and PID in bootloader mode. On attach the device again re-enumerates back to the runtime mode. For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. The touchpad firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x0603` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.5.2`. fwupd-1.9.16/plugins/hailuck/data/000077500000000000000000000000001460375044200167545ustar00rootroot00000000000000fwupd-1.9.16/plugins/hailuck/data/lspci-bl.txt000066400000000000000000000030331460375044200212210ustar00rootroot00000000000000Bus 003 Device 003: ID 0603:1020 Novatek Microelectronics Corp. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 1.10 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 8 idVendor 0x0603 Novatek Microelectronics Corp. idProduct 0x1020 bcdDevice 3.01 iManufacturer 0 iProduct 0 iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0022 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 1 Keyboard iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 10 fwupd-1.9.16/plugins/hailuck/data/lspci.txt000066400000000000000000000057371460375044200206430ustar00rootroot00000000000000Bus 003 Device 008: ID 258a:001e HAILUCK CO.,LTD USB KEYBOARD Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 1.10 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 8 idVendor 0x258a idProduct 0x001e bcdDevice 1.00 iManufacturer 1 HAILUCK CO.,LTD iProduct 2 USB KEYBOARD iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x003b bNumInterfaces 2 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 1 Keyboard iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 65 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 10 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 487 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 10 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/hailuck/fu-hailuck-bl-device.c000066400000000000000000000213731460375044200220750ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-hailuck-bl-device.h" #include "fu-hailuck-common.h" #include "fu-hailuck-kbd-firmware.h" #include "fu-hailuck-struct.h" struct _FuHailuckBlDevice { FuHidDevice parent_instance; }; G_DEFINE_TYPE(FuHailuckBlDevice, fu_hailuck_bl_device, FU_TYPE_HID_DEVICE) static gboolean fu_hailuck_bl_device_attach(FuDevice *device, FuProgress *progress, GError **error) { guint8 buf[6] = { FU_HAILUCK_REPORT_ID_SHORT, FU_HAILUCK_CMD_ATTACH, }; if (!fu_hid_device_set_report(FU_HID_DEVICE(device), buf[0], buf, sizeof(buf), 1000, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; if (!g_usb_device_reset(fu_usb_device_get_dev(FU_USB_DEVICE(device)), error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_hailuck_bl_device_probe(FuDevice *device, GError **error) { /* add instance ID */ fu_device_add_instance_str(device, "MODE", "KBD"); return fu_device_build_instance_id(device, error, "USB", "VID", "PID", "MODE", NULL); } static gboolean fu_hailuck_bl_device_read_block_start(FuHailuckBlDevice *self, guint32 length, GError **error) { guint8 buf[6] = { FU_HAILUCK_REPORT_ID_SHORT, FU_HAILUCK_CMD_READ_BLOCK_START, }; fu_memwrite_uint16(buf + 4, length, G_LITTLE_ENDIAN); return fu_hid_device_set_report(FU_HID_DEVICE(self), buf[0], buf, sizeof(buf), 100, FU_HID_DEVICE_FLAG_IS_FEATURE, error); } static gboolean fu_hailuck_bl_device_read_block(FuHailuckBlDevice *self, guint8 *data, gsize data_sz, GError **error) { gsize bufsz = data_sz + 2; g_autofree guint8 *buf = g_malloc0(bufsz); buf[0] = FU_HAILUCK_REPORT_ID_LONG; buf[1] = FU_HAILUCK_CMD_READ_BLOCK; if (!fu_hid_device_get_report(FU_HID_DEVICE(self), buf[0], buf, bufsz, 2000, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; if (!fu_memcpy_safe(data, data_sz, 0x0, /* dst */ buf, bufsz, 0x02, /* src */ data_sz, error)) return FALSE; /* success */ fu_device_sleep(FU_DEVICE(self), 10); return TRUE; } static GBytes * fu_hailuck_bl_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuHailuckBlDevice *self = FU_HAILUCK_BL_DEVICE(device); gsize fwsz = fu_device_get_firmware_size_max(device); g_autoptr(GByteArray) fwbuf = g_byte_array_new(); g_autoptr(GPtrArray) chunks = NULL; /* tell device amount of data to send */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); if (!fu_hailuck_bl_device_read_block_start(self, fwsz, error)) return NULL; /* receive data back */ fu_byte_array_set_size(fwbuf, fwsz, 0x00); chunks = fu_chunk_array_mutable_new(fwbuf->data, fwbuf->len, 0x0, 0x0, 2048); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_hailuck_bl_device_read_block(self, fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), error)) return NULL; fu_progress_step_done(progress); } /* success */ return g_bytes_new(fwbuf->data, fwbuf->len); } static gboolean fu_hailuck_bl_device_erase(FuHailuckBlDevice *self, FuProgress *progress, GError **error) { guint8 buf[6] = { FU_HAILUCK_REPORT_ID_SHORT, FU_HAILUCK_CMD_ERASE, }; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), buf[0], buf, sizeof(buf), 100, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; fu_device_sleep_full(FU_DEVICE(self), 2000, progress); return TRUE; } static gboolean fu_hailuck_bl_device_write_block_start(FuHailuckBlDevice *self, guint32 length, GError **error) { guint8 buf[6] = { FU_HAILUCK_REPORT_ID_SHORT, FU_HAILUCK_CMD_WRITE_BLOCK_START, }; fu_memwrite_uint16(buf + 4, length, G_LITTLE_ENDIAN); return fu_hid_device_set_report(FU_HID_DEVICE(self), buf[0], buf, sizeof(buf), 100, FU_HID_DEVICE_FLAG_IS_FEATURE, error); } static gboolean fu_hailuck_bl_device_write_block(FuHailuckBlDevice *self, const guint8 *data, gsize data_sz, GError **error) { gsize bufsz = data_sz + 2; g_autofree guint8 *buf = g_malloc0(bufsz); buf[0] = FU_HAILUCK_REPORT_ID_LONG; buf[1] = FU_HAILUCK_CMD_WRITE_BLOCK; if (!fu_memcpy_safe(buf, bufsz, 0x02, /* dst */ data, data_sz, 0x0, /* src */ data_sz, error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), buf[0], buf, bufsz, 2000, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; /* success */ fu_device_sleep(FU_DEVICE(self), 10); return TRUE; } static gboolean fu_hailuck_bl_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuHailuckBlDevice *self = FU_HAILUCK_BL_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) fw_new = NULL; g_autoptr(FuChunk) chk0 = NULL; g_autoptr(FuChunkArray) chunks = NULL; g_autofree guint8 *chk0_data = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "device-write-blk0"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 9, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* erase all contents */ if (!fu_hailuck_bl_device_erase(self, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* tell device amount of data to expect */ if (!fu_hailuck_bl_device_write_block_start(self, g_bytes_get_size(fw), error)) return FALSE; /* build packets */ chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 2048); /* intentionally corrupt first chunk so that CRC fails */ chk0 = fu_chunk_array_index(chunks, 0); chk0_data = fu_memdup_safe(fu_chunk_get_data(chk0), fu_chunk_get_data_sz(chk0), error); if (chk0_data == NULL) return FALSE; chk0_data[0] = 0x00; if (!fu_hailuck_bl_device_write_block(self, chk0_data, fu_chunk_get_data_sz(chk0), error)) return FALSE; /* send the rest of the chunks */ for (guint i = 1; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_hailuck_bl_device_write_block(self, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; fu_progress_set_percentage_full(fu_progress_get_child(progress), i + 1, fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* retry write of first block */ if (!fu_hailuck_bl_device_write_block_start(self, g_bytes_get_size(fw), error)) return FALSE; if (!fu_hailuck_bl_device_write_block(self, fu_chunk_get_data(chk0), fu_chunk_get_data_sz(chk0), error)) return FALSE; fu_progress_step_done(progress); /* verify */ fw_new = fu_hailuck_bl_device_dump_firmware(device, fu_progress_get_child(progress), error); fu_progress_step_done(progress); return fu_bytes_compare(fw, fw_new, error); } static void fu_hailuck_bl_device_init(FuHailuckBlDevice *self) { fu_device_set_firmware_size(FU_DEVICE(self), 0x4000); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_HAILUCK_KBD_FIRMWARE); fu_device_add_protocol(FU_DEVICE(self), "com.hailuck.kbd"); fu_device_set_name(FU_DEVICE(self), "Keyboard [bootloader]"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_icon(FU_DEVICE(self), "input-keyboard"); fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_NO_KERNEL_REBIND); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } static void fu_hailuck_bl_device_class_init(FuHailuckBlDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->dump_firmware = fu_hailuck_bl_device_dump_firmware; klass_device->write_firmware = fu_hailuck_bl_device_write_firmware; klass_device->attach = fu_hailuck_bl_device_attach; klass_device->probe = fu_hailuck_bl_device_probe; } fwupd-1.9.16/plugins/hailuck/fu-hailuck-bl-device.h000066400000000000000000000004721460375044200220770ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_HAILUCK_BL_DEVICE (fu_hailuck_bl_device_get_type()) G_DECLARE_FINAL_TYPE(FuHailuckBlDevice, fu_hailuck_bl_device, FU, HAILUCK_BL_DEVICE, FuHidDevice) fwupd-1.9.16/plugins/hailuck/fu-hailuck-common.h000066400000000000000000000003441460375044200215330ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_HAILUCK_REPORT_ID_SHORT 0x05 #define FU_HAILUCK_REPORT_ID_LONG 0x06 fwupd-1.9.16/plugins/hailuck/fu-hailuck-kbd-device.c000066400000000000000000000060221460375044200222320ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-hailuck-common.h" #include "fu-hailuck-kbd-device.h" #include "fu-hailuck-struct.h" #include "fu-hailuck-tp-device.h" struct _FuHailuckKbdDevice { FuHidDevice parent_instance; }; G_DEFINE_TYPE(FuHailuckKbdDevice, fu_hailuck_kbd_device, FU_TYPE_HID_DEVICE) static gboolean fu_hailuck_kbd_device_detach(FuDevice *device, FuProgress *progress, GError **error) { guint8 buf[6] = {FU_HAILUCK_REPORT_ID_SHORT, FU_HAILUCK_CMD_DETACH}; if (!fu_hid_device_set_report(FU_HID_DEVICE(device), buf[0], buf, sizeof(buf), 1000, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_hailuck_kbd_device_probe(FuDevice *device, GError **error) { g_autoptr(FuHailuckTpDevice) tp_device = fu_hailuck_tp_device_new(FU_DEVICE(device)); /* add extra keyboard-specific GUID */ fu_device_add_instance_str(device, "MODE", "KBD"); if (!fu_device_build_instance_id(device, error, "USB", "VID", "PID", "MODE", NULL)) return FALSE; /* add touchpad */ if (!fu_device_probe(FU_DEVICE(tp_device), error)) return FALSE; /* assume the TP has the same version as the keyboard */ fu_device_set_version(FU_DEVICE(tp_device), fu_device_get_version(device)); fu_device_set_version_format(FU_DEVICE(tp_device), fu_device_get_version_format(device)); fu_device_add_child(device, FU_DEVICE(tp_device)); /* success */ return TRUE; } static void fu_hailuck_kbd_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_hailuck_kbd_device_init(FuHailuckKbdDevice *self) { fu_device_set_firmware_size(FU_DEVICE(self), 0x4000); fu_device_add_protocol(FU_DEVICE(self), "com.hailuck.kbd"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_icon(FU_DEVICE(self), "input-keyboard"); fu_hid_device_set_interface(FU_HID_DEVICE(self), 0x1); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } static void fu_hailuck_kbd_device_class_init(FuHailuckKbdDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->detach = fu_hailuck_kbd_device_detach; klass_device->probe = fu_hailuck_kbd_device_probe; klass_device->set_progress = fu_hailuck_kbd_device_set_progress; } fwupd-1.9.16/plugins/hailuck/fu-hailuck-kbd-device.h000066400000000000000000000004771460375044200222470ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_HAILUCK_KBD_DEVICE (fu_hailuck_kbd_device_get_type()) G_DECLARE_FINAL_TYPE(FuHailuckKbdDevice, fu_hailuck_kbd_device, FU, HAILUCK_KBD_DEVICE, FuHidDevice) fwupd-1.9.16/plugins/hailuck/fu-hailuck-kbd-firmware.c000066400000000000000000000047771460375044200226260ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-hailuck-kbd-firmware.h" struct _FuHailuckKbdFirmware { FuIhexFirmwareClass parent_instance; }; G_DEFINE_TYPE(FuHailuckKbdFirmware, fu_hailuck_kbd_firmware, FU_TYPE_IHEX_FIRMWARE) static gboolean fu_hailuck_kbd_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { GPtrArray *records = fu_ihex_firmware_get_records(FU_IHEX_FIRMWARE(firmware)); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) fw_new = NULL; for (guint j = 0; j < records->len; j++) { FuIhexFirmwareRecord *rcd = g_ptr_array_index(records, j); if (rcd->record_type == FU_IHEX_FIRMWARE_RECORD_TYPE_EOF) break; if (rcd->record_type != FU_IHEX_FIRMWARE_RECORD_TYPE_DATA) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "only record 0x0 supported, got 0x%02x", rcd->record_type); return FALSE; } if (rcd->data->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "record 0x%x had zero size", j); return FALSE; } if (rcd->addr + rcd->data->len > buf->len) { if (rcd->addr + rcd->data->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "buffer would have zero size"); return FALSE; } fu_byte_array_set_size(buf, rcd->addr + rcd->data->len, 0x00); } if (!fu_memcpy_safe(buf->data, buf->len, rcd->addr, rcd->data->data, rcd->data->len, 0x0, rcd->data->len, error)) return FALSE; } /* set the main function executed on system init */ if (buf->len > 0x37FD && buf->data[1] == 0x38 && buf->data[2] == 0x00) { buf->data[0] = buf->data[0x37FB]; buf->data[1] = buf->data[0x37FC]; buf->data[2] = buf->data[0x37FD]; buf->data[0x37FB] = 0x00; buf->data[0x37FC] = 0x00; buf->data[0x37FD] = 0x00; } /* whole image */ fw_new = g_bytes_new(buf->data, buf->len); fu_firmware_set_bytes(firmware, fw_new); return TRUE; } static void fu_hailuck_kbd_firmware_init(FuHailuckKbdFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); } static void fu_hailuck_kbd_firmware_class_init(FuHailuckKbdFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_hailuck_kbd_firmware_parse; } fwupd-1.9.16/plugins/hailuck/fu-hailuck-kbd-firmware.h000066400000000000000000000005501460375044200226140ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_HAILUCK_KBD_FIRMWARE (fu_hailuck_kbd_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuHailuckKbdFirmware, fu_hailuck_kbd_firmware, FU, HAILUCK_KBD_FIRMWARE, FuIhexFirmware) fwupd-1.9.16/plugins/hailuck/fu-hailuck-plugin.c000066400000000000000000000016601460375044200215360ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-hailuck-bl-device.h" #include "fu-hailuck-kbd-device.h" #include "fu-hailuck-kbd-firmware.h" #include "fu-hailuck-plugin.h" struct _FuHailuckPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuHailuckPlugin, fu_hailuck_plugin, FU_TYPE_PLUGIN) static void fu_hailuck_plugin_init(FuHailuckPlugin *self) { } static void fu_hailuck_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_HAILUCK_KBD_FIRMWARE); fu_plugin_add_device_gtype(plugin, FU_TYPE_HAILUCK_BL_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_HAILUCK_KBD_DEVICE); } static void fu_hailuck_plugin_class_init(FuHailuckPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_hailuck_plugin_constructed; } fwupd-1.9.16/plugins/hailuck/fu-hailuck-plugin.h000066400000000000000000000003531460375044200215410ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuHailuckPlugin, fu_hailuck_plugin, FU, HAILUCK_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/hailuck/fu-hailuck-tp-device.c000066400000000000000000000171501460375044200221210ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-hailuck-common.h" #include "fu-hailuck-struct.h" #include "fu-hailuck-tp-device.h" struct _FuHailuckTpDevice { FuDevice parent_instance; }; G_DEFINE_TYPE(FuHailuckTpDevice, fu_hailuck_tp_device, FU_TYPE_DEVICE) static gboolean fu_hailuck_tp_device_probe(FuDevice *device, GError **error) { /* add extra touchpad-specific GUID */ fu_device_add_instance_str(device, "MODE", "TP"); return fu_device_build_instance_id(device, error, "USB", "VID", "PID", "MODE", NULL); } typedef struct { guint8 type; guint8 success; /* if 0xff, then cmd-0x10 */ } FuHailuckTpDeviceReq; static gboolean fu_hailuck_tp_device_cmd_cb(FuDevice *device, gpointer user_data, GError **error) { FuDevice *parent = fu_device_get_parent(device); FuHailuckTpDeviceReq *req = (FuHailuckTpDeviceReq *)user_data; guint8 buf[6] = { FU_HAILUCK_REPORT_ID_SHORT, FU_HAILUCK_CMD_GET_STATUS, req->type, }; guint8 success_tmp = req->success; if (!fu_hid_device_set_report(FU_HID_DEVICE(parent), buf[0], buf, sizeof(buf), 1000, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; if (!fu_hid_device_get_report(FU_HID_DEVICE(parent), buf[0], buf, sizeof(buf), 2000, FU_HID_DEVICE_FLAG_IS_FEATURE | FU_HID_DEVICE_FLAG_ALLOW_TRUNC, error)) return FALSE; if (success_tmp == 0xff) success_tmp = req->type - 0x10; if (buf[0] != FU_HAILUCK_REPORT_ID_SHORT || buf[1] != success_tmp) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "report mismatch for type=0x%02x[%s]: " "expected=0x%02x, received=0x%02x", req->type, fu_hailuck_cmd_to_string(req->type), success_tmp, buf[1]); return FALSE; } return TRUE; } static gboolean fu_hailuck_tp_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDevice *parent = fu_device_get_parent(device); const guint block_size = 1024; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; FuHailuckTpDeviceReq req = { .type = 0xff, .success = 0xff, }; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 85, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "end-program"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 3, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "pass"); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* erase */ req.type = FU_HAILUCK_CMD_I2C_ERASE; if (!fu_device_retry(device, fu_hailuck_tp_device_cmd_cb, 100, &req, error)) { g_prefix_error(error, "failed to erase: "); return FALSE; } fu_device_sleep(device, 10); fu_progress_step_done(progress); /* write */ chunks = fu_chunk_array_new_from_bytes(fw, 0x0, block_size); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GByteArray) buf = g_byte_array_new(); /* write block */ fu_byte_array_append_uint8(buf, FU_HAILUCK_REPORT_ID_LONG); fu_byte_array_append_uint8(buf, FU_HAILUCK_CMD_WRITE_TP); fu_byte_array_append_uint16(buf, 0xCCCC, G_LITTLE_ENDIAN); fu_byte_array_append_uint16(buf, fu_chunk_get_address(chk), G_LITTLE_ENDIAN); fu_byte_array_append_uint16(buf, 0xCCCC, G_LITTLE_ENDIAN); g_byte_array_append(buf, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); fu_byte_array_append_uint8(buf, 0xEE); fu_byte_array_append_uint8(buf, 0xD2); fu_byte_array_append_uint16(buf, 0xCCCC, G_LITTLE_ENDIAN); fu_byte_array_append_uint16(buf, 0xCCCC, G_LITTLE_ENDIAN); fu_byte_array_append_uint16(buf, 0xCCCC, G_LITTLE_ENDIAN); if (buf->len != block_size + 16) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "packet mismatch: len=0x%04x, expected=0x%04x", buf->len, block_size + 16); return FALSE; } if (!fu_hid_device_set_report(FU_HID_DEVICE(parent), buf->data[0], buf->data, buf->len, 1000, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { g_prefix_error(error, "failed to write block 0x%x: ", i); return FALSE; } fu_device_sleep(device, 150); /* verify block */ req.type = FU_HAILUCK_CMD_I2C_VERIFY_BLOCK; if (!fu_device_retry(device, fu_hailuck_tp_device_cmd_cb, 100, &req, error)) { g_prefix_error(error, "failed to verify block 0x%x: ", i); return FALSE; } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), i + 1, fu_chunk_array_length(chunks)); } fu_device_sleep(device, 50); fu_progress_step_done(progress); /* end-program */ req.type = FU_HAILUCK_CMD_I2C_END_PROGRAM; if (!fu_device_retry(device, fu_hailuck_tp_device_cmd_cb, 100, &req, error)) { g_prefix_error(error, "failed to end program: "); return FALSE; } fu_device_sleep(device, 50); fu_progress_step_done(progress); /* verify checksum */ req.type = FU_HAILUCK_CMD_I2C_VERIFY_CHECKSUM; if (!fu_device_retry(device, fu_hailuck_tp_device_cmd_cb, 100, &req, error)) { g_prefix_error(error, "failed to verify: "); return FALSE; } fu_device_sleep(device, 50); fu_progress_step_done(progress); /* signal that programming has completed */ req.type = FU_HAILUCK_CMD_I2C_PROGRAMPASS; req.success = 0x0; if (!fu_device_retry(device, fu_hailuck_tp_device_cmd_cb, 100, &req, error)) { g_prefix_error(error, "failed to program: "); return FALSE; } fu_progress_step_done(progress); /* success! */ return TRUE; } static void fu_hailuck_tp_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_hailuck_tp_device_init(FuHailuckTpDevice *self) { fu_device_retry_set_delay(FU_DEVICE(self), 50); /* ms */ fu_device_set_firmware_size(FU_DEVICE(self), 0x6018); fu_device_add_protocol(FU_DEVICE(self), "com.hailuck.tp"); fu_device_set_logical_id(FU_DEVICE(self), "TP"); fu_device_set_name(FU_DEVICE(self), "Touchpad"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); fu_device_add_icon(FU_DEVICE(self), "input-touchpad"); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } static void fu_hailuck_tp_device_class_init(FuHailuckTpDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_hailuck_tp_device_write_firmware; klass_device->probe = fu_hailuck_tp_device_probe; klass_device->set_progress = fu_hailuck_tp_device_set_progress; } FuHailuckTpDevice * fu_hailuck_tp_device_new(FuDevice *parent) { FuHailuckTpDevice *self; self = g_object_new(FU_TYPE_HAILUCK_TP_DEVICE, "parent", parent, NULL); return FU_HAILUCK_TP_DEVICE(self); } fwupd-1.9.16/plugins/hailuck/fu-hailuck-tp-device.h000066400000000000000000000005701460375044200221240ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_HAILUCK_TP_DEVICE (fu_hailuck_tp_device_get_type()) G_DECLARE_FINAL_TYPE(FuHailuckTpDevice, fu_hailuck_tp_device, FU, HAILUCK_TP_DEVICE, FuDevice) FuHailuckTpDevice * fu_hailuck_tp_device_new(FuDevice *parent); fwupd-1.9.16/plugins/hailuck/fu-hailuck.rs000066400000000000000000000012511460375044200204400ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum HailuckCmd { Erase = 0x45, ReadBlockStart = 0x52, Attach = 0x55, // guessed WriteBlockStart = 0x57, ReadBlock = 0x72, Detach = 0x75, // guessed WriteBlock = 0x77, GetStatus = 0xA1, WriteTp = 0xD0, // guessed I2cCheckChecksum = 0xF0, I2cEnterBl = 0xF1, I2cErase = 0xF2, I2cProgram = 0xF3, I2cVerifyBlock = 0xF4, I2cVerifyChecksum = 0xF5, I2cProgrampass = 0xF6, I2cEndProgram = 0xF7, } fwupd-1.9.16/plugins/hailuck/hailuck.quirk000066400000000000000000000007611460375044200205440ustar00rootroot00000000000000# bootloader [USB\VID_0603&PID_1020] Plugin = hailuck GType = FuHailuckBlDevice #Flags = is-bootloader [USB\VID_258A&PID_001E] Plugin = hailuck GType = FuHailuckKbdDevice Vendor = PINE64 CounterpartGuid = USB\VID_0603&PID_1020 [USB\VID_258A&PID_001E&MODE_KBD] Name = Keyboard [USB\VID_258A&PID_001F] Plugin = hailuck GType = FuHailuckKbdDevice CounterpartGuid = USB\VID_0603&PID_1020 [USB\VID_258A&PID_000D] Plugin = hailuck GType = FuHailuckKbdDevice CounterpartGuid = USB\VID_0603&PID_1020 fwupd-1.9.16/plugins/hailuck/meson.build000066400000000000000000000010511460375044200202020ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginNovatek"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('hailuck.quirk') plugin_builtins += static_library('fu_plugin_hailuck', rustgen.process('fu-hailuck.rs'), sources: [ 'fu-hailuck-bl-device.c', 'fu-hailuck-kbd-device.c', 'fu-hailuck-kbd-firmware.c', # fuzzing 'fu-hailuck-tp-device.c', 'fu-hailuck-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/intel-gsc/000077500000000000000000000000001460375044200163105ustar00rootroot00000000000000fwupd-1.9.16/plugins/intel-gsc/README.md000066400000000000000000000023631460375044200175730ustar00rootroot00000000000000--- title: Plugin: Intel GSC — Graphics System Controller --- ## Introduction This plugin is used to update the Intel graphics system controller via the Intel Management Engine. ## Firmware Format There are two firmware formats in use: * `$FPT` with children `FuIfwiFptFirmware`, where the `FW_DATA_IMAGE` is a `FuIfwiCpdFirmware` * A linear array of `FuOpromFirmware` images, each with a `FuIfwiCpdFirmware` This plugin supports the following protocol ID: * `com.intel.gsc` ## GUID Generation These devices use the standard PCI DeviceInstanceId values, e.g. * `MEI\VID_8086&DEV_4905` They also define custom per-part PCI IDs such as: * `MEI\VID_8086&DEV_4905&PART_FWCODE` * `MEI\VID_8086&DEV_4905&PART_FWDATA` * `MEI\VID_8086&DEV_4905&PART_OPROMCODE` * `MEI\VID_8086&DEV_4905&PART_OPROMDATA` ## Vendor ID Security The vendor ID is set from the PCI vendor, in this instance set to `MEI:0x8086` ## External Interface Access This plugin requires read/write access to `/dev/mei*`. ## Version Considerations This plugin has been available since fwupd version `1.8.7`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Vitaly Lubart: @vlubart fwupd-1.9.16/plugins/intel-gsc/fu-igsc-aux-device.c000066400000000000000000000144571460375044200220540ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel, Inc * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR Apache-2.0 */ #include "config.h" #include "fu-igsc-aux-device.h" #include "fu-igsc-aux-firmware.h" #include "fu-igsc-device.h" #include "fu-igsc-heci.h" struct _FuIgscAuxDevice { FuDevice parent_instance; guint32 oem_version; guint16 major_version; guint16 major_vcn; }; G_DEFINE_TYPE(FuIgscAuxDevice, fu_igsc_aux_device, FU_TYPE_DEVICE) static void fu_igsc_aux_device_to_string(FuDevice *device, guint idt, GString *str) { FuIgscAuxDevice *self = FU_IGSC_AUX_DEVICE(device); fu_string_append_kx(str, idt, "OemManufDataVersion", self->oem_version); fu_string_append_kx(str, idt, "MajorVersion", self->major_version); fu_string_append_kx(str, idt, "MajorVcn", self->major_vcn); } static gboolean fu_igsc_aux_device_probe(FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent(device); /* fix name */ if (parent != NULL) { g_autofree gchar *name = NULL; name = g_strdup_printf("%s Data", fu_device_get_name(parent)); fu_device_set_name(device, name); } /* add extra instance IDs */ fu_device_add_instance_str(device, "PART", "FWDATA"); if (!fu_device_build_instance_id(device, error, "MEI", "VEN", "DEV", "PART", NULL)) return FALSE; return fu_device_build_instance_id(device, error, "MEI", "VEN", "DEV", "SUBSYS", "PART", NULL); } static gboolean fu_igsc_aux_device_setup(FuDevice *device, GError **error) { FuIgscAuxDevice *self = FU_IGSC_AUX_DEVICE(device); FuIgscDevice *igsc_parent = FU_IGSC_DEVICE(fu_device_get_parent(device)); g_autofree gchar *version = NULL; /* get version */ if (!fu_igsc_device_get_aux_version(igsc_parent, &self->oem_version, &self->major_version, &self->major_vcn, error)) { g_prefix_error(error, "failed to get aux version: "); return FALSE; } version = g_strdup_printf("%u.%x", self->major_version, self->oem_version); fu_device_set_version(device, version); /* success */ return TRUE; } static FuFirmware * fu_igsc_aux_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuIgscAuxDevice *self = FU_IGSC_AUX_DEVICE(device); FuIgscDevice *igsc_parent = FU_IGSC_DEVICE(fu_device_get_parent(device)); g_autoptr(FuIgscAuxFirmware) firmware = FU_IGSC_AUX_FIRMWARE(fu_igsc_aux_firmware_new()); /* parse container */ if (!fu_firmware_parse(FU_FIRMWARE(firmware), fw, flags, error)) return NULL; /* search the device list for a match */ if (!fu_igsc_aux_firmware_match_device(firmware, self->major_version, self->major_vcn, fu_igsc_device_get_ssvid(igsc_parent), fu_igsc_device_get_ssdid(igsc_parent), error)) return NULL; /* verify is compatible */ if (fu_igsc_aux_firmware_get_major_version(firmware) != self->major_version) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "image is not for this product, got 0x%x, expected 0x%x", fu_igsc_aux_firmware_get_major_version(firmware), self->major_version); return NULL; } if (fu_igsc_aux_firmware_get_major_vcn(firmware) > self->major_vcn) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "image VCN is not compatible, got 0x%x, expected 0x%x", fu_igsc_aux_firmware_get_major_vcn(firmware), self->major_vcn); return NULL; } if (fu_igsc_aux_firmware_get_oem_version(firmware) <= self->oem_version) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid manufacturer data version, got 0x%x, expected 0x%x", fu_igsc_aux_firmware_get_oem_version(firmware), self->oem_version); return NULL; } /* success, but return container, not CPD */ return FU_FIRMWARE(g_steal_pointer(&firmware)); } static gboolean fu_igsc_aux_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuIgscDevice *igsc_parent = FU_IGSC_DEVICE(fu_device_get_parent(device)); g_autoptr(GBytes) fw_info = NULL; g_autoptr(GBytes) fw_payload = NULL; /* get image */ fw_info = fu_firmware_get_image_by_idx_bytes(firmware, FU_IFWI_FPT_FIRMWARE_IDX_INFO, error); if (fw_info == NULL) return FALSE; fw_payload = fu_firmware_get_image_by_idx_bytes(firmware, FU_IFWI_FPT_FIRMWARE_IDX_SDTA, error); if (fw_payload == NULL) return FALSE; return fu_igsc_device_write_blob(igsc_parent, GSC_FWU_HECI_PAYLOAD_TYPE_FWDATA, fw_info, fw_payload, progress, error); } static gboolean fu_igsc_aux_device_prepare(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { /* set PCI power policy */ return fu_device_prepare(fu_device_get_parent(device), progress, flags, error); } static gboolean fu_igsc_aux_device_cleanup(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { /* set PCI power policy */ return fu_device_cleanup(fu_device_get_parent(device), progress, flags, error); } static void fu_igsc_aux_device_init(FuIgscAuxDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_add_protocol(FU_DEVICE(self), "com.intel.gsc"); fu_device_set_logical_id(FU_DEVICE(self), "fw-data"); } static void fu_igsc_aux_device_class_init(FuIgscAuxDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_igsc_aux_device_to_string; klass_device->probe = fu_igsc_aux_device_probe; klass_device->setup = fu_igsc_aux_device_setup; klass_device->prepare_firmware = fu_igsc_aux_device_prepare_firmware; klass_device->write_firmware = fu_igsc_aux_device_write_firmware; klass_device->prepare = fu_igsc_aux_device_prepare; klass_device->cleanup = fu_igsc_aux_device_cleanup; } FuIgscAuxDevice * fu_igsc_aux_device_new(FuContext *ctx) { return g_object_new(FU_TYPE_IGSC_AUX_DEVICE, "context", ctx, NULL); } fwupd-1.9.16/plugins/intel-gsc/fu-igsc-aux-device.h000066400000000000000000000006221460375044200220460ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR Apache-2.0 */ #pragma once #include #define FU_TYPE_IGSC_AUX_DEVICE (fu_igsc_aux_device_get_type()) G_DECLARE_FINAL_TYPE(FuIgscAuxDevice, fu_igsc_aux_device, FU, IGSC_AUX_DEVICE, FuDevice) FuIgscAuxDevice * fu_igsc_aux_device_new(FuContext *ctx); fwupd-1.9.16/plugins/intel-gsc/fu-igsc-aux-firmware.c000066400000000000000000000205711460375044200224230ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-igsc-aux-firmware.h" #include "fu-igsc-heci.h" #include "fu-igsc-struct.h" struct _FuIgscAuxFirmware { FuIfwiFptFirmware parent_instance; guint32 oem_version; guint16 major_version; guint16 major_vcn; GPtrArray *device_infos; /* of igsc_fwdata_device_info */ gboolean has_manifest_ext; }; G_DEFINE_TYPE(FuIgscAuxFirmware, fu_igsc_aux_firmware, FU_TYPE_IFWI_FPT_FIRMWARE) #define MFT_EXT_TYPE_DEVICE_IDS 37 #define MFT_EXT_TYPE_FWDATA_UPDATE 29 struct mft_fwdata_update_ext { guint32 extension_type; guint32 extension_length; guint32 oem_manuf_data_version; guint16 major_vcn; guint16 flags; }; struct igsc_fwdata_device_info { guint16 vendor_id; guint16 device_id; guint16 subsys_vendor_id; guint16 subsys_device_id; }; struct igsc_fwdata_version { guint32 oem_manuf_data_version; guint16 major_version; guint16 major_vcn; }; static void fu_igsc_aux_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuIgscAuxFirmware *self = FU_IGSC_AUX_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "oem_version", self->oem_version); fu_xmlb_builder_insert_kx(bn, "major_version", self->major_version); fu_xmlb_builder_insert_kx(bn, "major_vcn", self->major_vcn); fu_xmlb_builder_insert_kx(bn, "device_infos", self->device_infos->len); fu_xmlb_builder_insert_kb(bn, "has_manifest_ext", self->has_manifest_ext); } gboolean fu_igsc_aux_firmware_match_device(FuIgscAuxFirmware *self, guint16 vendor_id, guint16 device_id, guint16 subsys_vendor_id, guint16 subsys_device_id, GError **error) { g_return_val_if_fail(FU_IS_IGSC_AUX_FIRMWARE(self), FALSE); for (guint i = 0; i < self->device_infos->len; i++) { struct igsc_fwdata_device_info *info = g_ptr_array_index(self->device_infos, i); if (info->vendor_id == vendor_id && info->device_id == device_id && info->subsys_vendor_id == subsys_vendor_id && info->subsys_device_id == subsys_device_id) return TRUE; } /* not us */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "could not find 0x%04x:0x%04x 0x%04x:0x%04x in the image", vendor_id, device_id, subsys_vendor_id, subsys_device_id); return FALSE; } guint32 fu_igsc_aux_firmware_get_oem_version(FuIgscAuxFirmware *self) { g_return_val_if_fail(FU_IS_IGSC_AUX_FIRMWARE(self), G_MAXUINT32); return self->oem_version; } guint16 fu_igsc_aux_firmware_get_major_version(FuIgscAuxFirmware *self) { g_return_val_if_fail(FU_IS_IGSC_AUX_FIRMWARE(self), G_MAXUINT16); return self->major_version; } guint16 fu_igsc_aux_firmware_get_major_vcn(FuIgscAuxFirmware *self) { g_return_val_if_fail(FU_IS_IGSC_AUX_FIRMWARE(self), G_MAXUINT16); return self->major_vcn; } static gboolean fu_igsc_aux_firmware_parse_version(FuIgscAuxFirmware *self, GError **error) { gsize bufsz = 0; const guint8 *buf; struct igsc_fwdata_version version = {0x0}; g_autoptr(GBytes) fw_info = NULL; fw_info = fu_firmware_get_image_by_idx_bytes(FU_FIRMWARE(self), FU_IFWI_FPT_FIRMWARE_IDX_SDTA, error); if (fw_info == NULL) return FALSE; buf = g_bytes_get_data(fw_info, &bufsz); if (!fu_memcpy_safe((guint8 *)&version, sizeof(version), 0x0, /* dst */ buf, bufsz, FU_STRUCT_IGSC_FWU_HECI_IMAGE_METADATA_SIZE, /* src */ sizeof(version), error)) { g_prefix_error(error, "no version: "); return FALSE; } self->oem_version = version.oem_manuf_data_version; self->major_vcn = version.major_vcn; self->major_version = version.major_version; return TRUE; } static gboolean fu_igsc_aux_firmware_parse_extension(FuIgscAuxFirmware *self, FuFirmware *fw, GError **error) { const guint8 *buf; gsize bufsz = 0; g_autoptr(GBytes) blob = NULL; /* get data */ blob = fu_firmware_get_bytes(fw, error); if (blob == NULL) return FALSE; buf = g_bytes_get_data(blob, &bufsz); if (fu_firmware_get_idx(fw) == MFT_EXT_TYPE_DEVICE_IDS) { for (gsize offset = 0; offset < bufsz; offset += sizeof(struct igsc_fwdata_device_info)) { struct igsc_fwdata_device_info device_info = {0x0}; if (!fu_memcpy_safe((guint8 *)&device_info, sizeof(device_info), 0x0, /* dst */ buf, bufsz, offset, /* src */ sizeof(device_info), error)) { g_prefix_error(error, "no ext header: "); return FALSE; } g_ptr_array_add(self->device_infos, fu_memdup_safe((const guint8 *)&device_info, sizeof(device_info), NULL)); } } else if (fu_firmware_get_idx(fw) == MFT_EXT_TYPE_FWDATA_UPDATE) { if (bufsz != sizeof(struct mft_fwdata_update_ext)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "signed data update manifest ext was 0x%x bytes", (guint)bufsz); return FALSE; } self->has_manifest_ext = TRUE; } /* success */ return TRUE; } static gboolean fu_igsc_aux_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuIgscAuxFirmware *self = FU_IGSC_AUX_FIRMWARE(firmware); g_autoptr(FuFirmware) fw_cpd = fu_ifwi_cpd_firmware_new(); g_autoptr(FuFirmware) fw_manifest = NULL; g_autoptr(GBytes) blob_dataimg = NULL; g_autoptr(GPtrArray) imgs = NULL; /* FuIfwiFptFirmware->parse */ if (!FU_FIRMWARE_CLASS(fu_igsc_aux_firmware_parent_class) ->parse(firmware, fw, offset, flags, error)) return FALSE; /* parse data section */ blob_dataimg = fu_firmware_get_image_by_idx_bytes(firmware, FU_IFWI_FPT_FIRMWARE_IDX_SDTA, error); if (blob_dataimg == NULL) return FALSE; /* parse as CPD */ if (!fu_firmware_parse(fw_cpd, blob_dataimg, flags, error)) return FALSE; /* get manifest */ fw_manifest = fu_firmware_get_image_by_idx(fw_cpd, FU_IFWI_CPD_FIRMWARE_IDX_MANIFEST, error); if (fw_manifest == NULL) return FALSE; /* parse all the manifest extensions */ imgs = fu_firmware_get_images(fw_manifest); for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); if (!fu_igsc_aux_firmware_parse_extension(self, img, error)) return FALSE; } if (!self->has_manifest_ext || self->device_infos->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "missing extensions"); return FALSE; } /* parse the info block */ if (!fu_igsc_aux_firmware_parse_version(self, error)) return FALSE; /* success */ return TRUE; } static GByteArray * fu_igsc_aux_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); return g_steal_pointer(&buf); } static gboolean fu_igsc_aux_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuIgscAuxFirmware *self = FU_IGSC_AUX_FIRMWARE(firmware); const gchar *tmp; /* simple properties */ tmp = xb_node_query_text(n, "oem_version", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT32, error)) return FALSE; self->oem_version = val; } tmp = xb_node_query_text(n, "major_version", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) return FALSE; self->major_version = val; } tmp = xb_node_query_text(n, "major_vcn", NULL); if (tmp != NULL) { guint64 val = 0; if (!fu_strtoull(tmp, &val, 0x0, G_MAXUINT16, error)) return FALSE; self->major_vcn = val; } /* success */ return TRUE; } static void fu_igsc_aux_firmware_init(FuIgscAuxFirmware *self) { self->device_infos = g_ptr_array_new_with_free_func(g_free); } static void fu_igsc_aux_firmware_finalize(GObject *object) { FuIgscAuxFirmware *self = FU_IGSC_AUX_FIRMWARE(object); g_ptr_array_unref(self->device_infos); G_OBJECT_CLASS(fu_igsc_aux_firmware_parent_class)->finalize(object); } static void fu_igsc_aux_firmware_class_init(FuIgscAuxFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_igsc_aux_firmware_finalize; klass_firmware->parse = fu_igsc_aux_firmware_parse; klass_firmware->write = fu_igsc_aux_firmware_write; klass_firmware->build = fu_igsc_aux_firmware_build; klass_firmware->export = fu_igsc_aux_firmware_export; } FuFirmware * fu_igsc_aux_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_IGSC_AUX_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/intel-gsc/fu-igsc-aux-firmware.h000066400000000000000000000015121460375044200224220ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_IGSC_AUX_FIRMWARE (fu_igsc_aux_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuIgscAuxFirmware, fu_igsc_aux_firmware, FU, IGSC_AUX_FIRMWARE, FuIfwiFptFirmware) FuFirmware * fu_igsc_aux_firmware_new(void); guint32 fu_igsc_aux_firmware_get_oem_version(FuIgscAuxFirmware *self); guint16 fu_igsc_aux_firmware_get_major_version(FuIgscAuxFirmware *self); guint16 fu_igsc_aux_firmware_get_major_vcn(FuIgscAuxFirmware *self); gboolean fu_igsc_aux_firmware_match_device(FuIgscAuxFirmware *self, guint16 vendor_id, guint16 device_id, guint16 subsys_vendor_id, guint16 subsys_device_id, GError **error); fwupd-1.9.16/plugins/intel-gsc/fu-igsc-code-firmware.c000066400000000000000000000076501460375044200225430ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-igsc-code-firmware.h" #include "fu-igsc-struct.h" struct _FuIgscCodeFirmware { FuIfwiFptFirmware parent_instance; guint32 hw_sku; }; G_DEFINE_TYPE(FuIgscCodeFirmware, fu_igsc_code_firmware, FU_TYPE_IFWI_FPT_FIRMWARE) #define GSC_FWU_IUP_NUM 2 #define FU_IGSC_FIRMWARE_MAX_SIZE (8 * 1024 * 1024) /* 8M */ static void fu_igsc_code_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuIgscCodeFirmware *self = FU_IGSC_CODE_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "hw_sku", self->hw_sku); } guint32 fu_igsc_code_firmware_get_hw_sku(FuIgscCodeFirmware *self) { g_return_val_if_fail(FU_IS_IFWI_FPT_FIRMWARE(self), G_MAXUINT32); return self->hw_sku; } static gboolean fu_igsc_code_firmware_parse_imgi(FuIgscCodeFirmware *self, GBytes *fw, GError **error) { g_autoptr(GByteArray) st_inf = NULL; /* the command is only supported on DG2 */ if (g_strcmp0(fu_firmware_get_id(FU_FIRMWARE(self)), "DG02") != 0) return TRUE; /* get hw_sku */ st_inf = fu_struct_igsc_fwu_gws_image_info_parse_bytes(fw, 0x0, error); if (st_inf == NULL) return FALSE; self->hw_sku = fu_struct_igsc_fwu_gws_image_info_get_instance_id(st_inf); return TRUE; } static gboolean fu_igsc_code_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuIgscCodeFirmware *self = FU_IGSC_CODE_FIRMWARE(firmware); g_autofree gchar *project = NULL; g_autofree gchar *version = NULL; g_autoptr(GBytes) fw_info = NULL; g_autoptr(GBytes) fw_imgi = NULL; g_autoptr(GByteArray) st_md1 = NULL; /* sanity check */ if (g_bytes_get_size(fw) > FU_IGSC_FIRMWARE_MAX_SIZE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "image size too big: 0x%x", (guint)g_bytes_get_size(fw)); return FALSE; } /* FuIfwiFptFirmware->parse */ if (!FU_FIRMWARE_CLASS(fu_igsc_code_firmware_parent_class) ->parse(firmware, fw, offset, flags, error)) return FALSE; fw_info = fu_firmware_get_image_by_idx_bytes(FU_FIRMWARE(self), FU_IFWI_FPT_FIRMWARE_IDX_INFO, error); if (fw_info == NULL) return FALSE; /* check metadata header format */ st_md1 = fu_struct_igsc_fwu_image_metadata_v1_parse_bytes(fw_info, 0x0, error); if (st_md1 == NULL) return FALSE; if (fu_struct_igsc_fwu_image_metadata_v1_get_version_format(st_md1) != 0x01) { /* Note that it's still ok to use the V1 metadata struct to get the * FW version because the FW version position and structure stays * the same in all versions of the struct */ g_warning("metadata format version is %u, instead of expected V1", fu_struct_igsc_fwu_image_metadata_v1_get_version_format(st_md1)); } project = fu_struct_igsc_fwu_image_metadata_v1_get_project(st_md1); fu_firmware_set_id(FU_FIRMWARE(self), project); version = g_strdup_printf("%04d.%04d", fu_struct_igsc_fwu_image_metadata_v1_get_version_hotfix(st_md1), fu_struct_igsc_fwu_image_metadata_v1_get_version_build(st_md1)); fu_firmware_set_version(FU_FIRMWARE(self), version); /* get instance ID for image */ fw_imgi = fu_firmware_get_image_by_idx_bytes(FU_FIRMWARE(self), FU_IFWI_FPT_FIRMWARE_IDX_IMGI, error); if (fw_imgi == NULL) return FALSE; if (!fu_igsc_code_firmware_parse_imgi(self, fw_imgi, error)) return FALSE; /* success */ return TRUE; } static void fu_igsc_code_firmware_init(FuIgscCodeFirmware *self) { } static void fu_igsc_code_firmware_class_init(FuIgscCodeFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_igsc_code_firmware_parse; klass_firmware->export = fu_igsc_code_firmware_export; } FuFirmware * fu_igsc_code_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_IGSC_CODE_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/intel-gsc/fu-igsc-code-firmware.h000066400000000000000000000007601460375044200225430ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_IGSC_CODE_FIRMWARE (fu_igsc_code_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuIgscCodeFirmware, fu_igsc_code_firmware, FU, IGSC_CODE_FIRMWARE, FuIfwiFptFirmware) FuFirmware * fu_igsc_code_firmware_new(void); guint32 fu_igsc_code_firmware_get_hw_sku(FuIgscCodeFirmware *self); fwupd-1.9.16/plugins/intel-gsc/fu-igsc-device.c000066400000000000000000000612701460375044200212540ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel, Inc * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR Apache-2.0 */ #include "config.h" #include "fu-igsc-aux-device.h" #include "fu-igsc-code-firmware.h" #include "fu-igsc-device.h" #include "fu-igsc-oprom-device.h" #include "fu-igsc-struct.h" struct _FuIgscDevice { FuMeiDevice parent_instance; gchar *project; guint32 hw_sku; guint16 subsystem_vendor; guint16 subsystem_model; gboolean oprom_code_devid_enforcement; }; #define FU_IGSC_DEVICE_FLAG_HAS_AUX (1 << 0) #define FU_IGSC_DEVICE_FLAG_HAS_OPROM (1 << 1) #define FU_IGSC_DEVICE_MEI_WRITE_TIMEOUT 60000 /* 60 sec */ #define FU_IGSC_DEVICE_MEI_READ_TIMEOUT 480000 /* 480 sec */ G_DEFINE_TYPE(FuIgscDevice, fu_igsc_device, FU_TYPE_MEI_DEVICE) #define GSC_FWU_STATUS_SUCCESS 0x0 #define GSC_FWU_STATUS_SIZE_ERROR 0x5 #define GSC_FWU_STATUS_UPDATE_OPROM_INVALID_STRUCTURE 0x1035 #define GSC_FWU_STATUS_UPDATE_OPROM_SECTION_NOT_EXIST 0x1032 #define GSC_FWU_STATUS_INVALID_COMMAND 0x8D #define GSC_FWU_STATUS_INVALID_PARAMS 0x85 #define GSC_FWU_STATUS_FAILURE 0x9E #define GSC_FWU_GET_CONFIG_FORMAT_VERSION 0x1 static void fu_igsc_device_to_string(FuDevice *device, guint idt, GString *str) { FuIgscDevice *self = FU_IGSC_DEVICE(device); FU_DEVICE_CLASS(fu_igsc_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "Project", self->project); fu_string_append_kx(str, idt, "HwSku", self->hw_sku); fu_string_append_kx(str, idt, "SubsystemVendor", self->subsystem_vendor); fu_string_append_kx(str, idt, "SubsystemModel", self->subsystem_model); fu_string_append_kb(str, idt, "OpromCodeDevidEnforcement", self->oprom_code_devid_enforcement); } gboolean fu_igsc_device_get_oprom_code_devid_enforcement(FuIgscDevice *self) { g_return_val_if_fail(FU_IS_IGSC_DEVICE(self), FALSE); return self->oprom_code_devid_enforcement; } guint16 fu_igsc_device_get_ssvid(FuIgscDevice *self) { g_return_val_if_fail(FU_IS_IGSC_DEVICE(self), G_MAXUINT16); return self->subsystem_vendor; } guint16 fu_igsc_device_get_ssdid(FuIgscDevice *self) { g_return_val_if_fail(FU_IS_IGSC_DEVICE(self), G_MAXUINT16); return self->subsystem_model; } static gboolean fu_igsc_device_heci_validate_response_header(FuIgscDevice *self, struct gsc_fwu_heci_response *resp_header, enum gsc_fwu_heci_command_id command_id, GError **error) { if (resp_header->header.command_id != command_id) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid command ID (%d): ", resp_header->header.command_id); return FALSE; } if (!resp_header->header.is_response) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not a response"); return FALSE; } if (resp_header->status != GSC_FWU_STATUS_SUCCESS) { const gchar *msg; switch (resp_header->status) { case GSC_FWU_STATUS_SIZE_ERROR: msg = "num of bytes to read/write/erase is bigger than partition size"; break; case GSC_FWU_STATUS_UPDATE_OPROM_INVALID_STRUCTURE: msg = "wrong oprom signature"; break; case GSC_FWU_STATUS_UPDATE_OPROM_SECTION_NOT_EXIST: msg = "update oprom section does not exists on flash"; break; case GSC_FWU_STATUS_INVALID_COMMAND: msg = "invalid HECI message sent"; break; case GSC_FWU_STATUS_INVALID_PARAMS: msg = "invalid command parameters"; break; case GSC_FWU_STATUS_FAILURE: /* fall through */ default: msg = "general firmware error"; break; } g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "HECI message failed: %s [0x%x]: ", msg, resp_header->status); return FALSE; } if (resp_header->reserved != 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "HECI message response is leaking data"); return FALSE; } /* success */ return TRUE; } static gboolean fu_igsc_device_command(FuIgscDevice *self, const guint8 *req_buf, gsize req_bufsz, guint8 *resp_buf, gsize resp_bufsz, GError **error) { gsize resp_readsz = 0; if (!fu_mei_device_write(FU_MEI_DEVICE(self), req_buf, req_bufsz, FU_IGSC_DEVICE_MEI_WRITE_TIMEOUT, error)) return FALSE; if (!fu_mei_device_read(FU_MEI_DEVICE(self), resp_buf, resp_bufsz, &resp_readsz, FU_IGSC_DEVICE_MEI_READ_TIMEOUT, error)) return FALSE; if (resp_readsz != resp_bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "read 0x%x bytes but expected 0x%x", (guint)resp_readsz, (guint)resp_bufsz); return FALSE; } return TRUE; } gboolean fu_igsc_device_get_version_raw(FuIgscDevice *self, enum gsc_fwu_heci_partition_version partition, guint8 *buf, gsize bufsz, GError **error) { struct gsc_fwu_heci_version_req req = {.header.command_id = GSC_FWU_HECI_COMMAND_ID_GET_IP_VERSION, .partition = partition}; guint8 res_buf[100]; struct gsc_fwu_heci_version_resp *res = (struct gsc_fwu_heci_version_resp *)res_buf; if (!fu_igsc_device_command(self, (const guint8 *)&req, sizeof(req), res_buf, sizeof(struct gsc_fwu_heci_version_resp) + bufsz, error)) { g_prefix_error(error, "invalid HECI message response: "); return FALSE; } if (!fu_igsc_device_heci_validate_response_header(self, &res->response, req.header.command_id, error)) return FALSE; if (res->partition != partition) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid HECI message response payload: 0x%x: ", res->partition); return FALSE; } if (bufsz > 0 && res->version_length != bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid HECI message response version_length: 0x%x, expected 0x%x: ", res->version_length, (guint)bufsz); return FALSE; } if (buf != NULL) { if (!fu_memcpy_safe(buf, bufsz, 0x0, /* dst */ res->version, res->version_length, 0x0, /* src*/ res->version_length, error)) { return FALSE; } } /* success */ return TRUE; } gboolean fu_igsc_device_get_aux_version(FuIgscDevice *self, guint32 *oem_version, guint16 *major_version, guint16 *major_vcn, GError **error) { struct gsc_fw_data_heci_version_req req = { .header.command_id = GSC_FWU_HECI_COMMAND_ID_GET_GFX_DATA_UPDATE_INFO}; struct gsc_fw_data_heci_version_resp res = {0x0}; if (!fu_igsc_device_command(self, (const guint8 *)&req, sizeof(req), (guint8 *)&res, sizeof(res), error)) return FALSE; if (!fu_igsc_device_heci_validate_response_header(self, &res.response, req.header.command_id, error)) return FALSE; /* success */ *major_vcn = res.major_vcn; *major_version = res.major_version; if (res.oem_version_fitb_valid) { *oem_version = res.oem_version_fitb; } else { *oem_version = res.oem_version_nvm; } return TRUE; } static gboolean fu_igsc_device_get_subsystem_ids(FuIgscDevice *self, GError **error) { struct gsc_fwu_heci_get_subsystem_ids_message_req req = { .header.command_id = GSC_FWU_HECI_COMMAND_ID_GET_SUBSYSTEM_IDS}; struct gsc_fwu_heci_get_subsystem_ids_message_resp res = {0x0}; if (!fu_igsc_device_command(self, (const guint8 *)&req, sizeof(req), (guint8 *)&res, sizeof(res), error)) return FALSE; if (!fu_igsc_device_heci_validate_response_header(self, &res.response, req.header.command_id, error)) return FALSE; /* success */ self->subsystem_vendor = res.ssvid; self->subsystem_model = res.ssdid; return TRUE; } #define GSC_IFWI_TAG_SOC2_SKU_BIT 0x1 #define GSC_IFWI_TAG_SOC3_SKU_BIT 0x2 #define GSC_IFWI_TAG_SOC1_SKU_BIT 0x4 #define GSC_DG2_SKUID_SOC1 0 #define GSC_DG2_SKUID_SOC2 1 #define GSC_DG2_SKUID_SOC3 2 static gboolean fu_igsc_device_get_config(FuIgscDevice *self, GError **error) { struct gsc_fwu_heci_get_config_message_req req = { .header.command_id = GSC_FWU_HECI_COMMAND_ID_GET_CONFIG, }; struct gsc_fwu_heci_get_config_message_resp res = {0x0}; if (!fu_igsc_device_command(self, (const guint8 *)&req, sizeof(req), (guint8 *)&res, sizeof(res), error)) { g_prefix_error(error, "invalid HECI message response: "); return FALSE; } if (!fu_igsc_device_heci_validate_response_header(self, &res.response, req.header.command_id, error)) return FALSE; if (res.format_version != GSC_FWU_GET_CONFIG_FORMAT_VERSION) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid config version 0x%x, expected 0x%x", res.format_version, (guint)GSC_FWU_GET_CONFIG_FORMAT_VERSION); return FALSE; } /* convert to firmware bit mask for easier comparison */ if (res.hw_sku == GSC_DG2_SKUID_SOC1) { self->hw_sku = GSC_IFWI_TAG_SOC1_SKU_BIT; } else if (res.hw_sku == GSC_DG2_SKUID_SOC3) { self->hw_sku = GSC_IFWI_TAG_SOC3_SKU_BIT; } else if (res.hw_sku == GSC_DG2_SKUID_SOC2) { self->hw_sku = GSC_IFWI_TAG_SOC2_SKU_BIT; } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid hw sku 0x%x, expected 0..2", res.hw_sku); return FALSE; } self->oprom_code_devid_enforcement = res.oprom_code_devid_enforcement; /* success */ return TRUE; } static gboolean fu_igsc_device_open(FuDevice *device, GError **error) { /* open then create context */ if (!FU_DEVICE_CLASS(fu_igsc_device_parent_class)->open(device, error)) return FALSE; return fu_mei_device_connect(FU_MEI_DEVICE(device), 0, error); } static gboolean fu_igsc_device_setup(FuDevice *device, GError **error) { FuIgscDevice *self = FU_IGSC_DEVICE(device); FuContext *ctx = fu_device_get_context(FU_DEVICE(self)); g_autofree gchar *version = NULL; g_autoptr(GByteArray) fw_code_version = fu_struct_igsc_fw_version_new(); /* get current version */ if (!fu_igsc_device_get_version_raw(self, GSC_FWU_HECI_PART_VERSION_GFX_FW, fw_code_version->data, fw_code_version->len, error)) { g_prefix_error(error, "cannot cannot get fw version: "); return FALSE; } self->project = fu_struct_igsc_fw_version_get_project(fw_code_version); version = g_strdup_printf("%u.%u", fu_struct_igsc_fw_version_get_hotfix(fw_code_version), fu_struct_igsc_fw_version_get_build(fw_code_version)); fu_device_set_version(device, version); /* get hardware SKU if supported */ if (g_strcmp0(self->project, "DG02") == 0) { if (!fu_igsc_device_get_config(self, error)) { g_prefix_error(error, "cannot cannot get SKU: "); return FALSE; } } /* allow vendors to differentiate their products */ if (!fu_igsc_device_get_subsystem_ids(self, error)) return FALSE; if (self->subsystem_vendor != 0x0 && self->subsystem_model != 0x0) { g_autofree gchar *subsys = g_strdup_printf("%04X%04X", self->subsystem_vendor, self->subsystem_model); fu_device_add_instance_str(device, "SUBSYS", subsys); } /* some devices have children */ if (fu_device_has_private_flag(device, FU_IGSC_DEVICE_FLAG_HAS_AUX)) { g_autoptr(FuIgscAuxDevice) device_child = fu_igsc_aux_device_new(ctx); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(device_child)); } if (fu_device_has_private_flag(device, FU_IGSC_DEVICE_FLAG_HAS_OPROM)) { g_autoptr(FuIgscOpromDevice) device_code = NULL; g_autoptr(FuIgscOpromDevice) device_data = NULL; device_code = fu_igsc_oprom_device_new(ctx, GSC_FWU_HECI_PAYLOAD_TYPE_OPROM_CODE); device_data = fu_igsc_oprom_device_new(ctx, GSC_FWU_HECI_PAYLOAD_TYPE_OPROM_DATA); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(device_code)); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(device_data)); } /* success */ return TRUE; } /* @line is indexed from 1 */ static gboolean fu_igsc_device_get_fw_status(FuIgscDevice *self, guint line, guint32 *fw_status, GError **error) { guint64 tmp64 = 0; g_autofree gchar *tmp = NULL; g_autofree gchar *hex = NULL; g_return_val_if_fail(line >= 1, FALSE); /* read value and convert to hex */ tmp = fu_mei_device_get_fw_status(FU_MEI_DEVICE(self), line - 1, error); if (tmp == NULL) { g_prefix_error(error, "device is corrupted: "); return FALSE; } hex = g_strdup_printf("0x%s", tmp); if (!fu_strtoull(hex, &tmp64, 0x1, G_MAXUINT32 - 0x1, error)) { g_prefix_error(error, "fw_status %s is invalid: ", tmp); return FALSE; } /* success */ if (fw_status != NULL) *fw_status = tmp64; return TRUE; } static gboolean fu_igsc_device_probe(FuDevice *device, GError **error) { FuIgscDevice *self = FU_IGSC_DEVICE(device); /* check firmware status */ if (!fu_igsc_device_get_fw_status(self, 1, NULL, error)) return FALSE; /* add extra instance IDs */ fu_device_add_instance_str(device, "PART", "FWCODE"); if (!fu_device_build_instance_id(device, error, "MEI", "VEN", "DEV", "PART", NULL)) return FALSE; return fu_device_build_instance_id(device, error, "MEI", "VEN", "DEV", "SUBSYS", "PART", NULL); } static FuFirmware * fu_igsc_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuIgscDevice *self = FU_IGSC_DEVICE(device); g_autoptr(FuFirmware) firmware = fu_igsc_code_firmware_new(); /* check project code */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; if (g_strcmp0(self->project, fu_firmware_get_id(firmware)) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware is for a different project, got %s, expected %s", fu_firmware_get_id(firmware), self->project); return NULL; } if (self->hw_sku != fu_igsc_code_firmware_get_hw_sku(FU_IGSC_CODE_FIRMWARE(firmware))) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware is for a different SKU, got 0x%x, expected 0x%x", fu_igsc_code_firmware_get_hw_sku(FU_IGSC_CODE_FIRMWARE(firmware)), self->hw_sku); return NULL; } /* success */ return g_steal_pointer(&firmware); } static gboolean fu_igsc_device_update_end(FuIgscDevice *self, GError **error) { struct gsc_fwu_heci_end_req req = {.header.command_id = GSC_FWU_HECI_COMMAND_ID_END}; return fu_mei_device_write(FU_MEI_DEVICE(self), (const guint8 *)&req, sizeof(req), FU_IGSC_DEVICE_MEI_WRITE_TIMEOUT, error); } static gboolean fu_igsc_device_update_data(FuIgscDevice *self, const guint8 *data, guint32 length, GError **error) { struct gsc_fwu_heci_data_req req = {.header.command_id = GSC_FWU_HECI_COMMAND_ID_DATA, .data_length = length}; struct gsc_fwu_heci_data_resp res = {0x0}; g_autoptr(GByteArray) buf = g_byte_array_new(); g_byte_array_append(buf, (const guint8 *)&req, sizeof(req)); g_byte_array_append(buf, data, length); if (!fu_igsc_device_command(self, buf->data, buf->len, (guint8 *)&res, sizeof(res), error)) return FALSE; return fu_igsc_device_heci_validate_response_header(self, &res.response, req.header.command_id, error); } static gboolean fu_igsc_device_update_start(FuIgscDevice *self, guint32 payload_type, GBytes *fw_info, GBytes *fw, GError **error) { struct gsc_fwu_heci_start_req req = {.header.command_id = GSC_FWU_HECI_COMMAND_ID_START, .payload_type = payload_type, .update_img_length = g_bytes_get_size(fw), .flags = {0}}; struct gsc_fwu_heci_start_resp res = {0x0}; g_autoptr(GByteArray) buf = g_byte_array_new(); g_byte_array_append(buf, (const guint8 *)&req, sizeof(req)); if (fw_info != NULL) fu_byte_array_append_bytes(buf, fw_info); if (!fu_igsc_device_command(self, buf->data, buf->len, (guint8 *)&res, sizeof(res), error)) return FALSE; return fu_igsc_device_heci_validate_response_header(self, &res.response, req.header.command_id, error); } static gboolean fu_igsc_device_no_update(FuIgscDevice *self, GError **error) { struct gsc_fwu_heci_no_update_req req = {.header.command_id = GSC_FWU_HECI_COMMAND_ID_NO_UPDATE}; return fu_mei_device_write(FU_MEI_DEVICE(self), (const guint8 *)&req, sizeof(req), FU_IGSC_DEVICE_MEI_WRITE_TIMEOUT, error); } static gboolean fu_igsc_device_write_chunks(FuIgscDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_igsc_device_update_data(self, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed on chunk %u (@0x%x): ", i, fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); } return TRUE; } /* the expectation is that it will fail eventually */ static gboolean fu_igsc_device_wait_for_reset(FuIgscDevice *self, GError **error) { g_autoptr(GByteArray) fw_code_version = fu_struct_igsc_fw_version_new(); for (guint i = 0; i < 20; i++) { if (!fu_igsc_device_get_version_raw(self, GSC_FWU_HECI_PART_VERSION_GFX_FW, fw_code_version->data, fw_code_version->len, NULL)) return TRUE; fu_device_sleep(FU_DEVICE(self), 100); } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "device did not reset"); return FALSE; } static gboolean fu_igsc_device_reconnect_cb(FuDevice *self, gpointer user_data, GError **error) { return fu_mei_device_connect(FU_MEI_DEVICE(self), 0, error); } gboolean fu_igsc_device_write_blob(FuIgscDevice *self, enum gsc_fwu_heci_payload_type payload_type, GBytes *fw_info, GBytes *fw, FuProgress *progress, GError **error) { gboolean cp_mode; guint32 sts5 = 0; gsize payloadsz = fu_mei_device_get_max_msg_length(FU_MEI_DEVICE(self)) - sizeof(struct gsc_fwu_heci_data_req); g_autoptr(FuChunkArray) chunks = NULL; /* progress */ if (payload_type == GSC_FWU_HECI_PAYLOAD_TYPE_GFX_FW) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "get-status"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "update-start"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, "write-chunks"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "update-end"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "wait-for-reboot"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 46, "reconnect"); } else { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "get-status"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "update-start"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 96, "write-chunks"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "update-end"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "wait-for-reboot"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reconnect"); } /* need to get the new version in a loop? */ if (!fu_igsc_device_get_fw_status(self, 5, &sts5, error)) return FALSE; cp_mode = (sts5 & HECI1_CSE_FS_MODE_MASK) == HECI1_CSE_FS_CP_MODE; fu_progress_step_done(progress); /* start */ if (!fu_igsc_device_update_start(self, payload_type, fw_info, fw, error)) { g_prefix_error(error, "failed to start: "); return FALSE; } fu_progress_step_done(progress); /* data */ chunks = fu_chunk_array_new_from_bytes(fw, 0x0, payloadsz); if (!fu_igsc_device_write_chunks(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* stop */ if (!fu_igsc_device_update_end(self, error)) { g_prefix_error(error, "failed to end: "); return FALSE; } fu_progress_step_done(progress); /* detect a firmware reboot */ if (payload_type == GSC_FWU_HECI_PAYLOAD_TYPE_GFX_FW || payload_type == GSC_FWU_HECI_PAYLOAD_TYPE_FWDATA) { if (!fu_igsc_device_wait_for_reset(self, error)) return FALSE; } fu_progress_step_done(progress); /* after Gfx FW update there is a FW reset so driver reconnect is needed */ if (payload_type == GSC_FWU_HECI_PAYLOAD_TYPE_GFX_FW) { if (cp_mode) { if (!fu_igsc_device_wait_for_reset(self, error)) return FALSE; } if (!fu_device_retry_full(FU_DEVICE(self), fu_igsc_device_reconnect_cb, 200, 300 /* ms */, NULL, error)) return FALSE; if (!fu_igsc_device_no_update(self, error)) { g_prefix_error(error, "failed to send no-update: "); return FALSE; } fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_igsc_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuIgscDevice *self = FU_IGSC_DEVICE(device); g_autoptr(GBytes) fw_info = NULL; g_autoptr(GBytes) fw_payload = NULL; /* get image, and install on ourself */ fw_info = fu_firmware_get_image_by_idx_bytes(firmware, FU_IFWI_FPT_FIRMWARE_IDX_INFO, error); if (fw_info == NULL) return FALSE; fw_payload = fu_firmware_get_image_by_idx_bytes(firmware, FU_IFWI_FPT_FIRMWARE_IDX_FWIM, error); if (fw_payload == NULL) return FALSE; return fu_igsc_device_write_blob(self, GSC_FWU_HECI_PAYLOAD_TYPE_GFX_FW, fw_info, fw_payload, progress, error); } static gboolean fu_igsc_device_set_pci_power_policy(FuIgscDevice *self, const gchar *val, GError **error) { g_autoptr(FuUdevDevice) parent = NULL; /* get PCI parent */ parent = fu_udev_device_get_parent_with_subsystem(FU_UDEV_DEVICE(self), "pci"); if (parent == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no PCI parent"); return FALSE; } return fu_udev_device_write_sysfs(parent, "power/control", val, error); } static gboolean fu_igsc_device_prepare(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuIgscDevice *self = FU_IGSC_DEVICE(device); return fu_igsc_device_set_pci_power_policy(self, "on", error); } static gboolean fu_igsc_device_cleanup(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuIgscDevice *self = FU_IGSC_DEVICE(device); return fu_igsc_device_set_pci_power_policy(self, "auto", error); } static void fu_igsc_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 96, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reload"); } static void fu_igsc_device_init(FuIgscDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_set_vendor(FU_DEVICE(self), "Intel"); fu_device_set_name(FU_DEVICE(self), "Graphics Card"); fu_device_set_summary(FU_DEVICE(self), "Discrete Graphics Card"); fu_device_add_icon(FU_DEVICE(self), "video-display"); fu_device_add_protocol(FU_DEVICE(self), "com.intel.gsc"); fu_device_add_icon(FU_DEVICE(self), "gpu"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_set_remove_delay(FU_DEVICE(self), 60000); fu_device_register_private_flag(FU_DEVICE(self), FU_IGSC_DEVICE_FLAG_HAS_AUX, "has-aux"); fu_device_register_private_flag(FU_DEVICE(self), FU_IGSC_DEVICE_FLAG_HAS_OPROM, "has-oprom"); } static void fu_igsc_device_finalize(GObject *object) { FuIgscDevice *self = FU_IGSC_DEVICE(object); g_free(self->project); G_OBJECT_CLASS(fu_igsc_device_parent_class)->finalize(object); } static void fu_igsc_device_class_init(FuIgscDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_igsc_device_finalize; klass_device->set_progress = fu_igsc_device_set_progress; klass_device->to_string = fu_igsc_device_to_string; klass_device->open = fu_igsc_device_open; klass_device->setup = fu_igsc_device_setup; klass_device->probe = fu_igsc_device_probe; klass_device->prepare = fu_igsc_device_prepare; klass_device->cleanup = fu_igsc_device_cleanup; klass_device->prepare_firmware = fu_igsc_device_prepare_firmware; klass_device->write_firmware = fu_igsc_device_write_firmware; } fwupd-1.9.16/plugins/intel-gsc/fu-igsc-device.h000066400000000000000000000021441460375044200212540ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel, Inc. * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR Apache-2.0 */ #pragma once #include #include "fu-igsc-heci.h" #define FU_TYPE_IGSC_DEVICE (fu_igsc_device_get_type()) G_DECLARE_FINAL_TYPE(FuIgscDevice, fu_igsc_device, FU, IGSC_DEVICE, FuMeiDevice) gboolean fu_igsc_device_get_oprom_code_devid_enforcement(FuIgscDevice *self); guint16 fu_igsc_device_get_ssvid(FuIgscDevice *self); guint16 fu_igsc_device_get_ssdid(FuIgscDevice *self); gboolean fu_igsc_device_write_blob(FuIgscDevice *self, enum gsc_fwu_heci_payload_type payload_type, GBytes *fw_info, GBytes *fw_payload, FuProgress *progress, GError **error); gboolean fu_igsc_device_get_aux_version(FuIgscDevice *self, guint32 *oem_version, guint16 *major_version, guint16 *major_vcn, GError **error); gboolean fu_igsc_device_get_version_raw(FuIgscDevice *self, enum gsc_fwu_heci_partition_version partition, guint8 *buf, gsize bufsz, GError **error); fwupd-1.9.16/plugins/intel-gsc/fu-igsc-heci.h000066400000000000000000000107431460375044200207310ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define HECI1_CSE_FS_FWUPDATE_STATE_IDLE_BIT (1 << 11) #define HECI1_CSE_FS_INITSTATE_COMPLETED_BIT (1 << 9) #define HECI1_CSE_GS1_PHASE_FWUPDATE 7 #define HECI1_CSE_FS_FWUPD_PHASE_SHIFT 28 #define HECI1_CSE_FS_FWUPD_PHASE_MASK 0xF #define HECI1_CSE_FS_FWUPD_PERCENT_SHIFT 16 #define HECI1_CSE_FS_FWUPD_PERCENT_MASK 0xFF #define HECI1_CSE_FS_MODE_MASK 0x3 #define HECI1_CSE_FS_CP_MODE 0x3 enum gsc_fwu_heci_partition_version { GSC_FWU_HECI_PART_VERSION_INVALID = 0, GSC_FWU_HECI_PART_VERSION_GFX_FW = 1, GSC_FWU_HECI_PART_VERSION_OPROM_DATA = 2, GSC_FWU_HECI_PART_VERSION_OPROM_CODE = 3, }; enum gsc_fwu_heci_payload_type { GSC_FWU_HECI_PAYLOAD_TYPE_INVALID = 0, GSC_FWU_HECI_PAYLOAD_TYPE_GFX_FW = 1, GSC_FWU_HECI_PAYLOAD_TYPE_OPROM_DATA = 2, GSC_FWU_HECI_PAYLOAD_TYPE_OPROM_CODE = 3, GSC_FWU_HECI_PAYLOAD_TYPE_FWDATA = 5, }; enum gsc_fwu_heci_command_id { GSC_FWU_HECI_COMMAND_ID_INVALID = 0, GSC_FWU_HECI_COMMAND_ID_START, /* start firmware updated flow */ GSC_FWU_HECI_COMMAND_ID_DATA, /* send firmware data to device */ GSC_FWU_HECI_COMMAND_ID_END, /* last command in update */ GSC_FWU_HECI_COMMAND_ID_GET_VERSION, /* retrieve version of a firmware */ GSC_FWU_HECI_COMMAND_ID_NO_UPDATE, /* do not wait for firmware update */ GSC_FWU_HECI_COMMAND_ID_GET_IP_VERSION, /* retrieve version of a partition */ GSC_FWU_HECI_COMMAND_ID_GET_CONFIG, /* get hardware config */ GSC_FWU_HECI_COMMAND_ID_STATUS, /* get status of most recent update */ GSC_FWU_HECI_COMMAND_ID_GET_GFX_DATA_UPDATE_INFO, /* get signed firmware data info */ GSC_FWU_HECI_COMMAND_ID_GET_SUBSYSTEM_IDS, /* get subsystem ids (vid/did) */ GSC_FWU_HECI_COMMAND_MAX }; struct gsc_fwu_heci_header { guint8 command_id; guint8 is_response : 1; guint8 reserved : 7; guint8 reserved2[2]; } __attribute__((packed)); struct gsc_fwu_heci_response { struct gsc_fwu_heci_header header; guint32 status; guint32 reserved; } __attribute__((packed)); struct gsc_fwu_heci_version_req { struct gsc_fwu_heci_header header; guint32 partition; } __attribute__((packed)); struct gsc_fwu_heci_version_resp { struct gsc_fwu_heci_response response; guint32 partition; guint32 version_length; guint8 version[]; } __attribute__((packed)); struct gsc_fw_data_heci_version_req { struct gsc_fwu_heci_header header; guint32 reserved[2]; } __attribute__((packed)); struct gsc_fw_data_heci_version_resp { struct gsc_fwu_heci_response response; guint32 format_version; guint32 oem_version_nvm; guint32 oem_version_fitb; guint16 major_version; guint16 major_vcn; guint32 oem_version_fitb_valid; guint32 flags; guint32 reserved[7]; } __attribute__((packed)); struct gsc_fwu_heci_get_config_message_req { struct gsc_fwu_heci_header header; guint32 reserved[2]; } __attribute__((packed)); struct gsc_fwu_heci_get_config_message_resp { struct gsc_fwu_heci_response response; guint32 format_version; guint32 hw_step; guint32 hw_sku; guint32 oprom_code_devid_enforcement : 1; guint32 flags : 31; guint32 reserved[7]; guint32 debug_config; } __attribute__((packed)); struct gsc_fwu_heci_get_subsystem_ids_message_req { struct gsc_fwu_heci_header header; guint32 reserved[2]; } __attribute__((packed)); struct gsc_fwu_heci_get_subsystem_ids_message_resp { struct gsc_fwu_heci_response response; guint16 ssvid; guint16 ssdid; guint32 reserved[2]; } __attribute__((packed)); struct gsc_fwu_heci_start_flags { guint32 force_update : 1; guint32 reserved : 31; } __attribute__((packed)); struct gsc_fwu_heci_start_req { struct gsc_fwu_heci_header header; guint32 update_img_length; guint32 payload_type; struct gsc_fwu_heci_start_flags flags; guint32 reserved[8]; } __attribute__((packed)); struct gsc_fwu_heci_start_resp { struct gsc_fwu_heci_response response; } __attribute__((packed)); struct gsc_fwu_heci_data_req { struct gsc_fwu_heci_header header; guint32 data_length; guint32 reserved; } __attribute__((packed)); struct gsc_fwu_heci_data_resp { struct gsc_fwu_heci_response response; } __attribute__((packed)); struct gsc_fwu_heci_end_req { struct gsc_fwu_heci_header header; guint32 reserved; } __attribute__((packed)); struct gsc_fwu_heci_end_resp { struct gsc_fwu_heci_response response; } __attribute__((packed)); struct gsc_fwu_heci_no_update_req { struct gsc_fwu_heci_header header; guint32 reserved; } __attribute__((packed)); fwupd-1.9.16/plugins/intel-gsc/fu-igsc-oprom-device.c000066400000000000000000000220501460375044200223770ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel, Inc * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR Apache-2.0 */ #include "config.h" #include "fu-igsc-device.h" #include "fu-igsc-oprom-device.h" #include "fu-igsc-oprom-firmware.h" #include "fu-igsc-struct.h" struct _FuIgscOpromDevice { FuDevice parent_instance; enum gsc_fwu_heci_payload_type payload_type; enum gsc_fwu_heci_partition_version partition_version; guint16 major_version; }; G_DEFINE_TYPE(FuIgscOpromDevice, fu_igsc_oprom_device, FU_TYPE_DEVICE) static void fu_igsc_oprom_device_to_string(FuDevice *device, guint idt, GString *str) { FuIgscOpromDevice *self = FU_IGSC_OPROM_DEVICE(device); fu_string_append_kx(str, idt, "PayloadType", self->payload_type); fu_string_append_kx(str, idt, "PartitionVersion", self->partition_version); } static gboolean fu_igsc_oprom_device_probe(FuDevice *device, GError **error) { FuIgscOpromDevice *self = FU_IGSC_OPROM_DEVICE(device); FuDevice *parent = fu_device_get_parent(device); g_autofree gchar *name = NULL; /* set strings now we know the type */ if (self->payload_type == GSC_FWU_HECI_PAYLOAD_TYPE_OPROM_CODE) { self->partition_version = GSC_FWU_HECI_PART_VERSION_OPROM_CODE; fu_device_add_instance_str(device, "PART", "OPROMCODE"); fu_device_set_logical_id(FU_DEVICE(self), "oprom-code"); if (parent != NULL) { name = g_strdup_printf("%s OptionROM Code", fu_device_get_name(parent)); fu_device_set_name(FU_DEVICE(self), name); } } else if (self->payload_type == GSC_FWU_HECI_PAYLOAD_TYPE_OPROM_DATA) { self->partition_version = GSC_FWU_HECI_PART_VERSION_OPROM_DATA; fu_device_add_instance_str(device, "PART", "OPROMDATA"); fu_device_set_logical_id(FU_DEVICE(self), "oprom-data"); if (parent != NULL) { name = g_strdup_printf("%s OptionROM Data", fu_device_get_name(parent)); fu_device_set_name(FU_DEVICE(self), name); } } /* add extra instance IDs */ if (!fu_device_build_instance_id(device, error, "MEI", "VEN", "DEV", "PART", NULL)) return FALSE; return fu_device_build_instance_id(device, error, "MEI", "VEN", "DEV", "SUBSYS", "PART", NULL); } static gboolean fu_igsc_oprom_device_setup(FuDevice *device, GError **error) { FuIgscOpromDevice *self = FU_IGSC_OPROM_DEVICE(device); FuIgscDevice *igsc_parent = FU_IGSC_DEVICE(fu_device_get_parent(device)); guint8 buf[8] = {0x0}; g_autofree gchar *version = NULL; g_autoptr(GByteArray) st = NULL; /* get version */ if (!fu_igsc_device_get_version_raw(igsc_parent, self->partition_version, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to get oprom version: "); return FALSE; } st = fu_struct_igsc_oprom_version_parse(buf, sizeof(buf), 0x0, error); if (st == NULL) return FALSE; self->major_version = fu_struct_igsc_oprom_version_get_major(st); version = g_strdup_printf("%u.%u.%u.%u", self->major_version, fu_struct_igsc_oprom_version_get_minor(st), fu_struct_igsc_oprom_version_get_hotfix(st), fu_struct_igsc_oprom_version_get_build(st)); fu_device_set_version(device, version); /* success */ return TRUE; } static FuFirmware * fu_igsc_oprom_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuIgscOpromDevice *self = FU_IGSC_OPROM_DEVICE(device); FuIgscDevice *igsc_parent = FU_IGSC_DEVICE(fu_device_get_parent(device)); guint16 vendor_id = fu_udev_device_get_vendor(FU_UDEV_DEVICE(igsc_parent)); guint16 device_id = fu_udev_device_get_model(FU_UDEV_DEVICE(igsc_parent)); guint16 subsys_vendor_id = fu_igsc_device_get_ssvid(igsc_parent); guint16 subsys_device_id = fu_igsc_device_get_ssvid(igsc_parent); g_autoptr(FuFirmware) firmware = NULL; g_autoptr(FuFirmware) fw_linear = fu_linear_firmware_new(FU_TYPE_IGSC_OPROM_FIRMWARE); /* parse container */ if (!fu_firmware_parse(fw_linear, fw, flags, error)) return NULL; /* get correct image */ firmware = fu_firmware_get_image_by_idx(fw_linear, self->payload_type, error); if (firmware == NULL) return NULL; /* major numbers must be the same, unless the device's major is zero, * because some platforms may come originally with 0 major number */ if (fu_igsc_oprom_firmware_get_major_version(FU_IGSC_OPROM_FIRMWARE(firmware)) != self->major_version && self->major_version != 0) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "image major version is not compatible, got 0x%x, expected 0x%x", fu_igsc_oprom_firmware_get_major_version(FU_IGSC_OPROM_FIRMWARE(firmware)), self->major_version); return NULL; } /* If oprom_code_devid_enforcement is set to True: * The update is accepted only if the update file contains a Device IDs allowlist * and the card's {VID, DID, SSVID, SSDID} is in the update file's Device IDs allowlist. * If the flag doesn't exist or is False: * The update is accepted only if the update file does not contain a Device ID allowlist */ if (self->payload_type == GSC_FWU_HECI_PAYLOAD_TYPE_OPROM_CODE) { if (fu_igsc_device_get_oprom_code_devid_enforcement(igsc_parent)) { if (!fu_igsc_oprom_firmware_match_device(FU_IGSC_OPROM_FIRMWARE(firmware), vendor_id, device_id, subsys_vendor_id, subsys_device_id, error)) return NULL; } else { if (fu_igsc_oprom_firmware_has_allowlist( FU_IGSC_OPROM_FIRMWARE(firmware))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device is not enforcing devid match, but " "firmware provided allowlist"); return NULL; } } } /* If the Device IDs allowlist (0x37) exists in the update image: * The update is accepted only if the card's {VID, DID, SSVID, SSDID} * is in the update image's Device IDs allowlist. * If the Device IDs allowlist (0x37) doesn't exist in the update image: * The update is accepted only if the card's SSVID and SSDID are zero. */ if (self->payload_type == GSC_FWU_HECI_PAYLOAD_TYPE_OPROM_DATA) { if (fu_igsc_oprom_firmware_has_allowlist(FU_IGSC_OPROM_FIRMWARE(firmware))) { if (!fu_igsc_oprom_firmware_match_device(FU_IGSC_OPROM_FIRMWARE(firmware), vendor_id, device_id, subsys_vendor_id, subsys_device_id, error)) return NULL; } else { if (subsys_vendor_id != 0x0 || subsys_device_id != 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware does not specify allowlist and SSVID " "and SSDID are nonzero"); return NULL; } } } /* success */ return g_steal_pointer(&firmware); } static gboolean fu_igsc_oprom_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuIgscOpromDevice *self = FU_IGSC_OPROM_DEVICE(device); FuIgscDevice *igsc_parent = FU_IGSC_DEVICE(fu_device_get_parent(device)); g_autoptr(GBytes) fw_payload = NULL; /* get image */ fw_payload = fu_firmware_get_bytes(firmware, error); if (fw_payload == NULL) return FALSE; /* OPROM image doesn't require metadata */ return fu_igsc_device_write_blob(igsc_parent, self->payload_type, NULL, fw_payload, progress, error); } static gboolean fu_igsc_aux_device_prepare(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { /* set PCI power policy */ return fu_device_prepare(fu_device_get_parent(device), progress, flags, error); } static gboolean fu_igsc_aux_device_cleanup(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { /* set PCI power policy */ return fu_device_cleanup(fu_device_get_parent(device), progress, flags, error); } static void fu_igsc_oprom_device_init(FuIgscOpromDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_add_protocol(FU_DEVICE(self), "com.intel.gsc"); } static void fu_igsc_oprom_device_class_init(FuIgscOpromDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_igsc_oprom_device_to_string; klass_device->probe = fu_igsc_oprom_device_probe; klass_device->setup = fu_igsc_oprom_device_setup; klass_device->prepare_firmware = fu_igsc_oprom_device_prepare_firmware; klass_device->write_firmware = fu_igsc_oprom_device_write_firmware; klass_device->prepare = fu_igsc_aux_device_prepare; klass_device->cleanup = fu_igsc_aux_device_cleanup; } FuIgscOpromDevice * fu_igsc_oprom_device_new(FuContext *ctx, enum gsc_fwu_heci_payload_type payload_type) { FuIgscOpromDevice *self = g_object_new(FU_TYPE_IGSC_OPROM_DEVICE, "context", ctx, NULL); self->payload_type = payload_type; return FU_IGSC_OPROM_DEVICE(self); } fwupd-1.9.16/plugins/intel-gsc/fu-igsc-oprom-device.h000066400000000000000000000007561460375044200224150ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel, Inc. * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR Apache-2.0 */ #pragma once #include #include "fu-igsc-heci.h" #define FU_TYPE_IGSC_OPROM_DEVICE (fu_igsc_oprom_device_get_type()) G_DECLARE_FINAL_TYPE(FuIgscOpromDevice, fu_igsc_oprom_device, FU, IGSC_OPROM_DEVICE, FuDevice) FuIgscOpromDevice * fu_igsc_oprom_device_new(FuContext *ctx, enum gsc_fwu_heci_payload_type payload_type); fwupd-1.9.16/plugins/intel-gsc/fu-igsc-oprom-firmware.c000066400000000000000000000165051460375044200227640ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-igsc-oprom-firmware.h" #include "fu-igsc-struct.h" struct _FuIgscOpromFirmware { FuOpromFirmware parent_instance; guint16 major_version; GPtrArray *device_infos; /* of FuIgscFwdataDeviceInfo */ }; typedef struct { guint16 vendor_id; /* may be 0x0 */ guint16 device_id; /* may be 0x0 */ guint16 subsys_vendor_id; guint16 subsys_device_id; } FuIgscFwdataDeviceInfo; G_DEFINE_TYPE(FuIgscOpromFirmware, fu_igsc_oprom_firmware, FU_TYPE_OPROM_FIRMWARE) static void fu_igsc_oprom_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuIgscOpromFirmware *self = FU_IGSC_OPROM_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "major_version", self->major_version); fu_xmlb_builder_insert_kx(bn, "device_infos", self->device_infos->len); } guint16 fu_igsc_oprom_firmware_get_major_version(FuIgscOpromFirmware *self) { g_return_val_if_fail(FU_IS_IGSC_OPROM_FIRMWARE(self), G_MAXUINT16); return self->major_version; } gboolean fu_igsc_oprom_firmware_has_allowlist(FuIgscOpromFirmware *self) { g_return_val_if_fail(FU_IS_IGSC_OPROM_FIRMWARE(self), FALSE); return self->device_infos->len > 0; } gboolean fu_igsc_oprom_firmware_match_device(FuIgscOpromFirmware *self, guint16 vendor_id, guint16 device_id, guint16 subsys_vendor_id, guint16 subsys_device_id, GError **error) { g_return_val_if_fail(FU_IS_IGSC_OPROM_FIRMWARE(self), FALSE); for (guint i = 0; i < self->device_infos->len; i++) { FuIgscFwdataDeviceInfo *info = g_ptr_array_index(self->device_infos, i); if (info->vendor_id == 0x0 && info->device_id == 0x0 && info->subsys_vendor_id == subsys_vendor_id && info->subsys_device_id == subsys_device_id) return TRUE; if (info->vendor_id == vendor_id && info->device_id == device_id && info->subsys_vendor_id == subsys_vendor_id && info->subsys_device_id == subsys_device_id) return TRUE; } /* not us */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "could not find 0x%04x:0x%04x 0x%04x:0x%04x in the image", vendor_id, device_id, subsys_vendor_id, subsys_device_id); return FALSE; } /* extension types */ #define MFT_EXT_TYPE_DEVICE_TYPE 7 #define MDF_EXT_TYPE_MODULE_ATTR 10 #define MFT_EXT_TYPE_SIGNED_PACKAGE_INFO 15 #define MFT_EXT_TYPE_IFWI_PART_MAN 22 #define MFT_EXT_TYPE_DEVICE_ID_ARRAY 55 static gboolean fu_igsc_oprom_firmware_parse_extension(FuIgscOpromFirmware *self, FuFirmware *fw, GError **error) { g_autoptr(GBytes) blob = NULL; /* get data */ blob = fu_firmware_get_bytes(fw, error); if (blob == NULL) return FALSE; if (fu_firmware_get_idx(fw) == MFT_EXT_TYPE_DEVICE_TYPE) { for (gsize offset = 0; offset < g_bytes_get_size(blob); offset += FU_STRUCT_IGSC_OPROM_SUBSYSTEM_DEVICE_ID_SIZE) { g_autofree FuIgscFwdataDeviceInfo *info = g_new0(FuIgscFwdataDeviceInfo, 1); g_autoptr(GByteArray) st = NULL; st = fu_struct_igsc_oprom_subsystem_device_id_parse_bytes(blob, offset, error); if (st == NULL) return FALSE; info->subsys_vendor_id = fu_struct_igsc_oprom_subsystem_device_id_get_subsys_vendor_id(st); info->subsys_device_id = fu_struct_igsc_oprom_subsystem_device_id_get_subsys_device_id(st); g_ptr_array_add(self->device_infos, g_steal_pointer(&info)); } } else if (fu_firmware_get_idx(fw) == MFT_EXT_TYPE_DEVICE_ID_ARRAY) { for (gsize offset = 0; offset < g_bytes_get_size(blob); offset += FU_STRUCT_IGSC_OPROM_SUBSYSTEM_DEVICE4_ID_SIZE) { g_autofree FuIgscFwdataDeviceInfo *info = g_new0(FuIgscFwdataDeviceInfo, 1); g_autoptr(GByteArray) st = NULL; st = fu_struct_igsc_oprom_subsystem_device4_id_parse_bytes(blob, offset, error); if (st == NULL) return FALSE; info->vendor_id = fu_struct_igsc_oprom_subsystem_device4_id_get_vendor_id(st); info->device_id = fu_struct_igsc_oprom_subsystem_device4_id_get_device_id(st); info->subsys_vendor_id = fu_struct_igsc_oprom_subsystem_device4_id_get_subsys_vendor_id(st); info->subsys_device_id = fu_struct_igsc_oprom_subsystem_device4_id_get_subsys_device_id(st); g_ptr_array_add(self->device_infos, g_steal_pointer(&info)); } } /* success */ return TRUE; } static gboolean fu_igsc_oprom_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuIgscOpromFirmware *self = FU_IGSC_OPROM_FIRMWARE(firmware); g_autoptr(FuFirmware) fw_cpd = NULL; g_autoptr(GPtrArray) cpd_imgs = NULL; /* FuOpromFirmware->parse */ if (!FU_FIRMWARE_CLASS(fu_igsc_oprom_firmware_parent_class) ->parse(firmware, fw, offset, flags, error)) return FALSE; /* check sanity */ if (fu_oprom_firmware_get_subsystem(FU_OPROM_FIRMWARE(firmware)) != FU_OPROM_FIRMWARE_SUBSYSTEM_EFI_BOOT_SRV_DRV) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid subsystem, got 0x%x, expected 0x%x", fu_oprom_firmware_get_subsystem(FU_OPROM_FIRMWARE(firmware)), (guint)FU_OPROM_FIRMWARE_SUBSYSTEM_EFI_BOOT_SRV_DRV); return FALSE; } if (fu_oprom_firmware_get_machine_type(FU_OPROM_FIRMWARE(firmware)) != FU_OPROM_FIRMWARE_MACHINE_TYPE_X64) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid machine type, got 0x%x, expected 0x%x", fu_oprom_firmware_get_machine_type(FU_OPROM_FIRMWARE(firmware)), (guint)FU_OPROM_FIRMWARE_MACHINE_TYPE_X64); return FALSE; } if (fu_oprom_firmware_get_compression_type(FU_OPROM_FIRMWARE(firmware)) != FU_OPROM_FIRMWARE_COMPRESSION_TYPE_NONE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid compression type, got 0x%x, expected 0x%x (uncompressed)", fu_oprom_firmware_get_compression_type(FU_OPROM_FIRMWARE(firmware)), (guint)FU_OPROM_FIRMWARE_COMPRESSION_TYPE_NONE); return FALSE; } /* get CPD */ fw_cpd = fu_firmware_get_image_by_id(firmware, "cpd", error); if (fw_cpd == NULL) return FALSE; if (!FU_IS_IFWI_CPD_FIRMWARE(fw_cpd)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "CPD was not FuIfwiCpdFirmware"); return FALSE; } /* parse all the manifest extensions */ cpd_imgs = fu_firmware_get_images(fw_cpd); for (guint i = 0; i < cpd_imgs->len; i++) { FuFirmware *img = g_ptr_array_index(cpd_imgs, i); if (!fu_igsc_oprom_firmware_parse_extension(self, img, error)) return FALSE; } /* success */ return TRUE; } static void fu_igsc_oprom_firmware_init(FuIgscOpromFirmware *self) { self->device_infos = g_ptr_array_new_with_free_func(g_free); } static void fu_igsc_oprom_firmware_finalize(GObject *object) { FuIgscOpromFirmware *self = FU_IGSC_OPROM_FIRMWARE(object); g_ptr_array_unref(self->device_infos); G_OBJECT_CLASS(fu_igsc_oprom_firmware_parent_class)->finalize(object); } static void fu_igsc_oprom_firmware_class_init(FuIgscOpromFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_igsc_oprom_firmware_finalize; klass_firmware->parse = fu_igsc_oprom_firmware_parse; klass_firmware->export = fu_igsc_oprom_firmware_export; } fwupd-1.9.16/plugins/intel-gsc/fu-igsc-oprom-firmware.h000066400000000000000000000013641460375044200227660ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_IGSC_OPROM_FIRMWARE (fu_igsc_oprom_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuIgscOpromFirmware, fu_igsc_oprom_firmware, FU, IGSC_OPROM_FIRMWARE, FuOpromFirmware) guint16 fu_igsc_oprom_firmware_get_major_version(FuIgscOpromFirmware *self); gboolean fu_igsc_oprom_firmware_has_allowlist(FuIgscOpromFirmware *self); gboolean fu_igsc_oprom_firmware_match_device(FuIgscOpromFirmware *self, guint16 vendor_id, guint16 device_id, guint16 subsys_vendor_id, guint16 subsys_device_id, GError **error); fwupd-1.9.16/plugins/intel-gsc/fu-igsc-plugin.c000066400000000000000000000020051460375044200213020ustar00rootroot00000000000000/* * Copyright (C) 2022 Intel * * SPDX-License-Identifier: LGPL-2.1+ OR Apache-2.0 */ #include "config.h" #include "fu-igsc-aux-firmware.h" #include "fu-igsc-code-firmware.h" #include "fu-igsc-device.h" #include "fu-igsc-oprom-firmware.h" #include "fu-igsc-plugin.h" struct _FuIgscPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuIgscPlugin, fu_igsc_plugin, FU_TYPE_PLUGIN) static void fu_igsc_plugin_init(FuIgscPlugin *self) { } static void fu_igsc_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "mei"); fu_plugin_add_device_gtype(plugin, FU_TYPE_IGSC_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_IGSC_CODE_FIRMWARE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_IGSC_AUX_FIRMWARE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_IGSC_OPROM_FIRMWARE); } static void fu_igsc_plugin_class_init(FuIgscPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_igsc_constructed; } fwupd-1.9.16/plugins/intel-gsc/fu-igsc-plugin.h000066400000000000000000000003421460375044200213110ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuIgscPlugin, fu_igsc_plugin, FU, IGSC_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/intel-gsc/fu-igsc.rs000066400000000000000000000031641460375044200202170ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(Parse)] struct IgscOpromVersion { major: u16le, minor: u16le, hotfix: u16le, build: u16le, } #[derive(New, Getters)] struct IgscFwVersion { project: [char; 4], // project code name hotfix: u16le, build: u16le, } #[derive(ParseBytes)] struct IgscOpromSubsystemDeviceId { subsys_vendor_id: u16le, subsys_device_id: u16le, } #[derive(ParseBytes)] struct IgscOpromSubsystemDevice4Id { vendor_id: u16le, device_id: u16le, subsys_vendor_id: u16le, subsys_device_id: u16le, } #[derive(ParseBytes)] struct IgscFwuGwsImageInfo { format_version: u32le == 0x1, instance_id: u32le, _reserved: [u32; 14], } /* represents a GSC FW sub-partition such as FTPR, RBEP */ #[derive(Getters)] struct IgscFwuFwImageData { version_major: u16le, version_minor: u16le, version_hotfix: u16le, version_build: u16le, flags: u16le, fw_type: u8, fw_sub_type: u8, arb_svn: u32le, tcb_svn: u32le, vcn: u32le, } #[derive(Getters)] struct IgscFwuIupData { iup_name: u32le, flags: u16le, _reserved: u16le, svn: u32le, vcn: u32le, } #[derive(Getters)] struct IgscFwuHeciImageMetadata { version_format: u32le = 0x1, } #[derive(ParseBytes)] struct IgscFwuImageMetadataV1 { version_format: u32le = 0x1, // struct IgscFwuHeciImageMetadata project: [char; 4], version_hotfix: u16, // version of the overall IFWI image, i.e. the combination of IPs version_build: u16, // struct IgscFwuFwImageData // struct IgscFwuIupData } fwupd-1.9.16/plugins/intel-gsc/igsc.quirk000066400000000000000000000047031460375044200203160ustar00rootroot00000000000000[87d90ca5-3495-4559-8105-3fbfa37b8b79] Plugin = igsc [MEI\VEN_8086&DEV_4905] Name = DG1 [MEI\VEN_8086&DEV_4906] Name = DG1 [MEI\VEN_8086&DEV_4907] Name = DG1 [MEI\VEN_8086&DEV_4908] Name = DG1 [MEI\VEN_8086&DEV_0201] Name = Xe_HP SDV [MEI\VEN_8086&DEV_0202] Name = Xe_HP SDV [MEI\VEN_8086&DEV_0203] Name = Xe_HP SDV [MEI\VEN_8086&DEV_0204] Name = Xe_HP SDV [MEI\VEN_8086&DEV_0205] Name = Xe_HP SDV [MEI\VEN_8086&DEV_0206] Name = Xe_HP SDV [MEI\VEN_8086&DEV_0207] Name = Xe_HP SDV [MEI\VEN_8086&DEV_0208] Name = Xe_HP SDV [MEI\VEN_8086&DEV_0209] Name = Xe_HP SDV [MEI\VEN_8086&DEV_020A] Name = Xe_HP SDV [MEI\VEN_8086&DEV_020B] Name = Xe_HP SDV [MEI\VEN_8086&DEV_020C] Name = Xe_HP SDV [MEI\VEN_8086&DEV_020D] Name = Xe_HP SDV [MEI\VEN_8086&DEV_020E] Name = Xe_HP SDV [MEI\VEN_8086&DEV_020F] Name = Xe_HP SDV [MEI\VEN_8086&DEV_0210] Name = Xe_HP SDV [MEI\VEN_8086&DEV_FF25] Name = Xe_HP SDV [MEI\VEN_8086&DEV_4F80] Name = DG2_SOC1 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_4F81] Name = DG2_SOC1 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_4F82] Name = DG2_SOC1 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_4F83] Name = DG2_SOC1 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_4F84] Name = DG2_SOC1 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_5690] Name = Arc A770M Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_5691] Name = Arc A730M Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_5692] Name = Arc A550M Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_56A0] Name = Arc A770 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_56A1] Name = Arc A750 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_56A2] Name = Arc A580 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_56C0] Name = DG2_SOC1 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_4F87] Name = DG2_SOC2 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_4F88] Name = DG2_SOC2 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_5693] Name = Arc A370M Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_5694] Name = Arc A350M Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_5695] Name = Iris(R) Xe MAX A200M Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_56A5] Name = Arc A380 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_56A6] Name = Arc A310 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_56A7] Name = DG2_SOC2 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_56A8] Name = DG2_SOC2 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_56A9] Name = DG2_SOC2 Flags = has-aux,has-oprom [MEI\VEN_8086&DEV_0BD0] Name = PVC [MEI\VEN_8086&DEV_0BD5] Name = PVC [MEI\VEN_8086&DEV_56C1] Name = ATS-M3 Flags = has-aux,has-oprom fwupd-1.9.16/plugins/intel-gsc/meson.build000066400000000000000000000012471460375044200204560ustar00rootroot00000000000000if get_option('plugin_igsc').require(gudev.found(), error_message: 'gudev is needed for plugin_igsc').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginIgsc"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('igsc.quirk') plugin_builtins += static_library('fu_plugin_igsc', rustgen.process('fu-igsc.rs'), sources: [ 'fu-igsc-plugin.c', 'fu-igsc-device.c', 'fu-igsc-code-firmware.c', 'fu-igsc-oprom-firmware.c', 'fu-igsc-aux-device.c', 'fu-igsc-aux-firmware.c', 'fu-igsc-oprom-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/intel-me/000077500000000000000000000000001460375044200161355ustar00rootroot00000000000000fwupd-1.9.16/plugins/intel-me/README.md000066400000000000000000000037141460375044200174210ustar00rootroot00000000000000--- title: Plugin: Intel ME --- ## Introduction This plugin is used to talk to the Intel ME device, typically CSME. It allows us to get the Platform Key as used for BootGuard and to get the version number for the Intel AMT. If AMT is enabled and provisioned and the AMT version is between 6.0 and 11.2, and you have not upgraded your firmware, you are vulnerable to CVE-2017-5689 and you should disable AMT in your system firmware. This code is inspired by 'AMT status checker for Linux' by Matthew Garrett which can be found here: That tool in turn is heavily based on mei-amt-version from samples/mei in the Linux source tree and copyright Intel Corporation. ## GUID Generation These devices use the existing GUIDs provided by the ME host interfaces. ## Metadata There have been several BootGuard key leaks that can be detected using the ME device. The metadata needed to match the KM checksum is found in the metadata, typically obtained from the LVFS project. This sets the `leaked-km` private flag which then causes the HSI `org.fwupd.hsi.Mei.KeyManifest` attribute to fail, and also adds a device inhibit which shows in the command line and GUI tools. The `org.linuxfoundation.bootguard.config` component is currently used to match against both the MCA and MKHI ME devices. The latest cabinet archive can also be installed into the `vendor-firmware` remote found in `/usr/share/fwupd/remotes.d/vendor/firmware/` which allows the detection to work even when offline -- although using the LVFS source is recommended for most users. New *OEM Public Key Hash* values found from `MEInfo` or calculated manually should be added to the checksums page on the LVFS. ## Vendor ID Security The devices are not upgradable and thus require no vendor ID set. ## External Interface Access This plugin requires `ioctl(IOCTL_MEI_CONNECT_CLIENT)` to `/dev/mei0`. ## Version Considerations This plugin has been available since fwupd version `1.8.7`. fwupd-1.9.16/plugins/intel-me/fu-intel-me-amt-device.c000066400000000000000000000256361460375044200224530ustar00rootroot00000000000000/* * Copyright (C) 2012 Intel Corporation. * Copyright (C) 2017 Google, Inc. * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-intel-me-amt-device.h" struct _FuIntelMeAmtDevice { FuMeiDevice parent_instance; }; G_DEFINE_TYPE(FuIntelMeAmtDevice, fu_intel_me_amt_device, FU_TYPE_MEI_DEVICE) #define AMT_MAJOR_VERSION 1 #define AMT_MINOR_VERSION 1 #define AMT_STATUS_SUCCESS 0x0 #define AMT_STATUS_INTERNAL_ERROR 0x1 #define AMT_STATUS_NOT_READY 0x2 #define AMT_STATUS_INVALID_AMT_MODE 0x3 #define AMT_STATUS_INVALID_MESSAGE_LENGTH 0x4 #define AMT_STATUS_HOST_IF_EMPTY_RESPONSE 0x4000 #define AMT_STATUS_SDK_RESOURCES 0x1004 #define AMT_BIOS_VERSION_LEN 65 #define AMT_VERSIONS_NUMBER 50 #define AMT_UNICODE_STRING_LEN 20 struct amt_unicode_string { guint16 length; char string[AMT_UNICODE_STRING_LEN]; } __attribute__((packed)); struct amt_version_type { struct amt_unicode_string description; struct amt_unicode_string version; } __attribute__((packed)); struct amt_version { guint8 major; guint8 minor; } __attribute__((packed)); struct amt_code_versions { guint8 bios[AMT_BIOS_VERSION_LEN]; guint32 count; struct amt_version_type versions[AMT_VERSIONS_NUMBER]; } __attribute__((packed)); struct amt_provisioning_state { guint8 bios[AMT_BIOS_VERSION_LEN]; guint32 count; guint8 state; } __attribute__((packed)); /*************************************************************************** * Intel Advanced Management Technology Host Interface ***************************************************************************/ struct amt_host_if_msg_header { struct amt_version version; guint16 _reserved; guint32 command; guint32 length; } __attribute__((packed)); struct amt_host_if_resp_header { struct amt_host_if_msg_header header; guint32 status; guchar data[0]; } __attribute__((packed)); #define AMT_HOST_IF_CODE_VERSIONS_REQUEST 0x0400001A #define AMT_HOST_IF_CODE_VERSIONS_RESPONSE 0x0480001A const struct amt_host_if_msg_header CODE_VERSION_REQ = { .version = {AMT_MAJOR_VERSION, AMT_MINOR_VERSION}, ._reserved = 0, .command = AMT_HOST_IF_CODE_VERSIONS_REQUEST, .length = 0}; #define AMT_HOST_IF_PROVISIONING_MODE_REQUEST 0x04000008 #define AMT_HOST_IF_PROVISIONING_MODE_RESPONSE 0x04800008 const struct amt_host_if_msg_header PROVISIONING_MODE_REQUEST = { .version = {AMT_MAJOR_VERSION, AMT_MINOR_VERSION}, ._reserved = 0, .command = AMT_HOST_IF_PROVISIONING_MODE_REQUEST, .length = 0}; #define AMT_HOST_IF_PROVISIONING_STATE_REQUEST 0x04000011 #define AMT_HOST_IF_PROVISIONING_STATE_RESPONSE 0x04800011 const struct amt_host_if_msg_header PROVISIONING_STATE_REQUEST = { .version = {AMT_MAJOR_VERSION, AMT_MINOR_VERSION}, ._reserved = 0, .command = AMT_HOST_IF_PROVISIONING_STATE_REQUEST, .length = 0}; struct amt_host_if { FuIntelMeAmtDevice self; }; static gboolean fu_intel_me_amt_device_verify_code_versions(const struct amt_host_if_resp_header *resp, GError **error) { struct amt_code_versions *code_ver = (struct amt_code_versions *)resp->data; gsize code_ver_len = resp->header.length - sizeof(guint32); guint32 ver_type_cnt = code_ver_len - sizeof(code_ver->bios) - sizeof(code_ver->count); if (code_ver->count != ver_type_cnt / sizeof(struct amt_version_type)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid offset"); return FALSE; } for (guint32 i = 0; i < code_ver->count; i++) { guint32 len = code_ver->versions[i].description.length; if (len > AMT_UNICODE_STRING_LEN) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "string too large"); return FALSE; } len = code_ver->versions[i].version.length; if (code_ver->versions[i].version.string[len] != '\0' || len != strlen(code_ver->versions[i].version.string)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "string was invalid size"); return FALSE; } } return TRUE; } static gboolean fu_intel_me_amt_device_status_set_error(guint32 status, GError **error) { if (status == AMT_STATUS_SUCCESS) return TRUE; if (status == AMT_STATUS_INTERNAL_ERROR) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "internal error"); return FALSE; } if (status == AMT_STATUS_NOT_READY) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "not ready"); return FALSE; } if (status == AMT_STATUS_INVALID_AMT_MODE) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid AMT mode"); return FALSE; } if (status == AMT_STATUS_INVALID_MESSAGE_LENGTH) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid message length"); return FALSE; } if (status == AMT_STATUS_HOST_IF_EMPTY_RESPONSE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Intel AMT is disabled"); return FALSE; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unknown error"); return FALSE; } static gboolean fu_intel_me_amt_device_host_if_call(FuIntelMeAmtDevice *self, const guchar *command, gssize command_sz, guint8 **read_buf, guint32 rcmd, guint expected_sz, unsigned long send_timeout, GError **error) { gsize in_buf_sz = fu_mei_device_get_max_msg_length(FU_MEI_DEVICE(self)); gsize out_buf_sz; struct amt_host_if_resp_header *msg_hdr; *read_buf = (guint8 *)g_malloc0(in_buf_sz); msg_hdr = (struct amt_host_if_resp_header *)*read_buf; if (!fu_mei_device_write(FU_MEI_DEVICE(self), command, command_sz, send_timeout, error)) return FALSE; if (!fu_mei_device_read(FU_MEI_DEVICE(self), *read_buf, in_buf_sz, &out_buf_sz, 2000, error)) return FALSE; if (out_buf_sz <= 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "empty response"); return FALSE; } if (expected_sz && expected_sz != out_buf_sz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "expected %u but got %u", (guint)expected_sz, (guint)out_buf_sz); return FALSE; } if (!fu_intel_me_amt_device_status_set_error(msg_hdr->status, error)) return FALSE; if (out_buf_sz < sizeof(struct amt_host_if_resp_header)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "invalid response: too small"); return FALSE; } if (out_buf_sz != (msg_hdr->header.length + sizeof(struct amt_host_if_msg_header))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "invalid response: headerlen"); return FALSE; } if (msg_hdr->header.command != rcmd) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "invalid response: rcmd"); return FALSE; } if (msg_hdr->header._reserved != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "invalid response: reserved"); return FALSE; } if (msg_hdr->header.version.major != AMT_MAJOR_VERSION || msg_hdr->header.version.minor < AMT_MINOR_VERSION) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "invalid response: version"); return FALSE; } return TRUE; } static gboolean fu_intel_me_amt_device_get_provisioning_state(FuIntelMeAmtDevice *self, guint8 *state, GError **error) { g_autofree struct amt_host_if_resp_header *response = NULL; if (!fu_intel_me_amt_device_host_if_call(self, (const guchar *)&PROVISIONING_STATE_REQUEST, sizeof(PROVISIONING_STATE_REQUEST), (guint8 **)&response, AMT_HOST_IF_PROVISIONING_STATE_RESPONSE, 0, 5000, error)) { g_prefix_error(error, "unable to get provisioning state: "); return FALSE; } *state = response->data[0]; return TRUE; } static gboolean fu_intel_me_amt_device_open(FuDevice *device, GError **error) { /* open then create context */ if (!FU_DEVICE_CLASS(fu_intel_me_amt_device_parent_class)->open(device, error)) return FALSE; return fu_mei_device_connect(FU_MEI_DEVICE(device), 0, error); } static gboolean fu_intel_me_amt_device_setup(FuDevice *device, GError **error) { FuIntelMeAmtDevice *self = FU_INTEL_ME_AMT_DEVICE(device); guint8 state; struct amt_code_versions ver; g_autofree struct amt_host_if_resp_header *response = NULL; g_autoptr(GString) version_bl = g_string_new(NULL); g_autoptr(GString) version_fw = g_string_new(NULL); /* check version */ if (!fu_intel_me_amt_device_host_if_call(self, (const guchar *)&CODE_VERSION_REQ, sizeof(CODE_VERSION_REQ), (guint8 **)&response, AMT_HOST_IF_CODE_VERSIONS_RESPONSE, 0, 5000, error)) { g_prefix_error(error, "Failed to check version: "); return FALSE; } if (!fu_intel_me_amt_device_verify_code_versions(response, error)) { g_prefix_error(error, "failed to verify code versions: "); return FALSE; } memcpy(&ver, response->data, sizeof(struct amt_code_versions)); if (!fu_intel_me_amt_device_get_provisioning_state(self, &state, error)) return FALSE; switch (state) { case 0: fu_device_set_name(device, "AMT [unprovisioned]"); break; case 1: fu_device_set_name(device, "AMT [being provisioned]"); break; case 2: fu_device_set_name(device, "AMT [provisioned]"); break; default: fu_device_set_name(device, "AMT [unknown]"); break; } /* get version numbers */ for (guint i = 0; i < ver.count; i++) { if (g_strcmp0(ver.versions[i].description.string, "AMT") == 0) { g_string_append(version_fw, ver.versions[i].version.string); continue; } if (g_strcmp0(ver.versions[i].description.string, "Recovery Version") == 0) { g_string_append(version_bl, ver.versions[i].version.string); continue; } if (g_strcmp0(ver.versions[i].description.string, "Build Number") == 0) { g_string_append_printf(version_fw, ".%s", ver.versions[i].version.string); continue; } if (g_strcmp0(ver.versions[i].description.string, "Recovery Build Num") == 0) { g_string_append_printf(version_bl, ".%s", ver.versions[i].version.string); continue; } } if (version_fw->len > 0) fu_device_set_version(device, version_fw->str); if (version_bl->len > 0) fu_device_set_version_bootloader(device, version_bl->str); /* success */ return TRUE; } static void fu_intel_me_amt_device_init(FuIntelMeAmtDevice *self) { fu_device_set_logical_id(FU_DEVICE(self), "AMT"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_INTEL_ME); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE_CHILD); fu_device_add_icon(FU_DEVICE(self), "computer"); fu_device_set_summary(FU_DEVICE(self), "Hardware and firmware technology for remote " "out-of-band management"); } static void fu_intel_me_amt_device_class_init(FuIntelMeAmtDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->open = fu_intel_me_amt_device_open; klass_device->setup = fu_intel_me_amt_device_setup; } fwupd-1.9.16/plugins/intel-me/fu-intel-me-amt-device.h000066400000000000000000000005371460375044200224510ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_INTEL_ME_AMT_DEVICE (fu_intel_me_amt_device_get_type()) G_DECLARE_FINAL_TYPE(FuIntelMeAmtDevice, fu_intel_me_amt_device, FU, INTEL_ME_AMT_DEVICE, FuMeiDevice) fwupd-1.9.16/plugins/intel-me/fu-intel-me-common.c000066400000000000000000000046241460375044200217170ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-intel-me-common.h" static gboolean fu_intel_me_mkhi_result_to_error(FuMkhiResult result, GError **error) { if (result == MKHI_STATUS_SUCCESS) return TRUE; switch (result) { case MKHI_STATUS_NOT_SUPPORTED: case MKHI_STATUS_NOT_AVAILABLE: case MKHI_STATUS_NOT_SET: g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "not supported [0x%x]", result); break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "generic failure [0x%x]", result); break; } return FALSE; } gboolean fu_intel_me_mkhi_verify_header(const FuMkhiHeader *hdr_req, const FuMkhiHeader *hdr_res, GError **error) { if (hdr_req->group_id != hdr_res->group_id) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid response group ID, requested 0x%x and got 0x%x", hdr_req->group_id, hdr_res->group_id); return FALSE; } if (hdr_req->command != hdr_res->command) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid response command, requested 0x%x and got 0x%x", (guint)hdr_req->command, (guint)hdr_res->command); return FALSE; } if (!hdr_res->is_resp) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid response group ID, not a response!"); return FALSE; } return fu_intel_me_mkhi_result_to_error(hdr_res->result, error); } GString * fu_intel_me_convert_checksum(GByteArray *buf, GError **error) { gboolean seen_non00_data = FALSE; gboolean seen_nonff_data = FALSE; g_autoptr(GString) checksum = g_string_new(NULL); /* create checksum, but only if non-zero and set */ for (gsize i = 0; i < buf->len; i++) { if (!seen_non00_data && buf->data[i] != 0x00) seen_non00_data = TRUE; if (!seen_nonff_data && buf->data[i] != 0xFF) seen_nonff_data = TRUE; g_string_append_printf(checksum, "%02x", buf->data[i]); } if (!seen_non00_data) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, "buffer was all 0x00"); return NULL; } if (!seen_nonff_data) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, "buffer was all 0xFF"); return NULL; } /* success */ return g_steal_pointer(&checksum); } fwupd-1.9.16/plugins/intel-me/fu-intel-me-common.h000066400000000000000000000030761460375044200217240ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef struct __attribute__((packed)) { guint8 group_id; guint8 command : 7; guint8 is_resp : 1; guint8 rsvd; guint8 result; } FuMkhiHeader; typedef enum { MKHI_GROUP_ID_CBM, MKHI_GROUP_ID_PM, /* no longer used */ MKHI_GROUP_ID_PWD, MKHI_GROUP_ID_FWCAPS, MKHI_GROUP_ID_APP, /* no longer used */ MKHI_GROUP_ID_FWUPDATE, /* for manufacturing downgrade */ MKHI_GROUP_ID_FIRMWARE_UPDATE, MKHI_GROUP_ID_BIST, MKHI_GROUP_ID_MDES, MKHI_GROUP_ID_ME_DBG, MKHI_GROUP_ID_MCA, /* sometimes called "FPF" */ MKHI_GROUP_ID_GEN = 0xFF } FuMkhiGroupId; #define MCA_READ_FILE_EX 0x02 #define MCA_READ_FILE_EX_CMD 0x0A typedef enum { MKHI_STATUS_SUCCESS, MKHI_STATUS_INVALID_STATE, MKHI_STATUS_MESSAGE_SKIPPED, MKHI_STATUS_SIZE_ERROR = 0x05, MKHI_STATUS_NOT_SET = 0x0F, /* guessed */ MKHI_STATUS_NOT_AVAILABLE = 0x18, /* guessed */ MKHI_STATUS_INVALID_ACCESS = 0x84, MKHI_STATUS_INVALID_PARAMS = 0x85, MKHI_STATUS_NOT_READY = 0x88, MKHI_STATUS_NOT_SUPPORTED = 0x89, MKHI_STATUS_INVALID_ADDRESS = 0x8C, MKHI_STATUS_INVALID_COMMAND = 0x8D, MKHI_STATUS_FAILURE = 0x9E, MKHI_STATUS_INVALID_RESOURCE = 0xE4, MKHI_STATUS_RESOURCE_IN_USE = 0xE5, MKHI_STATUS_NO_RESOURCE = 0xE6, MKHI_STATUS_GENERAL_ERROR = 0xFF } FuMkhiResult; GString * fu_intel_me_convert_checksum(GByteArray *buf, GError **error); gboolean fu_intel_me_mkhi_verify_header(const FuMkhiHeader *hdr_req, const FuMkhiHeader *hdr_res, GError **error); fwupd-1.9.16/plugins/intel-me/fu-intel-me-heci-device.c000066400000000000000000000136531460375044200225760ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-intel-me-common.h" #include "fu-intel-me-heci-device.h" G_DEFINE_TYPE(FuIntelMeHeciDevice, fu_intel_me_heci_device, FU_TYPE_MEI_DEVICE) #define FU_INTEL_ME_HECI_DEVICE_TIMEOUT 200 /* ms */ static gboolean fu_intel_me_heci_device_open(FuDevice *device, GError **error) { /* open then create context */ if (!FU_DEVICE_CLASS(fu_intel_me_heci_device_parent_class)->open(device, error)) return FALSE; return fu_mei_device_connect(FU_MEI_DEVICE(device), 0, error); } static GByteArray * fu_intel_me_heci_device_read_file_response(GByteArray *buf_res, guint32 datasz_req, GError **error) { guint32 datasz_res = 0; g_autoptr(GByteArray) buf = g_byte_array_new(); /* verify payload size */ if (!fu_memread_uint32_safe(buf_res->data, buf_res->len, sizeof(FuMkhiHeader), &datasz_res, G_LITTLE_ENDIAN, error)) return NULL; if (datasz_res > datasz_req || datasz_res == 0x0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid response data size, requested 0x%x and got 0x%x", datasz_req, datasz_res); return NULL; } /* copy out payload */ for (gsize i = 0; i < datasz_res; i++) { guint8 tmp = 0; if (!fu_memread_uint8_safe(buf_res->data, buf_res->len, sizeof(FuMkhiHeader) + sizeof(guint32) + i, &tmp, error)) return NULL; fu_byte_array_append_uint8(buf, tmp); } /* success */ return g_steal_pointer(&buf); } GByteArray * fu_intel_me_heci_device_read_file(FuIntelMeHeciDevice *self, const gchar *filename, GError **error) { FuMkhiHeader hdr_res = {0}; gsize filenamesz = strlen(filename); guint datasz_req = 0x80; g_autoptr(GByteArray) buf_fn = g_byte_array_new(); g_autoptr(GByteArray) buf_req = g_byte_array_new(); g_autoptr(GByteArray) buf_res = g_byte_array_new(); const FuMkhiHeader hdr_req = {.group_id = MKHI_GROUP_ID_MCA, .command = MCA_READ_FILE_EX}; /* filename must be a specific size */ fu_byte_array_set_size(buf_fn, 0x40, 0x0); if (!fu_memcpy_safe(buf_fn->data, buf_fn->len - 1, 0x0, (const guint8 *)filename, filenamesz, 0x0, filenamesz, error)) return NULL; /* request */ g_byte_array_append(buf_req, (const guint8 *)&hdr_req, sizeof(hdr_req)); g_byte_array_append(buf_req, buf_fn->data, buf_fn->len); /* Filename */ fu_byte_array_append_uint32(buf_req, 0x0, G_LITTLE_ENDIAN); /* Offset */ fu_byte_array_append_uint32(buf_req, datasz_req, G_LITTLE_ENDIAN); /* DataSize */ fu_byte_array_append_uint8(buf_req, (1 << 3)); /* Flags?? */ if (!fu_mei_device_write(FU_MEI_DEVICE(self), buf_req->data, buf_req->len, FU_INTEL_ME_HECI_DEVICE_TIMEOUT, error)) return NULL; /* response */ fu_byte_array_set_size(buf_res, sizeof(hdr_res) + sizeof(guint32) + datasz_req, 0x0); if (!fu_mei_device_read(FU_MEI_DEVICE(self), buf_res->data, buf_res->len, NULL, FU_INTEL_ME_HECI_DEVICE_TIMEOUT, error)) return NULL; /* verify header */ if (!fu_memcpy_safe((guint8 *)&hdr_res, sizeof(hdr_res), 0x0, /* dst */ buf_res->data, buf_res->len, 0x0, /* src */ sizeof(hdr_req), error)) return NULL; if (!fu_intel_me_mkhi_verify_header(&hdr_req, &hdr_res, error)) return NULL; return fu_intel_me_heci_device_read_file_response(buf_res, datasz_req, error); } GByteArray * fu_intel_me_heci_device_read_file_ex(FuIntelMeHeciDevice *self, guint32 file_id, guint32 section, guint32 datasz_req, GError **error) { FuMkhiHeader hdr_res = {0}; g_autoptr(GByteArray) buf_req = g_byte_array_new(); g_autoptr(GByteArray) buf_res = g_byte_array_new(); const FuMkhiHeader hdr_req = {.group_id = MKHI_GROUP_ID_MCA, .command = MCA_READ_FILE_EX_CMD}; /* request */ g_byte_array_append(buf_req, (const guint8 *)&hdr_req, sizeof(hdr_req)); fu_byte_array_append_uint32(buf_req, file_id, G_LITTLE_ENDIAN); /* FileId */ fu_byte_array_append_uint32(buf_req, 0x0, G_LITTLE_ENDIAN); /* Offset */ fu_byte_array_append_uint32(buf_req, datasz_req, G_LITTLE_ENDIAN); /* DataSize */ fu_byte_array_append_uint8(buf_req, section); /* Flags */ if (!fu_mei_device_write(FU_MEI_DEVICE(self), buf_req->data, buf_req->len, FU_INTEL_ME_HECI_DEVICE_TIMEOUT, error)) return NULL; /* response */ fu_byte_array_set_size(buf_res, sizeof(hdr_res) + sizeof(guint32) + datasz_req, 0x0); if (!fu_mei_device_read(FU_MEI_DEVICE(self), buf_res->data, buf_res->len, NULL, FU_INTEL_ME_HECI_DEVICE_TIMEOUT, error)) return NULL; /* verify header */ if (!fu_memcpy_safe((guint8 *)&hdr_res, sizeof(hdr_res), 0x0, /* dst */ buf_res->data, buf_res->len, 0x0, /* src */ sizeof(hdr_req), error)) return NULL; if (!fu_intel_me_mkhi_verify_header(&hdr_req, &hdr_res, error)) return NULL; return fu_intel_me_heci_device_read_file_response(buf_res, datasz_req, error); } static void fu_intel_me_heci_device_version_notify_cb(FuDevice *device, GParamSpec *pspec, gpointer user_data) { if (fu_device_has_private_flag(device, FU_INTEL_ME_HECI_DEVICE_FLAG_LEAKED_KM)) fu_device_inhibit(device, "leaked-km", "Provisioned with a leaked private key"); } static void fu_intel_me_heci_device_init(FuIntelMeHeciDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_icon(FU_DEVICE(self), "computer"); fu_device_register_private_flag(FU_DEVICE(self), FU_INTEL_ME_HECI_DEVICE_FLAG_LEAKED_KM, "leaked-km"); g_signal_connect(FWUPD_DEVICE(self), "notify::private-flags", G_CALLBACK(fu_intel_me_heci_device_version_notify_cb), NULL); } static void fu_intel_me_heci_device_class_init(FuIntelMeHeciDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->open = fu_intel_me_heci_device_open; } fwupd-1.9.16/plugins/intel-me/fu-intel-me-heci-device.h000066400000000000000000000014071460375044200225750ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_INTEL_ME_HECI_DEVICE (fu_intel_me_heci_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuIntelMeHeciDevice, fu_intel_me_heci_device, FU, INTEL_ME_HECI_DEVICE, FuMeiDevice) struct _FuIntelMeHeciDeviceClass { FuMeiDeviceClass parent_class; }; #define FU_INTEL_ME_HECI_DEVICE_FLAG_LEAKED_KM (1 << 0) GByteArray * fu_intel_me_heci_device_read_file(FuIntelMeHeciDevice *self, const gchar *filename, GError **error); GByteArray * fu_intel_me_heci_device_read_file_ex(FuIntelMeHeciDevice *self, guint32 file_id, guint32 section, guint32 datasz_req, GError **error); fwupd-1.9.16/plugins/intel-me/fu-intel-me-mca-device.c000066400000000000000000000077271460375044200224330ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-intel-me-common.h" #include "fu-intel-me-mca-device.h" struct _FuIntelMeMcaDevice { FuIntelMeHeciDevice parent_instance; }; G_DEFINE_TYPE(FuIntelMeMcaDevice, fu_intel_me_mca_device, FU_TYPE_INTEL_ME_HECI_DEVICE) static gboolean fu_intel_me_mca_device_add_checksum_for_id(FuIntelMeMcaDevice *self, guint32 file_id, guint32 section, GError **error) { g_autoptr(GByteArray) buf = NULL; g_autoptr(GString) checksum = NULL; /* * Call READ_FILE_EX with a larger-than-required data size -- which hopefully works when * SHA512 results start being returned too. * * Icelake/Jasperlake/Cometlake: 0x20 (SHA256) * Elkhartlake/Tigerlake/Alderlake/Raptorlake: 0x30 (SHA384) */ buf = fu_intel_me_heci_device_read_file_ex(FU_INTEL_ME_HECI_DEVICE(self), file_id, section, 0x40, error); if (buf == NULL) return FALSE; /* convert into checksum, but only if non-zero and set */ checksum = fu_intel_me_convert_checksum(buf, error); if (checksum == NULL) return FALSE; fu_device_add_checksum(FU_DEVICE(self), checksum->str); /* success */ return TRUE; } static gboolean fu_intel_me_mca_device_setup(FuDevice *device, GError **error) { FuIntelMeMcaDevice *self = FU_INTEL_ME_MCA_DEVICE(device); const guint32 file_ids[] = {0x40002300, /* CometLake: OEM Public Key Hash */ 0x40005B00, /* TigerLake: 1st OEM Public Key Hash */ 0x40005C00 /* TigerLake: 2nd OEM Public Key Hash */, G_MAXUINT32}; /* look for all the possible OEM Public Key hashes using the CML+ method */ for (guint i = 0; file_ids[i] != G_MAXUINT32; i++) { g_autoptr(GError) error_local = NULL; if (!fu_intel_me_mca_device_add_checksum_for_id(self, file_ids[i], 0x0, &error_local)) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED) || g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED)) { continue; } g_warning("failed to get public key using file-id 0x%x: %s", file_ids[i], error_local->message); } } /* no point even adding */ if (fu_device_get_checksums(device)->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no OEM public keys found"); return FALSE; } /* success */ return TRUE; } static void fu_intel_me_mca_device_add_security_attrs(FuDevice *device, FuSecurityAttrs *attrs) { FuIntelMeMcaDevice *self = FU_INTEL_ME_MCA_DEVICE(device); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_device_security_attr_new(FU_DEVICE(self), FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); fu_security_attrs_append(attrs, attr); /* verify keys */ if (fu_device_get_checksums(device)->len == 0) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); return; } if (fu_device_has_private_flag(device, FU_INTEL_ME_HECI_DEVICE_FLAG_LEAKED_KM)) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_intel_me_mca_device_init(FuIntelMeMcaDevice *self) { fu_device_set_logical_id(FU_DEVICE(self), "MCA"); fu_device_set_name(FU_DEVICE(self), "BootGuard Configuration"); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE_CHILD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_ONLY_CHECKSUM); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS); } static void fu_intel_me_mca_device_class_init(FuIntelMeMcaDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_intel_me_mca_device_setup; klass_device->add_security_attrs = fu_intel_me_mca_device_add_security_attrs; } fwupd-1.9.16/plugins/intel-me/fu-intel-me-mca-device.h000066400000000000000000000006151460375044200224250ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-intel-me-heci-device.h" #define FU_TYPE_INTEL_ME_MCA_DEVICE (fu_intel_me_mca_device_get_type()) G_DECLARE_FINAL_TYPE(FuIntelMeMcaDevice, fu_intel_me_mca_device, FU, INTEL_ME_MCA_DEVICE, FuIntelMeHeciDevice) fwupd-1.9.16/plugins/intel-me/fu-intel-me-mkhi-device.c000066400000000000000000000050461460375044200226130ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-intel-me-common.h" #include "fu-intel-me-mkhi-device.h" struct _FuIntelMeMkhiDevice { FuIntelMeHeciDevice parent_instance; }; G_DEFINE_TYPE(FuIntelMeMkhiDevice, fu_intel_me_mkhi_device, FU_TYPE_INTEL_ME_HECI_DEVICE) static gboolean fu_intel_me_mkhi_device_add_checksum_for_filename(FuIntelMeMkhiDevice *self, const gchar *filename, GError **error) { g_autoptr(GByteArray) buf = NULL; g_autoptr(GString) checksum = NULL; /* read from the MFS */ buf = fu_intel_me_heci_device_read_file(FU_INTEL_ME_HECI_DEVICE(self), filename, error); if (buf == NULL) return FALSE; /* convert into checksum, but only if non-zero and set */ checksum = fu_intel_me_convert_checksum(buf, error); if (checksum == NULL) return FALSE; fu_device_add_checksum(FU_DEVICE(self), checksum->str); /* success */ return TRUE; } static gboolean fu_intel_me_mkhi_device_setup(FuDevice *device, GError **error) { FuIntelMeMkhiDevice *self = FU_INTEL_ME_MKHI_DEVICE(device); const gchar *fns[] = {"/fpf/OemCred", NULL}; /* this is the legacy way to get the hash, which is removed in newer ME versions due to * possible path traversal attacks */ for (guint i = 0; fns[i] != NULL; i++) { g_autoptr(GError) error_local = NULL; if (!fu_intel_me_mkhi_device_add_checksum_for_filename(self, fns[i], &error_local)) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { continue; } g_warning("failed to get public key using %s: %s", fns[i], error_local->message); } } /* no point even adding */ if (fu_device_get_checksums(device)->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no OEM public keys found"); return FALSE; } /* success */ return TRUE; } static void fu_intel_me_mkhi_device_init(FuIntelMeMkhiDevice *self) { fu_device_set_logical_id(FU_DEVICE(self), "MKHI"); fu_device_set_name(FU_DEVICE(self), "BootGuard Configuration"); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE_CHILD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_ONLY_CHECKSUM); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS); } static void fu_intel_me_mkhi_device_class_init(FuIntelMeMkhiDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_intel_me_mkhi_device_setup; } fwupd-1.9.16/plugins/intel-me/fu-intel-me-mkhi-device.h000066400000000000000000000006221460375044200226130ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-intel-me-heci-device.h" #define FU_TYPE_INTEL_ME_MKHI_DEVICE (fu_intel_me_mkhi_device_get_type()) G_DECLARE_FINAL_TYPE(FuIntelMeMkhiDevice, fu_intel_me_mkhi_device, FU, INTEL_ME_MKHI_DEVICE, FuIntelMeHeciDevice) fwupd-1.9.16/plugins/intel-me/fu-intel-me-plugin.c000066400000000000000000000017421460375044200217230ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-intel-me-amt-device.h" #include "fu-intel-me-mca-device.h" #include "fu-intel-me-mkhi-device.h" #include "fu-intel-me-plugin.h" struct _FuIntelMePlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuIntelMePlugin, fu_intel_me_plugin, FU_TYPE_PLUGIN) static void fu_intel_me_plugin_init(FuIntelMePlugin *self) { } static void fu_intel_me_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "mei"); fu_plugin_add_device_gtype(plugin, FU_TYPE_INTEL_ME_AMT_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_INTEL_ME_MCA_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_INTEL_ME_MKHI_DEVICE); } static void fu_intel_me_plugin_class_init(FuIntelMePluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_intel_me_plugin_constructed; } fwupd-1.9.16/plugins/intel-me/fu-intel-me-plugin.h000066400000000000000000000003551460375044200217270ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuIntelMePlugin, fu_intel_me_plugin, FU, INTEL_ME_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/intel-me/intel-me.quirk000066400000000000000000000004721460375044200207270ustar00rootroot00000000000000# PTHI client (via the HECI device) [12f80028-b4b7-4b2d-aca8-46e0ff65814c] Plugin = intel_me GType = FuIntelMeAmtDevice # MKHI (legacy) [8e6a6715-9abc-4043-88ef-9e39c6f63e0f] Plugin = intel_me GType = FuIntelMeMkhiDevice # MCA? [dd17041c-09ea-4b17-a271-5b989867ec65] Plugin = intel_me GType = FuIntelMeMcaDevice fwupd-1.9.16/plugins/intel-me/meson.build000066400000000000000000000011541460375044200203000ustar00rootroot00000000000000if get_option('plugin_intel_me').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginIntelMe"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('intel-me.quirk') plugin_builtins += static_library('fu_plugin_intel_me', sources: [ 'fu-intel-me-common.c', 'fu-intel-me-plugin.c', 'fu-intel-me-amt-device.c', 'fu-intel-me-heci-device.c', 'fu-intel-me-mca-device.c', 'fu-intel-me-mkhi-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/intel-spi/000077500000000000000000000000001460375044200163275ustar00rootroot00000000000000fwupd-1.9.16/plugins/intel-spi/README.md000066400000000000000000000006271460375044200176130ustar00rootroot00000000000000--- title: Plugin: Intel SPI --- ## Introduction This plugin verifies the SPI contents, typically an Intel Flash descriptor. The result will be stored in an security attribute for HSI. ## External Interface Access This plugin requires read access to `/dev/port` and thus will not work if the kernel is locked down. ## Version Considerations This plugin has been available since fwupd version `1.6.0`. fwupd-1.9.16/plugins/intel-spi/fu-ifd-device.c000066400000000000000000000077241460375044200211140ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-ifd-device.h" #include "fu-intel-spi-device.h" typedef struct { FuIfdRegion region; guint32 offset; FuIfdAccess access[FU_IFD_REGION_MAX]; } FuIfdDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuIfdDevice, fu_ifd_device, FU_TYPE_DEVICE) #define GET_PRIVATE(o) (fu_ifd_device_get_instance_private(o)) static void fu_ifd_device_set_region(FuIfdDevice *self, FuIfdRegion region) { FuIfdDevicePrivate *priv = GET_PRIVATE(self); const gchar *region_str = fu_ifd_region_to_string(region); priv->region = region; fu_device_set_name(FU_DEVICE(self), fu_ifd_region_to_name(region)); fu_device_set_logical_id(FU_DEVICE(self), region_str); /* add instance ID */ fu_device_add_instance_strup(FU_DEVICE(self), "NAME", region_str); fu_device_build_instance_id(FU_DEVICE(self), NULL, "IFD", "NAME", NULL); } static void fu_ifd_device_set_freg(FuIfdDevice *self, guint32 freg) { FuIfdDevicePrivate *priv = GET_PRIVATE(self); guint32 freg_base = FU_IFD_FREG_BASE(freg); guint32 freg_limt = FU_IFD_FREG_LIMIT(freg); guint32 freg_size = (freg_limt - freg_base) + 1; priv->offset = freg_base; fu_device_set_firmware_size(FU_DEVICE(self), freg_size); } void fu_ifd_device_set_access(FuIfdDevice *self, FuIfdRegion region, FuIfdAccess access) { FuIfdDevicePrivate *priv = GET_PRIVATE(self); priv->access[region] = access; } static void fu_ifd_device_to_string(FuDevice *device, guint idt, GString *str) { FuIfdDevice *self = FU_IFD_DEVICE(device); FuIfdDevicePrivate *priv = GET_PRIVATE(self); fu_string_append(str, idt, "Region", fu_ifd_region_to_string(priv->region)); fu_string_append_kx(str, idt, "Offset", priv->offset); for (guint i = 0; i < FU_IFD_REGION_MAX; i++) { g_autofree gchar *title = NULL; if (priv->access[i] == FU_IFD_ACCESS_NONE) continue; title = g_strdup_printf("Access[%s]", fu_ifd_region_to_string(i)); fu_string_append(str, idt, title, fu_ifd_access_to_string(priv->access[i])); } } static GBytes * fu_ifd_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuIfdDevice *self = FU_IFD_DEVICE(device); FuIfdDevicePrivate *priv = GET_PRIVATE(self); FuDevice *parent = fu_device_get_parent(device); guint64 total_size = fu_device_get_firmware_size_max(device); return fu_intel_spi_device_dump(FU_INTEL_SPI_DEVICE(parent), device, priv->offset, total_size, progress, error); } static FuFirmware * fu_ifd_device_read_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuIfdDevice *self = FU_IFD_DEVICE(device); FuIfdDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(FuFirmware) firmware = fu_ifd_image_new(); g_autoptr(GBytes) blob = NULL; blob = fu_ifd_device_dump_firmware(device, progress, error); if (blob == NULL) return NULL; if (priv->region == FU_IFD_REGION_BIOS) firmware = fu_ifd_bios_new(); else firmware = fu_ifd_image_new(); if (!fu_firmware_parse(firmware, blob, FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return NULL; return g_steal_pointer(&firmware); } static void fu_ifd_device_init(FuIfdDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); fu_device_add_icon(FU_DEVICE(self), "computer"); } static void fu_ifd_device_class_init(FuIfdDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_ifd_device_to_string; klass_device->dump_firmware = fu_ifd_device_dump_firmware; klass_device->read_firmware = fu_ifd_device_read_firmware; } FuDevice * fu_ifd_device_new(FuContext *ctx, FuIfdRegion region, guint32 freg) { FuIfdDevice *self = FU_IFD_DEVICE(g_object_new(FU_TYPE_IFD_DEVICE, "context", ctx, NULL)); fu_ifd_device_set_region(self, region); fu_ifd_device_set_freg(self, freg); return FU_DEVICE(self); } fwupd-1.9.16/plugins/intel-spi/fu-ifd-device.h000066400000000000000000000010001460375044200210760ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_IFD_DEVICE (fu_ifd_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuIfdDevice, fu_ifd_device, FU, IFD_DEVICE, FuDevice) struct _FuIfdDeviceClass { FuDeviceClass parent_class; }; FuDevice * fu_ifd_device_new(FuContext *ctx, FuIfdRegion region, guint32 freg); void fu_ifd_device_set_access(FuIfdDevice *self, FuIfdRegion region, FuIfdAccess access); fwupd-1.9.16/plugins/intel-spi/fu-intel-spi-common.c000066400000000000000000000016771460375044200223100ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-intel-spi-common.h" guint16 fu_mmio_read16(gconstpointer addr, goffset offset) { addr = (guint8 *)addr + offset; return *(volatile const guint16 *)addr; } guint32 fu_mmio_read32(gconstpointer addr, goffset offset) { addr = (guint8 *)addr + offset; return *(volatile const guint32 *)addr; } void fu_mmio_write16(gpointer addr, goffset offset, guint16 val) { addr = (guint8 *)addr + offset; *(volatile guint16 *)addr = val; } void fu_mmio_write32(gpointer addr, goffset offset, guint32 val) { addr = (guint8 *)addr + offset; *(volatile guint32 *)addr = val; } guint32 fu_mmio_read32_le(gconstpointer addr, goffset offset) { return GUINT32_FROM_LE(fu_mmio_read32(addr, offset)); } void fu_mmio_write32_le(gpointer addr, goffset offset, guint32 val) { fu_mmio_write32(addr, offset, GUINT32_TO_LE(val)); } fwupd-1.9.16/plugins/intel-spi/fu-intel-spi-common.h000066400000000000000000000027561460375044200223140ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define ICH9_REG_BFPR 0x00 #define ICH9_REG_HSFS 0x04 #define ICH9_REG_HSFC 0x06 #define ICH9_REG_FADDR 0x08 #define ICH9_REG_RESRVD 0x0C #define ICH9_REG_FDATA0 0x10 #define ICH9_REG_FDATAN 0x14 #define ICH9_REG_FRAP 0x50 #define ICH9_REG_FREG0 0x54 #define ICH9_REG_PR0 0x74 #define ICH9_REG_FDOC 0xB0 #define ICH9_REG_FDOD 0xB4 #define PCH100_REG_FDOC 0xB4 #define PCH100_REG_FDOD 0xB8 #define PCH100_REG_FPR0 0x84 #define PCH100_REG_GPR0 0x98 #define PCH100_FADDR_FLA 0x07ffffff #define PCH100_HSFC_FCYCLE (0xf << 1) #define FDOC_FDSI (0x3F << 2) #define FDOC_FDSS (0x03 << 12) #define HSFS_FDONE (0x01 << 0) #define HSFS_FCERR (0x01 << 1) #define HSFS_AEL (0x01 << 2) #define HSFS_BERASE (0x03 << 3) #define HSFS_SCIP (0x01 << 5) #define HSFS_FDOPSS (0x01 << 13) #define HSFS_FDV (0x01 << 14) #define HSFS_FLOCKDN (0x01 << 15) #define HSFC_FGO (0x01 << 0) #define HSFC_FCYCLE (0x03 << 1) #define HSFC_FDBC (0x3f << 8) #define HSFC_SME (0x01 << 15) guint16 fu_mmio_read16(gconstpointer addr, goffset offset); void fu_mmio_write16(gpointer addr, goffset offset, guint16 val); guint32 fu_mmio_read32(gconstpointer addr, goffset offset); void fu_mmio_write32(gpointer addr, goffset offset, guint32 val); guint32 fu_mmio_read32_le(gconstpointer addr, goffset offset); void fu_mmio_write32_le(gpointer addr, goffset offset, guint32 val); fwupd-1.9.16/plugins/intel-spi/fu-intel-spi-device.c000066400000000000000000000354631460375044200222570ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #ifdef HAVE_ERRNO_H #include #endif #include #include "fu-ifd-device.h" #include "fu-intel-spi-common.h" #include "fu-intel-spi-device.h" #include "fu-intel-spi-struct.h" struct _FuIntelSpiDevice { FuDevice parent_instance; FuIntelSpiKind kind; guint32 phys_spibar; gpointer spibar; guint16 hsfs; guint16 frap; guint32 freg[4]; guint32 flvalsig; guint32 descriptor_map0; guint32 descriptor_map1; guint32 descriptor_map2; guint32 components_rcd; guint32 illegal_jedec; guint32 flpb; guint32 flash_master[4]; guint32 protected_range[4]; }; #define FU_INTEL_SPI_PHYS_SPIBAR_SIZE 0x10000 /* bytes */ #define FU_INTEL_SPI_READ_TIMEOUT 10 /* ms */ #define PCI_BASE_ADDRESS_0 0x0010 #define HSFS_FDOPSS_BIT 13 /* Flash Descriptor Override Pin-Strap Status */ #define HSFS_FDV_BIT 14 /* Flash Descriptor Valid */ /** * FU_INTEL_SPI_DEVICE_FLAG_ICH: * * Device is an I/O Controller Hub. */ #define FU_INTEL_SPI_DEVICE_FLAG_ICH (1 << 0) /** * FU_INTEL_SPI_DEVICE_FLAG_PCH: * * Device is a Platform Controller Hub. */ #define FU_INTEL_SPI_DEVICE_FLAG_PCH (1 << 1) G_DEFINE_TYPE(FuIntelSpiDevice, fu_intel_spi_device, FU_TYPE_DEVICE) static void fu_intel_spi_device_to_string(FuDevice *device, guint idt, GString *str) { FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device); fu_string_append(str, idt, "Kind", fu_intel_spi_kind_to_string(self->kind)); fu_string_append_kx(str, idt, "SPIBAR", self->phys_spibar); fu_string_append_kx(str, idt, "HSFS", self->hsfs); fu_string_append_kx(str, idt, "FRAP", self->frap); for (guint i = 0; i < 4; i++) { g_autofree gchar *title = g_strdup_printf("FREG%u", i); fu_string_append_kx(str, idt, title, self->freg[i]); } for (guint i = 0; i < 4; i++) { g_autofree gchar *title = g_strdup_printf("FLMSTR%u", i); fu_string_append_kx(str, idt, title, self->flash_master[i]); } fu_string_append_kx(str, idt, "FLVALSIG", self->flvalsig); fu_string_append_kx(str, idt, "FLMAP0", self->descriptor_map0); fu_string_append_kx(str, idt, "FLMAP1", self->descriptor_map1); fu_string_append_kx(str, idt, "FLMAP2", self->descriptor_map2); fu_string_append_kx(str, idt, "FLCOMP", self->components_rcd); fu_string_append_kx(str, idt, "FLILL", self->illegal_jedec); fu_string_append_kx(str, idt, "FLPB", self->flpb); /* PRx */ for (guint i = 0; i < 4; i++) { guint32 limit = 0; guint32 base = 0; FuIfdAccess access = FU_IFD_ACCESS_NONE; g_autofree gchar *title = NULL; g_autofree gchar *tmp = NULL; if (self->protected_range[i] == 0x0) continue; if ((self->protected_range[i] >> 31) & 0b1) access |= FU_IFD_ACCESS_WRITE; if ((self->protected_range[i] >> 15) & 0b1) access |= FU_IFD_ACCESS_READ; if (access != FU_IFD_ACCESS_NONE) { base = ((self->protected_range[i] >> 0) & 0x1FFF) << 12; limit = (((self->protected_range[i] >> 16) & 0x1FFF) << 12) | 0xFFFF; } title = g_strdup_printf("PR%u", i); tmp = g_strdup_printf("blocked %s from 0x%x to 0x%x [0x%x]", fu_ifd_access_to_string(access), base, limit, self->protected_range[i]); fu_string_append(str, idt, title, tmp); } } static gboolean fu_intel_spi_device_open(FuDevice *device, GError **error) { FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device); int fd; g_autoptr(GInputStream) istr = NULL; /* this will fail if the kernel is locked down */ fd = open("/dev/mem", O_SYNC | O_RDWR); if (fd == -1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, #ifdef HAVE_ERRNO_H "failed to open /dev/mem: %s", strerror(errno)); #else "failed to open /dev/mem"); #endif return FALSE; } istr = g_unix_input_stream_new(fd, TRUE); if (istr == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to create input stream"); return FALSE; } self->spibar = mmap(NULL, FU_INTEL_SPI_PHYS_SPIBAR_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, self->phys_spibar); if (self->spibar == MAP_FAILED) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, #ifdef HAVE_ERRNO_H "failed to mmap SPIBAR: %s", strerror(errno)); #else "failed to mmap SPIBAR"); #endif return FALSE; } /* success */ return TRUE; } static gboolean fu_intel_spi_device_close(FuDevice *device, GError **error) { FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device); /* close */ if (self->spibar != NULL) { if (munmap(self->spibar, FU_INTEL_SPI_PHYS_SPIBAR_SIZE) == -1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, #ifdef HAVE_ERRNO_H "failed to unmap SPIBAR: %s", strerror(errno)); #else "failed to unmap SPIBAR"); #endif return FALSE; } self->spibar = NULL; } /* success */ return TRUE; } static guint32 fu_intel_spi_device_read_reg(FuIntelSpiDevice *self, guint8 section, guint16 offset) { guint32 control = 0; control |= (((guint32)section) << 12) & FDOC_FDSS; control |= (((guint32)offset) << 2) & FDOC_FDSI; fu_mmio_write32_le(self->spibar, PCH100_REG_FDOC, control); return fu_mmio_read32_le(self->spibar, PCH100_REG_FDOD); } static void fu_intel_spi_device_add_security_attrs(FuDevice *device, FuSecurityAttrs *attrs) { FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device); FuIfdAccess access_global = FU_IFD_ACCESS_NONE; g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_device_security_attr_new(device, FWUPD_SECURITY_ATTR_ID_SPI_DESCRIPTOR); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); fu_security_attrs_append(attrs, attr); /* check for read access from other regions */ for (guint j = FU_IFD_REGION_BIOS; j < 4; j++) { FuIfdAccess access; access = fu_ifd_region_to_access(FU_IFD_REGION_DESC, self->flash_master[j - 1], TRUE); fwupd_security_attr_add_metadata(attr, fu_ifd_region_to_string(j), fu_ifd_access_to_string(access)); access_global |= access; } /* any region can write to the flash descriptor */ if (access_global & FU_IFD_ACCESS_WRITE) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* FLOCKDN is unset */ if ((self->hsfs >> 15 & 0b1) == 0) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static gboolean fu_intel_spi_device_probe(FuDevice *device, GError **error) { FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device); /* verify this was set in the quirk file */ if (self->kind == FU_INTEL_SPI_KIND_UNKNOWN) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "IntelSpiKind not set"); return FALSE; } /* specified explicitly as a physical address */ if (self->phys_spibar == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "IntelSpiBar not set"); return FALSE; } /* success */ return TRUE; } static gboolean fu_intel_spi_device_setup(FuDevice *device, GError **error) { FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device); guint64 total_size = 0; guint8 comp1_density; guint8 comp2_density; gboolean me_is_locked; guint16 reg_pr0 = fu_device_has_private_flag(device, FU_INTEL_SPI_DEVICE_FLAG_ICH) ? ICH9_REG_PR0 : PCH100_REG_FPR0; /* dump everything */ for (guint i = 0; i < 0xff; i += 4) { guint32 tmp = fu_mmio_read32(self->spibar, i); g_print("SPIBAR[0x%02x] = 0x%x\n", i, tmp); } /* read from descriptor */ self->hsfs = fu_mmio_read16(self->spibar, ICH9_REG_HSFS); self->frap = fu_mmio_read16(self->spibar, ICH9_REG_FRAP); for (guint i = FU_IFD_REGION_DESC; i < 4; i++) self->freg[i] = fu_mmio_read32(self->spibar, ICH9_REG_FREG0 + i * 4); self->flvalsig = fu_intel_spi_device_read_reg(self, 0, 0); self->descriptor_map0 = fu_intel_spi_device_read_reg(self, 0, 1); self->descriptor_map1 = fu_intel_spi_device_read_reg(self, 0, 2); self->descriptor_map2 = fu_intel_spi_device_read_reg(self, 0, 3); self->components_rcd = fu_intel_spi_device_read_reg(self, 1, 0); self->illegal_jedec = fu_intel_spi_device_read_reg(self, 1, 1); self->flpb = fu_intel_spi_device_read_reg(self, 1, 2); for (guint i = 0; i < 4; i++) self->flash_master[i] = fu_intel_spi_device_read_reg(self, 3, i); for (guint i = 0; i < 4; i++) { self->protected_range[i] = fu_mmio_read32(self->spibar, reg_pr0 + i * sizeof(guint32)); } /* set size */ comp1_density = (self->components_rcd & 0x0f) >> 0; if (comp1_density != 0xf) total_size += 1ull << (19 + comp1_density); comp2_density = (self->components_rcd & 0xf0) >> 4; if (comp2_density != 0xf) total_size += 1ull << (19 + comp2_density); fu_device_set_firmware_size(device, total_size); me_is_locked = !(self->hsfs & (1 << HSFS_FDV_BIT)) || /* assume locked if not valid */ (self->hsfs & (1 << HSFS_FDOPSS_BIT)); /* use status bit if valid */ /* add children */ for (guint i = FU_IFD_REGION_BIOS; i < 4; i++) { g_autoptr(FuDevice) child = NULL; if (self->freg[i] == 0x0) continue; child = fu_ifd_device_new(fu_device_get_context(device), i, self->freg[i]); for (guint j = 1; j < 4; j++) { FuIfdAccess access; access = fu_ifd_region_to_access(i, self->flash_master[j - 1], TRUE); fu_ifd_device_set_access(FU_IFD_DEVICE(child), j, access); } if (i == FU_IFD_REGION_ME && me_is_locked) fu_device_add_flag(child, FWUPD_DEVICE_FLAG_LOCKED); fu_device_add_child(device, child); } return TRUE; } static gboolean fu_intel_spi_device_wait(FuIntelSpiDevice *self, guint timeout_ms, GError **error) { g_usleep(1); for (guint i = 0; i < timeout_ms * 100; i++) { guint16 hsfs = fu_mmio_read16(self->spibar, ICH9_REG_HSFS); if (hsfs & HSFS_FDONE) return TRUE; if (hsfs & HSFS_FCERR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "HSFS transaction error"); return FALSE; } g_usleep(10); } g_set_error(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "HSFS timed out"); return FALSE; } static void fu_intel_spi_device_set_addr(FuIntelSpiDevice *self, guint32 addr) { guint32 addr_old = fu_mmio_read32(self->spibar, ICH9_REG_FADDR) & ~PCH100_FADDR_FLA; fu_mmio_write32(self->spibar, ICH9_REG_FADDR, (addr & PCH100_FADDR_FLA) | addr_old); } GBytes * fu_intel_spi_device_dump(FuIntelSpiDevice *self, FuDevice *device, guint32 offset, guint32 length, FuProgress *progress, GError **error) { guint8 block_len = 0x40; g_autoptr(GByteArray) buf = g_byte_array_sized_new(length); /* set FDONE, FCERR, AEL */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); fu_mmio_write16(self->spibar, ICH9_REG_HSFS, fu_mmio_read16(self->spibar, ICH9_REG_HSFS)); for (guint32 addr = offset; addr < offset + length; addr += block_len) { guint16 hsfc; guint32 buftmp32 = 0; /* set up read */ fu_intel_spi_device_set_addr(self, addr); hsfc = fu_mmio_read16(self->spibar, ICH9_REG_HSFC); hsfc &= ~PCH100_HSFC_FCYCLE; hsfc &= ~HSFC_FDBC; /* set byte count */ hsfc |= ((block_len - 1) << 8) & HSFC_FDBC; hsfc |= HSFC_FGO; fu_mmio_write16(self->spibar, ICH9_REG_HSFC, hsfc); if (!fu_intel_spi_device_wait(self, FU_INTEL_SPI_READ_TIMEOUT, error)) { g_prefix_error(error, "failed @0x%x: ", addr); return NULL; } /* copy out data */ for (guint i = 0; i < block_len; i++) { if (i % 4 == 0) buftmp32 = fu_mmio_read32(self->spibar, ICH9_REG_FDATA0 + i); fu_byte_array_append_uint8(buf, buftmp32 >> ((i % 4) * 8)); } /* progress */ fu_progress_set_percentage_full(progress, addr - offset + block_len, length); } /* success */ return g_bytes_new(buf->data, buf->len); } static GBytes * fu_intel_spi_device_dump_firmware2(FuDevice *device, FuProgress *progress, GError **error) { FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device); guint64 total_size = fu_device_get_firmware_size_max(device); return fu_intel_spi_device_dump(self, device, 0x0, total_size, progress, error); } static GBytes * fu_intel_spi_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { return fu_intel_spi_device_dump_firmware2(device, progress, error); } static FuFirmware * fu_intel_spi_device_read_firmware(FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(FuFirmware) firmware = fu_ifd_firmware_new(); g_autoptr(GBytes) blob = NULL; blob = fu_intel_spi_device_dump_firmware2(device, progress, error); if (blob == NULL) return NULL; if (!fu_firmware_parse(firmware, blob, FWUPD_INSTALL_FLAG_NONE, error)) return NULL; return g_steal_pointer(&firmware); } static gboolean fu_intel_spi_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuIntelSpiDevice *self = FU_INTEL_SPI_DEVICE(device); if (g_strcmp0(key, "IntelSpiBar") == 0) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->phys_spibar = tmp; return TRUE; } if (g_strcmp0(key, "IntelSpiKind") == 0) { /* validate */ self->kind = fu_intel_spi_kind_from_string(value); if (self->kind == FU_INTEL_SPI_KIND_UNKNOWN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s not supported", value); return FALSE; } /* get things like SPIBAR */ fu_device_add_instance_strup(device, "ID", value); return fu_device_build_instance_id(device, error, "INTEL_SPI_CHIPSET", "ID", NULL); } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no supported"); return FALSE; } static void fu_intel_spi_device_init(FuIntelSpiDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_icon(FU_DEVICE(self), "computer"); fu_device_set_physical_id(FU_DEVICE(self), "intel_spi"); fu_device_register_private_flag(FU_DEVICE(self), FU_INTEL_SPI_DEVICE_FLAG_ICH, "ich"); fu_device_register_private_flag(FU_DEVICE(self), FU_INTEL_SPI_DEVICE_FLAG_PCH, "pch"); } static void fu_intel_spi_device_class_init(FuIntelSpiDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_intel_spi_device_to_string; klass_device->probe = fu_intel_spi_device_probe; klass_device->setup = fu_intel_spi_device_setup; klass_device->dump_firmware = fu_intel_spi_device_dump_firmware; klass_device->read_firmware = fu_intel_spi_device_read_firmware; klass_device->open = fu_intel_spi_device_open; klass_device->close = fu_intel_spi_device_close; klass_device->set_quirk_kv = fu_intel_spi_device_set_quirk_kv; klass_device->add_security_attrs = fu_intel_spi_device_add_security_attrs; } fwupd-1.9.16/plugins/intel-spi/fu-intel-spi-device.h000066400000000000000000000007321460375044200222530ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_INTEL_SPI_DEVICE (fu_intel_spi_device_get_type()) G_DECLARE_FINAL_TYPE(FuIntelSpiDevice, fu_intel_spi_device, FU, INTEL_SPI_DEVICE, FuDevice) GBytes * fu_intel_spi_device_dump(FuIntelSpiDevice *self, FuDevice *device, guint32 offset, guint32 length, FuProgress *progress, GError **error); fwupd-1.9.16/plugins/intel-spi/fu-intel-spi-plugin.c000066400000000000000000000025031460375044200223030ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-intel-spi-device.h" #include "fu-intel-spi-plugin.h" struct _FuIntelSpiPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuIntelSpiPlugin, fu_intel_spi_plugin, FU_TYPE_PLUGIN) static gboolean fu_intel_spi_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { if (fu_kernel_locked_down()) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported when kernel locked down"); return FALSE; } return TRUE; } static void fu_intel_spi_plugin_init(FuIntelSpiPlugin *self) { } static void fu_intel_spi_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "IntelSpiKind"); fu_context_add_quirk_key(ctx, "IntelSpiBar"); fu_context_add_quirk_key(ctx, "IntelSpiBiosCntl"); fu_plugin_add_udev_subsystem(plugin, "pci"); fu_plugin_add_device_gtype(plugin, FU_TYPE_INTEL_SPI_DEVICE); } static void fu_intel_spi_plugin_class_init(FuIntelSpiPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_intel_spi_plugin_constructed; plugin_class->startup = fu_intel_spi_plugin_startup; } fwupd-1.9.16/plugins/intel-spi/fu-intel-spi-plugin.h000066400000000000000000000003601460375044200223070ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuIntelSpiPlugin, fu_intel_spi_plugin, FU, INTEL_SPI_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/intel-spi/fu-intel-spi.rs000066400000000000000000000004401460375044200212070ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString, FromString)] enum IntelSpiKind { Unknown, Apl, C620, Ich0, Ich2345, Ich6, Ich9, Pch100, Pch200, Pch300, Pch400, Poulsbo, } fwupd-1.9.16/plugins/intel-spi/generate-quirk.py000077500000000000000000000047671460375044200216450ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2021 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import sys class Chipset: def __init__(self, spibar=None, bios_cntl=0x0, flags=None): self.bios_cntl = bios_cntl self.spibar = spibar self.flags = flags if __name__ == "__main__": if len(sys.argv) != 2: print("required: /path/to/chipset_enable.c") sys.exit(1) chipsets = { "apl": Chipset(flags="ich", bios_cntl=0xDC), "c620": Chipset(flags="pch", bios_cntl=0xDC), "ich0": Chipset(flags="ich", bios_cntl=0x4E), "ich2345": Chipset(flags="ich", bios_cntl=0x4E), "ich6": Chipset(flags="ich", bios_cntl=0xDC), "pch100": Chipset(flags="pch", bios_cntl=0xDC), "pch200": Chipset(flags="pch", bios_cntl=0xDC), "pch300": Chipset(flags="pch", bios_cntl=0xDC), "pch400": Chipset(flags="pch", bios_cntl=0xDC), "poulsbo": Chipset(flags="ich", bios_cntl=0xD8), } devices = {"PCI\VEN_8086&DEV_A0A4": "pch100", "PCI\VEN_8086&DEV_9D24": "pch200"} with open("intel-spi.quirk", "w") as out_f: with open(sys.argv[1], "r") as in_f: lines = in_f.read().split("\n") for line in lines: if line.find("0x8086") == -1: continue if line.find("Sample") != -1: continue for char in ["}", "{", '"', " ", "\t"]: line = line.replace(char, "") ven, dev, _, _, _, _, kind, _ = line.split(",") if kind.startswith("enable_flash_"): kind = kind[13:] if kind not in chipsets: print("ignoring {}...".format(kind)) continue devices["PCI\VEN_{}&DEV_{}".format(ven[2:], dev[2:].upper())] = kind for device in devices: kind = devices[device] out_f.write("[{}]\n".format(device)) out_f.write("Plugin = intel_spi\n") out_f.write("IntelSpiKind = {}\n\n".format(kind)) for kind in sorted(chipsets): cs = chipsets[kind] out_f.write("\n[INTEL_SPI_CHIPSET\\ID_{}]\n".format(kind.upper())) if cs.spibar: out_f.write("IntelSpiBar = 0x{:x}\n".format(cs.spibar)) if cs.bios_cntl: out_f.write("IntelSpiBiosCntl = 0x{:X}\n".format(cs.bios_cntl)) if cs.flags: out_f.write("Flags = {}\n".format(cs.flags)) fwupd-1.9.16/plugins/intel-spi/intel-spi.quirk000066400000000000000000000161451460375044200213170ustar00rootroot00000000000000[PCI\VEN_8086&DEV_A0A4] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_9D24] Plugin = intel_spi IntelSpiKind = pch200 [PCI\VEN_8086&DEV_2410] Plugin = intel_spi IntelSpiKind = ich0 [PCI\VEN_8086&DEV_2420] Plugin = intel_spi IntelSpiKind = ich0 [PCI\VEN_8086&DEV_2440] Plugin = intel_spi IntelSpiKind = ich2345 [PCI\VEN_8086&DEV_244C] Plugin = intel_spi IntelSpiKind = ich2345 [PCI\VEN_8086&DEV_2450] Plugin = intel_spi IntelSpiKind = ich2345 [PCI\VEN_8086&DEV_2480] Plugin = intel_spi IntelSpiKind = ich2345 [PCI\VEN_8086&DEV_248C] Plugin = intel_spi IntelSpiKind = ich2345 [PCI\VEN_8086&DEV_24C0] Plugin = intel_spi IntelSpiKind = ich2345 [PCI\VEN_8086&DEV_24CC] Plugin = intel_spi IntelSpiKind = ich2345 [PCI\VEN_8086&DEV_24D0] Plugin = intel_spi IntelSpiKind = ich2345 [PCI\VEN_8086&DEV_25A1] Plugin = intel_spi IntelSpiKind = ich2345 [PCI\VEN_8086&DEV_2640] Plugin = intel_spi IntelSpiKind = ich6 [PCI\VEN_8086&DEV_2641] Plugin = intel_spi IntelSpiKind = ich6 [PCI\VEN_8086&DEV_2642] Plugin = intel_spi IntelSpiKind = ich6 [PCI\VEN_8086&DEV_2670] Plugin = intel_spi IntelSpiKind = ich6 [PCI\VEN_8086&DEV_8119] Plugin = intel_spi IntelSpiKind = poulsbo [PCI\VEN_8086&DEV_9D43] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_9D46] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_9D48] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_9D4B] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_9D4E] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_9D50] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_9D53] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_9D56] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_9D58] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_9D84] Plugin = intel_spi IntelSpiKind = pch300 [PCI\VEN_8086&DEV_0284] Plugin = intel_spi IntelSpiKind = pch400 [PCI\VEN_8086&DEV_0285] Plugin = intel_spi IntelSpiKind = pch400 [PCI\VEN_8086&DEV_A143] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A144] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A145] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A146] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A147] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A148] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A149] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A14A] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A14D] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A14E] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A150] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A151] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A152] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A153] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A154] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A155] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A1A4] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1C0] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1C1] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1C2] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1C3] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1C4] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1C5] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1C6] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1C7] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1C8] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1C9] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1CA] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1CB] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1CC] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A1CD] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A240] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A241] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A242] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A243] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A244] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A245] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A246] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A247] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A248] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A249] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_1BCA] Plugin = intel_spi IntelSpiKind = c620 [PCI\VEN_8086&DEV_A2C4] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A2C5] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A2C6] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A2C7] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A2C8] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A2C9] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A2CA] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A2CC] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_A2D2] Plugin = intel_spi IntelSpiKind = pch100 [PCI\VEN_8086&DEV_5AE8] Plugin = intel_spi IntelSpiKind = apl [PCI\VEN_8086&DEV_5AF0] Plugin = intel_spi IntelSpiKind = apl [PCI\VEN_8086&DEV_A303] Plugin = intel_spi IntelSpiKind = pch300 [PCI\VEN_8086&DEV_A304] Plugin = intel_spi IntelSpiKind = pch300 [PCI\VEN_8086&DEV_A305] Plugin = intel_spi IntelSpiKind = pch300 [PCI\VEN_8086&DEV_A306] Plugin = intel_spi IntelSpiKind = pch300 [PCI\VEN_8086&DEV_A308] Plugin = intel_spi IntelSpiKind = pch300 [PCI\VEN_8086&DEV_A309] Plugin = intel_spi IntelSpiKind = pch300 [PCI\VEN_8086&DEV_A30A] Plugin = intel_spi IntelSpiKind = pch300 [PCI\VEN_8086&DEV_A30C] Plugin = intel_spi IntelSpiKind = pch300 [PCI\VEN_8086&DEV_A30D] Plugin = intel_spi IntelSpiKind = pch300 [PCI\VEN_8086&DEV_A30E] Plugin = intel_spi IntelSpiKind = pch300 [PCI\VEN_8086&DEV_3482] Plugin = intel_spi IntelSpiKind = pch300 [PCI\VEN_8086&DEV_0684] Plugin = intel_spi IntelSpiKind = pch400 [PCI\VEN_8086&DEV_0685] Plugin = intel_spi IntelSpiKind = pch400 [PCI\VEN_8086&DEV_0687] Plugin = intel_spi IntelSpiKind = pch400 [PCI\VEN_8086&DEV_068C] Plugin = intel_spi IntelSpiKind = pch400 [PCI\VEN_8086&DEV_068D] Plugin = intel_spi IntelSpiKind = pch400 [PCI\VEN_8086&DEV_068E] Plugin = intel_spi IntelSpiKind = pch400 [PCI\VEN_8086&DEV_0697] Plugin = intel_spi IntelSpiKind = pch400 [INTEL_SPI_CHIPSET\ID_APL] IntelSpiBiosCntl = 0xDC Flags = ich [INTEL_SPI_CHIPSET\ID_C620] IntelSpiBiosCntl = 0xDC Flags = pch [INTEL_SPI_CHIPSET\ID_ICH0] IntelSpiBiosCntl = 0x4E Flags = ich [INTEL_SPI_CHIPSET\ID_ICH2345] IntelSpiBiosCntl = 0x4E Flags = ich [INTEL_SPI_CHIPSET\ID_ICH6] IntelSpiBiosCntl = 0xDC Flags = ich [INTEL_SPI_CHIPSET\ID_PCH100] IntelSpiBiosCntl = 0xDC Flags = pch [INTEL_SPI_CHIPSET\ID_PCH200] IntelSpiBiosCntl = 0xDC Flags = pch [INTEL_SPI_CHIPSET\ID_PCH300] IntelSpiBiosCntl = 0xDC Flags = pch [INTEL_SPI_CHIPSET\ID_PCH400] IntelSpiBiosCntl = 0xDC Flags = pch [INTEL_SPI_CHIPSET\ID_POULSBO] IntelSpiBiosCntl = 0xD8 Flags = ich fwupd-1.9.16/plugins/intel-spi/meson.build000066400000000000000000000012741460375044200204750ustar00rootroot00000000000000if get_option('plugin_intel_spi') if not lzma.found() or \ host_machine.system() != 'linux' or \ (host_cpu != 'x86' and host_cpu != 'x86_64') error('unsupported configuration for intel_spi') endif cargs = ['-DG_LOG_DOMAIN="FuPluginIntelSpi"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('intel-spi.quirk') plugin_builtins += static_library('fu_plugin_intel_spi', rustgen.process('fu-intel-spi.rs'), sources: [ 'fu-ifd-device.c', 'fu-intel-spi-common.c', 'fu-intel-spi-device.c', 'fu-intel-spi-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/intel-usb4/000077500000000000000000000000001460375044200164115ustar00rootroot00000000000000fwupd-1.9.16/plugins/intel-usb4/README.md000066400000000000000000000024301460375044200176670ustar00rootroot00000000000000--- title: Plugin: Intel USB4 --- ## Introduction This plugin supports the Goshen Ridge hardware which is a USB-4 controller from Intel. These devices can updated using multiple interfaces, but this plugin only uses the XHCI interface. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format, with vendor specific header. This plugin supports the following protocol ID: * `com.intel.thunderbolt` ## GUID Generation These devices use the standard USB DeviceInstanceId values for the USB Hub, e.g. * `USB\VID_8087&PID_0B40` (quirk-only) These devices also use a custom InstanceId, which is quite intentionally identical to thunderbolt plugin: * `TBT-{nvm_vendor_id}{nvm_product_id}` ## Update Behavior By default the USB4 controller will reboot at the end of the update. Some devices (e.g. inside some Dell docks) will instead be updated the next time the USB-C plug from the dock is unplugged from the host, or when activated manually. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x8087` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.8.5`. fwupd-1.9.16/plugins/intel-usb4/fu-intel-usb4-device.c000066400000000000000000000435661460375044200224260ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2021 Intel Corporation. * Copyright (C) 2021 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-intel-usb4-device.h" #include "fu-intel-usb4-struct.h" #define GR_USB_INTERFACE_NUMBER 0x0 #define GR_USB_BLOCK_SIZE 64 /* bmRequest type */ #define USB_REQ_TYPE_GET_MMIO 0xc0 /* bm Request type */ #define USB_REQ_TYPE_SET_MMIO 0x40 /* bm Request type */ /* bRequest */ #define REQ_HUB_GET_MMIO 64 #define REQ_HUB_SET_MMIO 65 /* wValue*/ #define MBOX_ACCESS (1 << 10) /* wIndex, mailbox register offset */ /* First 16 registers are Data[0]-Data[15] registers */ #define MBOX_REG_METADATA 16 #define MBOX_REG 17 /* no name? */ /* mask for the MBOX_REG register that has no name */ #define MBOX_ERROR (1 << 6) /* of the u8 status field */ #define MBOX_OPVALID (1 << 7) /* of the u8 status field */ #define MBOX_TIMEOUT 3000 /* NVM metadata offset and length fields are in dword units */ /* note that these won't work for DROM read */ #define NVM_OFFSET_TO_METADATA(p) ((((p) / 4) & 0x3fffff) << 2) /* bits 23:2 */ #define NVM_LENGTH_TO_METADATA(p) ((((p) / 4) & 0xf) << 24) /* bits 27:24 */ /* Default length for NVM READ */ #define NVM_READ_LENGTH 0x224 #define FU_INTEL_USB4_DEVICE_REMOVE_DELAY 60000 /* ms */ struct _FuIntelUsb4Device { FuUsbDevice parent_instance; guint blocksz; guint8 intf_nr; /* from DROM */ guint16 nvm_vendor_id; guint16 nvm_model_id; /* from DIGITAL */ guint16 nvm_device_id; }; G_DEFINE_TYPE(FuIntelUsb4Device, fu_intel_usb4_device, FU_TYPE_USB_DEVICE) /* wIndex contains the hub register offset, value BIT[10] is "access to * mailbox", rest of values are vendor specific or rsvd */ static gboolean fu_intel_usb4_device_get_mmio(FuDevice *device, guint16 mbox_reg, guint8 *buf, gsize bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, REQ_HUB_GET_MMIO, /* request */ MBOX_ACCESS, /* value */ mbox_reg, /* index */ buf, bufsz, NULL, /* actual length */ MBOX_TIMEOUT, NULL, error)) { g_prefix_error(error, "GET_MMIO failed to set control on mbox register index [0x%x]: ", mbox_reg); return FALSE; } /* verify status for specific hub mailbox register */ if (mbox_reg == MBOX_REG) { g_autoptr(GByteArray) st_regex = NULL; st_regex = fu_struct_intel_usb4_mbox_parse(buf, bufsz, 0x0, error); if (st_regex == NULL) return FALSE; /* error status bit */ if (fu_struct_intel_usb4_mbox_get_status(st_regex) & MBOX_ERROR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "GET_MMIO opcode [0x%x] nonzero error bit in status [0x%x]", fu_struct_intel_usb4_mbox_get_opcode(st_regex), fu_struct_intel_usb4_mbox_get_status(st_regex)); return FALSE; } /* operation valid (OV) bit should be 0'b */ if (fu_struct_intel_usb4_mbox_get_status(st_regex) & MBOX_OPVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "GET_MMIO opcode [0x%x] nonzero OV bit in status [0x%x]", fu_struct_intel_usb4_mbox_get_opcode(st_regex), fu_struct_intel_usb4_mbox_get_status(st_regex)); return FALSE; } } return TRUE; } static gboolean fu_intel_usb4_device_set_mmio(FuDevice *device, guint16 mbox_reg, guint8 *buf, gsize bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, REQ_HUB_SET_MMIO, /* request */ MBOX_ACCESS, /* value */ mbox_reg, /* index */ buf, bufsz, NULL, /* actual length */ MBOX_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to set mmio 0x%x: ", mbox_reg); return FALSE; } return TRUE; } /* * Read up to 64 bytes of data from the mbox data registers to a buffer. * The mailbox can hold 64 bytes of data in 16 doubleword data registers. * To get data from NVM or DROM to mbox registers issue a NVM Read or DROM * read operation before reading the mbox data registers. */ static gboolean fu_intel_usb4_device_mbox_data_read(FuDevice *device, guint8 *data, guint8 length, GError **error) { guint8 *ptr = data; if (length > 64 || length % 4) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid firmware data read length %u", length); return FALSE; } /* read 4 bytes per iteration */ for (gint i = 0; i < length / 4; i++) { if (!fu_intel_usb4_device_get_mmio(device, i, ptr, 0x4, error)) { g_prefix_error(error, "failed to read mbox data registers: "); return FALSE; } ptr += 4; } return TRUE; } /* * The mailbox can hold 64 bytes in 16 doubleword data registers. * A NVM write operation writes data from these registers to NVM * at the set offset */ static gboolean fu_intel_usb4_device_mbox_data_write(FuDevice *device, const guint8 *data, guint8 length, GError **error) { guint8 *ptr = (guint8 *)data; if (length > 64 || length % 4) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid firmware data write length %u", length); return FALSE; } /* writes 4 bytes per iteration */ for (gint i = 0; i < length / 4; i++) { if (!fu_intel_usb4_device_set_mmio(device, i, ptr, 0x4, error)) return FALSE; ptr += 4; } return TRUE; } static gboolean fu_intel_usb4_device_operation(FuDevice *device, FuIntelUsb4Opcode opcode, guint8 *metadata, GError **error) { gint max_tries = 100; g_autoptr(GByteArray) st_regex = fu_struct_intel_usb4_mbox_new(); /* Write metadata register for operations that use it */ switch (opcode) { case FU_INTEL_USB4_OPCODE_NVM_WRITE: case FU_INTEL_USB4_OPCODE_NVM_AUTH_WRITE: break; case FU_INTEL_USB4_OPCODE_NVM_READ: case FU_INTEL_USB4_OPCODE_NVM_SET_OFFSET: case FU_INTEL_USB4_OPCODE_DROM_READ: if (metadata == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "hub opcode 0x%x requires metadata", opcode); return FALSE; } if (!fu_intel_usb4_device_set_mmio(device, MBOX_REG_METADATA, metadata, 0x4, error)) { g_prefix_error(error, "failed to write metadata %s: ", metadata); return FALSE; } break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid hub opcode: 0x%x", opcode); return FALSE; } /* write the operation and poll completion or error */ fu_struct_intel_usb4_mbox_set_opcode(st_regex, opcode); fu_struct_intel_usb4_mbox_set_status(st_regex, MBOX_OPVALID); if (!fu_intel_usb4_device_set_mmio(device, MBOX_REG, st_regex->data, st_regex->len, error)) return FALSE; /* leave early as successful USB4 AUTH resets the device immediately */ if (opcode == FU_INTEL_USB4_OPCODE_NVM_AUTH_WRITE) return TRUE; for (gint i = 0; i <= max_tries; i++) { g_autoptr(GError) error_local = NULL; if (fu_intel_usb4_device_get_mmio(device, MBOX_REG, st_regex->data, st_regex->len, &error_local)) return TRUE; if (i == max_tries) { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "maximum tries exceeded: "); } fu_device_sleep(device, 10); /* ms */ } return FALSE; } static gboolean fu_intel_usb4_device_nvm_read(FuDevice *device, guint8 *buf, guint32 length, guint32 nvm_addr, GError **error) { guint8 tmpbuf[64] = {0x0}; while (length > 0) { guint32 unaligned_bytes = nvm_addr % 4; guint32 padded_len; guint32 nbytes; guint8 metadata[4]; if (length + unaligned_bytes < 64) { nbytes = length; padded_len = unaligned_bytes + length; /* align end to full dword boundary */ if (padded_len % 4) padded_len = (padded_len & ~0x3) + 4; } else { padded_len = 64; nbytes = padded_len - unaligned_bytes; } /* set nvm read offset in dwords */ fu_memwrite_uint32(metadata, NVM_OFFSET_TO_METADATA(nvm_addr), G_LITTLE_ENDIAN); /* and length field in dwords, note 0 means 16 dwords */ metadata[3] = (padded_len / 4) & 0xf; /* ask hub to read up to 64 bytes from NVM to mbox data regs */ if (!fu_intel_usb4_device_operation(device, FU_INTEL_USB4_OPCODE_NVM_READ, metadata, error)) { g_prefix_error(error, "hub NVM read error: "); return FALSE; } /* read the data from mbox data regs into our buffer */ if (!fu_intel_usb4_device_mbox_data_read(device, tmpbuf, padded_len, error)) { g_prefix_error(error, "hub firmware mbox data read error: "); return FALSE; } if (!fu_memcpy_safe(buf, length, 0x0, tmpbuf, sizeof(tmpbuf), unaligned_bytes, nbytes, error)) return FALSE; buf += nbytes; nvm_addr += nbytes; length -= nbytes; } return TRUE; } static gboolean fu_intel_usb4_device_nvm_write(FuDevice *device, GBytes *blob, guint32 nvm_addr, FuProgress *progress, GError **error) { guint8 metadata[4]; g_autoptr(FuChunkArray) chunks = NULL; if (nvm_addr % 4 != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Invalid NVM write offset 0x%x, must be DW aligned: ", nvm_addr); return FALSE; } if (g_bytes_get_size(blob) < 64 || g_bytes_get_size(blob) % 64) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Invalid NVM length 0x%x, must be 64 byte aligned: ", (guint)g_bytes_get_size(blob)); return FALSE; } /* set initial offset, must be DW aligned */ fu_memwrite_uint32(metadata, NVM_OFFSET_TO_METADATA(nvm_addr), G_LITTLE_ENDIAN); if (!fu_intel_usb4_device_operation(device, FU_INTEL_USB4_OPCODE_NVM_SET_OFFSET, metadata, error)) { g_prefix_error(error, "hub NVM set offset error: "); return FALSE; } /* write data in 64 byte blocks */ chunks = fu_chunk_array_new_from_bytes(blob, 0x0, 64); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); /* write data to mbox data regs */ if (!fu_intel_usb4_device_mbox_data_write(device, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "hub mbox data write error: "); return FALSE; } /* ask hub to write 64 bytes from data regs to NVM */ if (!fu_intel_usb4_device_operation(device, FU_INTEL_USB4_OPCODE_NVM_WRITE, NULL, error)) { g_prefix_error(error, "hub NVM write operation error: "); return FALSE; } /* done */ fu_progress_step_done(progress); } /* success */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_BUSY); return TRUE; } static gboolean fu_intel_usb4_device_activate(FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; if (!fu_intel_usb4_device_operation(device, FU_INTEL_USB4_OPCODE_NVM_AUTH_WRITE, NULL, error)) { g_prefix_error(error, "NVM authenticate failed: "); fu_device_set_update_state(device, FWUPD_UPDATE_STATE_FAILED); return FALSE; } fu_device_set_update_state(device, FWUPD_UPDATE_STATE_SUCCESS); return TRUE; } static FuFirmware * fu_intel_usb4_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuIntelUsb4Device *self = FU_INTEL_USB4_DEVICE(device); guint16 fw_vendor_id; guint16 fw_model_id; g_autoptr(FuFirmware) firmware = fu_intel_thunderbolt_firmware_new(); /* get vid:pid:rev */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* check is compatible */ fw_vendor_id = fu_intel_thunderbolt_nvm_get_vendor_id(FU_INTEL_THUNDERBOLT_NVM(firmware)); fw_model_id = fu_intel_thunderbolt_nvm_get_model_id(FU_INTEL_THUNDERBOLT_NVM(firmware)); if (self->nvm_vendor_id != fw_vendor_id || self->nvm_model_id != fw_model_id) { if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware 0x%04x:0x%04x does not match device 0x%04x:0x%04x", fw_vendor_id, fw_model_id, self->nvm_vendor_id, self->nvm_model_id); return NULL; } g_warning("firmware 0x%04x:0x%04x does not match device 0x%04x:0x%04x", fw_vendor_id, fw_model_id, self->nvm_vendor_id, self->nvm_model_id); } /* success */ return g_steal_pointer(&firmware); } static gboolean fu_intel_usb4_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { g_autoptr(GBytes) fw_image = NULL; g_return_val_if_fail(device != NULL, FALSE); g_return_val_if_fail(FU_IS_FIRMWARE(firmware), FALSE); /* get payload */ fw_image = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_PAYLOAD, error); if (fw_image == NULL) return FALSE; /* firmware install */ if (!fu_intel_usb4_device_nvm_write(device, fw_image, 0, progress, error)) return FALSE; /* success, but needs activation */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SKIPS_RESTART)) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); fu_device_set_version(device, fu_firmware_get_version(firmware)); return TRUE; } /* activate, wait for replug */ if (!fu_intel_usb4_device_operation(device, FU_INTEL_USB4_OPCODE_NVM_AUTH_WRITE, NULL, error)) { g_prefix_error(error, "NVM authenticate failed: "); return FALSE; } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* success */ return TRUE; } static gboolean fu_intel_usb4_device_setup(FuDevice *device, GError **error) { FuIntelUsb4Device *self = FU_INTEL_USB4_DEVICE(device); guint8 buf[NVM_READ_LENGTH] = {0x0}; g_autofree gchar *name = NULL; g_autoptr(FuFirmware) fw = fu_intel_thunderbolt_nvm_new(); g_autoptr(GBytes) blob = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_intel_usb4_device_parent_class)->setup(device, error)) return FALSE; /* read from device and parse firmware */ if (!fu_intel_usb4_device_nvm_read(device, buf, sizeof(buf), 0, error)) { g_prefix_error(error, "NVM read error: "); return FALSE; } blob = g_bytes_new(buf, sizeof(buf)); if (!fu_firmware_parse(fw, blob, FWUPD_INSTALL_FLAG_NONE, error)) { g_prefix_error(error, "NVM parse error: "); return FALSE; } self->nvm_vendor_id = fu_intel_thunderbolt_nvm_get_vendor_id(FU_INTEL_THUNDERBOLT_NVM(fw)); self->nvm_model_id = fu_intel_thunderbolt_nvm_get_model_id(FU_INTEL_THUNDERBOLT_NVM(fw)); self->nvm_device_id = fu_intel_thunderbolt_nvm_get_device_id(FU_INTEL_THUNDERBOLT_NVM(fw)); name = g_strdup_printf("TBT-%04x%04x", self->nvm_vendor_id, self->nvm_model_id); fu_device_add_instance_id(device, name); fu_device_set_version(device, fu_firmware_get_version(fw)); return TRUE; } static void fu_intel_usb4_device_to_string(FuDevice *device, guint idt, GString *str) { FuIntelUsb4Device *self = FU_INTEL_USB4_DEVICE(device); fu_string_append_kx(str, idt, "NvmVendorId", self->nvm_vendor_id); fu_string_append_kx(str, idt, "NvmModelId", self->nvm_model_id); fu_string_append_kx(str, idt, "NvmDeviceId", self->nvm_device_id); } static void fu_thunderbolt_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 78, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 22, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_intel_usb4_device_init(FuIntelUsb4Device *self) { self->intf_nr = GR_USB_INTERFACE_NUMBER; self->blocksz = GR_USB_BLOCK_SIZE; fu_device_add_protocol(FU_DEVICE(self), "com.intel.thunderbolt"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_INHERIT_ACTIVATION); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_GENERIC_GUIDS); fu_device_set_remove_delay(FU_DEVICE(self), FU_INTEL_USB4_DEVICE_REMOVE_DELAY); } static void fu_intel_usb4_device_class_init(FuIntelUsb4DeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_intel_usb4_device_to_string; klass_device->setup = fu_intel_usb4_device_setup; klass_device->prepare_firmware = fu_intel_usb4_device_prepare_firmware; klass_device->write_firmware = fu_intel_usb4_device_write_firmware; klass_device->activate = fu_intel_usb4_device_activate; klass_device->set_progress = fu_thunderbolt_device_set_progress; } fwupd-1.9.16/plugins/intel-usb4/fu-intel-usb4-device.h000066400000000000000000000014431460375044200224170ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2021 Intel Corporation. * Copyright (C) 2021 Dell Inc. * All rights reserved. * * This software and associated documentation (if any) is furnished * under a license and may only be used or copied in accordance * with the terms of the license. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * Dell Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_INTEL_USB4_DEVICE (fu_intel_usb4_device_get_type()) G_DECLARE_FINAL_TYPE(FuIntelUsb4Device, fu_intel_usb4_device, FU, INTEL_USB4_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/intel-usb4/fu-intel-usb4-plugin.c000066400000000000000000000037331460375044200224550ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-intel-usb4-device.h" #include "fu-intel-usb4-plugin.h" struct _FuIntelUsb4Plugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuIntelUsb4Plugin, fu_intel_usb4_plugin, FU_TYPE_PLUGIN) static void fu_intel_usb4_plugin_init(FuIntelUsb4Plugin *self) { fu_plugin_add_rule(FU_PLUGIN(self), FU_PLUGIN_RULE_RUN_BEFORE, "thunderbolt"); } static void fu_intel_usb4_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_INTEL_USB4_DEVICE); } static void fu_intel_usb4_plugin_device_registered(FuPlugin *plugin, FuDevice *device) { GPtrArray *devices = fu_plugin_get_devices(plugin); GPtrArray *instance_ids = fu_device_get_instance_ids(device); if (g_strcmp0(fu_device_get_plugin(device), "thunderbolt") != 0) return; /* prefer using this plugin over the thunderbolt one -- but the device ID is constructed * differently in each plugin as they're using very different update methods. * use the TBT-{nvm_vendor_id}{nvm_product_id} instance ID to match them up instead. */ for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); for (guint j = 0; j < instance_ids->len; j++) { const gchar *instance_id = g_ptr_array_index(instance_ids, j); if (g_str_has_prefix(instance_id, "TBT-") && fu_device_has_instance_id(device_tmp, instance_id)) { fu_device_remove_internal_flag( device, FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_inhibit(device, "hidden", "updated by the intel-usb4 plugin instead"); return; } } } } static void fu_intel_usb4_plugin_class_init(FuIntelUsb4PluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_intel_usb4_plugin_constructed; plugin_class->device_registered = fu_intel_usb4_plugin_device_registered; } fwupd-1.9.16/plugins/intel-usb4/fu-intel-usb4-plugin.h000066400000000000000000000003631460375044200224560ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuIntelUsb4Plugin, fu_intel_usb4_plugin, FU, INTEL_USB4_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/intel-usb4/fu-intel-usb4.rs000066400000000000000000000006231460375044200213560ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ // hub operation #[repr(u16le)] enum IntelUsb4Opcode { NVM_WRITE = 0x20, NVM_AUTH_WRITE = 0x21, NVM_READ = 0x22, NVM_SET_OFFSET = 0x23, DROM_READ = 0x24, } #[derive(New, Parse)] struct IntelUsb4Mbox { opcode: IntelUsb4Opcode, _rsvd: u8, status: u8, } fwupd-1.9.16/plugins/intel-usb4/intel-usb4.quirk000066400000000000000000000000541460375044200214530ustar00rootroot00000000000000[USB\VID_8087&PID_0B40] Plugin = intel_usb4 fwupd-1.9.16/plugins/intel-usb4/meson.build000066400000000000000000000007441460375044200205600ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginIntelUsb4"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('intel-usb4.quirk') plugin_builtins += static_library('fu_plugin_intel_usb4', rustgen.process('fu-intel-usb4.rs'), sources: [ 'fu-intel-usb4-device.c', 'fu-intel-usb4-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: [ plugin_deps, gudev, ], ) endif fwupd-1.9.16/plugins/iommu/000077500000000000000000000000001460375044200155515ustar00rootroot00000000000000fwupd-1.9.16/plugins/iommu/README.md000066400000000000000000000004071460375044200170310ustar00rootroot00000000000000--- title: Plugin: IOMMU --- ## Introduction This plugin checks if an IOMMU is available on the system. ## External Interface Access This plugin requires no extra access. ## Version Considerations This plugin has been available since fwupd version `1.5.0`. fwupd-1.9.16/plugins/iommu/fu-iommu-plugin.c000066400000000000000000000105321460375044200207500ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-iommu-plugin.h" struct _FuIommuPlugin { FuPlugin parent_instance; gboolean has_iommu; }; G_DEFINE_TYPE(FuIommuPlugin, fu_iommu_plugin, FU_TYPE_PLUGIN) static void fu_iommu_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuIommuPlugin *self = FU_IOMMU_PLUGIN(plugin); fu_string_append_kb(str, idt, "HasIommu", self->has_iommu); } static gboolean fu_iommu_plugin_backend_device_added(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { FuIommuPlugin *self = FU_IOMMU_PLUGIN(plugin); /* interesting device? */ if (!FU_IS_UDEV_DEVICE(device)) return TRUE; if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "iommu") != 0) return TRUE; self->has_iommu = TRUE; return TRUE; } static void fu_iommu_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuIommuPlugin *self = FU_IOMMU_PLUGIN(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GHashTable) cmdline = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_IOMMU); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); /* we might be able to fix this */ cmdline = fu_kernel_get_cmdline(&error_local); if (cmdline == NULL) { g_warning("failed to get kernel cmdline: %s", error_local->message); } else if (fu_kernel_check_cmdline_mutable(NULL)) { const gchar *value = g_hash_table_lookup(cmdline, "iommu"); fwupd_security_attr_set_kernel_current_value(attr, value); if (!g_hash_table_contains(cmdline, "iommu") && !g_hash_table_contains(cmdline, "intel_iommu") && !g_hash_table_contains(cmdline, "amd_iommu")) { fwupd_security_attr_set_kernel_target_value(attr, "iommu=force"); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_CAN_FIX); } if (g_strcmp0(value, "force") == 0) { fwupd_security_attr_set_kernel_target_value(attr, NULL); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_CAN_UNDO); } } fu_security_attr_add_bios_target_value(attr, "AmdVt", "enable"); fu_security_attr_add_bios_target_value(attr, "IOMMU", "enable"); fu_security_attr_add_bios_target_value(attr, "VtForDirectIo", "enable"); /** * Lenovo systems that offer a BIOS setting for ThunderboltAccess will * use this option to control whether the IOMMU is enabled by default * or not. * * It may be counter-intuitive; but as there are other more physically * difficult to attack PCIe devices it's better to have the IOMMU * enabled pre-boot even if it enables access to Thunderbolt/USB4. */ fu_security_attr_add_bios_target_value(attr, "com.thinklmi.ThunderboltAccess", "enable"); if (!self->has_iommu) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_iommu_plugin_init(FuIommuPlugin *self) { } static void fu_iommu_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_udev_subsystem(plugin, "iommu"); } static gboolean fu_iommu_fix_host_security_attr(FuPlugin *self, FwupdSecurityAttr *attr, GError **error) { return fu_kernel_add_cmdline_arg("iommu=force", error); } static gboolean fu_iommu_undo_host_security_attr(FuPlugin *self, FwupdSecurityAttr *attr, GError **error) { return fu_kernel_remove_cmdline_arg("iommu=force", error); } static void fu_iommu_plugin_class_init(FuIommuPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_iommu_plugin_constructed; plugin_class->to_string = fu_iommu_plugin_to_string; plugin_class->backend_device_added = fu_iommu_plugin_backend_device_added; plugin_class->add_security_attrs = fu_iommu_plugin_add_security_attrs; plugin_class->fix_host_security_attr = fu_iommu_fix_host_security_attr; plugin_class->undo_host_security_attr = fu_iommu_undo_host_security_attr; } fwupd-1.9.16/plugins/iommu/fu-iommu-plugin.h000066400000000000000000000003451460375044200207560ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuIommuPlugin, fu_iommu_plugin, FU, IOMMU_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/iommu/meson.build000066400000000000000000000006151460375044200177150ustar00rootroot00000000000000if hsi and (host_cpu == 'x86' or host_cpu == 'x86_64') cargs = ['-DG_LOG_DOMAIN="FuPluginIommu"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtins += static_library('fu_plugin_iommu', sources: [ 'fu-iommu-plugin.c', ], include_directories: plugin_incdirs, link_with: [ fwupdplugin, fwupd, ], c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/jabra-gnp/000077500000000000000000000000001460375044200162645ustar00rootroot00000000000000fwupd-1.9.16/plugins/jabra-gnp/README.md000066400000000000000000000017301460375044200175440ustar00rootroot00000000000000# Jabra GNP ## Introduction This plugin is used to firmware update for some Jabra devices (refer to the `jabra-gnp.quirk` file for more information). Notably this excludes devices supported by the `jabra` plugin, as well as 1st edition Jabra Evolve (non-SE) devices and the corresponding Jabra Link connectors. ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_0B0E&PID_24DB` ## Update Behavior The device is updated at runtime using USB control and interrupt transfers. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x0B0E` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.9.2`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Gianmarco: @gdpcastro fwupd-1.9.16/plugins/jabra-gnp/fu-jabra-gnp-common.c000066400000000000000000000100111460375044200221600ustar00rootroot00000000000000/* * Copyright (C) 2023 GN Audio * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-jabra-gnp-common.h" static guint64 fu_jabra_gnp_update_crc(guint64 acc, guint64 delta) { static guint64 crcLookupTable[] = { 0x00000000, 0xDB710641, 0x6D930AC3, 0xB6E20C82, 0xDB261586, 0x005713C7, 0xB6B51F45, 0x6DC41904, 0x6D3D2D4D, 0xB64C2B0C, 0x00AE278E, 0xDBDF21CF, 0xB61B38CB, 0x6D6A3E8A, 0xDB883208, 0x00F93449, 0xDA7A5A9A, 0x010B5CDB, 0xB7E95059, 0x6C985618, 0x015C4F1C, 0xDA2D495D, 0x6CCF45DF, 0xB7BE439E, 0xB74777D7, 0x6C367196, 0xDAD47D14, 0x01A57B55, 0x6C616251, 0xB7106410, 0x01F26892, 0xDA836ED3, 0x6F85B375, 0xB4F4B534, 0x0216B9B6, 0xD967BFF7, 0xB4A3A6F3, 0x6FD2A0B2, 0xD930AC30, 0x0241AA71, 0x02B89E38, 0xD9C99879, 0x6F2B94FB, 0xB45A92BA, 0xD99E8BBE, 0x02EF8DFF, 0xB40D817D, 0x6F7C873C, 0xB5FFE9EF, 0x6E8EEFAE, 0xD86CE32C, 0x031DE56D, 0x6ED9FC69, 0xB5A8FA28, 0x034AF6AA, 0xD83BF0EB, 0xD8C2C4A2, 0x03B3C2E3, 0xB551CE61, 0x6E20C820, 0x03E4D124, 0xD895D765, 0x6E77DBE7, 0xB506DDA6, 0xDF0B66EA, 0x047A60AB, 0xB2986C29, 0x69E96A68, 0x042D736C, 0xDF5C752D, 0x69BE79AF, 0xB2CF7FEE, 0xB2364BA7, 0x69474DE6, 0xDFA54164, 0x04D44725, 0x69105E21, 0xB2615860, 0x048354E2, 0xDFF252A3, 0x05713C70, 0xDE003A31, 0x68E236B3, 0xB39330F2, 0xDE5729F6, 0x05262FB7, 0xB3C42335, 0x68B52574, 0x684C113D, 0xB33D177C, 0x05DF1BFE, 0xDEAE1DBF, 0xB36A04BB, 0x681B02FA, 0xDEF90E78, 0x05880839, 0xB08ED59F, 0x6BFFD3DE, 0xDD1DDF5C, 0x066CD91D, 0x6BA8C019, 0xB0D9C658, 0x063BCADA, 0xDD4ACC9B, 0xDDB3F8D2, 0x06C2FE93, 0xB020F211, 0x6B51F450, 0x0695ED54, 0xDDE4EB15, 0x6B06E797, 0xB077E1D6, 0x6AF48F05, 0xB1858944, 0x076785C6, 0xDC168387, 0xB1D29A83, 0x6AA39CC2, 0xDC419040, 0x07309601, 0x07C9A248, 0xDCB8A409, 0x6A5AA88B, 0xB12BAECA, 0xDCEFB7CE, 0x079EB18F, 0xB17CBD0D, 0x6A0DBB4C, 0x6567CB95, 0xBE16CDD4, 0x08F4C156, 0xD385C717, 0xBE41DE13, 0x6530D852, 0xD3D2D4D0, 0x08A3D291, 0x085AE6D8, 0xD32BE099, 0x65C9EC1B, 0xBEB8EA5A, 0xD37CF35E, 0x080DF51F, 0xBEEFF99D, 0x659EFFDC, 0xBF1D910F, 0x646C974E, 0xD28E9BCC, 0x09FF9D8D, 0x643B8489, 0xBF4A82C8, 0x09A88E4A, 0xD2D9880B, 0xD220BC42, 0x0951BA03, 0xBFB3B681, 0x64C2B0C0, 0x0906A9C4, 0xD277AF85, 0x6495A307, 0xBFE4A546, 0x0AE278E0, 0xD1937EA1, 0x67717223, 0xBC007462, 0xD1C46D66, 0x0AB56B27, 0xBC5767A5, 0x672661E4, 0x67DF55AD, 0xBCAE53EC, 0x0A4C5F6E, 0xD13D592F, 0xBCF9402B, 0x6788466A, 0xD16A4AE8, 0x0A1B4CA9, 0xD098227A, 0x0BE9243B, 0xBD0B28B9, 0x667A2EF8, 0x0BBE37FC, 0xD0CF31BD, 0x662D3D3F, 0xBD5C3B7E, 0xBDA50F37, 0x66D40976, 0xD03605F4, 0x0B4703B5, 0x66831AB1, 0xBDF21CF0, 0x0B101072, 0xD0611633, 0xBA6CAD7F, 0x611DAB3E, 0xD7FFA7BC, 0x0C8EA1FD, 0x614AB8F9, 0xBA3BBEB8, 0x0CD9B23A, 0xD7A8B47B, 0xD7518032, 0x0C208673, 0xBAC28AF1, 0x61B38CB0, 0x0C7795B4, 0xD70693F5, 0x61E49F77, 0xBA959936, 0x6016F7E5, 0xBB67F1A4, 0x0D85FD26, 0xD6F4FB67, 0xBB30E263, 0x6041E422, 0xD6A3E8A0, 0x0DD2EEE1, 0x0D2BDAA8, 0xD65ADCE9, 0x60B8D06B, 0xBBC9D62A, 0xD60DCF2E, 0x0D7CC96F, 0xBB9EC5ED, 0x60EFC3AC, 0xD5E91E0A, 0x0E98184B, 0xB87A14C9, 0x630B1288, 0x0ECF0B8C, 0xD5BE0DCD, 0x635C014F, 0xB82D070E, 0xB8D43347, 0x63A53506, 0xD5473984, 0x0E363FC5, 0x63F226C1, 0xB8832080, 0x0E612C02, 0xD5102A43, 0x0F934490, 0xD4E242D1, 0x62004E53, 0xB9714812, 0xD4B55116, 0x0FC45757, 0xB9265BD5, 0x62575D94, 0x62AE69DD, 0xB9DF6F9C, 0x0F3D631E, 0xD44C655F, 0xB9887C5B, 0x62F97A1A, 0xD41B7698, 0x0F6A70D9}; guint64 t = acc >> 24; guint32 lookup = (guint32)(t & 0xFF); acc = ((acc) << 8) ^ crcLookupTable[lookup] ^ (delta); return acc & 0x00000000FFFFFFFF; } guint64 fu_jabra_gnp_calculate_crc(GBytes *bytes) { guint64 crc = 0; guint8 tmp[] = {0xFF, 0xFF, 0xFF, 0xFF}; g_autoptr(GByteArray) buf = g_byte_array_new(); g_byte_array_append(buf, tmp, sizeof(tmp)); fu_byte_array_append_bytes(buf, bytes); for (guint i = buf->len; i > 0; i -= 2) { if (i > 1) crc = fu_jabra_gnp_update_crc(crc, buf->data[i - 2] & 0xFF); crc = fu_jabra_gnp_update_crc(crc, buf->data[i - 1] & 0xFF); } return crc; } fwupd-1.9.16/plugins/jabra-gnp/fu-jabra-gnp-common.h000066400000000000000000000002571460375044200222000ustar00rootroot00000000000000/* * Copyright (C) 2023 GN Audio A/S * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include guint64 fu_jabra_gnp_calculate_crc(GBytes *bytes); fwupd-1.9.16/plugins/jabra-gnp/fu-jabra-gnp-device.c000066400000000000000000000564401460375044200221470ustar00rootroot00000000000000/* * Copyright (C) 2023 GN Audio * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-jabra-gnp-device.h" #include "fu-jabra-gnp-firmware.h" #include "fu-jabra-gnp-image.h" #define FU_JABRA_GNP_BUF_SIZE 63 #define FU_JABRA_GNP_MAX_RETRIES 3 #define FU_JABRA_GNP_RETRY_DELAY 100 /* ms */ #define FU_JABRA_GNP_STANDARD_SEND_TIMEOUT 3000 /* ms */ #define FU_JABRA_GNP_STANDARD_RECEIVE_TIMEOUT 1000 /* ms */ #define FU_JABRA_GNP_LONG_RECEIVE_TIMEOUT 30000 /* ms */ #define FU_JABRA_GNP_EXTRA_LONG_RECEIVE_TIMEOUT 60000 /* ms */ #define FU_JABRA_GNP_IFACE 0x05 struct _FuJabraGnpDevice { FuUsbDevice parent_instance; guint8 iface_hid; guint8 sequence_number; guint dfu_pid; }; typedef struct { guint8 *txbuf; const guint timeout; } FuJabraGnpTxData; typedef struct { guint8 *rxbuf; const guint timeout; } FuJabraGnpRxData; G_DEFINE_TYPE(FuJabraGnpDevice, fu_jabra_gnp_device, FU_TYPE_USB_DEVICE) static void fu_jabra_gnp_device_to_string(FuDevice *device, guint idt, GString *str) { FuJabraGnpDevice *self = FU_JABRA_GNP_DEVICE(device); fu_string_append_kx(str, idt, "IfaceHid", self->iface_hid); fu_string_append_kx(str, idt, "SequenceNumber", self->sequence_number); fu_string_append_kx(str, idt, "DfuPid", self->dfu_pid); } static guint8 _g_usb_device_get_interface_for_class(GUsbDevice *dev, guint8 intf_class, GError **error) { g_autoptr(GPtrArray) intfs = NULL; intfs = g_usb_device_get_interfaces(dev, error); if (intfs == NULL) return 0xFF; for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index(intfs, i); if (g_usb_interface_get_class(intf) == intf_class) return g_usb_interface_get_number(intf); } return 0xFF; } static gboolean fu_jabra_gnp_device_tx_cb(FuDevice *device, gpointer user_data, GError **error) { FuJabraGnpDevice *self = FU_JABRA_GNP_DEVICE(device); FuJabraGnpTxData *tx_data = (FuJabraGnpTxData *)user_data; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, 0x09, 0x0200 | FU_JABRA_GNP_IFACE, self->iface_hid, tx_data->txbuf, FU_JABRA_GNP_BUF_SIZE, NULL, tx_data->timeout, NULL, /* cancellable */ error)) { g_prefix_error(error, "failed to write to device: "); return FALSE; } return TRUE; } static gboolean fu_jabra_gnp_device_rx_cb(FuDevice *device, gpointer user_data, GError **error) { const guint8 match_buf[FU_JABRA_GNP_BUF_SIZE] = {FU_JABRA_GNP_IFACE, 0x08, 0x00, 0x00, 0x0A, 0x12, 0x02}; FuJabraGnpDevice *self = FU_JABRA_GNP_DEVICE(device); FuJabraGnpRxData *rx_data = (FuJabraGnpRxData *)user_data; if (!g_usb_device_interrupt_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), 0x81, rx_data->rxbuf, FU_JABRA_GNP_BUF_SIZE, NULL, rx_data->timeout, NULL, /* cancellable */ error)) { g_prefix_error(error, "failed to read from device: "); return FALSE; } else { if (rx_data->rxbuf[5] == match_buf[5] && rx_data->rxbuf[6] == match_buf[6]) { /* battery report, ignore and rx again */ if (!g_usb_device_interrupt_transfer( fu_usb_device_get_dev(FU_USB_DEVICE(self)), 0x81, rx_data->rxbuf, FU_JABRA_GNP_BUF_SIZE, NULL, rx_data->timeout, NULL, /* cancellable */ error)) { g_prefix_error(error, "failed to read from device: "); return FALSE; } } } return TRUE; } static gboolean fu_jabra_gnp_device_rx_with_sequence_cb(FuDevice *device, gpointer user_data, GError **error) { FuJabraGnpDevice *self = FU_JABRA_GNP_DEVICE(device); FuJabraGnpRxData *rx_data = (FuJabraGnpRxData *)user_data; if (!fu_jabra_gnp_device_rx_cb(device, user_data, error)) return FALSE; if (self->sequence_number != rx_data->rxbuf[3]) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "sequence_number error -- got 0x%x, expected 0x%x", rx_data->rxbuf[3], self->sequence_number); return FALSE; } self->sequence_number += 1; return TRUE; } static gboolean fu_jabra_gnp_device_read_name(FuJabraGnpDevice *self, GError **error) { guint8 txbuf[FU_JABRA_GNP_BUF_SIZE] = {FU_JABRA_GNP_IFACE, 0x08, 0x00, self->sequence_number, 0x46, 0x02, 0x00}; guint8 rxbuf[FU_JABRA_GNP_BUF_SIZE] = {0x00}; FuJabraGnpTxData tx_data = {.txbuf = txbuf, .timeout = FU_JABRA_GNP_STANDARD_SEND_TIMEOUT}; FuJabraGnpRxData rx_data = {.rxbuf = rxbuf, .timeout = FU_JABRA_GNP_STANDARD_RECEIVE_TIMEOUT}; g_autofree gchar *name = NULL; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_tx_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&tx_data, error)) return FALSE; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_rx_with_sequence_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&rx_data, error)) return FALSE; name = fu_memstrsafe(rxbuf, sizeof(rxbuf), 0x8, sizeof(rxbuf) - 8, error); if (name == NULL) return FALSE; fu_device_set_name(FU_DEVICE(self), name); return TRUE; } static gboolean fu_jabra_gnp_device_read_dfu_pid(FuJabraGnpDevice *self, GError **error) { guint8 txbuf[FU_JABRA_GNP_BUF_SIZE] = {FU_JABRA_GNP_IFACE, 0x08, 0x00, self->sequence_number, 0x46, 0x02, 0x13}; guint8 rxbuf[FU_JABRA_GNP_BUF_SIZE] = {0x00}; FuJabraGnpTxData tx_data = {.txbuf = txbuf, .timeout = FU_JABRA_GNP_STANDARD_SEND_TIMEOUT}; FuJabraGnpRxData rx_data = {.rxbuf = rxbuf, .timeout = FU_JABRA_GNP_STANDARD_RECEIVE_TIMEOUT}; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_tx_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&tx_data, error)) return FALSE; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_rx_with_sequence_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&rx_data, error)) return FALSE; self->dfu_pid = fu_memread_uint16(rx_data.rxbuf + 7, G_LITTLE_ENDIAN); return TRUE; } static gboolean fu_jabra_gnp_device_read_version(FuJabraGnpDevice *self, GError **error) { guint8 txbuf[FU_JABRA_GNP_BUF_SIZE] = {FU_JABRA_GNP_IFACE, 0x08, 0x00, self->sequence_number, 0x46, 0x02, 0x03}; guint8 rxbuf[FU_JABRA_GNP_BUF_SIZE] = {0x00}; FuJabraGnpTxData tx_data = {.txbuf = txbuf, .timeout = FU_JABRA_GNP_STANDARD_SEND_TIMEOUT}; FuJabraGnpRxData rx_data = {.rxbuf = rxbuf, .timeout = FU_JABRA_GNP_STANDARD_RECEIVE_TIMEOUT}; g_autofree gchar *version = NULL; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_tx_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&tx_data, error)) return FALSE; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_rx_with_sequence_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&rx_data, error)) return FALSE; version = fu_memstrsafe(rxbuf, sizeof(rxbuf), 0x8, sizeof(rxbuf) - 8, error); if (version == NULL) return FALSE; fu_device_set_version(FU_DEVICE(self), version); return TRUE; } static gboolean fu_jabra_gnp_device_write_partition(FuJabraGnpDevice *self, guint8 part, GError **error) { guint8 txbuf[FU_JABRA_GNP_BUF_SIZE] = {FU_JABRA_GNP_IFACE, 0x08, 0x00, self->sequence_number, 0x87, 0x0F, 0x2D, part}; guint8 rxbuf[FU_JABRA_GNP_BUF_SIZE] = {0x00}; FuJabraGnpTxData tx_data = {.txbuf = txbuf, .timeout = FU_JABRA_GNP_STANDARD_SEND_TIMEOUT}; FuJabraGnpRxData rx_data = {.rxbuf = rxbuf, .timeout = FU_JABRA_GNP_STANDARD_RECEIVE_TIMEOUT}; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_tx_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&tx_data, error)) return FALSE; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_rx_with_sequence_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&rx_data, error)) return FALSE; if (rx_data.rxbuf[5] != 0xFF) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "internal error: expected 0xFF, got 0x%02x 0x%02x", rx_data.rxbuf[5], rx_data.rxbuf[6]); return FALSE; } return TRUE; } static gboolean fu_jabra_gnp_device_start(FuJabraGnpDevice *self, GError **error) { guint8 txbuf[FU_JABRA_GNP_BUF_SIZE] = {FU_JABRA_GNP_IFACE, 0x08, 0x00, self->sequence_number, 0x86, 0x0F, 0x17}; guint8 rxbuf[FU_JABRA_GNP_BUF_SIZE] = {0x00}; FuJabraGnpTxData tx_data = {.txbuf = txbuf, .timeout = FU_JABRA_GNP_STANDARD_SEND_TIMEOUT}; FuJabraGnpRxData rx_data = {.rxbuf = rxbuf, .timeout = FU_JABRA_GNP_STANDARD_RECEIVE_TIMEOUT}; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_tx_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&tx_data, error)) return FALSE; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_rx_with_sequence_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&rx_data, error)) return FALSE; if (rx_data.rxbuf[5] != 0xFF) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "internal error: expected 0xFF, got 0x%02x 0x%02x", rx_data.rxbuf[5], rx_data.rxbuf[6]); return FALSE; } return TRUE; } static gboolean fu_jabra_gnp_device_flash_erase_done(FuJabraGnpDevice *self, GError **error) { guint8 rxbuf[FU_JABRA_GNP_BUF_SIZE] = {0x00}; const guint8 match_buf[FU_JABRA_GNP_BUF_SIZE] = {FU_JABRA_GNP_IFACE, 0x08, 0x00, 0x00, 0x06, 0x0F, 0x18}; FuJabraGnpRxData rx_data = {.rxbuf = rxbuf, .timeout = FU_JABRA_GNP_EXTRA_LONG_RECEIVE_TIMEOUT}; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_rx_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&rx_data, error)) return FALSE; if (rx_data.rxbuf[5] != match_buf[5] || rx_data.rxbuf[6] != match_buf[6]) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "internal error, buf did not match"); return FALSE; } return TRUE; } static gboolean fu_jabra_gnp_device_write_crc(FuJabraGnpDevice *self, guint32 crc, guint total_chunks, guint preload_count, GError **error) { guint8 txbuf[FU_JABRA_GNP_BUF_SIZE] = {FU_JABRA_GNP_IFACE, 0x08, 0x00, self->sequence_number, 0x8E, 0x0F, 0x19}; guint8 rxbuf[FU_JABRA_GNP_BUF_SIZE] = {0x00}; FuJabraGnpTxData tx_data = {.txbuf = txbuf, .timeout = FU_JABRA_GNP_STANDARD_SEND_TIMEOUT}; FuJabraGnpRxData rx_data = {.rxbuf = rxbuf, .timeout = FU_JABRA_GNP_STANDARD_RECEIVE_TIMEOUT}; fu_memwrite_uint32(txbuf + 7, crc, G_LITTLE_ENDIAN); fu_memwrite_uint16(txbuf + 11, total_chunks, G_LITTLE_ENDIAN); fu_memwrite_uint16(txbuf + 13, preload_count, G_LITTLE_ENDIAN); if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_tx_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&tx_data, error)) return FALSE; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_rx_with_sequence_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&rx_data, error)) return FALSE; if (rx_data.rxbuf[5] != 0xFF) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "internal error: expected 0xFF, got 0x%02x 0x%02x", rx_data.rxbuf[5], rx_data.rxbuf[6]); return FALSE; } return TRUE; } static gboolean fu_jabra_gnp_device_write_chunk(FuJabraGnpDevice *self, guint32 chunk_number, const guint8 *buf, guint32 bufsz, GError **error) { guint8 write_length = 0x00 + bufsz + 10; guint8 txbuf[FU_JABRA_GNP_BUF_SIZE] = {FU_JABRA_GNP_IFACE, 0x08, 0x00, 0x00, write_length, 0x0F, 0x1A}; FuJabraGnpTxData tx_data = {.txbuf = txbuf, .timeout = FU_JABRA_GNP_STANDARD_SEND_TIMEOUT}; fu_memwrite_uint16(txbuf + 7, chunk_number, G_LITTLE_ENDIAN); fu_memwrite_uint16(txbuf + 9, bufsz, G_LITTLE_ENDIAN); if (!fu_memcpy_safe(txbuf, sizeof(txbuf), 11, buf, bufsz, 0x0, bufsz, error)) return FALSE; return fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_tx_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&tx_data, error); } static gboolean fu_jabra_gnp_device_write_chunks(FuJabraGnpDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { gboolean failed_chunk = FALSE; guint32 preload_count = 100; guint8 rxbuf[FU_JABRA_GNP_BUF_SIZE] = {0x00}; const guint8 match_buf[FU_JABRA_GNP_BUF_SIZE] = {FU_JABRA_GNP_IFACE, 0x08, 0x00, 0x00, 0x06, 0x0F, 0x1B}; FuJabraGnpRxData rx_data = {.rxbuf = rxbuf, .timeout = FU_JABRA_GNP_LONG_RECEIVE_TIMEOUT}; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (gint chunk_number = 0; (guint)chunk_number < fu_chunk_array_length(chunks); chunk_number++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, chunk_number); if (!fu_jabra_gnp_device_write_chunk(self, chunk_number, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; if (((chunk_number % preload_count) == 0) || (guint)chunk_number == fu_chunk_array_length(chunks) - 1) { if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_rx_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&rx_data, error)) return FALSE; if (rx_data.rxbuf[5] != match_buf[5] || rx_data.rxbuf[6] != match_buf[6]) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "internal error, buf did not match"); return FALSE; } if (fu_memread_uint16(rxbuf + 7, G_LITTLE_ENDIAN) != chunk_number) { chunk_number--; failed_chunk = TRUE; } else failed_chunk = FALSE; } if (!failed_chunk) fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_jabra_gnp_device_read_verify_status(FuJabraGnpDevice *self, GError **error) { guint8 rxbuf[FU_JABRA_GNP_BUF_SIZE] = {0x00}; const guint8 match_buf[FU_JABRA_GNP_BUF_SIZE] = {FU_JABRA_GNP_IFACE, 0x08, 0x00, 0x00, 0x06, 0x0F, 0x1C}; FuJabraGnpRxData rx_data = {.rxbuf = rxbuf, .timeout = FU_JABRA_GNP_LONG_RECEIVE_TIMEOUT}; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_rx_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&rx_data, error)) return FALSE; if (rx_data.rxbuf[5] != match_buf[5] || rx_data.rxbuf[6] != match_buf[6]) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "internal error, buf did not match"); return FALSE; } return TRUE; } static gboolean fu_jabra_gnp_device_write_version(FuJabraGnpDevice *self, FuJabraGnpVersionData *version_data, GError **error) { guint8 txbuf[FU_JABRA_GNP_BUF_SIZE] = {FU_JABRA_GNP_IFACE, 0x08, 0x00, self->sequence_number, 0x89, 0x0F, 0x1E, version_data->major, version_data->minor, version_data->micro}; guint8 rxbuf[FU_JABRA_GNP_BUF_SIZE] = {0x00}; FuJabraGnpTxData tx_data = {.txbuf = txbuf, .timeout = FU_JABRA_GNP_STANDARD_SEND_TIMEOUT}; FuJabraGnpRxData rx_data = {.rxbuf = rxbuf, .timeout = FU_JABRA_GNP_STANDARD_RECEIVE_TIMEOUT}; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_tx_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&tx_data, error)) return FALSE; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_rx_with_sequence_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&rx_data, error)) return FALSE; if (rx_data.rxbuf[5] != 0xFF) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "internal error: expected 0xFF, got 0x%02x 0x%02x", rx_data.rxbuf[5], rx_data.rxbuf[6]); return FALSE; } return TRUE; } static gboolean fu_jabra_gnp_device_write_dfu_from_squif(FuJabraGnpDevice *self, GError **error) { guint8 txbuf[FU_JABRA_GNP_BUF_SIZE] = {FU_JABRA_GNP_IFACE, 0x08, 0x00, self->sequence_number, 0x86, 0x0F, 0x1D}; guint8 rxbuf[FU_JABRA_GNP_BUF_SIZE] = {0x00}; FuJabraGnpTxData tx_data = {.txbuf = txbuf, .timeout = FU_JABRA_GNP_STANDARD_SEND_TIMEOUT}; FuJabraGnpRxData rx_data = {.rxbuf = rxbuf, .timeout = FU_JABRA_GNP_STANDARD_RECEIVE_TIMEOUT}; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_tx_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&tx_data, error)) return FALSE; if (!fu_device_retry_full(FU_DEVICE(self), fu_jabra_gnp_device_rx_with_sequence_cb, FU_JABRA_GNP_MAX_RETRIES, FU_JABRA_GNP_RETRY_DELAY, (gpointer)&rx_data, error)) return FALSE; if (rx_data.rxbuf[5] != 0xFF) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "internal error: expected 0xFF, got 0x%02x 0x%02x", rx_data.rxbuf[5], rx_data.rxbuf[6]); return FALSE; } return TRUE; } static FuFirmware * fu_jabra_gnp_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuJabraGnpDevice *self = FU_JABRA_GNP_DEVICE(device); g_autoptr(FuFirmware) firmware = fu_jabra_gnp_firmware_new(); /* unzip and get images */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; if (fu_jabra_gnp_firmware_get_dfu_pid(FU_JABRA_GNP_FIRMWARE(firmware)) != self->dfu_pid) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "wrong DFU PID, got 0x%x, expected 0x%x", fu_jabra_gnp_firmware_get_dfu_pid(FU_JABRA_GNP_FIRMWARE(firmware)), self->dfu_pid); return NULL; } return g_steal_pointer(&firmware); } static gboolean fu_jabra_gnp_device_probe(FuDevice *device, GError **error) { g_autoptr(GError) error_local = NULL; FuJabraGnpDevice *self = FU_JABRA_GNP_DEVICE(device); self->iface_hid = _g_usb_device_get_interface_for_class(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_CLASS_HID, &error_local); if (self->iface_hid == 0xFF) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot find HID interface: %s", error_local->message); return FALSE; } fu_usb_device_add_interface(FU_USB_DEVICE(self), self->iface_hid); return TRUE; } static gboolean fu_jabra_gnp_device_setup(FuDevice *device, GError **error) { FuJabraGnpDevice *self = FU_JABRA_GNP_DEVICE(device); if (!fu_jabra_gnp_device_read_name(self, error)) return FALSE; if (!fu_jabra_gnp_device_read_version(self, error)) return FALSE; if (!fu_jabra_gnp_device_read_dfu_pid(self, error)) return FALSE; return TRUE; } static gboolean fu_jabra_gnp_device_write_image(FuJabraGnpDevice *self, FuFirmware *firmware, FuFirmware *img, FuProgress *progress, GError **error) { const guint chunk_size = 52; g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GBytes) blob = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "write-partition"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, "start"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 5, "flash-erase-done"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 91, "write-chunks"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "read-verify-status"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "write-version"); /* write partition */ blob = fu_firmware_get_bytes(img, error); if (blob == NULL) return FALSE; if (!fu_jabra_gnp_device_write_partition(self, fu_firmware_get_idx(img), error)) return FALSE; fu_progress_step_done(progress); /* start erasing */ if (!fu_jabra_gnp_device_start(self, error)) return FALSE; fu_progress_step_done(progress); /* poll for erase done */ if (!fu_jabra_gnp_device_flash_erase_done(self, error)) return FALSE; fu_progress_step_done(progress); /* write chunks */ chunks = fu_chunk_array_new_from_bytes(blob, 0x00, chunk_size); if (!fu_jabra_gnp_device_write_crc(self, fu_jabra_gnp_image_get_crc32(FU_JABRA_GNP_IMAGE(img)), fu_chunk_array_length(chunks), 100, error)) return FALSE; if (!fu_jabra_gnp_device_write_chunks(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* verify */ if (!fu_jabra_gnp_device_read_verify_status(self, error)) return FALSE; fu_progress_step_done(progress); /* write version */ if (!fu_jabra_gnp_device_write_version( self, fu_jabra_gnp_firmware_get_version_data(FU_JABRA_GNP_FIRMWARE(firmware)), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_jabra_gnp_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuJabraGnpDevice *self = FU_JABRA_GNP_DEVICE(device); g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); fu_progress_add_step(progress, FWUPD_STATUS_UNKNOWN, fu_firmware_get_size(img), fu_firmware_get_id(img)); } for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); if (!fu_jabra_gnp_device_write_image(self, firmware, img, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write %s: ", fu_firmware_get_id(img)); return FALSE; } fu_progress_step_done(progress); } /* write squif */ if (!fu_jabra_gnp_device_write_dfu_from_squif(self, error)) return FALSE; return TRUE; } static void fu_colorhug_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 85, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "reload"); } static void fu_jabra_gnp_device_init(FuJabraGnpDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_protocol(FU_DEVICE(self), "com.jabra.gnp"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_JABRA_GNP_FIRMWARE); } static void fu_jabra_gnp_device_class_init(FuJabraGnpDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_jabra_gnp_device_to_string; klass_device->prepare_firmware = fu_jabra_gnp_device_prepare_firmware; klass_device->probe = fu_jabra_gnp_device_probe; klass_device->setup = fu_jabra_gnp_device_setup; klass_device->write_firmware = fu_jabra_gnp_device_write_firmware; klass_device->set_progress = fu_colorhug_device_set_progress; } fwupd-1.9.16/plugins/jabra-gnp/fu-jabra-gnp-device.h000066400000000000000000000004351460375044200221450ustar00rootroot00000000000000/* * Copyright (C) 2023 GN Audio A/S * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_JABRA_GNP_DEVICE (fu_jabra_gnp_device_get_type()) G_DECLARE_FINAL_TYPE(FuJabraGnpDevice, fu_jabra_gnp_device, FU, JABRA_GNP_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/jabra-gnp/fu-jabra-gnp-firmware.c000066400000000000000000000130341460375044200225140ustar00rootroot00000000000000/* * Copyright (C) 2023 GN Audio A/S * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-jabra-gnp-firmware.h" #include "fu-jabra-gnp-image.h" struct _FuJabraGnpFirmware { FuArchiveFirmware parent_instance; guint16 dfu_pid; FuJabraGnpVersionData version_data; }; G_DEFINE_TYPE(FuJabraGnpFirmware, fu_jabra_gnp_firmware, FU_TYPE_FIRMWARE) static void fu_jabra_gnp_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuJabraGnpFirmware *self = FU_JABRA_GNP_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "dfu_pid", self->dfu_pid); } static gboolean fu_jabra_gnp_firmware_parse_version(FuJabraGnpFirmware *self, GError **error) { guint64 val = 0; g_auto(GStrv) split = NULL; split = g_strsplit(fu_firmware_get_version(FU_FIRMWARE(self)), ".", -1); if (g_strv_length(split) != 3) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "version invalid"); return FALSE; } if (!fu_strtoull(split[0], &val, 0x0, 0xFF, error)) return FALSE; self->version_data.major = (guint8)val; if (!fu_strtoull(split[1], &val, 0x0, 0xFF, error)) return FALSE; self->version_data.minor = (guint8)val; if (!fu_strtoull(split[2], &val, 0x0, 0xFF, error)) return FALSE; self->version_data.micro = (guint8)val; /* success */ return TRUE; } static gboolean fu_jabra_gnp_firmware_parse_info(FuJabraGnpFirmware *self, XbSilo *silo, GError **error) { const gchar *version; const gchar *dfu_pid_str; guint64 val = 0; g_autoptr(XbNode) dfu_pid = NULL; g_autoptr(XbNode) build_vector = NULL; build_vector = xb_silo_query_first(silo, "buildVector", error); if (build_vector == NULL) return FALSE; /* only first? */ version = xb_node_get_attr(build_vector, "version"); if (version == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "buildVector version missing"); return FALSE; } fu_firmware_set_version(FU_FIRMWARE(self), version); if (!fu_jabra_gnp_firmware_parse_version(self, error)) return FALSE; dfu_pid = xb_silo_query_first(silo, "buildVector/targetUsbPids", error); if (dfu_pid == NULL) return FALSE; dfu_pid_str = xb_node_query_text(dfu_pid, "usbPid", error); if (dfu_pid_str == NULL) return FALSE; if (!fu_strtoull(dfu_pid_str, &val, 0x0, 0xFFFF, error)) { g_prefix_error(error, "cannot parse usbPid of %s: ", dfu_pid_str); return FALSE; } self->dfu_pid = (guint16)val; /* success */ return TRUE; } static gboolean fu_jabra_gnp_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuJabraGnpFirmware *self = FU_JABRA_GNP_FIRMWARE(firmware); g_autoptr(FuFirmware) firmware_archive = fu_archive_firmware_new(); g_autoptr(FuFirmware) img_xml = NULL; g_autoptr(GBytes) img_blob = NULL; g_autoptr(GPtrArray) files = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; /* FuArchiveFirmware->parse */ fu_archive_firmware_set_format(FU_ARCHIVE_FIRMWARE(firmware_archive), FU_ARCHIVE_FORMAT_ZIP); fu_archive_firmware_set_compression(FU_ARCHIVE_FIRMWARE(firmware_archive), FU_ARCHIVE_COMPRESSION_NONE); if (!fu_firmware_parse_full(firmware_archive, fw, offset, flags, error)) return FALSE; /* parse the XML metadata */ img_xml = fu_archive_firmware_get_image_fnmatch(FU_ARCHIVE_FIRMWARE(firmware_archive), "info.xml", error); if (img_xml == NULL) return FALSE; img_blob = fu_firmware_get_bytes(img_xml, error); if (img_blob == NULL) return FALSE; if (!xb_builder_source_load_bytes(source, img_blob, XB_BUILDER_SOURCE_FLAG_NONE, error)) return FALSE; xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; if (!fu_jabra_gnp_firmware_parse_info(self, silo, error)) return FALSE; files = xb_silo_query(silo, "buildVector/files/file", 0, error); if (files == NULL) return FALSE; for (guint i = 0; i < files->len; i++) { XbNode *n = g_ptr_array_index(files, i); g_autoptr(FuJabraGnpImage) img = fu_jabra_gnp_image_new(); g_autoptr(GError) error_local = NULL; if (!fu_jabra_gnp_image_parse(img, n, firmware_archive, &error_local)) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_DATA)) { g_debug("ignoring image 0x%x: %s", i, error_local->message); continue; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } if (!fu_firmware_add_image_full(firmware, FU_FIRMWARE(img), error)) return FALSE; } /* success */ return TRUE; } guint16 fu_jabra_gnp_firmware_get_dfu_pid(FuJabraGnpFirmware *self) { g_return_val_if_fail(FU_IS_JABRA_GNP_FIRMWARE(self), G_MAXUINT16); return self->dfu_pid; } FuJabraGnpVersionData * fu_jabra_gnp_firmware_get_version_data(FuJabraGnpFirmware *self) { g_return_val_if_fail(FU_IS_JABRA_GNP_FIRMWARE(self), NULL); return &self->version_data; } static void fu_jabra_gnp_firmware_init(FuJabraGnpFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); } static void fu_jabra_gnp_firmware_class_init(FuJabraGnpFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_jabra_gnp_firmware_parse; klass_firmware->export = fu_jabra_gnp_firmware_export; } FuFirmware * fu_jabra_gnp_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_JABRA_GNP_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/jabra-gnp/fu-jabra-gnp-firmware.h000066400000000000000000000011141460375044200225150ustar00rootroot00000000000000/* * Copyright (C) 2023 GN Audio A/S * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_JABRA_GNP_FIRMWARE (fu_jabra_gnp_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuJabraGnpFirmware, fu_jabra_gnp_firmware, FU, JABRA_GNP_FIRMWARE, FuFirmware) typedef struct { guint8 major; guint8 minor; guint8 micro; } FuJabraGnpVersionData; FuFirmware * fu_jabra_gnp_firmware_new(void); guint16 fu_jabra_gnp_firmware_get_dfu_pid(FuJabraGnpFirmware *self); FuJabraGnpVersionData * fu_jabra_gnp_firmware_get_version_data(FuJabraGnpFirmware *self); fwupd-1.9.16/plugins/jabra-gnp/fu-jabra-gnp-image.c000066400000000000000000000070731460375044200217700ustar00rootroot00000000000000/* * Copyright (C) 2023 GN Audio A/S * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-jabra-gnp-common.h" #include "fu-jabra-gnp-image.h" struct _FuJabraGnpImage { FuArchiveFirmware parent_instance; guint32 crc32; }; G_DEFINE_TYPE(FuJabraGnpImage, fu_jabra_gnp_image, FU_TYPE_FIRMWARE) static void fu_jabra_gnp_image_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuJabraGnpImage *self = FU_JABRA_GNP_IMAGE(firmware); fu_xmlb_builder_insert_kx(bn, "crc32", self->crc32); } gboolean fu_jabra_gnp_image_parse(FuJabraGnpImage *self, XbNode *n, FuFirmware *firmware_archive, GError **error) { const gchar *crc_str = NULL; const gchar *language; const gchar *name; const gchar *part_str = NULL; guint64 crc_expected = 0; guint64 partition = 0; g_autoptr(FuFirmware) img_archive = NULL; g_autoptr(GBytes) blob = NULL; /* only match on US English */ language = xb_node_query_text(n, "language", NULL); if (language == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "language missing"); return FALSE; } if (g_strcmp0(language, "English") != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "language was not 'English', got '%s'", language); return FALSE; } /* get the CRC */ crc_str = xb_node_query_text(n, "crc", NULL); if (crc_str == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "crc missing"); return FALSE; } if (!fu_strtoull(crc_str, &crc_expected, 0x0, 0xFFFFFFFF, error)) { g_prefix_error(error, "cannot parse crc of %s: ", crc_str); return FALSE; } /* get the partition number */ part_str = xb_node_query_text(n, "partition", NULL); if (part_str == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "partition missing"); return FALSE; } if (!fu_strtoull(part_str, &partition, 0x0, 0xFFFFFFFF, error)) { g_prefix_error(error, "cannot parse partition of %s: ", part_str); return FALSE; } fu_firmware_set_idx(FU_FIRMWARE(self), partition); /* get the file pointed to by 'name' */ name = xb_node_get_attr(n, "name"); if (name == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "name missing"); return FALSE; } fu_firmware_set_id(FU_FIRMWARE(self), name); img_archive = fu_archive_firmware_get_image_fnmatch(FU_ARCHIVE_FIRMWARE(firmware_archive), name, error); if (img_archive == NULL) return FALSE; blob = fu_firmware_get_bytes(img_archive, error); if (blob == NULL) return FALSE; /* verify the CRC */ self->crc32 = fu_jabra_gnp_calculate_crc(blob); if (self->crc32 != (guint32)crc_expected) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum invalid, got 0x%x, expected 0x%x", (guint)self->crc32, (guint)crc_expected); return FALSE; } /* success */ fu_firmware_set_bytes(FU_FIRMWARE(self), blob); return TRUE; } guint32 fu_jabra_gnp_image_get_crc32(FuJabraGnpImage *self) { g_return_val_if_fail(FU_IS_JABRA_GNP_IMAGE(self), G_MAXUINT32); return self->crc32; } static void fu_jabra_gnp_image_init(FuJabraGnpImage *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); } static void fu_jabra_gnp_image_class_init(FuJabraGnpImageClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->export = fu_jabra_gnp_image_export; } FuJabraGnpImage * fu_jabra_gnp_image_new(void) { return FU_JABRA_GNP_IMAGE(g_object_new(FU_TYPE_JABRA_GNP_IMAGE, NULL)); } fwupd-1.9.16/plugins/jabra-gnp/fu-jabra-gnp-image.h000066400000000000000000000010051460375044200217620ustar00rootroot00000000000000/* * Copyright (C) 2023 GN Audio A/S * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_JABRA_GNP_IMAGE (fu_jabra_gnp_image_get_type()) G_DECLARE_FINAL_TYPE(FuJabraGnpImage, fu_jabra_gnp_image, FU, JABRA_GNP_IMAGE, FuFirmware) FuJabraGnpImage * fu_jabra_gnp_image_new(void); gboolean fu_jabra_gnp_image_parse(FuJabraGnpImage *self, XbNode *n, FuFirmware *firmware_archive, GError **error); guint32 fu_jabra_gnp_image_get_crc32(FuJabraGnpImage *self); fwupd-1.9.16/plugins/jabra-gnp/fu-jabra-gnp-plugin.c000066400000000000000000000014761460375044200222050ustar00rootroot00000000000000/* * Copyright (C) 2023 GN Audio A/S * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-jabra-gnp-device.h" #include "fu-jabra-gnp-firmware.h" #include "fu-jabra-gnp-plugin.h" struct _FuJabraGnpPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuJabraGnpPlugin, fu_jabra_gnp_plugin, FU_TYPE_PLUGIN) static void fu_jabra_gnp_plugin_init(FuJabraGnpPlugin *self) { } static void fu_jabra_gnp_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_JABRA_GNP_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_JABRA_GNP_FIRMWARE); } static void fu_jabra_gnp_plugin_class_init(FuJabraGnpPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_jabra_gnp_plugin_constructed; } fwupd-1.9.16/plugins/jabra-gnp/fu-jabra-gnp-plugin.h000066400000000000000000000003301460375044200221760ustar00rootroot00000000000000/* * Copyright (C) 2023 GN Audio A/S * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuJabraGnpPlugin, fu_jabra_gnp_plugin, FU, JABRA_GNP_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/jabra-gnp/jabra-gnp.quirk000066400000000000000000000106661460375044200212130ustar00rootroot00000000000000# Jabra Evolve 65 SE Stereo [runtime] [USB\VID_0B0E&PID_24FC] Plugin = jabra_gnp # Jabra Evolve 65 SE Stereo [runtime] [USB\VID_0B0E&PID_24FD] Plugin = jabra_gnp # Jabra Evolve 65 SE Stereo [runtime] [USB\VID_0B0E&PID_24FE] Plugin = jabra_gnp # Jabra Evolve 75 SE [runtime] [USB\VID_0B0E&PID_2502] Plugin = jabra_gnp # Jabra Evolve 75 SE [runtime] [USB\VID_0B0E&PID_2503] Plugin = jabra_gnp # Jabra Evolve 75 SE [runtime] [USB\VID_0B0E&PID_2504] Plugin = jabra_gnp # Jabra Evolve 65e [runtime] [USB\VID_0B0E&PID_248E] Plugin = jabra_gnp # Jabra Evolve 65t [runtime] [USB\VID_0B0E&PID_2497] Plugin = jabra_gnp # Jabra Link 380 [runtime] [USB\VID_0B0E&PID_24C7] Plugin = jabra_gnp # Jabra Link 380 [runtime] [USB\VID_0B0E&PID_24C8] Plugin = jabra_gnp # Jabra Link 380 [runtime] [USB\VID_0B0E&PID_24C9] Plugin = jabra_gnp # Jabra Link 380 [runtime] [USB\VID_0B0E&PID_24CA] Plugin = jabra_gnp # Jabra Evolve2 65 Mono [runtime] [USB\VID_0B0E&PID_24B3] Plugin = jabra_gnp # Jabra Evolve2 65 Mono [runtime] [USB\VID_0B0E&PID_24B4] Plugin = jabra_gnp # Jabra Evolve2 65 Mono [runtime] [USB\VID_0B0E&PID_24B5] Plugin = jabra_gnp # Jabra Evolve2 65 Mono [runtime] [USB\VID_0B0E&PID_24B6] Plugin = jabra_gnp # Jabra Evolve2 65 Stereo [runtime] [USB\VID_0B0E&PID_24B7] Plugin = jabra_gnp # Jabra Evolve2 65 Stereo [runtime] [USB\VID_0B0E&PID_24B8] Plugin = jabra_gnp # Jabra Evolve2 65 Stereo [runtime] [USB\VID_0B0E&PID_24A3] Plugin = jabra_gnp # Jabra Evolve2 65 Stereo [runtime] [USB\VID_0B0E&PID_24A4] Plugin = jabra_gnp # Jabra Evolve2 75 [runtime] [USB\VID_0B0E&PID_24D9] Plugin = jabra_gnp # Jabra Evolve2 75 [runtime] [USB\VID_0B0E&PID_24DA] Plugin = jabra_gnp # Jabra Evolve2 75 [runtime] [USB\VID_0B0E&PID_24DB] Plugin = jabra_gnp # Jabra Evolve2 85 [runtime] [USB\VID_0B0E&PID_24B9] Plugin = jabra_gnp # Jabra Evolve2 85 [runtime] [USB\VID_0B0E&PID_24BA] Plugin = jabra_gnp # Jabra Evolve2 85 [runtime] [USB\VID_0B0E&PID_24BB] Plugin = jabra_gnp # Jabra Evolve2 85 [runtime] [USB\VID_0B0E&PID_24BC] Plugin = jabra_gnp # Jabra Evolve2 75 [runtime] [USB\VID_0B0E&PID_24D9] Plugin = jabra_gnp # Jabra Speak2 40 [runtime] [USB\VID_0B0E&PID_AE6D] Plugin = jabra_gnp # Jabra Speak2 40 [runtime] [USB\VID_0B0E&PID_AE6B] Plugin = jabra_gnp # Jabra Speak2 40 [runtime] [USB\VID_0B0E&PID_AE6F] Plugin = jabra_gnp # Jabra Speak2 55 [runtime] [USB\VID_0B0E&PID_AE6C] Plugin = jabra_gnp # Jabra Speak2 55 [runtime] [USB\VID_0B0E&PID_AE6A] Plugin = jabra_gnp # Jabra Speak2 55 [runtime] [USB\VID_0B0E&PID_AE6E] Plugin = jabra_gnp # Jabra Speak2 55 [runtime] [USB\VID_0B0E&PID_AE70] Plugin = jabra_gnp # Jabra Speak2 75 [runtime] [USB\VID_0B0E&PID_24EF] Plugin = jabra_gnp # Jabra Speak2 75 [runtime] [USB\VID_0B0E&PID_24F0] Plugin = jabra_gnp # Jabra Speak2 75 [runtime] [USB\VID_0B0E&PID_24F1] Plugin = jabra_gnp # Jabra Evolve2 50 Mono [runtime] [USB\VID_0B0E&PID_2508] Plugin = jabra_gnp # Jabra Evolve2 50 Mono [runtime] [USB\VID_0B0E&PID_2509] Plugin = jabra_gnp # Jabra Evolve2 50 Mono [runtime] [USB\VID_0B0E&PID_250A] Plugin = jabra_gnp # Jabra Evolve2 50 Stereo [runtime] [USB\VID_0B0E&PID_2505] Plugin = jabra_gnp # Jabra Evolve2 50 Stereo [runtime] [USB\VID_0B0E&PID_2506] Plugin = jabra_gnp # Jabra Evolve2 50 Stereo [runtime] [USB\VID_0B0E&PID_2507] Plugin = jabra_gnp # Jabra Evolve2 55 Mono [runtime] [USB\VID_0B0E&PID_24F6] Plugin = jabra_gnp # Jabra Evolve2 55 Mono [runtime] [USB\VID_0B0E&PID_24F7] Plugin = jabra_gnp # Jabra Evolve2 55 Mono [runtime] [USB\VID_0B0E&PID_24F8] Plugin = jabra_gnp # Jabra Evolve2 55 Secure Stereo [runtime] [USB\VID_0B0E&PID_2512] Plugin = jabra_gnp # Jabra Evolve2 55 Secure Stereo [runtime] [USB\VID_0B0E&PID_2513] Plugin = jabra_gnp # Jabra Evolve2 55 Secure Stereo [runtime] [USB\VID_0B0E&PID_2514] Plugin = jabra_gnp # Jabra Evolve2 55 Stereo [runtime] [USB\VID_0B0E&PID_24F3] Plugin = jabra_gnp # Jabra Evolve2 55 Stereo [runtime] [USB\VID_0B0E&PID_24F4] Plugin = jabra_gnp # Jabra Evolve2 55 Stereo [runtime] [USB\VID_0B0E&PID_24F5] Plugin = jabra_gnp # Jabra Evolve2 65 Flex [runtime] [USB\VID_0B0E&PID_2517] Plugin = jabra_gnp # Jabra Evolve2 65 Flex [runtime] [USB\VID_0B0E&PID_2518] Plugin = jabra_gnp # Jabra Evolve2 65 Flex [runtime] [USB\VID_0B0E&PID_2519] Plugin = jabra_gnp # Jabra Evolve2 65 Flex Secure [runtime] [USB\VID_0B0E&PID_2523] Plugin = jabra_gnp # Jabra Evolve2 65 Flex Secure [runtime] [USB\VID_0B0E&PID_2524] Plugin = jabra_gnp # Jabra Evolve2 65 Flex Secure [runtime] [USB\VID_0B0E&PID_2525] Plugin = jabra_gnp fwupd-1.9.16/plugins/jabra-gnp/meson.build000066400000000000000000000007721460375044200204340ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginJabraGnp"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('jabra-gnp.quirk') plugin_builtins += static_library('fu_plugin_jabra_gnp', sources: [ 'fu-jabra-gnp-common.c', 'fu-jabra-gnp-device.c', 'fu-jabra-gnp-firmware.c', 'fu-jabra-gnp-image.c', 'fu-jabra-gnp-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/jabra/000077500000000000000000000000001460375044200155025ustar00rootroot00000000000000fwupd-1.9.16/plugins/jabra/README.md000066400000000000000000000020661460375044200167650ustar00rootroot00000000000000--- title: Plugin: Jabra --- ## Introduction This plugin is used to detach Jabra Speak Gen 1 devices to DFU mode. ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_0B0E&PID_0412` ## Quirk Use This plugin uses the following plugin-specific quirks: ### JabraMagic Two magic bytes sent to detach into DFU mode. Since: 1.3.3 ## Update Behavior The device usually presents in runtime mode, but on detach re-enumerates with a different USB VID and PID in DFU APP mode. The device is then further detached by the `dfu` plugin. On DFU attach the device again re-enumerates back to the Jabra runtime mode. For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x0A12` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.3.3`. fwupd-1.9.16/plugins/jabra/fu-jabra-device.c000066400000000000000000000113351460375044200205750ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-jabra-device.h" struct _FuJabraDevice { FuUsbDevice parent_instance; gchar *magic; }; G_DEFINE_TYPE(FuJabraDevice, fu_jabra_device, FU_TYPE_USB_DEVICE) static void fu_jabra_device_to_string(FuDevice *device, guint idt, GString *str) { FuJabraDevice *self = FU_JABRA_DEVICE(device); fu_string_append(str, idt, "Magic", self->magic); } static guint8 _g_usb_device_get_interface_for_class(GUsbDevice *dev, guint8 intf_class, GError **error) { g_autoptr(GPtrArray) intfs = NULL; intfs = g_usb_device_get_interfaces(dev, error); if (intfs == NULL) return 0xff; for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index(intfs, i); if (g_usb_interface_get_class(intf) == intf_class) return g_usb_interface_get_number(intf); } return 0xff; } /* slightly weirdly, this magic turns the device into appIDLE, so we * need the DFU plugin to further detach us into dfuIDLE */ static gboolean fu_jabra_device_prepare(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuJabraDevice *self = FU_JABRA_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); gsize magiclen = strlen(self->magic); guint8 adr = 0x00; guint8 rep = 0x00; guint8 iface_hid; guint8 buf[33] = {0x00}; g_autoptr(GError) error_local = NULL; /* parse string and create magic packet */ if (!fu_firmware_strparse_uint8_safe(self->magic, magiclen, 0, &rep, error)) return FALSE; if (!fu_firmware_strparse_uint8_safe(self->magic, magiclen, 2, &adr, error)) return FALSE; buf[0] = rep; buf[1] = adr; buf[2] = 0x00; buf[3] = 0x01; buf[4] = 0x85; buf[5] = 0x07; /* detach the HID interface from the kernel driver */ iface_hid = _g_usb_device_get_interface_for_class(usb_device, G_USB_DEVICE_CLASS_HID, &error_local); if (iface_hid == 0xff) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot find HID interface: %s", error_local->message); return FALSE; } g_debug("claiming interface 0x%02x", iface_hid); if (!g_usb_device_claim_interface(usb_device, (gint)iface_hid, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot claim interface 0x%02x: %s", iface_hid, error_local->message); return FALSE; } /* send magic to device */ if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, 0x09, 0x0200 | rep, 0x0003, buf, 33, NULL, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE, NULL, /* cancellable */ &error_local)) { g_debug("whilst sending magic: %s, ignoring", error_local->message); } /* wait for device to re-appear and be added to the dfu plugin */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_RESTART); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_jabra_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuJabraDevice *self = FU_JABRA_DEVICE(device); if (g_strcmp0(key, "JabraMagic") == 0) { if (value != NULL && strlen(value) == 4) { self->magic = g_strdup(value); return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unsupported jabra quirk format"); return FALSE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_jabra_device_init(FuJabraDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_set_remove_delay(FU_DEVICE(self), 20000); /* 10+10s! */ fu_device_add_protocol(FU_DEVICE(self), "org.usb.dfu"); } static void fu_jabra_device_finalize(GObject *object) { FuJabraDevice *self = FU_JABRA_DEVICE(object); g_free(self->magic); G_OBJECT_CLASS(fu_jabra_device_parent_class)->finalize(object); } static void fu_jabra_device_class_init(FuJabraDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_jabra_device_finalize; klass_device->to_string = fu_jabra_device_to_string; klass_device->prepare = fu_jabra_device_prepare; klass_device->set_quirk_kv = fu_jabra_device_set_quirk_kv; } fwupd-1.9.16/plugins/jabra/fu-jabra-device.h000066400000000000000000000004421460375044200205770ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_JABRA_DEVICE (fu_jabra_device_get_type()) G_DECLARE_FINAL_TYPE(FuJabraDevice, fu_jabra_device, FU, JABRA_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/jabra/fu-jabra-plugin.c000066400000000000000000000037471460375044200206440ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-jabra-device.h" #include "fu-jabra-plugin.h" struct _FuJabraPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuJabraPlugin, fu_jabra_plugin, FU_TYPE_PLUGIN) /* slightly weirdly, this takes us from appIDLE back into the actual * runtime mode where the device actually works */ static gboolean fu_jabra_plugin_cleanup(FuPlugin *plugin, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { GUsbDevice *usb_device; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; /* check for a property on the *dfu* FuDevice, which is also why we * can't just rely on using FuDevice->cleanup() */ if (!fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_ATTACH_EXTRA_RESET)) return TRUE; locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_RESTART); usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); if (!g_usb_device_reset(usb_device, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot reset USB device: %s [%i]", error_local->message, error_local->code); return FALSE; } /* wait for device to re-appear */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_jabra_plugin_init(FuJabraPlugin *self) { } static void fu_jabra_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "JabraMagic"); fu_plugin_add_device_gtype(plugin, FU_TYPE_JABRA_DEVICE); } static void fu_jabra_plugin_class_init(FuJabraPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_jabra_plugin_constructed; plugin_class->cleanup = fu_jabra_plugin_cleanup; } fwupd-1.9.16/plugins/jabra/fu-jabra-plugin.h000066400000000000000000000003451460375044200206400ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuJabraPlugin, fu_jabra_plugin, FU, JABRA_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/jabra/jabra.quirk000066400000000000000000000007371460375044200176450ustar00rootroot00000000000000# Jabra 410 [runtime] [USB\VID_0B0E&PID_0412] Plugin = jabra JabraMagic = 0201 CounterpartGuid = USB\VID_0B0E&PID_0411 # Jabra 510 [runtime] [USB\VID_0B0E&PID_0420] Plugin = jabra JabraMagic = 0201 CounterpartGuid = USB\VID_0B0E&PID_0421 # Jabra 710 [runtime] [USB\VID_0B0E&PID_2475] Plugin = jabra JabraMagic = 0508 CounterpartGuid = USB\VID_0B0E&PID_0982 # Jabra 810 [runtime] [USB\VID_0B0E&PID_2456] Plugin = jabra JabraMagic = 0508 CounterpartGuid = USB\VID_0B0E&PID_0971 fwupd-1.9.16/plugins/jabra/meson.build000066400000000000000000000006171460375044200176500ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginJabra"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('jabra.quirk') plugin_builtins += static_library('fu_plugin_jabra', sources: [ 'fu-jabra-plugin.c', 'fu-jabra-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/kinetic-dp/000077500000000000000000000000001460375044200164525ustar00rootroot00000000000000fwupd-1.9.16/plugins/kinetic-dp/README.md000066400000000000000000000026151460375044200177350ustar00rootroot00000000000000--- title: Plugin: Kinetic DP --- ## Introduction This plugin supports updating FW for Kinetic DP converter chips. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * com.kinet-ic.dp ## GUID Generation These devices use custom GUID values, e.g. * `MST\VEN_60AD&DEV_${dev_id}&CID_${customer_id}&CHW_${customer_board}` * `MST\VEN_60AD&DEV_${dev_id}&CID_${customer_id}` (only-quirk) * `MST\VEN_60AD&FAM_${family_id}` (only-quirk) ## Vendor ID Security The vendor ID is set from the PCI vendor, for example set to `DRM_DP_AUX_DEV:0x$(vid)` ## Requirements ### (Kernel) DP Aux Interface Kernel 4.6 introduced an DRM DP Aux interface for manipulation of the registers needed to access an DP hub. This patch can be backported to earlier kernels: ## Usage Supported devices will be displayed in `# fwupdmgr get-devices` output. ## External interface access This plugin requires read/write access to `/dev/drm_dp_aux*`. ## Version Considerations This plugin has been available since fwupd version `1.9.8`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Francis Lee @FrancisLeeKinetic fwupd-1.9.16/plugins/kinetic-dp/fu-kinetic-dp-device.c000066400000000000000000000165111460375044200225160ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2021 Jeffrey Lin * Copyright (C) 2022 Hai Su * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-kinetic-dp-device.h" typedef struct { FuKineticDpFamily family; FuKineticDpChip chip_id; FuKineticDpFwState fw_state; guint8 customer_id; guint8 customer_board; } FuKineticDpDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuKineticDpDevice, fu_kinetic_dp_device, FU_TYPE_DPAUX_DEVICE) #define GET_PRIVATE(o) (fu_kinetic_dp_device_get_instance_private(o)) static void fu_kinetic_dp_device_to_string(FuDevice *device, guint idt, GString *str) { FuKineticDpDevice *self = FU_KINETIC_DP_DEVICE(device); FuKineticDpDevicePrivate *priv = GET_PRIVATE(self); fu_string_append(str, idt, "Family", fu_kinetic_dp_family_to_string(priv->family)); fu_string_append(str, idt, "ChipId", fu_kinetic_dp_chip_to_string(priv->chip_id)); fu_string_append(str, idt, "FwState", fu_kinetic_dp_fw_state_to_string(priv->fw_state)); fu_string_append_kx(str, idt, "CustomerId", priv->customer_id); fu_string_append_kx(str, idt, "CustomerBoard", priv->customer_board); } void fu_kinetic_dp_device_set_fw_state(FuKineticDpDevice *self, FuKineticDpFwState fw_state) { FuKineticDpDevicePrivate *priv = GET_PRIVATE(self); priv->fw_state = fw_state; } FuKineticDpFwState fu_kinetic_dp_device_get_fw_state(FuKineticDpDevice *self) { FuKineticDpDevicePrivate *priv = GET_PRIVATE(self); return priv->fw_state; } void fu_kinetic_dp_device_set_chip_id(FuKineticDpDevice *self, FuKineticDpChip chip_id) { FuKineticDpDevicePrivate *priv = GET_PRIVATE(self); priv->chip_id = chip_id; } static FuKineticDpFamily fu_kinetic_dp_device_chip_id_to_family(FuKineticDpChip chip_id) { if (chip_id == FU_KINETIC_DP_CHIP_PUMA_2900 || chip_id == FU_KINETIC_DP_CHIP_PUMA_2920) return FU_KINETIC_DP_FAMILY_PUMA; if (chip_id == FU_KINETIC_DP_CHIP_MUSTANG_5200) return FU_KINETIC_DP_FAMILY_MUSTANG; if (chip_id == FU_KINETIC_DP_CHIP_JAGUAR_5000) return FU_KINETIC_DP_FAMILY_JAGUAR; return FU_KINETIC_DP_FAMILY_UNKNOWN; } static const gchar * fu_kinetic_dp_device_get_name_for_chip_id(FuKineticDpChip chip_id) { if (chip_id == FU_KINETIC_DP_CHIP_JAGUAR_5000) return "KTM50X0"; if (chip_id == FU_KINETIC_DP_CHIP_MUSTANG_5200) return "KTM52X0"; if (chip_id == FU_KINETIC_DP_CHIP_PUMA_2900) return "MC2900"; return NULL; } gboolean fu_kinetic_dp_device_dpcd_read_oui(FuKineticDpDevice *self, guint8 *buf, gsize bufsz, GError **error) { if (bufsz < DPCD_SIZE_IEEE_OUI) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "aux dpcd read buffer size [0x%x] is too small to read IEEE OUI", (guint)bufsz); return FALSE; } if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), DPCD_ADDR_IEEE_OUI, buf, DPCD_SIZE_IEEE_OUI, FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "aux dpcd read OUI failed: "); return FALSE; } return TRUE; } gboolean fu_kinetic_dp_device_dpcd_write_oui(FuKineticDpDevice *self, const guint8 *buf, GError **error) { if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), DPCD_ADDR_IEEE_OUI, buf, DPCD_SIZE_IEEE_OUI, FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "aux dpcd write OUI failed: "); return FALSE; } return TRUE; } static gboolean fu_kinetic_dp_device_ensure_customer(FuKineticDpDevice *self, GError **error) { FuKineticDpDevicePrivate *priv = GET_PRIVATE(self); /* board */ if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), DPCD_ADDR_CUSTOMER_BOARD, &priv->customer_board, sizeof(priv->customer_board), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "aux dpcd read customer board failed: "); return FALSE; } fu_device_add_instance_u8(FU_DEVICE(self), "CHW", priv->customer_board); /* id */ if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), DPCD_ADDR_CUSTOMER_ID, &priv->customer_id, sizeof(priv->customer_id), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "aux dpcd read customer ID failed: "); return FALSE; } fu_device_add_instance_u8(FU_DEVICE(self), "CID", priv->customer_id); if (!fu_device_build_instance_id_full(FU_DEVICE(self), FU_DEVICE_INSTANCE_FLAG_QUIRKS, error, "MST", "VEN", "DEV", "CID", NULL)) return FALSE; /* Kinetic EV board */ if (priv->customer_id == 0x0) { fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ENFORCE_REQUIRES); } /* success */ return fu_device_build_instance_id(FU_DEVICE(self), error, "MST", "VEN", "DEV", "CID", "CHW", NULL); } static gboolean fu_kinetic_dp_device_setup(FuDevice *device, GError **error) { FuKineticDpDevice *self = FU_KINETIC_DP_DEVICE(device); FuKineticDpDevicePrivate *priv = GET_PRIVATE(self); const gchar *chip_id_str; /* FuDpauxDevice->setup */ if (!FU_DEVICE_CLASS(fu_kinetic_dp_device_parent_class)->setup(device, error)) return FALSE; /* sanity check */ if (fu_dpaux_device_get_dpcd_ieee_oui(FU_DPAUX_DEVICE(device)) == 0x0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no IEEE OUI set"); return FALSE; } /* set up the device name */ chip_id_str = fu_kinetic_dp_device_get_name_for_chip_id(priv->chip_id); if (chip_id_str != NULL) fu_device_set_name(FU_DEVICE(self), chip_id_str); /* use the DPCD for the device */ fu_device_add_instance_u16(FU_DEVICE(self), "VEN", fu_dpaux_device_get_dpcd_ieee_oui(FU_DPAUX_DEVICE(device))); fu_device_add_instance_str(FU_DEVICE(self), "DEV", fu_dpaux_device_get_dpcd_dev_id(FU_DPAUX_DEVICE(device))); /* detect chip family */ priv->family = fu_kinetic_dp_device_chip_id_to_family(priv->chip_id); fu_device_add_instance_strup(FU_DEVICE(self), "FAM", fu_kinetic_dp_family_to_string(priv->family)); if (!fu_device_build_instance_id_full(FU_DEVICE(self), FU_DEVICE_INSTANCE_FLAG_QUIRKS, error, "MST", "VEN", "FAM", NULL)) return FALSE; /* read customer ID to get a more-specific GUID */ if (!fu_kinetic_dp_device_ensure_customer(self, error)) return FALSE; /* success */ return TRUE; } static void fu_kinetic_dp_device_class_init(FuKineticDpDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_kinetic_dp_device_setup; klass_device->to_string = fu_kinetic_dp_device_to_string; } static void fu_kinetic_dp_device_init(FuKineticDpDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.kinet-ic.dp"); fu_device_set_vendor(FU_DEVICE(self), "Kinetic Technologies"); fu_device_add_vendor_id(FU_DEVICE(self), "DRM_DP_AUX_DEV:0x329A"); fu_device_set_summary(FU_DEVICE(self), "DisplayPort Protocol Converter"); fu_device_add_icon(FU_DEVICE(self), "video-display"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE); } fwupd-1.9.16/plugins/kinetic-dp/fu-kinetic-dp-device.h000066400000000000000000000032651460375044200225250ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2021 Jeffrey Lin * Copyright (C) 2022 Hai Su * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-kinetic-dp-struct.h" struct _FuKineticDpDeviceClass { FuDpauxDeviceClass parent_class; }; #define FU_TYPE_KINETIC_DP_DEVICE (fu_kinetic_dp_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuKineticDpDevice, fu_kinetic_dp_device, FU, KINETIC_DP_DEVICE, FuDpauxDevice) /* OUI for KT */ #define MCA_OUI_BYTE_0 0x00 #define MCA_OUI_BYTE_1 0x60 #define MCA_OUI_BYTE_2 0xAD /* native DPCD fields defined in DP spec */ #define DPCD_ADDR_IEEE_OUI 0x00300 #define DPCD_SIZE_IEEE_OUI 3 #define DPCD_ADDR_BRANCH_DEV_ID_STR 0x00503 #define DPCD_ADDR_BRANCH_FW_SUB 0x00508 #define DPCD_ADDR_BRANCH_HW_REV 0x00509 #define DPCD_ADDR_BRANCH_FW_MAJ_REV 0x0050A #define DPCD_ADDR_BRANCH_FW_MIN_REV 0x0050B #define DPCD_ADDR_CUSTOMER_ID 0x00515 #define DPCD_ADDR_CUSTOMER_BOARD 0x0050F /* vendor-specific DPCD fields defined for Kinetic's usage */ #define DPCD_ADDR_BRANCH_FW_REV 0x0050C #define FU_KINETIC_DP_DEVICE_TIMEOUT 1000 void fu_kinetic_dp_device_set_fw_state(FuKineticDpDevice *self, FuKineticDpFwState fw_state); FuKineticDpFwState fu_kinetic_dp_device_get_fw_state(FuKineticDpDevice *self); void fu_kinetic_dp_device_set_chip_id(FuKineticDpDevice *self, FuKineticDpChip chip_id); gboolean fu_kinetic_dp_device_dpcd_read_oui(FuKineticDpDevice *self, guint8 *buf, gsize bufsz, GError **error); gboolean fu_kinetic_dp_device_dpcd_write_oui(FuKineticDpDevice *self, const guint8 *buf, GError **error); fwupd-1.9.16/plugins/kinetic-dp/fu-kinetic-dp-plugin.c000066400000000000000000000110511460375044200225470ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * Copyright (C) 2017 Peichen Huang * Copyright (C) 2017 Richard Hughes * Copyright (C) 2022 Hai Su * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-kinetic-dp-plugin.h" #include "fu-kinetic-dp-puma-device.h" #include "fu-kinetic-dp-puma-firmware.h" #include "fu-kinetic-dp-secure-device.h" #include "fu-kinetic-dp-secure-firmware.h" #include "fu-kinetic-dp-struct.h" struct _FuKineticDpPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuKineticDpPlugin, fu_kinetic_dp_plugin, FU_TYPE_PLUGIN) static FuKineticDpDevice * fu_kinetic_dp_plugin_create_device(FuDpauxDevice *dpaux_device, GError **error) { FuKineticDpChip chip_id = 0; FuKineticDpFwState fw_state = 0; const gchar *dev_id = fu_dpaux_device_get_dpcd_dev_id(dpaux_device); g_autoptr(FuKineticDpDevice) dp_device = NULL; const struct { FuKineticDpChip chip_id; FuKineticDpFwState fw_state; const gchar *id_str; } map[] = {{FU_KINETIC_DP_CHIP_JAGUAR_5000, FU_KINETIC_DP_FW_STATE_IROM, "5010IR"}, {FU_KINETIC_DP_CHIP_JAGUAR_5000, FU_KINETIC_DP_FW_STATE_APP, "KT50X0"}, {FU_KINETIC_DP_CHIP_MUSTANG_5200, FU_KINETIC_DP_FW_STATE_IROM, "5210IR"}, {FU_KINETIC_DP_CHIP_MUSTANG_5200, FU_KINETIC_DP_FW_STATE_APP, "KT52X0"}, {FU_KINETIC_DP_CHIP_MUSTANG_5200, FU_KINETIC_DP_FW_STATE_APP, "KT5200"}, {FU_KINETIC_DP_CHIP_PUMA_2900, FU_KINETIC_DP_FW_STATE_IROM, "PUMA"}, {FU_KINETIC_DP_CHIP_PUMA_2900, FU_KINETIC_DP_FW_STATE_APP, "MC290"}, {FU_KINETIC_DP_CHIP_PUMA_2900, FU_KINETIC_DP_FW_STATE_APP, "MC2910"}}; g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* sanity check */ if (dev_id == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "device has no DPCD device id"); return NULL; } /* find the device info by branch ID string */ for (guint32 i = 0; i < G_N_ELEMENTS(map); i++) { if (strncmp(dev_id, map[i].id_str, strlen(map[i].id_str)) == 0) { chip_id = map[i].chip_id; fw_state = map[i].fw_state; break; } } /* use the corresponding GType */ if (chip_id == FU_KINETIC_DP_CHIP_JAGUAR_5000 || chip_id == FU_KINETIC_DP_CHIP_MUSTANG_5200) { dp_device = FU_KINETIC_DP_DEVICE(g_object_new(FU_TYPE_KINETIC_DP_SECURE_DEVICE, NULL)); } else if (chip_id == FU_KINETIC_DP_CHIP_PUMA_2900) { dp_device = FU_KINETIC_DP_DEVICE(g_object_new(FU_TYPE_KINETIC_DP_PUMA_DEVICE, NULL)); } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s is not a supported Kinetic device", dev_id); return NULL; } fu_device_incorporate(FU_DEVICE(dp_device), FU_DEVICE(dpaux_device)); fu_kinetic_dp_device_set_chip_id(dp_device, chip_id); fu_kinetic_dp_device_set_fw_state(dp_device, fw_state); return g_steal_pointer(&dp_device); } static gboolean fu_kinetic_dp_plugin_backend_device_added(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { FuKineticDpPlugin *self = FU_KINETIC_DP_PLUGIN(plugin); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuKineticDpDevice) dev = NULL; g_autoptr(GError) error_local = NULL; /* check to see if this is device we care about? */ if (!FU_IS_DPAUX_DEVICE(device)) return TRUE; /* instantiate a new device */ dev = fu_kinetic_dp_plugin_create_device(FU_DPAUX_DEVICE(device), error); if (dev == NULL) return FALSE; locker = fu_device_locker_new(dev, &error_local); if (locker == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { g_debug("no device found: %s", error_local->message); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } fu_plugin_device_add(FU_PLUGIN(self), FU_DEVICE(dev)); return TRUE; } static void fu_kinetic_dp_plugin_init(FuKineticDpPlugin *self) { } static void fu_kinetic_dp_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "drm"); /* used for uevent only */ fu_plugin_add_device_udev_subsystem(plugin, "drm_dp_aux_dev"); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_KINETIC_DP_PUMA_FIRMWARE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_KINETIC_DP_SECURE_FIRMWARE); } static void fu_kinetic_dp_plugin_class_init(FuKineticDpPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_kinetic_dp_plugin_constructed; plugin_class->backend_device_added = fu_kinetic_dp_plugin_backend_device_added; } fwupd-1.9.16/plugins/kinetic-dp/fu-kinetic-dp-plugin.h000066400000000000000000000004431460375044200225570ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2022 Hai Su * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuKineticDpPlugin, fu_kinetic_dp_plugin, FU, KINETIC_DP_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/kinetic-dp/fu-kinetic-dp-puma-device.c000066400000000000000000000420771460375044200234640ustar00rootroot00000000000000/* * Copyright (C) 2022 Hai Su * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-kinetic-dp-puma-device.h" #include "fu-kinetic-dp-puma-firmware.h" /* Kinetic proprietary DPCD fields for Puma in both application and ISP driver */ #define PUMA_DPCD_SINK_MODE_REG 0x0050D #define PUMA_DPCD_CMD_STATUS_REG 0x0050E #define PUMA_DPCD_DATA_ADDR 0x80000ul #define PUMA_DPCD_DATA_SIZE 0x8000ul /* 0x80000ul ~ 0x87FFF, 32 KB*/ #define PUMA_DPCD_DATA_ADDR_END (PUMA_DPCD_DATA_ADDR + PUMA_DPCD_DATA_SIZE - 1) #define PUMA_CHUNK_PROCESS_MAX_WAIT 10000 /* max wait time in ms to process 32KB chunk */ #define FU_KINETIC_DP_PUMA_REQUEST_FLASH_ERASE_TIME 2 /* typical erase time, ms */ #define POLL_INTERVAL_MS 20 /* check the status of installing FW images */ struct _FuKineticDpPumaDevice { FuKineticDpDevice parent_instance; guint16 read_flash_prog_time; guint16 flash_id; guint16 flash_size; }; G_DEFINE_TYPE(FuKineticDpPumaDevice, fu_kinetic_dp_puma_device, FU_TYPE_KINETIC_DP_DEVICE) static void fu_kinetic_dp_puma_device_to_string(FuDevice *device, guint idt, GString *str) { FuKineticDpPumaDevice *self = FU_KINETIC_DP_PUMA_DEVICE(device); /* FuKineticDpDevice->to_string */ FU_DEVICE_CLASS(fu_kinetic_dp_puma_device_parent_class)->to_string(device, idt, str); fu_string_append_kx(str, idt, "ReadFlashProgTime", self->read_flash_prog_time); fu_string_append_kx(str, idt, "FlashId", self->flash_id); fu_string_append_kx(str, idt, "FlashSize", self->flash_size); } static gboolean fu_kinetic_dp_puma_device_wait_dpcd_cmd_status_cb(FuDevice *device, gpointer user_data, GError **error) { FuKineticDpPumaDevice *self = FU_KINETIC_DP_PUMA_DEVICE(device); guint8 status = 0; guint8 status_want = GPOINTER_TO_UINT(user_data); if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), PUMA_DPCD_CMD_STATUS_REG, &status, sizeof(status), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to read PUMA_DPCD_CMD_STATUS_REG for status: "); return FALSE; } if (status != status_want) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "flash mode was %s, wanted %s", fu_kinetic_dp_puma_mode_to_string(status), fu_kinetic_dp_puma_mode_to_string(status_want)); return FALSE; } /* success */ return TRUE; } static gboolean fu_kinetic_dp_puma_device_wait_dpcd_sink_mode_cb(FuDevice *device, gpointer user_data, GError **error) { FuKineticDpPumaDevice *self = FU_KINETIC_DP_PUMA_DEVICE(device); guint8 status = 0; guint8 status_want = GPOINTER_TO_UINT(user_data); if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), PUMA_DPCD_SINK_MODE_REG, &status, sizeof(status), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to read PUMA_DPCD_SINK_MODE_REG for status: "); return FALSE; } if (status != status_want) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "flash mode was %s, wanted %s", fu_kinetic_dp_puma_mode_to_string(status), fu_kinetic_dp_puma_mode_to_string(status_want)); return FALSE; } /* success */ return TRUE; } static gboolean fu_kinetic_dp_puma_device_enter_code_loading_mode(FuKineticDpPumaDevice *self, GError **error) { guint8 cmd = FU_KINETIC_DP_PUMA_REQUEST_CODE_LOAD_REQUEST; /* send cmd */ if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), PUMA_DPCD_SINK_MODE_REG, &cmd, sizeof(cmd), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to write PUMA_DPCD_SINK_MODE_REG with " "CODE_LOAD_REQUEST: "); return FALSE; } /* wait for the command to be processed */ if (!fu_device_retry_full(FU_DEVICE(self), fu_kinetic_dp_puma_device_wait_dpcd_sink_mode_cb, 5, POLL_INTERVAL_MS, GUINT_TO_POINTER(FU_KINETIC_DP_PUMA_REQUEST_CODE_LOAD_READY), error)) { g_prefix_error(error, "timeout waiting for REQUEST_FW_UPDATE_READY: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_kinetic_dp_puma_device_send_chunk(FuKineticDpPumaDevice *self, FuIOChannel *io_channel, GBytes *fw, GError **error) { g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 16); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), PUMA_DPCD_DATA_ADDR + fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed at 0x%x: ", fu_chunk_get_address(chk)); return FALSE; } } /* success */ return TRUE; } static gboolean fu_kinetic_dp_puma_device_send_payload(FuKineticDpPumaDevice *self, FuIOChannel *io_channel, GBytes *fw, FuProgress *progress, guint32 wait_time_ms, gboolean ignore_error, GError **error) { g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x0, PUMA_DPCD_DATA_SIZE); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GBytes) chk_blob = fu_chunk_get_bytes(chk); /* send a maximum 32KB chunk of payload to AUX window */ if (!fu_kinetic_dp_puma_device_send_chunk(self, io_channel, chk_blob, error)) { g_prefix_error(error, "failed to AUX write at 0x%x: ", fu_chunk_get_address(chk)); return FALSE; } /* check if data chunk received */ if (!fu_device_retry_full(FU_DEVICE(self), fu_kinetic_dp_puma_device_wait_dpcd_cmd_status_cb, wait_time_ms / POLL_INTERVAL_MS, POLL_INTERVAL_MS, GUINT_TO_POINTER(FU_KINETIC_DP_PUMA_MODE_CHUNK_PROCESSED), error)) { g_prefix_error(error, "timeout waiting for MODE_CHUNK_PROCESSED: "); return FALSE; } fu_progress_step_done(progress); } return TRUE; } static gboolean fu_kinetic_dp_puma_device_wait_drv_ready(FuKineticDpPumaDevice *self, FuIOChannel *io_channel, GError **error) { guint8 flashinfo[FU_STRUCT_KINETIC_DP_FLASH_INFO_SIZE] = {0}; g_autoptr(GByteArray) st = NULL; self->flash_id = 0; self->flash_size = 0; self->read_flash_prog_time = 10; g_debug("wait for isp driver ready..."); /* wait for the command to be processed */ if (!fu_device_retry_full(FU_DEVICE(self), fu_kinetic_dp_puma_device_wait_dpcd_sink_mode_cb, 20, POLL_INTERVAL_MS, GUINT_TO_POINTER(FU_KINETIC_DP_PUMA_REQUEST_CODE_BOOTUP_DONE), error)) { g_prefix_error(error, "timeout waiting for REQUEST_FW_UPDATE_READY: "); return FALSE; } if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), PUMA_DPCD_DATA_ADDR, flashinfo, sizeof(flashinfo), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to read Flash Info from Isp Driver: "); return FALSE; } st = fu_struct_kinetic_dp_flash_info_parse(flashinfo, sizeof(flashinfo), 0x0, error); self->flash_id = fu_struct_kinetic_dp_flash_info_get_id(st); self->flash_size = fu_struct_kinetic_dp_flash_info_get_size(st); self->read_flash_prog_time = fu_struct_kinetic_dp_flash_info_get_erase_time(st); if (self->read_flash_prog_time == 0) self->read_flash_prog_time = FU_KINETIC_DP_PUMA_REQUEST_FLASH_ERASE_TIME; return TRUE; } static gboolean fu_kinetic_dp_puma_device_send_isp_drv(FuKineticDpPumaDevice *self, GBytes *fw, FuProgress *progress, GError **error) { FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); if (!fu_kinetic_dp_puma_device_enter_code_loading_mode(self, error)) { g_prefix_error(error, "enter code loading mode failed: "); return FALSE; } fu_kinetic_dp_puma_device_send_payload(self, io_channel, fw, progress, PUMA_CHUNK_PROCESS_MAX_WAIT, TRUE, error); if (!fu_kinetic_dp_puma_device_wait_drv_ready(self, io_channel, error)) { g_prefix_error(error, "wait for ISP driver ready failed: "); return FALSE; } if (self->flash_size >= 0x400) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); if (self->flash_size == 0) { if (self->flash_id > 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SPI flash not supported"); return FALSE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SPI flash not connected"); return FALSE; } return TRUE; } static gboolean fu_kinetic_dp_puma_device_enable_fw_update_mode(FuKineticDpPumaDevice *self, FuKineticDpPumaFirmware *firmware, GError **error) { guint8 cmd; /* send cmd */ cmd = FU_KINETIC_DP_PUMA_REQUEST_FW_UPDATE_REQUEST; if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), PUMA_DPCD_SINK_MODE_REG, &cmd, sizeof(cmd), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to write PUMA_DPCD_SINK_MODE_REG with FW_UPDATE_REQUEST: "); return FALSE; } if (fu_kinetic_dp_device_get_fw_state(FU_KINETIC_DP_DEVICE(self)) == FU_KINETIC_DP_FW_STATE_APP) { guint8 flashinfo[FU_STRUCT_KINETIC_DP_FLASH_INFO_SIZE] = {0}; g_autoptr(GByteArray) st = NULL; /* Puma takes about 18ms (Winbond EF13) to get ISP driver ready for flash info */ fu_device_sleep(FU_DEVICE(self), 18); if (!fu_device_retry_full( FU_DEVICE(self), fu_kinetic_dp_puma_device_wait_dpcd_cmd_status_cb, 150, POLL_INTERVAL_MS, GUINT_TO_POINTER(FU_KINETIC_DP_PUMA_MODE_FLASH_INFO_READY), error)) { g_prefix_error(error, "timeout waiting for MODE_FLASH_INFO_READY: "); return FALSE; } /* get flash info */ if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), PUMA_DPCD_DATA_ADDR, flashinfo, sizeof(flashinfo), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to read Flash Info: "); return FALSE; } st = fu_struct_kinetic_dp_flash_info_parse(flashinfo, sizeof(flashinfo), 0x0, error); self->flash_id = fu_struct_kinetic_dp_flash_info_get_id(st); self->flash_size = fu_struct_kinetic_dp_flash_info_get_size(st); self->read_flash_prog_time = fu_struct_kinetic_dp_flash_info_get_erase_time(st); /* save flash info need to do memcopy copy here */ if (self->flash_size == 0) { if (self->flash_id > 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SPI flash not supported"); return FALSE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SPI flash not connected"); return FALSE; } if (self->flash_size >= 0x400) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); } /* checking for flash erase done */ g_debug("waiting for flash erasing..."); if (self->read_flash_prog_time) fu_device_sleep(FU_DEVICE(self), self->read_flash_prog_time); else fu_device_sleep(FU_DEVICE(self), FU_KINETIC_DP_PUMA_REQUEST_FLASH_ERASE_TIME); if (!fu_device_retry_full(FU_DEVICE(self), fu_kinetic_dp_puma_device_wait_dpcd_sink_mode_cb, 150, POLL_INTERVAL_MS, GUINT_TO_POINTER(FU_KINETIC_DP_PUMA_REQUEST_FW_UPDATE_READY), error)) { g_prefix_error(error, "timeout waiting for REQUEST_FW_UPDATE_READY: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_kinetic_dp_puma_device_setup(FuDevice *device, GError **error) { FuKineticDpPumaDevice *self = FU_KINETIC_DP_PUMA_DEVICE(device); guint8 dpcd_buf[3] = {0}; g_autofree gchar *version = NULL; /* FuKineticDpDevice->setup */ if (!FU_DEVICE_CLASS(fu_kinetic_dp_puma_device_parent_class)->setup(device, error)) return FALSE; /* read major and minor version */ if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), DPCD_ADDR_BRANCH_FW_MAJ_REV, dpcd_buf, 2, FU_KINETIC_DP_DEVICE_TIMEOUT, error)) return FALSE; /* read sub */ if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), DPCD_ADDR_BRANCH_FW_SUB, dpcd_buf + 2, 1, FU_KINETIC_DP_DEVICE_TIMEOUT, error)) return FALSE; version = g_strdup_printf("%1d.%03d.%02d", dpcd_buf[0], dpcd_buf[1], dpcd_buf[2]); fu_device_set_version(device, version); /* success */ return TRUE; } static gboolean fu_kinetic_dp_puma_device_prepare(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { guint8 mca_oui[DPCD_SIZE_IEEE_OUI] = {MCA_OUI_BYTE_0, MCA_OUI_BYTE_1, MCA_OUI_BYTE_2}; return fu_kinetic_dp_device_dpcd_write_oui(FU_KINETIC_DP_DEVICE(device), mca_oui, error); } static gboolean fu_kinetic_dp_puma_device_cleanup(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuKineticDpPumaDevice *self = FU_KINETIC_DP_PUMA_DEVICE(device); guint8 cmd = FU_KINETIC_DP_PUMA_REQUEST_CHIP_RESET_REQUEST; fu_device_sleep(FU_DEVICE(self), 3000); if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), PUMA_DPCD_SINK_MODE_REG, &cmd, sizeof(cmd), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to write PUMA_DPCD_SINK_MODE_REG with CHIP_RESET_REQUEST: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_kinetic_dp_puma_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuKineticDpPumaDevice *self = FU_KINETIC_DP_PUMA_DEVICE(device); FuKineticDpPumaFirmware *dp_firmware = FU_KINETIC_DP_PUMA_FIRMWARE(firmware); FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); g_autoptr(GBytes) app_fw_blob = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, NULL); /* only load driver if in IROM mode */ if (fu_kinetic_dp_device_get_fw_state(FU_KINETIC_DP_DEVICE(device)) != FU_KINETIC_DP_FW_STATE_APP) { g_autoptr(GBytes) isp_drv_blob = NULL; /* get image of ISP driver */ isp_drv_blob = fu_firmware_get_image_by_idx_bytes(firmware, FU_KINETIC_DP_FIRMWARE_IDX_ISP_DRV, error); if (isp_drv_blob == NULL) return FALSE; if (g_bytes_get_size(isp_drv_blob) > 0) { g_debug("loading isp driver because in IROM mode"); if (!fu_kinetic_dp_puma_device_send_isp_drv(self, isp_drv_blob, fu_progress_get_child(progress), error)) return FALSE; } } fu_progress_step_done(progress); /* enable FW update mode */ if (!fu_kinetic_dp_puma_device_enable_fw_update_mode(self, dp_firmware, error)) return FALSE; fu_progress_step_done(progress); /* send App FW image */ app_fw_blob = fu_firmware_get_image_by_idx_bytes(firmware, FU_KINETIC_DP_FIRMWARE_IDX_APP_FW, error); if (app_fw_blob == NULL) return FALSE; if (!fu_kinetic_dp_puma_device_send_payload(self, io_channel, app_fw_blob, fu_progress_get_child(progress), PUMA_CHUNK_PROCESS_MAX_WAIT, FALSE, error)) { g_prefix_error(error, "sending App Firmware payload failed: "); return FALSE; } fu_progress_step_done(progress); /* validate firmware image in Puma */ fu_device_sleep(FU_DEVICE(self), 100); if (!fu_device_retry_full(FU_DEVICE(self), fu_kinetic_dp_puma_device_wait_dpcd_sink_mode_cb, 100, POLL_INTERVAL_MS, GUINT_TO_POINTER(FU_KINETIC_DP_PUMA_REQUEST_FW_UPDATE_DONE), error)) { g_prefix_error(error, "validating App Firmware failed: "); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_kinetic_dp_puma_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_kinetic_dp_puma_device_init(FuKineticDpPumaDevice *self) { self->read_flash_prog_time = 10; fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_KINETIC_DP_PUMA_FIRMWARE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); } static void fu_kinetic_dp_puma_device_class_init(FuKineticDpPumaDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_kinetic_dp_puma_device_to_string; klass_device->setup = fu_kinetic_dp_puma_device_setup; klass_device->prepare = fu_kinetic_dp_puma_device_prepare; klass_device->cleanup = fu_kinetic_dp_puma_device_cleanup; klass_device->write_firmware = fu_kinetic_dp_puma_device_write_firmware; klass_device->set_progress = fu_kinetic_dp_puma_device_set_progress; } fwupd-1.9.16/plugins/kinetic-dp/fu-kinetic-dp-puma-device.h000066400000000000000000000005621460375044200234620ustar00rootroot00000000000000/* * Copyright (C) 2022 Hai Su * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-kinetic-dp-device.h" #define FU_TYPE_KINETIC_DP_PUMA_DEVICE (fu_kinetic_dp_puma_device_get_type()) G_DECLARE_FINAL_TYPE(FuKineticDpPumaDevice, fu_kinetic_dp_puma_device, FU, KINETIC_DP_PUMA_DEVICE, FuKineticDpDevice) fwupd-1.9.16/plugins/kinetic-dp/fu-kinetic-dp-puma-firmware.c000066400000000000000000000201731460375044200240320ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2021 Jeffrey Lin * Copyright (C) 2022 Hai Su * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-kinetic-dp-puma-device.h" #include "fu-kinetic-dp-puma-firmware.h" #include "fu-kinetic-dp-secure-device.h" struct _FuKineticDpPumaFirmware { FuFirmwareClass parent_instance; }; typedef struct { FuKineticDpChip chip_id; guint16 cmdb_version; guint32 cmdb_revision; } FuKineticDpPumaFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuKineticDpPumaFirmware, fu_kinetic_dp_puma_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_kinetic_dp_puma_firmware_get_instance_private(o)) #define HEADER_LEN_ISP_DRV_SIZE 4 #define APP_ID_STR_LEN 4 #define FU_KINETIC_DP_PUMA_REQUEST_FW_HEADER_SIZE 50 #define FU_KINETIC_DP_PUMA_REQUEST_FW_HASH_SIZE 32 #define PUMA_STS_FW_PAYLOAD_SIZE \ ((512 * 1024) + FU_KINETIC_DP_PUMA_REQUEST_FW_HEADER_SIZE + \ (FU_KINETIC_DP_PUMA_REQUEST_FW_HASH_SIZE * 2)) /* Puma STD F/W SPI mapping */ #define FU_KINETIC_DP_PUMA_REQUEST_FW_STD_VER_START_ADDR (PUMA_STS_FW_PAYLOAD_SIZE - 52) /*0x8003E*/ /* Puma STD F/W CMDB */ #define FU_KINETIC_DP_PUMA_REQUEST_CMDB_SIZE 128 #define FU_KINETIC_DP_PUMA_REQUEST_FW_CMDB_SIG_SIZE 4 #define FU_KINETIC_DP_PUMA_REQUEST_FW_CMDB_START_ADDR 0x7FE52 #define FU_KINETIC_DP_PUMA_REQUEST_FW_CMDB_STD_VER_ADDR 0x7FE56 #define FU_KINETIC_DP_PUMA_REQUEST_FW_CMDB_REV_ADDR 0x7FE58 #define FU_KINETIC_DP_PUMA_REQUEST_FW_CMDB_REV_SIZE 3 static void fu_kinetic_dp_puma_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuKineticDpPumaFirmware *self = FU_KINETIC_DP_PUMA_FIRMWARE(firmware); FuKineticDpPumaFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kv(bn, "chip_id", fu_kinetic_dp_chip_to_string(priv->chip_id)); fu_xmlb_builder_insert_kx(bn, "cmdb_version", priv->cmdb_version); fu_xmlb_builder_insert_kx(bn, "cmdb_revision", priv->cmdb_revision); } static gboolean fu_kinetic_dp_puma_firmware_parse_chip_id(GBytes *fw, FuKineticDpChip *chip_id, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); const struct { FuKineticDpChip chip_id; guint32 offset; const gchar *app_id; } map[] = { {FU_KINETIC_DP_CHIP_PUMA_2900, 0x080042UL, "PUMA"} /* Puma 512KB */ }; for (guint32 i = 0; i < G_N_ELEMENTS(map); i++) { if (fu_memcmp_safe(buf, bufsz, map[i].offset, (const guint8 *)map[i].app_id, APP_ID_STR_LEN, 0x0, APP_ID_STR_LEN, NULL)) { *chip_id = map[i].chip_id; return TRUE; } } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no valid Chip ID is found in the firmware"); return FALSE; } static gboolean fu_kinetic_dp_puma_device_parse_app_fw(FuKineticDpPumaFirmware *self, GBytes *fw, GError **error) { FuKineticDpPumaFirmwarePrivate *priv = GET_PRIVATE(self); gsize bufsz = 0; gsize offset = 0; guint32 checksum = 0; guint32 code_size = 0; guint32 std_fw_ver = 0; guint8 checksum_actual; guint8 cmdb_sig[FU_KINETIC_DP_PUMA_REQUEST_FW_CMDB_SIG_SIZE] = {'P', 'M', 'D', 'B'}; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GByteArray) st = NULL; /* sanity check */ if (bufsz < 512 * 1024) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware payload size (0x%x) is not valid", (guint)bufsz); return FALSE; } /* calculate code size */ st = fu_struct_kinetic_dp_puma_header_parse_bytes(fw, 0x0, error); if (st == NULL) return FALSE; offset += st->len; code_size += FU_STRUCT_KINETIC_DP_PUMA_HEADER_SIZE; for (guint i = 0; i < FU_STRUCT_KINETIC_DP_PUMA_HEADER_DEFAULT_OBJECT_COUNT; i++) { g_autoptr(GByteArray) st_obj = fu_struct_kinetic_dp_puma_header_info_parse_bytes(fw, offset, error); if (st_obj == NULL) return FALSE; code_size += fu_struct_kinetic_dp_puma_header_info_get_length(st_obj) + FU_STRUCT_KINETIC_DP_PUMA_HEADER_INFO_SIZE; offset += st_obj->len; } if (code_size < (512 * 1024) + offset) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid firmware -- file size 0x%x is not reasonable", code_size); return FALSE; } /* get STD F/W version */ std_fw_ver = (guint32)(buf[FU_KINETIC_DP_PUMA_REQUEST_FW_STD_VER_START_ADDR] << 8); /* minor */ std_fw_ver += (guint32)(buf[FU_KINETIC_DP_PUMA_REQUEST_FW_STD_VER_START_ADDR + 1] << 16); /* major */ std_fw_ver += (guint32)(buf[FU_KINETIC_DP_PUMA_REQUEST_FW_STD_VER_START_ADDR + 2]); /* rev */ fu_firmware_set_version_raw(FU_FIRMWARE(self), std_fw_ver); /* get cmbd block info */ if (memcmp(buf + FU_KINETIC_DP_PUMA_REQUEST_FW_CMDB_START_ADDR, cmdb_sig, sizeof(cmdb_sig)) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid firmware -- cmdb block not found"); return FALSE; } if (!fu_memread_uint24_safe(buf, bufsz, FU_KINETIC_DP_PUMA_REQUEST_FW_CMDB_REV_ADDR, &checksum, G_LITTLE_ENDIAN, error)) return FALSE; checksum <<= 1; /* calculate crc for cmbd block */ checksum_actual = fu_sum8(buf + FU_KINETIC_DP_PUMA_REQUEST_FW_CMDB_START_ADDR, FU_KINETIC_DP_PUMA_REQUEST_CMDB_SIZE); if (checksum_actual == checksum) { if (!fu_memread_uint16_safe(buf, bufsz, FU_KINETIC_DP_PUMA_REQUEST_FW_CMDB_STD_VER_ADDR, &priv->cmdb_version, G_BIG_ENDIAN, error)) return FALSE; if (!fu_memread_uint24_safe(buf, bufsz, FU_KINETIC_DP_PUMA_REQUEST_FW_CMDB_REV_ADDR, &priv->cmdb_revision, G_BIG_ENDIAN, error)) return FALSE; } return TRUE; } static gboolean fu_kinetic_dp_puma_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuKineticDpPumaFirmware *self = FU_KINETIC_DP_PUMA_FIRMWARE(firmware); FuKineticDpPumaFirmwarePrivate *priv = GET_PRIVATE(self); const guint8 *buf; gsize bufsz; gsize app_fw_size; guint32 isp_drv_size = 0; g_autoptr(GBytes) isp_drv_blob = NULL; g_autoptr(GBytes) app_fw_blob = NULL; g_autoptr(FuFirmware) isp_drv_img = NULL; g_autoptr(FuFirmware) app_fw_img = NULL; /* parse firmware according to Kinetic's FW image format * FW binary = 4 bytes Header(Little-Endian) + ISP driver + App FW * 4 bytes Header: size of ISP driver */ buf = g_bytes_get_data(fw, &bufsz); if (!fu_memread_uint32_safe(buf, bufsz, 0, &isp_drv_size, G_LITTLE_ENDIAN, error)) return FALSE; /* add ISP driver as a new image into firmware */ isp_drv_blob = fu_bytes_new_offset(fw, HEADER_LEN_ISP_DRV_SIZE, isp_drv_size, error); if (isp_drv_blob == NULL) return FALSE; isp_drv_img = fu_firmware_new_from_bytes(isp_drv_blob); fu_firmware_set_idx(isp_drv_img, FU_KINETIC_DP_FIRMWARE_IDX_ISP_DRV); fu_firmware_add_image(firmware, isp_drv_img); /* add App FW as a new image into firmware */ app_fw_size = g_bytes_get_size(fw) - HEADER_LEN_ISP_DRV_SIZE - isp_drv_size; app_fw_blob = fu_bytes_new_offset(fw, HEADER_LEN_ISP_DRV_SIZE + isp_drv_size, app_fw_size, error); if (app_fw_blob == NULL) return FALSE; app_fw_img = fu_firmware_new_from_bytes(app_fw_blob); fu_firmware_set_idx(app_fw_img, FU_KINETIC_DP_FIRMWARE_IDX_APP_FW); fu_firmware_add_image(firmware, app_fw_img); /* figure out which chip App FW it is for */ buf = g_bytes_get_data(app_fw_blob, &bufsz); if (!fu_kinetic_dp_puma_firmware_parse_chip_id(app_fw_blob, &priv->chip_id, error)) return FALSE; if (!fu_kinetic_dp_puma_device_parse_app_fw(self, app_fw_blob, error)) { g_prefix_error(error, "failed to parse info from Puma App firmware: "); return FALSE; } /* success */ return TRUE; } static void fu_kinetic_dp_puma_firmware_init(FuKineticDpPumaFirmware *self) { } static void fu_kinetic_dp_puma_firmware_class_init(FuKineticDpPumaFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_kinetic_dp_puma_firmware_parse; klass_firmware->export = fu_kinetic_dp_puma_firmware_export; } fwupd-1.9.16/plugins/kinetic-dp/fu-kinetic-dp-puma-firmware.h000066400000000000000000000007351460375044200240410ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2021 Jeffrey Lin * Copyright (C) 2022 Hai Su * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_KINETIC_DP_PUMA_FIRMWARE (fu_kinetic_dp_puma_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuKineticDpPumaFirmware, fu_kinetic_dp_puma_firmware, FU, KINETIC_DP_PUMA_FIRMWARE, FuFirmware) fwupd-1.9.16/plugins/kinetic-dp/fu-kinetic-dp-secure-device.c000066400000000000000000000712661460375044200240120ustar00rootroot00000000000000/* * Copyright (C) 2021 Jeffrey Lin * Copyright (C) 2022 Hai Su * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-kinetic-dp-secure-device.h" #include "fu-kinetic-dp-secure-firmware.h" #include "fu-kinetic-dp-struct.h" struct _FuKineticDpSecureDevice { FuKineticDpDevice parent_instance; guint16 read_flash_prog_time; guint16 flash_id; guint16 flash_size; gboolean isp_secure_auth_mode; FuKineticDpBank flash_bank; }; G_DEFINE_TYPE(FuKineticDpSecureDevice, fu_kinetic_dp_secure_device, FU_TYPE_KINETIC_DP_DEVICE) /* Kinetic proprietary DPCD fields for JaguarMustang, for both application and ISP driver */ #define DPCD_ADDR_CMD_STATUS_REG 0x0050D #define DPCD_ADDR_PARAM_REG 0x0050E /* DPCD registers are used while running ISP driver */ #define DPCD_ADDR_ISP_REPLY_LEN_REG 0x00513 #define DPCD_SIZE_ISP_REPLY_LEN_REG 1 #define DPCD_ADDR_ISP_REPLY_DATA_REG 0x00514 /* during ISP driver */ #define DPCD_SIZE_ISP_REPLY_DATA_REG 12 /* 0x00514 ~ 0x0051F*/ #define DPCD_ADDR_KT_AUX_WIN 0x80000ul #define DPCD_SIZE_KT_AUX_WIN 0x8000ul /* 0x80000ul ~ 0x87FFF, 32 KB */ #define DPCD_ADDR_KT_AUX_WIN_END (DPCD_ADDR_KT_AUX_WIN + DPCD_SIZE_KT_AUX_WIN - 1) #define DPCD_KT_CONFIRMATION_BIT 0x80 #define DPCD_KT_COMMAND_MASK 0x7F /* polling interval to check the status of installing FW images */ #define INSTALL_IMAGE_POLL_INTERVAL_MS 50 static void fu_kinetic_dp_secure_device_to_string(FuDevice *device, guint idt, GString *str) { FuKineticDpSecureDevice *self = FU_KINETIC_DP_SECURE_DEVICE(device); /* FuKineticDpDevice->to_string */ FU_DEVICE_CLASS(fu_kinetic_dp_secure_device_parent_class)->to_string(device, idt, str); fu_string_append_kx(str, idt, "ReadFlashProgTime", self->read_flash_prog_time); fu_string_append_kx(str, idt, "FlashId", self->flash_id); fu_string_append_kx(str, idt, "FlashSize", self->flash_size); fu_string_append_kx(str, idt, "IspSecureAuthMode", self->isp_secure_auth_mode); fu_string_append(str, idt, "FlashBank", fu_kinetic_dp_bank_to_string(self->flash_bank)); } static gboolean fu_kinetic_dp_secure_device_read_param_reg(FuKineticDpSecureDevice *self, guint8 *dpcd_val, GError **error) { if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), DPCD_ADDR_PARAM_REG, dpcd_val, 1, FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to read DPCD_KT_PARAM_REG: "); return FALSE; } return TRUE; } static gboolean fu_kinetic_dp_secure_device_write_kt_prop_cmd(FuKineticDpSecureDevice *self, guint8 cmd_id, GError **error) { cmd_id |= DPCD_KT_CONFIRMATION_BIT; if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), DPCD_ADDR_CMD_STATUS_REG, &cmd_id, sizeof(cmd_id), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to write DPCD_KT_CMD_STATUS_REG: "); return FALSE; } return TRUE; } static gboolean fu_kinetic_dp_secure_device_clear_kt_prop_cmd(FuKineticDpSecureDevice *self, GError **error) { guint8 cmd_id = FU_KINETIC_DP_DPCD_CMD_STS_NONE; if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), DPCD_ADDR_CMD_STATUS_REG, &cmd_id, sizeof(cmd_id), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to write DPCD_KT_CMD_STATUS_REG: "); return FALSE; } return TRUE; } static gboolean fu_kinetic_dp_secure_device_send_kt_prop_cmd_cb(FuDevice *device, gpointer user_data, GError **error) { FuKineticDpSecureDevice *self = FU_KINETIC_DP_SECURE_DEVICE(device); guint8 status = (guint8)FU_KINETIC_DP_DPCD_CMD_STS_NONE; guint8 cmd_id = GPOINTER_TO_UINT(user_data); if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), DPCD_ADDR_CMD_STATUS_REG, &status, sizeof(status), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to read DPCD_ADDR_CMD_STATUS_REG: "); return FALSE; } /* target responded */ if (status != (cmd_id | (guint8)DPCD_KT_CONFIRMATION_BIT)) { if (status != cmd_id) { status &= DPCD_KT_COMMAND_MASK; if (status == (guint8)FU_KINETIC_DP_DPCD_STS_CRC_FAILURE) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED, "chunk data CRC failed: "); return FALSE; } g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid value in DPCD_KT_CMD_STATUS_REG: 0x%x", status); return FALSE; } /* confirmation bit is cleared by sink, means sent command is processed */ return TRUE; } /* failed */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "waiting for prop cmd, got %s", fu_kinetic_dp_dpcd_to_string(status)); return FALSE; } static gboolean fu_kinetic_dp_secure_device_send_kt_prop_cmd(FuKineticDpSecureDevice *self, guint8 cmd_id, guint32 max_time_ms, guint16 poll_interval_ms, guint8 *status, GError **error) { if (!fu_kinetic_dp_secure_device_write_kt_prop_cmd(self, cmd_id, error)) return FALSE; /* wait for the sent proprietary command to be processed */ if (!fu_device_retry_full(FU_DEVICE(self), fu_kinetic_dp_secure_device_send_kt_prop_cmd_cb, max_time_ms / poll_interval_ms, poll_interval_ms, GUINT_TO_POINTER(cmd_id), error)) { g_prefix_error(error, "timeout waiting for prop command: "); return FALSE; } return TRUE; } static gboolean fu_kinetic_dp_secure_device_read_dpcd_reply_data_reg(FuKineticDpSecureDevice *self, guint8 *buf, gsize bufsz, guint8 *read_len, GError **error) { guint8 read_data_len; /* set the output to 0 */ *read_len = 0; if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), DPCD_ADDR_ISP_REPLY_LEN_REG, &read_data_len, 1, FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to read DPCD_ISP_REPLY_DATA_LEN_REG: "); return FALSE; } if (bufsz < read_data_len) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "buffer size [%u] is not enough to read DPCD_ISP_REPLY_DATA_REG [%u]", (guint)bufsz, read_data_len); return FALSE; } if (read_data_len > 0) { if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), DPCD_ADDR_ISP_REPLY_DATA_REG, buf, read_data_len, FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to read DPCD_ISP_REPLY_DATA_REG: "); return FALSE; } *read_len = read_data_len; } return TRUE; } static gboolean fu_kinetic_dp_secure_device_write_dpcd_reply_data_reg(FuKineticDpSecureDevice *self, const guint8 *buf, gsize len, GError **error) { guint8 len_u8 = len; if (len > DPCD_SIZE_ISP_REPLY_DATA_REG) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "length bigger than DPCD_SIZE_ISP_REPLY_DATA_REG [%u]", (guint)len); return FALSE; } if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), DPCD_ADDR_ISP_REPLY_DATA_REG, buf, len, FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to write DPCD_KT_REPLY_DATA_REG: "); return FALSE; } return fu_dpaux_device_write(FU_DPAUX_DEVICE(self), DPCD_ADDR_ISP_REPLY_LEN_REG, &len_u8, sizeof(len_u8), FU_KINETIC_DP_DEVICE_TIMEOUT, error); } static gboolean fu_kinetic_dp_secure_device_write_mca_oui(FuKineticDpSecureDevice *self, GError **error) { guint8 mca_oui[DPCD_SIZE_IEEE_OUI] = {MCA_OUI_BYTE_0, MCA_OUI_BYTE_1, MCA_OUI_BYTE_2}; return fu_kinetic_dp_device_dpcd_write_oui(FU_KINETIC_DP_DEVICE(self), mca_oui, error); } static gboolean fu_kinetic_dp_secure_device_enter_code_loading_mode(FuKineticDpSecureDevice *self, guint32 code_size, GError **error) { guint8 status = 0x0; guint8 buf[0x4] = {0x0}; if (fu_kinetic_dp_device_get_fw_state(FU_KINETIC_DP_DEVICE(self)) == FU_KINETIC_DP_FW_STATE_APP) { /* make DPCD 514 writable */ if (!fu_kinetic_dp_secure_device_send_kt_prop_cmd( self, FU_KINETIC_DP_DPCD_CMD_PREPARE_FOR_ISP_MODE, 500, 10, &status, error)) return FALSE; } /* update payload size to DPCD reply data reg first */ fu_memwrite_uint32(buf, code_size, G_LITTLE_ENDIAN); if (!fu_kinetic_dp_secure_device_write_dpcd_reply_data_reg(self, buf, sizeof(buf), error)) return FALSE; if (!fu_kinetic_dp_secure_device_send_kt_prop_cmd( self, FU_KINETIC_DP_DPCD_CMD_ENTER_CODE_LOADING_MODE, 500, 10, &status, error)) return FALSE; /* success */ return TRUE; } /** * fu_kinetic_dp_secure_device_crc16: * @buf: memory buffer * @bufsz: size of @buf * * Returns the cyclic redundancy check value for the given memory buffer. * This is a proprietary implementation only can be used in Kinetic's * Secure AUX-ISP protocol * * Returns: CRC value **/ static guint16 fu_kinetic_dp_secure_device_crc16(const guint8 *buf, guint16 bufsize) { guint16 crc = 0x1021; guint16 i, crc_tmp; guint8 data, flag, j; for (i = 0; i < bufsize; i++) { crc_tmp = crc; data = buf[i]; for (j = 8; j; j--) { flag = data ^ (crc_tmp >> 8); crc_tmp <<= 1; if (flag & 0x80) crc_tmp ^= 0x1021; data <<= 1; } crc = crc_tmp; } return crc; } static gboolean fu_kinetic_dp_secure_device_send_chunk(FuKineticDpSecureDevice *self, GBytes *fw, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 16); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), DPCD_ADDR_KT_AUX_WIN + fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed at 0x%x: ", fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_kinetic_dp_secure_device_send_payload(FuKineticDpSecureDevice *self, GBytes *fw, guint32 wait_time_ms, gint32 wait_interval_ms, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x0, DPCD_SIZE_KT_AUX_WIN); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); guint8 buf_crc16[0x4] = {0x0}; guint8 status = 0; g_autoptr(GBytes) fw_chk = fu_chunk_get_bytes(chk); /* send a maximum 32KB chunk of payload to AUX window */ if (!fu_kinetic_dp_secure_device_send_chunk(self, fw_chk, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to AUX write at 0x%x: ", fu_chunk_get_address(chk)); return FALSE; } /* send the CRC16 of current 32KB chunk to DPCD_REPLY_DATA_REG */ fu_memwrite_uint32(buf_crc16, fu_kinetic_dp_secure_device_crc16(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)), G_LITTLE_ENDIAN); if (!fu_kinetic_dp_secure_device_write_dpcd_reply_data_reg(self, buf_crc16, sizeof(buf_crc16), error)) { g_prefix_error(error, "failed to send CRC16 to reply data register: "); return FALSE; } /* notify that a chunk of payload has been sent to AUX window */ if (!fu_kinetic_dp_secure_device_send_kt_prop_cmd( self, FU_KINETIC_DP_DPCD_CMD_CHUNK_DATA_PROCESSED, wait_time_ms, wait_interval_ms, &status, error)) { g_prefix_error(error, "target failed to process payload chunk: "); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_kinetic_dp_secure_device_wait_dpcd_cmd_cleared_cb(FuDevice *device, gpointer user_data, GError **error) { FuKineticDpSecureDevice *self = FU_KINETIC_DP_SECURE_DEVICE(device); guint8 status = FU_KINETIC_DP_DPCD_CMD_STS_NONE; if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), DPCD_ADDR_CMD_STATUS_REG, &status, sizeof(status), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) return FALSE; /* status is cleared by sink */ if (status == FU_KINETIC_DP_DPCD_CMD_STS_NONE) return TRUE; if ((status & DPCD_KT_CONFIRMATION_BIT) > 0) { /* status is not cleared but confirmation bit is cleared, * it means that target responded with failure */ if (status == FU_KINETIC_DP_DPCD_STS_INVALID_IMAGE) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED, "invalid ISP driver"); return FALSE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to execute ISP driver"); return FALSE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "waiting for sink to clear status"); return FALSE; } static gboolean fu_kinetic_dp_secure_device_wait_dpcd_cmd_cleared(FuKineticDpSecureDevice *self, guint16 wait_time_ms, guint16 poll_interval_ms, GError **error) { if (!fu_device_retry_full(FU_DEVICE(self), fu_kinetic_dp_secure_device_wait_dpcd_cmd_cleared_cb, wait_time_ms / poll_interval_ms, poll_interval_ms, NULL, error)) { g_prefix_error(error, "timeout waiting for DPCD_ISP_SINK_STATUS_REG: "); return FALSE; } return TRUE; } static gboolean fu_kinetic_dp_secure_device_execute_isp_drv(FuKineticDpSecureDevice *self, GError **error) { guint8 status; guint8 read_len; guint8 reply_data[6] = {0}; g_autoptr(GByteArray) st = NULL; /* in Jaguar, it takes about FU_KINETIC_DP_DEVICE_TIMEOUT ms to boot up and initialize */ self->flash_id = 0; self->flash_size = 0; self->read_flash_prog_time = 10; if (!fu_kinetic_dp_secure_device_write_kt_prop_cmd(self, FU_KINETIC_DP_DPCD_CMD_EXECUTE_RAM_CODE, error)) return FALSE; if (!fu_kinetic_dp_secure_device_wait_dpcd_cmd_cleared(self, 1500, 100, error)) return FALSE; if (!fu_kinetic_dp_secure_device_read_param_reg(self, &status, error)) return FALSE; if (status != FU_KINETIC_DP_DPCD_STS_SECURE_ENABLED && status != FU_KINETIC_DP_DPCD_STS_SECURE_DISABLED) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "waiting for ISP driver ready failed!"); return FALSE; } self->isp_secure_auth_mode = (status == FU_KINETIC_DP_DPCD_STS_SECURE_ENABLED); if (!fu_kinetic_dp_secure_device_read_dpcd_reply_data_reg(self, reply_data, sizeof(reply_data), &read_len, error)) { g_prefix_error(error, "failed to read flash ID and size: "); return FALSE; } st = fu_struct_kinetic_dp_flash_info_parse(reply_data, sizeof(reply_data), 0x0, error); self->flash_id = fu_struct_kinetic_dp_flash_info_get_id(st); self->flash_size = fu_struct_kinetic_dp_flash_info_get_size(st); self->read_flash_prog_time = fu_struct_kinetic_dp_flash_info_get_erase_time(st); if (self->read_flash_prog_time == 0) self->read_flash_prog_time = 10; /* one bank size in Jaguar is 1024KB */ if (self->flash_size >= 2048) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); if (self->flash_size == 0) { if (self->flash_id > 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SPI flash not supported"); return FALSE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SPI flash not connected"); return FALSE; } /* success */ return TRUE; } static gboolean fu_kinetic_dp_secure_device_send_isp_drv(FuKineticDpSecureDevice *self, GBytes *fw, FuProgress *progress, GError **error) { if (!fu_kinetic_dp_secure_device_enter_code_loading_mode(self, g_bytes_get_size(fw), error)) { g_prefix_error(error, "enabling code-loading mode failed: "); return FALSE; } if (!fu_kinetic_dp_secure_device_send_payload(self, fw, 10000, 50, progress, error)) { g_prefix_error(error, "sending ISP driver payload failed: "); return FALSE; } g_debug("sending ISP driver payload..."); if (!fu_kinetic_dp_secure_device_execute_isp_drv(self, error)) { g_prefix_error(error, "ISP driver booting up failed: "); return FALSE; } return TRUE; } static gboolean fu_kinetic_dp_secure_device_enable_fw_update_mode(FuKineticDpSecureFirmware *firmware, FuKineticDpSecureDevice *self, GError **error) { guint8 status; guint8 buf[12] = {0}; /* send payload size to DPCD_MCA_REPLY_DATA_REG */ if (!fu_memwrite_uint32_safe(buf, sizeof(buf), 0, fu_kinetic_dp_secure_firmware_get_esm_payload_size(firmware), G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint32_safe(buf, sizeof(buf), 4, fu_kinetic_dp_secure_firmware_get_arm_app_code_size(firmware), G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint16_safe(buf, sizeof(buf), 8, fu_kinetic_dp_secure_firmware_get_app_init_data_size(firmware), G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint16_safe( buf, sizeof(buf), 10, (fu_kinetic_dp_secure_firmware_get_esm_xip_enabled(firmware) ? (1 << 15) : 0) | fu_kinetic_dp_secure_firmware_get_cmdb_block_size(firmware), G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_kinetic_dp_secure_device_write_dpcd_reply_data_reg(self, buf, sizeof(buf), error)) { g_prefix_error(error, "send payload size failed: "); return FALSE; } if (!fu_kinetic_dp_secure_device_send_kt_prop_cmd( self, FU_KINETIC_DP_DPCD_CMD_ENTER_FW_UPDATE_MODE, 200000, 500, &status, error)) { g_prefix_error(error, "entering F/W update mode failed: "); return FALSE; } return TRUE; } static gboolean fu_kinetic_dp_secure_device_send_app_fw(FuKineticDpSecureDevice *self, FuKineticDpSecureFirmware *firmware, GBytes *fw, FuProgress *progress, GError **error) { g_autoptr(GBytes) fw_app = NULL; g_autoptr(GBytes) fw_app_init = NULL; g_autoptr(GBytes) fw_app_data = NULL; g_autoptr(GBytes) fw_esm = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "send-sigs"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 38, "send-esm"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 57, "send-app"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 3, "send-initialized"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "send-app-id"); /* send ESM and App Certificates & RSA Signatures */ if (self->isp_secure_auth_mode) { g_autoptr(GBytes) fw_crt = NULL; fw_crt = fu_bytes_new_offset(fw, 0x0, FW_CERTIFICATE_SIZE * 2 + FW_RSA_SIGNATURE_BLOCK_SIZE * 2, error); if (fw_crt == NULL) return FALSE; if (!fu_kinetic_dp_secure_device_send_payload(self, fw_crt, 10000, 200, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to send certificates: "); return FALSE; } } fu_progress_step_done(progress); /* send ESM code */ fw_esm = fu_bytes_new_offset(fw, SPI_ESM_PAYLOAD_START, fu_kinetic_dp_secure_firmware_get_esm_payload_size(firmware), error); if (fw_esm == NULL) return FALSE; if (!fu_kinetic_dp_secure_device_send_payload(self, fw_esm, 10000, 200, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to send ESM payload: "); return FALSE; } fu_progress_step_done(progress); /* send App code */ fw_app = fu_bytes_new_offset(fw, SPI_APP_PAYLOAD_START, fu_kinetic_dp_secure_firmware_get_arm_app_code_size(firmware), error); if (fw_app == NULL) return FALSE; if (!fu_kinetic_dp_secure_device_send_payload(self, fw_app, 10000, 200, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to send App FW payload: "); return FALSE; } fu_progress_step_done(progress); /* send App initialized data */ fw_app_init = fu_bytes_new_offset(fw, fu_kinetic_dp_secure_firmware_get_esm_xip_enabled(firmware) ? SPI_APP_EXTEND_INIT_DATA_START : SPI_APP_NORMAL_INIT_DATA_START, fu_kinetic_dp_secure_firmware_get_app_init_data_size(firmware), error); if (fw_app_init == NULL) return FALSE; if (!fu_kinetic_dp_secure_device_send_payload(self, fw_app_init, 10000, 200, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to send App init data: "); return FALSE; } fu_progress_step_done(progress); /* send Application Identifier */ fw_app_data = fu_bytes_new_offset(fw, SPI_APP_ID_DATA_START, FU_STRUCT_KINETIC_DP_JAGUAR_FOOTER_SIZE, error); if (fw_app_data == NULL) return FALSE; if (!fu_kinetic_dp_secure_device_send_payload(self, fw_app_data, 10000, 200, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to send App ID data: "); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_kinetic_dp_secure_device_install_fw_images_cb(FuDevice *device, gpointer user_data, GError **error) { FuKineticDpSecureDevice *self = FU_KINETIC_DP_SECURE_DEVICE(device); guint8 status = 0; if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), DPCD_ADDR_CMD_STATUS_REG, &status, sizeof(status), FU_KINETIC_DP_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to read DPCD_MCA_CMD_REG: "); return FALSE; } /* confirmation bit is cleared */ if ((status & DPCD_KT_CONFIRMATION_BIT) == 0) { if ((status & FU_KINETIC_DP_DPCD_CMD_INSTALL_IMAGES) > 0) return TRUE; g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED, "failed to install images"); return FALSE; } /* failed */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "waiting for status, got %s", fu_kinetic_dp_dpcd_to_string(status)); return FALSE; } static gboolean fu_kinetic_dp_secure_device_install_fw_images(FuKineticDpSecureDevice *self, GError **error) { guint8 cmd_id = FU_KINETIC_DP_DPCD_CMD_INSTALL_IMAGES; if (!fu_kinetic_dp_secure_device_write_kt_prop_cmd(self, cmd_id, error)) { g_prefix_error(error, "failed to send DPCD command: "); return FALSE; } if (!fu_device_retry_full(FU_DEVICE(self), fu_kinetic_dp_secure_device_install_fw_images_cb, 1500, INSTALL_IMAGE_POLL_INTERVAL_MS, NULL, error)) { g_prefix_error(error, "timeout waiting for install command to be processed "); return FALSE; } /* success */ return TRUE; } static gboolean fu_kinetic_dp_secure_device_get_flash_bank_idx(FuKineticDpSecureDevice *self, GError **error) { guint8 status; guint8 buf[DPCD_SIZE_IEEE_OUI] = {0}; guint8 res = FU_KINETIC_DP_BANK_NONE; if (!fu_kinetic_dp_device_dpcd_read_oui(FU_KINETIC_DP_DEVICE(self), buf, sizeof(buf), error)) return FALSE; if (!fu_kinetic_dp_secure_device_write_mca_oui(self, error)) return FALSE; if (!fu_kinetic_dp_secure_device_send_kt_prop_cmd( self, FU_KINETIC_DP_DPCD_CMD_GET_ACTIVE_FLASH_BANK, 100, 20, &status, error)) return FALSE; if (!fu_kinetic_dp_secure_device_read_param_reg(self, &res, error)) return FALSE; if (!fu_kinetic_dp_secure_device_clear_kt_prop_cmd(self, error)) return FALSE; /* restore previous source OUI */ if (!fu_kinetic_dp_device_dpcd_write_oui(FU_KINETIC_DP_DEVICE(self), buf, error)) return FALSE; g_debug("secure aux got active flash bank 0x%x (0=BankA, 1=BankB, 2=TotalBanks)", res); self->flash_bank = (FuKineticDpBank)res; if (self->flash_bank == FU_KINETIC_DP_BANK_NONE) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "bank not NONE"); return FALSE; } /* success */ return TRUE; } static gchar * fu_kinetic_dp_secure_device_convert_version(FuDevice *device, guint64 version_raw) { guint8 buf[3] = {0}; fu_memwrite_uint24(buf, version_raw, G_LITTLE_ENDIAN); return g_strdup_printf("%1d.%03d.%02d", buf[2], buf[1], buf[0]); } static gboolean fu_kinetic_dp_secure_device_setup(FuDevice *device, GError **error) { FuKineticDpSecureDevice *self = FU_KINETIC_DP_SECURE_DEVICE(device); /* FuKineticDpDevice->setup */ if (!FU_DEVICE_CLASS(fu_kinetic_dp_secure_device_parent_class)->setup(device, error)) return FALSE; /* get flash info */ if (fu_kinetic_dp_device_get_fw_state(FU_KINETIC_DP_DEVICE(device)) == FU_KINETIC_DP_FW_STATE_APP) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE); if (!fu_kinetic_dp_secure_device_get_flash_bank_idx(self, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_kinetic_dp_secure_device_prepare(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuKineticDpSecureDevice *self = FU_KINETIC_DP_SECURE_DEVICE(device); return fu_kinetic_dp_secure_device_write_mca_oui(self, error); } static gboolean fu_kinetic_dp_secure_device_cleanup(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuKineticDpSecureDevice *self = FU_KINETIC_DP_SECURE_DEVICE(device); /* wait for flash clear to settle */ fu_device_sleep(FU_DEVICE(self), 2000); /* send reset command */ return fu_kinetic_dp_secure_device_write_kt_prop_cmd(self, FU_KINETIC_DP_DPCD_CMD_RESET_SYSTEM, error); } static gboolean fu_kinetic_dp_secure_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuKineticDpSecureDevice *self = FU_KINETIC_DP_SECURE_DEVICE(device); FuKineticDpSecureFirmware *dp_firmware = FU_KINETIC_DP_SECURE_FIRMWARE(firmware); g_autoptr(GBytes) app_fw_blob = NULL; g_autoptr(GBytes) isp_drv_blob = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 3, "isp"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 97, "app"); /* get image of ISP driver */ isp_drv_blob = fu_firmware_get_image_by_idx_bytes(firmware, FU_KINETIC_DP_FIRMWARE_IDX_ISP_DRV, error); if (isp_drv_blob == NULL) return FALSE; /* send ISP driver and execute it */ if (g_bytes_get_size(isp_drv_blob) > 0) { if (!fu_kinetic_dp_secure_device_send_isp_drv(self, isp_drv_blob, fu_progress_get_child(progress), error)) return FALSE; } fu_progress_step_done(progress); /* enable FW update mode */ if (!fu_kinetic_dp_secure_device_enable_fw_update_mode(dp_firmware, self, error)) return FALSE; /* get image of App FW */ app_fw_blob = fu_firmware_get_image_by_idx_bytes(firmware, FU_KINETIC_DP_FIRMWARE_IDX_APP_FW, error); if (app_fw_blob == NULL) return FALSE; if (!fu_kinetic_dp_secure_device_send_app_fw(self, dp_firmware, app_fw_blob, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* install FW images */ return fu_kinetic_dp_secure_device_install_fw_images(self, error); } static void fu_kinetic_dp_secure_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_kinetic_dp_secure_device_init(FuKineticDpSecureDevice *self) { self->read_flash_prog_time = 10; self->isp_secure_auth_mode = TRUE; fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_KINETIC_DP_SECURE_FIRMWARE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_retry_add_recovery(FU_DEVICE(self), G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED, NULL); } static void fu_kinetic_dp_secure_device_class_init(FuKineticDpSecureDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_kinetic_dp_secure_device_to_string; klass_device->prepare = fu_kinetic_dp_secure_device_prepare; klass_device->cleanup = fu_kinetic_dp_secure_device_cleanup; klass_device->setup = fu_kinetic_dp_secure_device_setup; klass_device->write_firmware = fu_kinetic_dp_secure_device_write_firmware; klass_device->set_progress = fu_kinetic_dp_secure_device_set_progress; klass_device->convert_version = fu_kinetic_dp_secure_device_convert_version; } fwupd-1.9.16/plugins/kinetic-dp/fu-kinetic-dp-secure-device.h000066400000000000000000000045551460375044200240140ustar00rootroot00000000000000/* * Copyright (C) 2021 Jeffrey Lin * Copyright (C) 2022 Hai Su * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-kinetic-dp-device.h" #define FU_TYPE_KINETIC_DP_SECURE_DEVICE (fu_kinetic_dp_secure_device_get_type()) G_DECLARE_FINAL_TYPE(FuKineticDpSecureDevice, fu_kinetic_dp_secure_device, FU, KINETIC_DP_SECURE_DEVICE, FuKineticDpDevice) /* Flash Memory Map */ #define STD_FW_PAYLOAD_SIZE (1024 * 1024) #define CUSTOMER_PROJ_ID_OFFSET \ (STD_FW_PAYLOAD_SIZE - FU_STRUCT_KINETIC_DP_JAGUAR_FOOTER_SIZE + 15) /* 0xFFFEF */ #define CUSTOMER_FW_VER_OFFSET \ (STD_FW_PAYLOAD_SIZE - FU_STRUCT_KINETIC_DP_JAGUAR_FOOTER_SIZE + 16) /* 0xFFFF0 */ #define CUSTOMER_FW_VER_SIZE 2 #define FW_CERTIFICATE_SIZE (1 * 1024) #define FW_RSA_SIGNATURE_SIZE 256 #define FW_RSA_SIGNATURE_BLOCK_SIZE (1 * 1024) #define ESM_PAYLOAD_BLOCK_SIZE (256 * 1024) #define APP_CODE_NORMAL_BLOCK_SIZE (384 * 1024) #define APP_CODE_EXTEND_BLOCK_SIZE (640 * 1024) #define APP_INIT_DATA_BLOCK_SIZE (24 * 1024) #define CMDB_BLOCK_SIZE (4 * 1024) #define SPI_ESM_CERTIFICATE_START 0 #define SPI_APP_CERTIFICATE_START (SPI_ESM_CERTIFICATE_START + FW_CERTIFICATE_SIZE) /*0x00400*/ #define SPI_ESM_RSA_SIGNATURE_START (SPI_APP_CERTIFICATE_START + FW_CERTIFICATE_SIZE) /*0x00800*/ #define SPI_APP_RSA_SIGNATURE_START \ (SPI_ESM_RSA_SIGNATURE_START + FW_RSA_SIGNATURE_BLOCK_SIZE) /*0x00C00*/ #define SPI_ESM_PAYLOAD_START \ (SPI_APP_RSA_SIGNATURE_START + FW_RSA_SIGNATURE_BLOCK_SIZE) /*0x01000*/ #define SPI_APP_PAYLOAD_START (SPI_ESM_PAYLOAD_START + ESM_PAYLOAD_BLOCK_SIZE) /*0x41000*/ #define SPI_APP_NORMAL_INIT_DATA_START \ (SPI_APP_PAYLOAD_START + APP_CODE_NORMAL_BLOCK_SIZE) /*0xA1000*/ #define SPI_APP_EXTEND_INIT_DATA_START \ (SPI_APP_PAYLOAD_START + APP_CODE_EXTEND_BLOCK_SIZE) /*0xE1000*/ #define SPI_CMDB_BLOCK_START 0xFE000UL #define SPI_APP_ID_DATA_START (STD_FW_PAYLOAD_SIZE - FU_STRUCT_KINETIC_DP_JAGUAR_FOOTER_SIZE) fwupd-1.9.16/plugins/kinetic-dp/fu-kinetic-dp-secure-firmware.c000066400000000000000000000203461460375044200243600ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2021 Jeffrey Lin * Copyright (C) 2022 Hai Su * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-kinetic-dp-secure-device.h" #include "fu-kinetic-dp-secure-firmware.h" struct _FuKineticDpSecureFirmware { FuFirmwareClass parent_instance; }; typedef struct { FuKineticDpChip chip_id; guint32 isp_drv_size; guint32 esm_payload_size; guint32 arm_app_code_size; guint16 app_init_data_size; guint16 cmdb_block_size; gboolean esm_xip_enabled; } FuKineticDpSecureFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuKineticDpSecureFirmware, fu_kinetic_dp_secure_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_kinetic_dp_secure_firmware_get_instance_private(o)) #define HEADER_LEN_ISP_DRV_SIZE 4 #define APP_ID_STR_LEN 4 static void fu_kinetic_dp_secure_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuKineticDpSecureFirmware *self = FU_KINETIC_DP_SECURE_FIRMWARE(firmware); FuKineticDpSecureFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kv(bn, "chip_id", fu_kinetic_dp_chip_to_string(priv->chip_id)); fu_xmlb_builder_insert_kx(bn, "isp_drv_size", priv->isp_drv_size); fu_xmlb_builder_insert_kx(bn, "esm_payload_size", priv->esm_payload_size); fu_xmlb_builder_insert_kx(bn, "arm_app_code_size", priv->arm_app_code_size); fu_xmlb_builder_insert_kx(bn, "app_init_data_size", priv->app_init_data_size); fu_xmlb_builder_insert_kx(bn, "cmdb_block_size", priv->cmdb_block_size); fu_xmlb_builder_insert_kb(bn, "esm_xip_enabled", priv->esm_xip_enabled); } static gboolean fu_kinetic_dp_secure_firmware_parse_chip_id(GBytes *fw, FuKineticDpChip *chip_id, gboolean *esm_xip_enabled, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); const struct { FuKineticDpChip chip_id; guint32 offset; const gchar *app_id; gboolean esm_xip_enabled; } map[] = { {FU_KINETIC_DP_CHIP_JAGUAR_5000, 0x0FFFE4UL, "JAGR", FALSE}, /* 1024KB */ {FU_KINETIC_DP_CHIP_JAGUAR_5000, 0x0A7036UL, "JAGR", FALSE}, /* 670KB ANZU */ {FU_KINETIC_DP_CHIP_JAGUAR_5000, 0x0FFFE4UL, "JAGX", TRUE}, /* 1024KB (640KB) */ {FU_KINETIC_DP_CHIP_JAGUAR_5000, 0x0E7036UL, "JAGX", TRUE}, /* 670KB ANZU (640KB) */ {FU_KINETIC_DP_CHIP_MUSTANG_5200, 0x0FFFE4UL, "MSTG", FALSE}, /* 1024KB */ {FU_KINETIC_DP_CHIP_MUSTANG_5200, 0x0A7036UL, "MSTG", FALSE}, /* 670KB ANZU */ {FU_KINETIC_DP_CHIP_MUSTANG_5200, 0x0FFFE4UL, "MSTX", TRUE}, /* 1024KB (640KB) */ {FU_KINETIC_DP_CHIP_MUSTANG_5200, 0x0E7036UL, "MSTX", TRUE}, /* 670KB ANZU (640KB) */ }; for (guint32 i = 0; i < G_N_ELEMENTS(map); i++) { if (fu_memcmp_safe(buf, bufsz, map[i].offset, (const guint8 *)map[i].app_id, APP_ID_STR_LEN, 0x0, APP_ID_STR_LEN, NULL)) { *chip_id = map[i].chip_id; *esm_xip_enabled = map[i].esm_xip_enabled; return TRUE; } } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no valid Chip ID is found in the firmware"); return FALSE; } guint32 fu_kinetic_dp_secure_firmware_get_esm_payload_size(FuKineticDpSecureFirmware *self) { FuKineticDpSecureFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_KINETIC_DP_SECURE_FIRMWARE(self), 0); return priv->esm_payload_size; } guint32 fu_kinetic_dp_secure_firmware_get_arm_app_code_size(FuKineticDpSecureFirmware *self) { FuKineticDpSecureFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_KINETIC_DP_SECURE_FIRMWARE(self), 0); return priv->arm_app_code_size; } guint16 fu_kinetic_dp_secure_firmware_get_app_init_data_size(FuKineticDpSecureFirmware *self) { FuKineticDpSecureFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_KINETIC_DP_SECURE_FIRMWARE(self), 0); return priv->app_init_data_size; } guint16 fu_kinetic_dp_secure_firmware_get_cmdb_block_size(FuKineticDpSecureFirmware *self) { FuKineticDpSecureFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_KINETIC_DP_SECURE_FIRMWARE(self), 0); return priv->cmdb_block_size; } gboolean fu_kinetic_dp_secure_firmware_get_esm_xip_enabled(FuKineticDpSecureFirmware *self) { FuKineticDpSecureFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_KINETIC_DP_SECURE_FIRMWARE(self), FALSE); return priv->esm_xip_enabled; } static gboolean fu_kinetic_dp_secure_device_parse_app_fw(FuKineticDpSecureFirmware *self, GBytes *fw, GError **error) { FuKineticDpSecureFirmwarePrivate *priv = GET_PRIVATE(self); guint32 app_code_block_size; guint32 std_fw_ver = 0; g_autoptr(GByteArray) st = NULL; /* sanity check */ if (g_bytes_get_size(fw) != STD_FW_PAYLOAD_SIZE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware payload size (0x%x) is not valid", (guint)g_bytes_get_size(fw)); return FALSE; } if (priv->esm_xip_enabled) { app_code_block_size = APP_CODE_EXTEND_BLOCK_SIZE; } else { app_code_block_size = APP_CODE_NORMAL_BLOCK_SIZE; } /* get FW info embedded in FW file */ st = fu_struct_kinetic_dp_jaguar_footer_parse_bytes(fw, SPI_APP_ID_DATA_START, error); if (st == NULL) return FALSE; /* get standard FW version */ std_fw_ver = (guint32)(fu_struct_kinetic_dp_jaguar_footer_get_fw_ver(st) << 8); std_fw_ver += fu_struct_kinetic_dp_jaguar_footer_get_fw_rev(st); fu_firmware_set_version_raw(FU_FIRMWARE(self), std_fw_ver); /* get each block size from FW buffer */ priv->esm_payload_size = ESM_PAYLOAD_BLOCK_SIZE; priv->arm_app_code_size = app_code_block_size; priv->app_init_data_size = APP_INIT_DATA_BLOCK_SIZE; priv->cmdb_block_size = CMDB_BLOCK_SIZE; return TRUE; } static gboolean fu_kinetic_dp_secure_firmware_parse(FuFirmware *firmware, GBytes *fw_bytes, gsize offset, FwupdInstallFlags flags, GError **error) { FuKineticDpSecureFirmware *self = FU_KINETIC_DP_SECURE_FIRMWARE(firmware); FuKineticDpSecureFirmwarePrivate *priv = GET_PRIVATE(self); const guint8 *buf; gsize bufsz; guint32 app_fw_payload_size = 0; g_autoptr(GBytes) isp_drv_blob = NULL; g_autoptr(GBytes) app_fw_blob = NULL; g_autoptr(FuFirmware) isp_drv_img = NULL; g_autoptr(FuFirmware) app_fw_img = NULL; /* parse firmware according to Kinetic's FW image format * FW binary = 4 bytes Header(Little-Endian) + ISP driver + App FW * 4 bytes Header: size of ISP driver */ buf = g_bytes_get_data(fw_bytes, &bufsz); if (!fu_memread_uint32_safe(buf, bufsz, 0, &priv->isp_drv_size, G_LITTLE_ENDIAN, error)) return FALSE; /* app firmware payload size */ app_fw_payload_size = g_bytes_get_size(fw_bytes) - HEADER_LEN_ISP_DRV_SIZE - priv->isp_drv_size; /* add ISP driver as a new image into firmware */ isp_drv_blob = fu_bytes_new_offset(fw_bytes, HEADER_LEN_ISP_DRV_SIZE, priv->isp_drv_size, error); if (isp_drv_blob == NULL) return FALSE; isp_drv_img = fu_firmware_new_from_bytes(isp_drv_blob); fu_firmware_set_idx(isp_drv_img, FU_KINETIC_DP_FIRMWARE_IDX_ISP_DRV); fu_firmware_add_image(firmware, isp_drv_img); /* add App FW as a new image into firmware */ app_fw_blob = fu_bytes_new_offset(fw_bytes, HEADER_LEN_ISP_DRV_SIZE + priv->isp_drv_size, app_fw_payload_size, error); if (app_fw_blob == NULL) return FALSE; app_fw_img = fu_firmware_new_from_bytes(app_fw_blob); fu_firmware_set_idx(app_fw_img, FU_KINETIC_DP_FIRMWARE_IDX_APP_FW); fu_firmware_add_image(firmware, app_fw_img); /* figure out which chip App FW it is for */ if (!fu_kinetic_dp_secure_firmware_parse_chip_id(app_fw_blob, &priv->chip_id, &priv->esm_xip_enabled, error)) return FALSE; if (!fu_kinetic_dp_secure_device_parse_app_fw(self, app_fw_blob, error)) { g_prefix_error(error, "failed to parse info from Jaguar or Mustang App firmware: "); return FALSE; } /* success */ return TRUE; } static void fu_kinetic_dp_secure_firmware_init(FuKineticDpSecureFirmware *self) { } static void fu_kinetic_dp_secure_firmware_class_init(FuKineticDpSecureFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_kinetic_dp_secure_firmware_parse; klass_firmware->export = fu_kinetic_dp_secure_firmware_export; } fwupd-1.9.16/plugins/kinetic-dp/fu-kinetic-dp-secure-firmware.h000066400000000000000000000020321460375044200243550ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2021 Jeffrey Lin * Copyright (C) 2022 Hai Su * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_KINETIC_DP_SECURE_FIRMWARE (fu_kinetic_dp_secure_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuKineticDpSecureFirmware, fu_kinetic_dp_secure_firmware, FU, KINETIC_DP_SECURE_FIRMWARE, FuFirmware) guint32 fu_kinetic_dp_secure_firmware_get_esm_payload_size(FuKineticDpSecureFirmware *self); guint32 fu_kinetic_dp_secure_firmware_get_arm_app_code_size(FuKineticDpSecureFirmware *self); guint16 fu_kinetic_dp_secure_firmware_get_app_init_data_size(FuKineticDpSecureFirmware *self); guint16 fu_kinetic_dp_secure_firmware_get_cmdb_block_size(FuKineticDpSecureFirmware *self); gboolean fu_kinetic_dp_secure_firmware_get_esm_xip_enabled(FuKineticDpSecureFirmware *self); guint8 fu_kinetic_dp_secure_firmware_get_customer_project_id(FuKineticDpSecureFirmware *self); fwupd-1.9.16/plugins/kinetic-dp/fu-kinetic-dp.rs000066400000000000000000000052301460375044200214570ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum KineticDpFamily { Unknown, Mustang, Jaguar, Puma, } #[derive(ToString)] enum KineticDpChip { None = 0, Bobcat_2800 = 1, Bobcat_2850 = 2, Pegasus = 3, Mystique = 4, Dp2vga = 5, Puma_2900 = 6, Puma_2920 = 7, Jaguar_5000 = 8, Mustang_5200 = 9, } enum KineticDpDev { Host = 0, Port1 = 1, Port2 = 2, Port3 = 3, MaxNum = 4, } #[derive(ToString)] enum KineticDpBank { A = 0, B = 1, None = 0xFF, } enum KineticDpFirmwareIdx { IspDrv = 0, AppFw = 1, } #[derive(ToString)] enum KineticDpFwState { None = 0, Irom = 1, BootCode = 2, App = 3, } #[derive(ParseBytes)] struct KineticDpPumaHeader { _unknown: u8, object_count: u8 == 8, // certificate + ESM + Signature + hash + certificate + Puma App + Signature + hash } #[derive(ParseBytes)] struct KineticDpPumaHeaderInfo { type: u8, subtype: u8, length: u32le, } #[derive(ToString)] enum KineticDpPumaMode { ChunkProcessed = 0x03, ChunkReceived = 0x07, FlashInfoReady = 0xA1, UpdateAbort = 0x55, } enum KineticDpPumaRequest { ChipResetRequest = 0, CodeLoadRequest = 0x01, CodeLoadReady = 0x03, CodeBootupDone = 0x07, CmdbGetinfoReq = 0xA0, CmdbGetinfoRead = 0xA1, CmdbGetinfoInvalid = 0xA2, CmdbGetinfoDone = 0xA3, FlashEraseDone = 0xE0, FlashRraseFail = 0xE1, FlashRraseRequest = 0xEE, FwUpdateDone = 0xF8, FwUpdateReady = 0xFC, FwUpdateRequest = 0xFE, } #[derive(ParseBytes)] struct KineticDpJaguarFooter { app_id_struct_ver: u32le, app_id: [u8; 4], app_ver_id: u32le, fw_ver: u16be, fw_rev: u8, customer_fw_project_id: u8, customer_fw_ver: u16be, chip_rev: u8, is_fpga_enabled: u8, reserved: [u8; 12], } #[derive(Parse)] struct KineticDpFlashInfo { id: u16be, size: u16be, erase_time: u16be, } #[derive(ToString)] enum KineticDpDpcd { // status CmdStsNone = 0x0, StsInvalidInfo = 0x01, StsCrcFailure = 0x02, StsInvalidImage = 0x03, StsSecureEnabled = 0x04, StsSecureDisabled = 0x05, StsSpiFlashFailure = 0x06, // command CmdPrepareForIspMode = 0x23, CmdEnterCodeLoadingMode = 0x24, CmdExecuteRamCode = 0x25, CmdEnterFwUpdateMode = 0x26, CmdChunkDataProcessed = 0x27, CmdInstallImages = 0x28, CmdResetSystem = 0x29, // other command CmdEnableAuxForward = 0x31, CmdDisableAuxForward = 0x32, CmdGetActiveFlashBank = 0x33, // 0x70 ~ 0x7F are reserved for other usage CmdReserved = 0x7f, } fwupd-1.9.16/plugins/kinetic-dp/meson.build000066400000000000000000000013501460375044200206130ustar00rootroot00000000000000if get_option('plugin_kinetic_dp').require(gudev.found(), error_message: 'gudev is needed for plugin_kinetic_dp').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginKineticDp"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtin_kinetic_dp = static_library('fu_plugin_kinetic_dp', rustgen.process('fu-kinetic-dp.rs'), sources: [ 'fu-kinetic-dp-device.c', 'fu-kinetic-dp-plugin.c', 'fu-kinetic-dp-puma-device.c', 'fu-kinetic-dp-puma-firmware.c', 'fu-kinetic-dp-secure-device.c', 'fu-kinetic-dp-secure-firmware.c', ], include_directories: plugin_incdirs, c_args: [ cargs, ], link_with: plugin_libs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_kinetic_dp endif fwupd-1.9.16/plugins/lenovo-thinklmi/000077500000000000000000000000001460375044200175425ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/README.md000066400000000000000000000006051460375044200210220ustar00rootroot00000000000000--- title: Plugin: Lenovo ThinkLMI --- ## Introduction This allows checking whether the firmware on a Lenovo system is configured to allow UEFI capsule updates using the thinklmi kernel module. ## External Interface Access This plugin requires: * read access to `/sys/class/firmware-attributes`. ## Version Considerations This plugin has been available since fwupd version `1.6.2`. fwupd-1.9.16/plugins/lenovo-thinklmi/fu-lenovo-thinklmi-plugin.c000066400000000000000000000077651460375044200247500ustar00rootroot00000000000000/* * Copyright (C) 2021 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-lenovo-thinklmi-plugin.h" struct _FuLenovoThinklmiPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuLenovoThinklmiPlugin, fu_lenovo_thinklmi_plugin, FU_TYPE_PLUGIN) #define BIOS_SETTING_SLEEP_MODE "com.thinklmi.SleepState" #define BIOS_SETTING_BOOT_ORDER_LOCK "com.thinklmi.BootOrderLock" #define BIOS_SETTING_SECURE_ROLLBACK "com.thinklmi.SecureRollBackPrevention" static gboolean fu_lenovo_thinklmi_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { const gchar *hwid = fu_context_get_hwid_value(fu_plugin_get_context(plugin), FU_HWIDS_KEY_MANUFACTURER); if (g_strcmp0(hwid, "LENOVO") != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported manufacturer"); return FALSE; } return TRUE; } static void fu_lenovo_thinklmi_plugin_cpu_registered(FuContext *ctx, FuDevice *device) { /* Ryzen 6000 doesn't support S3 even if the BIOS offers it */ if (fu_device_has_instance_id(device, "CPUID\\PRO_0&FAM_19&MOD_44")) { FwupdBiosSetting *attr = fu_context_get_bios_setting(ctx, BIOS_SETTING_SLEEP_MODE); if (attr != NULL) { g_debug("setting %s to read-only", fwupd_bios_setting_get_name(attr)); fwupd_bios_setting_set_read_only(attr, TRUE); } } } static void fu_lenovo_thinklmi_plugin_uefi_capsule_registered(FuContext *ctx, FuDevice *device) { FwupdBiosSetting *attr; /* check if boot order lock is turned on */ attr = fu_context_get_bios_setting(ctx, BIOS_SETTING_BOOT_ORDER_LOCK); if (attr == NULL) { g_debug("failed to find %s in cache", BIOS_SETTING_BOOT_ORDER_LOCK); return; } if (g_strcmp0(fwupd_bios_setting_get_current_value(attr), "Enable") == 0) { fu_device_inhibit(device, "uefi-capsule-bootorder", "BootOrder is locked in firmware setup"); } /* check if we're pending for a reboot */ if (fu_context_get_bios_setting_pending_reboot(ctx)) { fu_device_inhibit(device, "uefi-capsule-pending-reboot", "UEFI BIOS settings update pending reboot"); } } static void fu_lenovo_thinklmi_plugin_device_registered(FuPlugin *plugin, FuDevice *device) { if (g_strcmp0(fu_device_get_plugin(device), "uefi_capsule") == 0) { fu_lenovo_thinklmi_plugin_uefi_capsule_registered(fu_plugin_get_context(plugin), device); } else if (g_strcmp0(fu_device_get_plugin(device), "cpu") == 0) { fu_lenovo_thinklmi_plugin_cpu_registered(fu_plugin_get_context(plugin), device); } } static void fu_lenovo_thinklmi_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { FwupdBiosSetting *bios_attr; FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; bios_attr = fu_context_get_bios_setting(ctx, BIOS_SETTING_SECURE_ROLLBACK); if (bios_attr == NULL) { g_debug("failed to find %s in cache", BIOS_SETTING_SECURE_ROLLBACK); return; } attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_BIOS_ROLLBACK_PROTECTION); fu_security_attr_add_bios_target_value(attr, BIOS_SETTING_SECURE_ROLLBACK, "enable"); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); if (g_strcmp0(fwupd_bios_setting_get_current_value(bios_attr), "Disable") == 0) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_lenovo_thinklmi_plugin_init(FuLenovoThinklmiPlugin *self) { } static void fu_lenovo_thinklmi_plugin_class_init(FuLenovoThinklmiPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->startup = fu_lenovo_thinklmi_plugin_startup; plugin_class->device_registered = fu_lenovo_thinklmi_plugin_device_registered; plugin_class->add_security_attrs = fu_lenovo_thinklmi_plugin_add_security_attrs; } fwupd-1.9.16/plugins/lenovo-thinklmi/fu-lenovo-thinklmi-plugin.h000066400000000000000000000004361460375044200247410ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuLenovoThinklmiPlugin, fu_lenovo_thinklmi_plugin, FU, LENOVO_THINKLMI_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/lenovo-thinklmi/fu-self-test.c000066400000000000000000000160411460375044200222260ustar00rootroot00000000000000/* * Copyright (C) 2021 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "../uefi-capsule/fu-uefi-capsule-plugin.h" #include "fu-bios-settings-private.h" #include "fu-context-private.h" #include "fu-device-private.h" #include "fu-lenovo-thinklmi-plugin.h" #include "fu-plugin-private.h" typedef struct { FuContext *ctx; FuPlugin *plugin_uefi_capsule; FuPlugin *plugin_lenovo_thinklmi; } FuTest; static void _plugin_device_added_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data) { FuDevice **dev = (FuDevice **)user_data; *dev = device; } static gboolean fu_test_fatal_handler_cb(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { return log_level >= G_LOG_LEVEL_MESSAGE; } static gboolean fu_test_self_init(FuTest *self, GError **error) { gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_test_log_set_fatal_handler(fu_test_fatal_handler_cb, NULL); ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE | FU_QUIRKS_LOAD_FLAG_NO_VERIFY, error); g_assert_no_error(*error); g_assert_true(ret); ret = fu_context_load_hwinfo(ctx, progress, FU_CONTEXT_HWID_FLAG_LOAD_CONFIG, error); g_assert_no_error(*error); g_assert_true(ret); ret = fu_context_reload_bios_settings(ctx, error); #ifdef FU_THINKLMI_COMPAT g_assert_no_error(*error); g_assert_true(ret); #else g_assert_error(*error, G_FILE_ERROR, G_FILE_ERROR_NOENT); g_assert_false(ret); return FALSE; #endif self->plugin_uefi_capsule = fu_plugin_new_from_gtype(fu_uefi_capsule_plugin_get_type(), ctx); ret = fu_plugin_runner_startup(self->plugin_uefi_capsule, progress, error); g_assert_no_error(*error); g_assert_true(ret); self->plugin_lenovo_thinklmi = fu_plugin_new_from_gtype(fu_lenovo_thinklmi_plugin_get_type(), ctx); ret = fu_plugin_runner_startup(self->plugin_lenovo_thinklmi, progress, error); g_assert_no_error(*error); g_assert_true(ret); self->ctx = fu_plugin_get_context(self->plugin_lenovo_thinklmi); return TRUE; } static FuDevice * fu_test_probe_fake_esrt(FuTest *self) { gboolean ret; gulong added_id; FuDevice *dev = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; added_id = g_signal_connect(FU_PLUGIN(self->plugin_uefi_capsule), "device-added", G_CALLBACK(_plugin_device_added_cb), &dev); ret = fu_plugin_runner_coldplug(self->plugin_uefi_capsule, progress, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_nonnull(dev); g_assert_true(fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)); g_signal_handler_disconnect(self->plugin_uefi_capsule, added_id); return g_object_ref(dev); } static void fu_plugin_lenovo_thinklmi_bootorder_locked(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error = NULL; g_autofree gchar *test_dir = g_test_build_filename(G_TEST_DIST, "tests", "firmware-attributes", "locked", NULL); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", test_dir, TRUE); ret = fu_context_reload_bios_settings(self->ctx, &error); g_assert_no_error(error); g_assert_true(ret); dev = fu_test_probe_fake_esrt(self); fu_plugin_runner_device_register(self->plugin_lenovo_thinklmi, dev); g_assert_true(fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)); } static void fu_plugin_lenovo_thinklmi_bootorder_unlocked(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error = NULL; g_autofree gchar *test_dir = g_test_build_filename(G_TEST_DIST, "tests", "firmware-attributes", "unlocked", NULL); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", test_dir, TRUE); ret = fu_context_reload_bios_settings(self->ctx, &error); g_assert_no_error(error); g_assert_true(ret); dev = fu_test_probe_fake_esrt(self); fu_plugin_runner_device_register(self->plugin_lenovo_thinklmi, dev); g_assert_true(fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)); } static void fu_plugin_lenovo_thinklmi_reboot_pending(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error = NULL; g_autofree gchar *test_dir = g_test_build_filename(G_TEST_DIST, "tests", "firmware-attributes", "reboot-pending", NULL); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", test_dir, TRUE); ret = fu_context_reload_bios_settings(self->ctx, &error); g_assert_no_error(error); g_assert_true(ret); dev = fu_test_probe_fake_esrt(self); fu_plugin_runner_device_register(self->plugin_lenovo_thinklmi, dev); g_assert_true(fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)); } static void fu_test_self_free(FuTest *self) { if (self->plugin_uefi_capsule != NULL) g_object_unref(self->plugin_uefi_capsule); if (self->plugin_lenovo_thinklmi != NULL) g_object_unref(self->plugin_lenovo_thinklmi); g_free(self); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuTest, fu_test_self_free) #pragma clang diagnostic pop int main(int argc, char **argv) { g_autofree gchar *sysfsdir = NULL; g_autofree gchar *testdatadir = NULL; g_autofree gchar *confdir = NULL; g_autofree gchar *test_dir = NULL; g_autoptr(FuTest) self = g_new0(FuTest, 1); g_autoptr(GError) error = NULL; (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* starting thinklmi dir to make startup pass */ test_dir = g_test_build_filename(G_TEST_DIST, "tests", "firmware-attributes", "locked", NULL); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", test_dir, TRUE); /* starting ESRT path */ testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); (void)g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); /* change behavior of UEFI plugin for test mode */ sysfsdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_FW); (void)g_setenv("FWUPD_UEFI_ESP_PATH", sysfsdir, TRUE); (void)g_setenv("FWUPD_UEFI_TEST", "1", TRUE); /* to load fwupd.conf */ confdir = g_test_build_filename(G_TEST_DIST, "tests", "etc", "fwupd", NULL); (void)g_setenv("CONFIGURATION_DIRECTORY", confdir, TRUE); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_assert_cmpint(g_mkdir_with_parents("/tmp/fwupd-self-test/var/lib/fwupd", 0755), ==, 0); /* tests go here */ if (!fu_test_self_init(self, &error)) { g_test_skip(error->message); return 0; } g_test_add_data_func("/fwupd/plugin{lenovo-think-lmi:bootorder-locked}", self, fu_plugin_lenovo_thinklmi_bootorder_locked); g_test_add_data_func("/fwupd/plugin{lenovo-think-lmi:bootorder-unlocked}", self, fu_plugin_lenovo_thinklmi_bootorder_unlocked); g_test_add_data_func("/fwupd/plugin{lenovo-think-lmi:reboot-pending}", self, fu_plugin_lenovo_thinklmi_reboot_pending); return g_test_run(); } fwupd-1.9.16/plugins/lenovo-thinklmi/meson.build000066400000000000000000000022421460375044200217040ustar00rootroot00000000000000if get_option('plugin_uefi_capsule').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginLenovoThinkLmi"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtin_lenovo_thinklmi = static_library('fu_plugin_lenovo_thinklmi', sources: [ 'fu-lenovo-thinklmi-plugin.c', ], include_directories: plugin_incdirs, c_args: [ cargs, ], link_with: plugin_libs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_lenovo_thinklmi if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) env.set('FWUPD_LOCALSTATEDIR', '/tmp/fwupd-self-test/var') e = executable( 'lenovo-thinklmi-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ fwupd, fwupdplugin, plugin_builtin_lenovo_thinklmi, plugin_builtin_uefi_capsule, ], c_args: [ cargs, '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('lenovo-thinklmi-self-test', e, env: env) endif endif fwupd-1.9.16/plugins/lenovo-thinklmi/tests/000077500000000000000000000000001460375044200207045ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/000077500000000000000000000000001460375044200214475ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/efivars/000077500000000000000000000000001460375044200231065ustar00rootroot00000000000000fwupd-ddc0ee61-e7f0-4e7d-acc5-c070a398838e-0-0abba7dc-e516-4167-bbf5-4d9d1c739416000066400000000000000000000003461460375044200362430ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/efivars {iMi eY*@ZZ~I ʍ5Mm\EFI\fedora\fw\fwupd-697bd920-12cf-4da9-8385-996909bc6559.capfwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/esrt/000077500000000000000000000000001460375044200224245ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/esrt/entries/000077500000000000000000000000001460375044200240755ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/000077500000000000000000000000001460375044200253165ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/capsule_flags000066400000000000000000000000051460375044200300440ustar00rootroot000000000000000xfe fwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/fw_class000066400000000000000000000000451460375044200270410ustar00rootroot00000000000000ddc0ee61-e7f0-4e7d-acc5-c070a398838e fwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/fw_type000066400000000000000000000000021460375044200267060ustar00rootroot000000000000001 fwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/fw_version000066400000000000000000000000061460375044200274160ustar00rootroot0000000000000065586 fwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/last_attempt_status000066400000000000000000000000021460375044200313350ustar00rootroot000000000000001 fwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/last_attempt_version000066400000000000000000000000111460375044200314770ustar00rootroot0000000000000018472960 fwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/esrt/entries/entry0/lowest_supported_fw_version000066400000000000000000000000061460375044200331200ustar00rootroot0000000000000065582 fwupd-1.9.16/plugins/lenovo-thinklmi/tests/efi/fw_platform_size000066400000000000000000000000031460375044200247350ustar00rootroot0000000000000064 fwupd-1.9.16/plugins/lenovo-thinklmi/tests/etc/000077500000000000000000000000001460375044200214575ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/etc/fwupd/000077500000000000000000000000001460375044200226045ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/etc/fwupd/fwupd.conf000066400000000000000000000000341460375044200245750ustar00rootroot00000000000000[fwupd] Manufacturer=LENOVO fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/000077500000000000000000000000001460375044200247045ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/locked/000077500000000000000000000000001460375044200261455ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/locked/thinklmi/000077500000000000000000000000001460375044200277645ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/locked/thinklmi/attributes/000077500000000000000000000000001460375044200321525ustar00rootroot00000000000000BootOrderLock/000077500000000000000000000000001460375044200346035ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/locked/thinklmi/attributescurrent_value000066400000000000000000000000071460375044200374010ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/locked/thinklmi/attributes/BootOrderLockEnable display_name000066400000000000000000000000161460375044200371700ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/locked/thinklmi/attributes/BootOrderLockBootOrderLock pending_reboot000066400000000000000000000000021460375044200350040ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/locked/thinklmi/attributes0 fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/reboot-pending/000077500000000000000000000000001460375044200276205ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/reboot-pending/thinklmi/000077500000000000000000000000001460375044200314375ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/reboot-pending/thinklmi/attributes/000077500000000000000000000000001460375044200336255ustar00rootroot00000000000000BootOrderLock/000077500000000000000000000000001460375044200362565ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/reboot-pending/thinklmi/attributescurrent_value000066400000000000000000000000101460375044200410460ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/reboot-pending/thinklmi/attributes/BootOrderLockDisable display_name000066400000000000000000000000161460375044200406430ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/reboot-pending/thinklmi/attributes/BootOrderLockBootOrderLock pending_reboot000066400000000000000000000000021460375044200364570ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/reboot-pending/thinklmi/attributes1 fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/unlocked/000077500000000000000000000000001460375044200265105ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/unlocked/thinklmi/000077500000000000000000000000001460375044200303275ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/unlocked/thinklmi/attributes/000077500000000000000000000000001460375044200325155ustar00rootroot00000000000000BootOrderLock/000077500000000000000000000000001460375044200351465ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/unlocked/thinklmi/attributescurrent_value000066400000000000000000000000101460375044200377360ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/unlocked/thinklmi/attributes/BootOrderLockDisable display_name000066400000000000000000000000161460375044200375330ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/unlocked/thinklmi/attributes/BootOrderLockBootOrderLock pending_reboot000066400000000000000000000000021460375044200353470ustar00rootroot00000000000000fwupd-1.9.16/plugins/lenovo-thinklmi/tests/firmware-attributes/unlocked/thinklmi/attributes0 fwupd-1.9.16/plugins/linux-display/000077500000000000000000000000001460375044200172255ustar00rootroot00000000000000fwupd-1.9.16/plugins/linux-display/README.md000066400000000000000000000005751460375044200205130ustar00rootroot00000000000000--- title: Plugin: Linux Display --- ## Introduction This plugin checks if there are displays connected to each DRM device. The result will be used to inhibit devices that require a monitor to be attached. ## External Interface Access This plugin requires read access to `/sys/class/drm`. ## Version Considerations This plugin has been available since fwupd version `1.9.6`. fwupd-1.9.16/plugins/linux-display/fu-linux-display-plugin.c000066400000000000000000000063761460375044200241130ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-linux-display-plugin.h" struct _FuLinuxDisplayPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuLinuxDisplayPlugin, fu_linux_display_plugin, FU_TYPE_PLUGIN) static FuDisplayState fu_linux_display_plugin_get_display_state(FuLinuxDisplayPlugin *self) { FuDisplayState display_state = FU_DISPLAY_STATE_DISCONNECTED; GPtrArray *devices = fu_plugin_get_devices(FU_PLUGIN(self)); /* no devices detected */ if (devices->len == 0) return FU_DISPLAY_STATE_UNKNOWN; /* any connected display is good enough */ for (guint i = 0; i < devices->len; i++) { FuDrmDevice *drm_device = g_ptr_array_index(devices, i); if (fu_drm_device_get_state(drm_device) == FU_DISPLAY_STATE_CONNECTED) { display_state = FU_DISPLAY_STATE_CONNECTED; break; } } return display_state; } static void fu_linux_display_plugin_ensure_display_state(FuLinuxDisplayPlugin *self) { FuContext *ctx = fu_plugin_get_context(FU_PLUGIN(self)); if (!fu_plugin_has_flag(FU_PLUGIN(self), FWUPD_PLUGIN_FLAG_READY)) return; fu_context_set_display_state(ctx, fu_linux_display_plugin_get_display_state(self)); } static gboolean fu_linux_display_plugin_plugin_backend_device_added(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { FuLinuxDisplayPlugin *self = FU_LINUX_DISPLAY_PLUGIN(plugin); if (fu_drm_device_get_edid(FU_DRM_DEVICE(device)) != NULL) { if (!fu_device_setup(device, error)) return FALSE; fu_plugin_device_add(plugin, device); } fu_linux_display_plugin_ensure_display_state(self); return TRUE; } static gboolean fu_linux_display_plugin_plugin_ready(FuPlugin *plugin, FuProgress *progress, GError **error) { FuLinuxDisplayPlugin *self = FU_LINUX_DISPLAY_PLUGIN(plugin); fu_linux_display_plugin_ensure_display_state(self); return TRUE; } static gboolean fu_linux_display_plugin_plugin_backend_device_removed(FuPlugin *plugin, FuDevice *device, GError **error) { FuLinuxDisplayPlugin *self = FU_LINUX_DISPLAY_PLUGIN(plugin); fu_linux_display_plugin_ensure_display_state(self); return TRUE; } static gboolean fu_linux_display_plugin_plugin_backend_device_changed(FuPlugin *plugin, FuDevice *device, GError **error) { FuLinuxDisplayPlugin *self = FU_LINUX_DISPLAY_PLUGIN(plugin); if (!FU_IS_DRM_DEVICE(device)) return TRUE; fu_linux_display_plugin_ensure_display_state(self); return TRUE; } static void fu_linux_display_plugin_init(FuLinuxDisplayPlugin *self) { } static void fu_ata_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_udev_subsystem(plugin, "drm"); } static void fu_linux_display_plugin_class_init(FuLinuxDisplayPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_ata_plugin_constructed; plugin_class->ready = fu_linux_display_plugin_plugin_ready; plugin_class->backend_device_added = fu_linux_display_plugin_plugin_backend_device_added; plugin_class->backend_device_removed = fu_linux_display_plugin_plugin_backend_device_removed; plugin_class->backend_device_changed = fu_linux_display_plugin_plugin_backend_device_changed; } fwupd-1.9.16/plugins/linux-display/fu-linux-display-plugin.h000066400000000000000000000004301460375044200241010ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuLinuxDisplayPlugin, fu_linux_display_plugin, FU, LINUX_DISPLAY_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/linux-display/meson.build000066400000000000000000000005731460375044200213740ustar00rootroot00000000000000if host_machine.system() == 'linux' cargs = ['-DG_LOG_DOMAIN="FuPluginLinuxDisplay"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtins += static_library('fu_plugin_linux_display', sources: [ 'fu-linux-display-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/linux-lockdown/000077500000000000000000000000001460375044200174005ustar00rootroot00000000000000fwupd-1.9.16/plugins/linux-lockdown/README.md000066400000000000000000000005651460375044200206650ustar00rootroot00000000000000--- title: Plugin: Linux Kernel Lockdown --- ## Introduction This plugin checks if the currently running kernel is locked down. The result will be stored in an security attribute for HSI. ## External Interface Access This plugin requires read access to `/sys/sys/kernel/security`. ## Version Considerations This plugin has been available since fwupd version `1.5.0`. fwupd-1.9.16/plugins/linux-lockdown/fu-linux-lockdown-plugin.c000066400000000000000000000160461460375044200244340ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-linux-lockdown-plugin.h" #include "fu-linux-lockdown-struct.h" struct _FuLinuxLockdownPlugin { FuPlugin parent_instance; GFile *file; GFileMonitor *monitor; FuLinuxLockdown lockdown; }; G_DEFINE_TYPE(FuLinuxLockdownPlugin, fu_linux_lockdown_plugin, FU_TYPE_PLUGIN) static void fu_linux_lockdown_plugin_rescan(FuPlugin *plugin) { FuLinuxLockdownPlugin *self = FU_LINUX_LOCKDOWN_PLUGIN(plugin); gsize bufsz = 0; g_autofree gchar *buf = NULL; /* load file */ if (!g_file_load_contents(self->file, NULL, &buf, &bufsz, NULL, NULL)) { self->lockdown = FU_LINUX_LOCKDOWN_INVALID; } else if (g_strstr_len(buf, bufsz, "[none]") != NULL) { self->lockdown = FU_LINUX_LOCKDOWN_NONE; } else if (g_strstr_len(buf, bufsz, "[integrity]") != NULL) { self->lockdown = FU_LINUX_LOCKDOWN_INTEGRITY; } else if (g_strstr_len(buf, bufsz, "[confidentiality]") != NULL) { self->lockdown = FU_LINUX_LOCKDOWN_CONFIDENTIALITY; } else { self->lockdown = FU_LINUX_LOCKDOWN_UNKNOWN; } /* update metadata */ fu_plugin_add_report_metadata(plugin, "LinuxLockdown", fu_linux_lockdown_to_string(self->lockdown)); } static void fu_linux_lockdown_plugin_changed_cb(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { FuPlugin *plugin = FU_PLUGIN(user_data); FuContext *ctx = fu_plugin_get_context(plugin); fu_linux_lockdown_plugin_rescan(plugin); fu_context_security_changed(ctx); } static gboolean fu_linux_lockdown_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { FuLinuxLockdownPlugin *self = FU_LINUX_LOCKDOWN_PLUGIN(plugin); g_autofree gchar *path = NULL; g_autofree gchar *fn = NULL; path = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_SECURITY); fn = g_build_filename(path, "lockdown", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Kernel doesn't offer lockdown support."); return FALSE; } self->file = g_file_new_for_path(fn); self->monitor = g_file_monitor(self->file, G_FILE_MONITOR_NONE, NULL, error); if (self->monitor == NULL) return FALSE; g_signal_connect(G_FILE_MONITOR(self->monitor), "changed", G_CALLBACK(fu_linux_lockdown_plugin_changed_cb), plugin); fu_linux_lockdown_plugin_rescan(plugin); return TRUE; } static gboolean fu_linux_lockdown_plugin_ensure_security_attr_flags(FwupdSecurityAttr *attr, GError **error) { const gchar *value; g_autoptr(GHashTable) cmdline = NULL; g_autoptr(GHashTable) config = NULL; /* get current args */ cmdline = fu_kernel_get_cmdline(error); if (cmdline == NULL) return FALSE; /* can we modify the args */ if (!fu_kernel_check_cmdline_mutable(error)) return FALSE; /* get build flags */ config = fu_kernel_get_config(error); if (config == NULL) return FALSE; if (!g_hash_table_contains(config, "CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE")) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "config does not have CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE"); return FALSE; } /* we cannot change this */ if (g_hash_table_contains(config, "CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT") && fu_efivar_secure_boot_enabled(NULL)) { g_set_error_literal( error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "kernel lockdown cannot be changed when secure boot is enabled"); return FALSE; } /* set the current and target values */ value = g_hash_table_lookup(cmdline, "lockdown"); fwupd_security_attr_set_kernel_current_value(attr, value); if (g_strcmp0(value, "integrity") != 0) { fwupd_security_attr_set_kernel_target_value(attr, "lockdown=integrity"); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_CAN_FIX); } else { fwupd_security_attr_set_kernel_target_value(attr, "lockdown=none"); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_CAN_UNDO); } return TRUE; } static void fu_linux_lockdown_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuLinuxLockdownPlugin *self = FU_LINUX_LOCKDOWN_PLUGIN(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_KERNEL_LOCKDOWN); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); /* we might be able to fix this */ if (!fu_linux_lockdown_plugin_ensure_security_attr_flags(attr, &error_local)) g_info("failed to ensure attribute fix flags: %s", error_local->message); if (self->lockdown == FU_LINUX_LOCKDOWN_UNKNOWN) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); return; } /* load file */ if (self->lockdown == FU_LINUX_LOCKDOWN_INVALID) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } if (self->lockdown == FU_LINUX_LOCKDOWN_NONE) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_linux_lockdown_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuLinuxLockdownPlugin *self = FU_LINUX_LOCKDOWN_PLUGIN(plugin); fu_string_append(str, idt, "Lockdown", fu_linux_lockdown_to_string(self->lockdown)); } static void fu_linux_lockdown_plugin_init(FuLinuxLockdownPlugin *self) { } static void fu_linux_lockdown_finalize(GObject *obj) { FuLinuxLockdownPlugin *self = FU_LINUX_LOCKDOWN_PLUGIN(obj); if (self->file != NULL) g_object_unref(self->file); if (self->monitor != NULL) { g_file_monitor_cancel(self->monitor); g_object_unref(self->monitor); } G_OBJECT_CLASS(fu_linux_lockdown_plugin_parent_class)->finalize(obj); } static gboolean fu_linux_lockdown_plugin_fix_host_security_attr(FuPlugin *plugin, FwupdSecurityAttr *attr, GError **error) { return fu_kernel_add_cmdline_arg("lockdown=integrity", error); } static gboolean fu_linux_lockdown_plugin_undo_host_security_attr(FuPlugin *plugin, FwupdSecurityAttr *attr, GError **error) { return fu_kernel_remove_cmdline_arg("lockdown=integrity", error); } static void fu_linux_lockdown_plugin_class_init(FuLinuxLockdownPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_linux_lockdown_finalize; plugin_class->to_string = fu_linux_lockdown_plugin_to_string; plugin_class->startup = fu_linux_lockdown_plugin_startup; plugin_class->add_security_attrs = fu_linux_lockdown_plugin_add_security_attrs; plugin_class->fix_host_security_attr = fu_linux_lockdown_plugin_fix_host_security_attr; plugin_class->undo_host_security_attr = fu_linux_lockdown_plugin_undo_host_security_attr; } fwupd-1.9.16/plugins/linux-lockdown/fu-linux-lockdown-plugin.h000066400000000000000000000004331460375044200244320ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuLinuxLockdownPlugin, fu_linux_lockdown_plugin, FU, LINUX_LOCKDOWN_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/linux-lockdown/fu-linux-lockdown.rs000066400000000000000000000003251460375044200233330ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum LinuxLockdown { Unknown, Invalid, None, Integrity, Confidentiality, } fwupd-1.9.16/plugins/linux-lockdown/meson.build000066400000000000000000000006611460375044200215450ustar00rootroot00000000000000if host_machine.system() == 'linux' and hsi cargs = ['-DG_LOG_DOMAIN="FuPluginLinuxLockdown"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtins += static_library('fu_plugin_linux_lockdown', rustgen.process('fu-linux-lockdown.rs'), sources: [ 'fu-linux-lockdown-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/linux-sleep/000077500000000000000000000000001460375044200166705ustar00rootroot00000000000000fwupd-1.9.16/plugins/linux-sleep/README.md000066400000000000000000000005301460375044200201450ustar00rootroot00000000000000--- title: Plugin: Linux Kernel Sleep --- ## Introduction This plugin checks if s3 sleep is available. The result will be stored in an security attribute for HSI. ## External Interface Access This plugin requires read access to `/sys/power/mem_sleep`. ## Version Considerations This plugin has been available since fwupd version `1.5.0`. fwupd-1.9.16/plugins/linux-sleep/fu-linux-sleep-plugin.c000066400000000000000000000034111460375044200232040ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-linux-sleep-plugin.h" struct _FuLinuxSleepPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuLinuxSleepPlugin, fu_linux_sleep_plugin, FU_TYPE_PLUGIN) static void fu_linux_sleep_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GFile) file = g_file_new_for_path("/sys/power/mem_sleep"); /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_RAM); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fu_security_attrs_append(attrs, attr); /* load file */ if (!g_file_load_contents(file, NULL, &buf, &bufsz, NULL, &error_local)) { g_autofree gchar *fn = g_file_get_path(file); g_warning("could not open %s: %s", fn, error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } if (g_strstr_len(buf, bufsz, "[deep]") != NULL) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_linux_sleep_plugin_init(FuLinuxSleepPlugin *self) { } static void fu_linux_sleep_plugin_class_init(FuLinuxSleepPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->add_security_attrs = fu_linux_sleep_plugin_add_security_attrs; } fwupd-1.9.16/plugins/linux-sleep/fu-linux-sleep-plugin.h000066400000000000000000000003661460375044200232170ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuLinuxSleepPlugin, fu_linux_sleep_plugin, FU, LINUX_SLEEP_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/linux-sleep/meson.build000066400000000000000000000006551460375044200210400ustar00rootroot00000000000000if host_machine.system() == 'linux' and hsi and (host_cpu == 'x86' or host_cpu == 'x86_64') cargs = ['-DG_LOG_DOMAIN="FuPluginLinuxSleep"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtins += static_library('fu_plugin_linux_sleep', sources: [ 'fu-linux-sleep-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/linux-swap/000077500000000000000000000000001460375044200165325ustar00rootroot00000000000000fwupd-1.9.16/plugins/linux-swap/README.md000066400000000000000000000005561460375044200200170ustar00rootroot00000000000000--- title: Plugin: Linux Swap --- ## Introduction This plugin checks if the currently available swap partitions and files are all encrypted. The result will be stored in an security attribute for HSI. ## External Interface Access This plugin requires read access to `/proc` ## Version Considerations This plugin has been available since fwupd version `1.5.0`. fwupd-1.9.16/plugins/linux-swap/fu-linux-swap-plugin.c000066400000000000000000000077651460375044200227300ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-linux-swap-plugin.h" #include "fu-linux-swap.h" struct _FuLinuxSwapPlugin { FuPlugin parent_instance; GFile *file; GFileMonitor *monitor; }; G_DEFINE_TYPE(FuLinuxSwapPlugin, fu_linux_swap_plugin, FU_TYPE_PLUGIN) static void fu_linux_swap_plugin_changed_cb(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { FuPlugin *plugin = FU_PLUGIN(user_data); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_security_changed(ctx); } static gboolean fu_linux_swap_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { FuLinuxSwapPlugin *self = FU_LINUX_SWAP_PLUGIN(plugin); g_autofree gchar *fn = NULL; g_autofree gchar *procfs = NULL; procfs = fu_path_from_kind(FU_PATH_KIND_PROCFS); fn = g_build_filename(procfs, "swaps", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Kernel doesn't offer swap support."); return FALSE; } self->file = g_file_new_for_path(fn); self->monitor = g_file_monitor(self->file, G_FILE_MONITOR_NONE, NULL, error); if (self->monitor == NULL) return FALSE; g_signal_connect(G_FILE_MONITOR(self->monitor), "changed", G_CALLBACK(fu_linux_swap_plugin_changed_cb), plugin); return TRUE; } static void fu_linux_swap_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuLinuxSwapPlugin *self = FU_LINUX_SWAP_PLUGIN(plugin); gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autoptr(FuLinuxSwap) swap = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; if (self->file == NULL) return; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_KERNEL_SWAP); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENCRYPTED); fu_security_attrs_append(attrs, attr); /* load list of swaps */ if (!g_file_load_contents(self->file, NULL, &buf, &bufsz, NULL, &error_local)) { g_autofree gchar *fn = g_file_get_path(self->file); g_warning("could not open %s: %s", fn, error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } swap = fu_linux_swap_new(buf, bufsz, &error_local); if (swap == NULL) { g_autofree gchar *fn = g_file_get_path(self->file); g_warning("could not parse %s: %s", fn, error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } /* none configured */ if (!fu_linux_swap_get_enabled(swap)) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); return; } /* add security attribute */ if (!fu_linux_swap_get_encrypted(swap)) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_linux_swap_plugin_init(FuLinuxSwapPlugin *self) { } static void fu_linux_swap_finalize(GObject *obj) { FuLinuxSwapPlugin *self = FU_LINUX_SWAP_PLUGIN(obj); if (self->file != NULL) g_object_unref(self->file); if (self->monitor != NULL) { g_file_monitor_cancel(self->monitor); g_object_unref(self->monitor); } G_OBJECT_CLASS(fu_linux_swap_plugin_parent_class)->finalize(obj); } static void fu_linux_swap_plugin_class_init(FuLinuxSwapPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_linux_swap_finalize; plugin_class->startup = fu_linux_swap_plugin_startup; plugin_class->add_security_attrs = fu_linux_swap_plugin_add_security_attrs; } fwupd-1.9.16/plugins/linux-swap/fu-linux-swap-plugin.h000066400000000000000000000003631460375044200227200ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuLinuxSwapPlugin, fu_linux_swap_plugin, FU, LINUX_SWAP_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/linux-swap/fu-linux-swap.c000066400000000000000000000075221460375044200214230ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-linux-swap.h" struct _FuLinuxSwap { GObject parent_instance; guint encrypted_cnt; guint enabled_cnt; }; G_DEFINE_TYPE(FuLinuxSwap, fu_linux_swap, G_TYPE_OBJECT) static gchar * fu_strdup_nospaces(const gchar *line) { GString *str = g_string_new(NULL); for (guint i = 0; line[i] != '\0' && !g_ascii_isspace(line[i]); i++) g_string_append_c(str, line[i]); return g_string_free(str, FALSE); } static gboolean fu_linux_swap_verify_partition(FuLinuxSwap *self, const gchar *fn, GError **error) { g_autoptr(FuVolume) volume = NULL; /* find the device */ volume = fu_volume_new_by_device(fn, error); if (volume == NULL) return FALSE; /* this isn't technically encrypted, but isn't on disk in plaintext */ if (g_str_has_prefix(fn, "/dev/zram")) { g_debug("%s is zram, assuming encrypted", fn); self->encrypted_cnt++; return TRUE; } /* is this mount point encrypted */ if (fu_volume_is_encrypted(volume)) { g_debug("%s partition is encrypted", fn); self->encrypted_cnt++; } else { g_debug("%s partition is unencrypted", fn); } /* success */ return TRUE; } static gboolean fu_linux_swap_verify_file(FuLinuxSwap *self, const gchar *fn, GError **error) { guint32 devnum; g_autoptr(GFile) file = NULL; g_autoptr(GFileInfo) info = NULL; g_autoptr(FuVolume) volume = NULL; /* get the device number for the file */ file = g_file_new_for_path(fn); info = g_file_query_info(file, G_FILE_ATTRIBUTE_UNIX_DEVICE, G_FILE_QUERY_INFO_NONE, NULL, error); if (info == NULL) return FALSE; devnum = g_file_info_get_attribute_uint32(info, G_FILE_ATTRIBUTE_UNIX_DEVICE); /* find the device */ volume = fu_volume_new_by_devnum(devnum, error); if (volume == NULL) return FALSE; /* is this mount point encrypted */ if (fu_volume_is_encrypted(volume)) { g_debug("%s file is encrypted", fn); self->encrypted_cnt++; } else { g_debug("%s file is unencrypted", fn); } /* success */ return TRUE; } FuLinuxSwap * fu_linux_swap_new(const gchar *buf, gsize bufsz, GError **error) { g_autoptr(FuLinuxSwap) self = g_object_new(FU_TYPE_LINUX_SWAP, NULL); g_auto(GStrv) lines = NULL; /* look at each line in /proc/swaps */ if (bufsz == 0) bufsz = strlen(buf); lines = fu_strsplit(buf, bufsz, "\n", -1); if (g_strv_length(lines) > 2) { for (guint i = 1; lines[i] != NULL && lines[i][0] != '\0'; i++) { g_autofree gchar *fn = NULL; g_autofree gchar *ty = NULL; /* split */ if (g_utf8_strlen(lines[i], -1) < 45) continue; fn = fu_strdup_nospaces(lines[i]); ty = fu_strdup_nospaces(lines[i] + 40); /* partition, so use UDisks to see if backed by crypto */ if (g_strcmp0(ty, "partition") == 0) { self->enabled_cnt++; if (!fu_linux_swap_verify_partition(self, fn, error)) return NULL; } else if (g_strcmp0(ty, "file") == 0) { g_autofree gchar *base = NULL; g_autofree gchar *path = NULL; /* get the path to the file */ base = fu_path_from_kind(FU_PATH_KIND_HOSTFS_ROOT); path = g_build_filename(base, fn, NULL); self->enabled_cnt++; if (!fu_linux_swap_verify_file(self, path, error)) return NULL; } else { g_warning("unknown swap type: %s [%s]", ty, fn); } } } return g_steal_pointer(&self); } /* success if *all* the swap devices are encrypted */ gboolean fu_linux_swap_get_encrypted(FuLinuxSwap *self) { g_return_val_if_fail(FU_IS_LINUX_SWAP(self), FALSE); return self->enabled_cnt > 0 && self->enabled_cnt == self->encrypted_cnt; } gboolean fu_linux_swap_get_enabled(FuLinuxSwap *self) { g_return_val_if_fail(FU_IS_LINUX_SWAP(self), FALSE); return self->enabled_cnt > 0; } static void fu_linux_swap_class_init(FuLinuxSwapClass *klass) { } static void fu_linux_swap_init(FuLinuxSwap *self) { } fwupd-1.9.16/plugins/linux-swap/fu-linux-swap.h000066400000000000000000000007211460375044200214220ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_LINUX_SWAP (fu_linux_swap_get_type()) G_DECLARE_FINAL_TYPE(FuLinuxSwap, fu_linux_swap, FU, LINUX_SWAP, GObject) FuLinuxSwap * fu_linux_swap_new(const gchar *buf, gsize bufsz, GError **error); gboolean fu_linux_swap_get_enabled(FuLinuxSwap *self); gboolean fu_linux_swap_get_encrypted(FuLinuxSwap *self); fwupd-1.9.16/plugins/linux-swap/fu-self-test.c000066400000000000000000000051401460375044200212140ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-linux-swap.h" static void fu_linux_swap_none_func(void) { g_autoptr(FuLinuxSwap) swap = NULL; g_autoptr(GError) error = NULL; swap = fu_linux_swap_new("Filename\t\t\t\tType\t\tSize\tUsed\tPriority\n", 0, &error); g_assert_no_error(error); g_assert_nonnull(swap); g_assert_false(fu_linux_swap_get_enabled(swap)); g_assert_false(fu_linux_swap_get_encrypted(swap)); } static void fu_linux_swap_plain_func(void) { g_autoptr(FuLinuxSwap) swap = NULL; g_autoptr(GError) error = NULL; swap = fu_linux_swap_new("Filename\t\t\t\tType\t\tSize\tUsed\tPriority\n" "/dev/nvme0n1p4 partition\t5962748\t0\t-2\n", 0, &error); if (g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER) || g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN) || g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_EXEC_FAILED) || g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED) || g_error_matches(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT) || g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY) || g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_test_skip(error->message); return; } g_assert_no_error(error); g_assert_nonnull(swap); } static void fu_linux_swap_encrypted_func(void) { g_autoptr(FuLinuxSwap) swap = NULL; g_autoptr(GError) error = NULL; swap = fu_linux_swap_new("Filename\t\t\t\tType\t\tSize\tUsed\tPriority\n" "/dev/dm-1 partition\t5962748\t0\t-2\n", 0, &error); if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) || g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN) || g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_EXEC_FAILED) || g_error_matches(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { g_test_skip(error->message); return; } g_assert_no_error(error); g_assert_nonnull(swap); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); (void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE); /* tests go here */ g_test_add_func("/linux-swap/none", fu_linux_swap_none_func); g_test_add_func("/linux-swap/plain", fu_linux_swap_plain_func); g_test_add_func("/linux-swap/encrypted", fu_linux_swap_encrypted_func); return g_test_run(); } fwupd-1.9.16/plugins/linux-swap/meson.build000066400000000000000000000021141460375044200206720ustar00rootroot00000000000000if host_machine.system() == 'linux' and hsi cargs = ['-DG_LOG_DOMAIN="FuPluginLinuxSwap"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtin_linux_swap = static_library('fu_plugin_linux_swap', sources: [ 'fu-linux-swap-plugin.c', 'fu-linux-swap.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_linux_swap if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'linux-swap-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_linux_swap, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('linux-swap-self-test', e, env: env) # added to installed-tests endif endif fwupd-1.9.16/plugins/linux-tainted/000077500000000000000000000000001460375044200172105ustar00rootroot00000000000000fwupd-1.9.16/plugins/linux-tainted/README.md000066400000000000000000000005531460375044200204720ustar00rootroot00000000000000--- title: Plugin: Linux Kernel Tainted --- ## Introduction This plugin checks if the currently running kernel is tainted. The result will be stored in an security attribute for HSI. ## External Interface Access This plugin requires read access to `/sys/kernel/tainted`. ## Version Considerations This plugin has been available since fwupd version `1.5.0`. fwupd-1.9.16/plugins/linux-tainted/fu-linux-tainted-plugin.c000066400000000000000000000125401460375044200240470ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-linux-tainted-plugin.h" struct _FuLinuxTaintedPlugin { FuPlugin parent_instance; GFile *file; GFileMonitor *monitor; }; G_DEFINE_TYPE(FuLinuxTaintedPlugin, fu_linux_tainted_plugin, FU_TYPE_PLUGIN) #define KERNEL_TAINT_FLAG_PROPRIETARY_MODULE (1 << 0) #define KERNEL_TAINT_FLAG_MODULE_FORCE_LOAD (1 << 1) #define KERNEL_TAINT_FLAG_KERNEL_OUT_OF_SPEC (1 << 2) #define KERNEL_TAINT_FLAG_MODULE_FORCE_UNLOAD (1 << 3) #define KERNEL_TAINT_FLAG_PROCESSOR_MCE (1 << 4) #define KERNEL_TAINT_FLAG_BAD_PAGE (1 << 5) #define KERNEL_TAINT_FLAG_REQUESTED_BY_USERSPACE (1 << 6) #define KERNEL_TAINT_FLAG_KERNEL_DIED (1 << 7) #define KERNEL_TAINT_FLAG_ACPI_OVERRIDDEN (1 << 8) #define KERNEL_TAINT_FLAG_KERNEL_ISSUED_WARNING (1 << 9) #define KERNEL_TAINT_FLAG_STAGING_DRIVER_LOADED (1 << 10) #define KERNEL_TAINT_FLAG_FIRMWARE_WORKAROUND_APPLIED (1 << 11) #define KERNEL_TAINT_FLAG_EXTERNAL_MODULE_LOADED (1 << 12) #define KERNEL_TAINT_FLAG_UNSIGNED_MODULE_LOADED (1 << 13) #define KERNEL_TAINT_FLAG_SOFT_LOCKUP_OCCURRED (1 << 14) #define KERNEL_TAINT_FLAG_KERNEL_LIVE_PATCHED (1 << 15) #define KERNEL_TAINT_FLAG_AUXILIARY_TAINT (1 << 16) #define KERNEL_TAINT_FLAG_STRUCT_RANDOMIZATION_PLUGIN (1 << 17) #define KERNEL_TAINT_FLAG_IN_KERNEL_TEST (1 << 18) static void fu_linux_tainted_plugin_changed_cb(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { FuPlugin *plugin = FU_PLUGIN(user_data); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_security_changed(ctx); } static gboolean fu_linux_tainted_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { FuLinuxTaintedPlugin *self = FU_LINUX_TAINTED_PLUGIN(plugin); g_autofree gchar *fn = NULL; g_autofree gchar *procfs = NULL; procfs = fu_path_from_kind(FU_PATH_KIND_PROCFS); fn = g_build_filename(procfs, "sys", "kernel", "tainted", NULL); self->file = g_file_new_for_path(fn); self->monitor = g_file_monitor(self->file, G_FILE_MONITOR_NONE, NULL, error); if (self->monitor == NULL) return FALSE; g_signal_connect(G_FILE_MONITOR(self->monitor), "changed", G_CALLBACK(fu_linux_tainted_plugin_changed_cb), plugin); return TRUE; } static void fu_linux_tainted_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuLinuxTaintedPlugin *self = FU_LINUX_TAINTED_PLUGIN(plugin); gsize bufsz = 0; guint64 value = 0; g_autofree gchar *buf = NULL; g_autofree gchar *str = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_KERNEL_TAINTED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED); fu_security_attrs_append(attrs, attr); /* startup failed */ if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); return; } /* load file */ if (!g_file_load_contents(self->file, NULL, &buf, &bufsz, NULL, &error_local)) { g_autofree gchar *fn = g_file_get_path(self->file); g_warning("could not open %s: %s", fn, error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } /* do not assume NUL terminated */ str = g_strndup(buf, bufsz); if (!fu_strtoull(str, &value, 0, G_MAXUINT64, &error_local)) { g_warning("could not parse %s: %s", str, error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } /* only some taint flags are important */ if ((value & KERNEL_TAINT_FLAG_PROPRIETARY_MODULE) > 0 || (value & KERNEL_TAINT_FLAG_MODULE_FORCE_LOAD) > 0 || (value & KERNEL_TAINT_FLAG_MODULE_FORCE_UNLOAD) > 0 || (value & KERNEL_TAINT_FLAG_STAGING_DRIVER_LOADED) > 0 || (value & KERNEL_TAINT_FLAG_EXTERNAL_MODULE_LOADED) > 0 || (value & KERNEL_TAINT_FLAG_UNSIGNED_MODULE_LOADED) > 0 || (value & KERNEL_TAINT_FLAG_ACPI_OVERRIDDEN) > 0 || (value & KERNEL_TAINT_FLAG_AUXILIARY_TAINT) > 0) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_TAINTED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_linux_tainted_plugin_init(FuLinuxTaintedPlugin *self) { } static void fu_linux_tainted_finalize(GObject *obj) { FuLinuxTaintedPlugin *self = FU_LINUX_TAINTED_PLUGIN(obj); if (self->file != NULL) g_object_unref(self->file); if (self->monitor != NULL) { g_file_monitor_cancel(self->monitor); g_object_unref(self->monitor); } G_OBJECT_CLASS(fu_linux_tainted_plugin_parent_class)->finalize(obj); } static void fu_linux_tainted_plugin_class_init(FuLinuxTaintedPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_linux_tainted_finalize; plugin_class->startup = fu_linux_tainted_plugin_startup; plugin_class->add_security_attrs = fu_linux_tainted_plugin_add_security_attrs; } fwupd-1.9.16/plugins/linux-tainted/fu-linux-tainted-plugin.h000066400000000000000000000004301460375044200240470ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuLinuxTaintedPlugin, fu_linux_tainted_plugin, FU, LINUX_TAINTED_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/linux-tainted/meson.build000066400000000000000000000006031460375044200213510ustar00rootroot00000000000000if host_machine.system() == 'linux' and hsi cargs = ['-DG_LOG_DOMAIN="FuPluginLinuxTainted"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtins += static_library('fu_plugin_linux_tainted', sources: [ 'fu-linux-tainted-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/logind/000077500000000000000000000000001460375044200156775ustar00rootroot00000000000000fwupd-1.9.16/plugins/logind/README.md000066400000000000000000000007071460375044200171620ustar00rootroot00000000000000--- title: Plugin: logind --- ## Introduction This plugin is used to ensure that the machine does not enter a low power mode when updates are being performed. ## Vendor ID Security This protocol does not create a device and thus requires no vendor ID set. ## External Interface Access This plugin requires access to the dbus interface `org.freedesktop.login1`. ## Version Considerations This plugin has been available since fwupd version `1.4.0`. fwupd-1.9.16/plugins/logind/fu-logind-plugin.c000066400000000000000000000077031460375044200212320ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-logind-plugin.h" struct _FuLogindPlugin { FuPlugin parent_instance; GDBusProxy *logind_proxy; gint logind_fd; }; G_DEFINE_TYPE(FuLogindPlugin, fu_logind_plugin, FU_TYPE_PLUGIN) static void fu_logind_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuLogindPlugin *self = FU_LOGIND_PLUGIN(plugin); fu_string_append_kx(str, idt, "LogindFd", self->logind_fd); } static gboolean fu_logind_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { FuLogindPlugin *self = FU_LOGIND_PLUGIN(plugin); g_autofree gchar *name_owner = NULL; self->logind_proxy = g_dbus_proxy_new_for_bus_sync( G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NULL, "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", NULL, error); if (self->logind_proxy == NULL) { g_prefix_error(error, "failed to connect to logind: "); return FALSE; } name_owner = g_dbus_proxy_get_name_owner(self->logind_proxy); if (name_owner == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no owner for %s", g_dbus_proxy_get_name(self->logind_proxy)); return FALSE; } return TRUE; } static gboolean fu_logind_plugin_prepare(FuPlugin *plugin, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuLogindPlugin *self = FU_LOGIND_PLUGIN(plugin); g_autoptr(GError) error_local = NULL; g_autoptr(GUnixFDList) out_fd_list = NULL; g_autoptr(GVariant) res = NULL; const gchar *what = "shutdown:sleep:idle:handle-power-key:handle-suspend-key:" "handle-hibernate-key:handle-lid-switch"; /* already inhibited */ if (self->logind_fd >= 0) return TRUE; /* not yet connected */ if (self->logind_proxy == NULL) { g_warning("no logind connection to use"); return TRUE; } /* block shutdown and idle */ res = g_dbus_proxy_call_with_unix_fd_list_sync( self->logind_proxy, "Inhibit", g_variant_new("(ssss)", what, PACKAGE_NAME, "Firmware Update in Progress", "block"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, /* fd_list */ &out_fd_list, NULL, /* GCancellable */ &error_local); if (res == NULL) { g_warning("failed to Inhibit using logind: %s", error_local->message); return TRUE; } /* keep fd as cookie */ if (g_unix_fd_list_get_length(out_fd_list) != 1) { g_warning("invalid response from logind"); return TRUE; } self->logind_fd = g_unix_fd_list_get(out_fd_list, 0, NULL); g_debug("opened logind fd %i", self->logind_fd); return TRUE; } static gboolean fu_logind_plugin_cleanup(FuPlugin *plugin, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuLogindPlugin *self = FU_LOGIND_PLUGIN(plugin); if (self->logind_fd < 0) return TRUE; g_debug("closed logind fd %i", self->logind_fd); if (!g_close(self->logind_fd, error)) return FALSE; self->logind_fd = -1; return TRUE; } static void fu_logind_plugin_init(FuLogindPlugin *self) { self->logind_fd = -1; } static void fu_logind_finalize(GObject *obj) { FuLogindPlugin *self = FU_LOGIND_PLUGIN(obj); if (self->logind_fd >= 0) g_close(self->logind_fd, NULL); if (self->logind_proxy != NULL) g_object_unref(self->logind_proxy); G_OBJECT_CLASS(fu_logind_plugin_parent_class)->finalize(obj); } static void fu_logind_plugin_class_init(FuLogindPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_logind_finalize; plugin_class->to_string = fu_logind_plugin_to_string; plugin_class->startup = fu_logind_plugin_startup; plugin_class->cleanup = fu_logind_plugin_cleanup; plugin_class->prepare = fu_logind_plugin_prepare; } fwupd-1.9.16/plugins/logind/fu-logind-plugin.h000066400000000000000000000003501460375044200212260ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuLogindPlugin, fu_logind_plugin, FU, LOGIND_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/logind/meson.build000066400000000000000000000005541460375044200200450ustar00rootroot00000000000000if libsystemd.found() or elogind.found() cargs = ['-DG_LOG_DOMAIN="FuPluginLogind"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtins += static_library('fu_plugin_logind', sources: [ 'fu-logind-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/logitech-bulkcontroller/000077500000000000000000000000001460375044200212605ustar00rootroot00000000000000fwupd-1.9.16/plugins/logitech-bulkcontroller/README.md000066400000000000000000000052161460375044200225430ustar00rootroot00000000000000--- title: Plugin: Logitech Bulk Controller — Video Collaboration --- ## Introduction This plugin can upgrade the firmware on Logitech Video Collaboration products (Rally Bar and RallyBar Mini), using USB bulk transfer. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `com.logitech.vc.proto` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_046D&PID_089B` ## Quirk Use This plugin uses the following plugin-specific quirks: ### `Flags=check-buffer-size` Query the device at startup to see if we can use a larger buffer size. Since: 1.9.7 ## Update Behavior The peripheral firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Design Notes There are two protocols in use, `LogitechBulkcontrollerSendSync` and `LogitechBulkcontrollerSendUpd` which correspond to the two different bulk endpoints. The "Sync" interface accepts protobuf-formatted binary data as described in `proto/`, encapsulated further in a `LogitechBulkcontrollerSendSyncReq` and returned as a `LogitechBulkcontrollerSendSyncRes`. The sequence IDs seem to be used to allow parallel queries, although in practice some of the IDs are hardcoded to zero even when setting them in the request. There seems to be two possible flows when writing using the bulk "Sync" interface: ```mermaid sequenceDiagram Host->>+Device: Write Device-->>-Host: Ack (Write) Host->>+Device: Uninit Device-->>-Host: Ack (Uninit) Device->>+Host: Read Host-->>-Device: Ack (Read) Device->>+Host: Uninit Host-->>-Device: Ack (Uninit) ``` or... ```mermaid sequenceDiagram Host->>+Device: Write Device-->>-Host: Ack (Write) Host->>+Device: Uninit Device->>+Host: Read Device-->>-Host: Ack (Uninit) Host-->>-Device: Ack (Read) Device->>+Host: Uninit Host-->>-Device: Ack (Uninit) ``` Additionally, the device seems to force re-enumeration at random times, presumably restarting due to a protocol error or interface timeout. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x046D` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.7.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Sanjay Sheth: @vcdmp fwupd-1.9.16/plugins/logitech-bulkcontroller/fu-logitech-bulkcontroller-common.c000066400000000000000000000154171460375044200301670ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-logitech-bulkcontroller-common.h" #include "usb_msg.pb-c.h" static void proto_manager_set_header(Logi__Device__Proto__Header *header_msg) { gint64 timestamp_tv; g_return_if_fail(header_msg != NULL); timestamp_tv = g_get_real_time(); header_msg->id = g_uuid_string_random(); header_msg->timestamp = g_strdup_printf("%" G_GINT64_FORMAT, timestamp_tv / 1000); } GByteArray * proto_manager_generate_get_device_info_request(void) { GByteArray *buf = g_byte_array_new(); Logi__Device__Proto__Header header_msg = LOGI__DEVICE__PROTO__HEADER__INIT; Logi__Device__Proto__GetDeviceInfoRequest get_deviceinfo_msg = LOGI__DEVICE__PROTO__GET_DEVICE_INFO_REQUEST__INIT; Logi__Device__Proto__UsbMsg usb_msg = LOGI__DEVICE__PROTO__USB_MSG__INIT; Logi__Device__Proto__Request request_msg = LOGI__DEVICE__PROTO__REQUEST__INIT; request_msg.payload_case = LOGI__DEVICE__PROTO__REQUEST__PAYLOAD_GET_DEVICE_INFO_REQUEST; request_msg.get_device_info_request = &get_deviceinfo_msg; proto_manager_set_header(&header_msg); usb_msg.header = &header_msg; usb_msg.message_case = LOGI__DEVICE__PROTO__USB_MSG__MESSAGE_REQUEST; usb_msg.request = &request_msg; fu_byte_array_set_size(buf, logi__device__proto__usb_msg__get_packed_size(&usb_msg), 0x00); logi__device__proto__usb_msg__pack(&usb_msg, (unsigned char *)buf->data); g_free(header_msg.id); g_free(header_msg.timestamp); return buf; } GByteArray * proto_manager_generate_transition_to_device_mode_request(void) { GByteArray *buf = g_byte_array_new(); Logi__Device__Proto__Header header_msg = LOGI__DEVICE__PROTO__HEADER__INIT; Logi__Device__Proto__TransitionToDeviceModeRequest transition_to_device_mode_msg = LOGI__DEVICE__PROTO__TRANSITION_TO_DEVICE_MODE_REQUEST__INIT; Logi__Device__Proto__UsbMsg usb_msg = LOGI__DEVICE__PROTO__USB_MSG__INIT; Logi__Device__Proto__Request request_msg = LOGI__DEVICE__PROTO__REQUEST__INIT; request_msg.payload_case = LOGI__DEVICE__PROTO__REQUEST__PAYLOAD_TRANSITION_TO_DEVICEMODE_REQUEST; request_msg.transition_to_devicemode_request = &transition_to_device_mode_msg; proto_manager_set_header(&header_msg); usb_msg.header = &header_msg; usb_msg.message_case = LOGI__DEVICE__PROTO__USB_MSG__MESSAGE_REQUEST; usb_msg.request = &request_msg; fu_byte_array_set_size(buf, logi__device__proto__usb_msg__get_packed_size(&usb_msg), 0x00); logi__device__proto__usb_msg__pack(&usb_msg, (unsigned char *)buf->data); g_free(header_msg.id); g_free(header_msg.timestamp); return buf; } GByteArray * proto_manager_generate_set_device_time_request(GError **error) { g_autofree gchar *olson_location = NULL; g_autoptr(GByteArray) buf = g_byte_array_new(); Logi__Device__Proto__Header header_msg = LOGI__DEVICE__PROTO__HEADER__INIT; Logi__Device__Proto__SetDeviceTimeRequest set_devicetime_msg = LOGI__DEVICE__PROTO__SET_DEVICE_TIME_REQUEST__INIT; Logi__Device__Proto__UsbMsg usb_msg = LOGI__DEVICE__PROTO__USB_MSG__INIT; Logi__Device__Proto__Request request_msg = LOGI__DEVICE__PROTO__REQUEST__INIT; /* the device expects an olson_location, not a timezone */ olson_location = fu_common_get_olson_timezone_id(error); if (olson_location == NULL) return NULL; request_msg.payload_case = LOGI__DEVICE__PROTO__REQUEST__PAYLOAD_SET_DEVICE_TIME_REQUEST; request_msg.set_device_time_request = &set_devicetime_msg; set_devicetime_msg.ts = (g_get_real_time() / 1000) + SET_TIME_DELAY_MS; set_devicetime_msg.time_zone = olson_location; proto_manager_set_header(&header_msg); usb_msg.header = &header_msg; usb_msg.message_case = LOGI__DEVICE__PROTO__USB_MSG__MESSAGE_REQUEST; usb_msg.request = &request_msg; fu_byte_array_set_size(buf, logi__device__proto__usb_msg__get_packed_size(&usb_msg), 0x00); logi__device__proto__usb_msg__pack(&usb_msg, (unsigned char *)buf->data); g_free(header_msg.id); g_free(header_msg.timestamp); return g_steal_pointer(&buf); } GByteArray * proto_manager_decode_message(const guint8 *data, guint32 len, FuLogitechBulkcontrollerProtoId *proto_id, GError **error) { g_autoptr(GByteArray) buf_decoded = g_byte_array_new(); Logi__Device__Proto__UsbMsg *usb_msg = logi__device__proto__usb_msg__unpack(NULL, len, (const unsigned char *)data); if (usb_msg == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unable to unpack data"); return NULL; } switch (usb_msg->message_case) { case LOGI__DEVICE__PROTO__USB_MSG__MESSAGE_ACK: *proto_id = kProtoId_Ack; break; case LOGI__DEVICE__PROTO__USB_MSG__MESSAGE_RESPONSE: if (!usb_msg->response) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no USB response"); return NULL; } switch (usb_msg->response->payload_case) { case LOGI__DEVICE__PROTO__RESPONSE__PAYLOAD_GET_DEVICE_INFO_RESPONSE: if (usb_msg->response->get_device_info_response) { const gchar *tmp = usb_msg->response->get_device_info_response->payload; *proto_id = kProtoId_GetDeviceInfoResponse; if (tmp != NULL) g_byte_array_append(buf_decoded, (const guint8 *)tmp, strlen(tmp)); } break; case LOGI__DEVICE__PROTO__RESPONSE__PAYLOAD_TRANSITION_TO_DEVICEMODE_RESPONSE: if (usb_msg->response->transition_to_devicemode_response) { *proto_id = kProtoId_TransitionToDeviceModeResponse; if (!usb_msg->response->transition_to_devicemode_response ->success) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "transition mode request failed. error: %u", (guint)usb_msg->response ->transition_to_devicemode_response->error); return NULL; } } break; default: break; }; break; case LOGI__DEVICE__PROTO__USB_MSG__MESSAGE_EVENT: if (!usb_msg->response) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no USB event"); return NULL; } switch (usb_msg->event->payload_case) { case LOGI__DEVICE__PROTO__EVENT__PAYLOAD_KONG_EVENT: if (usb_msg->event->kong_event) { const gchar *tmp = usb_msg->event->kong_event->mqtt_event; *proto_id = kProtoId_KongEvent; if (tmp != NULL) g_byte_array_append(buf_decoded, (const guint8 *)tmp, strlen(tmp)); } break; case LOGI__DEVICE__PROTO__EVENT__PAYLOAD_HANDSHAKE_EVENT: if (usb_msg->event->handshake_event) { *proto_id = kProtoId_HandshakeEvent; } break; case LOGI__DEVICE__PROTO__EVENT__PAYLOAD_CRASH_DUMP_AVAILABLE_EVENT: *proto_id = kProtoId_CrashDumpAvailableEvent; break; default: break; }; break; default: break; }; logi__device__proto__usb_msg__free_unpacked(usb_msg, NULL); return g_steal_pointer(&buf_decoded); } fwupd-1.9.16/plugins/logitech-bulkcontroller/fu-logitech-bulkcontroller-common.h000066400000000000000000000016011460375044200301620ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "usb_msg.pb-c.h" #define SET_TIME_DELAY_MS 500 /* send future time to keep PC & device time as close as possible */ typedef enum { kProtoId_UnknownId, kProtoId_GetDeviceInfoResponse, kProtoId_TransitionToDeviceModeResponse, kProtoId_Ack, kProtoId_KongEvent, kProtoId_HandshakeEvent, kProtoId_CrashDumpAvailableEvent } FuLogitechBulkcontrollerProtoId; GByteArray * proto_manager_generate_get_device_info_request(void); GByteArray * proto_manager_generate_transition_to_device_mode_request(void); GByteArray * proto_manager_generate_set_device_time_request(GError **error); GByteArray * proto_manager_decode_message(const guint8 *data, guint32 len, FuLogitechBulkcontrollerProtoId *proto_id, GError **error); fwupd-1.9.16/plugins/logitech-bulkcontroller/fu-logitech-bulkcontroller-device.c000066400000000000000000001233521460375044200301340ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 Logitech, Inc. * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-logitech-bulkcontroller-common.h" #include "fu-logitech-bulkcontroller-device.h" #include "fu-logitech-bulkcontroller-struct.h" #define HASH_TIMEOUT 30000 #define UPD_INTERFACE_SUBPROTOCOL_ID 117 #define SYNC_INTERFACE_SUBPROTOCOL_ID 118 #define BULK_TRANSFER_TIMEOUT 2500 #define HASH_VALUE_SIZE 16 #define MAX_RETRIES 5 #define MAX_WAIT_COUNT 150 #define POST_INSTALL_SLEEP_DURATION 80 * 1000 /* ms */ enum { EP_OUT, EP_IN, EP_LAST }; typedef enum { BULK_INTERFACE_UPD, BULK_INTERFACE_SYNC } FuLogitechBulkcontrollerBulkInterface; struct _FuLogitechBulkcontrollerDevice { FuUsbDevice parent_instance; guint sync_ep[EP_LAST]; guint update_ep[EP_LAST]; guint sync_iface; guint update_iface; FuLogitechBulkcontrollerDeviceState status; FuLogitechBulkcontrollerUpdateState update_status; guint update_progress; /* percentage value */ gboolean is_sync_transfer_in_progress; GString *device_info_response_json; gsize transfer_bufsz; }; G_DEFINE_TYPE(FuLogitechBulkcontrollerDevice, fu_logitech_bulkcontroller_device, FU_TYPE_USB_DEVICE) static void fu_logitech_bulkcontroller_device_to_string(FuDevice *device, guint idt, GString *str) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); fu_string_append_kx(str, idt, "BufferSize", self->transfer_bufsz); fu_string_append_kx(str, idt, "SyncIface", self->sync_iface); fu_string_append_kx(str, idt, "UpdateIface", self->update_iface); fu_string_append(str, idt, "State", fu_logitech_bulkcontroller_device_state_to_string(self->status)); fu_string_append(str, idt, "UpdateState", fu_logitech_bulkcontroller_update_state_to_string(self->update_status)); if (self->device_info_response_json->len > 0) { fu_string_append(str, idt, "DeviceInfoResponse", self->device_info_response_json->str); } } static gboolean fu_logitech_bulkcontroller_device_probe(FuDevice *device, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); g_autoptr(GPtrArray) intfs = NULL; intfs = g_usb_device_get_interfaces(fu_usb_device_get_dev(FU_USB_DEVICE(self)), error); if (intfs == NULL) return FALSE; for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index(intfs, i); if (g_usb_interface_get_class(intf) == G_USB_DEVICE_CLASS_VENDOR_SPECIFIC && g_usb_interface_get_protocol(intf) == 0x1) { if (g_usb_interface_get_subclass(intf) == SYNC_INTERFACE_SUBPROTOCOL_ID) { g_autoptr(GPtrArray) endpoints = g_usb_interface_get_endpoints(intf); self->sync_iface = g_usb_interface_get_number(intf); if (endpoints == NULL) continue; for (guint j = 0; j < endpoints->len; j++) { GUsbEndpoint *ep = g_ptr_array_index(endpoints, j); if (j == EP_OUT) self->sync_ep[EP_OUT] = g_usb_endpoint_get_address(ep); else self->sync_ep[EP_IN] = g_usb_endpoint_get_address(ep); } } else if (g_usb_interface_get_subclass(intf) == UPD_INTERFACE_SUBPROTOCOL_ID) { g_autoptr(GPtrArray) endpoints = g_usb_interface_get_endpoints(intf); self->update_iface = g_usb_interface_get_number(intf); if (endpoints == NULL) continue; for (guint j = 0; j < endpoints->len; j++) { GUsbEndpoint *ep = g_ptr_array_index(endpoints, j); if (j == EP_OUT) self->update_ep[EP_OUT] = g_usb_endpoint_get_address(ep); else self->update_ep[EP_IN] = g_usb_endpoint_get_address(ep); } } } } fu_usb_device_add_interface(FU_USB_DEVICE(self), self->update_iface); fu_usb_device_add_interface(FU_USB_DEVICE(self), self->sync_iface); return TRUE; } static gboolean fu_logitech_bulkcontroller_device_send(FuLogitechBulkcontrollerDevice *self, guint8 *buf, gsize bufsz, FuLogitechBulkcontrollerBulkInterface interface_id, GError **error) { gint ep; g_return_val_if_fail(buf != NULL, FALSE); if (interface_id == BULK_INTERFACE_SYNC) { ep = self->sync_ep[EP_OUT]; } else if (interface_id == BULK_INTERFACE_UPD) { ep = self->update_ep[EP_OUT]; } else { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "interface is invalid"); return FALSE; } fu_dump_full(G_LOG_DOMAIN, "request", buf, bufsz, 20, FU_DUMP_FLAGS_SHOW_ASCII); if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), ep, buf, bufsz, NULL, /* transferred */ BULK_TRANSFER_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to send using bulk transfer: "); return FALSE; } return TRUE; } static gboolean fu_logitech_bulkcontroller_device_recv(FuLogitechBulkcontrollerDevice *self, guint8 *buf, gsize bufsz, FuLogitechBulkcontrollerBulkInterface interface_id, guint timeout, GError **error) { gint ep; gsize actual_length = 0; g_return_val_if_fail(buf != NULL, FALSE); if (interface_id == BULK_INTERFACE_SYNC) { ep = self->sync_ep[EP_IN]; } else if (interface_id == BULK_INTERFACE_UPD) { ep = self->update_ep[EP_IN]; } else { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "interface is invalid"); return FALSE; } g_debug("read response"); if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), ep, buf, bufsz, &actual_length, timeout, NULL, error)) { g_prefix_error(error, "failed to receive using bulk transfer: "); return FALSE; } fu_dump_full(G_LOG_DOMAIN, "response", buf, actual_length, 20, FU_DUMP_FLAGS_SHOW_ASCII); return TRUE; } typedef struct { FuLogitechBulkcontrollerCmd cmd; guint32 sequence_id; GByteArray *data; } FuLogitechBulkcontrollerResponse; static FuLogitechBulkcontrollerResponse * fu_logitech_bulkcontroller_response_new(void) { FuLogitechBulkcontrollerResponse *response = g_new0(FuLogitechBulkcontrollerResponse, 1); response->data = g_byte_array_new(); return response; } static void fu_logitech_bulkcontroller_response_free(FuLogitechBulkcontrollerResponse *response) { if (response->data != NULL) g_byte_array_unref(response->data); g_free(response); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuLogitechBulkcontrollerResponse, fu_logitech_bulkcontroller_response_free) static gboolean fu_logitech_bulkcontroller_device_sync_send_cmd(FuLogitechBulkcontrollerDevice *self, FuLogitechBulkcontrollerCmd cmd, GByteArray *buf, guint32 *sequence_id, GError **error) { g_autoptr(GByteArray) st_req = fu_struct_logitech_bulkcontroller_send_sync_req_new(); g_autofree gchar *str = NULL; guint32 sequence_id_tmp = g_random_int_range(0, G_MAXINT32); /* send */ fu_struct_logitech_bulkcontroller_send_sync_req_set_cmd(st_req, cmd); fu_struct_logitech_bulkcontroller_send_sync_req_set_sequence_id(st_req, sequence_id_tmp); if (buf != NULL) { fu_struct_logitech_bulkcontroller_send_sync_req_set_payload_length(st_req, buf->len); g_byte_array_append(st_req, buf->data, buf->len); } str = fu_struct_logitech_bulkcontroller_send_sync_req_to_string(st_req); g_debug("sending: %s", str); if (!fu_logitech_bulkcontroller_device_send(self, st_req->data, st_req->len, BULK_INTERFACE_SYNC, error)) return FALSE; /* success */ if (sequence_id != NULL) *sequence_id = sequence_id_tmp; return TRUE; } static gboolean fu_logitech_bulkcontroller_device_sync_send_ack(FuLogitechBulkcontrollerDevice *self, FuLogitechBulkcontrollerCmd cmd, guint32 *sequence_id, GError **error) { g_autoptr(GByteArray) buf_ack = g_byte_array_new(); fu_byte_array_append_uint32(buf_ack, cmd, G_LITTLE_ENDIAN); if (!fu_logitech_bulkcontroller_device_sync_send_cmd(self, FU_LOGITECH_BULKCONTROLLER_CMD_ACK, buf_ack, sequence_id, error)) { g_prefix_error(error, "failed to send ack for %s: ", fu_logitech_bulkcontroller_cmd_to_string(cmd)); return FALSE; } return TRUE; } static FuLogitechBulkcontrollerResponse * fu_logitech_bulkcontroller_device_sync_wait_any(FuLogitechBulkcontrollerDevice *self, GError **error) { g_autofree guint8 *buf = g_malloc0(self->transfer_bufsz); g_autoptr(GByteArray) st = NULL; g_autoptr(FuLogitechBulkcontrollerResponse) response = fu_logitech_bulkcontroller_response_new(); if (!fu_logitech_bulkcontroller_device_recv(self, buf, self->transfer_bufsz, BULK_INTERFACE_SYNC, BULK_TRANSFER_TIMEOUT, error)) return NULL; st = fu_struct_logitech_bulkcontroller_send_sync_res_parse(buf, self->transfer_bufsz, 0x0, error); if (st == NULL) return NULL; response->cmd = fu_struct_logitech_bulkcontroller_send_sync_res_get_cmd(st); response->sequence_id = fu_struct_logitech_bulkcontroller_send_sync_res_get_sequence_id(st); g_byte_array_append(response->data, buf + st->len, fu_struct_logitech_bulkcontroller_send_sync_res_get_payload_length(st)); if (response->data == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to receive packet"); return NULL; } return g_steal_pointer(&response); } static GByteArray * fu_logitech_bulkcontroller_device_sync_wait_cmd(FuLogitechBulkcontrollerDevice *self, FuLogitechBulkcontrollerCmd cmd, guint32 sequence_id, GError **error) { g_autoptr(FuLogitechBulkcontrollerResponse) response = NULL; response = fu_logitech_bulkcontroller_device_sync_wait_any(self, error); if (response == NULL) return NULL; if (response->cmd != cmd) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "command invalid, expected %s and got %s", fu_logitech_bulkcontroller_cmd_to_string(cmd), fu_logitech_bulkcontroller_cmd_to_string(response->cmd)); return NULL; } /* verify the sequence ID */ if (response->sequence_id != sequence_id) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "sequence ID invalid, expected 0x%04x and got 0x%04x", sequence_id, response->sequence_id); return NULL; } /* success */ return g_steal_pointer(&response->data); } static gboolean fu_logitech_bulkcontroller_device_sync_wait_cmd_retry_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); FuLogitechBulkcontrollerResponse *helper = (FuLogitechBulkcontrollerResponse *)user_data; helper->data = fu_logitech_bulkcontroller_device_sync_wait_cmd(self, helper->cmd, helper->sequence_id, error); if (helper->data == NULL) return FALSE; /* success */ return TRUE; } static GByteArray * fu_logitech_bulkcontroller_device_sync_wait_cmd_retry(FuLogitechBulkcontrollerDevice *self, FuLogitechBulkcontrollerCmd cmd, guint32 sequence_id, GError **error) { FuLogitechBulkcontrollerResponse helper = {.cmd = cmd, .sequence_id = sequence_id}; if (!fu_device_retry(FU_DEVICE(self), fu_logitech_bulkcontroller_device_sync_wait_cmd_retry_cb, MAX_RETRIES, &helper, error)) return NULL; return helper.data; } static gboolean fu_logitech_bulkcontroller_device_sync_check_ack_cmd(GByteArray *buf, FuLogitechBulkcontrollerCmd cmd, GError **error) { gchar ack_payload[6] = {0x0}; guint64 ack_cmd = 0; /* this is weird; base 10 number as ASCII as the ack payload... */ if (!fu_memcpy_safe((guint8 *)ack_payload, sizeof(ack_payload), 0x0, buf->data, buf->len, 0x0, sizeof(ack_payload) - 1, error)) { g_prefix_error(error, "failed to copy ack payload: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "ack_payload", (guint8 *)ack_payload, sizeof(ack_payload)); if (!fu_strtoull((const gchar *)ack_payload, &ack_cmd, 0, G_MAXUINT32, error)) { g_prefix_error(error, "failed to parse ack payload cmd: "); return FALSE; } g_debug("ack_cmd: %s [0x%x]", fu_logitech_bulkcontroller_cmd_to_string(ack_cmd), (guint)ack_cmd); if (ack_cmd != cmd) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "command invalid, expected %s and got %s", fu_logitech_bulkcontroller_cmd_to_string(cmd), fu_logitech_bulkcontroller_cmd_to_string(ack_cmd)); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_sync_wait_ack_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); FuLogitechBulkcontrollerResponse *helper = (FuLogitechBulkcontrollerResponse *)user_data; g_autoptr(GByteArray) buf = NULL; buf = fu_logitech_bulkcontroller_device_sync_wait_cmd(self, FU_LOGITECH_BULKCONTROLLER_CMD_ACK, helper->sequence_id, error); if (buf == NULL) return FALSE; if (!fu_logitech_bulkcontroller_device_sync_check_ack_cmd(buf, helper->cmd, error)) return FALSE; /* success */ return TRUE; } /* send command and wait for ACK */ static gboolean fu_logitech_bulkcontroller_device_sync_wait_ack(FuLogitechBulkcontrollerDevice *self, FuLogitechBulkcontrollerCmd cmd, guint32 sequence_id, GError **error) { FuLogitechBulkcontrollerResponse helper = {.cmd = cmd, .sequence_id = sequence_id}; return fu_device_retry_full(FU_DEVICE(self), fu_logitech_bulkcontroller_device_sync_wait_ack_cb, 10, 200, &helper, error); } static gboolean fu_logitech_bulkcontroller_device_sync_check_ack(FuLogitechBulkcontrollerResponse *response, FuLogitechBulkcontrollerCmd cmd, guint32 sequence_id, GError **error) { /* verify the sequence ID */ if (response->sequence_id != sequence_id) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "sequence ID invalid, expected 0x%04x and got 0x%04x", sequence_id, response->sequence_id); return FALSE; } return fu_logitech_bulkcontroller_device_sync_check_ack_cmd(response->data, cmd, error); } static GByteArray * fu_logitech_bulkcontroller_device_sync_write(FuLogitechBulkcontrollerDevice *self, GByteArray *req, GError **error) { guint32 sequence_id = 0; g_autoptr(GByteArray) res_ack = NULL; g_autoptr(GByteArray) res_read = NULL; g_autoptr(GByteArray) buf = NULL; /* send host->device buffer-write */ if (!fu_logitech_bulkcontroller_device_sync_send_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_WRITE, req, &sequence_id, error)) { g_prefix_error(error, "failed to send request: "); return NULL; } /* wait device->host ack */ if (!fu_logitech_bulkcontroller_device_sync_wait_ack( self, FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_WRITE, sequence_id, error)) { g_prefix_error(error, "failed to wait for ack: "); return NULL; } /* send host->device buffer-uninit */ if (!fu_logitech_bulkcontroller_device_sync_send_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT_BUFFER, NULL, &sequence_id, error)) { g_prefix_error(error, "failed to uninit buffer: "); return NULL; } /* wait device->host buffer-read|ack */ do { g_autoptr(FuLogitechBulkcontrollerResponse) response_tmp = NULL; response_tmp = fu_logitech_bulkcontroller_device_sync_wait_any(self, error); if (response_tmp == NULL) { g_prefix_error(error, "failed to wait for any: "); return NULL; } if (response_tmp->cmd == FU_LOGITECH_BULKCONTROLLER_CMD_ACK) { if (res_ack != NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "already received ack"); return NULL; } if (!fu_logitech_bulkcontroller_device_sync_check_ack( response_tmp, FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT_BUFFER, sequence_id, error)) { g_prefix_error(error, "failed to check uninit buffer: "); return NULL; } res_ack = g_steal_pointer(&response_tmp->data); } else if (response_tmp->cmd == FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_READ) { if (res_read != NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "already received read-buffer"); return NULL; } res_read = g_steal_pointer(&response_tmp->data); } } while (res_ack == NULL || res_read == NULL); /* send host->device ack */ if (!fu_logitech_bulkcontroller_device_sync_send_ack( self, FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_READ, &sequence_id, error)) { g_prefix_error(error, "failed to ack read buffer: "); return NULL; } /* wait device->host uninit */ buf = fu_logitech_bulkcontroller_device_sync_wait_cmd_retry( self, FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT_BUFFER, 0x0, /* why? */ error); if (buf == NULL) { g_prefix_error(error, "failed to wait for uninit buffer: "); return NULL; } /* send host->device ack */ if (!fu_logitech_bulkcontroller_device_sync_send_ack( self, FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT_BUFFER, NULL, error)) { g_prefix_error(error, "failed to ack uninit buffer: "); return NULL; } /* success */ return g_steal_pointer(&res_read); } static gboolean fu_logitech_bulkcontroller_device_upd_send_cmd(FuLogitechBulkcontrollerDevice *self, guint32 cmd, GBytes *buf, guint timeout, GError **error) { g_autofree guint8 *buf_tmp = g_malloc0(self->transfer_bufsz); GByteArray buf_ack = {.data = buf_tmp, .len = self->transfer_bufsz}; g_autoptr(GByteArray) buf_pkt = fu_struct_logitech_bulkcontroller_update_req_new(); fu_struct_logitech_bulkcontroller_update_req_set_cmd(buf_pkt, cmd); if (buf != NULL) { fu_struct_logitech_bulkcontroller_update_req_set_payload_length( buf_pkt, g_bytes_get_size(buf)); fu_byte_array_append_bytes(buf_pkt, buf); } if (!fu_logitech_bulkcontroller_device_send(self, buf_pkt->data, buf_pkt->len, BULK_INTERFACE_UPD, error)) return FALSE; /* receiving INIT ACK */ if (!fu_logitech_bulkcontroller_device_recv(self, buf_tmp, self->transfer_bufsz, BULK_INTERFACE_UPD, timeout, error)) return FALSE; if (fu_struct_logitech_bulkcontroller_update_res_get_cmd(&buf_ack) != FU_LOGITECH_BULKCONTROLLER_CMD_ACK) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "not CMD_ACK, got %s", fu_logitech_bulkcontroller_cmd_to_string( fu_struct_logitech_bulkcontroller_update_res_get_cmd(&buf_ack))); return FALSE; } if (fu_struct_logitech_bulkcontroller_update_res_get_cmd_req(&buf_ack) != cmd) { g_set_error( error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid upd message received, expected %s, got %s", fu_logitech_bulkcontroller_cmd_to_string(cmd), fu_logitech_bulkcontroller_cmd_to_string( fu_struct_logitech_bulkcontroller_update_res_get_cmd_req(&buf_ack))); return FALSE; } return TRUE; } static gchar * fu_logitech_bulkcontroller_device_compute_hash(GBytes *data) { guint8 md5buf[HASH_VALUE_SIZE] = {0}; gsize data_len = sizeof(md5buf); GChecksum *checksum = g_checksum_new(G_CHECKSUM_MD5); g_checksum_update(checksum, g_bytes_get_data(data, NULL), g_bytes_get_size(data)); g_checksum_get_digest(checksum, (guint8 *)&md5buf, &data_len); return g_base64_encode(md5buf, sizeof(md5buf)); } static FwupdStatus fu_logitech_bulkcontroller_device_update_state_to_status( FuLogitechBulkcontrollerUpdateState update_state) { if (update_state == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_DOWNLOADING) return FWUPD_STATUS_DEVICE_WRITE; if (update_state == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_STARTING) return FWUPD_STATUS_DEVICE_VERIFY; if (update_state == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_UPDATING) return FWUPD_STATUS_DEVICE_WRITE; if (update_state == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_CURRENT) return FWUPD_STATUS_IDLE; return FWUPD_STATUS_UNKNOWN; } static gboolean fu_logitech_bulkcontroller_device_json_parser(FuLogitechBulkcontrollerDevice *self, GByteArray *decoded_pkt, GError **error) { JsonArray *json_devices; JsonNode *json_root; JsonObject *json_device; JsonObject *json_object; JsonObject *json_payload; g_autoptr(JsonParser) json_parser = json_parser_new(); /* parse JSON reply */ if (!json_parser_load_from_data(json_parser, (const gchar *)decoded_pkt->data, decoded_pkt->len, error)) { g_prefix_error(error, "failed to parse json data: "); return FALSE; } json_root = json_parser_get_root(json_parser); if (json_root == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "did not get JSON root"); return FALSE; } json_object = json_node_get_object(json_root); json_payload = json_object_get_object_member(json_object, "payload"); if (json_payload == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "did not get JSON payload"); return FALSE; } json_devices = json_object_get_array_member(json_payload, "devices"); if (json_devices == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "did not get JSON devices"); return FALSE; } json_device = json_array_get_object_element(json_devices, 0); if (json_device == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "did not get JSON device"); return FALSE; } if (json_object_has_member(json_device, "name")) fu_device_set_name(FU_DEVICE(self), json_object_get_string_member(json_device, "name")); if (json_object_has_member(json_device, "sw")) fu_device_set_version(FU_DEVICE(self), json_object_get_string_member(json_device, "sw")); if (json_object_has_member(json_device, "type")) { fu_device_add_instance_id_full(FU_DEVICE(self), json_object_get_string_member(json_device, "type"), FU_DEVICE_INSTANCE_FLAG_QUIRKS); } if (json_object_has_member(json_device, "status")) self->status = json_object_get_int_member(json_device, "status"); if (json_object_has_member(json_device, "updateStatus")) self->update_status = json_object_get_int_member(json_device, "updateStatus"); /* updateProgress only available while firmware upgrade is going on */ if (json_object_has_member(json_device, "updateProgress")) self->update_progress = json_object_get_int_member(json_device, "updateProgress"); return TRUE; } static gboolean fu_logitech_bulkcontroller_device_parse_info(FuLogitechBulkcontrollerDevice *self, GByteArray *buf, GError **error) { FuLogitechBulkcontrollerProtoId proto_id = kProtoId_UnknownId; g_autofree gchar *bufstr = NULL; g_autoptr(GByteArray) decoded_pkt = NULL; decoded_pkt = proto_manager_decode_message(buf->data, buf->len, &proto_id, error); if (decoded_pkt == NULL) { g_prefix_error(error, "failed to unpack packet for device info request: "); return FALSE; } bufstr = fu_strsafe((const gchar *)decoded_pkt->data, decoded_pkt->len); g_debug("received device response: id: %u, length %u, data: %s", proto_id, buf->len, bufstr); if (proto_id != kProtoId_GetDeviceInfoResponse && proto_id != kProtoId_KongEvent) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "incorrect response for device info request"); return FALSE; } if (!fu_logitech_bulkcontroller_device_json_parser(self, decoded_pkt, error)) return FALSE; /* success */ g_string_assign(self->device_info_response_json, bufstr); return TRUE; } static gboolean fu_logitech_bulkcontroller_device_ensure_info_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); g_autoptr(GByteArray) buf = NULL; gboolean send_req = *(gboolean *)user_data; /* sending GetDeviceInfoRequest. Device reports quite a few matrix, including status, * progress etc * Two ways to get data from device: * 1. Listen for the data broadcasted by device, while firmware upgrade is going on * 2. Make explicit request to the device. Used when data is needed before/after firmware * upgrade */ if (send_req) { g_autoptr(GByteArray) device_request = proto_manager_generate_get_device_info_request(); buf = fu_logitech_bulkcontroller_device_sync_write(self, device_request, error); if (buf == NULL) return FALSE; } else { /* poll the out interface */ buf = fu_logitech_bulkcontroller_device_sync_wait_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_READ, 0x0, /* sequence_id */ error); if (buf == NULL) return FALSE; } return fu_logitech_bulkcontroller_device_parse_info(self, buf, error); } static gboolean fu_logitech_bulkcontroller_device_ensure_info(FuLogitechBulkcontrollerDevice *self, gboolean send_req, GError **error) { return fu_device_retry(FU_DEVICE(self), fu_logitech_bulkcontroller_device_ensure_info_cb, MAX_RETRIES, &send_req, error); } static gboolean fu_logitech_bulkcontroller_device_upd_send_init_cmd_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); return fu_logitech_bulkcontroller_device_upd_send_cmd(self, FU_LOGITECH_BULKCONTROLLER_CMD_INIT, NULL, BULK_TRANSFER_TIMEOUT, error); } static gboolean fu_logitech_bulkcontroller_device_write_fw(FuLogitechBulkcontrollerDevice *self, GBytes *fw, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes( fw, 0x0, self->transfer_bufsz - FU_STRUCT_LOGITECH_BULKCONTROLLER_UPDATE_REQ_SIZE); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GBytes) chk_blob = fu_chunk_get_bytes(chk); if (!fu_logitech_bulkcontroller_device_upd_send_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_DATA_TRANSFER, chk_blob, BULK_TRANSFER_TIMEOUT, error)) { g_prefix_error(error, "failed to send data packet 0x%x: ", i); return FALSE; } fu_progress_step_done(progress); } return TRUE; } static gboolean fu_logitech_bulkcontroller_device_verify_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); FuProgress *progress = FU_PROGRESS(user_data); g_autoptr(GError) error_local = NULL; g_autoptr(GByteArray) buf = NULL; /* poll the out interface */ buf = fu_logitech_bulkcontroller_device_sync_wait_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_BUFFER_READ, 0x0, /* sequence_id */ &error_local); if (buf == NULL) { g_autoptr(GByteArray) device_request = NULL; g_debug("manually requesting as no pending request: %s", error_local->message); device_request = proto_manager_generate_get_device_info_request(); buf = fu_logitech_bulkcontroller_device_sync_write(self, device_request, error); if (buf == NULL) return FALSE; } if (!fu_logitech_bulkcontroller_device_parse_info(self, buf, error)) return FALSE; g_debug("firmware update status: %s, progress: %u", fu_logitech_bulkcontroller_update_state_to_string(self->update_status), self->update_progress); fu_progress_set_status( progress, fu_logitech_bulkcontroller_device_update_state_to_status(self->update_status)); /* existing device image version is same as newly pushed image? */ if (self->update_status == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_ERROR || self->update_status == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_CURRENT) return TRUE; /* only update the child if the percentage is bigger -- which means the progressbar * may stall, but will never go backwards */ if (self->update_progress > fu_progress_get_percentage(progress)) fu_progress_set_percentage(progress, self->update_progress); /* keep waiting */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "waiting for verify to finish"); return FALSE; } static gboolean fu_logitech_bulkcontroller_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); g_autofree gchar *base64hash = NULL; g_autoptr(GByteArray) end_pkt = g_byte_array_new(); g_autoptr(GByteArray) start_pkt = g_byte_array_new(); g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) end_pkt_blob = NULL; g_autoptr(GBytes) start_pkt_blob = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "init"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 55, "device-write-blocks"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "end-transfer"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "uninit"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 40, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* sending INIT. Retry if device is not in IDLE state to receive the file */ if (!fu_device_retry(device, fu_logitech_bulkcontroller_device_upd_send_init_cmd_cb, MAX_RETRIES, NULL, error)) { g_prefix_error(error, "failed to write init transfer packet: please reboot the device: "); return FALSE; } /* transfer sent */ fu_byte_array_append_uint64(start_pkt, g_bytes_get_size(fw), G_LITTLE_ENDIAN); start_pkt_blob = g_bytes_new(start_pkt->data, start_pkt->len); if (!fu_logitech_bulkcontroller_device_upd_send_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_START_TRANSFER, start_pkt_blob, BULK_TRANSFER_TIMEOUT, error)) { g_prefix_error(error, "failed to write start transfer packet: "); return FALSE; } fu_progress_step_done(progress); /* push each block to device */ if (!fu_logitech_bulkcontroller_device_write_fw(self, fw, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write firmware: "); return FALSE; } fu_progress_step_done(progress); /* sending end transfer -- extend the bulk transfer timeout value, as android device takes * some time to calculate the hash and respond */ base64hash = fu_logitech_bulkcontroller_device_compute_hash(fw); fu_byte_array_append_uint32(end_pkt, 1, G_LITTLE_ENDIAN); /* update */ fu_byte_array_append_uint32(end_pkt, 0, G_LITTLE_ENDIAN); /* force */ fu_byte_array_append_uint32(end_pkt, FU_LOGITECH_BULKCONTROLLER_CHECKSUM_TYPE_MD5, G_LITTLE_ENDIAN); g_byte_array_append(end_pkt, (const guint8 *)base64hash, strlen(base64hash)); end_pkt_blob = g_bytes_new(end_pkt->data, end_pkt->len); if (!fu_logitech_bulkcontroller_device_upd_send_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_END_TRANSFER, end_pkt_blob, HASH_TIMEOUT, error)) { g_prefix_error(error, "failed to write end transfer packet: "); return FALSE; } fu_progress_step_done(progress); /* send uninit */ if (!fu_logitech_bulkcontroller_device_upd_send_cmd(self, FU_LOGITECH_BULKCONTROLLER_CMD_UNINIT, NULL, BULK_TRANSFER_TIMEOUT, error)) { g_prefix_error(error, "failed to write finish transfer packet: "); return FALSE; } fu_progress_step_done(progress); /* * image file pushed. Device validates and uploads new image on inactive partition. * Restart sync cb, to get the update progress * Normally status changes as follows: * While image being pushed: kUpdateStateCurrent->kUpdateStateDownloading (~5minutes) * After image push is complete: kUpdateStateDownloading->kUpdateStateReady * Validating image: kUpdateStateReady->kUpdateStateStarting * Uploading image: kUpdateStateStarting->kUpdateStateUpdating * Upload finished: kUpdateStateUpdating->kUpdateStateCurrent (~5minutes) * After upload is finished, device reboots itself */ if (!fu_device_retry_full(device, fu_logitech_bulkcontroller_device_verify_cb, 500, /* over 10 minutes */ 2500, /* ms */ fu_progress_get_child(progress), error)) return FALSE; if (self->update_status == FU_LOGITECH_BULKCONTROLLER_UPDATE_STATE_ERROR) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware upgrade failed"); return FALSE; } fu_progress_step_done(progress); /* success! */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_logitech_fu_logitech_bulkcontroller_device_set_time_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); FuLogitechBulkcontrollerProtoId proto_id = kProtoId_UnknownId; g_autofree gchar *bufstr = NULL; g_autoptr(GByteArray) decoded_pkt = NULL; g_autoptr(GByteArray) device_request = NULL; g_autoptr(GByteArray) buf = NULL; /* send SetDeviceTimeRequest to sync device clock with host */ device_request = proto_manager_generate_set_device_time_request(error); if (device_request == NULL) return FALSE; buf = fu_logitech_bulkcontroller_device_sync_write(self, device_request, error); if (buf == NULL) return FALSE; decoded_pkt = proto_manager_decode_message(buf->data, buf->len, &proto_id, error); if (decoded_pkt == NULL) { g_prefix_error(error, "failed to unpack packet: "); return FALSE; } bufstr = fu_strsafe((const gchar *)decoded_pkt->data, decoded_pkt->len); g_debug("received device response while processing: id: %u, length %u, data: %s", proto_id, buf->len, bufstr); if (proto_id != kProtoId_Ack) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "incorrect response"); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_set_time(FuLogitechBulkcontrollerDevice *self, GError **error) { return fu_device_retry(FU_DEVICE(self), fu_logitech_fu_logitech_bulkcontroller_device_set_time_cb, MAX_RETRIES, NULL, error); } static gboolean fu_logitech_bulkcontroller_device_transition_to_device_mode_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); FuLogitechBulkcontrollerProtoId proto_id = kProtoId_UnknownId; g_autoptr(GByteArray) req = NULL; g_autoptr(GByteArray) res = NULL; g_autoptr(GByteArray) decoded_pkt = NULL; req = proto_manager_generate_transition_to_device_mode_request(); res = fu_logitech_bulkcontroller_device_sync_write(self, req, error); if (res == NULL) return FALSE; decoded_pkt = proto_manager_decode_message(res->data, res->len, &proto_id, error); if (decoded_pkt == NULL) { g_prefix_error(error, "failed to unpack packet: "); return FALSE; } g_debug("received transition mode response: id: %u, length %u", proto_id, res->len); if (proto_id != kProtoId_TransitionToDeviceModeResponse) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "incorrect response"); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_transition_to_device_mode(FuLogitechBulkcontrollerDevice *self, GError **error) { return fu_device_retry(FU_DEVICE(self), fu_logitech_bulkcontroller_device_transition_to_device_mode_cb, MAX_RETRIES, NULL, error); } static gboolean fu_logitech_bulkcontroller_device_clear_queue_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); g_autofree guint8 *buf = g_malloc0(self->transfer_bufsz); g_autoptr(GError) error_local = NULL; if (!fu_logitech_bulkcontroller_device_recv(self, buf, self->transfer_bufsz, BULK_INTERFACE_SYNC, 250, /* ms */ &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT)) { g_debug("timed out successfully"); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* failed */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "got valid data, so keep going"); return FALSE; } static gboolean fu_logitech_bulkcontroller_device_clear_queue(FuLogitechBulkcontrollerDevice *self, GError **error) { g_debug("clearing any bulk data"); return fu_device_retry(FU_DEVICE(self), fu_logitech_bulkcontroller_device_clear_queue_cb, 3, NULL, error); } static gboolean fu_logitech_bulkcontroller_device_check_buffer_size(FuLogitechBulkcontrollerDevice *self, GError **error) { g_autoptr(GByteArray) buf = NULL; g_autoptr(GError) error_local = NULL; if (!fu_logitech_bulkcontroller_device_sync_send_cmd( self, FU_LOGITECH_BULKCONTROLLER_CMD_CHECK_BUFFERSIZE, NULL, /* data */ NULL, /* sequence_id */ error)) { g_prefix_error(error, "failed to send request: "); return FALSE; } buf = fu_logitech_bulkcontroller_device_sync_wait_cmd_retry( self, FU_LOGITECH_BULKCONTROLLER_CMD_CHECK_BUFFERSIZE, 0x0, /* always zero */ &error_local); if (buf != NULL) { self->transfer_bufsz = 16 * 1024; } else { g_debug("sticking to 8k buffersize: %s", error_local->message); } /* success */ return TRUE; } static gboolean fu_logitech_bulkcontroller_device_setup(FuDevice *device, GError **error) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(device); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_logitech_bulkcontroller_device_parent_class) ->setup(device, error)) { g_prefix_error(error, "failed to FuUsbDevice->setup: "); return FALSE; } /* empty the queue */ if (!fu_logitech_bulkcontroller_device_clear_queue(self, error)) { g_prefix_error(error, "failed to clear queue: "); return FALSE; } /* check if the device supports a 16kb transfer buffer */ if (fu_device_has_private_flag(device, FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_CHECK_BUFFER_SIZE)) { if (!fu_logitech_bulkcontroller_device_check_buffer_size(self, error)) { g_prefix_error(error, "failed to check buffer size: "); return FALSE; } } /* device supports modes of Device (supported), Appliance and BYOD (both unsupported) */ if (!fu_logitech_bulkcontroller_device_transition_to_device_mode(self, error)) { g_prefix_error(error, "failed to transition to device_mode: "); return FALSE; } /* the hardware is unable to handle requests -- firmware issue */ if (fu_device_has_private_flag(device, FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_POST_INSTALL)) { fu_device_sleep(device, POST_INSTALL_SLEEP_DURATION); fu_device_remove_private_flag(device, FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_POST_INSTALL); } /* set device time */ if (!fu_logitech_bulkcontroller_device_set_time(self, error)) { g_prefix_error(error, "failed to set time: "); return FALSE; } /* load current device data */ if (!fu_logitech_bulkcontroller_device_ensure_info(self, TRUE, error)) { g_prefix_error(error, "failed to ensure info: "); return FALSE; } /* success */ return TRUE; } static void fu_logitech_bulkcontroller_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, "reload"); } static void fu_logitech_bulkcontroller_device_init(FuLogitechBulkcontrollerDevice *self) { self->transfer_bufsz = 8 * 1024; self->device_info_response_json = g_string_new(NULL); fu_device_add_protocol(FU_DEVICE(self), "com.logitech.vc.proto"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_usb_device_set_claim_retry_count(FU_USB_DEVICE(self), 100); fu_usb_device_set_open_retry_count(FU_USB_DEVICE(self), 5); fu_device_retry_set_delay(FU_DEVICE(self), 1000); fu_device_set_remove_delay(FU_DEVICE(self), 10 * 60 * 1000); /* >1 min to finish init */ fu_device_register_private_flag(FU_DEVICE(self), FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_CHECK_BUFFER_SIZE, "check-buffer-size"); fu_device_register_private_flag(FU_DEVICE(self), FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_POST_INSTALL, "post-install"); /* these are unrecoverable */ fu_device_retry_add_recovery(FU_DEVICE(self), G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE, NULL); fu_device_retry_add_recovery(FU_DEVICE(self), G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_PERMISSION_DENIED, NULL); } static void fu_logitech_bulkcontroller_device_finalize(GObject *object) { FuLogitechBulkcontrollerDevice *self = FU_LOGITECH_BULKCONTROLLER_DEVICE(object); g_string_free(self->device_info_response_json, TRUE); G_OBJECT_CLASS(fu_logitech_bulkcontroller_device_parent_class)->finalize(object); } static void fu_logitech_bulkcontroller_device_class_init(FuLogitechBulkcontrollerDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_logitech_bulkcontroller_device_finalize; klass_device->to_string = fu_logitech_bulkcontroller_device_to_string; klass_device->write_firmware = fu_logitech_bulkcontroller_device_write_firmware; klass_device->probe = fu_logitech_bulkcontroller_device_probe; klass_device->setup = fu_logitech_bulkcontroller_device_setup; klass_device->set_progress = fu_logitech_bulkcontroller_device_set_progress; } fwupd-1.9.16/plugins/logitech-bulkcontroller/fu-logitech-bulkcontroller-device.h000066400000000000000000000010501460375044200301270ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_LOGITECH_BULKCONTROLLER_DEVICE (fu_logitech_bulkcontroller_device_get_type()) G_DECLARE_FINAL_TYPE(FuLogitechBulkcontrollerDevice, fu_logitech_bulkcontroller_device, FU, LOGITECH_BULKCONTROLLER_DEVICE, FuUsbDevice) #define FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_CHECK_BUFFER_SIZE (1 << 0) #define FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_POST_INSTALL (1 << 1) fwupd-1.9.16/plugins/logitech-bulkcontroller/fu-logitech-bulkcontroller-plugin.c000066400000000000000000000044571460375044200301770ustar00rootroot00000000000000/* * Copyright (c) 1999-2021 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-logitech-bulkcontroller-device.h" #include "fu-logitech-bulkcontroller-plugin.h" struct _FuLogitechBulkcontrollerPlugin { FuPlugin parent_instance; gboolean post_install; }; G_DEFINE_TYPE(FuLogitechBulkcontrollerPlugin, fu_logitech_bulkcontroller_plugin, FU_TYPE_PLUGIN) static void fu_logitech_bulkcontroller_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuLogitechBulkcontrollerPlugin *self = FU_LOGITECH_BULKCONTROLLER_PLUGIN(plugin); fu_string_append_kb(str, idt, "PostInstall", self->post_install); } static void fu_logitech_bulkcontroller_plugin_init(FuLogitechBulkcontrollerPlugin *self) { } static gboolean fu_logitech_bulkcontroller_plugin_write_firmware(FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuLogitechBulkcontrollerPlugin *self = FU_LOGITECH_BULKCONTROLLER_PLUGIN(plugin); g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; if (!fu_device_write_firmware(device, blob_fw, progress, flags, error)) return FALSE; self->post_install = TRUE; return TRUE; } static gboolean fu_logitech_bulkcontroller_plugin_device_created(FuPlugin *plugin, FuDevice *device, GError **error) { FuLogitechBulkcontrollerPlugin *self = FU_LOGITECH_BULKCONTROLLER_PLUGIN(plugin); if (self->post_install) { fu_device_add_private_flag(device, FU_LOGITECH_BULKCONTROLLER_DEVICE_FLAG_POST_INSTALL); self->post_install = FALSE; } return TRUE; } static void fu_logitech_bulkcontroller_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_LOGITECH_BULKCONTROLLER_DEVICE); } static void fu_logitech_bulkcontroller_plugin_class_init(FuLogitechBulkcontrollerPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_logitech_bulkcontroller_plugin_constructed; plugin_class->write_firmware = fu_logitech_bulkcontroller_plugin_write_firmware; plugin_class->device_created = fu_logitech_bulkcontroller_plugin_device_created; plugin_class->to_string = fu_logitech_bulkcontroller_plugin_to_string; } fwupd-1.9.16/plugins/logitech-bulkcontroller/fu-logitech-bulkcontroller-plugin.h000066400000000000000000000004661460375044200302000ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuLogitechBulkcontrollerPlugin, fu_logitech_bulkcontroller_plugin, FU, LOGITECH_BULKCONTROLLER_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/logitech-bulkcontroller/fu-logitech-bulkcontroller.rs000066400000000000000000000027271460375044200271030ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum LogitechBulkcontrollerDeviceState { Unknown = -1, Offline, Online, Idle, InUse, AudioOnly, Enumerating, } #[derive(ToString)] enum LogitechBulkcontrollerUpdateState { Unknown = -1, Current, Available, Starting = 3, Downloading, Ready, Updating, Scheduled, Error, } #[repr(u32le)] #[derive(ToString)] enum LogitechBulkcontrollerCmd { CheckBuffersize = 0xCC00, Init = 0xCC01, StartTransfer = 0xCC02, DataTransfer = 0xCC03, EndTransfer = 0xCC04, Uninit = 0xCC05, BufferRead = 0xCC06, BufferWrite = 0xCC07, UninitBuffer = 0xCC08, Ack = 0xFF01, Timeout = 0xFF02, Nack = 0xFF03, } #[derive(New, ToString, Getters)] struct LogitechBulkcontrollerSendSyncReq { cmd: LogitechBulkcontrollerCmd, payload_length: u32le, sequence_id: u32le, } #[derive(Parse)] struct LogitechBulkcontrollerSendSyncRes { cmd: LogitechBulkcontrollerCmd, payload_length: u32le, sequence_id: u32le, } #[derive(New)] struct LogitechBulkcontrollerUpdateReq { cmd: LogitechBulkcontrollerCmd, payload_length: u32le, } #[derive(Getters)] struct LogitechBulkcontrollerUpdateRes { cmd: LogitechBulkcontrollerCmd, _payload_length: u32le, cmd_req: LogitechBulkcontrollerCmd, } enum LogitechBulkcontrollerChecksumType { Sha256, Sha512, Md5, } fwupd-1.9.16/plugins/logitech-bulkcontroller/logitech-bulkcontroller.quirk000066400000000000000000000004431460375044200271730ustar00rootroot00000000000000# TODO: revisit InstallDuration [USB\VID_046D&PID_089B] Plugin = logitech_bulkcontroller InstallDuration = 1500 [USB\VID_046D&PID_08D3] Plugin = logitech_bulkcontroller InstallDuration = 1500 Flags = is-mini [USB\VID_046D&PID_087C] Plugin = logitech_bulkcontroller InstallDuration = 1500 fwupd-1.9.16/plugins/logitech-bulkcontroller/meson.build000066400000000000000000000012211460375044200234160ustar00rootroot00000000000000if gusb.found() and protobufc.found() and protoc.found() cargs = ['-DG_LOG_DOMAIN="FuPluginLogitechBulkController"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('logitech-bulkcontroller.quirk') subdir('proto') plugin_builtins += static_library('fu_plugin_logitech_bulkcontroller', rustgen.process('fu-logitech-bulkcontroller.rs'), sources: [ generated, 'fu-logitech-bulkcontroller-common.c', 'fu-logitech-bulkcontroller-device.c', 'fu-logitech-bulkcontroller-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/000077500000000000000000000000001460375044200224235ustar00rootroot00000000000000fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/antiflicker.proto000066400000000000000000000011761460375044200260100ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.proto"; import "device_common.proto"; /** * This message data structure holds information about the * current AntiFlicker configuration. * */ message AntiFlickerConfiguration { enum Mode { NTSC_60HZ = 0; PAL_50HZ = 1; } Mode mode = 1; } message SetAntiFlickerConfigurationRequest { AntiFlickerConfiguration.Mode mode = 1; } message SetAntiFlickerConfigurationResponse { bool success = 1; repeated Error errors = 2; } fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/ble_cfg.proto000066400000000000000000000006751460375044200251010ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.proto"; import "device_common.proto"; message SetBLECfgRequest { /** * (REQUIRED) If true, BLE is enabled and active otherwise disabled */ bool BLE_ON = 1; } message SetBLECfgResponse { bool success = 1; repeated Error errors = 2; } fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/crash_info.proto000066400000000000000000000216551460375044200256340ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.proto"; import "device_common.proto"; /** * Kong as an Android device can accumulate * crash debug information during its operation. * When Kong is running in device mode, those * crash dump files need to be copied over to * PC and uploaded to S3. * Note, if Kong is running in host mode, uploaded * files, and then moved to device mode, will it * copy the same files over? * * This message requests that crash dump files be * copied over to PC * * EXPECTED RESPONSE * SendCrashDumpResponse * */ message SendCrashDumpRequest { /** * Unused. Reserved for future use. */ bool reserved = 1; } /** * Crash dump information. Most of these * are supplied by the crash analytics service, so lets * pass this information along. */ message CrashDumpInfo { /** * the filename */ string file_name = 1; /** * the serial number */ string device_id = 2; /** * the software version */ string software_version = 3; /** * the file size */ uint64 file_size = 4; /** * timestamp */ uint64 timestamp = 5; /** * md5 for file */ string md5 = 6; /** * the device type . Kong|Diddy */ string device_type = 7; /** * the device mode. Hosted|Appliance */ string device_mode = 8; /** * the report type. BugReport|EventLog,Diagnostics */ string report_type = 9; /** * the content type. application/zip | text/plain | application/json */ string content_type = 10; } /** * Response which contains the crash dump file name * information and bool value to indicate will send * file */ message SendCrashDumpResponse { /** * (OPTIONAL) * If crash dump exists, this variable * contains the file name of crash dump * that will be copied over. */ string crash_dump_file = 1; /** * (REQUIRED) * bool value to indicate will send file * true if sending file over. * false if no file to send. * If true, caller will look at CrashDumpInfo */ bool will_send_file = 2; /** * (OPTIONAL) * Crash dump info */ CrashDumpInfo crash_dump_info = 3; } message SendCrashDumpRequestv2 { /** * The attestation challenge. * (REQUIRED) */ string challenge = 1; /** * Time to live * (REQUIRED) */ int32 ttl = 2; } /** * Response which contains the crash dump file name * information, bool value to indicate will send * file, body of the request and signature */ message SendCrashDumpResponsev2 { /** * (OPTIONAL) * If crash dump exists, this variable * contains the file name of crash dump * that will be copied over. */ string crash_dump_file = 1; /** * (REQUIRED) * bool value to indicate will send file * true if sending file over. * false if no file to send. * If true, caller will look at CrashDumpInfo */ bool will_send_file = 2; /** * (OPTIONAL) * The get upload url body. This is a json string */ string body = 3; /** * (OPTIONAL) * The get upload url body signature. */ string signature = 4; } /** * This is event sent from PC or Kong to indicate * Success */ message SendCrashDumpEvent { /** * (REQUIRED) * Contains the file name of crash dump * that is being sent or in process of being * received */ string crash_dump_file = 1; /** * (REQUIRED) * Transfer state. * true indicates file was received without errors and bug report file was uploaded * false means an error occurred */ bool success = 2; } /** * Place holder for Android requesting that a crash dump copy * get initiated from PC side */ message CrashDumpAvailableEvent { /** * Unused. Reserved for future use. */ bool reserved = 1; } /** * Ask device to generate a bug report. This could be * for gathering logcat, system logs, etc. * Similar to SendCrashDumpRequestv2, but bug report generation is on * demand. * EXPECTED RESPONSE: * GenerateCrashDumpResponse * It should follow the same flow as described here * https://docs.google.com/document/d/1D5nx1nenDu9ucZbYPXlNNxFEN1tx3W7k044mvi74x28/edit#heading=h.a9wyfbpb2282 */ message GenerateCrashDumpRequest { /** * The attestation challenge. * (REQUIRED) */ string challenge = 1; /** * Time to live * (REQUIRED) */ int32 ttl = 2; /** * The note to include in the bug report. This could be empty. * (OPTIONAL) */ string note = 3; /** * (OPTIONAL) * serial number of the intended recipient of the command. * If empty, the receiver should handle the command as the intended recipient. This also * handles backward compatibility with older Sync app where serial number is not defined. */ string serial_number = 4; /** * (OPTIONAL) * Request reference for connected devices. This could be empty for backwards * compatibility. */ string request_ref = 5; } /** * Response which contains the * crash dump file name information, * bool value to indicate will send file, * body of the request and signature. * Similar to SendCrashDumpResponsev2, but bug report generation is on * demand. * It should follow the same flow as described here * https://docs.google.com/document/d/1D5nx1nenDu9ucZbYPXlNNxFEN1tx3W7k044mvi74x28/edit#heading=h.a9wyfbpb2282 */ message GenerateCrashDumpResponse { /** * (OPTIONAL) * If crash dump exists, this variable * contains the file name of crash dump * that will be copied over. */ string crash_dump_file = 1; /** * (REQUIRED) * bool value to indicate will send file * true if sending file over. * false if no file to send. */ bool will_send_file = 2; /** * (OPTIONAL) * The get upload url body. This is a json string */ string body = 3; /** * (OPTIONAL) * The get upload url body signature. */ string signature = 4; } /** * Ask device to copy test result. * EXPECTED RESPONSE: * SendTestResultResponse */ message SendTestResultRequest { /** * Unused. Reserved for future use. */ bool reserved = 1; } /** * Test result response. */ message SendTestResultResponse { /** * (OPTIONAL) * If test result file exists, this variable * contains the file name * that will be copied over. */ string test_result_file = 1; /** * (REQUIRED) * bool value to indicate will send file * true if sending file over. * false if no file to send. */ bool will_send_file = 2; } /** * Device can use to notify Sync app to call * GetMemfaultManifestRequest API with attestation... */ message InitiateMemfaultManifestRequestEvent { /** * Unused. Reserved for future use. */ bool reserved = 1; } /** * Request attested manifest from device to be used in retrieving memfault * settings from futen. * This is to be included in UsbMsg. * * EXPECTED RESPONSE * GetMemfaultManifestResponse * Added 10/12/2022 EE */ message GetMemfaultManifestRequest { /** * The device serial number. * Reserve for future versions to indicate target device. * (OPTIONAL) */ string serial = 1; /** * The attestation challenge. * (REQUIRED) */ string challenge = 2; /** * Time to live * (REQUIRED) */ int32 ttl = 3; /** * Sender to provide in case of issue responding to initiate request * i.e Sender is busy, firmware update in progress, etc. */ Error error = 4; } /** * GetMemfaultManifestRequest response. * The device should send this message after receiving GetMemfaultManifestRequest. * If the device encounters any issues, this message should be sent with the error field filled * out. An empty error field indicates success. */ message GetMemfaultManifestResponse { /** * The device serial number. * Reserve for future versions to indicate target device. * (OPTIONAL) */ string serial = 1; /** * The manifest body. This is a json string * (REQUIRED) */ string body = 2; /** * The manifest body signature. * (REQUIRED) */ string signature = 3; /** * If any error are encountered while processing the request, * the device should respond with this error field. * (OPTIONAL) */ Error error = 4; } /** * For sending memfault settings to device. * * EXPECTED RESPONSE * SendMemfaultSettingsResponse * Added 10/12/2022 EE */ message SendMemfaultSettingsRequest { /** * The device serial number. * Reserve for future versions to indicate target device. * (OPTIONAL) */ string serial = 1; /** * (REQUIRED) * The memfault settings in bytes. */ bytes memfault_settings = 2; /** * (REQUIRED) * The memfault settings md5 hash */ string md5 = 3; } /** * Response to SendMemfaultSettingsRequest request. * The device should send this message after receiving SendMemfaultSettingsRequest. * If the device encounters any issues, this message should be sent with the error field filled * out. An empty error field indicates success. */ message SendMemfaultSettingsResponse { /** * If any error are encountered while processing the request, * the device should respond with this error field. * (OPTIONAL) */ Error error = 1; } fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/device_attestation.proto000066400000000000000000000011641460375044200273700ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.proto"; /** * Request for certificate chain * This is to be included in UsbMsg * EXPECTED RESPONSE * GetCertificateChainResponse */ message GetCertificateChainRequest { /** * attestation challenge */ string attestation = 1; /** * time to live */ int32 ttl = 2; } /** * Get certificate chain response */ message GetCertificateChainResponse { /** * array of certs */ repeated string certchain = 1; } fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/device_common.proto000066400000000000000000000022571460375044200263250ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.proto"; /** * This error messages describe a failure that was encountered * by the Sync service and primarily consist of an error code * and a short, human-readable message. Therefore, if a client * receives a message with a field reserved for Error messages, * it is prudent that the application first check if there are * errors before doing any further processing of the message. */ message Error { /** * (REQUIRED) Error code. */ uint32 error_code = 1; /** * (OPTIONAL) Short, human-readable error message. If no * message is available, then this will be an empty string. */ string error_message = 2; /** * (OPTIONAL) A URI to a log file or some other document * that contains more detailed information about the error. * If such a file is not available, this will be an empty * string. */ string error_log_uri = 3; /** * (OPTIONAL) An optional JSON string with additional * metadata that may be useful to the client. */ string json_metadata = 4; } fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/device_info.proto000066400000000000000000000011051460375044200257570ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.proto"; /** * Request Device information * This is to be included in UsbMsg * EXPECTED RESPONSE * GetDeviceInfoResponse */ message GetDeviceInfoRequest { /** * Unused. Reserved for future use. */ bool reserved = 1; } /** * Get device information response */ message GetDeviceInfoResponse { /** * payload contains actual mqtt message */ string payload = 1; } fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/device_mode.proto000066400000000000000000000070201460375044200257520ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.proto"; /** * Behavior change as of 1/28/2021 EE * Kong sync-agent should not deprovision when this message is * received. If would just start forwarding events to PC when message is * received. * * (Legacy) * Request to transition to device mode * Kong could be provisioned in Host mode. This message * will ask Kong to deprovisioned/remove host mode provisioning * data. * This is to be included in UsbMsg * EXPECTED RESPONSE * TransitionToDeviceModeResponse */ message TransitionToDeviceModeRequest { /** * Unused. Reserved for future use. */ bool reserved = 1; /** * The sender of request => * Possible values: * 0 - PC (default) * 1 - COS device */ int32 sender = 2; } /** * Request to transition to device mode response */ message TransitionToDeviceModeResponse { /** * boolean value to indicate Kong was able to transition to * device mode. If Kong is not provisioned, should just respond * with true value. * set to false if error was encountered during transition, and Kong * wasn't able to transition (is this possible?) */ bool success = 1; /** * the error in integer if success was false */ int32 error = 2; /** * the error description */ string error_description = 3; } /** * Added 1/28/2021 EE * Request to deprovision Kong * This request is sent by PC sync-agent when PC * is provisioned. * Kong sync-agent should deprovision (if provisioned) * * EXPECTED RESPONSE * SetDeprovisionResponse */ message SetDeprovisionRequest { /** * Unused. Reserved for future use. */ bool reserved = 1; } /** * Response to deprovision request */ message SetDeprovisionResponse { /** * boolean value to indicate Kong was able to deprovision Kong. * If Kong is not provisioned, should just respond * with true value. * set to false if error was encountered during deprovisioning. */ bool success = 1; /** * the error in integer if success was false */ int32 error = 2; /** * the error description */ string error_description = 3; } /** * Added 3/22/2021 EE * For sending a certificate as data. There are currently * 2 known certificate that will be transferred - Root CA, and 802.1x cert. * Upon receipt, sync-agent should verify using the supplied hash * and write the data to the file system. * * EXPECTED RESPONSE * SendCertificateDataResponse */ message SendCertificateDataRequest { /** * The certificate type */ enum CertType { /** * Reserved. Do not use. */ RESERVED = 0; /** * Root CA */ ROOT_CA = 1; /** * 802.1x cert */ NET_CONFIG = 2; } /** * (REQUIRED) * The certificate type */ CertType cert_type = 1; /** * (REQUIRED) * the certificate file name */ string file_name = 2; /** * (REQUIRED) * the certificate data */ bytes cert_data = 3; /** * (REQUIRED) * the certificate md5 hash */ string md5 = 4; } /** * Response to SendCertificateData Request */ message SendCertificateDataResponse { /** * (REQUIRED) * boolean value to indicate data was received, hash verified . * set to false if error was encountered during transfer and verification. */ bool success = 1; /** * (OPTIONAL) * the error in integer if success was false */ int32 error = 2; /** * (OPTIONAL) * the error description if there are errors */ string error_description = 3; } fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/device_request.proto000066400000000000000000000157721460375044200265330ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.proto"; /** * For reboot schedule request defined here * https://docs.google.com/document/d/10pG89Cw_siDvnmsKxdLQqutIqegH6ouSnMon_G28Nyc/edit#bookmark=id.k0bz8vzzaj9 */ message RebootSchedule { /** * The time to reboot. * If this is empty and ts is non-zero, it means to clear the reboot schedule */ string when = 1; /** * repeat mode defined here * https://docs.google.com/document/d/1yQp8Ju82bDuVfHmprP_5ToUJbitK7kOjmj_71Cqc-do/edit#heading=h.k8yyuyddqj1v */ uint32 repeat = 2; /** * timestamp the schedule request was made */ uint64 ts = 3; } /** * Request to reboot device * This is to be included in UsbMsg * * After the device receives this, the device should send a response, * followed by an mqtt event that conforms to device reboot or * schedule reboot defined here * https://docs.google.com/document/d/10pG89Cw_siDvnmsKxdLQqutIqegH6ouSnMon_G28Nyc/edit#bookmark=id.zcy4ldnyuij * and * https://docs.google.com/document/d/10pG89Cw_siDvnmsKxdLQqutIqegH6ouSnMon_G28Nyc/edit#bookmark=id.k0bz8vzzaj9 * * EXPECTED RESPONSE * RebootDeviceResponse */ message RebootDeviceRequest { /** * Unused. Reserved for future use. */ bool reserved = 1; /** * A timestamp indicating when the reboot request * was initiated. * The device should include this entry as part of the event information * it sends back to PC during a reboot request. */ uint64 iat = 2; /** * Below are newly defined attributes that includes rebootSchedule, * and also tries to keep the request in-line with cloud request * Note: * older versions of Sync app would not know about below */ /** * (REQUIRED)Reboot strategy defined in * https://docs.google.com/document/d/10pG89Cw_siDvnmsKxdLQqutIqegH6ouSnMon_G28Nyc/edit# */ uint32 strategy = 3; /** * (REQUIRED) Same behavior as in Raiden Backend API */ bool rebootNow = 4; /** * (OPTIONAL) Same behavior as in Raiden Backend API */ RebootSchedule schedule = 5; /** * (REQUIRED) For device to distinguish between older request and new one that * support schedule reboot. * This defaults to 0 for older Sync app. Sync app that supports * schedule reboot will pass with value 1. */ uint32 version = 6; /** * (OPTIONAL) * serial number of the intended recipient of the command. * If empty, the receiver should handle the command as the intended recipient. This also * handles backward compatibility with older Sync app where serial number is not defined. * Behavior for host and peripheral device: if command is for host device, host will * handle command and forward the command to peripheral device as well. If for peripheral * device, host will just forward command to peripheral device. */ string serial_number = 7; } /** * Reboot device response */ message RebootDeviceResponse { /** * bool value to indicate reboot was requested. If there are errors * while requesting a device to reboot, should set the value to false */ bool success = 1; } /** * This message requests that the speaker boost audio setting be changed. * The device should send a device info event after this setting request are handled. * * EXPECTED RESPONSE * SetSpeakerBoostResponse * */ message SetSpeakerBoostRequest { /** * (REQUIRED) The speaker boost setting to be set * * If value is 0, the request is to disable. If 1, * the request is to enable. */ int32 speaker_boost = 1; } message SetSpeakerBoostResponse { /** * (REQUIRED) set to true if the audio setting request was successfully sent, false * otherwise */ bool success = 1; } /** * This message requests that the noise reduction audio setting be changed. * The device should send a device info event after this setting request are handled. * * EXPECTED RESPONSE * SetNoiseReductionResponse * */ message SetNoiseReductionRequest { /** * (REQUIRED) The noise reduction setting to be set * * If value is 0, the request is to disable. If 1, * the request is to enable. */ int32 noise_reduction = 1; } message SetNoiseReductionResponse { /** * (REQUIRED) set to true if the audio setting request was successfully sent, false * otherwise */ bool success = 1; } /** * This message requests that the reverb mode audio setting be changed. * The device should send a device info event after this setting request are handled. * * EXPECTED RESPONSE * SetReverbModeResponse * */ message SetReverbModeRequest { /** * Reverb mode enumeration */ enum ReverbMode { DISABLED = 0; MILD = 1; NORMAL = 2; AGGRESSIVE = 3; } /** * (REQUIRED) The reverb mode setting to be set * * see Reverb mode enumeration */ ReverbMode reverb_mode = 1; } message SetReverbModeResponse { /** * (REQUIRED) set to true if the setting request was successfully sent, false otherwise */ bool success = 1; } /** * This message requests that the microphone eq mode audio setting be changed. * The device should send a device info event after this setting request are handled. * * EXPECTED RESPONSE * SetMicEQModeResponse * */ message SetMicEQModeRequest { /** * MicEQ mode enumeration */ enum MicEQMode { BASSBOOST = 0; NORMAL = 1; VOICEBOOST = 2; } /** * (REQUIRED) The microphone eq setting to be set * * see MicEQ mode enumeration */ MicEQMode mic_eq_mode = 1; } message SetMicEQModeResponse { /** * (REQUIRED) set to true if the setting request was successfully sent, false otherwise */ bool success = 1; } /** * This message requests that the speaker eq mode audio setting be changed. * The device should send a device info event after this setting request are handled. * * EXPECTED RESPONSE * SetSpeakerEQModeResponse * */ message SetSpeakerEQModeRequest { /** * SpeakerEQ mode enumeration */ enum SpeakerEQMode { BASSBOOST = 0; NORMAL = 1; VOICEBOOST = 2; } /** * (REQUIRED) The speaker eq setting to be set * * see SpeakerEQ mode enumeration */ SpeakerEQMode speaker_eq_mode = 1; } message SetSpeakerEQModeResponse { /** * (REQUIRED) set to true if the setting request was successfully sent, false otherwise */ bool success = 1; } /** * This message requests that the device forgets a peripheral. * After forgetting the peripheral, the device should send ForgetDeviceResponse * and indicate success or false. * The device should also send a device info event after sending the response * to indicate the new peripheral state. * Note: micpod uid comes from device in this format "uid": "33", * but Sync stores this as hex string , like uuid: "0x0021". * Sync app will send the uuid in hex string format. * EXPECTED RESPONSE * ForgetDeviceResponse * */ message ForgetDeviceRequest { /** * (REQUIRED) The uuid of peripheral to forget * */ string uuid = 1; } message ForgetDeviceResponse { /** * (REQUIRED) set to true if forget request was successfully handled */ bool success = 1; } fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/device_time.proto000066400000000000000000000007131460375044200257660ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.proto"; /** * Request for setting device time * This is to be included in UsbMsg */ message SetDeviceTimeRequest { /** * utc timestamp. */ uint64 ts = 1; /** * the time zone. */ string time_zone = 2; } /** * Send an ack as the response */ fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/firmware_update.proto000066400000000000000000000010551460375044200266670ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.proto"; /** * Request to start update * This is to be included in UsbMsg * EXPECTED RESPONSE * UpdateNowResponse */ message UpdateNowRequest { /** * Unused. Reserved for future use. */ bool reserved = 1; } /** * Update now response */ message UpdateNowResponse { /** * bool value to indicate update was started */ bool started = 1; } fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/meson.build000066400000000000000000000011111460375044200245570ustar00rootroot00000000000000 gen = generator(protoc, \ output: ['@BASENAME@.pb-c.c', '@BASENAME@.pb-c.h'], arguments: ['--proto_path=@CURRENT_SOURCE_DIR@', '--c_out=@BUILD_DIR@', '@INPUT@']) src = [ 'antiflicker.proto', 'ble_cfg.proto', 'crash_info.proto', 'device_attestation.proto', 'device_common.proto', 'device_info.proto', 'device_mode.proto', 'device_request.proto', 'device_time.proto', 'firmware_update.proto', 'rightsight.proto', 'ota_manifest.proto', 'usb_msg.proto', ] generated = gen.process(src) fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/ota_manifest.proto000066400000000000000000000026061460375044200261650ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.proto"; /** * Request device to create a GetManifestv2 body. See * https://docs.google.com/document/d/1l31A1TWhtJC0xR8GwuNtiGN4vPLURRsj5ZcC1uEIwVQ/edit#heading=h.ctbthi1iyxw1 * * * This is to be included in UsbMsg * * EXPECTED RESPONSE * GetManifestBodyResponse */ message GetManifestBodyRequest { /** * The attestation challenge. * (REQUIRED) */ string challenge = 1; /** * The manifest version. * (REQUIRED) */ string version = 2; /** * The channel. Dont use if empty or null * (OPTIONAL) */ string channel = 3; /** * The meta info in json format. This * field usually comes from PC. * (OPTIONAL) */ string meta_info = 4; /** * Time to live * (REQUIRED) */ int32 ttl = 5; /** * Serial number of attached device * (OPTIONAL) */ string serial_number = 6; /** * target version * (OPTIONAL) */ string target_version = 7; } /** * GetManifestv2 body response */ message GetManifestBodyResponse { /** * The get manifest body. This is a json string */ string body = 1; /** * The get manifest body signature. */ string signature = 2; /** * Serial number of attached device * (OPTIONAL) */ string serial_number = 3; } fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/rightsight.proto000066400000000000000000000225331460375044200256710ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.proto"; import "device_common.proto"; /** * This message data structure holds information about the * current RightSight configuration. * Note: * status = 0 = disabled/not working|1 = ok * * 11/5/2021 * Added modes to support RightSight 2 */ message RightSightConfiguration { /** * Enumeration of modes for speaker tracking. * For RightSight v2 */ enum SpeakerTrackingMode { /** * This is the old rightsight that we know that uses * camera to track persons. When selected, sub modes are * dynamic or call start. */ GROUP_VIEW = 0; /** * This does not indicate a default value. * */ SPEAKER_VIEW = 1; /** * Grid view. Valid when RightSight version=6 * */ GRID_VIEW = 2; } /** * Enumeration of speaker detection speed when in speaker view * For RightSight v2 */ enum SpeakerDetectionSpeed { SLOW_SPEAKER_SPEED = 0; DEFAULT_SPEAKER_SPEED = 1; FAST_SPEAKER_SPEED = 2; } /** * Enumeration of frame speed when in group view * For RightSight v2 * */ enum FramingSpeed { SLOW_FRAME_SPEED = 0; DEFAULT_FRAME_SPEED = 1; FAST_FRAME_SPEED = 2; } /** * Enumeration of modes that the RightSight service can be in. */ enum Mode { /** * This does not indicate a default value. * */ DO_NOT_USE = 0; /** * The camera will continually pan, tilt, and zoom * to properly frame everyone during a meeting. */ DYNAMIC = 1; /** * The camera will pan, tilt, and zoom to properly in * the meeting only when the call starts. */ ON_CALL_START = 2; } /** * (REQUIRED) If true, RightSight is enabled and active. */ bool enabled = 1; /** * (REQUIRED) The current mode that RightSight is in. */ Mode mode = 2; /** * (REQUIRED) A timestamp indicating when the RightSight * settings were last modified. This is the number of * milliseconds since the epoch. */ uint64 last_modified = 3; /** * (OPTIONAL) The tracking mode. * For RightSight v2 */ SpeakerTrackingMode trackingMode = 4; /** * (OPTIONAL) Valid when tracking mode is set to speaker view. Indicate if pip is * enabled or not. * Valid if device report has version 2 */ bool pip = 5; /** * (OPTIONAL) * Valid if trackingMode is speaker view * For RightSight v2 */ bool reduceTransitions = 6; /** * (OPTIONAL) * Valid if trackingMode is speaker view * For RightSight v2 */ SpeakerDetectionSpeed speakerDetectionSpeed = 7; /** * (DEPRECATED) * Frame speed value. * For RightSight v2 */ FramingSpeed framingSpeed = 8; /** * (OPTIONAL) * Group view frame speed value. * For RightSight v2 */ FramingSpeed groupFramingSpeed = 9; /** * (OPTIONAL) * Speaker view frame speed value. * For RightSight v2 */ FramingSpeed speakerFramingSpeed = 10; /** * (OPTIONAL) * 1. Valid when tracking mode is set to speaker view. * 2. Valid if device report has RS version 8 and Sight camera is connected. * * Indicate to use Sight camera in Speaker view * Possible values -1 = DISABLED - For option to signify off, greyed out, unable to select 0 = OFF - is Off and able to toggle 1 = ON - is On and able to toggle */ int32 useSight = 11; /** * (OPTIONAL) * Valid if device report has RS version 8 and Sight camera is connected. * * Intelligently switch between cameras to show the best view. * Possible values -1 = DISABLED - For option to signify off, greyed out, unable to select 0 = OFF - is Off and able to toggle 1 = ON - is On and able to toggle */ int32 smartSwitching = 12; } /** * RightSight is an auto-framing feature that is available in Kong. * With RightSight enabled, your device will automatically pan, tilt, and zoom * the camera lens in order to capture all meeting participants * within the image frame. This feature can be set to one of two * modes: dynamic and on call start. When in dynamic mode, the * device will actively pan, tilt, and zoom the camera lens when * appropriate in order to keep all participants in frame during * the entire course of the meeting. When in on call start mode, * the camera lens will pan, tilt, and zoom to capture everybody * in frame only when the meeting starts. * * When RightSight is enabled, it is set * to dynamic mode by default. * * This message requests that the RightSight configuration * settings be changed. * * EXPECTED RESPONSE * SetRightSightConfigurationResponse * Note: For RightSight v1 */ message SetRightSightConfigurationRequest { /** * (REQUIRED) If true, requests that RightSight be * turned on. If false, indicates that * RightSight should be turned off. */ bool enabled = 1; /** * (REQUIRED) The mode for RightSight to be in. A value is * required, but if none is provided, then this will * default to DYNAMIC mode. * * If enabled is set to false, then this will effectively * do nothing as RightSight is turned off. */ RightSightConfiguration.Mode mode = 2; } /** * Response which contains the RightSight configuration that was * set as a result of the request. */ message SetRightSightConfigurationResponse { /** * (OPTIONAL) If any errors occurred while processing the * request, then this field should be set accordingly. */ repeated Error errors = 1; /** * (REQUIRED) The RightSight configuration that was set on * the product. */ RightSightConfiguration right_sight_configuration = 2; } /** * * This message requests that the RightSight configuration * settings be changed. * After handling this request, the device should send the response and an updated device report. * * EXPECTED RESPONSE * SetRightSightConfigurationResponse Support CollabOS 1.5: RightSight 2 Group View the only additional options will be the dynamic/on call start Speed option not available in 1.5 Speaker view only the additional option for picture in picture will be available reduce transitions will not be available in 1.5 both speed options will not be available in 1.5 CollabOS 1.6 (version=3): Speaker view speaker detection framing speed Group View framing speed (version=4) separate framing speed variables for frame speed speakerFramingSpeed groupFramingSpeed * Note: For RightSight v2 */ message SetRightSightConfigurationRequestv2 { /** * (REQUIRED) If true, requests that RightSight be * turned on. If false, indicates that * RightSight should be turned off. */ bool enabled = 1; /** * (REQUIRED) The tracking mode for RightSight */ RightSightConfiguration.SpeakerTrackingMode trackingMode = 2; /** * (REQUIRED) The mode for RightSight to be in. A value is * required, but if none is provided, then this will * default to DYNAMIC mode. * Valid in Group view */ RightSightConfiguration.Mode mode = 3; /** * (OPTIONAL) Picture-in-picture. * Valid in speaker view */ bool pip = 4; /** * (OPTIONAL) Speaker detection speed * Valid in speaker view */ RightSightConfiguration.SpeakerDetectionSpeed speakerDetectionSpeed = 5; /** * DEPRECATED * (OPTIONAL) The framing speed. * value could be different based on tracking mode */ RightSightConfiguration.FramingSpeed framingSpeed = 6; /** * Not supported * (OPTIONAL) Reduce transitions * Valid in speaker view * * bool reduceTransitions = 7; */ /** * (OPTIONAL) The group framing speed. */ RightSightConfiguration.FramingSpeed groupFramingSpeed = 8; /** * (OPTIONAL) The speaker framing speed. */ RightSightConfiguration.FramingSpeed speakerFramingSpeed = 9; /** * (OPTIONAL) * 1. Valid when tracking mode is set to speaker view. * 2. Valid if device report has RS version 8 and Sight camera is connected. * * Indicate to use Sight camera in Speaker view * * Note: * 1. Proto uses default values. When device report comes in and this attribute does not exists, * int value DISABLED will be used. * 2. Incoming request may also contain DISABLED value. Device can ignore if Sight is not connected. * 3. Sync app UI will search for Sight peripheral device in report to show toggle or not * Possible values -1 = DISABLED - For option to signify off, greyed out, unable to select 0 = OFF - is Off and able to toggle 1 = ON - is On and able to toggle */ int32 useSight = 10; /** * (OPTIONAL) * Valid if device report has RS version 8 and Sight camera is connected. * * Intelligently switch between cameras to show the best view. * Note: 03/29/23 Current design shows UI is grayed out, so value should be false by default for now. * * Note: * 1. Proto uses default values. When device report comes in and this attribute does not exists, * int value DISABLED will be used. * 2. Incoming request may also contain DISABLED value. Device can ignore if Sight is not connected. * 3. Sync app UI will search for Sight peripheral device in report to show toggle or not * Possible values -1 = DISABLED - For option to signify off, greyed out, unable to select 0 = OFF - is Off and able to toggle 1 = ON - is On and able to toggle */ int32 smartSwitching = 11; } fwupd-1.9.16/plugins/logitech-bulkcontroller/proto/usb_msg.proto000066400000000000000000000135151460375044200251540ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * All Rights Reserved * * SPDX-License-Identifier: LGPL-2.1+ */ syntax = "proto3"; package logi.device.proto; option java_package = "com.logitech.vc.proto"; import "device_info.proto"; import "firmware_update.proto"; import "crash_info.proto"; import "device_mode.proto"; import "device_attestation.proto"; import "rightsight.proto"; import "ota_manifest.proto"; import "device_time.proto"; import "ble_cfg.proto"; import "antiflicker.proto"; import "device_request.proto"; /** * * Header message to be included in UsbMsg. This contains * message metadata that aids in processing of messages */ message Header { /** * A unique id of the message. If responding after receiving * data, the value stored in this field should be used in the ack message msgId field */ string id = 1; /** * A timestamp indicating when the message was * sent. This is the number of milliseconds that have * elapsed since the epoch, in string format */ string timestamp = 2; } /** * The Ack message. * This is to be included in UsbMsg */ message Acknowledge { /** * The message Id. This should be the same value * in UsbMsg.Header.id field */ string msgId = 1; /** * The message processing result. true indicates message was * successfully processed, false otherwise. */ bool success = 2; } /** * The Kong Event message. * Anything that is not part of * Request/Response messaging, but is being sent to mqtt distributor * should be considered as a KongEvent, and forwarded to device host. * This is to be included in UsbMsg */ message KongEvent { /** * mqtt_event contains actual mqtt message */ string mqtt_event = 1; } /** * Sent by Kong sync-agent. * If Kong sync-agent starts-up and it is in Device mode, then * it can send this event. When PC sync-agent receives this event, * it should send a TransitionToDeviceModeRequest. * This is to be included in UsbMsg */ message HandshakeEvent { /** * Unused. Reserved for future use. */ bool reserved = 1; } /** * The enclosing message. * This is the root message of all messagesszx */ message UsbMsg { /** * Header for the message containing additional * message metadata. */ Header header = 1; /** * The actual message being sent. One of these must be * included */ oneof message { /** * Ack message */ Acknowledge ack = 2; /** * Request message */ Request request = 3; /** * Response message */ Response response = 4; /** * Event */ Event event = 5; } } /** * The Request message. * This is to be included in UsbMsg */ message Request { oneof payload { GetDeviceInfoRequest get_device_info_request = 2; UpdateNowRequest update_now_request = 3; SendCrashDumpRequest crash_dump_request = 4; TransitionToDeviceModeRequest transition_to_devicemode_request = 5; GetCertificateChainRequest get_certificate_chain_request = 6; SetRightSightConfigurationRequest set_right_sight_configuration_request = 7; GetManifestBodyRequest get_manifest_body_request = 8; SendCrashDumpRequestv2 crash_dump_request_v2 = 9; SetDeviceTimeRequest set_device_time_request = 10; SetAntiFlickerConfigurationRequest set_anti_flicker_configuration_request = 11; SetBLECfgRequest set_ble_cfg_request = 12; SetDeprovisionRequest set_deprovision_request = 13; RebootDeviceRequest reboot_device_request = 14; SetSpeakerBoostRequest speaker_boost_request = 15; SetNoiseReductionRequest noise_reduction_request = 16; SetReverbModeRequest reverb_mode_request = 17; GenerateCrashDumpRequest generate_bug_report_request = 18; SendCertificateDataRequest send_certificate_data_request = 19; SetMicEQModeRequest mic_eq_mode_request = 20; SetSpeakerEQModeRequest speaker_eq_mode_request = 21; ForgetDeviceRequest forget_request = 22; SetRightSightConfigurationRequestv2 set_rightsight_configuration_request_v2 = 23; SendTestResultRequest send_test_result_request = 24; GetMemfaultManifestRequest get_memfault_manifest_request = 25; SendMemfaultSettingsRequest send_memfault_settings_request = 26; } } /** * The Response message. * This is to be included in UsbMsg */ message Response { oneof payload { GetDeviceInfoResponse get_device_info_response = 2; UpdateNowResponse update_now_response = 3; SendCrashDumpResponse crash_dump_response = 4; TransitionToDeviceModeResponse transition_to_devicemode_response = 5; GetCertificateChainResponse get_certificate_chain_response = 6; SetRightSightConfigurationResponse set_right_sight_configuration_response = 7; GetManifestBodyResponse get_manifest_body_response = 8; SendCrashDumpResponsev2 crash_dump_response_v2 = 9; SetAntiFlickerConfigurationResponse set_anti_flicker_configuration_response = 11; SetBLECfgResponse set_ble_cfg_response = 12; SetDeprovisionResponse set_deprovision_response = 13; RebootDeviceResponse reboot_device_response = 14; SetSpeakerBoostResponse speaker_boost_response = 15; SetNoiseReductionResponse noise_reduction_response = 16; SetReverbModeResponse reverb_mode_response = 17; GenerateCrashDumpResponse generate_bug_report_response = 18; SendCertificateDataResponse send_certificate_data_response = 19; SetMicEQModeResponse mic_eq_response = 20; SetSpeakerEQModeResponse speaker_eq_response = 21; ForgetDeviceResponse forget_response = 22; SendTestResultResponse send_test_result_response = 24; GetMemfaultManifestResponse get_memfault_manifest_response = 25; SendMemfaultSettingsResponse send_memfault_settings_response = 26; } } /** * The Event message. * This is to be included in UsbMsg */ message Event { oneof payload { KongEvent kong_event = 1; SendCrashDumpEvent send_crash_dump_event = 2; CrashDumpAvailableEvent crash_dump_available_event = 3; HandshakeEvent handshake_event = 4; InitiateMemfaultManifestRequestEvent initiate_memfault_manifest_request_event = 5; } } fwupd-1.9.16/plugins/logitech-hidpp/000077500000000000000000000000001460375044200173235ustar00rootroot00000000000000fwupd-1.9.16/plugins/logitech-hidpp/README.md000066400000000000000000000130131460375044200206000ustar00rootroot00000000000000--- title: Plugin: Logitech HID++ --- ## Introduction This plugin can flash the firmware on: * Logitech Unifying USB receivers, both the Nordic (U0007) device and the Texas Instruments (U0008) versions * Logitech Bolt USB receivers * Unifying peripherals through the Unifying receiver * Peripherals through the Bolt receiver and directly through BLE This plugin will not work with the different "Nano" USB receiver (U0010) as it does not use the Unifying protocol. Some bootloader protocol information was taken from the [Mousejack](https://www.mousejack.com/) project, specifically logitech-usb-restore.py and unifying.py. Other documentation was supplied by Logitech. Additional constants were taken from the [https://pwr-Solaar.github.io/Solaar/](Solaar) project. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a vendor-specific format that appears to be a subset of the Intel HEX format. This plugin supports the following protocol IDs: * `com.logitech.unifying` * `com.logitech.unifyingsigned` ## GUID Generation The Unifying receivers and peripherals use the standard USB DeviceInstanceId values when in DFU mode: * `USB\VID_046D&PID_AAAA` When in runtime mode, the HID raw DeviceInstanceId values are used: * `HIDRAW\VEN_046D&MOD_B33B405B0000` * `HIDRAW\VEN_046D&MOD_B33B405B0000&ENT_05` * `HIDRAW\VEN_046D&DEV_C52B` * `HIDRAW\VEN_046D&DEV_C52B&ENT_05` One additional legacy instance ID is added for peripherals: * `UFY\VID_046D&PID_C52B` The Bolt USB receiver and peripherals use HID raw DeviceInstanceId values regardless of their mode. This might change once these devices are handled by the Logitech Linux driver instead of by the generic hid driver. ## Vendor ID Security The vendor ID is set from the vendor ID, in this instance set to `USB:0x046D` in bootloader and `HIDRAW:0x046D` in runtime mode. ## Update Behavior Due to the variety of devices supported and the differences in how they're enumerated, the update behavior is slightly different between them. In all cases, the devices have to be put in bootloader mode to run the DFU process. While in bootloader mode, the user won't be able to use the device. For receivers, that also means that while they're in bootloader mode, the peripherals paired to them won't work during the update. A Unifying receiver presents in runtime mode, but on detach re-enumerates with a different USB PID in a bootloader mode. On attach the device again re-enumerates back to the runtime mode. For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. The Bolt receiver enumerates as a hidraw device both in runtime and bootloader mode, but with different HIDRAW devIDs. Peripherals paired to a receiver are enumerated as separate hidraw devices but those device files can't be used for DFU. Instead, all the DFU-related messages need to be piped through the hidraw device file of the receiver. They are polled and queried by the receiver and listed as its children. Note that this will likely change once the Logitech Linux driver supports Bolt devices. Bolt peripherals directly connected to the host through BLE are enumerated as individual hidraw devices and can be upgraded through their hidraw device files. ## Design Notes When a USB receiver is detected in bootloader mode we detach the hidraw driver from the kernel and use raw control transfers. This ensures that we don't accidentally corrupt the uploading firmware. For application firmware we use hidraw which means the hardware keeps working while probing, and also allows us to detect paired devices. ### How the code is organized Here's how the different devices are handled in the plugin: * Unifying receiver in runtime mode: FuLogitechHidppRuntimeUnifying (fu-logitech-hidpp-runtime-unifying.c) * Unifying receiver in bootloader mode: * Nordic chipset: FuLogitechHidppBootloaderNordic (fu-logitech-hidpp-bootloader-nordic.c) * TI chipset: FuLogitechHidppBootloaderTexas (fu-logitech-hidpp-bootloader-texas.c) * Bolt receiver in runtime mode: FuLogitechHidppRuntimeBolt (fu-logitech-hidpp-runtime-bolt.c) * Bolt receiver in bootloader mode and all peripherals: FuLogitechHidppDevice (fu-logitech-hidpp-device.c) FuLogitechHidppDevice effectively handles all devices that use the HID++2.0 protocol. Every device contains two updatable entities, the main application FW and the radio stack FW (SoftDevice). The latter will show up as a child device of the actual device and is handled by FuLogitechHidppRadio (fu-logitech-hidpp-radio.c), which simply defers to the parent device for most operations. ### Plugin-specific flags Even though the same code handles multiple different devices, there are some inherent differences in them that makes it necessary to handle some exceptional behaviors sometimes. In order to do that there are a few specific flags that can be used to tweak the plugin code for certain device types: * rebind-attach: some devices will have their device file unbound and re-bound after reset, so the device object can't be simply re-probed using the same file descriptor. * force-receiver-id: this flag is used to differentiate the receiver device in FuLogitechHidppDevice, since the receiver has a specific HID++ ID. * ble: used to differentiate devices in BLE mode. They require all the reports to be _long_. ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.3.4`. fwupd-1.9.16/plugins/logitech-hidpp/data/000077500000000000000000000000001460375044200202345ustar00rootroot00000000000000fwupd-1.9.16/plugins/logitech-hidpp/data/dump.csv.gz000066400000000000000000003104571460375044200223470ustar00rootroot00000000000000ɺIXdump.csv]n]S,7-GDA18I^>i)l=s7O36O2$%j/۶HSɦI~ԧ\ki}tso׻b9]쮾g7o]'6o7 _}M~tv1i態/!C6}v||qx|9|yܜ]]mNwnsջͫ?^\Wf1˜)n䟓?n>_뛋ݻ団v/nvnް/.CWhWjw^_\,o?]^ޒocmy mMr˵XyC3˫ݼ9~ w ՇO>]^Fqk^&)إfe?ݏGIj1]l)OPLI5e)b|R`K ԟ7 fŵ[yˣyKjʒ)|e5)h3Y|/yX[|%ڵ[̜eXS[*5g NVBܙ@LmإwrCYt"vpmN\q9lreYЧzWp9?_]bBL`PDd",ʦٜ|_)856 Ո!(%X'RѕPC y5ɄxۧuPNAyoF ~F}`4"E%JoJVOOF閣#%(>hՠA%d"T,ysqoLX3Gu D. 2ycna"܏%\k6[]z|iJ#[1)cA?u][j{#][d,i6bN?UזF&*9B(S/?3Te-R-\,ǔϷ@b~7ϋw JUU$? wh k$~[q 7}|٨A"h'>^v;SĂ=Y_> TbA2f184PHɲb$#fWѻw{c QG<,戮ÎpAI:uiZAviS8YҚ]"s,^M7Fmd/Bv_EP7${ !WNtsqǿW7W.w8娝:uLdaTdC6:V6}g &uK,mUJσ'(x|* JJrkMG0].rBTl<N,vK5ViPRJtMxpx*YZnҀ얚 =v^߃* HR0`ڂr54v^߃* 5v uB!PedMPUnطz=Ҁ`rAUrTik YrSi"t:JOwujSF%%C;A:uZn* !iaTi@ɾ)'jk\@R`|{Pg]CX:]؆:ϼUxg$T[l\Ҡd?Թ+ug^Tx5ԉ#r * <4Hkg^Txvu+զ\THJ'y}OPicP'X-4qcxB6Ҡ$ۼ'4L)uS:>|j#sCZ8y}OTi,'6w“ڲruTi )a^UgCr!lTǽ*y}OTi@$g8QAI =0* u"\ȚYGAc#QD$x(ΡNvW4()'4L׹k Yg} 4Q}[=ư)4Hry6!7Vi)їÓTD~t ur%ky64V)7I* L;p(W'%%aj$/Ax((rK* hSCܼ'4V&(W';>qҀ6!qHxJڄ\{J8}CkPQaP2~$vt XTPA`u˼.'HL3‘b-" yˏ]:p:" yNj_²:~n67*2t7f%uO67x/dS ʳQ5K.oqښ;bNMytO}oǐf2C:oÁrt%ÍkZ<}?3/y/wOkϏpɧdJZn`+WAu/kgu^.@k4gOyL?sRTrc|N2֨LЮoPSZGh5wFX" GʬԀډ $#6F}Ϡ`Z.y 5E 8VڔH^g$3׈}Or+M X pfc_ÉRmJd$2s?&(DhcmRO8}cW[W vx"(͋,grsJ'pb* :LtS,4<֖ľT LO߸Tupb* x Y?dB,3(ol\ * $#i{- [yxzM:ńgܿsFS< c6S _T)8pꓞ VJ9o 2rDzGٗڅ'嵚8Ww+I9VXl\NFbWyQOz2W9]<)wU.6T' `FN|QH% -<;?9 G`@w$UY{k\$>ڙv+Y! o<ŃbP<;:U0F}'7NJMRWx;xȓ᝟ѻp-QM)D fD"(iM;?~(ۃ,Nh+: ynyFTFP< 0=7yO`m":Ǽ])(xZr]{E ]Փ~D^+q|ky=DB `}z^^o{yՖ٣+<"$cV&S@|&{j4.x~^Qf>f׎aAOWԆ8M7''WUShD@U/ɫ6|yBw<:e$y]t@m}0L!\%>rrth$(o[h4z=G2R(L~`k~ӽy! T<)<ӠXĆ9؁)W &`2R KK??=xXrxĆGqsJ0Y|uB5H<壯I$8GV/Cx8z%'~)3-q$к>/WΝ Y6<u: B8Gh-1\4ԢQ~.Ix$Gˏ>A@o7rxp~To1r[kl/V%;tl? >ʒgx 7>A[ÎVCPsj=GJ|+@f`% d+y{\\?-NRE}7 i|5 s.4w2uYO3~g'禊/Ԟgw4*cgx$xLZs/S|0|)?f-*^$_Jj9RG;Gˏ>A@} ꃫ!(xyp.Y՛>x/`Dq#F(·*ԣ>g[9>CȏhG&Noiݛ"gsjċU\>*gA)s?k!hxϣ`DRM>|kQT@^=z?=_޷}ų!{[_Uo9>A.!hx#okG򃏹v_)xׯxw+u+G?T  L >OC'ʏVO3`^=ZC|Wޞ)'xfVDA|_f!<,E~ p[h??>"?W,--jxyiJ)-? xaGnFg51Z8P}?| {HyZHgL5 4>AXc- _,N{  0n޾Ghd!?G@3YU'Cu}eGӔ3<<9xᑀGz$xmHѼ,w.3p^+[SGydx_ﮅQG## Vo=ex+^:Ɨ!)xtpw=17GApݵ<,vه ~ 8 l%ĜԶ'5Q"oGT|N ޞ[? zCjsH=s?Cn o#ԨO{=۸8hK;e-E}7~A4<$ݎ!G:D[Ń~/rl+gZ|N G79BBF껙rD~ϩpV q g7}h7u<?k.9"?<q~}ܻa<8?|N ~ƵL'+A6S}Sv*gx~Aܨ gȭ»-R?v~nZS?ɭaqWC:Ghfm1P~$8?OSŝo&Cu)gs~4<܌0on WW~8Oe>~wUp9Bs69] A|C]gȭ`"VoqU<7>'  L>"^W19"Vo=x>hhsU3w0~=,bmFf!D>Dr>)'x x3k!hxϹ!GM>|E0!Yz|q-_qw:sgƣe'7_>]l#1W W׋F6-;^\[>~wQo/g,d}w5 9ICbg(gx]Nwwsg~>ϢLWC_~Ww]z?)x93\2<ϋ(ݵ<Z2Q؟4 HaOU@:L7dI|vLm*sg>/LKVCSKg(͟.Q7@hǨP4D|noW̆Io E{AGde|}GD-~^؟8Ga[kFԎz=sOVQpQБ80] A#2C~4>O {GK]Aj"oq͍W/yuW j!hx4>OԒ1tu{ x#>~/T$^D [8ic5%#Sg?\~g ƼU␂9k! Q84?_<.^wpax@kxq'Yu?D ^8<ϰ|?g(g?\~0x㊗9Ѳ}S\pE}p2!hxI#  KJ{̓]_I="Jw}f}S?%g? B4<$n~~%g;GyV]O?U%g~ Vo{] ApA<8?D c|@*[0Toa$?.Q?+g+3!L>QPD L?|^bhKlG!?10lY?\~UoG?xtj֋3`_wZ/W,'bL<Ȁ?G!hxϩ%ٟphq"6s X^qOLBw sj $w]N\C<$$vB{am NJy! A4qz5 ;+ Bd gokzï?d?v.;,8D7%k1hޝH!q?DɒNMsIh"Y|RzRB`h:Cv>roU<$(I#|d-eoz%QV\iC< j8HPuzZOkVa%}"i%8dNVc {$Ɋ+H(K=TaZ kKV/NoH@akeIBy¬| =qH$K2Nm)NCRc//H>qC)$YzqƠAz*^-dI'$ć5eɶԷH=!Hw8/YA7CG$˾$ *vm+4H@r9oBnvpqƠ@_LW; BR CR<$޶YH^;{p EδJVWr,U獑,1{{p@8ǵ$p*ʎ 88?Ʒ!YA6s:#YҎ;ejB$zIZGޟ#Czאx4H@%@ؗ+YҼKxZ\]W`8ԚY}@ ǡJaw2⩐$+Xu8,{iC@itq~5 F֧ c``dI$%n:K‰"sUq8dIw7Z $qH$K{&짹?zɜfR`:Wd ]Msppq^As'3D^ 8jBȳLƪ5쏷z>Ô_zUU\!tU 6h񜌃Vc q@w/Yɇ/Y$wߑTq@=4z/As2a/4HHCy C 9NsIpom~pٸJnye>9)Λq k5 ;a"ɇ/$eشݙofI@y"x- лv$K7hOr ;oL"]Fg']qArn &t2i~ L w2A@ ,iXTv/"&}on$yS 9'88LWc s'PqDй}@2zR16vJ˴Y oB%wHb s'X%8Ift֦=zYUZRaQP$w Aylq5 {9tvXQ#PTm'in|u=7$SH2N㐷y.YANg!ɒ%Yz' @2ְ?dGeW^YKl(K!KyIx8d3ǵ Q8ڭnt$tbtBFw^ŠAzN=+HW^D yzҿtM{7@Nz,7/g:<Ա ;Ɗ[,z MW? -;5 UH6~_V^D~x:xU%n; > :YH<ɇ/)n^,}Yp{1%nz=k\$wjzqh~Ocj $wj(Οx$QfoMUkWg߉b<(3]b ǷviMu!Uqޢu2,>";KbP E]6<9o¢&u!h1U/PrS񴼿|u`|;CK)ZNơA24 轈F;d !SWiHDk72E/s/u%sP;D:`:i4H@%9i%U]!O'.^r,|+#5OW\+$6mk%w8%k1h} qH$KKPK(KZ%x&U>;$:/UVo [}QVGO| Cq4H 9t'Y^jXDZC2z|4U>iѸ?MuA̿pk7f35䛺⪆u$MEmu$Ar%-ѫi]<+aƠAFD!,!k}kCS2}Wy\E#+lѓ4}CT׺6ӝ1h@R8!H@, :_`k3(a5x67#>x =wՕIj> $8Yd5 ;w2 UXXdл ;4<6q|cjD7X~ˠw*[Xd;C *k1h($%"ȠwZ7VHƍS ? ]:I 'U/)wsHZ;C1H Fkmzw%fzXhŸm--^@pUYzq( 4H@tB:@5IIdtŕP7ʿۿ>_C[l[U@z' \VNPZ%xih5 лk//H@O_.:\.ZDtJ~y3;jީ9-;y$*ŠAzюL\aQ@wz5{&S+cowj(7;ôj !!v!z Kj Yn>nH}uQՓ A8wj(WmڶC 0]_AED )K4f/V񾗒#v=mgowЯ!);tq~5 ;w2UdIA#eI*5 o!S+KY_Ya}]d !C ;!,q(Kfݴs;ʹ'ڸG!U{ HSyFnCy+1hޓ̡8m{HF!r3O6ި`H =7}lrfo@<Ӷ61({9t`\{,=j Rw-=6YzhOd5H@p I;C4H@t#A*;|d.Wk,{Aтe4K@pU vYzqr5 ;HPNnfpYz/%ezak=ᨭ`@g {i)$Yzq.ίƠAz/2@"738,=K lmU\vk2M4oͷi4z78Yz/Iw:dC4z7JsgswnW~V@y kYb8P1{:KbP aHdI[Hh [鼑)Z?4z7_ε,qXd- F:dIFPveZC~(BjrۭI$Ar%YƁӐŠA̡#x, MoЪFTzyg`۬GAi4z7T}dIq68_Aѻvɒ,in,e,5{~־)y3km+awy I@4zqj $w+s$A$ K@6 -;5dcڻUX}>xށޝb9$-Kaf5zwĊ ×0/)Mkx;T#^Q&0_=L ;w\B8YUq5 лÐD+xē_bf=K&M2SI~\ށ]9$zqhö61h޹abH i}04(lyK^ȫnӡ]]Wyw64H@>@ fHZ/wwR:}i_}X,$w,IHt;C=ѻ ݗ#$W  N_HF+j *W;KgZijii`%l7!}Oރy$Ơ@A5Zq9x{ا 3{hiW-OX~/4W%ˆV==HrKtz/Lb s'$ $ ʒ6Ҽ!K}fVFt*G[%S.;t%x5 ;w2d z <=$KJhoYzq0n~ZA^,dݻv߬|ӗq.yՂ,1 >Ǥ!4i(K"أ#y`kV4H@%r #Yb@ 2J+Ώ^wkwp!]/y'kb?ʎѻݶY#y1(Fv3/& |@2UaM@mڍ_{tWcwV>$cqGH"-R2|=٭i ̷Otȫg߁, Tt!| 8aKVc inK7w8YPUax`4} ċaxZ4zܘ. ;C7zqj $ aH£#|@2|kwp<4H IUr>fpcqhy~CI$"L\ $ }({hozS,ǿU Iwj0Wzo>{H6 AeGz%v6o'2VM$wj0Wy .|8|d- ;7 \ޑ@ zoUkk^dlj \b}w@ܘ= K@< 49 9W Yz/e5[)Q7/THЛ@ܘquG$:ǡ̶CƠAzwee]Zg]>B2%UkxП=;m*C΍Wyw;}!j $wo!_e\:7ܪրr\zAq!YnWWõCM/ `Ae5Y9,IRPIsvV'}HRoR{z*\*$fpZ`<ŠAzf) 4H/B2ntZ`~ܮ<'t?ZgCidd NWc h,Y%w qv7:ļؤ{k0!KDtH@q)u<v0j $hz dIAީ/ĹW=OnZ t9PN;kZgcpqhz-?1hcF{ErY/×8? h Iڟ'arS=S ksIKt8vHZg6KF<%dTº?`qI_o 琴#\dlq~5 {<C$tf=Ukܿ< ;7f\]}u2aKVc rKd =SJlӾW+įh{arMe$wn̸:6d as~5 {$F K@9 -;ޫPktc;͋>a\,7f\UA޳_d- {.mD K@G6Uk[~=9)x"~1铧 pS1I͒:j ! pCb%KCB&6Wc ikơ|hnuBa{N tDi$@F qHF2e>Kb I$ 9H% $[{ y%94)e8~ +ΑKge%MڬƠAQKN#K;%!O> \Cث!\xV YAuliͰ#: ݫp>ح~U!+4zwd1IKtq̪1h4zwaA ;B3psKg=m92Wut88;{ k5 ;tء|Î%w%$nx%Zm12 ;U%8d ݦ$Z $w5ګK ;B3p/8?ڦ}oZ[%!Q =K@dq IDޝ Z $wjpCzIÎ.\ 5U!uLU%8:HZ稥 f%!\VuWR+x$wjpWC8,@ X%X!BkpsVQBUON K@<aV{_ANmHdd !l%Qc>ۋBZQZ]3*>1hީ 8vxv%wj<eOȶݐ^dAzM<Yzqh _. =:% N>MOTo)ǣ6{<*,kvkHZ;DkCbP k"X!Bkp/$5c?9.`yYcU'ѷZvkvwYou2<|g5 ;w2 ]"^:G6>OO\x\N[uV lmu2.YADg,MTyoݹcv >Ss%:ݿfkZHBwW!K@<e~ZA^;  N6aL޷z'Nh@]jIBwW K@4~ ;Hd l2h`³M;mG=}T.kM $8s{ Z $=w2 iGq%k'w gvF?39Z$wSHѻtj $wdD!"z Z"|lzV׋D 5}d a4H@N.w%wGY[ngƵ8HZ [N!WcP kw2dI,i un y:KDku3u1,kCZA8$%" f;g3ĂDү'~Z}7O% Yzq(Z $w/s$I$!K@tD-?0N:(խ;\|hi$d ǡO\k1hރ̡CHdd .ˈ4$5|W|OR6:]dd !lzj $w$b,#fd .ˈfr_g3/ q/(se%]ZB Z $ptIrYFley,ޟlf?gfh_]lVSZZ Y ne8kVc GCGD.ˈ:OMUGϗ<ZC3A9uh%~CN, @,8i4H@I!H8K҆,SBDOspG/Isf#]NSq_P/ptBjN09  IH%wjB$zWY'P/3In_?:TZ邅pU N}- {9t,SByVi5O홅ް=:ހhcuMky`!\]Bkd-,s'#YnpԄ4pe³O嗀-FGZ邅pU NM!N!ob BjNFZ &89ZÞ칵Bޫm W8Z $"sHXXΓp[%xƸgyUoX7 ;[E"W8yz_A{d"HZHi2K^Uk聼z q.pP4 w8LiƠARIDu' *5~o%rWިlj^U%8A{k G'HZHsCmIuw@R'W;š0⭩8l$HRN!:su|Fy{Wc S $Y$#K@$\0YzU>ojt̥gwr:/+} µNơUWcP k]2dI;ZH$[exPa;$w:/+} µNa4H@QБ,)%%"@BY*u5CϽ2AAi׺@'}IAy+1hީ`Β!K@$\QoW8l|q\kӎp t?^U,8LƠAz8Ԧ7' K@$\Vσg_Uk<\W"\d{Wc S@*g#Yb ipf خ?Vp %V},8,@ :%V"K@$\gx ;>խ"\f AiҴj $&s$%Y=ptpxxYˬ4H UQ8@v%} µ!M>$2@%K5u*Bj s&) Xt8!?=T"\"]0 $8LƠA=R($%Y=p HF;TUk^zyC 蝴tU Y]aںy5 ;iiE{Yz'=I])R6-4 EEU%8d a4H@BPɢv @kIf`%"8Yp4}4HZkHZ@kCv5EHCmY׺Hu-; yr8tpt @*gN!sZ $wHC,v @k]$^ZѶFto_\׺HJ0Z'0mر POpkrB=Dy;%BF4[׺H @,8Jp璵4H@?0 dIFIγ5-Q - ]gw5+ W$wM՟}u2yb S@59#K@$\ݻh /i.o$nՍϰ9Yeu_׺HJ0Z'0 \b9tɒv @k]$:Jݦџw%}F)Azk!A\dZ% r $(s$r @nuk@2U4[+L\*Q`knj9$wO`!ð{4H@?0 gIِ%w67q)+|LsTfGz/=pd*pqfKc 'C )H%wK+{7[+Hj"H||xY^8 ;Yǧ+w8>4H@Yl%Yz'cL"c1)Fp!.eS/<ԭ"\"Y_C8YÎHZ 狑,iu2Y 6[$zqηyg|V ;YJpA\xϒ4H@?,1%C?µ.p]K&$;-V/oi~~YN}u2n>Kb CG %Yz'LiӻռPKI˾獏;6 .=Crv}u21h4zO9@r>qY,ic TDep_M6i?|LΦ oe HB7O'.Naְc9 H"0,qȒHx`HľޕnԫDr#% 2&nG%U14K>:6HZ̡CH 4HH.%j8@F A\df1h8@!^HXKxxK=ABX$ znh@Rj8o:4yr $ޓ8,Q )ABM%׉kM[Ph0~FGϦ6?V\pK~+ 8̶i/ǠAzc$K% K@ԄP&V!]k-L'Yz'|զ]0ۦ f:ob eDɒXOě$Ybրms*t07u4?*pK~pq(1h} xJ%C?µdkMָLsEI{-0+Lc! |YOHt;C1&/ǠAz'<9_RH2ihqTA qу_2)=bo ;ϗD84H@[%@%@Yb'W\j _$}by8= \$,kC&m|ZAuX%8K4׺)KdTzIabO:93}׏6m%:_.e \d+1h"Y<#\R,A%xn^Vbl(ǴtIy$w:YP);]ƠAz劫Hd =mHW\5X]m}}OIXBMCpKtWAydr $$s5$v(KAeIzYZexf}%AzW! K@<4H@? IH% MRtexI uNJB͒:ٞBZf$K~k]ʔ%ԧ!k?Ը!e$%:_.*{zq =zM{ %C?.ʒIÎ=[3jnh_i׺D'Ž{;ìr $!%Yz/%yzuo;߾"{r#Q׺D'Y<"K@<m1hiHXdIQIĴ?Cz-?Rg>l~4ztŴsH%wYw4HMHdC@lklDtKǽ uN3$'=%pqu^Auy9tɒ桟ZFsgi6k;{jy?@= ĵ,k'4Hgr$g5.yɒ桟ZM$yT^?W5?@y G4zq4$k1h@R8E,LR2~e`=Fa7La(3^Uhܞ}9O}jسsHeƤoU_#eڗ=s דܖA$k I*rhs*^R2ZgcC@2ϒehl=9EF`Xk`}IEfyld5 /`Pf8dtJ-vՍyZrpU},m⛧* Wr-3'#Ъvˣ`E13;[zw\11MR⦊VQw\1\'`dF#pYG40s ʌ2G>WՀ7} '},qŚ}FAf${0Z`n[7rQx/qUo䘺D֞>&lɿǸbcUϴ 8\aw5 pM|cCzm>1ZrUxJL8|r^(6jW81~]\q x[ BY Anqɼ97+P;MDU~R/v&.5JADNhWU Bc- 0s4(PG-* гfY=Y:>1W0+t\ydW>q2y~Q_x7n^ A|e WH\vfU~>4zG>D=?1V 0A4'ٵ$GG\Y_=l~9A!|N!{8?mr:կpԾݣўۭ҇z;}p$szTj!Z %4*:]VE4c\_*lr@ҫvA"p\G')U<˾exuk0Ekxi|;b|arrMÃ8ίϝۻ%"jxȏ`0mIGⵋhG6#*ȋc gT7zRsAAX A#5<(➄hDkZa4<^Α>.1@',9>-w#7 Mt:]ۭE~yljs:?C~ϩBxp{ҳzY\TUG; + ÀywWCP0sw#]8?eqـϩxꃡ{tܣ#٥p#,;+L5g>Ck]>A4Usxx7k!hxcs~xlnh4g޻gUrs:.#?<4aYuC7s\2v0 Lb>/2usR}'*حr'u|Y~ϋJY~$Wk!hxϋL#o >'z~vx"^_Ț(ڵK|`m^|-<s!hx4>70GF~4>7>V/ .|5G;ۡj>5>7nϳk|΃|3#@>7񱬟M>|E0[k֗[: }'9*#x w">Gs ?2 4xJY[%()xwUoo{?OGsC-cx6p-ȏ<(?`0hYx ֶN҂\p7}ᑀG ? x7] A#!!ȏg1^ {^|5g$]ţ񹡓6GAo_ A|N-~6qݐsRl=/n>\xeW?Cs: G>A0G3|NshQ_0Co*\_]>.áywWCS :w?{9ct?x8\16KgxIxs>A4Ax>2u"?>nQ?xWL|N'E}סyWCS >'xQ4|{><i?VCP Sbls#9C.j_5}e z?us: /w:!>2uќsd*V[ҽs: :aZ18b |N:h#"[/c->O"GGB4c%ƣɷ?+Jߦ>'ys4|Nl}w5 yu: t>81+?³W~Ivs0}jG`?n>'?1x *B-_ۺt/2G9ᢿE!LwWCӨtD9D7<xYÐ=s ǿ:??GyiJ|s7_/Y A|^b+<  Szf}[U>Aq5n2u^wv [2!:e[ϒPq*g {rj|.0}jx9?iH#OW8/ܨ2dj2cϭiJY~dGA_jϭsΏh|nɊ>z{,q=_##?xGs8;_ᑀLxΏHwGa얨½±jKGsJU ?=t}w5 A7GsKn1O?R?byo&1XOLU<KRiU/~G >Aȵ^js'b gSq~SxUŃegY|N:x{d`r`?n >'?ꉣ%b?{6R}3/ޟg9ᢾ4qc- utd9tMWxG7,$3@\ To tq,x?B/Y Ap@7,VOӝ~\Д3<8i޾F껞<gx*kxI]? A_w7k!hx }9uTŃZ[S\|>:ٜRy|΃0߾<(QM>|EJ\ŃۃzmvL`{/>g%  L>/CȏdMÃ\Ҭ_x(eGt*Ssi:+y^ΖTRp LWCP?-C}GA4{8쵿;BYطK~lZ'\綦TGn8A _ A#ܦK<8?2#=?2w\Ńӟ; kPT~Z?c%^!Mܑ ?;:MgUZg>QpJ 9 i?<笃z'6ʏк +r'vx+ON'Sמ? +\ox ?Gl[~gZs8U g%:a޾Q<,E~⑇rm=mQ/s)gxؖAﮆ8SK<8?lF4c=Wԓ߯'kO'قy+`[~Nԣ%!hxσL8?C4 rEqzz`$hy1m? +Wx8w4%̿wzc)st<]wCh82۟z{4}?.1Z-U<[mz=q1Wx`퓣*,l^#܆_ppΑ.:'0헱GsO-y[8? 'h?uiG2[_'>> (#Ve tNa4< sΏ(e,U'@* !?s/<p2a?BP?vQ~D>RjQDRNHO.8bL,Pn~88[o_ A|NqF#sy+N6ҽWxAQ9Na4]3,ꉝ<Naڟz5y/SI?zBkor{\ŃvΓ~/9dWC:`_ A|Nq񧎬v1޽U`;4[vpʍ'%pΑRyҟ82OZH}7:#"G ԃnU |N'E}7p<Wk!hxϩ w|)?t~xxdoދP.۞?x?B|< |墾82OZ6O?6?8(?~.@ wt+ Єϝt\w#:'Zl]Ljgʏ oU/G}Gi/`pgAꛂ<闫.:'Z Ewd? +[.J <| v#@? B#[ A#! GvGD4#xoU<zu\%uTƺ^&˟pN—nF'{Vo_ Aãy+P}7__,ȏP~IVŃG,.IK< t}5 <Ľp~4=+6O}Nzq|tRi&Zګz`L*O)N=C`z@R8)dځzV V}_*wUQ@ɕ'޴qkA֭ovI,cz3 ȝedzqz4&kA醀2R~O<Lub2ZcFP1&]b a TL@4nĄ<{>9pɬ&0 08PzOV0m\+YΓ$@ߑa|^9h/SǬ_7KbQ5QTx<[\ B= #e65w4LY{"iexx:O`,rbup`2-E|7tׇ0pU=O=b8>A `}F7њZ O:'0튲Ihe$o'ys=<~C?TSvk2\}`<mIIxi TL6 c'xUz?!F:gULT'x&WP1;G0alzIȺ[ uyǽb$'x0ǯbw2`ybzl]Qu*K~ Eo }'y:Zj&ގb¶ٺOSiҶ=_ 5+Ox {YGX%3LzOd<&kẠ#p&hmQI%V зULN1yqv'_ Bdy'-۶yGgH 8>LzOdo<\ BOMp'xҲm *=yTLT'x2'kA㩩ԅ3gOZ ?@}ApU G:tt1*a{ L'0]_ Bt1<: =`w=p"(o|%Nt( o~aNyw:sZ*&$&l矃4LHvf]c:ĽSrɪ_b<'u<=sZ*&xj*Ą𨋤eq>遪Bp̒qa'=\ԅsR`]TVP1gGɬç.dmYcJg0$_50ǓIT`mfxZ BesUIwnW稤z0Y]$=%ϳpqp5 4L`WIÄxEݬdz]fw*$!RSN'Wyi.ډ;8ߵ ,IϯhϬz<ƒg_F7_>%Pڧb8>Q}l{ b'\/IDMvV_l0!-}Jx4o騾 S';j*&xj*#uyƤ 8LJkgtnR9LMD (x*ڋp=pqXd-p<5ؑp8OLp  {G1w{/\,rv<n{5p&1&LѾgӊ+ZK an訾`OLzi_ b0&' 8w0Gb]$a4#kbNi 8ۋOঽ=VP1LJA `8O\p<5&<Qc*&x:.zyqpӮAָO ~V(6?/.? 7>w_bҮ6օWP01KQL<牏=`By'9U2la{U9.Q}wQ/zq 8>mØpiPcӘ Ͼdqֵ_|gy\OGE]'xio TLIZ} ' 8ɨJ8}>o7>w)*yb0v TLYс< ' 8JI7WtˁOՅ]Ez[UL`{yoV\ B(&$<ScOӘL""sflf>oQmo |R.U< <ϝCOV0]*1 I2= 5&Q?WP>R821t쯡^[1KWy,O>w2~+Ạ$Γ{<ՅG{^UzHQZOӝb_9%?W_T&=O<yr5yۆ1<=OgjLr.s<ǿ'y6w'eo\ B$A `W$xdOE!<^#ז%o^`Ry'IqXd-w<OւP1i`:@!1;|=q|~!]`,O4;. {=SHj**`y+35*tmsEWPam׳O=`Uؑ ~WVP>ITLPLҸ (i&1'zI^E T_F{neu.J/?HC|8{b5 P~ 瞷Ȱ%ng:y-%k{LkkLLgaA E[Bx H ,GX-o_KzP[XƽgAYsCaAO"sZ*,|8#EcrbX`o`IeQ&h{%ewBX:ܡ_b`$( @<ӵ(TX@tɖгOjw% VUg/=Up: 'r,[zd(J==@^BO}~Z/سOwlQ3w'e;֗YgLGZ*, #]fc:#[&*UM?jW \xAo/C1]_B4KXz»lyS?롶.+QpM[Ǔ.FBgHيd   }˻#Rp;{=3 y4XP, ,MװH,$Hm',V}/gR!sʿx;2L aq(fZPai_Ll=[ ƹݪR^uJőE=ZIg ( f(_bd9 P>-| f2wXo, ;Ⱦ*,|ׄsXzy(f}Pa;PG`1-g (n",1߯ówYk-&‚ d(,e& uTid .#00++s+1;H'M_]&o߈= oBYM6<VA΃ (\PaXG`W>O1"rvy+_w+^[W|J-lP,G'#0NzKPX[DOË-=jP>\S/Py_BdBI-| mu<fZ aE/#/4׳OCg—Pa4dʧ ̽-*xvJ} gVګ Wd x(b f߰EB!Qlyda#H~|RI'?5X@d*P]r*,(Hx\O``W!ٹݚR=[}Ϩ/q\?yJXrg ("ζ/GO>lnpoIRA%q8c X _DP~jBY =A<r*,|3CdzD~z%V sCsý9Jn&fK)@a| ( Pل°tOx,Rw/?}ݒWp1#շ+Vp .[00ӓm _BfzL PDɖn+;Yj5c*<^9_Chn',|n+ ܷ`'C*ȣQ`ɖ,GBNe[DxQKw ( r硘YB_" -$ɖnS= }b޷~Dyj̽]+2#SaA,=A<9L1(TX>[[~\f [$nG۷G;_hqҎ |%xyoz( tj׆bLd9 XG1 $ӳʖ47U8pDZT-z뵡ȳ+(Xl۟X9K)Vw=ʖIoZܚ;E5X)!^a`^Y(TX` mBH,V=ʖVASN o+~yjdGV-DJ1|[ QJկO gKǡ(+(TXB<Fal[ϖ`yġ lS_`-웪TdKS=7M%z (Un=[@4+Ew2"bz1[]eȈ|H}l*>cGր.%XʧH e- waKKO} ;")\ 4)k@d*v րi(ty5 P>yġ¾ k@א'x!稚ًD:MlO'=RjP>%pk{i(Ҭr*,|/,b`mP>5d7o8qjn,*['Xzy(f[×Pam׳O} yoR4>2+qN i/jP>% u=[@<~r*,|HC}+v lS_`5VAtH* |1z}bAt>]-.6Ce- P~ {/*K.ZP>}Kn<^9A\NdU Me{"Qas[U.ZP>E G+kQ# U%X%X '2x[%_Qkh;8P>% =A<Q# U%гO} y5ܶ{Mw(L|zE (?!Xzy($ (<Pي]=[@א$~2D(;LQcU,Ov 骰oa`(be9 '*^q%{D-eTUOj? Hk4oL-u|n+2|CQ&O/G'Ӈ4rJXr(%?H*2SNIsGa߁ P>E(TX@1K= @-elRP#VE:P>X>Z,SyWPa{zXXt'X'2x(r*jʧTXRӕM=AEԉ]j*,&r'nHgK|CNLObv\i ,Ply(r,8 Kl ;YU U*7z )__`v~% K|c,һ||CNeoR!7SWO2YJ/X` U;͖ҳQ>E[VPa %6H e ~e| (~7Wֲ{~ )lI8n~[BQ#_8[\N 9 \9(۪T|,>j8_U_-i=(|Uw[ϖF2~V4^BoEyg (ʤ(r_gOܷZM-O| ;x>ʷDE@N&!X$[zwB;>3==Z5ʇSĝ;q P>)װPubT (u43-"[Jgk\lw[=(g_tX@b\o|VnNEʧ{c_^γIW|HO-zEP(TX@W:XbAi;2-<fxA;5=ƾ,|jWd[,"(̚/GO]X{QRV|qUH25L^ݒ!My4X(`A'P> E12j, rl Q`)="RsKlox~{ / (UǾ}" OCQ+ȫQQ$]4 KoHL3$.6&P[eή6sJaʧ,ÆQs{5I- }3i?ظ=>_^GK- O-P4+-/עPa#` [,(5|x+xۖ{Kbl%׍'fL"#ʧrY- _D|YBO}UEJp KeTRG|鏇]fP>X KPZ,QF2V  찴hJ렝)/bG)6OZ AYۨ4ʷbQ. O$7ʗ.FK8’`  `lmHJKፁ K,l 8aV\BQF2V ],r|k `/컪T4r?r#<,-X\Dr|sPaI% qp"'Bْab{0!N|*HOy zSIXJկaRq(|y- FEFal"7ʷ%O/En.<۱ iK4.-X¾}"Q Ŵj,oeB|bịExet%RadA 'g' (\,X(=kvOmXe5 P u [XbQ@f]*V bJKbŗ're{H;Q'aYBO"-l1[]*}U*Vx!X_7WO^WŢ\}o(|'4,kQ򩯠 Xb= `ąbwj~tX@bQ*ȾX}b (TX@^&X YOb&)7"ED׏lqp߳b1K= *O[VP`qp߳A&!X$[bge *Ó(Txlw]{Z,Uaߣ}ٲ (#` =(؎I=cv P~(U(=g (?,(TX@Q&!X$[|P~lqոjWW껭 SPaG?硘_BeBE\,ʏ-tkj*,a-g (?SAŷ!bhMq:V/{6*ҳCf[,VPa嗍)UFɖԳ_)|7\&w@q I-p߳WԳCe- P~ {/|$[R÷ rU*a#D9k{6K@{!Xpߓȳ-Qh}ϖ2KXJ(ŔiXD[^̶N؁;2}m Ay(xVPai9nܾ-94R|_>bUa %Utpߓ( (TX`,Q`m&RAN/j+/-峆XQ3r-gK0Ӟ/Q4P&"=;ɐ[]BϾd yll74XPzKOF1j*,sR{D%`ݷvQWI㺥Nz C[9,HyP3[|YB{sW qk2;,Efp߲ (s". {O;,| U)\"t}==G*SD^ EAX%؞-|o i UhF|7cC\~m=)LY9RUkxPLF={5YX80@Wډϊ:/u{Η!Xpߓ (TX@W`JLZ,Bo ;kɐ& D|s[#azt=|կ.f{2Ӆ(TX@WX'1i{Z,*5j|u=ϡʧ sU{2(TX@W`ZÃXb=G-6-'vQ[<{^(Z,a-pߓ_BO}fȊ'HE-psbag;CU* Sg3WÕ X~-pߓ5!%E!{\[FeX.l"zՃ ϥx!*,|rn0WDCaWPaS*ȢqH=[@ԝf=_bU*ps{΅WA{*DCZ,psEXdKo7zRx&59)*,|j0WX}NwF?4G\qKRzD-% vv3eK|O-[,AVA&ui[֢Pai﩯" =7{ynl'CN,UX`C@ ,kQ$xBIJ`c}SwkQ,O,5X{rn'=j*,=5 RD-pԝfoiyf3zyjWX}Ob j,pW`PYZ,Bo 2U@$(É*,=X0,Z~o E_E([&+XXb=O-.N"JrIH-w)}S؏{2ӷFʧr:Ei{Z,:,/)Ց =2)IN Ê}S b=4-kQL#HE p%MS(ε^;UN]JOa1=[@<Ӆ(TX@N&!X$[zwlAa%V,[KdtkΗ'=O,PLW`!h%[z[\^=`yObRaS|{2eT (ߋ;-b{wOXƷiʋ|n?Ğ}ϓ"o*"'C1F" Iw'}χcƱ*6m( 'z <)װ-| 7ݱ (#`q-;{>`~T* ,`b6Ho4'=*,|RY(=\P> Fʏ"[dKN{Ynk4\ÇJ,p۫~Pj,p16/һ#eNT<^Cw{M4$ w|چ`A}MWPassPa?zɖb=jChEJt Q{ϫ+iVT}/s,-e(ZoX֢PaI% "7Rܳ%xeTR&(IR 3oo{e9l)8)VPaiLCH-5Y+T k4m:ğή{ȝp# Ee- #,r#Eč{Kj'Cą}g|tu#^0r-}sҖ}Z*,=8,- 7Rxk0뙝lȍ ٚ2G4W liC᧯]BoeB%ɍiʧ2jMJ/\ZG^ gl۰Z*,|',-g (w6Xy ,\Pɶ^hYDU'= l1=[@4_֢Pa ,r#E2=[@>n< L4I[g +U;= og)5 2}pC$7R$HZTljfzӻDhSvRa{_lAC.F2"7R$%xێN;Dz ^ 4[lP>Ŵj*, ,-g (daBtQWkJ?ᯛ<%~r=[@<~T (:˩h.!-|ì{JvǍ<y6s[`]2Ig (b (?ʄ:-b|P>a%!,ĥ$Yye/qz-p d$= j,p Q&X.!.}/ f=_rU*zoz8RIOv  {2ׁFʧQX$[{l2W"z_"&P>iBP>tkj*,$,гOZ>`[rU*hu1iIWzy([WPagPG(_{Il)f寘:1ή{kXbP>Eϖ(TX@$`~-?Ş-|ìEJzLc`^ -_S'CQkQh}/*IԵ|ì~J)zL5ꔘTX@ r'C1 (Ȅ:hkp G VsU*$-{=OIg ("L_ KɄ:dKIˏfRoUYm ۓxckI}"LF’KEԵ|E-dȋo}ji_`҇//|e D XJJaRa(´kj*, ud%&Z~Z>"iN[R>f=SsO/alH^$-?\V=PA^B{Ȅ:-姮}/QAl+U8"Gs]ASaq l ty5 FQX8[rIˏ/*^׉ẉ {w HZ~ }ObT (0TA΢}/[y^J qzj$?E HZ~ }Ob (Ʉ:-Yܵ|ElkxJ|LI6'ٶڟOGrN(<"i5 أHt-{4[,' (pUA]ˇEA^BOv gsIO"*ā7ޜS>"i᪂= 21E2"ҵ|ElkxJ1ӷK>I˿kp㡈ӭQsEܵ|EӬEi7_2i}/*ȹkp㡈n>[֢`^$;UhkpߋQ-T% Kf'-?^V= 7-kQL#HZ~5[U*ceʣmol_[IX-]ˇ ta5 Fi۠+^8$e3n poIp\'Cn/Iclcot9t?p(?S>tva pߓ^Q$S?`V`=|2zn>hBGBtyUHTERK$(8 m`5 X2tŋ)rK÷8YUB^آFtz"1"^L|5XྗzPY?(4Xྗ?ꃌ<]}fU"]ߛؠ߭Љ_zJ&‚^<(ߊ:1-kQ]{/(ll!Zz[hMV yX!E*Ejvט.{Z,aAE E3s71fZ ,{ RŢ"}/QE&wvɭVmkE{k (Z,X>㖂=4 ( PaHEAE^*0*Wku5  jUX@b lP>(TX@W\ËX۳O-e|J60rɃ (Z,`ʧHӮQ UX׳O-.E%^w,}WaSE-|4 ( P~z"VoUe>?y(>4Oᾗ"] Z,x(Ҵ=j,pKQ&!X$[bྗX'C|wtK^teO]b'CZ*,(j$&--{Z,Nۊ!'r-wk~ʧt߳C  Z*,|+HCEZ,Jʧ<{)l/+|Mt}/}/QE/g (?:1-Q i(g (Z,v^ ?zK{dXM Kש"]UK硘6s_BeBi(g (Z,r 6$ݫRl_Pq+yz] X++=8+FT⋴XzD-UG]Uw6;}< XI {)_f Dd(R-ԉ (EPaİG`2zѽ)wj9 a4H|EʧS/Ҩ2#[@b),{2ӮQ ہ}d =|2Z|OxJ"žyqo&ESe؍c-p>SCfȫQQ$ۡlaq[B f-v2$RL-oV3쨢\կOa}"pߓܲ 25e ,GX([.Q+gx_5>g rU>=i+(TX@(RUIl)=| K^ ?R![o^l}VMj(U}/G;G}ObZoYBG1-0,,nRfM<բT3X@1UzߎSCg(TX@17ܷx.Up J?LAO};" G!쓃~X@ߗ2Y|Tuqx|yǽhL`'#Pg44`SDXFQ!^A_柝nd|@ T}VflUT8ܜ2Tgh O܂{;Jl [NU@r 8{<+ŠVm)uǧ֋O^ٚ }rzR ´j * 6}^B>~TUc  k\S`'3d-F\=)h ="s͵yu RH =C L %1]~(yn@Fgf;e`>6E2p~$y2 ?+d5  @J=ɏm r_;*ߦ>n5Qss HrX ej * u(#vF*'q@u*){e9c?$I* un˸8$O!ͪU1ԝU#(0+2ԟULpcHI$Y:QAR熌 ~x2ӷۮƠRwc2>5^q*Yel͸J0* ubdH?zc<i)d5~bxSi@@XiEÜ4OOYk1h ʐ~VvxbZwgUݍ\b)2r?S ;B G3<e;zj * `ϳ5^ hqY-cT}pϿMq8RG Hrqt}5:s;33`jղUK8ǐñ~7L! hU@@dQ.'9H=0y|q5:9s TgH= "Z“̾1mVK'-gH= õT@@QF eHR!w<`Io]n]!ruR$eZ "w{޶J2X|a(IZw A~;"+xX8S)&1yo] BĞ0&0REJ" Jn& aj*&,&`zݳ;`2Z}VQajOV˔v3mo_no =Lzylsj*&,&' >Si׌>6sT:o͌J(ŐM{auWX꿲#=O<ծb/vyb{;`2>w[ڛдq%{iSejDTR#>wj O`s'kAhn_~q#p&'qrzUuAR]ihda^= s< 5ʙgEǝôj*&̣8i=ܾdօUO+=7U bIlt'4Z*&aL8O\ϓL(OPtxUAy#+E}%v(TQ󴣱Iq.b&r9{qLF9q7Uߑ%`Bh=DGUF;ŤIq0x5Ld<)GϿ@e7~hy{~>.CqblGb&y"vۂW0_;Bё_Γ{< HٝΓ XXD_ĆW\o#s DL8i TL609'xI>`2Z~u{+yz#Ca'~ 8ޙ*pa{i:& TLNс<' 8Qvqp򪒃 ˆkbE>Q.\ 8Lzi̴ij*&x'&' 8Qv/Y.Y q4, 4LT O`lk5p$Γ&"<17 ΦZ޿Fq6>Vl&4Z&eс=ɬͫIreztY;w_ S*FjkW!O8 WP1GGn< 84l;[~Cľqv顼qs' 8>Lzyʸ 8$ALJ͞'xR]<: OzWa_bv@B#Pd5?-уIgym=8aV 8><: I=8x£WϽU!Z8{?룶yOzz8yLւP1Ǔ= Ozj| x-,mBY)Zt=&Oz ۮp47ǻLUzHM,~+I?0 $6Lμ:z4qu TL0IØpt=>`Byb'- 8[_`򢑜zq!=\ut=>${;/IxC:3P<ǓpZ8{'[2rߣ'oxw}:*&*bsq<`ʰ#j*&Db O|`Lr$ҵ&UzxwD?&qaz48«Ah n{{ GclfUzCxeA WWPOy@2y lØpdiPc mQvÍS/a|wȼ |<0=O<yGb5p|Ĉ#IyƄ*=.Tf7EE;iCY+<8r*&M#y9OJp<5&VVj*zj@^Oh.}A_[8>I'>3.b2`7/O ~DxL xI\R38>*ybqd9p|yt\t0fp<5&x3W[q[2,sBp]o}+JN1yqpV+Ạ#p'xjLvϣ{hg;|@h5cHZ?5fp|'̽baybz17dH̝p-as)&=O4nc9p|8L8Olp<5&x7W[A8>;׻_g>TXuW_%SsI8y>Y Bạ̈1*=쯏xr+޳R 8ɉEp<ϓ TLmƄ4Ԙ[]x7u4t%Ix*yy8^ac9L1r5y ɤ۪|pΕLJN1yq0Y BŤq54‹  q<0w܏؛,z5P|=o'qefݶP1)s~op" LXnX=oUչ+m*ybr=ma{9`R8Kn H[xk4 }n->QT1io騾 ;8,b7IRDny}ULtT]ؠkA㩩ԅ]<=Oe2=wUIr=_gyc-6p<wua{yf{P1SS&Qdy'-O^D*ԣ[틢#Kgi|g2]iQ}wQv 8a TLNL8ORpw6iLDzؓc$;H{ir*& ȍr' 8l`2Jpt=T<: Iy%1WL7G,[4HOG/1)=O<Wa.b1L Iy'-;L d9$9b@3BṀ~}]x>wZ=z |d<a;KM~.\:x&eTA\cJx/ 0Q}u"z |df/\Bds1&'az"]ͷXLzQMtT'x0/ 8><:\hz"KR=c{PP~|cu|,gdMp7b'Y:/<8p/b2`yb{ {-MjZ'u;(DB/1Q}Q)Hq a;[dr&'yzR,}xB[]RT*HROG1ANơՅxd-p<5 byLb>G7/?Hq+O4wtT_ԅyqXAb8m2\Dyz`By2_lm^^B[w?=|uaz4qXd-Ld\th8蝝Ggϝ#=8Yo TL;ҡ0a='2b*=|5x쫻Y>dmΑϒI w=>w<ac9 9ҡH]س;g&}J3ֶΨt UL;Y^;۟t=>w<k`Ixgexx9#ƒ=J;%>|툪]"nO{T1Ǔ/z}sfVP1[GG0wx`2ߵoHx :&xE]w=>w2A~S~c 8t0RΑ~b􀣱6}R&sLJds'0ý |yt}sHaz&XGqMs_c<ϝìr*&x_1\ Nay9pw.4ףM{3kF&}p=wI3N!c 8>QLX]ϝ#=ֻlmo.?XHnA轱Ftϝ#=>\Յ;Y 4LsHCuaCs#*=)mMXl9F,ZtiIux8ϸ 8>\ԅCs.lR4Z*&xҡH]8;Gz|d_x{'"R_:;hI¡qẠ#pt=>we0 ݖ2)T\m5LsH5x8` |\ytCsH kWpNT'C7 )|)/&x1A!NׅWP1Ǔ= Is7ggGUAޯ-DCpnJ_O ?zsI3!ϸIxO:t CsIOnr@ZRO;O/¡qXd-Ld'??qUzؓ>k]p+w}0IxOz%&]ϝt]x5QLX]ϝ'=>oo-'1ni~Γ/¡qhuQ 4LsI#uz|z<|^ԅCs'0]_ BŤq2`z|z<|t=>w2.<|q5p >wxQ]ϝt]x5pytΓ{7s޵48KyElE;Oz||]ϝôj&^LX]ϝ'=> *=Xi3wX.; !Γj]ϝCմVP1Ǔ= IsI&گCտ Ul{֐/ۉT_wΓ]C^ BO:t ;Oz|nkWȭ2 -EmckxyU]q&y 8><: Is#_~<"=R3=RdnKmUk;Oz%&]C^ B(&LJΓ4&ֆsJ w }ZIϝ'=>^Յg8$;Z &>wt8Tf=>Bi;`2^мZs{W1Ǔ.# ;`2'kẠטD# ;Oz|v@}IIZMOzrKJG+ GX;Γ.w2q 8tAL8OgsՏ?]zOo/ƫʬsHOuzyf\BŤq| :ԅ#< $M޵GvTV1I$Wy'e|ḅ#p'ՅG__pд0t>Z _b{{V6j*&񣘰}ϓL(OfkJ Z] @z| G>w2u 4LsHN#uz|sH&_:BmOw|+E?4dzv?s'0T[ BŤq|2`z|sH/4NjDt7Ƌ=ySkPsHOz4qӵ TLVL8Obp< 8tQ 8!O b'z cy'=.<*=7nhu"H׿bΥy'=>]x{O㐧WP1;GϟDcy'=]ϣ5gH|׷sD @z| Cqu 4Lṣyz|sH/.qNp<ט Os'0}j*&xҡ;&'w7E# @z|A rL߮պҲ)_|[\23oW >w|/spw|푺;j*&$&']] = _nhP/ *&xE]8u=>w2=ܫAIÄxぉܟ*=t,C]bIIux8L b2^O el5*=H,ݔX(Ms>w|QL]ϝCݟaPw뵤G+4f /x`iyHq?XH-?ߌrES;Ijd]璕Ō\yt"OXO.pdo%4顾\ѳ÷cY_Nfϓs<|ט OsPz]xzOnCLCObyz<|qlaRT*tkKnlyyVsI3&sP^CL:GҡL]8T]<(Obm%4!Vq3R|Cr]|"_8;\ bI&Okc94veޛ#9Nu~~?dO>Ĥs|4v)Cl1q0 bIHMy;y˘cb|d94nNmi:"JK?5~]4k1Aǡ,Ej*(3a&y754>Վ|ﲎǺ1.ZSgL>w2Yz^CL:Gj*(3u8OBx:&d|Ф(<߿M0[]i͓2A 1SS$&'Qo)OV=Z_cɯ>h_Χz>MNac7!&xj*(vBM$j?a2;wžuõɅžSkh7ywI$)ϝCX}? b 8<:'$iI\&=ӡy|5;Na{7!&xj*Ą$0-յK>byqX1dhIYDz-w͜q9 0.F;Nơ1OMsΓy%CQwkz HLscjGL'q:bk7&QL(Oqhx&}$kO|hiw7!&$5&Ix:&1sHg&t+$SD׶{A 1SS$&'F(OV=b*YYI25ʻ0|bL 1ў8Pm{ 㩩ԅiOh?a2w.6!=OQ|_Է(t"HC :q6yI֞8(& {A 1gGg0<'Ly`E)-!c?G>w؎ɇ<ў8,{{1>w<:oz$sKjGL'q뮽 KƄ0)'Lkn\T>[)ގ}%wkaܥ6C 2kO|d6j{A 1G'0q'Ns|:(OV{cߺ3ãB@/ΛG$`)O>w2A 1IDL8OT]"=ެ .=qzx2ge%c'0Q=>w2qoe7!&qU]"=ޠ9sv2u+'N1)K&5#&=O |d*&A 0)K80<_sH&֍N_'&#qXS[ 1m'L8,ϸ<: z|]"=ެօS"ZhXr@m8^w%:&OIx'{A 1< I<ǓLfǧ&=g$i@k rpM^'Qýps&y'=otVOs 8ޥ&}D㰁^CLNL8O 8x`2OŒ6r_ktym}ӓ7.y; ;\ b |yt3Dz]|NMzKe)z5Sa5O4fGb7!& &y'=l_}WҺfhmƣ<쓚J%Gv 8>&}DO㰃^CLAL8O 8x푚pb>.$O' |d}$vaD: _%H/:˰ޭX)cH[2=}!&d!O ; ̣ט Dz] &=ԛleUO!&f0<8,wbOyy'=OIz8ƻ? |RM<8_nCLCpa=pODz0\6zBTm?.o.hyjonCLYL8O 8x1顝&鹙?tG. ; F.G'0a=@/K[HLVrǾe}:WCӲVi6x%E@/qX b 8tIL8O%j>C +oJdK@S&!&3.\ 8ay7!&!&y9>oZ84!f^=q1~]&=\x>y9^ơ1$ ъ$OƓOpuܤ(<Qrvۉ -j;L:gc&0ŞbG' LxCxtLHv>ܹoxm8O<8Lӧ&&6y܅;{^#LsԅIx j>7A"8h>}O);TEHek&0ў8 tT`PUIODŽl`R& s٫!%VK.\`y9=AA 1SS.$hIvI4 =3e{?Ȏɇ 1;$OhO|xl k/!&xg;]]$jI&sH|şϔ1X^S]l'מ8,wbw2`y5Oe;x>Ozt~#5T)n0]c!O>w2˵ F.SS&$ycBZ60;p]L+6}~beG!&x |d=wb;UN'hXR0ǣI1\8,ׅwb]1)'xjLpV?K ǫϧOOwS6>w9&y~¤ Os'㰁^#Lsc f0<)Y&'yVʾ< 5/5`⻾zt`<ϝC^瓽 㓙%Jӈ:*Ԛ[ciËy 8=sY߆NNwΚ hb\|.^k(A`dSw[5])}F}*]y;*&w7q}>ELCQV;v2b$[f | *M$^'9yzmA p} {Mk- { \ ލb >˄:dlS7˓'ΓΫH=رwK;F.>aA™U}k7,@í*>>|W1l_Ea|/MN761< Bͼ~b͎><} t#,);u hnCX@ti鮡x%iD>h~4-~ ;+7nEVw{.5IfKB~_l/ClνX4,Ni$K.HA Կ{(R Vl)<(t/(`l- ,ՓK%x>~]?>>'18.*5d ,d(ۻw bBȇ=5 _ů6ݗK>Nϯ:)<(-Jg(/!,^̑ҬBsznԉ+NvI#,)#<eC(ɐk(N`l[ʖk,t_ |bR}LƷ/tL+ f ("؍b (< z - 7+ʴ< kp㹟4mOK5N=0X>q7!,|%p3%dˣӦپp`/n27S#׵я+l# ry7,p+tEEW~W+}mcn-^NkX4[@<e['YX$[!x%l ph|5㉹Mue8޸8.@j-| \Aލb (, Lw EEM`Wny|z6..+YOC]Zv2N"٢>+V TL}ܙ5V(3 ^CX@U tJl\ʲbqzuׯ1[&yUA6f (LvO2Nb'h!6ߺRiHtBM_}>2#X@O_ 4[@<˅(LSHP>5-Tn":ʠ2z+|*/k -| nCX@dZ0 l1-|k[S*/{ Wdc-˓E1~y%˄:d5Q[&w|ݚR1~nx=' .d<ʧ le r;nCX@E&Xd`l˓X|i+ʧ=lf ("S^CX@ԑѥI3V{ U*a֏r'-sX=ejKN88a7lS>4A<iUۍbP0Tٰ BOЈr?2-_p z$>dw~J,)raєCVkbQ a)%sS$kBْ~~>Zr /EzwF ={^< G4>yfGQiXdQB 6ޚRQovst^:߿g{6Q[q%53,ȖׇbMz7!,dbB4^%`)'Xf[oMף9y 9%tXh j 7A%P,w:,u̖ 4[Bdx4l:֔ y g]i|>ʧ]ofK\e~nCX@ԑ0 dKl WC̵=׫Iݰ (6 -|+VzQ aSG*(5[@$`_Mhހ[ K]d1Łi/ lq|N(F8P u ɖthD.|5){ y[\-y5O (_Ò-OC wʧ0WAN-)hD[\}51~+_Ղ]Yb9 uYׂfZoO;ea?iy(:,{Q aSG,,-YO2x\ujJx!/V<3! (6Շ rlPunًb (?Ȅ:Klɚ-|j)t,$dX27A~%Y [7@ gP>_] F1dBEhIa))(I6z+|AAd | dP, F1Ń#a"RFa!,"J9mc,Rᷯ<+#X(,UjCBP,wF1%L3b$[re})=[X@t5,ڐ@<vQ aPg`-6hDSAjJE --[C 뭀0pUAڐ@4Xϖ(#a(dd K7Ne)ߐBZ~PD:N u[D˷N|2kՕ o/>TYTlW̯TpPKjlQ-?t硈gxF1PgE|Z~(.NbT7|,S!-򣱧(FNIXD˷N {˽ A>й?,}޼GXRJ?Âly(b &e/!,db ,$vJTD)<j8zS!, ilUˏ­v)XPg`-ߪ;ӪŽ)_R?Da^-Buq^哖?fK| >E1o<,-GP>iWd˳V[dd<^EP>ioUˏ| nCX@$`ǩ -ߪA姸ܛR~C6J7](~c3_!, OZ~ [(b(F$P u (|2ܛRs}rEx׃@ǫ}Z~P,"P uj OZ~Z=F~:4{}۴.ia (|J?4'P>E OF1O,,-'P>i"&(m]95il]tJ,Iˏd(bP~ uj OZ~ʋ{S*t~8W!,|eR,'MF1dBEE'-?[O9P_7 '-3(by7,O$,[3( h|N\‘[LDg/eۇ '-?^VUϠ|tNE1Ov TϠ|˽)1?x+^'3(xՃTϠ|P~ u'ZS-?Iϫ"w=3ԟ`oq+)b ( X4[@4iP~ u 3(+ȳMş _;қl哖B-N ʧHge(IE|Z~哖L/_&V lh=llP>i骂T/|VF1LSH_@gˣ;q} '&&Et;V@_âZ~P,F1_dBE|Z~哖bqR*/otu.}%~碌wP>i骂T/| |z^CX@$`"٢Z~哖bѝX)vg?̟4CGg7\1S%-?]UjC\܍bK|Kv ;jS%-ObTâ)_be7!,Ȅ:djʖU{GS*ܣmRn|ʷ5G|KZ5,ay(jr7,o{a.DwkDtK1=n4@w2xsF=؄ϰ)_c(88lQ`)`ɴ2mb&_'M@y8֩Z۴0S7 CC9=EZ_E1S4UAv0,AS%< l)5e@]UpZk i1 ,|`l/C\ߍb (:fal -|sYΖVMeO>{@C]=LJ?uʧQ aPg`ɒ-YO2x =hlgtf ("lًb (?Ȅ:eɖ'ɎG?c@а}ґ= -{Q`z?f (bP>u$P>eJũ$!oq+Wg2{щ_o`gӄҏP>rnCX@ԑ HP> $?'(Uܾ"/E',p߳t} {4$P,{F1{6˄:d)`lY~v'̏Ký{ˇ=>E1_4,VzB28`uv}v'~]KlBl-|޲P>u$ -VO2xYgW*p!,a"؀e/!,! ,NitwlT'KϦT۷o|Ν#X ` O514[:P rAލbK,2N{'4[ `ɀeE :?Ogm,p߫߄ҏق="dve/,psԑ*K=g2,TPou3psdZ>x(YU'wLSH$˳)-ԥt سH=g ILۃԺ۝GmV&T%nl1OZ~:Ы=܃P> eE}.)q5ٔ۳-QZ{ի{o-5o1ps嗫 W-{2˅(F}9P`lQ-{^K8i^n&魡?s?-ps_âZ>d(ϝ܍b ([Ƚ]^|F*51({V*z瓚FI=CS> Eh]Tj܍b (: *ȟuŰd,)^䓣a%^?{(| ~7Oa凃+X4A<[lٍb (?ƎI^`ɚ-@ٲ;C^tR-cίȶq}υ06$}Ob5|7!,|HE%keKZ^ 7P'maH=kpshBǕ6$}Ob\d7,ps@"n)-jDY`T v~d !2nH#J?Rpߓq?e7!,:]fK Ò4"i{ⶣWS*#Yڎȩ}{,ea 'CWȻQ acBCx$)@[aHX^'rUAf (Ob{Q aQL/hS<2{d۫)cs&?6O媂P~ .ٶP~>z dlgڵ+ȳ2ث gXdj,9{ Y\b-pߣPXE(F}ePg`1-&iDfY5y5L~4"WYRleEϗ(tLq-NS?xڙƓViDemӦӡV5V|yS`l <h =m7!,= ^-K= `ݗjJŷKٿq{v#,ASS El5'({KWULl?U&oO1}4KD|Kn (Ƚ3};{MڣS>M >0pߓXm ߎb <7G"GNw$Ԁ,Tpy|WY>KPF;;_]J?Âl EZ0زhDtJ]?1ջ6ɭ#Kw"p-pߓmkO$P~kiڝ=Ep4R۰1ʦwAPM <(g^CX@(_X}X&+5П'wc+8a凣 aєPΗ(ʽWHEV#ʧ^؟l߫,Jf}_%#P> Nz?E1"8}bOiGS*[p}5fy ,Z@+p YʏORu9XȼnTYj'g]|r[_!,~1[<(¬P~,d%-tܗ!# ك~:&|ְTikxhi(깓Q a'y x8EO wOVk@T|Ϸ/.¾O %O}Obu#v#XW}BE9XyN?'VK9l6O>$= ,pߓXߎb (?iXd spԝX&ItqJp5_5GsngX4[@<Q ab%d ,*vV4: )#Kt!,bg`|޲P~q/|ESC׳e/!,q-r"E_LS* TO&ŗz|xM}/] \,,d(zkpXb&["}/ڴ:iR)>U_zF?nbq  =U3(8z KXFX([bM4"ruiW=q([*Gy(:,{Q a=O,E-XIuw~H;D5~lK,ןal/CQVw’K(&NFq^%ʖ3M0%8܏ `MJ?4[=s)?Xyr4[:j7q}wxrfN ![l= ڱjE.{,yv_~ (~pD胹y}Qw)}"{<vՊg;!,|j1SQ\,"\,,u'ŎPvt\W;_Cd bJ?fKlPP u ɖʧر_I_pe˯W}"ʧkXf (^CX@^&X"&P>X8:Y33DaEެ?7GYjXߎb ( falI-|O_LS*u젰sFdUs#Xಂ),d(VxdBEN0p eo'N=~$Jb-u hXCX@`*ICuXʧ{K)jp $^ێlS*c(,u7%Ofhh= [aًb !LTˇ^,NzO>=AOƜux,q d x(ry7!,Ȅ:djpߋ9[湥|R逳AM ;EŜ#@z "i֤Z>x(]AލbKH$,'IT9+ۮT &lu[fÇ JwG7ubIwWǁ&CKQ aI%AWK'@XUˤ~ y+X^ڕs7>9T9ޤՈ:,[~S*x2}WxEBLs (aHyP,wF1BPRf (j6faɴW+4&v\|ۃ JJ?â硈딿P?N|Ζ(ْ4[@) ~w@諚,|Pq4[@<nG1G1KlI-|k0s RA9" XS(nCcs KS}Ob5|7,pߋ{EI%(W{mS*|tdR?綂><,d(=_v'ӇIXXFOW,+1ה Y??녞67^ }/".+ێ'Ca P~w3o;JڝHg{7'.8Y<&_3,|ERݥޢpߓX.F1ߚ#fXEONX|tM?٬d էnK+")p*F1Eɖْ;{l58EvPyt y3;XIwW䬞p؁e/,pߋ |X%hDrOZրD0)%x(|/5P~ ubˇdv'}/@X<FT}pQu "nOD˞!,|RUkxVPeϗ(9̬aXFOW,O/bhFKq!,|jW-p [_E1O}~" =Jpܒ"PF ۸HJL>d(6`ًb (Era? H e[27o~=v?.}P8<}/ҹ,~*d8P!TQ `qpߋ(,A``lY<ԜP$qzྗ\ UAqpߓ谘RnCX:'jS$[pƆ^)_ 9,8{mjOa %4[:P,wKEiN:$TH(>c9GMsy4W- )?ѹfK@kQ a锟QOl-u'UO/'掙ªߊ|xmp}/ѹfK|:XTZyW }הם)n_7[Z2 R{2݊gD(F}/Q[qƆ^jR!iC«kJՄI K{ΕWf8P,F1O"~caI`lYu w݉PɨU3)E->S?D-f (b(IS,-9j",}S*OF@;z4ddHڏe0D-װDP>ŲnCX@W:Xʧx,NTvHj:p2TAHu?v +{Z,Ua?GP> EX|ٍb ( falI-|jy'_FV-M&1%߅ҏ}LObQ`^rJLZ,rQ>]%.[r,Znp][ as,Hyqv"_;fX8%Nvߟh tw734I4O-_3Z,x(²nCX@1HEΚ-|:aЗZcOe$[ Df-#IR{=>ʧ,2nCX@ KX$[f (s+VKZ6u@ 9v (?_Ċf (Ku]f[vʏOȉhD+ȳ[|?iepOzޖ*ᾗHW'R8PҺ%n;}/щq\ΐ[G8l0({^`.~D =@ދb (?Ʉ:K) H{y!7ݸ 쫹xBMT{pRP>EXv戩 r)lӉq5yY_fZ֣ムྗર_f ("܍b (aEN(FO'RĴ(q@>l^&ӃpUA.=hVe7,ptD)^98eSΰLNb)JlSzv]Qږc QL ᪂\{<ezNٺsྗ!VCS*n*XJiL4[:Pe+(LSHDP>5$@}gi5&uR&6vF k֯?rKlPlًb (Β%JDP>5^tZ~hJY#ߣ]^w de: ^Xv ߍb (Ʉ:dKl;])LD4{0l rkn#Xྗx:$[p"_%KhJ7%as,HyPt+}Q a1 q/'RHྗ! l^hJE}3>Lz;*%v?' $L'R>Nppߓ딿P>*r"Eɚ-|k,G3Pg!EGVW5rzP>H1f (b}7!,|2}sd9dP>5-7ӏ9k샿EOP>H/ EC\ߍb (L HQf (R TT$r~p_w}/Ӊ=^#Xྗc[)_Yo(_N(%kDtrLObTH(wє{{ ( $c5d(K8)/!,|:W#T-]uX:"XS*mװ&(yƔm3,57GtP$[;{ )]9n8|q ( .*5P> ,{Q a_5,-NS~! zKlJŹwkc<ǔBmװd(Jl7!, JIhw*e!)6޾5s<}Π];XrJ?â)_y(jnCX `Q̰h*yc?6.`~?vo&1jE[,WZby7,p+W7XB-y=ΐva,״C=,HyP,{F1Q̼spI9a2?5;s\,b+PvH?lq[zPE wOa)|7! {(H3%}t4"ĦN//x2FZEV?{* v{2&/! S#:>&yNw@^-ǦJSa}gC8|F;KC*k~ܭ,;)e[uS;a5|P@ߖV{CV<B Ҳ'0aP3H2lFOWyqɋum[`5,F9x_p+djpzxx ,;0Nih*+h4j$ɫM߱im ]$ӓ[ uWA.W=e/Щ$M(r!t8(*<O-}׻7lA3:fChv!tOө#4{R^H^< V;(3%d2"/B> B=q! uliԩ#ʹyR QRjYé6-cs-Qr`HxWAg0z=7u{/(8x u/Jvirk5Ѷz>! u^HxKpAXd7! rULx:T{3yIY,+-Jy-(VC7zt B^_e0u@Yc vWowtwd8u-gLA/Lχ_CJlZ'@4C=BY_e0$W;ѡ)HYq6ql6m۟,cS`HXO9y01$uHK#OIY:&8vRMMO8O伷bqړ!& v:"_tʛCӤ8ek ٩WcynbҋSln#tI#&ȓqX! bIGg0<ց'uDow{Ǿ|7NNBۋb'< x ya y ٷtwTd0n_~f 8>va<8 L9xΓy'q$w+C4²3l2?olS}ǧm~ʓyqX.1d$pp|p|<8,w1_d$q$p|ybz]x+7%>^7iE7O>V4Ou'{A 1ǗbO;x^C1\ Yb9ސ@S[f2$r=4ד=[ t7 f< 8sh<[1Zxvb駴PKN?IxѹNssA 1DDo)OV7-Mz?d WD] kwV#O28a~7&Owpx[ysD<:ӗ&=>ưgZY3>ڇoٱ~`\xec &İ*}ǻ'"=@7 5]/I=8>} 8u#& qX 1 te`/x0ݯXzj]M SBnm 8&y~8Zup<ކ 3U3fz~U {h "J-4i> 8ާɇ&άs;-I0/x/z{n#L 8>n{=O&z|ClF뭆[=)HxkL!6#&7,s^CL!y$}1t Sk+IQi﹎Wi{1$(h&0q@[^CLў14wq `2?wp^_u.5x-\16#&'xs]{A 1A`y=O=X˦Oc':DNO:jQ ÿ;p|2M{^CLy #Q#aW?i&=إbǟW9ՐG '&ǧ1'8>ް~pS~#aap<]a2_']4>޼p|> $ p|abφ>$m!\1Z/ʷW??#v{pp|MID xcW s f0akyϔ'V?{,S[7 55$zK洺L1_ L 8>ް b 8YLxL4O_0Gޚ PNA`v曹2qh 8&CDWp|/$I'aWoMzJPz=5j)01R y/7򤦵9t L`L `:5M{׷]5mOLxK1sr~7!&3&Nf\B0.aW[NuOq{s[~$d菘dyz]8IG `9 >\B0 î{k]RP%U79[msv+LzqXָ>^aRY4O:[Cyr>Wo]3m?d菘htqHL4&m5O 0#v7!&xj3uaLJh8yrkp:>p=B}>w6M'^쁳p<5zؙpk?p|M:8ǡuv b 8>b"O|dLL3b II~g>Ϙ2 qX b |*#b§i8sgYąE>j҃v9\`O1ANag7!&zsgYĭ5A+l2?޾Sic4O<1O x..VPoRB g5';0jwYDssA 1g߹*Ob`LL3z~S[_ЋvuZ~r OV:8>ް'{A 1"O?qkcb oY-ÅjO=;Wuas'|pn#LsgɗNsgK}jIrv;= M |'qG:|dj r7!&O|Muk8`#R[FjË߲Rpvۇo;-l6CsIxG,Cv;eo twN{$s稿@1óv?n^ʳ7$dOy=qd/!& >^by=s#<)ݚoQ |~;G=h|xܱZ bIxgd{$HQ_=I?Y:U_dJk5a;G=V=qp}A0ϝ3yZ&sWNҸ_/djjI돍0Wj|xt/nCL:;+Q#G>wz$]w ۚi;C$4+:Qi p b 8<: Αw./g^jlCWԛ8p ?u#ᵿ>w| `2'&= *G 'l{΅.y~^>w2u 㹿a. s>{P[ Yq WUo==sK1\8U> b 8fz=Hx/ϝ# ɺbEy+}9!&xn kykA 1G; Hxy' WW;?ĨsM >w.voDϝC^d/&sfj}$<|$|HY?{L\Kl\-ӫ/k;ǭ>>w2\o[{wbOs&##sG—e5xp"?_>WtCkCLpWG>w2e}ݵp<'Lby stF8Mz1")?Gҕ -71Ǔ yqu 3`gmx`ϝƄzIfyBg9Qނ&Oܛ$|Y'xLb'7S|ֆW9jLgޛ@7Wz-9t |Y\b=qg1>w<: Ճ>w`ku$n/0^^{Ј->w>wEמ8x b 8iL8OpNEϝ#-;p߻&%۟5߸<xU֞8e ṭdΓy9ޓ잠{Hyl1|bҳ>$䵏htqX1$Gg0<)' P}rܽIrۦ}66V5K~\ϝ.1ў8.<{nCL:{j*äp͓k:ܛp 7߯{Ä߽? 0 .;~ Dyj*SBy|dV7\C8ׅ#|<1/1 )q뮽 tL܅1&Q阐{I-L(SLKy1p<1|wCsònCL6Mcyb4Oe8w= ˆ2t,˵>7\: 1Ǔg yqu2;w1X'{y'-;?+@R۽>w< Ep0'xewbw2`yb5Oe b |yt,,e^}?y4fOdVpz?Ǐ폶dI{Ldp'xLb2`y5O~G* p .,Yĸ>e5n Co;Oz`kirnCLbz|'x]^ n["kn($ybr~;Oz#|xB yLa;O: ;Oz<0DzOMbiv6q?mӶڥŘxtFyE]8@q~+A 1'G'0a=>@G񷓕b jXb'1Γ 8a.p|JӘpDpsC &LJy'=>p?ۻm7S25(+0O0Ǔ.!jyVvb2`y4OUG̭|J5A;ʛrRa} |<@qX b ||yt;Oz|u^G59 ۂkx>wyEws'd/!&xҡ'1<sI>&=@6j;Ɠ^@zd͓dOMzGߖW5:ihw:R7$`)OIxeo $`" &'E$ʓ[7 EZޭsHĤh{'A 1HÄP4O:cYMzd&ӎIJ)̖&ǭ^ |.z<|d;n#LsHSua Ugt-ߦ[no8>wpQNac7!&フyz|T=>wj䅹֨$)oY}.!&xz|T=>w<q mƄDx“]n}ֶSvIz<|xnCL`z|T=>wk&=`;@ѷ~q4'Ͱ&xE]8;췲pytsH&y"ÏRHz ӛsH=Qx8n#LṣU] =>ZI"jP%'DoVsHuz<|d»A 1Ǔ= ǧ*= \ IheuKKpsHuz<|d}$vb2N`z|T=>wx`2l-⤘--0Ǔ/jQx8,p1dDxSXx0,[ CL_bz<|djk7!&hg1a=>@z<0մMzK:SܳN.6>wxQN!a@:t G ǧ^Oރ&= 8V(45!&xEpT=>w21dz<|)-z6A[zKht /#L_cyqX b 8>%h}">w|"=3Û^;&xu=qȫDA 1SSA htL=a2{ܫ[_#FwN<>wE]8jO|d b 8 L]b@$p|>,ǿ`ٕD{ͷ箝>w O5H)ϝCYA1>w 0I')h<ҲS 9݂aн6_Bw| `2u::l=. ?Zt2.Rk@ :|8|n#Ls?. Xc5`/jQ?y5g`z1z<Ej ;}1r^tM >wRe>n"_tTnbրtQOIqX b 8yJG`LƓOȫɫIw n* ~Hz Cs9~/!&x%Li)JPcl:8ފް{A 1Ƿ L t`Rrv֫[_==b5e _㪫/i2u$Z}>w2A0]Z}3.yoդ['OS6k'$p>w2>ܻA 1{m/H#;F^F5={kzɾ;H=ט s'g>{7!&x H3I=#ZI_o-U讖rd!{x%]tQO#;eo 7 H$푀]zfKn5W-Ž }Ƈ;.RD'푀ϝF~/!&x H3u=I{$sG"I7]zJ{nʙP8|"H o; $hg1H=X{?HR!QmQ)ُ~atQOHqȫ 7 H$H$E&D_y*chޟGy՟aD]tQNA ٵvCLI LG"p\a5ta?#_bkL4O>{LbO2`y5OY0^ IKoo[uHzէyqȫ=A 1g{c = R<ǓgAqkp4Nn{[Þ1SqW6 EY'x$8Uo F.fG'=  |"yE/``O7_W3YMb%!&xE> N-1b 8<: {$$E,(/6*\|߯L{|]$Y=_ 8/ o1 spdpEo/Ȧi+ ʬ[tz 1)$6Sd͓2aObRIn{Qdς;F'?h-fdv`U}aĭWw!N!.A0]"w2=A 1q$r3P]>P,zIrV5q@~ dL:'n C$8po1Ĥs|"]̿7!ss}#Nī i$sl`PҲYa>H6},es|\Eǡϸp?^] >wt >Hbneϗ\?D@ri;m|GLq(=A 1{AL IxI8a2i&=CEӺwϴ< 1sk7zxzfvCLtnQoj}4IPw)by1'nu YZX=z_37"ܥ%Ox |d;nCLwnĄgP,pӤkIaypGΓyϔ'qQ۫v9ӷ5r3>w 08l1>w)<:'$fPd_} [y!i?p|<ϝêvCLb8Ox:& 'pzkT:؇[֫zMN\L ůKw4ٲA 1̣3p .',VlߺqrYx4+q~f7|LV_4O:8Pm{=~7!&ILAyR͓R,z{pDzs{nób\_nfb9>gA C$Y.ֻvb9>^2Rس 玮qdOJuUo?ޟ!smjGL4;s ]մvbڋ]Ysm}̢J !x)t&vL޿3x$8un#LsWYLس .;]vI >w2~+A 1ǻA`yς xK4) I|8m>a=qXb 8޻>^ {y'^wnm#]_:[L>`yqXFb 8&r8p&|"+f|};RK&!5ykO|dV1b |ryt(Oz~G6遈/nCS2{(CmChO|d{wb2N`9Ot?|r'Lf¶ؗד}9]~̽O15#&'x b 8>fD= s0~+5DjVZE˔l3wEh 8>&y~D= s'PbO,YPԳ>w9gY6AY=/1ǧ8:8ǡ= vbOщ= z箂GoW=lߺa A f1&4&Y;sa,&YPԳ>w9&.㞩/>sluz,b Oس\;][Mz{?OZ'h .c0=1_ 1'|C >wuRڊkgi&&ϧQAs'3X7%].]4w|x̲nCL%%&wdp|I'Lfk[_q ~>b}j`u*0Irt&;1Ĥs|9 tۋ0P>w| t@>Jmxdկ1鹞s'V31$P>wϘ| Kx[IQP2Þ Bw!N31Ĥs|Z=P_sWW'Lf]kc((=3|GL4O:8,wbI f0<)'PZ}rMzsE2LLJ&JX6ٍbJ' nb1\CLqQ>]Zx~.(啌3k}H{y[甽(aUl\&@1_9:6xfi]N >?ty z)2Ndk7,+"`XC) %2 5KشBqNMo`S5,pXލb ؞ e@d KD(LlD#t0|OV ,I);~isQ aSׇ Sb2bFX$Kw)ǣ0=e))XSvʧ3]Cqf (:jWkbD{ ܻ c[P>ŲnCX@1 dlSӁ;ai~XbMW/{|$J0M ?2x(rn#X~WäII༇ B}e~{t9aew%&~< 2-vF1Rlq-8!P3%cQ%C2+w'_ʏfE,]Q aS9w~O(VX\&s| (E%)ʏ"GJoًb (?Fb&[|bX¡teH6A+э' (Ew BSOCa(ΜYCf (z[butrY`$A2d(kQ aSCP$[bЈ:,ԉXf+Ⱦ~â)硈Q aSg,,Q`Q>]ekGq< 7<'7`/kHׇbynCX` gfK,-(OThYRU;ߔ[4XWyxIgSMP> [>s7!,|n*#٢ OeuR" )r?D |K~X a"*ji((L3XN|2?۾Y$'s֘xzY~mI`|R'`ADP>[F1%[s,-ڝAMXfOTж{59mX@Ȼ^%y(uv戩¾q-ڝAdO{N{T¾ - O-~Д /k'bo]( <(bc7!,|+hrUH9*gN-)‹ch!yd5 ZCg^#X2(# ba;!!:䇦T݃|^ /]( 7rPlw)ߐ gW)ߐC0˓(b=U2S~ox)/wKEE+r,-vy, {H[< aI E ,rF/Q aoȸdZXN,,lE :5;#p~d?X:Ӆϰhtzba)E&)X$["|JwݤK 'yr#WϿnVSAǸ_ cboZiA{ܥ?=Rt__ݯӟowo.^fD?{^~Я?wi?Wm6`2!Gjn +l>Ws]*C?)/J-\9J;w'I?[xO>~?oW-_ԫ^Q׉~K_,u2%&HYJYɯ(us+9eiρI%XcWn0|R~QmEcTMd*V*1/p9M~S.kj2<$7a wZt5ѯ*fenKo2O7iChϮ]Rf3kts{-2E{ ~_wifg\Z>=g[wJ9cf][>.Y%ɩ{ݛ}<;xOԉM|o}Vч;2W}7;څEgQM"uhE`wN 2$GS-1Zvbeslx;u\k~B^pW];HvQ.oumJLG$Gd7qFx|I؞6W5*δ97om:S<5q#l6ߔ6B0qŞs V8E=?-o hh& Bg{3IlM} ;S\(r9Aװ|kߺwǬ13oz"WٷyG5bF1o{̜c6_37>Y]rrs\'h)^;3fafkt >ɦ=gI,1sjb .t z$25| ?ߏ`e?ҬOYr?UW Q_[&Еt&+?lX@=KFt=t.T֖C*NшX]ȆhˀM9 hpN1ٟ0Gt%'fC}T3k>Xs.݃/4qhLm|1h-Ԇ1mC] L\7v7n"AlxPw?mP!=76[%kϯ`.X{1v}qӰD sAއ| ʵ+;N N2(.(zP\ ) 5?R!ݍ;Wt}je5ça֧ׯA EҸ@.F9DVfXv;Q2]Tµa! 5FӯN~D7zFݬ^FEƾ_4.*6T ko> )e:5yƖz>huAµ6Op'_iJͲ5c?jI-֫<Nk~pS&?[5NTL4wpes]0E%\d(Na ⸥k\W%9ܟLUVu}S*A4j[097te]cfN'+a, WTպUwfX4!z-7}1ozSM$D0ozHxB`UE1OQx:r$rҏŪ&WN9ebvHb~49ap_L/oxX0?.1;,51S7[pfbu,S1Ucy,/Pt:ċM K}N Q{,s^rfB#l5 id{4 HIvo&:_$em{}m[~"wspu"?@l9Ȣi]ő*vN`QC'cӅ 8X4 # Hd-%-a6it7iFFs8qʚyM)H{Ry/f{Ao{_?ßo72J!Wjzr1s9#,8&*s .zjԐ2WȎr9%:XHc,]3W1V]7}残=0: \r]ZzޣE[GЀmp>uaR==Zt%wC1Anq[R Z5kV]t ok {2[)Ŷ }w޼;.5 _?-w=,!r_ϼ~Nn}EH:O]}M/* \Õ_>%sOW;W%fL]qfҀɆXa )#x'N2{?0eW_)a8VB:O"^_BE뀼޾׬ȥ /};|rnj*$xG *{-GL de11o k vAbc>eyߎ?k y[>tlVboʯ~zEeƸk l[Qm>(Em ~3Nmvha{G}e 2V뽺]nKBuڠ`oS4}r[5I > wJxҜxnp.+Ċ+EoH5Øu~aV ӯ?Quezfw{׿l2t! fwupd-1.9.16/plugins/logitech-hidpp/data/dump.tdc000066400000000000000000025021401460375044200217010ustar00rootroot00000000000000TPDC?7IX !!   ,\% X7IX| Of l @`.   `  9!-e -`-  > @`8%` Q8  ld3@@@=O2`@v`v@k-X&`'jk #7  b8Z= k`=!uA"@7` m uu `= uuр7s E1 u "a =j Wuu7#4aB@u>ruA@-k W `;= k 3@urC`;=h uB@u!>`;=,u Wu 3@u#C`;=sK+ @c@@~?i`;= k 3@uul u`;=, u 3@u-C`;=9.u 3@u#v +i'E. A ;F`=1A5@G = d @3K @ @7?@@`6@@`@B! % C ,@7J>J J `J E-F +@B{F @' @x@A FP`CE ER@aR @[=H = ` RJ! @R@ @l `6@@ awEK )AQ" + II Q`IQQ# >@B^ QQ@x@A AQ$`Ej6@Q@=% +@@4Lr@BH/{!' G@@ " @arA(I I `IiqB) B@a Bl@@x@A A* #+@㷀@ " @$5@B, GB ! Bi- +@a B@U  @x@AB^`En7@ EAkc/ @p@4:/8@J A0 .@-@,A1JJ  `A2 "@@By@`8@B @x@A AR3`'E ER@aR4 @=R#5@?@ " @eA NB6II QA7 B@B_ b a[Q@x@A AQ,``E9@ Q,@=-`@4+@`y@"A ($: @ ={%$!;@x@ @ (N A<I٣I $4 IwA= B@`BA@x@A A.R$%A?@@ " @ɥA@B$A!`a BA"A@x@AB^B @`E ]:@A@! {=S;`=J#J! D <@@,AEJJ A*`@B@x@A AR` `EԀ 0!H :؀=`SI@B׀@p@S@"!+@#րI R`SA!`a BaA@x@A AQLDE<@N QeAM +@@4*E@`@AN A;=h{@=!$ O )@ݐ@o@~@ API@J#J#^X .@ @,AYJVJ  `Z +@@B@x@A AR[`CE4   aR\ &ƀ="A]@qŀ@k@S"!A1B^IĀI A_ B@QB@x@A AQ``E}?@QeAa +@@4Jb ;A;=O{- Ac@ʀ@ @ }AdI+IXAe "@`B@x@A EAY$%Ag G@@ Y" @$5@Bh Bg BAi B@a BA@x@ABj`CEB9@@Nؠ$on ;ck +\d`=! l@܀@,AmJ*J  dCAn "@@BA@x@A ARo`CEe@NRA@<@\BA~ K@x@AA\h `E0Fa\*BCa\@n=\( {@@@V!\ @"@B B;BBJv aB{@UJ5"@'@x@@AJ|N @"GaJ@E DDE=m=`=$v~>[`v` 8$8Xn8_~a 9! =&r`v! <@$@'%B J`J M  +@B_B@x@A B`CE/߀b +aR @=R#M@n@ #M @ /@D3!@B Ϡ B B@BJ @x@AAJ @`Esb_Q J@-aJ P`=HH(! @@@3 @ HARJJ J +@a B|B&d @x@A AR`CEPaaR 9R*T=NH@EQ@H@HT \/ B@\BC%@x@AA\`E a\ſa\ =\Z =#@ @~!\" B)BS B@aBx@J@x@AAJ`CEǀ|aJ 9@pla! #@ʃ@J #AJ6J  B@B~ dR@x@A AR .@`E!>a aR ;RD=R#dR2@^?@5" @1 @DK "".1 /C !" ҿAj 0  B@a$B  @x@A$Aj @`Ejaj ;=j@`E<,a JaJ ;@p`==%U@@!U#dAJ_J UBP@x@A AREIaR UaR =R=#U!z@@@S" @+:` K  xAN @3  B@B "9@x@AAN @`E^aNaN ;'`=N#N#@@!N @ BB B@aB@x@AAJe `EՀBaJ :JFaJ"qX+! J# )@@ "AJ J  B@a B B@x@A AR`CELa aR 9 R=#R=i <@'N@G)" @/ @! KUSB6mBo tL a<e9J8Ah (`M`@ B@BLHZ@x@A"Ah @`Erahah ;ŀ=h$@5@u'!h @ BĀ B B@aB@x@AAJ`CEaJQaJ 9J=`="@B<@!D _4AJ;J  +@RBy@x@A AR`CEDaRR= RcA@-@  B B@BG @x@A`Abp.=R{``>O k-kS`k@: =0 0` ka - -z:a!T A  `=؟* 5 .@uր@!. ;sJՠJ . +@Bz.@x@A B*`CEaR 8aR @ܔ=&.{@@6sT\T\ F B@BB)@x@AA\ @`EGL\a\ ;\N= ')@wM@ )B ) B@aBz@٣Bs@x@AAJ`CEaJ)aJ 9@p(ƀ=(3+0@Ā@!# AJàJ  +@B@x@A AR`CE~aR aR 9Rہ=RL@@@S" @f smAS @8wKc `@`  K u &&V Ac4 X [l|[ c `[P5@? cB  @x@2 =Ac6 @`EF7`ac7 = == >8 = @ @  b"AJ9 ? Bu BB&r:@? aBh@x8 9AAJ; @`ETaJ(e/r5FX< = =+ J! = = @ǀ@X %#<fX> G J`J P R? , a BX@x@A AR@``E{aR`.66aRA  =K=T.LB = @@ bL 8BC _ bb j`D@? a$B| q@x@A$AjEE<j77+F=%b\d!I2@ " = A{!H = @8=@1~4`@!9g yA.TԌhv/G?qI _ bb% iXWJ@? aB !c@x@A$A,K E ED,+L = @uC@ " @$g@BM ? BBB ! BN@? a B@q@x@ABO@/ `E89d P = =b! Q = @@ "w$asR G J{J sS@? a Bk¢@x@A ARTEna s::aRU = 8v=R#V = @q@s0;%jBW _ bb jsX@? jBy s@x@A$AjYE*j;;ajZ = -=j#$[ = @@ \ ? B`, B] ? JB !K@x@AAJ^  E倈b N@,<Qf_=$F$=&` = {M*a& `@3Ba @H@ ! @bAb _ bLb b `Lc@? abB !@x@A$AdE7+a,RSbe = jÀ=j%pj%f = @@,g G J6J 4th@? aRB@x@A ARiEEz,aR`.TTaRj  =R%k = @U}@`;k%l _ b|bjm@? jB@x@A$AjnE5-aj jUUajo = [9="p = @8@ $(%"Dq ? BBJr@? JB@x@AAJsERVket=ӤFu = WY=a% F"v = @T@: ) @BaAw _ bSb b륱 aB@x@A$Ay E>a, lmbz =$π=j { = @̀@,QJ̠J \}@? a B@x@A AR~E?aR FnnaR = =" = @@`;dB _ bhb @? jB@x@A$AjEzA@jooaj = D=%p(+c = @W@  ? BC,5 JB@x@AAJcs Epe@ =| = =ePa% % = @I`@ ! @bbAAOb_b b @? a$B@x@A$AEQa,>b = ڀ=^(f = @ـ@b=M'%IB>" G JؠJ J , aRB @x@A AR@CERaR aR = H=! = @@`;EiB _ bb j@? jBz !@x@A$AjE)MSjaj = P=j = @@ $( @"AJ ? BaO Bc@? JBC!K@x@AAJETaJe=1| = pc`{%  = @k@ $F @BۣA _ b]b bc@? aB@x@A$AED&da,R = z=" = @@h5' G JGJ J aRB@x@A AR@ `EQeaR aR = ==R(o = @U@`;B _ bb j@? a$Bc@x@A$AjEXfjaj = X\=(%p(+c = @[@ r ? B J@? B@C@x@AAJ@/ E_gaJe=F |{`|v`{% "@w@ ! @b:}bb b B@`B !@x@A$AE1wa,b = =j(f = @y@ m' @c G JJ @? aRB@x@A ARExaR aR = =" = @@ `;oD _ bdb j@? jB@x@A$AjEdyjaj = h= = @sg@$ ? BfB J@? JBc@x@AAJE zaJe=2| = `{%  = @U@ $F @BB _ bb b @? aB@x@A$AE=a,yb = =j%pj% = @,@, G JJ F@? aRB@x@A AREaR aR = T=R(o = @@ `;lB _ b#b j@? jB@x@A$AjE6paj2 jaj = s=j = @@~ `@"AJ ? Bvr J@? JB@c@x@AAJ@/ E+aJe==>| = {`{% F" = @@c"{A _ bfb ! @? abB@x@A$AEPIa,b = v `=" = @@ m'%c G JCJ @? aRB@x@A ARE^@XA FaR = Ȁ=! = @fÀ@`;ĭB _ b b ĩ@? jB@x@A$AjE{b jaj = U= = @~@~ ? BB [@? JB@o/l@x@AAJ@/ Ek7aJ@==Fc = {i`{%  = @@ $F @c"ZgB _ bb @? abB@x@A$AjETa, V = )`=j^% = @@, G JJ @? aRB@x@A ARE ̀ F  aR = Ӏ=R(o = @π@`;$y!`&'}Πb@6#$E`Bx oy@x@A$AjEb j  aj = ɠ=%-(6@w@+B׉BB$  B@JB@x@AAJECaJ !e=UF = `{%p" @e@ ! @biA båb bO B@aBy@x@A$A7 E`a,"#b  >`= `= @A@,JJ F B@B@x@A ARE F$$aR =RM߀=R"@ڀ@`|bb  B@jB@x@A$AjEBb j%%aj =jҖ=%-(%@.@ $( @K BB B@JB@x@AAJENaJ&;e=JaF =ζ`{% "@@ ' @B brb b륱 B@aB@x@A$A!E]la,y<=b" =j,`=^"#@*@,$J\JA#% B@aRBh@x@A AR&Ej"j>>aR' =R= (@j@`|eN)bb@6j* B@jB@x@A$Aj+Ebj??aj, =j}=j%p(%n-@ء@ .B9Bc/ B@JB@x@AAJ0ExZaJ@Ue1=F2 =`{% %3@½@ B! @bA4b!b ! b 5 B@aB W.X,@x@A$A6E xa,VWb7 =j)8`="8@6@,9J5J : B@aRB@x@A AR;E XXaR< =R=R"=@1@`|D>bb@6? B@jB$@x@A$Aj@Eb jYYajA =j(=( B@@~ $( @(DCBB$BD B@JB@$@x@AAJEE'faJZoeF=F,G =@(`{% H@qɀ@"!IbȀb ! cJ B@`B@x@A$AKEa,pqbL =jC`=^"M@NB@, NJAJ O B@aRB@x@A ARPE FrraRQ =R}$R%R@~ o+o`|ySbHb@6jcT B@jB@x@A$AjUEO, jssajV =jӹ=(%-(+cW@.@ rXB$ yY B@JB@@x@AAJZEqbwte[=WF\ =@, % "]@ Հ@ ! @;m^bԀb bco `B@x@A$A` @Eja,ba = ?`=zO`=$j%b@M@ m2 @,cJHJ d B@B@x@A AReEwa FaRf =R=R"g@ @`|hbb@6#i B@jB@x@A$AjjE jajk =jzŀ=j%-%l@Ā@$mB:B Jn B@JB@@x@AAJoE}bBep=Fq =@!`{% "r@@c" sb.b t B@`Bc@x@A$AuE"a,ybv =jA[#`="cw@Y@ mxJJ kM aRB`B/@x@A ARe `E&$a FY+{=&|@.@ N}bb@8'~ B@a$B@x@A$AjÈ2 jb =@퀃5р=j @Ѐ@ ) @(EBB B@B@x@AAJE3%bce=F =94`{% @}@ ! @c"z6bbj bc B@aBc@x@A$AEǦ5a,* b =jf6`="@Ze@,JdJA#F B@aRB@x@A ARE7a FaR =R%=R(o@ @`|bQb@6j B@jB{ o@x@A$AjE[٠@Y jaj =j܀=j%p(+c@>@ $( @ťBۀ B B@JB @x@AAJE8be=cF = ÀG`{% "@,@ ) @B[!bbj B@`B@x@A$AEvHa,cb =jrI`="@p@,JeJ R B@aRB@x@A ARE)Ja FaR =R41=R"@,@ `|$Dbbj B@jB@x@A$AjE @YY i 8aj =@=%p(%@@ BFBJ B@Bc@x@AAJEKbe=F =[`{%p"@@ ! @brAb:b bc B@aB@x@A$AE%b =jE~\aj^%@|@,JJA# B@aRBPB@x@A ARE25]a FaR =R<= @?8@"`|yBb7b  B@jB@x@A$AjEjaj =jI=j @@~r `@"AJBB B@JB@x@AAJE@^b @==F, =In`{% @@  c"Abb ! 륱 B@aB@x@A$AjEɀ Z^`.   A joaj"@c@,JχJ > B@aRB@x@A ARE@pa@Y R!8 8aR =@H=R(o@C@$`|ezBbUb@6j B@B@x@A$AjEh@YAXaj =@=( @F@ BJ$ , B@B@١B2a@x@AAJEqbc%e=pʄF =@`{% @8@ ! @c")bb b, B@`B@x@A$AEՀ&'b =jj(f@@,JJ  B@aRB@x@A ARELa F((aR =RDT="@O@&`|DEbb@6j B@jB@x@A$AjEj))aj =j =(%-(1@ @ r$( @"@BBOB J B@JB@@x@AAJEÀc*?e=քF =@+a% (@&@  c"qAbKb 륱 B@`B !R@x@A$AE2@Ab =jYj @@ !j @J$J  B@aRB}@@x@A AR@/ `E?Xa BBaR =R_=!@O[@(`||BbZb@6' B@a$B@x@A$AjEjCCaj =jR=j%pj%@@$B J B@JB@x@AAJEMπDYe O`ΤF =V7a% %@2@c";Ab1b  B@a$Bc@x@A$AE쀈Z[b =jaj(f @g@, JӪJA#F B@aRB|#B@x@A AR Eca\\aR =Rk= @f@*`|Bb^b@6j B@jB@x@A$AjEuaj ]]aj =j"=j @R@~ЏcB! B B@JB@x@AAJEڀ^se=Fc =Ca% @H>@ ) @BwBb=` ! b  B@aB@x@ =A @Etub =jø`"@"@, JJ F! B@a B@x@A AR"EoavvaR# =RHw=R(o$@r@,`|B%bb@6j& B@jBL6@x@A$Aj'E$+ajF wwaj( =j.=j )@@~*Bp- B$ + B@JB@x@AAJ,E怈xe-=+a. =N`="/@I@  bztB0bSb ! b1 B@aB@x@A$A2E>a,,b3 =jiĀ=j+c4@€@,5J5JA#F6 B@aRB@x@A AR7EL{aR@Y FaR8 =R=FR%9@\~@v @oK0.`|B:b}b j; B@jB~  @x@A$Aj<E6aj jaj= =jV:=%-1>@9@ nc?BB$ @ B@JBc@x@AAJAEYeB=ڤFC =_Za% "D@U@ `@B[AEbb F B@aBc@x@A$AGEa,cbH =j$Ѐ=j$j%I@΀@,JJ͠JA#(K B@aRB{@x@A ARLEaRcaRM =R=FR"N@@c0`|%qBObsb P B@jBc@x@A$AjQEBaj ajR =jE=j%-%S@Z@~rTBD BU B@JB@x@AAJVEeW=|FX =fa% "Y@Ta@ ! @bmZb`b b [ B@aB@x@A$A\Ea,b] =jۀ="^@/ڀ@,_J٠JA#` B@aRB@x@A ARaEaRaRb =R\=R"c@@2`|db)b@6je B@jB@x@A$AjfE0Naj ajg =jQ=( h@@ $( @(EiB|Pj B@JB@,@x@AAJkE aJcel=<|m =@q$% n@l@c"zAob`b cp B@`B,@x@A$AqEK'a,br =q=j"s@@,tJ>J cu B@aRB@x@A ARvEXaR FaRw =R="x@X@4`|dNybĠb@6#z B@jB@x@A$>!EY aj@Y jaj| =jo]=%p(+c}@\@ r~B+B J B@JBc@x@AAJEf aJc@==F =}`{% (@x@"yAbb B@aBc@x@A$AjE2a, b =j(=^%@@ 2%JJA#`  B@aRB@x@A AREaRcaR =R=!@ @6`|ErBbwb@6a`j B@jB@x@A$AjEejaj =ji=j @jh@ BgB B@JB@x@AAJE!aJc`==3|@ =,`{% @\@ $F @B!bb ! b B@aB@x@A$AE>-a,c A =j="@G@,JJA#F B@aRB@x@A ARE.aR AR =RL=R(o@@8`|ybb@6j B@jBF@x@A$AjE=q/jAj =jt=( @)@$cBs J$ #w` 6iy@ JB@x@AAJE,0aJ5n)`==E?| =ɔ?`{% @ @c":mbmb y B@aB@x@A$A EWJ@a,,*+B jq A`=j+c@@,J>J@F B@a B@x@A AREe@Y ,48' =R ɀ="@mĀ@:`|bàb@6 B@jB@x@A$AjE|Bb2 j- &!j =j\=%-(1@@~cBB J B@JB@@x@AAJEr8C!#c.!@==F =@tR`{% (@@" bb ! B@`B  !@x@A$AjEVSa,DEB =j8T`=^%@@ m,%JJ  B@aRBz,@x@A ARE͠@YA FFFAR =RԀ=!@ Ѐ@<`|w#bπbĩ B@jB @x@A$AjEUb jGB% = #= @~@~B㊀B c B@B@٢o!Ky@x@AAJE!DVaJH]`==F =@e`{% @d@ ! @b{Bbʦb  B@`B !@x@A$AEafa,^_B =j!g`=^%@D @,JJ  B@aRB@x@A ARE F``AR =Rd=R(o@ۀ@c>`|ĶBb3b@6# B@jB]L@x@A$AjEIhb jaaAj =j֗=j%-(&@1@ n$( @n"@BBn$B B@JB@x@AAJEOiaJbw`==QbF]L =x`{%p"@@ ) @BZ|Abyb b B@aB@x@A$AEdmya,cxyB =j-z`= @+@,JWJA#F B@aRB@x@A AREr FzzAR =R=R"@~@@`|$Bbbf B@jB@x@A$AjE{b j{{Aj =j|=%-(%@آ@ B8BJ B@JB@x@AAJE[|aJ|`==nFË`{% "@ʾ@ ! @bDAb(b b  B@aB`x@9 A @Eya,B =!=,9`=j^(f@7@,J6J  B@a B@x@A AR E FAR =R=R" @%@B`|OB bb  B@jB@x@A$AjEb jAj =j7=j @@~n$( @"AJBB B@JB@x@AAJE.gaJ; `==Fy =3Ϟ`{% @xʞ`@c"Abɀb ! c B@aB@x@A$AE„`,B =jD`="@EC@,JBJA#c B@aRBF@x@A >E FAR =R|$R% @~ $F'EoD`|eLB! LGb@6j" B@jB|  @x@A$Aj#EV, jAj$ =Һ=(%p(+c%@1@~&B B$ _ JB@!K@x@$ AJ( @Erbw`=)=^FF* =@{ڱ`{% "+@'ր@ " @A,bՀb bc- B@`B@x@A$A.Eqa,B/ =jP`=j$j%0@N@ 52 @y1JXJA#2 B@aRB@x@A AR3E~a FAR4 =R="5@~ @F`|DB6b b@6j7 B@jB@x@A$Aj8E jAj9 =jƀ=%-%:@ŀ@ r$( @"@B;BEB J< B@JB @x@AAJ=E~bB`=>= F* ? =@`{% "@@@ $F @BGAAb9b b륱B B@`B !y@x@A$ACE a,BD =jD\`=j%pj"E@Z@ m!j @FJJ  aRB@x@A ARH @`E-a FARI =R=R"J@5@H`|JBKbb@6jL B@a$B@x@A$AjME jAjN =j@Ҁ=j%p%O@р@$PBЀB JQ B@JB@x@AAJRE;bc`=S=FT = À@`{% "U@@c";AVbb W B@`Bc@x@A$AXEϧa,BY = `h`=%Z@if@,[JeJ F\ B@Bc@x@A AR]EaAR^ =R&=&_@!@J`|B`b`b a B@jB@x@A$AjbEcڀjAjc = ݀=j d@M@ ceB܀ Bf B@B@x@AAJgEbB@=h=kF/i =`{% j@3@ ) @c"ABkbb l B@aB@x@A$AjmE}a,yBn =js`="o@ r@,pJxqJA#Fq B@aRB@x@A ARrE*a aRs =R;2=R(ot@-@L`|Bubb@6jcv B@jB@@x@A$AjwE jajx =j=( y@@~zBVB J{ B@JB@x@AAJ|Ebc-e}=FW~ = `{% @@ ': @bzBBbBb ! b c B@aB@x@A$AE,./b =jXj+c@}@,J'JA#F B@aRB@x@A ARE:6.@T F00aREZR==R% ~@29@@V" @N`;IBb8b@6j B@jB@x@@Aj `E j11aj )E= @@ !j @"AJBB By B@aB@5y@x@AAJEGb2Ge=ȤF =@M`{% @@c"[Abb !  B@`B@x@A$AEʀHIb =j j$j"@n@,JڈJA# @ B@aRBW@x@A AREAa FJJaR =RI=R%@D@D$F @P`|%yHeb@6jcM jBz ,@x@A$Aj @`Eo@ jKKaj =a%p6@M@~cB B$ , B@aBW@x@AAJE Lae=w˄FQ = $a% "@;@bcE" @mbb ! b, B@aBy@x@A$AEրbcb =j–%j$j%@!@,JJ  B@aRBF@x@A AREM&addRh =R?U=R"@P@R`|b b  B@jB@x@A$AjE 'aj eeaj =  ="@@ $(n"EB^ J$B B@B@x@AAJEĀf{@==&ׄF =!?,7a% F"@'@G " bNb B@aBc@x@A$AjE9|}b =jl8j^"@̠@,J8JA# B@aRB@x@A AREFY9a~~aR =R`=R"@N\@T`|dNb[b@6j B@jB@x@A$AjE:jaj =ja=j%-(+c@@ BB B@JB@x@AAJETЀe=դFy =]8Ja% "@3@LF! @c"OAb2b ! b B@aB@@x@A$AE퀈,J =jKaj%@_@,J˫J c B@aRB/@x@A AREdLa aR =Rl=R"@g@V`|Ebeb@6 B@jBc@x@A$AjE| Mjaj =j$=( @a#@ $( @"DB"B$Bc B@JB@x@AAJE܀e=Ja* = ÀD]`=% @K?@  c"!b>b !  B@`B@x@A$AEE =jɹ^j"@*@ 5!j @ JJ F B@aRB@x@A AREp_a aR =RGx="@s@X`|ybb@6ja| B@jB@x@A$AjE+,`jaj =j/=(%-(+c@@ rBg. J$ J B@JB@8y@x@AAJE瀈e=3F =@Opa% (@J@ ! @c":mb[b b B@`B !B@x@A$A@/ EFqa,b =jpŀ=j%pj%@À@ mJ B@`B@x@A$Aj?ER(a,cb@ =j|=j#j"A@@,BJIJ FC B@aRB@x@A ARDE`aR FaRE =R=R"F@h@``|%\BGbԡb@6jH B@jB@x@A$AjIEZjajJ =jo^="K@]@ LB*BM B@JBc@x@AAJNEmaJ1eO=FP =~`{% F"Q@y@c"BRbb ! S B@aB@x@A$ATE4a,23bU =j'=j V@@,WJJ FX B@aRB* @x@A ARYEaR 44aRZ =R=R"[@ @b`|B\bwb[] B@jB; @x@A$Aj^Efaj j55aj_ =jj=+"(+c`@yi@ aBhBJ$ Fb B@JB@:y@x@AAJcE"aJ6Ked=Fe =@!`{% "f@g@c"WAgbńb h B@`Bc@x@A$AiE?a,LMbj =j=j k@G@ m, @,lJJA#m B@aRB@x@A ARnEaR FNNaRo =RS="p@@d`|dBqb"b r B@jB@x@A$AjsEDrjOOajt =ju=%-(%u@(@ vBt,w B@JBc@x@AAJaz E-aJPeem =L@|^pЕ`{% %{!@@ ! @bTA|btb b } B@a$B@x@A$A| E_Ka,,fgb ) `=^1@ @,JRJ  B@a B$X@x@A AREl  hhaR =R ʀ=&@lŀ@f`|E_BbĠb@6f B@jBcB@@x@A$AjE}b jiiaj =j{=j @ր@~$( @"AJB7Bc B@JB@x@AAJEz9aJyje=KF5n =`{% @Ȝ@M) @BەAb'b ! bc B@aB@x@A$AEWa,0Ab =jI`="@@,JJ >c B@aRBc@x@A ARE)EaRA@aR =@L= @5H@A $F @ K u% +EBbGbj B@BF@x@A$AjZ `Ejdf3 j2?u {#fT@@ yA.TԌhv/G?eAKb f B@bB@!$  E@x@A$A, /&,,@,@$B B! s B@a B ! @x@ABE6d AzJZ|bw#J(( ~@z@ & @J)J s B@RBx#B@x@A ARED3a saR =:=R-@<6@.$ aRs%s  ( Cb5b js B@jB~@s@x@A$Aj@/ `E jes=Ңw!~sVD%s = ){{ s@@, ~" @ s#Abfb $f bf> B@bBhW!@x@A$A, E E,$(%s@X@ " @$g@BBo! Bs B@a BY@x@ABEQd sd =J{j`=J%2J! @h@,sJHJA#s B@RB@x@A ARE^!a aR =R)=R%s@_$@ Qd0`zxb#b s B@jBQs@x@A$AjE܀jaj =jm="|@߀@ B)B B@JB@x@AAJEl/@T f=F{. My & =\{&F"@@3! @,2 2Tbb b B@aBm"@x@A$>ESaKb =j`=j @e@,JJ  B@aRBb@x@A ARE aR =RҀ=e6 R"@΀@C@"!+% Enter ICPuaMHobt̀b j B@jBT@x@A$AjEb jaj =j=)6()@k@$BψB . B@JB@!K.@x@AAJEBaJ Je=TFw =@H{% "@HC@ p \Ab  p B@`BP @x@A$AEcb =jaj$j+c@@ m!j @JJ! B@aRB@x@A AREta aR =RD|="@w@ ; u"a:Bbb@.# B@jBy@B@x@A$Aj@/ `E(0jdf=0 =6{#$f@e1@ Z @ZZfVAb f B@bBW!f@x@A$KE E,I8,&,'@7@P( @0$g@BB B  B@a B@! @x@ABEsd =@p۫b ! @>@ sJJ s B@BK@x@A AREb a saR =Rxj=&#@e@ @CuT"8 !=Bb5b js B@jB s@x@A$AjEC aj jaj =j!=j#j$@ @~Bo B B@JB@@x@AAJE Jf =KFz% =@߀{&}" @ۀ@ $F @7A bfڀb b% B@`BZ @x@A$AEQ bb =jU `="@S@,JPJ! B@aRB@x@A ARE^ a:faR =R=%p@r@ Sa pBbb  B@jB@x@A$AjE fdf=% =̀{#@ɀ@ f) @f,AZfAbȀb & b f B@aB@x@A$K% E,Ѐ, @oπ@ !, @Ht'_ BB! BB! B@a B@x@AB"Eld sd # =JC`="s$@A@,4v%J_JA#,s& B@RB@x@A AR'Ey aR( =R+$R(1)@~%[a B*bb@1js+ B@jB@x@A$Aj,E, jes-=s. ={F$s/@<@ asA0b $f 1 B@aBs@x@A$Es2 E,$,$,%03@@ ]rs4BༀB,  5 B@a Bs@x@AB6Eqcsd 7 = 1`=J&8@0@,9Jm/J s: B@B @x@A AR;E aR< =R4=%s=@@ sc9a C>bb@1s? B@jB{@x@A$Aj@Eb jesA=#rB =  {$F!C@Q@ s$F @as0Db f bftE B@aB'@x@A$EsF E,3,$,%sG@@$sHB,  sI B@a B@x@ABJE_bsd K =J`= %sL@4@,sMJJA#sN B@RB@x@A AROE aRP = Lހ=#Q@ـ@ [k*?  wCRbb jS B@B@x@A$AjTE6b jesU=>`V =.{$F!W@q@ [ asAXb f fsY B@aB@x@A$EsZ E,^,$,%s[@@& @$g@B\BB+Bs] B@a B@x@AB^EMbd _ =J `="s`@C @,saJ J sb B@RB? @x@A ARcE aRd =R}̀=R#e@ǀ@ sm+)7fbBb jsg B@jB@x@A$AjhEQb jaji =jՃ="j@2@~kB Jl B@JB@x@AAJmE;aJ Jfn=XNF}o =A{&"p@=@ % @2 A[qbsv@x@A$AsE^bt =j~aj&u@ᵀ@,vJMJ w B@aRB@x@A ARxElna aRy =Rv=R"z@|q@ Ի"P"aGHo{bpb½| B@jBz 4@x@A$Aj}E)aj jaj~ =jf-="@,@ $(w"AJB"BJ B B@JB@x@AAJEy Je=F =e{% @@ p 0Abb B@aB@x@A$AEbb =j8a`=^"@_@,JJA#c B@aRB@x@A ARE  a aR =R=R%@@ "E" aBbb½ B@jB@x@A$AjE jaj =j ׀=(%p(/l@jր@ rBՀB J B@JB@& @x@AAJE!b Je=Fyc =@ {% "@W@ @c P6b ! b B@`B@x@A$AEJ"acb =j #`=j c@( @ m' @ JJ  `JR- B@aRB@x@A ARE aR =R_ɀ="@Ā@ ):Kb%agDb'b j B@jB@x@A$AjE6}$baj =jʀ=j(%@$@B B$ c B@JB!y@x@AAJE8%aJ e==KF~ =>{F% @ :@ p$F @ . bl9b! b B@aB@x@A$AECb = `k&aj%pj+c@β@,J:J B@B@x@A AREPk'a aR =Rr=FR(o@Qn@ "e")c:Dbmb½ B@jB @x@A$AjE&(jaj =jc*=%@)@ nBB J$ g B@JB@x@AAJE^ e=ߤFJ =J{% F"@@c Ab !  B@aB@x@A$AE)bb =j ^*`=j^%@o\@,J[JA#c B@aRB@x@A ARE+a aR =R=R"@@ k`1 )afbbb½ B@jB@x@A$AjEyЀ jaj =jԀ=j%-(%@`Ӏ@$BҀBJ$  B@JB@x@AAJE,e=F = À{% "@=@ c jAbb !  B@`Bc@x@A$AEG-acb =j.`=%@ @,JyJA# B@aRBc@x@A ARE FaR =RTƀ="@@ "u/a^Bbb j B@jB@x@A$AjEz/b jaj =j}=%-(%@@~ncBf| B$  B@JB@x@AAJE50Je="HF. =;{% "@6@c hAb=b  B@aB 8p,@x@A$AE(cb =jM1a^%@@,JJ B@aRB@x@A ARE5h2a FaR =Ro=R"@Ek@ 33@ `cBbjbĩ B@jB.@x@A$AjE#3jaj =j<'=j%- @3A@&@ B%B B@JB@x@AAJ  EC e=ĤF =AT{?{% "@@ c qAb !  B@`B@x@A$AEɚ4b Z b = `Z5`="@TY@, JXJ Ry B@B@x@A AR E6aR@saR =@= @@ $F @K " @ibSbj B@B@x@A$AjE^̀ jaj =jЀ=j @A@~!j @(DBπ $Bc B@JB@x@AAJE7e=fF =Վ{F% @@ @! @ޘbbAy(! bc B@aB@x@A$AEkD8ab = `9`=j$j"@@,JfJ R B@B@x@A AR Ey FaR! =R.À=Rc"@@c3@P#bb½$ B@jBP @x@A$Aj%Ev:b jaj& =jz="'@y@ $(n"E(BCBJ$B) B@JB@J!K@x@AAJ*E2;J@=+=EF9, =@~8{% F"-@3@c A.b"b / B@`B@x@A$Aj0E -qb1 = `=j 2@ @ m, @3JyJ 4 B@B|LWJ@x@A AR5E<aR6 =RC="7@@y"Ø"asN8b b 9 B@jBJ@x@A$Aj:Ee=jaj; = h=%-(6<@@ =B^g > B@Bc@x@AAJ?E >Je@=F*A =!?&{% %B@!@ W @c חACb=b b D B@aB@x@A$AEE(܀bF =jK?aj%pj(fG@@,HJJ nI B@aRB@x@A ARJE5S@a aRK =RZ="L@AV@ #(%paBMbUbO.N B@jB @x@A$AjOEAjajP =j4= Q@@$RBB JS B@JB(#@x@AAJTECʀ eU=ĤFu V =3Ѐ{% F%W@ˀ@ ) @+c BXb ! b Y B@aB@x@A$AZEɅBbb[ =jEC`=j \@`D@,]JCJA#^ B@aRB@x@A AR_E aR` =oDaR"a@~ )!(o 38"%e Bbb;b@&jc B@jB/@@x@A$AjdE], jaje =j޻=j f@<@!j @(AJgB B$Bh B@JB@@x@AAJiEsEbwJej=eFk =@y{F% l@!u@ @}Ambtb! bn B@`B@x@A$AoEk/F/@Tcbp = 0`=j$j"q@@,rJ^J cs B@B@x@A ARtExGaR FaRu =R$=R(ov@@ CKW 8"3IwbbA;#x B@jB| !@x@A$AjyEaHjajz =je=j%-1{@d@ |BGB$ } B@JB@x@AAJ~EIJe=0 =j#{% "@@ c 5Ab"b !  B@aB "@x@A$AE ـb =jEJa @@,JJ F B@aRB@x@A AREPKa aR =RW=R"@.S@ S"ЃЂ@8BbRb@&j B@jB@x@A$AjE Ljaj =@݀5=j%-`3A@@ B B$  B@B@x@AAJE' e=F =̀{% "@dȀ@ * ! @c eAb c B@aBQ@x@A$AEMbb =jBN`="@EA@,J@JA#F B@aRB@x@A ARE aR =RhOaR @~ cos䓣Bb4b@(j B@jB@x@A$AjEB, jaj =j=j%-(+c@@~$( @nK@BBz B B@JB@),@x@AAJEpPbw Je=JF, =@v{%p%@r@ c ]Abeqb! c B@`Bc@x@A$AEP,Qa Z b =jy="@@ m!j$ }@ ! @ ARJGJ(R B@aRB@x@A ARE]RaRaR =R=R"@Y@ !$F!Ro sŃł@ a~bťb  B@jB @x@A$AjE^Sjaj =jxb=(%p(%@a@ !j @ťB4B$Bc B@JBc@x@AAJEkTaJA8@==Fc =@Og {% "@@ @ bb^! b륱 B@`B@x@A$AjEՀcA =jUa^"@t@,JJ {R B@aRB @x@A ARELVa  AR = T=R"@P@ ,e"Qpt b{Obj B@Bc@x@A$AjEWaj jaj =j =(%-(%@i @~B B J$  B@JB@x@AAJE Je=քFB =ɀ{% "@Bŀ@ c _`b !  B@aB@x@A$AEXbb =j?Y`=j)j%@>@,J=JA# @R B@aRBvLWB @x@A ARE aR =R>=R"@@ p tanDb b½ B@jBxc@x@A$AjE'Zb j  aj =j="@@ BsJ$ B@JB@x@AAJEm[J  e=/F =s{% F"@n@ ! @c dBbJb b B@aB .Xy@x@A$AE5)\  b =j-=j @-,@,J+J n B@aRBB@x@A ARE F  aR =Rs="@@ csth`ߊa`]bB4b(b j5 B@jB@x@A$Aj6E jaj7 =j(="8@@ 9BB$y: B@JB@٢o?2a3}@'@x@AAJ;E'hb Je<=F= =@@{% F">@a@ ! @J?b e! b @ B@`B@x@A$AAEXiabB =jj`=^%C@I@ b2$^DJJA#cE B@aRB@x@A ARFEπaRG =Re׀=R"H@Ҁ@  F @>oXEIb3bA;jJ B@jBsZ@x@A$AjKEBkb f  ajL =jŽ=(%-(1M@"@ r$( @"@BNBBiFO B@JB@@x@AAJPEFlJ!!eQ=JYFyR =@L{% "S@H@ W$F @6 ˪ATbeGb bcU B@`By@x@A$AVEOmac"#bW =jv€=^"X@@ m!jYJBJA#Z B@aRB@x@A AR[E]ynaR F$$aR\ =R=R"]@a|@  12 EajB^b{bf_ B@jBc@x@A$Aj`E4oaj2 j%%aja =jp8=(%-(%b@7@~cB,B J) d B@JB@x@AAJeEj J&&ef=F|3|=\ Ag =_{% "h@@@at @6Aibb! bj B@aB 'Z,@x@g =Ak @Epb ''bl =j=%pj%m@@,nJaJ@o B@a By@x@A ARpExgqR((aRq =R,o=R"r@j@cn_fMBsbibA;jt B@jBy@x@A$AjuE"rj))ajv =j{&=j%-%w@%@$xB7B$ y B@JB]L@x@AAJzEހ **e{=| =u{F% "}@߀@ ) @  ~b!b B@aB,@x@A$AE sbc+,b =j0Zt`=j^%@X@,JWJ F B@aRB@x@A AREua --aR =R=R"@@ck_Z1a8LDbb½ B@jB o@x@A$AjE j..aj =j,Ѐ=j(%@π@$B΀B J B@JBc@x@AAJE'v//e=F ={"@`@G Ab ! Zc B@aBc@x@A$AECwa01b =x`=j @(@,JJ B@aRB@x@A ARE F22aR =R[€=R"@@ c#NvBaBb'b@$ B@jB@x@A$AjEBvyb j33aj =jy=j @,@~Bx B$ c B@JB@x@AAJE1zJ44e=JDF =7{% c@3@c lbd2b  B@aB* @x@A$AEO퀈c56b =j|{aW @ޫ@,JJJA#Fc B@aRBc@x@A ARE]d|ay F77aR =R l=R%@mg@ 3ƀԀU)aUEbfb@$j B@jB@x@A$AjE}aj j88aj =jl#=%-(+c@"@~B+B B@JB@,,@x@AAJEj J99e=Fw =@V{% "@܀@ ': @+c QAbb ! b B@`B @x@A$AE~bc:;b =j"W`=^1@U@ m2 @,JTJ(> B@aRB@x@A ARE a <>e=F{ ={% @I@ $F @6 eb ^! bc B@aB@x@A$AE@ac?@b =j`="@%~,JJA# B@aRB@x@A ARE FAAaR =RI=R(o@@ SQ 䓣a<|ybb½ B@jBnb@x@A$AjE'sb jBBaj =jv=(%-(+c@u@~B[B J$  B@JB@x@AAJE.aJ JCCe=2A|c =4{% "@/@, |ybIb!  B@aB (,@x@A$AE4DEb =j^aj(f@@,J+J B@aRB@x@A AREBaa FFaR =Rh=R"@Jd@ cc = `ـ=j^%?@D؀@,@JנJ nA B@B@x@A ARBEaRXXaRC =R\=FR"D@@ N`͈$Pa#,Eb+b@$jF B@jBy@x@A$AjGEALaj YYajH =jO=j I@@~a$( @"DJBN B$BK B@JB@`{&@x@AAJLEJZZeM=I|xcN =@ {% O@ @ , 2Pbdb cQ B@`B$X@x@A$AREOÀ[\bS =ja"T@恀@ m,$UJRJ(>V B@aRB@x@A ARWE\:a F]]aRX =RA=R%Y@`=@  ##E#1sa DfZb=R%@@ `ү"Bbb½ B@jB@x@A$AjE&Iaj jhhaj =jL="@@ BfKJ$&5, B@JB@x@AAJEJiie=.| = {% F"@@ ! @6 ]BbIb by B@aBy@x@A$AE4jkb =jbaj @~@,; J/J  B@aRB@x@A AREA7a FllaR =R>="@M:@ "!kZ兺afgBb9b B@jB@x@A$AjE jmmaj =jH=%-(C@@ r!j @(@BBB J B@JB@@x@AAJEOb Jnne=ФF =@G{% %@@  c •Ab ! 륱 B@`B@@x@A$AEiacopb =j*`=j$j"@d(@ m!j @J'JA# B@aRB @x@A ARE qqaR =R="@@ ,`_aBbOb j B@jB @x@A$AjEjbfrraj =j=j%p%@S@~B B$ c B@JB@@x@AAJEWaJ sse=qjFQ =@]{F% %@,Y@ ! @ =$AbXb! bc B@`B@x@A$AEwttb = `=%pj%@w@,JJ  B@B @x@A ARE uuaR =Rր=R"@р@dWhb^b j B@jB5n@x@A$AjEvvaj =j=%-%@o@ $( @"@BBԌB$B B@JB@@x@AAJE FJwwe=tյ =@K{% "@HG@ ) @ hb  B@`B W'Z@x@A$AEaxyb =j=^"@ @ m!j @cJyJ  B@aRB@x@A ARExaR zzaR =RH=!@{@c` x|KdadDfbb½ B@jB@x@A$AjE&4j{{aj =j7=j%-j%@ @ Bn6 B B@JB@x@AAJE ||e=.|, ={%p%@@, LkbIb c B@aBc@x@A$AE3b Z }~b =jmk`="@i@,J:J F B@aRB@x@A AREA"a(aR =R *=R"@]%@#u`aDb$b j B@jB oB@x@A$AjE AXaj =@T=( @@ BB B@B@x@AAJENb Je=ФF; =?{% @@ * ) @c !cBb ^! b B@aB@x@A$AETab =j`=j+c@`@,JJA#{R B@aRB@x@A AREˀ aR RӀ=R%@΀@ c3ү"K[@T BbOb jjB`x |A$Aj@ aib jaj = >`=튀="@I@rBB$&5 B@B@x@AAJ EBJe =qUFc =H{%  @*D@ c iB bCb  B@aB@x@A$AEw b = `aj @o@,JJ  B@By@x@A ARE RaR =R="@@ cCp ӔP aBbnb  B@jB,@x@A$AjEub jaj =j y=%-(1@jx@ BwBµ B@JB@x@AAJE 1Je=F* =7{% (!@G2@, "b  # B@aB,@x@A$A$E1 b% =j<=j &@@,'J J R( B@aRBc@x@A AR)EaR* =Rͯ=R"+@$@ S~.KlaeD,bb j- B@jBc@x@A$Aj.Ecjaj/ =j#g=j 0@~f@ 1BeB2 B@JB@x@AAJ3E&Je4=.y5 =%{% 6@\ @ ! @4 B7b ^! b 8 B@aBc@x@A$A9Eڀyb: =jޚa1;@?@,<JJA#= B@aRB@x@A AR>EQa aR? =RkY=%@@T@ c+l+lwBAb2b½B B@jBy@x@A$AjCEA jajD =j=j E@(@ $( @"AJFB B$BG B@JB@x@AAJHE eI=IۄF{$XJ =΀{% K@ʀ@  jALbcɀb M B@aBQ@x@A$ANENbO =j눀="P@J@,QJJA#R B@aRB@x@A ARSE?RaRT =RG=R%U@B@ $F%osq 1laφBVbUb jW B@jBc@x@A$AjXE\ fajY =j=(%-(1Z@I@~ƹ!j @ť[B J\ B@JB@x@AAJ]Ee^=F_ =׼{% "`@@t ktAab~b 륱b B@aB ?n@x@A$AcEiraybd =j2`=j$j"e@0@,fJ\J g B@aRB@x@A ARhEw FaRi =R.=R"j@@ p"xa!dkbbĩl B@jB@x@A$AjmEb jajn =j="o@㧀@ pBABJq B@JB@x@AAJrE`Jes=sFBt =xf{% F"u@a@ , 'Lvb b w B@aB@x@A$AxE aby ='܀=^%z@ڀ@ ,% {J٠J | B@aRB`@x@A AR}EaRaR~ =RԚ=R"@@o!Rc~uDk*[bb  B@jB @x@A$AjENaj aj =jR=(+"(!@yQ@ rBPB B@JB@x@AAJE& aJ Je=F@ =.{% "@b @ {Ab  B@aB S"c@x@A$AEŀb =jԅaj$j%@7@,cJJ y B@aRBB@x@A ARE B@aRB{ ]L@x@A ARE[Ѡ DE aR =Rـ=R"@sԀ@ 5n2K%@77%; UBbӀb' B@jB@x@A$Aj  `Eb jaj =jn=(%-(%@ˏ@ rB*BJ B@aB@x` =AJ @EiHJe=FF =]N{% "@I@ 5n! @c5A bb b B@abB ?n,@x@A$A Eab =j#Ā=^% @€@,JJ ( B@aRB @x@A AREzaR FaR =R=R"@~@ 1d3`2% bm}bA; B@jBz ,@x@A$AjE6aj2 jaj = 9=( @M@~G$( @"DB8 J B@B@x@AAJE Je=|y ={% @F@c b ! c B@aB]L@x@A$A Ebb! =jm`= "@l@,#JkJ F$ B@aRB@x@A AR%E$a aR& =Rf,=R%'@'@ N$F @o EDD(bb ) B@jB@l@x@A$Aj*E%jaj+ =j=%p(+c,@@ !j @ť-Ba B. B@JB@x@AAJ/Ee0=-FB1 ={% "2@蜀@  3bHb 륱4 B@aB "c@x@A$A5E3Wab6 =jJ`=j$j"7@@,B8JJ R9 B@aRBy@x@A AR:E@΀ aR; =RՀ="<@Hр@ c# u%`mn!=bРb@"na|> B@jB{ @x@A$Aj?Eljb jaj@ =jC=%-%A@@$BBBC B@JBB@x@AAJDENEaJ JeE=WFBF =:K{% %G@F@, t Hb  I B@aB@x@A$AJEacbK =j=j L@S@,cMJJA#FN B@aRB@x@A AROEwaR aRP =R="Q@z@ o!c3))a*DRbbb@$jS B@jB@x@A$AjTEh3jajU =j6=j V@3@cWB5 B/l X B@JB@&nb@x@AAJYE eZ=pa[ =@{F% \@,@ ! @B]bb! bXF^ B@`B@x@A$A_Evaj b` =j=$j+ca@~@,bJꬠJ c B@aRB @x@A ARdEeRaRe =Rm=R(of@i@,Cuu&׮Bgbhb jh B@jB/@x@A$AjiE!jajj =j %=%-+ck@g$@ $( @"@BlB#B$Bm B@JB@@x@AAJnE eo=|,p =@{% "q@Dހ@ 'L , s\Arb s B@`B@x@A$AtEbbu =jX`=^"v@ W@ m!j @Ϣ 6! !%w VJ x B@aRB@x@A ARyEa aRz =L=R"{@@ SQ * @JW|bb½} B@jB@x@A$Aj~E%ˀ jaj =@݀΀=(%-(%@̀@~B]B B@B@@x@AAJEe=-F =@{ @懀@ ! @c AbHb ! bc B@`B@x@A$AE2Bab =je`=^%@@ mJ1JA# B@aRB{&B9 @x@A ARE@ DE  A@aR =@=R%@H@ c uȿMBbbĩ B@By@x@A$AjEtb@Y jaj =jSx=( @w@ $( @"AJBB J$Bc B@JBy@x@AAJEM0aJ Je=ΤF, =26{% :"@1@ ,  Ab ! 륱 B@aB@x@A$AEb =jaj+"j"@o@,J۩J R B@aRB@x@A AREba aR =Rj=R"@e@ s6 u &ΔBbVbj B@jBB@x@A$AjEhaj jaj =j!="@I@ B J$ B@JB@x@AAJE Je=pF =߀{% F"@*ۀ@ c jfBbڀb B@aB@x@A$AEvbb =jU`=^%@S@ 5cJYJA# B@aRB@x@A ARE a aR =R.=R"@@ 1 0 16%QYBbbf B@jBnb@x@A$AjE jaj =jˀ=(%-(1@ʀ@ rBBB J B@JB@y@x@AAJEb Je=Fxc =@{% "@̄@ m$F @6 nAb-b! b B@`B@x@A$AE?"Gcb =j:=j c@@ m!j @JJA#c B@aRB @x@A ARE%aR aR =RԽ="@5@ , 8E  8a/Bbb@$j B@jBB@x@A$AjEqajF jaj =jF- =ـ{ .@Ԁ@ , w A/bUb 0 B@aBy@x@A$Aj1E@b  E2 =j瓀="3@H@,4JJF5 B@aRBB@x@A AR6EJaR RaR7 =RR=R%8@M@5nu6)aB9bJbj: B@jBB@x@A$Aj;EMjaj< =j =(%-=@9@ r>B J? B@JB@x@AAJ@E FeA=ܤFnbB =ǀ{% "C@À@ 5n`@  BDbp€b E B@aB@x@A$AFEZ}b, bG =j=`=^+cH@;@,IJQJA#J B@aRB@x@A ARKEh@Y   aRL =R=R"M@t@ |y 8BNbbO B@jB@x@A$AjPEb2 j  ajQ = s=( R@ϲ@~SB/B JT B@B@x@AAJUEukaJ  eV=F W =jq{% X@l@Yr @( eBYbb b Z B@aB@x@A$A[E&a b\ =j&=%]@@,^JJA#_ B@aRB@x@A AR`E aR FaRa =R=R%b@@   K^n!cbb cd B@jBB@x@A$AjeEYjajf =j]= g@q\@ hB[B,ki B@JB@x@AAJjEJek='|l ={% m@R@ c Enb  o B@aB@x@A$ApEЀt ^bq =jKՀ=jj%r@Ӏ@,sJJA#t B@aRB,@x@A ARuE$aRv =R̓=R%w@,@ 11 %aVBxbb jy B@jB| 5n@x@A$AjzEGjaj{ =j7K=j |@J@ }BIB~ B@JB@x@AAJE2Je=:у* = {% @k@ ! @c hb  B@aBy@x@A$AEb =jXÀ=%@@,J%JRc B@aRB@x@A ARE?z aR =R=R%@S}@ #^ -Eb|b  B@jB@x@A$AjE5!jaj =jV9=( @8@ $( @w"AJBB B@JB@x@AAJEM e=U =={%p@@ y c  Ab ^! y B@aBc@x@A$AEӬ"bb =jl#`=^"@Zk@,JjJA# B@aRB@x@A ARE#$a aR =R+=R%@&@ 3^A+^A!BbUb½ B@jB@x@A$AjEh߀ jaj =j=(%p(C@D@~Bဃ J$ B B@JB@x@AAJE%e=oF =۠{% "@%@ ! @c !bb b, B@aBc@x@A$AEuV&a b =j'`=j @@,J`JA# B@aRB@x@A ARE͠@Y F!!aR = +-Հ=R"@Ѐ@ C B%B+j$sDbπb  B@B@x@A$AjE (bj""aj =j="@@ $(n(AJBMB B@JB@x@AAJED)J##e=WFx =J{% @E@, ?EAb,b B@aB 4 L6@x@A$AE*aj^W$'b =jD7,`=^"@5@,JJ ( f=Rh B@aRB @x@A ARE2RA@((aR =@=%@>@ Sm+ T Bbbj B@B@x@A$AjE-b j))aj =j0=j%p(+c@@~BB B@JBL6@x@AAJE?e.J**e=F|@. 8A +k{% "@{f@Dt @c |Ab ! b^c B@ Bc@x@A$AE /aB+,b =j="@U߀@,/JޠJ! B@ B@x@A AREӗ0aR F--aR =Rk=R"@˚@ c1 "yBb7b   B@jB@x@A$AjEZS1j..aj =jV=( @D@ rBU J$  B@JBc@x@AAJE2aJ //e=b!|y"`@% ={% @@@a hv/G?Bb}b B@aB* @x@A$AEgʀ01b =j3a^+c@@,cJZJA# B@aRB,@x@A AREuA4a 22aR =RI=R%@}D@ s!"; a^BbCb@$j B@jB@x@A$AjE 33aj =jt5aj%-`3A@~ B0B B@JBt@x@AAJE  J44e=˄FQ =f{" @@ !%c Abb B@aB@@x@A$AE t6b56b =j:47`=%@2@,JJ F B@aRB@x@A ARE@Y 77aR =R=R% @&@ %1 @B bb@%½ B@jB| @x@A$Aj E8b j88aj =j!=( @|@$BݨBJ B@JBc@x@AAJE$b9J99e=t6`= =h{%p:"@ac@ 5n c b   y B@aB@x@A$AE:ajt ::b = `^"=j @ @,J+JR B@By@x@A ARE1٠@Y R;;aR =R="@1܀@ +\+\ 64, b۠b j! B@jB5n@x@A$Aj"E;b j<?b. =jˀ=j /@\ʀ@,0JɠJA#F1 B@aRB; @x@A AR2Eӂ>aR F@@aR3 =Ry=R(o4@ۅ@ \]% aDf5bGb@%j6 B@jB@x@A$Aj7EZ>?aj2 jAAaj8 =jA=j%p( = {% "?@@ c CA@b|b 륱A B@`B@x@A$ABEg@bCDbC =juA`=jj"D@s@ m!j @FEJ^JA#F B@aRBc@x@A ARGEu,BaFEEaRH =R+4=F!I@/@ ,";]4]Q),Jb.boK B@jBL6@x@A$AjLE瀈 fFFajM =j{=j%pj%N@@~OB;BP B@JB@@x@AAJQECb JGGeR=F; S =@n{% %T@@ ,Ubb! V B@`B@x@A$AWE _Da,HIbX =j<E`="Y@@,,ZJJ R[ B@aRBc@x@A AR\Eր JJaR] =R݀= ^@ـ@ c)6ˀV`D_bؠb #` B@jBy@x@A$AjaEFb jKKajb =@瀃%=H@ c@@~dBB$&5,e B@B@x@AAJfE$MGaJ JLLeg=Fh =S{% i@[N@ ) @ Bjb ! bk B@aBy@x@A$AlEHacMNbm = `Ȁ="n@%ǀ@ !j$coJƠJ p B@B@x@A ARqEIaR OOaRr =R[=R(os@@  , HBtb(b@(u B@jB@x@A$AjvE>;JjPPajw =j>=j%-(+cx@,@yB= B$ z B@JB,@x@AAJ{E QQe|=F |} =}{"~@@ !%NK Ab-b! b B@aBc@x@A$AELKbcRSb =jrL`=%p1@p@,JOJA#F B@aRB @x@A AREY)Ma TTaR = 1=R%@m,@ @``!Bb+b½ B@BB@x@A$AjE jUUA! =jT=(%-%@@ n$( @"@BBB J$B B@JBB@x@AAJEgNVVe=貄Fu =S{% "@@ ) @+c 7Abb b륱 B@aB@x@A$AE[OaWXb =j&P`=^"@@,JJA# B@aRB@x@A ARE FYYaR =Rڀ=R"@ր@ c@``"xBboՠb@1j B@jB@x@A$AjEQb jZZE =j=(%-(%@`@$BB B@JB@x@AAJEJRJ[[e=\Fy = ÀO{% "@EK@ ': @+c iAb  b B@`BB@x@A$AESa\]b =jŀ= c@Ā@,JàJA#Fc B@aRB@x@A ARE|TaR@Y F^^aR =RY=R"@@@``#&XjccE =j)=j @*@ r) @"AJB( B B@JB@x@AAJE dde=ͤF ={% @@ ! @c AAbab b B@aBc@x@A$AELYbefb =!!~]Z`="@[@,JJJ B@aRB@x@A AREY[a ggaR =R=%@i@ #@``%L2Bbb@1 B@jBy@x@A$AjE jhhE =jhӀ=j%p(1@Ҁ@ $( @"@BB$B B@JB@x@AAJEg\b Jiie=F5n =S{% "@@ AbbW! 륱 B@aB@x@A$AEF]ajkb =j ^`="@l@,JJA#Fc B@aRB@x@A ARE llaR =Rŀ= @@ 3@``07Bbsb@1j B@jB@x@A$AjEy_b jmmE =j}=%p(%@b|@~B{B t B@JB,@x@AAJE5`Jnne=GF =:{% %@C6@ >Ab  B@aB@x@A$AEopbjaa^(f@@,JJ F B@aRB`x@9 AR @`Egba FqqaR =Go=R"@j@ C6%3 fBbb@( B@a$B@x@A$Aj E##cjrraj =j&=(%-(% @@  Bg% B$ B@JBc@x@AAJE sse=+Fc ={ @߀@ c +AbFb  B@aB@x@A$AE1dbtub =jhZe`=^%@X@,J3JA#F B@aRB|JeB@x@A ARE>fa vvaR =R=R%@F@ S3D3~%AaBbboc B@jB@x@A$AjÈywwaj =j]Ѐ=(%- @π@ !BB J" B@JB@x@AAJ#EKgxxe$=ͤF% =<{% "&@@F vB'b ! ( B@aB @@x@A$A)EChyyb* =jpH=j c+@F@,,J>J n- B@aRBQ@x@A AR.EY@Y zzaR/ =Ria"0@i@bc21!ů@VB1bb½2 B@jB /@x@A$Aj3E຀2 j{{aj4 =jh= 5@ǽ@~6B(B J$ 7 B@JB@٢o!K$X@x@AAJ8Efvjbw J||e9=nDQ: =@[|{% ;@w@N, c bB<bbe! c= B@`B !F@x@A$A>E1ka* }~b? =j= @@x@ m!j @AJJ B B@aRB@x@A ARCElaRaRD =R̰=!E@@ $F @1# %s@ @yRBFbkb G B@jB @x@A$AjHEdmaj fajI =jh=%-jCJ@mg@ !j @.`@BKBfBBL B@JBc@x@AAJME naJeN=2|O = &{% +cP@B!@ @xAQb b륱R B@aB="@x@A$ASEۀ bT = ``/=^"U@ހ@,VJݠJ W B@B@x@A ARXEob RaRY =R=%pZ@@ an6[bb \ B@jB@x@A$Aj]ERpjaj^ =jV=j%-j%_@uU@n`BTB) ca B@JB@x@AAJbE#qJec=+܃|d = {% ce@^@ ) @ fb ! bcg B@aB @x@A$AhEɀbi =jщra"j@0@,kJJ l B@aRB@x@A ARmE@sa %T63n =RVH= o@C@ \$F @+c  #&),oDpb#b@%,q B@jBB@x@A$AjrE>@Y jajs =j=j%-(%t@#@~!j @(@BuB B$Bv B@JB@x@AAJwEŷtb Jex=FʄF$Xy ={% %z@@ ! @2A{bab! b륱| B@aBB@x@A$A}EKsua,:$^~ =j3v`="@1@,JJJ Rc B@aRB@x@A AREY@Y aR =R=R"@Y@!~" 258;>ADGJ@WBbbb B@jB5n@x@A$AjEߥwb jaj =jp=j%-(%@ͨ@~!j @ťB,BB B@JB@x@AAJEfaxJe=F =Rg{"@b@ c ]Abb  B@aB,@x@A$AEyacb =j݀=$K@|ۀ@,JڠJA#(> B@aRB* @x@A AREzaR FaR =R=R%@ @ c   a Bbvb@$j B@jBc@x@A$AjEO{jaj =jR=(%-%@W@ nBQ J$  B@JB* @x@AAJE |Je=|* ={*"@D @ ! @c tAb ^! b B@aB@x@A$AEƀb =j}a^K@@,JJA#F B@aRB$X@x@A ARE=~a aR =R FaR =@=!@F@a u¹`pBbb@$ B@B@x@A$AjEĢb jaj =jH=%-j%@@) @(@BBBB)B B@JB@x@AAJEK^Je=̤Fz* =/d{% %@_@-I! @9  Ab  b륱 B@aB@x@A$AEacb =jـ=j^"@Y؀@,FJנJA#F B@aRB@x@A AREߐaR FaR =R="@@ ¸uҒ" u 0BbcbA;j B@jB]L@x@A$AjEfLjaj =jO=%-(%@I@ r$( @"@BBN J B@JB@x@AAJEJe=r|v = {% %@* @ ) @+c Abb b륱 B@aB@x@A$AEs ^b =jȀ=^"@xƀ@,JŀJ R B@aRB@x@A ARE~aR =R="@@ y8"¯uuam4Bbnb j B@jBy@x@A$AjE:jaj =j >=j%p(%@e=@ B<B B@JB@x@AAJE} e=ă| ={% "@B@2B! @+c Ab c B@aB*@x@A$AEbb =jq`="@%p@,JoJA# B@aRB{@x@A ARP`E(a aR =RZ0= @+@ @C BC2 b½ @a$B B@x` =Aj @`E" jaj = ?`== @@~$( @wK x%Bw怃(B B@B,@x@AAJ Ee =.F ={%p .`@3e @ 䠀@4f c BAbEb ! c B@`B "B@x@A$AE0[ab =)_`=$ .`@3kc @ `@,J+J@p B@B@x@A ARE=Ҡ@Y FaR =ـ=R(o@FՀ@ !$F!0 5 @BbԀbf B@jB @x@A$AjEčb jaj =jL=(%p .`@3A @ @~!j @ťBB$B B@Bc@x@AAJEKIJe =̤F* ! =G?O{% .`@3B" @ J@ ! @[A#b ! b륱$ B@`B@x@A$A%Ea b& =)x =^"'@@,(JFJ (Rc) B@aRB@x@A AR*EX@Y RaR+ =R!Ȁ=R%,@pÀ@! #D@-b€bj. B@jB@x@A$Aj/E{b jaj0 =jg=(%- .`@3C1 @ ~@<$( @"@B2B'BB$B3 B@B@oB&$X@x@AAJ4Ef7Je5=nw *A6 = )b={% .`@3B7 @ 8@c  A8bb 륱9 B@`B !!c@x@A$A:Ecb; =)aj$ .`@3e< @ `@ m, @,=J밠J@p4` > B@B$X@x@A AR?Eia FaR@ =q="A@m@ 3Tү0uBBbrlbjC B@jB@x@A$AjDE%jajE =j )=%- .`@3AF @ d(@$GB'B JH B@Bc@x@AAJIE eJ=F,K =G{% .`@3EL @ E@ ': @c AMb ! b N B@`Bc@x@A$AOEbbP =)\`=%p .`@3eQ @ `[@,RJZJ@peRS B@B@x@A ARTEa aRU =]=R+cV@@ cC% SDPaWbb jX B@jB@x@A$AjYE"π jajZ =jҀ= [@@$( @ūc\BrрB$Bc] B@JB@x@AAJ^Eb* e_=*F* ` ={% .`@3ba @ ㋀@ c IpEbbIb cc B@`B@x@A$AdE0Fabe =)Y`=j^"f@@,,gJ'J h B@aRB@x@A ARiE= FaRj =RĀ=R%k@I@ o!oS0cppr!dlbbA;/fm B@jBQ@x@A$AjnExb jajo =jT|=j%- .`@3Cp @ {@ rqBBJr B@B@x@AAJsEK4aJ Jet=̤F{u =G3:{% .`@3Bv @ 5@ @-oAwb b x B@`BB@x@A$AyEcbz =)a%{@\@,c|JȭJA#Fc} B@aRB@x@A AR~Efa aR =Rn=%p .`@3b @ i@c +eS,7bKbf B@Bc@x@A$AjEf"ajfaj =)%=j @A@ B$J B@JB C5 @'@x@AAJE݀ e=mF ={% .`@3b @ @'߀@ 5\ @ @ bހb b B@`B !@x@A$AEsbb =)Y`="@X@ m!j$JrWJ B@aRB@x@A AREa aR =R.=! .`@3b @ @ s01 PP0_"Hobb f`[ B@B ,@x@A$AjÈjaj =){π=j @΀@~ `@(AJB;B B B@JB@!K@x@AAJEe=F, =@~{% .`@3b @ @Ȉ@ , Ab*b !  B@`B @x@A$AECab =)?`="@@ mJ J c B@aRB@x@A ARE"@Y aR =R= @@B-'QZ` q0@`Bbbĩ B@jB; @x@A$AjEub jaj =j-y=j @x@~BwB$  B@JB@x@AAJE01aJCe=F = 7{% .` e @ j2@ $F @c "BbС ! bXF B@`B@x@A$AEt b =)P="@@,JJA#(> B@aRB@x@A ARE=b RaR =R=R6@M@ E*arBbb j B@jB$X@x@A$AjEcjaj =j B@aRB@x@A AREa(aR =RE`="@~ $F!oE T{S KDapDb b  B@jB@x@A$AjE, faj ==%- .`@3A @ 김@ r!j @"@BBKB B@B@x@AAJErbw Je=F =Gzx{% .`@3E @ s@ ! @MAb*b b륱 B@`B@x@A$AE.acb =)A=j$ .`@3e @ `@,JJ@p B@Bz$X@x@A ARE"aR aR =¬=R+c@@ y;SrSqSq䐂aBbb½ROjBB@x@A$AjE`aj2 jaj =j1d=j%p `@3A@c@~$( @ťBbB Jc B@JB@y@x@AAJE/aJ Je=Fw =@$"{% " @m@ B c ]A b ! 륱 B@`B @x@A$A E׀b =j˗a @-@ m!j @,JJ  B@aRB@x@A ARENa aR =RlV=R"@Q@!Ro3t?Sq5n,!db,b½ B@jBB@x@ =Aj @`EJ aj jaj = ?`= =j%p(C@-@~B B$  B@Bc@x@AAJE Je=R؄F ˀ{% "@ǀ@ ! @'Lbmƀb b B@aB@x@A$A!EXbb" =A`=$j(f#@?@,B$JOJA#% B@aRB@x@A AR&Ee aR' =R6aRR"(@y~ t"aD)bbĩ* B@jB@x@A$Aj+E쳁, jaj, =j|=j%-%-@׶@~$( @"@B.B8B/ B@JB@x@AAJ0Esobw> Je1=F2 ={u{% "3@p@ ) @ [A4bb! b륱5 B@aB - @x@A$A6E*acb7 =j+="8@@,c9JJ : B@aRBv@x@A AR;EaR aR< =R= =@@ c,Ki,aB>bkb½? B@jBB@x@A$Aj@E]ajF jajA =ja=%p(%B@i`@ CB_BJ$ cD B@JB5n@x@AAJEEaJ JeF=+|G ={% %H@N@ ! @K )cAIb  bXFJ B@aB@x@A$AKEԀbL =j8ـ=^(fM@׀@,NJJ cO B@aRB|@x@A ARPE"aRQ =Rٗ=R"R@6@y% .-P"a]LSbb jT B@jB@@x@A$AjUEKjajV =j5O=j%-(%W@N@ n$( @"@BXBMB JY B@JBc@x@AAJZE/Je[=7Ճ@\ = {"]@k@ 2]L^b _ B@aBc@x@A$A`E€ba =jXǀ=j b@ŀ@,cJ&J d B@aRB@x@A AReE=~b aRf =R=FR%g@E@ $F @o{aDfhbb ji B@jB @x@A$AjjE9ajajk =jS==%pl@<@!j @ mBB Jn B@JB@x@AAJoEJJep=VÃw!^|5nq = )N{*"r@@c ?aBsb ct B@aB "@x@A$AuEѰb 5j^Jobv =jp`=j$j(fw@\o@,xJnJ >yy B@aRB@x@A ARzE'aRA@ A{ =@/=R"|@*@ #+bp+e" ZsB}bNb@&4j~ B@By @x@A$AjEe@Y jaj =j=j%p+c@P@~B倃 BuƁJB@x@AAJ @E@==qFw, =ؤ{% "@&@ `@+c Abb B@abB@x@A$AjErZab =j`=G@1² @@,cJYJ  B@aRB@x@A AREѠ@Y F E =R5ـ="@Ԁ@ $F @%3 "7|@BbӀb B@jB@x@A$AjEb jaj =j=j @揀@ !j @(AJBKBJ B@JB C&$X@x@AAJEHaJ Je=[F* =N{F% @I@, 7Ab)b c B@aB,@x@A$AEa b =j2Ā=j$j"@€@ m' @JJ (R B@aRB@x@A ARE!{aR   aR =Rς=F!R%@*~@ C 5Bb}b f B@jBc@x@A$AjE6j  G =j0:=j%pj+c@9@rB8B B@JBc@x@AAJE/   e=Fy ={% "@i@ $F @+c MAb ! b B@aB@x@A$AEb b =jm`="@Hl@,JkJA# B@aRB* @x@A ARE$a aR =Ri,=R"@'@  SP _b n"~FިBb7bA;j B@jB@x@A$AjEJ@Y jaj =j=( @2@~B  B$ , B@JB@x@AAJEћb Je=RF{ ={% @ @ ! @+c zZBbmb! b, B@aB@x@A$AEWWacb =j`=^+c@@,JVJA#Rc B@aRB@x@A AREeΠ@Y aR =Rր=R%@iр@ c89:QZp!u8@ybРb@%j B@jB@x@A$AjEb jaj =j|=j%-(+c@،@ $( @"@BB8B$B B@JB@x@AAJErEaJye=Fc =bK{"@F@ c Nmbbj! B@aB@x@A$AEacb =j ="@@,J쾠J F B@aRB@x@A ARExaR FaR ==R%@{@ sLu9TG!%䅻 Dfb~zb@&j B@jBz@x@A$AjE3jaj =j 7=(%-@j6@$B5B J$ ic B@JB@@x@AAJE e=| =@{% "@O@ ': @c fBb  B@`B@x@A$AEbb =!bj`=^%@!i@ m'%JhJ  B@aRB @x@A ARE!a aR =R?)=R"@$@ qTB9r abb½ B@jB @x@A$AjE/ݠ@Y jaj =j=(%-(+c@@$%{߀ J B@JB@U@x@AAJE  eI7F {@={%p"@@ , h!bYb .` `BV@x@A$AE! B@aRB| @x@A AR"Eta F((aR# =R|="$@w@ 䅯9 B%bcb@&'& B@jB{@x@A$Aj'Er0j))aj( =j3=%p(+c)@T@ r*B2 J+ B@JB@x@AAJ,E **e-=zF. ={%/@5@ ) @ hv/G?3A0bb b .`1aB@x@A$A2Ebc+,b3 =jg`=j 4@ f@,5JveJA7F6 B@aRB* @x@A AR7Ea --aR8 =R<&=R"9@!@ -09",B:bbo; B@jBJ@x@A$Aj<Eڀ2 j..aj= =j|݀=j >@܀@ ?B@B@ B@JB @x@AAJAE//eB=F*C =@{% :"D@ז@ ! @+c $FBEb6b ! b .`F`B@x@A$AGE!Qa01bH =jQ`=j I@@,JJ JA7K B@aRB@x@A ARLE/ȀF22aRM = π=FR"N@7ˀ@ cÿ,38c16ObʀbP B@Bc@x@A$AjQEb 33ajR =j==j S@@~$( @ .`CűT B$ByU B@JB@U@x@AAJVE 99el=_Fm ={% (n@@]L Aobzb! .`a paB@x@A$AqEdbc:;br =jd`="s@b@ '$ ! !% ARtJ_J u B@aRB@x@A ARvEra <>e=F = 5k{"@@ cA1Abb ! B@aB@x@A$AENa?@b =j/`=%p.@ @,J JA#Fc B@aRB@x@A ARE FAAaR =R̀=R%@ Ȁ@L6P|1Np!ɐaBbǀb j B@jBL6@x@A$AjEb jBBaj = "=(%-%@}@$B₀B$ .`B@@x@AAJE!<JCCe=Fn, =@B{% "@^=@; ?Ab  .`ac`B$X@x@A$AEDEb =jշa^%@6@ mcJJA7 B@aRB$X@x@A AREna FFFaR =R^v=R"@q@ o)'E (/<TBbb½ B@jB@x@A$AjE<*jGGaj =j-=(%-(%@#@$B, J B@JB@@x@AAJE HHe=HFy =@{% "@@ $F @ SAb_b ! b .``B@x@A$AEI`cIJb =!!na`=j$j%@_@ m!j @J=`=,? =@{% %@@Q@ ! @` Q.W AAb ! bB B@`B,@x@A$ACEV`hibD =j`="E@%@,FJJ G B@aRB@x@A ARHÈjjaRI =R<Հ= J@Ѐ@ c1 9u:)"taBKbb½L B@jB} ]L@x@A$AjME.b fkkajN =j=j%-(%O@@~$( @4@BPBr B$BQ B@JB@x@AAJREDaJ> JlleS=6WFT =J{% %U@E@ y) @` E !\AVbQb! b륱W B@aB@x@A$AXE;`cmnbY =jm="Z@ξ@ !j$c[J:JA#\ B@aRB@x@A AR]EIwaR ooaR^ =R~=R"_@Mz@ p` 8*  `byb½a B@jB@x@A$AjbE2ajF jppajc =jX6=(%-(%d@5@ eBBJ$ cf B@JB@x@AAJgEV Jqqeh=ۤFbi =B{% "j@@ y! @b& E+c  kb  bcl B@aB@x@A$AmEݩ`t rrbnj=^(fo@鬀@,pJUJA#q B@aRBy@x@A ARrEdeRssaRs =m=R"t@|h@ :"!_maDubgb jv B@jBy@x@A$AjwE jttajx =js$=j%-(%y@#@ n$( @"@BzB/B{ B@JB@,@x@AAJ|Eq܀ uue}=yy~ =@]{"@݀@ Ab b B@`B@x@A$AE!bvwb =j3X"`=j+""@V@,JUJA# B@aRB@x@A ARE#a xxaR =R=FR%@@ 19t@VBbib½ B@jB@x@A$AjEʀ jyyaj =@݀΀=%p%@v̀@$B̀BJ B@Bc@x@AAJE$zze=F@ ={*"@O@ ': @` c }Ab  bc B@aB 'Z @x@A$AEA%`{:#A& =j&`=j^%@0@,J  B@aRB@x@A ARE@Y: F}}aR =Rc="@@ +d -LtBb'b½ B@jB@x@A$AjE.t'b~~aj =jw= @ @$Brv JFJB@@x@AAJ @E/(Je=6B|ژ =5{% @0@ 5\ @` E6 >{BbTb b B@abB@x@A$AE;b =jc)a  @Ʃ@,J2J (F B@aRB@x@A AREIb*a aR =R j=R(o@ae@ * 7{|laeBbdb j B@jB oy@x@A$AjE+aj j&(o =jS!=j @ @~r) @(AJBB B@JBc@x@AAJEV Je=פFB =N߀{% @ڀ@t Ab ! c B@aB* @x@A$AEݔ,bcb =j U-`= @pS@,JRJ(R B@aRB@@x@A ARE .a $$F =R=R%@@ @` Ho"? n@|ybVbj B@jBژ@x@A$AjEqǀ jaj =jʀ=j%p(1@W@~Bɀ B$ y B@JB@x@AAJE/b Je=yF =숀{% "@1@  c |ybb! y B@aBc@x@A$AE~>0b =j.C="@A@,J@J c B@aRB,@x@A ARE aR =R1aR"@~ ?D%bDb}b½ B@jBx@x@A$AjE, j&' =j=(%-(%@p@ rBзBJ$  B@JB@x@AAJEq2bw Je=?w!^| = ) w{% "@Mr@ ! @` >Ab  b B@aBc@x@A$AE,3`b =j=^.@,@,cJJA#4` c B@aRBnb@x@A ARE4aR aR =RU=R"@@ D w 5 U&J27Bbbj B@jB3k@x@A$AjE-_5jaj =jb=j%-(%@ @$( @"@BBja B B@JB,@x@AAJE6Je`5-||@ = {"@@ AbPb ! B@aB,@x@A$A  E;ր ^b = >`=ڀ=j/"@?ـ@,JؠJ  B@B@x` =AR @`E‘7aR =Rb=FR%@”@ o @` o  ) 7 _ + C!,V-Bb.b@$g! B@a$BL6@x@A$Aj EHM8` aj =jP=j%p% @#@ n BOJ$ c B@JBc@x@AAJE9Je=פF ={F% "@ @ 8Abk b c B@aB#6(,@x@A$AEVĀ&.W =j:aj$j%@킀@,JYJ Fc B@aRB@x@A AREc;;a FaR =R C=FR"@g>@  #"% @<pba2zb=bf B@jBB@x@A$AjE jaj =j=%-%!@!@$"B J# B@JB@x@AAJ$EqRaR0 =!  1= 1@z,@ ) @` c3<eP @D2b+b4 3 B@jB o@x@A$Aj4E aj5 = =j 6@@ !j @"AJ7BMB8 B@B@x@AAJ9E?be:=n|; =|{% <@ơ@ ! @` DA=b,b ! bc> B@aB*" @'@x@A$A?E\@` b@ \`="A@_@@Sf'BJ^J JC B@A*Bc@x@A ARDEARaRE =@M=R(oF@@Ce#` ژGb b jH B@B @x@A$AjIE Ӏ ajJ =jր=(%p(+cK@Հ@ $$( @ťLB\B$BM B@JB@C!K@x@AAJNEBb JeO=FP =@{% "Q@㏀@ y$F @` E+c "ARbCb b륱S B@`B !@x@A$ATE-JC`ybU =jV D`=^"V@@ m!jWJ$J X B@aRB@x@A ARYE;@YA aRZ =RȀ=R"[@?Ā@ Sep8@ @B\bÀb½] B@jB@x@A$Aj^E|Eb jaj_ =jR=j%-(%`@@ aBBb B@JBy@x@AAJcEH8FJed=ɤFe =4>{"f@9@ B!%N` E4 Agb ! b h B@aBc@x@A$AiEbj =jGa j%p%k@^@,lJʱJA#(Rm B@aRB@x@A ARnEjHa FaRo =Rr=FR%p@m@! @` c+_EQ@[&BqbHbjr B@jB oc@x@A$AjsEc&I` jajt =j)= u@J@$vB(J$ w B@JB@x@AAJxE Jey=kFz ={% F"{@'@ W) @b& B+c B|bb by} B@aB "c@x@A$A~EqJ`b =j]K`=j$jK@[@,JcJ  B@aRBz9>Bh @x@A ARE~La !8 =R;="@@ s6 pBbbf B@jB @x@ =Aj @`E jaj =jӀ=%-+c@Ҁ@$BMB J B@aB@!K@x@AAJEMbye= Fvy =@{% %@͌@ b+b B@`B0 @x@A$AEGNa bjK=^%@J@ m'%5nJ~INFc B@aRB @x@A AREO RaR =G =!@@ y zu{luadDb b4j B@jBy@x@A$AjE 2 jaj =j=j @@ / @"AJBd B B@JBc@x@AAJEyPe=F/ ={F% @z@ W`@` c ;AbBb c B@aB< Z@x@A$AE-5Q`b =jP=j^"@@,JJ! B@aRB@x@A ARE;RaRFaR =Rݳ=FR(o@?@ |{|"\4alZBbb B@jB5n@x@A$AjEgSaj aj =jIk=j%p(+c@j@~BB B@JB@x@AAJEH#TJe=ɤF}c =4){% "@$@W @` E+c ȨAb ! b B@aB@x@A$AEހt b =jy="@@,JGJ  B@aRB@x@A AREUUb RaR =R=R"@^@ cld?p_ n a#Bbʜb$ B@jB o@x@A$AjEUVjaj =jdY=( @X@ B B, F B@JB@x@AAJEcWaJe=k߃, = À[{% @@ c yBbb  B@`Bc@x@A$AÈb =jXa^+c@x@,J䊠J  B@aRB@x@A ARECYa aR =RK=R%@F@$X ?p Wh0aBbGbf B@jB@x@A$AjE~ jaj =jZa(%-(+c@a@$BBJ)  B@JB@x@AAJE Je=̈́Fy ={% "@B@ * ': @a c Ab  b B@aB@x@A$AEv[`b =j6\`=j%@5@,J~4JA# B@aRB@x@A ARE aR =RJ="@@ c h03t t]zBbb j B@jB@x@A$AjE]b j] =j=j%-(%@@$( @"@BBt B B@JB@y@x@AAJEd^aJce='wF{ =@j{F% %@e@  AbFbj! 륱 B@`B !@x@A$AE- _acb =jB=j @ހ@,JJ aK aRB@x@A AR@> @:`aR FaR 6R=FR"@J@ _P1n`63t@: @Rajaj 6jIV=%p(%@U@ n BB J$ c B@@Bc@x@AAJ @> @HbJe @6 @ɤF 64{% "@@ ! @` c Ab ! bc B@@6ǠB@x@A$A@> @6ɀb 6jca j^(f@Y@,JŇJ@j B@@By@x@A AR@> @5\@da aR 6RH=R"@C@ yN`5тx\|zBbPb@j B@@By o@x@A$Aj@> @c@ jaj 6j=j%-(%@J@~$( @"@BB B$B B@@B@x@AAJ @> @ee!@6 @kʄFwc" 6ҽ{% "#@$@ c A$bb 륱% B@@B* @x@A$A&@> @psfb' 6jx=j (@xv@,)JuJ@ܣ>* B@@BQ@x@A AR+@> @.gRaR, 6R6=R"-@1@ }arQ@ho.bgb@%j/ B@@B,@x@A$Aj0@> @6 aj1 6j="2@k@ 3BB4 B@@B@x@AAJ5@> @he6@6 @ t|; 7 6{% 8@@@ , inW9b ^! : B@@Bc@x@A$A;@> @aib< 6 >  :f=j = 9@d@,>JJ F? B@@B@x@A AR@@> @jRaRA 6$=R%B@ @ |tk@)SHoCbb jD B@@B|c@x@A$AjE@> @ ajF 6j!܀=j*(+cG@|ۀ@0HBB$ I B@@B@x@AAJJ@> @keK@6 @'bL 6{:% "M@U@ m c ANb ! XFO B@@Bc@x@A$AP@> @OlaybQ 6jm`=j R@1@,SJ J@T B@@B@x@A ARU@> @ FaRV 6RY΀=FR"W@ɀ@ !$F @a   k@xXb'b jY B@@B@x@A$AjZ@> @82n`.aj[ 6jʅ=%-(%\@&@$]B J$ ^ B@@Bc@x@AAJ_@> @=oJe`@6 @BPFa 6C{% "b@>@ " @` wAcbab bd B@@B@x@A$Ae@> @Hbf 6jypa j$j3g@ڷ@,hJFJ@i B@@B@x@A ARj@> @Upqa aRk 6Rw="l@Ys@, #q l@Bmbrbon B@@By@x@A$Ajo@> @82+rjajp 6jT/=%-%q@.@$rBB Js B@@B@8@x@AAJt@> @b eu@6 @Fv 6@  S{% %w 9@@, j Axb ! y B@@B W!,@x@A$Az@> @sbcb{ 6)ct`=j |@pa@ m, @}J`J@c~ B@@B@x@A AR@> @6ua aR 6R!=R"@@ 3 nEhqB"Q}Bbsb B@@B@x@A$Aj@> @} jaj 6jـ= @b؀@ ) @(AJB׀B J$Bc B@@B@$@x@AAJ@> @ve@6 @F|c 6@  {%  9@?@ O! @` c Ab ! bc B@@6ǠB@x@A$A@> @Lw`cb 6 >   x`=%pj" 9@ @ mJ J  `R-_ B@@B@x@A AR@> @5\ FaR 6Nˀ=R%@ƀ@ cC`76Ô6@@xbb jc B@@B$X@x@A$Aj@> @yb jaj 6j=j%-+c@@~$( @r"@BBWB B B@@B@x@AAJ@> @:zJe@6 @'MF 6@{% "@;@ c uxbBb ! 륱 B@@By@x@A$A@> @,cb 6j_{a"@@,J+J@g B@@Bc@x@A AR@> @5\m|a FaR 6Rt= @Np@ $F @` oS9P~0~ۏ @Dfbobĩ B@@B !@x@A$Aj@> @(}` jaj 6j=,=j%p(%@+@ !j @ťB*B$B B@@B@x@AAJ@> @G Je@6 @ȤF$X 6D{F% %@@ @! @b& (Ab ! b륱 B@@B@x@A$A@> @Ο~`t b 6jd=j$@j @3kc@Ƣ@,J2J@n B@@B$X@x@A AR@> @6[RaR 6A  c=R" 9@M^@ !d"!` c`ү"v?@4b]b j B@@B@x@A$Aj@> @6`aj 6)d="@@~!jnB B$B B@@B@x@AAJ@> @bҀe@6 @j$X 6 >  b؀{% F" 9@Ӏ@ / @b& B6 14Ebb ! b B@@B@x@A$A@> @6`  b 6)N`=^"@tL@,JKJ >c B@@B@x@A AR@> @aaR 6 >   =R" 9@@ cs*? WybBb{b  B@@B@x@A$Aj@> @6jaj 6)Ā=(%-(+c@`À@ B€B $  B@@B@x@AAJ@> @|e@6 @F$X 6 >  {% " 9@E}@$X  Ab , B@@B@x@A$A@> @7ab 6)=j)j%@@,J}J@ܦ{ B@@BzFB@@x@A AR@> @6aRA@ A 6@  I=" 9@@ o$F!` Ho"QёaBb bj B@@B@x@A$Aj@> @6j` jaj 6 >  m=%-% 9@@$BclJ B@@B@x@AAJ@> @%aJ J@=@6 @+8|vy 6G+{% %@&@ dAbBb B@@B 4  @'@x@A$Aj@ @6b 6jQaj$j%@@,JJ@nc B@@B@x@A AR@> @6Xa aR 6R_="@:[@ , @&QBZb½ B@ B* @x@A$AjEj G =jM=j @@ ' @"AJB B B@JBB@x@AAJEGπ e=ȤF$X =7Հ{F% F% @Ѐ@ @b& A b ! bc B@aB,@x@A$A EΊ`b =jz= @ڍ@,JFJA# B@aRBy@x@A AREUFaR   aR =RM=R"@MI@ c a K_Ea bHb j B@jB,@x@A$AjEaj j  aj =jc=j%p(+c@@~BB$  B@JBc@x@AAJEb J  e=j = ÀVÀ{% "@@ v b ! ! B@`Bc@x@A$A"Exb  b# =j 9`="$@7@,%J6J(Rc& B@aRB@x@A AR'E aR( = = )@@$X * $X*bfb@&+ B@B]L@x@A$Aj,E}b jaj- =j=j%-(%.@Z@~r/B B$ 0 B@JB@x@AAJ1EgJ >2=yF,3 =l{% %4@?h@$X G$X5b ! 6 B@aB@x@A$Aj7E"ab8 = `="9@ @,:JyJ F; B@B* @x@A AR<EaR FaR= =R?=R">@@ c _EasD?bb½@ B@jBc@x@A$AjAEUaj jajB =jX=(%-(%C@W@ DBOB$ E B@JB@x@AAJFEaJ JeG=*#|yH ={% "I@@ c AJbAb! K B@aBc@x@A$ALE,̀ bM =jЀ=j3N@<π@,OJΠJ P B@aRB@x@A ARQEbaRR =RP=R"S@@ \_ |aBTbb jU B@jB@x@A$AjVE:CajajW =jF=j%-(%X@@~YBzE B$ Z B@JB@x@AAJ[E e\=ȤF] = Àa:% "^@ y! @`_ A_b\b! b` B@`B@x@A$AaEG,cbb =jpza j c@x@,dJ>J e B@aRB@x@A ARfET1a aRg =R9=FR"h@]4@ yֿK_EavBib3b½j B@jB@x@A$AjkE jajl =jW=%-(%m@@ n$( @(@BnBB J$Bo B@JB@@x@AAJpEbeq=Fyr =@N{% "s@@ y ^Atb ! 륱u B@`B@x@A$AvEca bw =j$`=j^"x@s"@,yJ!JA#cz B@aRB@x@A AR{E F!!aR| =R="}@ހ@ ; AajB~br݀b j B@jB@x@A$AjE}b j""aj =j=%-(%@a@$BŘB c B@JB@@x@AAJERJ##e=dFs =@W{%@>S@ ': @` c tAb  B@`B,@x@A$AE `c$%b = `̀=j @ ̀@ m' @* JyˠJ  B@B@x@A AREaR F&&aR =RE=R"@@ /*a@Bb bf B@jB@x@A$AjE@j''aj = C=%-@B@ $( @n(AJB^B$B B@B@@x@AAJE ((e=&|| =@a%p"@ nb$F @`_ E AbAb ! bc B@`B@x@A$AE,,c)*b =^wa %pj"@u@ m!j @J+J  B@aRB@x@A ARE9.a ++aR =R5=!R"@91@, hqB"\}hJBb0bA;# B@jBF@x@A$AjE j,,aj =jP=j%-j+c@@ B B$ c B@JB@x@AAJEG--e=ȤF =+{% "@}@, uAb ȥ!  B@aB |- B@x@A$AE`ac./b =j `="@L@,JJ F B@aRBc@x@A ARE F00aR =R߀= @ڀ@ c#9Q/>ttaKBbOb j B@jB,@x@A$AjEbb j11aj =jږ=j%-(%@6@~B B$  B@JB@x@AAJENJ22e=jaF =T{F% %@#P@, ;AbOb  B@aBy@x@A$AEo a34b =jʀ=j^+c@ɀ@,JnȠJ  B@aRB@x@A ARE}aR F55aR =R/=R"@@o!` N!(o3t\9<@Bbbĩ B@jB /@x@A$AjE=` j66aj =j@="@?@~BCB B@JCB !Ky@x@AAJE J77e= |x =@{% @@ y! @b& _YBb&b ! b B@`B@x@A$AE`88b =j=^%@ @ mJyJ  B@aRB* @x@A AREoaR 99aR =RDw=R%@r@ cCl@KӐ:Bbb$ B@jBt@x@A$AjE+j::aj =j.=(*(+c@-@ $( @"@BB^B $Bc B@JBc@x@AAJE ;;e=F ={% "@@ $F @b& E cAbAbǝ! bc B@aB 'Z@x@A$AE,`<=b =j\b`=j)j"@`@,J*J  B@aRB@x@A ARE9a>>aR =R ="@I@ SK` sP<0y-Bbb jc B@jB* @x@A$AjEԀj??aj =jL؀=%-%@׀@$B B J$ y B@JB@$c@x@AAJEGb @@e=ȤFzc =@[{% %@@ y': @` E.W wb  B@`Bc@x 9A$A @EK`ABb = ?`= `=j @T @ m' @y J J  B@B@x@A ARE€FCCaR = ʀ="@ŀ@ cې;`&6s &5nb_b j B@B o@x@A$AjEa~b fDDaj =j⁀=j%-(%@A@$( @n(@BB B B@JB@@x@AAJE9aJEEe=iLF{ =@?{F%p%@*;@ c 5nb:bj! c B@`Bc@x@A$AEocFGb =aj%pj" @@,!JbJ " B@aRB@x@A AR#E|la FHHaR$ =R1t=R"%@o@ s6sޚDf&bnb½' B@jB@x@A$Aj(E(jIIaj) =jw+=j%p%*@*@ +B3B$ c, B@JB@x@AAJ-E JJe.= F/ =z{% "0@@ c zhA1b&b ! y2 B@aB @x@A$A3EbKLb4 = `2_`= 5@]@,6J\J 7 B@B@x@A AR8Ea MMaR9 =R=R":@*@@  =@Հ=j%-(%?@tԀ@ @BB$ A B@B@x@AAJBE+OOeC=FD = {% "E@e@ y/ @` Q6 _5nFb ! bG B@aB .X@x@A$AHEH`PQbI = ```="J@A@,KJJ L B@B@x@A ARME FRRaRN = iǀ= O@€@ L1,lL6Pb0b jcQ B@BB@x@A$AjREF{b jSSajS =j~=j%-(%T@"@~UB} B$ V B@JB@\$@x@AAJWE6JTTeX=NIF,Y =@<{% %Z@8@ ! @` E( A[bi7b b\ B@`Bc@x@A$A]ET UUb^ =j="_@X@ m'$`JJ a B@aRBژ@x@A ARbEۭbVVaRc =R=R"d@װ@ cL6q ebCbĩf B@jBW@x@A$AjgEaiaj WWajh =jl=(%-(%i@;@ $( @4@BjBkJk B@JBc@x@AAJlE$JXXem=F; n =*{% "o@$&@ W$F @` E+c qSApb%b b륱q B@aBc@x@A$ArEoYZbs =ja ^"t@@@Sf!juJZJA#Jv B@aRB@x@A ARwE|Wa F[[aRx =@(_=R"y@Z@$X L60tBzbYbf{ B@B y@x@A$Aj|Ej\\aj} =js=(%p(%~@@ rB7B J B@JB@!Kc@x@AAJE ]]e= Fuy =@zԀ{% "@π@ SAb&b ! B@`Bc@x@A$AE^^b =j=j%pj%@@@SmJ|JA#J B@aRBB@x@ =AR @`EEaRu__aR =@`=PM=R"@H@ c 0Ӕ_P91$`̠Bbb j B@B@x@A$AjEaj f``aj =j=j @ @ Bj$  B@JBc@x@AAJE Jaae=F =€{% F"@߽@ $F @b& c BbAb b B@aB@x@A$AE+x`bcb =jY8`=%@6@,J&JA# B@aRB@x@A ARE9@Y ddaR =R="@E@ co`'$/+an`,ƱBbb½ B@jB@x@A$AjEb jeeaj =jL=%-(+c@@~BB B@JB@x@AAJEFfJffe=ǤF|c =6l{% "@g@ ! @` E.W bCAb ! b B@aBc@x@A$AE!`ghb =j=^%@X@,JߠJA#(Rc B@aRB@x@A AREژaR FiiaR =R=R"@⛀@ ,$?o`kpepa :BbNbj B@jB$X@x@A$AjEaTajjjaj =jW=j @I@ $( @"AJBV B$Bc B@JB@x@AAJEaJ kke=i"| ={% @"@ W c Abb! c B@aB@x@A$AEnˀclmb =ja"@@,JaJA# B@aRB@x@A ARE|Ba nnaR =R&J= @E@ e`P Kf$XbDb½ B@jB~@x@A$AjE jooaj =jaj%-(+c@@ BKB$  B@JB@x@AAJE Jppe=̄F, =z{F% (@ĺ@ ! @a c V!b%b ! b B@aB@x@A$AEu`qrb =j+5`=j^K@3@,J2JA# B@aRB@x@A ARE쀈 ssaR =R=R"@.@ |`%qbb j B@jB}@x@A$AjEb jttaj =j0="@@ƹ$(n"EB쩀BB$B B@JB@x@AAJE+cJuue=F =i{% @fd@ c qb  B@aB@x@A$AEavwb =jހ=^"@5݀@,,JܠJA# B@aRB@x@A AREaR FxxaR =Rr=R%@Ϙ@ `l Q`Nb;b½ B@jBc@x@A$AjEFQjyyaj =jT=(%-(+c@3@ rBS J B@JB@x@AAJE Jzze  N| ={% "@@ l6bi b  B@a$B@x` =A @ESȀc{|b =ja^%@ކ@@Sf'+> ! $ C>JJJ J B@a B]L@x@A AR Ea?a }}aR =@ G=R" @iB@B #Fhqa+Aj bAb B@B@x@A$AjEc~~aj =jt=( @@$B0B B@JB@x@AAJEnbe=F =c{% @@$X Bbb !  B@aB@x@A$AEqab =j2`=%pj%@x0@,J/JA# B@aRB@x@A ARE aR =R=R%!@@ 3B"K^d?` n@"b{b c# B@jBy@x@A$Aj$Eb jaj% =j= &@w@ c'BզB$ ( B@JB@x@AAJ)E`Je*=rFq+ =f{% F",@La@ ! @` E-b  b. B@aB @x@A$A/E`b0 =jۀ=j^%1@ ڀ@,2Jy٠J 3 B@aRB; @x@A AR4EaR FaR5 =RL="6@@ C`zqBE %s)B7bb½8 B@jBB@x@A$Aj9E+Njaj: =jQ=%-(1;@ @$<BgP J= B@JB@x@AAJ>E aJ e?=3|}@ ={% %A@ @ / @` E+c ABbNb! b C B@aBc@x@A$ADE8ŀcbE =j`a j F@Ã@,GJ/J cH B@aRB@x@A ARIEFb½M B@B o@x@A$AjNE jAO =jQ= P@@ ) @(AJQBBBcR B@JB@!K@x@AAJSESeT=ԤF; U =@G{% V@@ c AWb X B@`B !* @x@A$AYEnacbZ =j/`= [@e-@,\J,J ] B@aRB@x@A AR^E FaR_ =R=R%`@@ @a Ho%cn+\?g@]ab_b jb B@jB @x@A$AjcEn` jajd =j=j%-(+ce@L@~fB B$ g B@JBc@x@AAJhE\Jei=voFj =b{ k@1^@c lb]b m B@aB@x@A$AnE|abo =j؀="p@׀@,qJr֠JA#jr B@aRBc@x@A ARsEaR FaRt =RI=%pF%u@@ sEqBaDvbbĩw B@jBژ@x@A$AjxEKjajy =jN=j%-j%z@M@ {B\B| B@JB@x@AAJ}EJe~=| = À {F% "@@ ! @` Ab2b ! b B@`B* @x@A$AE€@b =jFa j^.@@,JJ  B@aRB@x@A > E+9a aR =R@=R"@3<@ y1N`3da,b;b j B@jBQ@x@A$AjEaj =j=="@@ƹ$(%"DBB J$Bc B@JB@x@AAJE8b e=„F~ =,{% @u@ 5n 2b ! c B@aB@x@A$$^Ekab =j+`=^"@F*@,J)J >c B@aRB@x@A ARE aR =Ra=R%@@h `䐃, a7VDfb,b  B@jBɂ@x@A$AjESb faj = ߡ=(%-(+c@:@ rB , B@B@x@AAJEYJe=[lFc =_{% "@[@ @` c AbvZb b B@aB 4 @x@A$AE``c9 =jՀ=^%@Ӏ@,JWJ F B@aRB$X@x@A AREnaR FaR =R=R"@v@ c-l`aӋBb⎀bf B@jB*@x@A$AjEG2 2 jaj =jqK=(%-(%@J@~B-B J ` B@JB@x@AAJE{aJ Je=FF =p {% "@@ y c wyAbb!  B@aB@x@A$AEb =ja @}@,J|J  B@aRB@x@A ARE6a aR =R==R"@9@ $F @fO o%|Np]Lybp8b½ B@jBy@x@A$.!E jaj =j"= @~@ !j @(DBBJ$B B@JB@x@AAJEb Je=F ={% `3b@W@ 5n! @%yb  b륱 B@aBy@x@A$AEhayb =j(`=j$j"@/'@ ' @J&JrR B@aRB{ @x@A ARE aR =Rg="@@ zy `Dfb%b j B@jB; @x@A$AjE8b jaj =jĞ=%-+c@ @$Bµ B@JB@x@AAJEVaJ Je=@iF = À\{% (@W@ $F @ 9Ab[b b B@`B@x@A$AEE ab =joҀ=j%pj%@Ѐ@,cJAb 'Z bc  aB@x@A$AE绀b =j | a @jz@,JyJA" B@aRB@x@A ARE2a aR =R:=R"@5@ y,Kea BB bhbA;j B@jB*@x@A$Aj E{@Y jaj =j=j%-(+c @W@~$( @ťB B$B B@JBc@x@AAJEe=F*  =ꯀ{% "@=@ c  rAb ! 륱 B@aBV@x@A$AEeab =j%`="@$@,J#JA#R B@aRBc@x@A ARE FaR =R<= @߀@ c] ^U_a@bb j B@jB* @x@A$Aj Ebcaj! =j=j%-(%"@@ #B]B$ y$ B@JB@x@AAJ%ESaJ e&=%fFc' =Y{% %(@T@ ! @c ?@)b?b^! bXF* B@aBc@x@A$A+E*acb, =jMπ="-@̀@,.JJA#/ B@aRB@x@A AR0E8aR aR1 =Rꍀ=R"2@D@,%%` "xED3bb½4 B@jB@x@A$Aj5EAjaj6 =j?E=j%-(%7@D@ $( @ūc8BCB$B9 B@JB@x@AAJ:EE e;=ƤF@< ==a"=@h >b ! ? B@aB@x@A$A@E̸,cbA =xaj"B@[w@,CJvJ cD B@aRB@x@A AREE/aaRF =R7=R%G@2@o!oqBqB}|l?"l1$HbQb I B@jB o Ǝ@x@A$AjJE` fajK =j=(%-L@E@ rMB퀃 N B@JB@x@AAJOEb JeP=hFQ =笀{% "R@"@ ! @BSbb bcT B@aB " @x@A$AUEmbacbV =j"`=^%W@ @,XJXJ cY B@aRB5n@x@A ARZE{٠@Y aR[ =R&=R"\@܀@ c#pA17@B]bۀb½^ B@jB @x@A$Aj_Eb; aj` =j=(%-(&a@뗀@$bBJB J) c B@JB@x@AAJdEPaJ ee= cFf =yV{% "g@Q@ =Ahb$b! i B@aBy@x@A$AjE abk =jJ̀=)j%l@ʀ@,mJJA#(Rcn B@aRB@x@A >oE aR aRp =RЊ=R"q@-@3\D}a8Brbbjs B@jB@x@A$AjtE>!aj jaju =j+B=%-%v@A@ ) @"@BwB@BJ$Bx B@JB@x@AAJyE* Jez=F5n{ ="a% "|@fc A}b  륱~ B@aB@x@A$AE,1 b =jS=j^"@@,J!J  B@aRB,@x@A ARE7q#b RaR =Rx=R"@Ht@ C"ET%"x\|eFBbsb  B@jB@x@A$AjE,$jaj =jJ0=j%-(%@/@BB B@JB@x@AAJEE e=Mh =-{% "@{@ c Ab ! B@aB W.X@x@A$AẸ%bb =jd&`=%@bb@,JaJ B@aRB@x@A ARE'a aR =R"="@@BS}-~abYb½ B@jBB@x@A$AjE`ր jaj =jـ=j @C@~B؀ B$  B@JB@x@AAJE(b Je=hF{ =ۗ{% @!@ B! @ qbb! b B@aB@x@A$AEmM)aBb =j *`="@ @,JXJ c B@aRB@x@A ARE{Ġ@Y aR =R̀= @sǀ@ ! @ c4+m;  RHobƀb½ B@jB,@x@A$AjE+baj =j=%-(+c@ႀ@ $( @(@BBFBB B@JB@٢o)B@x@AAJE;,aJ e= NFy =@xA{% c@<@ Ab$be B@`B@x@A$AEcb =j5-a$j"@@,JJ  B@aRB@x@A AREn.a aR =Ru=R"@$q@ cs1Ӑ5v",?Bbpb½ B@jBc@x@A$AjE)/jaj =j#-=(%-%@,@ B+B$  B@JBc@x@AAJE*倈e=Fc ={ @h@ ! @ 5Abʡ ! bc B@aB@x@A$AE0bb =j`1`=^%@C_@,J^JA# B@aRB@x@A ARE2a aR =Rh=R%@@ c {"E1nb5Bb.b B@jBc@x@A$AjEEӀ jaj =jր=(%-@%@ $( @"AJBՀJ$B B@JB@x@AAJEˎ3e=MF ={% "@@ Abgb 륱 B@aB@x@A$AERJ4a,b =} 5`=j c@@,JIJ B@aRB@x@A ARE` FaR =Rɀ=R"@dĀ@ yN"45:sBbÀb jc B@jBnb@x@A$AjE|6b jaj =jv="@@nB2BB B@JB@x@AAJEm87Je=F =a>{% @9@ c Jb b B@aB@x@A$AEcb =j"8aj @@@S= @/ @7aﱠJ J B@B@x@A AREk9a FaR@r=" ~@n@ E6AEbmbf B@jB@x@A$AjE&:jaj =)*=*(1 @m)@ r B(B J B@JB@C@x@AAJ E e =F =@{% (@J@ $F @ ;Ab ! b  B@`B@x@A$AE;bcb =j]<`=j%pj.@(\@ m!j @ .` nb [J ( B@aRB @x@A ARE=a aR =j="@@Q7$>8Vg3$b'b½ B@jB@x@A$AjE*Ѐ2 jaj =jӀ= @@~BnҀ J$ , B@JBc@x@AAJ!E>e"=1F # ={% F%$@쌀@; B%bLb & B@aBQ@x@A$A'E7G?a,b( =jr@`=%pj%)@@,*J>JA#j+ B@aRB@x@A AR,EDaR- =Rŀ=R".@I@ $9%aeB/bb 0 B@jB@x@A$Aj1EyAbjaj2 =jS}=j%-&3@|@ 4BB 5 B@JB@x@AAJ6ER5BaJ @=7=פF8 =J;{% "9@6@F A:b ǝ! ; B@aBL6@x@A$Aj<EA= =jCa >@W@,?JîJA#@ B@aRB@x@A ARAEgDa aRB =Ro=R"C@j@ K["u4ј:%aBDbZb½E B@jBW@x@A$AjFEm#EjajG =j&=j H@R@ IB% B$ cJ B@JB@x@AAJKEހ} eL=uFM ={% N@0@ ! @+c BOb߀b! bcP B@aB @x@A$AQEzFbbR =jZG`="S@X@,TJiJ U B@aRBh@x@A ARVEHaaRW =R;= X@@ ;% <  a BYbb jZ B@jB@x@W =Aj[ @`È f  aj\ = {=Ѐ=%-(+c]@π@~$( @.`@B^BKBB_ B@aB@@x@AAJ`EIb J  ea=F`=vBb =@{% (c@щ@ y5\ @+c (Adb1be B@`B@x@A$AfEDJajc  bg =jDK`=^"h@@,iJJ j B@aRB@x@A ARkE)  aRl =R€=R"m@9@ E=% %> X2jnbb jo B@jB@x@A$AjpEvLbjajq =jDz=(%-(%r@y@ sBB$ t B@JB@@x@AAJuE72MaJ ev=Fzw =@/8{%p"x@s3@ ! @+c yb z B@`B5n@x@A$A{Eb| =jڭNa^%}@<@ m'% .`DFy~ J@/ B@aRBb@x@A AREdOa aR =l=R"@g@BE?% %@  DbGb½ B@jB@x@A$AjER Pjaj =j#=(%-(%@<@@r$( @n"@BB" J B@JB@x@AAJE e=ZMay ={%p"@݀@ $F @ nAbt܀b b B@aB@x@A$AE_Qajb =jWR`=^"@U@@S!jJbJA#J B@aRB@x@A AREmSa aR =@=R"@u@cEA%%B `Bbb j B@B@x@A$AjE jaj =jẁ=(%p @3A@̀@ƹB3B J B@JB@x@AAJEzTb Je=Fz =ATn{% "@@ ! @  "Abb! b B@`B@x@A$AEAUacb =j&V`=^%@~,JJ > B@aRB@x@A ARE aR =R=R"@@ #EC%@D z`bBbzb½ B@jBژ@x@A$AjEsWb jaj =jw=( @tv@ r$( @J "AJBuBJ$B B@JB@@x@AAJE/XJ@==F =@5{% @Y0@ ژb  c B@`B@x@A$AjEc b =j۪Yaj+"j"@=@ mcJJ  B@aRB$X@x@A AREaZa\!!aR =Rei="@d@ 3EE%F p5Dfb(b j B@jB@x@ =Aj @`E7[aj@ ""aj =j =%-1@@$B J B@aB@١K@x@AAJE J##e=BF =@ހ{% (@ـ@ ': @c AbYb b B@`B@x@A$AED\b$%b =juT]`= @R@ mJCJ  B@aRB@x@A ARER ^a &&aR =R =R"@^@BC% GS"S""1aBb b# B@jBnb@x@A$AjEƀ''aj =jhʀ= @ɀ@ $( @(AJB$B  B@JB@@x@AAJE__b ((e=F =@S{% @@c BxAb ! c B@`B@x@A$AE=`a)*b =j=j^"@y@@SmJJ J B@aRB@x@A AREaaR++aR =@=R%@緀@ cS,"Q"BbSb½ B@B* @x@A$AjEzpbj,,aj =j t=j%p`3A@es@ BrB$  B@JB@x@AAJE,caJ> --e=>| =1{% "@;-@ c eAb !  B@aB@x@A$AEc./b =jda%@@,cJfJA# B@aRB@x@A ARE^ea 00aR =RVf="@a@ ,c}|n"@?" Rbbb½ B@jB5n@x@A$AjEfajf11aj =j=%-(1@@ B`BJ$  B@JB c@x@AAJE 22e =#F$X =@ۀ{% " @ր@ , coA b>b !  B@`B@x@A$AE)gb34b =jSQh`=^%@O@,J JA#c B@aRB@x@A ARE6ia 55aR = =R"@C @ su6+g" 6]Bb b f B@B@x@A$AjEÀj66aj =jIǀ=j%-(%@ƀ@~nBB$  B@JBc@x@AAJEDj77e=ŤF/ =<{% " @~@ ! @ A!b ! b" B@aB@x 9A$A# @E:ka89b$ = ?`=="%@A@,&JJ ' B@B/@x@A AR(EرlaR ::aR) =R=R"*@䴀@ 7]F"qB\l+bPb½, B@jB@x@A$Aj-E_mmj;;aj. =jp=(%-(%/@?@ $( @(@B0Bo J$B1 B@JB@x@AAJ2E(nJ<b9 =joa^":@뢀@,;JWJA#c< B@aRB@x@A AR=Ez[pa@Y ??aR> =R(c=R"?@^@y 8"8%\bDf@b]b A B@jBy 7b@x@A$AjBEqaj j@@ajC =j=j%-(%D@@ EB@BJ$ cF B@JB !KL6@x@C =AJG @EҀ JAAeH=FcI =@{s؀{"J@Ӏ@%N *^AKb#b bcL B@`B@x@A$AMErBBbN =j=j%O@ @,PJvJA#Q B@aRB P 5@A ARR @`EIsaR CCaRS =RMQ=FR%T@L@ y"1Ӫ-t`̠QBUbb jcV B@a$BB@x@A$AjWEtaj jDDajX =j=%-Y@@ZB_BB[ B@JB@x@AAJ\E JEEe]=F]L^ =ƀ{% "_@@, ]B`b>b a B@aB@x@A$AbE)|ubFGbc =jYE@YA WWaR =R=%p@'@ c"T333TB9"3@pbbbĩ B@jBW@x@A$AjEb jXXaj =j&=j%pj%@@~$( @ťBB$By B@JB@x@AAJE)gaJYYe=F =m{% %@bh@ $F @+c hbɡ ! b륱 B@aBt@x@A$AE"acZ[b =j=G@² "@:@,JJ  B@aRB@x@A AREaR,\\aR =Rz=R"@՜@ `ӔPaDbAb j B@jB@x@A$AjECUaj ]]aj =jX=j%-(%@'@ BW B$ , B@JB@),@x@AAJEJ^^e=O#| =@{"@@ !%N.W Abfbj! b, B@`By@x@A$AEQ̀c_`b =ja%@䊀@,JPJA# B@aRB@x@A ARE^Ca FaaaR =RK=R%@bF@ @<aNbEb@$j B@jB@x@A$AjE jbbaj =!bia(%-@@ n$( @"DB)B J$B B@JB@@x@AAJEl Jcce=Fo =@T{% "@@ m c >6bb B@`Bc@x@A$AEubdeb =j6`=^"@e4@ m!j%J3J  B@aRB{ B @x@A ARE ffaR =R=R"@@o!RoÔ%"0}@*[bdb j B@jB@x@A$AjEb jggA =j=(%-(!@g@$BǪB  B@JB@@x@AAJE dJhhe=vF =@j{ @He@ * ': @/Ab  bc B@`B,@x@A$AEaijb =j߀=$%@#ހ@ mJݠJA# B@aRB@x@A AREaR FkkaR =RL=R%@@ " @#aҐq0C#"a.Bbb j B@jBc@x@A$AjE(Raj jllaj U=%-%@@!j @"@BBpTBc B@B`x@9 AJ @E Jmme=0 |B ={% "@@ $F @+c AbKb b륱 B@abB@x@A$A E6ɀcnob =jXaj$j" @@, J%J > B@aRB@x@A AREC@appaR =RG="@OC@ c 3"0"@"z`BbBb  B@jBh@x@A$AjE qqaj =jZa%p%@@ rBB JaIJB@x@AAJEQa  Jrre=ҤF =M{% %@@ c Ab Z 6! B@bBh@x@A$AEracstb =j2`=j !@^1@,"J0J c# B@aRBb@x@A AR$E逈uuaR% =R=R"&@@ cC0G1 `HB'b]b j( B@jBt@x@A$Aj)Elbvvaj* =j=j +@T@ ,B B- B@JB@@x@AAJ.E`aJ wwe/=wsFQ0 =@f{% 1@.b@ c iB2bab3 B@`B @x@A$A4Eyaxyb5 =j܀=j 6@ۀ@ m, @7JpڠJA#c8 B@aRBc@x@A AR9EaRy zzaR: =RA=F!;@@ $F @!6S}}B<bb½= B@jB $X@x@A$Aj>E Oaj j{{aj? =jR=j @@Q@~!j @w.`AJABYBB B@JB@!K,@x@AAJCE J||eD=`=hE =@{ F@ @ ?iAGb0b ! cH B@`Bc@x@A$AIEƀ }~bJ =jZa"K@@@SmLJ&JA#JM B@aRB@x@A ARNE(=acaRO =@D=%pP@0@@Ex$F @c+[ Q0aBQb?b R B@B$X@x@A$AjSEjajT =j?=j%pj1U@@ !j @ťVBB$ByW B@JB@x@AAJXE6b> eY=F,Z ="{% .W[@m@ 6$F @ 7A\b ǝ! b륱] B@aB@x@A$A^Eoab_ =j/`="`@K.@ !j$^caJ-J b B@aRB@x@A ARcE aRd =R=R"e@@ s} +d ulum&BfbJb jg B@jB~c@x@A$AjhEPbaji =jݥ=j(%j@:@kB B$  l B@JB @x@AAJmE]Jen=XpFo =c{"p@_@ !%N+c ~Aqbw^b ! b,r B@aB - @x@A$AsE^abt =j~ـ=%p(fu@׀@,vJMJ w B@aRBx @xu 9A ARx @`EkaR aRy =R'=R%z@@c unuoupu}l{ `̠_B{b뒀bĩ| B@a$Bc@x@A$Aj}EKjaj~ = jO=(%-%@N@ n$( @"@BB.B J$B B@B@$ @'@x@AAJEyJe=Fuc =@@i {% "@@ m) @+c 8Abb b륱 B@`B@x@A$AEÀ,b =j,a^"@@,JJA#c B@aRB$X@x@A ARE :a aR =RA=R"@=@ BDTݐpD{a Bb e=FB =m{% "@@  c 5Abb ! B@aBc@x@A$AEbb =jn`="@~l@,cJkJ  B@aRBc@x@A ARE %a aR =R,= @(@ 9Ґ"+cpc@Bb'b B@jB @x@A$AjEfaj =j=j @x@ BBJ$  B@JB@x@AAJEe=F ={% @U@ ! @W Zb  b B@aBc@x@A$AEWab =j`="@0@,JJA# B@aRB@x@A ARE aR =R]ր=R(o@р@ ]}3aZEbb fc B@jBc@x@A$AjE5b jaj =j=(%-(+c@@ $( @.`@BByB B@JB@y@x@AAJ  EEJe==XFh =@{K{% "@F@ B c yAbXb 륱 B@`BB@x@A$AECaBb =j~=^"@ݿ@ m!j*Qy JIJA# `JR- B@aRB @x@A AR EPxaR FaR =R=R" @X{@ U>%+i-lBbzbf B@jB @x@A$AjE3aj.aj =j_7=(%-(%@6@$BB Jc B@JB @x@AAJE^ e=F =@N{% "@@ 7Ab ! ^ B@`B,@x@A$AEbBb =jk`=^%@wi@ mJhJA# B@aRB@x@A AR E!a aR! =R)=R""@$@c ґ0}<1/*#bbb½$ B@jB@x@A$Aj%Ex݀ jaj& =j=j '@^@$(BB$ g) B@JBB@x@AAJ*Ee+=F, = À{"-@7@, .bbj! / B@`B WB@x@A$A0ETb1 =j$Y=j+"%2@W@,3JVJ 4 B@aRBy@x@A AR5E aR FaR6 =R=R%7@@* .1}}?a?w*[8bb j9 B@jB@x@A$Aj:E jaj; =jπ=j%-!<@w΀@=B̀B J$ > B@JB@x@AAJ?Eb J@=@="U|,A = {% "B@X@ y! @1#  ACb ! bD B@aBc@x@A$AjEEBa,bF =j`=j G@(@,HJJ >cI B@aRB@x@A ARJEaRK =Rg=R"L@@ #‘0u *uaBMb&b½N B@jB@x@A$AjOE5ubjajP =jx=j%-(%Q@$@ $( @(@BRBw B$BcS B@JB@x@AAJTE0aJ eU==CFV =6{% "W@1@ c Y2AXbXb! 륱Y B@aB@x@A$AZEByb[ =jhaW \@ɪ@,]J5JA#^ B@aRB@x@A AR_EPca aR` =Rj=H  S;"a@Xf@3, 0}r1}D@z$bbeb@%c B@jB@@x@A$AjdEaj jaje =jg"=j%-(!f@!@ gB#B$ h B@JB@x@AAJiE] Jej=Fk =n{F% "l@ۀ@ @c  Amb!b ! bcn B@aBL6@x@A$AoEbbp =jV`=j q@gT@,rJSJA#cs B@aRB@x@A ARtE a aRu =R=FR"v@@ ! @C+\ 0TґUajBwbjb x B@jBx@x@A$AjyExȠ@Y jajz =jˀ=j%-(%{@T@r|Bʀ B$ } B@JB@x@AAJ~Ee=F| ={% "@9@ W; @6 Abb b B@aBc@x@A$AE?a,b =j="@@@S(!j$^,JJ R `JR, B@aRB@x@A AREaR FaR = E=R"@@ S1tF,Ô [PBb b B@B@x@A$AjErajaj =ju=(%-(%@@ Bbt J B@JB@x@AAJE-aJBe="@| =3{% "@.@ AbAb ! ^ B@aB @x@A$AE'cb =jVa^3@@,cJ"JA# B@aRB@x@A ARE5`a aR =Rg=R"@=c@ c!@UQ bIBbbb j B@jB@x@ =Aj @`Ejaj =j@=j @@ BB$ c B@aB,@x@AAJEB׀e=äFc = À*݀{"@}؀@ )%Nc Bb ! bc B@`B,@x@A$AEɒbb =jR`=+"%@XQ@,JPJA#F B@aRBw5n@x@A ARE aaR =R}=R%@ @  sҐ‘+gґa@*BbFb j B@jB @x@A$AjE]ŀ aj =jȀ=(%-+c@>@$Bǀ̮$  B@JBc@x@AAJEe=eF =Ԇ{% "@#@ @+c Abb b B@aB@@x@A$AEk B@aRB@x@A AREaaR =Rz=R(o@ @!5mHHcb/b f B@jB@lc@x@A$AjEB€jaj =jŀ=j @3@ $( @"DBĀ Bc B@JB@x@AAJE}e=RF =Ƀ{% F"@@ ) @ i~b b B@aB$X@x@A$AEO9ab =j=$j"@@,JNJA# B@aRBL6@x@A ARE]aRFaR =R=R" @q@ +䐃/ aDf bݲb j B@jB@x@A$Aj Ekajaj =jto=j%p1@n@ B0B B@JB@x@AAJEj'aJe=F~c =[-{F% "@(@ ! @6 @{@ c q?bNb ! ^@ B@aB@x@A$AAE46acbB =jY=^(fC@@,cDJ'JA#E B@aRBy@x@A ARFEBaRFaRG =R=R"H@V@ 0ӔPR}@QavDIb¯b jJ B@jB{ o@x@A$AjKEhajfajL =jUl=j M@k@ NBB$ O B@JB @x@AAJPEO$aJeQ=ԤFQR =?*{"S@%@ c BTb ! U B@aB@x@A$AVE߀bW =ja%X@U@,YJJA#Z B@aRB@x@A AR[EVaaR\ =R^=R%]@Y@  I.p %a(lB^bXb _ B@jB@x@A$Aj`Ejajl caja =j=(%-+cb@]@ cBB $ d B@JBc@x@AAJeÈef=vFg =Ӏ{% "h@2π@ ! @1a0/ Aib΀b b j B@aB@x@A$AkExbnbbl =jI`=j m@G@,nJjJA#co B@aRB$X@x@A ARpEaFaRq =R:="r@@c$@t$U}$Qa4Bsbb jt B@jB@x@A$AjuE  @Av =j=%-(%w@۾@$xB@Bµy B@JB ,@x@AAJzEwb J!`={=Fny| =@}{% %}@x@, VA~b/b ! !n@* B@`B@x@A$AE3a "EB =j>=j @@ m, @ lJ J@0 B@aRB@x@A ARE' "_$F = Ա="@/@ 1z %K`%fjcnbbb" at B@B@x@A$AjEej1|!j =j2i=j @h@ ) @.`DBgB J$B B@JBc@x@AAJE4!J`==F{ =$'{F% @r"@#Q! @c nbb ! bc B@aB@x@A$AE܀B =jaj%pj"@B@,JJA#d<R B@aRB@x@A ARESa  AR = }[=R(o@V@,#V }ÔP"0DfbDb½ B@B* @x@A$AjEOj Aj =j=j%-+c@:@ $( @"@BB B$B B@JB@x@AAJE );@==W݄F =Ѐ{% "@ ̀@ J) @+c Abrˀb b륱 B@aB@x@A$AjE\bc B =jnF`= @D@,J;JA# B@aRB@x@A AREj@Y AR =RaRR"@r@ c3_~/D-0@VBb  B@jB@x@A$AjEj#+c =ju=j%- 5`@3A@л@~B1B B@JB@x@AAJEwt bw 2c@==F =ATdz{F% "@u@ y! @K {Abb! b B@`B@x@A$AjE/ aB =j$=j^(f@@,JJ (Rc B@aRB@x@A ARE aR+ AR =R=FR"@@ C} D}8,bb jc B@jB@x@A$AjEb aj j@u% =jf=j @^e@~r$( @"DBdB$Bc B@JB@@x@AAJE J`==0|$X =@ ${% @S@ c >b ! c B@`B@x@A$AEـB =jʙa"@+@, JJA# B@aRB@x@A AREPa FAR =R[X=R%@S@ S%  ȏ`Dfb!b B@jB@x@A$AjE4 j9% =j=(%-(1@ @ rB J$  B@JBc@x@AAJEǀ `==<ڄFc =̀{% "@Ȁ@! @ӟQ AbWb b, B@aB; @x@A$AEAbcB =jtC`=^(f@A@ '*Q#j` yJ@J@/ B@aRB; @x@A AREO@Y  6$F  +$R"@K~ 8,$F!Rc2o Bbb B@B@x@A$AjEֵ,fAj =jZ=(%-(%@@~!j @"@BBB B@JB@٢oc@x@AAJE\qbw`==ݤF|y =@Tw{% "@r@ $F @6 /Ab ! b륱 B@`B? @x@A$AE,a ^ B =j1=j$jB#;@/@,JSJ!c B@aRB @x@A AREj $ R!!AR =="@z@ s`26T ^Bbb@&#a B@jB @x@A$AjE""Aj =j}=j%p% @צ@$ B9B$ Jc B@JBB@x@AAJ Ew_J#*@==-]L =ge{F% "'@`@, Ubb ! c B@aB@x@A$AjEa$%B =jۀ=j @ـ@,JؠJA#F B@aRBy@x@A ARE aR &&AR =R=FR"@@ d`CqSq\aDbb@$j B@jB| o@x@A$AjEMj''Aj =jQ=%-(%@mP@$ BOB J$ ! B@JBc@x@AAJ"E J((`=#=|$ ={% "%@R @ B c aA&b ! ' B@aBF@x@A$A(EĀ)*B) =jaj^+c*@@,+JzJA#F, B@aRB@x@A AR-E;a i A@++AR. =@]C=R"/@>@ "‘ґ"F$* =:B0b!b½ac1 B@Bh@x@A$Aj2E4 j,,Aj3 =j=j%-(%4@@~5B B$ J6 B@JB@x@AAJ7E--`=8=<ńF9 = À{% ":@@ ! @.W A;bVb b< B@`B@x@A$A=EAna./B> =jf. `=j ?@,@,@J4J A B@aRBc@x@A ARBEOy F00 C =R=FR"D@_@ `S`npaX$`aaBEbb jF B@jB@x@A$AjGEՠ!b j1#!jH =ja=j%-(%I@@~JBBK B@JB@x@AAJLE\\"J22`=M=ݤFyN =Hb{% "O@]@ c  APb ! Q B@aB@x@A$ARE#ac34BS =j؀="T@bր@,UJՀJ(RcV B@aRB@x@A ARWE$aR F55ARX =!J= Y@@ ~(}`TF BZbdbj[ B@jB*@x@A$Aj\EwJ%j66Aj] =jN=j ^@^M@ ) @.`AJ_BLB$Bc` B@JB@x@AAJaE&J77`=b=|c = {% d@;@ 5n! @'`, Aebb ! bcf B@aB 4  @'@x@A$AgE ^88Bh =jƀ=G r ^"i@}Ā@@S'$,jJàJ Jk B@A*B,@x@A ARlE }'99ARm =@=R(on@@  +Y xI|}{zar=Bobb@$p B@BQ@x@A$AjqE8(j::Ajr =j<=(%-(+cs@{;@ r$( @"@BtB:B J$Bu B@JB@x@AAJvE ;;`=w= ƒ|yx = À {"y@T@ Azb ȥ! 륱{ B@`By@x@A$A|E)b<=B} =jo*`=j%p"~@6n@@SJmJA#J B@aRBL6@x@A ARE&+a >>AR =@D.="@)@ ByacNb b½ B@B]L@x@A$!1E45n??Aj =j=j%-%@@B|䀃 B$ y B@JB )* @x@AAJE,@@`==;F =@{F%@ @, Wbjb !  B@`B@x@A$AEAY-aABB =jt.`=j @@,J@JA# B@aRB@x@A ARENР@Y CD5\ = +؀=FRc@cӀ@ %  R"a ; bҀb f B@B@x@A$AjEՋ/bjDDAj =je=%-@Î@ B!B$  B@JBc@x@AAJE\G0JEE`==ݤF =LM{% "@H@ ) @ ; b  b B@aB@x@A$AE1aFDB$^ =j€=j^+c@Y@,JJA#(R B@aRB@x@A AREy2aR HA6!R =R=R"@|@ $F!?7RNaxV6albhbj B@jB @x@A$AjEw53jIIAj =j8=j%-(+c@U@$B7 J B@JB@x@AAJE JJ`==| ={% "@:@ *" @tAbb ! b B@aB@x@A$AE4bcKLB =jl5`=%@ k@,JwjJA# B@aRBL6@x@A ARE#6a MMAR =RA+=%p@&@cjXXAj =j5=(%-(%@:@ $( @(@BB4 B$B B@JB c@x@AAJE YY`==d|W =@{% "@@ ) @+c @b~b b륱 B@`B@x@A$AEi?bZ[B =jvi@`=j"@g@,JDJA# B@aRB@x@A AREw Aa \\AR {'(=R"@#@ 3$paQp[SEDfb"b B@jB`x@9 Aj @`Eۀ j]]Aj =j߀="@ހ@ BEBJ B@aB@x@AAJ EBb J^^@= =?`=c =p{%  @@ ! @( }B b b b  B@aB@x@A$AjE SCaj_`B = `0D`=^%@@, JJ  B@B@x@A AREʀ aaAR =Rр=R%@(̀@ CJqKHWBb̀b½ B@jB@x@A$AjEEb jbbAj =j/=(*(+c@@ r$( @"@BB뇀BJ B@JB@x@AAJE&AFaJ Jc"q@==Fh =G{% "!@cB@  c >A"b¡ c# B@aB@x@A$Aj$EcdeB% =jռGaj c&@7@,c'JJ c( B@aRB$X@x@A AR)EsHa ffAR* =Rg{="+@v@ cSH@B:EÔP@B,b2b@&j- B@jB* @x@A$Aj.EA/IajfggAj/ =@݀2=%p(%0@@~n1B}1 B2 B@B@x@AAJ3Eꀈ hh`=4=HF5 ={% %6@@ y! @c 'FA7bcb! b 8 B@aBc@x@A$A9ENJbijB: =jkfK`=j ;@d@,<J9JA#F= B@aRB@x@A AR>E[La kkAR? =R %="@@d @ ccPQ" (RBAbb fB B@jBc@x@A$AjCE؀jllAjD =jf܀= E@ۀ@ $( @ūcFB"BBG B@JB@x@AAJHEiMmm`=I=FJ =Q{% K@@ B c DBLbb iXFM B@aB m8p @'@x@A$ANEONanoBO =jO`=j P@~@,QJ J R B@A*B@x@A ARSE ppART =R΀=R(oU@ɀ@ cs`#G7Ft"*K.BVbib@'W B@jBy  @x@A$AjXEPb jqqAjY =j=j%-(!Z@l@$[B̄B\ B@JB@x@AAJ]E >QaJ Jrr`=^=PF_ =C{% "`@E?@ c $Aab  b B@aB@x@A$AcEcstBd =jRa(fe@@,fJJ Fcg B@aRBc@x@A ARhEpSa uuARi = Xx="j@s@* " R URT.Bkbb½l B@BF@x@A$AjmE%,TjvvAjn =j/= o@ @ cpBj.(J/l q B@JB@x@AAJrE瀈 ww`=s=-FFt ={% u@@ 5n c BvbHb cw B@aB@x@A$AxE3UbcxyBy =jecV`=^%z@a@,{J2JA#| B@aRB@x@A AR}E@Wa zzAR~ =R!=R%@L@ c+] "+Q/tFBbb j B@jB* @x@A$AjE j{{Aj =jGـ=j%-(+c@؀@ BB B@JBc@x@AAJENX||`==ϤF =>{% "@@ y! @ $"Ab  B@aB@x@A$AELYa*}~B =j Z`="@k @,J JA#F d* B@aRB@x@A AREÀ@Y FAR =Rˀ=R"@ƀ@ c01zU@ BbVb j B@jBc@x@A$AjEi[b jAj =j=( @Q@~$( @r(AJB B B@JB@x@AAJE:\J`==qMY`= =@{%p@&<@ 5n c oAb;b c B@aBy@x@A$AEv#A =j]aj"@@,JqJA#(> B@aRB@x@A AREm^a FAR =R.u=R%@p@ y-`UÐ-A HBbob j B@@]B}@x@A$AjE )_aj jAj =j,="@+@rBNBB B@JB@x@AAJE J`==Fc =}_a% @@ c +Bb-b B@aB@x@A$AE`a,@% =jC`a`=^%@^@@Sf'%JJA#J B@aRB@x@A ARE%ba AR =@=R%@-@ %|p1Ua)0 bb@$j B@B@x@A$AjE jAj =j(ր=(+"(!@Հ@ rBԀB J B@JB@=$X@x@AAJE3c`==F =@{% "@n@ B$F @ Ab  B@`B@x@A$AEIdacB =j e`=j%pj%@8@ m!j @JJA# B@aRB5n@x@A ARE FAR =RrȀ="@À@ cQ/.daBb;b j B@jB5n@x@A$AjEN|fb2 jAj =j= @;@~B~ J `y B@JBc@x@AAJE7gaJ J`==UJF/ =={%pF%@9@ ! @?m :Bbp8b! by B@aB@x@A$AE[B =j~ha%pj%@ޱ@,JJJA# B@aRB@x 9A AR @`Ehjia AR =Rr=R"@}m@ c "% `̠UBblb  B@a$Bc@x@A$AjE%jjAj =j{)= @(@ $( @"AJB7B,B B@JB 5@AAJ @Ev `==F =Z{% F"@@ ) @+c Abb b륱 B@abB -  @'@x@A$AEkbB =j]l`=j^"@k[@,JZJ B@A*Byb@x@A ARE ma AR =R=R"@@ y1 .p}0ykBbrb½ B@jBV@x@A$AjN`E jAj =j!Ӏ=j%-(1@Ҁ@$BрB J) c B@aB@x` =AJ @Enb J`==F ={% "@T@c QAH ! c @abB@x@A$A EFoacB =p`=% @9@,JJA#c B@aRB5n@x@A ARE AR =Rcŀ="@@.` 0taBb,b j B@jB; @x@A$AjE2yqb jAj = |=%-(%@@~cBw{(B$  B@B@)@x@AAJE4raJ`==:GF$X =@:{% "@5@c 1 AbYb !  B@`B@x@A$A E@ B! =j=^%"@<@,#JJ $ B@aRB @x@A AR%Eǫsb RAR& =Rp=R"'@ˮ@ o!!% 0䐃H60PB(b7b j) B@jB@x@A$Aj*EMgtaj jAj+ =jj=(%-(%,@!@n-BiB$ . B@JBc@x@AAJ/E"uJ`=0=ܤFW1 =({% "2@$@ @A3bp#b b4 B@aB@x@A$A5E[ހB6 =jva^%7@@,8JZJ >9 B@aRB@x@A AR:EhUwa FAR; =R]=R"<@pX@ #ßPa"Q` B=bWbf> B@jB`@x@A$Aj?ExjAj@ =jo=(%-(%A@@ rBB+B JC B@JB@xA 9AAJD @Ev̀ `=E=F F =fҀ{% "G@̀@ X1!Hbb! I B@abB@x@A$AJEybAK =j#Hz`=^%L@F@,MJEJA#cN B@aRB@x@A AROE ARP =R{$R"Q@ @3-"8$p$a FRbvb jS B@jBB@x@A$AjTE2 jAjU =j!=( V@}@~WBݼB J `cX B@JB@x@AAJYEv|bw J`=Z=F[ =|{F% \@Rw@ @c F]b ! bc^ B@aB@x@A$A_E1}aB` =j=)j%a@1@,bJJ c B@aRB@x@A ARdE~aR ARe =RR=R%f@@ C`9`p%A Hogbb½h B@@]BB@x@A$AjiE2daj jAjj =jg= k@f@ lB^BJ$ m B@JB@x@AAJnEJ`=o=:2|p =%{% F"q@ @ c RBrbUb s B@aB@x@A$AtE@ۀ Bu =j߀=j^%v@<ހ@,wJݠJA#x B@aRB,@x@A ARyEƖARz =Rn=R"{@ϙ@ S%$e:puvaMB|b;b } B@jBF@x@A$Aj~EMRjAj =jU=j%-(/)@#@ rBT B B@JB@x@AAJE J`==ܤF ={% "@@c Abpb B@aBc@x@A$AE[ɀB =ja%@@,JaJA# B@aRBy@x@A AREh@a AR =R$H="@xC@ cuw~TT0a`hBbBb B@jB  @x@A$AjE jAj =j=j @@ B;B B@JB@x@AAJEvb J`==FL6 =n{% @@ c ĚBbb! B@aBb@x@A$AEraB =j 3`="@k1@,J0J  B@aRB@x@A ARE AR =R= @@ cs:uv%eBbb j B@jB,@x@A$AjEbAj =j!= @{@ BݧB F B@JB5n@x@AAJEaaJ `==sF = g{% @Xb@ ! @ sBb  B@aBc@x@A$AEacB =j܀=^+c@-ۀ@,JڠJA# B@aRByc@x@A AREaR AR =R`=R+c@@ !8%; ʇBb'b½ B@jB@x@A$AjE2OjAj =R=(%-(1@@ n$( @n"@BBzQ J)B B@JBc@x@AAJE J`==:|c ={%p"@ @ c nuAbUb  B@aB@x@A$AE@ƀB =jea^"@Ƅ@,J2JA# B@aRB@x@A AREM=a AR =RD=R"@E@@ 1 D!:dpX[xBb?b B@jB@x@A$AjE jAj =j\=(%-(%@@$BB J B@JB@x@AAJEZ`==ܤF =O{% "@@ ': @c Ab ! b B@aB 4 @x@A$AEoaB =j0`=j c@t.@,J-J  B@aRB@x@A ARE怈 FAR = =R"@@ o"!w6$b_b jc B@B @x@A$AjEub jAj = ="@]@!j%(AJBBB$Bc B@B@x@AAJE]J`==}pF{* =c{% @6_@  c Ab^b c B@aB@x@A$AEacB =jـ=j$j"@؀@,5nJnנJA# B@aRB@x@A AREaR FAR = V="@@o! ZS":6 b bf B@B@x@A$AjELjAj = O=%-&@N@ rBWB J!  B@x@AAJEJ`==| = {% (@@ ! @b:b b  B@aB@x@A$AE$ÀcB =jVaj$j% @@,c J#JA# B@aRB @x@A AR E2:a AR =RA="@2=@ o"!D"T:aBq bB@B? B@JB@x@AAJ@EaJ `=A=xB =o {% "C@@ c ADbb! 륱E B@aBc@x@A$AFE cBG =j8a"H@~@,IJJA#J B@aRB@x@A ARKE7a ARL =R>= M@:@ :9a";/"aHyNb9b½O B@jB@x@A$AjPE jAjQ =j*=%p(%R@@ SBB cT B@JB @x@AAJUE$`=V=F; W ={% %X@_@ ! @c mYb Z B@aB@x@A$A[EiacB\ = `)`=^(f]@2(@,^J'J _ B@B@x@A AR`E FARa =Rk=R"b@@5nI~(}q"ulDcb0b jd B@jB o,@x@A$AjeE?b jAjf =jϟ=(%-(%g@,@ n$( @n"@BhBBi B@JBc@x@AAJjEWJ`=k=GjFl =]{%p"m@Y@ m c AnbbXb co B@aB "@x@A$ApEMaBq =j|Ӏ=^"r@р@,sJGJ t B@aRBL6@x@A ARuEZaR FARv =R =R"w@f@c$4"䐃(a9BxbҌbfy B@jBc@x@A$AjzEEjAj{ =jaI=(%-(%|@H@$}BB J) c~ B@JB@x@AAJEgJ`==F =X{% "@@c 0Abb  B@aB@x@A$AEB =j}aj c@y{@,JzJA# B@aRB@x@A ARE3a AR =R;=R"@6@,#)Pn-0d"BbdbA;j B@jBQ@x@A$AjE@Y jAj =j="@p@ƹcBBB B@JB@x@AAJE b J`==F ={% @A@ c ?Bb  B@aB@x@A$AEfacB =j&`=j @%@,J$J >@ B@aRBL6@x@A ARE݀AR =RQ="@@3GcB!b½ B@jB o@x@A$AjE$b fAj =j=*(+c@@ rBpJ B@JB@x@AAJETaJ%`==,gF =Z{% (@U@ ! @ [AbKb b B@aB@x@A$AE1a  B =jYЀ=j @΀@,J(J  B@aRBy@B.Q @x@A ARE?aRAR =R="@_@ CmpL6$Ea=Bbˉb j B@jB@x@A$AjEBaj2 !_Aj =jNF= @E@~/$( @4AJBB J B@JB@&Q@x@AAJEL J'N@==ѤFv, =@=a% @ c %Ab !  B@`B@x@A$AjEӹ,b =jzaj @fx@ m, @ JwJ@0 B@aRB@x@A ARE0a aR =R8=R(o@3@!RoSV np3]LBbAb½ B@jB@x@A$AjEg j r =j=j%p(+c@E@~B B$  B@JBc@x@AAJEb Je=oF$X =歀{% "@+@c R5Abb!  B@aB @x@A$AEucab =j#`=$j(f@!@,J_J R B@aRB@x@A AREڀ   aR =RB=R"@݀@ c('LU1KoBbb½ B@jB@x@A$AjE b j  aj =j=j%-%@瘀@~BIB$  B@JB@x@AAJEQaJ J  e=dFzc =W{% "@R@; Ab,b!  B@aB@x@A$AE ac  b =jC̀="@ˀ@,cJJ c B@aRB@x@A ARE$aR aR =R؋= @0@  s U+ccJbb½ B@jB@x@A$AjE?jaj =j3C=%-(%@B@ BAB ; B@JB@@x@AAJE1  >=F$X =@a% %"@nW @W R Ab  B@`By@x@A$AjE,b =jvajG b,ژ@/u@,JtJ c B@aRB@x@A ARE-a aR =Rz5=R" @0@ !%($""D,DaB bAb½ B@jB@x@A$Aj EL j r =j=(%-(%@'@ nB뀃J$  B@JB@@x@AAJEӤ@==TF =@ê{%p"@@ 5\ @+c EAbob bc B@`B@x@A$AjEZ`ab =jw `=^K@@ m!j$^JDJA# B@aRBzy@x@A AREgנ@Y FaR =R ߀=R"@{ڀ@ y) (dpK@p'B bـbf! B@jB@x@A$Aj"Eb jaj# =v=(%-(C$@ѕ@ R%B2BB& B@JB@x@AAJ'EtNJe(=`F) =iT{% "*@O@ B! @K A+bb ! b , B@aB@x@A$A-E b. =j=j%pj%/@ @,0Js J (1 B@aRB @x@A AR2E FaR3 =R1̀="4@Ȁ@ - %aB5bǀb j6 B@jB~c@x@A$Aj7E aj8 =j= 9@@ $( @"AJ:BUB J$B; B@JB@x@AAJ<E<Je== > =B{% F%?@=@ W) @+c ;AA@b+b bcA B@aBc@x@A$ABE bC = `Aa D@@,EJ J F B@B@x@A ARGE$oa !!OH =Rv=R"I@ r@ B%K]10#azBJbqbK B@jBz@x@A$AjLE*aj j""ajM = B.=j%-(+cN@-@ OB,BJP B@B@x@AAJQE1 J##eR=FɂS = À{% "T@m@ S! @+c BAUb  b V B@`B 8p@x@A$AWEb$%bX =ja`= Y@G`@@Sj' @ZJ_J Jc[ B@aRB$X@x@A AR\Ea &&aR] =@| =!^@@ " @%aB_b9b½` B@B @x@A$AjaELԀ j''ajb =j׀=j c@9@~!j @ūcdBր Be B@JB@x@AAJfEӏb> J((eg=TFBh =˕{% i@ @ 5n$F @+c o2Bjbob bck B@aB "@x@A$AlEYKac)*bm =j `="n@ @,coJ\J p B@aRB@x@A ARqEg @Y ++aRr =Rʀ=%ps@sŀ@ ӝ@%1@8BtbĀbĩu B@jB @x@A$AjvE}bf,,Lw =jv=j%pj+cx@Ӏ@ yB2BJz B@JB@x@AAJ{Et9aJ --e|=F~3:  K >} =`?{% +c~@:@ @+c Abb b B@aB,@x@A$AE./b = `#a"@@,JJ 4b4c B@B@x@A AREla 00aR =Rs=R"@o@ 6t"+fW1Bb}nb f B@jB@x@A$AjE'j11aj =j+=( @u*@rB)B ` B@JB@b& @'@x@AAJE 22e=Fy =@@{% @R@F c Bb ! , B@`B@x@A$AEb34b =j^`=^.@/]@ m!j*Q`# ! !% ARJ\J  `JR- B@aRB@x@A AREa 55aR =Rb=R%@@ cw OanAjb*b½ B@jBc@x@A$AjE1р j66aj =jԀ=( @Ӏ@ BaBJ$  B@JBc@x@AAJEb J77e=9F ={% @@ c ]BbTb  B@aB W'Z@x@A$AE>Hac89L =jg`=^%@@,J5J B@aRB@x@A AREL@Y: ::aR =R ǀ=R%@\€@'!(T:@T>Bbb½ B@jB@x@A$AjEzb j;;aj =jW~=j%-(1@}@ `@"@BBB B@JBB@x@AAJEY6J<<$5=ڤF =M<{"@7@c Ab ! B@aB@x@A$AjEc=>b =jaj$"@_@,J˯J (Rc B@aRB@x@A AREha* ??aR =Rp=FR%@l@c%TJE /a,kcbmkb j B@jB o@x@A$AjEt$aj @@Ho =j'= @W@$B&µ$ c B@JBc@x@AAJE JAAe=|F ={% F"@8@/ kcbb c B@aB "c@x@A$AEbBCb =j[`=j^%@Z@,JtYJ B@aRBB@x@A AREa DDaR =R<="@@#kYEkeaHobbf B@jB @x@A$AjE jEEaj =jр=%-(+c@Ѐ@$BZB J B@JB@!K@x@AAJEFFe=F =@{% %@ڊ@ `@W+c ZAb8b B@`B@x@A$AE#EaGHb =jD`= @@ m!j @JJ c B@aRB@x@A ARE1 FIIaR =RÀ=R"@=@ c3%Se%aaBbb j B@jBc@x@A$AjEwb jJJKc =j?{=j @z@ƹByBB B@JB@@x@AAJE>3JKKe=EF$X =@.9{% @|4@ ! @( #Bb  b B@`B@x@A$AEcLMb#ja%pj+c@P@, JJ  B@aRB`x@9 AR @`Eea FNNaR ={m=R%@h@ cCTWT.tT`̠BbBb½ B@a$Bc@x@A$Aj EY!jOOaj =j$=j @@@ $( @"AJ B# B B@JB@x@AAJE PPe=aF = À{% F"@ހ@ ) @ mAb|݀b b B@`BB@x@A$AEfbcQRH =jX`="@V@,cJUJA#j B@aRBv B@@x@A AREta SSaR =R)= @@S%t+_Kk0 bb!&' B@jBy@x@A$AjEʀ jTTaj =j΀=j%p(! @̀@ !BCB" B@JB@x@AAJ#EUUe$=FQ% =n{F% %&@@m @ q'bb ! b ( B@aB@x@A$A)EBVVE* =jF=j^(f+@ E@,,JxDJA#n- B@aRB@x@A AR.E FWWaR/ =RVaR"0@@ ccvEqa‰D1bb j2 B@jB]L@x@A$Aj3E jXXaj4 =j=j 5@@0k6BZB$  7 B@JB,@x@AAJ8EtbwJYYe9=F$X: =z{:% ;@u@  c f{B<b8b! y= B@aB@x@A$A>E#0ZZb? =j4=j @@73@,AJ2J B B@aRB@x@A ARCE F[[aRD =RG=FR%E@@ cs%Td%@ 1BFbb@(jG B@jBxc@x@A$AjHE0\\ajI =j=j%-(+cJ@@$KBt J$ L B@JBc@xJ 9AAJM @EbJ]]eN=FlO =h{F% "P@c@$X nAQbSb R B@abBc@x@A$ASE>^^bT =j"=^+cU@>!@,VJ JA#FcW B@aRB$X@x@A ARXEـ@Y __aRY =R=R"Z@܀@ ckYE @-B[bIb \ B@jBc@x@A$Aj]EK``aj^ =jӘ=j%-(%_@0@ rc`B Ba B@JB@x@AAJbEPS aa$5c=ڤFcd =V{% "e@ R@ c AfbnQbcg B@aB >"@x@A$AjhEY aybcbi =j̀="j@ʀ@,ykJ\J (Rl B@aRB@x@A ARmEfaR ddaRn =R= o@z@ ,%EqKe,dBpb慀bjq B@jB{ @x@A$AjrE>jeeajs =jB=j t@A@ uBABv B@JB@x@AAJwEt> ffex=F* y =Xa z@ ! @ B{bb ! b c| B@aB "@x@A$A}E,ygh"~ =j&vaj"@t@,cJsJ F B@aRB@x@A ARE-a iiaR =R4=R(o@ 0@ T+Y%E$cVBbx/bj B@jB; @x 9A$Aj @`E耈fjjaj =j=( @t@~$( @.`AJBB$B B@aB@x@AAJEkke=F ={% c@Q@ y Ab !  B@aB@x@A$AE_almb =j `=^"@#@,JJA# B@aRB* @x@A ARE nnaR = =ހ=R%@ـ@ SN|QtBb b j B@B* @x@A$AjE0 b jooaj =j=(%-(1@ @ Bp  B@JB@)B@x@AAJEM Jppe=8`Fu* =@S{% "@N@ c 1AbSb  B@`B@x@A$AE> aqrb =jjɀ=^%@ǀ@,J8JA#c B@aRB @x@A AREK aR FssaR =R=R"@S@ (}O[}@aBbbf B@jB !@x@A$AjE;jttaj =jJ?=(%-(%@>@$B B J)  B@JB@!K@x@AAJEY uu$5=ڤFy =@M{% "@@  >ɂb !  B@`B@x@A$AjE߲bcvwb =js`=jt%@rq@ m, @JpJ  B@aRB@x@A ARE)a xxaR =R1="@,@ N` EDbeb½ B@jB@x@A$AjEs jyyS =j=j%-(%@N@$B瀃 B$  B@JB@x@AAJEzze={F =ꦀ{F% %@7@ , @ Abbj! b B@aB@x@A$AE\ac{|b =j`=j%pj%@@,JJA# B@aRB@x@A ARE F}}aR = Cۀ=FR"@ր@ %Sre$U0D B$  B@JB"Mc@x@AAJEG!Je $`ZF! =@=qM{% "@H@ ! @(B#bb ! b$ B@`B@x@A$A%E"acD^& =j9À="'@@)f(JJ IJ) B@aRB@x@A AR*Ez#aR FaR+ =@=R.!,@}@ #"T"tU/b\B-b|bĩ. B@Bc@x@A$Aj/E5$aj jB0 =j,9=(%-(11@8@~$( @4@B2B7B J$B3 B@JB@x@AAJ4E" Je5=F6 ={% "7@\@ l8b ! 륱9 B@aB@x@A$A:E%b@Y b; =jU="<@@,=J!JR> B@aRBB@x@A AR?E0h&RDF@ =Ro=R"A@4k@o @o34"DD~hoBbjb jC B@jBc@x@A$AjDE#'jajE =G'=j%-(%F@&@ Rc} B@aRB@x@A AR~Ee1a FaR =Rl=R"@)h@ c101n'bgb j B@jB]L 5@A$Aj @`E 2aj jaj =j$=j @q#@~n$( @(DB"B B@aB@)@x@AAJE" Je=FB =@"{% @_݀@ ; @1! @# +c Ab ! bc B@`B@x@A$AE34@Tcb =jW4`="@(V@ m!j%JUJ(R B@aRB$X@x@A ARE5a aR =Rb=!@@ c sÐ-@a$&bj B@jBB$A$Aj @`E=ʀ jaj =j̀=j%p`k3C@@~B̀ B$  B@aB@x@AAJEą6@==EF ={% (@@ ! @4 +b`b b B@aB@x@A$AjEJA7ac[& =8`="@~@SJMJ J B@ Bc 5@A AR @ 5\ FAR =@`== @X@ ccܚDbĺbĩ B@B ox@x@A$Aj$`DEs9b jaj =jow=j%-(1@v@ $( @ @ (@BB+B B@JB@x@AAJEe/:aJ Je=F, =b5{F% %@0@h xbb! 륱 B@aB "@x@A$AED^ =j ;aj^"@k@,JרJ  B@aRB@x@A AREaBbĥb½%  jB@x@A$AjE^Gjaj =jWb=G@ j%-jC %@a@ $( @K f BBBc B@JBc@x@ =AJ @EeHJ@==F;  =] {% % @@ B$F @+c _A bb ! b륱 B@abBy@x@A$Aj EՀcH =jIa%pj"@s@,JߓJA# B@aRB@x@A ARELJa aR =RT=R"@O@ ﵌Uu䐃"}paBb]b B@jBQ@x@A$AjEKjaj =j =j%p%n@m @ B B$  B@JB@x@AAJE e=քFB =ɀ{% "@Bŀ@m @+c 6@Ab ! bc B@aB@x@A$A!ELbcb" = `?M`="#@ >@,$J=J % B@Bc@x@A AR&E G' = J= (@@ }ZT w4B)bb jc* B@BB@x@A$Aj+E"Nb jaj, =j=j%-(%-@ @~.Bj B$ / B@JB@x@AAJ0EmOJe1=*F2 =s{F% %3@n@  c m4bDb 5 B@aBc@x@A$A6E/)PaA7 =jK=j^+c8@@,9JJA#c: B@aRB @x@A AR;E=QaR FaR< =R=R"=@Q@ D"uu ҽy*>bb j? B@jB@x@A$Aj@E[Raj jajA =jC_="B@^@CB]BBD B@JB@@x@AAJE%  EJSJeF=ˤFG =@{>{% H@@ * ': @c #GIbb b J B@`B@x@A$AKE EL =js׀=j M@Հ@@Sm' @NJAJ JO B@aRBz B  @xM 9A ARP @`EWT rQ =@`= ="R@d@u "SPSuS`̠otSbАb T B@B/@x@A$AjUEIUjajV =jbM=*(+cW@L@ $( @(@BXBB Y B@JB@x@AAJZEeVJ@=[=mӃnb\ =U {% (]@@ $F @ {A^bb bc_ B@aB@x@A$Aj`ED^a =jWaj%pj"b@v@,cJ~J nd B@aRB@x@A >eE7Xa aRf =R?="g@:@q$S"S#S#vahbibi B@jB*@x@A$AjjE jajk =j=%p%l@\@$mBBJn B@JB c@x@AAJo%  EYb Jep=Fw,q =@{{% %r@A@ m @ 8Asb  b t B@`B@x@A$AuEjZa!v =j*[`=j w@ )@ m' @cxJ(JA#y B@aRB@x@A ARzE aR{ = H=R"|@@ #wu’u*u@B}bb j~ B@B@x@A$AjE!\b@Y jaj =@݀= @@ `@(AJBn B B B@B@١%` AJ @EX]aJ Je=)kF =@{^{% @Y@ $F @+c !6AbDbe `bc B@`B@x@A$AE/^acb =j]Ԁ=j%pj"@Ҁ@,J*JA# `R- B@aRB@x@A ARE<_aR aR =R֒=R%@8@ 3Ғ"6 u "a8Bbb½j B@jBz@x@A$AjEF`jaj =jGJ=j @I@  `@r"AJBB$ B B@JB@x@AAJEJaJe=ˤF =:{%pF"@@c yAb ! y B@aB@x@A$AEѽKc =j~ba @c|@,J{J  B@aRB@x@A ARE4ca aR =Rx<=R"@7@ CS6r N&BbBb½ B@jBt@x@A$AjEe jaj =j=j%-(1@Q@~B B$ g B@JB@x@AAJEd@==mF =ܱ{% "@&@ `@c Abb  B@aB $X@x@A$Aj%  Ergeab =j'f`="@&@,Jm%J@ B@a B@x@A AREހ FG =R2= @@ o @3S"X t aBbb@$j B@jBz =@'@x@A$Aj%`DEgb jaj = {==j%-(%@㜀@~BFB B@A"B@@x@AAJ%`EUhJe=hFB =@{y[{% %@V@6 @`Ab)b ! b B@`B@x@A$AEiacb =j;р="@π@ m'$^JJ(> B@aRB@x@A ARE!jaR FaR =R=R"@@ ccp %ucC%a bbj B@jBc@x@A$AjECkjG =j$G=( @F@ rcBEB J$ c B@JBc@x@AAJE/ e=Fc = À#la @j@ $F @6 b ! bc B@`B c@x@A$A%  E1 B =j_=j%p3@½@ 5!j @J.J j B@a B/@x@A ARE@)@~?Bڀ B$ c@ B@JB@@x@AAJAEГ}  eB=QF; C =@{% %D@ @c Eblb cF B@`B@x@A$AGEWO~ac bH = ``=^(fI@ @ m2 @yJJZJ K B@B@x@A ARLEdƠ@YA FaRM =R ΀=R"N@lɀ@ @NDObȠb@%ĩcP B@jBF@x@A$AjQE끀b jajR =j{=(%-(%S@ׄ@ wTB7BJU B@JBc@x@AAJVEr=JeW=FcX =ZC{% "Y@>@ $F @ =!Zbb b [ B@aB@x@A$A\Ecb] =j#a^%^@@,_JﶠJA#F` B@aRB@x@A ARaEpa FaRb =Rw=R"c@s@ (2azBdbzrb@$je B@jB@x@A$AjfE+jajg =j/=( h@u.@$iB-B Jj B@JB@x@AAJkE el=Fm ={% n@N@ ': @6 Bob Z b p B@aB@x@A$AqEbbr =jb`=j+"j%s@)a@,tJ`JA#Fu B@aRB@x@A ARvEa aRw =Rf!=R%x@@ o"!﵌aByb$b½z B@jB B@x@A$Aj{E.Հ jaj| =j؀="}@@ !j%"AJ~Br׀J B@JB@x@AAJEb Je=6F ={% F"@@h .AbQb  B@aBh@x@A$AE>e=F = À :{% @]5@ c Bb   B@`B @x@A$AE,?@b =j̯aj%pj%@.@,JJA#F B@aRB@x@A AREfa FAAaR =RMn=R% @i@  cipWuij<j3B!bb j" B@jB@x@A$Aj#E;"4  jBBaj$ =j%="%@@c&Bw$B' B@JB@x@AAJ(E JCCe)=GF** ={% F"+@ހ@ 5n) @ VB,b^b b - B@aB - @x@A$A.EIbcDEb/ =jaY`=j 0@W@, 1J0J 2 B@aRB@x@A AR3EVa FFaR4 =R ="5@f@ s`$ppaCB6bbf7 B@jBb@x@A$Aj8E jGGaj9 =jeπ= c:@΀@ r;B!B J< B@JB@x@AAJ=EdHHe>=Fz8/|; `A? = X{% @@@@a! @.W gBAbb b B B@`B@x@A$ACEBacIJbD = `=j E@q@,cFJJ cG B@aRBz"vB@x@A ARH' / `E FKKaRI =R="J@케@  t䐁@BKbXb jL B@a$B@x@A$AjMEub2 jLLajN =jy= O@jx@~ƹ$( @.`AJPBwB JQ B@JB@x@AAJRE1aJ JMMeS=CFT =6{% U@C2@ c  mCVb ! yW B@aB* @x@A$AXENObY =ja Z@@,[JsJA#g\ B@aRB@x@A AR]Eca PPaR^ =R6k=Rc_@f@ D<T B@aRBwc@x@A AREc]a FnnaR = e="@_`@ $F!opTq0 uBb_ B@Bnb 5@A$Aj @`E jooaj =j=%-(%@@ r!j @(@BB>B,J B@aB@)$X@x@AAJEqԀ Jppe=F =@]ڀ{% %@Հ@ ! @pAb b b륱 B@`B@x@A$AEbcqrb =jP`=j$j"@~N@,JMJA# B@aRB|@x@A AREa ssaR =R="@ @C#Srvw]aBbq b½ B@jB@x@A$AjE€2 jttaj =jƀ=%@qŀ@~$( @"@BBĀB J B@JB@@x@AAJE~uue=F =@{% F%@N@ 5\ @ dAb ! b륱 B@`B@x@A$A(  E9av*xA!f =j=j @ @ m!j @JJ@ B@a B@x` =AR @`EaR FxxaR = ?`=M=F!@@y1 p҃aӔ@ .;Bbb½ B@Bx@x@A$Aj E-laj jyyaj =jo=j%pj% @@~ Byn B$  B@JBc@x@AAJE'Jzze=5:|5n =-{% `3E@(@ ! @ AbPb b B@aBy@x@A$AE; {$% =j="@/@,JJA# B@aRB#B @x@A ARE|+!R =Rf=R(o@ơ@ #[`p ]{Bb2b$ B@jB{ y@x@A$AjEHZj}}aj =j]=(%-(%!@@ $( @ūc"B\ # B@JB@x@AAJ$EaJ > ~~e%=פFy& = i{% "'@@ y) @+c A(bob b륱) B@`Bc@x@A$A*EVрb+ =ja^",@䏀@,-JPJA#rc. B@aRB- 5@A AR/ @`EcHa aR0 =RP=R"1@kK@3D"zu `̠B2bJbjc3 B@a$Bژ@x@A$Aj4Ejaj5 =jn=(%p(%6@@$7B*B J8 B@JB@x@AAJ9Ep e:=F* ; =eŀ{% "<@@ 5n': @ 1A=b b ! b > B@aB (,@x@A$A?Ezbb@ =j;`=^%A@~9@,BJ8J C B@aRB@x@A ARDE aRE =R=R"F@@ Ct,4 tZBGbb jH B@jBy@x@A$AjIEb jajJ =j=j K@o@ $( @"AJLBϯB$BM B@JB@x@AAJNEiJeO={F{ P =n{"Q@Hj@ * c /ARb  S B@aBy@x@A$ATE$acbU =j=jt"V@@,WJJA#X B@aRBv@x@A ARYEaR FaRZ =RX=FR%[@@ S.tt/aB\bb½] B@jB o@x@A$Aj^E-Wjaj_ =jZ=%-+c`@@$aBqY Jb B@JB@x@AAJcEJed=5%|e ={% "f@@ ': @c =AgbPb bh B@aB@x@A$AiE:΀cbj =j`aj^%k@@,lJ-JA#m B@aRB7B@x@A ARnEHEa aRo = p M="p@dH@ c+_ "y`a >BqbGbr B@B@x@A$AjsEjajt =jO= u@@$vBB Jw B@JB@&@x@AAJxEU ey=֤Fxcz =@F€{% {@@ c !B|b ! } B@`B@x@A$A~Ewbb =j8`= @o6@ m!j @,J5J  B@aRB@x@A ARE aR =R=R(o@@ sÔ@%U7t-QPuBb^b jc B@jB@x@A$AjEpb jaj =j=j @\@~) @K +BB$B B@JBc@x@AAJEeJe=xxF =!k{% @2g@ c Abfb ! c B@aB@x@A$AE~!aFb =j=%pj"@@,@J}ߠJA# B@aRB@x@A AREaR FaR =R9=R%@@ , bUqqt 4^BbbA;j B@jB@x@A$AjETjaj =jW=j%-%n@V@ B^B$  B@JB@x@AAJEaJ> e="| ={% "@@ , Ab5b ! 5n B@aB@x@A$AEˀcb =jVa"@@,cJ"JA#Fc B@aRBc@x@A ARE-Ba(aR =RI= @9E@ 텱``$aBbDb@$j B@jB@x@A$AjEfaj =j8aj%-(%@@ B  B@JB@x@AAJE: e=F =&{% %@u@ * ! @ vAb  b B@aB@x@A$AEtbb =j4`="@H3@,J2J F B@aRB@x@A ARE aR =Ru=R"@@ Vqt'+aA Bb?b(½ B@@]B$X@x@A$AjEUb jaj =jժ=( @2@ $( @.`AJBJ)Bc B@JB@@x@AAJEbJe=]uF =@h{% @d@ 5\ @ Abxcb bc B@`B@x@A$AEcat b =j"=j"@W!@ m!j @J JA# B@aRBB@x@A ARE RaR =R="@܀@ %6% Eqa BbYb j B@jBF@x@A$AjEpb ]aj =j=%-(+c@_@JtJA#c? B@aRB@x@A AR@Ena aRA =R2v=R"B@q@y3Y+]qBCbpbD B@jB; @x@A$AjEE*jajF =j-=j%-(%G@,@ $( @"@BHBVBI B@JBW@x@AAJJE eK=FL ={"M@@ ;TANb4b! O B@aB@x@A$APEbbQ =jOa`="R@_@,SJJA#T B@aRBxJeBc@x@A ARUE,a aRV =R=R%W@1@ 7!oDa+BXbb½Y B@jB5n@x@A$AjZE jaj[ =jC׀=(%p\@ր@$]BՀB J$ c^ B@JBc@x@AAJ_E:e`=Fa =&{% "b@v@ Bcb  `cd B@aB@x@A$AeEJabf =j `=j$j%g@S @,hJJA#ni B@aRBy@x@A ARjE FaRk =Rɀ="l@Ā@ o)!#3a|[BmbBb jn B@jB <@x@A$AjoEU} b jajp =jŀ=%-+cq@#@$rB s B@JB@!K$X@x@AAJtE8 Jeu=]KFuyv =@>{ w@:@ ةAxbw9b y B@`B !@x@A$AzEbcb{ =j aj$%|@@,}JaJ ~ B@aRB@x@A AREpk a FaR =Rs="@`n@ 3!:6[qbmbf B@jB @x@A$AjE& jaj =*=j%-%@)@ Rr' @"@BBRB J)B B@JBc@x@AAJE} e=Fx =m{F% +c@@$X qbb! 륱 B@aB@x@A$AEb =j= @@,JpJA#j B@aRB@x@A AREYaR aR =R0a=R"@\@C3D Je=F =*{% "@t{@, ?Ab֡ !  B@aBc@x@A$AE5ab =j=^.@S@,cJJA#c B@aRB @x@A AREάaR aR =Rt=R"@֯@ cckXQ@@ c Zv++aNY@>B?bqb½@ B@jB@@x@A$AjAE,b jajB =j =(%-(%C@{@~DBܨB J$ E B@JB@x@AAJFEb-aJ,eG=FH = À h{% "I@Xc@ , AJb K B@`B @x@A$ALE.a* bM =j݀=G@² .N@8܀@,OJ۠J P B@aRB@x@A ARQE/aRaRR =Rh=R"S@@ N$F+!3 FUEat2BTb'b U B@jBc@x@A$AjVE9P0aj  AW =jS="X@@ !jn"AJYB}RAZ B@JB@x@AAJ[E 1J@=\=E|w3; ] = À{% ^@ @ A_b\b ` B@`Bc@x@A$AjaEGǀbb =jx2aj$j"c@م@,dJEJ 4` e B@aRB@x@A ARfET>3a FaRg =R F="h@\A@ ! elBib@bjj B@jB`@x@A$AjkE jajl =j_=%p+cm@@$nBB Jo B@JB@b2a,@x@AAJpEb4b Jeq=Fcr =@Z{% (s@@ J 5Atb ! u B@`B !,@x@A$AvEp5abw =j 16`=j$j%x@k/@ m!j @ @ yJ.J@.z B@aRB@x@A AR{E   aR| =R="}@@ e ÔPaJB~bnb j B@jB@x@A$AjE|7b j  aj =j =j @i@cBɥB$  B@JB,@x@AAJE_8J  e=qF|c =d{F% F%@>`@ ! @ @Bb ! b B@aB,@x@A$AE9ac  b =jڀ=j%pj%@ـ@,JؠJA# B@aRB@x@A ARE:aR FaR =RI=R"@@c1 ,$ a@ aBbb½ B@jB @x@A$AjEM;jaj =jP=j$+c@@$BfO J$  B@JB@x@AAJE<J >=&| ={% "@ @/ BAbAb h B@aBc@x@A$AjE,Āb =jR=a @@,JJ  B@aRB@x@ =AR @`E9;>a aR =RB=R"@I>@ Qke`BBb=b B@a$B ,@x@A$AjE jaj =jP=j%-(%@@ ) @(@BB B B@JB@x@AAJEF?e=ĄF =;{% "@@ ! @c WHAb ! b륱 B@aB "@x@A$AEm@ab =j-A`="@X,@,J+J B@aRB@x@A ARE䀈y FaR =R= @@#*t*Qb1aBbSb jc B@jBc@x@A$AjEaBb jaj =j٣=j%p(%@6@~ `@ťB B$ Bc B@JB@$/@'@x@AAJE[CJe=inF =@@+"a{% %@#]@ 5\ @+c G Ab\b bc B@`Bc@x@A$AEoDacb =j׀="@Հ@ m!j$JbJ(> B@aRB@x@A ARE|EaR FaR =R/=R"@@3*

    =j=j%-(%?@@~@Bw B$ A B@JB@x@AAJBEUYJ88eC=3hFD =[{% "E@V@ ; @W GAFbNb bG B@aB 'Zc@x@A$AHE9Za9:bI =jmр= J@π@,KJ;J L B@aRB@x@A ARMEF[aR F;;aRN =R=R"O@R@ "~u$a*ZBPbbĩQ B@jB c@x@A$AjREC\j<?b^ =jz^aj"_@ay@,`JxJ ca B@aRB@x@A ARbE1_a @@aRc =R9= d@4@ 4"eaBebXbf B@jB/@x@A$AjgEn jAAajh =j= i@Q@~jB B$ ck B@JB@$@x@AAJlE`BBem=vF* n =@宀{% o@2@ c @#Bpbb cq B@`B@x@A$ArE|daacCDbs =j$b`=^6t@#@ m!j @ uJ{"J v B@aRB@x@A ARwEۀ FEEaRx =R>=R(oy@ހ@ $F!RoEeaBzb݀bĩc{ B@jB o@x@A$Aj|Ecb jFFaj} =j=(%-(+c~@@ r!j @"@BBPBJ B@JBc@x@AAJERdJGGe=eFc =X{% "@S@ c Ab3b 륱 B@aBc@x@A$AEeaHIb =jS΀=^"@̀@,J JA#j B@aRB5n@x@A ARE+faR FJJaR = Ȍ=R"@'@ c+WE"䐃)} Bbbf B@B@x@A$AjE@gaj2 jKKaj =j:D=(%p(%@C@~BBB J B@JB@x@AAJE8 JLLe=F/ =)ha% "@v c \*Ab ! B@aBc@x@A$AE,MNb =jwij)j%@Nv@,JuJA# B@aRB@x@A ARE.ja OOaR =Rz6=R"@1@ y~`paHBb9b½ B@jB; @x@A$AjES jPPaj =j="@7@ B쀃J$&5 B@JB@x@AAJEڥkb JQQe=[F =Ϋ{% F"@@ y! @W 'Lbvb b B@aBy@x@A$AEaalaRSb =j!m`=j^%@@ JXJ  B@aRB@x@A AREn؀ TTaR =R="@vۀ@ !d`%Kc}daEbڠb@$j B@jBb@x@A$AjEnb jUUaj =jy=%-(+c@Ֆ@ r$( @"@BB5BB B@JB@x@AAJE|OoaJ JVVe=F =dU{% %@P@ b$F @ Abb b륱 B@aBF@x@A$AE pacWXb =j0ˀ=j$j"@ɀ@,cJȠJA#F B@aRB{ B\ @x@A AREqaR YYaR =R="@@ p&r0!g {Bbb½ B@jB; @x@A$AjE=rajF jZZaj =jA=j%-%@s@@~B?Bc B@JB; @x@AAJE J[[e=F$X = {F% %@X@ hv/G?Ab ! B@aB@x@A$AEsb\]b =jtt`=j @/s@,JrJA#n B@aRB@x@A ARE+ua ^^aR =R[3=R"@.@+_ьYKoBb"b  B@jB@x@A$AjE8(__aj =j=j @(@B逃 B$  B@JBc@x@AAJEv``e=@F@ =㨀{% @.@ c KBbb  B@aB@x@A$AEF^waabb =jlx`= @@,J8JA# B@aRB{c@x@A ARESՠ@Y ccaR =R܀=R%@[؀@EKpd}Bbנb@%ĩ B@jB@x@A$AjEڐyb jd1A =jb=j%-(+c@@~BB B@JB@x@AAJEaLzaJ Jeee,  Fc =MR{% "@M@ @ UAb ! b  B@a$B* @x` =A @E{acfgb =jȀ="@vƀ@,JŠJ@Fc B@a B@x@A AR E~|aR hhaR =R= @@ @#+^+kFɂ bqb½ B@jB@@x@A$AjE{:}jiiaj =j>= @d=@ BEEIJtte?=ǤF@ =6O{% A@J@ 6 cCBBb  C B@aB@x@A$ADEauvbE =jĀ= cF@SÀ@,GJ J (RH B@aRB@x@A ARIE{aR FwwaRJ =Ry=R%K@~@ cS}ÔPz )a+BLbFb@$jM B@jBc@x@A$AjNE`7aj jxxajO =j:= P@J@rcQB9BR B@JB@x@AAJSE JyyeT=h|U ={% V@@ ! @c  BWbb b X B@aB $X@x@A$AYEnbz{bZ =jn`=j^+c[@l@@S' @\JeJ J] B@aRB; @x@A AR^E{%a ||aR_ =@3-="`@(@ c c0#~FP bhƀb ! c? B@aB/@x@A$A@ERbcbA =jA`=^"B@?@,CJQJ FcD B@aRB@x@A AREE`@Y aRF =R$R"G@p~LpRtZ)pKv@=4HbbjI B@jB@x@A$AjJE況, jajK =jo=%-(+cL@ʶ@ MB*BJ$ N B@JB@x@AAJOEmobw JeP=FQ =Uu{"R@p@, ASb b T B@aBL6@x@A$AUE*acbV =j%=j)%W@@,XJJA#(RY B@ B@x@A ARZEaR aR[ =R=FR%\@ @ #d`9n` t.Q! *@B]bubj^ B@jB @x@A$Aj_E]jaj` =ja=%-%a@l`@$bB_B Jc B@JB@x@AAJdEJee=+|f ={H yg@I@ Ahb ! i B@aB@x@A$AjEԀcbk =jÔaj^%l@$@,mJJA#n B@aRBB@x@A ARoEKa aRp =ReS="q@N@!3bKf aFBrb#Nb½s B@jB@x@A$AjtE*jaju =j = v@@$wBf J$ cx B@JB@8 @x@AAJyE ez=2ՄFwL6{ =@Ȁ{% :(|@À@c B}bLb ~ B@`B@x@A$AE7~btb =je>`=$j%@<@ m' @ J2J  B@aRB@x@A AREE aR =R=R"@M@yC@䐃(yEBbb B@jBy@x@A$AjE˰b jaj =jS=j%-+c@@~BB B@JBc@x@AAJERlJe=ӤF| =:r{% "@m@ 5n$F @+c  Ab ! b B@aB@x@A$AE'ab =j=%pj%@h@, JJA# B@aRB@x@A AREaR FaR =R=R"@@ S%`uvuw+haCaBb^b@$jc B@jBz@x@A$AjEmZjaj =j]=j @Q@ B\ B$  B@JB@x@AAJEaJ> e=u(| ={% F"@*@W @(SBbb bc B@aB; @x 9A$A @Ezрb =ja"@@,cJiJ@ @c B@a Bc@x@A AREHa aR =R&P= @K@ cG\uvdpEbbJbA;j B@jB@x@A$AjEajfaj =j=j%-(+c@@ B[B  B@JB@x@AAJE e=҄F; =ŀ{% %@@ c bb1b 'Z  B@aB@x@A$AE{bb =j7;`="@9@,JJ F B@aRB@x@A ARE) aR =R=R"@2@5ns~ }{zy5@;Dbb f B@jBy@x@A$AjEbjaj =j4=(%-(%@@ BB B@JB@٢oB@x@AAJE7iJe=Fx =@+o{% "@rj@ * ! @c  Ab e B@`B W!B@x@A$AE$ab =j=^1@D@ m'*QJJ  B@aRBL6@x@A ARE˛aR aR =R^=R"@@ c T7` cBb+bA;# B@jBc@x@A$AjERWjaj =jZ=( @?@$BY J$ c B@JBc@x@AAJEaJ e=Z%| ={%p@@ c nBbub B@aBc@x@A$AE_΀cb =ja^%@@,J^JA#F B@aRB@x@A AREmEa aR =RM=R%@qH@ cvwd`Xa8BbGb½ B@jBW@x@A$AjEjaj =j=j%-(+c@@ `@n"@BB<B B@JB@x@AAJEz e=F = Àf€{"@@$X  Abb!&U B@`B@x@A$AExbcb =j,8`=j+""@6@,.5J   B@aRB@x@A ARE aR =R=FR%@@ D %zNbrb j B@jB/@x@A$AjEb@Y jaj =j!= @@$ BݬB J$ c B@JBc@x@AAJ EfaJ Je =F = l{% F"@Xg@ / @1a0 6b  B@aB$X@x@A$AE!ab =j=j^%@5@,JߠJA#Rc B@aRB$X@x@A AREaR aR =RX="@@ c &v$p? Ba Hobbj B@jB $X@x@A$AjE7Tjaj =jW=%-(+c@ @$BoV J B@JB@x@AAJ!EJe"=?"`=# ={%p%$@@ 5A%bYb Zy& B@aB @x@A$A'EDˀb( =jta )@׉@,*JCJA#+ B@aRB@x@A AR,ERBa aR- =RI=R".@FE@ EG$1SF`xb/bDb j0 B@jB@x@A$Aj1E jaj2 =j`aj 3@@ 4B B5 B@JB &B@x@AAJ6E_ Je7=Fzc8 = O{ 9@@ c h:b ȥ! ; B@`Bc@x@A$A<Etbcb= =j5`= >@y3@, ?J2JA#c@ B@aRB; @x@A ARAE뀈 aRB =R=R%C@@ G`vwxez`4a^CHoDbkb½E B@jB@x@A$AjFEzb jajG =j=j H@X@~IB B$ J B@JB@x@AAJKEcJeL=uFM =h{% cN@9d@ , BOb ! cP B@aB@x@A$AQEabR =jހ="S@݀@ 2$,TJܠJA#U B@aRBc@x@A ARVEaR\aRW =RA=R%X@@ , -Q8ayYb b@$jZ B@jB{@x@A$Aj[EQaj?aj\ =jT=(%-(C]@ @ ^BlS g$ _ B@JB@x@AAJ`E aJea='|b ={% "c@ @ %mdbBb ! e B@aB@x@A$AfE)ȀAg =jVaj%pj6h@@,iJ$JA#Fcj B@aRB@x@A ARkE6?aaRl =RF=R"m@GB@ |}Qxua!DnbAb fo B@jB@x@A$AjpEjajq =j=="r@@ sBB $ t B@JB@@x@AAJuEDev=ɤFvw =@4{% F"x@@ ! @yK }Byb b z B@`B@x@A$A{Eqa ZW b| =j1`=^%}@U0@ mc~J/J ̶ B@aRB@x@A ARE耈FaR =R=R"@@$qGQ@l.Bb\b j B@jB@x@A$AjE_bjaj =j맀=(%-(+c@F@$B J B@JBc@x@AAJE_aJe=krFc =e{% "@'a@ >$F @  Ab`b ! b B@aB@x@A$AElayKc =jۀ=j c@ڀ@,Jo٠JA#{ B@aRB@x@A AREzaRA@aR =@3="@@ (o!%xү !tސBbbj B@B@x@A$AjENjaj =jQ=j @P@ !j @(AJBIB$B B@JB @x@AAJE J@== |/ ={F% @ @ ! @zbAb#b ! b B@aB@@x@A$AjEŀyb =j/aj$j"@@,JJA# B@aRB@x@A AREb B@jB@x@A$AjE jaj =j2=%-+c@@$BB J$  B@JBc@x@AAJE)e=F; ={% c@c@ Ab !  B@aB@x@A$AEna  b =j.`=j^%@B-@@SW`@yJ,JA#J B@aRB$X@x@A ARE F  aR =@f=R"@@ 3`GFcyvb-b jc B@B@x@A$AjEDb  aj =jԤ=j%-(%@/@ ) @"@BB B$B B@JB@x@AAJE\aJ   e=LoF =b{% "@^@ ! @c Abf]bĩ! b륱 B@aB@x@A$AEQb =j=j @Y@,JJA# B@aRB@x@A ARE aR =Rۀ=FR"@ր@ CGS0v"FadBbLb j B@jB@x@A$AjE_b jaj = ے=j(%@8@ $( @ťB B$B B@B@x@AAJEJaJ Je=Fnb =P{F% @!L@ ) @+c 7AbKb B@aB@x@A$AElayb =jƀ=j^"@Ā@,JgJ  B@aRB @x@A AREz}aRFaR =R)=R%@~@7!1S% w"$`%$abb½ B@jB@x@A$AjE9aj faj =jx<="@;@ B =R+=R"?@'@ C#6 uvuwa?B@bp&bA B@jB@x@A$>BE j))ajC =j =j%-%D@f@$EBB JF B@JB@x@AAJGE **eH=FI ={% "J@K@ W': @9 ]AKb ! b L B@aB@x@A$AMEV ac+,bN =j `=%O@@,PJJA#cQ B@aRBc@x@A ARRE F--aRS =RbՀ= T@Ѐ@ %$ E4a&BUb&b jcV B@jB@x@A$AjWE( b j..ajX =j= Y@@~$( @"AJZBp B$Bc[ B@JB@x@AAJ\EDJ//e]=0WF^ =J{% _@E@ @ c A`bKb ca B@aB@x@A$AbE600bc =j=^"d@B@,eJJ f B@aRB @x@A ARgEy F11aRh =RkÀ=R(oi@ɾ@ c0 Qb/Bjb5bĩk B@jB} o; @x@A$AjlECw"  j22ajm =jz=(%-(+cn@@ oB{yJp B@JB@!K5n@x@AAJqE2J33er=ҤFs =@8{% "t@4@ ! @c pAubf3b b v B@`B@x@A$AwEQ 44bx =j=j y@U@ m' @yzJJA#{ B@aRBc@x@A AR|Eש55aR} =R="~@଀@ 5a XBbLb  B@jB,@x@A$AjE^ej66aj = h= @E@ $( @(AJBg  B@Bc@x@AAJE J77e=F =&{% @""@ $F @6 Ab!b bc B@aB@x@A$AEl܀89b =jaj%pj"@Ꚁ@,JVJA# B@aRB@x@A AREySa ::aR =R.[="@V@ $F!3@zT˵zuy"av,BbUb@$j B@jB; @x@A$AjEj;;aj =j=%p+c@@$BHB J B@JB@c@x@AAJE <b =j:F`=j$j%@D@ m' @cJJA#c B@aRB@x@A AREu??aR =R$R"@'@zzÔ "(t]LBb j B@jB|@x@A$AjE@Y f@@aj =j2= @@ ' @"DBB$B B@JB@x@AAJE(tbwJAAe=F =z{F% F"@fu@ $F @+c Bb  bc B@aB@x@A$AE/acBCb =j=%pj"@B@,JJA#R B@aRB@x@A AREaR FDDaR = U=R"@@ 䐃NaZThްDfb$b j B@By@x@A$AjECbjEEaj =je=j%-+c@@ Bsd B B@JB@x@AAJEJFFe=K0| =#{% "@@ ! @.W zBAbfb b B@aB@x@A$AEQـGHb =jf a"@Ǘ@,J3JA# B@aRBc@x@A ARE^P!a IIaR =RW= @bS@ Fzxy}a0mBbRb@$j B@jB@x@A$AjE "jJJaj =jm=j @@ $( @(AJB)B B@JB@x@AAJEk KKe=F =\̀{F% @Ȁ@ Abb ! c B@aB@x@A$AE#bLMb =j C$`=j^"@A@,J@J Fc B@aRBz B@x@A ARE+ NNaR = p%$R(o@~ ~Y`vBblb j B@B @x@A$AjE, jOOaj =!!="@Z@BB$ B@JB@x@AAJE q&bw JPPe=Fw =w{% @Gr@$X Bb  B@aB$X@x@A$AE,'acQRb =j="@'@@S#2%JJA#J B@aRB@x@A ARE(aRSSaR =@[=R%@@!R! # mbbb½ B@B@x@ =Aj @`E(_)jTTaj0 `=b)`=(%-(1@ @ rBla J B@B`x@9 AJ @E*a  UUe=0-|c = {% "@@ $F @+c hbKb! b B@abB >"$X@x@A$A E5րcVWb =j`+aG r%dj+c @@,c J,J  B@aRB{c@x@A ARECM,a XXaR = U=R"@SP@ $F 3SSSS ADbOb½ B@B  @x@A$AjE-aj2 jYYaj =jR =( @ @~!j @"AJBB J$Bc B@JB@x@AAJEPĀ JZZe=ѤFy =Yʀ{% F"@ŀ@ ! @( xAb ! b B@aB@x@A$AE.b[\b =j@/`=$j"!@f>@,"J=J c# B@aRB@x@A AR$E ]]aR% =R=R"&@@ cCSStZ/Q'b]b ( B@jBc@x@A$Aj)Ek0b^^aj* =j=%-+c+@X@ $( @"@B,B $B- B@JB@x@AAJ.Em1J__e/=sF0 =s{% "1@-o1` c aA2bnb 륱3 B@aBc@x@A$A4Ey)2``ab5 =j=j^"6@ @,7JwJ Rc8 B@aRB@@x6 9A AR9 @`E3aR bbaR: =RA=G@Qx ";@@!oSTQTSu@z,B<bbo= B@a$B oc@x@A$Aj>E \4jccaj? =j_=%-`3A@@@$ABa^ JB B@JB@x@AAJCE5aJ ddeD=*|tE ={% "F@@t AGb0b! H B@aB@x@A$AIEӀcefbJ =jI6aj$j%K@@,cLJJA#M B@aRB@x@A ARNE(J7a ggaRO =RQ=R"P@0M@ o)!cSuSuSu"tc/]L3 QbLb½R B@jB@@x@A$AjSE8jhhajT =j7 = U@@ !j @"DVBBBi2rW B@JB @x@AAJXE5 iieY=F*Z =-ǀ{% F"[@q€@ c  \b ] B@aB@x@A$A^E|9bcjkb_ =j<:`=$j"`@;;@,aJ:J b B@aRB@x@A ARcE llaRd =R|=R"e@@ ,s4"md`& fbAb jg B@jB; @x@A$AjhEP;b jmmaji =j貀=j%-1j@E@~ `@ kB B$ Bl B@JBc@x@AAJmEj<Jnnen=X}Fo =p{ p@l@ ! @  qbskb b5nr B@aB@x@A$AsE^&=aopbt =j="u@@,vJTJA#w B@aRBc@x@A ARxEk>aR FqqaRy =R= z@w@  $cpttc.paD{b㟠b@$j| B@jBnb@x@A$Aj}EX?jrraj~ =jv\=j%-@[@ $( @(AJB2B B@JB@x@AAJEx@Jsse=F =]{F% (@@ W c lAbb ! 륱 B@aBV@x@A$AEπtub =j#Aaj^"@@,JJA#F B@aRBL6@x@A ARE GBa vvaR =RN=R"@ J@ d`eaBbyIb@$j B@jBz@x@A$AjECajywwaj =j'="@@cBB J$c B@JB@x@AAJE xxe=ЄF =Ā{% @V@ ': @c _Bb ! b B@aB@x@A$AEyDbcyzb =j9E`=^%@08@,J7J > B@aRB@x@A ARE {{aR =R^=R%@@ "*t aJVb*b@$ B@jBc@x@A$AjE5Fb j||aj =j=(%-(1@@ r$( @"@BBiB B@JBW@x@AAJEgGJ}}e==zFW =m{% "@h@ c AbXb c B@aB@x@A$AEB#Ha@Y| ~~b =j'=j c@W&@@S~°!j @BJ%JJ B@aRBB@x@A AREހRaR =@{="@@   ! !!2a(BbIb@$c B@B@x@A$AjEPIb aj =jĝ=%-(%@ @ rBµ) y B@JBc@x@AAJEUJaJye=Fb = À[{% %@W@$X lAbwVb !  B@`B WB@x@A$AE]Kab =jр=j%pjK@π@,cJ`J F B@aRBy@x@A AREkLaR FaR =  =R"@@ !E!Q![!`!o Bbb@& B@B@x@A$AjECMajF jaj = G=%-%@F@ B>B B@B,@x@AAJEx Je=FB =`Na% "@@ ) @ AVAbb  B@aB@x@A$AE$A = `!{Oaj @y@,JxJ F B@Bc@x@A ARE 2Pa aR =R9=R"@5@@ !!e!JK!aBb4bfc B@jB o @'@x@A$AjE jaj =j#=j @@ BB$ a5 ic B@A"Bc@x@AAJEQe=F = {%p@T@ @ Vvb ! bc B@aB,@x@A$AEdRab =j$S`="@7#@,J"JA# B@aRBc@x@A ARE FaR =R\= @ހ@'a$F @ !Qi  U`Eb*b aj B@jBy@x@ =Aj @`E5Tb jaj =j=j%-(+c@@~!j @.`@BB} B$B B@aB@x@AAJERUJe==eF =X{% (B ÀS@ hv/G?RAXb 륱 B@`B,@x@A$AEBVacb =)e΀="@̀@, J1JA# B@aRB@x@A AR EPWaR F-, =R=R" @T@ I!aaBbb@$j B@jByc@x@A$AjE@Xj#!j = cD=j%-(%@C@ BB J B@B@x@AAJE] e=ޤF =IYa"@!!%N .`# +c gA 3 ǝ! b  B@aBy@x@ =A @E䷁,b ==j)(f@@,J\J@Fc B@a B@@x@A AR EksZb aR! =R{=FR%"@ov@!+V?ve+i|B#bub j$ B@jBQ@x@A$Aj%E.[aj j//%& =jq2= '@1@ `@"AJ(B-BB$ Bc) B@JB@x@AAJ*Ex Je+=Q, = Àd{% F"-@@ cA.bb / B@`B@x@A$A0E\bb1 =jf]`=j^"2@rd@,,3JcJA#4 B@aRB@x@A AR5E ^a aR6 =R$="7@ @c!  B@JB@x@AAJ?E_e@=Fz$XA ={% %B@W@.! @ .`c u6C 3 D B@aB@x@A$AEEO`abF =a`=j G@+@,cHJ JA#FcI B@aRBy@x@A ARJE FaRK =RV΀=R"L@ɀ@ #+VŴJo!a}cDMb"b@$jN B@jB@x@A$AjOE5bb2 jajP =j=j Q@@~$( @w(AJRBy BS B@JB C=b@x@AAJTE=caJ JeU=@PFwV = C{%pW@>@ c ّAXbWb! cY B@`B@x@A$AZEBb[ =jgda"\@ɷ@ m!j%` ! !% AR]J5J F^ B@aRBc@x@A AR_EOpea aR` =Rw=!a@\s@ 3 {Ajbbrb fc B@jB@x@A$AjdE+fjaje =jV/=j%pjCf@.@gBB$ ,h B@JBc@x@AAJiE]Fej=ޤF{k =M{% (l@@ TAmb ! n B@aB@x@A$AoEgbbp =jch`="q@za@,rJ`JA#s B@aRBc@x@A ARtEia aRu =R!= v@@ C+V % !aywbab@$jx B@jBy@x@A$AjyExՠ@Y jajz =j؀=j%-(%{@S@~|B׀ B$ } B@JB@x@AAJ~Ejb i A8e=F =㖀{% %@4@ ) @ .`Q6 m 3b! b B@aB,@x@A$AELkacb = l`="@ @,J J Rc B@aRB@x@A ARE aR = @ˀ=R"@ƀ@ SYr0|uJ@hDbb j B@Bx@x@A$AjEmb jaj = =j%-(%@@ cB^B$  B@BB@x@AAJE:nJe=!MF = À@{"@;@ !%N .`E+c UA 3zaj^%@@, J JA# B@aRB@x@A AREj{a FaR =Rq="@)m@ cTD䐁Xa8Hoblbf B@jB~c@x@A$AjE%|jaj =j()= @(@ r5> @"AJB'B Jc B@JB@x@AAJE' e=F ={% @b@c Ab ! c B@aBc@x@A$AE}bcb =j\~`=j @8[@,cJZJA# B@aRB|@x@A AREa aR =RY=R(o@@ A%7ԣ@b'b@%j B@jBz@x@A$AjEBπ2 jaj =jҀ=j( B@aRBc@x@A AREfaaR =Rn= @j@ < E䐃a Bb~ib@$ @j B@jBW@x@A$Aj!E"aj aj" =j&=j #@b%@~r$B$B$ c% B@JB@x@AAJ&E ހ Je'=F( ={% )@C߀@ ) @+` $XB*b ! bc+ B@aBc@x@A$A,Ebcb- =jY`=".@X@,/JWJA#F0 B@aRB@x@A AR1Ea aR2 =RO=R(o3@@ *fV$ÔPOa[B4bb@$j5 B@jB@x@A$Aj6E&̠@Y jaj7 =jπ=j%-(+c8@@9B_΀ B$ : B@JB @x@AAJ;Ee<=.Fc= =!?{">@鈀@c A?bIbj! @ B@aB@x@A$AAE4CbB =jG=j9|C@8F@,DJEJA#RE B@aRBz@x@A ARFE FaRG =RdaFR%H@@ yTp +tt5BIb/b jJ B@jB@x@A$AjKEA jajL =jѽ=%-M@-@rNBB$ O B@JBc@x@AAJPEubw JeQ=ФFw!^|    AR = ){{% "S@v@ c BTbdb cU B@aB@x@A$AVEO1aɂbW =jx=j^%X@@,YJFJ >Z B@aRB@x@A AR[E\aR aR\ =R ="]@d@ ӔPŵaB^bЪb½_ B@jB@x@A$Aj`Ecjaja =jkg=%-(+cb@f@ rcB'B Jd B@JB@x@AAJeEjJef=F g =V%{% %h@ @ ! @K  Aibb b j B@aB@x@A$AkEڀbl =jaj m@s@,nJߘJA#Fo B@aRB{c@x@A ARpEQa aRq =RY=R"r@U@"t c,iBsb~Tbt B@jB@x@A$AjuE aj2 jajv =j=j w@`@ $( @(AJxBBy B@JB B@x@AAJzE Je{=ۄFxy| =@΀{% }@Fʀ@ c ]A~b ! c B@`B@x} 9A$A @Ebb =jD`=j @%C@ m!j @,JBJ@ B@a Bc@x@A AREFaR =RAaRF!@~ o @o"~$%UxBbb½c B@jB !@x@A$AjE&, faj = {=j%pj+c@@~Bf B$  B@JBc@x@AAJErbw Je=.F| =x{% (@s@ @0AbIb! b B@aB ",@x@A$AE4.b =j2="@81@,J0J B@aRBxc@x@A ARE aR =Rv=R"@@"#%&L$a+Bb?b$ B@jBB@x@A$AjEAaj =j=(%-(%@@ Bu $  B@JBh@x@AAJE`Je=ФFw!^. = )f{% "@b@ y) @+c fAbdab b B@aBc@x@A$AEOat ^b =j!=j)j.@c@,JJA#4` c B@aRBc@x@A ARE RaR =R߀="@ڀ@,"3`<}d`@5nܭBbQbA;a`oc B@jB* @x@A$AjE\b jaj =j䖀=%-%@@@@ c Bb=b B@aBy@x@A$AEb =ja"@@,JJA# B@aRB@x@A AREoaBaR =RYw= @r@ yS"}x~u$aTBbb j B@jB @x@A$AjE+aj+uaj =j.=j @-@ BUB B@JB@x@AAJEe=$F~ ={F% @@ B! @.W  Bb?b ! b B@aB "@x@A$AE&bA =jVb`=j^+c@`@,J%J  B@aRB@x@A ARE3a aR =R =R+c@<@ c4"܌Bbb  B@jB@x@A$AjEԀjaj =j:؀="@׀@~cBB$&5 B@JB@x@AAJEA@==ƤFq =9{% @}@ y c x~Bb !  B@aBy@x@A$AjEKa b =j `=^%3@Z @,J J c  B@aRBQ@x@A ARE aR =Ryʀ=R%@ŀ@ (o'Eos "Qk0S]LBb5b j B@jB oc@x@A$AjE\~b jaj =j聀=(%-(6 @B@ !j @B B BB B@JBc@x@AAJ E9aJ Je=dLF*  =?{% "@;@ ! @CAb:b b륱 B@aB@x@A$AEic  b =ja^"@@,J`JA#gc B@aRB@x@A AREwla   aR = 3t=R"@o@ cC!E ?kBbnb½ B@B@x@A$AjE'j  aj =j~+=j%-(%n@*@ $( @"@B B:B)B! B@JB@x@AAJ"E   e#=Fb$ =p{"%@@; +uA&b b! ' B@aB@x@A$A(E bcb) = `;_`=j)"*@]@,+J J , B@B@x@A AR-Ea aR. =R=FR%/@0@ E%0C! x$ʒB0bb½1 B@jB@x@A$Aj2E jaj3 = +Հ=%-%4@Ԁ@$5BӀB J$ 6 B@Bc@x@AAJ7E&e8=F9 ={% ":@c@ ': @c n`A;b ! b< B@aB .X@x@A$A=EHab> =j`=j^%?@7@,@JJ cA B@aRB@x@A ARBE FaRC =Roǀ="D@€@ % 0 r0QaLBEb:b@$jF B@jB$X@x@A$AjGEA{b.ajH =j~=%-(%I@@$JB} J$ K B@JB@$L6@x@AAJLE6aJ eM=IIFzN =@<{% %O@8@  VAPbc7bQ B@`Bc@x@A$ARENbS =jwa T@ٰ@ m!j @UJEJA#V B@aRB@x@A ARWE\ia aRX =R q=R"Y@`l@ (o!RoDъ0C""v 8BZbkb j[ B@jB o@x@A$Aj\E$aj jaj] = ^(=j%-(%^@'@~!j @n(@B_BB` B@B@!K@x@AAJaEi Jeb=F]Lc =@]{ d@@ c \'Aebb ! cf B@`Bc@x@A$AgEbcbh =j\`=$"i@sZ@ mjJYJ(>k B@aRB@x@A ARlEaaRm =R=R%n@ @ t"/4\~$ÔaBobyb p B@jB@x@A$AjqE f  ajr =jҀ=j%p%s@[р@ tBB$ cu B@JB@x@AAJvE b J!!ew=Fx =!{% "y@@@ m$F @6 Azb ^! b @ { B@aB 'Z@x@A$A|EE""b} = ``>J="~@H@,QJ J j B@BB@x@A ARER##aR =R=R"@ @ c @Pŀtt Ia Bbb½ B@jB c@x@A$AjE f$$aj = '=(%-(%@@ rB羀B J$  @d B@B@c@x@AAJE&xb J%%e=-FB =@~{% "@ay@ , :Ab !  B@`B,@x@A$AE3a&'b =j=^+c@7@ m'*QJJA# B@aRB@@x@A AREaR ((aR =R}=R"@έ@ o$F!RoK "$ %Uf) aBb:b½ B@jBɂ@x@A$AjEAfajf))aj =ji=j%-(%@*@~!j @"@BBh B$B B@JB,@x@AAJE!aJ **e=H4|@ ='{"@"@ $F%N+c Abcb! b륱 B@aB@x@A$AEN݀+,b =jqaj$"@ћ@,J=J  B@aRB@x@A ARE[Ta --aR =R\=FR%@`W@ *%+ , - ac B@aRB@x@A >!0E[?aRA@@@aR4 G=R%@oB@B#3C L䐃,PQ BbAb@&4j B@B; `x@9 Aj @`E jAAaj =A|`=^=(*+c@@ rBB J B@B@@x@AAJ EiBBe =F =@Q{% " @@ @ !#A bb b  B@`B@x@A$AEqaCDb =j"2`=^%@0@ m'+,J/JA# B@aRB@x@A ARE FEEaR =R=R"@@ cC0:Tu @a6Bbqb j B@jB@x@A$AjEb2 jFFaj =j=( @`@~ƹBB B@JB@x@AAJE `aJ JGGe=rF|c =e{% !@Aa@ c 1B"b ! # B@aB@x@A$A$EHHb% =j: =j%pj%&@@,'J J ( B@aRBy@x@A AR)Eנ@Y IIaR* =Rހ="+@,ڀ@ c Su L|h@[B,b٠b@%½- B@jB@@x@A$Aj.EJJaj/ =j/=j 0@@$1BB$ F2 B@JB@c@x@AAJ3E%NJKKe4=-5 =@T{F% F(6@bO@ ': @c B7b ! by8 B@`B@x@A$A9E a,LMb: =jɀ=j ;@3Ȁ@,<JǠJA#c= B@aRB@x@A AR>EaR NNaR? =Rq=R"@@Ƀ@ c cP_Q0U+fJBAb5b½B B@jB* @x@A$AjCE@<jOOajD =j?=j%-(1E@&@ $( @(@BFB> B$BG B@JBc@x@AAJHE PPeI=H |$XJ ={% "K@@ / @ K!Lbcb b륱M B@aB@x@A$ANENbQRbO =!!rs`= P@q@,QJ@J R B@aRB@x@A ARSE[*a SSaRT = 2=R"U@c-@ csu Au I$ 4 BVb,b@&W B@B* @x@A$AjXE jTTajY =jf=j%-(#Z@@ [B"B\ B@JB@x@AAJ]EhUUe^=F_ = ÀY{F% "`@@ @6 ]Aabb ! b b B@`By@x@A$AcE\VVbd =ja=j^(fe@_@,fJgJ Fcg B@aRB@x@A ARhEvRWWaRi =R( =R"j@@ T tZ/`C /OBkbbA;jl B@jBy@x@A$AjmEӀ XXajn =j׀="o@ր@~kpBIB J$&5q B@JB@x@AAJrEYYes=]t =h{% u@@ y c Bvbb w B@aB@x@A$AxE KZZby =jO= z@N@,{JMJA#F| B@aRBc@x@A AR}ER[[aR~ =R==R%@ @ `C ,UagBb b j B@jB@x@A$AjE€2 \\aj =jŀ=j%-(+c@Ā@~BXB B@JB@@x@AAJE}]]e=F =@{F% "@~@ b:b ! B@`B,@x@A$AE%9^^b =j==^+c@1<@,J;JA# B@aRB@x@A ARE/__aR =RQ=R"@@ W0% r0 VWDb bA;j B@jBc@x@A$AjE2``aj = = @@ cBv B$ c B@Bc@x@AAJEkJaae=Fc =q{% @l@ GBbUb c B@aB@x@A$AE@'abcb =jr=^%@@,J?JA#F B@aRB@x@A AREMaR ddaR =R=R%@]@ IAA R|Bbɠb@(j B@jB@x@A$AjEYjeeaj =jL]=j%-(+c@\@ BB B@JB@x@AAJE[Jffe=ܤF =C{% "@@ ! @ "Ab O! b B@aBc@x@A$AEЀghb =ja"@t@,JJA#F B@aRB@x@A AREGa iiaR = O=R"@J@ ,P)Q 7vbgb½ B@B @x@A$AjEvjjjaj =j=( @B@$B J$ c B@JB@x@AAJE kke=~фF{ =Ā{% @3@ vbb c B@aB,@x@A$AEzb Zlmb =j:`=j+c@ 9@,Jv8J F B@aRBw@ Bl@x@A ARERA@nnaR =@?=R%@@@#T?0:TQ[` b b j B@B}$X@x@A$AjEb jooaj =j="@@/)n"IB[BB B@JB@x@AAJEhJppe={Fc =n{% @i@ , ib:b B@aB@x@A$AE%$aqrb =jL=^"@@ =%JJA#n B@aRBc@x@A ARE2aR FssaR =Rݢ=R%@2@!!R!\p ~Aq"aODfbbf B@jB@x@A$AjEVjttaj =jAZ=(%p(1@Y@ rBB J B@JB@y@x@AAJE@Juue=F = 8{% "@{@ / Ab ! B@`B/@x@A$AÈvwb =jaj$j%@Y@ mJŋJA# B@aRB|c@x@A AREDa xxaR =RL="@G@ c|ӐZ0XTuaBbLb½ B@jB~@x@A$Aj5  `E[7@ 2 jy<A = 0`== @H@~B B$  B@Bc@x` =AJ @EỀ Jzze=b΄F;  ={% F%@@ ! @ aB b}b! b B@abBy@x@A$A Ehwb{|b =j7`=j @5@,JkJA# B@aRB@x@A AREu@Y }0$F =R="@n@ $G J@l|Bbb f B@jBt@x@A$AjEb~&!j =j=%-(+c@欀@ $( @(@BBDB,B B@JB@x@AAJEeJe=xF =sk{% %@f@, ȎAb#b 륱 B@aB@x@A$A E !a:#"! =j5=j "@߀@,#JJ ($ B@aRB}c@x@A AR%EaR $!R& = {Ο=R"'@+@c $JT +f D FB(bbj) B@jB@x@A$Aj*ESjaj+ =j"W=j%-(%,@V@ -BUB) c. B@JB@x@AAJ/E%aJ} e0=Fc1 ={% "2@a@c $A3b ! c4 B@aBc@x@A$A5Eʀc4q$^6 =jۊ a(f7@>@,8JJA#9 B@aRBx@x@A AR:EA a aR; =RTI="<@D@ $#@ du`$+jL$B=b!bA;j> B@jBB@x@A$Aj?E?@Y jaj@ =j a%-(%A@ ~BBdB(B$ C B@JB B@x@AAJDEƸ JeE=G˄FvcF =@{% "G@@ y5\ @+c AHbbbe! bI B@`B@x@A$AJEMt bcbK =jq4 `=^%L@2@,MJ@JA#cN B@aRB@x@A AROEZ@Y FaRP = + =R"Q@f@ c34u$ bBRbb½S B@B@@x@A$AjTEb jajU =jU=j%-(%V@@~WBB$ X B@JB@٢o@x@AAJYEhbJeZ=F$X[ =@Xh{% "\@c@ ! @( 9A]bb ! b^ B@`B@x@A$A_Eab` =jހ="a@y܀@ m'$,bJ۠J c B@aRB@x@A ARdEaR FaRe =R=R"f@@ CŲ c4Bgblb#h B@jBc@x@A$AjiEPjajj =!! T=(%-(%k@eS@ $( @(@BlBRB J$Bm B@JB@x@AAJnE Jeo=|ycp ={% "q@B @ tArb ! 륱s B@aB@x@A$AtEǀbu =jaj%pj"v@#@,wJJA#x B@aRB@x@A ARyE>a aRz =RFF=R"{@A@ S RC-t B|bb j} B@jB,@x@A$Aj~E$ jaj =j="@@BpB$ B@JB@x@AAJEb Je=,ȄFc ={% F"@趀@ c wBbGb B@aBc@x@A$AE2qacb =jV1`=j @/@, J%JA# B@aRB@x@A ARE?aR =R="@C@!1`@.!cT?0PT~ WBbb  B@jB2W@x@A$AjEƣb f#%9 =jB=%-(+c@@ rBB  B@JB@!KQ@x@AAJEM_Je=ΤFvc =@Ee{% %@`@ [Ab   B@`B @x@A$AEab =jڀ=j$j+c@Jـ@ mJؠJ  B@aRB$X@x@A AREaR FaR =R="@ᔀ@ c sA"|kk=BbMb½ B@jB@x@A$AjEhMaj2 jaj =jP=%-%@S@~BO Jc B@JBc@x@AAJEaJ Je=o| ={% %@, @ ! @ (b b! b B@aB#'Zh 5@A$A @EuĀ&( =ja @@,JtJ@ B@a B@x@A ARE; a aR =R+C=R"@>@ PQ 0+ia:n!b=b  B@jB| c@x@A$AjE jaj =j=j @@ $( @(EBABBc B@JB@x@AAJE!e=ńF ={% @γ@ ) @+c t b,b bc B@aB-"@x@A$AEn"ab = ``<.#`= @,@,J J c B@B@x@A ARE$@Y: aR =R=R%@$@ T+aq0BDfbbf B@jB @x@A$AjE$b jaj =j7=j%-(+c@@~BB) Q B@JB@x@AAJE2\%aJ} Je=F =b{% "@k]@ @4 2Ab ! b B@aBy@x@A$AE&acb =j׀="@?ր@,JՠJ (R B@aRB@x@A AREƎ'aR aR =R= @֑@ @ ,Sq}6FvbBbj B@jBɂ@x@A$AjELJ(jaj =jM=%-(%@0@ BL(J$  B@JB@&@x@AAJE)Je=T|* =@ {% %@ @x Ovbob !  B@`B0 c@x@A$AEZcb =j*a$j.@@,JMJ! B@aRB@x@A AREg8+a aR = @=R"@k;@* $6 #0/S# MDb:bf B@B@x@A$AjE jaj =jv=(%-%@@ nB2B J$ &JBc@x@AAJEu,e=Fwy =m{% "@@ ! @ Abb b B@bB@x@A$AEj-ab =j2+.`=^% @)@, J(JA#j B@aRB/@x@A AR E FaR =R=R"@ @ $Xp! S b aBbub j B@jB@x@A$AjE/b jaj =j=(%-(%@x@$B؟B_  B@JB@x@AAJEY0Je=kF =_{% "@QZ@ GJAb  XF B@aB@x@A$AE1ab =jԀ=j c@$Ӏ@,JҠJA# B@aRB@x@A AR!E2aR@Y FaR" =RQ=R"#@@ c @B$bb j% B@jB !@x@A$Aj&E1G3aj Iaj' =jJ="(@@)n(AJ)B}I Jc* B@JB@x@AAJ+E4aJ e,=9|c- ={% .@@ ! @c .A/bTb! b 06`aB "@x@A$A1E? b2 =j€=j 3@K@,4JJ (Rc5 B@aRB @x@A AR6Ey5b RaR7 =Ra=R%8@|@ $Xl1 "0TaqB9b.bj: B@jB@x@A$Aj;EL56aj jaj< =j8=j%p(+c=@.@~$( @ť>B7 B$Bc? B@JB@x@AAJ@E JeA=ۤFWB ={% "C@@, ADbob cE B@aBL6@x@A$AFEZ7bG =j ="H@j@,IJ֮JA#>J B@aRB@x@A ARKEg8RaRL =Ro="M@j@ prS"arBNbMb$O B@jB@x@A$AjPEg#9jajQ =j&=j%-(%R@O@ rSB% BT B@JB@x@AAJUE eV=FW ={% "X@(@ ! @c AYb߀b b Z B@aB@x@A$A[Eu:bb\ =jZ;`="]@Y@,^JoXJA#_ B@aRB@x@A AR`Eabq = `A?`=^"r@@,sJ J t B@B@x@A ARuE$@Y: aRv =R€=R%w@(@ +\ qSq"#0@,Dfxbb½y B@jB@x@A$AjzEv@b jaj{ = 3z=j%p(+c|@y@ }BxBJ$ ~ B@B@x@AAJE12AJe=F =%8{"@l3@ !%Nc Ab  by B@aB@x@A$AE1 b =je=j%@@,J4J (R B@aRB@x@A ARE?Bb RaR =R簀=FR%@?@ #S#u aovCbb j B@jB y@x@A$AjEdCaj jaj =jIh=%-@g@r$( @"AJBBB B@JB@x@AAJEL DJeT =<&{% "@!@  c#˄Ab  륱 B@aBc@x@A$A0Eۀb jEaj^"@f@,* JҙJ ݡ B@x@A AR @RFa FaR =RZ=R"@U@ %3 +b6`7HBbPb½ B@a$B$X@x@A$AjؠEgGjaj j=j%p(&@U@ B B}B@x@AAJ}E e @o܄F/ =π{% "@+ˀ@ y! @c3Abʀb b á$B@x@A$ASEtHbbEI`=%@C@,cJkJ c B@x@A ARc  aR2JaR"@~ cCS#Kb  |lbb$Bc@x@A$Aj˱E ,aj=j @@ $( @"DBYB gƹB@x@AAJIEsKbw )e @F =y{F% @t@  c lb/b ! c B@a$B@x@A$At$B@@x@A$Aj=ΠE#[  jaj# -=j%-(%$@@~%Bc B4ڡB@x@AAJ7EL\  Je6%=F) kR{% "*@M@, hv/G?A+bFb j! 0RkB@x@A$A- @1]`*b. -jnȀ="/@ƀ@@Sf0J^ uaR3 -@a놀= 4@F@ cT% Gu!YtɂB5bb !BB@x@A$Aj E:_ jaj8 -jY>=j 9@=@ :BB$ ݡB@x@AAJELwee=ѤFy>D{% ?@@ +B@b ^! /$Bc@x@A$AEұ` QbC -qa #D 0@Up@,cEJoJ B$X@x@A AR E(b #A  aRH -0=(oI@+@o%+ctc B@x@A ARE aRLe;ڀ=R%J@Հ@ B t 䐃R +l |BHbb fҡ$BB@x@A$AjEfb jajEx=(%-%C@Ԑ@ CoB4Bµ$ Bc@x@AAJEIgJ @=-=\@] ch kOM% "i@J@ / @ }Ajb+b bck B@o>B@x@A$AjlE@,bm =j?ŀ=j n@À@,oJ J p B@aRB@x@A ARqE#|An  F  aRr = *у="s@3@ <}YE7 *Btb~b@&u B@Byc@x@A$AjvE7B  j  ajw =j2;=%-(%x@:@$yB9Bz B@JB@x@AAJ{E0 J  e|=F} ={% %~@k@ , iAb͡ 'Z B@aB5n@x@A$AE  b =jnj @>m@,JlJA#Fc B@aRB@x@A ARE%E  aR =Ro-="@(@ 9KXo`-%$Z Bb=b@&j B@jB@x@A$AjEK@Y jaj =j=j @(@ cB〃 B@JB@x@AAJEҜH J >=Sk` -¢{F% @ @  c $Bbnb B@aBc@x@A$AjEYXJ0cb =jwjj1@@@Sr°!j @ JDJ J B@aRB@x@A AREfϠ@Y aR =@ր=R(o@ZҀ@ p#-0QD<@{BbѠb  B@B @x@A$AjE튌 jaj =ju=j @ҍ@$B1BJ)  B@JB@x@AAJEtFL Je=Fx =XL{% @G@ c DBbb  B@aB W9@x@A$AENs b =j=%pj%@@,,J_J F B@aRB*@x@A AREyaR =Rŀ=R%@u@,&,Q[`QD>Bb῀b  B@jB@x@A$AjEyQ5 aj =j|=j%-1@{@ cBHB B@JB@x@AAJE4 Je= =:{% "@5@, Ab+bZ B@aBc@x@A$AEb =j6W"@@,cJJ  B@aRB@x@A ARE#gT)  aR =Rn=R"@'j@Q&1 Qz|tI/*-Bbib½ B@jB o,@x@A$AjE"U jaj =j2&=( @%@ $B$B B@JB@x@AAJE0ހ Je=F/ =${% `3b@i߀@, -Bb ! B@aB "c@x@A$AEX b =jY^+c@>X@,JWJ (R B@aRB@x@A ARE !!aR =Re=R%@@ #4tɂrژb%b  B@jB @x@A$AjEK̀j""aj =jπ=( @2@ B΀µ$  B@JB@x@AAJE҇K ##e=WFx =ʍ{% @@ b5\ @, KEbnb b B@aB@x@A$AEYC\$%b =j\j%@@,JWJ B@aRBz  @' @x@A AREf DE &&aR =R€="@n@&3RFt"Q bڼb½ B@ABB@x@A$AjEu` j''aj -j}y=(%-(1@x@$B9BJ B@JB@x@AAJEt1 J((e=Fc =d7{% (@2@ y': @ Abb b B@aB@x@A$AE1 ))b8j=j @@,JfJ (Rc B@aRB$X`x@9 AR @`E R**aR =?=R"@@ cCk0SC!Qk0`̠Ǝbbj B@a$Bc@x@A$Aj Edb j++aj =jg=j @f@~$( @(D BLB B@JB@x@AAJEJ,,e= =%{ @ @ c ,vb*b ! c B@aBc@x@A$AEۀ --b =j߀=j @%ހ@,JݠJA#  B@aRB@x@A ARE..aR =RH=FR%@@ S ?kE%0aDfbb j B@jB@x@A$AjE#R  //aj =jU=j%p+c @ @ !BoT B$ " B@JB@x@AAJ#E  J00e$=FL6% ={F% "&@@ ! @c @aF33aR/ =RG=R"0@FC@ &cC! 0 0aGB1bBb½2 B@jBh@x@A$>3E f44aj4 =jP="5@@ $(n"AJ6B BJ$B7 B@JB@x@AAJ8EKb J55e9=̤F: =7{% ;@@ c 8A<b  = B@aB 9t@x@A$A>Era67b? =j3^"@@e1@,AJ0J B B@aRB@x@? =ARC @`E逈88aRD =R=R%E@@ s r0QDъ0`̠~BFbGb½G B@a$BF@x@A$AjHEfb f99ajI =jꨀ=(%-(+cJ@E@ rKBJL B@JB@x@AAJME`aJ J::eN=nsFO =f{% "P@*b@ ! @c AQbab b R B@aB@x@A$ASEsac;>aj^ =jR=j _@Q@ $( @(AJ`BPBa B@JB* @x@AAJbE J??ec=Qd ={F% e@ @ c Afb.b ! g B@aB@x@A$AhEƀ@Abi =j:ej j@@,kJJA#l B@aRB@x@A /mE"=a BBaRn =RD=FR(oo@3@@ o @o4"䐃()@CVBpb?bq B@jB5n@x@A$AjrEfCCajs =j9=%p(+ct@@$uBB J$ v B@JBc@x@AAJwE0DDex=Fy ={% "z@f@ ': @ژ{b ! b| B@aB$X@x@A$A}EoEFb~ =j/j$j(f@5.@,J-J  B@aRB@x@A ARE GGaR =Ri=R"@@ PGtZ/Ô@7JFDb0b jc B@jB@x@A$AjEKfHHaj =jӥ=j%-%@.@ $( @"@BB B$B B@JB@x@AAJE]JIIe=WpF5n =c{% "@ _@F Abm^b 륱 B@aB@x@A$AEXacJKb =jzـ="@׀@,JGJA# B@aRBc@x@A AREf$J oA@LLaR =@="@j@ 3$U00*vqBb֒bo B@B@x@A$AjEKb jMMaj =jmO=%-(%@N@~B,B B@JB@:W@x@AAJEsJNNe=F* =@c {% "@@ 5n! @c  rAbb ! b B@`B@x@A$AE€cOPb =j$a^%@@ m2 @* JJ  B@aRB@x@A ARE:a QQaR =RA=!@=@ , 1$"QD@ a&Bb B@aRBB@x@A AR?EefrraR@ =Rn=R"A@qi@, '3> "Kc$"rBBbhb4½C B@jBy@x@A$AjDE!jssajE =j`%=( F@$@ rGB B J$ ,H B@JB @x@AAJIEs݀ tteJ={|*K =@c{% L@ހ@, tBMbb! !n@ cN B@`By@x@A$AOEbuvbP =j(YNsj%pj+cQ@W@ mRJVJA#S B@aRB; @x@A ARTEOB wwaRU =R="V@@!!!C4eu*zBWb{b½X B@jB; @x@A$AjYEˀ2 jxxajZ =jπ=%-+c[@{΀@~\B̀B J$ ] B@JB@x@AAJ^EQ Jyye_=Fx` = {% (a@P@ !Abb ! c B@aB5n@x@A$AdEBz{be =j`=$j%f@*@,gJJ h B@aRB@x@A ARiE ||aRj =R`=R"k@@  St-/4%~Ozalb%b m B@jB@x@A$AjnE/ub j}}ajo = x=%-%p@@ qBsw r B@B@x@AAJsE0aJ~~et=7CF}]Lu =6{% "v@1@ ! @ $wbVb bx B@aB@x@A$AyE=bz =jhWj^%{@Ǫ@,|J3JA#} B@aRB} BL6@x@A AR~EJcb FaR =Rj=R"@Zf@ ycRYdp%QYpn!beb@(' B@jB; @x@A$AjEjaj =ja"=j%-(%@!@ $( @"@BBB)B B@JB@x@AAJEXڀ@Y e=ݤFy =D{% "@ۀ@ b/ @6 t b Z b륱 B@aB@x@A$AEޕbb =jV\"@qT@,JSJA#rR B@aRB@x@A ARE ]\ aR =R="@@ csUVSBa[Dfb`bj B@jB @x@A$AjErȀ jaj = ˀ=%-(%@H@~Bʀ(B B@B@@x@AAJEd Je=zFW =@퉀{% "@5@ y! @6 Abb b B@`By@x@A$AE?acb =j=^%@ @,JwJ  B@aRB@x@A AREaR aR =R9=R"@@ yx|?C!Bbb½ B@jB; @x@A$AjErcT jaj =ju=j @t@~$( @"AJB@B B@JB @x@AAJE-e=@| =@3{% @.@ 8b c VhAb7b ! c B@`By@x@A$AE"b =jJa"@@ m!j$,JJA# B@aRB@x@A ARE/`a FaR =Rg=R%@#c@  ttps"\x$aBbbb j B@jB@x@A$AjEjaj =j>=(%p(+c@@ BB J$  B@JB@x@AAJE< e=F =!݀{% "@u؀@m @c ZAb ǝ! b|y B@aB 'Z@x@A$AEÒbb =jR`=j%pj(f@RQ@,JPJ F B@aRB@x@A ARE k~ aR =Ry=R"@ @ \v@$uaTBb=b j B@jB@x@A$AjEWŀ jaj =jȀ="@2@BǀB$ B@JB@x@AAJEހc Je=_`=c =҆{% F"@@ ) @6 Bbzb b B@aB@x@A$AEe B@aRB@x@A AREraRuaR = ="@v@  "u " u Bbⵀb  B@B@x@A$AjEnqv faj =j}r=%-(+c@q@ rB=B c B@JB@)@x@AAJE*r{e== = t0{% %@+@ ! @6 #Ab b bc B@`B@x@A$AEcb = `'dj @@ m' @JJ  B@B$X@x@A ARE]aaR =Rd="@$`@ u JE~%u Ib_b j:  jB@x@A$AjEaj2 aj =j#=%-(%@~@~n$( @.`@BBB Jc B@JBc@x@AAJE! Je=ʤFS, =ڀ{% % @_Հ@ $F @+c f b ! b륱 B@aB@x@A$A Exb =jOx%pj"@?N@,JMJA#j B@aRB@x@A AREdF aR =R^=R"@ @ s<C""t!/a}Dfb.b  B@jBx ! u@x@A$AjE< @Y jaj =jŀ=j%p%@(@ BĀ c B@JB@x@AAJE}e=DF$X ={% "@@ ! @+c \Ab_~b bc B@aB@x@A$A!EJ9ab" =js= #@@@Sr°' @c$J@J R% B@aRB@x@A AR&EW~c FaR' = =!(@c@ cv "P0 nb6ɂ)bϲb* B@B@x@A$Aj+Ek jaj, =jRo=j%-j%-@n@~$( @(@B.BB)B/ B@JB@x@A>0Ee'd} Je1=F2 =Q-{% %3@(@ , ɂ4bb! 륱5 B@aB@x@A$A6Ecb7 =j!a"8@@,c9JJA#: B@aRB@x@A AR;EYa aR< =Ra= =@]@ y (SP"("CaMDf>bm\b½? B@jB@x@A$Aj@E5 jajA =j=%-(%B@c@~CBB cD B@JBb@x@AAJEEр JeF=FL6G =ր{%H@@Ҁ@t AIb J B@aB@x@A$AKEbL = `L^(fM@K@,NJJJ O B@B@x@A ARPEe@Y: aRQ = +D =R"R@@ $F!!(C[CgB0hBSb b jT B@B@x@A$AjUE! jajV =j€=(%-W@@ n!j @`" "AJXBeBY B@JB@٢oB2ay@x@AAJZEze[=)Fc\ =@{ ]@{@c #4^bHb c_ B@`B@x@A$A`E/6aba =j]=^"b@@,cJ)J cd B@aRB/@x@A AReEc B@aRBw* @x@A AREa FaR =R.="@@ 3($)St).SnbDfbbf B@jBy@x@A$AjE jaj =j=%-%@뾀@ rBJB J B@JB@x@AAJEwe=F =y}{% %@x@ ! @( Zhb)b b B@aB@x@A$AE3cb =j2=j @@,JJ c B@aRB|@x@A ARE! FaR =Rñ=R"@!@ Ct .?1 ֐)Dbb B@jB@@x@A$AjEedfaj =j,i=j @h@$BgB J B@JB@@x@AAJE.!aJ e=F} =@'{G e @j"@ c }Bb ! B@`B !@x@A$AE܀b =jaj @H@ m!j @JJ c B@aRB@x@A ARESD aR =Rm[=F!@V@ Sp;/*`aBb7b½c B@jB@x@A$AjEIi jaj =j=j @&@~) @w.`AJB B$Bi B@JBc@x@AAJEʀe=Qd =Ѐ{% @̀@c ^JAbpˀb  B@aB@x@A$AEWb =jFa"@D@, JRJA#j B@aRB@x@A AREd FaR =RaR @h@ c%?)e p`Bb ĩ B@jB* @x@A$AjE븀jaj =jo=j%-(1@̻@~B+B B@JB@),@x@AAJErtbw> e=F =@^z{% .W@u@ y) @6  Abb! b B@`B@x@A$AE/acb =j,="@@,cJJ  B@aRB@x@A ARE aR =R=R"@@(sR`<|Bb~b@(j B@jBy@x@A$AjEbd jaj =jf=j @ne@~BdB c B@JB @x@AAJE( Je=0| =#{"@O@/ zBb  B@aB@x@A$AEـb =jʙc1.@-@,JJA#F B@aRB; @x@A AREPa aR =RSX=R%@S@ e#a\iBbb@$j B@jBc@x@A$AjE.  jaj =j=(%-+c@@ n;f  B@JB c@x@AAJE Je=6ڄFu =@̀{*c@Ȁ@ c AbQb  B@`B@x@A$AE<d^b =jpC`=^% @A@, J>JA#F-ȯj`'F ;` >A8 y  `  @'  5  > @@IE@`;    A =@`=`G "=  @@3c@I~ %~@ `@1 @# K (R`<[+` !C bbb`bB aj B@B j@x@A$AjEе, DE jaj =A6T=H <=!jj!@@ ?@!j @K $ BBB J BiJ B@B@J!K@x@AAJEVqbw J@==؃`=}3#:  B " =@Cw{"@r@@at! @ hv/G? Ab ,! biX$^ B@`B !@x@A$AjE, aj 5!b =j=j^"@h@ m`@> ! @ AR JJ R `JR-! B@aRB@x@A AR"E aRRA@aR# =@=G "$FR"$@@ @pS%"ϥ yAj%b_b@&j& B@B{ @x@A$Aj'Eq_ aj jaj(; ] c=%p%n)@\b@r$( @r"@B*BaBB$B+ B@B@!K@x@AAJ,E aJe-=y-|z. =@ {% "/@5@ $F @ =A0bb b륱1 B@`B !@x@A$A2Eրb3 = `` aj$j"4@@@Sm!j @5JzJ J6 B@B@x@A AR7EMa FaR8 =@=U="9@P@?ㅲaxB:bbf; B@B @x@A$Aj<E jaj= = { =%-%>@ @ r?BSB J@ B@JB@x@AAJAEĀ eB=ׄFC =ʀ{% %D@ŀ@ @.W AEb6b! b F B@aB "@x@A$AGE bcbH =jI@`=j%pj%I@>@,cJJJ K B@aRBzLW?@x@A ARLE.@YE aRM =R=R"N@&@ cŲ&䐃(@BObb@%½P B@jB| @x@A$AjQEb2 jajR =jA=j S@@ TBBJ$ U B@JB C!Kc@x@AAJVE;naJ JeW=FwcX =+t{% F"Y@yo@ ) @+c BZb  b[ B@aB@x@A$A\E)ab] =j=%^@M@ m!j%c_JJA#Fc` B@aRBc@x@A ARaEϠ8@T aRb =R|=!c@أ@ 1P)t/`%@BdbDb fe B@jB@x@A$AjfEV\jajg =j_=jj+ch@9@iB^ Bj B@JB@@x@AAJkEJel=^*|{m =@{% n@@c Aobyb p B@`B@x@A$AqEdӀbr =ja"s@@ mtJZJA#u B@aRBc@x@A ARvEqJa aRw =R*R=R(ox@yM@ %o.!3$4ǏacBybLbĩz B@jB@x@A$Aj{Ejaj| =j =( }@@ ~B<B B@JB@x@AAJE~ e=ԄF = 5wǀ{% @€@ $F @+c aBbb ! b B@aB 'Z@x@A$AE}bb =j;=`=^+c@;@,JJ B@aRB@x@A ARE@Y: aR =R=R%@@ 1 $1 "@Bbb@% B@jBx ,@x@A$AjEb.aj =j"=j @}@ BݱB J$  B@JB@x@AAJE kaJ @==Fc = Àq{"@fl@ !%N( #yBb ! b B@`B "@x@A$AjE&acA%b \ ``=)%@2@,JJ >c B@B@x@A ARE aRaR =Rf=R%@@ !)E"e",/Bb,b½c B@jB @x@A$AjE;Y!jaj = \=(%-1@@ r$( @"@BB[ J$B B@B@?!K  @x@AAJE"Je=C'| =@{% "@@ W c LAb^b 륱 B@`B@x@A$AEHЀb =jn#aj$j"@ώ@ m!j @` J;J , B@aRB{y@x@A AREVG$aaR =RO="@jJ@)7$/pAu$`A aq@BbIb j B@jB@x@A$AjE%aj2   aj =j]=%-%@@~ƹBB J B@JB@@x@AAJEc J  e=F| =@TĀ{% %@@ @ ղAb ! b B@`B@x@A$AEy&b  b =j:'`=%pj%@i8@ mJ7JA# B@aRB@x@A ARE  aR =R=R"@@ #Aq8ӔP8d`aBbtb  B@jByc@x@A$AjE~(bjaj =j = @g@ BƮB $  B@JB@x@AAJEh)aJ e=zF =m{% F"@E-aJ} e=($| ={%p"@@!! @ AbCb B@aB@x@A$AE-̀yb =jd.a%@ċ@,J0JA#c B@aRBc@x@A ARE;D/a aR =RK="@GG@ cC A8ÔPAm`OBbFb½ B@jB~@x@A$AjE jaj =j60` @@ $( @ "AJBBBc B@JB@&@x@AAJEH Je=ɤFy< 4{%p@@ y c OAb什b'Z  B@`B`x@9 A @Ev1bcb =j62`=^"@Z5@,J4J@ B@a B@x@A AR E aR =R{=R% @@ cSӔ@%O8TAsB bHbA;j B@jB@x@A$AjEc3b jaj =j笀=j%-(+c@H@~B B$  B@JB@@x@AAJEd4Je=kwF$X =@j{% "@%f@ ! @c Abeb b, B@`B@x@A$AEp 5ac b =j="@ހ@ m'$yJgJA# B@aRB@x@A ARE~6aR\!!aR =R1=R" @@!Rc(T<9T`zYB!bb j" B@jBc@x@A$Aj#ES7j""aj$ =jV=(%-(%%@U@ $( @(@B&BQB J' B@JB@x@AAJ(E8aJ ##e)=!* ={bxe "+@@ $F%N6 fA,b'bǝ! b륱- B@aB 'Z@x@A$A.Eʀ$%b/ = ``69aj$j"0@@,1JJ 2 B@B@x@A AR3E A:a &&aR4 =RH=R"5@(D@!3 s:T%; a76bCb½7 B@jB @x@A$Aj8E''aj9 =j6;a":@~ ;BB $c< B@JB@x@AAJ=E-  ((e>=Fc? =%{% F"@@i@ ! @Y Ab ! b B B@aBB@x@A$ACEsb f,,ajN =jȩ=%-+cO@&@ r$( @"@BPBJ$BQ B@JB@&@x@AAJREa?aJ J--eS=PtFuT =@g?`{% %U@ c@ $F @+c AVbkbb bcW B@`Bc@x@A$AXEU@a,c./bY =j݀=jj"Z@ۀ@,c[J\J \ B@aRB$X@x@A AR]EcAaR 00aR^ =R="_@k@ %=QT>@/axB`bזb½a B@jB@x@A$AjbEOBj11ajc =jrS=j%-(%d@R@ eB2Bf B@JB@@x@AAJgEp CJ22eh=Fi =@l{F% %j@ @ ! @.W /Akb b ! b l B@`B !@x@A$AmEƀ34bn =j'Daj o@@,pJJ q B@aRB@x@A ARrE>Ea 55aRs =RE=R"t@A@ %t_TtaBub|@bv B@jB @x@A$AjwE j66ajx =j=j y@u@ $( @(AJzBB$Bc{ B@JB@x@AAJ|EF77e}=DŽF~ ={% @K@ c 6Ab ! c B@aB "@x@A$AEpGa89b =j0H`= @+/@,J.J B@aRB@x@A ARE F::aR =R^=R%@@ q +i?ab =j_ڀ="@؀@,J-JA# dR- B@aRB@x@A AREHLaR??aR =R= @P@ yS%@QaA%aBbb j B@jB/@x@A$AjELMaj @@aj =!bSP=%-(%@O@~BB$  B@JB@&@x@AAJEUNJAAe=֤F$X =@Q{% %@ @ , _Ab !  B@`B@x@A$AEÀcBCb =jOa^.@c@ m, @yJρJ  B@aRB@x@A ARE:Pa FDDaR =RB=R"@=@ y B@`B@x@A$A?E4faybcb@ =j=^%A@>@ m' @BJJ C B@aRB@x@A ARDEgaR ddaRE =R`=!F@@ cCt+:(tȏ`;nbGb-b½H B@jB@@x@A$AjIEGghaj jeeajJ = j=%-j1K@5@ LBiJ$ M B@Bc@x@AAJNE"iJffeO=O5|* P =({% (Q@$@ @$F @#!8+c ARbj#b bS B@aBc@x@A$ATEUހ@ghbU =jxjaj^%V@ל@,WJCJA#X B@aRB}UBnb@x@A ARYEbUka FiiaRZ =R]=R"[@rX@ SP/ QmaPB\bWb@$'] B@jB{y@x@A$Aj^Eljjjaj_ =jm=j%-(%`@@ aB)Bb B@JB@x@AAJcEp̀} kked=Fe =\Ҁ{"f@̀@ ! @6 kAgb bZ b h B@aB@x@A$AiEmllbj =j=%k@@,lJbJA#Fcm B@aRB@x@A ARnE}CnRmmaRo =R.K=R"p@F@*c` 1 F?BqbEb jr B@jB@x@A$AjsE fnnajt =joa( u@@ $( @`" "AJvB@Bw B@JB@x@AAJxE Jooey=z = À{{% :"{@@  c YA|b'b$} B@`B@x@A$A~Evpbpqb = `G6q`=^"@4@,JJ  B@B@x@A ARE rraR =R=R"@@ *s0Cŀeb{b j B@jB @x@A$AjErbyssaj =j.=j%p(+c@@wB骀B J B@JB@x@AAJE,dsaJ tte=F =j{"@fe@ eb !&U B@aB@x@A$AEtauub =j_$=j @"@,J+JA# B@aRB@x@A ARE:ۀ(vvaR =R=FR%@>ހ@ yL6d`89aҪDb݀b  B@jB@x@A$AjEub fwwaj =jP= @@ B B  B@JB@x@AAJEGRvJxxe=S y =3X{% :"@S@ vZb   B@aB@x@A$AE wayzb =j΀=j^+c@è@,BJˠJA# B@aRB|JeB@x@A AREۄxaR F{{aR =Rz=R"@ۇ@ :C""#0S#Q(EbGbA;' B@jB@x@A$AjEb@yj||aj =jC=j%-(+c@L@ rBB J)  B@JB@x@AAJE }}e=j| =za% "@% ! @ Abb b B@aBc@x@A$AEo,y~b =jw{aj%@v@,cJruJ F B@aRBF@x@A ARE}.|aaR =R46="@1@ (1P_$Z (BbbA;j B@jB@x@A$AjEfaj =j|=j%-(%@@ $( @"@BB@BB B@JBF@x@AAJE}b e=Fr3(BEO A = v{% "@Ǧ@ y ,Ab&b 륱 B@`Bc@x@A$AEa~ab =j9!`="@@,JJA#4`  B@aRB@x@A ARE؀aR =R߀= @+ۀ@   aRB@x@A AREb+a aR =R2=R%@V.@ 33yBb-b½ B@jB@x@A$AjE怈caj =ju=j%-(+c@@  B1B$  B@JB@c@x@AAJ Eoe =F~ =@g{"@@ !%NK FAb b ! bAf@`BF@x@A$@A @E]aBb =j#`=%@@,JJ@c B@a B@x@A ARE aR =R܀=R%@؀@ ]`1k1WBb׀b j B@jB; @x@A$AjEbaj =j=(%@a@$BƒB J$  B@JB@@x@AAJ ELJe!=^F" =@ R{% :"#@RM@ , vA$b % B@`B !$X @x@A$A&Eacb' =jǀ=jj%(@ƀ@ m, @)JŠJ  `JRL6* B@A*B @x@A AR+E~aR laR, =RR="-@@ ːa"[a.bbc/ B@jB@x@A$Aj0E,:jaj1 =j==%-(%2@@$3Bd< J4 B@JB@x@AAJ5E e6=4|7 ={% %8@@ c 39bNb ^: B@aB@x@A$A;E9bb< =jrq`=%pjK=@o@,>J@J j? B@aRB@x@A AR@EG(a aRA =0=R"B@[+@ +y`0 ;BDCb*b jcD B@jBB@x@A$AjEE〈cajF =!bY= G@@ƹ$( @"AJHBB JI B@JB@x@AAJJETb eK=դFL =D{% F"M@@ y) @ ANb ! b륱O B@aB@x@A$APEZaybQ =j`=j^"R@r@, SJJA#T B@aRB@x@A ARUEрaRV =Rـ=R"W@Ԁ@  zzxy"*K0qXbdb½Y B@jB@@x@A$AjZEob faj[ =j=j%p(!\@^@~]BB$ ^ B@JB@x@AAJ_EHJe`=w[Fa =N{% "b@,J@ ! @( qcbIb ! bd B@aBc@x@A$AeE|acbf =jĀ=%g@À@,chJ JA#i B@aRB@x@A ARjE{aR FaRk =RG="l@~@+#r~=}{zy@ Dmbbĩn B@jB@x@A$AjoE7aj_ajp =@݀:=j%-(%q@@ $( @"@BrBa9 g$Bs B@B@x@AAJtEeu=|cv =!?{% "w@@, `Axb7b !륱y B@aBc@x@A$AzEb ^b{ =jӲ="|@2@,}JJA#~ B@aRB|@x@A AREiaRaR =RPq=R"@l@ +30 ;$bb@%j B@jB@x@A$AjE+%aj aj =j(=j%-(!@ @ nBl' B$  B@JB,@x@AAJEJe=F; ={"@@ 0AbNbȥ! B@aB @x@A$AE9bb =jY\`=(f@Z@,J(JA#F B@aRBB@x@A AREFa FaR =R=Rc@R@ CÔP aBbb@$j B@jBB@x@A$AjE jaj =jYҀ=(%-@р@ nBB J$ B@JBc@x@AAJETe=٤F =@{% "@@ uBb  B@aB@x@A$AEEat b =J=j @H@,JSJR B@aRB@x@A AREaaR RaR =R =R"@i@ S@Kc a!dbbj B@jB@x@A$AjE jaj =jx=j%-(+c@Կ@ rB4B J B@JB@x@AAJEoxe=wF5n =c~{ @y@]L G'Lb b Z B@aB@x@A$AE3a b =j=+c@@,JJ  B@aRB@x@A AREaRaR = =%@@ ccpbt"iuDb{b j B@B@x@A$AjEfaj2 aj =jj=j @ui@ BhB B@JB@x@AAJE"aJe=4|@ ='{F% :"@N#@  , ͇'b ! B@aBy@x@A$AE݀yb =jɝaj^%@*@,JJA#F B@aRB@x@A ARETa@Y FaR =Rf\=FR"@W@ 7 @1@ # (ost%tEc|b-b(c B@jBc@x@A$AjE+aj jaj =j=j @@~Bg B$ , B@JB@x@AAJEˀe=3ބF =р{% @̀@ @lbRb bc B@aB .B@x@A$AE9bb =j֋="@9@,JJ  B@aRB@x@A AREBaR FaR =RWJ=R%@E@  Ӫ)tRawHob b$ B@jBW@x@A$AjEF@Y jaj = +a(%-()w@ @ B~, B@B@x@AAJE͹ Je=٤F; = À{% "@ @ Ix c Abib B@`B,@x@A$AETubt b =jy=j)j+c@\x@,JwJA#/ B@aRBc@x@A ARE0aRFaR =R|8="@3@ % kpepB?Bb j B@jBc@x@A$AjEa aj =j= @M@̀=j $@ˀ@ m!j @`# %J J (& B@aRBc@x@A AR'EaRaR( =Rϋ=F!)@*@ c`$ aɠHo*bb j+ B@jBc@x@A$Aj,E?ajaj- =j$C=j .@B@ /BAB$ 0 B@JBc@x@AAJ1E+ e2=F3 = Àa% 4@e ! @ mRB5b ǝ! b6 B@`B@x@A$A7E,b8?j[="9@@,:J*JA#j; B@aRB@x@A AR@iu@ y U%% l1B?btb @ B@jB  ,@x@A$AjAE-jajB =j71=(%-(6C@0@ $( @4@BDB/B,BE B@JBF@x@AAJFEF逈eG=RH =>{% "I@@  AJb 륱K B@aB "@x@A$ALEͤbcbM =jd`=^"N@Oc@,OJbJ P B@aRBy@x@A ARQEaaRR =R#=R"S@@ 1 I1KfJsBTbVb jU B@jB; @x@A$AjVEa׀ ajW =jڀ=(%-(%X@?@$YBـZ B@JB@$@x@AAJ[E蒿be\=iFB] =@{% "^@(@/ .!_bb c` B@`B@x@A$AaEnNabb =`=j%c@ @ mdJiJ  `5ne B@aRB@x@A ARfE|ŀ ףA FaRg =R4̀="h@Ȁ@ TD@V7#Bibǀb jj B@jB@x@A$AjkEbajl =j=j m@郀@ cnBKB$ co B@JB@٢o@x@AAJpE<Jeq= OF@r =@}B{F s@=@ $F @ Btb)b ! bu B@`B@x@A$AvEcbw =jDaj x@@,yJJA#z B@aRB@x@A AR{EoaFaR| =Rv=FR(o}@-r@ TupD{1aTB~bqb j B@jB; @x@A$AjE*jaj =j8.=%-)w@-@$B,B J$  B@JB@x@AAJE+怈e=F; =#{% "@l@ ': @+c$Abˡ b B@aBc@x@A$AEb b =ji=^+c@ʤ@,J6J > B@aRBy@x@A ARE8]aRaR =Rd= @<`@,a\Bb_b  B@jB@x@A$AjEjaj =jG=j%-(%@@ $( @"@BBB B@JB@x@AAJEFԀe=R =:ڀ{% %@Հ@ GAb ! 륱 B@aBc@x@A$AȄbb =jP`="@cN@,JMJ c B@aRB@x@A AREaaR =R=R"@ @ pE`Ô@a]BbFb j B@jB,@x@A$AjEa€oaj = ŀ=(%p(%@I@ BĀ B$ c B@B@x@AAJE}e=lF| =䃀{% "@&@ Ab~b c B@aB@x@A$AEn9ab =j=j(f@@,JqJA# B@aRB@x@A ARE|aRFaR =R2=R"@@ ,#Ӕ@uEa-Bbb  B@jB @x@A$AjElajaj = {o="@n@ BJB  B@JB@x@AAJE'Je=:| =y-{% @(@$X "Bb)b B@aBB@x 9A$A @Eb =j=j @@,JtJ@ B@a B@x@A AREaR =RC="@@ 3ǐE}2Bbb  B@jB5n@x@A$AjEZjaj =j]=*(+c@ @ Bm\  B@JB@x@AAJEJe=F ={% (@@ 5n! @ EAbDb b B@aB@x@A$AE+рyb =jOaj @@,JJ  B@aRB@x@A ARE8Had ,aR =RO=R"@TK@ C%% BbJb  B@jB|  @x@A$AjEajoaj = K=j @@$BB J B@B@x@AAJEF,e=ˤFv =:ŀ{% @@ , vBb ! B@aB@x 9A$A @Ezbb =j;`=1@c9@,J8J@Fy B@a Bc@x@A ARE `xA =Rv=%@@ S%% BbBb@j B@jB@x@A$AjE`b j"Aj = 鰀= 7@C@~ƹ) @"AJX&B B$B @JB@x@AAJEhJ`==p{Fs =n{% @%j@ ! @c =Fbib b B@aB@x@A$A En$B =j )=^" @j'@, J&JA#F B@aRB@x@A ARE߀ F60% =R=R%@@ c%% v2zbmbA;j B@jB@x@A$AjE{+!j =j=(%-(1@\@B J B@JB@x@AAJEWJ`== % =\{% "@=X@ 8bb !  B@aB@x@A$AEB =j2=j @@,!JJ F" B@aRBc@x@A AR#EΠ AR$ =RՀ="%@ р@&~Z(o!oss($t&bxЀb ' B@jB%J@x@A$Aj(E Aj) =j= *@y@ !j @(E+BڋB,$Bc, B@JB@x@AAJ-EEJ `=.=%/ =K{% 0@ZF@ eA1b  c2 B@aB @x@A$A3Eat ^ B4 =jC=j$j"5@@,6JJ (R7 B@aRBc@x@A AR8E*@Y R AR9 =RÀ=R(o:@.@ o$F! 4t/ @;bbj< B@jB @x@A$Aj=Ewb j Aj> =j9{=j%-+c?@z@~!j @"@B@ByBA B@JB@x@AAJBE83J`=C=@D = À,9{% "E@o4@ L6 3!Fb ! 륱G B@`B@x@A$AHEW5w.WI =ja"J@E@,KJJ (RL B@aRB@x@I =ARM @`Eea F!!RN =Rm`=%pO@h@ c1 I$tFBPbLbjQ B@a$B@@x@A$AjRES!a, jAjS = $=j%pj#T@7@~UB# B$ "$`4cV B@B@x@AAJWE J`=X=[FY ={% %Z@ހ@ B! @ ;"A[bu݀b b \ B@aB W.X* @x@A$A]E`@u$^^ =j="_@p@,`JܚJ a B@aRB@x@A ARbESRARc =R[=R"d@V@ B  eebOb jf B@jB$X@x@A$AjgEnaj@Y fAjh =j=(%-(%i@P@~jB Jk B@JB@x@AAJlE J`=m=Fn =Ѐ{% "o@-̀@ B c #epbˀb q B@aBc@x@A$ArE{b Bs =j&=+ct@@,uJJ >cv B@aRBc@x@A ARwEBRARx =RI=R"y@E@ %%% ,?Dzb~Db j{ B@jB o@x@A$Aj|E2 Aj} = a ~@u@~r) @"AJB B B@B@x@AAJE J`==| ={% @J@ ! @c Ab  bic B@aB@x@A$AEtb/B =j4`=j^"@!3@,J2JA# B@aRB@x@A ARE AR = J="@@ ,% Bbb  B@B,@x@A$AjE*b Aj =j=%p(+c@@ $( @"@BBn  B@JB@x@AAJEbaJ  `=8`2uFt =h{% (@c@ m c aAbMb B@a$B,@x@A$AE8a!"B =j^ހ=j @܀@,J*JA# B@aRB|; @x@A AREEaR ##AR =R=R"@Y@ ctt  Bbŗb½ B@jBc@x@A$AjEPj$$Aj =jLT=j%p(%@S@ BB B@JB@x@AAJES aJ} %%`==ԤF =O{ @ @ ! @c 4Ab ! bc B@aB .X@x@A$AEǀc&'B =ja(f@d@,JЅJ B@aRBc@x@A ARE>a ((AR =RF=%@A@ c+c+[ݟ1a:Bb[b@$ B@jBy@x@A$AjEm@Y j))Aj =j= @N@~$( @"AJB(B$By B@JB@١B$@@x@AAJE**`==uȄFyB =@ເ{% :"@/@ c  Abb ! 륱 B@`Bc@x@A$AE{qac+,B = `1`=^"@0@,J~/J  B@B@x@A ARE F--AR =R7=R"@@ yPt1 aqbb j B@jB@x@A$AjEb j..Aj =j=(%-(+c@@~+BWB$ / B@JB@@x@AAJE_J//`==rFc =@e{% "@`@ ,  qb2b !  B@`By@x@A$AEac0,WA =jLۀ=^%@ـ@ m'%;` WJJ ( B@aRB@x@A ARE*aR F22AR =Rř=R"@&@c-됂T/bb@(# B@jBc@x@A$AjEMj33Aj =j9Q=(%-(%@P@ BOB J$  B@JB@x@AAJE7 9@T 44`==F = {% "@s @c Ab ǝ!  B@aB@x@A$AEĀ55B = `hɀ=%pj%@ǀ@,J6J F B@Bz@x@A AREE66AR =R=R"@U@c-`D~7a fBbb j B@jBF@x@A$AjE;aj2 f7 (o =j@?=%-`3A@>@~BB$  B@JB C@x@AAJER J88`=AN@ZŃB! =G{% "@@ Ab !  B@a$Bc@x` =A @Eٲbc9:A =jr`=^%@Xq@ mcJpJ@ B@a B@x@A AR E)a ;;AR =R1= @,@` @--`<K -#6 E` Akn bWb  B@jBژ@x@ =Aj @`Emj<<Aj =j=%-(+c@Y@ B瀃  B@aBc@x@AAJE==`==uF =즀{% %@*@ y$F @+c qVbb b B@aBc@x@A$AE{\a5n>?B =j`=^%@@,JJA# B@aRBzc@x@A !E @@AR =R=ۀ= !@ր@ c39aq#D"bb j# B@jBF@x@A$Aj$E b jAAg 9% j=jj%&@@~n'BOB) ( B@JB@x@AAJ)EJ aJ} JBB`=*=]F+ =P{%,@K@ c W-b2b! . B@aBL6@x@A$A/E acCDB0 =jFƀ="1@Ā@,2JJA#3 B@aRB@x@A AR4E*} aR EEAR5 =R߄= 6@:@ Ctb"TC""aD7bb½8 B@jB@x@A$Aj9E8 jFFAj: =j5<=%-%;@;@ <B:B = B@JB@,@x@AAJ>E7 G!.@=?=Fx3(  K 9@ @'{% %A@q@ W c [VABb C B@`B@x@A$AjDEb,HI FE jo`=^+cF@Mn@,GJmJ *RH B@aRB@x@A ARIE&2C  JJARJ =Rm.=R"K@)@ S4"t@TUBLb3b½M B@jB@x@A$AjNER jKBy 9O j=(%-(%P@6@ nQB䀃J$ R B@JBc@x@AAJSEٝLL`=T=ZFcU =ѣ{ V@@ ! @4 AWbub bcX B@aB@x@A$AYE`YaMNBZ =j`=^%[@@,\JbJA#] B@aRB@x@A AR^EmР@Y FOOAR_ =R؀=R%`@yӀ@ c8/"7$M`3$pS+QTBabҀbfb B@jB@x@A$AjcEb jPPAjd =jx=(%-e@Ԏ@$fB4BJg B@JB@x@AAJhEzGJQQ`=i=YFj =kM{% "k@H@ `@6 Blbb m B@aBc@x@A$AnERRBo =j= cp@@,qJqJA#(cr B@aRBy-Bx[ @x@A ARsE FSSARt =R@ƀ=R"u@@ sӔP$8 Np Q`xvbb jw B@jB* @x@A$AjxEzb2 jTTAjy = }= z@|@~r `@(D{BKB J| B@B@@x@AAJ}E5aJ JUU`=~=F*  =@;{% @6@ 0xb1b! c B@`B,@x@A$AEL6VWB =jLa^"@@ m' @> JJ  B@aRBy@x@A ARE*ha XXAR =Ro=R%@k@ {x8|}aIDfbjb½ B@jBW@x@A$AjE#aj jYYAj =j@'=j @&@ B%BJ$  B@JBc@x@AAJE7 JZZ`==Fnb = À{% @t@ L6$F @c Jb  b B@`B @x@A$AEb[\B =jZ`=%pj%@EY@ 5!j @JXJA# B@aRB*@x@A AREa ]]AR =R=!R%@@ $F @3a!&~aEb;b½ B@jB @x@A$AjER̀ j^^Aj =jЀ=j%-j6@,@~!j @"@BBπ B B@JB@x@AAJEوb@Y> J__`==ZF =Վ{% "@@\۠ @O5Abyb ^! b륱 B@aB@x@A$AE_D ac`aB =j|!`="@@,cJJJA#c B@aRBc@x@A AREm@Y bbAR =RÀ=%p@y@@-}{zy8U@ϾBb彀b  B@jB4@x@A$AjEv"b jcC 9 jtz=%pj%@y@~B0B c B@JBx@x@AAJEz2#Jdd`==F =j8{% %@3@ ) @ kLAbb  B@aB@x@A$AEefB =j&$a^(f@@,JJA#(> B@aRB@x@A AREe%a FggAR =Rl=R"@ h@ L`C"`C"HzBbvgbj B@jB@x@A$AjE &jhhAj =j%$=(%-(%@#@ nB"B J$ B@JBc@x@AAJE ii`==Fc = À {%p"@Y݀@ ! @( JAb ! b B@`B@x@A$AE'bjkB =jW(`=^%@5V@,JUJA# B@aRB@x@A ARE)a@Y llAR =RU=R"@@Ex~Z"'E"6$` $`$@Bbbb`b B@jB@x@A$AjE7ʀ jmmAj =j̀=(%-(%@"@$B̀J$ Jc B@JB@x@AAJE*nn`==?F ={% "@@  c cAbZb !  B@aB@x@A$AEDA+acopB =jv,`=^%@~,JCJA#c B@aRB@x@A ARER FqqAR =R=R"@^@ ypKˀ;S"CB(Bbʺb j B@jBx@x@A$AjEs-b jrrAj =j]w=j%-(%@v@n' @"@BBBB$B B@JB@x@AAJE_/.Jss`==F = ÀC5{"@0@ y!%N vb  b륱B  `B 9; @x@A$AEctuB =j/9 mj)K@q@,JݨJ > B@aRB@x@A AREa0a FvvAR =Ri=FR%@d@ "L" $zDf bgb½ B@jB @x@A$Aj Ez1jwwAj =j =(%-! @Z@ r$( @K BB J B@JB@x@AAJE xx`==F =ހ{% "@;ڀ@ W`@6 yAb ! 륱 B@aB; @x@A$AE2bcy:B =jT3`=j^"@S@,J~RJA# B@aRBB@x@A ARE 4a {;AR =R:=R"@@ c"pQ"0aknb b@$j B@jBy o,@x@A$Aj Eǀ2 j||Aj! =jʀ=j%p(%n"@@~#Bdɀ J$ `$ B@JB !K @x@AAJ%E5}}`=&=#F' =@{% "(@݃@ c qV)b>b * B@`B@x@A$A+E)>6a ,&, =jW=j -@@ m' @.J$JA#F/ B@aRBc@x@A AR0E77aR FAR1 =R伀=F!2@7@ @oK .S"q0aD3bbĩ4 B@jBW@x@A$Aj5Ep8aj jAj6 =j=t=j%-j%7@s@~8BrB9 B@JB@@x@AAJ:ED,9J`=;=ɤF< =@<2{% %=@-@ L6$F @ 4+A>b ! b ? B@`B@x@A$A@EBA =:a"B@^@, CJʥJ  dR-D B@aRB@x@A AREE^;a F BF =R~f=%pG@a@.L aBHbDb½I B@jBF@x@A$AjJE_<jAjK =j=j L@7@ MB B$ cN B@JB@x@AAJOEՀ> !@=P=gF@Q =ۀ{% R@׀@ y! @ LBSbրb! bcT B@aB W'Z@x@A$AjUEl=bBV =jQ>`="W@O@ '$cXJkJ Y B@aRB{Je@x@A ARZEz?a AR[ =R =R(o\@~ @.#Yeda]b b½^ B@jB} @x@A$Aj_E?ajF jAj` =ǀ=j%-(+ca@ƀ@~$( @4@BbB=B$Bc B@JB@!K@x@AAJdE@aJ J`=e=Ff =@w{"g@€@ W$F%N+c  hb#b! b륱i B@`B@x@A$AjE;AaBk =j==%p"l@@,mJ JA#cn B@aRB@x@A ARoEBaR ARp =Rʹ=R%q@(@ 3`6 x4|{zyarbb½s B@jB~@x@A$AjtEmCjAju =j*q=(%-%v@p@$wBoB J$ x B@JB@@x@AAJyE))DJ`=z=F{ =@/{% "|@d*@ y': @K hv/G?}b ! bc~ B@`Bc@x@A$AEB =jEa^%@B@ m'%cJJ  B@aRBQ@x@A ARE[Fa AR =Rlc=R"@^@ C+b u6@Db1b½ B@jB@x@A$AjEDGjAj =j=(%-(%@)@$B J$  B@JB@x@AAJE `==LFc =؀{% "@Ԁ@ * $F @+c $AbgӀb ! b B@aB@x@A$AEQHbcB =jNI`=j%pj%@L@,JPJ  B@aRB@x@A ARE_Ja AR =R ="@o@,.SӔ@dup^ abbb@$j B@jBvc@x@A$AjE jAj =jrĀ=j%- @3C@À@ ) @"@BB-B J$B B@JB@x@AAJEl|K`==F}@ =ATT{F% %@}@, hbb 륱 B@`B,@x@A$AE7LacB =j=j @v@,JJA#Fc B@aRB@x@A AREMaR FAR =R=R"@@ c% `$Y` ZaBDfbdb@$j B@jB/@x@A$AjEjNajt= =jn=j%-(+c@em@$BlB J B@JB@x@AAJE&OaJ `==8| =+{% "@J'@ c BAb W! B@aB @x@A$AEcB =jPa @@,J{J F B@aRB/@x@A AREXQa AR =RM`=R"@[@ s0D0Ayp97ôBbb@(j B@jB@x@A$AjE)Raj2 jAj =j=j @@  `@ūcBu B$ Bc B@JB@x@AAJE J`==0Fɂ =Հ{F% @Ѐ@ ! @+c WBbKb b B@aB@x@A$AE6SbB =jVKT`=j^"@I@,J%JA#F B@aRB@x@A AREDUaFAR =R =FR%@H@   a[Bbbo B@jB@x@A$AjEʽ fAj =jJ=j%-(+c@@~$( @"@BB B B@JB@y@x@AAJEQyVb J`==ҤF$X =@I{% "@z@  c EA.] ! 륱 B@`B @x@A$AE4WaB =j="@_@, JJA# B@aRB@x@A AREXaRAR =R=R"@鮀@ c\w 'U2$anbUb@$j B@jBc@x@A$AjElgYjAj =jj=(%p(%@J@ rBi J$ y B@JBc@x@AAJE"ZaJ `==t5|y =({% "@/$@, @c  b#b B@aB@x@A$AEyހcB =j[a^(f@@ '*Q,CB BlJ F#@aRB5n@x@A AREU\a AR =/]=R"@X@ @zT˵zuya@bWb@$ B@jB@x@A$AjE]ajF jAj =j=(%-( < @@~ BNB B@JB@x@AAJ È J`= =߄Fc =GxҀ{%p"@̀@ $F @ jAb0b! bZ B@aB W- B@x@A$AE^bB =jDH_`=^%@F@,JJ F B@aRBy B@x@A ARE( DE AR =R`$R"@9@ $F 3 1  S YsBbb c B@jB B@x@A$AjEjAj =j+=( @@ !j @"AJB缀BBc B@JB@x@AAJ!E6vabw `="=F# =2|{% $@sw@ ! @ A%b  b륱& B@aB@x@A$A'E1ba ZDB( =j=j$j")@C@,*JJ R+ B@aRB@x@A AR,EʨcaRRA@!sA- =@{=%.@ګ@ c""zu$t5@/bFb j0 B@B@x@A$Aj1EQddjAj2 =jg=%- <3 @(@$4Bf J) 5 B@JB@&@x@AAJ6EeaJ @=7=Y2|8 =@%{% "9@!@ B c :bt be! c; B@ B !c@x@A$Aj<E^ۀ$=B= =jfaj >@陀@ m!j @?JUJ R@ B@aRB@x@A ARAElRgaRA@ARB =@Z=R"C@tU@ y}~" DDbTbjE B@B c@x@A$AjFE hjAjG = o=%-( <H @@ ) @(@BIB.B J$BJ B@B@@x@AAJKEyɀ `=L=FwM =@qπ{% "N@ʀ@Q _AObb! 륱P B@`By@x@A$AQEibcBR =j)Ej`=%pj"S@C@ mTJBJA#U B@aRB@x@A ARVE ARW =RkaRR"X@~  (" S"/aBYb}b jZ B@jB@x@A$Aj[E, jAj\ =j$=j%- <] @@~^BB$ c_ B@JB@x@AAJ`Eslbw J`=a=F; b = y{% "c@Qt@ $F @6 Nldb ! bce B@aBc@x@A$AfE.maBg =j="h@4@,iJJA# @Rj B@aRBc@x@A ARkEnaR ARl = h= m@è@ cL"x4| Dnb/b½`L6o B@B@@x@A$AjpE6aojAjq =jd=j%-( <r @@ sBzc B$ Jt B@JB@x@AAJuEpJ`=v=>/|w =G"{F% %x@@ $X %AybXb z B@aB@x@A$A{EC؀B| =jmqaj^+c}@Ζ@,~J:JA# B@aRB@x@A AREQOra+ AR =RW=R"@aR@/}"6$~p!Lu'bQb j B@jBc@x@A$AjE saj jAj =jW="@ @ƹBBB B@JB@2a@x@AAJE^ƀ J`==ߤF =@Ǹ{% @ǀ@ c Eb  B@`B@x@A$AEtbcB =jBu`=^%@t@@@Sm!j%J?JA#Jc B@aRB@x@A AREAR = v$R%@~ vw"x|}V Bbjb½ B@Bc@x@A$AjEy, fAj =j=(*( < @a@ rBBJ ` B@JBc@x@AAJEpwbw J`==Fc = su{% "@:q@ 5n! @( VYAb bc B@aB@x@A$AE+xacB =j="@ @ JuJA# B@aRB5n@x@A AREyaR AR =RF=R"@@ 7"'#y~1'Bb bf B@jB@x@A$AjE^zajfAj = a=( @@~!j @(DBg` B B@B 5@AAJ @E{aJ `==",|/ ={% @@/ Bb=b! 륱 B@abB@x@A$AE(ՀB =jR|aj$j"@@,JJA# B@aRB@x@A ARE5L}a@Y AR =RS="@FO@; /39ِ.7hDfbNb f B@jB@@x@A$AjE~aj jAj =jD =%p < @ @ BB c B@JB@x@AAJECÀ J`==ĤF = 3ɀ{% (@~Ā@ @ GAb  bc B@aB@x@A$AE~bB =j>`=j @X=@,J=R"@=:@ sґҐ",\Bb9b½ B@B@x@A$AjE jAj =jD=(%-(%@@ rBB J$  B@JB@x@AAJEC`==K|@ = 3{% " @@ , A!b ! " B@aB@x@A$A#EiaB$ =)`=j+c%@T(@,c&J'J ' B@aRB@x@A AR(E FAR) = ="*@@ /t/Ƀ B+bGb j, B@Bc@x@A$Aj-E^b2 jAj. =j=%-(%/@I@~0B J$ 1 B@JB@x@AAJ2EWaJ J`=3=ejF4 =]{% %5@"Y@/ !A6bXb! XF7 B@aB@x@A$A8EkB9 = `= :@g@,;JJ < B@By@x@A AR=E AR> =Rր=R"?@Ҁ@ te]LB@brрb jA B@jB@x@A$AjBExb@YW j C =j=(%-(%D@a@ EBB $ F B@JB@x@AAJGEEJ`=H=w!^ `AI = )K{H =`=e "J@?G@ !%N, DxAKbFb ! bL B@aB@x@A$AMEa* AjN =j= O@@,PJJA#a!Q B@aRB@x@A ARRExaR F pARS =RN=R"T@{@  @ 9~a;UbbjV B@jB5n@x@A$AjWE4j AjX =j7=j%-`3CY@6@  `@.`@BZB^B$ B[ B@JBc@x@AAJ\E @=]=&|y* ^ ={% "_@@F `b=b ! a B@aB@x@A$AjbE(byBc =jOk`="d@i@,eJJA#f B@aRBc@x@A ARgE5"a aRh =R)=%pi@)%@ }"6a=Dfjb$bk B@jB@x@A$AjlE jajm =j<=j%-j+cn@@ oBB$ cp B@JB@x@AAJqEBer=ĤFys =7{% %t@z@ ! @ Aub ! bv B@aB W?n@x@A$AwETa bx =j`="y@<@,zJJ { B@aRBx @x@A AR|Eˀ F  aR} =RӀ=R"~@΀@%t  a BbKb jc B@jB + @'@x@A$AjE]b j  aj =j劀=(%-(%@C@$( @ūcBB$B B@A"B@x@AAJEBJ  @==eUF =H{% "@ D@ W`@+c {AbCb 륱 B@aB "@x@A$AjEkc Kc = ``a^"@@,JnJ > B@B@x@A ARExua FaR =R0}=R"@x@ Q "$h\{"$aBbwbf B@jBW@x@A$AjE0jaj =j4=(%-(%@3@ rBCB J B@JB@\$@x@AAJE e=F2(EO A =@z{% "@@ ! @?m 8Ab"b! b B@`B@x@A$AE bcb =j   @==F~, =W{% "@@ ) @#!8 AbbZ b B@aB@x@A$AjEbc!"b =je`=%@|c@,cJbJ Fc B@aRB@x@A AREa ##aR =R#="@@ $F @%)Lp+MTp 8Bb{b½`bF B@jB y@x@A$AjE׀F j$$N! =jۀ= @\ڀ@ !j @"AJBـBJ B@JB@!K@x@AAJE %%e=F@ =@{% @I@, JAb  c B@`B@x@A$AENa&'b =j`=$j"@* @,J JA# B@aRB@x@A ARE F((aR =RM̀=R%@Ȁ@0#KYM0TerBbb j B@jB; @x@A$AjE'b j))ajEj=j%p+c@ @~Bk B B@JBBD`x@9 AJ @E<J**e=/OF =@{B{% "@=@  c `AbJb B@`B@x@A$A E5+,b =jVa" @@, J#JA# B@aRB@x@A AREBoa F--aR =Rv=R"@Rr@,03PMN1~aakɂbqbĩ B@jB$X@x@A$AjE*j..aj =jY.=( @-@ rBB J B@JB@x@AAJEO //e=ѤF =D{% @@, LEb !  B@aB,@x@A$AE֡bc01b =ja`=^+c!@U`@,"J_J ># B@aRB@x@A AR$Ea 22O% =y =R%&@@ C%Ǵt"Q@ u+B'bHb@&j( B@jB@x@A$Aj)EjԠ@Y j33aj* =j׀=j +@N@ `@"AJ,Bր$ By- B@JB@x@AAJ.E44e/=rFc0 =ٕ{"1@+@nE c 2bb 3 B@aB - @x@A$A4ExKac56b5 =j `=j"6@ @,7Js J >8 B@aRB@x@A AR9E€ F77aR: =R3ʀ=FR%;@ŀ@ SӔP Qy ^Df<bb@$ = B@jBy @x@A$Aj>E ~b j88G? =j=%-1@@쀀@ rABLBkB B@JB@x@AAJCE9J99eD=LFE ={?{% "F@:@ y! @ ;AGb/b bZ,H B@aB@x@A$AIEc:;bJ =j+aj^%K@@,LJJA#FM B@aRB$X@x@A ARNE'la F<>eY=FFZ =%{% [@o@F  A\b ! 륱] B@aB@x@A$A^Eb?@b_ =j^`= `@2]@,aJ\J b B@aRB@x@A ARcEa AAaRd =Rr=R(oe@@ $F!osu$4ǏBfb1b½g B@jB@x@A$AjhEOр jBBaji =jԀ=j%p(+cj@7@~!j @ťkBӀ B$Bl B@JB@x@AAJmE֌b JCCen=[Fo =Β{% "p@@/ f-Aqbrb! 륱r B@ B,@x@A$AsE]HaDEbt =j`=$j"u@@ 2 @ @ ! !% ARvJ\J w B@aRB@x@A ARxEj FFaRy =Rǀ=!R"z@r€@ c"+YpǬDx,{bb½| B@jB@x@A$Aj}Ezb jGGaj~ =je~=j%-j%@}@~B!B$ B B@JB@x@AAJEx6aJ> JHHe=F =l<{% "@7@ $F @+c ,bb! b B@aB 4 @x@A$AEcIJT =j!a"@@,cJJ B@aRBw&BW@x@A ARE ia KKAR =Rp= @l@ Eǭ cqbkb@& B@jBQ@x@A$AjE$ajF jLLaj =j(=%-(%@{'@ B&B  B@JB; @x@AAJE JMM@==a* ={% %@T@ y! @+c qb  b B@aBB@x@A$AjEajNOb =j[`=^+c@7Z@,JYJ F B@aRB{@x@A AREa PPaR =RL=R"@@ E"\T &abb f B@jBy@x@A$AjE4Π@Y jQQaj =jр=(%-(%@@ $( @"@BB|ЀB B@JB@١B)* @x@AAJERRe=H@ m!j @JGJA# B@aRBB@x@A AREaR RTTaR =R="@@ +c 13OafDfbDb# B@jB@x@A$AjEO jUUL =jϿ=%-(%@*@<BB/l c B@JB@x@AAJEwVVe=ޤFF =}{% %@y@ y! @c Abrxb bc B@aB@x@A$AE\3acWXb =j=j%pj(f@@,JOJ  B@aRB@x@A AREjaR FYYDF =R#="@z@ ЂЃQyT1aBb欀bf B@jB@x@A$AjEejZZaj =jyi=%-%@h@$B9B J B@JB@c@x@AAJEw!J[[e=F@ =@h'{% %@"@ ) @6 Abb b B@`B,@x@A$AE܀\]b =j#a @@ m!j @cJJA#c B@aRB@x@A ARE Ta ^^aR =R[=R"@ W@ $ "<|6+mazBbxVb j B@jB@x@A$AjEaj* __aj =j=j @y@  `@(AJBB B@JBc@x@AAJE ``@==݄F* = р{% @Ẁ@ c kAb ȥ! c B@aBc@x@A$AjEb,abb =jF`=%pj"@7E@,,JDJ  B@aRB@x@A ARE ccaR =RVaRR%@@ $XmaBb!b½ B@jB} !B@x@A$AjE4 jddL =j=j @@~Bd B$  B@JB@x@AAJF  Etbw> Jeee= B@JBc@x@AAJ?Eqbw Jtte@=!FA =w{% "B@r@ ': @( WACb@,rJdJ s B@aRBnb@x@A ARtEw@Y: aRu =R=R"v@{@ cCA1P1 @wbb@%x B@jB@x@A$AjyEb jajz =j=j {@굀@~c|BJB} B@JB@x@AAJ~EnJ@== F =qt{% @o@ c Eb b ! B@aB@x@A$AjE *ayb =.=j @@,JJ Fc B@aRBc@x@A AREaRy FaR =RǨ=FR%@)@ SŰE%a&UBbb@$ B@jB@x@A$AjE\aj jaj =j/`=j @_@~B^B$ y B@JB@x@AAJE&Je=F$X ={% @a@ ': @+c Bb ! by B@aB@x@A$AEӀcNW =ja"@@@#P2$yJJ IF B@aRB@x@A AREJa F4y2h =@iR=!@M@ c"Q p KJ}Bb6b@) B@Bb@x@A$AjEAj$!j =j =j%-j1@&@ $( @.`@BB B$B B@JB ,@x@AAJE e=IԄF =@ǀ{% (@À@ $F @6 (Abd€b b륱 B@`B@x@A$AEN}bcb =jz=`="@;@,cJEJA#F B@aRB@x@A ARE\@Y aR =R=R"@d@ , s@%K`R11P@Bbbo B@jBc@x@A$AjEb jaj =js=(%-(%@в@~B/B J B@JB@x@AAJEik:@T Je=Fc =Vq{% "@l@ , |Abb! B@aBc@x@A$AE&aH =j=j(f@w@,JJ (R B@aRBx@x@A AREaR aR =R=R"@@ o$F!o.! $1uP aSBbnb  B@jB@x@A$AjEYjaj =j ]="@j\@ !j%"AJB[B $Bc B@JB@x@AAJE Je='| ={% @G@$X Ab ^! c B@aBx@x@A$AEЀb =jaj$j"@ @,JJA# B@aRB@x@A AREGa aR = [O="@J@ puT}uU} tT/,5Bb'b j B@B@x@A$AjE&jaj =j=%-+c@@$Bj J$  B@JB@@x@AAJE e=.фFu$X =@Ā{% (@鿀@ y': @ AbIb B@`Bc@x@A$AE3zbcb =jS: `=j @8@,J"JA#c B@aRB@x@A AREA(aR =R="@U@ tU%C$)YBbb j B@jB*@x@A$AjEǬ b fN! =jT=j%-(!@@ $( @n(@BBB B@JB@@x@AAJENh Je=ϤFz =@Bn{F%p%@i@  vB bZB@x@A$AE# acb ==j @`@,JJ  B@9B@x@A ARE aR FaR = =R" @杀@ !$F!o1AӔP&FLDf bRb½ B@B/@x@A$Aj EiVjaj =jY=j%p(%@?@ !j @ BX B B@JB@x@AAJEJe=q$| ={ @)@Q Abb  B@aB@x@A$AEẁH =ja %d(f +@@,JqJ  B@aRB@x@A AREDa aR =8L=%pR%@G@ $@6|{a-BbFb B@jB o/@x@A$Aj!E jaj" =j=j%pj%#@@ $BWB% B@JB@x@AAJ&E e'=΄F( ={ $F% ") +@μ@Q Z*b-b ! + B@aB "B@x@A$A,Ewbb- =)97`=  j^%. +@5@,/JJ 0 B@aRB@x@A AR1E&+ aR2 == "R"3 +@6@  P $Bs%u,]D4bb jc5 B@jB@x@A$Aj6Eb jaj7 =),="8@@~ƹ9BB$&50: B@JB@\$* @x@AAJ;E3eJe<=F@= =@#k{% >@pf@ , ZB?b !  @ B@`B$X@x@A$AAE acbB =j= , ^%C +@A߀@ m2$ DJހJ(RE B@aRB@x@A ARFEǗaR F!G =x=  R%H +@Ӛ@ CudǴt"* Ib?bjJ B@jB/@x@A$AjKENSjajL =)V=(%-(+cM@1@ rNBU J$ O B@JBc@x@AAJPEJeQ=V!|cR ={% "S@@ $F @ +c * Tbqb bU B@aB@x@A$AVE[ʀcbW =ja ^%X +@@,YJ^J Z B@aRBz@x@A AR[EiAa aR\ =I= ] +@yD@ c6%At"Q@$`#C^bCb_ B@jB@x@A$Aj`E2 jaja =)xa(%-(%b@ ~cB4B Jd B@JB@x@AAJeEv  Jef=F{yg =k{ %"% %h +@@ y! @6 Ixibb! b j B@aB@x@A$AkEsbbl =)*4`=+"j%m@2@,nJ1J o B@aRB@x@A ARpE aRq =R= "R"r +@@2]F  Qy` yRDsbb(½ct B@jBx @x@A$AjuE b jajv =)= w@q@ $( @"AJxBѨBJ$Bcy B@JB@x@AAJzEb!aJe{=tF| =h{% F"}@Vc@  c A~b c B@aB@x@A$AE"ab =j݀=j^"@"܀@![ !j @ nbJۀJ R B@aRB@x@A ARE#aR FaR =@N= a +@@2p`yu yBbbf B@B; @x@A$AjE3P$jaj =)S=%-j+c@@$BR J B@JB@x@AAJE %aJ e=o|Gc ={% %@ @  @c 'AbVb! b B@aB@x@A$AE@ǀcb =jj&aj%pj%@˅@,cJ7JA# B@aRB@x@A AREN>'a aR =RE=R"@ZA@2#$"TpGӔyQqb@b½ B@jB@x@A$AjE jaj =je= 1' +@@ cB!B  B@JB ,y@x@AAJE[(e=ܤF$X =@K{% F"@@ ) @+c qb  `b B@`B@x@A$AEp)ac[& =j1*`= ," +@i/@,J.J  B@aRB@x@A ARE FaR ==R"@@ c 3 Q %ctHobcb j B@jB; @x@A$AjEv+b jaj =j=j%-(+c@V@~B B$  B@JBc@x@AAJE^,Je=~qF =d{%p"@7`@ y! @( [Ab_b b B@aB@x@A$AE-ab =jڀ= % +@ـ@,J~ؠJ B@aRBc@x@A ARE.aR FaR =7=  R" +@@ C$yD@ yS$p_KdEJpWI@K[Bb=b@& B@B@x@A$AjE jaj =@݀A="@@ƹBB J$c B@B@x@AAJE@3b Je=Fc =0{% @{@ ! @c 1Bb ! b B@aBc@x@A$AEm4acQ =j-5`=  ^% +@R,@,J+J > B@aRB@x@A ARE䀈aR == R% +@@ ccSXKCXFLpD y*bXb½ B@jB@x@A$AjE[6b faj =)㣀=(%-(+c@@@ r$( @"@BBJ$B B@JB ?@x@AAJE[7Je=cnFx3ɂ =@a{% "@]@ y c 9b~\b c B@`B@x@A$AEh8a Z! 8b =j׀=jtB +@Հ@ m!j @,J_J  B@aRB; @x@A AREv9aRR@aR =@1=!g X"@@ sK+\>Idpq yhobbj B@B@x@A$AjEI:aj2 jaj =jyM=  j%-(% +@L@~ B9B J B@JB@1@x@AAJ E;aJ J@==} =@t {% "@@, nWbb!  B@`B@x@A$AjE b =j=b J@=#=F$ =G{ !% % +@]@ $F @ B&b  by' B@aB@x@A$Aj(Ej?aQ) =)*@`=  j^+c* +@?)@, +J(JrR, B@aRB}LW% @x@A AR-E aR. =d=  R(o/ +@@ PMEyB0b1b '1 B@jB; @x@A$Aj2E@Ab jaj3 =)Ƞ= !j%-(+c4 +@%@~r5B B6 B@JB@x@AAJ7EXBaJ} Je8=Hk?`=9 =G^{  % ": +@Z@ B! @K A;bcYb! b < B@aB@x@A$A=EMCajcb> =)iԀ=%?@Ҁ@,c@J8J nA B@aRBF@ 5@A ARB @`E[DaR GC =R= $F"D +@S@ c+ZQ@ %MQ ycKBEbb½F B@a$Bc@x@A$AjGEFEjajH =)bJ= I@I@ $( @"AJJB&BBcK B@JB@@x@AAJLEhFJeM=F* N =@X{ "% O +@@ y c ǹAPbb Q B@`B@x@A$ARE`bS =)~Ga^"T@v|@,UJ{JA# `>-_V B@aRB@x@A ARWE4Ha aRX =R<=R%Y@7@ B&!%|T5nTZbhb[ B@jB/@x@A$Aj\E jaj] =j= 1$(%-(+c^ +@a@~ `@r"@B_BB B` B@JB@@x@AAJaE Ieb=Fc =@{  d +@F@x Zeb !&Uf B@`B !5n@x@A$AgEgJayJh =)'K`=^"i@#&@ mefyjJ%J k B@aRB/@x@A ARlEހ!ARm =R>=R%n@@  "ML aBDfobb@$#a p B@jBw $X@x@A$AjqE%Lbjajr =j= $( s +@ @ tBm J$ Ju B@JB@x@AAJvEUMJ@=w=-hFx =G[{  % :"y + -V@ c BzbKb { B@`B "@x@A$Aj|E2Na,b} =)[р=^%~@π@,J)J F B@aRB@x@A ARE@OaR@Y aR =R쏀=R"@L@ dpKEJpQPHhBbbo B@jB@x@A$AjECPaj@A =jNG=  +@F@$B B J B@JB@x@AAJEM !!@==ҤF5n =G]Qa  %  +@@ Bbb,! B@aB@x@A$AjEԺyB =)zRj @Oy@,JxJ > B@aRB@x@A ARE1Sa aR =R9="@4@ 7)! 0 Op M3!aBb]b½E c B@jB oc@x@A$AjEh jaj =j=  (*(1 +@O@ r!j @(@BBJ$Bc B@JB@x@AAJETe=pF =G㮀{  :% ( +@,@ , ]gAbb c B@aB@x@A$AEudUab =)$V`=  j#j" +@"@,JXJ  B@aRBz,@x@A ARE۠@YcaR =$="@{ހ@ 6+\ P0M@8Bb݀bf B@jB@x@A$AjE Wb2   aj =j= c +@홀@~@BRB J B@JB@o&* @x@AAJERXaJ J  e=eF =@X{% F%@S@ ! @ Bb,be! b B@`B@x@A$AEYa  b =j0΀=j @̀@ mJˠJ  B@aRBc@x@A ARE%ZaR   aR =Rٌ= $FR" +@5@,3pJTJU`\}Bbb½ B@jB@@x@A$AjE@[aj jaj =)7D= 1!j*(+c +@C@~$( @(@BBBB$Bc B@JBc@x@AAJE2 Je=F{3"<| =G"\a =`G!% " +@p W$F @ Ab ! bc B@aB,@x@A$AE,b =)w]aj"@Dv@, JuJ > B@aRBc@x@A ARE.^a aR =Rk6=  +@1@ %efN`NuOnbhb6b B@jB}c@x@A$AjEM jaj =)=  j%-(% +@4@~B쀃 B$  B@JB@x@AAJEԥ_b Je=UF =G{  % % +@ @ y! @( `hbpb! by B@aB - y@x@A$AEZa`acb =)!a`="@@,cJQJ  B@aRB@x@A AREhؠ@Yh' aR =R =R"@pۀ@y3#PE@ud yDbڠb@&½ B@jBF@x@A$AjEbb faj =j{=j%-(%@֖@~$( @ūcB7BB B@JB@$@x@AAJEuOcJe=F =@]U{ c +@P@c aAbb  B@`B@x@A$AE dab =)*ˀ="@ɀ@@Sm, @JȠJA#J B@aRB@x@A ARE eaR FaRIR=R%@@ c3udǵfef VBb}bĩ B@jB oy`x@9 Aj @`E=fjaj =Bi`=A=a1$(%-@t@@ nB?B J ` B@B@!K@x@AAJ E e = | =@{*c @Q@ c $B b ! c B@`B@x@A$AEgb b =jth`=^%@,s@ mJrJA# B@aRB/@x@A ARE+ia !!aR =Ra3=R"@.@ C"F =@{% !@@ $F @6 B"bTb b# B@`TB !@x@A$A$E?^kac$%b% =jel`=j+"j%&@@,'J2J ( B@aRB@x@A AR)EMՀ F&&aR* =R܀=G@)R%+@U؀@,3ST~%HT@zl@,I׀b j- B@jB,IA$Aj. @`EӐmb j''aj/ =jT=j%-C0@@nc1BBB2 B@aB@x@AAJ3EZLnJ((e4=ۤF; 5 =JR{H@ % "6@M@, @7b  8 B@aBc@x@A$A9Eoac)*b: =jȀ=j^K;@pƀ@,<JŀJ >= B@aRB@x@A AR>E~paR F++aR? =R=R"@@@ $F!o"c+gKg cDAbZb½B B@jB@x@A$AjCEu:qj,,ajD =j>=j E@^=@ r!j @"AJFBb =jn~`=^+c@l@,cJZJA#F B@aRBF@x@A AREu%a(??aR =R0-=R"@(@ , %6Kԣab'b j B@jB~ @x@A$AjEF f@@aj =j=j%-(%@@~B@B B@JB J!K@x@AAJEb JAAe=Fc =@n{"@@ c  bb!&Z, B@`B@@x@A$AE XaBCb =j-`=+"%@@,JJA# B@aRBx B\ @x@A ARE DDaR =Rր=R%@'Ҁ@ Jd`=Mp? ]WDbрb j B@jB@@x@A$AjEb jEEaj =j=( @w@$BՌB c B@JBc@x@AAJE$FJFFe=F =L{F"@`G@ b   B@aB 'Z@x@A$AEaGHb = ``=j @=@,JJ n B@B$X@x@A ARExaR FIIaR =Rh="@{@  (MdpR%@Eb4bA; B@jB5n@x@A$AjE?4jJJaj =j7=%-o+c@@$Bw6 J)  B@JB@$c@x@AAJE KKe=G|V =@{% %@@ 'Abbb !  B@`Bc@x@A$AELbcLMb =jpk`=j @i@,J?JA# B@aRB@x@A AREZ"a NNaR =R*=R"@f%@ KMa9Bb$b@$j B@jB@x@A$AjE jOOaj =ja=%-(%@@ B B J$  B@JB @x@AAJEgPPe=F5n =@S{% "@@ ! @ :Abb b B@`B@x@A$AETacQRb =j`= @}@ myJJA# B@aRB@x@ =AR @`Eˀ FSSaR =RӀ=R"@΀@ K R`~[M`̠ BbWb! B@a$B5n@x@A$AjEb jTTaj =j =j%-(%@d@@r$( @4@BBƉB B@JB@x 9AAJ @E CJUUe=U`= =H{% "@BD@ $F @ EAb ! b륱 B@abB@x@A$AEcVWb =ja"@@,JJA# B@aRBc@x@A AREua FXXaR =R=}= @x@ KWK~a' Bbb½ B@jB@x@A$AjJ  `E$1aj@ jYYaj =j4=j%p(%@ @ Bl3 B$ c B@aB@x` =AJ @E JZZe=+F*  ={F% %@@Q A bFb c B@abB@x@A$A E1b[\b =jVh`=j^(f @f@,J$J  B@aRB@x@A ARE?aF]]aR =R&=R"@O"@y4G Kea/Bb!bj B@jB ,@x@A$AjEڀ f^^aj =jAހ="@݀@ BBJ B@JB@!K@x@AAJELb 4E J__e=ͤF]L =@<{% @@  c xBb  B@`Bc@x@A$A EQa`ab! =j`=^%"@b@, #JJ $ B@aRB@x@A AR%EȀ bbaR& =!JЀ=R%'@ˀ@ $F'E!%KW3<}p/S`+ B(bDbf) B@jB@x@A$Aj*Egb jccaj+ =j燀=(*(+c,@B@ r!j @"@B-BJ. B@JBc@x@AAJ/E?aJ Jdde0=RFV@1 =E{% "2@+A@c ,MA3b@b c4 B@aBc@x@A$A5Etcefb6 =jaj$j"7@@ ' @ ! !% AR8JoJ 9 B@aRB5n@x@A AR:Era ggaR; =R3z="<@u@4#+\%Kdpa=btb½> B@jBF@x@A$Aj?E .ajF jhhaj@ =j1=j%p%A@0@~BBQBC B@JB @x@AAJDE JiieE=FF ={F% %G@@ Hb+b! I B@aB@x@A$AJEbjkbK =jCe`=j%pj%L@c@,MJJ N B@aRB@x@A AROE#a llaRP =R#=FR"Q@@ 3|}J ,IDRbb S B@jB@x@A$AjTE׀jmmajU =j:ۀ= V@ڀ@ WBـB X B@JBc@x@AAJYE1nneZ=F[ =%{% F"\@k@ 7B]b  ^ B@aB@x@A$A_ENaopb` =j`=j^%a@. @,bJ J c B@aRB@x@A ARdE qqaRe =Rr̀=R"f@Ȁ@ 4CMp!BgbAb@(ja]Lh B@jBy@x@A$AjiELb jrrajj =j܄=j%-(+ck@8@$lB Jm B@JB@@x@AAJnE<Jsseo=TOFp =@B{% "q@ >@ / @ +c OArbo=b cs B@`B@x@A$AtEYctubu =jwa%v@ض@,wJDJA#x B@aRBc@x@A ARyEgoa FvvaRz =R w="{@{r@ cS~"efa9|bqbf} B@jB; @x@A$Aj~E*jwwaj = z.=%-(%@-@ cB9B$ B@B@@x@AAJEt xxe=F$X =@d{%p"@@ WAbb ! XF , B@`B* @x@A$AEbcy$A =jb`=^K@v`@ m' @J_JA# B@aRB B@x@A AREa {+!R =R =!@@ ccabQ HBb|b@&j B@jB* @x@A$AjE j||aj =j؀=j%-j%@{׀@ BB$  B@JBc@x@AAJE}}e=F ={% %@P@ m$F @c iAb ȥ! b B@aBc@x@A$AEKa Z|D~b =j `="@3 @,J J R B@aRB%IB@x@A ARE RA@aR =@Pʀ=R"@ŀ@ sdud:ȀǴ pBbb@&j B@B@x@A$AjE1~b jaj =j=(%-(%@@~rBq J$  B@JB@x@AAJE9aJ Je=b =jyaj+c@ٳ@,JEJA#F B@aRB@x@A ARELla aR =Rs=R"@Po@ 2 "fe"NaBbnb½ B@jB@x@A$AjE'aj jaj =jb+="@*@ $(n"AJBBJ$B B@JB@x@AAJEY Je=ڤFc =E{% @@ c EAb  B@aB @x@A$AEbb =j_`=j @o]@, J\JA# B@aRB@x@A AREaaR =R="@@ $F!o|%O>faBbqb½ B@jBy@x@A$AjEt faj =jԀ=%-(+c@Y@ r!j @ťBӀ J B@JB@@x@AAJEb Je=|Fc =@{% (@6@ @*!bb! bc B@`B@x@ =A @EHacb =j`=j$j"@@ m' @JdJ c B@a B4B@x@A ARE D aR =RFǀ="@€@ !Re"{zǑ +Bb b½ B@jBL6@x@A$AjE{bF jaj =j~=j%p#@}@~BZB$  B@JB@@x@AAJE6aJ Je=IF5n =@<{F% %@7@ TAb8b! X\y B@`B ! O*@x@A$AE#b =jBaj$j%@@,JJ  B@aRB@x@A ARE0ia aR =Rp=R"@5l@ c A@ a.Bbkb  B@jBc@x@A$AjE$jaj =j?(=j%-%@'@B&B$ CG@JB@x@AAJ#@E> e=F =2{% "@v@ c 6Ab !  B@abB@x@A$AEśbb =j[`= @[Z@, JYJ j B@aRB@x@A AR Ea aR =Ry=R"@@ $X A D7a*BbBb½ B@jB $X@x@A$AjEY΀ jaj =jр=j%-(%@F@~BЀ B$  B@JB@x@AAJEb Je=aF = ÀЏ{% "@@ / @W Ab|b! b B@`B@x@A$AEfEacb =j`="@@,JUJA# B@aRBx@x@ =AR! @`Et@ aR" =RĀ= #@l@ +c,o%Cx@ B$bؾb½% B@a$B@x@A$Aj&Ewb@Yjaj' =j{=%-(%(@z@ )BFB$ * B@JB@x@AAJ+E3aJ e,=FF$X- =u9{% %.@4@ A/bb! 0 B@aBc@x. 9A$A1 @Ecb2 =j(a^ <3 @@,4JJ@ݣRc5 B@a Bx@x@A AR6Efa aR7 =m=R"8@i@ ΢QagB9bhbj: B@jB@x@A$Aj;E!jaj< =j$%=(%-(%=@$@ w>B#B J$ ? B@JB@,/@x@AAJ@E# eA=FVB =@{% "C@_ހ@ c nADb ! E B@`B@x@A$AFEbcAG =jX`=^ <H @4W@ m!j% * IJVJ@0J B@aRBy@x@A ARKEa aRL = o=R"M@@ $F!R!K 4"S!t$XqBNb/b½O B@B@x@A$AjPE>ˀ i AXajQ =@΀=(%-(%R@ @$SB̀J$ T B@Bc@x@AAJUEĆeV=FFW = À{% "X@@ m @AYb`b bZ B@`Bh@x@A$A[EKBab\ = `w`=$j <] @@,^JFJ _ B@B@x@A AR`EY FaRa = =R"b@i@c5"hBcbջb jd B@jBc@x@A$AjeEtb jajf =jgx=%-%g@w@n' @ :@Bh #BBi B@JB@x@AAJjEf0Jek=Fz3(.@l =R6{% "m@1@ ) @ Anbb b륱o B@aB @x@A$ApEbq =jaj^ <r @x@ !j @csJ䩠JA#4` ct B@aRB@x@A ARuEba FaRv =j="w@e@ $F!R% ͣalBxbbbjy B@jBc@x@A$AjzEjaj{ =j"=%p(%|@\!@ r!j @ :@B}  B J~ B@JB@x@AAJEڀ e=F ={% %@Eۀ@ ! @'Ab b륱 B@aB @x@A$AEbcb =jU`=j$j < @T@,cJSJA#eF B@aRBz((Bb@x@A ARE a aR =P=R"@@ c#\s,-/7 Bbb B@jB5n@x@A$AjE#Ȁfaj =jˀ=j%p%@@ $( @ :@B gʀJ B@JB C@x@AAJEe=*F ={% "@焀@ ) @+c 5@AbEb ! b륱 B@aB,@x@A$AE0?ab =ja= < @@ m!j%cJ/JA# B@aRBc@x@A ARE=aR aR ==!@J@ 3%-.9RBbb fc B@jB@x@A$AjEqjaj =jPu=j%pj%@t@nBB$  B@JB@@x@AAJEK-Je=̤F{ =@;3{% %@.@W @.W Ab ! b B@`B@x@A$AEb =ja"@X@ mJĦJA#c B@aRBc@x@A ARE_a aR =Rg=H@@b@ o$F%Cx-@zhobKb½ B@jB5n@x@A$AjEfjaj =j=j%-(%@A@ !j @ :@B  B B B@JB@x@AAJEր e=rFQ =܀{% %@"؀@, nWb׀b! 륱 B@aB@x@A$AEsbcb =jR`="@ Q@,JvPJ  B@aRB@x@A ARE a aR =R:=R"@ @ S$:$ ad Dfbb½ B@jBc@x@A$AjEŀ ,aj =jȀ=j%p(%@ǀ@ BLB$ c B@JBL6@x@AAJEe=Fc =~{"@ȁ@ !%N Ab*bj! bc B@aBc@x@A$AE e(=9|) =-{% "*@P(@ ! @A+b ! b륱, B@aBc@x@A$A-Ecb. =ja"/@@,c0JJA#1 B@aRB@x@A AR2EYa(aR3 =Rca=%p4@\@ y4h15b)b j6 B@jB /@x@A$Aj7E0ajfaj8 =j=j%-j%9@ @ $( @"@B:Bl$B; B@JB@x@AAJ<EЀ e==7F}> =ր{% %?@р@ y) @+c @bRb b륱A B@aB "@x@A$ABE=b bC =jߐ="D@A@,EJJ F B@aRB @x@A ARGEGRaRH =R\O=R"I@J@L65"AaEDfJb(b@$K B@jB{ @x@A$AjLEJjajM =j=j%-(%N@5@ nOB BP B@JB,@x@AAJQEѾ eR=٤Fw!^P/ AS = )Ā{"T@@ W%N AAUbmbV B@aB@x@A$AWEXzbbX =j:`=(fY@8@,ZJSJA#4[ B@aRB@x@A AR\Ee@Y aR] =R=R%^@e@ % BV @@0B_bb@%jaoy` B@jB|@x@A$AjaEb jajb =jx=( c@ӯ@ ndB4BJe B@JBc@x@AAJfEshJeg=Fzch =_n{%p:"i@i@ c +Bjbb ck B@aB@x@A$AlE#abm =j=j n@x@,oJJA#Fp B@aRB@x@A ARqEaR FaRr =R="s@@ 5uŃ%Ńs5ahBtb{bfu B@jB @x@A$AjvEVjajw =jZ= x@qY@$yBXB Jz B@JB@x@AAJ{E;@T e|=$|} ={% ~@R@ ': @c qVb ! b B@aB@x@A$AÈb =j̍a @.@,JJA# B@aRB@x@A AREDa aR =R]L=R(o@G@ 666L6a6naLEb%b j B@jB@x@A$AjE/aj jaj =j=j @@$( @.`AJBsB$B B@JB@x@AAJE Je=7΄F{c ={% @@ c |AbRb c B@aB@x@A$AE=wbcb =jj7`= @5@,,J8JA# B@aRB@x@A AREJ aR =R=R%@N@ e6ѻ6L6ͥBbbA;j B@jBy@x@A$AjEѩb jaj =j]=j%-(6@@~nBB B@JB@x@AAJEXeJe=٤F =a aR =F=FR"@vA@ c Cswoѻkq^Bb@bĩ B@a$B~ B@x@A$Aj E jaj =jy= @@ n$( @"AJ B5B J$B B@JB@x@AAJEe=ȄF = Àp{% F"@@ ) @+c fAbb b B@`B "y@x@A$AEqab =j-1`=j^"@/@,J.J  B@aRB|/@x@A ARE FaR =R=R"@ @ Scq vuVA|abb j B@jB@x@A$AjEb jaj = '=j%-(+c @@~ƹ!BB" B@B@x@AAJ#E!_Je$=Fx% =e{% "&@]`@ ! @+c Q 'b ! b ( B@aB@x@A$A)Eab* =jڀ=j +@/ـ@,,JؠJA#c- B@aRBc@x@A AR.EaRy FaR/ =RX=FR"0@@$X6c}4%aD1bb j2 B@jB oc@x@A$Aj3EEJĀcb? =jy"a"@@ق@,AJEJ(>B B@aRB@x@A ARCEW;#a(aRD =R C= E@k>@ s+]\t@8BFb=b@(G B@jB{ @x@A$AjHE   ajI =jn=j%-(+cJ@@ KB*BL B@JB@x@AAJMEe$b J!!eN=ĄFO =M{% (P@@ @c eAQbb^! b R B@aB "@x@A$ASEm%ac"#bT = `` .&`="U@j,@,VJ+J FW B@B@x@A ARXE $$aRY =R=R"Z@@ \r2\r-wA|[bb j\ B@jB~ @x@A$Aj]E'b j%%aj^ =j=( _@a@~`BB J$ Fa B@JB@x@AAJbE\(aJ J&&ec=nFd = Àa{% e@>]@ , lfb ! g B@`B "@x@A$AhE)a'(bi =j׀=j.j@ր@,kJՠJ l B@aRBy@x@A ARmE*aR ))aRn =RN=R%o@@ d p |} _Hopbb q B@jB@x@A$AjrE!J+j**ajs =jM=H@*e-(+ct@@ uBeL, v B@JB@x@AAJwE,J++ex=)|y = {% "z@@ ! @c A{bDb b | B@aB@x@A$A}E/,-b~ =jV-aj @@,J%J Rc B@aRByy@x@A ARE<8.a ..aR =R?="@D;@!!~%Q 1raBb:bf B@jB oc@x@A$AjE j//aj =j;=%-(%@@$BB J) c B@JB@x@AAJEJ/b J00e=ˤF =B{% %@@, wAb ! c B@aB "c@x@A$AEj0ac12b =j+1`=j$j+c@c)@,cJ(J 1 B@aRB@x@A ARE 33aR =!J="@@ OV"udǴt MBbRb@& B@jB@x@A$AjEd2b j44aj =j=j%-%@K@) @"@BB B$B B@JB@$@x@AAJEX3J55e=lkF/ =@^{F% %@(Z@ ! @ AbYbj! b륱 B@`B,@x@A$AEr4a67b =jԀ=j @ Ӏ@,JuҠJA# B@aRB@x@A ARE5aR F88aR =R+=R"@@ c"Ô"pWUBbb½ B@jB oc@x@A$AjEG6j99aj =jJ=j%-(%@I@ $( @ťBJB$B B@JB@x@AAJE7J::e=| ={% "@@ L6 {Ab)b ! 륱 B@a5 "c@x@A$AEE;>aj =j8=j%-(%@@ BB$  B@JB@x@AAJE.:b??e=F ='{% "@o@ ! @c b AbΡ ! b B@aB@x@A$AEg;ac@Ab =j'<`="@@&@,J%JA#c B@aRB@x@A AREހ FBBaR = o= @@ " Bb3b j`F B@B@x@A$AjEI=b jCCaj =jɝ=H@1y@$@~ƹ$(ncB B$B B@JB@)@x@AAJEU>JDDe=QhFh =@[{% @ W@ c =`BblVb `Bc@x@A$A @EW?acEFb =jр="@π@ m!j$$XJRJ(> `JR- B@a B@x@A AREd@aR FGGaR =R=R(o@p@  kkaaBb܊bj B@jB o@x@A$AjECAjHHaj =jsG=(%-(+c@F@ rB/B J B@JBc@x@AAJEr IIe=Fz/ =nBa% "@@ Abb! ^ B@aB "@x@A$AEBa,cJKb =j&{C`=^(f@y@,JxJ j B@aRBz,@x@A AR`E2Da LLaR =R9=R"@5@ '1 @# K 7 "֨ &VBbr4b½ B@a$B @x` =Aj @`E퀈2 jMMaj =j=( @x@~BB J$ g B@aB@x@AAJ EENNe =F/ ={%  @U@ ) @ Bb ! b B@aB,@x@A$AEdFaOPb =j$G`=$j%@-#@,J"JA# B@aRB@x@A AREۀ FQQaR =R]=R%@ހ@ $F @%L"d"b$b½ B@jB@x@A$AjE.Hb jRRaj =j=%-+c@@ !j @"@BBnJ$B B@JB@x@AAJERIJSSe =6eF! =X{% ""@S@ @ #bQb b륱$ B@aB (c@x@A$A%E<JaTUb& =jm΀=j$j"'@̀@@Sf' @(J;J J) B@aRB@x@A AR*EIKaR FVVaR+ =@=",@e@ !R#dtTM,` -bчbf. B@B c@x@A$Aj/E@LjWWaj0 =j`D=%-C1@C@ r2BB J3 B@JB@x@AAJ4EW XXe5=ؤF6 = ÀSMa% %7@ $F @+c ee8b ! b 9 B@`B@x@A$A:Eݷ,cYZb; =jwNj$j%<@Xv@,c=JuJA#> B@aRB$X@x@A AR?E.Oa [[aR@ =R6=R"A@1@ $F @+c3$"% Th:FDBb_b½C B@jB@x@A$AjDEq j\\ajE =j= F@Q@~!j @"AJGB쀃 $BcH B@JB@&y@x@AAJIEP]]eJ=yF/K =@諀{% F"L@3@ @AMbb ! bcN B@`B !c@x@A$AOEaQaJ^_bP =j!R`=$j"Q@ @,RJJ S B@aRBc@x@A ARTE F``aRU =R2=R"V@ۀ@ C"VÔQx"aeWbڀb jX B@jB/@x@A$AjYESb jaaajZ =j=j%-+c[@@~\B_B$ ] B@JBc@x@AAJ^EOTJbbe_=bQ`=|y` =U{% "a@P@ c L0Abb6b ! c B@aB@x@A$AdE! UajcdAe =jKˀ="f@ɀ@,gJJ jh B@aRBc@x@A ARiE.VaR FeeaRj =RЉ=R"k@*@ )%oSt&t6C!~!dlbbĩm B@jBc@x@A$AjnE=Wjffajo =jAA=(%-(%p@@@ !j @(@BqB?B$Br B@JB@x@AAJsE; gget=Fu =0{% "v@r@ y! @2_Awb ! b륱x B@aBc@x@A$AyE´Xbhibz =jtY`=^"{@Ms@,|JrJA#} B@aRB@x@A AR~E+Za jjaR =Rr3=R"@.@ yctEBb8b j B@jB@x@A$AjEV jkkaj =j=(%-(%@7@$( @"@BB逃B$B B@JB@x@AAJEݢ[b Jlle=^Fc =Ѩ{% "@@ c Abyb 륱 B@aB@x@A$AEd^\acmnb =j]`=^"@@,J_J >c B@aRB@x@A AREqՠ@Y: ooaR =R,݀=R"@؀@ cs"$Yp9y@Bb׀b½ B@jB y@x@A$AjE^b jppaj =jt=(%-(%@ѓ@ rB4BJ B@JB@٢oB!K @'@x@AAJEL_Jqqe=_F = 5sR{% "@M@ , ^Abb B@AB@x@A$AE`acrsb =j8Ȁ=j/j%@ƀ@ m`@,JJ  B@aRB; @x@A AREaaR FttaR =Rʆ="@#@ cp5ô. aBbb# B@jB@x@A$AjE:baj2 juuaj =j*>= @=@~B e=F, =PO{% +c@J@ $F @+c NAbb! b B@aBB@x@A$AEkacb =  ŀ="@mÀ@,cJ J F B@aRBF@x@A ARE{laR aR =R=%p@ @ c˵zuy"4,IBbx~b½ B@jBc@x@A$AjE~7mjaj =j ;= @h:@ B9B c B@JBh@x@AAJE e=|~ ={ @A@ ! @+c Bb  B@aBc@x@A$AEnbb =jno`=^+c@m@@S' @JlJA#J B@aRB@x@A >E%pa aR =@I-=R(o@(@ o"!RS"",aBb b½O  B@x@A$AjE  jaj =j=(%-+c@@ n!j @n"@BBd〃J$B B@JB@y@x@AAJEqe=(Fy =@{%p" @㝀@ $F @+c q5A bCb b B@`B$X@x@A$A E.Xrab =jVs`=^"@@ m!j$^J$JA# B@aRB/@x@A ARE;Ϡ@Y FaR =Rր=R"@3Ҁ@ $F!R(o""Hh*Bbрbf B@jB@x@A$AjEŠtb jaj =j>=(%-(%@@$BBJ B@JB@x@AAJEHFuJe=ʤF =IL{% "@G@ @Ab ! b B@aB 'Z@x@A$A!Evacb" =j€=^%#@b@,$JοJ (% B@aRB@x@A AR&ExwaR FaR' =!J=R"(@{@ pKJP P  =B)bYb@&j* B@jB@x@A$Aj+Ec4xaj jaj, =j7=j(%-@F@' @"@B.B6B$Bc/ B@JB@x@AAJ0E Je1=k|5n2 ={"3@#@ A4bb 5 B@aBy@x@A$A6Eqybcb7 =jkz`=j)"8@j@,9JpiJ >: B@aRB@x@A AR;E~"{a aR< =R*=FR%=@r%@ 0uP "uPKa>b$bA;? B@jBV@x@A$Aj@E jajA =j=%-%B@@ rCBIB JD B@JB@x@AAJEE|eF= FG =|{% "H@ɚ@c ?dAIb(b J B@aB@x@A$AKEU}acbL =j?~`=j^%M@@,NJ JA#FO B@aRB$X@x@A ARPE ̠@Y FaRQ =RӀ=R"R@0π@y8MJ$``$`@BSb΀baFT B@jB5n@x@A$AjUEb2 jajV =j=j W@z@~XB߉B JY B@JB@٢o/lB@x@AAJZE-CaJ Je[=Fw\ =@I{% ]@kD@ `@ kB^b e! _ B@`B@x@A$A`Eba =jݾaj b@?@ m!j @cJJ d B@aRBc@x@A AReEua aRf =Rv}=F!g@x@ |}N$ `TT'W%qhb>b½i B@jB@x@A$AjjEH1aj jajk =j4=j l@/@~mB3 B$ ,n B@JB@ `@x@AAJoE Jep=TFq =@{H e r@ @, Esbkb t B@`B@x@A$AuEVbbv =j{h`="w@f@@SmxJIJA#Jy B@aRB@x@A ARzEca aR{ =@'= |@c"@ #``$`$pq|a;&B}b!b#`fc~ B@B} @x@A$AjE jaj =jzހ=j%-(1@݀@ B6B B@JB@x@AAJEqb> Je=F* =Y{% .W@@ $F @c Ab b! bc B@aB "y@x@A$AEQat b =jV="@T@ !j$cJcJ B@aRB@x@A ARE~ RaR =R9=R"@@83}<|}4kcbb j B@jB@x@A$AjE aj =j̀=( @ˀ@ BMB J$ c B@JB@$@x@A>Eb Je=R =@{% @Dž@ ;kcb(b B@`B@x@A$AE@ab =1`=j%pj/o@~ mJJ c B@aRB@x@A ARE  aR =RϾ="@(@ C},|}$aHobb½ B@jB@x@A$AjErb jaj =j3v=j%-+c@u@ BtBJ B@JBc@x@AAJE-.Je=F =4{bxs@i/@ $F%c CAb  bZ B@aB@x@A$AEb =j멏aj @K@,JJA# B@aRB@x@A ARE`a FaR =Rmh=FR+c@c@,8STdpI2PDJ!aSBb9b@$j B@jB{ o@x@A$AjEHjaj =j`= @6@$B J B@ B@x@AAJE e=PF =݀{% :"@ ـ@ ': @ GBbk؀b b B@aB@x@A$AEUbcb =jaS`=j^+c@Q@,J0JA#F B@aRB@x@A AREc a aR =R =R"@g @ cMu$r4]L]Bb bo B@jB@x@A$AjE jaj =jfɀ=j @Ȁ@ $( @"AJB"B B@JB@x@AAJEpe=Fu, =a{% @@c Ab b ! c B@aBc@x@A$AE e=5F =ڀ{% %@Հ@ , AbPb 륱 B@aBc@x@A$AE:bcb =jgP`=" @N@ 82$, J5JA# B@aRBynb@x@A AR EHa aR =R=R"@H @ %e4Z@Bb b B@jB@x@A$AjE€faj =jWƀ=(%-(%@ŀ@~BB B@JB@x@AAJEU~e=֤F =A{% "@@ ! @c uAb ! b  B@aB@x@A$AE9ab =j=^(f@[@, JJ ! B@aRB@x@A AR"E鰢aR aR# =R=R"$@@'%t"ta9?B%b^b c& B@jB* @x@A$Aj'Epljaj( =jo=( )@K@ $( @"AJ*Bn,Bc+ B@JB@x@AAJ,E'Je-=x:|. =-{% /@4)@ A0b(b c1 B@aBh@x@A$A2E~〈 Z^iqb3 =jaj$j"4@@,5J|J 6 B@aRBz@ B @x@A AR7EZaRA@aR8 =@8b="9@]@ ,"VHT`$`%$ |y:bb@&'; B@B$X@x@A$Aj<Ejaj= =j=%-+c>@@$?BFB J) V@ B@JB@@x@AAJAE eB=FvyC =@׀{% (D@Ҁ@ , &|yEb5b ! F B@`Bc@x@A$AGEbH =j=^%I@@ mJJJA#K B@aRBw@x@A ARLEHRaRM =R@P="N@K@ ypSITpMIakcObb jP B@jB @x@A$AjQE-jajR = =j%-(%S@ @  `@"@BTBm B$ BU B@Bc@x@AAJVE eW=FBX =ŀ{F% "Y@@c kcZbOb [ B@aB@x@A$A\E:{bb] = `];`=j^"^@9@,_J)J ` B@BL6@x@A ARaEH aRb =R=FR"c@\@ HTop?I%$VaDfdbb je B@jB@x@A$AjfEέb jajg =jV=j%-(%h@@~͓iBBj B@JB@c@x@AAJkEUiJel=֤Fym =@Eo{% "n@j@ TAob ! p B@`By@x@A$AqE$abr =j="s@g@@SmctJJA#Jcu B@aRB@x@A ARvE雰aR FaRw =@=R"x@@ *kd,+dpxBybeb½z B@B@x@A$Aj{EpWjaj| =jZ=( }@I@ r~PY J$ q B@JB@@x@AAJEJe=x%|h =@{% @2@ $F @ mBbb bc B@`B@x@A$AE}΀cb =ja^+c@@ m!j*Q,JtJA# B@aRB@x@A AREEa aR =R>M=R%@H@ 6 uabb B@jB@x@A$AjEaj2 jaj =j=(%-(+c@@~BZB J B@JB@x@AAJE Je=τF =€{% "@н@m @K `Ab4b! b B@aB@x@A$AExbb =jR8`=%pj%@6@,JJA# B@aRB@x@A ARE, aR =R=R"@=@y9I$P4@Bbb c B@jBz@x@A$AjEbjaj =jC= @@ BB c B@JB@x@AAJE:fJe=xF =&l{% F"@vg@ y) @ /Bb  bc B@aB@x@A$AE!a,b =j=j^%@;@,JߠJA#(Rc B@aRB@x@A AREΘaR aR =R~="@ޛ@ %aYJBbJb@$j B@jB$X@x@A$AjEUTajaj =jW=%-(+c@3@$BV J)  B@JB@x@AAJEaJ@Y e=]"|y, ={% %@@ y': @( hv/G?Ab|b ! b B@aB@x@A$AEbˀb =jaj @@,JiJA#r B@aRB@x@A AREpBaFaR =RJ=R"@|E@9#Sua+BbDb j B@jBB@x@A$AjEjaj =ja%-(%@@ $( @(@BBCB$B B@JB@@x@AAJE}Je=̄F =@q{% "@@ c )!Tbb ! 륱 B@`B@x@A$AEubyb =j45`= @3@,JJA#c B@aRB@x@A AREaR =R=R"@!@ c3zBbb j B@jBx@x@A$AjEbjaj =j(=j%-(#@@ BB$ c B@JB@x@AAJEcJe=uF = i{% "@_d@l @c DAb ! bc B@aB 'Z @'@x@A$AEa b =jM#="@!@,JJ  B@A*BB@x@A ARE,ڀFaR =R=R"@D݀@ CǨ999alBbܠb@$ B@jBy 5n@x@A$AjEbjaj =jC=(%-(%@@ rBB J$  B@JB@x@AAJE:QJe=F@Q&W{% "@xR@ c Abڡ  B@aB`x@9 A @E ayA =̀=^.@Sˀ@,JʠJ@F B@a B@x@A AR E΃aR aR =Rp=R" @҆@ S9 :::3:T :aNB b>b@$<c B@jB@x@A$AjEU?aj2 jaj =jB=(%-(%@A@~ӈBA J B@JB@x@AAJE J @==d |t =a% "@ ! @cS$Abwb b  B@aB@x@A$AjEb,b =jvaj%@t@,J]JA#F B@aRB@x@A AREp-a aR =R5=R" @`0@ cq$:T(:q,:T0:q4:TկB!b/bĩ" B@jBQ@x@A$Aj#E耈 jaj$ == %@@ $( @"AJ&B>BJ' B@JB@x@AAJ(E}b J  e)=F * =i{% +@@ , yq]A,bb c- B@aB,@x@A$A.E`a,  b/ =j `=j^"0@{@ 5, @ 1JJ R2 B@aRB|@x@A AR3E׀  aR4 =Rހ="5@%ڀ@ cs8:q<:T@:qD:aӡB6b٠b@$7 B@jB@x@A$Aj8Eb f  aj9 =j =%p(C0;@}@$;BܔB B< B@JB@x@AAJ=ENaJ Je>=F? = À T{% (@@ZO@ ': @c wIxAb  bB B@`B@x@A$ACE acbD =jɀ=j%pj%E@0Ȁ@,cFJǠJ FG B@aRB@x@A ARHEaR aRI =RY=R"J@@ c HI K Jao>DKb#b½L B@jB@x@A$AjME9<jajN =j?=%-%O@@ $( @"@BPB~>(JcQ B@JB@8W@x@AAJRE eS=A | T = {% "U@@$X AVb\b 륱W B@`B !y@x@A$AXEGbY =jٷ= Z@;@,[JJ \ B@aRB@x@A AR]EnRaR^ =Rv=R"_@q@ %M L O NaB`bJb ja B@jB@x@A$AjbET*jajc =j-=j%p(%d@3@ eB, Bf B@JBc@x@AAJgE eh=Fyi ={"j@@ !%Nc `Akbwb b l B@aB@x@A$AmEbbbn =ja`= o@_@,pJ]JA#jq B@aRB@x@A ARrEoa aRs =R$ =R%t@{@ 9udǩt >`Bubbov B@jBژ@x@A$AjwE jajx =j׀=( y@ր@ $( @ūczBBB J{ B@JB@)c@x@AAJ|E}e}=F~ =@q{% :"@@ 5\ @+c .OBbb bc B@`Bc@x@A$AEKab =j5 `=^"@ @ m!j% ! $ ARJJ  B@aRB@x@A ARE€aR =Rɀ=R"@-ŀ@7!R% QTSPA6tNZWAjbĀb j B@jB @x@A$AjE}b2   aj =j(=(%p(+c@@~ƹBB J B@JBc@x@AAJE9aJ J!!e=Fnb =?{% "@\:@B! @\Ab ! b B@aBQ@x@A$AE"#b =jδa$j%@0@,JJA# `RJ B@aRB@x@A AREka $$aR =RPs=R"@n@ yP 0; Bbb½ B@jBB@x@A$AjE9'aj%%aj =j*= @)@ $( @r"AJB) $By B@JB@x@AAJE &&e=AF = À{% F"@@ y) @+c CvAb`b bc B@`B@x@A$AEGb'(b = `l^`=j^"@\@ 5!j @cJ:J B@B@x@A ARETa ))aR =R =R"@h@  t^t aBbb B@jB _c@x@A$AjE j**aj =jkԀ=j%-(+c@Ӏ@ rB'B J B@JB@x@AAJEbb J++e=Fc =N{% "@@c LAb ! B@aB,@x@A$AEGac,-b =j`=%@@,cJJA#c B@aRB@x@A ARE ..aR =Rƀ=&@@ cK[ 0 ucr aJ,bfb@$jc B@jBB@x@A$AjE}zbf//aj =j}= @@@ B|  B@JB &@x@AAJE6aJ 00e=HF =@;{% @=7@ ) @c ,b  b B@`BB@x@A$AE12b =ja^%@@,J}JA# B@aRB@x@A AREha 33aR =RIp=R(o@k@ cCSPCPCPQDak}Hobb f B@jBc@x@A$AjE$j44aj =j'=j%-(+c@@Bf& B)  B@JB@@x@AAJE 55e=&F =@{% "@@h ϏAbAb  B@`B@x@A$AE,b/67b =j^[`="@Y@ m'$J*JA# B@aRB@x@A ARE9a 88aR =R=R"@E@:C PaBbbĩ B@jB@x@A$AjE j99aj =jLр=(%-(%@Ѐ@ BB J B@JB@x@AAJEF::e=ȤF =7{% "@@, (%Ab ! B@aB@x@A$AR  EDac;>aj =jz=j @F@  By$ c B@JB@x@AAJE2aJ??e=iEFc =8{"@'4@ VBb3b  B@aB@x@A$AEoc@Ab =jaj+"%@@,JnJA# B@aRB@x@A ARE|ea FBBaR =R+m=FR%@h@:#(QȀd +gYaBbgb@$j B@jB@x@A$AjE!jCCaj =j$=%-+c!@#@$"BOB J# B@JB@x@AAJ$E܀ DDe%= F& =v{% "'@݀@ y/ @ m4(b&bZ bZ) B@aBy@x@& =A* @EbcEFb+ =jCX`=j^%,@V@,-JJ@F. B@a B@x@A AR/Ea GGaR0 =R="1@.@ c3Ǫtkb`p a@D2bb½3 B@jB@x@A$Aj4E jHHaj5 =j5΀= 6@̀@$7B̀B J8 B@JB@x@AAJ9E+IIe:=F; ={% <@g@ , @( lB=b ! ba${ > B@aBc@x@A$A?EAaJKb@ =j`= A@=@,BJ C B@aRB@x@A ARDE FLLaRE =Rf=R(oF@Ȼ@ c C6E5@+g0GBGb4b½H B@jBc@x@A$AjIEFt<@T jMMajJ =jw=j K@+@~$( @(AJLBv B$BM B@JB@x@AAJNE/JNNeO=NB|P =5{% `3bQ@ 1@ c xARbi0b 륱S B@aB,@x@A$ATET뀈yOPbU =ja V@㩀@ , @yWJOJA#cX B@aRB@x@A ARYEaba FQQaRZ =Rj=![@ie@ S8Ǩ$QaxB\bdb@$j] B@jBQ@x@A$Aj^EjRRaj_ =jp!=j%-j1`@ @ aB,Bb B@JB@x@AAJcEoـ> SSed=Fe =[߀{% cf@ڀ@ Agb bǝ! h B@aB@x@A$AiEbcTUbj =j!U`="k@S@,clJRJA#Fcm B@aRBc@x@A ARnE a VVaRo = = p@@  cQ3<5nBqbb½r B@B* @x@A$AjsEǀF jWWajt =jˀ= u@oʀ@ vBɀBJ$ yw B@JB@x@AAJxEXXey=Fz ={% {@L@ ) @6 CnW|b  b} B@aB@x@A$A~E> aYZb =!b=^.@@,JJ @ B@aRBQ@x@A ARE aR F[[aR =RH=R(o@@ s+``%IoEbb f B@jB@x@A$AjE+q j\\aj =jt=(%-(+c@@ Bgs,  B@JB]L@x@AAJE, J]]e=3?| =2{% "@-@ ! @6 AbNb b B@aB@x@A$AE9W^_b =je a^%@Ǧ@,J3JA# B@aRB/@x@A AREF_a ``aR =Rf=R"@Rb@ `aBbabf B@jB@x@A$AjEjaaaj =jQ=(%-(%@@$BB Jc B@JB@B@x@AAJET bbe=٤F =@D܀{% "@׀@ c OuAb ! B@`B@x@A$AEڑbccdb =jR`=j c@eP@ m!j @JOJA# B@aRB@x@A AREa eeaR =R="@ @ ВЄ(Bb\bA;j B@jB@x@A$AjEnĠ@Y jffaj =jǀ=j @Q@$Bƀ F B@JB@x@AAJEgge=vF5n =݅{F% @2@ y c fBbb c B@aBy@x@A$AE|;hhb =j'@=j%pj+c@>@,J=J  B@aRBy@x@A ARE FiiaR =R=R(o@@ ЂЃ2taqBbgb j B@jB@x@A$AjEjjaj =  =j%-+c@i@ $( @"@BBɴB)B B@B@c@x@AAJEnJkke= e!='|F" ={% #@@4 b$b7b ! % B@aBc@x@A$A&EЀcb' =jX&aj0j%(@@@S')J$J J* B@aRB@x@A AR+E+G'a aR, =@N="-@/J@ cpeΐǵ%a_Ho.bIb/ B@B @x@A$Aj0E(jaj1 =j*=j 2@@ ) @"AJ3BB$Bi,4 B@JBB@x@AAJ5E8 e6=դFa7 = Ā{F% F(8@t@ , 4A9b ! : B@aB,@x@A$A;Ey)bcb< = `9*`=j =@J8@,>J7J ? B@B@x@A AR@E aRA =R=R"B@@;Eepd`ud"HtBCbLb D B@ B@x@A$AjEES+b j! AF = ׯ=j%-(6G@5@$HB I B@Bc@x@AAJJEg,JeK=[zFL =m{% "M@i@ ) @  8ANbvhb bO B@aBQ@x@A$APEa#-abQ =j= R@@,SJ[JA#FcT B@aRB@x@A ARUEn.aR F+$FV =R=R"W@r@$) @%pP0txBXbޜb@%jY B@jB@x@A$AjZEU/jaj[ =jY=H@e-(%\@X@ !jnc]BAB)B^ B@JB@x@AAJ_E{0Je`=Fa =h{F% "b@@ @"Acbb ! b d B@aB$X@x@A$AeÈ,5$^f =j&1aj$j"g@@,hJJA#i B@aRB@x@A ARjED2ay aRk =RK=FR"l@G@!$F @#~3%!0 8_|BmbFb jn B@jB@x@A$AjoE jajp =  3aj%-%q@h@ !j @"@BrBB$Bs B@B@x@AAJtE Jeu=Fv ={% "w@Y@, Axb  cy B@aB@x@A$AzEv4bcb{ =j65`="|@75@, }J4J ~ B@aRB@x@A ARE퀈 aR =Ra=R"@@ 3S!q'SBߘBb%b½ B@jB@x@A$AjE86b jaj =j=(%-(%@@ rB|J B@JB@?) @x@AAJEd7Je=@wF = @j{% "@e@ ! @ {jAb[b b B@`B@x@A$AEE 8acb =jv=^(f@ހ@ m2* JDJ c B@aRB@x@A ARES9aR F>, =R=R"@W@ C ! T` axbÙb@$j B@jB@x@A$AjER:aj2 j#!j =jfV=( @U@~n$( @"DB"B B@JB@x@AAJE`;aJ Je=F =E{% @@ $F @+c 4`b ! bc B@aB@x@A$AEɀ,b =ja%p+c@^~ !j @"@BBBB B@JB@x@AAJE  e=ʄF ={% "@?@$! @eb  b륱 B@aB@x@A$AEs?bb =j3@`=j$j"@ 2@,Jw1JA# B@aRBz[{B@x@A ARE aR =RK="@@ c${Dfbb j B@jB@x@A$AjEAb jaj =j=%-%@@$Be , B@JB &@x@A>EaBaJ Je=%tF =@g{% %@b@. c cAb@b , B@`B@x@A$AE*Cab =S݀=j @ۀ@ m!j @J!J c B@aRB@x@A ARE8DaR aR =Rܛ=R"@8@ (o!Roszy1U"a}QBbb½ B@jB@x@A$AjEOEjaj =j?S=%-(%@R@ !j @(@BBQBB B@JB @x@AAJEE FJe=ʤFw =5{% "@ @ @٣Ab  B@aB@x@A$AEƀcb =jGa$j"@W@,JÄJ   B@aRB@x@A ARE=Ha aR =RE=R"@@@ c% ~}{%q.BbIb½ B@jIBc@x@A$AjE` jaj =j=j%-%@C@~B B$  B@JB@x@AAJEIe=hDŽFh =ۺ{%p"@$@8) @6 Abb bc B@aBc@x@A$AEnpJab =j0K`="@.@,J`J  B@aRBc@x@A ARE{@Y FaR = +4= @@!d @6y!$q 21BTbĩ B@B@x@A$AjELb jaj =j=j%-(%@@~BNB B@JB@x@AAJE^MJe= qF =}d{F% % @_@5 J! @A b$b ! b B@aB@x@A$A ENab =j9ڀ=j$j+c@؀@,JJ c B@aRB 5@A AR @`EOaR FaR =RΘ=R"@)@ c+\\y%`̠PBbb j B@a$B@x@A$AjELPaj jaj =j#P="@O@BNBB$&5 B@JB@x@AAJE*QJe=Fc ={% F"@d @ c &B b  c! B@aB@x@A$A"EÀcb# =jRa^%$@H@, %JJA#& B@aRB@x@A AR'E:Sa FaR( =RdB=R")@=@ zy` a)B*b2b@$j+ B@jByc@x@A$Aj,EE* aj- =j=(%-(+c.@-@ r) @"@B/B J0 B@JB@y@x@AAJ1E̱Te2=MĄF3 =@{% "4@ @c SA5blb 륱6 B@`B$X@x@A$A7ERmUacb8 =j|-V`=j c9@+@ m2 @y:JIJA#c; B@aRB{,@x@A AR<E`@Y aR= =R=">@p@ yVy4 WB?bbo@ B@jB@x@A$AjAEWb2 jajB =jo=(%C@Ϣ@~DB/B JE B@JB@٢o@x@AAJFEm[XaJ JeG=F|yH =@Va{% I@\@ $F @6  AJb be! b K B@`B@x@A$ALEYabM =j׀=%pj(fN@Հ@ m!j @OJԠJA#P B@aRB@x@A ARQEZaR aRR =R=!R(oS@@ o @6%Q % ~aVBTbjb U B@jB@x@A$AjVEI[jajW =jM= X@qL@ YBKB, Z B@JB@x@AAJ[E\Je\=|] = {% ^@F@ @B_b dh` B@aB$X@x@A$AaEbb =jĀ]aj$j%c@$@,dJ~JA#aHRe B@aRB/@x@A ARfE7^a aRg =RU?=R%h@:@ Bq%a5nibb½j B@jB@x@A$AjkE* jajl =j=j%-+cm@@~nBn B$ o B@JB@x@AAJpE_b} Jeq=2Fr ={%p"s@쯀@ B) @ ,5ntbMb! bu B@aBB@x@A$AvE7j`acbw =jZ*a`=%x@(@,yJ&JA#z B@aRBc@x@A AR{EE@Y aR| =R="}@Y@ ct/t /Q@~bb½ B@jB~@x@A$AjE˜bbaj =jT=%-(%@@ BB c B@JB@٢o@x@AAJERXcaJ e=ӤF/ =@F^{% "@Y@ y! @.W b(b e B@`B !L6@x@A$AEdacb =jӀ=^%@\Ҁ@,JѠJ c B@aRB@x@A AREeaR aR =R=R"@捀@,<"+Y~+h@ aCDbRb½ B@jBF@x@A$AjEmFfjaj =jI=(%-(%@N@ n$( @n"@BBH J$B B@JBc@x@AAJEgJe=u|c ={%p"@/@ c  Abb c B@aB@x@A$AE{b =j}ha^"@ |@,Jy{J j B@aRB@x@A ARE4ia aR =R/<=R"@|7@ $F'Eo<%}" %8,b6b B@jB o$X@x@A$AjE jaj =j=(%-(%@@$BOB J B@JB@x@AAJEje=F ={% "@լ@ < {>b5b B@aB@x@A$AEgkab =jH'l`=j$j%@%@,JJA# B@aRB@x@A ARE*ހ FaR =R=R"@2@<#!0S!zy@Dbb jc B@jB@x@A$AjEmb jaj =@݀@="@@ƹ'%"AJBBB B@B@x@AAJE7UnJe=Fy, =#[{% F"@oV@ S c _Ab  c B@aBy@x@A$AEoacb =jЀ=j @Eπ@ !j @,JΠJA#c B@aRB; @x@A AREˇpaR FaR =R{="@ϊ@ 3% ~ƑDa?Bb;b½ B@jB/@x@A$AjERCqjaj =jF=%p(+c@@@ rBE J B@JB@x@AAJE e=Z| = Àra% %@@ * ! @W bu ! b B@`B@x@A$AE_cb =jzsj%pj(f@x@,cJVJ  B@aRB$X@x@A AREm1ta aR =R9=R"@q4@ C"S!tkcb3b½ B@jB@x@A$AjE쀈2 jaj =jl=j @@~$( @"EB0B J$B B@JBF@x@AAJEzue=F/ =o{% F"@@ ) @6 tkcbb bc B@aB@x@A$AEdvab =j1$w`=j^"@"@,JJ  B@aRBc@x@A ARE۠@Y FaR = {=FR"@ހ@ S% ""0S" Dfb{݀b  B@jB@x@A$AjExbjaj =j!=j%-(+cU@@~BB B@JB@٢o85n@x@AAJERyJe=Fz =@X{% "@XS@ @4 YBAb e! b  B@`B@x@A$A E zab =j̀=" @%̀@ m'$ JˠJ@0 B@aRBc@x@A ARE{aR aR =R`=!@@}@~B;bsڀb j< B@jBc@x@A$Aj=Ezb  A> =@݀=(%p%?@U@$@B A B@B@@x@AAJBEOJ@=C=aFrD =@T{% "E@>P@  c AFb  G B@`B !B@x@A$AjHE abI =jʀ=j J@ɀ@ m!j @KJȠJ cL B@aRB/@x@A ARMEaR FaRN =RN="O@@<"KX S!t$wBPbb½Q B@jB5n@x@A$AjRE=j ES =j@=%-(%T@@$UBh? J) V B@JB@x@AAJWE eX=$ |zY ={% %Z@@c yA[b>b \ B@aB5n@x@A$A]E)bb^ = `Jt`=%pj+c_@r@,`JJ ja B@B@x@A ARbE7+a   aRc =R2=R"d@3.@ c "~$`/0N$eb-b jf B@jBQ@x@A$AjgE j  ajh =jE=%-!i@@ƹ/ @"@BjBB Jk B@JB@x@AAJlEDb J  em=ŤF5nn =<{% "o@@$X _Apb ! 륱q B@aB@x@A$ArE]ac  bs = ``=j^"t@^@,uJJ >v B@B@x@A ARwEԀaRx =R܀=R"y@׀@ yy"!0 S!ߑB'zbPbA;{ B@jB/@x@A$Aj|E_b faj} =j=j%p(%~@J@~B B$ c B@JB@x@AAJEKJ >=g^F =Q{% "@ M@ 5n! @ S,bLb bc B@aB5n@x@A$AjElacb = `ǀ=%@ŀ@,JgJ F B@B@x@A AREz~aR FaR =R+="@~@ cÿBBBDbꀀbĩ B@jBL6@x@A$AjE:aj jaj =j==j%-(%@<@ $( @"@BBEB B@JB@x@AAJE Je= =x{F% "@@ \eAb#b ! 륱 B@aB@x@A$AEbb =j8q`=j^"@o@,JJA# B@aRB@x@A ARE(a aR =R/=R"@ +@ * q x|aYBb*b( B@jB@x@A$AjE〈jaj =*="@@~BB$c B@JB@: @x@AAJE)e=Fs =@!{% @c@ , Bb ! B@`B@x@A$AEZab = ``=^%@B@ myJJ  B@B/@x@A ARE aR =Rـ=R%@Ԁ@ o$F'E! C!"$_xaMBb9b jc B@jB o* @x@A$AjEDb jaj =j̐=(%-(+c@(@ !j @"@BBB B@JBc@x@AAJEHaJ~v Je=L[F =N{% "@ J@ y$F @6 CAbkIb  B@aB ",@x@A$AEQac !b =jĀ=^"@€@,JLJ r B@aRB@x@A ARE_{aR ""aR = =R"@g~@ 8|}{zy"r0 2FBb}b@&j B@B@x@A$AjE6j##aj =jj:=j%-(%@9@ B&B$  B@JB @x@AAJEl $$e=d3`A =P{"@@@a!K Abb! b B@aB@x@A$AEbc%&b =j%n`=j)%@l@,JkJA# B@aRB@x@A ARE%a ''aR =R,=FR%@(@= sC#"pBDBb|'b@&j B@jBژ@x@A$AjE j((aj =j=%-%@l@$BB J$  B@JBc@x@AAJE))e=F ={% "@I@ c FAb  B@aB/@x@A$AEWa*+b =j`=jj%@@,JJA#F B@aRB5n@x@A ARE F,,aR =RYր="@р@Ex(o!!%~ Ӕ~P~ a:|bb j B@jB o@x@A$AjE)b j--aj =j=%-(%@@$Bq B@JB@!K@x@AAJEEJ..eV  1XF =@=K{ @F@ " @lbKb bc B@`B@x` =A @E6a/0b =je=j$%@ſ@ m' @BJ1J@ B@a Bc@x@A AR EDxaR F11aR =R=F!R(o @D{@y =#"7ː7Vdp8a9D bzb j B@jBF@x@A$AjE3aj j22aj =jR7=j%-j%@6@~n$( @K BBBc B@JBc@x@AAJEQ J33e=ҤF} =E{% "@@/ hAb ! 륱 B@aB 'Z@x@A$AEتbc45b =j k`="@ki@,JhJ(R B@aRB@x@A ARE!a 66aR =R)= !@$@c3ѡt!Ѯљt8/mB"bYbj# B@jB@x@A$Aj$El݀ j77aj% =j=j%p(%n&@Q@~'B߀ B$ ( B@JB@x@AAJ)E88e*=t`=+ =㞀{% %,@.@w @1a0" ( -A-bb bc. B@aB@x@A$A/EyTajc9:b0 =j`="1@@,2JtJA#c3 B@aRB@x@A AR4E F;;aR5 =R.Ӏ=R"6@΀@%C4$3a 7b̀b j8 B@jB o@x@A$Aj9Eb j<EBaJ J==e?=UF@ =H{% "A@C@ L6/ @+c   Bb0b! b C B@aB "c@x@A$ADE >>bE =ja$j.F@ @,GJwJ H B@aRBz>B@x@A ARIE DE R??aRJ =RK=R"K@@ S,-<]L"2DLbb jM B@jB @x@A$AjNE(ub j@@ajO =jx=j P@ @$QBmw B$ cR B@JB,@x@AAJSE0JAAeT=FU =6{F% F"V@1@ BWbKbjX B@aB@x@A$AYE6cBCbZ =j_aj^%[@@,\J-JA#] B@aRB@x@A AR^ECca/DDaR_ =Rj=FR"`@Pf@  c "1 Qy@abeb jb B@jB4@x@A$AjcEaj EEajd =j:"=%-(+ce@!@ nfB B g B@JBc@x@AAJhEQ JFFei=ҤF j =={ k@ۀ@ c @lb  m B@aB,@x@A$AnEؕb5nGHbo =jU`=j^%p@^T@,qJSJA#r B@aRB@x@A ARsE a IIaRt =R=R%u@@ csљх$ lbDvbYbq w B@jB@x@A$AjxElȀ jJJajy =jˀ=j%-z@W@~{Bʀ Bc| B@JB@x@AAJ}EKKe~=tF =㉀{% "@-@ y! @6 wbb b B@aB@x@A$AEy?acLMb =j=%@@,J|J  B@aRBc@x@A AREaRy FNNaR =RH="@@ ;౟B$ iSEbb j B@jB$X@x@A$AjE raj jOOaj =ju= @t@~BIB B@JB@5@x@AAJE-JPPe=@| =@3{% @.@  c ͡Bb0.b ! B@`B@x@A$AE逈cQRb =j1a^%@@ m!j @JJ(>, B@aRB@x@A ARE(`a FSSaR =Rg=!@0c@ yCE"% $Xbbbj B@jBy@x@A$AjEjTTaj =j7=j @@ ) @"DBB$B B@JBc@x@AAJE6 UUe=F =.݀{% @p؀@ y! @c c$Xb ! b B@aB@x@A$AEbcVWb =jR`="@GQ@,JPJA#j B@aRB@x@A ARE a XXaR =Rx=R+c@ @ cb½ B@jB@x@A$AjEQŀ jYYaj =jȀ=(%-(6@1@~$( @ťBǀA$B B@JB@x@AAJE׀ZZe=XF =̆{% "@@ c bsb 륱 B@aB @x@A$AE^E a'Bb_bf B@jB@x@A$AjEjccaj =j=*(+c@u@ rBB J B@JB@@x@AAJEԀ dde=F =@ڀ{% (@VՀ@ 5\ @ Ab ! b B@`B@x@ =A @Ebcefb =jO`=j @ N@ m!j @JMJ@ c B@a BB@x@A AREa ggaR = j="@ @ $F!R?7?E;Bb'b½ B@B@x@A$AjE5€ jhhaj =jŀ=j @#@!j @.`AJBĀ B$B B@JB @x@AAJE}iie==FB ={F% @~@ @SAbXbj! bW  aBB@x@A$AEC9ajkb =j^=j$j"@@,J*JA" B@aRB@x@A AREPaR FllaR =R =R(o@]@ @E aUB bȲb½ B@jB @x@A$Aj Ekajmmaj =jko=j%-+c @n@ B'B$ / B@JB@x@AAJE^'aJ nne=ߤF$X =V-{% "@(@ c \Ab !   B@aBc@x@A$AEopb =ja @w@,J㠠JA# B@aRB@x@A AREYa qqaR =Ra=R"@\@ AE` bfb½ B@jBh@x 9A$Aj @`Eyjrraj! =j=j%-(%"@Z@ #B B$ $ B@aB@x@AAJ%E sse&=Fc' =ր{% "(@;Ҁ@ y! @c e)bрb b* B@aB (@x@A$A+Ebctub, =jL`="-@ K@,.JuJJ / B@aRBy@x@A AR0Ea vvaR1 =R; =R"2@@>"tD3bb4 B@jB~ c@x@A$Aj5E jwwaj6 =j€=j%-(%7@@~$( @.`@B8Bb B9 B@JB@x@AAJ:Ezxxe;="F< ={"=@{@ 5\%N V&A>b=b b륱? B@aB "@x@A$A@E(6acyzbA =j\="B@@,CJ+J D B@aRB@x@A AREE5aR F{{aRF =RѴ=R%G@-@ ъtfakBHbbĩcI B@jB @x@A$AjJEhj||ajK =j8l=(%pL@k@ rMBjB JN B@JB@\!KB@x@AAJOEC$J}}eP=ĤFQ =@7*{% "R@%@ ! @( {BSb ! b T B@`B@x@A$AUE߀~#[AV =ja^%W@X@ m'%yXJĝJ Y B@aRBy* @x@A ARZEVa aR[ =Rw^=R"\@Y@F># Bё;TOaCbB]bCb@$j^ B@jB& BJ@x@A$Aj_E^aj2 jaj` =j=( a@D@~$( @ "AJbB J$Bc B@JBc@x@AAJdE Jee=iFvf =Ӏ{% g@!π@F Ahb΀b ci B@aBb@x@A$AjEkbbk = `I`=%pj"l@G@,mJ^J Fn B@B@x@A ARoEya aRp =R)=R%q@@ c3JECE$a*PBrbbĩs B@jB@x@A$AjtE jaju =j=%-1v@⾀@ wBCBJx B@JB@x@AAJyEwb Jez=Fz{ =r}{% "|@x@ ! @c ɂ}b"b b ~ B@aB (@x@A$AE 3ab =j==j^%@@fcJ J I B@aRB@x@A AREaR aR =@α=R"@&@* >C KcD\t@ ENDbb½ B@B(c@x@A$AjEejaj =j1i=j @h@ r$( @"AJBgB J B@JB@x@AAJE(!aJ e=F ='{% @e"@ $F @+c Ab ! bc B@aB "@x@A$AE܀b =ja"@!@,cJJ c B@aRB@x@A ARESa aR =Rx[=%@V@>SEEF@ aQb4b½ B@jB @x@A$AjECajfaj =j=%p(+c@@ BJ$  B@JB@!K@x@AAJEʀ e=J݄Fw34P]L =@Ѐ{% "@̀@ ! @ LQbeˀb b, B@`B @x@A$AEPbb =jF`=^%@D@,JOJA#4`  B@aRB@x@A ARE]@Y`~ 28 =R aRR"@^@ @c6GEEH  DDb j B@jB-$Bc@x@A$AjE一jaj =jd=j%-(%@@~$( @"@BB B B B@JBc@x@AAJEktbw e=F5n = Àgz{% "@u@ c Abb! 륱 B@`B@x@A$AE/a1}$^ =j="@p@,JJ (R B@aRB/@x@A AREaR aR =R=R"@@ s$EIEABbcbj B@jB@x@A$AjEbjaj = f=(%p(%@qe@ BdB J$ c B@B@x@AAJE aJe=0| = ${% `3B@N@ ! @ /Ab ! bc B@aB@x@A$AEـcb =ja^(f@@,J~JA# B@aRB@x@A AREPa aR =RJX=R%@S@ ,"8"TaBbb@$j B@jBxc@x@A$AjE' ajfaj =j=j%-(%@ @ $( @"@BBk J$B B@JB@x@AAJE e=/ڄF =̀{"@ ɀ@ c /CAbnȀb B@aB/Z " .X@x@A$AE5bcb =jRC`=j/"@A@,J J >c B@aRB@x@A AREB@Y aR = +aRFR%@F~ yT"e" ĜBbbz B@B @x@A$AjEɵ, jaj =jQ=%-%@@ r `@ B BJ B@JB@x@AAJEPqbw Je=ѤF =@w{% "@r@ y! @c `nAb  bc B@aB]L@x@A$AE,acb = `$=j^%@a@,XJ (R B@B/@x@A AREaR -2h =!J="@@ y,"~ p$Xbdbj B@jB@x@A$AjEk_aj2 jaj =jb= @C@~$( @"D Ba J B@JB@x@AAJ E=@T Je =r-|5n = {% @,@ * c $Xbb! 륱 B@aB(= c@x@A$AExրb =ja @@,JsJ! B@aRB@x@A AREMa aR =RCU=R(o@P@ c%nc,"aSDfb b½ B@jBc@x@A$AjE aj jaj =j =j%p(+c@ @~BPB$  B@JB@x@AAJ!E Je"=ׄF# =ʀ{% "$@ŀ@ c T%b/b ! XF& B@aBc@x@A$A'Ebb( =j@@`= )@>@ v' @*J J IJ+ B@aRB@x@A AR,E'\aR- =@=!.@3@ S!V@'/bb 0 B@@]B o@x@A$Aj1Eb faj2 =j>=j%-j%3@@~4BB$ 5 B@JB@x@AAJ6E5naJ> Je7=F8 =!t{% %9@ko@ $F @ ,:b ! b; B@aB "@x@A$A<E)acb= =j=">@N@,c?JJ @ B@aRBc@x@A ARAEɠ aR aRB =Rs=R"C@ɣ@ )%K >ъaoDDb5b½E B@jB @x@A$AjFEP\ ajfajG =j_=j%-(%H@0@ !j @űIB^J$BJ B@JB; @x@AAJKE aJ eL=W*|M ={"N@@/ AObrb P B@aB@x@A$AQE]ӀbR =j a$"S@@,TJ`JA#cU B@aRB@x@R =ARV @`EjJ a aRW = ?`=R=R%X@oM@ ъ 6CYbLb fZ B@B}@x@A$Aj[Ejaj\ =jm =(%-%]@@ ^B-B, _ B@JB@&B@x@AAJ`Ex ea=FFb =@hǀ{% "c@€@ c Ixdbb ce B@`B@x@A$AfE|bbg =j=j h@@ m!j @iJsJA#j B@aRBB@x@A ARkE8aR aRl =R>@="m@;@ $F!Rc"daDnb:b½o B@jB@x@A$>pE jajq =j=%-(%r@@$sBXB Jct B@JB@x@AAJuEJev=}|tw ={% %x@а@ @" @J5Ayb/b b z B@aBc@x@A$A{Ekacb| =j:+`=j$j+c}@)@,~JJA# B@aRB@x@A ARE' aR =R=R"@7@ "! ?@"EP@2Bbb B@jBc@x@A$AjEb jaj =j6=j @@$BBJ B@JB@٢oBc@x@AAJE4YJe=F/ =@9_{% F"@rZ@ ) @ Bb e! b B@`B !@x@A$AEa$Xb =jԀ=j$j%@JӀ@ m!j @cJҠJ c B@aRBc@x@A AREɋaR FaR =R=F!R"@Վ@c?""9":albAb½c B@jBc@x@A$AjEOGaj jaj =jJ=j @2@~' @"DBI B$B B@JBc@x@AAJEJe=W| ={% @@  Je=F =t{% (@@ ) @c W.Abb ! b B@aB@x@A$AEga b =jl="@j@,cJjJA# B@aRBQ@x@A ARE#RaR =R0+=R"@&@y?3^BRp 3QBb%b j B@jBL6@x@A$AjE aj =j=( @@ƹBLB J$ / B@JB@x@AAJEb Je=h ={% @Λ@ @ Bb/b! by B@aBy@x@A$AEV ab =jL!`=j.@@,JJA# B@aRB@x@A ARE'͠@Y aR =RԀ="@#Ѐ@ C+]%~ÏBbπb½ B@jB !/@x@A$AjE"b jaj =j6=j%-( =R$=R"?@O @7+o IFEa kB@bbA B@jB !y@x@A$AjBE؀ jajC =jf܀=(%-(%D@ۀ@~EB"B JF B@JB@x@AAJGE\5eH=ݤFcI = ÀQ{% "J@@ ! @AKb ! b L B@`B "@x@A$AMEO6abN =j7`=j$j%O@b@,PJ J Q B@aRB@x@A ARRE FaRS =!J΀=R"T@ɀ@!!I  I"BUbeb cV B@jB @x@A$AjWEw8b jajX =j="Y@R@ $(%"AJZBBc[ B@JB@x@AAJ\E=9aJye]=PF^ =D{% F"_@??@ A`b>b ca B@aB "@x@A$AbE,bc =j:aj$j"d@@,eJJ f B@aRB@x@A ARgEp;aaRh =RKx="i@s@ IC!""Qn}Bjbb jk B@jB@x@A$AjlE,<jajm =j/=%-+cn@.@$oBEB J) p B@JB \$@x@AAJqE(aer=%Fs = {% %t@@ ': @ ʏAub@b ! bv B@`B@x@A$AwY`DE&=bbx ==Sc>`=j y@a@ m2 @ l@ ! ( ARzJ!J { B@aRB@x@A AR|E4?a  A} =R!=R"~@4@ IF"4xBAjbb B@jB oc@x@A$AjE jaj = Cـ=%-(%@؀@ Rr$( @(@BBB$B B@B @x@AAJEA@@==ƤFb =={% "@@ {`b ! 륱 B@aB@x@A$AjELAayb =j B`=%pj"@[ @,J JA# B@aRB@x@A ARE FaR =Rˀ=R"@ƀ@ "}{zy~aDfbEb j B@jB@x@A$AjE\Cb j G =j삀=j%-%@G@~B B$ c B@JB@x@AAJE:DJe=dMF =@{% "@ <@ y! @c VAb;b bc B@aBy@x@A$AEj b = `Ea"@@,J`J B@Bc@x@A AREwmFa F  aR = $u= @wp@ c"0 S"~ű$`>Bbobĩ B@B@x@A$AjE(Gj  aj =j,=j%-(%@+@ $( @(@BBJB B@JB@x@AAJE   e=F =y{F% %@@ / @ Ab b ! b륱 B@aB W.X@x@A$AE H  b = ``=j^"@ @,JwJ B@B@x@A ARE[IRaR =RAc=R"@^@ c"S!aTפb]b j B@jB@x@A$AjEJaj faj =j="@@~ƹBe J$c B@JB@x@AAJE J >=F$X =؀{% @Ӏ@ y! @K ݌b;b b B@aB@x@A$AjE&Kb&b = `ܒ=j%@>@,JJ B@Bc@x@A AREILaR aR =RvQ=R%@L@y@%   aHob-bĩ B@jBy@x@A$AjE4Maj2 jaj =j=j%-(+c@@~$( @"@BBd B B@JB@x@AAJE Je=ƤF* =ƀ{F% "@@, :AbVb! c B@aB@x@A$AEA|Nbb =jvENހ f**aj? ==j%-(+c@@)@~AB BB B@ B@!K@x@AAJCEՙ^++eD=ݤF`E =@ɟ{"F@@ .rAGbqb H B@`B !@x@A$AIE\U_a| ,,bJ =j Z=%K@lX@,LJWJ cM B@aRBh@x@A >NE`aR R--aRO =R=R"P@@ o$F'E!c11E%PaXBQbVbĩR B@jBc@x@A$AjSEì j..ajT =jπ=( U@N@~!j @"AJVB΀ BW B@JB@x@AAJXEa//eY=FZ ={% :"[@+@ A\bb c] B@aBc@x@A$A^EwCbay01b_ = `c`=^"`@ @,aJuJ jb B@B@x@A ARcE F22aRd =R'€=R"e@@s >utBfbb@(jg B@jBz oc@x@A$AjhE vdb j33aji =jy=(%p(+cj@x@kBWBBl B@JB@!Kc@x@AAJmE1eJ44en=DFto =@z7{% "p@2@6 @ qAqb-b b r B@`B ! @x@A$AsE 55bt =j=)j%u@@,vJJ w B@aRB@x@A ARxEf66aRy =R\=R"z@@ c4:"azB{b#b j| B@jB@x@A$Aj}E&dgaj2 77aj~ =jg= @@~ncBff J B@JB@x@AAJEhaJ@Y J88e=F =%{% F"@ @ \BbLb B@aBc@x@A$AE3ۀy9:b =jdia^%@ƙ@,J2JA#r> B@aRB@x@A AREARja ;;aR =RY=R"@QU@   ƣ x BbTbj B@jB@x@A$AjE kaj j<?b =jDm`= @TC@@Sf2 @" /JBJrR B@aRB@x@A ARE @@aR =@naR!@~ c $ $@ aBbZb j B@B @x@A$AjEi, jAAaj =j=j @X@~rB B B@JB@x@AAJErobw} JBBe=uF/ =x{% @)t@ pBbsb! B@aB@x@A$AEv.pacCDb =j=G@p ^+c@ @,cJuJ  B@aRB@x@A AREqaR_EEaR =R9= @@ 1t 5n%Bbb j B@jB@x@A$AjE araj fFFaj = {d= @c@~BWB F B@JB@x@AAJEsJGGe=/|* ="{% @@ OBb-b `B B@aB@x@A$AE؀HIb =jFta^%@@,JJA#= B@aRB@x@A ARE%Oua FJJaR =RV=R1@1R@yV"BbQbĩ B@jB@x@A$AjE vjKKaj =j0=(%-( B@Bc@x@AAJ?ENb Jcce@=ӤFyA =>{% "B@@ ^ACb ! D B@aBc@x@A$AEEZadebF =j `=^%G@k@,HJJA#I B@aRB@x@A ARJE ffaRK =Rـ=R"L@Ԁ@ )'ő_7K A#i1%%"kaBMbRb jN B@jB@x@A$AjOEhb jggajP =j=j Q@M@!j @"AJRBB$BcS B@JB@x@AAJTEHJhheU=p[FV =N{"W@&J@ )%N+c 5AXbIb bcY B@aB @x@A$AZEvaBijb[ =jĀ=j$"\@À@,]Jm JA#^ B@aRB@x@A AR_E{aR FkkaR` =:`=FR%a@~@ 3%&% 5(Bbb}`fc B@jB; @x@A$AjdE 7`llaje =j:=+cf@9@$gBVB Jh B@JB@x@AAJiE mmej=|*k ={% F"l@@ ': @( Amb-b! b n B@aB m2$X@x@A$AoEbcnobp =j>n`=j^%q@l@,rJ J s B@aRB|5n@x@A ARtE%%a ppaRu =R,=R"v@-(@ CkZ'%1ia6wb'b½x B@jB~ ,@x@A$AjyE jqqajz =j =j {@|@ $( @"D|BB$Bc} B@JB !K @'@x@AAJ~E2rre=FB =@@#{% @n@ c 2b ! c B@`B@x@A$AEWastb =j`=j @H@ m, @JJ  B@aRBc@x@A ARE΀@Y FuuaR =R}ր=F!@р@ c S$4% cʖDfbCb½ B@jBc@x@A$AjEMb jvvaj =j͍=j%-j+c@*@~B B$ , B@JBc@x@AAJEEJwwe=UXF@ =K{% (@G@ B! @c ndAbpFb b, B@aB 'Zc@x@A$AE[ayxyb =jv="@ֿ@,$XJBJ (> B@aRB@x@A AREhxaR FzzaR =R= @d{@ ccKc "u"BP=Bbzbj B@jB @x@A$AjE3j{{aj =j7=j%-(%@6@ $( @ūcB;B B@JB@x@AAJEv> ||e=F =f{% %@@ y) @6 Abb ! b륱 B@aBc@x@A$AEby}~b =j(k`="@i@,cJhJA#F B@aRB@x@A ARE "a\aR =R)=R"@%@ s-1aS+b$b j B@jB@x@A$AjE݀faj =j!=(%p(!@~@~rBB$ c B@JB@x@AAJEb e=F ={% "@S@ c O*b ! c B@aB@x@A$AETab =j`=^(f@-@,JJ  B@aRB@@x@A ARE aR =RWӀ=R"@΀@ tBtZ)` a+ZDb$b  B@jB@@x@A$AjE2b jaj =jŠ=(%-(%@ @ B~  B@JB 5@AAJ @EBJe[`<=:UF ==H{% "@C@ c AbUb  B@abB,@x@A$AE@b =jcaj%@Ƽ@@S, @`# !*J2J (Jc B@aRB@x@A AREMua FaR =@}="@ax@ v$F!R!?71  1MaBbwbf B@BQ^@x@A$AjE0jaj =jP4=%-(%@3@$BB J)  B@JB@x@AAJE[ e=ܤFu, =_{% %@@ Ab !  B@aB@x@A$AE᧥bcb =jg`=j$j%@\f@,JeJA# B@aRB@x@A AREa aR =R&="@"@  M+[ M`1]]a66Bbo!b½ B@jB@x@A$AjEu jaj =j݀=j%-%@D@$B܀ J$  B@JBc@x@AAJEe=}`= ={F% %@9@ ) @6 A\b b B@aB*@x@A$AEQajcb =j`=j @@,JzJA# B@aRB@x@A ARE FaR =R@Ѐ=R" @ˀ@ ]`"a`1B bb j B@jB@x@A$Aj Eb jaj = =j%-(%@@~ƹ' @(@BBSB B@B@x@AAJE?Je=RF =E{% "@@@ ! @+c Ab:b ! b륱 B@aB .X,@x@A$AE$cb =jUa @@,J#J  B@aRB@x@A ARE2ra FaR =Ry=R"@Bu@ y%"$4aSBbtbĩ B@jB$_@x@A$Aj!E-jaj" =jE1=j%p(%#@0@ $( @ť$BB$Bc% B@JB@x@AAJ&E?/e'=F( =8{F% ")@@ y c wA*bߡ ! 륱+ B@aB@x@A$A,EƤb,b- =d`=j^".@Uc@,/JbJ 0 B@aRB@x@A AR1Ea+ aR2 =R#=R"3@@ %u$EasB4bHb5 B@jB@x@A$Aj6EZ׀ jaj7 = ڀ="8@=@ 9BـJ$ : B@B@&B@x@AAJ;Eᒳb 4E Je<=bF= =@՘{% >@"@ y! @c /B?bb b @ B@`B,@x@A$AAEhNabB =j`=^%C@ @, DJ_JA#E B@aRB@x@A ARFEuŀ aRG =R*̀=R%H@Ȁ@ BEaS;BIbǀbfcJ B@jB@x@A$AjKEb jajL =j=(%-(+cM@烀@ r$( @"@BNBHBJO B@JBc@x@AAJPE` sb5b t B@Bx @x@A$AjuE?Ԡ@YW jajv =j׀= w@+@ xBր ,y B@JBc@x@AAJzEƏe{=GF| ={% F"}@@ ) @ e~bbb b, B@aB "y@x@A$AEMKab =jv `=j^%@ @,JCJ `R- B@aRB@x@A AREZ @Y FaR =Rʀ=R"@jŀ@cBOdENpuVGMd@ƎbĀbj B@jB5n@x@A$AjE}b jaj =ji=j%-(+c@ŀ@$B%BJ)  B@JB@x@AAJEh9aJ@Ye=F =X?{% "@:@c mAbb !  B@aBB@x@A$AEyb =j a%@m@,JٲJA#r B@aRBc@x@A AREka FaR =Rs="@o@ #p(S`$K %aBbtnbj B@jB@x@A$AjE'jaj =j+=%-(C@b*@ cB)B$ g B@JB@)y@x@AAJE e=F/ =@{% "@C@ y c ,VAb !  B@`B@x@A$AEbcb =j^`=^%@#]@ m!j @J\JA# B@aRB@x@A AREa aR =RS=!@@ 3_+%%aGBbb½ B@jB/@x@A$AjE$р jaj =jԀ=j%-j%@@~BdӀ B$  B@JB@@x@AAJEe=,`=@ =@{% %@區@ ! @+c uAbGb b B@`B@x@A$AE1Hajcb =jp`="@@ mJ ǭ^aD)Dfbb j B@jB@x@A$AjEejaj =jNi=%pj+c@h@  BB $ y! B@JB@c@x@AAJ"EL!Je#=T$ =@@'{% 4 %@"@ hv/G?A&b ' B@`B ! @x@A$A(E܀b) = ``a^%*@j@,+J֚J , B@B@x@A AR-ESa aR. =R[=R"/@V@  Ǭe "aUB0bPb½1 B@jB@x@A$Aj2Egjaj3 =j=(%-(%4@N@ n5B J6 B@JBc@x@AAJ7E e8=o݄Fzc9 =Ѐ{ :@+̀@ pA;bˀb Z,< B@aB@x@A$A=Eubt b> =j=j ?@}@,@J鈀JFA B@aRB@x@A ARBEAaR RaRC =RI="D@D@ c$@"a#BEbgbjF B@jB o,@x@A$AjGE jajH =ja I@K@ rJB JK B@JB@c@x@AAJLE JeM=|w  y`\yN = ){% :cO@D@ b5\ @ +c hBPbb b Q B@aBc@x@A$AREtb,bS =j4`=j T@3@ m!j @UJ2J *RV B@aRB@x@A ARWE뀈@Y FaRX =RT="Y@@ tP. T@oBZbb j[ B@jB@x@A$Aj\E$b2 jaj] =j= ^@ @~_Bh J` B@JBc@x@AAJaEbaJ Jeb=/uFycc =h{% d@c@ ! @6 VBebFb! b f B@aBe 5@A$Ag @E1abh =jTހ=%pj1i@܀@,jJ J@ݥ(Rk B@a B@x@A ARlE>aR aRm = 휀=Rcn@G@ UtQ.v%`J\Bobb p B@B@x@A$AjqEPajajr =jMT=j s@S@r$( @`" "AJtB B$Bu B@JB@x@AAJvEL aJ ew=ͤFx =D{% F"y@ @ y) @+c  Azb ! baai{ B@aBy@x@A$A|Eǀ Z b} =ja ~@]@,JɅJ R B@aRB@x@A ARE>aRA@aR =@F=R"@A@ PPÔPuP BbTb@&4j B@B@x@A$AjEg@Y jaj =j=j%-(6@L@~B B$ y B@JB@x@AAJEb Je=oȄF, =һ{% "@%@ m @+c JnAbb! ba"  B@aBB@x@A$AEtqacb =j1`="@/@,JgJA#R B@aRB]L@x@A ARE@Y aR =R5=R"@@ yuPdq[Ǯt@_Bbbj B@jBb@x@A$AjEb jaj =j=j%-(%@@~BMB  B@JB5n@x@AAJE_Je=rF ={e{"@`@ c rAb+b  B@aB @x@A$AEb =j=.@@,JJA#(> B@aRB$X@x@A AREր FaR =RJހ=R%@ـ@  X.Bbb j B@jB,@x@A$AjE#aj =j=(%-@@rBo J$ B@JBc@x@AAJEMJe=Fy =S{*"@N@ BbFb  B@aB@x@A$AE1 b = ` =j @- @,J J B@B@x@A AREĀ aR =Rj̀=R"@ǀ@ o$F!!C%  t?Nb,bĩ B@jBB@x@A$AjE>aj = ΃=j%-(+c@,@ !j @(@BB  B@B@x@A>E;Je=ͤF =A{% "@=@ c ZAba{% " @9@ ! @(  bFb B@aB@x@A$AE1  b =jaa^%@ò@,J/JA#1R B@aRB@x@A ARE>ka   aR =Rr=R"@Fn@'ECAt.'t 1a^]Lbmb½ B@jB@x@A$AjE&aj  aj =j1*=( @)@$B(B J B@JB@2a @'@x@AAJEK Je=ͤF, =@@({%p!@@  c ]L"b ! c# B@`B !$X@x@A$A$Eҝbcb% =j]`=j$j%&@Y\@ m!j @'J[J ( B@aRB@x@A AR)EaaR* =Rx="+@@ St@% P"u@aJ[,bE aR FaR? =R =R"@@ @ Cc"t! QBAbwb jcB B@jB y@x@A$AjCE jajD =j€=j%-1E@a@ $( @ FBBG B@JB@!Kc@x@AAJHEz b JeI=H|J =@{% "K@C{@c ALb Z 륱M B@`B,@x@A$ANE5 bO =j4:= P@8@ mcQJJA#cR B@aRB@x@A ARSE@Y aRT =R=R"U@@ ,s*% "¯q@4BVbb½W B@jB@x@A$AjXE ajY =j =(%p(%Z@j@ [BЮB,J\ B@JB@x@AAJ]E#hJe^=+6_ =n{% "`@_i@/C  Aab  b B@aB@x@A$AcE#abd =j=^.e@(@,fJJ (Rg B@aRB@x@A ARhE@ aRi =Rk=R"j@@ - L6kb+b jl B@jB@x@A$@mE>Vb|ajn =jY=( o@$@$pBX Jq B@JB@c@x@AAJrEaJ   es=F$|t =@{% u@@ L6vbab^! w B@`B@x@A$AxEK̀F!"by =jaj cz@⋀@ mc{JNJA#| B@aRB@x@A AR}EYDa ##aR~ =RK="@QG@ ,  t+hD"aHobFb j B@jB@x@A$AjE$$aj =jpaj @@ cB,B$  B@JB@x@AAJEf %%e=F =J{F @@ L6! @ Bbb B@aB 'Z"@x@A$AEvb,&'b =j7`=j @t5@,J4J F B@aRB@x@A ARE ((aR = =FRc@ @ ÔPtQ wbvb j B@vB@x@A$AjEb j))aj =j=%-1@l@$BͫB B@JBy@x@AAJEeJ**e=wF =j{%p"@Ef@ b   B@aB@x@A$AE a+,b =j=j^1@߀@,J}ޠJA# B@aRB$X@x@A AREaR F--aR =RL=R"@@ $~01{Dbb½ B@jBL6@x@A$AjE#Sj..aj =jV=j%-(%@ @ ) @"@BBoU Bc B@JB@x@AAJEJ//e=+!| ={% "@@ ! @c &AbEb b륱 B@aBc@x@A$AE0ʀ01b =jQaj @@,JJ  B@aRBc@x@A ARE>Aay 22aR =RH=FR"@ND@ 88$ `a$`q5n6bCbA;j B@jB| ,@x@A$AjE j33aj =jL aj%p(%@ ~ƹ$( @ťBB B@JB@x@AAJEK  J44e=̤F{ =7{% "@@ 5\ @ b ! b륱 B@aBB@x@A$AEs!b55b =jrx="@v@,J>JA#Fc B@aRB@x@A AREX/"R66aR =R7=R"@i2@ !$F%?79: ; ?b =jˀ=j)j%@%ʀ@,cJɠJ R B@aRB@x@A ARE)aRRA@@@aR =@[="@@ !)!oҐ?>rybbj B@B5n@x@A$Aj_  `E">*jAAaj =jA=j @@!j @"DBc@ Bc B@aB,@x` =AJ @E BBe=* | ={F% F%@@  m bEb! c B@abBL6@x@A$A E0+bcCDb =jcu,`=j$j" @s@,J/JA#{R B@aRB@x@A ARE=,-a EEaR =R3=FR"@M/@DA%@r@ Dfb.bA;j B@jB@x@A$AjE jFFaj =jP=%p+c@@ nB BAJ$  B@JBc@x@AAJEK.GGe=̤F =?{% "@@ c >Ab ^! , B@aBy@x@A$A E^/aHIb! =j0`=^%"@X@,#JJ $ B@aRB@x@A AR%E FJJaR& =R݀= '@؀@,DBp}pB(bcb j) B@jB@x@A$Aj*Ef1b jKKaj+ =j=j%-(%,@S@~-B B$ . B@JB@x@AAJ/EL2JLLe0=n_F1 =R{% %2@(N@ @W pA3bMb b4 B@aB@x@1 =A5 @Es3a,MNb6 =jȀ="7@ǀ@,8JrƠJ@9 B@a B@x@A AR:E4aRy FOOaR; =R<= <@@ c#% =ZB=bb j> B@jB@x@A$Aj?E;5aj jPPaj@ =j>=j%-(%A@=@~BBCBC B@JB@x@AAJDE JQQeE= |v,F ={% %G@@ v c AHb*b ! I B@aB@x@A$AJE6RRbK =j="L@@,MJJA#N B@aRBzWB @x@A AROEm7aRSSaRP =REu=R"Q@p@ c3<':%9aBRb bĩS B@jBB@x@A$AjTE")8aj fTTajU =,=( V@@ cWBf+J$ cX B@JBc@x@AAJYE JUUeZ=F$X[ ={% \@@ c {B]bEb c^ B@aBy@x@A$A_E09bVWb` =jU`:`=j1a@^@,,bJ#J nc B@aRB@x@A ARdE=;a XXaRe =R="f@Q@ DCT% p0{Bgbb@(jh B@jBy ! @x@A$AjiEҀYYajj =jHր=%-(+ck@Հ@ rlBB Jm B@JB@!K/@x@AAJnEK`=j v@\@ m!j @; wJJ rȭx B@aRB @x@A ARyE ]]aRz =RȀ="{@À@ S>aa|b[b j} B@jB @x@A$Aj~Ee|?b j^^aj =j=j @N@ B~ B$ c B@JB@@x@AAJE7@aJdE J__e=mJF =@={F% @/9@ y! @  b8bj^! bc B@`B@x@A$AEs`ab =jAaj%pj+c@@,JfJA#rR B@aRB@x@A AREjBa bbaR =Rr=R(o@m@ Dc?U"uvuw"agHoblbj B@jBL6@x 9A$Aj @`E&Cjccaj =j)=j%-+c@(@ $( @"@BBSB$B B@aB@x@AAJE dde='Fd =~{% "@@ |Ab*b ! 륱 B@aB@x@A$AEDbefb =j;]E`= @[@,JJ  B@aRB@x@A ARE"Fa ggaR =R=R"@"@ cs$ >"}l{ * lBbb B@jB @x@A$AjE jhhaj =jӀ=j%-(%@yҀ@ BB$ c B@JB@x 9AAJ @E/Giie=Fc =({% "@j@ y! @c Ab̡ bc B@abBc@x@A$AEFHacjkb =jI`="@9@,JJA# B@aRB@x@A AREĽ FllaR =R~ŀ=R"@@ y1U>2`a[BbDb jc B@jBF@x@A$AjEJyJb jmmaj =j|=j%-(%@'@~ƹ$( @ūcB{ B B@JB@x@AAJE4KJnne=RGF =:{"@ 6@ y5\%N+c JAbm5b b륱 B@aB @x@A$AEX5nopb =jLa"@简@,JSJ  B@aRB@x@A AREegMa FqqaR =Ro=R%@mj@ c 1/EA \?Bbibĩ B@@]Bc@x@A$AjE"Njrraj =jp&=(%p@%@ rB,B J B@JB@x@AAJEsހsse=F =o{% "@߀@ c Bbb B@aBc@x@A$AEObytub =j)ZP`=^%@X@,JWJ c B@aRBB@x@A AREQavvaR =R=R"@@ ґҐ"ґ@bbb j B@jB$X@x@A$AjÈ2 wwaj =jЀ=( @wπ@~ƹB΀B J B@JB@x@AAJERb Jxxe=FB ={% @P@ , eEb ! B@aB,@x@A$AECSayzb =jT`= c@.@,JJA# B@aRB@x 9A AR @`E {{aR =RT€=R%@@ c T<}0ґ`̠MBbb½ B@a$BQ@x@A$AjE/vUb j||aj =jy= @@ B{xJ$ ``JB@x@AAJE1VJ}}e=7DF =7{% `3b@2@ , BbRb  B@aB@x@A$AE=~b =jtWaj^+c @ԫ@ 2 @ J@J  B@aRB@x@A AR EJdXacaR =  l=R%@bg@ CqSqc}]Lbfb  B@B@x@A$AjEYaj aj =ja#=j%-(6@"@~nBB B@JB@x@AAJEXۀ> Je=ݤFy, =T{% "@܀@ $F @ Ab ! b  B@aB@x@A$AEޖZbcb =jW[`=%@iU@,cJTJA#nb B@aRB@x@A AR!E \aaR" =R=&#@@ZX @K DBtZ/va6N$b\b j% B@jB @x@A$Aj&Esɀaj' =j̀= (@D@ )Bˀ( c* B@JB@B@x@AAJ+E]b e,=~F- =@{% .@5@ W! @E/bb bc0 B@`BW@x@A$A1E@^b2 =jE=$j%3@D@,4JpCJA#y5 B@aRB@x@A AR6E aR7 =R_aR(o8@~ ,%UL@pBѹBB? B@JBc@x@AAJ@Es`bw JeA=AB =y{"C@Nt@ y c !D`  E B@aB@x@A$AFE.aa bG =j= H@"@,IJJ J B@aRB@x@A ARKEbaR aRL =RZ=R%M@@ ± e"Nb(b@%jO B@jB@x@A$AjPE/acjajQ =jd=(%-R@c@$SB[B J/l T B@JB@c@x@AAJUEdaJeV=7/|wW =@"{"X@@ y': @c BYbVb bcZ B@`By@x@A$A[E<؀b\ =j&eaj ]@@ m' @^JJA#c_ B@aRB{* @x@A AR`EJOfaaRa =RV="b@JR@,E[p `*BcbQb jd B@jB@x@A$AjeE gaj2 ajf =jY=%-g@ @~w$( @ūchBB Ji B@JBc@x@AAJjEWƀek=٤F$Xl =L̀{% %m@ǀ@ 7Bnb io B@aB@x@l =Ap @Eށhbbq =jBi`=%pj"r@e@@,sJ?J@t B@a B@x@A ARuE FaRv =RzjaRR"w@~ tt3p,cCBxbHbĩy B@jBc@x@A$AjzEr, jaj{ =j=%p,k|@W@ }BJ~ B@JB@x@AAJEokbw J@==~Fxc =u{% "@4q@ ! @ȅ#( :Abpb b B@aB@x@A$AjE+lab =j=j^%@@,,J{JA# B@aRB@x@A AREmaR aR =R/=R"@@ #1x|}䐁aBbb½ B@jB @x@A$AjE^njaj =ja=j @`@ $( @"AJB\B B@JB@x@AAJEoaJe=,|B = À{% @@ / @+c 5Ab;b ^! bic B@`B@x@A$AE!Հcb =jPpa"@@,cJJA# B@aRB@x@A ARE/Lqa aR =RS=%@;O@yE33L@$ IBbNb B@jB @x@A$AjErajF jaj =jB =%p(+c@ @ B BJ `, B@JB@@x@AAJE<À Je=ɤFqc =@0ɀ{% "@wĀ@ ! @ aO*b  b B@`B !5n@x@A$AE~sbb =j?t`=^%@b=@,J=R"@7:@ s0D𐁫6\aqb9b@$j B@jBy@x@A$AjE jaj =j9=j @@ BB B@JB@2aL6@x@AAJE =@{% ?@[@$X A@b ao! 륱A B@`By@x@A$ABEfaxbC =j&`=^"D@F%@ mcEJ$JA#aHRF B@aRB@x@A ARGE݀aRH =Rg=R%I@@ U:U@{BJb%b jK B@jB]L@x@A$AjLE e9=ÄF,: ={% ;@ݱ@ / @+c (A<b>b ! b= B@aB@x@A$A>E la zA? =jG,`="@@*@,AJJ rB B@aRBc@x@A ARCE.  qARD = =%E@N@ s$p#ќ`Su0BFbbjG B@jB@x@A$AjHEb j AjI =j-=%p(+cJ@@~KBB$ L B@JB@٢o/l@x@AAJME;ZJ`=N=ĤFQO =@+`{% "P@v[@ @9 AQb e! bR B@`B@x@A$ASEa* 1|%T =jՀ=^%U@AԀ@ m' @VJӠJA#W B@aRB@x@A ARXEόaR FARY =Rz=R"Z@ۏ@ ,+"HK/}/Na J[bGb@$j\ B@jB,@x@A$Aj]EVHj(p"^ =jK=(%-(%_@;@ `BJ B$ a B@JB@@x@AAJbEJ`=c=^|,d =@ {% "e@@ $F @+c &Afbyb bg B@`Bc@x@A$AhEc ^ Bi =jĀ=^%j@p€@@Sm!jkJJA#Jl B@aRBy@x@A ARmEz ARn =@=R"o@~@ b S "ѣE=pbn}bA;jq B@BF@x@A$AjrEq6j Ajs =j9=(%-(Ct@L@ ruB8 Jv B@JB@@x@AAJwE  `=x=|yy = 5{% "z@5@ ! @+c oA{bb b | B@aB@x@A$A}E~b B~ =jm`=j%pj%@ l@ mJukJ  B@aRB$X@x@A ARE$a !) =RA,="@'@ q $ÔP" aX2zbb! B@jB$X@x@A$AjE2 jAj =j= @@~$( @"DBSB B@JB C@x@AAJE`==F ={% #)`@3e@ל@ B$F @+c 8bb5b ! bc B@aB0!,@x@A$AE WB =A6`[=^"@$Z@ m!j%JYJ  B@B@x@A ARERAR =RI=!@@ c #  0 S`-M#Dfbb@' B@jB@x@A$AjE-Π@Y @u' =jр=%pj+c@@~BzЀ $  B@JB@١$@x@AAJE`==Fc =@{% +c@@ ': @+c ;AbPb ! b B@`Bc@x@A$AE;EaB = `r`=^%@@,J>J  B@B@x@A AREH@YA FAR =RÀ=R"@X@  Ô}@~$@Bbľb# B@jB o@x@A$AjEwb j'% =jK{=j%-(C@z@~$( @"@BB B$B B@JB@٢o!KB@x@AAJEV3J`==EF} =@J9{% "@4@ ) @1! @# +c hv/G?*Ab e! b륱 B@`Bc@x@A$AEB =ja"@w@ m!j$#j`4! !% ARJ㬠J  B@aRBb@x@A AREea FAR =Rm=R"@h@7!R(o~0abbb½ B@jB@x@A$AjEq!jAj =j$=(%-(C@Z@ `@ťB# J$ B B@JB@x@AAJE܀ `==yFy ={% "@7ހ@h b݀b c B@aB5n@x@A$AE~b !B =jX`=j$j"@W@,JmVJA# B@aRB@x@A AREa ";) =R7=R"@@!!Cu"w$`p##ED[Dbb j`b* B@jB@lc@x@A$AjE j#I[!j =j΀="@@ƹBf̀ J B@JB@x@AAJEb J$$`==FFQc ={% F"@Շ@c "'b5b!  B@aB " O@x@A$AE Bac%&B =j?`=j$j%@@ cJJ c B@aRBB@x@A ARE- ''AR =R="@5@ yw"ќ` `vw&HBbb½ B@jB @x@A$AjEtb j((Aj =j8x=*#@w@ rBvBJ$  B@JB@x@AAJE;0J))`==F =+6{% %@w1@ ! @  1Ab  bc B@aB "@x@A$AEc*+B =jaj @P@,cJJ B@aRB@x@A ARBB@`Eba F,,AR"R{j="@e@GYp #`B`̠+BbCb½ B@a$B@x` =Aj @`EVaj2 j--Aj =j!=%-(%@0@~$( @(@BB J B@aB@$@x@AAJ E J..`= =]Fzc =@߀{%  < @ۀ@ ) @ .Abxڀb b륱 B@`B@x@A$AEcb //B =)"=jj"@@ m!j @JJA#c B@aRB@x@A AREPR00AR =RX=F!@S@yG"ӔP`"Q=sBbbb j B@jB$X@x@A$AjEp j1#1 =j=%pj%@T@ B  B@JB@@x@AAJEǀ 22`= =F,! =@̀{%  <" @1ɀ@ y! @!`\y A#bȀbǝ! b $ B@`B@x@A$A%E~bc34B& =)C`=^%'@ B@,(JyAJA# @R) B@aRB@x@A AR*E 55AR+ =RA$R <, @~ c#K]œ ]wB-bb j. B@jB@x@A$Aj/E, j66Aj0 =)=( 1@@ n$( @"AJ2BRBB3 B@JB@x@AAJ4Eqbw J77`=5=F 6 =w{% 7@r@ m) @+c  A8b5b bc9 B@aB@x@A$A:E -a89B; =jN="<@@,=JJA#> B@aRB@x@A AR?E-aR ::AR@ =R׫=R <A @9@ G3uY"ѣKVu6*Bbb@(jC B@jBy @x@A$AjDE_j;; FE =)@,gJZJ >h B@aRB@x@A ARiEp@Yg DDARj =R+="k@@o!oSw 1`w pAlbbfm B@jB@x@j =Ajn @`Eb fEEAjo = ?`==%-(%p@䵀@ rqBCBJ) r B@B@x@AAJsE~nJFF`=t=Fu =vt{%  <v @o@ ! @]Awbb bx B@aB @x@A$AyE*GDB%z =).=^3{@-@,|J},JA#(} B@aRBx B @x@A AR~E倈 FHB!R =R;=%p@@ c"ќ`Su~`n!bbj B@jB !@x@A$AjEIIAj =j=j%-j%@@ $( @"@BB^B B@JB@x@AAJE\aJ> JJ`==Fh = Àb{%  < @]@F t b5b! 륱 B@`B "@x@A$AEaKLB =)P؀="@ր@,cJJ n B@aRB@x@A ARE-aR MMAR =R門=R < @A@  s` ` Yahobb½ B@jB@x@A$AjEJajF jNNAj =)bgb½? B@jB@x@A$Aj@E jttAjA =j+$=j%p(%B@#@$CB"B JD B@JBc@x@AAJEE: uu`=F=FG =.{%p"H@p݀@ @': @K }AIb ! bcJ B@aBc@x@A$AKE bvwBL =jW `= M@KV@,NJUJ O B@aRB@x@A ARPEa xxARQ =R{=R"R@@HÔPB_(+FBSb:b½T B@jB@x@A$AjUEUʀ jyyAjV =j̀=j W@0@~$( @ūcXB̀ B$BcY B@JB@x@AAJZEۅzz`=[=]F\ =̋{F% ]@@, [B^bwb 륱_ B@aB@x@A$A`EbAa{|Ba =jx`=j^"b@~,cJEJ d B@aRB@x@A AReEpy F}}ARf =R=FR%g@x@yHp-Td` yBhb亀b ji B@jBB@x@A$AjjEsb~~Ajk =jw=j%-(+cl@v@ mBBBn B@JB@2ay@x@AAJoE}/J`=p=F$Xq =@q5{% "r@0@ y! @c gAsbb ! b t B@`B@x@A$AuEcBv =j*a"w@@, xJJA#y B@aRB@x@A ARzEba AR{ =Ri=R"|@!e@yH#~aB}bdb> 4~ B@jB@x@A$AjEj=MA =!=( @t @ r$( @(AJBB J Bc B@JB@a@x@AAJE `==Fc =߀{%  +@@3b@Yڀ@ c !'b ! c B@aB@x@A$AEbcB =A6`T`=^"@,S@ m82*Q JRJA# B@B{,@x@A ARE a@Y C$F =RQ=R%@@ 37EV/9xJBbb½ B@jB@x@A$AjE:ǀ2 j@!j =jʀ=(%p(#@@~B~ɀ B$ F B@JB@x@AAJE`==AFc = À{% "@@ Ab\b ! , B@`B 'Z,@x@A$AEG>aB =jm=j%pj%@@,J:J (> B@aRB@x@A ARETaR FO$F =R="@a@ C@%@~?B B@ B@JB C@x@AAJAEπ `=B=NF/C =Հ{% %D@ р@$X "AEbiЀb! F B@aBc@x@A$AGET8b BH =j㏀=^+cI@D@ m=$^BJJJA#jK B@aRB@x@A ARLEF9RARM =RN=!N@I@ c1Kf tE4anBOb[b jcP B@jBB@x@A$AjQEa:jAjR =j= S@5@ TB$U B@JB@@x@AAJVE轀 `=W=|w   AX = )À{% Y@"@ $F @ |Zbb[ B@aB @x@A$A\Eoy;bAj] =j9<`=^%^@8@,_Jn7JA#*R` B@aRB@x@A ARaE| ARb =R.=R(oc@@ Ekfa dbb je B@jB@x@A$AjfE=b jAjg =j=j h@㮀@~iBCB) j B@JB@x@AAJkEg>J`=l= zFm =zm{%pn@h@ ! @+c 8 ob&b ! bp B@aB@x@A$AqE#?aBr =j;="s@@,tJJA#u B@aRB@x@A ARvE@aR FARw =R=R%x@@ 1$t%~acSKcybbĩz B@jBc@x@A$Aj{EUAjAj| = -Y=(%-(1}@X@ $( @(@B~BWB J$B B@B@x@AAJE+BaJ+U`==F = {% "@j@ c ǡAbˡ 륱 B@aB@x@A$AÈB = `ՌCaj"@5@,JJ B@B@x@A ARECDa AR =R^K=R"@F@ 7//9/cwBb(b j B@jB@x@A$AjEF jAj =jEa"@'@ƹBB B@JB@x@AAJEͺ J`==VBaJty ={% `3b@@ ! @c XBbib b B@aB@x@A$AETvFaj B =j{=j @hy@,JxJA#c B@aRBy* @x@A ARE1GRAR =R9=R%@4@ I"t/VBbSb  B@jB{y@x@A$AjEa Aj =j=j*(+c@H@ $( @(@BB B@JB@x@AAJEH`==F =خ{"@%@, Bbb c B@aB@x@A$AEodIaB =j$J`="@6#@ 5,%yJ"J R `JR- B@aRB@x@A ARE| F9 =R,=!F"@ހ@ o @o I"t/ ayb݀b j B@jB@x@A$AjEKb jAj = =jj%@噀@~nBGBc B@B@x@AAJERLaJ} J`== eF, =X{% @S@ mb&b! ^ B@aB@x@A$AEMaB =j9΀="@̀@,cJJA#c B@aRB@x@A ARENaR AR =RŒ= @@I#"6t"uaDbb½ B@jBW@x@A$AjE@OjAj =j-D= @C@ BBB g B@JB$X@x@AAJE+ 0@==F =Pa% @h ) @+c BMBb  B@aB * @x@A$AjE,B =jZ=^+c@@,J&J B@aRB@x@A ARE9sQb AR =Rz=R+c@5v@WI3腌 Bbub@( B@jBy * @x@A$AjE.Raj jAj =jH2=j%-(+c@1@BBB$ B@JBc@x@AAJEF J`==N =2{"@@ W! j+Ab  bXF B@aB@x@A$AEͥSbB =jeT`=j @Td@,JcJ > B@aRB@x@A AREUaAR = $=FR%@@ cC"0tt1{ bVb½ B@B@x@A$AjEa؀ fAj =jۀ=%-@B@ r$( @(DBڀJ B@JB@?&@x@AAJEV`==iF|3 S 4 fܙ{% "@#@@atE c ( bb 륱 B@aB`x@9 A @ nOWAj =B`= T=^"@R@,JQJ  B@Bx"vB k @x@A AR E XRAR = p= @ @ yS1@vv"t  bu b  B@Bc@x@ =Aj @`E| Aj =jɀ=j%p(+c@R@ BȀ B B@aB@x@AAJEYb@Y J`== P| ={% %@@@ ! @+c b ^! b  B@aBy@x@A$AE=ZaB = `="@ @,cJxJ rȭ B@B@x@A ARE[aR AR =RC= @@ ct1{1Pvv"D!b bj" B@jB@x@A$Aj#Ep\ajF jAj$ =js= %@@ $( @(AJ&BjrS3$B' B@JB@٥kBc@x@AAJ(E+]aJ J`=)=%>|* =@1{% +@,@ 'A,b@be! c- B@`B@x@A$A.E+B/ = `X^a^"0@@,1J&J 2 B@B@x@A AR3E8^_a AR4 =Re=R(o5@Aa@ s&6"B6b`b f7 B@jBy@x@A$Aj8E`j=M/9 =jG=(%-(+c:@@ ;BB, y< B@JB@@x@AAJ=EFՀ `=>=ǤFy? =@6ۀ{% "@@ր@ ! @c 4PAAb  bB B@`Bc@x@A$ACE͐abt  D =jl=j E@͓@ m2 @FJ9J G B@aRB@x@A ARHESLbaR RARI =RS="J@[O@ y"땂ddayKbNb@$jL B@jBJ@x@A$AjMEcjAjN =jf =%-(%O@ @<$( @(@BPB"B J)BQ B@JB@x@AAJREaÀ `=S=iyT =Mɀ{% %U@Ā@ y$F @ GyVb ! b륱W B@aBc@x@A$AXE~dbcBY =j?e`=j%pj"Z@r=@,[Jn@Q :kb  cl B@`B  Q@x@A$AmE(haBn =j=$j%o@@ m' @cpJ|J!q B@aRB@x@A ARrEiaR FARs =RA=R"t@@ Ta:Dub b jv B@jB~ @x@A$AjwE[jaj jAjx =j^=j%-%y@]@~' @"@BzB]B)B{ B@JB@ ,@x@AAJ|EkJ`=}=%)|@~ =@{% "@@ $F @+c mAb@b ! b륱 B@`B@x@A$AE+ҀB = `Zla%pj"@@,,J&J  B@B@x@A ARE8Ima FAR =RP=R"@(L@ c|$>0D+ɂbKbĩ B@jB@@x@A$AjEnjAj =jG=j%-%@@ BB$ c B@JB@x@AAJEF,`==ǤF =>ƀ{% "@@  YAb ! c B@aB (c@x@A$AE{ob 5^ B =j;p`="@W:@ '$cJ9J F B@aRBc@x@A ARE-AR =R=R"@@ 1FkbT|aBbNb j B@jB@x@A$AjEaqbAX Aj =@局=(%-(%@B@ Bµ$  B@B@x@AAJEiraJ J-@==l|Fy =o{% "@#k@ $F @c KAbjb b B@aB@x@A$AjEn%sB =j *=j%pj+c@n(@,J'JA#{R B@aRB; @x@A ARE aR =R="@@ B u-5Kea` b]b j B@jB@x@A$AjE{taj =j=j%-%@o@ nBОB B@JB@x@AAJEXuJe= &t =]{F% %@8Y@ aeb ^! B@aBy@x@A$AEvab =jӀ=j @Ҁ@,J|ѠJA# B@aRB@x@A AREwaR aR =RP=FR"@@ @o9D?1aDbb½ B@jB@x@A$AjEFxj  aj =jI= @@$BaH J$ c B@JBc@x@AAJEyJ  @==%| ={% @@ ?GBb@b c B@aBL6@x@A$AjE+  Kc = ``}zaj$j+c@{@,J-J B@B@x@A ARE84{a   AR = ;=R%@P7@ % C!"0 >Bb6b B@B@x@A$AjE jaj =j7=j%-+c@@ cBB B@JB@x@AAJEE|e=ǤFw =>{% "@@ ,Ab ! B@aB@x@A$AEf}a,D^ = `&~`=j @C%@,J$J B@Bc@x@A ARE݀y FaR =Rs=FR"@@JtQ$@v"EaQBb:b jc B@jBF@x@A$AjE`b jaj =j=j @L@~B B$ c B@JB@x@AAJETJe=hgF$X =Z{% @!V@F hv/G?(BbUb c B@aB,@x@A$Ag  EnE =j="@v@,JJ@ B@a B$X@x` =AR @`E FaR =RӀ=R%@π@cJtQ$Pv"&`̠bbm΀bĩ B@a$By@x@A$Aj E{B =j=(%-(+c @]@  B  B@JB@x@AAJECJe= / =H{% "@>D@ ! @ mhb ! b  B@aB@x@A$AE1 ^b =jajK@@,JJA# B@aRBz B* @x@A ARE RDF =R="@@ !#"dd" Zvb{b j B@jBy !j@x@A$AjEub jaj =jy= !@vx@$"BwB c# B@JB@x@AAJ$E1J@=%=%Fy& = 7{% '@W2@ ,J v(b  c) B@aBy@x@A$Aj*Et b+ =jD=^%,@@,-JJA#n,. B@aRB{@x@A AR/E*aR0 =Rկ=%p1@.@ c3t!%~4bYEJ[2bb j3 B@jB @x@A$Aj4Ecaj Iaj5 =j1g=j%-j+c6@f@ ) @"@B7BeB)B8 B@JB@x@AAJ9E8?@T   e:=@c; = n8%{% +c<@{ @ c h=b > B@`B@x@A$A?Eڀ!"b@ =ja"A@U@,BJJ C B@aRB|@x@A ARDEQa ##KcE =RwY= F@T@,JCt'%~"Q`~DfGb4b½H B@jBnb@x@A$AjIES aj@Y j$$AjJ =j=j%-(%K@@@ LB BM B@JB@x@AAJNE J%%@=O=ZۄF~yP = À΀{F Q@ʀ@ NARbuɀb cS B@`B@x@A$AjTE`b&'bU =jD`=j^(fV@B@,WJSJA#FcX B@aRB@x@A ARYEn@Y ((aRZ =R$R(o[@z~JSE%1@:B\bbj] B@jB@x@A$Aj^E, j))aj_ =jx="`@ֹ@ aB4BJb B@JB@x@AAJcE{rbw J**ed=Fe =kx{% :"f@s@ @ lBgbb b h B@aBy@x@A$AiE.a+,bj =j6="k@@@S2%lJJA#Jm B@aRB@x@A ARnEaR --aRo =@Ϭ=R"p@'@ ,c$ V4%,Bqbbr B@B@x@A$AjsE`j..ajt =jd=( cu@pc@ rvBbB Jw B@JB@8C@x@AAJxEaJ //ey=Fz =@"{% {@Y@ B$F @+c T|b ! b } B@`B !@x@A$A~E׀01b =jחaj%pj+c@:@ m!j @JJ  B@aRB; @x@A ARENa 22aR =ReV="@Q@ csC!@e NEb-b½ B@jB; @x@A$AjE8 ajf33Q =j =j @@~ `@"AJBx B$ B B@JB@x@AAJEŀ 44e=?؄F]L =ˀ{F% F(@ƀ@ ! @.W UAbZb! by B@aBc@x@A$AEEb 55b =j؅=%pj"@9@,JJA# B@aRB,@x@ =AR @`E<R66aR =R}D=R"@?@ C"% Kq0 `̠~BbDb j B@a$B @x@A$AjER 77aj =j=%-6@6@~$( @"@BB B$B B@JB@x@AAJEٳ88e=Fy ={% "@@; QAbub 륱 B@aBc@x@A$AE`oac9:H =j/`=^"@-@,J_JA# B@aRB@x@A AREm@Y F;;aR =R#=R"@}@ q4QCq S"":S"@Bbbĩ B@jBW@x@A$AjEb j<?b =j8ـ="@׀@,JJ (> B@aRB@x@A AREaR F@@aR =R=R"@#@FJC"Cq"qVu6u7a%Bbb@$j B@jB @x@A$AjEKjAAHo =jO=( @nN@ BMB J$  B@JB@x@AAJEJBBe=F$X = {% @S@ y`@+c Bb ^! XF B@aB @x 9A$A @E€CDb =jւaj+c@6@,JJ@ @R B@a B@x 9A AR @`E9a EEaR =R[A=R%@<@ c8$``$p%Bb%b½af B@a$Bx@x@A$AjE7 jFFaj =j="@@ BgJ$ B@JB@x@AAJEb JGGe=?ÄFc ={% @@ ! @( a,BbZb b B@aB@x@A$AEElHHb =jp=j @Qo@,JnJA# B@aRBy@x@A ARE'RIIaR =R/="@*@!!L9NaBbLb  B@jB,@x@A$AjER fJJaj = =%-(1@-@ $( @(@BB倃Bc B@B@x@AAJEٞKKe=F =Ť{% (@@ y c Abub 륱 B@aBc@x@A$AE`ZaLMNW =j`=j$j"@@,J^JA#h  aRB@x@A AREmѠ@X FNNaR =R%ـ=R"@Ԁ@ ~$F!9: 9K:Q 8ABbӠb@&b B@jB| @x@A$AjEb jOOaj =j=j%-%@ڏ@~j @"@B B<Bc B@JB@x@AAJ E{HaJ} JPPe =F, = ÀgN{% "@I@ y" @ Abb! b륱 B@`B@x@A$AEaQRb =j"Ā="@€@,JJA#F B@aRBc@x@A ARE{aR SSaR =RÂ=%p@~@  -L\y<a;{Bb}b½ B@jB@@x@A$AjE6jTTaj =j:=%pj%@h9@ $( @"@BB8BBc B@JB@& @x@AAJ E UUe!=F5n" =@{% %#@Y@ / A$b % B@`B ! @x@A$A&EVVb' =j7=^"(@@,)JJ * B@aRB@x@A AR+E*iaR WWaR, =Rp=R"-@.l@ :; < = aaB.bkb j/ B@jB@x@A$Aj0E$aj jXXaj1 =j4(=(%-(%2@'@3B&BB$ 4 B@JBc@x@AAJ5E7 JYYe6=?7 =+{ 8@r@ ! @c A9b  by: B@aB@x@A$A;EbZ[b< =j[`=j =@MZ@ 2 @ ! !% l>JYJ j? B@aRB@x@A AR@Ea \\aRA =R~="B@@K>%? @ A BCbGb½D B@jB@x@A$AjEER΀ j]]ajF =jр=%-G@7@ r$( @(AJHBЀJI B@JB@)c@x@AAJJEى^^@=K=ZFx3#xL =@͏{% (M@@ (ANbub 륱O B@`B@x@A$AjPE_Ea_`bQ =j`=j%pj"R@@ mSJZJ 4` T B@aRB* @x@A ARUEm@Y`~ FaaaRV =RĀ="W@u@ B%C D E  B2BXbᾀbjY B@jB@x@A$AjZEwb2 bbY[ =j|{=%p+c\@z@~]B8B J^ B@JBc@x@[ =AJ_ @Ez3aJ Jcce`=Fa =o9{% %b@4@ ! @c Acbb! b d B@abB@x@A$AeEdebf =j,a g@@,hJJ (Ri B@aRB@x@A ARjEfa ffaRk =Rm=R"l@i@ #F%GC"`QCGaBmb{hb n B@jB@x@A$AjoE!jggajp =j%=j q@z$@r$( @(AJrB#B$Bs B@JB@x@AAJtE hheu=F* v = {% w@Xހ@ ) @+c  Axb ! bcy B@aB@x@A$AzEiib{ =j9= |@@,}JJA#~ B@aRBy@x@A ARE)TRjjaR =R[=R%@)W@ 3"C"4~5BbVb j B@jB ,@x@A$AjEjkkaj =j<=(%-(+c@@ BB$  B@JB@x@AAJE7 lle=? ='р{% "@t̀@ Ab  B@aB@x@A$AEbmnb =jF`=^(f@LE@,JDJA# B@aRB@x@A ARE ooaR =Rx$R"@@/KC6+g7 8 aOBb;b j B@jB@x@A$AjER jppaj =j⼀=(%-(%@=@ kB B@JB@x@AAJEtbw Jqq@==ZF =z{ @v@ y c  Abtub y B@aB (,@x@A$AjE_0rrb =j5=j c@g3@,J2J  B@aRB@x@A ARE ssaR =R=%@@ KS9Keu4G5$X|b^bA; B@jBz c@x@A$AjEmb2 jttaj =j=%-@N@~B Jc B@JB@x@AAJEbaJ Juue=Fc =h{% "@/d@ ;lbcb! B@aB "@x@A$AEzav!A = ``ހ= @݀@,J}ܠJ F B@B@x@A AREaR xxaR =R:=R"@@ c1`p-@}Hobb½ B@jB~ @x@A$AjEQaj jyyaj =jT=j @S@~BNB$ F B@JB@x@AAJE Jzze=| =}{% @ @ c DBb1b ! c B@aB "@x@A$AEȀ{|b =jGaG r% @@,JJ  B@aRB{ ;V/ @x@A ARE)?a F}}aR =RF=%@AB@ s} uvu /$XbAbĩ B@jB @x@A$AjEQ~~! =j8=j%-(1@@ BB$  B@JB@x@AAJE7bxe=F ='{% "@r@  @yK $Xbס ^! b B@aBc@x@A$AEqa* b =j1`="@L0@,cJ/J n B@aRBc@x@A ARE aR =Rv= @@ w"Kf$``$FNb?b j B@jB@x@A$AjERbF jaj =j⧀=%-(%@?@ B$  B@JB@@x@AAJE_aJ Je=YrF~ =e{% %@a@ c_Tbt`b  B@aBy@x 9A$A @E_ab =jۀ=^K@ـ@,JNJ@ B@a BL6@x@A ARElaR ,Y) =R=R"@u@ pKYw"`Dib j B@jB{c@x@A$AjEMj"`!j = sQ=(%-(%@P@ nB7B J ` B@B@,B@x@AAJEz Je=F =@f{% " @ @c 7A bb  B@`Bc@x@A$A Eŀ'" =j9a^%@@#P"J'%JJ IJc B@aRB@x@A AREbo B@B@x@A$AjE jaj =j=( @{@$BB J B@JB@C@x@AAJEe=FB =@{% @X@ ڢB b ! ! B@`B !@x@A$A"Enacb# =j.`=j%pj%$@!-@ m%J,J & B@aRB@x@A AR'E FE( =Rh=")@@o!"[ aIB*b,b j+ B@jB@x@A$Aj,E6b jAj- = =j .@@ c/Br$ c0 B@B'$c@x@AAJ1E\Je2=>oF3 =@b{F% F(4@]@c ,5bYb c6 B@`B@x@A$A7EDacb8 =jl؀=j$j%9@ր@,:J;J >; B@aRB@x@A ><EQaR FaR= =R=FR">@a@ 䐂a"7KaVE?b͑bf@ B@jBB@x@A$AjAEJjEB =j\N=%-1C@M@ r `@"@BDBB JE B@JB@x@AAJFE_JeG=FH = ÀO {% "I@@ ) @ aAJb #! b륱K B@`B@x@A$ALEcbM =ja^"N@p@,OJJA#P B@aRB@x@A ARQE8a aRR =R@= S@;@ LL/LRL^MMa|BTbgb@$jU B@jB| o,@x@A$AjVEz2 jajW =j=j(%Xi@`@ YBB$ cZ B@JB@x@AAJ[Ee\=„F{y] = s{F% ^@=@ ! @K A_b ` B@aB@x@A$AaEkaQHb =j+`=j^%c@*@,dJ)JA#Fe B@aRB@x@A ARfEaFaRg =R9=FR(oh@@ @ FMTMM6NFTKBibb½j B@jB@x@A$AjkEaj ajl =j=j%-(%m@@~$( @n"@BnB[Bo B@JB@x@AAJpEYJeq=#lFr =_{%p"s@Z@ c tb>b ! cu B@aBy@x@A$AvE)abw =jFՀ="x@Ӏ@@Sf!j$^yyJJA#Jz B@aRB@x@A AR{E6aR FaR| =@㓀=R"}@>@ $F!R90CqSq90an6Df~bbĩ B@B@x@A$AjEGjaj =jEK=(%p@3A@J@ r!j @ťBB J$Bc B@JB@x@AAJEDaJ ${=ŤF = À4 {% "@@ c Ab ! 륱 B@`Bc@x@A$AjEʾKc = `~a^"@Y}@,cJ|J B@B$X@x@A ARE5a aR =R==R"@8@cLCr%r% akcb\b@$ B@jBx@x@A$AjE^@Y jaj =j=j%-(+c@B@B B$  B@JB@١2ay@x@AAJE@==fFc =@ɲ`{"@@c kcbbje! B@`B !,@x@A$AjElha, E =jm=j)%@hk@,JjJ c B@aRBB@x@A ARE#RaR =R+=FR%@&@ KSrK90aWFbWb@$j B@jB$X@x@A$AjEyߠ@Y aj =j=j%-%@c@ nBB B@JBc@x@AAJEe=i|$X = s{F% "@<@ W @W Fb  bXF B@aBc@x@A$AEVaE =j`=j^%@@,JnJ > B@aRB@x@A ARÈ FaR =R?Հ=R"@Ѐ@ L#DKT 8 aJDb b½a B@jB{ oy@x@A$AjEb jaj ==j%-(%@ @~Bk B) Jc B@JB@x@AAJEDJe=#WF$X =J{% "@E@ 5\ @+c yb>b b B@aB@x@A$AE(ab =jL= @@,JJA#o> B@aRB@x@A ARE6waR FaR =R~=R"@Bz@ o @%3%q -0avDbybj B@jB @x@A$AjE2aj jaj =jM6=j%-(%@5@ B B B@JB@x@AAJEC J${=ĤF =8{F% "@@ @Ab ! b B@aBB@x@A$AjEʩbb =jj`=j$j+c@eh@,JgJ  B@aRB@x@A ARE a aR =Rs(=R"@#@ c C"D1@;Bb @==fF =ٝ{% F"@%@ y c 7Bbb c B@aB@x@A$AjElSab =j`=^%@@,JjJA#rc B@aRB,@x@A AREyʠ@Y aR =R!Ҁ=R"@ỳ@ c S389Q0*  .Bb̀bj B@jBc@x@A$AjE@@T jE =j=(%-(+cj@∀@$BDB [,J B@JB@٢oB2ay@x@AAJEAaJ Je=0TFQ  =@{G{% "@B@ c Ab#be!  B@`B !y@x@A$A E cb =j)aj c @@ m' @ JJ  B@aRB@x@A AREta aR =R{="@w@ y c 8d` pBVBbvbA;j B@jB@x@A$/E/jaj =j*3=j @2@ cB1B J$ c B@JB@x@AAJE( e=F = {F% @a@ $F @6 VpBb  B@aB,@x@A$AEbcH =jf`=j%pj+c @>e@,!JdJ F" B@aRB@x@A AR#Ea aR$ =Ra%=FR(o%@ @ sÔ@0.:B&b,b@(j' B@jBB@x@A$Aj(EC٠@Y jB) = +܀=%-+c*@@ R~+B{ۀB, B@B@x@AAJ-Eʔe.=KF/ ={%p"0@@ ! @9 A1bfb bZ2 B@aBy@x@A$A3EPP acb4 =jm `=j^%5@@,6J;JA#7 B@aRB @x@A AR8E^Ǡ@Y FaR9 =R΀=R":@Vʀ@ `:t8* B;bɠbj< B@jB@x@A$Aj=E b2 jaj> =j]=j ?@@ $( @"AJ@BBA B@JB@٢o/l@x@AAJBEk> aJ J@=C=FtD =@XD{% E@?@ c rAFbbe! 륱G B@`B@x@A$AjHEbI =j# aj J@@ m!j @KJJA#L B@aRBc@x@A ARMEqa@Y aRN =Rx=F!O@ t@ t @@Pbxsb½Q B@jB@x@A$AjRE,aj jajS =j0=j%pj+cT@m/@~UB.B$ V B@JBc@x@AAJWE JeX=FBY ={% (Z@J@ B! @c I>A[b ! bc\ B@aB@x@A$A]Ebb^ =jc`="_@#b@,`JaJ >a B@aRBc@x@A ARbEa Oc =RF"=R"d@@ , b c8XBebb½f B@jB@x@A$AjgE(ր jajh =jـ=`Ze-(%i@@~@$(ncjBt؀ B$Bk B@JB@x@AAJlEb Jem=0Fn ={% "o@쒀@ y) @6 ApbKb! b q B@aBy@x@A$ArE5MaL6bs =jk `=^"t@ @,cuJ8J v B@aRB@x@A ARwECĠ@Y aRx =Rˀ=R"y@3ǀ@ 11+fK<~@Bzbƀb½{ B@jB$X@x@A$Aj|Eb jaj} =jV=j%-(%~@@ BB$ c B@JB@x@AAJEP;Je=ѤFc =8A{"@<@ c kb ! B@aB,@x@A$AEcb =ja%@N@,JJA#(R B@aRB@x@A AREma FaR =Ru=R%@p@ pC9ӔP9pXADb\bj B@jB @x@A$AjEk)jaj =j,=H@1c@O@$B+ J$ B@JBc@x@AAJE e=sF ={% `3b@,@, Bbb Z B@aB @x@A$AEybb =j``=j @^@,JgJA# B@aRB]L@x@A AREa aR =R4="@@ H9"z9 Ea Bbb B@jB@x@A$AjE jaj =jր=%-(+c@Հ@$BIB J B@JB@x@AAJEe=F ={% (@Ώ@!, @K $Ab/b b B@aB@x@A$AEJayb =jP `= @@,JJA# B@aRB@x@A ARE( FaR =RȀ=R"@0Ā@  D9t!/4aϛBbÀb B@jB@x@A$AjE|!b jaj =j6=j @@ $( @.`AJB~BJ B@JB@x@AAJE58"Je=F|B =%>{% @p9@ kiAb  c B@aB@x@A$AEcb =j#a @C@,yJJA# B@aRB@x@A AREj$a FaR =Rgr=R%@m@ * %Te]|yb-b½ B@jB@x@A$AjEP&%j] =j)=j%p(+c@4@ B( B B@JB@x@AAJEဈ> e=XF ={% "@ @ y! @c |ybsb! b B@aB,@x@A$AE]&byb =j]'`="@[@,cJTJA# B@aRBw Bnb@x@A AREk(a!aR =R= @k@M%3 B@aRB@x@A AR4E񺀈  aR5 =R€="6@@  C0:6aB7kab½8 B@jB oc@x@A$Aj9Exv7b faj: =jy= ;@J@ r$( @"AJ<BxJ$B= B@JB@x@AAJ>E18Je?=DF@ =7{% F%A@;3@ Sf) @+c 2qABb2b bcC B@aB "c@x@A$ADE퀈cQE =j9a^"F@@,GJ|J H B@aRB@@x@A ARIEd:a FaRJ =RJl="K@g@ cSH% `єaBLkbfM B@jB*@x@A$AjNE ;aj2 jajO =j#=j%-(6P@"@ QBRBR B@JB@x@AAJSE JeT=!FFU = À{F% "V@܀@ ! @+c wAWbaFaR_ =R=FR"`@=@ cPP%@H| Ӑ8Babbb B@jB@x@A$>cEɀ fajd =;̀=j e@̀@~$( @"AJfBB$Bcg B@JB@)@x@AAJhEB?b Jei=×F$Xj =@2{% k@~@ W c ,Alb ! cm B@`B@x@A$AnE@@abo =jA`="p@X~, qJJA#cr B@aRB@x@A ARsEַaRt =R=R%u@ں@ $F%os*ps\aBvbFb½cw B@jB@x@A$AjxE]sBb fajy =jv=(%-(+cz@G@ r!j @ť{BuJ$B| B@JBc@x@AAJ}E.CaJ Je~=eA|* =4{% "@ 0@c FAb/b 륱 B@aB@x@A$AEjꀈc b =Da^"@@ '$^yJiJ B@aRB5n@x@A ARExaEa !!aR =R,i=R"@d@5nMt|ExaBbcb@$ B@jB{ @x@A$AjEFj""aj =j =j%-(%@@ B?B B@JB @x@AAJE؀ ##e= Fv =iހ{"@ـ@  $F%N6 NAb!bx B@aBF@x@A$AE Gb$%b =j4TH`=j%p%@R@,JJA#Fc B@aRB@x@A ARE Ia &&aR =RI`=FR%@)@ \|}a(ak I`½ B@jB5n@x@A$AjEI j''aj =j$ʀ= @I`~$BȀB J B@JBc@x@AAJE'J` J((e=F = À{%pF"@d@ @': @( 4b ! b c B@`B,@x@A$AE=Ka)*b =j=j^%@<@,JJA# B@aRB@x@A ARELaR ++aR =R`="@@ $\$R: aHok'b½ B@jB5n@x@A$AjEBpMj,,aj =s= @@$Br J$ 5n B@JB@,@x@AAJE+NJ--e=J>|c =@1{% @-@  c `Bbd,b / B@`B W!,@x@A$AEO瀈c./b =jpOaj @ҥ@ m!j @J>J c B@aRB@x@A ARE]^Pa 00aR =Rf=R(o@ia@ !(o!Ro00acsBk`b j B@jB @x@A$AjEQaj j11aj =js=j%-(1@@~ƹ!j @` (@BB/B B@JBc@x@AAJEj J22e=F/ =bۀ{% "@ր@ B! @ǁAbb ! b륱 B@aB@x@A$AERbc34b =jQS`="@|O@,JNJ(> B@aRB@x@A ARETa 55aR =R=%p@ @ $X8pK$`$`$paBbz bj` B@jB/@x@A$AjE j66aj =jǀ=j%pj%@pƀ@ $( @"@BBB$Bc B@JB@x@AAJE U77e=F ={% %@I@ $X gAb ! 륱 B@aB (c@x@A$AE:Vac89b =!b="@@,JJ c B@aRBc@x@A AREWaR F::aR =RM=R"@@ z Bo 5E qaBbb@$ B@jBx c@x@A$AjE'mXaj j;;aj =jp=(%-(%@@~Bgo J$  B@JB@x@AAJE(YaJ J<b =jbZaj(f@â@,J/JA"Fc B@aRB@x@A AREB[[a@Y ??aR =Rb=R"@N^@ cDրb m]VB b]b  B@jB@x@A$Aj E\aj j@@aj =jD=" @@ BB  B@JB@x@AAJEOҀ JAAe=ФF = ?؀{% @Ӏ@ c VBb   B@aB (c@x@A$AE֍]bBCb =jN^`=j @iL@, JKJrR B@aRB{5n@x@A ARE_a DDaR =R ="@@ cR8I9C :bIBbcbA;j B@jBy c@x@A$Aj Ej jEEaj! =jÀ=%-(C"@N@$#B€J) c$ B@JB@١B!Ky@x@AAJ%E{`b JFFe&=rFb' =@ف{% ((@+}@ , /O*)b|be! c* B@`BQ@x@A$A+Ew7aacGHb, =j=j -@@ my.JbJA#/ B@aRB@x@A AR0EbaR IIaR1 =R+="2@@, NPCA%L%Ta//3bb½4 B@jB@x@A$Aj5E jcjJJaj6 =jm=j%-(%7@l@ c8BPB$ 9 B@JB@@x@AAJ:E%dJKK@=;=8/< =@+{F% %=@&@, />b.b ! ? B@`Bc@x@A$Aj@ELMbA =jKeaj B@@,CJJ D B@aRB@x@A AREE&Xfa NNaRF =R_=R"G@[@c N:Ӕ@@@,1 HbZbA;jI B@jB/@x@A$AjJEgjOOajK =j1=j%-(%L@@ MBB$ N B@JB@x@AAJOE4 PPeP=FQ =Հ{% "R@kЀ@c Sb ȥ! T B@aB@x@A$AUEhbQRbV =jJi`= W@MI@,XJHJA#FY B@aRB@x@A ARZEja SSaR[ =Rj =R"\@@ #:u:%+gX`nD]b8b@"j^ B@jBz@x@A$Aj_EO@Y jTTaj` =j=j%- @3Aa@3@~bB B$ c B@JB@c@x@AAJdExk@  JUUee=WFf =@~{% "g@z@ y/ @y+c IAhbqybe! bi B@`B !@x@A$AjE\4lacVWbk =j="l@@@Sm!j mJKJ Jn B@aRB@x@A ARoEjmaR+ XXaRp =@=R"q@z@ 31 IVTD yXBrb歀b js B@B} @x@A$AjtEfnaj jYYaju =jpj=(%-(+cv@i@~wB0B$ x B@JB@C!K@x@AAJyEw"oJZZez=FF{ =@g({% "|@#@'L @+c A}bb ! b~ B@`B@x@A$AE݀c[\b =j pa^K@m@ mJٛJ(> B@aRB@x@A ARE Uqa F]]aR =R\=R"@X@,NC-L6d`IBbWbj B@jBQ@x@A$AjErj^^aj =j"=(%-(%@@ rBB J$  B@JBc@x@AAJÈM__e=ބFc =Ҁ{% "@X̀@,N zO*b  B@aB 'Zc@x@A$AEsbc`ab =jGt`=^%@*F@,JEJ j B@aRB@@x@A ARE bbaR =RXu$R"@@ c S89:"]I"5nn!b!b j B@jB~ c@x@A$AjE42 jccaj =j=(%-(%@@~ƹBx J B@JB@x@AAJEuvbw Jdde=;F@ ={{F% "@v@ y! @c Bt bVb! b B@aBc@x@A$AEA1waefb =jx=0j%@@,JDJA# B@aRB@x@A ARENxaR ggaR =R=R"@W@! @cTSr"~}aDbêb  B@jBW@x@A$AjEcyjhhaj =jag= @f@ $( @"AJBB,Bc B@JB@x@AAJE\zJiie=ݤF =P%{% F"@ @F 6Ab  c B@aBc@x@A$AEڀjkb =j{aj$j"@v@@SfJᘀJ  B@aRB} @x@A AREQ|a llaR =@Y=R"@T@ cs]A \m\tBbdb½ B@B@@x@A$AjEw }jmmaj =j=j%-+c@d@ BB)  B@JB@x@AAJEȀ} nne=ۄF =΀{% "@:ʀ@ , !Abɀb!  B@aB/@x@A$AE~bcopb =jD`=%@C@,cJBJA# B@ B@@x@A ARE qqaR =R=aR"@~ є% yG ]L$6bb j B@jB@x@A$AjE, jrraj =j=%-(%@김@~BQB  B@JB@2ay@x@AAJErbw Jsse= F/ = x{% "@s@ W) @W+c Ab;b B@`B@x@A$AE&.actub =jV=^%@@,J%JA# `- B@aRB@x@A ARE3aR vvaR =R߬=R"@;@ \x]|}")avfBbb½`j* B@jB@x 9A$Aj @`E`jwwA =jBd=j%-(%@c@  `@"@BBB B B@aB @x@AAJEAJxxe=¤Fz@ =@1"{%p"@}@ ! @?m ҔAb ! bc B@`B@x@A$AE׀yzb =j藆a"@J@ mc5nmJ  B@aRB/@x@A ARENa {{aR =RyV=R"@Q@ Q *$``/`aVBbEb½a B@jBF@x@A$AjE\ j|8% =j =H@e-(% @:@ $(% B J$B B@JB @x@AAJ E }}e =d؄Fy =@ˀ{% "@ǀ@ $F @+c 'Lb~ƀb b륱 B@`B@x@A$AEibt~b =jA`=j"@?@,J\JA# B@aRB@x@A AREw aR =R'$R"@~ B@$pT+_$ ё1 a`2zbb j B@jB@x@A$AjE, jaj =j="@䶀@ƹBEBB B@JB@x@AAJ!Eobw Je"= F# =pu{% $@p@ ĕ'%b b & B@aB@x@A$A'E +acb( = `+=j )@@,*JJ >+ B@B@x@A AR,EaR aR- =Rͩ=%.@$@ y~ :e0au/bb½0 B@jB@x@A$Aj1E]jaj2 =j/a=*(#3@`@ r4B_B J5 B@JB@x@AAJ6E&Je7=F8 ={% "9@c@  c A:b ! ; B@aB - @x@A$A<EԀyb= =j͔aj >@/@,?JJ @ B@aRB2B @' @x@A ARAEKa aRB =R^S=R"C@N@ KWy% &%  BDb"b½E B@ABB@x@A$AjFEAaj2 jajG =j =j H@$@~IB J$ }J B@JB@$y@x@AAJKE JeL=HՄF{M =@Ȁ{% N@Ā@ ! @6 2BObcÀb b,P B@`B@x@A$AQEN~bbR =jk>`=j S@<@ m' @TJ9J U B@aRBc@x@A ARVE[@Y aRW =R=F!X@l@ $@q y%@yBYbb@%ĩZ B@jB@x@W =>[ @`EⰖbjaj\ =jr=j%-j+c]@ͳ@~$( @4@B^B.B_ B@aBc@x@AAJ`EilJea=Fb =]r{H O8P% (c@m@  |Adbb ! 륱e B@aB@x@A$AfE'bg =j,="h@+@,iJp*J j B@aRB<Bc@x@A ARkEv aRl =R)=R"m@@ !$F%o+W$P~ Bnbb jo B@jB@x@A$AjpEajq =jy=(%p(%r@֡@ !j @ťsB9B J$Bct B@JB@c@x@AAJuEZJev=(w   >w = )t`{% "x@[@ y! @iAyb b b륱z B@aBy@x@A$A{E ab| = `Bր=^"}@Ԁ@ mc~J J 4?"  B@B|@x@A AREaR aR =R=R"@@,O "M 1Bbbj B@jB@x@A$AjEHjaj =jL=(%-(%@|K@$BJB J B@JB@b@x@AAJE%Je=F| =@ {% "@a@ $F @ Ab ! b B@`B]L@x@A$AEb =jaj)j%@~@ m!j @J}JA# B@aRB@x@A ARE6a aR =Re>=G@.!!R"@9@cO)1 qKjto!Bb*b j B@jB $X@x@A$AjE@ jaj =j= @@) @K C#BxB$Bc B@JB@x 9AAJ @Eǭ@@@w Je=HF ={H {"% @@ @ Abgb bc B@abB@x@A$Am`ENiacb =ji)`=j^K@'@,J5J > B@a B@x@A ARE[@Y aR =R="@c@ c #zѕng@$Xbba B@jB@x@A$AjE⛤b jaj =jr=%-(%n@О@ rB.BJ B@JB@x@AAJEiWJe=F =Y]{% c@X@ ) @+c hv/G?3$Xbb b B@aB@x@A$Am`DEacb =jӀ=j @rр@,JРJ ( B@aRBy@x@A AREaR FaR =R=R"@@ c3ak\ @c@"n@.Dbabj B@ B@x@A$AjEEaj2 jaj =H=j @Y@ BG B B@JB !,,@x@AAJE aJ D38 J e=| =@O{% @G@ c Bb e! !@Wc B@`B ! @x@A$AEb =j<=j @@,J J  B@aRBz@x@A ARExaR =R=R%@{@ $F!o.!C} " m" 8Bbzb j B@jB 5@A$Aj @`E3ajaj = ?`=#7=j @~6@~!j @.`AJB5B$B B@B@$@x@AAJE% e=-y =@{:% @3b@_@, :Ab ! 륱 B@`Bc@x@A$AEbcb =A6`j`=$jK@?i@,JhJ  B@B@x@A ARE!a aR =Rk)=R%@$@ S"ќ(L'Bb1b½ B@jBW@x@A$AjE@݀ jaj =j=(%-1@@$Bx߀J$  B@JB@x@AAJEǘe=HF]L ={% "@@ ': @1a0# ( Abcb bF B@aB@x@A$AENTat b =jX=j @VW@,JVJA# B@aRB@x@A AREaR RaR =R|="n@@Oc)Ô"u $ RVbHb@&j B@jB@x@A$AjE[ˠ $ jaj =j΀=%-(%@I@ b~b 륱 B@aB@x@A$AEhBacb =j`=j @@,JWJA#F `R/ B@aRBy B@x@A AREv DE FaR =R-=R"@@ ys4p%$ft [Dfbbf B@jBy@x@A$AjEtb jaj =j}x=j%p(%@w@~B9B B@JB@x@AAJE0Je=CFy =x6{% " @1@ A!bb ! ^" B@aB,@x@A$A#E b$ =:aj %@@,&J J (>' B@aRB5n@x@A AR(Ecay FaR) =Rj=H@5 R"*@ f@ y5{"981@z\B+beb,nSjB @x@A$Aj-E@ ?aj. = /""=j /@!@ r0B B$ 1 B@B J!K@x@AAJ2E% e3=FV4 =@{% 5@aۀ@ c  6b ! 7 B@`By@x@A$A8Ebt b9 =jS=":@@ m!j%`# ! !% DF;J J < B@aRB@x@A AR=E2QRaR> = X=R%?@7T@ "1  l5n@bSb$A B@By@x@A$AjBE jajC =j==(%-(CD@@ EBB $ F B@JBc@x@C =AJG @E@ eH=HyI = }{0΀{% "J@zɀ@ ȤAKb L B@`B@x@A$AMEǃb Z%" 8bN =jC`=j%pj3O@UB@,PJAJ RQ B@aRB{@ B@x@A ARRE RA@aRS =@aR"T@~ Tbj B@jBc@x@A$AjE jaj =jI=(%-(%@@ BB$  B@JB@x@AAJE@e=H = À4{% "@|@ ^Ab !  B@`B >"@x@A$AEn@ /b =j.`=^(f@Q-@,J,J c B@aRB@x@A ARE FaR =R=R"@@ cPSr`a9WbPb j B@jB @x@A$AjE[b jaj =j㤀=(%-(%@=@~B J$  B@JB@x@AAJE\aJ Je=boFz/ =b{% "@^@ / Ab}]b!  B@aBc@x@A$AEhab =j؀=j c@׀@@S`@,Jo֠JA#Jc B@aRBy3By 5 A AR @`EuaR-aR =@`=*=R"@@ P$`^`{$`@RBbb  B@B @x@A$AjEJaj faj =jN="@M@ B@Bb$ n`JB@x@AAJEJe=| = Ās {% @@W)a;E! @ |Bbb b B@`By@x@A$AE €b = `"aj @@@SjJJ J B@B@x@A ARE9a FaR =@@="@<@ P+]paa*[b;bf B@B@x@A$AjE jaj =j=%-(!@y@ $( @.`@BBB J B@JB@/l @x@AAJE%b Je=F* =@{% (@b@ $F @ 6Ab ! bc B@`B@x@A$AEkacb =j+`=j @:*@ m!j @cJ)J  B@aRB@x@A ARE aR =Rm=R"@@ %t~a-Bb5b j B@jB@x@A$AjE?b jaj =jġ=%p(%@@~B(B$  B@JB,@x@AAJEYJe=GlF = À_{% "@[@ ! @( 4AbbZb ! b B@`B@x@A$AEMacb =jvՀ=%pj(f@Ӏ@,JDJA# B@aRB@x@A AREZaR FaR =R=R"@j@yP#33a Bb֎bf B@jB @x@A$AjEGjajo ] qK=j%-%@J@ $( @"@BB-B$B B@B@!Kc 5@AAJ @EhJe=F =@{` {% "@@ JAbb ! 륱 B@`B@x@A$A Eタb = `a" @y}@, J|J c B@Bc@x@A ARE5a aR =R== @9@ c3]`"[p aBbp8b B@jBB@x@A$AjE jaj =j =j%-(%@f@ BB$ c B@JB@x@AAJE e=F ={F% %@?@ y! @c 4Ab ! bc B@aB 'Z,@x@A$AEhab =j(`=j^(f!@'@,"Jo&J j# B@aRBv[{By@x@A AR$E߀ FaR% =RS=R"&@@ C[t&A%[B'bb jc( B@jB@x@A$Aj)E$b jaj* =j="+@ @$(n"AJ,BlB$B- B@JB@x@AAJ.EVJe/=,iFc0 =\{% 1@W@ 5\ @+c A2bGb b 3 B@aB,@x@A$A4E2ab5 =jhҀ=^"6@Ѐ@@S#!j%7J5JA#J8 B@aRBc@x@A AR9E?aR,aR: =@퐀=G@a;@G@  S "Cr6U+@z_B<bb = B@Bc@x@A$Aj>EDjaj? =jJH=(%-j+c@@G@ rAB B JB B@JB@1)@x@AAJCEMJeD=ΤFE =@A{% (F@@ RAGb H B@`B@x@A$AIEӻcAJ =j{aj%pj%K@^z@ mLJyJA#M B@aRB@x@A ARNE2a aRO =R:="P@5@ c00%D%auBQbUbR B@jB@x@A$AjSEh2 j BT =j= U@D@~VB JW B@JB@@x@AAJXE@=Y={Fp Z =@߯{% F%[@)@ $F @c 9B\bb b ] B@`B@x@A$Aj^Eueab_ =j%`= `@ $@ m!j @caJx#J b B@aRB@x@A ARcE FaRd =R,=R"e@߀@ $F!Rc B@aRBy@x@A AREǠ@Y FaR = +?π="@ʀ@ c,$Uk"z Dbbf B@B*@x@A$AjE b jaj =j=(%@@ rBQBJ B@JB@x@AAJE>Je=QF =D{% @?@ y! @+c UCAb,b b B@aBc@x@A$AEb =j?aj @@,J J ( B@aRBz@x@A ARE$qaaR =Rx="@0t@ cl`zn{t+:aY1Bbsb joRjB @x@A$AjE,A 2   aj =j+0= @/@~r$( @.`AJB.B J B@JB@,9/@'@x@AAJE1 J!!e=F =@@*{% @o@ nb c Ab ! c B@`B@x@A$AEb"#b =jc`=j @Gb@ m!j @,JaJA# B@aRBc@x@A AREa $$aR =R^"=F!@@ {|`a#JBb&b  B@jB@x@A$AjELրj%%aj =jـ=j%pj+c@7@~B؀ B$  B@JB@@x@AAJEӑ&&e=TF =@×{% 4 @@ y! @c L6bob b B@`By@x@A$AEZM''b =jR="@bP@ mJOJA# B@aRB{c@x@A ARER((aR =R=R"@ @ % "ґ9Ґ61aDbXb j B@jB@x@A$AjEgĀ ))aj =jǀ=(%-(%@<@ $( @ūcBƀ B@JB@@x@AAJp  E**e=F =@{څ{% "@)@ $F @6 )Abb b륱 B@`B@x@A$AEu;a+,b =j=^"@@ m!j$^c JoJA# B@aRB|@x@A AR E aR F--aR =R5=R" @@ Gp% QwBbbA;j B@jBzy@x@A$AjE n j..aj =jq=(%p(%@p@$BEB J B@JB@@x@AAJE) J//e=<| =@|/{% "@*@ ': @6 Ab+b b  B@`BJ@x@A$AE01b =jM ajj%@@ mJJA# B@aRB@x@A AR E$\ a 22aR! =Rc=""@0_@y Q"+d21/3B#b^b j$ B@jB@x@A$Aj%Eaj j33aj& =jB= '@@ $( @"AJ(BBB) B@JB @x@AAJ*E1 J44e+=F, =@ـ{% -@iԀ@ y$F @ 8A.b  bc/ B@`Bc@x@A$A0E55b1 =jf=^"2@ȑ@,3J4JA# f=Rt4 B@aRB@x@A AR5E?JaR 66aR6 =RQ=(o7@?M@QӔ @t ‘aÐaB8bLb½9 B@jB@x@A$Aj:Eaj j77aj; =j= =j%p(+c<@@~=BB> B@JB@@x@AAJ?EL J88e@=ThA =@<ǀ{% "B@€@ ! @ IACb ! b^D B@`B@x@A$AEE|b9:bF =j=`="G@j;@@Sm'$HJ:JA#JI B@aRB@x@A ARJE ;;aRK =@}=!L@@ c#2/ґ"aBMbDb½N B@B$X@x@A$AjOEgb j< J==eU=o}FyV =p{% W@#l@ B$F @+c u7AXbkb! b륱Y B@aBc@x@A$AZEt&a/>?b[ =j="\@@ !j$]JkJA#^ B@aRBz@x@A AR_EaR @@aR` =RC=R(oa@@ $F!R(o3"DTbbbb½c B@jB@x@A$AjdE YajF jAAaje =j}\=j%-(+cf@[@~!j @ťgB9B$Bh B@JB@x@AAJiEaJ JBBej='k ={"l@@c avAmb+b! n B@aB - @x@A$AoEЀCDbp =j<a$"q@@,rJ J s B@aRB@x@A ARtE#Ga EEaRu =RN=R%v@4J@  C҄"1`1aBwbIb x B@jB@x@A$AjyEjFFajz ==(%{@z@ |BB, } B@JBc@x@AAJ~E1 GGe=Fw3(. EO ! =!Ā{"@k@ ) @+c 1(Ab  b B@aB@x@A$AEy!@HIb =j9`=j @B8@,J7J 4b4 B@aRB@x@A ARE JJaR =R{="@@ SґFpcPBbAb j B@jB@x@A$AjELb jKKaj =j̯=%-o%@(@$Bµ) F B@JB@x@AAJEg JLLe=TzFc =m{% %@ i@ B': @.W oAbohb ! b B@aBB@x@A$AEY#!acMNb =j=j @@,JXJA#e B@aRB@x@A AREg"aR FOOaR =R ="@o@Qc"1x|}!Bbۜb@(j B@jB5n@x@A$AjEU#jPPaj =jvY=j%-(%@X@ $( @.`@BB1B J$Bi B@JB@x@AAJEt$JQQe=F =\{F% %@@$X Abb 륱 B@aB5n@x@A$AÈyRSb =j%aj @v@,J⊀J > B@aRB@x@A ARED&a TTaR =RK=R"@ G@NN$F!os11@xү"~aBbxFb B@jB * @x@A$AjE jUUaj =j'aj%-(%@{@ !j @ťBB B@JB@x@AAJE JVVe=̈́F = {% "@M@ c HtAb ! 륱 B@aB$X@x@A$AEv(bcWXb =j6)`=$j"@5@,J4J  B@aRB@x@A ARE YYaR =RP=R"@@ cyxxÔ "ayBbb@$j B@jBB@x@A$AjE1*b jZZaj =j=j%p%@@ Be B$ c B@JB@x@AAJEd+aJ J[[e=8wF =j{F% "@e@ )AbSb B@aB,@x@A$AE> ,a\]b =ji=j^%@ހ@,J5JA#F B@aRB@x@A >EL-aRF^^aR =R=R"@\@ yxu$t5{aBbșb½ B@jB}c@x@A$AjER.aj+u__aj =j^V="@U@ BB  B@JB@,* @x@AAJEY/aJ ``e=ڤFc =@M{ @@ y! @W "Bb ! bZ B@`By@x@A$AEɀabb =j0a^%@g@@Sm'%JӇJA#J B@aRB@x@A ARE@1accaR =@H=R%@C@ "*tGaQBb]b½ B@B$X@x@A$AjEt fddaj =j2a(*+c@a~ r$( @"@BBBJ$Bic B@JBc@x@AAJE  Jeee=|ʄF = À뽀{% "@6@ $F @6 Aqb b륱 B@`B@x@A$AEs3bcfgb =j34`=j%pj"@2@ !j @J1JA# B@aRB@x@A ARE hhaR = :=" @@ op" 4Q`G7  bb j B@B@x@A$Aj E5bF jiiaj =j=j%-%@@~B^B B@JB@c@x@AAJEa6aJ Jjje=tF =@g{F% %@b@ @?m b8b! b  B@`B@x@A$AE#7aklb =jF݀=j%pj%@ۀ@,JJA#c B@aRB| Bh@x@A ARE08aR mmaR =Rܛ=FR"@5@ +EutBfDbb I B@jB@x@A$Aj!EO9jnnaj" = GS= #@R@$$BB J$ c% B@Bc@x@AAJ&E> :Jooe'=F( =*{% F")@v @ $X 4*b ! + B@aB@x@A$A,Eƀpqb- =j;aj^%.@O@,/JJA#n0 B@aRB@x@A AR1E=@@ ! @c NCA?b|b ! b@ B@aB - @x@A$AAEfp>acuvbB =j0?`=%C@.@,DJYJ E B@aRBxc@x@A ARFEt@Y: FwwaRG =R+="H@@ +`Q66*@OBIbbĩJ B@jBQ@x@A$AjKE@b jxxajL =j=%-(%M@ܥ@~$( @"@BNBBB$BO B@JB@٢o$y@x@AAJPE^AJyyeQ=qFvR =@qd{% "S@_@ 8ATbb ! 륱U B@`B@x@A$AVEBacz{bW =j8ڀ=^"X@؀@,YJJ cZ B@aRB@x@A AR[ECaR F||aR\ =RŘ=R"]@@ c jajv =j=(%-(%w@ @~$( @"@BxB J$By B@JB@x@AAJzEıH@={=IĄFc| = À{% "}@@c 8A~b`b 륱 B@`B@x@A$AjEKmIab =x-J`=j c@+@,JFJ B@aRB@x@A AREY FaR =R=R"@]@cRS""~}"IBbbĩc B@jBc@x@A$AjEߟKb jaj =jo="@ʢ@ B+BJ B@JB@x@AAJEf[LJe=F]L =^a{% @\@ `Ebb B@aBc@x@A$AEMb =j=j @@,JaJA#c B@aRB @x@A AREsҠ@Y FaR =R ڀ=R%@hՀ@ #T6U +ndBbԀb  B@jB* @x@A$AjENaj =j=j*(+c@␀@nBBB B@JB@x@AAJEIOJe=Q =qO{% "@J@ y/ @+c 'Lbb ! b B@aB@x@A$AEPab =j:ŀ=.@À@,JJA#(> B@aRB@x@A ARE|QaR aR =R˃="@%@ 3V%%W iDb~bj B@jB$X@x@A$AjE7Rjaj =j,;=j @:@ B9B$  B@JB@x@AAJE# e=FB ={% @]@> @+c ABb ! b B@aB>",@x@A$AESbb =jnT`="@8m@,JlJ  B@aRB@x@A ARE%Ua aR =Re-=R%@(@ !d$F%CX1ZD-KBb'b½ B@jB@x@A$AjE= jaj =j=j%-(+c@*@~!j @(@BB〃 B$B B@JB@x@AAJEĜVe=EF ={"@@ y c Ab`b B@aB@x@A$AEKXWA@Tcb = 0`zX`=$"@@,JFJ B@B@x@A AREXϠ@Y: FaR =R ׀=R%@hҀ@ ySD"-Q@dBbрbĩ B@jB@x@A$AjEߊYb jaj =jg=(%-%@č@B#BB B@JB@x@AAJEfFZJe=F =VL{% "@G@ y! @ Abb bc B@aB@x@A$AE[acb =j€=^%@g@,JӿJA#( B@aRB| @x@A AREx\aR FaR =R=Rc@|@/ Rct𐁷u aCBbz{b@$j B@jB/@x@A$AjE4]aj@Y jaj =j 8=( @f7@$B6B J B@JB@x@AAJE Je=| ={% @C@! c Bb b! B@aB@x@A$AE^bb =jk_`=j%r@)j@,JiJ c B@aRB@x@A ARE"`a aR =RK*=R%@%@ s%v%%wuVBbb½ B@jB@x@A$AjE"ޠ@Y jaj =j= @@ ) @B+ B^J$B B@JB@x@AAJ Eab Je=*F =!?{% @ @ ! @c BbAbEb bc B@aB T@x@A$AE0Ubab =jWc`=j^"@@@Sr' @ @ J#J@.J B@aRB@x@A ARE=̀ aR =@Ӏ=R%@Qπ@ Ӕ@%1aN7Bb΀b B@B@x@A$AjEćdb jaj =jL=j%-(%n@@ r$( @"@B BBJ! B@JB@x@AAJ"EKCeaJ Je#=̤F$ =;I{% "%@D@c A&b  륱' B@aB@x@A$A(Ecb) =jfa"*@X@,c+JļJA#, B@aRB@x@A AR-Euga aR. =R}=&/@x@ yua'B0bcb½1 B@jB@x@A$Aj2Ef1hajfaj3 =j4=%pj%4@7@ 5B3J6 B@JB@)@x@AAJ7E쀈 e8=mFB9 =@{% %:@(@ ! @c  A;bb b < B@`By@x@A$A=Esibb> =jhj`=^%?@f@,@JVJ cA B@aRB@x@A ARBEka aRC =R4'=R"D@"@ tm/4aBEb!b fF B@jBB@x@A$AjGEۀjajH =jހ=j I@݀@~$( @"AJJBKBK B@JB@@x@AAJLEleM=FzN =@{% O@ʗ@ / @ 1lAPb*b ! bcQ B@`B@x@A$ARERma| ^bS =jV="T@U@ m!j$UJTm` RV B@ Bb@x@A ARWE n RaRX =RI=R%Y@@  QQe! HBZbb j[ B@jBB@x@A$Aj\E"ɀ jaj] =j̀=(%p(+c^@ˀ@<_B^BB$ ]L` B@JB@@x@AAJaEoeb=Fw b` gc = ){% "d@ㅀ@ B': @?m !AebEb b f B@aB@x@A$AgE/@p!@cbh =jUq`=j%pj(fi@~ mjJ"J 4b>k B@aRB@x@A ARlE= FaRm =RӾ="n@5@ !! Je=F =r{% "@Ă@ W c 2Ab*b ! 륱 B@aB@x@A$AE={a b =jA="@@@,cJ?JA#Fc B@aRB/@x@A ARE RaR =RO|aR"@~%o""; ]b b j B@jB !h@x@A$AjE", jaj =j=(%p(%@@ BfB$  B@JB@x@AAJEo}bw Je=F =u{% "@p@x AbEb  B@aBx@x@A$AE/+~a,b =j`=^(f@@,J.JA# B@aRB@x@A ARE=aR aR ==R"@U@cS"O 䐃9  -mBbb@&j B@jB; @x@A$AjE]jaj =jPa=j%-(%@`@ B B B@JBW@x@AAJEJJe=ˤF, =6{"@@c Ab  B@aB; @x@A$AEԀb =jaj)%@\@,JȒJA#Fc B@aRB@x@A AREKa aR =RS=FR%@N@ !$F @cSÔ@a~)OQ* bRb½ B@jBc@x@A$AjEejaj =j = @H@$B J B@JBc@x@AAJE e=mՄF =Ȁ{*F"@)Ā@; * bÀb c B@aB @x@A$AEs~bt b =j =^%@k@,J׀JF B@aRB@x@A ARE9aR RaR =RA=%p@<@ S#0+[:+dapHobebj B@jB5n@x@A$AjE jaj =j=j @l@~' @"AJBB B@JB@x@A>Ee=| ={% @>@  Ab !  B@aBc@x@A$AElab =j,`="@+@,J*JA#c B@aRB,@x@A ARE FaRsRI= @@ c3*GE)% GBb b j B@jBc`x@9 Aj @`E"b jaj ==j%p(1@@ Bf B$  B@aB@x@AAJ EZaJ Je =)mF =`{F% +c @[@ B! @W WA bDb B@aBB@x@A$AE/ab =jbր=j^(f@Ԁ@,J.J  B@aRB@x@A ARE {%p!@@ c dVA"b  Zy# B@`B@x@A$A$Eѿb% =ja^"&@S~@ m!j%'J}J ( B@aRB* @x@A AR)E6a aR* =R>=R%+@9@ Sq 3%qea* ,bNb½- B@jB; @x@A$Aj.Ee jaj/ =j=(%p(+c0@N@$1BJ2 B@JBc@x@AAJ3E쭒b Je4=mF|5 =ܳ{% "6@'@ @c /* 7bb b 8 B@aB@x@A$A9Eria1 b: =jn=j%pj%;@rl@,<JkJA#= B@aRBz; @x@A AR>E$RaR? =R,="@@(@ c+qTQ2dUDAbq'b jB B@jB@x@A$AjCE ajD =j= E@S@ rcFB  JG B@JB@c@x@AAJHEeI=j|J =@{% F%K@D@ BLb M B@`B W!L6@x@A$ANEWbO =j5\=^%P@Z@ mcQJJ cR B@aRB@x@A ARSERaRT =R="U@@ s.k[7+gBVbb jW B@jBy@x@A$AjXE΀2 ajY =j#Ҁ=j Z@р@ [BB\ B@JBc@x@AAJ]E!bhe^=)Xy_ ={F `@`@/ ~.Bab ! cb B@aB,@x@A$AcEEabd =`=j^%e@3@,fJJ g B@aRB@x@A ARhEEaRi =RpĀ=R%j@ο@ %1%qkb:b(ol B@jB@x@A$AjmE@ !j @(@BBh  B@JBc@x@AAJE!Je=F ='{% (@#@ B! @ b~"b b륱 B@aB@x@A$AEe݀ ZWUb =jaj$j"@뛀@,JWJ  B@aRB@x@A ARErTaaR =R'\="@W@ caV.%b½) B@Bnb@x@A$Aj*E@Y jaj+ =j^= ,@@ -B!B$ c. B@JBV@x@AAJ/Ed  e0=F; 1 =T{% 2@@ 8B3bb ! c4 B@aBy@x@A$A5Enac!"b6 = /`=^%7@~-@,8J,J ýF `R-9 B@aRB@x@A AR:E F##aR; =R=R%<@@ #6u"Tc)L=bdb j> B@jB@x@A$Aj?Eb j$$aj@ =j=(%-(+cA@U@ wBBµ$ C B@JB@٦B)@x@AAJDE]aJ%%eE=oFVF =@b{% "G@F^@ c 4Hb I B@`B@x@A$AJEa&'bK =j؀=^%L@׀@ m!j%MJ֠JA#N B@aRB@x@A AROEaR F((aRP =RF=R"Q@@ 3le"EfaDRbb#S B@jB@x@A$AjTE!Kj))ajU =jN=(%-`3AV@ @$WBmM JX B@JB@x@AAJYEJ**eZ=)|[ = {% "\@@ ': @, :6A]bCb b ^ B@aB@x@[ =A_ @E.€+,b` =jVa%pj%a@@,bJ%J c B@a B@x@A ARdE<9a --aRe =R@=R"f@4<@ C%EgeBgb;b jh B@jBc@x@A$AjiE j..ajj = J= k@@ƹ$( @"AJlBB Jm B@B@x@AAJnEIb J//eo=ʤFp =5{% F"q@@ B) @6 #Arb ! bcs B@aB@x@A$AtEkA@T00bu =jwp=j^"v@n@,wJDJ@x B@aRB* @x@A ARyEV'R11aRz =R.=R"{@G*@ Sek aiB|b)b } B@jBB@x@A$Aj~E  f22aj =j]=j%p(1@@~BB$  B@JB@x@AAJEd33e=llL6 =T{% "@@! @+c JAbb ! b B@aBc@x@A$AEYa| 44b = ^=%@\@,J_J  B@aRB@x@A AREqaR R55aR =R =R"@e@ c$pBFcBbbĩ B@jBc@x@A$AjE j66aj =jxԀ=(%-(%@Ӏ@ $( @"@BB8B$B B@JB@@x@AAJE77e=Z5n =@o{% "@@  ccAbb ! 륱 B@`B@x@A$AEHa88b =jL=j c@K@ m!j @JzJJF B@aRBB@x@A AREaR F99aR =R: ="@@; TsŮmt̀1Bbbj B@jBc@x@A$AjE j::aj = {€=%-(%@@ rBWBJ$ c B@JB@@x@AAJEz;;e=H|@ =@{% %@{@ * ! @c Ab6b bc B@`B@x@A$AE 6a<=b =jN=j%pj(f@@ mJJA# B@aRB@x@A ARE.aR F>>aR =R="@B@ o"!%  'NZBbbf B@jB @x@A$AjEhaj??aj =j9l=%-%@k@$BjB J B@JB@x@AAJE;$aJ @@e=F =4*{% %@v%@b$F @6 "!b ! b B@aB@@x@A$AE߀ABb =ja$j%@M@,JJA# B@aRB@x@A AREVa CCaR =R^=R"@Y@  % xt ` aABbPb½ B@jB@x@A$AjEVaj jDDaj =j=j @B@~!j @"AJB B$Bc B@JB@x@AAJE JEEe=^FQ =Ӏ{% F"@π@h IAby΀b c B@aB@x@A$AEdbFGb =jI`= @G@@Sf' @J_J J B@aRB@x@A AREqa HHaR =@'=!@@ t%& "aBbbĩ B@B(c@x@A$AjE jIIaj =j=j%-j)w@侀@ BDB B@JB@x@AAJEwb> JJJe=F = 5s}{% %@x@ L6$F @c >Abb! b  B@aB "@x@A$AE3acKLb =j,="@@,cJJ  B@aRBc@x@A AREaR MMaR =R=R"@@P$F%(oEadBbsb½ B@jB @x@A$AjEejNNaj =j*i=j @h@ !j @ūcBgBBcu  JBy@x@AAJE !JOOe=F = €('{"@["@ %NYBb  B@`B "@x@A$AE܀PQb =jڜa$" @:@, JJ  B@aRB@x@A AR ESa RRaR =Rw[=Rc@V@ e$F!e x$4CBb0b½ B@jB @x@A$AjE;jSSaj =j=(%-+c@ @ n!j @ B J$B B@JB !K@x@AAJE TTe=C݄FQ =@Ѐ{%p"@ˀ@ c Ab^b c B@`B !@x@A$AEIbUVb =jtF`=^%@D@ m!j$^J?J  B@aRB/@x@A AR!EV@YA WWaR" =R$R"#@^@ ,|} "l@$b % B@jB@x@A$Aj&EݸjXXaj' =jm=(%-(%(@ʻ@$)B)BJ* B@JB@x@AAJ+Ectbw YYe,=Fc- =Xz{% ".@u@ , /b  0 B@aB@x@A$A1E/acZ[b2 =j=j%pj%3@y@,4JJA#(R5 B@aRB@x@A AR6EaR \\aR7 =R="8@詀@ m n o":abD9bTb jc: B@jB5n@x@A$Aj;E~baj j]]aj< =jf= =@le@rc>BdBB? B@JB@x@AAJ@EaJ$X^^eA=0|/B =#{% F"C@E@c WBDb E B@aB@x@A$AFEـc_`bG =jÙaj H@#@,IJJ >J B@aRB@x@A ARKEPa FaaaRL =R4X=R"M@S@ 5n97867u4u6S"aWsBNbb@$O B@jB$X@x@A$AjPE jbbajQ =j=j R@@ SBd BT B@JB@x@AAJUE cceV=,ڄF$XW =̀{% X@Ȁ@ 5n! @1a0# K BYbCb b Z B@aBc@x@A$A[E-bcdeb\ =jQC`=+c]@A@,^J JA#F_ B@aRBx5n@x@A AR`E; ffaRa = +aR%b@+~c U"++bPBcbbd B@B@x@A$AjeEµ, jggajf =jB=j g@@ $( @"AJhBBi B@JB@x@AAJjEHqbw Jhhek=ɤF~ l =5w{F% m@r@ 5\ @  5Anb ! bo B@aB$X@x@A$ApE,aijbq =j=j^"r@R@,sJJA#(Rt B@aRB@x@A ARuEݣaRFkkaRv =R=R%w@馀@U$4"%aBxbUbjy B@jB@x@A$AjzEc_aj fllaj{ =jb="|@%@~}Ba B$il~ B@JB; @x@AAJEJmme=k-|u = {% @&@ ! @ ~Bbb b B@aB @x@A$AEqրno! =j  ^%@@@Sf'%yJsJ R B@aRB/@x@A ARE~Ma FppaR =@:U="@P@ #T}N"aerBbObĩ B@B@x@A$AjE jqqaj =j =(%-(@ m!jJJ  B@aRB@x@A ARE @YA uuaR =R=G = a@$@ 3zu"‘@z;$bb B@jB@x@A$AjEb jvvaj =j+=j%pj!@@ BB$ c B@JB @x@AAJE-naJ@Yw Jwwe=F5n =t{"@jo@, 0Ab͡j! B@aB@x@A$AE)a xxb =jW.=j%p%@,@,J$JA#rR B@aRB @x@A ARE; RyyaR =R=R(o@S@ 7$F!o" CTD t;aBbb j B@jB* @x@A$AjEzzaj =jJ="@@ !j n"AJB B$B B@JB@c@x@AAJEH\J{{e=P*5n =@8b{% F"@]@ ) @+c )Ab ^! bZ* B@`B,@x@A$AEa|}b =j׀=$j"@Zր@ m!j @JՀJ(R B@aRB@x@A ARE܎B@T~~aR =R=!R"@䑀@ S"u6 @TNBbPb  B@jB@x@A$AjEcJjaj =jM=j%-j+c@S@ BL B$ v B@JBy@x@AAJEaJ ge=k|* = {% "@$@ ! @( Abb^! b B@aB@x@A$AEpV =jaG@ %@@,JcJ dR- B@aRBc@x@A ARE~8a aR =R@@= @;@ 7" @c"& ' (xBb:b½ B@jB @x@A$AjE jaj ==j%-(%@@ !j @r"@BB=B$B B@JB@x@AAJEe=`= ={F% %@ư@ Ab'b ! 륱 B@aB@x@A$AEkaj b =jo=j$j"@&n@,JmJA#= B@aRB@x@A ARE&RaR =R).=R"@)@ cs"t5%hht5gg"`IBb(b j B@jB @x@A$AjE  aj =j=j%-%@@~ƹBh䀃 B$ g B@JB@x@AAJEe=F@ ={:% "v@㞀@ ! @ :AbBb ! bc B@aBc@x@A$AE-Y acb =jY `=j @@,J(J  B@aRB@x@A ARE:Р@Y: %T2 =R׀=FR" @OӀ@ yEYB)"‘ xC v   B   `   @ F B@B `8B @'@x 9 > @@ `@E  @ A =@`=I=H "=  @@3A@@  `@K J!JBB BiJ B@B@B!KJ@x@AAJEHG J@==Y `=z3"<    ! =@ ={F% (?@;@ c 'A@b ! 륱A B@aB "@x@A$ABEbbC = ``Z`=j$j"D@"Y@,EJXJ nF B@B@x@A ARGEaaRH =R3=R"I@@ $F!Ô0" TD"aSBJbb@$K B@jBy @x@A$AjLE͠@Y f//)M =jЀ="N@@!jn"AJOBcπB$BP B@JB@x@AAJQEb JeR='FS ={% F"T@ቀ@ @AUbBb2kT! b V B@aB "@x@A$AWE-DacbX =j``=^"Y@@ `'$^ZJ,J BJ[ B@aRB@x@A AR\E:@Y aR] =@€=R"^@N@1r~Z$F!Rt-%~"d 8B_bb ` B@B @x@A$AjaEvb jajb =jAz=(%-(+cc@y@ r!j @"@BdBB$Be B@JB@٢oB!K@x@AAJfEH2aJ Jeg=ɤFxch =@<8{% "i@3@'$F @6 {4Ajb bck B@`B !@x@A$AlEcbm =jaj$j"n@U@ m!j @oJJ p B@aRB|@x@A ARqEda -)r =Rl="s@g@!R6"t~"+aBtb\b#u B@jB @x@A$8`8صq'!UEdC =@Nַ`=`(f` a&@Հ@@S`6@`@ ^@ݔCJԠJ J `J@ D-F B@B{KwB x  @x@A AREeaR J 8eeAR =@A=R!R@@ `@RK%WARJJ  `JRiR B@B@dR @x@A AREJaRfgAR =RB`=d (r@@ !R"!@BBBBJ B@JBz@-"@'@x@AAJE hheK=Ѐ=`=8>La` 8w  @/`8A =! =ZA`=M!Y!@?@ M @{%BJ'J  ` @ B@a B@x` =B @`E@AR =@`==R#M@C@~#M @!@BB B B@BNB @x@AAJ Eb_Q J NAJ =J3`=J  @@@ >AR JJ J B@RBB$ @x@A AREjabAR =Rm=R"\@l@0 @>JykJJ,2 J B@RB>B%@x@A AREd&RAR =RH)="R@'@@So"!R m+֯BJJ$R B@RBR@x@A ARE AR = {=R,@'@ RR @PK?ALDD B@LB@ 5@AAL @`Erb LAL =@p`=@=L& @@~!L @!BB" B@Bx@x@AAJ#EX`)AJ$ = /b@ J ,%@@U#!J&#A&J+J ' B@B d@x@A AR(EЀ AR) =RҀ=$*@<р@ TAR+J , B@RB R"@x@A AR-E":RAR. =Rm=)6 R/@Ɍ@ R" @1 >ouXAR0J(JR1 B@RB@x@A AR2EGRF3?3 =RI="4@JH@ R%CRK !0WAR5J R6 B@RB@x@A AR7ERAR8 =Rz="R9@@R ";RAR:J6JA#; B@RB@x@A AR<E!j KAR= =R="R>@\@ R   u (4?J @ B@RBR@x@A ARAEy :B =R|="RC@z@ R KPARDJCJA#E B@RB@x@A ARFE.5RARG =R8="RH@k6@K"#Q:ARIJ J B@RB@x@A ARKE ARL =R="RM@@ R  ARNJQJRO B@RBR@x@A ARPE;ARQ =R="DR@y@ R KTGaxgSJؠ%g!K  T B@RB@x@A ARUEgRARV =Rj="W@h@ R "]#KBXJ^J$Y B@RBR@x@A ARZEI# RAR[ =R%=R"\@$@ @SZ5` K |\]F ^ B@NB N@x@AAN_Eހ AN` =@& a!& h-9/(a@@ V!N @ *bBB*c B@B{@*@x@AAJdEU a TAJe =@p8 `=J ,f@@!*%BdAgJJ *h B@B*@x@A ARiÈ ARj = {Kπ=R, 'k@%΀@@S" @  xANlF̀Fm B@NB @x@AANnEq "6ANo =@E`=;#N#p@(@ V!N @ qBD Br B@B@x@AAJsE7AJt =JaJ"q" 7 ,u@A@ "AvJJ Rw B@RB @x@A >xEv  ARy =Rly=# jz@w@ ," @ KUSB fAR{J(J R| B@RB@x@A AR}E2  RAR~ =R4="R@P3@ R  RecbARJ C B@RBR@x@A ARE@Y RAR =R~="R@@  eiv!TAARJ:J$ B@RBR@x@A ARE AR =RL=R<@f@5tZ @\rALD D B@LB@ۢYB* @x@AALEdaL6AL =@p!`='@^@ " B B B@B@x@AAJEۀAJ =J $J ,@k@ "!J\ _/JחJA#Z B@RB+@x@A ARER  =AR =RS= gB@^@~ B B@B@JCI@x@AAIa%E 1I@.A =@UL i j@*T@!V |JSJ NV B@B$BV@x@A AREmaR 8//aR =@I= V0)VaR@@ $ @K #J J F B@B G@x@A AREɠ A@00aR =@̀="R@1ˀ@,JʀJR B@B@x@A AREz 11aR =R=R(@@&DD$ B@LB@x@AALEA L22aL =LB=L'[(@1@ # @ B — B@JB@x@AAJE 34aJ =J a2&@S@ = $ AJJA#R B@RBMmB@x@A AREs a 55aR =Ruv=&O!R@t@ R" @J1J B@RB%@x@A ARE/ R66aR =R1=RD@T0@@Z"!R@  1kBC K B@KB@@x@AAKE 77aK =@pq=%<@@~" <B?B< B@By@J@x@AAJE)bo89aJ =@pWe`=! @c@!%<T|J$JA#< B@B$@x@A ARE7a ::aR =R =R#R@t@ R" @<J < B@RB<@x@A ARE؀ R;;aR =Rۀ="R@ـ@ <R !JZJA#R B@RB @x@A ARED<>aR =R2="R@ @K !J R B@RBR@x@A AREƀH3??aR =Rɀ=<&]`3k@Ȁ@ R  !JxǠJ m K B@RB@x@A ARE_@@aR =R[="@@ R K !JJA# B@RB@x@A ARE=RAAaR =R@="R@!?@K !J>J$R B@RBR@x@A AREmz DBBaR =RM="R@@ R  Ga !J J$R B@RBR@x@A ARECCaR =Rӷ="R@/@ R K !JJ$R B@RBR@x@A AREzpRDDaR =Rr=RFx@q@ @S4 !FF$N B@NB !@x@AANE,NEEaN =@-=N1*@1@ !N @ gB 8* B@B|@*@x@AAJE gFGaJ =@pܥaBq;! 8 @>@ # @ @{'%A JJ * B@By1B '~ @x@A AR E^a HHaR =@րq`="@_@Z" @ B1BB B@B@"9@x@AAJEaJIJaJ =@p؀=J"\@׀@ AJ~֠J  B@B}@x@A ARE)aR KKaR =R=R"\R$@p@@SU7ANF  B@NB N@x@AANELNLLaN =@}N=N#@M@!N @ BLB B@B@x@AAJ!E7aJMNaJ" = ƀ="q! #@Iŀ@$KA$JĠJ % B@B@x@A AR&EDaR OOaR' =R@=<(@@ " @c )JJ* B@RB@x@A AR+E: RPPaR, = {=="R-@J<@ R&R .J;J$R/ B@RB@x@A AR0ER QQaR1 =RN=!"2@@  3J J4 B@RB@x@A AR5Eر!RRaR6 =R$=E7@<@& 8DD$9 B@LB*@x@AAL:E_m"LSSaL; =LRo=''<@n@ " @ =BB> B@JB@x@AAJ?E(#aJTUaJ@ =Jx=J#A@@ %=!JBJDJ oC B@RB@x@A ARDE$aR =VVaRE =Rk=!zF@I@GFF9H B@NB9@x@AANIEz[%NWWaNJ =Nf]=N"`K@\@ LB2BM B@JB[@x@AAJNE&aJXYaJO =JՀ="q" P@Ԁ@$QJoӠJ R B@RB[@x@A ARSE'aR ZZaRT =R= *U@j@ }" @?cVJ W B@RB}@x@A ARXEI(R[[aRY =RL="RZ@J@ R&R[JYJR\ B@RBC@x@A AR]E)R\\aR^ =R=!`3g_@X@ R `J Ra B@RB@x@A ARbE ]]aRc =R€=+d@ @&eDnLf B@LB@x@AALgE)|*^^aLh =L*~=L i@}@ " @ jB Jk B@JB@x@AAJlE7+aJ_`aJm =J=! n@&@$סoxJA#op B@RB@x@A >qE,aR aaaRr =RM==!zs@*@(tFF¡u B@NB,@x@AANvEDj-NbbaNw =Nl=!& /x@|k@ yB Jz B@JB@x@AAJ{E%.aJcdaJ| =Ji= }@@$~J5JA# B@RB&@x@A ARE؜/aR eeaR =R䟀=R1@>@ R" @JJ R B@RB*@x@A ARE_X0RffaR = W[="R@Y@ }JJ$R B@B@x@A ARE1RggaR =R=C"d@5@ R JJ$R B@RB@x@A AREl hhaR =Rр=+@Ѐ@ y`%aDLD$L B@LB@5@x@AALE2iiaL =@pߌ=L'@?@ " @ B B B@B@x@AAJEzF3aJS@sjlaJ =J4`=! @@ " @!JoJ Z B@RB@x@A AREy5aRA@mmaR =@{=="@xz@ " @ B ʁ B@B@@x@AAJE46aJnoA =@p=J"\@w@@S "{HJJR B@B@x@A ARE7aR ppaR =@Ү=R"\R@/@  K JJA# B@B.@x@A ARE)g8RqqaR =R)j="R@h@ R"!R u%rD;J ¥R B@RBR@x@A ARE"9RrraR =R%="R@&$@)*J#J$ B@RB@x@A ARE6 ssaR =R="R@w߀@ R )K@2@ @O@}AJaJ@Z0 B@RB"5B@x@A ARE l~~aR =@Ԁd= j?@Ġ~  B B@B@J50@x@AABba =gC`= @"f@ $VOBVJeJ  V B@NB@x@A AREDaA@,WaR =@"= @ @ $ @O?ARJ@J B@B G@x@A AREۀ RaR = `'ހ="R@܀@R uvARJ R B@B@x@A AREEaR = ="R@ݗ@ R p ^ARJ>J  GF B@>BR@x@A ARE(RFRaR =RU="R@nS@ R%K)$u ARJ  B@RB@x@A ARE GRaR =R="R@@K &q(ARJwJ$ B@RBR@x@A ARE6 KaR =R*̀="R@ʀ@ 0 1TmARJ y  RBR@x@A AREHbz RaR =R="R@@0% 8ARJyJR B@RB@x@A AREC@IaR. R$`: =RSC="R@A@KB 8| JJA# B@RB@x@A AR E@Y RaR =R="R @$@ R  FUBJJ B@RB@x@A AREQJaR =R5="R@@ R +'ARJ R B@RB@x@A ARErKRaR =Ru=&]@3t@K &ARJsJ$ B@RBK@x@A ARE^.LR# %K =Rb1="@/@ R *4ARJJ$R B@RBR@x@A ARE KaR =R="R!@1@ D K #AR"JJ# B@RB@x@A AR$ElMbz RaR% =Rp="R&@ʦ@K u% AR'J,JR( B@RB# ;B6@x@A AR)E`NaR R:#* =Rc="R+@Kb@ u%AR,JaJD- B@RB@x@A AR.EyOR g!R/ =R="R0@@ R K  AR1JIJ$R2 B@RB ~@x@A AR3E؀ aR4 =Rڀ="R5@:ـ@K%I?mAR6J 7 B@RB2! ;R@x@A AR8EPaR9 = pw="D:@ϔ@IC)lAR;J/J$K< B@B$R@x@A AR=E OQRaR> =RuQ=!z?@HP@@S-o)BAN@F A B@NBN@x@AANBE RaN 4EGC =@ǀ=N>D@K@s!N @ EBƀ BF B@B@x@AAJGESaJ*?@saJH =JY.W`=JKI@,@8!Jo~B9טAJJ'J@K B@RB@x@A ARLE@YRA@aRM =@*= R~N@@~ >O B@B I@x@APAPXbaQ =`Y`=$ oR@[_@V "wBVSJ^J VT B@NBq@x@A ARUE^ZaqaRV =R^= V!R W@@@S$ @K<gARXJJ R FY B@RBD @x@A ARZEԀ aR[ =@E؀="R\@ր@ < Wd"AR]JJ$R^ B@B5@x@A AR_Ek[b RaR` =RK="Ra@@   "\bJJG"c B@RBR@x@A ARdEK\RaRe =RN="Rf@1M@ R CYBgJLJ$Rh B@RB@x@A ARiEy]RaRj =R] ="Rk@@ %Fu|IARlJ!J$Rm B@RB5@x@A ARnEà@Y aRo =Rŀ=&]zp@<Ā@5.5 |ARqJ r B@RB5@x@A ARsE~^aRt =R="u@@!NcWARvJFJ$w B@RBK@x@A ARxE :_RaRy =R=="Rz@j;@ # v'{J | B@RBK@x@A AR}E aR~ =R="R@@ KucJ`JR B@RBR@x@A ARE`aR = _="K@@=% l@  A)BA?JJ (R B@B@x@A ARElaRaR =Ro="@m@ ! B EKJ]J$R B@RB@x@A ARE((bRaR =R*=R '@)@'@SpZ`  B&B;AOG G B@OB(@x@A AOENT =@%ca3IA4@@ W%H @ BB B@B@x@AAJEZda A!ʒ@ϙ@ 8AJ =JO Br" = F` 1+! J4@@@ @@̥pAJdJ ~ B@RB{ @x@A AREOWaRA@aR =@I[=D@Z@ c @AKAKCYC B@B@b Ko@x@AAKEKaK =@p=K'#@@ !K @ BVB4 B@B !KJ@x@AAJE\΀E8caJ =Jla&\=@k@ AJjJ  B@RB.@x@A ARE!aRA@aR =@O&=$J@$@k@S" @ }@AK:rAQI7II B@B_ ;@x@A AQE&݀ Q@==|ifj @@ƿ#@F =A;{W@`ހ@ !e~ )g @% @ AГ `AV V$A ViqU^ B@`B E @x@AA E `E % %N@D@$NB  !  vk B@B a! N@x@ABkEc+c =J`=J"BJ)8@@,NJZJ N B@RB@x@A AREAˀ@Y aR =Rπ=R%N@y΀@l@S~Z N )BÌI QN B@QBN@x@A AQEȆbfQaQ = Ӡ=H '#k#@݉@ !Q @"@BB<B B@JB@x@AAJENBaJ (@==T`=- Ex = F{)&"@_E@) @)"qAIDI ! IҦ B@`Bo@g%@x@A AQE8AQ =Q1aQ @@,JJ  B@aRBf@x@A AREta aR = {y=H $-R"@x@""ANeIwI Q B@QB@x@A AQEi0aQ Qd4=c@(A A =@ )3{#4@1@ 4D4)D4IIY4 B@`Bl! @x@A A Ez5&&@4@ B=B! A B@a B@ @x@ABEA,"^ =@pFbJ'@@,AJJA#A B@B@x@A AREbO@TA AR =Rg=R%A@.f@p@S"!0.BIeIaA B@QB` s@x@A AQEaQeA=c A =A;(#{H=f&-!@!@o@S~@! @  AI I $4 I4 B@`Bt !@x@A B E@`E%$A@$@ Y! @$5A B=Mc@A =^{ A@@ @@ [/L@ I2IA B@aB@x@A EA Eb$%A@@ ABz B! A B@a BA@x@AB^EA c =JvaJ"A @u@ b'$/!JltJ IJA" B@RB@x@A AR#EN-a aR$ =@ 2=R%A%@0@!R@~!&I/IA' B@BbA@x@A AQ(E QeA)=dr7A* ={-%-!+@@ " @5!Dw,IqI `I4- B@aBp ,@x@A EA. E$%A/@^@ `@)w@B0BB B1 B@a BA@x@AB2E\cAc3 =Jd`=J#J! 4@c@,A5JbJ RA6 B@RB@x@A AR7Eia aR8 =R% =R#9@@ "!HCMܗB:I IA; B@QB@x@A AQ<Eր QeA==~`> =ۀ{%!?@ـ@ ! @ A@I`IAA B@aB@ 5@A EAB  `E݀$%AC@K@$ADB܀ AE B@B@i &NI@x@ABFEwcA5cG =@pɣ`=J#J%AH@7Ȁ@,IJǠJ AJ B@B|DSRE@x@A ARKEa aRL =RL=R#M@ʃ@! @eANI6I O B@QB@x@A AQPE<QeAQ=N`=R =>{%!S@R=@g@ST AB'@sDwTI AU B@aBh!$4@x@A EAV E@`EXA$%AW@@@ Y&U @$5@BXBBe BY B@BA@x@ABZE c[ =JƷbJ ^\@8@ !J @!]IIQ I^ B@QB$@x@A AQ_EnaD!"aQ` =Q /`=Q$"]a@k-@,bJ,J c B@RB@x@A ARdE ##aRe =R{=R%f@@ @7CgIbI Qh B@QBY@x@A AQiEAbf@Y Q$$fj=*k ={#W!y@@"@T620@bmV n B@aB=V@x@AFo E  & &p@]@ %U @ $B@BqBB ! Br B@a B@x@ABsE\b%&ct =J`=! u@~@,NvJJA#sNw B@RB o@x@A ARxEӀ@Y ''aRy =R؀=#%Nz@ ׀@"N#B{IyրI QN| B@QB o@x@A AQ}E[bf Q((eN~= o ={ N@e@ —$- @% @ K&0 @ >VȑV $A V B@aB N@x@AEN E@`E䗀$%N@A@ @$B@BB B! BN B@B |@x@ABEJbmR A9)AJ =@ڼP@k! @<@,NJJ@N B@B@x@A AREsQ@T AR =R>m`=R%N@x@ N }>IwIQN B@QBN@x@A AQE/a QAQ = Ӡ2=H ck@ @ %"CB1J B@JB !@x@AAJE J`==d-o- A =@]{&"@@ |">I4I ¦ B@`B@x@A AEAQ =Q=H@" @[@ T!Q @6JǨJ  B@aRB@x@A AREaaRAR =R_f=R"@d@"2ß>IFI Q B@QB@x@A AQE-aQAQ =Q =Q%>)o@@@ B B B@JB k@x@AAJE؀@==Fb[ =@wۀ{$"@ـ@ ! @BڿISI I  B@`B>@x@A AQE:b0 @s+ =Q`=G@#@ƀ@ TJSJ  B@aRB1@x@A ARE:aRA@aR =@타=R"@f@ |IҁIɴ B@B@x@A AQE:aQ QaQ =Qi>=Q @=@ # @`AB%BJ B@JB*}@x@AAJEG J@==5R.-  `$- = {H@ -$@@ $- @jI  IҪ B@aB@x@A AQEαaQ[ b =Q=*Q-@ @,JvJA# B@aRB@x@A AREUmRaR =Rr=R%@p@"3ΟIoI Q B@QB\+b@x@A AQE(QaQ =Q,=Q%>*@+@ <BPB B@JB@x@AAJEb e= ={"@t@"II B@aB@x@A AEfL@T71 . "8=֙R u @u7sIg`ud uu(Nu=]U uu7cQu{ q .=nV- \`k2 =g kW7r5=\?[u7_=au7a=\-h$={t kW74=ⲑ] ۀ7ba=hnu70%A[(%KY =ƺ_ b @iĀ@,JàJ ` B@eB@x@A EdE|aRA@,aR =@p=R*@@  IXIQ B@B@x@A AQEC8QaQ =Q;=~ @Y@$B: J B@JB@x@AAJE@==uQ( -].KD-{.{.&@ @!qB\KViV  B@aB E,dr |, `x@9 A^ @EPb0 > B3 =@`=t|`@L^!5^/@@,JCJ@ B@Bd@x@A AR E1}a aR =R͟=:R% @M@+ IIQ B@QB@x@A AQEV~Q;=Gmbs A`DA =[{#$A@W@  ^~$/ @% @dAVTV % V A B@aBA@x@AA  E h]  0@\@ ! @$B@BB$B! Bd B@a B@x@ABE?biN%AJ =J?a@"N@H@,NJJ@N B@RByGB@x@ =AR @`E N&  =R[=R%N@ۼ@+N IGI QN! B@a B[ Q@x@A AQ"E.u@bf # =Qx="$@@@$%Bw B& B@JB@x@AAJ'E0AaJ (=6d-- `D-){6{&*@3@ c% w@F+V,V! Ϻp, B@aB@x@AA-E;Aa)4A. = `[!b ^&/@@,0J*J I1 B@B@x@A AR2Eؠ@Y //AR3 =R܀="4@Lۀ@+5IڀI¤6 B@QB!e@x@A AQ7Ebf Q00`=8=^Bbu9 =㙀{"A:@@ )#AKA;VVA< B@aB@x@AB= E K *Wb*>@@ ! @ $B@B?BB B@ B@a B@x@ABAE*Ob17}B =J?`=J"BJ! C@;>@,NDJ=JA#_NE B@RB@x@A ARFE 88aRG =R="NH@@+NIII Qa>aJ B@QBN@x@A AQKEbf Q99aQL =Qɵ=Q#k#M@(@$NB B$ JO B@JB@@@x@AAJPEmJ::@=Q=L `=R =@q{-&X1uS@n@ $TV;V^9! -NU B@`B@x@AA^VE~{|0W=gJc`@ X7o_0Y=zKu0Z7&)bJx;œ[ =J`u"!J&x\@/@,x]JJAxw>x^ B@b>By T@x@A B>_E &Bœ` =R>Հ=R1Ta@Ӏ@+xbI&I Qxc B@QB0@x@A AQdEbf QCŒe =Q=&xf@@~YhgBq J$ Fh B@JBh@x@AAJiEGaJ JDD@=j=I-Tk =K{%-&xl@H@ x! @%a0qTmV7V! Vxn B@aBx@x@AA^oE"aTEJMPp =^?8Hd@^!5^%q@6@,rJ J@s B@aRB@x@A ARtE@Y KKaRu =R=R%v@+@+wII¤x B@QB@x@A AQyEIbf QLL@=z=ET{ ={#$A|@ū@ A$}V&VA~ B@aB&@x@AD E F  &@@ %b @ $B@BBB! B B@a B@٣Od@x@ABkEfJbMSAJ =@pV e ! @U@ "k"JTJ@ B@BT@x@A AREy a TTaR =R*=!%N@@+NIIaN B@QB@x@A AQEȀ QUUaQ =Q̀=#kQ#@@~Blˀ  B@JB@@x@AAJE VV@==4"Kd- =@戀{ @@ 0! @%! @\0V"V ! VϪ B@`B@x@AA^E @ a W\E = `+u`=^9%@s@ aJrJ  B@B!@x@A ARE+a :]]aR =R0=:R%@*/@+I.IĄ B@QB8T@x@A AQEu Q^^@==  ={#*@@8VV A B@aBs@x@AD E ) @@ %b @ $BG.BB B B@a B@@x@ABkEc_e =@pf@! @ @ NJuJ@N B@B \@x@A AREdJa ffaR =R#O=#%N@M@+NI I QN B@QB@x@A AQEaQ QggaQ =  =Q#kQ*@@~VBZB$ 2 B@B0a@x@AAJEq Jhh =`d- =ǀ{ @}Ā@ !! @VÀV ! VqWT B@aBu@x@AAE3,i"_`w@=={KuOl =WZ`==|ccl&{6 7ib=9`=9$=  @ B.=O`=`A@``@!2@(/ <@ i c @`V <no e> @@@`$ (~ `Q@ @ 2@ @` m!  "@ @ c  !z" "1 W   " @ U<B!`otL ad!jr j@y@Y  @+! !@8`~ @ &T 7 T;`     @] !` mR!c iv   0 V@!s b ` <(`e 'hI`@ASA@E3@Q@@I/A @B@M ; 7@G@1I  @Md @i`D@i"s iYY @(Mu @z i_B@fwupd-1.9.16/plugins/logitech-hidpp/data/lsusb-U0007-bootloader.txt000066400000000000000000000034761460375044200250000ustar00rootroot00000000000000Bus 001 Device 036: ID 046d:aaaa Logitech, Inc. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 32 idVendor 0x046d Logitech, Inc. idProduct 0xaaaa bcdDevice 1.02 iManufacturer 1 iProduct 2 iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 34 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 4 bmAttributes 0x80 (Bus Powered) MaxPower 98mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 25 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 1 fwupd-1.9.16/plugins/logitech-hidpp/data/lsusb-U0007.txt000066400000000000000000000101341460375044200226350ustar00rootroot00000000000000Bus 001 Device 049: ID 046d:c52b Logitech, Inc. Unifying Receiver Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 8 idVendor 0x046d Logitech, Inc. idProduct 0xc52b Unifying Receiver bcdDevice 12.07 iManufacturer 1 Logitech iProduct 2 USB Receiver iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 84 bNumInterfaces 3 bConfigurationValue 1 iConfiguration 4 RQR12.07_B0029 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 98mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 1 Keyboard iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 59 Report Descriptor: (length is 59) Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 8 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 2 Mouse iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 148 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 2 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 93 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 2 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/logitech-hidpp/data/lsusb-U0008-bootloader-old.txt000066400000000000000000000057551460375044200255570ustar00rootroot00000000000000 Bus 003 Device 036: ID 046d:aaac Logitech, Inc. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 32 idVendor 0x046d Logitech, Inc. idProduct 0xaaac bcdDevice 3.01 iManufacturer 1 Logitech iProduct 2 USB BootLoader iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 34 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 4 BOT03.01_B0008 bmAttributes 0x80 (Bus Powered) MaxPower 98mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 25 Report Descriptor: (length is 25) Item(Global): Usage Page, data= [ 0xb0 0xff ] 65456 (null) Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x20 ] 32 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 1 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/logitech-hidpp/data/lsusb-U0008-bootloader.txt000066400000000000000000000057551460375044200250030ustar00rootroot00000000000000 Bus 003 Device 039: ID 046d:aaac Logitech, Inc. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 32 idVendor 0x046d Logitech, Inc. idProduct 0xaaac bcdDevice 3.00 iManufacturer 1 Logitech iProduct 2 USB BootLoader iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 34 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 4 BOT03.00_B0006 bmAttributes 0x80 (Bus Powered) MaxPower 98mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 25 Report Descriptor: (length is 25) Item(Global): Usage Page, data= [ 0xb0 0xff ] 65456 (null) Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x20 ] 32 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 1 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/logitech-hidpp/data/lsusb-U0008-old.txt000066400000000000000000000426521460375044200234240ustar00rootroot00000000000000 Bus 003 Device 033: ID 046d:c52b Logitech, Inc. Unifying Receiver Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 32 idVendor 0x046d Logitech, Inc. idProduct 0xc52b Unifying Receiver bcdDevice 24.01 iManufacturer 1 Logitech iProduct 2 USB Receiver iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 84 bNumInterfaces 3 bConfigurationValue 1 iConfiguration 4 RQR24.01_B0023 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 98mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 1 Keyboard iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 59 Report Descriptor: (length is 59) Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x06 ] 6 Keyboard Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Usage Page, data= [ 0x07 ] 7 Keyboard Item(Local ): Usage Minimum, data= [ 0xe0 ] 224 Control Left Item(Local ): Usage Maximum, data= [ 0xe7 ] 231 GUI Right Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0x01 ] 1 Item(Global): Report Size, data= [ 0x01 ] 1 Item(Global): Report Count, data= [ 0x08 ] 8 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): Input, data= [ 0x03 ] 3 Constant Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x05 ] 5 Item(Global): Usage Page, data= [ 0x08 ] 8 LEDs Item(Local ): Usage Minimum, data= [ 0x01 ] 1 NumLock Item(Local ): Usage Maximum, data= [ 0x05 ] 5 Kana Item(Main ): Output, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x01 ] 1 Item(Global): Report Size, data= [ 0x03 ] 3 Item(Main ): Output, data= [ 0x01 ] 1 Constant Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x06 ] 6 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xa4 0x00 ] 164 Item(Global): Usage Page, data= [ 0x07 ] 7 Keyboard Item(Local ): Usage Minimum, data= [ 0x00 ] 0 No Event Item(Local ): Usage Maximum, data= [ 0xa4 0x00 ] 164 ExSel Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 8 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 2 Mouse iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 148 Report Descriptor: (length is 148) Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x02 ] 2 Mouse Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x02 ] 2 Item(Local ): Usage, data= [ 0x01 ] 1 Pointer Item(Main ): Collection, data= [ 0x00 ] 0 Physical Item(Global): Usage Page, data= [ 0x09 ] 9 Buttons Item(Local ): Usage Minimum, data= [ 0x01 ] 1 Button 1 (Primary) Item(Local ): Usage Maximum, data= [ 0x10 ] 16 (null) Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0x01 ] 1 Item(Global): Report Count, data= [ 0x10 ] 16 Item(Global): Report Size, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Global): Logical Minimum, data= [ 0x01 0xf8 ] 63489 Item(Global): Logical Maximum, data= [ 0xff 0x07 ] 2047 Item(Global): Report Size, data= [ 0x0c ] 12 Item(Global): Report Count, data= [ 0x02 ] 2 Item(Local ): Usage, data= [ 0x30 ] 48 Direction-X Item(Local ): Usage, data= [ 0x31 ] 49 Direction-Y Item(Main ): Input, data= [ 0x06 ] 6 Data Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Logical Minimum, data= [ 0x81 ] 129 Item(Global): Logical Maximum, data= [ 0x7f ] 127 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Local ): Usage, data= [ 0x38 ] 56 Wheel Item(Main ): Input, data= [ 0x06 ] 6 Data Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x0c ] 12 Consumer Item(Local ): Usage, data= [ 0x38 0x02 ] 568 AC Pan Item(Global): Report Count, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x06 ] 6 Data Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x0c ] 12 Consumer Item(Local ): Usage, data= [ 0x01 ] 1 Consumer Control Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x03 ] 3 Item(Global): Report Size, data= [ 0x10 ] 16 Item(Global): Report Count, data= [ 0x02 ] 2 Item(Global): Logical Minimum, data= [ 0x01 ] 1 Item(Global): Logical Maximum, data= [ 0x8c 0x02 ] 652 Item(Local ): Usage Minimum, data= [ 0x01 ] 1 Consumer Control Item(Local ): Usage Maximum, data= [ 0x8c 0x02 ] 652 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x80 ] 128 System Control Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x04 ] 4 Item(Global): Report Size, data= [ 0x02 ] 2 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Global): Logical Minimum, data= [ 0x01 ] 1 Item(Global): Logical Maximum, data= [ 0x03 ] 3 Item(Local ): Usage, data= [ 0x82 ] 130 System Sleep Item(Local ): Usage, data= [ 0x81 ] 129 System Power Down Item(Local ): Usage, data= [ 0x83 ] 131 System Wake Up Item(Main ): Input, data= [ 0x60 ] 96 Data Array Absolute No_Wrap Linear No_Preferred_State Null_State Non_Volatile Bitfield Item(Global): Report Size, data= [ 0x06 ] 6 Item(Main ): Input, data= [ 0x03 ] 3 Constant Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0xbc 0xff ] 65468 (null) Item(Local ): Usage, data= [ 0x88 ] 136 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x08 ] 8 Item(Local ): Usage Minimum, data= [ 0x01 ] 1 (null) Item(Local ): Usage Maximum, data= [ 0xff ] 255 (null) Item(Global): Logical Minimum, data= [ 0x01 ] 1 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 2 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 98 Report Descriptor: (length is 98) Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 (null) Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x10 ] 16 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x06 ] 6 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 (null) Item(Local ): Usage, data= [ 0x02 ] 2 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x11 ] 17 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x13 ] 19 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x02 ] 2 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x02 ] 2 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 (null) Item(Local ): Usage, data= [ 0x04 ] 4 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x20 ] 32 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x0e ] 14 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x41 ] 65 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x41 ] 65 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report ID, data= [ 0x21 ] 33 Item(Global): Report Count, data= [ 0x1f ] 31 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x42 ] 66 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x42 ] 66 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 2 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/logitech-hidpp/data/lsusb-U0008.txt000066400000000000000000000426511460375044200226470ustar00rootroot00000000000000Bus 003 Device 032: ID 046d:c52b Logitech, Inc. Unifying Receiver Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 32 idVendor 0x046d Logitech, Inc. idProduct 0xc52b Unifying Receiver bcdDevice 24.05 iManufacturer 1 Logitech iProduct 2 USB Receiver iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 84 bNumInterfaces 3 bConfigurationValue 1 iConfiguration 4 RQR24.05_B0029 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 98mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 1 Keyboard iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 59 Report Descriptor: (length is 59) Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x06 ] 6 Keyboard Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Usage Page, data= [ 0x07 ] 7 Keyboard Item(Local ): Usage Minimum, data= [ 0xe0 ] 224 Control Left Item(Local ): Usage Maximum, data= [ 0xe7 ] 231 GUI Right Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0x01 ] 1 Item(Global): Report Size, data= [ 0x01 ] 1 Item(Global): Report Count, data= [ 0x08 ] 8 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): Input, data= [ 0x03 ] 3 Constant Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x05 ] 5 Item(Global): Usage Page, data= [ 0x08 ] 8 LEDs Item(Local ): Usage Minimum, data= [ 0x01 ] 1 NumLock Item(Local ): Usage Maximum, data= [ 0x05 ] 5 Kana Item(Main ): Output, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x01 ] 1 Item(Global): Report Size, data= [ 0x03 ] 3 Item(Main ): Output, data= [ 0x01 ] 1 Constant Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x06 ] 6 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xa4 0x00 ] 164 Item(Global): Usage Page, data= [ 0x07 ] 7 Keyboard Item(Local ): Usage Minimum, data= [ 0x00 ] 0 No Event Item(Local ): Usage Maximum, data= [ 0xa4 0x00 ] 164 ExSel Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 8 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 2 Mouse iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 148 Report Descriptor: (length is 148) Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x02 ] 2 Mouse Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x02 ] 2 Item(Local ): Usage, data= [ 0x01 ] 1 Pointer Item(Main ): Collection, data= [ 0x00 ] 0 Physical Item(Global): Usage Page, data= [ 0x09 ] 9 Buttons Item(Local ): Usage Minimum, data= [ 0x01 ] 1 Button 1 (Primary) Item(Local ): Usage Maximum, data= [ 0x10 ] 16 (null) Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0x01 ] 1 Item(Global): Report Count, data= [ 0x10 ] 16 Item(Global): Report Size, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Global): Logical Minimum, data= [ 0x01 0xf8 ] 63489 Item(Global): Logical Maximum, data= [ 0xff 0x07 ] 2047 Item(Global): Report Size, data= [ 0x0c ] 12 Item(Global): Report Count, data= [ 0x02 ] 2 Item(Local ): Usage, data= [ 0x30 ] 48 Direction-X Item(Local ): Usage, data= [ 0x31 ] 49 Direction-Y Item(Main ): Input, data= [ 0x06 ] 6 Data Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Logical Minimum, data= [ 0x81 ] 129 Item(Global): Logical Maximum, data= [ 0x7f ] 127 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Local ): Usage, data= [ 0x38 ] 56 Wheel Item(Main ): Input, data= [ 0x06 ] 6 Data Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x0c ] 12 Consumer Item(Local ): Usage, data= [ 0x38 0x02 ] 568 AC Pan Item(Global): Report Count, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x06 ] 6 Data Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x0c ] 12 Consumer Item(Local ): Usage, data= [ 0x01 ] 1 Consumer Control Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x03 ] 3 Item(Global): Report Size, data= [ 0x10 ] 16 Item(Global): Report Count, data= [ 0x02 ] 2 Item(Global): Logical Minimum, data= [ 0x01 ] 1 Item(Global): Logical Maximum, data= [ 0x8c 0x02 ] 652 Item(Local ): Usage Minimum, data= [ 0x01 ] 1 Consumer Control Item(Local ): Usage Maximum, data= [ 0x8c 0x02 ] 652 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x80 ] 128 System Control Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x04 ] 4 Item(Global): Report Size, data= [ 0x02 ] 2 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Global): Logical Minimum, data= [ 0x01 ] 1 Item(Global): Logical Maximum, data= [ 0x03 ] 3 Item(Local ): Usage, data= [ 0x82 ] 130 System Sleep Item(Local ): Usage, data= [ 0x81 ] 129 System Power Down Item(Local ): Usage, data= [ 0x83 ] 131 System Wake Up Item(Main ): Input, data= [ 0x60 ] 96 Data Array Absolute No_Wrap Linear No_Preferred_State Null_State Non_Volatile Bitfield Item(Global): Report Size, data= [ 0x06 ] 6 Item(Main ): Input, data= [ 0x03 ] 3 Constant Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0xbc 0xff ] 65468 (null) Item(Local ): Usage, data= [ 0x88 ] 136 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x08 ] 8 Item(Local ): Usage Minimum, data= [ 0x01 ] 1 (null) Item(Local ): Usage Maximum, data= [ 0xff ] 255 (null) Item(Global): Logical Minimum, data= [ 0x01 ] 1 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 2 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 98 Report Descriptor: (length is 98) Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 (null) Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x10 ] 16 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x06 ] 6 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x01 ] 1 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 (null) Item(Local ): Usage, data= [ 0x02 ] 2 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x11 ] 17 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x13 ] 19 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x02 ] 2 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x02 ] 2 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Global): Usage Page, data= [ 0x00 0xff ] 65280 (null) Item(Local ): Usage, data= [ 0x04 ] 4 (null) Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Global): Report ID, data= [ 0x20 ] 32 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x0e ] 14 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x41 ] 65 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x41 ] 65 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report ID, data= [ 0x21 ] 33 Item(Global): Report Count, data= [ 0x1f ] 31 Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255 Item(Local ): Usage, data= [ 0x42 ] 66 (null) Item(Main ): Input, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Local ): Usage, data= [ 0x42 ] 66 (null) Item(Main ): Output, data= [ 0x00 ] 0 Data Array Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 2 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader-nordic.c000066400000000000000000000232041460375044200264040ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-logitech-hidpp-bootloader-nordic.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-struct.h" struct _FuLogitechHidppBootloaderNordic { FuLogitechHidppBootloader parent_instance; }; G_DEFINE_TYPE(FuLogitechHidppBootloaderNordic, fu_logitech_hidpp_bootloader_nordic, FU_TYPE_LOGITECH_HIDPP_BOOTLOADER) static gchar * fu_logitech_hidpp_bootloader_nordic_get_hw_platform_id(FuLogitechHidppBootloader *self, GError **error) { g_autoptr(FuLogitechHidppBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); req->cmd = FU_LOGITECH_HIDPP_BOOTLOADER_CMD_GET_HW_PLATFORM_ID; if (!fu_logitech_hidpp_bootloader_request(self, req, error)) { g_prefix_error(error, "failed to get HW ID: "); return NULL; } return g_strndup((const gchar *)req->data, req->len); } static gchar * fu_logitech_hidpp_bootloader_nordic_get_fw_version(FuLogitechHidppBootloader *self, GError **error) { guint16 micro; g_autoptr(FuLogitechHidppBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); req->cmd = FU_LOGITECH_HIDPP_BOOTLOADER_CMD_GET_FW_VERSION; if (!fu_logitech_hidpp_bootloader_request(self, req, error)) { g_prefix_error(error, "failed to get firmware version: "); return NULL; } /* RRRxx.yy_Bzzzz * 012345678901234*/ micro = (guint16)fu_logitech_hidpp_buffer_read_uint8((const gchar *)req->data + 10) << 8; micro += fu_logitech_hidpp_buffer_read_uint8((const gchar *)req->data + 12); return fu_logitech_hidpp_format_version( "RQR", fu_logitech_hidpp_buffer_read_uint8((const gchar *)req->data + 3), fu_logitech_hidpp_buffer_read_uint8((const gchar *)req->data + 6), micro); } static gboolean fu_logitech_hidpp_bootloader_nordic_setup(FuDevice *device, GError **error) { FuLogitechHidppBootloader *self = FU_LOGITECH_HIDPP_BOOTLOADER(device); g_autofree gchar *hw_platform_id = NULL; g_autofree gchar *version_fw = NULL; g_autoptr(GError) error_local = NULL; /* FuLogitechHidppBootloader->setup */ if (!FU_DEVICE_CLASS(fu_logitech_hidpp_bootloader_nordic_parent_class) ->setup(device, error)) return FALSE; /* get MCU */ hw_platform_id = fu_logitech_hidpp_bootloader_nordic_get_hw_platform_id(self, error); if (hw_platform_id == NULL) return FALSE; g_debug("hw-platform-id=%s", hw_platform_id); /* get firmware version, which is not fatal */ version_fw = fu_logitech_hidpp_bootloader_nordic_get_fw_version(self, &error_local); if (version_fw == NULL) { g_warning("failed to get firmware version: %s", error_local->message); fu_device_set_version(device, "RQR12.00_B0000"); } else { fu_device_set_version(device, version_fw); } return TRUE; } static gboolean fu_logitech_hidpp_bootloader_nordic_write_signature(FuLogitechHidppBootloader *self, guint16 addr, guint8 len, const guint8 *data, GError **error) { g_autoptr(FuLogitechHidppBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); req->cmd = 0xC0; req->addr = addr; req->len = len; memcpy(req->data, data, req->len); if (!fu_logitech_hidpp_bootloader_request(self, req, error)) { g_prefix_error(error, "failed to write sig @0x%02x: ", addr); return FALSE; } if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE_RAM_BUFFER_INVALID_ADDR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write @%04x: signature is too big", addr); return FALSE; } return TRUE; } static gboolean fu_logitech_hidpp_bootloader_nordic_write(FuLogitechHidppBootloader *self, guint16 addr, guint8 len, const guint8 *data, GError **error) { g_autoptr(FuLogitechHidppBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); req->cmd = FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE; req->addr = addr; req->len = len; if (req->len > 28) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write @%04x: data length too large %02x", addr, req->len); return FALSE; } memcpy(req->data, data, req->len); if (!fu_logitech_hidpp_bootloader_request(self, req, error)) { g_prefix_error(error, "failed to transfer fw @0x%02x: ", addr); return FALSE; } if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE_INVALID_ADDR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write @%04x: invalid address", addr); return FALSE; } if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE_VERIFY_FAIL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write @%04x: failed to verify flash content", addr); return FALSE; } if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE_NONZERO_START) { g_debug("wrote %d bytes at address %04x, value %02x", req->len, req->addr, req->data[0]); g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write @%04x: only 1 byte write of 0xff supported", addr); return FALSE; } if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE_INVALID_CRC) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write @%04x: invalid CRC", addr); return FALSE; } return TRUE; } static gboolean fu_logitech_hidpp_bootloader_nordic_erase(FuLogitechHidppBootloader *self, guint16 addr, GError **error) { g_autoptr(FuLogitechHidppBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); req->cmd = FU_LOGITECH_HIDPP_BOOTLOADER_CMD_ERASE_PAGE; req->addr = addr; req->len = 0x01; if (!fu_logitech_hidpp_bootloader_request(self, req, error)) { g_prefix_error(error, "failed to erase fw @0x%02x: ", addr); return FALSE; } if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_ERASE_PAGE_INVALID_ADDR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to erase @%04x: invalid page", addr); return FALSE; } if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_ERASE_PAGE_NONZERO_START) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to erase @%04x: byte 0x00 is not 0xff", addr); return FALSE; } return TRUE; } static gboolean fu_logitech_hidpp_bootloader_nordic_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuLogitechHidppBootloader *self = FU_LOGITECH_HIDPP_BOOTLOADER(device); const FuLogitechHidppBootloaderRequest *payload; guint16 addr; g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) reqs = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); if (fu_device_has_private_flag(device, FU_LOGITECH_HIDPP_BOOTLOADER_FLAG_IS_SIGNED)) { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 4, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 13, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "device-write0"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 82, "reset vector"); } else { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 22, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 72, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "device-write0"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 6, "reset-vector"); } /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* erase firmware pages up to the bootloader */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_ERASE); for (addr = fu_logitech_hidpp_bootloader_get_addr_lo(self); addr < fu_logitech_hidpp_bootloader_get_addr_hi(self); addr += fu_logitech_hidpp_bootloader_get_blocksize(self)) { if (!fu_logitech_hidpp_bootloader_nordic_erase(self, addr, error)) return FALSE; } fu_progress_step_done(progress); /* transfer payload */ reqs = fu_logitech_hidpp_bootloader_parse_requests(self, fw, error); if (reqs == NULL) return FALSE; fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 1; i < reqs->len; i++) { gboolean res; payload = g_ptr_array_index(reqs, i); if (payload->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE_SIGNATURE) { res = fu_logitech_hidpp_bootloader_nordic_write_signature(self, payload->addr, payload->len, payload->data, error); } else { res = fu_logitech_hidpp_bootloader_nordic_write(self, payload->addr, payload->len, payload->data, error); } if (!res) return FALSE; fu_progress_set_percentage_full(fu_progress_get_child(progress), i + 1, reqs->len); } fu_progress_step_done(progress); /* send the first managed packet last, excluding the reset vector */ payload = g_ptr_array_index(reqs, 0); if (!fu_logitech_hidpp_bootloader_nordic_write(self, payload->addr + 1, payload->len - 1, payload->data + 1, error)) return FALSE; fu_progress_step_done(progress); /* reset vector */ if (!fu_logitech_hidpp_bootloader_nordic_write(self, 0x0000, 0x01, payload->data, error)) return FALSE; fu_progress_step_done(progress); /* success! */ return TRUE; } static void fu_logitech_hidpp_bootloader_nordic_class_init(FuLogitechHidppBootloaderNordicClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_logitech_hidpp_bootloader_nordic_write_firmware; klass_device->setup = fu_logitech_hidpp_bootloader_nordic_setup; } static void fu_logitech_hidpp_bootloader_nordic_init(FuLogitechHidppBootloaderNordic *self) { } fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader-nordic.h000066400000000000000000000006771460375044200264220ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-logitech-hidpp-bootloader.h" #define FU_TYPE_LOGITECH_HIDPP_BOOTLOADER_NORDIC (fu_logitech_hidpp_bootloader_nordic_get_type()) G_DECLARE_FINAL_TYPE(FuLogitechHidppBootloaderNordic, fu_logitech_hidpp_bootloader_nordic, FU, LOGITECH_HIDPP_BOOTLOADER_NORDIC, FuLogitechHidppBootloader) fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader-texas.c000066400000000000000000000202741460375044200262560ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-logitech-hidpp-bootloader-texas.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-struct.h" struct _FuLogitechHidppBootloaderTexas { FuLogitechHidppBootloader parent_instance; }; G_DEFINE_TYPE(FuLogitechHidppBootloaderTexas, fu_logitech_hidpp_bootloader_texas, FU_TYPE_LOGITECH_HIDPP_BOOTLOADER) static gboolean fu_logitech_hidpp_bootloader_texas_erase_all(FuLogitechHidppBootloader *self, GError **error) { g_autoptr(FuLogitechHidppBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); req->cmd = FU_LOGITECH_HIDPP_BOOTLOADER_CMD_FLASH_RAM; req->len = 0x01; /* magic number */ req->data[0] = 0x00; /* magic number */ if (!fu_logitech_hidpp_bootloader_request(self, req, error)) { g_prefix_error(error, "failed to erase all pages: "); return FALSE; } return TRUE; } static gboolean fu_logitech_hidpp_bootloader_texas_compute_and_test_crc(FuLogitechHidppBootloader *self, GError **error) { g_autoptr(FuLogitechHidppBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); req->cmd = FU_LOGITECH_HIDPP_BOOTLOADER_CMD_FLASH_RAM; req->len = 0x01; /* magic number */ req->data[0] = 0x03; /* magic number */ if (!fu_logitech_hidpp_bootloader_request(self, req, error)) { g_prefix_error(error, "failed to compute and test CRC: "); return FALSE; } if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_FLASH_RAM_WRONG_CRC) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "CRC is incorrect"); return FALSE; } return TRUE; } static gboolean fu_logitech_hidpp_bootloader_texas_flash_ram_buffer(FuLogitechHidppBootloader *self, guint16 addr, GError **error) { g_autoptr(FuLogitechHidppBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); req->cmd = FU_LOGITECH_HIDPP_BOOTLOADER_CMD_FLASH_RAM; req->addr = addr; req->len = 0x01; /* magic number */ req->data[0] = 0x01; /* magic number */ if (!fu_logitech_hidpp_bootloader_request(self, req, error)) { g_prefix_error(error, "failed to flash ram buffer @%04x: ", addr); return FALSE; } if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_FLASH_RAM_INVALID_ADDR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to flash ram buffer @%04x: invalid flash page", addr); return FALSE; } if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_FLASH_RAM_PAGE0_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to flash ram buffer @%04x: invalid App JMP vector", addr); return FALSE; } if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_FLASH_RAM_INVALID_ORDER) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to flash ram buffer @%04x: page flashed before page 0", addr); return FALSE; } return TRUE; } static gboolean fu_logitech_hidpp_bootloader_texas_clear_ram_buffer(FuLogitechHidppBootloader *self, GError **error) { g_autoptr(FuLogitechHidppBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); req->cmd = FU_LOGITECH_HIDPP_BOOTLOADER_CMD_FLASH_RAM; req->addr = 0x0000; req->len = 0x01; /* magic number */ req->data[0] = 0x02; /* magic number */ if (!fu_logitech_hidpp_bootloader_request(self, req, error)) { g_prefix_error(error, "failed to clear ram buffer @%04x: ", req->addr); return FALSE; } return TRUE; } static gboolean fu_logitech_hidpp_bootloader_texas_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuLogitechHidppBootloader *self = FU_LOGITECH_HIDPP_BOOTLOADER(device); const FuLogitechHidppBootloaderRequest *payload; g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) reqs = NULL; g_autoptr(FuLogitechHidppBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); /* progress */ fu_progress_set_id(progress, G_STRLOC); if (fu_device_has_private_flag(device, FU_LOGITECH_HIDPP_BOOTLOADER_FLAG_IS_SIGNED)) { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 3, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, "clear"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 18, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 79, NULL); } else { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 11, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, "clear"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 75, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 12, NULL); } /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* transfer payload */ reqs = fu_logitech_hidpp_bootloader_parse_requests(self, fw, error); if (reqs == NULL) return FALSE; /* erase all flash pages */ if (!fu_logitech_hidpp_bootloader_texas_erase_all(self, error)) return FALSE; fu_progress_step_done(progress); /* set existing RAM buffer to 0xff's */ if (!fu_logitech_hidpp_bootloader_texas_clear_ram_buffer(self, error)) return FALSE; fu_progress_step_done(progress); /* write to RAM buffer */ for (guint i = 0; i < reqs->len; i++) { payload = g_ptr_array_index(reqs, i); /* check size */ if (payload->len != 16) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "payload size invalid @%04x: got 0x%02x", payload->addr, payload->len); return FALSE; } /* build packet */ req->cmd = payload->cmd; /* signature addresses do not need to fit inside 128 bytes */ if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE_SIGNATURE) req->addr = payload->addr; else req->addr = payload->addr % 0x80; req->len = payload->len; if (!fu_memcpy_safe(req->data, req->len, 0x0, /* dst */ payload->data, payload->len, 0x0, /* src */ payload->len, error)) return FALSE; if (!fu_logitech_hidpp_bootloader_request(self, req, error)) { g_prefix_error(error, "failed to write ram buffer @0x%02x: ", req->addr); return FALSE; } if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE_RAM_BUFFER_INVALID_ADDR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write ram buffer @%04x: invalid location", req->addr); return FALSE; } if (req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE_RAM_BUFFER_OVERFLOW) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to write ram buffer @%04x: invalid size 0x%02x", req->addr, req->len); return FALSE; } /* flush RAM buffer to EEPROM */ if ((payload->addr + 0x10) % 0x80 == 0 && req->cmd != FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE_SIGNATURE) { guint16 addr_start = payload->addr - (7 * 0x10); g_debug("addr flush @ 0x%04x for 0x%04x", payload->addr, addr_start); if (!fu_logitech_hidpp_bootloader_texas_flash_ram_buffer(self, addr_start, error)) { g_prefix_error(error, "failed to flash ram buffer @0x%04x: ", addr_start); return FALSE; } } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), i + 1, reqs->len); } fu_progress_step_done(progress); /* check CRC */ if (!fu_logitech_hidpp_bootloader_texas_compute_and_test_crc(self, error)) return FALSE; fu_progress_step_done(progress); /* success! */ return TRUE; } static gboolean fu_logitech_hidpp_bootloader_texas_setup(FuDevice *device, GError **error) { /* FuLogitechHidppBootloader->setup */ if (!FU_DEVICE_CLASS(fu_logitech_hidpp_bootloader_texas_parent_class)->setup(device, error)) return FALSE; fu_device_set_version(device, "RQR24.00_B0000"); return TRUE; } static void fu_logitech_hidpp_bootloader_texas_class_init(FuLogitechHidppBootloaderTexasClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_logitech_hidpp_bootloader_texas_write_firmware; klass_device->setup = fu_logitech_hidpp_bootloader_texas_setup; } static void fu_logitech_hidpp_bootloader_texas_init(FuLogitechHidppBootloaderTexas *self) { } fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader-texas.h000066400000000000000000000006721460375044200262630ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-logitech-hidpp-bootloader.h" #define FU_TYPE_LOGITECH_HIDPP_BOOTLOADER_TEXAS (fu_logitech_hidpp_bootloader_texas_get_type()) G_DECLARE_FINAL_TYPE(FuLogitechHidppBootloaderTexas, fu_logitech_hidpp_bootloader_texas, FU, LOGITECH_HIDPP_BOOTLOADER_TEXAS, FuLogitechHidppBootloader) fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader.c000066400000000000000000000327071460375044200251400ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-logitech-hidpp-bootloader.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-hidpp.h" #include "fu-logitech-hidpp-struct.h" typedef struct { guint16 flash_addr_lo; guint16 flash_addr_hi; guint16 flash_blocksize; } FuLogitechHidppBootloaderPrivate; #define FU_LOGITECH_HIDPP_DEVICE_EP1 0x81 #define FU_LOGITECH_HIDPP_DEVICE_EP3 0x83 G_DEFINE_TYPE_WITH_PRIVATE(FuLogitechHidppBootloader, fu_logitech_hidpp_bootloader, FU_TYPE_HID_DEVICE) #define GET_PRIVATE(o) (fu_logitech_hidpp_bootloader_get_instance_private(o)) static void fu_logitech_hidpp_bootloader_to_string(FuDevice *device, guint idt, GString *str) { FuLogitechHidppBootloader *self = FU_LOGITECH_HIDPP_BOOTLOADER(device); FuLogitechHidppBootloaderPrivate *priv = GET_PRIVATE(self); fu_string_append_kx(str, idt, "FlashAddrHigh", priv->flash_addr_hi); fu_string_append_kx(str, idt, "FlashAddrLow", priv->flash_addr_lo); fu_string_append_kx(str, idt, "FlashBlockSize", priv->flash_blocksize); } FuLogitechHidppBootloaderRequest * fu_logitech_hidpp_bootloader_request_new(void) { FuLogitechHidppBootloaderRequest *req = g_new0(FuLogitechHidppBootloaderRequest, 1); return req; } GPtrArray * fu_logitech_hidpp_bootloader_parse_requests(FuLogitechHidppBootloader *self, GBytes *fw, GError **error) { const gchar *tmp; g_auto(GStrv) lines = NULL; g_autoptr(GPtrArray) reqs = NULL; guint32 last_addr = 0; reqs = g_ptr_array_new_with_free_func(g_free); tmp = g_bytes_get_data(fw, NULL); lines = g_strsplit_set(tmp, "\n\r", -1); for (guint i = 0; lines[i] != NULL; i++) { g_autoptr(FuLogitechHidppBootloaderRequest) payload = NULL; guint8 rec_type = 0x00; guint16 offset = 0x0000; guint16 addr = 0x0; gboolean exit = FALSE; gsize linesz = strlen(lines[i]); /* skip empty lines */ tmp = lines[i]; if (linesz < 5) continue; payload = fu_logitech_hidpp_bootloader_request_new(); payload->len = fu_logitech_hidpp_buffer_read_uint8(tmp + 0x01); if (payload->len > 28) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware data invalid: too large %u bytes", payload->len); return NULL; } if (!fu_firmware_strparse_uint16_safe(tmp, linesz, 0x03, &addr, error)) return NULL; payload->addr = addr; payload->cmd = FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE_RAM_BUFFER; rec_type = fu_logitech_hidpp_buffer_read_uint8(tmp + 0x07); switch (rec_type) { case 0x00: /* data */ break; case 0x01: /* EOF */ exit = TRUE; break; case 0x03: /* start segment address */ /* this is used to specify the start address, it is doesn't matter in this context so we can safely ignore it */ continue; case 0x04: /* extended linear address */ if (!fu_firmware_strparse_uint16_safe(tmp, linesz, 0x09, &offset, error)) return NULL; if (offset != 0x0000) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "extended linear addresses with offset different from " "0 are not supported"); return NULL; } continue; case 0x05: /* start linear address */ /* this is used to specify the start address, it is doesn't matter in this context so we can safely ignore it */ continue; case 0xFD: /* custom - vendor */ /* record type of 0xFD indicates signature data */ payload->cmd = FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE_SIGNATURE; break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "intel hex file record type %02x not supported", rec_type); return NULL; } if (exit) break; /* read the data, but skip the checksum byte */ for (guint j = 0; j < payload->len; j++) { const gchar *ptr = tmp + 0x09 + (j * 2); if (ptr[0] == '\0') { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware data invalid: expected %u bytes", payload->len); return NULL; } payload->data[j] = fu_logitech_hidpp_buffer_read_uint8(ptr); } /* no need to bound check signature addresses */ if (payload->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_WRITE_SIGNATURE) { g_ptr_array_add(reqs, g_steal_pointer(&payload)); continue; } /* skip the bootloader */ if (payload->addr > fu_logitech_hidpp_bootloader_get_addr_hi(self)) { g_debug("skipping write @ %04x", payload->addr); continue; } /* skip the header */ if (payload->addr < fu_logitech_hidpp_bootloader_get_addr_lo(self)) { g_debug("skipping write @ %04x", payload->addr); continue; } /* make sure firmware addresses only go up */ if (payload->addr < last_addr) { g_debug("skipping write @ %04x", payload->addr); continue; } last_addr = payload->addr; /* pending */ g_ptr_array_add(reqs, g_steal_pointer(&payload)); } if (reqs->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware data invalid: no payloads found"); return NULL; } return g_steal_pointer(&reqs); } guint16 fu_logitech_hidpp_bootloader_get_addr_lo(FuLogitechHidppBootloader *self) { FuLogitechHidppBootloaderPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_LOGITECH_HIDPP_BOOTLOADER(self), 0x0000); return priv->flash_addr_lo; } guint16 fu_logitech_hidpp_bootloader_get_addr_hi(FuLogitechHidppBootloader *self) { FuLogitechHidppBootloaderPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_LOGITECH_HIDPP_BOOTLOADER(self), 0x0000); return priv->flash_addr_hi; } guint16 fu_logitech_hidpp_bootloader_get_blocksize(FuLogitechHidppBootloader *self) { FuLogitechHidppBootloaderPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_LOGITECH_HIDPP_BOOTLOADER(self), 0x0000); return priv->flash_blocksize; } static gboolean fu_logitech_hidpp_bootloader_attach(FuDevice *device, FuProgress *progress, GError **error) { FuLogitechHidppBootloader *self = FU_LOGITECH_HIDPP_BOOTLOADER(device); g_autoptr(FuLogitechHidppBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); req->cmd = FU_LOGITECH_HIDPP_BOOTLOADER_CMD_REBOOT; if (!fu_logitech_hidpp_bootloader_request(self, req, error)) { g_prefix_error(error, "failed to attach back to runtime: "); return FALSE; } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_logitech_hidpp_bootloader_set_bl_version(FuLogitechHidppBootloader *self, GError **error) { guint16 build; guint8 major; guint8 minor; g_autofree gchar *version = NULL; g_autoptr(FuLogitechHidppBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); /* call into hardware */ req->cmd = FU_LOGITECH_HIDPP_BOOTLOADER_CMD_GET_BL_VERSION; if (!fu_logitech_hidpp_bootloader_request(self, req, error)) { g_prefix_error(error, "failed to get firmware version: "); return FALSE; } /* BOTxx.yy_Bzzzz * 012345678901234 */ build = (guint16)fu_logitech_hidpp_buffer_read_uint8((const gchar *)req->data + 10) << 8; build += fu_logitech_hidpp_buffer_read_uint8((const gchar *)req->data + 12); major = fu_logitech_hidpp_buffer_read_uint8((const gchar *)req->data + 3); minor = fu_logitech_hidpp_buffer_read_uint8((const gchar *)req->data + 6); version = fu_logitech_hidpp_format_version("BOT", major, minor, build); if (version == NULL) { g_prefix_error(error, "failed to format firmware version: "); return FALSE; } fu_device_set_version_bootloader(FU_DEVICE(self), version); if ((major == 0x01 && minor >= 0x04) || (major == 0x03 && minor >= 0x02)) { fu_device_add_private_flag(FU_DEVICE(self), FU_LOGITECH_HIDPP_BOOTLOADER_FLAG_IS_SIGNED); fu_device_add_protocol(FU_DEVICE(self), "com.logitech.unifyingsigned"); } else { fu_device_add_protocol(FU_DEVICE(self), "com.logitech.unifying"); } return TRUE; } static gboolean fu_logitech_hidpp_bootloader_setup(FuDevice *device, GError **error) { FuLogitechHidppBootloader *self = FU_LOGITECH_HIDPP_BOOTLOADER(device); FuLogitechHidppBootloaderPrivate *priv = GET_PRIVATE(self); g_autoptr(FuLogitechHidppBootloaderRequest) req = fu_logitech_hidpp_bootloader_request_new(); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_logitech_hidpp_bootloader_parent_class)->setup(device, error)) return FALSE; /* get memory map */ req->cmd = FU_LOGITECH_HIDPP_BOOTLOADER_CMD_GET_MEMINFO; if (!fu_logitech_hidpp_bootloader_request(self, req, error)) { g_prefix_error(error, "failed to get meminfo: "); return FALSE; } if (req->len != 0x06) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to get meminfo: invalid size %02x", req->len); return FALSE; } /* parse values */ priv->flash_addr_lo = fu_memread_uint16(req->data + 0, G_BIG_ENDIAN); priv->flash_addr_hi = fu_memread_uint16(req->data + 2, G_BIG_ENDIAN); priv->flash_blocksize = fu_memread_uint16(req->data + 4, G_BIG_ENDIAN); /* get bootloader version */ return fu_logitech_hidpp_bootloader_set_bl_version(self, error); } gboolean fu_logitech_hidpp_bootloader_request(FuLogitechHidppBootloader *self, FuLogitechHidppBootloaderRequest *req, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length = 0; guint8 buf_request[32]; guint8 buf_response[32]; /* build packet */ memset(buf_request, 0x00, sizeof(buf_request)); buf_request[0x00] = req->cmd; buf_request[0x01] = req->addr >> 8; buf_request[0x02] = req->addr & 0xff; buf_request[0x03] = req->len; if (!fu_memcpy_safe(buf_request, sizeof(buf_request), 0x04, /* dst */ req->data, sizeof(req->data), 0x0, /* src */ sizeof(req->data), error)) return FALSE; /* send request */ fu_dump_raw(G_LOG_DOMAIN, "host->device", buf_request, sizeof(buf_request)); if (usb_device != NULL) { if (!fu_hid_device_set_report(FU_HID_DEVICE(self), 0x0, buf_request, sizeof(buf_request), FU_LOGITECH_HIDPP_DEVICE_TIMEOUT_MS, FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to send data: "); return FALSE; } } /* no response required when rebooting */ if (usb_device != NULL && req->cmd == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_REBOOT) { g_autoptr(GError) error_ignore = NULL; if (!g_usb_device_interrupt_transfer(usb_device, FU_LOGITECH_HIDPP_DEVICE_EP1, buf_response, sizeof(buf_response), &actual_length, FU_LOGITECH_HIDPP_DEVICE_TIMEOUT_MS, NULL, &error_ignore)) { g_debug("ignoring: %s", error_ignore->message); } else { fu_dump_raw(G_LOG_DOMAIN, "device->host", buf_response, actual_length); } return TRUE; } /* get response */ memset(buf_response, 0x00, sizeof(buf_response)); if (usb_device != NULL) { if (!g_usb_device_interrupt_transfer(usb_device, FU_LOGITECH_HIDPP_DEVICE_EP1, buf_response, sizeof(buf_response), &actual_length, FU_LOGITECH_HIDPP_DEVICE_TIMEOUT_MS, NULL, error)) { g_prefix_error(error, "failed to get data: "); return FALSE; } } else { /* emulated */ buf_response[0] = buf_request[0]; if (buf_response[0] == FU_LOGITECH_HIDPP_BOOTLOADER_CMD_GET_MEMINFO) { buf_response[3] = 0x06; /* len */ buf_response[4] = 0x40; /* lo MSB */ buf_response[5] = 0x00; /* lo LSB */ buf_response[6] = 0x6b; /* hi MSB */ buf_response[7] = 0xff; /* hi LSB */ buf_response[8] = 0x00; /* bs MSB */ buf_response[9] = 0x80; /* bs LSB */ } actual_length = sizeof(buf_response); } fu_dump_raw(G_LOG_DOMAIN, "device->host", buf_response, actual_length); /* parse response */ if ((buf_response[0x00] & 0xf0) != req->cmd) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid command response of %02x, expected %02x", buf_response[0x00], req->cmd); return FALSE; } req->cmd = buf_response[0x00]; req->addr = ((guint16)buf_response[0x01] << 8) + buf_response[0x02]; req->len = buf_response[0x03]; if (req->len > 28) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid data size of %02x", req->len); return FALSE; } memset(req->data, 0x00, 28); if (req->len > 0) memcpy(req->data, buf_response + 0x04, req->len); return TRUE; } static void fu_logitech_hidpp_bootloader_init(FuLogitechHidppBootloader *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_icon(FU_DEVICE(self), "preferences-desktop-keyboard"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_name(FU_DEVICE(self), "Unifying Receiver"); fu_device_set_summary(FU_DEVICE(self), "Miniaturised USB wireless receiver (bootloader)"); fu_device_set_remove_delay(FU_DEVICE(self), FU_LOGITECH_HIDPP_DEVICE_TIMEOUT_MS); fu_device_register_private_flag(FU_DEVICE(self), FU_LOGITECH_HIDPP_BOOTLOADER_FLAG_IS_SIGNED, "is-signed"); fu_usb_device_add_interface(FU_USB_DEVICE(self), 0x00); } static void fu_logitech_hidpp_bootloader_class_init(FuLogitechHidppBootloaderClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_logitech_hidpp_bootloader_to_string; klass_device->attach = fu_logitech_hidpp_bootloader_attach; klass_device->setup = fu_logitech_hidpp_bootloader_setup; } fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-bootloader.h000066400000000000000000000031101460375044200251270ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_LOGITECH_HIDPP_BOOTLOADER (fu_logitech_hidpp_bootloader_get_type()) G_DECLARE_DERIVABLE_TYPE(FuLogitechHidppBootloader, fu_logitech_hidpp_bootloader, FU, LOGITECH_HIDPP_BOOTLOADER, FuHidDevice) struct _FuLogitechHidppBootloaderClass { FuHidDeviceClass parent_class; }; /** * FU_LOGITECH_HIDPP_BOOTLOADER_FLAG_IS_SIGNED: * * Device requires signed firmware. * * Since: 1.7.0 */ #define FU_LOGITECH_HIDPP_BOOTLOADER_FLAG_IS_SIGNED (1 << 0) /* packet to and from device */ typedef struct __attribute__((packed)) { guint8 cmd; guint16 addr; guint8 len; guint8 data[28]; } FuLogitechHidppBootloaderRequest; FuLogitechHidppBootloaderRequest * fu_logitech_hidpp_bootloader_request_new(void); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuLogitechHidppBootloaderRequest, g_free); #pragma clang diagnostic pop GPtrArray * fu_logitech_hidpp_bootloader_parse_requests(FuLogitechHidppBootloader *self, GBytes *fw, GError **error); gboolean fu_logitech_hidpp_bootloader_request(FuLogitechHidppBootloader *self, FuLogitechHidppBootloaderRequest *req, GError **error); guint16 fu_logitech_hidpp_bootloader_get_addr_lo(FuLogitechHidppBootloader *self); guint16 fu_logitech_hidpp_bootloader_get_addr_hi(FuLogitechHidppBootloader *self); guint16 fu_logitech_hidpp_bootloader_get_blocksize(FuLogitechHidppBootloader *self); fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-common.c000066400000000000000000000017501460375044200242700ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-logitech-hidpp-common.h" guint8 fu_logitech_hidpp_buffer_read_uint8(const gchar *str) { guint64 tmp; gchar buf[3] = {0x0, 0x0, 0x0}; memcpy(buf, str, 2); tmp = g_ascii_strtoull(buf, NULL, 16); return tmp; } guint16 fu_logitech_hidpp_buffer_read_uint16(const gchar *str) { guint64 tmp; gchar buf[5] = {0x0, 0x0, 0x0, 0x0, 0x0}; memcpy(buf, str, 4); tmp = g_ascii_strtoull(buf, NULL, 16); return tmp; } gchar * fu_logitech_hidpp_format_version(const gchar *name, guint8 major, guint8 minor, guint16 build) { GString *str = g_string_new(NULL); for (guint i = 0; i < 3; i++) { if (g_ascii_isspace(name[i]) || name[i] == '\0') continue; g_string_append_c(str, name[i]); } g_string_append_printf(str, "%02x.%02x_B%04x", major, minor, build); return g_string_free(str, FALSE); } fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-common.h000066400000000000000000000020761460375044200242770ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_LOGITECH_HIDPP_DEVICE_VID 0x046d #define FU_LOGITECH_HIDPP_DEVICE_PID_RUNTIME 0xc52b #define FU_LOGITECH_HIDPP_DEVICE_PID_BOOTLOADER_NORDIC 0xaaaa #define FU_LOGITECH_HIDPP_DEVICE_PID_BOOTLOADER_NORDIC_PICO 0xaaae #define FU_LOGITECH_HIDPP_DEVICE_PID_BOOTLOADER_TEXAS 0xaaac #define FU_LOGITECH_HIDPP_DEVICE_PID_BOOTLOADER_TEXAS_PICO 0xaaad #define FU_LOGITECH_HIDPP_DEVICE_PID_BOOTLOADER_BOLT 0xab07 /* Signed firmware are very long to verify on the device */ #define FU_LOGITECH_HIDPP_DEVICE_TIMEOUT_MS 30000 /* Polling intervals (ms) */ #define FU_HIDPP_DEVICE_POLLING_INTERVAL 30000 #define FU_HIDPP_RECEIVER_RUNTIME_POLLING_INTERVAL 5000 #define FU_HIDPP_VERSION_BLE 0xFE guint8 fu_logitech_hidpp_buffer_read_uint8(const gchar *str); guint16 fu_logitech_hidpp_buffer_read_uint16(const gchar *str); gchar * fu_logitech_hidpp_format_version(const gchar *name, guint8 major, guint8 minor, guint16 build); fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-device.c000066400000000000000000001342031460375044200242370ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-device.h" #include "fu-logitech-hidpp-hidpp.h" #include "fu-logitech-hidpp-radio.h" #include "fu-logitech-hidpp-runtime-bolt.h" #include "fu-logitech-hidpp-struct.h" typedef struct { guint8 cached_fw_entity; /* * Device index: * - FU_LOGITECH_HIDPP_DEVICE_IDX_RECEIVER for the receiver or BLE devices * - pairing slot for paired Bolt devices. */ guint8 device_idx; guint16 hidpp_pid; guint8 hidpp_version; FuIOChannel *io_channel; gchar *model_id; GPtrArray *feature_index; /* of FuLogitechHidppHidppMap */ } FuLogitechHidppDevicePrivate; typedef struct { guint8 idx; guint16 feature; } FuLogitechHidppHidppMap; G_DEFINE_TYPE_WITH_PRIVATE(FuLogitechHidppDevice, fu_logitech_hidpp_device, FU_TYPE_UDEV_DEVICE) #define GET_PRIVATE(o) (fu_logitech_hidpp_device_get_instance_private(o)) typedef enum { FU_HIDPP_DEVICE_KIND_KEYBOARD, FU_HIDPP_DEVICE_KIND_REMOTE_CONTROL, FU_HIDPP_DEVICE_KIND_NUMPAD, FU_HIDPP_DEVICE_KIND_MOUSE, FU_HIDPP_DEVICE_KIND_TOUCHPAD, FU_HIDPP_DEVICE_KIND_TRACKBALL, FU_HIDPP_DEVICE_KIND_PRESENTER, FU_HIDPP_DEVICE_KIND_RECEIVER, FU_HIDPP_DEVICE_KIND_LAST } FuLogitechHidppDeviceKind; void fu_logitech_hidpp_device_set_device_idx(FuLogitechHidppDevice *self, guint8 device_idx) { FuLogitechHidppDevicePrivate *priv; g_return_if_fail(FU_IS_HIDPP_DEVICE(self)); priv = GET_PRIVATE(self); priv->device_idx = device_idx; } guint16 fu_logitech_hidpp_device_get_hidpp_pid(FuLogitechHidppDevice *self) { FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_HIDPP_DEVICE(self), G_MAXUINT16); return priv->hidpp_pid; } void fu_logitech_hidpp_device_set_hidpp_pid(FuLogitechHidppDevice *self, guint16 hidpp_pid) { FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_HIDPP_DEVICE(self)); priv->hidpp_pid = hidpp_pid; } static void fu_logitech_hidpp_device_set_model_id(FuLogitechHidppDevice *self, const gchar *model_id) { FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_HIDPP_DEVICE(self)); if (g_strcmp0(priv->model_id, model_id) == 0) return; g_free(priv->model_id); priv->model_id = g_strdup(model_id); } static const gchar * fu_logitech_hidpp_device_get_icon(FuLogitechHidppDeviceKind kind) { if (kind == FU_HIDPP_DEVICE_KIND_KEYBOARD) return "input-keyboard"; if (kind == FU_HIDPP_DEVICE_KIND_REMOTE_CONTROL) return "pda"; // ish if (kind == FU_HIDPP_DEVICE_KIND_NUMPAD) return "input-dialpad"; if (kind == FU_HIDPP_DEVICE_KIND_MOUSE) return "input-mouse"; if (kind == FU_HIDPP_DEVICE_KIND_TOUCHPAD) return "input-touchpad"; if (kind == FU_HIDPP_DEVICE_KIND_TRACKBALL) return "input-mouse"; // ish if (kind == FU_HIDPP_DEVICE_KIND_PRESENTER) return "pda"; // ish if (kind == FU_HIDPP_DEVICE_KIND_RECEIVER) return "preferences-desktop-keyboard"; return NULL; } static const gchar * fu_logitech_hidpp_device_get_summary(FuLogitechHidppDeviceKind kind) { if (kind == FU_HIDPP_DEVICE_KIND_KEYBOARD) return "Unifying Keyboard"; if (kind == FU_HIDPP_DEVICE_KIND_REMOTE_CONTROL) return "Unifying Remote Control"; if (kind == FU_HIDPP_DEVICE_KIND_NUMPAD) return "Unifying Number Pad"; if (kind == FU_HIDPP_DEVICE_KIND_MOUSE) return "Unifying Mouse"; if (kind == FU_HIDPP_DEVICE_KIND_TOUCHPAD) return "Unifying Touchpad"; if (kind == FU_HIDPP_DEVICE_KIND_TRACKBALL) return "Unifying Trackball"; if (kind == FU_HIDPP_DEVICE_KIND_PRESENTER) return "Unifying Presenter"; if (kind == FU_HIDPP_DEVICE_KIND_RECEIVER) return "Unifying Receiver"; return NULL; } static gboolean fu_logitech_hidpp_device_ping(FuLogitechHidppDevice *self, GError **error) { FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); gdouble version; g_autoptr(GError) error_local = NULL; g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); GPtrArray *children = NULL; /* handle failure */ msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = priv->device_idx; msg->sub_id = 0x00; /* rootIndex */ msg->function_id = 0x01 << 4; /* ping */ msg->data[0] = 0x00; msg->data[1] = 0x00; msg->data[2] = 0xaa; /* user-selected value */ msg->hidpp_version = priv->hidpp_version; if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, &error_local)) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { priv->hidpp_version = 1; return TRUE; } if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE)) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNREACHABLE); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* device no longer asleep */ fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNREACHABLE); children = fu_device_get_children(FU_DEVICE(self)); for (guint i = 0; i < children->len; i++) { FuDevice *radio = g_ptr_array_index(children, i); fu_device_remove_flag(radio, FWUPD_DEVICE_FLAG_UNREACHABLE); } /* if the device index is unset, grab it from the reply */ if (priv->device_idx == FU_LOGITECH_HIDPP_DEVICE_IDX_WIRED && msg->device_id != FU_LOGITECH_HIDPP_DEVICE_IDX_WIRED) { priv->device_idx = msg->device_id; g_debug("device index is %02x", priv->device_idx); } /* format version in BCD format */ if (priv->hidpp_version != FU_HIDPP_VERSION_BLE) { version = (gdouble)msg->data[0] + ((gdouble)msg->data[1]) / 100.f; priv->hidpp_version = (guint)version; } /* success */ return TRUE; } static gboolean fu_logitech_hidpp_device_close(FuDevice *device, GError **error) { FuLogitechHidppDevice *self = FU_HIDPP_DEVICE(device); FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); if (priv->io_channel != NULL) { if (!fu_io_channel_shutdown(priv->io_channel, error)) return FALSE; g_clear_object(&priv->io_channel); } return TRUE; } static gboolean fu_logitech_hidpp_device_poll(FuDevice *device, GError **error) { FuLogitechHidppDevice *self = FU_HIDPP_DEVICE(device); FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); const guint timeout = 1; /* ms */ g_autoptr(GError) error_local = NULL; g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); g_autoptr(FuDeviceLocker) locker = NULL; /* open */ locker = fu_device_locker_new(self, error); if (locker == NULL) return FALSE; /* flush pending data */ msg->device_id = priv->device_idx; msg->hidpp_version = priv->hidpp_version; if (!fu_logitech_hidpp_receive(priv->io_channel, msg, timeout, &error_local)) { if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) { g_warning("failed to get pending read: %s", error_local->message); return TRUE; } /* no data to receive */ g_clear_error(&error_local); } /* just ping */ if (!fu_logitech_hidpp_device_ping(self, &error_local)) { g_warning("failed to ping %s: %s", fu_device_get_name(FU_DEVICE(self)), error_local->message); return TRUE; } /* this is the first time the device has been active */ if (priv->feature_index->len == 0) { fu_device_probe_invalidate(FU_DEVICE(self)); if (!fu_device_setup(FU_DEVICE(self), error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_hidpp_device_open(FuDevice *device, GError **error) { FuLogitechHidppDevice *self = FU_HIDPP_DEVICE(device); FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); const gchar *devpath = fu_udev_device_get_device_file(FU_UDEV_DEVICE(device)); /* open */ priv->io_channel = fu_io_channel_new_file(devpath, error); if (priv->io_channel == NULL) return FALSE; return TRUE; } static void fu_logitech_hidpp_map_to_string(FuLogitechHidppHidppMap *map, guint idt, GString *str) { g_autofree gchar *title = g_strdup_printf("Feature%02x", map->idx); g_autofree gchar *tmp = g_strdup_printf("%s [0x%04x]", fu_logitech_hidpp_feature_to_string(map->feature), map->feature); fu_string_append(str, idt, title, tmp); } static void fu_logitech_hidpp_device_to_string(FuDevice *device, guint idt, GString *str) { FuLogitechHidppDevice *self = FU_HIDPP_DEVICE(device); FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); /* FuUdevDevice->to_string */ FU_DEVICE_CLASS(fu_logitech_hidpp_device_parent_class)->to_string(device, idt, str); fu_string_append_ku(str, idt, "HidppVersion", priv->hidpp_version); fu_string_append_ku(str, idt, "HidppPid", priv->hidpp_pid); fu_string_append_kx(str, idt, "DeviceIdx", priv->device_idx); fu_string_append(str, idt, "ModelId", priv->model_id); for (guint i = 0; i < priv->feature_index->len; i++) { FuLogitechHidppHidppMap *map = g_ptr_array_index(priv->feature_index, i); fu_logitech_hidpp_map_to_string(map, idt, str); } } static guint8 fu_logitech_hidpp_device_feature_get_idx(FuLogitechHidppDevice *self, guint16 feature) { FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); for (guint i = 0; i < priv->feature_index->len; i++) { FuLogitechHidppHidppMap *map = g_ptr_array_index(priv->feature_index, i); if (map->feature == feature) return map->idx; } return 0x00; } static gboolean fu_logitech_hidpp_device_create_radio_child(FuLogitechHidppDevice *self, guint8 entity, guint16 build, GError **error) { FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); FuContext *ctx = fu_device_get_context(FU_DEVICE(self)); g_autofree gchar *instance_id = NULL; g_autofree gchar *logical_id = NULL; g_autofree gchar *radio_version = NULL; g_autoptr(FuLogitechHidppRadio) radio = NULL; GPtrArray *children = fu_device_get_children(FU_DEVICE(self)); /* sanity check */ if (priv->model_id == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "model ID not set"); return FALSE; } radio_version = g_strdup_printf("0x%.4x", build); radio = fu_logitech_hidpp_radio_new(ctx, entity); fu_device_set_physical_id(FU_DEVICE(radio), fu_device_get_physical_id(FU_DEVICE(self))); /* * Use the parent logical id as well as the model id for the * logical id of the radio child device. This allows the radio * devices of two devices of the same type (same device type, * BLE mode) to coexist correctly. */ logical_id = g_strdup_printf("%s-%s", fu_device_get_logical_id(FU_DEVICE(self)), priv->model_id); fu_device_set_logical_id(FU_DEVICE(radio), logical_id); instance_id = g_strdup_printf("HIDRAW\\VEN_%04X&MOD_%s&ENT_05", (guint)FU_LOGITECH_HIDPP_DEVICE_VID, priv->model_id); fu_device_add_instance_id(FU_DEVICE(radio), instance_id); fu_device_set_version(FU_DEVICE(radio), radio_version); if (!fu_device_setup(FU_DEVICE(radio), error)) return FALSE; /* remove old radio device if it already existed */ for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index(children, i); if (g_strcmp0(fu_device_get_physical_id(FU_DEVICE(radio)), fu_device_get_physical_id(child)) == 0 && g_strcmp0(fu_device_get_logical_id(FU_DEVICE(radio)), fu_device_get_logical_id(child)) == 0) { fu_device_remove_child(FU_DEVICE(self), child); break; } } fu_device_add_child(FU_DEVICE(self), FU_DEVICE(radio)); return TRUE; } static gboolean fu_logitech_hidpp_device_fetch_firmware_info(FuLogitechHidppDevice *self, GError **error) { guint8 idx; guint8 entity_count; FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); gboolean radio_ok = FALSE; /* get the feature index */ idx = fu_logitech_hidpp_device_feature_get_idx(self, FU_LOGITECH_HIDPP_FEATURE_I_FIRMWARE_INFO); if (idx == 0x00) return TRUE; /* get the entity count */ msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = priv->device_idx; msg->sub_id = idx; msg->function_id = 0x00 << 4; /* getCount */ msg->hidpp_version = priv->hidpp_version; if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, error)) { g_prefix_error(error, "failed to get firmware count: "); return FALSE; } entity_count = msg->data[0]; g_debug("firmware entity count is %u", entity_count); /* get firmware, bootloader, hardware versions */ for (guint8 i = 0; i < entity_count; i++) { guint16 build; g_autofree gchar *version = NULL; g_autofree gchar *name = NULL; msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = priv->device_idx; msg->sub_id = idx; msg->function_id = 0x01 << 4; /* getInfo */ msg->data[0] = i; if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, error)) { g_prefix_error(error, "failed to get firmware info: "); return FALSE; } if (msg->data[1] == 0x00 && msg->data[2] == 0x00 && msg->data[3] == 0x00 && msg->data[4] == 0x00 && msg->data[5] == 0x00 && msg->data[6] == 0x00 && msg->data[7] == 0x00) { g_debug("no version set for entity %u", i); continue; } name = g_strdup_printf("%c%c%c", msg->data[1], msg->data[2], msg->data[3]); build = ((guint16)msg->data[6]) << 8 | msg->data[7]; version = fu_logitech_hidpp_format_version(name, msg->data[4], msg->data[5], build); g_debug("firmware entity 0x%02x version is %s", i, version); if (msg->data[0] == 0) { fu_device_set_version(FU_DEVICE(self), version); priv->cached_fw_entity = i; } else if (msg->data[0] == 1) { fu_device_set_version_bootloader(FU_DEVICE(self), version); } else if (msg->data[0] == 2) { fu_device_set_metadata(FU_DEVICE(self), "version-hw", version); } else if (msg->data[0] == 5 && fu_device_has_private_flag(FU_DEVICE(self), FU_LOGITECH_HIDPP_DEVICE_FLAG_ADD_RADIO)) { if (!fu_logitech_hidpp_device_create_radio_child(self, i, build, error)) { g_prefix_error(error, "failed to create radio: "); return FALSE; } radio_ok = TRUE; } } /* the device is probably in bootloader mode and the last SoftDevice FW upgrade failed */ if (fu_device_has_private_flag(FU_DEVICE(self), FU_LOGITECH_HIDPP_DEVICE_FLAG_ADD_RADIO) && !radio_ok) { g_debug("no radio found, creating a fake one for recovery"); if (!fu_logitech_hidpp_device_create_radio_child(self, 1, 0, error)) { g_prefix_error(error, "failed to create radio: "); return FALSE; } } /* not an error, the device just doesn't support this */ return TRUE; } static gboolean fu_logitech_hidpp_device_fetch_model_id(FuLogitechHidppDevice *self, GError **error) { FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); guint8 idx; g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); g_autoptr(GString) str = g_string_new(NULL); /* get the (optional) feature index */ idx = fu_logitech_hidpp_device_feature_get_idx(self, FU_LOGITECH_HIDPP_FEATURE_I_FIRMWARE_INFO); if (idx == 0x00) return TRUE; msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = priv->device_idx; msg->sub_id = idx; msg->function_id = 0x00 << 4; /* getDeviceInfo */ msg->hidpp_version = priv->hidpp_version; if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, error)) { g_prefix_error(error, "failed to get the model ID: "); return FALSE; } /* ignore extendedModelID in data[13] */ for (guint i = 7; i < 13; i++) g_string_append_printf(str, "%02X", msg->data[i]); fu_logitech_hidpp_device_set_model_id(self, str->str); /* add one more instance ID */ fu_device_add_instance_u16(FU_DEVICE(self), "VEN", FU_LOGITECH_HIDPP_DEVICE_VID); fu_device_add_instance_str(FU_DEVICE(self), "MOD", priv->model_id); return fu_device_build_instance_id(FU_DEVICE(self), error, "HIDRAW", "VEN", "MOD", NULL); } static gboolean fu_logitech_hidpp_device_fetch_battery_level(FuLogitechHidppDevice *self, GError **error) { FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); /* try using HID++2.0 */ if (priv->hidpp_version >= 2.f) { guint8 idx; /* try the Unified Battery feature first */ idx = fu_logitech_hidpp_device_feature_get_idx( self, FU_LOGITECH_HIDPP_FEATURE_UNIFIED_BATTERY); if (idx != 0x00) { gboolean socc = FALSE; /* state of charge capability */ g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = priv->device_idx; msg->sub_id = idx; msg->function_id = 0x00 << 4; /* get_capabilities */ msg->hidpp_version = priv->hidpp_version; if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, error)) { g_prefix_error(error, "failed to get battery info: "); return FALSE; } if (msg->data[1] & 0x02) socc = TRUE; msg->function_id = 0x01 << 4; /* get_status */ if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, error)) { g_prefix_error(error, "failed to get battery info: "); return FALSE; } if (socc) { fu_device_set_battery_level(FU_DEVICE(self), msg->data[0]); } else { switch (msg->data[1]) { case 1: /* critical */ fu_device_set_battery_level(FU_DEVICE(self), 5); break; case 2: /* low */ fu_device_set_battery_level(FU_DEVICE(self), 20); break; case 4: /* good */ fu_device_set_battery_level(FU_DEVICE(self), 55); break; case 8: /* full */ fu_device_set_battery_level(FU_DEVICE(self), 90); break; default: g_warning("unknown battery level: 0x%02x", msg->data[1]); break; } } return TRUE; } /* fall back to the legacy Battery Level feature */ idx = fu_logitech_hidpp_device_feature_get_idx( self, FU_LOGITECH_HIDPP_FEATURE_BATTERY_LEVEL_STATUS); if (idx != 0x00) { g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = priv->device_idx; msg->sub_id = idx; msg->function_id = 0x00 << 4; /* GetBatteryLevelStatus */ msg->hidpp_version = priv->hidpp_version; if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, error)) { g_prefix_error(error, "failed to get battery info: "); return FALSE; } if (msg->data[0] != 0x00) fu_device_set_battery_level(FU_DEVICE(self), msg->data[0]); return TRUE; } } /* try HID++1.0 battery mileage */ if (priv->hidpp_version == 1.f) { g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = priv->device_idx; msg->sub_id = FU_LOGITECH_HIDPP_SUBID_GET_REGISTER; msg->function_id = FU_LOGITECH_HIDPP_REGISTER_BATTERY_MILEAGE << 4; msg->hidpp_version = priv->hidpp_version; if (fu_logitech_hidpp_transfer(priv->io_channel, msg, NULL)) { if (msg->data[0] != 0x7F) fu_device_set_battery_level(FU_DEVICE(self), msg->data[0]); else g_warning("unknown battery level: 0x%02x", msg->data[0]); return TRUE; } /* try HID++1.0 battery status instead */ msg->function_id = FU_LOGITECH_HIDPP_REGISTER_BATTERY_STATUS << 4; if (fu_logitech_hidpp_transfer(priv->io_channel, msg, NULL)) { switch (msg->data[0]) { case 1: /* 0 - 10 */ fu_device_set_battery_level(FU_DEVICE(self), 5); break; case 3: /* 11 - 30 */ fu_device_set_battery_level(FU_DEVICE(self), 20); break; case 5: /* 31 - 80 */ fu_device_set_battery_level(FU_DEVICE(self), 55); break; case 7: /* 81 - 100 */ fu_device_set_battery_level(FU_DEVICE(self), 90); break; default: g_warning("unknown battery percentage: 0x%02x", msg->data[0]); break; } return TRUE; } } /* not an error, the device just doesn't support any of the methods */ return TRUE; } static gboolean fu_logitech_hidpp_feature_search(FuDevice *device, guint16 feature, GError **error) { FuLogitechHidppDevice *self = FU_HIDPP_DEVICE(device); FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); FuLogitechHidppHidppMap *map; g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); /* find the idx for the feature */ msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = priv->device_idx; msg->sub_id = 0x00; /* rootIndex */ msg->function_id = 0x00 << 4; /* getFeature */ msg->data[0] = feature >> 8; msg->data[1] = feature; msg->data[2] = 0x00; msg->hidpp_version = priv->hidpp_version; if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, error)) { g_prefix_error(error, "failed to get idx for feature %s [0x%04x]: ", fu_logitech_hidpp_feature_to_string(feature), feature); return FALSE; } /* zero index */ if (msg->data[0] == 0x00) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "feature %s [0x%04x] not found", fu_logitech_hidpp_feature_to_string(feature), feature); return FALSE; } /* add to map */ map = g_new0(FuLogitechHidppHidppMap, 1); map->idx = msg->data[0]; map->feature = feature; g_ptr_array_add(priv->feature_index, map); g_debug("added feature %s [0x%04x] as idx %02x", fu_logitech_hidpp_feature_to_string(feature), feature, map->idx); return TRUE; } static gboolean fu_logitech_hidpp_device_probe(FuDevice *device, GError **error) { FuLogitechHidppDevice *self = FU_HIDPP_DEVICE(device); FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); /* check the kernel has CONFIG_HIDRAW */ if (!g_file_test("/sys/class/hidraw", G_FILE_TEST_IS_DIR)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no kernel support for CONFIG_HIDRAW"); return FALSE; } /* set the physical ID */ if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error)) return FALSE; /* nearly... */ fu_device_add_vendor_id(device, "USB:0x046D"); /* * All devices connected to a Bolt receiver share the same * physical id, make them unique by using their pairing slot * (device index) as a basis for their logical id. */ if (priv->device_idx != FU_LOGITECH_HIDPP_DEVICE_IDX_WIRED && priv->device_idx != FU_LOGITECH_HIDPP_DEVICE_IDX_RECEIVER) { g_autoptr(GString) id_str = g_string_new(NULL); g_string_append_printf(id_str, "DEV_IDX=%d", priv->device_idx); fu_device_set_logical_id(device, id_str->str); } /* this is a non-standard extension */ fu_device_add_instance_u16(FU_DEVICE(self), "VID", fu_udev_device_get_vendor(FU_UDEV_DEVICE(device))); fu_device_add_instance_u16(FU_DEVICE(self), "PID", fu_udev_device_get_model(FU_UDEV_DEVICE(device))); return fu_device_build_instance_id(FU_DEVICE(self), error, "UFY", "VID", "PID", NULL); } static gboolean fu_logitech_hidpp_device_setup(FuDevice *device, GError **error) { FuLogitechHidppDevice *self = FU_HIDPP_DEVICE(device); FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); guint8 idx; const guint16 map_features[] = {FU_LOGITECH_HIDPP_FEATURE_GET_DEVICE_NAME_TYPE, FU_LOGITECH_HIDPP_FEATURE_I_FIRMWARE_INFO, FU_LOGITECH_HIDPP_FEATURE_BATTERY_LEVEL_STATUS, FU_LOGITECH_HIDPP_FEATURE_UNIFIED_BATTERY, FU_LOGITECH_HIDPP_FEATURE_DFU_CONTROL, FU_LOGITECH_HIDPP_FEATURE_DFU_CONTROL_SIGNED, FU_LOGITECH_HIDPP_FEATURE_DFU_CONTROL_BOLT, FU_LOGITECH_HIDPP_FEATURE_DFU, FU_LOGITECH_HIDPP_FEATURE_ROOT}; if (fu_device_has_private_flag(device, FU_LOGITECH_HIDPP_DEVICE_FLAG_BLE)) { priv->hidpp_version = FU_HIDPP_VERSION_BLE; priv->device_idx = FU_LOGITECH_HIDPP_DEVICE_IDX_RECEIVER; /* * Set the logical ID for BLE devices. Note that for BLE * devices, physical_id = HID_PHYS = MAC of the BT adapter, * logical_id = HID_UNIQ = MAC of the device. The physical id is * not enough to differentiate two BLE devices connected to the * same adapter. This is done here because private flags * are not loaded when the probe method runs, so we * can't tell the device is in BLE mode. */ if (!fu_udev_device_set_logical_id(FU_UDEV_DEVICE(device), "hid", error)) return FALSE; /* * BLE devices might not be ready for ping right after * they come up -> wait a bit before pinging. */ fu_device_sleep(device, 1000); /* ms */ } if (fu_device_has_private_flag(device, FU_LOGITECH_HIDPP_DEVICE_FLAG_FORCE_RECEIVER_ID)) priv->device_idx = FU_LOGITECH_HIDPP_DEVICE_IDX_RECEIVER; /* ping device to get HID++ version */ if (!fu_logitech_hidpp_device_ping(self, error)) return FALSE; /* did not get ID */ if (priv->device_idx == FU_LOGITECH_HIDPP_DEVICE_IDX_WIRED) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no HID++ ID"); return FALSE; } /* add known root for HID++2.0 */ g_ptr_array_set_size(priv->feature_index, 0); if (priv->hidpp_version >= 2.f) { FuLogitechHidppHidppMap *map = g_new0(FuLogitechHidppHidppMap, 1); map->idx = 0x00; map->feature = FU_LOGITECH_HIDPP_FEATURE_ROOT; g_ptr_array_add(priv->feature_index, map); } /* map some *optional* HID++2.0 features we might use */ for (guint i = 0; map_features[i] != FU_LOGITECH_HIDPP_FEATURE_ROOT; i++) { g_autoptr(GError) error_local = NULL; if (!fu_logitech_hidpp_feature_search(device, map_features[i], &error_local)) { g_debug("%s", error_local->message); if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT) || g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE)) { /* timed out, so not trying any more */ break; } } } /* get the model ID, typically something like B3630000000000 */ if (!fu_logitech_hidpp_device_fetch_model_id(self, error)) return FALSE; /* try using HID++2.0 */ idx = fu_logitech_hidpp_device_feature_get_idx( self, FU_LOGITECH_HIDPP_FEATURE_GET_DEVICE_NAME_TYPE); if (idx != 0x00) { const gchar *tmp; g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = priv->device_idx; msg->sub_id = idx; msg->function_id = 0x02 << 4; /* getDeviceType */ msg->hidpp_version = priv->hidpp_version; if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, error)) { g_prefix_error(error, "failed to get device type: "); return FALSE; } /* add nice-to-have data */ tmp = fu_logitech_hidpp_device_get_summary(msg->data[0]); if (tmp != NULL) fu_device_set_summary(FU_DEVICE(device), tmp); tmp = fu_logitech_hidpp_device_get_icon(msg->data[0]); if (tmp != NULL) fu_device_add_icon(FU_DEVICE(device), tmp); } idx = fu_logitech_hidpp_device_feature_get_idx(self, FU_LOGITECH_HIDPP_FEATURE_DFU_CONTROL); if (idx != 0x00) { fu_device_add_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_remove_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_add_protocol(FU_DEVICE(self), "com.logitech.unifying"); } idx = fu_logitech_hidpp_device_feature_get_idx(self, FU_LOGITECH_HIDPP_FEATURE_DFU_CONTROL_BOLT); if (idx == 0x00) idx = fu_logitech_hidpp_device_feature_get_idx( self, FU_LOGITECH_HIDPP_FEATURE_DFU_CONTROL_SIGNED); if (idx != 0x00) { /* check the feature is available */ g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = priv->device_idx; msg->sub_id = idx; msg->function_id = 0x00 << 4; /* getDfuStatus */ msg->hidpp_version = priv->hidpp_version; if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, error)) { g_prefix_error(error, "failed to get DFU status: "); return FALSE; } if ((msg->data[2] & 0x01) > 0) { g_warning("DFU mode not available"); } else { fu_device_remove_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } fu_device_add_protocol(FU_DEVICE(device), "com.logitech.unifyingsigned"); fu_device_add_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); } idx = fu_logitech_hidpp_device_feature_get_idx(self, FU_LOGITECH_HIDPP_FEATURE_DFU); if (idx != 0x00) { fu_device_add_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); if (fu_device_get_version(device) == NULL) { g_info("repairing device in bootloader mode"); fu_device_set_version(FU_DEVICE(device), "MPK00.00_B0000"); } /* we do not actually know which protocol when in recovery mode, * so force the metadata to have the specific regex set up */ fu_device_add_protocol(FU_DEVICE(self), "com.logitech.unifying"); fu_device_add_protocol(FU_DEVICE(self), "com.logitech.unifyingsigned"); } /* get the firmware information */ if (!fu_logitech_hidpp_device_fetch_firmware_info(self, error)) return FALSE; /* get the battery level */ if (!fu_logitech_hidpp_device_fetch_battery_level(self, error)) return FALSE; /* poll for pings to track active state */ fu_device_set_poll_interval(device, FU_HIDPP_DEVICE_POLLING_INTERVAL); return TRUE; } static gboolean fu_logitech_hidpp_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuLogitechHidppDevice *self = FU_HIDPP_DEVICE(device); FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); guint8 idx; g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in bootloader mode, skipping"); return TRUE; } /* these may require user action */ idx = fu_logitech_hidpp_device_feature_get_idx(self, FU_LOGITECH_HIDPP_FEATURE_DFU_CONTROL_BOLT); if (idx == 0x00) idx = fu_logitech_hidpp_device_feature_get_idx(self, FU_LOGITECH_HIDPP_FEATURE_DFU_CONTROL); if (idx != 0x00) { FuDevice *parent; g_autoptr(FwupdRequest) request = fwupd_request_new(); g_autoptr(GError) error_local = NULL; msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_LONG; msg->device_id = priv->device_idx; msg->sub_id = idx; msg->function_id = 0x01 << 4; /* setDfuControl */ msg->data[0] = 0x01; /* enterDfu */ msg->data[1] = 0x00; /* dfuControlParam */ msg->data[2] = 0x00; /* unused */ msg->data[3] = 0x00; /* unused */ msg->data[4] = 'D'; msg->data[5] = 'F'; msg->data[6] = 'U'; msg->hidpp_version = priv->hidpp_version; msg->flags = FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_SUB_ID | FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_LONGER_TIMEOUT; if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, &error_local)) { if (fu_device_has_private_flag( device, FU_LOGITECH_HIDPP_DEVICE_FLAG_NO_REQUEST_REQUIRED)) { g_debug("ignoring %s", error_local->message); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to put device into DFU mode: "); return FALSE; } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* so we detect off then on */ parent = fu_device_get_parent(device); if (parent != NULL) fu_device_set_poll_interval(parent, 500); /* generate a message if not already set */ if (!fu_device_has_private_flag( device, FU_LOGITECH_HIDPP_DEVICE_FLAG_NO_REQUEST_REQUIRED)) { if (fu_device_get_update_message(device) == NULL) { g_autofree gchar *str = NULL; str = g_strdup_printf( "%s needs to be manually restarted to complete the update. " "Please turn it off and on.", fu_device_get_name(device)); fu_device_set_update_message(device, str); } fwupd_request_set_message(request, fu_device_get_update_message(device)); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG); if (!fu_device_emit_request(device, request, progress, error)) return FALSE; } return TRUE; } /* this can reboot all by itself */ idx = fu_logitech_hidpp_device_feature_get_idx(self, FU_LOGITECH_HIDPP_FEATURE_DFU_CONTROL_SIGNED); if (idx != 0x00) { msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_LONG; msg->device_id = priv->device_idx; msg->sub_id = idx; msg->function_id = 0x01 << 4; /* setDfuControl */ msg->data[0] = 0x01; /* startDfu */ msg->data[1] = 0x00; /* dfuControlParam */ msg->data[2] = 0x00; /* unused */ msg->data[3] = 0x00; /* unused */ msg->data[4] = 'D'; msg->data[5] = 'F'; msg->data[6] = 'U'; msg->flags = FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_SUB_ID; if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, error)) { g_prefix_error(error, "failed to put device into DFU mode: "); return FALSE; } fu_device_sleep(device, 200); /* ms */ return fu_logitech_hidpp_device_setup(FU_DEVICE(self), error); } /* we don't know how */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "no method to detach"); return FALSE; } static gboolean fu_logitech_hidpp_device_check_status(guint8 status, GError **error) { switch (status & 0x7f) { case 0x00: g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid status value 0x%02x", status); break; case 0x01: /* packet success */ case 0x02: /* DFU success */ case 0x05: /* DFU success: entity restart required */ case 0x06: /* DFU success: system restart required */ /* success */ return TRUE; break; case 0x03: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_PENDING, "wait for event (command in progress)"); break; case 0x04: case 0x10: /* unknown */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "generic error"); break; case 0x11: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "bad voltage (power too low?)"); break; case 0x12: case 0x14: /* bad magic string */ case 0x21: /* bad firmware */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "unsupported firmware"); break; case 0x13: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "unsupported encryption mode"); break; case 0x15: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "erase failure"); break; case 0x16: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "DFU not started"); break; case 0x17: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "bad sequence number"); break; case 0x18: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "unsupported command"); break; case 0x19: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "command in progress"); break; case 0x1a: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "address out of range"); break; case 0x1b: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "unaligned address"); break; case 0x1c: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "bad size"); break; case 0x1d: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "missing program data"); break; case 0x1e: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "missing check data"); break; case 0x1f: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "program failed to write"); break; case 0x20: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "program failed to verify"); break; case 0x22: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "firmware check failure"); break; case 0x23: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "blocked command (restart required)"); break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "unhandled status value 0x%02x", status); break; } return FALSE; } static gboolean fu_logitech_hidpp_device_write_firmware_pkt(FuLogitechHidppDevice *self, guint8 idx, guint8 cmd, const guint8 *data, GError **error) { FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); guint32 packet_cnt; g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); g_autoptr(GError) error_local = NULL; /* send firmware data */ msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_LONG; msg->device_id = priv->device_idx; msg->sub_id = idx; msg->function_id = cmd << 4; /* dfuStart or dfuCmdDataX */ msg->hidpp_version = priv->hidpp_version; /* enable transfer workaround for devices paired to Bolt receiver */ if (priv->device_idx != FU_LOGITECH_HIDPP_DEVICE_IDX_WIRED && priv->device_idx != FU_LOGITECH_HIDPP_DEVICE_IDX_RECEIVER) msg->flags = FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_RETRY_STUCK; if (!fu_memcpy_safe(msg->data, sizeof(msg->data), 0x0, /* dst */ data, 16, 0x0, /* src */ 16, error)) return FALSE; if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, error)) { g_prefix_error(error, "failed to supply program data: "); return FALSE; } /* check error */ if (!fu_memread_uint32_safe(msg->data, sizeof(msg->data), 0x0, &packet_cnt, G_BIG_ENDIAN, error)) return FALSE; g_debug("packet_cnt=0x%04x", packet_cnt); if (fu_logitech_hidpp_device_check_status(msg->data[4], &error_local)) return TRUE; /* fatal error */ if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_PENDING)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, error_local->message); return FALSE; } /* wait for the HID++ notification */ g_debug("ignoring: %s", error_local->message); for (guint retry = 0; retry < 10; retry++) { g_autoptr(FuLogitechHidppHidppMsg) msg2 = fu_logitech_hidpp_msg_new(); msg2->flags = FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_FNCT_ID; if (!fu_logitech_hidpp_receive(priv->io_channel, msg2, 15000, error)) return FALSE; if (fu_logitech_hidpp_msg_is_reply(msg, msg2)) { g_autoptr(GError) error2 = NULL; if (!fu_logitech_hidpp_device_check_status(msg2->data[4], &error2)) { g_debug("got %s, waiting a bit longer", error2->message); continue; } return TRUE; } g_debug("got wrong packet, continue to wait..."); } /* nothing in the queue */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to get event after timeout"); return FALSE; } static gboolean fu_logitech_hidpp_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuLogitechHidppDevice *self = FU_HIDPP_DEVICE(device); FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); gsize sz = 0; const guint8 *data; guint8 cmd = 0x04; guint8 idx; g_autoptr(GBytes) fw = NULL; /* if we're in bootloader mode, we should be able to get this feature */ idx = fu_logitech_hidpp_device_feature_get_idx(self, FU_LOGITECH_HIDPP_FEATURE_DFU); if (idx == 0x00) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "no DFU feature available"); return FALSE; } /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* flash hardware -- the first data byte is the fw entity */ data = g_bytes_get_data(fw, &sz); if (priv->cached_fw_entity != data[0]) { g_debug("updating cached entity 0x%x with 0x%x", priv->cached_fw_entity, data[0]); priv->cached_fw_entity = data[0]; } fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); for (gsize i = 0; i < sz / 16; i++) { /* send packet and wait for reply */ g_debug("send data at addr=0x%04x", (guint)i * 16); if (!fu_logitech_hidpp_device_write_firmware_pkt(self, idx, cmd, data + (i * 16), error)) { g_prefix_error(error, "failed to write @0x%04x: ", (guint)i * 16); return FALSE; } /* use sliding window */ cmd = (cmd + 1) % 4; /* update progress-bar */ fu_progress_set_percentage_full(progress, (i + 1) * 16, sz); } return TRUE; } static gboolean fu_logitech_hidpp_device_reprobe_cb(FuDevice *device, gpointer user_data, GError **error) { return fu_logitech_hidpp_device_setup(device, error); } gboolean fu_logitech_hidpp_device_attach(FuLogitechHidppDevice *self, guint8 entity, FuProgress *progress, GError **error) { FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); FuDevice *device = FU_DEVICE(self); guint8 idx; g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); g_autoptr(GError) error_local = NULL; /* sanity check */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } /* if we're in bootloader mode, we should be able to get this feature */ idx = fu_logitech_hidpp_device_feature_get_idx(self, FU_LOGITECH_HIDPP_FEATURE_DFU); if (idx == 0x00) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "no DFU feature available"); return FALSE; } /* reboot back into firmware mode */ msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_LONG; msg->device_id = priv->device_idx; msg->sub_id = idx; msg->function_id = 0x05 << 4; /* restart */ msg->data[0] = entity; /* fwEntity */ msg->hidpp_version = priv->hidpp_version; msg->flags = FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_SUB_ID | FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_SWID | // inferred? FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_LONGER_TIMEOUT; if (!fu_logitech_hidpp_transfer(priv->io_channel, msg, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_READ) || g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_debug("ignoring '%s' on reset", error_local->message); } else { g_prefix_error(&error_local, "failed to restart device: "); g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } if (fu_device_has_private_flag(device, FU_LOGITECH_HIDPP_DEVICE_FLAG_REBIND_ATTACH)) { fu_device_set_poll_interval(device, 0); /* * Wait for device to become ready after flashing. * Possible race condition: after the device is reset, Linux might enumerate it as * a different hidraw device depending on timing. */ fu_device_sleep_full(FU_DEVICE(self), 1000, progress); /* ms */ } else { /* device file hasn't been unbound/re-bound, just probe again */ if (!fu_device_retry(device, fu_logitech_hidpp_device_reprobe_cb, 10, NULL, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_hidpp_device_attach_cached(FuDevice *device, FuProgress *progress, GError **error) { FuLogitechHidppDevice *self = FU_HIDPP_DEVICE(device); FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); if (fu_device_has_private_flag(device, FU_LOGITECH_HIDPP_DEVICE_FLAG_REBIND_ATTACH)) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return fu_logitech_hidpp_device_attach(self, priv->cached_fw_entity, progress, error); } static gboolean fu_logitech_hidpp_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuLogitechHidppDevice *self = FU_HIDPP_DEVICE(device); if (g_strcmp0(key, "LogitechHidppModelId") == 0) { fu_logitech_hidpp_device_set_model_id(self, value); return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_logitech_hidpp_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 97, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reload"); } static void fu_logitech_hidpp_device_finalize(GObject *object) { FuLogitechHidppDevice *self = FU_HIDPP_DEVICE(object); FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); g_ptr_array_unref(priv->feature_index); g_free(priv->model_id); G_OBJECT_CLASS(fu_logitech_hidpp_device_parent_class)->finalize(object); } static gboolean fu_logitech_hidpp_device_cleanup(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDevice *parent = fu_device_get_parent(device); if (parent != NULL) fu_device_set_poll_interval(parent, FU_HIDPP_RECEIVER_RUNTIME_POLLING_INTERVAL); return TRUE; } static void fu_logitech_hidpp_device_class_init(FuLogitechHidppDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_logitech_hidpp_device_finalize; klass_device->setup = fu_logitech_hidpp_device_setup; klass_device->open = fu_logitech_hidpp_device_open; klass_device->close = fu_logitech_hidpp_device_close; klass_device->write_firmware = fu_logitech_hidpp_device_write_firmware; klass_device->attach = fu_logitech_hidpp_device_attach_cached; klass_device->detach = fu_logitech_hidpp_device_detach; klass_device->poll = fu_logitech_hidpp_device_poll; klass_device->to_string = fu_logitech_hidpp_device_to_string; klass_device->probe = fu_logitech_hidpp_device_probe; klass_device->set_quirk_kv = fu_logitech_hidpp_device_set_quirk_kv; klass_device->cleanup = fu_logitech_hidpp_device_cleanup; klass_device->set_progress = fu_logitech_hidpp_device_set_progress; } static void fu_logitech_hidpp_device_init(FuLogitechHidppDevice *self) { FuLogitechHidppDevicePrivate *priv = GET_PRIVATE(self); priv->device_idx = FU_LOGITECH_HIDPP_DEVICE_IDX_WIRED; priv->feature_index = g_ptr_array_new_with_free_func(g_free); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_request_flag(FU_DEVICE(self), FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_vendor(FU_DEVICE(self), "Logitech"); fu_device_retry_set_delay(FU_DEVICE(self), 1000); fu_device_register_private_flag(FU_DEVICE(self), FU_LOGITECH_HIDPP_DEVICE_FLAG_FORCE_RECEIVER_ID, "force-receiver-id"); fu_device_register_private_flag(FU_DEVICE(self), FU_LOGITECH_HIDPP_DEVICE_FLAG_BLE, "ble"); fu_device_register_private_flag(FU_DEVICE(self), FU_LOGITECH_HIDPP_DEVICE_FLAG_REBIND_ATTACH, "rebind-attach"); fu_device_register_private_flag(FU_DEVICE(self), FU_LOGITECH_HIDPP_DEVICE_FLAG_NO_REQUEST_REQUIRED, "no-request-required"); fu_device_register_private_flag(FU_DEVICE(self), FU_LOGITECH_HIDPP_DEVICE_FLAG_ADD_RADIO, "add-radio"); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_USER_REPLUG); fu_device_set_battery_threshold(FU_DEVICE(self), 20); } FuLogitechHidppDevice * fu_logitech_hidpp_device_new(FuUdevDevice *parent) { FuLogitechHidppDevice *self = NULL; FuLogitechHidppDevicePrivate *priv; self = g_object_new(FU_TYPE_HIDPP_DEVICE, "context", fu_device_get_context(FU_DEVICE(parent)), "physical-id", fu_device_get_physical_id(FU_DEVICE(parent)), "udev-device", fu_udev_device_get_dev(parent), NULL); priv = GET_PRIVATE(self); priv->io_channel = fu_logitech_hidpp_runtime_get_io_channel(FU_HIDPP_RUNTIME(parent)); return self; } fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-device.h000066400000000000000000000035771460375044200242550ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_HIDPP_DEVICE (fu_logitech_hidpp_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuLogitechHidppDevice, fu_logitech_hidpp_device, FU, HIDPP_DEVICE, FuUdevDevice) struct _FuLogitechHidppDeviceClass { FuUdevDeviceClass parent_class; /* TODO: overridable methods */ }; /** * FU_LOGITECH_HIDPP_DEVICE_FLAG_FORCE_RECEIVER_ID: * * Device is a unifying or Bolt receiver. * * Since: 1.7.0 */ #define FU_LOGITECH_HIDPP_DEVICE_FLAG_FORCE_RECEIVER_ID (1 << 0) /** * FU_LOGITECH_HIDPP_DEVICE_FLAG_BLE: * * Device is connected using Bluetooth Low Energy. * * Since: 1.7.0 */ #define FU_LOGITECH_HIDPP_DEVICE_FLAG_BLE (1 << 1) /** * FU_LOGITECH_HIDPP_DEVICE_FLAG_REBIND_ATTACH: * * The device file is automatically unbound and re-bound after the * device is attached. * * Since: 1.7.0 */ #define FU_LOGITECH_HIDPP_DEVICE_FLAG_REBIND_ATTACH (1 << 2) /** * FU_LOGITECH_HIDPP_DEVICE_FLAG_NO_REQUEST_REQUIRED: * * No user-action is required for detach and attach. * * Since: 1.7.0 */ #define FU_LOGITECH_HIDPP_DEVICE_FLAG_NO_REQUEST_REQUIRED (1 << 3) /** * FU_LOGITECH_HIDPP_DEVICE_FLAG_ADD_RADIO: * * The device should add a softdevice (index 0x5), typically a radio. * * Since: 1.7.0 */ #define FU_LOGITECH_HIDPP_DEVICE_FLAG_ADD_RADIO (1 << 5) void fu_logitech_hidpp_device_set_device_idx(FuLogitechHidppDevice *self, guint8 device_idx); guint16 fu_logitech_hidpp_device_get_hidpp_pid(FuLogitechHidppDevice *self); void fu_logitech_hidpp_device_set_hidpp_pid(FuLogitechHidppDevice *self, guint16 hidpp_pid); gboolean fu_logitech_hidpp_device_attach(FuLogitechHidppDevice *self, guint8 entity, FuProgress *progress, GError **error); FuLogitechHidppDevice * fu_logitech_hidpp_device_new(FuUdevDevice *parent); fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp-msg.c000066400000000000000000000150501460375044200246660ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-logitech-hidpp-hidpp-msg.h" #include "fu-logitech-hidpp-hidpp.h" #include "fu-logitech-hidpp-struct.h" FuLogitechHidppHidppMsg * fu_logitech_hidpp_msg_new(void) { return g_new0(FuLogitechHidppHidppMsg, 1); } gsize fu_logitech_hidpp_msg_get_payload_length(FuLogitechHidppHidppMsg *msg) { if (msg->report_id == FU_LOGITECH_HIDPP_REPORT_ID_SHORT) return 0x07; if (msg->report_id == FU_LOGITECH_HIDPP_REPORT_ID_LONG) return 0x14; if (msg->report_id == FU_LOGITECH_HIDPP_REPORT_ID_VERY_LONG) return 0x2f; if (msg->report_id == HIDPP_REPORT_NOTIFICATION) return 0x08; return 0x0; } const gchar * fu_logitech_hidpp_msg_fcn_id_to_string(FuLogitechHidppHidppMsg *msg) { g_return_val_if_fail(msg != NULL, NULL); switch (msg->sub_id) { case FU_LOGITECH_HIDPP_SUBID_SET_REGISTER: case FU_LOGITECH_HIDPP_SUBID_GET_REGISTER: case FU_LOGITECH_HIDPP_SUBID_SET_LONG_REGISTER: case FU_LOGITECH_HIDPP_SUBID_GET_LONG_REGISTER: case FU_LOGITECH_HIDPP_SUBID_SET_VERY_LONG_REGISTER: case FU_LOGITECH_HIDPP_SUBID_GET_VERY_LONG_REGISTER: return fu_logitech_hidpp_register_to_string(msg->function_id); break; default: break; } return NULL; } gboolean fu_logitech_hidpp_msg_is_reply(FuLogitechHidppHidppMsg *msg1, FuLogitechHidppHidppMsg *msg2) { g_return_val_if_fail(msg1 != NULL, FALSE); g_return_val_if_fail(msg2 != NULL, FALSE); if (msg1->device_id != msg2->device_id && msg1->device_id != FU_LOGITECH_HIDPP_DEVICE_IDX_WIRED && msg2->device_id != FU_LOGITECH_HIDPP_DEVICE_IDX_WIRED) return FALSE; if (msg1->flags & FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_SUB_ID || msg2->flags & FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_SUB_ID) return TRUE; if (msg1->sub_id != msg2->sub_id) return FALSE; if (msg1->flags & FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_FNCT_ID || msg2->flags & FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_FNCT_ID) return TRUE; if (msg1->function_id != msg2->function_id) return FALSE; return TRUE; } /* HID++ error */ gboolean fu_logitech_hidpp_msg_is_error(FuLogitechHidppHidppMsg *msg, GError **error) { g_return_val_if_fail(msg != NULL, FALSE); if (msg->sub_id == FU_LOGITECH_HIDPP_SUBID_ERROR_MSG) { switch (msg->data[1]) { case HIDPP_ERR_INVALID_SUBID: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "invalid SubID"); break; case HIDPP_ERR_INVALID_ADDRESS: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid address"); break; case HIDPP_ERR_INVALID_VALUE: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid value"); break; case HIDPP_ERR_CONNECT_FAIL: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "connection request failed"); break; case HIDPP_ERR_TOO_MANY_DEVICES: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NO_SPACE, "too many devices connected"); break; case HIDPP_ERR_ALREADY_EXISTS: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_EXISTS, "already exists"); break; case HIDPP_ERR_BUSY: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "busy"); break; case HIDPP_ERR_UNKNOWN_DEVICE: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "unknown device"); break; case HIDPP_ERR_RESOURCE_ERROR: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE, "resource error"); break; case HIDPP_ERR_REQUEST_UNAVAILABLE: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_EXISTS, "request not valid in current context"); break; case HIDPP_ERR_INVALID_PARAM_VALUE: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "request parameter has unsupported value"); break; case HIDPP_ERR_WRONG_PIN_CODE: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED, "the pin code was wrong"); break; default: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "generic failure"); } return FALSE; } if (msg->sub_id == FU_LOGITECH_HIDPP_SUBID_ERROR_MSG_20) { switch (msg->data[1]) { case HIDPP_ERROR_CODE_INVALID_ARGUMENT: g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid argument 0x%02x", msg->data[2]); break; case HIDPP_ERROR_CODE_OUT_OF_RANGE: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "out of range"); break; case HIDPP_ERROR_CODE_HW_ERROR: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE, "hardware error"); break; case HIDPP_ERROR_CODE_INVALID_FEATURE_INDEX: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "invalid feature index"); break; case HIDPP_ERROR_CODE_INVALID_FUNCTION_ID: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "invalid function ID"); break; case HIDPP_ERROR_CODE_BUSY: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "busy"); break; case HIDPP_ERROR_CODE_UNSUPPORTED: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unsupported"); break; default: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "generic failure"); break; } return FALSE; } return TRUE; } void fu_logitech_hidpp_msg_copy(FuLogitechHidppHidppMsg *msg_dst, const FuLogitechHidppHidppMsg *msg_src) { g_return_if_fail(msg_dst != NULL); g_return_if_fail(msg_src != NULL); memset(msg_dst->data, 0x00, sizeof(msg_dst->data)); msg_dst->device_id = msg_src->device_id; msg_dst->sub_id = msg_src->sub_id; msg_dst->function_id = msg_src->function_id; memcpy(msg_dst->data, msg_src->data, sizeof(msg_dst->data)); } /* filter HID++1.0 messages */ gboolean fu_logitech_hidpp_msg_is_hidpp10_compat(FuLogitechHidppHidppMsg *msg) { g_return_val_if_fail(msg != NULL, FALSE); if (msg->sub_id == 0x40 || msg->sub_id == 0x41 || msg->sub_id == 0x49 || msg->sub_id == 0x4b || msg->sub_id == 0x8f) { return TRUE; } return FALSE; } gboolean fu_logitech_hidpp_msg_verify_swid(FuLogitechHidppHidppMsg *msg) { g_return_val_if_fail(msg != NULL, FALSE); if ((msg->function_id & 0x0f) != FU_LOGITECH_HIDPP_HIDPP_MSG_SW_ID) return FALSE; return TRUE; } fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp-msg.h000066400000000000000000000034531460375044200246770ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef enum { FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_NONE, FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_LONGER_TIMEOUT = 1 << 0, FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_SUB_ID = 1 << 1, FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_FNCT_ID = 1 << 2, FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_SWID = 1 << 3, FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_RETRY_STUCK = 1 << 4, /*< private >*/ FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_LAST } FuLogitechHidppHidppMsgFlags; typedef struct __attribute__((packed)) { guint8 report_id; guint8 device_id; guint8 sub_id; guint8 function_id; /* funcId:software_id */ guint8 data[47]; /* maximum supported by Windows XP SP2 */ /* not included in the packet sent to the hardware */ guint32 flags; guint8 hidpp_version; } FuLogitechHidppHidppMsg; /* this is specific to fwupd */ #define FU_LOGITECH_HIDPP_HIDPP_MSG_SW_ID 0x07 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuLogitechHidppHidppMsg, g_free); #pragma clang diagnostic pop FuLogitechHidppHidppMsg * fu_logitech_hidpp_msg_new(void); void fu_logitech_hidpp_msg_copy(FuLogitechHidppHidppMsg *msg_dst, const FuLogitechHidppHidppMsg *msg_src); gsize fu_logitech_hidpp_msg_get_payload_length(FuLogitechHidppHidppMsg *msg); gboolean fu_logitech_hidpp_msg_is_reply(FuLogitechHidppHidppMsg *msg1, FuLogitechHidppHidppMsg *msg2); gboolean fu_logitech_hidpp_msg_is_hidpp10_compat(FuLogitechHidppHidppMsg *msg); gboolean fu_logitech_hidpp_msg_is_error(FuLogitechHidppHidppMsg *msg, GError **error); gboolean fu_logitech_hidpp_msg_verify_swid(FuLogitechHidppHidppMsg *msg); const gchar * fu_logitech_hidpp_msg_fcn_id_to_string(FuLogitechHidppHidppMsg *msg); fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp.c000066400000000000000000000157001460375044200241040ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-hidpp.h" #include "fu-logitech-hidpp-struct.h" static gchar * fu_logitech_hidpp_msg_to_string(FuLogitechHidppHidppMsg *msg) { g_autoptr(GError) error = NULL; g_autoptr(GString) flags_str = g_string_new(NULL); g_autoptr(GString) str = g_string_new(NULL); g_return_val_if_fail(msg != NULL, NULL); if (msg->flags == FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_NONE) { g_string_append(flags_str, "none"); } else { if (msg->flags & FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_LONGER_TIMEOUT) g_string_append(flags_str, "longer-timeout,"); if (msg->flags & FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_SUB_ID) g_string_append(flags_str, "ignore-sub-id,"); if (msg->flags & FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_FNCT_ID) g_string_append(flags_str, "ignore-fnct-id,"); if (msg->flags & FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_SWID) g_string_append(flags_str, "ignore-swid,"); if (str->len > 0) g_string_truncate(str, str->len - 1); } g_string_append_printf(str, "flags: %02x [%s]\n", msg->flags, flags_str->str); g_string_append_printf(str, "report-id: %02x [%s]\n", msg->report_id, fu_logitech_hidpp_report_id_to_string(msg->report_id)); g_string_append_printf(str, "device-id: %02x [%s]\n", msg->device_id, fu_logitech_hidpp_device_idx_to_string(msg->device_id)); g_string_append_printf(str, "sub-id: %02x [%s]\n", msg->sub_id, fu_logitech_hidpp_subid_to_string(msg->sub_id)); g_string_append_printf(str, "function-id: %02x [%s]\n", msg->function_id, fu_logitech_hidpp_msg_fcn_id_to_string(msg)); if (!fu_logitech_hidpp_msg_is_error(msg, &error)) { g_string_append_printf(str, "error: %s\n", error->message); } if (str->len > 0) g_string_truncate(str, str->len - 1); return g_string_free(g_steal_pointer(&str), FALSE); } gboolean fu_logitech_hidpp_send(FuIOChannel *io_channel, FuLogitechHidppHidppMsg *msg, guint timeout, GError **error) { gsize len = fu_logitech_hidpp_msg_get_payload_length(msg); FuIOChannelFlags write_flags = FU_IO_CHANNEL_FLAG_FLUSH_INPUT; g_autofree gchar *str = NULL; /* only for HID++2.0 */ if (msg->hidpp_version >= 2.f) msg->function_id |= FU_LOGITECH_HIDPP_HIDPP_MSG_SW_ID; /* force long reports for BLE-direct devices */ if (msg->hidpp_version == FU_HIDPP_VERSION_BLE) { msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_LONG; len = 20; } fu_dump_raw(G_LOG_DOMAIN, "host->device", (guint8 *)msg, len); /* debugging */ str = fu_logitech_hidpp_msg_to_string(msg); g_debug("%s", str); /* only use blocking IO when it will be a short timeout for reboot */ if ((msg->flags & FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_LONGER_TIMEOUT) == 0) write_flags |= FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO; /* HID */ if (!fu_io_channel_write_raw(io_channel, (guint8 *)msg, len, timeout, write_flags, error)) { g_prefix_error(error, "failed to send: "); return FALSE; } /* success */ return TRUE; } gboolean fu_logitech_hidpp_receive(FuIOChannel *io_channel, FuLogitechHidppHidppMsg *msg, guint timeout, GError **error) { gsize read_size = 0; g_autofree gchar *str = NULL; if (!fu_io_channel_read_raw(io_channel, (guint8 *)msg, sizeof(FuLogitechHidppHidppMsg), &read_size, timeout, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error)) { g_prefix_error(error, "failed to receive: "); return FALSE; } /* check long enough, but allow returning oversize packets */ fu_dump_raw(G_LOG_DOMAIN, "device->host", (guint8 *)msg, read_size); if (read_size < fu_logitech_hidpp_msg_get_payload_length(msg)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "message length too small, " "got %" G_GSIZE_FORMAT " expected %" G_GSIZE_FORMAT, read_size, fu_logitech_hidpp_msg_get_payload_length(msg)); return FALSE; } /* debugging */ str = fu_logitech_hidpp_msg_to_string(msg); g_debug("%s", str); /* success */ return TRUE; } gboolean fu_logitech_hidpp_transfer(FuIOChannel *io_channel, FuLogitechHidppHidppMsg *msg, GError **error) { guint timeout = FU_LOGITECH_HIDPP_DEVICE_TIMEOUT_MS; guint ignore_cnt = 0; g_autoptr(FuLogitechHidppHidppMsg) msg_tmp = fu_logitech_hidpp_msg_new(); /* increase timeout for some operations */ if (msg->flags & FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_LONGER_TIMEOUT) timeout *= 10; /* send request */ if (!fu_logitech_hidpp_send(io_channel, msg, timeout, error)) return FALSE; /* keep trying to receive until we get a valid reply */ while (1) { msg_tmp->hidpp_version = msg->hidpp_version; if (msg->flags & FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_RETRY_STUCK) { g_autoptr(GError) error_local = NULL; /* retry the send once case the device is "stuck" */ if (!fu_logitech_hidpp_receive(io_channel, msg_tmp, 1000, &error_local)) { if (!fu_logitech_hidpp_send(io_channel, msg, timeout, error)) { return FALSE; } if (!fu_logitech_hidpp_receive(io_channel, msg_tmp, timeout, error)) { g_prefix_error(error, "failed to receive: "); return FALSE; } } } else { if (!fu_logitech_hidpp_receive(io_channel, msg_tmp, timeout, error)) { g_prefix_error(error, "failed to receive: "); return FALSE; } } /* we don't know how to handle this report packet */ if (fu_logitech_hidpp_msg_get_payload_length(msg_tmp) == 0x0) { g_debug("HID++1.0 report 0x%02x has unknown length, ignoring", msg_tmp->report_id); continue; } /* maybe something is also writing to the device? -- * we can't use the SwID as this is a HID++2.0 feature */ if (!fu_logitech_hidpp_msg_is_error(msg_tmp, error)) return FALSE; /* is valid reply */ if (fu_logitech_hidpp_msg_is_reply(msg, msg_tmp)) break; /* to ensure compatibility when an HID++ 2.0 device is * connected to an HID++ 1.0 receiver, any feature index * corresponding to an HID++ 1.0 sub-identifier which could be * sent by the receiver, must be assigned to a dummy feature */ if (msg->hidpp_version >= 2.f) { if (fu_logitech_hidpp_msg_is_hidpp10_compat(msg_tmp)) { g_debug("ignoring HID++1.0 reply"); continue; } /* not us */ if ((msg->flags & FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_IGNORE_SWID) == 0) { if (!fu_logitech_hidpp_msg_verify_swid(msg_tmp)) { g_debug("ignoring reply with SwId 0x%02i, expected 0x%02i", msg_tmp->function_id & 0x0f, FU_LOGITECH_HIDPP_HIDPP_MSG_SW_ID); continue; } } } /* hardware not responding */ if (ignore_cnt++ > 10) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "too many messages to ignore"); return FALSE; } g_debug("ignoring message %u", ignore_cnt); }; /* copy over data */ fu_logitech_hidpp_msg_copy(msg, msg_tmp); return TRUE; } fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-hidpp.h000066400000000000000000000045061460375044200241130ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /* * Based on the HID++ documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #define HIDPP_REPORT_NOTIFICATION 0x01 #define HIDPP_ERR_SUCCESS 0x00 #define HIDPP_ERR_INVALID_SUBID 0x01 #define HIDPP_ERR_INVALID_ADDRESS 0x02 #define HIDPP_ERR_INVALID_VALUE 0x03 #define HIDPP_ERR_CONNECT_FAIL 0x04 #define HIDPP_ERR_TOO_MANY_DEVICES 0x05 #define HIDPP_ERR_ALREADY_EXISTS 0x06 #define HIDPP_ERR_BUSY 0x07 #define HIDPP_ERR_UNKNOWN_DEVICE 0x08 #define HIDPP_ERR_RESOURCE_ERROR 0x09 #define HIDPP_ERR_REQUEST_UNAVAILABLE 0x0A #define HIDPP_ERR_INVALID_PARAM_VALUE 0x0B #define HIDPP_ERR_WRONG_PIN_CODE 0x0C /* * Bolt registers */ #define BOLT_REGISTER_HIDPP_REPORTING 0x00 #define BOLT_REGISTER_CONNECTION_STATE 0x02 #define BOLT_REGISTER_DEVICE_ACTIVITY 0xB3 #define BOLT_REGISTER_PAIRING_INFORMATION 0xB5 #define BOLT_REGISTER_PERFORM_DEVICE_DISCOVERY 0xC0 #define BOLT_REGISTER_PERFORM_DEVICE_PAIRING 0xC1 #define BOLT_REGISTER_RESET 0xF2 #define BOLT_REGISTER_RECEIVER_FW_INFORMATION 0xF4 #define BOLT_REGISTER_DFU_CONTROL 0xF5 #define BOLT_REGISTER_UNIQUE_IDENTIFIER 0xFB /* * HID++2.0 error codes */ #define HIDPP_ERROR_CODE_NO_ERROR 0x00 #define HIDPP_ERROR_CODE_UNKNOWN 0x01 #define HIDPP_ERROR_CODE_INVALID_ARGUMENT 0x02 #define HIDPP_ERROR_CODE_OUT_OF_RANGE 0x03 #define HIDPP_ERROR_CODE_HW_ERROR 0x04 #define HIDPP_ERROR_CODE_LOGITECH_INTERNAL 0x05 #define HIDPP_ERROR_CODE_INVALID_FEATURE_INDEX 0x06 #define HIDPP_ERROR_CODE_INVALID_FUNCTION_ID 0x07 #define HIDPP_ERROR_CODE_BUSY 0x08 #define HIDPP_ERROR_CODE_UNSUPPORTED 0x09 #include "fu-logitech-hidpp-hidpp-msg.h" gboolean fu_logitech_hidpp_send(FuIOChannel *io_channel, FuLogitechHidppHidppMsg *msg, guint timeout, GError **error); gboolean fu_logitech_hidpp_receive(FuIOChannel *io_channel, FuLogitechHidppHidppMsg *msg, guint timeout, GError **error); gboolean fu_logitech_hidpp_transfer(FuIOChannel *io_channel, FuLogitechHidppHidppMsg *msg, GError **error); fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-plugin.c000066400000000000000000000030001460375044200242640ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-logitech-hidpp-bootloader-nordic.h" #include "fu-logitech-hidpp-bootloader-texas.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-device.h" #include "fu-logitech-hidpp-plugin.h" #include "fu-logitech-hidpp-runtime-bolt.h" #include "fu-logitech-hidpp-runtime-unifying.h" struct _FuLogitechHidppPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuLogitechHidppPlugin, fu_logitech_hidpp_plugin, FU_TYPE_PLUGIN) static void fu_logitech_hidpp_plugin_init(FuLogitechHidppPlugin *self) { } static void fu_logitech_hidpp_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "LogitechHidppModelId"); fu_plugin_add_udev_subsystem(plugin, "hidraw"); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_CONFLICTS, "unifying"); fu_plugin_add_device_gtype(plugin, FU_TYPE_LOGITECH_HIDPP_BOOTLOADER_NORDIC); fu_plugin_add_device_gtype(plugin, FU_TYPE_LOGITECH_HIDPP_BOOTLOADER_TEXAS); fu_plugin_add_device_gtype(plugin, FU_TYPE_HIDPP_RUNTIME_UNIFYING); fu_plugin_add_device_gtype(plugin, FU_TYPE_HIDPP_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_HIDPP_RUNTIME_BOLT); } static void fu_logitech_hidpp_plugin_class_init(FuLogitechHidppPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_logitech_hidpp_plugin_constructed; } fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-plugin.h000066400000000000000000000004331460375044200243000ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuLogitechHidppPlugin, fu_logitech_hidpp_plugin, FU, LOGITECH_HIDPP_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-radio.c000066400000000000000000000076561460375044200241110ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-logitech-hidpp-device.h" #include "fu-logitech-hidpp-radio.h" struct _FuLogitechHidppRadio { FuDevice parent_instance; guint8 entity; }; G_DEFINE_TYPE(FuLogitechHidppRadio, fu_logitech_hidpp_radio, FU_TYPE_DEVICE) static void fu_logitech_hidpp_radio_to_string(FuDevice *device, guint idt, GString *str) { FuLogitechHidppRadio *self = FU_HIDPP_RADIO(device); fu_string_append_kx(str, idt, "Entity", self->entity); } static gboolean fu_logitech_hidpp_radio_attach(FuDevice *device, FuProgress *progress, GError **error) { FuLogitechHidppRadio *self = FU_HIDPP_RADIO(device); FuDevice *parent = fu_device_get_parent(device); g_autoptr(FuDeviceLocker) locker = NULL; /* open */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return fu_logitech_hidpp_device_attach(FU_HIDPP_DEVICE(parent), self->entity, progress, error); } static gboolean fu_logitech_hidpp_radio_detach(FuDevice *device, FuProgress *progress, GError **error) { FuDevice *parent = fu_device_get_parent(device); g_autoptr(FuDeviceLocker) locker = NULL; /* open */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; if (!fu_device_has_flag(parent, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return fu_device_detach_full(parent, progress, error); } static gboolean fu_logitech_hidpp_radio_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDevice *parent = fu_device_get_parent(device); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GBytes) fw = NULL; fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* open */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; return fu_device_write_firmware(parent, fw, progress, flags, error); } static void fu_logitech_hidpp_radio_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 96, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 3, "reload"); } static void fu_logitech_hidpp_radio_init(FuLogitechHidppRadio *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_set_vendor(FU_DEVICE(self), "Logitech"); fu_device_set_name(FU_DEVICE(self), "Radio"); fu_device_set_install_duration(FU_DEVICE(self), 270); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_BATTERY); fu_device_add_protocol(FU_DEVICE(self), "com.logitech.unifyingsigned"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_HEX); } static void fu_logitech_hidpp_radio_class_init(FuLogitechHidppRadioClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->detach = fu_logitech_hidpp_radio_detach; klass_device->attach = fu_logitech_hidpp_radio_attach; klass_device->write_firmware = fu_logitech_hidpp_radio_write_firmware; klass_device->to_string = fu_logitech_hidpp_radio_to_string; klass_device->set_progress = fu_logitech_hidpp_radio_set_progress; } FuLogitechHidppRadio * fu_logitech_hidpp_radio_new(FuContext *ctx, guint8 entity) { FuLogitechHidppRadio *self = NULL; self = g_object_new(FU_TYPE_LOGITECH_HIDPP_RADIO, "context", ctx, NULL); self->entity = entity; return self; } fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-radio.h000066400000000000000000000006351460375044200241040ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_LOGITECH_HIDPP_RADIO (fu_logitech_hidpp_radio_get_type()) G_DECLARE_FINAL_TYPE(FuLogitechHidppRadio, fu_logitech_hidpp_radio, FU, HIDPP_RADIO, FuDevice) FuLogitechHidppRadio * fu_logitech_hidpp_radio_new(FuContext *ctx, guint8 entity); fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.c000066400000000000000000000400511460375044200254160ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-device.h" #include "fu-logitech-hidpp-hidpp.h" #include "fu-logitech-hidpp-radio.h" #include "fu-logitech-hidpp-runtime-bolt.h" #include "fu-logitech-hidpp-struct.h" struct _FuLogitechHidppRuntimeBolt { FuLogitechHidppRuntime parent_instance; guint8 pairing_slots; }; G_DEFINE_TYPE(FuLogitechHidppRuntimeBolt, fu_logitech_hidpp_runtime_bolt, FU_TYPE_HIDPP_RUNTIME) static gboolean fu_logitech_hidpp_runtime_bolt_detach(FuDevice *device, FuProgress *progress, GError **error) { FuLogitechHidppRuntime *self = FU_HIDPP_RUNTIME(device); g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); g_autoptr(GError) error_local = NULL; msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_LONG; msg->device_id = FU_LOGITECH_HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = FU_LOGITECH_HIDPP_SUBID_SET_LONG_REGISTER; msg->function_id = BOLT_REGISTER_DFU_CONTROL; msg->data[0] = 1; /* Enable DFU */ msg->data[4] = 'P'; msg->data[5] = 'R'; msg->data[6] = 'E'; msg->hidpp_version = 1; msg->flags = FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_LONGER_TIMEOUT; if (!fu_logitech_hidpp_send(fu_logitech_hidpp_runtime_get_io_channel(self), msg, FU_LOGITECH_HIDPP_DEVICE_TIMEOUT_MS, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_WRITE)) { g_debug("failed to detach to bootloader: %s", error_local->message); } else { g_prefix_error(&error_local, "failed to detach to bootloader: "); g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_logitech_hidpp_runtime_bolt_to_string(FuDevice *device, guint idt, GString *str) { FuLogitechHidppRuntimeBolt *self = FU_HIDPP_RUNTIME_BOLT(device); FU_DEVICE_CLASS(fu_logitech_hidpp_runtime_bolt_parent_class)->to_string(device, idt, str); fu_string_append_ku(str, idt, "PairingSlots", self->pairing_slots); } static FuLogitechHidppDevice * fu_logitech_hidpp_runtime_bolt_find_paired_device(FuDevice *device, guint16 hidpp_pid) { GPtrArray *children = fu_device_get_children(device); for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index(children, i); if (FU_IS_HIDPP_DEVICE(child) && fu_logitech_hidpp_device_get_hidpp_pid(FU_HIDPP_DEVICE(child)) == hidpp_pid) return FU_HIDPP_DEVICE(g_object_ref(child)); } return NULL; } static gchar * fu_logitech_hidpp_runtime_bolt_query_device_name(FuLogitechHidppRuntime *self, guint8 slot, GError **error) { g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); g_autoptr(GString) dev_name = g_string_new(NULL); guint namelen; msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = FU_LOGITECH_HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = FU_LOGITECH_HIDPP_SUBID_GET_LONG_REGISTER; msg->function_id = BOLT_REGISTER_PAIRING_INFORMATION; msg->data[0] = 0x60 | slot; /* device name */ msg->data[1] = 1; msg->hidpp_version = 1; if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self), msg, error)) { g_prefix_error(error, "failed to retrieve the device name for slot %d: ", slot); return NULL; } namelen = msg->data[2]; g_string_append_len(dev_name, (const char *)(&(msg->data[3])), namelen); return g_string_free(g_steal_pointer(&dev_name), FALSE); } static gboolean fu_logitech_hidpp_runtime_bolt_update_paired_device(FuLogitechHidppRuntimeBolt *self, FuLogitechHidppHidppMsg *msg, GError **error) { FuLogitechHidppRuntime *runtime = FU_HIDPP_RUNTIME(self); gboolean reachable = FALSE; guint16 hidpp_pid; g_autoptr(FuLogitechHidppDevice) child = NULL; if ((msg->data[0] & 0x40) == 0) reachable = TRUE; hidpp_pid = (msg->data[1] << 8) | msg->data[2]; child = fu_logitech_hidpp_runtime_bolt_find_paired_device(FU_DEVICE(self), hidpp_pid); if (child != NULL) { g_debug("%s [%s] is reachable:%i", fu_device_get_name(FU_DEVICE(child)), fu_device_get_name(FU_DEVICE(child)), reachable); if (reachable) { g_autoptr(FuDeviceLocker) locker = NULL; /* known paired & reachable */ fu_device_probe_invalidate(FU_DEVICE(child)); locker = fu_device_locker_new(FU_DEVICE(child), error); if (locker == NULL) { g_prefix_error(error, "cannot rescan paired device: "); return FALSE; } fu_device_remove_flag(FU_DEVICE(child), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } else { GPtrArray *children = NULL; /* any successful 'ping' will clear this */ fu_device_add_flag(FU_DEVICE(child), FWUPD_DEVICE_FLAG_UNREACHABLE); children = fu_device_get_children(FU_DEVICE(child)); for (guint i = 0; i < children->len; i++) { FuDevice *radio = g_ptr_array_index(children, i); fu_device_add_flag(radio, FWUPD_DEVICE_FLAG_UNREACHABLE); } } } else if (reachable) { g_autofree gchar *name = NULL; /* unknown paired device, reachable state */ name = fu_logitech_hidpp_runtime_bolt_query_device_name(runtime, msg->device_id, error); if (name == NULL) return FALSE; child = fu_logitech_hidpp_device_new(FU_UDEV_DEVICE(self)); fu_device_set_name(FU_DEVICE(child), name); fu_logitech_hidpp_device_set_device_idx(child, msg->device_id); fu_logitech_hidpp_device_set_hidpp_pid(child, hidpp_pid); if (!fu_device_probe(FU_DEVICE(child), error)) return FALSE; if (!fu_device_setup(FU_DEVICE(child), error)) return FALSE; fu_device_add_child(FU_DEVICE(self), FU_DEVICE(child)); } else { /* unknown paired device, unreachable state */ g_warning("unknown paired device 0x%0x in slot %d (unreachable)", hidpp_pid, msg->device_id); } return TRUE; } static void fu_logitech_hidpp_runtime_bolt_poll_peripherals(FuDevice *device) { FuLogitechHidppRuntime *self = FU_HIDPP_RUNTIME(device); FuLogitechHidppRuntimeBolt *bolt = FU_HIDPP_RUNTIME_BOLT(device); for (guint i = 1; i <= bolt->pairing_slots; i++) { g_autofree gchar *name = NULL; g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); g_autoptr(GError) error_local = NULL; guint16 hidpp_pid; name = fu_logitech_hidpp_runtime_bolt_query_device_name(self, i, &error_local); if (name == NULL) { g_debug("cannot query paired device name for slot %u", i); continue; } msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = FU_LOGITECH_HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = FU_LOGITECH_HIDPP_SUBID_GET_LONG_REGISTER; msg->function_id = BOLT_REGISTER_PAIRING_INFORMATION; msg->data[0] = 0x50 | i; /* pairing information */ msg->hidpp_version = 1; if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self), msg, &error_local)) continue; hidpp_pid = (msg->data[2] << 8) | msg->data[3]; if ((msg->data[1] & 0x40) == 0) { /* paired device is reachable */ g_autoptr(FuLogitechHidppDevice) child = NULL; child = fu_logitech_hidpp_device_new(FU_UDEV_DEVICE(device)); fu_device_set_install_duration(FU_DEVICE(child), 270); fu_device_add_private_flag(FU_DEVICE(child), FU_LOGITECH_HIDPP_DEVICE_FLAG_ADD_RADIO); fu_device_set_name(FU_DEVICE(child), name); fu_logitech_hidpp_device_set_device_idx(child, i); fu_logitech_hidpp_device_set_hidpp_pid(child, hidpp_pid); if (!fu_device_probe(FU_DEVICE(child), &error_local)) continue; if (!fu_device_setup(FU_DEVICE(child), &error_local)) continue; fu_device_add_child(device, FU_DEVICE(child)); } } } static gboolean fu_logitech_hidpp_runtime_bolt_process_notification(FuLogitechHidppRuntimeBolt *self, FuLogitechHidppHidppMsg *msg) { g_autoptr(GError) error_local = NULL; /* HID++1.0 error */ if (!fu_logitech_hidpp_msg_is_error(msg, &error_local)) { g_warning("failed to get pending read: %s", error_local->message); return TRUE; } /* unifying receiver notification */ if (msg->report_id == FU_LOGITECH_HIDPP_REPORT_ID_SHORT) { switch (msg->sub_id) { case FU_LOGITECH_HIDPP_SUBID_DEVICE_CONNECTION: case FU_LOGITECH_HIDPP_SUBID_DEVICE_DISCONNECTION: case FU_LOGITECH_HIDPP_SUBID_DEVICE_LOCKING_CHANGED: if (!fu_logitech_hidpp_runtime_bolt_update_paired_device(self, msg, &error_local)) { g_warning("failed to update paired device status: %s", error_local->message); return FALSE; } break; case FU_LOGITECH_HIDPP_SUBID_LINK_QUALITY: g_debug("ignoring link quality message"); break; case FU_LOGITECH_HIDPP_SUBID_ERROR_MSG: g_debug("ignoring error message"); break; default: g_debug("unknown SubID %02x", msg->sub_id); break; } } return TRUE; } static FuLogitechHidppHidppMsg * fu_logitech_hidpp_runtime_bolt_find_newest_msg(GPtrArray *msgs, guint8 device_id, guint8 sub_id) { for (guint i = 0; i < msgs->len; i++) { FuLogitechHidppHidppMsg *msg = g_ptr_array_index(msgs, msgs->len - (i + 1)); if (msg->device_id == device_id && msg->sub_id == sub_id) return msg; } return NULL; } static gboolean fu_logitech_hidpp_runtime_bolt_poll(FuDevice *device, GError **error) { FuLogitechHidppRuntime *runtime = FU_HIDPP_RUNTIME(device); FuLogitechHidppRuntimeBolt *self = FU_HIDPP_RUNTIME_BOLT(device); const guint timeout = 1; /* ms */ g_autoptr(GPtrArray) msgs = g_ptr_array_new_with_free_func(g_free); /* open -- not a locker as we have no kernel driver */ if (!fu_device_open(device, error)) return FALSE; /* drain all the pending messages into the array */ while (TRUE) { g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); g_autoptr(GError) error_local = NULL; msg->hidpp_version = 1; if (!fu_logitech_hidpp_receive(fu_logitech_hidpp_runtime_get_io_channel(runtime), msg, timeout, &error_local)) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) break; g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "error polling Bolt receiver: "); return FALSE; } g_ptr_array_add(msgs, g_steal_pointer(&msg)); } /* process messages in order, but discard any message with a newer version */ for (guint i = 0; i < msgs->len; i++) { FuLogitechHidppHidppMsg *msg = g_ptr_array_index(msgs, i); FuLogitechHidppHidppMsg *msg_newest; /* find the newest message with the matching device and sub-IDs */ msg_newest = fu_logitech_hidpp_runtime_bolt_find_newest_msg(msgs, msg->device_id, msg->sub_id); if (msg != msg_newest) { g_debug("ignoring duplicate message device-id:%02x [%s] sub-id:%02x [%s]", msg->device_id, fu_logitech_hidpp_device_idx_to_string(msg->device_id), msg->sub_id, fu_logitech_hidpp_subid_to_string(msg->sub_id)); continue; } fu_logitech_hidpp_runtime_bolt_process_notification(self, msg); } return TRUE; } static gboolean fu_logitech_hidpp_runtime_bolt_setup_internal(FuDevice *device, GError **error) { FuContext *ctx = fu_device_get_context(device); FuLogitechHidppRuntime *self = FU_HIDPP_RUNTIME(device); FuLogitechHidppRuntimeBolt *bolt = FU_HIDPP_RUNTIME_BOLT(device); g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = FU_LOGITECH_HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = FU_LOGITECH_HIDPP_SUBID_GET_LONG_REGISTER; msg->function_id = BOLT_REGISTER_PAIRING_INFORMATION; msg->data[0] = 0x02; /* FW Version (contains the number of pairing slots) */ msg->hidpp_version = 1; if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self), msg, error)) { g_prefix_error(error, "failed to fetch the number of pairing slots: "); return FALSE; } bolt->pairing_slots = msg->data[8]; /* * TODO: Iterate only over the first three entity indexes for * now. */ for (guint i = 0; i < 3; i++) { guint16 version_raw = 0; g_autofree gchar *version = NULL; g_autoptr(FuLogitechHidppRadio) radio = NULL; g_autoptr(GString) radio_version = NULL; msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = FU_LOGITECH_HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = FU_LOGITECH_HIDPP_SUBID_GET_LONG_REGISTER; msg->function_id = BOLT_REGISTER_RECEIVER_FW_INFORMATION; msg->data[0] = i; msg->hidpp_version = 1; if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self), msg, error)) { g_prefix_error(error, "failed to read device config: "); return FALSE; } switch (msg->data[0]) { case 0: /* main application */ if (!fu_memread_uint16_safe(msg->data, sizeof(msg->data), 0x03, &version_raw, G_BIG_ENDIAN, error)) return FALSE; version = fu_logitech_hidpp_format_version("MPR", msg->data[1], msg->data[2], version_raw); fu_device_set_version(device, version); break; case 1: /* bootloader */ if (!fu_memread_uint16_safe(msg->data, sizeof(msg->data), 0x03, &version_raw, G_BIG_ENDIAN, error)) return FALSE; version = fu_logitech_hidpp_format_version("BOT", msg->data[1], msg->data[2], version_raw); fu_device_set_version_bootloader(device, version); break; case 5: /* SoftDevice */ radio_version = g_string_new(NULL); radio = fu_logitech_hidpp_radio_new(ctx, i); fu_device_add_instance_u16( FU_DEVICE(radio), "VEN", fu_udev_device_get_vendor(FU_UDEV_DEVICE(device))); fu_device_add_instance_u16( FU_DEVICE(radio), "DEV", fu_udev_device_get_model(FU_UDEV_DEVICE(device))); fu_device_add_instance_u8(FU_DEVICE(radio), "ENT", msg->data[0]); fu_device_set_physical_id(FU_DEVICE(radio), fu_device_get_physical_id(device)); fu_device_set_logical_id(FU_DEVICE(radio), "Receiver_SoftDevice"); if (!fu_device_build_instance_id(FU_DEVICE(radio), error, "HIDRAW", "VEN", "DEV", "ENT", NULL)) return FALSE; if (!fu_memread_uint16_safe(msg->data, sizeof(msg->data), 0x03, &version_raw, G_BIG_ENDIAN, error)) return FALSE; g_string_append_printf(radio_version, "0x%.4x", version_raw); fu_device_set_version(FU_DEVICE(radio), radio_version->str); fu_device_add_child(device, FU_DEVICE(radio)); break; default: break; } } /* enable HID++ notifications */ if (!fu_logitech_hidpp_runtime_enable_notifications(self, error)) { g_prefix_error(error, "failed to enable notifications: "); return FALSE; } fu_logitech_hidpp_runtime_bolt_poll_peripherals(device); /* success */ return TRUE; } static gboolean fu_logitech_hidpp_runtime_bolt_setup(FuDevice *device, GError **error) { g_autoptr(GError) error_local = NULL; for (guint i = 0; i < 5; i++) { g_clear_error(&error_local); /* HID++1.0 devices have to sleep to allow Solaar to talk to * the device first -- we can't use the SwID as this is a * HID++2.0 feature */ fu_device_sleep(device, 200); /* ms */ if (fu_logitech_hidpp_runtime_bolt_setup_internal(device, &error_local)) return TRUE; if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_DATA)) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } static void fu_logitech_hidpp_runtime_bolt_class_init(FuLogitechHidppRuntimeBoltClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->detach = fu_logitech_hidpp_runtime_bolt_detach; klass_device->setup = fu_logitech_hidpp_runtime_bolt_setup; klass_device->poll = fu_logitech_hidpp_runtime_bolt_poll; klass_device->to_string = fu_logitech_hidpp_runtime_bolt_to_string; } static void fu_logitech_hidpp_runtime_bolt_init(FuLogitechHidppRuntimeBolt *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_USER_REPLUG); fu_device_set_vendor(FU_DEVICE(self), "Logitech"); fu_device_set_name(FU_DEVICE(self), "Bolt Receiver"); fu_device_add_protocol(FU_DEVICE(self), "com.logitech.unifyingsigned"); } fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-bolt.h000066400000000000000000000006321460375044200254240ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-logitech-hidpp-runtime.h" #define FU_TYPE_HIDPP_RUNTIME_BOLT (fu_logitech_hidpp_runtime_bolt_get_type()) G_DECLARE_FINAL_TYPE(FuLogitechHidppRuntimeBolt, fu_logitech_hidpp_runtime_bolt, FU, HIDPP_RUNTIME_BOLT, FuLogitechHidppRuntime) fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-unifying.c000066400000000000000000000136461460375044200263200ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-hidpp.h" #include "fu-logitech-hidpp-runtime-unifying.h" #include "fu-logitech-hidpp-struct.h" struct _FuLogitechHidppRuntimeUnifying { FuLogitechHidppRuntime parent_instance; }; G_DEFINE_TYPE(FuLogitechHidppRuntimeUnifying, fu_logitech_hidpp_runtime_unifying, FU_TYPE_HIDPP_RUNTIME) #define GET_PRIVATE(o) (fu_logitech_hidpp_runtime_unifying_get_instance_private(o)) static gboolean fu_logitech_hidpp_runtime_unifying_detach(FuDevice *device, FuProgress *progress, GError **error) { FuLogitechHidppRuntime *self = FU_HIDPP_RUNTIME(device); g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); g_autoptr(GError) error_local = NULL; msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = FU_LOGITECH_HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = FU_LOGITECH_HIDPP_SUBID_SET_REGISTER; msg->function_id = FU_LOGITECH_HIDPP_REGISTER_DEVICE_FIRMWARE_UPDATE_MODE; msg->data[0] = 'I'; msg->data[1] = 'C'; msg->data[2] = 'P'; msg->hidpp_version = 1; msg->flags = FU_LOGITECH_HIDPP_HIDPP_MSG_FLAG_LONGER_TIMEOUT; if (!fu_logitech_hidpp_send(fu_logitech_hidpp_runtime_get_io_channel(self), msg, FU_LOGITECH_HIDPP_DEVICE_TIMEOUT_MS, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_WRITE)) { g_debug("failed to detach to bootloader: %s", error_local->message); } else { g_prefix_error(&error_local, "failed to detach to bootloader: "); g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_logitech_hidpp_runtime_unifying_setup_internal(FuDevice *device, GError **error) { FuLogitechHidppRuntime *self = FU_HIDPP_RUNTIME(device); guint8 config[10]; g_autofree gchar *version_fw = NULL; /* read all 10 bytes of the version register */ memset(config, 0x00, sizeof(config)); for (guint i = 0x01; i < 0x05; i++) { g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); /* workaround a bug in the 12.01 firmware, which fails with * INVALID_VALUE when reading MCU1_HW_VERSION */ if (i == 0x03) continue; msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = FU_LOGITECH_HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = FU_LOGITECH_HIDPP_SUBID_GET_REGISTER; msg->function_id = FU_LOGITECH_HIDPP_REGISTER_DEVICE_FIRMWARE_INFORMATION; msg->data[0] = i; msg->hidpp_version = 1; if (!fu_logitech_hidpp_transfer(fu_logitech_hidpp_runtime_get_io_channel(self), msg, error)) { g_prefix_error(error, "failed to read device config: "); return FALSE; } if (!fu_memcpy_safe(config, sizeof(config), i * 2, /* dst */ msg->data, sizeof(msg->data), 0x1, /* src */ 2, error)) return FALSE; } /* get firmware version */ version_fw = fu_logitech_hidpp_format_version("RQR", config[2], config[3], (guint16)config[4] << 8 | config[5]); fu_device_set_version(device, version_fw); /* get bootloader version */ if (fu_logitech_hidpp_runtime_get_version_bl_major(self) > 0) { g_autofree gchar *version_bl = NULL; version_bl = fu_logitech_hidpp_format_version( "BOT", fu_logitech_hidpp_runtime_get_version_bl_major(self), config[8], config[9]); fu_device_set_version_bootloader(FU_DEVICE(device), version_bl); /* is the USB receiver expecting signed firmware */ if ((fu_logitech_hidpp_runtime_get_version_bl_major(self) == 0x01 && config[8] >= 0x04) || (fu_logitech_hidpp_runtime_get_version_bl_major(self) == 0x03 && config[8] >= 0x02)) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_protocol(device, "com.logitech.unifyingsigned"); } } if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD)) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_protocol(device, "com.logitech.unifying"); } /* enable HID++ notifications */ if (!fu_logitech_hidpp_runtime_enable_notifications(self, error)) { g_prefix_error(error, "failed to enable notifications: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_hidpp_runtime_unifying_setup(FuDevice *device, GError **error) { g_autoptr(GError) error_local = NULL; for (guint i = 0; i < 5; i++) { g_clear_error(&error_local); /* HID++1.0 devices have to sleep to allow Solaar to talk to * the device first -- we can't use the SwID as this is a * HID++2.0 feature */ fu_device_sleep(device, 200); /* ms */ if (fu_logitech_hidpp_runtime_unifying_setup_internal(device, &error_local)) return TRUE; if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_DATA)) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } static void fu_logitech_hidpp_runtime_unifying_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 70, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 4, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 27, "reload"); } static void fu_logitech_hidpp_runtime_unifying_class_init(FuLogitechHidppRuntimeUnifyingClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->detach = fu_logitech_hidpp_runtime_unifying_detach; klass_device->setup = fu_logitech_hidpp_runtime_unifying_setup; klass_device->set_progress = fu_logitech_hidpp_runtime_unifying_set_progress; } static void fu_logitech_hidpp_runtime_unifying_init(FuLogitechHidppRuntimeUnifying *self) { } fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-runtime-unifying.h000066400000000000000000000006421460375044200263150ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-logitech-hidpp-runtime.h" #define FU_TYPE_HIDPP_RUNTIME_UNIFYING (fu_logitech_hidpp_runtime_unifying_get_type()) G_DECLARE_FINAL_TYPE(FuLogitechHidppRuntimeUnifying, fu_logitech_hidpp_runtime_unifying, FU, HIDPP_RUNTIME_UNIFYING, FuLogitechHidppRuntime) fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-runtime.c000066400000000000000000000202661460375044200244660ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-logitech-hidpp-common.h" #include "fu-logitech-hidpp-hidpp.h" #include "fu-logitech-hidpp-runtime.h" #include "fu-logitech-hidpp-struct.h" typedef struct { guint8 version_bl_major; FuIOChannel *io_channel; } FuLogitechHidppRuntimePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuLogitechHidppRuntime, fu_logitech_hidpp_runtime, FU_TYPE_UDEV_DEVICE) #define GET_PRIVATE(o) (fu_logitech_hidpp_runtime_get_instance_private(o)) FuIOChannel * fu_logitech_hidpp_runtime_get_io_channel(FuLogitechHidppRuntime *self) { FuLogitechHidppRuntimePrivate *priv; g_return_val_if_fail(FU_IS_HIDPP_RUNTIME(self), NULL); priv = GET_PRIVATE(self); return priv->io_channel; } guint8 fu_logitech_hidpp_runtime_get_version_bl_major(FuLogitechHidppRuntime *self) { FuLogitechHidppRuntimePrivate *priv; g_return_val_if_fail(FU_IS_HIDPP_RUNTIME(self), 0); priv = GET_PRIVATE(self); return priv->version_bl_major; } static void fu_logitech_hidpp_runtime_to_string(FuDevice *device, guint idt, GString *str) { FU_DEVICE_CLASS(fu_logitech_hidpp_runtime_parent_class)->to_string(device, idt, str); } gboolean fu_logitech_hidpp_runtime_enable_notifications(FuLogitechHidppRuntime *self, GError **error) { g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); FuLogitechHidppRuntimePrivate *priv = GET_PRIVATE(self); msg->report_id = FU_LOGITECH_HIDPP_REPORT_ID_SHORT; msg->device_id = FU_LOGITECH_HIDPP_DEVICE_IDX_RECEIVER; msg->sub_id = FU_LOGITECH_HIDPP_SUBID_SET_REGISTER; msg->function_id = FU_LOGITECH_HIDPP_REGISTER_HIDPP_NOTIFICATIONS; msg->data[0] = 0x00; msg->data[1] = 0x05; /* Wireless + SoftwarePresent */ msg->data[2] = 0x00; msg->hidpp_version = 1; return fu_logitech_hidpp_transfer(priv->io_channel, msg, error); } static gboolean fu_logitech_hidpp_runtime_close(FuDevice *device, GError **error) { FuLogitechHidppRuntime *self = FU_HIDPP_RUNTIME(device); FuLogitechHidppRuntimePrivate *priv = GET_PRIVATE(self); if (priv->io_channel != NULL) { if (!fu_io_channel_shutdown(priv->io_channel, error)) return FALSE; g_clear_object(&priv->io_channel); } return TRUE; } static gboolean fu_logitech_hidpp_runtime_poll(FuDevice *device, GError **error) { FuLogitechHidppRuntime *self = FU_HIDPP_RUNTIME(device); FuLogitechHidppRuntimePrivate *priv = GET_PRIVATE(self); const guint timeout = 1; /* ms */ g_autoptr(GError) error_local = NULL; g_autoptr(FuLogitechHidppHidppMsg) msg = fu_logitech_hidpp_msg_new(); g_autoptr(FuDeviceLocker) locker = NULL; /* open */ locker = fu_device_locker_new(self, error); if (locker == NULL) return FALSE; /* is there any pending data to read */ msg->hidpp_version = 1; if (!fu_logitech_hidpp_receive(priv->io_channel, msg, timeout, &error_local)) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) { return TRUE; } g_warning("failed to get pending read: %s", error_local->message); return TRUE; } /* HID++1.0 error */ if (!fu_logitech_hidpp_msg_is_error(msg, &error_local)) { g_warning("failed to get pending read: %s", error_local->message); return TRUE; } /* unifying receiver notification */ if (msg->report_id == FU_LOGITECH_HIDPP_REPORT_ID_SHORT) { switch (msg->sub_id) { case FU_LOGITECH_HIDPP_SUBID_DEVICE_CONNECTION: case FU_LOGITECH_HIDPP_SUBID_DEVICE_DISCONNECTION: case FU_LOGITECH_HIDPP_SUBID_DEVICE_LOCKING_CHANGED: g_debug("device connection event, do something"); break; case FU_LOGITECH_HIDPP_SUBID_LINK_QUALITY: g_debug("ignoring link quality message"); break; case FU_LOGITECH_HIDPP_SUBID_ERROR_MSG: g_debug("ignoring error message"); break; default: g_debug("unknown SubID %02x", msg->sub_id); break; } } return TRUE; } static gboolean fu_logitech_hidpp_runtime_open(FuDevice *device, GError **error) { FuLogitechHidppRuntime *self = FU_HIDPP_RUNTIME(device); FuLogitechHidppRuntimePrivate *priv = GET_PRIVATE(self); const gchar *devpath = fu_udev_device_get_device_file(FU_UDEV_DEVICE(device)); g_autoptr(FuIOChannel) io_channel = NULL; /* open, but don't block */ io_channel = fu_io_channel_new_file(devpath, error); if (io_channel == NULL) return FALSE; g_set_object(&priv->io_channel, io_channel); /* poll for notifications */ fu_device_set_poll_interval(device, FU_HIDPP_RECEIVER_RUNTIME_POLLING_INTERVAL); /* success */ return TRUE; } static gboolean fu_logitech_hidpp_runtime_probe(FuDevice *device, GError **error) { FuLogitechHidppRuntime *self = FU_HIDPP_RUNTIME(device); FuLogitechHidppRuntimePrivate *priv = GET_PRIVATE(self); GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); guint16 release = 0xffff; g_autoptr(GUdevDevice) udev_parent = NULL; g_autoptr(GUdevDevice) udev_parent_usb_interface = NULL; /* set the physical ID */ if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "usb", error)) return FALSE; /* generate bootloader-specific GUID */ udev_parent = g_udev_device_get_parent_with_subsystem(udev_device, "usb", "usb_device"); if (udev_parent != NULL) { const gchar *release_str; release_str = g_udev_device_get_property(udev_parent, "ID_REVISION"); if (release_str != NULL) release = g_ascii_strtoull(release_str, NULL, 16); } if (release != 0xffff) { g_autofree gchar *devid2 = NULL; const gchar *interface_str; switch (release &= 0xff00) { case 0x1200: /* Nordic */ devid2 = g_strdup_printf("USB\\VID_%04X&PID_%04X", (guint)FU_LOGITECH_HIDPP_DEVICE_VID, (guint)FU_LOGITECH_HIDPP_DEVICE_PID_BOOTLOADER_NORDIC); fu_device_add_counterpart_guid(device, devid2); priv->version_bl_major = 0x01; break; case 0x2400: /* Texas */ devid2 = g_strdup_printf("USB\\VID_%04X&PID_%04X", (guint)FU_LOGITECH_HIDPP_DEVICE_VID, (guint)FU_LOGITECH_HIDPP_DEVICE_PID_BOOTLOADER_TEXAS); fu_device_add_counterpart_guid(device, devid2); priv->version_bl_major = 0x03; break; case 0x0500: /* Bolt */ udev_parent_usb_interface = g_udev_device_get_parent_with_subsystem(udev_device, "usb", "usb_interface"); interface_str = g_udev_device_get_property(udev_parent_usb_interface, "INTERFACE"); if (interface_str == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "INTERFACE property not found in parent device"); return FALSE; } if (g_strcmp0(interface_str, "3/0/0") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "skipping hidraw device"); return FALSE; } devid2 = g_strdup_printf("USB\\VID_%04X&PID_%04X", (guint)FU_LOGITECH_HIDPP_DEVICE_VID, (guint)FU_LOGITECH_HIDPP_DEVICE_PID_BOOTLOADER_BOLT); fu_device_add_counterpart_guid(device, devid2); priv->version_bl_major = 0x03; break; default: g_warning("bootloader release %04x invalid", release); break; } } return TRUE; } static void fu_logitech_hidpp_runtime_finalize(GObject *object) { G_OBJECT_CLASS(fu_logitech_hidpp_runtime_parent_class)->finalize(object); } static void fu_logitech_hidpp_runtime_class_init(FuLogitechHidppRuntimeClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_logitech_hidpp_runtime_finalize; klass_device->open = fu_logitech_hidpp_runtime_open; klass_device->probe = fu_logitech_hidpp_runtime_probe; klass_device->close = fu_logitech_hidpp_runtime_close; klass_device->poll = fu_logitech_hidpp_runtime_poll; klass_device->to_string = fu_logitech_hidpp_runtime_to_string; } static void fu_logitech_hidpp_runtime_init(FuLogitechHidppRuntime *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_add_icon(FU_DEVICE(self), "usb-receiver"); fu_device_set_name(FU_DEVICE(self), "Unifying Receiver"); fu_device_set_summary(FU_DEVICE(self), "Miniaturised USB wireless receiver"); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-runtime.h000066400000000000000000000012641460375044200244700ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_HIDPP_RUNTIME (fu_logitech_hidpp_runtime_get_type()) G_DECLARE_DERIVABLE_TYPE(FuLogitechHidppRuntime, fu_logitech_hidpp_runtime, FU, HIDPP_RUNTIME, FuUdevDevice) struct _FuLogitechHidppRuntimeClass { FuUdevDeviceClass parent_class; }; gboolean fu_logitech_hidpp_runtime_enable_notifications(FuLogitechHidppRuntime *self, GError **error); FuIOChannel * fu_logitech_hidpp_runtime_get_io_channel(FuLogitechHidppRuntime *self); guint8 fu_logitech_hidpp_runtime_get_version_bl_major(FuLogitechHidppRuntime *self); fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp-self-test.c000066400000000000000000000016021460375044200247020ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-logitech-hidpp-common.h" static void fu_logitech_hidpp_common(void) { guint8 u8; guint16 u16; g_autofree gchar *ver1 = NULL; u8 = fu_logitech_hidpp_buffer_read_uint8("12"); g_assert_cmpint(u8, ==, 0x12); u16 = fu_logitech_hidpp_buffer_read_uint16("1234"); g_assert_cmpint(u16, ==, 0x1234); ver1 = fu_logitech_hidpp_format_version(" A ", 0x87, 0x65, 0x4321); g_assert_cmpstr(ver1, ==, "A87.65_B4321"); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func("/unifying/common", fu_logitech_hidpp_common); return g_test_run(); } fwupd-1.9.16/plugins/logitech-hidpp/fu-logitech-hidpp.rs000066400000000000000000000065471460375044200232150ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum LogitechHidppFeature { Root = 0x0000, IFeatureSet = 0x0001, IFirmwareInfo = 0x0003, GetDeviceNameType = 0x0005, DfuControl = 0x00C1, DfuControlSigned = 0x00C2, DfuControlBolt = 0x00C3, Dfu = 0x00D0, BatteryLevelStatus = 0x1000, UnifiedBattery = 0x1004, KbdReprogrammableKeys = 0x1B00, SpecialKeysButtons = 0x1B04, MousePointerBasic = 0x2200, AdjustableDpi = 0x2201, AdjustableReportRate = 0x8060, ColorLedEffects = 0x8070, OnboardProfiles = 0x8100, MouseButtonSpy = 0x8110, } #[derive(ToString)] enum LogitechHidppDeviceIdx { Wired = 0x00, Receiver = 0xFF, } #[derive(ToString)] enum LogitechHidppReportId { Short = 0x10, Long = 0x11, VeryLong = 0x12, } // HID++1.0 registers #[derive(ToString)] enum LogitechHidppRegister { HidppNotifications = 0x00, EnableIndividualFeatures = 0x01, BatteryStatus = 0x07, BatteryMileage = 0x0D, Profile = 0x0F, LedStatus = 0x51, LedIntensity = 0x54, LedColor = 0x57, OpticalSensorSettings = 0x61, CurrentResolution = 0x63, UsbRefreshRate = 0x64, GenericMemoryManagement = 0xA0, HotControl = 0xA1, ReadMemory = 0xA2, DeviceConnectionDisconnection = 0xB2, PairingInformation = 0xB5, DeviceFirmwareUpdateMode = 0xF0, DeviceFirmwareInformation = 0xF1, } #[derive(ToString)] enum LogitechHidppSubid { VendorSpecificKeys = 0x03, PowerKeys = 0x04, Roller = 0x05, MouseExtraButtons = 0x06, BatteryChargingLevel = 0x07, UserInterfaceEvent = 0x08, FLockStatus = 0x09, CalculatorResult = 0x0A, MenuNavigate = 0x0B, FnKey = 0x0C, BatteryMileage = 0x0D, UartRx = 0x0E, BacklightDurationUpdate = 0x17, DeviceDisconnection = 0x40, DeviceConnection = 0x41, DeviceDiscovery = 0x42, PinCodeRequest = 0x43, ReceiverWorkingMode = 0x44, ErrorMessage = 0x45, RfLinkChange = 0x46, Hci = 0x48, LinkQuality = 0x49, DeviceLockingChanged = 0x4a, WirelessDeviceChange = 0x4B, Acl = 0x51, VoipTelephonyEvent = 0x5B, Led = 0x60, GestureAndAir = 0x65, TouchpadMultiTouch = 0x66, Traceability = 0x78, SetRegister = 0x80, GetRegister = 0x81, SetLongRegister = 0x82, GetLongRegister = 0x83, SetVeryLongRegister = 0x84, GetVeryLongRegister = 0x85, ErrorMsg = 0x8F, ErrorMsg_20 = 0xFF, } enum LogitechHidppBootloaderCmd { General_error = 0x01, Read = 0x10, Write = 0x20, WriteInvalidAddr = 0x21, WriteVerifyFail = 0x22, WriteNonzeroStart = 0x23, WriteInvalidCrc = 0x24, ErasePage = 0x30, ErasePageInvalidAddr = 0x31, ErasePageNonzeroStart = 0x33, GetHwPlatformId = 0x40, GetFwVersion = 0x50, GetChecksum = 0x60, Reboot = 0x70, GetMeminfo = 0x80, GetBlVersion = 0x90, GetInitFwVersion = 0xa0, ReadSignature = 0xb0, WriteRamBuffer = 0xc0, WriteRamBufferInvalidAddr = 0xc1, WriteRamBufferOverflow = 0xc2, FlashRam = 0xd0, FlashRamInvalidAddr = 0xd1, FlashRamWrongCrc = 0xd2, FlashRamPage0Invalid = 0xd3, FlashRamInvalidOrder = 0xd4, WriteSignature = 0xe0, } fwupd-1.9.16/plugins/logitech-hidpp/logitech-hidpp.quirk000066400000000000000000000114701460375044200233030ustar00rootroot00000000000000# Unifying Receiver [HIDRAW\VEN_046D&DEV_C52B] Plugin = logitech_hidpp GType = FuLogitechHidppRuntimeUnifying VendorId = USB:0x046D InstallDuration = 30 # Bolt Receiver (runtime) [HIDRAW\VEN_046D&DEV_C548] Plugin = logitech_hidpp GType = FuLogitechHidppRuntimeBolt VendorId = USB:0x046D InstallDuration = 30 # Bolt Receiver (bootloader) [HIDRAW\VEN_046D&DEV_AB07] Plugin = logitech_hidpp Name = Bolt Receiver Vendor = Logitech GType = FuLogitechHidppDevice CounterpartGuid = HIDRAW\VEN_046D&DEV_C548 InstallDuration = 30 Flags = rebind-attach,force-receiver-id,replug-match-guid,add-radio LogitechHidppModelId = B601C5480000 # Bolt receiver radio (bootloader) [HIDRAW\VEN_046D&MOD_B601C5480000&ENT_05] CounterpartGuid = HIDRAW\VEN_046D&DEV_C548&ENT_05 Flags = is-bootloader # Nordic [USB\VID_046D&PID_AAAA] Plugin = logitech_hidpp GType = FuLogitechHidppBootloaderNordic FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 30 # Nordic Pico [USB\VID_046D&PID_AAAE] Plugin = logitech_hidpp GType = FuLogitechHidppBootloaderNordic FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 30 # Texas [USB\VID_046D&PID_AAAC] Plugin = logitech_hidpp GType = FuLogitechHidppBootloaderTexas FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 18 # Texas Pico [USB\VID_046D&PID_AAAD] Plugin = logitech_hidpp GType = FuLogitechHidppBootloaderTexas FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 18 # Texas Pico (another variant) / C-U0016 [USB\VID_046D&PID_AAE5] Plugin = logitech_hidpp GType = FuLogitechHidppBootloaderTexas FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 18 # Texas Unknown # https://github.com/Logitech/fw_updates/blob/1ddde61310573ecea54c4204b401393a90f4bbae/RQR24/RQR24.11/RQR24.11_B0036.txt#L22 [USB\VID_046D&PID_AAF8] Plugin = logitech_hidpp GType = FuLogitechHidppBootloaderTexas FirmwareSizeMin = 0x4000 CounterpartGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 18 # K780 (through Unifying receiver) [HIDRAW\VEN_046D&DEV_405B] Plugin = logitech_hidpp GType = FuLogitechHidppDevice ParentGuid = HIDRAW\VEN_046D&DEV_C52B InstallDuration = 150 # MR0077 [HIDRAW\VEN_046D&MOD_B02800000000] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio InstallDuration = 270 # MR0077 (BLE direct) [HIDRAW\VEN_046D&DEV_B028] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,ble,rebind-attach InstallDuration = 270 # YR0073 [HIDRAW\VEN_046D&MOD_B36300000000] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio InstallDuration = 270 # YR0073 (BLE direct) [HIDRAW\VEN_046D&DEV_B363] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,ble,rebind-attach InstallDuration = 270 # M650 [HIDRAW\VEN_046D&MOD_B02A00000000] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,no-request-required InstallDuration = 270 # M650 (BLE direct) [HIDRAW\VEN_046D&DEV_B02A] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,ble,rebind-attach,no-request-required InstallDuration = 270 # M750 [HIDRAW\VEN_046D&MOD_B02C00000000] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,no-request-required InstallDuration = 270 # M750 (BLE direct) [HIDRAW\VEN_046D&DEV_B02C] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,ble,rebind-attach,no-request-required InstallDuration = 270 # M650 For Business [HIDRAW\VEN_046D&MOD_B03200000000] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,no-request-required InstallDuration = 270 # M650 For Business (BLE direct) [HIDRAW\VEN_046D&DEV_B032] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,ble,rebind-attach,no-request-required InstallDuration = 270 # M550 [HIDRAW\VEN_046D&MOD_B02B00000000] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,no-request-required InstallDuration = 270 # M550 (BLE direct) [HIDRAW\VEN_046D&DEV_B02B] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,ble,rebind-attach,no-request-required InstallDuration = 270 # K650 [HIDRAW\VEN_046D&MOD_B36F00000000] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,no-request-required InstallDuration = 270 # K650 (BLE direct) [HIDRAW\VEN_046D&DEV_B36F] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,ble,rebind-attach,no-request-required InstallDuration = 270 # K650 For Business [HIDRAW\VEN_046D&MOD_B37000000000] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,no-request-required InstallDuration = 270 # K650 For Business (BLE direct) [HIDRAW\VEN_046D&DEV_B370] Plugin = logitech_hidpp GType = FuLogitechHidppDevice Flags = add-radio,ble,rebind-attach,no-request-required InstallDuration = 270 fwupd-1.9.16/plugins/logitech-hidpp/meson.build000066400000000000000000000027541460375044200214750ustar00rootroot00000000000000if gudev.found() and gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginLogitechHidpp"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('logitech-hidpp.quirk') plugin_builtin_logitech_hidpp = static_library('fu_plugin_logitech_hidpp', rustgen.process('fu-logitech-hidpp.rs'), sources: [ 'fu-logitech-hidpp-plugin.c', 'fu-logitech-hidpp-bootloader.c', 'fu-logitech-hidpp-bootloader-nordic.c', 'fu-logitech-hidpp-bootloader-texas.c', 'fu-logitech-hidpp-common.c', 'fu-logitech-hidpp-hidpp.c', 'fu-logitech-hidpp-device.c', 'fu-logitech-hidpp-hidpp-msg.c', 'fu-logitech-hidpp-runtime.c', 'fu-logitech-hidpp-runtime-unifying.c', 'fu-logitech-hidpp-runtime-bolt.c', 'fu-logitech-hidpp-radio.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_logitech_hidpp if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'logitech-hidpp-self-test', sources: [ 'fu-logitech-hidpp-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_logitech_hidpp, ], c_args: [ cargs, '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('logitech-hidpp-self-test', e, env: env) endif endif fwupd-1.9.16/plugins/logitech-rallysystem/000077500000000000000000000000001460375044200206075ustar00rootroot00000000000000fwupd-1.9.16/plugins/logitech-rallysystem/README.md000066400000000000000000000025421460375044200220710ustar00rootroot00000000000000--- title: Plugin: Logitech Rally System --- ## Introduction This plugin can upgrade the firmware on Logitech Video Collaboration products (RallySystem). ioctl is used to query system version and serial number from Audio module of RallySystem. USB bulk transfer is used to push new firmware image to TableHub module of RallySystem ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `com.logitech.vc.rallysystem` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_046D&DEV_088F` ## Update Behavior The peripheral firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Design Notes ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x046D` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb` and the ioctl interfaces `HIDIOCGFEATURE`, `HIDIOCSFEATURE` and `HIDIOCGINPUT`. ## Version Considerations This plugin has been available since fwupd version `1.9.7`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Sanjay Sheth: @vcdmp fwupd-1.9.16/plugins/logitech-rallysystem/fu-logitech-rallysystem-audio-device.c000066400000000000000000000147431460375044200301140ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_IOCTL_H #include #include #endif #include "fu-logitech-rallysystem-audio-device.h" #include "fu-logitech-rallysystem-struct.h" #define FU_LOGITECH_RALLYSYSTEM_AUDIO_DEVICE_IOCTL_TIMEOUT 2500 /* ms */ #define TOPOLOGY_DATA_LEN 513 /* plus 1 byte for the report id */ #define SERIAL_NUMBER_REQUEST_DATA_LEN 49 #define SERIAL_NUMBER_RESPONSE_DATA_LEN 128 struct _FuLogitechRallysystemAudioDevice { FuUdevDevice parent_instance; }; G_DEFINE_TYPE(FuLogitechRallysystemAudioDevice, fu_logitech_rallysystem_audio_device, FU_TYPE_UDEV_DEVICE) static gboolean fu_logitech_rallysystem_audio_device_set_feature(FuLogitechRallysystemAudioDevice *self, const guint8 *buf, guint bufsz, GError **error) { #ifdef HAVE_HIDRAW_H fu_dump_raw(G_LOG_DOMAIN, "HidSetFeature", buf, bufsz); return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(bufsz), (guint8 *)buf, NULL, FU_LOGITECH_RALLYSYSTEM_AUDIO_DEVICE_IOCTL_TIMEOUT, error); #else /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_logitech_rallysystem_audio_device_get_feature(FuLogitechRallysystemAudioDevice *self, guint8 *buf, guint bufsz, GError **error) { #ifdef HAVE_HIDRAW_H fu_dump_raw(G_LOG_DOMAIN, "HidGetFeatureReq", buf, bufsz); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGFEATURE(bufsz), buf, NULL, FU_LOGITECH_RALLYSYSTEM_AUDIO_DEVICE_IOCTL_TIMEOUT, error)) { return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "HidGetFeatureRes", buf, bufsz); return TRUE; #else /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_logitech_rallysystem_audio_device_set_version(FuLogitechRallysystemAudioDevice *self, GError **error) { guint8 buf[TOPOLOGY_DATA_LEN] = {0x3E, 0x0}; guint32 fwversion = 0; /* setup HID report to query current device version */ if (!fu_logitech_rallysystem_audio_device_get_feature(self, buf, sizeof(buf), error)) return FALSE; if (!fu_memread_uint24_safe( buf, sizeof(buf), 0xB8, /* topology size of 12 bytes * 15 elements, plus an offset */ &fwversion, G_BIG_ENDIAN, error)) return FALSE; fu_device_set_version_u32(FU_DEVICE(self), fwversion); /* success */ return TRUE; } static gboolean fu_logitech_rallysystem_audio_device_set_serial(FuLogitechRallysystemAudioDevice *self, GError **error) { guint8 buf_req[SERIAL_NUMBER_REQUEST_DATA_LEN] = {0x28, 0x85, 0x08, 0xBB, 0x1B, 0x00, 0x01, 0x30, 0, 0, 0, 0x0C}; guint8 buf_res[SERIAL_NUMBER_RESPONSE_DATA_LEN] = {0x29, 0x0}; g_autoptr(GByteArray) st = NULL; g_autoptr(GString) serial = g_string_new(NULL); /* setup HID report for serial number request */ if (!fu_logitech_rallysystem_audio_device_set_feature(self, buf_req, sizeof(buf_req), error)) return FALSE; /* wait 100ms for device to consume request and prepare for response */ fu_device_sleep(FU_DEVICE(self), 100); /* setup HID report to query serial number */ if (!fu_logitech_rallysystem_audio_device_get_feature(self, buf_res, sizeof(buf_res), error)) return FALSE; /* desired serial number format: PID:YYYYMMDD:EthernetMacAddress */ st = fu_struct_audio_serial_number_parse(buf_res, sizeof(buf_res), 0x0, error); if (st == NULL) return FALSE; g_string_append_printf(serial, "%04x:%04u%02u%02u:", fu_struct_audio_serial_number_get_pid(st), fu_struct_audio_serial_number_get_year(st), fu_struct_audio_serial_number_get_month(st), fu_struct_audio_serial_number_get_day(st)); for (guint i = 0x0; i < FU_STRUCT_AUDIO_SERIAL_NUMBER_SIZE_MAC_ADDRESS; i++) g_string_append_printf(serial, "%02x", st->data[i]); fu_device_set_serial(FU_DEVICE(self), serial->str); /* success */ return TRUE; } static gboolean fu_logitech_rallysystem_audio_device_setup(FuDevice *device, GError **error) { FuLogitechRallysystemAudioDevice *self = FU_LOGITECH_RALLYSYSTEM_AUDIO_DEVICE(device); if (!fu_logitech_rallysystem_audio_device_set_version(self, error)) return FALSE; if (!fu_logitech_rallysystem_audio_device_set_serial(self, error)) return FALSE; return TRUE; } static gboolean fu_logitech_rallysystem_audio_device_probe(FuDevice *device, GError **error) { /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_logitech_rallysystem_audio_device_parent_class) ->probe(device, error)) return FALSE; /* ignore unsupported subsystems */ if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "hidraw") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct subsystem=%s, expected hidraw", fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device))); return FALSE; } /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); } static void fu_logitech_rallysystem_audio_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 0, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 100, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_logitech_rallysystem_audio_device_init(FuLogitechRallysystemAudioDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.logitech.vc.rallysystem"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_retry_set_delay(FU_DEVICE(self), 1000); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | FU_UDEV_DEVICE_FLAG_OPEN_NONBLOCK | FU_UDEV_DEVICE_FLAG_IOCTL_RETRY); } static void fu_logitech_rallysystem_audio_device_class_init(FuLogitechRallysystemAudioDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_logitech_rallysystem_audio_device_probe; klass_device->setup = fu_logitech_rallysystem_audio_device_setup; klass_device->set_progress = fu_logitech_rallysystem_audio_device_set_progress; } fwupd-1.9.16/plugins/logitech-rallysystem/fu-logitech-rallysystem-audio-device.h000066400000000000000000000006251460375044200301130ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_LOGITECH_RALLYSYSTEM_AUDIO_DEVICE (fu_logitech_rallysystem_audio_device_get_type()) G_DECLARE_FINAL_TYPE(FuLogitechRallysystemAudioDevice, fu_logitech_rallysystem_audio_device, FU, LOGITECH_RALLYSYSTEM_AUDIO_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/logitech-rallysystem/fu-logitech-rallysystem-plugin.c000066400000000000000000000020421460375044200270410ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-logitech-rallysystem-audio-device.h" #include "fu-logitech-rallysystem-plugin.h" #include "fu-logitech-rallysystem-tablehub-device.h" struct _FuLogitechRallysystemPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuLogitechRallysystemPlugin, fu_logitech_rallysystem_plugin, FU_TYPE_PLUGIN) static void fu_logitech_rallysystem_plugin_init(FuLogitechRallysystemPlugin *self) { } static void fu_logitech_rallysystem_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "hidraw"); fu_plugin_add_device_gtype(plugin, FU_TYPE_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_LOGITECH_RALLYSYSTEM_AUDIO_DEVICE); } static void fu_logitech_rallysystem_plugin_class_init(FuLogitechRallysystemPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_logitech_rallysystem_plugin_constructed; } fwupd-1.9.16/plugins/logitech-rallysystem/fu-logitech-rallysystem-plugin.h000066400000000000000000000004341460375044200270510ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuLogitechRallysystemPlugin, fu_logitech_rallysystem_plugin, FU, LOGITECH_RALLYSYSTEM_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/logitech-rallysystem/fu-logitech-rallysystem-tablehub-device.c000066400000000000000000000313041460375044200305710ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-logitech-rallysystem-struct.h" #include "fu-logitech-rallysystem-tablehub-device.h" enum { EP_OUT, EP_IN, EP_LAST }; #define FU_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE_IOCTL_TIMEOUT 3000 /* 3 sec */ #define FU_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE_IOCTL_PROGRESS_TIMEOUT 90000 /* 90 sec */ struct _FuLogitechRallysystemTablehubDevice { FuUsbDevice parent_instance; guint bulk_ep[EP_LAST]; }; G_DEFINE_TYPE(FuLogitechRallysystemTablehubDevice, fu_logitech_rallysystem_tablehub_device, FU_TYPE_USB_DEVICE) static void fu_logitech_rallysystem_tablehub_device_to_string(FuDevice *device, guint idt, GString *str) { FuLogitechRallysystemTablehubDevice *self = FU_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE(device); fu_string_append_kx(str, idt, "EpBulkIn", self->bulk_ep[EP_IN]); fu_string_append_kx(str, idt, "EpBulkOut", self->bulk_ep[EP_OUT]); } static gboolean fu_logitech_rallysystem_tablehub_device_probe(FuDevice *device, GError **error) { FuLogitechRallysystemTablehubDevice *self = FU_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE(device); guint8 bulk_iface = G_MAXUINT8; g_autoptr(GPtrArray) intfs = NULL; intfs = g_usb_device_get_interfaces(fu_usb_device_get_dev(FU_USB_DEVICE(self)), error); if (intfs == NULL) return FALSE; for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index(intfs, i); if (g_usb_interface_get_class(intf) == G_USB_DEVICE_CLASS_VENDOR_SPECIFIC) { g_autoptr(GPtrArray) endpoints = g_usb_interface_get_endpoints(intf); bulk_iface = g_usb_interface_get_number(intf); if (endpoints == NULL) continue; for (guint j = 0; j < endpoints->len; j++) { GUsbEndpoint *ep = g_ptr_array_index(endpoints, j); if (j == EP_OUT) self->bulk_ep[EP_OUT] = g_usb_endpoint_get_address(ep); else self->bulk_ep[EP_IN] = g_usb_endpoint_get_address(ep); } } } if (bulk_iface == G_MAXUINT8) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no bulk interface found"); return FALSE; } fu_usb_device_add_interface(FU_USB_DEVICE(self), bulk_iface); return TRUE; } static gboolean fu_logitech_rallysystem_tablehub_device_send(FuLogitechRallysystemTablehubDevice *self, guint8 *buf, guint32 bufsz, GError **error) { gsize actual_length = 0; if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), self->bulk_ep[EP_OUT], buf, bufsz, &actual_length, FU_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE_IOCTL_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to send using bulk transfer: "); return FALSE; } if (bufsz != actual_length) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to send full packet using bulk transfer"); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "RallysystemBulkTx", buf, bufsz); return TRUE; } static gboolean fu_logitech_rallysystem_tablehub_device_recv(FuLogitechRallysystemTablehubDevice *self, guint8 *buf, guint32 bufsz, guint timeout, GError **error) { gsize actual_length = 0; if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), self->bulk_ep[EP_IN], buf, bufsz, &actual_length, timeout, NULL, error)) { g_prefix_error(error, "failed to receive using bulk transfer: "); return FALSE; } if (bufsz != actual_length) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to receive full packet using bulk transfer"); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "RallysystemBulkRx", buf, bufsz); return TRUE; } static gboolean fu_logitech_rallysystem_tablehub_device_write_fw(FuLogitechRallysystemTablehubDevice *self, GBytes *fw, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x200); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autofree guint8 *data_mut = NULL; data_mut = fu_memdup_safe(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error); if (data_mut == NULL) return FALSE; if (!fu_logitech_rallysystem_tablehub_device_send(self, data_mut, fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to send data packet 0x%x: ", i); return FALSE; } fu_progress_step_done(progress); } return TRUE; } static gboolean fu_logitech_rallysystem_tablehub_device_progress_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechRallysystemTablehubDevice *self = FU_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE(device); guint8 buf[FU_STRUCT_USB_PROGRESS_RESPONSE_SIZE] = {0x0}; g_autoptr(GByteArray) st_res = NULL; if (!fu_logitech_rallysystem_tablehub_device_recv( self, buf, sizeof(buf), FU_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE_IOCTL_PROGRESS_TIMEOUT, error)) { g_prefix_error(error, "failed to get progress report: "); return FALSE; } st_res = fu_struct_usb_progress_response_parse(buf, sizeof(buf), 0x0, error); if (st_res == NULL) return FALSE; if (fu_struct_usb_progress_response_get_completed(st_res) != 100) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "percentage only %u%%", fu_struct_usb_progress_response_get_completed(st_res)); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_rallysystem_tablehub_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuLogitechRallysystemTablehubDevice *self = FU_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE(device); guint8 buf[FU_STRUCT_USB_FIRMWARE_DOWNLOAD_RESPONSE_SIZE] = {0x0}; g_autoptr(GBytes) fw = NULL; g_autoptr(GByteArray) st_req = fu_struct_usb_firmware_download_request_new(); g_autoptr(GByteArray) st_res = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "init"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 4, "device-write-blocks"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 35, "uninit"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 60, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; fu_struct_usb_firmware_download_request_set_len(st_req, g_bytes_get_size(fw)); if (!fu_struct_usb_firmware_download_request_set_fw_version(st_req, fu_device_get_version(device), error)) { g_prefix_error(error, "failed to copy download mode payload: "); return FALSE; } if (!fu_logitech_rallysystem_tablehub_device_send(self, st_req->data, st_req->len, error)) { g_prefix_error(error, "failed to set download mode: "); return FALSE; } if (!fu_logitech_rallysystem_tablehub_device_recv( self, buf, sizeof(buf), FU_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error( error, "failed to receive set download mode response: please reboot the device: "); return FALSE; } st_res = fu_struct_usb_firmware_download_response_parse(buf, sizeof(buf), 0x0, error); if (st_res == NULL) return FALSE; fu_progress_step_done(progress); /* push each block to device */ if (!fu_logitech_rallysystem_tablehub_device_write_fw(self, fw, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* image file pushed. Device validates and uploads new image on inactive partition. * After upload is finished, device reboots itself */ if (!fu_device_retry_full(FU_DEVICE(self), fu_logitech_rallysystem_tablehub_device_progress_cb, 210, 1000, NULL, error)) { g_prefix_error(error, "failed to wait for 100pc: "); return FALSE; } fu_progress_step_done(progress); /* return no error since table hub may not come back right after reboot, it goes straight * to update camera/tv if needed and will be disappear until it finished the tasks */ fu_device_sleep_full(FU_DEVICE(self), 7 * 60 * 1000, fu_progress_get_child(progress)); fu_progress_step_done(progress); /* success! */ fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_logitech_rallysystem_tablehub_device_send_init_cmd_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechRallysystemTablehubDevice *self = FU_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE(device); guint8 buf[FU_STRUCT_USB_INIT_RESPONSE_SIZE] = {0x0}; g_autoptr(GByteArray) st_req = fu_struct_usb_init_request_new(); g_autoptr(GByteArray) st_res = NULL; if (!fu_logitech_rallysystem_tablehub_device_send(self, st_req->data, st_req->len, error)) { g_prefix_error(error, "failed to send init packet: "); return FALSE; } if (!fu_logitech_rallysystem_tablehub_device_recv( self, buf, sizeof(buf), FU_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error(error, "failed to receive init packet: "); return FALSE; } st_res = fu_struct_usb_init_response_parse(buf, sizeof(buf), 0x0, error); if (st_res == NULL) { g_prefix_error(error, "failed to get correct init packet: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_rallysystem_tablehub_device_setup(FuDevice *device, GError **error) { FuLogitechRallysystemTablehubDevice *self = FU_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE(device); guint8 buf[FU_STRUCT_USB_READ_VERSION_RESPONSE_SIZE] = {0x0}; g_autofree gchar *fw_version = NULL; g_autoptr(GByteArray) st_req = fu_struct_usb_read_version_request_new(); g_autoptr(GByteArray) st_res = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_logitech_rallysystem_tablehub_device_parent_class) ->setup(device, error)) return FALSE; /* sending INIT. Retry if device is not in IDLE state to receive the data */ if (!fu_device_retry(device, fu_logitech_rallysystem_tablehub_device_send_init_cmd_cb, 5, NULL, error)) { g_prefix_error(error, "failed to write init packet: please reboot the device: "); return FALSE; } /* query tablehub firmware version */ if (!fu_logitech_rallysystem_tablehub_device_send(self, st_req->data, st_req->len, error)) { g_prefix_error(error, "failed to send tablehub firmware version request: " "please reboot the device: "); return FALSE; } if (!fu_logitech_rallysystem_tablehub_device_recv( self, buf, sizeof(buf), FU_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error(error, "failed to get response for tablehub firmware " "version request: please reboot the device: "); return FALSE; } st_res = fu_struct_usb_read_version_response_parse(buf, sizeof(buf), 0x0, error); if (st_res == NULL) return FALSE; fw_version = fu_struct_usb_read_version_response_get_fw_version(st_res); fu_device_set_version(FU_DEVICE(self), fw_version); /* success! */ return TRUE; } static void fu_logitech_rallysystem_tablehub_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 55, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 45, "reload"); } static void fu_logitech_rallysystem_tablehub_device_init(FuLogitechRallysystemTablehubDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.logitech.vc.rallysystem"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_set_install_duration(FU_DEVICE(self), 5 * 60); fu_device_set_remove_delay(FU_DEVICE(self), 60 * 1000); /* wait for subcomponent */ } static void fu_logitech_rallysystem_tablehub_device_class_init(FuLogitechRallysystemTablehubDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_logitech_rallysystem_tablehub_device_to_string; klass_device->write_firmware = fu_logitech_rallysystem_tablehub_device_write_firmware; klass_device->probe = fu_logitech_rallysystem_tablehub_device_probe; klass_device->setup = fu_logitech_rallysystem_tablehub_device_setup; klass_device->set_progress = fu_logitech_rallysystem_tablehub_device_set_progress; } fwupd-1.9.16/plugins/logitech-rallysystem/fu-logitech-rallysystem-tablehub-device.h000066400000000000000000000007221460375044200305760ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE \ fu_logitech_rallysystem_tablehub_device_get_type() G_DECLARE_FINAL_TYPE(FuLogitechRallysystemTablehubDevice, fu_logitech_rallysystem_tablehub_device, FU, LOGITECH_RALLYSYSTEM_TABLEHUB_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/logitech-rallysystem/fu-logitech-rallysystem.rs000066400000000000000000000026631460375044200257600ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(Parse)] struct AudioSerialNumber { mac_address: [u8; 6], pid: u16le, year: u16le, month: u8, day: u8, } #[repr(u16le)] enum UsbCmdId { Init = 0xCC01, FirmwareDownload = 0xCC03, ReadVersion = 0xCC07, } #[repr(u16le)] enum UsbCmdStatus { Ok = 0x0, // inferred Req = 0xFFFF, InitReq = 0xBEEF, InitReqAck = 0x0999, } #[derive(New)] struct UsbInitRequest { id: UsbCmdId == Init, status: UsbCmdStatus == InitReq, len: u32le == 0, } #[derive(Parse)] struct UsbInitResponse { id: UsbCmdId == Init, status: UsbCmdStatus == InitReqAck, len: u32le == 0, // inferred } #[derive(New)] struct UsbFirmwareDownloadRequest { id: UsbCmdId == FirmwareDownload, status: UsbCmdStatus == Req, len: u32le, fw_version: [char; 16], } #[derive(Parse)] struct UsbFirmwareDownloadResponse { id: UsbCmdId == FirmwareDownload, status: UsbCmdStatus == Ok, len: u32le, } #[derive(New)] struct UsbReadVersionRequest { id: UsbCmdId == ReadVersion, status: UsbCmdStatus == Req, len: u32le == 0, } #[repr(u32le)] enum UsbImageState { New = 0x0, Valid = 0x1, Invalid = 0x2, } #[derive(Parse)] struct UsbReadVersionResponse { fw_version: [char; 16], img_state: UsbImageState, } #[derive(Parse)] struct UsbProgressResponse { completed: u32le, } fwupd-1.9.16/plugins/logitech-rallysystem/logitech-rallysystem.quirk000066400000000000000000000004541460375044200260530ustar00rootroot00000000000000# Logitech Rally System Audio, resides in TVHub [HIDRAW\VEN_046D&DEV_0885] Plugin = logitech_rallysystem GType = FuLogitechRallysystemAudioDevice # Logitech Rally System TableHub [USB\VID_046D&PID_088F] Plugin = logitech_rallysystem GType = FuLogitechRallysystemTablehubDevice Flags = signed-paylod fwupd-1.9.16/plugins/logitech-rallysystem/meson.build000066400000000000000000000011241460375044200227470ustar00rootroot00000000000000if gudev.found() and gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginLogitechRallysystem"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('logitech-rallysystem.quirk') plugin_builtins += static_library('fu_plugin_logitech_rallysystem', rustgen.process('fu-logitech-rallysystem.rs'), sources: [ 'fu-logitech-rallysystem-plugin.c', 'fu-logitech-rallysystem-audio-device.c', 'fu-logitech-rallysystem-tablehub-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/logitech-scribe/000077500000000000000000000000001460375044200174665ustar00rootroot00000000000000fwupd-1.9.16/plugins/logitech-scribe/README.md000066400000000000000000000024011460375044200207420ustar00rootroot00000000000000--- title: Plugin: Logitech Scribe --- ## Introduction This plugin can upgrade the firmware on Logitech Video Collaboration products (Scribe), using USB bulk transfer. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `com.logitech.vc.scribe` ## GUID Generation These devices use the standard UDEV DeviceInstanceId values, e.g. * `VIDEO4LINUX\VEN_046D&DEV_08E2` ## Quirk Use This plugin uses the following plugin-specific quirks: ## Update Behavior The peripheral firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Design Notes ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x046D` ## External Interface Access This plugin requires read access to `/dev/bus/usb`, `/dev/video0`. This plugin requires the `UVCIOC_CTRL_QUERY` ioctl interface. ## Version Considerations This plugin has been available since fwupd version `1.8.8`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Sanjay Sheth: @vcdmp fwupd-1.9.16/plugins/logitech-scribe/fu-logitech-scribe-device.c000066400000000000000000000456401460375044200245530ustar00rootroot00000000000000/* * Copyright (c) 1999-2022 Logitech, Inc. * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #ifdef HAVE_IOCTL_H #include #endif #include #include "fu-logitech-scribe-device.h" /* UPD interface follows TLV (Type, Length, Value) protocol */ /* Payload size limited to 8k for UPD interfaces */ #define UPD_PACKET_HEADER_SIZE (2 * sizeof(guint32)) #define HASH_TIMEOUT 1500 #define MAX_DATA_SIZE 8192 /* 8k */ #define PAYLOAD_SIZE MAX_DATA_SIZE - UPD_PACKET_HEADER_SIZE #define UPD_INTERFACE_SUBPROTOCOL_ID 101 #define BULK_TRANSFER_TIMEOUT 1000 #define HASH_VALUE_SIZE 16 #define LENGTH_OFFSET 0x4 #define COMMAND_OFFSET 0x0 #define MAX_RETRIES 5 #define MAX_HANDSHAKE_RETRIES 3 #define MAX_WAIT_COUNT 150 #define SESSION_TIMEOUT 1000 #define FU_LOGITECH_SCRIBE_CHECKSUM_KIND_MD5 2 #define FU_LOGITECH_SCRIBE_VERSION_SIZE 1024 /* max size of version data returned */ #define FU_LOGITECH_SCRIBE_PROTOCOL_ID 0x1 enum { EP_OUT, EP_IN, EP_LAST }; enum { BULK_INTERFACE_UPD }; typedef enum { CMD_CHECK_BUFFERSIZE = 0xCC00, CMD_INIT = 0xCC01, CMD_START_TRANSFER = 0xCC02, CMD_DATA_TRANSFER = 0xCC03, CMD_END_TRANSFER = 0xCC04, CMD_UNINIT = 0xCC05, CMD_BUFFER_READ = 0xCC06, CMD_BUFFER_WRITE = 0xCC07, CMD_UNINIT_BUFFER = 0xCC08, CMD_ACK = 0xFF01, CMD_TIMEOUT = 0xFF02, CMD_NACK = 0xFF03 } UsbCommands; #define FU_LOGITECH_SCRIBE_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ /* 2 byte for get len query */ #define kDefaultUvcGetLenQueryControlSize 2 const guchar kLogiCameraVersionSelector = 1; const guchar kLogiUvcXuDevInfoCsEepromVersion = 3; const guint kLogiVideoImageVersionMaxSize = 32; const guchar kLogiVideoAitInitiateSetMMPData = 1; const guchar kLogiVideoAitFinalizeSetMMPData = 1; const guchar kLogiUnitIdAccessMmp = 6; const guchar kLogiUvcXuAitCustomCsSetMmp = 4; const guchar kLogiUvcXuAitCustomCsGetMmpResult = 5; const guchar kLogiUnitIdPeripheralControl = 11; const guchar kLogiUnitIdCameraVersion = 8; const guchar kLogiAitSetMmpCmdFwBurning = 1; struct _FuLogitechScribeDevice { FuUdevDevice parent_instance; guint update_ep[EP_LAST]; guint update_iface; }; G_DEFINE_TYPE(FuLogitechScribeDevice, fu_logitech_scribe_device, FU_TYPE_UDEV_DEVICE) static gboolean fu_logitech_scribe_device_send(FuLogitechScribeDevice *self, FuUsbDevice *usb_device, GByteArray *buf, gint interface_id, GError **error) { gsize transferred = 0; gint ep; GCancellable *cancellable = NULL; g_return_val_if_fail(buf != NULL, FALSE); if (interface_id == BULK_INTERFACE_UPD) { ep = self->update_ep[EP_OUT]; } else { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "interface is invalid"); return FALSE; } if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(usb_device)), ep, (guint8 *)buf->data, buf->len, &transferred, BULK_TRANSFER_TIMEOUT, cancellable, error)) { g_prefix_error(error, "failed to send using bulk transfer: "); return FALSE; } return TRUE; } static gboolean fu_logitech_scribe_device_recv(FuLogitechScribeDevice *self, FuUsbDevice *usb_device, GByteArray *buf, gint interface_id, guint timeout, GError **error) { gsize received_length = 0; gint ep; g_return_val_if_fail(buf != NULL, FALSE); if (interface_id == BULK_INTERFACE_UPD) { ep = self->update_ep[EP_IN]; } else { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "interface is invalid"); return FALSE; } if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(usb_device)), ep, buf->data, buf->len, &received_length, timeout, NULL, error)) { g_prefix_error(error, "failed to receive using bulk transfer: "); return FALSE; } return TRUE; } static gboolean fu_logitech_scribe_device_send_upd_cmd(FuLogitechScribeDevice *self, FuUsbDevice *usb_device, guint32 cmd, GByteArray *buf, GError **error) { guint32 cmd_tmp = 0x0; guint timeout = BULK_TRANSFER_TIMEOUT; g_autoptr(GByteArray) buf_pkt = g_byte_array_new(); g_autoptr(GByteArray) buf_ack = g_byte_array_new(); fu_byte_array_append_uint32(buf_pkt, cmd, G_LITTLE_ENDIAN); /* Type(T) : Command type */ fu_byte_array_append_uint32(buf_pkt, buf != NULL ? buf->len : 0, G_LITTLE_ENDIAN); /*Length(L) : Length of payload */ if (buf != NULL) { g_byte_array_append(buf_pkt, buf->data, buf->len); /* Value(V) : Actual payload data */ } if (!fu_logitech_scribe_device_send(self, usb_device, buf_pkt, BULK_INTERFACE_UPD, error)) return FALSE; /* receiving INIT ACK */ fu_byte_array_set_size(buf_ack, MAX_DATA_SIZE, 0x00); /* extending the bulk transfer timeout value, as android device takes some time to calculate Hash and respond */ if (CMD_END_TRANSFER == cmd) timeout = HASH_TIMEOUT; if (!fu_logitech_scribe_device_recv(self, usb_device, buf_ack, BULK_INTERFACE_UPD, timeout, error)) return FALSE; if (!fu_memread_uint32_safe(buf_ack->data, buf_ack->len, COMMAND_OFFSET, &cmd_tmp, G_LITTLE_ENDIAN, error)) return FALSE; if (cmd_tmp != CMD_ACK) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "not CMD_ACK, got %x", cmd); return FALSE; } if (!fu_memread_uint32_safe(buf_ack->data, buf_ack->len, UPD_PACKET_HEADER_SIZE, &cmd_tmp, G_LITTLE_ENDIAN, error)) return FALSE; if (cmd_tmp != cmd) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid upd message received, expected %x, got %x", cmd, cmd_tmp); return FALSE; } return TRUE; } static gchar * fu_logitech_scribe_device_compute_hash(GBytes *data) { guint8 md5buf[HASH_VALUE_SIZE] = {0}; gsize data_len = sizeof(md5buf); GChecksum *checksum = g_checksum_new(G_CHECKSUM_MD5); g_checksum_update(checksum, g_bytes_get_data(data, NULL), g_bytes_get_size(data)); g_checksum_get_digest(checksum, (guint8 *)&md5buf, &data_len); return g_base64_encode(md5buf, sizeof(md5buf)); } static gboolean fu_logitech_scribe_device_query_data_size(FuLogitechScribeDevice *self, guchar unit_id, guchar control_selector, guint16 *data_size, GError **error) { guint8 size_data[kDefaultUvcGetLenQueryControlSize] = {0x0}; struct uvc_xu_control_query size_query; size_query.unit = unit_id; size_query.selector = control_selector; size_query.query = UVC_GET_LEN; size_query.size = kDefaultUvcGetLenQueryControlSize; size_query.data = size_data; g_debug("data size query request, unit: 0x%x selector: 0x%x", (guchar)unit_id, (guchar)control_selector); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), UVCIOC_CTRL_QUERY, (guint8 *)&size_query, NULL, FU_LOGITECH_SCRIBE_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; /* convert the data byte to int */ *data_size = size_data[1] << 8 | size_data[0]; g_debug("data size query response, size: %u unit: 0x%x selector: 0x%x", *data_size, (guchar)unit_id, (guchar)control_selector); fu_dump_raw(G_LOG_DOMAIN, "UVC_GET_LEN", size_data, kDefaultUvcGetLenQueryControlSize); /* success */ return TRUE; } static gboolean fu_logitech_scribe_device_get_xu_control(FuLogitechScribeDevice *self, guchar unit_id, guchar control_selector, guint16 data_size, guchar *data, GError **error) { struct uvc_xu_control_query control_query; g_debug("get xu control request, size: %" G_GUINT16_FORMAT " unit: 0x%x selector: 0x%x", data_size, (guchar)unit_id, (guchar)control_selector); control_query.unit = unit_id; control_query.selector = control_selector; control_query.query = UVC_GET_CUR; control_query.size = data_size; control_query.data = data; if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), UVCIOC_CTRL_QUERY, (guint8 *)&control_query, NULL, FU_LOGITECH_SCRIBE_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; g_debug("received get xu control response, size: %u unit: 0x%x selector: 0x%x", data_size, (guchar)unit_id, (guchar)control_selector); fu_dump_raw(G_LOG_DOMAIN, "UVC_GET_CUR", data, data_size); /* success */ return TRUE; } static void fu_logitech_scribe_device_to_string(FuDevice *device, guint idt, GString *str) { FuLogitechScribeDevice *self = FU_LOGITECH_SCRIBE_DEVICE(device); fu_string_append_kx(str, idt, "UpdateIface", self->update_iface); fu_string_append_kx(str, idt, "UpdateEpOut", self->update_ep[EP_OUT]); fu_string_append_kx(str, idt, "UpdateEpIn", self->update_ep[EP_IN]); } static gboolean fu_logitech_scribe_device_probe(FuDevice *device, GError **error) { const gchar *id_v4l_capabilities; const gchar *index; GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); /* check is valid */ if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "video4linux") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct subsystem=%s, expected video4linux", fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device))); return FALSE; } /* only interested in video capture device */ id_v4l_capabilities = g_udev_device_get_property(udev_device, "ID_V4L_CAPABILITIES"); if (g_strcmp0(id_v4l_capabilities, ":capture:") != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "only video capture device are supported"); return FALSE; } /* interested in lowest index only e,g, video0, ignore low format siblings like * video1/video2/video3 etc */ index = g_udev_device_get_sysfs_attr(udev_device, "index"); if (g_strcmp0(index, "0") != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "only device with lower index supported"); return FALSE; }; /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "video4linux", error); } static gboolean fu_logitech_scribe_device_send_upd_init_cmd_cb(FuDevice *device, gpointer user_data, GError **error) { FuLogitechScribeDevice *self = FU_LOGITECH_SCRIBE_DEVICE(device); return fu_logitech_scribe_device_send_upd_cmd(self, user_data, CMD_INIT, NULL, error); } static gboolean fu_logitech_scribe_device_write_fw(FuLogitechScribeDevice *self, FuUsbDevice *usb_device, GBytes *fw, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x0, PAYLOAD_SIZE); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GByteArray) data_pkt = g_byte_array_new(); g_byte_array_append(data_pkt, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); if (!fu_logitech_scribe_device_send_upd_cmd(self, usb_device, CMD_DATA_TRANSFER, data_pkt, error)) { g_prefix_error(error, "failed to send data packet 0x%x: ", i); return FALSE; } fu_progress_step_done(progress); } return TRUE; } static gboolean fu_logitech_scribe_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuLogitechScribeDevice *self = FU_LOGITECH_SCRIBE_DEVICE(device); g_autofree gchar *base64hash = NULL; g_autoptr(GByteArray) end_pkt = g_byte_array_new(); g_autoptr(GByteArray) start_pkt = g_byte_array_new(); g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GUsbInterface) intf = NULL; g_autoptr(GPtrArray) endpoints = NULL; g_autoptr(GUsbDevice) g_usb_device = NULL; g_autoptr(FuUsbDevice) usb_device = NULL; /* convert GUdevDevice to GUsbDevice */ g_usb_device = fu_udev_device_find_usb_device(FU_UDEV_DEVICE(device), error); if (g_usb_device == NULL) { return FALSE; } usb_device = fu_usb_device_new(fu_device_get_context(device), g_usb_device); if (usb_device == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to create usb device instance"); return FALSE; } /* re-open with new device set */ fu_usb_device_set_dev(usb_device, g_usb_device); if (!fu_device_open(FU_DEVICE(usb_device), error)) return FALSE; /* find the correct interface */ intf = g_usb_device_get_interface(fu_usb_device_get_dev(FU_USB_DEVICE(usb_device)), G_USB_DEVICE_CLASS_VENDOR_SPECIFIC, UPD_INTERFACE_SUBPROTOCOL_ID, FU_LOGITECH_SCRIBE_PROTOCOL_ID, error); if (intf == NULL) return FALSE; endpoints = g_usb_interface_get_endpoints(intf); if (endpoints == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to get usb device endpoints"); return FALSE; } self->update_iface = g_usb_interface_get_number(intf); for (guint j = 0; j < endpoints->len; j++) { GUsbEndpoint *ep = g_ptr_array_index(endpoints, j); if (j == EP_OUT) self->update_ep[EP_OUT] = g_usb_endpoint_get_address(ep); else self->update_ep[EP_IN] = g_usb_endpoint_get_address(ep); } fu_usb_device_add_interface(usb_device, self->update_iface); g_debug("usb data, iface: %u ep_out: %u ep_in: %u", self->update_iface, self->update_ep[EP_OUT], self->update_ep[EP_IN]); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "init"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "start-transfer"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "device-write-blocks"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "end-transfer"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "uninit"); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* sending INIT. Retry if device is not in IDLE state to receive the file */ if (!fu_device_retry(device, fu_logitech_scribe_device_send_upd_init_cmd_cb, MAX_RETRIES, usb_device, error)) { g_prefix_error(error, "failed to write init transfer packet: please reboot the device: "); return FALSE; } fu_progress_step_done(progress); /* start transfer */ fu_byte_array_append_uint64(start_pkt, g_bytes_get_size(fw), G_LITTLE_ENDIAN); if (!fu_logitech_scribe_device_send_upd_cmd(self, usb_device, CMD_START_TRANSFER, start_pkt, error)) { g_prefix_error(error, "failed to write start transfer packet: "); return FALSE; } fu_progress_step_done(progress); /* push each block to device */ if (!fu_logitech_scribe_device_write_fw(self, usb_device, fw, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* end transfer */ base64hash = fu_logitech_scribe_device_compute_hash(fw); fu_byte_array_append_uint32(end_pkt, 1, G_LITTLE_ENDIAN); /* update */ fu_byte_array_append_uint32(end_pkt, 0, G_LITTLE_ENDIAN); /* force */ fu_byte_array_append_uint32(end_pkt, FU_LOGITECH_SCRIBE_CHECKSUM_KIND_MD5, G_LITTLE_ENDIAN); /* checksum type */ g_byte_array_append(end_pkt, (const guint8 *)base64hash, strlen(base64hash)); if (!fu_logitech_scribe_device_send_upd_cmd(self, usb_device, CMD_END_TRANSFER, end_pkt, error)) { g_prefix_error(error, "failed to write end transfer packet: "); return FALSE; } fu_progress_step_done(progress); /* uninitialize */ /* no need to wait for ACK message, perhaps device reboot already in progress, ignore */ if (!fu_logitech_scribe_device_send_upd_cmd(self, usb_device, CMD_UNINIT, NULL, &error_local)) { g_debug( "failed to receive acknowledgment for uninitialize request, ignoring it: %s", error_local->message); } fu_progress_step_done(progress); /* * image file pushed. Device validates and uploads new image on inactive partition. Reboots * wait for RemoveDelay duration */ /* success! */ return TRUE; } static gboolean fu_logitech_scribe_device_set_version(FuDevice *device, GError **error) { FuLogitechScribeDevice *self = FU_LOGITECH_SCRIBE_DEVICE(device); guint32 fwversion = 0; guint16 data_len = 0; g_autofree guint8 *query_data = NULL; /* query current device version */ if (!fu_logitech_scribe_device_query_data_size(self, kLogiUnitIdCameraVersion, kLogiCameraVersionSelector, &data_len, error)) return FALSE; if (data_len > FU_LOGITECH_SCRIBE_VERSION_SIZE) { g_prefix_error(error, "version packet was too large at 0x%x bytes: ", data_len); return FALSE; } query_data = g_malloc0(data_len); if (!fu_logitech_scribe_device_get_xu_control(self, kLogiUnitIdCameraVersion, kLogiCameraVersionSelector, data_len, (guchar *)query_data, error)) return FALSE; /* little-endian data. MinorVersion byte 0, MajorVersion byte 1, BuildVersion byte 3 & 2 */ fwversion = (query_data[1] << 24) + (query_data[0] << 16) + (query_data[3] << 8) + query_data[2]; fu_device_set_version_u32(device, fwversion); /* success */ return TRUE; } static gboolean fu_logitech_scribe_device_setup(FuDevice *device, GError **error) { return fu_logitech_scribe_device_set_version(device, error); } static void fu_logitech_scribe_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_logitech_scribe_device_init(FuLogitechScribeDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.logitech.vc.scribe"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_retry_set_delay(FU_DEVICE(self), 1000); } static void fu_logitech_scribe_device_finalize(GObject *object) { G_OBJECT_CLASS(fu_logitech_scribe_device_parent_class)->finalize(object); } static void fu_logitech_scribe_device_class_init(FuLogitechScribeDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_logitech_scribe_device_finalize; klass_device->to_string = fu_logitech_scribe_device_to_string; klass_device->write_firmware = fu_logitech_scribe_device_write_firmware; klass_device->probe = fu_logitech_scribe_device_probe; klass_device->setup = fu_logitech_scribe_device_setup; klass_device->set_progress = fu_logitech_scribe_device_set_progress; } fwupd-1.9.16/plugins/logitech-scribe/fu-logitech-scribe-device.h000066400000000000000000000005601460375044200245500ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_LOGITECH_SCRIBE_DEVICE (fu_logitech_scribe_device_get_type()) G_DECLARE_FINAL_TYPE(FuLogitechScribeDevice, fu_logitech_scribe_device, FU, LOGITECH_SCRIBE_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/logitech-scribe/fu-logitech-scribe-plugin.c000066400000000000000000000015271460375044200246060ustar00rootroot00000000000000/* * Copyright (c) 1999-2022 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-logitech-scribe-device.h" #include "fu-logitech-scribe-plugin.h" struct _FuLogitechScribePlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuLogitechScribePlugin, fu_logitech_scribe_plugin, FU_TYPE_PLUGIN) static void fu_logitech_scribe_plugin_init(FuLogitechScribePlugin *self) { } static void fu_logitech_scribe_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "video4linux"); fu_plugin_add_device_gtype(plugin, FU_TYPE_LOGITECH_SCRIBE_DEVICE); } static void fu_logitech_scribe_plugin_class_init(FuLogitechScribePluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_logitech_scribe_plugin_constructed; } fwupd-1.9.16/plugins/logitech-scribe/fu-logitech-scribe-plugin.h000066400000000000000000000004361460375044200246110ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuLogitechScribePlugin, fu_logitech_scribe_plugin, FU, LOGITECH_SCRIBE_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/logitech-scribe/logitech-scribe.quirk000066400000000000000000000001441460375044200236050ustar00rootroot00000000000000[VIDEO4LINUX\VEN_046D&DEV_08E2] Plugin = logitech_scribe InstallDuration = 120 RemoveDelay = 120000 fwupd-1.9.16/plugins/logitech-scribe/meson.build000066400000000000000000000012061460375044200216270ustar00rootroot00000000000000if get_option('plugin_logitech_scribe').require(gusb.found() and gudev.found(), error_message: 'usb and udev are needed for plugin_logitech_scribe').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginLogitechScribe"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('logitech-scribe.quirk') plugin_builtins += static_library('fu_plugin_logitech_scribe', sources: [ 'fu-logitech-scribe-device.c', 'fu-logitech-scribe-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/logitech-tap/000077500000000000000000000000001460375044200170035ustar00rootroot00000000000000fwupd-1.9.16/plugins/logitech-tap/README.md000066400000000000000000000025231460375044200202640ustar00rootroot00000000000000--- title: Plugin: Logitech Tap — Video Collaboration --- ## Introduction This plugin can upgrade the firmware on Logitech Video Collaboration products (Tap), using ioctl. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `com.logitech.hardware.tap` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `HIDRAW\VEN_046D&DEV_0872` * `VIDEO4LINUX\VEN_046D&DEV_0876` ## Quirk Use This plugin uses the following plugin-specific quirks: ## Update Behavior The peripheral firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Design Notes ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x046D` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. This plugin requires the ioctl interfaces: `UVCIOC_CTRL_QUERY`, `HIDIOCGFEATURE`, `HIDIOCSFEATURE`, `HIDIOCGINPUT`. ## Version Considerations This plugin has been available since fwupd version `1.9.2`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Sanjay Sheth: @vcdmp fwupd-1.9.16/plugins/logitech-tap/fu-logitech-tap-device.c000066400000000000000000000031701460375044200233750ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-logitech-tap-device.h" G_DEFINE_TYPE(FuLogitechTapDevice, fu_logitech_tap_device, FU_TYPE_UDEV_DEVICE) static void fu_logitech_tap_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_logitech_tap_device_init(FuLogitechTapDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.logitech.hardware.tap"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_register_private_flag(FU_DEVICE(self), FU_LOGITECH_TAP_HDMI_DEVICE_FLAG_NEEDS_REBOOT, "needs-reboot"); } static void fu_logitech_tap_device_finalize(GObject *object) { G_OBJECT_CLASS(fu_logitech_tap_device_parent_class)->finalize(object); } static void fu_logitech_tap_device_class_init(FuLogitechTapDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_logitech_tap_device_finalize; klass_device->set_progress = fu_logitech_tap_device_set_progress; } fwupd-1.9.16/plugins/logitech-tap/fu-logitech-tap-device.h000066400000000000000000000011301460375044200233740ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_LOGITECH_TAP_DEVICE (fu_logitech_tap_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuLogitechTapDevice, fu_logitech_tap_device, FU, LOGITECH_TAP_DEVICE, FuUdevDevice) struct _FuLogitechTapDeviceClass { FuUdevDeviceClass parent_class; }; /** * FU_LOGITECH_TAP_HDMI_DEVICE_FLAG_NEEDS_REBOOT: * * Firmware updated for HDMI component, trigger composite device reboot */ #define FU_LOGITECH_TAP_HDMI_DEVICE_FLAG_NEEDS_REBOOT (1ull << 1) fwupd-1.9.16/plugins/logitech-tap/fu-logitech-tap-hdmi-device.c000066400000000000000000000354441460375044200243250ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #ifdef HAVE_IOCTL_H #include #endif #include #include "fu-logitech-tap-hdmi-device.h" #define FU_LOGITECH_TAP_HDMI_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ #define XU_INPUT_DATA_LEN 8 /* 2 byte for get len query */ #define kDefaultUvcGetLenQueryControlSize 2 const guint8 kLogiTapCameraVersionSelector = 1; const guint8 kLogiTapUvcXuAitCustomCsGetMmpResult = 5; const guint8 kLogiTapHdmiVerSetData = 0x0B; const guint8 kLogiUnitIdVidCapExtension = 0x06; const guint8 kLogiHdmiVerGetSelector = 2; const guint8 kLogiTapAitSetMmpCmdFwBurning = 0x01; const guint8 kLogiTapVideoAitInitiateSetMMPData = 1; const guint kLogiDefaultImageBlockSize = 32; const guint8 kLogiUvcXuAitCustomCsSetFwData = 0x03; const guint8 kLogiTapUvcXuAitCustomCsSetMmp = 4; const guint kLogiDefaultAitSleepIntervalMs = 1000; /* when finalize Ait, max polling duration is 120sec */ const guint kLogiDefaultAitFinalizeMaxPollingDurationMs = 120000; const guint8 kLogiDefaultAitSuccessValue = 0x00; const guint8 kLogiDefaultAitFailureValue = 0x82; struct _FuLogitechTapHdmiDevice { FuLogitechTapDevice parent_instance; }; G_DEFINE_TYPE(FuLogitechTapHdmiDevice, fu_logitech_tap_hdmi_device, FU_TYPE_LOGITECH_TAP_DEVICE) static gboolean fu_logitech_tap_hdmi_device_query_data_size(FuLogitechTapHdmiDevice *self, guint8 unit_id, guint8 control_selector, guint16 *data_size, GError **error) { guint8 size_data[kDefaultUvcGetLenQueryControlSize] = {0x0}; struct uvc_xu_control_query size_query = {.unit = unit_id, .selector = control_selector, .query = UVC_GET_LEN, .size = kDefaultUvcGetLenQueryControlSize, .data = size_data}; g_debug("data size query request, unit: 0x%x selector: 0x%x", unit_id, control_selector); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), UVCIOC_CTRL_QUERY, (guint8 *)&size_query, NULL, FU_LOGITECH_TAP_HDMI_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; /* convert the data byte to int */ if (!fu_memread_uint16_safe(size_data, sizeof(size_data), 0x0, data_size, G_LITTLE_ENDIAN, error)) return FALSE; g_debug("data size query response, size: %u unit: 0x%x selector: 0x%x", *data_size, unit_id, control_selector); fu_dump_raw(G_LOG_DOMAIN, "UVC_GET_LENRes", size_query.data, kDefaultUvcGetLenQueryControlSize); /* success */ return TRUE; } static gboolean fu_logitech_tap_hdmi_device_get_xu_control(FuLogitechTapHdmiDevice *self, guint8 unit_id, guint8 control_selector, guint16 data_size, guint8 *data, GError **error) { struct uvc_xu_control_query control_query = {.unit = unit_id, .selector = control_selector, .query = UVC_GET_CUR, .size = data_size, .data = data}; g_debug("get xu control request, size: %" G_GUINT16_FORMAT " unit: 0x%x selector: 0x%x", data_size, unit_id, control_selector); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), UVCIOC_CTRL_QUERY, (guint8 *)&control_query, NULL, FU_LOGITECH_TAP_HDMI_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; g_debug("received get xu control response, size: %u unit: 0x%x selector: 0x%x", control_query.size, unit_id, control_selector); fu_dump_raw(G_LOG_DOMAIN, "UVC_GET_CURRes", control_query.data, control_query.size); /* success */ return TRUE; } static gboolean fu_logitech_tap_hdmi_device_set_xu_control(FuLogitechTapHdmiDevice *self, guint8 unit_id, guint8 control_selector, guint16 data_size, guint8 *data, GError **error) { struct uvc_xu_control_query control_query = {.unit = unit_id, .selector = control_selector, .query = UVC_SET_CUR, .size = data_size, .data = data}; if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), UVCIOC_CTRL_QUERY, (guint8 *)&control_query, NULL, FU_LOGITECH_TAP_HDMI_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; g_debug("received set xu control response, size: %u unit: 0x%x selector: 0x%x", data_size, unit_id, control_selector); /* success */ return TRUE; } static gboolean fu_logitech_tap_hdmi_device_ait_initiate_update(FuLogitechTapHdmiDevice *self, GError **error) { guint16 data_len = 0; g_autofree guint8 *mmp_get_data = NULL; guint8 ait_initiate_update[XU_INPUT_DATA_LEN] = {kLogiTapAitSetMmpCmdFwBurning, 0, 0, kLogiTapVideoAitInitiateSetMMPData, 0, 0, 0, 0}; if (!fu_logitech_tap_hdmi_device_set_xu_control(self, kLogiUnitIdVidCapExtension, kLogiTapUvcXuAitCustomCsSetMmp, XU_INPUT_DATA_LEN, (guint8 *)&ait_initiate_update, error)) return FALSE; if (!fu_logitech_tap_hdmi_device_query_data_size(self, kLogiUnitIdVidCapExtension, kLogiTapUvcXuAitCustomCsGetMmpResult, &data_len, error)) return FALSE; if (data_len > XU_INPUT_DATA_LEN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "initiate query packet was too large at 0x%x bytes: ", data_len); return FALSE; } mmp_get_data = g_malloc0(data_len); if (!fu_logitech_tap_hdmi_device_get_xu_control(self, kLogiUnitIdVidCapExtension, kLogiTapUvcXuAitCustomCsGetMmpResult, data_len, (guint8 *)mmp_get_data, error)) return FALSE; if (mmp_get_data[0] != kLogiDefaultAitSuccessValue) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to initialize AIT update, invalid result data: 0x%x", mmp_get_data[0]); return FALSE; } /* success */ return TRUE; } static gboolean fu_logitech_tap_hdmi_device_ait_finalize_update(FuLogitechTapHdmiDevice *self, GError **error) { guint duration_ms = 0; guint8 ait_finalize_update[XU_INPUT_DATA_LEN] = {kLogiTapAitSetMmpCmdFwBurning, kLogiTapVideoAitInitiateSetMMPData, 0, 0, 0, 0, 0, 0}; fu_device_sleep(FU_DEVICE(self), 4 * kLogiDefaultAitSleepIntervalMs); /* 4 sec */ if (!fu_logitech_tap_hdmi_device_set_xu_control(self, kLogiUnitIdVidCapExtension, kLogiTapUvcXuAitCustomCsSetMmp, XU_INPUT_DATA_LEN, (guint8 *)&ait_finalize_update, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), kLogiDefaultAitSleepIntervalMs); /* 1 sec */ /* poll for burning fw result or return failure if it hits max polling */ for (int pass = 0;; pass++) { g_autofree guint8 *mmp_get_data = NULL; guint16 data_len = 0; fu_device_sleep(FU_DEVICE(self), kLogiDefaultAitSleepIntervalMs); /* 1 sec */ duration_ms = duration_ms + kLogiDefaultAitSleepIntervalMs; if (!fu_logitech_tap_hdmi_device_query_data_size( self, kLogiUnitIdVidCapExtension, kLogiTapUvcXuAitCustomCsGetMmpResult, &data_len, error)) return FALSE; mmp_get_data = g_malloc0(data_len); if (!fu_logitech_tap_hdmi_device_get_xu_control( self, kLogiUnitIdVidCapExtension, kLogiTapUvcXuAitCustomCsGetMmpResult, data_len, (guint8 *)mmp_get_data, error)) return FALSE; if (mmp_get_data[0] == kLogiDefaultAitSuccessValue) { if (pass == 0) g_usleep(8 * G_USEC_PER_SEC); break; } else if (mmp_get_data[0] == kLogiDefaultAitFailureValue) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to finalize image burning, invalid result data: 0x%x", mmp_get_data[0]); return FALSE; } if (duration_ms > kLogiDefaultAitFinalizeMaxPollingDurationMs) { /* if device never returns 0x82 or 0x00, bail out */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to finalize image burning, duration_ms: %u", duration_ms); return FALSE; } } /* success */ return TRUE; } static gboolean fu_logitech_tap_hdmi_device_write_fw(FuLogitechTapHdmiDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { /* init */ if (!fu_logitech_tap_hdmi_device_ait_initiate_update(self, error)) return FALSE; /* write */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); /* if needed, pad the last block to kLogiDefaultImageBlockSize size, * so that device always gets each block of kLogiDefaultImageBlockSize */ g_autofree guint8 *data_pkt = g_malloc0(kLogiDefaultImageBlockSize); if (!fu_memcpy_safe(data_pkt, kLogiDefaultImageBlockSize, 0x0, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, fu_chunk_get_data_sz(chk), error)) return FALSE; if (!fu_logitech_tap_hdmi_device_set_xu_control(self, kLogiUnitIdVidCapExtension, kLogiUvcXuAitCustomCsSetFwData, kLogiDefaultImageBlockSize, (guint8 *)data_pkt, error)) return FALSE; fu_progress_step_done(progress); } /* uninit */ if (!fu_logitech_tap_hdmi_device_ait_finalize_update(self, error)) return FALSE; /* signal for sensor device to trigger composite device reboot */ fu_device_add_private_flag(FU_DEVICE(self), FU_LOGITECH_TAP_HDMI_DEVICE_FLAG_NEEDS_REBOOT); return TRUE; } static gboolean fu_logitech_tap_hdmi_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuLogitechTapHdmiDevice *self = FU_LOGITECH_TAP_HDMI_DEVICE(device); g_autofree gchar *old_firmware_version = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* for troubleshooting purpose */ old_firmware_version = g_strdup(fu_device_get_version(device)); g_debug("update %s firmware", old_firmware_version); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); /* get image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* write */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); chunks = fu_chunk_array_new_from_bytes(fw, 0x0, kLogiDefaultImageBlockSize); if (!fu_logitech_tap_hdmi_device_write_fw(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_logitech_tap_hdmi_device_set_version(FuLogitechTapHdmiDevice *self, GError **error) { guint16 bufsz = 0; guint8 set_data[XU_INPUT_DATA_LEN] = {kLogiTapHdmiVerSetData, 0, 0, 0, 0, 0, 0, 0}; guint16 build = 0; guint16 minor = 0; guint16 major = 0; g_autofree guint8 *buf = NULL; if (!fu_logitech_tap_hdmi_device_set_xu_control(self, kLogiUnitIdVidCapExtension, kLogiTapCameraVersionSelector, XU_INPUT_DATA_LEN, (guint8 *)set_data, error)) return FALSE; /* query current device version */ if (!fu_logitech_tap_hdmi_device_query_data_size(self, kLogiUnitIdVidCapExtension, kLogiHdmiVerGetSelector, &bufsz, error)) return FALSE; if (bufsz > XU_INPUT_DATA_LEN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "version query packet was too large at 0x%x bytes: ", bufsz); return FALSE; } buf = g_malloc0(bufsz); if (!fu_logitech_tap_hdmi_device_get_xu_control(self, kLogiUnitIdVidCapExtension, kLogiHdmiVerGetSelector, bufsz, (guint8 *)buf, error)) return FALSE; /* MajorVersion bytes 3&2, MinorVersion bytes 5&4, BuildVersion bytes 7&6 */ if (!fu_memread_uint16_safe(buf, bufsz, 0x2, &major, G_BIG_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(buf, bufsz, 0x4, &minor, G_BIG_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(buf, bufsz, 0x6, &build, G_BIG_ENDIAN, error)) return FALSE; fu_device_set_version(FU_DEVICE(self), g_strdup_printf("%i.%i.%i", major, minor, build)); /* success */ return TRUE; } static gboolean fu_logitech_tap_hdmi_device_setup(FuDevice *device, GError **error) { FuLogitechTapHdmiDevice *self = FU_LOGITECH_TAP_HDMI_DEVICE(device); return fu_logitech_tap_hdmi_device_set_version(self, error); } static gboolean fu_logitech_tap_hdmi_device_probe(FuDevice *device, GError **error) { const gchar *id_v4l_capabilities; const gchar *index; GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_logitech_tap_hdmi_device_parent_class)->probe(device, error)) return FALSE; /* ignore unsupported subsystems */ if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "video4linux") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct subsystem=%s, expected video4linux", fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device))); return FALSE; } /* only interested in video capture device */ id_v4l_capabilities = g_udev_device_get_property(udev_device, "ID_V4L_CAPABILITIES"); if (g_strcmp0(id_v4l_capabilities, ":capture:") != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "only video capture device are supported"); return FALSE; } /* interested in lowest index only e,g, video0, ignore low format siblings like * video1/video2/video3 etc */ index = g_udev_device_get_sysfs_attr(udev_device, "index"); if (g_strcmp0(index, "0") != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "only device with lower index supported"); return FALSE; }; /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "video4linux", error); } static void fu_logitech_tap_hdmi_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_logitech_tap_hdmi_device_init(FuLogitechTapHdmiDevice *self) { fu_device_retry_set_delay(FU_DEVICE(self), 1000); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | FU_UDEV_DEVICE_FLAG_OPEN_NONBLOCK | FU_UDEV_DEVICE_FLAG_IOCTL_RETRY); } static void fu_logitech_tap_hdmi_device_class_init(FuLogitechTapHdmiDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_logitech_tap_hdmi_device_probe; klass_device->setup = fu_logitech_tap_hdmi_device_setup; klass_device->set_progress = fu_logitech_tap_hdmi_device_set_progress; klass_device->write_firmware = fu_logitech_tap_hdmi_device_write_firmware; } fwupd-1.9.16/plugins/logitech-tap/fu-logitech-tap-hdmi-device.h000066400000000000000000000005721460375044200243240ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-logitech-tap-device.h" #define FU_TYPE_LOGITECH_TAP_HDMI_DEVICE (fu_logitech_tap_hdmi_device_get_type()) G_DECLARE_FINAL_TYPE(FuLogitechTapHdmiDevice, fu_logitech_tap_hdmi_device, FU, LOGITECH_TAP_HDMI_DEVICE, FuLogitechTapDevice) fwupd-1.9.16/plugins/logitech-tap/fu-logitech-tap-plugin.c000066400000000000000000000060501460375044200234340ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-logitech-tap-hdmi-device.h" #include "fu-logitech-tap-plugin.h" #include "fu-logitech-tap-sensor-device.h" struct _FuLogitechTapPlugin { FuPlugin parent_instance; FuDevice *hdmi_device; /* ref */ FuDevice *sensor_device; /* ref */ }; G_DEFINE_TYPE(FuLogitechTapPlugin, fu_logitech_tap_plugin, FU_TYPE_PLUGIN) static gboolean fu_logitech_tap_plugin_composite_cleanup(FuPlugin *plugin, GPtrArray *devices, GError **error) { FuLogitechTapPlugin *self = FU_LOGITECH_TAP_PLUGIN(plugin); /* check if HDMI firmware successfully upgraded and signal for SENSOR to trigger composite * reboot is set */ for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index(devices, i); if ((g_strcmp0(fu_device_get_plugin(dev), "logitech_tap") == 0) && (FU_IS_LOGITECH_TAP_HDMI_DEVICE(dev)) && (fu_device_has_private_flag(dev, FU_LOGITECH_TAP_HDMI_DEVICE_FLAG_NEEDS_REBOOT)) && self->hdmi_device != NULL) { g_debug("device needs reboot"); if (!fu_logitech_tap_sensor_device_reboot_device( FU_LOGITECH_TAP_SENSOR_DEVICE(fu_device_get_proxy(dev)), error)) return FALSE; break; } } return TRUE; } static void fu_logitech_tap_plugin_init(FuLogitechTapPlugin *self) { } static void fu_logitech_tap_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "video4linux"); fu_plugin_add_udev_subsystem(plugin, "hidraw"); fu_plugin_add_device_gtype(plugin, FU_TYPE_LOGITECH_TAP_HDMI_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_LOGITECH_TAP_SENSOR_DEVICE); } static void fu_logitech_tap_plugin_device_registered(FuPlugin *plugin, FuDevice *device) { FuLogitechTapPlugin *self = FU_LOGITECH_TAP_PLUGIN(plugin); if (g_strcmp0(fu_device_get_plugin(device), "logitech_tap") != 0) return; if (FU_IS_LOGITECH_TAP_HDMI_DEVICE(device)) { g_set_object(&self->hdmi_device, device); if (self->sensor_device != NULL) fu_device_set_proxy(self->hdmi_device, self->sensor_device); } if (FU_IS_LOGITECH_TAP_SENSOR_DEVICE(device)) { g_set_object(&self->sensor_device, device); if (self->hdmi_device != NULL) fu_device_set_proxy(self->hdmi_device, self->sensor_device); } } static void fu_logitech_tap_finalize(GObject *obj) { FuLogitechTapPlugin *self = FU_LOGITECH_TAP_PLUGIN(obj); if (self->hdmi_device != NULL) g_object_unref(self->hdmi_device); if (self->sensor_device != NULL) g_object_unref(self->sensor_device); G_OBJECT_CLASS(fu_logitech_tap_plugin_parent_class)->finalize(obj); } static void fu_logitech_tap_plugin_class_init(FuLogitechTapPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_logitech_tap_finalize; plugin_class->constructed = fu_logitech_tap_plugin_constructed; plugin_class->device_registered = fu_logitech_tap_plugin_device_registered; plugin_class->composite_cleanup = fu_logitech_tap_plugin_composite_cleanup; } fwupd-1.9.16/plugins/logitech-tap/fu-logitech-tap-plugin.h000066400000000000000000000003501460375044200234360ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuLogitechTapPlugin, fu_logitech_tap_plugin, FU, LOGITECH_TAP_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/logitech-tap/fu-logitech-tap-sensor-device.c000066400000000000000000000251031460375044200247040ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #ifdef HAVE_IOCTL_H #include #include #endif #include #include "fu-logitech-tap-sensor-device.h" #define FU_LOGITECH_TAP_SENSOR_DEVICE_IOCTL_TIMEOUT 50000 /* ms */ #define HID_SET_DATA_LEN 5 #define HID_GET_DATA_LEN 5 #ifndef HIDIOCGINPUT #define HIDIOCGINPUT(len) _IOC(_IOC_READ, 'H', 0x0A, len) #endif /* device version */ const guint8 kHidReportIdAppSetCmd = 0x1b; const guint8 kHidReportIdAppGetCmd = 0x19; const guint8 kColossusAppCmdGetVer = 0x04; /* enable/disable TDE mode */ const guint8 kHidMcuTdeReportId = 0x1A; const guint8 kHidMcuTdeModeSelector = 0x02; const guint8 kHidMcuTdeModeEnable = 0x01; const guint8 kHidMcuTdeModeDisable = 0x00; /* serial number of the device */ const guint8 kHidMcuCmdSetSerialNumber = 0x1C; const guint8 kHidMcuCmdGetSerialNumber = 0x1D; const guint8 kHidMcuSerialNumberSetReportByte1 = 0x00; const guint8 kHidMcuSerialNumberSetReportByte2 = 0x70; const guint8 kHidMcuSerialNumberSetReportByte3 = 0x0E; const guint8 kHidMcuSerialNumberSetReportByte4 = 0x00; /* reboot device */ const guint8 kHidReportIdMcuSetCmd = 0x1a; const guint kLogiDefaultSensorSleepIntervalMs = 50; struct _FuLogitechTapSensorDevice { FuLogitechTapDevice parent_instance; }; G_DEFINE_TYPE(FuLogitechTapSensorDevice, fu_logitech_tap_sensor_device, FU_TYPE_LOGITECH_TAP_DEVICE) static gboolean fu_logitech_tap_sensor_device_set_feature(FuLogitechTapSensorDevice *self, const guint8 *data, guint datasz, GError **error) { #ifdef HAVE_HIDRAW_H fu_dump_raw(G_LOG_DOMAIN, "HidSetFeature", data, datasz); return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(datasz), (guint8 *)data, NULL, FU_LOGITECH_TAP_SENSOR_DEVICE_IOCTL_TIMEOUT, error); #else /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_logitech_tap_sensor_device_get_feature(FuLogitechTapSensorDevice *self, guint8 *data, guint datasz, GError **error) { #ifdef HAVE_HIDRAW_H g_autoptr(GError) error_local = NULL; fu_dump_raw(G_LOG_DOMAIN, "HidGetFeatureReq", data, datasz); /* try HIDIOCGINPUT request in case of failure */ if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGFEATURE(datasz), data, NULL, FU_LOGITECH_TAP_SENSOR_DEVICE_IOCTL_TIMEOUT, &error_local)) { g_debug("failed to send get request, retrying: %s", error_local->message); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGINPUT(datasz), data, NULL, FU_LOGITECH_TAP_SENSOR_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "HidGetFeatureRes", data, datasz); return TRUE; #else /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_logitech_tap_sensor_device_enable_tde(FuDevice *device, GError **error) { FuLogitechTapSensorDevice *self = FU_LOGITECH_TAP_SENSOR_DEVICE(device); guint8 set_data[HID_SET_DATA_LEN] = {kHidMcuTdeReportId, kHidMcuTdeModeSelector, kHidMcuTdeModeEnable, 0, 0}; return fu_logitech_tap_sensor_device_set_feature(self, (guint8 *)set_data, HID_SET_DATA_LEN, error); } static gboolean fu_logitech_tap_sensor_device_disable_tde(FuDevice *device, GError **error) { FuLogitechTapSensorDevice *self = FU_LOGITECH_TAP_SENSOR_DEVICE(device); guint8 set_data[HID_SET_DATA_LEN] = {kHidMcuTdeReportId, kHidMcuTdeModeSelector, kHidMcuTdeModeDisable, 0, 0}; return fu_logitech_tap_sensor_device_set_feature(self, (guint8 *)set_data, HID_SET_DATA_LEN, error); } gboolean fu_logitech_tap_sensor_device_reboot_device(FuLogitechTapSensorDevice *self, GError **error) { const guint8 pinclr = 05; const guint8 pinset = 06; const guint8 PWR = 45; const guint8 RST = 46; guint8 set_data[HID_SET_DATA_LEN] = {kHidReportIdMcuSetCmd, pinclr, PWR, 0, 0}; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 100, "attach"); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_RESTART); /* need to reopen the device, as at composite_cleanup time, device is already closed */ if (!fu_device_open(FU_DEVICE(self), error)) return FALSE; /* enable/disable TDE mode */ locker = fu_device_locker_new_full(FU_DEVICE(self), (FuDeviceLockerFunc)fu_logitech_tap_sensor_device_enable_tde, (FuDeviceLockerFunc)fu_logitech_tap_sensor_device_disable_tde, error); if (locker == NULL) return FALSE; /* setup HID report for power cycle */ if (!fu_logitech_tap_sensor_device_set_feature(self, (guint8 *)set_data, HID_SET_DATA_LEN, error)) return FALSE; set_data[1] = pinclr; set_data[2] = RST; if (!fu_logitech_tap_sensor_device_set_feature(self, (guint8 *)set_data, HID_SET_DATA_LEN, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), 2000); /* 2 sec */ set_data[1] = pinset; set_data[2] = PWR; if (!fu_logitech_tap_sensor_device_set_feature(self, (guint8 *)set_data, HID_SET_DATA_LEN, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), 2000); /* 2 sec */ set_data[1] = pinset; set_data[2] = RST; if (!fu_logitech_tap_sensor_device_set_feature(self, (guint8 *)set_data, HID_SET_DATA_LEN, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_logitech_tap_sensor_device_set_version(FuLogitechTapSensorDevice *self, GError **error) { guint32 version = 0; guint8 set_data[HID_SET_DATA_LEN] = {kHidReportIdAppSetCmd, kColossusAppCmdGetVer, 0, 0, 0}; guint8 get_data[HID_GET_DATA_LEN] = {kHidReportIdAppGetCmd, 0, 0, 0, 0}; /* setup HID report to query current device version */ if (!fu_logitech_tap_sensor_device_set_feature(self, (guint8 *)set_data, HID_SET_DATA_LEN, error)) return FALSE; if (!fu_logitech_tap_sensor_device_get_feature(self, (guint8 *)get_data, HID_GET_DATA_LEN, error)) return FALSE; /* MinorVersion byte 3, MajorVersion byte 4, BuildVersion byte 2 & 1 */ if (!fu_memread_uint32_safe(get_data, sizeof(get_data), 0x01, &version, G_LITTLE_ENDIAN, error)) return FALSE; fu_device_set_version_u32(FU_DEVICE(self), version); /* success */ return TRUE; } static gboolean fu_logitech_tap_sensor_device_set_serial(FuLogitechTapSensorDevice *self, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GString) serial_number = g_string_new(NULL); guint8 set_data[HID_SET_DATA_LEN] = {kHidMcuCmdSetSerialNumber, kHidMcuSerialNumberSetReportByte1, kHidMcuSerialNumberSetReportByte2, kHidMcuSerialNumberSetReportByte3, kHidMcuSerialNumberSetReportByte4}; /* enable/disable TDE mode */ locker = fu_device_locker_new_full(FU_DEVICE(self), (FuDeviceLockerFunc)fu_logitech_tap_sensor_device_enable_tde, (FuDeviceLockerFunc)fu_logitech_tap_sensor_device_disable_tde, error); if (locker == NULL) return FALSE; /* setup HID report for serial number */ if (!fu_logitech_tap_sensor_device_set_feature(self, set_data, HID_SET_DATA_LEN, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), kLogiDefaultSensorSleepIntervalMs); /* 50 ms */ /* serial number is a 12-byte-string that is stored in MCU */ /* each get request fetches 1 word (4 bytes), so iterate 3 times */ for (int index = 1; index <= 3; index++) { guint8 get_data[HID_GET_DATA_LEN] = {kHidMcuCmdGetSerialNumber, 0, 0, 0, 0}; if (!fu_logitech_tap_sensor_device_get_feature(self, (guint8 *)get_data, HID_GET_DATA_LEN, error)) return FALSE; g_string_append_printf(serial_number, "%c%c%c%c", get_data[1], get_data[2], get_data[3], get_data[4]); } fu_device_set_serial(FU_DEVICE(self), serial_number->str); /* success */ return TRUE; } static gboolean fu_logitech_tap_sensor_device_setup(FuDevice *device, GError **error) { FuLogitechTapSensorDevice *self = FU_LOGITECH_TAP_SENSOR_DEVICE(device); if (!fu_logitech_tap_sensor_device_set_version(self, error)) return FALSE; if (!fu_logitech_tap_sensor_device_set_serial(self, error)) return FALSE; return TRUE; } static gboolean fu_logitech_tap_sensor_device_probe(FuDevice *device, GError **error) { /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_logitech_tap_sensor_device_parent_class)->probe(device, error)) return FALSE; /* ignore unsupported subsystems */ if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "hidraw") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct subsystem=%s, expected hidraw", fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device))); return FALSE; } /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); } static void fu_logitech_tap_sensor_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 0, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 100, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_logitech_tap_sensor_device_init(FuLogitechTapSensorDevice *self) { fu_device_retry_set_delay(FU_DEVICE(self), 1000); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | FU_UDEV_DEVICE_FLAG_OPEN_NONBLOCK | FU_UDEV_DEVICE_FLAG_IOCTL_RETRY); } static void fu_logitech_tap_sensor_device_class_init(FuLogitechTapSensorDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_logitech_tap_sensor_device_probe; klass_device->setup = fu_logitech_tap_sensor_device_setup; klass_device->set_progress = fu_logitech_tap_sensor_device_set_progress; } fwupd-1.9.16/plugins/logitech-tap/fu-logitech-tap-sensor-device.h000066400000000000000000000007531460375044200247150ustar00rootroot00000000000000/* * Copyright (c) 1999-2023 Logitech, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-logitech-tap-device.h" #define FU_TYPE_LOGITECH_TAP_SENSOR_DEVICE (fu_logitech_tap_sensor_device_get_type()) G_DECLARE_FINAL_TYPE(FuLogitechTapSensorDevice, fu_logitech_tap_sensor_device, FU, LOGITECH_TAP_SENSOR_DEVICE, FuLogitechTapDevice) gboolean fu_logitech_tap_sensor_device_reboot_device(FuLogitechTapSensorDevice *self, GError **error); fwupd-1.9.16/plugins/logitech-tap/logitech-tap.quirk000066400000000000000000000004151460375044200224400ustar00rootroot00000000000000# Logitech Flare PIR Sensor [HIDRAW\VEN_046D&DEV_0872] Plugin = logitech_tap GType = FuLogitechTapSensorDevice # Logitech Tap HDMI Capture [VIDEO4LINUX\VEN_046D&DEV_0876] Plugin = logitech_tap GType = FuLogitechTapHdmiDevice InstallDuration = 120 RemoveDelay = 120000 fwupd-1.9.16/plugins/logitech-tap/meson.build000066400000000000000000000012751460375044200211520ustar00rootroot00000000000000if get_option('plugin_logitech_tap').require(gusb.found() and gudev.found(), error_message: 'usb and udev are needed for plugin_logitech_tap').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginLogitechTap"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('logitech-tap.quirk') plugin_builtins += static_library('fu_plugin_logitech_tap', sources: [ 'fu-logitech-tap-plugin.c', 'fu-logitech-tap-device.c', 'fu-logitech-tap-hdmi-device.c', 'fu-logitech-tap-sensor-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/mediatek-scaler/000077500000000000000000000000001460375044200174555ustar00rootroot00000000000000fwupd-1.9.16/plugins/mediatek-scaler/README.md000066400000000000000000000041711460375044200207370ustar00rootroot00000000000000--- title: Plugin: Mediatek Display Controller --- ## Introduction This plugin updates the firmware of DisplayPort device made by Mediatek. These devices communicate over I²C, via the DisplayPort aux channel. Devices are declared by kernel graphic driver, and queried with custom DDC/CI command to ensure the target devie. This plugin polls every drm dp aux device to find out the `i2c-dev` that is being used for DDC/CI communication. Devices should respond to a vendor specific command otherwise the display controller is ignored as unsupported. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format, which is written to the partition of the device flash that is not currently running. This plugin supports the following protocol ID: * `com.mediatek.scaler` ## GUID Generation Devices use instance ID that is composed of `Subsystem ID`, `Subsystem Model`, and the `Hardware Version`. The hardware version is read from the device. * `DISPLAY\VID_1028&PID_0C88&HWVER_2.1.2.1` * `DISPLAY\VID_1028&PID_0C8A&HWVER_2.1.2.1` * `PCI\VID_1028&PID_0C8A` (only-quirks) ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. On some hardware the MST device may not enumerate if there is no monitor actually plugged in. ## Quirk Use This plugin uses the following plugin-specific quirks: ### Flags:probe-vcp This flag is used if the VCP should be probed. ## Vendor ID security The vendor ID is set from the PCI vendor, for instance `PCI:0x1028` on Dell systems. ## External Interface Access This plugin requires access to i2c buses associated with the specified DisplayPort aux channel, for instance `/dev/i2c-5` and `/dev/drm_dp_aux3`. Note that the device number changes in various situations. ## Version Considerations This plugin has been available since fwupd version `1.9.6`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Crag Wang: @CragW * Greg Lo: @GregLo007 fwupd-1.9.16/plugins/mediatek-scaler/fu-mediatek-scaler-common.c000066400000000000000000000005251460375044200245530ustar00rootroot00000000000000/* * Copyright (C) 2023 Dell Technologies * Copyright (C) 2023 Mediatek Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-mediatek-scaler-common.h" gchar * mediatek_scaler_device_version_to_string(guint32 val) { return g_strdup_printf("%x.%x.%x", val & 0xff, (val >> 24) & 0xff, (val >> 16) & 0xff); } fwupd-1.9.16/plugins/mediatek-scaler/fu-mediatek-scaler-common.h000066400000000000000000000003441460375044200245570ustar00rootroot00000000000000/* * Copyright (C) 2023 Dell Technologies * Copyright (C) 2023 Mediatek Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gchar * mediatek_scaler_device_version_to_string(guint32 val); fwupd-1.9.16/plugins/mediatek-scaler/fu-mediatek-scaler-device.c000066400000000000000000000744621460375044200245350ustar00rootroot00000000000000/* * Copyright (C) 2023 Dell Technologies * Copyright (C) 2023 Mediatek Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-mediatek-scaler-common.h" #include "fu-mediatek-scaler-device.h" #include "fu-mediatek-scaler-firmware.h" #include "fu-mediatek-scaler-struct.h" #define DDC_DATA_LEN_DFT 0x80 #define DDC_DATA_FRAGEMENT_SIZE 0x0B /* 11 bytes for each DDC write */ #define DDC_DATA_PAGE_SIZE 0x1000 /* 4K bytes for each block page */ #define DDC_RW_MAX_RETRY_CNT 10 /* supported display controller type */ #define FU_MEDIATEK_SCALER_SUPPORTED_CONTROLLER_TYPE 0x00005605 /* timeout duration in ms to i2c-dev operation */ #define FU_MEDIATEK_SCALER_DEVICE_IOCTL_TIMEOUT 5000 /* delay time before a ddc read or write */ #define FU_MEDIATEK_SCALER_DDC_MSG_DELAY_MS 50 /* interval in ms between the poll to check device status */ #define FU_MEDIATEK_SCALER_DEVICE_POLL_INTERVAL 1000 /* firmware payload size */ #define FU_MEDIATEK_SCALER_FW_SIZE_MAX 0x100000 /** * FU_MEDIATEK_SCALER_DEVICE_FLAG_PROBE_VCP: * * Device VCP should be probed. */ #define FU_MEDIATEK_SCALER_DEVICE_FLAG_PROBE_VCP (1 << 0) struct _FuMediatekScalerDevice { FuUdevDevice parent_instance; FuUdevDevice *i2c_dev; }; G_DEFINE_TYPE(FuMediatekScalerDevice, fu_mediatek_scaler_device, FU_TYPE_UDEV_DEVICE) static void fu_mediatek_scaler_device_to_string(FuDevice *device, guint idt, GString *str) { FuMediatekScalerDevice *self = FU_MEDIATEK_SCALER_DEVICE(device); /* FuUdevDevice->to_string */ FU_DEVICE_CLASS(fu_mediatek_scaler_device_parent_class)->to_string(device, idt, str); if (self->i2c_dev != NULL) { fu_string_append(str, idt, "I2cDeviceFile", fu_udev_device_get_device_file(FU_UDEV_DEVICE(self->i2c_dev))); } } static gboolean fu_mediatek_scaler_ensure_device_address(FuMediatekScalerDevice *self, guint8 address, GError **error) { if (!fu_udev_device_ioctl(self->i2c_dev, I2C_SLAVE, (guint8 *)(guintptr)address, NULL, FU_MEDIATEK_SCALER_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error(error, "failed to set address '0x%02x' on %s: ", address, fu_udev_device_get_device_file(FU_UDEV_DEVICE(self->i2c_dev))); return FALSE; } return TRUE; } static gboolean fu_mediatek_scaler_device_set_i2c_dev(FuMediatekScalerDevice *self, const GPtrArray *devices, GError **error) { for (guint i = 0; i < devices->len; i++) { FuUdevDevice *device = g_ptr_array_index(devices, i); g_autoptr(GPtrArray) i2c_devs = fu_udev_device_get_children_with_subsystem(device, "i2c-dev"); if (i2c_devs->len == 0) { g_debug("no i2c-dev found under %s", fu_udev_device_get_sysfs_path(device)); continue; } if (i2c_devs->len > 1) { g_debug("ignoring %u additional i2c-dev under %s", i2c_devs->len - 1, fu_udev_device_get_sysfs_path(device)); } /* the first i2c_dev is enforced to represent the dp aux device */ self->i2c_dev = g_object_ref(g_ptr_array_index(i2c_devs, 0)); g_debug("found I2C bus at %s, using this device", fu_udev_device_get_sysfs_path(self->i2c_dev)); return fu_udev_device_set_physical_id(self->i2c_dev, "i2c", error); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no devices on the i2c bus"); return FALSE; } static gboolean fu_mediatek_scaler_device_use_aux_dev(FuMediatekScalerDevice *self, GError **error) { g_autoptr(GPtrArray) i2c_devices = fu_udev_device_get_siblings_with_subsystem(FU_UDEV_DEVICE(self), "i2c"); return fu_mediatek_scaler_device_set_i2c_dev(self, i2c_devices, error); } static gboolean fu_mediatek_scaler_device_ddc_write(FuMediatekScalerDevice *self, GByteArray *st_req, GError **error) { guint8 chksum = 0; g_autoptr(GByteArray) ddc_msgbox_write = g_byte_array_new(); const guint8 ddc_wfmt[] = {FU_DDC_I2C_ADDR_HOST_DEVICE, st_req->len | DDC_DATA_LEN_DFT}; /* write = addr_src, sizeof(cmd + op + data), cmd, op, data, checksum */ g_byte_array_append(ddc_msgbox_write, ddc_wfmt, sizeof(ddc_wfmt)); g_byte_array_append(ddc_msgbox_write, st_req->data, st_req->len); chksum ^= FU_DDC_I2C_ADDR_DISPLAY_DEVICE; for (gsize i = 0; i < ddc_msgbox_write->len; i++) chksum ^= ddc_msgbox_write->data[i]; g_byte_array_append(ddc_msgbox_write, &chksum, 1); /* print the raw data */ fu_dump_raw(G_LOG_DOMAIN, "DDC/CI write message", ddc_msgbox_write->data, ddc_msgbox_write->len); return fu_udev_device_pwrite(FU_UDEV_DEVICE(self->i2c_dev), 0x0, ddc_msgbox_write->data, ddc_msgbox_write->len, error); } static GByteArray * fu_mediatek_scaler_device_ddc_read(FuMediatekScalerDevice *self, GByteArray *st_req, GError **error) { guint8 buf[0x40] = {0x00}; /* default 64 bytes */ gsize report_data_sz = 0; guint8 checksum = 0; guint8 checksum_hw = 0; g_autoptr(GByteArray) st_res = g_byte_array_new(); /* write for read */ if (!fu_mediatek_scaler_device_ddc_write(self, st_req, error)) return NULL; /* DDCCI spec requires host to wait at least 50 - 200ms before next message */ fu_device_sleep(FU_DEVICE(self), FU_MEDIATEK_SCALER_DDC_MSG_DELAY_MS); /* read into tmp buffer */ if (!fu_udev_device_pread(FU_UDEV_DEVICE(self->i2c_dev), 0x0, buf, sizeof(buf), error)) return NULL; /* read buffer = addr(src) + length + data + checksum */ fu_dump_raw(G_LOG_DOMAIN, "DDC/CI read buffer", buf, sizeof(buf)); /* verify read buffer: [0] == source address */ if (buf[0] != FU_DDC_I2C_ADDR_DISPLAY_DEVICE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid read buffer: addr(src) expected 0x%02x, got 0x%02x.", (guint)FU_DDC_I2C_ADDR_DISPLAY_DEVICE, buf[0]); return NULL; } /* verify read buffer: [1] as the length of data */ if (buf[1] <= DDC_DATA_LEN_DFT) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid read buffer: size 0x%02x must greater than 0x%02x.", buf[1], (guint)DDC_DATA_LEN_DFT); return NULL; } /* verify read buffer: overflow guard from the length of data */ report_data_sz = buf[1] - DDC_DATA_LEN_DFT; if (report_data_sz + 3 > sizeof(buf)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid read buffer: size 0x%02x exceeded 0x%02x", (guint)report_data_sz, (guint)sizeof(buf)); return NULL; } /* verify read buffer: match the checksum */ checksum ^= FU_DDC_I2C_ADDR_CHECKSUM; for (gsize i = 0; i < report_data_sz + 2; i++) checksum ^= buf[i]; if (!fu_memread_uint8_safe(buf, sizeof(buf), report_data_sz + 2, &checksum_hw, error)) return NULL; if (checksum_hw != checksum) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid read buffer, checksum expected 0x%02x, got 0x%02x.", checksum, checksum_hw); return NULL; } /* truncate the last byte which is the checksum value */ g_byte_array_append(st_res, buf, report_data_sz + 2); /* print the raw data */ fu_dump_raw(G_LOG_DOMAIN, "DDC/CI read report", st_res->data, st_res->len); return g_steal_pointer(&st_res); } static gboolean fu_mediatek_scaler_device_set_ddc_priority(FuMediatekScalerDevice *self, FuDdcciPriority priority, GError **error) { g_autoptr(GByteArray) st_req = fu_struct_ddc_cmd_new(); g_autoptr(GError) error_local = NULL; fu_struct_ddc_cmd_set_vcp_code(st_req, FU_DDC_VCP_CODE_PRIORITY); fu_byte_array_append_uint8(st_req, priority); if (!fu_mediatek_scaler_device_ddc_write(self, st_req, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to set priority %s [0x%x], unsupported display: %s", fu_ddcci_priority_to_string(priority), priority, error_local->message); return FALSE; } fu_device_sleep(FU_DEVICE(self), FU_MEDIATEK_SCALER_DDC_MSG_DELAY_MS); return TRUE; } static gboolean fu_mediatek_scaler_display_is_connected(FuMediatekScalerDevice *self, GError **error) { g_autoptr(GByteArray) st_req = fu_struct_ddc_cmd_new(); g_autoptr(GByteArray) st_res = NULL; g_autoptr(GError) error_local = NULL; guint8 randval_req = 0; guint8 randval1 = g_random_int_range(1, 255); guint8 randval2 = g_random_int_range(1, 255); fu_struct_ddc_cmd_set_vcp_code(st_req, FU_DDC_VCP_CODE_SUM); fu_byte_array_append_uint8(st_req, randval1); fu_byte_array_append_uint8(st_req, randval2); st_res = fu_mediatek_scaler_device_ddc_read(self, st_req, &error_local); if (st_res == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to read report: %s", error_local->message); return FALSE; } if (!fu_memread_uint8_safe(st_res->data, st_res->len, 3, &randval_req, error)) return FALSE; /* device unique feature */ if (randval_req != (guint8)(randval1 + randval2)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsuccessful display feature test, expected 0x%02x, got 0x%02x.", (guint8)(randval1 + randval2), randval_req); return FALSE; } g_info("found mediatek display controller: %s, i2c-dev: %s", fu_udev_device_get_device_file(FU_UDEV_DEVICE(self)), fu_udev_device_get_device_file(FU_UDEV_DEVICE(self->i2c_dev))); return TRUE; } static gboolean fu_mediatek_scaler_display_is_connected_cb(FuDevice *device, gpointer user_data, GError **error) { FuMediatekScalerDevice *self = FU_MEDIATEK_SCALER_DEVICE(device); return fu_mediatek_scaler_display_is_connected(self, error); } static gchar * fu_mediatek_scaler_device_get_hardware_version(FuDevice *device, GError **error) { FuMediatekScalerDevice *self = FU_MEDIATEK_SCALER_DEVICE(device); g_autoptr(GByteArray) st_req = fu_struct_ddc_cmd_new(); g_autoptr(GByteArray) st_res = NULL; guint8 verbuf[4] = {0}; /* get the hardware version */ fu_struct_ddc_cmd_set_vcp_code(st_req, FU_DDC_VCP_CODE_VERSION); fu_byte_array_append_uint8(st_req, 0x00); st_res = fu_mediatek_scaler_device_ddc_read(self, st_req, error); if (st_res == NULL) return NULL; if (!fu_memread_uint8_safe(st_res->data, st_res->len, 3, &verbuf[0], error)) return NULL; if (!fu_memread_uint8_safe(st_res->data, st_res->len, 2, &verbuf[1], error)) return NULL; if (!fu_memread_uint8_safe(st_res->data, st_res->len, 5, &verbuf[2], error)) return NULL; if (!fu_memread_uint8_safe(st_res->data, st_res->len, 4, &verbuf[3], error)) return NULL; return g_strdup_printf("%x.%x.%x.%x", verbuf[0], verbuf[1], verbuf[2], verbuf[3]); } static gboolean fu_mediatek_scaler_device_ensure_firmware_version(FuMediatekScalerDevice *self, GError **error) { guint32 version_raw = 0x0; g_autoptr(GByteArray) st_res = NULL; g_autoptr(GByteArray) st_req = fu_struct_ddc_cmd_new(); /* get the installed firmware version */ fu_struct_ddc_cmd_set_vcp_code(st_req, FU_DDC_VCP_CODE_VERSION); fu_byte_array_append_uint8(st_req, 0x01); st_res = fu_mediatek_scaler_device_ddc_read(self, st_req, error); if (st_res == NULL) return FALSE; if (!fu_memread_uint32_safe(st_res->data, st_res->len, 2, &version_raw, G_LITTLE_ENDIAN, error)) return FALSE; fu_device_set_version_raw(FU_DEVICE(self), version_raw); return TRUE; } static gboolean fu_mediatek_scaler_device_open(FuDevice *device, GError **error) { FuMediatekScalerDevice *self = FU_MEDIATEK_SCALER_DEVICE(device); /* proxy */ if (!fu_device_open(FU_DEVICE(self->i2c_dev), error)) return FALSE; /* set the target address -- should be safe */ if (!fu_mediatek_scaler_ensure_device_address(self, FU_DDC_I2C_ADDR_DISPLAY_DEVICE >> 1, error)) return FALSE; /* we know this is a Mediatek scaler now */ if (fu_device_get_version_raw(device) != 0x0) { if (!fu_mediatek_scaler_device_set_ddc_priority(self, FU_DDCCI_PRIORITY_UP, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_mediatek_scaler_device_close(FuDevice *device, GError **error) { FuMediatekScalerDevice *self = FU_MEDIATEK_SCALER_DEVICE(device); /* do nothing for unsupported devices */ if (self->i2c_dev == NULL) return TRUE; /* set the target address */ if (!fu_mediatek_scaler_ensure_device_address(self, FU_DDC_I2C_ADDR_DISPLAY_DEVICE >> 1, error)) return FALSE; /* reset DDC priority */ if (!fu_mediatek_scaler_device_set_ddc_priority(self, FU_DDCCI_PRIORITY_NORMAL, error)) return FALSE; /* proxy */ if (!fu_device_close(FU_DEVICE(self->i2c_dev), error)) return FALSE; /* success */ return TRUE; } static gboolean fu_mediatek_scaler_device_verify_controller_type(FuMediatekScalerDevice *self, GError **error) { g_autoptr(GByteArray) st_req = fu_struct_ddc_cmd_new(); g_autoptr(GByteArray) st_res = NULL; g_autoptr(GError) error_local = NULL; guint32 controller_type = 0; fu_struct_ddc_cmd_set_opcode(st_req, FU_DDC_OPCODE_GET_VCP); fu_struct_ddc_cmd_set_vcp_code(st_req, FU_DDC_VCP_CODE_CONTROLLER_TYPE); st_res = fu_mediatek_scaler_device_ddc_read(self, st_req, error); if (st_res == NULL) return FALSE; if (!fu_memread_uint32_safe(st_res->data, st_res->len, st_res->len - 4, &controller_type, G_BIG_ENDIAN, error)) return FALSE; /* restrict to specific controller type */ if (controller_type != FU_MEDIATEK_SCALER_SUPPORTED_CONTROLLER_TYPE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "0x%x is not supported", controller_type); return FALSE; } /* success */ fu_device_sleep(FU_DEVICE(self), FU_MEDIATEK_SCALER_DDC_MSG_DELAY_MS); return TRUE; } static gboolean fu_mediatek_scaler_device_setup(FuDevice *device, GError **error) { FuMediatekScalerDevice *self = FU_MEDIATEK_SCALER_DEVICE(device); g_autofree gchar *hw_ver = NULL; /* verify the controller type */ if (!fu_mediatek_scaler_device_verify_controller_type(self, error)) { g_prefix_error(error, "invalid controller type: "); return FALSE; } /* mediatek display is connected */ if (!fu_mediatek_scaler_display_is_connected(self, error)) return FALSE; /* prioritize DDC/CI -- FuDevice->open() did not do this as the version is not set */ if (!fu_mediatek_scaler_device_set_ddc_priority(self, FU_DDCCI_PRIORITY_UP, error)) return FALSE; /* set hardware version */ hw_ver = fu_mediatek_scaler_device_get_hardware_version(device, error); if (hw_ver == NULL) return FALSE; fu_device_add_instance_str(device, "HWVER", hw_ver); if (!fu_device_build_instance_id(device, error, "DISPLAY", "VID", "PID", "HWVER", NULL)) return FALSE; /* get details */ if (!fu_mediatek_scaler_device_ensure_firmware_version(self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_mediatek_scaler_device_probe(FuDevice *device, GError **error) { FuMediatekScalerDevice *self = FU_MEDIATEK_SCALER_DEVICE(device); g_autofree gchar *vendor_id = NULL; g_autoptr(FuUdevDevice) udev_parent = NULL; /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_mediatek_scaler_device_parent_class)->probe(device, error)) return FALSE; /* set vid and pid from PCI bus */ udev_parent = fu_udev_device_get_parent_with_subsystem(FU_UDEV_DEVICE(device), "pci"); if (udev_parent == NULL) return FALSE; if (!fu_device_probe(FU_DEVICE(udev_parent), error)) return FALSE; fu_device_add_instance_u16(device, "VID", fu_udev_device_get_subsystem_vendor(udev_parent)); fu_device_add_instance_u16(device, "PID", fu_udev_device_get_subsystem_model(udev_parent)); if (!fu_device_build_instance_id_full(FU_DEVICE(self), FU_DEVICE_INSTANCE_FLAG_QUIRKS, error, "PCI", "VID", "PID", NULL)) return FALSE; if (!fu_device_has_private_flag(device, FU_MEDIATEK_SCALER_DEVICE_FLAG_PROBE_VCP)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%04X:%04X: is not supported", fu_udev_device_get_subsystem_vendor(udev_parent), fu_udev_device_get_subsystem_model(udev_parent)); return FALSE; } /* determine the i2c_dev for dp aux dev */ if (!fu_mediatek_scaler_device_use_aux_dev(self, error)) return FALSE; /* add IDs */ vendor_id = g_strdup_printf("PCI:0x%04X", fu_udev_device_get_subsystem_vendor(udev_parent)); fu_device_add_vendor_id(device, vendor_id); fu_device_set_physical_id(device, fu_udev_device_get_device_file(FU_UDEV_DEVICE(device))); /* success */ return TRUE; } static gboolean fu_mediatek_scaler_device_set_recv_info(FuDevice *device, gsize fw_sz, GError **error) { FuMediatekScalerDevice *self = FU_MEDIATEK_SCALER_DEVICE(device); g_autoptr(GByteArray) st_req = fu_struct_ddc_cmd_new(); fu_struct_ddc_cmd_set_vcp_code(st_req, FU_DDC_VCP_CODE_UPDATE_PREP); fu_byte_array_append_uint32(st_req, fw_sz, G_LITTLE_ENDIAN); return fu_mediatek_scaler_device_ddc_write(self, st_req, error); } static gboolean fu_mediatek_scaler_device_get_data_ack_size(FuDevice *device, guint32 *ack_sz, GError **error) { FuMediatekScalerDevice *self = FU_MEDIATEK_SCALER_DEVICE(device); g_autoptr(GByteArray) st_req = fu_struct_ddc_cmd_new(); g_autoptr(GByteArray) st_res = NULL; fu_struct_ddc_cmd_set_vcp_code(st_req, FU_DDC_VCP_CODE_UPDATE_ACK); st_res = fu_mediatek_scaler_device_ddc_read(self, st_req, error); if (st_res == NULL) return FALSE; if (!fu_memread_uint32_safe(st_res->data, st_res->len, 2, ack_sz, G_LITTLE_ENDIAN, error)) return FALSE; return TRUE; } static gboolean fu_mediatek_scaler_device_prepare_update_cb(FuDevice *device, gpointer user_data, GError **error) { guint32 acksz = 0; gsize fw_sz = *(gsize *)user_data; /* set the file length that to be transmit*/ if (!fu_mediatek_scaler_device_set_recv_info(device, fw_sz, error)) return FALSE; /* extra delay time needed */ fu_device_sleep(device, 100); /* device accepted the file length for data transition */ if (!fu_mediatek_scaler_device_get_data_ack_size(device, &acksz, error)) return FALSE; if (fw_sz != (gsize)acksz) { g_prefix_error(error, "device nak the incoming filesize, requested: %" G_GSIZE_FORMAT ", ack: %u", fw_sz, acksz); return FALSE; } return TRUE; } static gboolean fu_mediatek_scaler_device_prepare_update(FuDevice *device, gsize fw_sz, GError **error) { if (!fu_device_retry_full(device, fu_mediatek_scaler_device_prepare_update_cb, DDC_RW_MAX_RETRY_CNT, 10, /* ms */ &fw_sz, error)) { g_prefix_error(error, "failed to prepare update: "); return FALSE; } return TRUE; } static gboolean fu_mediatek_scaler_device_set_data(FuMediatekScalerDevice *self, FuChunk *chk, GError **error) { g_autoptr(FuChunkArray) chk_slices = NULL; g_autoptr(GBytes) chk_bytes = fu_chunk_get_bytes(chk); /* smaller slices to accodomate pch variants */ chk_slices = fu_chunk_array_new_from_bytes(chk_bytes, 0x00, DDC_DATA_FRAGEMENT_SIZE); for (guint i = 0; i < fu_chunk_array_length(chk_slices); i++) { g_autoptr(GByteArray) st_req = fu_struct_ddc_cmd_new(); g_autoptr(FuChunk) chk_slice = fu_chunk_array_index(chk_slices, i); fu_struct_ddc_cmd_set_vcp_code(st_req, FU_DDC_VCP_CODE_SET_DATA); g_byte_array_append(st_req, fu_chunk_get_data(chk_slice), (guint)fu_chunk_get_data_sz(chk_slice)); if (!fu_mediatek_scaler_device_ddc_write(self, st_req, error)) { g_prefix_error(error, "failed to send firmware to device: "); return FALSE; } } return TRUE; } static gboolean fu_mediatek_scaler_device_get_staged_data(FuMediatekScalerDevice *self, guint16 *chksum, guint32 *pktcnt, GError **error) { g_autoptr(GByteArray) st_req = fu_struct_ddc_cmd_new(); g_autoptr(GByteArray) st_res = NULL; fu_struct_ddc_cmd_set_vcp_code(st_req, FU_DDC_VCP_CODE_GET_STAGED); st_res = fu_mediatek_scaler_device_ddc_read(self, st_req, error); if (st_res == NULL) return FALSE; if (!fu_memread_uint16_safe(st_res->data, st_res->len, 2, chksum, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(st_res->data, st_res->len, 4, pktcnt, G_LITTLE_ENDIAN, error)) return FALSE; return TRUE; } static gboolean fu_mediatek_scaler_device_check_sent_info(FuMediatekScalerDevice *self, GBytes *fwdata, GError **error) { guint16 chksum = 0; guint32 pktcnt = 0; if (!fu_mediatek_scaler_device_get_staged_data(self, &chksum, &pktcnt, error)) return FALSE; /* verify the staged packets on chip */ if (g_bytes_get_size(fwdata) != pktcnt) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed data verification, sent size: %" G_GSIZE_FORMAT ", ack size: %u", g_bytes_get_size(fwdata), pktcnt); return FALSE; } /* verify the checksum on chip */ if (fu_sum16_bytes(fwdata) != chksum) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed data checksum comparison, expected: %u, got: %u", fu_sum16_bytes(fwdata), chksum); return FALSE; } return TRUE; } static gboolean fu_mediatek_scaler_device_run_isp(FuMediatekScalerDevice *self, guint16 chksum, GError **error) { g_autoptr(GByteArray) st_req = fu_struct_ddc_cmd_new(); fu_struct_ddc_cmd_set_vcp_code(st_req, FU_DDC_VCP_CODE_COMMIT_FW); fu_byte_array_append_uint16(st_req, chksum, G_LITTLE_ENDIAN); return fu_mediatek_scaler_device_ddc_write(self, st_req, error); } static gboolean fu_mediatek_scaler_device_commit_firmware(FuMediatekScalerDevice *self, GBytes *fw, GError **error) { if (!(fu_mediatek_scaler_device_run_isp(self, fu_sum16_bytes(fw), error))) { g_prefix_error(error, "failed to commit firmware: "); return FALSE; } return TRUE; } static gboolean fu_mediatek_scaler_device_set_isp_reboot(FuMediatekScalerDevice *self, GError **error) { g_autoptr(GByteArray) st_req = fu_struct_ddc_cmd_new(); g_autoptr(GError) error_local = NULL; /* device will reboot after this, so the write will timed out fail */ fu_struct_ddc_cmd_set_vcp_code(st_req, FU_DDC_VCP_CODE_REBOOT); if (!fu_mediatek_scaler_device_ddc_write(self, st_req, &error_local)) { if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to set isp reboot: "); return FALSE; } } return TRUE; } static gboolean fu_mediatek_scaler_device_get_isp_status(FuMediatekScalerDevice *self, GError **error) { guint8 isp_status = 0; g_autoptr(GByteArray) st_req = fu_struct_ddc_cmd_new(); g_autoptr(GByteArray) st_res = NULL; fu_struct_ddc_cmd_set_vcp_code(st_req, FU_DDC_VCP_CODE_GET_ISP_MODE); st_res = fu_mediatek_scaler_device_ddc_read(self, st_req, error); if (st_res == NULL) return FALSE; if (!fu_memread_uint8_safe(st_res->data, st_res->len, 2, &isp_status, error)) return FALSE; if (isp_status != 2) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "incorrect isp status, expected: 0x%02X, got 0x%u", (guint)2, isp_status); return FALSE; } return TRUE; } static gboolean fu_mediatek_scaler_device_verify(FuDevice *device, gsize sz, GError **error) { FuMediatekScalerDevice *self = FU_MEDIATEK_SCALER_DEVICE(device); guint base = sz / 1024 / 512; guint max_tries = base < 1 ? 60 : base * 60; if (!fu_device_retry_full(device, fu_mediatek_scaler_display_is_connected_cb, max_tries, FU_MEDIATEK_SCALER_DEVICE_POLL_INTERVAL, NULL, error)) { g_prefix_error(error, "display controller did not reconnect after %u retries: ", max_tries); return FALSE; } if (!fu_mediatek_scaler_device_get_isp_status(self, error)) { g_prefix_error(error, "failed to get isp status: "); return FALSE; } return TRUE; } static gboolean fu_mediatek_scaler_device_chunk_data_is_blank(FuChunk *chk) { const guint8 *data = fu_chunk_get_data(chk); for (gsize idx = 0; idx < fu_chunk_get_data_sz(chk); idx++) if (data[idx] != 0xFF) return FALSE; return TRUE; } static gboolean fu_mediatek_scaler_device_set_data_fast_forward(FuMediatekScalerDevice *self, FuChunk *chk, GError **error) { g_autoptr(GByteArray) st_req = fu_struct_ddc_cmd_new(); fu_struct_ddc_cmd_set_vcp_code(st_req, FU_DDC_VCP_CODE_SET_DATA_FF); fu_byte_array_append_uint32(st_req, fu_chunk_get_data_sz(chk), G_LITTLE_ENDIAN); return fu_mediatek_scaler_device_ddc_write(self, st_req, error); } static gboolean fu_mediatek_scaler_device_write_firmware_impl(FuMediatekScalerDevice *self, GBytes *fw, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x00, DDC_DATA_PAGE_SIZE); for (gint retry = 1; retry <= DDC_RW_MAX_RETRY_CNT; retry++) { g_autoptr(GError) error_local = NULL; for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); /* fast forward if chunk is empty, otherwise set data per fragment size */ if (fu_mediatek_scaler_device_chunk_data_is_blank(chk)) { if (!fu_mediatek_scaler_device_set_data_fast_forward(self, chk, error)) return FALSE; } else { if (!fu_mediatek_scaler_device_set_data(self, chk, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), 1); } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } /* exit the try loop when successes */ fu_device_sleep(FU_DEVICE(self), FU_MEDIATEK_SCALER_DDC_MSG_DELAY_MS); if (fu_mediatek_scaler_device_check_sent_info(self, fw, &error_local)) return TRUE; if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_WRITE)) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } g_debug("retry write_firmware: step: %d, max: %d", retry, DDC_RW_MAX_RETRY_CNT); } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "maximum tries exceeded"); return FALSE; } static gboolean fu_mediatek_scaler_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuMediatekScalerDevice *self = FU_MEDIATEK_SCALER_DEVICE(device); guint max_tries = 30; /* reboot the device */ if (!(fu_mediatek_scaler_device_set_isp_reboot(self, error))) return FALSE; /* wait for the device back */ if (!fu_device_retry_full(device, fu_mediatek_scaler_display_is_connected_cb, max_tries, FU_MEDIATEK_SCALER_DEVICE_POLL_INTERVAL, NULL, error)) { g_prefix_error(error, "display controller did not reconnect after %u retries: ", max_tries); return FALSE; } return TRUE; } static gboolean fu_mediatek_scaler_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuMediatekScalerDevice *self = FU_MEDIATEK_SCALER_DEVICE(device); gsize fw_size = 0; g_autoptr(GBytes) fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "prepare"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 75, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "commit"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 25, "verify"); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* prepare the device to accept firmware image */ fw_size = g_bytes_get_size(fw); if (!fu_mediatek_scaler_device_prepare_update(device, fw_size, error)) return FALSE; fu_progress_step_done(progress); /* write firmware to device */ if (!fu_mediatek_scaler_device_write_firmware_impl(self, fw, progress, error)) return FALSE; fu_progress_step_done(progress); /* send ISP command to commit the update */ if (!fu_mediatek_scaler_device_commit_firmware(self, fw, error)) return FALSE; fu_progress_step_done(progress); /* verify display and ISP status */ if (!fu_mediatek_scaler_device_verify(device, fw_size, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static FuFirmware * fu_mediatek_scaler_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_mediatek_scaler_firmware_new(); if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; g_info("firmware version old: %s, new: %s", fu_device_get_version(device), fu_firmware_get_version(firmware)); return g_steal_pointer(&firmware); } static gchar * fu_mediatek_scaler_device_convert_version(FuDevice *self, guint64 version_raw) { return mediatek_scaler_device_version_to_string(version_raw); } static void fu_mediatek_scaler_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_mediatek_scaler_device_init(FuMediatekScalerDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_vendor(FU_DEVICE(self), "Mediatek"); fu_device_add_protocol(FU_DEVICE(self), "com.mediatek.scaler"); fu_device_set_name(FU_DEVICE(self), "Display Controller"); fu_device_add_icon(FU_DEVICE(self), "video-display"); fu_device_set_firmware_size_max(FU_DEVICE(self), FU_MEDIATEK_SCALER_FW_SIZE_MAX); fu_device_register_private_flag(FU_DEVICE(self), FU_MEDIATEK_SCALER_DEVICE_FLAG_PROBE_VCP, "probe-vcp"); } static void fu_mediatek_scaler_device_class_init(FuMediatekScalerDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_mediatek_scaler_device_to_string; klass_device->convert_version = fu_mediatek_scaler_device_convert_version; klass_device->probe = fu_mediatek_scaler_device_probe; klass_device->setup = fu_mediatek_scaler_device_setup; klass_device->open = fu_mediatek_scaler_device_open; klass_device->close = fu_mediatek_scaler_device_close; klass_device->prepare_firmware = fu_mediatek_scaler_device_prepare_firmware; klass_device->write_firmware = fu_mediatek_scaler_device_write_firmware; klass_device->attach = fu_mediatek_scaler_device_attach; klass_device->reload = fu_mediatek_scaler_device_setup; klass_device->set_progress = fu_mediatek_scaler_device_set_progress; } fwupd-1.9.16/plugins/mediatek-scaler/fu-mediatek-scaler-device.h000066400000000000000000000006011460375044200245220ustar00rootroot00000000000000/* * Copyright (C) 2023 Dell Technologies * Copyright (C) 2023 Mediatek Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_MEDIATEK_SCALER_DEVICE (fu_mediatek_scaler_device_get_type()) G_DECLARE_FINAL_TYPE(FuMediatekScalerDevice, fu_mediatek_scaler_device, FU, MEDIATEK_SCALER_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/mediatek-scaler/fu-mediatek-scaler-firmware.c000066400000000000000000000040221460375044200250730ustar00rootroot00000000000000/* * Copyright (C) 2023 Dell Technologies * Copyright (C) 2023 Mediatek Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-mediatek-scaler-common.h" #include "fu-mediatek-scaler-firmware.h" #define MTK_FW_OFFSET_VERSION 0x7118 #define MTK_FW_OFFSET_TIMESTAMP_DATE 0x7200 #define MTK_FW_OFFSET_TIMESTAMP_TIME 0x720c #define MTK_FW_TIMESTAMP_DATE_SIZE 11 #define MTK_FW_TIMESTAMP_TIME_SIZE 8 struct _FuMediatekScalerFirmware { FuFirmwareClass parent_instance; }; G_DEFINE_TYPE(FuMediatekScalerFirmware, fu_mediatek_scaler_firmware, FU_TYPE_FIRMWARE) static gboolean fu_mediatek_scaler_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { const guint8 *buf = g_bytes_get_data(fw, NULL); const gsize bufsz = g_bytes_get_size(fw); guint32 ver_tmp = 0x0; g_autofree gchar *fw_version = NULL; g_autofree gchar *fw_date = NULL; g_autofree gchar *fw_time = NULL; /* read version from firmware */ if (!fu_memread_uint32_safe(buf, bufsz, MTK_FW_OFFSET_VERSION, &ver_tmp, G_LITTLE_ENDIAN, error)) return FALSE; fw_version = mediatek_scaler_device_version_to_string(ver_tmp); fu_firmware_set_version(firmware, fw_version); /* read timestamp from firmware */ fw_date = fu_memstrsafe(buf, bufsz, MTK_FW_OFFSET_TIMESTAMP_DATE, MTK_FW_TIMESTAMP_DATE_SIZE, error); fw_time = fu_memstrsafe(buf, bufsz, MTK_FW_OFFSET_TIMESTAMP_TIME, MTK_FW_TIMESTAMP_TIME_SIZE, error); g_info("firmware timestamp: %s, %s", fw_time, fw_date); return TRUE; } static void fu_mediatek_scaler_firmware_init(FuMediatekScalerFirmware *self) { } static void fu_mediatek_scaler_firmware_class_init(FuMediatekScalerFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_mediatek_scaler_firmware_parse; } FuFirmware * fu_mediatek_scaler_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_MEDIATEK_SCALER_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/mediatek-scaler/fu-mediatek-scaler-firmware.h000066400000000000000000000006761460375044200251130ustar00rootroot00000000000000/* * Copyright (C) 2023 Dell Technologies * Copyright (C) 2023 Mediatek Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_MEDIATEK_SCALER_FIRMWARE (fu_mediatek_scaler_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuMediatekScalerFirmware, fu_mediatek_scaler_firmware, FU, MEDIATEK_SCALER_FIRMWARE, FuFirmware) FuFirmware * fu_mediatek_scaler_firmware_new(void); fwupd-1.9.16/plugins/mediatek-scaler/fu-mediatek-scaler-plugin.c000066400000000000000000000020511460375044200245550ustar00rootroot00000000000000/* * Copyright (C) 2023 Dell Technologies * Copyright (C) 2023 Mediatek Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-mediatek-scaler-device.h" #include "fu-mediatek-scaler-firmware.h" #include "fu-mediatek-scaler-plugin.h" struct _FuMediatekScalerPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuMediatekScalerPlugin, fu_mediatek_scaler_plugin, FU_TYPE_PLUGIN) static void fu_mediatek_scaler_plugin_init(FuMediatekScalerPlugin *self) { } static void fu_mediatek_scaler_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "drm"); fu_plugin_add_device_udev_subsystem(plugin, "drm_dp_aux_dev"); fu_plugin_add_device_gtype(plugin, FU_TYPE_MEDIATEK_SCALER_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_MEDIATEK_SCALER_FIRMWARE); } static void fu_mediatek_scaler_plugin_class_init(FuMediatekScalerPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_mediatek_scaler_plugin_constructed; } fwupd-1.9.16/plugins/mediatek-scaler/fu-mediatek-scaler-plugin.h000066400000000000000000000004571460375044200245720ustar00rootroot00000000000000/* * Copyright (C) 2023 Dell Technologies * Copyright (C) 2023 Mediatek Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuMediatekScalerPlugin, fu_mediatek_scaler_plugin, FU, MEDIATEK_SCALER_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/mediatek-scaler/fu-mediatek-scaler.rs000066400000000000000000000015041460375044200234650ustar00rootroot00000000000000/* * Copyright (C) 2023 Dell Technologies * Copyright (C) 2023 Mediatek Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #[repr(u8)] enum DdcOpcode { GetVcp = 0x01, // standard get vcp feature Req = 0xCC, // vendor specific opcode } #[repr(u8)] enum DdcVcpCode { ControllerType = 0xC8, // standard display controller type Priority = 0x90, UpdatePrep = 0xF2, UpdateAck = 0xF3, SetData = 0xF4, GetStaged = 0xF5, SetDataFf = 0xF6, CommitFw = 0xF7, GetIspMode = 0xF8, Reboot = 0xFB, Sum = 0xFE, Version = 0xFF, } #[derive(New)] struct DdcCmd { opcode: DdcOpcode = Req, vcp_code: DdcVcpCode, } #[repr(u8)] enum DdcI2cAddr{ DisplayDevice = 0x6E, HostDevice = 0x51, Checksum = 0x50, } #[derive(ToString)] #[repr(u8)] enum DdcciPriority{ Normal, Up, } fwupd-1.9.16/plugins/mediatek-scaler/mediatek-scaler.quirk000066400000000000000000000001241460375044200235610ustar00rootroot00000000000000[PCI\VID_1028&PID_0C88] Flags = probe-vcp [PCI\VID_1028&PID_0C8A] Flags = probe-vcp fwupd-1.9.16/plugins/mediatek-scaler/meson.build000066400000000000000000000013731460375044200216230ustar00rootroot00000000000000if get_option('plugin_mediatek_scaler').require(gudev.found(), error_message: 'gudev is needed for plugin_mediatek_scaler').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginMediatekScaler"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('mediatek-scaler.quirk') plugin_builtins += static_library('fu_plugin_mediatek_scaler', rustgen.process( 'fu-mediatek-scaler.rs', # fuzzing ), sources: [ 'fu-mediatek-scaler-device.c', 'fu-mediatek-scaler-firmware.c', 'fu-mediatek-scaler-common.c', 'fu-mediatek-scaler-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/meson.build000066400000000000000000000052741460375044200165750ustar00rootroot00000000000000# some of these are controlled by meson tristate features plugin_deps = [ gio, giounix, gnutls, gmodule, gudev, gusb, libarchive, libjsonglib, libxmlb, libdrm_amdgpu, protobufc, fwupdplugin_rs_dep, ] plugins = { 'uefi-capsule': false, 'acpi-dmar': false, 'acpi-facp': false, 'acpi-ivrs': false, 'acpi-phat': false, 'algoltek-usb': false, 'amd-pmc': false, 'amd-gpu': false, 'analogix': false, 'android-boot': false, 'ata': false, 'audio-s5gen2': false, 'aver-hid': false, 'bcm57xx': false, 'bios': false, 'ccgx': false, 'ccgx-dmc': false, 'cfu': false, 'ch341a': false, 'ch347': false, 'colorhug': false, 'corsair': false, 'cpu': false, 'cros-ec': false, 'dell': false, 'dell-dock': false, 'dfu': false, 'dfu-csr': false, 'ebitdo': false, 'elantp': false, 'elanfp': false, 'emmc': false, 'ep963x': false, 'fastboot': false, 'flashrom': false, 'focalfp': false, 'fpc': false, 'fresco-pd': false, 'genesys': false, 'genesys-gl32xx': false, 'goodix-moc': false, 'goodix-tp': false, 'gpio': false, 'hailuck': false, 'intel-gsc': false, 'intel-me': false, 'intel-spi': false, 'intel-usb4': false, 'iommu': false, 'jabra': false, 'jabra-gnp': false, 'kinetic-dp': false, 'lenovo-thinklmi': false, 'linux-display': false, 'linux-lockdown': false, 'linux-sleep': false, 'linux-swap': false, 'linux-tainted': false, 'logind': false, 'logitech-hidpp': false, 'logitech-bulkcontroller': false, 'logitech-rallysystem': false, 'logitech-scribe': false, 'logitech-tap': false, 'mediatek-scaler': false, 'modem-manager': false, 'msr': false, 'mtd': false, 'nitrokey': false, 'nordic-hid': false, 'nvme': false, 'optionrom': false, 'parade-lspcon': false, 'pci-bcr': false, 'pci-mei': false, 'pci-psp': false, 'pixart-rf': false, 'powerd': false, 'qsi-dock': false, 'realtek-mst': false, 'redfish': false, 'rts54hid': false, 'rts54hub': false, 'steelseries': false, 'scsi': false, 'superio': false, 'synaptics-cape': false, 'synaptics-cxaudio': false, 'synaptics-mst': false, 'synaptics-prometheus': false, 'synaptics-rmi': false, 'system76-launch': false, 'test': false, 'thelio-io': false, 'thunderbolt': false, 'ti-tps6598x': false, 'tpm': false, 'uefi-dbx': false, 'uefi-esrt': false, 'uefi-pk': false, 'uefi-recovery': false, 'uf2': false, 'upower': false, 'usi-dock': false, 'vbe': false, 'vli': false, 'wacom-raw': false, 'wacom-usb': false, 'wistron-dock': false, } if get_option('plugin_vendor_example') plugins += {'vendor-example': false} endif foreach plugin, enabled: plugins subdir(plugin) endforeach fwupd-1.9.16/plugins/modem-manager/000077500000000000000000000000001460375044200171345ustar00rootroot00000000000000fwupd-1.9.16/plugins/modem-manager/README.md000066400000000000000000000071571460375044200204250ustar00rootroot00000000000000--- title: Plugin: ModemManager --- ## Introduction This plugin adds support for devices managed by ModemManager. ## GUID Generation These device use the ModemManager "Firmware Device IDs" as the GUID, e.g. * `USB\VID_413C&PID_81D7&REV_0318&CARRIER_VODAFONE` (only if not using `Flags=use-branch`) * `USB\VID_413C&PID_81D7&REV_0318` * `USB\VID_413C&PID_81D7` * `PCI\VID_105B&PID_E0AB&REV_0000&CARRIER_VODAFONE` (only if not using `Flags=use-branch`) * `PCI\VID_105B&PID_E0AB&REV_0000` * `PCI\VID_105B&PID_E0AB` ## Quirk Use This plugin uses the following plugin-specific quirk: ### ModemManagerBranchAtCommand AT command to execute to determine the firmware branch currently installed on the modem. Since: 1.7.4 ### ModemManagerFirehoseProgFile Firehose program file to use during the switch to EDL (Emergency Download) mode. Since: 1.8.10 ### Flags=use-branch Use the carrier (e.g. `VODAFONE`) as the device branch name so that `fwupdmgr sync` can downgrade the firmware as required. This is now the recommended mode for all modem devices with a carrier-specific firmware image, although it requires that the firmware branch is also set in the firmware metadata. Since: 1.9.8 ## Vendor ID Security The vendor ID is set from the USB or PCI vendor, for example `USB:0x413C` `PCI:0x105B` ## Update method: fastboot If the device supports the 'fastboot' update method, it must also report which AT command should be used to trigger the modem reboot into fastboot mode. Once the device is in fastboot mode, the firmware upgrade process will happen as defined e.g. in the 'flashfile.xml' file. Every file included in the CAB that is not listed in the associated 'flashfile.xml' will be totally ignored during the fastboot upgrade procedure. Update Protocol: `com.google.fastboot` For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the fastboot and runtime modes are treated as the same device. ## Update method: qmi-pdc If the device supports the 'qmi-pdc' update method, the contents of the CAB file should include files named as 'mcfg.*.mbn' which will be treated as MCFG configuration files to download into the device using the Persistent Device Configuration QMI service. If a device supports both 'fastboot' and 'qmi-pdc' methods, the fastboot operation will always be run before the QMI operation, so that e.g. the full partition where the MCFG files are stored can be wiped out before installing the new ones. Update protocol: `com.qualcomm.qmi_pdc` For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the fastboot and runtime modes are treated as the same device. ## Update method: mbim-qdu If the device supports the 'mbim-qdu' update method, the contents of the CAB file should include a package named as 'Firmware_*.7z' which is a compressed ota.bin file that will be downloaded to the ota partition of the device. Update protocol: `com.qualcomm.mbim_qdu` ## Update method: firehose If the device supports the 'firehose' update method, it should have QCDM port exposed and the contents of the CAB file should contain 'firehose-rawprogram.xml'. The device is then switched to the emergency download mode (EDL) and flashed with files described in 'firehose-rawprogram.xml'. Update protocol: `com.qualcomm.firehose` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb` and `/dev/bus/pci`. ## Version Considerations This plugin has been available since fwupd version `1.2.6`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Aleksander Morgado: @aleksander0m fwupd-1.9.16/plugins/modem-manager/fu-firehose-updater.c000066400000000000000000000620651460375044200231670ustar00rootroot00000000000000/* * Copyright (C) 2020 Aleksander Morgado * Copyright (C) 2021 Ivan Mikhanchuk * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-firehose-updater.h" /* Maximum amount of non-"response" (e.g. "log") XML messages that can be * received from the module when expecting a "response". This is just a safe * upper limit to avoid reading forever. */ #define MAX_RECV_MESSAGES 100 /* When initializing the conversation with the firehose interpreter, the * first step is to receive and process a bunch of messages sent by the * module. The initial timeout to receive the first message is longer in case * the module needs some initialization time itself; all the messages after * the first one are expected to be received much quicker. The default timeout * value should not be extremely long because the initialization phase ends * when we don't receive more messages, so it's expected that the timeout will * fully elapse after the last message sent by the module. */ #define INITIALIZE_INITIAL_TIMEOUT_MS 3000 #define INITIALIZE_TIMEOUT_MS 250 /* Maximum amount of time to wait for a message from the module. */ #define DEFAULT_RECV_TIMEOUT_MS 15000 /* The first configure attempt sent to the module will include all the defaults * listed below. If the module replies with a NAK specifying a different * (shorter) max payload size to use, the second configure attempt will be done * with that new suggested max payload size value. Only 2 configure attempts are * therefore expected. */ #define MAX_CONFIGURE_ATTEMPTS 2 /* Defaults for the firehose configuration step. The max payload size to target * in bytes may end up being a different if the module requests a shorter one. */ #define CONFIGURE_MEMORY_NAME "nand" #define CONFIGURE_VERBOSE 0 #define CONFIGURE_ALWAYS_VALIDATE 0 #define CONFIGURE_MAX_DIGEST_TABLE_SIZE_IN_BYTES 2048 #define CONFIGURE_MAX_PAYLOAD_SIZE_TO_TARGET_IN_BYTES 8192 #define CONFIGURE_ZLP_AWARE_HOST 1 #define CONFIGURE_SKIP_STORAGE_INIT 0 struct _FuFirehoseUpdater { GObject parent_instance; gchar *port; FuSaharaLoader *sahara; FuIOChannel *io_channel; }; G_DEFINE_TYPE(FuFirehoseUpdater, fu_firehose_updater, G_TYPE_OBJECT) static void fu_firehose_updater_log_message(const gchar *action, GBytes *msg) { const gchar *msg_data; gsize msg_size; g_autofree gchar *msg_strsafe = NULL; msg_data = (const gchar *)g_bytes_get_data(msg, &msg_size); if (msg_size > G_MAXINT) return; msg_strsafe = fu_strsafe(msg_data, msg_size); g_debug("%s: %.*s", action, (gint)msg_size, msg_strsafe); } static GBytes * fu_firehose_read(FuFirehoseUpdater *self, guint timeout_ms, FuIOChannelFlags flags, GError **error) { if (self->sahara != NULL) { GByteArray *bytearr = fu_sahara_loader_qdl_read(self->sahara, error); return bytearr == NULL ? NULL : g_bytes_new(bytearr->data, bytearr->len); } return fu_io_channel_read_bytes(self->io_channel, -1, timeout_ms, flags, error); } static gboolean fu_firehose_write(FuFirehoseUpdater *self, GBytes *bytes, guint timeout_ms, FuIOChannelFlags flags, GError **error) { if (self->sahara != NULL) return fu_sahara_loader_qdl_write_bytes(self->sahara, bytes, error); return fu_io_channel_write_bytes(self->io_channel, bytes, timeout_ms, flags, error); } static gboolean validate_program_action(XbNode *program, FuArchive *archive, GError **error) { const gchar *filename_attr; gsize file_size; guint64 computed_num_partition_sectors; guint64 num_partition_sectors; guint64 sector_size_in_bytes; g_autoptr(GBytes) file = NULL; filename_attr = xb_node_get_attr(program, "filename"); if (filename_attr == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Missing 'filename' attribute in 'program' action"); return FALSE; } /* contents of the CAB file are flat, no subdirectories; look for the * exact filename */ file = fu_archive_lookup_by_fn(archive, filename_attr, error); if (file == NULL) return FALSE; file_size = g_bytes_get_size(file); num_partition_sectors = xb_node_get_attr_as_uint(program, "num_partition_sectors"); if (num_partition_sectors == G_MAXUINT64) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Missing 'num_partition_sectors' attribute in 'program' action for " "filename '%s'", filename_attr); return FALSE; } sector_size_in_bytes = xb_node_get_attr_as_uint(program, "SECTOR_SIZE_IN_BYTES"); if (sector_size_in_bytes == G_MAXUINT64) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Missing 'SECTOR_SIZE_IN_BYTES' attribute in 'program' action for " "filename '%s'", filename_attr); return FALSE; } computed_num_partition_sectors = file_size / sector_size_in_bytes; if ((file_size % sector_size_in_bytes) != 0) computed_num_partition_sectors++; if (computed_num_partition_sectors != num_partition_sectors) { g_set_error( error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid 'num_partition_sectors' in 'program' action for filename '%s': " "expected %" G_GUINT64_FORMAT " instead of %" G_GUINT64_FORMAT " bytes", filename_attr, computed_num_partition_sectors, num_partition_sectors); return FALSE; } xb_node_set_data(program, "fwupd:ProgramFile", file); return TRUE; } gboolean fu_firehose_validate_rawprogram(GBytes *rawprogram, FuArchive *archive, XbSilo **out_silo, GPtrArray **out_action_nodes, GError **error) { g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; g_autoptr(XbNode) data_node = NULL; g_autoptr(GPtrArray) action_nodes = NULL; if (!xb_builder_source_load_bytes(source, rawprogram, XB_BUILDER_SOURCE_FLAG_NONE, error)) return FALSE; xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; data_node = xb_silo_get_root(silo); action_nodes = xb_node_get_children(data_node); if (action_nodes == NULL || action_nodes->len == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "No actions given"); return FALSE; } for (guint i = 0; i < action_nodes->len; i++) { XbNode *n = g_ptr_array_index(action_nodes, i); if ((g_strcmp0(xb_node_get_element(n), "program") == 0) && !validate_program_action(n, archive, error)) { return FALSE; } } *out_silo = g_steal_pointer(&silo); *out_action_nodes = g_steal_pointer(&action_nodes); return TRUE; } gboolean fu_firehose_updater_open(FuFirehoseUpdater *self, GError **error) { if (fu_sahara_loader_qdl_is_open(self->sahara)) { g_debug("using sahara qdl port for firehose"); return TRUE; } /* sanity check */ if (self->port == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no firehose port provided for filename"); return FALSE; } g_debug("opening firehose port..."); if (self->port != NULL) { self->io_channel = fu_io_channel_new_file(self->port, error); return self->io_channel != NULL; } g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "No device to write firehose commands to"); return FALSE; } gboolean fu_firehose_updater_close(FuFirehoseUpdater *self, GError **error) { if (self->io_channel != NULL) { g_debug("closing firehose port..."); if (!fu_io_channel_shutdown(self->io_channel, error)) return FALSE; g_clear_object(&self->io_channel); } return TRUE; } static gboolean fu_firehose_updater_check_operation_result(XbNode *node, gboolean *out_rawmode) { g_warn_if_fail(g_strcmp0(xb_node_get_element(node), "response") == 0); if (g_strcmp0(xb_node_get_attr(node, "value"), "ACK") != 0) return FALSE; if (out_rawmode) *out_rawmode = (g_strcmp0(xb_node_get_attr(node, "rawmode"), "true") == 0); return TRUE; } static gboolean fu_firehose_updater_process_response(GBytes *rsp_bytes, XbSilo **out_silo, XbNode **out_response_node, GError **error) { g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; g_autoptr(XbNode) data_node = NULL; g_autoptr(GPtrArray) action_nodes = NULL; if (!xb_builder_source_load_bytes(source, rsp_bytes, XB_BUILDER_SOURCE_FLAG_NONE, error)) return FALSE; xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; data_node = xb_silo_get_root(silo); if (data_node == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Missing root data node"); return FALSE; } action_nodes = xb_node_get_children(data_node); if (action_nodes != NULL) { for (guint j = 0; j < action_nodes->len; j++) { XbNode *node = g_ptr_array_index(action_nodes, j); if (g_strcmp0(xb_node_get_element(node), "response") == 0) { if (out_silo) *out_silo = g_steal_pointer(&silo); if (out_response_node) *out_response_node = g_object_ref(node); return TRUE; } if (g_strcmp0(xb_node_get_element(node), "log") == 0) { const gchar *value_attr = xb_node_get_attr(node, "value"); if (value_attr) g_debug("device log: %s", value_attr); } } } if (out_silo != NULL) *out_silo = NULL; if (out_response_node != NULL) *out_response_node = NULL; return TRUE; } static gboolean fu_firehose_updater_send_and_receive(FuFirehoseUpdater *self, GByteArray *take_cmd_bytearray, XbSilo **out_silo, XbNode **out_response_node, GError **error) { if (take_cmd_bytearray) { const gchar *cmd_header = "\n\n"; const gchar *cmd_trailer = ""; g_autoptr(GBytes) cmd_bytes = NULL; g_byte_array_prepend(take_cmd_bytearray, (const guint8 *)cmd_header, strlen(cmd_header)); g_byte_array_append(take_cmd_bytearray, (const guint8 *)cmd_trailer, strlen(cmd_trailer)); cmd_bytes = g_bytes_new(take_cmd_bytearray->data, take_cmd_bytearray->len); fu_firehose_updater_log_message("writing", cmd_bytes); if (!fu_firehose_write(self, cmd_bytes, 1500, FU_IO_CHANNEL_FLAG_FLUSH_INPUT, error)) { g_prefix_error(error, "Failed to write command: "); return FALSE; } } for (guint i = 0; i < MAX_RECV_MESSAGES; i++) { g_autoptr(GBytes) rsp_bytes = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbNode) response_node = NULL; rsp_bytes = fu_firehose_read(self, DEFAULT_RECV_TIMEOUT_MS, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error); if (rsp_bytes == NULL) { g_prefix_error(error, "Failed to read XML message: "); return FALSE; } fu_firehose_updater_log_message("reading", rsp_bytes); if (!fu_firehose_updater_process_response(rsp_bytes, &silo, &response_node, error)) { g_prefix_error(error, "Failed to parse XML message: "); return FALSE; } if (silo != NULL && response_node != NULL) { *out_silo = g_steal_pointer(&silo); *out_response_node = g_steal_pointer(&response_node); return TRUE; } /* continue until we get a 'response_node' */ } g_set_error(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "Didn't get any response in the last %d messages", MAX_RECV_MESSAGES); return FALSE; } static gboolean fu_firehose_updater_initialize(FuFirehoseUpdater *self, GError **error) { guint n_msg = 0; for (guint i = 0; i < MAX_RECV_MESSAGES; i++) { g_autoptr(GBytes) rsp_bytes = NULL; guint timeout = (i == 0 ? INITIALIZE_INITIAL_TIMEOUT_MS : INITIALIZE_TIMEOUT_MS); rsp_bytes = fu_firehose_read(self, timeout, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, NULL); if (rsp_bytes == NULL) break; fu_firehose_updater_log_message("reading", rsp_bytes); if (!fu_firehose_updater_process_response(rsp_bytes, NULL, NULL, error)) { g_prefix_error(error, "Failed to parse XML message: "); return FALSE; } n_msg++; } if (n_msg == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Couldn't read initial firehose messages from device"); return FALSE; } return TRUE; } static guint fu_firehose_updater_configure(FuFirehoseUpdater *self, GError **error) { gint max_payload_size = CONFIGURE_MAX_PAYLOAD_SIZE_TO_TARGET_IN_BYTES; for (guint i = 0; i < MAX_CONFIGURE_ATTEMPTS; i++) { GByteArray *cmd_bytearray = NULL; g_autoptr(XbSilo) rsp_silo = NULL; g_autoptr(XbNode) rsp_node = NULL; GString *cmd_str = g_string_new(NULL); g_string_append_printf(cmd_str, ""); cmd_bytearray = g_bytes_unref_to_array(g_string_free_to_bytes(cmd_str)); if (!fu_firehose_updater_send_and_receive(self, cmd_bytearray, &rsp_silo, &rsp_node, error)) { g_prefix_error(error, "Failed to run configure command: "); return 0; } /* retry if we're told to use a different max payload size */ if (!fu_firehose_updater_check_operation_result(rsp_node, NULL)) { guint64 suggested_max_payload_size; g_autoptr(XbNode) root = NULL; root = xb_silo_get_root(rsp_silo); suggested_max_payload_size = xb_node_get_attr_as_uint(root, "MaxPayloadSizeToTargetInBytes"); if ((suggested_max_payload_size > G_MAXINT) || ((gint)suggested_max_payload_size == max_payload_size)) { break; } suggested_max_payload_size = max_payload_size; continue; } /* if operation is successful, return the max payload size we requested */ return max_payload_size; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Configure operation failed"); return 0; } static gboolean fu_firehose_updater_reset(FuFirehoseUpdater *self, GError **error) { guint recv_cnt = 20; const gchar *cmd_str = ""; GByteArray *cmd_bytearray = NULL; g_autoptr(XbSilo) rsp_silo = NULL; g_autoptr(XbNode) rsp_node = NULL; cmd_bytearray = g_byte_array_append(g_byte_array_new(), (const guint8 *)cmd_str, strlen(cmd_str)); if (!fu_firehose_updater_send_and_receive(self, cmd_bytearray, &rsp_silo, &rsp_node, error)) { g_prefix_error(error, "Failed to run reset command: "); return FALSE; } if (!fu_firehose_updater_check_operation_result(rsp_node, NULL)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Reset operation failed"); return FALSE; } /* read out all of the remaining messages. otherwise modem won't go into reset */ while (--recv_cnt && fu_firehose_updater_send_and_receive(self, NULL, &rsp_silo, &rsp_node, NULL)) ; g_warn_if_fail(recv_cnt > 0); return TRUE; } static gboolean fu_firehose_updater_send_program_file(FuFirehoseUpdater *self, const gchar *program_filename, GBytes *program_file, gsize payload_size, gsize sector_size, GError **error) { g_autoptr(FuChunk) chk_last = NULL; g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(program_file, 0, payload_size); /* last block needs to be padded to the next sector_size, * so that we always send full sectors */ chk_last = fu_chunk_array_index(chunks, fu_chunk_array_length(chunks) - 1); if ((fu_chunk_get_data_sz(chk_last) % sector_size) != 0) { g_autoptr(GBytes) padded_bytes = NULL; gsize padded_sz = sector_size * (fu_chunk_get_data_sz(chk_last) / sector_size + 1); padded_bytes = fu_bytes_pad(fu_chunk_get_bytes(chk_last), padded_sz); fu_chunk_set_bytes(chk_last, padded_bytes); g_return_val_if_fail(fu_chunk_get_data_sz(chk_last) == padded_sz, FALSE); } for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); /* log only in blocks of 250 plus first/last */ if (i == 0 || i == (fu_chunk_array_length(chunks) - 1) || (i + 1) % 250 == 0) g_debug("sending %u bytes in block %u/%u of file '%s'", fu_chunk_get_data_sz(chk), i + 1, fu_chunk_array_length(chunks), program_filename); if (!fu_firehose_write(self, fu_chunk_get_bytes(chk), 1500, FU_IO_CHANNEL_FLAG_FLUSH_INPUT, error)) { g_prefix_error(error, "Failed to write block %u/%u of file '%s': ", i + 1, fu_chunk_array_length(chunks), program_filename); return FALSE; } } return TRUE; } static gboolean fu_firehose_updater_actions_validate(GPtrArray *action_nodes, guint max_payload_size, GError **error) { g_return_val_if_fail(action_nodes != NULL, FALSE); for (guint i = 0; i < action_nodes->len; i++) { const gchar *name = NULL; const gchar *program_filename = NULL; GBytes *program_file = NULL; guint64 program_sector_size_in_bytes = 0; XbNode *node = g_ptr_array_index(action_nodes, i); const gchar *action = xb_node_get_element(node); if (g_strcmp0(action, "program") != 0) continue; name = "fwupd:ProgramFile"; program_file = xb_node_get_data(node, name); if (program_file == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to validate program file: failed to get %s", name); return FALSE; } name = "filename"; program_filename = xb_node_get_attr(node, name); if (program_filename == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to validate program file: failed to get %s", name); return FALSE; } name = "SECTOR_SIZE_IN_BYTES"; program_sector_size_in_bytes = xb_node_get_attr_as_uint(node, name); if (program_sector_size_in_bytes > max_payload_size) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to validate program file '%s' command: " "requested sector size bigger (%" G_GUINT64_FORMAT " bytes) " "than maximum payload size agreed with device (%u bytes)", program_filename, program_sector_size_in_bytes, max_payload_size); return FALSE; } } return TRUE; } static gsize fu_firehose_updater_actions_get_total_file_size(GPtrArray *action_nodes) { gsize total_bytes = 0; g_return_val_if_fail(action_nodes != NULL, 0); for (guint i = 0; i < action_nodes->len; i++) { GBytes *program_file = NULL; XbNode *node = g_ptr_array_index(action_nodes, i); const gchar *action = xb_node_get_element(node); if (g_strcmp0(action, "program") != 0) continue; program_file = xb_node_get_data(node, "fwupd:ProgramFile"); if (program_file != NULL) total_bytes += g_bytes_get_size(program_file); } return total_bytes; } static gboolean fu_firehose_updater_run_action_program(FuFirehoseUpdater *self, XbNode *node, gboolean rawmode, guint max_payload_size, gsize *sent_bytes, GError **error) { GBytes *program_file = NULL; const gchar *program_filename = NULL; guint64 program_sector_size = 0; guint payload_size = 0; g_autoptr(XbSilo) rsp_silo = NULL; g_autoptr(XbNode) rsp_node = NULL; program_file = xb_node_get_data(node, "fwupd:ProgramFile"); if (program_file == NULL) return FALSE; program_filename = xb_node_get_attr(node, "filename"); if (program_filename == NULL) return FALSE; program_sector_size = xb_node_get_attr_as_uint(node, "SECTOR_SIZE_IN_BYTES"); if (program_sector_size == G_MAXUINT64) return FALSE; if (rawmode == FALSE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to download program file '%s': rawmode not enabled", program_filename); return FALSE; } while ((payload_size + (guint)program_sector_size) <= max_payload_size) payload_size += (guint)program_sector_size; if (payload_size == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "payload size invalid"); return FALSE; } g_debug("sending program file '%s' (0x%x bytes)", program_filename, (guint)g_bytes_get_size(program_file)); if (!fu_firehose_updater_send_program_file(self, program_filename, program_file, payload_size, program_sector_size, error)) { g_prefix_error(error, "Failed to send program file '%s': ", program_filename); return FALSE; } g_debug("waiting for program file download confirmation..."); if (!fu_firehose_updater_send_and_receive(self, NULL, &rsp_silo, &rsp_node, error)) { g_prefix_error(error, "Download confirmation not received for file '%s': ", program_filename); return FALSE; } if (!fu_firehose_updater_check_operation_result(rsp_node, &rawmode)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Download confirmation failed for file '%s'", program_filename); return FALSE; } if (rawmode != FALSE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Download confirmation failed for file '%s': rawmode still enabled", program_filename); return FALSE; } if (sent_bytes != NULL) *sent_bytes += g_bytes_get_size(program_file); return TRUE; } static gboolean fu_firehose_updater_run_action(FuFirehoseUpdater *self, XbNode *node, guint max_payload_size, gsize *sent_bytes, GError **error) { const gchar *action; gchar *cmd_str = NULL; gboolean rawmode = FALSE; GByteArray *cmd_bytearray = NULL; g_autoptr(XbSilo) rsp_silo = NULL; g_autoptr(XbNode) rsp_node = NULL; action = xb_node_get_element(node); cmd_str = xb_node_export(node, XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY, error); if (cmd_str == NULL) return FALSE; cmd_bytearray = g_byte_array_new_take((guint8 *)cmd_str, strlen(cmd_str)); g_debug("running command '%s'...", action); if (!fu_firehose_updater_send_and_receive(self, cmd_bytearray, &rsp_silo, &rsp_node, error)) { g_prefix_error(error, "Failed to run command '%s': ", action); return FALSE; } if (!fu_firehose_updater_check_operation_result(rsp_node, &rawmode)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Command '%s' failed", action); return FALSE; } if (g_strcmp0(action, "program") == 0) return fu_firehose_updater_run_action_program(self, node, rawmode, max_payload_size, sent_bytes, error); return TRUE; } static gboolean fu_firehose_updater_run_actions(FuFirehoseUpdater *self, XbSilo *silo, GPtrArray *action_nodes, guint max_payload_size, FuProgress *progress, GError **error) { gsize sent_bytes = 0; gsize total_bytes = 0; g_warn_if_fail(action_nodes->len > 0); if (!fu_firehose_updater_actions_validate(action_nodes, max_payload_size, error)) return FALSE; total_bytes = fu_firehose_updater_actions_get_total_file_size(action_nodes); for (guint i = 0; i < action_nodes->len; i++) { XbNode *node = g_ptr_array_index(action_nodes, i); if (!fu_firehose_updater_run_action(self, node, max_payload_size, &sent_bytes, error)) return FALSE; fu_progress_set_percentage_full(progress, sent_bytes, total_bytes); } return TRUE; } gboolean fu_firehose_updater_write(FuFirehoseUpdater *self, XbSilo *silo, GPtrArray *action_nodes, FuProgress *progress, GError **error) { guint max_payload_size; gboolean result; g_autoptr(GError) error_local = NULL; if (!fu_firehose_updater_initialize(self, error)) return FALSE; max_payload_size = fu_firehose_updater_configure(self, error); if (max_payload_size == 0) return FALSE; result = fu_firehose_updater_run_actions(self, silo, action_nodes, max_payload_size, progress, error); if (!fu_firehose_updater_reset(self, &error_local)) { if (result) g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } return result; } static void fu_firehose_updater_init(FuFirehoseUpdater *self) { } static void fu_firehose_updater_finalize(GObject *object) { FuFirehoseUpdater *self = FU_FIREHOSE_UPDATER(object); g_warn_if_fail(self->io_channel == NULL); g_free(self->port); g_object_unref(self->sahara); G_OBJECT_CLASS(fu_firehose_updater_parent_class)->finalize(object); } static void fu_firehose_updater_class_init(FuFirehoseUpdaterClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_firehose_updater_finalize; } FuFirehoseUpdater * fu_firehose_updater_new(const gchar *port, FuSaharaLoader *sahara) { FuFirehoseUpdater *self = g_object_new(FU_TYPE_FIREHOSE_UPDATER, NULL); if (port != NULL) self->port = g_strdup(port); if (sahara != NULL) self->sahara = g_object_ref(sahara); return self; } fwupd-1.9.16/plugins/modem-manager/fu-firehose-updater.h000066400000000000000000000017421460375044200231670ustar00rootroot00000000000000/* * Copyright (C) 2020 Aleksander Morgado * Copyright (C) 2021 Ivan Mikhanchuk * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-sahara-loader.h" #define FU_TYPE_FIREHOSE_UPDATER (fu_firehose_updater_get_type()) G_DECLARE_FINAL_TYPE(FuFirehoseUpdater, fu_firehose_updater, FU, FIREHOSE_UPDATER, GObject) FuFirehoseUpdater * fu_firehose_updater_new(const gchar *port, FuSaharaLoader *sahara); gboolean fu_firehose_updater_open(FuFirehoseUpdater *self, GError **error); gboolean fu_firehose_updater_write(FuFirehoseUpdater *self, XbSilo *silo, GPtrArray *action_nodes, FuProgress *progress, GError **error); gboolean fu_firehose_updater_close(FuFirehoseUpdater *self, GError **error); /* helpers */ gboolean fu_firehose_validate_rawprogram(GBytes *rawprogram, FuArchive *archive, XbSilo **out_silo, GPtrArray **out_action_nodes, GError **error); fwupd-1.9.16/plugins/modem-manager/fu-mbim-qdu-updater.c000066400000000000000000000313611460375044200230710ustar00rootroot00000000000000/* * Copyright (C) 2021 Jarvis Jiang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-mbim-qdu-updater.h" #include "fu-mm-utils.h" #define FU_MBIM_QDU_MAX_OPEN_ATTEMPTS 8 struct _FuMbimQduUpdater { GObject parent_instance; gchar *mbim_port; MbimDevice *mbim_device; }; G_DEFINE_TYPE(FuMbimQduUpdater, fu_mbim_qdu_updater, G_TYPE_OBJECT) typedef struct { GMainLoop *mainloop; MbimDevice *mbim_device; GError *error; guint open_attempts; } OpenContext; static void fu_mbim_qdu_updater_mbim_device_open_attempt(OpenContext *ctx); static void fu_mbim_qdu_updater_mbim_device_open_ready(GObject *mbim_device, GAsyncResult *res, gpointer user_data) { OpenContext *ctx = (OpenContext *)user_data; g_assert(ctx->open_attempts > 0); if (!mbim_device_open_full_finish(MBIM_DEVICE(mbim_device), res, &ctx->error)) { ctx->open_attempts--; if (ctx->open_attempts == 0) { g_clear_object(&ctx->mbim_device); g_main_loop_quit(ctx->mainloop); return; } /* retry */ g_debug("couldn't open mbim device: %s", ctx->error->message); g_clear_error(&ctx->error); fu_mbim_qdu_updater_mbim_device_open_attempt(ctx); return; } g_main_loop_quit(ctx->mainloop); } static void fu_mbim_qdu_updater_mbim_device_open_attempt(OpenContext *ctx) { /* all communication through the proxy */ MbimDeviceOpenFlags open_flags = MBIM_DEVICE_OPEN_FLAGS_PROXY; g_debug("trying to open MBIM device..."); mbim_device_open_full(ctx->mbim_device, open_flags, 10, NULL, fu_mbim_qdu_updater_mbim_device_open_ready, ctx); } static void fu_mbim_qdu_updater_mbim_device_new_ready(GObject *source, GAsyncResult *res, gpointer user_data) { OpenContext *ctx = (OpenContext *)user_data; ctx->mbim_device = mbim_device_new_finish(res, &ctx->error); if (ctx->mbim_device == NULL) { g_main_loop_quit(ctx->mainloop); return; } fu_mbim_qdu_updater_mbim_device_open_attempt(ctx); } gboolean fu_mbim_qdu_updater_open(FuMbimQduUpdater *self, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new(NULL, FALSE); g_autoptr(GFile) mbim_device_file = g_file_new_for_path(self->mbim_port); OpenContext ctx = { .mainloop = mainloop, .mbim_device = NULL, .error = NULL, .open_attempts = FU_MBIM_QDU_MAX_OPEN_ATTEMPTS, }; mbim_device_new(mbim_device_file, NULL, fu_mbim_qdu_updater_mbim_device_new_ready, &ctx); g_main_loop_run(mainloop); /* either we have all device or otherwise error is set */ if (ctx.mbim_device != NULL) { g_warn_if_fail(ctx.error == NULL); self->mbim_device = ctx.mbim_device; /* success */ return TRUE; } g_warn_if_fail(ctx.error != NULL); g_warn_if_fail(ctx.mbim_device == NULL); g_propagate_error(error, ctx.error); return FALSE; } typedef struct { GMainLoop *mainloop; MbimDevice *mbim_device; GError *error; } CloseContext; static void fu_mbim_qdu_updater_mbim_device_close_ready(GObject *mbim_device, GAsyncResult *res, gpointer user_data) { CloseContext *ctx = (CloseContext *)user_data; /* ignore errors when closing */ mbim_device_close_finish(MBIM_DEVICE(mbim_device), res, &ctx->error); g_clear_object(&ctx->mbim_device); g_main_loop_quit(ctx->mainloop); } gboolean fu_mbim_qdu_updater_close(FuMbimQduUpdater *self, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new(NULL, FALSE); CloseContext ctx = { .mainloop = mainloop, .mbim_device = g_steal_pointer(&self->mbim_device), .error = NULL, }; if (ctx.mbim_device == NULL) return TRUE; mbim_device_close(ctx.mbim_device, 5, NULL, fu_mbim_qdu_updater_mbim_device_close_ready, &ctx); g_main_loop_run(mainloop); /* we should always have both device cleared, and optionally error set */ g_warn_if_fail(ctx.mbim_device == NULL); if (ctx.error != NULL) { g_propagate_error(error, ctx.error); return FALSE; } /* update attach right after this */ return TRUE; } typedef struct { GMainLoop *mainloop; GError *error; gchar *firmware_version; } GetFirmwareVersionContext; static void fu_mbim_qdu_updater_caps_query_ready(MbimDevice *device, GAsyncResult *res, gpointer user_data) { GetFirmwareVersionContext *ctx = user_data; g_autofree gchar *firmware_version = NULL; g_autoptr(MbimMessage) response = NULL; response = mbim_device_command_finish(device, res, &ctx->error); if (!response || !mbim_message_response_get_result(response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &ctx->error)) { g_debug("operation failed: %s", ctx->error->message); g_main_loop_quit(ctx->mainloop); return; } if (!mbim_message_device_caps_response_parse(response, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &firmware_version, NULL, &ctx->error)) { g_debug("couldn't parse response message: %s", ctx->error->message); g_main_loop_quit(ctx->mainloop); return; } g_debug("[%s] Successfully request modem to query caps", mbim_device_get_path_display(device)); ctx->firmware_version = g_strdup(firmware_version); g_main_loop_quit(ctx->mainloop); } static void fu_mbim_qdu_updater_caps_query(MbimDevice *device, GetFirmwareVersionContext *ctx) { g_autoptr(MbimMessage) request = NULL; request = mbim_message_device_caps_query_new(NULL); mbim_device_command(device, request, 10, NULL, (GAsyncReadyCallback)fu_mbim_qdu_updater_caps_query_ready, ctx); } gchar * fu_mbim_qdu_updater_check_ready(FuMbimQduUpdater *self, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new(NULL, FALSE); GetFirmwareVersionContext ctx = { .mainloop = mainloop, .error = NULL, .firmware_version = NULL, }; fu_mbim_qdu_updater_caps_query(self->mbim_device, &ctx); g_main_loop_run(mainloop); if (ctx.error != NULL) { g_propagate_error(error, ctx.error); return NULL; } return ctx.firmware_version; } typedef struct { GMainLoop *mainloop; MbimDevice *mbim_device; GError *error; GBytes *blob; GArray *digest; FuChunkArray *chunks; guint chunk_sent; FuDevice *device; FuProgress *progress; } WriteContext; static void fu_mbim_qdu_updater_file_write_ready(MbimDevice *device, GAsyncResult *res, gpointer user_data) { WriteContext *ctx = user_data; g_autoptr(MbimMessage) response = NULL; response = mbim_device_command_finish(device, res, &ctx->error); if (!response || !mbim_message_response_get_result(response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &ctx->error)) { g_debug("operation failed: %s", ctx->error->message); g_object_unref(ctx->chunks); g_main_loop_quit(ctx->mainloop); return; } if (!mbim_message_qdu_file_write_response_parse(response, &ctx->error)) { g_debug("couldn't parse response message: %s", ctx->error->message); g_object_unref(ctx->chunks); g_main_loop_quit(ctx->mainloop); return; } ctx->chunk_sent++; fu_progress_set_percentage_full(ctx->progress, (gsize)ctx->chunk_sent, (gsize)fu_chunk_array_length(ctx->chunks)); if (ctx->chunk_sent < fu_chunk_array_length(ctx->chunks)) { g_autoptr(FuChunk) chk = fu_chunk_array_index(ctx->chunks, ctx->chunk_sent); g_autoptr(MbimMessage) request = mbim_message_qdu_file_write_set_new(fu_chunk_get_data_sz(chk), (const guint8 *)fu_chunk_get_data(chk), NULL); mbim_device_command(ctx->mbim_device, request, 20, NULL, (GAsyncReadyCallback)fu_mbim_qdu_updater_file_write_ready, ctx); return; } g_object_unref(ctx->chunks); g_main_loop_quit(ctx->mainloop); } static void fu_mbim_qdu_updater_file_open_ready(MbimDevice *device, GAsyncResult *res, gpointer user_data) { WriteContext *ctx = user_data; guint32 out_max_transfer_size; g_autoptr(FuChunk) chk = NULL; g_autoptr(MbimMessage) request = NULL; g_autoptr(MbimMessage) response = NULL; response = mbim_device_command_finish(device, res, &ctx->error); if (!response || !mbim_message_response_get_result(response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &ctx->error)) { g_debug("operation failed: %s", ctx->error->message); g_main_loop_quit(ctx->mainloop); return; } if (!mbim_message_qdu_file_open_response_parse(response, &out_max_transfer_size, NULL, &ctx->error)) { g_debug("couldn't parse response message: %s", ctx->error->message); g_main_loop_quit(ctx->mainloop); return; } ctx->chunks = fu_chunk_array_new_from_bytes(ctx->blob, 0x00, out_max_transfer_size); chk = fu_chunk_array_index(ctx->chunks, 0); request = mbim_message_qdu_file_write_set_new(fu_chunk_get_data_sz(chk), (const guint8 *)fu_chunk_get_data(chk), NULL); mbim_device_command(ctx->mbim_device, request, 10, NULL, (GAsyncReadyCallback)fu_mbim_qdu_updater_file_write_ready, ctx); } static void fu_mbim_qdu_updater_session_ready(MbimDevice *device, GAsyncResult *res, gpointer user_data) { WriteContext *ctx = user_data; g_autoptr(MbimMessage) response = NULL; g_autoptr(MbimMessage) request = NULL; response = mbim_device_command_finish(device, res, &ctx->error); if (!response || !mbim_message_response_get_result(response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &ctx->error)) { g_debug("operation failed: %s", ctx->error->message); g_main_loop_quit(ctx->mainloop); return; } if (!mbim_message_qdu_update_session_response_parse(response, NULL, NULL, NULL, NULL, NULL, NULL, &ctx->error)) { g_debug("couldn't parse response message: %s", ctx->error->message); g_main_loop_quit(ctx->mainloop); return; } g_debug("[%s] Successfully request modem to update session", mbim_device_get_path_display(device)); request = mbim_message_qdu_file_open_set_new(MBIM_QDU_FILE_TYPE_LITTLE_ENDIAN_PACKAGE, g_bytes_get_size(ctx->blob), NULL); mbim_device_command(device, request, 10, NULL, (GAsyncReadyCallback)fu_mbim_qdu_updater_file_open_ready, ctx); } static void fu_mbim_qdu_updater_set_update_session(MbimDevice *device, WriteContext *ctx) { g_autoptr(MbimMessage) request = NULL; request = mbim_message_qdu_update_session_set_new(MBIM_QDU_SESSION_ACTION_START, MBIM_QDU_SESSION_TYPE_LE, NULL); mbim_device_command(device, request, 10, NULL, (GAsyncReadyCallback)fu_mbim_qdu_updater_session_ready, ctx); } static GArray * fu_mbim_qdu_updater_get_checksum(GBytes *blob) { gsize file_size; gsize hash_size; GArray *digest; g_autoptr(GChecksum) checksum = NULL; /* get checksum, to be used as unique id */ file_size = g_bytes_get_size(blob); hash_size = g_checksum_type_get_length(G_CHECKSUM_SHA256); checksum = g_checksum_new(G_CHECKSUM_SHA256); g_checksum_update(checksum, g_bytes_get_data(blob, NULL), file_size); /* libqmi expects a GArray of bytes, not a GByteArray */ digest = g_array_sized_new(FALSE, FALSE, sizeof(guint8), hash_size); g_array_set_size(digest, hash_size); g_checksum_get_digest(checksum, (guint8 *)digest->data, &hash_size); return digest; } GArray * fu_mbim_qdu_updater_write(FuMbimQduUpdater *self, const gchar *filename, GBytes *blob, FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new(NULL, FALSE); g_autoptr(GArray) digest = fu_mbim_qdu_updater_get_checksum(blob); g_autoptr(FuChunkArray) chunks = NULL; WriteContext ctx = { .mainloop = mainloop, .mbim_device = self->mbim_device, .error = NULL, .blob = blob, .digest = digest, .chunks = chunks, .chunk_sent = 0, .device = device, .progress = progress, }; fu_mbim_qdu_updater_set_update_session(self->mbim_device, &ctx); g_main_loop_run(mainloop); if (ctx.error != NULL) { g_propagate_error(error, ctx.error); return NULL; } return g_steal_pointer(&digest); } MbimDevice * fu_mbim_qdu_updater_get_mbim_device(FuMbimQduUpdater *self) { return self->mbim_device; } static void fu_mbim_qdu_updater_init(FuMbimQduUpdater *self) { } static void fu_mbim_qdu_updater_finalize(GObject *object) { FuMbimQduUpdater *self = FU_MBIM_QDU_UPDATER(object); g_warn_if_fail(self->mbim_device == NULL); g_free(self->mbim_port); G_OBJECT_CLASS(fu_mbim_qdu_updater_parent_class)->finalize(object); } static void fu_mbim_qdu_updater_class_init(FuMbimQduUpdaterClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_mbim_qdu_updater_finalize; } FuMbimQduUpdater * fu_mbim_qdu_updater_new(const gchar *mbim_port) { FuMbimQduUpdater *self = g_object_new(FU_TYPE_MBIM_QDU_UPDATER, NULL); self->mbim_port = g_strdup(mbim_port); return self; } fwupd-1.9.16/plugins/modem-manager/fu-mbim-qdu-updater.h000066400000000000000000000015711460375044200230760ustar00rootroot00000000000000/* * Copyright (C) 2021 Jarvis Jiang * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-mm-device.h" #define FU_TYPE_MBIM_QDU_UPDATER (fu_mbim_qdu_updater_get_type()) G_DECLARE_FINAL_TYPE(FuMbimQduUpdater, fu_mbim_qdu_updater, FU, MBIM_QDU_UPDATER, GObject) FuMbimQduUpdater * fu_mbim_qdu_updater_new(const gchar *mbim_port); gboolean fu_mbim_qdu_updater_open(FuMbimQduUpdater *self, GError **error); GArray * fu_mbim_qdu_updater_write(FuMbimQduUpdater *self, const gchar *filename, GBytes *blob, FuDevice *device, FuProgress *progress, GError **error); gchar * fu_mbim_qdu_updater_check_ready(FuMbimQduUpdater *self, GError **error); gboolean fu_mbim_qdu_updater_close(FuMbimQduUpdater *self, GError **error); MbimDevice * fu_mbim_qdu_updater_get_mbim_device(FuMbimQduUpdater *self); fwupd-1.9.16/plugins/modem-manager/fu-mm-device.c000066400000000000000000001730351460375044200215670ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-firehose-updater.h" #include "fu-mbim-qdu-updater.h" #include "fu-mm-device.h" #include "fu-mm-utils.h" #include "fu-qmi-pdc-updater.h" #include "fu-sahara-loader.h" /* Amount of time for the modem to boot in fastboot mode. */ #define FU_MM_DEVICE_REMOVE_DELAY_RE_ENUMERATE 20000 /* ms */ /* Amount of time for the modem to be re-probed and exposed in MM after being * uninhibited. The timeout is long enough to cover the worst case, where the * modem boots without SIM card inserted (and therefore the initialization * may be very slow) and also where carrier config switching is explicitly * required (e.g. if switching from the default (DF) to generic (GC).*/ #define FU_MM_DEVICE_REMOVE_DELAY_REPROBE 210000 /* ms */ #define FU_MM_DEVICE_AT_RETRIES 3 #define FU_MM_DEVICE_AT_DELAY 3000 /* ms */ /* Amount of time for the modem to get firmware version */ #define MAX_WAIT_TIME_SECS 240 /* s */ struct _FuMmDevice { FuDevice parent_instance; MMManager *manager; /* ModemManager-based devices will have MMObject and inhibition_uid set, * udev-based ones won't (as device is already inhibited) */ MMObject *omodem; gchar *inhibition_uid; /* Properties read from the ModemManager-exposed modem, and to be * propagated to plain udev-exposed modem objects. We assume that * the firmware upgrade operation doesn't change the USB layout, and * therefore the USB interface of the modem device that was an * AT-capable TTY is assumed to be the same one after the upgrade. */ MMModemFirmwareUpdateMethod update_methods; gchar *detach_fastboot_at; gchar *branch_at; gint port_at_ifnum; gint port_qmi_ifnum; gint port_mbim_ifnum; /* fastboot detach handling */ gchar *port_at; FuIOChannel *io_channel; /* qmi-pdc update logic */ gchar *port_qmi; FuQmiPdcUpdater *qmi_pdc_updater; GArray *qmi_pdc_active_id; guint attach_idle; /* mbim-qdu update logic */ gchar *port_mbim; FuMbimQduUpdater *mbim_qdu_updater; /* firehose update handling */ gchar *port_qcdm; gchar *port_edl; gchar *firehose_prog_file; FuSaharaLoader *sahara_loader; FuFirehoseUpdater *firehose_updater; /* for sahara */ FuUsbDevice *usb_device; /* firmware path */ gchar *firmware_path; gchar *restore_firmware_path; }; enum { SIGNAL_ATTACH_FINISHED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE(FuMmDevice, fu_mm_device, FU_TYPE_DEVICE) static void fu_mm_device_to_string(FuDevice *device, guint idt, GString *str) { FuMmDevice *self = FU_MM_DEVICE(device); if (self->port_at != NULL) fu_string_append(str, idt, "AtPort", self->port_at); if (self->port_qmi != NULL) fu_string_append(str, idt, "QmiPort", self->port_qmi); if (self->port_mbim != NULL) fu_string_append(str, idt, "MbimPort", self->port_mbim); if (self->port_qcdm != NULL) fu_string_append(str, idt, "QcdmPort", self->port_qcdm); } const gchar * fu_mm_device_get_inhibition_uid(FuMmDevice *device) { g_return_val_if_fail(FU_IS_MM_DEVICE(device), NULL); return device->inhibition_uid; } MMModemFirmwareUpdateMethod fu_mm_device_get_update_methods(FuMmDevice *device) { g_return_val_if_fail(FU_IS_MM_DEVICE(device), MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE); return device->update_methods; } static gboolean validate_firmware_update_method(MMModemFirmwareUpdateMethod methods, GError **error) { static const MMModemFirmwareUpdateMethod supported_combinations[] = { MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT, MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC | MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT, MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU, MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE, #if MM_CHECK_VERSION(1, 19, 1) MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE | MM_MODEM_FIRMWARE_UPDATE_METHOD_SAHARA, #endif }; g_autofree gchar *methods_str = NULL; methods_str = mm_modem_firmware_update_method_build_string_from_mask(methods); for (guint i = 0; i < G_N_ELEMENTS(supported_combinations); i++) { if (supported_combinations[i] == methods) { g_info("valid firmware update combination: %s", methods_str); return TRUE; } } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid firmware update combination: %s", methods_str); return FALSE; } static void fu_mm_device_add_instance_id(FuDevice *dev, const gchar *device_id) { if (g_pattern_match_simple("???\\VID_????", device_id)) { fu_device_add_instance_id_full(dev, device_id, FU_DEVICE_INSTANCE_FLAG_QUIRKS); return; } if (g_pattern_match_simple("???\\VID_????&PID_????", device_id)) { fu_device_add_instance_id_full(dev, device_id, FU_DEVICE_INSTANCE_FLAG_QUIRKS); return; } if (g_pattern_match_simple("???\\VID_????&PID_????&REV_????", device_id)) { if (fu_device_has_internal_flag(dev, FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV)) fu_device_add_instance_id(dev, device_id); return; } if (g_pattern_match_simple("???\\VID_????&PID_????&REV_????&CARRIER_*", device_id)) { if (!fu_device_has_private_flag(dev, FU_MM_DEVICE_FLAG_USE_BRANCH)) fu_device_add_instance_id(dev, device_id); return; } g_warning("failed to add instance ID %s", device_id); } static gboolean fu_mm_device_probe_default(FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); MMModemFirmware *modem_fw; MMModem *modem = mm_object_peek_modem(self->omodem); MMModemPortInfo *ports = NULL; const gchar **device_ids; const gchar *version; guint n_ports = 0; GPtrArray *vendors; g_autoptr(MMFirmwareUpdateSettings) update_settings = NULL; g_autofree gchar *device_sysfs_path = NULL; g_autofree gchar *device_bus = NULL; /* inhibition uid is the modem interface 'Device' property, which may * be the device sysfs path or a different user-provided id */ self->inhibition_uid = mm_modem_dup_device(modem); /* find out what update methods we should use */ modem_fw = mm_object_peek_modem_firmware(self->omodem); update_settings = mm_modem_firmware_get_update_settings(modem_fw); self->update_methods = mm_firmware_update_settings_get_method(update_settings); if (self->update_methods == MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "modem cannot be put in programming mode"); return FALSE; } /* make sure the combination is supported */ if (!validate_firmware_update_method(self->update_methods, error)) return FALSE; /* various fastboot commands */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) { const gchar *tmp; tmp = mm_firmware_update_settings_get_fastboot_at(update_settings); if (tmp == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "modem does not set fastboot command"); return FALSE; } self->detach_fastboot_at = g_strdup(tmp); } /* get GUIDs */ device_ids = mm_firmware_update_settings_get_device_ids(update_settings); if (device_ids == NULL || device_ids[0] == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "modem did not specify any device IDs"); return FALSE; } /* get version string, which is fw_ver+config_ver */ version = mm_firmware_update_settings_get_version(update_settings); if (version == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "modem did not specify a firmware version"); return FALSE; } /* look for the AT and QMI/MBIM ports */ if (!mm_modem_get_ports(modem, &ports, &n_ports)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to get port information"); return FALSE; } if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) { for (guint i = 0; i < n_ports; i++) { if (ports[i].type == MM_MODEM_PORT_TYPE_AT) { self->port_at = g_strdup_printf("/dev/%s", ports[i].name); break; } } fu_device_add_protocol(device, "com.google.fastboot"); } if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) { for (guint i = 0; i < n_ports; i++) { if ((ports[i].type == MM_MODEM_PORT_TYPE_QMI) || (ports[i].type == MM_MODEM_PORT_TYPE_MBIM)) { self->port_qmi = g_strdup_printf("/dev/%s", ports[i].name); break; } } /* only set if fastboot wasn't already set */ if (fu_device_get_protocols(device)->len == 0) fu_device_add_protocol(device, "com.qualcomm.qmi_pdc"); } if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU) { for (guint i = 0; i < n_ports; i++) { if (ports[i].type == MM_MODEM_PORT_TYPE_MBIM) { self->port_mbim = g_strdup_printf("/dev/%s", ports[i].name); break; } } fu_device_add_protocol(device, "com.qualcomm.mbim_qdu"); } if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE) { for (guint i = 0; i < n_ports; i++) { if ((ports[i].type == MM_MODEM_PORT_TYPE_QCDM) || (ports[i].type == MM_MODEM_PORT_TYPE_IGNORED && g_strstr_len(ports[i].name, -1, "qcdm") != NULL)) self->port_qcdm = g_strdup_printf("/dev/%s", ports[i].name); else if (ports[i].type == MM_MODEM_PORT_TYPE_MBIM) self->port_mbim = g_strdup_printf("/dev/%s", ports[i].name); /* to read secboot status */ else if (ports[i].type == MM_MODEM_PORT_TYPE_AT) self->port_at = g_strdup_printf("/dev/%s", ports[i].name); } fu_device_add_protocol(device, "com.qualcomm.firehose"); } mm_modem_port_info_array_free(ports, n_ports); /* an at port is required for fastboot */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) && (self->port_at == NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find AT port"); return FALSE; } /* a qmi port is required for qmi-pdc */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) && (self->port_qmi == NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find QMI port"); return FALSE; } /* a mbim port is required for mbim-qdu */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU) && (self->port_mbim == NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find MBIM port"); return FALSE; } /* a qcdm or mbim port is required for firehose */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE) && (self->port_qcdm == NULL && self->port_mbim == NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find QCDM port"); return FALSE; } if (self->port_at != NULL) { fu_mm_utils_get_port_info(self->port_at, &device_bus, &device_sysfs_path, &self->port_at_ifnum, NULL); } if (self->port_qmi != NULL) { g_autofree gchar *qmi_device_sysfs_path = NULL; g_autofree gchar *qmi_device_bus = NULL; fu_mm_utils_get_port_info(self->port_qmi, &qmi_device_bus, &qmi_device_sysfs_path, &self->port_qmi_ifnum, NULL); if (device_sysfs_path == NULL && qmi_device_sysfs_path != NULL) { device_sysfs_path = g_steal_pointer(&qmi_device_sysfs_path); } else if (g_strcmp0(device_sysfs_path, qmi_device_sysfs_path) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "mismatched device sysfs path: %s != %s", device_sysfs_path, qmi_device_sysfs_path); return FALSE; } if (device_bus == NULL && qmi_device_bus != NULL) { device_bus = g_steal_pointer(&qmi_device_bus); } else if (g_strcmp0(device_bus, qmi_device_bus) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "mismatched device bus: %s != %s", device_bus, qmi_device_bus); return FALSE; } } if (self->port_mbim != NULL) { g_autofree gchar *mbim_device_sysfs_path = NULL; g_autofree gchar *mbim_device_bus = NULL; fu_mm_utils_get_port_info(self->port_mbim, &mbim_device_bus, &mbim_device_sysfs_path, &self->port_mbim_ifnum, NULL); if (device_sysfs_path == NULL && mbim_device_sysfs_path != NULL) { device_sysfs_path = g_steal_pointer(&mbim_device_sysfs_path); } else if (g_strcmp0(device_sysfs_path, mbim_device_sysfs_path) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "mismatched device sysfs path: %s != %s", device_sysfs_path, mbim_device_sysfs_path); return FALSE; } if (device_bus == NULL && mbim_device_bus != NULL) { device_bus = g_steal_pointer(&mbim_device_bus); } else if (g_strcmp0(device_bus, mbim_device_bus) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "mismatched device bus: %s != %s", device_bus, mbim_device_bus); return FALSE; } } if (self->port_qcdm != NULL) { g_autofree gchar *qcdm_device_sysfs_path = NULL; g_autofree gchar *qcdm_device_bus = NULL; fu_mm_utils_get_port_info(self->port_qcdm, &qcdm_device_bus, &qcdm_device_sysfs_path, NULL, NULL); if (device_sysfs_path == NULL && qcdm_device_sysfs_path != NULL) { device_sysfs_path = g_steal_pointer(&qcdm_device_sysfs_path); } else if (g_strcmp0(device_sysfs_path, qcdm_device_sysfs_path) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "mismatched device sysfs path: %s != %s", device_sysfs_path, qcdm_device_sysfs_path); return FALSE; } if (device_bus == NULL && qcdm_device_bus != NULL) { device_bus = g_steal_pointer(&qcdm_device_bus); } else if (g_strcmp0(device_bus, qcdm_device_bus) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "mismatched device bus: %s != %s", device_bus, qcdm_device_bus); return FALSE; } } /* if no device sysfs file, error out */ if (device_sysfs_path == NULL || device_bus == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find device details"); return FALSE; } /* add properties to fwupd device */ fu_device_set_physical_id(device, device_sysfs_path); if (mm_modem_get_manufacturer(modem) != NULL) fu_device_set_vendor(device, mm_modem_get_manufacturer(modem)); if (mm_modem_get_model(modem) != NULL) fu_device_set_name(device, mm_modem_get_model(modem)); /* only for modems that opt-in */ if (fu_device_has_private_flag(device, FU_MM_DEVICE_FLAG_USE_BRANCH)) fu_device_set_branch(device, mm_modem_get_carrier_configuration(modem)); fu_device_set_version(device, version); /* filter these */ for (guint i = 0; device_ids[i] != NULL; i++) fu_mm_device_add_instance_id(device, device_ids[i]); vendors = fu_device_get_vendor_ids(device); if (vendors == NULL || vendors->len == 0) { g_autofree gchar *path = NULL; g_autofree gchar *value_str = NULL; g_autoptr(GError) error_local = NULL; if (g_strcmp0(device_bus, "USB") == 0) path = g_build_filename(device_sysfs_path, "idVendor", NULL); else if (g_strcmp0(device_bus, "PCI") == 0) path = g_build_filename(device_sysfs_path, "vendor", NULL); if (path == NULL) { g_warning("failed to set vendor ID: unsupported bus: %s", device_bus); } else if (!g_file_get_contents(path, &value_str, NULL, &error_local)) { g_warning("failed to set vendor ID: %s", error_local->message); } else { guint64 value_int; /* note: the string value may be prefixed with '0x' (e.g. when reading * the PCI 'vendor' attribute, or not prefixed with anything, as in the * USB 'idVendor' attribute. */ value_int = g_ascii_strtoull(value_str, NULL, 16); if (value_int > G_MAXUINT16) { g_warning("failed to set vendor ID: invalid value: %s", value_str); } else { g_autofree gchar *vendor_id = g_strdup_printf("%s:0x%04X", device_bus, (guint)value_int); fu_device_add_vendor_id(device, vendor_id); } } } /* fix up vendor name */ if (g_strcmp0(fu_device_get_vendor(device), "QUALCOMM INCORPORATED") == 0) fu_device_set_vendor(device, "Qualcomm"); /* success */ return TRUE; } static gboolean fu_mm_device_probe_udev(FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); /* an at port is required for fastboot */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) && (self->port_at == NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find AT port"); return FALSE; } /* a qmi port is required for qmi-pdc */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) && (self->port_qmi == NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find QMI port"); return FALSE; } return TRUE; } static gboolean fu_mm_device_probe(FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); if (self->omodem) return fu_mm_device_probe_default(device, error); return fu_mm_device_probe_udev(device, error); } static gboolean fu_mm_device_io_open_qcdm(FuMmDevice *self, GError **error) { /* sanity check */ if (self->port_qcdm == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no QCDM port provided for filename"); return FALSE; } /* open device */ self->io_channel = fu_io_channel_new_file(self->port_qcdm, error); if (self->io_channel == NULL) return FALSE; /* success */ return TRUE; } static gboolean fu_mm_device_qcdm_cmd(FuMmDevice *self, const guint8 *cmd, gsize cmd_len, GError **error) { g_autoptr(GBytes) qcdm_req = NULL; g_autoptr(GBytes) qcdm_res = NULL; /* command */ qcdm_req = g_bytes_new(cmd, cmd_len); fu_dump_bytes(G_LOG_DOMAIN, "writing", qcdm_req); if (!fu_io_channel_write_bytes(self->io_channel, qcdm_req, 1500, FU_IO_CHANNEL_FLAG_FLUSH_INPUT, error)) { g_prefix_error(error, "failed to write qcdm command: "); return FALSE; } /* response */ qcdm_res = fu_io_channel_read_bytes(self->io_channel, -1, 1500, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error); if (qcdm_res == NULL) { g_prefix_error(error, "failed to read qcdm response: "); return FALSE; } fu_dump_bytes(G_LOG_DOMAIN, "read", qcdm_res); /* command == response */ if (g_bytes_compare(qcdm_res, qcdm_req) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to read valid qcdm response"); return FALSE; } return TRUE; } typedef struct { const gchar *cmd; gboolean has_response; } FuMmDeviceAtCmdHelper; static gboolean fu_mm_device_at_cmd_cb(FuDevice *device, gpointer user_data, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); FuMmDeviceAtCmdHelper *helper = (FuMmDeviceAtCmdHelper *)user_data; const gchar *buf; gsize bufsz = 0; g_autoptr(GBytes) at_req = NULL; g_autoptr(GBytes) at_res = NULL; g_autofree gchar *cmd_cr = g_strdup_printf("%s\r\n", helper->cmd); /* command */ at_req = g_bytes_new(cmd_cr, strlen(cmd_cr)); fu_dump_bytes(G_LOG_DOMAIN, "writing", at_req); if (!fu_io_channel_write_bytes(self->io_channel, at_req, 1500, FU_IO_CHANNEL_FLAG_FLUSH_INPUT, error)) { g_prefix_error(error, "failed to write %s: ", helper->cmd); return FALSE; } /* AT command has no response, return TRUE */ if (!helper->has_response) { g_debug("no response expected for AT command: '%s', assuming succeed", helper->cmd); return TRUE; } /* response */ at_res = fu_io_channel_read_bytes(self->io_channel, -1, 1500, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error); if (at_res == NULL) { g_prefix_error(error, "failed to read response for %s: ", helper->cmd); return FALSE; } fu_dump_bytes(G_LOG_DOMAIN, "read", at_res); buf = g_bytes_get_data(at_res, &bufsz); /* * the first time the modem returns may be the command itself with one \n missing. * this is because the modem AT has enabled echo */ if (g_strrstr(buf, helper->cmd) != NULL && bufsz == strlen(helper->cmd) + 1) { g_bytes_unref(at_res); at_res = fu_io_channel_read_bytes(self->io_channel, -1, 1500, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error); if (at_res == NULL) { g_prefix_error(error, "failed to read response for %s: ", helper->cmd); return FALSE; } buf = g_bytes_get_data(at_res, &bufsz); } if (bufsz < 6) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to read valid response for %s", helper->cmd); return FALSE; } /* return error if AT command failed */ if (g_strrstr(buf, "\r\nOK\r\n") == NULL) { g_autofree gchar *tmp = g_strndup(buf + 2, bufsz - 4); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to read valid response for %s: %s", helper->cmd, tmp); return FALSE; } /* set firmware branch if returned */ if (self->branch_at != NULL && g_strcmp0(helper->cmd, self->branch_at) == 0) { /* * example AT+GETFWBRANCH response: * * \r\nFOSS-002 \r\n\r\nOK\r\n * * remove \r\n, and OK to get branch name */ g_auto(GStrv) parts = g_strsplit(buf, "\r\n", -1); for (int j = 0; parts[j] != NULL; j++) { /* Ignore empty strings, and OK responses */ if (g_strcmp0(parts[j], "") != 0 && g_strcmp0(parts[j], "OK") != 0) { /* Set branch */ fu_device_set_branch(FU_DEVICE(self), parts[j]); g_info("firmware branch reported as '%s'", parts[j]); break; } } } if (g_strcmp0(helper->cmd, "AT+QSECBOOT=\"status\"") == 0) { /* * example AT+QSECBOOT="status" response: * * \r\n+QSECBOOT: "STATUS",1\r\n\r\nOK\r\n * * Secure boot status: * 1 - enabled * 0 - disabled */ g_auto(GStrv) parts = g_strsplit(buf, "\r\n", -1); for (int j = 0; parts[j] != NULL; j++) { if (g_strcmp0(parts[j], "+QSECBOOT: \"status\",1") == 0) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); break; } if (g_strcmp0(parts[j], "+QSECBOOT: \"status\",0") == 0) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); break; } } } if (g_strcmp0(helper->cmd, "AT+QCFG=\"secbootstat\"") == 0) { /* * example AT+QSECBOOT="status" response: * * \r\n+QSECBOOT: "STATUS",1\r\n\r\nOK\r\n * * Secure boot status: * 1 - enabled * 0 - disabled */ g_auto(GStrv) parts = g_strsplit(buf, "\r\n", -1); for (int j = 0; parts[j] != NULL; j++) { if (g_strcmp0(parts[j], "+QCFG: \"secbootstat\",1") == 0) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); break; } if (g_strcmp0(parts[j], "+QCFG: \"secbootstat\",0") == 0) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); break; } } } /* success */ return TRUE; } static gboolean fu_mm_device_at_cmd(FuMmDevice *self, const gchar *cmd, gboolean has_response, GError **error) { FuMmDeviceAtCmdHelper helper = {.cmd = cmd, .has_response = has_response}; return fu_device_retry_full(FU_DEVICE(self), fu_mm_device_at_cmd_cb, FU_MM_DEVICE_AT_RETRIES, FU_MM_DEVICE_AT_DELAY, &helper, error); } static gboolean fu_mm_device_io_open(FuMmDevice *self, GError **error) { /* sanity check */ if (self->port_at == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no AT port provided for filename"); return FALSE; } /* open device */ self->io_channel = fu_io_channel_new_file(self->port_at, error); if (self->io_channel == NULL) return FALSE; /* success */ return TRUE; } static gboolean fu_mm_device_io_close(FuMmDevice *self, GError **error) { if (self->io_channel != NULL) { if (!fu_io_channel_shutdown(self->io_channel, error)) return FALSE; g_clear_object(&self->io_channel); } return TRUE; } static gboolean fu_mm_device_detach_fastboot(FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); g_autoptr(FuDeviceLocker) locker = NULL; gboolean has_response = TRUE; /* boot to fastboot mode */ locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_mm_device_io_open, (FuDeviceLockerFunc)fu_mm_device_io_close, error); /* expect response for fastboot AT command */ if (fu_device_has_private_flag(FU_DEVICE(self), FU_MM_DEVICE_FLAG_DETACH_AT_FASTBOOT_HAS_NO_RESPONSE)) { has_response = FALSE; } if (locker == NULL) return FALSE; if (!fu_mm_device_at_cmd(self, "AT", TRUE, error)) return FALSE; if (!fu_mm_device_at_cmd(self, self->detach_fastboot_at, has_response, error)) { g_prefix_error(error, "rebooting into fastboot not supported: "); return FALSE; } /* success */ fu_device_set_remove_delay(device, FU_MM_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_mm_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; /* This plugin supports several methods to download firmware: * fastboot, qmi-pdc, firehose. A modem may require one of those, * or several, depending on the update type or the modem type. * * The first time this detach() method is executed is always for a * FuMmDevice that was created from a MM-exposed modem, which is the * moment when we're going to decide the amount of retries we need to * flash all firmware. * * If the FuMmModem is created from a MM-exposed modem and... * a) we only support fastboot, we just trigger the fastboot detach. * b) we support both fastboot and qmi-pdc, we will set the * ANOTHER_WRITE_REQUIRED flag in the device and we'll trigger * the fastboot detach. * c) we only support firehose, skip detach and switch to embedded * downloader mode (EDL) during write_firmware. * * If the FuMmModem is created from udev events... * c) it means we're in the extra required write that was flagged * in an earlier detach(), and we need to perform the qmi-pdc * update procedure at this time, so we just exit without any * detach. */ /* FuMmDevice created from MM... */ if (self->omodem != NULL) { /* both fastboot and qmi-pdc supported? another write required */ if ((self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) && (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC)) { g_debug("both fastboot and qmi-pdc supported, so the upgrade requires " "another write"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); } /* fastboot */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) return fu_mm_device_detach_fastboot(device, error); /* otherwise, assume we don't need any detach */ return TRUE; } /* FuMmDevice created from udev... * assume we don't need any detach */ return TRUE; } typedef struct { gchar *filename; GBytes *bytes; GArray *digest; gboolean active; } FuMmFileInfo; static void fu_mm_file_info_free(FuMmFileInfo *file_info) { g_clear_pointer(&file_info->digest, g_array_unref); g_free(file_info->filename); g_bytes_unref(file_info->bytes); g_free(file_info); } typedef struct { FuMmDevice *device; GError *error; GPtrArray *file_infos; } FuMmArchiveIterateCtx; static gboolean fu_mm_should_be_active(const gchar *version, const gchar *filename) { g_auto(GStrv) split = NULL; g_autofree gchar *carrier_id = NULL; /* The filename of the mcfg file is composed of a "mcfg." prefix, then the * carrier code, followed by the carrier version, and finally a ".mbn" * prefix. Here we try to guess, based on the carrier code, whether the * specific mcfg file should be activated after the firmware upgrade * operation. * * This logic requires that the previous device version includes the carrier * code also embedded in the version string. E.g. "xxxx.VF.xxxx". If we find * this match, we assume this is the active config to use. */ split = g_strsplit(filename, ".", -1); if (g_strv_length(split) < 4) return FALSE; if (g_strcmp0(split[0], "mcfg") != 0) return FALSE; carrier_id = g_strdup_printf(".%s.", split[1]); return (g_strstr_len(version, -1, carrier_id) != NULL); } static gboolean fu_mm_qmi_pdc_archive_iterate_mcfg(FuArchive *archive, const gchar *filename, GBytes *bytes, gpointer user_data, GError **error) { FuMmArchiveIterateCtx *ctx = user_data; FuMmFileInfo *file_info; /* filenames should be named as 'mcfg.*.mbn', e.g.: mcfg.A2.018.mbn */ if (!g_str_has_prefix(filename, "mcfg.") || !g_str_has_suffix(filename, ".mbn")) return TRUE; file_info = g_new0(FuMmFileInfo, 1); file_info->filename = g_strdup(filename); file_info->bytes = g_bytes_ref(bytes); file_info->active = fu_mm_should_be_active(fu_device_get_version(FU_DEVICE(ctx->device)), filename); g_ptr_array_add(ctx->file_infos, file_info); return TRUE; } static gboolean fu_mm_device_qmi_open(FuMmDevice *self, GError **error) { self->qmi_pdc_updater = fu_qmi_pdc_updater_new(self->port_qmi); return fu_qmi_pdc_updater_open(self->qmi_pdc_updater, error); } static gboolean fu_mm_device_qmi_close(FuMmDevice *self, GError **error) { g_autoptr(FuQmiPdcUpdater) updater = NULL; updater = g_steal_pointer(&self->qmi_pdc_updater); return fu_qmi_pdc_updater_close(updater, error); } static gboolean fu_mm_device_qmi_close_no_error(FuMmDevice *self, GError **error) { g_autoptr(FuQmiPdcUpdater) updater = NULL; updater = g_steal_pointer(&self->qmi_pdc_updater); fu_qmi_pdc_updater_close(updater, NULL); return TRUE; } static gboolean fu_mm_device_write_firmware_qmi_pdc(FuDevice *device, GBytes *fw, GArray **active_id, GError **error) { g_autoptr(FuArchive) archive = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GPtrArray) file_infos = g_ptr_array_new_with_free_func((GDestroyNotify)fu_mm_file_info_free); gint active_i = -1; FuMmArchiveIterateCtx archive_context = { .device = FU_MM_DEVICE(device), .error = NULL, .file_infos = file_infos, }; /* decompress entire archive ahead of time */ archive = fu_archive_new(fw, FU_ARCHIVE_FLAG_IGNORE_PATH, error); if (archive == NULL) return FALSE; /* boot to fastboot mode */ locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_mm_device_qmi_open, (FuDeviceLockerFunc)fu_mm_device_qmi_close, error); if (locker == NULL) return FALSE; /* process the list of MCFG files to write */ if (!fu_archive_iterate(archive, fu_mm_qmi_pdc_archive_iterate_mcfg, &archive_context, error)) return FALSE; for (guint i = 0; i < file_infos->len; i++) { FuMmFileInfo *file_info = g_ptr_array_index(file_infos, i); file_info->digest = fu_qmi_pdc_updater_write(archive_context.device->qmi_pdc_updater, file_info->filename, file_info->bytes, &archive_context.error); if (file_info->digest == NULL) { g_prefix_error(&archive_context.error, "Failed to write file '%s':", file_info->filename); break; } /* if we wrongly detect more than one, just assume the latest one; this * is not critical, it may just take a bit more time to perform the * automatic carrier config switching in ModemManager */ if (file_info->active) active_i = i; } /* set expected active configuration */ if (active_i >= 0 && active_id != NULL) { FuMmFileInfo *file_info = g_ptr_array_index(file_infos, active_i); *active_id = g_array_ref(file_info->digest); } if (archive_context.error != NULL) { g_propagate_error(error, archive_context.error); return FALSE; } return TRUE; } typedef struct { FuDevice *device; GMainLoop *mainloop; gchar *version; GError *error; } FuMmGetFirmwareVersionCtx; static gboolean fu_mm_device_mbim_open(FuMmDevice *self, GError **error) { self->mbim_qdu_updater = fu_mbim_qdu_updater_new(self->port_mbim); return fu_mbim_qdu_updater_open(self->mbim_qdu_updater, error); } static gboolean fu_mm_device_mbim_close(FuMmDevice *self, GError **error) { g_autoptr(FuMbimQduUpdater) updater = NULL; updater = g_steal_pointer(&self->mbim_qdu_updater); return fu_mbim_qdu_updater_close(updater, error); } static gboolean fu_mm_device_locker_new_timeout(gpointer user_data) { FuMmGetFirmwareVersionCtx *ctx = user_data; g_main_loop_quit(ctx->mainloop); return G_SOURCE_REMOVE; } static gboolean fu_mm_device_get_firmware_version_mbim_timeout(gpointer user_data) { FuMmGetFirmwareVersionCtx *ctx = user_data; FuMmDevice *self = FU_MM_DEVICE(ctx->device); g_clear_error(&ctx->error); ctx->version = fu_mbim_qdu_updater_check_ready(self->mbim_qdu_updater, &ctx->error); g_main_loop_quit(ctx->mainloop); return G_SOURCE_REMOVE; } static gchar * fu_mm_device_get_firmware_version_mbim(FuDevice *device, GError **error) { GTimer *timer = g_timer_new(); g_autoptr(GMainLoop) mainloop = g_main_loop_new(NULL, FALSE); FuMmGetFirmwareVersionCtx ctx = { .device = device, .mainloop = mainloop, .version = NULL, .error = NULL, }; while (ctx.version == NULL && g_timer_elapsed(timer, NULL) < MAX_WAIT_TIME_SECS) { g_autoptr(FuDeviceLocker) locker = NULL; g_clear_error(&ctx.error); locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_mm_device_mbim_open, (FuDeviceLockerFunc)fu_mm_device_mbim_close, &ctx.error); if (locker == NULL) { g_timeout_add_seconds(20, fu_mm_device_locker_new_timeout, &ctx); g_main_loop_run(mainloop); continue; } g_timeout_add_seconds(10, fu_mm_device_get_firmware_version_mbim_timeout, &ctx); g_main_loop_run(mainloop); } g_timer_destroy(timer); if (ctx.version == NULL && ctx.error != NULL) { g_propagate_error(error, ctx.error); return NULL; } return ctx.version; } static gboolean fu_mm_device_writeln(const gchar *fn, const gchar *buf, GError **error) { int fd; g_autoptr(FuIOChannel) io = NULL; fd = open(fn, O_WRONLY); if (fd < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "could not open %s", fn); return FALSE; } io = fu_io_channel_unix_new(fd); return fu_io_channel_write_raw(io, (const guint8 *)buf, strlen(buf), 1000, FU_IO_CHANNEL_FLAG_NONE, error); } static gboolean fu_mm_device_set_autosuspend_delay(FuMmDevice *self, guint timeout_ms, GError **error) { g_autofree gchar *device_sysfs_path = NULL; g_autofree gchar *autosuspend_delay_filename = NULL; g_autofree gchar *buf = g_strdup_printf("%u", timeout_ms); /* autosuspend delay updated for a proper firmware update */ fu_mm_utils_get_port_info(self->port_mbim, NULL, &device_sysfs_path, NULL, NULL); autosuspend_delay_filename = g_build_filename(device_sysfs_path, "/power/autosuspend_delay_ms", NULL); return fu_mm_device_writeln(autosuspend_delay_filename, buf, error); } static gboolean fu_mm_device_write_firmware_mbim_qdu(FuDevice *device, GBytes *fw, FuProgress *progress, GError **error) { XbNode *part = NULL; const gchar *filename = NULL; const gchar *csum; FuMmDevice *self = FU_MM_DEVICE(device); g_autofree gchar *csum_actual = NULL; g_autofree gchar *version = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GArray) digest = NULL; g_autoptr(GBytes) data_xml = NULL; g_autoptr(GBytes) data_part = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; /* decompress entire archive ahead of time */ archive = fu_archive_new(fw, FU_ARCHIVE_FLAG_IGNORE_PATH, error); if (archive == NULL) return FALSE; locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_mm_device_mbim_open, (FuDeviceLockerFunc)fu_mm_device_mbim_close, error); if (locker == NULL) return FALSE; /* load the manifest of operations */ data_xml = fu_archive_lookup_by_fn(archive, "flashfile.xml", error); if (data_xml == NULL) return FALSE; if (!xb_builder_source_load_bytes(source, data_xml, XB_BUILDER_SOURCE_FLAG_NONE, error)) return FALSE; xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; part = xb_silo_query_first(silo, "parts/part", error); if (part == NULL) return FALSE; filename = xb_node_get_attr(part, "filename"); csum = xb_node_get_attr(part, "MD5"); data_part = fu_archive_lookup_by_fn(archive, filename, error); if (data_part == NULL) return FALSE; csum_actual = g_compute_checksum_for_bytes(G_CHECKSUM_MD5, data_part); if (g_strcmp0(csum, csum_actual) != 0) { g_debug("[%s] MD5 not matched", filename); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "[%s] MD5 not matched", filename); return FALSE; } g_debug("[%s] MD5 matched", filename); /* autosuspend delay updated for a proper firmware update */ if (!fu_mm_device_set_autosuspend_delay(self, 20000, error)) return FALSE; fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); digest = fu_mbim_qdu_updater_write(self->mbim_qdu_updater, filename, data_part, device, progress, error); if (digest == NULL) return FALSE; if (!fu_device_locker_close(locker, error)) return FALSE; fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); version = fu_mm_device_get_firmware_version_mbim(device, error); if (version == NULL) return FALSE; return TRUE; } static gboolean fu_mm_find_device_file(FuDevice *device, gpointer userdata, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); const gchar *subsystem = (const gchar *)userdata; return fu_mm_utils_find_device_file(fu_device_get_physical_id(FU_DEVICE(self)), subsystem, &self->port_edl, error); } static gboolean fu_mm_device_find_edl_port(FuDevice *device, const gchar *subsystem, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); g_clear_pointer(&self->port_edl, g_free); return fu_device_retry_full(device, fu_mm_find_device_file, 30, 250, (gpointer)subsystem, error); } static gboolean fu_mm_device_qcdm_switch_to_edl(FuDevice *device, gpointer userdata, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); g_autoptr(GError) error_local = NULL; g_autoptr(FuDeviceLocker) locker = NULL; static const guint8 emergency_download[] = {0x4b, 0x65, 0x01, 0x00, 0x54, 0x0f, 0x7e}; locker = fu_device_locker_new_full(self, (FuDeviceLockerFunc)fu_mm_device_io_open_qcdm, (FuDeviceLockerFunc)fu_mm_device_io_close, &error_local); if (locker == NULL) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) { return fu_mm_device_find_edl_port(device, "wwan", error); } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } if (!fu_mm_device_qcdm_cmd(self, emergency_download, G_N_ELEMENTS(emergency_download), error)) return FALSE; g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Device haven't switched to EDL yet"); return FALSE; } #if MBIM_CHECK_VERSION(1, 27, 5) static void fu_mm_device_switch_to_edl_mbim_ready(MbimDevice *device, GAsyncResult *res, GMainLoop *loop) { /* No need to check for a response since MBIM * port goes away without sending one */ g_main_loop_quit(loop); } static gboolean fu_mm_device_mbim_switch_to_edl(FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(MbimMessage) message = NULL; g_autoptr(GMainLoop) mainloop = g_main_loop_new(NULL, FALSE); locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_mm_device_mbim_open, (FuDeviceLockerFunc)fu_mm_device_mbim_close, error); if (locker == NULL) return FALSE; message = mbim_message_qdu_quectel_reboot_set_new(MBIM_QDU_QUECTEL_REBOOT_TYPE_EDL, NULL); mbim_device_command(fu_mbim_qdu_updater_get_mbim_device(self->mbim_qdu_updater), message, 5, NULL, (GAsyncReadyCallback)fu_mm_device_switch_to_edl_mbim_ready, mainloop); g_main_loop_run(mainloop); return TRUE; } #endif // MBIM_CHECK_VERSION(1, 27, 5) static gboolean fu_mm_device_firehose_open(FuMmDevice *self, GError **error) { self->firehose_updater = fu_firehose_updater_new(self->port_edl, self->sahara_loader); return fu_firehose_updater_open(self->firehose_updater, error); } static gboolean fu_mm_device_firehose_close(FuMmDevice *self, GError **error) { g_autoptr(FuFirehoseUpdater) updater = NULL; updater = g_steal_pointer(&self->firehose_updater); return fu_firehose_updater_close(updater, error); } static gboolean fu_mm_device_firehose_write(FuMmDevice *self, XbSilo *rawprogram_silo, GPtrArray *rawprogram_actions, FuProgress *progress, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new_full(self, (FuDeviceLockerFunc)fu_mm_device_firehose_open, (FuDeviceLockerFunc)fu_mm_device_firehose_close, error); if (locker == NULL) return FALSE; return fu_firehose_updater_write(self->firehose_updater, rawprogram_silo, rawprogram_actions, progress, error); } #if MM_CHECK_VERSION(1, 19, 1) static gboolean fu_mm_device_sahara_open(FuMmDevice *self, GError **error) { self->sahara_loader = fu_sahara_loader_new(); return fu_sahara_loader_open(self->sahara_loader, self->usb_device, error); } static gboolean fu_mm_device_sahara_close(FuMmDevice *self, GError **error) { g_autoptr(FuSaharaLoader) loader = NULL; loader = g_steal_pointer(&self->sahara_loader); return fu_sahara_loader_close(loader, error); } #endif // MM_CHECK_VERSION(1, 19, 1) static gboolean fu_mm_setup_firmware_dir(FuMmDevice *self, GError **error) { g_autofree gchar *cachedir = NULL; g_autofree gchar *mm_fw_dir = NULL; /* create a directory to store firmware files for modem-manager plugin */ cachedir = fu_path_from_kind(FU_PATH_KIND_CACHEDIR_PKG); mm_fw_dir = g_build_filename(cachedir, "modem-manager", "firmware", NULL); if (g_mkdir_with_parents(mm_fw_dir, 0700) == -1) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to create '%s': %s", mm_fw_dir, g_strerror(errno)); return FALSE; } if (!fu_kernel_set_firmware_search_path(mm_fw_dir, error)) return FALSE; self->firmware_path = g_steal_pointer(&mm_fw_dir); return TRUE; } static gboolean fu_mm_copy_firehose_prog(FuMmDevice *self, GBytes *prog, GError **error) { g_autofree gchar *qcom_fw_dir = NULL; g_autofree gchar *firehose_file_path = NULL; if (self->firehose_prog_file == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Firehose prog filename is not set for the device"); return FALSE; } qcom_fw_dir = g_build_filename(self->firmware_path, "qcom", NULL); if (!fu_path_mkdir_parent(qcom_fw_dir, error)) return FALSE; firehose_file_path = g_build_filename(qcom_fw_dir, self->firehose_prog_file, NULL); if (!fu_bytes_set_contents(firehose_file_path, prog, error)) return FALSE; return TRUE; } static gboolean fu_mm_prepare_firmware_search_path(FuMmDevice *self, GError **error) { self->restore_firmware_path = fu_kernel_get_firmware_search_path(NULL); return fu_mm_setup_firmware_dir(self, error); } static gboolean fu_mm_restore_firmware_search_path(FuMmDevice *self, GError **error) { if (self->restore_firmware_path != NULL && strlen(self->restore_firmware_path) > 0) return fu_kernel_set_firmware_search_path(self->restore_firmware_path, error); return fu_kernel_reset_firmware_search_path(error); } static gboolean fu_mm_device_write_firmware_firehose(FuDevice *device, GBytes *fw, FuProgress *progress, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); MMModem *modem = mm_object_peek_modem(self->omodem); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(XbSilo) firehose_rawprogram_silo = NULL; g_autoptr(GBytes) firehose_prog = NULL; g_autoptr(GBytes) firehose_rawprogram = NULL; g_autoptr(GPtrArray) firehose_rawprogram_actions = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DECOMPRESSING, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); /* decompress entire archive ahead of time */ archive = fu_archive_new(fw, FU_ARCHIVE_FLAG_IGNORE_PATH, error); if (archive == NULL) return FALSE; /* lookup and validate firehose-rawprogram actions */ firehose_rawprogram = fu_archive_lookup_by_fn(archive, "firehose-rawprogram.xml", error); if (firehose_rawprogram == NULL) return FALSE; if (!fu_firehose_validate_rawprogram(firehose_rawprogram, archive, &firehose_rawprogram_silo, &firehose_rawprogram_actions, error)) { g_prefix_error(error, "Invalid firehose rawprogram manifest: "); return FALSE; } /* lookup firehose-prog bootloader */ firehose_prog = fu_archive_lookup_by_fn(archive, "firehose-prog.mbn", error); if (firehose_prog == NULL) return FALSE; fu_progress_step_done(progress); /* Firehose program needs to be loaded to the modem before firehose update process can * start. Generally, modems use Sahara protocol to load the firehose binary. * * In case of MHI PCI modems, the mhi-pci-generic driver reads the firehose binary from the * firmware-loader and writes it to the modem. **/ if (g_strv_contains(mm_modem_get_drivers(modem), "mhi-pci-generic") && self->port_qcdm != NULL) { /* modify firmware search path and restore it before function returns */ locker = fu_device_locker_new_full( self, (FuDeviceLockerFunc)fu_mm_prepare_firmware_search_path, (FuDeviceLockerFunc)fu_mm_restore_firmware_search_path, error); if (locker == NULL) return FALSE; /* firehose modems that use mhi_pci drivers require firehose binary * to be present in the firmware-loader search path. */ if (!fu_mm_copy_firehose_prog(self, firehose_prog, error)) return FALSE; /* trigger emergency download mode, up to 30s retrying until the QCDM * port goes away; this takes us to the EDL (embedded downloader) execution * environment */ if (!fu_device_retry_full(FU_DEVICE(self), fu_mm_device_qcdm_switch_to_edl, 30, 1000, NULL, error)) return FALSE; g_debug("found edl port: %s", self->port_edl); } #if MM_CHECK_VERSION(1, 19, 1) else if ((FU_MM_DEVICE(self)->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_SAHARA) && self->port_mbim != NULL) { /* switch to emergency download (EDL) execution environment */ if (!fu_mm_device_mbim_switch_to_edl(device, error)) return FALSE; locker = fu_device_locker_new_full(self, (FuDeviceLockerFunc)fu_mm_device_sahara_open, (FuDeviceLockerFunc)fu_mm_device_sahara_close, error); if (locker == NULL) return FALSE; /* use sahara port to load firehose binary */ if (!fu_sahara_loader_run(self->sahara_loader, firehose_prog, error)) return FALSE; } #endif // MM_CHECK_VERSION(1, 19, 1) else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "suitable port not found"); return FALSE; } fu_progress_step_done(progress); /* download all files in the firehose-rawprogram manifest via Firehose */ if (!fu_mm_device_firehose_write(self, firehose_rawprogram_silo, firehose_rawprogram_actions, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* flag as restart again, the module is switching to modem mode */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_RESTART); return TRUE; } static gboolean fu_mm_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GBytes) fw = NULL; /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* lock device */ locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; /* qmi pdc write operation */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) return fu_mm_device_write_firmware_qmi_pdc(device, fw, &self->qmi_pdc_active_id, error); /* mbim qdu write operation */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU) return fu_mm_device_write_firmware_mbim_qdu(device, fw, progress, error); /* firehose operation */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE) return fu_mm_device_write_firmware_firehose(device, fw, progress, error); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported update method"); return FALSE; } static gboolean fu_mm_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); /* load from quirks */ if (g_strcmp0(key, "ModemManagerBranchAtCommand") == 0) { self->branch_at = g_strdup(value); return TRUE; } if (g_strcmp0(key, "ModemManagerFirehoseProgFile") == 0) { self->firehose_prog_file = g_strdup(value); return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_mm_device_attach_qmi_pdc(FuMmDevice *self, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; /* ignore action if there is no active id specified */ if (self->qmi_pdc_active_id == NULL) return TRUE; /* errors closing may be expected if the device really reboots itself */ locker = fu_device_locker_new_full(self, (FuDeviceLockerFunc)fu_mm_device_qmi_open, (FuDeviceLockerFunc)fu_mm_device_qmi_close_no_error, error); if (locker == NULL) return FALSE; if (!fu_qmi_pdc_updater_activate(self->qmi_pdc_updater, self->qmi_pdc_active_id, error)) return FALSE; return TRUE; } static gboolean fu_mm_device_attach_noop_idle(gpointer user_data) { FuMmDevice *self = FU_MM_DEVICE(user_data); self->attach_idle = 0; g_signal_emit(self, signals[SIGNAL_ATTACH_FINISHED], 0); return G_SOURCE_REMOVE; } static gboolean fu_mm_device_attach_qmi_pdc_idle(gpointer user_data) { FuMmDevice *self = FU_MM_DEVICE(user_data); g_autoptr(GError) error = NULL; if (!fu_mm_device_attach_qmi_pdc(self, &error)) g_warning("qmi-pdc attach operation failed: %s", error->message); else g_debug("qmi-pdc attach operation successful"); self->attach_idle = 0; g_signal_emit(self, signals[SIGNAL_ATTACH_FINISHED], 0); return G_SOURCE_REMOVE; } static gboolean fu_mm_device_cleanup(FuDevice *device, FuProgress *progress, FwupdInstallFlags install_flags, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); /* restore default configuration */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU) { if (!fu_mm_device_set_autosuspend_delay(self, 2000, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_mm_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); g_autoptr(FuDeviceLocker) locker = NULL; /* lock device */ locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; /* we want this attach operation to be triggered asynchronously, because the engine * must learn that it has to wait for replug before we actually trigger the reset. */ if (self->update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC) self->attach_idle = g_idle_add((GSourceFunc)fu_mm_device_attach_qmi_pdc_idle, self); else self->attach_idle = g_idle_add((GSourceFunc)fu_mm_device_attach_noop_idle, self); /* wait for re-probing after uninhibiting */ fu_device_set_remove_delay(device, FU_MM_DEVICE_REMOVE_DELAY_REPROBE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_mm_device_setup_branch_at(FuMmDevice *self, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; /* nothing to do if there is no AT port available or * ModemManagerBranchAtCommand quirk is not set */ if (self->port_at == NULL || self->branch_at == NULL) return TRUE; if (fu_device_has_flag(self, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Firmware branches are not supported if the devices is signed"); return FALSE; } /* Create IO channel to send AT commands to the modem */ locker = fu_device_locker_new_full(self, (FuDeviceLockerFunc)fu_mm_device_io_open, (FuDeviceLockerFunc)fu_mm_device_io_close, error); if (locker == NULL) return FALSE; if (!fu_mm_device_at_cmd(self, self->branch_at, TRUE, error)) return FALSE; if (fu_device_get_branch(self) != NULL) g_info("using firmware branch: %s", fu_device_get_branch(self)); else g_info("using firmware branch: default"); return TRUE; } static void fu_mm_device_setup_secboot_status_quectel(FuMmDevice *self) { const gchar *version = fu_device_get_version(FU_DEVICE(self)); g_autofree gchar *name = NULL; const gchar *at_cmd[] = {"AT+QSECBOOT=\"status\"", "AT+QCFG=\"secbootstat\"", NULL}; struct { const gchar *name; const gchar *version; } secboot[] = {{"EM05GF", "EM05GFAR07A07M1G_01.005.01.005"}, {"EM05CE", "EM05CEFCR08A16M1G_LNV"}, {NULL, NULL}}; if (self->port_at != NULL) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; /* Create IO channel to send AT commands to the modem */ locker = fu_device_locker_new_full(self, (FuDeviceLockerFunc)fu_mm_device_io_open, (FuDeviceLockerFunc)fu_mm_device_io_close, &error_local); if (locker == NULL) { g_debug("failed to open AT port: %s", error_local->message); return; } /* try to query sec boot status with AT commands */ for (guint i = 0; at_cmd[i] != NULL; i++) { g_autoptr(GError) error_loop = NULL; if (!fu_mm_device_at_cmd(self, at_cmd[i], TRUE, &error_loop)) { g_debug("AT command failed (%s): %s", at_cmd[i], error_loop->message); } else { return; } } } /* find model name and compare with table from Quectel */ if (version == NULL) return; name = g_strndup(version, 6); for (guint i = 0; secboot[i].name != NULL; i++) { if (g_strcmp0(name, secboot[i].name) == 0) { if (fu_version_compare(version, secboot[i].version, FWUPD_VERSION_FORMAT_PLAIN) >= 0) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); } else { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); } return; } } } static void fu_mm_device_setup_secboot_status(FuDevice *device) { FuMmDevice *self = FU_MM_DEVICE(device); if (fu_device_has_vendor_id(device, "USB:0x2C7C") || fu_device_has_vendor_id(device, "PCI:0x1EAC")) fu_mm_device_setup_secboot_status_quectel(self); else if (fu_device_has_vendor_id(device, "USB:0x2CB7")) { fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_SAVE_INTO_BACKUP_REMOTE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); } } static gboolean fu_mm_device_setup(FuDevice *device, GError **error) { FuMmDevice *self = FU_MM_DEVICE(device); g_autoptr(GError) error_local = NULL; fu_mm_device_setup_secboot_status(device); if (!fu_mm_device_setup_branch_at(self, &error_local)) g_warning("Failed to set firmware branch: %s", error_local->message); return TRUE; } static void fu_mm_device_incorporate(FuDevice *device, FuDevice *donor_device) { FuMmDevice *self = FU_MM_DEVICE(device); FuMmDevice *donor = FU_MM_DEVICE(donor_device); g_return_if_fail(FU_IS_MM_DEVICE(self)); g_return_if_fail(FU_IS_MM_DEVICE(donor)); self->update_methods = fu_mm_device_get_update_methods(donor); self->detach_fastboot_at = g_strdup(donor->detach_fastboot_at); self->inhibition_uid = g_strdup(fu_mm_device_get_inhibition_uid(donor)); self->port_at_ifnum = donor->port_at_ifnum; self->port_qmi_ifnum = donor->port_qmi_ifnum; self->port_mbim_ifnum = donor->port_mbim_ifnum; g_set_object(&self->manager, donor->manager); } static void fu_mm_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_mm_device_init(FuMmDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_summary(FU_DEVICE(self), "Mobile broadband device"); fu_device_add_icon(FU_DEVICE(self), "modem"); fu_device_register_private_flag(FU_DEVICE(self), FU_MM_DEVICE_FLAG_DETACH_AT_FASTBOOT_HAS_NO_RESPONSE, "detach-at-fastboot-has-no-response"); fu_device_register_private_flag(FU_DEVICE(self), FU_MM_DEVICE_FLAG_UNINHIBIT_MM_AFTER_FASTBOOT_REBOOT, "uninhibit-modemmanager-after-fastboot-reboot"); fu_device_register_private_flag(FU_DEVICE(self), FU_MM_DEVICE_FLAG_USE_BRANCH, "use-branch"); } static void fu_mm_device_finalize(GObject *object) { FuMmDevice *self = FU_MM_DEVICE(object); if (self->usb_device != NULL) g_object_unref(self->usb_device); if (self->attach_idle) g_source_remove(self->attach_idle); if (self->qmi_pdc_active_id) g_array_unref(self->qmi_pdc_active_id); if (self->manager != NULL) g_object_unref(self->manager); if (self->omodem != NULL) g_object_unref(self->omodem); g_free(self->detach_fastboot_at); g_free(self->branch_at); g_free(self->port_at); g_free(self->port_qmi); g_free(self->port_mbim); g_free(self->port_qcdm); g_free(self->inhibition_uid); g_free(self->firmware_path); g_free(self->restore_firmware_path); g_free(self->firehose_prog_file); G_OBJECT_CLASS(fu_mm_device_parent_class)->finalize(object); } static void fu_mm_device_class_init(FuMmDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_mm_device_finalize; klass_device->setup = fu_mm_device_setup; klass_device->reload = fu_mm_device_setup; klass_device->to_string = fu_mm_device_to_string; klass_device->set_quirk_kv = fu_mm_device_set_quirk_kv; klass_device->probe = fu_mm_device_probe; klass_device->detach = fu_mm_device_detach; klass_device->write_firmware = fu_mm_device_write_firmware; klass_device->attach = fu_mm_device_attach; klass_device->cleanup = fu_mm_device_cleanup; klass_device->set_progress = fu_mm_device_set_progress; klass_device->incorporate = fu_mm_device_incorporate; /** * FuMmDevice::attach-finished: * @self: the #FuMmDevice instance that emitted the signal * * The ::attach-finished signal is emitted when the device has attached. **/ signals[SIGNAL_ATTACH_FINISHED] = g_signal_new("attach-finished", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } FuMmDevice * fu_mm_device_new(FuContext *ctx, MMManager *manager, MMObject *omodem) { FuMmDevice *self = g_object_new(FU_TYPE_MM_DEVICE, "context", ctx, NULL); self->manager = g_object_ref(manager); self->omodem = g_object_ref(omodem); self->port_at_ifnum = -1; self->port_qmi_ifnum = -1; self->port_mbim_ifnum = -1; return self; } FuMmDevice * fu_mm_shadow_device_new(FuMmDevice *device) { FuMmDevice *shadow_device = NULL; shadow_device = g_object_new(FU_TYPE_MM_DEVICE, "context", fu_device_get_context(FU_DEVICE(device)), NULL); fu_device_incorporate(FU_DEVICE(shadow_device), FU_DEVICE(device)); return shadow_device; } void fu_mm_device_set_usb_device(FuMmDevice *self, FuUsbDevice *usb_device) { g_return_if_fail(FU_IS_MM_DEVICE(self)); g_return_if_fail(FU_IS_USB_DEVICE(usb_device)); g_set_object(&self->usb_device, usb_device); } FuMmDevice * fu_mm_device_udev_new(FuContext *ctx, MMManager *manager, FuMmDevice *shadow_device) { FuMmDevice *self = g_object_new(FU_TYPE_MM_DEVICE, "context", ctx, NULL); g_debug("creating udev-based mm device at %s", fu_device_get_physical_id(FU_DEVICE(shadow_device))); fu_device_incorporate(FU_DEVICE(self), FU_DEVICE(shadow_device)); return self; } void fu_mm_device_udev_add_port(FuMmDevice *self, const gchar *subsystem, const gchar *path, gint ifnum) { g_return_if_fail(FU_IS_MM_DEVICE(self)); if (g_str_equal(subsystem, "usbmisc") && self->port_qmi == NULL && ifnum >= 0 && ifnum == self->port_qmi_ifnum) { g_debug("added QMI port %s (%s)", path, subsystem); self->port_qmi = g_strdup(path); return; } if (g_str_equal(subsystem, "tty") && self->port_at == NULL && ifnum >= 0 && ifnum == self->port_at_ifnum) { g_debug("added AT port %s (%s)", path, subsystem); self->port_at = g_strdup(path); return; } /* otherwise, ignore all other ports */ g_debug("ignoring port %s (%s)", path, subsystem); } fwupd-1.9.16/plugins/modem-manager/fu-mm-device.h000066400000000000000000000034001460375044200215600ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #ifndef __FU_MM_DEVICE_H #define __FU_MM_DEVICE_H #include #include /* * FU_MM_DEVICE_FLAG_DETACH_AT_FASTBOOT_HAS_NO_RESPONSE * * If no AT response is expected when entering fastboot mode. */ #define FU_MM_DEVICE_FLAG_DETACH_AT_FASTBOOT_HAS_NO_RESPONSE (1 << 0) /* * FU_MM_DEVICE_FLAG_UNINHIBIT_MM_AFTER_FASTBOOT_REBOOT * * after entering the fastboot state, the modem cannot execute the attach method * in the MM plugin plugin plugin. shadow_device needs to be used to uninhibit the modem * when fu_mm_plugin_udev_uevent_cb detects it. */ #define FU_MM_DEVICE_FLAG_UNINHIBIT_MM_AFTER_FASTBOOT_REBOOT (1 << 1) /* * FU_MM_DEVICE_FLAG_USE_BRANCH * * Use the carrier (e.g. `VODAFONE`) as the device branch name so that `fwupdmgr sync` can * upgrade or downgrade the firmware as required. */ #define FU_MM_DEVICE_FLAG_USE_BRANCH (1 << 2) #define FU_TYPE_MM_DEVICE (fu_mm_device_get_type()) G_DECLARE_FINAL_TYPE(FuMmDevice, fu_mm_device, FU, MM_DEVICE, FuDevice) FuMmDevice * fu_mm_device_new(FuContext *ctx, MMManager *manager, MMObject *omodem); void fu_mm_device_set_usb_device(FuMmDevice *self, FuUsbDevice *usb_device); const gchar * fu_mm_device_get_inhibition_uid(FuMmDevice *device); MMModemFirmwareUpdateMethod fu_mm_device_get_update_methods(FuMmDevice *device); FuMmDevice * fu_mm_shadow_device_new(FuMmDevice *device); FuMmDevice * fu_mm_device_udev_new(FuContext *ctx, MMManager *manager, FuMmDevice *shadow_device); void fu_mm_device_udev_add_port(FuMmDevice *self, const gchar *subsystem, const gchar *path, gint ifnum); #endif /* __FU_MM_DEVICE_H */ fwupd-1.9.16/plugins/modem-manager/fu-mm-plugin.c000066400000000000000000000431271460375044200216240ustar00rootroot00000000000000 /* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-mm-device.h" #include "fu-mm-utils.h" /* amount of time to wait for ports of the same device being exposed by kernel */ #define FU_MM_UDEV_DEVICE_PORTS_TIMEOUT 3 /* s */ /* out-of-tree modem-power driver is unsupported */ #define MODEM_POWER_SYSFS_PATH "/sys/class/modem-power" struct FuPluginData { MMManager *manager; gboolean manager_ready; GUdevClient *udev_client; GFileMonitor *modem_power_monitor; guint udev_timeout_id; /* when a device is inhibited from MM, we store all relevant details * ourselves to recreate a functional device object even without MM */ FuMmDevice *shadow_device; /* * Used to mark whether FU_MM_DEVICE_FLAG_UNINHIBIT_MM_AFTER_FASTBOOT_REBOOT is being used */ gboolean device_ready_uninhibit_manager; }; typedef FuPluginData FuModemManagerPlugin; #define FU_MODEM_MANAGER_PLUGIN(o) fu_plugin_get_data(FU_PLUGIN(o)) static void fu_mm_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); fu_string_append_kb(str, idt, "ManagerReady", self->manager_ready); if (self->shadow_device != NULL) fu_string_append(str, idt, "ShadowDevice", fu_device_get_id(self->shadow_device)); } static void fu_mm_plugin_load(FuContext *ctx) { fu_context_add_quirk_key(ctx, "ModemManagerBranchAtCommand"); } static void fu_mm_plugin_udev_device_removed(FuPlugin *plugin) { FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); FuMmDevice *dev; if (self->shadow_device == NULL) return; dev = fu_plugin_cache_lookup(plugin, fu_device_get_physical_id(FU_DEVICE(self->shadow_device))); if (dev == NULL) return; /* once the first port is gone, consider device is gone */ fu_plugin_cache_remove(plugin, fu_device_get_physical_id(FU_DEVICE(self->shadow_device))); fu_plugin_device_remove(plugin, FU_DEVICE(dev)); /* no need to wait for more ports, cancel that right away */ if (self->udev_timeout_id != 0) { g_source_remove(self->udev_timeout_id); self->udev_timeout_id = 0; } } static void fu_mm_plugin_uninhibit_device(FuPlugin *plugin) { FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); g_autoptr(FuMmDevice) shadow_device = NULL; g_clear_object(&self->udev_client); /* get the device removed from the plugin cache before uninhibiting */ fu_mm_plugin_udev_device_removed(plugin); shadow_device = g_steal_pointer(&self->shadow_device); if (self->manager != NULL && shadow_device != NULL) { const gchar *inhibition_uid = fu_mm_device_get_inhibition_uid(shadow_device); g_debug("uninhibit modemmanager device with uid %s", inhibition_uid); mm_manager_uninhibit_device_sync(self->manager, inhibition_uid, NULL, NULL); } } static gboolean fu_mm_plugin_udev_device_ports_timeout(gpointer user_data) { FuPlugin *plugin = user_data; FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); FuMmDevice *dev; g_autoptr(GError) error = NULL; g_return_val_if_fail(self->shadow_device != NULL, G_SOURCE_REMOVE); self->udev_timeout_id = 0; dev = fu_plugin_cache_lookup(plugin, fu_device_get_physical_id(FU_DEVICE(self->shadow_device))); if (dev != NULL) { if (!fu_device_probe(FU_DEVICE(dev), &error)) { g_debug("failed to probe MM device: %s", error->message); } else { fu_plugin_device_add(plugin, FU_DEVICE(dev)); } } return G_SOURCE_REMOVE; } static void fu_mm_plugin_udev_device_ports_timeout_reset(FuPlugin *plugin) { FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); g_return_if_fail(self->shadow_device != NULL); if (self->udev_timeout_id != 0) g_source_remove(self->udev_timeout_id); self->udev_timeout_id = g_timeout_add_seconds(FU_MM_UDEV_DEVICE_PORTS_TIMEOUT, fu_mm_plugin_udev_device_ports_timeout, plugin); } static void fu_mm_plugin_udev_device_port_added(FuPlugin *plugin, const gchar *subsystem, const gchar *path, gint ifnum) { FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); FuMmDevice *existing; g_autoptr(FuMmDevice) dev = NULL; g_return_if_fail(self->shadow_device != NULL); existing = fu_plugin_cache_lookup(plugin, fu_device_get_physical_id(FU_DEVICE(self->shadow_device))); if (existing != NULL) { /* add port to existing device */ fu_mm_device_udev_add_port(existing, subsystem, path, ifnum); fu_mm_plugin_udev_device_ports_timeout_reset(plugin); return; } /* create device and add to cache */ dev = fu_mm_device_udev_new(fu_plugin_get_context(plugin), self->manager, self->shadow_device); fu_mm_device_udev_add_port(dev, subsystem, path, ifnum); fu_plugin_cache_add(plugin, fu_device_get_physical_id(FU_DEVICE(self->shadow_device)), dev); /* wait a bit before probing, in case more ports get added */ fu_mm_plugin_udev_device_ports_timeout_reset(plugin); } static gboolean fu_mm_plugin_udev_uevent_cb(GUdevClient *udev, const gchar *action, GUdevDevice *device, gpointer user_data) { FuPlugin *plugin = FU_PLUGIN(user_data); FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); const gchar *subsystem = g_udev_device_get_subsystem(device); const gchar *name = g_udev_device_get_name(device); g_autofree gchar *path = NULL; g_autofree gchar *device_sysfs_path = NULL; g_autofree gchar *device_bus = NULL; gint ifnum = -1; if (action == NULL || subsystem == NULL || self->shadow_device == NULL || name == NULL) return TRUE; /* ignore if loading port info fails */ if (!fu_mm_utils_get_udev_port_info(device, &device_bus, &device_sysfs_path, &ifnum, NULL)) return TRUE; /* ignore non-USB and non-PCI events */ if (g_strcmp0(device_bus, "USB") != 0 && g_strcmp0(device_bus, "PCI") != 0) return TRUE; /* ignore all events for ports not owned by our device */ if (g_strcmp0(device_sysfs_path, fu_device_get_physical_id(FU_DEVICE(self->shadow_device))) != 0) return TRUE; /* device re creation, uninhibit manager */ if (self->device_ready_uninhibit_manager) { self->device_ready_uninhibit_manager = FALSE; fu_mm_plugin_uninhibit_device(plugin); } path = g_strdup_printf("/dev/%s", name); if ((g_str_equal(action, "add")) || (g_str_equal(action, "change"))) { g_debug("added port to shadow_device modem: %s (ifnum %d)", path, ifnum); fu_mm_plugin_udev_device_port_added(plugin, subsystem, path, ifnum); } else if (g_str_equal(action, "remove")) { g_debug("removed port from shadow_device modem: %s", path); fu_mm_plugin_udev_device_removed(plugin); } return TRUE; } static gboolean fu_mm_plugin_inhibit_device(FuPlugin *plugin, FuDevice *device, GError **error) { const gchar *inhibition_uid; static const gchar *subsystems[] = {"tty", "usbmisc", "wwan", NULL}; FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); g_autoptr(FuMmDevice) shadow_device = NULL; fu_mm_plugin_uninhibit_device(plugin); shadow_device = fu_mm_shadow_device_new(FU_MM_DEVICE(device)); inhibition_uid = fu_mm_device_get_inhibition_uid(shadow_device); g_debug("inhibit modemmanager device with uid %s", inhibition_uid); if (!mm_manager_inhibit_device_sync(self->manager, inhibition_uid, NULL, error)) return FALSE; /* setup shadow_device device info */ self->shadow_device = g_steal_pointer(&shadow_device); /* uninhibit when device re creation is detected */ if (fu_device_has_private_flag(device, FU_MM_DEVICE_FLAG_UNINHIBIT_MM_AFTER_FASTBOOT_REBOOT)) { self->device_ready_uninhibit_manager = TRUE; } else { self->device_ready_uninhibit_manager = FALSE; } /* only do modem port monitoring using udev if the module is expected * to reset itself into a fully different layout, e.g. a fastboot device */ if (fu_mm_device_get_update_methods(FU_MM_DEVICE(device)) & MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT) { self->udev_client = g_udev_client_new(subsystems); g_signal_connect(G_UDEV_CLIENT(self->udev_client), "uevent", G_CALLBACK(fu_mm_plugin_udev_uevent_cb), plugin); } return TRUE; } static void fu_mm_plugin_ensure_modem_power_inhibit(FuPlugin *plugin, FuDevice *device) { if (g_file_test(MODEM_POWER_SYSFS_PATH, G_FILE_TEST_EXISTS)) { fu_device_inhibit(device, "modem-power", "The modem-power kernel driver cannot be used"); } else { fu_device_uninhibit(device, "modem-power"); } } static void fu_mm_plugin_device_add(FuPlugin *plugin, MMObject *modem) { FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); const gchar *object_path = mm_object_get_path(modem); g_autoptr(FuMmDevice) dev = NULL; g_autoptr(GError) error = NULL; g_debug("added modem: %s", object_path); if (fu_plugin_cache_lookup(plugin, object_path) != NULL) { g_warning("MM device already added, ignoring"); return; } dev = fu_mm_device_new(fu_plugin_get_context(plugin), self->manager, modem); if (!fu_device_setup(FU_DEVICE(dev), &error)) { g_debug("failed to probe MM device: %s", error->message); return; } fu_mm_plugin_ensure_modem_power_inhibit(plugin, FU_DEVICE(dev)); fu_plugin_device_add(plugin, FU_DEVICE(dev)); fu_plugin_cache_add(plugin, object_path, dev); fu_plugin_cache_add(plugin, fu_device_get_physical_id(FU_DEVICE(dev)), dev); } static void fu_mm_plugin_device_added_cb(MMManager *manager, MMObject *modem, FuPlugin *plugin) { fu_mm_plugin_device_add(plugin, modem); } static void fu_mm_plugin_device_removed_cb(MMManager *manager, MMObject *modem, FuPlugin *plugin) { const gchar *object_path = mm_object_get_path(modem); FuMmDevice *dev = fu_plugin_cache_lookup(plugin, object_path); MMModemFirmwareUpdateMethod update_methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE; if (dev == NULL) return; g_debug("removed modem: %s", mm_object_get_path(modem)); #if MM_CHECK_VERSION(1, 19, 1) /* No information will be displayed during the upgrade process if the * device is removed, the main reason is that device is "removed" from * ModemManager, but it still exists in the system */ update_methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU | MM_MODEM_FIRMWARE_UPDATE_METHOD_SAHARA; #else update_methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU; #endif if (!(fu_mm_device_get_update_methods(FU_MM_DEVICE(dev)) & update_methods)) { fu_plugin_cache_remove(plugin, object_path); fu_plugin_device_remove(plugin, FU_DEVICE(dev)); } } static void fu_mm_plugin_teardown_manager(FuPlugin *plugin) { FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); if (self->manager_ready) { g_debug("ModemManager no longer available"); g_signal_handlers_disconnect_by_func(self->manager, G_CALLBACK(fu_mm_plugin_device_added_cb), plugin); g_signal_handlers_disconnect_by_func(self->manager, G_CALLBACK(fu_mm_plugin_device_removed_cb), plugin); self->manager_ready = FALSE; } } static void fu_mm_plugin_setup_manager(FuPlugin *plugin) { FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); const gchar *version = mm_manager_get_version(self->manager); GList *list; if (fu_version_compare(version, MM_REQUIRED_VERSION, FWUPD_VERSION_FORMAT_TRIPLET) < 0) { g_warning("ModemManager %s is available, but need at least %s", version, MM_REQUIRED_VERSION); return; } g_info("ModemManager %s is available", version); g_signal_connect(G_DBUS_OBJECT_MANAGER(self->manager), "object-added", G_CALLBACK(fu_mm_plugin_device_added_cb), plugin); g_signal_connect(G_DBUS_OBJECT_MANAGER(self->manager), "object-removed", G_CALLBACK(fu_mm_plugin_device_removed_cb), plugin); list = g_dbus_object_manager_get_objects(G_DBUS_OBJECT_MANAGER(self->manager)); for (GList *l = list; l != NULL; l = g_list_next(l)) { MMObject *modem = MM_OBJECT(l->data); fu_mm_plugin_device_add(plugin, modem); g_object_unref(modem); } g_list_free(list); self->manager_ready = TRUE; } static void fu_mm_plugin_name_owner_updated(FuPlugin *plugin) { FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); g_autofree gchar *name_owner = NULL; name_owner = g_dbus_object_manager_client_get_name_owner( G_DBUS_OBJECT_MANAGER_CLIENT(self->manager)); if (name_owner != NULL) fu_mm_plugin_setup_manager(plugin); else fu_mm_plugin_teardown_manager(plugin); } static gboolean fu_mm_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); g_signal_connect_swapped(MM_MANAGER(self->manager), "notify::name-owner", G_CALLBACK(fu_mm_plugin_name_owner_updated), plugin); fu_mm_plugin_name_owner_updated(plugin); return TRUE; } static void fu_mm_plugin_modem_power_changed_cb(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { FuPlugin *plugin = FU_PLUGIN(user_data); GPtrArray *devices = fu_plugin_get_devices(plugin); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); fu_mm_plugin_ensure_modem_power_inhibit(plugin, device); } } static gboolean fu_mm_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GFile) file = g_file_new_for_path(MODEM_POWER_SYSFS_PATH); connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) return FALSE; self->manager = mm_manager_new_sync(connection, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START, NULL, error); if (self->manager == NULL) return FALSE; /* detect presence of unsupported modem-power driver */ self->modem_power_monitor = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, error); if (self->modem_power_monitor == NULL) return FALSE; g_signal_connect(self->modem_power_monitor, "changed", G_CALLBACK(fu_mm_plugin_modem_power_changed_cb), plugin); return TRUE; } static gboolean fu_mm_plugin_detach(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; /* inhibit device and track it inside the plugin, not bound to the * lifetime of the FuMmDevice, because that object will only exist for * as long as the ModemManager device exists, and inhibiting will * implicitly remove the device from ModemManager. */ if (self->shadow_device == NULL) { if (!fu_mm_plugin_inhibit_device(plugin, device, error)) return FALSE; } /* reset */ if (!fu_device_detach_full(device, progress, error)) { fu_mm_plugin_uninhibit_device(plugin); return FALSE; } /* note: wait for replug set by device if it really needs it */ return TRUE; } static void fu_mm_plugin_device_attach_finished(gpointer user_data) { FuPlugin *plugin = FU_PLUGIN(user_data); fu_mm_plugin_uninhibit_device(plugin); } static gboolean fu_mm_plugin_attach(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; /* schedule device attach asynchronously, which is extremely important * so that engine can setup the device "waiting" logic before the actual * attach procedure happens (which will reset the module if it worked * properly) */ if (!fu_device_attach_full(device, progress, error)) return FALSE; /* this signal will always be emitted asynchronously */ g_signal_connect_swapped(FU_DEVICE(device), "attach-finished", G_CALLBACK(fu_mm_plugin_device_attach_finished), plugin); return TRUE; } static gboolean fu_mm_plugin_backend_device_added(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { FuDevice *device_tmp; g_autoptr(GUdevDevice) udev_device = NULL; /* interesting device? */ if (!FU_IS_USB_DEVICE(device)) return TRUE; /* look up the FuMmDevice for the USB device that just appeared */ udev_device = fu_usb_device_find_udev_device(FU_USB_DEVICE(device), error); if (udev_device == NULL) return FALSE; device_tmp = fu_plugin_cache_lookup(plugin, g_udev_device_get_sysfs_path(udev_device)); if (device_tmp == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s not added by ModemManager", g_udev_device_get_sysfs_path(udev_device)); return FALSE; } fu_mm_device_set_usb_device(FU_MM_DEVICE(device_tmp), FU_USB_DEVICE(device)); return TRUE; } static void fu_mm_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); (void)fu_plugin_alloc_data(plugin, sizeof(FuModemManagerPlugin)); } static void fu_mm_plugin_finalize(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuModemManagerPlugin *self = FU_MODEM_MANAGER_PLUGIN(plugin); fu_mm_plugin_uninhibit_device(plugin); if (self->udev_timeout_id) g_source_remove(self->udev_timeout_id); if (self->udev_client) g_object_unref(self->udev_client); if (self->manager != NULL) g_object_unref(self->manager); if (self->modem_power_monitor != NULL) g_object_unref(self->modem_power_monitor); /* G_OBJECT_CLASS(fu_mm_plugin_parent_class)->finalize() not required as modular */ } void fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs) { vfuncs->load = fu_mm_plugin_load; vfuncs->constructed = fu_mm_plugin_constructed; vfuncs->finalize = fu_mm_plugin_finalize; vfuncs->to_string = fu_mm_plugin_to_string; vfuncs->startup = fu_mm_plugin_startup; vfuncs->coldplug = fu_mm_plugin_coldplug; vfuncs->attach = fu_mm_plugin_attach; vfuncs->detach = fu_mm_plugin_detach; vfuncs->backend_device_added = fu_mm_plugin_backend_device_added; } fwupd-1.9.16/plugins/modem-manager/fu-mm-utils.c000066400000000000000000000123771460375044200214710ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-mm-utils.h" static gchar * find_device_bus_subsystem(GUdevDevice *device) { g_autoptr(GUdevDevice) iter = NULL; iter = g_object_ref(device); while (iter != NULL) { g_autoptr(GUdevDevice) next = NULL; const gchar *subsys; /* stop search as soon as we find a parent object * of one of the bus subsystems supported in * ModemManager */ subsys = g_udev_device_get_subsystem(iter); if ((g_strcmp0(subsys, "usb") == 0) || (g_strcmp0(subsys, "pcmcia") == 0) || (g_strcmp0(subsys, "pci") == 0) || (g_strcmp0(subsys, "platform") == 0) || (g_strcmp0(subsys, "pnp") == 0) || (g_strcmp0(subsys, "sdio") == 0)) { return g_ascii_strup(subsys, -1); } next = g_udev_device_get_parent(iter); g_set_object(&iter, next); } /* no more parents to check */ return NULL; } gboolean fu_mm_utils_get_udev_port_info(GUdevDevice *device, gchar **out_device_bus, gchar **out_device_sysfs_path, gint *out_port_usb_ifnum, GError **error) { gint port_usb_ifnum = -1; g_autoptr(GUdevDevice) parent = NULL; g_autofree gchar *device_sysfs_path = NULL; g_autofree gchar *device_bus = NULL; /* lookup the main bus the device is in; for supported devices it will * usually be either 'PCI' or 'USB' */ device_bus = find_device_bus_subsystem(device); if (device_bus == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to lookup device info: bus not found"); return FALSE; } if (g_strcmp0(device_bus, "USB") == 0) { /* ID_USB_INTERFACE_NUM is set on the port device itself */ const gchar *aux = g_udev_device_get_property(device, "ID_USB_INTERFACE_NUM"); if (aux != NULL) port_usb_ifnum = (guint16)g_ascii_strtoull(aux, NULL, 16); /* we need to traverse all parents of the give udev device until we find * the first 'usb_device' reported, which is the GUdevDevice associated with * the full USB device (i.e. all ports of the same device). */ parent = g_udev_device_get_parent(device); while (parent != NULL) { g_autoptr(GUdevDevice) next = NULL; if (g_strcmp0(g_udev_device_get_devtype(parent), "usb_device") == 0) { device_sysfs_path = g_strdup(g_udev_device_get_sysfs_path(parent)); break; } /* check next parent */ next = g_udev_device_get_parent(parent); g_set_object(&parent, next); } } else if (g_strcmp0(device_bus, "PCI") == 0) { /* the first parent in the 'pci' subsystem is our physical device */ parent = g_udev_device_get_parent(device); while (parent != NULL) { g_autoptr(GUdevDevice) next = NULL; if (g_strcmp0(g_udev_device_get_subsystem(parent), "pci") == 0) { device_sysfs_path = g_strdup(g_udev_device_get_sysfs_path(parent)); break; } /* check next parent */ next = g_udev_device_get_parent(parent); g_set_object(&parent, next); } } else { /* other subsystems, we don't support firmware upgrade for those */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "device bus unsupported: %s", device_bus); return FALSE; } if (device_sysfs_path == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to lookup device info: physical device not found"); return FALSE; } if (out_port_usb_ifnum != NULL) *out_port_usb_ifnum = port_usb_ifnum; if (out_device_sysfs_path != NULL) *out_device_sysfs_path = g_steal_pointer(&device_sysfs_path); if (out_device_bus != NULL) *out_device_bus = g_steal_pointer(&device_bus); return TRUE; } gboolean fu_mm_utils_get_port_info(const gchar *path, gchar **out_device_bus, gchar **out_device_sysfs_path, gint *out_port_usb_ifnum, GError **error) { g_autoptr(GUdevClient) client = NULL; g_autoptr(GUdevDevice) dev = NULL; client = g_udev_client_new(NULL); dev = g_udev_client_query_by_device_file(client, path); if (dev == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to lookup device by path"); return FALSE; } return fu_mm_utils_get_udev_port_info(dev, out_device_bus, out_device_sysfs_path, out_port_usb_ifnum, error); } gboolean fu_mm_utils_find_device_file(const gchar *device_sysfs_path, const gchar *subsystem, gchar **out_device_file, GError **error) { GList *devices; g_autoptr(GUdevClient) client = NULL; g_autofree gchar *device_file = NULL; g_return_val_if_fail(out_device_file != NULL, FALSE); client = g_udev_client_new(NULL); devices = g_udev_client_query_by_subsystem(client, subsystem); for (GList *l = devices; l != NULL; l = g_list_next(l)) { if (g_str_has_prefix(g_udev_device_get_sysfs_path(G_UDEV_DEVICE(l->data)), device_sysfs_path)) { device_file = g_strdup(g_udev_device_get_device_file(l->data)); if (device_file != NULL) break; } } g_list_free_full(devices, g_object_unref); if (device_file == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find %s port in device %s", subsystem, device_sysfs_path); return FALSE; } *out_device_file = g_steal_pointer(&device_file); return TRUE; } fwupd-1.9.16/plugins/modem-manager/fu-mm-utils.h000066400000000000000000000013261460375044200214660ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #ifndef __FU_MM_UTILS_H #define __FU_MM_UTILS_H #include gboolean fu_mm_utils_get_udev_port_info(GUdevDevice *dev, gchar **device_bus, gchar **device_sysfs_path, gint *port_usb_ifnum, GError **error); gboolean fu_mm_utils_get_port_info(const gchar *path, gchar **device_bus, gchar **device_sysfs_path, gint *port_usb_ifnum, GError **error); gboolean fu_mm_utils_find_device_file(const gchar *device_sysfs_path, const gchar *subsystem, gchar **out_device_file, GError **error); #endif /* __FU_MM_UTILS_H */ fwupd-1.9.16/plugins/modem-manager/fu-qmi-pdc-updater.c000066400000000000000000000506541460375044200227160ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-qmi-pdc-updater.h" #define FU_QMI_PDC_MAX_OPEN_ATTEMPTS 8 struct _FuQmiPdcUpdater { GObject parent_instance; gchar *qmi_port; QmiDevice *qmi_device; QmiClientPdc *qmi_client; }; G_DEFINE_TYPE(FuQmiPdcUpdater, fu_qmi_pdc_updater, G_TYPE_OBJECT) typedef struct { GMainLoop *mainloop; QmiDevice *qmi_device; QmiClientPdc *qmi_client; GError *error; guint open_attempts; } OpenContext; static void fu_qmi_pdc_updater_qmi_device_open_attempt(OpenContext *ctx); static void fu_qmi_pdc_updater_qmi_device_open_abort_ready(GObject *qmi_device, GAsyncResult *res, gpointer user_data) { OpenContext *ctx = (OpenContext *)user_data; g_warn_if_fail(ctx->error != NULL); /* ignore errors when aborting open */ qmi_device_close_finish(QMI_DEVICE(qmi_device), res, NULL); ctx->open_attempts--; if (ctx->open_attempts == 0) { g_clear_object(&ctx->qmi_client); g_clear_object(&ctx->qmi_device); g_main_loop_quit(ctx->mainloop); return; } /* retry */ g_clear_error(&ctx->error); fu_qmi_pdc_updater_qmi_device_open_attempt(ctx); } static void fu_qmi_pdc_updater_open_abort(OpenContext *ctx) { qmi_device_close_async(ctx->qmi_device, 15, NULL, fu_qmi_pdc_updater_qmi_device_open_abort_ready, ctx); } static void fu_qmi_pdc_updater_qmi_device_allocate_client_ready(GObject *qmi_device, GAsyncResult *res, gpointer user_data) { OpenContext *ctx = (OpenContext *)user_data; ctx->qmi_client = QMI_CLIENT_PDC( qmi_device_allocate_client_finish(QMI_DEVICE(qmi_device), res, &ctx->error)); if (ctx->qmi_client == NULL) { fu_qmi_pdc_updater_open_abort(ctx); return; } g_main_loop_quit(ctx->mainloop); } static void fu_qmi_pdc_updater_qmi_device_open_ready(GObject *qmi_device, GAsyncResult *res, gpointer user_data) { OpenContext *ctx = (OpenContext *)user_data; if (!qmi_device_open_finish(QMI_DEVICE(qmi_device), res, &ctx->error)) { fu_qmi_pdc_updater_open_abort(ctx); return; } qmi_device_allocate_client(ctx->qmi_device, QMI_SERVICE_PDC, QMI_CID_NONE, 5, NULL, fu_qmi_pdc_updater_qmi_device_allocate_client_ready, ctx); } static void fu_qmi_pdc_updater_qmi_device_open_attempt(OpenContext *ctx) { QmiDeviceOpenFlags open_flags = QMI_DEVICE_OPEN_FLAGS_NONE; /* automatically detect QMI and MBIM ports */ open_flags |= QMI_DEVICE_OPEN_FLAGS_AUTO; /* qmi pdc requires indications, so enable them by default */ open_flags |= QMI_DEVICE_OPEN_FLAGS_EXPECT_INDICATIONS; /* all communication through the proxy */ open_flags |= QMI_DEVICE_OPEN_FLAGS_PROXY; g_debug("trying to open QMI device..."); qmi_device_open(ctx->qmi_device, open_flags, 5, NULL, fu_qmi_pdc_updater_qmi_device_open_ready, ctx); } static void fu_qmi_pdc_updater_qmi_device_new_ready(GObject *source, GAsyncResult *res, gpointer user_data) { OpenContext *ctx = (OpenContext *)user_data; ctx->qmi_device = qmi_device_new_finish(res, &ctx->error); if (ctx->qmi_device == NULL) { g_main_loop_quit(ctx->mainloop); return; } fu_qmi_pdc_updater_qmi_device_open_attempt(ctx); } gboolean fu_qmi_pdc_updater_open(FuQmiPdcUpdater *self, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new(NULL, FALSE); g_autoptr(GFile) qmi_device_file = g_file_new_for_path(self->qmi_port); OpenContext ctx = { .mainloop = mainloop, .qmi_device = NULL, .qmi_client = NULL, .error = NULL, .open_attempts = FU_QMI_PDC_MAX_OPEN_ATTEMPTS, }; qmi_device_new(qmi_device_file, NULL, fu_qmi_pdc_updater_qmi_device_new_ready, &ctx); g_main_loop_run(mainloop); /* either we have all device, client and config list set, or otherwise error is set */ if ((ctx.qmi_device != NULL) && (ctx.qmi_client != NULL)) { g_warn_if_fail(!ctx.error); self->qmi_device = ctx.qmi_device; self->qmi_client = ctx.qmi_client; /* success */ return TRUE; } g_warn_if_fail(ctx.error != NULL); g_warn_if_fail(ctx.qmi_device == NULL); g_warn_if_fail(ctx.qmi_client == NULL); g_propagate_error(error, ctx.error); return FALSE; } typedef struct { GMainLoop *mainloop; QmiDevice *qmi_device; QmiClientPdc *qmi_client; GError *error; } CloseContext; static void fu_qmi_pdc_updater_qmi_device_close_ready(GObject *qmi_device, GAsyncResult *res, gpointer user_data) { CloseContext *ctx = (CloseContext *)user_data; /* ignore errors when closing if we had one already set when releasing client */ qmi_device_close_finish(QMI_DEVICE(qmi_device), res, (ctx->error == NULL) ? &ctx->error : NULL); g_clear_object(&ctx->qmi_device); g_main_loop_quit(ctx->mainloop); } static void fu_qmi_pdc_updater_qmi_device_release_client_ready(GObject *qmi_device, GAsyncResult *res, gpointer user_data) { CloseContext *ctx = (CloseContext *)user_data; qmi_device_release_client_finish(QMI_DEVICE(qmi_device), res, &ctx->error); g_clear_object(&ctx->qmi_client); qmi_device_close_async(ctx->qmi_device, 15, NULL, fu_qmi_pdc_updater_qmi_device_close_ready, ctx); } gboolean fu_qmi_pdc_updater_close(FuQmiPdcUpdater *self, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new(NULL, FALSE); CloseContext ctx = { .mainloop = mainloop, .qmi_device = g_steal_pointer(&self->qmi_device), .qmi_client = g_steal_pointer(&self->qmi_client), }; qmi_device_release_client(ctx.qmi_device, QMI_CLIENT(ctx.qmi_client), QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID, 5, NULL, fu_qmi_pdc_updater_qmi_device_release_client_ready, &ctx); g_main_loop_run(mainloop); /* we should always have both device and client cleared, and optionally error set */ g_warn_if_fail(ctx.qmi_device == NULL); g_warn_if_fail(ctx.qmi_client == NULL); if (ctx.error != NULL) { g_propagate_error(error, ctx.error); return FALSE; } return TRUE; } #define QMI_LOAD_CHUNK_SIZE 0x400 typedef struct { GMainLoop *mainloop; QmiClientPdc *qmi_client; GError *error; gulong indication_id; guint timeout_id; GBytes *blob; GArray *digest; gsize offset; guint token; } WriteContext; static void fu_qmi_pdc_updater_load_config(WriteContext *ctx); static gboolean fu_qmi_pdc_updater_load_config_timeout(gpointer user_data) { WriteContext *ctx = user_data; ctx->timeout_id = 0; g_signal_handler_disconnect(ctx->qmi_client, ctx->indication_id); ctx->indication_id = 0; g_set_error_literal(&ctx->error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't load mcfg: timed out"); g_main_loop_quit(ctx->mainloop); return G_SOURCE_REMOVE; } static void fu_qmi_pdc_updater_load_config_indication(QmiClientPdc *client, QmiIndicationPdcLoadConfigOutput *output, WriteContext *ctx) { gboolean frame_reset; guint32 remaining_size; guint16 error_code = 0; g_source_remove(ctx->timeout_id); ctx->timeout_id = 0; g_signal_handler_disconnect(ctx->qmi_client, ctx->indication_id); ctx->indication_id = 0; if (!qmi_indication_pdc_load_config_output_get_indication_result(output, &error_code, &ctx->error)) { g_main_loop_quit(ctx->mainloop); return; } if (error_code != 0) { /* when a given mcfg file already exists in the device, an "invalid id" error is * returned; the error naming here is a bit off, as the same protocol error number * is used both for 'invalid id' and 'invalid qos id' */ if (error_code == QMI_PROTOCOL_ERROR_INVALID_QOS_ID) { g_debug("file already available in device"); g_main_loop_quit(ctx->mainloop); return; } g_set_error(&ctx->error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't load mcfg: %s", qmi_protocol_error_get_string((QmiProtocolError)error_code)); g_main_loop_quit(ctx->mainloop); return; } if (qmi_indication_pdc_load_config_output_get_frame_reset(output, &frame_reset, NULL) && frame_reset) { g_set_error(&ctx->error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't load mcfg: sent data discarded"); g_main_loop_quit(ctx->mainloop); return; } if (!qmi_indication_pdc_load_config_output_get_remaining_size(output, &remaining_size, &ctx->error)) { g_prefix_error(&ctx->error, "couldn't load remaining size: "); g_main_loop_quit(ctx->mainloop); return; } if (remaining_size == 0) { g_debug("finished loading mcfg"); g_main_loop_quit(ctx->mainloop); return; } g_debug("loading next chunk (%u bytes remaining)", remaining_size); fu_qmi_pdc_updater_load_config(ctx); } static void fu_qmi_pdc_updater_load_config_ready(GObject *qmi_client, GAsyncResult *res, gpointer user_data) { WriteContext *ctx = (WriteContext *)user_data; g_autoptr(QmiMessagePdcLoadConfigOutput) output = NULL; output = qmi_client_pdc_load_config_finish(QMI_CLIENT_PDC(qmi_client), res, &ctx->error); if (output == NULL) { g_main_loop_quit(ctx->mainloop); return; } if (!qmi_message_pdc_load_config_output_get_result(output, &ctx->error)) { g_main_loop_quit(ctx->mainloop); return; } /* after receiving the response to our request, we now expect an indication * with the actual result of the operation */ g_warn_if_fail(ctx->indication_id == 0); ctx->indication_id = g_signal_connect(QMI_CLIENT_PDC(ctx->qmi_client), "load-config", G_CALLBACK(fu_qmi_pdc_updater_load_config_indication), ctx); /* don't wait forever */ g_warn_if_fail(ctx->timeout_id == 0); ctx->timeout_id = g_timeout_add_seconds(5, fu_qmi_pdc_updater_load_config_timeout, ctx); } static void fu_qmi_pdc_updater_load_config(WriteContext *ctx) { g_autoptr(QmiMessagePdcLoadConfigInput) input = NULL; g_autoptr(GArray) chunk = NULL; gsize full_size; gsize chunk_size; g_autoptr(GError) error = NULL; input = qmi_message_pdc_load_config_input_new(); qmi_message_pdc_load_config_input_set_token(input, ctx->token++, NULL); full_size = g_bytes_get_size(ctx->blob); if ((ctx->offset + QMI_LOAD_CHUNK_SIZE) > full_size) chunk_size = full_size - ctx->offset; else chunk_size = QMI_LOAD_CHUNK_SIZE; chunk = g_array_sized_new(FALSE, FALSE, sizeof(guint8), chunk_size); g_array_set_size(chunk, chunk_size); if (!fu_memcpy_safe((guint8 *)chunk->data, chunk_size, 0x0, /* dst */ (const guint8 *)g_bytes_get_data(ctx->blob, NULL), /* src */ g_bytes_get_size(ctx->blob), ctx->offset, chunk_size, &error)) { g_critical("failed to copy chunk: %s", error->message); } qmi_message_pdc_load_config_input_set_config_chunk(input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, ctx->digest, full_size, chunk, NULL); ctx->offset += chunk_size; qmi_client_pdc_load_config(ctx->qmi_client, input, 10, NULL, fu_qmi_pdc_updater_load_config_ready, ctx); } static GArray * fu_qmi_pdc_updater_get_checksum(GBytes *blob) { gsize file_size; gsize hash_size; GArray *digest; g_autoptr(GChecksum) checksum = NULL; /* get checksum, to be used as unique id */ file_size = g_bytes_get_size(blob); hash_size = g_checksum_type_get_length(G_CHECKSUM_SHA1); checksum = g_checksum_new(G_CHECKSUM_SHA1); g_checksum_update(checksum, g_bytes_get_data(blob, NULL), file_size); /* libqmi expects a GArray of bytes, not a GByteArray */ digest = g_array_sized_new(FALSE, FALSE, sizeof(guint8), hash_size); g_array_set_size(digest, hash_size); g_checksum_get_digest(checksum, (guint8 *)digest->data, &hash_size); return digest; } GArray * fu_qmi_pdc_updater_write(FuQmiPdcUpdater *self, const gchar *filename, GBytes *blob, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new(NULL, FALSE); g_autoptr(GArray) digest = fu_qmi_pdc_updater_get_checksum(blob); WriteContext ctx = { .mainloop = mainloop, .qmi_client = self->qmi_client, .error = NULL, .indication_id = 0, .timeout_id = 0, .blob = blob, .digest = digest, .offset = 0, .token = 0, }; fu_qmi_pdc_updater_load_config(&ctx); g_main_loop_run(mainloop); if (ctx.error != NULL) { g_propagate_error(error, ctx.error); return NULL; } return g_steal_pointer(&digest); } typedef struct { GMainLoop *mainloop; QmiClientPdc *qmi_client; GError *error; gulong indication_id; guint timeout_id; GArray *digest; guint token; } ActivateContext; static gboolean fu_qmi_pdc_updater_activate_config_timeout(gpointer user_data) { ActivateContext *ctx = user_data; ctx->timeout_id = 0; g_signal_handler_disconnect(ctx->qmi_client, ctx->indication_id); ctx->indication_id = 0; /* not an error, the device may go away without sending the indication */ g_main_loop_quit(ctx->mainloop); return G_SOURCE_REMOVE; } static void fu_qmi_pdc_updater_activate_config_indication(QmiClientPdc *client, QmiIndicationPdcActivateConfigOutput *output, ActivateContext *ctx) { guint16 error_code = 0; g_source_remove(ctx->timeout_id); ctx->timeout_id = 0; g_signal_handler_disconnect(ctx->qmi_client, ctx->indication_id); ctx->indication_id = 0; if (!qmi_indication_pdc_activate_config_output_get_indication_result(output, &error_code, &ctx->error)) { g_main_loop_quit(ctx->mainloop); return; } if (error_code != 0) { g_set_error(&ctx->error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't activate config: %s", qmi_protocol_error_get_string((QmiProtocolError)error_code)); g_main_loop_quit(ctx->mainloop); return; } /* assume ok */ g_debug("successful activate configuration indication: assuming device reset is ongoing"); g_main_loop_quit(ctx->mainloop); } static void fu_qmi_pdc_updater_activate_config_ready(GObject *qmi_client, GAsyncResult *res, gpointer user_data) { ActivateContext *ctx = (ActivateContext *)user_data; g_autoptr(QmiMessagePdcActivateConfigOutput) output = NULL; output = qmi_client_pdc_activate_config_finish(QMI_CLIENT_PDC(qmi_client), res, &ctx->error); if (output == NULL) { /* If we didn't receive a response, this is a good indication that the device * reset itself, we can consider this a successful operation. * Note: not using g_error_matches() to avoid matching the domain, because the * error may be either QMI_CORE_ERROR_TIMEOUT or MBIM_CORE_ERROR_TIMEOUT (same * numeric value), and we don't want to build-depend on libmbim just for this. */ if (ctx->error->code == QMI_CORE_ERROR_TIMEOUT) { g_debug("request to activate configuration timed out: assuming device " "reset is ongoing"); g_clear_error(&ctx->error); } g_main_loop_quit(ctx->mainloop); return; } if (!qmi_message_pdc_activate_config_output_get_result(output, &ctx->error)) { g_main_loop_quit(ctx->mainloop); return; } /* When we activate the config, if the operation is successful, we'll just * see the modem going away completely. So, do not consider an error the timeout * waiting for the Activate Config indication, as that is actually a good * thing. */ g_warn_if_fail(ctx->indication_id == 0); ctx->indication_id = g_signal_connect(QMI_CLIENT_PDC(ctx->qmi_client), "activate-config", G_CALLBACK(fu_qmi_pdc_updater_activate_config_indication), ctx); /* don't wait forever */ g_warn_if_fail(ctx->timeout_id == 0); ctx->timeout_id = g_timeout_add_seconds(5, fu_qmi_pdc_updater_activate_config_timeout, ctx); } static void fu_qmi_pdc_updater_activate_config(ActivateContext *ctx) { g_autoptr(QmiMessagePdcActivateConfigInput) input = NULL; input = qmi_message_pdc_activate_config_input_new(); qmi_message_pdc_activate_config_input_set_config_type(input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, NULL); qmi_message_pdc_activate_config_input_set_token(input, ctx->token++, NULL); g_debug("activating selected configuration..."); qmi_client_pdc_activate_config(ctx->qmi_client, input, 5, NULL, fu_qmi_pdc_updater_activate_config_ready, ctx); } static gboolean fu_qmi_pdc_updater_set_selected_config_timeout(gpointer user_data) { ActivateContext *ctx = user_data; ctx->timeout_id = 0; g_signal_handler_disconnect(ctx->qmi_client, ctx->indication_id); ctx->indication_id = 0; g_set_error_literal(&ctx->error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't set selected config: timed out"); g_main_loop_quit(ctx->mainloop); return G_SOURCE_REMOVE; } static void fu_qmi_pdc_updater_set_selected_config_indication(QmiClientPdc *client, QmiIndicationPdcSetSelectedConfigOutput *output, ActivateContext *ctx) { guint16 error_code = 0; g_source_remove(ctx->timeout_id); ctx->timeout_id = 0; g_signal_handler_disconnect(ctx->qmi_client, ctx->indication_id); ctx->indication_id = 0; if (!qmi_indication_pdc_set_selected_config_output_get_indication_result(output, &error_code, &ctx->error)) { g_main_loop_quit(ctx->mainloop); return; } if (error_code != 0) { g_set_error(&ctx->error, G_IO_ERROR, G_IO_ERROR_FAILED, "couldn't set selected config: %s", qmi_protocol_error_get_string((QmiProtocolError)error_code)); g_main_loop_quit(ctx->mainloop); return; } g_debug("current configuration successfully selected..."); /* now activate config */ fu_qmi_pdc_updater_activate_config(ctx); } static void fu_qmi_pdc_updater_set_selected_config_ready(GObject *qmi_client, GAsyncResult *res, gpointer user_data) { ActivateContext *ctx = (ActivateContext *)user_data; g_autoptr(QmiMessagePdcSetSelectedConfigOutput) output = NULL; output = qmi_client_pdc_set_selected_config_finish(QMI_CLIENT_PDC(qmi_client), res, &ctx->error); if (output == NULL) { g_main_loop_quit(ctx->mainloop); return; } if (!qmi_message_pdc_set_selected_config_output_get_result(output, &ctx->error)) { g_main_loop_quit(ctx->mainloop); return; } /* after receiving the response to our request, we now expect an indication * with the actual result of the operation */ g_warn_if_fail(ctx->indication_id == 0); ctx->indication_id = g_signal_connect(QMI_CLIENT_PDC(ctx->qmi_client), "set-selected-config", G_CALLBACK(fu_qmi_pdc_updater_set_selected_config_indication), ctx); /* don't wait forever */ g_warn_if_fail(ctx->timeout_id == 0); ctx->timeout_id = g_timeout_add_seconds(5, fu_qmi_pdc_updater_set_selected_config_timeout, ctx); } static void fu_qmi_pdc_updater_set_selected_config(ActivateContext *ctx) { g_autoptr(QmiMessagePdcSetSelectedConfigInput) input = NULL; #if !QMI_CHECK_VERSION(1, 32, 0) QmiConfigTypeAndId type_and_id = { .config_type = QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, .id = ctx->digest, }; #endif input = qmi_message_pdc_set_selected_config_input_new(); #if QMI_CHECK_VERSION(1, 32, 0) qmi_message_pdc_set_selected_config_input_set_type_with_id_v2( input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, ctx->digest, NULL); #else qmi_message_pdc_set_selected_config_input_set_type_with_id(input, &type_and_id, NULL); #endif qmi_message_pdc_set_selected_config_input_set_token(input, ctx->token++, NULL); g_debug("selecting current configuration..."); qmi_client_pdc_set_selected_config(ctx->qmi_client, input, 10, NULL, fu_qmi_pdc_updater_set_selected_config_ready, ctx); } gboolean fu_qmi_pdc_updater_activate(FuQmiPdcUpdater *self, GArray *digest, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new(NULL, FALSE); ActivateContext ctx = { .mainloop = mainloop, .qmi_client = self->qmi_client, .error = NULL, .indication_id = 0, .timeout_id = 0, .digest = digest, .token = 0, }; fu_qmi_pdc_updater_set_selected_config(&ctx); g_main_loop_run(mainloop); if (ctx.error != NULL) { g_propagate_error(error, ctx.error); return FALSE; } return TRUE; } static void fu_qmi_pdc_updater_init(FuQmiPdcUpdater *self) { } static void fu_qmi_pdc_updater_finalize(GObject *object) { FuQmiPdcUpdater *self = FU_QMI_PDC_UPDATER(object); g_warn_if_fail(self->qmi_client == NULL); g_warn_if_fail(self->qmi_device == NULL); g_free(self->qmi_port); G_OBJECT_CLASS(fu_qmi_pdc_updater_parent_class)->finalize(object); } static void fu_qmi_pdc_updater_class_init(FuQmiPdcUpdaterClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_qmi_pdc_updater_finalize; } FuQmiPdcUpdater * fu_qmi_pdc_updater_new(const gchar *qmi_port) { FuQmiPdcUpdater *self = g_object_new(FU_TYPE_QMI_PDC_UPDATER, NULL); self->qmi_port = g_strdup(qmi_port); return self; } fwupd-1.9.16/plugins/modem-manager/fu-qmi-pdc-updater.h000066400000000000000000000014701460375044200227130ustar00rootroot00000000000000/* * Copyright (C) 2019 Aleksander Morgado * * SPDX-License-Identifier: LGPL-2.1+ */ #ifndef __FU_QMI_PDC_UPDATER_H #define __FU_QMI_PDC_UPDATER_H #include #define FU_TYPE_QMI_PDC_UPDATER (fu_qmi_pdc_updater_get_type()) G_DECLARE_FINAL_TYPE(FuQmiPdcUpdater, fu_qmi_pdc_updater, FU, QMI_PDC_UPDATER, GObject) FuQmiPdcUpdater * fu_qmi_pdc_updater_new(const gchar *qmi_port); gboolean fu_qmi_pdc_updater_open(FuQmiPdcUpdater *self, GError **error); GArray * fu_qmi_pdc_updater_write(FuQmiPdcUpdater *self, const gchar *filename, GBytes *blob, GError **error); gboolean fu_qmi_pdc_updater_activate(FuQmiPdcUpdater *self, GArray *digest, GError **error); gboolean fu_qmi_pdc_updater_close(FuQmiPdcUpdater *self, GError **error); #endif /* __FU_QMI_PDC_UPDATER_H */ fwupd-1.9.16/plugins/modem-manager/fu-sahara-loader.c000066400000000000000000000342141460375044200224170ustar00rootroot00000000000000/* * Copyright (C) 2021 Quectel Wireless Solutions Co., Ltd. * Ivan Mikhanchuk * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-sahara-loader.h" #define SAHARA_VERSION 2 #define SAHARA_VERSION_COMPATIBLE 1 #define SAHARA_RAW_BUFFER_SIZE (4 * 1024) #define IO_TIMEOUT_MS 15000 struct _FuSaharaLoader { GObject parent_instance; FuUsbDevice *usb_device; int ep_in; int ep_out; gsize maxpktsize_in; gsize maxpktsize_out; }; G_DEFINE_TYPE(FuSaharaLoader, fu_sahara_loader, G_TYPE_OBJECT) /* Protocol definitions */ typedef enum { SAHARA_NO_CMD_ID = 0, SAHARA_HELLO_ID, SAHARA_HELLO_RESPONSE_ID, SAHARA_READ_DATA_ID, SAHARA_END_OF_IMAGE_TX_ID, SAHARA_DONE_ID, SAHARA_DONE_RESP_ID, SAHARA_RESET_ID, SAHARA_RESET_RESPONSE_ID, SAHARA_READ_DATA_64_BIT_ID = 0x12, SAHARA_LAST_CMD_ID } FuSaharaCommandId; typedef enum { SAHARA_STATUS_SUCCESS = 0, SAHARA_STATUS_FAILED, SAHARA_STATUS_LAST } FuSaharaStatusCode; typedef enum { SAHARA_MODE_IMAGE_TX_PENDING, SAHARA_MODE_IMAGE_TX_COMPLETE, SAHARA_MODE_LAST } FuSaharaMode; /* Sahara packet definition */ struct sahara_packet { guint32 command_id; guint32 length; union { struct { guint32 version; guint32 version_compatible; guint32 max_packet_length; guint32 mode; } hello; struct { guint32 version; guint32 version_compatible; guint32 status; guint32 mode; guint32 reserved[6]; } hello_response; struct { guint32 image_id; guint32 offset; guint32 length; } read_data; struct { guint32 image_id; guint32 status; } end_of_image_transfer; /* done packet = header only */ struct { guint32 image_transfer_status; } done_response; /* reset packet = header only */ /* reset response packet = header only */ struct { guint64 image_id; guint64 offset; guint64 length; } read_data_64bit; }; } __attribute((packed)); /* helper functions */ static FuSaharaCommandId sahara_packet_get_command_id(GByteArray *packet) { return ((struct sahara_packet *)(packet->data))->command_id; } static FuSaharaCommandId sahara_packet_get_length(GByteArray *packet) { return ((struct sahara_packet *)(packet->data))->length; } /* IO functions */ static gboolean fu_sahara_loader_find_interface(FuSaharaLoader *self, FuUsbDevice *usb_device, GError **error) { #ifdef HAVE_GUSB g_autoptr(GPtrArray) intfs = NULL; GUsbDevice *g_usb_device = fu_usb_device_get_dev(usb_device); /* all sahara devices use the same vid:pid pair */ if (g_usb_device_get_vid(g_usb_device) != 0x05c6 || g_usb_device_get_pid(g_usb_device) != 0x9008) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Wrong device and/or vendor id: 0x%04x 0x%04x", g_usb_device_get_vid(g_usb_device), g_usb_device_get_pid(g_usb_device)); return FALSE; } /* Parse usb interfaces and find suitable endpoints */ intfs = g_usb_device_get_interfaces(g_usb_device, error); if (intfs == NULL) return FALSE; for (guint i = 0; i < intfs->len; i++) { GUsbInterface *intf = g_ptr_array_index(intfs, i); if (g_usb_interface_get_class(intf) == 0xFF && g_usb_interface_get_subclass(intf) == 0xFF && g_usb_interface_get_protocol(intf) == 0xFF) { GUsbEndpoint *ep; g_autoptr(GPtrArray) endpoints = NULL; endpoints = g_usb_interface_get_endpoints(intf); if (endpoints == NULL || endpoints->len == 0) continue; for (guint j = 0; j < endpoints->len; j++) { ep = g_ptr_array_index(endpoints, j); if (g_usb_endpoint_get_direction(ep) == G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST) { self->ep_in = g_usb_endpoint_get_address(ep); self->maxpktsize_in = g_usb_endpoint_get_maximum_packet_size(ep); } else { self->ep_out = g_usb_endpoint_get_address(ep); self->maxpktsize_out = g_usb_endpoint_get_maximum_packet_size(ep); } } fu_usb_device_add_interface(usb_device, g_usb_interface_get_number(intf)); return TRUE; } } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no update interface found"); return FALSE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no GUsb support"); return FALSE; #endif } gboolean fu_sahara_loader_open(FuSaharaLoader *self, FuUsbDevice *usb_device, GError **error) { if (!fu_sahara_loader_find_interface(self, usb_device, error)) return FALSE; if (!fu_device_open(FU_DEVICE(usb_device), error)) return FALSE; self->usb_device = g_object_ref(usb_device); return TRUE; } gboolean fu_sahara_loader_close(FuSaharaLoader *self, GError **error) { if (!fu_device_close(FU_DEVICE(self->usb_device), error)) return FALSE; g_clear_object(&self->usb_device); return TRUE; } gboolean fu_sahara_loader_qdl_is_open(FuSaharaLoader *self) { if (self == NULL) return FALSE; return fu_usb_device_is_open(self->usb_device); } GByteArray * fu_sahara_loader_qdl_read(FuSaharaLoader *self, GError **error) { #ifdef HAVE_GUSB gsize actual_len = 0; g_autoptr(GByteArray) buf = g_byte_array_sized_new(SAHARA_RAW_BUFFER_SIZE); fu_byte_array_set_size(buf, SAHARA_RAW_BUFFER_SIZE, 0x00); if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(self->usb_device), self->ep_in, buf->data, buf->len, &actual_len, IO_TIMEOUT_MS, NULL, error)) { g_prefix_error(error, "failed to do bulk transfer (read): "); return NULL; } g_byte_array_set_size(buf, actual_len); g_debug("received %" G_GSIZE_FORMAT " bytes", actual_len); return g_steal_pointer(&buf); #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "GUsb not supported"); return NULL; #endif } static gboolean fu_sahara_loader_qdl_write(FuSaharaLoader *self, const guint8 *data, gsize sz, GError **error) { #ifdef HAVE_GUSB gsize actual_len = 0; g_autoptr(GPtrArray) chunks = NULL; g_autoptr(GByteArray) bytes = NULL; /* copy const data to mutable GByteArray */ bytes = g_byte_array_sized_new(sz); bytes = g_byte_array_append(bytes, data, sz); chunks = fu_chunk_array_mutable_new(bytes->data, bytes->len, 0, 0, self->maxpktsize_out); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(self->usb_device), self->ep_out, fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), &actual_len, IO_TIMEOUT_MS, NULL, error)) { g_prefix_error(error, "failed to do bulk transfer (write data): "); return FALSE; } if (actual_len != fu_chunk_get_data_sz(chk)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only wrote %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } } if (sz % self->maxpktsize_out == 0) { /* sent zlp packet if needed */ if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(self->usb_device), self->ep_out, NULL, 0, NULL, IO_TIMEOUT_MS, NULL, error)) { g_prefix_error(error, "failed to do bulk transfer (write zlp): "); return FALSE; } } return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "GUsb not supported"); return FALSE; #endif } gboolean fu_sahara_loader_qdl_write_bytes(FuSaharaLoader *self, GBytes *bytes, GError **error) { gsize sz; const guint8 *data = g_bytes_get_data(bytes, &sz); return fu_sahara_loader_qdl_write(self, data, sz, error); } static gboolean fu_sahara_loader_write_prog(FuSaharaLoader *self, guint32 offset, guint32 length, GBytes *prog, GError **error) { gsize sz; const guint8 *data = g_bytes_get_data(prog, &sz); g_return_val_if_fail(offset + length <= sz, FALSE); g_debug("SENDING --> RAW_DATA: %u bytes (offset = %u, total = %" G_GSIZE_FORMAT ")", length, offset, sz); return fu_sahara_loader_qdl_write(self, &data[offset], length, error); } static gboolean fu_sahara_loader_send_packet(FuSaharaLoader *self, GByteArray *pkt, GError **error) { guint8 *data = pkt->data; gsize sz = pkt->len; g_return_val_if_fail(pkt != NULL, FALSE); fu_dump_raw(G_LOG_DOMAIN, "tx packet", pkt->data, pkt->len); return fu_sahara_loader_qdl_write(self, data, sz, error); } /* packet composers */ static GByteArray * fu_sahara_create_byte_array_from_packet(const struct sahara_packet *pkt) { GByteArray *self; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(pkt != NULL, NULL); self = g_byte_array_sized_new(pkt->length); fu_byte_array_set_size(self, pkt->length, 0x00); if (!fu_memcpy_safe(self->data, self->len, 0, (gconstpointer)pkt, sizeof(struct sahara_packet), 0, pkt->length, &error_local)) { g_debug("sahara create packet failed: %s", error_local->message); return NULL; } return self; } static GByteArray * fu_sahara_loader_compose_reset_packet(void) { guint32 len = 0x08; struct sahara_packet pkt = {.command_id = GUINT32_TO_LE(SAHARA_RESET_ID), .length = GUINT32_TO_LE(len), {{0}}}; return fu_sahara_create_byte_array_from_packet(&pkt); } static GByteArray * fu_sahara_loader_compose_hello_response_packet(FuSaharaMode mode) { guint32 len = 0x30; struct sahara_packet pkt = {.command_id = GUINT32_TO_LE(SAHARA_HELLO_RESPONSE_ID), .length = GUINT32_TO_LE(len), {{0}}}; pkt.hello_response.version = GUINT32_TO_LE(SAHARA_VERSION); pkt.hello_response.version_compatible = GUINT32_TO_LE(SAHARA_VERSION_COMPATIBLE); pkt.hello_response.status = GUINT32_TO_LE(SAHARA_STATUS_SUCCESS); pkt.hello_response.mode = GUINT32_TO_LE(SAHARA_MODE_IMAGE_TX_PENDING); return fu_sahara_create_byte_array_from_packet(&pkt); } static GByteArray * fu_sahara_loader_compose_done_packet(void) { guint32 len = 0x08; struct sahara_packet pkt = {.command_id = GUINT32_TO_LE(SAHARA_DONE_ID), .length = GUINT32_TO_LE(len), {{0}}}; return fu_sahara_create_byte_array_from_packet(&pkt); } static gboolean fu_sahara_loader_send_reset_packet(FuSaharaLoader *self, GError **error) { g_autoptr(GByteArray) rx_packet = NULL; g_autoptr(GByteArray) tx_packet = NULL; tx_packet = fu_sahara_loader_compose_reset_packet(); if (!fu_sahara_loader_send_packet(self, tx_packet, error)) { g_prefix_error(error, "Failed to send reset packet: "); return FALSE; } rx_packet = fu_sahara_loader_qdl_read(self, error); if (rx_packet == NULL || sahara_packet_get_command_id(rx_packet) != SAHARA_RESET_RESPONSE_ID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to receive RESET_RESPONSE packet"); return FALSE; } g_debug("reset succeeded"); return TRUE; } static gboolean fu_sahara_loader_wait_hello_rsp(FuSaharaLoader *self, GError **error) { g_autoptr(GByteArray) rx_packet = NULL; g_autoptr(GByteArray) tx_packet = NULL; rx_packet = fu_sahara_loader_qdl_read(self, error); if (rx_packet == NULL) { g_autoptr(GByteArray) ping = NULL; ping = g_byte_array_sized_new(1); g_byte_array_set_size(ping, 1); fu_sahara_loader_send_packet(self, ping, NULL); rx_packet = fu_sahara_loader_qdl_read(self, error); } g_return_val_if_fail(rx_packet != NULL, FALSE); fu_dump_raw(G_LOG_DOMAIN, "rx packet", rx_packet->data, rx_packet->len); if (sahara_packet_get_command_id(rx_packet) != SAHARA_HELLO_ID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Received a different packet while waiting for the HELLO packet"); fu_sahara_loader_send_reset_packet(self, NULL); return FALSE; } tx_packet = fu_sahara_loader_compose_hello_response_packet(SAHARA_MODE_IMAGE_TX_PENDING); return fu_sahara_loader_send_packet(self, tx_packet, error); } /* main routine */ gboolean fu_sahara_loader_run(FuSaharaLoader *self, GBytes *prog, GError **error) { g_return_val_if_fail(prog != NULL, FALSE); g_debug("STATE -- SAHARA_WAIT_HELLO"); if (!fu_sahara_loader_wait_hello_rsp(self, error)) return FALSE; while (TRUE) { guint32 command_id; struct sahara_packet *pkt; g_autoptr(GByteArray) rx_packet = NULL; g_autoptr(GByteArray) tx_packet = NULL; g_autoptr(GError) error_local = NULL; g_debug("STATE -- SAHARA_WAIT_COMMAND"); rx_packet = fu_sahara_loader_qdl_read(self, error); if (rx_packet == NULL) break; if (rx_packet->len != sahara_packet_get_length(rx_packet)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Received packet length is not matching"); break; } fu_dump_raw(G_LOG_DOMAIN, "rx_packet", rx_packet->data, rx_packet->len); command_id = sahara_packet_get_command_id(rx_packet); pkt = (struct sahara_packet *)(rx_packet->data); if (command_id == SAHARA_HELLO_ID) { tx_packet = fu_sahara_loader_compose_hello_response_packet( SAHARA_MODE_IMAGE_TX_PENDING); fu_sahara_loader_send_packet(self, tx_packet, &error_local); } else if (command_id == SAHARA_READ_DATA_ID) { guint32 offset = pkt->read_data.offset; guint32 length = pkt->read_data.length; fu_sahara_loader_write_prog(self, offset, length, prog, &error_local); } else if (command_id == SAHARA_READ_DATA_64_BIT_ID) { guint64 offset = pkt->read_data_64bit.offset; guint64 length = pkt->read_data_64bit.length; fu_sahara_loader_write_prog(self, offset, length, prog, &error_local); } else if (command_id == SAHARA_END_OF_IMAGE_TX_ID) { guint32 status = pkt->end_of_image_transfer.status; if (status == SAHARA_STATUS_SUCCESS) { tx_packet = fu_sahara_loader_compose_done_packet(); fu_sahara_loader_send_packet(self, tx_packet, &error_local); } } else if (command_id == SAHARA_DONE_RESP_ID) { return TRUE; } else { g_warning("Unexpected packet received: cmd_id = %u, len = %u", command_id, sahara_packet_get_length(rx_packet)); } if (error_local != NULL) g_warning("%s", error_local->message); } fu_sahara_loader_send_reset_packet(self, NULL); return FALSE; } static void fu_sahara_loader_init(FuSaharaLoader *self) { } static void fu_sahara_loader_finalize(GObject *object) { G_OBJECT_CLASS(fu_sahara_loader_parent_class)->finalize(object); } static void fu_sahara_loader_class_init(FuSaharaLoaderClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_sahara_loader_finalize; } FuSaharaLoader * fu_sahara_loader_new(void) { return g_object_new(FU_TYPE_SAHARA_LOADER, NULL); } fwupd-1.9.16/plugins/modem-manager/fu-sahara-loader.h000066400000000000000000000015731460375044200224260ustar00rootroot00000000000000/* * Copyright (C) 2021 Quectel Wireless Solutions Co., Ltd. * Ivan Mikhanchuk * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_SAHARA_LOADER (fu_sahara_loader_get_type()) G_DECLARE_FINAL_TYPE(FuSaharaLoader, fu_sahara_loader, FU, SAHARA_LOADER, GObject) FuSaharaLoader * fu_sahara_loader_new(void); gboolean fu_sahara_loader_qdl_is_open(FuSaharaLoader *self); GByteArray * fu_sahara_loader_qdl_read(FuSaharaLoader *self, GError **error); gboolean fu_sahara_loader_qdl_write_bytes(FuSaharaLoader *self, GBytes *bytes, GError **error); gboolean fu_sahara_loader_open(FuSaharaLoader *self, FuUsbDevice *usb_device, GError **error); gboolean fu_sahara_loader_run(FuSaharaLoader *self, GBytes *prog, GError **error); gboolean fu_sahara_loader_close(FuSaharaLoader *self, GError **error); fwupd-1.9.16/plugins/modem-manager/meson.build000066400000000000000000000023031460375044200212740ustar00rootroot00000000000000libmm_glib = dependency('mm-glib', version: '>= 1.18.0', required: get_option('plugin_modem_manager')) libqmi_glib = dependency('qmi-glib', version: '>= 1.30.0', required: get_option('plugin_modem_manager')) libmbim_glib = dependency('mbim-glib', version: '>= 1.26.0', required: get_option('plugin_modem_manager')) if libmm_glib.found() and \ libqmi_glib.found() and \ libmbim_glib.found() and \ get_option('plugin_modem_manager').require(gudev.found(), error_message: 'gudev is needed for plugin_modem_manager').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginMm"'] cargs +=['-DMM_REQUIRED_VERSION="1.10.0"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('modem-manager.quirk') shared_module('fu_plugin_modem_manager', sources: [ 'fu-mm-plugin.c', 'fu-mm-device.c', 'fu-qmi-pdc-updater.c', 'fu-mbim-qdu-updater.c', 'fu-firehose-updater.c', 'fu-sahara-loader.c', 'fu-mm-utils.c' ], include_directories: plugin_incdirs, install: true, install_rpath: libdir_pkg, install_dir: libdir_pkg, c_args: cargs, link_with: plugin_libs, dependencies: [ plugin_deps, libmm_glib, libqmi_glib, libmbim_glib, ], ) endif fwupd-1.9.16/plugins/modem-manager/modem-manager.quirk000066400000000000000000000065061460375044200227310ustar00rootroot00000000000000 # DW5821e [USB\VID_413C&PID_81D7] Summary = Dell DW5821e LTE modem CounterpartGuid = USB\VID_413C&PID_81D6 # DW5821e/eSIM [USB\VID_413C&PID_81E0] Summary = Dell DW5821e/eSIM LTE modem CounterpartGuid = USB\VID_413C&PID_81E1 # T77W968 [USB\VID_0489&PID_E0B4] Summary = Foxconn T77w968 LTE modem CounterpartGuid = USB\VID_0489&PID_E0B7 # T77W968/eSIM [USB\VID_0489&PID_E0B5] Summary = Foxconn T77w968/eSIM LTE modem CounterpartGuid = USB\VID_0489&PID_E0B8 # T99W265 with MBIM only [USB\VID_0489&PID_E0DA] Summary = Foxconn T99W265 LTE modem with MBIM only CounterpartGuid = USB\VID_0489&PID_E0DA # T99W265 with MBIM and Serial port [USB\VID_0489&PID_E0DB] Summary = Foxconn T99W265 LTE modem with MBIM and Serial CounterpartGuid = USB\VID_0489&PID_E0DB # T99W175 based on QC LE1.2 [PCI\VID_105B&PID_E0AB] Summary = Foxconn T99W175 5G modem CounterpartGuid = PCI\VID_105B&PID_E0AB # T99W175(DW5930e with eSIM) [PCI\VID_105B&PID_E0B0] Summary = Foxconn T99W175 5G modem with eSIM for DW5930e CounterpartGuid = PCI\VID_105B&PID_E0B0 # T99W175(DW5930e without eSIM) [PCI\VID_105B&PID_E0B1] Summary = Foxconn T99W175 5G modem without eSIM for DW5930e CounterpartGuid = PCI\VID_105B&PID_E0B1 # T99W175 based on QC LE1.4 [PCI\VID_105B&PID_E0BF] Summary = Foxconn T99W175 5G modem CounterpartGuid = PCI\VID_105B&PID_E0BF # T99W175 variant [PCI\VID_105B&PID_E0C3] Summary = Foxconn T99W175 5G modem CounterpartGuid = PCI\VID_105B&PID_E0C3 # T99W368 Foxconn variant based on SDX65 [PCI\VID_105B&PID_E0D8] Summary = Foxconn T99W368 5G modem CounterpartGuid = PCI\VID_105B&PID_E0D8 # T99W373 Foxconn variant based on SDX62 [PCI\VID_105B&PID_E0D9] Summary = Foxconn T99W373 5G modem CounterpartGuid = PCI\VID_105B&PID_E0D9 # T99W373(DW5932e with eSIM) based on SDX62 [PCI\VID_105B&PID_E0F5] Summary = Foxconn T99W373 5G modem with eSIM for DW5932e CounterpartGuid = PCI\VID_105B&PID_E0F5 # T99W373 (DW5932e without eSIM) based on SDX62 [PCI\VID_105B&PID_E0F9] Summary = Foxconn T99W373 5G modem without eSIM for DW5932e CounterpartGuid = PCI\VID_105B&PID_E0F9 # Quectel EG25-G [USB\VID_2C7C&PID_0125] Summary = Quectel EG25-G modem CounterpartGuid = USB\VID_18D1&PID_D00D Flags = detach-at-fastboot-has-no-response ModemManagerBranchAtCommand = AT+GETFWBRANCH Plugin = modem_manager # Qualcomm Sahara device [USB\VID_05C6&PID_9008] Summary = Qualcomm based modems in EDL (sahara) Plugin = modem_manager # Quectel EM120 firehose prog file [PCI\VID_1EAC&PID_1001] Summary = Quectel EM120 firehose prog file ModemManagerFirehoseProgFile = prog_firehose_sdx24.mbn # Quectel EM160 firehose prog file [PCI\VID_1EAC&PID_1002] Summary = Quectel EM160 firehose prog file ModemManagerFirehoseProgFile = prog_firehose_sdx24.mbn # Quectel EM160 firehose prog file [PCI\VID_1EAC&PID_100D] Summary = Quectel EM160 firehose prog file ModemManagerFirehoseProgFile = prog_firehose_sdx24.mbn # Quectel RM520 firehose prog file [PCI\VID_1EAC&PID_1004] Summary = Quectel RM520 firehose prog file ModemManagerFirehoseProgFile = prog_firehose_sdx6x.elf # Quectel RM520 firehose prog file [PCI\VID_1EAC&PID_1007] Summary = Quectel RM520 firehose prog file ModemManagerFirehoseProgFile = prog_firehose_sdx6x.elf # Fibocom FM101 [USB\VID_2CB7&PID_01A2] Summary = Fibocom FM101-GL Module Flags = uninhibit-modemmanager-after-fastboot-reboot,detach-at-fastboot-has-no-response CounterpartGuid = USB\VID_2CB7&PID_D00D fwupd-1.9.16/plugins/msr/000077500000000000000000000000001460375044200152245ustar00rootroot00000000000000fwupd-1.9.16/plugins/msr/README.md000066400000000000000000000011171460375044200165030ustar00rootroot00000000000000--- title: Plugin: MSR --- ## Introduction This plugin checks if the Model-specific registers (MSRs) indicate the Direct Connect Interface (DCI) is enabled. DCI allows debugging of Intel processors using the USB3 port. DCI should always be disabled and locked on production hardware as it allows the attacker to disable other firmware protection methods. The result will be stored in a security attribute for HSI. ## External Interface Access This plugin requires read access to `/sys/class/msr`. ## Version Considerations This plugin has been available since fwupd version `1.5.0`. fwupd-1.9.16/plugins/msr/fu-msr-plugin.c000066400000000000000000000464561460375044200201140ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-msr-plugin.h" typedef union { guint32 data; struct { guint32 enabled : 1; guint32 rsrvd : 29; guint32 locked : 1; guint32 debug_occurred : 1; } __attribute__((packed)) fields; } FuMsrIa32Debug; typedef union { guint64 data; struct { guint64 rsrvd : 25; guint64 gds_ctrl : 1; guint64 gds_no : 1; } __attribute__((packed)) fields; } FuMsrIa32ArchCapabilities; typedef union { guint64 data; struct { guint64 rngds_mitg_dis : 1; guint64 rtm_allow : 1; guint64 rtm_locked : 1; guint64 fb_clear_dis : 1; guint64 gds_mitg_dis : 1; guint64 gds_mitg_lock : 1; } __attribute__((packed)) fields; } FuMsrIa32McuOptCtrl; typedef union { guint64 data; struct { guint32 lock_ro : 1; guint32 enable : 1; guint32 key_select : 1; guint32 save_key_for_standby : 1; guint32 policy_encryption_algo : 4; guint32 reserved1 : 23; guint32 bypass_enable : 1; guint32 mk_tme_keyid_bits : 4; guint32 reserved2 : 12; guint32 mk_tme_crypto_algs : 16; } __attribute__((packed)) fields; } FuMsrIa32TmeActivation; typedef union { guint32 data; struct { guint32 unknown0 : 23; /* 0 -> 22 inc */ guint32 sme_is_enabled : 1; guint32 unknown1 : 8; } __attribute__((packed)) fields; } FuMsrAMD64Syscfg; typedef union { guint32 data; struct { guint32 sev_is_enabled : 1; guint32 unknown0 : 31; } __attribute__((packed)) fields; } FuMsrAMD64Sev; struct _FuMsrPlugin { FuPlugin parent_instance; gboolean ia32_debug_supported; gboolean ia32_tme_supported; gboolean ia32_arch_capabilities_supported; gboolean ia32_mcu_opt_ctrl_supported; FuMsrIa32Debug ia32_debug; FuMsrIa32TmeActivation ia32_tme_activation; FuMsrIa32ArchCapabilities ia32_arch_capabilities; FuMsrIa32McuOptCtrl ia32_mcu_opt_ctrl; gboolean amd64_syscfg_supported; gboolean amd64_sev_supported; FuMsrAMD64Syscfg amd64_syscfg; FuMsrAMD64Sev amd64_sev; }; G_DEFINE_TYPE(FuMsrPlugin, fu_msr_plugin, FU_TYPE_PLUGIN) #define PCI_MSR_IA32_DEBUG_INTERFACE 0xc80 #define PCI_MSR_IA32_TME_ACTIVATION 0x982 #define PCI_MSR_IA32_BIOS_SIGN_ID 0x8b #define PCI_MSR_IA32_ARCH_CAPABILITIES 0x10a #define PCI_MSR_IA32_MCU_OPT_CTRL 0x123 #define PCI_MSR_AMD64_SYSCFG 0xC0010010 #define PCI_MSR_AMD64_SEV 0xC0010131 static void fu_msr_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuMsrPlugin *self = FU_MSR_PLUGIN(plugin); if (self->ia32_debug_supported) { fu_string_append_kb(str, idt, "Ia32DebugInterfaceEnabled", self->ia32_debug.fields.enabled); fu_string_append_kb(str, idt, "Ia32DebugInterfaceLocked", self->ia32_debug.fields.locked); fu_string_append_kb(str, idt, "Ia32DebugInterfaceDebugOccurred", self->ia32_debug.fields.debug_occurred); } if (self->ia32_tme_supported) { fu_string_append_kb(str, idt, "Ia32TmeActivateLockRo", self->ia32_tme_activation.fields.lock_ro); fu_string_append_kb(str, idt, "Ia32TmeActivateEnable", self->ia32_tme_activation.fields.enable); fu_string_append_kb(str, idt, "Ia32TmeActivateBypassEnable", self->ia32_tme_activation.fields.bypass_enable); } if (self->ia32_mcu_opt_ctrl_supported) { fu_string_append_kb(str, idt, "GdsMitgDis", self->ia32_mcu_opt_ctrl.fields.gds_mitg_dis > 0); fu_string_append_kb(str, idt, "GdsMitgLock", self->ia32_mcu_opt_ctrl.fields.gds_mitg_lock > 0); } if (self->amd64_syscfg_supported) { fu_string_append_kb(str, idt, "Amd64SyscfgSmeIsEnabled", self->amd64_syscfg.fields.sme_is_enabled); } if (self->amd64_sev_supported) { fu_string_append_kb(str, idt, "Amd64SevIsEnabled", self->amd64_sev.fields.sev_is_enabled); } } static gboolean fu_msr_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { FuMsrPlugin *self = FU_MSR_PLUGIN(plugin); guint eax = 0; guint ebx = 0; guint ecx = 0; guint edx = 0; if (!g_file_test("/dev/cpu", G_FILE_TEST_IS_DIR)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "missing kernel support"); return FALSE; } /* sdbg is supported: https://en.wikipedia.org/wiki/CPUID */ if (fu_cpu_get_vendor() == FU_CPU_VENDOR_INTEL) { if (!fu_cpuid(0x01, NULL, NULL, &ecx, NULL, error)) return FALSE; self->ia32_debug_supported = ((ecx >> 11) & 0x1) > 0; if (!fu_cpuid(0x07, NULL, NULL, &ecx, &edx, error)) return FALSE; self->ia32_tme_supported = ((ecx >> 13) & 0x1) > 0; self->ia32_arch_capabilities_supported = ((edx >> 29) & 0x1) > 0; self->ia32_mcu_opt_ctrl_supported = ((edx >> 9) & 0x1) > 0; } /* indicates support for SME and SEV */ if (fu_cpu_get_vendor() == FU_CPU_VENDOR_AMD) { if (!fu_cpuid(0x8000001f, &eax, &ebx, NULL, NULL, error)) return FALSE; g_debug("SME/SEV check MSR: eax 0%x, ebx 0%x", eax, ebx); self->amd64_syscfg_supported = ((eax >> 0) & 0x1) > 0; self->amd64_sev_supported = ((eax >> 1) & 0x1) > 0; } return TRUE; } static gboolean fu_msr_plugin_backend_device_added(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { FuMsrPlugin *self = FU_MSR_PLUGIN(plugin); FuDevice *device_cpu = fu_plugin_cache_lookup(plugin, "cpu"); guint8 buf[8] = {0x0}; g_autoptr(FuDeviceLocker) locker = NULL; g_autofree gchar *basename = NULL; /* interesting device? */ if (!FU_IS_UDEV_DEVICE(device)) return TRUE; if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "msr") != 0) return TRUE; /* we only care about the first processor */ basename = g_path_get_basename(fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device))); if (g_strcmp0(basename, "msr0") != 0) return TRUE; /* open the config */ fu_device_set_physical_id(FU_DEVICE(device), "msr"); locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; /* grab Intel MSR */ if (self->ia32_debug_supported) { if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), PCI_MSR_IA32_DEBUG_INTERFACE, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read IA32_DEBUG_INTERFACE: "); return FALSE; } if (!fu_memread_uint32_safe(buf, sizeof(buf), 0x0, &self->ia32_debug.data, G_LITTLE_ENDIAN, error)) return FALSE; } if (self->ia32_tme_supported) { if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), PCI_MSR_IA32_TME_ACTIVATION, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read IA32_TME_ACTIVATION: "); return FALSE; } if (!fu_memread_uint64_safe(buf, sizeof(buf), 0x0, &self->ia32_tme_activation.data, G_LITTLE_ENDIAN, error)) return FALSE; } if (self->ia32_arch_capabilities_supported) { if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), PCI_MSR_IA32_ARCH_CAPABILITIES, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read IA32_ARCH_CAPABILITIES: "); return FALSE; } if (!fu_memread_uint64_safe(buf, sizeof(buf), 0x0, &self->ia32_arch_capabilities.data, G_LITTLE_ENDIAN, error)) return FALSE; } if (self->ia32_mcu_opt_ctrl_supported) { if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), PCI_MSR_IA32_MCU_OPT_CTRL, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read IA32_MCU_OPT_CTRL: "); return FALSE; } if (!fu_memread_uint64_safe(buf, sizeof(buf), 0x0, &self->ia32_mcu_opt_ctrl.data, G_LITTLE_ENDIAN, error)) return FALSE; } /* grab AMD MSRs */ if (self->amd64_syscfg_supported) { if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), PCI_MSR_AMD64_SYSCFG, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read PCI_MSR_AMD64_SYSCFG: "); return FALSE; } if (!fu_memread_uint32_safe(buf, sizeof(buf), 0x0, &self->amd64_syscfg.data, G_LITTLE_ENDIAN, error)) return FALSE; } if (self->amd64_sev_supported) { if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), PCI_MSR_AMD64_SEV, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read PCI_MSR_AMD64_SEV: "); return FALSE; } if (!fu_memread_uint32_safe(buf, sizeof(buf), 0x0, &self->amd64_sev.data, G_LITTLE_ENDIAN, error)) return FALSE; } /* get microcode version */ if (device_cpu != NULL) { guint32 ver_raw; guint8 offset; if (!fu_cpuid(0x1, NULL, NULL, NULL, NULL, error)) return FALSE; if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), PCI_MSR_IA32_BIOS_SIGN_ID, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read IA32_BIOS_SIGN_ID: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "IA32_BIOS_SIGN_ID", buf, sizeof(buf)); offset = fu_cpu_get_vendor() == FU_CPU_VENDOR_AMD ? 0x0 : 0x4; if (!fu_memread_uint32_safe(buf, sizeof(buf), offset, &ver_raw, G_LITTLE_ENDIAN, error)) return FALSE; if (ver_raw != 0 && ver_raw != G_MAXUINT32) fu_device_set_version_u32(device_cpu, ver_raw); } /* success */ return TRUE; } static void fu_msr_plugin_device_registered(FuPlugin *plugin, FuDevice *dev) { if (g_strcmp0(fu_device_get_plugin(dev), "cpu") == 0) { fu_plugin_cache_add(plugin, "cpu", dev); return; } } static void fu_plugin_add_security_attr_dci_enabled(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuMsrPlugin *self = FU_MSR_PLUGIN(plugin); FuDevice *device = fu_plugin_cache_lookup(plugin, "cpu"); g_autoptr(FwupdSecurityAttr) attr = NULL; /* this MSR is only valid for a subset of Intel CPUs */ if (fu_cpu_get_vendor() != FU_CPU_VENDOR_INTEL) return; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_ENABLED); if (device != NULL) fwupd_security_attr_add_guids(attr, fu_device_get_guids(device)); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fu_security_attrs_append(attrs, attr); /* check fields */ if (!self->ia32_debug_supported) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); return; } if (self->ia32_debug.fields.enabled) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_plugin_add_security_attr_intel_gds(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuMsrPlugin *self = FU_MSR_PLUGIN(plugin); FuDevice *device = fu_plugin_cache_lookup(plugin, "cpu"); const gchar *mitigations_required; g_autoptr(FwupdSecurityAttr) attr = NULL; /* this MSR is only valid for a subset of Intel CPUs */ if (fu_cpu_get_vendor() != FU_CPU_VENDOR_INTEL) return; if (device == NULL) return; /* only specific CPUs are affected by GDS */ mitigations_required = fu_device_get_metadata(device, FU_DEVICE_METADATA_CPU_MITIGATIONS_REQUIRED); if (g_strcmp0(mitigations_required, "gds") != 0) return; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_INTEL_GDS); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); fwupd_security_attr_add_guids(attr, fu_device_get_guids(device)); /* processor is not vulnerable */ if (self->ia32_arch_capabilities.fields.gds_no) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); return; } /* enumeration for support of both IA32_MCU_OPT_CTRL[4] and IA32_MCU_OPT_CTRL[5] */ if (!self->ia32_arch_capabilities.fields.gds_ctrl) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* GDS mitigation has to be enabled [and locked] */ if (self->ia32_mcu_opt_ctrl.fields.gds_mitg_dis) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); return; } if (self->ia32_mcu_opt_ctrl.fields.gds_mitg_lock) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_plugin_add_security_attr_intel_tme_enabled(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuMsrPlugin *self = FU_MSR_PLUGIN(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* this MSR is only valid for a subset of Intel CPUs */ if (fu_cpu_get_vendor() != FU_CPU_VENDOR_INTEL) return; /* create attr (which should already have been created in the cpu plugin) */ attr = fu_security_attrs_get_by_appstream_id(attrs, FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM, NULL); if (attr == NULL) { attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENCRYPTED); fu_security_attrs_append(attrs, attr); } /* check fields */ if (!self->ia32_tme_supported) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); return; } if (!self->ia32_tme_activation.fields.enable) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fwupd_security_attr_remove_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); return; } if (self->ia32_tme_activation.fields.bypass_enable) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED); fwupd_security_attr_remove_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); return; } if (!self->ia32_tme_activation.fields.lock_ro) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED); fwupd_security_attr_remove_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } } static void fu_plugin_add_security_attr_dci_locked(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuMsrPlugin *self = FU_MSR_PLUGIN(plugin); FuDevice *device = fu_plugin_cache_lookup(plugin, "cpu"); g_autoptr(FwupdSecurityAttr) attr = NULL; /* this MSR is only valid for a subset of Intel CPUs */ if (fu_cpu_get_vendor() != FU_CPU_VENDOR_INTEL) return; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_LOCKED); if (device != NULL) fwupd_security_attr_add_guids(attr, fu_device_get_guids(device)); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); fu_security_attrs_append(attrs, attr); /* check fields */ if (!self->ia32_debug_supported) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); return; } if (!self->ia32_debug.fields.locked) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static gboolean fu_msr_plugin_safe_kernel_for_sme(FuPlugin *plugin, GError **error) { g_autofree gchar *min = fu_plugin_get_config_value(plugin, "MinimumSmeKernelVersion"); return fu_kernel_check_version(min, error); } static gboolean fu_msr_plugin_kernel_enabled_sme(GError **error) { g_autofree gchar *buf = NULL; gsize bufsz = 0; if (!g_file_get_contents("/proc/cpuinfo", &buf, &bufsz, error)) return FALSE; if (bufsz > 0) { g_auto(GStrv) tokens = fu_strsplit(buf, bufsz, " ", -1); for (guint i = 0; tokens[i] != NULL; i++) { if (g_strcmp0(tokens[i], "sme") == 0) return TRUE; } } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "sme support not enabled by kernel"); return FALSE; } static void fu_plugin_add_security_attr_amd_sme_enabled(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuMsrPlugin *self = FU_MSR_PLUGIN(plugin); FuDevice *device = fu_plugin_cache_lookup(plugin, "cpu"); g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; /* this MSR is only valid for a subset of AMD CPUs */ if (fu_cpu_get_vendor() != FU_CPU_VENDOR_AMD) return; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM); if (device != NULL) fwupd_security_attr_add_guids(attr, fu_device_get_guids(device)); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENCRYPTED); fu_security_attrs_append(attrs, attr); /* check fields */ if (!self->amd64_syscfg_supported) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); return; } if (!self->amd64_syscfg.fields.sme_is_enabled) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } if (!fu_msr_plugin_safe_kernel_for_sme(plugin, &error_local)) { g_debug("unable to properly detect SME: %s", error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_UNKNOWN); return; } if (!(fu_msr_plugin_kernel_enabled_sme(&error_local))) { g_debug("%s", error_local->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fwupd_security_attr_add_obsolete(attr, "pci_psp"); } static void fu_msr_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { fu_plugin_add_security_attr_dci_enabled(plugin, attrs); fu_plugin_add_security_attr_dci_locked(plugin, attrs); fu_plugin_add_security_attr_amd_sme_enabled(plugin, attrs); fu_plugin_add_security_attr_intel_tme_enabled(plugin, attrs); fu_plugin_add_security_attr_intel_gds(plugin, attrs); } static void fu_msr_plugin_init(FuMsrPlugin *self) { } static void fu_msr_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_udev_subsystem(plugin, "msr"); /* defaults changed here will also be reflected in the fwupd.conf man page */ fu_plugin_set_config_default(plugin, "MinimumSmeKernelVersion", "5.18.0"); } static void fu_msr_plugin_class_init(FuMsrPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_msr_plugin_constructed; plugin_class->to_string = fu_msr_plugin_to_string; plugin_class->startup = fu_msr_plugin_startup; plugin_class->backend_device_added = fu_msr_plugin_backend_device_added; plugin_class->add_security_attrs = fu_msr_plugin_add_security_attrs; plugin_class->device_registered = fu_msr_plugin_device_registered; } fwupd-1.9.16/plugins/msr/fu-msr-plugin.h000066400000000000000000000003371460375044200201050ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuMsrPlugin, fu_msr_plugin, FU, MSR_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/msr/fwupd-msr.conf000066400000000000000000000000041460375044200200110ustar00rootroot00000000000000msr fwupd-1.9.16/plugins/msr/meson.build000066400000000000000000000006701460375044200173710ustar00rootroot00000000000000if hsi and has_cpuid cargs = ['-DG_LOG_DOMAIN="FuPluginMsr"'] plugins += {meson.current_source_dir().split('/')[-1]: true} if libsystemd.found() install_data(['fwupd-msr.conf'], install_dir: systemd_modules_load_dir, ) endif plugin_builtins += static_library('fu_plugin_msr', sources: [ 'fu-msr-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/mtd/000077500000000000000000000000001460375044200152075ustar00rootroot00000000000000fwupd-1.9.16/plugins/mtd/README.md000066400000000000000000000046471460375044200165010ustar00rootroot00000000000000--- title: Plugin: MTD --- ## Introduction The Memory Technology Device (MTD) interface is a way of abstracting flash devices as if they were normal block devices. See for more details. This plugin supports the following protocol ID: * `org.infradead.mtd` ## GUID Generation These devices use custom DeviceInstanceId values built from the device `NAME` and DMI data, e.g. * `MTD\NAME_Factory` * `MTD\VENDOR_foo&NAME_baz` * `MTD\VENDOR_foo&PRODUCT_bar&NAME_baz` If the `FirmwareGType` quirk is set for the device then the firmware is read back from the device at daemon startup and parsed for the version number. In the event the firmware has multiple child images then the device GUIDs are used as firmware IDs. ## Update Behavior The MTD device is erased in chunks, written and then read back to verify. Although fwupd can read and write a raw image to the MTD partition there is no automatic way to get the *existing* version number. By providing the `GType` fwupd can read the MTD partition and discover additional metadata about the image. For instance, adding a quirk like: [MTD\VENDOR_PINE64&PRODUCT_PinePhone-Pro&NAME_spi1.0] FirmwareGType = FuUswidFirmware ... and then append or insert the image into the MTD image with prepared SBoM metadata: pip install uswid uswid --load uswid.ini --save metadata.uswid This would allow fwupd to read the MTD image data, look for a [uSWID](https://github.com/hughsie/python-uswid) data section and then parse the metadata from that. Any of the firmware formats supported by `fwupdtool get-firmware-gtypes` that can provide a version can be used. ## Quirk Use This plugin uses the following plugin-specific quirks: ### MtdMetadataOffset The offset to start searching within the MTD partition when using `FirmwareGType`. This is provided to avoid dumping a huge amount of MTD data to access a tiny chunk of data that will not be before a known offset. Since: 1.9.1 ### MtdMetadataSize The size of data to read from the MTD partition when using `FirmwareGType`. This is provided to avoid dumping a huge amount of MTD data to access a tiny chunk of data. Since: 1.9.1 ## Vendor ID Security The vendor ID is set from the system vendor, for example `DMI:LENOVO` ## External Interface Access This plugin requires read/write access to `/dev/mtd`. ## Version Considerations This plugin has been available since fwupd version `1.7.2`. fwupd-1.9.16/plugins/mtd/fu-mtd-device.c000066400000000000000000000351631460375044200200140ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_MTD_USER_H #include #endif #include "fu-mtd-device.h" struct _FuMtdDevice { FuUdevDevice parent_instance; guint64 erasesize; guint64 metadata_offset; guint64 metadata_size; }; G_DEFINE_TYPE(FuMtdDevice, fu_mtd_device, FU_TYPE_UDEV_DEVICE) #define FU_MTD_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ static void fu_mtd_device_to_string(FuDevice *device, guint idt, GString *str) { FuMtdDevice *self = FU_MTD_DEVICE(device); if (self->erasesize > 0) fu_string_append_kx(str, idt, "EraseSize", self->erasesize); fu_string_append_kx(str, idt, "MetadataOffset", self->metadata_offset); fu_string_append_kx(str, idt, "MetadataSize", self->metadata_size); } static gboolean fu_mtd_device_metadata_load(FuMtdDevice *self, GError **error) { GPtrArray *instance_ids; GType firmware_gtype = fu_device_get_firmware_gtype(FU_DEVICE(self)); const gchar *fn; g_autoptr(FuFirmware) firmware_child = NULL; g_autoptr(FuFirmware) firmware = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GFileInputStream) input = NULL; g_autoptr(GPtrArray) imgs = NULL; /* read contents at the search offset */ fn = fu_udev_device_get_device_file(FU_UDEV_DEVICE(self)); if (fn == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as no device file"); return FALSE; } file = g_file_new_for_path(fn); input = g_file_read(file, NULL, error); if (input == NULL) { g_prefix_error(error, "failed to open device: "); return FALSE; } blob = fu_bytes_get_contents_stream_full(G_INPUT_STREAM(input), self->metadata_offset, self->metadata_size, error); if (blob == NULL) { g_prefix_error(error, "failed to read from stream: "); return FALSE; } firmware = g_object_new(firmware_gtype, NULL); if (!fu_firmware_parse(firmware, blob, FWUPD_INSTALL_FLAG_NONE, error)) { g_prefix_error(error, "failed to parse image: "); return FALSE; } /* find the firmware child that matches any of the device GUID, then use the first * child that have a version, and finally use the main firmware as a fallback */ instance_ids = fu_device_get_instance_ids(FU_DEVICE(self)); for (guint i = 0; i < instance_ids->len; i++) { const gchar *instance_id = g_ptr_array_index(instance_ids, i); g_autofree gchar *guid = fwupd_guid_hash_string(instance_id); firmware_child = fu_firmware_get_image_by_id(firmware, guid, NULL); if (firmware_child != NULL) break; } imgs = fu_firmware_get_images(firmware); for (guint i = 0; i < imgs->len; i++) { FuFirmware *firmare_tmp = g_ptr_array_index(imgs, i); if (fu_firmware_get_version(firmare_tmp) != NULL || fu_firmware_get_version_raw(firmare_tmp) != 0) { firmware_child = g_object_ref(firmare_tmp); break; } } if (firmware_child == NULL) firmware_child = g_object_ref(firmware); /* copy over the version */ if (fu_firmware_get_version(firmware_child) != NULL) fu_device_set_version(FU_DEVICE(self), fu_firmware_get_version(firmware_child)); if (fu_firmware_get_version_raw(firmware_child) != G_MAXUINT64) { fu_device_set_version_raw(FU_DEVICE(self), fu_firmware_get_version_raw(firmware_child)); } /* success */ return TRUE; } static gboolean fu_mtd_device_setup(FuDevice *device, GError **error) { FuMtdDevice *self = FU_MTD_DEVICE(device); GType firmware_gtype = fu_device_get_firmware_gtype(device); g_autoptr(GError) error_local = NULL; /* nothing to do */ if (firmware_gtype == G_TYPE_INVALID) return TRUE; if (!fu_mtd_device_metadata_load(self, &error_local)) { g_warning("no version metadata found: %s", error_local->message); return TRUE; } /* success */ return TRUE; } static gboolean fu_mtd_device_open(FuDevice *device, GError **error) { g_autoptr(GError) error_local = NULL; /* FuUdevDevice->open */ if (!FU_DEVICE_CLASS(fu_mtd_device_parent_class)->open(device, &error_local)) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, error_local->message); return FALSE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* success */ return TRUE; } static gboolean fu_mtd_device_probe(FuDevice *device, GError **error) { FuContext *ctx = fu_device_get_context(device); FuMtdDevice *self = FU_MTD_DEVICE(device); const gchar *name; const gchar *vendor; guint64 flags = 0; guint64 size = 0; g_autoptr(GError) error_local = NULL; /* set physical ID */ if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "mtd", error)) return FALSE; /* flags have to exist */ if (!fu_udev_device_get_sysfs_attr_uint64(FU_UDEV_DEVICE(device), "flags", &flags, &error_local)) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no MTD flags"); return FALSE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* get name */ name = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "name", NULL); if (name != NULL) fu_device_set_name(FU_DEVICE(self), name); /* set vendor ID as the BIOS vendor */ vendor = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_MANUFACTURER); if (vendor != NULL) { g_autofree gchar *vendor_id = g_strdup_printf("DMI:%s", vendor); fu_device_add_vendor_id(device, vendor_id); } /* use vendor and product as an optional instance ID prefix */ fu_device_add_instance_strsafe(device, "NAME", name); fu_device_add_instance_strsafe(device, "VENDOR", vendor); fu_device_add_instance_strsafe(device, "PRODUCT", fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_PRODUCT_NAME)); fu_device_build_instance_id(device, NULL, "MTD", "NAME", NULL); fu_device_build_instance_id(device, NULL, "MTD", "VENDOR", "NAME", NULL); fu_device_build_instance_id(device, NULL, "MTD", "VENDOR", "PRODUCT", "NAME", NULL); /* get properties about the device */ if (!fu_udev_device_get_sysfs_attr_uint64(FU_UDEV_DEVICE(device), "size", &size, error)) return FALSE; fu_device_set_firmware_size_max(device, size); #ifdef HAVE_MTD_USER_H if ((flags & MTD_NO_ERASE) == 0) { if (!fu_udev_device_get_sysfs_attr_uint64(FU_UDEV_DEVICE(device), "erasesize", &self->erasesize, error)) return FALSE; } if (flags & MTD_WRITEABLE) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); #endif /* success */ return TRUE; } static gboolean fu_mtd_device_erase(FuMtdDevice *self, GBytes *fw, FuProgress *progress, GError **error) { #ifdef HAVE_MTD_USER_H g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x0, self->erasesize); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); /* erase each chunk */ for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); struct erase_info_user erase = { .start = fu_chunk_get_address(chk), .length = fu_chunk_get_data_sz(chk), }; if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), 2, (guint8 *)&erase, NULL, FU_MTD_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error(error, "failed to erase @0x%x: ", (guint)erase.start); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as mtd-user.h is unavailable"); return FALSE; #endif } static gboolean fu_mtd_device_write(FuMtdDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); /* rewind */ if (!fu_udev_device_seek(FU_UDEV_DEVICE(self), 0x0, error)) { g_prefix_error(error, "failed to rewind: "); return FALSE; } /* write each chunk */ for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_udev_device_pwrite(FU_UDEV_DEVICE(self), fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to write @0x%x: ", (guint)fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_mtd_device_verify(FuMtdDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); /* verify each chunk */ for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autofree guint8 *buf = g_malloc0(fu_chunk_get_data_sz(chk)); g_autoptr(GBytes) blob1 = fu_chunk_get_bytes(chk); g_autoptr(GBytes) blob2 = NULL; if (!fu_udev_device_pread(FU_UDEV_DEVICE(self), fu_chunk_get_address(chk), buf, fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to read @0x%x: ", (guint)fu_chunk_get_address(chk)); return FALSE; } blob2 = g_bytes_new_static(buf, fu_chunk_get_data_sz(chk)); if (!fu_bytes_compare(blob1, blob2, error)) { g_prefix_error(error, "failed to verify @0x%x: ", (guint)fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_mtd_device_write_verify(FuMtdDevice *self, GBytes *fw, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 10 * 1024); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 50, NULL); /* write */ if (!fu_mtd_device_write(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* verify */ if (!fu_mtd_device_verify(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static GBytes * fu_mtd_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuMtdDevice *self = FU_MTD_DEVICE(device); gsize bufsz = fu_device_get_firmware_size_max(device); g_autofree guint8 *buf = g_malloc0(bufsz); g_autoptr(GPtrArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); /* read each chunk */ chunks = fu_chunk_array_mutable_new(buf, bufsz, 0x0, 0x0, 10 * 1024); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_udev_device_pread(FU_UDEV_DEVICE(self), fu_chunk_get_address(chk), fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to read @0x%x: ", (guint)fu_chunk_get_address(chk)); return NULL; } fu_progress_step_done(progress); } /* success */ return g_bytes_new_take(g_steal_pointer(&buf), bufsz); } static gboolean fu_mtd_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuMtdDevice *self = FU_MTD_DEVICE(device); g_autoptr(GBytes) fw = NULL; /* get data to write */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; if (g_bytes_get_size(fw) > fu_device_get_firmware_size_max(device)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware too large, got 0x%x, expected <= 0x%x", (guint)g_bytes_get_size(fw), (guint)fu_device_get_firmware_size_max(device)); return FALSE; } /* just one step required */ if (self->erasesize == 0) return fu_mtd_device_write_verify(self, fw, progress, error); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 50, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, NULL); /* erase */ if (!fu_mtd_device_erase(self, fw, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* write */ if (!fu_mtd_device_write_verify(self, fw, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_mtd_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuMtdDevice *self = FU_MTD_DEVICE(device); /* load from quirks */ if (g_strcmp0(key, "MtdMetadataOffset") == 0) { if (!fu_strtoull(value, &self->metadata_offset, 0x0, G_MAXUINT32, error)) return FALSE; return TRUE; } if (g_strcmp0(key, "MtdMetadataSize") == 0) { if (!fu_strtoull(value, &self->metadata_size, 0x100, FU_FIRMWARE_SEARCH_MAGIC_BUFSZ_MAX, error)) return FALSE; return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_mtd_device_init(FuMtdDevice *self) { self->metadata_size = FU_FIRMWARE_SEARCH_MAGIC_BUFSZ_MAX; fu_device_set_summary(FU_DEVICE(self), "Memory Technology Device"); fu_device_add_protocol(FU_DEVICE(self), "org.infradead.mtd"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED); fu_device_add_icon(FU_DEVICE(self), "drive-harddisk-solidstate"); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | FU_UDEV_DEVICE_FLAG_OPEN_SYNC); } static void fu_mtd_device_class_init(FuMtdDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->open = fu_mtd_device_open; klass_device->probe = fu_mtd_device_probe; klass_device->setup = fu_mtd_device_setup; klass_device->to_string = fu_mtd_device_to_string; klass_device->dump_firmware = fu_mtd_device_dump_firmware; klass_device->write_firmware = fu_mtd_device_write_firmware; klass_device->set_quirk_kv = fu_mtd_device_set_quirk_kv; } fwupd-1.9.16/plugins/mtd/fu-mtd-device.h000066400000000000000000000004311460375044200200070ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_MTD_DEVICE (fu_mtd_device_get_type()) G_DECLARE_FINAL_TYPE(FuMtdDevice, fu_mtd_device, FU, MTD_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/mtd/fu-mtd-plugin.c000066400000000000000000000022761460375044200200520ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-mtd-device.h" #include "fu-mtd-plugin.h" struct _FuMtdPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuMtdPlugin, fu_mtd_plugin, FU_TYPE_PLUGIN) static gboolean fu_mtd_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { #ifndef HAVE_MTD_USER_H g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not compiled with mtd support"); return FALSE; #endif return TRUE; } static void fu_mtd_plugin_init(FuMtdPlugin *self) { } static void fu_mtd_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "MtdMetadataOffset"); fu_context_add_quirk_key(ctx, "MtdMetadataSize"); fu_plugin_add_device_udev_subsystem(plugin, "mtd"); fu_plugin_add_device_gtype(plugin, FU_TYPE_MTD_DEVICE); } static void fu_mtd_plugin_class_init(FuMtdPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_mtd_plugin_constructed; plugin_class->startup = fu_mtd_plugin_startup; } fwupd-1.9.16/plugins/mtd/fu-mtd-plugin.h000066400000000000000000000003371460375044200200530ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuMtdPlugin, fu_mtd_plugin, FU, MTD_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/mtd/fu-self-test.c000066400000000000000000000065271460375044200177030ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-context-private.h" #include "fu-mtd-device.h" static void fu_test_mtd_device_func(void) { #ifdef HAVE_GUDEV gsize bufsz; gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuDevice) device = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuProgress) progress = fu_progress_new(NULL); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) fw2 = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error = NULL; g_autoptr(GRand) rand = g_rand_new_with_seed(0); g_autoptr(GUdevClient) udev_client = g_udev_client_new(NULL); g_autoptr(GUdevDevice) udev_device = NULL; const gchar *dev_name; /* do not save silo */ ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_context_load_hwinfo(ctx, progress, FU_CONTEXT_HWID_FLAG_LOAD_SMBIOS, &error); g_assert_no_error(error); g_assert_true(ret); udev_device = g_udev_client_query_by_sysfs_path(udev_client, "/sys/devices/virtual/mtd/mtd0"); if (udev_device == NULL) { g_test_skip("could not find mtdram device"); return; } dev_name = g_udev_device_get_property(udev_device, "DEVNAME"); if (g_strcmp0(dev_name, "/dev/mtd0") != 0) { g_test_skip("DEVNAME not /dev/mtd0"); return; } if (!g_file_test(dev_name, G_FILE_TEST_EXISTS)) { g_test_skip("/dev/mtd0 doesn't exist"); return; } /* create device */ device = g_object_new(FU_TYPE_MTD_DEVICE, "context", ctx, "udev-device", udev_device, NULL); locker = fu_device_locker_new(device, &error); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip("no permission to read mtdram device"); return; } g_assert_no_error(error); g_assert_nonnull(locker); dev_name = fu_device_get_name(device); if (g_strcmp0(dev_name, "mtdram test device") != 0) { g_test_skip("device is not mtdram test device"); return; } bufsz = fu_device_get_firmware_size_max(device); g_assert_cmpint(bufsz, ==, 0x400000); /* create a random payload exactly the same size */ for (gsize i = 0; i < bufsz; i++) fu_byte_array_append_uint8(buf, g_rand_int_range(rand, 0x00, 0xFF)); fw = g_bytes_new(buf->data, buf->len); /* write with a verify */ ret = fu_device_write_firmware(device, fw, progress, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); /* dump back */ fu_progress_reset(progress); fw2 = fu_device_dump_firmware(device, progress, &error); g_assert_no_error(error); g_assert_nonnull(fw2); /* verify */ ret = fu_bytes_compare(fw, fw2, &error); g_assert_no_error(error); g_assert_true(ret); #else g_test_skip("no GUdev support"); #endif } int main(int argc, char **argv) { g_autofree gchar *testdatadir = NULL; (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); (void)g_setenv("FWUPD_MTD_VERBOSE", "1", TRUE); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); (void)g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", testdatadir, TRUE); (void)g_setenv("CONFIGURATION_DIRECTORY", testdatadir, TRUE); g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_test_add_func("/mtd/device", fu_test_mtd_device_func); return g_test_run(); } fwupd-1.9.16/plugins/mtd/meson.build000066400000000000000000000021621460375044200173520ustar00rootroot00000000000000if get_option('plugin_mtd').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginMtd"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('mtd.quirk') plugin_builtin_mtd = static_library('fu_plugin_mtd', sources: [ 'fu-mtd-plugin.c', 'fu-mtd-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_mtd if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'mtd-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_mtd, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ cargs, '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('mtd-self-test', e, env: env) # added to installed-tests endif endif fwupd-1.9.16/plugins/mtd/mtd.quirk000066400000000000000000000006321460375044200170510ustar00rootroot00000000000000[MTD\VENDOR_PINE64&PRODUCT_PinePhone-Pro&NAME_spi1.0] Name = Tow-Boot platform firmware FirmwareGType = FuUswidFirmware # Intel GPUs [MTD\NAME_i915.spi.1024.DAM] Flags = no-probe [MTD\NAME_i915.spi.1024.DESCRIPTOR] Flags = no-probe [MTD\NAME_i915.spi.1024.GSC] Flags = no-probe [MTD\NAME_i915.spi.1024.OptionROM] Flags = no-probe # Intel SPI Controller [MTD\NAME_0000:00:1f.5] Name = Internal SPI Controller fwupd-1.9.16/plugins/mtd/tests/000077500000000000000000000000001460375044200163515ustar00rootroot00000000000000fwupd-1.9.16/plugins/mtd/tests/dmi000077700000000000000000000000001460375044200246512../../../libfwupdplugin/tests/dmi/ustar00rootroot00000000000000fwupd-1.9.16/plugins/nitrokey/000077500000000000000000000000001460375044200162675ustar00rootroot00000000000000fwupd-1.9.16/plugins/nitrokey/README.md000066400000000000000000000025721460375044200175540ustar00rootroot00000000000000--- title: Plugin: Nitrokey --- ## Introduction This plugin is used to get the correct version number on Nitrokey storage devices. These devices have updatable firmware but so far no updates are available from the vendor. The device is switched to a DFU bootloader only when the secret firmware pin is entered into the nitrokey-app tool. This cannot be automated. ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_20A0&PID_4109` ## Update Behavior The device usually presents in runtime mode, but on detach re-enumerates with a different USB VID and PID in DFU mode. The device is then handled by the `dfu` plugin. On DFU attach the device again re-enumerates back to the runtime mode. For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x20A0` in runtime mode and `USB:0x03EB` in bootloader mode. ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.0.2`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Szczepan Zalega: @szszszsz fwupd-1.9.16/plugins/nitrokey/fu-nitrokey-common.c000066400000000000000000000015531460375044200222010ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-nitrokey-common.h" static guint32 fu_nitrokey_perform_crc32_mutate(guint32 crc, guint32 data) { crc = crc ^ data; for (guint i = 0; i < 32; i++) { if (crc & 0x80000000) { /* polynomial used in STM32 */ crc = (crc << 1) ^ 0x04C11DB7; } else { crc = (crc << 1); } } return crc; } guint32 fu_nitrokey_perform_crc32(const guint8 *data, gsize size) { guint32 crc = 0xffffffff; g_autofree guint32 *data_aligned = NULL; data_aligned = g_new0(guint32, (size / 4) + 1); memcpy(data_aligned, data, size); for (gsize idx = 0; idx * 4 < size; idx++) { guint32 data_aligned_le = GUINT32_FROM_LE(data_aligned[idx]); crc = fu_nitrokey_perform_crc32_mutate(crc, data_aligned_le); } return crc; } fwupd-1.9.16/plugins/nitrokey/fu-nitrokey-common.h000066400000000000000000000031111460375044200221760ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include guint32 fu_nitrokey_perform_crc32(const guint8 *data, gsize size); #define NITROKEY_TRANSACTION_TIMEOUT 100 /* ms */ #define NITROKEY_NR_RETRIES 5 #define NITROKEY_REQUEST_DATA_LENGTH 59 #define NITROKEY_REPLY_DATA_LENGTH 53 #define NITROKEY_CMD_GET_DEVICE_STATUS (0x20 + 14) typedef struct __attribute__((packed)) { guint8 command; guint8 payload[NITROKEY_REQUEST_DATA_LENGTH]; guint32 crc; } NitrokeyHidRequest; typedef struct __attribute__((packed)) { guint8 device_status; guint8 command_id; guint32 last_command_crc; guint8 last_command_status; guint8 payload[NITROKEY_REPLY_DATA_LENGTH]; guint32 crc; } NitrokeyHidResponse; /* based from libnitrokey/stick20_commands.h from libnitrokey v3.4.1 */ typedef struct __attribute__((packed)) { guint8 _padding[18]; /* stick20_commands.h:132 // 26 - 8 = 18 */ guint8 SendCounter; guint8 SendDataType; guint8 FollowBytesFlag; guint8 SendSize; guint16 MagicNumber_StickConfig; guint8 ReadWriteFlagUncryptedVolume; guint8 ReadWriteFlagCryptedVolume; guint8 VersionMajor; guint8 VersionMinor; guint8 VersionReservedByte; guint8 VersionBuildIteration; guint8 ReadWriteFlagHiddenVolume; guint8 FirmwareLocked; guint8 NewSDCardFound; guint8 SDFillWithRandomChars; guint32 ActiveSD_CardID; guint8 VolumeActiveFlag; guint8 NewSmartCardFound; guint8 UserPwRetryCount; guint8 AdminPwRetryCount; guint32 ActiveSmartCardID; guint8 StickKeysNotInitiated; } NitrokeyGetDeviceStatusPayload; fwupd-1.9.16/plugins/nitrokey/fu-nitrokey-device.c000066400000000000000000000107641460375044200221540ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-nitrokey-common.h" #include "fu-nitrokey-device.h" G_DEFINE_TYPE(FuNitrokeyDevice, fu_nitrokey_device, FU_TYPE_HID_DEVICE) typedef struct { guint8 command; const guint8 *buf_in; gsize buf_in_sz; guint8 *buf_out; gsize buf_out_sz; } NitrokeyRequest; static gboolean nitrokey_execute_cmd_cb(FuDevice *device, gpointer user_data, GError **error) { NitrokeyRequest *req = (NitrokeyRequest *)user_data; NitrokeyHidResponse res; guint32 crc_tmp; guint8 buf[64]; /* create the request */ memset(buf, 0x00, sizeof(buf)); buf[0] = req->command; if (req->buf_in != NULL) memcpy(&buf[1], req->buf_in, req->buf_in_sz); crc_tmp = fu_nitrokey_perform_crc32(buf, sizeof(buf) - 4); fu_memwrite_uint32(&buf[NITROKEY_REQUEST_DATA_LENGTH + 1], crc_tmp, G_LITTLE_ENDIAN); /* send request */ if (!fu_hid_device_set_report(FU_HID_DEVICE(device), 0x0002, buf, sizeof(buf), NITROKEY_TRANSACTION_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; /* get response */ memset(buf, 0x00, sizeof(buf)); if (!fu_hid_device_get_report(FU_HID_DEVICE(device), 0x0002, buf, sizeof(buf), NITROKEY_TRANSACTION_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; /* verify this is the answer to the question we asked */ memcpy(&res, buf, sizeof(buf)); if (GUINT32_FROM_LE(res.last_command_crc) != crc_tmp) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got response CRC %x, expected %x", GUINT32_FROM_LE(res.last_command_crc), crc_tmp); return FALSE; } /* verify the response checksum */ crc_tmp = fu_nitrokey_perform_crc32(buf, sizeof(res) - 4); if (GUINT32_FROM_LE(res.crc) != crc_tmp) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got packet CRC %x, expected %x", GUINT32_FROM_LE(res.crc), crc_tmp); return FALSE; } /* copy out the payload */ if (req->buf_out != NULL) memcpy(req->buf_out, &res.payload, req->buf_out_sz); /* success */ return TRUE; } static gboolean nitrokey_execute_cmd_full(FuDevice *device, guint8 command, const guint8 *buf_in, gsize buf_in_sz, guint8 *buf_out, gsize buf_out_sz, GError **error) { NitrokeyRequest req = { .command = command, .buf_in = buf_in, .buf_in_sz = buf_in_sz, .buf_out = buf_out, .buf_out_sz = buf_out_sz, }; g_return_val_if_fail(buf_in_sz <= NITROKEY_REQUEST_DATA_LENGTH, FALSE); g_return_val_if_fail(buf_out_sz <= NITROKEY_REPLY_DATA_LENGTH, FALSE); return fu_device_retry(device, nitrokey_execute_cmd_cb, NITROKEY_NR_RETRIES, &req, error); } static gboolean fu_nitrokey_device_setup(FuDevice *device, GError **error) { NitrokeyGetDeviceStatusPayload payload; guint8 buf_reply[NITROKEY_REPLY_DATA_LENGTH]; g_autofree gchar *version = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_nitrokey_device_parent_class)->setup(device, error)) return FALSE; /* get firmware version */ if (!nitrokey_execute_cmd_full(device, NITROKEY_CMD_GET_DEVICE_STATUS, NULL, 0, buf_reply, sizeof(buf_reply), error)) { g_prefix_error(error, "failed to do get firmware version: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "payload", buf_reply, sizeof(buf_reply)); memcpy(&payload, buf_reply, sizeof(payload)); version = g_strdup_printf("%u.%u", payload.VersionMajor, payload.VersionMinor); fu_device_set_version(FU_DEVICE(device), version); /* success */ return TRUE; } static void fu_nitrokey_device_init(FuNitrokeyDevice *self) { fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_add_protocol(FU_DEVICE(self), "org.usb.dfu"); fu_device_retry_set_delay(FU_DEVICE(self), 100); } static void fu_nitrokey_device_class_init(FuNitrokeyDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_nitrokey_device_setup; } fwupd-1.9.16/plugins/nitrokey/fu-nitrokey-device.h000066400000000000000000000005711460375044200221540ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_NITROKEY_DEVICE (fu_nitrokey_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuNitrokeyDevice, fu_nitrokey_device, FU, NITROKEY_DEVICE, FuHidDevice) struct _FuNitrokeyDeviceClass { FuHidDeviceClass parent_class; }; fwupd-1.9.16/plugins/nitrokey/fu-nitrokey-plugin.c000066400000000000000000000013421460375044200222030ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-nitrokey-device.h" #include "fu-nitrokey-plugin.h" struct _FuNitrokeyPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuNitrokeyPlugin, fu_nitrokey_plugin, FU_TYPE_PLUGIN) static void fu_nitrokey_plugin_init(FuNitrokeyPlugin *self) { } static void fu_nitrokey_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_NITROKEY_DEVICE); } static void fu_nitrokey_plugin_class_init(FuNitrokeyPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_nitrokey_plugin_constructed; } fwupd-1.9.16/plugins/nitrokey/fu-nitrokey-plugin.h000066400000000000000000000003561460375044200222140ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuNitrokeyPlugin, fu_nitrokey_plugin, FU, NITROKEY_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/nitrokey/fu-self-test.c000066400000000000000000000063221460375044200207540ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-nitrokey-common.h" static void fu_nitrokey_version_test(void) { /* use the Nitrokey Storage v0.53 status response for test, CRC 0xa2762d14 */ NitrokeyGetDeviceStatusPayload payload; NitrokeyHidResponse res; guint32 crc_tmp; /* 65 bytes of response from HIDAPI; first byte is always 0 */ const guint8 buf[] = {/*0x00,*/ 0x00, 0x2e, 0xef, 0xc4, 0x9b, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x2e, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x1c, 0x18, 0x33, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x45, 0x24, 0xf1, 0x4c, 0x01, 0x00, 0x03, 0x03, 0xc7, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x2d, 0x76, 0xa2}; /* testing the whole path, as in fu_nitrokey_device_setup()*/ memcpy(&res, buf, sizeof(buf)); memcpy(&payload, &res.payload, sizeof(payload)); /* verify the version number */ g_assert_cmpint(payload.VersionMajor, ==, 0); g_assert_cmpint(payload.VersionMinor, ==, 53); g_assert_cmpint(buf[34], ==, payload.VersionMinor); g_assert_cmpint(payload.VersionBuildIteration, ==, 0); /* verify the response checksum */ crc_tmp = fu_nitrokey_perform_crc32(buf, sizeof(res) - 4); g_assert_cmpint(GUINT32_FROM_LE(res.crc), ==, crc_tmp); } static void fu_nitrokey_version_test_static(void) { /* use static response from numbered bytes, to make sure fields occupy * expected bytes */ NitrokeyGetDeviceStatusPayload payload; NitrokeyHidResponse res; const guint8 buf[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, }; memcpy(&res, buf, sizeof(buf)); memcpy(&payload, &res.payload, sizeof(payload)); g_assert_cmpint(payload.VersionMajor, ==, 33); /* 0x1a */ g_assert_cmpint(payload.VersionMinor, ==, 34); /* 0x1b */ g_assert_cmpint(buf[34], ==, 34); } static void fu_nitrokey_func(void) { const guint8 buf[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; g_assert_cmpint(fu_nitrokey_perform_crc32(buf, 16), ==, 0x081B46CA); g_assert_cmpint(fu_nitrokey_perform_crc32(buf, 15), ==, 0xED7320AB); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func("/fwupd/nitrokey", fu_nitrokey_func); g_test_add_func("/fwupd/nitrokey-version-static", fu_nitrokey_version_test_static); g_test_add_func("/fwupd/nitrokey-version", fu_nitrokey_version_test); return g_test_run(); } fwupd-1.9.16/plugins/nitrokey/lsusb.txt000066400000000000000000000165571460375044200201760ustar00rootroot00000000000000Bus 001 Device 007: ID 20a0:4109 Clay Logic Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x20a0 Clay Logic idProduct 0x4109 bcdDevice 1.00 iManufacturer 1 Nitrokey iProduct 2 Nitrokey Storage iSerial 3 0000000000000 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 141 bNumInterfaces 3 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 8 Mass Storage bInterfaceSubClass 6 SCSI bInterfaceProtocol 80 Bulk-Only iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 3 bInterfaceClass 11 Chip/SmartCard bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 ChipCard Interface Descriptor: bLength 54 bDescriptorType 33 bcdCCID 1.10 (Warning: Only accurate for version 1.0) nMaxSlotIndex 0 bVoltageSupport 2 3.0V dwProtocols 2 T=1 dwDefaultClock 3600 dwMaximumClock 3600 bNumClockSupported 0 dwDataRate 9677 bps dwMaxDataRate 116129 bps bNumDataRatesSupp. 0 dwMaxIFSD 261 dwSyncProtocols 00000000 dwMechanical 00000000 dwFeatures 000104BA Auto configuration based on ATR Auto voltage selection Auto clock change Auto baud rate change Auto PPS made by CCID Auto IFSD exchange TPDU level exchange dwMaxCCIDMsgLen 271 bClassGetResponse 00 bClassEnvelope 00 wlcdLayout none bPINSupport 0 bMaxCCIDBusySlots 1 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0010 1x 16 bytes bInterval 16 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x04 EP 4 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x85 EP 5 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0200 1x 512 bytes bInterval 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 1 Keyboard iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 71 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x86 EP 6 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 5 Device Qualifier (for other device speed): bLength 10 bDescriptorType 6 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 bNumConfigurations 1 Device Status: 0x0000 (Bus Powered) Bus 001 Device 055: ID 03eb:2ff1 Atmel Corp. at32uc3a3 DFU bootloader Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x03eb Atmel Corp. idProduct 0x2ff1 at32uc3a3 DFU bootloader bcdDevice 10.00 iManufacturer 1 ATMEL iProduct 2 AT32UC3A DFU iSerial 3 1.0.3 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 27 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 0 bInterfaceClass 254 Application Specific Interface bInterfaceSubClass 1 Device Firmware Update bInterfaceProtocol 2 iInterface 0 Device Firmware Upgrade Interface Descriptor: bLength 9 bDescriptorType 33 bmAttributes 15 Will Detach Manifestation Tolerant Upload Supported Download Supported wDetachTimeout 0 milliseconds wTransferSize 65535 bytes bcdDFUVersion 1.01 Device Status: 0x0001 Self Powered fwupd-1.9.16/plugins/nitrokey/meson.build000066400000000000000000000023271460375044200204350ustar00rootroot00000000000000if get_option('plugin_nitrokey').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginNitrokey"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('nitrokey.quirk') plugin_builtin_nitrokey = static_library('fu_plugin_nitrokey', sources: [ 'fu-nitrokey-device.c', 'fu-nitrokey-common.c', 'fu-nitrokey-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_nitrokey if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'nitrokey-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: [ plugin_deps, valgrind, ], link_with: [ plugin_libs, plugin_builtin_nitrokey, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('nitrokey-self-test', e, env: env) # added to installed-tests endif endif fwupd-1.9.16/plugins/nitrokey/nitrokey.quirk000066400000000000000000000003111460375044200212030ustar00rootroot00000000000000# Nitrokey Storage [USB\VID_20A0&PID_4109] Plugin = nitrokey Flags = needs-bootloader,use-runtime-version CounterpartGuid = USB\VID_03EB&PID_2FF1 Summary = A secure memory stick Icon = media-removable fwupd-1.9.16/plugins/nordic-hid/000077500000000000000000000000001460375044200164435ustar00rootroot00000000000000fwupd-1.9.16/plugins/nordic-hid/README.md000066400000000000000000000065421460375044200177310ustar00rootroot00000000000000--- title: Plugin: Nordic HID --- ## Introduction This plugin is able flash the firmware for the hardware supported by `nRF52-Desktop`. Tested with the following devices: * [nrf52840dk development kit](https://www.nordicsemi.com/Products/nRF52840) * [nRF52840 Dongle](https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dongle) * nRF52840 Gaming Mouse * nRF52832 Desktop Keyboard The plugin is using Nordic Semiconductor [HID config channel](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/applications/nrf_desktop/doc/config_channel.html) to perform devices update. ## Firmware Format The cabinet file contains ZIP archive prepared by Nordic Semiconductor. This ZIP archive includes 2 signed image blobs for the target device, one firmware blob per application slot, and the `manifest.json` file with the metadata description. At the moment only [nRF Secure Immutable Bootloader](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/samples/bootloader/README.html#bootloader) aka "B0" is supported and tested. This plugin supports the following protocol ID: * Nordic HID Config Channel: `com.nordic.hidcfgchannel` ## GUID Generation For GUID generation the target board name, bootloader name and generation are used in addition to standard HIDRAW DeviceInstanceId values. The generation string is an application-specific property that allows to distinguish configurations that use the same board and bootloader, but are not interoperable. GUID examples: * `HIDRAW\VEN_1915&DEV_52DE&BOARD_nrf52840dk&BL_B0&GEN_default` * `HIDRAW\VEN_1915&DEV_52DE&BOARD_nrf52840dk&BL_MCUBOOT&GEN_office` Because handling of the generation parameter was introduced later, it is not supported by older versions of fwupd. To ensure compatibility with firmware updates that were released before introducing the support for the generation parameter, devices with the `default` generation report an additional GUID that omits the generation parameter. GUID examples (devices with generation set to `default` or without support for the generation parameter): * `HIDRAW\VEN_1915&DEV_52DE&BOARD_nrf52840dk&BL_B0` * `HIDRAW\VEN_1915&DEV_52DE&BOARD_nrf52840dk&BL_MCUBOOT` ## Quirk Use This plugin also uses the following plugin-specific quirks: ### NordicHidBootloader Explicitly set the expected bootloader type. Only the `B0` bootloader can be set by the quirk file. Other values can only be provided by device. This quirk must be set for devices without support of `bootloader variant` DFU option. ### NordicHidGeneration Explicitly set the expected generation. Only the `default` generation can be set by the quirk file. Other values can only be provided by device. This quirk must be set for devices that do not support the `devinfo` DFU option. ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Vendor ID Security The vendor ID is set from the HID vendor ID, in this instance set to `HIDRAW:0x1915`. ## External Interface Access This plugin requires ioctl `HIDIOCSFEATURE` and `HIDIOCGFEATURE` access. ## Version Considerations This plugin has been available since fwupd version `1.7.3`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Marek Pieta: @MarekPieta fwupd-1.9.16/plugins/nordic-hid/fu-nordic-hid-archive.c000066400000000000000000000131551460375044200226630ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-nordic-hid-archive.h" #include "fu-nordic-hid-firmware-b0.h" #include "fu-nordic-hid-firmware-mcuboot.h" /* current version format is 0 */ #define MAX_VERSION_FORMAT 0 struct _FuNordicHidArchive { FuFirmwareClass parent_instance; }; G_DEFINE_TYPE(FuNordicHidArchive, fu_nordic_hid_archive, FU_TYPE_FIRMWARE) static gboolean fu_nordic_hid_archive_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { JsonNode *json_root_node; JsonObject *json_obj; JsonArray *json_files; guint manifest_ver; guint files_cnt = 0; g_autoptr(FuArchive) archive = NULL; g_autoptr(GBytes) manifest = NULL; g_autoptr(JsonParser) parser = json_parser_new(); /* load archive */ archive = fu_archive_new(fw, FU_ARCHIVE_FLAG_IGNORE_PATH, error); if (archive == NULL) return FALSE; manifest = fu_archive_lookup_by_fn(archive, "manifest.json", error); if (manifest == NULL) return FALSE; /* parse JSON */ if (!json_parser_load_from_data(parser, (const gchar *)g_bytes_get_data(manifest, NULL), (gssize)g_bytes_get_size(manifest), error)) { g_prefix_error(error, "manifest not in JSON format: "); return FALSE; } json_root_node = json_parser_get_root(parser); if (json_root_node == NULL || !JSON_NODE_HOLDS_OBJECT(json_root_node)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "manifest invalid as has no root"); return FALSE; } json_obj = json_node_get_object(json_root_node); if (!json_object_has_member(json_obj, "format-version")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "manifest has invalid format"); return FALSE; } manifest_ver = json_object_get_int_member(json_obj, "format-version"); if (manifest_ver > MAX_VERSION_FORMAT) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unsupported manifest version"); return FALSE; } json_files = json_object_get_array_member(json_obj, "files"); if (json_files == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "manifest invalid as has no 'files' array"); return FALSE; } files_cnt = json_array_get_length(json_files); if (files_cnt == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "manifest invalid as contains no update images"); return FALSE; } for (guint i = 0; i < files_cnt; i++) { const gchar *filename = NULL; const gchar *bootloader_name = NULL; guint image_addr = 0; JsonObject *obj = json_array_get_object_element(json_files, i); g_autoptr(FuFirmware) image = NULL; g_autofree gchar *image_id = NULL; g_auto(GStrv) board_split = NULL; g_autoptr(GBytes) blob = NULL; if (!json_object_has_member(obj, "file")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "manifest invalid as has no file name for the image"); return FALSE; } filename = json_object_get_string_member(obj, "file"); blob = fu_archive_lookup_by_fn(archive, filename, error); if (blob == NULL) return FALSE; if (json_object_has_member(obj, "version_B0")) { bootloader_name = "B0"; image = g_object_new(FU_TYPE_NORDIC_HID_FIRMWARE_B0, NULL); } else if (json_object_has_member(obj, "version_MCUBOOT")) { bootloader_name = "MCUBOOT"; image = g_object_new(FU_TYPE_NORDIC_HID_FIRMWARE_MCUBOOT, NULL); } else if (json_object_has_member(obj, "version_MCUBOOT+XIP")) { bootloader_name = "MCUBOOT+XIP"; image = g_object_new(FU_TYPE_NORDIC_HID_FIRMWARE_MCUBOOT, NULL); } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "only B0 and MCUboot bootloaders are supported"); return FALSE; } /* the "board" field contains board name before "_" symbol */ if (!json_object_has_member(obj, "board")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "manifest invalid as has no target board information"); return FALSE; } board_split = g_strsplit(json_object_get_string_member(obj, "board"), "_", -1); if (board_split[0] == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "manifest invalid as has no target board information"); return FALSE; } /* The images for B0 or MCUboot+XIP bootloader are listed in strict order: * this is guaranteed by producer set the id format as * __N, i.e "nrf52840dk_B0_bank0". * For MCUBoot bootloader that swaps images, only a single image is available */ image_id = g_strdup_printf("%s_%s_bank%01u", board_split[0], bootloader_name, i); if (!fu_firmware_parse(image, blob, flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return FALSE; fu_firmware_set_id(image, image_id); fu_firmware_set_idx(image, i); if (json_object_has_member(obj, "load_address")) { image_addr = json_object_get_int_member(obj, "load_address"); fu_firmware_set_addr(image, image_addr); } if (!fu_firmware_add_image_full(firmware, image, error)) return FALSE; } /* success */ return TRUE; } static void fu_nordic_hid_archive_init(FuNordicHidArchive *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); } static void fu_nordic_hid_archive_class_init(FuNordicHidArchiveClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_nordic_hid_archive_parse; } fwupd-1.9.16/plugins/nordic-hid/fu-nordic-hid-archive.h000066400000000000000000000005411460375044200226630ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_NORDIC_HID_ARCHIVE (fu_nordic_hid_archive_get_type()) G_DECLARE_FINAL_TYPE(FuNordicHidArchive, fu_nordic_hid_archive, FU, NORDIC_HID_ARCHIVE, FuArchiveFirmware) fwupd-1.9.16/plugins/nordic-hid/fu-nordic-hid-cfg-channel.c000066400000000000000000001344031460375044200234070ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricardo Cañuelo * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_HIDRAW_H #include #include #endif #include "fu-nordic-hid-archive.h" #include "fu-nordic-hid-cfg-channel.h" #define HID_REPORT_ID 6 #define REPORT_SIZE 30 #define REPORT_DATA_MAX_LEN (REPORT_SIZE - 5) #define HWID_LEN 8 #define PEERS_CACHE_LEN 16 #define END_OF_TRANSFER_CHAR 0x0a #define INVALID_PEER_ID 0xFF #define FU_NORDIC_HID_CFG_CHANNEL_RETRIES 10 #define FU_NORDIC_HID_CFG_CHANNEL_RETRY_DELAY 50 /* ms */ #define FU_NORDIC_HID_CFG_CHANNEL_DFU_RETRY_DELAY 500 /* ms */ #define FU_NORDIC_HID_CFG_CHANNEL_PEERS_POLL_INTERVAL 2000 /* ms */ #define FU_NORDIC_HID_CFG_CHANNEL_IOCTL_TIMEOUT 5000 /* ms */ typedef enum { CONFIG_STATUS_PENDING, CONFIG_STATUS_GET_MAX_MOD_ID, CONFIG_STATUS_GET_HWID, CONFIG_STATUS_GET_BOARD_NAME, CONFIG_STATUS_INDEX_PEERS, CONFIG_STATUS_GET_PEER, CONFIG_STATUS_SET, CONFIG_STATUS_FETCH, CONFIG_STATUS_SUCCESS, CONFIG_STATUS_TIMEOUT, CONFIG_STATUS_REJECT, CONFIG_STATUS_WRITE_FAIL, CONFIG_STATUS_DISCONNECTED, CONFIG_STATUS_GET_PEERS_CACHE, CONFIG_STATUS_FAULT = 99, } FuNordicCfgStatus; typedef enum { DFU_STATE_INACTIVE, DFU_STATE_ACTIVE, DFU_STATE_STORING, DFU_STATE_CLEANING, } FuNordicCfgSyncState; typedef struct __attribute__((packed)) { guint8 report_id; guint8 recipient; guint8 event_id; guint8 status; guint8 data_len; guint8 data[REPORT_DATA_MAX_LEN]; } FuNordicCfgChannelMsg; typedef struct { guint8 idx; gchar *name; } FuNordicCfgChannelModuleOption; typedef struct { guint8 idx; gchar *name; GPtrArray *options; /* of FuNordicCfgChannelModuleOption */ } FuNordicCfgChannelModule; typedef struct { guint8 status; guint8 *buf; gsize bufsz; } FuNordicCfgChannelRcvHelper; typedef struct { guint8 dfu_state; guint32 img_length; guint32 img_csum; guint32 offset; guint16 sync_buffer_size; } FuNordicCfgChannelDfuInfo; G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuNordicCfgChannelMsg, g_free); G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuNordicCfgChannelDfuInfo, g_free); struct _FuNordicHidCfgChannel { FuUdevDevice parent_instance; gboolean dfu_support; gboolean peers_cache_support; guint8 peers_cache[PEERS_CACHE_LEN]; gchar *board_name; gchar *bl_name; gchar *generation; guint16 vid; guint16 pid; guint8 flash_area_id; guint32 flashed_image_len; guint8 peer_id; FuUdevDevice *parent_udev; GPtrArray *modules; /* of FuNordicCfgChannelModule */ }; G_DEFINE_TYPE(FuNordicHidCfgChannel, fu_nordic_hid_cfg_channel, FU_TYPE_UDEV_DEVICE) static FuNordicHidCfgChannel * fu_nordic_hid_cfg_channel_new(guint8 id, FuNordicHidCfgChannel *parent); static void fu_nordic_hid_cfg_channel_module_option_free(FuNordicCfgChannelModuleOption *opt) { g_free(opt->name); g_free(opt); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuNordicCfgChannelModuleOption, fu_nordic_hid_cfg_channel_module_option_free); static void fu_nordic_hid_cfg_channel_module_free(FuNordicCfgChannelModule *mod) { if (mod->options != NULL) g_ptr_array_unref(mod->options); g_free(mod->name); g_free(mod); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuNordicCfgChannelModule, fu_nordic_hid_cfg_channel_module_free); #ifdef HAVE_HIDRAW_H static FuUdevDevice * fu_nordic_hid_cfg_channel_get_udev_device(FuNordicHidCfgChannel *self, GError **error) { /* ourselves */ if (self->peer_id == 0) return FU_UDEV_DEVICE(self); /* parent */ if (self->parent_udev == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no parent for peer 0x%02x", self->peer_id); return NULL; } return self->parent_udev; } #endif static gboolean fu_nordic_hid_cfg_channel_send(FuNordicHidCfgChannel *self, guint8 *buf, gsize bufsz, GError **error) { #ifdef HAVE_HIDRAW_H FuUdevDevice *udev_device = fu_nordic_hid_cfg_channel_get_udev_device(self, error); if (udev_device == NULL) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "Sent", buf, bufsz); if (!fu_udev_device_ioctl(udev_device, HIDIOCSFEATURE(bufsz), buf, NULL, FU_NORDIC_HID_CFG_CHANNEL_IOCTL_TIMEOUT, error)) return FALSE; return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_nordic_hid_cfg_channel_receive(FuNordicHidCfgChannel *self, guint8 *buf, gsize bufsz, GError **error) { g_autoptr(FuNordicCfgChannelMsg) recv_msg = g_new0(FuNordicCfgChannelMsg, 1); #ifdef HAVE_HIDRAW_H FuUdevDevice *udev_device = fu_nordic_hid_cfg_channel_get_udev_device(self, error); if (udev_device == NULL) return FALSE; for (gint i = 1; i < 100; i++) { recv_msg->report_id = HID_REPORT_ID; recv_msg->recipient = self->peer_id; if (!fu_udev_device_ioctl(udev_device, HIDIOCGFEATURE(sizeof(*recv_msg)), (guint8 *)recv_msg, NULL, FU_NORDIC_HID_CFG_CHANNEL_IOCTL_TIMEOUT, error)) return FALSE; /* if the device is busy it return 06 00 00 00 00 response */ if (recv_msg->report_id == HID_REPORT_ID && (recv_msg->recipient | recv_msg->event_id | recv_msg->status | recv_msg->data_len)) break; fu_device_sleep(FU_DEVICE(self), 1); /* ms */ } if (!fu_memcpy_safe(buf, bufsz, 0, (guint8 *)recv_msg, sizeof(*recv_msg), 0, sizeof(*recv_msg), error)) { return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "Received", buf, bufsz); /* * [TODO]: Possibly add the report-id fix for Bluez versions < 5.56: * https://github.com/bluez/bluez/commit/35a2c50437cca4d26ac6537ce3a964bb509c9b62 * * See fu_pxi_ble_device_get_feature() in * plugins/pixart-rf/fu-pxi-ble-device.c for an example. */ return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_nordic_hid_cfg_channel_receive_cb(FuDevice *device, gpointer user_data, GError **error) { FuNordicCfgChannelRcvHelper *args = (FuNordicCfgChannelRcvHelper *)user_data; FuNordicHidCfgChannel *self = FU_NORDIC_HID_CFG_CHANNEL(device); FuNordicCfgChannelMsg *recv_msg = NULL; if (!fu_nordic_hid_cfg_channel_receive(self, args->buf, args->bufsz, error)) return FALSE; recv_msg = (FuNordicCfgChannelMsg *)args->buf; if (recv_msg->status != args->status) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "received status: 0x%02x, expected: 0x%02x", recv_msg->status, args->status); return FALSE; } /* success */ return TRUE; } /* * fu_nordic_hid_cfg_channel_get_event_id: * @module_name: module name, NULL for generic operations * @option_name: option name, NULL for generic module operations * * Construct Event ID from module and option names. * * Returns: %TRUE if module/option pair found */ static gboolean fu_nordic_hid_cfg_channel_get_event_id(FuNordicHidCfgChannel *self, const gchar *module_name, const gchar *option_name, guint8 *event_id) { FuNordicCfgChannelModule *mod = NULL; guint id = 0; *event_id = 0; /* for generic operations */ if (module_name == NULL) return TRUE; for (id = 0; id < self->modules->len; id++) { mod = g_ptr_array_index(self->modules, id); if (g_strcmp0(module_name, mod->name) == 0) break; } if (mod == NULL || id > 0x0f) return FALSE; *event_id = id << 4; /* for generic module operations */ if (option_name == NULL) return TRUE; for (guint i = 0; i < mod->options->len && i <= 0x0f; i++) { FuNordicCfgChannelModuleOption *opt = g_ptr_array_index(mod->options, i); if (g_strcmp0(option_name, opt->name) == 0) { *event_id = (id << 4) + opt->idx; return TRUE; } } /* module have no requested option */ return FALSE; } static gboolean fu_nordic_hid_cfg_channel_cmd_send_by_id(FuNordicHidCfgChannel *self, guint8 event_id, guint8 status, guint8 *data, guint8 data_len, GError **error) { g_autoptr(FuNordicCfgChannelMsg) msg = g_new0(FuNordicCfgChannelMsg, 1); msg->report_id = HID_REPORT_ID; msg->recipient = self->peer_id; msg->event_id = event_id; msg->status = status; msg->data_len = 0; if (data != NULL) { if (data_len > REPORT_DATA_MAX_LEN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "requested to send %d bytes, while maximum is %d", data_len, REPORT_DATA_MAX_LEN); return FALSE; } if (!fu_memcpy_safe(msg->data, REPORT_DATA_MAX_LEN, 0, data, data_len, 0, data_len, error)) return FALSE; msg->data_len = data_len; } if (!fu_nordic_hid_cfg_channel_send(self, (guint8 *)msg, sizeof(*msg), error)) { g_prefix_error(error, "failed to send: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_nordic_hid_cfg_channel_cmd_send(FuNordicHidCfgChannel *self, const gchar *module_name, const gchar *option_name, guint8 status, guint8 *data, guint8 data_len, GError **error) { guint8 event_id = 0; if (!fu_nordic_hid_cfg_channel_get_event_id(self, module_name, option_name, &event_id)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "requested non-existing module %s with option %s", module_name, option_name); return FALSE; } if (!fu_nordic_hid_cfg_channel_cmd_send_by_id(self, event_id, status, data, data_len, error)) { g_prefix_error(error, "failed to send: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_nordic_hid_cfg_channel_cmd_receive(FuNordicHidCfgChannel *self, guint8 status, FuNordicCfgChannelMsg *res, GError **error) { FuNordicCfgChannelRcvHelper helper; res->report_id = HID_REPORT_ID; helper.status = status; helper.buf = (guint8 *)res; helper.bufsz = sizeof(*res); if (!fu_device_retry(FU_DEVICE(self), fu_nordic_hid_cfg_channel_receive_cb, FU_NORDIC_HID_CFG_CHANNEL_RETRIES, &helper, error)) { g_prefix_error(error, "Failed on receive: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_nordic_hid_cfg_channel_is_cached_peer_connected(guint8 peer_cache_val) { return (peer_cache_val % 2) != 0; } static void fu_nordic_hid_cfg_channel_check_children_update_pending_cb(FuDevice *device, GParamSpec *pspec, gpointer user_data) { FuNordicHidCfgChannel *self = FU_NORDIC_HID_CFG_CHANNEL(user_data); GPtrArray *children = fu_device_get_children(FU_DEVICE(self)); gboolean update_pending = FALSE; for (guint i = 0; i < children->len; i++) { FuDevice *peer = g_ptr_array_index(children, i); if (fu_device_has_internal_flag(peer, FU_DEVICE_INTERNAL_FLAG_UPDATE_PENDING)) { update_pending = TRUE; break; } } if (update_pending) { fu_device_add_problem(FU_DEVICE(self), FWUPD_DEVICE_PROBLEM_UPDATE_PENDING); } else { fu_device_remove_problem(FU_DEVICE(self), FWUPD_DEVICE_PROBLEM_UPDATE_PENDING); } } static void fu_nordic_hid_cfg_channel_add_peer(FuNordicHidCfgChannel *self, guint8 peer_id) { g_autoptr(GError) error_local = NULL; g_autoptr(FuNordicHidCfgChannel) peer = NULL; peer = fu_nordic_hid_cfg_channel_new(peer_id, self); /* ensure that the general quirk content for Nordic HID devices is applied */ fu_device_add_instance_id_full(FU_DEVICE(peer), "HIDRAW\\VEN_1915", FU_DEVICE_INSTANCE_FLAG_QUIRKS); if (!fu_device_setup(FU_DEVICE(peer), &error_local)) { g_debug("failed to discover peer 0x%02x: %s", peer_id, error_local->message); return; } g_debug("peer 0x%02x discovered", peer_id); /* if any of the peripherals have a pending update, inhibit the dongle */ g_signal_connect(FU_DEVICE(peer), "notify::internal-flags", G_CALLBACK(fu_nordic_hid_cfg_channel_check_children_update_pending_cb), self); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(peer)); /* prohibit to close parent's communication descriptor */ fu_device_add_internal_flag(FU_DEVICE(peer), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); } static void fu_nordic_hid_cfg_channel_remove_peer(FuNordicHidCfgChannel *self, guint8 peer_id) { GPtrArray *children = fu_device_get_children(FU_DEVICE(self)); /* remove child device if already discovered */ for (guint i = 0; i < children->len; i++) { FuDevice *child_dev = g_ptr_array_index(children, i); FuNordicHidCfgChannel *child = FU_NORDIC_HID_CFG_CHANNEL(child_dev); if (child->peer_id == peer_id) { fu_device_remove_child(FU_DEVICE(self), child_dev); break; } } } static void fu_nordic_hid_cfg_channel_remove_disconnected_peers(FuNordicHidCfgChannel *self, guint8 peers_cache[PEERS_CACHE_LEN]) { for (guint i = 0; i < PEERS_CACHE_LEN; i++) { guint8 peer_id = i + 1; if (peers_cache == NULL || !fu_nordic_hid_cfg_channel_is_cached_peer_connected(peers_cache[i])) { fu_nordic_hid_cfg_channel_remove_peer(self, peer_id); if (peers_cache != NULL) self->peers_cache[i] = peers_cache[i]; } } } static gboolean fu_nordic_hid_cfg_channel_index_peers_cmd(FuNordicHidCfgChannel *self, gboolean *cmd_supported, GError **error) { g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); g_autoptr(GError) error_local = NULL; *cmd_supported = FALSE; if (!fu_nordic_hid_cfg_channel_cmd_send(self, NULL, NULL, CONFIG_STATUS_INDEX_PEERS, NULL, 0, error)) { g_prefix_error(error, "INDEX_PEERS cmd_send failed: "); return FALSE; } if (fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_DISCONNECTED, res, &error_local)) { /* forwarding configuration channel to peers not supported */ return TRUE; } /* Peers available */ if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) { g_prefix_error(error, "INDEX_PEERS cmd_receive failed: "); return FALSE; } *cmd_supported = TRUE; return TRUE; } static gboolean fu_nordic_hid_cfg_channel_get_next_peer_id_cmd(FuNordicHidCfgChannel *self, guint8 *peer_id, GError **error) { g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); if (!fu_nordic_hid_cfg_channel_cmd_send(self, NULL, NULL, CONFIG_STATUS_GET_PEER, NULL, 0, error)) { g_prefix_error(error, "GET_PEER cmd_send failed: "); return FALSE; } if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) { g_prefix_error(error, "GET_PEER cmd_receive failed: "); return FALSE; } *peer_id = res->data[8]; return TRUE; } static gboolean fu_nordic_hid_cfg_channel_read_peers_cache_cmd(FuNordicHidCfgChannel *self, gboolean *cmd_supported, guint8 peers_cache[PEERS_CACHE_LEN], GError **error) { g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); g_autoptr(GError) error_local = NULL; *cmd_supported = FALSE; if (!fu_nordic_hid_cfg_channel_cmd_send(self, NULL, NULL, CONFIG_STATUS_GET_PEERS_CACHE, NULL, 0, error)) { g_prefix_error(error, "GET_PEERS_CACHE cmd_send failed: "); return FALSE; } if (fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_DISCONNECTED, res, &error_local)) { /* configuration channel peers cache not supported */ return TRUE; } /* configuration channel peer caching available */ if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) { g_prefix_error(error, "GET_PEERS_CACHE cmd_receive failed: "); return FALSE; } if (!fu_memcpy_safe(peers_cache, PEERS_CACHE_LEN, 0, res->data, PEERS_CACHE_LEN, 0, PEERS_CACHE_LEN, error)) return FALSE; *cmd_supported = TRUE; return TRUE; } static gboolean fu_nordic_hid_cfg_channel_update_peers(FuNordicHidCfgChannel *self, guint8 peers_cache[PEERS_CACHE_LEN], GError **error) { gboolean peers_supported = FALSE; guint8 peer_id; guint cnt = 0; if (!fu_nordic_hid_cfg_channel_index_peers_cmd(self, &peers_supported, error)) return FALSE; if (!peers_supported) return TRUE; /* a device that does not support peers caching, would drop all of the peers because it * cannot determine if the previously discovered peer is still connected */ fu_nordic_hid_cfg_channel_remove_disconnected_peers(self, peers_cache); while (cnt++ <= 0xFF) { if (!fu_nordic_hid_cfg_channel_get_next_peer_id_cmd(self, &peer_id, error)) return FALSE; /* end of the list */ if (peer_id == INVALID_PEER_ID) break; g_debug("detected peer: 0x%02x", peer_id); if (peers_cache == NULL) { /* allow to properly discover dongles without peers cache support */ fu_nordic_hid_cfg_channel_add_peer(self, peer_id); } else { guint8 idx = peer_id - 1; if (self->peers_cache[idx] != peers_cache[idx] && fu_nordic_hid_cfg_channel_is_cached_peer_connected(peers_cache[idx])) { fu_nordic_hid_cfg_channel_remove_peer(self, peer_id); fu_nordic_hid_cfg_channel_add_peer(self, peer_id); self->peers_cache[idx] = peers_cache[idx]; } } } if (peer_id != INVALID_PEER_ID) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE, "too many peers detected"); return FALSE; } return TRUE; } static gboolean fu_nordic_hid_cfg_channel_setup_peers(FuNordicHidCfgChannel *self, GError **error) { gboolean peers_supported = FALSE; gboolean peers_cache_supported = FALSE; guint8 peers_cache[PEERS_CACHE_LEN] = {0x00}; if (self->peer_id != 0) { /* device connected through dongle cannot support peers */ return TRUE; } /* Send index peers command to a device before accessing peers cache. This is done to * prevent assertion failure on peripheral with legacy firmware that enables debug logs. */ if (!fu_nordic_hid_cfg_channel_index_peers_cmd(self, &peers_supported, error)) return FALSE; if (!peers_supported) return TRUE; if (!fu_nordic_hid_cfg_channel_read_peers_cache_cmd(self, &peers_cache_supported, peers_cache, error)) return FALSE; if (!peers_cache_supported) { if (!fu_nordic_hid_cfg_channel_update_peers(self, NULL, error)) return FALSE; } else { if (!fu_nordic_hid_cfg_channel_update_peers(self, peers_cache, error)) return FALSE; /* device must be kept open to allow polling */ if (!fu_device_open(FU_DEVICE(self), error)) return FALSE; /* mark device as supporting peers cache, ensure periodic polling for peers */ self->peers_cache_support = TRUE; fu_device_set_poll_interval(FU_DEVICE(self), FU_NORDIC_HID_CFG_CHANNEL_PEERS_POLL_INTERVAL); } return TRUE; } static gboolean fu_nordic_hid_cfg_channel_poll_peers(FuDevice *device, GError **error) { FuNordicHidCfgChannel *self = FU_NORDIC_HID_CFG_CHANNEL(device); gboolean peers_cache_supported = FALSE; guint8 peers_cache[PEERS_CACHE_LEN] = {0x00}; if (!fu_nordic_hid_cfg_channel_read_peers_cache_cmd(self, &peers_cache_supported, peers_cache, error)) return FALSE; if (!self->peers_cache_support || !peers_cache_supported) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unexpected poll of device without peers caching support"); return FALSE; } /* skip update if not needed */ if (!memcmp(self->peers_cache, peers_cache, PEERS_CACHE_LEN)) return TRUE; if (!fu_nordic_hid_cfg_channel_update_peers(self, peers_cache, error)) return FALSE; return TRUE; } static gboolean fu_nordic_hid_cfg_channel_get_board_name_cb(FuDevice *device, gpointer user_data, GError **error) { FuNordicHidCfgChannel *self = FU_NORDIC_HID_CFG_CHANNEL(device); g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); if (!fu_nordic_hid_cfg_channel_cmd_send(self, NULL, NULL, CONFIG_STATUS_GET_BOARD_NAME, NULL, 0, error)) return FALSE; if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) return FALSE; self->board_name = fu_memstrsafe(res->data, res->data_len, 0x0, res->data_len, error); if (self->board_name == NULL) return FALSE; /* success */ return TRUE; } static gboolean fu_nordic_hid_cfg_channel_get_bl_name(FuNordicHidCfgChannel *self, GError **error) { guint8 event_id = 0; g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); /* query for the bootloader name if the board support it */ if (fu_nordic_hid_cfg_channel_get_event_id(self, "dfu", "module_variant", &event_id)) { if (!fu_nordic_hid_cfg_channel_cmd_send(self, "dfu", "module_variant", CONFIG_STATUS_FETCH, NULL, 0, error)) return FALSE; if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) return FALSE; /* check if not set via quirk */ if (self->bl_name != NULL) { g_autofree gchar *tmp = g_strndup((const gchar *)res->data, res->data_len); g_debug("Bootloader readout '%s' overrides bootloader from quirk '%s'", tmp, self->bl_name); g_free(self->bl_name); } self->bl_name = fu_memstrsafe(res->data, res->data_len, 0x0, res->data_len, error); if (self->bl_name == NULL) return FALSE; } else { g_debug("the board has no support of bootloader runtime detection"); } /* always use the bank 0 for MCUBOOT bootloader that swaps images */ if (g_strcmp0(self->bl_name, "MCUBOOT") == 0) self->flash_area_id = 0; if (self->bl_name == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "the bootloader is not detected nor set via quirk"); return FALSE; } /* success */ return TRUE; } static gboolean fu_nordic_hid_cfg_channel_get_devinfo(FuNordicHidCfgChannel *self, GError **error) { guint8 event_id = 0; g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); /* query for the devinfo if the board supports it */ if (fu_nordic_hid_cfg_channel_get_event_id(self, "dfu", "devinfo", &event_id)) { gchar *generation; if (!fu_nordic_hid_cfg_channel_cmd_send(self, "dfu", "devinfo", CONFIG_STATUS_FETCH, NULL, 0, error)) return FALSE; if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) return FALSE; if (!fu_memread_uint16_safe(res->data, REPORT_SIZE, 0x00, &self->vid, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(res->data, REPORT_SIZE, 0x02, &self->pid, G_LITTLE_ENDIAN, error)) return FALSE; generation = fu_memstrsafe(res->data, res->data_len, 0x4, res->data_len - 0x04, error); if (generation == NULL) return FALSE; /* check if not set via quirk */ if (self->generation != NULL) { g_debug("generation readout '%s' overrides generation from quirk '%s'", generation, self->generation); g_free(self->generation); } self->generation = generation; } else { g_debug("the board has no support of devinfo runtime detection"); } if (self->generation == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "the generation is not detected nor set via quirk"); return FALSE; } /* success */ return TRUE; } /* * NOTE: * For devices connected directly to the host, * hw_id = HID_UNIQ = logical_id. */ static gboolean fu_nordic_hid_cfg_channel_get_hwid(FuNordicHidCfgChannel *self, GError **error) { guint8 hw_id[HWID_LEN] = {0x0}; g_autofree gchar *physical_id = NULL; g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); if (!fu_nordic_hid_cfg_channel_cmd_send(self, NULL, NULL, CONFIG_STATUS_GET_HWID, NULL, 0, error)) return FALSE; if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) return FALSE; if (!fu_memcpy_safe(hw_id, HWID_LEN, 0, res->data, REPORT_DATA_MAX_LEN, 0, HWID_LEN, error)) return FALSE; /* allows to detect the single device connected via several interfaces */ physical_id = g_strdup_printf("%s-%02x%02x%02x%02x%02x%02x%02x%02x", self->board_name, hw_id[0], hw_id[1], hw_id[2], hw_id[3], hw_id[4], hw_id[5], hw_id[6], hw_id[7]); fu_device_set_physical_id(FU_DEVICE(self), physical_id); /* success */ return TRUE; } static gboolean fu_nordic_hid_cfg_channel_load_module_opts(FuNordicHidCfgChannel *self, FuNordicCfgChannelModule *mod, GError **error) { for (guint8 i = 0; i < 0xFF; i++) { g_autoptr(FuNordicCfgChannelModuleOption) opt = NULL; g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); if (!fu_nordic_hid_cfg_channel_cmd_send_by_id(self, mod->idx << 4, CONFIG_STATUS_FETCH, NULL, 0, error)) return FALSE; if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) return FALSE; /* res->data: option name */ if (res->data[0] == END_OF_TRANSFER_CHAR) break; opt = g_new0(FuNordicCfgChannelModuleOption, 1); opt->name = fu_memstrsafe(res->data, res->data_len, 0x0, res->data_len, error); if (opt->name == NULL) return FALSE; opt->idx = i; g_ptr_array_add(mod->options, g_steal_pointer(&opt)); } /* success */ return TRUE; } static gboolean fu_nordic_hid_cfg_channel_load_module_info(FuNordicHidCfgChannel *self, guint8 module_idx, GError **error) { g_autoptr(FuNordicCfgChannelModule) mod = g_new0(FuNordicCfgChannelModule, 1); mod->idx = module_idx; mod->options = g_ptr_array_new_with_free_func( (GDestroyNotify)fu_nordic_hid_cfg_channel_module_option_free); if (!fu_nordic_hid_cfg_channel_load_module_opts(self, mod, error)) return FALSE; /* module description is the 1st loaded option */ if (mod->options->len > 0) { FuNordicCfgChannelModuleOption *opt = g_ptr_array_index(mod->options, 0); mod->name = g_strdup(opt->name); if (!g_ptr_array_remove_index(mod->options, 0)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot remove option"); return FALSE; } } /* success */ g_ptr_array_add(self->modules, g_steal_pointer(&mod)); return TRUE; } static gboolean fu_nordic_hid_cfg_channel_get_modinfo(FuNordicHidCfgChannel *self, GError **error) { g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); if (!fu_nordic_hid_cfg_channel_cmd_send(self, NULL, NULL, CONFIG_STATUS_GET_MAX_MOD_ID, NULL, 0, error)) return FALSE; if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) return FALSE; /* res->data[0]: maximum module idx */ for (guint i = 0; i <= res->data[0]; i++) { if (!fu_nordic_hid_cfg_channel_load_module_info(self, i, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_nordic_hid_cfg_channel_dfu_fwinfo(FuNordicHidCfgChannel *self, GError **error) { guint16 ver_rev; guint32 ver_build_nr; g_autofree gchar *version = NULL; g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); if (!fu_nordic_hid_cfg_channel_cmd_send(self, "dfu", "fwinfo", CONFIG_STATUS_FETCH, NULL, 0, error)) return FALSE; if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) return FALSE; /* parsing fwinfo answer */ /* TODO: add banks amount into quirk */ if (res->data[0] > 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid flash area returned by device"); return FALSE; } /* set the target flash ID area */ self->flash_area_id = res->data[0] ^ 1; if (!fu_memread_uint32_safe(res->data, REPORT_SIZE, 0x01, &self->flashed_image_len, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(res->data, REPORT_SIZE, 0x07, &ver_rev, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(res->data, REPORT_SIZE, 0x09, &ver_build_nr, G_LITTLE_ENDIAN, error)) return FALSE; version = g_strdup_printf("%u.%u.%u.%u", res->data[4], res->data[5], ver_rev, ver_build_nr); fu_device_set_version(FU_DEVICE(self), version); /* success */ return TRUE; } static gboolean fu_nordic_hid_cfg_channel_dfu_reboot(FuNordicHidCfgChannel *self, GError **error) { g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); if (!fu_nordic_hid_cfg_channel_cmd_send(self, "dfu", "reboot", CONFIG_STATUS_FETCH, NULL, 0, error)) return FALSE; if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) return FALSE; if (res->data_len != 1 || res->data[0] != 0x01) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "reboot data was invalid"); return FALSE; } /* success */ return TRUE; } static gboolean fu_nordic_hid_cfg_channel_dfu_sync_cb(FuDevice *device, gpointer user_data, GError **error) { FuNordicHidCfgChannel *self = FU_NORDIC_HID_CFG_CHANNEL(device); FuNordicCfgChannelRcvHelper *args = (FuNordicCfgChannelRcvHelper *)user_data; g_autoptr(FuNordicCfgChannelMsg) recv_msg = g_new0(FuNordicCfgChannelMsg, 1); /* allow to sync buffer more precisely and without annoying messages * it may take some time and depending on device workload */ for (gint i = 1; i < 30; i++) { if (!fu_nordic_hid_cfg_channel_cmd_send(self, "dfu", "sync", CONFIG_STATUS_FETCH, NULL, 0, error)) return FALSE; recv_msg->report_id = HID_REPORT_ID; fu_device_sleep(device, 2); /* ms */ if (!fu_nordic_hid_cfg_channel_receive(self, (guint8 *)recv_msg, sizeof(*recv_msg), error)) { return FALSE; } if (recv_msg->data_len != 0x0F) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "incorrect length of reply"); return FALSE; } if (recv_msg->data[0] == DFU_STATE_INACTIVE || recv_msg->data[0] == DFU_STATE_ACTIVE) { break; } } if (recv_msg->data[0] != args->status) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "sync received status: 0x%02x, expected: 0x%02x", recv_msg->data[0], args->status); return FALSE; } return fu_memcpy_safe(args->buf, args->bufsz, 0, (guint8 *)recv_msg, sizeof(*recv_msg), 0, sizeof(*recv_msg), error); } static gboolean fu_nordic_hid_cfg_channel_dfu_sync(FuNordicHidCfgChannel *self, FuNordicCfgChannelDfuInfo *dfu_info, guint8 expecting_state, GError **error) { FuNordicCfgChannelRcvHelper helper; g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); helper.status = expecting_state; helper.buf = (guint8 *)res; helper.bufsz = sizeof(*res); if (!fu_device_retry_full(FU_DEVICE(self), fu_nordic_hid_cfg_channel_dfu_sync_cb, FU_NORDIC_HID_CFG_CHANNEL_RETRIES, FU_NORDIC_HID_CFG_CHANNEL_DFU_RETRY_DELAY, &helper, error)) { g_prefix_error(error, "failed on dfu sync: "); return FALSE; } dfu_info->dfu_state = res->data[0]; if (!fu_memread_uint32_safe(res->data, REPORT_SIZE, 0x01, &dfu_info->img_length, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(res->data, REPORT_SIZE, 0x05, &dfu_info->img_csum, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(res->data, REPORT_SIZE, 0x09, &dfu_info->offset, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(res->data, REPORT_SIZE, 0x0D, &dfu_info->sync_buffer_size, G_LITTLE_ENDIAN, error)) return FALSE; return TRUE; } static gboolean fu_nordic_hid_cfg_channel_dfu_start(FuNordicHidCfgChannel *self, gsize img_length, guint32 img_crc, guint32 offset, GError **error) { guint8 data[REPORT_DATA_MAX_LEN] = {0}; g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); /* sanity check */ if (img_length > G_MAXUINT32) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "payload was too large"); return FALSE; } if (!fu_memwrite_uint32_safe(data, REPORT_DATA_MAX_LEN, 0x00, (guint32)img_length, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint32_safe(data, REPORT_DATA_MAX_LEN, 0x04, img_crc, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint32_safe(data, REPORT_DATA_MAX_LEN, 0x08, offset, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_nordic_hid_cfg_channel_cmd_send(self, "dfu", "start", CONFIG_STATUS_SET, data, 0x0C, error)) return FALSE; return fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error); } static gboolean fu_nordic_hid_cfg_channel_probe(FuDevice *device, GError **error) { return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); } static gboolean fu_nordic_hid_cfg_channel_generate_ids(FuNordicHidCfgChannel *self, GError **error) { FuDevice *device = FU_DEVICE(self); /* generate IDs */ fu_device_add_instance_strsafe(device, "BOARD", self->board_name); fu_device_add_instance_strsafe(device, "BL", self->bl_name); fu_device_add_instance_strsafe(device, "GEN", self->generation); /* If available, use VID and PID fetched in devinfo. Otherwise, use hardcoded VID and PID of * 0x00 only for devices connected via dongle. This prevents from inheriting VID and PID of * the dongle. */ if ((self->vid != 0x00 && self->pid != 0x00) || (self->peer_id != 0)) { fu_device_add_instance_u16(device, "VEN", self->vid); fu_device_add_instance_u16(device, "DEV", self->pid); } /* For the default generation, generate GUID without the generation parameter. * Required for compatibility with already released application images. */ if (g_strcmp0(self->generation, "default") == 0) { if (!fu_device_build_instance_id(device, error, "HIDRAW", "VEN", "DEV", "BOARD", "BL", NULL)) { g_prefix_error(error, "failed to add ID without generation: "); return FALSE; } } if (!fu_device_build_instance_id(device, error, "HIDRAW", "VEN", "DEV", "BOARD", "BL", "GEN", NULL)) { g_prefix_error(error, "failed to add complete ID: "); return FALSE; } return TRUE; } static gboolean fu_nordic_hid_cfg_channel_direct_discovery(FuNordicHidCfgChannel *self, GError **error) { g_autoptr(GError) error_board_name = NULL; g_autoptr(GError) error_fwinfo = NULL; FuDevice *device = FU_DEVICE(self); /* Get the board name. The first configuration channel operation is used to check if * hidraw instance supports the protocol. In case of failure, the hidraw instance is ignored * and predefined error code is returned to suppress warning log. This is needed to properly * handle hidraw instances that do not handle configuration channel requests. A device may * not support configuration channel at all (no configuration channel HID feature report). * The configuration channel requests are handled only by the first HID instance on device * (other instances reject the configuration channel operations). * * If the HID device is connected over BLE, the configuration channel operations right after * reconnection may fail with an ioctl error. Retry after a delay to ensure that the device * will be properly recognized by the fwupd tool. */ if (!fu_device_retry_full(device, fu_nordic_hid_cfg_channel_get_board_name_cb, 3, 50, NULL, &error_board_name)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Get board name failed: %s", error_board_name->message); return FALSE; } /* set the physical id based on board name and HW id to detect if the device is connected * via several interfaces */ if (!fu_nordic_hid_cfg_channel_get_hwid(self, error)) return FALSE; /* detect available modules first */ if (!fu_nordic_hid_cfg_channel_get_modinfo(self, error)) return FALSE; /* generate the custom visible name for the device if absent */ if (fu_device_get_name(device) == NULL) { const gchar *physical_id = NULL; physical_id = fu_device_get_physical_id(device); fu_device_set_name(device, physical_id); } /* get device info and version */ if (!fu_nordic_hid_cfg_channel_dfu_fwinfo(self, &error_fwinfo)) /* lack of firmware info support indicates that device does not support DFU. */ return TRUE; /* detect bootloader type */ if (!fu_nordic_hid_cfg_channel_get_bl_name(self, error)) return FALSE; /* detect vendor ID, product ID and generation */ if (!fu_nordic_hid_cfg_channel_get_devinfo(self, error)) return FALSE; /* generate device IDs. */ if (!fu_nordic_hid_cfg_channel_generate_ids(self, error)) return FALSE; self->dfu_support = TRUE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); return TRUE; } static gboolean fu_nordic_hid_cfg_channel_setup(FuDevice *device, GError **error) { FuNordicHidCfgChannel *self = FU_NORDIC_HID_CFG_CHANNEL(device); if (!fu_nordic_hid_cfg_channel_direct_discovery(self, error)) return FALSE; if (!fu_nordic_hid_cfg_channel_setup_peers(self, error)) return FALSE; return TRUE; } static void fu_nordic_hid_cfg_channel_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 97, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reload"); } static void fu_nordic_hid_cfg_channel_module_to_string(FuNordicCfgChannelModule *mod, guint idt, GString *str) { for (guint i = 0; i < mod->options->len; i++) { FuNordicCfgChannelModuleOption *opt = g_ptr_array_index(mod->options, i); g_autofree gchar *title = g_strdup_printf("Option%02x", i); fu_string_append(str, idt, title, opt->name); } } static void fu_nordic_hid_cfg_channel_to_string(FuDevice *device, guint idt, GString *str) { FuNordicHidCfgChannel *self = FU_NORDIC_HID_CFG_CHANNEL(device); fu_string_append(str, idt, "BoardName", self->board_name); fu_string_append_kx(str, idt, "PeerId", self->peer_id); if (self->vid != 0x00 && self->pid != 0x00) { fu_string_append_kx(str, idt, "VendorId", self->vid); fu_string_append_kx(str, idt, "ProductId", self->pid); } if (self->dfu_support) { fu_string_append(str, idt, "Bootloader", self->bl_name); fu_string_append(str, idt, "Generation", self->generation); fu_string_append_kx(str, idt, "FlashAreaId", self->flash_area_id); fu_string_append_kx(str, idt, "FlashedImageLen", self->flashed_image_len); } for (guint i = 0; i < self->modules->len; i++) { FuNordicCfgChannelModule *mod = g_ptr_array_index(self->modules, i); g_autofree gchar *title = g_strdup_printf("Module%02x", i); fu_string_append(str, idt, title, mod->name); fu_nordic_hid_cfg_channel_module_to_string(mod, idt + 1, str); } } static gboolean fu_nordic_hid_cfg_channel_write_firmware_chunk(FuNordicHidCfgChannel *self, FuChunk *chk, gboolean is_last, GError **error) { guint32 chunk_len; guint32 offset = 0; guint8 sync_state = DFU_STATE_ACTIVE; g_autoptr(FuNordicCfgChannelDfuInfo) dfu_info = g_new0(FuNordicCfgChannelDfuInfo, 1); chunk_len = fu_chunk_get_data_sz(chk); while (offset < chunk_len) { guint8 data_len; guint8 data[REPORT_DATA_MAX_LEN] = {0}; g_autoptr(FuNordicCfgChannelMsg) res = g_new0(FuNordicCfgChannelMsg, 1); data_len = ((offset + REPORT_DATA_MAX_LEN) < chunk_len) ? REPORT_DATA_MAX_LEN : (guint8)(chunk_len - offset); if (!fu_memcpy_safe(data, REPORT_DATA_MAX_LEN, 0, fu_chunk_get_data(chk), chunk_len, offset, data_len, error)) { return FALSE; } if (!fu_nordic_hid_cfg_channel_cmd_send(self, "dfu", "data", CONFIG_STATUS_SET, data, data_len, error)) return FALSE; if (!fu_nordic_hid_cfg_channel_cmd_receive(self, CONFIG_STATUS_SUCCESS, res, error)) return FALSE; offset += data_len; } /* sync should return inactive for the last chunk */ if (is_last) sync_state = DFU_STATE_INACTIVE; return fu_nordic_hid_cfg_channel_dfu_sync(self, dfu_info, sync_state, error); } static gboolean fu_nordic_hid_cfg_channel_write_firmware_blob(FuNordicHidCfgChannel *self, GBytes *blob, FuProgress *progress, GError **error) { g_autoptr(FuNordicCfgChannelDfuInfo) dfu_info = g_new0(FuNordicCfgChannelDfuInfo, 1); g_autoptr(FuChunkArray) chunks = NULL; if (!fu_nordic_hid_cfg_channel_dfu_sync(self, dfu_info, DFU_STATE_ACTIVE, error)) return FALSE; chunks = fu_chunk_array_new_from_bytes(blob, 0, dfu_info->sync_buffer_size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); gboolean is_last = (i == fu_chunk_array_length(chunks) - 1); if (!fu_nordic_hid_cfg_channel_write_firmware_chunk(self, chk, is_last, error)) { g_prefix_error(error, "chunk %u: ", fu_chunk_get_idx(chk)); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_nordic_hid_cfg_channel_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuNordicHidCfgChannel *self = FU_NORDIC_HID_CFG_CHANNEL(device); guint32 checksum; g_autofree gchar *csum_str = NULL; g_autofree gchar *image_id = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(FuNordicCfgChannelDfuInfo) dfu_info = g_new0(FuNordicCfgChannelDfuInfo, 1); /* select correct firmware per target board, bootloader and bank */ image_id = g_strdup_printf("%s_%s_bank%01u", self->board_name, self->bl_name, self->flash_area_id); firmware = fu_firmware_get_image_by_id(firmware, image_id, error); if (firmware == NULL) return FALSE; /* explicitly request a custom checksum calculation */ csum_str = fu_firmware_get_checksum(firmware, -1, error); if (csum_str == NULL) return FALSE; /* expecting checksum string in hex */ checksum = g_ascii_strtoull(csum_str, NULL, 16); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); /* TODO: check if there is unfinished operation before? */ blob = fu_firmware_get_bytes(firmware, error); if (blob == NULL) return FALSE; if (!fu_nordic_hid_cfg_channel_dfu_sync(self, dfu_info, DFU_STATE_INACTIVE, error)) return FALSE; if (!fu_nordic_hid_cfg_channel_dfu_start(self, g_bytes_get_size(blob), checksum, 0x0 /* offset */, error)) return FALSE; fu_progress_step_done(progress); /* write */ if (!fu_nordic_hid_cfg_channel_write_firmware_blob(self, blob, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* attach */ if (!fu_nordic_hid_cfg_channel_dfu_reboot(self, error)) return FALSE; fu_progress_step_done(progress); return TRUE; } static gboolean fu_nordic_hid_cfg_channel_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuNordicHidCfgChannel *self = FU_NORDIC_HID_CFG_CHANNEL(device); if (g_strcmp0(key, "NordicHidBootloader") == 0) { if (g_strcmp0(value, "B0") != 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "can be only 'B0' in quirk"); return FALSE; } self->bl_name = g_strdup(value); return TRUE; } if (g_strcmp0(key, "NordicHidGeneration") == 0) { if (g_strcmp0(value, "default") != 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "can be only 'default' in quirk"); return FALSE; } self->generation = g_strdup(value); return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_nordic_hid_cfg_channel_finalize(GObject *object) { FuNordicHidCfgChannel *self = FU_NORDIC_HID_CFG_CHANNEL(object); g_free(self->board_name); g_free(self->bl_name); g_free(self->generation); g_ptr_array_unref(self->modules); G_OBJECT_CLASS(fu_nordic_hid_cfg_channel_parent_class)->finalize(object); } static void fu_nordic_hid_cfg_channel_class_init(FuNordicHidCfgChannelClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); klass_device->probe = fu_nordic_hid_cfg_channel_probe; klass_device->set_progress = fu_nordic_hid_cfg_channel_set_progress; klass_device->set_quirk_kv = fu_nordic_hid_cfg_channel_set_quirk_kv; klass_device->setup = fu_nordic_hid_cfg_channel_setup; klass_device->poll = fu_nordic_hid_cfg_channel_poll_peers; klass_device->to_string = fu_nordic_hid_cfg_channel_to_string; klass_device->write_firmware = fu_nordic_hid_cfg_channel_write_firmware; object_class->finalize = fu_nordic_hid_cfg_channel_finalize; } static void fu_nordic_hid_cfg_channel_init(FuNordicHidCfgChannel *self) { self->modules = g_ptr_array_new_with_free_func((GDestroyNotify)fu_nordic_hid_cfg_channel_module_free); fu_device_set_vendor(FU_DEVICE(self), "Nordic"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_add_protocol(FU_DEVICE(self), "com.nordic.hidcfgchannel"); fu_device_retry_set_delay(FU_DEVICE(self), FU_NORDIC_HID_CFG_CHANNEL_RETRY_DELAY); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_NORDIC_HID_ARCHIVE); } static FuNordicHidCfgChannel * fu_nordic_hid_cfg_channel_new(guint8 id, FuNordicHidCfgChannel *parent) { FuNordicHidCfgChannel *self = g_object_new(FU_TYPE_NORDIC_HID_CFG_CHANNEL, "context", fu_device_get_context(FU_DEVICE(parent)), NULL); self->peer_id = id; self->parent_udev = FU_UDEV_DEVICE(parent); return self; } fwupd-1.9.16/plugins/nordic-hid/fu-nordic-hid-cfg-channel.h000066400000000000000000000005731460375044200234140ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_NORDIC_HID_CFG_CHANNEL (fu_nordic_hid_cfg_channel_get_type()) G_DECLARE_FINAL_TYPE(FuNordicHidCfgChannel, fu_nordic_hid_cfg_channel, FU, NORDIC_HID_CFG_CHANNEL, FuUdevDevice) fwupd-1.9.16/plugins/nordic-hid/fu-nordic-hid-firmware-b0.c000066400000000000000000000077571460375044200233700ustar00rootroot00000000000000/* * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-nordic-hid-firmware-b0.h" #define UPDATE_IMAGE_MAGIC_COMMON 0x281ee6de #define UPDATE_IMAGE_MAGIC_FWINFO 0x8fcebb4c #define UPDATE_IMAGE_MAGIC_NRF52 0x00003402 #define UPDATE_IMAGE_MAGIC_NRF53 0x00003502 struct _FuNordicHidFirmwareB0 { FuNordicHidFirmwareClass parent_instance; }; G_DEFINE_TYPE(FuNordicHidFirmwareB0, fu_nordic_hid_firmware_b0, FU_TYPE_NORDIC_HID_FIRMWARE) static GByteArray * fu_nordic_hid_firmware_b0_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) blob = NULL; fu_byte_array_append_uint32(buf, UPDATE_IMAGE_MAGIC_COMMON, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, UPDATE_IMAGE_MAGIC_FWINFO, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, UPDATE_IMAGE_MAGIC_NRF52, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, 0x00, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, 0x00, G_LITTLE_ENDIAN); /* version */ fu_byte_array_append_uint32(buf, 0x63, G_LITTLE_ENDIAN); blob = fu_firmware_get_bytes_with_patches(firmware, error); if (blob == NULL) return NULL; fu_byte_array_append_bytes(buf, blob); return g_steal_pointer(&buf); } static gboolean fu_nordic_hid_firmware_b0_read_fwinfo(FuFirmware *firmware, guint8 const *buf, gsize bufsz, GError **error) { guint32 magic_common; guint32 magic_fwinfo; guint32 magic_compat; guint32 offset; guint32 hdr_offset[5] = {0x0000, 0x0200, 0x400, 0x800, 0x1000}; guint8 ver_major = 0; guint8 ver_minor = 0; guint16 ver_rev = 0; guint32 ver_build_nr = 0; g_autofree gchar *version = NULL; /* find correct offset to fwinfo */ for (guint32 i = 0; i < G_N_ELEMENTS(hdr_offset); i++) { offset = hdr_offset[i]; if (!fu_memread_uint32_safe(buf, bufsz, offset, &magic_common, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, offset + 0x04, &magic_fwinfo, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, offset + 0x08, &magic_compat, G_LITTLE_ENDIAN, error)) return FALSE; /* version */ if (!fu_memread_uint32_safe(buf, bufsz, offset + 0x14, &ver_build_nr, G_LITTLE_ENDIAN, error)) return FALSE; if (magic_common != UPDATE_IMAGE_MAGIC_COMMON || magic_fwinfo != UPDATE_IMAGE_MAGIC_FWINFO) continue; switch (magic_compat) { case UPDATE_IMAGE_MAGIC_NRF52: case UPDATE_IMAGE_MAGIC_NRF53: /* currently only the build number is saved into the image */ version = g_strdup_printf("%u.%u.%u.%u", ver_major, ver_minor, ver_rev, ver_build_nr); fu_firmware_set_version(firmware, version); return TRUE; default: break; } } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unable to validate the update binary"); return FALSE; } static gboolean fu_nordic_hid_firmware_b0_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { const guint8 *buf; gsize bufsz = 0; if (!FU_FIRMWARE_CLASS(fu_nordic_hid_firmware_b0_parent_class) ->parse(firmware, fw, offset, flags, error)) return FALSE; buf = g_bytes_get_data(fw, &bufsz); if (buf == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unable to get the image binary"); return FALSE; } return fu_nordic_hid_firmware_b0_read_fwinfo(firmware, buf, bufsz, error); } static void fu_nordic_hid_firmware_b0_init(FuNordicHidFirmwareB0 *self) { } static void fu_nordic_hid_firmware_b0_class_init(FuNordicHidFirmwareB0Class *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_nordic_hid_firmware_b0_parse; klass_firmware->write = fu_nordic_hid_firmware_b0_write; } fwupd-1.9.16/plugins/nordic-hid/fu-nordic-hid-firmware-b0.h000066400000000000000000000006061460375044200233570ustar00rootroot00000000000000/* * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-nordic-hid-firmware.h" #define FU_TYPE_NORDIC_HID_FIRMWARE_B0 (fu_nordic_hid_firmware_b0_get_type()) G_DECLARE_FINAL_TYPE(FuNordicHidFirmwareB0, fu_nordic_hid_firmware_b0, FU, NORDIC_HID_FIRMWARE_B0, FuNordicHidFirmware) fwupd-1.9.16/plugins/nordic-hid/fu-nordic-hid-firmware-mcuboot.c000066400000000000000000000113251460375044200245210ustar00rootroot00000000000000/* * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-nordic-hid-firmware-mcuboot.h" #define IMAGE_MAGIC 0x96f3b83d #define IMAGE_TLV_INFO_MAGIC 0x6907 #define IMAGE_TLV_PROT_INFO_MAGIC 0x6908 struct _FuNordicHidFirmwareMcuboot { FuNordicHidFirmwareClass parent_instance; }; G_DEFINE_TYPE(FuNordicHidFirmwareMcuboot, fu_nordic_hid_firmware_mcuboot, FU_TYPE_NORDIC_HID_FIRMWARE) static GByteArray * fu_nordic_hid_firmware_mcuboot_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) blob = fu_firmware_get_bytes_with_patches(firmware, error); if (blob == NULL) return NULL; /* https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/mcuboot/design.html#image-format */ fu_byte_array_append_uint32(buf, IMAGE_MAGIC, G_LITTLE_ENDIAN); /* load_addr */ fu_byte_array_append_uint32(buf, 0x00, G_LITTLE_ENDIAN); /* hdr_size */ fu_byte_array_append_uint16(buf, 0x20, G_LITTLE_ENDIAN); /* protect_tlv_size */ fu_byte_array_append_uint16(buf, 0x00, G_LITTLE_ENDIAN); /* img_size */ fu_byte_array_append_uint32(buf, (guint32)g_bytes_get_size(blob), G_LITTLE_ENDIAN); /* flags */ fu_byte_array_append_uint32(buf, 0x00, G_LITTLE_ENDIAN); /* version */ fu_byte_array_append_uint8(buf, 0x01); fu_byte_array_append_uint8(buf, 0x02); fu_byte_array_append_uint16(buf, 0x03, G_LITTLE_ENDIAN); fu_byte_array_append_uint32(buf, 0x63, G_LITTLE_ENDIAN); /* pad */ fu_byte_array_append_uint32(buf, 0xffffffff, G_LITTLE_ENDIAN); /* payload */ fu_byte_array_append_bytes(buf, blob); /* TLV magic and total */ fu_byte_array_append_uint16(buf, IMAGE_TLV_INFO_MAGIC, G_LITTLE_ENDIAN); fu_byte_array_append_uint16(buf, 0x00, G_LITTLE_ENDIAN); return g_steal_pointer(&buf); } /* simple validation of the image */ static gboolean fu_nordic_hid_firmware_mcuboot_validate(FuFirmware *firmware, guint8 const *buf, gsize bufsz, GError **error) { guint32 magic; guint16 hdr_size; guint32 img_size; guint8 ver_major; guint8 ver_minor; guint16 ver_rev; guint32 ver_build_nr; guint16 magic_tlv; g_autofree gchar *version = NULL; if (!fu_memread_uint32_safe(buf, bufsz, 0, &magic, G_LITTLE_ENDIAN, error)) return FALSE; if (magic != IMAGE_MAGIC) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect image magic"); return FALSE; } /* ignore load_addr */ if (!fu_memread_uint16_safe(buf, bufsz, 8, &hdr_size, G_LITTLE_ENDIAN, error)) return FALSE; /* ignore protect_tlv_size */ if (!fu_memread_uint32_safe(buf, bufsz, 12, &img_size, G_LITTLE_ENDIAN, error)) return FALSE; /* ignore TLVs themselves * https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/mcuboot/design.html#protected-tlvs * check the magic values only */ if (!fu_memread_uint16_safe(buf, bufsz, hdr_size + img_size, &magic_tlv, G_LITTLE_ENDIAN, error)) return FALSE; if (magic_tlv != IMAGE_TLV_INFO_MAGIC && magic_tlv != IMAGE_TLV_PROT_INFO_MAGIC) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incorrect TLV info magic"); return FALSE; } /* version */ if (!fu_memread_uint8_safe(buf, bufsz, 0x14, &ver_major, error)) return FALSE; if (!fu_memread_uint8_safe(buf, bufsz, 0x15, &ver_minor, error)) return FALSE; if (!fu_memread_uint16_safe(buf, bufsz, 0x16, &ver_rev, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, 0x18, &ver_build_nr, G_LITTLE_ENDIAN, error)) return FALSE; version = g_strdup_printf("%u.%u.%u.%u", ver_major, ver_minor, ver_rev, ver_build_nr); fu_firmware_set_version(firmware, version); return TRUE; } static gboolean fu_nordic_hid_firmware_mcuboot_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { const guint8 *buf; gsize bufsz = 0; if (!FU_FIRMWARE_CLASS(fu_nordic_hid_firmware_mcuboot_parent_class) ->parse(firmware, fw, offset, flags, error)) return FALSE; buf = g_bytes_get_data(fw, &bufsz); if (buf == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unable to get the image binary"); return FALSE; } return fu_nordic_hid_firmware_mcuboot_validate(firmware, buf, bufsz, error); } static void fu_nordic_hid_firmware_mcuboot_init(FuNordicHidFirmwareMcuboot *self) { } static void fu_nordic_hid_firmware_mcuboot_class_init(FuNordicHidFirmwareMcubootClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_nordic_hid_firmware_mcuboot_parse; klass_firmware->write = fu_nordic_hid_firmware_mcuboot_write; } fwupd-1.9.16/plugins/nordic-hid/fu-nordic-hid-firmware-mcuboot.h000066400000000000000000000006371460375044200245320ustar00rootroot00000000000000/* * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-nordic-hid-firmware.h" #define FU_TYPE_NORDIC_HID_FIRMWARE_MCUBOOT (fu_nordic_hid_firmware_mcuboot_get_type()) G_DECLARE_FINAL_TYPE(FuNordicHidFirmwareMcuboot, fu_nordic_hid_firmware_mcuboot, FU, NORDIC_HID_FIRMWARE_MCUBOOT, FuNordicHidFirmware) fwupd-1.9.16/plugins/nordic-hid/fu-nordic-hid-firmware.c000066400000000000000000000050431460375044200230530ustar00rootroot00000000000000/* * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-nordic-hid-firmware.h" typedef struct { guint32 crc32; } FuNordicHidFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuNordicHidFirmware, fu_nordic_hid_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_nordic_hid_firmware_get_instance_private(o)) static void fu_nordic_hid_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuNordicHidFirmware *self = FU_NORDIC_HID_FIRMWARE(firmware); FuNordicHidFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "crc32", priv->crc32); } static gchar * fu_nordic_hid_firmware_get_checksum(FuFirmware *firmware, GChecksumType csum_kind, GError **error) { FuNordicHidFirmware *self = FU_NORDIC_HID_FIRMWARE(firmware); FuNordicHidFirmwarePrivate *priv = GET_PRIVATE(self); if (!fu_firmware_has_flag(firmware, FU_FIRMWARE_FLAG_HAS_CHECKSUM)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unable to calculate the checksum of the update binary"); return NULL; } return g_strdup_printf("%x", priv->crc32); } static guint32 fu_nordic_hid_firmware_crc32(const guint8 *buf, gsize bufsz) { guint crc32 = 0x01; /* maybe skipped "^" step in fu_crc32_full()? * according https://github.com/madler/zlib/blob/master/crc32.c#L225 */ crc32 ^= 0xFFFFFFFFUL; return fu_crc32_full(buf, bufsz, crc32, 0xEDB88320); } static gboolean fu_nordic_hid_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuNordicHidFirmware *self = FU_NORDIC_HID_FIRMWARE(firmware); FuNordicHidFirmwarePrivate *priv = GET_PRIVATE(self); const guint8 *buf; gsize bufsz = 0; buf = g_bytes_get_data(fw, &bufsz); if (buf == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unable to get the image binary"); return FALSE; } fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); priv->crc32 = fu_nordic_hid_firmware_crc32(buf, bufsz); /* success */ return TRUE; } static void fu_nordic_hid_firmware_init(FuNordicHidFirmware *self) { } static void fu_nordic_hid_firmware_class_init(FuNordicHidFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->export = fu_nordic_hid_firmware_export; klass_firmware->get_checksum = fu_nordic_hid_firmware_get_checksum; klass_firmware->parse = fu_nordic_hid_firmware_parse; } fwupd-1.9.16/plugins/nordic-hid/fu-nordic-hid-firmware.h000066400000000000000000000006421460375044200230600ustar00rootroot00000000000000/* * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_NORDIC_HID_FIRMWARE (fu_nordic_hid_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuNordicHidFirmware, fu_nordic_hid_firmware, FU, NORDIC_HID_FIRMWARE, FuFirmware) struct _FuNordicHidFirmwareClass { FuFirmwareClass parent_class; }; fwupd-1.9.16/plugins/nordic-hid/fu-nordic-hid-plugin.c000066400000000000000000000024031460375044200225320ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-nordic-hid-archive.h" #include "fu-nordic-hid-cfg-channel.h" #include "fu-nordic-hid-firmware-b0.h" #include "fu-nordic-hid-firmware-mcuboot.h" #include "fu-nordic-hid-plugin.h" struct _FuNordicHidPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuNordicHidPlugin, fu_nordic_hid_plugin, FU_TYPE_PLUGIN) static void fu_nordic_hid_plugin_init(FuNordicHidPlugin *self) { } static void fu_nordic_hid_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "NordicHidBootloader"); fu_plugin_add_udev_subsystem(plugin, "hidraw"); fu_plugin_add_device_gtype(plugin, FU_TYPE_NORDIC_HID_CFG_CHANNEL); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_NORDIC_HID_ARCHIVE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_NORDIC_HID_FIRMWARE_B0); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_NORDIC_HID_FIRMWARE_MCUBOOT); } static void fu_nordic_hid_plugin_class_init(FuNordicHidPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_nordic_hid_plugin_constructed; } fwupd-1.9.16/plugins/nordic-hid/fu-nordic-hid-plugin.h000066400000000000000000000003631460375044200225420ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuNordicHidPlugin, fu_nordic_hid_plugin, FU, NORDIC_HID_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/nordic-hid/meson.build000066400000000000000000000011101460375044200205760ustar00rootroot00000000000000if gudev.found() and gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginNordicHid"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('nordic-hid.quirk') plugin_builtins += static_library('fu_plugin_nordic_hid', sources: [ 'fu-nordic-hid-plugin.c', 'fu-nordic-hid-cfg-channel.c', 'fu-nordic-hid-firmware.c', 'fu-nordic-hid-firmware-b0.c', 'fu-nordic-hid-firmware-mcuboot.c', 'fu-nordic-hid-archive.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/nordic-hid/nordic-hid.quirk000066400000000000000000000002731460375044200215420ustar00rootroot00000000000000# Nordic Semiconductor ASA HID Devices [HIDRAW\VEN_1915] Plugin = nordic_hid Flags = no-generic-guids GType = FuNordicHidCfgChannel NordicHidBootloader = B0 NordicHidGeneration = default fwupd-1.9.16/plugins/nordic-hid/tests/000077500000000000000000000000001460375044200176055ustar00rootroot00000000000000fwupd-1.9.16/plugins/nordic-hid/tests/nordic-hid-b0.bin000066400000000000000000000000431460375044200226130ustar00rootroot00000000000000(LΏ4chello worldfwupd-1.9.16/plugins/nordic-hid/tests/nordic-hid-b0.builder.xml000066400000000000000000000001451460375044200242730ustar00rootroot00000000000000 aGVsbG8gd29ybGQ= fwupd-1.9.16/plugins/nordic-hid/tests/nordic-hid-mcuboot.bin000066400000000000000000000000571460375044200237670ustar00rootroot00000000000000= chello worldifwupd-1.9.16/plugins/nordic-hid/tests/nordic-hid-mcuboot.builder.xml000066400000000000000000000001521460375044200254400ustar00rootroot00000000000000 aGVsbG8gd29ybGQ= fwupd-1.9.16/plugins/nvme/000077500000000000000000000000001460375044200153705ustar00rootroot00000000000000fwupd-1.9.16/plugins/nvme/README.md000066400000000000000000000036411460375044200166530ustar00rootroot00000000000000--- title: Plugin: NVMe --- ## Introduction This plugin adds support for NVMe storage hardware. Devices are enumerated from the Identify Controller data structure and can be updated with appropriate firmware file. Firmware is sent in 4kB chunks and activated on next reboot. The device GUID is read from the vendor specific area and if not found then generated from the trimmed model string. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * `org.nvmexpress` ## GUID Generation These device use the NVMe DeviceInstanceId values, e.g. * `NVME\VEN_1179&DEV_010F` The FRU globally unique identifier (FGUID) is also added from the CNS if set. Please refer to this document for more details on how to add support for [FGUID](https://nvmexpress.org/wp-content/uploads/NVM_Express_Revision_1.3.pdf). Additionally, an extra instance IDs containing the version may be added. * `NVME\VEN_1179&DEV_010F&VER_3B2QGXA7` Additionally, for NVMe drives with Dell vendor firmware two extra GUIDs are added: * `STORAGE-DELL-${component-id}` and any optional GUID saved in the vendor extension block. ## Update Behavior The firmware is deployed when the device is in normal runtime mode, but it is only activated when the system is either restarted or in some cases shutdown. ## Quirk Use This plugin uses the following plugin-specific quirks: ### NvmeBlockSize The block size used for NVMe writes Since: 1.1.3 ### Flags * `force-align` if image should be padded, since 1.2.4 * `commit-ca3` download and activate immediately, since 1.8.15 ## Vendor ID Security The vendor ID is set from the udev vendor, for example set to `NVME:0x1179` ## External Interface Access This plugin requires ioctl `NVME_IOCTL_ADMIN_CMD` access. ## Version Considerations This plugin has been available since fwupd version `1.1.2`. fwupd-1.9.16/plugins/nvme/fu-nvme-common.c000066400000000000000000000123521460375044200204020ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-nvme-common.h" const gchar * fu_nvme_status_to_string(guint32 status) { switch (status) { case NVME_SC_SUCCESS: return "Command completed successfully"; case NVME_SC_INVALID_OPCODE: return "Associated command opcode field is not valid"; case NVME_SC_INVALID_FIELD: return "Unsupported value in a defined field"; case NVME_SC_CMDID_CONFLICT: return "Command identifier is already in use"; case NVME_SC_DATA_XFER_ERROR: return "Error while trying to transfer the data or metadata"; case NVME_SC_POWER_LOSS: return "Command aborted due to power loss notification"; case NVME_SC_INTERNAL: return "Internal error"; case NVME_SC_ABORT_REQ: return "Command Abort request"; case NVME_SC_ABORT_QUEUE: return "Delete I/O Submission Queue request"; case NVME_SC_FUSED_FAIL: return "Other command in a fused operation failing"; case NVME_SC_FUSED_MISSING: return "Missing Fused Command"; case NVME_SC_INVALID_NS: return "Namespace or the format of that namespace is invalid"; case NVME_SC_CMD_SEQ_ERROR: return "Protocol violation in a multicommand sequence"; case NVME_SC_SANITIZE_FAILED: return "No recovery actions has been successfully completed"; case NVME_SC_SANITIZE_IN_PROGRESS: return "A sanitize operation is in progress"; case NVME_SC_LBA_RANGE: return "LBA exceeds the size of the namespace"; case NVME_SC_NS_WRITE_PROTECTED: return "Namespace is write protected by the host"; case NVME_SC_CAP_EXCEEDED: return "Capacity of the namespace to be exceeded"; case NVME_SC_NS_NOT_READY: return "Namespace is not ready to be accessed"; case NVME_SC_RESERVATION_CONFLICT: return "Conflict with a reservation on the accessed namespace"; case NVME_SC_CQ_INVALID: return "Completion Queue does not exist"; case NVME_SC_QID_INVALID: return "Invalid queue identifier specified"; case NVME_SC_QUEUE_SIZE: return "Invalid queue size"; case NVME_SC_ABORT_LIMIT: return "Outstanding Abort commands has exceeded the limit"; case NVME_SC_ABORT_MISSING: return "Abort command is missing"; case NVME_SC_ASYNC_LIMIT: return "Outstanding Async commands has been exceeded"; case NVME_SC_FIRMWARE_SLOT: return "Slot is invalid or read only"; case NVME_SC_FIRMWARE_IMAGE: return "Image specified for activation is invalid"; case NVME_SC_INVALID_VECTOR: return "Creation failed due to an invalid interrupt vector"; case NVME_SC_INVALID_LOG_PAGE: return "Log page indicated is invalid"; case NVME_SC_INVALID_FORMAT: return "LBA Format specified is not supported"; case NVME_SC_FW_NEEDS_CONV_RESET: return "commit was successful, but activation requires reset"; case NVME_SC_INVALID_QUEUE: return "Failed to delete the I/O Completion Queue specified"; case NVME_SC_FEATURE_NOT_SAVEABLE: return "Feature Identifier does not support a saveable value"; case NVME_SC_FEATURE_NOT_CHANGEABLE: return "Feature Identifier is not able to be changed"; case NVME_SC_FEATURE_NOT_PER_NS: return "Feature Identifier specified is not namespace specific"; case NVME_SC_FW_NEEDS_SUBSYS_RESET: return "Commit was successful, activation requires NVM Subsystem"; case NVME_SC_FW_NEEDS_RESET: return "Commit was successful, activation requires a reset"; case NVME_SC_FW_NEEDS_MAX_TIME: return "Would exceed the Maximum Time for Firmware Activation"; case NVME_SC_FW_ACTIVATE_PROHIBITED: return "Image specified is being prohibited from activation"; case NVME_SC_OVERLAPPING_RANGE: return "Image has overlapping ranges"; case NVME_SC_NS_INSUFFICENT_CAP: return "Requires more free space than is currently available"; case NVME_SC_NS_ID_UNAVAILABLE: return "Number of namespaces supported has been exceeded"; case NVME_SC_NS_ALREADY_ATTACHED: return "Controller is already attached to the namespace"; case NVME_SC_NS_IS_PRIVATE: return "Namespace is private"; case NVME_SC_NS_NOT_ATTACHED: return "Controller is not attached to the namespace"; case NVME_SC_THIN_PROV_NOT_SUPP: return "Thin provisioning is not supported by the controller"; case NVME_SC_CTRL_LIST_INVALID: return "Controller list provided is invalid"; case NVME_SC_BP_WRITE_PROHIBITED: return "Trying to modify a Boot Partition while it is locked"; case NVME_SC_BAD_ATTRIBUTES: return "Bad attributes"; case NVME_SC_WRITE_FAULT: return "Write data could not be committed to the media"; case NVME_SC_READ_ERROR: return "Read data could not be recovered from the media"; case NVME_SC_GUARD_CHECK: return "End-to-end guard check failure"; case NVME_SC_APPTAG_CHECK: return "End-to-end application tag check failure"; case NVME_SC_REFTAG_CHECK: return "End-to-end reference tag check failure"; case NVME_SC_COMPARE_FAILED: return "Miscompare during a Compare command"; case NVME_SC_ACCESS_DENIED: return "Access denied"; case NVME_SC_UNWRITTEN_BLOCK: return "Read from an LBA range containing a unwritten block"; case NVME_SC_ANA_PERSISTENT_LOSS: return "Namespace is in the ANA Persistent Loss state"; case NVME_SC_ANA_INACCESSIBLE: return "Namespace being in the ANA Inaccessible state"; case NVME_SC_ANA_TRANSITION: return "Namespace transitioning between Async Access states"; default: return "Unknown"; } } fwupd-1.9.16/plugins/nvme/fu-nvme-common.h000066400000000000000000000060331460375044200204060ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include enum { /* * Generic Command Status: */ NVME_SC_SUCCESS = 0x0, NVME_SC_INVALID_OPCODE = 0x1, NVME_SC_INVALID_FIELD = 0x2, NVME_SC_CMDID_CONFLICT = 0x3, NVME_SC_DATA_XFER_ERROR = 0x4, NVME_SC_POWER_LOSS = 0x5, NVME_SC_INTERNAL = 0x6, NVME_SC_ABORT_REQ = 0x7, NVME_SC_ABORT_QUEUE = 0x8, NVME_SC_FUSED_FAIL = 0x9, NVME_SC_FUSED_MISSING = 0xa, NVME_SC_INVALID_NS = 0xb, NVME_SC_CMD_SEQ_ERROR = 0xc, NVME_SC_SGL_INVALID_LAST = 0xd, NVME_SC_SGL_INVALID_COUNT = 0xe, NVME_SC_SGL_INVALID_DATA = 0xf, NVME_SC_SGL_INVALID_METADATA = 0x10, NVME_SC_SGL_INVALID_TYPE = 0x11, NVME_SC_SGL_INVALID_OFFSET = 0x16, NVME_SC_SGL_INVALID_SUBTYPE = 0x17, NVME_SC_SANITIZE_FAILED = 0x1C, NVME_SC_SANITIZE_IN_PROGRESS = 0x1D, NVME_SC_NS_WRITE_PROTECTED = 0x20, NVME_SC_LBA_RANGE = 0x80, NVME_SC_CAP_EXCEEDED = 0x81, NVME_SC_NS_NOT_READY = 0x82, NVME_SC_RESERVATION_CONFLICT = 0x83, /* * Command Specific Status: */ NVME_SC_CQ_INVALID = 0x100, NVME_SC_QID_INVALID = 0x101, NVME_SC_QUEUE_SIZE = 0x102, NVME_SC_ABORT_LIMIT = 0x103, NVME_SC_ABORT_MISSING = 0x104, NVME_SC_ASYNC_LIMIT = 0x105, NVME_SC_FIRMWARE_SLOT = 0x106, NVME_SC_FIRMWARE_IMAGE = 0x107, NVME_SC_INVALID_VECTOR = 0x108, NVME_SC_INVALID_LOG_PAGE = 0x109, NVME_SC_INVALID_FORMAT = 0x10a, NVME_SC_FW_NEEDS_CONV_RESET = 0x10b, NVME_SC_INVALID_QUEUE = 0x10c, NVME_SC_FEATURE_NOT_SAVEABLE = 0x10d, NVME_SC_FEATURE_NOT_CHANGEABLE = 0x10e, NVME_SC_FEATURE_NOT_PER_NS = 0x10f, NVME_SC_FW_NEEDS_SUBSYS_RESET = 0x110, NVME_SC_FW_NEEDS_RESET = 0x111, NVME_SC_FW_NEEDS_MAX_TIME = 0x112, NVME_SC_FW_ACTIVATE_PROHIBITED = 0x113, NVME_SC_OVERLAPPING_RANGE = 0x114, NVME_SC_NS_INSUFFICENT_CAP = 0x115, NVME_SC_NS_ID_UNAVAILABLE = 0x116, NVME_SC_NS_ALREADY_ATTACHED = 0x118, NVME_SC_NS_IS_PRIVATE = 0x119, NVME_SC_NS_NOT_ATTACHED = 0x11a, NVME_SC_THIN_PROV_NOT_SUPP = 0x11b, NVME_SC_CTRL_LIST_INVALID = 0x11c, NVME_SC_BP_WRITE_PROHIBITED = 0x11e, /* * I/O Command Set Specific - NVM commands: */ NVME_SC_BAD_ATTRIBUTES = 0x180, NVME_SC_INVALID_PI = 0x181, NVME_SC_READ_ONLY = 0x182, NVME_SC_ONCS_NOT_SUPPORTED = 0x183, /* * I/O Command Set Specific - Fabrics commands: */ NVME_SC_CONNECT_FORMAT = 0x180, NVME_SC_CONNECT_CTRL_BUSY = 0x181, NVME_SC_CONNECT_INVALID_PARAM = 0x182, NVME_SC_CONNECT_RESTART_DISC = 0x183, NVME_SC_CONNECT_INVALID_HOST = 0x184, NVME_SC_DISCOVERY_RESTART = 0x190, NVME_SC_AUTH_REQUIRED = 0x191, /* * Media and Data Integrity Errors: */ NVME_SC_WRITE_FAULT = 0x280, NVME_SC_READ_ERROR = 0x281, NVME_SC_GUARD_CHECK = 0x282, NVME_SC_APPTAG_CHECK = 0x283, NVME_SC_REFTAG_CHECK = 0x284, NVME_SC_COMPARE_FAILED = 0x285, NVME_SC_ACCESS_DENIED = 0x286, NVME_SC_UNWRITTEN_BLOCK = 0x287, /* * Path-related Errors: */ NVME_SC_ANA_PERSISTENT_LOSS = 0x301, NVME_SC_ANA_INACCESSIBLE = 0x302, NVME_SC_ANA_TRANSITION = 0x303, NVME_SC_DNR = 0x4000, }; const gchar * fu_nvme_status_to_string(guint32 status); fwupd-1.9.16/plugins/nvme/fu-nvme-device.c000066400000000000000000000356661460375044200203660ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-nvme-common.h" #include "fu-nvme-device.h" #define FU_NVME_ID_CTRL_SIZE 0x1000 struct _FuNvmeDevice { FuUdevDevice parent_instance; guint pci_depth; guint64 write_block_size; }; #define FU_NVME_COMMIT_ACTION_CA0 0b000 /* replace only */ #define FU_NVME_COMMIT_ACTION_CA1 0b001 /* replace, and activate on next reset */ #define FU_NVME_COMMIT_ACTION_CA2 0b010 /* activate on next reset */ #define FU_NVME_COMMIT_ACTION_CA3 0b011 /* replace, and activate immediately */ /** * FU_NVME_DEVICE_FLAG_FORCE_ALIGN: * * Force alignment of the firmware file. */ #define FU_NVME_DEVICE_FLAG_FORCE_ALIGN (1 << 0) /** * FU_NVME_DEVICE_FLAG_COMMIT_CA3: * * Replace, and activate immediately rather than on next reset. */ #define FU_NVME_DEVICE_FLAG_COMMIT_CA3 (1 << 1) G_DEFINE_TYPE(FuNvmeDevice, fu_nvme_device, FU_TYPE_UDEV_DEVICE) #define FU_NVME_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ static void fu_nvme_device_to_string(FuDevice *device, guint idt, GString *str) { FuNvmeDevice *self = FU_NVME_DEVICE(device); FU_DEVICE_CLASS(fu_nvme_device_parent_class)->to_string(device, idt, str); fu_string_append_ku(str, idt, "PciDepth", self->pci_depth); } /* @addr_start and @addr_end are *inclusive* to match the NMVe specification */ static gchar * fu_nvme_device_get_string_safe(const guint8 *buf, guint16 addr_start, guint16 addr_end) { GString *str; g_return_val_if_fail(buf != NULL, NULL); g_return_val_if_fail(addr_start < addr_end, NULL); str = g_string_new_len(NULL, addr_end + addr_start + 1); for (guint16 i = addr_start; i <= addr_end; i++) { gchar tmp = (gchar)buf[i]; /* skip leading spaces */ if (g_ascii_isspace(tmp) && str->len == 0) continue; if (g_ascii_isprint(tmp)) g_string_append_c(str, tmp); } /* nothing found */ if (str->len == 0) { g_string_free(str, TRUE); return NULL; } return g_strchomp(g_string_free(str, FALSE)); } static gchar * fu_nvme_device_get_guid_safe(const guint8 *buf, guint16 addr_start) { if (!fu_common_guid_is_plausible(buf + addr_start)) return NULL; return fwupd_guid_to_string((const fwupd_guid_t *)(buf + addr_start), FWUPD_GUID_FLAG_MIXED_ENDIAN); } static gboolean fu_nvme_device_submit_admin_passthru(FuNvmeDevice *self, struct nvme_admin_cmd *cmd, GError **error) { gint rc = 0; guint32 err; /* submit admin command */ if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), NVME_IOCTL_ADMIN_CMD, (guint8 *)cmd, &rc, FU_NVME_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error(error, "failed to issue admin command 0x%02x: ", cmd->opcode); return FALSE; } /* check the error code */ err = rc & 0x3ff; switch (err) { case NVME_SC_SUCCESS: /* devices are always added with _NEEDS_REBOOT, so ignore */ case NVME_SC_FW_NEEDS_CONV_RESET: case NVME_SC_FW_NEEDS_SUBSYS_RESET: case NVME_SC_FW_NEEDS_RESET: return TRUE; default: break; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported: %s", fu_nvme_status_to_string(err)); return FALSE; } static gboolean fu_nvme_device_identify_ctrl(FuNvmeDevice *self, guint8 *data, GError **error) { struct nvme_admin_cmd cmd = { .opcode = 0x06, .nsid = 0x00, .addr = 0x0, /* memory address of data */ .data_len = FU_NVME_ID_CTRL_SIZE, .cdw10 = 0x01, .cdw11 = 0x00, }; memcpy(&cmd.addr, &data, sizeof(gpointer)); return fu_nvme_device_submit_admin_passthru(self, &cmd, error); } static gboolean fu_nvme_device_fw_commit(FuNvmeDevice *self, guint8 slot, guint8 action, guint8 bpid, GError **error) { struct nvme_admin_cmd cmd = { .opcode = 0x10, .cdw10 = (bpid << 31) | (action << 3) | slot, }; return fu_nvme_device_submit_admin_passthru(self, &cmd, error); } static gboolean fu_nvme_device_fw_download(FuNvmeDevice *self, guint32 addr, const guint8 *data, guint32 data_sz, GError **error) { struct nvme_admin_cmd cmd = { .opcode = 0x11, .addr = 0x0, /* memory address of data */ .data_len = data_sz, .cdw10 = (data_sz >> 2) - 1, /* convert to DWORDs */ .cdw11 = addr >> 2, /* convert to DWORDs */ }; memcpy(&cmd.addr, &data, sizeof(gpointer)); return fu_nvme_device_submit_admin_passthru(self, &cmd, error); } static void fu_nvme_device_parse_cns_maybe_dell(FuNvmeDevice *self, const guint8 *buf) { g_autofree gchar *component_id = NULL; g_autofree gchar *devid = NULL; g_autofree gchar *guid_efi = NULL; g_autofree gchar *guid = NULL; /* add extra component ID if set */ component_id = fu_nvme_device_get_string_safe(buf, 0xc36, 0xc3d); if (component_id == NULL || !g_str_is_ascii(component_id) || strlen(component_id) < 6) { g_debug("invalid component ID, skipping"); return; } /* do not add the FuUdevDevice instance IDs as generic firmware * should not be used on these OEM-specific devices */ fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_AUTO_INSTANCE_IDS); /* add instance ID *and* GUID as using no-auto-instance-ids */ devid = g_strdup_printf("STORAGE-DELL-%s", component_id); fu_device_add_instance_id(FU_DEVICE(self), devid); guid = fwupd_guid_hash_string(devid); fu_device_add_guid(FU_DEVICE(self), guid); /* also add the EFI GUID */ guid_efi = fu_nvme_device_get_guid_safe(buf, 0x0c26); if (guid_efi != NULL) fu_device_add_guid(FU_DEVICE(self), guid_efi); } static gboolean fu_nvme_device_parse_cns(FuNvmeDevice *self, const guint8 *buf, gsize sz, GError **error) { guint8 fawr; guint8 fwug; guint8 nfws; guint8 s1ro; g_autofree gchar *gu = NULL; g_autofree gchar *mn = NULL; g_autofree gchar *sn = NULL; g_autofree gchar *sr = NULL; /* wrong size */ if (sz != FU_NVME_ID_CTRL_SIZE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to parse blob, expected 0x%04x bytes", (guint)FU_NVME_ID_CTRL_SIZE); return FALSE; } /* get sanitized string from CNS -- see the following doc for offsets: * NVM-Express-1_3c-2018.05.24-Ratified.pdf */ sn = fu_nvme_device_get_string_safe(buf, 4, 23); if (sn != NULL) fu_device_set_serial(FU_DEVICE(self), sn); mn = fu_nvme_device_get_string_safe(buf, 24, 63); if (mn != NULL) fu_device_set_name(FU_DEVICE(self), mn); sr = fu_nvme_device_get_string_safe(buf, 64, 71); if (sr != NULL) fu_device_set_version(FU_DEVICE(self), sr); /* firmware update granularity (FWUG) */ fwug = buf[319]; if (fwug != 0x00 && fwug != 0xff) self->write_block_size = ((guint64)fwug) * 0x1000; /* firmware slot information */ fawr = (buf[260] & 0x10) >> 4; nfws = (buf[260] & 0x0e) >> 1; s1ro = buf[260] & 0x01; g_debug("fawr: %u, nr fw slots: %u, slot1 r/o: %u", fawr, nfws, s1ro); /* FRU globally unique identifier (FGUID) */ gu = fu_nvme_device_get_guid_safe(buf, 127); if (gu != NULL) fu_device_add_guid(FU_DEVICE(self), gu); /* Dell helpfully provide an EFI GUID we can use in the vendor offset, * but don't have a header or any magic we can use -- so check if the * component ID looks plausible and the GUID is "sane" */ fu_nvme_device_parse_cns_maybe_dell(self, buf); /* fall back to the device description */ if (fu_device_get_guids(FU_DEVICE(self))->len == 0) { g_debug("no vendor GUID, falling back to mn"); fu_device_add_instance_id(FU_DEVICE(self), mn); } return TRUE; } /* * Returns: * %TRUE: device is in PCI subsystem * %FALSE: device is, probably, NVMe-over-Fabrics */ static gboolean fu_nvme_device_is_pci(FuDevice *device, GError **error) { g_autoptr(GUdevDevice) device_tmp = NULL; GUdevDevice *gdev; gdev = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); device_tmp = g_udev_device_get_parent_with_subsystem(gdev, "pci", NULL); if (device_tmp == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device is not on PCI subsystem"); return FALSE; } return TRUE; } static gboolean fu_nvme_device_probe(FuDevice *device, GError **error) { FuNvmeDevice *self = FU_NVME_DEVICE(device); /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_nvme_device_parent_class)->probe(device, error)) return FALSE; /* fix up vendor name so we can remove it from the product name */ if (g_strcmp0(fu_device_get_vendor(FU_DEVICE(device)), "Samsung Electronics Co Ltd") == 0) fu_device_set_vendor(FU_DEVICE(device), "Samsung"); /* ignore non-PCI NVMe devices */ if (!fu_nvme_device_is_pci(device, error)) return FALSE; /* set the physical ID */ if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "pci", error)) return FALSE; /* look at the PCI depth to work out if in an external enclosure */ self->pci_depth = fu_udev_device_get_slot_depth(FU_UDEV_DEVICE(device), "pci"); if (self->pci_depth <= 2) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); } /* most devices need at least a warm reset, but some quirked drives * need a full "cold" shutdown and startup */ if (!fu_device_has_private_flag(device, FU_NVME_DEVICE_FLAG_COMMIT_CA3) && !fu_device_has_flag(self, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); return TRUE; } static gboolean fu_nvme_device_setup(FuDevice *device, GError **error) { FuNvmeDevice *self = FU_NVME_DEVICE(device); guint8 buf[FU_NVME_ID_CTRL_SIZE] = {0x0}; /* get and parse CNS */ if (!fu_nvme_device_identify_ctrl(self, buf, error)) { g_prefix_error(error, "failed to identify %s: ", fu_device_get_physical_id(FU_DEVICE(self))); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "CNS", buf, sizeof(buf)); if (!fu_nvme_device_parse_cns(self, buf, sizeof(buf), error)) return FALSE; /* add one extra instance ID so that we can match bad firmware */ fu_device_add_instance_strsafe(device, "VER", fu_device_get_version(device)); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "NVME", "VEN", "DEV", "VER", NULL); /* success */ return TRUE; } static gboolean fu_nvme_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuNvmeDevice *self = FU_NVME_DEVICE(device); g_autoptr(GBytes) fw2 = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; guint64 block_size = self->write_block_size > 0 ? self->write_block_size : 0x1000; guint8 commit_action = FU_NVME_COMMIT_ACTION_CA1; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 10, "commit"); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* some vendors provide firmware files whose sizes are not multiples * of blksz *and* the device won't accept blocks of different sizes */ if (fu_device_has_private_flag(device, FU_NVME_DEVICE_FLAG_FORCE_ALIGN)) { fw2 = fu_bytes_align(fw, block_size, 0xff); } else { fw2 = g_bytes_ref(fw); } /* write each block */ chunks = fu_chunk_array_new_from_bytes(fw2, 0x00, block_size); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_nvme_device_fw_download(self, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to write chunk %u: ", i); return FALSE; } fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* commit */ if (fu_device_has_private_flag(device, FU_NVME_DEVICE_FLAG_COMMIT_CA3)) commit_action = FU_NVME_COMMIT_ACTION_CA3; if (!fu_nvme_device_fw_commit(self, 0x00, /* let controller choose */ commit_action, 0x00, /* boot partition identifier */ error)) { g_prefix_error(error, "failed to commit to auto slot: "); return FALSE; } fu_progress_step_done(progress); /* success! */ return TRUE; } static gboolean fu_nvme_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuNvmeDevice *self = FU_NVME_DEVICE(device); if (g_strcmp0(key, "NvmeBlockSize") == 0) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->write_block_size = tmp; return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_nvme_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_nvme_device_init(FuNvmeDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_summary(FU_DEVICE(self), "NVM Express solid state drive"); fu_device_add_icon(FU_DEVICE(self), "drive-harddisk"); fu_device_add_protocol(FU_DEVICE(self), "org.nvmexpress"); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT); fu_device_register_private_flag(FU_DEVICE(self), FU_NVME_DEVICE_FLAG_FORCE_ALIGN, "force-align"); fu_device_register_private_flag(FU_DEVICE(self), FU_NVME_DEVICE_FLAG_COMMIT_CA3, "commit-ca3"); } static void fu_nvme_device_finalize(GObject *object) { G_OBJECT_CLASS(fu_nvme_device_parent_class)->finalize(object); } static void fu_nvme_device_class_init(FuNvmeDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_nvme_device_finalize; klass_device->to_string = fu_nvme_device_to_string; klass_device->set_quirk_kv = fu_nvme_device_set_quirk_kv; klass_device->setup = fu_nvme_device_setup; klass_device->write_firmware = fu_nvme_device_write_firmware; klass_device->probe = fu_nvme_device_probe; klass_device->set_progress = fu_nvme_device_set_progress; } FuNvmeDevice * fu_nvme_device_new_from_blob(FuContext *ctx, const guint8 *buf, gsize sz, GError **error) { g_autoptr(FuNvmeDevice) self = NULL; self = g_object_new(FU_TYPE_NVME_DEVICE, "context", ctx, NULL); if (!fu_nvme_device_parse_cns(self, buf, sz, error)) return NULL; return g_steal_pointer(&self); } fwupd-1.9.16/plugins/nvme/fu-nvme-device.h000066400000000000000000000006111460375044200203510ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_NVME_DEVICE (fu_nvme_device_get_type()) G_DECLARE_FINAL_TYPE(FuNvmeDevice, fu_nvme_device, FU, NVME_DEVICE, FuUdevDevice) FuNvmeDevice * fu_nvme_device_new_from_blob(FuContext *ctx, const guint8 *buf, gsize sz, GError **error); fwupd-1.9.16/plugins/nvme/fu-nvme-plugin.c000066400000000000000000000015121460375044200204040ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-nvme-device.h" #include "fu-nvme-plugin.h" struct _FuNvmePlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuNvmePlugin, fu_nvme_plugin, FU_TYPE_PLUGIN) static void fu_nvme_plugin_init(FuNvmePlugin *self) { } static void fu_nvme_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "NvmeBlockSize"); fu_plugin_add_device_udev_subsystem(plugin, "nvme"); fu_plugin_add_device_gtype(plugin, FU_TYPE_NVME_DEVICE); } static void fu_nvme_plugin_class_init(FuNvmePluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_nvme_plugin_constructed; } fwupd-1.9.16/plugins/nvme/fu-nvme-plugin.h000066400000000000000000000003421460375044200204110ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuNvmePlugin, fu_nvme_plugin, FU, NVME_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/nvme/fu-self-test.c000066400000000000000000000062011460375044200200510ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-context-private.h" #include "fu-device-private.h" #include "fu-nvme-device.h" static void fu_nvme_cns_func(void) { gboolean ret; gsize sz; const gchar *ci = g_getenv("CI_NETWORK"); g_autofree gchar *data = NULL; g_autofree gchar *path = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuNvmeDevice) dev = NULL; g_autoptr(GError) error = NULL; ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); path = g_test_build_filename(G_TEST_DIST, "tests", "TOSHIBA_THNSN5512GPU7.bin", NULL); if (!g_file_test(path, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing TOSHIBA_THNSN5512GPU7.bin"); return; } ret = g_file_get_contents(path, &data, &sz, &error); g_assert_no_error(error); g_assert_true(ret); dev = fu_nvme_device_new_from_blob(ctx, (guint8 *)data, sz, &error); g_assert_no_error(error); g_assert_nonnull(dev); fu_device_convert_instance_ids(FU_DEVICE(dev)); g_assert_cmpstr(fu_device_get_name(FU_DEVICE(dev)), ==, "THNSN5512GPU7 TOSHIBA"); g_assert_cmpstr(fu_device_get_version(FU_DEVICE(dev)), ==, "410557LA"); g_assert_cmpstr(fu_device_get_serial(FU_DEVICE(dev)), ==, "37RSDEADBEEF"); g_assert_cmpstr(fu_device_get_guid_default(FU_DEVICE(dev)), ==, "e1409b09-50cf-5aef-8ad8-760b9022f88d"); } static void fu_nvme_cns_all_func(void) { const gchar *fn; g_autofree gchar *path = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(GDir) dir = NULL; /* may or may not exist */ path = g_test_build_filename(G_TEST_DIST, "tests", "blobs", NULL); if (!g_file_test(path, G_FILE_TEST_EXISTS)) return; dir = g_dir_open(path, 0, NULL); while ((fn = g_dir_read_name(dir)) != NULL) { gsize sz; g_autofree gchar *data = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuNvmeDevice) dev = NULL; g_autoptr(GError) error = NULL; filename = g_build_filename(path, fn, NULL); g_print("parsing %s... ", filename); if (!g_file_get_contents(filename, &data, &sz, &error)) { g_print("failed to load %s: %s\n", filename, error->message); continue; } dev = fu_nvme_device_new_from_blob(ctx, (guint8 *)data, sz, &error); if (dev == NULL) { g_print("failed to load %s: %s\n", filename, error->message); continue; } g_assert_cmpstr(fu_device_get_name(FU_DEVICE(dev)), !=, NULL); g_assert_cmpstr(fu_device_get_version(FU_DEVICE(dev)), !=, NULL); g_assert_cmpstr(fu_device_get_serial(FU_DEVICE(dev)), !=, NULL); g_print("done\n"); } } int main(int argc, char **argv) { g_autofree gchar *testdatadir = NULL; (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", testdatadir, TRUE); /* tests go here */ g_test_add_func("/fwupd/cns", fu_nvme_cns_func); g_test_add_func("/fwupd/cns{all}", fu_nvme_cns_all_func); return g_test_run(); } fwupd-1.9.16/plugins/nvme/meson.build000066400000000000000000000026101460375044200175310ustar00rootroot00000000000000nvme_header = cc.has_header('linux/nvme_ioctl.h', required: get_option('plugin_nvme')) if nvme_header and \ get_option('plugin_nvme').require(gudev.found(), error_message: 'gudev is needed for plugin_nvme').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginNvme"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('nvme.quirk') plugin_builtin_nvme = static_library('fu_plugin_nvme', sources: [ 'fu-nvme-plugin.c', 'fu-nvme-common.c', 'fu-nvme-device.c', ], include_directories: plugin_incdirs, c_args: [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], link_with: plugin_libs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_nvme if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) env.set('FWUPD_DATADIR_QUIRKS', meson.current_source_dir()) e = executable( 'nvme-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_nvme, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('nvme-self-test', e, env: env) # added to installed-tests endif endif fwupd-1.9.16/plugins/nvme/nvme.quirk000066400000000000000000000017661460375044200174240ustar00rootroot00000000000000# Phison [NVME\VEN_1987] Flags = force-align,needs-shutdown # Kingston [NVME\VEN_2646] Flags = needs-shutdown # KIOXIA [NVME\VEN_1179] Flags = signed-payload [NVME\VEN_1E0F] Flags = signed-payload # Samsung [NVME\VEN_144D] Flags = signed-payload # SSSTC [NVME\VEN_14A4] Flags = signed-payload [NVME\VEN_1E95] Flags = signed-payload # SK Hynix [NVME\VEN_1C5C] Flags = signed-payload [NVME\VEN_1C5C&DEV_1D59] Flags = commit-ca3 # Kingston [NVME\VEN_2646&DEV_5013] Flags = signed-payload [NVME\VEN_2646&DEV_500A] Flags = signed-payload [NVME\VEN_2646&DEV_500B] Flags = unsigned-payload [NVME\VEN_2646&DEV_5012] Flags = unsigned-payload # Western Digital [NVME\VEN_101C] Flags = signed-payload # Solidigm [NVME\VEN_025E&DEV_F1AB] Flags = needs-shutdown [NVME\VEN_144D&DEV_A80A&VER_2B2QGXA7] Issue = https://www.pugetsystems.com/support/guides/critical-samsung-ssd-firmware-update/ [NVME\VEN_144D&DEV_A80A&VER_3B2QGXA7] Issue = https://www.pugetsystems.com/support/guides/critical-samsung-ssd-firmware-update/ fwupd-1.9.16/plugins/optionrom/000077500000000000000000000000001460375044200164515ustar00rootroot00000000000000fwupd-1.9.16/plugins/optionrom/README.md000066400000000000000000000011321460375044200177250ustar00rootroot00000000000000--- title: Plugin: OptionROM --- ## Introduction This plugin is also able to read and parse the firmware of some PCI devices which allows some host state verification to be done. ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `PCI\VEN_%04X&DEV_%04X` ## Vendor ID Security The device is not upgradable and thus requires no vendor ID set. ## External Interface Access This plugin requires read access to the rom file of PCI devices (`/sys/class/pci_bus/*/device/rom`) ## Version Considerations This plugin has been available since fwupd version `1.3.3`. fwupd-1.9.16/plugins/optionrom/fu-optionrom-device.c000066400000000000000000000046201460375044200225120ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-optionrom-device.h" struct _FuOptionromDevice { FuUdevDevice parent_instance; }; G_DEFINE_TYPE(FuOptionromDevice, fu_optionrom_device, FU_TYPE_UDEV_DEVICE) static gboolean fu_optionrom_device_probe(FuDevice *device, GError **error) { g_autofree gchar *fn = NULL; /* does the device even have ROM? */ fn = g_build_filename(fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device)), "rom", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unable to read firmware from device"); return FALSE; } /* FuUdevDevice->probe -- needed by FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT */ if (!FU_DEVICE_CLASS(fu_optionrom_device_parent_class)->probe(device, error)) return FALSE; /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "pci", error); } static GBytes * fu_optionrom_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(GBytes) fw = NULL; /* FuUdevDevice->dump_firmware */ fw = FU_DEVICE_CLASS(fu_optionrom_device_parent_class) ->dump_firmware(device, progress, error); if (fw == NULL) return NULL; if (g_bytes_get_size(fw) < 512) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware too small: %u bytes", (guint)g_bytes_get_size(fw)); return NULL; } return g_steal_pointer(&fw); } static void fu_optionrom_device_init(FuOptionromDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_set_logical_id(FU_DEVICE(self), "rom"); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT); } static void fu_optionrom_device_finalize(GObject *object) { G_OBJECT_CLASS(fu_optionrom_device_parent_class)->finalize(object); } static void fu_optionrom_device_class_init(FuOptionromDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_optionrom_device_finalize; klass_device->dump_firmware = fu_optionrom_device_dump_firmware; klass_device->probe = fu_optionrom_device_probe; } fwupd-1.9.16/plugins/optionrom/fu-optionrom-device.h000066400000000000000000000004671460375044200225240ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_OPTIONROM_DEVICE (fu_optionrom_device_get_type()) G_DECLARE_FINAL_TYPE(FuOptionromDevice, fu_optionrom_device, FU, OPTIONROM_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/optionrom/fu-optionrom-plugin.c000066400000000000000000000015471460375044200225560ustar00rootroot00000000000000/* * Copyright (C) 2015-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-optionrom-device.h" #include "fu-optionrom-plugin.h" struct _FuOptionromPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuOptionromPlugin, fu_optionrom_plugin, FU_TYPE_PLUGIN) static void fu_optionrom_plugin_init(FuOptionromPlugin *self) { } static void fu_optionrom_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_udev_subsystem(plugin, "pci"); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_CONFLICTS, "udev"); fu_plugin_add_device_gtype(plugin, FU_TYPE_OPTIONROM_DEVICE); } static void fu_optionrom_plugin_class_init(FuOptionromPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_optionrom_plugin_constructed; } fwupd-1.9.16/plugins/optionrom/fu-optionrom-plugin.h000066400000000000000000000003611460375044200225540ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuOptionromPlugin, fu_optionrom_plugin, FU, OPTIONROM_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/optionrom/fuzzing/000077500000000000000000000000001460375044200201455ustar00rootroot00000000000000fwupd-1.9.16/plugins/optionrom/fuzzing/header-data-payload.rom000066400000000000000000000010001460375044200244410ustar00rootroot00000000000000U K7hdr-data-payload PCIRVersion 1.0\fwupd-1.9.16/plugins/optionrom/fuzzing/header-no-data.rom000066400000000000000000000010001460375044200234240ustar00rootroot00000000000000U K7hdr-no-data fwupd-1.9.16/plugins/optionrom/fuzzing/ifr-header-data-payload.rom000066400000000000000000000012001460375044200252210ustar00rootroot00000000000000NVGIU K7ifr-hdr-data-payld PCIRVersion 1.0\fwupd-1.9.16/plugins/optionrom/fuzzing/naked-ifr.rom000066400000000000000000000002001460375044200225140ustar00rootroot00000000000000NVGIfwupd-1.9.16/plugins/optionrom/meson.build000066400000000000000000000005721460375044200206170ustar00rootroot00000000000000if gudev.found() cargs = ['-DG_LOG_DOMAIN="FuPluginOptionrom"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtins += static_library('fu_plugin_optionrom', sources: [ 'fu-optionrom-plugin.c', 'fu-optionrom-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/parade-lspcon/000077500000000000000000000000001460375044200171535ustar00rootroot00000000000000fwupd-1.9.16/plugins/parade-lspcon/README.md000066400000000000000000000036621460375044200204410ustar00rootroot00000000000000--- title: Plugin: Parade LSPCON --- ## Introduction This plugin updates the firmware of HDMI level shifter and protocol converter (LSPCON) devices made by Parade Technologies, such as the PS175. These devices communicate over I²C, via either the DisplayPort aux channel or a dedicated bus- this plugin uses a dedicated bus declared by system firmware for, flashing, and reads the device firmware version from DPCD. Quirks specify the DisplayPort bus over which DPCD is read for a given system. Firmware is stored on an external flash attached to an SPI bus on the device. The attached flash is assumed to be compatible with the W25Q20 series of devices, in particular supporting a 64k Block Erase command (0xD8) with 24-bit address and Write Enable for Volatile Status Register (0x05). ## Firmware Format The device firmware is in an unspecified binary format that is written directly to an inactive partition of the Flash attached to the device. This plugin supports the following protocol ID: * `com.paradetech.ps176` ## GUID Generation Devices use an extra instance ID derived from SMBIOS, e.g. * `I2C\NAME_1AF80175:00:00&FAMILY_Google_Hatch` ## Quirk Use This plugin uses the following plugin-specific quirks: ### ParadeLspconAuxDeviceName The sysfs name of the `drm_dp_aux_dev` over which device version should be read. Since: 1.6.2 ## Vendor ID security The vendor ID is specified by system firmware (such as ACPI tables) and is part of the device's name as read from sysfs. ## External Interface Access This plugin requires access to the DisplayPort aux channel to read DPCD, such as `/dev/drm_dp_aux0` as well as the i2c bus attached to the device, such as `/dev/i2c-7`. ## Version Considerations This plugin has been available since fwupd version `1.6.2`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Peter Marheine: @tari fwupd-1.9.16/plugins/parade-lspcon/fu-parade-lspcon-device.c000066400000000000000000000641451460375044200237260ustar00rootroot00000000000000/* * Copyright (C) 2021 Peter Marheine * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-parade-lspcon-device.h" /* device registers are split into pages, where * each page has its own I2C address */ #define I2C_ADDR_PAGE2 0x4A #define REG_ADDR_CLT2SPI 0x82 /* FLASH_ADDR_* are the upper 16 bits of the 24-bit flash address that gets * mapped into page 7. Writing 0x01, 0x42 will map the 256 bytes from 0x420100 * into page 7. */ #define REG_ADDR_FLASH_ADDR_LO 0x8E #define REG_ADDR_FLASH_ADDR_HI 0x8F /* 16-deep SPI write and read buffer FIFOs */ #define REG_ADDR_WR_FIFO 0x90 #define REG_ADDR_RD_FIFO 0x91 /* Low nibble is write operation length, high nibble for read commands. * Reset to 0 after command completion. */ #define REG_ADDR_SPI_LEN 0x92 #define REG_ADDR_SPI_CTL 0x93 /* set to do a write-only transaction */ #define SPI_CTL_NOREAD 0x04 /* set to begin executing command */ #define SPI_CTL_TRIGGER 0x01 /* operation status fields: set to 1 when operation begins, 2 when command has been * sent, reset to 0 when command completed */ #define REG_ADDR_SPI_STATUS 0x9e /* byte programming */ #define SPI_STATUS_BP_MASK 0x03 /* sector erase */ #define SPI_STATUS_SE_MASK 0x0C /* chip erase */ #define SPI_STATUS_CE_MASK 0x30 /* write WR_PROTECT_DISABLE to permit flash write operations */ #define REG_ADDR_WR_PROTECT 0xB3 #define WR_PROTECT_DISABLE 0x10 /* MPU control register */ #define REG_ADDR_MPU 0xBC /* write a magic sequence to this register to enable writes to * mapped memory via page 7, or anything else to disable */ #define REG_ADDR_MAP_WRITE 0xDA #define I2C_ADDR_PAGE5 0x4D #define REG_ADDR_ACTIVE_PARTITION 0x0E #define I2C_ADDR_PAGE7 0x4F #define FLASH_BLOCK_SIZE 0x10000 #define FU_PARADE_LSPCON_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ /* * user1: 0x10000 - 0x20000 * user2: 0x20000 - 0x30000 * flag: 0x00002 - 0x00004 */ struct _FuParadeLspconDevice { FuI2cDevice parent_instance; guint8 active_partition; gchar *aux_device_name; }; G_DEFINE_TYPE(FuParadeLspconDevice, fu_parade_lspcon_device, FU_TYPE_I2C_DEVICE) static void fu_parade_lspcon_device_init(FuParadeLspconDevice *self) { FuDevice *device = FU_DEVICE(self); fu_device_set_vendor(device, "Parade Technologies"); fu_device_add_vendor_id(device, "PCI:0x1AF8"); fu_device_add_protocol(device, "com.paradetech.ps176"); fu_device_add_icon(device, "video-display"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_GENERIC_GUIDS); fu_device_set_firmware_size(device, 0x10000); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PAIR); } static void fu_parade_lspcon_device_finalize(GObject *object) { FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE(object); g_free(self->aux_device_name); G_OBJECT_CLASS(fu_parade_lspcon_device_parent_class)->finalize(object); } static gboolean fu_parade_lspcon_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE(device); if (g_strcmp0(key, "ParadeLspconAuxDeviceName") == 0) { self->aux_device_name = g_strdup(value); return TRUE; } return FU_DEVICE_CLASS(fu_parade_lspcon_device_parent_class) ->set_quirk_kv(device, key, value, error); } static gboolean fu_parade_lspcon_device_probe(FuDevice *device, GError **error) { FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE(device); FuContext *context = fu_device_get_context(device); /* FuI2cDevice->probe */ if (!FU_DEVICE_CLASS(fu_parade_lspcon_device_parent_class)->probe(device, error)) return FALSE; /* custom instance IDs to get device quirks */ fu_device_add_instance_str(device, "FAMILY", fu_context_get_hwid_value(context, FU_HWIDS_KEY_FAMILY)); if (!fu_device_build_instance_id(device, error, "I2C", "NAME", "FAMILY", NULL)) return FALSE; /* should know which aux device over which we read DPCD version */ if (self->aux_device_name == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ParadeLspconAuxDeviceName must be specified"); return FALSE; } /* success */ return TRUE; } static gboolean fu_parade_lspcon_ensure_i2c_address(FuParadeLspconDevice *self, guint8 address, GError **error) { if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), I2C_SLAVE, (guint8 *)(guintptr)address, NULL, FU_PARADE_LSPCON_DEVICE_IOCTL_TIMEOUT, error)) { g_prefix_error(error, "failed to set I2C address: "); return FALSE; } return TRUE; } static gboolean fu_parade_lspcon_device_open(FuDevice *device, GError **error) { if (!FU_DEVICE_CLASS(fu_parade_lspcon_device_parent_class)->open(device, error)) return FALSE; /* general assumption is that page 2 is selected: code that uses another address * should use an address guard to ensure it gets reset */ return fu_parade_lspcon_ensure_i2c_address(FU_PARADE_LSPCON_DEVICE(device), I2C_ADDR_PAGE2, error); } /** * creates a scope in which the device's target I2C address is something * other than page 2, and resets it to page 2 when the scope is left. */ typedef struct { FuParadeLspconDevice *device; } FuParadeLspconI2cAddressGuard; static FuParadeLspconI2cAddressGuard * fu_parade_lspcon_i2c_address_guard_new(FuParadeLspconDevice *self, guint8 new_address, GError **error) { FuParadeLspconI2cAddressGuard *out; if (!fu_parade_lspcon_ensure_i2c_address(self, new_address, error)) return NULL; out = g_new0(FuParadeLspconI2cAddressGuard, 1); out->device = self; return out; } static void fu_parade_lspcon_i2c_address_guard_free(FuParadeLspconI2cAddressGuard *guard) { g_autoptr(GError) error_local = NULL; if (!fu_parade_lspcon_ensure_i2c_address(guard->device, I2C_ADDR_PAGE2, &error_local)) { g_warning("failed to set page2 back: %s", error_local->message); } g_free(guard); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuParadeLspconI2cAddressGuard, fu_parade_lspcon_i2c_address_guard_free); static gboolean fu_parade_lspcon_write_register(FuParadeLspconDevice *self, guint8 register_addr, guint8 value, GError **error) { guint8 transaction[] = {register_addr, value}; return fu_i2c_device_write(FU_I2C_DEVICE(self), transaction, sizeof(transaction), error); } static gboolean fu_parade_lspcon_read_register(FuParadeLspconDevice *self, guint8 register_addr, guint8 *value, GError **error) { FuI2cDevice *i2c_device = FU_I2C_DEVICE(self); if (!fu_i2c_device_write(i2c_device, ®ister_addr, 0x1, error)) return FALSE; return fu_i2c_device_read(i2c_device, value, 0x1, error); } /* map the page containing the given address into page 7 */ static gboolean fu_parade_lspcon_map_page(FuParadeLspconDevice *self, guint32 address, GError **error) { if (!fu_parade_lspcon_write_register(self, REG_ADDR_FLASH_ADDR_HI, address >> 16, error)) return FALSE; return fu_parade_lspcon_write_register(self, REG_ADDR_FLASH_ADDR_LO, address >> 8, error); } /* wait until the specified register masked with mask reads the expected * value, up to 10 seconds */ static gboolean fu_parade_lspcon_poll_register(FuParadeLspconDevice *self, guint8 register_address, guint8 mask, guint8 expected, GError **error) { guint8 value; g_autoptr(GTimer) timer = g_timer_new(); do { if (!fu_parade_lspcon_read_register(self, register_address, &value, error)) return FALSE; if ((value & mask) == expected) return TRUE; } while (g_timer_elapsed(timer, NULL) <= 10.0); g_set_error(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "register %x did not read %x (mask %x) within 10 seconds: read %x", register_address, expected, mask, value); return FALSE; } static gboolean fu_parade_lspcon_flash_read(FuParadeLspconDevice *self, guint32 base_address, guint8 *data, const gsize len, FuProgress *progress, GError **error) { FuI2cDevice *i2c_device = FU_I2C_DEVICE(self); gsize offset = 0; while (offset < len) { /* page 7 reads always start from the base of the mapped window- we'll * read the whole page then pull out the parts we care about, using the * full page everywhere except possibly in the first and last reads */ guint8 page_data[256] = {0x0}; guint8 page_data_start = base_address & 0xFF; gsize page_data_take = MIN((gssize)len, 256 - page_data_start); g_autoptr(FuParadeLspconI2cAddressGuard) guard = NULL; if (!fu_parade_lspcon_map_page(self, base_address, error)) return FALSE; guard = fu_parade_lspcon_i2c_address_guard_new(self, I2C_ADDR_PAGE7, error); if (guard == NULL) return FALSE; if (!fu_i2c_device_read(i2c_device, page_data, 256, error)) return FALSE; if (!fu_memcpy_safe(data, len, offset, page_data, sizeof(page_data), page_data_start, page_data_take, error)) return FALSE; base_address += page_data_take; offset += page_data_take; fu_progress_set_percentage_full(progress, offset, len); } return TRUE; } static gboolean fu_parade_lspcon_flash_transmit_command(FuParadeLspconDevice *self, const guint8 *command, gsize command_len, GError **error) { /* write length field is 4 bits wide */ g_return_val_if_fail(command_len > 0 && command_len <= 16, FALSE); /* fill transmit buffer */ for (gsize i = 0; i < command_len; i++) { if (!fu_parade_lspcon_write_register(self, REG_ADDR_WR_FIFO, command[i], error)) return FALSE; } /* set command length */ if (!fu_parade_lspcon_write_register(self, REG_ADDR_SPI_LEN, command_len - 1, error)) return FALSE; /* execute operation */ return fu_parade_lspcon_write_register(self, REG_ADDR_SPI_CTL, SPI_CTL_NOREAD | SPI_CTL_TRIGGER, error); } /* * set the flash Write Enable Latch, permitting the next program, erase or * status register write operation. */ static gboolean fu_parade_lspcon_flash_enable_write(FuParadeLspconDevice *self, GError **error) { const guint8 write_enable[] = {0x06}; return fu_parade_lspcon_flash_transmit_command(self, write_enable, sizeof(write_enable), error); } static gboolean fu_parade_lspcon_flash_read_status(FuParadeLspconDevice *self, guint8 *value, GError **error) { if (!fu_parade_lspcon_write_register(self, REG_ADDR_WR_FIFO, 0x05, error)) return FALSE; if (!fu_parade_lspcon_write_register(self, REG_ADDR_SPI_LEN, 0, error)) return FALSE; if (!fu_parade_lspcon_write_register(self, REG_ADDR_SPI_CTL, SPI_CTL_TRIGGER, error)) return FALSE; /* wait for command completion */ if (!fu_parade_lspcon_poll_register(self, REG_ADDR_SPI_CTL, SPI_CTL_TRIGGER, 0, error)) return FALSE; /* read SR value */ return fu_parade_lspcon_read_register(self, REG_ADDR_RD_FIFO, value, error); } /* poll the flash status register for operation completion */ static gboolean fu_parade_lspcon_flash_wait_ready(FuParadeLspconDevice *self, GError **error) { g_autoptr(GTimer) timer = g_timer_new(); do { guint8 status_register; if (!fu_parade_lspcon_flash_read_status(self, &status_register, error)) return FALSE; /* BUSY bit clears on completion */ if ((status_register & 1) == 0) return TRUE; /* flash operations generally take between 1ms and 4s; polling * at 1000 Hz is still quite responsive and not overly slow */ fu_device_sleep(FU_DEVICE(self), 1); /* ms */ } while (g_timer_elapsed(timer, NULL) <= 10.0); g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "flash did not become ready within 10 seconds"); return FALSE; } static gboolean fu_parade_lspcon_flash_write(FuParadeLspconDevice *self, guint32 base_address, GBytes *data, FuProgress *progress, GError **error) { FuI2cDevice *i2c_device = FU_I2C_DEVICE(self); const guint8 unlock_writes[] = {0xaa, 0x55, 0x50, 0x41, 0x52, 0x44}; gsize data_len = g_bytes_get_size(data); g_autoptr(FuChunkArray) chunks = NULL; /* address must be 256-byte aligned */ g_return_val_if_fail((base_address & 0xFF) == 0, FALSE); g_debug("flash write %" G_GSIZE_FORMAT " bytes at %#x", g_bytes_get_size(data), base_address); /* unlock map writes by writing the magic sequence */ for (gsize i = 0; i < sizeof(unlock_writes); i++) { if (!fu_parade_lspcon_write_register(self, REG_ADDR_MAP_WRITE, unlock_writes[i], error)) return FALSE; } /* reset clt2SPI, required before write */ if (!fu_parade_lspcon_write_register(self, REG_ADDR_CLT2SPI, 0x20, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), 100); /* ms */ if (!fu_parade_lspcon_write_register(self, REG_ADDR_CLT2SPI, 0, error)) return FALSE; chunks = fu_chunk_array_new_from_bytes(data, base_address, 256); for (gsize i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chunk = fu_chunk_array_index(chunks, i); guint32 address = fu_chunk_get_address(chunk); guint32 chunk_size = fu_chunk_get_data_sz(chunk); guint8 write_data[257] = {0x0}; g_autoptr(FuParadeLspconI2cAddressGuard) guard = NULL; /* map target address range in page 7 */ if (!fu_parade_lspcon_map_page(self, address, error)) return FALSE; /* write data to page 7 memory window */ guard = fu_parade_lspcon_i2c_address_guard_new(self, I2C_ADDR_PAGE7, error); if (guard == NULL) return FALSE; /* page write is prefixed with an offset: * we always start from offset 0 */ if (!fu_memcpy_safe(write_data, sizeof(write_data), 1, fu_chunk_get_data(chunk), chunk_size, 0, chunk_size, error)) return FALSE; if (!fu_i2c_device_write(i2c_device, write_data, chunk_size + 1, error)) return FALSE; fu_progress_set_percentage_full(progress, address - base_address, data_len); } /* re-lock map writes */ return fu_parade_lspcon_write_register(self, REG_ADDR_MAP_WRITE, 0, error); } static gboolean fu_parade_lspcon_flash_erase_block(FuParadeLspconDevice *self, guint32 base_address, guint32 size, GError **error) { const guint8 block_erase[] = {0xd8, base_address >> 16, base_address >> 8, base_address}; /* address must be block-aligned */ g_return_val_if_fail((base_address & (FLASH_BLOCK_SIZE - 1)) == 0, FALSE); g_return_val_if_fail(size == FLASH_BLOCK_SIZE, FALSE); g_debug("flash erase block at %#x", base_address); if (!fu_parade_lspcon_flash_enable_write(self, error)) return FALSE; if (!fu_parade_lspcon_flash_transmit_command(self, block_erase, sizeof(block_erase), error)) return FALSE; /* wait for command completion */ if (!fu_parade_lspcon_poll_register(self, REG_ADDR_SPI_STATUS, SPI_STATUS_SE_MASK, 0, error)) return FALSE; /* wait for flash to complete erase */ return fu_parade_lspcon_flash_wait_ready(self, error); } static gboolean fu_parade_lspcon_probe_active_flash_partition(FuParadeLspconDevice *self, guint8 *partition, GError **error) { guint8 data = 0x0; g_autoptr(FuParadeLspconI2cAddressGuard) guard = NULL; /* read currently-running flash partition number */ guard = fu_parade_lspcon_i2c_address_guard_new(self, I2C_ADDR_PAGE5, error); if (guard == NULL) return FALSE; if (!fu_parade_lspcon_read_register(self, REG_ADDR_ACTIVE_PARTITION, &data, error)) return FALSE; *partition = data; return TRUE; } static gboolean fu_parade_lspcon_device_reload(FuDevice *device, GError **error) { FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE(device); guint32 oui; guint8 version_buf[2] = {0x0}; g_autofree gchar *version = NULL; g_autofree gchar *oui_string = NULL; g_autolist(GUdevDevice) aux_devices = NULL; g_autoptr(FuDeviceLocker) aux_device_locker = NULL; g_autoptr(FuUdevDevice) aux_device = NULL; g_autoptr(GUdevClient) udev_client = g_udev_client_new(NULL); g_autoptr(GUdevEnumerator) enumerator = g_udev_enumerator_new(udev_client); /* determine active partition for flashing later */ if (!fu_parade_lspcon_probe_active_flash_partition(self, &self->active_partition, error)) return FALSE; g_info("device reports running from partition %d", self->active_partition); if (self->active_partition < 1 || self->active_partition > 3) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unexpected active flash partition: %d", self->active_partition); return FALSE; } /* find the drm_dp_aux_dev specified by quirks that is connected to the * LSPCON, in order to read DPCD from it */ if (self->aux_device_name == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no DP aux device specified, unable to query LSPCON"); return FALSE; } g_udev_enumerator_add_match_subsystem(enumerator, "drm_dp_aux_dev"); g_udev_enumerator_add_match_sysfs_attr(enumerator, "name", self->aux_device_name); aux_devices = g_udev_enumerator_execute(enumerator); if (aux_devices == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to locate a DP aux device named \"%s\"", self->aux_device_name); return FALSE; } if (g_list_length(aux_devices) > 1) { g_list_free_full(aux_devices, g_object_unref); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "found multiple DP aux devices with name \"%s\"", self->aux_device_name); return FALSE; } aux_device = g_object_new(FU_TYPE_DPAUX_DEVICE, "context", fu_device_get_context(device), "udev-device", aux_devices->data, NULL); g_debug("using aux dev %s", fu_udev_device_get_sysfs_path(aux_device)); /* the following open() requires the device have IDs set */ if (!fu_udev_device_set_physical_id(aux_device, "drm_dp_aux_dev", error)) return FALSE; /* open device to read version from DPCD */ aux_device_locker = fu_device_locker_new(aux_device, error); if (aux_device_locker == NULL) return FALSE; /* DPCD address 00500-00502: device OUI */ oui = fu_dpaux_device_get_dpcd_ieee_oui(FU_DPAUX_DEVICE(aux_device)); if (oui != 0x001CF8) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device OUI %06X does not match expected value for Paradetech", oui); return FALSE; } oui_string = g_strdup_printf("OUI:%06X", oui); fu_device_add_vendor_id(device, oui_string); /* DPCD address 0x50A, 0x50B: branch device firmware * major and minor revision */ if (!fu_udev_device_pread(aux_device, 0x50a, version_buf, sizeof(version_buf), error)) return FALSE; version = g_strdup_printf("%d.%d", version_buf[0], version_buf[1]); fu_device_set_version(device, version); return TRUE; } static gboolean fu_parade_lspcon_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE(device); const guint8 write_sr_volatile[] = {0x50}; const guint8 write_sr_disable_bp[] = { 0x01, /* write SR */ 0x80, /* write protect follows /WP signal, no block protection */ 0x00}; const guint8 write_sr_enable_bp[] = {0x01, 0x8c, 0x00}; /* if the boot partition is active we could flash either, but prefer * the first */ const guint8 target_partition = self->active_partition == 1 ? 2 : 1; const guint32 target_address = target_partition << 16; const guint8 flag_data[] = {0x55, 0xaa, target_partition, 1 - target_partition}; const guint8 *buf; gsize bufsz; g_autofree guint8 *readback_buf = NULL; g_autoptr(GBytes) blob_fw = NULL; g_autoptr(GBytes) flag_data_bytes = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 5, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 70, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 25, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 3, "device-write-boot"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 2, "device-verify-boot"); blob_fw = fu_firmware_get_bytes(firmware, error); if (blob_fw == NULL) return FALSE; buf = g_bytes_get_data(blob_fw, &bufsz); if (bufsz != FLASH_BLOCK_SIZE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid image size %#" G_GSIZE_MODIFIER "x, expected %#x", bufsz, (unsigned)FLASH_BLOCK_SIZE); return FALSE; } /* deassert flash /WP */ if (!fu_parade_lspcon_write_register(self, REG_ADDR_WR_PROTECT, WR_PROTECT_DISABLE, error)) return FALSE; /* disable flash protection until next power-off */ if (!fu_parade_lspcon_flash_transmit_command(self, write_sr_volatile, sizeof(write_sr_volatile), error)) return FALSE; if (!fu_parade_lspcon_flash_transmit_command(self, write_sr_disable_bp, sizeof(write_sr_disable_bp), error)) return FALSE; /* wait for SR write to complete */ if (!fu_parade_lspcon_flash_wait_ready(self, error)) return FALSE; /* erase entire target partition (one flash block) */ if (!fu_parade_lspcon_flash_erase_block(self, target_address, bufsz, error)) { g_prefix_error(error, "failed to erase flash partition %d: ", target_partition); return FALSE; } fu_progress_step_done(progress); /* write image */ if (!fu_parade_lspcon_flash_write(self, target_address, blob_fw, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write firmware to partition %d: ", target_partition); return FALSE; } fu_progress_step_done(progress); /* read back written image to verify */ readback_buf = g_malloc0(bufsz); if (!fu_parade_lspcon_flash_read(self, target_address, readback_buf, bufsz, fu_progress_get_child(progress), error)) return FALSE; if (!fu_memcmp_safe(buf, bufsz, 0x0, readback_buf, bufsz, 0x0, bufsz, error)) { g_prefix_error(error, "flash contents do not match: "); return FALSE; } fu_progress_step_done(progress); /* erase flag partition */ if (!fu_parade_lspcon_flash_erase_block(self, 0, FLASH_BLOCK_SIZE, error)) return FALSE; /* write flag indicating device should boot the target partition */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); flag_data_bytes = g_bytes_new_static(flag_data, sizeof(flag_data)); if (!fu_parade_lspcon_flash_write(self, 0, flag_data_bytes, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* verify flag partition */ if (!fu_parade_lspcon_flash_read(self, 0, readback_buf, sizeof(flag_data), fu_progress_get_child(progress), error)) return FALSE; if (!fu_memcmp_safe(flag_data, sizeof(flag_data), 0x0, readback_buf, bufsz, 0x0, MIN(sizeof(flag_data), bufsz), error)) { g_prefix_error(error, "flag partition contents do not match: "); return FALSE; } /* re-enable flash protection */ if (!fu_parade_lspcon_flash_transmit_command(self, write_sr_volatile, sizeof(write_sr_volatile), error)) return FALSE; if (!fu_parade_lspcon_flash_transmit_command(self, write_sr_enable_bp, sizeof(write_sr_enable_bp), error)) return FALSE; fu_progress_step_done(progress); /* reassert /WP to flash */ return fu_parade_lspcon_write_register(self, REG_ADDR_WR_PROTECT, 0, error); } static gboolean fu_parade_lspcon_set_mpu_running(FuParadeLspconDevice *self, gboolean running, GError **error) { /* reset */ if (!fu_parade_lspcon_write_register(self, REG_ADDR_MPU, 0xc0, error)) return FALSE; /* release reset, set MPU active or not */ return fu_parade_lspcon_write_register(self, REG_ADDR_MPU, running ? 0 : 0x40, error); } static gboolean fu_parade_lspcon_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE(device); return fu_parade_lspcon_set_mpu_running(self, FALSE, error); } static gboolean fu_parade_lspcon_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE(device); return fu_parade_lspcon_set_mpu_running(self, TRUE, error); } static GBytes * fu_parade_lspcon_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuParadeLspconDevice *self = FU_PARADE_LSPCON_DEVICE(device); g_autofree guint8 *data = g_malloc0(FLASH_BLOCK_SIZE); if (!fu_parade_lspcon_flash_read(self, self->active_partition * FLASH_BLOCK_SIZE, data, FLASH_BLOCK_SIZE, progress, error)) return NULL; return g_bytes_new_take(g_steal_pointer(&data), FLASH_BLOCK_SIZE); } static void fu_parade_lspcon_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_parade_lspcon_device_class_init(FuParadeLspconDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *klass_object = G_OBJECT_CLASS(klass); klass_object->finalize = fu_parade_lspcon_device_finalize; klass_device->set_quirk_kv = fu_parade_lspcon_device_set_quirk_kv; klass_device->probe = fu_parade_lspcon_device_probe; klass_device->setup = fu_parade_lspcon_device_reload; klass_device->open = fu_parade_lspcon_device_open; klass_device->reload = fu_parade_lspcon_device_reload; klass_device->detach = fu_parade_lspcon_device_detach; klass_device->write_firmware = fu_parade_lspcon_device_write_firmware; klass_device->attach = fu_parade_lspcon_device_attach; klass_device->dump_firmware = fu_parade_lspcon_device_dump_firmware; klass_device->set_progress = fu_parade_lspcon_device_set_progress; } fwupd-1.9.16/plugins/parade-lspcon/fu-parade-lspcon-device.h000066400000000000000000000005501460375044200237210ustar00rootroot00000000000000/* * Copyright (C) 2021 Peter Marheine * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_PARADE_LSPCON_DEVICE (fu_parade_lspcon_device_get_type()) G_DECLARE_FINAL_TYPE(FuParadeLspconDevice, fu_parade_lspcon_device, FU, PARADE_LSPCON_DEVICE, FuI2cDevice) fwupd-1.9.16/plugins/parade-lspcon/fu-parade-lspcon-plugin.c000066400000000000000000000016711460375044200237600ustar00rootroot00000000000000/* * Copyright (C) 2021 Peter Marheine * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-parade-lspcon-device.h" #include "fu-parade-lspcon-plugin.h" struct _FuParadeLspconPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuParadeLspconPlugin, fu_parade_lspcon_plugin, FU_TYPE_PLUGIN) static void fu_parade_lspcon_plugin_init(FuParadeLspconPlugin *self) { } static void fu_parade_lspcon_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "ParadeLspconAuxDeviceName"); fu_plugin_add_udev_subsystem(plugin, "i2c"); fu_plugin_add_device_gtype(plugin, FU_TYPE_PARADE_LSPCON_DEVICE); } static void fu_parade_lspcon_plugin_class_init(FuParadeLspconPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_parade_lspcon_plugin_constructed; } fwupd-1.9.16/plugins/parade-lspcon/fu-parade-lspcon-plugin.h000066400000000000000000000004301460375044200237550ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuParadeLspconPlugin, fu_parade_lspcon_plugin, FU, PARADE_LSPCON_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/parade-lspcon/meson.build000066400000000000000000000011471460375044200213200ustar00rootroot00000000000000if get_option('plugin_parade_lspcon').require(gudev.found(), error_message: 'gudev is needed for plugin_parade_lspcon').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginParadeLspcon"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('parade-lspcon.quirk') plugin_builtins += static_library('fu_plugin_parade_lspcon', sources: [ 'fu-parade-lspcon-device.c', 'fu-parade-lspcon-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/parade-lspcon/parade-lspcon.quirk000066400000000000000000000004071460375044200227610ustar00rootroot00000000000000# Parade PS175 [I2C\NAME_1AF80175:00] Plugin = parade_lspcon Name = PS175 # "Puff" Chromeboxes [I2C\NAME_1AF80175:00&FAMILY_Google_Hatch] ParadeLspconAuxDeviceName = DPDDC-B [I2C\NAME_1AF80175:00&FAMILY_Google_Puff] ParadeLspconAuxDeviceName = AUX B/DDI B/PHY B fwupd-1.9.16/plugins/pci-bcr/000077500000000000000000000000001460375044200157425ustar00rootroot00000000000000fwupd-1.9.16/plugins/pci-bcr/README.md000066400000000000000000000006361460375044200172260ustar00rootroot00000000000000--- title: Plugin: PCI BCR — BIOS Control Register --- ## Introduction This plugin checks if the system SPI chip is locked. The result will be stored in an security attribute for HSI. ## External Interface Access This plugin requires read access to the config space of PCI devices (`/sys/class/pci_bus/*/device/config`) ## Version Considerations This plugin has been available since fwupd version `1.5.0`. fwupd-1.9.16/plugins/pci-bcr/config000066400000000000000000000001001460375044200171210ustar00rootroot00000000000000P1."fwupd-1.9.16/plugins/pci-bcr/fu-pci-bcr-plugin.c000066400000000000000000000163421460375044200213370ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-pci-bcr-plugin.h" struct _FuPciBcrPlugin { FuPlugin parent_instance; gboolean has_device; guint8 bcr_addr; guint8 bcr; }; G_DEFINE_TYPE(FuPciBcrPlugin, fu_pci_bcr_plugin, FU_TYPE_PLUGIN) #define BCR_WPD (1 << 0) #define BCR_BLE (1 << 1) #define BCR_SMM_BWP (1 << 5) static void fu_pci_bcr_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuPciBcrPlugin *self = FU_PCI_BCR_PLUGIN(plugin); fu_string_append_kb(str, idt, "HasDevice", self->has_device); fu_string_append_kx(str, idt, "BcrAddr", self->bcr_addr); fu_string_append_kx(str, idt, "Bcr", self->bcr); } static void fu_pci_bcr_plugin_set_updatable(FuPlugin *plugin, FuDevice *dev) { FuPciBcrPlugin *self = FU_PCI_BCR_PLUGIN(plugin); if ((self->bcr & BCR_WPD) == 0 && (self->bcr & BCR_BLE) > 0) { fu_device_inhibit(dev, "bcr-locked", "BIOS locked"); } else { fu_device_uninhibit(dev, "bcr-locked"); } } static void fu_pci_bcr_plugin_device_registered(FuPlugin *plugin, FuDevice *dev) { FuPciBcrPlugin *self = FU_PCI_BCR_PLUGIN(plugin); if (g_strcmp0(fu_device_get_plugin(dev), "cpu") == 0 || g_strcmp0(fu_device_get_plugin(dev), "flashrom") == 0) { guint tmp = fu_device_get_metadata_integer(dev, "PciBcrAddr"); if (tmp != G_MAXUINT && self->bcr_addr != tmp) { g_info("overriding BCR addr from 0x%02x to 0x%02x", self->bcr_addr, tmp); self->bcr_addr = tmp; } } if (g_strcmp0(fu_device_get_plugin(dev), "flashrom") == 0 && fu_device_has_internal_flag(dev, FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE)) { /* PCI\VEN_8086 added first */ if (self->has_device) { fu_pci_bcr_plugin_set_updatable(plugin, dev); return; } fu_plugin_cache_add(plugin, "main-system-firmware", dev); } } static void fu_plugin_add_security_attr_bioswe(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPciBcrPlugin *self = FU_PCI_BCR_PLUGIN(plugin); FuDevice *msf_device = fu_plugin_cache_lookup(plugin, "main-system-firmware"); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_SPI_BIOSWE); if (msf_device != NULL) fwupd_security_attr_add_guids(attr, fu_device_get_guids(msf_device)); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fu_security_attrs_append(attrs, attr); /* no device */ if (!self->has_device) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } /* load file */ if ((self->bcr & BCR_WPD) == 1) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_plugin_add_security_attr_ble(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPciBcrPlugin *self = FU_PCI_BCR_PLUGIN(plugin); FuDevice *msf_device = fu_plugin_cache_lookup(plugin, "main-system-firmware"); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_SPI_BLE); if (msf_device != NULL) fwupd_security_attr_add_guids(attr, fu_device_get_guids(msf_device)); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); /* no device */ if (!self->has_device) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } /* load file */ if ((self->bcr & BCR_BLE) == 0) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_plugin_add_security_attr_smm_bwp(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPciBcrPlugin *self = FU_PCI_BCR_PLUGIN(plugin); FuDevice *msf_device = fu_plugin_cache_lookup(plugin, "main-system-firmware"); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_SPI_SMM_BWP); if (msf_device != NULL) fwupd_security_attr_add_guids(attr, fu_device_get_guids(msf_device)); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); fu_security_attrs_append(attrs, attr); /* no device */ if (!self->has_device) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } /* load file */ if ((self->bcr & BCR_SMM_BWP) == 0) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static gboolean fu_pci_bcr_plugin_backend_device_added(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { FuPciBcrPlugin *self = FU_PCI_BCR_PLUGIN(plugin); FuDevice *device_msf; g_autoptr(FuDeviceLocker) locker = NULL; /* not supported */ if (self->bcr_addr == 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "BCR not supported on this platform"); return FALSE; } /* interesting device? */ if (!FU_IS_UDEV_DEVICE(device)) return TRUE; if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "pci") != 0) return TRUE; /* open the config */ fu_udev_device_set_flags(FU_UDEV_DEVICE(device), FU_UDEV_DEVICE_FLAG_USE_CONFIG); if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "pci", error)) return FALSE; locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; /* grab BIOS Control Register */ if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), self->bcr_addr, &self->bcr, 1, error)) { g_prefix_error(error, "could not read BCR: "); return FALSE; } /* main-system-firmware device added first, probably from flashrom */ device_msf = fu_plugin_cache_lookup(plugin, "main-system-firmware"); if (device_msf != NULL) fu_pci_bcr_plugin_set_updatable(plugin, device_msf); /* success */ self->has_device = TRUE; return TRUE; } static void fu_pci_bcr_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { /* only Intel */ if (fu_cpu_get_vendor() != FU_CPU_VENDOR_INTEL) return; /* add attrs */ fu_plugin_add_security_attr_bioswe(plugin, attrs); fu_plugin_add_security_attr_ble(plugin, attrs); fu_plugin_add_security_attr_smm_bwp(plugin, attrs); } static void fu_pci_bcr_plugin_init(FuPciBcrPlugin *self) { /* this is true except for some Atoms */ self->bcr_addr = 0xdc; } static void fu_pci_bcr_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "PciBcrAddr"); fu_plugin_add_udev_subsystem(plugin, "pci"); } static void fu_pci_bcr_plugin_class_init(FuPciBcrPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_pci_bcr_plugin_constructed; plugin_class->to_string = fu_pci_bcr_plugin_to_string; plugin_class->add_security_attrs = fu_pci_bcr_plugin_add_security_attrs; plugin_class->device_registered = fu_pci_bcr_plugin_device_registered; plugin_class->backend_device_added = fu_pci_bcr_plugin_backend_device_added; } fwupd-1.9.16/plugins/pci-bcr/fu-pci-bcr-plugin.h000066400000000000000000000003521460375044200213360ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuPciBcrPlugin, fu_pci_bcr_plugin, FU, PCI_BCR_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/pci-bcr/meson.build000066400000000000000000000005641460375044200201110ustar00rootroot00000000000000if hsi cargs = ['-DG_LOG_DOMAIN="FuPluginPciBcr"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('pci-bcr.quirk') plugin_builtins += static_library('fu_plugin_pci_bcr', sources: [ 'fu-pci-bcr-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/pci-bcr/pci-bcr.quirk000066400000000000000000000003421460375044200203350ustar00rootroot00000000000000# ISA bridge i.e. 00:1F.0 # -> drivers/mfd/lpc_ich.c [PCI\VEN_8086&CLASS_060100] Plugin = pci_bcr # PCI devices, i.e. 00:1F.5 # -> drivers/mtd/spi-nor/controllers/intel-spi-pci.c [PCI\VEN_8086&CLASS_0C8000] Plugin = pci_bcr fwupd-1.9.16/plugins/pci-mei/000077500000000000000000000000001460375044200157465ustar00rootroot00000000000000fwupd-1.9.16/plugins/pci-mei/README.md000066400000000000000000000006061460375044200172270ustar00rootroot00000000000000--- title: Plugin: PCI MEI --- ## Introduction This plugin checks if the ME is in Manufacturing Mode. The result will be stored in an security attribute for HSI. ## External Interface Access This plugin requires read access to the config space of PCI devices (`/sys/class/pci_bus/*/device/config`) ## Version Considerations This plugin has been available since fwupd version `1.5.0`. fwupd-1.9.16/plugins/pci-mei/fu-mei-common.c000066400000000000000000000253721460375044200205730ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-mei-common.h" #include "fu-mei-struct.h" static gint fu_mei_common_cmp_version(FuMeiVersion *vers1, FuMeiVersion *vers2) { guint16 vers1buf[] = { vers1->major, vers1->minor, vers1->hotfix, vers1->buildno, }; guint16 vers2buf[] = { vers2->major, vers2->minor, vers2->hotfix, vers2->buildno, }; for (guint i = 0; i < 4; i++) { if (vers1buf[i] < vers2buf[i]) return -1; if (vers1buf[i] > vers2buf[i]) return 1; } return 0; } FuMeiIssue fu_mei_common_is_csme_vulnerable(FuMeiVersion *vers) { struct { guint8 major_eq; guint8 minor_eq; guint8 hotfix_ge; } verdata[] = {{11, 8, 92}, {11, 12, 92}, {11, 22, 92}, {12, 0, 90}, {13, 0, 60}, {13, 30, 30}, {13, 50, 20}, {14, 1, 65}, {14, 5, 45}, {15, 0, 40}, {15, 40, 20}, {0, 0, 0}}; for (guint i = 0; verdata[i].major_eq != 0; i++) { if (vers->major == verdata[i].major_eq && vers->minor == verdata[i].minor_eq) { return vers->hotfix >= verdata[i].hotfix_ge ? FU_MEI_ISSUE_PATCHED : FU_MEI_ISSUE_VULNERABLE; } } return FU_MEI_ISSUE_NOT_VULNERABLE; } FuMeiIssue fu_mei_common_is_txe_vulnerable(FuMeiVersion *vers) { struct { guint8 major_eq; guint8 minor_eq; guint8 hotfix_ge; } verdata[] = {{3, 1, 92}, {4, 0, 45}, {0, 0, 0}}; for (guint i = 0; verdata[i].major_eq != 0; i++) { if (vers->major == verdata[i].major_eq && vers->minor == verdata[i].minor_eq) { return vers->hotfix >= verdata[i].hotfix_ge ? FU_MEI_ISSUE_PATCHED : FU_MEI_ISSUE_VULNERABLE; } } return FU_MEI_ISSUE_NOT_VULNERABLE; } FuMeiIssue fu_mei_common_is_sps_vulnerable(FuMeiVersion *vers) { if (vers->major == 3 || vers->major > 5) return FU_MEI_ISSUE_NOT_VULNERABLE; if (vers->major == 4) { if (vers->hotfix < 44) return FU_MEI_ISSUE_VULNERABLE; if (vers->platform == 0xA) { /* Purley */ FuMeiVersion ver2 = { .major = 4, .minor = 1, .hotfix = 4, .buildno = 339, }; if (fu_mei_common_cmp_version(vers, &ver2) < 0) return FU_MEI_ISSUE_VULNERABLE; } else if (vers->platform == 0xE) { /* Bakerville */ FuMeiVersion ver2 = { .major = 4, .minor = 0, .hotfix = 4, .buildno = 112, }; if (fu_mei_common_cmp_version(vers, &ver2) < 0) return FU_MEI_ISSUE_VULNERABLE; } else if (vers->platform == 0xB) { /* Harrisonville */ FuMeiVersion ver2 = { .major = 4, .minor = 0, .hotfix = 4, .buildno = 193, }; if (fu_mei_common_cmp_version(vers, &ver2) < 0) return FU_MEI_ISSUE_VULNERABLE; } else if (vers->platform == 0x9) { /* Greenlow */ FuMeiVersion ver2 = { .major = 4, .minor = 1, .hotfix = 4, .buildno = 88, }; if (vers->minor < 1) return FU_MEI_ISSUE_NOT_VULNERABLE; if (fu_mei_common_cmp_version(vers, &ver2) < 0) return FU_MEI_ISSUE_VULNERABLE; } else if (vers->platform == 0xD) { /* MonteVista */ FuMeiVersion ver2 = { .major = 4, .minor = 8, .hotfix = 4, .buildno = 51, }; if (fu_mei_common_cmp_version(vers, &ver2) < 0) return FU_MEI_ISSUE_VULNERABLE; } return FU_MEI_ISSUE_NOT_VULNERABLE; } if (vers->major == 5) { if (vers->platform == 0x10) { /* Mehlow */ FuMeiVersion ver2 = {5, 1, 3, 89}; if (fu_mei_common_cmp_version(vers, &ver2) < 0) return FU_MEI_ISSUE_VULNERABLE; } return FU_MEI_ISSUE_NOT_VULNERABLE; } return FU_MEI_ISSUE_PATCHED; } void fu_mei_hfsts1_to_string(FuMeiHfsts1 hfsts1, guint idt, GString *str) { fu_string_append(str, idt, "WorkingState", fu_me_hfs_cws_to_string(hfsts1.fields.working_state)); fu_string_append_kb(str, idt, "MfgMode", hfsts1.fields.mfg_mode); fu_string_append_kb(str, idt, "FptBad", hfsts1.fields.fpt_bad); fu_string_append(str, idt, "OperationState", fu_me_hfs_state_to_string(hfsts1.fields.operation_state)); fu_string_append_kb(str, idt, "FwInitComplete", hfsts1.fields.fw_init_complete); fu_string_append_kb(str, idt, "FtBupLdFlr", hfsts1.fields.ft_bup_ld_flr); fu_string_append_kb(str, idt, "UpdateInProgress", hfsts1.fields.update_in_progress); fu_string_append(str, idt, "ErrorCode", fu_me_hfs_error_to_string(hfsts1.fields.error_code)); fu_string_append(str, idt, "OperationMode", fu_me_hfs_mode_to_string(hfsts1.fields.operation_mode)); fu_string_append_kx(str, idt, "ResetCount", hfsts1.fields.reset_count); fu_string_append_kb(str, idt, "BootOptions_present", hfsts1.fields.boot_options_present); fu_string_append_kb(str, idt, "BistFinished", hfsts1.fields.bist_finished); fu_string_append_kb(str, idt, "BistTestState", hfsts1.fields.bist_test_state); fu_string_append_kb(str, idt, "BistResetRequest", hfsts1.fields.bist_reset_request); fu_string_append_kx(str, idt, "CurrentPowerSource", hfsts1.fields.current_power_source); fu_string_append_kb(str, idt, "D3SupportValid", hfsts1.fields.d3_support_valid); fu_string_append_kb(str, idt, "D0i3SupportValid", hfsts1.fields.d0i3_support_valid); } void fu_mei_hfsts2_to_string(FuMeiHfsts2 hfsts2, guint idt, GString *str) { fu_string_append_kb(str, idt, "NftpLoadFailure", hfsts2.fields.nftp_load_failure); fu_string_append_kx(str, idt, "IccProgStatus", hfsts2.fields.icc_prog_status); fu_string_append_kb(str, idt, "InvokeMebx", hfsts2.fields.invoke_mebx); fu_string_append_kb(str, idt, "CpuReplaced", hfsts2.fields.cpu_replaced); fu_string_append_kb(str, idt, "Rsvd0", hfsts2.fields.rsvd0); fu_string_append_kb(str, idt, "MfsFailure", hfsts2.fields.mfs_failure); fu_string_append_kb(str, idt, "WarmResetRqst", hfsts2.fields.warm_reset_rqst); fu_string_append_kb(str, idt, "CpuReplacedValid", hfsts2.fields.cpu_replaced_valid); fu_string_append_kb(str, idt, "LowPowerState", hfsts2.fields.low_power_state); fu_string_append_kb(str, idt, "MePowerGate", hfsts2.fields.me_power_gate); fu_string_append_kb(str, idt, "IpuNeeded", hfsts2.fields.ipu_needed); fu_string_append_kb(str, idt, "ForcedSafeBoot", hfsts2.fields.forced_safe_boot); fu_string_append_kx(str, idt, "Rsvd1", hfsts2.fields.rsvd1); fu_string_append_kb(str, idt, "ListenerChange", hfsts2.fields.listener_change); fu_string_append_kx(str, idt, "StatusData", hfsts2.fields.status_data); fu_string_append_kx(str, idt, "CurrentPmevent", hfsts2.fields.current_pmevent); fu_string_append_kx(str, idt, "Phase", hfsts2.fields.phase); } void fu_mei_hfsts3_to_string(FuMeiHfsts3 hfsts3, guint idt, GString *str) { fu_string_append_kx(str, idt, "Chunk0", hfsts3.fields.chunk0); fu_string_append_kx(str, idt, "Chunk1", hfsts3.fields.chunk1); fu_string_append_kx(str, idt, "Chunk2", hfsts3.fields.chunk2); fu_string_append_kx(str, idt, "Chunk3", hfsts3.fields.chunk3); fu_string_append_kx(str, idt, "FwSku", hfsts3.fields.fw_sku); fu_string_append_kb(str, idt, "EncryptKeyCheck", hfsts3.fields.encrypt_key_check); fu_string_append_kb(str, idt, "PchConfigChange", hfsts3.fields.pch_config_change); fu_string_append_kb(str, idt, "IbbVerificationResult", hfsts3.fields.ibb_verification_result); fu_string_append_kb(str, idt, "IbbVerificationDone", hfsts3.fields.ibb_verification_done); fu_string_append_kx(str, idt, "Reserved11", hfsts3.fields.reserved_11); fu_string_append_kx(str, idt, "ActualIbbSize", hfsts3.fields.actual_ibb_size * 1024); fu_string_append_ku(str, idt, "NumberOfChunks", hfsts3.fields.number_of_chunks); fu_string_append_kb(str, idt, "EncryptKeyOverride", hfsts3.fields.encrypt_key_override); fu_string_append_kb(str, idt, "PowerDownMitigation", hfsts3.fields.power_down_mitigation); } void fu_mei_hfsts4_to_string(FuMeiHfsts4 hfsts4, guint idt, GString *str) { fu_string_append_kx(str, idt, "Rsvd0", hfsts4.fields.rsvd0); fu_string_append_kb(str, idt, "EnforcementFlow", hfsts4.fields.enforcement_flow); fu_string_append_kb(str, idt, "SxResumeType", hfsts4.fields.sx_resume_type); fu_string_append_kb(str, idt, "Rsvd1", hfsts4.fields.rsvd1); fu_string_append_kb(str, idt, "TpmsDisconnected", hfsts4.fields.tpms_disconnected); fu_string_append_kb(str, idt, "Rvsd2", hfsts4.fields.rvsd2); fu_string_append_kb(str, idt, "FwstsValid", hfsts4.fields.fwsts_valid); fu_string_append_kb(str, idt, "BootGuardSelfTest", hfsts4.fields.boot_guard_self_test); fu_string_append_kx(str, idt, "Rsvd3", hfsts4.fields.rsvd3); } void fu_mei_hfsts5_to_string(FuMeiHfsts5 hfsts5, guint idt, GString *str) { fu_string_append_kb(str, idt, "AcmActive", hfsts5.fields.acm_active); fu_string_append_kb(str, idt, "Valid", hfsts5.fields.valid); fu_string_append_kb(str, idt, "ResultCodeSource", hfsts5.fields.result_code_source); fu_string_append_kx(str, idt, "ErrorStatusCode", hfsts5.fields.error_status_code); fu_string_append_kx(str, idt, "AcmDoneSts", hfsts5.fields.acm_done_sts); fu_string_append_kx(str, idt, "TimeoutCount", hfsts5.fields.timeout_count); fu_string_append_kb(str, idt, "ScrtmIndicator", hfsts5.fields.scrtm_indicator); fu_string_append_kx(str, idt, "IncBootGuardAcm", hfsts5.fields.inc_boot_guard_acm); fu_string_append_kx(str, idt, "IncKeyManifest", hfsts5.fields.inc_key_manifest); fu_string_append_kx(str, idt, "IncBootPolicy", hfsts5.fields.inc_boot_policy); fu_string_append_kx(str, idt, "Rsvd0", hfsts5.fields.rsvd0); fu_string_append_kb(str, idt, "StartEnforcement", hfsts5.fields.start_enforcement); } void fu_mei_hfsts6_to_string(FuMeiHfsts6 hfsts6, guint idt, GString *str) { fu_string_append_kb(str, idt, "ForceBootGuardAcm", hfsts6.fields.force_boot_guard_acm); fu_string_append_kb(str, idt, "CpuDebugDisable", hfsts6.fields.cpu_debug_disable); fu_string_append_kb(str, idt, "BspInitDisable", hfsts6.fields.bsp_init_disable); fu_string_append_kb(str, idt, "ProtectBiosEnv", hfsts6.fields.protect_bios_env); fu_string_append_kx(str, idt, "Rsvd0", hfsts6.fields.rsvd0); fu_string_append_kx(str, idt, "ErrorEnforcePolicy", hfsts6.fields.error_enforce_policy); fu_string_append_kb(str, idt, "MeasuredBoot", hfsts6.fields.measured_boot); fu_string_append_kb(str, idt, "VerifiedBoot", hfsts6.fields.verified_boot); fu_string_append_kx(str, idt, "BootGuardAcmsvn", hfsts6.fields.boot_guard_acmsvn); fu_string_append_kx(str, idt, "Kmsvn", hfsts6.fields.kmsvn); fu_string_append_kx(str, idt, "Bpmsvn", hfsts6.fields.bpmsvn); fu_string_append_kx(str, idt, "KeyManifestId", hfsts6.fields.key_manifest_id); fu_string_append_kb(str, idt, "BootPolicyStatus", hfsts6.fields.boot_policy_status); fu_string_append_kb(str, idt, "Error", hfsts6.fields.error); fu_string_append_kb(str, idt, "BootGuardDisable", hfsts6.fields.boot_guard_disable); fu_string_append_kb(str, idt, "FpfDisable", hfsts6.fields.fpf_disable); fu_string_append_kb(str, idt, "FpfSocLock", hfsts6.fields.fpf_soc_lock); fu_string_append_kb(str, idt, "TxtSupport", hfsts6.fields.txt_support); } fwupd-1.9.16/plugins/pci-mei/fu-mei-common.h000066400000000000000000000104041460375044200205660ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef enum { FU_MEI_ISSUE_UNKNOWN, FU_MEI_ISSUE_NOT_VULNERABLE, FU_MEI_ISSUE_VULNERABLE, FU_MEI_ISSUE_PATCHED, } FuMeiIssue; typedef struct { guint8 platform; guint8 major; guint8 minor; guint8 hotfix; guint16 buildno; } FuMeiVersion; /* Host Firmware Status register 1 */ typedef union { guint32 data; struct { guint32 working_state : 4; guint32 mfg_mode : 1; guint32 fpt_bad : 1; guint32 operation_state : 3; guint32 fw_init_complete : 1; guint32 ft_bup_ld_flr : 1; guint32 update_in_progress : 1; guint32 error_code : 4; guint32 operation_mode : 4; guint32 reset_count : 4; guint32 boot_options_present : 1; guint32 bist_finished : 1; guint32 bist_test_state : 1; guint32 bist_reset_request : 1; guint32 current_power_source : 2; guint32 d3_support_valid : 1; guint32 d0i3_support_valid : 1; } __attribute__((packed)) fields; } FuMeiHfsts1; /* Host Firmware Status Register 2 */ typedef union { guint32 data; struct { guint32 nftp_load_failure : 1; guint32 icc_prog_status : 2; guint32 invoke_mebx : 1; guint32 cpu_replaced : 1; guint32 rsvd0 : 1; guint32 mfs_failure : 1; guint32 warm_reset_rqst : 1; guint32 cpu_replaced_valid : 1; guint32 low_power_state : 1; guint32 me_power_gate : 1; guint32 ipu_needed : 1; guint32 forced_safe_boot : 1; guint32 rsvd1 : 2; guint32 listener_change : 1; guint32 status_data : 8; guint32 current_pmevent : 4; guint32 phase : 4; } __attribute__((packed)) fields; } FuMeiHfsts2; /* Host Firmware Status Register 3 */ typedef union { guint32 data; struct { guint32 chunk0 : 1; guint32 chunk1 : 1; guint32 chunk2 : 1; guint32 chunk3 : 1; guint32 fw_sku : 3; guint32 encrypt_key_check : 1; guint32 pch_config_change : 1; guint32 ibb_verification_result : 1; guint32 ibb_verification_done : 1; guint32 reserved_11 : 3; guint32 actual_ibb_size : 14; guint32 number_of_chunks : 2; guint32 encrypt_key_override : 1; guint32 power_down_mitigation : 1; } __attribute__((packed)) fields; } FuMeiHfsts3; /* Host Firmware Status Register 4 */ typedef union { guint32 data; struct { guint32 rsvd0 : 9; guint32 enforcement_flow : 1; guint32 sx_resume_type : 1; guint32 rsvd1 : 1; guint32 tpms_disconnected : 1; guint32 rvsd2 : 1; guint32 fwsts_valid : 1; guint32 boot_guard_self_test : 1; guint32 rsvd3 : 16; } __attribute__((packed)) fields; } FuMeiHfsts4; /* Host Firmware Status Register 5 */ typedef union { guint32 data; struct { guint32 acm_active : 1; guint32 valid : 1; guint32 result_code_source : 1; guint32 error_status_code : 5; guint32 acm_done_sts : 1; guint32 timeout_count : 7; guint32 scrtm_indicator : 1; guint32 inc_boot_guard_acm : 4; guint32 inc_key_manifest : 4; guint32 inc_boot_policy : 4; guint32 rsvd0 : 2; guint32 start_enforcement : 1; } __attribute__((packed)) fields; } FuMeiHfsts5; /* Host Firmware Status Register 6 */ typedef union { guint32 data; struct { guint32 force_boot_guard_acm : 1; guint32 cpu_debug_disable : 1; guint32 bsp_init_disable : 1; guint32 protect_bios_env : 1; guint32 rsvd0 : 2; guint32 error_enforce_policy : 2; guint32 measured_boot : 1; guint32 verified_boot : 1; guint32 boot_guard_acmsvn : 4; guint32 kmsvn : 4; guint32 bpmsvn : 4; guint32 key_manifest_id : 4; guint32 boot_policy_status : 1; guint32 error : 1; guint32 boot_guard_disable : 1; guint32 fpf_disable : 1; guint32 fpf_soc_lock : 1; guint32 txt_support : 1; } __attribute__((packed)) fields; } FuMeiHfsts6; FuMeiIssue fu_mei_common_is_csme_vulnerable(FuMeiVersion *vers); FuMeiIssue fu_mei_common_is_txe_vulnerable(FuMeiVersion *vers); FuMeiIssue fu_mei_common_is_sps_vulnerable(FuMeiVersion *vers); void fu_mei_hfsts1_to_string(FuMeiHfsts1 hfsts1, guint idt, GString *str); void fu_mei_hfsts2_to_string(FuMeiHfsts2 hfsts2, guint idt, GString *str); void fu_mei_hfsts3_to_string(FuMeiHfsts3 hfsts3, guint idt, GString *str); void fu_mei_hfsts4_to_string(FuMeiHfsts4 hfsts4, guint idt, GString *str); void fu_mei_hfsts5_to_string(FuMeiHfsts5 hfsts5, guint idt, GString *str); void fu_mei_hfsts6_to_string(FuMeiHfsts6 hfsts6, guint idt, GString *str); fwupd-1.9.16/plugins/pci-mei/fu-mei.rs000066400000000000000000000020011460375044200174670ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum MeiFamily { Unknown, Sps, Txe, Me, Csme, } # HFS1[3:0] Current Working State Values #[derive(ToString)] enum MeHfsCws { Reset, Initializing, Recovery, Test, Disabled, Normal, Wait, Transition, Invalid, } # HFS1[8:6] Current Operation State Values #[derive(ToString)] enum MeHfsState { Preboot, M0WithUma = 1, M3WithoutUma = 4, M0WithoutUma = 5, BringUp = 6, Error = 7, } # HFS[19:16] Current Operation Mode Values #[derive(ToString)] enum MeHfsMode { Normal, Debug = 2, Disable, OverrideJumper, OverrideMei, Unknown6, MaybeSps, } # HFS[15:12] Error Code Values #[derive(ToString)] enum MeHfsError { NoError, UncategorizedFailure, Disabled, ImageFailure, DebugFailure, } enum MeHfsEnforcementPolicy { Nothing, ShutdownTo, ShutdownNow, Shutdown_30mins, } fwupd-1.9.16/plugins/pci-mei/fu-pci-mei-plugin.c000066400000000000000000000447441460375044200213560ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-mei-common.h" #include "fu-mei-struct.h" #include "fu-pci-mei-plugin.h" struct _FuPciMeiPlugin { FuPlugin parent_instance; FuDevice *pci_device; FuMeiHfsts1 hfsts1; FuMeiHfsts2 hfsts2; FuMeiHfsts3 hfsts3; FuMeiHfsts4 hfsts4; FuMeiHfsts5 hfsts5; FuMeiHfsts6 hfsts6; FuMeiFamily family; FuMeiVersion vers; FuMeiIssue issue; }; G_DEFINE_TYPE(FuPciMeiPlugin, fu_pci_mei_plugin, FU_TYPE_PLUGIN) #define PCI_CFG_HFS_1 0x40 #define PCI_CFG_HFS_2 0x48 #define PCI_CFG_HFS_3 0x60 #define PCI_CFG_HFS_4 0x64 #define PCI_CFG_HFS_5 0x68 #define PCI_CFG_HFS_6 0x6c static void fu_pci_mei_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuPciMeiPlugin *self = FU_PCI_MEI_PLUGIN(plugin); fu_string_append(str, idt, "HFSTS1", NULL); fu_mei_hfsts1_to_string(self->hfsts1, idt + 1, str); fu_string_append(str, idt, "HFSTS2", NULL); fu_mei_hfsts2_to_string(self->hfsts2, idt + 1, str); fu_string_append(str, idt, "HFSTS3", NULL); fu_mei_hfsts3_to_string(self->hfsts3, idt + 1, str); fu_string_append(str, idt, "HFSTS4", NULL); fu_mei_hfsts4_to_string(self->hfsts4, idt + 1, str); fu_string_append(str, idt, "HFSTS5", NULL); fu_mei_hfsts5_to_string(self->hfsts5, idt + 1, str); fu_string_append(str, idt, "HFSTS6", NULL); fu_mei_hfsts6_to_string(self->hfsts6, idt + 1, str); } static FuMeiFamily fu_mei_detect_family(FuPlugin *plugin) { FuPciMeiPlugin *self = FU_PCI_MEI_PLUGIN(plugin); guint8 ver = self->vers.major; if (ver == 1 || ver == 2) { if (self->hfsts1.fields.operation_mode == 0xf) return FU_MEI_FAMILY_SPS; return FU_MEI_FAMILY_TXE; } if (ver == 3 || ver == 4 || ver == 5) return FU_MEI_FAMILY_TXE; if (ver == 6 || ver == 7 || ver == 8 || ver == 9 || ver == 10) return FU_MEI_FAMILY_ME; if (ver == 11 || ver == 12 || ver == 13 || ver == 14 || ver == 15 || ver == 16) return FU_MEI_FAMILY_CSME; return FU_MEI_FAMILY_UNKNOWN; } static gboolean fu_mei_parse_fwvers(FuPlugin *plugin, const gchar *fwvers, GError **error) { FuPciMeiPlugin *self = FU_PCI_MEI_PLUGIN(plugin); guint64 tmp64 = 0; g_auto(GStrv) lines = NULL; g_auto(GStrv) sections = NULL; g_auto(GStrv) split = NULL; /* we only care about the first version */ lines = g_strsplit(fwvers, "\n", -1); if (g_strv_length(lines) < 1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "expected data, got %s", fwvers); return FALSE; } /* split platform : version */ sections = g_strsplit(lines[0], ":", -1); if (g_strv_length(sections) != 2) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "expected platform:major.minor.micro.build, got %s", lines[0]); return FALSE; } /* parse platform and versions */ if (!fu_strtoull(sections[0], &tmp64, 0, G_MAXUINT8, error)) { g_prefix_error(error, "failed to process platform version %s: ", sections[0]); return FALSE; } self->vers.platform = tmp64; split = g_strsplit(sections[1], ".", -1); if (g_strv_length(split) != 4) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "expected major.minor.micro.build, got %s", sections[1]); return FALSE; } if (!fu_strtoull(split[0], &tmp64, 0, G_MAXUINT8, error)) { g_prefix_error(error, "failed to process major version %s: ", split[0]); return FALSE; } self->vers.major = tmp64; if (!fu_strtoull(split[1], &tmp64, 0, G_MAXUINT8, error)) { g_prefix_error(error, "failed to process minor version %s: ", split[1]); return FALSE; } self->vers.minor = tmp64; if (!fu_strtoull(split[2], &tmp64, 0, G_MAXUINT8, error)) { g_prefix_error(error, "failed to process hotfix version %s: ", split[2]); return FALSE; } self->vers.hotfix = tmp64; if (!fu_strtoull(split[3], &tmp64, 0, G_MAXUINT16, error)) { g_prefix_error(error, "failed to process buildno version %s: ", split[3]); return FALSE; } self->vers.buildno = tmp64; /* check the AMT version for issues using the data from: * https://downloadcenter.intel.com/download/28632 */ self->family = fu_mei_detect_family(plugin); if (self->family == FU_MEI_FAMILY_CSME) self->issue = fu_mei_common_is_csme_vulnerable(&self->vers); else if (self->family == FU_MEI_FAMILY_TXE) self->issue = fu_mei_common_is_txe_vulnerable(&self->vers); else if (self->family == FU_MEI_FAMILY_SPS) self->issue = fu_mei_common_is_sps_vulnerable(&self->vers); g_debug("%s version parsed as %u.%u.%u", fu_mei_family_to_string(self->family), self->vers.major, self->vers.minor, self->vers.hotfix); return TRUE; } static gboolean fu_pci_mei_plugin_backend_device_added(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { FuPciMeiPlugin *self = FU_PCI_MEI_PLUGIN(plugin); const gchar *fwvers; guint8 buf[4] = {0x0}; g_autoptr(FuDeviceLocker) locker = NULL; /* interesting device? */ if (!FU_IS_UDEV_DEVICE(device)) return TRUE; if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)), "pci") != 0) return TRUE; /* open the config */ fu_udev_device_set_flags(FU_UDEV_DEVICE(device), FU_UDEV_DEVICE_FLAG_USE_CONFIG); if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "pci", error)) return FALSE; locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; /* grab MEI config registers */ if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), PCI_CFG_HFS_1, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read HFS1: "); return FALSE; } self->hfsts1.data = fu_memread_uint32(buf, G_LITTLE_ENDIAN); if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), PCI_CFG_HFS_2, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read HFS2: "); return FALSE; } self->hfsts2.data = fu_memread_uint32(buf, G_LITTLE_ENDIAN); if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), PCI_CFG_HFS_3, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read HFS3: "); return FALSE; } self->hfsts3.data = fu_memread_uint32(buf, G_LITTLE_ENDIAN); if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), PCI_CFG_HFS_4, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read HFS4: "); return FALSE; } self->hfsts4.data = fu_memread_uint32(buf, G_LITTLE_ENDIAN); if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), PCI_CFG_HFS_5, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read HFS5: "); return FALSE; } self->hfsts5.data = fu_memread_uint32(buf, G_LITTLE_ENDIAN); if (!fu_udev_device_pread(FU_UDEV_DEVICE(device), PCI_CFG_HFS_6, buf, sizeof(buf), error)) { g_prefix_error(error, "could not read HFS6: "); return FALSE; } self->hfsts6.data = fu_memread_uint32(buf, G_LITTLE_ENDIAN); g_set_object(&self->pci_device, device); /* check firmware version */ fwvers = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "mei/mei0/fw_ver", NULL); if (fwvers != NULL) { if (!fu_mei_parse_fwvers(plugin, fwvers, error)) return FALSE; } /* success */ return TRUE; } static void fu_plugin_add_security_attrs_manufacturing_mode(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPciMeiPlugin *self = FU_PCI_MEI_PLUGIN(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_MEI_MANUFACTURING_MODE); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); fu_security_attrs_append(attrs, attr); /* no device */ if (self->pci_device == NULL) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } /* Manufacturing Mode */ fwupd_security_attr_add_metadata(attr, "kind", fu_mei_family_to_string(self->family)); if (self->hfsts1.fields.mfg_mode) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_plugin_add_security_attrs_override_strap(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPciMeiPlugin *self = FU_PCI_MEI_PLUGIN(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_MEI_OVERRIDE_STRAP); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); fu_security_attrs_append(attrs, attr); /* no device */ if (self->pci_device == NULL) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } /* Flash Descriptor Security Override Strap */ fwupd_security_attr_add_metadata(attr, "kind", fu_mei_family_to_string(self->family)); if (self->hfsts1.fields.operation_mode == FU_ME_HFS_MODE_OVERRIDE_JUMPER) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_plugin_add_security_attrs_bootguard_enabled(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPciMeiPlugin *self = FU_PCI_MEI_PLUGIN(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ENABLED); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); /* no device */ if (self->pci_device == NULL) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } /* not supported */ if (self->family == FU_MEI_FAMILY_TXE) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); return; } /* disabled at runtime? */ if (self->hfsts6.fields.boot_guard_disable) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_plugin_add_security_attrs_bootguard_verified(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPciMeiPlugin *self = FU_PCI_MEI_PLUGIN(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_VERIFIED); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); fu_security_attrs_append(attrs, attr); /* no device */ if (self->pci_device == NULL) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } /* not supported */ if (self->family == FU_MEI_FAMILY_TXE) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); return; } /* actively disabled */ if (self->hfsts6.fields.boot_guard_disable) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } /* measured boot is not sufficient, verified is required */ if (!self->hfsts6.fields.verified_boot) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_plugin_add_security_attrs_bootguard_acm(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPciMeiPlugin *self = FU_PCI_MEI_PLUGIN(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ACM); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); fu_security_attrs_append(attrs, attr); /* no device */ if (self->pci_device == NULL) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } /* not supported */ if (self->family == FU_MEI_FAMILY_TXE) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); return; } /* actively disabled */ if (self->hfsts6.fields.boot_guard_disable) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } /* ACM protection required */ if (!self->hfsts6.fields.force_boot_guard_acm) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_plugin_add_security_attrs_bootguard_policy(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPciMeiPlugin *self = FU_PCI_MEI_PLUGIN(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_POLICY); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); fu_security_attrs_append(attrs, attr); /* no device */ if (self->pci_device == NULL) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } /* not supported */ if (self->family == FU_MEI_FAMILY_TXE) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); return; } /* actively disabled */ if (self->hfsts6.fields.boot_guard_disable) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } /* policy must be to immediately shutdown or after 30 mins -- the latter isn't ideal but * we've been testing for this accidentally for a long time now */ if (self->hfsts6.fields.error_enforce_policy != FU_ME_HFS_ENFORCEMENT_POLICY_SHUTDOWN_NOW && self->hfsts6.fields.error_enforce_policy != FU_ME_HFS_ENFORCEMENT_POLICY_SHUTDOWN_30MINS) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_plugin_add_security_attrs_bootguard_otp(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPciMeiPlugin *self = FU_PCI_MEI_PLUGIN(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_OTP); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); fu_security_attrs_append(attrs, attr); /* no device */ if (self->pci_device == NULL) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } /* not supported */ if (self->family == FU_MEI_FAMILY_TXE) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); return; } /* actively disabled */ if (self->hfsts6.fields.boot_guard_disable) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } /* ensure vendor set the FPF OTP fuse */ if (!self->hfsts6.fields.fpf_soc_lock) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_plugin_add_security_attrs_bootguard(FuPlugin *plugin, FuSecurityAttrs *attrs) { fu_plugin_add_security_attrs_bootguard_enabled(plugin, attrs); fu_plugin_add_security_attrs_bootguard_verified(plugin, attrs); fu_plugin_add_security_attrs_bootguard_acm(plugin, attrs); fu_plugin_add_security_attrs_bootguard_policy(plugin, attrs); fu_plugin_add_security_attrs_bootguard_otp(plugin, attrs); } static void fu_plugin_add_security_attrs_mei_version(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuPciMeiPlugin *self = FU_PCI_MEI_PLUGIN(plugin); g_autofree gchar *version = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_MEI_VERSION); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); fu_security_attrs_append(attrs, attr); /* not enabled */ if (self->pci_device == NULL) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); return; } /* format version as string */ version = g_strdup_printf("%u:%u.%u.%u.%u", self->vers.platform, self->vers.major, self->vers.minor, self->vers.hotfix, self->vers.buildno); if (self->issue == FU_MEI_ISSUE_UNKNOWN) { g_warning("ME family not supported for %s", version); return; } fwupd_security_attr_add_metadata(attr, "version", version); fwupd_security_attr_add_metadata(attr, "kind", fu_mei_family_to_string(self->family)); /* Flash Descriptor Security Override Strap */ if (self->issue == FU_MEI_ISSUE_VULNERABLE) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_pci_mei_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; /* only Intel */ if (fu_cpu_get_vendor() != FU_CPU_VENDOR_INTEL) return; /* CPU supported */ attr = fu_security_attrs_get_by_appstream_id(attrs, FWUPD_SECURITY_ATTR_ID_SUPPORTED_CPU, NULL); if (attr != NULL) fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); fu_plugin_add_security_attrs_manufacturing_mode(plugin, attrs); fu_plugin_add_security_attrs_override_strap(plugin, attrs); fu_plugin_add_security_attrs_bootguard(plugin, attrs); fu_plugin_add_security_attrs_mei_version(plugin, attrs); } static void fu_pci_mei_plugin_init(FuPciMeiPlugin *self) { } static void fu_pci_mei_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "pci"); } static void fu_pci_mei_finalize(GObject *obj) { FuPciMeiPlugin *self = FU_PCI_MEI_PLUGIN(obj); if (self->pci_device != NULL) g_object_unref(self->pci_device); G_OBJECT_CLASS(fu_pci_mei_plugin_parent_class)->finalize(obj); } static void fu_pci_mei_plugin_class_init(FuPciMeiPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_pci_mei_finalize; plugin_class->constructed = fu_pci_mei_plugin_constructed; plugin_class->to_string = fu_pci_mei_plugin_to_string; plugin_class->add_security_attrs = fu_pci_mei_plugin_add_security_attrs; plugin_class->backend_device_added = fu_pci_mei_plugin_backend_device_added; } fwupd-1.9.16/plugins/pci-mei/fu-pci-mei-plugin.h000066400000000000000000000003521460375044200213460ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuPciMeiPlugin, fu_pci_mei_plugin, FU, PCI_MEI_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/pci-mei/meson.build000066400000000000000000000006641460375044200201160ustar00rootroot00000000000000if hsi cargs = ['-DG_LOG_DOMAIN="FuPluginPciMei"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('pci-mei.quirk') plugin_builtins += static_library('fu_plugin_pci_mei', rustgen.process( 'fu-mei.rs', ), sources: [ 'fu-pci-mei-plugin.c', 'fu-mei-common.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/pci-mei/pci-mei.quirk000066400000000000000000000000451460375044200203450ustar00rootroot00000000000000[PCI\DRIVER_mei_me] Plugin = pci_mei fwupd-1.9.16/plugins/pci-psp/000077500000000000000000000000001460375044200157765ustar00rootroot00000000000000fwupd-1.9.16/plugins/pci-psp/README.md000066400000000000000000000015751460375044200172650ustar00rootroot00000000000000--- title: Plugin: PCI PSP — Platform Secure Processor --- ## Introduction This plugin checks all information reported from the AMD Platform Secure processor into the operating system on select SOCs. The lack of these sysfs files does *NOT* indicate the lack of these security features, it only indicates the lack of the ability to export it to the operating system. The availability of the sysfs files indicates that the PSP supports exporting this information into the operating system. ## External Interface Access This plugin requires read only access to attributes located within `/sys/bus/pci/devices/`. ## Version Considerations This plugin has been available since fwupd version `1.8.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Mario Limonciello: @superm1 fwupd-1.9.16/plugins/pci-psp/fu-pci-psp-device.c000066400000000000000000000277661460375044200214040ustar00rootroot00000000000000/* * Copyright (C) 2022 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-pci-psp-device.h" struct _FuPciPspDevice { FuUdevDevice parent_instance; gboolean supported; }; G_DEFINE_TYPE(FuPciPspDevice, fu_pci_psp_device, FU_TYPE_UDEV_DEVICE) static gboolean fu_pci_psp_device_probe(FuDevice *device, GError **error) { const gchar *bootloader_version; const gchar *tee_version; g_autoptr(GError) error_boot = NULL; g_autoptr(GError) error_tee = NULL; bootloader_version = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "bootloader_version", &error_boot); if (bootloader_version == NULL) g_info("failed to read bootloader version: %s", error_boot->message); else fu_device_set_version_bootloader(device, bootloader_version); tee_version = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "tee_version", &error_tee); if (tee_version == NULL) g_info("failed to read bootloader version: %s", error_tee->message); else fu_device_set_version(device, tee_version); return TRUE; } static gboolean fu_pci_psp_device_get_attr(FwupdSecurityAttr *attr, const gchar *path, const gchar *file, gboolean *out, GError **error) { guint64 val = 0; g_autofree gchar *fn = g_build_filename(path, file, NULL); g_autofree gchar *buf = NULL; gsize bufsz = 0; if (!g_file_get_contents(fn, &buf, &bufsz, error)) { g_prefix_error(error, "could not open %s: ", fn); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); return FALSE; } if (!fu_strtoull(buf, &val, 0, G_MAXUINT32, error)) return FALSE; *out = val ? TRUE : FALSE; return TRUE; } static void fu_pci_psp_device_set_valid_data(FuDevice *device, FuSecurityAttrs *attrs) { FuPciPspDevice *self = FU_PCI_PSP_DEVICE(device); g_autoptr(FwupdSecurityAttr) attr = NULL; if (self->supported) return; /* CPU supported */ self->supported = TRUE; attr = fu_security_attrs_get_by_appstream_id(attrs, FWUPD_SECURITY_ATTR_ID_SUPPORTED_CPU, NULL); if (attr != NULL) fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static FwupdSecurityAttr * fu_pci_psp_device_get_security_attr(FuDevice *device, FuSecurityAttrs *attrs, const gchar *appstream_id) { g_autoptr(FwupdSecurityAttr) attr = NULL; attr = fu_security_attrs_get_by_appstream_id(attrs, appstream_id, NULL); if (attr == NULL) { attr = fu_device_security_attr_new(device, appstream_id); fu_security_attrs_append(attrs, attr); } else if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA)) { g_debug("found missing data on old attribute, repopulating"); fwupd_security_attr_remove_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); } return g_steal_pointer(&attr); } static void fu_pci_psp_device_add_security_attrs_tsme(FuDevice *device, const gchar *path, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; gboolean val; attr = fu_pci_psp_device_get_security_attr(device, attrs, FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { g_debug("ignoring already populated attribute"); return; } fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENCRYPTED); if (!fu_pci_psp_device_get_attr(attr, path, "tsme_status", &val, &error_local)) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); g_debug("%s", error_local->message); return; } fu_pci_psp_device_set_valid_data(device, attrs); /* BIOS knob used on Lenovo systems */ fu_security_attr_add_bios_target_value(attr, "com.thinklmi.TSME", "enable"); if (!val) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); return; } fwupd_security_attr_add_obsolete(attr, "msr"); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_pci_psp_device_add_security_attrs_fused_part(FuDevice *device, const gchar *path, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; gboolean val; attr = fu_pci_psp_device_get_security_attr(device, attrs, FWUPD_SECURITY_ATTR_ID_PLATFORM_FUSED); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { g_debug("ignoring already populated attribute"); return; } fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); if (!fu_pci_psp_device_get_attr(attr, path, "fused_part", &val, &error_local)) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); g_debug("%s", error_local->message); return; } fu_pci_psp_device_set_valid_data(device, attrs); if (!val) { g_debug("part is not fused"); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_pci_psp_device_add_security_attrs_debug_locked_part(FuDevice *device, const gchar *path, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; gboolean val; attr = fu_pci_psp_device_get_security_attr(device, attrs, FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_LOCKED); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { g_debug("ignoring already populated attribute"); return; } fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); if (!fu_pci_psp_device_get_attr(attr, path, "debug_lock_on", &val, &error_local)) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); g_debug("%s", error_local->message); return; } fu_pci_psp_device_set_valid_data(device, attrs); if (!val) { g_debug("debug lock disabled"); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_pci_psp_device_add_security_attrs_rollback_protection(FuDevice *device, const gchar *path, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; gboolean val; attr = fu_pci_psp_device_get_security_attr(device, attrs, FWUPD_SECURITY_ATTR_ID_AMD_ROLLBACK_PROTECTION); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { g_debug("ignoring already populated attribute"); return; } fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); if (!fu_pci_psp_device_get_attr(attr, path, "anti_rollback_status", &val, &error_local)) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); g_debug("%s", error_local->message); return; } fu_pci_psp_device_set_valid_data(device, attrs); if (!val) { g_debug("rollback protection not enforced"); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); return; } fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_pci_psp_device_add_security_attrs_rom_armor(FuDevice *device, const gchar *path, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; gboolean val; /* create attr */ attr = fu_pci_psp_device_get_security_attr(device, attrs, FWUPD_SECURITY_ATTR_ID_AMD_SPI_WRITE_PROTECTION); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { g_debug("ignoring already populated attribute"); return; } fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); if (!fu_pci_psp_device_get_attr(attr, path, "rom_armor_enforced", &val, &error_local)) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); g_debug("%s", error_local->message); return; } fu_pci_psp_device_set_valid_data(device, attrs); if (!val) { g_debug("ROM armor not enforced"); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_pci_psp_device_add_security_attrs_rpmc(FuDevice *device, const gchar *path, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error_local = NULL; gboolean val; /* create attr */ attr = fu_pci_psp_device_get_security_attr(device, attrs, FWUPD_SECURITY_ATTR_ID_AMD_SPI_REPLAY_PROTECTION); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { g_debug("ignoring already populated attribute"); return; } fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); if (!fu_pci_psp_device_get_attr(attr, path, "rpmc_spirom_available", &val, &error_local)) { fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); g_debug("%s", error_local->message); return; } fu_pci_psp_device_set_valid_data(device, attrs); if (!val) { g_debug("no RPMC compatible SPI rom present"); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED); return; } if (!fu_pci_psp_device_get_attr(attr, path, "rpmc_production_enabled", &val, &error_local)) { g_debug("%s", error_local->message); return; } if (!val) { g_debug("no RPMC compatible SPI rom present"); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_pci_psp_device_add_security_attrs(FuDevice *device, FuSecurityAttrs *attrs) { FuPciPspDevice *self = FU_PCI_PSP_DEVICE(device); const gchar *sysfs_path = NULL; if (device != NULL) sysfs_path = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device)); /* ccp not loaded */ if (sysfs_path == NULL) return; self->supported = FALSE; fu_pci_psp_device_add_security_attrs_tsme(device, sysfs_path, attrs); fu_pci_psp_device_add_security_attrs_fused_part(device, sysfs_path, attrs); fu_pci_psp_device_add_security_attrs_debug_locked_part(device, sysfs_path, attrs); fu_pci_psp_device_add_security_attrs_rollback_protection(device, sysfs_path, attrs); fu_pci_psp_device_add_security_attrs_rpmc(device, sysfs_path, attrs); fu_pci_psp_device_add_security_attrs_rom_armor(device, sysfs_path, attrs); } static void fu_pci_psp_device_init(FuPciPspDevice *self) { fu_device_set_name(FU_DEVICE(self), "Secure Processor"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_icon(FU_DEVICE(self), "computer"); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_HOST_CPU_CHILD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_PROBE_COMPLETE); fu_device_set_vendor(FU_DEVICE(self), "Advanced Micro Devices, Inc."); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_physical_id(FU_DEVICE(self), "pci-psp"); } static void fu_pci_psp_device_class_init(FuPciPspDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_pci_psp_device_probe; klass_device->add_security_attrs = fu_pci_psp_device_add_security_attrs; } fwupd-1.9.16/plugins/pci-psp/fu-pci-psp-device.h000066400000000000000000000010311460375044200213620ustar00rootroot00000000000000/* * Copyright (C) 2022 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_PCI_PSP_DEVICE (fu_pci_psp_device_get_type()) G_DECLARE_FINAL_TYPE(FuPciPspDevice, fu_pci_psp_device, FU, PCI_PSP_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/pci-psp/fu-pci-psp-plugin.c000066400000000000000000000017551460375044200214310ustar00rootroot00000000000000/* * Copyright (C) 2022 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-pci-psp-device.h" #include "fu-pci-psp-plugin.h" struct _FuPciPspPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuPciPspPlugin, fu_pci_psp_plugin, FU_TYPE_PLUGIN) static void fu_pci_psp_plugin_init(FuPciPspPlugin *self) { } static void fu_pci_psp_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "pci"); fu_plugin_add_device_gtype(plugin, FU_TYPE_PCI_PSP_DEVICE); } static void fu_pci_psp_plugin_class_init(FuPciPspPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_pci_psp_plugin_constructed; } fwupd-1.9.16/plugins/pci-psp/fu-pci-psp-plugin.h000066400000000000000000000010221460375044200214210ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2022 Advanced Micro Devices Inc. * All rights reserved. * * This file is provided under a dual MIT/LGPLv2 license. When using or * redistributing this file, you may do so under either license. * AMD Chooses the MIT license part of Dual MIT/LGPLv2 license agreement. * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include G_DECLARE_FINAL_TYPE(FuPciPspPlugin, fu_pci_psp_plugin, FU, PCI_PSP_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/pci-psp/meson.build000066400000000000000000000006771460375044200201520ustar00rootroot00000000000000if hsi and (host_cpu == 'x86' or host_cpu == 'x86_64') cargs = ['-DG_LOG_DOMAIN="FuPluginPciPsp"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('pci-psp.quirk') plugin_builtins += static_library('fu_plugin_pci_psp', sources: [ 'fu-pci-psp-plugin.c', 'fu-pci-psp-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/pci-psp/pci-psp.quirk000066400000000000000000000000421460375044200204220ustar00rootroot00000000000000[PCI\DRIVER_ccp] Plugin = pci_psp fwupd-1.9.16/plugins/pixart-rf/000077500000000000000000000000001460375044200163375ustar00rootroot00000000000000fwupd-1.9.16/plugins/pixart-rf/README.md000066400000000000000000000025151460375044200176210ustar00rootroot00000000000000--- title: Plugin: PixArt RF --- ## Introduction This plugin allows the user to update any supported Pixart RF Device using a custom HID-based OTA protocol ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * `com.pixart.rf` ## GUID Generation These devices use the standard HIDRAW DeviceInstanceId values for both Pixart Imaging, Inc and Primax Electronics, Ltd, e.g. * `HIDRAW\VEN_093A&DEV_2801` * `HIDRAW\VEN_0461&DEV_4EEF` * `HIDRAW\VEN_0461&DEV_4EEF&NAME_${NAME}` * `HIDRAW\VEN_0461&DEV_4EEF&MODEL_${MODEL_NAME}` Additionally, a custom GUID values including the name is used, e.g. ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x093A` ## External Interface Access This plugin requires ioctl `HIDIOCSFEATURE` and `HIDIOCGFEATURE` access. ## Version Considerations This plugin has been available since fwupd version `1.5.5`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Sam Chen: @sam412081go fwupd-1.9.16/plugins/pixart-rf/fu-pxi-ble-device.c000066400000000000000000000743111460375044200217160ustar00rootroot00000000000000/* * Copyright (C) 2020 Jimmy Yu * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_HIDRAW_H #include #include #endif #include "fu-pxi-ble-device.h" #include "fu-pxi-common.h" #include "fu-pxi-firmware.h" #include "fu-pxi-struct.h" #define PXI_HID_DEV_OTA_INPUT_REPORT_ID 0x05 #define PXI_HID_DEV_OTA_RETRANSMIT_REPORT_ID 0x06 #define PXI_HID_DEV_OTA_FEATURE_REPORT_ID 0x07 #define PXI_HID_DEV_OTA_REPORT_USAGE_PAGE 0xff02u #define PXI_HID_DEV_OTA_RETRANSMIT_USAGE_PAGE 0xff01u #define PXI_HID_DEV_OTA_NOTIFY_USAGE_PAGE 0xff00u #define ERR_COMMAND_SUCCESS 0x0 #define FU_PXI_DEVICE_OBJECT_SIZE_MAX 4096 /* bytes */ #define FU_PXI_BLE_DEVICE_OTA_BUF_SZ 512 /* bytes */ #define FU_PXI_BLE_DEVICE_NOTIFY_RET_LEN 4 /* bytes */ #define FU_PXI_BLE_DEVICE_FW_INFO_RET_LEN 8 /* bytes */ #define FU_PXI_BLE_DEVICE_NOTIFY_TIMEOUT_MS 5000 #define FU_PXI_BLE_DEVICE_SET_REPORT_RETRIES 30 /* OTA target selection */ enum ota_process_setting { OTA_MAIN_FW, /* Main firmware */ OTA_HELPER_FW, /* Helper firmware */ OTA_EXTERNAL_RESOURCE, /* External resource */ }; struct _FuPxiBleDevice { FuUdevDevice parent_instance; struct ota_fw_state fwstate; guint8 retransmit_id; guint8 feature_report_id; guint8 input_report_id; gchar *model_name; }; G_DEFINE_TYPE(FuPxiBleDevice, fu_pxi_ble_device, FU_TYPE_UDEV_DEVICE) #ifdef HAVE_HIDRAW_H static gboolean fu_pxi_ble_device_get_raw_info(FuPxiBleDevice *self, struct hidraw_devinfo *info, GError **error) { if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGRAWINFO, (guint8 *)info, NULL, FU_PXI_DEVICE_IOCTL_TIMEOUT, error)) { return FALSE; } return TRUE; } #endif static void fu_pxi_ble_device_to_string(FuDevice *device, guint idt, GString *str) { FuPxiBleDevice *self = FU_PXI_BLE_DEVICE(device); /* FuUdevDevice->to_string */ FU_DEVICE_CLASS(fu_pxi_ble_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "ModelName", self->model_name); fu_pxi_ota_fw_state_to_string(&self->fwstate, idt, str); fu_string_append_kx(str, idt, "RetransmitID", self->retransmit_id); fu_string_append_kx(str, idt, "FeatureReportID", self->feature_report_id); fu_string_append_kx(str, idt, "InputReportID", self->input_report_id); } static FuFirmware * fu_pxi_ble_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuPxiBleDevice *self = FU_PXI_BLE_DEVICE(device); g_autoptr(FuFirmware) firmware = fu_pxi_firmware_new(); if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; if (fu_device_has_private_flag(device, FU_PXI_DEVICE_FLAG_IS_HPAC) && fu_pxi_firmware_is_hpac(FU_PXI_FIRMWARE(firmware))) { g_autoptr(GBytes) fw_tmp = NULL; guint32 hpac_fw_size = 0; const guint8 *fw_ptr = g_bytes_get_data(fw, NULL); if (!fu_memread_uint32_safe(fw_ptr, g_bytes_get_size(fw), 9, &hpac_fw_size, G_LITTLE_ENDIAN, error)) return NULL; hpac_fw_size += 264; fw_tmp = fu_bytes_new_offset(fw, 9, hpac_fw_size, error); if (fw_tmp == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "HPAC F/W preparation failed."); return NULL; } fu_firmware_set_bytes(firmware, fw_tmp); } else if (!fu_device_has_private_flag(device, FU_PXI_DEVICE_FLAG_IS_HPAC) && !fu_pxi_firmware_is_hpac(FU_PXI_FIRMWARE(firmware))) { const gchar *model_name; /* check is compatible with hardware */ model_name = fu_pxi_firmware_get_model_name(FU_PXI_FIRMWARE(firmware)); if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { if (self->model_name == NULL || model_name == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "legacy device or firmware detected, " "--force required"); return NULL; } if (g_strcmp0(self->model_name, model_name) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incompatible firmware, got %s, expected %s.", model_name, self->model_name); return NULL; } } } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "The firmware is incompatible with the device"); return NULL; } return g_steal_pointer(&firmware); } #ifdef HAVE_HIDRAW_H static gboolean fu_pxi_ble_device_set_feature_cb(FuDevice *device, gpointer user_data, GError **error) { GByteArray *req = (GByteArray *)user_data; return fu_udev_device_ioctl(FU_UDEV_DEVICE(device), HIDIOCSFEATURE(req->len), (guint8 *)req->data, NULL, FU_PXI_DEVICE_IOCTL_TIMEOUT, error); } #endif static gboolean fu_pxi_ble_device_set_feature(FuPxiBleDevice *self, GByteArray *req, GError **error) { #ifdef HAVE_HIDRAW_H fu_dump_raw(G_LOG_DOMAIN, "SetFeature", req->data, req->len); return fu_device_retry(FU_DEVICE(self), fu_pxi_ble_device_set_feature_cb, FU_PXI_BLE_DEVICE_SET_REPORT_RETRIES, req, error); #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_pxi_ble_device_get_feature(FuPxiBleDevice *self, guint8 *buf, guint bufsz, GError **error) { #ifdef HAVE_HIDRAW_H if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGFEATURE(bufsz), buf, NULL, FU_PXI_DEVICE_IOCTL_TIMEOUT, error)) { return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "GetFeature", buf, bufsz); /* prepend the report-id and cmd for versions of bluez that do not have * https://github.com/bluez/bluez/commit/35a2c50437cca4d26ac6537ce3a964bb509c9b62 */ if (bufsz > 2 && buf[0] != self->feature_report_id) { g_debug("doing fixup for old bluez version"); memmove(buf + 2, buf, bufsz - 2); buf[0] = self->feature_report_id; buf[1] = 0x0; } return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_pxi_ble_device_search_hid_feature_report_id(FuFirmware *descriptor, guint16 usage_page, guint8 *report_id, GError **error) { g_autoptr(FuFirmware) item_id = NULL; g_autoptr(FuHidReport) report = NULL; /* check ota retransmit feature report usage page exists */ report = fu_hid_descriptor_find_report(FU_HID_DESCRIPTOR(descriptor), error, "usage-page", usage_page, "usage", 0x01, "feature", 0x02, NULL); if (report == NULL) return FALSE; /* find report-id */ item_id = fu_firmware_get_image_by_id(FU_FIRMWARE(report), "report-id", error); if (item_id == NULL) return FALSE; /* success */ *report_id = fu_hid_report_item_get_value(FU_HID_REPORT_ITEM(item_id)); return TRUE; } static gboolean fu_pxi_ble_device_search_hid_input_report_id(FuFirmware *descriptor, guint16 usage_page, guint8 *report_id, GError **error) { g_autoptr(FuHidReport) report = NULL; g_autoptr(FuFirmware) item_id = NULL; /* check ota retransmit feature report usage page exist or not */ report = fu_hid_descriptor_find_report(FU_HID_DESCRIPTOR(descriptor), error, "usage-page", usage_page, "usage", 0x01, "input", 0x02, NULL); if (report == NULL) return FALSE; /* find report-id */ item_id = fu_firmware_get_image_by_id(FU_FIRMWARE(report), "report-id", error); if (item_id == NULL) return FALSE; /* success */ *report_id = fu_hid_report_item_get_value(FU_HID_REPORT_ITEM(item_id)); return TRUE; } static gboolean fu_pxi_ble_device_check_support_report_id(FuPxiBleDevice *self, GError **error) { #ifdef HAVE_HIDRAW_H gint desc_size = 0; g_autoptr(FuFirmware) descriptor = fu_hid_descriptor_new(); g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error_local1 = NULL; g_autoptr(GError) error_local2 = NULL; g_autoptr(GError) error_local3 = NULL; g_autoptr(GError) error_local = NULL; struct hidraw_report_descriptor rpt_desc = {0x0}; /* Get Report Descriptor Size */ if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGRDESCSIZE, (guint8 *)&desc_size, NULL, FU_PXI_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; rpt_desc.size = desc_size; if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGRDESC, (guint8 *)&rpt_desc, NULL, FU_PXI_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "HID descriptor", rpt_desc.value, rpt_desc.size); /* parse the descriptor, but use the defaults if it fails */ fw = g_bytes_new(rpt_desc.value, rpt_desc.size); if (!fu_firmware_parse(descriptor, fw, FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_debug("failed to parse descriptor: %s", error_local->message); return TRUE; } /* check ota retransmit feature report usage page exists */ if (!fu_pxi_ble_device_search_hid_feature_report_id(descriptor, PXI_HID_DEV_OTA_RETRANSMIT_USAGE_PAGE, &self->retransmit_id, &error_local1)) { g_debug("failed to parse descriptor: %s", error_local1->message); } g_debug("usage-page: 0x%x retransmit_id: %d", PXI_HID_DEV_OTA_RETRANSMIT_USAGE_PAGE, self->retransmit_id); /* check ota feature report usage page exists */ if (!fu_pxi_ble_device_search_hid_feature_report_id(descriptor, PXI_HID_DEV_OTA_REPORT_USAGE_PAGE, &self->feature_report_id, &error_local2)) { g_debug("failed to parse descriptor: %s", error_local2->message); } g_debug("usage-page: 0x%x feature_report_id: %d", PXI_HID_DEV_OTA_RETRANSMIT_USAGE_PAGE, self->retransmit_id); /* check ota notify input report usage page exist or not */ if (!fu_pxi_ble_device_search_hid_input_report_id(descriptor, PXI_HID_DEV_OTA_NOTIFY_USAGE_PAGE, &self->input_report_id, &error_local3)) { g_debug("failed to parse descriptor: %s", error_local3->message); } g_debug("usage-page: 0x%x input_report_id: %d", PXI_HID_DEV_OTA_RETRANSMIT_USAGE_PAGE, self->input_report_id); /* success */ return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE #endif } static gboolean fu_pxi_ble_device_fw_ota_check_retransmit(FuPxiBleDevice *self, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new(); /* write fw ota retransmit command to reset the ota state */ fu_byte_array_append_uint8(req, self->retransmit_id); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_OTA_RETRANSMIT); return fu_pxi_ble_device_set_feature(self, req, error); } static gboolean fu_pxi_ble_device_check_support_resume(FuPxiBleDevice *self, FuFirmware *firmware, FuProgress *progress, GError **error) { g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; guint16 checksum_tmp = 0x0; /* get the default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* check offset is invalid or not */ chunks = fu_chunk_array_new_from_bytes(fw, 0x0, FU_PXI_DEVICE_OBJECT_SIZE_MAX); if (self->fwstate.offset > fu_chunk_array_length(chunks)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "offset from device is invalid: " "got 0x%x, current maximum 0x%x", self->fwstate.offset, fu_chunk_array_length(chunks)); return FALSE; } /* calculate device current checksum */ for (guint i = 0; i < self->fwstate.offset; i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); checksum_tmp += fu_sum16(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); } /* check current file is different with previous fw bin or not */ if (self->fwstate.checksum != checksum_tmp) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "checksum is different from previous fw: " "got 0x%04x, expected 0x%04x", self->fwstate.checksum, checksum_tmp); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_wait_notify(FuPxiBleDevice *self, goffset port, guint8 *status, guint16 *checksum, GError **error) { g_autoptr(GTimer) timer = g_timer_new(); guint8 res[FU_PXI_BLE_DEVICE_OTA_BUF_SZ] = {0}; guint8 cmd_status = 0x0; /* skip the wrong report id ,and keep polling until result is correct */ while (g_timer_elapsed(timer, NULL) * 1000.f < FU_PXI_BLE_DEVICE_NOTIFY_TIMEOUT_MS) { if (!fu_udev_device_pread(FU_UDEV_DEVICE(self), port, res, (FU_PXI_BLE_DEVICE_NOTIFY_RET_LEN + 1) - port, error)) return FALSE; if (res[0] == self->input_report_id) break; } /* timeout */ if (res[0] != self->input_report_id) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Timed-out waiting for HID report"); return FALSE; } /* get the opcode if status is not null */ if (status != NULL) { guint8 status_tmp = 0x0; if (!fu_memread_uint8_safe(res, sizeof(res), 0x1, &status_tmp, error)) return FALSE; /* need check command result if command is fw upgrade */ if (status_tmp == FU_PXI_DEVICE_CMD_FW_UPGRADE) { if (!fu_memread_uint8_safe(res, sizeof(res), 0x2, &cmd_status, error)) return FALSE; if (cmd_status != ERR_COMMAND_SUCCESS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cmd status was 0x%02x", cmd_status); return FALSE; } } /* propagate */ *status = status_tmp; } if (checksum != NULL) { if (!fu_memread_uint16_safe(res, sizeof(res), 0x3, checksum, G_LITTLE_ENDIAN, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_fw_object_create(FuPxiBleDevice *self, FuChunk *chk, GError **error) { guint8 opcode = 0; g_autoptr(GByteArray) req = g_byte_array_new(); /* request */ fu_byte_array_append_uint8(req, self->feature_report_id); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE); fu_byte_array_append_uint32(req, fu_chunk_get_address(chk), G_LITTLE_ENDIAN); fu_byte_array_append_uint32(req, fu_chunk_get_data_sz(chk), G_LITTLE_ENDIAN); if (!fu_pxi_ble_device_set_feature(self, req, error)) return FALSE; /* check object create success or not */ if (!fu_pxi_ble_device_wait_notify(self, 0x0, &opcode, NULL, error)) return FALSE; if (opcode != FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "FwObjectCreate opcode got 0x%02x, expected 0x%02x", opcode, FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_write_payload(FuPxiBleDevice *self, FuChunk *chk, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new(); fu_byte_array_append_uint8(req, self->feature_report_id); g_byte_array_append(req, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); return fu_pxi_ble_device_set_feature(self, req, error); } static gboolean fu_pxi_ble_device_write_chunk(FuPxiBleDevice *self, FuChunk *chk, GError **error) { guint32 prn = 0; guint16 checksum; guint16 checksum_device = 0; g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GBytes) chk_bytes = fu_chunk_get_bytes(chk); /* send create fw object command */ if (!fu_pxi_ble_device_fw_object_create(self, chk, error)) return FALSE; /* write payload */ chunks = fu_chunk_array_new_from_bytes(chk_bytes, fu_chunk_get_address(chk), self->fwstate.mtu_size); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk2 = fu_chunk_array_index(chunks, i); if (!fu_pxi_ble_device_write_payload(self, chk2, error)) return FALSE; prn++; /* wait notify from device when PRN over threshold write or * offset reach max object sz or write offset reach fw length */ if (prn >= self->fwstate.prn_threshold || i == fu_chunk_array_length(chunks) - 1) { guint8 opcode = 0; if (!fu_pxi_ble_device_wait_notify(self, 0x0, &opcode, &checksum_device, error)) return FALSE; if (opcode != FU_PXI_DEVICE_CMD_FW_WRITE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "FwWrite opcode invalid 0x%02x", opcode); return FALSE; } prn = 0; } } /* the last chunk */ checksum = fu_sum16(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); self->fwstate.checksum += checksum; if (checksum_device != self->fwstate.checksum) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "checksum fail, got 0x%04x, expected 0x%04x", checksum_device, checksum); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_reset(FuPxiBleDevice *self, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new(); fu_byte_array_append_uint8(req, self->feature_report_id); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_MCU_RESET); /* OTA reset command */ fu_byte_array_append_uint8(req, OTA_RESET); /* OTA reset reason */ if (!fu_pxi_ble_device_set_feature(self, req, error)) { g_prefix_error(error, "failed to reset: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_fw_ota_init(FuPxiBleDevice *self, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new(); /* write fw ota init command */ fu_byte_array_append_uint8(req, self->feature_report_id); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_OTA_INIT); return fu_pxi_ble_device_set_feature(self, req, error); } static gboolean fu_pxi_ble_device_fw_ota_init_new(FuPxiBleDevice *self, gsize bufsz, GError **error) { guint8 res[FU_PXI_BLE_DEVICE_OTA_BUF_SZ] = {0x0}; guint8 fw_version[10] = {0x0}; g_autoptr(GByteArray) req = g_byte_array_new(); /* write fw ota init new command */ fu_byte_array_append_uint8(req, self->feature_report_id); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW); fu_byte_array_append_uint32(req, bufsz, G_LITTLE_ENDIAN); fu_byte_array_append_uint8(req, 0x0); /* OTA setting */ g_byte_array_append(req, fw_version, sizeof(fw_version)); if (!fu_pxi_ble_device_set_feature(self, req, error)) return FALSE; /* delay for BLE device read command */ fu_device_sleep(FU_DEVICE(self), 10); /* ms */ /* read fw ota init new command */ res[0] = self->feature_report_id; res[1] = FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW; if (!fu_pxi_ble_device_get_feature(self, res, sizeof(res), error)) return FALSE; /* shared state */ if (!fu_pxi_ota_fw_state_parse(&self->fwstate, res, sizeof(res), 0x05, error)) return FALSE; if (self->fwstate.spec_check_result != FU_PXI_OTA_SPEC_CHECK_RESULT_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "FwInitNew spec check fail: %s [0x%02x]", fu_pxi_ota_spec_check_result_to_string(self->fwstate.spec_check_result), self->fwstate.spec_check_result); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_fw_upgrade(FuPxiBleDevice *self, FuFirmware *firmware, FuProgress *progress, GError **error) { const gchar *version; guint8 fw_version[5] = {0x0}; guint8 opcode = 0; guint16 checksum; g_autoptr(GBytes) fw = NULL; g_autoptr(GByteArray) req = g_byte_array_new(); fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; checksum = fu_sum16_bytes(fw); fu_byte_array_append_uint8(req, self->feature_report_id); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_UPGRADE); fu_byte_array_append_uint32(req, g_bytes_get_size(fw), G_LITTLE_ENDIAN); fu_byte_array_append_uint16(req, checksum, G_LITTLE_ENDIAN); if (!fu_device_has_private_flag(FU_DEVICE(self), FU_PXI_DEVICE_FLAG_IS_HPAC)) { version = fu_firmware_get_version(firmware); if (!fu_memcpy_safe(fw_version, sizeof(fw_version), 0x0, /* dst */ (guint8 *)version, strlen(version), 0x0, /* src */ strlen(version), error)) return FALSE; } g_byte_array_append(req, fw_version, sizeof(fw_version)); /* send fw upgrade command */ if (!fu_pxi_ble_device_set_feature(self, req, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "fw upgrade", req->data, req->len); /* wait fw upgrade command result */ if (!fu_pxi_ble_device_wait_notify(self, 0x1, &opcode, NULL, error)) { g_prefix_error(error, "FwUpgrade command fail, " "fw-checksum: 0x%04x fw-size: %" G_GSIZE_FORMAT ": ", checksum, g_bytes_get_size(fw)); return FALSE; } if (opcode != FU_PXI_DEVICE_CMD_FW_UPGRADE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "FwUpgrade opcode invalid 0x%02x", opcode); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuPxiBleDevice *self = FU_PXI_BLE_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GError) error_local = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "ota-init"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 0, "check-support-resume"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 0, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, NULL); /* get the default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* send fw ota retransmit command to reset status */ if (!fu_pxi_ble_device_fw_ota_check_retransmit(self, error)) { g_prefix_error(error, "failed to OTA check retransmit: "); return FALSE; } /* send fw ota init command */ if (!fu_pxi_ble_device_fw_ota_init(self, error)) return FALSE; if (!fu_pxi_ble_device_fw_ota_init_new(self, g_bytes_get_size(fw), error)) return FALSE; fu_progress_step_done(progress); /* prepare write fw into device */ chunks = fu_chunk_array_new_from_bytes(fw, 0x0, FU_PXI_DEVICE_OBJECT_SIZE_MAX); if (!fu_pxi_ble_device_check_support_resume(self, firmware, fu_progress_get_child(progress), &error_local)) { g_debug("do not resume: %s", error_local->message); self->fwstate.offset = 0; self->fwstate.checksum = 0; } fu_progress_step_done(progress); /* write fw into device */ for (guint i = self->fwstate.offset; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_pxi_ble_device_write_chunk(self, chk, error)) return FALSE; fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)self->fwstate.offset + 1, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* fw upgrade command */ if (!fu_pxi_ble_device_fw_upgrade(self, firmware, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* send device reset command */ if (!fu_pxi_ble_device_reset(self, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_pxi_ble_device_fw_get_info(FuPxiBleDevice *self, GError **error) { guint8 res[FU_PXI_BLE_DEVICE_OTA_BUF_SZ] = {0x0}; guint8 opcode = 0x0; guint16 checksum = 0; guint16 hpac_ver = 0; g_autofree gchar *version_str = NULL; g_autoptr(GByteArray) req = g_byte_array_new(); fu_byte_array_append_uint8(req, self->feature_report_id); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_GET_INFO); if (!fu_pxi_ble_device_set_feature(self, req, error)) return FALSE; /* delay for BLE device read command */ fu_device_sleep(FU_DEVICE(self), 10); /* ms */ res[0] = self->feature_report_id; res[1] = FU_PXI_DEVICE_CMD_FW_GET_INFO; if (!fu_pxi_ble_device_get_feature(self, res, FU_PXI_BLE_DEVICE_FW_INFO_RET_LEN + 3, error)) return FALSE; if (!fu_memread_uint8_safe(res, sizeof(res), 0x4, &opcode, error)) return FALSE; if (opcode != FU_PXI_DEVICE_CMD_FW_GET_INFO) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "FwGetInfo opcode invalid 0x%02x", opcode); return FALSE; } /* set current version */ if (!fu_device_has_private_flag(FU_DEVICE(self), FU_PXI_DEVICE_FLAG_IS_HPAC)) { version_str = g_strndup((gchar *)res + 0x6, 5); } else { if (!fu_memread_uint16_safe(res, FU_PXI_BLE_DEVICE_OTA_BUF_SZ, 9, &hpac_ver, G_BIG_ENDIAN, error)) return FALSE; version_str = fu_pxi_hpac_version_info_parse(hpac_ver); } fu_device_set_version(FU_DEVICE(self), version_str); /* add current checksum */ if (!fu_memread_uint16_safe(res, sizeof(res), 0xb, &checksum, G_LITTLE_ENDIAN, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_pxi_ble_device_get_model_info(FuPxiBleDevice *self, GError **error) { guint8 res[FU_PXI_BLE_DEVICE_OTA_BUF_SZ] = {0x0}; guint8 opcode = 0x0; guint8 model_name[FU_PXI_DEVICE_MODEL_NAME_LEN] = {0x0}; g_autoptr(GByteArray) req = g_byte_array_new(); fu_byte_array_append_uint8(req, self->feature_report_id); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_OTA_GET_MODEL); if (!fu_pxi_ble_device_set_feature(self, req, error)) return FALSE; /* delay for BLE device read command */ fu_device_sleep(FU_DEVICE(self), 10); /* ms */ res[0] = self->feature_report_id; if (!fu_pxi_ble_device_get_feature(self, res, sizeof(res), error)) return FALSE; if (!fu_memread_uint8_safe(res, sizeof(res), 0x4, &opcode, error)) return FALSE; /* old firmware */ if (opcode != FU_PXI_DEVICE_CMD_FW_OTA_GET_MODEL) return TRUE; /* get model from res */ if (!fu_memcpy_safe(model_name, sizeof(model_name), 0x0, /* dst */ (guint8 *)res, sizeof(res), 0x6, /* src */ sizeof(model_name), error)) return FALSE; g_clear_pointer(&self->model_name, g_free); if (model_name[0] != 0x00 && model_name[0] != 0xFF) self->model_name = g_strndup((gchar *)model_name, sizeof(model_name)); /* success */ return TRUE; } static gboolean fu_pxi_ble_device_probe(FuDevice *device, GError **error) { /* set the logical and physical ID */ if (!fu_udev_device_set_logical_id(FU_UDEV_DEVICE(device), "hid", error)) return FALSE; return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); } static gboolean fu_pxi_ble_device_setup_guid(FuPxiBleDevice *self, GError **error) { #ifdef HAVE_HIDRAW_H FuDevice *device = FU_DEVICE(self); struct hidraw_devinfo hid_raw_info = {0x0}; g_autoptr(GString) dev_name = NULL; g_autoptr(GString) model_name = NULL; /* extra GUID with device name */ if (!fu_pxi_ble_device_get_raw_info(self, &hid_raw_info, error)) return FALSE; dev_name = g_string_new(fu_device_get_name(device)); g_string_ascii_up(dev_name); g_string_replace(dev_name, " ", "_", 0); /* extra GUID with model name*/ model_name = g_string_new(self->model_name); g_string_ascii_up(model_name); g_string_replace(model_name, " ", "_", 0); /* generate IDs */ fu_device_add_instance_u16(device, "VEN", hid_raw_info.vendor); fu_device_add_instance_u16(device, "DEV", hid_raw_info.product); fu_device_add_instance_str(device, "NAME", dev_name->str); fu_device_add_instance_str(device, "MODEL", model_name->str); if (!fu_device_build_instance_id(device, error, "HIDRAW", "VEN", "DEV", "NAME", NULL)) return FALSE; if (!fu_device_build_instance_id(device, error, "HIDRAW", "VEN", "DEV", "MODEL", NULL)) return FALSE; #endif return TRUE; } static gboolean fu_pxi_ble_device_setup(FuDevice *device, GError **error) { FuPxiBleDevice *self = FU_PXI_BLE_DEVICE(device); if (!fu_pxi_ble_device_check_support_report_id(self, error)) { g_prefix_error(error, "failed to check report id: "); return FALSE; } if (!fu_pxi_ble_device_fw_ota_check_retransmit(self, error)) { g_prefix_error(error, "failed to OTA check retransmit: "); return FALSE; } if (!fu_pxi_ble_device_fw_ota_init(self, error)) { g_prefix_error(error, "failed to OTA init: "); return FALSE; } if (!fu_pxi_ble_device_fw_get_info(self, error)) { g_prefix_error(error, "failed to get info: "); return FALSE; } if (!fu_pxi_ble_device_get_model_info(self, error)) { g_prefix_error(error, "failed to get model: "); return FALSE; } if (!fu_pxi_ble_device_setup_guid(self, error)) { g_prefix_error(error, "failed to setup GUID: "); return FALSE; } return TRUE; } static void fu_pxi_ble_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_pxi_ble_device_init(FuPxiBleDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_vendor_id(FU_DEVICE(self), "USB:0x093A"); fu_device_add_protocol(FU_DEVICE(self), "com.pixart.rf"); fu_device_retry_set_delay(FU_DEVICE(self), 50); self->retransmit_id = PXI_HID_DEV_OTA_RETRANSMIT_REPORT_ID; self->feature_report_id = PXI_HID_DEV_OTA_FEATURE_REPORT_ID; self->input_report_id = PXI_HID_DEV_OTA_INPUT_REPORT_ID; fu_device_register_private_flag(FU_DEVICE(self), FU_PXI_DEVICE_FLAG_IS_HPAC, "is-hpac"); } static void fu_pxi_ble_device_finalize(GObject *object) { FuPxiBleDevice *self = FU_PXI_BLE_DEVICE(object); g_free(self->model_name); G_OBJECT_CLASS(fu_pxi_ble_device_parent_class)->finalize(object); } static void fu_pxi_ble_device_class_init(FuPxiBleDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_pxi_ble_device_finalize; klass_device->probe = fu_pxi_ble_device_probe; klass_device->setup = fu_pxi_ble_device_setup; klass_device->to_string = fu_pxi_ble_device_to_string; klass_device->write_firmware = fu_pxi_ble_device_write_firmware; klass_device->prepare_firmware = fu_pxi_ble_device_prepare_firmware; klass_device->set_progress = fu_pxi_ble_device_set_progress; } fwupd-1.9.16/plugins/pixart-rf/fu-pxi-ble-device.h000066400000000000000000000005421460375044200217160ustar00rootroot00000000000000/* * Copyright (C) 2020 Jimmy Yu * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_PXI_BLE_DEVICE (fu_pxi_ble_device_get_type()) G_DECLARE_FINAL_TYPE(FuPxiBleDevice, fu_pxi_ble_device, FU, PXI_BLE_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/pixart-rf/fu-pxi-common.c000066400000000000000000000100441460375044200212000ustar00rootroot00000000000000/* * Copyright (C) 2021 Jimmy Yu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-pxi-common.h" #include "fu-pxi-struct.h" void fu_pxi_ota_fw_state_to_string(struct ota_fw_state *fwstate, guint idt, GString *str) { fu_string_append_kx(str, idt, "Status", fwstate->status); fu_string_append_kx(str, idt, "NewFlow", fwstate->new_flow); fu_string_append_kx(str, idt, "CurrentObjectOffset", fwstate->offset); fu_string_append_kx(str, idt, "CurrentChecksum", fwstate->checksum); fu_string_append_kx(str, idt, "MaxObjectSize", fwstate->max_object_size); fu_string_append_kx(str, idt, "MtuSize", fwstate->mtu_size); fu_string_append_kx(str, idt, "PacketReceiptNotificationThreshold", fwstate->prn_threshold); fu_string_append(str, idt, "SpecCheckResult", fu_pxi_ota_spec_check_result_to_string(fwstate->spec_check_result)); } gboolean fu_pxi_ota_fw_state_parse(struct ota_fw_state *fwstate, const guint8 *buf, gsize bufsz, gsize offset, GError **error) { if (!fu_memread_uint8_safe(buf, bufsz, offset + 0x00, &fwstate->status, error)) return FALSE; if (!fu_memread_uint8_safe(buf, bufsz, offset + 0x01, &fwstate->new_flow, error)) return FALSE; if (!fu_memread_uint16_safe(buf, bufsz, offset + 0x2, &fwstate->offset, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(buf, bufsz, offset + 0x4, &fwstate->checksum, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, offset + 0x06, &fwstate->max_object_size, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(buf, bufsz, offset + 0x0A, &fwstate->mtu_size, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(buf, bufsz, offset + 0x0C, &fwstate->prn_threshold, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint8_safe(buf, bufsz, offset + 0x0E, &fwstate->spec_check_result, error)) return FALSE; /* success */ return TRUE; } gboolean fu_pxi_composite_receiver_cmd(guint8 opcode, guint8 sn, guint8 target, GByteArray *wireless_mod_cmd, GByteArray *ota_cmd, GError **error) { guint8 checksum = 0x0; guint8 hid_sn = sn; guint8 len = 0x0; guint8 ota_sn = sn + 1; guint8 rf_cmd_code = FU_PXI_BLE_DEVICE_RF_CMD_CODE; guint8 rid = PXI_HID_WIRELESS_DEV_OTA_REPORT_ID; if (ota_cmd == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "ota cmd is NULL"); return FALSE; } /* append ota dispatch header */ fu_byte_array_append_uint8(wireless_mod_cmd, opcode); /* wireless module ota op code */ fu_byte_array_append_uint8(wireless_mod_cmd, ota_sn); /* wireless module ota command sn */ /* append ota command length and content */ for (guint idx = 0; idx < ota_cmd->len; idx++) fu_byte_array_append_uint8(wireless_mod_cmd, ota_cmd->data[idx]); /* append target of wireless module and hid command serial number */ g_byte_array_prepend(wireless_mod_cmd, &target, 0x01); /* target */ g_byte_array_prepend(wireless_mod_cmd, &hid_sn, 0x01); /* hid command sn */ /* prepend length and rf command code, the param len not include "hid_sn" byte and "target" * byte */ len = wireless_mod_cmd->len - 2; g_byte_array_prepend(wireless_mod_cmd, &len, 0x01); g_byte_array_prepend(wireless_mod_cmd, &rf_cmd_code, 0x01); /* command code */ /* prepend checksum */ checksum = fu_sum8(wireless_mod_cmd->data, wireless_mod_cmd->len); g_byte_array_prepend(wireless_mod_cmd, &checksum, 0x01); /* prepend feature report id */ g_byte_array_prepend(wireless_mod_cmd, &rid, 0x01); return TRUE; } gchar * fu_pxi_hpac_version_info_parse(const guint16 hpac_ver) { return g_strdup_printf("%u%u.%u%u.%u", hpac_ver / 10000, (hpac_ver / 1000) % 10, ((hpac_ver / 100) % 10), (hpac_ver / 10) % 10, hpac_ver % 10); } fwupd-1.9.16/plugins/pixart-rf/fu-pxi-common.h000066400000000000000000000055031460375044200212110ustar00rootroot00000000000000/* * Copyright (C) 2021 Jimmy Yu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_PXI_DEVICE_FLAG_IS_HPAC (1 << 0) #define PXI_HID_WIRELESS_DEV_OTA_REPORT_ID 0x03 #define FU_PXI_DEVICE_CMD_FW_OTA_INIT 0x10u #define FU_PXI_DEVICE_CMD_FW_WRITE 0x17u #define FU_PXI_DEVICE_CMD_FW_UPGRADE 0x18u #define FU_PXI_DEVICE_CMD_FW_MCU_RESET 0x22u #define FU_PXI_DEVICE_CMD_FW_GET_INFO 0x23u #define FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE 0x25u #define FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW 0x27u #define FU_PXI_DEVICE_CMD_FW_OTA_RETRANSMIT 0x28u #define FU_PXI_DEVICE_CMD_FW_OTA_DISCONNECT 0x29u #define FU_PXI_DEVICE_CMD_FW_OTA_GET_NUM_OF_MODELS 0x2au #define FU_PXI_DEVICE_CMD_FW_OTA_GET_MODEL 0x2bu #define FU_PXI_DEVICE_CMD_FW_OTA_PAYLOAD_CONTENT 0x40u #define FU_PXI_DEVICE_CMD_FW_OTA_CHECK_CRC 0x41u #define FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW_CHECK 0x42u #define FU_PXI_DEVICE_CMD_FW_OTA_PRECEDING 0x44u #define FU_PXI_BLE_DEVICE_RF_CMD_CODE 0x65u #define FU_PXI_BLE_DEVICE_RF_CMD_HID_SN 0x0 #define FU_PXI_BLE_DEVICE_RF_CMD_HID_SN 0x0 #define FU_PXI_WIRELESS_DEVICE_TARGET_RECEIVER 0 #define FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ 64 /* bytes */ #define FU_PXI_DEVICE_MODEL_NAME_LEN 12 /* bytes */ #define FU_PXI_DEVICE_OBJECT_SIZE_MAX 4096 /* bytes */ #define FU_PXI_WIRELESS_DEVICE_RETRY_MAXIMUM 1000 #define FU_PXI_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ /* pixart device model structure */ struct ota_fw_dev_model { guint8 status; guint8 name[FU_PXI_DEVICE_MODEL_NAME_LEN]; guint8 type; guint8 target; guint8 version[5]; guint16 checksum; }; /* pixart fw info structure */ struct ota_fw_info { guint8 status; guint8 version[5]; guint16 checksum; }; /* ota disconnect reason */ enum ota_disconnect_reason { OTA_CODE_JUMP = 1, /* OTA code jump */ OTA_UPDATE_DONE = 2, /* OTA update done */ OTA_RESET, /* OTA reset */ }; /* ota rsp code for wireless module */ enum wireless_module_type { OTA_WIRELESS_MODULE_TYPE_MOUSE, OTA_WIRELESS_MODULE_TYPE_KEYBOARD, OTA_WIRELESS_MODULE_TYPE_RECEIVER, }; struct ota_fw_state { guint8 status; guint8 new_flow; guint16 offset; guint16 checksum; guint32 max_object_size; guint16 mtu_size; guint16 prn_threshold; guint8 spec_check_result; }; gboolean fu_pxi_composite_receiver_cmd(guint8 opcode, guint8 sn, guint8 target, GByteArray *wireless_mod_cmd, GByteArray *ota_cmd, GError **error); void fu_pxi_ota_fw_state_to_string(struct ota_fw_state *fwstate, guint idt, GString *str); gboolean fu_pxi_ota_fw_state_parse(struct ota_fw_state *fwstate, const guint8 *buf, gsize bufsz, gsize offset, GError **error); gchar * fu_pxi_hpac_version_info_parse(const guint16 hpac_ver); fwupd-1.9.16/plugins/pixart-rf/fu-pxi-firmware.c000066400000000000000000000174111460375044200215310ustar00rootroot00000000000000/* * Copyright (C) 2020 Jimmy Yu * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-pxi-common.h" #include "fu-pxi-firmware.h" #define PIXART_RF_FW_HEADER_SIZE 32 /* bytes */ #define PIXART_RF_FW_HEADER_TAG_OFFSET 24 /* The hpac header is start from 821st byte from the end */ #define PIXART_RF_FW_HEADER_HPAC_POS_FROM_END 821 #define PIXART_RF_FW_HEADER_HPAC_VERSION_POS_FROM_END 823 #define PIXART_RF_FW_HEADER_MAGIC 0x55AA55AA55AA55AA struct _FuPxiFirmware { FuFirmware parent_instance; gchar *model_name; gboolean is_hpac; }; G_DEFINE_TYPE(FuPxiFirmware, fu_pxi_firmware, FU_TYPE_FIRMWARE) const gchar * fu_pxi_firmware_get_model_name(FuPxiFirmware *self) { g_return_val_if_fail(FU_IS_PXI_FIRMWARE(self), NULL); return self->model_name; } static void fu_pxi_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuPxiFirmware *self = FU_PXI_FIRMWARE(firmware); fu_xmlb_builder_insert_kv(bn, "model_name", self->model_name); } static gboolean fu_pxi_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { guint64 magic = 0; FuPxiFirmware *self = FU_PXI_FIRMWARE(firmware); /* is a footer, in normal bin file, the header is starts from the 32nd byte from the end. */ if (!fu_memread_uint64_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), g_bytes_get_size(fw) - PIXART_RF_FW_HEADER_SIZE + PIXART_RF_FW_HEADER_TAG_OFFSET, &magic, G_BIG_ENDIAN, error)) { g_prefix_error(error, "failed to read magic: "); return FALSE; } if (magic != PIXART_RF_FW_HEADER_MAGIC) { /* if the magic number is not found, then start from the 821st byte from the end for * HPAC header */ if (!fu_memread_uint64_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), g_bytes_get_size(fw) - PIXART_RF_FW_HEADER_HPAC_POS_FROM_END + PIXART_RF_FW_HEADER_TAG_OFFSET, &magic, G_BIG_ENDIAN, error)) { g_prefix_error(error, "failed to read magic: "); return FALSE; } if (magic != PIXART_RF_FW_HEADER_MAGIC) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid magic, expected 0x%08X got 0x%08X", (guint32)PIXART_RF_FW_HEADER_MAGIC, (guint32)magic); return FALSE; } self->is_hpac = TRUE; } /* success */ return TRUE; } static gboolean fu_pxi_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuPxiFirmware *self = FU_PXI_FIRMWARE(firmware); const guint8 *buf; gsize bufsz = 0; guint32 version_raw = 0; guint8 fw_header[PIXART_RF_FW_HEADER_SIZE]; guint8 model_name[FU_PXI_DEVICE_MODEL_NAME_LEN] = {0x0}; guint16 hpac_ver = 0; g_autofree gchar *version = NULL; /* get fw header from buf */ buf = g_bytes_get_data(fw, &bufsz); if (fu_pxi_firmware_is_hpac(self)) { if (!fu_memcpy_safe(fw_header, sizeof(fw_header), 0x0, buf, bufsz, bufsz - PIXART_RF_FW_HEADER_HPAC_POS_FROM_END, sizeof(fw_header), error)) { g_prefix_error(error, "failed to read fw header: "); return FALSE; } } else { if (!fu_memcpy_safe(fw_header, sizeof(fw_header), 0x0, buf, bufsz, bufsz - sizeof(fw_header), sizeof(fw_header), error)) { g_prefix_error(error, "failed to read fw header: "); return FALSE; } } fu_dump_raw(G_LOG_DOMAIN, "fw header", fw_header, sizeof(fw_header)); /* set fw version */ if (fu_pxi_firmware_is_hpac(self)) { if (!fu_memread_uint16_safe(buf, bufsz, bufsz - PIXART_RF_FW_HEADER_HPAC_VERSION_POS_FROM_END, &hpac_ver, G_BIG_ENDIAN, error)) return FALSE; version_raw = hpac_ver; version = fu_pxi_hpac_version_info_parse(hpac_ver); } else { version_raw = (((guint32)(fw_header[0] - '0')) << 16) + (((guint32)(fw_header[2] - '0')) << 8) + (guint32)(fw_header[4] - '0'); version = fu_version_from_uint32(version_raw, FWUPD_VERSION_FORMAT_DELL_BIOS); } fu_firmware_set_version_raw(firmware, version_raw); fu_firmware_set_version(firmware, version); /* set fw model name */ if (!fu_memcpy_safe(model_name, sizeof(model_name), 0x0, fw_header, sizeof(fw_header), 0x05, sizeof(model_name), error)) { g_prefix_error(error, "failed to get fw model name: "); return FALSE; } self->model_name = g_strndup((gchar *)model_name, sizeof(model_name)); /* success */ return TRUE; } static gboolean fu_pxi_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuPxiFirmware *self = FU_PXI_FIRMWARE(firmware); const gchar *tmp; /* optional properties */ tmp = xb_node_query_text(n, "model_name", NULL); if (tmp != NULL) self->model_name = g_strdup(tmp); /* success */ return TRUE; } static GByteArray * fu_pxi_firmware_write(FuFirmware *firmware, GError **error) { FuPxiFirmware *self = FU_PXI_FIRMWARE(firmware); guint8 fw_header[PIXART_RF_FW_HEADER_SIZE] = {0x0}; guint64 version_raw = fu_firmware_get_version_raw(firmware); g_autoptr(GByteArray) buf = NULL; g_autoptr(GBytes) blob = NULL; const guint8 tag[] = { 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, }; /* data first */ blob = fu_firmware_get_bytes_with_patches(firmware, error); if (blob == NULL) return NULL; if (fu_pxi_firmware_is_hpac(self)) { buf = g_byte_array_sized_new(g_bytes_get_size(blob)); fu_byte_array_append_bytes(buf, blob); return g_steal_pointer(&buf); } buf = g_byte_array_sized_new(g_bytes_get_size(blob) + sizeof(fw_header)); fu_byte_array_append_bytes(buf, blob); /* footer */ if (!fu_memcpy_safe(fw_header, sizeof(fw_header), PIXART_RF_FW_HEADER_TAG_OFFSET, /* dst */ tag, sizeof(tag), 0x0, /* src */ sizeof(tag), error)) return NULL; fw_header[0] = ((version_raw >> 16) & 0xff) + '0'; fw_header[1] = '.'; fw_header[2] = ((version_raw >> 8) & 0xff) + '0'; fw_header[3] = '.'; fw_header[4] = ((version_raw >> 0) & 0xff) + '0'; if (!g_ascii_isdigit(fw_header[0]) || !g_ascii_isdigit(fw_header[2]) || !g_ascii_isdigit(fw_header[4])) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot write invalid version number 0x%x", (guint)version_raw); return NULL; } if (self->model_name != NULL) { gsize model_namesz = MIN(strlen(self->model_name), FU_PXI_DEVICE_MODEL_NAME_LEN); if (!fu_memcpy_safe(fw_header, sizeof(fw_header), 0x05, /* dst */ (const guint8 *)self->model_name, model_namesz, 0x0, /* src */ model_namesz, error)) { g_prefix_error(error, "failed to get fw model name: "); return NULL; } } g_byte_array_append(buf, fw_header, sizeof(fw_header)); return g_steal_pointer(&buf); } static void fu_pxi_firmware_init(FuPxiFirmware *self) { } static void fu_pxi_firmware_finalize(GObject *object) { FuPxiFirmware *self = FU_PXI_FIRMWARE(object); g_free(self->model_name); G_OBJECT_CLASS(fu_pxi_firmware_parent_class)->finalize(object); } static void fu_pxi_firmware_class_init(FuPxiFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_pxi_firmware_finalize; klass_firmware->check_magic = fu_pxi_firmware_check_magic; klass_firmware->parse = fu_pxi_firmware_parse; klass_firmware->build = fu_pxi_firmware_build; klass_firmware->write = fu_pxi_firmware_write; klass_firmware->export = fu_pxi_firmware_export; } FuFirmware * fu_pxi_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_PXI_FIRMWARE, NULL)); } gboolean fu_pxi_firmware_is_hpac(FuPxiFirmware *self) { return self->is_hpac; } fwupd-1.9.16/plugins/pixart-rf/fu-pxi-firmware.h000066400000000000000000000007641460375044200215410ustar00rootroot00000000000000/* * Copyright (C) 2020 Jimmy Yu * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_PXI_DEVICE_MODEL_NAME_LEN 12 /* bytes */ #define FU_TYPE_PXI_FIRMWARE (fu_pxi_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuPxiFirmware, fu_pxi_firmware, FU, PXI_FIRMWARE, FuFirmware) FuFirmware * fu_pxi_firmware_new(void); const gchar * fu_pxi_firmware_get_model_name(FuPxiFirmware *self); gboolean fu_pxi_firmware_is_hpac(FuPxiFirmware *self); fwupd-1.9.16/plugins/pixart-rf/fu-pxi-plugin.c000066400000000000000000000022451460375044200212120ustar00rootroot00000000000000/* * Copyright (C) 2020 Jimmy Yu * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-pxi-ble-device.h" #include "fu-pxi-firmware.h" #include "fu-pxi-plugin.h" #include "fu-pxi-receiver-device.h" struct _FuPxiPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuPxiPlugin, fu_pxi_plugin, FU_TYPE_PLUGIN) static void fu_pxi_plugin_init(FuPxiPlugin *self) { } static void fu_pxi_plugin_object_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_set_name(plugin, "pixart_rf"); } static void fu_pxi_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "hidraw"); fu_plugin_add_device_gtype(plugin, FU_TYPE_PXI_BLE_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_PXI_RECEIVER_DEVICE); fu_plugin_add_firmware_gtype(plugin, "pixart", FU_TYPE_PXI_FIRMWARE); } static void fu_pxi_plugin_class_init(FuPxiPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->constructed = fu_pxi_plugin_object_constructed; plugin_class->constructed = fu_pxi_plugin_constructed; } fwupd-1.9.16/plugins/pixart-rf/fu-pxi-plugin.h000066400000000000000000000003371460375044200212170ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuPxiPlugin, fu_pxi_plugin, FU, PXI_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/pixart-rf/fu-pxi-receiver-device.c000066400000000000000000000674701460375044200227700ustar00rootroot00000000000000/* * Copyright (C) 2020 Jimmy Yu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_HIDRAW_H #include #include #endif #include "fu-pxi-firmware.h" #include "fu-pxi-receiver-device.h" #include "fu-pxi-struct.h" #include "fu-pxi-wireless-device.h" struct _FuPxiReceiverDevice { FuUdevDevice parent_instance; struct ota_fw_state fwstate; guint8 sn; guint vendor; guint product; }; G_DEFINE_TYPE(FuPxiReceiverDevice, fu_pxi_receiver_device, FU_TYPE_UDEV_DEVICE) #ifdef HAVE_HIDRAW_H static gboolean fu_pxi_receiver_device_get_raw_info(FuPxiReceiverDevice *self, struct hidraw_devinfo *info, GError **error) { if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGRAWINFO, (guint8 *)info, NULL, FU_PXI_DEVICE_IOCTL_TIMEOUT, error)) { return FALSE; } return TRUE; } #endif static void fu_pxi_receiver_device_to_string(FuDevice *device, guint idt, GString *str) { FuPxiReceiverDevice *self = FU_PXI_RECEIVER_DEVICE(device); fu_pxi_ota_fw_state_to_string(&self->fwstate, idt, str); fu_string_append_kx(str, idt, "Vendor", self->vendor); fu_string_append_kx(str, idt, "Product", self->product); } static FuFirmware * fu_pxi_receiver_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_pxi_firmware_new(); if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; if (fu_device_has_private_flag(device, FU_PXI_DEVICE_FLAG_IS_HPAC) && fu_pxi_firmware_is_hpac(FU_PXI_FIRMWARE(firmware))) { g_autoptr(GBytes) fw_tmp = NULL; guint32 hpac_fw_size = 0; const guint8 *fw_ptr = g_bytes_get_data(fw, NULL); if (!fu_memread_uint32_safe(fw_ptr, g_bytes_get_size(fw), 9, &hpac_fw_size, G_LITTLE_ENDIAN, error)) return NULL; hpac_fw_size += 264; fw_tmp = fu_bytes_new_offset(fw, 9, hpac_fw_size, error); if (fw_tmp == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "HPAC F/W preparation failed."); return NULL; } fu_firmware_set_bytes(firmware, fw_tmp); } else if (fu_device_has_private_flag(device, FU_PXI_DEVICE_FLAG_IS_HPAC) != fu_pxi_firmware_is_hpac(FU_PXI_FIRMWARE(firmware))) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "The firmware is incompatible with the device"); return NULL; } return g_steal_pointer(&firmware); } static gboolean fu_pxi_receiver_device_set_feature(FuPxiReceiverDevice *self, const guint8 *buf, guint bufsz, GError **error) { #ifdef HAVE_HIDRAW_H fu_dump_raw(G_LOG_DOMAIN, "SetFeature", buf, bufsz); return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(bufsz), (guint8 *)buf, NULL, FU_PXI_DEVICE_IOCTL_TIMEOUT, error); #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_pxi_receiver_device_get_feature(FuPxiReceiverDevice *self, guint8 *buf, guint bufsz, GError **error) { #ifdef HAVE_HIDRAW_H if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGFEATURE(bufsz), buf, NULL, FU_PXI_DEVICE_IOCTL_TIMEOUT, error)) { return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "GetFeature", buf, bufsz); return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_pxi_receiver_device_fw_ota_init_new(FuPxiReceiverDevice *device, gsize bufsz, GError **error) { guint8 fw_version[10] = {0x0}; g_autoptr(GByteArray) receiver_device_cmd = g_byte_array_new(); g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); FuPxiReceiverDevice *self = FU_PXI_RECEIVER_DEVICE(device); fu_byte_array_append_uint8(ota_cmd, 0X06); /* ota init new command length */ fu_byte_array_append_uint8(ota_cmd, FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW); /* ota init new op code */ fu_byte_array_append_uint32(ota_cmd, bufsz, G_LITTLE_ENDIAN); /* fw size */ fu_byte_array_append_uint8(ota_cmd, 0x0); /* ota setting */ g_byte_array_append(ota_cmd, fw_version, sizeof(fw_version)); /* ota version */ self->sn++; if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW, self->sn, FU_PXI_WIRELESS_DEVICE_TARGET_RECEIVER, receiver_device_cmd, ota_cmd, error)) return FALSE; if (!fu_pxi_receiver_device_set_feature(self, receiver_device_cmd->data, receiver_device_cmd->len, error)) return FALSE; return TRUE; } static gboolean fu_pxi_receiver_device_fw_ota_ini_new_check(FuPxiReceiverDevice *device, GError **error) { FuPxiReceiverDevice *self = FU_PXI_RECEIVER_DEVICE(device); guint8 buf[FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ] = {0x0}; g_autoptr(GByteArray) receiver_device_cmd = g_byte_array_new(); g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); /* ota command */ fu_byte_array_append_uint8(ota_cmd, 0x1); /* ota command length */ fu_byte_array_append_uint8(ota_cmd, FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW_CHECK); /* ota command */ self->sn++; /* get pixart wireless module ota command */ if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW_CHECK, self->sn, FU_PXI_WIRELESS_DEVICE_TARGET_RECEIVER, receiver_device_cmd, ota_cmd, error)) return FALSE; if (!fu_pxi_receiver_device_set_feature(self, receiver_device_cmd->data, receiver_device_cmd->len, error)) return FALSE; /* delay for wireless module device read command */ fu_device_sleep(FU_DEVICE(device), 5); /* ms */ buf[0] = PXI_HID_WIRELESS_DEV_OTA_REPORT_ID; if (!fu_pxi_receiver_device_get_feature(self, buf, sizeof(buf), error)) return FALSE; /* shared state */ return fu_pxi_ota_fw_state_parse(&self->fwstate, buf, sizeof(buf), 0x09, error); } static gboolean fu_pxi_receiver_device_get_cmd_response(FuPxiReceiverDevice *device, guint8 *buf, guint bufsz, GError **error) { guint16 retry = 0; while (1) { guint8 sn = 0x0; memset(buf, 0, bufsz); buf[0] = PXI_HID_WIRELESS_DEV_OTA_REPORT_ID; fu_device_sleep(FU_DEVICE(device), 5); /* ms */ if (!fu_pxi_receiver_device_get_feature(device, buf, bufsz, error)) return FALSE; if (!fu_memread_uint8_safe(buf, bufsz, 0x4, &sn, error)) return FALSE; if (device->sn != sn) retry++; else break; if (retry == FU_PXI_WIRELESS_DEVICE_RETRY_MAXIMUM) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "reach retry maximum, hid sn fail, " "got 0x%04x, expected 0x%04x", sn, device->sn); return FALSE; } } return TRUE; } static gboolean fu_pxi_receiver_device_check_crc(FuDevice *device, guint16 checksum, GError **error) { FuPxiReceiverDevice *self = FU_PXI_RECEIVER_DEVICE(device); guint8 buf[FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ] = {0x0}; guint8 status = 0x0; g_autoptr(GByteArray) receiver_device_cmd = g_byte_array_new(); g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); /* ota check crc command */ fu_byte_array_append_uint8(ota_cmd, 0x3); /* ota command length */ fu_byte_array_append_uint8(ota_cmd, FU_PXI_DEVICE_CMD_FW_OTA_CHECK_CRC); /* ota command */ fu_byte_array_append_uint16(ota_cmd, checksum, G_LITTLE_ENDIAN); /* checkesum */ /* increase the serial number */ self->sn++; /* get pixart wireless module for ota check crc command */ if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_OTA_CHECK_CRC, self->sn, FU_PXI_WIRELESS_DEVICE_TARGET_RECEIVER, receiver_device_cmd, ota_cmd, error)) return FALSE; if (!fu_pxi_receiver_device_set_feature(self, receiver_device_cmd->data, receiver_device_cmd->len, error)) return FALSE; if (!fu_pxi_receiver_device_get_cmd_response(self, buf, sizeof(buf), error)) return FALSE; if (!fu_memread_uint8_safe(buf, sizeof(buf), 0x5, &status, error)) return FALSE; if (status == FU_PXI_WIRELESS_MODULE_OTA_RSP_CODE_ERROR) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "checksum error: expected 0x%04x", checksum); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_receiver_device_fw_object_create(FuDevice *device, FuChunk *chk, GError **error) { FuPxiReceiverDevice *self = FU_PXI_RECEIVER_DEVICE(device); guint8 buf[FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ] = {0x0}; guint8 status = 0x0; g_autoptr(GByteArray) receiver_device_cmd = g_byte_array_new(); g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); /* ota object create command */ fu_byte_array_append_uint8(ota_cmd, 0x9); /* ota command length */ fu_byte_array_append_uint8(ota_cmd, FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE); /* ota command */ fu_byte_array_append_uint32(ota_cmd, fu_chunk_get_address(chk), G_LITTLE_ENDIAN); fu_byte_array_append_uint32(ota_cmd, fu_chunk_get_data_sz(chk), G_LITTLE_ENDIAN); /* increase the serial number */ self->sn++; /* get pixart wireless module for ota object create command */ if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE, self->sn, FU_PXI_WIRELESS_DEVICE_TARGET_RECEIVER, receiver_device_cmd, ota_cmd, error)) return FALSE; if (!fu_pxi_receiver_device_set_feature(self, receiver_device_cmd->data, receiver_device_cmd->len, error)) return FALSE; if (!fu_pxi_receiver_device_get_cmd_response(self, buf, sizeof(buf), error)) return FALSE; if (!fu_memread_uint8_safe(buf, sizeof(buf), 0x5, &status, error)) return FALSE; if (status != FU_PXI_WIRELESS_MODULE_OTA_RSP_CODE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cmd rsp check fail: %s [0x%02x]", fu_pxi_wireless_module_ota_rsp_code_to_string(status), status); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_receiver_device_write_payload(FuDevice *device, FuChunk *chk, GError **error) { FuPxiReceiverDevice *self = FU_PXI_RECEIVER_DEVICE(device); g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); g_autoptr(GByteArray) receiver_device_cmd = g_byte_array_new(); /* ota write payload command */ fu_byte_array_append_uint8(ota_cmd, fu_chunk_get_data_sz(chk)); /* ota command length */ g_byte_array_append(ota_cmd, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); /* payload content */ /* increase the serial number */ self->sn++; /* get pixart wireless module for writes payload command */ if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_OTA_PAYLOAD_CONTENT, self->sn, FU_PXI_WIRELESS_DEVICE_TARGET_RECEIVER, receiver_device_cmd, ota_cmd, error)) return FALSE; return fu_pxi_receiver_device_set_feature(self, receiver_device_cmd->data, receiver_device_cmd->len, error); } static gboolean fu_pxi_receiver_device_write_chunk(FuDevice *device, FuChunk *chk, GError **error) { FuPxiReceiverDevice *self = FU_PXI_RECEIVER_DEVICE(device); guint32 prn = 0; g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GBytes) chk_bytes = fu_chunk_get_bytes(chk); /* send create fw object command */ if (!fu_pxi_receiver_device_fw_object_create(device, chk, error)) return FALSE; /* write payload */ chunks = fu_chunk_array_new_from_bytes(chk_bytes, fu_chunk_get_address(chk), self->fwstate.mtu_size); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk2 = fu_chunk_array_index(chunks, i); /* calculate checksum of each payload packet */ self->fwstate.checksum += fu_sum16(fu_chunk_get_data(chk2), fu_chunk_get_data_sz(chk2)); if (!fu_pxi_receiver_device_write_payload(device, chk2, error)) return FALSE; prn++; /* check crc at fw when PRN over threshold write or * offset reach max object sz or write offset reach fw length */ if (prn >= self->fwstate.prn_threshold || i == fu_chunk_array_length(chunks) - 1) { if (!fu_pxi_receiver_device_check_crc(device, self->fwstate.checksum, error)) return FALSE; prn = 0; } } /* success */ return TRUE; } static gboolean fu_pxi_receiver_device_fw_upgrade(FuDevice *device, FuFirmware *firmware, FuProgress *progress, GError **error) { FuPxiReceiverDevice *self = FU_PXI_RECEIVER_DEVICE(device); const gchar *version; guint8 fw_version[5] = {0x0}; guint8 res[FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ] = {0x0}; guint8 result = 0x0; g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); g_autoptr(GByteArray) receiver_device_cmd = g_byte_array_new(); g_autoptr(GBytes) fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 5, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 95, NULL); fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* ota fw upgrade command */ fu_byte_array_append_uint8(ota_cmd, 0x0c); /* ota fw upgrade command length */ fu_byte_array_append_uint8( ota_cmd, FU_PXI_DEVICE_CMD_FW_UPGRADE); /* ota fw upgrade command opccode */ fu_byte_array_append_uint32(ota_cmd, g_bytes_get_size(fw), G_LITTLE_ENDIAN); /* ota fw upgrade command fw size */ fu_byte_array_append_uint16(ota_cmd, fu_sum16_bytes(fw), G_LITTLE_ENDIAN); /* ota fw upgrade command checksum */ if (!fu_device_has_private_flag(FU_DEVICE(self), FU_PXI_DEVICE_FLAG_IS_HPAC)) { version = fu_firmware_get_version(firmware); if (!fu_memcpy_safe(fw_version, sizeof(fw_version), 0x0, /* dst */ (guint8 *)version, strlen(version), 0x0, /* src */ sizeof(fw_version), error)) return FALSE; } g_byte_array_append(ota_cmd, fw_version, sizeof(fw_version)); fu_dump_raw(G_LOG_DOMAIN, "ota_cmd ", ota_cmd->data, ota_cmd->len); self->sn++; /* get pixart wireless module ota command */ if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_UPGRADE, self->sn, FU_PXI_WIRELESS_DEVICE_TARGET_RECEIVER, receiver_device_cmd, ota_cmd, error)) return FALSE; fu_progress_step_done(progress); /* send ota fw upgrade command */ if (!fu_pxi_receiver_device_set_feature(self, receiver_device_cmd->data, receiver_device_cmd->len, error)) return FALSE; /* delay for wireless module device read command */ fu_device_sleep(device, 5); /* ms */ if (!fu_pxi_receiver_device_get_cmd_response(self, res, sizeof(res), error)) return FALSE; if (!fu_memread_uint8_safe(res, sizeof(res), 0x5, &result, error)) return FALSE; if (result != FU_PXI_WIRELESS_MODULE_OTA_RSP_CODE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cmd rsp check fail: %s [0x%02x]", fu_pxi_wireless_module_ota_rsp_code_to_string(result), result); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_pxi_receiver_device_reset(FuDevice *device, GError **error) { FuPxiReceiverDevice *self = FU_PXI_RECEIVER_DEVICE(device); g_autoptr(GByteArray) receiver_device_cmd = g_byte_array_new(); g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); /* ota mcu reset command */ fu_byte_array_append_uint8(ota_cmd, 0x1); /* ota mcu reset command */ fu_byte_array_append_uint8( ota_cmd, FU_PXI_DEVICE_CMD_FW_MCU_RESET); /* ota mcu reset command op code */ fu_byte_array_append_uint8(ota_cmd, OTA_RESET); /* ota mcu reset command reason */ self->sn++; /* get pixart wireless module ota command */ if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_MCU_RESET, self->sn, FU_PXI_WIRELESS_DEVICE_TARGET_RECEIVER, receiver_device_cmd, ota_cmd, error)) return FALSE; /* send ota mcu reset command */ return fu_pxi_receiver_device_set_feature(self, receiver_device_cmd->data, receiver_device_cmd->len, error); } static gboolean fu_pxi_receiver_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuPxiReceiverDevice *self = FU_PXI_RECEIVER_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 9, "ota-init"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, NULL); /* get the default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* send fw ota init command */ if (!fu_pxi_receiver_device_fw_ota_init_new(self, g_bytes_get_size(fw), error)) return FALSE; if (!fu_pxi_receiver_device_fw_ota_ini_new_check(self, error)) return FALSE; fu_progress_step_done(progress); chunks = fu_chunk_array_new_from_bytes(fw, 0x0, FU_PXI_DEVICE_OBJECT_SIZE_MAX); /* prepare write fw into device */ self->fwstate.offset = 0; self->fwstate.checksum = 0; /* write fw into device */ for (guint i = self->fwstate.offset; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_pxi_receiver_device_write_chunk(device, chk, error)) return FALSE; fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + self->fwstate.offset + 1, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* fw upgrade command */ if (!fu_pxi_receiver_device_fw_upgrade(device, firmware, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* delay for wireless module device read command */ fu_device_sleep(device, 5); /* ms */ /* send device reset command */ if (!fu_pxi_receiver_device_reset(device, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_pxi_receiver_device_get_peripheral_info(FuPxiReceiverDevice *device, struct ota_fw_dev_model *model, guint idx, GError **error) { guint8 buf[FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ] = {0x0}; guint16 checksum = 0; guint16 hpac_ver = 0; g_autofree gchar *version_str = NULL; g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); g_autoptr(GByteArray) receiver_device_cmd = g_byte_array_new(); fu_byte_array_append_uint8(ota_cmd, 0x1); /* ota init new command length */ fu_byte_array_append_uint8(ota_cmd, FU_PXI_DEVICE_CMD_FW_OTA_GET_MODEL); fu_byte_array_append_uint8(ota_cmd, idx); device->sn++; if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_OTA_GET_MODEL, device->sn, FU_PXI_WIRELESS_DEVICE_TARGET_RECEIVER, receiver_device_cmd, ota_cmd, error)) return FALSE; if (!fu_pxi_receiver_device_set_feature(device, receiver_device_cmd->data, receiver_device_cmd->len, error)) return FALSE; fu_device_sleep(FU_DEVICE(device), 5); /* ms */ buf[0] = PXI_HID_WIRELESS_DEV_OTA_REPORT_ID; if (!fu_pxi_receiver_device_get_feature(device, buf, sizeof(buf), error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "model_info", buf, sizeof(buf)); if (!fu_memread_uint8_safe(buf, sizeof(buf), 0x9, &model->status, error)) return FALSE; if (!fu_memcpy_safe(model->name, FU_PXI_DEVICE_MODEL_NAME_LEN, 0x0, /* dst */ buf, FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ, 0xa, /* src */ FU_PXI_DEVICE_MODEL_NAME_LEN, error)) return FALSE; if (!fu_memread_uint8_safe(buf, sizeof(buf), 0x16, &model->type, error)) return FALSE; if (!fu_memread_uint8_safe(buf, sizeof(buf), 0x17, &model->target, error)) return FALSE; if (!fu_memcpy_safe(model->version, 5, 0x0, /* dst */ buf, sizeof(buf), 0x18, /* src */ 5, error)) return FALSE; if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x1D, &checksum, G_LITTLE_ENDIAN, error)) return FALSE; /* set current version and model name */ model->checksum = checksum; g_debug("checksum %x", model->checksum); if (!fu_device_has_private_flag(FU_DEVICE(device), FU_PXI_DEVICE_FLAG_IS_HPAC)) { version_str = g_strndup((gchar *)model->version, 5); } else { if (!fu_memread_uint16_safe(model->version, 5, 3, &hpac_ver, G_BIG_ENDIAN, error)) return FALSE; version_str = fu_pxi_hpac_version_info_parse(hpac_ver); } g_debug("version_str %s", version_str); return TRUE; } static gboolean fu_pxi_receiver_device_get_peripheral_num(FuPxiReceiverDevice *device, guint8 *num_of_models, GError **error) { FuPxiReceiverDevice *self = FU_PXI_RECEIVER_DEVICE(device); guint8 buf[FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ] = {0x0}; g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); g_autoptr(GByteArray) receiver_device_cmd = g_byte_array_new(); fu_byte_array_append_uint8(ota_cmd, 0x1); /* ota init new command length */ fu_byte_array_append_uint8( ota_cmd, FU_PXI_DEVICE_CMD_FW_OTA_GET_NUM_OF_MODELS); /* ota init new op code */ self->sn++; if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_OTA_GET_NUM_OF_MODELS, self->sn, FU_PXI_WIRELESS_DEVICE_TARGET_RECEIVER, receiver_device_cmd, ota_cmd, error)) return FALSE; if (!fu_pxi_receiver_device_set_feature(self, receiver_device_cmd->data, receiver_device_cmd->len, error)) return FALSE; fu_device_sleep(FU_DEVICE(device), 5); /* ms */ buf[0] = PXI_HID_WIRELESS_DEV_OTA_REPORT_ID; if (!fu_pxi_receiver_device_get_feature(device, buf, sizeof(buf), error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "buf from get model num", buf, sizeof(buf)); if (!fu_memread_uint8_safe(buf, sizeof(buf), 0xA, num_of_models, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_pxi_receiver_device_add_peripherals(FuPxiReceiverDevice *device, guint idx, GError **error) { #ifdef HAVE_HIDRAW_H struct ota_fw_dev_model model = {0x0}; guint16 hpac_ver = 0; g_autofree gchar *model_name = NULL; g_autofree gchar *model_version = NULL; /* get the all wireless peripherals info */ if (!fu_pxi_receiver_device_get_peripheral_info(device, &model, idx, error)) return FALSE; if (!fu_device_has_private_flag(FU_DEVICE(device), FU_PXI_DEVICE_FLAG_IS_HPAC)) { model_version = g_strndup((gchar *)model.version, 5); } else { if (!fu_memread_uint16_safe(model.version, 5, 3, &hpac_ver, G_BIG_ENDIAN, error)) return FALSE; model_version = fu_pxi_hpac_version_info_parse(hpac_ver); } model_name = g_strndup((gchar *)model.name, FU_PXI_DEVICE_MODEL_NAME_LEN); /* idx 0 is for local_device */ if (idx == 0) { fu_device_set_version(FU_DEVICE(device), model_version); fu_device_add_instance_u16(FU_DEVICE(device), "VEN", device->vendor); fu_device_add_instance_u16(FU_DEVICE(device), "DEV", device->product); fu_device_add_instance_str(FU_DEVICE(device), "MODEL", model_name); if (!fu_device_build_instance_id(FU_DEVICE(device), error, "HIDRAW", "VEN", "DEV", "MODEL", NULL)) return FALSE; } else { FuContext *ctx = fu_device_get_context(FU_DEVICE(device)); g_autoptr(FuPxiWirelessDevice) child = fu_pxi_wireless_device_new(ctx, &model); g_autofree gchar *logical_id = g_strdup_printf("IDX:0x%02x", idx); fu_device_add_instance_u16(FU_DEVICE(child), "VEN", device->vendor); fu_device_add_instance_u16(FU_DEVICE(child), "DEV", device->product); fu_device_add_instance_str(FU_DEVICE(child), "MODEL", model_name); if (!fu_device_build_instance_id(FU_DEVICE(child), error, "HIDRAW", "VEN", "DEV", "MODEL", NULL)) return FALSE; fu_device_set_name(FU_DEVICE(child), model_name); fu_device_set_version(FU_DEVICE(child), model_version); fu_device_set_logical_id(FU_DEVICE(child), logical_id); fu_device_add_child(FU_DEVICE(device), FU_DEVICE(child)); } return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_pxi_receiver_device_setup_guid(FuPxiReceiverDevice *device, GError **error) { #ifdef HAVE_HIDRAW_H struct hidraw_devinfo hid_raw_info = {0x0}; g_autofree gchar *devid = NULL; g_autoptr(GString) dev_name = NULL; /* extra GUID with device name */ if (!fu_pxi_receiver_device_get_raw_info(device, &hid_raw_info, error)) return FALSE; device->vendor = (guint)hid_raw_info.vendor; device->product = (guint)hid_raw_info.product; dev_name = g_string_new(fu_device_get_name(FU_DEVICE(device))); g_string_ascii_up(dev_name); g_string_replace(dev_name, " ", "_", 0); devid = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&NAME_%s", (guint)hid_raw_info.vendor, (guint)hid_raw_info.product, dev_name->str); fu_device_add_instance_id(FU_DEVICE(device), devid); return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_pxi_receiver_device_check_peripherals(FuPxiReceiverDevice *device, GError **error) { guint8 num = 0; /* add wireless peripherals */ if (!fu_pxi_receiver_device_get_peripheral_num(device, &num, error)) return FALSE; g_debug("peripheral num: %u", num); for (guint8 idx = 0; idx < num; idx++) { if (!fu_pxi_receiver_device_add_peripherals(device, idx, error)) return FALSE; } return TRUE; } static gboolean fu_pxi_receiver_device_setup(FuDevice *device, GError **error) { FuPxiReceiverDevice *self = FU_PXI_RECEIVER_DEVICE(device); if (!fu_pxi_receiver_device_setup_guid(self, error)) { g_prefix_error(error, "failed to setup GUID: "); return FALSE; } if (!fu_pxi_receiver_device_fw_ota_init_new(self, 0x0000, error)) { g_prefix_error(error, "failed to OTA init new: "); return FALSE; } if (!fu_pxi_receiver_device_fw_ota_ini_new_check(self, error)) { g_prefix_error(error, "failed to OTA init new check: "); return FALSE; } if (!fu_pxi_receiver_device_check_peripherals(self, error)) { g_prefix_error(error, "failed to add wireless module: "); return FALSE; } return TRUE; } static gboolean fu_pxi_receiver_device_probe(FuDevice *device, GError **error) { /* set the logical and physical ID */ if (!fu_udev_device_set_logical_id(FU_UDEV_DEVICE(device), "hid", error)) return FALSE; return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); } static void fu_pxi_receiver_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_pxi_receiver_device_init(FuPxiReceiverDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_icon(FU_DEVICE(self), "usb-receiver"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_vendor_id(FU_DEVICE(self), "USB:0x093A"); fu_device_add_protocol(FU_DEVICE(self), "com.pixart.rf"); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_PXI_FIRMWARE); fu_device_register_private_flag(FU_DEVICE(self), FU_PXI_DEVICE_FLAG_IS_HPAC, "is-hpac"); } static void fu_pxi_receiver_device_class_init(FuPxiReceiverDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_pxi_receiver_device_to_string; klass_device->setup = fu_pxi_receiver_device_setup; klass_device->probe = fu_pxi_receiver_device_probe; klass_device->write_firmware = fu_pxi_receiver_device_write_firmware; klass_device->prepare_firmware = fu_pxi_receiver_device_prepare_firmware; klass_device->set_progress = fu_pxi_receiver_device_set_progress; } fwupd-1.9.16/plugins/pixart-rf/fu-pxi-receiver-device.h000066400000000000000000000006311460375044200227570ustar00rootroot00000000000000/* * Copyright (C) 2020 Jimmy Yu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-pxi-common.h" #define FU_TYPE_PXI_RECEIVER_DEVICE (fu_pxi_receiver_device_get_type()) G_DECLARE_FINAL_TYPE(FuPxiReceiverDevice, fu_pxi_receiver_device, FU, PXI_RECEIVER_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/pixart-rf/fu-pxi-wireless-device.c000066400000000000000000000576361460375044200230240ustar00rootroot00000000000000/* * Copyright (C) 2020 Jimmy Yu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_HIDRAW_H #include #include #endif #include "fu-pxi-common.h" #include "fu-pxi-firmware.h" #include "fu-pxi-receiver-device.h" #include "fu-pxi-struct.h" #include "fu-pxi-wireless-device.h" #define FU_PXI_WIRELESS_DEV_DELAY_MS 50 #define FU_PXI_WIRELESS_DEV_PAYLOAD_DELAY_MS 15 struct _FuPxiWirelessDevice { FuDevice parent_instance; struct ota_fw_state fwstate; guint8 sn; struct ota_fw_dev_model model; }; G_DEFINE_TYPE(FuPxiWirelessDevice, fu_pxi_wireless_device, FU_TYPE_DEVICE) static void fu_pxi_wireless_device_to_string(FuDevice *device, guint idt, GString *str) { FuPxiWirelessDevice *self = FU_PXI_WIRELESS_DEVICE(device); fu_pxi_ota_fw_state_to_string(&self->fwstate, idt, str); fu_string_append(str, idt, "ModelName", (gchar *)self->model.name); fu_string_append_kx(str, idt, "ModelType", self->model.type); fu_string_append_kx(str, idt, "ModelTarget", self->model.target); } static FuPxiReceiverDevice * fu_pxi_wireless_device_get_parent(FuDevice *self, GError **error) { FuDevice *parent = fu_device_get_parent(FU_DEVICE(self)); if (parent == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no parent set"); return NULL; } return FU_PXI_RECEIVER_DEVICE(FU_UDEV_DEVICE(parent)); } static FuFirmware * fu_pxi_wireless_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuPxiReceiverDevice *parent; g_autoptr(FuFirmware) firmware = fu_pxi_firmware_new(); parent = fu_pxi_wireless_device_get_parent(device, error); if (parent == NULL) return NULL; if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; if (fu_device_has_private_flag(FU_DEVICE(parent), FU_PXI_DEVICE_FLAG_IS_HPAC) && fu_pxi_firmware_is_hpac(FU_PXI_FIRMWARE(firmware))) { g_autoptr(GBytes) fw_tmp = NULL; guint32 hpac_fw_size = 0; const guint8 *fw_ptr = g_bytes_get_data(fw, NULL); if (!fu_memread_uint32_safe(fw_ptr, g_bytes_get_size(fw), 9, &hpac_fw_size, G_LITTLE_ENDIAN, error)) return NULL; hpac_fw_size += 264; fw_tmp = fu_bytes_new_offset(fw, 9, hpac_fw_size, error); if (fw_tmp == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "HPAC F/W preparation failed."); return NULL; } fu_firmware_set_bytes(firmware, fw_tmp); } else if (fu_device_has_private_flag(FU_DEVICE(parent), FU_PXI_DEVICE_FLAG_IS_HPAC) != fu_pxi_firmware_is_hpac(FU_PXI_FIRMWARE(firmware))) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "The firmware is incompatible with the device"); return NULL; } return g_steal_pointer(&firmware); } static gboolean fu_pxi_wireless_device_set_feature(FuDevice *self, const guint8 *buf, guint bufsz, GError **error) { #ifdef HAVE_HIDRAW_H fu_dump_raw(G_LOG_DOMAIN, "SetFeature", buf, bufsz); return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(bufsz), (guint8 *)buf, NULL, FU_PXI_DEVICE_IOCTL_TIMEOUT, error); #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_pxi_wireless_device_get_feature(FuDevice *self, guint8 *buf, guint bufsz, GError **error) { #ifdef HAVE_HIDRAW_H if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGFEATURE(bufsz), buf, NULL, FU_PXI_DEVICE_IOCTL_TIMEOUT, error)) { return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "GetFeature", buf, bufsz); return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_pxi_wireless_device_get_cmd_response(FuPxiWirelessDevice *device, guint8 *buf, guint bufsz, GError **error) { FuPxiReceiverDevice *parent; guint16 retry = 0; guint8 status = 0x0; parent = fu_pxi_wireless_device_get_parent(FU_DEVICE(device), error); if (parent == NULL) return FALSE; while (1) { guint8 sn = 0x0; memset(buf, 0, bufsz); buf[0] = PXI_HID_WIRELESS_DEV_OTA_REPORT_ID; fu_device_sleep(FU_DEVICE(device), FU_PXI_WIRELESS_DEV_DELAY_MS); /* ms */ if (!fu_pxi_wireless_device_get_feature(FU_DEVICE(parent), buf, bufsz, error)) return FALSE; if (!fu_memread_uint8_safe(buf, bufsz, 0x4, &sn, error)) return FALSE; if (device->sn != sn) retry++; else break; if (retry == FU_PXI_WIRELESS_DEVICE_RETRY_MAXIMUM) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "reach retry maximum hid sn fail, got 0x%04x, expected 0x%04x", sn, device->sn); return FALSE; } /*if wireless device not reply to receiver, keep to wait */ if (!fu_memread_uint8_safe(buf, bufsz, 0x5, &status, error)) return FALSE; if (status == FU_PXI_WIRELESS_MODULE_OTA_RSP_CODE_NOT_READY) { retry = 0x0; g_debug("FU_PXI_WIRELESS_MODULE_OTA_RSP_CODE_NOT_READY"); } } return TRUE; } static gboolean fu_pxi_wireless_device_check_crc(FuDevice *device, guint16 checksum, GError **error) { FuPxiReceiverDevice *parent; FuPxiWirelessDevice *self = FU_PXI_WIRELESS_DEVICE(device); guint8 buf[FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ] = {0x0}; guint16 checksum_device = 0x0; guint8 status = 0x0; g_autoptr(GByteArray) receiver_cmd = g_byte_array_new(); g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); /* proxy */ parent = fu_pxi_wireless_device_get_parent(device, error); if (parent == NULL) return FALSE; /* ota check crc command */ fu_byte_array_append_uint8(ota_cmd, 0x3); /* ota command length */ fu_byte_array_append_uint8(ota_cmd, FU_PXI_DEVICE_CMD_FW_OTA_CHECK_CRC); /* ota command */ fu_byte_array_append_uint16(ota_cmd, checksum, G_LITTLE_ENDIAN); /* checkesum */ /* increase the serial number */ self->sn++; if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_OTA_CHECK_CRC, self->sn, FU_PXI_WIRELESS_DEVICE_TARGET_RECEIVER, receiver_cmd, ota_cmd, error)) return FALSE; if (!fu_pxi_wireless_device_set_feature(FU_DEVICE(parent), receiver_cmd->data, receiver_cmd->len, error)) return FALSE; if (!fu_pxi_wireless_device_get_cmd_response(self, buf, sizeof(buf), error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "crc buf", buf, sizeof(buf)); if (!fu_memread_uint8_safe(buf, sizeof(buf), 0x5, &status, error)) return FALSE; if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x6, &checksum_device, G_LITTLE_ENDIAN, error)) return FALSE; if (status == FU_PXI_WIRELESS_MODULE_OTA_RSP_CODE_ERROR) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "checksum fail, got 0x%04x, expected 0x%04x", checksum_device, checksum); return FALSE; } if (status != FU_PXI_WIRELESS_MODULE_OTA_RSP_CODE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "status:%s", fu_pxi_wireless_module_ota_rsp_code_to_string(status)); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_wireless_device_fw_object_create(FuDevice *device, FuChunk *chk, GError **error) { FuPxiReceiverDevice *parent; FuPxiWirelessDevice *self = FU_PXI_WIRELESS_DEVICE(device); guint8 buf[FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ] = {0x0}; guint8 status = 0x0; g_autoptr(GByteArray) receiver_cmd = g_byte_array_new(); g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); /* proxy */ parent = fu_pxi_wireless_device_get_parent(device, error); if (parent == NULL) return FALSE; /* ota object create command */ fu_byte_array_append_uint8(ota_cmd, 0x9); /* ota command length */ fu_byte_array_append_uint8(ota_cmd, FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE); /* ota command */ fu_byte_array_append_uint32(ota_cmd, fu_chunk_get_address(chk), G_LITTLE_ENDIAN); fu_byte_array_append_uint32(ota_cmd, fu_chunk_get_data_sz(chk), G_LITTLE_ENDIAN); /* increase the serial number */ self->sn++; /* get pixart wireless module for ota object create command */ if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE, self->sn, self->model.target, receiver_cmd, ota_cmd, error)) return FALSE; if (!fu_pxi_wireless_device_set_feature(FU_DEVICE(parent), receiver_cmd->data, receiver_cmd->len, error)) return FALSE; /* delay for wireless module device get command response*/ fu_device_sleep(device, FU_PXI_WIRELESS_DEV_DELAY_MS); if (!fu_pxi_wireless_device_get_cmd_response(self, buf, sizeof(buf), error)) return FALSE; if (!fu_memread_uint8_safe(buf, sizeof(buf), 0x5, &status, error)) return FALSE; if (status != FU_PXI_WIRELESS_MODULE_OTA_RSP_CODE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cmd rsp check fail: %s [0x%02x]", fu_pxi_wireless_module_ota_rsp_code_to_string(status), status); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_wireless_device_write_payload(FuDevice *device, FuChunk *chk, GError **error) { FuPxiReceiverDevice *parent; FuPxiWirelessDevice *self = FU_PXI_WIRELESS_DEVICE(device); g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); g_autoptr(GByteArray) receiver_cmd = g_byte_array_new(); /* proxy */ parent = fu_pxi_wireless_device_get_parent(device, error); if (parent == NULL) return FALSE; /* ota write payload command */ fu_byte_array_append_uint8(ota_cmd, fu_chunk_get_data_sz(chk)); /* ota command length */ g_byte_array_append(ota_cmd, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); /* payload content */ /* increase the serial number */ self->sn++; /* get pixart wireless module for ota write payload command */ if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_OTA_PAYLOAD_CONTENT, self->sn, self->model.target, receiver_cmd, ota_cmd, error)) return FALSE; if (!fu_pxi_wireless_device_set_feature(FU_DEVICE(parent), receiver_cmd->data, receiver_cmd->len, error)) return FALSE; /* delay for each payload packet */ fu_device_sleep(device, FU_PXI_WIRELESS_DEV_PAYLOAD_DELAY_MS); /* success */ return TRUE; } static gboolean fu_pxi_wireless_device_write_chunk(FuDevice *device, FuChunk *chk, GError **error) { FuPxiWirelessDevice *self = FU_PXI_WIRELESS_DEVICE(device); guint32 prn = 0; g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GBytes) chk_bytes = fu_chunk_get_bytes(chk); /* send create fw object command */ if (!fu_pxi_wireless_device_fw_object_create(device, chk, error)) return FALSE; /* write payload */ chunks = fu_chunk_array_new_from_bytes(chk_bytes, fu_chunk_get_address(chk), self->fwstate.mtu_size); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk2 = fu_chunk_array_index(chunks, i); /* calculate checksum of each payload packet */ self->fwstate.checksum += fu_sum16(fu_chunk_get_data(chk2), fu_chunk_get_data_sz(chk2)); if (!fu_pxi_wireless_device_write_payload(device, chk2, error)) return FALSE; prn++; /* check crc at fw when PRN over threshold write or * offset reach max object sz or write offset reach fw length */ if (prn >= self->fwstate.prn_threshold || i == (fu_chunk_array_length(chunks) - 1)) { if (!fu_pxi_wireless_device_check_crc(device, self->fwstate.checksum, error)) return FALSE; prn = 0; } } /* success */ return TRUE; } static gboolean fu_pxi_wireless_device_fw_ota_preceding(FuDevice *device, GError **error) { FuPxiReceiverDevice *parent; FuPxiWirelessDevice *self = FU_PXI_WIRELESS_DEVICE(device); g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); g_autoptr(GByteArray) receiver_cmd = g_byte_array_new(); /* proxy */ parent = fu_pxi_wireless_device_get_parent(device, error); if (parent == NULL) return FALSE; fu_byte_array_append_uint8(ota_cmd, 0x01); /* ota preceding command length */ fu_byte_array_append_uint8(ota_cmd, FU_PXI_DEVICE_CMD_FW_OTA_PRECEDING); /* ota preceding op code */ /* increase the serial number */ self->sn++; if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_OTA_PRECEDING, self->sn, self->model.target, receiver_cmd, ota_cmd, error)) return FALSE; return fu_pxi_wireless_device_set_feature(FU_DEVICE(parent), receiver_cmd->data, receiver_cmd->len, error); } static gboolean fu_pxi_wireless_device_fw_ota_init_new(FuDevice *device, gsize bufsz, GError **error) { FuPxiReceiverDevice *parent; FuPxiWirelessDevice *self = FU_PXI_WIRELESS_DEVICE(device); guint8 buf[FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ] = {0x0}; guint8 status = 0x0; guint8 fw_version[10] = {0x0}; g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); g_autoptr(GByteArray) receiver_cmd = g_byte_array_new(); /* proxy */ parent = fu_pxi_wireless_device_get_parent(device, error); if (parent == NULL) return FALSE; fu_byte_array_append_uint8(ota_cmd, 0X06); /* ota init new command length */ fu_byte_array_append_uint8(ota_cmd, FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW); /* ota init new op code */ fu_byte_array_append_uint32(ota_cmd, bufsz, G_LITTLE_ENDIAN); /* fw size */ fu_byte_array_append_uint8(ota_cmd, 0x0); /* ota setting */ g_byte_array_append(ota_cmd, fw_version, sizeof(fw_version)); /* ota version */ /* increase the serial number */ self->sn++; if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW, self->sn, self->model.target, receiver_cmd, ota_cmd, error)) return FALSE; if (!fu_pxi_wireless_device_set_feature(FU_DEVICE(parent), receiver_cmd->data, receiver_cmd->len, error)) return FALSE; /* delay for wireless module device get command response*/ fu_device_sleep(device, FU_PXI_WIRELESS_DEV_DELAY_MS); if (!fu_pxi_wireless_device_get_cmd_response(self, buf, sizeof(buf), error)) return FALSE; if (!fu_memread_uint8_safe(buf, sizeof(buf), 0x5, &status, error)) return FALSE; if (status != FU_PXI_WIRELESS_MODULE_OTA_RSP_CODE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cmd rsp check fail: %s [0x%02x]", fu_pxi_wireless_module_ota_rsp_code_to_string(status), status); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_wireless_device_fw_ota_ini_new_check(FuDevice *device, GError **error) { FuPxiReceiverDevice *parent; FuPxiWirelessDevice *self = FU_PXI_WIRELESS_DEVICE(device); guint8 buf[FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ] = {0x0}; guint8 status = 0x0; g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); g_autoptr(GByteArray) receiver_cmd = g_byte_array_new(); /* proxy */ parent = fu_pxi_wireless_device_get_parent(device, error); if (parent == NULL) return FALSE; /* ota command */ fu_byte_array_append_uint8(ota_cmd, 0x1); /* ota command length */ fu_byte_array_append_uint8(ota_cmd, FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW_CHECK); /* ota command */ /* increase the serial number */ self->sn++; /* get pixart wireless module ota command */ if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW_CHECK, self->sn, self->model.target, receiver_cmd, ota_cmd, error)) return FALSE; if (!fu_pxi_wireless_device_set_feature(FU_DEVICE(parent), receiver_cmd->data, receiver_cmd->len, error)) return FALSE; /* delay for wireless module device get command response*/ fu_device_sleep(device, FU_PXI_WIRELESS_DEV_DELAY_MS); if (!fu_pxi_wireless_device_get_cmd_response(self, buf, sizeof(buf), error)) return FALSE; if (!fu_memread_uint8_safe(buf, sizeof(buf), 0x5, &status, error)) return FALSE; if (status != FU_PXI_WIRELESS_MODULE_OTA_RSP_CODE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cmd rsp check fail: %s [0x%02x]", fu_pxi_wireless_module_ota_rsp_code_to_string(status), status); return FALSE; } /* shared state */ return fu_pxi_ota_fw_state_parse(&self->fwstate, buf, sizeof(buf), 0x09, error); } static gboolean fu_pxi_wireless_device_fw_upgrade(FuDevice *device, FuFirmware *firmware, FuProgress *progress, GError **error) { FuPxiReceiverDevice *parent; FuPxiWirelessDevice *self = FU_PXI_WIRELESS_DEVICE(device); guint8 buf[FU_PXI_RECEIVER_DEVICE_OTA_BUF_SZ] = {0x0}; guint8 status = 0x0; const gchar *version; guint8 fw_version[5] = {0x0}; g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); g_autoptr(GByteArray) receiver_cmd = g_byte_array_new(); g_autoptr(GBytes) fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 5, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 95, NULL); /* proxy */ parent = fu_pxi_wireless_device_get_parent(device, error); if (parent == NULL) return FALSE; fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* ota fw upgrade command */ fu_byte_array_append_uint8(ota_cmd, 0x0c); /* ota fw upgrade command length */ fu_byte_array_append_uint8( ota_cmd, FU_PXI_DEVICE_CMD_FW_UPGRADE); /* ota fw upgrade command opccode */ fu_byte_array_append_uint32(ota_cmd, g_bytes_get_size(fw), G_LITTLE_ENDIAN); /* ota fw upgrade command fw size */ fu_byte_array_append_uint16(ota_cmd, fu_sum16_bytes(fw), G_LITTLE_ENDIAN); /* ota fw upgrade command checksum */ if (!fu_device_has_private_flag(FU_DEVICE(parent), FU_PXI_DEVICE_FLAG_IS_HPAC)) { version = fu_firmware_get_version(firmware); if (!fu_memcpy_safe(fw_version, sizeof(fw_version), 0x0, /* dst */ (guint8 *)version, strlen(version), 0x0, /* src */ sizeof(fw_version), error)) return FALSE; } g_byte_array_append(ota_cmd, fw_version, sizeof(fw_version)); self->sn++; /* get pixart wireless module ota command */ if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_UPGRADE, self->sn, self->model.target, receiver_cmd, ota_cmd, error)) return FALSE; fu_progress_step_done(progress); /* send ota fw upgrade command */ if (!fu_pxi_wireless_device_set_feature(FU_DEVICE(parent), receiver_cmd->data, receiver_cmd->len, error)) return FALSE; /* delay for wireless module device get command response*/ fu_device_sleep(device, FU_PXI_WIRELESS_DEV_DELAY_MS); if (!fu_pxi_wireless_device_get_cmd_response(self, buf, sizeof(buf), error)) return FALSE; if (!fu_memread_uint8_safe(buf, sizeof(buf), 0x5, &status, error)) return FALSE; if (status != FU_PXI_WIRELESS_MODULE_OTA_RSP_CODE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cmd rsp check fail: %s [0x%02x]", fu_pxi_wireless_module_ota_rsp_code_to_string(status), status); return FALSE; } fu_progress_step_done(progress); return TRUE; } static gboolean fu_pxi_wireless_device_reset(FuDevice *device, GError **error) { FuPxiReceiverDevice *parent; FuPxiWirelessDevice *self = FU_PXI_WIRELESS_DEVICE(device); g_autoptr(GByteArray) ota_cmd = g_byte_array_new(); g_autoptr(GByteArray) receiver_cmd = g_byte_array_new(); /* proxy */ parent = fu_pxi_wireless_device_get_parent(device, error); if (parent == NULL) return FALSE; /* ota mcu reset command */ fu_byte_array_append_uint8(ota_cmd, 0x1); /* ota mcu reset command */ fu_byte_array_append_uint8( ota_cmd, FU_PXI_DEVICE_CMD_FW_MCU_RESET); /* ota mcu reset command op code */ fu_byte_array_append_uint8(ota_cmd, OTA_RESET); /* ota mcu reset command reason */ self->sn++; /* get pixart wireless module ota command */ if (!fu_pxi_composite_receiver_cmd(FU_PXI_DEVICE_CMD_FW_MCU_RESET, self->sn, self->model.target, receiver_cmd, ota_cmd, error)) return FALSE; /* send ota mcu reset command */ return fu_pxi_wireless_device_set_feature(FU_DEVICE(parent), receiver_cmd->data, receiver_cmd->len, error); } static gboolean fu_pxi_wireless_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuPxiWirelessDevice *self = FU_PXI_WIRELESS_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 9, "ota-init"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, NULL); /* get the default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* send preceding command */ if (!fu_pxi_wireless_device_fw_ota_preceding(device, error)) return FALSE; /* send fw ota init command */ if (!fu_pxi_wireless_device_fw_ota_init_new(device, g_bytes_get_size(fw), error)) return FALSE; if (!fu_pxi_wireless_device_fw_ota_ini_new_check(device, error)) return FALSE; fu_progress_step_done(progress); chunks = fu_chunk_array_new_from_bytes(fw, 0x0, FU_PXI_DEVICE_OBJECT_SIZE_MAX); /* prepare write fw into device */ self->fwstate.offset = 0; self->fwstate.checksum = 0; /* write fw into device */ for (guint i = self->fwstate.offset; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_pxi_wireless_device_write_chunk(device, chk, error)) return FALSE; fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + self->fwstate.offset + 1, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* fw upgrade command */ if (!fu_pxi_wireless_device_fw_upgrade(device, firmware, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* send device reset command */ fu_device_sleep(device, FU_PXI_WIRELESS_DEV_DELAY_MS); if (!fu_pxi_wireless_device_reset(device, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_pxi_wireless_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_pxi_wireless_device_init(FuPxiWirelessDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_vendor_id(FU_DEVICE(self), "USB:0x093A"); fu_device_add_protocol(FU_DEVICE(self), "com.pixart.rf"); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_PXI_FIRMWARE); } static void fu_pxi_wireless_device_class_init(FuPxiWirelessDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_pxi_wireless_device_write_firmware; klass_device->to_string = fu_pxi_wireless_device_to_string; klass_device->prepare_firmware = fu_pxi_wireless_device_prepare_firmware; klass_device->set_progress = fu_pxi_wireless_device_set_progress; } FuPxiWirelessDevice * fu_pxi_wireless_device_new(FuContext *ctx, struct ota_fw_dev_model *model) { FuPxiWirelessDevice *self = NULL; self = g_object_new(FU_TYPE_PXI_WIRELESS_DEVICE, "context", ctx, NULL); self->model.status = model->status; for (guint idx = 0; idx < FU_PXI_DEVICE_MODEL_NAME_LEN; idx++) self->model.name[idx] = model->name[idx]; self->model.type = model->type; self->model.target = model->target; self->sn = model->target; return self; } fwupd-1.9.16/plugins/pixart-rf/fu-pxi-wireless-device.h000066400000000000000000000007341460375044200230140ustar00rootroot00000000000000/* * Copyright (C) 2020 Jimmy Yu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-pxi-common.h" #define FU_TYPE_PXI_WIRELESS_DEVICE (fu_pxi_wireless_device_get_type()) G_DECLARE_FINAL_TYPE(FuPxiWirelessDevice, fu_pxi_wireless_device, FU, PXI_WIRELESS_DEVICE, FuDevice) FuPxiWirelessDevice * fu_pxi_wireless_device_new(FuContext *ctx, struct ota_fw_dev_model *model); fwupd-1.9.16/plugins/pixart-rf/fu-pxi.rs000066400000000000000000000007201460375044200201140ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum PxiOtaSpecCheckResult { Ok = 1, FwOutOfBounds = 2, ProcessIllegal = 3, Reconnect = 4, FwImgVersionError = 5, DeviceLowBattery = 6, } #[derive(ToString)] enum PxiWirelessModuleOtaRspCode { Ok, Finish, Fail, Error, WritePktLenError, WritePktTotalSizeError, ReadPktLenError, NotReady, } fwupd-1.9.16/plugins/pixart-rf/fu-self-test.c000066400000000000000000000040431460375044200210220ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-pxi-firmware.h" static void fu_pxi_firmware_xml_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *csum1 = NULL; g_autofree gchar *csum2 = NULL; g_autofree gchar *xml_out = NULL; g_autofree gchar *xml_src = NULL; g_autoptr(FuFirmware) firmware1 = fu_pxi_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_pxi_firmware_new(); g_autoptr(FuFirmware) firmware3 = fu_pxi_firmware_new(); g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "pixart.builder.xml", NULL); ret = g_file_get_contents(filename, &xml_src, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_build_from_xml(firmware1, xml_src, &error); g_assert_no_error(error); g_assert_true(ret); fw = fu_firmware_write(firmware1, &error); g_assert_no_error(error); g_assert_nonnull(fw); csum1 = fu_firmware_get_checksum(firmware1, G_CHECKSUM_SHA1, &error); g_assert_no_error(error); g_assert_cmpstr(csum1, ==, "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"); /* ensure we can parse */ ret = fu_firmware_parse(firmware3, fw, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); /* ensure we can round-trip */ xml_out = fu_firmware_export_to_xml(firmware1, FU_FIRMWARE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); ret = fu_firmware_build_from_xml(firmware2, xml_out, &error); g_assert_no_error(error); g_assert_true(ret); csum2 = fu_firmware_get_checksum(firmware2, G_CHECKSUM_SHA1, &error); g_assert_cmpstr(csum1, ==, csum2); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func("/pxi/firmware{xml}", fu_pxi_firmware_xml_func); return g_test_run(); } fwupd-1.9.16/plugins/pixart-rf/meson.build000066400000000000000000000026001460375044200204770ustar00rootroot00000000000000if get_option('plugin_pixart_rf').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginPixartRf"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('pixart-rf.quirk') plugin_builtin_pxi = static_library('fu_plugin_pxi', rustgen.process( 'fu-pxi.rs', # fuzzing ), sources: [ 'fu-pxi-plugin.c', 'fu-pxi-common.c', # fuzzing 'fu-pxi-ble-device.c', 'fu-pxi-receiver-device.c', 'fu-pxi-wireless-device.c', 'fu-pxi-firmware.c', # fuzzing ], include_directories: plugin_incdirs, c_args: cargs, dependencies: plugin_deps, link_with: plugin_libs, ) plugin_builtins += plugin_builtin_pxi if get_option('tests') install_data(['tests/pixart.builder.xml'], install_dir: join_paths(installed_test_datadir, 'tests')) env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'pxi-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_pxi, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('pxi-self-test', e, env: env) endif endif fwupd-1.9.16/plugins/pixart-rf/pixart-rf.quirk000066400000000000000000000075741460375044200213450ustar00rootroot00000000000000# Pixart 2801 [HIDRAW\VEN_093A&DEV_2801] Plugin = pixart_rf GType = FuPxiBleDevice # Pixart 2860 [HIDRAW\VEN_093A&DEV_2860] Plugin = pixart_rf GType = FuPxiBleDevice # Pixart 2802 [HIDRAW\VEN_093A&DEV_2802] Plugin = pixart_rf GType = FuPxiBleDevice # Pixart 2822 [HIDRAW\VEN_093A&DEV_2822] Plugin = pixart_rf GType = FuPxiBleDevice # Pixart 2861 [HIDRAW\VEN_093A&DEV_2861] Plugin = pixart_rf GType = FuPxiReceiverDevice # Pixart 2862 [HIDRAW\VEN_093A&DEV_2862] Plugin = pixart_rf GType = FuPxiReceiverDevice # Pixart 2452 [HIDRAW\VEN_093A&DEV_2452] Plugin = pixart_rf GType = FuPxiReceiverDevice # Primax Mouse [HIDRAW\VEN_0461&DEV_4EEF] Plugin = pixart_rf GType = FuPxiBleDevice # Primax US KB [HIDRAW\VEN_0461&DEV_4EEE] Plugin = pixart_rf GType = FuPxiBleDevice # Primax UK KB [HIDRAW\VEN_0461&DEV_4EF4] Plugin = pixart_rf GType = FuPxiBleDevice # Primax JP KB [HIDRAW\VEN_0461&DEV_4EF5] Plugin = pixart_rf GType = FuPxiBleDevice # Chicony Cheetos keyboard [HIDRAW\VEN_04F2&DEV_2051] Plugin = pixart_rf GType = FuPxiBleDevice # Chicony Cheetos keyboard v2 [HIDRAW\VEN_04F2&DEV_2125] Plugin = pixart_rf GType = FuPxiBleDevice # Chicony Cheetos keyboard UK [HIDRAW\VEN_04F2&DEV_2188] Plugin = pixart_rf GType = FuPxiBleDevice # Chicony Cheetos keyboard v2 UK [HIDRAW\VEN_04F2&DEV_2189] Plugin = pixart_rf GType = FuPxiBleDevice # Chicony Cheetos mouse [HIDRAW\VEN_04F2&DEV_2103] Plugin = pixart_rf GType = FuPxiBleDevice # Elecom Bluetooth Keyboard [HIDRAW\VEN_056E&DEV_1095] Plugin = pixart_rf GType = FuPxiBleDevice # Targus BT Mouse AMB844 [HIDRAW\VEN_1048&DEV_1013] Plugin = pixart_rf GType = FuPxiBleDevice # Targus BT Keyboard AKB872 [HIDRAW\VEN_1048&DEV_3003] Plugin = pixart_rf GType = FuPxiBleDevice # Targus BT Keyboard AKB869 [HIDRAW\VEN_1048&DEV_3004] Plugin = pixart_rf GType = FuPxiBleDevice # AKR123 Bluetooth Keyboard US [HIDRAW\VEN_04F2&DEV_2199] Plugin = pixart_rf GType = FuPxiBleDevice # AKR123 Bluetooth Keyboard UK [HIDRAW\VEN_04F2&DEV_2174] Plugin = pixart_rf GType = FuPxiBleDevice # AMR120 Bluetooth Mouse [HIDRAW\VEN_04F2&DEV_2198] Plugin = pixart_rf GType = FuPxiBleDevice # j5 Wireless Keyboard [HIDRAW\VEN_2DE5&DEV_2125] Plugin = pixart_rf GType = FuPxiBleDevice # j5 Wireless Mouse [HIDRAW\VEN_2DE5&DEV_2103] Plugin = pixart_rf GType = FuPxiBleDevice # CTL Bluetooth Keyboard [HIDRAW\VEN_04F2&DEV_2179] Plugin = pixart_rf GType = FuPxiBleDevice # CTL Bluetooth Mouse [HIDRAW\VEN_04F2&DEV_2178] Plugin = pixart_rf GType = FuPxiBleDevice # MW150BT [HIDRAW\VEN_04F2&DEV_2177] Plugin = pixart_rf GType = FuPxiBleDevice # CKW150BTUK [HIDRAW\VEN_04F2&DEV_2176] Plugin = pixart_rf GType = FuPxiBleDevice # CKW150BTUS [HIDRAW\VEN_04F2&DEV_2175] Plugin = pixart_rf GType = FuPxiBleDevice # Primax Black Mocha KB [HIDRAW\VEN_0461&DEV_4EFA] Plugin = pixart_rf GType = FuPxiBleDevice # Primax Vivaldi2 Mouse [HIDRAW\VEN_03F0&DEV_614A] Plugin = pixart_rf GType = FuPxiBleDevice # HP 320 BTKB [HIDRAW\VEN_0461&DEV_4F01] Plugin = pixart_rf GType = FuPxiBleDevice # Pixart 2403 [HIDRAW\VEN_093A&DEV_2403] Plugin = pixart_rf GType = FuPxiReceiverDevice # Pixart 2463 [HIDRAW\VEN_093A&DEV_2463] Plugin = pixart_rf GType = FuPxiReceiverDevice # Pixart 2832 [HIDRAW\VEN_093A&DEV_2832] Plugin = pixart_rf GType = FuPxiBleDevice # Pixart 2851 [HIDRAW\VEN_093A&DEV_2851] Plugin = pixart_rf GType = FuPxiBleDevice # Pixart 2852 [HIDRAW\VEN_093A&DEV_2852] Plugin = pixart_rf GType = FuPxiBleDevice # Twinkies Mouse [HIDRAW\VEN_03F0&DEV_6D4A] Plugin = pixart_rf GType = FuPxiBleDevice Flags = is-hpac # Twinkies Keyboard [HIDRAW\VEN_03F0&DEV_7C4A] Plugin = pixart_rf GType = FuPxiBleDevice Flags = is-hpac # Twinkies Dongle [HIDRAW\VEN_03F0&DEV_6841] Plugin = pixart_rf GType = FuPxiReceiverDevice Flags = is-hpac # Rata BLE Mouse [HIDRAW\VEN_03F0&DEV_7F4A] Plugin = pixart_rf GType = FuPxiBleDevice Flags = is-hpac # Remi BLE Mouse [HIDRAW\VEN_03F0&DEV_804A] Plugin = pixart_rf GType = FuPxiBleDevice Flags = is-hpac fwupd-1.9.16/plugins/pixart-rf/tests/000077500000000000000000000000001460375044200175015ustar00rootroot00000000000000fwupd-1.9.16/plugins/pixart-rf/tests/pixart.bin000066400000000000000000000000531460375044200215000ustar00rootroot00000000000000hello world1.2.3TestUUUUfwupd-1.9.16/plugins/pixart-rf/tests/pixart.builder.xml000066400000000000000000000002451460375044200231600ustar00rootroot00000000000000 0x00010203 Test aGVsbG8gd29ybGQ= fwupd-1.9.16/plugins/powerd/000077500000000000000000000000001460375044200157235ustar00rootroot00000000000000fwupd-1.9.16/plugins/powerd/README.md000066400000000000000000000007601460375044200172050ustar00rootroot00000000000000--- title: Plugin: Powerd --- ## Introduction This plugin is used to ensure that some updates are not done on battery power and that there is sufficient battery power for certain updates that are. ## Vendor ID Security This protocol does not create a device and thus requires no vendor ID set. ## External interface access This plugin requires access to the `org.chromium.PowerManager` DBus interface. ## Version Considerations This plugin has been available since fwupd version `1.6.2`. fwupd-1.9.16/plugins/powerd/fu-powerd-plugin.c000066400000000000000000000140741460375044200213010ustar00rootroot00000000000000/* * Copyright (C) 2021 Twain Byrnes * Copyright (C) 2021 George Popoola * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-powerd-plugin.h" struct _FuPowerdPlugin { FuPlugin parent_instance; GDBusProxy *proxy; /* nullable */ }; G_DEFINE_TYPE(FuPowerdPlugin, fu_powerd_plugin, FU_TYPE_PLUGIN) typedef enum { FU_POWERD_BATTERY_STATE_UNKNOWN, FU_POWERD_BATTERY_STATE_CHARGING, FU_POWERD_BATTERY_STATE_DISCHARGING, FU_POWERD_BATTERY_STATE_EMPTY, FU_POWERD_BATTERY_STATE_FULLY_CHARGED, } FuPowerdBatteryState; typedef enum { FU_POWERD_EXTERNAL_POWER_UNKNOWN, FU_POWERD_EXTERNAL_POWER_AC, FU_POWERD_EXTERNAL_POWER_USB, FU_POWERD_EXTERNAL_POWER_DISCONNECTED } FuPowerdExternal; static gboolean fu_powerd_plugin_create_suspend_file(GError **error) { g_autofree gchar *lockdir = NULL; g_autofree gchar *inhibitsuspend_filename = NULL; g_autofree gchar *getpid_str = NULL; lockdir = fu_path_from_kind(FU_PATH_KIND_LOCKDIR); inhibitsuspend_filename = g_build_filename(lockdir, "power_override", "fwupd.lock", NULL); getpid_str = g_strdup_printf("%d", getpid()); if (!g_file_set_contents(inhibitsuspend_filename, getpid_str, -1, error)) { g_prefix_error(error, "lock file unable to be created: "); return FALSE; } return TRUE; } static gboolean fu_powerd_plugin_delete_suspend_file(GError **error) { g_autoptr(GError) local_error = NULL; g_autofree gchar *lockdir = NULL; g_autoptr(GFile) inhibitsuspend_file = NULL; lockdir = fu_path_from_kind(FU_PATH_KIND_LOCKDIR); inhibitsuspend_file = g_file_new_build_filename(lockdir, "power_override", "fwupd.lock", NULL); if (!g_file_delete(inhibitsuspend_file, NULL, &local_error) && !g_error_matches(local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_propagate_prefixed_error(error, g_steal_pointer(&local_error), "lock file unable to be deleted"); return FALSE; } return TRUE; } static void fu_powerd_plugin_rescan(FuPlugin *plugin, GVariant *parameters) { FuContext *ctx = fu_plugin_get_context(plugin); guint32 power_type; guint32 current_state; gdouble current_level; g_variant_get(parameters, "(uud)", &power_type, ¤t_state, ¤t_level); /* checking if percentage is invalid */ if (current_level < 1 || current_level > 100) current_level = FWUPD_BATTERY_LEVEL_INVALID; fu_context_set_battery_level(ctx, current_level); /* plugged in */ if (power_type == FU_POWERD_EXTERNAL_POWER_AC || power_type == FU_POWERD_EXTERNAL_POWER_USB) { switch (current_state) { case FU_POWERD_BATTERY_STATE_CHARGING: fu_context_set_power_state(ctx, FU_POWER_STATE_AC_CHARGING); break; case FU_POWERD_BATTERY_STATE_FULLY_CHARGED: fu_context_set_power_state(ctx, FU_POWER_STATE_AC_FULLY_CHARGED); break; default: fu_context_set_power_state(ctx, FU_POWER_STATE_AC); break; } return; } /* fallback */ switch (current_state) { case FU_POWERD_BATTERY_STATE_CHARGING: fu_context_set_power_state(ctx, FU_POWER_STATE_AC_CHARGING); break; case FU_POWERD_BATTERY_STATE_DISCHARGING: fu_context_set_power_state(ctx, FU_POWER_STATE_BATTERY_DISCHARGING); break; case FU_POWERD_BATTERY_STATE_EMPTY: fu_context_set_power_state(ctx, FU_POWER_STATE_BATTERY_EMPTY); break; case FU_POWERD_BATTERY_STATE_FULLY_CHARGED: fu_context_set_power_state(ctx, FU_POWER_STATE_AC_FULLY_CHARGED); break; default: fu_context_set_power_state(ctx, FU_POWER_STATE_UNKNOWN); break; } } static void fu_powerd_plugin_proxy_changed_cb(GDBusProxy *proxy, const gchar *sender_name, const gchar *signal_name, GVariant *parameters, FuPlugin *plugin) { if (!g_str_equal(signal_name, "BatteryStatePoll")) return; fu_powerd_plugin_rescan(plugin, parameters); } static gboolean fu_powerd_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { FuPowerdPlugin *self = FU_POWERD_PLUGIN(plugin); g_autofree gchar *name_owner = NULL; if (!fu_powerd_plugin_delete_suspend_file(error)) return FALSE; /* establish proxy for method call to powerd */ self->proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.chromium.PowerManager", "/org/chromium/PowerManager", "org.chromium.PowerManager", NULL, error); if (self->proxy == NULL) { g_prefix_error(error, "failed to connect to powerd: "); return FALSE; } name_owner = g_dbus_proxy_get_name_owner(self->proxy); if (name_owner == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no service that owns the name for %s", g_dbus_proxy_get_name(self->proxy)); return FALSE; } fu_powerd_plugin_rescan(plugin, g_dbus_proxy_call_sync(self->proxy, "GetBatteryState", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, G_SOURCE_REMOVE)); g_signal_connect(G_DBUS_PROXY(self->proxy), "g-signal", G_CALLBACK(fu_powerd_plugin_proxy_changed_cb), plugin); return TRUE; } static gboolean fu_powerd_plugin_prepare(FuPlugin *plugin, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { return fu_powerd_plugin_create_suspend_file(error); } static gboolean fu_powerd_plugin_cleanup(FuPlugin *plugin, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { return fu_powerd_plugin_delete_suspend_file(error); } static void fu_powerd_plugin_init(FuPowerdPlugin *self) { } static void fu_powerd_finalize(GObject *obj) { FuPowerdPlugin *self = FU_POWERD_PLUGIN(obj); if (self->proxy != NULL) g_object_unref(self->proxy); G_OBJECT_CLASS(fu_powerd_plugin_parent_class)->finalize(obj); } static void fu_powerd_plugin_class_init(FuPowerdPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_powerd_finalize; plugin_class->startup = fu_powerd_plugin_startup; plugin_class->cleanup = fu_powerd_plugin_cleanup; plugin_class->prepare = fu_powerd_plugin_prepare; } fwupd-1.9.16/plugins/powerd/fu-powerd-plugin.h000066400000000000000000000003501460375044200212760ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuPowerdPlugin, fu_powerd_plugin, FU, POWERD_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/powerd/meson.build000066400000000000000000000010151460375044200200620ustar00rootroot00000000000000if get_option('plugin_powerd').disable_auto_if(host_machine.system() != 'linux').require(gio.version().version_compare('>=2.58'), error_message: 'gio 2.58 or later needed for powerd').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginPowerd"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtins += static_library('fu_plugin_powerd', sources: [ 'fu-powerd-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/qsi-dock/000077500000000000000000000000001460375044200161355ustar00rootroot00000000000000fwupd-1.9.16/plugins/qsi-dock/README.md000066400000000000000000000017251460375044200174210ustar00rootroot00000000000000--- title: Plugin: QSI Dock --- ## Introduction This plugin uses the MCU to write all the dock firmware components. The MCU version is provided by the DMC bcdDevice. This plugin supports the following protocol ID: * `com.qsi.dock` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_047D&PID_808D` ## Update Behavior The device usually presents in runtime mode, but on detach re-enumerates with the same USB PID in an unlocked mode. On attach the device again re-enumerates back to the runtime locked mode. ## Vendor ID Security The vendor ID is set from the USB vendor. ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.8.8`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Kevin Chen: @hssinf fwupd-1.9.16/plugins/qsi-dock/fu-qsi-dock-child-device.c000066400000000000000000000046241460375044200227470ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-qsi-dock-child-device.h" #include "fu-qsi-dock-mcu-device.h" struct _FuQsiDockChildDevice { FuDevice parent_instance; guint8 chip_idx; }; G_DEFINE_TYPE(FuQsiDockChildDevice, fu_qsi_dock_child_device, FU_TYPE_DEVICE) void fu_qsi_dock_child_device_set_chip_idx(FuQsiDockChildDevice *self, guint8 chip_idx) { self->chip_idx = chip_idx; } static void fu_qsi_dock_child_device_to_string(FuDevice *device, guint idt, GString *str) { FuQsiDockChildDevice *self = FU_QSI_DOCK_CHILD_DEVICE(device); fu_string_append_kx(str, idt, "ChipIdx", self->chip_idx); } /* use the parents parser */ static FuFirmware * fu_qsi_dock_mcu_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuDevice *parent = fu_device_get_parent(device); if (parent == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no parent"); return NULL; } return fu_device_prepare_firmware(parent, fw, flags, error); } /* only update this specific child component */ static gboolean fu_qsi_dock_mcu_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuQsiDockChildDevice *self = FU_QSI_DOCK_CHILD_DEVICE(device); FuDevice *parent = fu_device_get_parent(device); if (parent == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no parent"); return FALSE; } return fu_qsi_dock_mcu_device_write_firmware_with_idx(FU_QSI_DOCK_MCU_DEVICE(parent), firmware, self->chip_idx, progress, flags, error); } static void fu_qsi_dock_child_device_init(FuQsiDockChildDevice *self) { fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); } static void fu_qsi_dock_child_device_class_init(FuQsiDockChildDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_qsi_dock_child_device_to_string; klass_device->prepare_firmware = fu_qsi_dock_mcu_device_prepare_firmware; klass_device->write_firmware = fu_qsi_dock_mcu_device_write_firmware; } FuDevice * fu_qsi_dock_child_new(FuContext *ctx) { return g_object_new(FU_TYPE_QSI_DOCK_CHILD_DEVICE, "context", ctx, NULL); } fwupd-1.9.16/plugins/qsi-dock/fu-qsi-dock-child-device.h000066400000000000000000000007621460375044200227530ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_QSI_DOCK_CHILD_DEVICE (fu_qsi_dock_child_device_get_type()) G_DECLARE_FINAL_TYPE(FuQsiDockChildDevice, fu_qsi_dock_child_device, FU, QSI_DOCK_CHILD_DEVICE, FuDevice) FuDevice * fu_qsi_dock_child_new(FuContext *ctx); void fu_qsi_dock_child_device_set_chip_idx(FuQsiDockChildDevice *self, guint8 chip_idx); fwupd-1.9.16/plugins/qsi-dock/fu-qsi-dock-common.h000066400000000000000000000033101460375044200217130ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2022 Kevin Chen * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_QSI_DOCK_REPORT_ID 5 #define FU_QSI_DOCK_FIRMWARE_IDX_NONE 0x00 #define FU_QSI_DOCK_FIRMWARE_IDX_DMC_PD 0x01 #define FU_QSI_DOCK_FIRMWARE_IDX_DP 0x02 #define FU_QSI_DOCK_FIRMWARE_IDX_TBT4 0x04 #define FU_QSI_DOCK_FIRMWARE_IDX_USB3 0x08 #define FU_QSI_DOCK_FIRMWARE_IDX_USB2 0x10 #define FU_QSI_DOCK_FIRMWARE_IDX_AUDIO 0x20 #define FU_QSI_DOCK_FIRMWARE_IDX_I225 0x40 #define FU_QSI_DOCK_FIRMWARE_IDX_MCU 0x80 typedef struct __attribute__((packed)) { guint8 DMC[5]; guint8 PD[5]; guint8 DP5x[5]; guint8 DP6x[5]; guint8 TBT4[5]; guint8 USB3[5]; guint8 USB2[5]; guint8 AUDIO[5]; guint8 I255[5]; guint8 MCU[2]; guint8 bcdVersion[2]; } FuQsiDockIspVersionInMcu; typedef enum { FU_QSI_DOCK_CMD1_BOOT = 0x11, FU_QSI_DOCK_CMD1_SYSTEM = 0x31, FU_QSI_DOCK_CMD1_MCU = 0x51, FU_QSI_DOCK_CMD1_SPI = 0x61, FU_QSI_DOCK_CMD1_I2C_VMM = 0x71, FU_QSI_DOCK_CMD1_I2C_CCG = 0x81, FU_QSI_DOCK_CMD1_MASS_MCU = 0xC0, FU_QSI_DOCK_CMD1_MASS_SPI, FU_QSI_DOCK_CMD1_MASS_I2C_VMM, FU_QSI_DOCK_CMD1_MASS_I2C_CY, } FuQsiDockCmd; typedef enum { FU_QSI_DOCK_CMD2_CMD_DEVICE_STATUS, FU_QSI_DOCK_CMD2_CMD_SET_BOOT_MODE, FU_QSI_DOCK_CMD2_CMD_SET_AP_MODE, FU_QSI_DOCK_CMD2_CMD_ERASE_AP_PAGE, FU_QSI_DOCK_CMD2_CMD_CHECKSUM, FU_QSI_DOCK_CMD2_CMD_DEVICE_VERSION, FU_QSI_DOCK_CMD2_CMD_DEVICE_PCB_VERSION, FU_QSI_DOCK_CMD2_CMD_DEVICE_SN, } FuQsiDockCmd2_0x51; typedef enum { FU_QSI_DOCK_CMD2_SPI_EXTERNAL_FLASH_INI, FU_QSI_DOCK_CMD2_SPI_EXTERNAL_FLASH_ERASE, FU_QSI_DOCK_CMD2_SPI_EXTERNAL_FLASH_CHECKSUM, } FuQsiDockCmd2_0x61; fwupd-1.9.16/plugins/qsi-dock/fu-qsi-dock-mcu-device.c000066400000000000000000000372521460375044200224530ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2022 Kevin Chen * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-qsi-dock-child-device.h" #include "fu-qsi-dock-common.h" #include "fu-qsi-dock-mcu-device.h" struct _FuQsiDockMcuDevice { FuHidDevice parent_instance; }; G_DEFINE_TYPE(FuQsiDockMcuDevice, fu_qsi_dock_mcu_device, FU_TYPE_HID_DEVICE) #define FU_QSI_DOCK_MCU_DEVICE_TIMEOUT 90000 /* ms */ #define FU_QSI_DOCK_TX_ISP_LENGTH 61 #define FU_QSI_DOCK_TX_ISP_LENGTH_MCU 60 #define FU_QSI_DOCK_EXTERN_FLASH_PAGE_SIZE 256 static gboolean fu_qsi_dock_mcu_device_tx(FuQsiDockMcuDevice *self, guint8 CmdPrimary, guint8 CmdSecond, const guint8 *inbuf, gsize inbufsz, GError **error) { guint8 buf[64] = {FU_QSI_DOCK_REPORT_ID, CmdPrimary, CmdSecond}; return fu_hid_device_set_report(FU_HID_DEVICE(self), FU_QSI_DOCK_REPORT_ID, buf, sizeof(buf), FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error); } static gboolean fu_qsi_dock_mcu_device_rx(FuQsiDockMcuDevice *self, guint8 *outbuf, gsize outbufsz, GError **error) { guint8 buf[64]; if (!fu_hid_device_get_report(FU_HID_DEVICE(self), FU_QSI_DOCK_REPORT_ID, buf, sizeof(buf), FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) { return FALSE; } if (outbuf != NULL) { if (!fu_memcpy_safe(outbuf, outbufsz, 0x0, /* dst */ buf, sizeof(buf), 0x5, /* src */ outbufsz, error)) return FALSE; } return TRUE; } static gboolean fu_qsi_dock_mcu_device_txrx(FuQsiDockMcuDevice *self, guint8 cmd1, guint8 cmd2, const guint8 *inbuf, gsize inbufsz, guint8 *outbuf, gsize outbufsz, GError **error) { if (!fu_qsi_dock_mcu_device_tx(self, cmd1, cmd2, inbuf, inbufsz, error)) return FALSE; return fu_qsi_dock_mcu_device_rx(self, outbuf, outbufsz, error); } static gboolean fu_qsi_dock_mcu_device_get_status(FuQsiDockMcuDevice *self, GError **error) { guint8 response = 0; guint8 cmd1 = FU_QSI_DOCK_CMD1_MCU; guint8 cmd2 = FU_QSI_DOCK_CMD2_CMD_DEVICE_STATUS; if (!fu_qsi_dock_mcu_device_txrx(self, cmd1, cmd2, &cmd1, sizeof(cmd1), &response, sizeof(response), error)) return FALSE; /* success */ return TRUE; } static gboolean fu_qsi_dock_mcu_device_enumerate_children(FuQsiDockMcuDevice *self, GError **error) { guint8 outbuf[49] = {0}; struct { const gchar *name; guint8 chip_idx; gsize offset; } components[] = { {"MCU", FU_QSI_DOCK_FIRMWARE_IDX_MCU, G_STRUCT_OFFSET(FuQsiDockIspVersionInMcu, MCU)}, {"bcdVersion", FU_QSI_DOCK_FIRMWARE_IDX_NONE, G_STRUCT_OFFSET(FuQsiDockIspVersionInMcu, bcdVersion)}, {NULL, 0, 0}}; for (guint i = 0; components[i].name != NULL; i++) { const guint8 *val = outbuf + components[i].offset; g_autofree gchar *version = NULL; g_autoptr(FuDevice) child = NULL; child = fu_qsi_dock_child_new(fu_device_get_context(FU_DEVICE(self))); if (g_strcmp0(components[i].name, "bcdVersion") == 0) { if ((val[0] == 0x00 && val[1] == 0x00) || (val[0] == 0xFF && val[1] == 0xFF)) { g_debug("ignoring %s", components[i].name); continue; } version = g_strdup_printf("%x.%x.%02x", val[0] & 0xFu, val[0] >> 4, val[1]); g_debug("ignoring %s --> %s", components[i].name, version); continue; } if (g_strcmp0(components[i].name, "MCU") == 0) { if ((val[0] == 0x00 && val[1] == 0x00) || (val[0] == 0xFF && val[1] == 0xFF)) { g_debug("ignoring %s", components[i].name); continue; } version = g_strdup_printf("%X.%X", val[0], val[1]); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_version(child, version); fu_device_set_name(child, "Dock Management Controller"); } else { g_warning("unhandled %s", components[i].name); } /* add virtual device */ fu_device_add_instance_u16(child, "VID", fu_usb_device_get_vid(FU_USB_DEVICE(self))); fu_device_add_instance_u16(child, "PID", fu_usb_device_get_pid(FU_USB_DEVICE(self))); fu_device_add_instance_str(child, "CID", components[i].name); if (!fu_device_build_instance_id(child, error, "USB", "VID", "PID", "CID", NULL)) return FALSE; if (fu_device_get_name(child) == NULL) fu_device_set_name(child, components[i].name); fu_device_set_logical_id(child, components[i].name); fu_qsi_dock_child_device_set_chip_idx(FU_QSI_DOCK_CHILD_DEVICE(child), components[i].chip_idx); fu_device_add_child(FU_DEVICE(self), child); } /* success */ return TRUE; } static gboolean fu_qsi_dock_mcu_device_setup(FuDevice *device, GError **error) { FuQsiDockMcuDevice *self = FU_QSI_DOCK_MCU_DEVICE(device); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_qsi_dock_mcu_device_parent_class)->setup(device, error)) return FALSE; /* get status and component versions */ if (!fu_qsi_dock_mcu_device_get_status(self, error)) return FALSE; if (!fu_qsi_dock_mcu_device_enumerate_children(self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_qsi_dock_mcu_device_checksum(FuQsiDockMcuDevice *self, guint32 checksum, guint32 length, GError **error) { guint8 buf[64] = {FU_QSI_DOCK_REPORT_ID, FU_QSI_DOCK_CMD1_SPI, FU_QSI_DOCK_CMD2_SPI_EXTERNAL_FLASH_CHECKSUM, 0}; guint8 fw_length[4]; guint8 checksum_val[2]; fu_memwrite_uint32(fw_length, length, G_LITTLE_ENDIAN); fu_memwrite_uint16(checksum_val, checksum, G_LITTLE_ENDIAN); /* fw length */ if (!fu_memcpy_safe(buf, sizeof(buf), 0x03, /* dst */ fw_length, sizeof(fw_length), 0x0, sizeof(fw_length), error)) return FALSE; /* checksum */ if (!fu_memcpy_safe(buf, sizeof(buf), 0x07, /* dst */ checksum_val, sizeof(checksum_val), 0x0, sizeof(checksum_val), error)) return FALSE; /* SetReport+GetReport */ if (!fu_hid_device_set_report(FU_HID_DEVICE(self), FU_QSI_DOCK_REPORT_ID, buf, sizeof(buf), FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) return FALSE; memset(buf, 0x0, sizeof(buf)); if (!fu_hid_device_get_report(FU_HID_DEVICE(self), FU_QSI_DOCK_REPORT_ID, buf, sizeof(buf), FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) return FALSE; /* MCU Checksum Compare Result 0:Pass 1:Fail */ if (buf[2] != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum did not match"); return FALSE; } return TRUE; } static gboolean fu_qsi_dock_mcu_device_write_chunk(FuQsiDockMcuDevice *self, FuChunk *chk_page, guint32 *checksum_tmp, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GBytes) chk_bytes = fu_chunk_get_bytes(chk_page); chunks = fu_chunk_array_new_from_bytes(chk_bytes, 0x0, FU_QSI_DOCK_TX_ISP_LENGTH_MCU); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); guint8 checksum_buf[FU_QSI_DOCK_TX_ISP_LENGTH_MCU] = {0x0}; guint8 buf[64] = { FU_QSI_DOCK_REPORT_ID, FU_QSI_DOCK_CMD1_MASS_SPI, }; /* SetReport */ if (!fu_memcpy_safe(buf, sizeof(buf), 0x04, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), FU_QSI_DOCK_REPORT_ID, buf, sizeof(buf), FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) return FALSE; /* sum checksum value */ if (!fu_memcpy_safe(checksum_buf, sizeof(checksum_buf), 0x0, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; *checksum_tmp += fu_sum32(checksum_buf, sizeof(checksum_buf)); /* GetReport */ memset(buf, 0x0, sizeof(buf)); if (!fu_hid_device_get_report(FU_HID_DEVICE(self), FU_QSI_DOCK_REPORT_ID, buf, sizeof(buf), FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) return FALSE; /* MCU ACK 0:Pass 1:Fail */ if (buf[2] != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "ACK error for chunk %u", i); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_qsi_dock_mcu_device_write_chunks(FuQsiDockMcuDevice *self, FuChunkArray *chunks, guint32 *checksum, FuProgress *progress, GError **error) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_qsi_dock_mcu_device_write_chunk(self, chk, checksum, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write chunk 0x%x: ", i); return FALSE; } fu_progress_step_done(progress); } return TRUE; } static gboolean fu_qsi_dock_mcu_device_wait_for_spi_initial_ready_cb(FuDevice *device, gpointer user_data, GError **error) { FuQsiDockMcuDevice *self = FU_QSI_DOCK_MCU_DEVICE(device); guint8 buf[64] = {FU_QSI_DOCK_REPORT_ID, FU_QSI_DOCK_CMD1_SPI, FU_QSI_DOCK_CMD2_SPI_EXTERNAL_FLASH_INI}; /* SetReport+GetReport */ if (!fu_hid_device_set_report(FU_HID_DEVICE(self), FU_QSI_DOCK_REPORT_ID, buf, sizeof(buf), FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) return FALSE; memset(buf, 0x0, sizeof(buf)); if (!fu_hid_device_get_report(FU_HID_DEVICE(self), FU_QSI_DOCK_REPORT_ID, buf, sizeof(buf), FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_qsi_dock_mcu_device_wait_for_spi_erase_ready_cb(FuQsiDockMcuDevice *self, guint32 length, gpointer user_data, GError **error) { guint8 buf[64] = {FU_QSI_DOCK_REPORT_ID, FU_QSI_DOCK_CMD1_SPI, FU_QSI_DOCK_CMD2_SPI_EXTERNAL_FLASH_ERASE}; guint32 offset = 0; guint8 fw_length[4]; guint8 flash_offset[4]; fu_memwrite_uint32(fw_length, length, G_LITTLE_ENDIAN); fu_memwrite_uint32(flash_offset, offset, G_LITTLE_ENDIAN); /* write erase flash size */ if (!fu_memcpy_safe(buf, sizeof(buf), 0x03, /* dst */ fw_length, sizeof(fw_length), 0x0, sizeof(fw_length), error)) return FALSE; if (!fu_memcpy_safe(buf, sizeof(buf), 0x07, /* dst */ flash_offset, sizeof(flash_offset), 0x0, sizeof(flash_offset), error)) return FALSE; /* SetReport+GetReport */ if (!fu_hid_device_set_report(FU_HID_DEVICE(self), FU_QSI_DOCK_REPORT_ID, buf, sizeof(buf), FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) return FALSE; memset(buf, 0x0, sizeof(buf)); if (!fu_hid_device_get_report(FU_HID_DEVICE(self), FU_QSI_DOCK_REPORT_ID, buf, sizeof(buf), FU_QSI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) return FALSE; /* success */ return TRUE; } gboolean fu_qsi_dock_mcu_device_write_firmware_with_idx(FuQsiDockMcuDevice *self, FuFirmware *firmware, guint8 chip_idx, FuProgress *progress, FwupdInstallFlags flags, GError **error) { guint32 checksum_val = 0; g_autoptr(GBytes) fw_align = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 10, NULL); /* align data */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; fw_align = fu_bytes_align(fw, FU_QSI_DOCK_EXTERN_FLASH_PAGE_SIZE, 0x0); /* initial external flash */ if (!fu_device_retry(FU_DEVICE(self), fu_qsi_dock_mcu_device_wait_for_spi_initial_ready_cb, 30, NULL, error)) { g_prefix_error(error, "failed to wait for initial: "); return FALSE; } if (!fu_qsi_dock_mcu_device_wait_for_spi_erase_ready_cb(self, g_bytes_get_size(fw), fu_progress_get_child(progress), error)) return FALSE; /* write external flash */ chunks = fu_chunk_array_new_from_bytes(fw_align, 0, FU_QSI_DOCK_EXTERN_FLASH_PAGE_SIZE); if (!fu_qsi_dock_mcu_device_write_chunks(self, chunks, &checksum_val, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* verify flash data */ if (!fu_qsi_dock_mcu_device_checksum(self, checksum_val, g_bytes_get_size(fw), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_qsi_dock_mcu_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { return fu_qsi_dock_mcu_device_write_firmware_with_idx(FU_QSI_DOCK_MCU_DEVICE(device), firmware, 0xFF, progress, flags, error); } static gboolean fu_qsi_dock_mcu_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuQsiDockMcuDevice *self = FU_QSI_DOCK_MCU_DEVICE(device); if (!fu_qsi_dock_mcu_device_get_status(self, error)) return FALSE; /* success */ return TRUE; } static void fu_qsi_dock_mcu_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_qsi_dock_mcu_device_init(FuQsiDockMcuDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_AUTODETECT_EPS); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_NUMBER); fu_device_add_protocol(FU_DEVICE(self), "com.qsi.dock"); } static void fu_qsi_dock_mcu_device_class_init(FuQsiDockMcuDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_qsi_dock_mcu_device_setup; klass_device->attach = fu_qsi_dock_mcu_device_attach; klass_device->set_progress = fu_qsi_dock_mcu_device_set_progress; klass_device->write_firmware = fu_qsi_dock_mcu_device_write_firmware; } fwupd-1.9.16/plugins/qsi-dock/fu-qsi-dock-mcu-device.h000066400000000000000000000011241460375044200224450ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_QSI_DOCK_MCU_DEVICE (fu_qsi_dock_mcu_device_get_type()) G_DECLARE_FINAL_TYPE(FuQsiDockMcuDevice, fu_qsi_dock_mcu_device, FU, QSI_DOCK_MCU_DEVICE, FuHidDevice) gboolean fu_qsi_dock_mcu_device_write_firmware_with_idx(FuQsiDockMcuDevice *self, FuFirmware *firmware, guint8 chip_idx, FuProgress *progress, FwupdInstallFlags flags, GError **error); fwupd-1.9.16/plugins/qsi-dock/fu-qsi-dock-plugin.c000066400000000000000000000014371460375044200217240ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2022 Kevin Chen * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-qsi-dock-mcu-device.h" #include "fu-qsi-dock-plugin.h" struct _FuQsiDockPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuQsiDockPlugin, fu_qsi_dock_plugin, FU_TYPE_PLUGIN) static void fu_qsi_dock_plugin_init(FuQsiDockPlugin *self) { } static void fu_qsi_dock_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_QSI_DOCK_MCU_DEVICE); } static void fu_qsi_dock_plugin_class_init(FuQsiDockPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_qsi_dock_plugin_constructed; } fwupd-1.9.16/plugins/qsi-dock/fu-qsi-dock-plugin.h000066400000000000000000000003551460375044200217270ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuQsiDockPlugin, fu_qsi_dock_plugin, FU, QSI_DOCK_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/qsi-dock/meson.build000066400000000000000000000007031460375044200202770ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginQsiDock"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('qsi-dock.quirk') plugin_builtins += static_library('fu_plugin_qsi_dock', sources: [ 'fu-qsi-dock-child-device.c', 'fu-qsi-dock-mcu-device.c', 'fu-qsi-dock-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/qsi-dock/qsi-dock.quirk000066400000000000000000000001051460375044200207200ustar00rootroot00000000000000[USB\VID_047D&PID_808D] Plugin = qsi_dock GType = FuQsiDockMcuDevice fwupd-1.9.16/plugins/realtek-mst/000077500000000000000000000000001460375044200166535ustar00rootroot00000000000000fwupd-1.9.16/plugins/realtek-mst/README.md000066400000000000000000000035201460375044200201320ustar00rootroot00000000000000--- title: Plugin: Realtek MST --- ## Introduction This plugin updates the firmware of DisplayPort MST hub devices made by Realtek, such as the RTD2141b and RTD2142. These devices communicate over I²C, via the DisplayPort aux channel. Devices are declared by system firmware, and quirks specify the aux channel to which the device is connected for a given system. System firmware must specify the device's presence because while they can be identified partially through the presence of Realtek's OUI in the Branch Device OUI fields of DPCD (DisplayPort Configuration Data), they do not have unique Device Identification strings. This plugin was neither written, verified, supported or endorsed by Realtek Semiconductor Corp. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format, which is written to the partition of the device flash that is not currently running. This plugin supports the following protocol ID: * `com.realtek.rtd2142` ## GUID Generation Devices use an extra instance ID derived from SMBIOS, e.g. * `I2C\NAME_10EC2142:00&FAMILY_Google_Hatch` ## Quirk Use This plugin uses the following plugin-specific quirks: ### RealtekMstDpAuxName Specifies the name of the drm_dp_aux_dev channel over which the device should be reached. Since: 1.6.2 ## Vendor ID security The vendor ID is specified by system firmware (such as ACPI tables). ## External Interface Access This plugin requires access to i2c buses associated with the specified DisplayPort aux channel, usually `/dev/i2c-5` or similar. ## Version Considerations This plugin has been available since fwupd version `1.6.2`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Peter Marheine: @tari fwupd-1.9.16/plugins/realtek-mst/fu-realtek-mst-device.c000066400000000000000000000751751460375044200231330ustar00rootroot00000000000000/* * Copyright (C) 2021 Peter Marheine * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_ERRNO_H #include #endif #include #include #include #include "fu-realtek-mst-device.h" /* firmware debug address */ #define I2C_ADDR_DEBUG 0x35 /* programming address */ #define I2C_ADDR_ISP 0x4a /* some kind of operation attribute bits */ #define REG_CMD_ATTR 0x60 /* write set to begin executing, cleared when done */ #define CMD_ERASE_BUSY 0x01 /* 24-bit address for commands */ #define REG_CMD_ADDR_HI 0x64 #define REG_CMD_ADDR_MID 0x65 #define REG_CMD_ADDR_LO 0x66 /* register for erase commands */ #define REG_ERASE_OPCODE 0x61 #define CMD_OPCODE_ERASE_SECTOR 0x20 #define CMD_OPCODE_ERASE_BLOCK 0xD8 /* register for read commands */ #define REG_READ_OPCODE 0x6A #define CMD_OPCODE_READ 0x03 /* register for write commands */ #define REG_WRITE_OPCODE 0x6D #define CMD_OPCODE_WRITE 0x02 /* mode register address */ #define REG_MCU_MODE 0x6F /* when bit is set in mode register, ISP mode is active */ #define MCU_MODE_ISP (1 << 7) /* write set to begin write, reset by device when complete */ #define MCU_MODE_WRITE_BUSY (1 << 5) /* when bit is clear, write buffer contains data */ #define MCU_MODE_WRITE_BUF (1 << 4) /* write data into write buffer */ #define REG_WRITE_FIFO 0x70 /* number of bytes to write minus 1 (0xff means 256 bytes) */ #define REG_WRITE_LEN 0x71 /* Indirect registers allow access to registers with 16-bit addresses. Write * 0x9F to the LO register, then the top byte of the address to HI, the * bottom byte of the address to LO, then read or write HI to read or write * the value of the target register. */ #define REG_INDIRECT_LO 0xF4 #define REG_INDIRECT_HI 0xF5 /* GPIO configuration/access registers */ #define REG_GPIO88_CONFIG 0x104F #define REG_GPIO88_VALUE 0xFE3F /* flash chip properties */ #define FLASH_SIZE 0x100000 #define FLASH_SECTOR_SIZE 4096 #define FLASH_BLOCK_SIZE 65536 /* MST flash layout */ #define FLASH_USER1_ADDR 0x10000 #define FLASH_FLAG1_ADDR 0xfe304 #define FLASH_USER2_ADDR 0x80000 #define FLASH_FLAG2_ADDR 0xff304 #define FLASH_USER_SIZE 0x70000 enum dual_bank_mode { DUAL_BANK_USER_ONLY = 0, DUAL_BANK_DIFF = 1, DUAL_BANK_COPY = 2, DUAL_BANK_USER_ONLY_FLAG = 3, DUAL_BANK_MAX_VALUE = 3, }; enum flash_bank { FLASH_BANK_BOOT = 0, FLASH_BANK_USER1 = 1, FLASH_BANK_USER2 = 2, FLASH_BANK_MAX_VALUE = 2, FLASH_BANK_INVALID = 255, }; struct dual_bank_info { gboolean is_enabled; enum dual_bank_mode mode; enum flash_bank active_bank; guint8 user1_version[2]; guint8 user2_version[2]; }; struct _FuRealtekMstDevice { FuI2cDevice parent_instance; gchar *dp_aux_dev_name; gchar *dp_card_kernel_name; enum flash_bank active_bank; }; G_DEFINE_TYPE(FuRealtekMstDevice, fu_realtek_mst_device, FU_TYPE_I2C_DEVICE) #define FU_REALTEK_MST_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ static gboolean fu_realtek_mst_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE(device); if (g_strcmp0(key, "RealtekMstDpAuxName") == 0) { self->dp_aux_dev_name = g_strdup(value); } else if (g_strcmp0(key, "RealtekMstDrmCardKernelName") == 0) { self->dp_card_kernel_name = g_strdup(value); } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported quirk key: %s", key); return FALSE; } return TRUE; } static FuUdevDevice * locate_i2c_bus(const GPtrArray *i2c_devices) { for (guint i = 0; i < i2c_devices->len; i++) { FuUdevDevice *i2c_device = g_ptr_array_index(i2c_devices, i); FuUdevDevice *bus_device; g_autoptr(GPtrArray) i2c_buses = fu_udev_device_get_children_with_subsystem(i2c_device, "i2c-dev"); if (i2c_buses->len == 0) { g_debug("no i2c-dev found under %s", fu_udev_device_get_sysfs_path(i2c_device)); continue; } if (i2c_buses->len > 1) { g_debug("ignoring %u additional i2c-dev under %s", i2c_buses->len - 1, fu_udev_device_get_sysfs_path(i2c_device)); } bus_device = g_object_ref(g_ptr_array_index(i2c_buses, 0)); g_debug("found I2C bus at %s, using this device", fu_udev_device_get_sysfs_path(bus_device)); return bus_device; } return NULL; } static gboolean fu_realtek_mst_device_use_aux_dev(FuRealtekMstDevice *self, GError **error) { g_autoptr(GUdevClient) udev_client = g_udev_client_new(NULL); g_autoptr(GUdevEnumerator) udev_enumerator = g_udev_enumerator_new(udev_client); g_autoptr(GList) matches = NULL; FuUdevDevice *bus_device = NULL; g_udev_enumerator_add_match_subsystem(udev_enumerator, "drm_dp_aux_dev"); g_udev_enumerator_add_match_sysfs_attr(udev_enumerator, "name", self->dp_aux_dev_name); matches = g_udev_enumerator_execute(udev_enumerator); /* from a drm_dp_aux_dev with the given name, locate its sibling i2c * device and in turn the i2c-dev under that representing the actual * I2C bus that runs over DPDDC on the port represented by the * drm_dp_aux_dev */ for (GList *element = matches; element != NULL; element = element->next) { g_autoptr(FuUdevDevice) device = NULL; g_autoptr(GPtrArray) i2c_devices = NULL; device = fu_udev_device_new(fu_device_get_context(FU_DEVICE(self)), element->data); if (bus_device != NULL) { g_debug("ignoring additional aux device %s", fu_udev_device_get_sysfs_path(device)); continue; } i2c_devices = fu_udev_device_get_siblings_with_subsystem(device, "i2c"); bus_device = locate_i2c_bus(i2c_devices); } if (bus_device == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "did not find an i2c-dev associated with DP aux \"%s\"", self->dp_aux_dev_name); return FALSE; } fu_udev_device_set_dev(FU_UDEV_DEVICE(self), fu_udev_device_get_dev(bus_device)); return TRUE; } static gboolean fu_realtek_mst_device_use_drm_card(FuRealtekMstDevice *self, GError **error) { g_autoptr(GUdevClient) udev_client = g_udev_client_new(NULL); g_autoptr(GUdevEnumerator) enumerator = g_udev_enumerator_new(udev_client); g_autoptr(GList) drm_devices = NULL; g_autoptr(FuUdevDevice) bus_device = NULL; /* from a drm device with the given name, find an i2c device under it * and in turn an i2c-dev device representing the DPDDC bus */ g_debug("search for DRM device with name %s", self->dp_card_kernel_name); g_udev_enumerator_add_match_subsystem(enumerator, "drm"); g_udev_enumerator_add_match_name(enumerator, self->dp_card_kernel_name); drm_devices = g_udev_enumerator_execute(enumerator); for (GList *element = drm_devices; element != NULL; element = element->next) { g_autoptr(FuUdevDevice) drm_device = NULL; g_autoptr(GPtrArray) i2c_devices = NULL; drm_device = fu_udev_device_new(fu_device_get_context(FU_DEVICE(self)), element->data); if (bus_device != NULL) { g_debug("ignoring additional drm device %s", fu_udev_device_get_sysfs_path(drm_device)); continue; } i2c_devices = fu_udev_device_get_children_with_subsystem(drm_device, "i2c"); bus_device = locate_i2c_bus(i2c_devices); } if (bus_device == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "did not find an i2c-dev associated with drm device %s", self->dp_card_kernel_name); return FALSE; } fu_udev_device_set_dev(FU_UDEV_DEVICE(self), fu_udev_device_get_dev(bus_device)); return TRUE; } static gboolean mst_ensure_device_address(FuRealtekMstDevice *self, guint8 address, GError **error) { return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), I2C_SLAVE, (guint8 *)(guintptr)address, NULL, FU_REALTEK_MST_DEVICE_IOCTL_TIMEOUT, error); } /** Write a value to a device register */ static gboolean mst_write_register(FuRealtekMstDevice *self, guint8 address, guint8 value, GError **error) { const guint8 command[] = {address, value}; return fu_i2c_device_write(FU_I2C_DEVICE(self), command, sizeof(command), error); } static gboolean mst_write_register_multi(FuRealtekMstDevice *self, guint8 address, const guint8 *data, gsize count, GError **error) { g_autofree guint8 *command = g_malloc0(count + 1); memcpy(command + 1, data, count); command[0] = address; return fu_i2c_device_write(FU_I2C_DEVICE(self), command, count + 1, error); } /** Read a register from the device */ static gboolean mst_read_register(FuRealtekMstDevice *self, guint8 address, guint8 *value, GError **error) { if (!fu_i2c_device_write(FU_I2C_DEVICE(self), &address, 0x1, error)) return FALSE; return fu_i2c_device_read(FU_I2C_DEVICE(self), value, 0x1, error); } static gboolean mst_set_indirect_address(FuRealtekMstDevice *self, guint16 address, GError **error) { if (!mst_write_register(self, REG_INDIRECT_LO, 0x9F, error)) return FALSE; if (!mst_write_register(self, REG_INDIRECT_HI, address >> 8, error)) return FALSE; return mst_write_register(self, REG_INDIRECT_LO, address, error); } static gboolean mst_read_register_indirect(FuRealtekMstDevice *self, guint16 address, guint8 *value, GError **error) { if (!mst_set_indirect_address(self, address, error)) return FALSE; return mst_read_register(self, REG_INDIRECT_HI, value, error); } static gboolean mst_write_register_indirect(FuRealtekMstDevice *self, guint16 address, guint8 value, GError **error) { if (!mst_set_indirect_address(self, address, error)) return FALSE; return mst_write_register(self, REG_INDIRECT_HI, value, error); } /** * Wait until a device register reads an expected value. * * Waiting up to @timeout_seconds, poll the given @address for the read value * bitwise-ANDed with @mask to be equal to @expected. * * Returns an error if the timeout expires or in case of an I/O error. */ static gboolean mst_poll_register(FuRealtekMstDevice *self, guint8 address, guint8 mask, guint8 expected, guint timeout_seconds, GError **error) { guint8 value; g_autoptr(GTimer) timer = g_timer_new(); if (!mst_read_register(self, address, &value, error)) return FALSE; while ((value & mask) != expected && g_timer_elapsed(timer, NULL) <= timeout_seconds) { fu_device_sleep(FU_DEVICE(self), 1); /* ms */ if (!mst_read_register(self, address, &value, error)) return FALSE; } if ((value & mask) == expected) return TRUE; g_set_error(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "register %x still reads %x after %us, wanted %x (mask %x)", address, value, timeout_seconds, expected, mask); return FALSE; } static gboolean mst_set_gpio88(FuRealtekMstDevice *self, gboolean level, GError **error) { guint8 value; /* ensure pin is configured as push-pull GPIO */ if (!mst_read_register_indirect(self, REG_GPIO88_CONFIG, &value, error)) return FALSE; if (!mst_write_register_indirect(self, REG_GPIO88_CONFIG, (value & 0xF0) | 1, error)) return FALSE; /* set output level */ g_debug("set pin 88 = %d", level); if (!mst_read_register_indirect(self, REG_GPIO88_VALUE, &value, error)) return FALSE; return mst_write_register_indirect(self, REG_GPIO88_VALUE, (value & 0xFE) | (level != FALSE), error); } static gboolean fu_realtek_mst_device_probe(FuDevice *device, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE(device); FuContext *context = fu_device_get_context(device); /* FuI2cDevice->probe */ if (!FU_DEVICE_CLASS(fu_realtek_mst_device_parent_class)->probe(device, error)) return FALSE; /* add custom instance ID and load matching quirks */ fu_device_add_instance_str(device, "FAMILY", fu_context_get_hwid_value(context, FU_HWIDS_KEY_FAMILY)); if (!fu_device_build_instance_id(device, error, "I2C", "NAME", "FAMILY", NULL)) return FALSE; /* having loaded quirks, check this device is supported */ if (self->dp_aux_dev_name != NULL) { if (!fu_realtek_mst_device_use_aux_dev(self, error)) return FALSE; } else if (self->dp_card_kernel_name != NULL) { if (!fu_realtek_mst_device_use_drm_card(self, error)) return FALSE; } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "one of RealtekMstDpAuxName or RealtekMstDrmCardKernelName" " must be specified"); return FALSE; } /* locate its sibling i2c device and use that instead */ /* success */ return TRUE; } static gboolean fu_realtek_mst_device_get_dual_bank_info(FuRealtekMstDevice *self, struct dual_bank_info *info, GError **error) { const guint8 request[] = {0x01}; guint8 response[11] = {0x0}; if (!mst_ensure_device_address(self, I2C_ADDR_DEBUG, error)) return FALSE; /* switch to DDCCI mode */ if (!mst_write_register(self, 0xca, 0x09, error)) return FALSE; /* wait for mode switch to complete */ fu_device_sleep(FU_DEVICE(self), 200); /* ms */ /* request dual bank state and read back */ if (!fu_i2c_device_write(FU_I2C_DEVICE(self), request, sizeof(request), error)) return FALSE; if (!fu_i2c_device_read(FU_I2C_DEVICE(self), response, sizeof(response), error)) return FALSE; if (response[0] != 0xca || response[1] != 9) { /* unexpected response code or length usually means the current * firmware doesn't support dual-bank mode at all */ g_debug("unexpected response code %#x, length %d", response[0], response[1]); info->is_enabled = FALSE; return TRUE; } /* enable flag, assume anything other than 1 is unsupported */ if (response[2] != 1) { info->is_enabled = FALSE; return TRUE; } info->is_enabled = TRUE; info->mode = response[3]; if (info->mode > DUAL_BANK_MAX_VALUE) { g_debug("unexpected dual bank mode value %#x", info->mode); info->is_enabled = FALSE; return TRUE; } info->active_bank = response[4]; if (info->active_bank > FLASH_BANK_MAX_VALUE) { g_debug("unexpected active flash bank value %#x", info->active_bank); info->is_enabled = FALSE; return TRUE; } info->user1_version[0] = response[5]; info->user1_version[1] = response[6]; info->user2_version[0] = response[7]; info->user2_version[1] = response[8]; /* last two bytes of response are reserved */ return TRUE; } static gboolean fu_realtek_mst_device_probe_version(FuDevice *device, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE(device); struct dual_bank_info info = {0x0}; guint8 *active_version; g_autofree gchar *version_str = NULL; /* ensure probed state is cleared in case of error */ fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE); self->active_bank = FLASH_BANK_INVALID; fu_device_set_version(device, NULL); if (!fu_realtek_mst_device_get_dual_bank_info(FU_REALTEK_MST_DEVICE(self), &info, error)) return FALSE; if (!info.is_enabled) { fu_device_inhibit(device, "dual-bank", "Dual-bank mode is not enabled"); return TRUE; } if (info.mode != DUAL_BANK_DIFF) { fu_device_inhibit(device, "dual-bank", "Can only update from dual-bank-diff mode"); return TRUE; } /* dual-bank mode seems to be fully supported, so we can update * regardless of the active bank- if it's FLASH_BANK_BOOT, updating is * possible even if the current version is unknown */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_uninhibit(device, "dual-bank"); g_info("device is currently running from bank %u", info.active_bank); g_return_val_if_fail(info.active_bank <= FLASH_BANK_MAX_VALUE, FALSE); self->active_bank = info.active_bank; g_info("firmware version reports user1 %d.%d, user2 %d.%d", info.user1_version[0], info.user1_version[1], info.user2_version[0], info.user2_version[1]); if (info.active_bank == FLASH_BANK_USER1) active_version = info.user1_version; else if (info.active_bank == FLASH_BANK_USER2) active_version = info.user2_version; else /* only user bank versions are reported, can't tell otherwise */ return TRUE; version_str = g_strdup_printf("%u.%u", active_version[0], active_version[1]); fu_device_set_version(FU_DEVICE(self), version_str); return TRUE; } static gboolean flash_iface_read(FuRealtekMstDevice *self, guint32 address, guint8 *buf, const gsize buf_size, FuProgress *progress, GError **error) { gsize bytes_read = 0; guint8 byte; const guint8 req[] = {0x70}; g_return_val_if_fail(address < FLASH_SIZE, FALSE); g_return_val_if_fail(buf_size <= FLASH_SIZE, FALSE); g_debug("read %#" G_GSIZE_MODIFIER "x bytes from %#08x", buf_size, address); /* read must start one byte prior to the desired address and ignore the * first byte of data, since the first read value is unpredictable */ address = (address - 1) & 0xFFFFFF; if (!mst_write_register(self, REG_CMD_ADDR_HI, address >> 16, error)) return FALSE; if (!mst_write_register(self, REG_CMD_ADDR_MID, address >> 8, error)) return FALSE; if (!mst_write_register(self, REG_CMD_ADDR_LO, address, error)) return FALSE; if (!mst_write_register(self, REG_READ_OPCODE, CMD_OPCODE_READ, error)) return FALSE; /* ignore first byte of data */ if (!fu_i2c_device_write(FU_I2C_DEVICE(self), req, sizeof(req), error)) return FALSE; if (!fu_i2c_device_read(FU_I2C_DEVICE(self), &byte, 0x1, error)) return FALSE; while (bytes_read < buf_size) { /* read up to 256 bytes in one transaction */ gsize read_len = buf_size - bytes_read; if (read_len > 256) read_len = 256; if (!fu_i2c_device_read(FU_I2C_DEVICE(self), buf + bytes_read, read_len, error)) return FALSE; bytes_read += read_len; fu_progress_set_percentage_full(progress, bytes_read, buf_size); } return TRUE; } static gboolean flash_iface_erase_sector(FuRealtekMstDevice *self, guint32 address, GError **error) { /* address must be 4k-aligned */ g_return_val_if_fail((address & 0xFFF) == 0, FALSE); g_debug("sector erase %#08x-%#08x", address, address + FLASH_SECTOR_SIZE); /* sector address */ if (!mst_write_register(self, REG_CMD_ADDR_HI, address >> 16, error)) return FALSE; if (!mst_write_register(self, REG_CMD_ADDR_MID, address >> 8, error)) return FALSE; if (!mst_write_register(self, REG_CMD_ADDR_LO, address, error)) return FALSE; /* command type + WREN */ if (!mst_write_register(self, REG_CMD_ATTR, 0xB8, error)) return FALSE; /* sector erase opcode */ if (!mst_write_register(self, REG_ERASE_OPCODE, CMD_OPCODE_ERASE_SECTOR, error)) return FALSE; /* begin operation and wait for completion */ if (!mst_write_register(self, REG_CMD_ATTR, 0xB8 | CMD_ERASE_BUSY, error)) return FALSE; return mst_poll_register(self, REG_CMD_ATTR, CMD_ERASE_BUSY, 0, 10, error); } static gboolean flash_iface_erase_block(FuRealtekMstDevice *self, guint32 address, GError **error) { /* address must be 64k-aligned */ g_return_val_if_fail((address & 0xFFFF) == 0, FALSE); g_debug("block erase %#08x-%#08x", address, address + FLASH_BLOCK_SIZE); /* block address */ if (!mst_write_register(self, REG_CMD_ADDR_HI, address >> 16, error)) return FALSE; if (!mst_write_register(self, REG_CMD_ADDR_MID, 0, error)) return FALSE; if (!mst_write_register(self, REG_CMD_ADDR_LO, 0, error)) return FALSE; /* command type + WREN */ if (!mst_write_register(self, REG_CMD_ATTR, 0xB8, error)) return FALSE; /* block erase opcode */ if (!mst_write_register(self, REG_ERASE_OPCODE, CMD_OPCODE_ERASE_BLOCK, error)) return FALSE; /* begin operation and wait for completion */ if (!mst_write_register(self, REG_CMD_ATTR, 0xB8 | CMD_ERASE_BUSY, error)) return FALSE; return mst_poll_register(self, REG_CMD_ATTR, CMD_ERASE_BUSY, 0, 10, error); } static gboolean flash_iface_write(FuRealtekMstDevice *self, guint32 address, GBytes *data, FuProgress *progress, GError **error) { gsize total_size = g_bytes_get_size(data); g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(data, address, 256); g_debug("write %#" G_GSIZE_MODIFIER "x bytes at %#08x", total_size, address); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chunk = fu_chunk_array_index(chunks, i); guint32 chunk_address = fu_chunk_get_address(chunk); guint32 chunk_size = fu_chunk_get_data_sz(chunk); /* write opcode */ if (!mst_write_register(self, REG_WRITE_OPCODE, CMD_OPCODE_WRITE, error)) return FALSE; /* write length */ if (!mst_write_register(self, REG_WRITE_LEN, chunk_size - 1, error)) return FALSE; /* target address */ if (!mst_write_register(self, REG_CMD_ADDR_HI, chunk_address >> 16, error)) return FALSE; if (!mst_write_register(self, REG_CMD_ADDR_MID, chunk_address >> 8, error)) return FALSE; if (!mst_write_register(self, REG_CMD_ADDR_LO, chunk_address, error)) return FALSE; /* ensure write buffer is empty */ if (!mst_poll_register(self, REG_MCU_MODE, MCU_MODE_WRITE_BUF, MCU_MODE_WRITE_BUF, 10, error)) { g_prefix_error(error, "failed waiting for write buffer to clear: "); return FALSE; } /* write data into FIFO */ if (!mst_write_register_multi(self, REG_WRITE_FIFO, fu_chunk_get_data(chunk), chunk_size, error)) return FALSE; /* begin operation and wait for completion */ if (!mst_write_register(self, REG_MCU_MODE, MCU_MODE_ISP | MCU_MODE_WRITE_BUSY, error)) return FALSE; if (!mst_poll_register(self, REG_MCU_MODE, MCU_MODE_WRITE_BUSY, 0, 10, error)) { g_prefix_error(error, "timed out waiting for write at %#x to complete: ", address); return FALSE; } fu_progress_set_percentage_full(progress, i + 1, fu_chunk_array_length(chunks)); } return TRUE; } static gboolean fu_realtek_mst_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE(device); if (!mst_ensure_device_address(self, I2C_ADDR_ISP, error)) return FALSE; /* Switch to programming mode (stops regular operation) */ if (!mst_write_register(self, REG_MCU_MODE, MCU_MODE_ISP, error)) return FALSE; g_debug("wait for ISP mode ready"); if (!mst_poll_register(self, REG_MCU_MODE, MCU_MODE_ISP, MCU_MODE_ISP, 60, error)) return FALSE; /* magic value makes the MCU clock run faster than normal; this both * helps programming performance and fixes flakiness where register * writes sometimes get nacked for no apparent reason */ if (!mst_write_register_indirect(self, 0x06A0, 0x74, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); /* Disable hardware write protect, assuming Flash ~WP is connected to * device pin 88, a GPIO. */ return mst_set_gpio88(self, 1, error); } static gboolean fu_realtek_mst_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE(device); /* write an inactive bank: USER2 if USER1 is active, otherwise USER1 * (including if the boot bank is active) */ guint32 base_addr = self->active_bank == FLASH_BANK_USER1 ? FLASH_USER2_ADDR : FLASH_USER1_ADDR; guint32 flag_addr = self->active_bank == FLASH_BANK_USER1 ? FLASH_FLAG2_ADDR : FLASH_FLAG1_ADDR; GBytes *firmware_bytes = fu_firmware_get_bytes(firmware, error); const guint8 flag_data[] = {0xaa, 0xaa, 0xaa, 0xff, 0xff}; g_autofree guint8 *readback_buf = g_malloc0(FLASH_USER_SIZE); g_return_val_if_fail(g_bytes_get_size(firmware_bytes) == FLASH_USER_SIZE, FALSE); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 20, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 70, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 9, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "flag"); if (!mst_ensure_device_address(self, I2C_ADDR_ISP, error)) return FALSE; /* erase old image */ g_debug("erase old image from %#x", base_addr); for (guint32 offset = 0; offset < FLASH_USER_SIZE; offset += FLASH_BLOCK_SIZE) { if (!flash_iface_erase_block(self, base_addr + offset, error)) return FALSE; fu_progress_set_percentage_full(fu_progress_get_child(progress), offset + FLASH_BLOCK_SIZE, FLASH_USER_SIZE); } fu_progress_step_done(progress); /* write new image */ g_debug("write new image to %#x", base_addr); if (!flash_iface_write(self, base_addr, firmware_bytes, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* verify */ if (!flash_iface_read(self, base_addr, readback_buf, FLASH_USER_SIZE, fu_progress_get_child(progress), error)) return FALSE; if (memcmp(g_bytes_get_data(firmware_bytes, NULL), readback_buf, FLASH_USER_SIZE) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "flash contents after write do not match firmware image"); return FALSE; } fu_progress_step_done(progress); /* Erase old flag and write new one. The MST appears to modify the * flag value once booted, so we always write the same value here and * it picks up what we've updated. */ if (!flash_iface_erase_sector(self, flag_addr & ~(FLASH_SECTOR_SIZE - 1), error)) return FALSE; if (!flash_iface_write(self, flag_addr, g_bytes_new_static(flag_data, sizeof(flag_data)), fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static FuFirmware * fu_realtek_mst_device_read_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE(device); guint32 bank_address; g_autofree guint8 *image_bytes = NULL; if (self->active_bank == FLASH_BANK_USER1) bank_address = FLASH_USER1_ADDR; else if (self->active_bank == FLASH_BANK_USER2) bank_address = FLASH_USER2_ADDR; else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot read firmware from bank %u", self->active_bank); return NULL; } image_bytes = g_malloc0(FLASH_USER_SIZE); if (!mst_ensure_device_address(self, I2C_ADDR_ISP, error)) return NULL; if (!flash_iface_read(self, bank_address, image_bytes, FLASH_USER_SIZE, progress, error)) return NULL; return fu_firmware_new_from_bytes( g_bytes_new_take(g_steal_pointer(&image_bytes), FLASH_USER_SIZE)); } static GBytes * fu_realtek_mst_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE(device); g_autofree guint8 *flash_contents = g_malloc0(FLASH_SIZE); if (!mst_ensure_device_address(self, I2C_ADDR_ISP, error)) return NULL; fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); if (!flash_iface_read(self, 0, flash_contents, FLASH_SIZE, progress, error)) return NULL; fu_progress_set_status(progress, FWUPD_STATUS_IDLE); return g_bytes_new_take(g_steal_pointer(&flash_contents), FLASH_SIZE); } static gboolean fu_realtek_mst_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE(device); guint8 value; if (!mst_ensure_device_address(self, I2C_ADDR_ISP, error)) return FALSE; /* re-enable hardware write protect via GPIO */ if (!mst_set_gpio88(self, 0, error)) return FALSE; if (!mst_read_register(self, REG_MCU_MODE, &value, error)) return FALSE; if ((value & MCU_MODE_ISP) != 0) { g_autoptr(GError) error_local = NULL; g_debug("resetting device to exit ISP mode"); /* Set register EE bit 2 to request reset. This write can fail * spuriously, so we ignore the write result and verify the device is * no longer in programming mode after giving it time to reset. */ if (!mst_read_register(self, 0xEE, &value, error)) return FALSE; if (!mst_write_register(self, 0xEE, value | 2, &error_local)) { g_debug("write spuriously failed, ignoring: %s", error_local->message); } /* allow device some time to reset */ fu_device_sleep(device, 1000); /* ms */ /* verify device has exited programming mode and actually reset */ if (!mst_read_register(self, REG_MCU_MODE, &value, error)) return FALSE; if ((value & MCU_MODE_ISP) == MCU_MODE_ISP) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NEEDS_USER_ACTION, "device failed to reset when requested"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN); return FALSE; } } else { g_debug("device is already in normal mode"); } fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static void fu_realtek_mst_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_realtek_mst_device_init(FuRealtekMstDevice *self) { self->active_bank = FLASH_BANK_INVALID; fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_GENERIC_GUIDS); fu_device_add_protocol(FU_DEVICE(self), "com.realtek.rtd2142"); fu_device_set_vendor(FU_DEVICE(self), "Realtek"); fu_device_add_vendor_id(FU_DEVICE(self), "PCI:0x10EC"); fu_device_set_summary(FU_DEVICE(self), "DisplayPort MST hub"); fu_device_add_icon(FU_DEVICE(self), "video-display"); fu_device_set_firmware_size(FU_DEVICE(self), FLASH_USER_SIZE); } static void fu_realtek_mst_device_finalize(GObject *object) { FuRealtekMstDevice *self = FU_REALTEK_MST_DEVICE(object); g_free(self->dp_aux_dev_name); g_free(self->dp_card_kernel_name); G_OBJECT_CLASS(fu_realtek_mst_device_parent_class)->finalize(object); } static void fu_realtek_mst_device_class_init(FuRealtekMstDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *klass_object = G_OBJECT_CLASS(klass); klass_object->finalize = fu_realtek_mst_device_finalize; klass_device->probe = fu_realtek_mst_device_probe; klass_device->set_quirk_kv = fu_realtek_mst_device_set_quirk_kv; klass_device->setup = fu_realtek_mst_device_probe_version; klass_device->detach = fu_realtek_mst_device_detach; klass_device->attach = fu_realtek_mst_device_attach; klass_device->write_firmware = fu_realtek_mst_device_write_firmware; klass_device->reload = fu_realtek_mst_device_probe_version; /* read active image */ klass_device->read_firmware = fu_realtek_mst_device_read_firmware; /* dump whole flash */ klass_device->dump_firmware = fu_realtek_mst_device_dump_firmware; klass_device->set_progress = fu_realtek_mst_device_set_progress; } fwupd-1.9.16/plugins/realtek-mst/fu-realtek-mst-device.h000066400000000000000000000005021460375044200231160ustar00rootroot00000000000000/* * Copyright (C) 2021 Peter Marheine * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_REALTEK_MST_DEVICE (fu_realtek_mst_device_get_type()) G_DECLARE_FINAL_TYPE(FuRealtekMstDevice, fu_realtek_mst_device, FU, REALTEK_MST_DEVICE, FuI2cDevice) fwupd-1.9.16/plugins/realtek-mst/fu-realtek-mst-plugin.c000066400000000000000000000017321460375044200231560ustar00rootroot00000000000000/* * Copyright (C) 2021 Peter Marheine * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-realtek-mst-device.h" #include "fu-realtek-mst-plugin.h" struct _FuRealtekMstPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuRealtekMstPlugin, fu_realtek_mst_plugin, FU_TYPE_PLUGIN) static void fu_realtek_mst_plugin_init(FuRealtekMstPlugin *self) { } static void fu_realtek_mst_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "RealtekMstDpAuxName"); fu_context_add_quirk_key(ctx, "RealtekMstDrmCardKernelName"); fu_plugin_add_udev_subsystem(plugin, "i2c"); fu_plugin_add_device_gtype(plugin, FU_TYPE_REALTEK_MST_DEVICE); } static void fu_realtek_mst_plugin_class_init(FuRealtekMstPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_realtek_mst_plugin_constructed; } fwupd-1.9.16/plugins/realtek-mst/fu-realtek-mst-plugin.h000066400000000000000000000003661460375044200231650ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuRealtekMstPlugin, fu_realtek_mst_plugin, FU, REALTEK_MST_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/realtek-mst/meson.build000066400000000000000000000011301460375044200210100ustar00rootroot00000000000000if get_option('plugin_realtek_mst').require(gudev.found(), error_message: 'gudev is needed for plugin_realtek_mst').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginRealtekMst"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('realtek-mst.quirk') plugin_builtins += static_library('fu_plugin_realtek_mst', sources: [ 'fu-realtek-mst-device.c', 'fu-realtek-mst-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/realtek-mst/realtek-mst.quirk000066400000000000000000000004261460375044200221620ustar00rootroot00000000000000[I2C\NAME_10EC2142:00] Plugin = realtek_mst Name = RTD2142 [I2C\NAME_10EC2142:00&FAMILY_Google_Hatch] RealtekMstDpAuxName = DPDDC-C [I2C\NAME_10EC2141:00] Plugin = realtek_mst Name = RTD2141B [I2C\NAME_10EC2141:00&FAMILY_Google_Zork] RealtekMstDrmCardKernelName = card0-DP-1 fwupd-1.9.16/plugins/redfish/000077500000000000000000000000001460375044200160475ustar00rootroot00000000000000fwupd-1.9.16/plugins/redfish/README.md000066400000000000000000000063341460375044200173340ustar00rootroot00000000000000--- title: Plugin: Redfish --- ## Introduction Redfish is an open industry standard specification and schema that helps enable simple and secure management of modern scalable platform hardware. By specifying a RESTful interface and utilizing JSON and OData, Redfish helps customers integrate solutions within their existing tool chains. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * `org.dmtf.redfish` ## GUID Generation These devices use the provided GUID provided in the `SoftwareId` property without modification if it is a valid GUID. If the property is not a GUID then the vendor instance ID is used instead: * `REDFISH\VENDOR_${RedfishManufacturer}&SOFTWAREID_${RedfishSoftwareId}` Additionally, this Instance ID is added for quirk and parent matching: * `REDFISH\VENDOR_${RedfishManufacturer}&ID_${RedfishId}` ## Update Behavior The firmware will be deployed as appropriate. The Redfish API does not specify when the firmware will actually be written to the SPI device. ## Vendor ID Security No vendor ID is set as there is no vendor field in the schema. ## Quirk Use This plugin uses the following plugin-specific quirks: ### RedfishResetPreDelay Delay in ms to use before querying the manager after a cleanup reset, default 0ms. Since: 1.8.0 ### RedfishResetPostDelay Delay in ms to use before querying /redfish/v1/UpdateService after a cleanup reset, default 0ms. Since: 1.8.0 ### Flags=no-manager-reset-request The BMC device will auto-reboot and so fwupd should not explicitly call `/redfish/v1/Managers/1/Actions/Manager.Reset`. Since: 1.9.11 ## Setting Service IP Manually The service IP may not be automatically discoverable due to the absence of Type 42 entry in SMBIOS. In this case, you have to specify the service IP to RedfishUri in /etc/fwupd/redfish.conf Take HPE Gen10 for example, the service IP can be found with the following command: ```shell ilorest --nologo list --selector=EthernetInterface. -j ``` This command lists all network interfaces, and the Redfish service IP belongs to one of "Manager Network" Interfaces. For example: ```json { "@odata.context": "/redfish/v1/$metadata#EthernetInterface.EthernetInterface", "@odata.id": "/redfish/v1/Managers/1/EthernetInterfaces/1/", "@odata.type": "#EthernetInterface.v1_0_3.EthernetInterface", "Description": "Configuration of this Manager Network Interface", "HostName": "myredfish", "IPv4Addresses": [ { "SubnetMask": "255.255.255.0", "AddressOrigin": "DHCP", "Gateway": "192.168.0.1", "Address": "192.168.0.133" } ], ... ``` In this example, the service IP is "192.168.0.133". Since the conventional HTTP port is 80 and HTTPS port is 443, we can set RedfishUri to either "http://192.168.0.133:80" or "https://192.168.0.133:443" and verify the uri with ```shell curl http://192.168.0.133:80/redfish/v1/ ``` or ```shell curl -k https://192.168.0.133:443/redfish/v1/ ``` ## External Interface Access This requires HTTP access to a given URL. ## Version Considerations This plugin has been available since fwupd version `1.1.0`. fwupd-1.9.16/plugins/redfish/fu-ipmi-device.c000066400000000000000000000451141460375044200210230ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "fu-ipmi-device.h" #define FU_IPMI_DEVICE_TIMEOUT 1500 /* ms */ #define FU_IPMI_TRANSACTION_RETRY_COUNT 5 #define FU_IPMI_TRANSACTION_RETRY_DELAY 200 /* ms */ /* not defined in linux/ipmi_msgdefs.h */ #define IPMI_SET_USER_ACCESS 0x43 #define IPMI_SET_USER_NAME 0x45 #define IPMI_GET_USER_NAME 0x46 #define IPMI_SET_USER_PASSWORD 0x47 #define IPMI_PASSWORD_DISABLE_USER 0x00 #define IPMI_PASSWORD_ENABLE_USER 0x01 #define IPMI_PASSWORD_SET_PASSWORD 0x02 #define IPMI_PASSWORD_TEST_PASSWORD 0x03 /* these are not provided in ipmi_msgdefs.h */ #define IPMI_INVALID_COMMAND_ON_LUN_ERR 0xC2 #define IPMI_OUT_OF_SPACE_ERR 0xC4 #define IPMI_CANCELLED_OR_INVALID_ERR 0xC5 #define IPMI_OUT_OF_RANGE_ERR 0xC9 #define IPMI_CANNOT_RETURN_DATA_ERR 0xCA #define IPMI_NOT_FOUND_ERR 0xCB #define IPMI_INVALID_DATA_FIELD_ERR 0xCC #define IPMI_COMMAND_ILLEGAL_ERR 0xCD #define IPMI_RESPONSE_NOT_PROVIDED_ERR 0xCE #define IPMI_DUPLICATED_REQUEST_ERR 0xCF #define IPMI_SDR_IN_UPDATE_MODE_ERR 0xD0 #define IPMI_DESTINATION_UNAVAILABLE_ERR 0xD3 #define IPMI_INSUFFICIENT_PRIVILEGE_ERR 0xD4 #define IPMI_COMMAND_DISABLED_ERR 0xD6 #ifndef IPMI_DEVICE_IN_UPDATE_MODE_ERR #define IPMI_DEVICE_IN_UPDATE_MODE_ERR 0xD1 #endif #ifndef IPMI_DEVICE_IN_INIT_ERR #define IPMI_DEVICE_IN_INIT_ERR 0xD2 #endif struct _FuIpmiDevice { FuUdevDevice parent_instance; glong seq; guint8 device_id; guint8 device_rev; guint8 version_ipmi; }; G_DEFINE_TYPE(FuIpmiDevice, fu_ipmi_device, FU_TYPE_UDEV_DEVICE) #define FU_IPMI_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ static void fu_ipmi_device_to_string(FuDevice *device, guint idt, GString *str) { FuIpmiDevice *self = FU_IPMI_DEVICE(device); fu_string_append_kx(str, idt, "DeviceId", self->device_id); fu_string_append_kx(str, idt, "DeviceRev", self->device_rev); fu_string_append_kx(str, idt, "VersionIpmi", self->version_ipmi); } static gboolean fu_ipmi_device_send(FuIpmiDevice *self, guint8 netfn, guint8 cmd, const guint8 *buf, gsize bufsz, GError **error) { struct ipmi_system_interface_addr addr = {.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE, .channel = IPMI_BMC_CHANNEL}; struct ipmi_req req = { .addr = (guint8 *)&addr, .addr_len = sizeof(addr), .msgid = self->seq++, .msg.data_len = (guint16)bufsz, .msg.netfn = netfn, .msg.cmd = cmd, }; g_autofree guint8 *buf2 = NULL; if (buf != NULL) { buf2 = fu_memdup_safe(buf, bufsz, error); if (buf2 == NULL) return FALSE; req.msg.data = buf2; } fu_dump_raw(G_LOG_DOMAIN, "ipmi-send", buf2, bufsz); return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), IPMICTL_SEND_COMMAND, (guint8 *)&req, NULL, FU_IPMI_DEVICE_IOCTL_TIMEOUT, error); } static gboolean fu_ipmi_device_recv(FuIpmiDevice *self, guint8 *netfn, guint8 *cmd, glong *seq, guint8 *buf, gsize bufsz, gsize *len, /* optional, out */ GError **error) { struct ipmi_addr addr = {0}; struct ipmi_recv recv = { .addr = (guint8 *)&addr, .addr_len = sizeof(addr), .msg.data = buf, .msg.data_len = bufsz, }; if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), IPMICTL_RECEIVE_MSG_TRUNC, (guint8 *)&recv, NULL, FU_IPMI_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "ipmi-recv", buf, bufsz); if (netfn != NULL) *netfn = recv.msg.netfn; if (cmd != NULL) *cmd = recv.msg.cmd; if (seq != NULL) *seq = recv.msgid; if (len != NULL) *len = (gsize)recv.msg.data_len; return TRUE; } static gboolean fu_ipmi_device_lock(GObject *device, GError **error) { FuIpmiDevice *self = FU_IPMI_DEVICE(device); FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); struct flock lock = {.l_type = F_WRLCK, .l_whence = SEEK_SET}; if (fcntl(fu_io_channel_unix_get_fd(io_channel), F_SETLKW, &lock) == -1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "error locking IPMI device: %m"); return FALSE; } return TRUE; } static gboolean fu_ipmi_device_unlock(GObject *device, GError **error) { FuIpmiDevice *self = FU_IPMI_DEVICE(device); FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); struct flock lock = {.l_type = F_UNLCK}; if (fcntl(fu_io_channel_unix_get_fd(io_channel), F_SETLKW, &lock) == -1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "error unlocking IPMI device: %m"); return FALSE; } return TRUE; } static const gchar * fu_ipmi_device_errcode_to_string(guint8 errcode) { if (errcode == IPMI_CC_NO_ERROR) return "no-error"; if (errcode == IPMI_NODE_BUSY_ERR) return "node-busy"; if (errcode == IPMI_INVALID_COMMAND_ERR) return "invalid-command"; if (errcode == IPMI_TIMEOUT_ERR) return "timeout"; if (errcode == IPMI_ERR_MSG_TRUNCATED) return "msg-truncated"; if (errcode == IPMI_REQ_LEN_INVALID_ERR) return "req-len-invalid"; if (errcode == IPMI_REQ_LEN_EXCEEDED_ERR) return "req-len-exceeded"; if (errcode == IPMI_DEVICE_IN_UPDATE_MODE_ERR) return "device-in-update-mode"; if (errcode == IPMI_DEVICE_IN_INIT_ERR) return "device-in-init"; if (errcode == IPMI_NOT_IN_MY_STATE_ERR) return "not-in-my-state"; if (errcode == IPMI_LOST_ARBITRATION_ERR) return "lost-arbitration"; if (errcode == IPMI_BUS_ERR) return "bus-error"; if (errcode == IPMI_NAK_ON_WRITE_ERR) return "nak-on-write"; if (errcode == IPMI_ERR_UNSPECIFIED) return "unspecified"; /* these are not defined in ipmi_msgdefs.h but used in reality */ if (errcode == IPMI_INVALID_COMMAND_ON_LUN_ERR) return "invalid-command-on-lun"; if (errcode == IPMI_OUT_OF_SPACE_ERR) return "out-of-space"; if (errcode == IPMI_CANCELLED_OR_INVALID_ERR) return "cancelled-or-invalid"; if (errcode == IPMI_OUT_OF_RANGE_ERR) return "out-of-range"; if (errcode == IPMI_CANNOT_RETURN_DATA_ERR) return "cannot-return-data"; if (errcode == IPMI_NOT_FOUND_ERR) return "not-found"; if (errcode == IPMI_INVALID_DATA_FIELD_ERR) return "invalid-data-field"; if (errcode == IPMI_COMMAND_ILLEGAL_ERR) return "command-illegal"; if (errcode == IPMI_RESPONSE_NOT_PROVIDED_ERR) return "response-not-provided"; if (errcode == IPMI_DUPLICATED_REQUEST_ERR) return "duplicated-request"; if (errcode == IPMI_SDR_IN_UPDATE_MODE_ERR) return "sdr-in-update-mode"; if (errcode == IPMI_DESTINATION_UNAVAILABLE_ERR) return "destination-unavailable"; if (errcode == IPMI_INSUFFICIENT_PRIVILEGE_ERR) return "insufficient-privilege"; if (errcode == IPMI_COMMAND_DISABLED_ERR) return "command-disabled"; return "unknown"; } static gboolean fu_ipmi_device_errcode_to_error(guint8 errcode, GError **error) { /* success */ if (errcode == IPMI_CC_NO_ERROR) return TRUE; /* data not found, seemingly Lenovo specific */ if (errcode == IPMI_INVALID_DATA_FIELD_ERR || errcode == IPMI_NOT_FOUND_ERR) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "CC error: %s [0x%02X]", fu_ipmi_device_errcode_to_string(errcode), errcode); return FALSE; } /* fallback */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "CC error: %s [0x%02X]", fu_ipmi_device_errcode_to_string(errcode), errcode); return FALSE; } typedef struct { guint8 netfn; guint8 cmd; const guint8 *req_buf; gsize req_bufsz; guint8 *resp_buf; gsize resp_bufsz; gsize *resp_len; gint timeout_ms; } FuIpmiDeviceTransactionHelper; static gboolean fu_ipmi_device_transaction_cb(FuDevice *device, gpointer user_data, GError **error) { FuIpmiDevice *self = FU_IPMI_DEVICE(device); FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); FuIpmiDeviceTransactionHelper *helper = (FuIpmiDeviceTransactionHelper *)user_data; GPollFD pollfds[1]; gsize resp_buf2sz = helper->resp_bufsz + 1; gsize resp_len2 = 0; g_autoptr(GTimer) timer = g_timer_new(); g_autoptr(FuDeviceLocker) lock = NULL; g_autofree guint8 *resp_buf2 = g_malloc0(resp_buf2sz); lock = fu_device_locker_new_full(self, fu_ipmi_device_lock, fu_ipmi_device_unlock, error); if (lock == NULL) return FALSE; if (!fu_ipmi_device_send(self, helper->netfn, helper->cmd, helper->req_buf, helper->req_bufsz, error)) return FALSE; pollfds[0].fd = fu_io_channel_unix_get_fd(io_channel); pollfds[0].events = POLLIN; for (;;) { guint8 resp_netfn = 0; guint8 resp_cmd = 0; glong seq = 0; gint rc; rc = g_poll(pollfds, 1, helper->timeout_ms - (g_timer_elapsed(timer, NULL) * 1000.f)); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "poll() error %m"); return FALSE; } if (rc == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "timeout waiting for response " "(netfn %d, cmd %d)", helper->netfn, helper->cmd); return FALSE; } if (!(pollfds[0].revents & POLLIN)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "unexpected status"); return FALSE; } if (!fu_ipmi_device_recv(self, &resp_netfn, &resp_cmd, &seq, resp_buf2, resp_buf2sz, &resp_len2, error)) return FALSE; if (seq != self->seq - 1) { g_debug("out-of-sequence reply: " "expected %ld, got %ld", self->seq, seq); if (g_timer_elapsed(timer, NULL) * 1000.f >= helper->timeout_ms) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "timed out"); return FALSE; } } else { if (!fu_ipmi_device_errcode_to_error(resp_buf2[0], error)) return FALSE; if (helper->resp_buf != NULL) { if (!fu_memcpy_safe(helper->resp_buf, helper->resp_bufsz, 0x0, /* dst */ resp_buf2, resp_buf2sz, 0x01, /* src */ helper->resp_bufsz, error)) return FALSE; } if (helper->resp_len != NULL) *helper->resp_len = resp_len2 - 1; g_debug("IPMI netfn: %02x->%02x, cmd: %02x->%02x", helper->netfn, resp_netfn, helper->cmd, resp_cmd); break; } } return TRUE; } static gboolean fu_ipmi_device_transaction(FuIpmiDevice *self, guint8 netfn, guint8 cmd, const guint8 *req_buf, gsize req_bufsz, guint8 *resp_buf, /* optional */ gsize resp_bufsz, gsize *resp_len, /* optional, out */ gint timeout_ms, GError **error) { FuIpmiDeviceTransactionHelper helper = { .netfn = netfn, .cmd = cmd, .req_buf = req_buf, .req_bufsz = req_bufsz, .resp_buf = resp_buf, .resp_bufsz = resp_bufsz, .resp_len = resp_len, .timeout_ms = timeout_ms, }; fu_device_retry_add_recovery(FU_DEVICE(self), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, NULL); return fu_device_retry_full(FU_DEVICE(self), fu_ipmi_device_transaction_cb, FU_IPMI_TRANSACTION_RETRY_COUNT, FU_IPMI_TRANSACTION_RETRY_DELAY, &helper, error); } static gboolean fu_ipmi_device_probe(FuDevice *device, GError **error) { FuIpmiDevice *self = FU_IPMI_DEVICE(device); const gchar *physical_ids[] = {"/dev/ipmi0", "/dev/ipmi/0", "/dev/ipmidev/0", NULL}; /* look for the IPMI device */ for (guint i = 0; physical_ids[i] != NULL; i++) { if (g_file_test(physical_ids[i], G_FILE_TEST_EXISTS)) { fu_device_set_physical_id(FU_DEVICE(self), physical_ids[i]); return TRUE; } } /* cannot continue */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no BMC device found"); return FALSE; } static gboolean fu_ipmi_device_setup(FuDevice *device, GError **error) { FuIpmiDevice *self = FU_IPMI_DEVICE(device); gsize resp_len = 0; guint8 resp[16] = {0}; /* get IPMI versions */ if (!fu_ipmi_device_transaction(self, IPMI_NETFN_APP_REQUEST, IPMI_GET_DEVICE_ID_CMD, NULL, 0, resp, sizeof(resp), &resp_len, FU_IPMI_DEVICE_TIMEOUT, error)) return FALSE; if (resp_len == 11 || resp_len == 15) { guint8 bcd; g_autoptr(GString) str = g_string_new(NULL); self->device_id = resp[0]; self->device_rev = resp[1]; bcd = resp[3] & 0x0f; bcd += 10 * (resp[4] >> 3); /* rev1.rev2.aux_revision */ g_string_append_printf(str, "%u.%02u", resp[2], bcd); if (resp_len == 15) { g_string_append_printf(str, ".%02x%02x%02x%02x", resp[11], resp[12], resp[13], resp[14]); } fu_device_set_version(device, str->str); bcd = resp[4] & 0x0f; bcd += 10 * (resp[4] >> 4); self->version_ipmi = bcd; } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to parse DEVICE_ID_CMD response (sz: %" G_GSIZE_FORMAT ")", resp_len); return FALSE; } /* success */ return TRUE; } gchar * fu_ipmi_device_get_user_password(FuIpmiDevice *self, guint8 user_id, GError **error) { const guint8 req[1] = {user_id}; guint8 resp[0x10] = {0}; gsize resp_len = 0; g_return_val_if_fail(FU_IS_IPMI_DEVICE(self), NULL); g_return_val_if_fail(user_id != 0x0, NULL); /* run transaction */ if (!fu_ipmi_device_transaction(self, IPMI_NETFN_APP_REQUEST, IPMI_GET_USER_NAME, req, sizeof(req), resp, sizeof(resp), &resp_len, FU_IPMI_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to get username: "); return NULL; } if (resp_len != sizeof(resp)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to retrieve username from IPMI, got 0x%x bytes", (guint)resp_len); return NULL; } /* success */ return fu_memstrsafe(resp, sizeof(resp), 0x0, resp_len, error); } gboolean fu_ipmi_device_set_user_name(FuIpmiDevice *self, guint8 user_id, const gchar *username, GError **error) { guint8 req[0x11] = {user_id}; gsize username_sz; g_return_val_if_fail(FU_IS_IPMI_DEVICE(self), FALSE); g_return_val_if_fail(user_id != 0x0, FALSE); g_return_val_if_fail(username != NULL, FALSE); /* copy into buffer */ username_sz = strlen(username); if (!fu_memcpy_safe(req, sizeof(req), 0x1, /* dst */ (guint8 *)username, username_sz, 0x0, /* src */ username_sz, error)) { g_prefix_error(error, "username invalid: "); return FALSE; } /* run transaction */ if (!fu_ipmi_device_transaction(self, IPMI_NETFN_APP_REQUEST, IPMI_SET_USER_NAME, req, sizeof(req), NULL, /* resp */ 0, NULL, FU_IPMI_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to set user %02x name: ", user_id); return FALSE; } /* success */ return TRUE; } gboolean fu_ipmi_device_set_user_enable(FuIpmiDevice *self, guint8 user_id, gboolean value, GError **error) { guint8 op = value ? IPMI_PASSWORD_ENABLE_USER : IPMI_PASSWORD_DISABLE_USER; const guint8 req[] = {user_id, op}; g_return_val_if_fail(FU_IS_IPMI_DEVICE(self), FALSE); g_return_val_if_fail(user_id != 0x0, FALSE); /* run transaction */ if (!fu_ipmi_device_transaction(self, IPMI_NETFN_APP_REQUEST, IPMI_SET_USER_PASSWORD, req, sizeof(req), NULL, /* resp */ 0, NULL, FU_IPMI_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to set user %02x enable: ", user_id); return FALSE; } /* success */ return TRUE; } gboolean fu_ipmi_device_set_user_password(FuIpmiDevice *self, guint8 user_id, const gchar *password, GError **error) { guint8 req[0x12] = {user_id, IPMI_PASSWORD_SET_PASSWORD}; gsize password_sz; g_return_val_if_fail(FU_IS_IPMI_DEVICE(self), FALSE); g_return_val_if_fail(user_id != 0x0, FALSE); g_return_val_if_fail(password != NULL, FALSE); /* copy into buffer */ password_sz = strlen(password); if (!fu_memcpy_safe(req, sizeof(req), 0x2, /* dst */ (guint8 *)password, password_sz, 0x0, /* src */ password_sz, error)) { g_prefix_error(error, "password invalid: "); return FALSE; } /* run transaction */ if (!fu_ipmi_device_transaction(self, IPMI_NETFN_APP_REQUEST, IPMI_SET_USER_PASSWORD, req, sizeof(req), NULL, /* resp */ 0, NULL, FU_IPMI_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to set user %02x password: ", user_id); return FALSE; } /* success */ return TRUE; } gboolean fu_ipmi_device_set_user_priv(FuIpmiDevice *self, guint8 user_id, guint8 priv_limit, guint8 channel, GError **error) { const guint8 req[] = {channel, user_id, priv_limit, 0x0}; g_return_val_if_fail(FU_IS_IPMI_DEVICE(self), FALSE); g_return_val_if_fail(user_id != 0x0, FALSE); g_return_val_if_fail(channel <= 0x0F, FALSE); g_return_val_if_fail(priv_limit <= 0x0F, FALSE); /* run transaction */ if (!fu_ipmi_device_transaction(self, IPMI_NETFN_APP_REQUEST, IPMI_SET_USER_ACCESS, req, sizeof(req), NULL, /* resp */ 0, NULL, FU_IPMI_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to set user %02x privs of 0x%02x, 0x%02x: ", user_id, priv_limit, channel); return FALSE; } /* success */ return TRUE; } gboolean fu_redfish_device_set_user_group_redfish_enable_advantech(FuIpmiDevice *self, guint8 user_id, GError **error) { const guint8 req[] = {0x39, 0x28, 0x0, user_id, 0x3, 0x1}; guint8 resp[0x3] = {0}; gsize resp_len = 0; g_return_val_if_fail(FU_IS_IPMI_DEVICE(self), FALSE); g_return_val_if_fail(user_id != 0x0, FALSE); /* run transaction */ if (!fu_ipmi_device_transaction(self, 0x2e, 0x08, req, sizeof(req), resp, sizeof(resp), &resp_len, FU_IPMI_DEVICE_TIMEOUT, error)) { g_prefix_error(error, "failed to set user %02x redfish group enable: ", user_id); return FALSE; } /* success */ return TRUE; } static void fu_ipmi_device_init(FuIpmiDevice *self) { fu_device_set_name(FU_DEVICE(self), "IPMI"); fu_device_set_summary(FU_DEVICE(self), "Intelligent Platform Management Interface"); fu_device_add_icon(FU_DEVICE(self), "computer"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); } static void fu_ipmi_device_class_init(FuIpmiDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_ipmi_device_probe; klass_device->setup = fu_ipmi_device_setup; klass_device->to_string = fu_ipmi_device_to_string; } FuIpmiDevice * fu_ipmi_device_new(FuContext *ctx) { FuIpmiDevice *self; self = g_object_new(FU_TYPE_IPMI_DEVICE, "context", ctx, "device-file", "/dev/ipmi0", NULL); return self; } fwupd-1.9.16/plugins/redfish/fu-ipmi-device.h000066400000000000000000000021241460375044200210220ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_IPMI_DEVICE (fu_ipmi_device_get_type()) G_DECLARE_FINAL_TYPE(FuIpmiDevice, fu_ipmi_device, FU, IPMI_DEVICE, FuUdevDevice) FuIpmiDevice * fu_ipmi_device_new(FuContext *ctx); gchar * fu_ipmi_device_get_user_password(FuIpmiDevice *self, guint8 user_id, GError **error); gboolean fu_ipmi_device_set_user_name(FuIpmiDevice *self, guint8 user_id, const gchar *username, GError **error); gboolean fu_ipmi_device_set_user_password(FuIpmiDevice *self, guint8 user_id, const gchar *password, GError **error); gboolean fu_ipmi_device_set_user_enable(FuIpmiDevice *self, guint8 user_id, gboolean value, GError **error); gboolean fu_ipmi_device_set_user_priv(FuIpmiDevice *self, guint8 user_id, guint8 priv_limit, guint8 channel, GError **error); gboolean fu_redfish_device_set_user_group_redfish_enable_advantech(FuIpmiDevice *self, guint8 user_id, GError **error); fwupd-1.9.16/plugins/redfish/fu-redfish-backend.c000066400000000000000000000402131460375044200216340ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-redfish-backend.h" #include "fu-redfish-common.h" #include "fu-redfish-legacy-device.h" #include "fu-redfish-multipart-device.h" #include "fu-redfish-request.h" #include "fu-redfish-smbios.h" #include "fu-redfish-smc-device.h" struct _FuRedfishBackend { FuBackend parent_instance; gchar *hostname; gchar *username; gchar *password; guint port; gchar *vendor; gchar *version; gchar *uuid; gchar *update_uri_path; gchar *push_uri_path; gboolean use_https; gboolean cacheck; gboolean wildcard_targets; gint64 max_image_size; /* bytes */ GType device_gtype; GHashTable *request_cache; /* str:GByteArray */ CURLSH *curlsh; }; G_DEFINE_TYPE(FuRedfishBackend, fu_redfish_backend, FU_TYPE_BACKEND) const gchar * fu_redfish_backend_get_vendor(FuRedfishBackend *self) { return self->vendor; } const gchar * fu_redfish_backend_get_version(FuRedfishBackend *self) { return self->version; } const gchar * fu_redfish_backend_get_uuid(FuRedfishBackend *self) { return self->uuid; } FuRedfishRequest * fu_redfish_backend_request_new(FuRedfishBackend *self) { FuRedfishRequest *request = g_object_new(FU_TYPE_REDFISH_REQUEST, NULL); CURL *curl; CURLU *uri; g_autofree gchar *user_agent = NULL; g_autofree gchar *port = g_strdup_printf("%u", self->port); /* set the cache location */ fu_redfish_request_set_cache(request, self->request_cache); fu_redfish_request_set_curlsh(request, self->curlsh); /* set up defaults */ curl = fu_redfish_request_get_curl(request); uri = fu_redfish_request_get_uri(request); (void)curl_url_set(uri, CURLUPART_SCHEME, self->use_https ? "https" : "http", 0); (void)curl_url_set(uri, CURLUPART_HOST, self->hostname, 0); (void)curl_url_set(uri, CURLUPART_PORT, port, 0); (void)curl_easy_setopt(curl, CURLOPT_CURLU, uri); /* since DSP0266 makes Basic Authorization a requirement, * it is safe to use Basic Auth for all implementations */ (void)curl_easy_setopt(curl, CURLOPT_HTTPAUTH, (glong)CURLAUTH_BASIC); (void)curl_easy_setopt(curl, CURLOPT_TIMEOUT, (glong)180); (void)curl_easy_setopt(curl, CURLOPT_USERNAME, self->username); (void)curl_easy_setopt(curl, CURLOPT_PASSWORD, self->password); /* setup networking */ user_agent = g_strdup_printf("%s/%s", PACKAGE_NAME, PACKAGE_VERSION); (void)curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent); (void)curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 60L); if (!self->cacheck) { (void)curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); (void)curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); } /* success */ return request; } static gboolean fu_redfish_backend_coldplug_member(FuRedfishBackend *self, JsonObject *member, GError **error) { g_autoptr(FuDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; /* create of the correct type */ dev = g_object_new(self->device_gtype, "context", fu_backend_get_context(FU_BACKEND(self)), "backend", self, "member", member, NULL); /* some vendors do not specify the Targets array when updating */ if (self->wildcard_targets) fu_device_add_private_flag(dev, FU_REDFISH_DEVICE_FLAG_WILDCARD_TARGETS); /* probe + setup */ locker = fu_device_locker_new(dev, &error_local); if (locker == NULL) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_debug("failed to setup: %s", error_local->message); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } if (self->max_image_size != 0) fu_device_set_firmware_size_max(dev, (guint64)self->max_image_size); fu_backend_device_added(FU_BACKEND(self), dev); return TRUE; } static gboolean fu_redfish_backend_coldplug_collection(FuRedfishBackend *self, JsonObject *collection, GError **error) { JsonArray *members = json_object_get_array_member(collection, "Members"); for (guint i = 0; i < json_array_get_length(members); i++) { JsonObject *json_obj; JsonObject *member_id; const gchar *member_uri; g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(self); member_id = json_array_get_object_element(members, i); member_uri = json_object_get_string_member(member_id, "@odata.id"); if (member_uri == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no @odata.id string"); return FALSE; } /* create the device for the member */ if (!fu_redfish_request_perform(request, member_uri, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, error)) return FALSE; json_obj = fu_redfish_request_get_json_object(request); if (!fu_redfish_backend_coldplug_member(self, json_obj, error)) return FALSE; } return TRUE; } static gboolean fu_redfish_backend_coldplug_inventory(FuRedfishBackend *self, JsonObject *inventory, GError **error) { JsonObject *json_obj; const gchar *collection_uri; g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(self); if (inventory == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no inventory object"); return FALSE; } collection_uri = json_object_get_string_member(inventory, "@odata.id"); if (collection_uri == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no @odata.id string"); return FALSE; } if (!fu_redfish_request_perform(request, collection_uri, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, error)) return FALSE; json_obj = fu_redfish_request_get_json_object(request); return fu_redfish_backend_coldplug_collection(self, json_obj, error); } static void fu_redfish_backend_check_wildcard_targets(FuRedfishBackend *self) { g_autoptr(GPtrArray) devices = fu_backend_get_devices(FU_BACKEND(self)); g_autoptr(GHashTable) device_by_id0 = g_hash_table_new(g_str_hash, g_str_equal); /* does the SoftwareId exist from a different device */ for (guint i = 0; i < devices->len; i++) { FuDevice *device_old; FuDevice *device_tmp = g_ptr_array_index(devices, i); GPtrArray *ids = fu_device_get_instance_ids(device_tmp); const gchar *id0 = g_ptr_array_index(ids, 0); device_old = g_hash_table_lookup(device_by_id0, id0); if (device_old == NULL) { g_hash_table_insert(device_by_id0, (gpointer)device_tmp, (gpointer)id0); continue; } fu_device_add_flag(device_tmp, FWUPD_DEVICE_FLAG_WILDCARD_INSTALL); fu_device_add_flag(device_old, FWUPD_DEVICE_FLAG_WILDCARD_INSTALL); } } static void fu_redfish_backend_set_push_uri_path(FuRedfishBackend *self, const gchar *push_uri_path) { g_free(self->push_uri_path); self->push_uri_path = g_strdup(push_uri_path); } static gboolean fu_redfish_backend_has_smc_update_path(JsonObject *update_svc) { JsonObject *tmp_obj; const gchar *tmp_str; if (!json_object_has_member(update_svc, "Actions")) return FALSE; tmp_obj = json_object_get_object_member(update_svc, "Actions"); if (tmp_obj == NULL || !json_object_has_member(tmp_obj, "#UpdateService.StartUpdate")) return FALSE; tmp_obj = json_object_get_object_member(tmp_obj, "#UpdateService.StartUpdate"); if (tmp_obj == NULL || !json_object_has_member(tmp_obj, "target")) return FALSE; tmp_str = json_object_get_string_member(tmp_obj, "target"); return g_str_equal(tmp_str, "/redfish/v1/UpdateService/Actions/UpdateService.StartUpdate"); } static gboolean fu_redfish_backend_coldplug(FuBackend *backend, FuProgress *progress, GError **error) { FuRedfishBackend *self = FU_REDFISH_BACKEND(backend); JsonObject *json_obj; g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(self); /* nothing set */ if (self->update_uri_path == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no update_uri_path"); return FALSE; } /* get the update service */ if (!fu_redfish_request_perform(request, self->update_uri_path, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, error)) return FALSE; json_obj = fu_redfish_request_get_json_object(request); if (!json_object_has_member(json_obj, "ServiceEnabled")) { if (!json_object_get_boolean_member(json_obj, "ServiceEnabled")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "service is not enabled"); return FALSE; } } if (self->push_uri_path == NULL && json_object_has_member(json_obj, "MultipartHttpPushUri")) { const gchar *tmp = json_object_get_string_member(json_obj, "MultipartHttpPushUri"); if (tmp != NULL) { if (fu_redfish_backend_has_smc_update_path(json_obj)) { self->device_gtype = FU_TYPE_REDFISH_SMC_DEVICE; } else { self->device_gtype = FU_TYPE_REDFISH_MULTIPART_DEVICE; } fu_redfish_backend_set_push_uri_path(self, tmp); } } if (self->push_uri_path == NULL && json_object_has_member(json_obj, "HttpPushUri")) { const gchar *tmp = json_object_get_string_member(json_obj, "HttpPushUri"); if (tmp != NULL) { self->device_gtype = FU_TYPE_REDFISH_LEGACY_DEVICE; fu_redfish_backend_set_push_uri_path(self, tmp); } } if (self->push_uri_path == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "HttpPushUri and MultipartHttpPushUri are invalid"); return FALSE; } if (json_object_has_member(json_obj, "MaxImageSizeBytes")) { self->max_image_size = json_object_get_int_member(json_obj, "MaxImageSizeBytes"); } if (json_object_has_member(json_obj, "FirmwareInventory")) { JsonObject *tmp = json_object_get_object_member(json_obj, "FirmwareInventory"); return fu_redfish_backend_coldplug_inventory(self, tmp, error); } if (json_object_has_member(json_obj, "SoftwareInventory")) { JsonObject *tmp = json_object_get_object_member(json_obj, "SoftwareInventory"); return fu_redfish_backend_coldplug_inventory(self, tmp, error); } /* work out if we have multiple devices with the same SoftwareId */ if (self->wildcard_targets) fu_redfish_backend_check_wildcard_targets(self); /* success */ return TRUE; } static void fu_redfish_backend_set_update_uri_path(FuRedfishBackend *self, const gchar *update_uri_path) { /* not changed */ if (g_strcmp0(self->update_uri_path, update_uri_path) == 0) return; g_free(self->update_uri_path); self->update_uri_path = g_strdup(update_uri_path); } static gboolean fu_redfish_backend_setup(FuBackend *backend, FuProgress *progress, GError **error) { FuRedfishBackend *self = FU_REDFISH_BACKEND(backend); JsonObject *json_obj; JsonObject *json_update_service = NULL; const gchar *data_id; const gchar *version = NULL; g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(self); /* sanity check */ if (self->port == 0 || self->port > G_MAXUINT16) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid port specified: 0x%x", self->port); return FALSE; } /* try to connect */ if (!fu_redfish_request_perform(request, "/redfish/v1/", FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, error)) return FALSE; json_obj = fu_redfish_request_get_json_object(request); if (json_object_has_member(json_obj, "ServiceVersion")) { version = json_object_get_string_member(json_obj, "ServiceVersion"); } else if (json_object_has_member(json_obj, "RedfishVersion")) { version = json_object_get_string_member(json_obj, "RedfishVersion"); } if (version != NULL) { g_free(self->version); self->version = g_strdup(version); } if (json_object_has_member(json_obj, "UUID")) { g_free(self->uuid); self->uuid = g_strdup(json_object_get_string_member(json_obj, "UUID")); } if (json_object_has_member(json_obj, "Vendor")) { g_free(self->vendor); self->vendor = g_strdup(json_object_get_string_member(json_obj, "Vendor")); } if (json_object_has_member(json_obj, "UpdateService")) json_update_service = json_object_get_object_member(json_obj, "UpdateService"); if (json_update_service == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no UpdateService object"); return FALSE; } data_id = json_object_get_string_member(json_update_service, "@odata.id"); if (data_id == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no @odata.id string"); return FALSE; } fu_redfish_backend_set_update_uri_path(self, data_id); return TRUE; } static void fu_redfish_backend_invalidate(FuBackend *backend) { FuRedfishBackend *self = FU_REDFISH_BACKEND(backend); g_hash_table_remove_all(self->request_cache); } void fu_redfish_backend_set_hostname(FuRedfishBackend *self, const gchar *hostname) { g_free(self->hostname); self->hostname = g_strdup(hostname); } void fu_redfish_backend_set_port(FuRedfishBackend *self, guint port) { self->port = port; } void fu_redfish_backend_set_https(FuRedfishBackend *self, gboolean use_https) { self->use_https = use_https; } void fu_redfish_backend_set_cacheck(FuRedfishBackend *self, gboolean cacheck) { self->cacheck = cacheck; } void fu_redfish_backend_set_wildcard_targets(FuRedfishBackend *self, gboolean wildcard_targets) { self->wildcard_targets = wildcard_targets; } void fu_redfish_backend_set_username(FuRedfishBackend *self, const gchar *username) { g_free(self->username); self->username = g_strdup(username); } const gchar * fu_redfish_backend_get_username(FuRedfishBackend *self) { return self->username; } void fu_redfish_backend_set_password(FuRedfishBackend *self, const gchar *password) { g_free(self->password); self->password = g_strdup(password); } const gchar * fu_redfish_backend_get_push_uri_path(FuRedfishBackend *self) { return self->push_uri_path; } static void fu_redfish_backend_to_string(FuBackend *backend, guint idt, GString *str) { FuRedfishBackend *self = FU_REDFISH_BACKEND(backend); fu_string_append(str, idt, "Hostname", self->hostname); fu_string_append(str, idt, "Username", self->username); fu_string_append_kb(str, idt, "Password", self->password != NULL); fu_string_append_ku(str, idt, "Port", self->port); fu_string_append(str, idt, "UpdateUriPath", self->update_uri_path); fu_string_append(str, idt, "PushUriPath", self->push_uri_path); fu_string_append_kb(str, idt, "UseHttps", self->use_https); fu_string_append_kb(str, idt, "Cacheck", self->cacheck); fu_string_append_kb(str, idt, "WildcardTargets", self->wildcard_targets); fu_string_append_kx(str, idt, "MaxImageSize", self->max_image_size); fu_string_append(str, idt, "DeviceGType", g_type_name(self->device_gtype)); } static void fu_redfish_backend_finalize(GObject *object) { FuRedfishBackend *self = FU_REDFISH_BACKEND(object); g_hash_table_unref(self->request_cache); curl_share_cleanup(self->curlsh); g_free(self->update_uri_path); g_free(self->push_uri_path); g_free(self->hostname); g_free(self->username); g_free(self->password); g_free(self->vendor); g_free(self->version); g_free(self->uuid); G_OBJECT_CLASS(fu_redfish_backend_parent_class)->finalize(object); } static void fu_redfish_backend_class_init(FuRedfishBackendClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuBackendClass *klass_backend = FU_BACKEND_CLASS(klass); klass_backend->coldplug = fu_redfish_backend_coldplug; klass_backend->setup = fu_redfish_backend_setup; klass_backend->invalidate = fu_redfish_backend_invalidate; klass_backend->to_string = fu_redfish_backend_to_string; object_class->finalize = fu_redfish_backend_finalize; } static void fu_redfish_backend_init(FuRedfishBackend *self) { self->use_https = TRUE; self->device_gtype = FU_TYPE_REDFISH_DEVICE; self->request_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_byte_array_unref); self->curlsh = curl_share_init(); curl_share_setopt(self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); curl_share_setopt(self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); curl_share_setopt(self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); curl_share_setopt(self->curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); } FuRedfishBackend * fu_redfish_backend_new(FuContext *ctx) { return FU_REDFISH_BACKEND(g_object_new(FU_REDFISH_TYPE_BACKEND, "name", "redfish", "can-invalidate", TRUE, "context", ctx, NULL)); } fwupd-1.9.16/plugins/redfish/fu-redfish-backend.h000066400000000000000000000025751460375044200216520ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-redfish-request.h" #define FU_REDFISH_TYPE_BACKEND (fu_redfish_backend_get_type()) G_DECLARE_FINAL_TYPE(FuRedfishBackend, fu_redfish_backend, FU, REDFISH_BACKEND, FuBackend) FuRedfishBackend * fu_redfish_backend_new(FuContext *ctx); const gchar * fu_redfish_backend_get_vendor(FuRedfishBackend *self); const gchar * fu_redfish_backend_get_version(FuRedfishBackend *self); const gchar * fu_redfish_backend_get_uuid(FuRedfishBackend *self); void fu_redfish_backend_set_hostname(FuRedfishBackend *self, const gchar *hostname); void fu_redfish_backend_set_username(FuRedfishBackend *self, const gchar *username); const gchar * fu_redfish_backend_get_username(FuRedfishBackend *self); void fu_redfish_backend_set_password(FuRedfishBackend *self, const gchar *password); void fu_redfish_backend_set_port(FuRedfishBackend *self, guint port); void fu_redfish_backend_set_https(FuRedfishBackend *self, gboolean use_https); void fu_redfish_backend_set_cacheck(FuRedfishBackend *self, gboolean cacheck); void fu_redfish_backend_set_wildcard_targets(FuRedfishBackend *self, gboolean wildcard_targets); const gchar * fu_redfish_backend_get_push_uri_path(FuRedfishBackend *self); FuRedfishRequest * fu_redfish_backend_request_new(FuRedfishBackend *self); fwupd-1.9.16/plugins/redfish/fu-redfish-common.c000066400000000000000000000057361460375044200215500ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-redfish-common.h" gchar * fu_redfish_common_buffer_to_ipv4(const guint8 *buffer) { GString *str = g_string_new(NULL); for (guint i = 0; i < 4; i++) { g_string_append_printf(str, "%u", buffer[i]); if (i != 3) g_string_append(str, "."); } return g_string_free(str, FALSE); } gchar * fu_redfish_common_buffer_to_ipv6(const guint8 *buffer) { GString *str = g_string_new(NULL); for (guint i = 0; i < 16; i += 4) { g_string_append_printf(str, "%02x%02x%02x%02x", buffer[i + 0], buffer[i + 1], buffer[i + 2], buffer[i + 3]); if (i != 12) g_string_append(str, ":"); } return g_string_free(str, FALSE); } gchar * fu_redfish_common_buffer_to_mac(const guint8 *buffer) { GString *str = g_string_new(NULL); for (guint i = 0; i < 6; i++) { g_string_append_printf(str, "%02X", buffer[i]); if (i != 5) g_string_append(str, ":"); } return g_string_free(str, FALSE); } gchar * fu_redfish_common_fix_version(const gchar *version) { g_auto(GStrv) split = NULL; g_return_val_if_fail(version != NULL, NULL); /* not valid */ if (g_strcmp0(version, "-*") == 0) return NULL; /* find the section preficed with "v" */ split = g_strsplit(version, " ", -1); for (guint i = 0; split[i] != NULL; i++) { if (g_str_has_prefix(split[i], "v")) { g_debug("using %s for %s", split[i] + 1, version); return g_strdup(split[i] + 1); } } /* find the thing with dots */ for (guint i = 0; split[i] != NULL; i++) { if (g_strstr_len(split[i], -1, ".")) { if (g_strcmp0(split[i], version) != 0) g_debug("using %s for %s", split[i], version); return g_strdup(split[i]); } } /* we failed to do anything clever */ return g_strdup(version); } /* parses a Lenovo XCC-format version like "11A-1.02" */ gboolean fu_redfish_common_parse_version_lenovo(const gchar *version, gchar **out_build, /* out */ gchar **out_version, /* out */ GError **error) { g_auto(GStrv) versplit = g_strsplit(version, "-", -1); /* sanity check */ if (g_strv_length(versplit) != 2) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not two sections"); return FALSE; } if (strlen(versplit[0]) != 3) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid length first section"); return FALSE; } /* milestone */ if (!g_ascii_isdigit(versplit[0][0]) || !g_ascii_isdigit(versplit[0][1])) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "milestone number invalid"); return FALSE; } /* build is only one letter from A -> Z */ if (!g_ascii_isalpha(versplit[0][2])) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "build letter invalid"); return FALSE; } /* success */ if (out_build != NULL) *out_build = g_strdup(versplit[0]); if (out_version != NULL) *out_version = g_strdup(versplit[1]); return TRUE; } fwupd-1.9.16/plugins/redfish/fu-redfish-common.h000066400000000000000000000021041460375044200215370ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /* SMBIOS */ #define REDFISH_SMBIOS_TABLE_TYPE 0x2a /* 42 */ #define REDFISH_PROTOCOL_REDFISH_OVER_IP 0x04 /* EFI */ #define REDFISH_EFI_INFORMATION_GUID "16faa37e-4b6a-4891-9028-242de65a3b70" #define REDFISH_EFI_INFORMATION_INDICATIONS "RedfishIndications" #define REDFISH_EFI_INFORMATION_FW_CREDENTIALS "RedfishFWCredentials" #define REDFISH_EFI_INFORMATION_OS_CREDENTIALS "RedfishOSCredentials" #define REDFISH_EFI_INDICATIONS_FW_CREDENTIALS 0x00000001 #define REDFISH_EFI_INDICATIONS_OS_CREDENTIALS 0x00000002 /* shared */ gchar * fu_redfish_common_buffer_to_ipv4(const guint8 *buffer); gchar * fu_redfish_common_buffer_to_ipv6(const guint8 *buffer); gchar * fu_redfish_common_buffer_to_mac(const guint8 *buffer); gchar * fu_redfish_common_fix_version(const gchar *version); gboolean fu_redfish_common_parse_version_lenovo(const gchar *version, gchar **out_build, gchar **out_version, GError **error); fwupd-1.9.16/plugins/redfish/fu-redfish-device.c000066400000000000000000000735541460375044200215220ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-redfish-backend.h" #include "fu-redfish-common.h" #include "fu-redfish-device.h" typedef struct { FuRedfishBackend *backend; JsonObject *member; guint64 milestone; gchar *build; guint reset_pre_delay; /* default of 0ms */ guint reset_post_delay; /* default of 0ms */ } FuRedfishDevicePrivate; enum { PROP_0, PROP_BACKEND, PROP_MEMBER, PROP_LAST }; G_DEFINE_TYPE_WITH_PRIVATE(FuRedfishDevice, fu_redfish_device, FU_TYPE_DEVICE) #define GET_PRIVATE(o) (fu_redfish_device_get_instance_private(o)) static void fu_redfish_device_to_string(FuDevice *device, guint idt, GString *str) { FuRedfishDevice *self = FU_REDFISH_DEVICE(device); FuRedfishDevicePrivate *priv = GET_PRIVATE(self); if (priv->milestone > 0x0) fu_string_append_kx(str, idt, "Milestone", priv->milestone); if (priv->build != NULL) fu_string_append(str, idt, "Build", priv->build); fu_string_append_ku(str, idt, "ResetPretDelay", priv->reset_pre_delay); fu_string_append_ku(str, idt, "ResetPostDelay", priv->reset_post_delay); } static void fu_redfish_device_set_device_class(FuRedfishDevice *self, const gchar *tmp) { if (g_strcmp0(tmp, "NetworkController") == 0) { fu_device_add_icon(FU_DEVICE(self), "network-wired"); return; } if (g_strcmp0(tmp, "MassStorageController") == 0) { fu_device_add_icon(FU_DEVICE(self), "drive-multidisk"); return; } if (g_strcmp0(tmp, "DisplayController") == 0) { fu_device_add_icon(FU_DEVICE(self), "video-display"); return; } if (g_strcmp0(tmp, "DockingStation") == 0) { fu_device_add_icon(FU_DEVICE(self), "dock"); return; } if (g_strcmp0(tmp, "WirelessController") == 0) { fu_device_add_icon(FU_DEVICE(self), "network-wireless"); return; } g_debug("no icon mapping for %s", tmp); } static gboolean fu_redfish_device_probe_related_pcie_item(FuRedfishDevice *self, const gchar *uri, GError **error) { FuRedfishDevicePrivate *priv = GET_PRIVATE(self); JsonObject *json_obj; guint64 vendor_id = 0x0; guint64 model_id = 0x0; guint64 subsystem_vendor_id = 0x0; guint64 subsystem_model_id = 0x0; g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(priv->backend); /* get URI */ if (!fu_redfish_request_perform(request, uri, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON | FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE, error)) return FALSE; json_obj = fu_redfish_request_get_json_object(request); /* optional properties */ if (json_object_has_member(json_obj, "DeviceClass")) { const gchar *tmp = json_object_get_string_member(json_obj, "DeviceClass"); if (tmp != NULL && tmp[0] != '\0') fu_redfish_device_set_device_class(self, tmp); } if (json_object_has_member(json_obj, "VendorId")) { const gchar *tmp = json_object_get_string_member(json_obj, "VendorId"); if (tmp != NULL && tmp[0] != '\0') { if (!fu_strtoull(tmp, &vendor_id, 0, G_MAXUINT16, error)) return FALSE; } } if (json_object_has_member(json_obj, "DeviceId")) { const gchar *tmp = json_object_get_string_member(json_obj, "DeviceId"); if (tmp != NULL && tmp[0] != '\0') { if (!fu_strtoull(tmp, &model_id, 0, G_MAXUINT16, error)) return FALSE; } } if (json_object_has_member(json_obj, "SubsystemVendorId")) { const gchar *tmp = json_object_get_string_member(json_obj, "SubsystemVendorId"); if (tmp != NULL && tmp[0] != '\0') { if (!fu_strtoull(tmp, &subsystem_vendor_id, 0, G_MAXUINT16, error)) return FALSE; } } if (json_object_has_member(json_obj, "SubsystemId")) { const gchar *tmp = json_object_get_string_member(json_obj, "SubsystemId"); if (tmp != NULL && tmp[0] != '\0') { if (!fu_strtoull(tmp, &subsystem_model_id, 0, G_MAXUINT16, error)) return FALSE; } } /* add vendor ID */ if (vendor_id != 0x0) { g_autofree gchar *vendor_id_str = NULL; vendor_id_str = g_strdup_printf("PCI:0x%04X", (guint)vendor_id); fu_device_add_vendor_id(FU_DEVICE(self), vendor_id_str); } /* add more instance IDs if possible */ if (vendor_id != 0x0) fu_device_add_instance_u16(FU_DEVICE(self), "VEN", vendor_id); if (model_id != 0x0) fu_device_add_instance_u16(FU_DEVICE(self), "DEV", model_id); if (subsystem_vendor_id != 0x0 && subsystem_model_id != 0x0) { g_autofree gchar *subsys = NULL; subsys = g_strdup_printf("%04X%04X", (guint)subsystem_vendor_id, (guint)subsystem_model_id); fu_device_add_instance_str(FU_DEVICE(self), "SUBSYS", subsys); } fu_device_build_instance_id(FU_DEVICE(self), NULL, "PCI", "VEN", "DEV", NULL); fu_device_build_instance_id(FU_DEVICE(self), NULL, "PCI", "VEN", "DEV", "SUBSYS", NULL); /* success */ return TRUE; } static gboolean fu_redfish_device_probe_related_pcie_functions(FuRedfishDevice *self, const gchar *uri, GError **error) { FuRedfishDevicePrivate *priv = GET_PRIVATE(self); JsonObject *json_obj; g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(priv->backend); /* get URI */ if (!fu_redfish_request_perform(request, uri, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON | FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE, error)) return FALSE; json_obj = fu_redfish_request_get_json_object(request); if (json_object_has_member(json_obj, "Members")) { JsonArray *members_array = json_object_get_array_member(json_obj, "Members"); for (guint i = 0; i < json_array_get_length(members_array); i++) { JsonObject *related_item; related_item = json_array_get_object_element(members_array, i); if (json_object_has_member(related_item, "@odata.id")) { const gchar *id = json_object_get_string_member(related_item, "@odata.id"); if (!fu_redfish_device_probe_related_pcie_item(self, id, error)) return FALSE; } } } /* success */ return TRUE; } static gboolean fu_redfish_device_probe_related_item(FuRedfishDevice *self, const gchar *uri, GError **error) { FuRedfishDevicePrivate *priv = GET_PRIVATE(self); JsonObject *json_obj; g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(priv->backend); /* get URI */ if (!fu_redfish_request_perform(request, uri, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON | FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE, error)) return FALSE; json_obj = fu_redfish_request_get_json_object(request); /* optional properties */ if (json_object_has_member(json_obj, "SerialNumber")) { const gchar *tmp = json_object_get_string_member(json_obj, "SerialNumber"); if (tmp != NULL && tmp[0] != '\0' && g_strcmp0(tmp, "N/A") != 0) fu_device_set_serial(FU_DEVICE(self), tmp); } if (json_object_has_member(json_obj, "HotPluggable")) { /* this is better than the heuristic we get from the device name */ if (json_object_get_boolean_member(json_obj, "HotPluggable")) fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); else fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); } /* sometimes an array, sometimes an object! */ if (json_object_has_member(json_obj, "PCIeFunctions")) { JsonNode *pcie_functions = json_object_get_member(json_obj, "PCIeFunctions"); if (JSON_NODE_HOLDS_OBJECT(pcie_functions)) { JsonObject *obj = json_node_get_object(pcie_functions); if (json_object_has_member(obj, "@odata.id")) { const gchar *id = json_object_get_string_member(obj, "@odata.id"); if (!fu_redfish_device_probe_related_pcie_functions(self, id, error)) return FALSE; } } } return TRUE; } /* parses a Lenovo XCC-format version like "11A-1.02" */ static gboolean fu_redfish_device_set_version_lenovo(FuRedfishDevice *self, const gchar *version, GError **error) { FuRedfishDevicePrivate *priv = GET_PRIVATE(self); g_autofree gchar *out_build = NULL; g_autofree gchar *out_version = NULL; /* split up Lenovo format */ if (!fu_redfish_common_parse_version_lenovo(version, &out_build, &out_version, error)) return FALSE; /* split out milestone */ priv->milestone = g_ascii_strtoull(out_build, NULL, 10); if (priv->milestone == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "version milestone invalid"); return FALSE; } /* odd numbered builds are unsigned */ if (priv->milestone % 2 != 0) { fu_device_add_private_flag(FU_DEVICE(self), FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD); } /* build is only one letter from A -> Z */ if (!g_ascii_isalpha(out_build[2])) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "build letter invalid"); return FALSE; } priv->build = g_strndup(out_build + 2, 1); fu_device_set_version(FU_DEVICE(self), out_version); fu_device_set_version_format(FU_DEVICE(self), fu_version_guess_format(out_version)); return TRUE; } static void fu_redfish_device_set_version(FuRedfishDevice *self, const gchar *tmp) { /* OEM specific */ if (g_strcmp0(fu_device_get_vendor(FU_DEVICE(self)), "Lenovo") == 0) { g_autoptr(GError) error_local = NULL; if (!fu_redfish_device_set_version_lenovo(self, tmp, &error_local)) { g_debug("failed to parse Lenovo version %s: %s", tmp, error_local->message); } } /* fallback */ if (fu_device_get_version(FU_DEVICE(self)) == NULL) { g_autofree gchar *ver = fu_redfish_common_fix_version(tmp); if (ver != NULL) { fu_device_set_version(FU_DEVICE(self), ver); fu_device_set_version_format(FU_DEVICE(self), fu_version_guess_format(ver)); } } } static void fu_redfish_device_set_version_lowest(FuRedfishDevice *self, const gchar *tmp) { /* OEM specific */ if (g_strcmp0(fu_device_get_vendor(FU_DEVICE(self)), "Lenovo") == 0) { g_autoptr(GError) error_local = NULL; g_autofree gchar *out_version = NULL; if (!fu_redfish_common_parse_version_lenovo(tmp, NULL, &out_version, &error_local)) { g_debug("failed to parse Lenovo version %s: %s", tmp, error_local->message); } fu_device_set_version_lowest(FU_DEVICE(self), out_version); } /* fallback */ if (fu_device_get_version_lowest(FU_DEVICE(self)) == NULL) { g_autofree gchar *ver = fu_redfish_common_fix_version(tmp); fu_device_set_version_lowest(FU_DEVICE(self), ver); } } static void fu_redfish_device_set_name(FuRedfishDevice *self, const gchar *name) { /* useless */ if (g_str_has_prefix(name, "Firmware:")) name += 9; /* device type */ if (g_str_has_prefix(name, "DEVICE-")) { name += 7; fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); } else if (g_str_has_prefix(name, "DISK-")) { name += 5; fu_device_add_icon(FU_DEVICE(self), "drive-harddisk"); } else if (g_str_has_prefix(name, "POWER-")) { name += 6; fu_device_add_icon(FU_DEVICE(self), "ac-adapter"); fu_device_set_summary(FU_DEVICE(self), "Redfish power supply unit"); } else { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); } /* heuristics */ if (g_strcmp0(name, "BMC") == 0) fu_device_set_summary(FU_DEVICE(self), "Redfish baseboard management controller"); if (g_str_has_suffix(name, "HBA") == 0) fu_device_set_summary(FU_DEVICE(self), "Redfish host bus adapter"); /* success */ fu_device_set_name(FU_DEVICE(self), name); } static void fu_redfish_device_set_vendor(FuRedfishDevice *self, const gchar *vendor) { g_autofree gchar *vendor_upper = NULL; g_autofree gchar *vendor_id = NULL; /* fixup a common mistake */ if (g_strcmp0(vendor, "LEN") == 0 || g_strcmp0(vendor, "LNVO") == 0) vendor = "Lenovo"; fu_device_set_vendor(FU_DEVICE(self), vendor); /* add vendor-id */ vendor_upper = g_ascii_strup(vendor, -1); g_strdelimit(vendor_upper, " ", '_'); vendor_id = g_strdup_printf("REDFISH:%s", vendor_upper); fu_device_add_vendor_id(FU_DEVICE(self), vendor_id); } static void fu_redfish_backend_smc_license_check(FuDevice *device) { FuRedfishDevice *self = FU_REDFISH_DEVICE(device); FuRedfishBackend *backend = fu_redfish_device_get_backend(self); g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(backend); g_autoptr(GError) error_local = NULL; /* see if we don't get an license error */ if (!fu_redfish_request_perform(request, fu_redfish_backend_get_push_uri_path(backend), FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_MISSING_LICENSE); else g_debug("supermicro license check returned %s\n", error_local->message); } } static gboolean fu_redfish_device_probe(FuDevice *dev, GError **error) { FuRedfishDevice *self = FU_REDFISH_DEVICE(dev); FuRedfishDevicePrivate *priv = GET_PRIVATE(self); JsonObject *member = priv->member; const gchar *guid = NULL; g_autofree gchar *guid_lower = NULL; /* required to POST later */ if (!json_object_has_member(member, "@odata.id")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no @odata.id string"); return FALSE; } fu_device_set_physical_id(dev, "Redfish-Inventory"); fu_device_set_logical_id(dev, json_object_get_string_member(member, "@odata.id")); if (json_object_has_member(member, "Id")) { const gchar *tmp = json_object_get_string_member(member, "Id"); if (tmp != NULL) fu_device_set_backend_id(dev, tmp); } /* get SoftwareId, falling back to vendor-specific versions */ if (json_object_has_member(member, "SoftwareId")) { guid = json_object_get_string_member(member, "SoftwareId"); } else if (json_object_has_member(member, "Oem")) { JsonObject *oem = json_object_get_object_member(member, "Oem"); if (oem != NULL && json_object_has_member(oem, "Hpe")) { JsonObject *hpe = json_object_get_object_member(oem, "Hpe"); if (hpe != NULL && json_object_has_member(hpe, "DeviceClass")) guid = json_object_get_string_member(hpe, "DeviceClass"); } } /* GUID is required */ if (guid == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no GUID for device"); return FALSE; } /* device properties */ if (json_object_has_member(member, "Manufacturer")) { const gchar *tmp = json_object_get_string_member(member, "Manufacturer"); if (tmp != NULL && tmp[0] != '\0') fu_redfish_device_set_vendor(self, tmp); } /* the version can encode the instance ID suffix */ if (json_object_has_member(member, "Version")) { const gchar *tmp = json_object_get_string_member(member, "Version"); if (tmp != NULL && tmp[0] != '\0') fu_redfish_device_set_version(self, tmp); } /* ReleaseDate may or may not have a timezone */ if (json_object_has_member(member, "ReleaseDate")) { const gchar *tmp = json_object_get_string_member(member, "ReleaseDate"); if (tmp != NULL && tmp[0] != '\0') { g_autoptr(GDateTime) dt = NULL; g_autoptr(GTimeZone) tz = g_time_zone_new_utc(); dt = g_date_time_new_from_iso8601(tmp, tz); if (dt != NULL) { guint64 unixtime = (guint64)g_date_time_to_unix(dt); fu_device_set_version_build_date(dev, unixtime); } else { g_warning("failed to parse ISO8601 %s", tmp); } } } /* add IDs */ fu_device_add_instance_strsafe(dev, "VENDOR", fu_device_get_vendor(dev)); fu_device_add_instance_str(dev, "SOFTWAREID", guid); fu_device_add_instance_str(dev, "ID", fu_device_get_backend_id(dev)); /* some vendors use a GUID, others use an ID like BMC-AFBT-10 */ guid_lower = g_ascii_strdown(guid, -1); if (fwupd_guid_is_valid(guid_lower)) { fu_device_add_guid(dev, guid_lower); } else { if (fu_device_has_private_flag(dev, FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD)) fu_device_add_instance_str(dev, "TYPE", "UNSIGNED"); fu_device_build_instance_id(dev, NULL, "REDFISH", "VENDOR", "SOFTWAREID", "TYPE", NULL); fu_device_build_instance_id(dev, NULL, "REDFISH", "VENDOR", "SOFTWAREID", NULL); } /* used for quirking and parenting */ fu_device_build_instance_id(dev, NULL, "REDFISH", "VENDOR", "ID", NULL); if (json_object_has_member(member, "Name")) { const gchar *tmp = json_object_get_string_member(member, "Name"); if (tmp != NULL && tmp[0] != '\0') fu_redfish_device_set_name(self, tmp); } if (json_object_has_member(member, "LowestSupportedVersion")) { const gchar *tmp = json_object_get_string_member(member, "LowestSupportedVersion"); if (tmp != NULL && tmp[0] != '\0') fu_redfish_device_set_version_lowest(self, tmp); } if (json_object_has_member(member, "Description")) { const gchar *tmp = json_object_get_string_member(member, "Description"); if (tmp != NULL && tmp[0] != '\0') fu_device_set_description(dev, tmp); } /* reasons why the device might not be updatable */ if (json_object_has_member(member, "Updateable")) { if (json_object_get_boolean_member(member, "Updateable")) fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE); } if (fu_device_has_private_flag(dev, FU_REDFISH_DEVICE_FLAG_IS_BACKUP)) fu_device_inhibit(dev, "is-backup", "Is a backup partition"); else fu_device_uninhibit(dev, "is-backup"); /* use related items to set extra instance IDs */ if (fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE) && json_object_has_member(member, "RelatedItem")) { JsonArray *related_item_array = json_object_get_array_member(member, "RelatedItem"); for (guint i = 0; i < json_array_get_length(related_item_array); i++) { JsonObject *related_item; related_item = json_array_get_object_element(related_item_array, i); if (json_object_has_member(related_item, "@odata.id")) { const gchar *id = json_object_get_string_member(related_item, "@odata.id"); if (!fu_redfish_device_probe_related_item(self, id, error)) return FALSE; } } } /* for Supermicro check whether we have a proper Redfish license installed */ if (g_strcmp0("SMCI", fu_device_get_vendor(dev)) == 0) fu_redfish_backend_smc_license_check(dev); /* success */ return TRUE; } FuRedfishBackend * fu_redfish_device_get_backend(FuRedfishDevice *self) { FuRedfishDevicePrivate *priv = GET_PRIVATE(self); return priv->backend; } typedef struct { FwupdError error_code; gchar *location; gboolean completed; GHashTable *messages_seen; FuProgress *progress; } FuRedfishDevicePollCtx; static void fu_redfish_device_poll_set_message_id(FuRedfishDevice *self, FuRedfishDevicePollCtx *ctx, const gchar *message_id) { /* ignore */ if (g_pattern_match_simple("TaskEvent.*.TaskProgressChanged", message_id) || g_pattern_match_simple("TaskEvent.*.TaskCompletedWarning", message_id) || g_pattern_match_simple("TaskEvent.*.TaskCompletedOK", message_id) || g_pattern_match_simple("Base.*.Success", message_id)) return; /* set flags */ if (g_pattern_match_simple("Base.*.ResetRequired", message_id)) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); return; } /* set error code */ if (g_pattern_match_simple("Update.*.AwaitToActivate", message_id)) { ctx->error_code = FWUPD_ERROR_NEEDS_USER_ACTION; return; } if (g_pattern_match_simple("Update.*.TransferFailed", message_id)) { ctx->error_code = FWUPD_ERROR_WRITE; return; } if (g_pattern_match_simple("Update.*.ActivateFailed", message_id)) { ctx->error_code = FWUPD_ERROR_INVALID_FILE; return; } if (g_pattern_match_simple("Update.*.VerificationFailed", message_id) || g_pattern_match_simple("LenovoFirmwareUpdateRegistry.*.UpdateVerifyFailed", message_id)) { ctx->error_code = FWUPD_ERROR_INVALID_FILE; return; } if (g_pattern_match_simple("Update.*.ApplyFailed", message_id)) { ctx->error_code = FWUPD_ERROR_WRITE; return; } /* set status */ if (g_pattern_match_simple("Update.*.TargetDetermined", message_id)) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_LOADING); return; } if (g_pattern_match_simple("LenovoFirmwareUpdateRegistry.*.UpdateAssignment", message_id)) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_LOADING); return; } if (g_pattern_match_simple("LenovoFirmwareUpdateRegistry.*.PayloadApplyInProgress", message_id)) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_WRITE); return; } if (g_pattern_match_simple("LenovoFirmwareUpdateRegistry.*.PayloadApplyCompleted", message_id)) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_IDLE); return; } if (g_pattern_match_simple("LenovoFirmwareUpdateRegistry.*.UpdateVerifyInProgress", message_id)) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_VERIFY); return; } if (g_pattern_match_simple("Update.*.TransferringToComponent", message_id)) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_LOADING); return; } if (g_pattern_match_simple("Update.*.VerifyingAtComponent", message_id)) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_VERIFY); return; } if (g_pattern_match_simple("Update.*.UpdateInProgress", message_id)) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_WRITE); return; } if (g_pattern_match_simple("Update.*.UpdateSuccessful", message_id)) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_IDLE); return; } if (g_pattern_match_simple("Update.*.InstallingOnComponent", message_id)) { fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_WRITE); return; } } static gboolean fu_redfish_device_poll_task_once(FuRedfishDevice *self, FuRedfishDevicePollCtx *ctx, GError **error) { FuRedfishDevicePrivate *priv = GET_PRIVATE(self); JsonObject *json_obj; const gchar *message = "Unknown failure"; const gchar *state_tmp; g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(priv->backend); /* create URI and poll */ if (!fu_redfish_request_perform(request, ctx->location, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, error)) return FALSE; /* percentage is optional */ json_obj = fu_redfish_request_get_json_object(request); if (json_object_has_member(json_obj, "PercentComplete")) { gint64 pc = json_object_get_int_member(json_obj, "PercentComplete"); if (pc >= 0 && pc <= 100) fu_progress_set_percentage(ctx->progress, (guint)pc); } /* print all messages we've not seen yet */ if (json_object_has_member(json_obj, "Messages")) { JsonArray *json_msgs = json_object_get_array_member(json_obj, "Messages"); guint json_msgs_sz = json_array_get_length(json_msgs); for (guint i = 0; i < json_msgs_sz; i++) { JsonObject *json_message = json_array_get_object_element(json_msgs, i); const gchar *message_id = NULL; g_autofree gchar *message_key = NULL; /* set additional device properties */ if (json_object_has_member(json_message, "MessageId")) message_id = json_object_get_string_member(json_message, "MessageId"); if (json_object_has_member(json_message, "Message")) message = json_object_get_string_member(json_message, "Message"); /* ignore messages we've seen before */ message_key = g_strdup_printf("%s;%s", message_id, message); if (g_hash_table_contains(ctx->messages_seen, message_key)) { g_debug("ignoring %s", message_key); continue; } g_hash_table_add(ctx->messages_seen, g_steal_pointer(&message_key)); /* use the message */ g_debug("message #%u [%s]: %s", i, message_id, message); fu_redfish_device_poll_set_message_id(self, ctx, message_id); } } /* use taskstate to set context */ if (!json_object_has_member(json_obj, "TaskState")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no TaskState for task manager"); return FALSE; } state_tmp = json_object_get_string_member(json_obj, "TaskState"); g_debug("TaskState now %s", state_tmp); if (g_strcmp0(state_tmp, "Completed") == 0) { ctx->completed = TRUE; return TRUE; } if (g_strcmp0(state_tmp, "Cancelled") == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Task was cancelled"); return FALSE; } if (g_strcmp0(state_tmp, "Exception") == 0 || g_strcmp0(state_tmp, "UserIntervention") == 0) { g_set_error_literal(error, FWUPD_ERROR, ctx->error_code, message); return FALSE; } /* try again */ return TRUE; } static FuRedfishDevicePollCtx * fu_redfish_device_poll_ctx_new(FuProgress *progress, const gchar *location) { FuRedfishDevicePollCtx *ctx = g_new0(FuRedfishDevicePollCtx, 1); ctx->messages_seen = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); ctx->location = g_strdup(location); ctx->error_code = FWUPD_ERROR_INTERNAL; ctx->progress = g_object_ref(progress); return ctx; } static void fu_redfish_device_poll_ctx_free(FuRedfishDevicePollCtx *ctx) { g_hash_table_unref(ctx->messages_seen); g_object_unref(ctx->progress); g_free(ctx->location); g_free(ctx); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuRedfishDevicePollCtx, fu_redfish_device_poll_ctx_free) #pragma clang diagnostic pop gboolean fu_redfish_device_poll_task(FuRedfishDevice *self, const gchar *location, FuProgress *progress, GError **error) { const guint timeout = 2400; g_autoptr(GTimer) timer = g_timer_new(); g_autoptr(FuRedfishDevicePollCtx) ctx = fu_redfish_device_poll_ctx_new(progress, location); /* sleep and then reprobe hardware */ do { fu_device_sleep(FU_DEVICE(self), 1000); /* ms */ if (!fu_redfish_device_poll_task_once(self, ctx, error)) return FALSE; if (ctx->completed) return TRUE; } while (g_timer_elapsed(timer, NULL) < timeout); /* success */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to poll %s for success after %u seconds", location, timeout); return FALSE; } guint fu_redfish_device_get_reset_pre_delay(FuRedfishDevice *self) { FuRedfishDevicePrivate *priv = GET_PRIVATE(self); return priv->reset_pre_delay; } guint fu_redfish_device_get_reset_post_delay(FuRedfishDevice *self) { FuRedfishDevicePrivate *priv = GET_PRIVATE(self); return priv->reset_post_delay; } static gboolean fu_redfish_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuRedfishDevice *self = FU_REDFISH_DEVICE(device); FuRedfishDevicePrivate *priv = GET_PRIVATE(self); guint64 tmp = 0; if (g_strcmp0(key, "RedfishResetPreDelay") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT, error)) return FALSE; priv->reset_pre_delay = tmp; return TRUE; } if (g_strcmp0(key, "RedfishResetPostDelay") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT, error)) return FALSE; priv->reset_post_delay = tmp; return TRUE; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_redfish_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuRedfishDevice *self = FU_REDFISH_DEVICE(object); FuRedfishDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_BACKEND: g_value_set_object(value, priv->backend); break; case PROP_MEMBER: g_value_set_pointer(value, priv->member); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_redfish_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuRedfishDevice *self = FU_REDFISH_DEVICE(object); FuRedfishDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_BACKEND: g_set_object(&priv->backend, g_value_get_object(value)); break; case PROP_MEMBER: priv->member = json_object_ref(g_value_get_pointer(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_redfish_device_init(FuRedfishDevice *self) { fu_device_set_summary(FU_DEVICE(self), "Redfish device"); fu_device_add_protocol(FU_DEVICE(self), "org.dmtf.redfish"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED); fu_device_register_private_flag(FU_DEVICE(self), FU_REDFISH_DEVICE_FLAG_IS_BACKUP, "is-backup"); fu_device_register_private_flag(FU_DEVICE(self), FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD, "unsigned-build"); fu_device_register_private_flag(FU_DEVICE(self), FU_REDFISH_DEVICE_FLAG_WILDCARD_TARGETS, "wildcard-targets"); fu_device_register_private_flag(FU_DEVICE(self), FU_REDFISH_DEVICE_FLAG_MANAGER_RESET, "manager-reset"); fu_device_register_private_flag(FU_DEVICE(self), FU_REDFISH_DEVICE_FLAG_NO_MANAGER_RESET_REQUEST, "no-manager-reset-request"); } static void fu_redfish_device_finalize(GObject *object) { FuRedfishDevice *self = FU_REDFISH_DEVICE(object); FuRedfishDevicePrivate *priv = GET_PRIVATE(self); if (priv->backend != NULL) g_object_unref(priv->backend); if (priv->member != NULL) json_object_unref(priv->member); g_free(priv->build); G_OBJECT_CLASS(fu_redfish_device_parent_class)->finalize(object); } static void fu_redfish_device_class_init(FuRedfishDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->get_property = fu_redfish_device_get_property; object_class->set_property = fu_redfish_device_set_property; object_class->finalize = fu_redfish_device_finalize; klass_device->to_string = fu_redfish_device_to_string; klass_device->probe = fu_redfish_device_probe; klass_device->set_quirk_kv = fu_redfish_device_set_quirk_kv; /** * FuRedfishDevice:backend: * * The backend that added the device. */ pspec = g_param_spec_object("backend", NULL, NULL, FU_TYPE_BACKEND, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_BACKEND, pspec); /** * FuRedfishDevice:member: * * The JSON root member for the device. */ pspec = g_param_spec_pointer("member", NULL, NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_MEMBER, pspec); } fwupd-1.9.16/plugins/redfish/fu-redfish-device.h000066400000000000000000000034711460375044200215160ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-redfish-backend.h" #define FU_TYPE_REDFISH_DEVICE (fu_redfish_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuRedfishDevice, fu_redfish_device, FU, REDFISH_DEVICE, FuDevice) struct _FuRedfishDeviceClass { FuDeviceClass parent_class; }; /** * FU_REDFISH_DEVICE_FLAG_IS_BACKUP: * * The device is the other half of a dual image firmware. */ #define FU_REDFISH_DEVICE_FLAG_IS_BACKUP (1 << 0) /** * FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD: * * Use unsigned development builds. */ #define FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD (1 << 1) /** * FU_REDFISH_DEVICE_FLAG_MANAGER_RESET: * * Reset the manager (typically the BMC) after updating this device. */ #define FU_REDFISH_DEVICE_FLAG_MANAGER_RESET (1 << 2) /** * FU_REDFISH_DEVICE_FLAG_WILDCARD_TARGETS: * * Do not specify the `odata.id` in the multipart update Targets array and allow * the BMC to deploy the firmware onto all compatible hardware. * * To use this option the payload must contain metadata that restricts it to a * specific SoftwareId. */ #define FU_REDFISH_DEVICE_FLAG_WILDCARD_TARGETS (1 << 3) /** * FU_REDFISH_DEVICE_FLAG_NO_MANAGER_RESET_REQUEST: * * The BMC device will auto-reboot and so fwupd should not explicitly call * `/redfish/v1/Managers/1/Actions/Manager.Reset`. */ #define FU_REDFISH_DEVICE_FLAG_NO_MANAGER_RESET_REQUEST (1 << 4) FuRedfishBackend * fu_redfish_device_get_backend(FuRedfishDevice *self); gboolean fu_redfish_device_poll_task(FuRedfishDevice *self, const gchar *location, FuProgress *progress, GError **error); guint fu_redfish_device_get_reset_pre_delay(FuRedfishDevice *self); guint fu_redfish_device_get_reset_post_delay(FuRedfishDevice *self); fwupd-1.9.16/plugins/redfish/fu-redfish-legacy-device.c000066400000000000000000000117501460375044200227520ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-redfish-legacy-device.h" #include "fu-redfish-request.h" struct _FuRedfishLegacyDevice { FuRedfishDevice parent_instance; }; G_DEFINE_TYPE(FuRedfishLegacyDevice, fu_redfish_legacy_device, FU_TYPE_REDFISH_DEVICE) static gboolean fu_redfish_legacy_device_detach(FuDevice *dev, FuProgress *progress, GError **error) { FuRedfishLegacyDevice *self = FU_REDFISH_LEGACY_DEVICE(dev); FuRedfishBackend *backend = fu_redfish_device_get_backend(FU_REDFISH_DEVICE(self)); g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(backend); g_autoptr(JsonBuilder) builder = json_builder_new(); /* create header */ json_builder_begin_object(builder); json_builder_set_member_name(builder, "HttpPushUriTargetsBusy"); json_builder_add_boolean_value(builder, TRUE); json_builder_set_member_name(builder, "HttpPushUriTargets"); json_builder_begin_array(builder); json_builder_add_string_value(builder, fu_device_get_logical_id(FU_DEVICE(self))); json_builder_end_array(builder); json_builder_end_object(builder); /* patch the two fields */ return fu_redfish_request_perform_full(request, "/redfish/v1/UpdateService", "PATCH", builder, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON | FU_REDFISH_REQUEST_PERFORM_FLAG_USE_ETAG, error); } static gboolean fu_redfish_legacy_device_attach(FuDevice *dev, FuProgress *progress, GError **error) { FuRedfishLegacyDevice *self = FU_REDFISH_LEGACY_DEVICE(dev); FuRedfishBackend *backend = fu_redfish_device_get_backend(FU_REDFISH_DEVICE(self)); g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(backend); g_autoptr(JsonBuilder) builder = json_builder_new(); /* create header */ json_builder_begin_object(builder); json_builder_set_member_name(builder, "HttpPushUriTargetsBusy"); json_builder_add_boolean_value(builder, FALSE); json_builder_set_member_name(builder, "HttpPushUriTargets"); json_builder_begin_array(builder); json_builder_end_array(builder); json_builder_end_object(builder); /* patch the two fields */ return fu_redfish_request_perform_full(request, "/redfish/v1/UpdateService", "PATCH", builder, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON | FU_REDFISH_REQUEST_PERFORM_FLAG_USE_ETAG, error); } static gboolean fu_redfish_legacy_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuRedfishLegacyDevice *self = FU_REDFISH_LEGACY_DEVICE(device); FuRedfishBackend *backend = fu_redfish_device_get_backend(FU_REDFISH_DEVICE(self)); CURL *curl; JsonObject *json_obj; const gchar *location; g_autoptr(FuRedfishRequest) request = NULL; g_autoptr(GBytes) fw = NULL; /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* POST data */ request = fu_redfish_backend_request_new(backend); curl = fu_redfish_request_get_curl(request); (void)curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); (void)curl_easy_setopt(curl, CURLOPT_POSTFIELDS, g_bytes_get_data(fw, NULL)); (void)curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)g_bytes_get_size(fw)); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); if (!fu_redfish_request_perform(request, fu_redfish_backend_get_push_uri_path(backend), FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, error)) return FALSE; /* poll the task for progress */ json_obj = fu_redfish_request_get_json_object(request); if (!json_object_has_member(json_obj, "@odata.id")) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no task returned for %s", fu_redfish_backend_get_push_uri_path(backend)); return FALSE; } location = json_object_get_string_member(json_obj, "@odata.id"); return fu_redfish_device_poll_task(FU_REDFISH_DEVICE(self), location, progress, error); } static void fu_redfish_legacy_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_redfish_legacy_device_init(FuRedfishLegacyDevice *self) { fu_device_set_summary(FU_DEVICE(self), "Redfish legacy device"); } static void fu_redfish_legacy_device_class_init(FuRedfishLegacyDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->attach = fu_redfish_legacy_device_attach; klass_device->detach = fu_redfish_legacy_device_detach; klass_device->write_firmware = fu_redfish_legacy_device_write_firmware; klass_device->set_progress = fu_redfish_legacy_device_set_progress; } fwupd-1.9.16/plugins/redfish/fu-redfish-legacy-device.h000066400000000000000000000005641460375044200227600ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-redfish-device.h" #define FU_TYPE_REDFISH_LEGACY_DEVICE (fu_redfish_legacy_device_get_type()) G_DECLARE_FINAL_TYPE(FuRedfishLegacyDevice, fu_redfish_legacy_device, FU, REDFISH_LEGACY_DEVICE, FuRedfishDevice) fwupd-1.9.16/plugins/redfish/fu-redfish-multipart-device.c000066400000000000000000000117611460375044200235310ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-redfish-multipart-device.h" #include "fu-redfish-request.h" struct _FuRedfishMultipartDevice { FuRedfishDevice parent_instance; }; G_DEFINE_TYPE(FuRedfishMultipartDevice, fu_redfish_multipart_device, FU_TYPE_REDFISH_DEVICE) G_DEFINE_AUTOPTR_CLEANUP_FUNC(curl_mime, curl_mime_free) static GString * fu_redfish_multipart_device_get_parameters(FuRedfishMultipartDevice *self) { g_autoptr(GString) str = g_string_new(NULL); g_autoptr(JsonBuilder) builder = json_builder_new(); g_autoptr(JsonGenerator) json_generator = json_generator_new(); g_autoptr(JsonNode) json_root = NULL; /* create header */ json_builder_begin_object(builder); json_builder_set_member_name(builder, "Targets"); json_builder_begin_array(builder); if (!fu_device_has_private_flag(FU_DEVICE(self), FU_REDFISH_DEVICE_FLAG_WILDCARD_TARGETS)) { const gchar *logical_id = fu_device_get_logical_id(FU_DEVICE(self)); json_builder_add_string_value(builder, logical_id); } json_builder_end_array(builder); json_builder_set_member_name(builder, "@Redfish.OperationApplyTime"); json_builder_add_string_value(builder, "Immediate"); json_builder_end_object(builder); /* export as a string */ json_root = json_builder_get_root(builder); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); json_generator_to_gstring(json_generator, str); return g_steal_pointer(&str); } static gboolean fu_redfish_multipart_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuRedfishMultipartDevice *self = FU_REDFISH_MULTIPART_DEVICE(device); FuRedfishBackend *backend = fu_redfish_device_get_backend(FU_REDFISH_DEVICE(self)); CURL *curl; JsonObject *json_obj; curl_mimepart *part; const gchar *location; g_autoptr(curl_mime) mime = NULL; g_autoptr(FuRedfishRequest) request = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GString) params = NULL; /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* create the multipart request */ request = fu_redfish_backend_request_new(backend); curl = fu_redfish_request_get_curl(request); mime = curl_mime_init(curl); (void)curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); params = fu_redfish_multipart_device_get_parameters(self); part = curl_mime_addpart(mime); curl_mime_name(part, "UpdateParameters"); (void)curl_mime_type(part, "application/json"); (void)curl_mime_data(part, params->str, CURL_ZERO_TERMINATED); g_debug("request: %s", params->str); part = curl_mime_addpart(mime); curl_mime_name(part, "UpdateFile"); (void)curl_mime_type(part, "application/octet-stream"); (void)curl_mime_filedata(part, "firmware.bin"); (void)curl_mime_data(part, g_bytes_get_data(fw, NULL), g_bytes_get_size(fw)); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); if (!fu_redfish_request_perform(request, fu_redfish_backend_get_push_uri_path(backend), FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, error)) return FALSE; if (fu_redfish_request_get_status_code(request) != 202) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to upload: %li", fu_redfish_request_get_status_code(request)); return FALSE; } /* prefer the header, otherwise fall back to the response */ json_obj = fu_redfish_request_get_json_object(request); if (json_object_has_member(json_obj, "TaskMonitor")) { const gchar *tmp = json_object_get_string_member(json_obj, "TaskMonitor"); g_debug("task manager for cleanup is %s", tmp); } /* poll the task for progress */ if (!json_object_has_member(json_obj, "@odata.id")) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no task returned for %s", fu_redfish_backend_get_push_uri_path(backend)); return FALSE; } location = json_object_get_string_member(json_obj, "@odata.id"); return fu_redfish_device_poll_task(FU_REDFISH_DEVICE(self), location, progress, error); } static void fu_redfish_multipart_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_redfish_multipart_device_init(FuRedfishMultipartDevice *self) { fu_device_set_summary(FU_DEVICE(self), "Redfish multipart device"); } static void fu_redfish_multipart_device_class_init(FuRedfishMultipartDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_redfish_multipart_device_write_firmware; klass_device->set_progress = fu_redfish_multipart_device_set_progress; } fwupd-1.9.16/plugins/redfish/fu-redfish-multipart-device.h000066400000000000000000000006031460375044200235270ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-redfish-device.h" #define FU_TYPE_REDFISH_MULTIPART_DEVICE (fu_redfish_multipart_device_get_type()) G_DECLARE_FINAL_TYPE(FuRedfishMultipartDevice, fu_redfish_multipart_device, FU, REDFISH_MULTIPART_DEVICE, FuRedfishDevice) fwupd-1.9.16/plugins/redfish/fu-redfish-network-device.c000066400000000000000000000126701460375044200232010ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-redfish-network-device.h" #include "fu-redfish-network.h" struct _FuRedfishNetworkDevice { GObject parent_instance; gchar *object_path; }; G_DEFINE_TYPE(FuRedfishNetworkDevice, fu_redfish_network_device, G_TYPE_OBJECT) gboolean fu_redfish_network_device_get_state(FuRedfishNetworkDevice *self, FuRedfishNetworkDeviceState *state, GError **error) { g_autoptr(GVariant) retval = NULL; g_autoptr(GDBusProxy) proxy = NULL; g_return_val_if_fail(FU_IS_REDFISH_NETWORK_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect to device */ proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, NETWORK_MANAGER_SERVICE_NAME, self->object_path, NETWORK_MANAGER_INTERFACE_DEVICE, NULL, error); if (proxy == NULL) return FALSE; retval = g_dbus_proxy_get_cached_property(proxy, "State"); if (retval == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_CONNECTED, "could not find State"); return FALSE; } if (state != NULL) *state = g_variant_get_uint32(retval); return TRUE; } gboolean fu_redfish_network_device_connect(FuRedfishNetworkDevice *self, GError **error) { g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GTimer) timer = g_timer_new(); g_autoptr(GVariant) success = NULL; g_return_val_if_fail(FU_IS_REDFISH_NETWORK_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* connect to manager */ proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, NETWORK_MANAGER_SERVICE_NAME, NETWORK_MANAGER_PATH, NETWORK_MANAGER_INTERFACE, NULL, error); if (proxy == NULL) return FALSE; /* activate with some good defaults */ success = g_dbus_proxy_call_sync(proxy, "ActivateConnection", g_variant_new("(ooo)", "/", self->object_path, "/"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (success == NULL) return FALSE; /* wait until the network interface comes up */ do { FuRedfishNetworkDeviceState state = FU_REDFISH_NETWORK_DEVICE_STATE_UNKNOWN; if (!fu_redfish_network_device_get_state(self, &state, error)) return FALSE; g_debug("%s device state is now %s [%u]", self->object_path, fu_redfish_network_device_state_to_string(state), state); if (state == FU_REDFISH_NETWORK_DEVICE_STATE_ACTIVATED) return TRUE; g_usleep(50 * 1000); } while (g_timer_elapsed(timer, NULL) < 5.f); /* timed out */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "could not activate connection"); return FALSE; } gchar * fu_redfish_network_device_get_address(FuRedfishNetworkDevice *self, GError **error) { g_autofree gchar *address = NULL; g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GDBusProxy) proxy2 = NULL; g_autoptr(GVariant) addr_data = NULL; g_autoptr(GVariant) ip4_config = NULL; g_return_val_if_fail(FU_IS_REDFISH_NETWORK_DEVICE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* connect to device */ proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, NETWORK_MANAGER_SERVICE_NAME, self->object_path, NETWORK_MANAGER_INTERFACE_DEVICE, NULL, error); if (proxy == NULL) return NULL; ip4_config = g_dbus_proxy_get_cached_property(proxy, "Ip4Config"); if (ip4_config == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_CONNECTED, "could not find IPv4 config"); return NULL; } proxy2 = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, NETWORK_MANAGER_SERVICE_NAME, g_variant_get_string(ip4_config, NULL), NETWORK_MANAGER_INTERFACE_IP4_CONFIG, NULL, error); if (proxy2 == NULL) return NULL; addr_data = g_dbus_proxy_get_cached_property(proxy2, "AddressData"); if (addr_data != NULL) { g_autoptr(GVariant) addr_data0 = g_variant_get_child_value(addr_data, 0); g_autoptr(GVariantDict) dict = g_variant_dict_new(addr_data0); g_variant_dict_lookup(dict, "address", "s", &address); } if (address == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_CONNECTED, "could not find IP address for device"); return NULL; } /* success */ return g_steal_pointer(&address); } FuRedfishNetworkDevice * fu_redfish_network_device_new(const gchar *object_path) { FuRedfishNetworkDevice *self = g_object_new(FU_TYPE_REDFISH_NETWORK_DEVICE, NULL); self->object_path = g_strdup(object_path); return self; } static void fu_redfish_network_device_init(FuRedfishNetworkDevice *self) { } static void fu_redfish_network_device_finalize(GObject *object) { FuRedfishNetworkDevice *self = FU_REDFISH_NETWORK_DEVICE(object); g_free(self->object_path); G_OBJECT_CLASS(fu_redfish_network_device_parent_class)->finalize(object); } static void fu_redfish_network_device_class_init(FuRedfishNetworkDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_redfish_network_device_finalize; } fwupd-1.9.16/plugins/redfish/fu-redfish-network-device.h000066400000000000000000000014451460375044200232040ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-redfish-struct.h" #define FU_TYPE_REDFISH_NETWORK_DEVICE (fu_redfish_network_device_get_type()) G_DECLARE_FINAL_TYPE(FuRedfishNetworkDevice, fu_redfish_network_device, FU, REDFISH_NETWORK_DEVICE, GObject) FuRedfishNetworkDevice * fu_redfish_network_device_new(const gchar *object_path); gboolean fu_redfish_network_device_get_state(FuRedfishNetworkDevice *self, FuRedfishNetworkDeviceState *state, GError **error); gchar * fu_redfish_network_device_get_address(FuRedfishNetworkDevice *self, GError **error); gboolean fu_redfish_network_device_connect(FuRedfishNetworkDevice *self, GError **error); fwupd-1.9.16/plugins/redfish/fu-redfish-network.c000066400000000000000000000127111460375044200217400ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-redfish-network.h" typedef struct { FuRedfishNetworkDevice *device; const gchar *mac_addr; guint16 vid; guint16 pid; } FuRedfishNetworkMatchHelper; static gboolean fu_redfish_network_device_match_device(FuRedfishNetworkMatchHelper *helper, const gchar *object_path, GError **error) { g_autoptr(GDBusProxy) proxy = NULL; /* connect to device */ proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, NULL, NETWORK_MANAGER_SERVICE_NAME, object_path, NETWORK_MANAGER_INTERFACE_DEVICE, NULL, error); if (proxy == NULL) { g_prefix_error(error, "failed to connect to interface %s: ", object_path); return FALSE; } /* compare MAC address different */ if (helper->mac_addr != NULL) { const gchar *mac_addr = NULL; g_autoptr(GVariant) hw_address = NULL; hw_address = g_dbus_proxy_get_cached_property(proxy, "HwAddress"); if (hw_address == NULL) return TRUE; mac_addr = g_variant_get_string(hw_address, NULL); /* verify */ g_debug("mac_addr=%s", mac_addr); if (g_strcmp0(mac_addr, helper->mac_addr) == 0) helper->device = fu_redfish_network_device_new(object_path); } /* compare VID:PID */ if (helper->vid != 0x0 && helper->pid != 0x0) { #ifdef HAVE_GUDEV const gchar *sysfs_path = NULL; const gchar *tmp; guint16 pid = 0; guint16 vid = 0; g_autoptr(GVariant) udi = NULL; g_autoptr(GUdevClient) udev_client = NULL; g_autoptr(GUdevDevice) udev_device = NULL; udi = g_dbus_proxy_get_cached_property(proxy, "Udi"); if (udi == NULL) return TRUE; sysfs_path = g_variant_get_string(udi, NULL); /* get the VID and PID */ udev_client = g_udev_client_new(NULL); udev_device = g_udev_client_query_by_sysfs_path(udev_client, sysfs_path); if (udev_device == NULL) return TRUE; tmp = g_udev_device_get_property(udev_device, "ID_VENDOR_ID"); if (tmp != NULL) vid = g_ascii_strtoull(tmp, NULL, 16); tmp = g_udev_device_get_property(udev_device, "ID_MODEL_ID"); if (tmp != NULL) pid = g_ascii_strtoull(tmp, NULL, 16); /* verify */ g_debug("%s: 0x%04x, 0x%04x", sysfs_path, vid, pid); if (vid == helper->vid && pid == helper->pid) helper->device = fu_redfish_network_device_new(object_path); #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no UDev support"); return FALSE; #endif } /* assume success */ return TRUE; } static gboolean fu_redfish_network_device_match(FuRedfishNetworkMatchHelper *helper, GError **error) { g_autoptr(GDBusProxy) proxy = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GVariant) devices = NULL; g_auto(GStrv) paths = NULL; /* get devices */ proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, NETWORK_MANAGER_SERVICE_NAME, NETWORK_MANAGER_PATH, NETWORK_MANAGER_INTERFACE, NULL, &error_local); if (proxy == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR) || g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY) || g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "D-Bus is not running"); return FALSE; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to construct proxy for %s: ", NETWORK_MANAGER_SERVICE_NAME); return FALSE; } devices = g_dbus_proxy_call_sync(proxy, "GetDevices", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error_local); if (devices == NULL) { if (g_error_matches(error_local, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN) || g_error_matches(error_local, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "NetworkManager is not running"); return FALSE; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to call GetDevices() on %s: ", g_dbus_proxy_get_name(proxy)); return FALSE; } /* look at each device */ g_variant_get(devices, "(^ao)", &paths); for (guint i = 0; paths[i] != NULL; i++) { g_debug("device %u: %s", i, paths[i]); if (!fu_redfish_network_device_match_device(helper, paths[i], error)) return FALSE; if (helper->device != NULL) break; } if (helper->device == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "could not find device"); return FALSE; } return TRUE; } FuRedfishNetworkDevice * fu_redfish_network_device_for_mac_addr(const gchar *mac_addr, GError **error) { FuRedfishNetworkMatchHelper helper = { .mac_addr = mac_addr, }; if (!fu_redfish_network_device_match(&helper, error)) { g_prefix_error(error, "missing %s: ", mac_addr); return NULL; } return helper.device; } FuRedfishNetworkDevice * fu_redfish_network_device_for_vid_pid(guint16 vid, guint16 pid, GError **error) { FuRedfishNetworkMatchHelper helper = { .vid = vid, .pid = pid, }; if (!fu_redfish_network_device_match(&helper, error)) { g_prefix_error(error, "missing 0x%04x:0x%04x: ", vid, pid); return NULL; } return helper.device; } fwupd-1.9.16/plugins/redfish/fu-redfish-network.h000066400000000000000000000014201460375044200217400ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-redfish-network-device.h" #define NETWORK_MANAGER_SERVICE_NAME "org.freedesktop.NetworkManager" #define NETWORK_MANAGER_INTERFACE "org.freedesktop.NetworkManager" #define NETWORK_MANAGER_INTERFACE_IP4_CONFIG "org.freedesktop.NetworkManager.IP4Config" #define NETWORK_MANAGER_INTERFACE_DEVICE "org.freedesktop.NetworkManager.Device" #define NETWORK_MANAGER_PATH "/org/freedesktop/NetworkManager" FuRedfishNetworkDevice * fu_redfish_network_device_for_mac_addr(const gchar *mac_addr, GError **error); FuRedfishNetworkDevice * fu_redfish_network_device_for_vid_pid(guint16 vid, guint16 pid, GError **error); fwupd-1.9.16/plugins/redfish/fu-redfish-plugin.c000066400000000000000000000560121460375044200215470ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_LINUX_IPMI_H #include "fu-ipmi-device.h" #endif #include "fu-redfish-backend.h" #include "fu-redfish-common.h" #include "fu-redfish-device.h" #include "fu-redfish-network.h" #include "fu-redfish-plugin.h" #include "fu-redfish-smbios.h" #include "fu-redfish-struct.h" #define FU_REDFISH_PLUGIN_CLEANUP_RETRIES_DELAY 10 /* seconds */ struct _FuRedfishPlugin { FuPlugin parent_instance; FuRedfishBackend *backend; FuRedfishSmbios *smbios; /* nullable */ }; G_DEFINE_TYPE(FuRedfishPlugin, fu_redfish_plugin, FU_TYPE_PLUGIN) static void fu_redfish_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuRedfishPlugin *self = FU_REDFISH_PLUGIN(plugin); fu_backend_add_string(FU_BACKEND(self->backend), idt, str); if (self->smbios != NULL) { g_autofree gchar *smbios = fu_firmware_to_string(FU_FIRMWARE(self->smbios)); fu_string_append(str, idt, "Smbios", smbios); } fu_string_append(str, idt, "Vendor", fu_redfish_backend_get_vendor(self->backend)); fu_string_append(str, idt, "Version", fu_redfish_backend_get_version(self->backend)); fu_string_append(str, idt, "UUID", fu_redfish_backend_get_uuid(self->backend)); } static gchar * fu_common_generate_password(guint length) { GString *str = g_string_sized_new(length); /* get a random password string */ while (str->len < length) { gchar tmp = (gchar)g_random_int_range(0x0, 0xff); if (g_ascii_isalnum(tmp)) g_string_append_c(str, tmp); } return g_string_free(str, FALSE); } static gboolean fu_redfish_plugin_change_expired(FuPlugin *plugin, GError **error) { FuRedfishPlugin *self = FU_REDFISH_PLUGIN(plugin); g_autofree gchar *password_new = fu_common_generate_password(15); g_autofree gchar *uri = NULL; g_autoptr(FuRedfishRequest) request = NULL; g_autoptr(JsonBuilder) builder = json_builder_new(); /* select correct, falling back to default for old fwupd versions */ uri = fu_plugin_get_config_value(plugin, "UserUri"); if (uri == NULL) { uri = g_strdup("/redfish/v1/AccountService/Accounts/2"); if (!fu_plugin_set_config_value(plugin, "UserUri", uri, error)) return FALSE; } /* now use Redfish to change the temporary password to the actual password */ request = fu_redfish_backend_request_new(self->backend); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Password"); json_builder_add_string_value(builder, password_new); json_builder_end_object(builder); if (!fu_redfish_request_perform_full(request, uri, "PATCH", builder, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON | FU_REDFISH_REQUEST_PERFORM_FLAG_USE_ETAG, error)) return FALSE; fu_redfish_backend_set_password(self->backend, password_new); /* success */ return fu_plugin_set_config_value(plugin, "Password", password_new, error); } static gboolean fu_redfish_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { FuRedfishPlugin *self = FU_REDFISH_PLUGIN(plugin); g_autoptr(GPtrArray) devices = NULL; g_autoptr(GError) error_local = NULL; /* get the list of devices */ if (!fu_backend_coldplug(FU_BACKEND(self->backend), progress, &error_local)) { /* did the user password expire? */ if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_AUTH_EXPIRED)) { if (!fu_redfish_plugin_change_expired(plugin, error)) return FALSE; if (!fu_backend_coldplug(FU_BACKEND(self->backend), progress, error)) { fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_AUTH_REQUIRED); return FALSE; } } else { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } devices = fu_backend_get_devices(FU_BACKEND(self->backend)); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); if (fu_context_has_hwid_flag(fu_plugin_get_context(plugin), "reset-required")) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_plugin_device_add(plugin, device); } /* this is no longer relevant */ if (devices->len > 0) { fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_CONFLICTS, "bios"); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_CONFLICTS, "uefi_capsule"); } return TRUE; } void fu_redfish_plugin_set_credentials(FuPlugin *plugin, const gchar *username, const gchar *password) { FuRedfishPlugin *self = FU_REDFISH_PLUGIN(plugin); fu_redfish_backend_set_username(self->backend, username); fu_redfish_backend_set_password(self->backend, password); } static gboolean fu_redfish_plugin_discover_uefi_credentials(FuPlugin *plugin, GError **error) { FuRedfishPlugin *self = FU_REDFISH_PLUGIN(plugin); gsize bufsz = 0; guint32 indications = 0x0; g_autofree gchar *userpass_safe = NULL; g_autofree guint8 *buf = NULL; g_auto(GStrv) split = NULL; g_autoptr(GBytes) userpass = NULL; /* get the uint32 specifying if there are EFI variables set */ if (!fu_efivar_get_data(REDFISH_EFI_INFORMATION_GUID, REDFISH_EFI_INFORMATION_INDICATIONS, &buf, &bufsz, NULL, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, 0x0, &indications, G_LITTLE_ENDIAN, error)) return FALSE; if ((indications & REDFISH_EFI_INDICATIONS_OS_CREDENTIALS) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no indications for OS credentials"); return FALSE; } /* read the correct EFI var for runtime */ userpass = fu_efivar_get_data_bytes(REDFISH_EFI_INFORMATION_GUID, REDFISH_EFI_INFORMATION_OS_CREDENTIALS, NULL, error); if (userpass == NULL) return FALSE; /* it might not be NUL terminated */ userpass_safe = g_strndup(g_bytes_get_data(userpass, NULL), g_bytes_get_size(userpass)); split = g_strsplit(userpass_safe, ":", -1); if (g_strv_length(split) != 2) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid format for username:password, got '%s'", userpass_safe); return FALSE; } fu_redfish_backend_set_username(self->backend, split[0]); fu_redfish_backend_set_password(self->backend, split[1]); return TRUE; } static gboolean fu_redfish_plugin_discover_smbios_table(FuPlugin *plugin, GError **error) { FuRedfishPlugin *self = FU_REDFISH_PLUGIN(plugin); FuContext *ctx = fu_plugin_get_context(plugin); const gchar *smbios_data_fn; g_autoptr(GPtrArray) type42_tables = NULL; /* in self tests */ smbios_data_fn = g_getenv("FWUPD_REDFISH_SMBIOS_DATA"); if (smbios_data_fn != NULL) { g_autoptr(GBytes) type42_blob = fu_bytes_get_contents(smbios_data_fn, error); g_autoptr(FuRedfishSmbios) smbios = fu_redfish_smbios_new(); if (type42_blob == NULL) return FALSE; if (!fu_firmware_parse(FU_FIRMWARE(smbios), type42_blob, FWUPD_INSTALL_FLAG_NO_SEARCH, error)) { g_prefix_error(error, "failed to parse SMBIOS entry type 42: "); return FALSE; } g_set_object(&self->smbios, smbios); return TRUE; } /* is optional */ type42_tables = fu_context_get_smbios_data(ctx, REDFISH_SMBIOS_TABLE_TYPE, NULL); if (type42_tables == NULL) return TRUE; for (guint i = 0; i < type42_tables->len; i++) { GBytes *type42_blob = g_ptr_array_index(type42_tables, i); g_autoptr(FuRedfishSmbios) smbios = fu_redfish_smbios_new(); if (!fu_firmware_parse(FU_FIRMWARE(smbios), type42_blob, FWUPD_INSTALL_FLAG_NO_SEARCH, error)) { g_prefix_error(error, "failed to parse SMBIOS entry type 42: "); return FALSE; } if (fu_redfish_smbios_get_interface_type(smbios) == FU_REDFISH_SMBIOS_INTERFACE_TYPE_NETWORK) { g_set_object(&self->smbios, smbios); return TRUE; } } /* success */ return TRUE; } static gboolean fu_redfish_plugin_autoconnect_network_device(FuRedfishPlugin *self, GError **error) { g_autofree gchar *hostname = NULL; g_autoptr(FuRedfishNetworkDevice) device = NULL; /* we have no data */ if (self->smbios == NULL) return TRUE; /* get IP, falling back to hostname, then MAC, then VID:PID */ hostname = g_strdup(fu_redfish_smbios_get_ip_addr(self->smbios)); if (hostname == NULL) hostname = g_strdup(fu_redfish_smbios_get_hostname(self->smbios)); if (device == NULL) { const gchar *mac_addr = fu_redfish_smbios_get_mac_addr(self->smbios); if (mac_addr != NULL) { g_autoptr(GError) error_network = NULL; device = fu_redfish_network_device_for_mac_addr(mac_addr, &error_network); if (device == NULL) g_debug("failed to get device: %s", error_network->message); } } if (device == NULL) { guint16 vid = fu_redfish_smbios_get_vid(self->smbios); guint16 pid = fu_redfish_smbios_get_pid(self->smbios); if (vid != 0x0 && pid != 0x0) { g_autoptr(GError) error_network = NULL; device = fu_redfish_network_device_for_vid_pid(vid, pid, &error_network); if (device == NULL) g_debug("failed to get device: %s", error_network->message); } } /* autoconnect device if required */ if (device != NULL) { FuRedfishNetworkDeviceState state = FU_REDFISH_NETWORK_DEVICE_STATE_UNKNOWN; if (!fu_redfish_network_device_get_state(device, &state, error)) return FALSE; g_info("device state is now %s [%u]", fu_redfish_network_device_state_to_string(state), state); if (state == FU_REDFISH_NETWORK_DEVICE_STATE_DISCONNECTED) { if (!fu_redfish_network_device_connect(device, error)) return FALSE; } if (hostname == NULL) { hostname = fu_redfish_network_device_get_address(device, error); if (hostname == NULL) return FALSE; } } if (hostname == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no hostname"); return FALSE; } fu_redfish_backend_set_hostname(self->backend, hostname); fu_redfish_backend_set_port(self->backend, fu_redfish_smbios_get_port(self->smbios)); return TRUE; } #ifdef HAVE_LINUX_IPMI_H static gboolean fu_redfish_plugin_ipmi_create_user(FuPlugin *plugin, GError **error) { FuRedfishPlugin *self = FU_REDFISH_PLUGIN(plugin); const gchar *username_fwupd = "fwupd"; guint8 user_id = G_MAXUINT8; g_autofree gchar *password_new = fu_common_generate_password(15); g_autofree gchar *password_tmp = fu_common_generate_password(15); g_autofree gchar *uri = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuIpmiDevice) device = fu_ipmi_device_new(fu_plugin_get_context(plugin)); g_autoptr(FuRedfishRequest) request = NULL; g_autoptr(JsonBuilder) builder = json_builder_new(); /* create device */ locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; /* check for existing user, and if not then remember the first spare slot */ for (guint8 i = 2; i < 0xFF; i++) { g_autofree gchar *username = fu_ipmi_device_get_user_password(device, i, NULL); if (username == NULL && user_id == G_MAXUINT8) { g_debug("KCS slot %u free", i); user_id = i; continue; } if (g_strcmp0(username, username_fwupd) == 0) { g_debug("%s user exists in KCS slot %u", username, i); user_id = i; break; } } if (user_id == G_MAXUINT8) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "all KCS slots full, cannot create user"); return FALSE; } /* create a user with appropriate permissions */ if (!fu_ipmi_device_set_user_name(device, user_id, username_fwupd, error)) return FALSE; if (!fu_ipmi_device_set_user_enable(device, user_id, TRUE, error)) return FALSE; if (!fu_ipmi_device_set_user_priv(device, user_id, 0x4, 1, error)) return FALSE; if (!fu_ipmi_device_set_user_password(device, user_id, password_tmp, error)) return FALSE; /* OEM specific for Advantech manufacture */ if (fu_context_has_hwid_guid(fu_plugin_get_context(plugin), "18789130-a714-53c0-b025-fa93801d3995")) { if (!fu_redfish_device_set_user_group_redfish_enable_advantech(device, user_id, error)) return FALSE; } fu_redfish_backend_set_username(self->backend, username_fwupd); fu_redfish_backend_set_password(self->backend, password_tmp); /* wait for Redfish to sync */ g_usleep(2 * G_USEC_PER_SEC); /* XCC is the only BMC implementation that does not map the user_ids 1:1 */ if (fu_context_has_hwid_guid(fu_plugin_get_context(plugin), "42f00735-c9ab-5374-bd63-a5deee5881e0")) user_id -= 1; /* now use Redfish to change the temporary password to the actual password */ request = fu_redfish_backend_request_new(self->backend); uri = g_strdup_printf("/redfish/v1/AccountService/Accounts/%u", (guint)user_id); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Password"); json_builder_add_string_value(builder, password_new); json_builder_end_object(builder); if (!fu_redfish_request_perform_full(request, uri, "PATCH", builder, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON | FU_REDFISH_REQUEST_PERFORM_FLAG_USE_ETAG, error)) return FALSE; fu_redfish_backend_set_password(self->backend, password_new); /* success */ if (!fu_plugin_set_config_value(plugin, "UserUri", uri, error)) return FALSE; if (!fu_plugin_set_config_value(plugin, "Username", username_fwupd, error)) return FALSE; if (!fu_plugin_set_config_value(plugin, "Password", password_new, error)) return FALSE; return TRUE; } #endif static gboolean fu_redfish_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { FuRedfishPlugin *self = FU_REDFISH_PLUGIN(plugin); #ifdef HAVE_LINUX_IPMI_H gboolean credentials_invalid = FALSE; #endif g_autofree gchar *password = NULL; g_autofree gchar *redfish_uri = NULL; g_autofree gchar *username = NULL; #ifdef HAVE_LINUX_IPMI_H g_autofree gchar *user_uri = NULL; #endif g_autoptr(GError) error_uefi = NULL; /* optional */ if (!fu_redfish_plugin_discover_smbios_table(plugin, error)) return FALSE; if (!fu_redfish_plugin_autoconnect_network_device(self, error)) return FALSE; if (!fu_redfish_plugin_discover_uefi_credentials(plugin, &error_uefi)) { g_debug("failed to get username and password automatically: %s", error_uefi->message); } /* override with the conf file */ redfish_uri = fu_plugin_get_config_value(plugin, "Uri"); if (redfish_uri != NULL) { const gchar *ip_str = NULL; g_auto(GStrv) split = NULL; guint64 port = 0; if (g_str_has_prefix(redfish_uri, "https://")) { fu_redfish_backend_set_https(self->backend, TRUE); ip_str = redfish_uri + strlen("https://"); port = 443; } else if (g_str_has_prefix(redfish_uri, "http://")) { fu_redfish_backend_set_https(self->backend, FALSE); ip_str = redfish_uri + strlen("http://"); port = 80; } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid scheme"); return FALSE; } split = g_strsplit(ip_str, ":", 2); fu_redfish_backend_set_hostname(self->backend, split[0]); if (g_strv_length(split) > 1) port = g_ascii_strtoull(split[1], NULL, 10); if (port == 0 || port == G_MAXUINT64) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no valid port specified"); return FALSE; } fu_redfish_backend_set_port(self->backend, port); } username = fu_plugin_get_config_value(plugin, "Username"); if (username != NULL) fu_redfish_backend_set_username(self->backend, username); password = fu_plugin_get_config_value(plugin, "Password"); if (password != NULL) fu_redfish_backend_set_password(self->backend, password); fu_redfish_backend_set_cacheck(self->backend, fu_plugin_get_config_value_boolean(plugin, "CACheck")); if (fu_context_has_hwid_flag(fu_plugin_get_context(plugin), "wildcard-targets")) fu_redfish_backend_set_wildcard_targets(self->backend, TRUE); #ifdef HAVE_LINUX_IPMI_H /* test if the existing credentials work */ user_uri = fu_plugin_get_config_value(plugin, "UserUri"); if (username != NULL && password != NULL && user_uri != NULL) { g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(self->backend); g_autoptr(GError) error_local = NULL; if (!fu_redfish_request_perform(request, user_uri, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED)) { credentials_invalid = TRUE; } else { g_propagate_prefixed_error( error, g_steal_pointer(&error_local), "existing username and password did not work: "); return FALSE; } } } /* we got neither a type 42 entry or config value, lets try IPMI */ if (fu_redfish_backend_get_username(self->backend) == NULL || credentials_invalid) { if (!fu_context_has_hwid_flag(fu_plugin_get_context(plugin), "ipmi-create-user")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no username and password specified, " "and no vendor quirk for 'ipmi-create-user'"); return FALSE; } if (!fu_plugin_get_config_value_boolean(plugin, "IpmiDisableCreateUser")) { g_info("attempting to [re-]create user using IPMI"); if (!fu_redfish_plugin_ipmi_create_user(plugin, error)) return FALSE; } } #endif return fu_backend_setup(FU_BACKEND(self->backend), progress, error); } static gboolean fu_redfish_plugin_cleanup_setup_cb(FuDevice *device, gpointer user_data, GError **error) { FuRedfishPlugin *self = FU_REDFISH_PLUGIN(user_data); FuProgress *progress = fu_progress_new(G_STRLOC); /* the network adaptor might not auto-connect when coming back */ if (!fu_redfish_plugin_autoconnect_network_device(self, error)) return FALSE; return fu_backend_setup(FU_BACKEND(self->backend), progress, error); } static gboolean fu_redfish_plugin_cleanup_coldplug_cb(FuDevice *device, gpointer user_data, GError **error) { FuRedfishPlugin *self = FU_REDFISH_PLUGIN(user_data); FuProgress *progress = fu_progress_new(G_STRLOC); if (!fu_redfish_plugin_autoconnect_network_device(self, error)) return FALSE; return fu_redfish_plugin_coldplug(FU_PLUGIN(self), progress, error); } static gboolean fu_redfish_plugin_cleanup(FuPlugin *plugin, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuRedfishPlugin *self = FU_REDFISH_PLUGIN(plugin); guint64 reset_timeout = 0; g_autofree gchar *restart_timeout_str = NULL; g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(self->backend); g_autoptr(GPtrArray) devices = NULL; /* nothing to do */ if (!fu_device_has_private_flag(device, FU_REDFISH_DEVICE_FLAG_MANAGER_RESET)) return TRUE; /* update failed; don't reboot BMC */ if (fu_device_get_update_state(device) == FWUPD_UPDATE_STATE_FAILED) return TRUE; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "manager-reboot"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "pre-delay"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 67, "poll-manager"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 18, "post-delay"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 9, "recoldplug"); /* ask the BMC to reboot */ if (!fu_device_has_private_flag(device, FU_REDFISH_DEVICE_FLAG_NO_MANAGER_RESET_REQUEST)) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "ResetType"); json_builder_add_string_value(builder, "ForceRestart"); json_builder_end_object(builder); if (!fu_redfish_request_perform_full(request, "/redfish/v1/Managers/1/Actions/Manager.Reset", "POST", builder, FU_REDFISH_REQUEST_PERFORM_FLAG_NONE, error)) { g_prefix_error(error, "failed to reset manager: "); return FALSE; } } fu_progress_step_done(progress); /* remove all the devices */ devices = fu_backend_get_devices(FU_BACKEND(self->backend)); for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); fu_backend_device_removed(FU_BACKEND(self->backend), device_tmp); } /* work around manager bugs... */ fu_backend_invalidate(FU_BACKEND(self->backend)); fu_device_sleep_full(device, fu_redfish_device_get_reset_pre_delay(FU_REDFISH_DEVICE(device)), fu_progress_get_child(progress)); fu_progress_step_done(progress); /* read the config file to work out how long to wait */ restart_timeout_str = fu_plugin_get_config_value(plugin, "ManagerResetTimeout"); if (!fu_strtoull(restart_timeout_str, &reset_timeout, 1, 86400, error)) return FALSE; /* wait for the BMC to come back */ if (!fu_device_retry_full(device, fu_redfish_plugin_cleanup_setup_cb, reset_timeout / FU_REDFISH_PLUGIN_CLEANUP_RETRIES_DELAY, FU_REDFISH_PLUGIN_CLEANUP_RETRIES_DELAY * 1000, self, error)) { g_prefix_error(error, "manager failed to come back from setup: "); return FALSE; } fu_progress_step_done(progress); /* work around manager bugs... */ fu_device_sleep_full(device, fu_redfish_device_get_reset_post_delay(FU_REDFISH_DEVICE(device)), fu_progress_get_child(progress)); fu_progress_step_done(progress); /* get the new list of devices */ if (!fu_device_retry_full(device, fu_redfish_plugin_cleanup_coldplug_cb, reset_timeout / FU_REDFISH_PLUGIN_CLEANUP_RETRIES_DELAY, FU_REDFISH_PLUGIN_CLEANUP_RETRIES_DELAY * 1000, self, error)) { g_prefix_error(error, "manager failed to come back from coldplug: "); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_redfish_plugin_init(FuRedfishPlugin *self) { } static void fu_redfish_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); FuRedfishPlugin *self = FU_REDFISH_PLUGIN(plugin); fu_context_add_quirk_key(ctx, "RedfishResetPreDelay"); fu_context_add_quirk_key(ctx, "RedfishResetPostDelay"); self->backend = fu_redfish_backend_new(ctx); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_REDFISH_SMBIOS); fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_SECURE_CONFIG); /* defaults changed here will also be reflected in the fwupd.conf man page */ fu_plugin_set_config_default(plugin, "CACheck", "false"); fu_plugin_set_config_default(plugin, "IpmiDisableCreateUser", "false"); fu_plugin_set_config_default(plugin, "ManagerResetTimeout", "1800"); /* seconds */ fu_plugin_set_config_default(plugin, "Password", NULL); fu_plugin_set_config_default(plugin, "Uri", NULL); fu_plugin_set_config_default(plugin, "Username", NULL); fu_plugin_set_config_default(plugin, "UserUri", NULL); } static void fu_redfish_finalize(GObject *obj) { FuRedfishPlugin *self = FU_REDFISH_PLUGIN(obj); if (self->smbios != NULL) g_object_unref(self->smbios); if (self->backend != NULL) g_object_unref(self->backend); G_OBJECT_CLASS(fu_redfish_plugin_parent_class)->finalize(obj); } static void fu_redfish_plugin_class_init(FuRedfishPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_redfish_finalize; plugin_class->constructed = fu_redfish_plugin_constructed; plugin_class->to_string = fu_redfish_plugin_to_string; plugin_class->startup = fu_redfish_plugin_startup; plugin_class->coldplug = fu_redfish_plugin_coldplug; plugin_class->cleanup = fu_redfish_plugin_cleanup; } fwupd-1.9.16/plugins/redfish/fu-redfish-plugin.h000066400000000000000000000005241460375044200215510ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuRedfishPlugin, fu_redfish_plugin, FU, REDFISH_PLUGIN, FuPlugin) void fu_redfish_plugin_set_credentials(FuPlugin *plugin, const gchar *username, const gchar *password); fwupd-1.9.16/plugins/redfish/fu-redfish-request.c000066400000000000000000000233361460375044200217440ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-redfish-request.h" struct _FuRedfishRequest { GObject parent_instance; CURL *curl; CURLU *uri; GByteArray *buf; glong status_code; JsonParser *json_parser; JsonObject *json_obj; GHashTable *cache; /* nullable */ }; G_DEFINE_TYPE(FuRedfishRequest, fu_redfish_request, G_TYPE_OBJECT) typedef gchar curlptr; G_DEFINE_AUTOPTR_CLEANUP_FUNC(curlptr, curl_free) JsonObject * fu_redfish_request_get_json_object(FuRedfishRequest *self) { g_return_val_if_fail(FU_IS_REDFISH_REQUEST(self), NULL); return self->json_obj; } CURL * fu_redfish_request_get_curl(FuRedfishRequest *self) { g_return_val_if_fail(FU_IS_REDFISH_REQUEST(self), NULL); return self->curl; } CURLU * fu_redfish_request_get_uri(FuRedfishRequest *self) { g_return_val_if_fail(FU_IS_REDFISH_REQUEST(self), NULL); return self->uri; } glong fu_redfish_request_get_status_code(FuRedfishRequest *self) { g_return_val_if_fail(FU_IS_REDFISH_REQUEST(self), G_MAXLONG); return self->status_code; } static gboolean fu_redfish_request_load_json(FuRedfishRequest *self, GByteArray *buf, GError **error) { JsonNode *json_root; g_autoptr(GString) str = g_string_new(NULL); g_autoptr(JsonGenerator) json_generator = json_generator_new(); /* load */ if (buf->data == NULL || buf->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "there was no JSON payload"); return FALSE; } if (!json_parser_load_from_data(self->json_parser, (const gchar *)buf->data, (gssize)buf->len, error)) { return FALSE; } json_root = json_parser_get_root(self->json_parser); if (json_root == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no JSON root node"); return FALSE; } self->json_obj = json_node_get_object(json_root); if (self->json_obj == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no JSON object"); return FALSE; } /* dump for humans */ json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); json_generator_to_gstring(json_generator, str); g_debug("response: %s", str->str); /* unauthorized */ if (json_object_has_member(self->json_obj, "error")) { FwupdError error_code = FWUPD_ERROR_INTERNAL; JsonObject *json_error; const gchar *id = NULL; const gchar *msg = "Unknown failure"; /* extended error present */ json_error = json_object_get_object_member(self->json_obj, "error"); if (json_object_has_member(json_error, "@Message.ExtendedInfo")) { JsonArray *json_error_array; json_error_array = json_object_get_array_member(json_error, "@Message.ExtendedInfo"); if (json_array_get_length(json_error_array) > 0) { JsonObject *json_error2; json_error2 = json_array_get_object_element(json_error_array, 0); if (json_object_has_member(json_error2, "MessageId")) id = json_object_get_string_member(json_error2, "MessageId"); if (json_object_has_member(json_error2, "Message")) msg = json_object_get_string_member(json_error2, "Message"); } } else { if (json_object_has_member(json_error, "code")) id = json_object_get_string_member(json_error, "code"); if (json_object_has_member(json_error, "message")) msg = json_object_get_string_member(json_error, "message"); } if (g_strcmp0(id, "Base.1.8.AccessDenied") == 0) error_code = FWUPD_ERROR_AUTH_FAILED; else if (g_strcmp0(id, "Base.1.8.PasswordChangeRequired") == 0) error_code = FWUPD_ERROR_AUTH_EXPIRED; else if (g_strcmp0(id, "SMC.1.0.OemLicenseNotPassed") == 0) error_code = FWUPD_ERROR_NOT_SUPPORTED; else if (g_strcmp0(id, "SMC.1.0.OemFirmwareAlreadyInUpdateMode") == 0 || g_strcmp0(id, "SMC.1.0.OemBiosUpdateIsInProgress") == 0) error_code = FWUPD_ERROR_ALREADY_PENDING; g_set_error_literal(error, FWUPD_ERROR, error_code, msg); return FALSE; } /* success */ return TRUE; } gboolean fu_redfish_request_perform(FuRedfishRequest *self, const gchar *path, FuRedfishRequestPerformFlags flags, GError **error) { CURLcode res; g_autofree gchar *str = NULL; g_autoptr(curlptr) uri_str = NULL; g_return_val_if_fail(FU_IS_REDFISH_REQUEST(self), FALSE); g_return_val_if_fail(path != NULL, FALSE); g_return_val_if_fail(self->status_code == 0, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* already in cache? */ if (flags & FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE && self->cache != NULL) { GByteArray *buf = g_hash_table_lookup(self->cache, path); if (buf != NULL) { if (flags & FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON) return fu_redfish_request_load_json(self, buf, error); g_byte_array_unref(self->buf); self->buf = g_byte_array_ref(buf); return TRUE; } } /* do request */ (void)curl_url_set(self->uri, CURLUPART_PATH, path, 0); (void)curl_url_get(self->uri, CURLUPART_URL, &uri_str, 0); res = curl_easy_perform(self->curl); curl_easy_getinfo(self->curl, CURLINFO_RESPONSE_CODE, &self->status_code); str = g_strndup((const gchar *)self->buf->data, self->buf->len); g_debug("%s: %s [%li]", uri_str, str, self->status_code); /* check result */ if (res != CURLE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to request %s: %s", uri_str, curl_easy_strerror(res)); return FALSE; } /* invalid user */ if (fu_redfish_request_get_status_code(self) == 401) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "authentication failed"); return FALSE; } /* load JSON */ if (flags & FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON) { if (!fu_redfish_request_load_json(self, self->buf, error)) { g_prefix_error(error, "failed to parse %s: ", uri_str); return FALSE; } } /* save to cache */ if (self->cache != NULL) g_hash_table_insert(self->cache, g_strdup(path), g_byte_array_ref(self->buf)); /* success */ return TRUE; } typedef struct curl_slist _curl_slist; G_DEFINE_AUTOPTR_CLEANUP_FUNC(_curl_slist, curl_slist_free_all) static void fu_redfish_request_reset(FuRedfishRequest *self) { self->status_code = 0; self->json_obj = NULL; } gboolean fu_redfish_request_perform_full(FuRedfishRequest *self, const gchar *path, const gchar *request, JsonBuilder *builder, FuRedfishRequestPerformFlags flags, GError **error) { g_autofree gchar *etag_header = NULL; g_autoptr(_curl_slist) hs = NULL; g_autoptr(GString) str = g_string_new(NULL); g_autoptr(JsonGenerator) json_generator = json_generator_new(); g_autoptr(JsonNode) json_root = NULL; g_return_val_if_fail(FU_IS_REDFISH_REQUEST(self), FALSE); g_return_val_if_fail(path != NULL, FALSE); g_return_val_if_fail(request != NULL, FALSE); g_return_val_if_fail(builder != NULL, FALSE); /* get etag */ if (flags & FU_REDFISH_REQUEST_PERFORM_FLAG_USE_ETAG) { JsonObject *json_obj; if (!fu_redfish_request_perform(self, path, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, error)) { g_prefix_error(error, "failed to request etag: "); return FALSE; } json_obj = fu_redfish_request_get_json_object(self); if (json_object_has_member(json_obj, "@odata.etag")) { etag_header = g_strdup_printf("If-Match: %s", json_object_get_string_member(json_obj, "@odata.etag")); } /* allow us to re-use the request */ fu_redfish_request_reset(self); } /* export as a string */ json_root = json_builder_get_root(builder); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); json_generator_to_gstring(json_generator, str); g_debug("request to %s: %s", path, str->str); /* patch */ (void)curl_easy_setopt(self->curl, CURLOPT_CUSTOMREQUEST, request); (void)curl_easy_setopt(self->curl, CURLOPT_POSTFIELDS, str->str); (void)curl_easy_setopt(self->curl, CURLOPT_POSTFIELDSIZE, (long)str->len); hs = curl_slist_append(hs, "Content-Type: application/json"); if (etag_header != NULL) hs = curl_slist_append(hs, etag_header); (void)curl_easy_setopt(self->curl, CURLOPT_HTTPHEADER, hs); return fu_redfish_request_perform(self, path, flags, error); } static size_t fu_redfish_request_write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) { GByteArray *buf = (GByteArray *)userdata; gsize realsize = size * nmemb; g_byte_array_append(buf, (const guint8 *)ptr, realsize); return realsize; } void fu_redfish_request_set_cache(FuRedfishRequest *self, GHashTable *cache) { g_return_if_fail(FU_IS_REDFISH_REQUEST(self)); g_return_if_fail(cache != NULL); g_return_if_fail(self->cache == NULL); self->cache = g_hash_table_ref(cache); } void fu_redfish_request_set_curlsh(FuRedfishRequest *self, CURLSH *curlsh) { g_return_if_fail(FU_IS_REDFISH_REQUEST(self)); g_return_if_fail(curlsh != NULL); (void)curl_easy_setopt(self->curl, CURLOPT_SHARE, curlsh); } static void fu_redfish_request_init(FuRedfishRequest *self) { self->curl = curl_easy_init(); self->uri = curl_url(); self->buf = g_byte_array_new(); self->json_parser = json_parser_new(); (void)curl_easy_setopt(self->curl, CURLOPT_WRITEFUNCTION, fu_redfish_request_write_cb); (void)curl_easy_setopt(self->curl, CURLOPT_WRITEDATA, self->buf); } static void fu_redfish_request_finalize(GObject *object) { FuRedfishRequest *self = FU_REDFISH_REQUEST(object); if (self->cache != NULL) g_hash_table_unref(self->cache); g_object_unref(self->json_parser); g_byte_array_unref(self->buf); curl_easy_cleanup(self->curl); curl_url_cleanup(self->uri); G_OBJECT_CLASS(fu_redfish_request_parent_class)->finalize(object); } static void fu_redfish_request_class_init(FuRedfishRequestClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_redfish_request_finalize; } fwupd-1.9.16/plugins/redfish/fu-redfish-request.h000066400000000000000000000024731460375044200217500ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #define FU_TYPE_REDFISH_REQUEST (fu_redfish_request_get_type()) G_DECLARE_FINAL_TYPE(FuRedfishRequest, fu_redfish_request, FU, REDFISH_REQUEST, GObject) typedef enum { FU_REDFISH_REQUEST_PERFORM_FLAG_NONE = 0, FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON = 1 << 0, FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE = 1 << 1, FU_REDFISH_REQUEST_PERFORM_FLAG_USE_ETAG = 1 << 2, } FuRedfishRequestPerformFlags; gboolean fu_redfish_request_perform(FuRedfishRequest *self, const gchar *path, FuRedfishRequestPerformFlags flags, GError **error); gboolean fu_redfish_request_perform_full(FuRedfishRequest *self, const gchar *path, const gchar *request, JsonBuilder *builder, FuRedfishRequestPerformFlags flags, GError **error); JsonObject * fu_redfish_request_get_json_object(FuRedfishRequest *self); CURL * fu_redfish_request_get_curl(FuRedfishRequest *self); void fu_redfish_request_set_curlsh(FuRedfishRequest *self, CURLSH *curlsh); CURLU * fu_redfish_request_get_uri(FuRedfishRequest *self); glong fu_redfish_request_get_status_code(FuRedfishRequest *self); void fu_redfish_request_set_cache(FuRedfishRequest *self, GHashTable *cache); fwupd-1.9.16/plugins/redfish/fu-redfish-smbios.c000066400000000000000000000275311460375044200215510ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-redfish-common.h" #include "fu-redfish-smbios.h" #include "fu-redfish-struct.h" struct _FuRedfishSmbios { FuFirmwareClass parent_instance; FuRedfishSmbiosInterfaceType interface_type; guint16 port; gchar *hostname; gchar *mac_addr; gchar *ip_addr; guint16 vid; guint16 pid; }; G_DEFINE_TYPE(FuRedfishSmbios, fu_redfish_smbios, FU_TYPE_FIRMWARE) FuRedfishSmbiosInterfaceType fu_redfish_smbios_get_interface_type(FuRedfishSmbios *self) { return self->interface_type; } guint16 fu_redfish_smbios_get_port(FuRedfishSmbios *self) { return self->port; } guint16 fu_redfish_smbios_get_vid(FuRedfishSmbios *self) { return self->vid; } guint16 fu_redfish_smbios_get_pid(FuRedfishSmbios *self) { return self->pid; } const gchar * fu_redfish_smbios_get_hostname(FuRedfishSmbios *self) { return self->hostname; } const gchar * fu_redfish_smbios_get_mac_addr(FuRedfishSmbios *self) { return self->mac_addr; } const gchar * fu_redfish_smbios_get_ip_addr(FuRedfishSmbios *self) { return self->ip_addr; } static void fu_redfish_smbios_set_hostname(FuRedfishSmbios *self, const gchar *hostname) { g_free(self->hostname); self->hostname = g_strdup(hostname); } static void fu_redfish_smbios_set_mac_addr(FuRedfishSmbios *self, const gchar *mac_addr) { g_free(self->mac_addr); self->mac_addr = g_strdup(mac_addr); } static void fu_redfish_smbios_set_ip_addr(FuRedfishSmbios *self, const gchar *ip_addr) { g_free(self->ip_addr); self->ip_addr = g_strdup(ip_addr); } static void fu_redfish_smbios_set_port(FuRedfishSmbios *self, guint16 port) { self->port = port; } static void fu_redfish_smbios_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuRedfishSmbios *self = FU_REDFISH_SMBIOS(firmware); fu_xmlb_builder_insert_kv(bn, "interface_type", fu_redfish_smbios_interface_type_to_string(self->interface_type)); fu_xmlb_builder_insert_kx(bn, "port", self->port); fu_xmlb_builder_insert_kv(bn, "hostname", self->hostname); fu_xmlb_builder_insert_kv(bn, "mac_addr", self->mac_addr); fu_xmlb_builder_insert_kv(bn, "ip_addr", self->ip_addr); fu_xmlb_builder_insert_kx(bn, "vid", self->vid); fu_xmlb_builder_insert_kx(bn, "pid", self->pid); } static gboolean fu_redfish_smbios_build(FuFirmware *firmware, XbNode *n, GError **error) { FuRedfishSmbios *self = FU_REDFISH_SMBIOS(firmware); const gchar *tmp; guint64 tmpu; /* optional properties */ tmpu = xb_node_query_text_as_uint(n, "port", NULL); if (tmpu != G_MAXUINT64) fu_redfish_smbios_set_port(self, (guint16)tmpu); tmpu = xb_node_query_text_as_uint(n, "vid", NULL); if (tmpu != G_MAXUINT64) self->vid = (guint16)tmpu; tmpu = xb_node_query_text_as_uint(n, "pid", NULL); if (tmpu != G_MAXUINT64) self->pid = (guint16)tmpu; tmp = xb_node_query_text(n, "hostname", NULL); if (tmp != NULL) fu_redfish_smbios_set_hostname(self, tmp); tmp = xb_node_query_text(n, "mac_addr", NULL); if (tmp != NULL) fu_redfish_smbios_set_mac_addr(self, tmp); tmp = xb_node_query_text(n, "ip_addr", NULL); if (tmp != NULL) fu_redfish_smbios_set_ip_addr(self, tmp); /* success */ return TRUE; } static gboolean fu_redfish_smbios_parse_interface_data(FuRedfishSmbios *self, GBytes *fw, gsize offset, GError **error) { gsize bufsz = 0; gsize offset_mac_addr = G_MAXSIZE; gsize offset_vid_pid = G_MAXSIZE; guint8 interface_type = 0x0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* parse the data depending on the interface type */ if (!fu_memread_uint8_safe(buf, bufsz, offset, &interface_type, error)) return FALSE; g_debug("interface_type: %s [0x%x]", fu_redfish_interface_type_to_string(interface_type), interface_type); offset++; switch (interface_type) { case FU_REDFISH_INTERFACE_TYPE_USB_NETWORK: case FU_REDFISH_INTERFACE_TYPE_PCI_NETWORK: offset_vid_pid = 0x00; break; case FU_REDFISH_INTERFACE_TYPE_USB_NETWORK_V2: offset_vid_pid = 0x01; offset_mac_addr = 0x06; break; case FU_REDFISH_INTERFACE_TYPE_PCI_NETWORK_V2: offset_vid_pid = 0x01; offset_mac_addr = 0x09; break; default: g_debug("unknown Network Interface"); break; } /* MAC address */ if (offset_mac_addr != G_MAXSIZE) { guint8 mac_addr[6] = {0x0}; g_autofree gchar *mac_addr_str = NULL; if (!fu_memcpy_safe(mac_addr, sizeof(mac_addr), 0x0, /* dst */ buf, bufsz, offset + offset_mac_addr, /* src */ sizeof(mac_addr), error)) return FALSE; mac_addr_str = fu_redfish_common_buffer_to_mac(mac_addr); fu_redfish_smbios_set_mac_addr(self, mac_addr_str); } /* VID:PID */ if (offset_vid_pid != G_MAXSIZE) { if (!fu_memread_uint16_safe(buf, bufsz, offset + offset_vid_pid, &self->vid, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(buf, bufsz, offset + offset_vid_pid + 0x02, &self->pid, G_LITTLE_ENDIAN, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_redfish_smbios_parse_over_ip(FuRedfishSmbios *self, GBytes *fw, gsize offset, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); guint8 hostname_length; guint8 service_ip_address_format; g_autoptr(GByteArray) st = NULL; /* port + IP address */ st = fu_struct_redfish_protocol_over_ip_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; fu_redfish_smbios_set_port(self, fu_struct_redfish_protocol_over_ip_get_service_ip_port(st)); service_ip_address_format = fu_struct_redfish_protocol_over_ip_get_service_ip_address_format(st); if (service_ip_address_format == FU_REDFISH_IP_ADDRESS_FORMAT_V4) { const guint8 *ip_address = fu_struct_redfish_protocol_over_ip_get_service_ip_address(st, NULL); g_autofree gchar *tmp = fu_redfish_common_buffer_to_ipv4(ip_address); fu_redfish_smbios_set_ip_addr(self, tmp); } else if (service_ip_address_format == FU_REDFISH_IP_ADDRESS_FORMAT_V6) { const guint8 *ip_address = fu_struct_redfish_protocol_over_ip_get_service_ip_address(st, NULL); g_autofree gchar *tmp = fu_redfish_common_buffer_to_ipv6(ip_address); fu_redfish_smbios_set_ip_addr(self, tmp); } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "address format is invalid"); return FALSE; } /* hostname */ hostname_length = fu_struct_redfish_protocol_over_ip_get_service_hostname_len(st); if (hostname_length > 0) { g_autofree gchar *hostname = g_malloc0(hostname_length + 1); if (!fu_memcpy_safe((guint8 *)hostname, hostname_length, 0x0, /* dst */ buf, bufsz, offset + st->len, /* src */ hostname_length, error)) return FALSE; fu_redfish_smbios_set_hostname(self, hostname); } /* success */ return TRUE; } static gboolean fu_redfish_smbios_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuRedfishSmbios *self = FU_REDFISH_SMBIOS(firmware); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GByteArray) st = NULL; /* check size */ if (bufsz < 0x09 + offset) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "SMBIOS entry too small: %" G_GSIZE_FORMAT, bufsz); return FALSE; } /* parse */ st = fu_struct_redfish_smbios_type42_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; /* check length */ if (fu_struct_redfish_smbios_type42_get_length(st) != bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "size of table 0x%x does not match binary 0x%x", fu_struct_redfish_smbios_type42_get_length(st), (guint)bufsz); return FALSE; } /* check length */ offset += FU_STRUCT_REDFISH_SMBIOS_TYPE42_SIZE; if (fu_struct_redfish_smbios_type42_get_data_length(st) > 0) { if (!fu_redfish_smbios_parse_interface_data(self, fw, offset, error)) return FALSE; } offset += fu_struct_redfish_smbios_type42_get_data_length(st); /* parse protocol records */ self->interface_type = fu_struct_redfish_smbios_type42_get_interface_type(st); if (self->interface_type == FU_REDFISH_SMBIOS_INTERFACE_TYPE_NETWORK) { guint8 protocol_rcds = 0; if (!fu_memread_uint8_safe(buf, bufsz, offset, &protocol_rcds, error)) return FALSE; offset += 1; g_debug("protocol_rcds: %u", protocol_rcds); for (guint i = 0; i < protocol_rcds; i++) { guint8 protocol_id = 0; guint8 protocol_sz = 0; if (!fu_memread_uint8_safe(buf, bufsz, offset, &protocol_id, error)) return FALSE; if (!fu_memread_uint8_safe(buf, bufsz, offset + 0x1, &protocol_sz, error)) return FALSE; if (protocol_id == REDFISH_PROTOCOL_REDFISH_OVER_IP) { if (!fu_redfish_smbios_parse_over_ip(self, fw, offset + 0x2, error)) return FALSE; } else { g_debug("ignoring protocol ID 0x%02x", protocol_id); } offset += protocol_sz + 1; } } /* success */ return TRUE; } static GByteArray * fu_redfish_smbios_write(FuFirmware *firmware, GError **error) { FuRedfishSmbios *self = FU_REDFISH_SMBIOS(firmware); gsize hostname_sz = 0; g_autoptr(GByteArray) st = fu_struct_redfish_protocol_over_ip_new(); g_autoptr(GByteArray) buf = g_byte_array_new(); if (self->hostname != NULL) hostname_sz = strlen(self->hostname); fu_byte_array_append_uint8(buf, REDFISH_SMBIOS_TABLE_TYPE); fu_byte_array_append_uint8(buf, 0x6D + hostname_sz); /* length */ fu_byte_array_append_uint16(buf, 0x1234, G_LITTLE_ENDIAN); /* handle */ fu_byte_array_append_uint8(buf, FU_REDFISH_SMBIOS_INTERFACE_TYPE_NETWORK); fu_byte_array_append_uint8(buf, 0x09); /* iface datalen */ fu_byte_array_append_uint8(buf, FU_REDFISH_INTERFACE_TYPE_USB_NETWORK); /* iface */ fu_byte_array_append_uint16(buf, self->vid, G_LITTLE_ENDIAN); /* iface:VID */ fu_byte_array_append_uint16(buf, self->pid, G_LITTLE_ENDIAN); /* iface:PID */ fu_byte_array_append_uint8(buf, 0x02); /* iface:serialsz */ fu_byte_array_append_uint8(buf, 0x03); /* iType */ fu_byte_array_append_uint8(buf, 'S'); /* iface:serial */ fu_byte_array_append_uint8(buf, 'n'); /* iface:serial */ fu_byte_array_append_uint8(buf, 0x1); /* nr protocol rcds */ /* protocol record */ fu_byte_array_append_uint8(buf, REDFISH_PROTOCOL_REDFISH_OVER_IP); fu_byte_array_append_uint8(buf, st->len + hostname_sz); if (self->hostname != NULL) hostname_sz = strlen(self->hostname); fu_struct_redfish_protocol_over_ip_set_service_ip_port(st, self->port); fu_struct_redfish_protocol_over_ip_set_service_ip_address_format( st, FU_REDFISH_IP_ADDRESS_FORMAT_V4); fu_struct_redfish_protocol_over_ip_set_service_ip_assignment_type( st, FU_REDFISH_IP_ASSIGNMENT_TYPE_STATIC); fu_struct_redfish_protocol_over_ip_set_service_hostname_len(st, hostname_sz); g_byte_array_append(buf, st->data, st->len); if (hostname_sz > 0) g_byte_array_append(buf, (guint8 *)self->hostname, hostname_sz); return g_steal_pointer(&buf); } static void fu_redfish_smbios_finalize(GObject *object) { FuRedfishSmbios *self = FU_REDFISH_SMBIOS(object); g_free(self->hostname); g_free(self->mac_addr); g_free(self->ip_addr); G_OBJECT_CLASS(fu_redfish_smbios_parent_class)->finalize(object); } static void fu_redfish_smbios_init(FuRedfishSmbios *self) { } static void fu_redfish_smbios_class_init(FuRedfishSmbiosClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_redfish_smbios_finalize; klass_firmware->parse = fu_redfish_smbios_parse; klass_firmware->write = fu_redfish_smbios_write; klass_firmware->build = fu_redfish_smbios_build; klass_firmware->export = fu_redfish_smbios_export; } FuRedfishSmbios * fu_redfish_smbios_new(void) { return FU_REDFISH_SMBIOS(g_object_new(FU_TYPE_REDFISH_SMBIOS, NULL)); } fwupd-1.9.16/plugins/redfish/fu-redfish-smbios.h000066400000000000000000000015231460375044200215470ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-redfish-struct.h" #define FU_TYPE_REDFISH_SMBIOS (fu_redfish_smbios_get_type()) G_DECLARE_FINAL_TYPE(FuRedfishSmbios, fu_redfish_smbios, FU, REDFISH_SMBIOS, FuFirmware) FuRedfishSmbios * fu_redfish_smbios_new(void); FuRedfishSmbiosInterfaceType fu_redfish_smbios_get_interface_type(FuRedfishSmbios *self); guint16 fu_redfish_smbios_get_port(FuRedfishSmbios *self); guint16 fu_redfish_smbios_get_vid(FuRedfishSmbios *self); guint16 fu_redfish_smbios_get_pid(FuRedfishSmbios *self); const gchar * fu_redfish_smbios_get_hostname(FuRedfishSmbios *self); const gchar * fu_redfish_smbios_get_mac_addr(FuRedfishSmbios *self); const gchar * fu_redfish_smbios_get_ip_addr(FuRedfishSmbios *self); fwupd-1.9.16/plugins/redfish/fu-redfish-smc-device.c000066400000000000000000000177361460375044200223020ustar00rootroot00000000000000/* * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-redfish-backend.h" #include "fu-redfish-common.h" #include "fu-redfish-smc-device.h" struct _FuRedfishSmcDevice { FuRedfishDevice parent_instance; }; G_DEFINE_TYPE(FuRedfishSmcDevice, fu_redfish_smc_device, FU_TYPE_REDFISH_DEVICE) G_DEFINE_AUTOPTR_CLEANUP_FUNC(curl_mime, curl_mime_free) static const gchar * fu_redfish_smc_device_get_task(JsonObject *json_obj) { JsonObject *tmp_obj; JsonArray *tmp_ary; const gchar *msgid; if (!json_object_has_member(json_obj, "Accepted")) return NULL; tmp_obj = json_object_get_object_member(json_obj, "Accepted"); if (tmp_obj == NULL || !json_object_has_member(tmp_obj, "@Message.ExtendedInfo")) return NULL; tmp_ary = json_object_get_array_member(tmp_obj, "@Message.ExtendedInfo"); if (tmp_ary == NULL || json_array_get_length(tmp_ary) != 1) return NULL; tmp_obj = json_array_get_object_element(tmp_ary, 0); if (tmp_obj == NULL) return NULL; msgid = json_object_get_string_member(tmp_obj, "MessageId"); if (g_strcmp0(msgid, "SMC.1.0.OemSimpleupdateAcceptedMessage") != 0) return NULL; tmp_ary = json_object_get_array_member(tmp_obj, "MessageArgs"); if (tmp_ary == NULL) return NULL; if (json_array_get_length(tmp_ary) != 1) return NULL; return json_array_get_string_element(tmp_ary, 0); } static GString * fu_redfish_smc_device_get_parameters(FuRedfishSmcDevice *self) { g_autoptr(GString) str = g_string_new(NULL); g_autoptr(JsonBuilder) builder = json_builder_new(); g_autoptr(JsonGenerator) json_generator = json_generator_new(); g_autoptr(JsonNode) json_root = NULL; /* create header */ /* https://supermicro.com/manuals/other/RedishRefGuide.pdf */ json_builder_begin_object(builder); json_builder_set_member_name(builder, "Targets"); json_builder_begin_array(builder); json_builder_add_string_value(builder, "/redfish/v1/Systems/1/Bios"); json_builder_end_array(builder); json_builder_set_member_name(builder, "@Redfish.OperationApplyTime"); json_builder_add_string_value(builder, "OnStartUpdateRequest"); json_builder_set_member_name(builder, "Oem"); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Supermicro"); json_builder_begin_object(builder); json_builder_set_member_name(builder, "BIOS"); json_builder_begin_object(builder); json_builder_set_member_name(builder, "PreserveME"); json_builder_add_boolean_value(builder, TRUE); json_builder_set_member_name(builder, "PreserveNVRAM"); json_builder_add_boolean_value(builder, TRUE); json_builder_set_member_name(builder, "PreserveSMBIOS"); json_builder_add_boolean_value(builder, TRUE); json_builder_set_member_name(builder, "BackupBIOS"); json_builder_add_boolean_value(builder, FALSE); json_builder_end_object(builder); json_builder_end_object(builder); json_builder_end_object(builder); json_builder_end_object(builder); /* export as a string */ json_root = json_builder_get_root(builder); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); json_generator_to_gstring(json_generator, str); return g_steal_pointer(&str); } static gboolean fu_redfish_smc_device_start_update(FuDevice *device, FuProgress *progress, GError **error) { FuRedfishBackend *backend = fu_redfish_device_get_backend(FU_REDFISH_DEVICE(device)); JsonObject *json_obj; CURL *curl; const gchar *location = NULL; g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(backend); curl = fu_redfish_request_get_curl(request); (void)curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ""); if (!fu_redfish_request_perform( request, "/redfish/v1/UpdateService/Actions/UpdateService.StartUpdate", FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, error)) { if (g_error_matches(*error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_UPDATE_PENDING); } return FALSE; } json_obj = fu_redfish_request_get_json_object(request); location = fu_redfish_smc_device_get_task(json_obj); if (location == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no task returned for %s", fu_redfish_backend_get_push_uri_path(backend)); return FALSE; } return fu_redfish_device_poll_task(FU_REDFISH_DEVICE(device), location, progress, error); } static gboolean fu_redfish_smc_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuRedfishSmcDevice *self = FU_REDFISH_SMC_DEVICE(device); FuRedfishBackend *backend = fu_redfish_device_get_backend(FU_REDFISH_DEVICE(self)); CURL *curl; JsonObject *json_obj; curl_mimepart *part; const gchar *location = NULL; gboolean ret; g_autoptr(curl_mime) mime = NULL; g_autoptr(FuRedfishRequest) request = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GString) params = NULL; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 50, "apply"); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* create the multipart for uploading the image request */ request = fu_redfish_backend_request_new(backend); curl = fu_redfish_request_get_curl(request); mime = curl_mime_init(curl); (void)curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime); params = fu_redfish_smc_device_get_parameters(self); part = curl_mime_addpart(mime); curl_mime_name(part, "UpdateParameters"); (void)curl_mime_type(part, "application/json"); (void)curl_mime_data(part, params->str, CURL_ZERO_TERMINATED); g_debug("request: %s", params->str); part = curl_mime_addpart(mime); curl_mime_name(part, "UpdateFile"); (void)curl_mime_type(part, "application/octet-stream"); (void)curl_mime_filedata(part, "firmware.bin"); (void)curl_mime_data(part, g_bytes_get_data(fw, NULL), g_bytes_get_size(fw)); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); if (!fu_redfish_request_perform(request, fu_redfish_backend_get_push_uri_path(backend), FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON, error)) { if (g_error_matches(*error, FWUPD_ERROR, FWUPD_ERROR_ALREADY_PENDING)) { fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_UPDATE_PENDING); } return FALSE; } if (fu_redfish_request_get_status_code(request) != 202) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to upload: %li", fu_redfish_request_get_status_code(request)); return FALSE; } json_obj = fu_redfish_request_get_json_object(request); /* poll the verify task for progress */ location = fu_redfish_smc_device_get_task(json_obj); if (location == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no task returned for %s", fu_redfish_backend_get_push_uri_path(backend)); return FALSE; } if (!fu_redfish_device_poll_task(FU_REDFISH_DEVICE(self), location, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); ret = fu_redfish_smc_device_start_update(device, fu_progress_get_child(progress), error); fu_progress_step_done(progress); return ret; } static void fu_redfish_smc_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_redfish_smc_device_init(FuRedfishSmcDevice *self) { fu_device_set_summary(FU_DEVICE(self), "Redfish Supermicro device"); } static void fu_redfish_smc_device_class_init(FuRedfishSmcDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_redfish_smc_device_write_firmware; klass_device->set_progress = fu_redfish_smc_device_set_progress; } fwupd-1.9.16/plugins/redfish/fu-redfish-smc-device.h000066400000000000000000000006531460375044200222750ustar00rootroot00000000000000/* * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-redfish-backend.h" #include "fu-redfish-device.h" #define FU_TYPE_REDFISH_SMC_DEVICE (fu_redfish_smc_device_get_type()) G_DECLARE_FINAL_TYPE(FuRedfishSmcDevice, fu_redfish_smc_device, FU, REDFISH_SMC_DEVICE, FuRedfishDevice) struct _FuRedfishSmcDeviceClass { FuRedfishDeviceClass parent_class; }; fwupd-1.9.16/plugins/redfish/fu-redfish.rs000066400000000000000000000033741460375044200204600ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, ParseBytes)] struct RedfishProtocolOverIp { service_uuid: Guid, host_ip_assignment_type: u8, host_ip_address_format: u8, host_ip_address: [u8; 16], host_ip_mask: [u8; 16], service_ip_assignment_type: u8, service_ip_address_format: u8, service_ip_address: [u8; 16], service_ip_mask: [u8; 16], service_ip_port: u16le, service_ip_vlan_id: u32le, service_hostname_len: u8, // optional service_hostname goes here } #[derive(ToString)] enum RedfishInterfaceType { UsbNetwork = 0x02, PciNetwork = 0x03, UsbNetworkV2 = 0x04, PciNetworkV2 = 0x05, } enum RedfishIpAssignmentType { Static = 0x00, Dhcp = 0x02, AutoConfig = 0x03, HostSelect = 0x04, } enum RedfishIpAddressFormat { Unknown = 0x00, V4 = 0x01, V6 = 0x02, } #[repr(u8)] #[derive(ToString)] enum RedfishSmbiosInterfaceType { Unknown = 0x00, Kcs = 0x02, 8250Uart = 0x03, 16450Uart = 0x04, 16550Uart = 0x05, 16650Uart = 0x06, 16750Uart = 0x07, 16850Uart = 0x08, Mctp = 0x3F, Network = 0x40, Oem = 0xF0, } #[derive(ParseBytes)] struct RedfishSmbiosType42 { type: u8 == 42, length: u8, handle: u16le, interface_type: RedfishSmbiosInterfaceType = Network, data_length: u8, // data: [u8; data_length], // protocol_cnt: u8, // protocol_records } #[derive(ToString)] enum RedfishNetworkDeviceState { Unknown = 0, Unmanaged = 10, Unavailable = 20, Disconnected = 30, Prepare = 40, Config = 50, NeedAuth = 60, IpConfig = 70, IpCheck = 80, Secondaries = 90, Activated = 100, Deactivating = 110, Failed = 120, } fwupd-1.9.16/plugins/redfish/fu-self-test.c000066400000000000000000000374261460375044200205450ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-context-private.h" #include "fu-device-private.h" #ifdef HAVE_LINUX_IPMI_H #include "fu-ipmi-device.h" #endif #include "fu-plugin-private.h" #include "fu-redfish-common.h" #include "fu-redfish-network.h" #include "fu-redfish-plugin.h" #include "fu-redfish-smc-device.h" #include "fu-redfish-struct.h" typedef struct { FuPlugin *plugin; FuPlugin *smc_plugin; FuPlugin *unlicensed_plugin; } FuTest; static void fu_test_self_init(FuTest *self) { gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE | FU_QUIRKS_LOAD_FLAG_NO_VERIFY, &error); g_assert_no_error(error); g_assert_true(ret); /* generic BMC */ self->plugin = fu_plugin_new_from_gtype(fu_redfish_plugin_get_type(), ctx); ret = fu_plugin_runner_startup(self->plugin, progress, &error); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) { g_test_skip("no redfish.py running"); g_clear_error(&error); } else { g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_runner_coldplug(self->plugin, progress, &error); g_assert_no_error(error); g_assert_true(ret); } /* supermicro BMC */ self->smc_plugin = fu_plugin_new_from_gtype(fu_redfish_plugin_get_type(), ctx); ret = fu_plugin_runner_startup(self->smc_plugin, progress, &error); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) { g_test_skip("no redfish.py running"); g_clear_error(&error); } else { g_assert_no_error(error); g_assert_true(ret); fu_redfish_plugin_set_credentials(self->smc_plugin, "smc_username", "password2"); ret = fu_plugin_runner_coldplug(self->smc_plugin, progress, &error); g_assert_no_error(error); g_assert_true(ret); } /* unlicensed supermicro BMC */ self->unlicensed_plugin = fu_plugin_new_from_gtype(fu_redfish_plugin_get_type(), ctx); ret = fu_plugin_runner_startup(self->unlicensed_plugin, progress, &error); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE)) { g_test_skip("no redfish.py running"); } else { g_assert_no_error(error); g_assert_true(ret); fu_redfish_plugin_set_credentials(self->unlicensed_plugin, "unlicensed_username", "password2"); ret = fu_plugin_runner_coldplug(self->unlicensed_plugin, progress, &error); g_assert_no_error(error); g_assert_true(ret); } } static void fu_test_redfish_ipmi_func(void) { #ifdef HAVE_LINUX_IPMI_H gboolean ret; g_autoptr(FuIpmiDevice) device = fu_ipmi_device_new(NULL); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error = NULL; g_autofree gchar *username = NULL; g_autofree gchar *str = NULL; /* sanity check */ if (!g_file_test("/dev/ipmi0", G_FILE_TEST_EXISTS)) { g_test_skip("no IPMI hardware"); return; } /* create device */ locker = fu_device_locker_new(device, &error); if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) { g_test_skip("permission denied for access to IPMI hardware"); return; } g_assert_no_error(error); g_assert_nonnull(locker); str = fu_device_to_string(FU_DEVICE(device)); g_debug("%s", str); /* add user that can do redfish commands */ if (g_getenv("FWUPD_REDFISH_SELF_TEST") == NULL) { g_test_skip("not doing destructive tests"); return; } ret = fu_ipmi_device_set_user_name(device, 0x04, "fwupd", &error); g_assert_no_error(error); g_assert_true(ret); username = fu_ipmi_device_get_user_password(device, 0x04, &error); g_assert_no_error(error); g_assert_nonnull(username); g_debug("username=%s", username); ret = fu_ipmi_device_set_user_enable(device, 0x04, TRUE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_ipmi_device_set_user_priv(device, 0x04, 0x4, 1, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_ipmi_device_set_user_password(device, 0x04, "Passw0rd123", &error); g_assert_no_error(error); g_assert_true(ret); #else g_test_skip("no linux/ipmi.h, so skipping"); #endif } static void fu_test_redfish_common_func(void) { const guint8 buf[16] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; g_autofree gchar *ipv4 = NULL; g_autofree gchar *ipv6 = NULL; g_autofree gchar *maca = NULL; ipv4 = fu_redfish_common_buffer_to_ipv4(buf); g_assert_cmpstr(ipv4, ==, "0.1.2.3"); ipv6 = fu_redfish_common_buffer_to_ipv6(buf); g_assert_cmpstr(ipv6, ==, "00010203:04050607:08090a0b:0c0d0e0f"); maca = fu_redfish_common_buffer_to_mac(buf); g_assert_cmpstr(maca, ==, "00:01:02:03:04:05"); } static void fu_test_redfish_common_version_func(void) { struct { const gchar *in; const gchar *op; } strs[] = {{"1.2.3", "1.2.3"}, {"P50 v1.2.3 PROD", "1.2.3"}, {"P50 1.2.3 DEV", "1.2.3"}, {NULL, NULL}}; for (guint i = 0; strs[i].in != NULL; i++) { g_autofree gchar *tmp = fu_redfish_common_fix_version(strs[i].in); g_assert_cmpstr(tmp, ==, strs[i].op); } } static void fu_test_redfish_common_lenovo_func(void) { struct { const gchar *in; gboolean ret; const gchar *build; const gchar *version; } values[] = {{"11A-1.02", TRUE, "11A", "1.02"}, {"11A-0.00", TRUE, "11A", "0.00"}, {"99Z-9.99", TRUE, "99Z", "9.99"}, {"9-9-9.99", FALSE, NULL, NULL}, {"999-9.99", FALSE, NULL, NULL}, {"ACB-9.99", FALSE, NULL, NULL}, {NULL, FALSE, NULL, NULL}}; for (guint i = 0; values[i].in != NULL; i++) { gboolean ret; g_autofree gchar *build = NULL; g_autofree gchar *version = NULL; ret = fu_redfish_common_parse_version_lenovo(values[i].in, &build, &version, NULL); g_assert_cmpint(ret, ==, values[i].ret); g_assert_cmpstr(build, ==, values[i].build); g_assert_cmpstr(version, ==, values[i].version); } } static void fu_test_redfish_network_mac_addr_func(void) { FuRedfishNetworkDeviceState state = FU_REDFISH_NETWORK_DEVICE_STATE_UNKNOWN; gboolean ret; g_autofree gchar *ip_addr = NULL; g_autoptr(FuRedfishNetworkDevice) device = NULL; g_autoptr(GError) error = NULL; device = fu_redfish_network_device_for_mac_addr("00:13:F7:29:C2:D8", &error); if (device == NULL && g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_test_skip("no hardware"); return; } if (device == NULL && g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_autofree gchar *str = g_strdup_printf("not supported: %s", error->message); g_test_skip(str); return; } g_assert_no_error(error); g_assert_nonnull(device); ret = fu_redfish_network_device_get_state(device, &state, &error); g_assert_no_error(error); g_assert_true(ret); if (state == FU_REDFISH_NETWORK_DEVICE_STATE_DISCONNECTED) { ret = fu_redfish_network_device_connect(device, &error); g_assert_no_error(error); g_assert_true(ret); } ip_addr = fu_redfish_network_device_get_address(device, &error); g_assert_no_error(error); g_assert_nonnull(ip_addr); } static void fu_test_redfish_network_vid_pid_func(void) { g_autofree gchar *ip_addr = NULL; g_autoptr(FuRedfishNetworkDevice) device = NULL; g_autoptr(GError) error = NULL; device = fu_redfish_network_device_for_vid_pid(0x0707, 0x0201, &error); if (device == NULL && g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_test_skip("no hardware"); return; } if (device == NULL && g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_autofree gchar *str = g_strdup_printf("not supported: %s", error->message); g_test_skip(str); return; } g_assert_no_error(error); g_assert_nonnull(device); ip_addr = fu_redfish_network_device_get_address(device, &error); g_assert_no_error(error); g_assert_nonnull(ip_addr); } static void fu_test_redfish_devices_func(gconstpointer user_data) { FuDevice *dev; FuTest *self = (FuTest *)user_data; GPtrArray *devices; g_autofree gchar *devstr0 = NULL; g_autofree gchar *devstr1 = NULL; devices = fu_plugin_get_devices(self->plugin); g_assert_nonnull(devices); if (devices->len == 0) { g_test_skip("no redfish support"); return; } g_assert_cmpint(devices->len, ==, 2); /* BMC */ dev = g_ptr_array_index(devices, 1); devstr0 = fu_device_to_string(dev); g_debug("%s", devstr0); g_assert_cmpstr(fu_device_get_id(dev), ==, "62c1cd95692c5225826cf8568a460427ea3b1827"); g_assert_cmpstr(fu_device_get_name(dev), ==, "BMC Firmware"); g_assert_cmpstr(fu_device_get_vendor(dev), ==, "Lenovo"); g_assert_cmpstr(fu_device_get_version(dev), ==, "1.02"); g_assert_cmpstr(fu_device_get_version_lowest(dev), ==, "0.12"); g_assert_cmpint(fu_device_get_version_format(dev), ==, FWUPD_VERSION_FORMAT_PAIR); g_assert_cmpint(fu_device_get_version_build_date(dev), ==, 1552608000); g_assert_true(fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_true(fu_device_has_protocol(dev, "org.dmtf.redfish")); g_assert_true( fu_device_has_guid(dev, "REDFISH\\VENDOR_Lenovo&SOFTWAREID_UEFI-AFE1-6&TYPE_UNSIGNED")); g_assert_true(fu_device_has_vendor_id(dev, "REDFISH:LENOVO")); /* BIOS */ dev = g_ptr_array_index(devices, 0); devstr1 = fu_device_to_string(dev); g_debug("%s", devstr1); g_assert_cmpstr(fu_device_get_id(dev), ==, "562313e34c756a05a2e878861377765582bbf971"); g_assert_cmpstr(fu_device_get_name(dev), ==, "BIOS Firmware"); g_assert_cmpstr(fu_device_get_vendor(dev), ==, "Contoso"); g_assert_cmpstr(fu_device_get_version(dev), ==, "1.45"); g_assert_cmpstr(fu_device_get_serial(dev), ==, "12345"); g_assert_cmpstr(fu_device_get_version_lowest(dev), ==, "1.10"); g_assert_cmpint(fu_device_get_version_format(dev), ==, FWUPD_VERSION_FORMAT_PAIR); g_assert_cmpint(fu_device_get_version_build_date(dev), ==, 1552608000); g_assert_true(fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)); g_assert_true(fu_device_has_icon(dev, "network-wired")); g_assert_true(fu_device_has_protocol(dev, "org.dmtf.redfish")); g_assert_true(fu_device_has_guid(dev, "fee82a67-6ce2-4625-9f44-237ad2402c28")); g_assert_true(fu_device_has_guid(dev, "a6d3294e-37e5-50aa-ae2f-c0c457af16f3")); g_assert_true(fu_device_has_vendor_id(dev, "REDFISH:CONTOSO")); } static void fu_test_redfish_unlicensed_devices_func(gconstpointer user_data) { FuDevice *dev; FuTest *self = (FuTest *)user_data; GPtrArray *devices; devices = fu_plugin_get_devices(self->unlicensed_plugin); g_assert_nonnull(devices); if (devices->len == 0) { g_test_skip("no redfish support"); return; } g_assert_cmpint(devices->len, ==, 2); dev = g_ptr_array_index(devices, 0); g_assert_true(FU_IS_REDFISH_SMC_DEVICE(dev)); g_assert_true(fu_device_has_inhibit( dev, fwupd_device_problem_to_string(FWUPD_DEVICE_PROBLEM_MISSING_LICENSE))); dev = g_ptr_array_index(devices, 1); g_assert_true(FU_IS_REDFISH_SMC_DEVICE(dev)); g_assert_true(fu_device_has_inhibit( dev, fwupd_device_problem_to_string(FWUPD_DEVICE_PROBLEM_MISSING_LICENSE))); } static void fu_test_redfish_smc_devices_func(gconstpointer user_data) { FuDevice *dev; FuTest *self = (FuTest *)user_data; GPtrArray *devices; devices = fu_plugin_get_devices(self->smc_plugin); g_assert_nonnull(devices); if (devices->len == 0) { g_test_skip("no redfish support"); return; } g_assert_cmpint(devices->len, ==, 2); dev = g_ptr_array_index(devices, 0); g_assert_true(FU_IS_REDFISH_SMC_DEVICE(dev)); dev = g_ptr_array_index(devices, 1); g_assert_true(FU_IS_REDFISH_SMC_DEVICE(dev)); } static void fu_test_redfish_update_func(gconstpointer user_data) { FuDevice *dev; FuTest *self = (FuTest *)user_data; GPtrArray *devices; gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(GBytes) blob_fw = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); devices = fu_plugin_get_devices(self->plugin); g_assert_nonnull(devices); if (devices->len == 0) { g_test_skip("no redfish support"); return; } g_assert_cmpint(devices->len, ==, 2); /* BMC */ dev = g_ptr_array_index(devices, 1); blob_fw = g_bytes_new_static("hello", 5); ret = fu_plugin_runner_write_firmware(self->plugin, dev, blob_fw, progress, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_true(fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)); /* try again */ ret = fu_plugin_runner_write_firmware(self->plugin, dev, blob_fw, progress, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE); g_assert_false(ret); } static void fu_test_redfish_smc_update_func(gconstpointer user_data) { FuDevice *dev; FuTest *self = (FuTest *)user_data; GPtrArray *devices; gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(GBytes) blob_fw = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); devices = fu_plugin_get_devices(self->smc_plugin); g_assert_nonnull(devices); if (devices->len == 0) { g_test_skip("no redfish support"); return; } g_assert_cmpint(devices->len, ==, 2); /* BMC */ dev = g_ptr_array_index(devices, 1); blob_fw = g_bytes_new_static("hello", 5); ret = fu_plugin_runner_write_firmware(self->plugin, dev, blob_fw, progress, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); /* stuck update */ blob_fw = g_bytes_new_static("stuck", 5); ret = fu_plugin_runner_write_firmware(self->plugin, dev, blob_fw, progress, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_false(ret); g_assert_true(fu_device_has_inhibit( dev, fwupd_device_problem_to_string(FWUPD_DEVICE_PROBLEM_UPDATE_PENDING))); } static void fu_test_self_free(FuTest *self) { if (self->plugin != NULL) g_object_unref(self->plugin); if (self->smc_plugin != NULL) g_object_unref(self->smc_plugin); if (self->unlicensed_plugin != NULL) g_object_unref(self->unlicensed_plugin); g_free(self); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuTest, fu_test_self_free) #pragma clang diagnostic pop int main(int argc, char **argv) { g_autoptr(FuTest) self = g_new0(FuTest, 1); g_autofree gchar *smbios_data_fn = NULL; g_autofree gchar *testdatadir = NULL; (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); (void)g_setenv("FWUPD_REDFISH_VERBOSE", "1", TRUE); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); smbios_data_fn = g_build_filename(testdatadir, "redfish-smbios.bin", NULL); (void)g_setenv("FWUPD_REDFISH_SMBIOS_DATA", smbios_data_fn, TRUE); (void)g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); (void)g_setenv("CONFIGURATION_DIRECTORY", testdatadir, TRUE); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", testdatadir, TRUE); g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); fu_test_self_init(self); g_test_add_func("/redfish/ipmi", fu_test_redfish_ipmi_func); g_test_add_func("/redfish/common", fu_test_redfish_common_func); g_test_add_func("/redfish/common{version}", fu_test_redfish_common_version_func); g_test_add_func("/redfish/common{lenovo}", fu_test_redfish_common_lenovo_func); g_test_add_func("/redfish/network{mac_addr}", fu_test_redfish_network_mac_addr_func); g_test_add_func("/redfish/network{vid_pid}", fu_test_redfish_network_vid_pid_func); g_test_add_data_func("/redfish/unlicensed_plugin{devices}", self, fu_test_redfish_unlicensed_devices_func); g_test_add_data_func("/redfish/smc_plugin{devices}", self, fu_test_redfish_smc_devices_func); g_test_add_data_func("/redfish/smc_plugin{update}", self, fu_test_redfish_smc_update_func); g_test_add_data_func("/redfish/plugin{devices}", self, fu_test_redfish_devices_func); g_test_add_data_func("/redfish/plugin{update}", self, fu_test_redfish_update_func); return g_test_run(); } fwupd-1.9.16/plugins/redfish/meson.build000066400000000000000000000046521460375044200202200ustar00rootroot00000000000000if get_option('plugin_redfish').require(libcurl.found(), error_message: 'curl is needed for plugin_redfish').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginRedfish"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('redfish.quirk') ipmi_src = [] if have_linux_ipmi ipmi_src += 'fu-ipmi-device.c' endif plugin_builtin_redfish = static_library('fu_plugin_redfish', rustgen.process( 'fu-redfish.rs', # fuzzing ), sources: [ 'fu-redfish-plugin.c', 'fu-redfish-backend.c', 'fu-redfish-common.c', # fuzzing 'fu-redfish-device.c', 'fu-redfish-smc-device.c', 'fu-redfish-legacy-device.c', 'fu-redfish-multipart-device.c', 'fu-redfish-network.c', 'fu-redfish-network-device.c', 'fu-redfish-request.c', 'fu-redfish-smbios.c', # fuzzing ipmi_src, ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: [ plugin_deps, libcurl, ], ) plugin_builtins += plugin_builtin_redfish if get_option('tests') install_data(['tests/redfish-smbios.bin'], install_dir: join_paths(installed_test_datadir, 'tests')) install_data(['tests/redfish.conf'], install_dir: join_paths(installed_test_datadir, 'tests'), install_mode: 'rw-r-----', ) install_data(['tests/efi/efivars/RedfishIndications-16faa37e-4b6a-4891-9028-242de65a3b70'], install_dir: join_paths(installed_test_datadir, 'tests', 'efi', 'efivars')) install_data(['tests/efi/efivars/RedfishOSCredentials-16faa37e-4b6a-4891-9028-242de65a3b70'], install_dir: join_paths(installed_test_datadir, 'tests', 'efi', 'efivars')) env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) env.set('FWUPD_LOCALSTATEDIR', '/tmp/fwupd-self-test/var') e = executable( 'redfish-self-test', rustgen.process('fu-redfish.rs'), sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: [ plugin_deps, libcurl, ], link_with: [ fwupd, fwupdplugin, plugin_builtin_redfish, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ cargs, '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('redfish-self-test', e, env: env) # added to installed-tests endif endif fwupd-1.9.16/plugins/redfish/redfish.quirk000066400000000000000000000016171460375044200205550ustar00rootroot00000000000000# Lenovo ThinkSystem [42f00735-c9ab-5374-bd63-a5deee5881e0] Flags = wildcard-targets,reset-required,ipmi-create-user [REDFISH\VENDOR_Lenovo&ID_BMC-Backup] ParentGuid = REDFISH\VENDOR_Lenovo&ID_BMC-Primary Flags = dual-image,is-backup,no-probe [REDFISH\VENDOR_Lenovo&ID_BMC-Primary] Flags = dual-image,manager-reset RedfishResetPreDelay = 10000 RedfishResetPostDelay = 40000 [REDFISH\VENDOR_Lenovo&ID_LXPM] Flags = ~updatable [REDFISH\VENDOR_Lenovo&ID_LXPMLinuxDriver] Flags = ~updatable ParentGuid = REDFISH\VENDOR_Lenovo&ID_LXPM [REDFISH\VENDOR_Lenovo&ID_LXPMWindowsDriver] Flags = ~updatable ParentGuid = REDFISH\VENDOR_Lenovo&ID_LXPM [REDFISH\VENDOR_SMCI&ID_Backup_BIOS] Flags = is-backup,no-probe # Advantech [18789130-a714-53c0-b025-fa93801d3995] Flags = wildcard-targets,ipmi-create-user [REDFISH\VENDOR_Advantech&ID_BMC] Flags = manager-reset,no-manager-reset-request RedfishResetPreDelay = 100000 fwupd-1.9.16/plugins/redfish/tests/000077500000000000000000000000001460375044200172115ustar00rootroot00000000000000fwupd-1.9.16/plugins/redfish/tests/efi/000077500000000000000000000000001460375044200177545ustar00rootroot00000000000000fwupd-1.9.16/plugins/redfish/tests/efi/efivars/000077500000000000000000000000001460375044200214135ustar00rootroot00000000000000RedfishIndications-16faa37e-4b6a-4891-9028-242de65a3b70000066400000000000000000000000101460375044200315150ustar00rootroot00000000000000fwupd-1.9.16/plugins/redfish/tests/efi/efivarsRedfishOSCredentials-16faa37e-4b6a-4891-9028-242de65a3b70000066400000000000000000000000251460375044200317560ustar00rootroot00000000000000fwupd-1.9.16/plugins/redfish/tests/efi/efivarsusername:passwordfwupd-1.9.16/plugins/redfish/tests/fwupd.conf000077700000000000000000000000001460375044200247462../../../data/fwupd.confustar00rootroot00000000000000fwupd-1.9.16/plugins/redfish/tests/redfish-smbios.bin000066400000000000000000000001661460375044200226240ustar00rootroot00000000000000*v4@ vxVSnd4 localhostfwupd-1.9.16/plugins/redfish/tests/redfish-smbios.builder.xml000066400000000000000000000002531460375044200242760ustar00rootroot00000000000000 0x1234 localhost 0.0.0.0 0x9876 0x5678 fwupd-1.9.16/plugins/redfish/tests/redfish.conf000066400000000000000000000001121460375044200214760ustar00rootroot00000000000000[redfish] Uri=http://localhost:4661 Username=username2 Password=password2 fwupd-1.9.16/plugins/redfish/tests/redfish.py000077500000000000000000000404561460375044200212230ustar00rootroot00000000000000#!/usr/bin/python3 # pylint: disable=invalid-name,missing-docstring # # Copyright (C) 2021 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ import json from flask import Flask, Response, request app = Flask(__name__) HARDCODED_SMC_USERNAME = "smc_username" HARDCODED_UNL_USERNAME = "unlicensed_username" HARDCODED_USERNAMES = {"username2", HARDCODED_SMC_USERNAME, HARDCODED_UNL_USERNAME} HARDCODED_PASSWORD = "password2" app._percentage545: int = 0 app._percentage546: int = 0 def _failure(msg: str, status=400): res = { "error": {"message": msg}, } return Response(response=json.dumps(res), status=401, mimetype="application/json") @app.route("/redfish/v1/") def index(): # reset counter app._percentage545 = 0 app._percentage546 = 0 # check password from the config file try: if ( request.authorization["username"] not in HARDCODED_USERNAMES or request.authorization["password"] != HARDCODED_PASSWORD ): return _failure("unauthorised", status=401) except (KeyError, TypeError): return _failure("invalid") res = { "@odata.id": "/redfish/v1/", "@odata.etag": "653b835e9ee4af9ea7ea", "RedfishVersion": "1.6.0", "UUID": "92384634-2938-2342-8820-489239905423", "UpdateService": {"@odata.id": "/redfish/v1/UpdateService"}, } return Response(json.dumps(res), status=200, mimetype="application/json") @app.route("/redfish/v1/UpdateService") def update_service(): res = { "@odata.id": "/redfish/v1/UpdateService", "@odata.type": "#UpdateService.v1_8_0.UpdateService", "@odata.etag": "653b835e9ee4af9ea7ea", "FirmwareInventory": { "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory" }, "HttpPushUri": "/FWUpdate", "HttpPushUriOptions": { "HttpPushUriApplyTime": { "ApplyTime": "Immediate", } }, "HttpPushUriOptionsBusy": False, "ServiceEnabled": True, } if request.authorization["username"] == HARDCODED_UNL_USERNAME: res["MultipartHttpPushUri"] = "/FWUpdate-unlicensed" res["Actions"] = { "#UpdateService.StartUpdate": { "target": "/redfish/v1/UpdateService/Actions/UpdateService.StartUpdate" } } elif request.authorization["username"] == HARDCODED_SMC_USERNAME: res["MultipartHttpPushUri"] = "/FWUpdate-smc" res["Actions"] = { "#UpdateService.StartUpdate": { "target": "/redfish/v1/UpdateService/Actions/UpdateService.StartUpdate" } } else: res["MultipartHttpPushUri"] = "/FWUpdate" return Response(json.dumps(res), status=200, mimetype="application/json") @app.route("/redfish/v1/UpdateService/FirmwareInventory") def firmware_inventory(): res = { "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory", "@odata.type": "#SoftwareInventoryCollection.SoftwareInventoryCollection", "@odata.etag": "653b835e9ee4af9ea7ea", "Members": [ {"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/BMC"}, {"@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/BIOS"}, ], "Members@odata.count": 2, } return Response(json.dumps(res), status=200, mimetype="application/json") @app.route("/redfish/v1/UpdateService/FirmwareInventory/BMC") def firmware_inventory_bmc(): res = { "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/BMC", "@odata.type": "#SoftwareInventory.v1_2_3.SoftwareInventory", "@odata.etag": "653b835e9ee4af9ea7ea", "Id": "BMC", "LowestSupportedVersion": "11A-0.12", "Name": "Lenovo BMC Firmware", "RelatedItem": [{"@odata.id": "/redfish/v1/Managers/BMC"}], "SoftwareId": "UEFI-AFE1-6", "UefiDevicePaths": ["BMC(0x1,0x0ABCDEF)"], "Updateable": True, "Version": "11A-1.02", "ReleaseDate": "2019-03-15T00:00:00", } if request.authorization["username"] in { HARDCODED_UNL_USERNAME, HARDCODED_SMC_USERNAME, }: res["Manufacturer"] = "SMCI" else: res["Manufacturer"] = "Lenovo" return Response(json.dumps(res), status=200, mimetype="application/json") @app.route("/redfish/v1/Managers/BMC") def redfish_managers_bmc(): res = {} return Response(json.dumps(res), status=200, mimetype="application/json") @app.route("/redfish/v1/Chassis/1/PCIeDevices/slot_3/PCIeFunctions/slot_2.00") def firmware_chassis_pcie_function_slot2(): res = { "VendorId": "0x14e4", "FunctionId": 1, "SubsystemId": "0x4042", "DeviceClass": "NetworkController", "SubsystemVendorId": "0x17aa", "DeviceId": "0x165f", "RevisionId": "0x00", "ClassCode": "0x020000", } return Response(json.dumps(res), status=200, mimetype="application/json") @app.route("/redfish/v1/Chassis/1/PCIeDevices/slot_3/PCIeFunctions") def firmware_chassis_pcie_functions(): res = { "Members": [ { "@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/slot_3/PCIeFunctions/slot_2.00" } ], } return Response(json.dumps(res), status=200, mimetype="application/json") @app.route("/redfish/v1/Systems/437XR1138R2") def firmware_systems_slot7(): res = { "SerialNumber": "12345", "@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/slot_3", "@odata.etag": "653b835e9ee4af9ea7ea", "PCIeFunctions": { "@odata.id": "/redfish/v1/Chassis/1/PCIeDevices/slot_3/PCIeFunctions" }, "DeviceType": "SingleFunction", } return Response(json.dumps(res), status=200, mimetype="application/json") @app.route("/redfish/v1/UpdateService/FirmwareInventory/BIOS") def firmware_inventory_bios(): res = { "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/BIOS", "@odata.type": "#SoftwareInventory.v1_2_3.SoftwareInventory", "@odata.etag": "653b835e9ee4af9ea7ea", "Id": "BIOS", "LowestSupportedVersion": "P79 v1.10", "Name": "Contoso BIOS Firmware", "RelatedItem": [{"@odata.id": "/redfish/v1/Systems/437XR1138R2"}], "ReleaseDate": "2017-12-06T12:00:00", "SoftwareId": "FEE82A67-6CE2-4625-9F44-237AD2402C28", "Updateable": True, "Version": "P79 v1.45", "ReleaseDate": "2019-03-15T00:00:00Z", } if request.authorization["username"] in { HARDCODED_UNL_USERNAME, HARDCODED_SMC_USERNAME, }: res["Manufacturer"] = "SMCI" else: res["Manufacturer"] = "Contoso" return Response(json.dumps(res), status=200, mimetype="application/json") @app.route("/redfish/v1/TaskService/999") def task_manager(): res = { "@odata.id": "/redfish/v1/TaskService/999", "@odata.type": "#Task.v1_4_3.Task", "@odata.etag": "653b835e9ee4af9ea7ea", "Id": "545", "Name": "Task 545", } return Response(json.dumps(res), status=200, mimetype="application/json") @app.route("/redfish/v1/TaskService/Tasks/545") def task_status_545(): res = { "@odata.id": "/redfish/v1/TaskService/Tasks/545", "@odata.type": "#Task.v1_4_3.Task", "@odata.etag": "653b835e9ee4af9ea7ea", "Id": "545", "Name": "Task 545", "PercentComplete": app._percentage545, } if app._percentage545 == 0: res["TaskState"] = "Running" elif app._percentage545 in [25, 50, 75]: res["TaskState"] = "Running" res["TaskStatus"] = "OK" res["Messages"] = [ { "Message": "Applying image", "MessageId": "Update.1.1.TransferringToComponent", } ] elif app._percentage545 == 100: res["TaskState"] = "Completed" res["TaskStatus"] = "OK" res["Messages"] = [ { "Message": "Applying image", "MessageId": "Update.1.1.TransferringToComponent", }, { "Message": "A reset is required", "MessageId": "Base.1.10.ResetRequired", }, { "Message": "Task completed OK", "MessageId": "TaskEvent.1.0.TaskCompletedOK", }, ] else: res["TaskState"] = "Exception" res["TaskStatus"] = "Warning" res["Messages"] = [ { "Message": "Error verifying image", "MessageId": "Update.1.0.ApplyFailed", "Severity": "Warning", } ] app._percentage545 += 25 return Response(response=json.dumps(res), status=200, mimetype="application/json") @app.route("/redfish/v1/TaskService/Tasks/546") def task_status_546(): res = { "@odata.type": "#Task.v1_4_3.Task", "@odata.id": "/redfish/v1/TaskService/Tasks/546", "@odata.etag": "653b835e9ee4af9ea7ea", "Id": "546", "Name": "BIOS Verify", "TaskState": "Running", "StartTime": "2022-09-29T14:50:54+00:00", "PercentComplete": app._percentage546, "HidePayload": True, "TaskMonitor": "/redfish/v1/TaskMonitor/gd5n5ffS4gi9r6YKVZmgIIaj8ECLfnc", "TaskStatus": "OK", "Messages": [ { "MessageId": "", "RelatedProperties": [""], "Message": "", "MessageArgs": [""], "Severity": "", } ], "Oem": {}, } if app._percentage546 == 0: res["TaskState"] = "Running" elif app._percentage546 in [25, 50, 75]: res["TaskState"] = "Running" elif app._percentage546 == 100: res["TaskState"] = "Completed" app._percentage546 += 25 return Response(response=json.dumps(res), status=200, mimetype="application/json") @app.route("/FWUpdate-unlicensed", methods=["GET"]) def fwupdate_unlicensed(): res = { "error": { "code": "Base.v1_4_0.GeneralError", "Message": "A general error has occurred. See ExtendedInfo for more information.", "@Message.ExtendedInfo": [ { "MessageId": "SMC.1.0.OemLicenseNotPassed", "Severity": "Warning", "Resolution": "Please check if there was the next step with respective API to execute.", "Message": "The BIOS firmware update was already in update mode.", "MessageArgs": ["BIOS"], "RelatedProperties": ["EnterUpdateMode_StatusCheck"], } ], } } return Response(json.dumps(res), status=405, mimetype="application/json") data = json.loads(request.form["UpdateParameters"]) if data["@Redfish.OperationApplyTime"] != "Immediate": return _failure("apply invalid") if data["Targets"][0] != "/redfish/v1/UpdateService/FirmwareInventory/BMC": return _failure("id invalid") fileitem = request.files["UpdateFile"] if not fileitem.filename.endswith(".bin"): return _failure("filename invalid") if fileitem.read().decode() != "hello": return _failure("data invalid") res = { "Version": "P79 v1.45", "@odata.id": "/redfish/v1/TaskService/Tasks/545", "@odata.etag": "653b835e9ee4af9ea7ea", "TaskMonitor": "/redfish/v1/TaskService/999", } # Location set to the URI of a task monitor. return Response( json.dumps(res), status=202, mimetype="application/json", headers={"Location": "http://localhost:4661/redfish/v1/TaskService/Tasks/545"}, ) @app.route("/FWUpdate-smc", methods=["POST"]) def fwupdate_smc(): data = json.loads(request.form["UpdateParameters"]) if data["@Redfish.OperationApplyTime"] != "OnStartUpdateRequest": return _failure("apply invalid") if data["Targets"][0] != "/redfish/v1/Systems/1/Bios": return _failure("id invalid") fileitem = request.files["UpdateFile"] if not fileitem.filename.endswith(".bin"): return _failure("filename invalid") filecontents = fileitem.read().decode() if filecontents == "hello": app._percentage546 = 0 res = { "Accepted": { "code": "Base.v1_4_0.Accepted", "Message": "Successfully Accepted Request. Please see the location header and ExtendedInfo for more information.", "@Message.ExtendedInfo": [ { "MessageId": "SMC.1.0.OemSimpleupdateAcceptedMessage", "Severity": "Ok", "Resolution": "No resolution was required.", "Message": "Please also check Task Resource /redfish/v1/TaskService/Tasks/546 to see more information.", "MessageArgs": ["/redfish/v1/TaskService/Tasks/546"], "RelatedProperties": ["BiosVerifyAccepted"], } ], } } # Location set to the URI of a task monitor. return Response( json.dumps(res), status=202, mimetype="application/json", headers={ "Location": "http://localhost:4661/redfish/v1/TaskService/Tasks/546" }, ) elif filecontents == "stuck": res = { "error": { "code": "Base.v1_4_0.GeneralError", "Message": "A general error has occurred. See ExtendedInfo for more information.", "@Message.ExtendedInfo": [ { "MessageId": "SMC.1.0.OemFirmwareAlreadyInUpdateMode", "Severity": "Warning", "Resolution": "Please check if there was the next step with respective API to execute.", "Message": "The BIOS firmware update was already in update mode.", "MessageArgs": ["BIOS"], "RelatedProperties": ["EnterUpdateMode_StatusCheck"], } ], } } return Response(json.dumps(res), status=405, mimetype="application/json") else: return _failure("data invalid") @app.route("/FWUpdate", methods=["POST"]) def fwupdate(): data = json.loads(request.form["UpdateParameters"]) if data["@Redfish.OperationApplyTime"] != "Immediate": return _failure("apply invalid") if data["Targets"][0] != "/redfish/v1/UpdateService/FirmwareInventory/BMC": return _failure("id invalid") fileitem = request.files["UpdateFile"] if not fileitem.filename.endswith(".bin"): return _failure("filename invalid") if fileitem.read().decode() != "hello": return _failure("data invalid") res = { "Version": "P79 v1.45", "@odata.id": "/redfish/v1/TaskService/Tasks/545", "@odata.etag": "653b835e9ee4af9ea7ea", "TaskMonitor": "/redfish/v1/TaskService/999", } # Location set to the URI of a task monitor. return Response( json.dumps(res), status=202, mimetype="application/json", headers={"Location": "http://localhost:4661/redfish/v1/TaskService/Tasks/545"}, ) @app.route( "/redfish/v1/UpdateService/Actions/UpdateService.StartUpdate", methods=["POST"] ) def startupdate(): res = { "Accepted": { "code": "Base.v1_4_0.Accepted", "Message": "Successfully Accepted Request. Please see the location header and ExtendedInfo for more information.", "@Message.ExtendedInfo": [ { "MessageId": "SMC.1.0.OemSimpleupdateAcceptedMessage", "Severity": "Ok", "Resolution": "No resolution was required.", "Message": "Please also check Task Resource /redfish/v1/TaskService/Tasks/546 to see more information.", "MessageArgs": ["/redfish/v1/TaskService/Tasks/546"], "RelatedProperties": ["BiosUpdateAccepted"], } ], } } app._percentage546 = 0 return Response( json.dumps(res), status=202, mimetype="application/json", headers={"Location": "http://localhost:4661/redfish/v1/TaskService/Tasks/546"}, ) if __name__ == "__main__": app.run(host="0.0.0.0", port=4661) fwupd-1.9.16/plugins/rts54hid/000077500000000000000000000000001460375044200160715ustar00rootroot00000000000000fwupd-1.9.16/plugins/rts54hid/README.md000066400000000000000000000032201460375044200173450ustar00rootroot00000000000000--- title: Plugin: RTS54HID --- ## Introduction This plugin allows the user to update any supported hub and attached downstream ICs using a custom HID-based flashing protocol. It does not support any RTS54xx device using the HUB update protocol. Other devices connected to the RTS54HIDxx using I2C will be supported soon. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * `com.realtek.rts54` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_0BDA&PID_1100` Child I²C devices are created using the device number as a suffix, for instance: * `USB\VID_0BDA&PID_1100&I2C_01` ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x0BDA` ## Quirk Use This plugin uses the following plugin-specific quirks: ### Rts54TargetAddr The target address of a child module. Since: 1.1.3 ### Rts54I2cSpeed The I2C speed to operate at (0, 1, 2). Since: 1.1.3 ### Rts54RegisterAddrLen The I2C register address length of commands. Since: 1.1.3 ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.2.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Ricky Wu: @AnyProblem fwupd-1.9.16/plugins/rts54hid/fu-rts54hid-common.h000066400000000000000000000030351460375044200216070ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2018 Realtek Semiconductor Corporation * Copyright (C) 2018 Dell Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_RTS54HID_TRANSFER_BLOCK_SIZE 0x80 #define FU_RTS54FU_HID_REPORT_LENGTH 0xc0 /* [vendor-cmd:64] [data-payload:128] */ #define FU_RTS54HID_CMD_BUFFER_OFFSET_DATA 0x40 typedef struct __attribute__((packed)) { guint8 target_addr; guint8 data_sz; guint8 speed; } FuRts54HidI2cParameters; typedef struct __attribute__((packed)) { guint8 cmd; guint8 ext; union { guint32 dwregaddr; struct { guint8 cmd_data0; guint8 cmd_data1; guint8 cmd_data2; guint8 cmd_data3; }; }; guint16 bufferlen; union { FuRts54HidI2cParameters parameters_i2c; guint32 parameters; }; } FuRts54HidCmdBuffer; typedef enum { FU_RTS54HID_I2C_SPEED_250K, FU_RTS54HID_I2C_SPEED_400K, FU_RTS54HID_I2C_SPEED_800K, /* */ FU_RTS54HID_I2C_SPEED_LAST, } FuRts54HidI2cSpeed; typedef enum { FU_RTS54HID_CMD_READ_DATA = 0xc0, FU_RTS54HID_CMD_WRITE_DATA = 0x40, /* */ FU_RTS54HID_CMD_LAST, } FuRts54HidCmd; typedef enum { FU_RTS54HID_EXT_MCUMODIFYCLOCK = 0x06, FU_RTS54HID_EXT_READ_STATUS = 0x09, FU_RTS54HID_EXT_I2C_WRITE = 0xc6, FU_RTS54HID_EXT_WRITEFLASH = 0xc8, FU_RTS54HID_EXT_I2C_READ = 0xd6, FU_RTS54HID_EXT_READFLASH = 0xd8, FU_RTS54HID_EXT_VERIFYUPDATE = 0xd9, FU_RTS54HID_EXT_ERASEBANK = 0xe8, FU_RTS54HID_EXT_RESET2FLASH = 0xe9, /* */ FU_RTS54HID_EXT_LAST, } FuRts54HidExt; fwupd-1.9.16/plugins/rts54hid/fu-rts54hid-device.c000066400000000000000000000266041460375044200215600ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-rts54hid-common.h" #include "fu-rts54hid-device.h" struct _FuRts54HidDevice { FuHidDevice parent_instance; gboolean fw_auth; gboolean dual_bank; }; G_DEFINE_TYPE(FuRts54HidDevice, fu_rts54hid_device, FU_TYPE_HID_DEVICE) static void fu_rts54hid_device_to_string(FuDevice *device, guint idt, GString *str) { FuRts54HidDevice *self = FU_RTS54HID_DEVICE(device); fu_string_append_kb(str, idt, "FwAuth", self->fw_auth); fu_string_append_kb(str, idt, "DualBank", self->dual_bank); } static gboolean fu_rts54hid_device_set_clock_mode(FuRts54HidDevice *self, gboolean enable, GError **error) { FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_MCUMODIFYCLOCK, .cmd_data0 = (guint8)enable, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = 0, .parameters = 0, }; guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0}; if (!fu_memcpy_safe(buf, sizeof(buf), 0x0, /* dst */ (const guint8 *)&cmd_buffer, sizeof(cmd_buffer), 0x0, /* src */ sizeof(cmd_buffer), error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), 0x0, buf, sizeof(buf), FU_RTS54HID_DEVICE_TIMEOUT * 2, FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to set clock-mode=%i: ", enable); return FALSE; } return TRUE; } static gboolean fu_rts54hid_device_reset_to_flash(FuRts54HidDevice *self, GError **error) { FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_RESET2FLASH, .dwregaddr = 0, .bufferlen = 0, .parameters = 0, }; guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0}; if (!fu_memcpy_safe(buf, sizeof(buf), 0x0, /* dst */ (const guint8 *)&cmd_buffer, sizeof(cmd_buffer), 0x0, /* src */ sizeof(cmd_buffer), error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), 0x0, buf, sizeof(buf), FU_RTS54HID_DEVICE_TIMEOUT * 2, FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to soft reset: "); return FALSE; } return TRUE; } static gboolean fu_rts54hid_device_write_flash(FuRts54HidDevice *self, guint32 addr, const guint8 *data, guint16 data_sz, GError **error) { FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_WRITEFLASH, .dwregaddr = GUINT32_TO_LE(addr), .bufferlen = GUINT16_TO_LE(data_sz), .parameters = 0, }; guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0}; g_return_val_if_fail(data_sz <= 128, FALSE); g_return_val_if_fail(data != NULL, FALSE); g_return_val_if_fail(data_sz != 0, FALSE); if (!fu_memcpy_safe(buf, sizeof(buf), 0x0, /* dst */ (const guint8 *)&cmd_buffer, sizeof(cmd_buffer), 0x0, /* src */ sizeof(cmd_buffer), error)) return FALSE; if (!fu_memcpy_safe(buf, sizeof(buf), FU_RTS54HID_CMD_BUFFER_OFFSET_DATA, /* dst */ data, data_sz, 0x0, /* src */ data_sz, error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), 0x0, buf, sizeof(buf), FU_RTS54HID_DEVICE_TIMEOUT * 2, FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write flash @%08x: ", (guint)addr); return FALSE; } return TRUE; } static gboolean fu_rts54hid_device_verify_update_fw(FuRts54HidDevice *self, FuProgress *progress, GError **error) { const FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_VERIFYUPDATE, .cmd_data0 = 1, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = GUINT16_TO_LE(1), .parameters = 0, }; guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0}; /* set then get */ if (!fu_memcpy_safe(buf, sizeof(buf), 0x0, /* dst */ (const guint8 *)&cmd_buffer, sizeof(cmd_buffer), 0x0, /* src */ sizeof(cmd_buffer), error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), 0x0, buf, sizeof(buf), FU_RTS54HID_DEVICE_TIMEOUT * 2, FU_HID_DEVICE_FLAG_NONE, error)) return FALSE; fu_device_sleep_full(FU_DEVICE(self), 4000, progress); /* ms */ if (!fu_hid_device_get_report(FU_HID_DEVICE(self), 0x0, buf, sizeof(buf), FU_RTS54HID_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_NONE, error)) return FALSE; /* check device status */ if (buf[0] != 0x01) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "firmware flash failed"); return FALSE; } /* success */ return TRUE; } static gboolean fu_rts54hid_device_erase_spare_bank(FuRts54HidDevice *self, GError **error) { FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_ERASEBANK, .cmd_data0 = 0, .cmd_data1 = 1, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = 0, .parameters = 0, }; guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0}; if (!fu_memcpy_safe(buf, sizeof(buf), 0x0, /* dst */ (const guint8 *)&cmd_buffer, sizeof(cmd_buffer), 0x0, /* src */ sizeof(cmd_buffer), error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), 0x0, buf, sizeof(buf), FU_RTS54HID_DEVICE_TIMEOUT * 2, FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to erase spare bank: "); return FALSE; } return TRUE; } static gboolean fu_rts54hid_device_ensure_status(FuRts54HidDevice *self, GError **error) { const FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_READ_DATA, .ext = FU_RTS54HID_EXT_READ_STATUS, .cmd_data0 = 0, .cmd_data1 = 0, .cmd_data2 = 0, .cmd_data3 = 0, .bufferlen = GUINT16_TO_LE(32), .parameters = 0, }; guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0}; g_autofree gchar *version = NULL; /* set then get */ if (!fu_memcpy_safe(buf, sizeof(buf), 0x0, /* dst */ (const guint8 *)&cmd_buffer, sizeof(cmd_buffer), 0x0, /* src */ sizeof(cmd_buffer), error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), 0x0, buf, sizeof(buf), FU_RTS54HID_DEVICE_TIMEOUT * 2, FU_HID_DEVICE_FLAG_NONE, error)) return FALSE; if (!fu_hid_device_get_report(FU_HID_DEVICE(self), 0x0, buf, sizeof(buf), FU_RTS54HID_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_NONE, error)) return FALSE; /* check the hardware capabilities */ self->dual_bank = (buf[7] & 0xf0) == 0x80; self->fw_auth = (buf[13] & 0x02) > 0; /* hub version is more accurate than bcdVersion */ version = g_strdup_printf("%x.%x", buf[10], buf[11]); fu_device_set_version(FU_DEVICE(self), version); return TRUE; } static gboolean fu_rts54hid_device_setup(FuDevice *device, GError **error) { FuRts54HidDevice *self = FU_RTS54HID_DEVICE(device); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_rts54hid_device_parent_class)->setup(device, error)) return FALSE; /* check this device is correct */ if (!fu_rts54hid_device_ensure_status(self, error)) return FALSE; /* both conditions must be set */ if (!self->fw_auth) { fu_device_set_update_error(device, "device does not support authentication"); } else if (!self->dual_bank) { fu_device_set_update_error(device, "device does not support dual-bank updating"); } else { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); } /* success */ return TRUE; } static gboolean fu_rts54hid_device_close(FuDevice *device, GError **error) { FuRts54HidDevice *self = FU_RTS54HID_DEVICE(device); /* set MCU to normal clock rate */ if (!fu_rts54hid_device_set_clock_mode(self, FALSE, error)) return FALSE; /* FuHidDevice->close */ return FU_DEVICE_CLASS(fu_rts54hid_device_parent_class)->close(device, error); } static gboolean fu_rts54hid_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuRts54HidDevice *self = FU_RTS54HID_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 46, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 52, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reset"); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* set MCU to high clock rate for better ISP performance */ if (!fu_rts54hid_device_set_clock_mode(self, TRUE, error)) return FALSE; /* erase spare flash bank only if it is not empty */ if (!fu_rts54hid_device_erase_spare_bank(self, error)) return FALSE; fu_progress_step_done(progress); /* write each block */ chunks = fu_chunk_array_new_from_bytes(fw, 0x00, FU_RTS54HID_TRANSFER_BLOCK_SIZE); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); /* write chunk */ if (!fu_rts54hid_device_write_flash(self, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* get device to authenticate the firmware */ if (!fu_rts54hid_device_verify_update_fw(self, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* send software reset to run available flash code */ if (!fu_rts54hid_device_reset_to_flash(self, error)) return FALSE; fu_progress_step_done(progress); /* success! */ return TRUE; } static void fu_rts54hid_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 62, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 38, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_rts54hid_device_init(FuRts54HidDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.realtek.rts54"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); } static void fu_rts54hid_device_class_init(FuRts54HidDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_rts54hid_device_write_firmware; klass_device->to_string = fu_rts54hid_device_to_string; klass_device->setup = fu_rts54hid_device_setup; klass_device->close = fu_rts54hid_device_close; klass_device->set_progress = fu_rts54hid_device_set_progress; } fwupd-1.9.16/plugins/rts54hid/fu-rts54hid-device.h000066400000000000000000000005431460375044200215570ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_RTS54HID_DEVICE (fu_rts54hid_device_get_type()) G_DECLARE_FINAL_TYPE(FuRts54HidDevice, fu_rts54hid_device, FU, RTS54HID_DEVICE, FuHidDevice) #define FU_RTS54HID_DEVICE_TIMEOUT 1000 /* ms */ fwupd-1.9.16/plugins/rts54hid/fu-rts54hid-module.c000066400000000000000000000157641460375044200216130ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-rts54hid-common.h" #include "fu-rts54hid-device.h" #include "fu-rts54hid-module.h" struct _FuRts54HidModule { FuDevice parent_instance; guint8 target_addr; guint8 i2c_speed; guint8 register_addr_len; }; G_DEFINE_TYPE(FuRts54HidModule, fu_rts54hid_module, FU_TYPE_DEVICE) static void fu_rts54hid_module_to_string(FuDevice *module, guint idt, GString *str) { FuRts54HidModule *self = FU_RTS54HID_MODULE(module); fu_string_append_kx(str, idt, "TargetAddr", self->target_addr); fu_string_append_kx(str, idt, "I2cSpeed", self->i2c_speed); fu_string_append_kx(str, idt, "RegisterAddrLen", self->register_addr_len); } static FuRts54HidDevice * fu_rts54hid_module_get_parent(FuRts54HidModule *self, GError **error) { FuDevice *parent = fu_device_get_parent(FU_DEVICE(self)); if (parent == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no parent set"); return NULL; } return FU_RTS54HID_DEVICE(parent); } static gboolean fu_rts54hid_module_i2c_write(FuRts54HidModule *self, const guint8 *data, guint8 data_sz, GError **error) { FuRts54HidDevice *parent; const FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_I2C_WRITE, .dwregaddr = 0, .bufferlen = GUINT16_TO_LE(data_sz), .parameters_i2c = {.target_addr = self->target_addr, .data_sz = self->register_addr_len, .speed = self->i2c_speed | 0x80}, }; guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0}; g_return_val_if_fail(data_sz <= 128, FALSE); g_return_val_if_fail(data != NULL, FALSE); g_return_val_if_fail(data_sz != 0, FALSE); /* get parent to issue command */ parent = fu_rts54hid_module_get_parent(self, error); if (parent == NULL) return FALSE; if (!fu_memcpy_safe(buf, sizeof(buf), 0x0, /* dst */ (const guint8 *)&cmd_buffer, sizeof(cmd_buffer), 0x0, /* src */ sizeof(cmd_buffer), error)) return FALSE; if (!fu_memcpy_safe(buf, sizeof(buf), FU_RTS54HID_CMD_BUFFER_OFFSET_DATA, /* dst */ data, data_sz, 0x0, /* src */ data_sz, error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(parent), 0x0, buf, sizeof(buf), FU_RTS54HID_DEVICE_TIMEOUT * 2, FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write i2c @%04x: ", self->target_addr); return FALSE; } return TRUE; } static gboolean fu_rts54hid_module_i2c_read(FuRts54HidModule *self, guint32 cmd, guint8 *data, guint8 data_sz, GError **error) { FuRts54HidDevice *parent; const FuRts54HidCmdBuffer cmd_buffer = { .cmd = FU_RTS54HID_CMD_WRITE_DATA, .ext = FU_RTS54HID_EXT_I2C_READ, .dwregaddr = GUINT32_TO_LE(cmd), .bufferlen = GUINT16_TO_LE(data_sz), .parameters_i2c = {.target_addr = self->target_addr, .data_sz = self->register_addr_len, .speed = self->i2c_speed | 0x80}, }; guint8 buf[FU_RTS54FU_HID_REPORT_LENGTH] = {0}; g_return_val_if_fail(data_sz <= 192, FALSE); g_return_val_if_fail(data != NULL, FALSE); g_return_val_if_fail(data_sz != 0, FALSE); /* get parent to issue command */ parent = fu_rts54hid_module_get_parent(self, error); if (parent == NULL) return FALSE; /* read from module */ if (!fu_memcpy_safe(buf, sizeof(buf), 0x0, /* dst */ (const guint8 *)&cmd_buffer, sizeof(cmd_buffer), 0x0, /* src */ sizeof(cmd_buffer), error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(parent), 0x0, buf, sizeof(buf), FU_RTS54HID_DEVICE_TIMEOUT * 2, FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write i2c @%04x: ", self->target_addr); return FALSE; } if (!fu_hid_device_get_report(FU_HID_DEVICE(parent), 0x0, buf, sizeof(buf), FU_RTS54HID_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_NONE, error)) return FALSE; return fu_memcpy_safe(data, data_sz, 0x0, buf, sizeof(buf), FU_RTS54HID_CMD_BUFFER_OFFSET_DATA, data_sz, error); } static gboolean fu_rts54hid_module_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuRts54HidModule *self = FU_RTS54HID_MODULE(device); guint64 tmp = 0; /* load target address from quirks */ if (g_strcmp0(key, "Rts54TargetAddr") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->target_addr = tmp; return TRUE; } /* load i2c speed from quirks */ if (g_strcmp0(key, "Rts54I2cSpeed") == 0) { if (!fu_strtoull(value, &tmp, 0, FU_RTS54HID_I2C_SPEED_LAST - 1, error)) return FALSE; self->i2c_speed = tmp; return TRUE; } /* load register address length from quirks */ if (g_strcmp0(key, "Rts54RegisterAddrLen") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; self->register_addr_len = tmp; return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static gboolean fu_rts54hid_module_write_firmware(FuDevice *module, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuRts54HidModule *self = FU_RTS54HID_MODULE(module); g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* build packets */ chunks = fu_chunk_array_new_from_bytes(fw, 0x00, FU_RTS54HID_TRANSFER_BLOCK_SIZE); if (0) { if (!fu_rts54hid_module_i2c_read(self, 0x0000, NULL, 0, error)) return FALSE; if (!fu_rts54hid_module_i2c_write(self, NULL, 0, error)) return FALSE; } /* write each block */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); /* write chunk */ if (!fu_rts54hid_module_i2c_write(self, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; /* update progress */ fu_progress_set_percentage_full(progress, (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } /* success! */ return TRUE; } static void fu_rts54hid_module_init(FuRts54HidModule *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); } static void fu_rts54hid_module_class_init(FuRts54HidModuleClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_rts54hid_module_write_firmware; klass_device->to_string = fu_rts54hid_module_to_string; klass_device->set_quirk_kv = fu_rts54hid_module_set_quirk_kv; } fwupd-1.9.16/plugins/rts54hid/fu-rts54hid-module.h000066400000000000000000000004561460375044200216100ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_RTS54HID_MODULE (fu_rts54hid_module_get_type()) G_DECLARE_FINAL_TYPE(FuRts54HidModule, fu_rts54hid_module, FU, RTS54HID_MODULE, FuDevice) fwupd-1.9.16/plugins/rts54hid/fu-rts54hid-plugin.c000066400000000000000000000024301460375044200216060ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-rts54hid-device.h" #include "fu-rts54hid-module.h" #include "fu-rts54hid-plugin.h" struct _FuRts54HidPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuRts54HidPlugin, fu_rts54hid_plugin, FU_TYPE_PLUGIN) static void fu_rts54hid_plugin_init(FuRts54HidPlugin *self) { } static void fu_rts54hid_plugin_object_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_set_name(plugin, "rts54hid"); } static void fu_rts54hid_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "Rts54TargetAddr"); fu_context_add_quirk_key(ctx, "Rts54I2cSpeed"); fu_context_add_quirk_key(ctx, "Rts54RegisterAddrLen"); fu_plugin_add_device_gtype(plugin, FU_TYPE_RTS54HID_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_RTS54HID_MODULE); } static void fu_rts54hid_plugin_class_init(FuRts54HidPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->constructed = fu_rts54hid_plugin_object_constructed; plugin_class->constructed = fu_rts54hid_plugin_constructed; } fwupd-1.9.16/plugins/rts54hid/fu-rts54hid-plugin.h000066400000000000000000000003561460375044200216200ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuRts54HidPlugin, fu_rts54hid_plugin, FU, RTS54HID_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/rts54hid/meson.build000066400000000000000000000006721460375044200202400ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginRts54Hid"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('rts54hid.quirk') plugin_builtins += static_library('fu_plugin_rts54hid', sources: [ 'fu-rts54hid-device.c', 'fu-rts54hid-module.c', 'fu-rts54hid-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/rts54hid/rts54hid.quirk000066400000000000000000000007021460375044200206130ustar00rootroot00000000000000# Realtek USBHub (HID Device) [USB\VID_0BDA&PID_5A00] Plugin = rts54hid Flags = enforce-requires GType = FuRts54HidDevice FirmwareSizeMin = 0x10000 FirmwareSizeMax = 0x40000 Children = FuRts54HidModule|USB\VID_0BDA&PID_5A00&I2C_01 # this is a fictitious example... [USB\VID_0BDA&PID_5A00&I2C_01] Plugin = rts54hid Name = HDMI Converter Flags = updatable FirmwareSize = 0x20000 Rts54TargetAddr = 0x00 Rts54I2cSpeed = 0x00 Rts54RegisterAddrLen = 0x04 fwupd-1.9.16/plugins/rts54hub/000077500000000000000000000000001460375044200161035ustar00rootroot00000000000000fwupd-1.9.16/plugins/rts54hub/README.md000066400000000000000000000024041460375044200173620ustar00rootroot00000000000000--- title: Plugin: RTS54HUB --- ## Introduction This plugin allows the user to update any supported hub and attached downstream ICs using a custom HUB-based flashing protocol. It does not support any RTS54xx device using the HID update protocol. Other devices connected to the RTS54xx using I2C will be supported soon. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol IDs: * `com.realtek.rts54` * `com.realtek.rts54.i2c` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_0BDA&PID_5423` ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x0BDA` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.2.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Ricky Wu: @AnyProblem fwupd-1.9.16/plugins/rts54hub/data/000077500000000000000000000000001460375044200170145ustar00rootroot00000000000000fwupd-1.9.16/plugins/rts54hub/data/lsusb.txt000066400000000000000000000111441460375044200207060ustar00rootroot00000000000000Bus 001 Device 038: ID 0bda:5423 Realtek Semiconductor Corp. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.10 bDeviceClass 9 Hub bDeviceSubClass 0 bDeviceProtocol 2 TT per port bMaxPacketSize0 64 idVendor 0x0bda Realtek Semiconductor Corp. idProduct 0x5423 bcdDevice 1.19 iManufacturer 1 Generic iProduct 2 4-Port USB 2.0 Hub iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 41 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xe0 Self Powered Remote Wakeup MaxPower 0mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 9 Hub bInterfaceSubClass 0 bInterfaceProtocol 1 Single TT iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0001 1x 1 bytes bInterval 12 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 1 bNumEndpoints 1 bInterfaceClass 9 Hub bInterfaceSubClass 0 bInterfaceProtocol 2 TT per port iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0001 1x 1 bytes bInterval 12 Hub Descriptor: bLength 9 bDescriptorType 41 nNbrPorts 5 wHubCharacteristic 0x00a9 Per-port power switching Per-port overcurrent protection TT think time 16 FS bits Port indicators bPwrOn2PwrGood 0 * 2 milli seconds bHubContrCurrent 100 milli Ampere DeviceRemovable 0x00 PortPwrCtrlMask 0xff Hub Port Status: Port 1: 0000.0100 power Port 2: 0000.0100 power Port 3: 0000.0100 power Port 4: 0000.0100 power Port 5: 0000.0503 highspeed power enable connect Binary Object Store Descriptor: bLength 5 bDescriptorType 15 wTotalLength 73 bNumDeviceCaps 5 USB 2.0 Extension Device Capability: bLength 7 bDescriptorType 16 bDevCapabilityType 2 bmAttributes 0x0000f41e BESL Link Power Management (LPM) Supported BESL value 1024 us Deep BESL value 61440 us SuperSpeed USB Device Capability: bLength 10 bDescriptorType 16 bDevCapabilityType 3 bmAttributes 0x00 wSpeedsSupported 0x000e Device can operate at Full Speed (12Mbps) Device can operate at High Speed (480Mbps) Device can operate at SuperSpeed (5Gbps) bFunctionalitySupport 1 Lowest fully-functional device speed is Full Speed (12Mbps) bU1DevExitLat 10 micro seconds bU2DevExitLat 1023 micro seconds SuperSpeedPlus USB Device Capability: bLength 28 bDescriptorType 16 bDevCapabilityType 10 bmAttributes 0x00000023 Sublink Speed Attribute count 3 Sublink Speed ID count 1 wFunctionalitySupport 0x1100 bmSublinkSpeedAttr[0] 0x00050030 Speed Attribute ID: 0 5Gb/s Symmetric RX SuperSpeed bmSublinkSpeedAttr[1] 0x000500b0 Speed Attribute ID: 0 5Gb/s Symmetric TX SuperSpeed bmSublinkSpeedAttr[2] 0x000a4031 Speed Attribute ID: 1 10Gb/s Symmetric RX SuperSpeedPlus bmSublinkSpeedAttr[3] 0x000a40b1 Speed Attribute ID: 1 10Gb/s Symmetric TX SuperSpeedPlus Container ID Device Capability: bLength 20 bDescriptorType 16 bDevCapabilityType 4 bReserved 0 ContainerID {20b9cde5-7039-e011-a935-0002a5d5c51b} ** UNRECOGNIZED: 03 10 0b Device Status: 0x0001 Self Powered fwupd-1.9.16/plugins/rts54hub/fu-rts54hub-device.c000066400000000000000000000376111460375044200216040ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-rts54hub-device.h" struct _FuRts54HubDevice { FuUsbDevice parent_instance; gboolean fw_auth; gboolean dual_bank; gboolean running_on_flash; guint8 vendor_cmd; }; G_DEFINE_TYPE(FuRts54HubDevice, fu_rts54hub_device, FU_TYPE_USB_DEVICE) #define FU_RTS54HUB_DEVICE_TIMEOUT 1000 /* ms */ #define FU_RTS54HUB_DEVICE_TIMEOUT_RW 1000 /* ms */ #define FU_RTS54HUB_DEVICE_TIMEOUT_ERASE 5000 /* ms */ #define FU_RTS54HUB_DEVICE_TIMEOUT_AUTH 10000 /* ms */ #define FU_RTS54HUB_DEVICE_BLOCK_SIZE 4096 #define FU_RTS54HUB_DEVICE_STATUS_LEN 25 #define FU_RTS54HUB_I2C_CONFIG_REQUEST 0xF6 #define FU_RTS54HUB_I2C_WRITE_REQUEST 0xC6 #define FU_RTS54HUB_I2C_READ_REQUEST 0xD6 typedef enum { FU_RTS54HUB_VENDOR_CMD_NONE = 0x00, FU_RTS54HUB_VENDOR_CMD_STATUS = 1 << 0, FU_RTS54HUB_VENDOR_CMD_FLASH = 1 << 1, } FuRts54HubVendorCmd; static void fu_rts54hub_device_to_string(FuDevice *device, guint idt, GString *str) { FuRts54HubDevice *self = FU_RTS54HUB_DEVICE(device); fu_string_append_kb(str, idt, "FwAuth", self->fw_auth); fu_string_append_kb(str, idt, "DualBank", self->dual_bank); fu_string_append_kb(str, idt, "RunningOnFlash", self->running_on_flash); } gboolean fu_rts54hub_device_i2c_config(FuRts54HubDevice *self, guint8 target_addr, guint8 sub_length, FuRts54HubI2cSpeed speed, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); guint16 value = 0; guint16 index = 0x8080; value = ((guint16)target_addr << 8) | sub_length; index += speed; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_RTS54HUB_I2C_CONFIG_REQUEST, value, /* value */ index, /* idx */ NULL, 0, /* data */ NULL, /* actual */ FU_RTS54HUB_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to issue i2c Conf cmd 0x%02x: ", target_addr); return FALSE; } return TRUE; } gboolean fu_rts54hub_device_i2c_write(FuRts54HubDevice *self, guint32 sub_addr, const guint8 *data, gsize datasz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); g_autofree guint8 *datarw = fu_memdup_safe(data, datasz, error); if (datarw == NULL) return FALSE; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_RTS54HUB_I2C_WRITE_REQUEST, sub_addr, /* value */ 0x0000, /* idx */ datarw, datasz, /* data */ NULL, FU_RTS54HUB_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to write I2C: "); return FALSE; } return TRUE; } gboolean fu_rts54hub_device_i2c_read(FuRts54HubDevice *self, guint32 sub_addr, guint8 *data, gsize datasz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_RTS54HUB_I2C_READ_REQUEST, 0x0000, sub_addr, data, datasz, NULL, FU_RTS54HUB_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to read I2C: "); return FALSE; } return TRUE; } static gboolean fu_rts54hub_device_highclockmode(FuRts54HubDevice *self, guint16 value, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0x06, /* request */ value, /* value */ 0, /* idx */ NULL, 0, /* data */ NULL, /* actual */ FU_RTS54HUB_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to set highclockmode: "); return FALSE; } return TRUE; } static gboolean fu_rts54hub_device_reset_flash(FuRts54HubDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xC0 + 0x29, /* request */ 0x0, /* value */ 0x0, /* idx */ NULL, 0, /* data */ NULL, /* actual */ FU_RTS54HUB_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to reset flash: "); return FALSE; } return TRUE; } static gboolean fu_rts54hub_device_write_flash(FuRts54HubDevice *self, guint32 addr, const guint8 *data, gsize datasz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_len = 0; g_autofree guint8 *datarw = NULL; /* make mutable */ datarw = fu_memdup_safe(data, datasz, error); if (datarw == NULL) return FALSE; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xC0 + 0x08, /* request */ addr % (1 << 16), /* value */ addr / (1 << 16), /* idx */ datarw, datasz, /* data */ &actual_len, FU_RTS54HUB_DEVICE_TIMEOUT_RW, NULL, error)) { g_prefix_error(error, "failed to write flash: "); return FALSE; } if (actual_len != datasz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only wrote %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } return TRUE; } #if 0 static gboolean fu_rts54hub_device_read_flash (FuRts54HubDevice *self, guint32 addr, guint8 *data, gsize datasz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); gsize actual_len = 0; if (!g_usb_device_control_transfer (usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xC0 + 0x18, /* request */ addr % (1 << 16), /* value */ addr / (1 << 16), /* idx */ data, datasz, /* data */ &actual_len, FU_RTS54HUB_DEVICE_TIMEOUT_RW, NULL, error)) { g_prefix_error (error, "failed to read flash: "); return FALSE; } if (actual_len != datasz) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only read %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } return TRUE; } #endif static gboolean fu_rts54hub_device_flash_authentication(FuRts54HubDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xC0 + 0x19, /* request */ 0x01, /* value */ 0x0, /* idx */ NULL, 0, /* data */ NULL, /* actual */ FU_RTS54HUB_DEVICE_TIMEOUT_AUTH, NULL, error)) { g_prefix_error(error, "failed to authenticate: "); return FALSE; } return TRUE; } static gboolean fu_rts54hub_device_erase_flash(FuRts54HubDevice *self, guint8 erase_type, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xC0 + 0x28, /* request */ erase_type * 256, /* value */ 0x0, /* idx */ NULL, 0, /* data */ NULL, /* actual */ FU_RTS54HUB_DEVICE_TIMEOUT_ERASE, NULL, error)) { g_prefix_error(error, "failed to erase flash: "); return FALSE; } return TRUE; } gboolean fu_rts54hub_device_vendor_cmd(FuRts54HubDevice *self, guint8 value, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); /* don't set something that's already set */ if (self->vendor_cmd == value) { g_debug("skipping vendor command 0x%02x as already set", value); return TRUE; } if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0x02, /* request */ value, /* value */ 0x0bda, /* idx */ NULL, 0, /* data */ NULL, /* actual */ FU_RTS54HUB_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to issue vendor cmd 0x%02x: ", value); return FALSE; } self->vendor_cmd = value; return TRUE; } static gboolean fu_rts54hub_device_ensure_status(FuRts54HubDevice *self, GError **error) { guint8 data[FU_RTS54HUB_DEVICE_STATUS_LEN] = {0}; GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_len = 0; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0x09, /* request */ 0x0, /* value */ 0x0, /* idx */ data, sizeof(data), &actual_len, /* actual */ FU_RTS54HUB_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to get status: "); return FALSE; } if (actual_len != FU_RTS54HUB_DEVICE_STATUS_LEN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only read %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } /* check the hardware capabilities */ self->dual_bank = (data[7] & 0x80) == 0x80; self->fw_auth = (data[13] & 0x02) > 0; self->running_on_flash = (data[15] & 0x02) > 0; return TRUE; } static gboolean fu_rts54hub_device_setup(FuDevice *device, GError **error) { FuRts54HubDevice *self = FU_RTS54HUB_DEVICE(device); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_rts54hub_device_parent_class)->setup(device, error)) return FALSE; /* check this device is correct */ if (!fu_rts54hub_device_vendor_cmd(self, FU_RTS54HUB_VENDOR_CMD_STATUS, error)) { g_prefix_error(error, "failed to vendor enable: "); return FALSE; } if (!fu_rts54hub_device_ensure_status(self, error)) return FALSE; /* all three conditions must be set */ if (!self->running_on_flash) { fu_device_set_update_error(device, "device is abnormally running from ROM"); } else if (!self->fw_auth) { fu_device_set_update_error(device, "device does not support authentication"); } else if (!self->dual_bank) { fu_device_set_update_error(device, "device does not support dual-bank updating"); } else { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); } /* success */ return TRUE; } static gboolean fu_rts54hub_device_close(FuDevice *device, GError **error) { FuRts54HubDevice *self = FU_RTS54HUB_DEVICE(device); /* disable vendor commands */ if (self->vendor_cmd != FU_RTS54HUB_VENDOR_CMD_NONE) { if (!fu_rts54hub_device_vendor_cmd(self, FU_RTS54HUB_VENDOR_CMD_NONE, error)) { g_prefix_error(error, "failed to disable vendor command: "); return FALSE; } } /* FuUsbDevice->close */ return FU_DEVICE_CLASS(fu_rts54hub_device_parent_class)->close(device, error); } static gboolean fu_rts54hub_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuRts54HubDevice *self = FU_RTS54HUB_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 46, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 52, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* enable vendor commands */ if (!fu_rts54hub_device_vendor_cmd(self, FU_RTS54HUB_VENDOR_CMD_STATUS | FU_RTS54HUB_VENDOR_CMD_FLASH, error)) { g_prefix_error(error, "failed to cmd enable: "); return FALSE; } /* erase spare flash bank only if it is not empty */ if (!fu_rts54hub_device_erase_flash(self, 1, error)) return FALSE; fu_progress_step_done(progress); /* set MCU clock to high clock mode */ if (!fu_rts54hub_device_highclockmode(self, 0x0001, error)) { g_prefix_error(error, "failed to enable MCU clock: "); return FALSE; } /* set SPI controller clock to high clock mode */ if (!fu_rts54hub_device_highclockmode(self, 0x0101, error)) { g_prefix_error(error, "failed to enable SPI clock: "); return FALSE; } /* write each block */ chunks = fu_chunk_array_new_from_bytes(fw, 0x00, FU_RTS54HUB_DEVICE_BLOCK_SIZE); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); /* write chunk */ if (!fu_rts54hub_device_write_flash(self, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* get device to authenticate the firmware */ if (!fu_rts54hub_device_flash_authentication(self, error)) return FALSE; fu_progress_step_done(progress); /* send software reset to run available flash code */ if (!fu_rts54hub_device_reset_flash(self, error)) return FALSE; fu_progress_step_done(progress); /* don't reset the vendor command enable, the device will be rebooted */ self->vendor_cmd = FU_RTS54HUB_VENDOR_CMD_NONE; /* success! */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static FuFirmware * fu_rts54hub_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0; guint8 tmp = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); if (!fu_memread_uint8_safe(buf, bufsz, 0x7ef3, &tmp, error)) return NULL; if ((tmp & 0xf0) != 0x80) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware needs to be dual bank"); return NULL; } return fu_firmware_new_from_bytes(fw); } static void fu_rts54hub_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 62, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 38, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_rts54hub_device_init(FuRts54HubDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.realtek.rts54"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } static void fu_rts54hub_device_class_init(FuRts54HubDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_rts54hub_device_write_firmware; klass_device->setup = fu_rts54hub_device_setup; klass_device->to_string = fu_rts54hub_device_to_string; klass_device->prepare_firmware = fu_rts54hub_device_prepare_firmware; klass_device->close = fu_rts54hub_device_close; klass_device->set_progress = fu_rts54hub_device_set_progress; } fwupd-1.9.16/plugins/rts54hub/fu-rts54hub-device.h000066400000000000000000000022531460375044200216030ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_RTS54HUB_DEVICE (fu_rts54hub_device_get_type()) typedef enum { FU_RTS54HUB_I2C_SPEED_100K, FU_RTS54HUB_I2C_SPEED_200K, FU_RTS54HUB_I2C_SPEED_300K, FU_RTS54HUB_I2C_SPEED_400K, FU_RTS54HUB_I2C_SPEED_500K, FU_RTS54HUB_I2C_SPEED_600K, FU_RTS54HUB_I2C_SPEED_700K, FU_RTS54HUB_I2C_SPEED_800K, FU_RTS54HUB_I2C_SPEED_LAST } FuRts54HubI2cSpeed; G_DECLARE_FINAL_TYPE(FuRts54HubDevice, fu_rts54hub_device, FU, RTS54HUB_DEVICE, FuUsbDevice) gboolean fu_rts54hub_device_vendor_cmd(FuRts54HubDevice *self, guint8 value, GError **error); gboolean fu_rts54hub_device_i2c_config(FuRts54HubDevice *self, guint8 target_addr, guint8 sub_length, FuRts54HubI2cSpeed speed, GError **error); gboolean fu_rts54hub_device_i2c_write(FuRts54HubDevice *self, guint32 sub_addr, const guint8 *data, gsize datasz, GError **error); gboolean fu_rts54hub_device_i2c_read(FuRts54HubDevice *self, guint32 sub_addr, guint8 *data, gsize datasz, GError **error); fwupd-1.9.16/plugins/rts54hub/fu-rts54hub-plugin.c000066400000000000000000000026461460375044200216430ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-rts54hub-device.h" #include "fu-rts54hub-plugin.h" #include "fu-rts54hub-rtd21xx-background.h" #include "fu-rts54hub-rtd21xx-foreground.h" struct _FuRts54HubPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuRts54HubPlugin, fu_rts54hub_plugin, FU_TYPE_PLUGIN) static void fu_rts54hub_plugin_init(FuRts54HubPlugin *self) { } static void fu_rts54hub_plugin_object_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_set_name(plugin, "rts54hub"); } static void fu_rts54hub_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "Rts54TargetAddr"); fu_context_add_quirk_key(ctx, "Rts54I2cSpeed"); fu_context_add_quirk_key(ctx, "Rts54RegisterAddrLen"); fu_plugin_add_device_gtype(plugin, FU_TYPE_RTS54HUB_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_RTS54HUB_RTD21XX_BACKGROUND); fu_plugin_add_device_gtype(plugin, FU_TYPE_RTS54HUB_RTD21XX_FOREGROUND); } static void fu_rts54hub_plugin_class_init(FuRts54HubPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->constructed = fu_rts54hub_plugin_object_constructed; plugin_class->constructed = fu_rts54hub_plugin_constructed; } fwupd-1.9.16/plugins/rts54hub/fu-rts54hub-plugin.h000066400000000000000000000003561460375044200216440ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuRts54HubPlugin, fu_rts54hub_plugin, FU, RTS54HUB_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/rts54hub/fu-rts54hub-rtd21xx-background.c000066400000000000000000000244631460375044200237770ustar00rootroot00000000000000/* * Copyright (C) 2021 Realtek Corporation * Copyright (C) 2021 Ricky Wu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-rts54hub-device.h" #include "fu-rts54hub-rtd21xx-background.h" struct _FuRts54hubRtd21xxBackground { FuRts54hubRtd21xxDevice parent_instance; }; G_DEFINE_TYPE(FuRts54hubRtd21xxBackground, fu_rts54hub_rtd21xx_background, FU_TYPE_RTS54HUB_RTD21XX_DEVICE) #define ISP_DATA_BLOCKSIZE 32 #define ISP_PACKET_SIZE 257 typedef enum { ISP_CMD_FW_UPDATE_START = 0x01, ISP_CMD_FW_UPDATE_ISP_DONE = 0x02, ISP_CMD_GET_FW_INFO = 0x03, ISP_CMD_FW_UPDATE_EXIT = 0x04, ISP_CMD_GET_PROJECT_ID_ADDR = 0x05, ISP_CMD_SYNC_IDENTIFY_CODE = 0x06, } IspCmd; static gboolean fu_rts54hub_rtd21xx_ensure_version_unlocked(FuRts54hubRtd21xxBackground *self, GError **error) { guint8 buf_rep[7] = {0x00}; guint8 buf_req[] = {ISP_CMD_GET_FW_INFO}; g_autofree gchar *version = NULL; if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_BACKGROUND_OPCODE, buf_req, sizeof(buf_req), error)) { g_prefix_error(error, "failed to get version number: "); return FALSE; } /* wait for device ready */ fu_device_sleep(FU_DEVICE(self), 300); /* ms */ if (!fu_rts54hub_rtd21xx_device_i2c_read(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, 0x00, buf_rep, sizeof(buf_rep), error)) { g_prefix_error(error, "failed to get version number: "); return FALSE; } /* set version */ version = g_strdup_printf("%u.%u", buf_rep[1], buf_rep[2]); fu_device_set_version(FU_DEVICE(self), version); return TRUE; } static gboolean fu_rts54hub_rtd21xx_background_detach_raw(FuRts54hubRtd21xxBackground *self, GError **error) { guint8 buf = 0x02; if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), 0x6A, 0x31, &buf, sizeof(buf), error)) { g_prefix_error(error, "failed to detach: "); return FALSE; } /* wait for device ready */ fu_device_sleep(FU_DEVICE(self), 300); /* ms */ return TRUE; } static gboolean fu_rts54hub_rtd21xx_background_detach_cb(FuDevice *device, gpointer user_data, GError **error) { FuRts54hubRtd21xxBackground *self = FU_RTS54HUB_RTD21XX_BACKGROUND(device); guint8 status = 0xfe; if (!fu_rts54hub_rtd21xx_background_detach_raw(self, error)) return FALSE; if (!fu_rts54hub_rtd21xx_device_read_status_raw(FU_RTS54HUB_RTD21XX_DEVICE(self), &status, error)) return FALSE; if (status != ISP_STATUS_IDLE_SUCCESS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "detach status was 0x%02x", status); return FALSE; } /* success */ return TRUE; } static gboolean fu_rts54hub_rtd21xx_background_detach(FuDevice *device, FuProgress *progress, GError **error) { FuRts54HubDevice *parent = FU_RTS54HUB_DEVICE(fu_device_get_parent(device)); g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; return fu_device_retry(device, fu_rts54hub_rtd21xx_background_detach_cb, 100, NULL, error); } static gboolean fu_rts54hub_rtd21xx_background_attach(FuDevice *device, FuProgress *progress, GError **error) { FuRts54HubDevice *parent = FU_RTS54HUB_DEVICE(fu_device_get_parent(device)); FuRts54hubRtd21xxDevice *self = FU_RTS54HUB_RTD21XX_DEVICE(device); g_autoptr(FuDeviceLocker) locker = NULL; guint8 buf[] = {ISP_CMD_FW_UPDATE_EXIT}; /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; if (!fu_rts54hub_rtd21xx_device_i2c_write(self, UC_ISP_TARGET_ADDR, UC_BACKGROUND_OPCODE, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to attach: "); return FALSE; } fu_device_sleep_full(device, 1000, progress); /* ms */ /* success */ fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_rts54hub_rtd21xx_background_setup(FuDevice *device, GError **error) { FuRts54hubRtd21xxBackground *self = FU_RTS54HUB_RTD21XX_BACKGROUND(device); g_autoptr(FuDeviceLocker) locker = NULL; /* get version */ locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_device_detach, (FuDeviceLockerFunc)fu_device_attach, error); if (locker == NULL) return FALSE; if (!fu_rts54hub_rtd21xx_ensure_version_unlocked(self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_rts54hub_rtd21xx_background_reload(FuDevice *device, GError **error) { FuRts54HubDevice *parent = FU_RTS54HUB_DEVICE(fu_device_get_parent(device)); g_autoptr(FuDeviceLocker) locker = NULL; /* open parent device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; return fu_rts54hub_rtd21xx_background_setup(device, error); } static gboolean fu_rts54hub_rtd21xx_background_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuRts54hubRtd21xxBackground *self = FU_RTS54HUB_RTD21XX_BACKGROUND(device); const guint8 *fwbuf; gsize fwbufsz = 0; guint32 project_addr; guint8 project_id_count; guint8 read_buf[10] = {0x0}; guint8 write_buf[ISP_PACKET_SIZE] = {0x0}; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "setup"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "exit"); /* open device */ locker = fu_device_locker_new(self, error); if (locker == NULL) return FALSE; /* simple image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; fwbuf = g_bytes_get_data(fw, &fwbufsz); /* get project ID address */ write_buf[0] = ISP_CMD_GET_PROJECT_ID_ADDR; if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_BACKGROUND_OPCODE, write_buf, 1, error)) { g_prefix_error(error, "failed to get project ID address: "); return FALSE; } /* read back 6 bytes data */ fu_device_sleep(device, I2C_DELAY_AFTER_SEND * 40); if (!fu_rts54hub_rtd21xx_device_i2c_read(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, 0x00, read_buf, 6, error)) { g_prefix_error(error, "failed to read project ID: "); return FALSE; } if (read_buf[0] != ISP_STATUS_IDLE_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed project ID with error 0x%02x: ", read_buf[0]); return FALSE; } /* verify project ID */ project_addr = fu_memread_uint32(read_buf + 1, G_BIG_ENDIAN); project_id_count = read_buf[5]; write_buf[0] = ISP_CMD_SYNC_IDENTIFY_CODE; if (!fu_memcpy_safe(write_buf, sizeof(write_buf), 0x1, /* dst */ fwbuf, fwbufsz, project_addr, /* src */ project_id_count, error)) { g_prefix_error(error, "failed to write project ID from 0x%04x: ", project_addr); return FALSE; } if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_BACKGROUND_OPCODE, write_buf, project_id_count + 1, error)) { g_prefix_error(error, "failed to send fw update start cmd: "); return FALSE; } if (!fu_rts54hub_rtd21xx_device_read_status(FU_RTS54HUB_RTD21XX_DEVICE(self), NULL, error)) return FALSE; /* background FW update start command */ write_buf[0] = ISP_CMD_FW_UPDATE_START; fu_memwrite_uint16(write_buf + 1, ISP_DATA_BLOCKSIZE, G_BIG_ENDIAN); if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_BACKGROUND_OPCODE, write_buf, 3, error)) { g_prefix_error(error, "failed to send fw update start cmd: "); return FALSE; } fu_progress_step_done(progress); /* send data */ chunks = fu_chunk_array_new_from_bytes(fw, 0x00, ISP_DATA_BLOCKSIZE); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_rts54hub_rtd21xx_device_read_status(FU_RTS54HUB_RTD21XX_DEVICE(self), NULL, error)) return FALSE; if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_BACKGROUND_ISP_DATA_OPCODE, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to write @0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* update finish command */ if (!fu_rts54hub_rtd21xx_device_read_status(FU_RTS54HUB_RTD21XX_DEVICE(self), NULL, error)) return FALSE; write_buf[0] = ISP_CMD_FW_UPDATE_ISP_DONE; if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_BACKGROUND_OPCODE, write_buf, 1, error)) { g_prefix_error(error, "failed update finish cmd: "); return FALSE; } /* exit fw mode */ if (!fu_rts54hub_rtd21xx_device_read_status(FU_RTS54HUB_RTD21XX_DEVICE(self), NULL, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_rts54hub_rtd21xx_background_init(FuRts54hubRtd21xxBackground *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); } static void fu_rts54hub_rtd21xx_background_class_init(FuRts54hubRtd21xxBackgroundClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_rts54hub_rtd21xx_background_setup; klass_device->reload = fu_rts54hub_rtd21xx_background_reload; klass_device->attach = fu_rts54hub_rtd21xx_background_attach; klass_device->detach = fu_rts54hub_rtd21xx_background_detach; klass_device->write_firmware = fu_rts54hub_rtd21xx_background_write_firmware; } fwupd-1.9.16/plugins/rts54hub/fu-rts54hub-rtd21xx-background.h000066400000000000000000000010121460375044200237650ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricky Wu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-rts54hub-rtd21xx-device.h" #define FU_TYPE_RTS54HUB_RTD21XX_BACKGROUND (fu_rts54hub_rtd21xx_background_get_type()) G_DECLARE_FINAL_TYPE(FuRts54hubRtd21xxBackground, fu_rts54hub_rtd21xx_background, FU, RTS54HUB_RTD21XX_BACKGROUND, FuRts54hubRtd21xxDevice) fwupd-1.9.16/plugins/rts54hub/fu-rts54hub-rtd21xx-device.c000066400000000000000000000146021460375044200231110ustar00rootroot00000000000000/* * Copyright (C) 2021 Realtek Corporation * Copyright (C) 2021 Ricky Wu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-rts54hub-device.h" #include "fu-rts54hub-rtd21xx-device.h" typedef struct { guint8 target_addr; guint8 i2c_speed; guint8 register_addr_len; } FuRts54hubRtd21xxDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuRts54hubRtd21xxDevice, fu_rts54hub_rtd21xx_device, FU_TYPE_DEVICE) #define GET_PRIVATE(o) (fu_rts54hub_rtd21xx_device_get_instance_private(o)) typedef enum { VENDOR_CMD_DISABLE = 0x00, VENDOR_CMD_ENABLE = 0x01, VENDOR_CMD_ACCESS_FLASH = 0x02, } VendorCmd; static void fu_rts54hub_rtd21xx_device_to_string(FuDevice *module, guint idt, GString *str) { FuRts54hubRtd21xxDevice *self = FU_RTS54HUB_RTD21XX_DEVICE(module); FuRts54hubRtd21xxDevicePrivate *priv = GET_PRIVATE(self); fu_string_append_kx(str, idt, "TargetAddr", priv->target_addr); fu_string_append_kx(str, idt, "I2cSpeed", priv->i2c_speed); fu_string_append_kx(str, idt, "RegisterAddrLen", priv->register_addr_len); } static FuRts54HubDevice * fu_rts54hub_rtd21xx_device_get_parent(FuRts54hubRtd21xxDevice *self, GError **error) { FuDevice *parent = fu_device_get_parent(FU_DEVICE(self)); if (parent == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no parent set"); return NULL; } return FU_RTS54HUB_DEVICE(parent); } static gboolean fu_rts54hub_rtd21xx_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuRts54hubRtd21xxDevice *self = FU_RTS54HUB_RTD21XX_DEVICE(device); FuRts54hubRtd21xxDevicePrivate *priv = GET_PRIVATE(self); guint64 tmp = 0; /* load target address from quirks */ if (g_strcmp0(key, "Rts54TargetAddr") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->target_addr = tmp; return TRUE; } /* load i2c speed from quirks */ if (g_strcmp0(key, "Rts54I2cSpeed") == 0) { if (!fu_strtoull(value, &tmp, 0, FU_RTS54HUB_I2C_SPEED_LAST - 1, error)) return FALSE; priv->i2c_speed = tmp; return TRUE; } /* load register address length from quirks */ if (g_strcmp0(key, "Rts54RegisterAddrLen") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->register_addr_len = tmp; return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } gboolean fu_rts54hub_rtd21xx_device_i2c_write(FuRts54hubRtd21xxDevice *self, guint8 target_addr, guint8 sub_addr, const guint8 *data, gsize datasz, GError **error) { FuRts54HubDevice *parent; FuRts54hubRtd21xxDevicePrivate *priv = GET_PRIVATE(self); parent = fu_rts54hub_rtd21xx_device_get_parent(self, error); if (parent == NULL) return FALSE; if (!fu_rts54hub_device_vendor_cmd(parent, VENDOR_CMD_ENABLE, error)) return FALSE; if (target_addr != priv->target_addr) { if (!fu_rts54hub_device_i2c_config(parent, target_addr, 1, FU_RTS54HUB_I2C_SPEED_200K, error)) return FALSE; priv->target_addr = target_addr; } if (!fu_rts54hub_device_i2c_write(parent, sub_addr, data, datasz, error)) { g_prefix_error(error, "failed to write I2C @0x%02x:%02x: ", target_addr, sub_addr); return FALSE; } fu_device_sleep(FU_DEVICE(self), I2C_DELAY_AFTER_SEND); return TRUE; } gboolean fu_rts54hub_rtd21xx_device_i2c_read(FuRts54hubRtd21xxDevice *self, guint8 target_addr, guint8 sub_addr, guint8 *data, gsize datasz, GError **error) { FuRts54HubDevice *parent; FuRts54hubRtd21xxDevicePrivate *priv = GET_PRIVATE(self); parent = fu_rts54hub_rtd21xx_device_get_parent(self, error); if (parent == NULL) return FALSE; if (!fu_rts54hub_device_vendor_cmd(parent, VENDOR_CMD_ENABLE, error)) return FALSE; if (target_addr != priv->target_addr) { if (!fu_rts54hub_device_i2c_config(parent, target_addr, 1, FU_RTS54HUB_I2C_SPEED_200K, error)) return FALSE; priv->target_addr = target_addr; } if (!fu_rts54hub_device_i2c_read(parent, sub_addr, data, datasz, error)) { g_prefix_error(error, "failed to read I2C: "); return FALSE; } return TRUE; } gboolean fu_rts54hub_rtd21xx_device_read_status_raw(FuRts54hubRtd21xxDevice *self, guint8 *status, GError **error) { guint8 buf = 0x00; if (!fu_rts54hub_rtd21xx_device_i2c_read(self, UC_ISP_TARGET_ADDR, UC_FOREGROUND_STATUS, &buf, sizeof(buf), error)) return FALSE; if (status != NULL) *status = buf; return TRUE; } static gboolean fu_rts54hub_rtd21xx_device_read_status_cb(FuDevice *device, gpointer user_data, GError **error) { FuRts54hubRtd21xxDevice *self = FU_RTS54HUB_RTD21XX_DEVICE(device); guint8 status = 0xfd; if (!fu_rts54hub_rtd21xx_device_read_status_raw(self, &status, error)) return FALSE; if (status == ISP_STATUS_BUSY) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "status was 0x%02x", status); return FALSE; } return TRUE; } gboolean fu_rts54hub_rtd21xx_device_read_status(FuRts54hubRtd21xxDevice *self, guint8 *status, GError **error) { return fu_device_retry(FU_DEVICE(self), fu_rts54hub_rtd21xx_device_read_status_cb, 4200, status, error); } static void fu_rts54hub_rtd21xx_device_init(FuRts54hubRtd21xxDevice *self) { fu_device_add_icon(FU_DEVICE(self), "video-display"); fu_device_add_protocol(FU_DEVICE(self), "com.realtek.rts54.i2c"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_set_install_duration(FU_DEVICE(self), 100); /* seconds */ fu_device_set_logical_id(FU_DEVICE(self), "I2C"); fu_device_retry_set_delay(FU_DEVICE(self), 30); /* ms */ } static void fu_rts54hub_rtd21xx_device_class_init(FuRts54hubRtd21xxDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_rts54hub_rtd21xx_device_to_string; klass_device->set_quirk_kv = fu_rts54hub_rtd21xx_device_set_quirk_kv; } fwupd-1.9.16/plugins/rts54hub/fu-rts54hub-rtd21xx-device.h000066400000000000000000000032241460375044200231140ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricky Wu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_RTS54HUB_RTD21XX_DEVICE (fu_rts54hub_rtd21xx_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuRts54hubRtd21xxDevice, fu_rts54hub_rtd21xx_device, FU, RTS54HUB_RTD21XX_DEVICE, FuDevice) struct _FuRts54hubRtd21xxDeviceClass { FuDeviceClass parent_class; }; #define I2C_DELAY_AFTER_SEND 5 /* ms */ #define UC_ISP_TARGET_ADDR 0x3A #define UC_FOREGROUND_STATUS 0x31 #define UC_FOREGROUND_OPCODE 0x33 #define UC_FOREGROUND_ISP_DATA_OPCODE 0x34 #define UC_BACKGROUND_OPCODE 0x31 #define UC_BACKGROUND_ISP_DATA_OPCODE 0x32 typedef enum { ISP_STATUS_BUSY = 0xBB, /* host must wait for device */ ISP_STATUS_IDLE_SUCCESS = 0x11, /* previous command was OK */ ISP_STATUS_IDLE_FAILURE = 0x12, /* previous command failed */ } IspStatus; gboolean fu_rts54hub_rtd21xx_device_read_status(FuRts54hubRtd21xxDevice *self, guint8 *status, GError **error); gboolean fu_rts54hub_rtd21xx_device_read_status_raw(FuRts54hubRtd21xxDevice *self, guint8 *status, GError **error); gboolean fu_rts54hub_rtd21xx_device_i2c_read(FuRts54hubRtd21xxDevice *self, guint8 target_addr, guint8 sub_addr, guint8 *data, gsize datasz, GError **error); gboolean fu_rts54hub_rtd21xx_device_i2c_write(FuRts54hubRtd21xxDevice *self, guint8 target_addr, guint8 sub_addr, const guint8 *data, gsize datasz, GError **error); fwupd-1.9.16/plugins/rts54hub/fu-rts54hub-rtd21xx-foreground.c000066400000000000000000000271251460375044200240300ustar00rootroot00000000000000/* * Copyright (C) 2021 Realtek Corporation * Copyright (C) 2021 Ricky Wu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-rts54hub-device.h" #include "fu-rts54hub-rtd21xx-foreground.h" struct _FuRts54hubRtd21xxForeground { FuRts54hubRtd21xxDevice parent_instance; }; G_DEFINE_TYPE(FuRts54hubRtd21xxForeground, fu_rts54hub_rtd21xx_foreground, FU_TYPE_RTS54HUB_RTD21XX_DEVICE) #define ISP_DATA_BLOCKSIZE 256 #define ISP_PACKET_SIZE 257 typedef enum { ISP_CMD_ENTER_FW_UPDATE = 0x01, ISP_CMD_GET_PROJECT_ID_ADDR = 0x02, ISP_CMD_SYNC_IDENTIFY_CODE = 0x03, ISP_CMD_GET_FW_INFO = 0x04, ISP_CMD_FW_UPDATE_START = 0x05, ISP_CMD_FW_UPDATE_ISP_DONE = 0x06, ISP_CMD_FW_UPDATE_RESET = 0x07, ISP_CMD_FW_UPDATE_EXIT = 0x08, } IspCmd; static gboolean fu_rts54hub_rtd21xx_ensure_version_unlocked(FuRts54hubRtd21xxForeground *self, GError **error) { guint8 buf_rep[7] = {0x00}; guint8 buf_req[] = {ISP_CMD_GET_FW_INFO}; g_autofree gchar *version = NULL; if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_FOREGROUND_OPCODE, buf_req, sizeof(buf_req), error)) { g_prefix_error(error, "failed to get version number: "); return FALSE; } /* wait for device ready */ fu_device_sleep(FU_DEVICE(self), 300); /* ms */ if (!fu_rts54hub_rtd21xx_device_i2c_read(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, 0x00, buf_rep, sizeof(buf_rep), error)) { g_prefix_error(error, "failed to get version number: "); return FALSE; } /* set version */ version = g_strdup_printf("%u.%u", buf_rep[1], buf_rep[2]); fu_device_set_version(FU_DEVICE(self), version); return TRUE; } static gboolean fu_rts54hub_rtd21xx_foreground_detach_raw(FuRts54hubRtd21xxForeground *self, GError **error) { guint8 buf = 0x03; if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), 0x6A, 0x31, &buf, sizeof(buf), error)) { g_prefix_error(error, "failed to detach: "); return FALSE; } /* wait for device ready */ fu_device_sleep(FU_DEVICE(self), 300); /* ms */ return TRUE; } static gboolean fu_rts54hub_rtd21xx_foreground_detach_cb(FuDevice *device, gpointer user_data, GError **error) { FuRts54hubRtd21xxForeground *self = FU_RTS54HUB_RTD21XX_FOREGROUND(device); guint8 status = 0xfe; if (!fu_rts54hub_rtd21xx_foreground_detach_raw(self, error)) return FALSE; if (!fu_rts54hub_rtd21xx_device_read_status_raw(FU_RTS54HUB_RTD21XX_DEVICE(self), &status, error)) return FALSE; if (status != ISP_STATUS_IDLE_SUCCESS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "detach status was 0x%02x", status); return FALSE; } /* success */ return TRUE; } static gboolean fu_rts54hub_rtd21xx_foreground_detach(FuDevice *device, FuProgress *progress, GError **error) { FuRts54HubDevice *parent = FU_RTS54HUB_DEVICE(fu_device_get_parent(device)); g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; return fu_device_retry(device, fu_rts54hub_rtd21xx_foreground_detach_cb, 100, NULL, error); } static gboolean fu_rts54hub_rtd21xx_foreground_attach(FuDevice *device, FuProgress *progress, GError **error) { FuRts54HubDevice *parent = FU_RTS54HUB_DEVICE(fu_device_get_parent(device)); FuRts54hubRtd21xxForeground *self = FU_RTS54HUB_RTD21XX_FOREGROUND(device); guint8 buf[] = {ISP_CMD_FW_UPDATE_RESET}; g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; /* exit fw mode */ if (!fu_rts54hub_rtd21xx_device_read_status(FU_RTS54HUB_RTD21XX_DEVICE(self), NULL, error)) return FALSE; if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_FOREGROUND_OPCODE, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to ISP_CMD_FW_UPDATE_RESET: "); return FALSE; } /* the device needs some time to restart with the new firmware before * it can be queried again */ fu_device_sleep_full(device, 60000, progress); /* ms */ /* success */ return TRUE; } static gboolean fu_rts54hub_rtd21xx_foreground_exit(FuDevice *device, GError **error) { FuRts54HubDevice *parent = FU_RTS54HUB_DEVICE(fu_device_get_parent(device)); FuRts54hubRtd21xxForeground *self = FU_RTS54HUB_RTD21XX_FOREGROUND(device); guint8 buf[] = {ISP_CMD_FW_UPDATE_EXIT}; g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_FOREGROUND_OPCODE, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to ISP_CMD_FW_UPDATE_EXIT: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_rts54hub_rtd21xx_foreground_setup(FuDevice *device, GError **error) { FuRts54hubRtd21xxForeground *self = FU_RTS54HUB_RTD21XX_FOREGROUND(device); g_autoptr(FuDeviceLocker) locker = NULL; /* get version */ locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_device_detach, (FuDeviceLockerFunc)fu_rts54hub_rtd21xx_foreground_exit, error); if (locker == NULL) return FALSE; if (!fu_rts54hub_rtd21xx_ensure_version_unlocked(self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_rts54hub_rtd21xx_foreground_reload(FuDevice *device, GError **error) { FuRts54HubDevice *parent = FU_RTS54HUB_DEVICE(fu_device_get_parent(device)); g_autoptr(FuDeviceLocker) locker = NULL; /* open parent device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; return fu_rts54hub_rtd21xx_foreground_setup(device, error); } static gboolean fu_rts54hub_rtd21xx_foreground_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuRts54hubRtd21xxForeground *self = FU_RTS54HUB_RTD21XX_FOREGROUND(device); const guint8 *fwbuf; gsize fwbufsz = 0; guint32 project_addr; guint8 project_id_count; guint8 read_buf[10] = {0x0}; guint8 write_buf[ISP_PACKET_SIZE] = {0x0}; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "setup"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "finish"); /* open device */ locker = fu_device_locker_new(self, error); if (locker == NULL) return FALSE; /* simple image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; fwbuf = g_bytes_get_data(fw, &fwbufsz); /* enable ISP high priority */ write_buf[0] = ISP_CMD_ENTER_FW_UPDATE; write_buf[1] = 0x01; if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_FOREGROUND_OPCODE, write_buf, 2, error)) { g_prefix_error(error, "failed to enable ISP: "); return FALSE; } if (!fu_rts54hub_rtd21xx_device_read_status(FU_RTS54HUB_RTD21XX_DEVICE(self), NULL, error)) return FALSE; /* get project ID address */ write_buf[0] = ISP_CMD_GET_PROJECT_ID_ADDR; if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_FOREGROUND_OPCODE, write_buf, 1, error)) { g_prefix_error(error, "failed to get project ID address: "); return FALSE; } /* read back 6 bytes data */ fu_device_sleep(FU_DEVICE(self), I2C_DELAY_AFTER_SEND * 40); if (!fu_rts54hub_rtd21xx_device_i2c_read(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_FOREGROUND_STATUS, read_buf, 6, error)) { g_prefix_error(error, "failed to read project ID: "); return FALSE; } if (read_buf[0] != ISP_STATUS_IDLE_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed project ID with error 0x%02x: ", read_buf[0]); return FALSE; } /* verify project ID */ project_addr = fu_memread_uint32(read_buf + 1, G_BIG_ENDIAN); project_id_count = read_buf[5]; write_buf[0] = ISP_CMD_SYNC_IDENTIFY_CODE; if (!fu_memcpy_safe(write_buf, sizeof(write_buf), 0x1, /* dst */ fwbuf, fwbufsz, project_addr, /* src */ project_id_count, error)) { g_prefix_error(error, "failed to write project ID from 0x%04x: ", project_addr); return FALSE; } if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_FOREGROUND_OPCODE, write_buf, project_id_count + 1, error)) { g_prefix_error(error, "failed to send fw update start cmd: "); return FALSE; } if (!fu_rts54hub_rtd21xx_device_read_status(FU_RTS54HUB_RTD21XX_DEVICE(self), NULL, error)) return FALSE; /* foreground FW update start command */ write_buf[0] = ISP_CMD_FW_UPDATE_START; fu_memwrite_uint16(write_buf + 1, ISP_DATA_BLOCKSIZE, G_BIG_ENDIAN); if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_FOREGROUND_OPCODE, write_buf, 3, error)) { g_prefix_error(error, "failed to send fw update start cmd: "); return FALSE; } fu_progress_step_done(progress); /* send data */ chunks = fu_chunk_array_new_from_bytes(fw, 0x00, ISP_DATA_BLOCKSIZE); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_rts54hub_rtd21xx_device_read_status(FU_RTS54HUB_RTD21XX_DEVICE(self), NULL, error)) return FALSE; if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_FOREGROUND_ISP_DATA_OPCODE, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to write @0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* update finish command */ if (!fu_rts54hub_rtd21xx_device_read_status(FU_RTS54HUB_RTD21XX_DEVICE(self), NULL, error)) return FALSE; write_buf[0] = ISP_CMD_FW_UPDATE_ISP_DONE; if (!fu_rts54hub_rtd21xx_device_i2c_write(FU_RTS54HUB_RTD21XX_DEVICE(self), UC_ISP_TARGET_ADDR, UC_FOREGROUND_OPCODE, write_buf, 1, error)) { g_prefix_error(error, "failed update finish cmd: "); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_rts54hub_rtd21xx_foreground_init(FuRts54hubRtd21xxForeground *self) { } static void fu_rts54hub_rtd21xx_foreground_class_init(FuRts54hubRtd21xxForegroundClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_rts54hub_rtd21xx_foreground_setup; klass_device->reload = fu_rts54hub_rtd21xx_foreground_reload; klass_device->attach = fu_rts54hub_rtd21xx_foreground_attach; klass_device->detach = fu_rts54hub_rtd21xx_foreground_detach; klass_device->write_firmware = fu_rts54hub_rtd21xx_foreground_write_firmware; } fwupd-1.9.16/plugins/rts54hub/fu-rts54hub-rtd21xx-foreground.h000066400000000000000000000010121460375044200240200ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricky Wu * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-rts54hub-rtd21xx-device.h" #define FU_TYPE_RTS54HUB_RTD21XX_FOREGROUND (fu_rts54hub_rtd21xx_foreground_get_type()) G_DECLARE_FINAL_TYPE(FuRts54hubRtd21xxForeground, fu_rts54hub_rtd21xx_foreground, FU, RTS54HUB_RTD21XX_FOREGROUND, FuRts54hubRtd21xxDevice) fwupd-1.9.16/plugins/rts54hub/meson.build000066400000000000000000000010221460375044200202400ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginRts54Hub"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('rts54hub.quirk') plugin_builtins += static_library('fu_plugin_rts54hub', sources: [ 'fu-rts54hub-device.c', 'fu-rts54hub-rtd21xx-device.c', 'fu-rts54hub-rtd21xx-background.c', 'fu-rts54hub-rtd21xx-foreground.c', 'fu-rts54hub-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/rts54hub/rts54hub.quirk000066400000000000000000000032261460375044200206430ustar00rootroot00000000000000# RTS5423 Development Board [USB\VID_0BDA&PID_5B00] Plugin = rts54hub Flags = enforce-requires GType = FuRts54HubDevice FirmwareSizeMin = 0x20000 FirmwareSizeMax = 0x40000 # Lenovo HotRod [USB\VID_17EF&PID_30BF] Plugin = rts54hub GType = FuRts54HubDevice Vendor = Lenovo FirmwareSizeMin = 0x20000 FirmwareSizeMax = 0x40000 Children = FuRts54hubRtd21xxForeground|USB\VID_17EF&PID_30BF&I2C_01 [USB\VID_17EF&PID_30BF&I2C_01] Plugin = rts54hub Name = HDMI Converter Flags = updatable FirmwareSize = 0x30000 Rts54TargetAddr = 0x20 Rts54I2cSpeed = 0x2 Rts54RegisterAddrLen = 0x04 # Acer D501 Dock [USB\VID_2BEF&PID_1009] Plugin = rts54hub GType = FuRts54HubDevice Name = Acer D501 Dock USB Hub FirmwareSizeMin = 0x10000 FirmwareSizeMax = 0x40000 Children = FuRts54hubRtd21xxBackground|USB\VID_2BEF&PID_1009&I2C_01 [USB\VID_2BEF&PID_1009&I2C_01] Plugin = rts54hub Vendor = Realtek Name = Acer D501 Dock HDMI Converter Flags = updatable FirmwareSizeMin = 0x10000 FirmwareSizeMax = 0x90000 Rts54TargetAddr = 0x20 Rts54I2cSpeed = 0x2 Rts54RegisterAddrLen = 0x04 #Acer T34 Dock gen1 [USB\VID_0502&PID_0702] Plugin = rts54hub Flags = updatable GType = FuRts54HubDevice FirmwareSizeMin = 0x10000 FirmwareSizeMax = 0x20000 #Acer T34 Dock gen2 [USB\VID_0502&PID_0701] Plugin = rts54hub Flags = updatable GType = FuRts54HubDevice FirmwareSizeMin = 0x10000 FirmwareSizeMax = 0x20000 #Acer U33 Dock gen2 [USB\VID_0502&PID_0801] Plugin = rts54hub Flags = updatable GType = FuRts54HubDevice FirmwareSizeMin = 0x10000 FirmwareSizeMax = 0x20000 #Acer U33 Dock gen1 [USB\VID_0502&PID_0802] Plugin = rts54hub Flags = updatable GType = FuRts54HubDevice FirmwareSizeMin = 0x10000 FirmwareSizeMax = 0x20000 fwupd-1.9.16/plugins/scsi/000077500000000000000000000000001460375044200153645ustar00rootroot00000000000000fwupd-1.9.16/plugins/scsi/README.md000066400000000000000000000016271460375044200166510ustar00rootroot00000000000000--- title: Plugin: SCSI --- ## Introduction This plugin adds support for SCSI storage hardware. Most SCSI devices are enumerated and some UFS devices may also be updatable. Firmware is sent in 4kB chunks and activated on next reboot only. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * `org.jedec.ufs` ## GUID Generation These device use the SCSI DeviceInstanceId values, e.g. * `SCSI\VEN_HP&DEV_EG0900JETKB&REV_HPD4` * `SCSI\VEN_HP&DEV_EG0900JETKB` ## Vendor ID Security The vendor ID is set from the vendor, for example set to `SCSI:HP` ## External Interface Access This plugin requires only reading from sysfs for enumeration, but requires using a `sg_io ioctl` for UFS updates. ## Version Considerations This plugin has been available since fwupd version `1.7.6`. fwupd-1.9.16/plugins/scsi/fu-scsi-device.c000066400000000000000000000212011460375044200203320ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-scsi-device.h" #include "fu-scsi-struct.h" struct _FuScsiDevice { FuUdevDevice parent_instance; guint64 ffu_timeout; }; G_DEFINE_TYPE(FuScsiDevice, fu_scsi_device, FU_TYPE_UDEV_DEVICE) #define BUFFER_VENDOR_MODE 0x01 #define BUFFER_DUFS_MODE 0x02 #define BUFFER_FFU_MODE 0x0E #define BUFFER_EHS_MODE 0x1C #define SENSE_BUFF_LEN 18 #define WRITE_BUF_CMDLEN 10 #define READ_BUF_CMDLEN 10 #define WRITE_BUFFER_CMD 0x3B #define READ_BUFFER_CMD 0x3C #define FU_SCSI_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ static void fu_scsi_device_to_string(FuDevice *device, guint idt, GString *str) { FuScsiDevice *self = FU_SCSI_DEVICE(device); FU_DEVICE_CLASS(fu_scsi_device_parent_class)->to_string(device, idt, str); fu_string_append_kx(str, idt, "FfuTimeout", self->ffu_timeout); } static gboolean fu_scsi_device_probe(FuDevice *device, GError **error) { FuScsiDevice *self = FU_SCSI_DEVICE(device); GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); guint64 removable = 0; g_autofree gchar *vendor_id = NULL; g_autoptr(FuUdevDevice) ufshci_parent = NULL; const gchar *subsystem_parents[] = {"pci", "platform", NULL}; /* check is valid */ if (g_strcmp0(g_udev_device_get_devtype(udev_device), "disk") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct devtype=%s, expected disk", g_udev_device_get_devtype(udev_device)); return FALSE; } if (!g_udev_device_get_property_as_boolean(udev_device, "ID_SCSI")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "has no ID_SCSI"); return FALSE; } /* vendor sanity */ if (g_strcmp0(fu_device_get_vendor(device), "ATA") == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no assigned vendor"); return FALSE; } vendor_id = g_strdup_printf("SCSI:%s", fu_device_get_vendor(device)); fu_device_add_vendor_id(device, vendor_id); /* the ufshci controller could really be on any bus... search in order of priority */ for (guint i = 0; subsystem_parents[i] != NULL && ufshci_parent == NULL; i++) { ufshci_parent = fu_udev_device_get_parent_with_subsystem(FU_UDEV_DEVICE(device), subsystem_parents[i]); } if (ufshci_parent != NULL) { guint64 ufs_features = 0; /* check if this is a UFS device */ g_info("found ufshci controller at %s", fu_udev_device_get_sysfs_path(ufshci_parent)); if (fu_udev_device_get_sysfs_attr_uint64(ufshci_parent, "device_descriptor/ufs_features", &ufs_features, NULL)) { fu_device_set_summary(device, "UFS device"); /* least significant bit specifies FFU capability */ if (ufs_features & 0x1) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED); fu_device_add_protocol(device, "org.jedec.ufs"); } if (!fu_udev_device_get_sysfs_attr_uint64(ufshci_parent, "device_descriptor/ffu_timeout", &self->ffu_timeout, error)) { g_prefix_error(error, "no ffu timeout specified: "); return FALSE; } } } /* add GUIDs */ fu_device_add_instance_strsafe(device, "VEN", fu_device_get_vendor(device)); fu_device_add_instance_strsafe(device, "DEV", fu_device_get_name(device)); fu_device_add_instance_strsafe(device, "REV", fu_device_get_version(device)); if (!fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, error, "SCSI", "VEN", NULL)) return FALSE; if (!fu_device_build_instance_id(device, error, "SCSI", "VEN", "DEV", NULL)) return FALSE; if (!fu_device_build_instance_id(device, error, "SCSI", "VEN", "DEV", "REV", NULL)) return FALSE; /* is internal? */ if (fu_udev_device_get_sysfs_attr_uint64(FU_UDEV_DEVICE(device), "removable", &removable, NULL)) { if (removable == 0x0) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL); } /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "scsi:scsi_target", error); } static FuFirmware * fu_scsi_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_firmware_new(); fu_firmware_set_alignment(firmware, FU_FIRMWARE_ALIGNMENT_4K); if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; return g_steal_pointer(&firmware); } static gboolean fu_scsi_device_send_scsi_cmd_v3(FuScsiDevice *self, const guint8 *cdb, guint8 cdbsz, const guint8 *buf, guint32 bufsz, gint dir, GError **error) { guint8 sense_buffer[SENSE_BUFF_LEN] = {0}; struct sg_io_hdr io_hdr = {.interface_id = 'S'}; io_hdr.cmd_len = cdbsz; io_hdr.mx_sb_len = sizeof(sense_buffer); io_hdr.dxfer_direction = dir; io_hdr.dxfer_len = bufsz; io_hdr.dxferp = (guint8 *)buf; /* pointer to command buf */ io_hdr.cmdp = (guint8 *)cdb; io_hdr.sbp = sense_buffer; io_hdr.timeout = 60000; /* ms */ g_debug("cmd=0x%x len=0x%x", cdb[0], (guint)bufsz); if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), SG_IO, (guint8 *)&io_hdr, NULL, FU_SCSI_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; if (io_hdr.status) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Command fail with status %x, senseKey %s, asc 0x%02x, ascq 0x%02x", io_hdr.status, fu_scsi_sense_key_to_string(sense_buffer[2]), sense_buffer[12], sense_buffer[13]); return FALSE; } /* success */ return TRUE; } static gboolean fu_scsi_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuScsiDevice *self = FU_SCSI_DEVICE(device); guint32 chunksz = 0x1000; guint32 offset = 0; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* prepare chunks */ chunks = fu_chunk_array_new_from_bytes(fw, 0x00, chunksz); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); /* write each block */ for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); guint8 cdb[WRITE_BUF_CMDLEN] = {WRITE_BUFFER_CMD, BUFFER_FFU_MODE, 0x0 /* buf_id */}; fu_memwrite_uint24(cdb + 3, offset, G_BIG_ENDIAN); fu_memwrite_uint24(cdb + 6, fu_chunk_get_data_sz(chk), G_BIG_ENDIAN); if (!fu_scsi_device_send_scsi_cmd_v3(self, cdb, sizeof(cdb), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), SG_DXFER_TO_DEV, error)) { g_prefix_error(error, "SG_IO WRITE BUFFER data error for v3 chunk 0x%x: ", fu_chunk_get_idx(chk)); return FALSE; } /* chunk done */ fu_progress_step_done(progress); offset += fu_chunk_get_data_sz(chk); } /* success! */ fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_UPDATE_PENDING); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); return TRUE; } static void fu_scsi_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reload"); } static void fu_scsi_device_init(FuScsiDevice *self) { fu_device_add_icon(FU_DEVICE(self), "drive-harddisk"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_summary(FU_DEVICE(self), "SCSI device"); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_SYNC | FU_UDEV_DEVICE_FLAG_IOCTL_RETRY); } static void fu_scsi_device_class_init(FuScsiDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_scsi_device_to_string; klass_device->probe = fu_scsi_device_probe; klass_device->prepare_firmware = fu_scsi_device_prepare_firmware; klass_device->write_firmware = fu_scsi_device_write_firmware; klass_device->set_progress = fu_scsi_device_set_progress; } fwupd-1.9.16/plugins/scsi/fu-scsi-device.h000066400000000000000000000004361460375044200203460ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_SCSI_DEVICE (fu_scsi_device_get_type()) G_DECLARE_FINAL_TYPE(FuScsiDevice, fu_scsi_device, FU, SCSI_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/scsi/fu-scsi-plugin.c000066400000000000000000000013511460375044200203750ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-scsi-device.h" #include "fu-scsi-plugin.h" struct _FuScsiPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuScsiPlugin, fu_scsi_plugin, FU_TYPE_PLUGIN) static void fu_scsi_plugin_init(FuScsiPlugin *self) { } static void fu_scsi_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_udev_subsystem(plugin, "block"); fu_plugin_add_device_gtype(plugin, FU_TYPE_SCSI_DEVICE); } static void fu_scsi_plugin_class_init(FuScsiPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_scsi_plugin_constructed; } fwupd-1.9.16/plugins/scsi/fu-scsi-plugin.h000066400000000000000000000003421460375044200204010ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuScsiPlugin, fu_scsi_plugin, FU, SCSI_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/scsi/fu-scsi.rs000066400000000000000000000007701460375044200173070ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum ScsiSenseKey { NoSense = 0x00, RecoveredError = 0x01, NotReady = 0x02, MediumError = 0x03, HardwareError = 0x04, IllegalRequest = 0x05, UnitAttention = 0x06, DataProtect = 0x07, BlankCheck = 0x08, VendorSpecific = 0x09, CopyAborted = 0x0A, AbortedCommand = 0x0B, Equal = 0x0C, VolumeOverflow = 0x0D, Miscompare = 0x0E, } fwupd-1.9.16/plugins/scsi/meson.build000066400000000000000000000011111460375044200175200ustar00rootroot00000000000000if get_option('plugin_scsi').require(gudev.found(), error_message: 'gudev is needed for plugin_scsi').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginScsi"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('scsi.quirk') plugin_builtins += static_library('fu_plugin_scsi', rustgen.process('fu-scsi.rs'), sources: [ 'fu-scsi-plugin.c', 'fu-scsi-device.c', ], include_directories: plugin_incdirs, c_args: [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], link_with: plugin_libs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/scsi/scsi.quirk000066400000000000000000000013201460375044200173760ustar00rootroot00000000000000[SCSI\VEN_LSI&DEV_VirtualSES] Flags = no-probe [SCSI\VEN_Marvell&DEV_Console] Flags = no-probe [SCSI\VEN_HP&DEV_EK0800JVYPN&REV_HPD5] Issue = MTX-6f54e8621f4c490a918defdab6 [SCSI\VEN_HP&DEV_EK0800JVYPN&REV_HPD6] Issue = MTX-6f54e8621f4c490a918defdab6 [SCSI\VEN_HP&DEV_EO1600JVYPP&REV_HPD5] Issue = MTX-6f54e8621f4c490a918defdab6 [SCSI\VEN_HP&DEV_EO1600JVYPP&REV_HPD6] Issue = MTX-6f54e8621f4c490a918defdab6 [SCSI\VEN_HP&DEV_MK0800JVYPQ&REV_HPD5] Issue = MTX-6f54e8621f4c490a918defdab6 [SCSI\VEN_HP&DEV_MK0800JVYPQ&REV_HPD6] Issue = MTX-6f54e8621f4c490a918defdab6 [SCSI\VEN_HP&DEV_MO1600JVYPR&REV_HPD5] Issue = MTX-6f54e8621f4c490a918defdab6 [SCSI\VEN_HP&DEV_MO1600JVYPR&REV_HPD6] Issue = MTX-6f54e8621f4c490a918defdab6 fwupd-1.9.16/plugins/steelseries/000077500000000000000000000000001460375044200167525ustar00rootroot00000000000000fwupd-1.9.16/plugins/steelseries/README.md000066400000000000000000000033771460375044200202430ustar00rootroot00000000000000--- title: Plugin: SteelSeries --- ## Introduction This plugin allows to update firmware on SteelSeries gamepads: * Stratus Duo * Stratus Duo USB wireless adapter * Stratus+ * Aerox 3 Wireless * Rival 3 Wireless SteelSeries Rival 100 gaming mice support is limited by getting the correct version number. These mice have updatable firmware but so far no updates are available from the vendor. This plugin supports the following protocol IDs: * `com.steelseries.fizz` * `com.steelseries.gamepad` * `com.steelseries.sonic` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_1038&PID_1702` ## Quirk Use This plugin uses the following plugin-specific quirks: ### Flags:is-receiver The device is a USB receiver. Since 1.8.1 ## Update Behavior ### Gamepad Gamepad and/or its wireless adapter must be connected to host via USB cable to apply an update. The device is switched to bootloader mode to flash updates, and is reset automatically to new firmware after flashing. ### Mice The device is not upgradable and thus requires no vendor ID set. ### Wireless Mice ### Rival 3 Wireless The mouse switch button underneath must be set to 2.4G, and its 2.4G USB Wireless adapter must be connected to host. ### Aerox 3 Wireless The mouse switch button underneath must be set to 2.4G, and its 2.4G USB Wireless adapter must be connected to host; or the mouse must be connected to host via the USB-A to USB-C cable. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x1038` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available in `fwupd >= 0.8.0` but only learned how to update devices in `1.8.1`. fwupd-1.9.16/plugins/steelseries/fu-steelseries-device.c000066400000000000000000000114411460375044200233130ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-steelseries-device.h" typedef struct { gint iface_idx_offset; guint8 iface_idx; guint8 ep; gsize ep_in_size; } FuSteelseriesDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuSteelseriesDevice, fu_steelseries_device, FU_TYPE_USB_DEVICE) #define GET_PRIVATE(o) (fu_steelseries_device_get_instance_private(o)) /* @iface_idx_offset can be negative to specify from the end */ void fu_steelseries_device_set_iface_idx_offset(FuSteelseriesDevice *self, gint iface_idx_offset) { FuSteelseriesDevicePrivate *priv = GET_PRIVATE(self); priv->iface_idx_offset = iface_idx_offset; } gboolean fu_steelseries_device_cmd(FuSteelseriesDevice *self, guint8 *data, gsize datasz, gboolean answer, GError **error) { FuSteelseriesDevicePrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_len = 0; gboolean ret; ret = g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, 0x09, 0x0200, priv->iface_idx, data, datasz, &actual_len, STEELSERIES_TRANSACTION_TIMEOUT, NULL, error); if (!ret) { g_prefix_error(error, "failed to do control transfer: "); return FALSE; } if (actual_len != datasz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only wrote %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } /* cleanup the buffer before receiving any data */ memset(data, 0x00, datasz); /* do not expect the answer from device */ if (answer != TRUE) return TRUE; ret = g_usb_device_interrupt_transfer(usb_device, priv->ep, data, priv->ep_in_size, &actual_len, STEELSERIES_TRANSACTION_TIMEOUT, NULL, error); if (!ret) { g_prefix_error(error, "failed to do EP transfer: "); return FALSE; } if (actual_len != priv->ep_in_size) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only read %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } /* success */ return TRUE; } static gboolean fu_steelseries_device_probe(FuDevice *device, GError **error) { FuSteelseriesDevice *self = FU_STEELSERIES_DEVICE(device); FuSteelseriesDevicePrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); GUsbInterface *iface = NULL; GUsbEndpoint *ep = NULL; guint8 ep_id; guint16 packet_size; g_autoptr(GPtrArray) ifaces = NULL; g_autoptr(GPtrArray) endpoints = NULL; ifaces = g_usb_device_get_interfaces(usb_device, error); if (ifaces == NULL) return FALSE; /* use the correct interface for interrupt transfer, either specifying an absolute offset, * or a negative offset value for the "last" one */ if (priv->iface_idx_offset >= 0) { if ((guint)priv->iface_idx_offset > ifaces->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "update interface 0x%x not found", (guint)priv->iface_idx_offset); return FALSE; } priv->iface_idx = priv->iface_idx_offset; } else { priv->iface_idx = ifaces->len - 1; } iface = g_ptr_array_index(ifaces, priv->iface_idx); endpoints = g_usb_interface_get_endpoints(iface); /* expecting to have only one endpoint for communication */ if (endpoints == NULL || endpoints->len != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "endpoint not found"); return FALSE; } ep = g_ptr_array_index(endpoints, 0); ep_id = g_usb_endpoint_get_address(ep); packet_size = g_usb_endpoint_get_maximum_packet_size(ep); priv->ep = ep_id; priv->ep_in_size = packet_size; fu_usb_device_add_interface(FU_USB_DEVICE(self), priv->iface_idx); /* success */ return TRUE; } static void fu_steelseries_device_to_string(FuDevice *device, guint idt, GString *str) { FuSteelseriesDevice *self = FU_STEELSERIES_DEVICE(device); FuSteelseriesDevicePrivate *priv = GET_PRIVATE(self); FU_DEVICE_CLASS(fu_steelseries_device_parent_class)->to_string(device, idt, str); fu_string_append_kx(str, idt, "Interface", priv->iface_idx); fu_string_append_kx(str, idt, "Endpoint", priv->ep); } static void fu_steelseries_device_init(FuSteelseriesDevice *self) { fu_device_register_private_flag(FU_DEVICE(self), FU_STEELSERIES_DEVICE_FLAG_IS_RECEIVER, "is-receiver"); } static void fu_steelseries_device_class_init(FuSteelseriesDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_steelseries_device_to_string; klass_device->probe = fu_steelseries_device_probe; } fwupd-1.9.16/plugins/steelseries/fu-steelseries-device.h000066400000000000000000000020111460375044200233110ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * Copyright (C) 2021 Denis Pynkin * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_STEELSERIES_DEVICE (fu_steelseries_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuSteelseriesDevice, fu_steelseries_device, FU, STEELSERIES_DEVICE, FuUsbDevice) struct _FuSteelseriesDeviceClass { FuUsbDeviceClass parent_class; }; #define STEELSERIES_BUFFER_CONTROL_SIZE 64 #define STEELSERIES_TRANSACTION_TIMEOUT 5000 /** * FU_STEELSERIES_DEVICE_FLAG_IS_RECEIVER: * * The device is a USB receiver. * * Since 1.8.1 */ #define FU_STEELSERIES_DEVICE_FLAG_IS_RECEIVER (1 << 0) void fu_steelseries_device_set_iface_idx_offset(FuSteelseriesDevice *self, gint iface_idx_offset); gboolean fu_steelseries_device_cmd(FuSteelseriesDevice *self, guint8 *data, gsize datasz, gboolean answer, GError **error); fwupd-1.9.16/plugins/steelseries/fu-steelseries-firmware.c000066400000000000000000000041661460375044200236760ustar00rootroot00000000000000/* * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-steelseries-firmware.h" struct _FuSteelseriesFirmware { FuFirmwareClass parent_instance; guint32 checksum; }; G_DEFINE_TYPE(FuSteelseriesFirmware, fu_steelseries_firmware, FU_TYPE_FIRMWARE) static gboolean fu_steelseries_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuSteelseriesFirmware *self = FU_STEELSERIES_FIRMWARE(firmware); guint32 checksum_tmp; guint32 checksum; if (!fu_memread_uint32_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), g_bytes_get_size(fw) - sizeof(checksum), &checksum, G_LITTLE_ENDIAN, error)) return FALSE; checksum_tmp = fu_crc32(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw) - sizeof(checksum_tmp)); if (checksum_tmp != checksum) { if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "checksum mismatch, got 0x%08x, expected 0x%08x", checksum_tmp, checksum); return FALSE; } g_debug("ignoring checksum mismatch, got 0x%08x, expected 0x%08x", checksum_tmp, checksum); } self->checksum = checksum; fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); /* success */ return TRUE; } static void fu_steelseries_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuSteelseriesFirmware *self = FU_STEELSERIES_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "checksum", self->checksum); } static void fu_steelseries_firmware_init(FuSteelseriesFirmware *self) { } static void fu_steelseries_firmware_class_init(FuSteelseriesFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_steelseries_firmware_parse; klass_firmware->export = fu_steelseries_firmware_export; } FuFirmware * fu_steelseries_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_STEELSERIES_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/steelseries/fu-steelseries-firmware.h000066400000000000000000000006321460375044200236750ustar00rootroot00000000000000/* * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_STEELSERIES_FIRMWARE (fu_steelseries_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuSteelseriesFirmware, fu_steelseries_firmware, FU, STEELSERIES_FIRMWARE, FuFirmware) FuFirmware * fu_steelseries_firmware_new(void); fwupd-1.9.16/plugins/steelseries/fu-steelseries-fizz-hid.c000066400000000000000000000145101460375044200236000ustar00rootroot00000000000000/* * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-steelseries-fizz-hid.h" #define STEELSERIES_BUFFER_REPORT_SIZE 64 + 1 #define STEELSERIES_HID_GET_REPORT 0x04U #define STEELSERIES_HID_MAX_RETRIES 100 #define STEELSERIES_HID_VERSION_COMMAND 0x90U #define STEELSERIES_HID_VERSION_REPORT_ID_OFFSET 0x00U #define STEELSERIES_HID_VERSION_COMMAND_OFFSET 0x01U #define STEELSERIES_HID_VERSION_MODE_OFFSET 0x02U struct _FuSteelseriesFizzHid { FuUdevDevice parent_instance; }; G_DEFINE_TYPE(FuSteelseriesFizzHid, fu_steelseries_fizz_hid, FU_TYPE_UDEV_DEVICE) typedef struct { guint8 *buf; gsize bufsz; } FuSteelseriesFizzHidCommandHelper; static gboolean fu_steelseries_fizz_hid_command_cb(FuDevice *device, gpointer user_data, GError **error) { FuSteelseriesFizzHidCommandHelper *helper = (FuSteelseriesFizzHidCommandHelper *)user_data; gboolean ret; guint8 rdata[STEELSERIES_BUFFER_REPORT_SIZE] = {0}; guint8 report_id = 0; g_autoptr(GError) error_local = NULL; /* force the request for each iteration to avoid a loop due the lost single packet -- * this is safe since the device doesn't support update over bluetooth */ if (!fu_udev_device_pwrite(FU_UDEV_DEVICE(device), 0, helper->buf, helper->bufsz, error)) { g_prefix_error(error, "failed to write report: "); return FALSE; } ret = fu_udev_device_pread(FU_UDEV_DEVICE(device), 0, rdata, sizeof(rdata), &error_local); if (!fu_memread_uint8_safe(rdata, sizeof(rdata), STEELSERIES_HID_VERSION_REPORT_ID_OFFSET, &report_id, error)) return FALSE; if (!ret) { /* since fu_udev_device_pread() treats unexpected data size as error * we have to check the output additionally since the size of * unexpected data size from mouse input data is only 16b */ if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_FAILED) || report_id != 0x01) { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to read report: "); return FALSE; } } fu_dump_raw(G_LOG_DOMAIN, "got report", rdata, sizeof(rdata)); if (report_id != STEELSERIES_HID_GET_REPORT) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "data with unexpected Report ID (%u)", report_id); return FALSE; } if (!fu_memcpy_safe(helper->buf, helper->bufsz, 0, rdata, sizeof(rdata), 0, helper->bufsz, error)) { g_prefix_error(error, "failed to return data: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_steelseries_fizz_hid_command(FuDevice *device, guint8 *data, gsize datasz, GError **error) { FuSteelseriesFizzHidCommandHelper helper = { .buf = data, .bufsz = datasz, }; /* In BT mode the sync and data channels are sharing the device descriptor with the * management channel. * This is the reason why we receive "unexpected" packets with 0x01 or 0x05 Report IDs over * the same descriptor on mouse connecting, waking up or just moving the mouse -- hence * trying to repeat the query/response cycle lot of times */ return fu_device_retry_full(device, fu_steelseries_fizz_hid_command_cb, STEELSERIES_HID_MAX_RETRIES, 0, /* ms */ &helper, error); } static gboolean fu_steelseries_fizz_hid_ensure_version(FuDevice *device, GError **error) { guint8 data[STEELSERIES_BUFFER_REPORT_SIZE] = {0}; const guint8 report_id = STEELSERIES_HID_GET_REPORT; const guint8 cmd = STEELSERIES_HID_VERSION_COMMAND; const guint8 mode = 0U; /* string */ g_autofree gchar *version = NULL; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_HID_VERSION_REPORT_ID_OFFSET, report_id, error)) return FALSE; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_HID_VERSION_COMMAND_OFFSET, cmd, error)) return FALSE; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_HID_VERSION_MODE_OFFSET, mode, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "Version", data, sizeof(data)); if (!fu_steelseries_fizz_hid_command(device, data, sizeof(data), error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "Version", data, sizeof(data)); /* success */ version = fu_memstrsafe(data, sizeof(data), 0x1, sizeof(data) - 1, error); if (version == NULL) { g_prefix_error(error, "unable to read version: "); return FALSE; } fu_device_set_version(device, version); return TRUE; } static gboolean fu_steelseries_fizz_hid_detach(FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(FwupdRequest) request = NULL; g_autofree gchar *msg = NULL; /* the user has to do something */ msg = g_strdup_printf( "%s needs to be manually connected either via the USB cable, " "or via the 2.4G USB Wireless adapter to start the update. " "Please plug either the USB-C cable and put the switch button underneath to off, " "or the 2.4G USB Wireless adapter and put the switch button underneath to 2.4G.", fu_device_get_name(device)); request = fwupd_request_new(); fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_PRESS_UNLOCK); fwupd_request_set_message(request, msg); if (!fu_device_emit_request(device, request, progress, error)) return FALSE; /* success */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_steelseries_fizz_hid_setup(FuDevice *device, GError **error) { if (!fu_steelseries_fizz_hid_ensure_version(device, error)) return FALSE; /* success */ return TRUE; } static void fu_steelseries_fizz_hid_class_init(FuSteelseriesFizzHidClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_steelseries_fizz_hid_setup; klass_device->detach = fu_steelseries_fizz_hid_detach; } static void fu_steelseries_fizz_hid_init(FuSteelseriesFizzHid *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_request_flag(FU_DEVICE(self), FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE); fu_device_set_physical_id(FU_DEVICE(self), "hid"); fu_device_add_protocol(FU_DEVICE(self), "com.steelseries.fizz"); fu_device_set_remove_delay(FU_DEVICE(self), 300000); /* 5min */ } fwupd-1.9.16/plugins/steelseries/fu-steelseries-fizz-hid.h000066400000000000000000000005511460375044200236050ustar00rootroot00000000000000/* * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_STEELSERIES_FIZZ_HID (fu_steelseries_fizz_hid_get_type()) G_DECLARE_FINAL_TYPE(FuSteelseriesFizzHid, fu_steelseries_fizz_hid, FU, STEELSERIES_FIZZ_HID, FuHidDevice) fwupd-1.9.16/plugins/steelseries/fu-steelseries-fizz-tunnel.c000066400000000000000000000271531460375044200243500ustar00rootroot00000000000000/* * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-steelseries-firmware.h" #include "fu-steelseries-fizz-tunnel.h" struct _FuSteelseriesFizzTunnel { FuDevice parent_instance; }; G_DEFINE_TYPE(FuSteelseriesFizzTunnel, fu_steelseries_fizz_tunnel, FU_TYPE_DEVICE) static gboolean fu_steelseries_fizz_tunnel_ping(FuDevice *device, gboolean *reached, GError **error) { FuDevice *parent = fu_device_get_parent(device); guint8 status; guint8 level; g_autoptr(GError) error_local = NULL; g_autofree gchar *version = NULL; if (!fu_steelseries_fizz_get_connection_status(parent, &status, error)) { g_prefix_error(error, "failed to get connection status: "); return FALSE; } g_debug("ConnectionStatus: %u", status); *reached = status != STEELSERIES_FIZZ_CONNECTION_STATUS_NOT_CONNECTED; if (!*reached) { /* success */ return TRUE; } /* ping device anyway */ if (!fu_steelseries_fizz_get_battery_level(fu_device_get_parent(device), TRUE, &level, &error_local)) { *reached = FALSE; if (!g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT)) { /* failure */ *error = g_steal_pointer(&error_local); return FALSE; } /* success */ return TRUE; } g_debug("BatteryLevel: 0x%02x", level); /* * CHARGING: Most significant bit. When bit is set to 1 it means battery is currently * charging/plugged in * * LEVEL: 7 least significant bit value of the battery. Values are between 2-21, to get % * you can do (LEVEL - 1) * 5 */ fu_device_set_battery_level(device, ((level & STEELSERIES_FIZZ_BATTERY_LEVEL_STATUS_BITS) - 1U) * 5U); /* re-read version after reconnect/update */ version = fu_steelseries_fizz_get_version(parent, TRUE, error); if (version == NULL) { *reached = FALSE; g_prefix_error(error, "unable to read version from device %s: ", fu_device_get_id(device)); return FALSE; } fu_device_set_version(device, version); /* success */ return TRUE; } static gboolean fu_steelseries_fizz_tunnel_wait_for_reconnect_cb(FuDevice *device, gpointer user_data, GError **error) { FuDevice *parent = fu_device_get_parent(device); guint8 status; gboolean reached; if (!fu_steelseries_fizz_get_connection_status(parent, &status, error)) { g_prefix_error(error, "failed to get connection status: "); return FALSE; } g_debug("ConnectionStatus: %u", status); if (status == STEELSERIES_FIZZ_CONNECTION_STATUS_NOT_CONNECTED) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device is unreachable"); return FALSE; } /* ping */ if (!fu_steelseries_fizz_tunnel_ping(device, &reached, error)) { g_prefix_error(error, "failed to ping on reconnect: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_steelseries_fizz_tunnel_wait_for_reconnect(FuDevice *device, guint delay, GError **error) { return fu_device_retry_full(device, fu_steelseries_fizz_tunnel_wait_for_reconnect_cb, delay / 1000, 1000, NULL, error); } static gboolean fu_steelseries_fizz_tunnel_attach(FuDevice *device, FuProgress *progress, GError **error) { FuDevice *parent = fu_device_get_parent(device); guint remove_delay = fu_device_get_remove_delay(device); g_autoptr(GError) error_local = NULL; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 67, "sleep"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 33, NULL); if (!fu_steelseries_fizz_reset(parent, TRUE, STEELSERIES_FIZZ_RESET_MODE_NORMAL, &error_local)) g_warning("failed to reset: %s", error_local->message); fu_progress_step_done(progress); /* wait for receiver to reset the connection status to 0 */ fu_device_sleep_full(device, 2000, fu_progress_get_child(progress)); /* ms */ remove_delay -= 2000; fu_progress_step_done(progress); if (!fu_steelseries_fizz_tunnel_wait_for_reconnect(device, remove_delay, error)) { g_prefix_error(error, "device %s did not come back: ", fu_device_get_id(device)); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_steelseries_fizz_tunnel_probe(FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(parent)); guint16 release; /* set the version if the release has been set */ release = g_usb_device_get_release(usb_device); if (release != 0x0 && fu_device_get_version_format(device) == FWUPD_VERSION_FORMAT_UNKNOWN) { fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_BCD); fu_device_set_version_u16(device, release); } /* add GUIDs in order of priority */ fu_device_add_instance_str(device, "PROTOCOL", "FIZZ_TUNNEL"); fu_device_add_instance_u16(device, "VID", g_usb_device_get_vid(usb_device)); fu_device_add_instance_u16(device, "PID", g_usb_device_get_pid(usb_device)); fu_device_add_instance_u16(device, "REV", release); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "STEELSERIES", "VID", "PROTOCOL", NULL); fu_device_build_instance_id(device, NULL, "STEELSERIES", "VID", "PID", "PROTOCOL", NULL); if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV)) { fu_device_build_instance_id(device, NULL, "STEELSERIES", "VID", "PID", "REV", "PROTOCOL", NULL); } /* success */ return TRUE; } static gboolean fu_steelseries_fizz_tunnel_setup(FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent(device); guint32 calculated_crc; guint32 stored_crc; gboolean reached; guint8 fs = STEELSERIES_FIZZ_FILESYSTEM_MOUSE; guint8 id = STEELSERIES_FIZZ_MOUSE_FILESYSTEM_BACKUP_APP_ID; g_autoptr(GError) error_local = NULL; /* ping */ if (!fu_steelseries_fizz_tunnel_ping(device, &reached, &error_local)) { g_debug("ignoring error on ping: %s", error_local->message); /* success */ return TRUE; } if (!reached) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNREACHABLE); /* success */ return TRUE; } if (!fu_steelseries_fizz_get_crc32_fs(parent, TRUE, fs, id, &calculated_crc, &stored_crc, error)) { g_prefix_error(error, "failed to get file CRC32 from FS 0x%02x ID 0x%02x: ", fs, id); return FALSE; } if (calculated_crc != stored_crc) { g_warning("%s: checksum mismatch, got 0x%08x, expected 0x%08x", fu_device_get_name(device), calculated_crc, stored_crc); } /* success */ return TRUE; } static gboolean fu_steelseries_fizz_tunnel_poll(FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent(device); guint32 calculated_crc; guint32 stored_crc; gboolean reached; guint8 fs = STEELSERIES_FIZZ_FILESYSTEM_MOUSE; guint8 id = STEELSERIES_FIZZ_MOUSE_FILESYSTEM_BACKUP_APP_ID; g_autoptr(GError) error_local = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* open */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; if (!fu_steelseries_fizz_tunnel_ping(device, &reached, error)) { g_prefix_error(error, "failed to ping: "); return FALSE; } if (!reached) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNREACHABLE); /* success */ return TRUE; } if (!fu_steelseries_fizz_get_crc32_fs(parent, TRUE, fs, id, &calculated_crc, &stored_crc, &error_local)) { g_debug("ignoring error on get file CRC32 from FS 0x%02x ID 0x%02x: %s", fs, id, error_local->message); /* success */ return TRUE; } if (calculated_crc != stored_crc) g_warning("%s: checksum mismatch, got 0x%08x, expected 0x%08x", fu_device_get_name(device), calculated_crc, stored_crc); fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_UNREACHABLE); /* success */ return TRUE; } static gboolean fu_steelseries_fizz_tunnel_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDevice *parent = fu_device_get_parent(device); guint8 fs = STEELSERIES_FIZZ_FILESYSTEM_MOUSE; guint8 id = STEELSERIES_FIZZ_MOUSE_FILESYSTEM_BACKUP_APP_ID; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, NULL); if (!fu_steelseries_fizz_write_firmware_fs(parent, TRUE, fs, id, firmware, fu_progress_get_child(progress), flags, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static FuFirmware * fu_steelseries_fizz_tunnel_read_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuDevice *parent = fu_device_get_parent(device); guint8 fs = STEELSERIES_FIZZ_FILESYSTEM_MOUSE; guint8 id = STEELSERIES_FIZZ_MOUSE_FILESYSTEM_BACKUP_APP_ID; g_autoptr(FuFirmware) firmware = NULL; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 100, NULL); firmware = fu_steelseries_fizz_read_firmware_fs(parent, TRUE, fs, id, fu_device_get_firmware_size_max(device), fu_progress_get_child(progress), error); if (firmware == NULL) return NULL; fu_progress_step_done(progress); /* success */ return g_steal_pointer(&firmware); } static void fu_steelseries_fizz_tunnel_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 6, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_steelseries_fizz_tunnel_class_init(FuSteelseriesFizzTunnelClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->attach = fu_steelseries_fizz_tunnel_attach; klass_device->probe = fu_steelseries_fizz_tunnel_probe; klass_device->setup = fu_steelseries_fizz_tunnel_setup; klass_device->poll = fu_steelseries_fizz_tunnel_poll; klass_device->write_firmware = fu_steelseries_fizz_tunnel_write_firmware; klass_device->read_firmware = fu_steelseries_fizz_tunnel_read_firmware; klass_device->set_progress = fu_steelseries_fizz_tunnel_set_progress; } static void fu_steelseries_fizz_tunnel_init(FuSteelseriesFizzTunnel *self) { fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_AUTO_PAUSE_POLLING); fu_device_add_protocol(FU_DEVICE(self), "com.steelseries.fizz"); fu_device_set_logical_id(FU_DEVICE(self), "tunnel"); fu_device_set_install_duration(FU_DEVICE(self), 38); /* 38 s */ fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); /* 10 s */ fu_device_set_poll_interval(FU_DEVICE(self), 60000); /* 1 min */ fu_device_set_battery_threshold(FU_DEVICE(self), 20); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_STEELSERIES_FIRMWARE); } FuSteelseriesFizzTunnel * fu_steelseries_fizz_tunnel_new(FuSteelseriesFizz *parent) { return g_object_new(FU_TYPE_STEELSERIES_FIZZ_TUNNEL, "parent", FU_DEVICE(parent), NULL); } fwupd-1.9.16/plugins/steelseries/fu-steelseries-fizz-tunnel.h000066400000000000000000000007551460375044200243540ustar00rootroot00000000000000/* * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-steelseries-fizz.h" #define FU_TYPE_STEELSERIES_FIZZ_TUNNEL (fu_steelseries_fizz_tunnel_get_type()) G_DECLARE_FINAL_TYPE(FuSteelseriesFizzTunnel, fu_steelseries_fizz_tunnel, FU, STEELSERIES_FIZZ_TUNNEL, FuDevice) FuSteelseriesFizzTunnel * fu_steelseries_fizz_tunnel_new(FuSteelseriesFizz *parent); fwupd-1.9.16/plugins/steelseries/fu-steelseries-fizz.c000066400000000000000000000633441460375044200230470ustar00rootroot00000000000000/* * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-steelseries-firmware.h" #include "fu-steelseries-fizz-tunnel.h" #include "fu-steelseries-fizz.h" #define STEELSERIES_BUFFER_TRANSFER_SIZE 52 #define STEELSERIES_FIZZ_COMMAND_ERROR_SUCCESS 0 #define STEELSERIES_FIZZ_COMMAND_ERROR_FILE_NOT_FOUND 1 #define STEELSERIES_FIZZ_COMMAND_ERROR_FILE_TOO_SHORT 2 #define STEELSERIES_FIZZ_COMMAND_ERROR_FLASH_FAILED 3 #define STEELSERIES_FIZZ_COMMAND_ERROR_PERMISSION_DENIED 4 #define STEELSERIES_FIZZ_COMMAND_ERROR_OPERATION_NO_SUPPORTED 5 #define STEELSERIES_FIZZ_COMMAND_TUNNEL_BIT 1U << 6 #define STEELSERIES_FIZZ_COMMAND_OFFSET 0x00U #define STEELSERIES_FIZZ_ERROR_OFFSET 0x01U #define STEELSERIES_FIZZ_VERSION_COMMAND 0x90U #define STEELSERIES_FIZZ_VERSION_COMMAND_OFFSET 0x00U #define STEELSERIES_FIZZ_VERSION_MODE_OFFSET 0x01U #define STEELSERIES_FIZZ_BATTERY_LEVEL_COMMAND 0x92U #define STEELSERIES_FIZZ_BATTERY_LEVEL_COMMAND_OFFSET 0x00U #define STEELSERIES_FIZZ_BATTERY_LEVEL_LEVEL_OFFSET 0x01U #define STEELSERIES_FIZZ_PAIRED_STATUS_COMMAND 0xBBU #define STEELSERIES_FIZZ_PAIRED_STATUS_COMMAND_OFFSET 0x00U #define STEELSERIES_FIZZ_PAIRED_STATUS_STATUS_OFFSET 0x01U #define STEELSERIES_FIZZ_CONNECTION_STATUS_COMMAND 0xBCU #define STEELSERIES_FIZZ_CONNECTION_STATUS_COMMAND_OFFSET 0x00U #define STEELSERIES_FIZZ_CONNECTION_STATUS_STATUS_OFFSET 0x01U #define STEELSERIES_FIZZ_WRITE_ACCESS_FILE_COMMAND 0x03U #define STEELSERIES_FIZZ_WRITE_ACCESS_FILE_COMMAND_OFFSET 0x00U #define STEELSERIES_FIZZ_WRITE_ACCESS_FILE_FILESYSTEM_OFFSET 0x01U #define STEELSERIES_FIZZ_WRITE_ACCESS_FILE_ID_OFFSET 0x02U #define STEELSERIES_FIZZ_WRITE_ACCESS_FILE_SIZE_OFFSET 0x03U #define STEELSERIES_FIZZ_WRITE_ACCESS_FILE_OFFSET_OFFSET 0x05U #define STEELSERIES_FIZZ_WRITE_ACCESS_FILE_DATA_OFFSET 0x09U #define STEELSERIES_FIZZ_READ_ACCESS_FILE_COMMAND 0x83U #define STEELSERIES_FIZZ_READ_ACCESS_FILE_COMMAND_OFFSET 0x00U #define STEELSERIES_FIZZ_READ_ACCESS_FILE_FILESYSTEM_OFFSET 0x01U #define STEELSERIES_FIZZ_READ_ACCESS_FILE_ID_OFFSET 0x02U #define STEELSERIES_FIZZ_READ_ACCESS_FILE_SIZE_OFFSET 0x03U #define STEELSERIES_FIZZ_READ_ACCESS_FILE_OFFSET_OFFSET 0x05U #define STEELSERIES_FIZZ_READ_ACCESS_FILE_DATA_OFFSET 0x02U #define STEELSERIES_FIZZ_ERASE_FILE_COMMAND 0x02U #define STEELSERIES_FIZZ_ERASE_FILE_COMMAND_OFFSET 0x0U #define STEELSERIES_FIZZ_ERASE_FILE_FILESYSTEM_OFFSET 0x1U #define STEELSERIES_FIZZ_ERASE_FILE_ID_OFFSET 0x2U #define STEELSERIES_FIZZ_RESET_COMMAND 0x01U #define STEELSERIES_FIZZ_RESET_COMMAND_OFFSET 0x0U #define STEELSERIES_FIZZ_RESET_MODE_OFFSET 0x1U #define STEELSERIES_FIZZ_FILE_CRC32_COMMAND 0x84U #define STEELSERIES_FIZZ_FILE_CRC32_COMMAND_OFFSET 0x00U #define STEELSERIES_FIZZ_FILE_CRC32_FILESYSTEM_OFFSET 0x01U #define STEELSERIES_FIZZ_FILE_CRC32_ID_OFFSET 0x02U #define STEELSERIES_FIZZ_FILE_CRC32_CALCULATED_CRC_OFFSET 0x02U #define STEELSERIES_FIZZ_FILE_CRC32_STORED_CRC_OFFSET 0x06U struct _FuSteelseriesFizz { FuSteelseriesDevice parent_instance; }; G_DEFINE_TYPE(FuSteelseriesFizz, fu_steelseries_fizz, FU_TYPE_STEELSERIES_DEVICE) static gboolean fu_steelseries_fizz_command_error_to_error(guint8 cmd, guint8 err, GError **error) { /* success */ if (err == STEELSERIES_FIZZ_COMMAND_ERROR_SUCCESS) return TRUE; if (err == STEELSERIES_FIZZ_COMMAND_ERROR_FILE_NOT_FOUND) { g_set_error(error, G_IO_ERROR, G_FILE_ERROR_NOENT, "command 0x%02x returned error 0x%02x", cmd, err); return FALSE; } /* targeted offset is past the file end */ if (err == STEELSERIES_FIZZ_COMMAND_ERROR_FILE_TOO_SHORT) { g_set_error(error, G_IO_ERROR, G_FILE_ERROR_NOSPC, "command 0x%02x returned error 0x%02x", cmd, err); return FALSE; } /* when internal flash returns error */ if (err == STEELSERIES_FIZZ_COMMAND_ERROR_FLASH_FAILED) { g_set_error(error, G_IO_ERROR, G_FILE_ERROR_IO, "command 0x%02x returned error 0x%02x", cmd, err); return FALSE; } /* USB API doesn't have permission to access this file */ if (err == STEELSERIES_FIZZ_COMMAND_ERROR_PERMISSION_DENIED) { g_set_error(error, G_IO_ERROR, G_FILE_ERROR_ACCES, "command 0x%02x returned error 0x%02x", cmd, err); return FALSE; } /* USB API doesn't have permission to access this file */ if (err == STEELSERIES_FIZZ_COMMAND_ERROR_OPERATION_NO_SUPPORTED) { g_set_error(error, G_IO_ERROR, G_FILE_ERROR_PERM, "command 0x%02x returned error 0x%02x", cmd, err); return FALSE; } /* fallback */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "command 0x%02x returned error 0x%02x", cmd, err); return FALSE; } static gboolean fu_steelseries_fizz_command_and_check_error(FuDevice *device, guint8 *data, gsize datasz, GError **error) { gint gerr = G_FILE_ERROR_FAILED; const guint8 command = data[0]; guint8 err; guint8 cmd; if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, datasz, TRUE, error)) return FALSE; if (!fu_memread_uint8_safe(data, datasz, STEELSERIES_FIZZ_COMMAND_OFFSET, &cmd, error)) return FALSE; if (cmd != command) { g_set_error(error, G_IO_ERROR, gerr, "command invalid, got 0x%02x, expected 0x%02x", cmd, command); return FALSE; } if (!fu_memread_uint8_safe(data, datasz, STEELSERIES_FIZZ_ERROR_OFFSET, &err, error)) return FALSE; return fu_steelseries_fizz_command_error_to_error(cmd, err, error); } gchar * fu_steelseries_fizz_get_version(FuDevice *device, gboolean tunnel, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; guint8 cmd = STEELSERIES_FIZZ_VERSION_COMMAND; const guint8 mode = 0U; /* string */ if (tunnel) cmd |= STEELSERIES_FIZZ_COMMAND_TUNNEL_BIT; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_VERSION_COMMAND_OFFSET, cmd, error)) return NULL; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_VERSION_MODE_OFFSET, mode, error)) return NULL; fu_dump_raw(G_LOG_DOMAIN, "Version", data, sizeof(data)); if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), TRUE, error)) return NULL; fu_dump_raw(G_LOG_DOMAIN, "Version", data, sizeof(data)); /* success */ return fu_memstrsafe(data, sizeof(data), 0x0, sizeof(data), error); } static gboolean fu_steelseries_fizz_write_fs(FuDevice *device, gboolean tunnel, guint8 fs, guint8 id, GBytes *fw, FuProgress *progress, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; guint8 cmd = STEELSERIES_FIZZ_WRITE_ACCESS_FILE_COMMAND; g_autoptr(FuChunkArray) chunks = NULL; if (tunnel) cmd |= STEELSERIES_FIZZ_COMMAND_TUNNEL_BIT; chunks = fu_chunk_array_new_from_bytes(fw, 0x0, STEELSERIES_BUFFER_TRANSFER_SIZE); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); const guint16 size = fu_chunk_get_data_sz(chk); const guint32 offset = fu_chunk_get_address(chk); if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_WRITE_ACCESS_FILE_COMMAND_OFFSET, cmd, error)) return FALSE; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_WRITE_ACCESS_FILE_FILESYSTEM_OFFSET, fs, error)) return FALSE; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_WRITE_ACCESS_FILE_ID_OFFSET, id, error)) return FALSE; if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_FIZZ_WRITE_ACCESS_FILE_SIZE_OFFSET, size, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint32_safe(data, sizeof(data), STEELSERIES_FIZZ_WRITE_ACCESS_FILE_OFFSET_OFFSET, offset, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memcpy_safe(data, sizeof(data), STEELSERIES_FIZZ_WRITE_ACCESS_FILE_DATA_OFFSET, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, fu_chunk_get_data_sz(chk), error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "AccessFile", data, sizeof(data)); if (!fu_steelseries_fizz_command_and_check_error(device, data, sizeof(data), error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "AccessFile", data, sizeof(data)); fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_steelseries_fizz_erase_fs(FuDevice *device, gboolean tunnel, guint8 fs, guint8 id, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; guint8 cmd = STEELSERIES_FIZZ_ERASE_FILE_COMMAND; if (tunnel) cmd |= STEELSERIES_FIZZ_COMMAND_TUNNEL_BIT; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_ERASE_FILE_COMMAND_OFFSET, cmd, error)) return FALSE; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_ERASE_FILE_FILESYSTEM_OFFSET, fs, error)) return FALSE; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_ERASE_FILE_ID_OFFSET, id, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "EraseFile", data, sizeof(data)); if (!fu_steelseries_fizz_command_and_check_error(device, data, sizeof(data), error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "EraseFile", data, sizeof(data)); /* success */ return TRUE; } gboolean fu_steelseries_fizz_reset(FuDevice *device, gboolean tunnel, guint8 mode, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; guint8 cmd = STEELSERIES_FIZZ_RESET_COMMAND; if (tunnel) cmd |= STEELSERIES_FIZZ_COMMAND_TUNNEL_BIT; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_RESET_COMMAND_OFFSET, cmd, error)) return FALSE; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_RESET_MODE_OFFSET, mode, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "Reset", data, sizeof(data)); if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), FALSE, error)) return FALSE; /* success */ return TRUE; } gboolean fu_steelseries_fizz_get_crc32_fs(FuDevice *device, gboolean tunnel, guint8 fs, guint8 id, guint32 *calculated_crc, guint32 *stored_crc, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; guint8 cmd = STEELSERIES_FIZZ_FILE_CRC32_COMMAND; if (tunnel) cmd |= STEELSERIES_FIZZ_COMMAND_TUNNEL_BIT; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_FILE_CRC32_COMMAND_OFFSET, cmd, error)) return FALSE; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_FILE_CRC32_FILESYSTEM_OFFSET, fs, error)) return FALSE; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_FILE_CRC32_ID_OFFSET, id, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "FileCRC32", data, sizeof(data)); if (!fu_steelseries_fizz_command_and_check_error(device, data, sizeof(data), error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "FileCRC32", data, sizeof(data)); if (!fu_memread_uint32_safe(data, sizeof(data), STEELSERIES_FIZZ_FILE_CRC32_CALCULATED_CRC_OFFSET, calculated_crc, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(data, sizeof(data), STEELSERIES_FIZZ_FILE_CRC32_STORED_CRC_OFFSET, stored_crc, G_LITTLE_ENDIAN, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_steelseries_fizz_read_fs(FuDevice *device, gboolean tunnel, guint8 fs, guint8 id, guint8 *buf, gsize bufsz, FuProgress *progress, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; guint8 cmd = STEELSERIES_FIZZ_READ_ACCESS_FILE_COMMAND; g_autoptr(GPtrArray) chunks = NULL; if (tunnel) cmd |= STEELSERIES_FIZZ_COMMAND_TUNNEL_BIT; chunks = fu_chunk_array_mutable_new(buf, bufsz, 0x0, 0x0, STEELSERIES_BUFFER_TRANSFER_SIZE); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); const guint16 size = fu_chunk_get_data_sz(chk); const guint32 offset = fu_chunk_get_address(chk); if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_READ_ACCESS_FILE_COMMAND_OFFSET, cmd, error)) return FALSE; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_READ_ACCESS_FILE_FILESYSTEM_OFFSET, fs, error)) return FALSE; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_READ_ACCESS_FILE_ID_OFFSET, id, error)) return FALSE; if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_FIZZ_READ_ACCESS_FILE_SIZE_OFFSET, size, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint32_safe(data, sizeof(data), STEELSERIES_FIZZ_READ_ACCESS_FILE_OFFSET_OFFSET, offset, G_LITTLE_ENDIAN, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "AccessFile", data, sizeof(data)); if (!fu_steelseries_fizz_command_and_check_error(device, data, sizeof(data), error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "AccessFile", data, sizeof(data)); if (!fu_memcpy_safe(fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), 0x00, data, sizeof(data), STEELSERIES_FIZZ_READ_ACCESS_FILE_DATA_OFFSET, fu_chunk_get_data_sz(chk), error)) return FALSE; fu_progress_step_done(progress); } /* success */ return TRUE; } gboolean fu_steelseries_fizz_get_battery_level(FuDevice *device, gboolean tunnel, guint8 *level, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; guint8 cmd = STEELSERIES_FIZZ_BATTERY_LEVEL_COMMAND; if (tunnel) cmd |= STEELSERIES_FIZZ_COMMAND_TUNNEL_BIT; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_BATTERY_LEVEL_COMMAND_OFFSET, cmd, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "BatteryLevel", data, sizeof(data)); if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), TRUE, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "BatteryLevel", data, sizeof(data)); if (!fu_memread_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_BATTERY_LEVEL_LEVEL_OFFSET, level, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_steelseries_fizz_get_paired_status(FuDevice *device, guint8 *status, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; const guint8 cmd = STEELSERIES_FIZZ_PAIRED_STATUS_COMMAND; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_PAIRED_STATUS_COMMAND_OFFSET, cmd, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "PairedStatus", data, sizeof(data)); if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), TRUE, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "PairedStatus", data, sizeof(data)); if (!fu_memread_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_PAIRED_STATUS_STATUS_OFFSET, status, error)) return FALSE; /* success */ return TRUE; } gboolean fu_steelseries_fizz_get_connection_status(FuDevice *device, guint8 *status, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; const guint8 cmd = STEELSERIES_FIZZ_CONNECTION_STATUS_COMMAND; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_CONNECTION_STATUS_COMMAND_OFFSET, cmd, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "ConnectionStatus", data, sizeof(data)); if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), TRUE, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "ConnectionStatus", data, sizeof(data)); if (!fu_memread_uint8_safe(data, sizeof(data), STEELSERIES_FIZZ_CONNECTION_STATUS_STATUS_OFFSET, status, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_steelseries_fizz_attach(FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(GError) error_local = NULL; if (!fu_steelseries_fizz_reset(device, FALSE, STEELSERIES_FIZZ_RESET_MODE_NORMAL, &error_local)) g_warning("failed to reset: %s", error_local->message); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* success */ return TRUE; } static gboolean fu_steelseries_fizz_setup(FuDevice *device, GError **error) { guint32 calculated_crc; guint32 stored_crc; guint8 fs = STEELSERIES_FIZZ_FILESYSTEM_MOUSE; guint8 id = STEELSERIES_FIZZ_MOUSE_FILESYSTEM_BACKUP_APP_ID; g_autofree gchar *version = NULL; /* in bootloader mode */ if (fu_device_has_private_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_steelseries_fizz_parent_class)->setup(device, error)) return FALSE; /* skip if in bootloader mode */ if (fu_device_has_private_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; /* it is a USB receiver */ if (fu_device_has_private_flag(device, FU_STEELSERIES_DEVICE_FLAG_IS_RECEIVER)) { guint8 status; if (!fu_steelseries_fizz_get_paired_status(device, &status, error)) { g_prefix_error(error, "failed to get paired status: "); return FALSE; } if (status != 0) { g_autoptr(FuSteelseriesFizzTunnel) mouse_device = fu_steelseries_fizz_tunnel_new(FU_STEELSERIES_FIZZ(device)); fu_device_add_child(device, FU_DEVICE(mouse_device)); } fs = STEELSERIES_FIZZ_FILESYSTEM_RECEIVER; id = STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_BACKUP_APP_ID; } version = fu_steelseries_fizz_get_version(device, FALSE, error); if (version == NULL) { g_prefix_error(error, "failed to get version: "); return FALSE; } fu_device_set_version(device, version); /* it is a USB receiver */ if (fu_device_has_private_flag(device, FU_STEELSERIES_DEVICE_FLAG_IS_RECEIVER)) { fs = STEELSERIES_FIZZ_FILESYSTEM_RECEIVER; id = STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_BACKUP_APP_ID; } if (!fu_steelseries_fizz_get_crc32_fs(device, FALSE, fs, id, &calculated_crc, &stored_crc, error)) { g_prefix_error(error, "failed to get CRC32 FS 0x%02x ID 0x%02x: ", fs, id); return FALSE; } if (calculated_crc != stored_crc) g_warning("%s: checksum mismatch, got 0x%08x, expected 0x%08x", fu_device_get_name(device), calculated_crc, stored_crc); /* success */ return TRUE; } gboolean fu_steelseries_fizz_write_firmware_fs(FuDevice *device, gboolean tunnel, guint8 fs, guint8 id, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { guint32 calculated_crc; guint32 stored_crc; const guint8 *buf; gsize bufsz; g_autoptr(GBytes) blob = NULL; fu_progress_set_id(progress, G_STRLOC); if (tunnel) { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 13, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 87, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1, NULL); } else { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 38, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 60, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 2, NULL); } blob = fu_firmware_get_bytes(firmware, error); if (blob == NULL) return FALSE; buf = fu_bytes_get_data_safe(blob, &bufsz, error); if (buf == NULL) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "File", buf, bufsz); if (!fu_steelseries_fizz_erase_fs(device, tunnel, fs, id, error)) { g_prefix_error(error, "failed to erase FS 0x%02x ID 0x%02x: ", fs, id); return FALSE; } fu_progress_step_done(progress); if (!fu_steelseries_fizz_write_fs(device, tunnel, fs, id, blob, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write FS 0x%02x ID 0x%02x: ", fs, id); return FALSE; } fu_progress_step_done(progress); if (!fu_steelseries_fizz_get_crc32_fs(device, tunnel, fs, id, &calculated_crc, &stored_crc, error)) { g_prefix_error(error, "failed to get CRC32 FS 0x%02x ID 0x%02x: ", fs, id); return FALSE; } if (calculated_crc != stored_crc) { g_warning("%s: checksum mismatch, got 0x%08x, expected 0x%08x", fu_device_get_name(device), calculated_crc, stored_crc); } fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_steelseries_fizz_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { guint8 fs = STEELSERIES_FIZZ_FILESYSTEM_MOUSE; guint8 id = STEELSERIES_FIZZ_MOUSE_FILESYSTEM_BACKUP_APP_ID; /* it is a USB receiver */ if (fu_device_has_private_flag(device, FU_STEELSERIES_DEVICE_FLAG_IS_RECEIVER)) { fs = STEELSERIES_FIZZ_FILESYSTEM_RECEIVER; id = STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_BACKUP_APP_ID; } fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, 1); if (!fu_steelseries_fizz_write_firmware_fs(device, FALSE, fs, id, firmware, fu_progress_get_child(progress), flags, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } FuFirmware * fu_steelseries_fizz_read_firmware_fs(FuDevice *device, gboolean tunnel, guint8 fs, guint8 id, gsize size, FuProgress *progress, GError **error) { g_autoptr(FuFirmware) firmware = fu_steelseries_firmware_new(); g_autoptr(GBytes) blob = NULL; g_autofree guint8 *buf = NULL; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 100, NULL); buf = g_malloc0(size); if (!fu_steelseries_fizz_read_fs(device, tunnel, fs, id, buf, size, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to read FS 0x%02x ID 0x%02x: ", fs, id); return NULL; } fu_progress_step_done(progress); fu_dump_raw(G_LOG_DOMAIN, "Firmware", buf, size); blob = g_bytes_new_take(g_steal_pointer(&buf), size); if (!fu_firmware_parse(firmware, blob, FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return NULL; /* success */ return g_steal_pointer(&firmware); } static FuFirmware * fu_steelseries_fizz_read_firmware(FuDevice *device, FuProgress *progress, GError **error) { guint8 fs = STEELSERIES_FIZZ_FILESYSTEM_MOUSE; guint8 id = STEELSERIES_FIZZ_MOUSE_FILESYSTEM_BACKUP_APP_ID; g_autoptr(FuFirmware) firmware = NULL; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 100, NULL); /* it is a USB receiver */ if (fu_device_has_private_flag(device, FU_STEELSERIES_DEVICE_FLAG_IS_RECEIVER)) { fs = STEELSERIES_FIZZ_FILESYSTEM_RECEIVER; id = STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_BACKUP_APP_ID; } firmware = fu_steelseries_fizz_read_firmware_fs(device, FALSE, fs, id, fu_device_get_firmware_size_max(device), fu_progress_get_child(progress), error); if (firmware == NULL) return NULL; fu_progress_step_done(progress); /* success */ return g_steal_pointer(&firmware); } static void fu_steelseries_fizz_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 82, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 18, "reload"); } static void fu_steelseries_fizz_class_init(FuSteelseriesFizzClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->attach = fu_steelseries_fizz_attach; klass_device->setup = fu_steelseries_fizz_setup; klass_device->write_firmware = fu_steelseries_fizz_write_firmware; klass_device->read_firmware = fu_steelseries_fizz_read_firmware; klass_device->set_progress = fu_steelseries_fizz_set_progress; } static void fu_steelseries_fizz_init(FuSteelseriesFizz *self) { fu_steelseries_device_set_iface_idx_offset(FU_STEELSERIES_DEVICE(self), 0x03); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_protocol(FU_DEVICE(self), "com.steelseries.fizz"); fu_device_set_install_duration(FU_DEVICE(self), 13); /* 13 s */ fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_USER_REPLUG); /* 40 s */ fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_STEELSERIES_FIRMWARE); } fwupd-1.9.16/plugins/steelseries/fu-steelseries-fizz.h000066400000000000000000000070201460375044200230410ustar00rootroot00000000000000/* * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-steelseries-device.h" #define FU_TYPE_STEELSERIES_FIZZ (fu_steelseries_fizz_get_type()) G_DECLARE_FINAL_TYPE(FuSteelseriesFizz, fu_steelseries_fizz, FU, STEELSERIES_FIZZ, FuSteelseriesDevice) FuSteelseriesFizz * fu_steelseries_fizz_new(FuDevice *self); #define STEELSERIES_FIZZ_FILESYSTEM_RECEIVER 0x01U #define STEELSERIES_FIZZ_FILESYSTEM_MOUSE 0x02U #define STEELSERIES_FIZZ_CONNECTION_STATUS_NOT_CONNECTED 0x00U #define STEELSERIES_FIZZ_RESET_MODE_NORMAL 0x00U #define STEELSERIES_FIZZ_RESET_MODE_BOOTLOADER 0x01U #define STEELSERIES_FIZZ_BATTERY_LEVEL_CHARGING_BIT 0x80U #define STEELSERIES_FIZZ_BATTERY_LEVEL_STATUS_BITS 0x7fU #define STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_MAIN_BOOT_ID 0x01U #define STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_FSDATA_FILE_ID 0x02U #define STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_FACTORY_SETTINGS_ID 0x03U #define STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_MAIN_APP_ID 0x04U #define STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_BACKUP_APP_ID 0x05U #define STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_PROFILES_MOUSE_ID 0x06U #define STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_PROFILES_LIGHTING_ID 0x0fU #define STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_PROFILES_DEVICE_ID 0x10U #define STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_PROFILES_RESERVED_ID 0x11U #define STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_RECOVERY_ID 0x0dU #define STEELSERIES_FIZZ_RECEIVER_FILESYSTEM_FREE_SPACE_ID 0xf1U #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_SOFT_DEVICE_ID 0x00U #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_PROFILES_MOUSE_ID 0x06U #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_MAIN_APP_ID 0x07U #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_BACKUP_APP_ID 0x08U #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_MSB_DATA_ID 0x09U #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_FACTORY_SETTINGS_ID 0x0aU #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_FSDATA_FILE_ID 0x0bU #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_MAIN_BOOT_ID 0x0cU #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_RECOVERY_ID 0x0eU #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_PROFILES_LIGHTING_ID 0x0fU #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_PROFILES_DEVICE_ID 0x10U #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_FDS_PAGES_ID 0x12U #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_PROFILES_BLUETOOTH_ID 0x13U #define STEELSERIES_FIZZ_MOUSE_FILESYSTEM_FREE_SPACE_ID 0xf0U gchar * fu_steelseries_fizz_get_version(FuDevice *device, gboolean tunnel, GError **error); gboolean fu_steelseries_fizz_reset(FuDevice *device, gboolean tunnel, guint8 mode, GError **error); gboolean fu_steelseries_fizz_get_crc32_fs(FuDevice *device, gboolean tunnel, guint8 fs, guint8 id, guint32 *calculated_crc, guint32 *stored_crc, GError **error); FuFirmware * fu_steelseries_fizz_read_firmware_fs(FuDevice *device, gboolean tunnel, guint8 fs, guint8 id, gsize size, FuProgress *progress, GError **error); gboolean fu_steelseries_fizz_write_firmware_fs(FuDevice *device, gboolean tunnel, guint8 fs, guint8 id, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error); gboolean fu_steelseries_fizz_get_battery_level(FuDevice *device, gboolean tunnel, guint8 *level, GError **error); gboolean fu_steelseries_fizz_get_connection_status(FuDevice *device, guint8 *status, GError **error); fwupd-1.9.16/plugins/steelseries/fu-steelseries-gamepad.c000066400000000000000000000214531460375044200234560ustar00rootroot00000000000000/* * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-steelseries-gamepad.h" #define STEELSERIES_BUFFER_TRANSFER_SIZE 32 struct _FuSteelseriesGamepad { FuSteelseriesDevice parent_instance; }; G_DEFINE_TYPE(FuSteelseriesGamepad, fu_steelseries_gamepad, FU_TYPE_STEELSERIES_DEVICE) static gboolean fu_steelseries_gamepad_cmd_erase(FuDevice *device, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0xA1, 0xAA, 0x55}; /* USB receiver for gamepad is using different options */ if (fu_device_has_private_flag(device, FU_STEELSERIES_DEVICE_FLAG_IS_RECEIVER)) { /* USB receiver */ data[8] = 0xD0; data[9] = 0x01; } else { /* gamepad */ data[9] = 0x02; /* magic is needed for newer gamepad */ data[13] = 0x02; } if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), FALSE, error)) { g_prefix_error(error, "unable erase flash block: "); return FALSE; } /* timeout to give some time to erase */ fu_device_sleep(device, 20); /* ms */ return TRUE; } static gboolean fu_steelseries_gamepad_setup(FuDevice *device, GError **error) { g_autofree gchar *bootloader_version = NULL; guint16 fw_ver; guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; /* get version of FW and bootloader */ data[0] = 0x12; if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), TRUE, error)) return FALSE; if (!fu_memread_uint16_safe(data, sizeof(data), 0x01, &fw_ver, G_LITTLE_ENDIAN, error)) return FALSE; fu_device_set_version_u16(FU_DEVICE(device), fw_ver); if (!fu_memread_uint16_safe(data, sizeof(data), 0x03, &fw_ver, G_LITTLE_ENDIAN, error)) return FALSE; bootloader_version = fu_version_from_uint16(fw_ver, FWUPD_VERSION_FORMAT_BCD); fu_device_set_version_bootloader(device, bootloader_version); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); /* success */ return TRUE; } static gboolean fu_steelseries_gamepad_attach(FuDevice *device, FuProgress *progress, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0xA6, 0xAA, 0x55}; g_autoptr(GError) error_local = NULL; if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; /* switch to runtime mode */ if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), FALSE, &error_local)) g_debug("ignoring error on reset: %s", error_local->message); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_steelseries_gamepad_detach(FuDevice *device, FuProgress *progress, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0x02, 0x08}; g_autoptr(GError) error_local = NULL; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; /* switch to bootloader mode */ if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), FALSE, &error_local)) g_debug("ignoring error on reset: %s", error_local->message); /* controller will be renumbered after switching to bootloader mode */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_steelseries_gamepad_write_firmware_chunks(FuDevice *device, FuChunkArray *chunks, FuProgress *progress, guint32 *checksum, GError **error) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chunk = fu_chunk_array_index(chunks, i); guint16 chunk_checksum; guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0xA3}; /* block ID */ if (!fu_memwrite_uint16_safe(data, STEELSERIES_BUFFER_CONTROL_SIZE, 0x01, (guint16)i, G_LITTLE_ENDIAN, error)) return FALSE; /* 32B of data only */ if (!fu_memcpy_safe(data, STEELSERIES_BUFFER_CONTROL_SIZE, 0x03, fu_chunk_get_data(chunk), STEELSERIES_BUFFER_TRANSFER_SIZE, 0, fu_chunk_get_data_sz(chunk), error)) return FALSE; /* block checksum */ /* probably not necessary */ chunk_checksum = fu_sum16(data + 3, STEELSERIES_BUFFER_TRANSFER_SIZE); if (!fu_memwrite_uint16_safe(data, STEELSERIES_BUFFER_CONTROL_SIZE, 0x03 + STEELSERIES_BUFFER_TRANSFER_SIZE, chunk_checksum, G_LITTLE_ENDIAN, error)) return FALSE; *checksum += (guint32)chunk_checksum; if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), FALSE, error)) { g_prefix_error(error, "unable to flash block %u: ", i); return FALSE; } /* timeout to give some time to flash the block on device */ fu_device_sleep(device, 10); /* ms */ fu_progress_step_done(progress); } return TRUE; } static gboolean fu_steelseries_gamepad_write_checksum(FuDevice *device, guint32 checksum, GError **error) { /* write checksum */ guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0xA5, 0xAA, 0x55}; if (!fu_memwrite_uint32_safe(data, STEELSERIES_BUFFER_CONTROL_SIZE, 0x03, checksum, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), TRUE, error)) { g_prefix_error(error, "unable to write checksum: "); return FALSE; } /* validate checksum */ if (data[0] != 0xA5 || data[1] != 0xAA || data[2] != 0x55 || data[3] != 0x01) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Controller is unable to validate checksum"); return FALSE; } return TRUE; } static gboolean fu_steelseries_gamepad_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { guint32 checksum = 0; g_autoptr(GBytes) blob = NULL; g_autoptr(FuChunkArray) chunks = NULL; blob = fu_firmware_get_bytes(firmware, error); if (blob == NULL) return FALSE; chunks = fu_chunk_array_new_from_bytes(blob, 0, STEELSERIES_BUFFER_TRANSFER_SIZE); if (fu_chunk_array_length(chunks) > (G_MAXUINT16 + 1)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "too many firmware chunks for the device"); return FALSE; } fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1, NULL); /* erase all first */ if (!fu_steelseries_gamepad_cmd_erase(device, error)) return FALSE; fu_progress_step_done(progress); if (!fu_steelseries_gamepad_write_firmware_chunks(device, chunks, fu_progress_get_child(progress), &checksum, error)) return FALSE; fu_progress_step_done(progress); if (!fu_steelseries_gamepad_write_checksum(device, checksum, error)) return FALSE; fu_progress_step_done(progress); return TRUE; } static void fu_steelseries_gamepad_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 93, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "reload"); } static void fu_steelseries_gamepad_class_init(FuSteelseriesGamepadClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_steelseries_gamepad_setup; klass_device->attach = fu_steelseries_gamepad_attach; klass_device->detach = fu_steelseries_gamepad_detach; klass_device->write_firmware = fu_steelseries_gamepad_write_firmware; klass_device->set_progress = fu_steelseries_gamepad_set_progress; } static void fu_steelseries_gamepad_init(FuSteelseriesGamepad *self) { fu_steelseries_device_set_iface_idx_offset(FU_STEELSERIES_DEVICE(self), -1); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_BCD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_protocol(FU_DEVICE(self), "com.steelseries.gamepad"); fu_device_set_firmware_size_max(FU_DEVICE(self), (G_MAXUINT16 + 1) * STEELSERIES_BUFFER_TRANSFER_SIZE); } fwupd-1.9.16/plugins/steelseries/fu-steelseries-gamepad.h000066400000000000000000000006221460375044200234560ustar00rootroot00000000000000/* * Copyright (C) 2021 Denis Pynkin * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-steelseries-device.h" #define FU_TYPE_STEELSERIES_GAMEPAD (fu_steelseries_gamepad_get_type()) G_DECLARE_FINAL_TYPE(FuSteelseriesGamepad, fu_steelseries_gamepad, FU, STEELSERIES_GAMEPAD, FuSteelseriesDevice) fwupd-1.9.16/plugins/steelseries/fu-steelseries-mouse.c000066400000000000000000000046531460375044200232130ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-steelseries-mouse.h" #define STEELSERIES_TRANSACTION_TIMEOUT 1000 /* ms */ struct _FuSteelseriesMouse { FuUsbDevice parent_instance; }; G_DEFINE_TYPE(FuSteelseriesMouse, fu_steelseries_mouse, FU_TYPE_USB_DEVICE) static gboolean fu_steelseries_mouse_setup(FuDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); gboolean ret; gsize actual_len = 0; guint8 data[32]; g_autofree gchar *version = NULL; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_steelseries_mouse_parent_class)->setup(device, error)) return FALSE; memset(data, 0x00, sizeof(data)); data[0] = 0x16; ret = g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_CLASS, G_USB_DEVICE_RECIPIENT_INTERFACE, 0x09, 0x0200, 0x0000, data, sizeof(data), &actual_len, STEELSERIES_TRANSACTION_TIMEOUT, NULL, error); if (!ret) { g_prefix_error(error, "failed to do control transfer: "); return FALSE; } if (actual_len != 32) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only wrote %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } ret = g_usb_device_interrupt_transfer(usb_device, 0x81, /* EP1 IN */ data, sizeof(data), &actual_len, STEELSERIES_TRANSACTION_TIMEOUT, NULL, error); if (!ret) { g_prefix_error(error, "failed to do EP1 transfer: "); return FALSE; } if (actual_len != 32) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only read %" G_GSIZE_FORMAT "bytes", actual_len); return FALSE; } version = g_strdup_printf("%i.%i.%i", data[0], data[1], data[2]); fu_device_set_version(FU_DEVICE(device), version); /* success */ return TRUE; } static void fu_steelseries_mouse_init(FuSteelseriesMouse *self) { fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_usb_device_add_interface(FU_USB_DEVICE(self), 0x00); } static void fu_steelseries_mouse_class_init(FuSteelseriesMouseClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_steelseries_mouse_setup; } fwupd-1.9.16/plugins/steelseries/fu-steelseries-mouse.h000066400000000000000000000004731460375044200232140ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_STEELSERIES_MOUSE (fu_steelseries_mouse_get_type()) G_DECLARE_FINAL_TYPE(FuSteelseriesMouse, fu_steelseries_mouse, FU, STEELSERIES_MOUSE, FuUsbDevice) fwupd-1.9.16/plugins/steelseries/fu-steelseries-plugin.c000066400000000000000000000025621460375044200233560ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-steelseries-fizz-hid.h" #include "fu-steelseries-fizz-tunnel.h" #include "fu-steelseries-fizz.h" #include "fu-steelseries-gamepad.h" #include "fu-steelseries-mouse.h" #include "fu-steelseries-plugin.h" #include "fu-steelseries-sonic.h" struct _FuSteelseriesPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuSteelseriesPlugin, fu_steelseries_plugin, FU_TYPE_PLUGIN) static void fu_steelseries_plugin_init(FuSteelseriesPlugin *self) { } static void fu_steelseries_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_FIZZ); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_FIZZ_HID); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_FIZZ_TUNNEL); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_GAMEPAD); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_MOUSE); fu_plugin_add_device_gtype(plugin, FU_TYPE_STEELSERIES_SONIC); fu_plugin_add_udev_subsystem(plugin, "hidraw"); } static void fu_steelseries_plugin_class_init(FuSteelseriesPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_steelseries_plugin_constructed; } fwupd-1.9.16/plugins/steelseries/fu-steelseries-plugin.h000066400000000000000000000003671460375044200233640ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuSteelseriesPlugin, fu_steelseries_plugin, FU, STEELSERIES_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/steelseries/fu-steelseries-sonic.c000066400000000000000000001006311460375044200231670ustar00rootroot00000000000000/* * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-steelseries-sonic.h" #define STEELSERIES_BUFFER_FLASH_TRANSFER_SIZE 128 #define STEELSERIES_BUFFER_RAM_TRANSFER_SIZE 48 #define STEELSERIES_SONIC_WIRELESS_STATUS_OPCODE_OFFSET 0x0U #define STEELSERIES_SONIC_WIRELESS_STATUS_VALUE_OFFSET 0x0U #define STEELSERIES_SONIC_BATTERY_OPCODE_OFFSET 0x0U #define STEELSERIES_SONIC_BATTERY_BAT_MODE_OFFSET 0x1U #define STEELSERIES_SONIC_BATTERY_VALUE_OFFSET 0x0U #define STEELSERIES_SONIC_READ_FROM_RAM_OPCODE_OFFSET 0x0U #define STEELSERIES_SONIC_READ_FROM_RAM_OFFSET_OFFSET 0x2U #define STEELSERIES_SONIC_READ_FROM_RAM_SIZE_OFFSET 0x4U #define STEELSERIES_SONIC_READ_FROM_RAM_DATA_OFFSET 0x0U #define STEELSERIES_SONIC_READ_FROM_FLASH_OPCODE_OFFSET 0x0U #define STEELSERIES_SONIC_READ_FROM_FLASH_CHIPID_OFFSET 0x2U #define STEELSERIES_SONIC_READ_FROM_FLASH_OFFSET_OFFSET 0x4U #define STEELSERIES_SONIC_READ_FROM_FLASH_SIZE_OFFSET 0x8U #define STEELSERIES_SONIC_WRITE_TO_RAM_OPCODE_OFFSET 0x0U #define STEELSERIES_SONIC_WRITE_TO_RAM_OFFSET_OFFSET 0x2U #define STEELSERIES_SONIC_WRITE_TO_RAM_SIZE_OFFSET 0x4U #define STEELSERIES_SONIC_WRITE_TO_RAM_DATA_OFFSET 0x6U #define STEELSERIES_SONIC_WRITE_TO_FLASH_OPCODE_OFFSET 0x0U #define STEELSERIES_SONIC_WRITE_TO_FLASH_CHIPID_OFFSET 0x2U #define STEELSERIES_SONIC_WRITE_TO_FLASH_OFFSET_OFFSET 0x4U #define STEELSERIES_SONIC_WRITE_TO_FLASH_SIZE_OFFSET 0x8U #define STEELSERIES_SONIC_ERASE_OPCODE_OFFSET 0x0U #define STEELSERIES_SONIC_ERASE_CHIPID_OFFSET 0x2U #define STEELSERIES_SONIC_RESTART_CHIPID_OFFSET 0x0U typedef enum { STEELSERIES_SONIC_CHIP_NORDIC = 0, STEELSERIES_SONIC_CHIP_HOLTEK, STEELSERIES_SONIC_CHIP_MOUSE, /*< private >*/ STEELSERIES_SONIC_CHIP_LAST, } SteelseriesSonicChip; typedef enum { STEELSERIES_SONIC_WIRELESS_STATE_OFF, /* WDS not initiated, radio is off */ STEELSERIES_SONIC_WIRELESS_STATE_IDLE, /* WDS initiated, USB receiver is transmitting beacon * (mouse will not have this state) */ STEELSERIES_SONIC_WIRELESS_STATE_SEARCH, /* WDS initiated, mouse is trying to synchronize to * receiver (receiver will not have this state) */ STEELSERIES_SONIC_WIRELESS_STATE_LOCKED, /* USB receiver and mouse are synchronized, but not * necessarily connected. */ STEELSERIES_SONIC_WIRELESS_STATE_CONNECTED, /* USB receiver and mouse are connected. */ STEELSERIES_SONIC_WIRELESS_STATE_TERMINATED, /* Mouse has been disconnected from the USB * receiver. */ } SteelseriesSonicWirelessStatus; const guint16 STEELSERIES_SONIC_READ_FROM_RAM_OPCODE[] = {0x00c3U, 0x00c3U, 0x0083U}; const guint16 STEELSERIES_SONIC_READ_FROM_FLASH_OPCODE[] = {0x00c5U, 0x00c5U, 0x0085U}; const guint16 STEELSERIES_SONIC_WRITE_TO_RAM_OPCODE[] = {0x0043U, 0x0043U, 0x0003U}; const guint16 STEELSERIES_SONIC_WRITE_TO_FLASH_OPCODE[] = {0x0045U, 0x0045U, 0x0005U}; const guint16 STEELSERIES_SONIC_ERASE_OPCODE[] = {0x0048U, 0x0048U, 0x0008U}; const guint16 STEELSERIES_SONIC_RESTART_OPCODE[] = {0x0041U, 0x0041U, 0x0001U}; const guint16 STEELSERIES_SONIC_CHIP[] = {0x0002U, 0x0003U, 0x0002U}; const guint32 STEELSERIES_SONIC_FIRMWARE_SIZE[] = {0x9000U, 0x4000U, 0x12000U}; const gchar *STEELSERIES_SONIC_FIRMWARE_ID[] = {"app-nordic.bin", "app-holtek.bin", "mouse-app.bin"}; const guint STEELSERIES_SONIC_WRITE_PROGRESS_STEP_VALUE[][2] = {{5, 95}, {11, 89}, {3, 97}}; struct _FuSteelseriesSonic { FuSteelseriesDevice parent_instance; }; G_DEFINE_TYPE(FuSteelseriesSonic, fu_steelseries_sonic, FU_TYPE_STEELSERIES_DEVICE) static gboolean fu_steelseries_sonic_wireless_status(FuDevice *device, SteelseriesSonicWirelessStatus *status, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; const guint16 opcode = 0xE8U; /* USB receiver */ guint8 value; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_SONIC_WIRELESS_STATUS_OPCODE_OFFSET, opcode, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "WirelessStatus", data, sizeof(data)); if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), TRUE, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "WirelessStatus", data, sizeof(data)); if (!fu_memread_uint8_safe(data, sizeof(data), STEELSERIES_SONIC_WIRELESS_STATUS_VALUE_OFFSET, &value, error)) return FALSE; *status = value; /* success */ return TRUE; } static gboolean fu_steelseries_sonic_battery_state(FuDevice *device, guint16 *value, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; const guint16 opcode = 0xAAU; const guint8 bat_mode = 0x01U; /* percentage */ if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_SONIC_BATTERY_OPCODE_OFFSET, opcode, error)) return FALSE; if (!fu_memwrite_uint8_safe(data, sizeof(data), STEELSERIES_SONIC_BATTERY_BAT_MODE_OFFSET, bat_mode, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "BatteryState", data, sizeof(data)); if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), TRUE, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "BatteryState", data, sizeof(data)); if (!fu_memread_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_BATTERY_VALUE_OFFSET, value, G_LITTLE_ENDIAN, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_steelseries_sonic_read_from_ram(FuDevice *device, SteelseriesSonicChip chip, guint32 address, guint8 *buf, guint16 bufsz, FuProgress *progress, GError **error) { const guint16 opcode = STEELSERIES_SONIC_READ_FROM_RAM_OPCODE[chip]; guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; g_autoptr(GPtrArray) chunks = NULL; chunks = fu_chunk_array_mutable_new(buf, bufsz, 0x0, 0x0, STEELSERIES_BUFFER_RAM_TRANSFER_SIZE); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); const guint16 offset = fu_chunk_get_address(chk); const guint16 size = fu_chunk_get_data_sz(chk); if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_READ_FROM_RAM_OPCODE_OFFSET, opcode, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_READ_FROM_RAM_OFFSET_OFFSET, offset, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_READ_FROM_RAM_SIZE_OFFSET, size, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), TRUE, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "ReadFromRAM", data, sizeof(data)); if (!fu_memcpy_safe(fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), 0x0, data, sizeof(data), STEELSERIES_SONIC_READ_FROM_RAM_DATA_OFFSET, fu_chunk_get_data_sz(chk), error)) return FALSE; fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_steelseries_sonic_read_from_flash(FuDevice *device, SteelseriesSonicChip chip, guint32 address, guint8 *buf, guint32 bufsz, FuProgress *progress, GError **error) { const guint16 opcode = STEELSERIES_SONIC_READ_FROM_FLASH_OPCODE[chip]; const guint16 chipid = STEELSERIES_SONIC_CHIP[chip]; guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; g_autoptr(GPtrArray) chunks = NULL; chunks = fu_chunk_array_mutable_new(buf, bufsz, address, 0x0, STEELSERIES_BUFFER_FLASH_TRANSFER_SIZE); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); const guint32 offset = fu_chunk_get_address(chk); const guint16 size = fu_chunk_get_data_sz(chk); if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_READ_FROM_FLASH_OPCODE_OFFSET, opcode, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_READ_FROM_FLASH_CHIPID_OFFSET, chipid, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint32_safe(data, sizeof(data), STEELSERIES_SONIC_READ_FROM_FLASH_OFFSET_OFFSET, offset, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_READ_FROM_FLASH_SIZE_OFFSET, size, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), FALSE, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "ReadFromFlash", data, sizeof(data)); /* timeout to give some time to read from flash to ram */ fu_device_sleep(device, 15); /* ms */ if (!fu_steelseries_sonic_read_from_ram(device, chip, offset, fu_chunk_get_data_out(chk), size, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_steelseries_sonic_write_to_ram(FuDevice *device, SteelseriesSonicChip chip, guint16 address, GBytes *fw, FuProgress *progress, GError **error) { const guint16 opcode = STEELSERIES_SONIC_WRITE_TO_RAM_OPCODE[chip]; guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; g_autoptr(FuChunkArray) chunks = NULL; chunks = fu_chunk_array_new_from_bytes(fw, 0x0, STEELSERIES_BUFFER_RAM_TRANSFER_SIZE); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); const guint16 offset = fu_chunk_get_address(chk); const guint16 size = fu_chunk_get_data_sz(chk); if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_WRITE_TO_RAM_OPCODE_OFFSET, opcode, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_WRITE_TO_RAM_OFFSET_OFFSET, offset, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_WRITE_TO_RAM_SIZE_OFFSET, size, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memcpy_safe(data, sizeof(data), STEELSERIES_SONIC_WRITE_TO_RAM_DATA_OFFSET, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, fu_chunk_get_data_sz(chk), error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "WriteToRAM", data, sizeof(data)); if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), FALSE, error)) return FALSE; /* timeout to give some time to write to ram */ fu_device_sleep(device, 15); /* ms */ fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_steelseries_sonic_write_to_flash(FuDevice *device, SteelseriesSonicChip chip, guint32 address, GBytes *fw, FuProgress *progress, GError **error) { const guint16 opcode = STEELSERIES_SONIC_WRITE_TO_FLASH_OPCODE[chip]; const guint16 chipid = STEELSERIES_SONIC_CHIP[chip]; guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; g_autoptr(FuChunkArray) chunks = NULL; chunks = fu_chunk_array_new_from_bytes(fw, 0x0, STEELSERIES_BUFFER_FLASH_TRANSFER_SIZE); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GBytes) chk_blob = fu_chunk_get_bytes(chk); const guint32 offset = fu_chunk_get_address(chk); const guint16 size = fu_chunk_get_data_sz(chk); if (!fu_steelseries_sonic_write_to_ram(device, chip, offset, chk_blob, fu_progress_get_child(progress), error)) return FALSE; if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_WRITE_TO_FLASH_OPCODE_OFFSET, opcode, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_WRITE_TO_FLASH_CHIPID_OFFSET, chipid, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint32_safe(data, sizeof(data), STEELSERIES_SONIC_WRITE_TO_FLASH_OFFSET_OFFSET, offset, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_WRITE_TO_FLASH_SIZE_OFFSET, size, G_LITTLE_ENDIAN, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "WriteToFlash", data, sizeof(data)); if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), FALSE, error)) return FALSE; /* timeout to give some time to write from ram to flash */ fu_device_sleep(device, 15); /* ms */ fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_steelseries_sonic_erase(FuDevice *device, SteelseriesSonicChip chip, FuProgress *progress, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; const guint16 opcode = STEELSERIES_SONIC_ERASE_OPCODE[chip]; const guint16 chipid = STEELSERIES_SONIC_CHIP[chip]; fu_progress_set_id(progress, G_STRLOC); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_ERASE); fu_progress_set_steps(progress, 1); if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_ERASE_OPCODE_OFFSET, opcode, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_ERASE_CHIPID_OFFSET, chipid, G_LITTLE_ENDIAN, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "Erase", data, sizeof(data)); if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), FALSE, error)) return FALSE; /* timeout to give some time to erase flash */ fu_device_sleep_full(device, 1000, fu_progress_get_child(progress)); /* ms */ fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_steelseries_sonic_restart(FuDevice *device, SteelseriesSonicChip chip, FuProgress *progress, GError **error) { guint8 data[STEELSERIES_BUFFER_CONTROL_SIZE] = {0}; const guint16 opcode = STEELSERIES_SONIC_RESTART_OPCODE[chip]; fu_progress_set_id(progress, G_STRLOC); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_RESTART); fu_progress_set_steps(progress, 1); if (!fu_memwrite_uint16_safe(data, sizeof(data), STEELSERIES_SONIC_RESTART_CHIPID_OFFSET, opcode, G_LITTLE_ENDIAN, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "Restart", data, sizeof(data)); if (!fu_steelseries_device_cmd(FU_STEELSERIES_DEVICE(device), data, sizeof(data), FALSE, error)) return FALSE; /* timeout to give some time to restart chip */ fu_device_sleep_full(device, 3000, progress); /* ms */ fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_steelseries_sonic_wait_for_connect_cb(FuDevice *device, gpointer user_data, GError **error) { SteelseriesSonicWirelessStatus *wl_status = (SteelseriesSonicWirelessStatus *)user_data; if (!fu_steelseries_sonic_wireless_status(device, wl_status, error)) { g_prefix_error(error, "failed to get wireless status: "); return FALSE; } g_debug("WirelessStatus: %u", *wl_status); if (*wl_status != STEELSERIES_SONIC_WIRELESS_STATE_CONNECTED) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device is unreachable"); return FALSE; } /* success */ return TRUE; } static gboolean fu_steelseries_sonic_wait_for_connect(FuDevice *device, guint delay, FuProgress *progress, GError **error) { SteelseriesSonicWirelessStatus wl_status; g_autoptr(FwupdRequest) request = NULL; g_autoptr(GError) error_local = NULL; g_autofree gchar *msg = NULL; if (!fu_steelseries_sonic_wireless_status(device, &wl_status, error)) { g_prefix_error(error, "failed to get wireless status: "); return FALSE; } g_debug("WirelessStatus: %u", wl_status); if (wl_status == STEELSERIES_SONIC_WIRELESS_STATE_CONNECTED) { /* success */ return TRUE; } /* the user has to do something */ msg = g_strdup_printf("%s needs to be connected to start the update. " "Please put the switch button underneath to 2.4G, or " "click on any button to reconnect it.", fu_device_get_name(device)); request = fwupd_request_new(); fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_PRESS_UNLOCK); fwupd_request_set_message(request, msg); if (!fu_device_emit_request(device, request, progress, error)) return FALSE; if (!fu_device_retry_full(device, fu_steelseries_sonic_wait_for_connect_cb, delay / 1000, 1000, &wl_status, &error_local)) g_debug("%s", error_local->message); if (wl_status != STEELSERIES_SONIC_WIRELESS_STATE_CONNECTED) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NEEDS_USER_ACTION, msg); return FALSE; } /* success */ return TRUE; } static gboolean fu_steelseries_sonic_attach(FuDevice *device, FuProgress *progress, GError **error) { SteelseriesSonicChip chip; g_autoptr(FwupdRequest) request = NULL; g_autofree gchar *msg = NULL; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 50, "mouse"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 50, "holtek"); /* mouse */ chip = STEELSERIES_SONIC_CHIP_MOUSE; if (!fu_steelseries_sonic_restart(device, chip, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to restart chip %u: ", chip); return FALSE; } fu_progress_step_done(progress); /* USB receiver (nordic, holtek; same command) */ chip = STEELSERIES_SONIC_CHIP_HOLTEK; if (!fu_steelseries_sonic_restart(device, chip, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to restart chip %u: ", chip); return FALSE; } fu_progress_step_done(progress); /* the user has to do something */ msg = g_strdup_printf("%s needs to be manually restarted to complete the update. " "Please unplug the 2.4G USB Wireless adapter and then re-plug it.", fu_device_get_name(device)); request = fwupd_request_new(); fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); fwupd_request_set_message(request, msg); if (!fu_device_emit_request(device, request, progress, error)) return FALSE; /* success */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_steelseries_sonic_prepare(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { guint16 bat_state; if (!fu_steelseries_sonic_wait_for_connect(device, fu_device_get_remove_delay(device), progress, error)) return FALSE; if (!fu_steelseries_sonic_battery_state(device, &bat_state, error)) { g_prefix_error(error, "failed to get battery state: "); return FALSE; } g_debug("BatteryState: %u%%", bat_state); fu_device_set_battery_level(device, bat_state); /* success */ return TRUE; } static gboolean fu_steelseries_sonic_write_chip(FuDevice *device, SteelseriesSonicChip chip, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { const guint8 *buf; gsize bufsz; g_autoptr(FuFirmware) fw = NULL; g_autoptr(GBytes) blob = NULL; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, STEELSERIES_SONIC_WRITE_PROGRESS_STEP_VALUE[chip][0], NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, STEELSERIES_SONIC_WRITE_PROGRESS_STEP_VALUE[chip][1], NULL); fw = fu_firmware_get_image_by_id(firmware, STEELSERIES_SONIC_FIRMWARE_ID[chip], error); if (fw == NULL) return FALSE; blob = fu_firmware_get_bytes(fw, error); if (blob == NULL) return FALSE; buf = fu_bytes_get_data_safe(blob, &bufsz, error); if (buf == NULL) return FALSE; fu_dump_raw(G_LOG_DOMAIN, STEELSERIES_SONIC_FIRMWARE_ID[chip], buf, bufsz); if (!fu_steelseries_sonic_erase(device, chip, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to erase chip %u: ", chip); return FALSE; } fu_progress_step_done(progress); if (!fu_steelseries_sonic_write_to_flash(device, chip, 0x0, blob, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write to flash chip %u: ", chip); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static FuFirmware * fu_steelseries_sonic_read_chip(FuDevice *device, SteelseriesSonicChip chip, FuProgress *progress, GError **error) { guint32 bufsz; g_autoptr(GBytes) blob = NULL; g_autofree guint8 *buf = NULL; fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, 1); bufsz = STEELSERIES_SONIC_FIRMWARE_SIZE[chip]; buf = g_malloc0(bufsz); if (!fu_steelseries_sonic_read_from_flash(device, chip, 0x0, buf, bufsz, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to read from flash chip %u: ", chip); return NULL; } fu_progress_step_done(progress); blob = g_bytes_new_take(g_steal_pointer(&buf), bufsz); return fu_firmware_new_from_bytes(blob); } static gboolean fu_steelseries_sonic_verify_chip(FuDevice *device, SteelseriesSonicChip chip, FuFirmware *firmware, FuProgress *progress, GError **error) { g_autoptr(FuFirmware) fw_tmp = NULL; g_autoptr(FuFirmware) fw = NULL; g_autoptr(GBytes) blob_tmp = NULL; g_autoptr(GBytes) blob = NULL; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 100, NULL); fw = fu_firmware_get_image_by_id(firmware, STEELSERIES_SONIC_FIRMWARE_ID[chip], error); if (fw == NULL) return FALSE; blob = fu_firmware_get_bytes(fw, error); if (blob == NULL) return FALSE; fw_tmp = fu_steelseries_sonic_read_chip(device, chip, fu_progress_get_child(progress), error); if (fw_tmp == NULL) { g_prefix_error(error, "failed to read from flash chip %u: ", chip); return FALSE; } blob_tmp = fu_firmware_get_bytes(fw_tmp, error); if (blob_tmp == NULL) return FALSE; if (!fu_bytes_compare(blob_tmp, blob, error)) { fu_dump_raw(G_LOG_DOMAIN, "Verify", g_bytes_get_data(blob_tmp, NULL), g_bytes_get_size(blob_tmp)); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static FuFirmware * fu_steelseries_sonic_read_firmware(FuDevice *device, FuProgress *progress, GError **error) { SteelseriesSonicChip chip; g_autoptr(FuFirmware) firmware = fu_archive_firmware_new(); g_autoptr(FuFirmware) firmware_nordic = NULL; g_autoptr(FuFirmware) firmware_holtek = NULL; g_autoptr(FuFirmware) firmware_mouse = NULL; if (!fu_steelseries_sonic_wait_for_connect(device, fu_device_get_remove_delay(device), progress, error)) return NULL; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 18, "nordic"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 8, "holtek"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 73, "mouse"); fu_archive_firmware_set_format(FU_ARCHIVE_FIRMWARE(firmware), FU_ARCHIVE_FORMAT_ZIP); fu_archive_firmware_set_compression(FU_ARCHIVE_FIRMWARE(firmware), FU_ARCHIVE_COMPRESSION_NONE); /* nordic */ chip = STEELSERIES_SONIC_CHIP_NORDIC; firmware_nordic = fu_steelseries_sonic_read_chip(device, chip, fu_progress_get_child(progress), error); if (firmware_nordic == NULL) return NULL; fu_firmware_set_id(firmware_nordic, STEELSERIES_SONIC_FIRMWARE_ID[chip]); fu_firmware_add_image(firmware, firmware_nordic); fu_progress_step_done(progress); /* holtek */ chip = STEELSERIES_SONIC_CHIP_HOLTEK; firmware_holtek = fu_steelseries_sonic_read_chip(device, chip, fu_progress_get_child(progress), error); if (firmware_holtek == NULL) return NULL; fu_firmware_set_id(firmware_holtek, STEELSERIES_SONIC_FIRMWARE_ID[chip]); fu_firmware_add_image(firmware, firmware_holtek); fu_progress_step_done(progress); /* mouse */ chip = STEELSERIES_SONIC_CHIP_MOUSE; firmware_mouse = fu_steelseries_sonic_read_chip(device, chip, fu_progress_get_child(progress), error); if (firmware_mouse == NULL) return NULL; fu_firmware_set_id(firmware_mouse, STEELSERIES_SONIC_FIRMWARE_ID[chip]); fu_firmware_add_image(firmware, firmware_mouse); fu_progress_step_done(progress); /* success */ fu_firmware_set_id(firmware, FU_FIRMWARE_ID_PAYLOAD); return g_steal_pointer(&firmware); } static gboolean fu_steelseries_sonic_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { SteelseriesSonicChip chip; fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 34, "device-write-mouse"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 30, "device-verify-mouse"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 17, "device-write-nordic"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 7, "device-verify-nordic"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 8, "device-write-holtek"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 3, "device-verify-holtek"); /* mouse */ chip = STEELSERIES_SONIC_CHIP_MOUSE; if (!fu_steelseries_sonic_write_chip(device, chip, firmware, fu_progress_get_child(progress), flags, error)) return FALSE; fu_progress_step_done(progress); if (!fu_steelseries_sonic_verify_chip(device, chip, firmware, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* nordic */ chip = STEELSERIES_SONIC_CHIP_NORDIC; if (!fu_steelseries_sonic_write_chip(device, chip, firmware, fu_progress_get_child(progress), flags, error)) return FALSE; fu_progress_step_done(progress); if (!fu_steelseries_sonic_verify_chip(device, chip, firmware, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* holtek */ chip = STEELSERIES_SONIC_CHIP_HOLTEK; if (!fu_steelseries_sonic_write_chip(device, STEELSERIES_SONIC_CHIP_HOLTEK, firmware, fu_progress_get_child(progress), flags, error)) return FALSE; fu_progress_step_done(progress); if (!fu_steelseries_sonic_verify_chip(device, chip, firmware, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_steelseries_sonic_parse_firmware(FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { guint32 checksum_tmp; guint32 checksum; g_autoptr(GBytes) blob = NULL; blob = fu_firmware_get_bytes(firmware, error); if (blob == NULL) return FALSE; if (!fu_memread_uint32_safe(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), g_bytes_get_size(blob) - sizeof(checksum), &checksum, G_LITTLE_ENDIAN, error)) return FALSE; checksum_tmp = fu_crc32(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob) - sizeof(checksum_tmp)); checksum_tmp = ~checksum_tmp; if (checksum_tmp != checksum) { if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "checksum mismatch for %s, got 0x%08x, expected 0x%08x", fu_firmware_get_id(firmware), checksum_tmp, checksum); return FALSE; } g_debug("ignoring checksum mismatch, got 0x%08x, expected 0x%08x", checksum_tmp, checksum); } fu_firmware_add_flag(firmware, FU_FIRMWARE_FLAG_HAS_CHECKSUM); /* success */ return TRUE; } static FuFirmware * fu_steelseries_sonic_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { SteelseriesSonicChip chip; g_autoptr(FuFirmware) firmware_nordic = NULL; g_autoptr(FuFirmware) firmware_holtek = NULL; g_autoptr(FuFirmware) firmware_mouse = NULL; g_autoptr(FuFirmware) firmware = NULL; firmware = fu_archive_firmware_new(); if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* mouse */ chip = STEELSERIES_SONIC_CHIP_MOUSE; firmware_mouse = fu_firmware_get_image_by_id(firmware, STEELSERIES_SONIC_FIRMWARE_ID[chip], error); if (firmware_mouse == NULL) return NULL; if (!fu_steelseries_sonic_parse_firmware(firmware_mouse, flags, error)) return NULL; /* nordic */ chip = STEELSERIES_SONIC_CHIP_NORDIC; firmware_nordic = fu_firmware_get_image_by_id(firmware, STEELSERIES_SONIC_FIRMWARE_ID[chip], error); if (firmware_nordic == NULL) return NULL; if (!fu_steelseries_sonic_parse_firmware(firmware_nordic, flags, error)) return NULL; /* holtek */ chip = STEELSERIES_SONIC_CHIP_HOLTEK; firmware_holtek = fu_firmware_get_image_by_id(firmware, STEELSERIES_SONIC_FIRMWARE_ID[chip], error); if (firmware_holtek == NULL) return NULL; if (!fu_steelseries_sonic_parse_firmware(firmware_holtek, flags, error)) return NULL; /* success */ return g_steal_pointer(&firmware); } static void fu_steelseries_sonic_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 92, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 3, "reload"); } static void fu_steelseries_sonic_class_init(FuSteelseriesSonicClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->attach = fu_steelseries_sonic_attach; klass_device->prepare = fu_steelseries_sonic_prepare; klass_device->read_firmware = fu_steelseries_sonic_read_firmware; klass_device->write_firmware = fu_steelseries_sonic_write_firmware; klass_device->prepare_firmware = fu_steelseries_sonic_prepare_firmware; klass_device->set_progress = fu_steelseries_sonic_set_progress; } static void fu_steelseries_sonic_init(FuSteelseriesSonic *self) { fu_steelseries_device_set_iface_idx_offset(FU_STEELSERIES_DEVICE(self), -1); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_BCD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_request_flag(FU_DEVICE(self), FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE); fu_device_add_protocol(FU_DEVICE(self), "com.steelseries.sonic"); fu_device_set_install_duration(FU_DEVICE(self), 120); /* 2 min */ fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_USER_REPLUG); /* 40 s */ fu_device_set_battery_level(FU_DEVICE(self), FWUPD_BATTERY_LEVEL_INVALID); fu_device_set_battery_threshold(FU_DEVICE(self), 20); } fwupd-1.9.16/plugins/steelseries/fu-steelseries-sonic.h000066400000000000000000000006071460375044200231760ustar00rootroot00000000000000/* * Copyright (C) 2022 Gaël PORTAY * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-steelseries-device.h" #define FU_TYPE_STEELSERIES_SONIC (fu_steelseries_sonic_get_type()) G_DECLARE_FINAL_TYPE(FuSteelseriesSonic, fu_steelseries_sonic, FU, STEELSERIES_SONIC, FuSteelseriesDevice) fwupd-1.9.16/plugins/steelseries/meson.build000066400000000000000000000012141460375044200211120ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginSteelSeries"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('steelseries.quirk') plugin_builtins += static_library('fu_plugin_steelseries', sources: [ 'fu-steelseries-plugin.c', 'fu-steelseries-device.c', 'fu-steelseries-firmware.c', 'fu-steelseries-fizz-hid.c', 'fu-steelseries-fizz-tunnel.c', 'fu-steelseries-fizz.c', 'fu-steelseries-mouse.c', 'fu-steelseries-gamepad.c', 'fu-steelseries-sonic.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/steelseries/steelseries.quirk000066400000000000000000000047241460375044200223650ustar00rootroot00000000000000# Rival 100 [USB\VID_1038&PID_1702] Plugin = steelseries GType = FuSteelseriesMouse Summary = An optical gaming mouse Icon = input-mouse # Rival 3 Wireless [USB\VID_1038&PID_1830] Plugin = steelseries GType = FuSteelseriesSonic Icon = input-mouse # Aerox 3 Wireless [HIDRAW\VEN_0111&DEV_183A] Plugin = steelseries GType = FuSteelseriesFizzHid Name = Aerox 3 Wireless Mouse via Bluetooth Icon = input-mouse [USB\VID_1038&PID_1838] Plugin = steelseries GType = FuSteelseriesFizz Name = Aerox 3 Wireless USB Receiver Icon = usb-receiver CounterpartGuid = USB\VID_1038&PID_1839 FirmwareSize = 0x23000 Flags = is-receiver [USB\VID_1038&PID_1839] Plugin = steelseries GType = FuSteelseriesFizz Name = Aerox 3 Wireless USB Receiver bootloader Icon = usb-receiver FirmwareSize = 0x23000 Flags = is-bootloader,is-receiver,~usable-during-update [STEELSERIES\VID_1038&PID_1838&PROTOCOL_FIZZ_TUNNEL] Plugin = steelseries GType = FuSteelseriesFizzTunnel Name = Aerox 3 Wireless Mouse via USB Receiver Icon = input-mouse FirmwareSize = 0x27000 [USB\VID_1038&PID_183A] Plugin = steelseries GType = FuSteelseriesFizz Name = Aerox 3 Wireless Mouse Icon = input-mouse CounterpartGuid = USB\VID_1038&PID_1839,HIDRAW\VEN_0111&DEV_183A FirmwareSize = 0x27000 [USB\VID_1038&PID_183B] Plugin = steelseries GType = FuSteelseriesFizz Name = Aerox 3 Wireless Mouse bootloader Icon = input-mouse FirmwareSize = 0x27000 Flags = is-bootloader,~usable-during-update # Stratus Duo RX [USB\VID_1038&PID_1430] Plugin = steelseries GType = FuSteelseriesGamepad Name = Stratus Duo RX Icon = usb-receiver CounterpartGuid = USB\VID_1038&PID_1432 Flags = is-receiver [USB\VID_1038&PID_1432] Plugin = steelseries GType = FuSteelseriesGamepad Name = Stratus Duo RX bootloader Icon = usb-receiver CounterpartGuid = USB\VID_1038&PID_1430 Flags = is-receiver,is-bootloader # Stratus Duo [USB\VID_1038&PID_1431] Plugin = steelseries GType = FuSteelseriesGamepad Name = Stratus Duo Gamepad Icon = input-gaming CounterpartGuid = USB\VID_1038&PID_1433 [USB\VID_1038&PID_1433] Plugin = steelseries GType = FuSteelseriesGamepad Name = Stratus Duo bootloader CounterpartGuid = USB\VID_1038&PID_1431 Flags = is-bootloader # Stratus+ [USB\VID_1038&PID_1434] Plugin = steelseries GType = FuSteelseriesGamepad Name = Stratus+ gamepad Icon = input-gaming CounterpartGuid = USB\VID_1038&PID_1435 [USB\VID_1038&PID_1435] Plugin = steelseries GType = FuSteelseriesGamepad Name = Stratus+ bootloader CounterpartGuid = USB\VID_1038&PID_1434 Flags = is-bootloader fwupd-1.9.16/plugins/superio/000077500000000000000000000000001460375044200161115ustar00rootroot00000000000000fwupd-1.9.16/plugins/superio/README.md000066400000000000000000000035541460375044200173770ustar00rootroot00000000000000--- title: Plugin: SuperIO --- ## Introduction This plugin enumerates the various ITE85* SuperIO embedded controller ICs found in many laptops. Vendors wanting to expose the SuperIO functionality will need to add a HwId quirk entry to `superio.quirk`. See [the Wikipedia page](https://en.wikipedia.org/wiki/Super_I/O) for more details about SuperIO and what the EC actually does. Other useful links: * * * * * ## GUID Generation These devices use a custom GUID generated using the SuperIO chipset name: * `SuperIO-$(chipset)`, for example `SuperIO-IT8512` ## Update Behavior The firmware is deployed when the device is in normal runtime mode, but it is only activated on machine reboot. The firmware write is normally scheduled to be done very early in the boot process to minimize the chance the EC chip locking up if the user is actually using the keyboard controller. ## Vendor ID Security The vendor ID is set from the baseboard vendor, for example `DMI:Star Labs` ## Quirk Use This plugin uses the following plugin-specific quirks: ### SuperioControlPort Control (status/command) port number, e.g. `0x66` Since: 1.6.2 ### SuperioDataPort Data port number, e.g. `0x62` Since: 1.6.2 ### SuperioAutoloadAction Autoload action, specified by ITE: `none`, `disable`, `on`, `off` Since: 1.6.2 ### SuperioTimeout Maximum wait time in ms (default value is `250`) Since: 1.6.2 ## External Interface Access This plugin requires access to raw system memory via `inb`/`outb`. ## Version Considerations This plugin has been available since fwupd version `1.1.2`. fwupd-1.9.16/plugins/superio/fu-superio-common.c000066400000000000000000000025151460375044200216440ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-superio-common.h" const gchar * fu_superio_ldn_to_text(guint8 ldn) { if (ldn == SIO_LDN_FDC) return "Floppy Disk Controller"; if (ldn == SIO_LDN_GPIO) return "General Purpose IO"; if (ldn == SIO_LDN_PARALLEL_PORT) return "Parallel Port"; if (ldn == SIO_LDN_UART1) return "Serial Port 1"; if (ldn == SIO_LDN_UART2) return "Serial Port 2"; if (ldn == SIO_LDN_UART3) return "Serial Port 3"; if (ldn == SIO_LDN_UART4) return "Serial Port 4"; if (ldn == SIO_LDN_SWUC) return "System Wake-Up Control"; if (ldn == SIO_LDN_KBC_MOUSE) return "KBC/Mouse"; if (ldn == SIO_LDN_KBC_KEYBOARD) return "KBC/Keyboard"; if (ldn == SIO_LDN_CIR) return "Consumer IR"; if (ldn == SIO_LDN_SMFI) return "Shared Memory/Flash"; if (ldn == SIO_LDN_RTCT) return "RTC-like Timer"; if (ldn == SIO_LDN_SSSP1) return "Serial Peripheral"; if (ldn == SIO_LDN_PECI) return "Platform Environmental Control"; if (ldn == SIO_LDN_PM1) return "Power Management 1"; if (ldn == SIO_LDN_PM2) return "Power Management 2"; if (ldn == SIO_LDN_PM3) return "Power Management 3"; if (ldn == SIO_LDN_PM4) return "Power Management 4"; if (ldn == SIO_LDN_PM5) return "Power Management 5"; return NULL; } fwupd-1.9.16/plugins/superio/fu-superio-common.h000066400000000000000000000077421460375044200216600ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /* for all LDNs */ #define SIO_LDNxx_IDX_LDNSEL 0x07 #define SIO_LDNxx_IDX_CHIPID1 0x20 #define SIO_LDNxx_IDX_CHIPID2 0x21 #define SIO_LDNxx_IDX_CHIPVER 0x22 #define SIO_LDNxx_IDX_SIOCTRL 0x23 #define SIO_LDNxx_IDX_SIOIRQ 0x25 #define SIO_LDNxx_IDX_SIOGP 0x26 #define SIO_LDNxx_IDX_SIOPWR 0x2d #define SIO_LDNxx_IDX_D2ADR 0x2e #define SIO_LDNxx_IDX_D2DAT 0x2f #define SIO_LDNxx_IDX_IOBAD0 0x60 /* 16 bit */ #define SIO_LDNxx_IDX_IOBAD1 0x62 /* 16 bit */ /* these registers are only accessible by EC */ #define GCTRL_ECHIPID1 0x2000 #define GCTRL_ECHIPID2 0x2001 #define GCTRL_ECHIPVER 0x2002 /* to create sub-addresses */ #define SIO_DEPTH2_I2EC_ADDRL 0x10 #define SIO_DEPTH2_I2EC_ADDRH 0x11 #define SIO_DEPTH2_I2EC_DATA 0x12 /* * The PMC is a communication channel used between the host and the EC. * Compatible mode uses four registers: * * Name | EC | HOST | ADDR * _____________________|_______________|_______________|______ * PMDIR | RO | WO | 0x62 * PMDOR | WO | RO | 0x62 * PMCMDR | RO | RO | 0x66 * PMSTR | RO | RO | 0x66 */ #define SIO_EC_PMC_PM1STS 0x00 #define SIO_EC_PMC_PM1DO 0x01 #define SIO_EC_PMC_PM1DOSCI 0x02 #define SIO_EC_PMC_PM1DOCMI 0x03 #define SIO_EC_PMC_PM1DI 0x04 #define SIO_EC_PMC_PM1DISCI 0x05 #define SIO_EC_PMC_PM1CTL 0x06 #define SIO_EC_PMC_PM1IC 0x07 #define SIO_EC_PMC_PM1IE 0x08 /* SPI commands */ #define SIO_SPI_CMD_READ 0x03 #define SIO_SPI_CMD_HS_READ 0x0b #define SIO_SPI_CMD_FAST_READ_DUAL_OP 0x3b #define SIO_SPI_CMD_FAST_READ_DUAL_IO 0xbb #define SIO_SPI_CMD_4K_SECTOR_ERASE 0xd7 /* or 0x20 or 0x52 */ #define SIO_SPI_CMD_64K_BLOCK_ERASE 0xd8 #define SIO_SPI_CMD_CHIP_ERASE 0xc7 /* or 0x60 */ #define SIO_SPI_CMD_PAGE_PROGRAM 0x02 #define SIO_SPI_CMD_WRITE_WORD 0xad #define SIO_SPI_CMD_RDSR 0x05 /* read status register */ #define SIO_SPI_CMD_WRSR 0x01 /* write status register */ #define SIO_SPI_CMD_WREN 0x06 /* write enable */ #define SIO_SPI_CMD_WRDI 0x04 /* write disable */ #define SIO_SPI_CMD_RDID 0xab #define SIO_SPI_CMD_JEDEC_ID 0x9f #define SIO_SPI_CMD_DPD 0xb9 /* deep sleep */ #define SIO_SPI_CMD_RDPD 0xab /* wake from deep sleep */ /* EC Status Register (see ec/google/chromeec/ec_commands.h) */ #define SIO_STATUS_EC_OBF (1 << 0) /* o/p buffer full */ #define SIO_STATUS_EC_IBF (1 << 1) /* i/p buffer full */ #define SIO_STATUS_EC_IS_BUSY (1 << 2) #define SIO_STATUS_EC_IS_CMD (1 << 3) #define SIO_STATUS_EC_BURST_ENABLE (1 << 4) #define SIO_STATUS_EC_SCI (1 << 5) /* 1 if more events in queue */ /* EC Command Register (see KB3700-ds-01.pdf) */ #define SIO_CMD_EC_READ 0x80 #define SIO_CMD_EC_WRITE 0x81 #define SIO_CMD_EC_BURST_ENABLE 0x82 #define SIO_CMD_EC_BURST_DISABLE 0x83 #define SIO_CMD_EC_QUERY_EVENT 0x84 #define SIO_CMD_EC_GET_NAME_STR 0x92 #define SIO_CMD_EC_GET_VERSION_STR 0x93 #define SIO_CMD_EC_DISABLE_HOST_WA 0xdc #define SIO_CMD_EC_ENABLE_HOST_WA 0xfc typedef enum { SIO_LDN_FDC = 0x00, /* IT87 */ SIO_LDN_UART1 = 0x01, /* IT87+IT89 */ SIO_LDN_UART2 = 0x02, /* IT87+IT89 */ SIO_LDN_PARALLEL_PORT = 0x03, /* IT87 */ SIO_LDN_SWUC = 0x04, /* IT87+IT89 */ SIO_LDN_KBC_MOUSE = 0x05, /* IT87+IT89 */ SIO_LDN_KBC_KEYBOARD = 0x06, /* IT87+IT89 */ SIO_LDN_GPIO = 0x07, /* IT87 */ SIO_LDN_UART3 = 0x08, /* IT87 */ SIO_LDN_UART4 = 0x09, /* IT87 */ SIO_LDN_CIR = 0x0a, /* IT89 */ SIO_LDN_SMFI = 0x0f, /* IT89 */ SIO_LDN_RTCT = 0x10, /* IT89 */ SIO_LDN_PM1 = 0x11, /* IT89 */ SIO_LDN_PM2 = 0x12, /* IT89 */ SIO_LDN_SSSP1 = 0x13, /* IT89 */ SIO_LDN_PECI = 0x14, /* IT89 */ SIO_LDN_PM3 = 0x17, /* IT89 */ SIO_LDN_PM4 = 0x18, /* IT89 */ SIO_LDN_PM5 = 0x19, /* IT89 */ SIO_LDN_LAST = 0x1a } SioLdn; const gchar * fu_superio_ldn_to_text(guint8 ldn); fwupd-1.9.16/plugins/superio/fu-superio-device.c000066400000000000000000000357671460375044200216320ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2021, TUXEDO Computers GmbH * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-superio-common.h" #include "fu-superio-device.h" #define FU_PLUGIN_SUPERIO_DEFAULT_TIMEOUT 250 /* ms */ typedef struct { gchar *chipset; guint timeout_ms; guint16 port; guint16 data_port; guint16 control_port; guint16 id; } FuSuperioDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuSuperioDevice, fu_superio_device, FU_TYPE_UDEV_DEVICE) #define GET_PRIVATE(o) (fu_superio_device_get_instance_private(o)) enum { PROP_0, PROP_CHIPSET, PROP_LAST }; gboolean fu_superio_device_io_read(FuSuperioDevice *self, guint8 addr, guint8 *data, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE(self); if (priv->port == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "port isn't set"); return FALSE; } if (!fu_udev_device_pwrite(FU_UDEV_DEVICE(self), priv->port, &addr, 1, error)) return FALSE; if (!fu_udev_device_pread(FU_UDEV_DEVICE(self), priv->port + 1, data, 1, error)) return FALSE; return TRUE; } static gboolean fu_superio_device_io_read16(FuSuperioDevice *self, guint8 addr, guint16 *data, GError **error) { guint8 msb; guint8 lsb; if (!fu_superio_device_io_read(self, addr, &msb, error)) return FALSE; if (!fu_superio_device_io_read(self, addr + 1, &lsb, error)) return FALSE; *data = ((guint16)msb << 8) | (guint16)lsb; return TRUE; } gboolean fu_superio_device_io_write(FuSuperioDevice *self, guint8 addr, guint8 data, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE(self); if (priv->port == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "port isn't set"); return FALSE; } if (!fu_udev_device_pwrite(FU_UDEV_DEVICE(self), priv->port, &addr, 1, error)) return FALSE; if (!fu_udev_device_pwrite(FU_UDEV_DEVICE(self), priv->port + 1, &data, 1, error)) return FALSE; return TRUE; } static gboolean fu_superio_device_set_ldn(FuSuperioDevice *self, guint8 ldn, GError **error) { return fu_superio_device_io_write(self, SIO_LDNxx_IDX_LDNSEL, ldn, error); } static gboolean fu_superio_device_regdump(FuSuperioDevice *self, guint8 ldn, GError **error) { const gchar *ldnstr = fu_superio_ldn_to_text(ldn); guint8 buf[0xff] = {0x00}; guint16 iobad0 = 0x0; guint16 iobad1 = 0x0; g_autoptr(GString) str = g_string_new(NULL); /* set LDN */ if (!fu_superio_device_set_ldn(self, ldn, error)) return FALSE; for (guint i = 0x00; i < 0xff; i++) { if (!fu_superio_device_io_read(self, i, &buf[i], error)) return FALSE; } /* get the i/o base addresses */ if (!fu_superio_device_io_read16(self, SIO_LDNxx_IDX_IOBAD0, &iobad0, error)) return FALSE; if (!fu_superio_device_io_read16(self, SIO_LDNxx_IDX_IOBAD1, &iobad1, error)) return FALSE; g_string_append_printf(str, "LDN:0x%02x ", ldn); if (iobad0 != 0x0) g_string_append_printf(str, "IOBAD0:0x%04x ", iobad0); if (iobad1 != 0x0) g_string_append_printf(str, "IOBAD1:0x%04x ", iobad1); if (ldnstr != NULL) g_string_append_printf(str, "(%s)", ldnstr); fu_dump_raw(G_LOG_DOMAIN, str->str, buf, sizeof(buf)); return TRUE; } static void fu_superio_device_to_string(FuDevice *device, guint idt, GString *str) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); FuSuperioDevicePrivate *priv = GET_PRIVATE(self); /* FuUdevDevice->to_string */ FU_DEVICE_CLASS(fu_superio_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "Chipset", priv->chipset); fu_string_append_kx(str, idt, "Id", priv->id); fu_string_append_kx(str, idt, "Port", priv->port); fu_string_append_kx(str, idt, "DataPort", priv->data_port); fu_string_append_kx(str, idt, "ControlPort", priv->control_port); } static gboolean fu_superio_device_check_id(FuSuperioDevice *self, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE(self); guint16 id_tmp; /* no quirk entry */ if (priv->id == 0x0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "invalid SuperioId"); return FALSE; } /* can't check the ID, assume it's correct */ if (priv->port == 0) return TRUE; /* check ID, which can be done from any LDN */ if (!fu_superio_device_io_read16(self, SIO_LDNxx_IDX_CHIPID1, &id_tmp, error)) return FALSE; if (priv->id != id_tmp) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SuperIO chip not supported, got %04x, expected %04x", (guint)id_tmp, (guint)priv->id); return FALSE; } return TRUE; } static gboolean fu_superio_device_wait_for(FuSuperioDevice *self, guint8 mask, gboolean set, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GTimer) timer = g_timer_new(); do { guint8 status = 0x00; if (!fu_udev_device_pread(FU_UDEV_DEVICE(self), priv->control_port, &status, 1, error)) return FALSE; if (g_timer_elapsed(timer, NULL) * 1000.0f > priv->timeout_ms) break; if (set && (status & mask) != 0) return TRUE; if (!set && (status & mask) == 0) return TRUE; } while (TRUE); g_set_error(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "timed out whilst waiting for 0x%02x:%i", mask, set); return FALSE; } gboolean fu_superio_device_ec_read_data(FuSuperioDevice *self, guint8 *data, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE(self); if (!fu_superio_device_wait_for(self, SIO_STATUS_EC_OBF, TRUE, error)) return FALSE; return fu_udev_device_pread(FU_UDEV_DEVICE(self), priv->data_port, data, 1, error); } gboolean fu_superio_device_ec_write_data(FuSuperioDevice *self, guint8 data, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE(self); if (!fu_superio_device_wait_for(self, SIO_STATUS_EC_IBF, FALSE, error)) return FALSE; return fu_udev_device_pwrite(FU_UDEV_DEVICE(self), priv->data_port, &data, 1, error); } gboolean fu_superio_device_ec_write_cmd(FuSuperioDevice *self, guint8 cmd, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE(self); if (!fu_superio_device_wait_for(self, SIO_STATUS_EC_IBF, FALSE, error)) return FALSE; return fu_udev_device_pwrite(FU_UDEV_DEVICE(self), priv->control_port, &cmd, 1, error); } static gboolean fu_superio_device_ec_flush(FuSuperioDevice *self, GError **error) { FuSuperioDevicePrivate *priv = GET_PRIVATE(self); guint8 status = 0x00; g_autoptr(GTimer) timer = g_timer_new(); do { guint8 unused = 0; if (!fu_udev_device_pread(FU_UDEV_DEVICE(self), priv->control_port, &status, 1, error)) return FALSE; if ((status & SIO_STATUS_EC_OBF) == 0) break; if (!fu_udev_device_pread(FU_UDEV_DEVICE(self), priv->data_port, &unused, 1, error)) return FALSE; if (g_timer_elapsed(timer, NULL) * 1000.f > priv->timeout_ms) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "timed out whilst waiting for flush"); return FALSE; } } while (TRUE); return TRUE; } gboolean fu_superio_device_reg_read(FuSuperioDevice *self, guint8 address, guint8 *data, GError **error) { if (!fu_superio_device_ec_write_cmd(self, SIO_CMD_EC_READ, error)) return FALSE; if (!fu_superio_device_ec_write_data(self, address, error)) return FALSE; return fu_superio_device_ec_read_data(self, data, error); } gboolean fu_superio_device_reg_write(FuSuperioDevice *self, guint8 address, guint8 data, GError **error) { if (!fu_superio_device_ec_write_cmd(self, SIO_CMD_EC_WRITE, error)) return FALSE; if (!fu_superio_device_ec_write_data(self, address, error)) return FALSE; return fu_superio_device_ec_write_data(self, data, error); } static gboolean fu_superio_device_probe(FuDevice *device, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); FuSuperioDevicePrivate *priv = GET_PRIVATE(self); g_autofree gchar *devid = NULL; g_autofree gchar *name = NULL; /* use the chipset name as the logical ID and for the GUID */ fu_device_set_logical_id(device, priv->chipset); devid = g_strdup_printf("SuperIO-%s", priv->chipset); fu_device_add_instance_id(device, devid); name = g_strdup_printf("SuperIO %s", priv->chipset); fu_device_set_name(FU_DEVICE(self), name); return TRUE; } static gboolean fu_superio_device_setup(FuDevice *device, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); FuSuperioDevicePrivate *priv = GET_PRIVATE(self); /* check ID is correct */ if (!fu_superio_device_check_id(self, error)) { g_prefix_error(error, "failed to probe id: "); return FALSE; } /* discover the data port and control port from PM1 */ if (priv->data_port == 0 && priv->control_port == 0) { /* dump LDNs */ if (g_getenv("FWUPD_SUPERIO_VERBOSE") != NULL) { for (guint j = 0; j < SIO_LDN_LAST; j++) { if (!fu_superio_device_regdump(self, j, error)) return FALSE; } } /* set Power Management I/F Channel 1 LDN */ if (!fu_superio_device_set_ldn(self, SIO_LDN_PM1, error)) return FALSE; /* get the PM1 IOBAD0 address */ if (!fu_superio_device_io_read16(self, SIO_LDNxx_IDX_IOBAD0, &priv->data_port, error)) return FALSE; /* get the PM1 IOBAD1 address */ if (!fu_superio_device_io_read16(self, SIO_LDNxx_IDX_IOBAD1, &priv->control_port, error)) return FALSE; } /* sanity check that EC is usable */ if (!fu_superio_device_wait_for(self, SIO_STATUS_EC_IBF, FALSE, error)) { g_prefix_error(error, "sanity check: "); return FALSE; } /* drain */ if (!fu_superio_device_ec_flush(self, error)) { g_prefix_error(error, "failed to flush: "); return FALSE; } /* dump PMC register map */ if (g_getenv("FWUPD_SUPERIO_VERBOSE") != NULL) { guint8 buf[0xff] = {0x00}; for (guint i = 0x00; i < 0xff; i++) { g_autoptr(GError) error_local = NULL; if (!fu_superio_device_reg_read(self, i, &buf[i], &error_local)) { g_debug("param: 0x%02x = %s", i, error_local->message); continue; } } fu_dump_raw(G_LOG_DOMAIN, "EC Registers", buf, sizeof(buf)); } /* success */ return TRUE; } static FuFirmware * fu_superio_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { gsize sz = 0; const guint8 *buf = g_bytes_get_data(fw, &sz); const guint8 sig1[] = {0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5}; const guint8 sig2[] = {0x85, 0x12, 0x5a, 0x5a, 0xaa}; /* find signature -- maybe ignore byte 0x14 too? */ for (gsize off = 0; off < sz; off += 16) { if (memcmp(&buf[off], sig1, sizeof(sig1)) == 0 && memcmp(&buf[off + 8], sig2, sizeof(sig2)) == 0) { g_debug("found signature at 0x%04x", (guint)off); return fu_firmware_new_from_bytes(fw); } } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "did not detect signature in firmware image"); return NULL; } static void fu_superio_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(object); FuSuperioDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_CHIPSET: g_value_set_string(value, priv->chipset); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_superio_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(object); FuSuperioDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_CHIPSET: g_free(priv->chipset); priv->chipset = g_value_dup_string(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static gboolean fu_superio_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); FuSuperioDevicePrivate *priv = GET_PRIVATE(self); guint64 tmp = 0; if (g_strcmp0(key, "SuperioAutoloadAction") == 0) return TRUE; if (g_strcmp0(key, "SuperioId") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; priv->id = tmp; return TRUE; } if (g_strcmp0(key, "SuperioPort") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; priv->port = tmp; return TRUE; } if (g_strcmp0(key, "SuperioControlPort") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; priv->control_port = tmp; return TRUE; } if (g_strcmp0(key, "SuperioDataPort") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; priv->data_port = tmp; return TRUE; } if (g_strcmp0(key, "SuperioTimeout") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT, error)) return FALSE; priv->timeout_ms = tmp; return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_superio_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_superio_device_init(FuSuperioDevice *self) { FuSuperioDevicePrivate *priv = GET_PRIVATE(self); priv->timeout_ms = FU_PLUGIN_SUPERIO_DEFAULT_TIMEOUT; fu_device_set_physical_id(FU_DEVICE(self), "/dev/port"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_protocol(FU_DEVICE(self), "tw.com.ite.superio"); fu_device_set_summary(FU_DEVICE(self), "Embedded controller"); fu_device_add_icon(FU_DEVICE(self), "computer"); } static void fu_superio_device_finalize(GObject *object) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(object); FuSuperioDevicePrivate *priv = GET_PRIVATE(self); g_free(priv->chipset); G_OBJECT_CLASS(fu_superio_device_parent_class)->finalize(object); } static void fu_superio_device_class_init(FuSuperioDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); /* properties */ object_class->get_property = fu_superio_device_get_property; object_class->set_property = fu_superio_device_set_property; /** * FuSuperioDevice:chipset: * * The SuperIO chipset name being used. */ pspec = g_param_spec_string("chipset", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_CHIPSET, pspec); object_class->finalize = fu_superio_device_finalize; klass_device->to_string = fu_superio_device_to_string; klass_device->set_quirk_kv = fu_superio_device_set_quirk_kv; klass_device->probe = fu_superio_device_probe; klass_device->setup = fu_superio_device_setup; klass_device->prepare_firmware = fu_superio_device_prepare_firmware; klass_device->set_progress = fu_superio_device_set_progress; } fwupd-1.9.16/plugins/superio/fu-superio-device.h000066400000000000000000000020561460375044200216200ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_SUPERIO_DEVICE (fu_superio_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuSuperioDevice, fu_superio_device, FU, SUPERIO_DEVICE, FuUdevDevice) struct _FuSuperioDeviceClass { FuUdevDeviceClass parent_class; }; gboolean fu_superio_device_ec_read_data(FuSuperioDevice *self, guint8 *data, GError **error); gboolean fu_superio_device_ec_write_data(FuSuperioDevice *self, guint8 data, GError **error); gboolean fu_superio_device_ec_write_cmd(FuSuperioDevice *self, guint8 cmd, GError **error); gboolean fu_superio_device_reg_read(FuSuperioDevice *self, guint8 address, guint8 *data, GError **error); gboolean fu_superio_device_reg_write(FuSuperioDevice *self, guint8 address, guint8 data, GError **error); gboolean fu_superio_device_io_read(FuSuperioDevice *self, guint8 addr, guint8 *data, GError **error); gboolean fu_superio_device_io_write(FuSuperioDevice *self, guint8 addr, guint8 data, GError **error); fwupd-1.9.16/plugins/superio/fu-superio-it55-device.c000066400000000000000000000442371460375044200224060ustar00rootroot00000000000000/* * Copyright (C) 2021, TUXEDO Computers GmbH * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-superio-common.h" #include "fu-superio-it55-device.h" /* ROM of IT5570 consists of 64KB blocks. Blocks can be further subdivided in * 256-byte chunks, which is especially visible when erasing the ROM. This is * because in case of erasure offset within a block is specified in chunks (even * though erasure is done one kilobyte at a time). * * Accessing ROM requires entering a special mode, which should be always left * to restore normal operation of EC (handling of buttons, keyboard, etc.). */ #define SIO_CMD_EC_WRITE_BLOCK 0x02 #define SIO_CMD_EC_READ_BLOCK 0x03 #define SIO_CMD_EC_ERASE_KBYTE 0x05 #define SIO_CMD_EC_WRITE_1ST_KBYTE 0x06 #define EC_ROM_ACCESS_ON_1 0xDE #define EC_ROM_ACCESS_ON_2 0xDC #define EC_ROM_ACCESS_OFF 0xFE #define BLOCK_SIZE 0x10000 #define CHUNK_SIZE 0x100 #define CHUNKS_IN_KBYTE 0x4 #define CHUNKS_IN_BLOCK 0x100 #define MAX_FLASHING_ATTEMPTS 5 typedef enum { AUTOLOAD_NO_ACTION, AUTOLOAD_DISABLE, AUTOLOAD_SET_ON, AUTOLOAD_SET_OFF, } AutoloadAction; struct _FuEcIt55Device { FuSuperioDevice parent_instance; gchar *prj_name; AutoloadAction autoload_action; }; G_DEFINE_TYPE(FuEcIt55Device, fu_superio_it55_device, FU_TYPE_SUPERIO_DEVICE) static void fu_superio_it55_device_to_string(FuDevice *device, guint idt, GString *str) { FuEcIt55Device *self = FU_SUPERIO_IT55_DEVICE(device); /* FuSuperioDevice->to_string */ FU_DEVICE_CLASS(fu_superio_it55_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "PrjName", self->prj_name); fu_string_append_kx(str, idt, "AutoloadAction", self->autoload_action); } static gboolean fu_superio_it55_device_ec_project(FuSuperioDevice *device, GError **error) { FuEcIt55Device *self = FU_SUPERIO_IT55_DEVICE(device); gchar project[16] = {0x0}; if (!fu_superio_device_ec_write_cmd(device, SIO_CMD_EC_GET_NAME_STR, error)) return FALSE; for (guint i = 0; i < sizeof(project) - 1; ++i) { guint8 tmp = 0; if (!fu_superio_device_ec_read_data(device, &tmp, error)) { g_prefix_error(error, "failed to read firmware project: "); return FALSE; } if (tmp == '$') break; project[i] = tmp; } self->prj_name = g_strdup(project); /* success */ return TRUE; } static gboolean fu_superio_it55_device_ec_version(FuSuperioDevice *self, GError **error) { gchar version[16] = {'1', '.', '\0'}; if (!fu_superio_device_ec_write_cmd(self, SIO_CMD_EC_GET_VERSION_STR, error)) return FALSE; for (guint i = 2; i < sizeof(version) - 1; i++) { guint8 tmp = 0; if (!fu_superio_device_ec_read_data(self, &tmp, error)) { g_prefix_error(error, "failed to read firmware version: "); return FALSE; } if (tmp == '$') break; version[i] = tmp; } fu_device_set_version(FU_DEVICE(self), version); /* success */ return TRUE; } static gboolean fu_superio_it55_device_ec_size(FuSuperioDevice *self, GError **error) { guint8 tmp = 0; if (!fu_superio_device_reg_read(self, 0xf9, &tmp, error)) return FALSE; switch (tmp & 0xf0) { case 0xf0: fu_device_set_firmware_size(FU_DEVICE(self), BLOCK_SIZE * 4); break; case 0x40: fu_device_set_firmware_size(FU_DEVICE(self), BLOCK_SIZE * 3); break; default: fu_device_set_firmware_size(FU_DEVICE(self), BLOCK_SIZE * 2); break; } return TRUE; } static gboolean fu_superio_it55_device_setup(FuDevice *device, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); /* FuSuperioDevice->setup */ if (!FU_DEVICE_CLASS(fu_superio_it55_device_parent_class)->setup(device, error)) return FALSE; /* basic initialization */ if (!fu_superio_device_reg_write(self, 0xf9, 0x20, error) || !fu_superio_device_reg_write(self, 0xfa, 0x02, error) || !fu_superio_device_reg_write(self, 0xfb, 0x00, error) || !fu_superio_device_reg_write(self, 0xf8, 0xb1, error)) { g_prefix_error(error, "initialization: "); return FALSE; } /* Order of interactions with EC below matters. Additionally, reading EC * project seems to be mandatory for successful firmware operations. * Test after making changes here! */ /* get size from the EC */ if (!fu_superio_it55_device_ec_size(self, error)) return FALSE; /* get installed firmware project from the EC */ if (!fu_superio_it55_device_ec_project(self, error)) return FALSE; /* get installed firmware version from the EC */ if (!fu_superio_it55_device_ec_version(self, error)) return FALSE; /* success */ return TRUE; } static GBytes * fu_plugin_superio_patch_autoload(FuDevice *device, GBytes *fw, GError **error) { FuEcIt55Device *self = FU_SUPERIO_IT55_DEVICE(device); guint offset; gsize sz = 0; const guint8 *unpatched = g_bytes_get_data(fw, &sz); gboolean small_flash = (sz <= BLOCK_SIZE * 2); g_autofree guint8 *patched = NULL; if (self->autoload_action == AUTOLOAD_NO_ACTION) return g_bytes_ref(fw); for (offset = 0; offset < sz - 6; ++offset) { if (unpatched[offset] == 0xa5 && (unpatched[offset + 1] == 0xa5 || unpatched[offset + 1] == 0xa4) && unpatched[offset + 5] == 0x5a) break; } if (offset >= sz - 6) return g_bytes_ref(fw); /* not big enough */ if (offset + 8 >= sz) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "image is too small to patch"); return NULL; } patched = fu_memdup_safe(unpatched, sz, error); if (patched == NULL) return NULL; if (self->autoload_action == AUTOLOAD_DISABLE) { patched[offset + 2] = (small_flash ? 0x94 : 0x85); patched[offset + 8] = 0x00; } else if (self->autoload_action == AUTOLOAD_SET_ON) { patched[offset + 2] = (small_flash ? 0x94 : 0x85); patched[offset + 8] = (small_flash ? 0x7f : 0xbe); } else if (self->autoload_action == AUTOLOAD_SET_OFF) { patched[offset + 2] = (small_flash ? 0xa5 : 0xb5); patched[offset + 8] = 0xaa; } return g_bytes_new_take(g_steal_pointer(&patched), sz); } /* progress callback is optional to not affect device progress during writing * firmware */ static GBytes * fu_superio_it55_device_get_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); guint64 fwsize = fu_device_get_firmware_size_min(device); guint64 block_count = (fwsize + BLOCK_SIZE - 1) / BLOCK_SIZE; goffset offset = 0; g_autofree guint8 *buf = NULL; buf = g_malloc0(fwsize); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, block_count); for (guint i = 0; i < block_count; ++i) { if (!fu_superio_device_ec_write_cmd(self, SIO_CMD_EC_READ_BLOCK, error) || !fu_superio_device_ec_write_cmd(self, i, error)) return NULL; for (guint j = 0; j < BLOCK_SIZE; ++j, ++offset) { if (!fu_superio_device_ec_read_data(self, &buf[offset], error)) return NULL; } fu_progress_step_done(progress); } return g_bytes_new_take(g_steal_pointer(&buf), fwsize); } static GBytes * fu_superio_it55_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; /* require detach -> attach */ locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_device_detach, (FuDeviceLockerFunc)fu_device_attach, error); if (locker == NULL) return NULL; fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); return fu_superio_it55_device_get_firmware(device, progress, error); } static gboolean fu_superio_it55_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; /* leave ROM access mode */ if (!fu_superio_device_ec_write_cmd(self, EC_ROM_ACCESS_OFF, error)) return FALSE; /* success */ fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_superio_it55_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; /* enter ROM access mode */ if (!fu_superio_device_ec_write_cmd(self, EC_ROM_ACCESS_ON_1, error) || !fu_superio_device_ec_write_cmd(self, EC_ROM_ACCESS_ON_2, error)) return FALSE; /* success */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_superio_it55_device_erase(FuDevice *device, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); guint64 fwsize = fu_device_get_firmware_size_min(device); guint64 chunk_count = (fwsize + CHUNK_SIZE - 1) / CHUNK_SIZE; for (guint i = 0; i < chunk_count; i += CHUNKS_IN_KBYTE) { if (!fu_superio_device_ec_write_cmd(self, SIO_CMD_EC_ERASE_KBYTE, error) || !fu_superio_device_ec_write_cmd(self, i / CHUNKS_IN_BLOCK, error) || !fu_superio_device_ec_write_cmd(self, i % CHUNKS_IN_BLOCK, error) || !fu_superio_device_ec_write_cmd(self, 0x00, error)) return FALSE; fu_device_sleep(device, 1); /* ms */ } fu_device_sleep(device, 100); /* ms */ return TRUE; } static gboolean fu_superio_it55_device_write_attempt(FuDevice *device, GBytes *firmware, FuProgress *progress, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); const guint8 *fw_data = NULL; g_autoptr(GBytes) erased_fw = NULL; g_autoptr(GBytes) written_fw = NULL; g_autoptr(FuChunkArray) blocks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "device-write-blk0"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 9, NULL); if (!fu_superio_it55_device_erase(device, error)) return FALSE; erased_fw = fu_superio_it55_device_get_firmware(device, fu_progress_get_child(progress), error); if (erased_fw == NULL) { g_prefix_error(error, "failed to read erased firmware: "); return FALSE; } if (!fu_bytes_is_empty(erased_fw)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "firmware was not erased"); return FALSE; } fu_progress_step_done(progress); /* write everything but the first kilobyte */ blocks = fu_chunk_array_new_from_bytes(firmware, 0x00, BLOCK_SIZE); for (guint i = 0; i < fu_chunk_array_length(blocks); ++i) { g_autoptr(FuChunk) block = fu_chunk_array_index(blocks, i); gboolean first = (i == 0); guint32 offset = 0; guint32 bytes_left = fu_chunk_get_data_sz(block); const guint8 *data = fu_chunk_get_data(block); if (!fu_superio_device_ec_write_cmd(self, SIO_CMD_EC_WRITE_BLOCK, error) || !fu_superio_device_ec_write_cmd(self, 0x00, error) || !fu_superio_device_ec_write_cmd(self, i, error) || !fu_superio_device_ec_write_cmd(self, first ? 0x04 : 0x00, error) || !fu_superio_device_ec_write_cmd(self, 0x00, error)) return FALSE; for (guint j = 0; j < CHUNKS_IN_BLOCK; ++j) { if (first && j < CHUNKS_IN_KBYTE) { offset += CHUNK_SIZE; bytes_left -= CHUNK_SIZE; continue; } for (guint k = 0; k < CHUNK_SIZE; ++k) { if (bytes_left == 0) { if (!fu_superio_device_ec_write_data(self, 0xff, error)) return FALSE; continue; } if (!fu_superio_device_ec_write_data(self, data[offset], error)) return FALSE; ++offset; --bytes_left; } } fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(blocks)); } fu_progress_step_done(progress); /* now write the first kilobyte */ if (!fu_superio_device_ec_write_cmd(self, SIO_CMD_EC_WRITE_1ST_KBYTE, error)) return FALSE; fw_data = g_bytes_get_data(firmware, NULL); for (guint i = 0; i < CHUNK_SIZE * CHUNKS_IN_KBYTE; ++i) if (!fu_superio_device_ec_write_data(self, fw_data[i], error)) return FALSE; fu_progress_step_done(progress); fu_device_sleep(device, 1); /* ms */ written_fw = fu_superio_it55_device_get_firmware(device, fu_progress_get_child(progress), error); if (written_fw == NULL) { g_prefix_error(error, "failed to read firmware: "); return FALSE; } if (!fu_bytes_compare(written_fw, firmware, error)) { g_prefix_error(error, "firmware verification: "); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_superio_it55_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { gsize fwsize; g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) fw_patched = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); /* require detach -> attach */ locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_device_detach, (FuDeviceLockerFunc)fu_device_attach, error); if (locker == NULL) return FALSE; /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; fwsize = g_bytes_get_size(fw); if (fwsize < 1024) { g_prefix_error(error, "firmware is too small %u: ", (guint)fwsize); return FALSE; } fw_patched = fu_plugin_superio_patch_autoload(device, fw, error); if (fw_patched == NULL) return FALSE; fu_progress_step_done(progress); /* try this many times; the failure-to-flash case leaves you without a * keyboard and future boot may completely fail */ for (guint i = 1;; ++i) { g_autoptr(GError) error_chk = NULL; if (fu_superio_it55_device_write_attempt(device, fw_patched, fu_progress_get_child(progress), &error_chk)) break; if (i == MAX_FLASHING_ATTEMPTS) { g_propagate_error(error, g_steal_pointer(&error_chk)); return FALSE; } g_warning("failure %u: %s", i, error_chk->message); } fu_progress_step_done(progress); /* success */ return TRUE; } static gchar * fu_ec_extract_field(GBytes *fw, const gchar *name, GError **error) { guint offset; gsize prefix_len; gsize fwsz = 0; const gchar *value; const guint8 *buf = g_bytes_get_data(fw, &fwsz); g_autofree gchar *field = g_strdup_printf("%s:", name); prefix_len = strlen(field); for (offset = 0; offset < fwsz - prefix_len; ++offset) { if (memcmp(&buf[offset], field, prefix_len) == 0) break; } if (offset >= fwsz - prefix_len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "did not find %s field in the firmware image", name); return NULL; } offset += prefix_len; value = (const gchar *)&buf[offset]; for (; offset < fwsz; ++offset) { if (buf[offset] == '$') return g_strndup(value, (const gchar *)&buf[offset] - value); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "couldn't extract %s field value from the firmware image", name); return NULL; } static FuFirmware * fu_superio_it55_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuEcIt55Device *self = FU_SUPERIO_IT55_DEVICE(device); g_autofree gchar *date = NULL; g_autofree gchar *prj_name = NULL; g_autofree gchar *version = NULL; prj_name = fu_ec_extract_field(fw, "PRJ", error); if (prj_name == NULL) return NULL; version = fu_ec_extract_field(fw, "VER", NULL); if (version == NULL) version = g_strdup("(unknown version)"); date = fu_ec_extract_field(fw, "DATE", NULL); if (date == NULL) date = g_strdup("(unknown build date)"); g_debug("new firmware: %s %s built on %s", prj_name, version, date); if (g_strcmp0(prj_name, self->prj_name) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware targets %s instead of %s", prj_name, self->prj_name); return NULL; } return fu_firmware_new_from_bytes(fw); } static gboolean fu_superio_it55_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuEcIt55Device *self = FU_SUPERIO_IT55_DEVICE(device); /* FuSuperioDevice->set_quirk_kv */ if (!FU_DEVICE_CLASS(fu_superio_it55_device_parent_class) ->set_quirk_kv(device, key, value, error)) return FALSE; if (g_strcmp0(key, "SuperioAutoloadAction") == 0) { if (g_strcmp0(value, "none") == 0) { self->autoload_action = AUTOLOAD_NO_ACTION; } else if (g_strcmp0(value, "disable") == 0) { self->autoload_action = AUTOLOAD_DISABLE; } else if (g_strcmp0(value, "on") == 0) { self->autoload_action = AUTOLOAD_SET_ON; } else if (g_strcmp0(value, "off") == 0) { self->autoload_action = AUTOLOAD_SET_OFF; } else { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid value"); return FALSE; } } /* success */ return TRUE; } static void fu_superio_it55_device_init(FuEcIt55Device *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ONLY_OFFLINE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); /* version string example: 1.07.02TR1 */ fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); } static void fu_superio_it55_device_finalize(GObject *obj) { FuEcIt55Device *self = FU_SUPERIO_IT55_DEVICE(obj); g_free(self->prj_name); G_OBJECT_CLASS(fu_superio_it55_device_parent_class)->finalize(obj); } static void fu_superio_it55_device_class_init(FuEcIt55DeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); G_OBJECT_CLASS(klass)->finalize = fu_superio_it55_device_finalize; klass_device->to_string = fu_superio_it55_device_to_string; klass_device->attach = fu_superio_it55_device_attach; klass_device->detach = fu_superio_it55_device_detach; klass_device->dump_firmware = fu_superio_it55_device_dump_firmware; klass_device->write_firmware = fu_superio_it55_device_write_firmware; klass_device->setup = fu_superio_it55_device_setup; klass_device->prepare_firmware = fu_superio_it55_device_prepare_firmware; klass_device->set_quirk_kv = fu_superio_it55_device_set_quirk_kv; } fwupd-1.9.16/plugins/superio/fu-superio-it55-device.h000066400000000000000000000005221460375044200224000ustar00rootroot00000000000000/* * Copyright (C) 2021, TUXEDO Computers GmbH * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-superio-device.h" #define FU_TYPE_EC_IT55_DEVICE (fu_superio_it55_device_get_type()) G_DECLARE_FINAL_TYPE(FuEcIt55Device, fu_superio_it55_device, FU, SUPERIO_IT55_DEVICE, FuSuperioDevice) fwupd-1.9.16/plugins/superio/fu-superio-it85-device.c000066400000000000000000000041261460375044200224020ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-superio-common.h" #include "fu-superio-it85-device.h" struct _FuSuperioIt85Device { FuSuperioDevice parent_instance; }; G_DEFINE_TYPE(FuSuperioIt85Device, fu_superio_it85_device, FU_TYPE_SUPERIO_DEVICE) static gchar * fu_superio_it85_device_get_str(FuSuperioDevice *self, guint8 idx, GError **error) { g_autoptr(GString) str = g_string_new(NULL); if (!fu_superio_device_ec_write_cmd(self, idx, error)) return NULL; for (guint i = 0; i < 0xff; i++) { guint8 c = 0; if (!fu_superio_device_ec_read_data(self, &c, error)) return NULL; if (c == '$') break; g_string_append_c(str, c); } return g_string_free(g_steal_pointer(&str), FALSE); } static gboolean fu_superio_it85_device_setup(FuDevice *device, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); guint8 size_tmp = 0; g_autofree gchar *name = NULL; g_autofree gchar *version = NULL; /* FuSuperioDevice->setup */ if (!FU_DEVICE_CLASS(fu_superio_it85_device_parent_class)->setup(device, error)) return FALSE; /* get EC size */ if (!fu_superio_device_reg_read(self, 0xe5, &size_tmp, error)) { g_prefix_error(error, "failed to get EC size: "); return FALSE; } fu_device_set_firmware_size(FU_DEVICE(self), ((guint32)size_tmp) << 10); /* get EC strings */ name = fu_superio_it85_device_get_str(self, SIO_CMD_EC_GET_NAME_STR, error); if (name == NULL) { g_prefix_error(error, "failed to get EC name: "); return FALSE; } fu_device_set_name(FU_DEVICE(self), name); version = fu_superio_it85_device_get_str(self, SIO_CMD_EC_GET_VERSION_STR, error); if (version == NULL) { g_prefix_error(error, "failed to get EC version: "); return FALSE; } fu_device_set_version(FU_DEVICE(self), version); return TRUE; } static void fu_superio_it85_device_init(FuSuperioIt85Device *self) { } static void fu_superio_it85_device_class_init(FuSuperioIt85DeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_superio_it85_device_setup; } fwupd-1.9.16/plugins/superio/fu-superio-it85-device.h000066400000000000000000000005521460375044200224060ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-superio-device.h" #define FU_TYPE_SUPERIO_IT85_DEVICE (fu_superio_it85_device_get_type()) G_DECLARE_FINAL_TYPE(FuSuperioIt85Device, fu_superio_it85_device, FU, SUPERIO_IT85_DEVICE, FuSuperioDevice) fwupd-1.9.16/plugins/superio/fu-superio-it89-device.c000066400000000000000000000524661460375044200224200ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-superio-common.h" #include "fu-superio-it89-device.h" struct _FuSuperioIt89Device { FuSuperioDevice parent_instance; }; G_DEFINE_TYPE(FuSuperioIt89Device, fu_superio_it89_device, FU_TYPE_SUPERIO_DEVICE) static gboolean fu_superio_it89_device_read_ec_register(FuSuperioDevice *self, guint16 addr, guint8 *outval, GError **error) { if (!fu_superio_device_io_write(self, SIO_LDNxx_IDX_D2ADR, SIO_DEPTH2_I2EC_ADDRH, error)) return FALSE; if (!fu_superio_device_io_write(self, SIO_LDNxx_IDX_D2DAT, addr >> 8, error)) return FALSE; if (!fu_superio_device_io_write(self, SIO_LDNxx_IDX_D2ADR, SIO_DEPTH2_I2EC_ADDRL, error)) return FALSE; if (!fu_superio_device_io_write(self, SIO_LDNxx_IDX_D2DAT, addr & 0xff, error)) return FALSE; if (!fu_superio_device_io_write(self, SIO_LDNxx_IDX_D2ADR, SIO_DEPTH2_I2EC_DATA, error)) return FALSE; return fu_superio_device_io_read(self, SIO_LDNxx_IDX_D2DAT, outval, error); } static gboolean fu_superio_it89_device_ec_size(FuSuperioDevice *self, GError **error) { guint8 tmp = 0; /* not sure why we can't just use SIO_LDNxx_IDX_CHIPID1, * but lets do the same as the vendor flash tool... */ if (!fu_superio_it89_device_read_ec_register(self, GCTRL_ECHIPID1, &tmp, error)) return FALSE; if (tmp == 0x85) { g_warning("possibly IT85xx class device?!"); fu_device_set_firmware_size(FU_DEVICE(self), 0x20000); return TRUE; } g_debug("ECHIPID1: 0x%02x", (guint)tmp); /* can't we just use SIO_LDNxx_IDX_CHIPVER... */ if (!fu_superio_it89_device_read_ec_register(self, GCTRL_ECHIPVER, &tmp, error)) return FALSE; g_debug("ECHIPVER: 0x%02x", (guint)tmp); if (tmp >> 4 == 0x00) { fu_device_set_firmware_size(FU_DEVICE(self), 0x20000); return TRUE; } if (tmp >> 4 == 0x04) { fu_device_set_firmware_size(FU_DEVICE(self), 0x30000); return TRUE; } if (tmp >> 4 == 0x08) { fu_device_set_firmware_size(FU_DEVICE(self), 0x40000); return TRUE; } g_warning("falling back to default size"); fu_device_set_firmware_size(FU_DEVICE(self), 0x20000); return TRUE; } static gboolean fu_superio_it89_device_setup(FuDevice *device, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); guint8 version_tmp[2] = {0x00}; g_autofree gchar *version = NULL; /* FuSuperioDevice->setup */ if (!FU_DEVICE_CLASS(fu_superio_it89_device_parent_class)->setup(device, error)) return FALSE; /* try to recover this */ if (g_getenv("FWUPD_SUPERIO_RECOVER") != NULL) { fu_device_set_firmware_size(FU_DEVICE(self), 0x20000); return TRUE; } /* get version */ if (!fu_superio_device_reg_read(self, 0x00, &version_tmp[0], error)) { g_prefix_error(error, "failed to get version major: "); return FALSE; } if (!fu_superio_device_reg_read(self, 0x01, &version_tmp[1], error)) { g_prefix_error(error, "failed to get version minor: "); return FALSE; } version = g_strdup_printf("%02u.%02u", version_tmp[0], version_tmp[1]); fu_device_set_version(FU_DEVICE(self), version); /* get size from the EC */ if (!fu_superio_it89_device_ec_size(self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_superio_it89_device_ec_pm1do_sci(FuSuperioDevice *self, guint8 val, GError **error) { if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DOSCI, error)) return FALSE; if (!fu_superio_device_ec_write_cmd(self, val, error)) return FALSE; return TRUE; } static gboolean fu_superio_it89_device_ec_pm1do_smi(FuSuperioDevice *self, guint8 val, GError **error) { if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DOCMI, error)) return FALSE; if (!fu_superio_device_ec_write_cmd(self, val, error)) return FALSE; return TRUE; } static gboolean fu_superio_device_ec_read_status(FuSuperioDevice *self, GError **error) { guint8 tmp = 0x00; /* read status register */ if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci(self, SIO_SPI_CMD_RDSR, error)) return FALSE; /* wait for write */ do { if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DI, error)) return FALSE; if (!fu_superio_device_ec_read_data(self, &tmp, error)) return FALSE; } while ((tmp & SIO_STATUS_EC_OBF) != 0); /* watch SCI events */ return fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DISCI, error); } static gboolean fu_superio_device_ec_write_disable(FuSuperioDevice *self, GError **error) { guint8 tmp = 0x00; /* read existing status */ if (!fu_superio_device_ec_read_status(self, error)) return FALSE; /* write disable */ if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci(self, SIO_SPI_CMD_WRDI, error)) return FALSE; /* read status register */ if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci(self, SIO_SPI_CMD_RDSR, error)) return FALSE; /* wait for read */ do { if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DI, error)) return FALSE; if (!fu_superio_device_ec_read_data(self, &tmp, error)) return FALSE; } while ((tmp & SIO_STATUS_EC_IBF) != 0); /* watch SCI events */ return fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DISCI, error); } static gboolean fu_superio_device_ec_write_enable(FuSuperioDevice *self, GError **error) { guint8 tmp = 0x0; /* read existing status */ if (!fu_superio_device_ec_read_status(self, error)) return FALSE; /* write enable */ if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci(self, SIO_SPI_CMD_WREN, error)) return FALSE; /* read status register */ if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci(self, SIO_SPI_CMD_RDSR, error)) return FALSE; /* wait for !BUSY */ do { if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DI, error)) return FALSE; if (!fu_superio_device_ec_read_data(self, &tmp, error)) return FALSE; } while ((tmp & 3) != SIO_STATUS_EC_IBF); /* watch SCI events */ return fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DISCI, error); } static GBytes * fu_superio_it89_device_read_addr(FuSuperioDevice *self, guint32 addr, guint size, FuProgress *progress, GError **error) { g_autofree guint8 *buf = NULL; /* check... */ if (!fu_superio_device_ec_write_disable(self, error)) return NULL; if (!fu_superio_device_ec_read_status(self, error)) return NULL; /* high speed read */ if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DO, error)) return NULL; if (!fu_superio_it89_device_ec_pm1do_sci(self, SIO_SPI_CMD_HS_READ, error)) return NULL; /* set address, MSB, MID, LSB */ if (!fu_superio_it89_device_ec_pm1do_smi(self, addr >> 16, error)) return NULL; if (!fu_superio_it89_device_ec_pm1do_smi(self, addr >> 8, error)) return NULL; if (!fu_superio_it89_device_ec_pm1do_smi(self, addr & 0xff, error)) return NULL; /* padding for HS? */ if (!fu_superio_it89_device_ec_pm1do_smi(self, 0x0, error)) return NULL; /* read out data */ buf = g_malloc0(size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, size); for (guint i = 0; i < size; i++) { if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DI, error)) return NULL; if (!fu_superio_device_ec_read_data(self, &buf[i], error)) return NULL; /* update progress */ fu_progress_step_done(progress); } /* check again... */ if (!fu_superio_device_ec_read_status(self, error)) return NULL; /* success */ return g_bytes_new_take(g_steal_pointer(&buf), size); } static gboolean fu_superio_it89_device_write_addr(FuSuperioDevice *self, guint addr, GBytes *fw, GError **error) { gsize size = 0; const guint8 *buf = g_bytes_get_data(fw, &size); /* sanity check */ if ((addr & 0xff) != 0x00) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "write addr unaligned, got 0x%04x", (guint)addr); return FALSE; } if (size % 2 != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "write length not supported, got 0x%04x", (guint)size); return FALSE; } /* enable writes */ if (!fu_superio_device_ec_write_enable(self, error)) return FALSE; /* write DWORDs */ if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci(self, SIO_SPI_CMD_WRITE_WORD, error)) return FALSE; /* set address, MSB, MID, LSB */ if (!fu_superio_it89_device_ec_pm1do_smi(self, addr >> 16, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_smi(self, addr >> 8, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_smi(self, addr & 0xff, error)) return FALSE; /* write data two bytes at a time */ for (guint i = 0; i < size; i += 2) { if (i > 0) { if (!fu_superio_device_ec_read_status(self, error)) return FALSE; if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci(self, SIO_SPI_CMD_WRITE_WORD, error)) return FALSE; } if (!fu_superio_it89_device_ec_pm1do_smi(self, buf[i + 0], error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_smi(self, buf[i + 1], error)) return FALSE; } /* reset back? */ if (!fu_superio_device_ec_write_disable(self, error)) return FALSE; return fu_superio_device_ec_read_status(self, error); } static gboolean fu_superio_it89_device_erase_addr(FuSuperioDevice *self, guint addr, GError **error) { /* enable writes */ if (!fu_superio_device_ec_write_enable(self, error)) return FALSE; /* sector erase */ if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci(self, SIO_SPI_CMD_4K_SECTOR_ERASE, error)) return FALSE; /* set address, MSB, MID, LSB */ if (!fu_superio_it89_device_ec_pm1do_smi(self, addr >> 16, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_smi(self, addr >> 8, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_smi(self, addr & 0xff, error)) return FALSE; /* watch SCI events */ if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DISCI, error)) return FALSE; return fu_superio_device_ec_read_status(self, error); } /* The 14th byte of the 16 byte signature is always read from the hardware as * 0x00 rather than the specified 0xAA. Fix up the firmware to match the * .ROM file which uses 0x7F as the number of bytes to mirror to e-flash... */ static GBytes * fu_plugin_superio_fix_signature(FuSuperioDevice *self, GBytes *fw, GError **error) { gsize sz = 0; const guint8 *buf = g_bytes_get_data(fw, &sz); g_autofree guint8 *buf2 = NULL; const guint signature_offset = 0x4d; /* IT85, IT89 is 0x8d */ /* not big enough */ if (sz < signature_offset + 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "image too small to fix"); return NULL; } /* not zero */ if (buf[signature_offset] != 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "nonzero signature byte"); return NULL; } /* fix signature to match SMT version */ buf2 = fu_memdup_safe(buf, sz, error); if (buf2 == NULL) return NULL; buf2[signature_offset] = 0x7f; return g_bytes_new_take(g_steal_pointer(&buf2), sz); } static GBytes * fu_superio_it89_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); guint64 fwsize = fu_device_get_firmware_size_min(device); g_autoptr(FuDeviceLocker) locker = NULL; /* require detach -> attach */ locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_device_detach, (FuDeviceLockerFunc)fu_device_attach, error); if (locker == NULL) return NULL; fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); return fu_superio_it89_device_read_addr(self, 0x0, fwsize, progress, error); } static FuFirmware * fu_superio_it89_device_read_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); g_autoptr(GBytes) blob = NULL; g_autoptr(GBytes) fw = NULL; blob = fu_superio_it89_device_dump_firmware(device, progress, error); fw = fu_plugin_superio_fix_signature(self, blob, error); return fu_firmware_new_from_bytes(fw); } static gboolean fu_superio_it89_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); /* re-enable HOSTWA -- use 0xfd for LCFC */ if (!fu_superio_device_ec_write_cmd(self, SIO_CMD_EC_ENABLE_HOST_WA, error)) return FALSE; /* success */ fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_superio_it89_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); guint8 tmp = 0x00; /* turn off HOSTWA bit, keeping HSEMIE and HSEMW high */ if (!fu_superio_device_ec_write_cmd(self, SIO_CMD_EC_DISABLE_HOST_WA, error)) return FALSE; if (!fu_superio_device_ec_read_data(self, &tmp, error)) return FALSE; if (tmp != 0x33) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to clear HOSTWA, got 0x%02x, expected 0x33", tmp); return FALSE; } /* success */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_superio_it89_device_check_eflash(FuSuperioDevice *self, FuProgress *progress, GError **error) { g_autoptr(GBytes) fw = NULL; const guint64 fwsize = fu_device_get_firmware_size_min(FU_DEVICE(self)); const guint sigsz = 16; /* last 16 bytes of eeprom */ fw = fu_superio_it89_device_read_addr(self, fwsize - sigsz, sigsz, progress, error); if (fw == NULL) { g_prefix_error(error, "failed to read signature bytes: "); return FALSE; } /* cannot flash here without keyboard programmer */ if (!fu_bytes_is_empty(fw)) { gsize sz = 0; const guint8 *buf = g_bytes_get_data(fw, &sz); g_autoptr(GString) str = g_string_new(NULL); for (guint i = 0; i < sz; i++) g_string_append_printf(str, "0x%02x ", buf[i]); if (str->len > 0) g_string_truncate(str, str->len - 1); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "e-flash has been protected: %s", str->str); return FALSE; } /* success */ return TRUE; } static gboolean fu_superio_it89_device_write_chunk(FuSuperioDevice *self, FuChunk *chk, FuProgress *progress, GError **error) { g_autoptr(GBytes) fw1 = NULL; g_autoptr(GBytes) fw2 = NULL; g_autoptr(GBytes) fw3 = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, "page-erase"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 21, "check-erase"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 59, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 20, NULL); /* erase page */ if (!fu_superio_it89_device_erase_addr(self, fu_chunk_get_address(chk), error)) { g_prefix_error(error, "failed to erase @0x%04x: ", (guint)fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); /* check erased */ fw1 = fu_superio_it89_device_read_addr(self, fu_chunk_get_address(chk), fu_chunk_get_data_sz(chk), fu_progress_get_child(progress), error); if (fw1 == NULL) { g_prefix_error(error, "failed to read erased bytes @0x%04x: ", (guint)fu_chunk_get_address(chk)); return FALSE; } if (!fu_bytes_is_empty(fw1)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "sector was not erased"); return FALSE; } fu_progress_step_done(progress); /* skip empty page */ fw2 = g_bytes_new_static(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); if (fu_bytes_is_empty(fw2)) { fu_progress_finished(progress); return TRUE; } /* write page */ if (!fu_superio_it89_device_write_addr(self, fu_chunk_get_address(chk), fw2, error)) { g_prefix_error(error, "failed to write @0x%04x: ", (guint)fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); /* verify page */ fw3 = fu_superio_it89_device_read_addr(self, fu_chunk_get_address(chk), fu_chunk_get_data_sz(chk), fu_progress_get_child(progress), error); if (fw3 == NULL) { g_prefix_error(error, "failed to read written " "bytes @0x%04x: ", (guint)fu_chunk_get_address(chk)); return FALSE; } if (!fu_bytes_compare(fw2, fw3, error)) { g_prefix_error(error, "failed to verify @0x%04x: ", (guint)fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_superio_it89_device_get_jedec_id(FuSuperioDevice *self, guint8 *id, GError **error) { /* read status register */ if (!fu_superio_device_ec_read_status(self, error)) return FALSE; if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DO, error)) return FALSE; if (!fu_superio_it89_device_ec_pm1do_sci(self, SIO_SPI_CMD_JEDEC_ID, error)) return FALSE; /* wait for reads */ for (guint i = 0; i < 4; i++) { if (!fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DI, error)) return FALSE; if (!fu_superio_device_ec_read_data(self, &id[i], error)) return FALSE; } /* watch SCI events */ return fu_superio_device_ec_write_cmd(self, SIO_EC_PMC_PM1DISCI, error); } static gboolean fu_superio_it89_device_write_chunks(FuSuperioDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks) - 1); for (guint i = 0; i < fu_chunk_array_length(chunks) - 1; i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); /* try this many times; the failure-to-flash case leaves you * without a keyboard and future boot may completely fail */ for (guint j = 0;; j++) { g_autoptr(GError) error_chk = NULL; if (fu_superio_it89_device_write_chunk(self, chk, fu_progress_get_child(progress), &error_chk)) break; if (j > 5) { g_propagate_error(error, g_steal_pointer(&error_chk)); return FALSE; } g_warning("failure %u: %s", j, error_chk->message); fu_progress_reset(fu_progress_get_child(progress)); } /* set progress */ fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_superio_it89_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuSuperioDevice *self = FU_SUPERIO_DEVICE(device); guint8 id[4] = {0x0}; g_autoptr(GBytes) fw_fixed = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "check-eflash"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 95, NULL); /* check JEDEC ID */ if (!fu_superio_it89_device_get_jedec_id(self, id, error)) { g_prefix_error(error, "failed to get JEDEC ID: "); return FALSE; } if (id[0] != 0xff || id[1] != 0xff || id[2] != 0xfe || id[3] != 0xff) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "JEDEC ID not valid, 0x%02x%02x%02x%02x", id[0], id[1], id[2], id[3]); return FALSE; } /* check eflash is writable */ if (!fu_superio_it89_device_check_eflash(self, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* disable the mirroring of e-flash */ if (g_getenv("FWUPD_SUPERIO_DISABLE_MIRROR") != NULL) { fw_fixed = fu_plugin_superio_fix_signature(self, fw, error); if (fw_fixed == NULL) return FALSE; } else { fw_fixed = g_bytes_ref(fw); } /* chunks of 1kB, skipping the final chunk */ chunks = fu_chunk_array_new_from_bytes(fw_fixed, 0x00, 0x400); if (!fu_superio_it89_device_write_chunks(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_superio_it89_device_init(FuSuperioIt89Device *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ONLY_OFFLINE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); } static void fu_superio_it89_device_class_init(FuSuperioIt89DeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->attach = fu_superio_it89_device_attach; klass_device->detach = fu_superio_it89_device_detach; klass_device->read_firmware = fu_superio_it89_device_read_firmware; klass_device->dump_firmware = fu_superio_it89_device_dump_firmware; klass_device->write_firmware = fu_superio_it89_device_write_firmware; klass_device->setup = fu_superio_it89_device_setup; } fwupd-1.9.16/plugins/superio/fu-superio-it89-device.h000066400000000000000000000005521460375044200224120ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-superio-device.h" #define FU_TYPE_SUPERIO_IT89_DEVICE (fu_superio_it89_device_get_type()) G_DECLARE_FINAL_TYPE(FuSuperioIt89Device, fu_superio_it89_device, FU, SUPERIO_IT89_DEVICE, FuSuperioDevice) fwupd-1.9.16/plugins/superio/fu-superio-plugin.c000066400000000000000000000072731460375044200216600ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2021, TUXEDO Computers GmbH * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-superio-it55-device.h" #include "fu-superio-it85-device.h" #include "fu-superio-it89-device.h" #include "fu-superio-plugin.h" struct _FuSuperioPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuSuperioPlugin, fu_superio_plugin, FU_TYPE_PLUGIN) #define FU_QUIRKS_SUPERIO_GTYPE "SuperioGType" static gboolean fu_superio_plugin_coldplug_chipset(FuPlugin *plugin, const gchar *guid, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); const gchar *dmi_vendor; const gchar *chipset; GType custom_gtype; g_autoptr(FuSuperioDevice) dev = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* get chipset */ chipset = fu_context_lookup_quirk_by_id(ctx, guid, FU_QUIRKS_SUPERIO_GTYPE); if (chipset == NULL) return TRUE; /* create IT89xx, IT89xx or IT5570 */ custom_gtype = g_type_from_name(chipset); if (custom_gtype == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "SuperIO GType %s unsupported", chipset); return FALSE; } dev = g_object_new(custom_gtype, "device-file", "/dev/port", "chipset", chipset, "context", ctx, NULL); /* add this so we can attach all the other quirks */ fu_device_add_instance_str(FU_DEVICE(dev), "GUID", guid); if (!fu_device_build_instance_id(FU_DEVICE(dev), error, "SUPERIO", "GUID", NULL)) return FALSE; /* set ID and ports via quirks */ if (!fu_device_probe(FU_DEVICE(dev), error)) return FALSE; /* set vendor ID as the motherboard vendor */ dmi_vendor = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_BASEBOARD_MANUFACTURER); if (dmi_vendor != NULL) { g_autofree gchar *vendor_id = g_strdup_printf("DMI:%s", dmi_vendor); fu_device_add_vendor_id(FU_DEVICE(dev), vendor_id); } /* unlock */ locker = fu_device_locker_new(dev, error); if (locker == NULL) return FALSE; fu_plugin_device_add(plugin, FU_DEVICE(dev)); return TRUE; } static gboolean fu_superio_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); GPtrArray *hwids; if (fu_kernel_locked_down()) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported when kernel locked down"); return FALSE; } hwids = fu_context_get_hwid_guids(ctx); for (guint i = 0; i < hwids->len; i++) { const gchar *guid = g_ptr_array_index(hwids, i); if (!fu_superio_plugin_coldplug_chipset(plugin, guid, error)) return FALSE; } return TRUE; } static void fu_superio_plugin_init(FuSuperioPlugin *self) { } static void fu_superio_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "SuperioGType"); fu_context_add_quirk_key(ctx, "SuperioId"); fu_context_add_quirk_key(ctx, "SuperioPort"); fu_context_add_quirk_key(ctx, "SuperioControlPort"); fu_context_add_quirk_key(ctx, "SuperioDataPort"); fu_context_add_quirk_key(ctx, "SuperioTimeout"); fu_context_add_quirk_key(ctx, "SuperioAutoloadAction"); fu_plugin_add_device_gtype(plugin, FU_TYPE_EC_IT55_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_SUPERIO_IT85_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_SUPERIO_IT89_DEVICE); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "linux_lockdown"); } static void fu_superio_plugin_class_init(FuSuperioPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_superio_plugin_constructed; plugin_class->coldplug = fu_superio_plugin_coldplug; } fwupd-1.9.16/plugins/superio/fu-superio-plugin.h000066400000000000000000000003531460375044200216550ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuSuperioPlugin, fu_superio_plugin, FU, SUPERIO_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/superio/meson.build000066400000000000000000000010251460375044200202510ustar00rootroot00000000000000if gudev.found() cargs = ['-DG_LOG_DOMAIN="FuPluginSuperio"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('superio.quirk') plugin_builtins += static_library('fu_plugin_superio', sources: [ 'fu-superio-plugin.c', 'fu-superio-device.c', 'fu-superio-it55-device.c', 'fu-superio-it85-device.c', 'fu-superio-it89-device.c', 'fu-superio-common.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/superio/superio.quirk000066400000000000000000000057221460375044200206620ustar00rootroot00000000000000# N13xWU [992f1bc7-f8ee-567a-88dd-30e5158d72ed] SuperioGType = FuSuperioIt85Device [SUPERIO\GUID_992f1bc7-f8ee-567a-88dd-30e5158d72ed] SuperioId = 0x8587 SuperioPort = 0x2e # W740SU [f00d8c4e-dce2-51c3-89d6-6cbc5fc5cdbb] SuperioGType = FuSuperioIt85Device [SUPERIO\GUID_f00d8c4e-dce2-51c3-89d6-6cbc5fc5cdbb] SuperioId = 0x8587 SuperioPort = 0x2e # Star LabTop Mk III (HwId - AMI) [013b60e5-1023-5bee-8ae5-14cae21377b7] SuperioGType = FuSuperioIt89Device [SUPERIO\GUID_013b60e5-1023-5bee-8ae5-14cae21377b7] SuperioId = 0x8987 SuperioPort = 0x4e InstallDuration = 20 Flags = unsigned-payload # Star LabTop MK III (HwId - coreboot) [8f8ca82b-30e1-5907-bc9d-4257a49898d4] SuperioGType = FuSuperioIt89Device [SUPERIO\GUID_8f8ca82b-30e1-5907-bc9d-4257a49898d4] SuperioId = 0x8987 SuperioPort = 0x4e InstallDuration = 20 Flags = unsigned-payload # Star LabTop Mk IV (HwId) [baf1d04e-fd16-5e6a-93cc-1c23d171f879] SuperioGType = FuSuperioIt89Device [SUPERIO\GUID_baf1d04e-fd16-5e6a-93cc-1c23d171f879] SuperioId = 0x8987 SuperioPort = 0x4e InstallDuration = 20 # StarBook Mk V (HwId) [85aba599-addd-5985-a2e8-eddb41c61ba3] SuperioGType = FuSuperioIt89Device [SUPERIO\GUID_85aba599-addd-5985-a2e8-eddb41c61ba3] SuperioId = 0x5570 SuperioPort = 0x4e InstallDuration = 20 Flags = signed-payload # StarBook Mk VI - Intel (HwId) [5c917039-d938-5c9a-b22a-9c392b1534f3] SuperioGType = FuSuperioIt89Device [SUPERIO\GUID_5c917039-d938-5c9a-b22a-9c392b1534f3] SuperioId = 0x5570 SuperioPort = 0x4e InstallDuration = 20 Flags = signed-payload # StarBook Mk VI - AMD (HwId) [b39593ad-7522-52a5-a4ab-1b2ca2153956] SuperioGType = FuSuperioIt89Device [SUPERIO\b39593ad-7522-52a5-a4ab-1b2ca2153956] SuperioId = 0x5570 SuperioPort = 0x4e InstallDuration = 20 Flags = signed-payload # Byte Mk I - AMD (HwId) [79a64046-5643-59c4-91d0-e68b33db5829] SuperioGType = FuSuperioIt89Device [SUPERIO\79a64046-5643-59c4-91d0-e68b33db5829] SuperioId = 0x5570 SuperioPort = 0x4e InstallDuration = 20 Flags = signed-payload # Star Lite Mk II (HwId) [013b60e5-1023-5bee-8ae5-14cae21377b7] SuperioGType = FuSuperioIt89Device [SUPERIO\GUID_013b60e5-1023-5bee-8ae5-14cae21377b7] SuperioId = 0x8987 SuperioPort = 0x4e InstallDuration = 20 Flags = unsigned-payload # Star Lite Mk III (HwId) [d5521faa-c50b-5d64-971d-8fd400030c51] SuperioGType = FuSuperioIt89Device [SUPERIO\GUID_d5521faa-c50b-5d64-971d-8fd400030c51] SuperioId = 0x8987 SuperioPort = 0x4e InstallDuration = 20 Flags = signed-payload # TUXEDO InfinityBook S 14 Gen6 (HwId) [9366407e-433e-51e9-bd79-a517e3f31536] SuperioGType = FuEcIt55Device [SUPERIO\GUID_9366407e-433e-51e9-bd79-a517e3f31536] SuperioId = 0x5570 SuperioControlPort = 0x66 SuperioDataPort = 0x62 SuperioAutoloadAction = disable SuperioTimeout = 650 # TUXEDO InfinityBook S 15 Gen6 (HwId) [16cabee1-8d79-5164-9400-346dddbfe2c5] SuperioGType = FuEcIt55Device [SUPERIO\GUID_16cabee1-8d79-5164-9400-346dddbfe2c5] SuperioId = 0x5570 SuperioControlPort = 0x66 SuperioDataPort = 0x62 SuperioAutoloadAction = disable SuperioTimeout = 650 fwupd-1.9.16/plugins/synaptics-cape/000077500000000000000000000000001460375044200173465ustar00rootroot00000000000000fwupd-1.9.16/plugins/synaptics-cape/README.md000066400000000000000000000024251460375044200206300ustar00rootroot00000000000000--- title: Plugin: Synaptics CAPE --- ## Introduction This plugin is used to update Synaptics CAPE based audio devices. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob. This plugin supports the following protocol ID: * `com.synaptics.cape` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_1395&PID_0293` These devices also use custom GUID values, e.g. * `SYNAPTICS_CAPE\CX31993` (only-quirk) ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x1395` ### Plugin-specific flags * use-in-report-interrupt: some devices will support IN_REPORT that allow host communicate with device over interrupt instead of control endpoint, since: 1.7.0 ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.7.0`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Simon Ho: @CNXT-Simon fwupd-1.9.16/plugins/synaptics-cape/fu-synaptics-cape-device.c000066400000000000000000000674671460375044200243260ustar00rootroot00000000000000/* * Copyright (C) 2021 Synaptics Incorporated * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaptics-cape-device.h" #include "fu-synaptics-cape-hid-firmware.h" #include "fu-synaptics-cape-struct.h" /* defines timings */ #define FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_WRITE_TIMEOUT 20 /* ms */ #define FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_READ_TIMEOUT 30 /* ms */ #define FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_RETRY_INTERVAL 10 /* ms */ #define FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_RETRY_TIMEOUT 300 /* ms */ #define FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_INTR_TIMEOUT 5000 /* ms */ #define FU_SYNAPTICS_CAPE_DEVICE_USB_RESET_DELAY_MS 5000 /* ms */ #define FU_SYNAPTICS_CAPE_DEVICE_HDR_WRITE_DELAY_MS 150 /* ms */ /* defines CAPE command constant values and macro */ #define FU_SYNAPTICS_CAPE_DEVICE_GOLEM_REPORT_ID 1 /* HID report id */ #define FU_SYNAPTICS_CAPE_CMD_MAX_DATA_LEN 13 /* number of guint32 */ #define FU_SYNAPTICS_CAPE_CMD_WRITE_DATAL_LEN 8 /* number of guint32 */ #define FU_SYNAPTICS_CAPE_CMD_APP_ID_CTRL 0xb32d2300u /* CAPE command return codes */ #define FU_SYNAPTICS_CAPE_MODULE_RC_GENERIC_FAILURE (-1025) #define FU_SYNAPTICS_CAPE_MODULE_RC_ALREADY_EXISTS (-1026) #define FU_SYNAPTICS_CAPE_MODULE_RC_NULL_APP_POINTER (-1027) #define FU_SYNAPTICS_CAPE_MODULE_RC_NULL_MODULE_POINTER (-1028) #define FU_SYNAPTICS_CAPE_MODULE_RC_NULL_STREAM_POINTER (-1029) #define FU_SYNAPTICS_CAPE_MODULE_RC_NULL_POINTER (-1030) #define FU_SYNAPTICS_CAPE_MODULE_RC_BAD_APP_ID (-1031) #define FU_SYNAPTICS_CAPE_MODULE_RC_MODULE_TYPE_HAS_NO_API (-1034) #define FU_SYNAPTICS_CAPE_MODULE_RC_BAD_MAGIC_NUMBER (-1052) #define FU_SYNAPTICS_CAPE_MODULE_RC_CMD_MODE_UNSUPPORTED (-1056) #define FU_SYNAPTICS_CAPE_ERROR_EAGAIN (-11) #define FU_SYNAPTICS_CAPE_ERROR_SFU_FAIL (-200) #define FU_SYNAPTICS_CAPE_ERROR_SFU_WRITE_FAIL (-201) #define FU_SYNAPTICS_CAPE_ERROR_SFU_READ_FAIL (-202) #define FU_SYNAPTICS_CAPE_ERROR_SFU_CRC_ERROR (-203) #define FU_SYNAPTICS_CAPE_ERROR_SFU_USB_ID_NOT_MATCH (-204) #define FU_SYNAPTICS_CAPE_ERROR_SFU_VERSION_DOWNGRADE (-205) #define FU_SYNAPTICS_CAPE_ERROR_SFU_HEADER_CORRUPTION (-206) #define FU_SYNAPTICS_CAPE_ERROR_SFU_IMAGE_CORRUPTION (-207) #define FU_SYNAPTICS_CAPE_ERROR_SFU_ALREADY_ACTIVE (-208) #define FU_SYNAPTICS_CAPE_ERROR_SFU_NOT_READY (-209) #define FU_SYNAPTICS_CAPE_ERROR_SFU_SIGN_TRANSFER_CORRUPTION (-210) #define FU_SYNAPTICS_CAPE_ERROR_SFU_DIGITAL_SIGNATURE_VERFIICATION_FAILED (-211) #define FU_SYNAPTICS_CAPE_ERROR_SFU_TASK_NOT_RUNING (-212) #define FU_SYNAPTICS_CMD_GET_FLAG 0x100 /* GET flag */ #define FU_SYNAPTICS_CAPE_FM3_HID_INTR_IN_EP 0x83 /* CAPE message structure, Little endian */ typedef struct __attribute__((packed)) { gint16 data_len : 16; /* data length in dwords */ guint16 cmd_id : 15; /* command id */ guint16 reply : 1; /* host want a reply from device, 1 = true */ guint32 module_id; /* module id */ guint32 data[FU_SYNAPTICS_CAPE_CMD_MAX_DATA_LEN]; /* command data */ } FuCapCmd; /* CAPE HID report structure */ typedef struct __attribute__((packed)) { guint16 report_id; /* two bytes of report id, this should be 1 */ FuCapCmd cmd; } FuCapCmdHidReport; /* CAPE commands */ typedef enum { FU_SYNAPTICS_CMD_FW_UPDATE_START = 0xC8, /* notifies firmware update started */ FU_SYNAPTICS_CMD_FW_UPDATE_WRITE = 0xC9, /* updates firmware data */ FU_SYNAPTICS_CMD_FW_UPDATE_END = 0xCA, /* notifies firmware update finished */ FU_SYNAPTICS_CMD_MCU_SOFT_RESET = 0xAF, /* reset device*/ FU_SYNAPTICS_CMD_FW_GET_ACTIVE_PARTITION = 0x1CF, /* gets cur active partition number */ FU_SYNAPTICS_CMD_GET_VERSION = 0x103, /* gets cur firmware version */ } FuCommand; /* CAPE fwupd device structure */ struct _FuSynapticsCapeDevice { FuHidDevice parent_instance; guint32 active_partition; /* active partition, either 1 or 2 */ }; /** * FU_SYNAPTICS_CAPE_DEVICE_FLAG_USE_IN_REPORT_INTERRUPT: * * gets HID REPORT via Interrupt instead of Control endpoint. */ #define FU_SYNAPTICS_CAPE_DEVICE_FLAG_USE_IN_REPORT_INTERRUPT (1 << 0) G_DEFINE_TYPE(FuSynapticsCapeDevice, fu_synaptics_cape_device, FU_TYPE_HID_DEVICE) /* sends SET_REPORT to device */ static gboolean fu_synaptics_cape_device_set_report(FuSynapticsCapeDevice *self, const FuCapCmdHidReport *data, GError **error) { g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE); g_return_val_if_fail(data != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); fu_dump_raw(G_LOG_DOMAIN, "SetReport", (guint8 *)data, sizeof(*data)); return fu_hid_device_set_report(FU_HID_DEVICE(self), FU_SYNAPTICS_CAPE_DEVICE_GOLEM_REPORT_ID, (guint8 *)data, sizeof(*data), FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_WRITE_TIMEOUT, FU_HID_DEVICE_FLAG_NONE, error); } /* gets HID report over control ep */ static gboolean fu_synaptics_cape_device_get_report(FuSynapticsCapeDevice *self, FuCapCmdHidReport *data, GError **error) { g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE); g_return_val_if_fail(data != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fu_hid_device_get_report(FU_HID_DEVICE(self), FU_SYNAPTICS_CAPE_DEVICE_GOLEM_REPORT_ID, (guint8 *)data, sizeof(*data), FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_READ_TIMEOUT, FU_HID_DEVICE_FLAG_NONE, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "GetReport", (guint8 *)data, sizeof(*data)); /* success */ return TRUE; } /* gets HID report over interrupt ep */ static gboolean fu_synaptics_cape_device_get_report_intr(FuSynapticsCapeDevice *self, FuCapCmdHidReport *data, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(FU_HID_DEVICE(self))); gsize actual_len = 0; g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE); g_return_val_if_fail(data != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!g_usb_device_interrupt_transfer(usb_device, FU_SYNAPTICS_CAPE_FM3_HID_INTR_IN_EP, (guint8 *)data, sizeof(*data), &actual_len, FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_INTR_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to get report over interrupt ep: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "GetReport", (guint8 *)data, sizeof(*data)); /* success */ return TRUE; } /* dump CAPE command error if any */ static gboolean fu_synaptics_cape_device_rc_set_error(const FuCapCmd *rsp, GError **error) { g_return_val_if_fail(rsp != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (rsp->data_len >= 0) return TRUE; switch (rsp->data_len) { case FU_SYNAPTICS_CAPE_MODULE_RC_GENERIC_FAILURE: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "generic failure"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_ALREADY_EXISTS: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "already exists"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_NULL_APP_POINTER: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "null app pointer"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_NULL_MODULE_POINTER: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "null module pointer"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_NULL_POINTER: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "null pointer"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_BAD_APP_ID: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "bad app id"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_MODULE_TYPE_HAS_NO_API: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "has no api"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_BAD_MAGIC_NUMBER: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "bad magic number"); break; case FU_SYNAPTICS_CAPE_MODULE_RC_CMD_MODE_UNSUPPORTED: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "mode unsupported"); break; case FU_SYNAPTICS_CAPE_ERROR_EAGAIN: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "query timeout"); break; case FU_SYNAPTICS_CAPE_ERROR_SFU_FAIL: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "command failed"); break; case FU_SYNAPTICS_CAPE_ERROR_SFU_WRITE_FAIL: g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "writing to flash failed"); break; case FU_SYNAPTICS_CAPE_ERROR_SFU_READ_FAIL: g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "reading from flash failed"); break; case FU_SYNAPTICS_CAPE_ERROR_SFU_CRC_ERROR: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware data has been corrupted"); break; case FU_SYNAPTICS_CAPE_ERROR_SFU_USB_ID_NOT_MATCH: g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "vendor and device IDs do not match"); break; case FU_SYNAPTICS_CAPE_ERROR_SFU_VERSION_DOWNGRADE: g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_VERSION_NEWER, "update is older than current version"); break; case FU_SYNAPTICS_CAPE_ERROR_SFU_HEADER_CORRUPTION: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware header data has been corrupted"); break; case FU_SYNAPTICS_CAPE_ERROR_SFU_IMAGE_CORRUPTION: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "firmware payload data has been corrupted"); break; case FU_SYNAPTICS_CAPE_ERROR_SFU_ALREADY_ACTIVE: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to active new firmward"); break; case FU_SYNAPTICS_CAPE_ERROR_SFU_NOT_READY: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "firmware update is not ready"); break; case FU_SYNAPTICS_CAPE_ERROR_SFU_SIGN_TRANSFER_CORRUPTION: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "signature data has been corrupted"); break; case FU_SYNAPTICS_CAPE_ERROR_SFU_DIGITAL_SIGNATURE_VERFIICATION_FAILED: g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "digital signature is invalid"); break; case FU_SYNAPTICS_CAPE_ERROR_SFU_TASK_NOT_RUNING: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "firmware update task is not running"); break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "unknown error %d", rsp->data_len); } /* success */ return FALSE; } /* sends a FuCapCmd structure command to device to get the response in the same structure */ static gboolean fu_synaptics_cape_device_sendcmd_ex(FuSynapticsCapeDevice *self, FuCapCmd *req, guint delay_ms, GError **error) { FuCapCmdHidReport report = {0}; guint elapsed_ms = 0; gboolean is_get = FALSE; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE); g_return_val_if_fail(req != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* first two bytes are report id */ report.report_id = GINT16_TO_LE(FU_SYNAPTICS_CAPE_DEVICE_GOLEM_REPORT_ID); if (!fu_memcpy_safe((guint8 *)&report.cmd, sizeof(report.cmd), 0, /* dst */ (const guint8 *)req, sizeof(*req), 0, /* src */ sizeof(*req), error)) return FALSE; /* sets data length to MAX for any GET commands */ if (FU_SYNAPTICS_CMD_GET_FLAG & report.cmd.cmd_id) { is_get = TRUE; report.cmd.data_len = GINT16_TO_LE(FU_SYNAPTICS_CAPE_CMD_MAX_DATA_LEN); } else { report.cmd.data_len = GINT16_TO_LE(report.cmd.data_len); } report.cmd.cmd_id = GUINT16_TO_LE(report.cmd.cmd_id); report.cmd.module_id = GUINT32_TO_LE(report.cmd.module_id); if (!fu_synaptics_cape_device_set_report(self, &report, error)) { g_prefix_error(error, "failed to send: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), delay_ms); /* waits for the command to complete. There are two approaches to get status from device: * 1. gets IN_REPORT over interrupt endpoint. device won't reply until a command operation * has completed. This works only on devices support interrupt endpoint. * 2. polls GET_REPORT over control endpoint. device will return 'reply==0' before a * command operation has completed. */ if (fu_device_has_private_flag(FU_DEVICE(self), FU_SYNAPTICS_CAPE_DEVICE_FLAG_USE_IN_REPORT_INTERRUPT)) { if (!fu_synaptics_cape_device_get_report_intr(self, &report, &error_local)) { /* ignoring io error for software reset command */ if ((req->cmd_id == FU_SYNAPTICS_CMD_MCU_SOFT_RESET) && (req->module_id == FU_SYNAPTICS_CAPE_CMD_APP_ID_CTRL) && (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE) || g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED))) { g_debug("ignoring: %s", error_local->message); return TRUE; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to get IN_REPORT: "); return FALSE; } } else { for (; elapsed_ms < FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_RETRY_TIMEOUT; elapsed_ms += FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_RETRY_INTERVAL) { if (!fu_synaptics_cape_device_get_report(self, &report, &error_local)) { /* ignoring io error for software reset command */ if ((req->cmd_id == FU_SYNAPTICS_CMD_MCU_SOFT_RESET) && (req->module_id == FU_SYNAPTICS_CAPE_CMD_APP_ID_CTRL) && (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE) || g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED))) { g_debug("ignoring: %s", error_local->message); return TRUE; } g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to get GET_REPORT: "); return FALSE; } if (report.cmd.reply) break; fu_device_sleep(FU_DEVICE(self), FU_SYNAPTICS_CAPE_DEVICE_USB_CMD_RETRY_INTERVAL); } } if (!report.cmd.reply) { if (error != NULL) g_prefix_error(error, "send command time out:: "); else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware don't respond to command"); return FALSE; } /* copies returned data if it is GET command */ if (is_get) { req->data_len = (gint16)fu_memread_uint16((guint8 *)&report.cmd, G_LITTLE_ENDIAN); for (int i = 0; i < FU_SYNAPTICS_CAPE_CMD_MAX_DATA_LEN; i++) req->data[i] = GUINT32_FROM_LE(report.cmd.data[i]); } return fu_synaptics_cape_device_rc_set_error(&report.cmd, error); } /* a simple version of sendcmd_ex without returned data */ static gboolean fu_synaptics_cape_device_sendcmd(FuSynapticsCapeDevice *self, const guint32 module_id, const guint32 cmd_id, const guint32 *data, const guint32 data_len, const guint delay_ms, GError **error) { FuCapCmd cmd = {0}; const guint32 dataszbyte = data_len * sizeof(guint32); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); cmd.cmd_id = GUINT16_TO_LE(cmd_id); cmd.module_id = GUINT32_TO_LE(module_id); if (data_len != 0 && data != NULL) { cmd.data_len = data_len; if (!fu_memcpy_safe((guint8 *)cmd.data, sizeof(cmd.data), 0, /* dst */ (const guint8 *)data, dataszbyte, 0, /* src */ dataszbyte, error)) return FALSE; } return fu_synaptics_cape_device_sendcmd_ex(self, &cmd, delay_ms, error); } static void fu_synaptics_cape_device_to_string(FuDevice *device, guint idt, GString *str) { FuSynapticsCapeDevice *self = FU_SYNAPTICS_CAPE_DEVICE(device); g_return_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self)); fu_string_append_ku(str, idt, "ActivePartition", self->active_partition); } /* resets device */ static gboolean fu_synaptics_cape_device_reset(FuSynapticsCapeDevice *self, GError **error) { g_autoptr(GTimer) timer = g_timer_new(); g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (!fu_synaptics_cape_device_sendcmd(self, FU_SYNAPTICS_CAPE_CMD_APP_ID_CTRL, FU_SYNAPTICS_CMD_MCU_SOFT_RESET, NULL, 0, 0, error)) { g_prefix_error(error, "reset command is not supported: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), FU_SYNAPTICS_CAPE_DEVICE_USB_RESET_DELAY_MS); g_debug("reset took %.2lfms", g_timer_elapsed(timer, NULL) * 1000); /* success */ return TRUE; } /** * fu_synaptics_cape_device_get_active_partition: * @self: a #FuSynapticsCapeDevice * @error: return location for an error * * updates active partition information to FuSynapticsCapeDevice::active_partition * * Returns: returns TRUE if operation is successful, otherwise, return FALSE if * unsuccessful. * **/ static gboolean fu_synaptics_cape_device_setup_active_partition(FuSynapticsCapeDevice *self, GError **error) { FuCapCmd cmd = {0}; g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); cmd.cmd_id = FU_SYNAPTICS_CMD_FW_GET_ACTIVE_PARTITION; cmd.module_id = FU_SYNAPTICS_CAPE_CMD_APP_ID_CTRL; if (!fu_synaptics_cape_device_sendcmd_ex(self, &cmd, 0, error)) return FALSE; self->active_partition = GUINT32_FROM_LE(cmd.data[0]); if (self->active_partition != FU_SYNAPTICS_CAPE_FIRMWARE_PARTITION_1 && self->active_partition != FU_SYNAPTICS_CAPE_FIRMWARE_PARTITION_2) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "partition number out of range, returned partition number is %u", self->active_partition); return FALSE; } /* success */ return TRUE; } /* gets version number from device and saves to FU_DEVICE */ static gboolean fu_synaptics_cape_device_setup_version(FuSynapticsCapeDevice *self, GError **error) { guint32 version_raw; FuCapCmd cmd = {0}; cmd.cmd_id = GUINT16_TO_LE(FU_SYNAPTICS_CMD_GET_VERSION); cmd.module_id = GUINT32_TO_LE(FU_SYNAPTICS_CAPE_CMD_APP_ID_CTRL); cmd.data_len = GUINT16_TO_LE(4); g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* gets version number from device */ if (!fu_synaptics_cape_device_sendcmd_ex(self, &cmd, 0, error)) return FALSE; /* the version number are stored in lowest byte of a sequence of returned data */ version_raw = (GUINT32_FROM_LE(cmd.data[0]) << 24) | ((GUINT32_FROM_LE(cmd.data[1]) & 0xFF) << 16) | ((GUINT32_FROM_LE(cmd.data[2]) & 0xFF) << 8) | (GUINT32_FROM_LE(cmd.data[3]) & 0xFF); fu_device_set_version_u32(FU_DEVICE(self), version_raw); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); /* success */ return TRUE; } static gboolean fu_synaptics_cape_device_setup(FuDevice *device, GError **error) { FuSynapticsCapeDevice *self = FU_SYNAPTICS_CAPE_DEVICE(device); g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_synaptics_cape_device_parent_class)->setup(device, error)) return FALSE; if (!fu_synaptics_cape_device_setup_version(self, error)) { g_prefix_error(error, "failed to get firmware version info: "); return FALSE; } if (!fu_synaptics_cape_device_setup_active_partition(self, error)) { g_prefix_error(error, "failed to get active partition info: "); return FALSE; } /* success */ return TRUE; } static FuFirmware * fu_synaptics_cape_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapticsCapeDevice *self = FU_SYNAPTICS_CAPE_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); g_autoptr(FuFirmware) firmware = fu_synaptics_cape_hid_firmware_new(); gsize offset = 0; g_autoptr(GBytes) new_fw = NULL; /* a "fw" includes two firmware data for each partition, we need to divide a 'fw' into * two equal parts. */ gsize bufsz = g_bytes_get_size(fw); g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), NULL); g_return_val_if_fail(usb_device != NULL, NULL); g_return_val_if_fail(fw != NULL, NULL); g_return_val_if_fail(firmware != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if ((guint32)bufsz % 4 != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "data not aligned to 32 bits"); return NULL; } /* uses second partition if active partition is 1 */ if (self->active_partition == 1) offset = bufsz / 2; new_fw = fu_bytes_new_offset(fw, offset, bufsz / 2, error); if (new_fw == NULL) return NULL; if (!fu_firmware_parse(firmware, new_fw, flags, error)) return NULL; /* verify if correct device */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) == 0) { const guint16 vid = fu_synaptics_cape_firmware_get_vid(FU_SYNAPTICS_CAPE_FIRMWARE(firmware)); const guint16 pid = fu_synaptics_cape_firmware_get_pid(FU_SYNAPTICS_CAPE_FIRMWARE(firmware)); if (vid != 0x0 && pid != 0x0 && (g_usb_device_get_vid(usb_device) != vid || g_usb_device_get_pid(usb_device) != pid)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "USB vendor or product incorrect, " "got: %04X:%04X expected %04X:%04X", vid, pid, g_usb_device_get_vid(usb_device), g_usb_device_get_pid(usb_device)); return NULL; } } /* success */ return g_steal_pointer(&firmware); } /* sends firmware header to device */ static gboolean fu_synaptics_cape_device_write_firmware_header(FuSynapticsCapeDevice *self, GBytes *fw, GError **error) { const guint8 *buf = NULL; gsize bufsz = 0; g_autofree guint32 *buf32 = NULL; g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE); g_return_val_if_fail(fw != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); buf = g_bytes_get_data(fw, &bufsz); /* checks size */ if (bufsz != 20) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware header is not 20 bytes"); return FALSE; } /* 32 bit align */ buf32 = g_new0(guint32, bufsz / sizeof(guint32)); if (!fu_memcpy_safe((guint8 *)buf32, bufsz, 0x0, /* dst */ buf, bufsz, 0x0, /* src */ bufsz, error)) return FALSE; return fu_synaptics_cape_device_sendcmd(self, FU_SYNAPTICS_CAPE_CMD_APP_ID_CTRL, FU_SYNAPTICS_CMD_FW_UPDATE_START, buf32, bufsz / sizeof(guint32), FU_SYNAPTICS_CAPE_DEVICE_HDR_WRITE_DELAY_MS, error); } /* sends firmware image to device */ static gboolean fu_synaptics_cape_device_write_firmware_image(FuSynapticsCapeDevice *self, GBytes *fw, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = NULL; g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE); g_return_val_if_fail(fw != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); chunks = fu_chunk_array_new_from_bytes(fw, 0x00, sizeof(guint32) * FU_SYNAPTICS_CAPE_CMD_WRITE_DATAL_LEN); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); gsize bufsz = fu_chunk_get_data_sz(chk); g_autofree guint32 *buf32 = NULL; /* 32 bit align */ buf32 = g_new0(guint32, bufsz / sizeof(guint32)); if (!fu_memcpy_safe((guint8 *)buf32, bufsz, 0x0, /* dst */ fu_chunk_get_data(chk), bufsz, 0x0, /* src */ bufsz, error)) return FALSE; if (!fu_synaptics_cape_device_sendcmd(self, FU_SYNAPTICS_CAPE_CMD_APP_ID_CTRL, FU_SYNAPTICS_CMD_FW_UPDATE_WRITE, buf32, bufsz / sizeof(guint32), 0, error)) { g_prefix_error(error, "failed send on chk %u: ", i); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } /* performs firmware update */ static gboolean fu_synaptics_cape_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuSynapticsCapeDevice *self = FU_SYNAPTICS_CAPE_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) fw_header = NULL; g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_DEVICE(self), FALSE); g_return_val_if_fail(firmware != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 2, "device-write-hdr"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 69, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 29, NULL); fw_header = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_HEADER, error); if (fw_header == NULL) return FALSE; if (!fu_synaptics_cape_device_write_firmware_header(self, fw_header, error)) { g_prefix_error(error, "update header failed: "); return FALSE; } fu_progress_step_done(progress); /* performs the actual write */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; if (!fu_synaptics_cape_device_write_firmware_image(self, fw, fu_progress_get_child(progress), error)) { g_prefix_error(error, "update image failed: "); return FALSE; } fu_progress_step_done(progress); /* verify the firmware image */ if (!fu_synaptics_cape_device_sendcmd(self, FU_SYNAPTICS_CAPE_CMD_APP_ID_CTRL, FU_SYNAPTICS_CMD_FW_UPDATE_END, NULL, 0, 0, error)) { g_prefix_error(error, "failed to verify firmware: "); return FALSE; } fu_progress_step_done(progress); /* sends software reset to boot into the newly flashed firmware */ if (!fu_synaptics_cape_device_reset(self, error)) return FALSE; fu_progress_step_done(progress); /* success */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_synaptics_cape_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_synaptics_cape_device_init(FuSynapticsCapeDevice *self) { fu_device_add_icon(FU_DEVICE(self), "audio-card"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_set_install_duration(FU_DEVICE(self), 3); /* seconds */ fu_device_add_protocol(FU_DEVICE(self), "com.synaptics.cape"); fu_device_retry_set_delay(FU_DEVICE(self), 100); /* ms */ fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_register_private_flag(FU_DEVICE(self), FU_SYNAPTICS_CAPE_DEVICE_FLAG_USE_IN_REPORT_INTERRUPT, "use-in-report-interrupt"); } static void fu_synaptics_cape_device_class_init(FuSynapticsCapeDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_synaptics_cape_device_to_string; klass_device->setup = fu_synaptics_cape_device_setup; klass_device->write_firmware = fu_synaptics_cape_device_write_firmware; klass_device->prepare_firmware = fu_synaptics_cape_device_prepare_firmware; klass_device->set_progress = fu_synaptics_cape_device_set_progress; } fwupd-1.9.16/plugins/synaptics-cape/fu-synaptics-cape-device.h000066400000000000000000000005651460375044200243150ustar00rootroot00000000000000/* * Copyright (C) 2021 Synaptics Incorporated * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_SYNAPTICS_CAPE_DEVICE (fu_synaptics_cape_device_get_type()) G_DECLARE_FINAL_TYPE(FuSynapticsCapeDevice, fu_synaptics_cape_device, FU, SYNAPTICS_CAPE_DEVICE, FuHidDevice) fwupd-1.9.16/plugins/synaptics-cape/fu-synaptics-cape-firmware.c000066400000000000000000000051741460375044200246660ustar00rootroot00000000000000/* * Copyright (C) 2021 Synaptics Incorporated * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-cape-firmware.h" #include "fu-synaptics-cape-struct.h" typedef struct { guint16 vid; guint16 pid; } FuSynapticsCapeFirmwarePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuSynapticsCapeFirmware, fu_synaptics_cape_firmware, FU_TYPE_FIRMWARE) #define GET_PRIVATE(o) (fu_synaptics_cape_firmware_get_instance_private(o)) guint16 fu_synaptics_cape_firmware_get_vid(FuSynapticsCapeFirmware *self) { FuSynapticsCapeFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_FIRMWARE(self), 0); return priv->vid; } void fu_synaptics_cape_firmware_set_vid(FuSynapticsCapeFirmware *self, guint16 vid) { FuSynapticsCapeFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_SYNAPTICS_CAPE_FIRMWARE(self)); priv->vid = vid; } guint16 fu_synaptics_cape_firmware_get_pid(FuSynapticsCapeFirmware *self) { FuSynapticsCapeFirmwarePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_SYNAPTICS_CAPE_FIRMWARE(self), 0); return priv->pid; } void fu_synaptics_cape_firmware_set_pid(FuSynapticsCapeFirmware *self, guint16 pid) { FuSynapticsCapeFirmwarePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_SYNAPTICS_CAPE_FIRMWARE(self)); priv->pid = pid; } static void fu_synaptics_cape_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuSynapticsCapeFirmware *self = FU_SYNAPTICS_CAPE_FIRMWARE(firmware); FuSynapticsCapeFirmwarePrivate *priv = GET_PRIVATE(self); fu_xmlb_builder_insert_kx(bn, "vid", priv->vid); fu_xmlb_builder_insert_kx(bn, "pid", priv->pid); } static gboolean fu_synaptics_cape_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuSynapticsCapeFirmware *self = FU_SYNAPTICS_CAPE_FIRMWARE(firmware); FuSynapticsCapeFirmwarePrivate *priv = GET_PRIVATE(self); guint64 tmp; /* optional properties */ tmp = xb_node_query_text_as_uint(n, "vid", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) priv->vid = tmp; tmp = xb_node_query_text_as_uint(n, "pid", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) priv->pid = tmp; /* success */ return TRUE; } static void fu_synaptics_cape_firmware_init(FuSynapticsCapeFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); } static void fu_synaptics_cape_firmware_class_init(FuSynapticsCapeFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->export = fu_synaptics_cape_firmware_export; klass_firmware->build = fu_synaptics_cape_firmware_build; } fwupd-1.9.16/plugins/synaptics-cape/fu-synaptics-cape-firmware.h000066400000000000000000000014021460375044200246610ustar00rootroot00000000000000/* * Copyright (C) 2021 Synaptics Incorporated * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_SYNAPTICS_CAPE_FIRMWARE (fu_synaptics_cape_firmware_get_type()) G_DECLARE_DERIVABLE_TYPE(FuSynapticsCapeFirmware, fu_synaptics_cape_firmware, FU, SYNAPTICS_CAPE_FIRMWARE, FuFirmware) struct _FuSynapticsCapeFirmwareClass { FuFirmwareClass parent_class; }; guint16 fu_synaptics_cape_firmware_get_vid(FuSynapticsCapeFirmware *self); void fu_synaptics_cape_firmware_set_vid(FuSynapticsCapeFirmware *self, guint16 vid); guint16 fu_synaptics_cape_firmware_get_pid(FuSynapticsCapeFirmware *self); void fu_synaptics_cape_firmware_set_pid(FuSynapticsCapeFirmware *self, guint16 pid); fwupd-1.9.16/plugins/synaptics-cape/fu-synaptics-cape-hid-firmware.c000066400000000000000000000077111460375044200254270ustar00rootroot00000000000000/* * Copyright (C) 2021 Synaptics Incorporated * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaptics-cape-hid-firmware.h" #include "fu-synaptics-cape-struct.h" struct _FuSynapticsCapeHidFirmware { FuSynapticsCapeFirmware parent_instance; }; G_DEFINE_TYPE(FuSynapticsCapeHidFirmware, fu_synaptics_cape_hid_firmware, FU_TYPE_SYNAPTICS_CAPE_FIRMWARE) static gboolean fu_synaptics_cape_hid_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuSynapticsCapeHidFirmware *self = FU_SYNAPTICS_CAPE_HID_FIRMWARE(firmware); gsize bufsz = g_bytes_get_size(fw); g_autofree gchar *version_str = NULL; g_autoptr(FuFirmware) img_hdr = fu_firmware_new(); g_autoptr(GByteArray) st = NULL; g_autoptr(GBytes) fw_body = NULL; g_autoptr(GBytes) fw_hdr = NULL; /* sanity check */ if ((guint32)bufsz % 4 != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "data not aligned to 32 bits"); return FALSE; } /* unpack */ st = fu_struct_synaptics_cape_hid_hdr_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; fu_synaptics_cape_firmware_set_vid(FU_SYNAPTICS_CAPE_FIRMWARE(self), fu_struct_synaptics_cape_hid_hdr_get_vid(st)); fu_synaptics_cape_firmware_set_pid(FU_SYNAPTICS_CAPE_FIRMWARE(self), fu_struct_synaptics_cape_hid_hdr_get_pid(st)); version_str = g_strdup_printf("%u.%u.%u.%u", fu_struct_synaptics_cape_hid_hdr_get_ver_z(st), fu_struct_synaptics_cape_hid_hdr_get_ver_y(st), fu_struct_synaptics_cape_hid_hdr_get_ver_x(st), fu_struct_synaptics_cape_hid_hdr_get_ver_w(st)); fu_firmware_set_version(FU_FIRMWARE(self), version_str); /* top-most part of header */ fw_hdr = fu_bytes_new_offset(fw, 0, FU_STRUCT_SYNAPTICS_CAPE_HID_HDR_OFFSET_VER_W, error); if (fw_hdr == NULL) return FALSE; fu_firmware_set_id(img_hdr, FU_FIRMWARE_ID_HEADER); fu_firmware_set_bytes(img_hdr, fw_hdr); fu_firmware_add_image(firmware, img_hdr); /* body */ fw_body = fu_bytes_new_offset(fw, st->len, bufsz - st->len, error); if (fw_body == NULL) return FALSE; fu_firmware_set_id(firmware, FU_FIRMWARE_ID_PAYLOAD); fu_firmware_set_bytes(firmware, fw_body); return TRUE; } static GByteArray * fu_synaptics_cape_hid_firmware_write(FuFirmware *firmware, GError **error) { FuSynapticsCapeHidFirmware *self = FU_SYNAPTICS_CAPE_HID_FIRMWARE(firmware); guint64 ver = fu_firmware_get_version_raw(firmware); g_autoptr(GByteArray) buf = fu_struct_synaptics_cape_hid_hdr_new(); g_autoptr(GBytes) payload = NULL; /* pack */ fu_struct_synaptics_cape_hid_hdr_set_vid( buf, fu_synaptics_cape_firmware_get_vid(FU_SYNAPTICS_CAPE_FIRMWARE(self))); fu_struct_synaptics_cape_hid_hdr_set_pid( buf, fu_synaptics_cape_firmware_get_pid(FU_SYNAPTICS_CAPE_FIRMWARE(self))); fu_struct_synaptics_cape_hid_hdr_set_crc(buf, 0xFFFF); fu_struct_synaptics_cape_hid_hdr_set_ver_w(buf, ver >> 0); fu_struct_synaptics_cape_hid_hdr_set_ver_x(buf, ver >> 16); fu_struct_synaptics_cape_hid_hdr_set_ver_y(buf, ver >> 32); fu_struct_synaptics_cape_hid_hdr_set_ver_z(buf, ver >> 48); /* payload */ payload = fu_firmware_get_bytes_with_patches(firmware, error); if (payload == NULL) return NULL; fu_byte_array_append_bytes(buf, payload); fu_byte_array_align_up(buf, FU_FIRMWARE_ALIGNMENT_4, 0xFF); return g_steal_pointer(&buf); } static void fu_synaptics_cape_hid_firmware_init(FuSynapticsCapeHidFirmware *self) { } static void fu_synaptics_cape_hid_firmware_class_init(FuSynapticsCapeHidFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_synaptics_cape_hid_firmware_parse; klass_firmware->write = fu_synaptics_cape_hid_firmware_write; } FuFirmware * fu_synaptics_cape_hid_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_SYNAPTICS_CAPE_HID_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/synaptics-cape/fu-synaptics-cape-hid-firmware.h000066400000000000000000000010001460375044200254150ustar00rootroot00000000000000/* * Copyright (C) 2021 Synaptics Incorporated * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-synaptics-cape-firmware.h" #define FU_TYPE_SYNAPTICS_CAPE_HID_FIRMWARE (fu_synaptics_cape_hid_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuSynapticsCapeHidFirmware, fu_synaptics_cape_hid_firmware, FU, SYNAPTICS_CAPE_HID_FIRMWARE, FuSynapticsCapeFirmware) FuFirmware * fu_synaptics_cape_hid_firmware_new(void); fwupd-1.9.16/plugins/synaptics-cape/fu-synaptics-cape-plugin.c000066400000000000000000000020571460375044200243450ustar00rootroot00000000000000/* * Copyright (C) 2021 Synaptics Incorporated * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-cape-device.h" #include "fu-synaptics-cape-hid-firmware.h" #include "fu-synaptics-cape-plugin.h" #include "fu-synaptics-cape-sngl-firmware.h" struct _FuSynapticsCapePlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuSynapticsCapePlugin, fu_synaptics_cape_plugin, FU_TYPE_PLUGIN) static void fu_synaptics_cape_plugin_init(FuSynapticsCapePlugin *self) { } static void fu_synaptics_cape_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_SYNAPTICS_CAPE_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_SYNAPTICS_CAPE_HID_FIRMWARE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_SYNAPTICS_CAPE_SNGL_FIRMWARE); } static void fu_synaptics_cape_plugin_class_init(FuSynapticsCapePluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_synaptics_cape_plugin_constructed; } fwupd-1.9.16/plugins/synaptics-cape/fu-synaptics-cape-plugin.h000066400000000000000000000004331460375044200243460ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuSynapticsCapePlugin, fu_synaptics_cape_plugin, FU, SYNAPTICS_CAPE_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/synaptics-cape/fu-synaptics-cape-sngl-firmware.c000066400000000000000000000066611460375044200256310ustar00rootroot00000000000000/* * Copyright (C) 2023 Synaptics Incorporated * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaptics-cape-sngl-firmware.h" #include "fu-synaptics-cape-struct.h" struct _FuSynapticsCapeSnglFirmware { FuSynapticsCapeFirmware parent_instance; }; G_DEFINE_TYPE(FuSynapticsCapeSnglFirmware, fu_synaptics_cape_sngl_firmware, FU_TYPE_SYNAPTICS_CAPE_FIRMWARE) static gboolean fu_synaptics_cape_sngl_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuSynapticsCapeSnglFirmware *self = FU_SYNAPTICS_CAPE_SNGL_FIRMWARE(firmware); gsize bufsz = 0; guint16 num_fw_file; guint32 crc_calc; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GByteArray) st = NULL; g_autofree gchar *version_str = NULL; /* sanity check */ if ((guint32)bufsz % 4 != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "data not aligned to 32 bits"); return FALSE; } /* unpack */ st = fu_struct_synaptics_cape_sngl_hdr_parse_bytes(fw, offset, error); if (st == NULL) return FALSE; if (fu_struct_synaptics_cape_sngl_hdr_get_file_size(st) != bufsz) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "file size is incorrect"); return FALSE; } crc_calc = fu_crc32(buf + 8, bufsz - 8); if (crc_calc != fu_struct_synaptics_cape_sngl_hdr_get_file_crc(st)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "CRC did not match, got 0x%x, expected 0x%x", fu_struct_synaptics_cape_sngl_hdr_get_file_crc(st), crc_calc); return FALSE; } fu_synaptics_cape_firmware_set_vid(FU_SYNAPTICS_CAPE_FIRMWARE(self), fu_struct_synaptics_cape_sngl_hdr_get_vid(st)); fu_synaptics_cape_firmware_set_pid(FU_SYNAPTICS_CAPE_FIRMWARE(self), fu_struct_synaptics_cape_sngl_hdr_get_pid(st)); version_str = fu_version_from_uint32(fu_struct_synaptics_cape_sngl_hdr_get_fw_version(st), FWUPD_VERSION_FORMAT_QUAD); fu_firmware_set_version(FU_FIRMWARE(self), version_str); /* add each file */ num_fw_file = fu_struct_synaptics_cape_sngl_hdr_get_fw_file_num(st); if (num_fw_file == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no image files found"); return FALSE; } /* success */ return TRUE; } static GByteArray * fu_synaptics_cape_sngl_firmware_write(FuFirmware *firmware, GError **error) { FuSynapticsCapeSnglFirmware *self = FU_SYNAPTICS_CAPE_SNGL_FIRMWARE(firmware); g_autoptr(GByteArray) buf = fu_struct_synaptics_cape_sngl_hdr_new(); /* pack */ fu_struct_synaptics_cape_sngl_hdr_set_vid( buf, fu_synaptics_cape_firmware_get_vid(FU_SYNAPTICS_CAPE_FIRMWARE(self))); fu_struct_synaptics_cape_sngl_hdr_set_pid( buf, fu_synaptics_cape_firmware_get_pid(FU_SYNAPTICS_CAPE_FIRMWARE(self))); /* success */ return g_steal_pointer(&buf); } static void fu_synaptics_cape_sngl_firmware_init(FuSynapticsCapeSnglFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); } static void fu_synaptics_cape_sngl_firmware_class_init(FuSynapticsCapeSnglFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_synaptics_cape_sngl_firmware_parse; klass_firmware->write = fu_synaptics_cape_sngl_firmware_write; } fwupd-1.9.16/plugins/synaptics-cape/fu-synaptics-cape-sngl-firmware.h000066400000000000000000000007151460375044200256300ustar00rootroot00000000000000/* * Copyright (C) 2023 Synaptics Incorporated * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-synaptics-cape-firmware.h" #define FU_TYPE_SYNAPTICS_CAPE_SNGL_FIRMWARE (fu_synaptics_cape_sngl_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuSynapticsCapeSnglFirmware, fu_synaptics_cape_sngl_firmware, FU, SYNAPTICS_CAPE_SNGL_FIRMWARE, FuSynapticsCapeFirmware) fwupd-1.9.16/plugins/synaptics-cape/fu-synaptics-cape.rs000066400000000000000000000024241460375044200232510ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, ParseBytes)] struct SynapticsCapeHidHdr { vid: u32le, pid: u32le, update_type: u32le, signature: u32le == 0x43534645, // "EFSC" crc: u32le, ver_w: u16le, ver_x: u16le, ver_y: u16le, ver_z: u16le, reserved: u32le, } #[derive(New, ParseBytes)] struct SynapticsCapeSnglHdr { magic: u32le == 0x4C474E53, // "SNGL" file_crc: u32le, file_size: u32le, magic2: u32le, img_type: u32le, fw_version: u32le, vid: u16le, pid: u16le, fw_file_num: u32le, version: u32le, fw_crc: u32le, _reserved: [u8; 8], machine_name: [char; 16], time_stamp: [char; 16], } struct SynapticsCapeSnglFile { Id: u32le, Crc: u32le, File: u32le, Size: u32le, } enum SynapticsCapeSnglImgTypeId { Hid0 = 0x30444948, // hid file for partition 0 Hid1 = 0x31444948, // hid file for partition 1 Hof0 = 0x30464F48, // hid + offer file for partition 0 Hof1 = 0x31464F48, // hid + offer file for partition 1 Sfsx = 0x58534653, // sfs file Sofx = 0x58464F53, // sfs + offer file Sign = 0x4e474953, // digital signature file } enum SynapticsCapeFirmwarePartition { Auto, 1, 2, } fwupd-1.9.16/plugins/synaptics-cape/meson.build000066400000000000000000000012241460375044200215070ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsCape"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('synaptics-cape.quirk') plugin_builtins += static_library('fu_plugin_synaptics_cape', rustgen.process( 'fu-synaptics-cape.rs', # fuzzing ), sources: [ 'fu-synaptics-cape-plugin.c', 'fu-synaptics-cape-device.c', 'fu-synaptics-cape-firmware.c', # fuzzing 'fu-synaptics-cape-hid-firmware.c', # fuzzing 'fu-synaptics-cape-sngl-firmware.c', # fuzzing ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/synaptics-cape/synaptics-cape.quirk000066400000000000000000000047741460375044200233620ustar00rootroot00000000000000# Framework Audio Card [USB\VID_32AC&PID_0010] Guid[quirk] = SYNAPTICS_CAPE\CX31993 # EPOS Raw Plus [USB\VID_1395&PID_0280] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0281] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt # RAW Teams [USB\VID_1395&PID_0294] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0295] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0296] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0297] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt # This is the outdated PID for 0x0298 [USB\VID_1395&PID_0286] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = add-counterpart-guids CounterpartGuid = USB\VID_1395&PID_0298 [USB\VID_1395&PID_0298] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0299] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0400] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt # EPOS Morgan-T [USB\VID_1395&PID_0200] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0288] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0289] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_028A] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_028B] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_028C] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_028D] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_028E] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_028F] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt # EPOS Morgan-V [USB\VID_1395&PID_0290] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0291] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0292] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt [USB\VID_1395&PID_0293] Guid[quirk] = SYNAPTICS_CAPE\CX31993 Flags = use-in-report-interrupt # USB audio codec USB receiver [SYNAPTICS_CAPE\CX31993] Plugin = synaptics_cape Icon = usb-receiver # USB audio codec Hifi [SYNAPTICS_CAPE\CX31988] Plugin = synaptics_cape fwupd-1.9.16/plugins/synaptics-cape/tests/000077500000000000000000000000001460375044200205105ustar00rootroot00000000000000fwupd-1.9.16/plugins/synaptics-cape/tests/synaptics-cape.bin000066400000000000000000000001001460375044200241140ustar00rootroot00000000000000Tfwupd-1.9.16/plugins/synaptics-cape/tests/synaptics-cape.builder.xml000066400000000000000000000004021460375044200255760ustar00rootroot00000000000000 has-vid-pid payload 8.41.24.0 EFSCh... header fwupd-1.9.16/plugins/synaptics-cxaudio/000077500000000000000000000000001460375044200200725ustar00rootroot00000000000000fwupd-1.9.16/plugins/synaptics-cxaudio/README.md000066400000000000000000000025471460375044200213610ustar00rootroot00000000000000--- title: Plugin: Synaptics CxAudio — Conexant Audio --- ## Introduction This plugin is used to update a small subset of Conexant (now owned by Synaptics) audio devices. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a modified SREC file format. This plugin supports the following protocol ID: * `com.synaptics.synaptics-cxaudio` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_17EF&PID_3083` These devices also use custom GUID values, e.g. * `SYNAPTICS_CXAUDIO\CX2198X` (only-quirk) ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x17EF` ## Quirk Use This plugin uses the following plugin-specific quirks: ### CxaudioChipIdBase Base integer for ChipID. Since: 1.3.2 ### CxaudioSoftwareReset If the chip supports self-reset. Since: 1.3.2 ### CxaudioPatch1ValidAddr Address of patch location #1. Since: 1.3.2 ### CxaudioPatch2ValidAddr Address of patch location #2. Since: 1.3.2 ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.3.2`. fwupd-1.9.16/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-common.h000066400000000000000000000025331460375044200256130ustar00rootroot00000000000000/* * Copyright (C) 2005 Synaptics Incorporated * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /* usb */ #define FU_SYNAPTICS_CXAUDIO_INPUT_REPORT_SIZE 35 #define FU_SYNAPTICS_CXAUDIO_OUTPUT_REPORT_SIZE 39 #define FU_SYNAPTICS_CXAUDIO_USB_TIMEOUT 2000 /* ms */ /* commands */ #define FU_SYNAPTICS_CXAUDIO_MEM_WRITEID 0x4 #define FU_SYNAPTICS_CXAUDIO_MEM_READID 0x5 /* EEPROM */ #define FU_SYNAPTICS_CXAUDIO_EEPROM_VALIDITY_SIGNATURE_OFFSET 0x0000 #define FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_OFFSET 0x0014 #define FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET 0x0020 #define FU_SYNAPTICS_CXAUDIO_EEPROM_CPX_PATCH_VERSION_ADDRESS 0x0022 #define FU_SYNAPTICS_CXAUDIO_EEPROM_CPX_PATCH2_VERSION_ADDRESS 0x0176 #define FU_SYNAPTICS_CXAUDIO_EEPROM_STORAGE_SIZE_ADDRESS 0x0005 #define FU_SYNAPTICS_CXAUDIO_EEPROM_STORAGE_PADDING_SIZE 0x4 /* bytes */ #define FU_SYNAPTICS_CXAUDIO_DEVICE_CAPABILITIES_STRIDX 50 #define FU_SYNAPTICS_CXAUDIO_SIGNATURE_BYTE 'S' #define FU_SYNAPTICS_CXAUDIO_SIGNATURE_PATCH_BYTE 'P' #define FU_SYNAPTICS_CXAUDIO_REG_FIRMWARE_PARK_ADDR 0x1000 #define FU_SYNAPTICS_CXAUDIO_REG_FIRMWARE_VERSION_ADDR 0x1001 #define FU_SYNAPTICS_CXAUDIO_REG_RESET_ADDR 0x0400 #define FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE (8 * 1024) fwupd-1.9.16/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-device.c000066400000000000000000000703011460375044200255530ustar00rootroot00000000000000/* * Copyright (C) 2005 Synaptics Incorporated * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaptics-cxaudio-common.h" #include "fu-synaptics-cxaudio-device.h" #include "fu-synaptics-cxaudio-firmware.h" #include "fu-synaptics-cxaudio-struct.h" struct _FuSynapticsCxaudioDevice { FuHidDevice parent_instance; guint32 chip_id_base; guint32 chip_id; gboolean serial_number_set; gboolean sw_reset_supported; guint32 eeprom_layout_version; guint32 eeprom_patch2_valid_addr; guint32 eeprom_patch_valid_addr; guint32 eeprom_storage_address; guint32 eeprom_storage_sz; guint32 eeprom_sz; guint8 patch_level; }; G_DEFINE_TYPE(FuSynapticsCxaudioDevice, fu_synaptics_cxaudio_device, FU_TYPE_HID_DEVICE) static void fu_synaptics_cxaudio_device_to_string(FuDevice *device, guint idt, GString *str) { FuSynapticsCxaudioDevice *self = FU_SYNAPTICS_CXAUDIO_DEVICE(device); fu_string_append_ku(str, idt, "ChipIdBase", self->chip_id_base); fu_string_append_ku(str, idt, "ChipId", self->chip_id); fu_string_append_kx(str, idt, "EepromLayoutVersion", self->eeprom_layout_version); fu_string_append_kx(str, idt, "EepromStorageAddress", self->eeprom_storage_address); fu_string_append_kx(str, idt, "EepromStorageSz", self->eeprom_storage_sz); fu_string_append_kx(str, idt, "EepromSz", self->eeprom_sz); fu_string_append_kb(str, idt, "SwResetSupported", self->sw_reset_supported); fu_string_append_kb(str, idt, "SerialNumberSet", self->serial_number_set); } static gboolean fu_synaptics_cxaudio_device_output_report(FuSynapticsCxaudioDevice *self, guint8 *buf, guint16 bufsz, GError **error) { /* weird */ if (buf[0] == 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "report 0 not supported"); return FALSE; } /* to device */ return fu_hid_device_set_report(FU_HID_DEVICE(self), buf[0], buf, bufsz, FU_SYNAPTICS_CXAUDIO_USB_TIMEOUT, FU_HID_DEVICE_FLAG_RETRY_FAILURE, error); } static gboolean fu_synaptics_cxaudio_device_input_report(FuSynapticsCxaudioDevice *self, guint8 ReportID, guint8 *buf, guint16 bufsz, GError **error) { return fu_hid_device_get_report(FU_HID_DEVICE(self), ReportID, buf, bufsz, FU_SYNAPTICS_CXAUDIO_USB_TIMEOUT, FU_HID_DEVICE_FLAG_RETRY_FAILURE, error); } typedef enum { FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_OPERATION_LAST } FuSynapticsCxaudioOperation; typedef enum { FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE = 0, FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_VERIFY = (1 << 4), } FuSynapticsCxaudioOperationFlags; static gboolean fu_synaptics_cxaudio_device_operation(FuSynapticsCxaudioDevice *self, FuSynapticsCxaudioOperation operation, FuSynapticsCxaudioMemKind mem_kind, guint32 addr, guint8 *buf, gsize bufsz, FuSynapticsCxaudioOperationFlags flags, GError **error) { const guint32 idx_read = 0x1; const guint32 idx_write = 0x5; const guint32 payload_max = 0x20; guint32 size = 0x02800; g_autoptr(GPtrArray) chunks = NULL; g_return_val_if_fail(bufsz > 0, FALSE); g_return_val_if_fail(buf != NULL, FALSE); /* check if memory operation is supported by device */ if (operation == FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE && mem_kind == FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_ROM) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "trying to write unwritable section %u", mem_kind); return FALSE; } /* check memory address - should be within valid range */ if (mem_kind == FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM) size = 0x20000; if (addr > size) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "address out of range 0x%x < 0x%x", addr, size); return FALSE; } /* send to hardware */ chunks = fu_chunk_array_mutable_new(buf, bufsz, addr, 0x0, payload_max); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint8 inbuf[FU_SYNAPTICS_CXAUDIO_INPUT_REPORT_SIZE] = {0}; guint8 outbuf[FU_SYNAPTICS_CXAUDIO_OUTPUT_REPORT_SIZE] = {0}; /* first byte is always report ID */ outbuf[0] = FU_SYNAPTICS_CXAUDIO_MEM_WRITEID; /* set memory address and payload length (if relevant) */ if (fu_chunk_get_address(chk) >= 64 * 1024) outbuf[1] |= 1 << 4; outbuf[2] = fu_chunk_get_data_sz(chk); fu_memwrite_uint16(outbuf + 3, fu_chunk_get_address(chk), G_BIG_ENDIAN); /* set memtype */ if (mem_kind == FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM) outbuf[1] |= 1 << 5; /* fill the report payload part */ if (operation == FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE) { outbuf[1] |= 1 << 6; if (!fu_memcpy_safe(outbuf, sizeof(outbuf), idx_write, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; } if (!fu_synaptics_cxaudio_device_output_report(self, outbuf, sizeof(outbuf), error)) return FALSE; /* issue additional write directive to read */ if (operation == FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE && flags & FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_VERIFY) { outbuf[1] &= ~(1 << 6); if (!fu_synaptics_cxaudio_device_output_report(self, outbuf, sizeof(outbuf), error)) return FALSE; } if (operation == FU_SYNAPTICS_CXAUDIO_OPERATION_READ || flags & FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_VERIFY) { if (!fu_synaptics_cxaudio_device_input_report( self, FU_SYNAPTICS_CXAUDIO_MEM_READID, inbuf, sizeof(inbuf), error)) return FALSE; } if (operation == FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE && flags & FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_VERIFY) { if (!fu_memcmp_safe(outbuf, sizeof(outbuf), idx_write, inbuf, sizeof(inbuf), idx_read, payload_max, error)) { g_prefix_error(error, "failed to verify on packet %u @0x%x: ", fu_chunk_get_idx(chk), fu_chunk_get_address(chk)); return FALSE; } } if (operation == FU_SYNAPTICS_CXAUDIO_OPERATION_READ) { if (!fu_memcpy_safe(fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), 0x0, /* dst */ inbuf, sizeof(inbuf), idx_read, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; } } /* success */ return TRUE; } static gboolean fu_synaptics_cxaudio_device_register_clear_bit(FuSynapticsCxaudioDevice *self, guint32 address, guint8 bit_position, GError **error) { guint8 tmp = 0x0; if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, address, &tmp, sizeof(tmp), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) return FALSE; tmp &= ~(1 << bit_position); return fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, address, &tmp, sizeof(guint8), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error); } static gboolean fu_synaptics_cxaudio_device_register_set_bit(FuSynapticsCxaudioDevice *self, guint32 address, guint8 bit_position, GError **error) { guint8 tmp = 0x0; if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, address, &tmp, sizeof(tmp), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) return FALSE; tmp |= 1 << bit_position; return fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, address, &tmp, sizeof(tmp), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error); } static gchar * fu_synaptics_cxaudio_device_eeprom_read_string(FuSynapticsCxaudioDevice *self, guint32 address, GError **error) { guint8 buf[FU_STRUCT_SYNAPTICS_CXAUDIO_STRING_HEADER_SIZE] = {0}; guint8 header_length; g_autofree gchar *str = NULL; g_autoptr(GByteArray) st = NULL; /* read header */ if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, address, buf, sizeof(buf), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read EEPROM string header @0x%x: ", address); return NULL; } /* sanity check */ st = fu_struct_synaptics_cxaudio_string_header_parse(buf, sizeof(buf), 0x0, error); if (st == NULL) return NULL; header_length = fu_struct_synaptics_cxaudio_string_header_get_length(st); if (header_length < st->len) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM string header length invalid"); return NULL; } /* allocate buffer + NUL terminator */ str = g_malloc0(header_length - st->len + 1); if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, address + sizeof(buf), (guint8 *)str, header_length - sizeof(buf), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read EEPROM string @0x%x: ", address); return NULL; } return g_steal_pointer(&str); } static gboolean fu_synaptics_cxaudio_device_ensure_patch_level(FuSynapticsCxaudioDevice *self, GError **error) { guint8 tmp = 0x0; if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, self->eeprom_patch_valid_addr, &tmp, sizeof(tmp), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read EEPROM patch validation byte: "); return FALSE; } if (tmp == FU_SYNAPTICS_CXAUDIO_SIGNATURE_PATCH_BYTE) { self->patch_level = 1; return TRUE; } if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, self->eeprom_patch2_valid_addr, &tmp, sizeof(tmp), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read EEPROM patch validation byte: "); return FALSE; } if (tmp == FU_SYNAPTICS_CXAUDIO_SIGNATURE_PATCH_BYTE) { self->patch_level = 2; return TRUE; } /* not sure what to do here */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM patch version undiscoverable"); return FALSE; } static gboolean fu_synaptics_cxaudio_device_setup(FuDevice *device, GError **error) { FuSynapticsCxaudioDevice *self = FU_SYNAPTICS_CXAUDIO_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); guint32 addr = FU_SYNAPTICS_CXAUDIO_EEPROM_CPX_PATCH_VERSION_ADDRESS; guint8 chip_id_offset = 0x0; guint8 sigbuf[FU_STRUCT_SYNAPTICS_CXAUDIO_VALIDITY_SIGNATURE_SIZE] = {0x0}; guint8 verbuf_fw[4] = {0x0}; guint8 verbuf_patch[3] = {0x0}; g_autofree gchar *cap_str = NULL; g_autofree gchar *chip_id = NULL; g_autofree gchar *summary = NULL; g_autofree gchar *version_fw = NULL; g_autofree gchar *version_patch = NULL; g_autoptr(GByteArray) st_inf = NULL; g_autoptr(GByteArray) st_sig = NULL; guint8 cinfo[FU_STRUCT_SYNAPTICS_CXAUDIO_CUSTOM_INFO_SIZE] = {0x0}; /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_synaptics_cxaudio_device_parent_class)->setup(device, error)) return FALSE; /* get the ChipID */ if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, 0x1005, &chip_id_offset, sizeof(chip_id_offset), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read ChipID: "); return FALSE; } self->chip_id = self->chip_id_base + chip_id_offset; /* add instance ID */ chip_id = g_strdup_printf("CX%u", self->chip_id); fu_device_add_instance_str(device, "ID", chip_id); if (!fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, error, "SYNAPTICS_CXAUDIO", "ID", NULL)) return FALSE; /* set summary */ summary = g_strdup_printf("CX%u USB audio device", self->chip_id); fu_device_set_summary(device, summary); /* read the EEPROM validity signature */ if (!fu_synaptics_cxaudio_device_operation( self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_VALIDITY_SIGNATURE_OFFSET, sigbuf, sizeof(sigbuf), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read EEPROM signature bytes: "); return FALSE; } /* blank EEPROM */ if (sigbuf[0] == 0xff && sigbuf[1] == 0xff) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM is missing or blank"); return FALSE; } /* is disabled on EVK board using jumper */ if ((sigbuf[0] == 0x00 && sigbuf[1] == 0x00) || (sigbuf[0] == 0xff && sigbuf[1] == 0x00)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM has been disabled using a jumper"); return FALSE; } /* check magic byte */ st_sig = fu_struct_synaptics_cxaudio_validity_signature_parse(sigbuf, sizeof(sigbuf), 0x0, error); if (st_sig == NULL) return FALSE; if (fu_struct_synaptics_cxaudio_validity_signature_get_magic_byte(st_sig) != FU_STRUCT_SYNAPTICS_CXAUDIO_VALIDITY_SIGNATURE_DEFAULT_MAGIC_BYTE) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM magic byte invalid, got 0x%02x expected 0x%02x", fu_struct_synaptics_cxaudio_validity_signature_get_magic_byte(st_sig), (guint)FU_STRUCT_SYNAPTICS_CXAUDIO_VALIDITY_SIGNATURE_DEFAULT_MAGIC_BYTE); return FALSE; } /* calculate EEPROM size */ self->eeprom_sz = (guint32)1 << (fu_struct_synaptics_cxaudio_validity_signature_get_eeprom_size_code(st_sig) + 8); if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_STORAGE_SIZE_ADDRESS, sigbuf, sizeof(sigbuf), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read EEPROM signature bytes: "); return FALSE; } self->eeprom_storage_sz = fu_memread_uint16(sigbuf, G_LITTLE_ENDIAN); if (self->eeprom_storage_sz < self->eeprom_sz - FU_SYNAPTICS_CXAUDIO_EEPROM_STORAGE_PADDING_SIZE) { self->eeprom_storage_address = self->eeprom_sz - self->eeprom_storage_sz - FU_SYNAPTICS_CXAUDIO_EEPROM_STORAGE_PADDING_SIZE; } /* get EEPROM custom info */ if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET, cinfo, sizeof(cinfo), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read EEPROM custom info: "); return FALSE; } /* parse */ st_inf = fu_struct_synaptics_cxaudio_custom_info_parse(cinfo, sizeof(cinfo), 0x0, error); if (st_inf == NULL) return FALSE; if (fu_struct_synaptics_cxaudio_custom_info_get_layout_signature(st_inf) == FU_SYNAPTICS_CXAUDIO_SIGNATURE_BYTE) self->eeprom_layout_version = fu_struct_synaptics_cxaudio_custom_info_get_layout_version(st_inf); /* serial number, which also allows us to recover it after write */ if (self->eeprom_layout_version >= 0x01) { guint16 serial_number_string_address = fu_struct_synaptics_cxaudio_custom_info_get_serial_number_string_address( st_inf); self->serial_number_set = serial_number_string_address != 0x0; if (self->serial_number_set) { g_autofree gchar *tmp = NULL; tmp = fu_synaptics_cxaudio_device_eeprom_read_string( self, serial_number_string_address, error); if (tmp == NULL) return FALSE; fu_device_set_serial(device, tmp); } } /* read fw version */ if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, FU_SYNAPTICS_CXAUDIO_REG_FIRMWARE_VERSION_ADDR, verbuf_fw, sizeof(verbuf_fw), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read EEPROM firmware version: "); return FALSE; } version_fw = g_strdup_printf("%02X.%02X.%02X.%02X", verbuf_fw[1], verbuf_fw[0], verbuf_fw[3], verbuf_fw[2]); fu_device_set_version_bootloader(device, version_fw); /* use a different address if a patch is in use */ if (self->eeprom_patch_valid_addr != 0x0) { if (!fu_synaptics_cxaudio_device_ensure_patch_level(self, error)) return FALSE; } if (self->patch_level == 2) addr = FU_SYNAPTICS_CXAUDIO_EEPROM_CPX_PATCH2_VERSION_ADDRESS; if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, addr, verbuf_patch, sizeof(verbuf_patch), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read EEPROM patch version: "); return FALSE; } version_patch = g_strdup_printf("%02X-%02X-%02X", verbuf_patch[0], verbuf_patch[1], verbuf_patch[2]); fu_device_set_version(device, version_patch); /* find out if patch supports additional capabilities (optional) */ cap_str = g_usb_device_get_string_descriptor(usb_device, FU_SYNAPTICS_CXAUDIO_DEVICE_CAPABILITIES_STRIDX, NULL); if (cap_str != NULL) { g_auto(GStrv) split = g_strsplit(cap_str, ";", -1); for (guint i = 0; split[i] != NULL; i++) { g_debug("capability: %s", split[i]); if (g_strcmp0(split[i], "RESET") == 0) self->sw_reset_supported = TRUE; } } /* success */ return TRUE; } static FuFirmware * fu_synaptics_cxaudio_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapticsCxaudioDevice *self = FU_SYNAPTICS_CXAUDIO_DEVICE(device); guint32 chip_id_base; g_autoptr(FuFirmware) firmware = fu_synaptics_cxaudio_firmware_new(); if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; chip_id_base = fu_synaptics_cxaudio_firmware_get_devtype(FU_SYNAPTICS_CXAUDIO_FIRMWARE(firmware)); if (chip_id_base != self->chip_id_base) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "device 0x%04u is incompatible with firmware 0x%04u", self->chip_id_base, chip_id_base); return NULL; } return g_steal_pointer(&firmware); } static gboolean fu_synaptics_cxaudio_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuSynapticsCxaudioDevice *self = FU_SYNAPTICS_CXAUDIO_DEVICE(device); GPtrArray *records = fu_srec_firmware_get_records(FU_SREC_FIRMWARE(firmware)); FuSynapticsCxaudioFileKind file_kind; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 3, "park"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "init"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "invalidate"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "unpark"); /* check if a patch file fits completely into the EEPROM */ for (guint i = 0; i < records->len; i++) { FuSrecFirmwareRecord *rcd = g_ptr_array_index(records, i); if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16) continue; if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_LAST) continue; if (rcd->addr > self->eeprom_sz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "EEPROM address 0x%02x is bigger than size 0x%02x", rcd->addr, self->eeprom_sz); return FALSE; } } /* park the FW: run only the basic functionality until the upgrade is over */ if (!fu_synaptics_cxaudio_device_register_set_bit( self, FU_SYNAPTICS_CXAUDIO_REG_FIRMWARE_PARK_ADDR, 7, error)) return FALSE; fu_device_sleep(device, 10); /* ms */ fu_progress_step_done(progress); /* initialize layout signature and version to 0 if transitioning from * EEPROM layout version 1 => 0 */ file_kind = fu_synaptics_cxaudio_firmware_get_file_type(FU_SYNAPTICS_CXAUDIO_FIRMWARE(firmware)); if (file_kind == FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_FW && self->eeprom_layout_version >= 1 && fu_synaptics_cxaudio_firmware_get_layout_version( FU_SYNAPTICS_CXAUDIO_FIRMWARE(firmware)) == 0) { guint8 value = 0; if (!fu_synaptics_cxaudio_device_operation( self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET + FU_STRUCT_SYNAPTICS_CXAUDIO_CUSTOM_INFO_OFFSET_LAYOUT_SIGNATURE, &value, sizeof(value), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to initialize layout signature: "); return FALSE; } if (!fu_synaptics_cxaudio_device_operation( self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET + FU_STRUCT_SYNAPTICS_CXAUDIO_CUSTOM_INFO_OFFSET_LAYOUT_VERSION, &value, sizeof(value), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to initialize layout signature: "); return FALSE; } } fu_progress_step_done(progress); /* perform the actual write */ for (guint i = 0; i < records->len; i++) { FuSrecFirmwareRecord *rcd = g_ptr_array_index(records, i); if (rcd->kind != FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32) continue; g_debug("writing @0x%04x len:0x%02x", rcd->addr, rcd->buf->len); if (!fu_synaptics_cxaudio_device_operation( self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, rcd->addr, rcd->buf->data, rcd->buf->len, FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_VERIFY, error)) { g_prefix_error(error, "failed to write @0x%04x len:0x%02x: ", rcd->addr, rcd->buf->len); return FALSE; } fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)records->len); } fu_progress_step_done(progress); /* in case of a full FW upgrade invalidate the old FW patch (if any) * as it may have not been done by the S37 file */ if (file_kind == FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_FW) { guint8 buf[FU_STRUCT_SYNAPTICS_CXAUDIO_PATCH_INFO_SIZE] = {0}; g_autoptr(GByteArray) st_pat = NULL; if (!fu_synaptics_cxaudio_device_operation( self, FU_SYNAPTICS_CXAUDIO_OPERATION_READ, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_OFFSET, buf, sizeof(buf), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to read EEPROM patch info: "); return FALSE; } st_pat = fu_struct_synaptics_cxaudio_patch_info_parse(buf, sizeof(buf), 0x0, error); if (st_pat == NULL) return FALSE; if (fu_struct_synaptics_cxaudio_patch_info_get_patch_signature(st_pat) == FU_SYNAPTICS_CXAUDIO_SIGNATURE_PATCH_BYTE) { fu_struct_synaptics_cxaudio_patch_info_set_patch_signature(st_pat, 0x0); fu_struct_synaptics_cxaudio_patch_info_set_patch_address(st_pat, 0x0); if (!fu_synaptics_cxaudio_device_operation( self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_EEPROM, FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_OFFSET, st_pat->data, st_pat->len, FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, error)) { g_prefix_error(error, "failed to write empty EEPROM patch info: "); return FALSE; } g_debug("invalidated old FW patch for CX2070x (RAM) device"); } } fu_progress_step_done(progress); /* unpark the FW */ if (!fu_synaptics_cxaudio_device_register_clear_bit( self, FU_SYNAPTICS_CXAUDIO_REG_FIRMWARE_PARK_ADDR, 7, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_synaptics_cxaudio_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuSynapticsCxaudioDevice *self = FU_SYNAPTICS_CXAUDIO_DEVICE(device); guint8 tmp = 1 << 6; g_autoptr(GError) error_local = NULL; /* is disabled on EVK board using jumper */ if (!self->sw_reset_supported) return TRUE; /* wait for re-enumeration */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* this fails on success */ if (!fu_synaptics_cxaudio_device_operation(self, FU_SYNAPTICS_CXAUDIO_OPERATION_WRITE, FU_SYNAPTICS_CXAUDIO_MEM_KIND_CPX_RAM, FU_SYNAPTICS_CXAUDIO_REG_RESET_ADDR, &tmp, sizeof(tmp), FU_SYNAPTICS_CXAUDIO_OPERATION_FLAG_NONE, &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED)) { return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } return TRUE; } static gboolean fu_synaptics_cxaudio_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuSynapticsCxaudioDevice *self = FU_SYNAPTICS_CXAUDIO_DEVICE(device); guint64 tmp = 0; if (g_strcmp0(key, "CxaudioChipIdBase") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->chip_id_base = tmp; return TRUE; } if (g_strcmp0(key, "CxaudioSoftwareReset") == 0) return fu_strtobool(value, &self->sw_reset_supported, error); if (g_strcmp0(key, "CxaudioPatch1ValidAddr") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->eeprom_patch_valid_addr = tmp; return TRUE; } if (g_strcmp0(key, "CxaudioPatch2ValidAddr") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; self->eeprom_patch2_valid_addr = tmp; return TRUE; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_synaptics_cxaudio_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 3, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 37, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 60, "reload"); } static void fu_synaptics_cxaudio_device_init(FuSynapticsCxaudioDevice *self) { self->sw_reset_supported = TRUE; fu_device_add_icon(FU_DEVICE(self), "audio-card"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_install_duration(FU_DEVICE(self), 3); /* seconds */ fu_device_add_protocol(FU_DEVICE(self), "com.synaptics.cxaudio"); fu_device_retry_set_delay(FU_DEVICE(self), 100); /* ms */ fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } static void fu_synaptics_cxaudio_device_class_init(FuSynapticsCxaudioDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_synaptics_cxaudio_device_to_string; klass_device->set_quirk_kv = fu_synaptics_cxaudio_device_set_quirk_kv; klass_device->setup = fu_synaptics_cxaudio_device_setup; klass_device->write_firmware = fu_synaptics_cxaudio_device_write_firmware; klass_device->attach = fu_synaptics_cxaudio_device_attach; klass_device->prepare_firmware = fu_synaptics_cxaudio_device_prepare_firmware; klass_device->set_progress = fu_synaptics_cxaudio_device_set_progress; } fwupd-1.9.16/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-device.h000066400000000000000000000005711460375044200255620ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_SYNAPTICS_CXAUDIO_DEVICE (fu_synaptics_cxaudio_device_get_type()) G_DECLARE_FINAL_TYPE(FuSynapticsCxaudioDevice, fu_synaptics_cxaudio_device, FU, SYNAPTICS_CXAUDIO_DEVICE, FuHidDevice) fwupd-1.9.16/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-firmware.c000066400000000000000000000301331460375044200261270ustar00rootroot00000000000000/* * Copyright (C) 2005 Synaptics Incorporated * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaptics-cxaudio-common.h" #include "fu-synaptics-cxaudio-firmware.h" #include "fu-synaptics-cxaudio-struct.h" struct _FuSynapticsCxaudioFirmware { FuSrecFirmwareClass parent_instance; FuSynapticsCxaudioFileKind file_kind; FuSynapticsCxaudioDeviceKind device_kind; guint8 layout_signature; guint8 layout_version; guint16 vendor_id; guint16 product_id; guint16 revision_id; }; G_DEFINE_TYPE(FuSynapticsCxaudioFirmware, fu_synaptics_cxaudio_firmware, FU_TYPE_SREC_FIRMWARE) FuSynapticsCxaudioFileKind fu_synaptics_cxaudio_firmware_get_file_type(FuSynapticsCxaudioFirmware *self) { g_return_val_if_fail(FU_IS_SYNAPTICS_CXAUDIO_FIRMWARE(self), 0); return self->file_kind; } FuSynapticsCxaudioDeviceKind fu_synaptics_cxaudio_firmware_get_devtype(FuSynapticsCxaudioFirmware *self) { g_return_val_if_fail(FU_IS_SYNAPTICS_CXAUDIO_FIRMWARE(self), 0); return self->device_kind; } guint8 fu_synaptics_cxaudio_firmware_get_layout_version(FuSynapticsCxaudioFirmware *self) { g_return_val_if_fail(FU_IS_SYNAPTICS_CXAUDIO_FIRMWARE(self), 0); return self->layout_version; } static void fu_synaptics_cxaudio_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuSynapticsCxaudioFirmware *self = FU_SYNAPTICS_CXAUDIO_FIRMWARE(firmware); fu_xmlb_builder_insert_kv(bn, "file_kind", fu_synaptics_cxaudio_file_kind_to_string(self->file_kind)); fu_xmlb_builder_insert_kv(bn, "device_kind", fu_synaptics_cxaudio_device_kind_to_string(self->device_kind)); fu_xmlb_builder_insert_kx(bn, "layout_signature", self->layout_signature); fu_xmlb_builder_insert_kx(bn, "layout_version", self->layout_version); if (self->layout_version >= 1) { fu_xmlb_builder_insert_kx(bn, "vid", self->vendor_id); fu_xmlb_builder_insert_kx(bn, "pid", self->product_id); fu_xmlb_builder_insert_kx(bn, "rev", self->revision_id); } } typedef struct { const gchar *str; guint32 addr; guint32 len; } FuSynapticsCxaudioFirmwareBadblock; static void fu_synaptics_cxaudio_firmware_badblock_add(GPtrArray *badblocks, const gchar *str, guint32 addr, guint32 len) { FuSynapticsCxaudioFirmwareBadblock *bb = g_new0(FuSynapticsCxaudioFirmwareBadblock, 1); g_debug("created reserved range @0x%04x len:0x%x: %s", addr, len, str); bb->str = str; bb->addr = addr; bb->len = len; g_ptr_array_add(badblocks, bb); } static gboolean fu_synaptics_cxaudio_firmware_is_addr_valid(GPtrArray *badblocks, guint32 addr, guint32 len) { for (guint j = 0; j < badblocks->len; j++) { FuSynapticsCxaudioFirmwareBadblock *bb = g_ptr_array_index(badblocks, j); if (addr <= bb->addr + bb->len - 1 && addr + len - 1 >= bb->addr) { g_debug("addr @0x%04x len:0x%x invalid " "as 0x%02x->0x%02x protected: %s", addr, len, bb->addr, bb->addr + bb->len - 1, bb->str); return FALSE; } } return TRUE; } static gboolean fu_synaptics_cxaudio_firmware_is_record_valid(GPtrArray *badblocks, FuSrecFirmwareRecord *rcd) { /* the entire record is not within an ignored range */ return fu_synaptics_cxaudio_firmware_is_addr_valid(badblocks, rcd->addr, rcd->buf->len); } static void fu_synaptics_cxaudio_firmware_avoid_badblocks(GPtrArray *badblocks, GPtrArray *records) { g_autoptr(GPtrArray) records_new = g_ptr_array_new(); /* find records that include addresses with blocks we want to avoid */ for (guint i = 0; i < records->len; i++) { FuSrecFirmwareRecord *rcd = g_ptr_array_index(records, i); FuSrecFirmwareRecord *rcd1; if (rcd->kind != FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32) continue; if (fu_synaptics_cxaudio_firmware_is_record_valid(badblocks, rcd)) { rcd1 = fu_srec_firmware_record_new(rcd->ln, rcd->kind, rcd->addr); g_byte_array_append(rcd1->buf, rcd->buf->data, rcd->buf->len); g_ptr_array_add(records_new, rcd1); continue; } g_debug("splitting record @0x%04x len:0x%x as protected", rcd->addr, rcd->buf->len); for (guint j = 0; j < rcd->buf->len; j++) { if (!fu_synaptics_cxaudio_firmware_is_addr_valid(badblocks, rcd->addr + j, 0x1)) continue; rcd1 = fu_srec_firmware_record_new(rcd->ln, rcd->kind, rcd->addr + j); g_byte_array_append(rcd1->buf, rcd->buf->data + j, 0x1); g_ptr_array_add(records_new, rcd1); } } /* swap the old set of records with the new records */ g_ptr_array_set_size(records, 0); for (guint i = 0; i < records_new->len; i++) { FuSrecFirmwareRecord *rcd1 = g_ptr_array_index(records_new, i); g_ptr_array_add(records, rcd1); } } static gboolean fu_synaptics_cxaudio_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuSynapticsCxaudioFirmware *self = FU_SYNAPTICS_CXAUDIO_FIRMWARE(firmware); GPtrArray *records = fu_srec_firmware_get_records(FU_SREC_FIRMWARE(firmware)); guint8 dev_kind_candidate = G_MAXUINT8; g_autoptr(GByteArray) st = NULL; g_autoptr(GByteArray) st_sig = NULL; g_autoptr(GByteArray) st_pat = NULL; guint8 shadow[FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE] = {0x0}; /* copy shadow EEPROM */ for (guint i = 0; i < records->len; i++) { FuSrecFirmwareRecord *rcd = g_ptr_array_index(records, i); if (rcd->kind != FU_FIRMWARE_SREC_RECORD_KIND_S3_DATA_32) continue; if (rcd->addr > FU_SYNAPTICS_CXAUDIO_EEPROM_SHADOW_SIZE) continue; if (rcd->buf->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "record 0x%x had zero size", i); return FALSE; } if (!fu_memcpy_safe(shadow, sizeof(shadow), rcd->addr, /* dst */ rcd->buf->data, rcd->buf->len, 0x0, /* src */ rcd->buf->len, error)) return FALSE; } /* parse EEPROM map */ st = fu_struct_synaptics_cxaudio_custom_info_parse( shadow, sizeof(shadow), FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET, error); if (st == NULL) return FALSE; self->layout_signature = fu_struct_synaptics_cxaudio_custom_info_get_layout_signature(st); self->layout_version = fu_struct_synaptics_cxaudio_custom_info_get_layout_version(st); self->vendor_id = fu_struct_synaptics_cxaudio_custom_info_get_vendor_id(st); self->product_id = fu_struct_synaptics_cxaudio_custom_info_get_product_id(st); self->revision_id = fu_struct_synaptics_cxaudio_custom_info_get_revision_id(st); /* just layout version byte is not enough in case of old CX20562 patch * files that could have non-zero value of the Layout version */ st_sig = fu_struct_synaptics_cxaudio_validity_signature_parse( shadow, sizeof(shadow), FU_SYNAPTICS_CXAUDIO_EEPROM_VALIDITY_SIGNATURE_OFFSET, error); if (st_sig == NULL) return FALSE; st_pat = fu_struct_synaptics_cxaudio_patch_info_parse( shadow, sizeof(shadow), FU_SYNAPTICS_CXAUDIO_EEPROM_PATCH_INFO_OFFSET, error); if (st_pat == NULL) return FALSE; if (fu_struct_synaptics_cxaudio_validity_signature_get_magic_byte(st_sig) == FU_SYNAPTICS_CXAUDIO_SIGNATURE_BYTE) { self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2070X; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_FW; g_debug("FileKind: CX2070x (FW)"); } else if (fu_struct_synaptics_cxaudio_patch_info_get_patch_signature(st_pat) == FU_SYNAPTICS_CXAUDIO_SIGNATURE_PATCH_BYTE) { self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2070X; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_PATCH; g_debug("FileKind: CX2070x (Patch)"); } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "CX20562 is not supported"); return FALSE; } for (guint i = records->len - 3; i < records->len; i++) { FuSrecFirmwareRecord *rcd = g_ptr_array_index(records, i); if (rcd->kind == FU_FIRMWARE_SREC_RECORD_KIND_S9_TERMINATION_16) continue; if (rcd->buf->len < 2) continue; if (memcmp(rcd->buf->data, "CX", 2) == 0) { dev_kind_candidate = rcd->buf->data[2]; g_debug("DeviceKind signature suspected 0x%0x", dev_kind_candidate); break; } } /* check the signature character to see if it defines the device */ switch (dev_kind_candidate) { case '2': /* fallthrough */ /* CX2070x */ case '4': /* CX2070x-21Z */ case '6': /* CX2070x-21Z */ self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2070X; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2070X_PATCH; g_debug("FileKind: CX2070x overwritten from signature"); break; case '3': self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2077X; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2077X_PATCH; g_debug("FileKind: CX2077x overwritten from signature"); break; case '5': self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2076X; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2076X_PATCH; g_debug("FileKind: CX2076x overwritten from signature"); break; case '7': self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2085X; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2085X_PATCH; g_debug("FileKind: CX2085x overwritten from signature"); break; case '8': self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2089X; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2089X_PATCH; g_debug("FileKind: CX2089x overwritten from signature"); break; case '9': self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2098X; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2098X_PATCH; g_debug("FileKind: CX2098x overwritten from signature"); break; case 'A': self->device_kind = FU_SYNAPTICS_CXAUDIO_DEVICE_KIND_CX2198X; self->file_kind = FU_SYNAPTICS_CXAUDIO_FILE_KIND_CX2198X_PATCH; g_debug("FileKind: CX2198x overwritten from signature"); break; default: /* probably future devices */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "DeviceKind signature invalid 0x%x", dev_kind_candidate); return FALSE; } /* ignore records with protected content */ if (self->layout_version >= 1) { guint16 serial_number_string_address = fu_struct_synaptics_cxaudio_custom_info_get_serial_number_string_address(st); g_autoptr(GPtrArray) badblocks = g_ptr_array_new_with_free_func(g_free); /* add standard ranges to ignore */ fu_synaptics_cxaudio_firmware_badblock_add(badblocks, "test mark", 0x00BC, 0x02); fu_synaptics_cxaudio_firmware_badblock_add( badblocks, "application status", FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET + FU_STRUCT_SYNAPTICS_CXAUDIO_CUSTOM_INFO_OFFSET_APPLICATION_STATUS, sizeof(guint8)); fu_synaptics_cxaudio_firmware_badblock_add( badblocks, "boot bytes", FU_SYNAPTICS_CXAUDIO_EEPROM_VALIDITY_SIGNATURE_OFFSET, FU_STRUCT_SYNAPTICS_CXAUDIO_VALIDITY_SIGNATURE_SIZE + 1); /* serial number address and also string pointer itself if set */ if (serial_number_string_address != 0x0) { guint16 addr_tmp; guint16 addr_str = 0; addr_tmp = FU_SYNAPTICS_CXAUDIO_EEPROM_CUSTOM_INFO_OFFSET + FU_STRUCT_SYNAPTICS_CXAUDIO_CUSTOM_INFO_OFFSET_SERIAL_NUMBER_STRING_ADDRESS; fu_synaptics_cxaudio_firmware_badblock_add(badblocks, "serial number", addr_tmp, sizeof(guint16)); if (!fu_memread_uint16_safe(shadow, sizeof(shadow), addr_tmp, &addr_str, G_LITTLE_ENDIAN, error)) return FALSE; fu_synaptics_cxaudio_firmware_badblock_add(badblocks, "serial number data", addr_str, shadow[addr_str]); } fu_synaptics_cxaudio_firmware_avoid_badblocks(badblocks, records); } /* success */ return TRUE; } static void fu_synaptics_cxaudio_firmware_init(FuSynapticsCxaudioFirmware *self) { } static void fu_synaptics_cxaudio_firmware_class_init(FuSynapticsCxaudioFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_synaptics_cxaudio_firmware_parse; klass_firmware->export = fu_synaptics_cxaudio_firmware_export; } FuFirmware * fu_synaptics_cxaudio_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_SYNAPTICS_CXAUDIO_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-firmware.h000066400000000000000000000015031460375044200261330ustar00rootroot00000000000000/* * Copyright (C) 2005 Synaptics Incorporated * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-synaptics-cxaudio-struct.h" #define FU_TYPE_SYNAPTICS_CXAUDIO_FIRMWARE (fu_synaptics_cxaudio_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuSynapticsCxaudioFirmware, fu_synaptics_cxaudio_firmware, FU, SYNAPTICS_CXAUDIO_FIRMWARE, FuSrecFirmware) FuFirmware * fu_synaptics_cxaudio_firmware_new(void); FuSynapticsCxaudioFileKind fu_synaptics_cxaudio_firmware_get_file_type(FuSynapticsCxaudioFirmware *self); FuSynapticsCxaudioDeviceKind fu_synaptics_cxaudio_firmware_get_devtype(FuSynapticsCxaudioFirmware *self); guint8 fu_synaptics_cxaudio_firmware_get_layout_version(FuSynapticsCxaudioFirmware *self); fwupd-1.9.16/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-plugin.c000066400000000000000000000023301460375044200256070ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-cxaudio-device.h" #include "fu-synaptics-cxaudio-firmware.h" #include "fu-synaptics-cxaudio-plugin.h" struct _FuSynapticsCxaudioPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuSynapticsCxaudioPlugin, fu_synaptics_cxaudio_plugin, FU_TYPE_PLUGIN) static void fu_synaptics_cxaudio_plugin_init(FuSynapticsCxaudioPlugin *self) { } static void fu_synaptics_cxaudio_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "CxaudioChipIdBase"); fu_context_add_quirk_key(ctx, "CxaudioPatch1ValidAddr"); fu_context_add_quirk_key(ctx, "CxaudioPatch2ValidAddr"); fu_context_add_quirk_key(ctx, "CxaudioSoftwareReset"); fu_plugin_add_device_gtype(plugin, FU_TYPE_SYNAPTICS_CXAUDIO_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_SYNAPTICS_CXAUDIO_FIRMWARE); } static void fu_synaptics_cxaudio_plugin_class_init(FuSynapticsCxaudioPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_synaptics_cxaudio_plugin_constructed; } fwupd-1.9.16/plugins/synaptics-cxaudio/fu-synaptics-cxaudio-plugin.h000066400000000000000000000004441460375044200256200ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuSynapticsCxaudioPlugin, fu_synaptics_cxaudio_plugin, FU, SYNAPTICS_CXAUDIO_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/synaptics-cxaudio/fu-synaptics-cxaudio.rs000066400000000000000000000026001460375044200245150ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum SynapticsCxaudioDeviceKind { Unknown, Cx20562 = 20562, Cx2070x = 20700, Cx2077x = 20770, Cx2076x = 20760, Cx2085x = 20850, Cx2089x = 20890, Cx2098x = 20980, Cx2198x = 21980, } enum SynapticsCxaudioMemKind { Eeprom, CpxRam, CpxRom, } #[derive(ToString)] enum SynapticsCxaudioFileKind { Unknown, Cx2070xFw, Cx2070xPatch, Cx2077xPatch, Cx2076xPatch, Cx2085xPatch, Cx2089xPatch, Cx2098xPatch, Cx2198xPatch, } #[derive(Parse)] struct SynapticsCxaudioCustomInfo { patch_version_string_address: u16le, cpx_patch_version: [u8; 3], spx_patch_version: [u8; 4], layout_signature: u8, layout_version: u8, application_status: u8, vendor_id: u16le, product_id: u16le, revision_id: u16le, language_string_address: u16le, manufacturer_string_address: u16le, product_string_address: u16le, serial_number_string_address: u16le, } #[derive(Parse)] struct SynapticsCxaudioStringHeader { length: u8, type: u8 == 0x03, } #[derive(Parse)] struct SynapticsCxaudioValiditySignature { magic_byte: u8 = 0x4C, // 'L' eeprom_size_code: u8, } #[derive(Parse, Setters)] struct SynapticsCxaudioPatchInfo { patch_signature: u8, patch_address: u16le, } fwupd-1.9.16/plugins/synaptics-cxaudio/meson.build000066400000000000000000000010371460375044200222350ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsCxaudio"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('synaptics-cxaudio.quirk') plugin_builtins += static_library('fu_plugin_synaptics_cxaudio', rustgen.process('fu-synaptics-cxaudio.rs'), sources: [ 'fu-synaptics-cxaudio-plugin.c', 'fu-synaptics-cxaudio-device.c', 'fu-synaptics-cxaudio-firmware.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/synaptics-cxaudio/synaptics-cxaudio.quirk000066400000000000000000000024611460375044200246210ustar00rootroot00000000000000# ThinkPad TBT3-TR Gen 2 dock [USB\VID_17EF&PID_3083] Guid[quirk] = SYNAPTICS_CXAUDIO\ID_CX2098X ParentGuid = USB\VID_17EF&PID_307F&HUB_0006 # ThinkPad TBT3-TR Gen 2 dock UAC2.0 [USB\VID_17EF&PID_30CF] Guid[quirk] = SYNAPTICS_CXAUDIO\ID_CX2098X ParentGuid = USB\VID_17EF&PID_307F&HUB_0006 # ThinkPad TBT3-TR Gen 2 dock substitute [USB\VID_17EF&PID_30C9] Guid[quirk] = SYNAPTICS_CXAUDIO\ID_CX2198X ParentGuid = USB\VID_17EF&PID_307F&HUB_0006 # ThinkPad TBT3-MS Gen 2 dock [USB\VID_17EF&PID_3092] Guid[quirk] = SYNAPTICS_CXAUDIO\ID_CX2198X ParentGuid = USB\VID_17EF&PID_308F # ThinkPad USB-C Dock Gen2 Audio [USB\VID_17EF&PID_A396] Guid[quirk] = SYNAPTICS_CXAUDIO\ID_CX2198X ParentGuid = USB\VID_17EF&PID_A391 # ThinkPad USB-C Dock Gen2 Audio as updated in firmware version 49-0E-41 [USB\VID_17EF&PID_30D1] Guid[quirk] = SYNAPTICS_CXAUDIO\ID_CX2198X ParentGuid = USB\VID_17EF&PID_A391 # Google Pixel USB-C headphones [USB\VID_18D1&PID_5033] Guid[quirk] = SYNAPTICS_CXAUDIO\ID_CX2198X # Google Pixel USB-C <-> 3.5mm adapter [USB\VID_18D1&PID_5034] Guid[quirk] = SYNAPTICS_CXAUDIO\ID_CX2198X [SYNAPTICS_CXAUDIO\ID_CX2098X] Plugin = synaptics_cxaudio CxaudioChipIdBase = 20980 [SYNAPTICS_CXAUDIO\ID_CX2198X] Plugin = synaptics_cxaudio CxaudioChipIdBase = 21980 CxaudioPatch1ValidAddr = 0x0014 CxaudioPatch2ValidAddr = 0x0171 fwupd-1.9.16/plugins/synaptics-mst/000077500000000000000000000000001460375044200172415ustar00rootroot00000000000000fwupd-1.9.16/plugins/synaptics-mst/README.md000066400000000000000000000063501460375044200205240ustar00rootroot00000000000000--- title: Plugin: Synaptics MST --- ## Introduction This plugin supports querying and flashing Synaptics MST hubs used in Dell systems and docks. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. This plugin supports the following protocol ID: * `com.synaptics.mst` ## GUID Generation These devices use custom GUID values, e.g. * `MST-$(board-ID)` * `MST-$(device_kind)-$(chip-ID)-$(board-ID)` * `MST-$(device_kind)-$(board-ID)` * `MST-$(device_kind)` Please refer to the plugin source for more details about how the GUID is constructed for specific hardware. ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. On some hardware the MST device may not enumerate if there is no monitor actually plugged in. ## Vendor ID Security The vendor ID is set from the PCI vendor, for example set to `DRM_DP_AUX_DEV:0x$(vid)` ## Quirk Use This plugin uses the following plugin-specific quirks: ### Flags:manual-restart-required The device must be restarted manually after the update has completed. ### Flags:ignore-board-id Ignore board ID firmware mismatch. ## Requirements ### (Kernel) DP Aux Interface Kernel 4.6 introduced an DRM DP Aux interface for manipulation of the registers needed to access an MST hub. This patch can be backported to earlier kernels: ## Usage Supported devices will be displayed in `# fwupdmgr get-devices` output. Here is an example output from a Dell WD15 dock: ```text Dell WD15/TB16 wired Dock Synaptics VMM3332 Guid: 653cd006-5433-57db-8632-0413af4d3fcc DeviceID: MST-1-1-0-0 Plugin: synaptics_mst Flags: allow-online Version: 3.10.002 Created: 2017-01-13 Modified: 2017-01-13 Trusted: none ``` Payloads can be flashed just like any other plugin from LVFS. ## Supported devices Not all Dell systems or accessories contain MST hubs. Here is a sample list of systems known to support them however: * Dell WD15 dock * Dell TB16 dock * Dell TB18DC * Latitude E5570 * Latitude E5470 * Latitude E5270 * Latitude E7470 * Latitude E7270 * Latitude E7450 * Latitude E7250 * Latitude E5550 * Latitude E5450 * Latitude E5250 * Latitude Rugged 5414 * Latitude Rugged 7214 * Latitude Rugged 7414 ## External Interface Access This plugin requires read/write access to `/dev/drm_dp_aux*`. ## Version Considerations This plugin has been available since fwupd version `1.3.6`. ## Data Flow ```mermaid flowchart LR subgraph MST Controller MST(Controller) SPI[(SPI)] end subgraph Kernel gpu(GPU\ndriver) end subgraph fwupd Process fwupdengine(FuEngine) mst_plugin(Synaptics MST\nPlugin) end fwupdengine-->mst_plugin mst_plugin<--/dev/drm_dp_aux-->gpu gpu<--DDC/I2C-->MST MST---SPI ``` ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Apollo Ling: @ApolloLing fwupd-1.9.16/plugins/synaptics-mst/fu-self-test.c000066400000000000000000000162631460375044200217330ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-context-private.h" #include "fu-plugin-private.h" #include "fu-synaptics-mst-common.h" #include "fu-synaptics-mst-firmware.h" #include "fu-synaptics-mst-plugin.h" static void _plugin_device_added_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data) { GPtrArray **devices = (GPtrArray **)user_data; g_ptr_array_add(*devices, g_object_ref(device)); } static void _test_add_fake_devices_from_dir(FuPlugin *plugin, const gchar *path) { const gchar *basename; gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GDir) dir = g_dir_open(path, 0, &error); g_assert_no_error(error); g_assert_nonnull(dir); ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_context_load_hwinfo(ctx, progress, FU_CONTEXT_HWID_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); while ((basename = g_dir_read_name(dir)) != NULL) { g_autofree gchar *fn = g_build_filename(path, basename, NULL); g_autoptr(FuProgress) progress_local = fu_progress_new(G_STRLOC); g_autoptr(FuUdevDevice) dev = NULL; if (!g_str_has_prefix(basename, "drm_dp_aux")) continue; dev = g_object_new(FU_TYPE_DPAUX_DEVICE, "context", ctx, "physical-id", "PCI_SLOT_NAME=0000:3e:00.0", "logical-id", basename, "subsystem", "drm_dp_aux_dev", "device-file", fn, "dpcd-ieee-oui", SYNAPTICS_IEEE_OUI, NULL); g_debug("creating drm_dp_aux_dev object backed by %s", fn); ret = fu_plugin_runner_backend_device_added(plugin, FU_DEVICE(dev), progress_local, &error); g_assert_no_error(error); g_assert_true(ret); } } /* test with no Synaptics MST devices */ static void fu_plugin_synaptics_mst_none_func(void) { gboolean ret; const gchar *ci = g_getenv("CI_NETWORK"); g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuPlugin) plugin = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_autofree gchar *filename = NULL; ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_context_load_hwinfo(ctx, progress, FU_CONTEXT_HWID_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); plugin = fu_plugin_new_from_gtype(fu_synaptics_mst_plugin_get_type(), ctx); g_signal_connect(FU_PLUGIN(plugin), "device-added", G_CALLBACK(_plugin_device_added_cb), &devices); ret = fu_plugin_runner_startup(plugin, progress, &error); if (!ret && g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip("Skipping tests due to unsupported configuration"); return; } g_assert_no_error(error); g_assert_true(ret); filename = g_test_build_filename(G_TEST_DIST, "tests", "no_devices", NULL); if (!g_file_test(filename, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing no_devices"); return; } _test_add_fake_devices_from_dir(plugin, filename); g_assert_cmpint(devices->len, ==, 0); } /* emulate adding/removing a Dell TB16 dock */ static void fu_plugin_synaptics_mst_tb16_func(void) { gboolean ret; const gchar *ci = g_getenv("CI_NETWORK"); g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(FuPlugin) plugin = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_autofree gchar *filename = NULL; ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_context_load_hwinfo(ctx, progress, FU_CONTEXT_HWID_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); plugin = fu_plugin_new_from_gtype(fu_synaptics_mst_plugin_get_type(), ctx); g_signal_connect(FU_PLUGIN(plugin), "device-added", G_CALLBACK(_plugin_device_added_cb), &devices); ret = fu_plugin_runner_startup(plugin, progress, &error); if (!ret && g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip("Skipping tests due to unsupported configuration"); return; } g_assert_no_error(error); g_assert_true(ret); filename = g_test_build_filename(G_TEST_DIST, "tests", "tb16_dock", NULL); if (!g_file_test(filename, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing tb16_dock"); return; } _test_add_fake_devices_from_dir(plugin, filename); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); g_autofree gchar *tmp = fu_device_to_string(device); g_debug("%s", tmp); } g_assert_cmpint(devices->len, ==, 2); } static void fu_synaptics_mst_firmware_xml_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *csum1 = NULL; g_autofree gchar *csum2 = NULL; g_autofree gchar *xml_out = NULL; g_autofree gchar *xml_src = NULL; g_autoptr(FuFirmware) firmware1 = fu_synaptics_mst_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_synaptics_mst_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "synaptics-mst.builder.xml", NULL); ret = g_file_get_contents(filename, &xml_src, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_build_from_xml(firmware1, xml_src, &error); g_assert_no_error(error); g_assert_true(ret); csum1 = fu_firmware_get_checksum(firmware1, G_CHECKSUM_SHA1, &error); g_assert_no_error(error); g_assert_cmpstr(csum1, ==, "bfcdf3e6ca6cef45543bfbb57509c92aec9a39fb"); /* ensure we can round-trip */ xml_out = fu_firmware_export_to_xml(firmware1, FU_FIRMWARE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); ret = fu_firmware_build_from_xml(firmware2, xml_out, &error); g_assert_no_error(error); g_assert_true(ret); csum2 = fu_firmware_get_checksum(firmware2, G_CHECKSUM_SHA1, &error); g_assert_cmpstr(csum1, ==, csum2); } int main(int argc, char **argv) { g_autofree gchar *testdatadir = NULL; (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); (void)g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", testdatadir, TRUE); (void)g_setenv("CONFIGURATION_DIRECTORY", testdatadir, TRUE); g_assert_cmpint(g_mkdir_with_parents("/tmp/fwupd-self-test/var/lib/fwupd", 0755), ==, 0); /* tests go here */ g_test_add_func("/fwupd/plugin/synaptics_mst{none}", fu_plugin_synaptics_mst_none_func); g_test_add_func("/fwupd/plugin/synaptics_mst{tb16}", fu_plugin_synaptics_mst_tb16_func); g_test_add_func("/fwupd/plugin/synaptics_mst/firmware{xml}", fu_synaptics_mst_firmware_xml_func); return g_test_run(); } fwupd-1.9.16/plugins/synaptics-mst/fu-synaptics-mst-common.c000066400000000000000000000124601460375044200241240ustar00rootroot00000000000000/* * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2019 Richard Hughes * Copyright (C) 2021 Apollo Ling * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-mst-common.h" FuSynapticsMstFamily fu_synaptics_mst_family_from_chip_id(guint16 chip_id) { if (chip_id >= 0x7000 && chip_id < 0x8000) return FU_SYNAPTICS_MST_FAMILY_SPYDER; if ((chip_id >= 0x6000 && chip_id < 0x7000) || (chip_id >= 0x8000 && chip_id < 0x9000)) return FU_SYNAPTICS_MST_FAMILY_CAYENNE; if (chip_id >= 0x5000 && chip_id < 0x6000) return FU_SYNAPTICS_MST_FAMILY_PANAMERA; if (chip_id >= 0x3000 && chip_id < 0x4000) return FU_SYNAPTICS_MST_FAMILY_LEAF; if (chip_id >= 0x2000 && chip_id < 0x3000) return FU_SYNAPTICS_MST_FAMILY_TESLA; return FU_SYNAPTICS_MST_FAMILY_UNKNOWN; } guint8 fu_synaptics_mst_calculate_crc8(guint8 crc, const guint8 *buf, gsize bufsz) { static const guint16 CRC8_table[] = { 0x00, 0xd5, 0x7f, 0xaa, 0xfe, 0x2b, 0x81, 0x54, 0x29, 0xfc, 0x56, 0x83, 0xd7, 0x02, 0xa8, 0x7d, 0x52, 0x87, 0x2d, 0xf8, 0xac, 0x79, 0xd3, 0x06, 0x7b, 0xae, 0x04, 0xd1, 0x85, 0x50, 0xfa, 0x2f, 0xa4, 0x71, 0xdb, 0x0e, 0x5a, 0x8f, 0x25, 0xf0, 0x8d, 0x58, 0xf2, 0x27, 0x73, 0xa6, 0x0c, 0xd9, 0xf6, 0x23, 0x89, 0x5c, 0x08, 0xdd, 0x77, 0xa2, 0xdf, 0x0a, 0xa0, 0x75, 0x21, 0xf4, 0x5e, 0x8b, 0x9d, 0x48, 0xe2, 0x37, 0x63, 0xb6, 0x1c, 0xc9, 0xb4, 0x61, 0xcb, 0x1e, 0x4a, 0x9f, 0x35, 0xe0, 0xcf, 0x1a, 0xb0, 0x65, 0x31, 0xe4, 0x4e, 0x9b, 0xe6, 0x33, 0x99, 0x4c, 0x18, 0xcd, 0x67, 0xb2, 0x39, 0xec, 0x46, 0x93, 0xc7, 0x12, 0xb8, 0x6d, 0x10, 0xc5, 0x6f, 0xba, 0xee, 0x3b, 0x91, 0x44, 0x6b, 0xbe, 0x14, 0xc1, 0x95, 0x40, 0xea, 0x3f, 0x42, 0x97, 0x3d, 0xe8, 0xbc, 0x69, 0xc3, 0x16, 0xef, 0x3a, 0x90, 0x45, 0x11, 0xc4, 0x6e, 0xbb, 0xc6, 0x13, 0xb9, 0x6c, 0x38, 0xed, 0x47, 0x92, 0xbd, 0x68, 0xc2, 0x17, 0x43, 0x96, 0x3c, 0xe9, 0x94, 0x41, 0xeb, 0x3e, 0x6a, 0xbf, 0x15, 0xc0, 0x4b, 0x9e, 0x34, 0xe1, 0xb5, 0x60, 0xca, 0x1f, 0x62, 0xb7, 0x1d, 0xc8, 0x9c, 0x49, 0xe3, 0x36, 0x19, 0xcc, 0x66, 0xb3, 0xe7, 0x32, 0x98, 0x4d, 0x30, 0xe5, 0x4f, 0x9a, 0xce, 0x1b, 0xb1, 0x64, 0x72, 0xa7, 0x0d, 0xd8, 0x8c, 0x59, 0xf3, 0x26, 0x5b, 0x8e, 0x24, 0xf1, 0xa5, 0x70, 0xda, 0x0f, 0x20, 0xf5, 0x5f, 0x8a, 0xde, 0x0b, 0xa1, 0x74, 0x09, 0xdc, 0x76, 0xa3, 0xf7, 0x22, 0x88, 0x5d, 0xd6, 0x03, 0xa9, 0x7c, 0x28, 0xfd, 0x57, 0x82, 0xff, 0x2a, 0x80, 0x55, 0x01, 0xd4, 0x7e, 0xab, 0x84, 0x51, 0xfb, 0x2e, 0x7a, 0xaf, 0x05, 0xd0, 0xad, 0x78, 0xd2, 0x07, 0x53, 0x86, 0x2c, 0xf9}; guint8 val; guint16 remainder = (guint16)crc; const guint8 *message = buf; for (guint32 byte = 0; byte < bufsz; ++byte) { val = (guint8)(message[byte] ^ remainder); remainder = CRC8_table[val]; } return (guint8)remainder; } guint16 fu_synaptics_mst_calculate_crc16(guint16 crc, const guint8 *buf, gsize bufsz) { static const guint16 CRC16_table[] = { 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, 0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, 0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, 0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, 0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, 0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, 0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, 0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, 0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, 0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, 0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, 0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, 0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, 0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202}; guint8 val; guint16 remainder = crc; const guint8 *message = buf; for (guint32 byte = 0; byte < bufsz; ++byte) { val = (guint8)(message[byte] ^ (remainder >> 8)); remainder = CRC16_table[val] ^ (remainder << 8); } return remainder; } fwupd-1.9.16/plugins/synaptics-mst/fu-synaptics-mst-common.h000066400000000000000000000013121460375044200241230ustar00rootroot00000000000000/* * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2017 Peichen Huang * Copyright (C) 2019 Richard Hughes * Copyright (C) 2021 Apollo Ling * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-synaptics-mst-struct.h" #define SYNAPTICS_FLASH_MODE_DELAY 3 /* seconds */ #define SYNAPTICS_IEEE_OUI 0x90CC24 FuSynapticsMstFamily fu_synaptics_mst_family_from_chip_id(guint16 chip_id); guint8 fu_synaptics_mst_calculate_crc8(guint8 crc, const guint8 *buf, gsize bufsz); guint16 fu_synaptics_mst_calculate_crc16(guint16 crc, const guint8 *buf, gsize bufsz); fwupd-1.9.16/plugins/synaptics-mst/fu-synaptics-mst-device.c000066400000000000000000001472251460375044200241030ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * Copyright (C) 2016 Mario Limonciello * Copyright (C) 2017 Peichen Huang * Copyright (C) 2018 Ryan Chang * Copyright (C) 2021 Apollo Ling * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaptics-mst-common.h" #include "fu-synaptics-mst-device.h" #include "fu-synaptics-mst-firmware.h" #include "fu-synaptics-mst-struct.h" #define FU_SYNAPTICS_MST_ID_CTRL_SIZE 0x1000 #define SYNAPTICS_UPDATE_ENUMERATE_TRIES 3 #define BIT(n) (1 << (n)) #define FLASH_SECTOR_ERASE_4K 0x1000 #define FLASH_SECTOR_ERASE_32K 0x2000 #define FLASH_SECTOR_ERASE_64K 0x3000 #define EEPROM_TAG_OFFSET 0x1FFF0 #define EEPROM_BANK_OFFSET 0x20000 #define EEPROM_ESM_OFFSET 0x40000 #define ESM_CODE_SIZE 0x40000 #define PAYLOAD_SIZE_512K 0x80000 #define PAYLOAD_SIZE_64K 0x10000 #define MAX_RETRY_COUNTS 10 #define BLOCK_UNIT 64 #define BANKTAG_0 0 #define BANKTAG_1 1 #define REG_ESM_DISABLE 0x2000fc #define REG_QUAD_DISABLE 0x200fc0 #define REG_HDCP22_DISABLE 0x200f90 #define FLASH_SETTLE_TIME 5000 /* ms */ #define FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT 2000 /* ms */ #define CAYENNE_FIRMWARE_SIZE 0x50000 /* bytes */ #define PANAMERA_FIRMWARE_SIZE 0x1A000 /* bytes */ #define UNIT_SIZE 32 #define ADDR_MEMORY_CUSTOMER_ID_CAYENNE 0x9000024E #define ADDR_MEMORY_BOARD_ID_CAYENNE 0x9000024F #define ADDR_MEMORY_CUSTOMER_ID_SPYDER 0x9000020E #define ADDR_MEMORY_BOARD_ID_SPYDER 0x9000020F #define ADDR_MEMORY_CUSTOMER_ID 0x170E #define ADDR_MEMORY_BOARD_ID 0x170F #define REG_CHIP_ID 0x507 #define REG_FIRMWARE_VERSION 0x50A /** * FU_SYNAPTICS_MST_DEVICE_FLAG_IGNORE_BOARD_ID: * * Ignore board ID firmware mismatch. */ #define FU_SYNAPTICS_MST_DEVICE_FLAG_IGNORE_BOARD_ID (1 << 0) /** * FU_SYNAPTICS_MST_DEVICE_FLAG_MANUAL_RESTART_REQUIRED: * * The device must be restarted manually after the update has completed. */ #define FU_SYNAPTICS_MST_DEVICE_FLAG_MANUAL_RESTART_REQUIRED (1 << 1) struct _FuSynapticsMstDevice { FuDpauxDevice parent_instance; gchar *device_kind; gchar *system_type; guint64 write_block_size; FuSynapticsMstFamily family; guint8 active_bank; guint32 board_id; guint16 chip_id; }; G_DEFINE_TYPE(FuSynapticsMstDevice, fu_synaptics_mst_device, FU_TYPE_DPAUX_DEVICE) static void fu_synaptics_mst_device_finalize(GObject *object) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(object); g_free(self->device_kind); g_free(self->system_type); G_OBJECT_CLASS(fu_synaptics_mst_device_parent_class)->finalize(object); } static void fu_synaptics_mst_device_udev_device_notify_cb(FuUdevDevice *udev_device, GParamSpec *pspec, gpointer user_data) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(user_data); if (fu_udev_device_get_dev(FU_UDEV_DEVICE(self)) != NULL) { fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT); } else { fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT); } } static void fu_synaptics_mst_device_init(FuSynapticsMstDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.synaptics.mst"); fu_device_set_vendor(FU_DEVICE(self), "Synaptics"); fu_device_add_vendor_id(FU_DEVICE(self), "DRM_DP_AUX_DEV:0x06CB"); fu_device_set_summary(FU_DEVICE(self), "Multi-stream transport device"); fu_device_add_icon(FU_DEVICE(self), "video-display"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_register_private_flag(FU_DEVICE(self), FU_SYNAPTICS_MST_DEVICE_FLAG_IGNORE_BOARD_ID, "ignore-board-id"); fu_device_register_private_flag(FU_DEVICE(self), FU_SYNAPTICS_MST_DEVICE_FLAG_MANUAL_RESTART_REQUIRED, "manual-restart-required"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_PROBE_COMPLETE); fu_device_add_request_flag(FU_DEVICE(self), FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); /* this is set from ->incorporate() */ g_signal_connect(FU_UDEV_DEVICE(self), "notify::udev-device", G_CALLBACK(fu_synaptics_mst_device_udev_device_notify_cb), self); } static void fu_synaptics_mst_device_to_string(FuDevice *device, guint idt, GString *str) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); /* FuDpauxDevice->to_string */ FU_DEVICE_CLASS(fu_synaptics_mst_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "DeviceKind", self->device_kind); if (self->family == FU_SYNAPTICS_MST_FAMILY_PANAMERA) fu_string_append_kx(str, idt, "ActiveBank", self->active_bank); if (self->board_id != 0x0) fu_string_append_ku(str, idt, "BoardId", self->board_id); if (self->chip_id != 0x0) fu_string_append_kx(str, idt, "ChipId", self->chip_id); } static gboolean fu_synaptics_mst_device_rc_to_error(FuSynapticsMstUpdcRc rc, GError **error) { gint code = G_IO_ERROR_FAILED; /* yay */ if (rc == FU_SYNAPTICS_MST_UPDC_RC_SUCCESS) return TRUE; /* map */ switch (rc) { case FU_SYNAPTICS_MST_UPDC_RC_INVALID: code = G_IO_ERROR_INVALID_DATA; break; case FU_SYNAPTICS_MST_UPDC_RC_UNSUPPORTED: code = G_IO_ERROR_NOT_SUPPORTED; break; case FU_SYNAPTICS_MST_UPDC_RC_FAILED: code = G_IO_ERROR_FAILED; break; case FU_SYNAPTICS_MST_UPDC_RC_DISABLED: code = G_IO_ERROR_NOT_FOUND; break; case FU_SYNAPTICS_MST_UPDC_RC_CONFIGURE_SIGN_FAILED: case FU_SYNAPTICS_MST_UPDC_RC_FIRMWARE_SIGN_FAILED: case FU_SYNAPTICS_MST_UPDC_RC_ROLLBACK_FAILED: code = G_IO_ERROR_INVALID_ARGUMENT; break; default: break; } g_set_error(error, G_IO_ERROR, code, "%s [%u]", fu_synaptics_mst_updc_rc_to_string(rc), rc); return FALSE; } typedef struct { FuSynapticsMstUpdcRc rc; FuSynapticsMstUpdcCmd rc_cmd; } FuSynapticsMstUpdcRcHelper; static gboolean fu_synaptics_mst_device_rc_send_command_and_wait_cb(FuDevice *device, gpointer user_data, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); FuSynapticsMstUpdcRcHelper *helper = (FuSynapticsMstUpdcRcHelper *)user_data; guint8 buf[2] = {0}; if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), FU_SYNAPTICS_MST_REG_RC_CMD, buf, sizeof(buf), FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failed to read command: "); return FALSE; } if (buf[0] & 0x80) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "command in active state"); return FALSE; } /* success */ helper->rc = buf[1]; return TRUE; } static gboolean fu_synaptics_mst_device_rc_send_command_and_wait(FuSynapticsMstDevice *self, FuSynapticsMstUpdcCmd rc_cmd, GError **error) { guint8 buf[1] = {rc_cmd | 0x80}; FuSynapticsMstUpdcRcHelper helper = { .rc = FU_SYNAPTICS_MST_UPDC_RC_SUCCESS, .rc_cmd = rc_cmd, }; if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), FU_SYNAPTICS_MST_REG_RC_CMD, buf, sizeof(buf), FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failed to write command: "); return FALSE; } /* wait command complete */ if (!fu_device_retry_full(FU_DEVICE(self), fu_synaptics_mst_device_rc_send_command_and_wait_cb, 30, 100, /* ms */ &helper, error)) { g_prefix_error(error, "remote command failed: "); return FALSE; } if (helper.rc != FU_SYNAPTICS_MST_UPDC_RC_SUCCESS) { if (!fu_synaptics_mst_device_rc_to_error(helper.rc, error)) { g_prefix_error(error, "remote command failed: "); return FALSE; } } return TRUE; } static gboolean fu_synaptics_mst_device_rc_set_command(FuSynapticsMstDevice *self, FuSynapticsMstUpdcCmd rc_cmd, guint32 offset, const guint8 *buf, gsize bufsz, GError **error) { g_autoptr(GPtrArray) chunks = fu_chunk_array_new(buf, bufsz, offset, 0x0, UNIT_SIZE); /* just sent command */ if (chunks->len == 0) { g_debug("no data, just sending command %s [0x%x]", fu_synaptics_mst_updc_cmd_to_string(rc_cmd), rc_cmd); return fu_synaptics_mst_device_rc_send_command_and_wait(self, rc_cmd, error); } /* read each chunk */ for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint8 buf2[4] = {0}; g_debug("writing chunk of 0x%x bytes at offset 0x%x", fu_chunk_get_data_sz(chk), fu_chunk_get_address(chk)); /* write data */ if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), FU_SYNAPTICS_MST_REG_RC_DATA, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failure writing data register: "); return FALSE; } /* write offset */ fu_memwrite_uint32(buf2, fu_chunk_get_address(chk), G_LITTLE_ENDIAN); if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), FU_SYNAPTICS_MST_REG_RC_OFFSET, buf2, sizeof(buf2), FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failure writing offset register: "); return FALSE; } /* write length */ fu_memwrite_uint32(buf2, fu_chunk_get_data_sz(chk), G_LITTLE_ENDIAN); if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), FU_SYNAPTICS_MST_REG_RC_LEN, buf2, sizeof(buf2), FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failure writing length register: "); return FALSE; } /* send command */ g_debug("data, sending command %s [0x%x]", fu_synaptics_mst_updc_cmd_to_string(rc_cmd), rc_cmd); if (!fu_synaptics_mst_device_rc_send_command_and_wait(self, rc_cmd, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_mst_device_rc_get_command(FuSynapticsMstDevice *self, FuSynapticsMstUpdcCmd rc_cmd, guint32 offset, guint8 *buf, gsize bufsz, GError **error) { g_autoptr(GPtrArray) chunks = fu_chunk_array_mutable_new(buf, bufsz, offset, 0x0, UNIT_SIZE); /* just sent command */ if (chunks->len == 0) { g_debug("no data, just sending command %s [0x%x]", fu_synaptics_mst_updc_cmd_to_string(rc_cmd), rc_cmd); return fu_synaptics_mst_device_rc_send_command_and_wait(self, rc_cmd, error); } /* read each chunk */ for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint8 buf2[4] = {0}; g_debug("reading chunk of 0x%x bytes at offset 0x%x", fu_chunk_get_data_sz(chk), fu_chunk_get_address(chk)); /* write offset */ fu_memwrite_uint32(buf2, fu_chunk_get_address(chk), G_LITTLE_ENDIAN); if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), FU_SYNAPTICS_MST_REG_RC_OFFSET, buf2, sizeof(buf2), FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failed to write offset: "); return FALSE; } /* write length */ fu_memwrite_uint32(buf2, fu_chunk_get_data_sz(chk), G_LITTLE_ENDIAN); if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), FU_SYNAPTICS_MST_REG_RC_LEN, buf2, sizeof(buf2), FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failed to write length: "); return FALSE; } /* send command */ g_debug("data, sending command %s [0x%x]", fu_synaptics_mst_updc_cmd_to_string(rc_cmd), rc_cmd); if (!fu_synaptics_mst_device_rc_send_command_and_wait(self, rc_cmd, error)) return FALSE; /* read data */ if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), FU_SYNAPTICS_MST_REG_RC_DATA, fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failed to read data: "); return FALSE; } } /* success */ return TRUE; } static gboolean fu_synaptics_mst_device_disable_rc(FuSynapticsMstDevice *self, GError **error) { g_autoptr(GError) error_local = NULL; /* in test mode */ if (fu_udev_device_get_dev(FU_UDEV_DEVICE(self)) == NULL) return TRUE; /* in test mode */ if (fu_udev_device_get_dev(FU_UDEV_DEVICE(self)) == NULL) return TRUE; if (!fu_synaptics_mst_device_rc_set_command(self, FU_SYNAPTICS_MST_UPDC_CMD_DISABLE_RC, 0, NULL, 0, &error_local)) { /* ignore disabled */ if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) return TRUE; g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to disable remote control: "); return FALSE; } return TRUE; } static gboolean fu_synaptics_mst_device_enable_rc(FuSynapticsMstDevice *self, GError **error) { const gchar *sc = "PRIUS"; /* in test mode */ if (fu_udev_device_get_dev(FU_UDEV_DEVICE(self)) == NULL) return TRUE; if (!fu_synaptics_mst_device_disable_rc(self, error)) { g_prefix_error(error, "failed to disable-to-enable: "); return FALSE; } if (!fu_synaptics_mst_device_rc_set_command(self, FU_SYNAPTICS_MST_UPDC_CMD_ENABLE_RC, 0, (guint8 *)sc, strlen(sc), error)) { g_prefix_error(error, "failed to enable remote control: "); return FALSE; } return TRUE; } static gboolean fu_synaptics_mst_device_rc_special_get_command(FuSynapticsMstDevice *self, FuSynapticsMstUpdcCmd rc_cmd, guint32 cmd_offset, guint8 *cmd_data, gsize cmd_datasz, guint8 *buf, gsize bufsz, GError **error) { if (cmd_datasz > 0) { guint8 buf2[4] = {0}; /* write cmd data */ if (cmd_data != NULL) { if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), FU_SYNAPTICS_MST_REG_RC_DATA, cmd_data, cmd_datasz, FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "Failed to write command data: "); return FALSE; } } /* write offset */ fu_memwrite_uint32(buf2, cmd_offset, G_LITTLE_ENDIAN); if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), FU_SYNAPTICS_MST_REG_RC_OFFSET, buf2, sizeof(buf2), FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failed to write offset: "); return FALSE; } /* write length */ fu_memwrite_uint32(buf2, cmd_datasz, G_LITTLE_ENDIAN); if (!fu_dpaux_device_write(FU_DPAUX_DEVICE(self), FU_SYNAPTICS_MST_REG_RC_LEN, buf2, sizeof(buf2), FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failed to write length: "); return FALSE; } } /* send command */ g_debug("sending command 0x%x", rc_cmd); if (!fu_synaptics_mst_device_rc_send_command_and_wait(self, rc_cmd, error)) return FALSE; if (bufsz > 0) { if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), FU_SYNAPTICS_MST_REG_RC_DATA, buf, bufsz, FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failed to read length: "); return FALSE; } } /* success */ return TRUE; } static gboolean fu_synaptics_mst_device_get_flash_checksum(FuSynapticsMstDevice *self, guint32 offset, guint32 length, guint32 *checksum, GError **error) { guint8 buf[4] = {0}; g_return_val_if_fail(length > 0, FALSE); if (!fu_synaptics_mst_device_rc_special_get_command( self, FU_SYNAPTICS_MST_UPDC_CMD_CAL_EEPROM_CHECKSUM, offset, NULL, length, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to get flash checksum: "); return FALSE; } *checksum = fu_memread_uint32(buf, G_LITTLE_ENDIAN); return TRUE; } static gboolean fu_synaptics_mst_device_set_flash_sector_erase(FuSynapticsMstDevice *self, guint16 rc_cmd, guint16 offset, GError **error) { guint8 buf[2] = {0}; /* Need to add Wp control ? */ fu_memwrite_uint16(buf, rc_cmd + offset, G_LITTLE_ENDIAN); if (!fu_synaptics_mst_device_rc_set_command(self, FU_SYNAPTICS_MST_UPDC_CMD_FLASH_ERASE, 0, buf, sizeof(buf), error)) { g_prefix_error(error, "can't sector erase flash at offset %x: ", offset); return FALSE; } return TRUE; } typedef struct { GBytes *fw; FuChunkArray *chunks; FuProgress *progress; guint8 bank_to_update; guint32 checksum; } FuSynapticsMstDeviceHelper; static void fu_synaptics_mst_device_helper_free(FuSynapticsMstDeviceHelper *helper) { if (helper->chunks != NULL) g_object_unref(helper->chunks); if (helper->fw != NULL) g_bytes_unref(helper->fw); if (helper->progress != NULL) g_object_unref(helper->progress); g_free(helper); } static FuSynapticsMstDeviceHelper * fu_synaptics_mst_device_helper_new(void) { FuSynapticsMstDeviceHelper *helper = g_new0(FuSynapticsMstDeviceHelper, 1); helper->bank_to_update = BANKTAG_1; return helper; } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuSynapticsMstDeviceHelper, fu_synaptics_mst_device_helper_free) static gboolean fu_synaptics_mst_device_update_esm_cb(FuDevice *device, gpointer user_data, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); FuSynapticsMstDeviceHelper *helper = (FuSynapticsMstDeviceHelper *)user_data; guint32 flash_checksum = 0; /* erase ESM firmware; erase failure is fatal */ for (guint32 j = 0; j < 4; j++) { if (!fu_synaptics_mst_device_set_flash_sector_erase(self, FLASH_SECTOR_ERASE_64K, j + 4, error)) { g_prefix_error(error, "failed to erase sector %u: ", j); return FALSE; } } g_debug("waiting for flash clear to settle"); fu_device_sleep(FU_DEVICE(self), FLASH_SETTLE_TIME); /* write firmware */ fu_progress_set_id(helper->progress, G_STRLOC); fu_progress_set_steps(helper->progress, fu_chunk_array_length(helper->chunks)); for (guint i = 0; i < fu_chunk_array_length(helper->chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(helper->chunks, i); g_autoptr(GError) error_local = NULL; if (!fu_synaptics_mst_device_rc_set_command( self, FU_SYNAPTICS_MST_UPDC_CMD_WRITE_TO_EEPROM, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), &error_local)) { g_warning("failed to write ESM: %s", error_local->message); break; } fu_progress_step_done(helper->progress); } /* check ESM checksum */ if (!fu_synaptics_mst_device_get_flash_checksum(self, EEPROM_ESM_OFFSET, ESM_CODE_SIZE, &flash_checksum, error)) return FALSE; /* ESM update done */ if (helper->checksum != flash_checksum) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum 0x%x mismatched 0x%x", flash_checksum, helper->checksum); return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_mst_device_update_esm(FuSynapticsMstDevice *self, GBytes *fw, FuProgress *progress, GError **error) { guint32 flash_checksum = 0; g_autoptr(FuSynapticsMstDeviceHelper) helper = fu_synaptics_mst_device_helper_new(); /* ESM checksum same */ helper->fw = fu_bytes_new_offset(fw, EEPROM_ESM_OFFSET, ESM_CODE_SIZE, error); if (helper->fw == NULL) return FALSE; helper->checksum = fu_sum32_bytes(helper->fw); if (!fu_synaptics_mst_device_get_flash_checksum(self, EEPROM_ESM_OFFSET, ESM_CODE_SIZE, &flash_checksum, error)) { return FALSE; } if (helper->checksum == flash_checksum) { g_debug("ESM checksum already matches"); return TRUE; } g_debug("ESM checksum %x doesn't match expected %x", flash_checksum, helper->checksum); helper->progress = g_object_ref(progress); helper->chunks = fu_chunk_array_new_from_bytes(helper->fw, EEPROM_ESM_OFFSET, BLOCK_UNIT); return fu_device_retry(FU_DEVICE(self), fu_synaptics_mst_device_update_esm_cb, MAX_RETRY_COUNTS, helper, error); } static gboolean fu_synaptics_mst_device_update_tesla_leaf_firmware_cb(FuDevice *device, gpointer user_data, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); FuSynapticsMstDeviceHelper *helper = (FuSynapticsMstDeviceHelper *)user_data; guint32 flash_checksum = 0; if (!fu_synaptics_mst_device_set_flash_sector_erase(self, 0xffff, 0, error)) return FALSE; g_debug("waiting for flash clear to settle"); fu_device_sleep(FU_DEVICE(self), FLASH_SETTLE_TIME); fu_progress_set_steps(helper->progress, fu_chunk_array_length(helper->chunks)); for (guint i = 0; i < fu_chunk_array_length(helper->chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(helper->chunks, i); g_autoptr(GError) error_local = NULL; if (!fu_synaptics_mst_device_rc_set_command( self, FU_SYNAPTICS_MST_UPDC_CMD_WRITE_TO_EEPROM, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), &error_local)) { g_warning("Failed to write flash offset 0x%04x: %s, retrying", fu_chunk_get_address(chk), error_local->message); /* repeat once */ if (!fu_synaptics_mst_device_rc_set_command( self, FU_SYNAPTICS_MST_UPDC_CMD_WRITE_TO_EEPROM, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "can't write flash offset 0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } } fu_progress_step_done(helper->progress); } /* check data just written */ if (!fu_synaptics_mst_device_get_flash_checksum(self, 0, g_bytes_get_size(helper->fw), &flash_checksum, error)) return FALSE; if (helper->checksum != flash_checksum) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum 0x%x mismatched 0x%x", flash_checksum, helper->checksum); return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_mst_device_update_tesla_leaf_firmware(FuSynapticsMstDevice *self, GBytes *fw, FuProgress *progress, GError **error) { g_autoptr(FuSynapticsMstDeviceHelper) helper = fu_synaptics_mst_device_helper_new(); helper->fw = g_bytes_ref(fw); helper->checksum = fu_sum32_bytes(fw); helper->progress = g_object_ref(progress); helper->chunks = fu_chunk_array_new_from_bytes(fw, 0x0, BLOCK_UNIT); return fu_device_retry(FU_DEVICE(self), fu_synaptics_mst_device_update_tesla_leaf_firmware_cb, MAX_RETRY_COUNTS, helper, error); } static gboolean fu_synaptics_mst_device_get_active_bank_panamera(FuSynapticsMstDevice *self, GError **error) { guint32 buf[16]; /* get used bank */ if (!fu_synaptics_mst_device_rc_get_command(self, FU_SYNAPTICS_MST_UPDC_CMD_READ_FROM_MEMORY, (gint)0x20010c, (guint8 *)buf, ((sizeof(buf) / sizeof(buf[0])) * 4), error)) { g_prefix_error(error, "get active bank failed: "); return FALSE; } if ((buf[0] & BIT(7)) || (buf[0] & BIT(30))) self->active_bank = BANKTAG_1; else self->active_bank = BANKTAG_0; return TRUE; } static gboolean fu_synaptics_mst_device_update_panamera_firmware_cb(FuDevice *device, gpointer user_data, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); FuSynapticsMstDeviceHelper *helper = (FuSynapticsMstDeviceHelper *)user_data; guint32 erase_offset = helper->bank_to_update * 2; guint32 flash_checksum; /* erase storage */ if (!fu_synaptics_mst_device_set_flash_sector_erase(self, FLASH_SECTOR_ERASE_64K, erase_offset++, error)) return FALSE; if (!fu_synaptics_mst_device_set_flash_sector_erase(self, FLASH_SECTOR_ERASE_64K, erase_offset, error)) return FALSE; g_debug("waiting for flash clear to settle"); fu_device_sleep(FU_DEVICE(self), FLASH_SETTLE_TIME); /* write */ fu_progress_set_steps(helper->progress, fu_chunk_array_length(helper->chunks)); for (guint i = 0; i < fu_chunk_array_length(helper->chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(helper->chunks, i); g_autoptr(GError) error_local = NULL; if (!fu_synaptics_mst_device_rc_set_command( self, FU_SYNAPTICS_MST_UPDC_CMD_WRITE_TO_EEPROM, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), &error_local)) { g_warning("write failed: %s, retrying", error_local->message); /* repeat once */ if (!fu_synaptics_mst_device_rc_set_command( self, FU_SYNAPTICS_MST_UPDC_CMD_WRITE_TO_EEPROM, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "firmware write failed: "); return FALSE; } } fu_progress_step_done(helper->progress); } /* verify CRC */ for (guint32 i = 0; i < 4; i++) { guint8 buf[4] = {0}; fu_device_sleep(FU_DEVICE(self), 1); /* wait crc calculation */ if (!fu_synaptics_mst_device_rc_special_get_command( self, FU_SYNAPTICS_MST_UPDC_CMD_CAL_EEPROM_CHECK_CRC16, (EEPROM_BANK_OFFSET * helper->bank_to_update), NULL, g_bytes_get_size(helper->fw), buf, sizeof(buf), error)) { g_prefix_error(error, "failed to get flash checksum: "); return FALSE; } flash_checksum = fu_memread_uint32(buf, G_LITTLE_ENDIAN); } if (helper->checksum != flash_checksum) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum 0x%x mismatched 0x%x", flash_checksum, helper->checksum); return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_mst_device_update_panamera_set_new_valid_cb(FuDevice *device, gpointer user_data, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); FuSynapticsMstDeviceHelper *helper = (FuSynapticsMstDeviceHelper *)user_data; guint8 buf[16] = {0x0}; guint8 buf_verify[16] = {0x0}; g_autoptr(GDateTime) dt = g_date_time_new_now_utc(); buf[0] = helper->bank_to_update; buf[1] = g_date_time_get_month(dt); buf[2] = g_date_time_get_day_of_month(dt); buf[3] = g_date_time_get_year(dt) - 2000; buf[4] = (helper->checksum >> 8) & 0xff; buf[5] = helper->checksum & 0xff; buf[15] = fu_synaptics_mst_calculate_crc8(0, buf, sizeof(buf) - 1); g_debug("tag date %x %x %x crc %x %x %x %x", buf[1], buf[2], buf[3], buf[0], buf[4], buf[5], buf[15]); if (!fu_synaptics_mst_device_rc_set_command( self, FU_SYNAPTICS_MST_UPDC_CMD_WRITE_TO_EEPROM, (EEPROM_BANK_OFFSET * helper->bank_to_update + EEPROM_TAG_OFFSET), buf, sizeof(buf), error)) { g_prefix_error(error, "failed to write tag: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 1); /* ms */ if (!fu_synaptics_mst_device_rc_get_command( self, FU_SYNAPTICS_MST_UPDC_CMD_READ_FROM_EEPROM, (EEPROM_BANK_OFFSET * helper->bank_to_update + EEPROM_TAG_OFFSET), buf_verify, sizeof(buf_verify), error)) { g_prefix_error(error, "failed to read tag: "); return FALSE; } if (memcmp(buf, buf_verify, sizeof(buf)) != 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "set tag valid fail"); return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_mst_device_update_panamera_set_old_invalid_cb(FuDevice *device, gpointer user_data, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); FuSynapticsMstDeviceHelper *helper = (FuSynapticsMstDeviceHelper *)user_data; guint8 checksum_tmp = 0; guint8 checksum_nul = 0; /* CRC8 is not 0xff, erase last 4k of bank# */ if (helper->checksum != 0xff) { guint32 erase_offset = (EEPROM_BANK_OFFSET * self->active_bank + EEPROM_BANK_OFFSET - 0x1000) / 0x1000; g_debug("erasing offset 0x%x", erase_offset); if (!fu_synaptics_mst_device_set_flash_sector_erase(self, FLASH_SECTOR_ERASE_4K, erase_offset, error)) return FALSE; } /* set CRC8 to 0x00 */ if (!fu_synaptics_mst_device_rc_set_command( self, FU_SYNAPTICS_MST_UPDC_CMD_WRITE_TO_EEPROM, (EEPROM_BANK_OFFSET * self->active_bank + EEPROM_TAG_OFFSET + 15), &checksum_nul, sizeof(checksum_nul), error)) { g_prefix_error(error, "failed to clear CRC: "); return FALSE; } if (!fu_synaptics_mst_device_rc_get_command( self, FU_SYNAPTICS_MST_UPDC_CMD_READ_FROM_EEPROM, (EEPROM_BANK_OFFSET * self->active_bank + EEPROM_TAG_OFFSET + 15), &checksum_tmp, sizeof(checksum_tmp), error)) { g_prefix_error(error, "failed to read CRC from flash: "); return FALSE; } if (checksum_tmp != checksum_nul) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "set tag invalid fail, got 0x%x and expected 0x%x", checksum_tmp, checksum_nul); return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_mst_device_update_panamera_firmware(FuSynapticsMstDevice *self, GBytes *fw, FuProgress *progress, GError **error) { guint32 fw_size = 0; guint8 checksum8 = 0; g_autoptr(FuSynapticsMstDeviceHelper) helper = fu_synaptics_mst_device_helper_new(); /* get used bank */ if (!fu_synaptics_mst_device_get_active_bank_panamera(self, error)) return FALSE; if (self->active_bank == BANKTAG_1) helper->bank_to_update = BANKTAG_0; g_debug("bank to update:%x", helper->bank_to_update); /* get firmware size */ if (!fu_memread_uint32_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), 0x400, &fw_size, G_BIG_ENDIAN, error)) return FALSE; fw_size += 0x410; if (fw_size > PANAMERA_FIRMWARE_SIZE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid firmware size 0x%x", fw_size); return FALSE; } /* current max firmware size is 104K */ if (fw_size < g_bytes_get_size(fw)) fw_size = PANAMERA_FIRMWARE_SIZE; g_debug("calculated fw size as %u", fw_size); helper->fw = fu_bytes_new_offset(fw, 0x0, fw_size, error); if (helper->fw == NULL) return FALSE; helper->checksum = fu_synaptics_mst_calculate_crc16(0, g_bytes_get_data(helper->fw, NULL), g_bytes_get_size(helper->fw)); helper->progress = g_object_ref(progress); helper->chunks = fu_chunk_array_new_from_bytes(helper->fw, EEPROM_BANK_OFFSET * helper->bank_to_update, BLOCK_UNIT); if (!fu_device_retry_full(FU_DEVICE(self), fu_synaptics_mst_device_update_panamera_firmware_cb, MAX_RETRY_COUNTS, 2, /* ms */ helper, error)) return FALSE; /* set bank_to_update tag valid */ if (!fu_device_retry(FU_DEVICE(self), fu_synaptics_mst_device_update_panamera_set_new_valid_cb, MAX_RETRY_COUNTS, helper, error)) return FALSE; /* set active_bank tag invalid */ if (!fu_synaptics_mst_device_rc_get_command( self, FU_SYNAPTICS_MST_UPDC_CMD_READ_FROM_EEPROM, (EEPROM_BANK_OFFSET * self->active_bank + EEPROM_TAG_OFFSET + 15), &checksum8, sizeof(checksum8), error)) { g_prefix_error(error, "failed to read tag from flash: "); return FALSE; } helper->checksum = checksum8; return fu_device_retry(FU_DEVICE(self), fu_synaptics_mst_device_update_panamera_set_old_invalid_cb, MAX_RETRY_COUNTS, helper, error); } static gboolean fu_synaptics_mst_device_panamera_prepare_write(FuSynapticsMstDevice *self, GError **error) { guint32 buf[4] = {0}; /* Need to detect flash mode and ESM first ? */ /* disable flash Quad mode and ESM/HDCP2.2*/ /* disable ESM first */ buf[0] = 0x21; if (!fu_synaptics_mst_device_rc_set_command(self, FU_SYNAPTICS_MST_UPDC_CMD_WRITE_TO_MEMORY, (gint)REG_ESM_DISABLE, (guint8 *)buf, 4, error)) { g_prefix_error(error, "ESM disable failed: "); return FALSE; } /* wait for ESM exit */ fu_device_sleep(FU_DEVICE(self), 1); /* ms */ /* disable QUAD mode */ if (!fu_synaptics_mst_device_rc_get_command(self, FU_SYNAPTICS_MST_UPDC_CMD_READ_FROM_MEMORY, (gint)REG_QUAD_DISABLE, (guint8 *)buf, ((sizeof(buf) / sizeof(buf[0])) * 4), error)) { g_prefix_error(error, "quad query failed: "); return FALSE; } buf[0] = 0x00; if (!fu_synaptics_mst_device_rc_set_command(self, FU_SYNAPTICS_MST_UPDC_CMD_WRITE_TO_MEMORY, (gint)REG_QUAD_DISABLE, (guint8 *)buf, 4, error)) { g_prefix_error(error, "quad disable failed: "); return FALSE; } /* disable HDCP2.2 */ if (!fu_synaptics_mst_device_rc_get_command(self, FU_SYNAPTICS_MST_UPDC_CMD_READ_FROM_MEMORY, (gint)REG_HDCP22_DISABLE, (guint8 *)buf, 4, error)) { g_prefix_error(error, "HDCP query failed: "); return FALSE; } buf[0] = buf[0] & (~BIT(2)); if (!fu_synaptics_mst_device_rc_set_command(self, FU_SYNAPTICS_MST_UPDC_CMD_WRITE_TO_MEMORY, (gint)REG_HDCP22_DISABLE, (guint8 *)buf, 4, error)) { g_prefix_error(error, "HDCP disable failed: "); return FALSE; } return TRUE; } static gboolean fu_synaptics_mst_device_update_cayenne_firmware_cb(FuDevice *device, gpointer user_data, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); FuSynapticsMstDeviceHelper *helper = (FuSynapticsMstDeviceHelper *)user_data; guint32 flash_checksum; guint8 buf[4] = {0}; if (!fu_synaptics_mst_device_set_flash_sector_erase(self, 0xffff, 0, error)) return FALSE; g_debug("waiting for flash clear to settle"); fu_device_sleep(FU_DEVICE(self), FLASH_SETTLE_TIME); fu_progress_set_steps(helper->progress, fu_chunk_array_length(helper->chunks)); for (guint i = 0; i < fu_chunk_array_length(helper->chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(helper->chunks, i); g_autoptr(GError) error_local = NULL; if (!fu_synaptics_mst_device_rc_set_command( self, FU_SYNAPTICS_MST_UPDC_CMD_WRITE_TO_EEPROM, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), &error_local)) { g_warning("Failed to write flash offset 0x%04x: %s, retrying", fu_chunk_get_address(chk), error_local->message); /* repeat once */ if (!fu_synaptics_mst_device_rc_set_command( self, FU_SYNAPTICS_MST_UPDC_CMD_WRITE_TO_EEPROM, fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "can't write flash offset 0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } } fu_progress_step_done(helper->progress); } /* verify CRC */ if (!fu_synaptics_mst_device_rc_special_get_command( self, FU_SYNAPTICS_MST_UPDC_CMD_CAL_EEPROM_CHECK_CRC16, 0, NULL, g_bytes_get_size(helper->fw), buf, sizeof(buf), error)) { g_prefix_error(error, "Failed to get flash checksum: "); return FALSE; } flash_checksum = fu_memread_uint32(buf, G_LITTLE_ENDIAN); if (helper->checksum != flash_checksum) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum 0x%x mismatched 0x%x", flash_checksum, helper->checksum); return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_mst_device_update_cayenne_firmware(FuSynapticsMstDevice *self, GBytes *fw, FuProgress *progress, GError **error) { g_autoptr(FuSynapticsMstDeviceHelper) helper = fu_synaptics_mst_device_helper_new(); helper->fw = fu_bytes_new_offset(fw, 0x0, CAYENNE_FIRMWARE_SIZE, error); if (helper->fw == NULL) return FALSE; helper->checksum = fu_synaptics_mst_calculate_crc16(0, g_bytes_get_data(helper->fw, NULL), g_bytes_get_size(helper->fw)); helper->progress = g_object_ref(progress); helper->chunks = fu_chunk_array_new_from_bytes(helper->fw, 0x0, BLOCK_UNIT); if (!fu_device_retry(FU_DEVICE(self), fu_synaptics_mst_device_update_cayenne_firmware_cb, MAX_RETRY_COUNTS, helper, error)) return FALSE; if (!fu_synaptics_mst_device_rc_set_command(self, FU_SYNAPTICS_MST_UPDC_CMD_ACTIVATE_FIRMWARE, 0, NULL, 0, error)) { g_prefix_error(error, "active firmware failed: "); return FALSE; } return TRUE; } static gboolean fu_synaptics_mst_device_restart(FuSynapticsMstDevice *self, GError **error) { guint8 buf[4] = {0xF5, 0, 0, 0}; gint offset; g_autoptr(GError) error_local = NULL; switch (self->family) { case FU_SYNAPTICS_MST_FAMILY_TESLA: case FU_SYNAPTICS_MST_FAMILY_LEAF: case FU_SYNAPTICS_MST_FAMILY_PANAMERA: offset = 0x2000FC; break; case FU_SYNAPTICS_MST_FAMILY_CAYENNE: case FU_SYNAPTICS_MST_FAMILY_SPYDER: offset = 0x2020021C; break; default: g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unsupported chip family"); return FALSE; } /* issue the reboot command, ignore return code (triggers before returning) */ if (!fu_synaptics_mst_device_rc_set_command(self, FU_SYNAPTICS_MST_UPDC_CMD_WRITE_TO_MEMORY, offset, buf, sizeof(buf), &error_local)) g_debug("failed to restart: %s", error_local->message); return TRUE; } static FuFirmware * fu_synaptics_mst_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); g_autoptr(FuFirmware) firmware = fu_synaptics_mst_firmware_new(); /* check firmware and board ID match */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; if ((flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) == 0 && !fu_device_has_private_flag(device, FU_SYNAPTICS_MST_DEVICE_FLAG_IGNORE_BOARD_ID)) { guint16 board_id = fu_synaptics_mst_firmware_get_board_id(FU_SYNAPTICS_MST_FIRMWARE(firmware)); if (board_id != self->board_id) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "board ID mismatch, got 0x%04x, expected 0x%04x", board_id, self->board_id); return NULL; } } return fu_firmware_new_from_bytes(fw); } static gboolean fu_synaptics_mst_device_attach(FuDevice *device, FuProgress *progress, GError **error) { /* some devices do not use a GPIO to reset the chip */ if (fu_device_has_private_flag(device, FU_SYNAPTICS_MST_DEVICE_FLAG_MANUAL_RESTART_REQUIRED)) { g_autoptr(FwupdRequest) request = fwupd_request_new(); fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REPLUG_POWER); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); if (!fu_device_emit_request(device, request, progress, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } /* success */ return TRUE; } static gboolean fu_synaptics_mst_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 10, NULL); fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* enable remote control and disable on exit */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SKIPS_RESTART)) { locker = fu_device_locker_new_full(self, (FuDeviceLockerFunc)fu_synaptics_mst_device_enable_rc, (FuDeviceLockerFunc)fu_synaptics_mst_device_restart, error); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); fu_device_set_remove_delay(FU_DEVICE(self), 10000); /* a long time */ } else { locker = fu_device_locker_new_full( self, (FuDeviceLockerFunc)fu_synaptics_mst_device_enable_rc, (FuDeviceLockerFunc)fu_synaptics_mst_device_disable_rc, error); } if (locker == NULL) return FALSE; /* update firmware */ switch (self->family) { case FU_SYNAPTICS_MST_FAMILY_TESLA: case FU_SYNAPTICS_MST_FAMILY_LEAF: if (!fu_synaptics_mst_device_update_tesla_leaf_firmware(self, fw, progress, error)) { g_prefix_error(error, "Firmware update failed: "); return FALSE; } break; case FU_SYNAPTICS_MST_FAMILY_PANAMERA: if (!fu_synaptics_mst_device_panamera_prepare_write(self, error)) { g_prefix_error(error, "Failed to prepare for write: "); return FALSE; } if (!fu_synaptics_mst_device_update_esm(self, fw, progress, error)) { g_prefix_error(error, "ESM update failed: "); return FALSE; } if (!fu_synaptics_mst_device_update_panamera_firmware(self, fw, progress, error)) { g_prefix_error(error, "Firmware update failed: "); return FALSE; } break; case FU_SYNAPTICS_MST_FAMILY_CAYENNE: case FU_SYNAPTICS_MST_FAMILY_SPYDER: if (!fu_synaptics_mst_device_update_cayenne_firmware(self, fw, progress, error)) { g_prefix_error(error, "Firmware update failed: "); return FALSE; } break; default: g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unsupported chip family"); return FALSE; } fu_progress_step_done(progress); /* wait for flash clear to settle */ fu_device_sleep_full(device, 2000, fu_progress_get_child(progress)); /* ms */ fu_progress_step_done(progress); return TRUE; } FuSynapticsMstDevice * fu_synaptics_mst_device_new(FuDpauxDevice *device) { FuSynapticsMstDevice *self = g_object_new(FU_TYPE_SYNAPTICS_MST_DEVICE, NULL); if (device != NULL) fu_device_incorporate(FU_DEVICE(self), FU_DEVICE(device)); return self; } static gboolean fu_synaptics_mst_device_ensure_board_id(FuSynapticsMstDevice *self, GError **error) { gint offset; guint8 buf[2] = {0x0}; /* in test mode we need to open a different file node instead */ if (fu_udev_device_get_dev(FU_UDEV_DEVICE(self)) == NULL) { g_autofree gchar *filename = NULL; g_autofree gchar *dirname = NULL; gint fd; dirname = g_path_get_dirname(fu_udev_device_get_device_file(FU_UDEV_DEVICE(self))); filename = g_strdup_printf("%s/remote/%s_eeprom", dirname, fu_device_get_logical_id(FU_DEVICE(self))); if (!g_file_test(filename, G_FILE_TEST_EXISTS)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no device exists %s", filename); return FALSE; } fd = open(filename, O_RDONLY); if (fd == -1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, "cannot open device %s", filename); return FALSE; } if (read(fd, buf, 2) != 2) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "error reading EEPROM file %s", filename); close(fd); return FALSE; } self->board_id = fu_memread_uint16(buf, G_BIG_ENDIAN); close(fd); return TRUE; } switch (self->family) { case FU_SYNAPTICS_MST_FAMILY_TESLA: case FU_SYNAPTICS_MST_FAMILY_LEAF: case FU_SYNAPTICS_MST_FAMILY_PANAMERA: offset = (gint)ADDR_MEMORY_CUSTOMER_ID; break; case FU_SYNAPTICS_MST_FAMILY_CAYENNE: offset = (gint)ADDR_MEMORY_CUSTOMER_ID_CAYENNE; break; case FU_SYNAPTICS_MST_FAMILY_SPYDER: offset = (gint)ADDR_MEMORY_CUSTOMER_ID_SPYDER; break; default: g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unsupported chip family"); return FALSE; } /* get board ID via MCU address 0x170E instead of flash access due to HDCP2.2 running */ if (!fu_synaptics_mst_device_rc_get_command(self, FU_SYNAPTICS_MST_UPDC_CMD_READ_FROM_MEMORY, offset, buf, sizeof(buf), error)) { g_prefix_error(error, "memory query failed: "); return FALSE; } self->board_id = fu_memread_uint16(buf, G_BIG_ENDIAN); return TRUE; } void fu_synaptics_mst_device_set_system_type(FuSynapticsMstDevice *self, const gchar *system_type) { g_return_if_fail(FU_IS_SYNAPTICS_MST_DEVICE(self)); self->system_type = g_strdup(system_type); } static gboolean fu_synaptics_mst_device_setup(FuDevice *device, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); FuDevice *parent; const gchar *name_family; const gchar *name_parent = NULL; guint8 buf_ver[3] = {0x0}; guint8 buf_cid[2] = {0x0}; guint8 rc_cap = 0x0; g_autofree gchar *guid0 = NULL; g_autofree gchar *guid1 = NULL; g_autofree gchar *guid2 = NULL; g_autofree gchar *guid3 = NULL; g_autofree gchar *name = NULL; g_autofree gchar *version = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; /* not a correct device */ if (fu_dpaux_device_get_dpcd_ieee_oui(FU_DPAUX_DEVICE(device)) != SYNAPTICS_IEEE_OUI) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "not a supported OUI, got 0x%x", fu_dpaux_device_get_dpcd_ieee_oui(FU_DPAUX_DEVICE(device))); return FALSE; } /* read support for remote control */ if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), FU_SYNAPTICS_MST_REG_RC_CAP, &rc_cap, sizeof(rc_cap), FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failed to read remote control capabilities: "); return FALSE; } if ((rc_cap & 0x04) == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no support for remote control"); return FALSE; } /* enable remote control and disable on exit */ locker = fu_device_locker_new_full(self, (FuDeviceLockerFunc)fu_synaptics_mst_device_enable_rc, (FuDeviceLockerFunc)fu_synaptics_mst_device_disable_rc, &error_local); if (locker == NULL) { if (g_strcmp0(fu_device_get_name(device), "DPMST") == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "downstream endpoint not supported"); } else { g_propagate_error(error, g_steal_pointer(&error_local)); } return FALSE; } /* read firmware version: the third byte is vendor-specific usage */ if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), REG_FIRMWARE_VERSION, buf_ver, sizeof(buf_ver), FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failed to read firmware version: "); return FALSE; } version = g_strdup_printf("%1d.%02d.%02d", buf_ver[0], buf_ver[1], buf_ver[2]); fu_device_set_version(FU_DEVICE(self), version); /* read board chip_id */ if (!fu_dpaux_device_read(FU_DPAUX_DEVICE(self), REG_CHIP_ID, buf_cid, sizeof(buf_cid), FU_SYNAPTICS_MST_DEVICE_READ_TIMEOUT, error)) { g_prefix_error(error, "failed to read chip id: "); return FALSE; } self->chip_id = fu_memread_uint16(buf_cid, G_BIG_ENDIAN); if (self->chip_id == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid chip ID"); return FALSE; } self->family = fu_synaptics_mst_family_from_chip_id(self->chip_id); /* VMM >= 6 use RSA2048 */ switch (self->family) { case FU_SYNAPTICS_MST_FAMILY_TESLA: case FU_SYNAPTICS_MST_FAMILY_LEAF: case FU_SYNAPTICS_MST_FAMILY_PANAMERA: fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); break; case FU_SYNAPTICS_MST_FAMILY_CAYENNE: case FU_SYNAPTICS_MST_FAMILY_SPYDER: fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); break; default: g_warning("family 0x%02x does not indicate unsigned/signed payload", self->family); break; } /* check the active bank for debugging */ if (self->family == FU_SYNAPTICS_MST_FAMILY_PANAMERA) { if (!fu_synaptics_mst_device_get_active_bank_panamera(self, error)) return FALSE; } /* read board ID */ if (!fu_synaptics_mst_device_ensure_board_id(self, error)) return FALSE; parent = fu_device_get_parent(FU_DEVICE(self)); if (parent != NULL) name_parent = fu_device_get_name(parent); if (name_parent != NULL) { name = g_strdup_printf("VMM%04x inside %s", self->chip_id, name_parent); } else { name = g_strdup_printf("VMM%04x", self->chip_id); } fu_device_set_name(FU_DEVICE(self), name); /* set up the device name and kind via quirks */ guid0 = g_strdup_printf("MST-%u", self->board_id); fu_device_add_instance_id(FU_DEVICE(self), guid0); /* requires user to do something */ if (fu_device_has_private_flag(device, FU_SYNAPTICS_MST_DEVICE_FLAG_MANUAL_RESTART_REQUIRED)) { fu_device_set_remove_delay(device, FU_DEVICE_REMOVE_DELAY_USER_REPLUG); } /* this is a host system, use system ID */ name_family = fu_synaptics_mst_family_to_string(self->family); if (g_strcmp0(self->device_kind, "system") == 0) { g_autofree gchar *guid = NULL; guid = g_strdup_printf("MST-%s-%s-%u", name_family, self->system_type, self->board_id); fu_device_add_instance_id(FU_DEVICE(self), guid); /* docks or something else */ } else if (self->device_kind != NULL) { g_auto(GStrv) templates = NULL; templates = g_strsplit(self->device_kind, ",", -1); for (guint i = 0; templates[i] != NULL; i++) { g_autofree gchar *dock_id1 = NULL; g_autofree gchar *dock_id2 = NULL; dock_id1 = g_strdup_printf("MST-%s-%u", templates[i], self->board_id); fu_device_add_instance_id(FU_DEVICE(self), dock_id1); dock_id2 = g_strdup_printf("MST-%s-vmm%04x-%u", templates[i], self->chip_id, self->board_id); fu_device_add_instance_id(FU_DEVICE(self), dock_id2); } } else { /* devices are explicit opt-in */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ignoring %s device with no SynapticsMstDeviceKind quirk", guid0); return FALSE; } /* detect chip family */ switch (self->family) { case FU_SYNAPTICS_MST_FAMILY_TESLA: fu_device_set_firmware_size_max(device, 0x10000); fu_device_set_firmware_size_max(device, 0x10000); break; case FU_SYNAPTICS_MST_FAMILY_PANAMERA: fu_device_set_firmware_size_max(device, 0x80000); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE); break; default: break; } /* add non-standard GUIDs */ guid1 = g_strdup_printf("MST-%s-vmm%04x-%u", name_family, self->chip_id, self->board_id); fu_device_add_instance_id(FU_DEVICE(self), guid1); guid2 = g_strdup_printf("MST-%s-%u", name_family, self->board_id); fu_device_add_instance_id(FU_DEVICE(self), guid2); guid3 = g_strdup_printf("MST-%s", name_family); fu_device_add_instance_id_full(FU_DEVICE(self), guid3, FU_DEVICE_INSTANCE_FLAG_QUIRKS); /* whitebox customers */ if ((self->board_id >> 8) == 0x0) fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_ENFORCE_REQUIRES); return TRUE; } static gboolean fu_synaptics_mst_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuSynapticsMstDevice *self = FU_SYNAPTICS_MST_DEVICE(device); if (g_strcmp0(key, "SynapticsMstDeviceKind") == 0) { self->device_kind = g_strdup(value); return TRUE; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_synaptics_mst_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 45, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 54, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reload"); } static void fu_synaptics_mst_device_class_init(FuSynapticsMstDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_synaptics_mst_device_finalize; klass_device->to_string = fu_synaptics_mst_device_to_string; klass_device->set_quirk_kv = fu_synaptics_mst_device_set_quirk_kv; klass_device->setup = fu_synaptics_mst_device_setup; klass_device->write_firmware = fu_synaptics_mst_device_write_firmware; klass_device->attach = fu_synaptics_mst_device_attach; klass_device->prepare_firmware = fu_synaptics_mst_device_prepare_firmware; klass_device->set_progress = fu_synaptics_mst_device_set_progress; } fwupd-1.9.16/plugins/synaptics-mst/fu-synaptics-mst-device.h000066400000000000000000000010271460375044200240750ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_SYNAPTICS_MST_DEVICE (fu_synaptics_mst_device_get_type()) G_DECLARE_FINAL_TYPE(FuSynapticsMstDevice, fu_synaptics_mst_device, FU, SYNAPTICS_MST_DEVICE, FuDpauxDevice) FuSynapticsMstDevice * fu_synaptics_mst_device_new(FuDpauxDevice *device); void fu_synaptics_mst_device_set_system_type(FuSynapticsMstDevice *self, const gchar *system_type); fwupd-1.9.16/plugins/synaptics-mst/fu-synaptics-mst-firmware.c000066400000000000000000000056061460375044200244540ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-mst-firmware.h" struct _FuSynapticsMstFirmware { FuFirmwareClass parent_instance; guint16 board_id; }; G_DEFINE_TYPE(FuSynapticsMstFirmware, fu_synaptics_mst_firmware, FU_TYPE_FIRMWARE) #define ADDR_CUSTOMER_ID 0x10E guint16 fu_synaptics_mst_firmware_get_board_id(FuSynapticsMstFirmware *self) { g_return_val_if_fail(FU_IS_SYNAPTICS_MST_FIRMWARE(self), 0); return self->board_id; } static void fu_synaptics_mst_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuSynapticsMstFirmware *self = FU_SYNAPTICS_MST_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "board_id", self->board_id); } static gboolean fu_synaptics_mst_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuSynapticsMstFirmware *self = FU_SYNAPTICS_MST_FIRMWARE(firmware); const guint8 *buf; gsize bufsz; buf = g_bytes_get_data(fw, &bufsz); if (!fu_memread_uint16_safe(buf, bufsz, ADDR_CUSTOMER_ID, &self->board_id, G_BIG_ENDIAN, error)) return FALSE; /* success */ return TRUE; } static GByteArray * fu_synaptics_mst_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) blob = NULL; /* assumed header */ fu_byte_array_set_size(buf, ADDR_CUSTOMER_ID + sizeof(guint16), 0x00); if (!fu_memwrite_uint16_safe(buf->data, buf->len, ADDR_CUSTOMER_ID, fu_firmware_get_idx(firmware), G_BIG_ENDIAN, error)) return NULL; /* payload */ blob = fu_firmware_get_bytes_with_patches(firmware, error); if (blob == NULL) return NULL; fu_byte_array_append_bytes(buf, blob); /* success */ return g_steal_pointer(&buf); } static gboolean fu_synaptics_rmi_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuSynapticsMstFirmware *self = FU_SYNAPTICS_MST_FIRMWARE(firmware); guint64 tmp; /* optional properties */ tmp = xb_node_query_text_as_uint(n, "board_id", NULL); if (tmp != G_MAXUINT64) self->board_id = tmp; /* success */ return TRUE; } static void fu_synaptics_mst_firmware_init(FuSynapticsMstFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_NO_AUTO_DETECTION); } static void fu_synaptics_mst_firmware_class_init(FuSynapticsMstFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_synaptics_mst_firmware_parse; klass_firmware->export = fu_synaptics_mst_firmware_export; klass_firmware->write = fu_synaptics_mst_firmware_write; klass_firmware->build = fu_synaptics_rmi_firmware_build; } FuFirmware * fu_synaptics_mst_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_SYNAPTICS_MST_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/synaptics-mst/fu-synaptics-mst-firmware.h000066400000000000000000000007571460375044200244630ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_SYNAPTICS_MST_FIRMWARE (fu_synaptics_mst_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuSynapticsMstFirmware, fu_synaptics_mst_firmware, FU, SYNAPTICS_MST_FIRMWARE, FuFirmware) FuFirmware * fu_synaptics_mst_firmware_new(void); guint16 fu_synaptics_mst_firmware_get_board_id(FuSynapticsMstFirmware *self); fwupd-1.9.16/plugins/synaptics-mst/fu-synaptics-mst-plugin.c000066400000000000000000000060371460375044200241350ustar00rootroot00000000000000/* * Copyright (C) 2017 Mario Limonciello * Copyright (C) 2017 Peichen Huang * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-mst-device.h" #include "fu-synaptics-mst-firmware.h" #include "fu-synaptics-mst-plugin.h" struct _FuSynapticsMstPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuSynapticsMstPlugin, fu_synaptics_mst_plugin, FU_TYPE_PLUGIN) static gboolean fu_synaptics_mst_plugin_backend_device_added(FuPlugin *plugin, FuDevice *device, FuProgress *progress, GError **error) { FuSynapticsMstPlugin *self = FU_SYNAPTICS_MST_PLUGIN(plugin); FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuSynapticsMstDevice) dev = NULL; g_autoptr(GError) error_local = NULL; /* interesting device? */ if (!FU_IS_DPAUX_DEVICE(device)) return TRUE; /* for SynapticsMstDeviceKind=system devices */ dev = fu_synaptics_mst_device_new(FU_DPAUX_DEVICE(device)); fu_synaptics_mst_device_set_system_type( dev, fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_PRODUCT_SKU)); /* open */ locker = fu_device_locker_new(dev, &error_local); if (locker == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED) || g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_READ)) { g_debug("no device found: %s", error_local->message); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } fu_plugin_device_add(FU_PLUGIN(self), FU_DEVICE(dev)); return TRUE; } static gboolean fu_synaptics_mst_plugin_write_firmware(FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; if (!fu_device_write_firmware(device, blob_fw, progress, flags, error)) return FALSE; if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SKIPS_RESTART)) fu_plugin_device_remove(plugin, device); return TRUE; } static void fu_synaptics_mst_plugin_init(FuSynapticsMstPlugin *self) { } static void fu_synaptics_mst_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "SynapticsMstDeviceKind"); fu_plugin_add_udev_subsystem(plugin, "drm"); /* used for uevent only */ fu_plugin_add_device_udev_subsystem(plugin, "drm_dp_aux_dev"); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_SYNAPTICS_MST_FIRMWARE); } static void fu_synaptics_mst_plugin_class_init(FuSynapticsMstPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_synaptics_mst_plugin_constructed; plugin_class->write_firmware = fu_synaptics_mst_plugin_write_firmware; plugin_class->backend_device_added = fu_synaptics_mst_plugin_backend_device_added; } fwupd-1.9.16/plugins/synaptics-mst/fu-synaptics-mst-plugin.h000066400000000000000000000004301460375044200241310ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuSynapticsMstPlugin, fu_synaptics_mst_plugin, FU, SYNAPTICS_MST_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/synaptics-mst/fu-synaptics-mst.rs000066400000000000000000000023551460375044200230420ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum SynapticsMstFamily { Unknown, Tesla, Leaf, Panamera, Cayenne, Spyder, } #[derive(ToString)] enum SynapticsMstUpdcRc { Success, Invalid, Unsupported, Failed, Disabled, ConfigureSignFailed, FirmwareSignFailed, RollbackFailed, } #[derive(ToString)] enum SynapticsMstUpdcCmd { EnableRc = 0x01, DisableRc = 0x02, GetId = 0x03, GetVersion = 0x04, FlashMapping = 0x07, EnableFlashChipErase = 0x08, CalEepromChecksum = 0x11, FlashErase = 0x14, CalEepromCheckCrc8 = 0x16, CalEepromCheckCrc16 = 0x17, ActivateFirmware = 0x18, WriteToEeprom = 0x20, WriteToMemory = 0x21, WriteToTxDpcd = 0x22, // TX0 WriteToTxDpcdTx1 = 0x23, WriteToTxDpcdTx2 = 0x24, WriteToTxDpcdTx3 = 0x25, ReadFromEeprom = 0x30, ReadFromMemory = 0x31, ReadFromTxDpcd = 0x32, // TX0 ReadFromTxDpcdTx1 = 0x33, ReadFromTxDpcdTx2 = 0x34, ReadFromTxDpcdTx3 = 0x35, } enum SynapticsMstRegRc { Cap = 0x4B0, State = 0x4B1, Cmd = 0x4B2, Result = 0x4B3, Len = 0x4B8, Offset = 0x4BC, Data = 0x4C0, } fwupd-1.9.16/plugins/synaptics-mst/meson.build000066400000000000000000000033101460375044200214000ustar00rootroot00000000000000if get_option('plugin_synaptics_mst').require(gudev.found(), error_message: 'gudev is needed for plugin_synaptics_mst').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsMST"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('synaptics-mst.quirk') plugin_builtin_synaptics_mst = static_library('fu_plugin_synaptics_mst', rustgen.process( 'fu-synaptics-mst.rs', # fuzzing ), sources: [ 'fu-synaptics-mst-plugin.c', 'fu-synaptics-mst-common.c', 'fu-synaptics-mst-device.c', 'fu-synaptics-mst-firmware.c', # fuzzing ], include_directories: plugin_incdirs, c_args: [ cargs, ], link_with: plugin_libs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_synaptics_mst if get_option('tests') install_data(['tests/synaptics-mst.builder.xml'], install_dir: join_paths(installed_test_datadir, 'tests')) env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) env.set('FWUPD_LOCALSTATEDIR', '/tmp/fwupd-self-test/var') env.set('FWUPD_DATADIR_QUIRKS', meson.current_source_dir()) e = executable( 'synaptics-mst-self-test', rustgen.process('fu-synaptics-mst.rs'), sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: [ plugin_deps, valgrind, ], link_with: [ fwupd, fwupdplugin, plugin_builtin_synaptics_mst, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ cargs, '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('synaptics-mst-self-test', e, env: env) endif endif fwupd-1.9.16/plugins/synaptics-mst/synaptics-mst-evb.quirk000066400000000000000000000011601460375044200237040ustar00rootroot00000000000000# Synaptics MST early validation board support # # This is not installed by default, but can be used to exercise new boards # that don't yet have customer ID or board ID bytes filled out. # # To use it, load the quirk file into the quirks directory for the fwupd installation # Usually this is /usr/share/fwupd/quirks.d # # Note: The flag "ignore-board-id" will be used to ignore the board ID checking in # during flashing. This shouldn't be used in practice for production boards. # [MST-2] Name = Synaptics EVB development board SynapticsMstDeviceKind = panamera_evb [MST-panamera_evb-vmm5331-2] Flags = ignore-board-id fwupd-1.9.16/plugins/synaptics-mst/synaptics-mst.quirk000066400000000000000000000026661460375044200231460ustar00rootroot00000000000000# GUID generation for Synaptics MST plugin # # SYNAPTICSMST\BID is the 16 bit board ID which contains: # * Customer ID in first byte # * Board ID in the second byte # # SynapticsMstDeviceKind = system # * Will map to a GUID containing HwID product SKU # * These GUIDs will look like MST-${PRODUCTSKU}-${BOARDID} # SynapticsMstDeviceKind != system # * Will map to a GUID containing each comma delimited substring # * These GUIDs will look like MST-${DEVICEKIND}-${CHIPID}-${BOARDID} # # By default the Synaptics MST device will restart after update # To override this behavior add FWUPD_DEVICE_FLAG_SKIPS_RESTART # [MST-162] Name = Luxshare Quad USB4 Dock SynapticsMstDeviceKind = dock Flags = manual-restart-required [MST-272] Name = Dell X6 Platform SynapticsMstDeviceKind = system [MST-273] Name = Dell X7 Platform SynapticsMstDeviceKind = system [MST-274] Name = Dell WD15/TB16/TB18 wired Dock SynapticsMstDeviceKind = wd15,tb16,tb18 [MST-275] Name = Dell WLD15 Wireless Dock SynapticsMstDeviceKind = wld15 [MST-277] Name = Dell Rugged Platform SynapticsMstDeviceKind = system # ThinkPad Workstation Dock [MST-513] ParentGuid = USB\VID_17EF&PID_305A SynapticsMstDeviceKind = dock # ThinkPad Thunderbolt 3 Workstation Dock [MST-595] ParentGuid = TBT-01081720 SynapticsMstDeviceKind = dock [MST-596] Name = ThinkPad USB-C Dock Gen2 SynapticsMstDeviceKind = dock [MST-602] Name = ThinkPad Universal ThunderBolt 4 Dock SynapticsMstDeviceKind = dock fwupd-1.9.16/plugins/synaptics-mst/tests/000077500000000000000000000000001460375044200204035ustar00rootroot00000000000000fwupd-1.9.16/plugins/synaptics-mst/tests/dmi000077700000000000000000000000001460375044200267032../../../libfwupdplugin/tests/dmi/ustar00rootroot00000000000000fwupd-1.9.16/plugins/synaptics-mst/tests/synaptics-mst.bin000066400000000000000000000004241460375044200237130ustar00rootroot000000000000004davefwupd-1.9.16/plugins/synaptics-mst/tests/synaptics-mst.builder.xml000066400000000000000000000001761460375044200253740ustar00rootroot00000000000000 0x1234 0x42 ZGF2ZQ== fwupd-1.9.16/plugins/synaptics-prometheus/000077500000000000000000000000001460375044200206315ustar00rootroot00000000000000fwupd-1.9.16/plugins/synaptics-prometheus/README.md000066400000000000000000000024341460375044200221130ustar00rootroot00000000000000--- title: Plugin: Synaptics Prometheus --- ## Introduction This plugin can flash the firmware on the Synaptics Prometheus fingerprint readers. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format. The binary file has a vendor-specific header that is used when flashing the image. This plugin supports the following protocol ID: * `com.synaptics.prometheus` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_06CB&PID_00A9` * `USB\VID_06CB&PID_00A9-cfg` * `USB\VID_06CB&PID_00A9&CFG1_3483&CFG2_500` ## Update Behavior The device usually presents in runtime mode, but on detach re-enumerates with the same USB PID in an unlocked mode. On attach the device again re-enumerates back to the runtime locked mode. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x06CB` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.2.9`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Vincent Huang: @synavincent fwupd-1.9.16/plugins/synaptics-prometheus/data/000077500000000000000000000000001460375044200215425ustar00rootroot00000000000000fwupd-1.9.16/plugins/synaptics-prometheus/data/lsusb.txt000066400000000000000000000044351460375044200234410ustar00rootroot00000000000000Bus 001 Device 043: ID 06cb:00a9 Synaptics, Inc. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 255 Vendor Specific Class bDeviceSubClass 16 bDeviceProtocol 255 bMaxPacketSize0 8 idVendor 0x06cb Synaptics, Inc. idProduct 0x00a9 bcdDevice 0.00 iManufacturer 0 iProduct 0 iSerial 1 942cfe315551 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0027 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 3 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 4 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/synaptics-prometheus/fu-self-test.c000066400000000000000000000077421460375044200233250ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-plugin-private.h" #include "fu-synaprom-device.h" #include "fu-synaprom-firmware.h" static void fu_test_synaprom_firmware_func(void) { const gchar *ci = g_getenv("CI_NETWORK"); const guint8 *buf; gboolean ret; gsize sz = 0; g_autofree gchar *filename = NULL; g_autoptr(FuSynapromDevice) device = fu_synaprom_device_new(NULL); g_autoptr(GBytes) blob1 = NULL; g_autoptr(GBytes) blob2 = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error = NULL; g_autoptr(FuFirmware) firmware2 = NULL; g_autoptr(FuFirmware) firmware = fu_synaprom_firmware_new(); filename = g_test_build_filename(G_TEST_DIST, "tests", "test.pkg", NULL); if (!g_file_test(filename, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing test.pkg"); return; } fw = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(fw); buf = g_bytes_get_data(fw, &sz); g_assert_cmpint(sz, ==, 294); g_assert_cmpint(buf[0], ==, 0x01); g_assert_cmpint(buf[1], ==, 0x00); ret = fu_firmware_parse(firmware, fw, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); /* does not exist */ blob1 = fu_firmware_get_image_by_id_bytes(firmware, "NotGoingToExist", NULL); g_assert_null(blob1); blob1 = fu_firmware_get_image_by_id_bytes(firmware, "cfg-update-header", NULL); g_assert_null(blob1); /* header needs to exist */ blob1 = fu_firmware_get_image_by_id_bytes(firmware, "mfw-update-header", &error); g_assert_no_error(error); g_assert_nonnull(blob1); buf = g_bytes_get_data(blob1, &sz); g_assert_cmpint(sz, ==, 24); g_assert_cmpint(buf[0], ==, 0x41); g_assert_cmpint(buf[1], ==, 0x00); g_assert_cmpint(buf[2], ==, 0x00); g_assert_cmpint(buf[3], ==, 0x00); g_assert_cmpint(buf[4], ==, 0xff); /* payload needs to exist */ fu_synaprom_device_set_version(device, 10, 1, 1234); firmware2 = fu_synaprom_device_prepare_fw(FU_DEVICE(device), fw, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(firmware2); blob2 = fu_firmware_get_image_by_id_bytes(firmware2, "mfw-update-payload", &error); g_assert_no_error(error); g_assert_nonnull(blob2); buf = g_bytes_get_data(blob2, &sz); g_assert_cmpint(sz, ==, 2); g_assert_cmpint(buf[0], ==, 'R'); g_assert_cmpint(buf[1], ==, 'H'); } static void fu_synaprom_firmware_xml_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *csum1 = NULL; g_autofree gchar *csum2 = NULL; g_autofree gchar *xml_out = NULL; g_autofree gchar *xml_src = NULL; g_autoptr(FuFirmware) firmware1 = fu_synaprom_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_synaprom_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "synaptics-prometheus.builder.xml", NULL); ret = g_file_get_contents(filename, &xml_src, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_build_from_xml(firmware1, xml_src, &error); g_assert_no_error(error); g_assert_true(ret); csum1 = fu_firmware_get_checksum(firmware1, G_CHECKSUM_SHA1, &error); g_assert_no_error(error); g_assert_cmpstr(csum1, ==, "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"); /* ensure we can round-trip */ xml_out = fu_firmware_export_to_xml(firmware1, FU_FIRMWARE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); ret = fu_firmware_build_from_xml(firmware2, xml_out, &error); g_assert_no_error(error); g_assert_true(ret); csum2 = fu_firmware_get_checksum(firmware2, G_CHECKSUM_SHA1, &error); g_assert_cmpstr(csum1, ==, csum2); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_test_add_func("/synaprom/firmware", fu_test_synaprom_firmware_func); g_test_add_func("/synaprom/firmware{xml}", fu_synaprom_firmware_xml_func); return g_test_run(); } fwupd-1.9.16/plugins/synaptics-prometheus/fu-synaprom-common.c000066400000000000000000000052211460375044200245430ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-synaprom-common.h" enum { FU_SYNAPROM_RESULT_OK = 0, FU_SYNAPROM_RESULT_GEN_OPERATION_CANCELED = 103, FU_SYNAPROM_RESULT_GEN_INVALID = 110, FU_SYNAPROM_RESULT_GEN_BAD_PARAM = 111, FU_SYNAPROM_RESULT_GEN_NULL_POINTER = 112, FU_SYNAPROM_RESULT_GEN_UNEXPECTED_FORMAT = 114, FU_SYNAPROM_RESULT_GEN_TIMEOUT = 117, FU_SYNAPROM_RESULT_GEN_OBJECT_DOESNT_EXIST = 118, FU_SYNAPROM_RESULT_GEN_ERROR = 119, FU_SYNAPROM_RESULT_SENSOR_MALFUNCTIONED = 202, FU_SYNAPROM_RESULT_SYS_OUT_OF_MEMORY = 602, }; GByteArray * fu_synaprom_request_new(guint8 cmd, const guint8 *buf, gsize bufsz) { GByteArray *blob = g_byte_array_new(); fu_byte_array_append_uint8(blob, cmd); if (buf != NULL) g_byte_array_append(blob, buf, bufsz); return blob; } GByteArray * fu_synaprom_reply_new(gsize cmdlen) { GByteArray *blob = g_byte_array_new(); fu_byte_array_set_size(blob, cmdlen, 0x00); return blob; } gboolean fu_synaprom_error_from_status(guint16 status, GError **error) { if (status == FU_SYNAPROM_RESULT_OK) return TRUE; switch (status) { case FU_SYNAPROM_RESULT_GEN_OPERATION_CANCELED: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "cancelled"); break; case FU_SYNAPROM_RESULT_GEN_BAD_PARAM: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "bad parameter"); break; case FU_SYNAPROM_RESULT_GEN_NULL_POINTER: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "NULL pointer"); break; case FU_SYNAPROM_RESULT_GEN_UNEXPECTED_FORMAT: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "unexpected format"); break; case FU_SYNAPROM_RESULT_GEN_TIMEOUT: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "timed out"); break; case FU_SYNAPROM_RESULT_GEN_OBJECT_DOESNT_EXIST: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "object does not exist"); break; case FU_SYNAPROM_RESULT_GEN_ERROR: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "generic error"); break; case FU_SYNAPROM_RESULT_SENSOR_MALFUNCTIONED: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, "sensor malfunctioned"); break; case FU_SYNAPROM_RESULT_SYS_OUT_OF_MEMORY: g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_AGAIN, "out of heap memory"); break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "error status: 0x%x", status); } return FALSE; } fwupd-1.9.16/plugins/synaptics-prometheus/fu-synaprom-common.h000066400000000000000000000005751460375044200245570ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include GByteArray * fu_synaprom_request_new(guint8 cmd, const guint8 *buf, gsize bufsz); GByteArray * fu_synaprom_reply_new(gsize cmdlen); gboolean fu_synaprom_error_from_status(guint16 status, GError **error); fwupd-1.9.16/plugins/synaptics-prometheus/fu-synaprom-config.c000066400000000000000000000221731460375044200245250ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaprom-common.h" #include "fu-synaprom-config.h" #include "fu-synaprom-firmware.h" #include "fu-synaprom-struct.h" struct _FuSynapromConfig { FuDevice parent_instance; guint32 configid1; /* config ID1 */ guint32 configid2; /* config ID2 */ }; #define FU_SYNAPROM_CMD_IOTA_FIND_FLAGS_ALLIOTAS 0x0001 /* itype ignored*/ #define FU_SYNAPROM_CMD_IOTA_FIND_FLAGS_READMAX 0x0002 /* nbytes ignored */ #define FU_SYNAPROM_MAX_IOTA_READ_SIZE (64 * 1024) /* max size of iota data returned */ #define FU_SYNAPROM_IOTA_ITYPE_CONFIG_VERSION 0x0009 /* Configuration id and version */ G_DEFINE_TYPE(FuSynapromConfig, fu_synaprom_config, FU_TYPE_DEVICE) static gboolean fu_synaprom_config_setup(FuDevice *device, GError **error) { FuDevice *parent = fu_device_get_parent(device); FuSynapromConfig *self = FU_SYNAPROM_CONFIG(device); g_autofree gchar *configid1_str = NULL; g_autofree gchar *configid2_str = NULL; g_autofree gchar *version = NULL; g_autoptr(GByteArray) reply = NULL; g_autoptr(GByteArray) request = NULL; g_autoptr(GByteArray) st_cfg = NULL; g_autoptr(GByteArray) st_hdr = NULL; g_autoptr(GByteArray) st_cmd = fu_struct_synaprom_cmd_iota_find_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* get IOTA */ fu_struct_synaprom_cmd_iota_find_set_itype(st_cmd, FU_SYNAPROM_IOTA_ITYPE_CONFIG_VERSION); fu_struct_synaprom_cmd_iota_find_set_flags(st_cmd, FU_SYNAPROM_CMD_IOTA_FIND_FLAGS_READMAX); request = fu_synaprom_request_new(FU_SYNAPROM_CMD_IOTA_FIND, st_cmd->data, st_cmd->len); reply = fu_synaprom_reply_new(FU_STRUCT_SYNAPROM_REPLY_IOTA_FIND_HDR_SIZE + FU_SYNAPROM_MAX_IOTA_READ_SIZE); if (!fu_synaprom_device_cmd_send(FU_SYNAPROM_DEVICE(parent), request, reply, progress, 5000, error)) return FALSE; if (reply->len < FU_STRUCT_SYNAPROM_REPLY_IOTA_FIND_HDR_SIZE + FU_STRUCT_SYNAPROM_IOTA_CONFIG_VERSION_SIZE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CFG return data invalid size: 0x%04x", reply->len); return FALSE; } st_hdr = fu_struct_synaprom_reply_iota_find_hdr_parse(reply->data, reply->len, 0x0, error); if (st_hdr == NULL) return FALSE; if (fu_struct_synaprom_reply_iota_find_hdr_get_itype(st_hdr) != FU_SYNAPROM_IOTA_ITYPE_CONFIG_VERSION) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CFG iota had invalid itype: 0x%04x", fu_struct_synaprom_reply_iota_find_hdr_get_itype(st_hdr)); return FALSE; } st_cfg = fu_struct_synaprom_iota_config_version_parse(reply->data, reply->len, st_hdr->len, error); if (st_cfg == NULL) return FALSE; self->configid1 = fu_struct_synaprom_iota_config_version_get_config_id1(st_cfg); self->configid2 = fu_struct_synaprom_iota_config_version_get_config_id2(st_cfg); /* we should have made these a %08% uint32_t... */ configid1_str = g_strdup_printf("%u", self->configid1); configid2_str = g_strdup_printf("%u", self->configid2); /* append the configid to the generated GUID */ fu_device_add_instance_str(device, "CFG1", configid1_str); fu_device_add_instance_str(device, "CFG2", configid2_str); if (!fu_device_build_instance_id(device, error, "USB", "VID", "PID", "CFG1", "CFG2", NULL)) return FALSE; /* no downgrades are allowed */ version = g_strdup_printf("%04u", fu_struct_synaprom_iota_config_version_get_version(st_cfg)); fu_device_set_version(FU_DEVICE(self), version); fu_device_set_version_lowest(FU_DEVICE(self), version); return TRUE; } static FuFirmware * fu_synaprom_config_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG(device); FuDevice *parent = fu_device_get_parent(device); g_autoptr(GByteArray) st_hdr = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(FuFirmware) firmware = fu_synaprom_firmware_new(); if (fu_synaprom_device_get_product_type(FU_SYNAPROM_DEVICE(parent)) == FU_SYNAPROM_PRODUCT_TYPE_TRITON) { if (!fu_synaprom_firmware_set_signature_size(FU_SYNAPROM_FIRMWARE(firmware), FU_SYNAPROM_FIRMWARE_TRITON_SIGSIZE)) return NULL; } /* parse the firmware */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* check the update header product and version */ blob = fu_firmware_get_image_by_id_bytes(firmware, "cfg-update-header", error); if (blob == NULL) return NULL; st_hdr = fu_struct_synaprom_cfg_hdr_parse_bytes(blob, 0x0, error); if (st_hdr == NULL) { g_prefix_error(error, "CFG metadata is invalid: "); return NULL; } if (fu_struct_synaprom_cfg_hdr_get_product(st_hdr) != FU_SYNAPROM_PRODUCT_PROMETHEUS) { if (flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) { g_warning("CFG metadata not compatible, " "got 0x%02x expected 0x%02x", fu_struct_synaprom_cfg_hdr_get_product(st_hdr), (guint)FU_SYNAPROM_PRODUCT_PROMETHEUS); } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CFG metadata not compatible, " "got 0x%02x expected 0x%02x", fu_struct_synaprom_cfg_hdr_get_product(st_hdr), (guint)FU_SYNAPROM_PRODUCT_PROMETHEUS); return NULL; } } if (fu_struct_synaprom_cfg_hdr_get_id1(st_hdr) != self->configid1 || fu_struct_synaprom_cfg_hdr_get_id2(st_hdr) != self->configid2) { if (flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) { g_warning("CFG version not compatible, " "got %u:%u expected %u:%u", fu_struct_synaprom_cfg_hdr_get_id1(st_hdr), fu_struct_synaprom_cfg_hdr_get_id2(st_hdr), self->configid1, self->configid2); } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "CFG version not compatible, " "got %u:%u expected %u:%u", fu_struct_synaprom_cfg_hdr_get_id1(st_hdr), fu_struct_synaprom_cfg_hdr_get_id2(st_hdr), self->configid1, self->configid2); return NULL; } } /* success */ return g_steal_pointer(&firmware); } static gboolean fu_synaprom_config_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDevice *parent = fu_device_get_parent(device); g_autoptr(GBytes) fw = NULL; /* get default image */ fw = fu_firmware_get_image_by_id_bytes(firmware, "cfg-update-payload", error); if (fw == NULL) return FALSE; /* I assume the CFG/MFW difference is detected in the device...*/ return fu_synaprom_device_write_fw(FU_SYNAPROM_DEVICE(parent), fw, progress, error); } static void fu_synaprom_config_init(FuSynapromConfig *self) { fu_device_add_protocol(FU_DEVICE(self), "com.synaptics.prometheus.config"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_logical_id(FU_DEVICE(self), "cfg"); fu_device_set_name(FU_DEVICE(self), "Prometheus IOTA Config"); fu_device_set_summary(FU_DEVICE(self), "Fingerprint reader config"); fu_device_add_icon(FU_DEVICE(self), "touchpad-disabled"); } static void fu_synaprom_config_constructed(GObject *obj) { FuSynapromConfig *self = FU_SYNAPROM_CONFIG(obj); FuDevice *parent = fu_device_get_parent(FU_DEVICE(self)); g_autofree gchar *devid = NULL; /* append the firmware kind to the generated GUID */ devid = g_strdup_printf("USB\\VID_%04X&PID_%04X-cfg", fu_usb_device_get_vid(FU_USB_DEVICE(parent)), fu_usb_device_get_pid(FU_USB_DEVICE(parent))); fu_device_add_instance_id(FU_DEVICE(self), devid); G_OBJECT_CLASS(fu_synaprom_config_parent_class)->constructed(obj); } static gboolean fu_synaprom_config_attach(FuDevice *device, FuProgress *progress, GError **error) { FuDevice *parent = fu_device_get_parent(device); return fu_device_attach_full(parent, progress, error); } static gboolean fu_synaprom_config_detach(FuDevice *device, FuProgress *progress, GError **error) { FuDevice *parent = fu_device_get_parent(device); return fu_device_detach_full(parent, progress, error); } static void fu_synaprom_config_class_init(FuSynapromConfigClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->constructed = fu_synaprom_config_constructed; klass_device->write_firmware = fu_synaprom_config_write_firmware; klass_device->prepare_firmware = fu_synaprom_config_prepare_firmware; klass_device->setup = fu_synaprom_config_setup; klass_device->reload = fu_synaprom_config_setup; klass_device->attach = fu_synaprom_config_attach; klass_device->detach = fu_synaprom_config_detach; } FuSynapromConfig * fu_synaprom_config_new(FuSynapromDevice *device) { FuSynapromConfig *self; self = g_object_new(FU_TYPE_SYNAPROM_CONFIG, "parent", device, NULL); return FU_SYNAPROM_CONFIG(self); } fwupd-1.9.16/plugins/synaptics-prometheus/fu-synaprom-config.h000066400000000000000000000006711460375044200245310ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-synaprom-device.h" #define FU_TYPE_SYNAPROM_CONFIG (fu_synaprom_config_get_type()) G_DECLARE_FINAL_TYPE(FuSynapromConfig, fu_synaprom_config, FU, SYNAPROM_CONFIG, FuDevice) FuSynapromConfig * fu_synaprom_config_new(FuSynapromDevice *device); fwupd-1.9.16/plugins/synaptics-prometheus/fu-synaprom-device.c000066400000000000000000000402051460375044200245130ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaprom-common.h" #include "fu-synaprom-config.h" #include "fu-synaprom-device.h" #include "fu-synaprom-firmware.h" struct _FuSynapromDevice { FuUsbDevice parent_instance; guint8 vmajor; guint8 vminor; guint32 product_type; }; /* vendor-specific USB control requests to write DFT word (Hayes) */ #define FU_SYNAPROM_USB_CTRLREQUEST_VENDOR_WRITEDFT 21 /* endpoint addresses for command and fingerprint data */ #define FU_SYNAPROM_USB_REQUEST_EP 0x01 #define FU_SYNAPROM_USB_REPLY_EP 0x81 #define FU_SYNAPROM_USB_FINGERPRINT_EP 0x82 #define FU_SYNAPROM_USB_INTERRUPT_EP 0x83 /* le */ typedef struct __attribute__((packed)) { guint16 status; } FuSynapromReplyGeneric; /* le */ typedef struct __attribute__((packed)) { guint16 status; guint32 buildtime; /* Unix-style build time */ guint32 buildnum; /* build number */ guint8 vmajor; /* major version */ guint8 vminor; /* minor version */ guint8 target; /* target, e.g. VCSFW_TARGET_ROM */ guint8 product; /* product, e.g. VCSFW_PRODUCT_FALCON */ guint8 siliconrev; /* silicon revision */ guint8 formalrel; /* boolean: non-zero -> formal release */ guint8 platform; /* Platform (PCB) revision */ guint8 patch; /* patch level */ guint8 serial_number[6]; /* 48-bit Serial Number */ guint8 security[2]; /* bytes 0 and 1 of OTP */ guint32 patchsig; /* opaque patch signature */ guint8 iface; /* interface type, see below */ guint8 otpsig[3]; /* OTP Patch Signature */ guint16 otpspare1; /* spare space */ guint8 reserved; /* reserved byte */ guint8 device_type; /* device type */ } FuSynapromReplyGetVersion; /* the following bits describe security options in ** FuSynapromReplyGetVersion::security[1] bit-field */ #define FU_SYNAPROM_SECURITY1_PROD_SENSOR (1 << 5) G_DEFINE_TYPE(FuSynapromDevice, fu_synaprom_device, FU_TYPE_USB_DEVICE) gboolean fu_synaprom_device_cmd_send(FuSynapromDevice *device, GByteArray *request, GByteArray *reply, FuProgress *progress, guint timeout_ms, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); gboolean ret; gsize actual_len = 0; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 25, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 75, NULL); fu_dump_full(G_LOG_DOMAIN, "REQST", request->data, request->len, 16, FU_DUMP_FLAGS_SHOW_ADDRESSES); ret = g_usb_device_bulk_transfer(usb_device, FU_SYNAPROM_USB_REQUEST_EP, request->data, request->len, &actual_len, timeout_ms, NULL, error); if (!ret) { g_prefix_error(error, "failed to request: "); return FALSE; } if (actual_len < request->len) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only sent 0x%04x of 0x%04x", (guint)actual_len, request->len); return FALSE; } fu_progress_step_done(progress); ret = g_usb_device_bulk_transfer(usb_device, FU_SYNAPROM_USB_REPLY_EP, reply->data, reply->len, NULL, /* allowed to return short read */ timeout_ms, NULL, error); if (!ret) { g_prefix_error(error, "failed to reply: "); return FALSE; } fu_dump_full(G_LOG_DOMAIN, "REPLY", reply->data, actual_len, 16, FU_DUMP_FLAGS_SHOW_ADDRESSES); fu_progress_step_done(progress); /* parse as FuSynapromReplyGeneric */ if (reply->len >= sizeof(FuSynapromReplyGeneric)) { FuSynapromReplyGeneric *hdr = (FuSynapromReplyGeneric *)reply->data; return fu_synaprom_error_from_status(GUINT16_FROM_LE(hdr->status), error); } /* success */ return TRUE; } guint32 fu_synaprom_device_get_product_type(FuSynapromDevice *self) { return self->product_type; } void fu_synaprom_device_set_version(FuSynapromDevice *self, guint8 vmajor, guint8 vminor, guint32 buildnum) { g_autofree gchar *str = NULL; /* We decide to skip 10.02.xxxxxx firmware, so we force the minor version from 0x02 ** to 0x01 to make the devices with 0x02 minor version firmware allow to be updated ** back to minor version 0x01. */ if (vmajor == 0x0a && vminor == 0x02) { g_debug("quirking vminor from %02x to 01", vminor); vminor = 0x01; } /* set display version */ str = g_strdup_printf("%02u.%02u.%u", vmajor, vminor, buildnum); fu_device_set_version(FU_DEVICE(self), str); /* we need this for checking the firmware compatibility later */ self->vmajor = vmajor; self->vminor = vminor; } static void fu_synaprom_device_set_serial_number(FuSynapromDevice *self, guint64 serial_number) { g_autofree gchar *str = NULL; str = g_strdup_printf("%" G_GUINT64_FORMAT, serial_number); fu_device_set_serial(FU_DEVICE(self), str); } static gboolean fu_synaprom_device_setup(FuDevice *device, GError **error) { FuSynapromDevice *self = FU_SYNAPROM_DEVICE(device); FuSynapromReplyGetVersion pkt; guint32 product; guint64 serial_number = 0; g_autoptr(GByteArray) request = NULL; g_autoptr(GByteArray) reply = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_synaprom_device_parent_class)->setup(device, error)) return FALSE; /* get version */ request = fu_synaprom_request_new(FU_SYNAPROM_CMD_GET_VERSION, NULL, 0); reply = fu_synaprom_reply_new(sizeof(FuSynapromReplyGetVersion)); if (!fu_synaprom_device_cmd_send(self, request, reply, progress, 250, error)) { g_prefix_error(error, "failed to get version: "); return FALSE; } memcpy(&pkt, reply->data, sizeof(pkt)); product = GUINT32_FROM_LE(pkt.product); g_info("product ID is %u, version=%u.%u, buildnum=%u prod=%i", product, pkt.vmajor, pkt.vminor, GUINT32_FROM_LE(pkt.buildnum), pkt.security[1] & FU_SYNAPROM_SECURITY1_PROD_SENSOR); fu_synaprom_device_set_version(self, pkt.vmajor, pkt.vminor, GUINT32_FROM_LE(pkt.buildnum)); /* get serial number */ memcpy(&serial_number, pkt.serial_number, sizeof(pkt.serial_number)); fu_synaprom_device_set_serial_number(self, serial_number); /* check device type */ if (product == FU_SYNAPROM_PRODUCT_PROMETHEUS || product == FU_SYNAPROM_PRODUCT_TRITON) { fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else if (product == FU_SYNAPROM_PRODUCT_PROMETHEUSPBL || product == FU_SYNAPROM_PRODUCT_PROMETHEUSMSBL || product == FU_SYNAPROM_PRODUCT_TRITONPBL || product == FU_SYNAPROM_PRODUCT_TRITONMSBL) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "device %u is not supported by this plugin", product); return FALSE; } if (product == FU_SYNAPROM_PRODUCT_TRITON || product == FU_SYNAPROM_PRODUCT_TRITONPBL || product == FU_SYNAPROM_PRODUCT_TRITONMSBL) self->product_type = FU_SYNAPROM_PRODUCT_TYPE_TRITON; else self->product_type = FU_SYNAPROM_PRODUCT_TYPE_PROMETHEUS; /* add updatable config child, if this is a production sensor */ if (fu_device_get_children(device)->len == 0 && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER) && pkt.security[1] & FU_SYNAPROM_SECURITY1_PROD_SENSOR) { g_autoptr(FuSynapromConfig) cfg = fu_synaprom_config_new(self); fu_device_add_child(FU_DEVICE(device), FU_DEVICE(cfg)); } /* success */ return TRUE; } FuFirmware * fu_synaprom_device_prepare_fw(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { guint32 product_id; g_autoptr(FuFirmware) firmware = fu_synaprom_firmware_new(); FuSynapromDevice *self = FU_SYNAPROM_DEVICE(device); if (self->product_type == FU_SYNAPROM_PRODUCT_TYPE_TRITON) { if (!fu_synaprom_firmware_set_signature_size(FU_SYNAPROM_FIRMWARE(firmware), FU_SYNAPROM_FIRMWARE_TRITON_SIGSIZE)) return NULL; } /* check the update header product and version */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; product_id = fu_synaprom_firmware_get_product_id(FU_SYNAPROM_FIRMWARE(firmware)); if (product_id != FU_SYNAPROM_PRODUCT_PROMETHEUS && product_id != FU_SYNAPROM_PRODUCT_TRITON) { if (flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) { g_warning("MFW metadata not compatible, " "got 0x%02x expected 0x%02x or 0x%02x", product_id, (guint)FU_SYNAPROM_PRODUCT_PROMETHEUS, (guint)FU_SYNAPROM_PRODUCT_TRITON); } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "MFW metadata not compatible, " "got 0x%02x expected 0x%02x or 0x%02x", product_id, (guint)FU_SYNAPROM_PRODUCT_PROMETHEUS, (guint)FU_SYNAPROM_PRODUCT_TRITON); return NULL; } } /* success */ return g_steal_pointer(&firmware); } static gboolean fu_synaprom_device_write_chunks(FuSynapromDevice *self, GPtrArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { GByteArray *chunk = g_ptr_array_index(chunks, i); g_autoptr(GByteArray) request = NULL; g_autoptr(GByteArray) reply = NULL; /* patch */ request = fu_synaprom_request_new(FU_SYNAPROM_CMD_BOOTLDR_PATCH, chunk->data, chunk->len); reply = fu_synaprom_reply_new(sizeof(FuSynapromReplyGeneric)); if (!fu_synaprom_device_cmd_send(self, request, reply, fu_progress_get_child(progress), 20000, error)) return FALSE; fu_progress_step_done(progress); } /* success */ return TRUE; } gboolean fu_synaprom_device_write_fw(FuSynapromDevice *self, GBytes *fw, FuProgress *progress, GError **error) { const guint8 *buf; gsize bufsz = 0; gsize offset = 0; g_autoptr(GPtrArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99, NULL); /* collect chunks */ buf = g_bytes_get_data(fw, &bufsz); chunks = g_ptr_array_new_with_free_func((GDestroyNotify)g_byte_array_unref); while (offset != bufsz) { guint32 chunksz = 0; g_autofree guint8 *chunkbuf = NULL; g_autoptr(GByteArray) chunk = g_byte_array_new(); /* get chunk size */ if (!fu_memread_uint32_safe(buf, bufsz, offset, &chunksz, G_LITTLE_ENDIAN, error)) return FALSE; offset += sizeof(guint32); /* read out chunk */ chunkbuf = g_malloc0(chunksz); if (!fu_memcpy_safe(chunkbuf, chunksz, 0x0, /* dst */ buf, bufsz, offset, /* src */ chunksz, error)) return FALSE; offset += chunksz; /* add chunk */ g_byte_array_append(chunk, chunkbuf, chunksz); g_ptr_array_add(chunks, g_steal_pointer(&chunk)); } fu_progress_step_done(progress); /* write chunks */ if (!fu_synaprom_device_write_chunks(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success! */ return TRUE; } static gboolean fu_synaprom_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuSynapromDevice *self = FU_SYNAPROM_DEVICE(device); g_autoptr(GBytes) fw = NULL; /* get default image */ fw = fu_firmware_get_image_by_id_bytes(firmware, "mfw-update-payload", error); if (fw == NULL) return FALSE; return fu_synaprom_device_write_fw(self, fw, progress, error); } static gboolean fu_synaprom_device_attach(FuDevice *device, FuProgress *progress, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); gboolean ret; gsize actual_len = 0; guint8 data[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; /* sanity check */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } ret = g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_SYNAPROM_USB_CTRLREQUEST_VENDOR_WRITEDFT, 0x0000, 0x0000, data, sizeof(data), &actual_len, 2000, NULL, error); if (!ret) return FALSE; if (actual_len != sizeof(data)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only sent 0x%04x of 0x%04x", (guint)actual_len, (guint)sizeof(data)); return FALSE; } if (!g_usb_device_reset(usb_device, error)) { g_prefix_error(error, "failed to force-reset device: "); return FALSE; } fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_synaprom_device_detach(FuDevice *device, FuProgress *progress, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); gboolean ret; gsize actual_len = 0; guint8 data[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}; /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in bootloader mode, skipping"); return TRUE; } ret = g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_SYNAPROM_USB_CTRLREQUEST_VENDOR_WRITEDFT, 0x0000, 0x0000, data, sizeof(data), &actual_len, 2000, NULL, error); if (!ret) return FALSE; if (actual_len != sizeof(data)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "only sent 0x%04x of 0x%04x", (guint)actual_len, (guint)sizeof(data)); return FALSE; } fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_RESTART); if (!g_usb_device_reset(usb_device, error)) { g_prefix_error(error, "failed to force-reset device: "); return FALSE; } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static void fu_synaprom_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 96, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_synaprom_device_init(FuSynapromDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_RETRY_OPEN); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_protocol(FU_DEVICE(self), "com.synaptics.prometheus"); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_set_name(FU_DEVICE(self), "Prometheus"); fu_device_set_summary(FU_DEVICE(self), "Fingerprint reader"); fu_device_set_vendor(FU_DEVICE(self), "Synaptics"); fu_device_add_icon(FU_DEVICE(self), "touchpad-disabled"); fu_usb_device_add_interface(FU_USB_DEVICE(self), 0x0); } static void fu_synaprom_device_class_init(FuSynapromDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_synaprom_device_write_firmware; klass_device->prepare_firmware = fu_synaprom_device_prepare_fw; klass_device->setup = fu_synaprom_device_setup; klass_device->reload = fu_synaprom_device_setup; klass_device->attach = fu_synaprom_device_attach; klass_device->detach = fu_synaprom_device_detach; klass_device->set_progress = fu_synaprom_device_set_progress; } FuSynapromDevice * fu_synaprom_device_new(FuUsbDevice *device) { FuSynapromDevice *self; self = g_object_new(FU_TYPE_SYNAPROM_DEVICE, NULL); if (device != NULL) fu_device_incorporate(FU_DEVICE(self), FU_DEVICE(device)); return FU_SYNAPROM_DEVICE(self); } fwupd-1.9.16/plugins/synaptics-prometheus/fu-synaprom-device.h000066400000000000000000000040131460375044200245150ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_SYNAPROM_DEVICE (fu_synaprom_device_get_type()) G_DECLARE_FINAL_TYPE(FuSynapromDevice, fu_synaprom_device, FU, SYNAPROM_DEVICE, FuUsbDevice) #define FU_SYNAPROM_PRODUCT_PROMETHEUS 65 /* Prometheus (b1422) */ #define FU_SYNAPROM_PRODUCT_PROMETHEUSPBL 66 #define FU_SYNAPROM_PRODUCT_PROMETHEUSMSBL 67 #define FU_SYNAPROM_PRODUCT_TRITON 69 /* Triton */ #define FU_SYNAPROM_PRODUCT_TRITONPBL 70 #define FU_SYNAPROM_PRODUCT_TRITONMSBL 71 /** * Product type definitions */ #define FU_SYNAPROM_PRODUCT_TYPE_DENALI 0 #define FU_SYNAPROM_PRODUCT_TYPE_HAYES 1 #define FU_SYNAPROM_PRODUCT_TYPE_SHASTA 2 #define FU_SYNAPROM_PRODUCT_TYPE_STELLER 3 #define FU_SYNAPROM_PRODUCT_TYPE_WHITNEY 4 #define FU_SYNAPROM_PRODUCT_TYPE_PROMETHEUS 5 #define FU_SYNAPROM_PRODUCT_TYPE_PACIFIC_PEAK 6 #define FU_SYNAPROM_PRODUCT_TYPE_MORGAN 7 #define FU_SYNAPROM_PRODUCT_TYPE_OX6101 8 #define FU_SYNAPROM_PRODUCT_TYPE_TRITON 9 #define FU_SYNAPROM_CMD_GET_VERSION 0x01 #define FU_SYNAPROM_CMD_BOOTLDR_PATCH 0x7d #define FU_SYNAPROM_CMD_IOTA_FIND 0x8e FuSynapromDevice * fu_synaprom_device_new(FuUsbDevice *device); gboolean fu_synaprom_device_cmd_send(FuSynapromDevice *device, GByteArray *request, GByteArray *reply, FuProgress *progress, guint timeout_ms, GError **error); gboolean fu_synaprom_device_write_fw(FuSynapromDevice *self, GBytes *fw, FuProgress *progress, GError **error); /* for self tests */ void fu_synaprom_device_set_version(FuSynapromDevice *self, guint8 vmajor, guint8 vminor, guint32 buildnum); FuFirmware * fu_synaprom_device_prepare_fw(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error); guint32 fu_synaprom_device_get_product_type(FuSynapromDevice *self); fwupd-1.9.16/plugins/synaptics-prometheus/fu-synaprom-firmware.c000066400000000000000000000140051460375044200250670ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaprom-firmware.h" #include "fu-synaprom-struct.h" struct _FuSynapromFirmware { FuFirmware parent_instance; guint32 product_id; guint32 signature_size; }; G_DEFINE_TYPE(FuSynapromFirmware, fu_synaprom_firmware, FU_TYPE_FIRMWARE) /* use only first 12 bit of 16 bits as tag value */ #define FU_SYNAPROM_FIRMWARE_TAG_MAX 0xfff0 #define FU_SYNAPROM_FIRMWARE_COUNT_MAX 64 guint32 fu_synaprom_firmware_get_product_id(FuSynapromFirmware *self) { g_return_val_if_fail(FU_IS_SYNAPROM_FIRMWARE(self), 0x0); return self->product_id; } gboolean fu_synaprom_firmware_set_signature_size(FuSynapromFirmware *self, guint32 signature_size) { g_return_val_if_fail(FU_IS_SYNAPROM_FIRMWARE(self), FALSE); self->signature_size = signature_size; return TRUE; } static void fu_synaprom_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuSynapromFirmware *self = FU_SYNAPROM_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "product_id", self->product_id); } static gboolean fu_synaprom_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuSynapromFirmware *self = FU_SYNAPROM_FIRMWARE(firmware); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); if (bufsz < self->signature_size + FU_STRUCT_SYNAPROM_HDR_SIZE) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "blob is too small to be firmware"); return FALSE; } bufsz -= self->signature_size; /* parse each chunk */ while (offset < bufsz) { guint32 hdrsz; guint32 tag; g_autoptr(FuFirmware) img = NULL; g_autoptr(GByteArray) st_hdr = NULL; g_autoptr(GBytes) bytes = NULL; /* verify item header */ st_hdr = fu_struct_synaprom_hdr_parse(buf, bufsz, offset, error); if (st_hdr == NULL) return FALSE; tag = fu_struct_synaprom_hdr_get_tag(st_hdr); if (tag >= FU_SYNAPROM_FIRMWARE_TAG_MAX) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "tag 0x%04x is too large", tag); return FALSE; } /* sanity check */ img = fu_firmware_get_image_by_idx(firmware, tag, NULL); if (img != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "tag 0x%04x already present in image", tag); return FALSE; } hdrsz = fu_struct_synaprom_hdr_get_bufsz(st_hdr); if (hdrsz == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "empty header for tag 0x%04x", tag); return FALSE; } offset += st_hdr->len; bytes = fu_bytes_new_offset(fw, offset, hdrsz, error); if (bytes == NULL) return FALSE; g_debug("adding 0x%04x (%s) with size 0x%04x", tag, fu_synaprom_firmware_tag_to_string(tag), hdrsz); img = fu_firmware_new_from_bytes(bytes); fu_firmware_set_idx(img, tag); fu_firmware_set_id(img, fu_synaprom_firmware_tag_to_string(tag)); if (!fu_firmware_add_image_full(firmware, img, error)) return FALSE; /* metadata */ if (tag == FU_SYNAPROM_FIRMWARE_TAG_MFW_UPDATE_HEADER) { g_autofree gchar *version = NULL; g_autoptr(GByteArray) st_mfw = NULL; st_mfw = fu_struct_synaprom_mfw_hdr_parse(buf, bufsz, offset, error); if (st_mfw == NULL) return FALSE; self->product_id = fu_struct_synaprom_mfw_hdr_get_product(st_mfw); version = g_strdup_printf("%u.%u", fu_struct_synaprom_mfw_hdr_get_vmajor(st_mfw), fu_struct_synaprom_mfw_hdr_get_vminor(st_mfw)); fu_firmware_set_version(firmware, version); } /* next item */ offset += hdrsz; } return TRUE; } static GByteArray * fu_synaprom_firmware_write(FuFirmware *firmware, GError **error) { FuSynapromFirmware *self = FU_SYNAPROM_FIRMWARE(firmware); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) st_hdr = fu_struct_synaprom_hdr_new(); g_autoptr(GByteArray) st_mfw = fu_struct_synaprom_mfw_hdr_new(); g_autoptr(GBytes) payload = NULL; /* add header */ fu_struct_synaprom_hdr_set_tag(st_hdr, FU_SYNAPROM_FIRMWARE_TAG_MFW_UPDATE_HEADER); fu_struct_synaprom_hdr_set_bufsz(st_hdr, st_mfw->len); g_byte_array_append(buf, st_hdr->data, st_hdr->len); fu_struct_synaprom_mfw_hdr_set_product(st_mfw, self->product_id); g_byte_array_append(buf, st_mfw->data, st_mfw->len); /* add payload */ payload = fu_firmware_get_bytes_with_patches(firmware, error); if (payload == NULL) return NULL; fu_struct_synaprom_hdr_set_tag(st_hdr, FU_SYNAPROM_FIRMWARE_TAG_MFW_UPDATE_PAYLOAD); fu_struct_synaprom_hdr_set_bufsz(st_hdr, g_bytes_get_size(payload)); g_byte_array_append(buf, st_hdr->data, st_hdr->len); fu_byte_array_append_bytes(buf, payload); /* add signature */ for (guint i = 0; i < self->signature_size; i++) fu_byte_array_append_uint8(buf, 0xff); return g_steal_pointer(&buf); } static gboolean fu_synaprom_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuSynapromFirmware *self = FU_SYNAPROM_FIRMWARE(firmware); guint64 tmp; /* simple properties */ tmp = xb_node_query_text_as_uint(n, "product_id", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT32) self->product_id = tmp; /* success */ return TRUE; } static void fu_synaprom_firmware_init(FuSynapromFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); fu_firmware_set_images_max(FU_FIRMWARE(self), FU_SYNAPROM_FIRMWARE_COUNT_MAX); self->signature_size = FU_SYNAPROM_FIRMWARE_PROMETHEUS_SIGSIZE; } static void fu_synaprom_firmware_class_init(FuSynapromFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_synaprom_firmware_parse; klass_firmware->write = fu_synaprom_firmware_write; klass_firmware->export = fu_synaprom_firmware_export; klass_firmware->build = fu_synaprom_firmware_build; } FuFirmware * fu_synaprom_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_SYNAPROM_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/synaptics-prometheus/fu-synaprom-firmware.h000066400000000000000000000012411460375044200250720ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Synaptics Inc * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_SYNAPROM_FIRMWARE (fu_synaprom_firmware_get_type()) #define FU_SYNAPROM_FIRMWARE_PROMETHEUS_SIGSIZE 0x100 #define FU_SYNAPROM_FIRMWARE_TRITON_SIGSIZE 0x180 G_DECLARE_FINAL_TYPE(FuSynapromFirmware, fu_synaprom_firmware, FU, SYNAPROM_FIRMWARE, FuFirmware) FuFirmware * fu_synaprom_firmware_new(void); guint32 fu_synaprom_firmware_get_product_id(FuSynapromFirmware *self); gboolean fu_synaprom_firmware_set_signature_size(FuSynapromFirmware *self, guint32 signature_size); fwupd-1.9.16/plugins/synaptics-prometheus/fu-synaprom-plugin.c000066400000000000000000000021431460375044200245510ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaprom-device.h" #include "fu-synaprom-firmware.h" #include "fu-synaprom-plugin.h" struct _FuSynapromPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuSynapromPlugin, fu_synaprom_plugin, FU_TYPE_PLUGIN) static void fu_synaprom_plugin_init(FuSynapromPlugin *self) { } static void fu_synaprom_plugin_object_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_set_name(plugin, "synaptics_prometheus"); } static void fu_synaprom_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_SYNAPROM_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_SYNAPROM_FIRMWARE); } static void fu_synaprom_plugin_class_init(FuSynapromPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->constructed = fu_synaprom_plugin_object_constructed; plugin_class->constructed = fu_synaprom_plugin_constructed; } fwupd-1.9.16/plugins/synaptics-prometheus/fu-synaprom-plugin.h000066400000000000000000000003561460375044200245620ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuSynapromPlugin, fu_synaprom_plugin, FU, SYNAPROM_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/synaptics-prometheus/fu-synaprom.rs000066400000000000000000000032421460375044200234600ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, Parse)] struct SynapromMfwHdr { product: u32le, id: u32le = 0xFF, // MFW unique id used for compat verification buildtime: u32le = 0xFF, // unix-style buildnum: u32le = 0xFF, vmajor: u8 = 10, // major version vminor: u8 = 1, // minor version unused: [u8; 6], } #[derive(ToString)] #[repr(u16le)] enum SynapromFirmwareTag { MfwUpdateHeader = 0x0001, MfwUpdatePayload = 0x0002, CfgUpdateHeader = 0x0003, CfgUpdatePayload = 0x0004, } #[derive(New, Parse)] struct SynapromHdr { tag: SynapromFirmwareTag, bufsz: u32le, } #[derive(ParseBytes)] struct SynapromCfgHdr { product: u32le = 65, // Prometheus (b1422) id1: u32le, id2: u32le, version: u16le, _unused: [u8; 2], } #[derive(Parse)] struct SynapromIotaConfigVersion { config_id1: u32le, // YYMMDD config_id2: u32le, // HHMMSS version: u16le, _unused: [u16; 3], } #[derive(Parse)] struct SynapromReplyIotaFindHdr { status: u16le, fullsize: u32le, nbytes: u16le, itype: u16le, } // Iotas can exceed the size of available RAM in the part: to allow the host to read them the // IOTA_FIND command supports transferring iotas with multiple commands #[derive(New, Getters)] struct SynapromCmdIotaFind { itype: u16le, // type of iotas to find flags: u16le, maxniotas: u8, // maximum number of iotas to return, 0 = unlimited firstidx: u8, // first index of iotas to return _dummy: [u8; 2], offset: u32le, // byte offset of data to return nbytes: u32le, // maximum number of bytes to return } fwupd-1.9.16/plugins/synaptics-prometheus/meson.build000066400000000000000000000026541460375044200230020ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsPrometheus"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('synaptics-prometheus.quirk') plugin_builtin_synaprom = static_library('fu_plugin_synaprom', rustgen.process( 'fu-synaprom.rs', # fuzzing ), sources: [ 'fu-synaprom-plugin.c', 'fu-synaprom-common.c', 'fu-synaprom-config.c', 'fu-synaprom-device.c', 'fu-synaprom-firmware.c', # fuzzing ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_synaprom if get_option('tests') install_data(['tests/synaptics-prometheus.builder.xml'], install_dir: join_paths(installed_test_datadir, 'tests')) env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'synaptics-prometheus-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_synaprom, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ cargs, '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('synaptics-prometheus-self-test', e, env: env) # added to installed-tests endif endif fwupd-1.9.16/plugins/synaptics-prometheus/synaptics-prometheus.quirk000066400000000000000000000026211460375044200261150ustar00rootroot00000000000000[USB\VID_06CB&PID_00A9] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_00BD] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_00DF] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_00E9] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_00C2] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_00F9] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_00FC] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_00D8] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_00F0] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_0103] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_0123] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_0126] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_0129] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_0100] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_0168] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_015F] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_0104] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_0160] Plugin = synaptics_prometheus InstallDuration = 2 [USB\VID_06CB&PID_0106] Plugin = synaptics_prometheus InstallDuration = 2 fwupd-1.9.16/plugins/synaptics-prometheus/tests/000077500000000000000000000000001460375044200217735ustar00rootroot00000000000000fwupd-1.9.16/plugins/synaptics-prometheus/tests/synaptics-prometheus.bin000066400000000000000000000004571460375044200267010ustar00rootroot00000000000000B  hello worldfwupd-1.9.16/plugins/synaptics-prometheus/tests/synaptics-prometheus.builder.xml000066400000000000000000000002331460375044200303460ustar00rootroot00000000000000 1.2 0x42 aGVsbG8gd29ybGQ= fwupd-1.9.16/plugins/synaptics-rmi/000077500000000000000000000000001460375044200172255ustar00rootroot00000000000000fwupd-1.9.16/plugins/synaptics-rmi/README.md000066400000000000000000000025061460375044200205070ustar00rootroot00000000000000--- title: Plugin: Synaptics RMI4 --- ## Introduction This plugin updates integrated Synaptics RMI4 devices, typically touchpads. ## GUID Generation The HID DeviceInstanceId values are used, e.g. `HIDRAW\VEN_06CB&DEV_4875`. These devices also use custom GUID values constructed using the board ID, e.g. * `SYNAPTICS_RMI\TM3038-002` * `SYNAPTICS_RMI\TM3038` ## Update Behavior The device usually presents in HID mode, and the firmware is written to the device by switching to a SERIO mode where the touchpad is nonfunctional. Once complete the device is reset to get out of SERIO mode and to load the new firmware version. ## Vendor ID Security The vendor ID is set from the udev vendor, in this instance set to `HIDRAW:0x06CB` ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a proprietary (but documented) file format. This plugin supports the following protocol ID: * `com.synaptics.rmi` ## External Interface Access This plugin requires ioctl access to `HIDIOCSFEATURE` and `HIDIOCGFEATURE`. ## Version Considerations This plugin has been available since fwupd version `1.3.3`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * David Chiu: @blueue * Vincent Huang: @vhuag fwupd-1.9.16/plugins/synaptics-rmi/fu-self-test.c000066400000000000000000000062011460375044200217060ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-rmi-firmware.h" static void fu_synaptics_rmi_firmware_0x_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *csum1 = NULL; g_autofree gchar *csum2 = NULL; g_autofree gchar *xml_out = NULL; g_autofree gchar *xml_src = NULL; g_autoptr(FuFirmware) firmware1 = fu_synaptics_rmi_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_synaptics_rmi_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "synaptics-rmi-0x.builder.xml", NULL); ret = g_file_get_contents(filename, &xml_src, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_build_from_xml(firmware1, xml_src, &error); g_assert_no_error(error); g_assert_true(ret); csum1 = fu_firmware_get_checksum(firmware1, G_CHECKSUM_SHA1, &error); g_assert_no_error(error); g_assert_cmpstr(csum1, ==, "8b097c034028a69e6416bcc39f312e2fa9247381"); /* ensure we can round-trip */ xml_out = fu_firmware_export_to_xml(firmware1, FU_FIRMWARE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); ret = fu_firmware_build_from_xml(firmware2, xml_out, &error); g_assert_no_error(error); g_assert_true(ret); csum2 = fu_firmware_get_checksum(firmware2, G_CHECKSUM_SHA1, &error); g_assert_cmpstr(csum1, ==, csum2); } static void fu_synaptics_rmi_firmware_10_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *csum1 = NULL; g_autofree gchar *csum2 = NULL; g_autofree gchar *xml_out = NULL; g_autofree gchar *xml_src = NULL; g_autoptr(FuFirmware) firmware1 = fu_synaptics_rmi_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_synaptics_rmi_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "synaptics-rmi-10.builder.xml", NULL); ret = g_file_get_contents(filename, &xml_src, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_build_from_xml(firmware1, xml_src, &error); g_assert_no_error(error); g_assert_true(ret); csum1 = fu_firmware_get_checksum(firmware1, G_CHECKSUM_SHA1, &error); g_assert_no_error(error); g_assert_cmpstr(csum1, ==, "bd85539bb100e5bd6debb00b06b5a7e7fa9bd030"); /* ensure we can round-trip */ xml_out = fu_firmware_export_to_xml(firmware1, FU_FIRMWARE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); ret = fu_firmware_build_from_xml(firmware2, xml_out, &error); g_assert_no_error(error); g_assert_true(ret); csum2 = fu_firmware_get_checksum(firmware2, G_CHECKSUM_SHA1, &error); g_assert_cmpstr(csum1, ==, csum2); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func("/synaptics-rmi/firmware{0x}", fu_synaptics_rmi_firmware_0x_func); g_test_add_func("/synaptics-rmi/firmware{10}", fu_synaptics_rmi_firmware_10_func); return g_test_run(); } fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-common.c000066400000000000000000000115131460375044200240720ustar00rootroot00000000000000/* * Copyright (C) 2012 Andrew Duggan * Copyright (C) 2012 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #ifdef HAVE_GNUTLS #include #include #endif #include "fu-synaptics-rmi-common.h" #define RMI_FUNCTION_QUERY_OFFSET 0 #define RMI_FUNCTION_COMMAND_OFFSET 1 #define RMI_FUNCTION_CONTROL_OFFSET 2 #define RMI_FUNCTION_DATA_OFFSET 3 #define RMI_FUNCTION_INTERRUPT_SOURCES_OFFSET 4 #define RMI_FUNCTION_NUMBER 5 #define RMI_FUNCTION_VERSION_MASK 0x60 #define RMI_FUNCTION_INTERRUPT_SOURCES_MASK 0x7 #ifdef HAVE_GNUTLS typedef guchar gnutls_data_t; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_data_t, gnutls_free) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_pubkey_t, gnutls_pubkey_deinit, NULL) #pragma clang diagnostic pop #endif guint32 fu_synaptics_rmi_generate_checksum(const guint8 *data, gsize len) { guint32 lsw = 0xffff; guint32 msw = 0xffff; for (gsize i = 0; i < len / 2; i++) { lsw += fu_memread_uint16(&data[i * 2], G_LITTLE_ENDIAN); msw += lsw; lsw = (lsw & 0xffff) + (lsw >> 16); msw = (msw & 0xffff) + (msw >> 16); } return msw << 16 | lsw; } FuSynapticsRmiFunction * fu_synaptics_rmi_function_parse(GByteArray *buf, guint16 page_base, guint interrupt_count, GError **error) { FuSynapticsRmiFunction *func; guint8 interrupt_offset; const guint8 *data = buf->data; /* not expected */ if (buf->len != RMI_DEVICE_PDT_ENTRY_SIZE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "PDT entry buffer invalid size %u != %i", buf->len, RMI_DEVICE_PDT_ENTRY_SIZE); return NULL; } func = g_new0(FuSynapticsRmiFunction, 1); func->query_base = data[RMI_FUNCTION_QUERY_OFFSET] + page_base; func->command_base = data[RMI_FUNCTION_COMMAND_OFFSET] + page_base; func->control_base = data[RMI_FUNCTION_CONTROL_OFFSET] + page_base; func->data_base = data[RMI_FUNCTION_DATA_OFFSET] + page_base; func->interrupt_source_count = data[RMI_FUNCTION_INTERRUPT_SOURCES_OFFSET] & RMI_FUNCTION_INTERRUPT_SOURCES_MASK; func->function_number = data[RMI_FUNCTION_NUMBER]; func->function_version = (data[RMI_FUNCTION_INTERRUPT_SOURCES_OFFSET] & RMI_FUNCTION_VERSION_MASK) >> 5; if (func->interrupt_source_count > 0) { func->interrupt_reg_num = (interrupt_count + 8) / 8 - 1; /* set an enable bit for each data source */ interrupt_offset = interrupt_count % 8; func->interrupt_mask = 0; for (guint i = interrupt_offset; i < (func->interrupt_source_count + interrupt_offset); i++) func->interrupt_mask |= 1 << i; } return func; } gboolean fu_synaptics_rmi_device_writeln(const gchar *fn, const gchar *buf, GError **error) { int fd; g_autoptr(FuIOChannel) io = NULL; fd = open(fn, O_WRONLY); if (fd < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "could not open %s", fn); return FALSE; } io = fu_io_channel_unix_new(fd); return fu_io_channel_write_raw(io, (const guint8 *)buf, strlen(buf), 1000, FU_IO_CHANNEL_FLAG_NONE, error); } gboolean fu_synaptics_verify_sha256_signature(GBytes *payload, GBytes *pubkey, GBytes *signature, GError **error) { #ifdef HAVE_GNUTLS gnutls_datum_t hash; gnutls_datum_t m; gnutls_datum_t e; gnutls_datum_t sig; gnutls_hash_hd_t sha2; g_auto(gnutls_pubkey_t) pub = NULL; gint ec; guint8 exponent[] = {1, 0, 1}; guint hash_length = gnutls_hash_get_len(GNUTLS_DIG_SHA256); g_autoptr(gnutls_data_t) hash_data = NULL; /* hash firmware data */ hash_data = gnutls_malloc(hash_length); gnutls_hash_init(&sha2, GNUTLS_DIG_SHA256); gnutls_hash(sha2, g_bytes_get_data(payload, NULL), g_bytes_get_size(payload)); gnutls_hash_deinit(sha2, hash_data); /* hash */ hash.size = hash_length; hash.data = hash_data; /* modulus */ m.size = g_bytes_get_size(pubkey); m.data = (guint8 *)g_bytes_get_data(pubkey, NULL); /* exponent */ e.size = sizeof(exponent); e.data = exponent; /* signature */ sig.size = g_bytes_get_size(signature); sig.data = (guint8 *)g_bytes_get_data(signature, NULL); gnutls_pubkey_init(&pub); ec = gnutls_pubkey_import_rsa_raw(pub, &m, &e); if (ec < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to import RSA key: %s", gnutls_strerror(ec)); return FALSE; } ec = gnutls_pubkey_verify_hash2(pub, GNUTLS_SIGN_RSA_SHA256, 0, &hash, &sig); if (ec < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to verify firmware: %s", gnutls_strerror(ec)); return FALSE; } #endif /* success */ return TRUE; } fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-common.h000066400000000000000000000017651460375044200241070ustar00rootroot00000000000000/* * Copyright (C) 2012 Andrew Duggan * Copyright (C) 2012 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define RMI_PRODUCT_ID_LENGTH 10 #define RMI_DEVICE_PDT_ENTRY_SIZE 6 typedef struct { guint16 query_base; guint16 command_base; guint16 control_base; guint16 data_base; guint8 interrupt_source_count; guint8 function_number; guint8 function_version; guint8 interrupt_reg_num; guint8 interrupt_mask; } FuSynapticsRmiFunction; guint32 fu_synaptics_rmi_generate_checksum(const guint8 *data, gsize len); FuSynapticsRmiFunction * fu_synaptics_rmi_function_parse(GByteArray *buf, guint16 page_base, guint interrupt_count, GError **error); gboolean fu_synaptics_rmi_device_writeln(const gchar *fn, const gchar *buf, GError **error); gboolean fu_synaptics_verify_sha256_signature(GBytes *payload, GBytes *pubkey, GBytes *signature, GError **error); fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-device.c000066400000000000000000000644331460375044200240520ustar00rootroot00000000000000/* * Copyright (C) 2012 Andrew Duggan * Copyright (C) 2012 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-rmi-common.h" #include "fu-synaptics-rmi-firmware.h" #include "fu-synaptics-rmi-ps2-device.h" #include "fu-synaptics-rmi-struct.h" #include "fu-synaptics-rmi-v5-device.h" #include "fu-synaptics-rmi-v6-device.h" #include "fu-synaptics-rmi-v7-device.h" #define RMI_DEVICE_PAGE_SIZE 0x100 #define RMI_DEVICE_PAGE_SCAN_START 0x00e9 #define RMI_DEVICE_PAGE_SCAN_END 0x0005 #define RMI_DEVICE_F01_BASIC_QUERY_LEN 11 #define RMI_DEVICE_F01_LTS_RESERVED_SIZE 19 #define RMI_DEVICE_F01_QRY1_HAS_LTS (1 << 2) #define RMI_DEVICE_F01_QRY1_HAS_SENSOR_ID (1 << 3) #define RMI_DEVICE_F01_QRY1_HAS_PROPS_2 (1 << 7) #define RMI_DEVICE_F01_QRY42_DS4_QUERIES (1 << 0) #define RMI_DEVICE_F01_QRY43_01_PACKAGE_ID (1 << 0) #define RMI_DEVICE_F01_QRY43_01_BUILD_ID (1 << 1) #define RMI_F34_COMMAND_MASK 0x0f #define RMI_F34_STATUS_MASK 0x07 #define RMI_F34_STATUS_SHIFT 4 #define RMI_F34_ENABLED_MASK 0x80 #define RMI_F34_COMMAND_V1_MASK 0x3f #define RMI_F34_STATUS_V1_MASK 0x3f #define RMI_F34_ENABLED_V1_MASK 0x80 #define RMI_F01_CMD_DEVICE_RESET 1 #define RMI_F01_DEFAULT_RESET_DELAY_MS 100 typedef struct { FuSynapticsRmiFlash flash; GPtrArray *functions; FuSynapticsRmiFunction *f01; FuSynapticsRmiFunction *f34; guint8 current_page; guint16 sig_size; /* 0x0 for non-secure update */ guint8 max_page; gboolean in_iep_mode; } FuSynapticsRmiDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuSynapticsRmiDevice, fu_synaptics_rmi_device, FU_TYPE_UDEV_DEVICE) #define GET_PRIVATE(o) (fu_synaptics_rmi_device_get_instance_private(o)) FuSynapticsRmiFlash * fu_synaptics_rmi_device_get_flash(FuSynapticsRmiDevice *self) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); return &priv->flash; } static void fu_synaptics_rmi_flash_to_string(FuSynapticsRmiFlash *flash, guint idt, GString *str) { if (flash->bootloader_id[0] != 0x0) { g_autofree gchar *tmp = g_strdup_printf("%02x.%02x", flash->bootloader_id[0], flash->bootloader_id[1]); fu_string_append(str, idt, "BootloaderId", tmp); } fu_string_append_kx(str, idt, "BlockSize", flash->block_size); fu_string_append_kx(str, idt, "BlockCountFw", flash->block_count_fw); fu_string_append_kx(str, idt, "BlockCountCfg", flash->block_count_cfg); fu_string_append_kx(str, idt, "FlashConfigLength", flash->config_length); fu_string_append_kx(str, idt, "PayloadLength", flash->payload_length); fu_string_append_kx(str, idt, "BuildID", flash->build_id); } static void fu_synaptics_rmi_device_to_string(FuDevice *device, guint idt, GString *str) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device); FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); /* FuUdevDevice->to_string */ FU_DEVICE_CLASS(fu_synaptics_rmi_device_parent_class)->to_string(device, idt, str); fu_string_append_kx(str, idt, "CurrentPage", priv->current_page); fu_string_append_kx(str, idt, "InIepMode", priv->in_iep_mode); fu_string_append_kx(str, idt, "MaxPage", priv->max_page); fu_string_append_kx(str, idt, "SigSize", priv->sig_size); if (priv->f34 != NULL) { fu_string_append_kx(str, idt, "BlVer", priv->f34->function_version + 0x5); } fu_synaptics_rmi_flash_to_string(&priv->flash, idt, str); } FuSynapticsRmiFunction * fu_synaptics_rmi_device_get_function(FuSynapticsRmiDevice *self, guint8 function_number, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); if (priv->functions->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no RMI functions, perhaps read the PDT?"); return NULL; } for (guint i = 0; i < priv->functions->len; i++) { FuSynapticsRmiFunction *func = g_ptr_array_index(priv->functions, i); if (func->function_number == function_number) return func; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get RMI function 0x%02x", function_number); return NULL; } GByteArray * fu_synaptics_rmi_device_read(FuSynapticsRmiDevice *self, guint16 addr, gsize req_sz, GError **error) { FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_GET_CLASS(self); return klass_rmi->read(self, addr, req_sz, error); } GByteArray * fu_synaptics_rmi_device_read_packet_register(FuSynapticsRmiDevice *self, guint16 addr, gsize req_sz, GError **error) { FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_GET_CLASS(self); if (klass_rmi->read_packet_register == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "packet register reads not supported"); return NULL; } return klass_rmi->read_packet_register(self, addr, req_sz, error); } gboolean fu_synaptics_rmi_device_write(FuSynapticsRmiDevice *self, guint16 addr, GByteArray *req, FuSynapticsRmiDeviceFlags flags, GError **error) { FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_GET_CLASS(self); return klass_rmi->write(self, addr, req, flags, error); } gboolean fu_synaptics_rmi_device_set_page(FuSynapticsRmiDevice *self, guint8 page, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_GET_CLASS(self); if (priv->current_page == page) return TRUE; if (!klass_rmi->set_page(self, page, error)) return FALSE; priv->current_page = page; return TRUE; } void fu_synaptics_rmi_device_set_iepmode(FuSynapticsRmiDevice *self, gboolean iepmode) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); priv->in_iep_mode = iepmode; } gboolean fu_synaptics_rmi_device_write_bus_select(FuSynapticsRmiDevice *self, guint8 bus, GError **error) { FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_GET_CLASS(self); if (klass_rmi->write_bus_select == NULL) return TRUE; return klass_rmi->write_bus_select(self, bus, error); } gboolean fu_synaptics_rmi_device_reset(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) req = g_byte_array_new(); fu_byte_array_append_uint8(req, RMI_F01_CMD_DEVICE_RESET); if (!fu_synaptics_rmi_device_write(self, priv->f01->command_base, req, FU_SYNAPTICS_RMI_DEVICE_FLAG_ALLOW_FAILURE, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), RMI_F01_DEFAULT_RESET_DELAY_MS); return TRUE; } static gboolean fu_synaptics_rmi_device_scan_pdt(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); guint interrupt_count = 0; /* clear old list */ g_ptr_array_set_size(priv->functions, 0); /* scan pages */ for (guint page = 0; page < priv->max_page; page++) { gboolean found = FALSE; guint32 page_start = RMI_DEVICE_PAGE_SIZE * page; guint32 pdt_start = page_start + RMI_DEVICE_PAGE_SCAN_START; guint32 pdt_end = page_start + RMI_DEVICE_PAGE_SCAN_END; /* set page */ if (!fu_synaptics_rmi_device_set_page(self, page, error)) return FALSE; /* read out functions */ for (guint addr = pdt_start; addr >= pdt_end; addr -= RMI_DEVICE_PDT_ENTRY_SIZE) { g_autofree FuSynapticsRmiFunction *func = NULL; g_autoptr(GByteArray) res = NULL; res = fu_synaptics_rmi_device_read(self, addr, RMI_DEVICE_PDT_ENTRY_SIZE, error); if (res == NULL) { g_prefix_error(error, "failed to read page %u PDT entry @ 0x%04x: ", page, addr); return FALSE; } func = fu_synaptics_rmi_function_parse(res, page_start, interrupt_count, error); if (func == NULL) return FALSE; if (func->function_number == 0) break; interrupt_count += func->interrupt_source_count; g_ptr_array_add(priv->functions, g_steal_pointer(&func)); found = TRUE; } if (!found) break; } /* for debug */ for (guint i = 0; i < priv->functions->len; i++) { FuSynapticsRmiFunction *func = g_ptr_array_index(priv->functions, i); g_debug("PDT-%02u fn:0x%02x vr:%d sc:%d ms:0x%x " "db:0x%02x cb:0x%02x cm:0x%02x qb:0x%02x", i, func->function_number, func->function_version, func->interrupt_source_count, func->interrupt_mask, func->data_base, func->control_base, func->command_base, func->query_base); } /* success */ return TRUE; } void fu_synaptics_rmi_device_set_sig_size(FuSynapticsRmiDevice *self, guint16 sig_size) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); priv->sig_size = sig_size; } guint16 fu_synaptics_rmi_device_get_sig_size(FuSynapticsRmiDevice *self) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); return priv->sig_size; } void fu_synaptics_rmi_device_set_max_page(FuSynapticsRmiDevice *self, guint8 max_page) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); priv->max_page = max_page; } static void fu_synaptics_rmi_device_set_product_id(FuSynapticsRmiDevice *self, const gchar *product_id) { g_autofree gchar *instance_id = NULL; g_auto(GStrv) product_id_split = g_strsplit(product_id, "-", 2); /* use the product ID as an instance ID */ instance_id = g_strdup_printf("SYNAPTICS_RMI\\%s", product_id); fu_device_add_instance_id(FU_DEVICE(self), instance_id); /* also add the product ID without the sub-number */ if (g_strv_length(product_id_split) == 2) { g_autofree gchar *instance_id_major = NULL; instance_id_major = g_strdup_printf("SYNAPTICS_RMI\\%s", product_id_split[0]); fu_device_add_instance_id(FU_DEVICE(self), instance_id_major); } } static gboolean fu_synaptics_rmi_device_query_status(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_GET_CLASS(self); return klass_rmi->query_status(self, error); } static gboolean fu_synaptics_rmi_device_query_build_id(FuSynapticsRmiDevice *self, guint32 *build_id, GError **error) { FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_GET_CLASS(self); if (klass_rmi->query_build_id == NULL) return TRUE; return klass_rmi->query_build_id(self, build_id, error); } static gboolean fu_synaptics_rmi_device_query_product_sub_id(FuSynapticsRmiDevice *self, guint8 *product_sub_id, GError **error) { FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_GET_CLASS(self); if (klass_rmi->query_product_sub_id == NULL) return TRUE; return klass_rmi->query_product_sub_id(self, product_sub_id, error); } static gboolean fu_synaptics_rmi_device_setup(FuDevice *device, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device); FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); guint16 addr; guint16 prod_info_addr; guint8 ds4_query_length = 0; guint8 product_sub_id = 0; gboolean has_build_id_query = FALSE; gboolean has_dds4_queries = FALSE; gboolean has_lts; gboolean has_package_id_query = FALSE; gboolean has_query42; gboolean has_sensor_id; g_autofree gchar *bl_ver = NULL; g_autofree gchar *fw_ver = NULL; g_autofree gchar *product_id = NULL; g_autoptr(GByteArray) f01_basic = NULL; g_autoptr(GByteArray) f01_product_id = NULL; g_autoptr(GByteArray) f01_ds4 = NULL; /* assume reset */ priv->in_iep_mode = FALSE; /* read PDT */ if (!fu_synaptics_rmi_device_scan_pdt(self, error)) return FALSE; priv->f01 = fu_synaptics_rmi_device_get_function(self, 0x01, error); if (priv->f01 == NULL) return FALSE; addr = priv->f01->query_base; /* set page */ if (!fu_synaptics_rmi_device_set_page(self, 0, error)) return FALSE; /* force entering iep mode again */ if (!fu_synaptics_rmi_device_enter_iep_mode(self, FU_SYNAPTICS_RMI_DEVICE_FLAG_FORCE, error)) return FALSE; f01_basic = fu_synaptics_rmi_device_read(self, addr, RMI_DEVICE_F01_BASIC_QUERY_LEN, error); if (f01_basic == NULL) { g_prefix_error(error, "failed to read the basic query: "); return FALSE; } has_lts = (f01_basic->data[1] & RMI_DEVICE_F01_QRY1_HAS_LTS) > 0; has_sensor_id = (f01_basic->data[1] & RMI_DEVICE_F01_QRY1_HAS_SENSOR_ID) > 0; has_query42 = (f01_basic->data[1] & RMI_DEVICE_F01_QRY1_HAS_PROPS_2) > 0; /* get the product ID */ addr += 11; f01_product_id = fu_synaptics_rmi_device_read(self, addr, RMI_PRODUCT_ID_LENGTH, error); if (f01_product_id == NULL) { g_prefix_error(error, "failed to read the product id: "); return FALSE; } if (!fu_synaptics_rmi_device_query_product_sub_id(self, &product_sub_id, error)) { g_prefix_error(error, "failed to query product sub id: "); return FALSE; } if (product_sub_id == 0) { /* HID */ product_id = g_strndup((const gchar *)f01_product_id->data, f01_product_id->len); } else { /* PS/2 */ g_autofree gchar *tmp = g_strndup((const gchar *)f01_product_id->data, 6); product_id = g_strdup_printf("%s-%03d", tmp, product_sub_id); } if (product_id != NULL) fu_synaptics_rmi_device_set_product_id(self, product_id); /* force entering iep mode again */ if (!fu_synaptics_rmi_device_enter_iep_mode(self, FU_SYNAPTICS_RMI_DEVICE_FLAG_FORCE, error)) return FALSE; /* skip */ prod_info_addr = addr + 6; addr += 10; if (has_lts) addr++; if (has_sensor_id) addr++; if (has_lts) addr += RMI_DEVICE_F01_LTS_RESERVED_SIZE; /* read package ids */ if (has_query42) { g_autoptr(GByteArray) f01_tmp = NULL; f01_tmp = fu_synaptics_rmi_device_read(self, addr++, 1, error); if (f01_tmp == NULL) { g_prefix_error(error, "failed to read query 42: "); return FALSE; } has_dds4_queries = (f01_tmp->data[0] & RMI_DEVICE_F01_QRY42_DS4_QUERIES) > 0; } if (has_dds4_queries) { g_autoptr(GByteArray) f01_tmp = NULL; f01_tmp = fu_synaptics_rmi_device_read(self, addr++, 1, error); if (f01_tmp == NULL) { g_prefix_error(error, "failed to read DS4 query length: "); return FALSE; } ds4_query_length = f01_tmp->data[0]; } f01_ds4 = fu_synaptics_rmi_device_read(self, addr, 0x1, error); if (f01_ds4 == NULL) { g_prefix_error(error, "failed to read F01 Query43: "); return FALSE; } has_package_id_query = (f01_ds4->data[0] & RMI_DEVICE_F01_QRY43_01_PACKAGE_ID) > 0; has_build_id_query = (f01_ds4->data[0] & RMI_DEVICE_F01_QRY43_01_BUILD_ID) > 0; addr += ds4_query_length; if (has_package_id_query) prod_info_addr++; if (has_build_id_query) { g_autoptr(GByteArray) f01_tmp = NULL; guint8 buf32[4] = {0x0}; f01_tmp = fu_synaptics_rmi_device_read(self, prod_info_addr, 0x3, error); if (f01_tmp == NULL) { g_prefix_error(error, "failed to read build ID bytes: "); return FALSE; } if (!fu_memcpy_safe(buf32, sizeof(buf32), 0x0, /* dst */ f01_tmp->data, f01_tmp->len, 0x0, /* src */ f01_tmp->len, error)) return FALSE; if (!fu_memread_uint32_safe(buf32, sizeof(buf32), 0x0, &priv->flash.build_id, G_LITTLE_ENDIAN, error)) return FALSE; } /* read build ID, typically only for PS/2 */ if (!fu_synaptics_rmi_device_query_build_id(self, &priv->flash.build_id, error)) { g_prefix_error(error, "failed to query build id: "); return FALSE; } /* get Function34_Query0,1 */ priv->f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (priv->f34 == NULL) return FALSE; if (priv->f34->function_version == 0x0) { if (!fu_synaptics_rmi_v5_device_setup(self, error)) { g_prefix_error(error, "failed to do v5 setup: "); return FALSE; } } else if (priv->f34->function_version == 0x1) { if (!fu_synaptics_rmi_v6_device_setup(self, error)) { g_prefix_error(error, "failed to do v6 setup: "); return FALSE; } } else if (priv->f34->function_version == 0x2) { if (!fu_synaptics_rmi_v7_device_setup(self, error)) { g_prefix_error(error, "failed to do v7 setup: "); return FALSE; } } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "f34 function version 0x%02x unsupported", priv->f34->function_version); return FALSE; } if (!fu_synaptics_rmi_device_query_status(self, error)) { g_prefix_error(error, "failed to read bootloader status: "); return FALSE; } /* set versions */ fw_ver = g_strdup_printf("%u.%u.%u", f01_basic->data[2], f01_basic->data[3], priv->flash.build_id); fu_device_set_version(device, fw_ver); if (priv->f34->function_version == 0x0 || priv->f34->function_version == 0x1) { bl_ver = g_strdup_printf("%c.0.0", priv->flash.bootloader_id[1]); } else { bl_ver = g_strdup_printf("%u.0.0", priv->flash.bootloader_id[1]); } fu_device_set_version_bootloader(device, bl_ver); /* success */ return TRUE; } static FuFirmware * fu_synaptics_rmi_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device); FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(FuFirmware) firmware = fu_synaptics_rmi_firmware_new(); g_autoptr(GBytes) bytes_cfg = NULL; g_autoptr(GBytes) bytes_bin = NULL; gsize size_expected; if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* check sizes */ bytes_bin = fu_firmware_get_image_by_id_bytes(firmware, "ui", error); if (bytes_bin == NULL) return NULL; size_expected = ((gsize)priv->flash.block_count_fw * (gsize)priv->flash.block_size) + fu_synaptics_rmi_firmware_get_sig_size(FU_SYNAPTICS_RMI_FIRMWARE(firmware)); if (g_bytes_get_size(bytes_bin) != size_expected) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "file firmware invalid size 0x%04x, expected 0x%04x", (guint)g_bytes_get_size(bytes_bin), (guint)size_expected); return NULL; } bytes_cfg = fu_firmware_get_image_by_id_bytes(firmware, "config", error); if (bytes_cfg == NULL) return NULL; size_expected = (gsize)priv->flash.block_count_cfg * (gsize)priv->flash.block_size; if (g_bytes_get_size(bytes_cfg) != size_expected) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "file config invalid size 0x%04x, expected 0x%04x", (guint)g_bytes_get_size(bytes_cfg), (guint)size_expected); return NULL; } return g_steal_pointer(&firmware); } static gboolean fu_synaptics_rmi_device_poll(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) f34_db = NULL; /* get if the last flash read completed successfully */ f34_db = fu_synaptics_rmi_device_read(self, priv->f34->data_base, 0x1, error); if (f34_db == NULL) { g_prefix_error(error, "failed to read f34_db: "); return FALSE; } if ((f34_db->data[0] & 0x1f) != 0x0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "flash status invalid: 0x%x", (guint)(f34_db->data[0] & 0x1f)); return FALSE; } /* success */ return TRUE; } gboolean fu_synaptics_rmi_device_poll_wait(FuSynapticsRmiDevice *self, GError **error) { g_autoptr(GError) error_local = NULL; /* try to poll every 20ms for up to 400ms */ for (guint i = 0; i < 20; i++) { fu_device_sleep(FU_DEVICE(self), 20); g_clear_error(&error_local); if (fu_synaptics_rmi_device_poll(self, &error_local)) return TRUE; g_debug("failed: %s", error_local->message); } /* proxy the last error */ g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } static gboolean fu_synaptics_rmi_device_wait_for_attr(FuSynapticsRmiDevice *self, guint8 source_mask, guint timeout_ms, GError **error) { FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_GET_CLASS(self); return klass_rmi->wait_for_attr(self, source_mask, timeout_ms, error); } gboolean fu_synaptics_rmi_device_enter_iep_mode(FuSynapticsRmiDevice *self, FuSynapticsRmiDeviceFlags flags, GError **error) { FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_GET_CLASS(self); FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); /* already set */ if ((flags & FU_SYNAPTICS_RMI_DEVICE_FLAG_FORCE) == 0 && priv->in_iep_mode) return TRUE; if (klass_rmi->enter_iep_mode != NULL) { g_debug("enabling RMI iep_mode"); if (!klass_rmi->enter_iep_mode(self, error)) { g_prefix_error(error, "failed to enable RMI iep_mode: "); return FALSE; } } priv->in_iep_mode = TRUE; return TRUE; } gboolean fu_synaptics_rmi_device_wait_for_idle(FuSynapticsRmiDevice *self, guint timeout_ms, RmiDeviceWaitForIdleFlags flags, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); guint8 f34_command; guint8 f34_enabled; guint8 f34_status; g_autoptr(GByteArray) res = NULL; g_autoptr(GError) error_local = NULL; /* try to get report without requesting */ if (timeout_ms > 0 && !fu_synaptics_rmi_device_wait_for_attr(self, priv->f34->interrupt_mask, timeout_ms, &error_local)) { if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to wait for attr: "); return FALSE; } } else if ((flags & RMI_DEVICE_WAIT_FOR_IDLE_FLAG_REFRESH_F34) == 0) { /* device reported idle via an event */ return TRUE; } /* if for some reason we are not getting attention reports for HID devices * then we can still continue after the timeout and read F34 status * but if we have to wait for the timeout to elapse every time then this * will be slow */ if (priv->f34->function_version == 0x1) { res = fu_synaptics_rmi_device_read(self, priv->flash.status_addr, 0x2, error); if (res == NULL) return FALSE; f34_command = res->data[0] & RMI_F34_COMMAND_V1_MASK; f34_status = res->data[1] & RMI_F34_STATUS_V1_MASK; f34_enabled = !!(res->data[1] & RMI_F34_ENABLED_MASK); } else { res = fu_synaptics_rmi_device_read(self, priv->flash.status_addr, 0x1, error); if (res == NULL) return FALSE; f34_command = res->data[0] & RMI_F34_COMMAND_MASK; f34_status = (res->data[0] >> RMI_F34_STATUS_SHIFT) & RMI_F34_STATUS_MASK; f34_enabled = !!(res->data[0] & RMI_F34_ENABLED_MASK); } /* PS/2 */ if (FU_IS_SYNAPTICS_RMI_PS2_DEVICE(self)) { if (f34_command == 0) { g_debug("F34 zero as PS/2"); return TRUE; } } /* is idle */ if (f34_status == 0x0 && f34_command == 0x0) { if (f34_enabled == 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "idle but enabled unset"); return FALSE; } return TRUE; } /* failed */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "timed out waiting for idle [cmd:0x%x, sta:0x%x, ena:0x%x]", f34_command, f34_status, f34_enabled); return FALSE; } gboolean fu_synaptics_rmi_device_disable_sleep(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_GET_CLASS(self); if (klass_rmi->disable_sleep == NULL) return TRUE; return klass_rmi->disable_sleep(self, error); } gboolean fu_synaptics_rmi_device_write_bootloader_id(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); gint block_data_offset = RMI_F34_BLOCK_DATA_OFFSET; g_autoptr(GByteArray) bootloader_id_req = g_byte_array_new(); if (priv->f34->function_version == 0x1) block_data_offset = RMI_F34_BLOCK_DATA_V1_OFFSET; /* write bootloader_id into F34_Flash_Data0,1 */ g_byte_array_append(bootloader_id_req, priv->flash.bootloader_id, sizeof(priv->flash.bootloader_id)); if (!fu_synaptics_rmi_device_write(self, priv->f34->data_base + block_data_offset, bootloader_id_req, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write bootloader_id: "); return FALSE; } /* success */ return TRUE; } gboolean fu_synaptics_rmi_device_disable_irqs(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GByteArray) interrupt_disable_req = g_byte_array_new(); fu_byte_array_append_uint8(interrupt_disable_req, priv->f34->interrupt_mask | priv->f01->interrupt_mask); if (!fu_synaptics_rmi_device_write(self, priv->f01->control_base + 1, interrupt_disable_req, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to disable interrupts: "); return FALSE; } return TRUE; } static gboolean fu_synaptics_rmi_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device); FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); if (priv->f34->function_version == 0x0 || priv->f34->function_version == 0x1) { return fu_synaptics_rmi_v5_device_write_firmware(device, firmware, progress, flags, error); } if (priv->f34->function_version == 0x2) { return fu_synaptics_rmi_v7_device_write_firmware(device, firmware, progress, flags, error); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "f34 function version 0x%02x unsupported", priv->f34->function_version); return FALSE; } static void fu_synaptics_rmi_device_init(FuSynapticsRmiDevice *self) { FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); fu_device_add_protocol(FU_DEVICE(self), "com.synaptics.rmi"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); priv->current_page = 0xfe; priv->functions = g_ptr_array_new_with_free_func(g_free); } static void fu_synaptics_rmi_device_finalize(GObject *object) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(object); FuSynapticsRmiDevicePrivate *priv = GET_PRIVATE(self); g_ptr_array_unref(priv->functions); G_OBJECT_CLASS(fu_synaptics_rmi_device_parent_class)->finalize(object); } static void fu_synaptics_rmi_device_class_init(FuSynapticsRmiDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_synaptics_rmi_device_finalize; klass_device->to_string = fu_synaptics_rmi_device_to_string; klass_device->prepare_firmware = fu_synaptics_rmi_device_prepare_firmware; klass_device->setup = fu_synaptics_rmi_device_setup; klass_device->write_firmware = fu_synaptics_rmi_device_write_firmware; } fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-device.h000066400000000000000000000107771460375044200240610ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-synaptics-rmi-common.h" #define FU_TYPE_SYNAPTICS_RMI_DEVICE (fu_synaptics_rmi_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuSynapticsRmiDevice, fu_synaptics_rmi_device, FU, SYNAPTICS_RMI_DEVICE, FuUdevDevice) typedef enum { FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE = 0, FU_SYNAPTICS_RMI_DEVICE_FLAG_ALLOW_FAILURE = 1 << 0, FU_SYNAPTICS_RMI_DEVICE_FLAG_FORCE = 1 << 1, } FuSynapticsRmiDeviceFlags; struct _FuSynapticsRmiDeviceClass { FuUdevDeviceClass parent_class; gboolean (*setup)(FuSynapticsRmiDevice *self, GError **error); gboolean (*query_status)(FuSynapticsRmiDevice *self, GError **error); gboolean (*write)(FuSynapticsRmiDevice *self, guint16 addr, GByteArray *req, FuSynapticsRmiDeviceFlags flags, GError **error); GByteArray *(*read)(FuSynapticsRmiDevice *self, guint16 addr, gsize req_sz, GError **error); GByteArray *(*read_packet_register)(FuSynapticsRmiDevice *self, guint16 addr, gsize req_sz, GError **error); gboolean (*wait_for_attr)(FuSynapticsRmiDevice *self, guint8 source_mask, guint timeout_ms, GError **error); gboolean (*set_page)(FuSynapticsRmiDevice *self, guint8 page, GError **error); gboolean (*disable_sleep)(FuSynapticsRmiDevice *self, GError **error); gboolean (*write_bus_select)(FuSynapticsRmiDevice *self, guint8 bus, GError **error); gboolean (*query_build_id)(FuSynapticsRmiDevice *self, guint32 *build_id, GError **error); gboolean (*query_product_sub_id)(FuSynapticsRmiDevice *self, guint8 *product_sub_id, GError **error); gboolean (*enter_iep_mode)(FuSynapticsRmiDevice *self, GError **error); }; typedef struct { guint16 block_count_cfg; guint16 block_count_fw; guint16 block_size; guint16 config_length; guint16 payload_length; guint32 build_id; guint8 bootloader_id[2]; guint8 status_addr; gboolean has_pubkey; } FuSynapticsRmiFlash; #define RMI_F34_HAS_NEW_REG_MAP (1 << 0) #define RMI_F34_HAS_CONFIG_ID (1 << 2) #define RMI_F34_BLOCK_DATA_OFFSET 2 #define RMI_F34_BLOCK_DATA_V1_OFFSET 1 #define RMI_F34_ENABLE_WAIT_MS 300 /* ms */ #define RMI_F34_IDLE_WAIT_MS 500 /* ms */ #define RMI_DEVICE_PAGE_SELECT_REGISTER 0xff #define RMI_DEVICE_BUS_SELECT_REGISTER 0xfe #define RMI_KEY_SIZE_2K 0x100 typedef enum { RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE = 0, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_REFRESH_F34 = (1 << 0), } RmiDeviceWaitForIdleFlags; void fu_synaptics_rmi_device_set_iepmode(FuSynapticsRmiDevice *self, gboolean iepmode); gboolean fu_synaptics_rmi_device_set_page(FuSynapticsRmiDevice *self, guint8 page, GError **error); gboolean fu_synaptics_rmi_device_write_bootloader_id(FuSynapticsRmiDevice *self, GError **error); gboolean fu_synaptics_rmi_device_disable_irqs(FuSynapticsRmiDevice *self, GError **error); GByteArray * fu_synaptics_rmi_device_read(FuSynapticsRmiDevice *self, guint16 addr, gsize req_sz, GError **error); GByteArray * fu_synaptics_rmi_device_read_packet_register(FuSynapticsRmiDevice *self, guint16 addr, gsize req_sz, GError **error); gboolean fu_synaptics_rmi_device_write(FuSynapticsRmiDevice *self, guint16 addr, GByteArray *req, FuSynapticsRmiDeviceFlags flags, GError **error); gboolean fu_synaptics_rmi_device_reset(FuSynapticsRmiDevice *self, GError **error); gboolean fu_synaptics_rmi_device_wait_for_idle(FuSynapticsRmiDevice *self, guint timeout_ms, RmiDeviceWaitForIdleFlags flags, GError **error); gboolean fu_synaptics_rmi_device_disable_sleep(FuSynapticsRmiDevice *self, GError **error); FuSynapticsRmiFlash * fu_synaptics_rmi_device_get_flash(FuSynapticsRmiDevice *self); FuSynapticsRmiFunction * fu_synaptics_rmi_device_get_function(FuSynapticsRmiDevice *self, guint8 function_number, GError **error); gboolean fu_synaptics_rmi_device_poll_wait(FuSynapticsRmiDevice *self, GError **error); void fu_synaptics_rmi_device_set_sig_size(FuSynapticsRmiDevice *self, guint16 sig_size); guint16 fu_synaptics_rmi_device_get_sig_size(FuSynapticsRmiDevice *self); void fu_synaptics_rmi_device_set_max_page(FuSynapticsRmiDevice *self, guint8 max_page); gboolean fu_synaptics_rmi_device_enter_iep_mode(FuSynapticsRmiDevice *self, FuSynapticsRmiDeviceFlags flags, GError **error); gboolean fu_synaptics_rmi_device_write_bus_select(FuSynapticsRmiDevice *self, guint8 bus, GError **error); fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-firmware.c000066400000000000000000000477341460375044200244340ustar00rootroot00000000000000/* * Copyright (C) 2012 Andrew Duggan * Copyright (C) 2012 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-synaptics-rmi-common.h" #include "fu-synaptics-rmi-firmware.h" #include "fu-synaptics-rmi-struct.h" typedef enum { RMI_FIRMWARE_KIND_UNKNOWN = 0x00, RMI_FIRMWARE_KIND_0X = 0x01, RMI_FIRMWARE_KIND_10 = 0x10, RMI_FIRMWARE_KIND_LAST, } RmiFirmwareKind; struct _FuSynapticsRmiFirmware { FuFirmware parent_instance; RmiFirmwareKind kind; guint32 checksum; guint8 io; guint8 bootloader_version; guint32 build_id; guint32 package_id; guint16 product_info; gchar *product_id; guint32 sig_size; }; G_DEFINE_TYPE(FuSynapticsRmiFirmware, fu_synaptics_rmi_firmware, FU_TYPE_FIRMWARE) #define RMI_IMG_FW_OFFSET 0x100 #define RMI_IMG_V10_CNTR_ADDR_OFFSET 0x0c #define RMI_IMG_MAX_CONTAINERS 1024 static gboolean fu_synaptics_rmi_firmware_add_image(FuFirmware *firmware, const gchar *id, GBytes *fw, gsize offset, gsize bufsz, GError **error) { g_autoptr(GBytes) bytes = NULL; g_autoptr(FuFirmware) img = NULL; bytes = fu_bytes_new_offset(fw, offset, bufsz, error); if (bytes == NULL) return FALSE; img = fu_firmware_new_from_bytes(bytes); fu_firmware_set_id(img, id); return fu_firmware_add_image_full(firmware, img, error); } static gboolean fu_synaptics_rmi_firmware_add_image_v10(FuFirmware *firmware, const gchar *id, GBytes *fw, gsize offset, gsize bufsz, gsize sig_sz, GError **error) { g_autoptr(GBytes) bytes = NULL; g_autoptr(FuFirmware) img = NULL; g_autofree gchar *sig_id = NULL; if (!fu_synaptics_rmi_firmware_add_image(firmware, id, fw, offset, bufsz, error)) return FALSE; if (sig_sz != 0) { bytes = fu_bytes_new_offset(fw, offset + bufsz, sig_sz, error); if (bytes == NULL) return FALSE; img = fu_firmware_new_from_bytes(bytes); sig_id = g_strdup_printf("%s-signature", id); fu_firmware_set_id(img, sig_id); fu_firmware_add_image(firmware, img); } return TRUE; } static void fu_synaptics_rmi_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuSynapticsRmiFirmware *self = FU_SYNAPTICS_RMI_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "kind", self->kind); fu_xmlb_builder_insert_kv(bn, "product_id", self->product_id); if (flags & FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG) { fu_xmlb_builder_insert_kx(bn, "bootloader_version", self->bootloader_version); fu_xmlb_builder_insert_kx(bn, "io", self->io); fu_xmlb_builder_insert_kx(bn, "checksum", self->checksum); fu_xmlb_builder_insert_kx(bn, "build_id", self->build_id); fu_xmlb_builder_insert_kx(bn, "package_id", self->package_id); fu_xmlb_builder_insert_kx(bn, "product_info", self->product_info); fu_xmlb_builder_insert_kx(bn, "sig_size", self->sig_size); } } static gboolean fu_synaptics_rmi_firmware_parse_v10(FuFirmware *firmware, GBytes *fw, GError **error) { FuSynapticsRmiFirmware *self = FU_SYNAPTICS_RMI_FIRMWARE(firmware); guint16 container_id; guint32 cntrs_len; guint32 offset; guint32 cntr_addr; guint8 product_id[RMI_PRODUCT_ID_LENGTH] = {0x0}; gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); guint32 signature_size; g_autoptr(GByteArray) st_dsc = NULL; if (!fu_memread_uint32_safe(buf, bufsz, RMI_IMG_V10_CNTR_ADDR_OFFSET, &cntr_addr, G_LITTLE_ENDIAN, error)) return FALSE; g_debug("v10 RmiContainerDescriptor at 0x%x", cntr_addr); st_dsc = fu_struct_rmi_container_descriptor_parse_bytes(fw, cntr_addr, error); if (st_dsc == NULL) { g_prefix_error(error, "RmiContainerDescriptor invalid: "); return FALSE; } container_id = fu_struct_rmi_container_descriptor_get_container_id(st_dsc); if (container_id != FU_RMI_CONTAINER_ID_TOP_LEVEL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "toplevel container_id invalid, got 0x%x expected 0x%x", (guint)container_id, (guint)FU_RMI_CONTAINER_ID_TOP_LEVEL); return FALSE; } offset = fu_struct_rmi_container_descriptor_get_content_address(st_dsc); if (offset > bufsz - sizeof(guint32) - st_dsc->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "image offset invalid, got 0x%x, size 0x%x", (guint)offset, (guint)bufsz); return FALSE; } cntrs_len = fu_struct_rmi_container_descriptor_get_content_length(st_dsc) / 4; if (cntrs_len > RMI_IMG_MAX_CONTAINERS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "too many containers in file [%u], maximum is %u", cntrs_len, (guint)RMI_IMG_MAX_CONTAINERS); return FALSE; } g_debug("offset=0x%x (cntrs_len=%u)", offset, cntrs_len); for (guint32 i = 0; i < cntrs_len; i++) { guint32 content_addr; guint32 addr; guint32 length; g_autoptr(GByteArray) st_dsc2 = NULL; if (!fu_memread_uint32_safe(buf, bufsz, offset, &addr, G_LITTLE_ENDIAN, error)) return FALSE; g_debug("parsing RmiContainerDescriptor at 0x%x", addr); st_dsc2 = fu_struct_rmi_container_descriptor_parse_bytes(fw, addr, error); if (st_dsc2 == NULL) return FALSE; container_id = fu_struct_rmi_container_descriptor_get_container_id(st_dsc2); content_addr = fu_struct_rmi_container_descriptor_get_content_address(st_dsc2); length = fu_struct_rmi_container_descriptor_get_content_length(st_dsc2); signature_size = fu_struct_rmi_container_descriptor_get_signature_size(st_dsc2); g_debug("RmiContainerDescriptor 0x%02x @ 0x%x (len 0x%x) sig_size 0x%x", container_id, content_addr, length, signature_size); if (length == 0 || length > bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "length invalid, length 0x%x, size 0x%x", (guint)length, (guint)bufsz); return FALSE; } if (content_addr > bufsz - length) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "address invalid, got 0x%x (length 0x%x), size 0x%x", (guint)content_addr, (guint)length, (guint)bufsz); return FALSE; } switch (container_id) { case FU_RMI_CONTAINER_ID_BL: if (!fu_memread_uint8_safe(buf, bufsz, content_addr, &self->bootloader_version, error)) return FALSE; break; case FU_RMI_CONTAINER_ID_UI: case FU_RMI_CONTAINER_ID_CORE_CODE: if (!fu_synaptics_rmi_firmware_add_image_v10(firmware, "ui", fw, content_addr, length, signature_size, error)) return FALSE; break; case FU_RMI_CONTAINER_ID_FLASH_CONFIG: if (!fu_synaptics_rmi_firmware_add_image_v10(firmware, "flash-config", fw, content_addr, length, signature_size, error)) return FALSE; break; case FU_RMI_CONTAINER_ID_UI_CONFIG: case FU_RMI_CONTAINER_ID_CORE_CONFIG: if (!fu_synaptics_rmi_firmware_add_image_v10(firmware, "config", fw, content_addr, length, signature_size, error)) return FALSE; break; case FU_RMI_CONTAINER_ID_FIXED_LOCATION_DATA: if (!fu_synaptics_rmi_firmware_add_image_v10(firmware, "fixed-location-data", fw, content_addr, length, signature_size, error)) return FALSE; break; case FU_RMI_CONTAINER_ID_EXTERNAL_TOUCH_AFE_CONFIG: if (!fu_synaptics_rmi_firmware_add_image_v10(firmware, "afe-config", fw, content_addr, length, signature_size, error)) return FALSE; break; case FU_RMI_CONTAINER_ID_DISPLAY_CONFIG: if (!fu_synaptics_rmi_firmware_add_image_v10(firmware, "display-config", fw, content_addr, length, signature_size, error)) return FALSE; break; case FU_RMI_CONTAINER_ID_GENERAL_INFORMATION: if (length < 0x18 + RMI_PRODUCT_ID_LENGTH) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "content_addr invalid, got 0x%x (length 0x%x)", content_addr, (guint)length); return FALSE; } g_clear_pointer(&self->product_id, g_free); self->io = 1; if (!fu_memread_uint32_safe(buf, bufsz, content_addr, &self->package_id, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, bufsz, content_addr + 0x04, &self->build_id, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memcpy_safe(product_id, sizeof(product_id), 0x0, /* dst */ buf, bufsz, content_addr + 0x18, /* src */ sizeof(product_id), error)) return FALSE; break; default: g_debug("unsupported container %s [0x%02x]", fu_rmi_container_id_to_string(container_id), container_id); break; } offset += 4; } if (product_id[0] != '\0') { g_free(self->product_id); self->product_id = g_strndup((const gchar *)product_id, sizeof(product_id)); } return TRUE; } static gboolean fu_synaptics_rmi_firmware_parse_v0x(FuFirmware *firmware, GBytes *fw, GError **error) { FuSynapticsRmiFirmware *self = FU_SYNAPTICS_RMI_FIRMWARE(firmware); guint32 cfg_sz; guint32 img_sz; g_autoptr(GByteArray) st_img = NULL; /* main firmware */ st_img = fu_struct_rmi_img_parse_bytes(fw, 0x0, error); if (st_img == NULL) return FALSE; img_sz = fu_struct_rmi_img_get_image_size(st_img); if (img_sz > 0) { /* payload, then signature appended */ if (self->sig_size > 0) { guint32 sig_offset = img_sz - self->sig_size; if (!fu_synaptics_rmi_firmware_add_image(firmware, "sig", fw, RMI_IMG_FW_OFFSET + sig_offset, self->sig_size, error)) return FALSE; } if (!fu_synaptics_rmi_firmware_add_image(firmware, "ui", fw, RMI_IMG_FW_OFFSET, img_sz, error)) return FALSE; } /* config */ cfg_sz = fu_struct_rmi_img_get_config_size(st_img); if (cfg_sz > 0) { if (!fu_synaptics_rmi_firmware_add_image(firmware, "config", fw, RMI_IMG_FW_OFFSET + img_sz, cfg_sz, error)) return FALSE; } return TRUE; } static gboolean fu_synaptics_rmi_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuSynapticsRmiFirmware *self = FU_SYNAPTICS_RMI_FIRMWARE(firmware); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GByteArray) st_img = NULL; /* sanity check */ st_img = fu_struct_rmi_img_parse_bytes(fw, 0x0, error); if (st_img == NULL) return FALSE; if (bufsz % 2 != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "data not aligned to 16 bits"); return FALSE; } /* verify checksum */ self->checksum = fu_struct_rmi_img_get_checksum(st_img); if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint32 checksum_calculated = fu_synaptics_rmi_generate_checksum(buf + 4, bufsz - 4); if (self->checksum != checksum_calculated) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "checksum verification failed, got 0x%08x, actual 0x%08x", (guint)self->checksum, (guint)checksum_calculated); return FALSE; } } /* parse legacy image */ g_clear_pointer(&self->product_id, g_free); self->io = fu_struct_rmi_img_get_io_offset(st_img); self->bootloader_version = fu_struct_rmi_img_get_bootloader_version(st_img); if (self->io == 1) { self->build_id = fu_struct_rmi_img_get_fw_build_id(st_img); self->package_id = fu_struct_rmi_img_get_package_id(st_img); } self->product_id = fu_struct_rmi_img_get_product_id(st_img); self->product_info = fu_struct_rmi_img_get_product_info(st_img); fu_firmware_set_size(firmware, fu_struct_rmi_img_get_image_size(st_img)); /* parse partitions, but ignore lockdown */ switch (self->bootloader_version) { case 2: case 3: case 4: case 5: case 6: if ((self->io & 0x10) >> 1) self->sig_size = fu_struct_rmi_img_get_signature_size(st_img); if (!fu_synaptics_rmi_firmware_parse_v0x(firmware, fw, error)) return FALSE; self->kind = RMI_FIRMWARE_KIND_0X; break; case 16: case 17: if (!fu_synaptics_rmi_firmware_parse_v10(firmware, fw, error)) return FALSE; self->kind = RMI_FIRMWARE_KIND_10; break; default: g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unsupported image version 0x%02x", self->bootloader_version); return FALSE; } /* success */ return TRUE; } guint32 fu_synaptics_rmi_firmware_get_sig_size(FuSynapticsRmiFirmware *self) { return self->sig_size; } static GByteArray * fu_synaptics_rmi_firmware_write_v0x(FuFirmware *firmware, GError **error) { FuSynapticsRmiFirmware *self = FU_SYNAPTICS_RMI_FIRMWARE(firmware); gsize bufsz = 0; guint32 csum; g_autoptr(FuFirmware) img = NULL; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) st_img = fu_struct_rmi_img_new(); g_autoptr(GBytes) buf_blob = NULL; /* default image */ img = fu_firmware_get_image_by_id(firmware, "ui", error); if (img == NULL) return NULL; buf_blob = fu_firmware_write(img, error); if (buf_blob == NULL) return NULL; bufsz = g_bytes_get_size(buf_blob); /* create empty block */ fu_struct_rmi_img_set_bootloader_version(st_img, 0x2); /* not hierarchical */ if (self->product_id != NULL) { if (!fu_struct_rmi_img_set_product_id(st_img, self->product_id, error)) return NULL; } fu_struct_rmi_img_set_product_info(st_img, 0x1234); fu_struct_rmi_img_set_image_size(st_img, bufsz); fu_struct_rmi_img_set_config_size(st_img, bufsz); g_byte_array_append(buf, st_img->data, st_img->len); fu_byte_array_set_size(buf, RMI_IMG_FW_OFFSET + 0x4 + bufsz, 0x00); fu_memwrite_uint32(buf->data + RMI_IMG_FW_OFFSET, 0xDEAD, G_LITTLE_ENDIAN); /* img */ fu_memwrite_uint32(buf->data + RMI_IMG_FW_OFFSET + bufsz, 0xBEEF, G_LITTLE_ENDIAN); /* config */ /* fixup checksum */ csum = fu_synaptics_rmi_generate_checksum(buf->data + 4, buf->len - 4); fu_memwrite_uint32(buf->data + FU_STRUCT_RMI_IMG_OFFSET_CHECKSUM, csum, G_LITTLE_ENDIAN); /* success */ return g_steal_pointer(&buf); } static GByteArray * fu_synaptics_rmi_firmware_write_v10(FuFirmware *firmware, GError **error) { FuSynapticsRmiFirmware *self = FU_SYNAPTICS_RMI_FIRMWARE(firmware); gsize bufsz; guint32 csum; g_autoptr(FuFirmware) img = NULL; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) desc_hdr = fu_struct_rmi_container_descriptor_new(); g_autoptr(GByteArray) desc = fu_struct_rmi_container_descriptor_new(); g_autoptr(GBytes) buf_blob = NULL; /* header | desc_hdr | offset_table | desc | flash_config | * \0x0 \0x20 \0x24 \0x44 |0x48 */ guint32 offset_table[] = { GUINT32_TO_LE(RMI_IMG_FW_OFFSET + 0x24)}; /* offset to first descriptor */ fu_struct_rmi_container_descriptor_set_container_id(desc, FU_RMI_CONTAINER_ID_FLASH_CONFIG); fu_struct_rmi_container_descriptor_set_content_address(desc, RMI_IMG_FW_OFFSET + 0x44); /* default image */ img = fu_firmware_get_image_by_id(firmware, "ui", error); if (img == NULL) return NULL; buf_blob = fu_firmware_write(img, error); if (buf_blob == NULL) return NULL; bufsz = g_bytes_get_size(buf_blob); fu_struct_rmi_container_descriptor_set_content_length(desc, bufsz); /* create empty block */ fu_byte_array_set_size(buf, RMI_IMG_FW_OFFSET + 0x48, 0x00); buf->data[FU_STRUCT_RMI_IMG_OFFSET_IO_OFFSET] = 0x1; buf->data[FU_STRUCT_RMI_IMG_OFFSET_BOOTLOADER_VERSION] = 16; /* hierarchical */ if (self->product_id != NULL) { gsize product_id_sz = strlen(self->product_id); if (!fu_memcpy_safe(buf->data, buf->len, FU_STRUCT_RMI_IMG_OFFSET_PRODUCT_ID, /* dst */ (const guint8 *)self->product_id, product_id_sz, 0x0, /* src */ product_id_sz, error)) return NULL; } fu_memwrite_uint32(buf->data + FU_STRUCT_RMI_IMG_OFFSET_FW_BUILD_ID, 0x1234, G_LITTLE_ENDIAN); fu_memwrite_uint32(buf->data + FU_STRUCT_RMI_IMG_OFFSET_PACKAGE_ID, 0x4321, G_LITTLE_ENDIAN); fu_memwrite_uint16(buf->data + FU_STRUCT_RMI_IMG_OFFSET_PRODUCT_INFO, 0x3456, G_LITTLE_ENDIAN); fu_memwrite_uint32(buf->data + FU_STRUCT_RMI_IMG_OFFSET_IMAGE_SIZE, bufsz, G_LITTLE_ENDIAN); fu_memwrite_uint32(buf->data + FU_STRUCT_RMI_IMG_OFFSET_CONFIG_SIZE, bufsz, G_LITTLE_ENDIAN); fu_memwrite_uint32(buf->data + RMI_IMG_V10_CNTR_ADDR_OFFSET, RMI_IMG_FW_OFFSET, G_LITTLE_ENDIAN); /* hierarchical section */ fu_struct_rmi_container_descriptor_set_container_id(desc_hdr, FU_RMI_CONTAINER_ID_TOP_LEVEL); fu_struct_rmi_container_descriptor_set_content_length(desc_hdr, 0x1 * 4); /* bytes */ fu_struct_rmi_container_descriptor_set_content_address(desc_hdr, RMI_IMG_FW_OFFSET + 0x20); /* offset to table */ memcpy(buf->data + RMI_IMG_FW_OFFSET + 0x00, desc_hdr->data, desc_hdr->len); memcpy(buf->data + RMI_IMG_FW_OFFSET + 0x20, offset_table, sizeof(offset_table)); memcpy(buf->data + RMI_IMG_FW_OFFSET + 0x24, desc->data, desc->len); fu_memwrite_uint32(buf->data + RMI_IMG_FW_OFFSET + 0x44, 0xfeed, G_LITTLE_ENDIAN); /* flash_config */ /* fixup checksum */ csum = fu_synaptics_rmi_generate_checksum(buf->data + 4, buf->len - 4); fu_memwrite_uint32(buf->data + FU_STRUCT_RMI_IMG_OFFSET_CHECKSUM, csum, G_LITTLE_ENDIAN); /* success */ return g_steal_pointer(&buf); } static gboolean fu_synaptics_rmi_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { FuSynapticsRmiFirmware *self = FU_SYNAPTICS_RMI_FIRMWARE(firmware); const gchar *product_id; guint64 tmp; /* either 0x or 10 */ tmp = xb_node_query_text_as_uint(n, "kind", NULL); if (tmp != G_MAXUINT64) self->kind = tmp; /* any string */ product_id = xb_node_query_text(n, "product_id", NULL); if (product_id != NULL) { gsize product_id_sz = strlen(product_id); if (product_id_sz > RMI_PRODUCT_ID_LENGTH) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "product_id not supported, %u of %u bytes", (guint)product_id_sz, (guint)RMI_PRODUCT_ID_LENGTH); return FALSE; } g_free(self->product_id); self->product_id = g_strdup(product_id); } /* success */ return TRUE; } static GByteArray * fu_synaptics_rmi_firmware_write(FuFirmware *firmware, GError **error) { FuSynapticsRmiFirmware *self = FU_SYNAPTICS_RMI_FIRMWARE(firmware); /* two supported container formats */ if (self->kind == RMI_FIRMWARE_KIND_0X) return fu_synaptics_rmi_firmware_write_v0x(firmware, error); if (self->kind == RMI_FIRMWARE_KIND_10) return fu_synaptics_rmi_firmware_write_v10(firmware, error); /* not supported */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "kind not supported"); return NULL; } static void fu_synaptics_rmi_firmware_init(FuSynapticsRmiFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); fu_firmware_set_images_max(FU_FIRMWARE(self), RMI_IMG_MAX_CONTAINERS); } static void fu_synaptics_rmi_firmware_finalize(GObject *obj) { FuSynapticsRmiFirmware *self = FU_SYNAPTICS_RMI_FIRMWARE(obj); g_free(self->product_id); G_OBJECT_CLASS(fu_synaptics_rmi_firmware_parent_class)->finalize(obj); } static void fu_synaptics_rmi_firmware_class_init(FuSynapticsRmiFirmwareClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_synaptics_rmi_firmware_finalize; klass_firmware->parse = fu_synaptics_rmi_firmware_parse; klass_firmware->export = fu_synaptics_rmi_firmware_export; klass_firmware->build = fu_synaptics_rmi_firmware_build; klass_firmware->write = fu_synaptics_rmi_firmware_write; } FuFirmware * fu_synaptics_rmi_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_SYNAPTICS_RMI_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-firmware.h000066400000000000000000000007571460375044200244330ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_SYNAPTICS_RMI_FIRMWARE (fu_synaptics_rmi_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuSynapticsRmiFirmware, fu_synaptics_rmi_firmware, FU, SYNAPTICS_RMI_FIRMWARE, FuFirmware) FuFirmware * fu_synaptics_rmi_firmware_new(void); guint32 fu_synaptics_rmi_firmware_get_sig_size(FuSynapticsRmiFirmware *self); fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-hid-device.c000066400000000000000000000421531460375044200246070ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * Copyright (c) 2020 Synaptics Incorporated. * Copyright (C) 2012 Andrew Duggan * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-synaptics-rmi-hid-device.h" #include "fu-synaptics-rmi-v5-device.h" #include "fu-synaptics-rmi-v7-device.h" struct _FuSynapticsRmiHidDevice { FuSynapticsRmiDevice parent_instance; }; G_DEFINE_TYPE(FuSynapticsRmiHidDevice, fu_synaptics_rmi_hid_device, FU_TYPE_SYNAPTICS_RMI_DEVICE) #define RMI_WRITE_REPORT_ID 0x9 /* output report */ #define RMI_READ_ADDR_REPORT_ID 0xa /* output report */ #define RMI_READ_DATA_REPORT_ID 0xb /* input report */ #define RMI_ATTN_REPORT_ID 0xc /* input report */ #define RMI_SET_RMI_MODE_REPORT_ID 0xf /* feature report */ #define RMI_DEVICE_DEFAULT_TIMEOUT 2000 #define HID_RMI4_REPORT_ID 0 #define HID_RMI4_READ_INPUT_COUNT 1 #define HID_RMI4_READ_INPUT_DATA 2 #define HID_RMI4_READ_OUTPUT_ADDR 2 #define HID_RMI4_READ_OUTPUT_COUNT 4 #define HID_RMI4_WRITE_OUTPUT_COUNT 1 #define HID_RMI4_WRITE_OUTPUT_ADDR 2 #define HID_RMI4_WRITE_OUTPUT_DATA 4 #define HID_RMI4_FEATURE_MODE 1 #define HID_RMI4_ATTN_INTERRUPT_SOURCES 1 #define HID_RMI4_ATTN_DATA 2 /* * This bit disables whatever sleep mode may be selected by the sleep_mode * field and forces the device to run at full power without sleeping. */ #define RMI_F01_CRTL0_NOSLEEP_BIT (1 << 2) /* * msleep mode controls power management on the device and affects all * functions of the device. */ #define RMI_F01_CTRL0_SLEEP_MODE_MASK 0x03 #define RMI_SLEEP_MODE_NORMAL 0x00 #define RMI_SLEEP_MODE_SENSOR_SLEEP 0x01 #define FU_SYNAPTICS_RMI_HID_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ static GByteArray * fu_synaptics_rmi_hid_device_read(FuSynapticsRmiDevice *rmi_device, guint16 addr, gsize req_sz, GError **error) { FuSynapticsRmiHidDevice *self = FU_SYNAPTICS_RMI_HID_DEVICE(rmi_device); FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) req = g_byte_array_new(); /* maximum size */ if (req_sz > 0xffff) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "data to read was too long"); return NULL; } /* report then old 1 byte read count */ fu_byte_array_append_uint8(req, RMI_READ_ADDR_REPORT_ID); fu_byte_array_append_uint8(req, 0x0); /* address */ fu_byte_array_append_uint16(req, addr, G_LITTLE_ENDIAN); /* read output count */ fu_byte_array_append_uint16(req, req_sz, G_LITTLE_ENDIAN); /* request */ for (guint j = req->len; j < 21; j++) fu_byte_array_append_uint8(req, 0x0); fu_dump_full(G_LOG_DOMAIN, "ReportWrite", req->data, req->len, 80, FU_DUMP_FLAGS_NONE); if (!fu_io_channel_write_byte_array(io_channel, req, RMI_DEVICE_DEFAULT_TIMEOUT, FU_IO_CHANNEL_FLAG_SINGLE_SHOT | FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO, error)) return NULL; /* keep reading responses until we get enough data */ while (buf->len < req_sz) { guint8 input_count_sz = 0; g_autoptr(GByteArray) res = NULL; res = fu_io_channel_read_byte_array(io_channel, req_sz, RMI_DEVICE_DEFAULT_TIMEOUT, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error); if (res == NULL) return NULL; if (res->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "response zero sized"); return NULL; } fu_dump_full(G_LOG_DOMAIN, "ReportRead", res->data, res->len, 80, FU_DUMP_FLAGS_NONE); /* ignore non data report events */ if (res->data[HID_RMI4_REPORT_ID] != RMI_READ_DATA_REPORT_ID) { g_debug("ignoring report with ID 0x%02x", res->data[HID_RMI4_REPORT_ID]); continue; } if (res->len < HID_RMI4_READ_INPUT_DATA) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "response too small: 0x%02x", res->len); return NULL; } input_count_sz = res->data[HID_RMI4_READ_INPUT_COUNT]; if (input_count_sz == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "input count zero"); return NULL; } if (input_count_sz + (guint)HID_RMI4_READ_INPUT_DATA > res->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "underflow 0x%02x from expected 0x%02x", res->len, (guint)input_count_sz + HID_RMI4_READ_INPUT_DATA); return NULL; } g_byte_array_append(buf, res->data + HID_RMI4_READ_INPUT_DATA, input_count_sz); } fu_dump_full(G_LOG_DOMAIN, "DeviceRead", buf->data, buf->len, 80, FU_DUMP_FLAGS_NONE); return g_steal_pointer(&buf); } static GByteArray * fu_synaptics_rmi_hid_device_read_packet_register(FuSynapticsRmiDevice *rmi_device, guint16 addr, gsize req_sz, GError **error) { return fu_synaptics_rmi_hid_device_read(rmi_device, addr, req_sz, error); } static gboolean fu_synaptics_rmi_hid_device_write(FuSynapticsRmiDevice *rmi_device, guint16 addr, GByteArray *req, FuSynapticsRmiDeviceFlags flags, GError **error) { FuSynapticsRmiHidDevice *self = FU_SYNAPTICS_RMI_HID_DEVICE(rmi_device); FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); guint8 len = 0x0; g_autoptr(GByteArray) buf = g_byte_array_new(); /* check size */ if (req != NULL) { if (req->len > 0xff) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "data to write was too long"); return FALSE; } len = req->len; } /* report */ fu_byte_array_append_uint8(buf, RMI_WRITE_REPORT_ID); /* length */ fu_byte_array_append_uint8(buf, len); /* address */ fu_byte_array_append_uint16(buf, addr, G_LITTLE_ENDIAN); /* optional data */ if (req != NULL) g_byte_array_append(buf, req->data, req->len); /* pad out to 21 bytes for some reason */ for (guint i = buf->len; i < 21; i++) fu_byte_array_append_uint8(buf, 0x0); fu_dump_full(G_LOG_DOMAIN, "DeviceWrite", buf->data, buf->len, 80, FU_DUMP_FLAGS_NONE); return fu_io_channel_write_byte_array(io_channel, buf, RMI_DEVICE_DEFAULT_TIMEOUT, FU_IO_CHANNEL_FLAG_SINGLE_SHOT | FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO, error); } static gboolean fu_synaptics_rmi_hid_device_wait_for_attr(FuSynapticsRmiDevice *rmi_device, guint8 source_mask, guint timeout_ms, GError **error) { FuSynapticsRmiHidDevice *self = FU_SYNAPTICS_RMI_HID_DEVICE(rmi_device); FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); g_autoptr(GTimer) timer = g_timer_new(); /* wait for event from hardware */ while (g_timer_elapsed(timer, NULL) * 1000.f < timeout_ms) { g_autoptr(GByteArray) res = NULL; g_autoptr(GError) error_local = NULL; /* read from fd */ res = fu_io_channel_read_byte_array(io_channel, HID_RMI4_ATTN_INTERRUPT_SOURCES + 1, timeout_ms, FU_IO_CHANNEL_FLAG_NONE, &error_local); if (res == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) break; g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } fu_dump_full(G_LOG_DOMAIN, "ReportRead", res->data, res->len, 80, FU_DUMP_FLAGS_NONE); if (res->len < HID_RMI4_ATTN_INTERRUPT_SOURCES + 1) { g_debug("attr: ignoring small read of %u", res->len); continue; } if (res->data[HID_RMI4_REPORT_ID] != RMI_ATTN_REPORT_ID) { g_debug("attr: ignoring invalid report ID 0x%x", res->data[HID_RMI4_REPORT_ID]); continue; } /* success */ if (source_mask & res->data[HID_RMI4_ATTN_INTERRUPT_SOURCES]) return TRUE; /* wrong mask */ g_debug("source mask did not match: 0x%x", res->data[HID_RMI4_ATTN_INTERRUPT_SOURCES]); } /* urgh */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no attr report, timed out"); return FALSE; } typedef enum { HID_RMI4_MODE_MOUSE = 0, HID_RMI4_MODE_ATTN_REPORTS = 1, HID_RMI4_MODE_NO_PACKED_ATTN_REPORTS = 2, } FuSynapticsRmiHidMode; static gboolean fu_synaptics_rmi_hid_device_set_mode(FuSynapticsRmiHidDevice *self, FuSynapticsRmiHidMode mode, GError **error) { const guint8 data[] = {0x0f, mode}; fu_dump_raw(G_LOG_DOMAIN, "SetMode", data, sizeof(data)); return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(sizeof(data)), (guint8 *)data, NULL, FU_SYNAPTICS_RMI_HID_DEVICE_IOCTL_TIMEOUT, error); } static gboolean fu_synaptics_rmi_hid_device_open(FuDevice *device, GError **error) { FuSynapticsRmiHidDevice *self = FU_SYNAPTICS_RMI_HID_DEVICE(device); /* FuUdevDevice->open */ if (!FU_DEVICE_CLASS(fu_synaptics_rmi_hid_device_parent_class)->open(device, error)) return FALSE; /* set up touchpad so we can query it */ if (!fu_synaptics_rmi_hid_device_set_mode(self, HID_RMI4_MODE_ATTN_REPORTS, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_synaptics_rmi_hid_device_close(FuDevice *device, GError **error) { FuSynapticsRmiHidDevice *self = FU_SYNAPTICS_RMI_HID_DEVICE(device); g_autoptr(GError) error_local = NULL; /* turn it back to mouse mode */ if (!fu_synaptics_rmi_hid_device_set_mode(self, HID_RMI4_MODE_MOUSE, &error_local)) { /* if just detached for replug, swallow error */ if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED)) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } g_debug("ignoring: %s", error_local->message); } /* FuUdevDevice->close */ return FU_DEVICE_CLASS(fu_synaptics_rmi_hid_device_parent_class)->close(device, error); } static gboolean fu_synaptics_rmi_hid_device_rebind_driver(FuSynapticsRmiDevice *self, GError **error) { GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(self)); const gchar *hid_id; const gchar *driver; const gchar *subsystem; g_autofree gchar *fn_rebind = NULL; g_autofree gchar *fn_unbind = NULL; g_autoptr(GUdevDevice) parent_hid = NULL; g_autoptr(GUdevDevice) parent_phys = NULL; g_auto(GStrv) hid_strs = NULL; /* get actual HID node */ parent_hid = g_udev_device_get_parent_with_subsystem(udev_device, "hid", NULL); if (parent_hid == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no HID parent device for %s", g_udev_device_get_sysfs_path(udev_device)); return FALSE; } /* build paths */ parent_phys = g_udev_device_get_parent_with_subsystem(udev_device, "i2c", NULL); if (parent_phys == NULL) { parent_phys = g_udev_device_get_parent_with_subsystem(udev_device, "usb", NULL); if (parent_phys == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no parent device for %s", g_udev_device_get_sysfs_path(parent_hid)); return FALSE; } } /* find the physical ID to use for the rebind */ hid_strs = g_strsplit(g_udev_device_get_sysfs_path(parent_phys), "/", -1); hid_id = hid_strs[g_strv_length(hid_strs) - 1]; if (hid_id == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no HID_PHYS in %s", g_udev_device_get_sysfs_path(parent_phys)); return FALSE; } g_debug("HID_PHYS: %s", hid_id); driver = g_udev_device_get_driver(parent_phys); subsystem = g_udev_device_get_subsystem(parent_phys); fn_rebind = g_build_filename("/sys/bus/", subsystem, "drivers", driver, "bind", NULL); fn_unbind = g_build_filename("/sys/bus/", subsystem, "drivers", driver, "unbind", NULL); /* unbind hidraw, then bind it again to get a replug */ fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); if (!fu_synaptics_rmi_device_writeln(fn_unbind, hid_id, error)) return FALSE; if (!fu_synaptics_rmi_device_writeln(fn_rebind, hid_id, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_synaptics_rmi_hid_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device); FuSynapticsRmiFunction *f34; f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; if (f34->function_version == 0x0 || f34->function_version == 0x1) { if (!fu_synaptics_rmi_v5_device_detach(device, progress, error)) return FALSE; } else if (f34->function_version == 0x2) { if (!fu_synaptics_rmi_v7_device_detach(device, progress, error)) return FALSE; } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "f34 function version 0x%02x unsupported", f34->function_version); return FALSE; } return fu_synaptics_rmi_hid_device_rebind_driver(self, error); } static gboolean fu_synaptics_rmi_hid_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device); /* sanity check */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } /* reset device */ if (!fu_synaptics_rmi_device_reset(self, error)) return FALSE; /* rebind to rescan PDT with new firmware running */ return fu_synaptics_rmi_hid_device_rebind_driver(self, error); } static gboolean fu_synaptics_rmi_hid_device_set_page(FuSynapticsRmiDevice *self, guint8 page, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new(); fu_byte_array_append_uint8(req, page); if (!fu_synaptics_rmi_device_write(self, RMI_DEVICE_PAGE_SELECT_REGISTER, req, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to set RMA page 0x%x: ", page); return FALSE; } return TRUE; } static gboolean fu_synaptics_rmi_hid_device_probe(FuDevice *device, GError **error) { return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); } static gboolean fu_synaptics_rmi_hid_device_disable_sleep(FuSynapticsRmiDevice *rmi_device, GError **error) { FuSynapticsRmiFunction *f01; g_autoptr(GByteArray) f01_control0 = NULL; f01 = fu_synaptics_rmi_device_get_function(rmi_device, 0x34, error); if (f01 == NULL) return FALSE; f01_control0 = fu_synaptics_rmi_device_read(rmi_device, f01->control_base, 0x1, error); if (f01_control0 == NULL) { g_prefix_error(error, "failed to write get f01_control0: "); return FALSE; } f01_control0->data[0] |= RMI_F01_CRTL0_NOSLEEP_BIT; f01_control0->data[0] = (f01_control0->data[0] & ~RMI_F01_CTRL0_SLEEP_MODE_MASK) | RMI_SLEEP_MODE_NORMAL; if (!fu_synaptics_rmi_device_write(rmi_device, f01->control_base, f01_control0, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write f01_control0: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_rmi_hid_device_query_status(FuSynapticsRmiDevice *rmi_device, GError **error) { FuSynapticsRmiFunction *f34; f34 = fu_synaptics_rmi_device_get_function(rmi_device, 0x34, error); if (f34 == NULL) return FALSE; if (f34->function_version == 0x0 || f34->function_version == 0x1) { return fu_synaptics_rmi_v5_device_query_status(rmi_device, error); } if (f34->function_version == 0x2) { return fu_synaptics_rmi_v7_device_query_status(rmi_device, error); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "f34 function version 0x%02x unsupported", f34->function_version); return FALSE; } static void fu_synaptics_rmi_hid_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 3, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 88, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 7, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_synaptics_rmi_hid_device_init(FuSynapticsRmiHidDevice *self) { fu_device_set_name(FU_DEVICE(self), "Touchpad"); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_PROBE_COMPLETE); fu_synaptics_rmi_device_set_max_page(FU_SYNAPTICS_RMI_DEVICE(self), 0xff); } static void fu_synaptics_rmi_hid_device_class_init(FuSynapticsRmiHidDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_CLASS(klass); klass_device->attach = fu_synaptics_rmi_hid_device_attach; klass_device->detach = fu_synaptics_rmi_hid_device_detach; klass_device->probe = fu_synaptics_rmi_hid_device_probe; klass_device->open = fu_synaptics_rmi_hid_device_open; klass_device->close = fu_synaptics_rmi_hid_device_close; klass_device->set_progress = fu_synaptics_rmi_hid_device_set_progress; klass_rmi->write = fu_synaptics_rmi_hid_device_write; klass_rmi->read = fu_synaptics_rmi_hid_device_read; klass_rmi->wait_for_attr = fu_synaptics_rmi_hid_device_wait_for_attr; klass_rmi->set_page = fu_synaptics_rmi_hid_device_set_page; klass_rmi->query_status = fu_synaptics_rmi_hid_device_query_status; klass_rmi->read_packet_register = fu_synaptics_rmi_hid_device_read_packet_register; klass_rmi->disable_sleep = fu_synaptics_rmi_hid_device_disable_sleep; } fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-hid-device.h000066400000000000000000000006731460375044200246150ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (c) 2012 Synaptics Incorporated. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-synaptics-rmi-device.h" #define FU_TYPE_SYNAPTICS_RMI_HID_DEVICE (fu_synaptics_rmi_hid_device_get_type()) G_DECLARE_FINAL_TYPE(FuSynapticsRmiHidDevice, fu_synaptics_rmi_hid_device, FU, SYNAPTICS_RMI_HID_DEVICE, FuSynapticsRmiDevice) fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-plugin.c000066400000000000000000000021471460375044200241030ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-rmi-firmware.h" #include "fu-synaptics-rmi-hid-device.h" #include "fu-synaptics-rmi-plugin.h" #include "fu-synaptics-rmi-ps2-device.h" struct _FuSynapticsRmiPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuSynapticsRmiPlugin, fu_synaptics_rmi_plugin, FU_TYPE_PLUGIN) static void fu_synaptics_rmi_plugin_init(FuSynapticsRmiPlugin *self) { } static void fu_synaptics_rmi_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "hidraw"); fu_plugin_add_udev_subsystem(plugin, "serio"); fu_plugin_add_device_gtype(plugin, FU_TYPE_SYNAPTICS_RMI_HID_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_SYNAPTICS_RMI_PS2_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_SYNAPTICS_RMI_FIRMWARE); } static void fu_synaptics_rmi_plugin_class_init(FuSynapticsRmiPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_synaptics_rmi_plugin_constructed; } fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-plugin.h000066400000000000000000000004301460375044200241010ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuSynapticsRmiPlugin, fu_synaptics_rmi_plugin, FU, SYNAPTICS_RMI_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-ps2-device.c000066400000000000000000000700241460375044200245450ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * Copyright (c) 2020 Synaptics Incorporated. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-rmi-ps2-device.h" #include "fu-synaptics-rmi-v5-device.h" #include "fu-synaptics-rmi-v7-device.h" struct _FuSynapticsRmiPs2Device { FuSynapticsRmiDevice parent_instance; }; G_DEFINE_TYPE(FuSynapticsRmiPs2Device, fu_synaptics_rmi_ps2_device, FU_TYPE_SYNAPTICS_RMI_DEVICE) enum EPS2DataPortCommand { edpAuxFullRMIBackDoor = 0x7F, edpAuxAccessModeByte1 = 0xE0, edpAuxAccessModeByte2 = 0xE1, edpAuxIBMReadSecondaryID = 0xE1, edpAuxSetScaling1To1 = 0xE6, edpAuxSetScaling2To1 = 0xE7, edpAuxSetResolution = 0xE8, edpAuxStatusRequest = 0xE9, edpAuxSetStreamMode = 0xEA, edpAuxReadData = 0xEB, edpAuxResetWrapMode = 0xEC, edpAuxSetWrapMode = 0xEE, edpAuxSetRemoteMode = 0xF0, edpAuxReadDeviceType = 0xF2, edpAuxSetSampleRate = 0xF3, edpAuxEnable = 0xF4, edpAuxDisable = 0xF5, edpAuxSetDefault = 0xF6, edpAuxResend = 0xFE, edpAuxReset = 0xFF, }; typedef enum { esdrTouchPad = 0x47, esdrStyk = 0x46, esdrControlBar = 0x44, esdrRGBControlBar = 0x43, } ESynapticsDeviceResponse; enum EStatusRequestSequence { esrIdentifySynaptics = 0x00, esrReadTouchPadModes = 0x01, esrReadModeByte = 0x01, esrReadEdgeMargins = 0x02, esrReadCapabilities = 0x02, esrReadModelID = 0x03, esrReadCompilationDate = 0x04, esrReadSerialNumberPrefix = 0x06, esrReadSerialNumberSuffix = 0x07, esrReadResolutions = 0x08, esrReadExtraCapabilities1 = 0x09, esrReadExtraCapabilities2 = 0x0A, esrReadExtraCapabilities3 = 0x0B, esrReadExtraCapabilities4 = 0x0C, esrReadExtraCapabilities5 = 0x0D, esrReadCoordinates = 0x0D, esrReadExtraCapabilities6 = 0x0E, esrReadExtraCapabilities7 = 0x0F, }; enum EPS2DataPortStatus { edpsAcknowledge = 0xFA, edpsError = 0xFC, edpsResend = 0xFE, edpsTimeOut = 0x100 }; enum ESetSampleRateSequence { essrSetModeByte1 = 0x0A, essrSetModeByte2 = 0x14, essrSetModeByte3 = 0x28, essrSetModeByte4 = 0x3C, essrSetDeluxeModeByte1 = 0x0A, essrSetDeluxeModeByte2 = 0x3C, essrSetDeluxeModeByte3 = 0xC8, essrFastRecalibrate = 0x50, essrPassThroughCommandTunnel = 0x28 }; enum EDeviceType { edtUnknown, edtTouchPad, }; enum EStickDeviceType { esdtNone = 0, esdtIBM, esdtJYTSyna = 5, esdtSynaptics = 6, esdtUnknown = 0xFFFFFFFF }; static gboolean fu_synaptics_rmi_ps2_device_read_ack(FuSynapticsRmiPs2Device *self, guint8 *pbuf, GError **error) { FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); for (guint i = 0; i < 60; i++) { g_autoptr(GError) error_local = NULL; if (!fu_io_channel_read_raw(io_channel, pbuf, 0x1, NULL, 10, FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO, &error_local)) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) { g_warning("read timed out: %u", i); fu_device_sleep(FU_DEVICE(self), 1); /* ms */ continue; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } return TRUE; } g_set_error(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "read timed out"); return FALSE; } /* read a single byte from the touchpad */ static gboolean fu_synaptics_rmi_ps2_device_read_byte(FuSynapticsRmiPs2Device *self, guint8 *pbuf, guint timeout, GError **error) { FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); g_return_val_if_fail(timeout > 0, FALSE); return fu_io_channel_read_raw(io_channel, pbuf, 0x1, NULL, timeout, FU_IO_CHANNEL_FLAG_NONE, error); } /* write a single byte to the touchpad and the read the acknowledge */ static gboolean fu_synaptics_rmi_ps2_device_write_byte(FuSynapticsRmiPs2Device *self, guint8 buf, guint timeout, FuSynapticsRmiDeviceFlags flags, GError **error) { FuIOChannel *io_channel = fu_udev_device_get_io_channel(FU_UDEV_DEVICE(self)); gboolean do_write = TRUE; g_return_val_if_fail(timeout > 0, FALSE); for (guint i = 0;; i++) { guint8 res = 0; g_autoptr(GError) error_local = NULL; if (do_write) { if (!fu_io_channel_write_raw(io_channel, &buf, sizeof(buf), timeout, FU_IO_CHANNEL_FLAG_FLUSH_INPUT | FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO, error)) return FALSE; } do_write = FALSE; for (;;) { /* attempt to read acknowledge... */ if (!fu_synaptics_rmi_ps2_device_read_ack(self, &res, &error_local)) { if (i > 3) { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "read ack failed: "); return FALSE; } g_warning("read ack failed: %s, retrying", error_local->message); break; } if (res == edpsAcknowledge) return TRUE; if (res == edpsResend) { do_write = TRUE; g_debug("resend"); fu_device_sleep(FU_DEVICE(self), 1000); /* ms */ break; } if (res == edpsError) { do_write = TRUE; g_debug("error"); fu_device_sleep(FU_DEVICE(self), 10); /* ms */ break; } g_debug("other response: 0x%x", res); fu_device_sleep(FU_DEVICE(self), 10); /* ms */ } if (i >= 3) { if (flags & FU_SYNAPTICS_RMI_DEVICE_FLAG_ALLOW_FAILURE) { /* just break without error return because FW * will not return ACK for commands like RESET */ break; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot write byte after retries"); return FALSE; } } /* success */ return TRUE; } static gboolean fu_synaptics_rmi_ps2_device_set_resolution_sequence(FuSynapticsRmiPs2Device *self, guint8 arg, gboolean send_e6s, GError **error) { /* send set scaling twice if send_e6s */ for (gint i = send_e6s ? 2 : 1; i > 0; --i) { if (!fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxSetScaling1To1, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) return FALSE; } for (gint i = 3; i >= 0; --i) { guint8 ucTwoBitArg = (arg >> (i * 2)) & 0x3; if (!fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxSetResolution, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { return FALSE; } if (!fu_synaptics_rmi_ps2_device_write_byte(self, ucTwoBitArg, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_rmi_ps2_device_status_request_sequence(FuSynapticsRmiPs2Device *self, guint8 ucArgument, guint32 *buf, GError **error) { gboolean success = FALSE; /* allow 3 retries */ for (guint i = 0; i < 3; ++i) { g_autoptr(GError) error_local = NULL; if (!fu_synaptics_rmi_ps2_device_set_resolution_sequence(self, ucArgument, FALSE, &error_local)) { g_debug("failed set try #%u: %s", i, error_local->message); continue; } if (!fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxStatusRequest, 10, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, &error_local)) { g_debug("failed write try #%u: %s", i, error_local->message); continue; } success = TRUE; break; } if (success == FALSE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed"); return FALSE; } /* read the response from the status request */ for (gint i = 0; i < 3; ++i) { guint8 tmp = 0x0; if (!fu_synaptics_rmi_ps2_device_read_byte(self, &tmp, 10, error)) { g_prefix_error(error, "failed to read byte: "); return FALSE; } *buf = ((*buf) << 8) | tmp; } return TRUE; } static gboolean fu_synaptics_rmi_ps2_device_sample_rate_sequence(FuSynapticsRmiPs2Device *self, guint8 param, guint8 arg, gboolean send_e6s, GError **error) { /* allow 3 retries */ for (guint i = 0;; i++) { g_autoptr(GError) error_local = NULL; if (i > 0) { /* always send two E6s when retrying */ send_e6s = TRUE; } if (!fu_synaptics_rmi_ps2_device_set_resolution_sequence(self, arg, send_e6s, &error_local) || !fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxSetSampleRate, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, &error_local) || !fu_synaptics_rmi_ps2_device_write_byte(self, param, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, &error_local)) { if (i > 3) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } g_warning("failed, will retry: %s", error_local->message); continue; } break; } /* success */ return TRUE; } static gboolean fu_synaptics_rmi_ps2_device_detect_synaptics_styk(FuSynapticsRmiPs2Device *self, gboolean *result, GError **error) { guint8 buf; if (!fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxIBMReadSecondaryID, 10, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write IBMReadSecondaryID(0xE1): "); return FALSE; } if (!fu_synaptics_rmi_ps2_device_read_byte(self, &buf, 10, error)) { g_prefix_error(error, "failed to receive IBMReadSecondaryID: "); return FALSE; } if (buf == esdtJYTSyna || buf == esdtSynaptics) *result = TRUE; return TRUE; } static gboolean fu_synaptics_rmi_ps2_device_query_build_id(FuSynapticsRmiDevice *rmi_device, guint32 *build_id, GError **error) { FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device); guint32 buf = 0; gboolean is_synaptics_styk = FALSE; ESynapticsDeviceResponse esdr; if (!fu_synaptics_rmi_ps2_device_status_request_sequence(self, esrIdentifySynaptics, &buf, error)) { g_prefix_error(error, "failed to request IdentifySynaptics: "); return FALSE; } g_debug("identify Synaptics response = 0x%x", buf); esdr = (buf & 0xFF00) >> 8; if (!fu_synaptics_rmi_ps2_device_detect_synaptics_styk(self, &is_synaptics_styk, error)) { g_prefix_error(error, "failed to detect Synaptics styk: "); return FALSE; } fu_synaptics_rmi_device_set_iepmode(rmi_device, FALSE); if (esdr == esdrTouchPad || is_synaptics_styk) { /* Get the firmware id from the Extra Capabilities 2 Byte * The firmware id is located in bits 0 - 23 */ if (!fu_synaptics_rmi_ps2_device_status_request_sequence(self, esrReadExtraCapabilities2, build_id, error)) { g_prefix_error(error, "failed to read extraCapabilities2: "); return FALSE; } } return TRUE; } static gboolean fu_synaptics_rmi_ps2_device_query_product_sub_id(FuSynapticsRmiDevice *rmi_device, guint8 *sub_id, GError **error) { FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device); guint32 buf = 0; if (!fu_synaptics_rmi_ps2_device_status_request_sequence(self, esrReadCapabilities, &buf, error)) { g_prefix_error(error, "failed to status_request_sequence read esrReadCapabilities: "); return FALSE; } *sub_id = (buf >> 8) & 0xFF; return TRUE; } static gboolean fu_synaptics_rmi_ps2_device_enter_iep_mode(FuSynapticsRmiDevice *rmi_device, GError **error) { FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device); /* disable stream */ if (!fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxDisable, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to disable stream mode: "); return FALSE; } /* enable RMI mode */ if (!fu_synaptics_rmi_ps2_device_sample_rate_sequence(self, essrSetModeByte2, edpAuxFullRMIBackDoor, FALSE, error)) { g_prefix_error(error, "failed to enter RMI mode: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_rmi_ps2_device_write_rmi_register(FuSynapticsRmiPs2Device *self, guint8 addr, const guint8 *buf, guint8 buflen, guint timeout, FuSynapticsRmiDeviceFlags flags, GError **error) { g_return_val_if_fail(timeout > 0, FALSE); if (!fu_synaptics_rmi_device_enter_iep_mode(FU_SYNAPTICS_RMI_DEVICE(self), FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) return FALSE; if (!fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxSetScaling2To1, timeout, flags, error)) { g_prefix_error(error, "failed to edpAuxSetScaling2To1: "); return FALSE; } if (!fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxSetSampleRate, timeout, flags, error)) { g_prefix_error(error, "failed to edpAuxSetSampleRate: "); return FALSE; } if (!fu_synaptics_rmi_ps2_device_write_byte(self, addr, timeout, flags, error)) { g_prefix_error(error, "failed to write address: "); return FALSE; } for (guint8 i = 0; i < buflen; i++) { if (!fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxSetSampleRate, timeout, flags, error)) { g_prefix_error(error, "failed to set byte %u: ", i); return FALSE; } if (!fu_synaptics_rmi_ps2_device_write_byte(self, buf[i], timeout, flags, error)) { g_prefix_error(error, "failed to write byte %u: ", i); return FALSE; } } /* success */ fu_device_sleep(FU_DEVICE(self), 20); /* ms */ return TRUE; } static gboolean fu_synaptics_rmi_ps2_device_read_rmi_register(FuSynapticsRmiPs2Device *self, guint8 addr, guint8 *buf, GError **error) { g_return_val_if_fail(buf != NULL, FALSE); if (!fu_synaptics_rmi_device_enter_iep_mode(FU_SYNAPTICS_RMI_DEVICE(self), FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) return FALSE; for (guint retries = 0;; retries++) { g_autoptr(GError) error_local = NULL; if (!fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxSetScaling2To1, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error) || !fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxSetSampleRate, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error) || !fu_synaptics_rmi_ps2_device_write_byte(self, addr, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error) || !fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxStatusRequest, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write command in Read RMI register: "); return FALSE; } if (!fu_synaptics_rmi_ps2_device_read_byte(self, buf, 10, &error_local)) { if (retries++ > 2) { g_propagate_prefixed_error( error, g_steal_pointer(&error_local), "failed to read byte @0x%x after %u retries: ", addr, retries); return FALSE; } g_debug("failed to read byte @0x%x: %s", addr, error_local->message); continue; } /* success */ break; } /* success */ fu_device_sleep(FU_DEVICE(self), 20); /* ms */ return TRUE; } static GByteArray * fu_synaptics_rmi_ps2_device_read_rmi_packet_register(FuSynapticsRmiPs2Device *self, guint8 addr, guint req_sz, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); if (!fu_synaptics_rmi_device_enter_iep_mode(FU_SYNAPTICS_RMI_DEVICE(self), FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) return NULL; if (!fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxSetScaling2To1, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error) || !fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxSetSampleRate, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error) || !fu_synaptics_rmi_ps2_device_write_byte(self, addr, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error) || !fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxStatusRequest, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write command in Read RMI Packet Register: "); return NULL; } for (guint i = 0; i < req_sz; ++i) { guint8 tmp = 0; if (!fu_synaptics_rmi_ps2_device_read_byte(self, &tmp, 10, error)) { g_prefix_error(error, "failed to read byte %u: ", i); return NULL; } fu_byte_array_append_uint8(buf, tmp); } fu_device_sleep(FU_DEVICE(self), 20); /* ms */ return g_steal_pointer(&buf); } static gboolean fu_synaptics_rmi_ps2_device_query_status(FuSynapticsRmiDevice *rmi_device, GError **error) { FuSynapticsRmiFunction *f34; g_debug("ps2 query status"); f34 = fu_synaptics_rmi_device_get_function(rmi_device, 0x34, error); if (f34 == NULL) return FALSE; if (f34->function_version == 0x0 || f34->function_version == 0x1) { return fu_synaptics_rmi_v5_device_query_status(rmi_device, error); } if (f34->function_version == 0x2) { return fu_synaptics_rmi_v7_device_query_status(rmi_device, error); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "f34 function version 0x%02x unsupported", f34->function_version); return FALSE; } static gboolean fu_synaptics_rmi_ps2_device_set_page(FuSynapticsRmiDevice *rmi_device, guint8 page, GError **error) { FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device); if (!fu_synaptics_rmi_ps2_device_write_rmi_register(self, RMI_DEVICE_PAGE_SELECT_REGISTER, &page, 1, 20, FALSE, error)) { g_prefix_error(error, "failed to write page %u: ", page); return FALSE; } return TRUE; } static GByteArray * fu_synaptics_rmi_ps2_device_read(FuSynapticsRmiDevice *rmi_device, guint16 addr, gsize req_sz, GError **error) { FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device); g_autoptr(GByteArray) buf = NULL; g_autofree gchar *dump = NULL; if (!fu_synaptics_rmi_device_set_page(rmi_device, addr >> 8, error)) { g_prefix_error(error, "failed to set RMI page: "); return NULL; } for (guint retries = 0;; retries++) { buf = g_byte_array_new(); for (guint i = 0; i < req_sz; i++) { guint8 tmp = 0x0; if (!fu_synaptics_rmi_ps2_device_read_rmi_register( self, (guint8)((addr & 0x00FF) + i), &tmp, error)) { g_prefix_error(error, "failed register read 0x%x: ", addr + i); return NULL; } fu_byte_array_append_uint8(buf, tmp); } if (buf->len != req_sz) { g_debug("buf->len(%u) != req_sz(%u)", buf->len, (guint)req_sz); if (retries++ > 2) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "buffer length did not match: %u vs %u", buf->len, (guint)req_sz); return NULL; } continue; } break; } dump = g_strdup_printf("R %x", addr); fu_dump_full(G_LOG_DOMAIN, dump, buf->data, buf->len, 80, FU_DUMP_FLAGS_NONE); return g_steal_pointer(&buf); } static GByteArray * fu_synaptics_rmi_ps2_device_read_packet_register(FuSynapticsRmiDevice *rmi_device, guint16 addr, gsize req_sz, GError **error) { FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device); g_autoptr(GByteArray) buf = NULL; g_autofree gchar *dump = g_strdup_printf("R %x", addr); if (!fu_synaptics_rmi_device_set_page(rmi_device, addr >> 8, error)) { g_prefix_error(error, "failed to set RMI page: "); return NULL; } buf = fu_synaptics_rmi_ps2_device_read_rmi_packet_register(self, addr, req_sz, error); if (buf == NULL) { g_prefix_error(error, "failed packet register read %x: ", addr); return NULL; } fu_dump_full(G_LOG_DOMAIN, dump, buf->data, buf->len, 80, FU_DUMP_FLAGS_NONE); return g_steal_pointer(&buf); } static gboolean fu_synaptics_rmi_ps2_device_write(FuSynapticsRmiDevice *rmi_device, guint16 addr, GByteArray *req, FuSynapticsRmiDeviceFlags flags, GError **error) { FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device); g_autofree gchar *str = g_strdup_printf("W %x", addr); if (!fu_synaptics_rmi_device_set_page(rmi_device, addr >> 8, error)) { g_prefix_error(error, "failed to set RMI page: "); return FALSE; } if (!fu_synaptics_rmi_ps2_device_write_rmi_register(self, addr & 0x00FF, req->data, req->len, 1000, /* timeout */ flags, error)) { g_prefix_error(error, "failed to write register %x: ", addr); return FALSE; } fu_dump_full(G_LOG_DOMAIN, str, req->data, req->len, 80, FU_DUMP_FLAGS_NONE); return TRUE; } static gboolean fu_synaptics_rmi_ps2_device_write_bus_select(FuSynapticsRmiDevice *rmi_device, guint8 bus, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new(); fu_byte_array_append_uint8(req, bus); if (!fu_synaptics_rmi_ps2_device_write(rmi_device, RMI_DEVICE_BUS_SELECT_REGISTER, req, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write rmi register %u: ", bus); return FALSE; } return TRUE; } static gboolean fu_synaptics_rmi_ps2_device_probe(FuDevice *device, GError **error) { /* psmouse is the usual mode, but serio is needed for update */ if (g_strcmp0(fu_udev_device_get_driver(FU_UDEV_DEVICE(device)), "serio_raw") == 0) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else { fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "platform", error); } static gboolean fu_synaptics_rmi_ps2_device_open(FuDevice *device, GError **error) { FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(device); guint8 buf[2] = {0x0}; /* FuUdevDevice->open */ if (!FU_DEVICE_CLASS(fu_synaptics_rmi_ps2_device_parent_class)->open(device, error)) return FALSE; /* in serio_raw mode */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { /* clear out any data in the serio_raw queue */ for (guint i = 0; i < 0xffff; i++) { guint8 tmp = 0; if (!fu_synaptics_rmi_ps2_device_read_byte(self, &tmp, 20, NULL)) break; } /* send reset -- may take 300-500ms */ if (!fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxReset, 600, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to reset: "); return FALSE; } /* read the 0xAA 0x00 announcing the touchpad is ready */ if (!fu_synaptics_rmi_ps2_device_read_byte(self, &buf[0], 500, error) || !fu_synaptics_rmi_ps2_device_read_byte(self, &buf[1], 500, error)) { g_prefix_error(error, "failed to read 0xAA00: "); return FALSE; } if (buf[0] != 0xAA || buf[1] != 0x00) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to read 0xAA00, got 0x%02X%02X: ", buf[0], buf[1]); return FALSE; } /* disable the device so that it stops reporting finger data */ if (!fu_synaptics_rmi_ps2_device_write_byte(self, edpAuxDisable, 50, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to disable stream mode: "); return FALSE; } } /* success */ return TRUE; } static gboolean fu_synaptics_rmi_ps2_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device); FuSynapticsRmiFunction *f34; /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in bootloader mode, skipping"); return TRUE; } /* put in serio_raw mode so that we can do register writes */ if (!fu_udev_device_write_sysfs(FU_UDEV_DEVICE(device), "drvctl", "serio_raw", error)) { g_prefix_error(error, "failed to write to drvctl: "); return FALSE; } /* rescan device */ if (!fu_device_close(device, error)) return FALSE; if (!fu_device_rescan(device, error)) return FALSE; if (!fu_device_open(device, error)) return FALSE; f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; if (f34->function_version == 0x0 || f34->function_version == 0x1) { if (!fu_synaptics_rmi_v5_device_detach(device, progress, error)) return FALSE; } else if (f34->function_version == 0x2) { if (!fu_synaptics_rmi_v7_device_detach(device, progress, error)) return FALSE; } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "f34 function version 0x%02x unsupported", f34->function_version); return FALSE; } /* set iepmode before querying device forcibly because of FW requirement */ if (!fu_synaptics_rmi_device_enter_iep_mode(self, FU_SYNAPTICS_RMI_DEVICE_FLAG_FORCE, error)) return FALSE; if (!fu_synaptics_rmi_ps2_device_query_status(self, error)) { g_prefix_error(error, "failed to query status after detach: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_rmi_ps2_device_setup(FuDevice *device, GError **error) { /* we can only scan the PDT in serio_raw mode */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) return TRUE; return FU_DEVICE_CLASS(fu_synaptics_rmi_ps2_device_parent_class)->setup(device, error); } static gboolean fu_synaptics_rmi_ps2_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuSynapticsRmiDevice *rmi_device = FU_SYNAPTICS_RMI_DEVICE(device); /* sanity check */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } /* set iepmode before reset device forcibly because of FW requirement */ fu_synaptics_rmi_device_set_iepmode(rmi_device, FALSE); /* delay after writing */ fu_device_sleep_full(device, 2000, progress); /* ms */ /* reset device */ if (!fu_synaptics_rmi_device_enter_iep_mode(rmi_device, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) return FALSE; if (!fu_synaptics_rmi_device_reset(rmi_device, error)) { g_prefix_error(error, "failed to reset device: "); return FALSE; } fu_device_sleep_full(device, 5000, progress); /* ms */ /* back to psmouse */ if (!fu_udev_device_write_sysfs(FU_UDEV_DEVICE(device), "drvctl", "psmouse", error)) { g_prefix_error(error, "failed to write to drvctl: "); return FALSE; } /* rescan device */ return fu_device_rescan(device, error); } static void fu_synaptics_rmi_ps2_device_init(FuSynapticsRmiPs2Device *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_set_name(FU_DEVICE(self), "TouchStyk"); fu_device_set_vendor(FU_DEVICE(self), "Synaptics"); fu_device_add_vendor_id(FU_DEVICE(self), "HIDRAW:0x06CB"); fu_synaptics_rmi_device_set_max_page(FU_SYNAPTICS_RMI_DEVICE(self), 0x1); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE); } static gboolean fu_synaptics_rmi_ps2_device_wait_for_attr(FuSynapticsRmiDevice *rmi_device, guint8 source_mask, guint timeout_ms, GError **error) { fu_device_sleep(FU_DEVICE(rmi_device), timeout_ms); return TRUE; } static void fu_synaptics_rmi_ps2_device_class_init(FuSynapticsRmiPs2DeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_CLASS(klass); klass_device->attach = fu_synaptics_rmi_ps2_device_attach; klass_device->detach = fu_synaptics_rmi_ps2_device_detach; klass_device->setup = fu_synaptics_rmi_ps2_device_setup; klass_device->probe = fu_synaptics_rmi_ps2_device_probe; klass_device->open = fu_synaptics_rmi_ps2_device_open; klass_rmi->read = fu_synaptics_rmi_ps2_device_read; klass_rmi->write = fu_synaptics_rmi_ps2_device_write; klass_rmi->set_page = fu_synaptics_rmi_ps2_device_set_page; klass_rmi->query_status = fu_synaptics_rmi_ps2_device_query_status; klass_rmi->query_build_id = fu_synaptics_rmi_ps2_device_query_build_id; klass_rmi->query_product_sub_id = fu_synaptics_rmi_ps2_device_query_product_sub_id; klass_rmi->wait_for_attr = fu_synaptics_rmi_ps2_device_wait_for_attr; klass_rmi->enter_iep_mode = fu_synaptics_rmi_ps2_device_enter_iep_mode; klass_rmi->write_bus_select = fu_synaptics_rmi_ps2_device_write_bus_select; klass_rmi->read_packet_register = fu_synaptics_rmi_ps2_device_read_packet_register; } fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-ps2-device.h000066400000000000000000000006731460375044200245550ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * Copyright (c) 2020 Synaptics Incorporated. * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-synaptics-rmi-device.h" #define FU_TYPE_SYNAPTICS_RMI_PS2_DEVICE (fu_synaptics_rmi_ps2_device_get_type()) G_DECLARE_FINAL_TYPE(FuSynapticsRmiPs2Device, fu_synaptics_rmi_ps2_device, FU, SYNAPTICS_RMI_PS2_DEVICE, FuSynapticsRmiDevice) fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-v5-device.c000066400000000000000000000433121460375044200243730ustar00rootroot00000000000000/* * Copyright (C) 2012 Andrew Duggan * Copyright (C) 2012 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-rmi-firmware.h" #include "fu-synaptics-rmi-v5-device.h" #define RMI_F34_WRITE_FW_BLOCK 0x02 #define RMI_F34_ERASE_ALL 0x03 #define RMI_F34_WRITE_LOCKDOWN_BLOCK 0x04 #define RMI_F34_WRITE_CONFIG_BLOCK 0x06 #define RMI_F34_WRITE_SIGNATURE 0x0b #define RMI_F34_ENABLE_FLASH_PROG 0x0f #define RMI_F34_BLOCK_SIZE_OFFSET 1 #define RMI_F34_FW_BLOCKS_OFFSET 3 #define RMI_F34_CONFIG_BLOCKS_OFFSET 5 #define RMI_F34_ERASE_WAIT_MS (5 * 1000) /* ms */ gboolean fu_synaptics_rmi_v5_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device); FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); g_autoptr(GByteArray) enable_req = g_byte_array_new(); /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in bootloader mode, skipping"); return TRUE; } /* disable interrupts */ if (!fu_synaptics_rmi_device_disable_irqs(self, error)) return FALSE; if (!fu_synaptics_rmi_device_write_bus_select(self, 0, error)) { g_prefix_error(error, "failed to write bus select: "); return FALSE; } /* unlock bootloader and rebind kernel driver */ if (!fu_synaptics_rmi_device_write_bootloader_id(self, error)) return FALSE; fu_byte_array_append_uint8(enable_req, RMI_F34_ENABLE_FLASH_PROG); if (!fu_synaptics_rmi_device_write(self, flash->status_addr, enable_req, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to enable programming: "); return FALSE; } fu_device_sleep(device, RMI_F34_ENABLE_WAIT_MS); return TRUE; } static gboolean fu_synaptics_rmi_v5_device_erase_all(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFunction *f34; FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); g_autoptr(GByteArray) erase_cmd = g_byte_array_new(); /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; /* all other versions */ fu_byte_array_append_uint8(erase_cmd, RMI_F34_ERASE_ALL); if (!fu_synaptics_rmi_device_write(self, flash->status_addr, erase_cmd, FU_SYNAPTICS_RMI_DEVICE_FLAG_ALLOW_FAILURE, error)) { g_prefix_error(error, "failed to erase core config: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), RMI_F34_ERASE_WAIT_MS); fu_synaptics_rmi_device_set_iepmode(self, FALSE); if (!fu_synaptics_rmi_device_enter_iep_mode(self, FU_SYNAPTICS_RMI_DEVICE_FLAG_FORCE, error)) return FALSE; if (!fu_synaptics_rmi_device_wait_for_idle(self, RMI_F34_ERASE_WAIT_MS, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_REFRESH_F34, error)) { g_prefix_error(error, "failed to wait for idle for erase: "); return FALSE; } return TRUE; } static gboolean fu_synaptics_rmi_v5_device_write_block(FuSynapticsRmiDevice *self, guint8 cmd, guint32 address, const guint8 *data, gsize datasz, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new(); g_byte_array_append(req, data, datasz); fu_byte_array_append_uint8(req, cmd); if (!fu_synaptics_rmi_device_write(self, address, req, FU_SYNAPTICS_RMI_DEVICE_FLAG_ALLOW_FAILURE, error)) { g_prefix_error(error, "failed to write block @0x%x: ", address); return FALSE; } if (!fu_synaptics_rmi_device_wait_for_idle(self, RMI_F34_IDLE_WAIT_MS, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE, error)) { g_prefix_error(error, "failed to wait for idle @0x%x: ", address); return FALSE; } return TRUE; } static gboolean fu_synaptics_rmi_v5_device_secure_check(FuDevice *device, GBytes *payload, GBytes *signature, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device); FuSynapticsRmiFunction *f34; guint16 rsa_pubkey_len = fu_synaptics_rmi_device_get_sig_size(self) / 8; guint16 rsa_block_cnt = rsa_pubkey_len / 3; guint16 rsa_block_remain = rsa_pubkey_len % 3; g_autoptr(GByteArray) pubkey_buf = g_byte_array_new(); g_autoptr(GBytes) pubkey = NULL; fu_dump_bytes(G_LOG_DOMAIN, "Signature", signature); f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; /* parse RSA public key modulus */ if (rsa_block_remain > 0) rsa_block_cnt += 1; for (guint retries = 0;; retries++) { /* need read another register to reset the offset of packet register */ if (!fu_synaptics_rmi_v5_device_query_status(self, error)) { g_prefix_error(error, "failed to read status: "); return FALSE; } if (!fu_synaptics_rmi_device_enter_iep_mode(self, FU_SYNAPTICS_RMI_DEVICE_FLAG_FORCE, error)) return FALSE; for (guint16 block_num = 0; block_num < rsa_block_cnt; block_num++) { g_autoptr(GByteArray) res = NULL; res = fu_synaptics_rmi_device_read_packet_register( self, f34->query_base + 14, /* addr of flash properties + 5 */ 0x3, error); if (res == NULL) return FALSE; if (res->len != 0x3) g_debug("read %u bytes in return", res->len); if (rsa_block_remain && block_num + 1 == rsa_block_cnt) { g_byte_array_remove_range(res, rsa_block_remain, res->len - rsa_block_remain); } for (guint i = 0; i < res->len / 2; i++) { guint8 tmp = res->data[i]; res->data[i] = res->data[res->len - i - 1]; res->data[res->len - i - 1] = tmp; } if (rsa_block_remain && block_num + 1 == rsa_block_cnt) { g_byte_array_prepend(pubkey_buf, res->data, rsa_block_remain); } else { g_byte_array_prepend(pubkey_buf, res->data, res->len); } } if (rsa_pubkey_len != pubkey_buf->len) { if (retries++ > 2) { g_set_error( error, G_IO_ERROR, G_IO_ERROR_FAILED, "RSA public key length not matched %u: after %u retries: ", pubkey_buf->len, retries); return FALSE; } g_byte_array_set_size(pubkey_buf, 0); continue; } /* success */ break; } fu_dump_full(G_LOG_DOMAIN, "RSA public key", pubkey_buf->data, pubkey_buf->len, 16, FU_DUMP_FLAGS_NONE); /* sanity check size */ if (rsa_pubkey_len != pubkey_buf->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "RSA public key length did not match: %u != %u: ", rsa_pubkey_len, pubkey_buf->len); return FALSE; } pubkey = g_bytes_new(pubkey_buf->data, pubkey_buf->len); return fu_synaptics_verify_sha256_signature(payload, pubkey, signature, error); } gboolean fu_synaptics_rmi_v5_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device); FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); FuSynapticsRmiFunction *f34; FuSynapticsRmiFirmware *rmi_firmware = FU_SYNAPTICS_RMI_FIRMWARE(firmware); guint32 address; guint32 firmware_length = fu_firmware_get_size(firmware) - fu_synaptics_rmi_firmware_get_sig_size(rmi_firmware); g_autoptr(GBytes) bytes_bin = NULL; g_autoptr(GBytes) bytes_cfg = NULL; g_autoptr(GBytes) signature_bin = NULL; g_autoptr(GBytes) firmware_bin = NULL; g_autoptr(FuChunkArray) chunks_bin = NULL; g_autoptr(FuChunkArray) chunks_cfg = NULL; g_autoptr(GByteArray) req_addr = g_byte_array_new(); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, "enter-iep"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "write-signature"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, "cfg-image"); /* we should be in bootloader mode now, but check anyway */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not bootloader, perhaps need detach?!"); return FALSE; } if (!fu_synaptics_rmi_device_enter_iep_mode(self, FU_SYNAPTICS_RMI_DEVICE_FLAG_FORCE, error)) return FALSE; /* check is idle */ if (!fu_synaptics_rmi_device_wait_for_idle(self, 0, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_REFRESH_F34, error)) { g_prefix_error(error, "not idle: "); return FALSE; } if (fu_synaptics_rmi_firmware_get_sig_size(rmi_firmware) == 0 && fu_synaptics_rmi_device_get_sig_size(self) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "device secure but firmware not secure"); return FALSE; } if (fu_synaptics_rmi_firmware_get_sig_size(rmi_firmware) != 0 && fu_synaptics_rmi_device_get_sig_size(self) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "device not secure but firmware secure"); return FALSE; } /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; /* get both images */ bytes_bin = fu_firmware_get_image_by_id_bytes(firmware, "ui", error); if (bytes_bin == NULL) return FALSE; bytes_cfg = fu_firmware_get_image_by_id_bytes(firmware, "config", error); if (bytes_cfg == NULL) return FALSE; /* verify signature if set */ firmware_bin = fu_bytes_new_offset(bytes_bin, 0, firmware_length, error); if (firmware_bin == NULL) return FALSE; signature_bin = fu_firmware_get_image_by_id_bytes(firmware, "sig", NULL); if (signature_bin != NULL) { if (!fu_synaptics_rmi_v5_device_secure_check(device, firmware_bin, signature_bin, error)) { g_prefix_error(error, "secure check failed: "); return FALSE; } } /* disable powersaving */ if (!fu_synaptics_rmi_device_disable_sleep(self, error)) { g_prefix_error(error, "failed to disable sleep: "); return FALSE; } /* unlock again */ if (!fu_synaptics_rmi_device_write_bootloader_id(self, error)) { g_prefix_error(error, "failed to unlock again: "); return FALSE; } fu_progress_step_done(progress); /* erase all */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_ERASE); if (!fu_synaptics_rmi_v5_device_erase_all(self, error)) { g_prefix_error(error, "failed to erase all: "); return FALSE; } fu_progress_step_done(progress); /* write initial address */ fu_byte_array_append_uint16(req_addr, 0x0, G_LITTLE_ENDIAN); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); if (!fu_synaptics_rmi_device_write(self, f34->data_base, req_addr, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write 1st address zero: "); return FALSE; } /* write each block */ if (f34->function_version == 0x01) address = f34->data_base + RMI_F34_BLOCK_DATA_V1_OFFSET; else address = f34->data_base + RMI_F34_BLOCK_DATA_OFFSET; chunks_bin = fu_chunk_array_new_from_bytes(firmware_bin, 0x00, flash->block_size); chunks_cfg = fu_chunk_array_new_from_bytes(bytes_cfg, 0x00, flash->block_size); for (guint i = 0; i < fu_chunk_array_length(chunks_bin); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks_bin, i); if (!fu_synaptics_rmi_v5_device_write_block(self, RMI_F34_WRITE_FW_BLOCK, address, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to write bin block %u: ", fu_chunk_get_idx(chk)); return FALSE; } fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks_bin)); } fu_progress_step_done(progress); /* payload signature */ if (signature_bin != NULL && fu_synaptics_rmi_device_get_sig_size(self) != 0) { FuProgress *progress_child = fu_progress_get_child(progress); g_autoptr(FuChunkArray) chunks_sig = NULL; chunks_sig = fu_chunk_array_new_from_bytes(signature_bin, 0x00, /* start addr */ flash->block_size); if (!fu_synaptics_rmi_device_write(self, f34->data_base, req_addr, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write 1st address zero: "); return FALSE; } fu_progress_set_id(progress_child, G_STRLOC); fu_progress_set_steps(progress_child, fu_chunk_array_length(chunks_sig)); for (guint i = 0; i < fu_chunk_array_length(chunks_sig); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks_sig, i); if (!fu_synaptics_rmi_v5_device_write_block(self, RMI_F34_WRITE_SIGNATURE, address, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to write bin block %u: ", fu_chunk_get_idx(chk)); return FALSE; } fu_progress_step_done(progress_child); } fu_device_sleep(device, 1000); /* ms */ } fu_progress_step_done(progress); if (!fu_synaptics_rmi_device_enter_iep_mode(self, FU_SYNAPTICS_RMI_DEVICE_FLAG_FORCE, error)) return FALSE; /* program the configuration image */ if (!fu_synaptics_rmi_device_write(self, f34->data_base, req_addr, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to 2nd write address zero: "); return FALSE; } for (guint i = 0; i < fu_chunk_array_length(chunks_cfg); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks_cfg, i); if (!fu_synaptics_rmi_v5_device_write_block(self, RMI_F34_WRITE_CONFIG_BLOCK, address, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to write cfg block %u: ", fu_chunk_get_idx(chk)); return FALSE; } fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks_cfg)); } fu_progress_step_done(progress); /* success */ return TRUE; } gboolean fu_synaptics_rmi_v5_device_setup(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFunction *f34; FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); guint8 flash_properties2 = 0; g_autoptr(GByteArray) f34_data0 = NULL; g_autoptr(GByteArray) f34_data2 = NULL; g_autoptr(GByteArray) buf_flash_properties2 = NULL; /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; /* get bootloader ID */ f34_data0 = fu_synaptics_rmi_device_read(self, f34->query_base, 0x2, error); if (f34_data0 == NULL) { g_prefix_error(error, "failed to read bootloader ID: "); return FALSE; } flash->bootloader_id[0] = f34_data0->data[0]; flash->bootloader_id[1] = f34_data0->data[1]; /* get flash properties1 */ f34_data2 = fu_synaptics_rmi_device_read(self, f34->query_base + 0x2, 0x7, error); if (f34_data2 == NULL) return FALSE; if (f34_data2->data[0] & 0x80) { /* get flash properties2 */ buf_flash_properties2 = fu_synaptics_rmi_device_read(self, f34->query_base + 0x9, 1, error); if (buf_flash_properties2 == NULL) { g_prefix_error(error, "failed to read Flash Properties 2: "); return FALSE; } if (!fu_memread_uint8_safe(buf_flash_properties2->data, buf_flash_properties2->len, 0x0, /* offset */ &flash_properties2, error)) { g_prefix_error(error, "failed to parse Flash Properties 2: "); return FALSE; } if (flash_properties2 & 0x01) { guint16 sig_size = 0; g_autoptr(GByteArray) buf_rsa_key = NULL; buf_rsa_key = fu_synaptics_rmi_device_read(self, f34->query_base + 0x9 + 0x1, 2, error); if (buf_rsa_key == NULL) { g_prefix_error(error, "failed to read RSA key length: "); return FALSE; } if (!fu_memread_uint16_safe(buf_rsa_key->data, buf_rsa_key->len, 0x0, /* offset */ &sig_size, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to parse RSA key length: "); return FALSE; } fu_synaptics_rmi_device_set_sig_size(self, sig_size); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); } else { fu_synaptics_rmi_device_set_sig_size(self, 0); } } if (!fu_memread_uint16_safe(f34_data2->data, f34_data2->len, RMI_F34_BLOCK_SIZE_OFFSET, &flash->block_size, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(f34_data2->data, f34_data2->len, RMI_F34_FW_BLOCKS_OFFSET, &flash->block_count_fw, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(f34_data2->data, f34_data2->len, RMI_F34_CONFIG_BLOCKS_OFFSET, &flash->block_count_cfg, G_LITTLE_ENDIAN, error)) return FALSE; flash->status_addr = f34->data_base + RMI_F34_BLOCK_DATA_OFFSET + flash->block_size; return TRUE; } gboolean fu_synaptics_rmi_v5_device_query_status(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFunction *f01; g_autoptr(GByteArray) f01_db = NULL; /* f01 */ f01 = fu_synaptics_rmi_device_get_function(self, 0x01, error); if (f01 == NULL) return FALSE; f01_db = fu_synaptics_rmi_device_read(self, f01->data_base, 0x1, error); if (f01_db == NULL) { g_prefix_error(error, "failed to read the f01 data base: "); return FALSE; } if (f01_db->data[0] & 0x40) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else { fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } return TRUE; } fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-v5-device.h000066400000000000000000000011601460375044200243730ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-synaptics-rmi-device.h" gboolean fu_synaptics_rmi_v5_device_detach(FuDevice *device, FuProgress *progress, GError **error); gboolean fu_synaptics_rmi_v5_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error); gboolean fu_synaptics_rmi_v5_device_setup(FuSynapticsRmiDevice *self, GError **error); gboolean fu_synaptics_rmi_v5_device_query_status(FuSynapticsRmiDevice *self, GError **error); fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-v6-device.c000066400000000000000000000040311460375044200243670ustar00rootroot00000000000000/* * Copyright (C) 2012 Andrew Duggan * Copyright (C) 2012 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-rmi-v6-device.h" #define RMI_F34_CONFIG_BLOCKS_OFFSET 2 gboolean fu_synaptics_rmi_v6_device_setup(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); FuSynapticsRmiFunction *f34; g_autoptr(GByteArray) f34_data0 = NULL; g_autoptr(GByteArray) f34_data2 = NULL; g_autoptr(GByteArray) f34_data3 = NULL; /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; /* get bootloader ID */ f34_data0 = fu_synaptics_rmi_device_read(self, f34->query_base, 0x2, error); if (f34_data0 == NULL) { g_prefix_error(error, "failed to read bootloader ID: "); return FALSE; } if (!fu_memread_uint8_safe(f34_data0->data, f34_data0->len, 0x0, &flash->bootloader_id[0], error)) return FALSE; if (!fu_memread_uint8_safe(f34_data0->data, f34_data0->len, 0x1, &flash->bootloader_id[1], error)) return FALSE; /* get flash properties */ f34_data2 = fu_synaptics_rmi_device_read(self, f34->query_base + 0x02, 2, error); if (f34_data2 == NULL) return FALSE; if (!fu_memread_uint16_safe(f34_data2->data, f34_data2->len, 0x0, &flash->block_size, G_LITTLE_ENDIAN, error)) return FALSE; f34_data3 = fu_synaptics_rmi_device_read(self, f34->query_base + 0x03, 8, error); if (f34_data3 == NULL) return FALSE; if (!fu_memread_uint16_safe(f34_data3->data, f34_data3->len, 0x0, &flash->block_count_fw, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(f34_data3->data, f34_data3->len, RMI_F34_CONFIG_BLOCKS_OFFSET, &flash->block_count_cfg, G_LITTLE_ENDIAN, error)) return FALSE; flash->status_addr = f34->data_base + 2; return TRUE; } fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-v6-device.h000066400000000000000000000003671460375044200244040ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-synaptics-rmi-device.h" gboolean fu_synaptics_rmi_v6_device_setup(FuSynapticsRmiDevice *self, GError **error); fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-v7-device.c000066400000000000000000000760711460375044200244050ustar00rootroot00000000000000/* * Copyright (C) 2012 Andrew Duggan * Copyright (C) 2012 Synaptics Inc. * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-synaptics-rmi-struct.h" #include "fu-synaptics-rmi-v7-device.h" #define RMI_F34_ERASE_WAIT_MS 10000 /* ms */ typedef enum { RMI_FLASH_CMD_IDLE = 0x00, RMI_FLASH_CMD_ENTER_BL, RMI_FLASH_CMD_READ, RMI_FLASH_CMD_WRITE, RMI_FLASH_CMD_ERASE, RMI_FLASH_CMD_ERASE_AP, RMI_FLASH_CMD_SENSOR_ID, RMI_FLASH_CMD_SIGNATURE, } RmiFlashCommand; gboolean fu_synaptics_rmi_v7_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device); g_autoptr(GByteArray) enable_req = g_byte_array_new(); FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); FuSynapticsRmiFunction *f34; /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; /* disable interrupts */ if (!fu_synaptics_rmi_device_disable_irqs(self, error)) return FALSE; /* enter BL */ fu_byte_array_append_uint8(enable_req, FU_RMI_PARTITION_ID_BOOTLOADER); fu_byte_array_append_uint32(enable_req, 0x0, G_LITTLE_ENDIAN); fu_byte_array_append_uint8(enable_req, RMI_FLASH_CMD_ENTER_BL); fu_byte_array_append_uint8(enable_req, flash->bootloader_id[0]); fu_byte_array_append_uint8(enable_req, flash->bootloader_id[1]); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 1, enable_req, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to enable programming: "); return FALSE; } /* wait for idle */ if (!fu_synaptics_rmi_device_wait_for_idle(self, RMI_F34_ENABLE_WAIT_MS, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE, error)) return FALSE; if (!fu_synaptics_rmi_device_poll_wait(self, error)) return FALSE; fu_device_sleep(device, RMI_F34_ENABLE_WAIT_MS); return TRUE; } static gboolean fu_synaptics_rmi_v7_device_erase_partition(FuSynapticsRmiDevice *self, guint8 partition_id, GError **error) { FuSynapticsRmiFunction *f34; FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); g_autoptr(GByteArray) erase_cmd = g_byte_array_new(); /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; fu_byte_array_append_uint8(erase_cmd, partition_id); fu_byte_array_append_uint32(erase_cmd, 0x0, G_LITTLE_ENDIAN); fu_byte_array_append_uint8(erase_cmd, RMI_FLASH_CMD_ERASE); fu_byte_array_append_uint8(erase_cmd, flash->bootloader_id[0]); fu_byte_array_append_uint8(erase_cmd, flash->bootloader_id[1]); fu_device_sleep(FU_DEVICE(self), 1000); /* ms */ if (!fu_synaptics_rmi_device_write(self, f34->data_base + 1, erase_cmd, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to unlock erasing: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 100); /* ms */ /* wait for ATTN */ if (!fu_synaptics_rmi_device_wait_for_idle(self, RMI_F34_ERASE_WAIT_MS, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE, error)) { g_prefix_error(error, "failed to wait for idle: "); return FALSE; } if (!fu_synaptics_rmi_device_poll_wait(self, error)) { g_prefix_error(error, "failed to get flash success: "); return FALSE; } return TRUE; } static gboolean fu_synaptics_rmi_v7_device_erase_all(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFunction *f34; FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); g_autoptr(GByteArray) erase_cmd = g_byte_array_new(); /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; fu_byte_array_append_uint8(erase_cmd, FU_RMI_PARTITION_ID_CORE_CODE); fu_byte_array_append_uint32(erase_cmd, 0x0, G_LITTLE_ENDIAN); if (flash->bootloader_id[1] >= 8) { /* For bootloader v8 */ fu_byte_array_append_uint8(erase_cmd, RMI_FLASH_CMD_ERASE_AP); } else { /* For bootloader v7 */ fu_byte_array_append_uint8(erase_cmd, RMI_FLASH_CMD_ERASE); } fu_byte_array_append_uint8(erase_cmd, flash->bootloader_id[0]); fu_byte_array_append_uint8(erase_cmd, flash->bootloader_id[1]); /* for BL8 device, we need hold 1 seconds after querying F34 status to * avoid not get attention by following giving erase command */ if (flash->bootloader_id[1] >= 8) fu_device_sleep(FU_DEVICE(self), 1000); /* ms */ if (!fu_synaptics_rmi_device_write(self, f34->data_base + 1, erase_cmd, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to unlock erasing: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 100); /* ms */ if (flash->bootloader_id[1] >= 8) { /* wait for ATTN */ if (!fu_synaptics_rmi_device_wait_for_idle(self, RMI_F34_ERASE_WAIT_MS, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE, error)) { g_prefix_error(error, "failed to wait for idle: "); return FALSE; } } if (!fu_synaptics_rmi_device_poll_wait(self, error)) { g_prefix_error(error, "failed to get flash success: "); return FALSE; } /* for BL7, we need erase config partition */ if (flash->bootloader_id[1] == 7) { g_autoptr(GByteArray) erase_config_cmd = g_byte_array_new(); fu_byte_array_append_uint8(erase_config_cmd, FU_RMI_PARTITION_ID_CORE_CONFIG); fu_byte_array_append_uint32(erase_config_cmd, 0x0, G_LITTLE_ENDIAN); fu_byte_array_append_uint8(erase_config_cmd, RMI_FLASH_CMD_ERASE); fu_device_sleep(FU_DEVICE(self), 100); /* ms */ if (!fu_synaptics_rmi_device_write(self, f34->data_base + 1, erase_config_cmd, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to erase core config: "); return FALSE; } /* wait for ATTN */ fu_device_sleep(FU_DEVICE(self), 100); /* ms */ if (!fu_synaptics_rmi_device_wait_for_idle( self, RMI_F34_ERASE_WAIT_MS, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_REFRESH_F34, error)) { g_prefix_error(error, "failed to wait for idle: "); return FALSE; } if (!fu_synaptics_rmi_device_poll_wait(self, error)) { g_prefix_error(error, "failed to get flash success: "); return FALSE; } } return TRUE; } static gboolean fu_synaptics_rmi_v7_device_write_blocks(FuSynapticsRmiDevice *self, guint32 address, GBytes *fw, GError **error) { FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); g_autoptr(FuChunkArray) chunks = NULL; /* write FW blocks */ chunks = fu_chunk_array_new_from_bytes(fw, 0x00, flash->block_size); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GByteArray) req = g_byte_array_new(); g_byte_array_append(req, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); if (!fu_synaptics_rmi_device_write(self, address, req, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write block @0x%x:%x: ", address, fu_chunk_get_address(chk)); return FALSE; } } /* wait for idle */ if (!fu_synaptics_rmi_device_wait_for_idle(self, RMI_F34_IDLE_WAIT_MS, RMI_DEVICE_WAIT_FOR_IDLE_FLAG_NONE, error)) { g_prefix_error(error, "failed to wait for idle @0x%x: ", address); return FALSE; } /* success */ return TRUE; } static gboolean fu_synaptics_rmi_v7_device_write_partition_signature(FuSynapticsRmiDevice *self, FuFirmware *firmware, const gchar *id, FuRmiPartitionId partition_id, GError **error) { FuSynapticsRmiFunction *f34; FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); g_autoptr(GByteArray) req_offset = g_byte_array_new(); g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GBytes) bytes = NULL; /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; /*check if signature exists */ bytes = fu_firmware_get_image_by_id_bytes(firmware, g_strdup_printf("%s-signature", id), NULL); if (bytes == NULL) { return TRUE; } /* write partition signature */ g_info("writing partition signature %s…", fu_rmi_partition_id_to_string(partition_id)); fu_byte_array_append_uint16(req_offset, 0x0, G_LITTLE_ENDIAN); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x2, req_offset, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write offset: "); return FALSE; } chunks = fu_chunk_array_new_from_bytes(bytes, 0x00, (gsize)flash->payload_length * (gsize)flash->block_size); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GBytes) chk_blob = fu_chunk_get_bytes(chk); g_autoptr(GByteArray) req_trans_sz = g_byte_array_new(); g_autoptr(GByteArray) req_cmd = g_byte_array_new(); fu_byte_array_append_uint16(req_trans_sz, fu_chunk_get_data_sz(chk) / flash->block_size, G_LITTLE_ENDIAN); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x3, req_trans_sz, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write transfer length: "); return FALSE; } fu_byte_array_append_uint8(req_cmd, RMI_FLASH_CMD_SIGNATURE); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x4, req_cmd, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write signature command: "); return FALSE; } if (!fu_synaptics_rmi_v7_device_write_blocks(self, f34->data_base + 0x5, chk_blob, error)) return FALSE; } return TRUE; } static gboolean fu_synaptics_rmi_v7_device_write_partition(FuSynapticsRmiDevice *self, FuFirmware *firmware, const gchar *id, FuRmiPartitionId partition_id, GBytes *bytes, FuProgress *progress, GError **error) { FuSynapticsRmiFunction *f34; FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); g_autoptr(GByteArray) req_offset = g_byte_array_new(); g_autoptr(GByteArray) req_partition_id = g_byte_array_new(); g_autoptr(FuChunkArray) chunks = NULL; /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; /* write partition id */ g_info("writing partition %s…", fu_rmi_partition_id_to_string(partition_id)); fu_byte_array_append_uint8(req_partition_id, partition_id); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x1, req_partition_id, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write flash partition: "); return FALSE; } fu_byte_array_append_uint16(req_offset, 0x0, G_LITTLE_ENDIAN); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x2, req_offset, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write offset: "); return FALSE; } /* write partition */ chunks = fu_chunk_array_new_from_bytes(bytes, 0x00, (gsize)flash->payload_length * (gsize)flash->block_size); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks) + 1); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GBytes) chk_blob = fu_chunk_get_bytes(chk); g_autoptr(GByteArray) req_trans_sz = g_byte_array_new(); g_autoptr(GByteArray) req_cmd = g_byte_array_new(); fu_byte_array_append_uint16(req_trans_sz, fu_chunk_get_data_sz(chk) / flash->block_size, G_LITTLE_ENDIAN); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x3, req_trans_sz, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write transfer length: "); return FALSE; } fu_byte_array_append_uint8(req_cmd, RMI_FLASH_CMD_WRITE); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x4, req_cmd, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to flash command: "); return FALSE; } if (!fu_synaptics_rmi_v7_device_write_blocks(self, f34->data_base + 0x5, chk_blob, error)) return FALSE; fu_progress_step_done(progress); } if (!fu_synaptics_rmi_v7_device_write_partition_signature(self, firmware, id, partition_id, error)) return FALSE; fu_progress_step_done(progress); return TRUE; } static GBytes * fu_synaptics_rmi_v7_device_get_pubkey(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); FuSynapticsRmiFunction *f34; const gsize key_size = RMI_KEY_SIZE_2K; g_autoptr(GByteArray) req_addr_zero = g_byte_array_new(); g_autoptr(GByteArray) req_cmd = g_byte_array_new(); g_autoptr(GByteArray) req_partition_id = g_byte_array_new(); g_autoptr(GByteArray) req_transfer_length = g_byte_array_new(); g_autoptr(GByteArray) res = NULL; g_autoptr(GByteArray) pubkey = g_byte_array_new(); /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return NULL; /* set partition id for bootloader 7 */ fu_byte_array_append_uint8(req_partition_id, FU_RMI_PARTITION_ID_PUBKEY); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x1, req_partition_id, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write flash partition id: "); return NULL; } fu_byte_array_append_uint16(req_addr_zero, 0x0, G_LITTLE_ENDIAN); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x2, req_addr_zero, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write flash config address: "); return NULL; } /* set transfer length */ fu_byte_array_append_uint16(req_transfer_length, key_size / flash->block_size, G_LITTLE_ENDIAN); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x3, req_transfer_length, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to set transfer length: "); return NULL; } /* set command to read */ fu_byte_array_append_uint8(req_cmd, RMI_FLASH_CMD_READ); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x4, req_cmd, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write command to read: "); return NULL; } if (!fu_synaptics_rmi_device_poll_wait(self, error)) { g_prefix_error(error, "failed to wait: "); return NULL; } /* read back entire buffer in blocks */ res = fu_synaptics_rmi_device_read(self, f34->data_base + 0x5, (guint32)key_size, error); if (res == NULL) { g_prefix_error(error, "failed to read: "); return NULL; } for (guint i = 0; i < res->len; i++) fu_byte_array_append_uint8(pubkey, res->data[res->len - i - 1]); /* success */ return g_bytes_new(pubkey->data, pubkey->len); } static gboolean fu_synaptics_rmi_v7_device_secure_check(FuSynapticsRmiDevice *self, FuFirmware *firmware, GError **error) { FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); g_autoptr(GBytes) pubkey = NULL; g_autoptr(GPtrArray) imgs = NULL; if (flash->bootloader_id[1] >= 10 || flash->has_pubkey == FALSE) return TRUE; pubkey = fu_synaptics_rmi_v7_device_get_pubkey(self, error); if (pubkey == NULL) { g_prefix_error(error, "get pubkey failed: "); return FALSE; } imgs = fu_firmware_get_images(firmware); for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); const gchar *id = fu_firmware_get_id(img); g_autoptr(GBytes) byte_payload = NULL; g_autoptr(GBytes) byte_signature = NULL; g_autofree gchar *id_signature = NULL; if (g_str_has_suffix(id, "-signature")) continue; id_signature = g_strdup_printf("%s-signature", id); byte_signature = fu_firmware_get_image_by_id_bytes(firmware, id_signature, NULL); if (byte_signature == NULL) continue; byte_payload = fu_firmware_get_bytes(img, error); if (byte_payload == NULL) return FALSE; if (!fu_synaptics_verify_sha256_signature(byte_payload, pubkey, byte_signature, error)) { g_prefix_error(error, "%s secure check failed: ", id); return FALSE; } g_info("%s signature verified successfully", id); } return TRUE; } gboolean fu_synaptics_rmi_v7_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device); FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); FuSynapticsRmiFunction *f34; g_autoptr(GBytes) bytes_bin = NULL; g_autoptr(GBytes) bytes_cfg = NULL; g_autoptr(GBytes) bytes_flashcfg = NULL; g_autoptr(GBytes) bytes_fld = NULL; g_autoptr(GBytes) bytes_afe = NULL; g_autoptr(GBytes) bytes_displayconfig = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); if (flash->bootloader_id[1] > 8) { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "disable-sleep"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 0, "verify-signature"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "fixed-location-data"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 8, "flash-config"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 9, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 81, "core-code"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "core-config"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 0, "external-touch-afe-config"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 0, "display-config"); } else if (flash->bootloader_id[1] == 8) { fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "disable-sleep"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 0, "verify-signature"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 0, "fixed-location-data"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 16, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 0, "flash-config"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 81, "core-code"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "core-config"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 0, "external-touch-afe-config"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 0, "display-config"); } else { fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "disable-sleep"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_READ, 2, "verify-signature"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 2, "fixed-location-data"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 3, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 89, "core-code"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 2, "core-config"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 2, "external-touch-afe-config"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 2, "display-config"); } /* we should be in bootloader mode now, but check anyway */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not bootloader, perhaps need detach?!"); return FALSE; } /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; /* get both images */ bytes_bin = fu_firmware_get_image_by_id_bytes(firmware, "ui", error); if (bytes_bin == NULL) return FALSE; bytes_cfg = fu_firmware_get_image_by_id_bytes(firmware, "config", error); if (bytes_cfg == NULL) return FALSE; if (flash->bootloader_id[1] >= 8) { bytes_flashcfg = fu_firmware_get_image_by_id_bytes(firmware, "flash-config", error); if (bytes_flashcfg == NULL) return FALSE; } bytes_fld = fu_firmware_get_image_by_id_bytes(firmware, "fixed-location-data", NULL); bytes_afe = fu_firmware_get_image_by_id_bytes(firmware, "afe-config", NULL); bytes_displayconfig = fu_firmware_get_image_by_id_bytes(firmware, "display-config", NULL); /* disable powersaving */ if (!fu_synaptics_rmi_device_disable_sleep(self, error)) return FALSE; fu_progress_step_done(progress); /* verify signature */ if (!fu_synaptics_rmi_v7_device_secure_check(self, firmware, error)) return FALSE; fu_progress_step_done(progress); /* write fld before erase if exists */ if (bytes_fld != NULL) { if (!fu_synaptics_rmi_v7_device_write_partition( self, firmware, "fixed-location-data", FU_RMI_PARTITION_ID_FIXED_LOCATION_DATA, bytes_fld, fu_progress_get_child(progress), error)) return FALSE; } fu_progress_step_done(progress); /* write flash config for BL > v8 */ if (flash->bootloader_id[1] > 8) { if (!fu_synaptics_rmi_v7_device_erase_partition(self, FU_RMI_PARTITION_ID_FLASH_CONFIG, error)) return FALSE; if (!fu_synaptics_rmi_v7_device_write_partition(self, firmware, "flash-config", FU_RMI_PARTITION_ID_FLASH_CONFIG, bytes_flashcfg, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } /* erase all */ if (!fu_synaptics_rmi_v7_device_erase_all(self, error)) { g_prefix_error(error, "failed to erase all: "); return FALSE; } fu_progress_step_done(progress); /* write flash config for v8 */ if (flash->bootloader_id[1] == 8) { if (!fu_synaptics_rmi_v7_device_write_partition(self, firmware, "flash-config", FU_RMI_PARTITION_ID_FLASH_CONFIG, bytes_flashcfg, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } /* write core code */ if (!fu_synaptics_rmi_v7_device_write_partition(self, firmware, "ui", FU_RMI_PARTITION_ID_CORE_CODE, bytes_bin, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* write core config */ if (!fu_synaptics_rmi_v7_device_write_partition(self, firmware, "config", FU_RMI_PARTITION_ID_CORE_CONFIG, bytes_cfg, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* write afe-config if exists */ if (bytes_afe != NULL) { if (!fu_synaptics_rmi_v7_device_write_partition( self, firmware, "afe-config", FU_RMI_PARTITION_ID_EXTERNAL_TOUCH_AFE_CONFIG, bytes_afe, fu_progress_get_child(progress), error)) return FALSE; } fu_progress_step_done(progress); /* write display config if exists */ if (bytes_displayconfig != NULL) { if (!fu_synaptics_rmi_v7_device_write_partition(self, firmware, "display-config", FU_RMI_PARTITION_ID_DISPLAY_CONFIG, bytes_displayconfig, fu_progress_get_child(progress), error)) return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_synaptics_rmi_device_read_flash_config_v7(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); FuSynapticsRmiFunction *f34; g_autoptr(GByteArray) req_addr_zero = g_byte_array_new(); g_autoptr(GByteArray) req_cmd = g_byte_array_new(); g_autoptr(GByteArray) req_partition_id = g_byte_array_new(); g_autoptr(GByteArray) req_transfer_length = g_byte_array_new(); g_autoptr(GByteArray) res = NULL; gsize partition_size = FU_STRUCT_RMI_PARTITION_TBL_SIZE; /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; /* set partition id for bootloader 7 */ fu_byte_array_append_uint8(req_partition_id, FU_RMI_PARTITION_ID_FLASH_CONFIG); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x1, req_partition_id, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write flash partition id: "); return FALSE; } fu_byte_array_append_uint16(req_addr_zero, 0x0, G_LITTLE_ENDIAN); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x2, req_addr_zero, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write flash config address: "); return FALSE; } /* set transfer length */ fu_byte_array_append_uint16(req_transfer_length, flash->config_length, G_LITTLE_ENDIAN); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x3, req_transfer_length, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to set transfer length: "); return FALSE; } /* set command to read */ fu_byte_array_append_uint8(req_cmd, RMI_FLASH_CMD_READ); if (!fu_synaptics_rmi_device_write(self, f34->data_base + 0x4, req_cmd, FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to write command to read: "); return FALSE; } if (!fu_synaptics_rmi_device_poll_wait(self, error)) { g_prefix_error(error, "failed to wait: "); return FALSE; } /* read back entire buffer in blocks */ res = fu_synaptics_rmi_device_read(self, f34->data_base + 0x5, (guint32)flash->block_size * (guint32)flash->config_length, error); if (res == NULL) { g_prefix_error(error, "failed to read: "); return FALSE; } /* debugging */ fu_dump_full(G_LOG_DOMAIN, "FlashConfig", res->data, res->len, 80, FU_DUMP_FLAGS_NONE); if ((res->data[0] & 0x0f) == 1) partition_size += 0x2; /* parse the config length */ for (guint i = 0x2; i < res->len; i += partition_size) { guint16 partition_id; g_autoptr(GByteArray) st_prt = NULL; st_prt = fu_struct_rmi_partition_tbl_parse(res->data, res->len, i, error); if (st_prt == NULL) return FALSE; partition_id = fu_struct_rmi_partition_tbl_get_partition_id(st_prt); g_debug("found partition %s (0x%02x)", fu_rmi_partition_id_to_string(partition_id), partition_id); if (partition_id == FU_RMI_PARTITION_ID_CORE_CONFIG) { flash->block_count_cfg = fu_struct_rmi_partition_tbl_get_partition_len(st_prt); continue; } if (partition_id == FU_RMI_PARTITION_ID_CORE_CODE) { flash->block_count_fw = fu_struct_rmi_partition_tbl_get_partition_len(st_prt); continue; } if (partition_id == FU_RMI_PARTITION_ID_PUBKEY) { flash->has_pubkey = TRUE; continue; } if (partition_id == FU_RMI_PARTITION_ID_NONE) break; } /* success */ return TRUE; } gboolean fu_synaptics_rmi_v7_device_setup(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFlash *flash = fu_synaptics_rmi_device_get_flash(self); FuSynapticsRmiFunction *f34; guint8 offset; g_autoptr(GByteArray) f34_data0 = NULL; g_autoptr(GByteArray) f34_dataX = NULL; /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; f34_data0 = fu_synaptics_rmi_device_read(self, f34->query_base, 1, error); if (f34_data0 == NULL) { g_prefix_error(error, "failed to read bootloader ID: "); return FALSE; } offset = (f34_data0->data[0] & 0b00000111) + 1; f34_dataX = fu_synaptics_rmi_device_read(self, f34->query_base + offset, 21, error); if (f34_dataX == NULL) return FALSE; if (!fu_memread_uint8_safe(f34_dataX->data, f34_dataX->len, 0x0, &flash->bootloader_id[0], error)) return FALSE; if (!fu_memread_uint8_safe(f34_dataX->data, f34_dataX->len, 0x1, &flash->bootloader_id[1], error)) return FALSE; if (!fu_memread_uint16_safe(f34_dataX->data, f34_dataX->len, 0x07, &flash->block_size, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(f34_dataX->data, f34_dataX->len, 0x0d, &flash->config_length, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(f34_dataX->data, f34_dataX->len, 0x0f, &flash->payload_length, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(f34_dataX->data, f34_dataX->len, 0x02, &flash->build_id, G_LITTLE_ENDIAN, error)) return FALSE; /* sanity check */ if ((guint32)flash->block_size * (guint32)flash->config_length > G_MAXUINT16) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "block size 0x%x or config length 0x%x invalid", flash->block_size, flash->config_length); return FALSE; } /* read flash config */ return fu_synaptics_rmi_device_read_flash_config_v7(self, error); } gboolean fu_synaptics_rmi_v7_device_query_status(FuSynapticsRmiDevice *self, GError **error) { FuSynapticsRmiFunction *f34; guint8 status; g_autoptr(GByteArray) f34_data = NULL; /* f34 */ f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error); if (f34 == NULL) return FALSE; f34_data = fu_synaptics_rmi_device_read(self, f34->data_base, 0x1, error); if (f34_data == NULL) { g_prefix_error(error, "failed to read the f01 data base: "); return FALSE; } status = f34_data->data[0]; if (status & 0x80) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } else { fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } if (status == 0x01) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "operation only supported in bootloader mode"); return FALSE; } if (status == 0x02) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "partition ID is not supported by the bootloader"); return FALSE; } if (status == 0x03) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "partition supported, but command not supported"); return FALSE; } if (status == 0x04) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid block offset"); return FALSE; } if (status == 0x05) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid transfer"); return FALSE; } if (status == 0x06) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "partition has not been erased"); return FALSE; } if (status == 0x07) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_SIGNATURE_INVALID, "flash programming key incorrect"); return FALSE; } if (status == 0x08) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "bad partition table"); return FALSE; } if (status == 0x09) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "transfer checksum failed"); return FALSE; } if (status == 0x1f) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "flash hardware failure"); return FALSE; } return TRUE; } fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi-v7-device.h000066400000000000000000000011601460375044200243750ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-synaptics-rmi-device.h" gboolean fu_synaptics_rmi_v7_device_detach(FuDevice *device, FuProgress *progress, GError **error); gboolean fu_synaptics_rmi_v7_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error); gboolean fu_synaptics_rmi_v7_device_setup(FuSynapticsRmiDevice *self, GError **error); gboolean fu_synaptics_rmi_v7_device_query_status(FuSynapticsRmiDevice *self, GError **error); fwupd-1.9.16/plugins/synaptics-rmi/fu-synaptics-rmi.rs000066400000000000000000000034601460375044200230100ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] #[repr(u16le)] enum RmiPartitionId { None = 0x00, Bootloader = 0x01, DeviceConfig, FlashConfig, ManufacturingBlock, GuestSerialization, GlobalParameters, CoreCode, CoreConfig, GuestCode, DisplayConfig, ExternalTouchAfeConfig, UtilityParameter, Pubkey, FixedLocationData = 0x0E, } #[derive(Parse)] struct RmiPartitionTbl { partition_id: RmiPartitionId, partition_len: u16le, partition_addr: u16le, partition_prop: u16le, } #[derive(New, ParseBytes)] struct RmiImg { checksum: u32le, _reserved1: [u8; 2], io_offset: u8, bootloader_version: u8, image_size: u32le, config_size: u32le, product_id: [char; 10], package_id: u32le, product_info: u32le, _reserved3: [u8; 46], fw_build_id: u32le, signature_size: u32le, } #[derive(ToString)] #[repr(u16le)] enum RmiContainerId { TopLevel, Ui, UiConfig, Bl, BlImage, BlConfig, BlLockdownInfo, PermanentConfig, GuestCode, BlProtocolDescriptor, UiProtocolDescriptor, RmiSelfDiscovery, RmiPageContent, GeneralInformation, DeviceConfig, FlashConfig, GuestSerialization, GlobalParameters, CoreCode, CoreConfig, DisplayConfig, ExternalTouchAfeConfig, Utility, UtilityParameter, FixedLocationData = 27, } #[derive(New, ParseBytes)] struct RmiContainerDescriptor { content_checksum: u32le, container_id: RmiContainerId, minor_version: u8, major_version: u8, signature_size: u32le, container_option_flags: u32le, content_options_length: u32le, content_options_address: u32le, content_length: u32le, content_address: u32le, } fwupd-1.9.16/plugins/synaptics-rmi/meson.build000066400000000000000000000035341460375044200213740ustar00rootroot00000000000000if get_option('plugin_synaptics_rmi').require(gnutls.found(), error_message: 'gnutls is needed for plugin_synaptics_rmi').allowed() and \ get_option('plugin_synaptics_rmi').require(gudev.found(), error_message: 'gudev is needed for plugin_synaptics_rmi').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginSynapticsRmi"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('synaptics-rmi.quirk') plugin_builtin_synaptics_rmi = static_library('fu_plugin_synaptics_rmi', rustgen.process( 'fu-synaptics-rmi.rs', # fuzzing ), sources: [ 'fu-synaptics-rmi-plugin.c', 'fu-synaptics-rmi-common.c', # fuzzing 'fu-synaptics-rmi-device.c', 'fu-synaptics-rmi-hid-device.c', 'fu-synaptics-rmi-ps2-device.c', 'fu-synaptics-rmi-v5-device.c', 'fu-synaptics-rmi-v6-device.c', 'fu-synaptics-rmi-v7-device.c', 'fu-synaptics-rmi-firmware.c', # fuzzing ], include_directories: plugin_incdirs, c_args: cargs, dependencies: plugin_deps, link_with: plugin_libs, ) plugin_builtins += plugin_builtin_synaptics_rmi if get_option('tests') install_data(['tests/synaptics-rmi-0x.builder.xml','tests/synaptics-rmi-10.builder.xml'], install_dir: join_paths(installed_test_datadir, 'tests')) env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'synaptics-rmi-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_synaptics_rmi, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('synaptics-rmi-self-test', e, env: env) endif endif fwupd-1.9.16/plugins/synaptics-rmi/synaptics-rmi.quirk000066400000000000000000000005661460375044200231130ustar00rootroot00000000000000# Dell K12A kbd-dock [HIDRAW\VEN_06CB&DEV_2819] Plugin = synaptics_rmi GType = FuSynapticsRmiHidDevice Vendor = Synaptics Flags = only-supported # LENOVO X1 Nano [SERIO\FWID_LEN0305-PNP0F13] Plugin = synaptics_rmi GType = FuSynapticsRmiPs2Device # LENOVO X1 Panther [SERIO\FWID_LEN0306-PNP0F13] Plugin = synaptics_rmi GType = FuSynapticsRmiPs2Device InstallDuration = 236 fwupd-1.9.16/plugins/synaptics-rmi/tests/000077500000000000000000000000001460375044200203675ustar00rootroot00000000000000fwupd-1.9.16/plugins/synaptics-rmi/tests/synaptics-rmi-0x.bin000066400000000000000000000004101460375044200242030ustar00rootroot00000000000000V"Example4fwupd-1.9.16/plugins/synaptics-rmi/tests/synaptics-rmi-0x.builder.xml000066400000000000000000000003361460375044200256670ustar00rootroot00000000000000 0x01 Example ui ZGF2ZQ== fwupd-1.9.16/plugins/synaptics-rmi/tests/synaptics-rmi-10.bin000066400000000000000000000005101460375044200240750ustar00rootroot00000000000000dExample!CV44 $Dfwupd-1.9.16/plugins/synaptics-rmi/tests/synaptics-rmi-10.builder.xml000066400000000000000000000003361460375044200255600ustar00rootroot00000000000000 0x10 Example ui ZGF2ZQ== fwupd-1.9.16/plugins/system76-launch/000077500000000000000000000000001460375044200173745ustar00rootroot00000000000000fwupd-1.9.16/plugins/system76-launch/README.md000066400000000000000000000023411460375044200206530ustar00rootroot00000000000000--- title: Plugin: System76 Launch --- ## Introduction This plugin is used to detach the System76 Launch device to DFU or UF2 mode. To switch to bootloader mode a USB packet must be written, as specified by the System76 EC protocol. ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_3384&PID_0001&REV_0001` ## Update Behavior The device usually presents in runtime mode, but on detach re-enumerates with a different USB VID and PID in DFU or UF2 mode. The device is then handled by the `dfu` or `uf2` plugin. On DFU or UF2 attach the device again re-enumerates back to the runtime mode. For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x3384` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.5.6`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Jeremy Soller: @jackpot51 fwupd-1.9.16/plugins/system76-launch/fu-system76-launch-device.c000066400000000000000000000202331460375044200243560ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2021 Jeremy Soller * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-system76-launch-device.h" #define SYSTEM76_LAUNCH_CMD_VERSION 3 #define SYSTEM76_LAUNCH_CMD_RESET 6 #define SYSTEM76_LAUNCH_CMD_SECURITY_SET 21 #define SYSTEM76_LAUNCH_TIMEOUT 1000 enum SecurityState { /* Default value, flashing is prevented, cannot be set with CMD_SECURITY_SET */ SECURITY_STATE_LOCK = 0, /* Flashing is allowed, cannot be set with CMD_SECURITY_SET */ SECURITY_STATE_UNLOCK = 1, /* Flashing will be prevented on the next reboot */ SECURITY_STATE_PREPARE_LOCK = 2, /* Flashing will be allowed on the next reboot */ SECURITY_STATE_PREPARE_UNLOCK = 3, }; struct _FuSystem76LaunchDevice { FuUsbDevice parent_instance; }; G_DEFINE_TYPE(FuSystem76LaunchDevice, fu_system76_launch_device, FU_TYPE_USB_DEVICE) typedef struct { guint8 *data; gsize len; } FuSystem76LaunchDeviceHelper; static gboolean fu_system76_launch_device_response_cb(FuDevice *device, gpointer user_data, GError **error) { const guint8 ep_in = 0x82; gsize actual_len = 0; FuSystem76LaunchDeviceHelper *helper = (FuSystem76LaunchDeviceHelper *)user_data; /* receive response */ if (!g_usb_device_interrupt_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(device)), ep_in, helper->data, helper->len, &actual_len, SYSTEM76_LAUNCH_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to read response: "); return FALSE; } if (actual_len < helper->len) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "response truncated: received %" G_GSIZE_FORMAT " bytes", actual_len); return FALSE; } /* success */ return TRUE; } static gboolean fu_system76_launch_device_command(FuDevice *device, guint8 *data, gsize len, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); const guint8 ep_out = 0x03; gsize actual_len = 0; FuSystem76LaunchDeviceHelper helper = {.data = data, .len = len}; /* send command */ if (!g_usb_device_interrupt_transfer(usb_device, ep_out, data, len, &actual_len, SYSTEM76_LAUNCH_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to send command: "); return FALSE; } if (actual_len < len) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "command truncated: sent %" G_GSIZE_FORMAT " bytes", actual_len); return FALSE; } return fu_device_retry(device, fu_system76_launch_device_response_cb, 5, &helper, error); } static gboolean fu_system76_launch_device_version_cb(FuDevice *device, gpointer user_data, GError **error) { guint8 data[32] = {SYSTEM76_LAUNCH_CMD_VERSION, 0}; g_autofree gchar *version = NULL; /* execute version command */ if (!fu_system76_launch_device_command(device, data, sizeof(data), error)) { g_prefix_error(error, "failed to execute version command: "); return FALSE; } version = g_strdup_printf("%s", &data[2]); fu_device_set_version(device, version); return TRUE; } static gboolean fu_system76_launch_device_setup(FuDevice *device, GError **error) { /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_system76_launch_device_parent_class)->setup(device, error)) return FALSE; /* set version */ return fu_device_retry_full(device, fu_system76_launch_device_version_cb, 5, 500, NULL, error); } static gboolean fu_system76_launch_device_reset(FuDevice *device, guint8 *rc, GError **error) { guint8 data[32] = {SYSTEM76_LAUNCH_CMD_RESET, 0}; /* execute reset command */ if (!fu_system76_launch_device_command(device, data, sizeof(data), error)) { g_prefix_error(error, "failed to execute reset command: "); return FALSE; } *rc = data[1]; return TRUE; } static gboolean fu_system76_launch_device_security_set(FuDevice *device, enum SecurityState state, guint8 *rc, GError **error) { guint8 data[32] = {SYSTEM76_LAUNCH_CMD_SECURITY_SET, 0, state, 0}; /* execute security set command */ if (!fu_system76_launch_device_command(device, data, sizeof(data), error)) { g_prefix_error(error, "failed to execute security set command: "); return FALSE; } *rc = data[1]; return TRUE; } static gboolean fu_system76_launch_device_detach(FuDevice *device, FuProgress *progress, GError **error) { guint8 rc = 0x0; g_autoptr(FwupdRequest) request = fwupd_request_new(); g_autoptr(GTimer) timer = g_timer_new(); /* prompt for unlock if reset was blocked */ if (!fu_system76_launch_device_reset(device, &rc, error)) return FALSE; /* unlikely, but already unlocked */ if (rc == 0) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } /* notify device of desire to unlock */ if (!fu_system76_launch_device_security_set(device, SECURITY_STATE_PREPARE_UNLOCK, &rc, error)) return FALSE; /* generate a message if not already set */ if (fu_device_get_update_message(device) == NULL) { g_autofree gchar *msg = NULL; const gchar *unlock_keys; switch (fu_usb_device_get_pid(FU_USB_DEVICE(device))) { case 0x0001: /* launch_1 */ unlock_keys = "Fn+Esc"; break; case 0x000B: /* thelio_io_2 */ unlock_keys = "the Power button"; break; default: unlock_keys = "Left Ctrl+Right Ctrl+Esc"; break; } msg = g_strdup_printf( "To ensure you have physical access, %s needs to be manually unlocked. " "Please press %s to unlock and re-run the update.", fu_device_get_name(device), unlock_keys); fu_device_set_update_message(device, msg); } /* the user has to do something */ fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_PRESS_UNLOCK); fwupd_request_set_message(request, fu_device_get_update_message(device)); if (!fu_device_emit_request(device, request, progress, error)) return FALSE; /* poll for the user-unlock */ do { fu_device_sleep(device, 1000); /* ms */ if (!fu_system76_launch_device_reset(device, &rc, error)) return FALSE; } while (rc != 0 && g_timer_elapsed(timer, NULL) * 1000.f < FU_DEVICE_REMOVE_DELAY_USER_REPLUG); if (rc != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NEEDS_USER_ACTION, fu_device_get_update_message(device)); return FALSE; } /* success */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_system76_launch_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 30, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 40, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 25, "reload"); } static void fu_system76_launch_device_init(FuSystem76LaunchDevice *self) { fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV); fu_device_add_request_flag(FU_DEVICE(self), FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_add_protocol(FU_DEVICE(self), "com.microsoft.uf2"); fu_device_add_protocol(FU_DEVICE(self), "org.usb.dfu"); fu_device_retry_set_delay(FU_DEVICE(self), 100); fu_usb_device_add_interface(FU_USB_DEVICE(self), 0x01); } static void fu_system76_launch_device_class_init(FuSystem76LaunchDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_system76_launch_device_setup; klass_device->detach = fu_system76_launch_device_detach; klass_device->set_progress = fu_system76_launch_device_set_progress; } fwupd-1.9.16/plugins/system76-launch/fu-system76-launch-device.h000066400000000000000000000006511460375044200243650ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2021 Jeremy Soller * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_SYSTEM76_LAUNCH_DEVICE (fu_system76_launch_device_get_type()) G_DECLARE_FINAL_TYPE(FuSystem76LaunchDevice, fu_system76_launch_device, FU, SYSTEM76_LAUNCH_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/system76-launch/fu-system76-launch-plugin.c000066400000000000000000000015541460375044200244220ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2021 Jeremy Soller * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-system76-launch-device.h" #include "fu-system76-launch-plugin.h" struct _FuSystem76LaunchPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuSystem76LaunchPlugin, fu_system76_launch_plugin, FU_TYPE_PLUGIN) static void fu_system76_launch_plugin_init(FuSystem76LaunchPlugin *self) { } static void fu_system76_launch_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_SYSTEM76_LAUNCH_DEVICE); } static void fu_system76_launch_plugin_class_init(FuSystem76LaunchPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_system76_launch_plugin_constructed; } fwupd-1.9.16/plugins/system76-launch/fu-system76-launch-plugin.h000066400000000000000000000004361460375044200244250ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuSystem76LaunchPlugin, fu_system76_launch_plugin, FU, SYSTEM76_LAUNCH_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/system76-launch/meson.build000066400000000000000000000007001460375044200215330ustar00rootroot00000000000000if gusb.found() plugins += {meson.current_source_dir().split('/')[-1]: true} cargs = ['-DG_LOG_DOMAIN="FuPluginSystem76Launch"'] plugin_quirks += files('system76-launch.quirk') plugin_builtins += static_library('fu_plugin_system76_launch', sources: [ 'fu-system76-launch-plugin.c', 'fu-system76-launch-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/system76-launch/system76-launch.quirk000066400000000000000000000014331460375044200234230ustar00rootroot00000000000000# System76 launch_1 [USB\VID_3384&PID_0001] Plugin = system76_launch CounterpartGuid = USB\VID_03EB&PID_2FF4 # System76 launch_lite_1 [USB\VID_3384&PID_0005] Plugin = system76_launch CounterpartGuid = USB\VID_03EB&PID_2FF9 # System76 launch_2 [USB\VID_3384&PID_0006] Plugin = system76_launch CounterpartGuid = USB\VID_03EB&PID_2FF9 # System76 launch_heavy_1 [USB\VID_3384&PID_0007] Plugin = system76_launch CounterpartGuid = USB\VID_03EB&PID_2FF9 # System76 launch_3 [USB\VID_3384&PID_0009] Plugin = system76_launch CounterpartGuid = BLOCK\VEN_2E8A&DEV_0003 # System76 launch_heavy_3 [USB\VID_3384&PID_000A] Plugin = system76_launch CounterpartGuid = BLOCK\VEN_2E8A&DEV_0003 # System76 thelio_io_2 [USB\VID_3384&PID_000B] Plugin = system76_launch CounterpartGuid = BLOCK\VEN_2E8A&DEV_0003 fwupd-1.9.16/plugins/test/000077500000000000000000000000001460375044200154025ustar00rootroot00000000000000fwupd-1.9.16/plugins/test/README.md000066400000000000000000000011501460375044200166560ustar00rootroot00000000000000--- title: Plugin: Test --- ## Introduction This plugin is used when running the self tests in the fwupd project. ## GUID Generation The devices created by this plugin use hardcoded GUIDs that do not correspond to any kind of DeviceInstanceId values. In other cases devices use the standard BLE DeviceInstanceId values, e.g. * `USB\VID_2DC8&PID_AB11` ## Vendor ID Security The fake device is only for local testing and thus requires no vendor ID set. ## External Interface Access This plugin requires no extra access. ## Version Considerations This plugin has been available since fwupd version `0.8.0`. fwupd-1.9.16/plugins/test/fu-test-ble-device.c000066400000000000000000000013341460375044200211330ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-test-ble-device.h" struct _FuTestBleDevice { FuBluezDevice parent_instance; }; G_DEFINE_TYPE(FuTestBleDevice, fu_test_ble_device, FU_TYPE_BLUEZ_DEVICE) static void fu_test_ble_device_init(FuTestBleDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "org.test.testble"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV); } static void fu_test_ble_device_class_init(FuTestBleDeviceClass *klass) { } fwupd-1.9.16/plugins/test/fu-test-ble-device.h000066400000000000000000000004621460375044200211410ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_TEST_BLE_DEVICE (fu_test_ble_device_get_type()) G_DECLARE_FINAL_TYPE(FuTestBleDevice, fu_test_ble_device, FU, TEST_BLE_DEVICE, FuBluezDevice) fwupd-1.9.16/plugins/test/fu-test-ble-plugin.c000066400000000000000000000014411460375044200211710ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-test-ble-device.h" #include "fu-test-ble-plugin.h" struct _FuTestBlePlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuTestBlePlugin, fu_test_ble_plugin, FU_TYPE_PLUGIN) static void fu_test_ble_plugin_init(FuTestBlePlugin *self) { fu_plugin_add_flag(FU_PLUGIN(self), FWUPD_PLUGIN_FLAG_TEST_ONLY); } static void fu_test_ble_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_TEST_BLE_DEVICE); } static void fu_test_ble_plugin_class_init(FuTestBlePluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_test_ble_plugin_constructed; } fwupd-1.9.16/plugins/test/fu-test-ble-plugin.h000066400000000000000000000003551460375044200212010ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuTestBlePlugin, fu_test_ble_plugin, FU, TEST_BLE_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/test/fu-test-plugin.c000066400000000000000000000312721460375044200204360ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-test-plugin.h" struct _FuTestPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuTestPlugin, fu_test_plugin, FU_TYPE_PLUGIN) static gboolean fu_test_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(FuDevice) device = NULL; device = fu_device_new(ctx); fu_device_set_id(device, "FakeDevice"); fu_device_add_guid(device, "b585990a-003e-5270-89d5-3705a17f9a43"); fu_device_set_name(device, "Integrated_Webcam(TM)"); fu_device_add_icon(device, "preferences-desktop-keyboard"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_request_flag(device, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); fu_device_add_protocol(device, "com.acme.test"); fu_device_set_summary(device, "Fake webcam"); fu_device_set_vendor(device, "ACME Corp."); fu_device_add_vendor_id(device, "USB:0x046D"); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version_bootloader(device, "0.1.2"); fu_device_set_version(device, "1.2.2"); fu_device_set_version_lowest(device, "1.2.0"); if (fu_plugin_get_config_value_boolean(plugin, "RegistrationSupported")) { fu_plugin_device_register(plugin, device); if (fu_device_get_metadata(device, "BestDevice") == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Device not set by another plugin"); return FALSE; } } fu_plugin_device_add(plugin, device); if (fu_plugin_get_config_value_boolean(plugin, "CompositeChild")) { g_autoptr(FuDevice) child1 = NULL; g_autoptr(FuDevice) child2 = NULL; child1 = fu_device_new(ctx); fu_device_add_vendor_id(child1, "USB:FFFF"); fu_device_add_protocol(child1, "com.acme"); fu_device_set_physical_id(child1, "fake"); fu_device_set_logical_id(child1, "child1"); fu_device_add_guid(child1, "7fddead7-12b5-4fb9-9fa0-6d30305df755"); fu_device_set_name(child1, "Module1"); fu_device_set_version_format(child1, FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_version(child1, "1"); fu_device_add_parent_guid(child1, "b585990a-003e-5270-89d5-3705a17f9a43"); fu_device_add_flag(child1, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(child1, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_flag(child1, FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST); fu_plugin_device_add(plugin, child1); child2 = fu_device_new(ctx); fu_device_add_vendor_id(child2, "USB:FFFF"); fu_device_add_protocol(child2, "com.acme"); fu_device_set_physical_id(child2, "fake"); fu_device_set_logical_id(child2, "child2"); fu_device_add_guid(child2, "b8fe6b45-8702-4bcd-8120-ef236caac76f"); fu_device_set_name(child2, "Module2"); fu_device_set_version_format(child2, FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_version(child2, "10"); fu_device_add_parent_guid(child2, "b585990a-003e-5270-89d5-3705a17f9a43"); fu_device_add_flag(child2, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(child2, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_flag(child2, FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST); fu_plugin_device_add(plugin, child2); } return TRUE; } static void fu_test_plugin_device_registered(FuPlugin *plugin, FuDevice *device) { fu_device_set_metadata(device, "BestDevice", "/dev/urandom"); } static gboolean fu_test_plugin_verify(FuPlugin *plugin, FuDevice *device, FuProgress *progress, FuPluginVerifyFlags flags, GError **error) { if (g_strcmp0(fu_device_get_version(device), "1.2.2") == 0) { fu_device_add_checksum(device, "90d0ad436d21e0687998cd2127b2411135e1f730"); fu_device_add_checksum( device, "921631916a60b295605dbae6a0309f9b64e2401b3de8e8506e109fc82c586e3a"); return TRUE; } if (g_strcmp0(fu_device_get_version(device), "1.2.3") == 0) { fu_device_add_checksum(device, "7998cd212721e068b2411135e1f90d0ad436d730"); fu_device_add_checksum( device, "dbae6a0309b3de8e850921631916a60b2956056e109fc82c586e3f9b64e2401a"); return TRUE; } if (g_strcmp0(fu_device_get_version(device), "1.2.4") == 0) { fu_device_add_checksum(device, "2b8546ba805ad10bf8a2e5ad539d53f303812ba5"); fu_device_add_checksum( device, "b546c241029ce4e16c99eb6bfd77b86e4490aa3826ba71b8a4114e96a2d69bcd"); return TRUE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no checksum for %s", fu_device_get_version(device)); return FALSE; } static gchar * fu_test_plugin_get_version(GBytes *blob_fw) { gsize bufsz = 0; const gchar *buf = g_bytes_get_data(blob_fw, &bufsz); guint64 val = 0; g_autoptr(GError) error_local = NULL; g_autofree gchar *str_safe = fu_strsafe(buf, bufsz); if (str_safe == NULL) return NULL; if (!fu_strtoull(str_safe, &val, 0, G_MAXUINT32, &error_local)) { g_debug("invalid version specified: %s", error_local->message); return NULL; } if (val == 0x0) return NULL; return fu_version_from_uint32(val, FWUPD_VERSION_FORMAT_TRIPLET); } static gboolean fu_test_plugin_write_firmware(FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) { g_autofree gchar *decompress_delay_str = NULL; g_autofree gchar *write_delay_str = NULL; g_autofree gchar *verify_delay_str = NULL; guint64 delay_decompress_ms = 0; guint64 delay_write_ms = 0; guint64 delay_verify_ms = 0; guint64 delay_request_ms = 0; if (!fu_plugin_get_config_value_boolean(plugin, "WriteSupported")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device was not in supported mode"); return FALSE; } fu_progress_set_status(progress, FWUPD_STATUS_DECOMPRESSING); decompress_delay_str = fu_plugin_get_config_value(plugin, "DecompressDelay"); if (decompress_delay_str != NULL) { if (!fu_strtoull(decompress_delay_str, &delay_decompress_ms, 0, 10000, error)) { g_prefix_error(error, "failed to parse DecompressDelay: "); return FALSE; } } for (guint i = 0; i <= delay_decompress_ms; i++) { fu_device_sleep(device, 1); fu_progress_set_percentage_full(progress, i, delay_decompress_ms); } /* send an interactive request, and wait some time */ if (fu_plugin_get_config_value_boolean(plugin, "RequestSupported")) { g_autofree gchar *request_delay_str = NULL; g_autoptr(FwupdRequest) request = fwupd_request_new(); fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); fwupd_request_set_message(request, "Please pretend to remove the device you cannot see or " "touch and please re-insert it."); if (!fu_device_emit_request(device, request, progress, error)) return FALSE; request_delay_str = fu_plugin_get_config_value(plugin, "RequestDelay"); if (request_delay_str != NULL) { if (!fu_strtoull(request_delay_str, &delay_request_ms, 0, 10000, error)) { g_prefix_error(error, "failed to parse RequestDelay: "); return FALSE; } } g_usleep(delay_request_ms * 1000); } fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); write_delay_str = fu_plugin_get_config_value(plugin, "WriteDelay"); if (write_delay_str != NULL) { if (!fu_strtoull(write_delay_str, &delay_write_ms, 0, 10000, error)) { g_prefix_error(error, "failed to parse WriteDelay: "); return FALSE; } } for (guint i = 0; i <= delay_write_ms; i++) { fu_device_sleep(device, 1); fu_progress_set_percentage_full(progress, i, delay_write_ms); } fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_VERIFY); verify_delay_str = fu_plugin_get_config_value(plugin, "VerifyDelay"); if (verify_delay_str != NULL) { if (!fu_strtoull(verify_delay_str, &delay_verify_ms, 0, 10000, error)) { g_prefix_error(error, "failed to parse VerifyDelay: "); return FALSE; } } for (guint i = 0; i <= delay_verify_ms; i++) { fu_device_sleep(device, 1); fu_progress_set_percentage_full(progress, i, delay_verify_ms); } /* composite test, upgrade composite devices */ if (fu_plugin_get_config_value_boolean(plugin, "CompositeChild")) { fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PLAIN); if (g_strcmp0(fu_device_get_logical_id(device), "child1") == 0) { fu_device_set_version(device, "2"); return TRUE; } if (g_strcmp0(fu_device_get_logical_id(device), "child2") == 0) { fu_device_set_version(device, "11"); return TRUE; } } /* upgrade, or downgrade */ if (fu_plugin_get_config_value_boolean(plugin, "NeedsActivation")) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); } else if (fu_plugin_get_config_value_boolean(plugin, "NeedsReboot")) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); } else { g_autofree gchar *ver = fu_test_plugin_get_version(blob_fw); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); if (ver != NULL) { fu_device_set_version(device, ver); } else { if (flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) { fu_device_set_version(device, "1.2.2"); } else { fu_device_set_version(device, "1.2.3"); } } } /* do this all over again */ if (fu_plugin_get_config_value_boolean(plugin, "AnotherWriteRequired") && !fu_device_get_metadata_boolean(device, "DoneAnotherWriteRequired")) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); fu_device_set_metadata_boolean(device, "DoneAnotherWriteRequired", TRUE); } /* for the self tests only */ fu_device_set_metadata_integer(device, "nr-update", fu_device_get_metadata_integer(device, "nr-update") + 1); return TRUE; } static gboolean fu_test_plugin_activate(FuPlugin *plugin, FuDevice *device, FuProgress *process, GError **error) { fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.3"); return TRUE; } static gboolean fu_test_plugin_get_results(FuPlugin *plugin, FuDevice *device, GError **error) { fu_device_set_update_state(device, FWUPD_UPDATE_STATE_SUCCESS); return TRUE; } static gboolean fu_test_plugin_composite_prepare(FuPlugin *plugin, GPtrArray *devices, GError **error) { if (fu_plugin_get_config_value_boolean(plugin, "CompositeChild")) { for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); fu_device_set_metadata(device, "frimbulator", "1"); } } return TRUE; } static gboolean fu_test_plugin_composite_cleanup(FuPlugin *plugin, GPtrArray *devices, GError **error) { if (fu_plugin_get_config_value_boolean(plugin, "CompositeChild")) { for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); fu_device_set_metadata(device, "frombulator", "1"); } } return TRUE; } static void fu_test_plugin_init(FuTestPlugin *self) { fu_plugin_add_flag(FU_PLUGIN(self), FWUPD_PLUGIN_FLAG_TEST_ONLY); } static void fu_test_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_set_config_default(plugin, "AnotherWriteRequired", "false"); fu_plugin_set_config_default(plugin, "CompositeChild", "false"); fu_plugin_set_config_default(plugin, "DecompressDelay", "0"); fu_plugin_set_config_default(plugin, "NeedsActivation", "false"); fu_plugin_set_config_default(plugin, "NeedsReboot", "false"); fu_plugin_set_config_default(plugin, "RegistrationSupported", "false"); fu_plugin_set_config_default(plugin, "RequestDelay", "10"); /* ms */ fu_plugin_set_config_default(plugin, "RequestSupported", "false"); fu_plugin_set_config_default(plugin, "VerifyDelay", "0"); fu_plugin_set_config_default(plugin, "WriteDelay", "0"); fu_plugin_set_config_default(plugin, "WriteSupported", "true"); } static void fu_test_finalize(GObject *obj) { g_debug("destroy"); G_OBJECT_CLASS(fu_test_plugin_parent_class)->finalize(obj); } static void fu_test_plugin_class_init(FuTestPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_test_finalize; plugin_class->constructed = fu_test_plugin_constructed; plugin_class->composite_cleanup = fu_test_plugin_composite_cleanup; plugin_class->composite_prepare = fu_test_plugin_composite_prepare; plugin_class->get_results = fu_test_plugin_get_results; plugin_class->activate = fu_test_plugin_activate; plugin_class->write_firmware = fu_test_plugin_write_firmware; plugin_class->verify = fu_test_plugin_verify; plugin_class->coldplug = fu_test_plugin_coldplug; plugin_class->device_registered = fu_test_plugin_device_registered; } fwupd-1.9.16/plugins/test/fu-test-plugin.h000066400000000000000000000003421460375044200204350ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuTestPlugin, fu_test_plugin, FU, TEST_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/test/meson.build000066400000000000000000000012111460375044200175370ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginTest"'] plugins += {meson.current_source_dir().split('/')[-1]: true} if bluez.allowed() plugin_quirks += files('test-ble.quirk') endif plugin_builtins += static_library('fu_plugin_test', sources: [ 'fu-test-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) if bluez.allowed() plugin_builtins += static_library('fu_plugin_test_ble', sources: [ 'fu-test-ble-plugin.c', 'fu-test-ble-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/test/test-ble.quirk000066400000000000000000000001621460375044200201750ustar00rootroot00000000000000[BLUETOOTH\VID_0461&PID_4EEE&REV_0001] Plugin = test_ble [BLUETOOTH\VID_0461&PID_4EEF&REV_0001] Plugin = test_ble fwupd-1.9.16/plugins/thelio-io/000077500000000000000000000000001460375044200163145ustar00rootroot00000000000000fwupd-1.9.16/plugins/thelio-io/README.md000066400000000000000000000022421460375044200175730ustar00rootroot00000000000000--- title: Plugin: Thelio IO --- ## Introduction This plugin is used to detach the Thelio IO device to DFU mode. To switch to this mode `1` has to be written to the `bootloader` file in sysfs. ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_1209&PID_1776&REV_0001` ## Update Behavior The device usually presents in runtime mode, but on detach re-enumerates with a different USB VID and PID in DFU mode. The device is then handled by the `dfu` plugin. On DFU attach the device again re-enumerates back to the runtime mode. For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x1209` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.3.2`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Jeremy Soller: @jackpot51 fwupd-1.9.16/plugins/thelio-io/fu-thelio-io-device.c000066400000000000000000000100001460375044200222050ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Jeremy Soller * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-thelio-io-device.h" struct _FuThelioIoDevice { FuUsbDevice parent_instance; }; G_DEFINE_TYPE(FuThelioIoDevice, fu_thelio_io_device, FU_TYPE_USB_DEVICE) static gboolean fu_thelio_io_device_probe(FuDevice *device, GError **error) { const gchar *devpath; g_autofree gchar *fn = NULL; g_autofree gchar *buf = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GUdevDevice) udev_device = NULL; /* this is the atmel bootloader */ fu_device_add_counterpart_guid(device, "USB\\VID_03EB&PID_2FF4"); /* convert GUsbDevice to GUdevDevice */ udev_device = fu_usb_device_find_udev_device(FU_USB_DEVICE(device), error); if (udev_device == NULL) return FALSE; devpath = g_udev_device_get_sysfs_path(udev_device); if (G_UNLIKELY(devpath == NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Could not determine sysfs path for device"); return FALSE; } /* pre-1.0.0 firmware versions do not implement this */ fn = g_build_filename(devpath, "revision", NULL); if (!g_file_get_contents(fn, &buf, NULL, &error_local)) { if (g_error_matches(error_local, G_FILE_ERROR, G_FILE_ERROR_FAILED)) { g_debug("FW revision unimplemented: %s", error_local->message); fu_device_set_version(device, "0.0.0"); } else { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } else { fu_device_set_version(device, (const gchar *)buf); } return TRUE; } static gboolean fu_thelio_io_device_detach(FuDevice *device, FuProgress *progress, GError **error) { const gchar *devpath; g_autofree gchar *fn = NULL; g_autoptr(FuIOChannel) io_channel = NULL; g_autoptr(GUdevDevice) udev_device = NULL; const guint8 buf[] = {'1', '\n'}; /* convert GUsbDevice to GUdevDevice */ udev_device = fu_usb_device_find_udev_device(FU_USB_DEVICE(device), error); if (udev_device == NULL) return FALSE; devpath = g_udev_device_get_sysfs_path(udev_device); if (G_UNLIKELY(devpath == NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Could not determine sysfs path for device"); return FALSE; } fn = g_build_filename(devpath, "bootloader", NULL); io_channel = fu_io_channel_new_file(fn, error); if (io_channel == NULL) return FALSE; if (!fu_io_channel_write_raw(io_channel, buf, sizeof(buf), 500, FU_IO_CHANNEL_FLAG_SINGLE_SHOT, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_thelio_io_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_thelio_io_device_init(FuThelioIoDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_protocol(FU_DEVICE(self), "org.usb.dfu"); } static void fu_thelio_io_device_class_init(FuThelioIoDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_thelio_io_device_probe; klass_device->detach = fu_thelio_io_device_detach; klass_device->set_progress = fu_thelio_io_device_set_progress; } fwupd-1.9.16/plugins/thelio-io/fu-thelio-io-device.h000066400000000000000000000005571460375044200222320ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Jeremy Soller * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_THELIO_IO_DEVICE (fu_thelio_io_device_get_type()) G_DECLARE_FINAL_TYPE(FuThelioIoDevice, fu_thelio_io_device, FU, THELIO_IO_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/thelio-io/fu-thelio-io-plugin.c000066400000000000000000000014441460375044200222600ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * Copyright (C) 2019 Jeremy Soller * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-thelio-io-device.h" #include "fu-thelio-io-plugin.h" struct _FuThelioIoPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuThelioIoPlugin, fu_thelio_io_plugin, FU_TYPE_PLUGIN) static void fu_thelio_io_plugin_init(FuThelioIoPlugin *self) { } static void fu_thelio_io_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_THELIO_IO_DEVICE); } static void fu_thelio_io_plugin_class_init(FuThelioIoPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_thelio_io_plugin_constructed; } fwupd-1.9.16/plugins/thelio-io/fu-thelio-io-plugin.h000066400000000000000000000003601460375044200222610ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuThelioIoPlugin, fu_thelio_io_plugin, FU, THELIO_IO_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/thelio-io/meson.build000066400000000000000000000006431460375044200204610ustar00rootroot00000000000000if gudev.found() cargs = ['-DG_LOG_DOMAIN="FuPluginThelioIo"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('thelio-io.quirk') plugin_builtins += static_library('fu_plugin_thelio_io', sources: [ 'fu-thelio-io-plugin.c', 'fu-thelio-io-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/thelio-io/thelio-io.quirk000066400000000000000000000001111460375044200212530ustar00rootroot00000000000000# System76 Thelio IO [USB\VID_1209&PID_1776&REV_0001] Plugin = thelio_io fwupd-1.9.16/plugins/thunderbolt/000077500000000000000000000000001460375044200167555ustar00rootroot00000000000000fwupd-1.9.16/plugins/thunderbolt/README.md000066400000000000000000000113551460375044200202410ustar00rootroot00000000000000--- title: Plugin: Thunderbolt --- ## Introduction Thunderbolt™ is the brand name of a hardware interface developed by Intel that allows the connection of external peripherals to a computer. Versions 1 and 2 use the same connector as Mini DisplayPort (MDP), whereas version 3 uses USB Type-C. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an unspecified binary file format, with vendor specific header. This plugin supports the following protocol ID: * `com.intel.thunderbolt` ## GUID Generation These devices use a custom GUID generation scheme. When the device is in "safe mode" the GUID is hardcoded using: * `TBT-safemode` ... and when in runtime mode the GUID is: * `TBT-$(vid)$(pid)-native` when native, and `TBT-$(vid)$(pid)` otherwise. Additionally for host system thunderbolt controllers another GUID is added containing the domain designation of the controller. This is intended to be used for systems with multiple host controllers to disambiguate between controllers. * `TBT-$(vid)$(pid)-native-controller$(num)` For retimers the only GUID created is as follows: * `TBT-$(vid)$(pid)-retimer$index` The retimer index is oriented around the physical connection within the machine. It is important as multiple controllers may otherwise identify identically. ## Update Behavior For most devices the firmware is written to the device at runtime and the update is applied immediately. Once complete the controller may reboot which may cause all connected USB and TBT devices to be reenumerated. For some devices and circumstances (such as the Dell WD19 with a new enough kernel) the device will remain functional for the duration of the update. The update will complete on unplug. If a user sets `DelayedActivation` configuration option then the update will be staged but not completed until `activate` is separately called such as at logout or shutdown. ## Vendor ID Security The vendor ID is set from the udev vendor, for example set to `TBT:0x$(vid)` ## Runtime Power Management Thunderbolt controllers are slightly unusual in that they power down completely when no thunderbolt devices are detected. This poses a problem for fwupd as it can't coldplug devices to see if there are firmware updates available, and also can't ensure the controller stays awake during a firmware upgrade. On Dell hardware the `Thunderbolt::CanForcePower` metadata value is set as the system can force the thunderbolt controller on during coldplug or during the firmware update process. This is typically done calling a SMI or ACPI method which asserts the GPIO for the duration of the request. On non-Dell hardware you will have to insert a Thunderbolt device (e.g. a dock) into the laptop to be able to update the controller itself. ## Safe Mode Thunderbolt hardware is also slightly unusual in that it goes into "safe mode" whenever it encounters a critical firmware error, for instance if an update failed to be completed. In this safe mode you cannot query the controller vendor or model and therefore the thunderbolt plugin cannot add the correct GUID used to match it to the correct firmware. In this case the metadata value `Thunderbolt::IsSafeMode` is set which would allow a different plugin to add the correct GUID based on some out-of-band device discovery. At the moment this only happens on Dell hardware. ## GUID generation for LVFS The GUID for the controller, which must appear in the metadata when uploading an NVM to LVFS, can be generated by a tool like `appstream-util` (with `generate-guid` command) or by Python (with `uuid.uuid5(uuid.NAMESPACE_DNS, 'string')`). The format of the string used as input is "TBT-vvvvdddd", where vvvvv is the vendor ID and dddd is the device ID, both in hex, as appear in the controller's DROM and exposed in the relevant sysfs attributes. If the controller is in native enumeration mode, the string "-native" is added at the end so the format is "TBT-vvvvdddd-native". ## Quirk Use This plugin uses the following plugin-specific quirks: ### Flags=retimer-offline-mode Use offline mode interface to update retimers. Since: 1.9.1 ## External Interface Access This plugin requires read/write access to `/sys/bus/thunderbolt`. ## Version Considerations This plugin has been available since fwupd version `0.8.0`. ## Data Flow ```mermaid flowchart LR subgraph Thunderbolt Controller controller(TBT/USB4) TBT_SPI[(SPI)] end subgraph Kernel thunderbolt(Thunderbolt\ndriver) end subgraph fwupd Process fwupdengine(FuEngine) tbt_plugin(Thunderbolt\nPlugin) end fwupdengine-->tbt_plugin tbt_plugin<--sysfs-->thunderbolt thunderbolt<--mailbox-->controller controller---TBT_SPI thunderbolt~~~controller ``` fwupd-1.9.16/plugins/thunderbolt/fu-self-test.c000066400000000000000000001031071460375044200214410ustar00rootroot00000000000000/* * Copyright (C) 2017 Christian J. Kellner * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "fu-context-private.h" #include "fu-plugin-private.h" #include "fu-thunderbolt-plugin.h" #include "fu-udev-device-private.h" static gchar * udev_mock_add_nvmem(UMockdevTestbed *bed, gboolean active, const char *parent, int id) { g_autofree gchar *name = NULL; gchar *path; name = g_strdup_printf("%s%d", active ? "nvm_active" : "nvm_non_active", id); path = umockdev_testbed_add_device(bed, "nvmem", name, parent, "nvmem", "", NULL, NULL); g_assert_nonnull(path); return path; } static gchar * udev_mock_add_usb4_port(UMockdevTestbed *bed, int id) { g_autofree gchar *name = g_strdup_printf("usb4_port%d", id); gchar *path = umockdev_testbed_add_device(bed, "thunderbolt", name, NULL, "security", "secure", NULL, "DEVTYPE", "thunderbolt_usb4_port", NULL); g_assert_nonnull(path); return path; } typedef struct MockDevice MockDevice; struct MockDevice { const char *name; /* sysfs: device_name */ const char *id; /* sysfs: device */ const char *nvm_version; const char *nvm_parsed_version; int delay_ms; int domain_id; struct MockDevice *children; /* optionally filled out */ const char *uuid; }; typedef struct MockTree MockTree; struct MockTree { const MockDevice *device; MockTree *parent; GPtrArray *children; gchar *sysfs_parent; int sysfs_id; int sysfs_nvm_id; gchar *uuid; UMockdevTestbed *bed; gchar *path; gchar *nvm_non_active; gchar *nvm_active; guint nvm_authenticate; gchar *nvm_version; FuDevice *fu_device; }; static MockTree * mock_tree_new(MockTree *parent, MockDevice *device, int *id) { MockTree *node = g_slice_new0(MockTree); int current_id = (*id)++; node->device = device; node->sysfs_id = current_id; node->sysfs_nvm_id = current_id; node->parent = parent; if (device->uuid) node->uuid = g_strdup(device->uuid); else node->uuid = g_uuid_string_random(); node->nvm_version = g_strdup(device->nvm_version); return node; } static void mock_tree_free(MockTree *tree) { for (guint i = 0; i < tree->children->len; i++) { MockTree *child = g_ptr_array_index(tree->children, i); mock_tree_free(child); } g_ptr_array_free(tree->children, TRUE); if (tree->fu_device) g_object_unref(tree->fu_device); g_free(tree->uuid); if (tree->bed != NULL) { if (tree->nvm_active) { umockdev_testbed_uevent(tree->bed, tree->nvm_active, "remove"); umockdev_testbed_remove_device(tree->bed, tree->nvm_active); } if (tree->nvm_non_active) { umockdev_testbed_uevent(tree->bed, tree->nvm_non_active, "remove"); umockdev_testbed_remove_device(tree->bed, tree->nvm_non_active); } if (tree->path) { umockdev_testbed_uevent(tree->bed, tree->path, "remove"); umockdev_testbed_remove_device(tree->bed, tree->path); } g_object_unref(tree->bed); } g_free(tree->nvm_version); g_free(tree->nvm_active); g_free(tree->nvm_non_active); g_free(tree->path); g_free(tree->sysfs_parent); g_slice_free(MockTree, tree); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(MockTree, mock_tree_free); #pragma clang diagnostic pop static GPtrArray * mock_tree_init_children(MockTree *node, int *id) { GPtrArray *children = g_ptr_array_new(); MockDevice *iter; for (iter = node->device->children; iter && iter->name; iter++) { MockTree *child = mock_tree_new(node, iter, id); g_ptr_array_add(children, child); child->children = mock_tree_init_children(child, id); } return children; } static MockTree * mock_tree_init(MockDevice *device) { MockTree *tree; int devices = 0; tree = mock_tree_new(NULL, device, &devices); tree->children = mock_tree_init_children(tree, &devices); return tree; } static void mock_tree_dump(const MockTree *node, int level) { if (node->path) { g_debug("%*s * %s [%s] at %s", level, " ", node->device->name, node->uuid, node->path); g_debug("%*s non-active nvmem at %s", level, " ", node->nvm_non_active); g_debug("%*s active nvmem at %s", level, " ", node->nvm_active); } else { g_debug("%*s * %s [%s] %d", level, " ", node->device->name, node->uuid, node->sysfs_id); } for (guint i = 0; i < node->children->len; i++) { const MockTree *child = g_ptr_array_index(node->children, i); mock_tree_dump(child, level + 2); } } static void mock_tree_firmware_verify(const MockTree *node, GBytes *data) { g_autoptr(GFile) nvm_device = NULL; g_autoptr(GFile) nvm = NULL; g_autoptr(GInputStream) is = NULL; g_autoptr(GChecksum) chk = NULL; g_autoptr(GError) error = NULL; g_autofree gchar *sum_data = NULL; const gchar *sum_disk = NULL; gsize s; sum_data = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, data); chk = g_checksum_new(G_CHECKSUM_SHA1); g_assert_nonnull(node); g_assert_nonnull(node->nvm_non_active); nvm_device = g_file_new_for_path(node->nvm_non_active); nvm = g_file_get_child(nvm_device, "nvmem"); is = (GInputStream *)g_file_read(nvm, NULL, &error); g_assert_no_error(error); g_assert_nonnull(is); do { g_autoptr(GBytes) b = NULL; const guchar *d; b = g_input_stream_read_bytes(is, 4096, NULL, &error); g_assert_no_error(error); g_assert_nonnull(is); d = g_bytes_get_data(b, &s); if (s > 0) g_checksum_update(chk, d, (gssize)s); } while (s > 0); sum_disk = g_checksum_get_string(chk); g_assert_cmpstr(sum_data, ==, sum_disk); } typedef gboolean (*MockTreePredicate)(const MockTree *node, gpointer data); static const MockTree * mock_tree_contains(const MockTree *node, MockTreePredicate predicate, gpointer data) { if (predicate(node, data)) return node; for (guint i = 0; i < node->children->len; i++) { const MockTree *child; const MockTree *match; child = g_ptr_array_index(node->children, i); match = mock_tree_contains(child, predicate, data); if (match != NULL) return match; } return NULL; } static gboolean mock_tree_all(const MockTree *node, MockTreePredicate predicate, gpointer data) { if (!predicate(node, data)) return FALSE; for (guint i = 0; i < node->children->len; i++) { const MockTree *child; child = g_ptr_array_index(node->children, i); if (!mock_tree_all(child, predicate, data)) return FALSE; } return TRUE; } static gboolean mock_tree_compare_uuid(const MockTree *node, gpointer data) { const gchar *uuid = (const gchar *)data; return g_str_equal(node->uuid, uuid); } static const MockTree * mock_tree_find_uuid(const MockTree *root, const char *uuid) { return mock_tree_contains(root, mock_tree_compare_uuid, (gpointer)uuid); } static gboolean mock_tree_node_have_fu_device(const MockTree *node, gpointer data) { return node->fu_device != NULL; } static void write_controller_fw(const gchar *nvm) { g_autoptr(GFile) nvm_device = NULL; g_autoptr(GFile) nvmem = NULL; g_autofree gchar *fw_path = NULL; g_autoptr(GFile) fw_file = NULL; g_autoptr(GInputStream) is = NULL; g_autoptr(GOutputStream) os = NULL; g_autoptr(GError) error = NULL; gssize n; fw_path = g_test_build_filename(G_TEST_DIST, "tests", "minimal-fw-controller.bin", NULL); fw_file = g_file_new_for_path(fw_path); g_assert_nonnull(fw_file); nvm_device = g_file_new_for_path(nvm); g_assert_nonnull(nvm_device); nvmem = g_file_get_child(nvm_device, "nvmem"); g_assert_nonnull(nvmem); os = (GOutputStream *)g_file_append_to(nvmem, G_FILE_CREATE_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(os); is = (GInputStream *)g_file_read(fw_file, NULL, &error); g_assert_no_error(error); g_assert_nonnull(is); n = g_output_stream_splice(os, is, G_OUTPUT_STREAM_SPLICE_NONE, NULL, &error); g_assert_no_error(error); g_assert_cmpuint(n, >, 0); } static gboolean mock_tree_attach_device(gpointer user_data) { MockTree *tree = (MockTree *)user_data; const MockDevice *dev = tree->device; g_autofree gchar *idstr = NULL; g_autofree gchar *authenticate = NULL; g_assert_nonnull(tree); g_assert_nonnull(tree->sysfs_parent); g_assert_nonnull(dev); idstr = g_strdup_printf("%d-%d", dev->domain_id, tree->sysfs_id); authenticate = g_strdup_printf("0x%x", tree->nvm_authenticate); tree->path = umockdev_testbed_add_device(tree->bed, "thunderbolt", idstr, tree->sysfs_parent, "device_name", dev->name, "device", dev->id, "vendor", "042", "vendor_name", "GNOME.org", "authorized", "1", "nvm_authenticate", authenticate, "nvm_version", tree->nvm_version, "unique_id", tree->uuid, NULL, "DEVTYPE", "thunderbolt_device", NULL); tree->nvm_non_active = udev_mock_add_nvmem(tree->bed, FALSE, tree->path, tree->sysfs_id); tree->nvm_active = udev_mock_add_nvmem(tree->bed, TRUE, tree->path, tree->sysfs_id); g_assert_nonnull(tree->path); g_assert_nonnull(tree->nvm_non_active); g_assert_nonnull(tree->nvm_active); write_controller_fw(tree->nvm_active); for (guint i = 0; i < tree->children->len; i++) { MockTree *child; child = g_ptr_array_index(tree->children, i); child->bed = g_object_ref(tree->bed); child->sysfs_parent = g_strdup(tree->path); g_timeout_add(child->device->delay_ms, mock_tree_attach_device, child); } return FALSE; } typedef struct SyncContext { MockTree *tree; GMainLoop *loop; } SyncContext; static gboolean on_sync_timeout(gpointer user_data) { SyncContext *ctx = (SyncContext *)user_data; g_main_loop_quit(ctx->loop); return FALSE; } static void sync_device_added(FuPlugin *plugin, FuDevice *device, gpointer user_data) { SyncContext *ctx = (SyncContext *)user_data; MockTree *tree = ctx->tree; const gchar *uuid = fu_device_get_physical_id(device); MockTree *target; target = (MockTree *)mock_tree_find_uuid(tree, uuid); if (target == NULL) { g_critical("Got device that could not be matched: %s", uuid); return; } if (target->fu_device != NULL) g_object_unref(target->fu_device); target->fu_device = g_object_ref(device); } static void sync_device_removed(FuPlugin *plugin, FuDevice *device, gpointer user_data) { SyncContext *ctx = (SyncContext *)user_data; MockTree *tree = ctx->tree; const gchar *uuid = fu_device_get_physical_id(device); MockTree *target; target = (MockTree *)mock_tree_find_uuid(tree, uuid); if (target == NULL) { g_warning("Got device that could not be matched: %s", uuid); return; } if (target->fu_device == NULL) { g_warning("Got remove event for out-of-tree device %s", uuid); return; } g_object_unref(target->fu_device); target->fu_device = NULL; } static void mock_tree_sync(MockTree *root, FuPlugin *plugin, int timeout_ms) { g_autoptr(GMainLoop) mainloop = g_main_loop_new(NULL, FALSE); gulong id_add; gulong id_del; SyncContext ctx = { .tree = root, .loop = mainloop, }; id_add = g_signal_connect(FU_PLUGIN(plugin), "device-added", G_CALLBACK(sync_device_added), &ctx); id_del = g_signal_connect(FU_PLUGIN(plugin), "device-removed", G_CALLBACK(sync_device_removed), &ctx); if (timeout_ms > 0) g_timeout_add(timeout_ms, on_sync_timeout, &ctx); g_main_loop_run(mainloop); g_signal_handler_disconnect(plugin, id_add); g_signal_handler_disconnect(plugin, id_del); } typedef struct AttachContext { /* in */ MockTree *tree; GMainLoop *loop; /* out */ gboolean complete; } AttachContext; static void mock_tree_plugin_device_added(FuPlugin *plugin, FuDevice *device, gpointer user_data) { AttachContext *ctx = (AttachContext *)user_data; MockTree *tree = ctx->tree; const gchar *uuid = fu_device_get_physical_id(device); MockTree *target; target = (MockTree *)mock_tree_find_uuid(tree, uuid); if (target == NULL) { g_warning("Got device that could not be matched: %s", uuid); return; } g_set_object(&target->fu_device, device); if (mock_tree_all(tree, mock_tree_node_have_fu_device, NULL)) { ctx->complete = TRUE; g_main_loop_quit(ctx->loop); } } static gboolean mock_tree_settle(MockTree *root, FuPlugin *plugin) { g_autoptr(GMainLoop) mainloop = g_main_loop_new(NULL, FALSE); gulong id; AttachContext ctx = { .tree = root, .loop = mainloop, }; id = g_signal_connect(FU_PLUGIN(plugin), "device-added", G_CALLBACK(mock_tree_plugin_device_added), &ctx); g_main_loop_run(mainloop); g_signal_handler_disconnect(plugin, id); return ctx.complete; } static gboolean mock_tree_attach(MockTree *root, UMockdevTestbed *bed, FuPlugin *plugin) { root->bed = g_object_ref(bed); root->sysfs_parent = udev_mock_add_usb4_port(bed, 1); g_assert_nonnull(root->sysfs_parent); g_timeout_add(root->device->delay_ms, mock_tree_attach_device, root); return mock_tree_settle(root, plugin); } /* the unused parameter makes the function signature compatible * with 'MockTreePredicate' */ static gboolean mock_tree_node_is_detached(const MockTree *node, gpointer unused) { gboolean ret = node->path == NULL; /* consistency checks: if ret, make sure we are * fully detached */ if (ret) { g_assert_null(node->nvm_active); g_assert_null(node->nvm_non_active); g_assert_null(node->bed); } else { g_assert_nonnull(node->nvm_active); g_assert_nonnull(node->nvm_non_active); g_assert_nonnull(node->bed); } return ret; } static void mock_tree_detach(MockTree *node) { UMockdevTestbed *bed; if (mock_tree_node_is_detached(node, NULL)) return; for (guint i = 0; i < node->children->len; i++) { MockTree *child = g_ptr_array_index(node->children, i); mock_tree_detach(child); g_free(child->sysfs_parent); child->sysfs_parent = NULL; } bed = node->bed; umockdev_testbed_uevent(bed, node->nvm_active, "remove"); umockdev_testbed_remove_device(bed, node->nvm_active); umockdev_testbed_uevent(bed, node->nvm_non_active, "remove"); umockdev_testbed_remove_device(bed, node->nvm_non_active); umockdev_testbed_uevent(bed, node->path, "remove"); umockdev_testbed_remove_device(bed, node->path); g_free(node->path); g_free(node->nvm_non_active); g_free(node->nvm_active); node->path = NULL; node->nvm_non_active = NULL; node->nvm_active = NULL; g_object_unref(bed); node->bed = NULL; } typedef enum UpdateResult { UPDATE_SUCCESS = 0, /* nvm_authenticate will report error condition */ UPDATE_FAIL_DEVICE_INTERNAL = 1, /* device to be updated will NOT re-appear */ UPDATE_FAIL_DEVICE_NOSHOW = 2 } UpdateResult; typedef struct UpdateContext { GFileMonitor *monitor; UpdateResult result; guint timeout; GBytes *data; UMockdevTestbed *bed; FuPlugin *plugin; MockTree *node; gchar *version; } UpdateContext; static void update_context_free(UpdateContext *ctx) { if (ctx == NULL) return; g_file_monitor_cancel(ctx->monitor); g_object_unref(ctx->bed); g_object_unref(ctx->plugin); g_object_unref(ctx->monitor); g_bytes_unref(ctx->data); g_free(ctx->version); g_free(ctx); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(UpdateContext, update_context_free); #pragma clang diagnostic pop static gboolean reattach_tree(gpointer user_data) { UpdateContext *ctx = (UpdateContext *)user_data; MockTree *node = ctx->node; g_debug("Mock update done, reattaching tree..."); node->bed = g_object_ref(ctx->bed); g_timeout_add(node->device->delay_ms, mock_tree_attach_device, node); return FALSE; } static void udev_file_changed_cb(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { UpdateContext *ctx = (UpdateContext *)user_data; gboolean ok; gsize len; g_autofree gchar *data = NULL; g_autoptr(GError) error = NULL; g_debug("Got update trigger"); ok = g_file_monitor_cancel(monitor); g_assert_true(ok); ok = g_file_load_contents(file, NULL, &data, &len, NULL, &error); g_assert_no_error(error); g_assert_true(ok); if (!g_str_has_prefix(data, "1")) return; /* verify the firmware is correct */ mock_tree_firmware_verify(ctx->node, ctx->data); g_debug("Removing tree below and including: %s", ctx->node->path); mock_tree_detach(ctx->node); ctx->node->nvm_authenticate = (guint)ctx->result; /* update the version only on "success" simulations */ if (ctx->result == UPDATE_SUCCESS) { g_free(ctx->node->nvm_version); ctx->node->nvm_version = g_strdup(ctx->version); } g_debug("Simulating update to '%s' with result: 0x%x", ctx->version, ctx->node->nvm_authenticate); if (ctx->result == UPDATE_FAIL_DEVICE_NOSHOW) { g_debug("Simulating no-show fail:" " device tree will not reappear"); return; } g_debug("Device tree reattachment in %3.2f seconds", ctx->timeout / 1000.0); g_timeout_add(ctx->timeout, reattach_tree, ctx); } static UpdateContext * mock_tree_prepare_for_update(MockTree *node, FuPlugin *plugin, const char *version, GBytes *fw_data, guint timeout_ms) { UpdateContext *ctx; g_autoptr(GFile) dir = NULL; g_autoptr(GFile) f = NULL; g_autoptr(GError) error = NULL; GFileMonitor *monitor; ctx = g_new0(UpdateContext, 1); dir = g_file_new_for_path(node->path); f = g_file_get_child(dir, "nvm_authenticate"); monitor = g_file_monitor_file(f, G_FILE_MONITOR_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(monitor); ctx->node = node; ctx->plugin = g_object_ref(plugin); ctx->bed = g_object_ref(node->bed); ctx->timeout = timeout_ms; ctx->monitor = monitor; ctx->version = g_strdup(version); ctx->data = g_bytes_ref(fw_data); g_signal_connect(G_FILE_MONITOR(monitor), "changed", G_CALLBACK(udev_file_changed_cb), ctx); return ctx; } static MockDevice root_one = { .name = "Laptop", .id = "0x23", .nvm_version = "20.2", .nvm_parsed_version = "20.02", .children = (MockDevice[]){ { .name = "Thunderbolt Cable", .id = "0x24", .nvm_version = "20.0", .nvm_parsed_version = "20.00", .children = (MockDevice[]){{ .name = "Thunderbolt Dock", .id = "0x25", .nvm_version = "10.0", .nvm_parsed_version = "10.00", }, { NULL, } }, }, { .name = "Thunderbolt Cable", .id = "0x24", .nvm_version = "23.0", .nvm_parsed_version = "23.00", .children = (MockDevice[]){{ .name = "Thunderbolt SSD", .id = "0x26", .nvm_version = "5.0", .nvm_parsed_version = "05.00", }, { NULL, }}, }, { NULL, }, }, }; typedef struct TestParam { gboolean initialize_tree; gboolean attach_and_coldplug; const char *firmware_file; } TestParam; typedef enum TestFlags { TEST_INITIALIZE_TREE = 1 << 0, TEST_ATTACH = 1 << 1, TEST_PREPARE_FIRMWARE = 1 << 2, TEST_PREPARE_ALL = TEST_INITIALIZE_TREE | TEST_ATTACH | TEST_PREPARE_FIRMWARE } TestFlags; #define TEST_INIT_FULL (GUINT_TO_POINTER(TEST_PREPARE_ALL)) #define TEST_INIT_NONE (GUINT_TO_POINTER(0)) typedef struct ThunderboltTest { UMockdevTestbed *bed; FuPlugin *plugin; FuContext *ctx; GUdevClient *udev_client; /* if TestParam::initialize_tree */ MockTree *tree; /* if TestParam::firmware_file is nonnull */ GMappedFile *fw_file; GBytes *fw_data; } ThunderboltTest; static void fu_thunderbolt_gudev_uevent_cb(GUdevClient *gudev_client, const gchar *action, GUdevDevice *udev_device, ThunderboltTest *tt) { if (g_strcmp0(action, "add") == 0) { g_autoptr(FuUdevDevice) device = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); device = fu_udev_device_new(tt->ctx, udev_device); if (!fu_device_probe(FU_DEVICE(device), &error_local)) { g_warning("failed to probe: %s", error_local->message); return; } if (!fu_plugin_runner_backend_device_added(tt->plugin, FU_DEVICE(device), progress, &error_local)) g_debug("failed to add: %s", error_local->message); return; } if (g_strcmp0(action, "remove") == 0) { if (tt->tree->fu_device != NULL) fu_plugin_device_remove(tt->plugin, tt->tree->fu_device); return; } if (g_strcmp0(action, "change") == 0) { const gchar *uuid = g_udev_device_get_sysfs_attr(udev_device, "unique_id"); MockTree *target = (MockTree *)mock_tree_find_uuid(tt->tree, uuid); g_assert_nonnull(target); fu_udev_device_emit_changed(FU_UDEV_DEVICE(target->fu_device)); return; } } static void test_set_up(ThunderboltTest *tt, gconstpointer params) { TestFlags flags = GPOINTER_TO_UINT(params); gboolean ret; g_autofree gchar *sysfs = NULL; g_autoptr(GError) error = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); const gchar *udev_subsystems[] = {"thunderbolt", NULL}; tt->ctx = fu_context_new(); ret = fu_context_load_quirks(tt->ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE | FU_QUIRKS_LOAD_FLAG_NO_VERIFY, &error); g_assert_no_error(error); g_assert_true(ret); tt->bed = umockdev_testbed_new(); g_assert_nonnull(tt->bed); sysfs = umockdev_testbed_get_sys_dir(tt->bed); g_debug("mock sysfs at %s", sysfs); tt->plugin = fu_plugin_new_from_gtype(fu_thunderbolt_plugin_get_type(), tt->ctx); g_assert_nonnull(tt->plugin); ret = fu_plugin_runner_startup(tt->plugin, progress, &error); g_assert_no_error(error); g_assert_true(ret); if (flags & TEST_INITIALIZE_TREE) { tt->tree = mock_tree_init(&root_one); g_assert_nonnull(tt->tree); } if (!umockdev_in_mock_environment()) { g_warning("Need to run with umockdev-wrapper"); return; } tt->udev_client = g_udev_client_new(udev_subsystems); g_assert_nonnull(tt->udev_client); g_signal_connect(G_UDEV_CLIENT(tt->udev_client), "uevent", G_CALLBACK(fu_thunderbolt_gudev_uevent_cb), tt); if (flags & TEST_ATTACH) { g_assert_true(flags & TEST_INITIALIZE_TREE); ret = mock_tree_attach(tt->tree, tt->bed, tt->plugin); g_assert_true(ret); } if (flags & TEST_PREPARE_FIRMWARE) { g_autofree gchar *fw_path = NULL; fw_path = g_test_build_filename(G_TEST_DIST, "tests", "minimal-fw.bin", NULL); tt->fw_file = g_mapped_file_new(fw_path, FALSE, &error); g_assert_no_error(error); g_assert_nonnull(tt->fw_file); tt->fw_data = g_mapped_file_get_bytes(tt->fw_file); g_assert_nonnull(tt->fw_data); } } static void test_tear_down(ThunderboltTest *tt, gconstpointer user_data) { g_object_unref(tt->plugin); g_object_unref(tt->ctx); g_object_unref(tt->bed); g_object_unref(tt->udev_client); if (tt->tree) mock_tree_free(tt->tree); if (tt->fw_data) g_bytes_unref(tt->fw_data); if (tt->fw_file) g_mapped_file_unref(tt->fw_file); } static gboolean test_tree_uuids(const MockTree *node, gpointer data) { const MockTree *root = (MockTree *)data; const gchar *uuid = node->uuid; const MockTree *found; g_assert_nonnull(uuid); g_debug("Looking for %s", uuid); found = mock_tree_find_uuid(root, uuid); g_assert_nonnull(node); g_assert_nonnull(found); g_assert_cmpstr(node->uuid, ==, found->uuid); /* return false so we traverse the whole tree */ return FALSE; } static void test_tree(ThunderboltTest *tt, gconstpointer user_data) { const MockTree *found; gboolean ret; g_autoptr(MockTree) tree = NULL; tree = mock_tree_init(&root_one); g_assert_nonnull(tree); mock_tree_dump(tree, 0); (void)mock_tree_contains(tree, test_tree_uuids, tree); found = mock_tree_find_uuid(tree, "nonexistentuuid"); g_assert_null(found); ret = mock_tree_attach(tree, tt->bed, tt->plugin); g_assert_true(ret); mock_tree_detach(tree); ret = mock_tree_all(tree, mock_tree_node_is_detached, NULL); g_assert_true(ret); } static void test_image_validation(ThunderboltTest *tt, gconstpointer user_data) { gboolean ret; g_autofree gchar *ctl_path = NULL; g_autofree gchar *fwi_path = NULL; g_autofree gchar *bad_path = NULL; g_autoptr(GMappedFile) fwi_file = NULL; g_autoptr(GMappedFile) ctl_file = NULL; g_autoptr(GMappedFile) bad_file = NULL; g_autoptr(GBytes) fwi_data = NULL; g_autoptr(GBytes) ctl_data = NULL; g_autoptr(GBytes) bad_data = NULL; g_autoptr(GError) error = NULL; g_autoptr(FuFirmware) firmware_fwi = fu_intel_thunderbolt_firmware_new(); g_autoptr(FuFirmware) firmware_ctl = fu_intel_thunderbolt_nvm_new(); g_autoptr(FuFirmware) firmware_bad = fu_intel_thunderbolt_nvm_new(); /* image as if read from the controller (i.e. no headers) */ ctl_path = g_test_build_filename(G_TEST_DIST, "tests", "minimal-fw-controller.bin", NULL); ctl_file = g_mapped_file_new(ctl_path, FALSE, &error); g_assert_no_error(error); g_assert_nonnull(ctl_file); ctl_data = g_mapped_file_get_bytes(ctl_file); g_assert_nonnull(ctl_data); /* parse controller image */ ret = fu_firmware_parse(firmware_ctl, ctl_data, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); /* valid firmware update image */ fwi_path = g_test_build_filename(G_TEST_DIST, "tests", "minimal-fw.bin", NULL); fwi_file = g_mapped_file_new(fwi_path, FALSE, &error); g_assert_no_error(error); g_assert_nonnull(fwi_file); fwi_data = g_mapped_file_get_bytes(fwi_file); g_assert_nonnull(fwi_data); /* parse */ ret = fu_firmware_parse(firmware_fwi, fwi_data, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); /* a wrong/bad firmware update image */ bad_path = g_test_build_filename(G_TEST_DIST, "tests", "colorhug.bin", NULL); bad_file = g_mapped_file_new(bad_path, FALSE, &error); g_assert_no_error(error); g_assert_nonnull(bad_file); bad_data = g_mapped_file_get_bytes(bad_file); g_assert_nonnull(bad_data); /* parse; should fail, bad image */ ret = fu_firmware_parse(firmware_bad, bad_data, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_false(ret); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA); g_debug("expected image validation error [ctl]: %s", error->message); g_clear_error(&error); /* now for some testing ... this should work */ ret = fu_firmware_check_compatible(firmware_ctl, firmware_fwi, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); } static void test_change_uevent(ThunderboltTest *tt, gconstpointer user_data) { FuPlugin *plugin = tt->plugin; MockTree *tree = tt->tree; gboolean ret; const gchar *version_after; /* test sanity check */ g_assert_nonnull(tree); /* simulate change of version via a change even, i.e. * without add, remove. */ umockdev_testbed_set_attribute(tt->bed, tree->path, "nvm_version", "42.23"); umockdev_testbed_uevent(tt->bed, tree->path, "change"); /* we just "wait" for 500ms, should be enough */ mock_tree_sync(tree, plugin, 500); /* the tree should not have changed */ ret = mock_tree_all(tree, mock_tree_node_have_fu_device, NULL); g_assert_true(ret); /* we should have the version change in the FuDevice */ version_after = fu_device_get_version(tree->fu_device); g_assert_cmpstr(version_after, ==, "42.23"); } static void test_update_working(ThunderboltTest *tt, gconstpointer user_data) { FuPlugin *plugin = tt->plugin; MockTree *tree = tt->tree; GBytes *fw_data = tt->fw_data; gboolean ret; const gchar *version_after; g_autoptr(GError) error = NULL; g_autoptr(UpdateContext) up_ctx = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* test sanity check */ g_assert_nonnull(tree); g_assert_nonnull(fw_data); /* simulate an update, where the device goes away and comes back * after the time in the last parameter (given in ms) */ up_ctx = mock_tree_prepare_for_update(tree, plugin, "42.23", fw_data, 1000); g_assert_nonnull(up_ctx); ret = fu_plugin_runner_write_firmware(plugin, tree->fu_device, fw_data, progress, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); /* we wait until the plugin has picked up all the * subtree changes */ ret = mock_tree_settle(tree, plugin); g_assert_true(ret); ret = fu_plugin_runner_attach(plugin, tree->fu_device, progress, &error); g_assert_no_error(error); g_assert_true(ret); version_after = fu_device_get_version(tree->fu_device); g_debug("version after update: %s", version_after); g_assert_cmpstr(version_after, ==, "42.23"); /* make sure all pending events have happened */ ret = mock_tree_settle(tree, plugin); g_assert_true(ret); /* now we check if the every tree node has a corresponding FuDevice, * this implicitly checks that we are handling uevents correctly * after the event, and that we are in sync with the udev tree */ ret = mock_tree_all(tree, mock_tree_node_have_fu_device, NULL); g_assert_true(ret); } static void test_update_wd19(ThunderboltTest *tt, gconstpointer user_data) { FuPlugin *plugin = tt->plugin; MockTree *tree = tt->tree; GBytes *fw_data = tt->fw_data; gboolean ret; const gchar *version_before; const gchar *version_after; g_autoptr(GError) error = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* test sanity check */ g_assert_nonnull(tree); g_assert_nonnull(fw_data); /* simulate a wd19 update which will not disappear / re-appear */ fu_device_add_flag(tree->fu_device, FWUPD_DEVICE_FLAG_SKIPS_RESTART); fu_device_add_flag(tree->fu_device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); version_before = fu_device_get_version(tree->fu_device); ret = fu_plugin_runner_write_firmware(plugin, tree->fu_device, fw_data, progress, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_device_has_flag(tree->fu_device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); g_assert_true(ret); version_after = fu_device_get_version(tree->fu_device); g_assert_cmpstr(version_after, ==, version_before); } static void test_update_fail(ThunderboltTest *tt, gconstpointer user_data) { FuPlugin *plugin = tt->plugin; MockTree *tree = tt->tree; GBytes *fw_data = tt->fw_data; gboolean ret; const gchar *version_after; g_autoptr(GError) error = NULL; g_autoptr(UpdateContext) up_ctx = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* test sanity check */ g_assert_nonnull(tree); g_assert_nonnull(fw_data); /* simulate an update, as in test_update_working, * but simulate an error indicated by the device */ up_ctx = mock_tree_prepare_for_update(tree, plugin, "42.23", fw_data, 1000); up_ctx->result = UPDATE_FAIL_DEVICE_INTERNAL; ret = fu_plugin_runner_write_firmware(plugin, tree->fu_device, fw_data, progress, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); /* we wait until the plugin has picked up all the * subtree changes, and make sure we still receive * udev updates correctly and are in sync */ ret = mock_tree_settle(tree, plugin); g_assert_true(ret); ret = fu_plugin_runner_attach(plugin, tree->fu_device, progress, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL); g_assert_false(ret); /* make sure all pending events have happened */ ret = mock_tree_settle(tree, plugin); g_assert_true(ret); /* version should *not* have changed (but we get parsed version) */ version_after = fu_device_get_version(tree->fu_device); g_debug("version after update: %s", version_after); g_assert_cmpstr(version_after, ==, tree->device->nvm_parsed_version); ret = mock_tree_all(tree, mock_tree_node_have_fu_device, NULL); g_assert_true(ret); } static void test_update_fail_nowshow(ThunderboltTest *tt, gconstpointer user_data) { FuPlugin *plugin = tt->plugin; MockTree *tree = tt->tree; GBytes *fw_data = tt->fw_data; gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(UpdateContext) up_ctx = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* test sanity check */ g_assert_nonnull(tree); g_assert_nonnull(fw_data); /* simulate an update, as in test_update_working, * but simulate an error indicated by the device */ up_ctx = mock_tree_prepare_for_update(tree, plugin, "42.23", fw_data, 1000); up_ctx->result = UPDATE_FAIL_DEVICE_NOSHOW; ret = fu_plugin_runner_write_firmware(plugin, tree->fu_device, fw_data, progress, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); mock_tree_sync(tree, plugin, 500); ret = mock_tree_all(tree, mock_tree_node_have_fu_device, NULL); g_assert_false(ret); } int main(int argc, char **argv) { g_autofree gchar *testdatadir = NULL; (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", testdatadir, TRUE); g_test_add("/thunderbolt/basic", ThunderboltTest, NULL, test_set_up, test_tree, test_tear_down); g_test_add("/thunderbolt/image-validation", ThunderboltTest, TEST_INIT_NONE, test_set_up, test_image_validation, test_tear_down); g_test_add("/thunderbolt/change-uevent", ThunderboltTest, GUINT_TO_POINTER(TEST_INITIALIZE_TREE | TEST_ATTACH), test_set_up, test_change_uevent, test_tear_down); g_test_add("/thunderbolt/update{working}", ThunderboltTest, TEST_INIT_FULL, test_set_up, test_update_working, test_tear_down); g_test_add("/thunderbolt/update{failing}", ThunderboltTest, TEST_INIT_FULL, test_set_up, test_update_fail, test_tear_down); g_test_add("/thunderbolt/update{failing-noshow}", ThunderboltTest, TEST_INIT_FULL, test_set_up, test_update_fail_nowshow, test_tear_down); g_test_add("/thunderbolt/update{delayed_activation}", ThunderboltTest, TEST_INIT_FULL, test_set_up, test_update_wd19, test_tear_down); return g_test_run(); } fwupd-1.9.16/plugins/thunderbolt/fu-thunderbolt-common.c000066400000000000000000000052011460375044200233470ustar00rootroot00000000000000/* * Copyright (C) 2017 Christian J. Kellner * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-thunderbolt-common.h" static gboolean fu_thunderbolt_device_check_usb4_port_path(FuUdevDevice *device, const gchar *attribute, GError **err) { g_autofree const gchar *path = g_build_filename(fu_udev_device_get_sysfs_path(device), attribute, NULL); g_autofree gchar *fn = g_strdup_printf("%s", path); g_autoptr(GFile) file = g_file_new_for_path(fn); if (!g_file_query_exists(file, NULL)) { g_set_error(err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find %s", fn); return FALSE; } return TRUE; } gboolean fu_thunderbolt_udev_set_port_offline(FuUdevDevice *device, GError **error) { const gchar *offline = "usb4_port1/offline"; const gchar *rescan = "usb4_port1/rescan"; g_autoptr(GError) error_local = NULL; if (!fu_thunderbolt_device_check_usb4_port_path(device, offline, &error_local)) { g_debug("failed to check usb4 offline path: %s", error_local->message); return TRUE; } if (!fu_udev_device_write_sysfs(device, offline, "1", error)) { g_prefix_error(error, "setting usb4 port offline failed: "); return FALSE; } if (!fu_thunderbolt_device_check_usb4_port_path(device, rescan, &error_local)) { g_debug("failed to check usb4 rescan path: %s", error_local->message); return TRUE; } if (!fu_udev_device_write_sysfs(device, rescan, "1", error)) { g_prefix_error(error, "rescan on port failed: "); return FALSE; } return TRUE; } gboolean fu_thunderbolt_udev_set_port_online(FuUdevDevice *device, GError **error) { FuUdevDevice *udev = FU_UDEV_DEVICE(device); const gchar *offline = "usb4_port1/offline"; g_autoptr(GError) error_local = NULL; if (!fu_thunderbolt_device_check_usb4_port_path(device, offline, &error_local)) { g_debug("failed to check usb4 port path: %s", error_local->message); return TRUE; } if (!fu_udev_device_write_sysfs(udev, offline, "0", error)) { g_prefix_error(error, "setting port online failed: "); return FALSE; } return TRUE; } guint16 fu_thunderbolt_udev_get_attr_uint16(FuUdevDevice *device, const gchar *name, GError **error) { const gchar *str; guint64 val; str = fu_udev_device_get_sysfs_attr(device, name, error); if (str == NULL) return 0x0; val = g_ascii_strtoull(str, NULL, 16); if (val == 0x0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to parse %s", str); return 0; } if (val > G_MAXUINT16) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s overflows", name); return 0x0; } return (guint16)val; } fwupd-1.9.16/plugins/thunderbolt/fu-thunderbolt-common.h000066400000000000000000000012361460375044200233600ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /** * FU_THUNDERBOLT_DEVICE_FLAG_FORCE_ENUMERATION: * * Forces composite device components to be enumerated. */ #define FU_THUNDERBOLT_DEVICE_FLAG_FORCE_ENUMERATION (1ull << 1) gboolean fu_thunderbolt_udev_set_port_online(FuUdevDevice *device, GError **error); gboolean fu_thunderbolt_udev_set_port_offline(FuUdevDevice *device, GError **error); guint16 fu_thunderbolt_udev_get_attr_uint16(FuUdevDevice *device, const gchar *name, GError **error); fwupd-1.9.16/plugins/thunderbolt/fu-thunderbolt-controller.c000066400000000000000000000306161460375044200242520ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2017 Christian J. Kellner * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-thunderbolt-common.h" #include "fu-thunderbolt-controller.h" typedef enum { FU_THUNDERBOLT_CONTROLLER_KIND_DEVICE, FU_THUNDERBOLT_CONTROLLER_KIND_HOST, } FuThunderboltControllerKind; struct _FuThunderboltController { FuThunderboltDevice parent_instance; FuThunderboltControllerKind controller_kind; gboolean safe_mode; gboolean is_native; guint16 gen; guint host_online_timer_id; }; G_DEFINE_TYPE(FuThunderboltController, fu_thunderbolt_controller, FU_TYPE_THUNDERBOLT_DEVICE) /* byte offsets in firmware image */ #define FU_TBT_OFFSET_NATIVE 0x7B #define FU_TBT_CHUNK_SZ 0x40 static void fu_thunderbolt_controller_check_safe_mode(FuThunderboltController *self) { const gchar *devpath = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(self)); /* failed to read, for host check for safe mode */ if (self->controller_kind != FU_THUNDERBOLT_CONTROLLER_KIND_DEVICE) return; g_warning("%s is in safe mode -- VID/DID will " "need to be set by another plugin", devpath); self->safe_mode = TRUE; fu_device_set_version(FU_DEVICE(self), "00.00"); fu_device_add_instance_id(FU_DEVICE(self), "TBT-safemode"); fu_device_set_metadata_boolean(FU_DEVICE(self), FU_DEVICE_METADATA_TBT_IS_SAFE_MODE, TRUE); } static const gchar * fu_thunderbolt_controller_kind_to_string(FuThunderboltController *self) { if (self->controller_kind == FU_THUNDERBOLT_CONTROLLER_KIND_HOST) { if (self->gen >= 4) return "USB4 host controller"; return "Thunderbolt host controller"; } if (self->controller_kind == FU_THUNDERBOLT_CONTROLLER_KIND_DEVICE) { if (self->gen >= 4) return "USB4 device controller"; return "Thunderbolt device controller"; } return "Unknown"; } static void fu_thunderbolt_controller_to_string(FuDevice *device, guint idt, GString *str) { FuThunderboltController *self = FU_THUNDERBOLT_CONTROLLER(device); /* FuThunderboltDevice->to_string */ FU_DEVICE_CLASS(fu_thunderbolt_controller_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "DeviceType", fu_thunderbolt_controller_kind_to_string(self)); fu_string_append_kb(str, idt, "SafeMode", self->safe_mode); fu_string_append_kb(str, idt, "NativeMode", self->is_native); fu_string_append_ku(str, idt, "Generation", self->gen); } static gboolean fu_thunderbolt_controller_probe(FuDevice *device, GError **error) { FuThunderboltController *self = FU_THUNDERBOLT_CONTROLLER(device); const gchar *unique_id; g_autofree gchar *parent_name = NULL; /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS(fu_thunderbolt_controller_parent_class)->probe(device, error)) return FALSE; /* determine if host controller or not */ parent_name = fu_udev_device_get_parent_name(FU_UDEV_DEVICE(self)); if (parent_name != NULL && g_str_has_prefix(parent_name, "domain")) self->controller_kind = FU_THUNDERBOLT_CONTROLLER_KIND_HOST; unique_id = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "unique_id", NULL); if (unique_id != NULL) fu_device_set_physical_id(device, unique_id); /* success */ return TRUE; } static gboolean fu_thunderbolt_controller_read_status_block(FuThunderboltController *self, GError **error) { gsize nr_chunks; g_autoptr(GFile) nvmem = NULL; g_autoptr(GBytes) controller_fw = NULL; g_autoptr(GInputStream) istr = NULL; g_autoptr(FuFirmware) firmware = NULL; nvmem = fu_thunderbolt_device_find_nvmem(FU_THUNDERBOLT_DEVICE(self), TRUE, error); if (nvmem == NULL) return FALSE; /* read just enough bytes to read the status byte */ nr_chunks = (FU_TBT_OFFSET_NATIVE + FU_TBT_CHUNK_SZ - 1) / FU_TBT_CHUNK_SZ; istr = G_INPUT_STREAM(g_file_read(nvmem, NULL, error)); if (istr == NULL) return FALSE; controller_fw = g_input_stream_read_bytes(istr, nr_chunks * FU_TBT_CHUNK_SZ, NULL, error); if (controller_fw == NULL) return FALSE; firmware = fu_firmware_new_from_gtypes(controller_fw, 0x0, FWUPD_INSTALL_FLAG_NO_SEARCH, error, FU_TYPE_INTEL_THUNDERBOLT_NVM, FU_TYPE_FIRMWARE, G_TYPE_INVALID); if (firmware == NULL) return FALSE; if (FU_IS_INTEL_THUNDERBOLT_NVM(firmware)) { self->is_native = fu_intel_thunderbolt_nvm_is_native(FU_INTEL_THUNDERBOLT_NVM(firmware)); } return TRUE; } static gboolean fu_thunderbolt_controller_can_update(FuThunderboltController *self) { g_autoptr(GError) nvmem_error = NULL; g_autoptr(GFile) non_active_nvmem = NULL; non_active_nvmem = fu_thunderbolt_device_find_nvmem(FU_THUNDERBOLT_DEVICE(self), FALSE, &nvmem_error); if (non_active_nvmem == NULL) { g_debug("%s", nvmem_error->message); return FALSE; } return TRUE; } static gboolean fu_thunderbolt_controller_set_port_online_cb(gpointer user_data) { FuThunderboltController *self = FU_THUNDERBOLT_CONTROLLER(user_data); g_autoptr(GError) error_local = NULL; if (!fu_thunderbolt_udev_set_port_online(FU_UDEV_DEVICE(self), &error_local)) g_warning("failed to set online after initial delay: %s", error_local->message); /* no longer valid */ self->host_online_timer_id = 0; return G_SOURCE_REMOVE; } static gboolean fu_thunderbolt_controller_setup_usb4(FuThunderboltController *self, GError **error) { if (!fu_thunderbolt_udev_set_port_offline(FU_UDEV_DEVICE(self), error)) return FALSE; self->host_online_timer_id = g_timeout_add_seconds(5, fu_thunderbolt_controller_set_port_online_cb, self); return TRUE; } static void fu_thunderbolt_controller_set_signed(FuDevice *device) { FuThunderboltController *self = FU_THUNDERBOLT_CONTROLLER(device); GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); const gchar *tmp; /* if it's a USB4 type not of host and generation 3; it's Intel */ tmp = g_udev_device_get_property(udev_device, "USB4_TYPE"); if (g_strcmp0(tmp, "host") != 0 && self->gen == 3) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); } static gboolean fu_thunderbolt_controller_setup(FuDevice *device, GError **error) { FuThunderboltController *self = FU_THUNDERBOLT_CONTROLLER(device); const gchar *tmp = NULL; guint16 did; guint16 vid; g_autoptr(GError) error_gen = NULL; g_autoptr(GError) error_version = NULL; /* try to read the version */ if (!fu_thunderbolt_device_get_version(FU_THUNDERBOLT_DEVICE(self), &error_version)) { if (self->controller_kind != FU_THUNDERBOLT_CONTROLLER_KIND_HOST && g_error_matches(error_version, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_propagate_error(error, g_steal_pointer(&error_version)); return FALSE; } g_debug("%s", error_version->message); } /* these may be missing on ICL or later */ vid = fu_udev_device_get_vendor(FU_UDEV_DEVICE(self)); if (vid == 0x0) g_debug("failed to get Vendor ID"); did = fu_udev_device_get_model(FU_UDEV_DEVICE(self)); if (did == 0x0) g_debug("failed to get Device ID"); /* requires kernel 5.5 or later, non-fatal if not available */ self->gen = fu_thunderbolt_udev_get_attr_uint16(FU_UDEV_DEVICE(self), "generation", &error_gen); if (self->gen == 0) g_debug("unable to read generation: %s", error_gen->message); if (self->controller_kind == FU_THUNDERBOLT_CONTROLLER_KIND_HOST) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_set_summary(device, "Unmatched performance for high-speed I/O"); } else { tmp = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(self), "device_name", NULL); } /* set the controller name */ if (tmp == NULL) tmp = fu_thunderbolt_controller_kind_to_string(self); fu_device_set_name(device, tmp); /* set vendor string */ tmp = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(self), "vendor_name", NULL); if (tmp != NULL) fu_device_set_vendor(device, tmp); if (fu_device_get_version(device) == NULL) fu_thunderbolt_controller_check_safe_mode(self); if (self->safe_mode) { fu_device_set_update_error(device, "Device is in safe mode"); } else { g_autofree gchar *device_id = NULL; g_autofree gchar *domain_id = NULL; if (fu_thunderbolt_controller_can_update(self)) { const gchar *devpath = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(self)); g_autofree gchar *vendor_id = NULL; g_autofree gchar *domain = g_path_get_basename(devpath); /* USB4 controllers don't have a concept of legacy vs native * so don't try to read a native attribute from their NVM */ if (self->controller_kind == FU_THUNDERBOLT_CONTROLLER_KIND_HOST && self->gen < 4) { /* read first block of firmware to get the is-native attribute */ if (!fu_thunderbolt_controller_read_status_block(self, error)) return FALSE; } else { self->is_native = FALSE; } domain_id = g_strdup_printf("TBT-%04x%04x%s-controller%s", (guint)vid, (guint)did, self->is_native ? "-native" : "", domain); vendor_id = g_strdup_printf("TBT:0x%04X", (guint)vid); fu_device_add_vendor_id(device, vendor_id); device_id = g_strdup_printf("TBT-%04x%04x%s", (guint)vid, (guint)did, self->is_native ? "-native" : ""); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); /* check if device is authorized */ if (!fu_thunderbolt_device_check_authorized(FU_THUNDERBOLT_DEVICE(self), error)) return FALSE; } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "updates are distributed as part of the platform"); return FALSE; } fu_device_add_instance_id(device, device_id); if (domain_id != NULL) fu_device_add_instance_id(device, domain_id); } /* determine if we can update on unplug */ if (fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "nvm_authenticate_on_disconnect", NULL) != NULL) { fu_thunderbolt_device_set_auth_method(FU_THUNDERBOLT_DEVICE(self), "nvm_authenticate_on_disconnect"); /* flushes image */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); /* forces the device to write to authenticate on disconnect attribute */ fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_SKIPS_RESTART); /* control the order of activation (less relevant; install too though) */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST); } else { fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); } if (self->controller_kind == FU_THUNDERBOLT_CONTROLLER_KIND_HOST && fu_device_has_private_flag(FU_DEVICE(self), FU_THUNDERBOLT_DEVICE_FLAG_FORCE_ENUMERATION)) { g_autoptr(GError) error_local = NULL; if (!fu_thunderbolt_controller_setup_usb4(self, &error_local)) g_warning("failed to setup host: %s", error_local->message); } /* set up signed payload attribute */ fu_thunderbolt_controller_set_signed(device); /* success */ return TRUE; } static gboolean fu_thunderbolt_controller_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { /* FuThunderboltDevice->write_firmware */ if (!FU_DEVICE_CLASS(fu_thunderbolt_controller_parent_class) ->write_firmware(device, firmware, progress, flags, error)) return FALSE; if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE)) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_thunderbolt_controller_init(FuThunderboltController *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_register_private_flag(FU_DEVICE(self), FU_THUNDERBOLT_DEVICE_FLAG_FORCE_ENUMERATION, "force-enumeration"); } static void fu_thunderbolt_controller_finalize(GObject *object) { FuThunderboltController *self = FU_THUNDERBOLT_CONTROLLER(object); if (self->host_online_timer_id != 0) g_source_remove(self->host_online_timer_id); G_OBJECT_CLASS(fu_thunderbolt_controller_parent_class)->finalize(object); } static void fu_thunderbolt_controller_class_init(FuThunderboltControllerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_thunderbolt_controller_finalize; klass_device->setup = fu_thunderbolt_controller_setup; klass_device->probe = fu_thunderbolt_controller_probe; klass_device->to_string = fu_thunderbolt_controller_to_string; klass_device->write_firmware = fu_thunderbolt_controller_write_firmware; } fwupd-1.9.16/plugins/thunderbolt/fu-thunderbolt-controller.h000066400000000000000000000006341460375044200242540ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-thunderbolt-device.h" #define FU_TYPE_THUNDERBOLT_CONTROLLER (fu_thunderbolt_controller_get_type()) G_DECLARE_FINAL_TYPE(FuThunderboltController, fu_thunderbolt_controller, FU, THUNDERBOLT_CONTROLLER, FuThunderboltDevice) fwupd-1.9.16/plugins/thunderbolt/fu-thunderbolt-device.c000066400000000000000000000320031460375044200233160ustar00rootroot00000000000000/* * Copyright (C) 2017 Christian J. Kellner * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include "fu-thunderbolt-device.h" typedef struct { const gchar *auth_method; } FuThunderboltDevicePrivate; #define TBT_NVM_RETRY_TIMEOUT 200 /* ms */ #define FU_PLUGIN_THUNDERBOLT_UPDATE_TIMEOUT 60000 /* ms */ G_DEFINE_TYPE_WITH_PRIVATE(FuThunderboltDevice, fu_thunderbolt_device, FU_TYPE_UDEV_DEVICE) #define GET_PRIVATE(o) (fu_thunderbolt_device_get_instance_private(o)) GFile * fu_thunderbolt_device_find_nvmem(FuThunderboltDevice *self, gboolean active, GError **error) { const gchar *nvmem_dir = active ? "nvm_active" : "nvm_non_active"; const gchar *name; const gchar *devpath = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(self)); g_autoptr(GDir) d = NULL; if (G_UNLIKELY(devpath == NULL)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Could not determine sysfs path for device"); return NULL; } d = g_dir_open(devpath, 0, error); if (d == NULL) return NULL; while ((name = g_dir_read_name(d)) != NULL) { if (g_str_has_prefix(name, nvmem_dir)) { g_autoptr(GFile) parent = g_file_new_for_path(devpath); g_autoptr(GFile) nvm_dir = g_file_get_child(parent, name); return g_file_get_child(nvm_dir, "nvmem"); } } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Could not find non-volatile memory location"); return NULL; } gboolean fu_thunderbolt_device_check_authorized(FuThunderboltDevice *self, GError **error) { guint64 status; g_autofree gchar *attribute = NULL; const gchar *devpath = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(self)); /* read directly from file to prevent udev caching */ g_autofree gchar *safe_path = g_build_path("/", devpath, "authorized", NULL); if (!g_file_test(safe_path, G_FILE_TEST_EXISTS)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "missing authorized attribute"); return FALSE; } if (!g_file_get_contents(safe_path, &attribute, NULL, error)) return FALSE; status = g_ascii_strtoull(attribute, NULL, 16); if (status == G_MAXUINT64 && errno == ERANGE) { g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errno), "failed to read 'authorized: %s", g_strerror(errno)); return FALSE; } if (status == 1 || status == 2) fu_device_uninhibit(FU_DEVICE(self), "not-authorized"); else fu_device_inhibit(FU_DEVICE(self), "not-authorized", "Not authorized"); return TRUE; } gboolean fu_thunderbolt_device_get_version(FuThunderboltDevice *self, GError **error) { const gchar *devpath = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(self)); g_auto(GStrv) split = NULL; g_autofree gchar *version_raw = NULL; g_autofree gchar *version = NULL; /* read directly from file to prevent udev caching */ g_autofree gchar *safe_path = g_build_path("/", devpath, "nvm_version", NULL); if (!g_file_test(safe_path, G_FILE_TEST_EXISTS)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "missing nvm_version attribute"); return FALSE; } for (guint i = 0; i < 50; i++) { g_autoptr(GError) error_local = NULL; /* glib can't return a properly mapped -ENODATA but the * kernel only returns -ENODATA or -EAGAIN */ if (g_file_get_contents(safe_path, &version_raw, NULL, &error_local)) break; g_debug("attempt %u: failed to read NVM version", i); fu_device_sleep(FU_DEVICE(self), TBT_NVM_RETRY_TIMEOUT); /* safe mode probably */ if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) break; } if (version_raw == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to read NVM"); return FALSE; } split = g_strsplit(version_raw, ".", -1); if (g_strv_length(split) != 2) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "invalid nvm_version format: %s", version_raw); return FALSE; } version = g_strdup_printf("%02x.%02x", (guint)g_ascii_strtoull(split[0], NULL, 16), (guint)g_ascii_strtoull(split[1], NULL, 16)); fu_device_set_version(FU_DEVICE(self), version); return TRUE; } static void fu_thunderbolt_device_to_string(FuDevice *device, guint idt, GString *str) { FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE(device); FuThunderboltDevicePrivate *priv = GET_PRIVATE(self); /* FuUdevDevice->to_string */ FU_DEVICE_CLASS(fu_thunderbolt_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "AuthMethod", priv->auth_method); } void fu_thunderbolt_device_set_auth_method(FuThunderboltDevice *self, const gchar *auth_method) { FuThunderboltDevicePrivate *priv = GET_PRIVATE(self); priv->auth_method = auth_method; } static gboolean fu_thunderbolt_device_activate(FuDevice *device, FuProgress *progress, GError **error) { FuUdevDevice *udev = FU_UDEV_DEVICE(device); return fu_udev_device_write_sysfs(udev, "nvm_authenticate", "1", error); } static gboolean fu_thunderbolt_device_authenticate(FuDevice *device, GError **error) { FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE(device); FuThunderboltDevicePrivate *priv = GET_PRIVATE(self); FuUdevDevice *udev = FU_UDEV_DEVICE(device); return fu_udev_device_write_sysfs(udev, priv->auth_method, "1", error); } static gboolean fu_thunderbolt_device_flush_update(FuDevice *device, GError **error) { FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE(device); FuThunderboltDevicePrivate *priv = GET_PRIVATE(self); FuUdevDevice *udev = FU_UDEV_DEVICE(device); return fu_udev_device_write_sysfs(udev, priv->auth_method, "2", error); } static gboolean fu_thunderbolt_device_attach(FuDevice *device, FuProgress *progress, GError **error) { const gchar *attribute; guint64 status; /* now check if the update actually worked */ attribute = fu_udev_device_get_sysfs_attr(FU_UDEV_DEVICE(device), "nvm_authenticate", error); if (attribute == NULL) return FALSE; status = g_ascii_strtoull(attribute, NULL, 16); if (status == G_MAXUINT64 && errno == ERANGE) { g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errno), "failed to read 'nvm_authenticate: %s", g_strerror(errno)); return FALSE; } /* anything else then 0x0 means we got an error */ if (status != 0x0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "update failed (status %" G_GINT64_MODIFIER "x)", status); return FALSE; } return TRUE; } static gboolean fu_thunderbolt_device_rescan(FuDevice *device, GError **error) { FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE(device); /* refresh updatability */ if (!fu_thunderbolt_device_check_authorized(self, error)) return FALSE; /* refresh the version */ return fu_thunderbolt_device_get_version(self, error); } static gboolean fu_thunderbolt_device_write_data(FuThunderboltDevice *self, GBytes *blob_fw, FuProgress *progress, GError **error) { gsize fw_size; gsize nwritten; gssize n; g_autoptr(GFile) nvmem = NULL; g_autoptr(GOutputStream) os = NULL; nvmem = fu_thunderbolt_device_find_nvmem(self, FALSE, error); if (nvmem == NULL) return FALSE; os = (GOutputStream *)g_file_append_to(nvmem, G_FILE_CREATE_NONE, NULL, error); if (os == NULL) return FALSE; nwritten = 0; fw_size = g_bytes_get_size(blob_fw); do { g_autoptr(GBytes) fw_data = NULL; fw_data = fu_bytes_new_offset(blob_fw, nwritten, fw_size - nwritten, error); if (fw_data == NULL) return FALSE; n = g_output_stream_write_bytes(os, fw_data, NULL, error); if (n < 0) return FALSE; nwritten += n; fu_progress_set_percentage_full(progress, nwritten, fw_size); } while (nwritten < fw_size); if (nwritten != fw_size) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "Could not write all data to nvmem"); return FALSE; } return g_output_stream_close(os, NULL, error); } static FuFirmware * fu_thunderbolt_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE(device); g_autoptr(FuFirmware) firmware = NULL; g_autoptr(FuFirmware) firmware_old = NULL; g_autoptr(GBytes) controller_fw = NULL; g_autoptr(GFile) nvmem = NULL; /* parse */ firmware = fu_firmware_new_from_gtypes(fw, 0x0, flags, error, FU_TYPE_INTEL_THUNDERBOLT_FIRMWARE, FU_TYPE_FIRMWARE, G_TYPE_INVALID); if (firmware == NULL) return NULL; /* get current NVMEM */ nvmem = fu_thunderbolt_device_find_nvmem(self, TRUE, error); if (nvmem == NULL) return NULL; controller_fw = g_file_load_bytes(nvmem, NULL, NULL, error); if (controller_fw == NULL) return NULL; firmware_old = fu_firmware_new_from_gtypes(controller_fw, 0x0, flags, error, FU_TYPE_INTEL_THUNDERBOLT_NVM, FU_TYPE_FIRMWARE, G_TYPE_INVALID); if (firmware_old == NULL) return NULL; if (!fu_firmware_check_compatible(firmware_old, firmware, flags, error)) return NULL; /* success */ return g_steal_pointer(&firmware); } static gboolean fu_thunderbolt_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuThunderboltDevice *self = FU_THUNDERBOLT_DEVICE(device); g_autoptr(GBytes) blob_fw = NULL; /* get default image */ blob_fw = fu_firmware_get_bytes(firmware, error); if (blob_fw == NULL) return FALSE; fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); if (!fu_thunderbolt_device_write_data(self, blob_fw, progress, error)) { g_prefix_error(error, "could not write firmware to thunderbolt device at %s: ", fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(self))); return FALSE; } /* flush the image if supported by kernel and/or device */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE)) { if (!fu_thunderbolt_device_flush_update(device, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); } /* using an active delayed activation flow later (either shutdown or another plugin) */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SKIPS_RESTART)) { g_debug("skipping Thunderbolt reset per quirk request"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); return TRUE; } /* authenticate (possibly on unplug if device supports it) */ if (!fu_thunderbolt_device_authenticate(FU_DEVICE(self), error)) { g_prefix_error(error, "could not start thunderbolt device upgrade: "); return FALSE; } /* whether to wait for a device replug or not */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE)) { fu_device_set_remove_delay(device, FU_PLUGIN_THUNDERBOLT_UPDATE_TIMEOUT); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_RESTART); } return TRUE; } static gboolean fu_thunderbolt_device_probe(FuDevice *device, GError **error) { g_autoptr(FuUdevDevice) udev_parent = NULL; /* if the PCI ID is Intel then it's signed, no idea otherwise */ udev_parent = fu_udev_device_get_parent_with_subsystem(FU_UDEV_DEVICE(device), "pci"); if (udev_parent != NULL) { if (!fu_device_probe(FU_DEVICE(udev_parent), error)) return FALSE; if (fu_udev_device_get_vendor(udev_parent) == 0x8086) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); } /* success */ return TRUE; } static void fu_thunderbolt_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_thunderbolt_device_init(FuThunderboltDevice *self) { FuThunderboltDevicePrivate *priv = GET_PRIVATE(self); priv->auth_method = "nvm_authenticate"; fu_device_add_icon(FU_DEVICE(self), "thunderbolt"); fu_device_add_protocol(FU_DEVICE(self), "com.intel.thunderbolt"); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_PROBE_COMPLETE); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); } static void fu_thunderbolt_device_class_init(FuThunderboltDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->activate = fu_thunderbolt_device_activate; klass_device->to_string = fu_thunderbolt_device_to_string; klass_device->probe = fu_thunderbolt_device_probe; klass_device->prepare_firmware = fu_thunderbolt_device_prepare_firmware; klass_device->write_firmware = fu_thunderbolt_device_write_firmware; klass_device->attach = fu_thunderbolt_device_attach; klass_device->rescan = fu_thunderbolt_device_rescan; klass_device->set_progress = fu_thunderbolt_device_set_progress; } fwupd-1.9.16/plugins/thunderbolt/fu-thunderbolt-device.h000066400000000000000000000015351460375044200233310ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_THUNDERBOLT_DEVICE (fu_thunderbolt_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuThunderboltDevice, fu_thunderbolt_device, FU, THUNDERBOLT_DEVICE, FuUdevDevice) struct _FuThunderboltDeviceClass { FuUdevDeviceClass parent_class; }; gboolean fu_thunderbolt_device_get_version(FuThunderboltDevice *self, GError **error); GFile * fu_thunderbolt_device_find_nvmem(FuThunderboltDevice *self, gboolean active, GError **error); gboolean fu_thunderbolt_device_check_authorized(FuThunderboltDevice *self, GError **error); void fu_thunderbolt_device_set_auth_method(FuThunderboltDevice *self, const gchar *auth_method); fwupd-1.9.16/plugins/thunderbolt/fu-thunderbolt-plugin.c000066400000000000000000000102671460375044200233650ustar00rootroot00000000000000/* * Copyright (C) 2017 Christian J. Kellner * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-thunderbolt-common.h" #include "fu-thunderbolt-controller.h" #include "fu-thunderbolt-plugin.h" #include "fu-thunderbolt-retimer.h" struct _FuThunderboltPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuThunderboltPlugin, fu_thunderbolt_plugin, FU_TYPE_PLUGIN) /* 5 seconds sleep until retimer is available after nvm update */ #define FU_THUNDERBOLT_RETIMER_CLEANUP_DELAY 5000000 static gboolean fu_thunderbolt_plugin_safe_kernel(FuPlugin *plugin, GError **error) { g_autofree gchar *min = fu_plugin_get_config_value(plugin, "MinimumKernelVersion"); return fu_kernel_check_version(min, error); } static gboolean fu_thunderbolt_plugin_device_created(FuPlugin *plugin, FuDevice *dev, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_INHIBITS_IDLE, "thunderbolt requires device wakeup"); if (fu_context_has_hwid_flag(ctx, "retimer-offline-mode")) fu_device_add_private_flag(dev, FU_THUNDERBOLT_DEVICE_FLAG_FORCE_ENUMERATION); return TRUE; } static void fu_thunderbolt_plugin_device_registered(FuPlugin *plugin, FuDevice *device) { if (g_strcmp0(fu_device_get_plugin(device), "thunderbolt") != 0) return; /* Operating system will handle finishing updates later */ if (fu_plugin_get_config_value_boolean(plugin, "DelayedActivation") && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE)) { g_info("turning on delayed activation for %s", fu_device_get_name(device)); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SKIPS_RESTART); fu_device_remove_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); } } static gboolean fu_thunderbolt_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { return fu_thunderbolt_plugin_safe_kernel(plugin, error); } static gboolean fu_thunderbolt_plugin_composite_prepare(FuPlugin *plugin, GPtrArray *devices, GError **error) { for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index(devices, i); if ((g_strcmp0(fu_device_get_plugin(dev), "thunderbolt") == 0) && fu_device_has_private_flag(dev, FU_THUNDERBOLT_DEVICE_FLAG_FORCE_ENUMERATION) && fu_device_has_internal_flag(dev, FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE)) { return fu_thunderbolt_retimer_set_parent_port_offline(dev, error); } } return TRUE; } static gboolean fu_thunderbolt_plugin_composite_cleanup(FuPlugin *plugin, GPtrArray *devices, GError **error) { for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index(devices, i); if ((g_strcmp0(fu_device_get_plugin(dev), "thunderbolt") == 0) && fu_device_has_private_flag(dev, FU_THUNDERBOLT_DEVICE_FLAG_FORCE_ENUMERATION) && fu_device_has_internal_flag(dev, FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE)) { g_usleep(FU_THUNDERBOLT_RETIMER_CLEANUP_DELAY); return fu_thunderbolt_retimer_set_parent_port_online(dev, error); } } return TRUE; } static void fu_thunderbolt_plugin_init(FuThunderboltPlugin *self) { } static void fu_thunderbolt_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_udev_subsystem(plugin, "thunderbolt"); fu_plugin_add_device_gtype(plugin, FU_TYPE_THUNDERBOLT_CONTROLLER); fu_plugin_add_device_gtype(plugin, FU_TYPE_THUNDERBOLT_RETIMER); /* defaults changed here will also be reflected in the fwupd.conf man page */ fu_plugin_set_config_default(plugin, "DelayedActivation", "false"); fu_plugin_set_config_default(plugin, "MinimumKernelVersion", "4.13.0"); } static void fu_thunderbolt_plugin_class_init(FuThunderboltPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_thunderbolt_plugin_constructed; plugin_class->startup = fu_thunderbolt_plugin_startup; plugin_class->device_registered = fu_thunderbolt_plugin_device_registered; plugin_class->device_created = fu_thunderbolt_plugin_device_created; plugin_class->composite_prepare = fu_thunderbolt_plugin_composite_prepare; plugin_class->composite_cleanup = fu_thunderbolt_plugin_composite_cleanup; } fwupd-1.9.16/plugins/thunderbolt/fu-thunderbolt-plugin.h000066400000000000000000000003671460375044200233720ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuThunderboltPlugin, fu_thunderbolt_plugin, FU, THUNDERBOLT_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/thunderbolt/fu-thunderbolt-retimer.c000066400000000000000000000107231460375044200235330ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2017 Christian J. Kellner * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-thunderbolt-common.h" #include "fu-thunderbolt-retimer.h" struct _FuThunderboltRetimer { FuThunderboltDevice parent_instance; }; G_DEFINE_TYPE(FuThunderboltRetimer, fu_thunderbolt_retimer, FU_TYPE_THUNDERBOLT_DEVICE) static FuUdevDevice * fu_thunderbolt_retimer_get_udev_grandparent(FuDevice *device, GError **error) { g_autoptr(GUdevDevice) udev_parent1 = NULL; g_autoptr(GUdevDevice) udev_parent2 = NULL; GUdevDevice *udev_device = NULL; FuThunderboltRetimer *self = FU_THUNDERBOLT_RETIMER(device); udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); if (udev_device == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get udev device for retimer"); return NULL; } udev_parent1 = g_udev_device_get_parent(udev_device); if (udev_parent1 == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get parent device for retimer"); return NULL; } udev_parent2 = g_udev_device_get_parent(udev_parent1); if (udev_parent2 == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get host router device for retimer"); return NULL; } return fu_udev_device_new(fu_device_get_context(FU_DEVICE(self)), g_steal_pointer(&udev_parent2)); } gboolean fu_thunderbolt_retimer_set_parent_port_offline(FuDevice *device, GError **error) { g_autoptr(FuUdevDevice) parent = fu_thunderbolt_retimer_get_udev_grandparent(device, error); if (parent == NULL) return FALSE; return fu_thunderbolt_udev_set_port_offline(parent, error); } gboolean fu_thunderbolt_retimer_set_parent_port_online(FuDevice *device, GError **error) { g_autoptr(FuUdevDevice) parent = fu_thunderbolt_retimer_get_udev_grandparent(device, error); if (parent == NULL) return FALSE; return fu_thunderbolt_udev_set_port_online(parent, error); } static gboolean fu_thunderbolt_retimer_probe(FuDevice *device, GError **error) { const gchar *devpath = fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device)); g_autofree gchar *physical_id = g_path_get_basename(devpath); /* device */ if (physical_id != NULL) fu_device_set_physical_id(device, physical_id); return TRUE; } static gboolean fu_thunderbolt_retimer_setup(FuDevice *device, GError **error) { FuThunderboltRetimer *self = FU_THUNDERBOLT_RETIMER(device); guint16 did; guint16 vid; g_autofree gchar *instance = NULL; /* get version */ if (!fu_thunderbolt_device_get_version(FU_THUNDERBOLT_DEVICE(self), error)) return FALSE; /* as defined in PCIe 4.0 spec */ vid = fu_udev_device_get_vendor(FU_UDEV_DEVICE(self)); if (vid == 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "missing vendor id"); return FALSE; } did = fu_udev_device_get_model(FU_UDEV_DEVICE(self)); if (did == 0x0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "missing device id"); return FALSE; } instance = g_strdup_printf("TBT-%04x%04x-retimer%s", (guint)vid, (guint)did, fu_device_get_physical_id(device)); fu_device_add_instance_id(device, instance); /* hardcoded for now: * 1. unsure if ID_VENDOR_FROM_DATABASE works in this instance * 2. we don't recognize anyone else yet */ if (fu_device_get_vendor(device) == NULL) fu_device_set_vendor(device, "Intel"); /* success */ return TRUE; } static void fu_thunderbolt_retimer_init(FuThunderboltRetimer *self) { fu_device_set_name(FU_DEVICE(self), "USB4 Retimer"); fu_device_set_summary( FU_DEVICE(self), "A physical layer protocol-aware, software-transparent extension device " "that forms two separate electrical link segments"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE); } static void fu_thunderbolt_retimer_class_init(FuThunderboltRetimerClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_thunderbolt_retimer_setup; klass_device->probe = fu_thunderbolt_retimer_probe; } fwupd-1.9.16/plugins/thunderbolt/fu-thunderbolt-retimer.h000066400000000000000000000012111460375044200235300ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2020 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-thunderbolt-device.h" #define FU_TYPE_THUNDERBOLT_RETIMER (fu_thunderbolt_retimer_get_type()) G_DECLARE_FINAL_TYPE(FuThunderboltRetimer, fu_thunderbolt_retimer, FU, THUNDERBOLT_RETIMER, FuThunderboltDevice) gboolean fu_thunderbolt_retimer_set_parent_port_offline(FuDevice *device, GError **error); gboolean fu_thunderbolt_retimer_set_parent_port_online(FuDevice *device, GError **error); fwupd-1.9.16/plugins/thunderbolt/meson.build000066400000000000000000000032621460375044200211220ustar00rootroot00000000000000if gudev.found() and (host_cpu == 'x86' or host_cpu == 'x86_64') cargs = ['-DG_LOG_DOMAIN="FuPluginThunderbolt"'] plugin_quirks += files('thunderbolt.quirk') plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtin_thunderbolt = static_library('fu_plugin_thunderbolt', sources: [ 'fu-thunderbolt-plugin.c', 'fu-thunderbolt-common.c', 'fu-thunderbolt-device.c', 'fu-thunderbolt-retimer.c', 'fu-thunderbolt-controller.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_thunderbolt # we use functions from 2.52 in the tests if get_option('tests') and run_sanitize_unsafe_tests and umockdev.found() and gio.version().version_compare('>= 2.52') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) env.set('FWUPD_LOCALSTATEDIR', '/tmp/fwupd-self-test/var') env.set('FWUPD_DATADIR_QUIRKS', meson.current_source_dir()) e = executable( 'thunderbolt-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: [ gudev, plugin_deps, umockdev, ], link_with: [ fwupd, fwupdplugin, plugin_builtin_thunderbolt, ], c_args: [ cargs, '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) if get_option('b_sanitize') == 'address' env.prepend('LD_PRELOAD', 'libasan.so.5', 'libumockdev-preload.so.0', separator: ' ') else env.prepend('LD_PRELOAD', 'libumockdev-preload.so.0') endif test('thunderbolt-self-test', e, env: env, timeout: 120) endif endif fwupd-1.9.16/plugins/thunderbolt/tests/000077500000000000000000000000001460375044200201175ustar00rootroot00000000000000fwupd-1.9.16/plugins/thunderbolt/tests/COPYING000066400000000000000000000027321460375044200211560ustar00rootroot00000000000000Copyright(c) 2017 Intel Corporation Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Intel Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. fwupd-1.9.16/plugins/thunderbolt/tests/colorhug.bin000077700000000000000000000000001460375044200313502../../../src/tests/colorhug/firmware.binustar00rootroot00000000000000fwupd-1.9.16/plugins/thunderbolt/tests/minimal-fw-controller.bin000066400000000000000000000004771460375044200250420ustar00rootroot00000000000000$40`fwupd-1.9.16/plugins/thunderbolt/tests/minimal-fw.bin000066400000000000000000000005001460375044200226440ustar00rootroot00000000000000$40`fwupd-1.9.16/plugins/thunderbolt/thunderbolt.quirk000066400000000000000000000005451460375044200223700ustar00rootroot00000000000000[THUNDERBOLT\TYPE_THUNDERBOLT_DEVICE] Plugin = thunderbolt GType = FuThunderboltController [THUNDERBOLT\TYPE_THUNDERBOLT_RETIMER] Plugin = thunderbolt GType = FuThunderboltRetimer # Google Redrix [​af1d04d1-fbe9-5197-973d-fdc9310a33bd] Flags = retimer-offline-mode # Google Rex [​​fe682559-28dc-58b3-bf61-aa8414d9f363] Flags = retimer-offline-mode fwupd-1.9.16/plugins/ti-tps6598x/000077500000000000000000000000001460375044200163675ustar00rootroot00000000000000fwupd-1.9.16/plugins/ti-tps6598x/README.md000066400000000000000000000022421460375044200176460ustar00rootroot00000000000000--- title: Plugin: TI TPS6598x --- ## Introduction The TPS65982DMC is a dock management controller for docks, hubs, and monitors implementing TI PD controllers. Suitable Power Delivery (PD) Controller devices include TPS6598x which are updated as part of the DMC firmware. There may be multiple PD devices attached to each DMC device. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob as a SHA-256+RSA-3072 signed binary file. This plugin supports the following protocol ID: * `com.ti.tps6598x` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_0451&PID_ACE1` (enforce-requires) Child devices also have an additional instance IDs which corresponds to the index, e.g. * `USB\VID_2188&PID_5988&REV_0714&PD_00` * `USB\VID_2188&PID_5988&PD_00` ## Update Behavior The device usually presents in runtime mode. ## Vendor ID Security The vendor ID is set from the USB vendor, in this instance set to `USB:0x0451` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.8.9`. fwupd-1.9.16/plugins/ti-tps6598x/fu-ti-tps6598x-common.c000066400000000000000000000006551460375044200224030ustar00rootroot00000000000000/* * Copyright (C) 2021 Texas Instruments Incorporated * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-ti-tps6598x-common.h" gboolean fu_ti_tps6598x_byte_array_is_nonzero(GByteArray *buf) { if (buf->len == 0) return FALSE; for (guint j = 1; j < buf->len; j++) { if (buf->data[j] != 0x0) return TRUE; } return FALSE; } fwupd-1.9.16/plugins/ti-tps6598x/fu-ti-tps6598x-common.h000066400000000000000000000030131460375044200223770ustar00rootroot00000000000000/* * Copyright (C) 2021 Texas Instruments Incorporated * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include /* registers */ #define TI_TPS6598X_REGISTER_TBT_VID 0x00 /* ro, 4 bytes -- Intel assigned */ #define TI_TPS6598X_REGISTER_TBT_DID 0x01 /* ro, 4 bytes -- Intel assigned */ #define TI_TPS6598X_REGISTER_PROTO_VER 0x02 /* ro, 4 bytes */ #define TI_TPS6598X_REGISTER_MODE 0x03 /* ro, 4 bytes */ #define TI_TPS6598X_REGISTER_TYPE 0x04 /* ro, 4 bytes */ #define TI_TPS6598X_REGISTER_UID 0x05 /* ro, 16 bytes */ #define TI_TPS6598X_REGISTER_OUID 0x06 /* ro, 8 bytes */ #define TI_TPS6598X_REGISTER_CMD1 0x08 /* ro, 4CC */ #define TI_TPS6598X_REGISTER_DATA1 0x09 /* rw, 64 bytes */ #define TI_TPS6598X_REGISTER_VERSION 0x0F /* rw, 4 bytes */ #define TI_TPS6598X_REGISTER_CMD2 0x10 /* ro, 4CC */ #define TI_TPS6598X_REGISTER_DATA2 0x11 /* rw, 64 bytes */ #define TI_TPS6598X_REGISTER_CMD3 0x1E /* ro, variable */ #define TI_TPS6598X_REGISTER_DATA3 0x1F /* ro, variable */ #define TI_TPS6598X_REGISTER_OTP_CONFIG 0x2D /* ro, 12 bytes */ #define TI_TPS6598X_REGISTER_BUILD_IDENTIFIER 0x2E /* ro, 64 bytes */ #define TI_TPS6598X_REGISTER_DEVICE_INFO 0x2F /* ro, 47 bytes */ #define TI_TPS6598X_REGISTER_TX_IDENTITY 0x47 /* rw, 49 bytes */ #define FU_TI_TPS6598X_PD_MAX 2 /* devices */ gboolean fu_ti_tps6598x_byte_array_is_nonzero(GByteArray *buf); fwupd-1.9.16/plugins/ti-tps6598x/fu-ti-tps6598x-device.c000066400000000000000000000546771460375044200223670ustar00rootroot00000000000000/* * Copyright (C) 2021 Texas Instruments Incorporated * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-ti-tps6598x-common.h" #include "fu-ti-tps6598x-device.h" #include "fu-ti-tps6598x-firmware.h" #include "fu-ti-tps6598x-pd-device.h" #include "fu-ti-tps6598x-struct.h" struct _FuTiTps6598xDevice { FuUsbDevice parent_instance; gchar *uid; gchar *ouid; }; G_DEFINE_TYPE(FuTiTps6598xDevice, fu_ti_tps6598x_device, FU_TYPE_USB_DEVICE) #define TI_TPS6598X_DEVICE_USB_TIMEOUT 2000 /* ms */ /* command types in USB messages from PC to device */ #define TI_TPS6598X_USB_REQUEST_WRITE 0xFD #define TI_TPS6598X_USB_REQUEST_READ 0xFE #define TI_TPS6598X_USB_BUFFER_SIZE 8 /* bytes */ static void fu_ti_tps6598x_device_to_string(FuDevice *device, guint idt, GString *str) { FuTiTps6598xDevice *self = FU_TI_TPS6598X_DEVICE(device); fu_string_append(str, idt, "UID", self->uid); fu_string_append(str, idt, "oUID", self->ouid); } /* read @length bytes from address @addr */ static GByteArray * fu_ti_tps6598x_device_usbep_read_raw(FuTiTps6598xDevice *self, guint16 addr, guint8 length, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize actual_length = 0; g_autofree gchar *title = g_strdup_printf("read@0x%x", addr); g_autoptr(GByteArray) buf = g_byte_array_new(); /* first byte is length */ fu_byte_array_set_size(buf, length + 1, 0x0); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, TI_TPS6598X_USB_REQUEST_READ, addr, 0x0, /* idx */ buf->data, buf->len, &actual_length, TI_TPS6598X_DEVICE_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to contact device: "); return NULL; } fu_dump_raw(G_LOG_DOMAIN, title, buf->data, buf->len); if (actual_length != buf->len) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "got 0x%x but requested 0x%x", (guint)actual_length, (guint)buf->len); return NULL; } /* success */ return g_steal_pointer(&buf); } /* read @length bytes from address @addr */ static GByteArray * fu_ti_tps6598x_device_usbep_read(FuTiTps6598xDevice *self, guint16 addr, guint8 length, GError **error) { g_autoptr(GByteArray) buf = NULL; /* first byte is length */ buf = fu_ti_tps6598x_device_usbep_read_raw(self, addr, length, error); if (buf == NULL) return NULL; /* check then remove size */ if (buf->data[0] < length) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "response 0x%x but requested 0x%x", (guint)buf->data[0], (guint)length); return NULL; } g_byte_array_remove_index(buf, 0); /* success */ return g_steal_pointer(&buf); } static gboolean fu_ti_tps6598x_device_usbep_write(FuTiTps6598xDevice *self, guint16 addr, GByteArray *buf, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); g_autoptr(GPtrArray) chunks = NULL; g_autofree gchar *title = g_strdup_printf("write@0x%x", addr); fu_dump_raw(G_LOG_DOMAIN, title, buf->data, buf->len); chunks = fu_chunk_array_mutable_new(buf->data, buf->len, 0x0, 0x0, TI_TPS6598X_USB_BUFFER_SIZE); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint16 idx = 0; gsize actual_length = 0; /* for the first chunk use the total data length */ if (i == 0) idx = buf->len; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, TI_TPS6598X_USB_REQUEST_WRITE, addr, idx, /* idx */ fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), &actual_length, TI_TPS6598X_DEVICE_USB_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to contact device: "); return FALSE; } if (actual_length != fu_chunk_get_data_sz(chk)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "wrote 0x%x but expected 0x%x", (guint)actual_length, (guint)fu_chunk_get_data_sz(chk)); return FALSE; } } /* success */ return TRUE; } /* read the specified DATA register */ static GByteArray * fu_ti_tps6598x_device_read_data(FuTiTps6598xDevice *self, gsize bufsz, GError **error) { g_autoptr(GByteArray) buf = fu_ti_tps6598x_device_usbep_read(self, TI_TPS6598X_REGISTER_DATA3, bufsz, error); if (buf == NULL) { g_prefix_error(error, "failed to read data at 0x%x: ", (guint)TI_TPS6598X_REGISTER_DATA3); return NULL; } return g_steal_pointer(&buf); } /* write to the DATA register */ static gboolean fu_ti_tps6598x_device_write_data(FuTiTps6598xDevice *self, GByteArray *buf, GError **error) { if (!fu_ti_tps6598x_device_usbep_write(self, TI_TPS6598X_REGISTER_DATA3, buf, error)) { g_prefix_error(error, "failed to write data at 0x%x: ", (guint)TI_TPS6598X_REGISTER_DATA3); return FALSE; } return TRUE; } static gboolean fu_ti_tps6598x_device_write_4cc(FuTiTps6598xDevice *self, const gchar *cmd, GByteArray *data, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); /* sanity check */ if (strlen(cmd) != 4) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "expected 4-char cmd"); return FALSE; } if (data != NULL) { if (!fu_ti_tps6598x_device_write_data(self, data, error)) return FALSE; } for (guint i = 0; i < 4; i++) fu_byte_array_append_uint8(buf, cmd[i]); return fu_ti_tps6598x_device_usbep_write(self, TI_TPS6598X_REGISTER_CMD3, buf, error); } static gboolean fu_ti_tps6598x_device_reset_hard(FuTiTps6598xDevice *self, GError **error) { return fu_ti_tps6598x_device_write_4cc(self, "GAID", NULL, error); } static gboolean fu_ti_tps6598x_device_wait_for_command_cb(FuDevice *device, gpointer user_data, GError **error) { FuTiTps6598xDevice *self = FU_TI_TPS6598X_DEVICE(device); g_autoptr(GByteArray) buf = NULL; /* 4 bytes of data and the first byte is length */ buf = fu_ti_tps6598x_device_usbep_read(self, TI_TPS6598X_REGISTER_CMD3, 4, error); if (buf == NULL) return FALSE; /* check the value of the cmd register */ if (buf->data[0] != 0 || buf->data[1] != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "invalid status register, got 0x%02x:0x%02x", buf->data[1], buf->data[2]); return FALSE; } /* success */ return TRUE; } /* wait for a 4CC command to complete, defaults for count is 15ms, delay 100ms */ static gboolean fu_ti_tps6598x_device_wait_for_command(FuTiTps6598xDevice *self, guint count, guint delay, GError **error) { return fu_device_retry_full(FU_DEVICE(self), fu_ti_tps6598x_device_wait_for_command_cb, count, delay, NULL, error); } static gboolean fu_ti_tps6598x_device_target_reboot(FuTiTps6598xDevice *self, guint8 slaveNum, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); fu_byte_array_append_uint8(buf, slaveNum); fu_byte_array_append_uint8(buf, 0); if (!fu_ti_tps6598x_device_write_4cc(self, "DSRT", buf, error)) return FALSE; return fu_ti_tps6598x_device_wait_for_command(self, 15, 100, error); } static gboolean fu_ti_tps6598x_device_maybe_reboot(FuTiTps6598xDevice *self, GError **error) { /* reset the targets first */ if (!fu_ti_tps6598x_device_target_reboot(self, 0, error)) return FALSE; if (!fu_ti_tps6598x_device_target_reboot(self, 1, error)) return FALSE; return fu_ti_tps6598x_device_reset_hard(self, error); } /* prepare device to receive the upcoming data transactions */ static gboolean fu_ti_tps6598x_device_sfwi(FuTiTps6598xDevice *self, GError **error) { guint8 res; g_autoptr(GByteArray) buf = NULL; if (!fu_ti_tps6598x_device_write_4cc(self, "SFWi", NULL, error)) return FALSE; if (!fu_ti_tps6598x_device_wait_for_command(self, 15, 100, error)) return FALSE; buf = fu_ti_tps6598x_device_read_data(self, 6, error); if (buf == NULL) return FALSE; res = buf->data[0] & 0b1111; if (res != FU_TI_TPS6598X_SFWI_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "SFWi failed, got %s [0x%02x]", fu_ti_tps6598x_sfwi_to_string(res), res); return FALSE; } /* success */ g_debug("prod-key-present: %u", (guint)(buf->data[2] & 0b00010) >> 1); g_debug("engr-key-present: %u", (guint)(buf->data[2] & 0b00100) >> 2); g_debug("new-flash-region: %u", (guint)(buf->data[2] & 0b11000) >> 3); return TRUE; } /* provide device with the next 64 bytes to be flashed into the SPI */ static gboolean fu_ti_tps6598x_device_sfwd(FuTiTps6598xDevice *self, GByteArray *data, GError **error) { guint8 res; g_autoptr(GByteArray) buf = NULL; if (!fu_ti_tps6598x_device_write_4cc(self, "SFWd", data, error)) return FALSE; if (!fu_ti_tps6598x_device_wait_for_command(self, 15, 100, error)) return FALSE; buf = fu_ti_tps6598x_device_read_data(self, 1, error); if (buf == NULL) return FALSE; res = buf->data[0] & 0b1111; if (res != FU_TI_TPS6598X_SFWD_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "SFWd failed, got %s [0x%02x]", fu_ti_tps6598x_sfwd_to_string(res), res); return FALSE; } /* success */ g_debug("more-data-expected: %i", (buf->data[0] & 0x80) > 0); return TRUE; } /* pass the image signature information to device for verification of the data */ static gboolean fu_ti_tps6598x_device_sfws(FuTiTps6598xDevice *self, GByteArray *data, GError **error) { guint8 res; g_autoptr(GByteArray) buf = NULL; if (!fu_ti_tps6598x_device_write_4cc(self, "SFWs", data, error)) return FALSE; if (!fu_ti_tps6598x_device_wait_for_command(self, 300, 1000, error)) return FALSE; buf = fu_ti_tps6598x_device_read_data(self, 10, error); if (buf == NULL) return FALSE; res = buf->data[0] & 0b1111; if (res != FU_TI_TPS6598X_SFWS_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "SFWs failed, got %s [0x%02x]", fu_ti_tps6598x_sfws_to_string(res), res); return FALSE; } /* success */ g_debug("more-data-expected: %i", (buf->data[0] & 0x80) > 0); g_debug("signature-data-block: %u", (guint)buf->data[1]); g_debug("prod-key-present: %u", (guint)(buf->data[2] & 0b00010) >> 1); g_debug("engr-key-present: %u", (guint)(buf->data[2] & 0b00100) >> 2); g_debug("new-flash-region: %u", (guint)(buf->data[2] & 0b11000) >> 3); g_debug("hash-match: %u", (guint)(buf->data[2] & 0b1100000) >> 5); return TRUE; } GByteArray * fu_ti_tps6598x_device_read_target_register(FuTiTps6598xDevice *self, guint8 target, guint8 addr, guint8 length, GError **error) { g_autoptr(GByteArray) buf = NULL; g_autoptr(GByteArray) data = g_byte_array_new(); fu_byte_array_append_uint8(data, target); fu_byte_array_append_uint8(data, addr); fu_byte_array_append_uint8(data, length); if (!fu_ti_tps6598x_device_write_4cc(self, "DSRD", data, error)) return NULL; if (!fu_ti_tps6598x_device_wait_for_command(self, 300, 1000, error)) return NULL; buf = fu_ti_tps6598x_device_read_data(self, length + 1, error); if (buf == NULL) return NULL; /* check then remove response code */ if (buf->data[0] != 0x00) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "response code 0x%02x", (guint)buf->data[0]); return NULL; } g_byte_array_remove_index(buf, 0); /* success */ return g_steal_pointer(&buf); } static gboolean fu_ti_tps6598x_device_ensure_version(FuTiTps6598xDevice *self, GError **error) { g_autofree gchar *str = NULL; g_autoptr(GByteArray) buf = NULL; /* get bcdVersion */ buf = fu_ti_tps6598x_device_usbep_read(self, TI_TPS6598X_REGISTER_VERSION, 4, error); if (buf == NULL) return FALSE; str = g_strdup_printf("%X.%X.%X", buf->data[2], buf->data[1], buf->data[0]); fu_device_set_version(FU_DEVICE(self), str); return TRUE; } static gboolean fu_ti_tps6598x_device_ensure_mode(FuTiTps6598xDevice *self, GError **error) { g_autofree gchar *str = NULL; g_autoptr(GByteArray) buf = NULL; buf = fu_ti_tps6598x_device_usbep_read(self, TI_TPS6598X_REGISTER_MODE, 4, error); if (buf == NULL) return FALSE; /* ensure in recognized mode */ str = fu_memstrsafe(buf->data, buf->len, 0x0, 4, error); if (str == NULL) return FALSE; if (g_strcmp0(str, "APP ") == 0) { fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } if (g_strcmp0(str, "BOOT") == 0) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } /* unhandled */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "device in unknown mode: %s", str); return FALSE; } static gboolean fu_ti_tps6598x_device_ensure_uid(FuTiTps6598xDevice *self, GError **error) { g_autoptr(GByteArray) buf = fu_ti_tps6598x_device_usbep_read(self, TI_TPS6598X_REGISTER_UID, 16, error); if (buf == NULL) return FALSE; g_free(self->uid); self->uid = fu_byte_array_to_string(buf); return TRUE; } static gboolean fu_ti_tps6598x_device_ensure_ouid(FuTiTps6598xDevice *self, GError **error) { g_autoptr(GByteArray) buf = fu_ti_tps6598x_device_usbep_read(self, TI_TPS6598X_REGISTER_OUID, 8, error); if (buf == NULL) return FALSE; g_free(self->ouid); self->ouid = fu_byte_array_to_string(buf); return TRUE; } static gboolean fu_ti_tps6598x_device_setup(FuDevice *device, GError **error) { FuTiTps6598xDevice *self = FU_TI_TPS6598X_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); /* there are two devices with the same VID:PID -- ignore the non-vendor one */ if (g_usb_device_get_device_class(usb_device) != G_USB_DEVICE_CLASS_VENDOR_SPECIFIC) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "non-vendor specific interface ignored"); return FALSE; } /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_ti_tps6598x_device_parent_class)->setup(device, error)) return FALSE; /* get hardware details */ if (!fu_ti_tps6598x_device_ensure_version(self, error)) { g_prefix_error(error, "failed to read version: "); return FALSE; } if (!fu_ti_tps6598x_device_ensure_mode(self, error)) { g_prefix_error(error, "failed to read mode: "); return FALSE; } if (!fu_ti_tps6598x_device_ensure_uid(self, error)) { g_prefix_error(error, "failed to read UID: "); return FALSE; } if (!fu_ti_tps6598x_device_ensure_ouid(self, error)) { g_prefix_error(error, "failed to read oUID: "); return FALSE; } /* create child PD devices */ for (guint i = 0; i < FU_TI_TPS6598X_PD_MAX; i++) { g_autoptr(FuDevice) device_pd = fu_ti_tps6598x_pd_device_new(device, i); fu_device_add_child(device, device_pd); } /* success */ return TRUE; } static void fu_ti_tps6598x_device_report_metadata_pre(FuDevice *device, GHashTable *metadata) { FuTiTps6598xDevice *self = FU_TI_TPS6598X_DEVICE(device); for (guint i = 0; i < 0xFF; i++) { g_autoptr(GByteArray) buf = NULL; g_autoptr(GError) error_local = NULL; buf = fu_ti_tps6598x_device_usbep_read_raw(self, i, 62, &error_local); if (buf == NULL) { g_debug("failed to get DMC register 0x%02x: %s", i, error_local->message); continue; } if (!fu_ti_tps6598x_byte_array_is_nonzero(buf)) continue; g_hash_table_insert(metadata, g_strdup_printf("Tps6598xDmcRegister@0x%02x", i), fu_byte_array_to_string(buf)); } } static gboolean fu_ti_tps6598x_device_write_chunks(FuTiTps6598xDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GByteArray) buf = g_byte_array_new(); /* align */ g_byte_array_append(buf, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); fu_byte_array_align_up(buf, FU_FIRMWARE_ALIGNMENT_64, 0xFF); if (!fu_ti_tps6598x_device_sfwd(self, buf, error)) { g_prefix_error(error, "failed to write chunk %u: ", i); return FALSE; } /* update progress */ fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_ti_tps6598x_device_write_sfws_chunks(FuTiTps6598xDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GByteArray) buf = g_byte_array_new(); /* align and pad low before sending */ g_byte_array_append(buf, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); fu_byte_array_align_up(buf, FU_FIRMWARE_ALIGNMENT_64, 0x0); if (!fu_ti_tps6598x_device_sfws(self, buf, error)) { g_prefix_error(error, "failed to write chunk %u: ", i); return FALSE; } /* update progress */ fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_ti_tps6598x_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuTiTps6598xDevice *self = FU_TI_TPS6598X_DEVICE(device); g_autoptr(GError) error_local = NULL; /* hopefully this fails because the hardware rebooted */ if (!fu_ti_tps6598x_device_maybe_reboot(self, &error_local)) { if (!g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE)) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } g_debug("ignoring expected failure: %s", error_local->message); } /* success! */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } gboolean fu_ti_tps6598x_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuTiTps6598xDevice *self = FU_TI_TPS6598X_DEVICE(device); g_autoptr(GBytes) fw_sig = NULL; g_autoptr(GBytes) fw_pubkey = NULL; g_autoptr(GBytes) fw_payload = NULL; g_autoptr(FuChunkArray) chunks_pubkey = NULL; g_autoptr(FuChunkArray) chunks_sig = NULL; g_autoptr(FuChunkArray) chunks_payload = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 91, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 7, NULL); /* get payload image */ fw_payload = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_PAYLOAD, error); if (fw_payload == NULL) return FALSE; /* SFWi */ if (!fu_ti_tps6598x_device_sfwi(self, error)) return FALSE; fu_progress_step_done(progress); /* write each SFWd block */ chunks_payload = fu_chunk_array_new_from_bytes(fw_payload, 0x0, 64); if (!fu_ti_tps6598x_device_write_chunks(self, chunks_payload, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write SFWd: "); return FALSE; } fu_progress_step_done(progress); /* SFWs with signature */ fw_sig = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_SIGNATURE, error); if (fw_sig == NULL) return FALSE; chunks_sig = fu_chunk_array_new_from_bytes(fw_sig, 0x0, 64); if (!fu_ti_tps6598x_device_write_sfws_chunks(self, chunks_sig, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write SFWs with signature: "); return FALSE; } fu_progress_step_done(progress); /* SFWs with pubkey */ fw_pubkey = fu_firmware_get_image_by_id_bytes(firmware, "pubkey", error); if (fw_pubkey == NULL) return FALSE; chunks_pubkey = fu_chunk_array_new_from_bytes(fw_pubkey, 0x0, 64); if (!fu_ti_tps6598x_device_write_sfws_chunks(self, chunks_pubkey, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write SFWs with pubkey: "); return FALSE; } fu_progress_step_done(progress); /* success! */ return TRUE; } static void fu_ti_tps6598x_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 91, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 9, "reload"); } static void fu_ti_tps6598x_device_init(FuTiTps6598xDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.ti.tps6598x"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ENFORCE_REQUIRES); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_TI_TPS6598X_FIRMWARE); fu_device_set_remove_delay(FU_DEVICE(self), 30000); fu_usb_device_add_interface(FU_USB_DEVICE(self), 0x0); } static void fu_ti_tps6598x_device_finalize(GObject *object) { FuTiTps6598xDevice *self = FU_TI_TPS6598X_DEVICE(object); g_free(self->uid); g_free(self->ouid); G_OBJECT_CLASS(fu_ti_tps6598x_device_parent_class)->finalize(object); } static void fu_ti_tps6598x_device_class_init(FuTiTps6598xDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_ti_tps6598x_device_finalize; klass_device->to_string = fu_ti_tps6598x_device_to_string; klass_device->write_firmware = fu_ti_tps6598x_device_write_firmware; klass_device->attach = fu_ti_tps6598x_device_attach; klass_device->setup = fu_ti_tps6598x_device_setup; klass_device->report_metadata_pre = fu_ti_tps6598x_device_report_metadata_pre; klass_device->set_progress = fu_ti_tps6598x_device_set_progress; } fwupd-1.9.16/plugins/ti-tps6598x/fu-ti-tps6598x-device.h000066400000000000000000000013441460375044200223530ustar00rootroot00000000000000/* * Copyright (C) 2021 Texas Instruments Incorporated * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_TI_TPS6598X_DEVICE (fu_ti_tps6598x_device_get_type()) G_DECLARE_FINAL_TYPE(FuTiTps6598xDevice, fu_ti_tps6598x_device, FU, TI_TPS6598X_DEVICE, FuUsbDevice) GByteArray * fu_ti_tps6598x_device_read_target_register(FuTiTps6598xDevice *self, guint8 target, guint8 addr, guint8 length, GError **error); gboolean fu_ti_tps6598x_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error); fwupd-1.9.16/plugins/ti-tps6598x/fu-ti-tps6598x-firmware.c000066400000000000000000000106031460375044200227210ustar00rootroot00000000000000/* * Copyright (C) 2021 Texas Instruments Incorporated * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-ti-tps6598x-firmware.h" struct _FuTiTps6598xFirmware { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuTiTps6598xFirmware, fu_ti_tps6598x_firmware, FU_TYPE_FIRMWARE) #define FU_TI_TPS6598X_FIRMWARE_BINARY_ID 0xACEF0001 #define FU_TI_TPS6598X_FIRMWARE_PUBKEY_SIZE 0x180 /* bytes */ static gboolean fu_ti_tps6598x_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { guint32 magic = 0; if (!fu_memread_uint32_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset, &magic, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read magic: "); return FALSE; } if (magic != FU_TI_TPS6598X_FIRMWARE_BINARY_ID) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid magic, expected 0x%04X got 0x%04X", (guint32)FU_TI_TPS6598X_FIRMWARE_BINARY_ID, magic); return FALSE; } /* success */ return TRUE; } static gboolean fu_ti_tps6598x_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { guint8 verbuf[3] = {0x0}; g_autofree gchar *version_str = NULL; g_autoptr(FuFirmware) img_payload = fu_firmware_new(); g_autoptr(FuFirmware) img_pubkey = fu_firmware_new(); g_autoptr(FuFirmware) img_sig = fu_firmware_new(); g_autoptr(GBytes) blob_payload = NULL; g_autoptr(GBytes) blob_pubkey = NULL; g_autoptr(GBytes) blob_sig = NULL; /* skip magic */ offset += 0x4; /* pubkey */ blob_pubkey = fu_bytes_new_offset(fw, offset, FU_TI_TPS6598X_FIRMWARE_PUBKEY_SIZE, error); if (blob_pubkey == NULL) return FALSE; fu_firmware_set_bytes(img_pubkey, blob_pubkey); fu_firmware_set_id(img_pubkey, "pubkey"); fu_firmware_add_image(firmware, img_pubkey); offset += g_bytes_get_size(blob_pubkey); /* RSA signature */ blob_sig = fu_bytes_new_offset(fw, offset, FU_TI_TPS6598X_FIRMWARE_PUBKEY_SIZE, error); if (blob_sig == NULL) return FALSE; fu_firmware_set_bytes(img_sig, blob_sig); fu_firmware_set_id(img_sig, FU_FIRMWARE_ID_SIGNATURE); fu_firmware_add_image(firmware, img_sig); offset += g_bytes_get_size(blob_sig); /* payload */ blob_payload = fu_bytes_new_offset(fw, offset, g_bytes_get_size(fw) - offset, error); if (blob_payload == NULL) return FALSE; if (!fu_memcpy_safe(verbuf, sizeof(verbuf), 0x0, g_bytes_get_data(blob_payload, NULL), g_bytes_get_size(blob_payload), 0x34, sizeof(verbuf), error)) return FALSE; version_str = g_strdup_printf("%X.%X.%X", verbuf[2], verbuf[1], verbuf[0]); fu_firmware_set_version(img_payload, version_str); fu_firmware_set_bytes(img_payload, blob_payload); fu_firmware_set_id(img_payload, FU_FIRMWARE_ID_PAYLOAD); fu_firmware_add_image(firmware, img_payload); /* success */ return TRUE; } static GByteArray * fu_ti_tps6598x_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) blob_payload = NULL; g_autoptr(GBytes) blob_pubkey = NULL; g_autoptr(GBytes) blob_sig = NULL; /* magic */ fu_byte_array_append_uint32(buf, FU_TI_TPS6598X_FIRMWARE_BINARY_ID, G_LITTLE_ENDIAN); /* pubkey */ blob_pubkey = fu_firmware_get_image_by_id_bytes(firmware, "pubkey", error); if (blob_pubkey == NULL) return NULL; fu_byte_array_append_bytes(buf, blob_pubkey); /* sig */ blob_sig = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_SIGNATURE, error); if (blob_sig == NULL) return NULL; fu_byte_array_append_bytes(buf, blob_sig); /* payload */ blob_payload = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_PAYLOAD, error); if (blob_payload == NULL) return NULL; fu_byte_array_append_bytes(buf, blob_payload); /* add EOF */ return g_steal_pointer(&buf); } static void fu_ti_tps6598x_firmware_init(FuTiTps6598xFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_DEDUPE_ID); } static void fu_ti_tps6598x_firmware_class_init(FuTiTps6598xFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_ti_tps6598x_firmware_check_magic; klass_firmware->parse = fu_ti_tps6598x_firmware_parse; klass_firmware->write = fu_ti_tps6598x_firmware_write; } fwupd-1.9.16/plugins/ti-tps6598x/fu-ti-tps6598x-firmware.h000066400000000000000000000006401460375044200227260ustar00rootroot00000000000000/* * Copyright (C) 2021 Texas Instruments Incorporated * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_TI_TPS6598X_FIRMWARE (fu_ti_tps6598x_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuTiTps6598xFirmware, fu_ti_tps6598x_firmware, FU, TI_TPS6598X_FIRMWARE, FuFirmware) fwupd-1.9.16/plugins/ti-tps6598x/fu-ti-tps6598x-pd-device.c000066400000000000000000000153361460375044200227550ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-ti-tps6598x-common.h" #include "fu-ti-tps6598x-device.h" #include "fu-ti-tps6598x-firmware.h" #include "fu-ti-tps6598x-pd-device.h" struct _FuTiTps6598xPdDevice { FuDevice parent_instance; guint8 target; }; G_DEFINE_TYPE(FuTiTps6598xPdDevice, fu_ti_tps6598x_pd_device, FU_TYPE_DEVICE) static gboolean fu_ti_tps6598x_pd_device_probe(FuDevice *device, GError **error) { FuTiTps6598xPdDevice *self = FU_TI_TPS6598X_PD_DEVICE(device); g_autofree gchar *name = g_strdup_printf("TPS6598X PD#%u", self->target); g_autofree gchar *logical_id = g_strdup_printf("PD%u", self->target); fu_device_set_name(device, name); fu_device_set_logical_id(device, logical_id); fu_device_add_instance_u8(device, "PD", self->target); return TRUE; } static gboolean fu_ti_tps6598x_pd_device_ensure_version(FuTiTps6598xPdDevice *self, GError **error) { FuTiTps6598xDevice *proxy = FU_TI_TPS6598X_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); g_autoptr(GByteArray) buf = NULL; g_autofree gchar *str = NULL; buf = fu_ti_tps6598x_device_read_target_register(proxy, self->target, TI_TPS6598X_REGISTER_VERSION, 4, error); if (buf == NULL) return FALSE; str = g_strdup_printf("%02X%02X.%02X.%02X", buf->data[3], buf->data[2], buf->data[1], buf->data[0]); fu_device_set_version(FU_DEVICE(self), str); return TRUE; } static gboolean fu_ti_tps6598x_pd_device_ensure_tx_identity(FuTiTps6598xPdDevice *self, GError **error) { FuTiTps6598xDevice *proxy = FU_TI_TPS6598X_DEVICE(fu_device_get_proxy(FU_DEVICE(self))); guint16 val = 0; g_autoptr(GByteArray) buf = NULL; buf = fu_ti_tps6598x_device_read_target_register(proxy, self->target, TI_TPS6598X_REGISTER_TX_IDENTITY, 47, error); if (buf == NULL) return FALSE; if (!fu_memread_uint16_safe(buf->data, buf->len, 0x01, &val, G_LITTLE_ENDIAN, error)) return FALSE; if (val != 0x0 && val != 0xFF) fu_device_add_instance_u16(FU_DEVICE(self), "VID", val); if (!fu_memread_uint16_safe(buf->data, buf->len, 0x0B, &val, G_LITTLE_ENDIAN, error)) return FALSE; if (val != 0x0 && val != 0xFF) fu_device_add_instance_u16(FU_DEVICE(self), "PID", val); if (!fu_memread_uint16_safe(buf->data, buf->len, 0x09, &val, G_LITTLE_ENDIAN, error)) return FALSE; if (val != 0x0 && val != 0xFF) fu_device_add_instance_u16(FU_DEVICE(self), "REV", val); /* success */ return TRUE; } static gboolean fu_ti_tps6598x_pd_device_setup(FuDevice *device, GError **error) { FuTiTps6598xPdDevice *self = FU_TI_TPS6598X_PD_DEVICE(device); /* register reads are slow, so do as few as possible */ if (!fu_ti_tps6598x_pd_device_ensure_version(self, error)) return FALSE; if (!fu_ti_tps6598x_pd_device_ensure_tx_identity(self, error)) return FALSE; /* add new instance IDs */ if (!fu_device_build_instance_id(FU_DEVICE(self), error, "USB", "VID", "PID", "PD", NULL)) return FALSE; return fu_device_build_instance_id(FU_DEVICE(self), error, "USB", "VID", "PID", "REV", "PD", NULL); } static void fu_ti_tps6598x_pd_device_report_metadata_pre(FuDevice *device, GHashTable *metadata) { FuTiTps6598xPdDevice *self = FU_TI_TPS6598X_PD_DEVICE(device); FuTiTps6598xDevice *proxy = FU_TI_TPS6598X_DEVICE(fu_device_get_proxy(device)); /* this is too slow to do for each update... */ if (g_getenv("FWUPD_TI_TPS6598X_VERBOSE") == NULL) return; for (guint i = 0; i < 0x80; i++) { g_autoptr(GByteArray) buf = NULL; g_autoptr(GError) error_local = NULL; buf = fu_ti_tps6598x_device_read_target_register(proxy, self->target, i, 63, &error_local); if (buf == NULL) { g_debug("failed to get target 0x%02x register 0x%02x: %s", self->target, i, error_local->message); continue; } if (!fu_ti_tps6598x_byte_array_is_nonzero(buf)) continue; g_hash_table_insert( metadata, g_strdup_printf("Tps6598xPd%02xRegister@0x%02x", self->target, i), fu_byte_array_to_string(buf)); } } static gboolean fu_ti_tps6598x_pd_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuTiTps6598xDevice *proxy = FU_TI_TPS6598X_DEVICE(fu_device_get_proxy(device)); return fu_device_attach_full(FU_DEVICE(proxy), progress, error); } static gboolean fu_ti_tps6598x_pd_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuTiTps6598xDevice *proxy = FU_TI_TPS6598X_DEVICE(fu_device_get_proxy(device)); return fu_ti_tps6598x_device_write_firmware(FU_DEVICE(proxy), firmware, progress, flags, error); } static void fu_ti_tps6598x_pd_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 91, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 9, "reload"); } static void fu_ti_tps6598x_pd_device_init(FuTiTps6598xPdDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.ti.tps6598x"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_TI_TPS6598X_FIRMWARE); fu_device_set_remove_delay(FU_DEVICE(self), 30000); } static void fu_ti_tps6598x_pd_device_class_init(FuTiTps6598xPdDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_ti_tps6598x_pd_device_write_firmware; klass_device->attach = fu_ti_tps6598x_pd_device_attach; klass_device->setup = fu_ti_tps6598x_pd_device_setup; klass_device->probe = fu_ti_tps6598x_pd_device_probe; klass_device->report_metadata_pre = fu_ti_tps6598x_pd_device_report_metadata_pre; klass_device->set_progress = fu_ti_tps6598x_pd_device_set_progress; } FuDevice * fu_ti_tps6598x_pd_device_new(FuDevice *proxy, guint8 target) { FuTiTps6598xPdDevice *self = g_object_new(FU_TYPE_TI_TPS6598X_PD_DEVICE, "context", fu_device_get_context(proxy), "proxy", proxy, NULL); self->target = target; return FU_DEVICE(self); } fwupd-1.9.16/plugins/ti-tps6598x/fu-ti-tps6598x-pd-device.h000066400000000000000000000006671460375044200227630ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include #define FU_TYPE_TI_TPS6598X_PD_DEVICE (fu_ti_tps6598x_pd_device_get_type()) G_DECLARE_FINAL_TYPE(FuTiTps6598xPdDevice, fu_ti_tps6598x_pd_device, FU, TI_TPS6598X_PD_DEVICE, FuDevice) FuDevice * fu_ti_tps6598x_pd_device_new(FuDevice *proxy, guint8 target); fwupd-1.9.16/plugins/ti-tps6598x/fu-ti-tps6598x-plugin.c000066400000000000000000000016671460375044200224150ustar00rootroot00000000000000/* * Copyright (C) 2021 Texas Instruments Incorporated * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #include "config.h" #include "fu-ti-tps6598x-device.h" #include "fu-ti-tps6598x-firmware.h" #include "fu-ti-tps6598x-plugin.h" struct _FuTiTps6598xPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuTiTps6598xPlugin, fu_ti_tps6598x_plugin, FU_TYPE_PLUGIN) static void fu_ti_tps6598x_plugin_init(FuTiTps6598xPlugin *self) { } static void fu_ti_tps6598x_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_TI_TPS6598X_DEVICE); fu_plugin_add_firmware_gtype(plugin, "ti-tps6598x", FU_TYPE_TI_TPS6598X_FIRMWARE); } static void fu_ti_tps6598x_plugin_class_init(FuTiTps6598xPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_ti_tps6598x_plugin_constructed; } fwupd-1.9.16/plugins/ti-tps6598x/fu-ti-tps6598x-plugin.h000066400000000000000000000004621460375044200224120ustar00rootroot00000000000000/* * Copyright (C) 2021 Texas Instruments Incorporated * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR MIT */ #pragma once #include G_DECLARE_FINAL_TYPE(FuTiTps6598xPlugin, fu_ti_tps6598x_plugin, FU, TI_TPS6598X_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/ti-tps6598x/fu-ti-tps6598x.rs000066400000000000000000000021061460375044200213100ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum TiTps6598xSfwi { Success = 0x0, FailFlashErrorOrBusy = 0x4, FailFlashInvalidAddress = 0x5, FailLastBootWasUart = 0x6, FailSfwiAfterComplete = 0x7, FailNoValidFlashRegion = 0x8, FailUnknownError = 0xF, } #[derive(ToString)] enum TiTps6598xSfwd { Success = 0x0, FailFlashEraseWriteError = 0x4, FailSfwiNotRunFirst = 0x6, FailTooMuchData = 0x7, FailIdNotInHeader = 0x8, FailBinaryTooLarge = 0x9, FailDeviceIdMismatch = 0xA, FailFlashErrorReadOnly = 0xD, FailUnknownError = 0xF, } #[derive(ToString)] enum TiTps6598xSfws { Success = 0x0, FailFlashEraseWriteError = 0x4, FailSfwdNotRunOrNoKeyExists = 0x6, FailTooMuchData = 0x7, FailCrcFail = 0x8, FailDidCheckFail = 0x9, FailVersionCheckFail = 0xA, FailNoHashMatchRuleSatisfied = 0xB, FailEngrFwUpdateAttemptWhileRunningProd = 0xC, FailIncompatibleRomVersion = 0xD, FailCrcBusy = 0xE, FailUnknownError = 0xF, } fwupd-1.9.16/plugins/ti-tps6598x/meson.build000066400000000000000000000010661460375044200205340ustar00rootroot00000000000000if gusb.found() cargs = ['-DG_LOG_DOMAIN="FuPluginTiTps6598x"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('ti-tps6598x.quirk') plugin_builtins += static_library('fu_plugin_ti_tps6598x', rustgen.process('fu-ti-tps6598x.rs'), sources: [ 'fu-ti-tps6598x-common.c', 'fu-ti-tps6598x-device.c', 'fu-ti-tps6598x-firmware.c', 'fu-ti-tps6598x-pd-device.c', 'fu-ti-tps6598x-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/ti-tps6598x/ti-tps6598x.quirk000066400000000000000000000000551460375044200214100ustar00rootroot00000000000000[USB\VID_0451&PID_ACE1] Plugin = ti_tps6598x fwupd-1.9.16/plugins/tpm/000077500000000000000000000000001460375044200152235ustar00rootroot00000000000000fwupd-1.9.16/plugins/tpm/README.md000066400000000000000000000044201460375044200165020ustar00rootroot00000000000000--- title: Plugin: TPM --- ## Introduction This allows enumerating Trusted Platform Modules, also known as "TPM" devices, although it does not allow the user to update the firmware on them. The TPM Event Log records which events are registered for the PCR0 hash, which may help in explaining why PCR0 values are differing for some firmware. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `org.trustedcomputinggroup.tpm2` ## GUID Generation These devices use custom GUIDs: * `TPM\VEN_$(manufacturer)&DEV_$(type)` * `TPM\VEN_$(manufacturer)&MOD_$(vendor-string)` * `TPM\VEN_$(manufacturer)&DEV_$(type)_VER_$(family)`, * `TPM\VEN_$(manufacturer)&MOD_$(vendor-string)_VER_$(family)` ...where `family` is either `2.0` or `1.2` Example GUIDs from a real system containing a TPM from Intel: ```text Guid: 34801700-3a50-5b05-820c-fe14580e4c2d <- TPM\VEN_INTC&DEV_0000 Guid: 03f304f4-223e-54f4-b2c1-c3cf3b5817c6 <- TPM\VEN_INTC&DEV_0000&VER_2.0 ``` ## Update Behavior The plugin detects if the TPM device is updatable (if the commands `FieldUpgradeStart` and `FieldUpgradeData` are implemented) and provides stub functionality to read and write the firmware. Nearly all TPMs in the wild have this behavior disabled, and are instead updated using vendor-specific commands used from a UEFI UpdateCapsule and with an OEM-provided ESRT entry. If a vendor wanted to use the plugin code provided here they would still need to tell us how to set up the correct `keyHandle` for `FieldUpgradeStart` and how to handle authorization. We do not know of any vendor that does TPM updates using the standardized API, and so the code provided here is more of a *this is how it should be implemented* rather than with any expectation it is actually going to just work. ## Vendor ID Security The vendor ID is set from the TPM vendor, e.g. `TPM:STM` ## External Interface Access This plugin uses the tpm2-tss library to access the TPM. It requires access to `/sys/class/tpm` and optionally requires read only access to `/sys/kernel/security/tpm0/binary_bios_measurements`. ## Version Considerations This plugin has been available since fwupd version `1.3.6`. fwupd-1.9.16/plugins/tpm/fu-self-test.c000066400000000000000000000205351460375044200177120ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-context-private.h" #include "fu-plugin-private.h" #include "fu-security-attrs-private.h" #include "fu-tpm-eventlog-common.h" #include "fu-tpm-eventlog-parser.h" #include "fu-tpm-plugin.h" #include "fu-tpm-v1-device.h" #include "fu-tpm-v2-device.h" static void fu_tpm_device_1_2_func(void) { FuTpmDevice *device; GPtrArray *devices; gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuPlugin) plugin = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(FuSecurityAttrs) attrs = fu_security_attrs_new(); g_autoptr(FwupdSecurityAttr) attr0 = NULL; g_autoptr(FwupdSecurityAttr) attr1 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) pcr0s = NULL; g_autoptr(GPtrArray) pcrXs = NULL; /* do not save silo */ ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); /* load the plugin */ plugin = fu_plugin_new_from_gtype(fu_tpm_plugin_get_type(), ctx); ret = fu_plugin_runner_startup(plugin, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* get the v1.2 device */ devices = fu_plugin_get_devices(plugin); g_assert_cmpint(devices->len, ==, 1); device = g_ptr_array_index(devices, 0); g_assert_true(FU_IS_TPM_DEVICE(device)); /* verify checksums set correctly */ pcr0s = fu_tpm_device_get_checksums(device, 0); g_assert_nonnull(pcr0s); g_assert_cmpint(pcr0s->len, ==, 1); pcrXs = fu_tpm_device_get_checksums(device, 999); g_assert_nonnull(pcrXs); g_assert_cmpint(pcrXs->len, ==, 0); /* verify HSI attributes */ fu_plugin_runner_add_security_attrs(plugin, attrs); attr0 = fu_security_attrs_get_by_appstream_id(attrs, FWUPD_SECURITY_ATTR_ID_TPM_VERSION_20, &error); g_assert_no_error(error); g_assert_nonnull(attr0); g_assert_cmpint(fwupd_security_attr_get_result(attr0), ==, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); attr1 = fu_security_attrs_get_by_appstream_id(attrs, FWUPD_SECURITY_ATTR_ID_TPM_EMPTY_PCR, &error); g_assert_no_error(error); g_assert_nonnull(attr1); /* Some PCRs are empty, but PCRs 0-7 are set (tests/tpm0/pcrs) */ g_assert_cmpint(fwupd_security_attr_get_result(attr1), ==, FWUPD_SECURITY_ATTR_RESULT_VALID); } static void fu_tpm_device_2_0_func(void) { gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuTpmDevice) device = fu_tpm_v2_device_new(ctx); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) pcr0s = NULL; g_autoptr(GPtrArray) pcrXs = NULL; const gchar *tpm_server_running = g_getenv("TPM_SERVER_RUNNING"); (void)g_setenv("FWUPD_FORCE_TPM2", "1", TRUE); #ifdef HAVE_GETUID if (tpm_server_running == NULL && (getuid() != 0 || geteuid() != 0)) { g_test_skip("TPM2.0 tests require simulated TPM2.0 running or need root access " "with physical TPM"); g_unsetenv("FWUPD_FORCE_TPM2"); return; } #endif fu_device_set_physical_id(FU_DEVICE(device), "dummy"); locker = fu_device_locker_new(FU_DEVICE(device), &error); if (locker == NULL && tpm_server_running == NULL && g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_test_skip("no physical or simulated TPM 2.0 device available"); g_unsetenv("FWUPD_FORCE_TPM2"); return; } g_assert_no_error(error); g_assert_nonnull(locker); ret = fu_device_setup(FU_DEVICE(device), &error); g_assert_no_error(error); g_assert_true(ret); pcr0s = fu_tpm_device_get_checksums(device, 0); g_assert_nonnull(pcr0s); g_assert_cmpint(pcr0s->len, >=, 1); pcrXs = fu_tpm_device_get_checksums(device, 999); g_assert_nonnull(pcrXs); g_assert_cmpint(pcrXs->len, ==, 0); g_unsetenv("FWUPD_FORCE_TPM2"); } static void fu_tpm_eventlog_parse_v1_func(void) { const gchar *ci = g_getenv("CI_NETWORK"); const gchar *tmp; gboolean ret; gsize bufsz = 0; g_autofree gchar *fn = NULL; g_autofree guint8 *buf = NULL; g_autoptr(GPtrArray) items = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) pcr0s = NULL; fn = g_test_build_filename(G_TEST_DIST, "tests", "binary_bios_measurements-v1", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing binary_bios_measurements-v1"); return; } ret = g_file_get_contents(fn, (gchar **)&buf, &bufsz, &error); g_assert_no_error(error); g_assert_true(ret); items = fu_tpm_eventlog_parser_new(buf, bufsz, FU_TPM_EVENTLOG_PARSER_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(items); pcr0s = fu_tpm_eventlog_calc_checksums(items, 0, &error); g_assert_no_error(error); g_assert_nonnull(pcr0s); g_assert_cmpint(pcr0s->len, ==, 1); tmp = g_ptr_array_index(pcr0s, 0); g_assert_cmpstr(tmp, ==, "543ae96e57b6fc4003531cd0dab1d9ba7f8166e0"); } static void fu_tpm_eventlog_parse_v2_func(void) { const gchar *ci = g_getenv("CI_NETWORK"); const gchar *tmp; gboolean ret; gsize bufsz = 0; g_autofree gchar *fn = NULL; g_autofree guint8 *buf = NULL; g_autoptr(GPtrArray) items = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) pcr0s = NULL; fn = g_test_build_filename(G_TEST_DIST, "tests", "binary_bios_measurements-v2", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing binary_bios_measurements-v2"); return; } ret = g_file_get_contents(fn, (gchar **)&buf, &bufsz, &error); g_assert_no_error(error); g_assert_true(ret); items = fu_tpm_eventlog_parser_new(buf, bufsz, FU_TPM_EVENTLOG_PARSER_FLAG_NONE, &error); g_assert_no_error(error); g_assert_nonnull(items); pcr0s = fu_tpm_eventlog_calc_checksums(items, 0, &error); g_assert_no_error(error); g_assert_nonnull(pcr0s); g_assert_cmpint(pcr0s->len, ==, 2); tmp = g_ptr_array_index(pcr0s, 0); g_assert_cmpstr(tmp, ==, "ebead4b31c7c49e193c440cd6ee90bc1b61a3ca6"); tmp = g_ptr_array_index(pcr0s, 1); g_assert_cmpstr(tmp, ==, "6d9fed68092cfb91c9552bcb7879e75e1df36efd407af67690dc3389a5722fab"); } static void fu_tpm_empty_pcr_func(void) { gboolean ret; g_autofree gchar *testdatadir = NULL; g_auto(GStrv) environ_old = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuPlugin) plugin = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(FuSecurityAttrs) attrs = fu_security_attrs_new(); g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error = NULL; /* do not save silo */ ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); /* save environment and set broken PCR data */ environ_old = g_get_environ(); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", "empty_pcr", NULL); (void)g_setenv("FWUPD_SYSFSTPMDIR", testdatadir, TRUE); /* load the plugin */ plugin = fu_plugin_new_from_gtype(fu_tpm_plugin_get_type(), ctx); ret = fu_plugin_runner_startup(plugin, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* verify HSI attr */ fu_plugin_runner_add_security_attrs(plugin, attrs); attr = fu_security_attrs_get_by_appstream_id(attrs, FWUPD_SECURITY_ATTR_ID_TPM_EMPTY_PCR, &error); g_assert_no_error(error); g_assert_nonnull(attr); /* PCR 6 is empty (tests/empty_pcr/tpm0/pcrs) */ g_assert_cmpint(fwupd_security_attr_get_result(attr), ==, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); /* restore default environment */ (void)g_setenv("FWUPD_SYSFSTPMDIR", g_environ_getenv(environ_old, "FWUPD_SYSFSTPMDIR"), TRUE); } int main(int argc, char **argv) { g_autofree gchar *testdatadir = NULL; (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); (void)g_setenv("FWUPD_SYSFSTPMDIR", testdatadir, TRUE); (void)g_setenv("FWUPD_UEFI_TEST", "1", TRUE); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", testdatadir, TRUE); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); (void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE); /* tests go here */ g_test_add_func("/tpm/pcrs1.2", fu_tpm_device_1_2_func); g_test_add_func("/tpm/pcrs2.0", fu_tpm_device_2_0_func); g_test_add_func("/tpm/empty-pcr", fu_tpm_empty_pcr_func); g_test_add_func("/tpm/eventlog-parse{v1}", fu_tpm_eventlog_parse_v1_func); g_test_add_func("/tpm/eventlog-parse{v2}", fu_tpm_eventlog_parse_v2_func); return g_test_run(); } fwupd-1.9.16/plugins/tpm/fu-tpm-device.c000066400000000000000000000061531460375044200200410ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-tpm-device.h" typedef struct { guint idx; gchar *checksum; } FuTpmDevicePcrItem; typedef struct { gchar *family; GPtrArray *items; /* of FuTpmDevicePcrItem */ } FuTpmDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuTpmDevice, fu_tpm_device, FU_TYPE_UDEV_DEVICE) #define GET_PRIVATE(o) (fu_tpm_device_get_instance_private(o)) void fu_tpm_device_set_family(FuTpmDevice *self, const gchar *family) { FuTpmDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_TPM_DEVICE(self)); priv->family = g_strdup(family); } const gchar * fu_tpm_device_get_family(FuTpmDevice *self) { FuTpmDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_TPM_DEVICE(self), NULL); return priv->family; } void fu_tpm_device_add_checksum(FuTpmDevice *self, guint idx, const gchar *checksum) { FuTpmDevicePrivate *priv = GET_PRIVATE(self); FuTpmDevicePcrItem *item; g_return_if_fail(FU_IS_TPM_DEVICE(self)); g_return_if_fail(checksum != NULL); item = g_new0(FuTpmDevicePcrItem, 1); item->idx = idx; item->checksum = g_strdup(checksum); g_debug("added PCR-%02u=%s", item->idx, item->checksum); g_ptr_array_add(priv->items, item); } GPtrArray * fu_tpm_device_get_checksums(FuTpmDevice *self, guint idx) { FuTpmDevicePrivate *priv = GET_PRIVATE(self); g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free); g_return_val_if_fail(FU_IS_TPM_DEVICE(self), NULL); for (guint i = 0; i < priv->items->len; i++) { FuTpmDevicePcrItem *item = g_ptr_array_index(priv->items, i); if (item->idx == idx) g_ptr_array_add(array, g_strdup(item->checksum)); } return g_steal_pointer(&array); } static void fu_tpm_device_to_string(FuDevice *device, guint idt, GString *str) { FuTpmDevice *self = FU_TPM_DEVICE(device); FuTpmDevicePrivate *priv = GET_PRIVATE(self); if (priv->family != NULL) fu_string_append(str, idt, "Family", priv->family); } static void fu_tpm_v2_device_item_free(FuTpmDevicePcrItem *item) { g_free(item->checksum); g_free(item); } static void fu_tpm_device_finalize(GObject *object) { FuTpmDevice *self = FU_TPM_DEVICE(object); FuTpmDevicePrivate *priv = GET_PRIVATE(self); g_free(priv->family); g_ptr_array_unref(priv->items); G_OBJECT_CLASS(fu_tpm_device_parent_class)->finalize(object); } static void fu_tpm_device_init(FuTpmDevice *self) { FuTpmDevicePrivate *priv = GET_PRIVATE(self); priv->items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_tpm_v2_device_item_free); fu_device_set_name(FU_DEVICE(self), "TPM"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_icon(FU_DEVICE(self), "computer"); fu_udev_device_set_flags(FU_UDEV_DEVICE(self), FU_UDEV_DEVICE_FLAG_NONE); } static void fu_tpm_device_class_init(FuTpmDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_tpm_device_finalize; klass_device->to_string = fu_tpm_device_to_string; } fwupd-1.9.16/plugins/tpm/fu-tpm-device.h000066400000000000000000000011711460375044200200410ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_TPM_DEVICE (fu_tpm_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuTpmDevice, fu_tpm_device, FU, TPM_DEVICE, FuUdevDevice) struct _FuTpmDeviceClass { FuDeviceClass parent_class; }; void fu_tpm_device_set_family(FuTpmDevice *self, const gchar *family); const gchar * fu_tpm_device_get_family(FuTpmDevice *self); void fu_tpm_device_add_checksum(FuTpmDevice *self, guint idx, const gchar *checksum); GPtrArray * fu_tpm_device_get_checksums(FuTpmDevice *self, guint idx); fwupd-1.9.16/plugins/tpm/fu-tpm-eventlog-common.c000066400000000000000000000130631460375044200217110ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-tpm-eventlog-common.h" const gchar * fu_tpm_eventlog_pcr_to_string(gint pcr) { if (pcr == 0) return "BIOS"; if (pcr == 1) return "BIOS Configuration"; if (pcr == 2) return "Option ROMs"; if (pcr == 3) return "Option ROM configuration"; if (pcr == 4) return "Initial program loader code"; if (pcr == 5) return "Initial program loader code configuration"; if (pcr == 6) return "State transitions and wake events"; if (pcr == 7) return "Platform manufacturer specific measurements"; if (pcr >= 8 && pcr <= 15) return "Static operating system"; if (pcr == 16) return "Debug"; if (pcr == 17) return "Dynamic root of trust measurement and launch control policy"; if (pcr >= 18 && pcr <= 22) return "Trusted OS"; if (pcr == 23) return "Application support"; return "Undefined"; } guint32 fu_tpm_eventlog_hash_get_size(TPM2_ALG_ID hash_kind) { if (hash_kind == TPM2_ALG_SHA1) return TPM2_SHA1_DIGEST_SIZE; if (hash_kind == TPM2_ALG_SHA256) return TPM2_SHA256_DIGEST_SIZE; if (hash_kind == TPM2_ALG_SHA384) return TPM2_SHA384_DIGEST_SIZE; if (hash_kind == TPM2_ALG_SHA512) return TPM2_SHA512_DIGEST_SIZE; if (hash_kind == TPM2_ALG_SM3_256) return TPM2_SM3_256_DIGEST_SIZE; return 0; } gchar * fu_tpm_eventlog_strhex(GBytes *blob) { GString *csum = g_string_new(NULL); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(blob, &bufsz); for (guint i = 0; i < bufsz; i++) g_string_append_printf(csum, "%02x", buf[i]); return g_string_free(csum, FALSE); } gchar * fu_tpm_eventlog_blobstr(GBytes *blob) { g_return_val_if_fail(blob != NULL, NULL); return g_base64_encode((const guchar *)g_bytes_get_data(blob, NULL), g_bytes_get_size(blob)); } GPtrArray * fu_tpm_eventlog_calc_checksums(GPtrArray *items, guint8 pcr, GError **error) { guint cnt_sha1 = 0; guint cnt_sha256 = 0; guint cnt_sha384 = 0; guint8 digest_sha1[TPM2_SHA1_DIGEST_SIZE] = {0x0}; guint8 digest_sha256[TPM2_SHA256_DIGEST_SIZE] = {0x0}; guint8 digest_sha384[TPM2_SHA384_DIGEST_SIZE] = {0x0}; gsize digest_sha1_len = sizeof(digest_sha1); gsize digest_sha256_len = sizeof(digest_sha256); gsize digest_sha384_len = sizeof(digest_sha384); g_autoptr(GPtrArray) csums = g_ptr_array_new_with_free_func(g_free); /* sanity check */ if (items->len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no event log data"); return NULL; } /* take existing PCR hash, append new measurement to that, * hash that with the same algorithm */ for (guint i = 0; i < items->len; i++) { FuTpmEventlogItem *item = g_ptr_array_index(items, i); if (item->pcr != pcr) continue; /* if TXT is enabled then the first event for PCR0 should be a StartupLocality */ if (item->kind == FU_TPM_EVENTLOG_ITEM_KIND_EV_NO_ACTION && item->pcr == 0 && item->blob != NULL && i == 0) { g_autoptr(GByteArray) st_loc = NULL; st_loc = fu_struct_tpm_efi_startup_locality_event_parse_bytes(item->blob, 0x0, NULL); if (st_loc != NULL) { guint8 locality = fu_struct_tpm_efi_startup_locality_event_get_locality(st_loc); digest_sha384[TPM2_SHA384_DIGEST_SIZE - 1] = locality; digest_sha256[TPM2_SHA256_DIGEST_SIZE - 1] = locality; digest_sha1[TPM2_SHA1_DIGEST_SIZE - 1] = locality; continue; } } if (item->checksum_sha1 != NULL) { g_autoptr(GChecksum) csum_sha1 = g_checksum_new(G_CHECKSUM_SHA1); g_checksum_update(csum_sha1, (const guchar *)digest_sha1, digest_sha1_len); g_checksum_update( csum_sha1, (const guchar *)g_bytes_get_data(item->checksum_sha1, NULL), g_bytes_get_size(item->checksum_sha1)); g_checksum_get_digest(csum_sha1, digest_sha1, &digest_sha1_len); cnt_sha1++; } if (item->checksum_sha256 != NULL) { g_autoptr(GChecksum) csum_sha256 = g_checksum_new(G_CHECKSUM_SHA256); g_checksum_update(csum_sha256, (const guchar *)digest_sha256, digest_sha256_len); g_checksum_update( csum_sha256, (const guchar *)g_bytes_get_data(item->checksum_sha256, NULL), g_bytes_get_size(item->checksum_sha256)); g_checksum_get_digest(csum_sha256, digest_sha256, &digest_sha256_len); cnt_sha256++; } if (item->checksum_sha384 != NULL) { g_autoptr(GChecksum) csum_sha384 = g_checksum_new(G_CHECKSUM_SHA384); g_checksum_update(csum_sha384, (const guchar *)digest_sha384, digest_sha384_len); g_checksum_update( csum_sha384, (const guchar *)g_bytes_get_data(item->checksum_sha384, NULL), g_bytes_get_size(item->checksum_sha384)); g_checksum_get_digest(csum_sha384, digest_sha384, &digest_sha384_len); cnt_sha384++; } } if (cnt_sha1 == 0 && cnt_sha256 == 0 && cnt_sha384 == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no SHA1, SHA256, or SHA384 data"); return NULL; } if (cnt_sha1 > 0) { g_autoptr(GBytes) blob_sha1 = NULL; blob_sha1 = g_bytes_new_static(digest_sha1, sizeof(digest_sha1)); g_ptr_array_add(csums, fu_tpm_eventlog_strhex(blob_sha1)); } if (cnt_sha256 > 0) { g_autoptr(GBytes) blob_sha256 = NULL; blob_sha256 = g_bytes_new_static(digest_sha256, sizeof(digest_sha256)); g_ptr_array_add(csums, fu_tpm_eventlog_strhex(blob_sha256)); } if (cnt_sha384 > 0) { g_autoptr(GBytes) blob_sha384 = NULL; blob_sha384 = g_bytes_new_static(digest_sha384, sizeof(digest_sha384)); g_ptr_array_add(csums, fu_tpm_eventlog_strhex(blob_sha384)); } return g_steal_pointer(&csums); } fwupd-1.9.16/plugins/tpm/fu-tpm-eventlog-common.h000066400000000000000000000012531460375044200217140ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-tpm-struct.h" typedef struct { guint8 pcr; FuTpmEventlogItemKind kind; GBytes *checksum_sha1; GBytes *checksum_sha256; GBytes *checksum_sha384; GBytes *blob; } FuTpmEventlogItem; const gchar * fu_tpm_eventlog_pcr_to_string(gint pcr); guint32 fu_tpm_eventlog_hash_get_size(TPM2_ALG_ID hash_kind); gchar * fu_tpm_eventlog_strhex(GBytes *blob); gchar * fu_tpm_eventlog_blobstr(GBytes *blob); GPtrArray * fu_tpm_eventlog_calc_checksums(GPtrArray *items, guint8 pcr, GError **error); fwupd-1.9.16/plugins/tpm/fu-tpm-eventlog-parser.c000066400000000000000000000203471460375044200217200ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-tpm-eventlog-parser.h" #include "fu-tpm-struct.h" #define FU_TPM_EVENTLOG_V1_IDX_PCR 0x00 #define FU_TPM_EVENTLOG_V1_IDX_TYPE 0x04 #define FU_TPM_EVENTLOG_V1_IDX_DIGEST 0x08 #define FU_TPM_EVENTLOG_V1_IDX_EVENT_SIZE 0x1c #define FU_TPM_EVENTLOG_V1_SIZE 0x20 #define FU_TPM_EVENTLOG_V2_HDR_SIGNATURE "Spec ID Event03" static void fu_tpm_eventlog_parser_item_free(FuTpmEventlogItem *item) { if (item->blob != NULL) g_bytes_unref(item->blob); if (item->checksum_sha1 != NULL) g_bytes_unref(item->checksum_sha1); if (item->checksum_sha256 != NULL) g_bytes_unref(item->checksum_sha256); if (item->checksum_sha384 != NULL) g_bytes_unref(item->checksum_sha384); g_free(item); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuTpmEventlogItem, fu_tpm_eventlog_parser_item_free); void fu_tpm_eventlog_item_to_string(FuTpmEventlogItem *item, guint idt, GString *str) { const gchar *tmp; g_autofree gchar *pcrstr = g_strdup_printf("%s (%u)", fu_tpm_eventlog_pcr_to_string(item->pcr), item->pcr); fu_string_append(str, idt, "PCR", pcrstr); fu_string_append_kx(str, idt, "Type", item->kind); tmp = fu_tpm_eventlog_item_kind_to_string(item->kind); if (tmp != NULL) fu_string_append(str, idt, "Description", tmp); if (item->checksum_sha1 != NULL) { g_autofree gchar *csum = fu_tpm_eventlog_strhex(item->checksum_sha1); fu_string_append(str, idt, "ChecksumSha1", csum); } if (item->checksum_sha256 != NULL) { g_autofree gchar *csum = fu_tpm_eventlog_strhex(item->checksum_sha256); fu_string_append(str, idt, "ChecksumSha256", csum); } if (item->checksum_sha384 != NULL) { g_autofree gchar *csum = fu_tpm_eventlog_strhex(item->checksum_sha384); fu_string_append(str, idt, "ChecksumSha384", csum); } if (item->blob != NULL) { g_autofree gchar *blobstr = fu_tpm_eventlog_blobstr(item->blob); if (blobstr != NULL) fu_string_append(str, idt, "BlobStr", blobstr); } } static GPtrArray * fu_tpm_eventlog_parser_parse_blob_v2(const guint8 *buf, gsize bufsz, FuTpmEventlogParserFlags flags, GError **error) { guint32 hdrsz = 0x0; g_autoptr(GPtrArray) items = NULL; /* advance over the header block */ if (!fu_memread_uint32_safe(buf, bufsz, FU_TPM_EVENTLOG_V1_IDX_EVENT_SIZE, &hdrsz, G_LITTLE_ENDIAN, error)) return NULL; items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_tpm_eventlog_parser_item_free); for (gsize idx = FU_TPM_EVENTLOG_V1_SIZE + hdrsz; idx < bufsz;) { guint32 pcr; guint32 digestcnt; guint32 datasz = 0; g_autoptr(GBytes) checksum_sha1 = NULL; g_autoptr(GBytes) checksum_sha256 = NULL; g_autoptr(GBytes) checksum_sha384 = NULL; g_autoptr(GByteArray) st = NULL; /* read checksum block */ st = fu_struct_tpm_event_log2_parse(buf, bufsz, idx, error); if (st == NULL) return NULL; idx += st->len; digestcnt = fu_struct_tpm_event_log2_get_digest_count(st); for (guint i = 0; i < digestcnt; i++) { guint16 alg_type = 0; guint32 alg_size = 0; g_autofree guint8 *digest = NULL; /* get checksum type */ if (!fu_memread_uint16_safe(buf, bufsz, idx, &alg_type, G_LITTLE_ENDIAN, error)) return NULL; alg_size = fu_tpm_eventlog_hash_get_size(alg_type); if (alg_size == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "hash algorithm 0x%x size not known", alg_type); return NULL; } /* build checksum */ idx += sizeof(alg_type); /* copy hash */ digest = g_malloc0(alg_size); if (!fu_memcpy_safe(digest, alg_size, 0x0, /* dst */ buf, bufsz, idx, /* src */ alg_size, error)) return NULL; /* save this for analysis */ if (alg_type == TPM2_ALG_SHA1) checksum_sha1 = g_bytes_new_take(g_steal_pointer(&digest), alg_size); else if (alg_type == TPM2_ALG_SHA256) checksum_sha256 = g_bytes_new_take(g_steal_pointer(&digest), alg_size); else if (alg_type == TPM2_ALG_SHA384) checksum_sha384 = g_bytes_new_take(g_steal_pointer(&digest), alg_size); /* next block */ idx += alg_size; } /* read data block */ if (!fu_memread_uint32_safe(buf, bufsz, idx, &datasz, G_LITTLE_ENDIAN, error)) return NULL; if (datasz > 1024 * 1024) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "event log item too large"); return NULL; } /* save blob if PCR=0 */ idx += sizeof(datasz); pcr = fu_struct_tpm_event_log2_get_pcr(st); if (pcr == ESYS_TR_PCR0 || flags & FU_TPM_EVENTLOG_PARSER_FLAG_ALL_PCRS) { g_autoptr(FuTpmEventlogItem) item = NULL; /* build item */ item = g_new0(FuTpmEventlogItem, 1); item->pcr = pcr; item->kind = fu_struct_tpm_event_log2_get_type(st); item->checksum_sha1 = g_steal_pointer(&checksum_sha1); item->checksum_sha256 = g_steal_pointer(&checksum_sha256); item->checksum_sha384 = g_steal_pointer(&checksum_sha384); if (datasz > 0) { g_autofree guint8 *data = g_malloc0(datasz); if (!fu_memcpy_safe(data, datasz, 0x0, /* dst */ buf, bufsz, idx, datasz, /* src */ error)) return NULL; item->blob = g_bytes_new_take(g_steal_pointer(&data), datasz); fu_dump_bytes(G_LOG_DOMAIN, "TpmEvent", item->blob); } g_ptr_array_add(items, g_steal_pointer(&item)); } /* next entry */ idx += datasz; } /* success */ return g_steal_pointer(&items); } GPtrArray * fu_tpm_eventlog_parser_new(const guint8 *buf, gsize bufsz, FuTpmEventlogParserFlags flags, GError **error) { gchar sig[] = FU_TPM_EVENTLOG_V2_HDR_SIGNATURE; g_autoptr(GPtrArray) items = NULL; g_return_val_if_fail(buf != NULL, NULL); /* look for TCG v2 signature */ if (!fu_memcpy_safe((guint8 *)sig, sizeof(sig), 0x0, /* dst */ buf, bufsz, FU_TPM_EVENTLOG_V1_SIZE, /* src */ sizeof(sig), error)) return NULL; if (g_strcmp0(sig, FU_TPM_EVENTLOG_V2_HDR_SIGNATURE) == 0) return fu_tpm_eventlog_parser_parse_blob_v2(buf, bufsz, flags, error); /* assume v1 structure */ items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_tpm_eventlog_parser_item_free); for (gsize idx = 0; idx < bufsz; idx += FU_TPM_EVENTLOG_V1_SIZE) { guint32 datasz = 0; guint32 pcr = 0; guint32 event_type = 0; if (!fu_memread_uint32_safe(buf, bufsz, idx + FU_TPM_EVENTLOG_V1_IDX_PCR, &pcr, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memread_uint32_safe(buf, bufsz, idx + FU_TPM_EVENTLOG_V1_IDX_TYPE, &event_type, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memread_uint32_safe(buf, bufsz, idx + FU_TPM_EVENTLOG_V1_IDX_EVENT_SIZE, &datasz, G_LITTLE_ENDIAN, error)) return NULL; if (datasz > 1024 * 1024) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "event log item too large"); return NULL; } if (pcr == ESYS_TR_PCR0 || flags & FU_TPM_EVENTLOG_PARSER_FLAG_ALL_PCRS) { g_autoptr(FuTpmEventlogItem) item = NULL; guint8 digest[TPM2_SHA1_DIGEST_SIZE] = {0x0}; /* copy hash */ if (!fu_memcpy_safe(digest, sizeof(digest), 0x0, /* dst */ buf, bufsz, idx + FU_TPM_EVENTLOG_V1_IDX_DIGEST, /* src */ sizeof(digest), error)) return NULL; /* build item */ item = g_new0(FuTpmEventlogItem, 1); item->pcr = pcr; item->kind = event_type; item->checksum_sha1 = g_bytes_new(digest, sizeof(digest)); if (datasz > 0) { g_autofree guint8 *data = g_malloc0(datasz); if (!fu_memcpy_safe(data, datasz, 0x0, /* dst */ buf, bufsz, idx + FU_TPM_EVENTLOG_V1_SIZE, /* src */ datasz, error)) return NULL; item->blob = g_bytes_new_take(g_steal_pointer(&data), datasz); fu_dump_bytes(G_LOG_DOMAIN, "TpmEvent", item->blob); } g_ptr_array_add(items, g_steal_pointer(&item)); } idx += datasz; } return g_steal_pointer(&items); } fwupd-1.9.16/plugins/tpm/fu-tpm-eventlog-parser.h000066400000000000000000000010771460375044200217240ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-tpm-eventlog-common.h" typedef enum { FU_TPM_EVENTLOG_PARSER_FLAG_NONE = 0, FU_TPM_EVENTLOG_PARSER_FLAG_ALL_PCRS = 1 << 0, FU_TPM_EVENTLOG_PARSER_FLAG_LAST } FuTpmEventlogParserFlags; GPtrArray * fu_tpm_eventlog_parser_new(const guint8 *buf, gsize bufsz, FuTpmEventlogParserFlags flags, GError **error); void fu_tpm_eventlog_item_to_string(FuTpmEventlogItem *item, guint idt, GString *str); fwupd-1.9.16/plugins/tpm/fu-tpm-eventlog.c000066400000000000000000000104511460375044200204210ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuTpmEventlog" #include "config.h" #include #include #include #include #include #include "fu-tpm-eventlog-parser.h" static gint fu_tmp_eventlog_sort_cb(gconstpointer a, gconstpointer b) { FuTpmEventlogItem *item_a = *((FuTpmEventlogItem **)a); FuTpmEventlogItem *item_b = *((FuTpmEventlogItem **)b); if (item_a->pcr > item_b->pcr) return 1; if (item_a->pcr < item_b->pcr) return -1; return 0; } static gboolean fu_tmp_eventlog_process(const gchar *fn, gint pcr, GError **error) { gsize bufsz = 0; g_autofree guint8 *buf = NULL; g_autoptr(GPtrArray) items = NULL; g_autoptr(GString) str = g_string_new(NULL); gint max_pcr = 0; /* parse this */ if (!g_file_get_contents(fn, (gchar **)&buf, &bufsz, error)) return FALSE; items = fu_tpm_eventlog_parser_new(buf, bufsz, FU_TPM_EVENTLOG_PARSER_FLAG_ALL_PCRS, error); if (items == NULL) return FALSE; g_ptr_array_sort(items, fu_tmp_eventlog_sort_cb); for (guint i = 0; i < items->len; i++) { FuTpmEventlogItem *item = g_ptr_array_index(items, i); if (item->pcr > max_pcr) max_pcr = item->pcr; if (pcr >= 0 && item->pcr != pcr) continue; fu_tpm_eventlog_item_to_string(item, 0, str); g_string_append(str, "\n"); } if (pcr > max_pcr) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid PCR specified: %d", pcr); return FALSE; } fu_string_append(str, 0, "Reconstructed PCRs", NULL); for (guint8 i = 0; i <= max_pcr; i++) { g_autoptr(GPtrArray) pcrs = fu_tpm_eventlog_calc_checksums(items, i, NULL); if (pcrs == NULL) continue; for (guint j = 0; j < pcrs->len; j++) { const gchar *csum = g_ptr_array_index(pcrs, j); g_autofree gchar *title = NULL; g_autofree gchar *pretty = NULL; if (pcr >= 0 && i != (guint)pcr) continue; title = g_strdup_printf("PCR %x", i); pretty = fwupd_checksum_format_for_display(csum); fu_string_append(str, 1, title, pretty); } } /* success */ g_print("%s", str->str); return TRUE; } int main(int argc, char *argv[]) { const gchar *fn; gboolean verbose = FALSE; gboolean interactive = isatty(fileno(stdout)) != 0; gint pcr = -1; g_autoptr(GError) error = NULL; g_autoptr(GOptionContext) context = g_option_context_new(NULL); const GOptionEntry options[] = {{"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, /* TRANSLATORS: command line option */ N_("Show extra debugging information"), NULL}, {"pcr", 'p', 0, G_OPTION_ARG_INT, &pcr, /* TRANSLATORS: command line option */ N_("Only show single PCR value"), NULL}, {NULL}}; setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); #ifndef SUPPORTED_BUILD /* make critical warnings fatal */ (void)g_setenv("G_DEBUG", "fatal-criticals", FALSE); #endif #ifdef HAVE_GETUID /* ensure root user */ if (argc < 2 && interactive && (getuid() != 0 || geteuid() != 0)) /* TRANSLATORS: we're poking around as a power user */ g_printerr("%s\n", _("This program may only work correctly as root")); #endif /* TRANSLATORS: program name */ g_set_application_name(_("fwupd TPM event log utility")); g_option_context_add_main_entries(context, options, NULL); g_option_context_set_description(context, /* TRANSLATORS: CLI description */ _("This tool will read and parse the TPM event log " "from the system firmware.")); if (!g_option_context_parse(context, &argc, &argv, &error)) { /* TRANSLATORS: the user didn't read the man page */ g_print("%s: %s\n", _("Failed to parse arguments"), error->message); return EXIT_FAILURE; } /* set verbose? */ if (verbose) { (void)g_setenv("G_MESSAGES_DEBUG", "all", FALSE); (void)g_setenv("FWUPD_TPM_EVENTLOG_VERBOSE", "1", FALSE); } /* allow user to chose a local file */ fn = argc <= 1 ? "/sys/kernel/security/tpm0/binary_bios_measurements" : argv[1]; if (!fu_tmp_eventlog_process(fn, pcr, &error)) { /* TRANSLATORS: failed to read measurements file */ g_printerr("%s: %s\n", _("Failed to parse file"), error->message); return EXIT_FAILURE; } return EXIT_SUCCESS; } fwupd-1.9.16/plugins/tpm/fu-tpm-plugin.c000066400000000000000000000300151460375044200200720ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-tpm-eventlog-parser.h" #include "fu-tpm-plugin.h" #include "fu-tpm-v1-device.h" #include "fu-tpm-v2-device.h" struct _FuTpmPlugin { FuPlugin parent_instance; FuTpmDevice *tpm_device; FuDevice *bios_device; GPtrArray *ev_items; /* of FuTpmEventlogItem */ }; G_DEFINE_TYPE(FuTpmPlugin, fu_tpm_plugin, FU_TYPE_PLUGIN) static void fu_tpm_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuTpmPlugin *self = FU_TPM_PLUGIN(plugin); if (self->tpm_device != NULL) fu_string_append(str, idt, "TpmDevice", fu_device_get_id(self->tpm_device)); if (self->bios_device != NULL) fu_string_append(str, idt, "BiosDevice", fu_device_get_id(self->bios_device)); } static void fu_tpm_plugin_set_bios_pcr0s(FuPlugin *plugin) { FuTpmPlugin *self = FU_TPM_PLUGIN(plugin); g_autoptr(GPtrArray) pcr0s = NULL; if (self->tpm_device == NULL) return; if (self->bios_device == NULL) return; /* add all the PCR0s */ pcr0s = fu_tpm_device_get_checksums(self->tpm_device, 0); if (pcr0s->len == 0) return; for (guint i = 0; i < pcr0s->len; i++) { const gchar *checksum = g_ptr_array_index(pcr0s, i); fu_device_add_checksum(self->bios_device, checksum); } fu_device_add_flag(self->bios_device, FWUPD_DEVICE_FLAG_CAN_VERIFY); } /* set the PCR0 as the device checksum */ static void fu_tpm_plugin_device_registered(FuPlugin *plugin, FuDevice *device) { FuTpmPlugin *self = FU_TPM_PLUGIN(plugin); if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE)) { g_set_object(&self->bios_device, device); fu_tpm_plugin_set_bios_pcr0s(plugin); } } static void fu_tpm_plugin_device_added(FuPlugin *plugin, FuDevice *dev) { FuTpmPlugin *self = FU_TPM_PLUGIN(plugin); g_autoptr(GPtrArray) pcr0s = NULL; g_set_object(&self->tpm_device, FU_TPM_DEVICE(dev)); fu_plugin_add_report_metadata(plugin, "TpmFamily", fu_tpm_device_get_family(FU_TPM_DEVICE(dev))); /* ensure */ fu_tpm_plugin_set_bios_pcr0s(plugin); /* add extra plugin metadata */ pcr0s = fu_tpm_device_get_checksums(self->tpm_device, 0); for (guint i = 0; i < pcr0s->len; i++) { const gchar *csum = g_ptr_array_index(pcr0s, i); GChecksumType csum_type = fwupd_checksum_guess_kind(csum); if (csum_type == G_CHECKSUM_SHA1) { fu_plugin_add_report_metadata(plugin, "Pcr0_SHA1", csum); continue; } if (csum_type == G_CHECKSUM_SHA256) { fu_plugin_add_report_metadata(plugin, "Pcr0_SHA256", csum); continue; } if (csum_type == G_CHECKSUM_SHA384) { fu_plugin_add_report_metadata(plugin, "Pcr0_SHA384", csum); continue; } } } static void fu_tpm_plugin_add_security_attr_version(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuTpmPlugin *self = FU_TPM_PLUGIN(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_TPM_VERSION_20); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_FOUND); fu_security_attrs_append(attrs, attr); /* check exists, and in v2.0 mode */ if (self->tpm_device == NULL) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } if (g_strcmp0(fu_tpm_device_get_family(self->tpm_device), "2.0") != 0) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); return; } /* success */ fwupd_security_attr_add_guids(attr, fu_device_get_guids(FU_DEVICE(self->tpm_device))); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_tpm_plugin_add_security_attr_eventlog(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuTpmPlugin *self = FU_TPM_PLUGIN(plugin); gboolean reconstructed = TRUE; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) pcr0s_calc = NULL; g_autoptr(GPtrArray) pcr0s_real = NULL; /* no TPM device */ if (self->tpm_device == NULL) return; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_TPM_RECONSTRUCTION_PCR0); fwupd_security_attr_add_guids(attr, fu_device_get_guids(self->tpm_device)); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); fu_security_attrs_append(attrs, attr); /* check reconstructed to PCR0 */ if (self->ev_items == NULL) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } /* calculate from the eventlog */ pcr0s_calc = fu_tpm_eventlog_calc_checksums(self->ev_items, 0, &error); if (pcr0s_calc == NULL) { g_warning("failed to get eventlog reconstruction: %s", error->message); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* compare against the real PCR0s */ pcr0s_real = fu_tpm_device_get_checksums(self->tpm_device, 0); for (guint i = 0; i < pcr0s_calc->len; i++) { const gchar *checksum = g_ptr_array_index(pcr0s_calc, i); reconstructed = FALSE; for (guint j = 0; j < pcr0s_real->len; j++) { const gchar *checksum_tmp = g_ptr_array_index(pcr0s_real, j); /* skip unless same algorithm */ if (strlen(checksum) != strlen(checksum_tmp)) continue; g_debug("comparing TPM %s and EVT %s", checksum, checksum_tmp); if (g_strcmp0(checksum, checksum_tmp) == 0) { reconstructed = TRUE; break; } } /* all algorithms must match */ if (!reconstructed) break; } if (!reconstructed) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_tpm_plugin_add_security_attr_empty(FuPlugin *plugin, FuSecurityAttrs *attrs) { FuTpmPlugin *self = FU_TPM_PLUGIN(plugin); g_autoptr(FwupdSecurityAttr) attr = NULL; /* no TPM device */ if (self->tpm_device == NULL) return; /* add attributes */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_TPM_EMPTY_PCR); fwupd_security_attr_add_guids(attr, fu_device_get_guids(self->tpm_device)); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); fu_security_attrs_append(attrs, attr); /* check PCRs 0 through 7 for empty checksums */ for (guint pcr = 0; pcr <= 7; pcr++) { g_autoptr(GPtrArray) checksums = fu_tpm_device_get_checksums(self->tpm_device, pcr); for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index(checksums, i); gboolean empty = TRUE; /* empty checksum is zero, so made entirely of zeroes */ for (guint j = 0; checksum[j] != '\0'; j++) { if (checksum[j] != '0') { empty = FALSE; break; } } if (empty) { fwupd_security_attr_set_result( attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); return; } } } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_tpm_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) return; fu_tpm_plugin_add_security_attr_version(plugin, attrs); fu_tpm_plugin_add_security_attr_eventlog(plugin, attrs); fu_tpm_plugin_add_security_attr_empty(plugin, attrs); } static gchar * fu_tpm_plugin_eventlog_report_metadata(FuPlugin *plugin) { FuTpmPlugin *self = FU_TPM_PLUGIN(plugin); GString *str = g_string_new(""); g_autoptr(GPtrArray) pcrs = NULL; for (guint i = 0; i < self->ev_items->len; i++) { FuTpmEventlogItem *item = g_ptr_array_index(self->ev_items, i); g_autofree gchar *blobstr = NULL; g_autofree gchar *checksum = NULL; if (item->blob == NULL) continue; if (item->checksum_sha1 != NULL) checksum = fu_tpm_eventlog_strhex(item->checksum_sha1); else if (item->checksum_sha256 != NULL) checksum = fu_tpm_eventlog_strhex(item->checksum_sha256); else if (item->checksum_sha384 != NULL) checksum = fu_tpm_eventlog_strhex(item->checksum_sha384); else continue; g_string_append_printf(str, "0x%08x %s", item->kind, checksum); blobstr = fu_tpm_eventlog_blobstr(item->blob); if (blobstr != NULL) g_string_append_printf(str, " [%s]", blobstr); g_string_append(str, "\n"); } pcrs = fu_tpm_eventlog_calc_checksums(self->ev_items, 0, NULL); if (pcrs != NULL) { for (guint j = 0; j < pcrs->len; j++) { const gchar *csum = g_ptr_array_index(pcrs, j); g_string_append_printf(str, "PCR0: %s\n", csum); } } if (str->len > 0) g_string_truncate(str, str->len - 1); return g_string_free(str, FALSE); } static gboolean fu_tpm_plugin_coldplug_eventlog(FuPlugin *plugin, GError **error) { FuTpmPlugin *self = FU_TPM_PLUGIN(plugin); gsize bufsz = 0; const gchar *fn = "/sys/kernel/security/tpm0/binary_bios_measurements"; g_autofree gchar *str = NULL; g_autofree guint8 *buf = NULL; /* do not show a warning if no TPM exists, or the kernel is too old */ if (!g_file_test(fn, G_FILE_TEST_EXISTS)) { g_debug("no %s, so skipping", fn); return TRUE; } if (!g_file_get_contents(fn, (gchar **)&buf, &bufsz, error)) return FALSE; if (bufsz == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to read data from %s", fn); return FALSE; } self->ev_items = fu_tpm_eventlog_parser_new(buf, bufsz, FU_TPM_EVENTLOG_PARSER_FLAG_NONE, error); if (self->ev_items == NULL) return FALSE; /* add optional report metadata */ str = fu_tpm_plugin_eventlog_report_metadata(plugin); fu_plugin_add_report_metadata(plugin, "TpmEventLog", str); return TRUE; } static gboolean fu_tpm_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { g_autoptr(GError) error_local = NULL; /* best effort */ if (!fu_tpm_plugin_coldplug_eventlog(plugin, &error_local)) g_warning("failed to load eventlog: %s", error_local->message); /* success */ return TRUE; } static gboolean fu_tpm_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { FuTpmPlugin *self = FU_TPM_PLUGIN(plugin); g_autofree gchar *sysfstpmdir = NULL; g_autofree gchar *fn_pcrs = NULL; /* look for TPM v1.2 */ sysfstpmdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_TPM); fn_pcrs = g_build_filename(sysfstpmdir, "tpm0", "pcrs", NULL); if (g_file_test(fn_pcrs, G_FILE_TEST_EXISTS) && g_getenv("FWUPD_FORCE_TPM2") == NULL) { self->tpm_device = fu_tpm_v1_device_new(fu_plugin_get_context(plugin)); g_object_set(self->tpm_device, "device-file", fn_pcrs, NULL); fu_device_set_physical_id(FU_DEVICE(self->tpm_device), "tpm"); if (!fu_device_probe(FU_DEVICE(self->tpm_device), error)) return FALSE; fu_plugin_device_add(plugin, FU_DEVICE(self->tpm_device)); } /* success */ return TRUE; } static void fu_tpm_plugin_init(FuTpmPlugin *self) { } static void fu_tpm_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); /* old name */ fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_CONFLICTS, "tpm_eventlog"); fu_plugin_add_device_udev_subsystem(plugin, "tpm"); fu_plugin_add_device_gtype(plugin, FU_TYPE_TPM_V2_DEVICE); } static void fu_tpm_finalize(GObject *obj) { FuTpmPlugin *self = FU_TPM_PLUGIN(obj); if (self->tpm_device != NULL) g_object_unref(self->tpm_device); if (self->bios_device != NULL) g_object_unref(self->bios_device); if (self->ev_items != NULL) g_ptr_array_unref(self->ev_items); G_OBJECT_CLASS(fu_tpm_plugin_parent_class)->finalize(obj); } static void fu_tpm_plugin_class_init(FuTpmPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_tpm_finalize; plugin_class->constructed = fu_tpm_plugin_constructed; plugin_class->to_string = fu_tpm_plugin_to_string; plugin_class->startup = fu_tpm_plugin_startup; plugin_class->coldplug = fu_tpm_plugin_coldplug; plugin_class->device_added = fu_tpm_plugin_device_added; plugin_class->device_registered = fu_tpm_plugin_device_registered; plugin_class->add_security_attrs = fu_tpm_plugin_add_security_attrs; } fwupd-1.9.16/plugins/tpm/fu-tpm-plugin.h000066400000000000000000000003371460375044200201030ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuTpmPlugin, fu_tpm_plugin, FU, TPM_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/tpm/fu-tpm-v1-device.c000066400000000000000000000045261460375044200203670ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-tpm-v1-device.h" struct _FuTpmV1Device { FuTpmDevice parent_instance; }; G_DEFINE_TYPE(FuTpmV1Device, fu_tpm_v1_device, FU_TYPE_TPM_DEVICE) static gboolean _g_string_isxdigit(GString *str) { for (gsize i = 0; i < str->len; i++) { if (!g_ascii_isxdigit(str->str[i])) return FALSE; } return TRUE; } static void fu_tpm_device_parse_line(const gchar *line, gpointer user_data) { FuTpmDevice *self = FU_TPM_DEVICE(user_data); guint64 idx; g_autofree gchar *idxstr = NULL; g_auto(GStrv) split = NULL; g_autoptr(GString) str = NULL; g_autoptr(GError) error_local = NULL; /* split into index:hash */ if (line == NULL || line[0] == '\0') return; split = g_strsplit(line, ":", -1); if (g_strv_length(split) != 2) { g_debug("unexpected format, skipping: %s", line); return; } /* get index */ idxstr = fu_strstrip(split[0]); if (!fu_strtoull(idxstr, &idx, 0, 64, &error_local)) { g_debug("unexpected index %s, skipping: %s", idxstr, error_local->message); return; } /* parse hash */ str = g_string_new(split[1]); g_string_replace(str, " ", "", 0); if ((str->len != 40 && str->len != 64) || !_g_string_isxdigit(str)) { g_debug("not SHA-1 or SHA-256, skipping: %s", split[1]); return; } g_string_ascii_down(str); fu_tpm_device_add_checksum(self, idx, str->str); } static gboolean fu_tpm_v1_device_probe(FuDevice *device, GError **error) { FuTpmV1Device *self = FU_TPM_V1_DEVICE(device); g_auto(GStrv) lines = NULL; g_autofree gchar *buf_pcrs = NULL; /* get entire contents */ if (!g_file_get_contents(fu_udev_device_get_device_file(FU_UDEV_DEVICE(device)), &buf_pcrs, NULL, error)) return FALSE; /* find PCR lines */ lines = g_strsplit(buf_pcrs, "\n", -1); for (guint i = 0; lines[i] != NULL; i++) { if (g_str_has_prefix(lines[i], "PCR-")) fu_tpm_device_parse_line(lines[i] + 4, self); } return TRUE; } static void fu_tpm_v1_device_init(FuTpmV1Device *self) { } static void fu_tpm_v1_device_class_init(FuTpmV1DeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_tpm_v1_device_probe; } FuTpmDevice * fu_tpm_v1_device_new(FuContext *ctx) { return FU_TPM_DEVICE(g_object_new(FU_TYPE_TPM_V1_DEVICE, "context", ctx, NULL)); } fwupd-1.9.16/plugins/tpm/fu-tpm-v1-device.h000066400000000000000000000005671460375044200203750ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-tpm-device.h" #define FU_TYPE_TPM_V1_DEVICE (fu_tpm_v1_device_get_type()) G_DECLARE_FINAL_TYPE(FuTpmV1Device, fu_tpm_v1_device, FU, TPM_V1_DEVICE, FuTpmDevice) FuTpmDevice * fu_tpm_v1_device_new(FuContext *ctx); fwupd-1.9.16/plugins/tpm/fu-tpm-v2-device.c000066400000000000000000000407511460375044200203700ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-tpm-v2-device.h" struct _FuTpmV2Device { FuTpmDevice parent_instance; ESYS_CONTEXT *esys_context; }; G_DEFINE_TYPE(FuTpmV2Device, fu_tpm_v2_device, FU_TYPE_TPM_DEVICE) static gboolean fu_tpm_v2_device_probe(FuDevice *device, GError **error) { return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "tpm", error); } static gboolean fu_tpm_v2_device_get_uint32(FuTpmV2Device *self, guint32 query, guint32 *val, GError **error) { TSS2_RC rc; g_autofree TPMS_CAPABILITY_DATA *capability = NULL; g_return_val_if_fail(val != NULL, FALSE); rc = Esys_GetCapability(self->esys_context, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_CAP_TPM_PROPERTIES, query, 1, NULL, &capability); if (rc != TSS2_RC_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "capability request failed for query %x", query); return FALSE; } if (capability->data.tpmProperties.count == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no properties returned for query %x", query); return FALSE; } if (capability->data.tpmProperties.tpmProperty[0].property != query) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "wrong query returned (got %x expected %x)", capability->data.tpmProperties.tpmProperty[0].property, query); return FALSE; } *val = capability->data.tpmProperties.tpmProperty[0].value; return TRUE; } static gchar * fu_tpm_v2_device_get_string(FuTpmV2Device *self, guint32 query, GError **error) { guint32 val_be = 0; guint32 val; gchar result[5] = {'\0'}; /* return four bytes */ if (!fu_tpm_v2_device_get_uint32(self, query, &val_be, error)) return NULL; val = GUINT32_FROM_BE(val_be); memcpy(result, (gchar *)&val, 4); /* convert non-ASCII into spaces */ for (guint i = 0; i < 4; i++) { if (!g_ascii_isgraph(result[i])) result[i] = 0x20; } return fu_strstrip(result); } /* taken from TCG-TPM-Vendor-ID-Registry-Version-1.01-Revision-1.00.pdf */ static const gchar * fu_tpm_v2_device_convert_manufacturer(const gchar *manufacturer) { if (g_strcmp0(manufacturer, "AMD") == 0) return "Advanced Micro Devices, Inc."; if (g_strcmp0(manufacturer, "ATML") == 0) return "Atmel"; if (g_strcmp0(manufacturer, "BRCM") == 0) return "Broadcom"; if (g_strcmp0(manufacturer, "HPE") == 0) return "HPE"; if (g_strcmp0(manufacturer, "IBM") == 0) return "IBM"; if (g_strcmp0(manufacturer, "IFX") == 0) return "Infineon"; if (g_strcmp0(manufacturer, "INTC") == 0) return "Intel"; if (g_strcmp0(manufacturer, "LEN") == 0) return "Lenovo"; if (g_strcmp0(manufacturer, "MSFT") == 0) return "Microsoft"; if (g_strcmp0(manufacturer, "NSM") == 0) return "National Semiconductor"; if (g_strcmp0(manufacturer, "NTZ") == 0) return "Nationz"; if (g_strcmp0(manufacturer, "NTC") == 0) return "Nuvoton Technology"; if (g_strcmp0(manufacturer, "QCOM") == 0) return "Qualcomm"; if (g_strcmp0(manufacturer, "SMSC") == 0) return "SMSC"; if (g_strcmp0(manufacturer, "STM") == 0) return "ST Microelectronics"; if (g_strcmp0(manufacturer, "SMSN") == 0) return "Samsung"; if (g_strcmp0(manufacturer, "SNS") == 0) return "Sinosun"; if (g_strcmp0(manufacturer, "TXN") == 0) return "Texas Instruments"; if (g_strcmp0(manufacturer, "WEC") == 0) return "Winbond"; if (g_strcmp0(manufacturer, "ROCC") == 0) return "Fuzhou Rockchip"; if (g_strcmp0(manufacturer, "GOOG") == 0) return "Google"; return NULL; } static gboolean fu_tpm_v2_device_setup_pcrs(FuTpmV2Device *self, GError **error) { TSS2_RC rc; g_autofree TPMS_CAPABILITY_DATA *capability_data = NULL; TPML_PCR_SELECTION pcr_selection_in = { 0, }; g_autofree TPML_DIGEST *pcr_values = NULL; /* get hash algorithms supported by the TPM */ rc = Esys_GetCapability(self->esys_context, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_CAP_PCRS, 0, 1, NULL, &capability_data); if (rc != TSS2_RC_SUCCESS) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to get hash algorithms supported by TPM"); return FALSE; } /* fetch PCR 0 for every supported hash algorithm */ pcr_selection_in.count = capability_data->data.assignedPCR.count; for (guint i = 0; i < pcr_selection_in.count; i++) { pcr_selection_in.pcrSelections[i].hash = capability_data->data.assignedPCR.pcrSelections[i].hash; pcr_selection_in.pcrSelections[i].sizeofSelect = capability_data->data.assignedPCR.pcrSelections[i].sizeofSelect; pcr_selection_in.pcrSelections[i].pcrSelect[0] = 0b00000001; } rc = Esys_PCR_Read(self->esys_context, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &pcr_selection_in, NULL, NULL, &pcr_values); if (rc != TSS2_RC_SUCCESS) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to read PCR values from TPM"); return FALSE; } for (guint i = 0; i < pcr_values->count; i++) { g_autoptr(GString) str = NULL; gboolean valid = FALSE; str = g_string_new(NULL); for (guint j = 0; j < pcr_values->digests[i].size; j++) { gint64 val = pcr_values->digests[i].buffer[j]; if (val > 0) valid = TRUE; g_string_append_printf(str, "%02x", pcr_values->digests[i].buffer[j]); } if (valid) { /* constant PCR index 0, since we only read this single PCR */ fu_tpm_device_add_checksum(FU_TPM_DEVICE(self), 0, str->str); } } /* success */ return TRUE; } static gboolean fu_tpm_v2_device_ensure_commands(FuTpmV2Device *self, GError **error) { gboolean seen_upgrade_data = FALSE; gboolean seen_upgrade_start = FALSE; TSS2_RC rc; g_autofree TPMS_CAPABILITY_DATA *capability = NULL; g_autoptr(GString) str = g_string_new(NULL); rc = Esys_GetCapability(self->esys_context, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_CAP_COMMANDS, TPM2_CC_FIRST, TPM2_MAX_CAP_CC, NULL, &capability); if (rc != TSS2_RC_SUCCESS) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "capability request failed for TPM2_CAP_COMMANDS"); return FALSE; } for (guint i = 0; i < capability->data.ppCommands.count; i++) { guint cap_cmd = capability->data.ppCommands.commandCodes[i] & 0xFFFF; if (str->len > 0) g_string_append(str, ", "); g_string_append_printf(str, "0x%04x", cap_cmd); /* ones we care about */ if (cap_cmd == TPM2_CC_FieldUpgradeStart) { seen_upgrade_start = TRUE; } else if (cap_cmd == TPM2_CC_FieldUpgradeData) { seen_upgrade_data = TRUE; } else if (cap_cmd == TPM2_CC_FirmwareRead) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); } } g_debug("CAP_COMMANDS: %s", str->str); /* both available */ if (seen_upgrade_start && seen_upgrade_data) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); /* success */ return TRUE; } static gboolean fu_tpm_v2_device_setup(FuDevice *device, GError **error) { FuTpmV2Device *self = FU_TPM_V2_DEVICE(device); TSS2_RC rc; const gchar *tmp; guint32 tpm_type = 0; guint32 version1 = 0; guint32 version2 = 0; guint64 version_raw; g_autofree gchar *manufacturer = NULL; g_autofree gchar *model1 = NULL; g_autofree gchar *model2 = NULL; g_autofree gchar *model3 = NULL; g_autofree gchar *model4 = NULL; g_autofree gchar *model = NULL; g_autofree gchar *vendor_id = NULL; g_autofree gchar *family = NULL; /* suppress warning messages about missing TCTI libraries for tpm2-tss <2.3 */ if (g_getenv("FWUPD_UEFI_VERBOSE") == NULL) (void)g_setenv("TSS2_LOG", "esys+none,tcti+none", FALSE); rc = Esys_Startup(self->esys_context, TPM2_SU_CLEAR); if (rc != TSS2_RC_SUCCESS) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to initialize TPM"); return FALSE; } /* lookup guaranteed details from TPM */ family = fu_tpm_v2_device_get_string(self, TPM2_PT_FAMILY_INDICATOR, error); if (family == NULL) { g_prefix_error(error, "failed to read TPM family: "); return FALSE; } fu_tpm_device_set_family(FU_TPM_DEVICE(self), family); manufacturer = fu_tpm_v2_device_get_string(self, TPM2_PT_MANUFACTURER, error); if (manufacturer == NULL) { g_prefix_error(error, "failed to read TPM manufacturer: "); return FALSE; } model1 = fu_tpm_v2_device_get_string(self, TPM2_PT_VENDOR_STRING_1, error); if (model1 == NULL) { g_prefix_error(error, "failed to read TPM vendor string: "); return FALSE; } if (!fu_tpm_v2_device_get_uint32(self, TPM2_PT_VENDOR_TPM_TYPE, &tpm_type, error)) { g_prefix_error(error, "failed to read TPM type: "); return FALSE; } /* these are not guaranteed by spec and may be NULL */ model2 = fu_tpm_v2_device_get_string(self, TPM2_PT_VENDOR_STRING_2, error); model3 = fu_tpm_v2_device_get_string(self, TPM2_PT_VENDOR_STRING_3, error); model4 = fu_tpm_v2_device_get_string(self, TPM2_PT_VENDOR_STRING_4, error); model = g_strjoin("", model1, model2, model3, model4, NULL); /* add GUIDs to daemon */ fu_device_add_instance_str(device, "VEN", manufacturer); fu_device_add_instance_u16(device, "DEV", tpm_type); fu_device_add_instance_str(device, "MOD", model); fu_device_add_instance_str(device, "VER", family); fu_device_build_instance_id(device, NULL, "TPM", "VEN", "DEV", NULL); fu_device_build_instance_id(device, NULL, "TPM", "VEN", "MOD", NULL); fu_device_build_instance_id(device, NULL, "TPM", "VEN", "DEV", "VER", NULL); fu_device_build_instance_id(device, NULL, "TPM", "VEN", "MOD", "VER", NULL); /* enforce vendors can only ship updates for their own hardware */ vendor_id = g_strdup_printf("TPM:%s", manufacturer); fu_device_add_vendor_id(device, vendor_id); tmp = fu_tpm_v2_device_convert_manufacturer(manufacturer); fu_device_set_vendor(device, tmp != NULL ? tmp : manufacturer); /* get version */ if (!fu_tpm_v2_device_get_uint32(self, TPM2_PT_FIRMWARE_VERSION_1, &version1, error)) return FALSE; if (!fu_tpm_v2_device_get_uint32(self, TPM2_PT_FIRMWARE_VERSION_2, &version2, error)) return FALSE; version_raw = ((guint64)version1) << 32 | ((guint64)version2); fu_device_set_version_u64(device, version_raw); /* get capabilities */ if (!fu_tpm_v2_device_ensure_commands(self, error)) return FALSE; /* get PCRs */ return fu_tpm_v2_device_setup_pcrs(self, error); } static gboolean fu_tpm_v2_device_upgrade_data(FuTpmV2Device *self, GBytes *fw, FuProgress *progress, GError **error) { TPMT_HA *first_digest; TPMT_HA *next_digest; TSS2_RC rc; g_autoptr(FuChunkArray) chunks = NULL; chunks = fu_chunk_array_new_from_bytes(fw, 0x0, TPM2_MAX_DIGEST_BUFFER); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); TPM2B_MAX_BUFFER data = {.size = g_bytes_get_size(fw)}; if (!fu_memcpy_safe((guint8 *)data.buffer, sizeof(data.buffer), 0x0, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; rc = Esys_FieldUpgradeData(self->esys_context, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &data, &next_digest, &first_digest); if (rc == TPM2_RC_COMMAND_CODE || (rc == (TPM2_RC_COMMAND_CODE | TSS2_RESMGR_RC_LAYER)) || (rc == (TPM2_RC_COMMAND_CODE | TSS2_RESMGR_TPM_RC_LAYER))) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "TPM2_FieldUpgradeData not supported: 0x%x", rc); return FALSE; } /* update progress */ fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_tpm_v2_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuTpmV2Device *self = FU_TPM_V2_DEVICE(device); TPM2B_DIGEST digest = {0x0}; TSS2_RC rc; g_autoptr(GBytes) fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99, NULL); /* validate the signature and that the authorization is valid */ rc = Esys_FieldUpgradeStart(self->esys_context, ESYS_TR_NONE, /* TODO: authorization */ ESYS_TR_NONE, /* TODO: keyHandle */ ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, &digest, NULL); if (rc == TPM2_RC_SIGNATURE) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "the signature check failed"); return FALSE; } if (rc == TPM2_RC_COMMAND_CODE || (rc == (TPM2_RC_COMMAND_CODE | TSS2_RESMGR_RC_LAYER)) || (rc == (TPM2_RC_COMMAND_CODE | TSS2_RESMGR_TPM_RC_LAYER))) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "TPM2_FieldUpgradeStart not supported: 0x%x", rc); return FALSE; } /* deploy data to device */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; if (!fu_tpm_v2_device_upgrade_data(self, fw, fu_progress_get_child(progress), error)) return FALSE; /* success! */ return TRUE; } static GBytes * fu_tpm_v2_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuTpmV2Device *self = FU_TPM_V2_DEVICE(device); TSS2_RC rc; guint chunks_max = fu_device_get_firmware_size_max(device) / TPM2_MAX_DIGEST_BUFFER; g_autoptr(GByteArray) buf = g_byte_array_new(); /* keep reading chunks until we get a zero sized response */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); for (guint i = 0; i < chunks_max; i++) { g_autofree TPM2B_MAX_BUFFER **data = g_new0(TPM2B_MAX_BUFFER *, 1); g_debug("getting firmware chunk 0x%x", i); rc = Esys_FirmwareRead(self->esys_context, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, i /* seqnum */, (TPM2B_MAX_BUFFER **)&data); if (rc == TPM2_RC_COMMAND_CODE || (rc == (TPM2_RC_COMMAND_CODE | TSS2_RESMGR_RC_LAYER)) || (rc == (TPM2_RC_COMMAND_CODE | TSS2_RESMGR_TPM_RC_LAYER))) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "TPM2_FirmwareRead not supported: 0x%x", rc); return NULL; } if (data[0] == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no data returned"); return NULL; } if (data[0]->size == 0) break; /* yes, the blocks are returned in reverse order */ g_byte_array_prepend(buf, data[0]->buffer, data[0]->size); Esys_Free(data[0]); } /* success */ return g_bytes_new(buf->data, buf->len); } static gboolean fu_tpm_v2_device_open(FuDevice *device, GError **error) { FuTpmV2Device *self = FU_TPM_V2_DEVICE(device); TSS2_RC rc; /* setup TSS */ rc = Esys_Initialize(&self->esys_context, NULL, NULL); if (rc != TSS2_RC_SUCCESS) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to initialize TPM library"); return FALSE; } /* success */ return TRUE; } static gboolean fu_tpm_v2_device_close(FuDevice *device, GError **error) { FuTpmV2Device *self = FU_TPM_V2_DEVICE(device); Esys_Finalize(&self->esys_context); return TRUE; } static void fu_tpm_v2_device_init(FuTpmV2Device *self) { fu_device_add_protocol(FU_DEVICE(self), "org.trustedcomputinggroup.tpm2"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_AFFECTS_FDE); fu_device_set_firmware_size_max(FU_DEVICE(self), 32 * 1024 * 1024); } static void fu_tpm_v2_device_class_init(FuTpmV2DeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_tpm_v2_device_setup; klass_device->probe = fu_tpm_v2_device_probe; klass_device->open = fu_tpm_v2_device_open; klass_device->close = fu_tpm_v2_device_close; klass_device->write_firmware = fu_tpm_v2_device_write_firmware; klass_device->dump_firmware = fu_tpm_v2_device_dump_firmware; } FuTpmDevice * fu_tpm_v2_device_new(FuContext *ctx) { FuTpmV2Device *self; self = g_object_new(FU_TYPE_TPM_V2_DEVICE, "context", ctx, NULL); return FU_TPM_DEVICE(self); } fwupd-1.9.16/plugins/tpm/fu-tpm-v2-device.h000066400000000000000000000005671460375044200203760ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-tpm-device.h" #define FU_TYPE_TPM_V2_DEVICE (fu_tpm_v2_device_get_type()) G_DECLARE_FINAL_TYPE(FuTpmV2Device, fu_tpm_v2_device, FU, TPM_V2_DEVICE, FuTpmDevice) FuTpmDevice * fu_tpm_v2_device_new(FuContext *ctx); fwupd-1.9.16/plugins/tpm/fu-tpm.rs000066400000000000000000000030131460375044200167760ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] #[repr(u32le)] enum TpmEventlogItemKind { EV_PREBOOT_CERT = 0x00000000, EV_POST_CODE = 0x00000001, EV_NO_ACTION = 0x00000003, EV_SEPARATOR = 0x00000004, EV_ACTION = 0x00000005, EV_EVENT_TAG = 0x00000006, EV_S_CRTM_CONTENTS = 0x00000007, EV_S_CRTM_VERSION = 0x00000008, EV_CPU_MICROCODE = 0x00000009, EV_PLATFORM_CONFIG_FLAGS = 0x0000000a, EV_TABLE_OF_DEVICES = 0x0000000b, EV_COMPACT_HASH = 0x0000000c, EV_NONHOST_CODE = 0x0000000f, EV_NONHOST_CONFIG = 0x00000010, EV_NONHOST_INFO = 0x00000011, EV_OMIT_BOOT_DEVICE_EVENTS = 0x00000012, EV_EFI_EVENT_BASE = 0x80000000, EV_EFI_VARIABLE_DRIVER_CONFIG = 0x80000001, EV_EFI_VARIABLE_BOOT = 0x80000002, EV_EFI_BOOT_SERVICES_APPLICATION = 0x80000003, EV_EFI_BOOT_SERVICES_DRIVER = 0x80000004, EV_EFI_RUNTIME_SERVICES_DRIVER = 0x80000005, EV_EFI_GPT_EVENT = 0x80000006, EV_EFI_ACTION = 0x80000007, EV_EFI_PLATFORM_FIRMWARE_BLOB = 0x80000008, EV_EFI_HANDOFF_TABLES = 0x80000009, EV_EFI_HCRTM_EVENT = 0x80000010, EV_EFI_VARIABLE_AUTHORITY = 0x800000e0, } #[derive(Parse)] struct TpmEventLog2 { pcr: u32le, type: TpmEventlogItemKind, digest_count: u32le, } #[derive(ParseBytes)] struct TpmEfiStartupLocalityEvent { signature: [char; 16] == "StartupLocality", locality: u8, // from which TPM2_Startup() was issued -- which is the initial value of PCR0 } fwupd-1.9.16/plugins/tpm/fuzzing/000077500000000000000000000000001460375044200167175ustar00rootroot00000000000000fwupd-1.9.16/plugins/tpm/fuzzing/v2.bin000066400000000000000000000000201460375044200177300ustar00rootroot00000000000000Spec ID Event03 fwupd-1.9.16/plugins/tpm/meson.build000066400000000000000000000034011460375044200173630ustar00rootroot00000000000000tpm2tss_tpm = dependency('tss2-esys', version: '>= 2.0', required: get_option('plugin_tpm')) if hsi and \ tpm2tss_tpm.found() and \ get_option('plugin_tpm').require(gudev.found(), error_message: 'gudev is needed for plugin_tpm').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginTpm"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('tpm.quirk') plugin_builtin_tpm = static_library('fu_plugin_tpm', rustgen.process('fu-tpm.rs'), sources: [ 'fu-tpm-plugin.c', 'fu-tpm-device.c', 'fu-tpm-v1-device.c', 'fu-tpm-v2-device.c', 'fu-tpm-eventlog-common.c', 'fu-tpm-eventlog-parser.c', ], include_directories: plugin_incdirs, link_with: [ fwupdplugin, fwupd, ], c_args: cargs, dependencies: [ plugin_deps, tpm2tss_tpm, ], ) plugin_builtins += plugin_builtin_tpm if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) env.set('FWUPD_LOCALSTATEDIR', '/tmp/fwupd-self-test/var') e = executable( 'tpm-self-test', rustgen.process('fu-tpm.rs'), sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: [ plugin_deps, tpm2tss_tpm, ], link_with: [ fwupd, fwupdplugin, plugin_builtin_tpm, ], c_args: [ cargs, '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('tpm-self-test', e, env: env) endif executable( 'fwupdtpmevlog', rustgen.process('fu-tpm.rs'), sources: [ 'fu-tpm-eventlog.c', ], include_directories: plugin_incdirs, dependencies: [ plugin_deps, tpm2tss_tpm, ], link_with: [ plugin_libs, plugin_builtin_tpm, ], ) endif fwupd-1.9.16/plugins/tpm/tests/000077500000000000000000000000001460375044200163655ustar00rootroot00000000000000fwupd-1.9.16/plugins/tpm/tests/empty_pcr/000077500000000000000000000000001460375044200203675ustar00rootroot00000000000000fwupd-1.9.16/plugins/tpm/tests/empty_pcr/tpm0/000077500000000000000000000000001460375044200212475ustar00rootroot00000000000000fwupd-1.9.16/plugins/tpm/tests/empty_pcr/tpm0/active000066400000000000000000000000021460375044200224350ustar00rootroot000000000000001 fwupd-1.9.16/plugins/tpm/tests/empty_pcr/tpm0/caps000066400000000000000000000001011460375044200221100ustar00rootroot00000000000000Manufacturer: 0x49465800 TCG version: 1.2 Firmware version: 6.40 fwupd-1.9.16/plugins/tpm/tests/empty_pcr/tpm0/enabled000066400000000000000000000000021460375044200225540ustar00rootroot000000000000001 fwupd-1.9.16/plugins/tpm/tests/empty_pcr/tpm0/owned000066400000000000000000000000021460375044200222760ustar00rootroot000000000000001 fwupd-1.9.16/plugins/tpm/tests/empty_pcr/tpm0/pcrs000066400000000000000000000031701460375044200221420ustar00rootroot00000000000000PCR-00: 3C 97 99 20 C9 00 99 60 09 27 D5 DA B3 81 EB 95 1E 7F C8 68 PCR-01: CE 9F A4 B2 01 09 D8 81 14 EA 1A 6D 13 94 CD 45 5F 52 69 23 PCR-02: 47 09 7A 9A AD C3 26 A4 93 91 26 63 A1 6F DF 53 D7 88 96 8E PCR-03: B2 A8 3B 0E BF 2F 83 74 29 9A 5B 2B DF C3 1E A9 55 AD 72 36 PCR-04: A4 A5 87 4C 59 94 8D 9B 93 66 0A F4 19 D8 6F F8 94 36 20 CC PCR-05: 00 0B 58 00 89 72 EF 6C 2A AC 79 33 C4 AE 67 6B A6 EF CF 6A PCR-06: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 PCR-07: 0A 2A 68 15 85 0D AC B2 D1 F4 E0 C1 F4 56 D5 E2 81 08 6D EA PCR-08: DB A7 29 4E 49 BA D7 9E 53 99 0A 6E 3A CB 52 97 B9 08 3A 66 PCR-09: 19 F9 6F 10 83 F5 5B 50 98 26 C3 14 73 43 35 21 1F E6 39 E9 PCR-10: 37 3D 89 9E 10 0D DD 2D 21 B5 F4 96 8D 4F DC A7 6D 1A C7 BD PCR-11: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 PCR-12: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 PCR-13: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 PCR-14: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 PCR-15: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 PCR-16: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 PCR-17: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF PCR-18: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF PCR-19: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF PCR-20: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF PCR-21: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF PCR-22: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF PCR-23: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fwupd-1.9.16/plugins/tpm/tests/tpm0/000077500000000000000000000000001460375044200172455ustar00rootroot00000000000000fwupd-1.9.16/plugins/tpm/tests/tpm0/active000066400000000000000000000000021460375044200204330ustar00rootroot000000000000001 fwupd-1.9.16/plugins/tpm/tests/tpm0/caps000066400000000000000000000001011460375044200201060ustar00rootroot00000000000000Manufacturer: 0x49465800 TCG version: 1.2 Firmware version: 6.40 fwupd-1.9.16/plugins/tpm/tests/tpm0/enabled000066400000000000000000000000021460375044200205520ustar00rootroot000000000000001 fwupd-1.9.16/plugins/tpm/tests/tpm0/owned000066400000000000000000000000021460375044200202740ustar00rootroot000000000000001 fwupd-1.9.16/plugins/tpm/tests/tpm0/pcrs000066400000000000000000000031701460375044200201400ustar00rootroot00000000000000PCR-00: 3C 97 99 20 C9 00 99 60 09 27 D5 DA B3 81 EB 95 1E 7F C8 68 PCR-01: CE 9F A4 B2 01 09 D8 81 14 EA 1A 6D 13 94 CD 45 5F 52 69 23 PCR-02: 47 09 7A 9A AD C3 26 A4 93 91 26 63 A1 6F DF 53 D7 88 96 8E PCR-03: B2 A8 3B 0E BF 2F 83 74 29 9A 5B 2B DF C3 1E A9 55 AD 72 36 PCR-04: A4 A5 87 4C 59 94 8D 9B 93 66 0A F4 19 D8 6F F8 94 36 20 CC PCR-05: 00 0B 58 00 89 72 EF 6C 2A AC 79 33 C4 AE 67 6B A6 EF CF 6A PCR-06: B2 A8 3B 0E BF 2F 83 74 29 9A 5B 2B DF C3 1E A9 55 AD 72 36 PCR-07: 0A 2A 68 15 85 0D AC B2 D1 F4 E0 C1 F4 56 D5 E2 81 08 6D EA PCR-08: DB A7 29 4E 49 BA D7 9E 53 99 0A 6E 3A CB 52 97 B9 08 3A 66 PCR-09: 19 F9 6F 10 83 F5 5B 50 98 26 C3 14 73 43 35 21 1F E6 39 E9 PCR-10: 37 3D 89 9E 10 0D DD 2D 21 B5 F4 96 8D 4F DC A7 6D 1A C7 BD PCR-11: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 PCR-12: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 PCR-13: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 PCR-14: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 PCR-15: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 PCR-16: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 PCR-17: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF PCR-18: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF PCR-19: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF PCR-20: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF PCR-21: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF PCR-22: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF PCR-23: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fwupd-1.9.16/plugins/tpm/tpm.quirk000066400000000000000000000000671460375044200171030ustar00rootroot00000000000000[TPM\VEN_MSFT&MOD_Pluton.TPM.A] Flags = host-cpu-child fwupd-1.9.16/plugins/uefi-capsule/000077500000000000000000000000001460375044200170055ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/README.md000066400000000000000000000147121460375044200202710ustar00rootroot00000000000000--- title: Plugin: UEFI Capsule --- ## Introduction The Unified Extensible Firmware Interface (UEFI) is a specification that defines the software interface between an OS and platform firmware. With the UpdateCapsule boot service it can be used to update system firmware. If you don't want or need this functionality you can use the `-Dplugin_uefi_capsule=disabled` option. When this plugin is enabled, the companion UEFI binary may also be built from the [fwupd-efi](https://github.com/fwupd/fwupd-efi) project if not already present on the filesystem. This behavior can be overridden using the meson option `-Defi_binary=false`. For this companion binary to work with secure boot, it will need to be signed by an authority trusted with shim and/or the host environment. ## Lenovo Specific Behavior On Lenovo hardware only the boot label is set to `Linux-Firmware-Updater` rather than "Linux Firmware Updater" (with spaces) due to long-fixed EFI boot manager bugs. Many users will have these old BIOS versions installed and so we use the `use-legacy-bootmgr-desc` quirk to use the safe name. On some Lenovo hardware only one capsule is installable due to possible problems with the UpdateCapsule coalesce operation. As soon as one UEFI device has been scheduled for update the other UEFI devices found in the ESRT will be marked as `updatable-hidden` rather than `updatable`. Rebooting will restore them so they can be updated on next OS boot. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in EFI capsule file format. See the [UEFI specification](https://www.uefi.org/sites/default/files/resources/UEFI%20Spec%202_6.pdf) for details. This plugin supports the following protocol ID: * `org.uefi.capsule` ## Version Format The ESRT table is the way the firmware tells fwupd about what devices are updatable. Unfortunately the information it contains is minimal: * GUID of the sub-component * Enumerated result of the last update * Firmware version as a **32 bit unsigned number** By default, we show the ESRT devices as decimal or hexadecimal numbers as different vendors format the number in different ways. When fwupd gets information about how to format the *raw* version is converts the number into a more familiar form. When the hardware GUID is static, a quirk may be appropriate, for example: [28108d08-5027-42c2-a5b8-92d6ede9b97b] VersionFormat = quad As the GUID may be model specific, the daemon also reads the metadata and copies the version format from that. This means that `fwupdmgr get-devices` may return the UEFI device as a number initially, then once `fwupdmgr refresh` has completed it may start showing the exact same device as `A.B.C.D`, aka `quad` format. The two main formats used by vendors are `triplet`, `quad` and `dell-bios`. 0xAABBCCDD -> 0xAA.0xBB.0xCCCC is `triplet`, used for Lenovo 0xAABBCCDD -> 0xAA.0xBB.0xCC.0xDD is `quad`, used for HP 0xAABBCCDD -> 0xBB.0xCC.0xDD is `dell-bios`, used for Dell There are more details about firmware version formats and a full list of all the different allowed values on the [LVFS](https://lvfs.readthedocs.io/en/latest/metainfo.html#version-format). NOTE: Firmware can require either the `quad` or `triplet` string version format, but it may be more portable to depend on the number -- which will also work if the metadata has not been refreshed yet. ## Update Behavior ### Capsule update on-disk Described in [UEFI specification](https://www.uefi.org/sites/default/files/resources/UEFI%20Spec%202_6.pdf) § 8.5.5 - Delivery of Capsules via file on Mass Storage device. If the firmware supports this, it will be the preferred method of updating on aarch64 platforms. You can explicitly disable it by by modifying *DisableCapsuleUpdateOnDisk* in `/etc/fwupd/uefi_capsule.conf`. The spec expects runtime *SetVariable* to be available in order to enable this feature, we need to set `EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED` in *OsIndications* variable to trigger processing of submitted capsule on next reboot. However some firmware implementations (e.g U-Boot), can't set the variable at runtime, but ignore the variable in next reboot and apply the capsule anyway. The directory \EFI\UpdateCapsule is checked for capsules only within the EFI system partition on the device specified in the active boot option determine by reference to *BootNext* variable or *BootOrder* variable processing. Since setting *BootNext*, for capsule update on-disk, is not yet implemented, the only available option is place the \EFI\UpdateCapsule within the ESP partition indicated by the current *BootOrder*. Note that this will be always needed if your firmware doesn't support *SetVariable* at runtime (even if *BootNext* functionality is added). ### Runtime capsule updates The firmware is deployed when the OS is running, but it is only written when the system has been restarted and the `fwupd*.efi` binary has been run. To achieve this fwupd sets up the EFI `BootNext` variable, creating the new boot entry if required. ## GUID Generation These devices use the UEFI GUID as provided in the ESRT. Additionally, for the system device the `main-system-firmware` internal flag is also added. For compatibility with Windows 10, the plugin also adds GUIDs of the form `UEFI\RES_{$(esrt)}`. ## Vendor ID Security The vendor ID is set from the BIOS vendor, for example `DMI:LENOVO` for all devices that are not marked as supporting Firmware Management Protocol. For FMP device no vendor ID is set. ## Custom EFI System Partition (ESP) Since version 1.1.0 fwupd will autodetect the ESP if it is mounted on `/boot/efi`, `/boot`, or `/efi`, and UDisks is available on the system. In other cases the mount point of the ESP needs to be manually specified using the option *EspLocation* in `/etc/fwupd/fwupd.conf`. Setting an invalid directory will disable the fwupd plugin. ## Quirk Use This plugin uses the following plugin-specific quirks: ### `Flags=cod-indexed-filename` Use a Capsule-on-Disk filename of `CapsuleUpdateFileXXXX.bin` rather than including the ESRT GUID. This alternative format may be needed for some early InsydeH2O firmwares. ## External Interface Access This plugin requires: * read/write access to the EFI system partition. * read access to `/sys/firmware/efi/esrt/` * read access to `/sys/firmware/efi/fw_platform_size` * read/write access to `/sys/firmware/efi/efivars` ## Version Considerations This plugin has been available since fwupd version `0.8.0` but was renamed to the current name in `1.5.5`. fwupd-1.9.16/plugins/uefi-capsule/fu-acpi-uefi.c000066400000000000000000000102741460375044200214270ustar00rootroot00000000000000/* * Copyright (C) 2023 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-acpi-uefi.h" #include "fu-uefi-struct.h" #define INSYDE_QUIRK_COD_WORKING 0x1 struct _FuAcpiUefi { FuAcpiTable parent_instance; guint32 insyde_cod_status; gboolean is_insyde; gchar *guid; }; G_DEFINE_TYPE(FuAcpiUefi, fu_acpi_uefi, FU_TYPE_ACPI_TABLE) static void fu_acpi_uefi_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuAcpiUefi *self = FU_ACPI_UEFI(firmware); /* FuAcpiTable->export */ FU_FIRMWARE_CLASS(fu_acpi_uefi_parent_class)->export(firmware, flags, bn); fu_xmlb_builder_insert_kb(bn, "is_insyde", self->is_insyde); fu_xmlb_builder_insert_kx(bn, "insyde_cod_status", self->insyde_cod_status); fu_xmlb_builder_insert_kv(bn, "guid", self->guid); } static gboolean fu_acpi_uefi_parse_insyde(FuAcpiUefi *self, const guint8 *buf, gsize bufsz, gsize offset, GError **error) { const gchar *needle = "$QUIRK"; gsize data_offset = 0; g_autoptr(GByteArray) st_qrk = NULL; if (!fu_memmem_safe(buf, bufsz, (const guint8 *)needle, strlen(needle), &data_offset, error)) { g_prefix_error(error, "$QUIRK not found"); return FALSE; } offset += data_offset; /* parse */ st_qrk = fu_struct_acpi_insyde_quirk_parse(buf, bufsz, offset, error); if (st_qrk == NULL) return FALSE; if (fu_struct_acpi_insyde_quirk_get_size(st_qrk) < st_qrk->len) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_READ, "$QUIRK structure is too small"); return FALSE; } self->insyde_cod_status = fu_struct_acpi_insyde_quirk_get_flags(st_qrk) & INSYDE_QUIRK_COD_WORKING; return TRUE; } static gboolean fu_acpi_uefi_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuAcpiUefi *self = FU_ACPI_UEFI(firmware); fwupd_guid_t guid = {0x0}; gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* FuAcpiTable->parse */ if (!FU_FIRMWARE_CLASS(fu_acpi_uefi_parent_class) ->parse(FU_FIRMWARE(self), fw, offset, FWUPD_INSTALL_FLAG_NONE, error)) return FALSE; /* check signature and read flags */ if (g_strcmp0(fu_firmware_get_id(FU_FIRMWARE(self)), "UEFI") != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Not a UEFI table, got %s", fu_firmware_get_id(FU_FIRMWARE(self))); return FALSE; } /* GUID for the table */ if (!fu_memcpy_safe((guint8 *)guid, sizeof(guid), 0x0, /* dst */ buf, bufsz, offset + 0x24, /* src */ sizeof(guid), error)) return FALSE; self->guid = fwupd_guid_to_string(&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN); /* parse Insyde-specific data */ self->is_insyde = (g_strcmp0(self->guid, FU_EFI_INSYDE_GUID) == 0); if (self->is_insyde) { g_autoptr(GError) error_local = NULL; if (!fu_acpi_uefi_parse_insyde(self, buf, bufsz, offset, &error_local)) g_debug("%s", error_local->message); } /* success */ return TRUE; } static void fu_acpi_uefi_init(FuAcpiUefi *self) { } static void fu_acpi_uefi_finalize(GObject *object) { FuAcpiUefi *self = FU_ACPI_UEFI(object); g_free(self->guid); G_OBJECT_CLASS(fu_acpi_uefi_parent_class)->finalize(object); } static void fu_acpi_uefi_class_init(FuAcpiUefiClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_acpi_uefi_finalize; klass_firmware->parse = fu_acpi_uefi_parse; klass_firmware->export = fu_acpi_uefi_export; } FuFirmware * fu_acpi_uefi_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_ACPI_UEFI, NULL)); } gboolean fu_acpi_uefi_cod_functional(FuAcpiUefi *self, GError **error) { g_return_val_if_fail(FU_IS_ACPI_UEFI(self), FALSE); if (!self->is_insyde || self->insyde_cod_status > 0) return TRUE; g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Capsule-on-Disk may have a firmware bug"); return FALSE; } gboolean fu_acpi_uefi_cod_indexed_filename(FuAcpiUefi *self) { g_return_val_if_fail(FU_IS_ACPI_UEFI(self), FALSE); if (self->is_insyde) return TRUE; return FALSE; } fwupd-1.9.16/plugins/uefi-capsule/fu-acpi-uefi.h000066400000000000000000000010131460375044200214230ustar00rootroot00000000000000/* * Copyright (C) 2023 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_ACPI_UEFI (fu_acpi_uefi_get_type()) G_DECLARE_FINAL_TYPE(FuAcpiUefi, fu_acpi_uefi, FU, ACPI_UEFI, FuAcpiTable) #define FU_EFI_INSYDE_GUID "9d4bf935-a674-4710-ba02-bf0aa1758c7b" FuFirmware * fu_acpi_uefi_new(void); gboolean fu_acpi_uefi_cod_functional(FuAcpiUefi *self, GError **error); gboolean fu_acpi_uefi_cod_indexed_filename(FuAcpiUefi *self); fwupd-1.9.16/plugins/uefi-capsule/fu-self-test.c000066400000000000000000000235761460375044200215040ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-context-private.h" #include "fu-uefi-backend.h" #include "fu-uefi-bgrt.h" #include "fu-uefi-cod-device.h" #include "fu-uefi-common.h" static void fu_uefi_bgrt_func(void) { gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(FuUefiBgrt) bgrt = fu_uefi_bgrt_new(); ret = fu_uefi_bgrt_setup(bgrt, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_true(fu_uefi_bgrt_get_supported(bgrt)); g_assert_cmpint(fu_uefi_bgrt_get_xoffset(bgrt), ==, 123); g_assert_cmpint(fu_uefi_bgrt_get_yoffset(bgrt), ==, 456); g_assert_cmpint(fu_uefi_bgrt_get_width(bgrt), ==, 54); g_assert_cmpint(fu_uefi_bgrt_get_height(bgrt), ==, 24); } static void fu_uefi_framebuffer_func(void) { gboolean ret; guint32 height = 0; guint32 width = 0; g_autoptr(GError) error = NULL; ret = fu_uefi_get_framebuffer_size(&width, &height, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(width, ==, 456); g_assert_cmpint(height, ==, 789); } static void fu_uefi_bitmap_func(void) { gboolean ret; gsize sz = 0; guint32 height = 0; guint32 width = 0; g_autofree gchar *fn = NULL; g_autofree gchar *buf = NULL; g_autoptr(GError) error = NULL; fn = g_test_build_filename(G_TEST_DIST, "tests", "test.bmp", NULL); ret = g_file_get_contents(fn, &buf, &sz, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_nonnull(buf); ret = fu_uefi_get_bitmap_size((guint8 *)buf, sz, &width, &height, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(width, ==, 54); g_assert_cmpint(height, ==, 24); } static GByteArray * fu_uefi_cod_device_build_efi_string(const gchar *text) { GByteArray *array = g_byte_array_new(); glong items_written = 0; g_autofree gunichar2 *test_utf16 = NULL; g_autoptr(GError) error = NULL; fu_byte_array_append_uint32(array, 0x0, G_LITTLE_ENDIAN); /* attrs */ test_utf16 = g_utf8_to_utf16(text, -1, NULL, &items_written, &error); g_assert_no_error(error); g_assert_nonnull(test_utf16); g_byte_array_append(array, (const guint8 *)test_utf16, items_written * 2); return array; } static GByteArray * fu_uefi_cod_device_build_efi_result(const gchar *guidstr) { GByteArray *array = g_byte_array_new(); fwupd_guid_t guid = {0x0}; gboolean ret; guint8 timestamp[16] = {0x0}; g_autoptr(GError) error = NULL; fu_byte_array_append_uint32(array, 0x0, G_LITTLE_ENDIAN); /* attrs */ fu_byte_array_append_uint32(array, 0x3A, G_LITTLE_ENDIAN); /* VariableTotalSize */ fu_byte_array_append_uint32(array, 0xFF, G_LITTLE_ENDIAN); /* Reserved */ ret = fwupd_guid_from_string(guidstr, &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, &error); g_assert_no_error(error); g_assert_true(ret); g_byte_array_append(array, guid, sizeof(guid)); /* CapsuleGuid */ g_byte_array_append(array, timestamp, sizeof(timestamp)); /* CapsuleProcessed */ fu_byte_array_append_uint32(array, FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_BATT, G_LITTLE_ENDIAN); /* Status */ return array; } static void fu_uefi_cod_device_write_efi_name(const gchar *name, GByteArray *array) { gboolean ret; g_autoptr(GError) error = NULL; g_autofree gchar *fn = g_strdup_printf("%s-%s", name, FU_EFIVAR_GUID_EFI_CAPSULE_REPORT); g_autofree gchar *path = NULL; path = g_test_build_filename(G_TEST_DIST, "tests", "efi", "efivars", fn, NULL); ret = g_file_set_contents(path, (gchar *)array->data, array->len, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_uefi_cod_device_func(void) { gboolean ret; g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error = NULL; g_autofree gchar *str = NULL; /* these are checked into git and so are not required */ if (g_getenv("FWUPD_UEFI_CAPSULE_RECREATE_COD_DATA") != NULL) { g_autoptr(GByteArray) cap0 = NULL; g_autoptr(GByteArray) cap1 = NULL; g_autoptr(GByteArray) last = NULL; g_autoptr(GByteArray) max = NULL; last = fu_uefi_cod_device_build_efi_string("Capsule0001"); max = fu_uefi_cod_device_build_efi_string("Capsule9999"); cap0 = fu_uefi_cod_device_build_efi_result("99999999-bf9d-540b-b92b-172ce31013c1"); cap1 = fu_uefi_cod_device_build_efi_result("cc4cbfa9-bf9d-540b-b92b-172ce31013c1"); fu_uefi_cod_device_write_efi_name("CapsuleLast", last); fu_uefi_cod_device_write_efi_name("CapsuleMax", max); fu_uefi_cod_device_write_efi_name("Capsule0000", cap0); fu_uefi_cod_device_write_efi_name("Capsule0001", cap1); } /* create device */ dev = g_object_new(FU_TYPE_UEFI_COD_DEVICE, "fw-class", "cc4cbfa9-bf9d-540b-b92b-172ce31013c1", NULL); ret = fu_device_get_results(dev, &error); g_assert_no_error(error); g_assert_true(ret); /* debug */ str = fu_device_to_string(dev); g_debug("%s", str); g_assert_cmpint(fu_device_get_update_state(dev), ==, FWUPD_UPDATE_STATE_FAILED_TRANSIENT); g_assert_cmpstr(fu_device_get_update_error(dev), ==, "failed to update to 0: error-pwr-evt-batt"); g_assert_cmpint(fu_uefi_device_get_status(FU_UEFI_DEVICE(dev)), ==, FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_BATT); } static void fu_uefi_plugin_func(void) { FuUefiDevice *dev; gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuBackend) backend = fu_uefi_backend_new(ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; #ifndef __linux__ g_test_skip("ESRT data is mocked only on Linux"); return; #endif /* do not save silo */ ret = fu_context_load_quirks(ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); /* add each device */ ret = fu_backend_coldplug(backend, progress, &error); g_assert_no_error(error); g_assert_true(ret); devices = fu_backend_get_devices(backend); g_assert_cmpint(devices->len, ==, 3); /* system firmware */ dev = g_ptr_array_index(devices, 0); ret = fu_device_probe(FU_DEVICE(dev), &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_uefi_device_get_kind(dev), ==, FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE); g_assert_cmpstr(fu_uefi_device_get_guid(dev), ==, "ddc0ee61-e7f0-4e7d-acc5-c070a398838e"); g_assert_cmpint(fu_uefi_device_get_hardware_instance(dev), ==, 0x0); g_assert_cmpint(fu_uefi_device_get_version(dev), ==, 65586); g_assert_cmpint(fu_uefi_device_get_version_lowest(dev), ==, 65582); g_assert_cmpint(fu_uefi_device_get_version_error(dev), ==, 18472960); g_assert_cmpint(fu_uefi_device_get_capsule_flags(dev), ==, 0xfe); g_assert_cmpint(fu_uefi_device_get_status(dev), ==, FU_UEFI_DEVICE_STATUS_ERROR_UNSUCCESSFUL); /* system firmware */ dev = g_ptr_array_index(devices, 1); ret = fu_device_probe(FU_DEVICE(dev), &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_uefi_device_get_kind(dev), ==, FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE); g_assert_cmpstr(fu_uefi_device_get_guid(dev), ==, "671d19d0-d43c-4852-98d9-1ce16f9967e4"); g_assert_cmpint(fu_uefi_device_get_version(dev), ==, 3090287969); g_assert_cmpint(fu_uefi_device_get_version_lowest(dev), ==, 1); g_assert_cmpint(fu_uefi_device_get_version_error(dev), ==, 0); g_assert_cmpint(fu_uefi_device_get_capsule_flags(dev), ==, 32784); g_assert_cmpint(fu_uefi_device_get_status(dev), ==, FU_UEFI_DEVICE_STATUS_SUCCESS); /* invalid */ dev = g_ptr_array_index(devices, 2); ret = fu_device_probe(FU_DEVICE(dev), &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_false(ret); } static void fu_uefi_update_info_func(void) { FuUefiDevice *dev; gboolean ret; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuBackend) backend = fu_uefi_backend_new(ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(FuUefiUpdateInfo) info = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; #ifndef __linux__ g_test_skip("ESRT data is mocked only on Linux"); return; #endif /* add each device */ ret = fu_backend_coldplug(backend, progress, &error); g_assert_no_error(error); g_assert_true(ret); devices = fu_backend_get_devices(backend); g_assert_cmpint(devices->len, ==, 3); dev = g_ptr_array_index(devices, 0); g_assert_cmpint(fu_uefi_device_get_kind(dev), ==, FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE); g_assert_cmpstr(fu_uefi_device_get_guid(dev), ==, "ddc0ee61-e7f0-4e7d-acc5-c070a398838e"); info = fu_uefi_device_load_update_info(dev, &error); g_assert_no_error(error); g_assert_nonnull(info); g_assert_cmpint(fu_firmware_get_version_raw(FU_FIRMWARE(info)), ==, 0x7); g_assert_cmpstr(fu_uefi_update_info_get_guid(info), ==, "697bd920-12cf-4da9-8385-996909bc6559"); g_assert_cmpint(fu_uefi_update_info_get_capsule_flags(info), ==, 0x50000); g_assert_cmpint(fu_uefi_update_info_get_hw_inst(info), ==, 0x0); g_assert_cmpint(fu_uefi_update_info_get_status(info), ==, FU_UEFI_UPDATE_INFO_STATUS_ATTEMPT_UPDATE); g_assert_cmpstr(fu_uefi_update_info_get_capsule_fn(info), ==, "/EFI/fedora/fw/fwupd-697bd920-12cf-4da9-8385-996909bc6559.cap"); } int main(int argc, char **argv) { g_autofree gchar *testdatadir = NULL; (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); (void)g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); (void)g_setenv("FWUPD_SYSFSDRIVERDIR", testdatadir, TRUE); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", testdatadir, TRUE); (void)g_setenv("FWUPD_UEFI_TEST", "1", TRUE); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); (void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE); /* tests go here */ g_test_add_func("/uefi/bgrt", fu_uefi_bgrt_func); g_test_add_func("/uefi/framebuffer", fu_uefi_framebuffer_func); g_test_add_func("/uefi/bitmap", fu_uefi_bitmap_func); g_test_add_func("/uefi/cod-device", fu_uefi_cod_device_func); g_test_add_func("/uefi/update-info", fu_uefi_update_info_func); g_test_add_func("/uefi/plugin", fu_uefi_plugin_func); return g_test_run(); } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-backend-freebsd.c000066400000000000000000000115251460375044200235120ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2021 3mdeb Embedded Systems Consulting * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #ifdef HAVE_FREEBSD_ESRT #include #include #endif #include #include "fu-uefi-backend-freebsd.h" #include "fu-uefi-common.h" #include "fu-uefi-device.h" struct _FuUefiBackendFreebsd { FuUefiBackend parent_instance; }; G_DEFINE_TYPE(FuUefiBackendFreebsd, fu_uefi_backend_freebsd, FU_TYPE_UEFI_BACKEND) #ifdef HAVE_FREEBSD_ESRT static FuUefiDevice * fu_uefi_backend_device_new(FuUefiBackend *self, const gchar *physical_id, struct efi_esrt_entry_v1 *entry, guint64 idx, GError **error) { g_autoptr(FuUefiDevice) dev = NULL; g_autofree gchar *backend_id = NULL; g_autofree gchar *fw_class = NULL; uint32_t status; uuid_to_string(&entry->fw_class, &fw_class, &status); if (status != uuid_s_ok) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "uuid_to_string error"); return NULL; } /* create object */ dev = g_object_new(fu_uefi_backend_get_device_gtype(self), "fw-class", fw_class, "capsule-flags", entry->capsule_flags, "kind", entry->fw_type, "fw-version", entry->fw_version, "last-attempt-status", entry->last_attempt_status, "last-attempt-version", entry->last_attempt_version, "fw-version-lowest", entry->lowest_supported_fw_version, "fmp-hardware-instance", (guint64)0x0, "version-format", FWUPD_VERSION_FORMAT_NUMBER, NULL); /* set ID */ backend_id = g_strdup_printf("ESRT/%u", (guint)idx); fu_device_set_backend_id(FU_DEVICE(dev), backend_id); fu_device_set_physical_id(FU_DEVICE(dev), physical_id); fu_device_set_logical_id(FU_DEVICE(dev), fw_class); return g_steal_pointer(&dev); } #endif static gboolean fu_uefi_backend_freebsd_setup(FuBackend *backend, GError **error) { g_autofree gchar *efi_ver = fu_kenv_get_string("efi-version", error); if (efi_ver == NULL) { g_prefix_error(error, "System does not support UEFI mode, no efi-version kenv: "); return FALSE; } if (fu_version_compare(efi_ver, "2.0.0.0", FWUPD_VERSION_FORMAT_QUAD) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "System does not support UEFI mode, got efi-version of %s", efi_ver); return FALSE; } return TRUE; } static gboolean fu_uefi_backend_freebsd_coldplug(FuBackend *backend, GError **error) { #ifdef HAVE_FREEBSD_ESRT FuUefiBackend *self = FU_UEFI_BACKEND(backend); const gchar *esrt_dev = "/dev/efi"; struct efi_get_table_ioc table = {.uuid = EFI_TABLE_ESRT}; gint efi_fd; struct efi_esrt_entry_v1 *entries; g_autofree struct efi_esrt_table *esrt = NULL; efi_fd = g_open(esrt_dev, O_RDONLY, 0); if (efi_fd < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot open %s", esrt_dev); return FALSE; } if (ioctl(efi_fd, EFIIOC_GET_TABLE, &table) == -1) { g_close(efi_fd, NULL); g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Cannot determine size of ESRT table"); return FALSE; } esrt = g_malloc(table.table_len); if (esrt == NULL) { g_close(efi_fd, NULL); g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Cannot allocate memory for ESRT table"); return FALSE; } table.buf = esrt; table.buf_len = table.table_len; if (ioctl(efi_fd, EFIIOC_GET_TABLE, &table) == -1) { g_close(efi_fd, NULL); g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Cannot fill ESRT table"); return FALSE; } entries = (struct efi_esrt_entry_v1 *)esrt->entries; for (guint i = 0; i < esrt->fw_resource_count; i++) { g_autoptr(FuUefiDevice) dev = NULL; dev = fu_uefi_backend_device_new(self, esrt_dev, &entries[i], i, error); if (dev == NULL) return FALSE; fu_backend_device_added(backend, FU_DEVICE(dev)); } /* success */ return TRUE; #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ESRT access API is missing from the kernel"); return FALSE; #endif } void fu_uefi_backend_freebsd_set_device_gtype(FuBackend *backend, GType device_gtype) { } static void fu_uefi_backend_freebsd_init(FuUefiBackendFreebsd *self) { } static void fu_uefi_backend_freebsd_class_init(FuUefiBackendFreebsdClass *klass) { FuBackendClass *klass_backend = FU_BACKEND_CLASS(klass); klass_backend->setup = fu_uefi_backend_freebsd_setup; klass_backend->coldplug = fu_uefi_backend_freebsd_coldplug; } FuBackend * fu_uefi_backend_new(FuContext *ctx) { return g_object_new(FU_TYPE_UEFI_BACKEND_FREEBSD, "name", "uefi", "context", ctx, NULL); } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-backend-freebsd.h000066400000000000000000000006051460375044200235140ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-uefi-backend.h" #define FU_TYPE_UEFI_BACKEND_FREEBSD (fu_uefi_backend_freebsd_get_type()) G_DECLARE_FINAL_TYPE(FuUefiBackendFreebsd, fu_uefi_backend_freebsd, FU, UEFI_BACKEND_FREEBSD, FuUefiBackend) fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-backend-linux.c000066400000000000000000000156201460375044200232370ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-uefi-backend-linux.h" #include "fu-uefi-cod-device.h" #include "fu-uefi-common.h" #include "fu-uefi-nvram-device.h" struct _FuUefiBackendLinux { FuUefiBackend parent_instance; gboolean use_rt_set_variable; }; G_DEFINE_TYPE(FuUefiBackendLinux, fu_uefi_backend_linux, FU_TYPE_UEFI_BACKEND) /* yes, unsized uint_t */ static guint fu_uefi_backend_linux_read(const gchar *path, const gchar *filename) { return fu_uefi_read_file_as_uint64(path, filename); } static FuUefiDevice * fu_uefi_backend_linux_device_new(FuUefiBackendLinux *self, const gchar *physical_id, const gchar *path) { g_autoptr(FuUefiDevice) dev = NULL; g_autofree gchar *fw_class = NULL; g_autofree gchar *fw_class_fn = NULL; g_return_val_if_fail(path != NULL, NULL); /* read values from sysfs */ fw_class_fn = g_build_filename(path, "fw_class", NULL); if (g_file_get_contents(fw_class_fn, &fw_class, NULL, NULL)) g_strdelimit(fw_class, "\n", '\0'); /* Create object, assuming a verfmt of NUMBER unless told otherwise by * a quirk entry or metadata. * * The hardware instance is not in the ESRT table and we should really * write the EFI stub to query with FMP -- but we still have not ever * seen a PCIe device with FMP support... */ dev = g_object_new(fu_uefi_backend_get_device_gtype(FU_UEFI_BACKEND(self)), "fw-class", fw_class, "capsule-flags", fu_uefi_backend_linux_read(path, "capsule_flags"), "kind", fu_uefi_backend_linux_read(path, "fw_type"), "fw-version", fu_uefi_backend_linux_read(path, "fw_version"), "last-attempt-status", fu_uefi_backend_linux_read(path, "last_attempt_status"), "last-attempt-version", fu_uefi_backend_linux_read(path, "last_attempt_version"), "fw-version-lowest", fu_uefi_backend_linux_read(path, "lowest_supported_fw_version"), "fmp-hardware-instance", (guint64)0x0, "version-format", FWUPD_VERSION_FORMAT_NUMBER, NULL); /* u-boot for instance */ if (!self->use_rt_set_variable) fu_device_add_private_flag(FU_DEVICE(dev), FU_UEFI_DEVICE_FLAG_NO_RT_SET_VARIABLE); /* set ID */ fu_device_set_backend_id(FU_DEVICE(dev), path); fu_device_set_physical_id(FU_DEVICE(dev), physical_id); fu_device_set_logical_id(FU_DEVICE(dev), fw_class); return g_steal_pointer(&dev); } static gboolean fu_uefi_backend_linux_check_efivarfs(FuUefiBackendLinux *self, GError **error) { g_autofree gchar *sysfsfwdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_FW); g_autofree gchar *sysfsefivardir = g_build_filename(sysfsfwdir, "efi", "efivars", NULL); g_autoptr(GUnixMountEntry) mount = g_unix_mount_at(sysfsefivardir, NULL); /* in the self tests */ if (g_getenv("FWUPD_UEFI_TEST") != NULL) return TRUE; if (mount == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "%s was not mounted", sysfsefivardir); return FALSE; } if (g_unix_mount_is_readonly(mount)) { GType gtype = fu_uefi_backend_get_device_gtype(FU_UEFI_BACKEND(self)); if (gtype != FU_TYPE_UEFI_COD_DEVICE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "%s is read only and no CoD", sysfsefivardir); return FALSE; } /* this is fine! just do not use SetVariable... */ self->use_rt_set_variable = FALSE; } return TRUE; } static gboolean fu_uefi_backend_linux_coldplug(FuBackend *backend, FuProgress *progress, GError **error) { FuUefiBackendLinux *self = FU_UEFI_BACKEND_LINUX(backend); const gchar *fn; g_autofree gchar *esrt_entries = NULL; g_autofree gchar *esrt_path = NULL; g_autofree gchar *sysfsfwdir = NULL; g_autoptr(GDir) dir = NULL; /* make sure that efivarfs is suitable */ if (!fu_uefi_backend_linux_check_efivarfs(self, error)) return FALSE; /* get the directory of ESRT entries */ sysfsfwdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_FW); esrt_path = g_build_filename(sysfsfwdir, "efi", "esrt", NULL); esrt_entries = g_build_filename(esrt_path, "entries", NULL); dir = g_dir_open(esrt_entries, 0, error); if (dir == NULL) return FALSE; /* add each device */ while ((fn = g_dir_read_name(dir)) != NULL) { g_autofree gchar *path = g_build_filename(esrt_entries, fn, NULL); g_autoptr(FuUefiDevice) dev = fu_uefi_backend_linux_device_new(self, esrt_path, path); fu_backend_device_added(backend, FU_DEVICE(dev)); } /* success */ return TRUE; } static gboolean fu_uefi_backend_linux_check_smbios_enabled(FuContext *ctx, GError **error) { GBytes *bios_blob; const guint8 *data; gsize sz; g_autoptr(GPtrArray) bios_tables = fu_context_get_smbios_data(ctx, 0, NULL); if (bios_tables == NULL) { const gchar *tmp = g_getenv("FWUPD_DELL_FAKE_SMBIOS"); if (tmp != NULL) return TRUE; g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "SMBIOS not supported"); return FALSE; } bios_blob = g_ptr_array_index(bios_tables, 0); data = g_bytes_get_data(bios_blob, &sz); if (sz < 0x14) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "offset bigger than size %" G_GSIZE_FORMAT, sz); return FALSE; } if (data[1] < 0x14) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "SMBIOS 2.3 not supported"); return FALSE; } if (!(data[0x13] & (1 << 3))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "System does not support UEFI mode"); return FALSE; } return TRUE; } static gboolean fu_uefi_backend_linux_setup(FuBackend *backend, FuProgress *progress, GError **error) { g_autoptr(GError) error_local = NULL; /* using a pre-cooked SMBIOS */ if (g_getenv("FWUPD_SYSFSFWDIR") != NULL) return TRUE; /* check SMBIOS for 'UEFI Specification is supported' */ if (!fu_uefi_backend_linux_check_smbios_enabled(fu_backend_get_context(backend), &error_local)) { g_autofree gchar *fw = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_FW); g_autofree gchar *fn = g_build_filename(fw, "efi", NULL); if (g_file_test(fn, G_FILE_TEST_EXISTS)) { g_warning("SMBIOS BIOS Characteristics Extension Byte 2 is invalid -- " "UEFI Specification is unsupported, but %s exists: %s", fn, error_local->message); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } return TRUE; } static void fu_uefi_backend_linux_init(FuUefiBackendLinux *self) { self->use_rt_set_variable = TRUE; } static void fu_uefi_backend_linux_class_init(FuUefiBackendLinuxClass *klass) { FuBackendClass *klass_backend = FU_BACKEND_CLASS(klass); klass_backend->coldplug = fu_uefi_backend_linux_coldplug; klass_backend->setup = fu_uefi_backend_linux_setup; } FuBackend * fu_uefi_backend_new(FuContext *ctx) { return g_object_new(FU_TYPE_UEFI_BACKEND_LINUX, "name", "uefi", "context", ctx, NULL); } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-backend-linux.h000066400000000000000000000005731460375044200232450ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-uefi-backend.h" #define FU_TYPE_UEFI_BACKEND_LINUX (fu_uefi_backend_linux_get_type()) G_DECLARE_FINAL_TYPE(FuUefiBackendLinux, fu_uefi_backend_linux, FU, UEFI_BACKEND_LINUX, FuUefiBackend) fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-backend.c000066400000000000000000000047041460375044200221030ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uefi-backend.h" #include "fu-uefi-nvram-device.h" typedef struct { GType device_gtype; } FuUefiBackendPrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuUefiBackend, fu_uefi_backend, FU_TYPE_BACKEND) #define GET_PRIVATE(o) (fu_uefi_backend_get_instance_private(o)) void fu_uefi_backend_set_device_gtype(FuUefiBackend *self, GType device_gtype) { FuUefiBackendPrivate *priv = GET_PRIVATE(self); priv->device_gtype = device_gtype; } GType fu_uefi_backend_get_device_gtype(FuUefiBackend *self) { FuUefiBackendPrivate *priv = GET_PRIVATE(self); return priv->device_gtype; } static void fu_uefi_backend_to_string(FuBackend *backend, guint idt, GString *str) { FuUefiBackend *self = FU_UEFI_BACKEND(backend); FuUefiBackendPrivate *priv = GET_PRIVATE(self); fu_string_append(str, idt, "DeviceGType", g_type_name(priv->device_gtype)); } /* create virtual object not backed by an ESRT entry */ FuUefiDevice * fu_uefi_backend_device_new_from_dev(FuUefiBackend *self, FuDevice *dev) { FuUefiDevice *device; FuUefiBackendPrivate *priv = GET_PRIVATE(self); const gchar *tmp; g_return_val_if_fail(fu_device_get_guid_default(dev) != NULL, NULL); tmp = fu_device_get_metadata(dev, FU_DEVICE_METADATA_UEFI_DEVICE_KIND); device = g_object_new(priv->device_gtype, "fw-class", fu_device_get_guid_default(dev), "kind", fu_uefi_device_kind_from_string(tmp), "capsule-flags", fu_device_get_metadata_integer(dev, FU_DEVICE_METADATA_UEFI_CAPSULE_FLAGS), "fw-version", fu_device_get_metadata_integer(dev, FU_DEVICE_METADATA_UEFI_FW_VERSION), NULL); fu_device_incorporate(FU_DEVICE(device), dev); return device; } FuUefiDevice * fu_uefi_backend_device_new_from_guid(FuUefiBackend *self, const gchar *guid) { FuUefiDevice *device; FuUefiBackendPrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(guid != NULL, NULL); device = g_object_new(priv->device_gtype, "fw-class", guid, NULL); fu_device_set_version_format(FU_DEVICE(device), FWUPD_VERSION_FORMAT_NUMBER); return device; } static void fu_uefi_backend_init(FuUefiBackend *self) { FuUefiBackendPrivate *priv = GET_PRIVATE(self); priv->device_gtype = FU_TYPE_UEFI_NVRAM_DEVICE; } static void fu_uefi_backend_class_init(FuUefiBackendClass *klass) { FuBackendClass *klass_backend = FU_BACKEND_CLASS(klass); klass_backend->to_string = fu_uefi_backend_to_string; } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-backend.h000066400000000000000000000013651460375044200221100ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-uefi-device.h" #define FU_TYPE_UEFI_BACKEND (fu_uefi_backend_get_type()) G_DECLARE_DERIVABLE_TYPE(FuUefiBackend, fu_uefi_backend, FU, UEFI_BACKEND, FuBackend) struct _FuUefiBackendClass { FuBackendClass parent_class; }; FuBackend * fu_uefi_backend_new(FuContext *ctx); void fu_uefi_backend_set_device_gtype(FuUefiBackend *self, GType device_gtype); GType fu_uefi_backend_get_device_gtype(FuUefiBackend *self); FuUefiDevice * fu_uefi_backend_device_new_from_guid(FuUefiBackend *self, const gchar *guid); FuUefiDevice * fu_uefi_backend_device_new_from_dev(FuUefiBackend *self, FuDevice *dev); fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-bgrt.c000066400000000000000000000055261460375044200214550ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uefi-bgrt.h" #include "fu-uefi-common.h" struct _FuUefiBgrt { GObject parent_instance; guint32 xoffset; guint32 yoffset; guint32 width; guint32 height; }; G_DEFINE_TYPE(FuUefiBgrt, fu_uefi_bgrt, G_TYPE_OBJECT) gboolean fu_uefi_bgrt_setup(FuUefiBgrt *self, GError **error) { gsize sz = 0; guint64 type; guint64 version; g_autofree gchar *bgrtdir = NULL; g_autofree gchar *data = NULL; g_autofree gchar *imagefn = NULL; g_autofree gchar *sysfsfwdir = NULL; g_return_val_if_fail(FU_IS_UEFI_BGRT(self), FALSE); sysfsfwdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_FW); bgrtdir = g_build_filename(sysfsfwdir, "acpi", "bgrt", NULL); if (!g_file_test(bgrtdir, G_FILE_TEST_EXISTS)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "BGRT is not supported"); return FALSE; } type = fu_uefi_read_file_as_uint64(bgrtdir, "type"); if (type != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "BGRT type was %" G_GUINT64_FORMAT, type); return FALSE; } version = fu_uefi_read_file_as_uint64(bgrtdir, "version"); if (version != 1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "BGRT version was %" G_GUINT64_FORMAT, version); return FALSE; } /* load image */ self->xoffset = fu_uefi_read_file_as_uint64(bgrtdir, "xoffset"); self->yoffset = fu_uefi_read_file_as_uint64(bgrtdir, "yoffset"); imagefn = g_build_filename(bgrtdir, "image", NULL); if (!g_file_get_contents(imagefn, &data, &sz, error)) { g_prefix_error(error, "failed to load BGRT image: "); return FALSE; } if (!fu_uefi_get_bitmap_size((guint8 *)data, sz, &self->width, &self->height, error)) { g_prefix_error(error, "BGRT image invalid: "); return FALSE; } /* success */ return TRUE; } gboolean fu_uefi_bgrt_get_supported(FuUefiBgrt *self) { g_return_val_if_fail(FU_IS_UEFI_BGRT(self), FALSE); if (self->width == 0 || self->height == 0) return FALSE; return TRUE; } guint32 fu_uefi_bgrt_get_xoffset(FuUefiBgrt *self) { g_return_val_if_fail(FU_IS_UEFI_BGRT(self), 0); return self->xoffset; } guint32 fu_uefi_bgrt_get_yoffset(FuUefiBgrt *self) { g_return_val_if_fail(FU_IS_UEFI_BGRT(self), 0); return self->yoffset; } guint32 fu_uefi_bgrt_get_width(FuUefiBgrt *self) { g_return_val_if_fail(FU_IS_UEFI_BGRT(self), 0); return self->width; } guint32 fu_uefi_bgrt_get_height(FuUefiBgrt *self) { g_return_val_if_fail(FU_IS_UEFI_BGRT(self), 0); return self->height; } static void fu_uefi_bgrt_class_init(FuUefiBgrtClass *klass) { } static void fu_uefi_bgrt_init(FuUefiBgrt *self) { } FuUefiBgrt * fu_uefi_bgrt_new(void) { FuUefiBgrt *self; self = g_object_new(FU_TYPE_UEFI_BGRT, NULL); return FU_UEFI_BGRT(self); } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-bgrt.h000066400000000000000000000011701460375044200214510ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_UEFI_BGRT (fu_uefi_bgrt_get_type()) G_DECLARE_FINAL_TYPE(FuUefiBgrt, fu_uefi_bgrt, FU, UEFI_BGRT, GObject) FuUefiBgrt * fu_uefi_bgrt_new(void); gboolean fu_uefi_bgrt_setup(FuUefiBgrt *self, GError **error); gboolean fu_uefi_bgrt_get_supported(FuUefiBgrt *self); guint32 fu_uefi_bgrt_get_xoffset(FuUefiBgrt *self); guint32 fu_uefi_bgrt_get_yoffset(FuUefiBgrt *self); guint32 fu_uefi_bgrt_get_width(FuUefiBgrt *self); guint32 fu_uefi_bgrt_get_height(FuUefiBgrt *self); fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-bootmgr.c000066400000000000000000000342001460375044200221570ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-uefi-bootmgr.h" #include "fu-uefi-common.h" #include "fu-uefi-device.h" static gboolean fu_uefi_bootmgr_add_to_boot_order(guint16 boot_entry, GError **error) { gsize boot_order_size = 0; guint i = 0; guint32 attr = 0; g_autofree guint16 *boot_order = NULL; g_autofree guint16 *new_boot_order = NULL; /* get the current boot order */ if (!fu_efivar_get_data(FU_EFIVAR_GUID_EFI_GLOBAL, "BootOrder", (guint8 **)&boot_order, &boot_order_size, &attr, error)) return FALSE; /* already set next */ for (i = 0; i < boot_order_size / sizeof(guint16); i++) { guint16 val = boot_order[i]; if (val == boot_entry) return TRUE; } /* add the new boot index to the end of the list */ new_boot_order = g_malloc0(boot_order_size + sizeof(guint16)); if (boot_order_size != 0) memcpy(new_boot_order, boot_order, boot_order_size); attr |= FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS; i = boot_order_size / sizeof(guint16); new_boot_order[i] = boot_entry; boot_order_size += sizeof(guint16); if (!fu_efivar_set_data(FU_EFIVAR_GUID_EFI_GLOBAL, "BootOrder", (guint8 *)new_boot_order, boot_order_size, attr, error)) { g_prefix_error(error, "could not set BootOrder(%u): ", boot_entry); return FALSE; } /* success */ return TRUE; } static guint16 fu_uefi_bootmgr_parse_name(const gchar *name) { gint rc; gint scanned = 0; guint16 entry = 0; /* BootXXXX */ rc = sscanf(name, "Boot%hX%n", &entry, &scanned); if (rc != 1 || scanned != 8) return G_MAXUINT16; return entry; } gboolean fu_uefi_bootmgr_verify_fwupd(GError **error) { g_autoptr(GPtrArray) names = NULL; names = fu_efivar_get_names(FU_EFIVAR_GUID_EFI_GLOBAL, error); if (names == NULL) return FALSE; for (guint i = 0; i < names->len; i++) { const gchar *desc; const gchar *name = g_ptr_array_index(names, i); guint16 entry; g_autoptr(FuEfiLoadOption) loadopt = fu_efi_load_option_new(); g_autoptr(GBytes) loadopt_blob = NULL; g_autoptr(GError) error_local = NULL; /* not BootXXXX */ entry = fu_uefi_bootmgr_parse_name(name); if (entry == G_MAXUINT16) continue; /* parse key */ loadopt_blob = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_EFI_GLOBAL, name, NULL, &error_local); if (loadopt_blob == NULL) { g_debug("failed to get data for name %s: %s", name, error_local->message); continue; } if (!fu_firmware_parse(FU_FIRMWARE(loadopt), loadopt_blob, FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_debug("%s -> load option was invalid: %s", name, error_local->message); continue; } desc = fu_firmware_get_id(FU_FIRMWARE(loadopt)); if (g_strcmp0(desc, "Linux Firmware Updater") == 0 || g_strcmp0(desc, "Linux-Firmware-Updater") == 0) { g_debug("found %s at Boot%04X", desc, entry); return TRUE; } } /* did not find */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "no 'Linux Firmware Updater' entry found"); return FALSE; } static gboolean fu_uefi_setup_bootnext_with_loadopt(FuEfiLoadOption *loadopt, FuUefiBootmgrFlags flags, GError **error) { const gchar *name = NULL; guint32 attr; guint16 boot_next = G_MAXUINT16; guint8 boot_nextbuf[2] = {0}; g_autofree guint8 *set_entries = g_malloc0(G_MAXUINT16); g_autoptr(GBytes) loadopt_blob = NULL; g_autoptr(GBytes) loadopt_blob_old = NULL; g_autoptr(GPtrArray) names = NULL; /* write */ loadopt_blob = fu_firmware_write(FU_FIRMWARE(loadopt), error); if (loadopt_blob == NULL) return FALSE; /* find existing BootXXXX entry for fwupd */ names = fu_efivar_get_names(FU_EFIVAR_GUID_EFI_GLOBAL, error); if (names == NULL) return FALSE; for (guint i = 0; i < names->len; i++) { const gchar *desc; guint16 entry = 0; g_autoptr(GBytes) loadopt_blob_tmp = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(FuEfiLoadOption) loadopt_tmp = fu_efi_load_option_new(); /* not BootXXXX */ name = g_ptr_array_index(names, i); entry = fu_uefi_bootmgr_parse_name(name); if (entry == G_MAXUINT16) continue; /* mark this as used */ set_entries[entry] = 1; loadopt_blob_tmp = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_EFI_GLOBAL, name, &attr, &error_local); if (loadopt_blob_tmp == NULL) { g_debug("failed to get data for name %s: %s", name, error_local->message); continue; } if (!fu_firmware_parse(FU_FIRMWARE(loadopt_tmp), loadopt_blob_tmp, FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_debug("%s -> load option was invalid: %s", name, error_local->message); continue; } desc = fu_firmware_get_id(FU_FIRMWARE(loadopt_tmp)); if (g_strcmp0(desc, "Linux Firmware Updater") != 0 && g_strcmp0(desc, "Linux-Firmware-Updater") != 0) { g_debug("%s -> '%s' : does not match", name, desc); continue; } loadopt_blob_old = g_steal_pointer(&loadopt_blob_tmp); boot_next = entry; break; } /* already exists */ if (loadopt_blob_old != NULL) { /* is different than before */ if (!fu_bytes_compare(loadopt_blob, loadopt_blob_old, NULL)) { g_debug("%s: updating existing boot entry", name); if (!fu_efivar_set_data_bytes(FU_EFIVAR_GUID_EFI_GLOBAL, name, loadopt_blob, attr, error)) { g_prefix_error(error, "could not set boot variable active: "); return FALSE; } } else { g_debug("%s: re-using existing boot entry", name); } /* create a new one */ } else { g_autofree gchar *boot_next_name = NULL; for (guint16 value = 0; value < G_MAXUINT16; value++) { if (set_entries[value]) continue; boot_next = value; break; } if (boot_next == G_MAXUINT16) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "no free boot variables (tried %x)", boot_next); return FALSE; } boot_next_name = g_strdup_printf("Boot%04X", (guint)boot_next); g_debug("%s -> creating new entry", boot_next_name); if (!fu_efivar_set_data_bytes(FU_EFIVAR_GUID_EFI_GLOBAL, boot_next_name, loadopt_blob, FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS, error)) { g_prefix_error(error, "could not set boot variable %s: ", boot_next_name); return FALSE; } } /* TODO: conditionalize this on the UEFI version? */ if (flags & FU_UEFI_BOOTMGR_FLAG_MODIFY_BOOTORDER) { if (!fu_uefi_bootmgr_add_to_boot_order(boot_next, error)) return FALSE; } /* set the boot next */ fu_memwrite_uint16(boot_nextbuf, boot_next, G_LITTLE_ENDIAN); if (!fu_efivar_set_data(FU_EFIVAR_GUID_EFI_GLOBAL, "BootNext", boot_nextbuf, sizeof(boot_nextbuf), FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS, error)) { g_prefix_error(error, "could not set BootNext(%u): ", boot_next); return FALSE; } return TRUE; } static gboolean fu_uefi_bootmgr_shim_is_safe(const gchar *source_shim, GError **error) { g_autoptr(GBytes) current_sbatlevel_bytes = NULL; g_autoptr(FuFirmware) shim = fu_pefile_firmware_new(); g_autoptr(FuFirmware) sbatlevel_section = NULL; g_autoptr(FuFirmware) previous_sbatlevel = NULL; g_autoptr(FuFirmware) current_sbatlevel = fu_csv_firmware_new(); g_autoptr(GError) error_local = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GPtrArray) shim_entries = NULL; blob = fu_bytes_get_contents(source_shim, error); if (blob == NULL) return FALSE; if (!fu_firmware_parse(shim, blob, FWUPD_INSTALL_FLAG_NONE, error)) { g_prefix_error(error, "failed to load %s: ", source_shim); return FALSE; } sbatlevel_section = fu_firmware_get_image_by_id(shim, ".sbatlevel", &error_local); if (sbatlevel_section == NULL) { g_debug("no sbatlevel section was found"); /* if there is no .sbatlevel section, then it will not update, it should be safe */ if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) return TRUE; g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* not safe if variable is not set but new shim would set it */ current_sbatlevel_bytes = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_SHIM, "SbatLevelRT", NULL, error); if (current_sbatlevel_bytes == NULL) return FALSE; fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(current_sbatlevel), "$id"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(current_sbatlevel), "component_generation"); fu_csv_firmware_add_column_id(FU_CSV_FIRMWARE(current_sbatlevel), "date_stamp"); if (!fu_firmware_parse(current_sbatlevel, current_sbatlevel_bytes, FWUPD_INSTALL_FLAG_NONE, error)) { g_prefix_error(error, "failed to load SbatLevelRT: "); return FALSE; } /* * For every new shim entry, we need a matching entry in the * current sbatlevel. That is the entry of the shim is not * newer than current sbatlevel. * * The opposite way might work (for example shim's latest * sbatlevel matches) or not (shim is too old), but it will * not brick the current OS. */ previous_sbatlevel = fu_firmware_get_image_by_id(sbatlevel_section, "previous", error); if (previous_sbatlevel == NULL) return FALSE; shim_entries = fu_firmware_get_images(previous_sbatlevel); for (guint idx = 0; idx < shim_entries->len; idx++) { FuCsvEntry *current_entry = NULL; FuCsvEntry *shim_entry = g_ptr_array_index(shim_entries, idx); const gchar *entry_id = fu_firmware_get_id(FU_FIRMWARE(shim_entry)); guint64 current_generation = 0; guint64 shim_generation = 0; current_entry = FU_CSV_ENTRY(fu_firmware_get_image_by_id(FU_FIRMWARE(current_sbatlevel), entry_id, &error_local)); if (current_entry == NULL) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "shim sbatlevel for %s has a bricking update for entry %s " "(missing entry in current UEFI variable)", source_shim, entry_id); } else { g_prefix_error(&error_local, "while looking for entry in current sbatlevel: "); g_propagate_error(error, g_steal_pointer(&error_local)); } return FALSE; } if (!fu_csv_entry_get_value_by_column_id_uint64(shim_entry, "component_generation", &shim_generation, error)) { g_prefix_error(error, "sbatlevel entry %s for shim %s: ", entry_id, source_shim); return FALSE; } if (!fu_csv_entry_get_value_by_column_id_uint64(current_entry, "component_generation", ¤t_generation, error)) { g_prefix_error(error, "entry %s from current sbatlevel: ", entry_id); return FALSE; } if (current_generation < shim_generation) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "sbatlevel for shim %s has a bricking update for entry " "%s (newer generation)", source_shim, entry_id); return FALSE; } } /* success */ return TRUE; } gboolean fu_uefi_bootmgr_bootnext(FuVolume *esp, const gchar *description, FuUefiBootmgrFlags flags, GError **error) { const gchar *filepath = NULL; gboolean use_fwup_path = TRUE; gboolean secure_boot = FALSE; g_autofree gchar *shim_app = NULL; g_autofree gchar *shim_cpy = NULL; g_autofree gchar *source_app = NULL; g_autofree gchar *source_shim = NULL; g_autofree gchar *target_app = NULL; g_autoptr(FuEfiDevicePathList) dp_buf = NULL; g_autoptr(FuEfiLoadOption) loadopt = fu_efi_load_option_new(); /* skip for self tests */ if (g_getenv("FWUPD_UEFI_TEST") != NULL) return TRUE; /* if secure boot was turned on this might need to be installed separately */ source_app = fu_uefi_get_built_app_path("fwupd", error); if (source_app == NULL) return FALSE; /* test if we should use shim */ secure_boot = fu_efivar_secure_boot_enabled(NULL); if (secure_boot) { shim_app = fu_uefi_get_esp_app_path("shim", error); if (shim_app == NULL) return FALSE; /* copy in an updated shim if we have one */ source_shim = fu_uefi_get_built_app_path("shim", NULL); if (source_shim != NULL) { if (!fu_uefi_esp_target_verify(source_shim, esp, shim_app)) { if (!fu_uefi_bootmgr_shim_is_safe(source_shim, error)) return FALSE; if (!fu_uefi_esp_target_copy(source_shim, esp, shim_app, error)) return FALSE; } } if (fu_uefi_esp_target_exists(esp, shim_app)) { /* use a custom copy of shim for firmware updates */ if (flags & FU_UEFI_BOOTMGR_FLAG_USE_SHIM_UNIQUE) { shim_cpy = fu_uefi_get_esp_app_path("shimfwupd", error); if (shim_cpy == NULL) return FALSE; if (!fu_uefi_esp_target_verify(shim_app, esp, shim_cpy)) { if (!fu_uefi_esp_target_copy(shim_app, esp, shim_cpy, error)) return FALSE; } filepath = shim_cpy; } else { filepath = shim_app; } use_fwup_path = FALSE; } else if ((flags & FU_UEFI_BOOTMGR_FLAG_USE_SHIM_FOR_SB) > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_BROKEN_SYSTEM, "Secure boot is enabled, but shim isn't installed to " "%s", shim_app); return FALSE; } } /* test if correct asset in place */ target_app = fu_uefi_get_esp_app_path("fwupd", error); if (target_app == NULL) return FALSE; if (!fu_uefi_esp_target_verify(source_app, esp, target_app)) { if (!fu_uefi_esp_target_copy(source_app, esp, target_app, error)) return FALSE; } /* no shim, so use this directly */ if (use_fwup_path) filepath = target_app; /* add the fwupdx64.efi ESP path as the shim loadopt data */ if (!use_fwup_path) { g_autofree gchar *fwup_fs_basename = g_path_get_basename(target_app); if (!fu_efi_load_option_set_optional_path(loadopt, fwup_fs_basename, error)) return FALSE; } /* add DEVICE_PATH */ dp_buf = fu_uefi_device_build_dp_buf(esp, filepath, error); if (dp_buf == NULL) return FALSE; fu_firmware_add_image(FU_FIRMWARE(loadopt), FU_FIRMWARE(dp_buf)); fu_firmware_set_id(FU_FIRMWARE(loadopt), description); /* save as BootNext */ return fu_uefi_setup_bootnext_with_loadopt(loadopt, flags, error); } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-bootmgr.h000066400000000000000000000011651460375044200221700ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef enum { FU_UEFI_BOOTMGR_FLAG_NONE = 0, FU_UEFI_BOOTMGR_FLAG_USE_SHIM_FOR_SB = 1 << 0, FU_UEFI_BOOTMGR_FLAG_USE_SHIM_UNIQUE = 1 << 1, FU_UEFI_BOOTMGR_FLAG_MODIFY_BOOTORDER = 1 << 2, FU_UEFI_BOOTMGR_FLAG_LAST } FuUefiBootmgrFlags; gboolean fu_uefi_bootmgr_verify_fwupd(GError **error); gboolean fu_uefi_bootmgr_bootnext(FuVolume *esp, const gchar *description, FuUefiBootmgrFlags flags, GError **error); fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-capsule-plugin.c000066400000000000000000001334251460375044200234470ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-acpi-uefi.h" #include "fu-uefi-backend.h" #include "fu-uefi-bgrt.h" #include "fu-uefi-bootmgr.h" #include "fu-uefi-capsule-plugin.h" #include "fu-uefi-cod-device.h" #include "fu-uefi-common.h" #include "fu-uefi-grub-device.h" #include "fu-uefi-struct.h" #include "fu-uefi-update-info.h" struct _FuUefiCapsulePlugin { FuPlugin parent_instance; FuUefiBgrt *bgrt; FuFirmware *acpi_uefi; /* optional */ FuVolume *esp; FuBackend *backend; GFile *fwupd_efi_file; GFileMonitor *fwupd_efi_monitor; }; G_DEFINE_TYPE(FuUefiCapsulePlugin, fu_uefi_capsule_plugin, FU_TYPE_PLUGIN) static void fu_uefi_capsule_plugin_to_string(FuPlugin *plugin, guint idt, GString *str) { FuUefiCapsulePlugin *self = FU_UEFI_CAPSULE_PLUGIN(plugin); fu_backend_add_string(self->backend, idt, str); if (self->bgrt != NULL) { fu_string_append_kb(str, idt, "BgrtSupported", fu_uefi_bgrt_get_supported(self->bgrt)); } } static gboolean fu_uefi_capsule_plugin_fwupd_efi_parse(FuUefiCapsulePlugin *self, GError **error) { FuContext *ctx = fu_plugin_get_context(FU_PLUGIN(self)); const guint8 needle[] = "f\0w\0u\0p\0d\0-\0e\0f\0i\0 \0v\0e\0r\0s\0i\0o\0n\0 "; gsize offset = 0; g_autofree gchar *fn = g_file_get_path(self->fwupd_efi_file); g_autofree gchar *version = NULL; g_autoptr(GBytes) buf = NULL; g_autoptr(GBytes) ubuf = NULL; /* find the UTF-16 version string */ buf = fu_bytes_get_contents(fn, error); if (buf == NULL) return FALSE; if (!fu_memmem_safe(g_bytes_get_data(buf, NULL), g_bytes_get_size(buf), needle, sizeof(needle), &offset, error)) { g_prefix_error(error, "searching %s: ", fn); return FALSE; } ubuf = fu_bytes_new_offset(buf, offset + sizeof(needle), 30, error); if (ubuf == NULL) return FALSE; /* convert to UTF-8 */ version = fu_utf16_to_utf8_bytes(ubuf, G_LITTLE_ENDIAN, error); if (version == NULL) { g_prefix_error(error, "converting %s: ", fn); return FALSE; } /* success */ fu_context_add_runtime_version(ctx, "org.freedesktop.fwupd-efi", version); return TRUE; } static void fu_uefi_capsule_plugin_fwupd_efi_changed_cb(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { FuUefiCapsulePlugin *self = FU_UEFI_CAPSULE_PLUGIN(user_data); FuContext *ctx = fu_plugin_get_context(FU_PLUGIN(self)); g_autoptr(GError) error_local = NULL; if (!fu_uefi_capsule_plugin_fwupd_efi_parse(self, &error_local)) { fu_context_add_runtime_version(ctx, "org.freedesktop.fwupd-efi", "1.0"); g_warning("failed to get new fwupd efi runtime version: %s", error_local->message); return; } } static gboolean fu_uefi_capsule_plugin_fwupd_efi_probe(FuUefiCapsulePlugin *self, GError **error) { FuContext *ctx = fu_plugin_get_context(FU_PLUGIN(self)); g_autofree gchar *fn = NULL; /* find the app binary */ fn = fu_uefi_get_built_app_path("fwupd", error); if (fn == NULL) return FALSE; self->fwupd_efi_file = g_file_new_for_path(fn); self->fwupd_efi_monitor = g_file_monitor_file(self->fwupd_efi_file, G_FILE_MONITOR_NONE, NULL, error); if (self->fwupd_efi_monitor == NULL) return FALSE; g_signal_connect(G_FILE_MONITOR(self->fwupd_efi_monitor), "changed", G_CALLBACK(fu_uefi_capsule_plugin_fwupd_efi_changed_cb), self); if (!fu_uefi_capsule_plugin_fwupd_efi_parse(self, error)) { fu_context_add_runtime_version(ctx, "org.freedesktop.fwupd-efi", "1.0"); return FALSE; } return TRUE; } static gboolean fu_uefi_capsule_plugin_clear_results(FuPlugin *plugin, FuDevice *device, GError **error) { FuUefiDevice *device_uefi = FU_UEFI_DEVICE(device); return fu_uefi_device_clear_status(device_uefi, error); } static gchar * fu_uefi_capsule_plugin_efivar_attrs_to_string(guint32 attrs) { const gchar *data[7] = {0}; guint idx = 0; if (attrs & FU_EFIVAR_ATTR_NON_VOLATILE) data[idx++] = "non-volatile"; if (attrs & FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS) data[idx++] = "bootservice-access"; if (attrs & FU_EFIVAR_ATTR_RUNTIME_ACCESS) data[idx++] = "runtime-access"; if (attrs & FU_EFIVAR_ATTR_HARDWARE_ERROR_RECORD) data[idx++] = "hardware-error-record"; if (attrs & FU_EFIVAR_ATTR_AUTHENTICATED_WRITE_ACCESS) data[idx++] = "authenticated-write-access"; if (attrs & FU_EFIVAR_ATTR_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) data[idx++] = "time-based-authenticated-write-access"; if (attrs & FU_EFIVAR_ATTR_APPEND_WRITE) data[idx++] = "append-write"; return g_strjoinv(",", (gchar **)data); } static void fu_uefi_capsule_plugin_add_security_attrs_secureboot(FuPlugin *plugin, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(GError) error = NULL; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_UEFI_SECUREBOOT); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(attrs, attr); /* SB not available or disabled */ if (!fu_efivar_secure_boot_enabled(&error)) { if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND); return; } fu_security_attr_add_bios_target_value(attr, "SecureBoot", "enable"); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_uefi_capsule_plugin_add_security_attrs_bootservices(FuPlugin *plugin, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; const gchar *guids[] = {FU_EFIVAR_GUID_SECURITY_DATABASE, FU_EFIVAR_GUID_EFI_GLOBAL}; /* create attr */ attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_UEFI_BOOTSERVICE_VARS); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_LOCKED); fu_security_attrs_append(attrs, attr); for (guint j = 0; j < G_N_ELEMENTS(guids); j++) { g_autoptr(GPtrArray) names = fu_efivar_get_names(guids[j], NULL); /* sanity check */ if (names == NULL) continue; for (guint i = 0; i < names->len; i++) { const gchar *name = g_ptr_array_index(names, i); g_autoptr(GError) error_local = NULL; gsize data_sz = 0; guint32 data_attr = 0; if (!fu_efivar_get_data(guids[j], name, NULL, &data_sz, &data_attr, &error_local)) { g_warning("failed to read %s-%s: %s", name, guids[j], error_local->message); continue; } if ((data_attr & FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS) > 0 && (data_attr & FU_EFIVAR_ATTR_RUNTIME_ACCESS) == 0) { g_debug("%s-%s attr of size 0x%x had flags %s", name, guids[j], (guint)data_sz, fu_uefi_capsule_plugin_efivar_attrs_to_string(data_attr)); fwupd_security_attr_add_metadata(attr, "guid", guids[j]); fwupd_security_attr_add_metadata(attr, "name", name); fwupd_security_attr_add_flag( attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); fwupd_security_attr_set_result( attr, FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED); return; } } } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_uefi_capsule_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { fu_uefi_capsule_plugin_add_security_attrs_secureboot(plugin, attrs); fu_uefi_capsule_plugin_add_security_attrs_bootservices(plugin, attrs); } static GBytes * fu_uefi_capsule_plugin_get_splash_data(guint width, guint height, GError **error) { const gchar *const *langs = g_get_language_names(); g_autofree gchar *datadir_pkg = NULL; g_autofree gchar *filename_archive = NULL; g_autofree gchar *langs_str = NULL; g_autoptr(FuArchive) archive = NULL; g_autoptr(GBytes) blob_archive = NULL; /* load archive */ datadir_pkg = fu_path_from_kind(FU_PATH_KIND_DATADIR_PKG); filename_archive = g_build_filename(datadir_pkg, "uefi-capsule-ux.tar.xz", NULL); blob_archive = fu_bytes_get_contents(filename_archive, error); if (blob_archive == NULL) return NULL; archive = fu_archive_new(blob_archive, FU_ARCHIVE_FLAG_NONE, error); if (archive == NULL) return NULL; /* find the closest locale match, falling back to `en` and `C` */ for (guint i = 0; langs[i] != NULL; i++) { g_autofree gchar *fn = NULL; g_autoptr(GBytes) blob_tmp = NULL; if (g_str_has_suffix(langs[i], ".UTF-8")) continue; fn = g_strdup_printf("fwupd-%s-%u-%u.bmp", langs[i], width, height); blob_tmp = fu_archive_lookup_by_fn(archive, fn, NULL); if (blob_tmp != NULL) { g_debug("using UX image %s", fn); return g_bytes_ref(blob_tmp); } g_debug("no %s found", fn); } /* we found nothing */ langs_str = g_strjoinv(",", (gchar **)langs); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to get splash file for %s in %s", langs_str, datadir_pkg); return NULL; } static gboolean fu_uefi_capsule_plugin_write_splash_data(FuUefiCapsulePlugin *self, FuDevice *device, GBytes *blob, GError **error) { guint32 screen_x, screen_y; gsize buf_size = g_bytes_get_size(blob); gssize size; guint32 height, width; guint8 csum = 0; fwupd_guid_t guid = {0x0}; g_autofree gchar *capsule_path = NULL; g_autofree gchar *esp_path = NULL; g_autofree gchar *fn = NULL; g_autofree gchar *basename = NULL; g_autofree gchar *directory = NULL; g_autoptr(GByteArray) st_cap = fu_struct_efi_capsule_header_new(); g_autoptr(GByteArray) st_uxh = fu_struct_efi_ux_capsule_header_new(); g_autoptr(GFile) ofile = NULL; g_autoptr(GOutputStream) ostream = NULL; /* get screen dimensions */ if (!fu_uefi_get_framebuffer_size(&screen_x, &screen_y, error)) return FALSE; if (!fu_uefi_get_bitmap_size((const guint8 *)g_bytes_get_data(blob, NULL), buf_size, &width, &height, error)) { g_prefix_error(error, "splash invalid: "); return FALSE; } /* save to a predictable filename */ esp_path = fu_volume_get_mount_point(self->esp); directory = fu_uefi_get_esp_path_for_os(); basename = g_strdup_printf("fwupd-%s.cap", FU_EFIVAR_GUID_UX_CAPSULE); capsule_path = g_build_filename(directory, "fw", basename, NULL); fn = g_build_filename(esp_path, capsule_path, NULL); if (!fu_path_mkdir_parent(fn, error)) return FALSE; ofile = g_file_new_for_path(fn); ostream = G_OUTPUT_STREAM(g_file_replace(ofile, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error)); if (ostream == NULL) return FALSE; fu_struct_efi_capsule_header_set_flags(st_cap, EFI_CAPSULE_HEADER_FLAGS_PERSIST_ACROSS_RESET); if (!fwupd_guid_from_string(FU_EFIVAR_GUID_UX_CAPSULE, &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return FALSE; fu_struct_efi_capsule_header_set_guid(st_cap, &guid); fu_struct_efi_capsule_header_set_image_size(st_cap, g_bytes_get_size(blob) + FU_STRUCT_EFI_CAPSULE_HEADER_SIZE + FU_STRUCT_EFI_UX_CAPSULE_HEADER_SIZE); fu_struct_efi_ux_capsule_header_set_x_offset(st_uxh, (screen_x / 2) - (width / 2)); if (screen_y == fu_uefi_bgrt_get_height(self->bgrt)) { fu_struct_efi_ux_capsule_header_set_y_offset(st_uxh, (gdouble)screen_y * 0.8f); } else { fu_struct_efi_ux_capsule_header_set_y_offset( st_uxh, fu_uefi_bgrt_get_yoffset(self->bgrt) + fu_uefi_bgrt_get_height(self->bgrt)); }; /* header, payload and image has to add to zero */ csum += fu_sum8(st_cap->data, st_cap->len); csum += fu_sum8(st_uxh->data, st_uxh->len); csum += fu_sum8_bytes(blob); fu_struct_efi_ux_capsule_header_set_checksum(st_uxh, 0x100 - csum); /* write capsule file */ size = g_output_stream_write(ostream, st_cap->data, st_cap->len, NULL, error); if (size < 0) return FALSE; size = g_output_stream_write(ostream, st_uxh->data, st_uxh->len, NULL, error); if (size < 0) return FALSE; size = g_output_stream_write_bytes(ostream, blob, NULL, error); if (size < 0) return FALSE; /* write display capsule location as UPDATE_INFO */ return fu_uefi_device_write_update_info(FU_UEFI_DEVICE(device), capsule_path, "fwupd-ux-capsule", FU_EFIVAR_GUID_UX_CAPSULE, error); } static gboolean fu_uefi_capsule_plugin_update_splash(FuPlugin *plugin, FuDevice *device, GError **error) { FuUefiCapsulePlugin *self = FU_UEFI_CAPSULE_PLUGIN(plugin); guint best_idx = G_MAXUINT; guint32 lowest_border_pixels = G_MAXUINT; guint32 screen_height = 768; guint32 screen_width = 1024; g_autoptr(GBytes) image_bmp = NULL; struct { guint32 width; guint32 height; } sizes[] = {{640, 480}, /* matching the sizes in po/make-images */ {800, 600}, {1024, 768}, {1920, 1080}, {3840, 2160}, {5120, 2880}, {5688, 3200}, {7680, 4320}, {0, 0}}; /* no UX capsule support, so deleting var if it exists */ if (fu_device_has_private_flag(device, FU_UEFI_DEVICE_FLAG_NO_UX_CAPSULE)) { g_info("not providing UX capsule"); return fu_efivar_delete(FU_EFIVAR_GUID_FWUPDATE, "fwupd-ux-capsule", error); } /* get the boot graphics resource table data */ if (!fu_uefi_bgrt_get_supported(self->bgrt)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "BGRT is not supported"); return FALSE; } if (!fu_uefi_get_framebuffer_size(&screen_width, &screen_height, error)) return FALSE; g_debug("framebuffer size %" G_GUINT32_FORMAT " x%" G_GUINT32_FORMAT, screen_width, screen_height); /* find the 'best sized' pre-generated image */ for (guint i = 0; sizes[i].width != 0; i++) { guint32 border_pixels; /* disregard any images that are bigger than the screen */ if (sizes[i].width > screen_width) continue; if (sizes[i].height > screen_height) continue; /* is this the best fit for the display */ border_pixels = (screen_width * screen_height) - (sizes[i].width * sizes[i].height); if (border_pixels < lowest_border_pixels) { lowest_border_pixels = border_pixels; best_idx = i; } } if (best_idx == G_MAXUINT) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to find a suitable image to use"); return FALSE; } /* get the raw data */ image_bmp = fu_uefi_capsule_plugin_get_splash_data(sizes[best_idx].width, sizes[best_idx].height, error); if (image_bmp == NULL) return FALSE; /* perform the upload */ return fu_uefi_capsule_plugin_write_splash_data(self, device, image_bmp, error); } static gboolean fu_uefi_capsule_plugin_write_firmware(FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) { const gchar *str; guint32 flashes_left; g_autoptr(GError) error_splash = NULL; /* test the flash counter */ flashes_left = fu_device_get_flashes_left(device); if (flashes_left > 0) { g_debug("%s has %" G_GUINT32_FORMAT " flashes left", fu_device_get_name(device), flashes_left); if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && flashes_left <= 2) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s only has %" G_GUINT32_FORMAT " flashes left -- " "see https://github.com/fwupd/fwupd/wiki/Dell-TPM:-flashes-left for " "more information.", fu_device_get_name(device), flashes_left); return FALSE; } } /* TRANSLATORS: this is shown when updating the firmware after the reboot */ str = _("Installing firmware update…"); g_assert(str != NULL); /* perform the update */ fu_progress_set_status(progress, FWUPD_STATUS_SCHEDULING); if (!fu_uefi_capsule_plugin_update_splash(plugin, device, &error_splash)) g_info("failed to upload UEFI UX capsule text: %s", error_splash->message); return fu_device_write_firmware(device, blob_fw, progress, flags, error); } static void fu_uefi_capsule_plugin_load_config(FuPlugin *plugin, FuDevice *device) { guint64 sz_reqd = 0; g_autofree gchar *require_esp_free_space = NULL; g_autoptr(GError) error_local = NULL; /* parse free space needed for ESP */ require_esp_free_space = fu_plugin_get_config_value(plugin, "RequireESPFreeSpace"); if (!fu_strtoull(require_esp_free_space, &sz_reqd, 0, G_MAXUINT64, &error_local)) g_warning("invalid ESP free space specified: %s", error_local->message); fu_uefi_device_set_require_esp_free_space(FU_UEFI_DEVICE(device), sz_reqd); /* shim used for SB or not? */ if (!fu_plugin_get_config_value_boolean(plugin, "DisableShimForSecureBoot")) fu_device_add_private_flag(device, FU_UEFI_DEVICE_FLAG_USE_SHIM_FOR_SB); /* enable the fwupd.efi debug log? */ if (fu_plugin_get_config_value_boolean(plugin, "EnableEfiDebugging")) fu_device_add_private_flag(device, FU_UEFI_DEVICE_FLAG_ENABLE_EFI_DEBUGGING); } static gboolean fu_uefi_capsule_plugin_is_esp_linux(FuVolume *esp, GError **error) { const gchar *prefixes[] = {"grub", "shim", "systemd-boot", "zfsbootmenu", NULL}; g_autofree gchar *prefixes_str = NULL; g_autofree gchar *mount_point = fu_volume_get_mount_point(esp); g_autoptr(GPtrArray) files = NULL; /* look for any likely basenames */ if (mount_point == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no mountpoint for ESP"); return FALSE; } files = fu_path_get_files(mount_point, error); if (files == NULL) return FALSE; for (guint i = 0; i < files->len; i++) { const gchar *fn = g_ptr_array_index(files, i); g_autofree gchar *basename = g_path_get_basename(fn); g_autofree gchar *basename_lower = g_utf8_strdown(basename, -1); for (guint j = 0; prefixes[j] != NULL; j++) { if (!g_str_has_prefix(basename_lower, prefixes[j])) continue; if (!g_str_has_suffix(basename_lower, ".efi")) continue; g_info("found %s which indicates a Linux ESP, using %s", fn, mount_point); return TRUE; } } /* failed */ prefixes_str = g_strjoinv("|", (gchar **)prefixes); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "did not any files with prefix %s in %s", prefixes_str, mount_point); return FALSE; } static gint fu_uefi_capsule_plugin_sort_volume_score_cb(gconstpointer a, gconstpointer b, gpointer user_data) { GHashTable *esp_scores = (GHashTable *)user_data; guint esp1_score = GPOINTER_TO_UINT(g_hash_table_lookup(esp_scores, *((FuVolume **)a))); guint esp2_score = GPOINTER_TO_UINT(g_hash_table_lookup(esp_scores, *((FuVolume **)b))); if (esp1_score < esp2_score) return 1; if (esp1_score > esp2_score) return -1; return 0; } static FuVolume * fu_uefi_capsule_plugin_get_default_esp(FuPlugin *plugin, GError **error) { g_autoptr(GPtrArray) esp_volumes = NULL; const gchar *recovery_partitions[] = { "DELLRESTORE", "DELLUTILITY", "DIAGS", "HP_RECOVERY", "IBM_SERVICE", "INTELRST", "LENOVO_RECOVERY", "OS", "PQSERVICE", "RECOVERY", "RECOVERY_PARTITION", "SERVICEV001", "SERVICEV002", "SYSTEM_RESERVED", "WINRE_DRV", NULL, }; /* from https://github.com/storaged-project/udisks/blob/master/data/80-udisks2.rules */ const gchar *user_esp_location = fu_context_get_esp_location(fu_plugin_get_context(plugin)); /* show which volumes we're choosing from */ esp_volumes = fu_context_get_esp_volumes(fu_plugin_get_context(plugin), error); if (esp_volumes == NULL) return NULL; /* we found more than one: lets look for the best one */ if (esp_volumes->len > 1) { g_autoptr(GString) str = g_string_new("more than one ESP possible:"); g_autoptr(GHashTable) esp_scores = g_hash_table_new(g_direct_hash, g_direct_equal); for (guint i = 0; i < esp_volumes->len; i++) { FuVolume *esp = g_ptr_array_index(esp_volumes, i); guint score = 0; g_autofree gchar *name = NULL; g_autofree gchar *kind = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; /* ignore the volume completely if we cannot mount it */ locker = fu_volume_locker(esp, &error_local); if (locker == NULL) { g_warning("failed to mount ESP: %s", error_local->message); continue; } /* if user specified, make sure that it matches */ if (user_esp_location != NULL) { g_autofree gchar *mount = fu_volume_get_mount_point(esp); if (g_strcmp0(mount, user_esp_location) != 0) { g_debug("skipping %s as it's not the user " "specified ESP", mount); continue; } } /* ignore a partition that claims to be a recovery partition */ name = fu_volume_get_partition_name(esp); if (name != NULL) { g_autoptr(GString) name_safe = g_string_new(name); g_string_replace(name_safe, " ", "_", 0); g_string_replace(name_safe, "\"", "", 0); g_string_ascii_up(name_safe); if (g_strv_contains(recovery_partitions, name_safe->str)) { if (g_strcmp0(name_safe->str, name) == 0) { g_debug("skipping partition '%s'", name); } else { g_debug("skipping partition '%s' -> '%s'", name, name_safe->str); } continue; } } /* big partitions are better than small partitions */ score += fu_volume_get_size(esp) / (1024 * 1024); /* prefer partitions with the ESP flag set over msftdata */ kind = fu_volume_get_partition_kind(esp); if (g_strcmp0(kind, FU_VOLUME_KIND_ESP) == 0) score += 0x20000; /* prefer linux ESP */ if (!fu_uefi_capsule_plugin_is_esp_linux(esp, &error_local)) { g_debug("not a Linux ESP: %s", error_local->message); } else { score += 0x10000; } g_hash_table_insert(esp_scores, (gpointer)esp, GUINT_TO_POINTER(score)); } if (g_hash_table_size(esp_scores) == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no EFI system partition found"); return NULL; } g_ptr_array_sort_with_data(esp_volumes, fu_uefi_capsule_plugin_sort_volume_score_cb, esp_scores); for (guint i = 0; i < esp_volumes->len; i++) { FuVolume *esp = g_ptr_array_index(esp_volumes, i); guint score = GPOINTER_TO_UINT(g_hash_table_lookup(esp_scores, esp)); g_string_append_printf(str, "\n - 0x%x:\t%s", score, fu_volume_get_id(esp)); } g_debug("%s", str->str); } if (esp_volumes->len == 1) { FuVolume *esp = g_ptr_array_index(esp_volumes, 0); g_autoptr(FuDeviceLocker) locker = NULL; /* ensure it can be mounted */ locker = fu_volume_locker(esp, error); if (locker == NULL) return NULL; /* if user specified, does it match mountpoints ? */ if (user_esp_location != NULL) { g_autofree gchar *mount = fu_volume_get_mount_point(esp); if (g_strcmp0(mount, user_esp_location) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "user specified ESP %s not found", user_esp_location); return NULL; } } } /* "success" */ return g_object_ref(g_ptr_array_index(esp_volumes, 0)); } static void fu_uefi_capsule_plugin_validate_esp(FuUefiCapsulePlugin *self) { g_autofree gchar *kind = NULL; /* nothing found */ if (self->esp == NULL) { fu_plugin_add_flag(FU_PLUGIN(self), FWUPD_PLUGIN_FLAG_ESP_NOT_FOUND); fu_plugin_add_flag(FU_PLUGIN(self), FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE); fu_plugin_add_flag(FU_PLUGIN(self), FWUPD_PLUGIN_FLAG_USER_WARNING); return; } /* found *something* but it wasn't quite an ESP... */ kind = fu_volume_get_partition_kind(self->esp); if (kind != NULL && g_strcmp0(fu_volume_kind_convert_to_gpt(kind), FU_VOLUME_KIND_ESP) != 0) { fu_plugin_add_flag(FU_PLUGIN(self), FWUPD_PLUGIN_FLAG_ESP_NOT_VALID); fu_plugin_add_flag(FU_PLUGIN(self), FWUPD_PLUGIN_FLAG_USER_WARNING); } } static void fu_uefi_capsule_plugin_register_proxy_device(FuPlugin *plugin, FuDevice *device) { FuUefiCapsulePlugin *self = FU_UEFI_CAPSULE_PLUGIN(plugin); g_autoptr(FuUefiDevice) dev = NULL; g_autoptr(GError) error_local = NULL; /* load all configuration variables */ dev = fu_uefi_backend_device_new_from_dev(FU_UEFI_BACKEND(self->backend), device); fu_uefi_capsule_plugin_load_config(plugin, FU_DEVICE(dev)); if (self->esp == NULL) { self->esp = fu_uefi_capsule_plugin_get_default_esp(plugin, &error_local); if (self->esp == NULL) g_warning("cannot find default ESP: %s", error_local->message); fu_uefi_capsule_plugin_validate_esp(self); } if (self->esp == NULL) { fu_device_inhibit(device, "no-esp", error_local->message); } else { fu_uefi_device_set_esp(dev, self->esp); fu_device_uninhibit(device, "no-esp"); } fu_plugin_device_add(plugin, FU_DEVICE(dev)); } static void fu_uefi_capsule_plugin_device_registered(FuPlugin *plugin, FuDevice *device) { if (fu_device_get_metadata(device, FU_DEVICE_METADATA_UEFI_DEVICE_KIND) != NULL) { if (fu_device_get_guid_default(device) == NULL) { g_autofree gchar *dbg = fu_device_to_string(device); g_warning("cannot create proxy device as no GUID: %s", dbg); return; } fu_uefi_capsule_plugin_register_proxy_device(plugin, device); } } static const gchar * fu_uefi_capsule_plugin_uefi_type_to_string(FuUefiDeviceKind device_kind) { if (device_kind == FU_UEFI_DEVICE_KIND_UNKNOWN) return "Unknown Firmware"; if (device_kind == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) return "System Firmware"; if (device_kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE) return "Device Firmware"; if (device_kind == FU_UEFI_DEVICE_KIND_UEFI_DRIVER) return "UEFI Driver"; if (device_kind == FU_UEFI_DEVICE_KIND_FMP) return "Firmware Management Protocol"; return NULL; } static gchar * fu_uefi_capsule_plugin_get_name_for_type(FuPlugin *plugin, FuUefiDeviceKind device_kind) { GString *display_name; /* set Display Name prefix for capsules that are not PCI cards */ display_name = g_string_new(fu_uefi_capsule_plugin_uefi_type_to_string(device_kind)); if (device_kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE) g_string_prepend(display_name, "UEFI "); return g_string_free(display_name, FALSE); } static gboolean fu_uefi_capsule_plugin_coldplug_device(FuPlugin *plugin, FuUefiDevice *dev, GError **error) { FuUefiCapsulePlugin *self = FU_UEFI_CAPSULE_PLUGIN(plugin); FuContext *ctx = fu_plugin_get_context(plugin); FuUefiDeviceKind device_kind; /* probe to get add GUIDs (and hence any quirk fixups) */ if (!fu_device_probe(FU_DEVICE(dev), error)) return FALSE; if (!fu_device_setup(FU_DEVICE(dev), error)) return FALSE; /* if not already set by quirks */ if (fu_context_has_hwid_flag(ctx, "use-legacy-bootmgr-desc")) { fu_device_add_private_flag(FU_DEVICE(dev), FU_UEFI_DEVICE_FLAG_USE_LEGACY_BOOTMGR_DESC); } if (fu_context_has_hwid_flag(ctx, "supports-boot-order-lock")) { fu_device_add_private_flag(FU_DEVICE(dev), FU_UEFI_DEVICE_FLAG_SUPPORTS_BOOT_ORDER_LOCK); } if (fu_context_has_hwid_flag(ctx, "no-ux-capsule")) fu_device_add_private_flag(FU_DEVICE(dev), FU_UEFI_DEVICE_FLAG_NO_UX_CAPSULE); if (fu_context_has_hwid_flag(ctx, "no-lid-closed")) fu_device_add_internal_flag(FU_DEVICE(dev), FU_DEVICE_INTERNAL_FLAG_NO_LID_CLOSED); if (fu_context_has_hwid_flag(ctx, "display-required")) { fu_device_add_internal_flag(FU_DEVICE(dev), FU_DEVICE_INTERNAL_FLAG_DISPLAY_REQUIRED); } if (fu_context_has_hwid_flag(ctx, "modify-bootorder")) fu_device_add_private_flag(FU_DEVICE(dev), FU_UEFI_DEVICE_FLAG_MODIFY_BOOTORDER); /* detected InsydeH2O */ if (self->acpi_uefi != NULL && fu_acpi_uefi_cod_indexed_filename(FU_ACPI_UEFI(self->acpi_uefi))) { fu_device_add_private_flag(FU_DEVICE(dev), FU_UEFI_DEVICE_FLAG_COD_INDEXED_FILENAME); } /* set fallback name if nothing else is set */ device_kind = fu_uefi_device_get_kind(dev); if (fu_device_get_name(FU_DEVICE(dev)) == NULL) { g_autofree gchar *name = NULL; name = fu_uefi_capsule_plugin_get_name_for_type(plugin, device_kind); if (name != NULL) fu_device_set_name(FU_DEVICE(dev), name); if (device_kind != FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) { fu_device_add_internal_flag(FU_DEVICE(dev), FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY); } } /* set fallback vendor if nothing else is set */ if (fu_device_get_vendor(FU_DEVICE(dev)) == NULL && device_kind == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) { const gchar *vendor = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_MANUFACTURER); if (vendor != NULL) fu_device_set_vendor(FU_DEVICE(dev), vendor); } /* set vendor ID as the BIOS vendor */ if (device_kind != FU_UEFI_DEVICE_KIND_FMP) { const gchar *dmi_vendor; dmi_vendor = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_BIOS_VENDOR); if (dmi_vendor != NULL) { g_autofree gchar *vendor_id = g_strdup_printf("DMI:%s", dmi_vendor); fu_device_add_vendor_id(FU_DEVICE(dev), vendor_id); } } fu_device_add_request_flag(FU_DEVICE(dev), FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); /* success */ return TRUE; } static void fu_uefi_capsule_plugin_test_secure_boot(FuPlugin *plugin) { const gchar *result_str = "Disabled"; if (fu_efivar_secure_boot_enabled(NULL)) result_str = "Enabled"; fu_plugin_add_report_metadata(plugin, "SecureBoot", result_str); } static gboolean fu_uefi_capsule_plugin_parse_acpi_uefi(FuUefiCapsulePlugin *self, GError **error) { g_autofree gchar *fn = NULL; g_autofree gchar *path = NULL; g_autoptr(GBytes) blob = NULL; /* if we have a table, parse it and validate it */ path = fu_path_from_kind(FU_PATH_KIND_ACPI_TABLES); fn = g_build_filename(path, "UEFI", NULL); blob = fu_bytes_get_contents(fn, error); if (blob == NULL) return FALSE; self->acpi_uefi = fu_acpi_uefi_new(); return fu_firmware_parse(self->acpi_uefi, blob, FWUPD_INSTALL_FLAG_NONE, error); } static gboolean fu_uefi_capsule_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { FuUefiCapsulePlugin *self = FU_UEFI_CAPSULE_PLUGIN(plugin); FuContext *ctx = fu_plugin_get_context(plugin); guint64 nvram_total; g_autofree gchar *nvram_total_str = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GError) error_acpi_uefi = NULL; /* don't let user's environment influence test suite failures */ if (g_getenv("FWUPD_UEFI_TEST") != NULL) return TRUE; /* for the uploaded report */ if (fu_context_has_hwid_flag(ctx, "use-legacy-bootmgr-desc")) fu_plugin_add_report_metadata(plugin, "BootMgrDesc", "legacy"); /* some platforms have broken SMBIOS data */ if (fu_context_has_hwid_flag(ctx, "uefi-force-enable")) return TRUE; /* use GRUB to load updates */ if (fu_plugin_get_config_value_boolean(plugin, "EnableGrubChainLoad")) { fu_uefi_backend_set_device_gtype(FU_UEFI_BACKEND(self->backend), FU_TYPE_UEFI_GRUB_DEVICE); } /* check we can use this backend */ if (!fu_backend_setup(self->backend, progress, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_WRITE)) { fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_EFIVAR_NOT_MOUNTED); fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE); fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_USER_WARNING); } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* are the EFI dirs set up so we can update each device */ if (!fu_efivar_supported(error)) return FALSE; nvram_total = fu_efivar_space_used(error); if (nvram_total == G_MAXUINT64) return FALSE; nvram_total_str = g_strdup_printf("%" G_GUINT64_FORMAT, nvram_total); fu_plugin_add_report_metadata(plugin, "EfivarNvramUsed", nvram_total_str); /* we use this both for quirking the CoD implementation sanity and the CoD filename */ if (!fu_uefi_capsule_plugin_parse_acpi_uefi(self, &error_acpi_uefi)) g_debug("failed to load ACPI UEFI table: %s", error_acpi_uefi->message); /* test for invalid ESP in coldplug, and set the update-error rather * than showing no output if the plugin had self-disabled here */ return TRUE; } static gboolean fu_uefi_capsule_plugin_unlock(FuPlugin *plugin, FuDevice *device, GError **error) { FuUefiDevice *device_uefi = FU_UEFI_DEVICE(device); FuDevice *device_alt = NULL; FwupdDeviceFlags device_flags_alt = 0; guint flashes_left = 0; guint flashes_left_alt = 0; if (fu_uefi_device_get_kind(device_uefi) != FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unable to unlock %s", fu_device_get_name(device)); return FALSE; } /* for unlocking TPM1.2 <-> TPM2.0 switching */ g_debug("unlocking upgrades for: %s (%s)", fu_device_get_name(device), fu_device_get_id(device)); device_alt = fu_device_get_alternate(device); if (device_alt == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No alternate device for %s", fu_device_get_name(device)); return FALSE; } g_debug("preventing upgrades for: %s (%s)", fu_device_get_name(device_alt), fu_device_get_id(device_alt)); flashes_left = fu_device_get_flashes_left(device); flashes_left_alt = fu_device_get_flashes_left(device_alt); if (flashes_left == 0) { /* flashes left == 0 on both means no flashes left */ if (flashes_left_alt == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ERROR: %s has no flashes left.", fu_device_get_name(device)); /* flashes left == 0 on just unlocking device is ownership */ } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ERROR: %s is currently OWNED. " "Ownership must be removed to switch modes.", fu_device_get_name(device_alt)); } return FALSE; } /* clone the info from real device but prevent it from being flashed */ device_flags_alt = fu_device_get_flags(device_alt); fu_device_set_flags(device, device_flags_alt); fu_device_inhibit(device_alt, "alt-device", "Preventing upgrades as alternate"); /* make sure that this unlocked device can be updated */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version(device, "0.0.0.0"); return TRUE; } static void fu_plugin_uefi_update_state_notify_cb(GObject *object, GParamSpec *pspec, FuPlugin *plugin) { FuDevice *device = FU_DEVICE(object); GPtrArray *devices; g_autofree gchar *msg = NULL; /* device is not in needs-reboot state */ if (fu_device_get_update_state(device) != FWUPD_UPDATE_STATE_NEEDS_REBOOT) return; /* only do this on hardware that cannot coalesce multiple capsules */ if (!fu_context_has_hwid_flag(fu_plugin_get_context(plugin), "no-coalesce")) return; /* mark every other device for this plugin as non-updatable */ msg = g_strdup_printf("Cannot update as %s [%s] needs reboot", fu_device_get_name(device), fu_device_get_id(device)); devices = fu_plugin_get_devices(plugin); for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); if (device_tmp == device) continue; fu_device_inhibit(device_tmp, "no-coalesce", msg); } } static gboolean fu_uefi_capsule_plugin_check_cod_support(FuUefiCapsulePlugin *self, GError **error) { gsize bufsz = 0; guint64 value = 0; g_autofree guint8 *buf = NULL; if (!fu_efivar_get_data(FU_EFIVAR_GUID_EFI_GLOBAL, "OsIndicationsSupported", &buf, &bufsz, NULL, error)) { g_prefix_error(error, "failed to read EFI variable: "); return FALSE; } if (!fu_memread_uint64_safe(buf, bufsz, 0x0, &value, G_LITTLE_ENDIAN, error)) return FALSE; if ((value & EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED) == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Capsule-on-Disk is not supported"); return FALSE; } /* no table, nothing to check */ if (self->acpi_uefi == NULL) return TRUE; return fu_acpi_uefi_cod_functional(FU_ACPI_UEFI(self->acpi_uefi), error); } static gboolean fu_uefi_capsule_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { FuUefiCapsulePlugin *self = FU_UEFI_CAPSULE_PLUGIN(plugin); const gchar *str; gboolean has_fde = FALSE; g_autoptr(GError) error_udisks2 = NULL; g_autoptr(GError) error_fde = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) devices = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 63, "find-esp"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "check-cod"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 8, "check-bitlocker"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "coldplug"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 26, "add-devices"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "setup-bgrt"); if (self->esp == NULL) { self->esp = fu_uefi_capsule_plugin_get_default_esp(plugin, &error_udisks2); if (self->esp == NULL) g_warning("cannot find default ESP: %s", error_udisks2->message); fu_uefi_capsule_plugin_validate_esp(self); } fu_progress_step_done(progress); /* firmware may lie */ if (!fu_plugin_get_config_value_boolean(plugin, "DisableCapsuleUpdateOnDisk")) { g_autoptr(GError) error_cod = NULL; if (!fu_uefi_capsule_plugin_check_cod_support(self, &error_cod)) { g_debug("not using CapsuleOnDisk support: %s", error_cod->message); } else { fu_uefi_backend_set_device_gtype(FU_UEFI_BACKEND(self->backend), FU_TYPE_UEFI_COD_DEVICE); } } fu_progress_step_done(progress); /* warn the user that BitLocker might ask for recovery key after fw update */ if (!fu_common_check_full_disk_encryption(&error_fde)) { g_debug("FDE in use, set flag: %s", error_fde->message); has_fde = TRUE; } fu_progress_step_done(progress); /* add each device */ if (!fu_backend_coldplug(self->backend, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); devices = fu_backend_get_devices(self->backend); for (guint i = 0; i < devices->len; i++) { FuUefiDevice *dev = g_ptr_array_index(devices, i); g_autoptr(GError) error_device = NULL; if (self->esp != NULL) fu_uefi_device_set_esp(dev, self->esp); if (!fu_uefi_capsule_plugin_coldplug_device(plugin, dev, &error_device)) { if (g_error_matches(error_device, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_warning("skipping device that failed coldplug: %s", error_device->message); continue; } g_propagate_error(error, g_steal_pointer(&error_device)); return FALSE; } fu_device_add_flag(FU_DEVICE(dev), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(dev), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); /* only system firmware "BIOS" can change the PCRx registers */ if (fu_uefi_device_get_kind(dev) == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE && has_fde) fu_device_add_flag(FU_DEVICE(dev), FWUPD_DEVICE_FLAG_AFFECTS_FDE); /* load all configuration variables */ fu_uefi_capsule_plugin_load_config(plugin, FU_DEVICE(dev)); /* watch in case we set needs-reboot in the engine */ g_signal_connect(FU_DEVICE(dev), "notify::update-state", G_CALLBACK(fu_plugin_uefi_update_state_notify_cb), plugin); fu_plugin_device_add(plugin, FU_DEVICE(dev)); } fu_progress_step_done(progress); /* for debugging problems later */ fu_uefi_capsule_plugin_test_secure_boot(plugin); if (!fu_uefi_bgrt_setup(self->bgrt, &error_local)) g_debug("BGRT setup failed: %s", error_local->message); str = fu_uefi_bgrt_get_supported(self->bgrt) ? "Enabled" : "Disabled"; g_info("UX capsule support : %s", str); fu_plugin_add_report_metadata(plugin, "UEFIUXCapsule", str); fu_progress_step_done(progress); return TRUE; } static gboolean fu_uefi_capsule_plugin_cleanup_esp(FuUefiCapsulePlugin *self, GError **error) { g_autofree gchar *esp_os_base = fu_uefi_get_esp_path_for_os(); g_autofree gchar *esp_path = NULL; g_autofree gchar *pattern = NULL; g_autoptr(FuDeviceLocker) esp_locker = NULL; g_autoptr(GPtrArray) files = NULL; /* delete any files matching the glob in the ESP */ esp_locker = fu_volume_locker(self->esp, error); if (esp_locker == NULL) return FALSE; esp_path = fu_volume_get_mount_point(self->esp); if (esp_path == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no mountpoint for ESP"); return FALSE; } files = fu_path_get_files(esp_path, error); if (files == NULL) return FALSE; pattern = g_build_filename(esp_path, esp_os_base, "fw", "fwupd*.cap", NULL); for (guint i = 0; i < files->len; i++) { const gchar *fn = g_ptr_array_index(files, i); if (g_pattern_match_simple(pattern, fn)) { g_autoptr(GFile) file = g_file_new_for_path(fn); g_debug("deleting %s", fn); if (!g_file_delete(file, NULL, error)) return FALSE; } } /* success */ return TRUE; } static gboolean fu_uefi_capsule_plugin_cleanup_bootnext(FuUefiCapsulePlugin *self, GError **error) { guint16 boot_next = 0; g_autofree gchar *boot_xxxxstr = NULL; g_autofree gchar *loadoptstr = NULL; g_autoptr(FuEfiLoadOption) loadopt = fu_efi_load_option_new(); g_autoptr(GBytes) boot_nextbuf = NULL; g_autoptr(GBytes) boot_xxxxbuf = NULL; /* unset */ if (!fu_efivar_exists(FU_EFIVAR_GUID_EFI_GLOBAL, "BootNext")) return TRUE; /* get the value of BootNext */ boot_nextbuf = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_EFI_GLOBAL, "BootNext", NULL, error); if (boot_nextbuf == NULL) return FALSE; if (!fu_memread_uint16_safe(g_bytes_get_data(boot_nextbuf, NULL), g_bytes_get_size(boot_nextbuf), 0x0, &boot_next, G_LITTLE_ENDIAN, error)) return FALSE; /* get the correct BootXXXX key */ boot_xxxxstr = g_strdup_printf("Boot%04X", boot_next); boot_xxxxbuf = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_EFI_GLOBAL, boot_xxxxstr, NULL, error); if (boot_xxxxbuf == NULL) return FALSE; if (!fu_firmware_parse(FU_FIRMWARE(loadopt), boot_xxxxbuf, FWUPD_INSTALL_FLAG_NONE, error)) return FALSE; /* is this us? */ loadoptstr = fu_firmware_to_string(FU_FIRMWARE(loadopt)); g_debug("EFI LoadOption: %s", loadoptstr); if (g_strcmp0(fu_firmware_get_id(FU_FIRMWARE(loadopt)), "Linux Firmware Updater") == 0 || g_strcmp0(fu_firmware_get_id(FU_FIRMWARE(loadopt)), "Linux-Firmware-Updater") == 0) { g_warning("BootNext was not deleted automatically, so removing: " "this normally indicates a BIOS bug"); if (!fu_efivar_delete(FU_EFIVAR_GUID_EFI_GLOBAL, "BootNext", error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_uefi_capsule_plugin_reboot_cleanup(FuPlugin *plugin, FuDevice *device, GError **error) { FuUefiCapsulePlugin *self = FU_UEFI_CAPSULE_PLUGIN(plugin); /* provide an escape hatch for debugging */ if (!fu_plugin_get_config_value_boolean(plugin, "RebootCleanup")) return TRUE; /* delete capsules */ if (!fu_uefi_capsule_plugin_cleanup_esp(self, error)) return FALSE; /* delete any old variables */ if (!fu_efivar_delete_with_glob(FU_EFIVAR_GUID_FWUPDATE, "fwupd*-*", error)) return FALSE; /* this should not be required, but, hey -- here were are */ if (!fu_uefi_capsule_plugin_cleanup_bootnext(self, error)) return FALSE; /* success */ return TRUE; } static void fu_uefi_capsule_plugin_init(FuUefiCapsulePlugin *self) { self->bgrt = fu_uefi_bgrt_new(); fu_plugin_add_flag(FU_PLUGIN(self), FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY); } static void fu_uefi_capsule_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); FuUefiCapsulePlugin *self = FU_UEFI_CAPSULE_PLUGIN(plugin); g_autoptr(GError) error_local = NULL; self->backend = fu_uefi_backend_new(ctx); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_RUN_AFTER, "upower"); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "tpm"); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "dell"); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "linux_lockdown"); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "acpi_phat"); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_CONFLICTS, "uefi"); /* old name */ fu_plugin_add_firmware_gtype(FU_PLUGIN(self), NULL, FU_TYPE_ACPI_UEFI); fu_plugin_add_firmware_gtype(FU_PLUGIN(self), NULL, FU_TYPE_UEFI_UPDATE_INFO); /* defaults changed here will also be reflected in the fwupd.conf man page */ fu_plugin_set_config_default(plugin, "DisableCapsuleUpdateOnDisk", "false"); fu_plugin_set_config_default(plugin, "DisableShimForSecureBoot", "false"); fu_plugin_set_config_default(plugin, "EnableEfiDebugging", "false"); fu_plugin_set_config_default(plugin, "EnableGrubChainLoad", "false"); fu_plugin_set_config_default(plugin, "OverrideESPMountPoint", NULL); fu_plugin_set_config_default(plugin, "RebootCleanup", "true"); fu_plugin_set_config_default(plugin, "RequireESPFreeSpace", "0"); /* in MB */ /* add a requirement on the fwupd-efi version -- which can change */ if (!fu_uefi_capsule_plugin_fwupd_efi_probe(self, &error_local)) g_debug("failed to get fwupd-efi runtime version: %s", error_local->message); } static void fu_uefi_capsule_finalize(GObject *obj) { FuUefiCapsulePlugin *self = FU_UEFI_CAPSULE_PLUGIN(obj); if (self->esp != NULL) g_object_unref(self->esp); if (self->fwupd_efi_file != NULL) g_object_unref(self->fwupd_efi_file); if (self->acpi_uefi != NULL) g_object_unref(self->acpi_uefi); if (self->fwupd_efi_monitor != NULL) { g_file_monitor_cancel(self->fwupd_efi_monitor); g_object_unref(self->fwupd_efi_monitor); } if (self->backend != NULL) g_object_unref(self->backend); if (self->bgrt != NULL) g_object_unref(self->bgrt); G_OBJECT_CLASS(fu_uefi_capsule_plugin_parent_class)->finalize(obj); } static void fu_uefi_capsule_plugin_class_init(FuUefiCapsulePluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_uefi_capsule_finalize; plugin_class->constructed = fu_uefi_capsule_plugin_constructed; plugin_class->to_string = fu_uefi_capsule_plugin_to_string; plugin_class->clear_results = fu_uefi_capsule_plugin_clear_results; plugin_class->add_security_attrs = fu_uefi_capsule_plugin_add_security_attrs; plugin_class->device_registered = fu_uefi_capsule_plugin_device_registered; plugin_class->startup = fu_uefi_capsule_plugin_startup; plugin_class->unlock = fu_uefi_capsule_plugin_unlock; plugin_class->coldplug = fu_uefi_capsule_plugin_coldplug; plugin_class->write_firmware = fu_uefi_capsule_plugin_write_firmware; plugin_class->reboot_cleanup = fu_uefi_capsule_plugin_reboot_cleanup; } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-capsule-plugin.h000066400000000000000000000003711460375044200234450ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuUefiCapsulePlugin, fu_uefi_capsule_plugin, FU, UEFI_CAPSULE_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-cod-device.c000066400000000000000000000170421460375044200225150ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-uefi-cod-device.h" #include "fu-uefi-common.h" struct _FuUefiCodDevice { FuUefiDevice parent_instance; }; G_DEFINE_TYPE(FuUefiCodDevice, fu_uefi_cod_device, FU_TYPE_UEFI_DEVICE) static gboolean fu_uefi_cod_device_get_results_for_idx(FuDevice *device, guint idx, GError **error) { FuUefiDevice *device_uefi = FU_UEFI_DEVICE(device); fwupd_guid_t guid = {0x0}; gsize bufsz = 0; guint32 status = 0; guint32 total_size = 0; g_autofree gchar *guidstr = NULL; g_autofree gchar *name = NULL; g_autofree guint8 *buf = NULL; /* read out result */ name = g_strdup_printf("Capsule%04u", idx); if (!fu_efivar_get_data(FU_EFIVAR_GUID_EFI_CAPSULE_REPORT, name, &buf, &bufsz, NULL, error)) { g_prefix_error(error, "failed to read %s: ", name); return FALSE; } /* sanity check */ if (!fu_memread_uint32_safe(buf, bufsz, 0x00, &total_size, G_LITTLE_ENDIAN, error)) return FALSE; if (total_size < 0x3A) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "EFI_CAPSULE_RESULT_VARIABLE_HEADER too small"); return FALSE; } /* verify guid */ if (!fu_memcpy_safe(guid, sizeof(guid), 0x0, /* dst */ buf, bufsz, 0x08, /* src */ sizeof(guid), error)) return FALSE; guidstr = fwupd_guid_to_string(&guid, FWUPD_GUID_FLAG_MIXED_ENDIAN); if (g_strcmp0(guidstr, fu_uefi_device_get_guid(device_uefi)) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "wrong GUID, expected %s, got %s", fu_uefi_device_get_guid(device_uefi), guidstr); return FALSE; } /* get status */ if (!fu_memread_uint32_safe(buf, bufsz, 0x28, &status, G_LITTLE_ENDIAN, error)) return FALSE; fu_uefi_device_set_status(device_uefi, status); return TRUE; } #define VARIABLE_IDX_SIZE 11 /* of CHAR16 */ static gboolean fu_uefi_cod_device_get_variable_idx(const gchar *name, guint *value, GError **error) { guint64 tmp = 0; g_autofree gchar *str = NULL; g_autoptr(GBytes) buf = NULL; /* parse the value */ buf = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_EFI_CAPSULE_REPORT, name, NULL, error); if (buf == NULL) return FALSE; str = fu_utf16_to_utf8_bytes(buf, G_LITTLE_ENDIAN, error); if (str == NULL) return FALSE; if (!g_str_has_prefix(str, "Capsule")) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "wrong contents, got %s", str); return FALSE; } if (!fu_strtoull(str + strlen("Capsule"), &tmp, 0, G_MAXUINT32, error)) return FALSE; if (value != NULL) *value = tmp; return TRUE; } static gboolean fu_uefi_cod_device_get_results(FuDevice *device, GError **error) { guint capsule_last = 1024; /* tell us where to stop */ if (!fu_uefi_cod_device_get_variable_idx("CapsuleLast", &capsule_last, error)) return FALSE; for (guint i = 0; i <= capsule_last; i++) { g_autoptr(GError) error_local = NULL; if (fu_uefi_cod_device_get_results_for_idx(device, i, &error_local)) return TRUE; if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND) && !g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } /* nothing found */ return TRUE; } static gchar * fu_uefi_cod_device_get_indexed_filename(FuUefiDevice *self, GError **error) { g_autofree gchar *esp_path = fu_volume_get_mount_point(fu_uefi_device_get_esp(self)); for (guint i = 0; i < 0xFFFF; i++) { g_autofree gchar *basename = g_strdup_printf("CapsuleUpdateFile%04X.bin", i); g_autofree gchar *cod_path = g_build_filename(esp_path, "EFI", "UpdateCapsule", basename, NULL); if (!g_file_test(cod_path, G_FILE_TEST_EXISTS)) return g_steal_pointer(&cod_path); } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "all potential CapsuleUpdateFile file names are taken"); return NULL; } static gchar * fu_uefi_cod_device_get_filename(FuUefiDevice *self, GError **error) { g_autofree gchar *esp_path = fu_volume_get_mount_point(fu_uefi_device_get_esp(self)); g_autofree gchar *basename = NULL; /* InsydeH2O */ if (fu_device_has_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_COD_INDEXED_FILENAME)) return fu_uefi_cod_device_get_indexed_filename(self, error); /* fallback */ basename = g_strdup_printf("fwupd-%s.cap", fu_uefi_device_get_guid(self)); return g_build_filename(esp_path, "EFI", "UpdateCapsule", basename, NULL); } static gboolean fu_uefi_cod_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE(device); g_autofree gchar *cod_path = NULL; g_autoptr(GBytes) fw = NULL; /* ensure we have the existing state */ if (fu_uefi_device_get_guid(self) == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot update device info with no GUID"); return FALSE; } /* copy the capsule */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; cod_path = fu_uefi_cod_device_get_filename(self, error); if (cod_path == NULL) return FALSE; g_info("using %s", cod_path); if (!fu_path_mkdir_parent(cod_path, error)) return FALSE; if (!fu_bytes_set_contents(cod_path, fw, error)) return FALSE; /* * NOTE: The EFI spec requires setting OsIndications! * RT->SetVariable is not supported for all hardware, and so when using * U-Boot, it applies the capsule even if OsIndications isn't set. * The capsule is then deleted by U-Boot after it has been deployed. */ if (!fu_device_has_private_flag(device, FU_UEFI_DEVICE_FLAG_NO_RT_SET_VARIABLE)) { gsize bufsz = 0; guint64 os_indications = 0; g_autofree guint8 *buf = NULL; g_autoptr(GError) error_local = NULL; /* the firmware does not normally populate OsIndications by default */ if (!fu_efivar_get_data(FU_EFIVAR_GUID_EFI_GLOBAL, "OsIndications", &buf, &bufsz, NULL, &error_local)) { g_debug("failed to read EFI variable: %s", error_local->message); } else { if (!fu_memread_uint64_safe(buf, bufsz, 0x0, &os_indications, G_LITTLE_ENDIAN, error)) return FALSE; } os_indications |= EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED; if (!fu_efivar_set_data(FU_EFIVAR_GUID_EFI_GLOBAL, "OsIndications", (guint8 *)&os_indications, sizeof(os_indications), FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS, error)) { g_prefix_error(error, "Could not set OsIndications: "); return FALSE; } } /* success */ return TRUE; } static void fu_uefi_cod_device_report_metadata_pre(FuDevice *device, GHashTable *metadata) { /* FuUefiDevice */ FU_DEVICE_CLASS(fu_uefi_cod_device_parent_class)->report_metadata_pre(device, metadata); g_hash_table_insert(metadata, g_strdup("CapsuleApplyMethod"), g_strdup("cod")); } static void fu_uefi_cod_device_init(FuUefiCodDevice *self) { fu_device_set_summary(FU_DEVICE(self), "UEFI System Resource Table device (Updated via caspule-on-disk)"); } static void fu_uefi_cod_device_class_init(FuUefiCodDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_uefi_cod_device_write_firmware; klass_device->get_results = fu_uefi_cod_device_get_results; klass_device->report_metadata_pre = fu_uefi_cod_device_report_metadata_pre; } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-cod-device.h000066400000000000000000000005161460375044200225200ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-uefi-device.h" #define FU_TYPE_UEFI_COD_DEVICE (fu_uefi_cod_device_get_type()) G_DECLARE_FINAL_TYPE(FuUefiCodDevice, fu_uefi_cod_device, FU, UEFI_COD_DEVICE, FuUefiDevice) fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-common.c000066400000000000000000000225671460375044200220130ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uefi-common.h" #include "fu-uefi-device.h" static const gchar * fu_uefi_bootmgr_get_suffix(GError **error) { guint64 firmware_bits; struct { guint64 bits; const gchar *arch; } suffixes[] = { #if defined(__x86_64__) {64, "x64"}, #elif defined(__aarch64__) {64, "aa64"}, #elif defined(__loongarch_lp64) {64, "loongarch64"}, #elif (defined(__riscv) && __riscv_xlen == 64) {64, "riscv64"}, #endif #if defined(__i386__) || defined(__i686__) {32, "ia32"}, #elif defined(__arm__) {32, "arm"}, #endif {0, NULL} }; g_autofree gchar *sysfsfwdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_FW); g_autofree gchar *sysfsefidir = g_build_filename(sysfsfwdir, "efi", NULL); firmware_bits = fu_uefi_read_file_as_uint64(sysfsefidir, "fw_platform_size"); if (firmware_bits == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "%s/fw_platform_size cannot be found", sysfsefidir); return NULL; } for (guint i = 0; suffixes[i].arch != NULL; i++) { if (firmware_bits != suffixes[i].bits) continue; return suffixes[i].arch; } /* this should exist */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "%s/fw_platform_size has unknown value %" G_GUINT64_FORMAT, sysfsefidir, firmware_bits); return NULL; } /* return without the ESP dir prepended */ gchar * fu_uefi_get_esp_app_path(const gchar *cmd, GError **error) { const gchar *suffix = fu_uefi_bootmgr_get_suffix(error); g_autofree gchar *base = NULL; if (suffix == NULL) return NULL; base = fu_uefi_get_esp_path_for_os(); return g_strdup_printf("%s/%s%s.efi", base, cmd, suffix); } /** * fu_uefi_get_built_app_path: * @basename: the prefix for the binary * @error: (nullable): optional return location for an error * * Gets the path intended to be used for an EFI binary on the local system. * The binary is matched against the correct architecture and if secure * boot is enabled. * * Returns: The full path to the binary, or %NULL if not found * * Since: 1.8.1 **/ gchar * fu_uefi_get_built_app_path(const gchar *binary, GError **error) { const gchar *suffix; g_autofree gchar *prefix = NULL; g_autofree gchar *source_path = NULL; g_autofree gchar *source_path_signed = NULL; gboolean source_path_exists = FALSE; gboolean source_path_signed_exists = FALSE; suffix = fu_uefi_bootmgr_get_suffix(error); if (suffix == NULL) return NULL; prefix = fu_path_from_kind(FU_PATH_KIND_EFIAPPDIR); source_path = g_strdup_printf("%s/%s%s.efi", prefix, binary, suffix); source_path_signed = g_strdup_printf("%s.signed", source_path); source_path_exists = g_file_test(source_path, G_FILE_TEST_EXISTS); source_path_signed_exists = g_file_test(source_path_signed, G_FILE_TEST_EXISTS); if (fu_efivar_secure_boot_enabled(NULL)) { if (!source_path_signed_exists) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "%s cannot be found", source_path_signed); return NULL; } return g_steal_pointer(&source_path_signed); } if (source_path_exists) return g_steal_pointer(&source_path); if (source_path_signed_exists) return g_steal_pointer(&source_path_signed); g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "%s and %s cannot be found", source_path, source_path_signed); return NULL; } gboolean fu_uefi_get_framebuffer_size(guint32 *width, guint32 *height, GError **error) { guint32 height_tmp; guint32 width_tmp; g_autofree gchar *sysfsdriverdir = NULL; g_autofree gchar *fbdir = NULL; sysfsdriverdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_DRIVERS); fbdir = g_build_filename(sysfsdriverdir, "efi-framebuffer", "efi-framebuffer.0", NULL); if (!g_file_test(fbdir, G_FILE_TEST_EXISTS)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "EFI framebuffer not found"); return FALSE; } height_tmp = fu_uefi_read_file_as_uint64(fbdir, "height"); width_tmp = fu_uefi_read_file_as_uint64(fbdir, "width"); if (width_tmp == 0 || height_tmp == 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "EFI framebuffer has invalid size " "%" G_GUINT32_FORMAT "x%" G_GUINT32_FORMAT, width_tmp, height_tmp); return FALSE; } if (width != NULL) *width = width_tmp; if (height != NULL) *height = height_tmp; return TRUE; } gboolean fu_uefi_get_bitmap_size(const guint8 *buf, gsize bufsz, guint32 *width, guint32 *height, GError **error) { guint32 ui32; g_return_val_if_fail(buf != NULL, FALSE); /* check header */ if (bufsz < 26 || memcmp(buf, "BM", 2) != 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid BMP header signature"); return FALSE; } /* starting address */ if (!fu_memread_uint32_safe(buf, bufsz, 10, &ui32, G_LITTLE_ENDIAN, error)) return FALSE; if (ui32 < 26) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "BMP header invalid @ %" G_GUINT32_FORMAT "x", ui32); return FALSE; } /* BITMAPINFOHEADER header */ if (!fu_memread_uint32_safe(buf, bufsz, 14, &ui32, G_LITTLE_ENDIAN, error)) return FALSE; if (ui32 < 26 - 14) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "BITMAPINFOHEADER invalid @ %" G_GUINT32_FORMAT "x", ui32); return FALSE; } /* dimensions */ if (width != NULL) { if (!fu_memread_uint32_safe(buf, bufsz, 18, width, G_LITTLE_ENDIAN, error)) return FALSE; } if (height != NULL) { if (!fu_memread_uint32_safe(buf, bufsz, 22, height, G_LITTLE_ENDIAN, error)) return FALSE; } return TRUE; } /* return without the ESP dir prepended */ gchar * fu_uefi_get_esp_path_for_os(void) { #ifndef EFI_OS_DIR const gchar *os_release_id = NULL; const gchar *id_like = NULL; g_autofree gchar *esp_path = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GHashTable) os_release = fwupd_get_os_release(&error_local); /* try to lookup /etc/os-release ID key */ if (os_release != NULL) { os_release_id = g_hash_table_lookup(os_release, "ID"); } else { g_debug("failed to get ID: %s", error_local->message); } if (os_release_id == NULL) os_release_id = "unknown"; /* if ID key points at something existing return it */ esp_path = g_build_filename("EFI", os_release_id, NULL); if (g_file_test(esp_path, G_FILE_TEST_IS_DIR) || os_release == NULL) return g_steal_pointer(&esp_path); /* if ID key doesn't exist, try ID_LIKE */ id_like = g_hash_table_lookup(os_release, "ID_LIKE"); if (id_like != NULL) { g_auto(GStrv) split = g_strsplit(id_like, " ", -1); for (guint i = 0; split[i] != NULL; i++) { g_autofree gchar *id_like_path = g_build_filename("EFI", split[i], NULL); if (g_file_test(id_like_path, G_FILE_TEST_IS_DIR)) { g_debug("using ID_LIKE key from os-release"); return g_steal_pointer(&id_like_path); } } } return g_steal_pointer(&esp_path); #else return g_build_filename("EFI", EFI_OS_DIR, NULL); #endif } guint64 fu_uefi_read_file_as_uint64(const gchar *path, const gchar *attr_name) { guint64 tmp = 0; g_autofree gchar *data = NULL; g_autofree gchar *fn = g_build_filename(path, attr_name, NULL); g_autoptr(GError) error_local = NULL; if (!g_file_get_contents(fn, &data, NULL, NULL)) return 0x0; if (!fu_strtoull(data, &tmp, 0, G_MAXUINT64, &error_local)) { g_warning("invalid string specified: %s", error_local->message); return G_MAXUINT64; } return tmp; } gboolean fu_uefi_esp_target_verify(const gchar *source_fn, FuVolume *esp, const gchar *target_no_mountpoint) { gsize len = 0; g_autofree gchar *source_csum = NULL; g_autofree gchar *source_data = NULL; g_autofree gchar *target_csum = NULL; g_autofree gchar *target_data = NULL; g_autofree gchar *esp_path = fu_volume_get_mount_point(esp); g_autofree gchar *target_fn = g_build_filename(esp_path, target_no_mountpoint, NULL); /* nothing in target yet */ if (!g_file_test(target_fn, G_FILE_TEST_EXISTS)) return FALSE; /* test if the file needs to be updated */ if (!g_file_get_contents(source_fn, &source_data, &len, NULL)) return FALSE; source_csum = g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar *)source_data, len); if (!g_file_get_contents(target_fn, &target_data, &len, NULL)) return FALSE; target_csum = g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar *)target_data, len); return g_strcmp0(target_csum, source_csum) == 0; } gboolean fu_uefi_esp_target_exists(FuVolume *esp, const gchar *target_no_mountpoint) { g_autofree gchar *esp_path = fu_volume_get_mount_point(esp); g_autofree gchar *target_fn = g_build_filename(esp_path, target_no_mountpoint, NULL); return g_file_test(target_fn, G_FILE_TEST_EXISTS); } gboolean fu_uefi_esp_target_copy(const gchar *source_fn, FuVolume *esp, const gchar *target_no_mountpoint, GError **error) { g_autofree gchar *esp_path = fu_volume_get_mount_point(esp); g_autofree gchar *target_fn = g_build_filename(esp_path, target_no_mountpoint, NULL); g_autoptr(GFile) source_file = g_file_new_for_path(source_fn); g_autoptr(GFile) target_file = g_file_new_for_path(target_fn); if (!g_file_copy(source_file, target_file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, error)) { g_prefix_error(error, "Failed to copy %s to %s: ", source_fn, target_fn); return FALSE; } return TRUE; } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-common.h000066400000000000000000000025051460375044200220060ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #ifdef HAVE_EFI_TIME_T #include #endif #define EFI_CAPSULE_HEADER_FLAGS_PERSIST_ACROSS_RESET 0x00010000 #define EFI_CAPSULE_HEADER_FLAGS_POPULATE_SYSTEM_TABLE 0x00020000 #define EFI_CAPSULE_HEADER_FLAGS_INITIATE_RESET 0x00040000 #define EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED 0x0000000000000004ULL gchar * fu_uefi_get_esp_app_path(const gchar *cmd, GError **error); gchar * fu_uefi_get_built_app_path(const gchar *binary, GError **error); gboolean fu_uefi_get_bitmap_size(const guint8 *buf, gsize bufsz, guint32 *width, guint32 *height, GError **error); gboolean fu_uefi_get_framebuffer_size(guint32 *width, guint32 *height, GError **error); gchar * fu_uefi_get_esp_path_for_os(void); guint64 fu_uefi_read_file_as_uint64(const gchar *path, const gchar *attr_name); gboolean fu_uefi_esp_target_exists(FuVolume *esp, const gchar *target_no_mountpoint); gboolean fu_uefi_esp_target_verify(const gchar *source_fn, FuVolume *esp, const gchar *target_no_mountpoint); gboolean fu_uefi_esp_target_copy(const gchar *source_fn, FuVolume *esp, const gchar *target_no_mountpoint, GError **error); fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-device.c000066400000000000000000000633041460375044200217540ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-uefi-common.h" #include "fu-uefi-device.h" #include "fu-uefi-struct.h" typedef struct { FuVolume *esp; FuDeviceLocker *esp_locker; gchar *fw_class; FuUefiDeviceKind kind; guint32 capsule_flags; guint32 fw_version; guint32 fw_version_lowest; FuUefiDeviceStatus last_attempt_status; guint32 last_attempt_version; guint64 fmp_hardware_instance; gboolean missing_header; gboolean automounted_esp; gsize require_esp_free_space; } FuUefiDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuUefiDevice, fu_uefi_device, FU_TYPE_DEVICE) #define GET_PRIVATE(o) (fu_uefi_device_get_instance_private(o)) #define FU_EFI_FMP_CAPSULE_GUID "6dcbd5ed-e82d-4c44-bda1-7194199ad92a" enum { PROP_0, PROP_FW_CLASS, PROP_KIND, PROP_CAPSULE_FLAGS, PROP_FW_VERSION, PROP_FW_VERSION_LOWEST, PROP_LAST_ATTEMPT_STATUS, PROP_LAST_ATTEMPT_VERSION, PROP_FMP_HARDWARE_INSTANCE, PROP_LAST }; void fu_uefi_device_set_esp(FuUefiDevice *self, FuVolume *esp) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_UEFI_DEVICE(self)); g_return_if_fail(FU_IS_VOLUME(esp)); g_set_object(&priv->esp, esp); } static void fu_uefi_device_to_string(FuDevice *device, guint idt, GString *str) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuUefiDevicePrivate *priv = GET_PRIVATE(self); fu_string_append(str, idt, "Kind", fu_uefi_device_kind_to_string(priv->kind)); fu_string_append(str, idt, "FwClass", priv->fw_class); fu_string_append_kx(str, idt, "CapsuleFlags", priv->capsule_flags); fu_string_append_kx(str, idt, "FwVersion", priv->fw_version); fu_string_append_kx(str, idt, "FwVersionLowest", priv->fw_version_lowest); fu_string_append(str, idt, "LastAttemptStatus", fu_uefi_device_status_to_string(priv->last_attempt_status)); fu_string_append_kx(str, idt, "LastAttemptVersion", priv->last_attempt_version); if (priv->esp != NULL) { g_autofree gchar *kind = fu_volume_get_partition_kind(priv->esp); g_autofree gchar *mount_point = fu_volume_get_mount_point(priv->esp); fu_string_append(str, idt, "EspId", fu_volume_get_id(priv->esp)); if (mount_point != NULL) fu_string_append(str, idt, "EspPath", mount_point); if (kind != NULL) { const gchar *guid = fu_volume_kind_convert_to_gpt(kind); fu_string_append(str, idt, "EspKind", kind); if (g_strcmp0(kind, guid) != 0) fu_string_append(str, idt, "EspGuid", guid); } } fu_string_append_ku(str, idt, "RequireESPFreeSpace", priv->require_esp_free_space); } static void fu_uefi_device_report_metadata_pre(FuDevice *device, GHashTable *metadata) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuUefiDevicePrivate *priv = GET_PRIVATE(self); g_autofree gchar *kind = fu_volume_get_partition_kind(priv->esp); g_autofree gchar *mount_point = fu_volume_get_mount_point(priv->esp); /* record if we had an invalid header during update */ g_hash_table_insert(metadata, g_strdup("MissingCapsuleHeader"), g_strdup(priv->missing_header ? "True" : "False")); /* where and how the ESP was mounted during installation */ g_hash_table_insert(metadata, g_strdup("EspPath"), g_steal_pointer(&mount_point)); if (kind != NULL) { g_hash_table_insert(metadata, g_strdup("EspKind"), g_steal_pointer(&kind)); } } static void fu_uefi_device_report_metadata_post(FuDevice *device, GHashTable *metadata) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuUefiDevicePrivate *priv = GET_PRIVATE(self); /* the actual last_attempt values */ g_hash_table_insert(metadata, g_strdup("LastAttemptStatus"), g_strdup_printf("0x%x", priv->last_attempt_status)); g_hash_table_insert(metadata, g_strdup("LastAttemptVersion"), g_strdup_printf("0x%x", priv->last_attempt_version)); } FuUefiDeviceKind fu_uefi_device_get_kind(FuUefiDevice *self) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UEFI_DEVICE(self), 0); return priv->kind; } guint32 fu_uefi_device_get_version(FuUefiDevice *self) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UEFI_DEVICE(self), 0x0); return priv->fw_version; } guint32 fu_uefi_device_get_version_lowest(FuUefiDevice *self) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UEFI_DEVICE(self), 0x0); return priv->fw_version_lowest; } guint32 fu_uefi_device_get_version_error(FuUefiDevice *self) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UEFI_DEVICE(self), 0x0); return priv->last_attempt_version; } guint64 fu_uefi_device_get_hardware_instance(FuUefiDevice *self) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UEFI_DEVICE(self), 0x0); return priv->fmp_hardware_instance; } FuUefiDeviceStatus fu_uefi_device_get_status(FuUefiDevice *self) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UEFI_DEVICE(self), 0); return priv->last_attempt_status; } void fu_uefi_device_set_status(FuUefiDevice *self, FuUefiDeviceStatus status) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); const gchar *tmp; g_autofree gchar *err_msg = NULL; g_autofree gchar *version_str = NULL; g_return_if_fail(FU_IS_UEFI_DEVICE(self)); /* cache for later */ priv->last_attempt_status = status; /* all good */ if (status == FU_UEFI_DEVICE_STATUS_SUCCESS) { fu_device_set_update_state(FU_DEVICE(self), FWUPD_UPDATE_STATE_SUCCESS); return; } /* something went wrong */ if (status == FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_AC || status == FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_BATT) { fu_device_set_update_state(FU_DEVICE(self), FWUPD_UPDATE_STATE_FAILED_TRANSIENT); } else { fu_device_set_update_state(FU_DEVICE(self), FWUPD_UPDATE_STATE_FAILED); } version_str = g_strdup_printf("%u", priv->last_attempt_version); tmp = fu_uefi_device_status_to_string(status); if (tmp == NULL) { err_msg = g_strdup_printf("failed to update to %s", version_str); } else { err_msg = g_strdup_printf("failed to update to %s: %s", version_str, tmp); } fu_device_set_update_error(FU_DEVICE(self), err_msg); } void fu_uefi_device_set_require_esp_free_space(FuUefiDevice *self, gsize require_esp_free_space) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); g_return_if_fail(FU_IS_UEFI_DEVICE(self)); priv->require_esp_free_space = require_esp_free_space; } guint32 fu_uefi_device_get_capsule_flags(FuUefiDevice *self) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UEFI_DEVICE(self), 0x0); return priv->capsule_flags; } const gchar * fu_uefi_device_get_guid(FuUefiDevice *self) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_UEFI_DEVICE(self), NULL); return priv->fw_class; } gchar * fu_uefi_device_build_varname(FuUefiDevice *self) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); return g_strdup_printf("fwupd-%s-%" G_GUINT64_FORMAT, priv->fw_class, priv->fmp_hardware_instance); } FuUefiUpdateInfo * fu_uefi_device_load_update_info(FuUefiDevice *self, GError **error) { g_autofree gchar *varname = fu_uefi_device_build_varname(self); g_autoptr(FuUefiUpdateInfo) info = fu_uefi_update_info_new(); g_autoptr(GBytes) fw = NULL; g_return_val_if_fail(FU_IS_UEFI_DEVICE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* get the existing status */ fw = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_FWUPDATE, varname, NULL, error); if (fw == NULL) return NULL; if (!fu_firmware_parse(FU_FIRMWARE(info), fw, FWUPD_INSTALL_FLAG_NONE, error)) return NULL; return g_steal_pointer(&info); } gboolean fu_uefi_device_clear_status(FuUefiDevice *self, GError **error) { gsize datasz = 0; g_autofree gchar *varname = fu_uefi_device_build_varname(self); g_autofree guint8 *data = NULL; g_autoptr(GByteArray) st_inf = NULL; g_return_val_if_fail(FU_IS_UEFI_DEVICE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* get the existing status */ if (!fu_efivar_get_data(FU_EFIVAR_GUID_FWUPDATE, varname, &data, &datasz, NULL, error)) return FALSE; st_inf = fu_struct_efi_update_info_parse(data, datasz, 0x0, error); if (st_inf == NULL) { g_prefix_error(error, "EFI variable is corrupt: "); return FALSE; } /* just copy the new EfiUpdateInfo and save it back */ fu_struct_efi_update_info_set_status(st_inf, FU_UEFI_UPDATE_INFO_STATUS_UNKNOWN); memcpy(data, st_inf->data, st_inf->len); if (!fu_efivar_set_data(FU_EFIVAR_GUID_FWUPDATE, varname, data, datasz, FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS, error)) { g_prefix_error(error, "could not set EfiUpdateInfo: "); return FALSE; } /* success */ return TRUE; } FuEfiDevicePathList * fu_uefi_device_build_dp_buf(FuVolume *esp, const gchar *capsule_path, GError **error) { g_autoptr(FuEfiDevicePathList) dp_buf = fu_efi_device_path_list_new(); g_autoptr(FuEfiFilePathDevicePath) dp_file = fu_efi_file_path_device_path_new(); g_autoptr(FuEfiHardDriveDevicePath) dp_hd = NULL; g_autofree gchar *name_with_root = NULL; dp_hd = fu_efi_hard_drive_device_path_new_from_volume(esp, error); if (dp_hd == NULL) return NULL; name_with_root = g_strdup_printf("/%s", capsule_path); if (!fu_efi_file_path_device_path_set_name(dp_file, name_with_root, error)) return NULL; fu_firmware_add_image(FU_FIRMWARE(dp_buf), FU_FIRMWARE(dp_hd)); fu_firmware_add_image(FU_FIRMWARE(dp_buf), FU_FIRMWARE(dp_file)); return g_steal_pointer(&dp_buf); } GBytes * fu_uefi_device_fixup_firmware(FuUefiDevice *self, GBytes *fw, GError **error) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); fwupd_guid_t esrt_guid = {0x0}; guint hdrsize = getpagesize(); gsize bufsz; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autofree gchar *guid_new = NULL; g_autoptr(GByteArray) st_cap = fu_struct_efi_capsule_header_new(); priv->missing_header = FALSE; /* GUID is the first 16 bytes */ if (bufsz < sizeof(fwupd_guid_t)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Invalid payload"); return NULL; } guid_new = fwupd_guid_to_string((fwupd_guid_t *)buf, FWUPD_GUID_FLAG_MIXED_ENDIAN); /* ESRT header matches payload */ if (g_strcmp0(fu_uefi_device_get_guid(self), guid_new) == 0) { g_debug("ESRT matches payload GUID"); return g_bytes_ref(fw); } if (g_strcmp0(guid_new, FU_EFI_FMP_CAPSULE_GUID) == 0 || fu_device_has_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_NO_CAPSULE_HEADER_FIXUP)) { return g_bytes_ref(fw); } /* create a fake header with plausible contents */ g_info("missing or invalid embedded capsule header"); priv->missing_header = TRUE; fu_struct_efi_capsule_header_set_flags(st_cap, priv->capsule_flags); fu_struct_efi_capsule_header_set_header_size(st_cap, hdrsize); fu_struct_efi_capsule_header_set_image_size(st_cap, bufsz + hdrsize); if (!fwupd_guid_from_string(fu_uefi_device_get_guid(self), &esrt_guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) { g_prefix_error(error, "Invalid ESRT GUID: "); return NULL; } fu_struct_efi_capsule_header_set_guid(st_cap, &esrt_guid); /* pad to the headersize then add the payload */ fu_byte_array_set_size(st_cap, hdrsize, 0x00); g_byte_array_append(st_cap, buf, bufsz); return g_bytes_new(st_cap->data, st_cap->len); } gboolean fu_uefi_device_write_update_info(FuUefiDevice *self, const gchar *capsule_path, const gchar *varname, const gchar *guid_str, GError **error) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); fwupd_guid_t guid = {0x0}; g_autoptr(FuEfiDevicePathList) dp_buf = NULL; g_autoptr(GBytes) dp_blob = NULL; g_autoptr(GByteArray) st_inf = fu_struct_efi_update_info_new(); /* set the body as the device path */ if (g_getenv("FWUPD_UEFI_TEST") != NULL) { g_debug("not building device path, in tests...."); return TRUE; } /* convert to EFI device path */ dp_buf = fu_uefi_device_build_dp_buf(priv->esp, capsule_path, error); if (dp_buf == NULL) return FALSE; dp_blob = fu_firmware_write(FU_FIRMWARE(dp_buf), error); if (dp_blob == NULL) return FALSE; /* save this header and body to the hardware */ if (!fwupd_guid_from_string(guid_str, &guid, FWUPD_GUID_FLAG_MIXED_ENDIAN, error)) return FALSE; fu_struct_efi_update_info_set_flags(st_inf, priv->capsule_flags); fu_struct_efi_update_info_set_hw_inst(st_inf, priv->fmp_hardware_instance); fu_struct_efi_update_info_set_status(st_inf, FU_UEFI_UPDATE_INFO_STATUS_ATTEMPT_UPDATE); fu_struct_efi_update_info_set_guid(st_inf, &guid); fu_byte_array_append_bytes(st_inf, dp_blob); if (!fu_efivar_set_data(FU_EFIVAR_GUID_FWUPDATE, varname, st_inf->data, st_inf->len, FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS, error)) { g_prefix_error(error, "could not set DP_BUF with %s: ", capsule_path); return FALSE; } /* success */ return TRUE; } static gboolean fu_uefi_check_asset(FuDevice *device, GError **error) { g_autofree gchar *source_app = fu_uefi_get_built_app_path("fwupd", error); if (source_app == NULL) { if (fu_efivar_secure_boot_enabled(NULL)) g_prefix_error(error, "missing signed bootloader for secure boot: "); return FALSE; } return TRUE; } static gboolean fu_uefi_device_prepare(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuUefiDevicePrivate *priv = GET_PRIVATE(self); /* mount if required */ priv->esp_locker = fu_volume_locker(priv->esp, error); if (priv->esp_locker == NULL) return FALSE; /* sanity checks */ if (!fu_uefi_check_asset(device, error)) return FALSE; return TRUE; } static gboolean fu_uefi_device_cleanup(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuUefiDevicePrivate *priv = GET_PRIVATE(self); /* unmount ESP if we opened it */ if (!fu_device_locker_close(priv->esp_locker, error)) return FALSE; g_clear_object(&priv->esp_locker); return TRUE; } static gboolean fu_uefi_device_probe(FuDevice *device, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuUefiDevicePrivate *priv = GET_PRIVATE(self); /* broken sysfs? */ if (priv->fw_class == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to read fw_class"); return FALSE; } /* this is invalid */ if (!fwupd_guid_is_valid(priv->fw_class)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ESRT GUID '%s' was not valid", priv->fw_class); return FALSE; } /* add GUID first, as quirks may set the version format */ fu_device_add_guid(device, priv->fw_class); /* set versions */ fu_device_set_version_raw(device, priv->fw_version); if (priv->fw_version_lowest != 0) { g_autofree gchar *version_lowest = fu_version_from_uint32(priv->fw_version_lowest, fu_device_get_version_format(self)); fu_device_set_version_lowest_raw(device, priv->fw_version_lowest); fu_device_set_version_lowest(device, version_lowest); } /* set flags */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR); /* add icons */ if (priv->kind == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) { fu_device_add_icon(device, "computer"); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE); } /* whether to create a missing header */ if (priv->kind == FU_UEFI_DEVICE_KIND_FMP || priv->kind == FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE) fu_device_add_private_flag(device, FU_UEFI_DEVICE_FLAG_NO_CAPSULE_HEADER_FIXUP); /* success */ return TRUE; } static void fu_uefi_device_capture_efi_debugging(FuDevice *device) { g_autofree gchar *str = NULL; g_autoptr(GBytes) buf = NULL; g_autoptr(GError) error_local = NULL; /* get the EFI variable contents */ buf = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_DEBUG_LOG", NULL, &error_local); if (buf == NULL) { g_warning("failed to capture EFI debugging: %s", error_local->message); return; } /* convert from UCS-2 to UTF-8 */ str = fu_utf16_to_utf8_bytes(buf, G_LITTLE_ENDIAN, &error_local); if (str == NULL) { g_warning("failed to capture EFI debugging: %s", error_local->message); return; } /* success, dump into journal */ g_info("EFI debugging: %s", str); } gboolean fu_uefi_device_perhaps_enable_debugging(FuUefiDevice *self, GError **error) { if (fu_device_has_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_ENABLE_EFI_DEBUGGING)) { const guint8 data = 1; if (!fu_efivar_set_data(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_VERBOSE", &data, sizeof(data), FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS, error)) { g_prefix_error(error, "failed to enable debugging: "); return FALSE; } return TRUE; } /* unset this */ if (fu_efivar_exists(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_VERBOSE")) { if (!fu_efivar_delete(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_VERBOSE", error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_uefi_device_get_results(FuDevice *device, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuUefiDevicePrivate *priv = GET_PRIVATE(self); /* capture EFI binary debug output */ if (fu_device_has_private_flag(device, FU_UEFI_DEVICE_FLAG_ENABLE_EFI_DEBUGGING)) fu_uefi_device_capture_efi_debugging(device); /* just set the update error */ fu_uefi_device_set_status(self, priv->last_attempt_status); return TRUE; } FuVolume * fu_uefi_device_get_esp(FuUefiDevice *self) { FuUefiDevicePrivate *priv = GET_PRIVATE(self); return priv->esp; } static FuFirmware * fu_uefi_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuUefiDevicePrivate *priv = GET_PRIVATE(self); gsize sz_reqd = priv->require_esp_free_space; /* check there is enough space in the ESP */ if (sz_reqd == 0) { g_info("required ESP free space is not configured, using 2 x %uMB + 20MB", (guint)g_bytes_get_size(fw) / (1024 * 1024)); sz_reqd = g_bytes_get_size(fw) * 2 + (20u * 1024 * 1024); } if (!fu_volume_check_free_space(priv->esp, sz_reqd, error)) return NULL; /* success */ return fu_firmware_new_from_bytes(fw); } static void fu_uefi_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuUefiDevice *self = FU_UEFI_DEVICE(object); FuUefiDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_FW_CLASS: priv->fw_class = g_value_dup_string(value); break; case PROP_KIND: priv->kind = g_value_get_uint(value); break; case PROP_CAPSULE_FLAGS: priv->capsule_flags = g_value_get_uint(value); break; case PROP_FW_VERSION: priv->fw_version = g_value_get_uint(value); break; case PROP_FW_VERSION_LOWEST: priv->fw_version_lowest = g_value_get_uint(value); break; case PROP_LAST_ATTEMPT_STATUS: fu_uefi_device_set_status(self, g_value_get_uint(value)); break; case PROP_LAST_ATTEMPT_VERSION: priv->last_attempt_version = g_value_get_uint(value); break; case PROP_FMP_HARDWARE_INSTANCE: priv->fmp_hardware_instance = g_value_get_uint64(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_uefi_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_uefi_device_init(FuUefiDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "org.uefi.capsule"); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS); fu_device_register_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_NO_UX_CAPSULE, "no-ux-capsule"); fu_device_register_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_USE_SHIM_UNIQUE, "use-shim-unique"); fu_device_register_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_USE_LEGACY_BOOTMGR_DESC, "use-legacy-bootmgr-desc"); fu_device_register_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_SUPPORTS_BOOT_ORDER_LOCK, "supports-boot-order-lock"); fu_device_register_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_USE_SHIM_FOR_SB, "use-shim-for-sb"); fu_device_register_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_NO_RT_SET_VARIABLE, "no-rt-set-variable"); fu_device_register_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_NO_CAPSULE_HEADER_FIXUP, "no-capsule-header-fixup"); fu_device_register_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_ENABLE_EFI_DEBUGGING, "enable-debugging"); fu_device_register_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_COD_INDEXED_FILENAME, "cod-indexed-filename"); fu_device_register_private_flag(FU_DEVICE(self), FU_UEFI_DEVICE_FLAG_MODIFY_BOOTORDER, "modify-bootorder"); } static void fu_uefi_device_finalize(GObject *object) { FuUefiDevice *self = FU_UEFI_DEVICE(object); FuUefiDevicePrivate *priv = GET_PRIVATE(self); g_free(priv->fw_class); if (priv->esp != NULL) g_object_unref(priv->esp); if (priv->esp_locker != NULL) g_object_unref(priv->esp_locker); G_OBJECT_CLASS(fu_uefi_device_parent_class)->finalize(object); } static gchar * fu_uefi_device_convert_version(FuDevice *device, guint64 version_raw) { return fu_version_from_uint32(version_raw, fu_device_get_version_format(device)); } static void fu_uefi_device_class_init(FuUefiDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->set_property = fu_uefi_device_set_property; object_class->finalize = fu_uefi_device_finalize; klass_device->to_string = fu_uefi_device_to_string; klass_device->probe = fu_uefi_device_probe; klass_device->prepare_firmware = fu_uefi_device_prepare_firmware; klass_device->prepare = fu_uefi_device_prepare; klass_device->cleanup = fu_uefi_device_cleanup; klass_device->report_metadata_pre = fu_uefi_device_report_metadata_pre; klass_device->report_metadata_post = fu_uefi_device_report_metadata_post; klass_device->get_results = fu_uefi_device_get_results; klass_device->set_progress = fu_uefi_device_set_progress; klass_device->convert_version = fu_uefi_device_convert_version; /** * FuUefiDevice:fw-class: * * The firmware class, i.e. the ESRT GUID. */ pspec = g_param_spec_string("fw-class", NULL, NULL, NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FW_CLASS, pspec); /** * FuUefiDevice:kind: * * The device kind. */ pspec = g_param_spec_uint("kind", NULL, NULL, FU_UEFI_DEVICE_KIND_UNKNOWN, FU_UEFI_DEVICE_KIND_LAST - 1, FU_UEFI_DEVICE_KIND_UNKNOWN, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_KIND, pspec); /** * FuUefiDevice:capsule-flags: * * The capsule flags to use for the update. */ pspec = g_param_spec_uint("capsule-flags", NULL, NULL, 0, G_MAXUINT32, 0, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_CAPSULE_FLAGS, pspec); /** * FuUefiDevice:fw-version: * * The current firmware version. */ pspec = g_param_spec_uint("fw-version", NULL, NULL, 0, G_MAXUINT32, 0, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FW_VERSION, pspec); /** * FuUefiDevice:fw-version-lowest: * * The lowest possible installable version. */ pspec = g_param_spec_uint("fw-version-lowest", NULL, NULL, 0, G_MAXUINT32, 0, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FW_VERSION_LOWEST, pspec); /** * FuUefiDevice:last-attempt-status: * * The last attempt status value. */ pspec = g_param_spec_uint("last-attempt-status", NULL, NULL, FU_UEFI_DEVICE_STATUS_SUCCESS, FU_UEFI_DEVICE_STATUS_LAST - 1, FU_UEFI_DEVICE_STATUS_SUCCESS, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_LAST_ATTEMPT_STATUS, pspec); /** * FuUefiDevice:last-attempt-version: * * The last attempt firmware version. */ pspec = g_param_spec_uint("last-attempt-version", NULL, NULL, 0, G_MAXUINT32, 0, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_LAST_ATTEMPT_VERSION, pspec); /** * FuUefiDevice:fmp-hardware-instance: * * The FMP hardware instance. */ pspec = g_param_spec_uint64("fmp-hardware-instance", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FMP_HARDWARE_INSTANCE, pspec); } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-device.h000066400000000000000000000071301460375044200217540ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2015 Peter Jones * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-uefi-device.h" #include "fu-uefi-update-info.h" #define FU_TYPE_UEFI_DEVICE (fu_uefi_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuUefiDevice, fu_uefi_device, FU, UEFI_DEVICE, FuDevice) struct _FuUefiDeviceClass { FuDeviceClass parent_class; }; /** * FU_UEFI_DEVICE_FLAG_NO_UX_CAPSULE: * * No not use the additional UX capsule. */ #define FU_UEFI_DEVICE_FLAG_NO_UX_CAPSULE (1 << 0) /** * FU_UEFI_DEVICE_FLAG_USE_SHIM_UNIQUE: * * Use a unique shim filename to work around a common BIOS bug. */ #define FU_UEFI_DEVICE_FLAG_USE_SHIM_UNIQUE (1 << 1) /** * FU_UEFI_DEVICE_FLAG_USE_LEGACY_BOOTMGR_DESC: * * Use the legacy boot manager description to work around a Lenovo BIOS bug. */ #define FU_UEFI_DEVICE_FLAG_USE_LEGACY_BOOTMGR_DESC (1 << 2) /** * FU_UEFI_DEVICE_FLAG_SUPPORTS_BOOT_ORDER_LOCK: * * The BIOS might have Boot Order Lock enabled which can cause failures when * not using grub chainloading or capsule-on-disk. */ #define FU_UEFI_DEVICE_FLAG_SUPPORTS_BOOT_ORDER_LOCK (1 << 3) /** * FU_UEFI_DEVICE_FLAG_USE_SHIM_FOR_SB: * * Use shim to load fwupdx64.efi when SecureBoot is turned on. */ #define FU_UEFI_DEVICE_FLAG_USE_SHIM_FOR_SB (1 << 5) /** * FU_UEFI_DEVICE_FLAG_NO_RT_SET_VARIABLE: * * Do not use RT->SetVariable. */ #define FU_UEFI_DEVICE_FLAG_NO_RT_SET_VARIABLE (1 << 6) /** * FU_UEFI_DEVICE_FLAG_NO_CAPSULE_HEADER_FIXUP: * * Do not prepend a plausible missing capsule header. */ #define FU_UEFI_DEVICE_FLAG_NO_CAPSULE_HEADER_FIXUP (1 << 7) /** * FU_UEFI_DEVICE_FLAG_ENABLE_EFI_DEBUGGING: * * Enable debugging the EFI binary. */ #define FU_UEFI_DEVICE_FLAG_ENABLE_EFI_DEBUGGING (1 << 8) /** * FU_UEFI_DEVICE_FLAG_COD_INDEXED_FILENAME: * * Use a Capsule-on-Disk filename of `CapsuleUpdateFileXXXX.bin`. */ #define FU_UEFI_DEVICE_FLAG_COD_INDEXED_FILENAME (1 << 9) /** * FU_UEFI_DEVICE_FLAG_MODIFY_BOOTORDER: * * Modify `BootOrder` as well as `BootNext` to work around BIOS bugs. */ #define FU_UEFI_DEVICE_FLAG_MODIFY_BOOTORDER (1 << 10) void fu_uefi_device_set_esp(FuUefiDevice *self, FuVolume *esp); gboolean fu_uefi_device_clear_status(FuUefiDevice *self, GError **error); FuUefiDeviceKind fu_uefi_device_get_kind(FuUefiDevice *self); const gchar * fu_uefi_device_get_guid(FuUefiDevice *self); FuVolume * fu_uefi_device_get_esp(FuUefiDevice *self); gchar * fu_uefi_device_build_varname(FuUefiDevice *self); guint32 fu_uefi_device_get_version(FuUefiDevice *self); guint32 fu_uefi_device_get_version_lowest(FuUefiDevice *self); guint32 fu_uefi_device_get_version_error(FuUefiDevice *self); guint32 fu_uefi_device_get_capsule_flags(FuUefiDevice *self); guint64 fu_uefi_device_get_hardware_instance(FuUefiDevice *self); FuUefiDeviceStatus fu_uefi_device_get_status(FuUefiDevice *self); FuUefiUpdateInfo * fu_uefi_device_load_update_info(FuUefiDevice *self, GError **error); gboolean fu_uefi_device_write_update_info(FuUefiDevice *self, const gchar *capsule_path, const gchar *varname, const gchar *guid, GError **error); GBytes * fu_uefi_device_fixup_firmware(FuUefiDevice *self, GBytes *fw, GError **error); void fu_uefi_device_set_status(FuUefiDevice *self, FuUefiDeviceStatus status); void fu_uefi_device_set_require_esp_free_space(FuUefiDevice *self, gsize require_esp_free_space); gboolean fu_uefi_device_perhaps_enable_debugging(FuUefiDevice *self, GError **error); FuEfiDevicePathList * fu_uefi_device_build_dp_buf(FuVolume *esp, const gchar *capsule_path, GError **error); fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-grub-device.c000066400000000000000000000141651460375044200227120ustar00rootroot00000000000000/* * Copyright (C) 2021 Mario Limonciello * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uefi-common.h" #include "fu-uefi-grub-device.h" struct _FuUefiGrubDevice { FuUefiDevice parent_instance; }; G_DEFINE_TYPE(FuUefiGrubDevice, fu_uefi_grub_device, FU_TYPE_UEFI_DEVICE) static gboolean fu_uefi_grub_device_mkconfig(FuDevice *device, const gchar *esp_path, const gchar *target_app, GError **error) { const gchar *argv_mkconfig[] = {"", "-o", "/boot/grub/grub.cfg", NULL}; const gchar *argv_reboot[] = {"", "fwupd", NULL}; g_autofree gchar *grub_mkconfig = NULL; g_autofree gchar *grub_reboot = NULL; g_autofree gchar *grub_target = NULL; g_autofree gchar *localstatedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); g_autofree gchar *output = NULL; g_autoptr(GString) str = g_string_new(NULL); /* find grub.conf */ if (!g_file_test(argv_mkconfig[2], G_FILE_TEST_EXISTS)) argv_mkconfig[2] = "/boot/grub2/grub.cfg"; if (!g_file_test(argv_mkconfig[2], G_FILE_TEST_EXISTS)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "could not find grub.conf"); return FALSE; } /* find grub-mkconfig */ grub_mkconfig = fu_path_find_program("grub-mkconfig", NULL); if (grub_mkconfig == NULL) grub_mkconfig = fu_path_find_program("grub2-mkconfig", NULL); if (grub_mkconfig == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "could not find grub-mkconfig"); return FALSE; } /* find grub-reboot */ grub_reboot = fu_path_find_program("grub-reboot", NULL); if (grub_reboot == NULL) grub_reboot = fu_path_find_program("grub2-reboot", NULL); if (grub_reboot == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "could not find grub-reboot"); return FALSE; } /* replace ESP info in conf with what we detected */ g_string_append_printf(str, "EFI_PATH=%s\n", target_app); g_string_replace(str, esp_path, "", 0); g_string_append_printf(str, "ESP=%s\n", esp_path); grub_target = g_build_filename(localstatedir, "uefi_capsule.conf", NULL); if (!g_file_set_contents(grub_target, str->str, -1, error)) return FALSE; /* refresh GRUB configuration */ argv_mkconfig[0] = grub_mkconfig; if (!g_spawn_sync(NULL, (gchar **)argv_mkconfig, NULL, G_SPAWN_DEFAULT, NULL, NULL, &output, NULL, NULL, error)) return FALSE; g_debug("%s", output); /* make fwupd default */ argv_reboot[0] = grub_reboot; return g_spawn_sync(NULL, (gchar **)argv_reboot, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL, NULL, error); } static gboolean fu_uefi_grub_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuVolume *esp = fu_uefi_device_get_esp(self); const gchar *fw_class = fu_uefi_device_get_guid(self); g_autofree gchar *basename = NULL; g_autofree gchar *capsule_path = NULL; g_autofree gchar *directory = NULL; g_autofree gchar *esp_path = fu_volume_get_mount_point(esp); g_autofree gchar *fn = NULL; g_autofree gchar *source_app = NULL; g_autofree gchar *target_app = NULL; g_autofree gchar *varname = fu_uefi_device_build_varname(self); g_autoptr(GBytes) fixed_fw = NULL; g_autoptr(GBytes) fw = NULL; /* ensure we have the existing state */ if (fw_class == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot update device info with no GUID"); return FALSE; } /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* save the blob to the ESP */ directory = fu_uefi_get_esp_path_for_os(); basename = g_strdup_printf("fwupd-%s.cap", fw_class); capsule_path = g_build_filename(directory, "fw", basename, NULL); fn = g_build_filename(esp_path, capsule_path, NULL); if (!fu_path_mkdir_parent(fn, error)) return FALSE; fixed_fw = fu_uefi_device_fixup_firmware(self, fw, error); if (fixed_fw == NULL) return FALSE; if (!fu_bytes_set_contents(fn, fixed_fw, error)) return FALSE; /* skip for self tests */ if (g_getenv("FWUPD_UEFI_TEST") != NULL) return TRUE; /* enable debugging in the EFI binary */ if (!fu_uefi_device_perhaps_enable_debugging(self, error)) return FALSE; /* delete the old log to save space */ if (fu_efivar_exists(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_DEBUG_LOG")) { if (!fu_efivar_delete(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_DEBUG_LOG", error)) return FALSE; } /* set the blob header shared with fwupd.efi */ if (!fu_uefi_device_write_update_info(self, capsule_path, varname, fw_class, error)) return FALSE; /* if secure boot was turned on this might need to be installed separately */ source_app = fu_uefi_get_built_app_path("fwupd", error); if (source_app == NULL) return FALSE; /* test if correct asset in place */ target_app = fu_uefi_get_esp_app_path("fwupd", error); if (target_app == NULL) return FALSE; if (!fu_uefi_esp_target_verify(source_app, esp, target_app)) { if (!fu_uefi_esp_target_copy(source_app, esp, target_app, error)) return FALSE; } /* we are using GRUB instead of NVRAM variables */ return fu_uefi_grub_device_mkconfig(device, esp_path, target_app, error); } static void fu_uefi_grub_device_report_metadata_pre(FuDevice *device, GHashTable *metadata) { /* FuUefiDevice */ FU_DEVICE_CLASS(fu_uefi_grub_device_parent_class)->report_metadata_pre(device, metadata); g_hash_table_insert(metadata, g_strdup("CapsuleApplyMethod"), g_strdup("grub")); } static void fu_uefi_grub_device_init(FuUefiGrubDevice *self) { fu_device_set_summary(FU_DEVICE(self), "UEFI System Resource Table device (updated via grub)"); } static void fu_uefi_grub_device_class_init(FuUefiGrubDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_uefi_grub_device_write_firmware; klass_device->report_metadata_pre = fu_uefi_grub_device_report_metadata_pre; } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-grub-device.h000066400000000000000000000005231460375044200227100ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-uefi-device.h" #define FU_TYPE_UEFI_GRUB_DEVICE (fu_uefi_grub_device_get_type()) G_DECLARE_FINAL_TYPE(FuUefiGrubDevice, fu_uefi_grub_device, FU, UEFI_GRUB_DEVICE, FuUefiDevice) fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-nvram-device.c000066400000000000000000000115451460375044200230750ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2018 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uefi-bootmgr.h" #include "fu-uefi-common.h" #include "fu-uefi-nvram-device.h" struct _FuUefiNvramDevice { FuUefiDevice parent_instance; }; G_DEFINE_TYPE(FuUefiNvramDevice, fu_uefi_nvram_device, FU_TYPE_UEFI_DEVICE) static gboolean fu_uefi_nvram_device_get_results(FuDevice *device, GError **error) { g_autoptr(GError) error_local = NULL; /* check if something rudely removed our BOOTXXXX entry */ if (!fu_uefi_bootmgr_verify_fwupd(&error_local)) { if (fu_device_has_private_flag(device, FU_UEFI_DEVICE_FLAG_SUPPORTS_BOOT_ORDER_LOCK)) { g_prefix_error(&error_local, "boot entry missing; " "perhaps 'Boot Order Lock' enabled in the BIOS: "); fu_device_set_update_state(device, FWUPD_UPDATE_STATE_FAILED_TRANSIENT); } else { g_prefix_error(&error_local, "boot entry missing: "); fu_device_set_update_state(device, FWUPD_UPDATE_STATE_FAILED); } fu_device_set_update_error(device, error_local->message); return TRUE; } /* parent */ return FU_DEVICE_CLASS(fu_uefi_nvram_device_parent_class)->get_results(device, error); } static gboolean fu_uefi_nvram_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuUefiDevice *self = FU_UEFI_DEVICE(device); FuUefiBootmgrFlags bootmgr_flags = FU_UEFI_BOOTMGR_FLAG_NONE; const gchar *bootmgr_desc = "Linux Firmware Updater"; const gchar *fw_class = fu_uefi_device_get_guid(self); FuVolume *esp = fu_uefi_device_get_esp(self); g_autofree gchar *esp_path = fu_volume_get_mount_point(esp); g_autoptr(GBytes) fixed_fw = NULL; g_autoptr(GBytes) fw = NULL; g_autofree gchar *capsule_path = NULL; g_autofree gchar *basename = NULL; g_autofree gchar *directory = NULL; g_autofree gchar *fn = NULL; g_autofree gchar *varname = fu_uefi_device_build_varname(self); /* ensure we have the existing state */ if (fw_class == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "cannot update device info with no GUID"); return FALSE; } /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* save the blob to the ESP */ directory = fu_uefi_get_esp_path_for_os(); basename = g_strdup_printf("fwupd-%s.cap", fw_class); capsule_path = g_build_filename(directory, "fw", basename, NULL); fn = g_build_filename(esp_path, capsule_path, NULL); if (!fu_path_mkdir_parent(fn, error)) return FALSE; fixed_fw = fu_uefi_device_fixup_firmware(self, fw, error); if (fixed_fw == NULL) return FALSE; if (!fu_bytes_set_contents(fn, fixed_fw, error)) return FALSE; /* enable debugging in the EFI binary */ if (!fu_uefi_device_perhaps_enable_debugging(self, error)) return FALSE; /* delete the old log to save space */ if (fu_efivar_exists(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_DEBUG_LOG")) { if (!fu_efivar_delete(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_DEBUG_LOG", error)) return FALSE; } /* set the blob header shared with fwupd.efi */ if (!fu_uefi_device_write_update_info(self, capsule_path, varname, fw_class, error)) return FALSE; /* update the firmware before the bootloader runs */ if (fu_device_has_private_flag(device, FU_UEFI_DEVICE_FLAG_USE_SHIM_FOR_SB)) bootmgr_flags |= FU_UEFI_BOOTMGR_FLAG_USE_SHIM_FOR_SB; if (fu_device_has_private_flag(device, FU_UEFI_DEVICE_FLAG_USE_SHIM_UNIQUE)) bootmgr_flags |= FU_UEFI_BOOTMGR_FLAG_USE_SHIM_UNIQUE; if (fu_device_has_private_flag(device, FU_UEFI_DEVICE_FLAG_MODIFY_BOOTORDER)) bootmgr_flags |= FU_UEFI_BOOTMGR_FLAG_MODIFY_BOOTORDER; /* some legacy devices use the old name to deduplicate boot entries */ if (fu_device_has_private_flag(device, FU_UEFI_DEVICE_FLAG_USE_LEGACY_BOOTMGR_DESC)) bootmgr_desc = "Linux-Firmware-Updater"; if (!fu_uefi_bootmgr_bootnext(esp, bootmgr_desc, bootmgr_flags, error)) return FALSE; /* success! */ return TRUE; } static void fu_uefi_nvram_device_report_metadata_pre(FuDevice *device, GHashTable *metadata) { /* FuUefiDevice */ FU_DEVICE_CLASS(fu_uefi_nvram_device_parent_class)->report_metadata_pre(device, metadata); g_hash_table_insert(metadata, g_strdup("CapsuleApplyMethod"), g_strdup("nvram")); } static void fu_uefi_nvram_device_init(FuUefiNvramDevice *self) { fu_device_set_summary(FU_DEVICE(self), "UEFI System Resource Table device (updated via NVRAM)"); } static void fu_uefi_nvram_device_class_init(FuUefiNvramDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->get_results = fu_uefi_nvram_device_get_results; klass_device->write_firmware = fu_uefi_nvram_device_write_firmware; klass_device->report_metadata_pre = fu_uefi_nvram_device_report_metadata_pre; } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-nvram-device.h000066400000000000000000000005301460375044200230720ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-uefi-device.h" #define FU_TYPE_UEFI_NVRAM_DEVICE (fu_uefi_nvram_device_get_type()) G_DECLARE_FINAL_TYPE(FuUefiNvramDevice, fu_uefi_nvram_device, FU, UEFI_NVRAM_DEVICE, FuUefiDevice) fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-tool.c000066400000000000000000000371471460375044200215000ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include #include "fu-context-private.h" #include "fu-uefi-backend.h" #include "fu-uefi-cod-device.h" #include "fu-uefi-common.h" #include "fu-uefi-grub-device.h" #include "fu-uefi-nvram-device.h" #include "fu-uefi-update-info.h" /* custom return code */ #define EXIT_NOTHING_TO_DO 2 typedef struct { GCancellable *cancellable; GMainLoop *loop; GOptionContext *context; } FuUtilPrivate; static void fu_util_ignore_cb(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { } static guint fu_util_prompt_for_number(guint maxnum) { gint retval; guint answer = 0; do { char buffer[64]; /* swallow the \n at end of line too */ if (!fgets(buffer, sizeof(buffer), stdin)) break; if (strlen(buffer) == sizeof(buffer) - 1) continue; /* get a number */ retval = sscanf(buffer, "%u", &answer); /* positive */ if (retval == 1 && answer <= maxnum) break; /* TRANSLATORS: the user isn't reading the question */ g_print(_("Please enter a number from 0 to %u: "), maxnum); } while (TRUE); return answer; } static void fu_util_private_free(FuUtilPrivate *priv) { if (priv->context != NULL) g_option_context_free(priv->context); g_free(priv); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free) #pragma clang diagnostic pop int main(int argc, char *argv[]) { gboolean action_enable = FALSE; gboolean action_info = FALSE; gboolean action_list = FALSE; gboolean action_log = FALSE; gboolean action_set_debug = FALSE; gboolean action_supported = FALSE; gboolean action_unset_debug = FALSE; gboolean action_version = FALSE; gboolean ret; gboolean verbose = FALSE; g_autofree gchar *apply = FALSE; g_autofree gchar *type = FALSE; g_autofree gchar *esp_path = NULL; g_autofree gchar *flags = FALSE; g_autoptr(FuUtilPrivate) priv = g_new0(FuUtilPrivate, 1); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) esp_volumes = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuVolume) esp = NULL; const GOptionEntry options[] = { {"verbose", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &verbose, /* TRANSLATORS: command line option */ N_("Show extra debugging information"), NULL}, {"version", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_version, /* TRANSLATORS: command line option */ N_("Display version"), NULL}, {"log", 'L', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_log, /* TRANSLATORS: command line option */ N_("Show the debug log from the last attempted update"), NULL}, {"list", 'l', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_list, /* TRANSLATORS: command line option */ N_("List supported firmware updates"), NULL}, {"supported", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_supported, /* TRANSLATORS: command line option */ N_("Query for firmware update support"), NULL}, {"info", 'i', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_info, /* TRANSLATORS: command line option */ N_("Show the information of firmware update status"), NULL}, {"enable", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_enable, /* TRANSLATORS: command line option */ N_("Enable firmware update support on supported systems"), NULL}, {"esp-path", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &esp_path, /* TRANSLATORS: command line option */ N_("Override the default ESP path"), /* TRANSLATORS: command argument: uppercase, spaces->dashes */ N_("PATH")}, {"set-debug", 'd', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_set_debug, /* TRANSLATORS: command line option */ N_("Set the debugging flag during update"), NULL}, {"unset-debug", 'D', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &action_unset_debug, /* TRANSLATORS: command line option */ N_("Unset the debugging flag during update"), NULL}, {"apply", 'a', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &apply, /* TRANSLATORS: command line option */ N_("Apply firmware updates"), /* TRANSLATORS: command argument: uppercase, spaces->dashes */ NC_("A single GUID", "GUID")}, {"method", 'm', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &type, /* TRANSLATORS: command line option */ N_("Device update method"), "nvram|cod|grub"}, {"flags", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &flags, /* TRANSLATORS: command line option */ N_("Use quirk flags when installing firmware"), NULL}, {NULL}}; setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); #ifndef SUPPORTED_BUILD /* make critical warnings fatal */ (void)g_setenv("G_DEBUG", "fatal-criticals", FALSE); #endif /* ensure root user */ #ifdef HAVE_GETUID if (getuid() != 0 || geteuid() != 0) /* TRANSLATORS: we're poking around as a power user */ g_printerr("%s\n", _("This program may only work correctly as root")); #endif /* get a action_list of the commands */ priv->context = g_option_context_new(NULL); g_option_context_set_description( priv->context, /* TRANSLATORS: CLI description */ _("This tool allows an administrator to debug UpdateCapsule operation.")); /* TRANSLATORS: program name */ g_set_application_name(_("UEFI Firmware Utility")); g_option_context_add_main_entries(priv->context, options, NULL); ret = g_option_context_parse(priv->context, &argc, &argv, &error); if (!ret) { /* TRANSLATORS: the user didn't read the man page */ g_print("%s: %s\n", _("Failed to parse arguments"), error->message); return EXIT_FAILURE; } /* set verbose? */ if (verbose) { (void)g_setenv("G_MESSAGES_DEBUG", "all", FALSE); } else { g_log_set_handler(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, fu_util_ignore_cb, NULL); } /* nothing specified */ if (!action_enable && !action_info && !action_list && !action_log && !action_set_debug && !action_supported && !action_unset_debug && !action_version && apply == NULL) { g_autofree gchar *tmp = NULL; tmp = g_option_context_get_help(priv->context, TRUE, NULL); g_printerr("%s\n\n%s", _("No action specified!"), tmp); return EXIT_FAILURE; } /* action_version first */ if (action_version) g_print("fwupd version: %s\n", PACKAGE_VERSION); /* override the default ESP path */ if (esp_path != NULL) { g_autoptr(FuVolume) volume = NULL; volume = fu_volume_new_esp_for_path(esp_path, &error); if (volume == NULL) { /* TRANSLATORS: ESP is EFI System Partition */ g_print("%s: %s\n", _("ESP specified was not valid"), error->message); return EXIT_FAILURE; } fu_context_add_esp_volume(ctx, volume); } /* get the default ESP only */ esp_volumes = fu_context_get_esp_volumes(ctx, &error); if (esp_volumes == NULL) { g_printerr("%s\n", error->message); return EXIT_FAILURE; } if (esp_volumes->len > 1) { guint idx; /* TRANSLATORS: get interactive prompt */ g_print("%s\n", _("Choose the ESP:")); /* TRANSLATORS: this is to abort the interactive prompt */ g_print("0.\t%s\n", _("Cancel")); for (guint i = 0; i < esp_volumes->len; i++) { FuVolume *esp_tmp = g_ptr_array_index(esp_volumes, i); g_autofree gchar *id_type = fu_volume_get_id_type(esp_tmp); g_print("%u.\t%s (%s)\n", i + 1, fu_volume_get_id(esp_tmp), id_type); } idx = fu_util_prompt_for_number(esp_volumes->len); if (idx == 0) { /* TRANSLATORS: we asked the user to choose an option and they declined */ g_printerr("%s\n", _("Request canceled")); return EXIT_FAILURE; } esp = g_object_ref(g_ptr_array_index(esp_volumes, idx - 1)); } else { esp = g_object_ref(g_ptr_array_index(esp_volumes, 0)); } /* show the debug action_log from the last attempted update */ if (action_log) { g_autofree gchar *str = NULL; g_autoptr(GBytes) buf = NULL; g_autoptr(GError) error_local = NULL; buf = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_DEBUG_LOG", NULL, &error_local); if (buf == NULL) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } str = fu_utf16_to_utf8_bytes(buf, G_LITTLE_ENDIAN, &error_local); if (str == NULL) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } g_print("%s", str); } if (action_list || action_supported || action_info) { g_autoptr(FuBackend) backend = fu_uefi_backend_new(ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error_local = NULL; /* load SMBIOS */ if (!fu_context_load_hwinfo(ctx, progress, FU_CONTEXT_HWID_FLAG_LOAD_ALL, &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } /* add each device */ if (!fu_backend_coldplug(backend, progress, &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } /* set ESP */ devices = fu_backend_get_devices(backend); for (guint i = 0; i < devices->len; i++) { FuUefiDevice *dev = g_ptr_array_index(devices, i); fu_uefi_device_set_esp(dev, esp); } } /* action_list action_supported firmware updates */ if (action_list) { for (guint i = 0; i < devices->len; i++) { FuUefiDevice *dev = g_ptr_array_index(devices, i); g_print("%s type, {%s} version %" G_GUINT32_FORMAT " can be updated " "to any version above %" G_GUINT32_FORMAT "\n", fu_uefi_device_kind_to_string(fu_uefi_device_get_kind(dev)), fu_uefi_device_get_guid(dev), fu_uefi_device_get_version(dev), fu_uefi_device_get_version_lowest(dev) - 1); } } /* query for firmware update support */ if (action_supported) { if (devices->len > 0) { g_print("%s\n", _("Firmware updates are supported on this machine.")); } else { g_print("%s\n", _("Firmware updates are not supported on this machine.")); } } /* show the information of firmware update status */ if (action_info) { for (guint i = 0; i < devices->len; i++) { FuUefiDevice *dev = g_ptr_array_index(devices, i); g_autoptr(FuUefiUpdateInfo) info = NULL; g_autoptr(GError) error_local = NULL; /* load any existing update info */ info = fu_uefi_device_load_update_info(dev, &error_local); g_print("Information for the update status entry %u:\n", i); if (info == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_print(" Firmware GUID: {%s}\n", fu_uefi_device_get_guid(dev)); g_print(" Update Status: No update info found\n\n"); } else { g_printerr("Failed: %s\n\n", error_local->message); } continue; } g_print(" Information Version: %" G_GUINT32_FORMAT "\n", (guint32)fu_firmware_get_version_raw(FU_FIRMWARE(info))); g_print(" Firmware GUID: {%s}\n", fu_uefi_update_info_get_guid(info)); g_print(" Capsule Flags: 0x%08" G_GUINT32_FORMAT "x\n", fu_uefi_update_info_get_capsule_flags(info)); g_print(" Hardware Instance: %" G_GUINT64_FORMAT "\n", fu_uefi_update_info_get_hw_inst(info)); g_print(" Update Status: %s\n", fu_uefi_update_info_status_to_string( fu_uefi_update_info_get_status(info))); g_print(" Capsule File Path: %s\n\n", fu_uefi_update_info_get_capsule_fn(info)); } } /* action_enable firmware update support on action_supported systems */ if (action_enable) { g_printerr("Unsupported, use `fwupdmgr unlock`\n"); return EXIT_FAILURE; } /* set the debugging flag during update */ if (action_set_debug) { const guint8 data = 1; g_autoptr(GError) error_local = NULL; if (!fu_efivar_set_data(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_VERBOSE", &data, sizeof(data), FU_EFIVAR_ATTR_NON_VOLATILE | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS, &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } g_print("%s\n", _("Enabled fwupdate debugging")); } /* unset the debugging flag during update */ if (action_unset_debug) { g_autoptr(GError) error_local = NULL; if (!fu_efivar_delete(FU_EFIVAR_GUID_FWUPDATE, "FWUPDATE_VERBOSE", &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } g_print("%s\n", _("Disabled fwupdate debugging")); } /* apply firmware updates */ if (apply != NULL) { g_autoptr(FuBackend) backend = fu_uefi_backend_new(ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(FuUefiDevice) dev = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GBytes) fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "hwinfo"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "prepare"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "cleanup"); /* load SMBIOS */ if (!fu_context_load_hwinfo(ctx, fu_progress_get_child(progress), FU_CONTEXT_HWID_FLAG_LOAD_ALL, &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } fu_progress_step_done(progress); /* type is specified, otherwise use default */ if (type != NULL) { if (g_strcmp0(type, "nvram") == 0) { fu_uefi_backend_set_device_gtype(FU_UEFI_BACKEND(backend), FU_TYPE_UEFI_NVRAM_DEVICE); } else if (g_strcmp0(type, "grub") == 0) { fu_uefi_backend_set_device_gtype(FU_UEFI_BACKEND(backend), FU_TYPE_UEFI_GRUB_DEVICE); } else if (g_strcmp0(type, "cod") == 0) { fu_uefi_backend_set_device_gtype(FU_UEFI_BACKEND(backend), FU_TYPE_UEFI_COD_DEVICE); } else { g_printerr("invalid type specified\n"); return EXIT_FAILURE; } } if (argv[1] == NULL) { g_printerr("capsule filename required\n"); return EXIT_FAILURE; } fw = fu_bytes_get_contents(argv[1], &error_local); if (fw == NULL) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } dev = fu_uefi_backend_device_new_from_guid(FU_UEFI_BACKEND(backend), apply); fu_uefi_device_set_esp(dev, esp); if (flags != NULL) fu_device_set_custom_flags(FU_DEVICE(dev), flags); if (!fu_device_prepare(FU_DEVICE(dev), fu_progress_get_child(progress), FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } fu_progress_step_done(progress); if (!fu_device_write_firmware(FU_DEVICE(dev), fw, fu_progress_get_child(progress), FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } fu_progress_step_done(progress); if (!fu_device_cleanup(FU_DEVICE(dev), fu_progress_get_child(progress), FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_printerr("failed: %s\n", error_local->message); return EXIT_FAILURE; } fu_progress_step_done(progress); } /* success */ return EXIT_SUCCESS; } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-update-info.c000066400000000000000000000102201460375044200227150ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uefi-common.h" #include "fu-uefi-update-info.h" #define EFIDP_MEDIA_TYPE 0x04 #define EFIDP_MEDIA_FILE 0x4 struct _FuUefiUpdateInfo { FuFirmware parent_instance; gchar *guid; gchar *capsule_fn; guint32 capsule_flags; guint64 hw_inst; FuUefiUpdateInfoStatus status; }; G_DEFINE_TYPE(FuUefiUpdateInfo, fu_uefi_update_info, FU_TYPE_FIRMWARE) static void fu_uefi_update_info_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuUefiUpdateInfo *self = FU_UEFI_UPDATE_INFO(firmware); fu_xmlb_builder_insert_kv(bn, "guid", self->guid); fu_xmlb_builder_insert_kv(bn, "capsule_fn", self->capsule_fn); fu_xmlb_builder_insert_kx(bn, "capsule_flags", self->capsule_flags); fu_xmlb_builder_insert_kx(bn, "hw_inst", self->hw_inst); fu_xmlb_builder_insert_kv(bn, "status", fu_uefi_update_info_status_to_string(self->status)); } static gboolean fu_uefi_update_info_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuUefiUpdateInfo *self = FU_UEFI_UPDATE_INFO(firmware); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GByteArray) st_inf = NULL; g_return_val_if_fail((self), FALSE); st_inf = fu_struct_efi_update_info_parse(buf, bufsz, 0x0, error); if (st_inf == NULL) { g_prefix_error(error, "EFI variable is corrupt: "); return FALSE; } fu_firmware_set_version_raw(firmware, fu_struct_efi_update_info_get_version(st_inf)); self->capsule_flags = fu_struct_efi_update_info_get_flags(st_inf); self->hw_inst = fu_struct_efi_update_info_get_hw_inst(st_inf); self->status = fu_struct_efi_update_info_get_status(st_inf); self->guid = fwupd_guid_to_string(fu_struct_efi_update_info_get_guid(st_inf), FWUPD_GUID_FLAG_MIXED_ENDIAN); if (bufsz > FU_STRUCT_EFI_UPDATE_INFO_SIZE) { g_autoptr(FuFirmware) dp = NULL; g_autoptr(FuEfiDevicePathList) dpbuf = fu_efi_device_path_list_new(); if (!fu_firmware_parse_full(FU_FIRMWARE(dpbuf), fw, FU_STRUCT_EFI_UPDATE_INFO_SIZE, FWUPD_INSTALL_FLAG_NONE, error)) { g_prefix_error(error, "failed to parse dpbuf: "); return FALSE; } dp = fu_firmware_get_image_by_gtype(FU_FIRMWARE(dpbuf), FU_TYPE_EFI_FILE_PATH_DEVICE_PATH, error); if (dp == NULL) return FALSE; self->capsule_fn = fu_efi_file_path_device_path_get_name(FU_EFI_FILE_PATH_DEVICE_PATH(dp), error); if (self->capsule_fn == NULL) return FALSE; } return TRUE; } const gchar * fu_uefi_update_info_get_guid(FuUefiUpdateInfo *self) { g_return_val_if_fail(FU_IS_UEFI_UPDATE_INFO(self), NULL); return self->guid; } const gchar * fu_uefi_update_info_get_capsule_fn(FuUefiUpdateInfo *self) { g_return_val_if_fail(FU_IS_UEFI_UPDATE_INFO(self), NULL); return self->capsule_fn; } guint32 fu_uefi_update_info_get_capsule_flags(FuUefiUpdateInfo *self) { g_return_val_if_fail(FU_IS_UEFI_UPDATE_INFO(self), 0); return self->capsule_flags; } guint64 fu_uefi_update_info_get_hw_inst(FuUefiUpdateInfo *self) { g_return_val_if_fail(FU_IS_UEFI_UPDATE_INFO(self), 0); return self->hw_inst; } FuUefiUpdateInfoStatus fu_uefi_update_info_get_status(FuUefiUpdateInfo *self) { g_return_val_if_fail(FU_IS_UEFI_UPDATE_INFO(self), 0); return self->status; } static void fu_uefi_update_info_finalize(GObject *object) { FuUefiUpdateInfo *self = FU_UEFI_UPDATE_INFO(object); g_free(self->guid); g_free(self->capsule_fn); G_OBJECT_CLASS(fu_uefi_update_info_parent_class)->finalize(object); } static void fu_uefi_update_info_class_init(FuUefiUpdateInfoClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); klass_firmware->parse = fu_uefi_update_info_parse; klass_firmware->export = fu_uefi_update_info_export; object_class->finalize = fu_uefi_update_info_finalize; } static void fu_uefi_update_info_init(FuUefiUpdateInfo *self) { } FuUefiUpdateInfo * fu_uefi_update_info_new(void) { FuUefiUpdateInfo *self; self = g_object_new(FU_TYPE_UEFI_UPDATE_INFO, NULL); return FU_UEFI_UPDATE_INFO(self); } fwupd-1.9.16/plugins/uefi-capsule/fu-uefi-update-info.h000066400000000000000000000013511460375044200227270ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-uefi-struct.h" #define FU_TYPE_UEFI_UPDATE_INFO (fu_uefi_update_info_get_type()) G_DECLARE_FINAL_TYPE(FuUefiUpdateInfo, fu_uefi_update_info, FU, UEFI_UPDATE_INFO, FuFirmware) FuUefiUpdateInfo * fu_uefi_update_info_new(void); const gchar * fu_uefi_update_info_get_guid(FuUefiUpdateInfo *self); const gchar * fu_uefi_update_info_get_capsule_fn(FuUefiUpdateInfo *self); guint32 fu_uefi_update_info_get_capsule_flags(FuUefiUpdateInfo *self); guint64 fu_uefi_update_info_get_hw_inst(FuUefiUpdateInfo *self); FuUefiUpdateInfoStatus fu_uefi_update_info_get_status(FuUefiUpdateInfo *self); fwupd-1.9.16/plugins/uefi-capsule/fu-uefi.rs000066400000000000000000000025141460375044200207150ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, Getters)] struct EfiUxCapsuleHeader { version: u8 == 0x01, checksum: u8, image_type: u8, _reserved: u8, mode: u32le, x_offset: u32le, y_offset: u32le, } #[derive(New, Getters)] struct EfiCapsuleHeader { guid: Guid, header_size: u32le = $struct_size, flags: u32le, image_size: u32le, } #[derive(ToString)] #[repr(u32le)] enum UefiUpdateInfoStatus { Unknown, AttemptUpdate, Attempted, } #[derive(New, Parse)] struct EfiUpdateInfo { version: u32le = 0x7, guid: Guid, flags: u32le, hw_inst: u64le, time_attempted: [u8; 16], // a EFI_TIME_T status: UefiUpdateInfoStatus, // EFI_DEVICE_PATH goes here } #[derive(Parse)] struct AcpiInsydeQuirk { signature: [char; 6], size: u32le, flags: u32le, } #[derive(ToString, FromString)] enum UefiDeviceKind { Unknown, SystemFirmware, DeviceFirmware, UefiDriver, Fmp, DellTpmFirmware, Last, } #[derive(ToString)] enum UefiDeviceStatus { Success = 0x00, ErrorUnsuccessful = 0x01, ErrorInsufficientResources = 0x02, ErrorIncorrectVersion = 0x03, ErrorInvalidFormat = 0x04, ErrorAuthError = 0x05, ErrorPwrEvtAc = 0x06, ErrorPwrEvtBatt = 0x07, Last, } fwupd-1.9.16/plugins/uefi-capsule/fwupd.grub.conf.in000077500000000000000000000013501460375044200223460ustar00rootroot00000000000000#! /bin/sh # SPDX-License-Identifier: LGPL-2.1+ set -e [ -d ${pkgdatadir:?} ] # shellcheck source=/dev/null . "$pkgdatadir/grub-mkconfig_lib" if [ -f @localstatedir@/lib/fwupd/uefi_capsule.conf ] && ls /sys/firmware/efi/efivars/fwupd-*-0abba7dc-e516-4167-bbf5-4d9d1c739416 1>/dev/null 2>&1; then . @localstatedir@/lib/fwupd/uefi_capsule.conf if [ "${EFI_PATH}" != "" ] && [ "${ESP}" != "" ]; then echo "Adding Linux Firmware Updater entry" >&2 cat << EOF menuentry 'Linux Firmware Updater' \$menuentry_id_option 'fwupd' { EOF ${grub_probe:?} --version > /dev/null prepare_grub_to_access_device "$(${grub_probe} --target=device ${ESP})" | sed -e "s/^/\t/" cat << EOF chainloader ${EFI_PATH} } EOF fi fi fwupd-1.9.16/plugins/uefi-capsule/fwupdate.md000066400000000000000000000010231460375044200211420ustar00rootroot00000000000000--- title: fwupdate compatibility layer --- % fwupdate(1) {{PACKAGE_VERSION}} | fwupdate man page ## NAME **fwupdate** — debugging utility for UEFI firmware updates ## SYNOPSIS | **fwupdate** \[CMD] ## DESCRIPTION **fwupdate** is a deprecated tool that allows deploying capsule updates. ## OPTIONS The fwupdate command takes various options depending on the action. Run **fwupdate --help** for the full list. ## BUGS See GitHub Issues: ## SEE ALSO fwupd-1.9.16/plugins/uefi-capsule/make-images.py000077500000000000000000000164051460375044200215500ustar00rootroot00000000000000#!/usr/bin/python3 # -*- coding: utf-8 -*- # # Copyright (C) 2017 Peter Jones # Copyright (C) 2020 Richard Hughes # # SPDX-License-Identifier: LGPL-2.1+ # # pylint: disable=wrong-import-position,too-many-locals,unused-argument,too-many-statements # pylint: disable=invalid-name,too-many-instance-attributes,missing-module-docstring # pylint: disable=missing-function-docstring,missing-class-docstring,too-few-public-methods import os import sys import argparse import tarfile import math import io import struct from typing import Dict, Optional, Any import cairo import gi gi.require_version("Pango", "1.0") gi.require_version("PangoCairo", "1.0") from gi.repository import Pango, PangoCairo def languages(podir: str): for x in open(os.path.join(podir, "LINGUAS"), "r").readlines(): yield x.strip() yield "en" class PotFile: def __init__(self, fn=None): self.msgs: Dict[str, str] = {} if fn: self.parse(fn) def parse(self, fn: str) -> None: with open(fn, "r") as f: lang_en: Optional[str] = None for line in f.read().split("\n"): if not line: continue if line[0] == "#": continue try: key, value = line.split(" ", maxsplit=1) except ValueError: continue if key == "msgid": lang_en = value[1:-1] continue if key == "msgstr" and lang_en: self.msgs[lang_en] = value[1:-1] lang_en = None continue def _cairo_surface_write_to_bmp(img: cairo.ImageSurface) -> bytes: data = bytes(img.get_data()) return ( b"BM" + struct.pack( " int: # open output archive with tarfile.open(args.out, "w:xz", preset=8) as tar: for lang in languages(args.podir): # these are the 1.6:1 of some common(ish) screen widths if lang == "en": label_translated: str = args.label else: potfile = PotFile(os.path.join(args.podir, f"{lang}.po")) try: label_translated = potfile.msgs[args.label] except KeyError: continue if label_translated == args.label: continue for width, height in ( (640, 480), (800, 600), (1024, 768), (1280, 720), (1280, 800), (1366, 768), (1536, 864), (1600, 900), (1920, 1080), (1920, 1200), (2160, 1350), (2560, 1440), (3840, 2160), (5120, 2880), (5688, 3200), (7680, 4320), ): # generate PangoLanguage font_desc = f"Sans {height / 32:.2f}px" fd = Pango.FontDescription(font_desc) font_option = cairo.FontOptions() font_option.set_antialias(cairo.ANTIALIAS_SUBPIXEL) l = Pango.Language.from_string(lang) # create surface img = cairo.ImageSurface(cairo.FORMAT_RGB24, 1, 1) cctx = cairo.Context(img) layout = PangoCairo.create_layout(cctx) pctx = layout.get_context() pctx.set_font_description(fd) pctx.set_language(l) fs = pctx.load_fontset(fd, l) PangoCairo.context_set_font_options(pctx, font_option) attrs = Pango.AttrList() length = len(bytes(label_translated, "utf8")) items = Pango.itemize(pctx, label_translated, 0, length, attrs, None) if not items: continue gs = Pango.GlyphString() Pango.shape(label_translated, length, items[0].analysis, gs) del img, cctx, pctx, layout def find_size(fs, f, data): """find our size, I hope...""" (ink, log) = gs.extents(f) if ink.height == 0 or ink.width == 0: return False data.update({"log": log, "ink": ink}) return True data: Dict[str, Any] = {} fs.foreach(find_size, data) if len(data) == 0: print("Missing sans fonts") return 2 log = data["log"] ink = data["ink"] surface_height = math.ceil(max(ink.height, log.height) / Pango.SCALE) surface_width = math.ceil(max(ink.width, log.width) / Pango.SCALE) x = -math.ceil(log.x / Pango.SCALE) y = -math.ceil(log.y / Pango.SCALE) img = cairo.ImageSurface( cairo.FORMAT_RGB24, surface_width, surface_height ) cctx = cairo.Context(img) layout = PangoCairo.create_layout(cctx) pctx = layout.get_context() PangoCairo.context_set_font_options(pctx, font_option) cctx.set_source_rgb(1, 1, 1) cctx.move_to(x, y - surface_height / 2) def do_write(fs, f, data): """write out glyphs""" ink = gs.extents(f)[0] if ink.height == 0 or ink.width == 0: return False PangoCairo.show_glyph_string(cctx, f, gs) return True # flip the image to write the bitmap upside-down mat = cairo.Matrix() mat.scale(1, -1) cctx.transform(mat) fs.foreach(do_write, None) img.flush() # convert to BMP and add to archive with io.BytesIO() as io_bmp: io_bmp.write(_cairo_surface_write_to_bmp(img)) filename = f"fwupd-{lang}-{width}-{height}.bmp" tarinfo = tarfile.TarInfo(filename) tarinfo.size = io_bmp.tell() io_bmp.seek(0) tar.addfile(tarinfo, fileobj=io_bmp) # success return 0 if __name__ == "__main__": parser = argparse.ArgumentParser(description="Make UX images") parser.add_argument("--label", help="Update text", required=True) parser.add_argument("--podir", help="Po location", required=True) parser.add_argument("--out", help="Output archive", required=True) sys.exit(main(parser.parse_args())) fwupd-1.9.16/plugins/uefi-capsule/meson.build000066400000000000000000000127001460375044200211470ustar00rootroot00000000000000if get_option('plugin_uefi_capsule').disable_auto_if(host_machine.system() not in ['linux', 'freebsd']).allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginUefiCapsule"'] plugins += {meson.current_source_dir().split('/')[-1]: true} efi_os_dir = get_option('efi_os_dir') if efi_os_dir != '' cargs += '-DEFI_OS_DIR="' + efi_os_dir + '"' endif plugin_quirks += files('uefi-capsule.quirk') backend_srcs = ['fu-uefi-backend.c'] if host_machine.system() == 'linux' backend_srcs += 'fu-uefi-backend-linux.c' # replace @localstatedir@ con2 = configuration_data() con2.set('localstatedir', localstatedir) configure_file( input: 'fwupd.grub.conf.in', output: '35_fwupd', configuration: con2, install: true, install_dir: join_paths(sysconfdir, 'grub.d') ) elif host_machine.system() == 'freebsd' backend_srcs += 'fu-uefi-backend-freebsd.c' else error('no ESRT support for @0@'.format(host_machine.system())) endif plugin_builtin_uefi_capsule = static_library('fu_plugin_uefi_capsule', rustgen.process('fu-uefi.rs'), sources: [ 'fu-uefi-capsule-plugin.c', 'fu-uefi-bgrt.c', 'fu-uefi-bootmgr.c', 'fu-uefi-common.c', 'fu-uefi-cod-device.c', 'fu-uefi-nvram-device.c', 'fu-uefi-grub-device.c', 'fu-uefi-device.c', 'fu-uefi-update-info.c', 'fu-acpi-uefi.c', backend_srcs, ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: [ plugin_deps, platform_deps, ], ) plugin_builtins += plugin_builtin_uefi_capsule if get_option('compat_cli') fwupdate = executable( 'fwupdate', rustgen.process('fu-uefi.rs'), sources: [ 'fu-uefi-tool.c', ], include_directories: plugin_incdirs, dependencies: [ plugin_deps, platform_deps, ], link_with: [ plugin_libs, plugin_builtin_uefi_capsule, ], install: true, install_dir: bindir, install_rpath: libdir_pkg, c_args: cargs, ) endif if get_option('compat_cli') if get_option('man') custom_target('fwupdate.1', input: 'fwupdate.md', output: 'fwupdate.1', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, ], install: true, install_dir: join_paths(mandir, 'man1'), ) endif if build_docs md_targets += custom_target('fwupdate.md', input: 'fwupdate.md', output: 'fwupdate.md', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, '--md', ], ) man_md += ['"fwupdate.md"'] endif endif # add all the .po files as inputs to watch ux_linguas = run_command( ['cat', files(join_paths(meson.project_source_root(), 'po', 'LINGUAS'))], check: true, ).stdout().strip().split('\n') ux_capsule_pofiles = [] foreach ux_lingua: ux_linguas ux_capsule_pofiles += join_paths(meson.project_source_root(), 'po', '@0@.po'.format(ux_lingua)) endforeach if get_option('efi_binary') efi_binary = dependency('fwupd-efi', fallback: ['fwupd-efi', 'fwupd_efi_dep']) endif if get_option('plugin_uefi_capsule_splash') # add the archive of pregenerated images splash_deps = run_command([ python3, join_paths(meson.project_source_root(), 'po', 'test-deps'), join_paths(meson.project_source_root(), 'po', 'LINGUAS'), ], check: false) if splash_deps.returncode() != 0 error(splash_deps.stderr().strip()) endif custom_target('ux-capsule-tar', input: [ join_paths(meson.project_source_root(), 'po', 'LINGUAS'), files('make-images.py'), ux_capsule_pofiles, ], output: 'uefi-capsule-ux.tar.xz', command: [ python3.full_path(), files('make-images.py'), '--podir', join_paths(meson.project_source_root(), 'po'), '--label', 'Installing firmware update…', '--out', '@OUTPUT@', ], install: true, install_dir: join_paths(datadir, 'fwupd'), ) endif if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) env.set('FWUPD_LOCALSTATEDIR', '/tmp/fwupd-self-test/var') e = executable( 'uefi-self-test', rustgen.process('fu-uefi.rs'), sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: [ plugin_deps, platform_deps, ], link_with: [ fwupd, fwupdplugin, plugin_builtin_uefi_capsule, ], c_args: [ cargs, '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('uefi-self-test', e, env: env) # to use these do `sudo systemctl edit fwupd.service` and set # Environment="FWUPD_SYSFSFWDIR=/usr/share/installed-tests/fwupd" install_data([ 'tests/efi/esrt/entries/entry0/capsule_flags', 'tests/efi/esrt/entries/entry0/fw_class', 'tests/efi/esrt/entries/entry0/fw_type', 'tests/efi/esrt/entries/entry0/fw_version', 'tests/efi/esrt/entries/entry0/last_attempt_status', 'tests/efi/esrt/entries/entry0/last_attempt_version', 'tests/efi/esrt/entries/entry0/lowest_supported_fw_version', ], install_dir: join_paths(installed_test_datadir, 'efi/esrt/entries/entry0'), ) install_data([ 'tests/efi/efivars/CapsuleMax-39b68c46-f7fb-441b-b6ec-16b0f69821f3', ], install_dir: join_paths(installed_test_datadir, 'efi/efivars'), ) endif summary({ 'efi_os_dir': efi_os_dir, 'efi binary': get_option('efi_binary'), 'capsule splash': get_option('plugin_uefi_capsule_splash'), }, section:'uefi capsule options') endif fwupd-1.9.16/plugins/uefi-capsule/tests/000077500000000000000000000000001460375044200201475ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/.gitignore000066400000000000000000000001421460375044200221340ustar00rootroot00000000000000EFI efi/efivars/fwupd-c34cb672-a81e-5d32-9d89-cbcabe8ec37b-0-0abba7dc-e516-4167-bbf5-4d9d1c739416 fwupd-1.9.16/plugins/uefi-capsule/tests/acpi/000077500000000000000000000000001460375044200210635ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/acpi/bgrt/000077500000000000000000000000001460375044200220215ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/acpi/bgrt/image000077700000000000000000000000001460375044200251262../../test.bmpustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/acpi/bgrt/status000066400000000000000000000000021460375044200232570ustar00rootroot000000000000001 fwupd-1.9.16/plugins/uefi-capsule/tests/acpi/bgrt/type000066400000000000000000000000021460375044200227150ustar00rootroot000000000000000 fwupd-1.9.16/plugins/uefi-capsule/tests/acpi/bgrt/version000066400000000000000000000000021460375044200234210ustar00rootroot000000000000001 fwupd-1.9.16/plugins/uefi-capsule/tests/acpi/bgrt/xoffset000066400000000000000000000000041460375044200234140ustar00rootroot00000000000000123 fwupd-1.9.16/plugins/uefi-capsule/tests/acpi/bgrt/yoffset000066400000000000000000000000041460375044200234150ustar00rootroot00000000000000456 fwupd-1.9.16/plugins/uefi-capsule/tests/efi-framebuffer/000077500000000000000000000000001460375044200231745ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi-framebuffer/efi-framebuffer.0/000077500000000000000000000000001460375044200263575ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi-framebuffer/efi-framebuffer.0/height000066400000000000000000000000041460375044200275440ustar00rootroot00000000000000789 fwupd-1.9.16/plugins/uefi-capsule/tests/efi-framebuffer/efi-framebuffer.0/width000066400000000000000000000000041460375044200274130ustar00rootroot00000000000000456 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/000077500000000000000000000000001460375044200207125ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/efivars/000077500000000000000000000000001460375044200223515ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/efivars/Capsule0000-39b68c46-f7fb-441b-b6ec-16b0f69821f3000066400000000000000000000000601460375044200310400ustar00rootroot00000000000000: T+,fwupd-1.9.16/plugins/uefi-capsule/tests/efi/efivars/Capsule0001-39b68c46-f7fb-441b-b6ec-16b0f69821f3000066400000000000000000000000601460375044200310410ustar00rootroot00000000000000:L̝ T+,fwupd-1.9.16/plugins/uefi-capsule/tests/efi/efivars/CapsuleLast-39b68c46-f7fb-441b-b6ec-16b0f69821f3000066400000000000000000000000321460375044200313630ustar00rootroot00000000000000Capsule0001fwupd-1.9.16/plugins/uefi-capsule/tests/efi/efivars/CapsuleMax-39b68c46-f7fb-441b-b6ec-16b0f69821f3000066400000000000000000000000321460375044200312050ustar00rootroot00000000000000Capsule9999fwupd-ddc0ee61-e7f0-4e7d-acc5-c070a398838e-0-0abba7dc-e516-4167-bbf5-4d9d1c739416000066400000000000000000000003461460375044200355060ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/efivars {iMi eY*@ZZ~I ʍ5Mm\EFI\fedora\fw\fwupd-697bd920-12cf-4da9-8385-996909bc6559.capfwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/000077500000000000000000000000001460375044200216675ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/000077500000000000000000000000001460375044200233405ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/000077500000000000000000000000001460375044200245615ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/capsule_flags000066400000000000000000000000051460375044200273070ustar00rootroot000000000000000xfe fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/fw_class000066400000000000000000000000451460375044200263040ustar00rootroot00000000000000ddc0ee61-e7f0-4e7d-acc5-c070a398838e fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/fw_type000066400000000000000000000000021460375044200261510ustar00rootroot000000000000001 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/fw_version000066400000000000000000000000061460375044200266610ustar00rootroot0000000000000065586 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/last_attempt_status000066400000000000000000000000021460375044200306000ustar00rootroot000000000000001 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/last_attempt_version000066400000000000000000000000111460375044200307420ustar00rootroot0000000000000018472960 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry0/lowest_supported_fw_version000066400000000000000000000000061460375044200323630ustar00rootroot0000000000000065582 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/000077500000000000000000000000001460375044200245625ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/capsule_flags000066400000000000000000000000071460375044200273120ustar00rootroot000000000000000x8010 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/fw_class000066400000000000000000000000451460375044200263050ustar00rootroot00000000000000671d19d0-d43c-4852-98d9-1ce16f9967e4 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/fw_type000066400000000000000000000000021460375044200261520ustar00rootroot000000000000002 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/fw_version000066400000000000000000000000131460375044200266600ustar00rootroot000000000000003090287969 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/last_attempt_status000066400000000000000000000000021460375044200306010ustar00rootroot000000000000000 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/last_attempt_version000066400000000000000000000000021460375044200307430ustar00rootroot000000000000000 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry1/lowest_supported_fw_version000066400000000000000000000000021460375044200323600ustar00rootroot000000000000001 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/000077500000000000000000000000001460375044200245635ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/capsule_flags000077700000000000000000000000001460375044200335012../entry1/capsule_flagsustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/fw_class000066400000000000000000000000451460375044200263060ustar00rootroot0000000000000000000000-0000-0000-0000-000000000000 fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/fw_type000077700000000000000000000000001460375044200312132../entry1/fw_typeustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/fw_version000077700000000000000000000000001460375044200324232../entry1/fw_versionustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/last_attempt_status000077700000000000000000000000001460375044200362712../entry1/last_attempt_statusustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/last_attempt_version000077700000000000000000000000001460375044200365752../entry1/last_attempt_versionustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/esrt/entries/entry2/lowest_supported_fw_version000077700000000000000000000000001460375044200416272../entry1/lowest_supported_fw_versionustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/efi/fw_platform_size000066400000000000000000000000031460375044200242000ustar00rootroot0000000000000064 fwupd-1.9.16/plugins/uefi-capsule/tests/example/000077500000000000000000000000001460375044200216025ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-capsule/tests/example/.gitignore000066400000000000000000000000321460375044200235650ustar00rootroot00000000000000firmware.bin firmware.cab fwupd-1.9.16/plugins/uefi-capsule/tests/example/build.sh000077500000000000000000000002541460375044200232410ustar00rootroot00000000000000#!/bin/bash appstream-util validate-relax firmware.metainfo.xml echo -n "hello world" > firmware.bin gcab --create --nopath firmware.cab firmware.bin firmware.metainfo.xml fwupd-1.9.16/plugins/uefi-capsule/tests/example/firmware.metainfo.xml000066400000000000000000000020251460375044200257400ustar00rootroot00000000000000 example.firmware UEFI Firmware

    Example UEFI firmware for fwupd

    The test device can be updated using the UEFI capsule plugin.

    ddc0ee61-e7f0-4e7d-acc5-c070a398838e https://fwupd.org/ CC0-1.0 CC0-1.0 Richard Hughes

    This release updates a frobnicator to frob faster.

    org.freedesktop.fwupd fwupd-1.9.16/plugins/uefi-capsule/tests/test.bmp000066400000000000000000000046721460375044200216370ustar00rootroot00000000000000BM zl6@  BGRs  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ĞwSIGIFCGEHVtȸmODCGGFFEFVnûȱ|E  #;pØg:  "I|ǹo* )BGEBCDE<-TƋM3ACEEDFH9 0uɾv, >wZ0 Bʇ5 5fh1 #ȓ0BͿ5 ?ş. Eų77M 1þ™+q̶R>ǿƹ%^nűő1Ǘ#žíiL IîD/|H {ʶoiƒ)>Ő+i! xʤ4 |qḻU|4İb5#ǵX:ʃkͶSȗ;Y4ǘ(eτh˳Rk.ƛ4 ĀiȰUDZUoà 5ɷd0ʊlɲX Ț<\ĝ 8ά[ for details. This plugin supports the following protocol ID: * `org.uefi.dbx` ## GUID Generation These devices use the GUID constructed of the uppercase SHA256 of the X509 certificates found in the system KEK and optionally the EFI architecture. e.g. * `UEFI\CRT_{sha256}` (quirk-only) * `UEFI\CRT_{sha256}&ARCH_{arch}` ...where `arch` is typically one of `IA32`, `X64`, `ARM` or `AA64` ## Metadata Microsoft actually removes checksums in some UEFI dbx updates, which is probably a result of OEM pressure about SPI usage -- but local dbx updates are append-only. This means that if you remove hashes then you can have a different number of dbx checksums on your machine depending on whether you went `A→B→C→D` or `A→D`... As we count the number of SHA256 checksums to build the *dbx version number* then we might overcount (due to the now-removed entries) -- in some cases enough to not actually apply the new update at all. In these cases we look at the *last-entry* dbx checksum and compare to the set we know, to see if fwupd needs to artificially lower the reported version. This dbx *last-entry* checksum is added to the LVFS metadata and is copied to the device object when the exact checksum matches. This ensures the locally reported version number always matches what a factory install using just the new dbx would report. The `org.linuxfoundation.dbx.*.firmware` components will match against a hash of the system PK. The latest cabinet archive can also be installed into the `vendor-firmware` remote found in `/usr/share/fwupd/remotes.d/vendor/firmware/` which allows the version-fixup to work even when offline -- although using the LVFS source is recommended for most users. Both reported versions and *last-entry checksums* can be found from the `fwupdtool firmware-parse DBXUpdate-$VERSION$.x64.bin efi-signature-list` command. ## Update Behavior The firmware is deployed when the machine is in normal runtime mode, but it is only activated when the system is restarted. ## Vendor ID Security The vendor ID is hardcoded to `UEFI:Microsoft` for all devices. ## External Interface Access This plugin requires: * read/write access to `/sys/firmware/efi/efivars` ## Version Considerations This plugin has been available since fwupd version `1.5.0`. fwupd-1.9.16/plugins/uefi-dbx/dbxtool.md000066400000000000000000000012221460375044200201200ustar00rootroot00000000000000--- title: dbxtool command line utility --- % dbxtool(1) {{PACKAGE_VERSION}} | dbxtool man page ## NAME **dbxtool** — modify the dbx revocation list ## SYNOPSIS | **dbxtool** [CMD] ## DESCRIPTION This manual page documents briefly the **dbxtool** command. **dbxtool** allows a user to operate on the UEFI dbx revocation list. This tool can be used to list the current dbx contents or update it to a newer version. ## OPTIONS The dbxtool command takes various options depending on the action. Run **dbxtool --help** for the full list. ## BUGS See GitHub Issues: ## SEE ALSO fwupd-1.9.16/plugins/uefi-dbx/fu-dbxtool.c000066400000000000000000000240251460375044200203600ustar00rootroot00000000000000/* * Copyright (C) 2015 Peter Jones * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-context-private.h" #include "fu-uefi-dbx-common.h" /* custom return code */ #define EXIT_NOTHING_TO_DO 2 static void fu_util_ignore_cb(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { } static FuFirmware * fu_dbxtool_get_siglist_system(GError **error) { g_autoptr(GBytes) blob = NULL; g_autoptr(FuFirmware) dbx = fu_efi_signature_list_new(); blob = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_SECURITY_DATABASE, "dbx", NULL, error); if (blob == NULL) return NULL; if (!fu_firmware_parse(dbx, blob, FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return NULL; return g_steal_pointer(&dbx); } static FuFirmware * fu_dbxtool_get_siglist_local(const gchar *filename, GError **error) { g_autoptr(GBytes) blob = NULL; g_autoptr(FuFirmware) siglist = fu_efi_signature_list_new(); blob = fu_bytes_get_contents(filename, error); if (blob == NULL) return NULL; if (!fu_firmware_parse(siglist, blob, FWUPD_INSTALL_FLAG_NONE, error)) return NULL; return g_steal_pointer(&siglist); } static gboolean fu_dbxtool_siglist_inclusive(FuFirmware *outer, FuFirmware *inner) { g_autoptr(GPtrArray) sigs = fu_firmware_get_images(inner); for (guint i = 0; i < sigs->len; i++) { FuEfiSignature *sig = g_ptr_array_index(sigs, i); g_autofree gchar *checksum = NULL; g_autoptr(FuFirmware) img = NULL; checksum = fu_firmware_get_checksum(FU_FIRMWARE(sig), G_CHECKSUM_SHA256, NULL); if (checksum == NULL) continue; img = fu_firmware_get_image_by_checksum(outer, checksum, NULL); if (img == NULL) return FALSE; } return TRUE; } static const gchar * fu_dbxtool_guid_to_string(const gchar *guid) { if (g_strcmp0(guid, FU_EFI_SIGNATURE_GUID_ZERO) == 0) return "zero"; if (g_strcmp0(guid, FU_EFI_SIGNATURE_GUID_MICROSOFT) == 0) return "microsoft"; if (g_strcmp0(guid, FU_EFI_SIGNATURE_GUID_OVMF) == 0 || g_strcmp0(guid, FU_EFI_SIGNATURE_GUID_OVMF_LEGACY) == 0) return "ovmf"; return guid; } int main(int argc, char *argv[]) { gboolean action_apply = FALSE; gboolean action_list = FALSE; gboolean action_version = FALSE; gboolean force = FALSE; gboolean verbose = FALSE; g_autofree gchar *dbxfile = NULL; g_autoptr(GError) error = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(GOptionContext) context = NULL; g_autofree gchar *tmp = NULL; g_autofree gchar *esp_path = NULL; const GOptionEntry options[] = { {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, /* TRANSLATORS: command line option */ N_("Show extra debugging information"), NULL}, {"version", '\0', 0, G_OPTION_ARG_NONE, &action_version, /* TRANSLATORS: command line option */ N_("Show the calculated version of the dbx"), NULL}, {"list", 'l', 0, G_OPTION_ARG_NONE, &action_list, /* TRANSLATORS: command line option */ N_("List entries in dbx"), NULL}, {"apply", 'a', 0, G_OPTION_ARG_NONE, &action_apply, /* TRANSLATORS: command line option */ N_("Apply update files"), NULL}, {"dbx", 'd', 0, G_OPTION_ARG_STRING, &dbxfile, /* TRANSLATORS: command line option */ N_("Specify the dbx database file"), /* TRANSLATORS: command argument: uppercase, spaces->dashes */ N_("FILENAME")}, {"esp-path", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &esp_path, /* TRANSLATORS: command line option */ N_("Override the default ESP path"), /* TRANSLATORS: command argument: uppercase, spaces->dashes */ N_("PATH")}, {"force", 'f', 0, G_OPTION_ARG_NONE, &force, /* TRANSLATORS: command line option */ N_("Apply update even when not advised"), NULL}, {NULL}}; setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); #ifndef SUPPORTED_BUILD /* make critical warnings fatal */ (void)g_setenv("G_DEBUG", "fatal-criticals", FALSE); #endif /* get a action_list of the commands */ context = g_option_context_new(NULL); g_option_context_set_description( context, /* TRANSLATORS: description of dbxtool */ _("This tool allows an administrator to apply UEFI dbx updates.")); /* TRANSLATORS: program name */ g_set_application_name(_("UEFI dbx Utility")); g_option_context_add_main_entries(context, options, NULL); if (!g_option_context_parse(context, &argc, &argv, &error)) { /* TRANSLATORS: the user didn't read the man page */ g_print("%s: %s\n", _("Failed to parse arguments"), error->message); return EXIT_FAILURE; } /* set verbose? */ if (verbose) { (void)g_setenv("G_MESSAGES_DEBUG", "all", FALSE); } else { g_log_set_handler(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, fu_util_ignore_cb, NULL); } /* override the default ESP path */ if (esp_path != NULL) fu_context_set_esp_location(ctx, esp_path); /* list contents, either of the existing system, or an update */ if (action_list || action_version) { guint cnt = 1; g_autoptr(FuFirmware) dbx = NULL; g_autoptr(GPtrArray) sigs = NULL; if (dbxfile != NULL) { dbx = fu_dbxtool_get_siglist_local(dbxfile, &error); if (dbx == NULL) { g_printerr("%s: %s\n", /* TRANSLATORS: could not read existing system data */ _("Failed to load local dbx"), error->message); return EXIT_FAILURE; } } else { dbx = fu_dbxtool_get_siglist_system(&error); if (dbx == NULL) { g_printerr("%s: %s\n", /* TRANSLATORS: could not read existing system data */ _("Failed to load system dbx"), error->message); return EXIT_FAILURE; } } if (action_version) { /* TRANSLATORS: the detected version number of the dbx */ g_print("%s: %s\n", _("Version"), fu_firmware_get_version(dbx)); return EXIT_SUCCESS; } sigs = fu_firmware_get_images(FU_FIRMWARE(dbx)); for (guint i = 0; i < sigs->len; i++) { FuEfiSignature *sig = g_ptr_array_index(sigs, i); g_autofree gchar *checksum = NULL; checksum = fu_firmware_get_checksum(FU_FIRMWARE(sig), G_CHECKSUM_SHA256, NULL); g_print("%4u: {%s} {%s} %s\n", cnt++, fu_dbxtool_guid_to_string(fu_efi_signature_get_owner(sig)), fu_efi_signature_kind_to_string(fu_efi_signature_get_kind(sig)), checksum); } g_info("version: %s", fu_firmware_get_version(FU_FIRMWARE(dbx))); return EXIT_SUCCESS; } #ifdef HAVE_GETUID /* ensure root user */ if (getuid() != 0 || geteuid() != 0) { /* TRANSLATORS: we're poking around as a power user */ g_printerr("%s\n", _("This program may only work correctly as root")); } #endif /* apply update */ if (action_apply) { g_autoptr(FuFirmware) dbx_system = NULL; g_autoptr(FuFirmware) dbx_update = fu_efi_signature_list_new(); g_autoptr(GBytes) blob = NULL; if (dbxfile == NULL) { /* TRANSLATORS: user did not include a filename parameter */ g_printerr("%s\n", _("Filename required")); return EXIT_FAILURE; } /* TRANSLATORS: reading existing dbx from the system */ g_print("%s\n", _("Parsing system dbx…")); dbx_system = fu_dbxtool_get_siglist_system(&error); if (dbx_system == NULL) { /* TRANSLATORS: could not read existing system data */ g_printerr("%s: %s\n", _("Failed to load system dbx"), error->message); return EXIT_FAILURE; } /* TRANSLATORS: reading new dbx from the update */ g_print("%s\n", _("Parsing dbx update…")); blob = fu_bytes_get_contents(dbxfile, &error); if (blob == NULL) { /* TRANSLATORS: could not read file */ g_printerr("%s: %s\n", _("Failed to load local dbx"), error->message); return EXIT_FAILURE; } if (!fu_firmware_parse(dbx_update, blob, FWUPD_INSTALL_FLAG_NONE, &error)) { /* TRANSLATORS: could not parse file */ g_printerr("%s: %s\n", _("Failed to parse local dbx"), error->message); return EXIT_FAILURE; } /* check this is a newer dbx update */ if (!force && fu_dbxtool_siglist_inclusive(dbx_system, dbx_update)) { g_printerr("%s\n", /* TRANSLATORS: same or newer update already applied */ _("Cannot apply as dbx update has already been applied.")); return EXIT_FAILURE; } /* check if on live media */ if (fu_common_is_live_media() && !force) { /* TRANSLATORS: the user is using a LiveCD or LiveUSB install disk */ g_printerr("%s\n", _("Cannot apply updates on live media")); return EXIT_FAILURE; } /* validate this is safe to apply */ if (!force) { /* TRANSLATORS: ESP refers to the EFI System Partition */ g_print("%s\n", _("Validating ESP contents…")); if (!fu_uefi_dbx_signature_list_validate(ctx, FU_EFI_SIGNATURE_LIST(dbx_update), FWUPD_INSTALL_FLAG_NONE, &error)) { g_printerr("%s: %s\n", /* TRANSLATORS: something with a blocked hash exists * in the users ESP -- which would be bad! */ _("Failed to validate ESP contents"), error->message); return EXIT_FAILURE; } } /* TRANSLATORS: actually sending the update to the hardware */ g_print("%s\n", _("Applying update…")); if (!fu_efivar_set_data_bytes( FU_EFIVAR_GUID_SECURITY_DATABASE, "dbx", blob, FU_EFIVAR_ATTR_APPEND_WRITE | FU_EFIVAR_ATTR_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_NON_VOLATILE, &error)) { /* TRANSLATORS: dbx file failed to be applied as an update */ g_printerr("%s: %s\n", _("Failed to apply update"), error->message); return EXIT_FAILURE; } /* TRANSLATORS: success */ g_print("%s\n", _("Done!")); return EXIT_SUCCESS; } /* nothing specified */ tmp = g_option_context_get_help(context, TRUE, NULL); /* TRANSLATORS: user did not tell the tool what to do */ g_printerr("%s\n\n%s", _("No action specified!"), tmp); return EXIT_FAILURE; } fwupd-1.9.16/plugins/uefi-dbx/fu-efi-image.c000066400000000000000000000232001460375044200205220ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-efi-image-struct.h" #include "fu-efi-image.h" struct _FuEfiImage { GObject parent_instance; gchar *checksum; }; typedef struct { gsize offset; gsize size; gchar *name; } FuEfiImageRegion; G_DEFINE_TYPE(FuEfiImage, fu_efi_image, G_TYPE_OBJECT) #define _DOS_OFFSET_SIGNATURE 0x00 #define _DOS_OFFSET_TO_PE_HEADER 0x3c #define _PEI_OFFSET_SIGNATURE 0x00 #define _PEI_OFFSET_MACHINE 0x04 #define _PEI_OFFSET_NUMBER_OF_SECTIONS 0x06 #define _PEI_OFFSET_OPTIONAL_HEADER_SIZE 0x14 #define _PEI_HEADER_SIZE 0x18 #define _PE_OFFSET_SIZE_OF_HEADERS 0x54 #define _PE_OFFSET_CHECKSUM 0x58 #define _PE_OFFSET_DEBUG_TABLE_OFFSET 0x98 #define _PEP_OFFSET_SIZE_OF_HEADERS 0x54 #define _PEP_OFFSET_CHECKSUM 0x58 #define _PEP_OFFSET_DEBUG_TABLE_OFFSET 0xa8 #define _SECTION_HEADER_OFFSET_NAME 0x0 #define _SECTION_HEADER_OFFSET_SIZE 0x10 #define _SECTION_HEADER_OFFSET_PTR 0x14 #define _SECTION_HEADER_SIZE 0x28 #define IMAGE_FILE_MACHINE_AMD64 0x8664 #define IMAGE_FILE_MACHINE_I386 0x014c #define IMAGE_FILE_MACHINE_THUMB 0x01c2 #define IMAGE_FILE_MACHINE_AARCH64 0xaa64 static gint fu_efi_image_region_sort_cb(gconstpointer a, gconstpointer b) { const FuEfiImageRegion *r1 = *((const FuEfiImageRegion **)a); const FuEfiImageRegion *r2 = *((const FuEfiImageRegion **)b); if (r1->offset < r2->offset) return -1; if (r1->offset > r2->offset) return 1; return 0; } static FuEfiImageRegion * fu_efi_image_add_region(GPtrArray *checksum_regions, const gchar *name, gsize offset_start, gsize offset_end) { FuEfiImageRegion *r = g_new0(FuEfiImageRegion, 1); r->name = g_strdup(name); r->offset = offset_start; r->size = offset_end - offset_start; g_ptr_array_add(checksum_regions, r); return r; } static void fu_efi_image_region_free(FuEfiImageRegion *r) { g_free(r->name); g_free(r); } FuEfiImage * fu_efi_image_new(GBytes *data, GError **error) { FuEfiImageRegion *r; const guint8 *buf; gsize bufsz; gsize image_bytes = 0; gsize checksum_offset; gsize data_dir_debug_offset; gsize offset_tmp; guint16 dos_sig = 0; guint16 machine = 0; guint16 opthdrsz; guint16 sections; guint32 baseaddr = 0; guint32 cert_table_size; guint32 header_size; guint32 nt_sig = 0; g_autoptr(FuEfiImage) self = g_object_new(FU_TYPE_EFI_IMAGE, NULL); g_autoptr(GChecksum) checksum = g_checksum_new(G_CHECKSUM_SHA256); g_autoptr(GPtrArray) checksum_regions = NULL; /* verify this is a DOS file */ buf = fu_bytes_get_data_safe(data, &bufsz, error); if (buf == NULL) return NULL; if (!fu_memread_uint16_safe(buf, bufsz, _DOS_OFFSET_SIGNATURE, &dos_sig, G_LITTLE_ENDIAN, error)) return NULL; if (dos_sig != 0x5a4d) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Invalid DOS header magic %04x", dos_sig); return NULL; } /* verify the PE signature */ if (!fu_memread_uint32_safe(buf, bufsz, _DOS_OFFSET_TO_PE_HEADER, &baseaddr, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memread_uint32_safe(buf, bufsz, baseaddr + _PEI_OFFSET_SIGNATURE, &nt_sig, G_LITTLE_ENDIAN, error)) return NULL; if (nt_sig != 0x4550) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Invalid PE header signature %08x", nt_sig); return NULL; } /* which machine type are we reading */ if (!fu_memread_uint16_safe(buf, bufsz, baseaddr + _PEI_OFFSET_MACHINE, &machine, G_LITTLE_ENDIAN, error)) return NULL; if (machine == IMAGE_FILE_MACHINE_AMD64 || machine == IMAGE_FILE_MACHINE_AARCH64) { /* a.out header directly follows PE header */ if (!fu_memread_uint16_safe(buf, bufsz, baseaddr + _PEI_HEADER_SIZE, &machine, G_LITTLE_ENDIAN, error)) return NULL; if (machine != 0x020b) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Invalid a.out machine type %04x", machine); return NULL; } if (!fu_memread_uint32_safe(buf, bufsz, baseaddr + _PEP_OFFSET_SIZE_OF_HEADERS, &header_size, G_LITTLE_ENDIAN, error)) return NULL; checksum_offset = baseaddr + _PEP_OFFSET_CHECKSUM; /* now, this is odd. sbsigntools seems to think that we're * skipping the CertificateTable -- but we actually seems to be * ignoring Debug instead */ data_dir_debug_offset = baseaddr + _PEP_OFFSET_DEBUG_TABLE_OFFSET; } else if (machine == IMAGE_FILE_MACHINE_I386 || machine == IMAGE_FILE_MACHINE_THUMB) { /* a.out header directly follows PE header */ if (!fu_memread_uint16_safe(buf, bufsz, baseaddr + _PEI_HEADER_SIZE, &machine, G_LITTLE_ENDIAN, error)) return NULL; if (machine != 0x010b) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Invalid a.out machine type %04x", machine); return NULL; } if (!fu_memread_uint32_safe(buf, bufsz, baseaddr + _PE_OFFSET_SIZE_OF_HEADERS, &header_size, G_LITTLE_ENDIAN, error)) return NULL; checksum_offset = baseaddr + _PE_OFFSET_CHECKSUM; data_dir_debug_offset = baseaddr + _PE_OFFSET_DEBUG_TABLE_OFFSET; } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Invalid PE header machine %04x", machine); return NULL; } /* get sections */ if (!fu_memread_uint32_safe(buf, bufsz, data_dir_debug_offset + sizeof(guint32), &cert_table_size, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memread_uint16_safe(buf, bufsz, baseaddr + _PEI_OFFSET_NUMBER_OF_SECTIONS, §ions, G_LITTLE_ENDIAN, error)) return NULL; g_debug("number_of_sections: %u", sections); /* get header size */ if (!fu_memread_uint16_safe(buf, bufsz, baseaddr + _PEI_OFFSET_OPTIONAL_HEADER_SIZE, &opthdrsz, G_LITTLE_ENDIAN, error)) return NULL; g_debug("optional_header_size: 0x%x", opthdrsz); /* first region: beginning to checksum_offset field */ checksum_regions = g_ptr_array_new_with_free_func((GDestroyNotify)fu_efi_image_region_free); r = fu_efi_image_add_region(checksum_regions, "begin->cksum", 0x0, checksum_offset); image_bytes += r->size + sizeof(guint32); /* second region: end of checksum_offset to certificate table entry */ r = fu_efi_image_add_region(checksum_regions, "cksum->datadir[DEBUG]", checksum_offset + sizeof(guint32), data_dir_debug_offset); image_bytes += r->size + FU_STRUCT_EFI_IMAGE_DATA_DIR_ENTRY_SIZE; /* third region: end of checksum_offset to end of headers */ r = fu_efi_image_add_region(checksum_regions, "datadir[DEBUG]->headers", data_dir_debug_offset + FU_STRUCT_EFI_IMAGE_DATA_DIR_ENTRY_SIZE, header_size); image_bytes += r->size; /* add COFF sections */ offset_tmp = baseaddr + _PEI_HEADER_SIZE + opthdrsz; for (guint i = 0; i < sections; i++) { guint32 file_offset = 0; guint32 file_size = 0; gchar name[9] = {'\0'}; if (!fu_memread_uint32_safe(buf, bufsz, offset_tmp + _SECTION_HEADER_OFFSET_PTR, &file_offset, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_memread_uint32_safe(buf, bufsz, offset_tmp + _SECTION_HEADER_OFFSET_SIZE, &file_size, G_LITTLE_ENDIAN, error)) return NULL; if (file_size == 0) continue; if (!fu_memcpy_safe((guint8 *)name, sizeof(name), 0x0, /* dst */ buf, bufsz, offset_tmp + _SECTION_HEADER_OFFSET_NAME, /* src */ sizeof(name) - 1, error)) return NULL; r = fu_efi_image_add_region(checksum_regions, name, file_offset, file_offset + file_size); image_bytes += r->size; if (file_offset + r->size > bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "file-aligned section %s extends beyond end of file", r->name); return NULL; } offset_tmp += _SECTION_HEADER_SIZE; } /* make sure in order */ g_ptr_array_sort(checksum_regions, fu_efi_image_region_sort_cb); /* for the data at the end of the image */ if (image_bytes + cert_table_size < bufsz) { fu_efi_image_add_region(checksum_regions, "endjunk", image_bytes, bufsz - cert_table_size); } else if (image_bytes + cert_table_size > bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum_offset areas outside image size"); return NULL; } /* calculate the checksum we would find in the dbx */ for (guint i = 0; i < checksum_regions->len; i++) { r = g_ptr_array_index(checksum_regions, i); g_debug("region %s: 0x%04x -> 0x%04x [0x%04x]", r->name, (guint)r->offset, (guint)(r->offset + r->size - 1), (guint)r->size); g_checksum_update(checksum, (const guchar *)buf + r->offset, (gssize)r->size); } self->checksum = g_strdup(g_checksum_get_string(checksum)); return g_steal_pointer(&self); } const gchar * fu_efi_image_get_checksum(FuEfiImage *self) { return self->checksum; } static void fu_efi_image_finalize(GObject *obj) { FuEfiImage *self = FU_EFI_IMAGE(obj); g_free(self->checksum); G_OBJECT_CLASS(fu_efi_image_parent_class)->finalize(obj); } static void fu_efi_image_class_init(FuEfiImageClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_efi_image_finalize; } static void fu_efi_image_init(FuEfiImage *self) { } fwupd-1.9.16/plugins/uefi-dbx/fu-efi-image.h000066400000000000000000000006041460375044200205320ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_EFI_IMAGE (fu_efi_image_get_type()) G_DECLARE_FINAL_TYPE(FuEfiImage, fu_efi_image, FU, EFI_IMAGE, GObject) FuEfiImage * fu_efi_image_new(GBytes *data, GError **error); const gchar * fu_efi_image_get_checksum(FuEfiImage *self); fwupd-1.9.16/plugins/uefi-dbx/fu-efi-image.rs000066400000000000000000000002671460375044200207340ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(Getters)] struct EfiImageDataDirEntry { addr: u32le, size: u32le, } fwupd-1.9.16/plugins/uefi-dbx/fu-self-test.c000066400000000000000000000025071460375044200206140ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-efi-image.h" #include "fu-uefi-dbx-common.h" static void fu_efi_image_func(void) { const gchar *ci = g_getenv("CI_NETWORK"); const gchar *csum = NULL; g_autofree gchar *fn = NULL; g_autoptr(FuEfiImage) img = NULL; g_autoptr(GBytes) bytes = NULL; g_autoptr(GError) error = NULL; fn = g_test_build_filename(G_TEST_DIST, "tests", "fwupdx64.efi", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS) && ci == NULL) { g_test_skip("Missing fwupdx64.efi"); return; } g_assert_nonnull(fn); bytes = fu_bytes_get_contents(fn, &error); g_assert_no_error(error); g_assert_nonnull(bytes); img = fu_efi_image_new(bytes, &error); g_assert_no_error(error); g_assert_nonnull(img); csum = fu_efi_image_get_checksum(img); g_assert_cmpstr(csum, ==, "e99707d4378140c01eb3f867240d5cc9e237b126d3db0c3b4bbcd3da1720ddff"); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); (void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE); /* tests go here */ g_test_add_func("/uefi-dbx/image", fu_efi_image_func); return g_test_run(); } fwupd-1.9.16/plugins/uefi-dbx/fu-uefi-dbx-common.c000066400000000000000000000136561460375044200217060ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-efi-image.h" #include "fu-uefi-dbx-common.h" static gchar * fu_uefi_dbx_get_authenticode_hash(const gchar *fn, GError **error) { g_autoptr(FuEfiImage) img = NULL; g_autoptr(GBytes) bytes = NULL; g_autoptr(GMappedFile) mmap = NULL; g_debug("getting Authenticode hash of %s", fn); mmap = g_mapped_file_new(fn, FALSE, error); if (mmap == NULL) return NULL; bytes = g_mapped_file_get_bytes(mmap); img = fu_efi_image_new(bytes, error); if (img == NULL) return NULL; g_debug("Authenticode hash was %s", fu_efi_image_get_checksum(img)); return g_strdup(fu_efi_image_get_checksum(img)); } static GPtrArray * fu_uefi_dbx_get_basenames_bootxxxx(void) { g_autoptr(GPtrArray) files = g_ptr_array_new_with_free_func(g_free); /* hardcoded, as we chainload from shim to these */ g_ptr_array_add(files, g_strdup_printf("fwupd%s.efi", EFI_MACHINE_TYPE_NAME)); g_ptr_array_add(files, g_strdup_printf("grub%s.efi", EFI_MACHINE_TYPE_NAME)); for (guint i = 0; i <= G_MAXUINT16; i++) { g_autofree gchar *basename = NULL; g_autofree gchar *basename_down = NULL; g_autofree gchar *fullpath = NULL; g_autofree gchar *name = g_strdup_printf("Boot%04X", i); g_autoptr(FuEfiLoadOption) loadopt = NULL; g_autoptr(FuFirmware) dp_buf = NULL; g_autoptr(FuFirmware) dp_file = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error_local = NULL; /* load EFI load option */ blob = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_EFI_GLOBAL, name, NULL, NULL); if (blob == NULL) continue; loadopt = fu_efi_load_option_new(); if (!fu_firmware_parse(FU_FIRMWARE(loadopt), blob, FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_warning("failed to parse %s: %s", name, error_local->message); continue; } /* get EfiFilePath from the list of DEVICE PATHs */ dp_buf = fu_firmware_get_image_by_gtype(FU_FIRMWARE(loadopt), FU_TYPE_EFI_DEVICE_PATH_LIST, &error_local); if (dp_buf == NULL) { g_warning("no DP list for %s: %s", name, error_local->message); continue; } dp_file = fu_firmware_get_image_by_gtype(dp_buf, FU_TYPE_EFI_FILE_PATH_DEVICE_PATH, &error_local); if (dp_file == NULL) { g_debug("no EfiFilePathDevicePath list for %s: %s", name, error_local->message); continue; } fullpath = fu_efi_file_path_device_path_get_name(FU_EFI_FILE_PATH_DEVICE_PATH(dp_file), &error_local); if (fullpath == NULL) { g_warning("no EfiFilePathDevicePath name for %s: %s", name, error_local->message); continue; } g_debug("found %s from %s", fullpath, name); /* add lowercase basename if not already present */ basename = g_path_get_basename(fullpath); basename_down = g_utf8_strdown(basename, -1); if (g_ptr_array_find_with_equal_func(files, basename_down, g_str_equal, NULL)) continue; g_ptr_array_add(files, g_steal_pointer(&basename_down)); } return g_steal_pointer(&files); } static gboolean fu_uefi_dbx_signature_list_validate_volume(FuEfiSignatureList *siglist, FuVolume *esp, FwupdInstallFlags flags, GError **error) { g_autofree gchar *esp_path = NULL; g_autoptr(GPtrArray) files = NULL; g_autoptr(GPtrArray) basenames = NULL; /* get list of files contained in the ESP */ esp_path = fu_volume_get_mount_point(esp); if (esp_path == NULL) return TRUE; files = fu_path_get_files(esp_path, error); if (files == NULL) return FALSE; /* filter the list of possible names from BootXXXX */ if (flags & FWUPD_INSTALL_FLAG_FORCE) { basenames = fu_uefi_dbx_get_basenames_bootxxxx(); if (basenames->len > 0) { g_autofree gchar *str = fu_strjoin(",", basenames); g_info("EFI binaries to check: %s", str); } } /* verify each file does not exist in the ESP */ for (guint i = 0; i < files->len; i++) { const gchar *fn = g_ptr_array_index(files, i); g_autofree gchar *checksum = NULL; g_autoptr(FuFirmware) img = NULL; g_autoptr(GError) error_local = NULL; /* is listed in the BootXXXX variables */ if (basenames != NULL && basenames->len > 0) { g_autofree gchar *basename = g_path_get_basename(fn); g_autofree gchar *basename_down = g_utf8_strdown(basename, -1); if (!g_ptr_array_find_with_equal_func(files, basename_down, g_str_equal, NULL)) { g_debug("%s was not in a BootXXXX variable", fn); continue; } } /* get checksum of file */ checksum = fu_uefi_dbx_get_authenticode_hash(fn, &error_local); if (checksum == NULL) { g_debug("failed to get checksum for %s: %s", fn, error_local->message); continue; } /* Authenticode signature is present in dbx! */ g_debug("fn=%s, checksum=%s", fn, checksum); img = fu_firmware_get_image_by_checksum(FU_FIRMWARE(siglist), checksum, NULL); if (img != NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NEEDS_USER_ACTION, "%s Authenticode checksum [%s] is present in dbx", fn, checksum); return FALSE; } } /* success */ return TRUE; } gboolean fu_uefi_dbx_signature_list_validate(FuContext *ctx, FuEfiSignatureList *siglist, FwupdInstallFlags flags, GError **error) { g_autoptr(GPtrArray) volumes = NULL; volumes = fu_context_get_esp_volumes(ctx, error); if (volumes == NULL) return FALSE; for (guint i = 0; i < volumes->len; i++) { FuVolume *esp = g_ptr_array_index(volumes, i); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; locker = fu_volume_locker(esp, &error_local); if (locker == NULL) { if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } g_debug("failed to mount ESP: %s", error_local->message); continue; } if (!fu_uefi_dbx_signature_list_validate_volume(siglist, esp, flags, error)) return FALSE; } return TRUE; } fwupd-1.9.16/plugins/uefi-dbx/fu-uefi-dbx-common.h000066400000000000000000000004601460375044200217000ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gboolean fu_uefi_dbx_signature_list_validate(FuContext *ctx, FuEfiSignatureList *siglist, FwupdInstallFlags flags, GError **error); fwupd-1.9.16/plugins/uefi-dbx/fu-uefi-dbx-device.c000066400000000000000000000156571460375044200216600ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uefi-dbx-common.h" #include "fu-uefi-dbx-device.h" struct _FuUefiDbxDevice { FuDevice parent_instance; }; G_DEFINE_TYPE(FuUefiDbxDevice, fu_uefi_dbx_device, FU_TYPE_DEVICE) static gboolean fu_uefi_dbx_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags install_flags, GError **error) { const guint8 *buf; gsize bufsz = 0; g_autoptr(GBytes) fw = NULL; /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* write entire chunk to efivarfs */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); buf = g_bytes_get_data(fw, &bufsz); if (!fu_efivar_set_data(FU_EFIVAR_GUID_SECURITY_DATABASE, "dbx", buf, bufsz, FU_EFIVAR_ATTR_APPEND_WRITE | FU_EFIVAR_ATTR_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | FU_EFIVAR_ATTR_RUNTIME_ACCESS | FU_EFIVAR_ATTR_BOOTSERVICE_ACCESS | FU_EFIVAR_ATTR_NON_VOLATILE, error)) { return FALSE; } /* success! */ return TRUE; } static gboolean fu_uefi_dbx_device_set_version_number(FuDevice *device, GError **error) { g_autoptr(GBytes) dbx_blob = NULL; g_autoptr(FuFirmware) dbx = fu_efi_signature_list_new(); g_autoptr(GPtrArray) sigs = NULL; /* use the number of checksums in the dbx as a version number, ignoring * some owners that do not make sense */ dbx_blob = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_SECURITY_DATABASE, "dbx", NULL, error); if (dbx_blob == NULL) return FALSE; if (!fu_firmware_parse(dbx, dbx_blob, FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return FALSE; /* add the last checksum to the device */ sigs = fu_firmware_get_images(dbx); if (sigs->len > 0) { FuEfiSignature *sig = g_ptr_array_index(sigs, sigs->len - 1); g_autofree gchar *csum = fu_firmware_get_checksum(FU_FIRMWARE(sig), G_CHECKSUM_SHA256, NULL); if (csum != NULL) fu_device_add_checksum(device, csum); } fu_device_set_version(device, fu_firmware_get_version(dbx)); return TRUE; } static void fu_uefi_dbx_device_version_notify_cb(FuDevice *device, GParamSpec *pspec, gpointer user_data) { fu_device_set_version_lowest(device, fu_device_get_version(device)); } static FuFirmware * fu_uefi_dbx_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuContext *ctx = fu_device_get_context(device); g_autoptr(FuFirmware) siglist = fu_efi_signature_list_new(); /* parse dbx */ if (!fu_firmware_parse(siglist, fw, flags, error)) return NULL; /* validate this is safe to apply */ if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { // fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_VERIFY); if (!fu_uefi_dbx_signature_list_validate(ctx, FU_EFI_SIGNATURE_LIST(siglist), flags, error)) { g_prefix_error(error, "Blocked executable in the ESP, " "ensure grub and shim are up to date: "); return NULL; } } /* default blob */ return fu_firmware_new_from_bytes(fw); } static gboolean fu_uefi_dbx_device_probe(FuDevice *device, GError **error) { g_autoptr(FuFirmware) kek = fu_efi_signature_list_new(); g_autoptr(GBytes) kek_blob = NULL; g_autoptr(GPtrArray) sigs = NULL; /* use each of the certificates in the KEK to generate the GUIDs */ kek_blob = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_EFI_GLOBAL, "KEK", NULL, error); if (kek_blob == NULL) return FALSE; if (!fu_firmware_parse(kek, kek_blob, FWUPD_INSTALL_FLAG_NONE, error)) return FALSE; fu_device_add_instance_strup(device, "ARCH", EFI_MACHINE_TYPE_NAME); sigs = fu_firmware_get_images(kek); for (guint j = 0; j < sigs->len; j++) { FuEfiSignature *sig = g_ptr_array_index(sigs, j); g_autofree gchar *checksum = NULL; checksum = fu_firmware_get_checksum(FU_FIRMWARE(sig), G_CHECKSUM_SHA256, error); if (checksum == NULL) return FALSE; fu_device_add_instance_strup(device, "CRT", checksum); fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "UEFI", "CRT", NULL); fu_device_build_instance_id(device, NULL, "UEFI", "CRT", "ARCH", NULL); } return fu_uefi_dbx_device_set_version_number(device, error); } static void fu_uefi_dbx_device_report_metadata_pre(FuDevice *device, GHashTable *metadata) { guint64 nvram_total = fu_efivar_space_used(NULL); if (nvram_total != G_MAXUINT64) { g_hash_table_insert(metadata, g_strdup("EfivarNvramUsed"), g_strdup_printf("%" G_GUINT64_FORMAT, nvram_total)); } } static void fu_uefi_dbx_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_uefi_dbx_device_init(FuUefiDbxDevice *self) { fu_device_set_physical_id(FU_DEVICE(self), "dbx"); fu_device_set_name(FU_DEVICE(self), "UEFI dbx"); fu_device_set_summary(FU_DEVICE(self), "UEFI revocation database"); fu_device_add_vendor_id(FU_DEVICE(self), "UEFI:Linux Foundation"); fu_device_add_protocol(FU_DEVICE(self), "org.uefi.dbx"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_NUMBER); fu_device_set_install_duration(FU_DEVICE(self), 1); fu_device_add_icon(FU_DEVICE(self), "computer"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_ONLY_CHECKSUM); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_VERSION); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE_CHILD); if (!fu_common_is_live_media()) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); g_signal_connect(FWUPD_DEVICE(self), "notify::version", G_CALLBACK(fu_uefi_dbx_device_version_notify_cb), NULL); } static void fu_uefi_dbx_device_class_init(FuUefiDbxDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_uefi_dbx_device_probe; klass_device->write_firmware = fu_uefi_dbx_device_write_firmware; klass_device->prepare_firmware = fu_uefi_dbx_prepare_firmware; klass_device->set_progress = fu_uefi_dbx_device_set_progress; klass_device->report_metadata_pre = fu_uefi_dbx_device_report_metadata_pre; } FuUefiDbxDevice * fu_uefi_dbx_device_new(FuContext *ctx) { FuUefiDbxDevice *self; self = g_object_new(FU_TYPE_UEFI_DBX_DEVICE, "context", ctx, NULL); return self; } fwupd-1.9.16/plugins/uefi-dbx/fu-uefi-dbx-device.h000066400000000000000000000005501460375044200216470ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_UEFI_DBX_DEVICE (fu_uefi_dbx_device_get_type()) G_DECLARE_FINAL_TYPE(FuUefiDbxDevice, fu_uefi_dbx_device, FU, UEFI_DBX_DEVICE, FuDevice) FuUefiDbxDevice * fu_uefi_dbx_device_new(FuContext *ctx); fwupd-1.9.16/plugins/uefi-dbx/fu-uefi-dbx-plugin.c000066400000000000000000000033231460375044200217020ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uefi-dbx-device.h" #include "fu-uefi-dbx-plugin.h" struct _FuUefiDbxPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuUefiDbxPlugin, fu_uefi_dbx_plugin, FU_TYPE_PLUGIN) static gboolean fu_uefi_dbx_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(FuUefiDbxDevice) device = fu_uefi_dbx_device_new(ctx); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 99, "probe"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "setup"); if (!fu_device_probe(FU_DEVICE(device), error)) return FALSE; fu_progress_step_done(progress); if (!fu_device_setup(FU_DEVICE(device), error)) return FALSE; fu_progress_step_done(progress); if (fu_context_has_hwid_flag(fu_plugin_get_context(plugin), "no-dbx-updates")) { fu_device_inhibit(FU_DEVICE(device), "no-dbx", "System firmware cannot accept DBX updates"); } fu_plugin_device_add(plugin, FU_DEVICE(device)); return TRUE; } static void fu_uefi_dbx_plugin_init(FuUefiDbxPlugin *self) { } static void fu_uefi_dbx_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "uefi_capsule"); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_EFI_SIGNATURE_LIST); } static void fu_uefi_dbx_plugin_class_init(FuUefiDbxPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_uefi_dbx_plugin_constructed; plugin_class->coldplug = fu_uefi_dbx_plugin_coldplug; } fwupd-1.9.16/plugins/uefi-dbx/fu-uefi-dbx-plugin.h000066400000000000000000000003551460375044200217110ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuUefiDbxPlugin, fu_uefi_dbx_plugin, FU, UEFI_DBX_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/uefi-dbx/fuzzing/000077500000000000000000000000001460375044200176225ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-dbx/fuzzing/example.bin000066400000000000000000000001141460375044200217430ustar00rootroot000000000000000000000000000000L0111111111111111122222222222222222222222222222222fwupd-1.9.16/plugins/uefi-dbx/meson.build000066400000000000000000000040161460375044200202710ustar00rootroot00000000000000cargs = ['-DG_LOG_DOMAIN="FuPluginUefiDbx"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('uefi-dbx.quirk') plugin_builtin_uefi_dbx = static_library('fu_plugin_uefi_dbx', rustgen.process('fu-efi-image.rs'), sources: [ 'fu-uefi-dbx-plugin.c', 'fu-uefi-dbx-common.c', 'fu-uefi-dbx-device.c', 'fu-efi-image.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_uefi_dbx if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'uefi-dbx-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_uefi_dbx, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ cargs, '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('uefi-dbx-self-test', e, env: env) # added to installed-tests endif dbxtool = executable( 'dbxtool', sources: [ 'fu-dbxtool.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_uefi_dbx, ], install: true, install_dir: bindir, install_rpath: libdir_pkg, c_args: cargs, ) if get_option('man') custom_target('dbxtool.1', input: 'dbxtool.md', output: 'dbxtool.1', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, ], install: true, install_dir: join_paths(mandir, 'man1'), ) endif if build_docs md_targets += custom_target('dbxtool.md', input: 'dbxtool.md', output: 'dbxtool.md', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, '--md', ], ) man_md += ['"dbxtool.md"'] endif fwupd-1.9.16/plugins/uefi-dbx/uefi-dbx.quirk000066400000000000000000000050611460375044200207100ustar00rootroot00000000000000# Manufacturer=Apple Inc. [80f95c96-a739-5ef5-8482-3d65cb39ff55] Flags = no-dbx-updates # Manufacturer=FUJITSU # BaseboardManufacturer=FUJITSU # BaseboardProduct=FJNBB38 [71c3a6cd-3e9a-5b49-a4eb-0e2a57dd265b] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=83D5 [35ed19f7-015a-5da8-adf7-b31dc515c23a] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=83DA [a8cd7a16-e01e-5cdd-a098-cd5a29868d4c] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=83DD [580e5a81-1979-5e29-8d16-bb1ddcc43e87] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=83E7 [c7211192-6168-530e-b42d-650b9f091c7a] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=83E8 [624f1327-d66b-5f20-865f-9a978dc940ac] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=83E9 [af7be2cf-a1c2-50ce-b880-c090ee252249] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=8401 [48bd5791-ceaa-5f79-b8e6-c45bdf956c1e] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=8460 [7bf700eb-c41b-5898-9e09-5a26ca10c14e] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=8461 [c9f16517-3849-50cf-8b8a-48773695bfe4] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=8462 [d605b1fa-0eb3-5bd4-9ab4-2c65d7fc2a8b] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=8463 [7e2cbc58-92cf-5504-a1d4-b4f85de526a1] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=8464 [35ccf8b1-7f97-5f84-b3cb-8d343d2b595c] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=8584 [49549ee6-fa8e-5bb7-a346-9e93d2652b5d] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=8589 [23bfe0fb-be17-55a2-86e0-7970254b357c] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=8617 [cb686451-b325-57e4-a4fc-84bddb2ee470] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=8618 [2edc773c-03f9-5d72-966d-5f612f4cf127] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=8619 [2cc829ba-013c-5843-9b60-406e1ac6e106] Flags = no-dbx-updates # Manufacturer=HP # BaseboardManufacturer=HP # BaseboardProduct=8620 [3f4f41b0-f419-5715-879e-73500cdb3c5d] Flags = no-dbx-updates fwupd-1.9.16/plugins/uefi-esrt/000077500000000000000000000000001460375044200163265ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-esrt/README.md000066400000000000000000000005451460375044200176110ustar00rootroot00000000000000--- title: Plugin: UEFI ESRT --- ## Introduction This allows enabling the BIOS setup option for UEFI capsule updates without manually going into BIOS setup. ## External Interface Access This plugin requires: * read/write access to `/sys/class/firmware-attributes` ## Version Considerations This plugin has been available since fwupd version `1.9.6`. fwupd-1.9.16/plugins/uefi-esrt/fu-uefi-esrt-plugin.c000066400000000000000000000035301460375044200223020ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2017 Dell, Inc. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uefi-esrt-plugin.h" struct _FuUefiEsrtPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuUefiEsrtPlugin, fu_uefi_esrt_plugin, FU_TYPE_PLUGIN) #define LENOVO_CAPSULE_SETTING "com.thinklmi.WindowsUEFIFirmwareUpdate" #define DELL_CAPSULE_SETTING "com.dell.CapsuleFirmwareUpdate" static gboolean fu_uefi_esrt_check_esrt(void) { g_autofree gchar *sysfsfwdir = NULL; g_autofree gchar *esrtdir = NULL; /* already exists */ sysfsfwdir = fu_path_from_kind(FU_PATH_KIND_SYSFSDIR_FW); esrtdir = g_build_filename(sysfsfwdir, "efi", "esrt", NULL); return g_file_test(esrtdir, G_FILE_TEST_EXISTS); } static void fu_uefi_esrt_plugin_add_security_attrs(FuPlugin *plugin, FuSecurityAttrs *attrs) { g_autoptr(FwupdSecurityAttr) attr = NULL; if (!fu_efivar_supported(NULL)) return; attr = fu_plugin_security_attr_new(plugin, FWUPD_SECURITY_ATTR_ID_BIOS_CAPSULE_UPDATES); fu_security_attr_add_bios_target_value(attr, LENOVO_CAPSULE_SETTING, "enable"); fu_security_attr_add_bios_target_value(attr, DELL_CAPSULE_SETTING, "enabled"); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); if (fu_uefi_esrt_check_esrt()) fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); else fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fu_security_attrs_append(attrs, attr); } static void fu_uefi_esrt_plugin_init(FuUefiEsrtPlugin *self) { fu_plugin_add_rule(FU_PLUGIN(self), FU_PLUGIN_RULE_BETTER_THAN, "bios"); } static void fu_uefi_esrt_plugin_class_init(FuUefiEsrtPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->add_security_attrs = fu_uefi_esrt_plugin_add_security_attrs; } fwupd-1.9.16/plugins/uefi-esrt/fu-uefi-esrt-plugin.h000066400000000000000000000003601460375044200223050ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuUefiEsrtPlugin, fu_uefi_esrt_plugin, FU, UEFI_ESRT_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/uefi-esrt/meson.build000066400000000000000000000007031460375044200204700ustar00rootroot00000000000000if get_option('plugin_uefi_capsule').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginUefiEsrt"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtins += static_library('fu_plugin_uefi_esrt', sources: [ 'fu-uefi-esrt-plugin.c', ], include_directories: plugin_incdirs, c_args: [ cargs, ], link_with: plugin_libs, dependencies: [ plugin_deps, ], ) endif fwupd-1.9.16/plugins/uefi-pk/000077500000000000000000000000001460375044200157635ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-pk/README.md000066400000000000000000000007721460375044200172500ustar00rootroot00000000000000--- title: Plugin: UEFI PK --- ## Introduction The platform key (PK) specifies the machine owner, typically the OEM that created the laptop or desktop. Several device manufacturers decide to ship the default "AMI Test PK" platform key instead of a Device Manufacturer specific one. This will cause an HSI-1 failure. ## External Interface Access This plugin requires: * read access to `/sys/firmware/efi/efivars` ## Version Considerations This plugin has been available since fwupd version `1.5.5`. fwupd-1.9.16/plugins/uefi-pk/fu-uefi-pk-device.c000066400000000000000000000153741460375044200213460ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-uefi-pk-device.h" struct _FuUefiPkDevice { FuDevice parent_instance; gboolean has_pk_test_key; }; G_DEFINE_TYPE(FuUefiPkDevice, fu_uefi_pk_device, FU_TYPE_DEVICE) static void fu_uefi_pk_device_to_string(FuDevice *device, guint idt, GString *str) { FuUefiPkDevice *self = FU_UEFI_PK_DEVICE(device); fu_string_append_kb(str, idt, "HasPkTestKey", self->has_pk_test_key); } #define FU_UEFI_PK_CHECKSUM_AMI_TEST_KEY "a773113bafaf5129aa83fd0912e95da4fa555f91" static void _gnutls_datum_deinit(gnutls_datum_t *d) { gnutls_free(d->data); gnutls_free(d); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(gnutls_datum_t, _gnutls_datum_deinit) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(gnutls_x509_crt_t, gnutls_x509_crt_deinit, NULL) #pragma clang diagnostic pop static gboolean fu_uefi_pk_device_parse_buf(FuUefiPkDevice *self, const gchar *buf, gsize bufsz, GError **error) { const gchar *needles[] = { "DO NOT TRUST", "DO NOT SHIP", NULL, }; g_auto(GStrv) infos = NULL; /* look for things that should not exist */ for (guint i = 0; needles[i] != NULL; i++) { if (g_strstr_len(buf, bufsz, needles[i]) != NULL) { g_info("got %s, marking unsafe", buf); self->has_pk_test_key = TRUE; break; } } /* extract the info from C=JP,ST=KN,L=YK,O=Lenovo Ltd.,CN=Lenovo Ltd. PK CA 2012 */ infos = fu_strsplit(buf, bufsz, ",", -1); for (guint i = 0; infos[i] != NULL; i++) { if (fu_device_get_vendor(FU_DEVICE(self)) == NULL && g_str_has_prefix(infos[i], "O=")) { fu_device_set_vendor(FU_DEVICE(self), infos[i] + 2); } if (fu_device_get_summary(FU_DEVICE(self)) == NULL && g_str_has_prefix(infos[i], "CN=")) { fu_device_set_summary(FU_DEVICE(self), infos[i] + 3); } } /* success */ return TRUE; } static gboolean fu_uefi_pk_device_parse_signature(FuUefiPkDevice *self, FuEfiSignature *sig, GError **error) { gchar buf[1024] = {'\0'}; guchar key_id[20] = {'\0'}; gsize key_idsz = sizeof(key_id); gnutls_datum_t d = {0}; gnutls_x509_dn_t dn = {0x0}; gsize bufsz = sizeof(buf); int rc; g_autofree gchar *key_idstr = NULL; g_auto(gnutls_x509_crt_t) crt = NULL; g_autoptr(gnutls_datum_t) subject = NULL; g_autoptr(GBytes) blob = NULL; /* create certificate */ rc = gnutls_x509_crt_init(&crt); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "crt_init: %s [%i]", gnutls_strerror(rc), rc); return FALSE; } /* parse certificate */ blob = fu_firmware_get_bytes(FU_FIRMWARE(sig), error); if (blob == NULL) return FALSE; d.size = g_bytes_get_size(blob); d.data = (unsigned char *)g_bytes_get_data(blob, NULL); rc = gnutls_x509_crt_import(crt, &d, GNUTLS_X509_FMT_DER); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "crt_import: %s [%i]", gnutls_strerror(rc), rc); return FALSE; } /* look in issuer */ if (gnutls_x509_crt_get_issuer_dn(crt, buf, &bufsz) == GNUTLS_E_SUCCESS) { g_debug("PK issuer: %s", buf); if (!fu_uefi_pk_device_parse_buf(self, buf, bufsz, error)) return FALSE; } /* look in subject */ subject = (gnutls_datum_t *)gnutls_malloc(sizeof(gnutls_datum_t)); if (gnutls_x509_crt_get_subject(crt, &dn) == GNUTLS_E_SUCCESS) { gnutls_x509_dn_get_str(dn, subject); g_debug("PK subject: %s", subject->data); if (!fu_uefi_pk_device_parse_buf(self, (const gchar *)subject->data, subject->size, error)) return FALSE; } /* key ID */ rc = gnutls_x509_crt_get_key_id(crt, 0, key_id, &key_idsz); if (rc < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to get key ID: %s [%i]", gnutls_strerror(rc), rc); return FALSE; } key_idstr = g_compute_checksum_for_data(G_CHECKSUM_SHA1, key_id, key_idsz); if (key_idstr == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to calculate key ID for 0x%x bytes", (guint)key_idsz); return FALSE; } fu_device_add_instance_strup(FU_DEVICE(self), "CRT", key_idstr); /* success, certificate was parsed correctly */ return fu_device_build_instance_id(FU_DEVICE(self), error, "UEFI", "CRT", NULL); } static gboolean fu_uefi_pk_device_probe(FuDevice *device, GError **error) { FuUefiPkDevice *self = FU_UEFI_PK_DEVICE(device); g_autoptr(FuFirmware) img = NULL; g_autoptr(FuFirmware) pk = fu_efi_signature_list_new(); g_autoptr(GBytes) pk_blob = NULL; g_autoptr(GPtrArray) sigs = NULL; pk_blob = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_EFI_GLOBAL, "PK", NULL, error); if (pk_blob == NULL) return FALSE; if (!fu_firmware_parse(pk, pk_blob, FU_FIRMWARE_FLAG_NONE, error)) { g_prefix_error(error, "failed to parse PK: "); return FALSE; } /* by checksum */ img = fu_firmware_get_image_by_checksum(pk, FU_UEFI_PK_CHECKSUM_AMI_TEST_KEY, NULL); if (img != NULL) self->has_pk_test_key = TRUE; /* by text */ sigs = fu_firmware_get_images(pk); for (guint i = 0; i < sigs->len; i++) { FuEfiSignature *sig = g_ptr_array_index(sigs, i); if (!fu_uefi_pk_device_parse_signature(self, sig, error)) return FALSE; } /* success */ return TRUE; } static void fu_uefi_pk_device_add_security_attrs(FuDevice *device, FuSecurityAttrs *attrs) { FuUefiPkDevice *self = FU_UEFI_PK_DEVICE(device); g_autoptr(FwupdSecurityAttr) attr = NULL; /* create attr */ attr = fu_device_security_attr_new(device, FWUPD_SECURITY_ATTR_ID_UEFI_PK); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); fu_security_attrs_append(attrs, attr); /* test key is not secure */ if (self->has_pk_test_key) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } static void fu_uefi_pk_device_init(FuUefiPkDevice *self) { fu_device_set_physical_id(FU_DEVICE(self), "pk"); fu_device_set_name(FU_DEVICE(self), "UEFI Platform Key"); fu_device_add_parent_guid(FU_DEVICE(self), "main-system-firmware"); } static void fu_uefi_pk_device_class_init(FuUefiPkDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_uefi_pk_device_to_string; klass_device->add_security_attrs = fu_uefi_pk_device_add_security_attrs; klass_device->probe = fu_uefi_pk_device_probe; } FuUefiPkDevice * fu_uefi_pk_device_new(FuContext *ctx) { return g_object_new(FU_TYPE_UEFI_PK_DEVICE, "context", ctx, NULL); } fwupd-1.9.16/plugins/uefi-pk/fu-uefi-pk-device.h000066400000000000000000000005411460375044200213410ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_UEFI_PK_DEVICE (fu_uefi_pk_device_get_type()) G_DECLARE_FINAL_TYPE(FuUefiPkDevice, fu_uefi_pk_device, FU, UEFI_PK_DEVICE, FuDevice) FuUefiPkDevice * fu_uefi_pk_device_new(FuContext *ctx); fwupd-1.9.16/plugins/uefi-pk/fu-uefi-pk-plugin.c000066400000000000000000000016101460375044200213710ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uefi-pk-device.h" #include "fu-uefi-pk-plugin.h" struct _FuUefiPkPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuUefiPkPlugin, fu_uefi_pk_plugin, FU_TYPE_PLUGIN) static gboolean fu_uefi_pk_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(FuUefiPkDevice) device = fu_uefi_pk_device_new(ctx); if (!fu_device_setup(FU_DEVICE(device), error)) return FALSE; fu_plugin_device_add(plugin, FU_DEVICE(device)); return TRUE; } static void fu_uefi_pk_plugin_init(FuUefiPkPlugin *self) { } static void fu_uefi_pk_plugin_class_init(FuUefiPkPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->coldplug = fu_uefi_pk_plugin_coldplug; } fwupd-1.9.16/plugins/uefi-pk/fu-uefi-pk-plugin.h000066400000000000000000000003521460375044200214000ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuUefiPkPlugin, fu_uefi_pk_plugin, FU, UEFI_PK_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/uefi-pk/meson.build000066400000000000000000000007501460375044200201270ustar00rootroot00000000000000if hsi and \ get_option('plugin_uefi_pk').require(gnutls.found(), error_message: 'gnutls is needed for plugin_uefi_pk').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginUefiPk"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_builtins += static_library('fu_plugin_uefi_pk', sources: [ 'fu-uefi-pk-device.c', 'fu-uefi-pk-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/uefi-recovery/000077500000000000000000000000001460375044200172075ustar00rootroot00000000000000fwupd-1.9.16/plugins/uefi-recovery/README.md000066400000000000000000000013231460375044200204650ustar00rootroot00000000000000--- title: Plugin: UEFI Recovery --- ## Introduction Some devices have firmware bugs which mean they do not include a valid ESRT table in old firmware versions. Create a 'fake' UEFI device with the lowest possible version so that it can be updated to a version of firmware which does have an ESRT table. ## GUID Generation All the HwId GUIDs are used for the fake UEFI device, and so should be used in the firmware metadata for releases that should recover the system. ## Vendor ID Security The vendor ID is set from the BIOS vendor, for example `DMI:LENOVO` ## External Interface Access This plugin requires no extra access. ## Version Considerations This plugin has been available since fwupd version `1.3.1`. fwupd-1.9.16/plugins/uefi-recovery/fu-uefi-recovery-plugin.c000066400000000000000000000047711460375044200240540ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uefi-recovery-plugin.h" struct _FuUefiRecoveryPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuUefiRecoveryPlugin, fu_uefi_recovery_plugin, FU_TYPE_PLUGIN) static gboolean fu_uefi_recovery_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { /* are the EFI dirs set up so we can update each device */ return fu_efivar_supported(error); } static gboolean fu_uefi_recovery_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { FuContext *ctx = fu_plugin_get_context(plugin); GPtrArray *hwids = fu_context_get_hwid_guids(ctx); const gchar *dmi_vendor; g_autoptr(FuDevice) device = fu_device_new(fu_plugin_get_context(plugin)); fu_device_set_id(device, "uefi-recovery"); fu_device_set_name(device, "System Firmware ESRT Recovery"); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "0.0.0"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_set_metadata(device, FU_DEVICE_METADATA_UEFI_DEVICE_KIND, "system-firmware"); fu_device_add_icon(device, "computer"); for (guint i = 0; i < hwids->len; i++) { const gchar *hwid = g_ptr_array_index(hwids, i); fu_device_add_guid(device, hwid); } /* set vendor ID as the BIOS vendor */ dmi_vendor = fu_context_get_hwid_value(ctx, FU_HWIDS_KEY_BIOS_VENDOR); if (dmi_vendor != NULL) { g_autofree gchar *vendor_id = g_strdup_printf("DMI:%s", dmi_vendor); fu_device_add_vendor_id(device, vendor_id); } fu_plugin_device_register(plugin, device); return TRUE; } static void fu_uefi_recovery_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); /* make sure that UEFI plugin is ready to receive devices */ fu_plugin_add_rule(plugin, FU_PLUGIN_RULE_RUN_AFTER, "uefi_capsule"); fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_REQUIRE_HWID); } static void fu_uefi_recovery_plugin_init(FuUefiRecoveryPlugin *self) { } static void fu_uefi_recovery_plugin_class_init(FuUefiRecoveryPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_uefi_recovery_plugin_constructed; plugin_class->coldplug = fu_uefi_recovery_plugin_coldplug; plugin_class->startup = fu_uefi_recovery_plugin_startup; } fwupd-1.9.16/plugins/uefi-recovery/fu-uefi-recovery-plugin.h000066400000000000000000000004301460375044200240450ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuUefiRecoveryPlugin, fu_uefi_recovery_plugin, FU, UEFI_RECOVERY_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/uefi-recovery/meson.build000066400000000000000000000007611460375044200213550ustar00rootroot00000000000000if get_option('plugin_uefi_capsule').disable_auto_if(host_machine.system() != 'linux').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginUefiRecovery"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('uefi-recovery.quirk') plugin_builtins += static_library('fu_plugin_uefi_recovery', sources: [ 'fu-uefi-recovery-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: [ cargs, ], dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/uefi-recovery/uefi-recovery.quirk000066400000000000000000000001741460375044200230520ustar00rootroot00000000000000# Silicom Minnowboard Turbot MNW2MAX1.X64.0100.R01.1811141729 [ea358e00-39f1-55b6-97be-a39225a585e1] Plugin = uefi_recovery fwupd-1.9.16/plugins/uf2/000077500000000000000000000000001460375044200151175ustar00rootroot00000000000000fwupd-1.9.16/plugins/uf2/README.md000066400000000000000000000035541460375044200164050ustar00rootroot00000000000000--- title: Plugin: UF2 --- ## Introduction This plugin allows the user to update any supported UF2 Device by writing firmware onto a mass storage device. A UF2 device exposes a VFAT block device which has a virtual file `INFO_UF2.TXT` where metadata can be read from. It may also have a the current firmware exported as a file `CURRENT.UF2` which is in a 512 byte-block UF2 format. Writing any file to the MSD will cause the firmware to be written. Sometimes the device will restart and the volume will be unmounted and then mounted again. In some cases the volume may not “come back” until the user manually puts the device back in programming mode. Match the block devices using the VID, PID and UUID, and then create a UF2 device which can be used to flash firmware. Note: We only read metadata from allow-listed IDs to avoid causing regressions on non-UF2 volumes. To get the UUID you can use commands like: udisksctl info -b /dev/sda1 The UF2 format is specified [here](https://github.com/Microsoft/uf2>). ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in the UF2 file format. This plugin supports the following protocol ID: * `com.microsoft.uf2` ## GUID Generation These devices use standard USB DeviceInstanceId values, e.g. * `USB\VID_1234&PID_5678` * `USB\VID_1234&PID_5678&UUID_E478-FA50` Additionally, the UF2 Board-ID and Family-ID may be added: * `UF2\BOARD_{Board-ID}` * `UF2\FAMILY_{Family-ID}` ## Update Behavior The firmware is deployed when the device is inserted, and the firmware will typically be written as the file is copied. ## Vendor ID Security The vendor ID is set from the USB vendor. ## External Interface Access This plugin requires permission to mount, write a file and unmount the mass storage device. ## Version Considerations This plugin has been available since fwupd version `1.7.4`. fwupd-1.9.16/plugins/uf2/fu-self-test.c000066400000000000000000000033241460375044200176030ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uf2-firmware.h" static void fu_uf2_firmware_xml_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *csum1 = NULL; g_autofree gchar *csum2 = NULL; g_autofree gchar *xml_out = NULL; g_autofree gchar *xml_src = NULL; g_autoptr(FuFirmware) firmware1 = fu_uf2_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_uf2_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "uf2.builder.xml", NULL); ret = g_file_get_contents(filename, &xml_src, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_build_from_xml(firmware1, xml_src, &error); g_assert_no_error(error); g_assert_true(ret); csum1 = fu_firmware_get_checksum(firmware1, G_CHECKSUM_SHA1, &error); g_assert_no_error(error); g_assert_cmpstr(csum1, ==, "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"); /* ensure we can round-trip */ xml_out = fu_firmware_export_to_xml(firmware1, FU_FIRMWARE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); ret = fu_firmware_build_from_xml(firmware2, xml_out, &error); g_assert_no_error(error); g_assert_true(ret); csum2 = fu_firmware_get_checksum(firmware2, G_CHECKSUM_SHA1, &error); g_assert_cmpstr(csum1, ==, csum2); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* tests go here */ g_test_add_func("/uf2/firmware{xml}", fu_uf2_firmware_xml_func); return g_test_run(); } fwupd-1.9.16/plugins/uf2/fu-uf2-device.c000066400000000000000000000312641460375044200176320ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uf2-device.h" #include "fu-uf2-firmware.h" struct _FuUf2Device { FuUdevDevice parent_instance; guint64 family_id; FuVolume *volume; /* non-null when fwupd has mounted it privately */ }; G_DEFINE_TYPE(FuUf2Device, fu_uf2_device, FU_TYPE_UDEV_DEVICE) static FuFirmware * fu_uf2_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuUf2Device *self = FU_UF2_DEVICE(device); g_autoptr(FuFirmware) firmware = fu_uf2_firmware_new(); if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* check the family_id matches if we can read the old firmware */ if (self->family_id > 0 && fu_firmware_get_idx(firmware) > 0 && self->family_id != fu_firmware_get_idx(firmware)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "family ID was different, expected 0x%08x and got 0x%08x", (guint)self->family_id, (guint)fu_firmware_get_idx(firmware)); return NULL; } /* success: but return the raw data */ return fu_firmware_new_from_bytes(fw); } static gboolean fu_uf2_device_probe_current_fw(FuDevice *device, GBytes *fw, GError **error) { g_autofree gchar *csum_sha256 = NULL; g_autoptr(FuFirmware) firmware = fu_uf2_firmware_new(); g_autoptr(GBytes) fw_raw = NULL; /* parse to get version */ if (!fu_firmware_parse(firmware, fw, FWUPD_INSTALL_FLAG_NONE, error)) return FALSE; if (fu_firmware_get_version(firmware) != NULL) fu_device_set_version(device, fu_firmware_get_version(firmware)); /* add instance ID for quirks */ if (fu_firmware_get_idx(firmware) != 0x0) { fu_device_add_instance_u32(device, "FAMILY", (guint32)fu_firmware_get_idx(firmware)); } (void)fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, NULL, "UF2", "FAMILY", NULL); /* add device checksum */ fw_raw = fu_firmware_get_bytes(firmware, error); if (fw_raw == NULL) return FALSE; csum_sha256 = g_compute_checksum_for_bytes(G_CHECKSUM_SHA256, fw_raw); fu_device_add_checksum(device, csum_sha256); /* success */ return TRUE; } static gchar * fu_block_device_get_full_path(FuUf2Device *self, const gchar *filename, GError **error) { const gchar *devfile = fu_udev_device_get_device_file(FU_UDEV_DEVICE(self)); g_autoptr(FuVolume) volume = NULL; g_autofree gchar *mount_point = NULL; /* sanity check */ if (devfile == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "invalid path: no devfile"); return NULL; } /* find volume */ volume = fu_volume_new_by_device(devfile, error); if (volume == NULL) return NULL; /* success */ mount_point = fu_volume_get_mount_point(volume); return g_build_filename(mount_point, filename, NULL); } static gboolean fu_block_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuUf2Device *self = FU_UF2_DEVICE(device); gssize wrote; g_autofree gchar *fn = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GOutputStream) ostr = NULL; /* get blob */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* open file for writing; no cleverness */ fn = fu_block_device_get_full_path(self, "FIRMWARE.UF2", error); if (fn == NULL) return FALSE; file = g_file_new_for_path(fn); ostr = G_OUTPUT_STREAM(g_file_replace(file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error)); if (ostr == NULL) return FALSE; /* write in one chunk and let the kernel do the right thing :) */ wrote = g_output_stream_write(ostr, g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), NULL, error); if (wrote < 0) return FALSE; if ((gsize)wrote != g_bytes_get_size(fw)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "only wrote 0x%x bytes", (guint)wrote); return FALSE; } /* success */ return TRUE; } static GBytes * fu_block_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuUf2Device *self = FU_UF2_DEVICE(device); g_autofree gchar *fn = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GInputStream) istr = NULL; /* open for reading */ fn = fu_block_device_get_full_path(self, "CURRENT.UF2", error); if (fn == NULL) return NULL; file = g_file_new_for_path(fn); istr = G_INPUT_STREAM(g_file_read(file, NULL, error)); if (istr == NULL) return NULL; /* read all in one big chunk */ return fu_bytes_get_contents_stream(istr, G_MAXUINT32, error); } static FuFirmware * fu_uf2_device_read_firmware(FuDevice *device, FuProgress *progress, GError **error) { g_autoptr(FuFirmware) firmware = fu_uf2_firmware_new(); g_autoptr(GBytes) fw = NULL; fw = fu_device_dump_firmware(device, progress, error); if (fw == NULL) return NULL; if (!fu_firmware_parse(firmware, fw, FWUPD_INSTALL_FLAG_NONE, error)) return NULL; return g_steal_pointer(&firmware); } static gboolean fu_uf2_device_volume_mount(FuUf2Device *self, GError **error) { const gchar *devfile = fu_udev_device_get_device_file(FU_UDEV_DEVICE(self)); /* mount volume if required */ self->volume = fu_volume_new_by_device(devfile, error); if (self->volume == NULL) return FALSE; return fu_volume_mount(self->volume, error); } static gboolean fu_uf2_device_check_volume_mounted_cb(FuDevice *self, gpointer user_data, GError **error) { const gchar *devfile = fu_udev_device_get_device_file(FU_UDEV_DEVICE(user_data)); g_autoptr(FuVolume) volume = NULL; /* mount volume if required */ volume = fu_volume_new_by_device(devfile, error); if (volume == NULL) return FALSE; if (!fu_volume_is_mounted(volume)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not mounted"); return FALSE; } /* success */ return TRUE; } static gboolean fu_uf2_device_open(FuDevice *device, GError **error) { FuUf2Device *self = FU_UF2_DEVICE(device); g_autoptr(GError) error_local = NULL; /* wait for the user session to auto-mount the volume -- ideally we want to avoid using * fu_volume_mount() which would make the volume only accessible by the fwupd user */ if (!fu_device_retry_full(device, fu_uf2_device_check_volume_mounted_cb, 20, /* count */ 50, /* ms */ device, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { /* maybe no session running? */ if (!fu_uf2_device_volume_mount(self, error)) return FALSE; } else { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } /* success */ return TRUE; } static gboolean fu_uf2_device_close(FuDevice *device, GError **error) { FuUf2Device *self = FU_UF2_DEVICE(device); /* we only do this when mounting for the fwupd user */ if (self->volume != NULL) { if (!fu_volume_unmount(self->volume, error)) return FALSE; g_clear_object(&self->volume); } /* success */ return TRUE; } static gboolean fu_uf2_device_setup(FuDevice *device, GError **error) { FuUf2Device *self = FU_UF2_DEVICE(device); gsize bufsz = 0; g_autofree gchar *buf = NULL; g_autofree gchar *fn1 = NULL; g_autofree gchar *fn2 = NULL; g_auto(GStrv) lines = NULL; g_autoptr(GBytes) fw = NULL; /* this has to exist */ fn1 = fu_block_device_get_full_path(self, "INFO_UF2.TXT", error); if (fn1 == NULL) return FALSE; if (!g_file_get_contents(fn1, &buf, &bufsz, error)) return FALSE; lines = fu_strsplit(buf, bufsz, "\n", -1); for (guint i = 0; lines[i] != NULL; i++) { if (g_str_has_prefix(lines[i], "Model: ")) { fu_device_set_name(device, lines[i] + 7); } else if (g_str_has_prefix(lines[i], "Board-ID: ")) { fu_device_add_instance_strsafe(device, "BOARD", lines[i] + 10); } } fu_device_build_instance_id(device, NULL, "UF2", "BOARD", NULL); /* this might exist */ fn2 = fu_block_device_get_full_path(self, "CURRENT.UF2", error); fw = fu_bytes_get_contents(fn2, NULL); if (fw != NULL) { if (!fu_uf2_device_probe_current_fw(device, fw, error)) return FALSE; } else { fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_NUMBER); } /* success */ return TRUE; } static gboolean fu_uf2_device_probe(FuDevice *device, GError **error) { GUdevDevice *udev_device = fu_udev_device_get_dev(FU_UDEV_DEVICE(device)); const gchar *tmp; guint64 vid = 0; guint64 pid = 0; /* check is valid */ tmp = g_udev_device_get_property(udev_device, "ID_BUS"); if (g_strcmp0(tmp, "usb") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct ID_BUS=%s, expected usb", tmp); return FALSE; } tmp = g_udev_device_get_property(udev_device, "ID_FS_TYPE"); if (g_strcmp0(tmp, "vfat") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not correct ID_FS_TYPE=%s, expected vfat", tmp); return FALSE; } /* set the physical ID */ if (!fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "block", error)) return FALSE; /* more instance IDs */ tmp = g_udev_device_get_property(udev_device, "ID_VENDOR_ID"); if (tmp != NULL) vid = g_ascii_strtoull(tmp, NULL, 16); if (vid != 0x0) fu_device_add_instance_u16(device, "VID", vid); tmp = g_udev_device_get_property(udev_device, "ID_MODEL_ID"); if (tmp != NULL) pid = g_ascii_strtoull(tmp, NULL, 16); if (pid != 0x0) fu_device_add_instance_u16(device, "PID", pid); if (!fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, error, "USB", "VID", NULL)) return FALSE; if (!fu_device_build_instance_id(device, error, "USB", "VID", "PID", NULL)) return FALSE; /* only add UUID if it is set */ tmp = g_udev_device_get_property(udev_device, "ID_FS_UUID"); if (tmp != NULL) { fu_device_add_instance_str(device, "UUID", tmp); if (!fu_device_build_instance_id(device, error, "USB", "VID", "PID", "UUID", NULL)) return FALSE; } /* vendor-id */ if (vid != 0x0) { g_autofree gchar *vendor_id = g_strdup_printf("USB:0x%04X", (guint)vid); fu_device_add_vendor_id(device, vendor_id); } /* check the quirk matched to avoid mounting *all* vfat devices */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not marked as updatable in uf2.quirk"); return FALSE; } /* success */ return TRUE; } static void fu_uf2_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_uf2_device_to_string(FuDevice *device, guint idt, GString *str) { FuUf2Device *self = FU_UF2_DEVICE(device); FU_DEVICE_CLASS(fu_uf2_device_parent_class)->to_string(device, idt, str); if (self->family_id > 0) fu_string_append_kx(str, idt, "FamilyId", self->family_id); } static void fu_uf2_device_init(FuUf2Device *self) { fu_device_add_protocol(FU_DEVICE(self), "com.microsoft.uf2"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); } static void fu_uf2_device_finalize(GObject *obj) { FuUf2Device *self = FU_UF2_DEVICE(obj); /* should be done by ->close(), but check to be sure */ if (self->volume != NULL) g_object_unref(self->volume); G_OBJECT_CLASS(fu_uf2_device_parent_class)->finalize(obj); } static void fu_uf2_device_class_init(FuUf2DeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *klass_object = G_OBJECT_CLASS(klass); klass_object->finalize = fu_uf2_device_finalize; klass_device->to_string = fu_uf2_device_to_string; klass_device->probe = fu_uf2_device_probe; klass_device->setup = fu_uf2_device_setup; klass_device->open = fu_uf2_device_open; klass_device->close = fu_uf2_device_close; klass_device->prepare_firmware = fu_uf2_device_prepare_firmware; klass_device->set_progress = fu_uf2_device_set_progress; klass_device->read_firmware = fu_uf2_device_read_firmware; klass_device->write_firmware = fu_block_device_write_firmware; klass_device->dump_firmware = fu_block_device_dump_firmware; } fwupd-1.9.16/plugins/uf2/fu-uf2-device.h000066400000000000000000000004321460375044200176300ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_UF2_DEVICE (fu_uf2_device_get_type()) G_DECLARE_FINAL_TYPE(FuUf2Device, fu_uf2_device, FU, UF2_DEVICE, FuUdevDevice) fwupd-1.9.16/plugins/uf2/fu-uf2-firmware.c000066400000000000000000000171731460375044200202120ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uf2-firmware.h" #include "fu-uf2-struct.h" struct _FuUf2Firmware { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuUf2Firmware, fu_uf2_firmware, FU_TYPE_FIRMWARE) #define FU_UF2_FIRMWARE_BLOCK_FLAG_NONE 0x00000000 #define FU_UF2_FIRMWARE_BLOCK_FLAG_NOFLASH 0x00000001 #define FU_UF2_FIRMWARE_BLOCK_FLAG_IS_CONTAINER 0x00001000 #define FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_FAMILY 0x00002000 #define FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_MD5 0x00004000 #define FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_EXTENSION_TAG 0x00008000 #define FU_U2F_FIRMWARE_TAG_VERSION 0x9fc7bc /* semver of firmware file (UTF-8) */ #define FU_U2F_FIRMWARE_TAG_DESCRIPTION 0x650d9d /* description of device (UTF-8) */ #define FU_U2F_FIRMWARE_TAG_PAGE_SZ 0x0be9f7 /* page size of target device (uint32_t) */ #define FU_U2F_FIRMWARE_TAG_SHA1 0xb46db0 /* SHA-2 checksum of firmware */ #define FU_U2F_FIRMWARE_TAG_DEVICE_ID 0xc8a729 /* device type identifier (uint32_t or uint64_t) */ static gboolean fu_uf2_firmware_parse_chunk(FuUf2Firmware *self, FuChunk *chk, GByteArray *tmp, GError **error) { gsize bufsz = fu_chunk_get_data_sz(chk); const guint8 *buf = fu_chunk_get_data(chk); guint32 flags = 0; guint32 datasz = 0; g_autoptr(GByteArray) st = NULL; /* parse */ st = fu_struct_uf2_parse(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0, /* offset */ error); if (st == NULL) return FALSE; flags = fu_struct_uf2_get_flags(st); if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_IS_CONTAINER) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "container U2F firmware not supported"); return FALSE; } datasz = fu_struct_uf2_get_payload_size(st); if (datasz > 476) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "data size impossible got 0x%08x", datasz); return FALSE; } if (fu_struct_uf2_get_block_no(st) != fu_chunk_get_idx(chk)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "block count invalid, expected 0x%04x and got 0x%04x", fu_chunk_get_idx(chk), fu_struct_uf2_get_block_no(st)); return FALSE; } if (fu_struct_uf2_get_num_blocks(st) == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "block count invalid, expected > 0"); return FALSE; } if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_FAMILY) { if (fu_struct_uf2_get_family_id(st) == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "family_id required but not supplied"); return FALSE; } } /* assume first chunk is representative of firmware */ if (fu_chunk_get_idx(chk) == 0) { fu_firmware_set_addr(FU_FIRMWARE(self), fu_struct_uf2_get_target_addr(st)); fu_firmware_set_idx(FU_FIRMWARE(self), fu_struct_uf2_get_family_id(st)); } /* just append raw data */ g_byte_array_append(tmp, fu_struct_uf2_get_data(st, NULL), datasz); if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_MD5) { if (datasz < 24) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not enough space for MD5 checksum"); return FALSE; } } if (flags & FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_EXTENSION_TAG) { gsize offset = FU_STRUCT_UF2_OFFSET_DATA; while (offset < bufsz) { guint8 sz = 0; guint32 tag = 0; /* [SZ][TAG][TAG][TAG][TAG][DATA....] */ if (!fu_memread_uint8_safe(buf, bufsz, offset, &sz, error)) return FALSE; if (sz < 4) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid extension tag size"); return FALSE; } if (!fu_memread_uint32_safe(buf, bufsz, offset, &tag, G_LITTLE_ENDIAN, error)) return FALSE; tag &= 0xFFFFFF; if (tag == FU_U2F_FIRMWARE_TAG_VERSION) { g_autofree gchar *utf8buf = g_malloc0(sz); if (!fu_memcpy_safe((guint8 *)utf8buf, sz, 0x0, /* dst */ buf, bufsz, offset + 0x4, /* src */ sz - 4, error)) return FALSE; fu_firmware_set_version(FU_FIRMWARE(self), utf8buf); } else if (tag == FU_U2F_FIRMWARE_TAG_DESCRIPTION) { g_autofree gchar *utf8buf = g_malloc0(sz); if (!fu_memcpy_safe((guint8 *)utf8buf, sz, 0x0, /* dst */ buf, bufsz, offset + 0x4, /* src */ sz - 4, error)) return FALSE; fu_firmware_set_id(FU_FIRMWARE(self), utf8buf); } else { if (g_getenv("FWUPD_FUZZER_RUNNING") == NULL) g_warning("unknown tag 0x%06x", tag); } /* next! */ offset += sz; } } /* success */ return TRUE; } static gboolean fu_uf2_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuUf2Firmware *self = FU_UF2_FIRMWARE(firmware); g_autoptr(GByteArray) tmp = g_byte_array_new(); g_autoptr(GBytes) blob = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* read in fixed sized chunks */ chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 512); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_uf2_firmware_parse_chunk(self, chk, tmp, error)) return FALSE; } /* success */ blob = g_bytes_new(tmp->data, tmp->len); fu_firmware_set_bytes(firmware, blob); return TRUE; } static GByteArray * fu_uf2_firmware_write_chunk(FuUf2Firmware *self, FuChunk *chk, guint chk_len, GError **error) { guint32 addr = fu_firmware_get_addr(FU_FIRMWARE(self)); guint32 flags = FU_UF2_FIRMWARE_BLOCK_FLAG_NONE; g_autoptr(GByteArray) st = fu_struct_uf2_new(); /* optional */ if (fu_firmware_get_idx(FU_FIRMWARE(self)) > 0) flags |= FU_UF2_FIRMWARE_BLOCK_FLAG_HAS_FAMILY; /* offset from base address */ addr += fu_chunk_get_idx(chk) * fu_chunk_get_data_sz(chk); /* build UF2 packet */ fu_struct_uf2_set_flags(st, flags); fu_struct_uf2_set_target_addr(st, addr); fu_struct_uf2_set_payload_size(st, fu_chunk_get_data_sz(chk)); fu_struct_uf2_set_block_no(st, fu_chunk_get_idx(chk)); fu_struct_uf2_set_num_blocks(st, chk_len); fu_struct_uf2_set_family_id(st, fu_firmware_get_idx(FU_FIRMWARE(self))); if (!fu_struct_uf2_set_data(st, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) return NULL; /* success */ return g_steal_pointer(&st); } static GByteArray * fu_uf2_firmware_write(FuFirmware *firmware, GError **error) { FuUf2Firmware *self = FU_UF2_FIRMWARE(firmware); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* data first */ fw = fu_firmware_get_bytes_with_patches(firmware, error); if (fw == NULL) return NULL; /* write in chunks */ chunks = fu_chunk_array_new_from_bytes(fw, fu_firmware_get_addr(firmware), 256); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); g_autoptr(GByteArray) tmp = NULL; tmp = fu_uf2_firmware_write_chunk(self, chk, fu_chunk_array_length(chunks), error); if (tmp == NULL) return NULL; g_byte_array_append(buf, tmp->data, tmp->len); } /* success */ return g_steal_pointer(&buf); } static void fu_uf2_firmware_init(FuUf2Firmware *self) { } static void fu_uf2_firmware_class_init(FuUf2FirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_uf2_firmware_parse; klass_firmware->write = fu_uf2_firmware_write; } FuFirmware * fu_uf2_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_UF2_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/uf2/fu-uf2-firmware.h000066400000000000000000000005121460375044200202040ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_UF2_FIRMWARE (fu_uf2_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuUf2Firmware, fu_uf2_firmware, FU, UF2_FIRMWARE, FuFirmware) FuFirmware * fu_uf2_firmware_new(void); fwupd-1.9.16/plugins/uf2/fu-uf2-plugin.c000066400000000000000000000014761460375044200176730ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-uf2-device.h" #include "fu-uf2-firmware.h" #include "fu-uf2-plugin.h" struct _FuUf2Plugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuUf2Plugin, fu_uf2_plugin, FU_TYPE_PLUGIN) static void fu_uf2_plugin_init(FuUf2Plugin *self) { } static void fu_uf2_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_UF2_DEVICE); fu_plugin_add_firmware_gtype(plugin, "uf2", FU_TYPE_UF2_FIRMWARE); fu_plugin_add_device_udev_subsystem(plugin, "block"); } static void fu_uf2_plugin_class_init(FuUf2PluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_uf2_plugin_constructed; } fwupd-1.9.16/plugins/uf2/fu-uf2-plugin.h000066400000000000000000000003371460375044200176730ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuUf2Plugin, fu_uf2_plugin, FU, UF2_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/uf2/fu-uf2.rs000066400000000000000000000006071460375044200165740ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, Parse)] struct Uf2 { magic0: u32le == 0x0A324655, magic1: u32le == 0x9E5D5157, flags: u32le, target_addr: u32le, payload_size: u32le, block_no: u32le, num_blocks: u32le, family_id: u32le, data: [u8; 476], magic_end: u32le == 0x0AB16F30, } fwupd-1.9.16/plugins/uf2/meson.build000066400000000000000000000024601460375044200172630ustar00rootroot00000000000000 if get_option('plugin_uf2').require(gudev.found(), error_message: 'gudev is needed for plugin_uf2').allowed() cargs = ['-DG_LOG_DOMAIN="FuPluginUf2"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('uf2.quirk') plugin_builtin_uf2 = static_library('fu_plugin_uf2', rustgen.process( 'fu-uf2.rs', # fuzzing ), sources: [ 'fu-uf2-plugin.c', 'fu-uf2-device.c', 'fu-uf2-firmware.c', # fuzzing ], include_directories: plugin_incdirs, c_args: cargs, dependencies: plugin_deps, link_with: plugin_libs, ) plugin_builtins += plugin_builtin_uf2 if get_option('tests') install_data(['tests/uf2.builder.xml'], install_dir: join_paths(installed_test_datadir, 'tests')) env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'uf2-self-test', sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_uf2, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('uf2-self-test', e, env: env) endif endif fwupd-1.9.16/plugins/uf2/tests/000077500000000000000000000000001460375044200162615ustar00rootroot00000000000000fwupd-1.9.16/plugins/uf2/tests/uf2.bin000066400000000000000000000010001460375044200174360ustar00rootroot00000000000000UF2 WQ] hello world0o fwupd-1.9.16/plugins/uf2/tests/uf2.builder.xml000066400000000000000000000001631460375044200211240ustar00rootroot00000000000000 0x0100 aGVsbG8gd29ybGQ= fwupd-1.9.16/plugins/uf2/uf2.quirk000066400000000000000000000043271460375044200166760ustar00rootroot00000000000000# Raspberry Pi RP2 [no CURRENT.UF2] [USB\VID_2E8A&PID_0003] Guid = UF2\FAMILY_E48BFF56 Flags = updatable,will-disappear # Adafruit Trinket [USB\VID_1781&PID_0C9F] Flags = updatable # Adafruit Trinket M0 [USB\VID_239A&PID_001E] Flags = updatable # Adafruit Feather M0 Express [USB\VID_239A&PID_001B] Flags = updatable # nRF52840 MDK [USB\VID_239A&PID_0029] Flags = updatable # the following values are from https://github.com/microsoft/uf2/blob/master/utils/uf2families.json # although are lightly edited to split up Name and Vendor in the correct way. [UF2\FAMILY_16573617] Name = ATMEGA32 Vendor = Microchip [UF2\FAMILY_1851780A] Name = SAML21 Vendor = Microchip [UF2\FAMILY_1B57745F] Name = NRF52 Vendor = Nordic [UF2\FAMILY_1C5F21B0] Name = ESP32 Vendor = ESP32 [UF2\FAMILY_1E1F432D] Name = STM32L1 Vendor = ST [UF2\FAMILY_202E3A91] Name = STM32L0 Vendor = ST [UF2\FAMILY_21460FF0] Name = STM32WL Vendor = ST [UF2\FAMILY_2ABC77EC] Name = LPC55 Vendor = NXP [UF2\FAMILY_300F5633] Name = STM32G0 Vendor = ST [UF2\FAMILY_31D228C6] Name = GD32F350 Vendor = GD [UF2\FAMILY_04240BDF] Name = STM32L5 Vendor = ST [UF2\FAMILY_4C71240A] Name = STM32G4 Vendor = ST [UF2\FAMILY_4FB2D5BD] Name = MIMXRT10XX Vendor = NXP [UF2\FAMILY_53B80F00] Name = STM32F7 Vendor = ST [UF2\FAMILY_55114460] Name = SAMD51 Vendor = Microchip [UF2\FAMILY_57755A57] Name = STM32F4 Vendor = ST [UF2\FAMILY_5A18069B] Name = FX2 Vendor = Cypress [UF2\FAMILY_5D1A0A2E] Name = STM32F2 Vendor = ST [UF2\FAMILY_5EE21072] Name = STM32F1 Vendor = ST [UF2\FAMILY_647824B6] Name = STM32F0 Vendor = ST [UF2\FAMILY_68ED2B88] Name = SAMD21 Vendor = Microchip [UF2\FAMILY_6B846188] Name = STM32F3 Vendor = ST [UF2\FAMILY_6D0922FA] Name = STM32F407 Vendor = ST [UF2\FAMILY_6DB66082] Name = STM32H7 Vendor = ST [UF2\FAMILY_70D16653] Name = STM32WB Vendor = ST [UF2\FAMILY_7EAB61ED] Name = ESP8266 [UF2\FAMILY_7F83E793] Name = KL32L2 Vendor = NXP [UF2\FAMILY_8FB060FE] Name = STM32F407VG Vendor = ST [UF2\FAMILY_ADA52840] Name = NRF52840 Vendor = Nordic [UF2\FAMILY_BFDD4EEE] Name = ESP32S2 [UF2\FAMILY_C47E5767] Name = ESP32S3 [UF2\FAMILY_D42BA06C] Name = ESP32C3 [UF2\FAMILY_E48BFF56] Name = RP2040 Vendor = Raspberry Pi [UF2\FAMILY_00FF6919] Name = STM32L4 Vendor = ST fwupd-1.9.16/plugins/upower/000077500000000000000000000000001460375044200157445ustar00rootroot00000000000000fwupd-1.9.16/plugins/upower/README.md000066400000000000000000000006451460375044200172300ustar00rootroot00000000000000--- title: Plugin: UPower --- ## Introduction This plugin is used to ensure that some updates are not done on battery power. ## Vendor ID Security This protocol does not create a device and thus requires no vendor ID set. ## External Interface Access This plugin requires access to the dbus interface `org.freedesktop.UPower`. ## Version Considerations This plugin has been available since fwupd version `0.8.0`. fwupd-1.9.16/plugins/upower/fu-upower-plugin.c000066400000000000000000000135151460375044200213420ustar00rootroot00000000000000/* * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-upower-plugin.h" struct _FuUpowerPlugin { FuPlugin parent_instance; GDBusProxy *proxy; /* nullable */ GDBusProxy *proxy_manager; /* nullable */ }; typedef enum { UP_DEVICE_STATE_UNKNOWN, UP_DEVICE_STATE_CHARGING, UP_DEVICE_STATE_DISCHARGING, UP_DEVICE_STATE_EMPTY, UP_DEVICE_STATE_FULLY_CHARGED, UP_DEVICE_STATE_PENDING_CHARGE, UP_DEVICE_STATE_PENDING_DISCHARGE, UP_DEVICE_STATE_LAST } UpDeviceState; G_DEFINE_TYPE(FuUpowerPlugin, fu_upower_plugin, FU_TYPE_PLUGIN) static void fu_upower_plugin_rescan_devices(FuPlugin *plugin) { FuUpowerPlugin *self = FU_UPOWER_PLUGIN(plugin); FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(GVariant) percentage_val = NULL; g_autoptr(GVariant) type_val = NULL; g_autoptr(GVariant) state_val = NULL; /* check that we "have" a battery */ type_val = g_dbus_proxy_get_cached_property(self->proxy, "Type"); if (type_val == NULL || g_variant_get_uint32(type_val) == 0) { fu_context_set_power_state(ctx, FU_POWER_STATE_UNKNOWN); fu_context_set_battery_level(ctx, FWUPD_BATTERY_LEVEL_INVALID); return; } state_val = g_dbus_proxy_get_cached_property(self->proxy, "State"); if (state_val == NULL || g_variant_get_uint32(state_val) == 0) { g_warning("failed to query power state"); fu_context_set_power_state(ctx, FU_POWER_STATE_UNKNOWN); fu_context_set_battery_level(ctx, FWUPD_BATTERY_LEVEL_INVALID); return; } /* map from UpDeviceState to FuPowerState */ switch (g_variant_get_uint32(state_val)) { case UP_DEVICE_STATE_CHARGING: case UP_DEVICE_STATE_PENDING_CHARGE: fu_context_set_power_state(ctx, FU_POWER_STATE_AC_CHARGING); break; case UP_DEVICE_STATE_DISCHARGING: case UP_DEVICE_STATE_PENDING_DISCHARGE: fu_context_set_power_state(ctx, FU_POWER_STATE_BATTERY_DISCHARGING); break; case UP_DEVICE_STATE_EMPTY: fu_context_set_power_state(ctx, FU_POWER_STATE_BATTERY_EMPTY); break; case UP_DEVICE_STATE_FULLY_CHARGED: fu_context_set_power_state(ctx, FU_POWER_STATE_AC_FULLY_CHARGED); break; default: fu_context_set_power_state(ctx, FU_POWER_STATE_UNKNOWN); break; } /* get percentage */ percentage_val = g_dbus_proxy_get_cached_property(self->proxy, "Percentage"); if (percentage_val == NULL) { g_warning("failed to query power percentage level"); fu_context_set_battery_level(ctx, FWUPD_BATTERY_LEVEL_INVALID); return; } fu_context_set_battery_level(ctx, g_variant_get_double(percentage_val)); } static void fu_upower_plugin_rescan_manager(FuPlugin *plugin) { FuUpowerPlugin *self = FU_UPOWER_PLUGIN(plugin); FuContext *ctx = fu_plugin_get_context(plugin); g_autoptr(GVariant) lid_is_closed = NULL; g_autoptr(GVariant) lid_is_present = NULL; /* check that we "have" a lid */ lid_is_present = g_dbus_proxy_get_cached_property(self->proxy_manager, "LidIsPresent"); lid_is_closed = g_dbus_proxy_get_cached_property(self->proxy_manager, "LidIsClosed"); if (lid_is_present == NULL || lid_is_closed == NULL) { g_warning("failed to query lid state"); fu_context_set_lid_state(ctx, FU_LID_STATE_UNKNOWN); return; } if (!g_variant_get_boolean(lid_is_present)) { fu_context_set_lid_state(ctx, FU_LID_STATE_UNKNOWN); return; } if (g_variant_get_boolean(lid_is_closed)) { fu_context_set_lid_state(ctx, FU_LID_STATE_CLOSED); return; } fu_context_set_lid_state(ctx, FU_LID_STATE_OPEN); } static void fu_upower_plugin_proxy_changed_cb(GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, FuPlugin *plugin) { fu_upower_plugin_rescan_manager(plugin); fu_upower_plugin_rescan_devices(plugin); } static gboolean fu_upower_plugin_startup(FuPlugin *plugin, FuProgress *progress, GError **error) { FuUpowerPlugin *self = FU_UPOWER_PLUGIN(plugin); g_autofree gchar *name_owner = NULL; self->proxy_manager = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", NULL, error); if (self->proxy_manager == NULL) { g_prefix_error(error, "failed to connect to upower: "); return FALSE; } self->proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.UPower", "/org/freedesktop/UPower/devices/DisplayDevice", "org.freedesktop.UPower.Device", NULL, error); if (self->proxy == NULL) { g_prefix_error(error, "failed to connect to upower: "); return FALSE; } name_owner = g_dbus_proxy_get_name_owner(self->proxy); if (name_owner == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no owner for %s", g_dbus_proxy_get_name(self->proxy)); return FALSE; } g_signal_connect(G_DBUS_PROXY(self->proxy), "g-properties-changed", G_CALLBACK(fu_upower_plugin_proxy_changed_cb), plugin); g_signal_connect(G_DBUS_PROXY(self->proxy_manager), "g-properties-changed", G_CALLBACK(fu_upower_plugin_proxy_changed_cb), plugin); fu_upower_plugin_rescan_devices(plugin); fu_upower_plugin_rescan_manager(plugin); /* success */ return TRUE; } static void fu_upower_plugin_init(FuUpowerPlugin *self) { } static void fu_upower_finalize(GObject *obj) { FuUpowerPlugin *self = FU_UPOWER_PLUGIN(obj); if (self->proxy != NULL) g_object_unref(self->proxy); if (self->proxy_manager != NULL) g_object_unref(self->proxy_manager); G_OBJECT_CLASS(fu_upower_plugin_parent_class)->finalize(obj); } static void fu_upower_plugin_class_init(FuUpowerPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_upower_finalize; plugin_class->startup = fu_upower_plugin_startup; } fwupd-1.9.16/plugins/upower/fu-upower-plugin.h000066400000000000000000000003501460375044200213400ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuUpowerPlugin, fu_upower_plugin, FU, UPOWER_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/upower/meson.build000066400000000000000000000006371460375044200201140ustar00rootroot00000000000000if get_option('plugin_upower').disable_auto_if(host_machine.system() != 'linux').allowed() plugins += {meson.current_source_dir().split('/')[-1]: true} cargs = ['-DG_LOG_DOMAIN="FuPluginUpower"'] plugin_builtins += static_library('fu_plugin_upower', sources: [ 'fu-upower-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/usi-dock/000077500000000000000000000000001460375044200161415ustar00rootroot00000000000000fwupd-1.9.16/plugins/usi-dock/README.md000066400000000000000000000026161460375044200174250ustar00rootroot00000000000000--- title: Plugin: USI Dock --- ## Introduction This plugin uses the MCU to write all the dock firmware components. The MCU version is provided by the DMC bcdDevice. This plugin supports the following protocol ID: * `com.usi.dock` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_17EF&PID_7226` Additionally, some extra "component ID" instance IDs are added. * `USB\VID_17EF&PID_7226&CID_USB3` * `USB\VID_17EF&PID_7226&CID_40B0&DMCVER_10.10` ## Update Behavior The device usually presents in runtime mode, but on detach re-enumerates with the same USB PID in an unlocked mode. On attach the device again re-enumerates back to the runtime locked mode. ## Vendor ID Security The vendor ID is set from the USB vendor. ## Quirk Use This plugin uses the following plugin-specific quirks: ### `Flags=verfmt-hp` Use the HP-style `quad` version format. Since: 1.7.4 ### `Flags=set-chip-type` Workaround a provisioning problem by setting the chip type when the new update has completed. Since: 1.9.2 ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.7.4`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Victor Cheng: @victor-cheng fwupd-1.9.16/plugins/usi-dock/data/000077500000000000000000000000001460375044200170525ustar00rootroot00000000000000fwupd-1.9.16/plugins/usi-dock/data/lsusb.txt000066400000000000000000000044551460375044200207530ustar00rootroot00000000000000Bus 001 Device 024: ID 17ef:30b4 Lenovo ThinkPad Thunderbolt 4 Dock MCU Controller Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x17ef Lenovo idProduct 0x30b4 bcdDevice 1.00 iManufacturer 1 Lenovo iProduct 2 ThinkPad Thunderbolt 4 Dock MCU Controller iSerial 3 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0029 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 100mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 39 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/usi-dock/fu-usi-dock-child-device.c000066400000000000000000000046241460375044200227570ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-usi-dock-child-device.h" #include "fu-usi-dock-mcu-device.h" struct _FuUsiDockChildDevice { FuDevice parent_instance; guint8 chip_idx; }; G_DEFINE_TYPE(FuUsiDockChildDevice, fu_usi_dock_child_device, FU_TYPE_DEVICE) void fu_usi_dock_child_device_set_chip_idx(FuUsiDockChildDevice *self, guint8 chip_idx) { self->chip_idx = chip_idx; } static void fu_usi_dock_child_device_to_string(FuDevice *device, guint idt, GString *str) { FuUsiDockChildDevice *self = FU_USI_DOCK_CHILD_DEVICE(device); fu_string_append_kx(str, idt, "ChipIdx", self->chip_idx); } /* use the parents parser */ static FuFirmware * fu_usi_dock_mcu_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuDevice *parent = fu_device_get_parent(device); if (parent == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no parent"); return NULL; } return fu_device_prepare_firmware(parent, fw, flags, error); } /* only update this specific child component */ static gboolean fu_usi_dock_mcu_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuUsiDockChildDevice *self = FU_USI_DOCK_CHILD_DEVICE(device); FuDevice *parent = fu_device_get_parent(device); if (parent == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no parent"); return FALSE; } return fu_usi_dock_mcu_device_write_firmware_with_idx(FU_USI_DOCK_MCU_DEVICE(parent), firmware, self->chip_idx, progress, flags, error); } static void fu_usi_dock_child_device_init(FuUsiDockChildDevice *self) { fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN); } static void fu_usi_dock_child_device_class_init(FuUsiDockChildDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_usi_dock_child_device_to_string; klass_device->prepare_firmware = fu_usi_dock_mcu_device_prepare_firmware; klass_device->write_firmware = fu_usi_dock_mcu_device_write_firmware; } FuDevice * fu_usi_dock_child_new(FuContext *ctx) { return g_object_new(FU_TYPE_USI_DOCK_CHILD_DEVICE, "context", ctx, NULL); } fwupd-1.9.16/plugins/usi-dock/fu-usi-dock-child-device.h000066400000000000000000000007621460375044200227630ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_USI_DOCK_CHILD_DEVICE (fu_usi_dock_child_device_get_type()) G_DECLARE_FINAL_TYPE(FuUsiDockChildDevice, fu_usi_dock_child_device, FU, USI_DOCK_CHILD_DEVICE, FuDevice) FuDevice * fu_usi_dock_child_new(FuContext *ctx); void fu_usi_dock_child_device_set_chip_idx(FuUsiDockChildDevice *self, guint8 chip_idx); fwupd-1.9.16/plugins/usi-dock/fu-usi-dock-dmc-device.c000066400000000000000000000036751460375044200224440ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-usi-dock-dmc-device.h" struct _FuUsiDockDmcDevice { FuUsbDevice parent_instance; }; G_DEFINE_TYPE(FuUsiDockDmcDevice, fu_usi_dock_dmc_device, FU_TYPE_USB_DEVICE) static void fu_usi_dock_dmc_device_parent_notify_cb(FuDevice *device, GParamSpec *pspec, gpointer user_data) { FuDevice *parent = fu_device_get_parent(device); if (parent != NULL) { g_autoptr(GError) error = NULL; /* slightly odd: the MCU device uses the DMC version number */ g_info("absorbing DMC version into MCU"); fu_device_set_version_format(parent, fu_device_get_version_format(device)); fu_device_set_version(parent, fu_device_get_version(device)); fu_device_set_serial(parent, fu_device_get_serial(device)); /* allow matching firmware */ fu_device_add_instance_str(parent, "CID", fu_device_get_name(device)); if (!fu_device_build_instance_id(parent, &error, "USB", "VID", "PID", "CID", NULL)) { g_warning("failed to build ID: %s", error->message); return; } /* this might match Flags=set-chip-type */ fu_device_add_instance_str(parent, "DMCVER", fu_device_get_version(device)); if (!fu_device_build_instance_id_full(parent, FU_DEVICE_INSTANCE_FLAG_QUIRKS, &error, "USB", "VID", "PID", "CID", "DMCVER", NULL)) { g_warning("failed to build MCU DMC Instance ID: %s", error->message); return; } /* use a better device name */ fu_device_set_name(device, "Dock Management Controller Information"); } } static void fu_usi_dock_dmc_device_init(FuUsiDockDmcDevice *self) { g_signal_connect(FU_DEVICE(self), "notify::parent", G_CALLBACK(fu_usi_dock_dmc_device_parent_notify_cb), NULL); } static void fu_usi_dock_dmc_device_class_init(FuUsiDockDmcDeviceClass *klass) { } fwupd-1.9.16/plugins/usi-dock/fu-usi-dock-dmc-device.h000066400000000000000000000005371460375044200224430ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_USI_DOCK_DMC_DEVICE (fu_usi_dock_dmc_device_get_type()) G_DECLARE_FINAL_TYPE(FuUsiDockDmcDevice, fu_usi_dock_dmc_device, FU, USI_DOCK_DMC_DEVICE, FuUsbDevice) fwupd-1.9.16/plugins/usi-dock/fu-usi-dock-mcu-device.c000066400000000000000000000642131460375044200224600ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2021 Victor Cheng * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-usi-dock-child-device.h" #include "fu-usi-dock-dmc-device.h" #include "fu-usi-dock-mcu-device.h" #include "fu-usi-dock-struct.h" struct _FuUsiDockMcuDevice { FuHidDevice parent_instance; }; G_DEFINE_TYPE(FuUsiDockMcuDevice, fu_usi_dock_mcu_device, FU_TYPE_HID_DEVICE) #define FU_USI_DOCK_MCU_DEVICE_TIMEOUT 5000 /* ms */ #define USB_HID_REPORT_ID1 1u #define USB_HID_REPORT_ID2 2u #define DP_VERSION_FROM_MCU 0x01 /* if in use */ #define NIC_VERSION_FROM_MCU 0x2 /* if in use */ #define W25Q16DV_PAGE_SIZE 256 #define FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP (1 << 0) #define FU_USI_DOCK_DEVICE_FLAG_SET_CHIP_TYPE (1 << 1) #define FU_USI_DOCK_DEVICE_FLAG_WAITING_FOR_UNPLUG (1 << 2) static gboolean fu_usi_dock_mcu_device_tx(FuUsiDockMcuDevice *self, FuUsiDockTag2 tag2, const guint8 *buf, gsize bufsz, GError **error) { g_autoptr(GByteArray) st = fu_struct_usi_dock_mcu_cmd_req_new(); fu_struct_usi_dock_mcu_cmd_req_set_length(st, 0x3 + bufsz); fu_struct_usi_dock_mcu_cmd_req_set_tag3(st, tag2); if (buf != NULL) { if (!fu_struct_usi_dock_mcu_cmd_req_set_buf(st, buf, bufsz, error)) return FALSE; } /* special cases */ if (st->data[FU_STRUCT_USI_DOCK_MCU_CMD_REQ_OFFSET_BUF + 0] == FU_USI_DOCK_MCU_CMD_FW_UPDATE) st->data[FU_STRUCT_USI_DOCK_MCU_CMD_REQ_OFFSET_BUF + 1] = 0xFF; return fu_hid_device_set_report(FU_HID_DEVICE(self), USB_HID_REPORT_ID2, st->data, st->len, FU_USI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error); } static gboolean fu_usi_dock_mcu_device_rx(FuUsiDockMcuDevice *self, guint8 cmd, guint8 *outbuf, gsize outbufsz, GError **error) { guint8 buf[64] = {0}; g_autoptr(GByteArray) st_rsp = NULL; if (!fu_hid_device_get_report(FU_HID_DEVICE(self), USB_HID_REPORT_ID2, buf, sizeof(buf), FU_USI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) { return FALSE; } st_rsp = fu_struct_usi_dock_mcu_cmd_res_parse(buf, sizeof(buf), 0x0, error); if (st_rsp == NULL) return FALSE; if (outbuf != NULL) { if (!fu_memcpy_safe(outbuf, outbufsz, 0x0, /* dst */ buf, sizeof(buf), FU_STRUCT_USI_DOCK_MCU_CMD_RES_OFFSET_BUF, /* src */ outbufsz, error)) return FALSE; } return TRUE; } static gboolean fu_usi_dock_mcu_device_txrx(FuUsiDockMcuDevice *self, FuUsiDockTag2 tag2, const guint8 *inbuf, gsize inbufsz, guint8 *outbuf, gsize outbufsz, GError **error) { if (!fu_usi_dock_mcu_device_tx(self, tag2, inbuf, inbufsz, error)) { g_prefix_error(error, "failed to transmit: "); return FALSE; } if (!fu_usi_dock_mcu_device_rx(self, FU_USI_DOCK_MCU_CMD_ALL, outbuf, outbufsz, error)) { g_prefix_error(error, "failed to receive: "); return FALSE; } return TRUE; } static gboolean fu_usi_dock_mcu_device_get_status(FuUsiDockMcuDevice *self, GError **error) { guint8 cmd = FU_USI_DOCK_MCU_CMD_MCU_STATUS; guint8 response = 0; if (!fu_usi_dock_mcu_device_txrx(self, FU_USI_DOCK_TAG2_CMD_MCU, &cmd, sizeof(cmd), &response, sizeof(response), error)) { g_prefix_error(error, "failed to send CMD MCU: "); return FALSE; } if (response == 0x1) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_BUSY, "device is busy"); return FALSE; } if (response == 0xFF) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "device timed out"); return FALSE; } /* success */ return TRUE; } static gboolean fu_usi_dock_mcu_device_enumerate_children(FuUsiDockMcuDevice *self, GError **error) { guint8 inbuf[] = {FU_USI_DOCK_MCU_CMD_READ_MCU_VERSIONPAGE, DP_VERSION_FROM_MCU | NIC_VERSION_FROM_MCU}; guint8 outbuf[49] = {0x0}; struct { const gchar *name; guint8 chip_idx; gsize offset; } components[] = { {"DMC", FU_USI_DOCK_FIRMWARE_IDX_DMC_PD, FU_STRUCT_USI_DOCK_ISP_VERSION_OFFSET_DMC}, {"PD", FU_USI_DOCK_FIRMWARE_IDX_DP, FU_STRUCT_USI_DOCK_ISP_VERSION_OFFSET_PD}, {"DP5x", FU_USI_DOCK_FIRMWARE_IDX_NONE, FU_STRUCT_USI_DOCK_ISP_VERSION_OFFSET_DP5X}, {"DP6x", FU_USI_DOCK_FIRMWARE_IDX_NONE, FU_STRUCT_USI_DOCK_ISP_VERSION_OFFSET_DP6X}, {"TBT4", FU_USI_DOCK_FIRMWARE_IDX_TBT4, FU_STRUCT_USI_DOCK_ISP_VERSION_OFFSET_TBT4}, {"USB3", FU_USI_DOCK_FIRMWARE_IDX_USB3, FU_STRUCT_USI_DOCK_ISP_VERSION_OFFSET_USB3}, {"USB2", FU_USI_DOCK_FIRMWARE_IDX_USB2, FU_STRUCT_USI_DOCK_ISP_VERSION_OFFSET_USB2}, {"AUDIO", FU_USI_DOCK_FIRMWARE_IDX_AUDIO, FU_STRUCT_USI_DOCK_ISP_VERSION_OFFSET_AUDIO}, {"I255", FU_USI_DOCK_FIRMWARE_IDX_I225, FU_STRUCT_USI_DOCK_ISP_VERSION_OFFSET_I255}, {"MCU", FU_USI_DOCK_FIRMWARE_IDX_MCU, FU_STRUCT_USI_DOCK_ISP_VERSION_OFFSET_MCU}, {"bcdVersion", FU_USI_DOCK_FIRMWARE_IDX_NONE, FU_STRUCT_USI_DOCK_ISP_VERSION_OFFSET_BCDVERSION}, {NULL, 0, 0}}; /* assume DP and NIC in-use */ if (!fu_usi_dock_mcu_device_txrx(self, FU_USI_DOCK_TAG2_CMD_MCU, inbuf, sizeof(inbuf), outbuf, sizeof(outbuf), error)) return FALSE; for (guint i = 0; components[i].name != NULL; i++) { const guint8 *val = outbuf + components[i].offset; g_autofree gchar *version = NULL; g_autoptr(FuDevice) child = NULL; child = fu_usi_dock_child_new(fu_device_get_context(FU_DEVICE(self))); if (g_strcmp0(components[i].name, "bcdVersion") == 0) { if ((val[0] == 0x00 && val[1] == 0x00) || (val[0] == 0xFF && val[1] == 0xFF)) { g_debug("ignoring %s", components[i].name); continue; } if (fu_device_has_private_flag(FU_DEVICE(self), FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP)) { version = g_strdup_printf("%x.%x.%x.%x", val[0] >> 4, val[0] & 0xFu, val[1] >> 4, val[1] & 0xFu); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version(FU_DEVICE(self), version); } else { version = g_strdup_printf("%x.%x.%02x", val[0] & 0xFu, val[0] >> 4, val[1]); g_debug("ignoring %s --> %s", components[i].name, version); } continue; } if (g_strcmp0(components[i].name, "DMC") == 0) { if ((val[2] == 0x00 && val[3] == 0x00 && val[4] == 0x00) || (val[2] == 0xFF && val[3] == 0xFF && val[4] == 0xFF)) { g_debug("ignoring %s", components[i].name); continue; } version = g_strdup_printf("%d.%d.%d", val[2], val[3], val[4]); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(child, version); fu_device_set_name(child, "Dock Management Controller"); } else if (g_strcmp0(components[i].name, "PD") == 0) { if ((val[1] == 0x00 && val[2] == 0x00 && val[3] == 0x00 && val[4] == 0x00) || (val[1] == 0xFF && val[2] == 0xFF && val[3] == 0xFF && val[4] == 0xFF)) { g_debug("ignoring %s", components[i].name); continue; } if (fu_device_has_private_flag(FU_DEVICE(self), FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP)) { version = g_strdup_printf("%d.%d.%d.%d", val[3], val[4], val[1], val[2]); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_QUAD); } else { version = g_strdup_printf("%d.%d.%d", val[2], val[3], val[4]); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); } fu_device_set_version(child, version); fu_device_set_name(child, "Power Delivery"); } else if (g_strcmp0(components[i].name, "TBT4") == 0) { if ((val[1] == 0x00 && val[2] == 0x00 && val[3] == 0x00) || (val[1] == 0xFF && val[2] == 0xFF && val[3] == 0xFF)) { g_debug("ignoring %s", components[i].name); continue; } version = g_strdup_printf("%02x.%02x.%02x", val[1], val[2], val[3]); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(child, version); fu_device_add_icon(child, "thunderbolt"); fu_device_set_name(child, "Thunderbolt 4 Controller"); } else if (g_strcmp0(components[i].name, "DP5x") == 0) { if ((val[2] == 0x00 && val[3] == 0x00 && val[4] == 0x00) || (val[2] == 0xFF && val[3] == 0xFF && val[4] == 0xFF)) { g_debug("ignoring %s", components[i].name); continue; } version = g_strdup_printf("%d.%02d.%03d", val[2], val[3], val[4]); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(child, version); fu_device_add_icon(child, "video-display"); fu_device_set_name(child, "Display Port 5"); } else if (g_strcmp0(components[i].name, "DP6x") == 0) { if ((val[2] == 0x00 && val[3] == 0x00 && val[4] == 0x00) || (val[2] == 0xFF && val[3] == 0xFF && val[4] == 0xFF)) { g_debug("ignoring %s", components[i].name); continue; } if (fu_device_has_private_flag(FU_DEVICE(self), FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP)) { version = g_strdup_printf("%x.%x.%x.%x", val[3], val[4], val[2], val[1]); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_name(child, "USB/PD HUB"); } else { version = g_strdup_printf("%d.%02d.%03d", val[2], val[3], val[4]); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_name(child, "Display Port 6"); } fu_device_set_version(child, version); fu_device_add_icon(child, "video-display"); } else if (g_strcmp0(components[i].name, "USB3") == 0) { if ((val[3] == 0x00 && val[4] == 0x00) || (val[3] == 0xFF && val[4] == 0xFF)) { g_debug("ignoring %s", components[i].name); continue; } version = g_strdup_printf("%02X%02X", val[3], val[4]); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_NUMBER); fu_device_set_version(child, version); fu_device_set_name(child, "USB 3 Hub"); } else if (g_strcmp0(components[i].name, "USB2") == 0) { if ((val[0] == 0x00 && val[1] == 0x00 && val[2] == 0x00 && val[3] == 0x00 && val[4] == 0x00) || (val[0] == 0xFF && val[1] == 0xFF && val[2] == 0xFF && val[3] == 0xFF && val[4] == 0xFF)) { g_debug("ignoring %s", components[i].name); continue; } version = g_strdup_printf("%c%c%c%c%c", val[0], val[1], val[2], val[3], val[4]); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_version(child, version); fu_device_set_name(child, "USB 2 Hub"); } else if (g_strcmp0(components[i].name, "AUDIO") == 0) { if ((val[2] == 0x00 && val[3] == 0x00 && val[4] == 0x00) || (val[2] == 0xFF && val[3] == 0xFF && val[4] == 0xFF)) { g_debug("ignoring %s", components[i].name); continue; } version = g_strdup_printf("%02X-%02X-%02X", val[2], val[3], val[4]); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_version(child, version); fu_device_set_name(child, "Audio Controller"); } else if (g_strcmp0(components[i].name, "I255") == 0) { if ((val[2] == 0x00 && val[3] == 0x00 && val[4] == 0x00) || (val[2] == 0xFF && val[3] == 0xFF && val[4] == 0xFF)) { g_debug("ignoring %s", components[i].name); continue; } version = g_strdup_printf("%x.%x.%x", val[2] >> 4, val[3], val[4]); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(child, version); fu_device_add_icon(child, "network-wired"); fu_device_set_name(child, "Ethernet Adapter"); } else if (g_strcmp0(components[i].name, "MCU") == 0) { if ((val[0] == 0x00 && val[1] == 0x00) || (val[0] == 0xFF && val[1] == 0xFF)) { g_debug("ignoring %s", components[i].name); continue; } if (fu_device_has_private_flag(FU_DEVICE(self), FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP)) { version = g_strdup_printf("%x.%x.%x.%x", val[0] >> 4, val[0] & 0xFu, val[1] >> 4, val[1] & 0xFu); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_QUAD); } else { version = g_strdup_printf("%X.%X", val[0], val[1]); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_PLAIN); } fu_device_set_version(child, version); fu_device_set_name(child, "Dock Management Controller"); } else { g_warning("unhandled %s", components[i].name); } /* add virtual device */ fu_device_add_instance_u16(child, "VID", fu_usb_device_get_vid(FU_USB_DEVICE(self))); fu_device_add_instance_u16(child, "PID", fu_usb_device_get_pid(FU_USB_DEVICE(self))); fu_device_add_instance_str(child, "CID", components[i].name); if (!fu_device_build_instance_id(child, error, "USB", "VID", "PID", "CID", NULL)) return FALSE; if (fu_device_get_name(child) == NULL) fu_device_set_name(child, components[i].name); fu_device_set_logical_id(child, components[i].name); fu_usi_dock_child_device_set_chip_idx(FU_USI_DOCK_CHILD_DEVICE(child), components[i].chip_idx); fu_device_add_child(FU_DEVICE(self), child); } /* success */ return TRUE; } static gboolean fu_usi_dock_mcu_device_setup(FuDevice *device, GError **error) { FuUsiDockMcuDevice *self = FU_USI_DOCK_MCU_DEVICE(device); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_usi_dock_mcu_device_parent_class)->setup(device, error)) return FALSE; /* get status and component versions */ if (!fu_usi_dock_mcu_device_get_status(self, error)) { g_prefix_error(error, "failed to get status: "); return FALSE; } if (!fu_usi_dock_mcu_device_enumerate_children(self, error)) { g_prefix_error(error, "failed to enumerate children: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_usi_dock_mcu_device_write_chunk(FuUsiDockMcuDevice *self, FuChunk *chk, GError **error) { g_autoptr(GByteArray) st_req = fu_struct_usi_dock_hid_req_new(); fu_struct_usi_dock_hid_req_set_length(st_req, fu_chunk_get_data_sz(chk)); fu_struct_usi_dock_hid_req_set_tag3(st_req, FU_USI_DOCK_TAG2_MASS_DATA_SPI); if (!fu_memcpy_safe(st_req->data, st_req->len, FU_STRUCT_USI_DOCK_HID_REQ_OFFSET_BUF, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), USB_HID_REPORT_ID2, st_req->data, st_req->len, FU_USI_DOCK_MCU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_USE_INTERRUPT_TRANSFER, error)) return FALSE; return fu_usi_dock_mcu_device_rx(self, FU_USI_DOCK_MCU_CMD_ALL, NULL, 0x0, error); } static gboolean fu_usi_dock_mcu_device_write_page(FuUsiDockMcuDevice *self, FuChunk *chk_page, GError **error) { g_autoptr(FuChunkArray) chunks = NULL; g_autoptr(GBytes) chk_blob = fu_chunk_get_bytes(chk_page); chunks = fu_chunk_array_new_from_bytes(chk_blob, 0x0, FU_STRUCT_USI_DOCK_HID_REQ_SIZE_BUF); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_usi_dock_mcu_device_write_chunk(self, chk, error)) return FALSE; } return TRUE; } static gboolean fu_usi_dock_mcu_device_write_pages(FuUsiDockMcuDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_usi_dock_mcu_device_write_page(self, chk, error)) { g_prefix_error(error, "failed to write chunk 0x%x: ", i); return FALSE; } fu_progress_step_done(progress); } return TRUE; } static gboolean fu_usi_dock_mcu_device_wait_for_spi_ready_cb(FuDevice *device, gpointer user_data, GError **error) { FuUsiDockMcuDevice *self = FU_USI_DOCK_MCU_DEVICE(device); guint8 buf[] = {FU_USI_DOCK_SPI_CMD_READ_STATUS}; guint8 val = 0; if (!fu_usi_dock_mcu_device_txrx(self, FU_USI_DOCK_TAG2_CMD_SPI, buf, sizeof(buf), &val, sizeof(val), error)) return FALSE; if (val != FU_USI_DOCK_SPI_STATE_READY) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "SPI state is %s [0x%02x]", fu_usi_dock_spi_state_to_string(val), val); return FALSE; } /* success */ return TRUE; } static gboolean fu_usi_dock_mcu_device_wait_for_spi_initial_ready_cb(FuDevice *device, gpointer user_data, GError **error) { FuUsiDockMcuDevice *self = FU_USI_DOCK_MCU_DEVICE(device); guint8 buf[] = {FU_USI_DOCK_SPI_CMD_INITIAL}; guint8 val = 0; if (!fu_usi_dock_mcu_device_txrx(self, FU_USI_DOCK_TAG2_CMD_SPI, buf, sizeof(buf), &val, sizeof(val), error)) return FALSE; if (val != FU_USI_DOCK_SPI_STATE_READY) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "SPI state is %s [0x%02x]", fu_usi_dock_spi_state_to_string(val), val); return FALSE; } /* success */ return TRUE; } static gboolean fu_usi_dock_mcu_device_wait_for_checksum_cb(FuDevice *device, gpointer user_data, GError **error) { FuUsiDockMcuDevice *self = FU_USI_DOCK_MCU_DEVICE(device); if (!fu_usi_dock_mcu_device_rx(self, FU_USI_DOCK_MCU_CMD_ALL, (guint8 *)user_data, sizeof(guint8), error)) return FALSE; /* success */ return TRUE; } gboolean fu_usi_dock_mcu_device_write_firmware_with_idx(FuUsiDockMcuDevice *self, FuFirmware *firmware, guint8 chip_idx, FuProgress *progress, FwupdInstallFlags flags, GError **error) { guint8 cmd; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; guint8 checksum = 0xFF; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 5, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 69, "write-external"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 25, "wait-for-checksum"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 0, "internal-flash"); /* initial external flash */ if (!fu_device_retry(FU_DEVICE(self), fu_usi_dock_mcu_device_wait_for_spi_initial_ready_cb, 30, NULL, error)) { g_prefix_error(error, "failed to wait for initial: "); return FALSE; } fu_progress_step_done(progress); /* erase external flash */ cmd = FU_USI_DOCK_SPI_CMD_ERASE_FLASH; if (!fu_usi_dock_mcu_device_txrx(self, FU_USI_DOCK_TAG2_CMD_SPI, &cmd, sizeof(cmd), NULL, 0x0, error)) return FALSE; if (!fu_device_retry(FU_DEVICE(self), fu_usi_dock_mcu_device_wait_for_spi_ready_cb, 30, NULL, error)) { g_prefix_error(error, "failed to wait for erase: "); return FALSE; } fu_progress_step_done(progress); /* write external flash */ cmd = FU_USI_DOCK_SPI_CMD_PROGRAM; if (!fu_usi_dock_mcu_device_txrx(self, FU_USI_DOCK_TAG2_CMD_SPI, &cmd, sizeof(cmd), NULL, 0x0, error)) return FALSE; fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; chunks = fu_chunk_array_new_from_bytes(fw, 0x0, W25Q16DV_PAGE_SIZE); if (!fu_usi_dock_mcu_device_write_pages(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* file transfer – finished */ cmd = FU_USI_DOCK_SPI_CMD_TRANSFER_FINISH; if (!fu_usi_dock_mcu_device_txrx(self, FU_USI_DOCK_TAG2_CMD_SPI, &cmd, sizeof(cmd), NULL, 0x0, error)) return FALSE; /* MCU checksum */ if (!fu_device_retry(FU_DEVICE(self), fu_usi_dock_mcu_device_wait_for_checksum_cb, 300, &checksum, error)) { g_prefix_error(error, "failed to wait for checksum: "); return FALSE; } if (checksum != 0x0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid checksum result for CMD_FWBUFER_CHECKSUM, got 0x%02x", checksum); return FALSE; } fu_progress_step_done(progress); /* internal flash */ cmd = FU_USI_DOCK_MCU_CMD_FW_UPDATE; if (!fu_usi_dock_mcu_device_txrx(self, FU_USI_DOCK_TAG2_CMD_MCU, &cmd, sizeof(cmd), NULL, 0x0, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_usi_dock_mcu_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { return fu_usi_dock_mcu_device_write_firmware_with_idx(FU_USI_DOCK_MCU_DEVICE(device), firmware, 0xFF, /* all */ progress, flags, error); } static gboolean fu_usi_dock_mcu_device_reload(FuDevice *device, GError **error) { FuUsiDockMcuDevice *self = FU_USI_DOCK_MCU_DEVICE(device); guint8 inbuf[] = {FU_USI_DOCK_MCU_CMD_SET_CHIP_TYPE, 1, 1}; if (fu_device_has_private_flag(device, FU_USI_DOCK_DEVICE_FLAG_SET_CHIP_TYPE)) { g_info("repairing device with CMD_SET_CHIP_TYPE"); if (!fu_usi_dock_mcu_device_txrx(self, FU_USI_DOCK_TAG2_CMD_MCU, inbuf, sizeof(inbuf), NULL, 0x0, error)) return FALSE; } return TRUE; } static gboolean fu_usi_dock_mcu_device_attach(FuDevice *device, FuProgress *progress, GError **error) { fu_device_set_remove_delay(device, 900000); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* success */ return TRUE; } static gboolean fu_usi_dock_mcu_device_insert_cb(gpointer user_data) { FuDevice *device = FU_DEVICE(user_data); g_autoptr(FwupdRequest) request = fwupd_request_new(); g_autoptr(GError) error_local = NULL; /* interactive request to start the SPI write */ fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_INSERT_USB_CABLE); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); if (!fu_device_emit_request(device, request, NULL, &error_local)) g_critical("%s", error_local->message); /* success */ return G_SOURCE_REMOVE; } static void fu_usi_dock_mcu_device_internal_flags_notify_cb(FuDevice *device, GParamSpec *pspec, gpointer user_data) { if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_UNCONNECTED) && fu_device_has_private_flag(device, FU_USI_DOCK_DEVICE_FLAG_WAITING_FOR_UNPLUG)) { g_debug("starting 40s countdown"); g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, 40, /* seconds */ fu_usi_dock_mcu_device_insert_cb, g_object_ref(device), g_object_unref); fu_device_remove_private_flag(device, FU_USI_DOCK_DEVICE_FLAG_WAITING_FOR_UNPLUG); } } static gboolean fu_usi_dock_mcu_device_cleanup(FuDevice *device, FuProgress *progress, FwupdInstallFlags install_flags, GError **error) { g_autoptr(FwupdRequest) request = fwupd_request_new(); /* wait for the user to unplug then start the 40 second timer */ fu_device_add_private_flag(device, FU_USI_DOCK_DEVICE_FLAG_WAITING_FOR_UNPLUG); fu_device_set_remove_delay(device, 900000); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_BUSY); /* interactive request to start the SPI write */ fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_USB_CABLE); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); return fu_device_emit_request(device, request, progress, error); } static void fu_usi_dock_mcu_device_replace(FuDevice *device, FuDevice *donor) { if (fu_device_has_private_flag(donor, FU_USI_DOCK_DEVICE_FLAG_SET_CHIP_TYPE)) fu_device_add_private_flag(device, FU_USI_DOCK_DEVICE_FLAG_SET_CHIP_TYPE); } static void fu_usi_dock_mcu_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 48, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 52, "reload"); } static void fu_usi_dock_mcu_device_init(FuUsiDockMcuDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG); fu_device_add_request_flag(FU_DEVICE(self), FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); g_signal_connect(FWUPD_DEVICE(self), "notify::internal-flags", G_CALLBACK(fu_usi_dock_mcu_device_internal_flags_notify_cb), NULL); fu_device_register_private_flag(FU_DEVICE(self), FU_USI_DOCK_DEVICE_FLAG_VERFMT_HP, "verfmt-hp"); fu_device_register_private_flag(FU_DEVICE(self), FU_USI_DOCK_DEVICE_FLAG_SET_CHIP_TYPE, "set-chip-type"); fu_device_register_private_flag(FU_DEVICE(self), FU_USI_DOCK_DEVICE_FLAG_WAITING_FOR_UNPLUG, "waiting-for-unplug"); fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_AUTODETECT_EPS); fu_device_add_protocol(FU_DEVICE(self), "com.usi.dock"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_NUMBER); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_retry_set_delay(FU_DEVICE(self), 1000); fu_device_add_icon(FU_DEVICE(self), "dock"); } static void fu_usi_dock_mcu_device_class_init(FuUsiDockMcuDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_usi_dock_mcu_device_write_firmware; klass_device->attach = fu_usi_dock_mcu_device_attach; klass_device->setup = fu_usi_dock_mcu_device_setup; klass_device->set_progress = fu_usi_dock_mcu_device_set_progress; klass_device->cleanup = fu_usi_dock_mcu_device_cleanup; klass_device->reload = fu_usi_dock_mcu_device_reload; klass_device->replace = fu_usi_dock_mcu_device_replace; } fwupd-1.9.16/plugins/usi-dock/fu-usi-dock-mcu-device.h000066400000000000000000000011241460375044200224550ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_USI_DOCK_MCU_DEVICE (fu_usi_dock_mcu_device_get_type()) G_DECLARE_FINAL_TYPE(FuUsiDockMcuDevice, fu_usi_dock_mcu_device, FU, USI_DOCK_MCU_DEVICE, FuHidDevice) gboolean fu_usi_dock_mcu_device_write_firmware_with_idx(FuUsiDockMcuDevice *self, FuFirmware *firmware, guint8 chip_idx, FuProgress *progress, FwupdInstallFlags flags, GError **error); fwupd-1.9.16/plugins/usi-dock/fu-usi-dock-plugin.c000066400000000000000000000027261460375044200217360ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * Copyright (C) 2021 Victor Cheng * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-usi-dock-dmc-device.h" #include "fu-usi-dock-mcu-device.h" #include "fu-usi-dock-plugin.h" #define USI_DOCK_TBT_INSTANCE_ID "THUNDERBOLT\\VEN_0108&DEV_2031" struct _FuUsiDockPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuUsiDockPlugin, fu_usi_dock_plugin, FU_TYPE_PLUGIN) static void fu_usi_dock_plugin_dmc_registered(FuPlugin *plugin, FuDevice *device) { /* usb device from thunderbolt plugin */ if (g_strcmp0(fu_device_get_plugin(device), "thunderbolt") == 0 && fu_device_has_guid(device, USI_DOCK_TBT_INSTANCE_ID)) { g_autofree gchar *msg = NULL; msg = g_strdup_printf("firmware update inhibited by [%s] plugin", fu_plugin_get_name(plugin)); fu_device_inhibit(device, "usb-blocked", msg); } } static void fu_usi_dock_plugin_init(FuUsiDockPlugin *self) { } static void fu_usi_dock_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_USI_DOCK_MCU_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_USI_DOCK_DMC_DEVICE); } static void fu_usi_dock_plugin_class_init(FuUsiDockPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_usi_dock_plugin_constructed; plugin_class->device_registered = fu_usi_dock_plugin_dmc_registered; } fwupd-1.9.16/plugins/usi-dock/fu-usi-dock-plugin.h000066400000000000000000000003551460375044200217370ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuUsiDockPlugin, fu_usi_dock_plugin, FU, USI_DOCK_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/usi-dock/fu-usi-dock.rs000066400000000000000000000044741460375044200206460ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] enum UsiDockSpiState { None, SwitchSuccess, SwitchFail, CmdSuccess, CmdFail, RwSuccess, RwFail, Ready, Busy, Timeout, FlashFound, FlashNotFound, } enum UsiDockFirmwareIdx { None = 0x00, DmcPd = 0x01, Dp = 0x02, Tbt4 = 0x04, Usb3 = 0x08, Usb2 = 0x10, Audio = 0x20, I225 = 0x40, Mcu = 0x80, } #[repr(u8)] enum UsiDockTag2 { IspBoot = 0, // before Common CMD for bootload, with TAG0, TAG1, CMD Isp = 0x5A, // before Common, with TAG0, TAG1, CMD CmdMcu = 0x6A, // USB->MCU(Common-cmd mode), with TAG0, TAG1, CMD CmdSpi = 0x7A, // USB->MCU->SPI(Common-cmd mode), with TAG0, TAG1, CMD CmdI2c = 0x8A, // USB->MCU->I2C(Mass data transmission) MassDataMcu = 0x6B, // MASS data transfer for MCU 0xA0 MassDataSpi = 0x7B, // MASS data transfer for External flash 0xA1 MassDataI2c = 0x8B, // MASS data transfer for TBT flash } #[repr(u8)] enum UsiDockMcuCmd { McuNone = 0x0, McuStatus = 0x1, McuJump2boot = 0x2, ReadMcuVersionpage = 0x3, SetI225Pwr = 0x4, DockReset = 0x5, VersionWriteback = 0x6, SetChipType = 0x9, FwInitial = 0x0A, FwUpdate = 0x0B, FwTargetChecksum = 0x0C, FwIspEnd = 0x0D, All = 0xFF, } enum UsiDockSpiCmd { Initial = 0x01, EraseFlash = 0x02, Program = 0x03, WriteResponse = 0x04, ReadStatus = 0x05, Checksum = 0x06, End = 0x07, TransferFinish = 0x08, ErrorEnd = 0x09, } #[derive(New)] struct UsiDockHidReq { id: u8 == 2, length: u8, buf: [u8; 61], tag3: UsiDockTag2, } #[derive(New)] struct UsiDockMcuCmdReq { id: u8 == 2, length: u8, tag1: u8 == 0xFE, tag2: u8 == 0xFF, buf: [u8; 59], tag3: UsiDockTag2, } #[derive(Parse)] struct UsiDockMcuCmdRes { id: u8 == 2, cmd1: UsiDockMcuCmd, tag1: u8 == 0xFE, tag2: u8 == 0xFF, cmd2: UsiDockMcuCmd, buf: [u8; 58], tag3: UsiDockTag2, } struct UsiDockIspVersion { dmc: [u8; 5], pd: [u8; 5], dp5x: [u8; 5], dp6x: [u8; 5], tbt4: [u8; 5], usb3: [u8; 5], usb2: [u8; 5], audio: [u8; 5], i255: [u8; 5], mcu: [u8; 2], bcdversion: [u8; 2], } fwupd-1.9.16/plugins/usi-dock/meson.build000066400000000000000000000010101460375044200202730ustar00rootroot00000000000000if gusb.found() plugins += {meson.current_source_dir().split('/')[-1]: true} cargs = ['-DG_LOG_DOMAIN="FuPluginUsiDock"'] plugin_quirks += files('usi-dock.quirk') plugin_builtins += static_library('fu_plugin_usi_dock', rustgen.process('fu-usi-dock.rs'), sources: [ 'fu-usi-dock-child-device.c', 'fu-usi-dock-dmc-device.c', 'fu-usi-dock-mcu-device.c', 'fu-usi-dock-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/usi-dock/usi-dock.quirk000066400000000000000000000013201460375044200207300ustar00rootroot00000000000000[USB\VID_17EF&PID_30B4&CID_40B0] Plugin = usi_dock GType = FuUsiDockMcuDevice Name = ThinkPad Thunderbolt 4 Dock [USB\VID_17EF&PID_30B4&CID_40B1] Plugin = usi_dock GType = FuUsiDockMcuDevice Name = ThinkPad Universal Thunderbolt 4 Smart Dock [USB\VID_17EF&PID_30B4] Plugin = usi_dock GType = FuUsiDockMcuDevice Name = ThinkPad Thunderbolt 4 Dock # fix up a device-provisioning problem for this specific version [USB\VID_17EF&PID_30B4&CID_40B0&DMCVER_10.10] Flags = set-chip-type [USB\VID_17EF&PID_30B5] Plugin = usi_dock GType = FuUsiDockDmcDevice ParentGuid = USB\VID_17EF&PID_30B4 [USB\VID_03F0&PID_0505] Plugin = usi_dock GType = FuUsiDockMcuDevice Name = USB-C G2 Multiport Hub MCU Controller Flags = verfmt-hp fwupd-1.9.16/plugins/vbe/000077500000000000000000000000001460375044200151775ustar00rootroot00000000000000fwupd-1.9.16/plugins/vbe/README.md000066400000000000000000000237401460375044200164640ustar00rootroot00000000000000--- title: Plugin: VBE — Verified Boot for Embedded --- ## Introduction This plugin is used for boards which use the VBE system. This allows the platform to be updated from user space. ## Firmware Format Firmware updates are held within a CAB file, a Windows format which allows files to be collected and compressed, similar to a tar file. The CAB file can be created using the `gcab` tool, as shown in the VBE `example` directory. Inside the CAB file is the firmware itself, in Flat Image Tree (FIT) format. This is typically called `firmware.fit` and can be created by the `mkimage` tool, or using device tree tools such as `dtc` and `fdtput`. The FIT supports multiple configuration, each of which is intended to update a particular board type. This allows the same firmware update to be used for a number of related boards, including sharing certain images, at the expense of increasing the update size. A typical FIT file looks like this: ```fdt / { timestamp = <0x62a74c6c>; description = "Firmware image with one or more FDT blobs"; creator = "U-Boot mkimage 2021.01+dfsg-3ubuntu0~20.04.4"; #address-cells = <0x00000001>; images { firmware-1 { description = "v1.2.4"; type = "firmware"; arch = "arm64"; os = "u-boot"; compression = "none"; store-offset = <0x10000>; data = <...>; hash-1 { value = <0xa738ea1c>; algo = "crc32"; }; }; }; configurations { default = "conf-1"; conf-1 { version = "1.2.4"; compatible = "pine64,rockpro64-v2.1", "pine64,rockpro64"; firmware = "firmware-1"; }; }; }; ``` Each configuration includes the version of the update, the board(s) it is compatible with and a list of firmware 'images' in the `firmware` property. The `compatible` property is optional in the case where there is only one configuration that applies to all boards that receive this update. The images themselves are in a separate `images` node. Each image includes various fields to indicate its type as well as option hashes. The `data` property contains the actual firmware data. Multiple images can be including in each configuration, but each must have a different `store-offset` property, indicating the offset from the start of the firmware region where this particular image is kept. The default store offset is zero, which is typically suitable if there is only one image. It is common to use an 'external' FIT, meaning that the `data` property is omitted and the data is placed in the same file after the FIT itself. In this case `data-offset` and `data-size` allow the data to be relocated. The data is stored started at the next 32-bit aligned file position after the FIT. The `mkimage` tool allows converting a FIT to an external FIT, with the `-E` flag. See the `plugins/vbe/example` directory for an example of building a firmware update. ## Operation The daemon will decompress the cabinet archive and extract the firmware blob, then write it to storage. The firmware will be used on the next reboot, so no changes to firmware happen until there is a reboot. This plugin supports the following protocol ID: * `org.vbe` ## Board information VBE requires the board firmware to provide information about the firmware within the device tree passed to the Operating System. For systems without device tree, currently the only option is to install a file for use by fwupd, typically in `/var/lib/fwupd/vbe/system.dtb`. In either case, there must be one or more nodes with the required information. The format depends on which VBE method is used, but the information must be in a subnode of `chosen/fwupd`. Here is an example: ```fdt / { compatible = "pine64,rockpro64-v2.1"; chosen { fwupd { firmware { compatible = "fwupd,vbe-simple"; cur-version = "1.2.3"; storage = "/tmp/testfw"; area-start = <0x100000>; area-size = <0x100000>; skip-offset = <0x8000>; part-uuid = "62db0ccf-03"; part-id = "3"; }; }; }; }; ``` The first compatible string indicates the board type. Typically it is a single string, but a list is also supported. VBE matches the string(s) here against those in the update itself, using the configuration which has the earliest match (within the list) to the board's compatible string(s). Since more specific boards are at the start of the compatible string, this allows for one configuration to have an update for a specific board, while another provides an update for all other boards. The compatible string of the firmware update indicates the VBE method that is being used. It must have `fwupd` as the manufacturer and the model must start with `vbe-`. Other properties within the node are determined by that method, but some are common to all: * `compatible` - indicates the VBE method to use, in the form `fwupd,`. * `cur-version` - indicates the version that is currently installed, if this is known by the firmware. Note that fwupd keeps its own information about the installed version, so this is not needed by fwupd itself. But it can be useful for other utilities. ## Important files * `/var/lib/fwupd/vbe/system.dtb` - this file holds the system information. It overrides the system device tree if any, meaning that the system device tree is ignored if this file is preset. For systems that don't support device tree (e.g ACPI systems), this file is needed for VBE to work ## Available VBE methods Note that all VBE methods must subclass `FuVbeDevice` since it provides access to the device tree and the VBE directory, among other things. At present only one method is available: * `fwupd,vbe-simple` - writes a single copy of the firmware to media ### vbe-simple With this, only a single copy of the firmware is available so if the write fails, the board may not boot. This is implemented in the `vbe-simple.c` file and has the device GUID `ea1b96eb-a430-4033-8708-498b6d98178b` within fwupd. Properties for this method are: * `compatible` - must be `fwupd,vbe-simple` * `storage` - device to store firmware in. Two options are supported: a full path such as `/dev/mmcblk1` or a device number, like `mmc1`. Note that only mmc is currently supported. * `area-start` - start offset in bytes of the firmware area within the storage * `area-size` - size in bytes of the firmware area within the storage * `skip-offset` - offset to preserve at the start of the firmware area. This means that the first part of the image is ignored, with just the latter part being written. For example, if this is 0x200 then the first 512 bytes of the image (which must be present in the image) are skipped and the bytes after that are written to the store offset. * `part-uuid` - the UUID of the partition containing the fwupd state. This is not used by fwupd at present but may allow the bootloader to check the fwupd state on boot * `part-id` - the partition number containing the fwupd state. This is not used by fwupd at present but may allow the bootloader to check the fwupd state on boot ## Finding out more You can use the U-Boot mailing list or `u-boot` IRC on `libera.chat` to ask questions specific to VBE and firmware. ## Sending patches Use the [fwupd developer site](https://fwupd.org/lvfs/docs/developers) to find information about downloading the code, submitting bugs/feature requests and sending patches. ## Vendor ID Security This does not update USB devices and thus requires no vendor ID set. ## External Interface Access This plugin requires access to system firmware, e.g. via a file or an eMMC device. ## Documentation The following documents may help in understanding VBE: * [VBE](https://docs.google.com/document/d/e/2PACX-1vQjXLPWMIyVktaTMf8edHZYDrEvMYD_iNzIj1FgPmKF37fpglAC47Tt5cvPBC5fvTdoK-GA5Zv1wifo/pub) * [VBE Bootflows](https://docs.google.com/document/d/e/2PACX-1vR0OzhuyRJQ8kdeOibS3xB1rVFy3J4M_QKTM5-3vPIBNcdvR0W8EXu9ymG-yWfqthzWoM4JUNhqwydN/pub) * [VBE Firmware update](https://docs.google.com/document/d/e/2PACX-1vTnlIL17vVbl6TVoTHWYMED0bme7oHHNk-g5VGxblbPiKIdGDALE1HKId8Go5f0g1eziLsv4h9bocbk/pub) * [FIT](https://github.com/u-boot/u-boot/blob/master/doc/uImage.FIT/source_file_format.txt) * [U-Boot Standard boot](https://u-boot.readthedocs.io/en/latest/develop/bootstd.html) ## Useful information For development you may find the following useful. To set up the vbe directory: ```bash mkdir /var/lib/fwupd/vbe chmod a+rwx /var/lib/fwupd/vbe dtc /path/to/fwupd/plugins/vbe/example/test.dts -o \ /var/lib/fwupd/vbe/system.dtb ``` To build the example: ```bash sudo apt install appstream-util u-boot-tools cd /path/to/fwupd/plugins/vbe/example dd if=/dev/zero of=update.bin bs=1M count=1 ./build.sh ``` To install the cab on your development computer: ```bash # Set up the test file dd if=/dev/zero of=/tmp/testfw bs=1M count=3 sudo build/src/fwupdtool install plugins/vbe/example/Vbe-Board-1.2.4.cab \ bb3b05a8-ebef-11ec-be98-d3a15278be95 ``` To dump out the firmware: ```bash sudo rm fw.bin; sudo build/src/fwupdtool firmware-dump fw.bin \ bb3b05a8-ebef-11ec-be98-d3a15278be95 ``` ## To do Add an update method that actually supports verified boot, including maintaining update state. The update happens in two passes, the first installing firmware in the 'B' slot and the second writing it to the 'A' slot, to avoid bricking the device in the event of a write failure or non-functional firmware. Figure out how to select the right update for a board out of many that might be on LVFS. At present the selection mechanism only works within the FIT configuration. This probably needs to use the `` piece of the xml file to specify an identifier for the family of boards supported by the update. ## Version Considerations This plugin has been available since fwupd version `1.8.2`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Simon Glass: @sjg20 fwupd-1.9.16/plugins/vbe/example/000077500000000000000000000000001460375044200166325ustar00rootroot00000000000000fwupd-1.9.16/plugins/vbe/example/.gitignore000066400000000000000000000000461460375044200206220ustar00rootroot00000000000000/.gitignore /firmware.fit /update.bin fwupd-1.9.16/plugins/vbe/example/build.sh000077500000000000000000000007461460375044200202770ustar00rootroot00000000000000#!/bin/sh set -e appstream-util validate-relax com.Vbe.Board.metainfo.xml #dd if=/dev/zero of=update.bin bs=1M count=1 mkimage -D "-p 0x100" -n "v1.2.4" -O U-Boot -A arm64 -C none -T firmware -f auto -d update.bin firmware.fit fdtput firmware.fit -t s /configurations/conf-1 version "1.2.4" # Make the data external, now that we have finished fiddling with the FDT mkimage -E -F firmware.fit gcab --create --zip --nopath Vbe-Board-1.2.4.cab firmware.fit com.Vbe.Board.metainfo.xml fwupd-1.9.16/plugins/vbe/example/com.Vbe.Board.metainfo.xml000066400000000000000000000024041460375044200234740ustar00rootroot00000000000000 com.Vbe.Laptop.firmware VBE BoardFirmware System firmware for a board, for use with VBE

    The board can be updated using Verified Boot for Embedded (VBE).

    bb3b05a8-ebef-11ec-be98-d3a15278be95 http://www.TBD.com/ CC0-1.0 Proprietary Vbe

    This release updates firmware to version 1.2.3 which includes support for a mythical new feature.

    org.freedesktop.fwupd
    fwupd-1.9.16/plugins/vbe/example/my-file000066400000000000000000000000171460375044200201150ustar00rootroot00000000000000this is a test fwupd-1.9.16/plugins/vbe/example/rockpro64.dts000066400000000000000000000006571460375044200212070ustar00rootroot00000000000000/dts-v1/; /* * This file can be used to try our a VBE simple update on ROCKPro64 using * Debian or some other distribution. */ / { compatible = "pine64,rockpro64-v2.1"; chosen { fwupd { firmware { compatible = "fwupd,vbe-simple"; cur-version = "1.2.3"; bootloader-version = "2022.01"; storage = "mmc1"; area-start = <0x0>; area-size = <0x1000000>; skip-offset = <0x8000>; }; }; }; }; fwupd-1.9.16/plugins/vbe/example/test.dts000066400000000000000000000007401460375044200203260ustar00rootroot00000000000000/dts-v1/; /* * This file is suitable for testing on the host device as it uses a local * file. To set it up: * */ / { compatible = "pine64,rockpro64-v2.1"; chosen { fwupd { firmware { compatible = "fwupd,vbe-simple"; cur-version = "1.2.3"; bootloader-version = "2022.01"; storage = "/tmp/testfw"; area-start = <0x100000>; area-size = <0x100000>; skip-offset = <0x8000>; part-uuid = "62db0ccf-03"; part-id = "3"; }; }; }; }; fwupd-1.9.16/plugins/vbe/fu-vbe-device.c000066400000000000000000000123271460375044200177710ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2022 Google LLC * Written by Simon Glass * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vbe-device.h" enum { PROP_0, PROP_VBE_METHOD, PROP_FDT_ROOT, PROP_FDT_NODE, PROP_LAST }; typedef struct { FuFdtImage *fdt_root; FuFdtImage *fdt_node; gchar **compatible; } FuVbeDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuVbeDevice, fu_vbe_device, FU_TYPE_DEVICE) #define GET_PRIVATE(o) (fu_vbe_device_get_instance_private(o)) static void fu_vbe_device_to_string(FuDevice *device, guint idt, GString *str) { FuVbeDevice *self = FU_VBE_DEVICE(device); FuVbeDevicePrivate *priv = GET_PRIVATE(self); if (priv->compatible != NULL) { g_autofree gchar *tmp = g_strjoinv(":", priv->compatible); fu_string_append(str, idt, "Compatible", tmp); } } FuFdtImage * fu_vbe_device_get_fdt_node(FuVbeDevice *self) { FuVbeDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_VBE_DEVICE(self), NULL); return priv->fdt_node; } gchar ** fu_vbe_device_get_compatible(FuVbeDevice *self) { FuVbeDevicePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(FU_IS_VBE_DEVICE(self), NULL); return priv->compatible; } static void fu_vbe_device_init(FuVbeDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_add_protocol(FU_DEVICE(self), "org.vbe"); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE); fu_device_set_physical_id(FU_DEVICE(self), "vbe"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_add_icon(FU_DEVICE(self), "computer"); } static gboolean fu_vbe_device_probe(FuDevice *device, GError **error) { FuVbeDevice *self = FU_VBE_DEVICE(device); FuVbeDevicePrivate *priv = GET_PRIVATE(self); g_autofree gchar *version = NULL; g_autofree gchar *version_bootloader = NULL; g_return_val_if_fail(FU_IS_VBE_DEVICE(device), FALSE); /* get a list of compatible strings */ if (!fu_fdt_image_get_attr_strlist(priv->fdt_root, FU_FIT_FIRMWARE_ATTR_COMPATIBLE, &priv->compatible, error)) return FALSE; /* get baseclass shared attributes */ fu_fdt_image_get_attr_str(priv->fdt_node, "cur-version", &version, NULL); if (version != NULL) fu_device_set_version(device, version); fu_fdt_image_get_attr_str(priv->fdt_node, "bootloader-version", &version_bootloader, NULL); if (version_bootloader != NULL) fu_device_set_version_bootloader(device, version_bootloader); /* success */ return TRUE; } static void fu_vbe_device_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { FuVbeDevice *self = FU_VBE_DEVICE(obj); FuVbeDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_FDT_ROOT: g_value_set_object(value, priv->fdt_root); break; case PROP_FDT_NODE: g_value_set_object(value, priv->fdt_node); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } } static void fu_vbe_device_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { FuVbeDevice *self = FU_VBE_DEVICE(obj); FuVbeDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_FDT_ROOT: g_set_object(&priv->fdt_root, g_value_get_object(value)); break; case PROP_FDT_NODE: g_set_object(&priv->fdt_node, g_value_get_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec); break; } } static void fu_vbe_device_finalize(GObject *obj) { FuVbeDevice *self = FU_VBE_DEVICE(obj); FuVbeDevicePrivate *priv = GET_PRIVATE(self); g_strfreev(priv->compatible); if (priv->fdt_root != NULL) g_object_unref(priv->fdt_root); if (priv->fdt_node != NULL) g_object_unref(priv->fdt_node); G_OBJECT_CLASS(fu_vbe_device_parent_class)->finalize(obj); } static void fu_vbe_device_class_init(FuVbeDeviceClass *klass) { GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->get_property = fu_vbe_device_get_property; object_class->set_property = fu_vbe_device_set_property; pspec = g_param_spec_object("fdt-root", NULL, "FDT root containing method parameters", FU_TYPE_FDT_IMAGE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FDT_ROOT, pspec); pspec = g_param_spec_object("fdt-node", NULL, "FDT image within the device tree containing method parameters'", FU_TYPE_FDT_IMAGE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FDT_NODE, pspec); object_class->finalize = fu_vbe_device_finalize; klass_device->to_string = fu_vbe_device_to_string; klass_device->probe = fu_vbe_device_probe; } fwupd-1.9.16/plugins/vbe/fu-vbe-device.h000066400000000000000000000006621460375044200177750ustar00rootroot00000000000000/* * Copyright (C) 2022 Google LLC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_VBE_DEVICE (fu_vbe_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuVbeDevice, fu_vbe_device, FU, VBE_DEVICE, FuDevice) struct _FuVbeDeviceClass { FuDeviceClass parent_class; }; FuFdtImage * fu_vbe_device_get_fdt_node(FuVbeDevice *self); gchar ** fu_vbe_device_get_compatible(FuVbeDevice *self); fwupd-1.9.16/plugins/vbe/fu-vbe-plugin.c000066400000000000000000000064501460375044200200300ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2022 Google LLC * Written by Simon Glass * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vbe-plugin.h" #include "fu-vbe-simple-device.h" struct _FuVbePlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuVbePlugin, fu_vbe_plugin, FU_TYPE_PLUGIN) static gboolean fu_vbe_plugin_coldplug_img(FuPlugin *plugin, FuFdtImage *fdt_root, FuFdtImage *fdt_node, GError **error) { GType device_gtype = G_TYPE_INVALID; g_autofree gchar *compatible = NULL; g_auto(GStrv) split = NULL; g_autoptr(FuDevice) dev = NULL; /* we expect 'fwupd,vbe-' */ if (!fu_fdt_image_get_attr_str(fdt_node, FU_FIT_FIRMWARE_ATTR_COMPATIBLE, &compatible, error)) { g_prefix_error(error, "missing update mechanism: "); return FALSE; } split = g_strsplit(compatible, ",", 2); if (g_strv_length(split) != 2) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "update mechanism is invalid: %s", compatible); return FALSE; } if (g_strcmp0(split[0], "fwupd") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "update mechanism should have manufacturer of fwupd: %s", split[0]); return FALSE; } /* skip past 'vbe-' */ if (!g_str_has_prefix(split[1], "vbe-")) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "update mechanism is missing vbe prefix: %s", split[1]); return FALSE; } if (g_strcmp0(split[1], "vbe-simple") == 0) { device_gtype = FU_TYPE_VBE_SIMPLE_DEVICE; } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no driver for VBE method '%s'", split[1]); return FALSE; } /* success */ dev = g_object_new(device_gtype, "context", fu_plugin_get_context(plugin), "fdt-root", fdt_root, "fdt-node", fdt_node, NULL); fu_plugin_device_add(plugin, dev); return TRUE; } static gboolean fu_vbe_plugin_coldplug(FuPlugin *plugin, FuProgress *progress, GError **error) { g_autoptr(FuFirmware) fdt = NULL; g_autoptr(FuFdtImage) fdt_root = NULL; g_autoptr(GPtrArray) fdt_imgs = NULL; /* get compatible from root node */ fdt = fu_context_get_fdt(fu_plugin_get_context(plugin), error); if (fdt == NULL) return FALSE; fdt_root = fu_fdt_firmware_get_image_by_path(FU_FDT_FIRMWARE(fdt), "/chosen/fwupd", error); if (fdt_root == NULL) return FALSE; fdt_imgs = fu_firmware_get_images(FU_FIRMWARE(fdt_root)); for (guint i = 0; i < fdt_imgs->len; i++) { FuFdtImage *fdt_node = g_ptr_array_index(fdt_imgs, i); g_autoptr(GError) error_local = NULL; if (!fu_vbe_plugin_coldplug_img(plugin, fdt_root, fdt_node, &error_local)) { g_warning("%s", error_local->message); continue; } } /* nothing found? */ if (fu_plugin_get_devices(plugin)->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no valid VBE update mechanism found"); return FALSE; } /* success */ return TRUE; } static void fu_vbe_plugin_init(FuVbePlugin *self) { } static void fu_vbe_plugin_class_init(FuVbePluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->coldplug = fu_vbe_plugin_coldplug; } fwupd-1.9.16/plugins/vbe/fu-vbe-plugin.h000066400000000000000000000003371460375044200200330ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuVbePlugin, fu_vbe_plugin, FU, VBE_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/vbe/fu-vbe-simple-device.c000066400000000000000000000337531460375044200212660ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2022 Google LLC * Written by Simon Glass * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_ERRNO_H #include #endif #include #include #include "fu-vbe-simple-device.h" /** * @skip_offset: This allows an initial part of the image to be skipped when * writing. This means that the first part of the image is ignored, with just * the latter part being written. For example, if this is 0x200 then the first * 512 bytes of the image (which must be present in the image) are skipped and * the bytes after that are written to the store offset. */ struct _FuVbeSimpleDevice { FuVbeDevice parent_instance; gchar *storage; /* e.g. "mmc1" */ gchar *devname; /* e.g. /dev/mmcblk1 */ guint32 area_start; guint32 area_size; guint32 skip_offset; gint fd; }; G_DEFINE_TYPE(FuVbeSimpleDevice, fu_vbe_simple_device, FU_TYPE_VBE_DEVICE) static void fu_vbe_simple_device_to_string(FuDevice *device, guint idt, GString *str) { FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device); /* FuVbeDevice->to_string */ FU_DEVICE_CLASS(fu_vbe_simple_device_parent_class)->to_string(device, idt, str); if (self->storage != NULL) fu_string_append(str, idt, "Storage", self->storage); if (self->devname != NULL) fu_string_append(str, idt, "Devname", self->devname); fu_string_append_kx(str, idt, "AreaStart", self->area_start); fu_string_append_kx(str, idt, "AreaSize", self->area_size); if (self->skip_offset != 0) fu_string_append_kx(str, idt, "SkipOffset", self->skip_offset); } static gboolean fu_vbe_simple_device_parse_devnum(const gchar *str, guint *value, GError **error) { guint64 val64 = 0; /* skip non-numeric part */ while (*str != '\0' && g_ascii_isdigit(*str)) str++; /* convert to uint */ if (!fu_strtoull(str, &val64, 0x0, G_MAXUINT, error)) return FALSE; if (value != NULL) *value = val64; return TRUE; } static gboolean fu_vbe_simple_device_probe(FuDevice *device, GError **error) { FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device); FuFdtImage *fdt_node; g_return_val_if_fail(FU_IS_VBE_DEVICE(self), FALSE); /* FuVbeDevice->probe */ if (!FU_DEVICE_CLASS(fu_vbe_simple_device_parent_class)->probe(device, error)) return FALSE; fdt_node = fu_vbe_device_get_fdt_node(FU_VBE_DEVICE(self)); if (!fu_fdt_image_get_attr_str(fdt_node, "storage", &self->storage, error)) return FALSE; /* if this is an absolute path, use it */ if (g_str_has_prefix(self->storage, "/")) { self->devname = g_strdup(self->storage); } else { guint devnum = 0; /* obtain the 1 from "mmc1" */ if (!fu_vbe_simple_device_parse_devnum(self->storage, &devnum, error)) { g_prefix_error(error, "cannot parse storage property %s: ", self->storage); return FALSE; } if (g_str_has_prefix(self->storage, "mmc")) { self->devname = g_strdup_printf("/dev/mmcblk%u", devnum); } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "unsupported 'storage' media '%s'", self->storage); return FALSE; } } /* get area */ if (!fu_fdt_image_get_attr_u32(fdt_node, "area-start", &self->area_start, error)) return FALSE; if (!fu_fdt_image_get_attr_u32(fdt_node, "area-size", &self->area_size, error)) return FALSE; /* an optional skip offset to skip everything, which could be useful for testing */ fu_fdt_image_get_attr_u32(fdt_node, "skip-offset", &self->skip_offset, NULL); if (self->skip_offset > self->area_size) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "store offset 0x%x is larger than size 0x%x", (guint)self->skip_offset, (guint)self->area_size); return FALSE; } /* success */ return TRUE; } static gboolean fu_vbe_simple_device_open(FuDevice *device, GError **error) { FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device); /* open device */ self->fd = open(self->devname, O_RDWR); if (self->fd == -1) { #ifdef HAVE_ERRNO_H g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot open %s [%s]", self->devname, g_strerror(errno)); #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot open %s", self->devname); return FALSE; #endif } /* success */ return TRUE; } static gboolean fu_vbe_simple_device_close(FuDevice *device, GError **error) { FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device); /* close device */ close(self->fd); self->fd = -1; /* success */ return TRUE; } static FuFdtImage * fu_vbe_simple_device_get_cfg_compatible(FuVbeSimpleDevice *self, FuFirmware *firmware, GError **error) { gchar **device_compatible; g_autoptr(FuFdtImage) fdt_configurations = NULL; g_autoptr(GPtrArray) img_configurations = NULL; g_autofree gchar *str = NULL; /* get all configurations */ fdt_configurations = fu_fdt_firmware_get_image_by_path(FU_FDT_FIRMWARE(firmware), "/" FU_FIT_FIRMWARE_ID_CONFIGURATIONS, error); if (fdt_configurations == NULL) return NULL; img_configurations = fu_firmware_get_images(firmware); /* look for a configuration with the device compatible strings in priority order */ device_compatible = fu_vbe_device_get_compatible(FU_VBE_DEVICE(self)); for (guint j = 0; device_compatible[j] != NULL; j++) { for (guint i = 0; i < img_configurations->len; i++) { FuFdtImage *img = g_ptr_array_index(img_configurations, i); g_auto(GStrv) img_compatible = NULL; if (!fu_fdt_image_get_attr_strlist(img, FU_FIT_FIRMWARE_ATTR_COMPATIBLE, &img_compatible, error)) return NULL; if (g_strv_contains((const gchar *const *)img_compatible, device_compatible[j])) return g_object_ref(img); } } /* failure */ str = g_strjoinv(", ", device_compatible); g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no images found that match %s", str); return NULL; } static FuFirmware * fu_vbe_simple_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device); g_autofree gchar *version = NULL; g_auto(GStrv) firmware_ids = NULL; g_autoptr(FuFdtImage) img_cfg = NULL; g_autoptr(FuFirmware) firmware = fu_fit_firmware_new(); g_autoptr(FuFirmware) firmware_container = fu_firmware_new(); /* parse all images */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* look for a compatible configuration */ img_cfg = fu_vbe_simple_device_get_cfg_compatible(self, firmware, error); if (img_cfg == NULL) return NULL; if (!fu_fdt_image_get_attr_str(img_cfg, FU_FIT_FIRMWARE_ATTR_VERSION, &version, error)) return NULL; /* check the firmware images exists */ if (!fu_fdt_image_get_attr_strlist(img_cfg, "firmware", &firmware_ids, error)) return NULL; for (guint i = 0; firmware_ids[i] != NULL; i++) { g_autofree gchar *path = NULL; g_autoptr(FuFdtImage) img_firmware = NULL; path = g_strdup_printf("/%s/%s", FU_FIT_FIRMWARE_ID_IMAGES, firmware_ids[i]); img_firmware = fu_fdt_firmware_get_image_by_path(FU_FDT_FIRMWARE(firmware), path, error); if (img_firmware == NULL) return NULL; fu_firmware_add_image(firmware_container, FU_FIRMWARE(img_firmware)); } /* success: return the container */ return g_steal_pointer(&firmware_container); } static gboolean fu_vbe_simple_device_write_firmware_img(FuVbeSimpleDevice *self, FuFdtImage *img, FuProgress *progress, GError **error) { const guint8 *buf; gssize rc; gsize bufsz = 0; gsize seek_to; guint32 store_offset = 0; g_autoptr(GBytes) blob = NULL; /* get data */ blob = fu_fdt_image_get_attr(img, FU_FIT_FIRMWARE_ATTR_DATA, error); if (blob == NULL) return FALSE; buf = g_bytes_get_data(blob, &bufsz); fu_fdt_image_get_attr_u32(img, "store-offset", &store_offset, NULL); /* sanity check */ if (store_offset + bufsz > self->area_size) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "image '%s' store_offset=0x%x, bufsz=0x%x, area_size=0x%x", fu_firmware_get_id(FU_FIRMWARE(img)), (guint)store_offset, (guint)bufsz, (guint)self->area_size); return FALSE; } if (self->skip_offset >= bufsz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "image '%s' skip_offset=0x%x, bufsz=0x%x, area_size=0x%x", fu_firmware_get_id(FU_FIRMWARE(img)), (guint)store_offset, (guint)bufsz, (guint)self->area_size); return FALSE; } /* seek to correct address */ seek_to = self->area_start + store_offset + self->skip_offset; g_debug("writing image '%s' bufsz 0x%x (skipping 0x%x) to store_offset 0x%x, seek 0x%x\n", fu_firmware_get_id(FU_FIRMWARE(img)), (guint)bufsz, (guint)self->skip_offset, (guint)store_offset, (guint)seek_to); rc = lseek(self->fd, seek_to, SEEK_SET); if (rc < 0) { #ifdef HAVE_ERRNO_H g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "cannot seek file '%s' to 0x%x [%s]", self->devname, (guint)seek_to, g_strerror(errno)); #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "cannot seek file '%s' to 0x%x", self->devname, (guint)seek_to); #endif return FALSE; } /* write buffer */ rc = write(self->fd, buf + self->skip_offset, bufsz - self->skip_offset); if (rc < 0) { #ifdef HAVE_ERRNO_H g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "cannot write file '%s' [%s]", self->devname, g_strerror(errno)); #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "cannot write file '%s'", self->devname); #endif return FALSE; } /* success */ return TRUE; } static gboolean fu_vbe_simple_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device); g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware); g_return_val_if_fail(FU_IS_VBE_DEVICE(self), FALSE); /* write each firmware image */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, imgs->len); for (guint i = 0; i < imgs->len; i++) { FuFdtImage *img = g_ptr_array_index(imgs, i); if (!fu_vbe_simple_device_write_firmware_img(self, img, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } /* success */ return TRUE; } static GBytes * fu_vbe_simple_device_upload(FuDevice *device, FuProgress *progress, GError **error) { FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(device); gssize rc; g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GPtrArray) chunks = NULL; /* notify UI */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); /* seek to start */ rc = lseek(self->fd, self->area_start, SEEK_SET); if (rc < 0) { #ifdef HAVE_ERRNO_H g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cannot seek file %s to 0x%x [%s]", self->devname, (guint)self->area_start, g_strerror(errno)); #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cannot seek file %s to 0x%x", self->devname, (guint)self->area_start); #endif return NULL; } /* process in chunks */ chunks = fu_chunk_array_new(NULL, self->area_size - self->area_start, 0x0, 0x0, 0x100000); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); g_autofree guint8 *tmpbuf = g_malloc0(fu_chunk_get_data_sz(chk)); rc = read(self->fd, tmpbuf, fu_chunk_get_data_sz(chk)); if (rc != (gssize)fu_chunk_get_data_sz(chk)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "incomplete read of %s @0x%x", self->devname, fu_chunk_get_address(chk)); return NULL; } g_byte_array_append(buf, tmpbuf, fu_chunk_get_data_sz(chk)); fu_progress_step_done(progress); } /* success */ return g_bytes_new(buf->data, buf->len); } static void fu_vbe_simple_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_vbe_simple_device_init(FuVbeSimpleDevice *self) { fu_device_set_name(FU_DEVICE(self), "simple"); fu_device_set_vendor(FU_DEVICE(self), "U-Boot"); fu_device_add_vendor_id(FU_DEVICE(self), "VBE:U-Boot"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version_lowest(FU_DEVICE(self), "0.0.1"); } static void fu_vbe_simple_device_constructed(GObject *obj) { FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(obj); fu_device_add_guid(FU_DEVICE(self), "bb3b05a8-ebef-11ec-be98-d3a15278be95"); } static void fu_vbe_simple_device_finalize(GObject *obj) { FuVbeSimpleDevice *self = FU_VBE_SIMPLE_DEVICE(obj); g_free(self->devname); g_free(self->storage); G_OBJECT_CLASS(fu_vbe_simple_device_parent_class)->finalize(obj); } static void fu_vbe_simple_device_class_init(FuVbeSimpleDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->constructed = fu_vbe_simple_device_constructed; object_class->finalize = fu_vbe_simple_device_finalize; klass_device->to_string = fu_vbe_simple_device_to_string; klass_device->probe = fu_vbe_simple_device_probe; klass_device->open = fu_vbe_simple_device_open; klass_device->close = fu_vbe_simple_device_close; klass_device->set_progress = fu_vbe_simple_device_set_progress; klass_device->prepare_firmware = fu_vbe_simple_device_prepare_firmware; klass_device->write_firmware = fu_vbe_simple_device_write_firmware; klass_device->dump_firmware = fu_vbe_simple_device_upload; } fwupd-1.9.16/plugins/vbe/fu-vbe-simple-device.h000066400000000000000000000004421460375044200212600ustar00rootroot00000000000000/* * Copyright (C) 2022 Google LLC * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-vbe-device.h" #define FU_TYPE_VBE_SIMPLE_DEVICE (fu_vbe_simple_device_get_type()) G_DECLARE_FINAL_TYPE(FuVbeSimpleDevice, fu_vbe_simple_device, FU, VBE_SIMPLE_DEVICE, FuVbeDevice) fwupd-1.9.16/plugins/vbe/meson.build000066400000000000000000000007731460375044200173500ustar00rootroot00000000000000plugins += {meson.current_source_dir().split('/')[-1]: true} cargs = ['-DG_LOG_DOMAIN="FuPluginVbe"'] plugin_builtins += static_library('fu_plugin_vbe', sources : [ 'fu-vbe-plugin.c', 'fu-vbe-device.c', 'fu-vbe-simple-device.c', ], include_directories : [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], link_with : [ fwupd, fwupdplugin, ], c_args : [ cargs, '-DLOCALSTATEDIR="' + localstatedir + '"', ], dependencies : [ plugin_deps, ], ) fwupd-1.9.16/plugins/vendor-example/000077500000000000000000000000001460375044200173515ustar00rootroot00000000000000fwupd-1.9.16/plugins/vendor-example/.gitignore000066400000000000000000000004071460375044200213420ustar00rootroot00000000000000README.md fu-vendor-example-common.c fu-vendor-example-common.h fu-vendor-example-device.c fu-vendor-example-device.h fu-vendor-example-firmware.c fu-vendor-example-firmware.h fu-vendor-example-plugin.c fu-vendor-example-plugin.h meson.build vendor-example.quirk fwupd-1.9.16/plugins/vendor-example/README.md.in000066400000000000000000000027371460375044200212460ustar00rootroot00000000000000--- title: Plugin: {{Vendor}} {{Example}} --- ## Introduction The {{Example}} is a bla bla bla. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a packed binary file format. This plugin supports the following protocol ID: * `com.{{vendor}}.{{example}}` ## GUID Generation {% if Parent == 'Usb' -%} These devices use the standard USB DeviceInstanceId values, e.g. * `USB\ID_XXX` * `USB\VID_273F&PID_1001` {%- else -%} These devices use the standard TODO DeviceInstanceId values, e.g. * `TODO\VID_XXX` {%- endif %} ## Update Behavior The device is updated by bla bla bla. ## Vendor ID Security {% if Parent in ['Usb', 'Hid'] -%} The vendor ID is set from the USB vendor, in this instance set to `USB:0x273F` {%- else -%} The vendor ID is set from the TODO vendor, in this instance set to `TODO:0x273F` {%- endif %} ## Quirk Use This plugin uses the following plugin-specific quirks: ### {{Vendor}}{{Example}}StartAddr The bla bla bla. Since: 1.8.TODO ## External Interface Access {% if Parent in ['Usb', 'Hid'] -%} This plugin requires read/write access to `/dev/bus/usb`. {%- else -%} This plugin requires read/write access to `TODO`. {%- endif %} ## Version Considerations This plugin has been available since fwupd version `SET_VERSION_HERE`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * {{Vendor}}: @github-username fwupd-1.9.16/plugins/vendor-example/fu-vendor-example-common.c.in000066400000000000000000000004321460375044200247450ustar00rootroot00000000000000/* * Copyright (C) {{Year}} {{Author}} <{{Email}}> * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-{{vendor}}-{{example}}-common.h" const gchar * fu_{{vendor}}_{{example}}_strerror(guint8 code) { if (code == 0) return "success"; return NULL; } fwupd-1.9.16/plugins/vendor-example/fu-vendor-example-common.h.in000066400000000000000000000003111460375044200247460ustar00rootroot00000000000000/* * Copyright (C) {{Year}} {{Author}} <{{Email}}> * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include const gchar * fu_{{vendor}}_{{example}}_strerror(guint8 code); fwupd-1.9.16/plugins/vendor-example/fu-vendor-example-device.c.in000066400000000000000000000264631460375044200247300ustar00rootroot00000000000000/* * Copyright (C) {{Year}} {{Author}} <{{Email}}> * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-{{vendor}}-{{example}}-common.h" #include "fu-{{vendor}}-{{example}}-device.h" #include "fu-{{vendor}}-{{example}}-firmware.h" #include "fu-{{vendor}}-{{example}}-struct.h" /* this can be set using Flags=example in the quirk file */ #define FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE_FLAG_EXAMPLE (1 << 0) struct _Fu{{Vendor}}{{Example}}Device { Fu{{Parent}}Device parent_instance; guint16 start_addr; }; G_DEFINE_TYPE(Fu{{Vendor}}{{Example}}Device, fu_{{vendor}}_{{example}}_device, FU_TYPE_{{PARENT}}_DEVICE) static void fu_{{vendor}}_{{example}}_device_to_string(FuDevice *device, guint idt, GString *str) { Fu{{Vendor}}{{Example}}Device *self = FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE(device); FU_DEVICE_CLASS(fu_{{vendor}}_{{example}}_device_parent_class)->to_string(device, idt, str); fu_string_append_kx(str, idt, "StartAddr", self->start_addr); } /* TODO: this is only required if the device instance state is required elsewhere */ guint16 fu_{{vendor}}_{{example}}_device_get_start_addr(Fu{{Vendor}}{{Example}}Device *self) { g_return_val_if_fail(FU_IS_{{VENDOR}}_{{EXAMPLE}}_DEVICE(self), G_MAXUINT16); return self->start_addr; } static gboolean fu_{{vendor}}_{{example}}_device_detach(FuDevice *device, FuProgress *progress, GError **error) { Fu{{Vendor}}{{Example}}Device *self = FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE(device); /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in bootloader mode, skipping"); return TRUE; } /* TODO: switch the device into bootloader mode */ g_assert(self != NULL); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_{{vendor}}_{{example}}_device_attach(FuDevice *device, FuProgress *progress, GError **error) { Fu{{Vendor}}{{Example}}Device *self = FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE(device); /* sanity check */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } /* TODO: switch the device into runtime mode */ g_assert(self != NULL); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_{{vendor}}_{{example}}_device_reload(FuDevice *device, GError **error) { Fu{{Vendor}}{{Example}}Device *self = FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE(device); /* TODO: reprobe the hardware, or delete this vfunc to use ->setup() as a fallback */ g_assert(self != NULL); return TRUE; } static gboolean fu_{{vendor}}_{{example}}_device_probe(FuDevice *device, GError **error) { Fu{{Vendor}}{{Example}}Device *self = FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE(device); /* Fu{{Parent}}Device->probe */ if (!FU_DEVICE_CLASS(fu_{{vendor}}_{{example}}_device_parent_class)->probe(device, error)) return FALSE; /* TODO: probe the device for properties available before it is opened */ if (fu_device_has_private_flag(device, FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE_FLAG_EXAMPLE)) self->start_addr = 0x100; {%- if Parent == 'Udev' -%} /* get from sysfs */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "pci", error); {%- else %} /* success */ return TRUE; {%- endif %} } static gboolean fu_{{vendor}}_{{example}}_device_setup(FuDevice *device, GError **error) { Fu{{Vendor}}{{Example}}Device *self = FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE(device); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); /* {{Parent}}Device->setup */ if (!FU_DEVICE_CLASS(fu_{{vendor}}_{{example}}_device_parent_class)->setup(device, error)) return FALSE; /* TODO: get the version and other properties from the hardware while open */ g_assert(self != NULL); g_assert(usb_device != NULL); fu_device_set_version(device, "1.2.3"); /* success */ return TRUE; } static gboolean fu_{{vendor}}_{{example}}_device_prepare(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { Fu{{Vendor}}{{Example}}Device *self = FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE(device); /* TODO: anything the device has to do before the update starts */ g_assert(self != NULL); return TRUE; } static gboolean fu_{{vendor}}_{{example}}_device_cleanup(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { Fu{{Vendor}}{{Example}}Device *self = FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE(device); /* TODO: anything the device has to do when the update has completed */ g_assert(self != NULL); return TRUE; } static FuFirmware * fu_{{vendor}}_{{example}}_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { Fu{{Vendor}}{{Example}}Device *self = FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE(device); g_autoptr(FuFirmware) firmware = fu_{{vendor}}_{{example}}_firmware_new(); /* TODO: you do not need to use this vfunc if not checking attributes */ if (self->start_addr != fu_{{vendor}}_{{example}}_firmware_get_start_addr(FU_{{VENDOR}}_{{EXAMPLE}}_FIRMWARE(firmware))) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "start address mismatch, got 0x%04x, expected 0x%04x", fu_{{vendor}}_{{example}}_firmware_get_start_addr(FU_{{VENDOR}}_{{EXAMPLE}}_FIRMWARE(firmware)), self->start_addr); return NULL; } if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; return g_steal_pointer(&firmware); } static gboolean fu_{{vendor}}_{{example}}_device_write_blocks(Fu{{Vendor}}{{Example}}Device *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); /* TODO: send to hardware */ g_assert(chk != NULL); {%- if Parent == 'Hid' %} guint8 buf[64] = { 0x12, 0x24, 0x0 }; /* TODO: this is the preamble */ /* TODO: copy in payload */ if (!fu_memcpy_safe(buf, sizeof(buf), 0x2, /* TODO: copy to dst at offset */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), 0x01, buf, sizeof(buf), 5000, /* ms */ FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to send packet: "); return FALSE; } if (!fu_hid_device_get_report(FU_HID_DEVICE(self), 0x01, buf, sizeof(buf), 5000, /* ms */ FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "failed to receive packet: "); return FALSE; } {%- endif %} /* update progress */ fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_{{vendor}}_{{example}}_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { Fu{{Vendor}}{{Example}}Device *self = FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 44, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 35, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* write each block */ chunks = fu_chunk_array_new_from_bytes(fw, self->start_addr, 64 /* block_size */); if (!fu_{{vendor}}_{{example}}_device_write_blocks(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* TODO: verify each block */ fu_progress_step_done(progress); /* success! */ return TRUE; } static gboolean fu_{{vendor}}_{{example}}_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { Fu{{Vendor}}{{Example}}Device *self = FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE(device); /* TODO: parse value from quirk file */ if (g_strcmp0(key, "{{Vendor}}{{Example}}StartAddr") == 0) { guint64 tmp = 0; if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error)) return FALSE; self->start_addr = tmp; return TRUE; } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_{{vendor}}_{{example}}_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 57, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 43, "reload"); } static void fu_{{vendor}}_{{example}}_device_init(Fu{{Vendor}}{{Example}}Device *self) { self->start_addr = 0x5000; fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_add_protocol(FU_DEVICE(self), "com.{{vendor}}.{{example}}"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG); fu_device_register_private_flag(FU_DEVICE(self), FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE_FLAG_EXAMPLE, "example"); {%- if Parent == 'Usb' %} fu_usb_device_add_interface(FU_USB_DEVICE(self), 0x01); {%- endif %} {%- if Parent == 'Hid' %} fu_hid_device_add_flag(FU_HID_DEVICE(self), FU_HID_DEVICE_FLAG_RETRY_FAILURE); {%- endif %} } static void fu_{{vendor}}_{{example}}_device_finalize(GObject *object) { Fu{{Vendor}}{{Example}}Device *self = FU_{{VENDOR}}_{{EXAMPLE}}_DEVICE(object); /* TODO: free any allocated instance state here */ g_assert(self != NULL); G_OBJECT_CLASS(fu_{{vendor}}_{{example}}_device_parent_class)->finalize(object); } static void fu_{{vendor}}_{{example}}_device_class_init(Fu{{Vendor}}{{Example}}DeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_{{vendor}}_{{example}}_device_finalize; klass_device->to_string = fu_{{vendor}}_{{example}}_device_to_string; klass_device->probe = fu_{{vendor}}_{{example}}_device_probe; klass_device->setup = fu_{{vendor}}_{{example}}_device_setup; klass_device->reload = fu_{{vendor}}_{{example}}_device_reload; klass_device->prepare = fu_{{vendor}}_{{example}}_device_prepare; klass_device->cleanup = fu_{{vendor}}_{{example}}_device_cleanup; klass_device->attach = fu_{{vendor}}_{{example}}_device_attach; klass_device->detach = fu_{{vendor}}_{{example}}_device_detach; klass_device->prepare_firmware = fu_{{vendor}}_{{example}}_device_prepare_firmware; klass_device->write_firmware = fu_{{vendor}}_{{example}}_device_write_firmware; klass_device->set_quirk_kv = fu_{{vendor}}_{{example}}_device_set_quirk_kv; klass_device->set_progress = fu_{{vendor}}_{{example}}_device_set_progress; } fwupd-1.9.16/plugins/vendor-example/fu-vendor-example-device.h.in000066400000000000000000000007561460375044200247320ustar00rootroot00000000000000/* * Copyright (C) {{Year}} {{Author}} <{{Email}}> * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_{{VENDOR}}_{{EXAMPLE}}_DEVICE (fu_{{vendor}}_{{example}}_device_get_type()) G_DECLARE_FINAL_TYPE(Fu{{Vendor}}{{Example}}Device, fu_{{vendor}}_{{example}}_device, FU, {{VENDOR}}_{{EXAMPLE}}_DEVICE, Fu{{Parent}}Device) guint16 fu_{{vendor}}_{{example}}_device_get_start_addr(Fu{{Vendor}}{{Example}}Device *self); fwupd-1.9.16/plugins/vendor-example/fu-vendor-example-firmware.c.in000066400000000000000000000076321460375044200253020ustar00rootroot00000000000000/* * Copyright (C) {{Year}} {{Author}} <{{Email}}> * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-{{vendor}}-{{example}}-firmware.h" #include "fu-{{vendor}}-{{example}}-struct.h" struct _Fu{{Vendor}}{{Example}}Firmware { FuFirmware parent_instance; guint16 start_addr; }; G_DEFINE_TYPE(Fu{{Vendor}}{{Example}}Firmware, fu_{{vendor}}_{{example}}_firmware, FU_TYPE_FIRMWARE) static void fu_{{vendor}}_{{example}}_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { Fu{{Vendor}}{{Example}}Firmware *self = FU_{{VENDOR}}_{{EXAMPLE}}_FIRMWARE(firmware); fu_xmlb_builder_insert_kx(bn, "start_addr", self->start_addr); } static gboolean fu_{{vendor}}_{{example}}_firmware_build(FuFirmware *firmware, XbNode *n, GError **error) { Fu{{Vendor}}{{Example}}Firmware *self = FU_{{VENDOR}}_{{EXAMPLE}}_FIRMWARE(firmware); guint64 tmp; /* TODO: load from .builder.xml */ tmp = xb_node_query_text_as_uint(n, "start_addr", NULL); if (tmp != G_MAXUINT64 && tmp <= G_MAXUINT16) self->start_addr = tmp; /* success */ return TRUE; } static gboolean fu_{{vendor}}_{{example}}_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { const guint8 magic[4] = "TODO"; //return fu_struct_{{vendor}}_{{example}}_header_validate_bytes(fw, // offset, // error); return fu_memcmp_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset, magic, sizeof(magic), 0x0, sizeof(magic), error); } static gboolean fu_{{vendor}}_{{example}}_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { Fu{{Vendor}}{{Example}}Firmware *self = FU_{{VENDOR}}_{{EXAMPLE}}_FIRMWARE(firmware); gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GByteArray) st_hdr = NULL; /* TODO: parse firmware into images */ st_hdr = fu_struct_{{vendor}}_{{example}}_hdr_parse(buf, bufsz, offset, error); if (st_hdr == NULL) return FALSE; self->start_addr = 0x1234; fu_firmware_set_version(firmware, "1.2.3"); fu_firmware_set_bytes(firmware, fw); return TRUE; } static GByteArray * fu_{{vendor}}_{{example}}_firmware_write(FuFirmware *firmware, GError **error) { Fu{{Vendor}}{{Example}}Firmware *self = FU_{{VENDOR}}_{{EXAMPLE}}_FIRMWARE(firmware); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) fw = NULL; /* data first */ fw = fu_firmware_get_bytes_with_patches(firmware, error); if (fw == NULL) return NULL; /* TODO: write to the buffer with the correct format */ g_assert(self != NULL); fu_byte_array_append_bytes(buf, fw); /* success */ return g_steal_pointer(&buf); } guint16 fu_{{vendor}}_{{example}}_firmware_get_start_addr(Fu{{Vendor}}{{Example}}Firmware *self) { g_return_val_if_fail(FU_IS_{{VENDOR}}_{{EXAMPLE}}_FIRMWARE(self), G_MAXUINT16); return self->start_addr; } static void fu_{{vendor}}_{{example}}_firmware_init(Fu{{Vendor}}{{Example}}Firmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_VID_PID); } static void fu_{{vendor}}_{{example}}_firmware_class_init(Fu{{Vendor}}{{Example}}FirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_{{vendor}}_{{example}}_check_magic; klass_firmware->parse = fu_{{vendor}}_{{example}}_firmware_parse; klass_firmware->write = fu_{{vendor}}_{{example}}_firmware_write; klass_firmware->build = fu_{{vendor}}_{{example}}_firmware_build; klass_firmware->export = fu_{{vendor}}_{{example}}_firmware_export; } FuFirmware * fu_{{vendor}}_{{example}}_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_{{VENDOR}}_{{EXAMPLE}}_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/vendor-example/fu-vendor-example-firmware.h.in000066400000000000000000000010571460375044200253020ustar00rootroot00000000000000/* * Copyright (C) {{Year}} {{Author}} <{{Email}}> * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_{{VENDOR}}_{{EXAMPLE}}_FIRMWARE (fu_{{vendor}}_{{example}}_firmware_get_type()) G_DECLARE_FINAL_TYPE(Fu{{Vendor}}{{Example}}Firmware, fu_{{vendor}}_{{example}}_firmware, FU, {{VENDOR}}_{{EXAMPLE}}_FIRMWARE, FuFirmware) FuFirmware * fu_{{vendor}}_{{example}}_firmware_new(void); guint16 fu_{{vendor}}_{{example}}_firmware_get_start_addr(Fu{{Vendor}}{{Example}}Firmware *self); fwupd-1.9.16/plugins/vendor-example/fu-vendor-example-plugin.c.in000066400000000000000000000021651460375044200247600ustar00rootroot00000000000000/* * Copyright (C) {{Year}} {{Author}} <{{Email}}> * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-{{vendor}}-{{example}}-device.h" #include "fu-{{vendor}}-{{example}}-firmware.h" #include "fu-{{vendor}}-{{example}}-plugin.h" struct _Fu{{Vendor}}{{Example}}Plugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(Fu{{Vendor}}{{Example}}Plugin, fu_{{vendor}}_{{example}}_plugin, FU_TYPE_PLUGIN) static void fu_{{vendor}}_{{example}}_plugin_init(Fu{{Vendor}}{{Example}}Plugin *self) { } static void fu_{{vendor}}_{{example}}_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "{{Vendor}}{{Example}}StartAddr"); fu_plugin_add_device_gtype(plugin, FU_TYPE_{{VENDOR}}_{{EXAMPLE}}_DEVICE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_{{VENDOR}}_{{EXAMPLE}}_FIRMWARE); } static void fu_{{vendor}}_{{example}}_plugin_class_init(Fu{{Vendor}}{{Example}}PluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_{{vendor}}_{{example}}_plugin_constructed; } fwupd-1.9.16/plugins/vendor-example/fu-vendor-example-plugin.h.in000066400000000000000000000004511460375044200247610ustar00rootroot00000000000000/* * Copyright (C) {{Year}} {{Author}} <{{Email}}> * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(Fu{{Vendor}}{{Example}}Plugin, fu_{{vendor}}_{{example}}_plugin, FU, {{VENDOR}}_{{EXAMPLE}}_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/vendor-example/fu-vendor-example.rs000066400000000000000000000004401460375044200232530ustar00rootroot00000000000000// Copyright (C) {{Year}} {{Author}} <{{Email}}> // SPDX-License-Identifier: LGPL-2.1+ #[derive(New, ValidateBytes, Parse)] struct {{Vendor}}{{Example}} { signature: u8 == 0xDE, address: u16le, } #[derive(ToString)] enum {{Vendor}}{{Example}}Status { Unknown, Failed, } fwupd-1.9.16/plugins/vendor-example/meson.build.in000066400000000000000000000014041460375044200221170ustar00rootroot00000000000000{%- if Parent in ['Usb', 'Hid'] -%} if gusb.found() {%- elif Parent == 'Udev' -%} if gudev.found() {%- endif %} cargs = ['-DG_LOG_DOMAIN="FuPlugin{{Vendor}}{{Example}}"'] plugins += {meson.current_source_dir().split('/')[-1]: true} plugin_quirks += files('{{vendor}}-{{example}}.quirk') plugin_builtins += static_library('fu_plugin_{{vendor}}_{{example}}', rustgen.process('fu-{{vendor}}-{{example}}.rs'), sources: [ 'fu-{{vendor}}-{{example}}-common.c', 'fu-{{vendor}}-{{example}}-device.c', 'fu-{{vendor}}-{{example}}-firmware.c', 'fu-{{vendor}}-{{example}}-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) {%- if Parent in ['Usb', 'Hid', 'Udev'] %} endif {%- endif %} fwupd-1.9.16/plugins/vendor-example/vendor-example.quirk.in000066400000000000000000000002261460375044200237610ustar00rootroot00000000000000# {{Example}} {%- if Parent in ['Usb', 'Hid'] %} [USB\VID_TODO&PID_TODO] {%- else %} [{{PARENT}}\ID_XXX] {%- endif %} Plugin = {{vendor}}_{{example}} fwupd-1.9.16/plugins/vli/000077500000000000000000000000001460375044200152155ustar00rootroot00000000000000fwupd-1.9.16/plugins/vli/README.md000066400000000000000000000060541460375044200165010ustar00rootroot00000000000000--- title: Plugin: VIA --- ## Introduction This plugin is used to update USB hubs from VIA. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in an undisclosed binary file format. This plugin supports the following protocol ID: * `com.vli.i2c` * `com.vli.pd` * `com.vli.usbhub` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_17EF&PID_3083&REV_0001` * `USB\VID_17EF&PID_3083` All VLI devices also use custom GUID values for the device type, e.g. * `USB\VID_17EF&PID_3083&DEV_VL812B3` These devices also use custom GUID values for the SPI flash configuration, e.g. * `CFI\FLASHID_37303840` * `CFI\FLASHID_3730` * `CFI\FLASHID_37` Optional PD child devices sharing the SPI flash use two extra GUIDs, e.g. * `USB\VID_17EF&PID_3083&DEV_VL102` * `USB\VID_17EF&PID_3083&APP_26` Optional I²C child devices use just one extra GUID, e.g. * `USB\VID_17EF&PID_3083&I2C_MSP430` * `USB\VID_17EF&PID_3083&I2C_PS186` ## Vendor ID Security The vendor ID is set from the USB vendor, for example set to `USB:0x2109` ## Quirk Use This plugin uses the following plugin-specific quirks: ### VliDeviceKind Device kind, e.g. `VL102`. Since: 1.3.7 ### VliSpiAutoDetect SPI autodetect (default 0x1). Since: 1.3.7 ### CfiDeviceCmdReadId Flash command to read the ID. Since: 1.3.3 ### CfiDeviceCmdReadIdSz Size of the ReadId response. The `CfiDeviceCmdReadId` and `CfiDeviceCmdReadIdSz` quirks have to be assigned to the device instance attribute, rather then the flash part as the ID is required to query the other flash chip parameters. For example: [USB\VID_2109&PID_0210] Plugin = vli GType = FuVliUsbhubDevice CfiDeviceCmdReadId = 0xf8 CfiDeviceCmdReadIdSz = 4 # W3IRDFLASHxxx [CFI\\FLASHID_37303840] CfiDeviceCmdChipErase = 0xc7 CfiDeviceCmdSectorErase = 0x20 ### Flags:attach-with-gpiob This flag is used if device needs GPIO-B to reset the device. ### Flags:unlock-legacy813 This flag is used for unlocking VL813 with a custom VDR request. ### Flags:has-shared-spi-pd This flag is used for devices that share SPI with the PD device. ### Flags:has-msp430 This flag is used if device has a MSP430 attached via I²C. ### Flags:has-rtd21xx This flag is used if device has a RTD21XX attached via I²C. ### Flags:has-i2c-ps186 This flag is used if device has a PS186 attached via I²C. ### Flags:skips-rom This flag handles cases to update in firmware mode, skips ROM mode entirely. ### Flags:attach-with-usb This flag is used if device needs unplug & re-plug usb cable to reset the device. ### Flags:attach-with-power This flag is used if device needs unplug & re-plug power cord to reset the device. ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.3.3`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Emily Miller: @memily fwupd-1.9.16/plugins/vli/fu-self-test.c000066400000000000000000000032471460375044200177050ustar00rootroot00000000000000/* * Copyright (C) 2023 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-pd-common.h" static void fu_test_vli_pd_common_func(void) { struct { guint32 fwver; FuVliDeviceKind device_kind; } map[] = {{0x00, FU_VLI_DEVICE_KIND_UNKNOWN}, {0x01, FU_VLI_DEVICE_KIND_VL100}, {0x02, FU_VLI_DEVICE_KIND_VL100}, {0x03, FU_VLI_DEVICE_KIND_VL100}, {0x04, FU_VLI_DEVICE_KIND_VL101}, {0x05, FU_VLI_DEVICE_KIND_VL101}, {0x06, FU_VLI_DEVICE_KIND_VL101}, {0x07, FU_VLI_DEVICE_KIND_VL102}, {0x08, FU_VLI_DEVICE_KIND_VL102}, {0x09, FU_VLI_DEVICE_KIND_VL103}, {0x0A, FU_VLI_DEVICE_KIND_VL103}, {0x0B, FU_VLI_DEVICE_KIND_VL104}, {0x0C, FU_VLI_DEVICE_KIND_VL105}, {0x0D, FU_VLI_DEVICE_KIND_VL106}, {0x0E, FU_VLI_DEVICE_KIND_VL107}, {0x0F, FU_VLI_DEVICE_KIND_UNKNOWN}, {0xA0, FU_VLI_DEVICE_KIND_UNKNOWN}, {0xA1, FU_VLI_DEVICE_KIND_VL108}, {0xA2, FU_VLI_DEVICE_KIND_VL109}, {0xA3, FU_VLI_DEVICE_KIND_UNKNOWN}, {0xA4, FU_VLI_DEVICE_KIND_UNKNOWN}, {0xB1, FU_VLI_DEVICE_KIND_VL108}, {0xB2, FU_VLI_DEVICE_KIND_VL109}, {0xB3, FU_VLI_DEVICE_KIND_UNKNOWN}, {0xB4, FU_VLI_DEVICE_KIND_UNKNOWN}, {0xFF, FU_VLI_DEVICE_KIND_UNKNOWN}}; for (guint i = 0; map[i].fwver != 0xFF; i++) { g_debug("checking fwver 0x%02X", map[i].fwver); g_assert_cmpint(fu_vli_pd_common_guess_device_kind((guint32)map[i].fwver << 24), ==, map[i].device_kind); } } int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); g_test_add_func("/vli/pd-common", fu_test_vli_pd_common_func); return g_test_run(); } fwupd-1.9.16/plugins/vli/fu-vli-common.c000066400000000000000000000071571460375044200200630ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-common.h" guint32 fu_vli_common_device_kind_get_size(FuVliDeviceKind device_kind) { if (device_kind == FU_VLI_DEVICE_KIND_VL100) return 0x8000; /* 32KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL101) return 0xc000; /* 48KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL102) return 0x8000; /* 32KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL103) return 0x8000; /* 32KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL104) return 0xc000; /* 48KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL105) return 0xc000; /* 48KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL106) return 0x8000; /* 32KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL107) return 0xC800; /* 50KB */ if (device_kind == FU_VLI_DEVICE_KIND_VL108) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL109) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL122) return 0x80000; if (device_kind == FU_VLI_DEVICE_KIND_VL210) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL211) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL212) return 0x20000 * 2; if (device_kind == FU_VLI_DEVICE_KIND_VL810) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL811) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL811PB0) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL811PB3) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL812B0) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL812B3) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL812Q4S) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL813) return 0x8000; if (device_kind == FU_VLI_DEVICE_KIND_VL815) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL817) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL817S) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL819Q7) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL819Q8) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL820Q7) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL820Q8) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL821Q7) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL821Q8) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL822T) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL822Q5) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL822Q7) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL822Q8) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL822C0) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_PS186) return 0x40000; if (device_kind == FU_VLI_DEVICE_KIND_VL650) return 0x40000; if (device_kind == FU_VLI_DEVICE_KIND_VL830) return 0x20000; if (device_kind == FU_VLI_DEVICE_KIND_VL832) return 0x28000; return 0x0; } guint32 fu_vli_common_device_kind_get_offset(FuVliDeviceKind device_kind) { if (device_kind == FU_VLI_DEVICE_KIND_VL100) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL101) return 0x10000; if (device_kind == FU_VLI_DEVICE_KIND_VL102) return 0x20000; if (device_kind == FU_VLI_DEVICE_KIND_VL103) return 0x20000; if (device_kind == FU_VLI_DEVICE_KIND_VL104) return 0x20000; if (device_kind == FU_VLI_DEVICE_KIND_VL105) return 0x20000; if (device_kind == FU_VLI_DEVICE_KIND_VL106) return 0x20000; if (device_kind == FU_VLI_DEVICE_KIND_VL107) return 0x20000; if (device_kind == FU_VLI_DEVICE_KIND_VL108) return 0x20000; if (device_kind == FU_VLI_DEVICE_KIND_VL109) return 0x20000; return 0x0; } fwupd-1.9.16/plugins/vli/fu-vli-common.h000066400000000000000000000005521460375044200200600ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-struct.h" guint32 fu_vli_common_device_kind_get_size(FuVliDeviceKind device_kind); guint32 fu_vli_common_device_kind_get_offset(FuVliDeviceKind device_kind); fwupd-1.9.16/plugins/vli/fu-vli-device.c000066400000000000000000000523071460375044200200270ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-device.h" #include "fu-vli-struct.h" typedef struct { FuVliDeviceKind kind; FuCfiDevice *cfi_device; gboolean spi_auto_detect; guint8 spi_cmd_read_id_sz; guint32 flash_id; } FuVliDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuVliDevice, fu_vli_device, FU_TYPE_USB_DEVICE) #define GET_PRIVATE(o) (fu_vli_device_get_instance_private(o)) enum { PROP_0, PROP_KIND, PROP_LAST }; FuCfiDevice * fu_vli_device_get_cfi_device(FuVliDevice *self) { FuVliDevicePrivate *priv = GET_PRIVATE(self); return priv->cfi_device; } static gboolean fu_vli_device_spi_write_enable(FuVliDevice *self, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS(self); if (klass->spi_write_enable != NULL) { if (!klass->spi_write_enable(self, error)) { g_prefix_error(error, "failed to write enable SPI: "); return FALSE; } } return TRUE; } static gboolean fu_vli_device_spi_chip_erase(FuVliDevice *self, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS(self); if (klass->spi_chip_erase != NULL) { if (!klass->spi_chip_erase(self, error)) { g_prefix_error(error, "failed to erase SPI data: "); return FALSE; } } return TRUE; } static gboolean fu_vli_device_spi_write_status(FuVliDevice *self, guint8 status, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS(self); if (klass->spi_write_status != NULL) { if (!klass->spi_write_status(self, status, error)) { g_prefix_error(error, "failed to write SPI status 0x%x: ", status); return FALSE; } } return TRUE; } static gboolean fu_vli_device_spi_read_status(FuVliDevice *self, guint8 *status, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS(self); if (klass->spi_read_status != NULL) { if (!klass->spi_read_status(self, status, error)) { g_prefix_error(error, "failed to read status: "); return FALSE; } } return TRUE; } static gboolean fu_vli_device_spi_sector_erase(FuVliDevice *self, guint32 addr, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS(self); if (klass->spi_sector_erase != NULL) { if (!klass->spi_sector_erase(self, addr, error)) { g_prefix_error(error, "failed to erase SPI data @0x%x: ", addr); return FALSE; } } return TRUE; } gboolean fu_vli_device_spi_read_block(FuVliDevice *self, guint32 addr, guint8 *buf, gsize bufsz, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS(self); if (klass->spi_read_data != NULL) { if (!klass->spi_read_data(self, addr, buf, bufsz, error)) { g_prefix_error(error, "failed to read SPI data @0x%x: ", addr); return FALSE; } } return TRUE; } static gboolean fu_vli_device_spi_write_data(FuVliDevice *self, guint32 addr, const guint8 *buf, gsize bufsz, GError **error) { FuVliDeviceClass *klass = FU_VLI_DEVICE_GET_CLASS(self); if (klass->spi_write_data != NULL) { if (!klass->spi_write_data(self, addr, buf, bufsz, error)) { g_prefix_error(error, "failed to write SPI data @0x%x: ", addr); return FALSE; } } return TRUE; } static gboolean fu_vli_device_spi_wait_finish(FuVliDevice *self, GError **error) { const guint32 rdy_cnt = 2; guint32 cnt = 0; for (guint32 idx = 0; idx < 1000; idx++) { guint8 status = 0x7f; /* must get bit[1:0] == 0 twice in a row for success */ if (!fu_vli_device_spi_read_status(self, &status, error)) return FALSE; if ((status & 0x03) == 0x00) { if (cnt++ >= rdy_cnt) return TRUE; } else { cnt = 0; } fu_device_sleep(FU_DEVICE(self), 500); /* ms */ } g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to wait for SPI"); return FALSE; } gboolean fu_vli_device_spi_erase_sector(FuVliDevice *self, guint32 addr, GError **error) { const guint32 bufsz = 0x1000; /* erase sector */ if (!fu_vli_device_spi_write_enable(self, error)) { g_prefix_error(error, "->spi_write_enable failed: "); return FALSE; } if (!fu_vli_device_spi_write_status(self, 0x00, error)) { g_prefix_error(error, "->spi_write_status failed: "); return FALSE; } if (!fu_vli_device_spi_write_enable(self, error)) { g_prefix_error(error, "->spi_write_enable failed: "); return FALSE; } if (!fu_vli_device_spi_sector_erase(self, addr, error)) { g_prefix_error(error, "->spi_sector_erase failed: "); return FALSE; } if (!fu_vli_device_spi_wait_finish(self, error)) { g_prefix_error(error, "->spi_wait_finish failed: "); return FALSE; } /* verify it really was blanked */ for (guint32 offset = 0; offset < bufsz; offset += FU_VLI_DEVICE_TXSIZE) { guint8 buf[FU_VLI_DEVICE_TXSIZE] = {0x0}; if (!fu_vli_device_spi_read_block(self, addr + offset, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read back empty: "); return FALSE; } for (guint i = 0; i < sizeof(buf); i++) { if (buf[i] != 0xff) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to check blank @0x%x", addr + offset + i); return FALSE; } } } /* success */ return TRUE; } GBytes * fu_vli_device_spi_read(FuVliDevice *self, guint32 address, gsize bufsz, FuProgress *progress, GError **error) { g_autofree guint8 *buf = g_malloc0(bufsz); g_autoptr(GPtrArray) chunks = NULL; /* get data from hardware */ chunks = fu_chunk_array_mutable_new(buf, bufsz, address, 0x0, FU_VLI_DEVICE_TXSIZE); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_vli_device_spi_read_block(self, fu_chunk_get_address(chk), fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "SPI data read failed @0x%x: ", fu_chunk_get_address(chk)); return NULL; } fu_progress_step_done(progress); } return g_bytes_new_take(g_steal_pointer(&buf), bufsz); } gboolean fu_vli_device_spi_write_block(FuVliDevice *self, guint32 address, const guint8 *buf, gsize bufsz, FuProgress *progress, GError **error) { g_autofree guint8 *buf_tmp = g_malloc0(bufsz); /* sanity check */ if (bufsz > FU_VLI_DEVICE_TXSIZE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot write 0x%x in one block", (guint)bufsz); return FALSE; } /* write */ g_debug("writing 0x%x block @0x%x", (guint)bufsz, address); if (!fu_vli_device_spi_write_enable(self, error)) { g_prefix_error(error, "enabling SPI write failed: "); return FALSE; } if (!fu_vli_device_spi_write_data(self, address, buf, bufsz, error)) { g_prefix_error(error, "SPI data write failed: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), 1); /* ms */ /* verify */ if (!fu_vli_device_spi_read_block(self, address, buf_tmp, bufsz, error)) { g_prefix_error(error, "SPI data read failed: "); return FALSE; } return fu_memcmp_safe(buf, bufsz, 0, buf_tmp, bufsz, 0, bufsz, error); } gboolean fu_vli_device_spi_write(FuVliDevice *self, guint32 address, const guint8 *buf, gsize bufsz, FuProgress *progress, GError **error) { FuChunk *chk; g_autoptr(GPtrArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 99, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "device-write-chk0"); /* write SPI data, then CRC bytes last */ g_debug("writing 0x%x bytes @0x%x", (guint)bufsz, address); chunks = fu_chunk_array_new(buf, bufsz, 0x0, 0x0, FU_VLI_DEVICE_TXSIZE); if (chunks->len > 1) { FuProgress *progress_local = fu_progress_get_child(progress); fu_progress_set_id(progress_local, G_STRLOC); fu_progress_set_steps(progress_local, chunks->len - 1); for (guint i = 1; i < chunks->len; i++) { chk = g_ptr_array_index(chunks, i); if (!fu_vli_device_spi_write_block(self, fu_chunk_get_address(chk) + address, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), fu_progress_get_child(progress_local), error)) { g_prefix_error(error, "failed to write block 0x%x: ", fu_chunk_get_idx(chk)); return FALSE; } fu_progress_step_done(progress_local); } } fu_progress_step_done(progress); /* chk0 */ chk = g_ptr_array_index(chunks, 0); if (!fu_vli_device_spi_write_block(self, fu_chunk_get_address(chk) + address, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write CRC block: "); return FALSE; } fu_progress_step_done(progress); return TRUE; } gboolean fu_vli_device_spi_erase_all(FuVliDevice *self, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 99, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1, NULL); if (!fu_vli_device_spi_write_enable(self, error)) return FALSE; if (!fu_vli_device_spi_write_status(self, 0x00, error)) return FALSE; if (!fu_vli_device_spi_write_enable(self, error)) return FALSE; if (!fu_vli_device_spi_chip_erase(self, error)) return FALSE; fu_device_sleep_full(FU_DEVICE(self), 4000, fu_progress_get_child(progress)); /* ms */ fu_progress_step_done(progress); /* verify chip was erased */ for (guint addr = 0; addr < 0x10000; addr += 0x1000) { guint8 buf[FU_VLI_DEVICE_TXSIZE] = {0x0}; if (!fu_vli_device_spi_read_block(self, addr, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read @0x%x: ", addr); return FALSE; } for (guint i = 0; i < sizeof(buf); i++) { if (buf[i] != 0xff) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to verify erase @0x%x: ", addr); return FALSE; } } fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)addr + 0x1000, (gsize)0x10000); } fu_progress_step_done(progress); return TRUE; } gboolean fu_vli_device_spi_erase(FuVliDevice *self, guint32 addr, gsize sz, FuProgress *progress, GError **error) { g_autoptr(GPtrArray) chunks = fu_chunk_array_new(NULL, sz, addr, 0x0, 0x1000); g_debug("erasing 0x%x bytes @0x%x", (guint)sz, addr); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); g_debug("erasing @0x%x", fu_chunk_get_address(chk)); if (!fu_vli_device_spi_erase_sector(FU_VLI_DEVICE(self), fu_chunk_get_address(chk), error)) { g_prefix_error(error, "failed to erase FW sector @0x%x: ", fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); } return TRUE; } static gchar * fu_vli_device_get_flash_id_str(FuVliDevice *self) { FuVliDevicePrivate *priv = GET_PRIVATE(self); if (priv->spi_cmd_read_id_sz == 4) return g_strdup_printf("%08X", priv->flash_id); if (priv->spi_cmd_read_id_sz == 2) return g_strdup_printf("%04X", priv->flash_id); if (priv->spi_cmd_read_id_sz == 1) return g_strdup_printf("%02X", priv->flash_id); return g_strdup_printf("%X", priv->flash_id); } void fu_vli_device_set_kind(FuVliDevice *self, FuVliDeviceKind device_kind) { FuVliDevicePrivate *priv = GET_PRIVATE(self); guint32 sz; /* set and notify if different */ if (priv->kind != device_kind) { priv->kind = device_kind; g_object_notify(G_OBJECT(self), "kind"); } /* newer chips use SHA-256 and ECDSA-256 */ switch (device_kind) { case FU_VLI_DEVICE_KIND_MSP430: case FU_VLI_DEVICE_KIND_PS186: case FU_VLI_DEVICE_KIND_RTD21XX: case FU_VLI_DEVICE_KIND_VL100: case FU_VLI_DEVICE_KIND_VL101: case FU_VLI_DEVICE_KIND_VL102: case FU_VLI_DEVICE_KIND_VL103: case FU_VLI_DEVICE_KIND_VL104: case FU_VLI_DEVICE_KIND_VL105: case FU_VLI_DEVICE_KIND_VL106: case FU_VLI_DEVICE_KIND_VL107: case FU_VLI_DEVICE_KIND_VL108: case FU_VLI_DEVICE_KIND_VL109: case FU_VLI_DEVICE_KIND_VL120: case FU_VLI_DEVICE_KIND_VL122: case FU_VLI_DEVICE_KIND_VL210: case FU_VLI_DEVICE_KIND_VL211: case FU_VLI_DEVICE_KIND_VL212: case FU_VLI_DEVICE_KIND_VL810: case FU_VLI_DEVICE_KIND_VL811: case FU_VLI_DEVICE_KIND_VL811PB0: case FU_VLI_DEVICE_KIND_VL811PB3: case FU_VLI_DEVICE_KIND_VL812B0: case FU_VLI_DEVICE_KIND_VL812B3: case FU_VLI_DEVICE_KIND_VL812Q4S: case FU_VLI_DEVICE_KIND_VL813: case FU_VLI_DEVICE_KIND_VL815: case FU_VLI_DEVICE_KIND_VL817: case FU_VLI_DEVICE_KIND_VL817S: case FU_VLI_DEVICE_KIND_VL819Q7: case FU_VLI_DEVICE_KIND_VL819Q8: case FU_VLI_DEVICE_KIND_VL820Q7: case FU_VLI_DEVICE_KIND_VL820Q8: case FU_VLI_DEVICE_KIND_VL821Q7: case FU_VLI_DEVICE_KIND_VL821Q8: case FU_VLI_DEVICE_KIND_VL822T: case FU_VLI_DEVICE_KIND_VL822Q5: case FU_VLI_DEVICE_KIND_VL822Q7: case FU_VLI_DEVICE_KIND_VL822Q8: case FU_VLI_DEVICE_KIND_VL822C0: case FU_VLI_DEVICE_KIND_VL830: case FU_VLI_DEVICE_KIND_VL832: fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); break; case FU_VLI_DEVICE_KIND_VL650: fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); break; default: g_warning("device kind %s [0x%02x] does not indicate unsigned/signed payload", fu_vli_device_kind_to_string(device_kind), device_kind); break; } /* set maximum firmware size */ sz = fu_vli_common_device_kind_get_size(device_kind); if (sz > 0x0) fu_device_set_firmware_size_max(FU_DEVICE(self), sz); /* add extra DEV GUID too */ fu_device_add_instance_str(FU_DEVICE(self), "DEV", fu_vli_device_kind_to_string(priv->kind)); fu_device_build_instance_id(FU_DEVICE(self), NULL, "USB", "VID", "PID", "DEV", NULL); } void fu_vli_device_set_spi_auto_detect(FuVliDevice *self, gboolean spi_auto_detect) { FuVliDevicePrivate *priv = GET_PRIVATE(self); priv->spi_auto_detect = spi_auto_detect; } FuVliDeviceKind fu_vli_device_get_kind(FuVliDevice *self) { FuVliDevicePrivate *priv = GET_PRIVATE(self); return priv->kind; } guint32 fu_vli_device_get_offset(FuVliDevice *self) { FuVliDevicePrivate *priv = GET_PRIVATE(self); return fu_vli_common_device_kind_get_offset(priv->kind); } static void fu_vli_device_to_string(FuDevice *device, guint idt, GString *str) { FuVliDevice *self = FU_VLI_DEVICE(device); FuVliDevicePrivate *priv = GET_PRIVATE(self); /* parent */ FU_DEVICE_CLASS(fu_vli_device_parent_class)->to_string(device, idt, str); if (priv->kind != FU_VLI_DEVICE_KIND_UNKNOWN) { fu_string_append(str, idt, "DeviceKind", fu_vli_device_kind_to_string(priv->kind)); } fu_string_append_kb(str, idt, "SpiAutoDetect", priv->spi_auto_detect); if (priv->flash_id != 0x0) { g_autofree gchar *tmp = fu_vli_device_get_flash_id_str(self); fu_string_append(str, idt, "FlashId", tmp); } fu_device_add_string(FU_DEVICE(priv->cfi_device), idt + 1, str); } static gboolean fu_vli_device_spi_read_flash_id(FuVliDevice *self, GError **error) { FuVliDevicePrivate *priv = GET_PRIVATE(self); GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); guint8 buf[4] = {0x0}; guint8 spi_cmd = 0x0; if (!fu_cfi_device_get_cmd(priv->cfi_device, FU_CFI_DEVICE_CMD_READ_ID, &spi_cmd, error)) return FALSE; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xc0 | (priv->spi_cmd_read_id_sz * 2), spi_cmd, 0x0000, buf, sizeof(buf), NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to read chip ID: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "SpiCmdReadId", buf, sizeof(buf)); if (priv->spi_cmd_read_id_sz == 4) { if (!fu_memread_uint32_safe(buf, sizeof(buf), 0x0, &priv->flash_id, G_BIG_ENDIAN, error)) return FALSE; } else if (priv->spi_cmd_read_id_sz == 2) { guint16 tmp = 0; if (!fu_memread_uint16_safe(buf, sizeof(buf), 0x0, &tmp, G_BIG_ENDIAN, error)) return FALSE; priv->flash_id = tmp; } else if (priv->spi_cmd_read_id_sz == 1) { guint8 tmp = 0; if (!fu_memread_uint8_safe(buf, sizeof(buf), 0x0, &tmp, error)) return FALSE; priv->flash_id = tmp; } return TRUE; } static gboolean fu_vli_device_setup(FuDevice *device, GError **error) { FuVliDevice *self = FU_VLI_DEVICE(device); FuVliDevicePrivate *priv = GET_PRIVATE(self); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_vli_device_parent_class)->setup(device, error)) return FALSE; /* get the flash chip attached */ if (priv->spi_auto_detect) { if (!fu_vli_device_spi_read_flash_id(self, error)) { g_prefix_error(error, "failed to read SPI chip ID: "); return FALSE; } if (priv->flash_id != 0x0) { g_autofree gchar *flash_id = fu_vli_device_get_flash_id_str(self); /* use the correct flash device */ fu_cfi_device_set_flash_id(priv->cfi_device, flash_id); if (!fu_device_setup(FU_DEVICE(priv->cfi_device), error)) return FALSE; /* add extra instance IDs to include the SPI variant */ fu_device_add_instance_str(device, "SPI", flash_id); if (!fu_device_build_instance_id(device, error, "USB", "VID", "PID", "SPI", NULL)) return FALSE; fu_device_build_instance_id(device, NULL, "USB", "VID", "PID", "SPI", "REV", NULL); } } /* success */ return TRUE; } static gboolean fu_vli_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuVliDevice *self = FU_VLI_DEVICE(device); FuVliDevicePrivate *priv = GET_PRIVATE(self); guint64 tmp = 0; if (g_strcmp0(key, "CfiDeviceCmdReadIdSz") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->spi_cmd_read_id_sz = tmp; return TRUE; } if (g_strcmp0(key, "VliSpiAutoDetect") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error)) return FALSE; priv->spi_auto_detect = tmp > 0; return TRUE; } if (g_strcmp0(key, "VliDeviceKind") == 0) { FuVliDeviceKind device_kind; device_kind = fu_vli_device_kind_from_string(value); if (device_kind == FU_VLI_DEVICE_KIND_UNKNOWN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "VliDeviceKind %s is not supported", value); return FALSE; } fu_vli_device_set_kind(self, device_kind); return TRUE; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_vli_device_report_metadata_pre(FuDevice *device, GHashTable *metadata) { FuVliDevice *self = FU_VLI_DEVICE(device); g_hash_table_insert(metadata, g_strdup("GType"), g_strdup(G_OBJECT_TYPE_NAME(self))); } static void fu_vli_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuVliDevice *self = FU_VLI_DEVICE(object); FuVliDevicePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_KIND: g_value_set_uint(value, priv->kind); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_vli_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuVliDevice *self = FU_VLI_DEVICE(object); switch (prop_id) { case PROP_KIND: fu_vli_device_set_kind(self, g_value_get_uint(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_vli_device_constructed(GObject *obj) { FuVliDevice *self = FU_VLI_DEVICE(obj); FuVliDevicePrivate *priv = GET_PRIVATE(self); priv->cfi_device = fu_cfi_device_new(fu_device_get_context(FU_DEVICE(self)), NULL); } static void fu_vli_device_init(FuVliDevice *self) { FuVliDevicePrivate *priv = GET_PRIVATE(self); priv->spi_cmd_read_id_sz = 2; priv->spi_auto_detect = TRUE; fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ADD_INSTANCE_ID_REV); } static void fu_vli_device_finalize(GObject *obj) { FuVliDevice *self = FU_VLI_DEVICE(obj); FuVliDevicePrivate *priv = GET_PRIVATE(self); g_object_unref(priv->cfi_device); G_OBJECT_CLASS(fu_vli_device_parent_class)->finalize(obj); } static void fu_vli_device_class_init(FuVliDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; /* properties */ object_class->get_property = fu_vli_device_get_property; object_class->set_property = fu_vli_device_set_property; object_class->constructed = fu_vli_device_constructed; object_class->finalize = fu_vli_device_finalize; /** * FuVliDevice:kind: * * The kind of VLI device. */ pspec = g_param_spec_uint("kind", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_KIND, pspec); klass_device->to_string = fu_vli_device_to_string; klass_device->set_quirk_kv = fu_vli_device_set_quirk_kv; klass_device->setup = fu_vli_device_setup; klass_device->report_metadata_pre = fu_vli_device_report_metadata_pre; } fwupd-1.9.16/plugins/vli/fu-vli-device.h000066400000000000000000000045421460375044200200320ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-common.h" #define FU_TYPE_VLI_DEVICE (fu_vli_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuVliDevice, fu_vli_device, FU, VLI_DEVICE, FuUsbDevice) struct _FuVliDeviceClass { FuUsbDeviceClass parent_class; gboolean (*spi_chip_erase)(FuVliDevice *self, GError **error); gboolean (*spi_sector_erase)(FuVliDevice *self, guint32 addr, GError **error); gboolean (*spi_read_data)(FuVliDevice *self, guint32 addr, guint8 *buf, gsize bufsz, GError **error); gboolean (*spi_read_status)(FuVliDevice *self, guint8 *status, GError **error); gboolean (*spi_write_enable)(FuVliDevice *self, GError **error); gboolean (*spi_write_data)(FuVliDevice *self, guint32 addr, const guint8 *buf, gsize bufsz, GError **error); gboolean (*spi_write_status)(FuVliDevice *self, guint8 status, GError **error); }; #define FU_VLI_DEVICE_TIMEOUT 3000 /* ms */ #define FU_VLI_DEVICE_TXSIZE 0x20 /* bytes */ void fu_vli_device_set_kind(FuVliDevice *self, FuVliDeviceKind device_kind); void fu_vli_device_set_spi_auto_detect(FuVliDevice *self, gboolean spi_auto_detect); FuVliDeviceKind fu_vli_device_get_kind(FuVliDevice *self); guint32 fu_vli_device_get_offset(FuVliDevice *self); FuCfiDevice * fu_vli_device_get_cfi_device(FuVliDevice *self); gboolean fu_vli_device_spi_erase_sector(FuVliDevice *self, guint32 addr, GError **error); gboolean fu_vli_device_spi_erase_all(FuVliDevice *self, FuProgress *progress, GError **error); gboolean fu_vli_device_spi_erase(FuVliDevice *self, guint32 addr, gsize sz, FuProgress *progress, GError **error); gboolean fu_vli_device_spi_read_block(FuVliDevice *self, guint32 addr, guint8 *buf, gsize bufsz, GError **error); GBytes * fu_vli_device_spi_read(FuVliDevice *self, guint32 address, gsize bufsz, FuProgress *progress, GError **error); gboolean fu_vli_device_spi_write_block(FuVliDevice *self, guint32 address, const guint8 *buf, gsize bufsz, FuProgress *progress, GError **error); gboolean fu_vli_device_spi_write(FuVliDevice *self, guint32 address, const guint8 *buf, gsize bufsz, FuProgress *progress, GError **error); fwupd-1.9.16/plugins/vli/fu-vli-pd-common.c000066400000000000000000000027111460375044200204530ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-pd-common.h" #include "fu-vli-struct.h" FuVliDeviceKind fu_vli_pd_common_guess_device_kind(guint32 fwver) { guint32 tmp = (fwver & 0xFF000000) >> 24; if (tmp < 0xA0) tmp = tmp & 0x0F; if (tmp == FU_VLI_DEVICE_FW_TAG_VL100A || tmp == FU_VLI_DEVICE_FW_TAG_VL100B || tmp == FU_VLI_DEVICE_FW_TAG_VL100C) return FU_VLI_DEVICE_KIND_VL100; if (tmp == FU_VLI_DEVICE_FW_TAG_VL101A || tmp == FU_VLI_DEVICE_FW_TAG_VL101B || tmp == FU_VLI_DEVICE_FW_TAG_VL101C) return FU_VLI_DEVICE_KIND_VL101; if (tmp == FU_VLI_DEVICE_FW_TAG_VL102A || tmp == FU_VLI_DEVICE_FW_TAG_VL102B) return FU_VLI_DEVICE_KIND_VL102; if (tmp == FU_VLI_DEVICE_FW_TAG_VL103A || tmp == FU_VLI_DEVICE_FW_TAG_VL103B) return FU_VLI_DEVICE_KIND_VL103; if (tmp == FU_VLI_DEVICE_FW_TAG_VL104) return FU_VLI_DEVICE_KIND_VL104; if (tmp == FU_VLI_DEVICE_FW_TAG_VL105) return FU_VLI_DEVICE_KIND_VL105; if (tmp == FU_VLI_DEVICE_FW_TAG_VL106) return FU_VLI_DEVICE_KIND_VL106; if (tmp == FU_VLI_DEVICE_FW_TAG_VL107) return FU_VLI_DEVICE_KIND_VL107; if (tmp == FU_VLI_DEVICE_FW_TAG_VL108A || tmp == FU_VLI_DEVICE_FW_TAG_VL108B) return FU_VLI_DEVICE_KIND_VL108; if (tmp == FU_VLI_DEVICE_FW_TAG_VL109A || tmp == FU_VLI_DEVICE_FW_TAG_VL109B) return FU_VLI_DEVICE_KIND_VL109; return FU_VLI_DEVICE_KIND_UNKNOWN; } fwupd-1.9.16/plugins/vli/fu-vli-pd-common.h000066400000000000000000000021351460375044200204600ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-common.h" #define VLI_USBHUB_PD_FLASHMAP_ADDR_LEGACY 0x4000 #define VLI_USBHUB_PD_FLASHMAP_ADDR 0x1003 #define FU_VLI_DEVICE_FW_TAG_VL100A 0x01 #define FU_VLI_DEVICE_FW_TAG_VL100B 0x02 #define FU_VLI_DEVICE_FW_TAG_VL100C 0x03 #define FU_VLI_DEVICE_FW_TAG_VL101A 0x04 #define FU_VLI_DEVICE_FW_TAG_VL101B 0x05 #define FU_VLI_DEVICE_FW_TAG_VL101C 0x06 #define FU_VLI_DEVICE_FW_TAG_VL102A 0x07 #define FU_VLI_DEVICE_FW_TAG_VL102B 0x08 #define FU_VLI_DEVICE_FW_TAG_VL103A 0x09 #define FU_VLI_DEVICE_FW_TAG_VL103B 0x0A #define FU_VLI_DEVICE_FW_TAG_VL104 0x0B #define FU_VLI_DEVICE_FW_TAG_VL105 0x0C #define FU_VLI_DEVICE_FW_TAG_VL106 0x0D #define FU_VLI_DEVICE_FW_TAG_VL107 0x0E #define FU_VLI_DEVICE_FW_TAG_VL108A 0xA1 #define FU_VLI_DEVICE_FW_TAG_VL108B 0xB1 #define FU_VLI_DEVICE_FW_TAG_VL109A 0xA2 #define FU_VLI_DEVICE_FW_TAG_VL109B 0xB2 FuVliDeviceKind fu_vli_pd_common_guess_device_kind(guint32 fwver); fwupd-1.9.16/plugins/vli/fu-vli-pd-device.c000066400000000000000000000657631460375044200204420ustar00rootroot00000000000000/* * Copyright (C) 2015 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-pd-device.h" #include "fu-vli-pd-firmware.h" #include "fu-vli-pd-parade-device.h" #include "fu-vli-struct.h" struct _FuVliPdDevice { FuVliDevice parent_instance; }; /** * FU_VLI_PD_DEVICE_FLAG_HAS_I2C_PS186: * * Device has a PS186 attached via I²C. */ #define FU_VLI_PD_DEVICE_FLAG_HAS_I2C_PS186 (1 << 0) /** * FU_VLI_PD_DEVICE_FLAG_SKIPS_ROM: * * Device updates while in firmware mode, skips ROM mode in detach. */ #define FU_VLI_PD_DEVICE_FLAG_SKIPS_ROM (1 << 1) G_DEFINE_TYPE(FuVliPdDevice, fu_vli_pd_device, FU_TYPE_VLI_DEVICE) static gboolean fu_vli_pd_device_read_regs(FuVliPdDevice *self, guint16 addr, guint8 *buf, gsize bufsz, GError **error) { g_autofree gchar *title = g_strdup_printf("ReadRegs@0x%x", addr); if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xe0, ((addr & 0xff) << 8) | 0x01, addr >> 8, buf, bufsz, NULL, 1000, NULL, error)) { g_prefix_error(error, "failed to write register @0x%x: ", addr); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, title, buf, bufsz); return TRUE; } static gboolean fu_vli_pd_device_read_reg(FuVliPdDevice *self, guint16 addr, guint8 *value, GError **error) { return fu_vli_pd_device_read_regs(self, addr, value, 0x1, error); } static gboolean fu_vli_pd_device_write_reg(FuVliPdDevice *self, guint16 addr, guint8 value, GError **error) { g_autofree gchar *title = g_strdup_printf("WriteReg@0x%x", addr); fu_dump_raw(G_LOG_DOMAIN, title, &value, sizeof(value)); if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xe0, ((addr & 0xff) << 8) | 0x02, addr >> 8, &value, sizeof(value), NULL, 1000, NULL, error)) { g_prefix_error(error, "failed to write register @0x%x: ", addr); return FALSE; } return TRUE; } static gboolean fu_vli_pd_device_spi_read_status(FuVliDevice *self, guint8 *status, GError **error) { guint8 spi_cmd = 0x0; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_READ_STATUS, &spi_cmd, error)) return FALSE; return g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xc5, spi_cmd, 0x0000, status, 0x1, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_pd_device_spi_read_data(FuVliDevice *self, guint32 addr, guint8 *buf, gsize bufsz, GError **error) { guint8 spi_cmd = 0x0; guint16 value; guint16 index; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_READ_DATA, &spi_cmd, error)) return FALSE; value = ((addr << 8) & 0xff00) | spi_cmd; index = addr >> 8; return g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xc4, value, index, buf, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_pd_device_spi_write_status(FuVliDevice *self, guint8 status, GError **error) { guint8 spi_cmd = 0x0; guint16 value; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_WRITE_STATUS, &spi_cmd, error)) return FALSE; value = ((guint16)status << 8) | spi_cmd; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd8, value, 0x0, NULL, 0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } /* Fix_For_GD_&_EN_SPI_Flash */ fu_device_sleep(FU_DEVICE(self), 100); /* ms */ return TRUE; } static gboolean fu_vli_pd_device_spi_write_enable(FuVliDevice *self, GError **error) { guint8 spi_cmd = 0x0; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_WRITE_EN, &spi_cmd, error)) return FALSE; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd4, spi_cmd, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } return TRUE; } static gboolean fu_vli_pd_device_spi_chip_erase(FuVliDevice *self, GError **error) { guint8 spi_cmd = 0x0; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_CHIP_ERASE, &spi_cmd, error)) return FALSE; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd1, spi_cmd, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } return TRUE; } static gboolean fu_vli_pd_device_spi_sector_erase(FuVliDevice *self, guint32 addr, GError **error) { guint8 spi_cmd = 0x0; guint16 value; guint16 index; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_SECTOR_ERASE, &spi_cmd, error)) return FALSE; value = ((addr << 8) & 0xff00) | spi_cmd; index = addr >> 8; return g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd2, value, index, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_pd_device_spi_write_data(FuVliDevice *self, guint32 addr, const guint8 *buf, gsize bufsz, GError **error) { guint8 spi_cmd = 0x0; guint16 value; guint16 index; g_autofree guint8 *buf_mut = NULL; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_PAGE_PROG, &spi_cmd, error)) return FALSE; value = ((addr << 8) & 0xff00) | spi_cmd; index = addr >> 8; buf_mut = fu_memdup_safe(buf, bufsz, error); if (buf_mut == NULL) return FALSE; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xdc, value, index, buf_mut, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } return TRUE; } static gboolean fu_vli_pd_device_parade_setup(FuVliPdDevice *self, GError **error) { g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error_local = NULL; /* add child */ dev = fu_vli_pd_parade_device_new(FU_VLI_DEVICE(self)); if (!fu_device_probe(dev, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_debug("%s", error_local->message); } else { g_warning("cannot create I²C parade device: %s", error_local->message); } return TRUE; } if (!fu_device_setup(dev, error)) { g_prefix_error(error, "failed to set up parade device: "); return FALSE; } fu_device_add_child(FU_DEVICE(self), dev); return TRUE; } static gboolean fu_vli_pd_device_setup(FuDevice *device, GError **error) { FuVliPdDevice *self = FU_VLI_PD_DEVICE(device); guint32 version_raw; guint8 verbuf[4] = {0x0}; guint8 value = 0; guint8 cmp = 0; /* FuVliDevice->setup */ if (!FU_DEVICE_CLASS(fu_vli_pd_device_parent_class)->setup(device, error)) return FALSE; /* get version */ if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xe2, 0x0001, 0x0000, verbuf, sizeof(verbuf), NULL, 1000, NULL, error)) { g_prefix_error(error, "failed to get version: "); return FALSE; } if (!fu_memread_uint32_safe(verbuf, sizeof(verbuf), 0x0, &version_raw, G_BIG_ENDIAN, error)) return FALSE; fu_device_set_version_u32(FU_DEVICE(self), version_raw); /* get device kind if not already in ROM mode */ if (fu_vli_device_get_kind(FU_VLI_DEVICE(self)) == FU_VLI_DEVICE_KIND_UNKNOWN) { if (!fu_vli_pd_device_read_reg(self, FU_VLI_PD_REGISTER_ADDRESS_PROJ_LEGACY, &value, error)) return FALSE; if (value != 0xFF) { switch (value & 0xF0) { case 0x00: fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL100); break; case 0x10: /* this is also the code for VL101, but VL102 is more likely */ fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL102); break; case 0x80: fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL103); break; case 0x90: fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL104); break; case 0xA0: fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL105); break; case 0xB0: fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL106); break; case 0xC0: fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL107); break; default: g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unable to map 0x0018=0x%02X to device kind", value); return FALSE; } } else { if (!fu_vli_pd_device_read_reg(self, FU_VLI_PD_REGISTER_ADDRESS_PROJ_ID_HIGH, &value, error)) return FALSE; if (!fu_vli_pd_device_read_reg(self, FU_VLI_PD_REGISTER_ADDRESS_PROJ_ID_LOW, &cmp, error)) return FALSE; if ((value == 0x35) && (cmp == 0x96)) fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL108); else if ((value == 0x36) && (cmp == 0x01)) fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL109); else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "unable to map 0x9C9D=0x%02X%02X to device kind", value, cmp); return FALSE; } } } /* get bootloader mode */ if (!fu_vli_pd_device_read_reg(self, 0x00F7, &value, error)) return FALSE; if ((value & 0x80) == 0x00) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); else fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); /* detect any I²C child, e.g. parade device */ if (fu_device_has_private_flag(device, FU_VLI_PD_DEVICE_FLAG_HAS_I2C_PS186)) { if (!fu_vli_pd_device_parade_setup(self, error)) return FALSE; } /* success */ return TRUE; } static FuFirmware * fu_vli_pd_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuVliPdDevice *self = FU_VLI_PD_DEVICE(device); FuVliDeviceKind device_kind; g_autoptr(FuFirmware) firmware = fu_vli_pd_firmware_new(); /* check size */ if (g_bytes_get_size(fw) > fu_device_get_firmware_size_max(device)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware too large, got 0x%x, expected <= 0x%x", (guint)g_bytes_get_size(fw), (guint)fu_device_get_firmware_size_max(device)); return NULL; } /* check is compatible with firmware */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; device_kind = fu_vli_pd_firmware_get_kind(FU_VLI_PD_FIRMWARE(firmware)); if (fu_vli_device_get_kind(FU_VLI_DEVICE(self)) != device_kind) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware incompatible, got %s, expected %s", fu_vli_device_kind_to_string(device_kind), fu_vli_device_kind_to_string(fu_vli_device_get_kind(FU_VLI_DEVICE(self)))); return NULL; } /* we could check this against flags */ g_debug("parsed version: %s", fu_firmware_get_version(firmware)); return g_steal_pointer(&firmware); } static GBytes * fu_vli_pd_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuVliPdDevice *self = FU_VLI_PD_DEVICE(device); g_autoptr(FuDeviceLocker) locker = NULL; /* require detach -> attach */ locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_device_detach, (FuDeviceLockerFunc)fu_device_attach, error); if (locker == NULL) return NULL; fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); return fu_vli_device_spi_read(FU_VLI_DEVICE(self), 0x0, fu_device_get_firmware_size_max(device), progress, error); } static gboolean fu_vli_pd_device_write_gpios(FuVliPdDevice *self, GError **error) { /* disable UART-Rx mode */ if (!fu_vli_pd_device_write_reg(self, 0x0015, 0x7F, error)) return FALSE; /* disable 'Watch Mode', chip is not in debug mode */ if (!fu_vli_pd_device_write_reg(self, 0x0019, 0x00, error)) return FALSE; /* GPIO3 output enable, switch/CMOS/Boost control pin */ if (!fu_vli_pd_device_write_reg(self, 0x001C, 0x02, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_device_write_dual_firmware(FuVliPdDevice *self, GBytes *fw, FuProgress *progress, GError **error) { const guint8 *buf = NULL; const guint8 *sbuf = NULL; gsize bufsz = 0; gsize sbufsz = 0; guint16 crc_actual; guint16 crc_file = 0x0; guint32 sec_addr = 0x28000; g_autoptr(GBytes) spi_fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, "crc"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 45, "backup"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 45, "primary"); /* check spi fw1 crc16 */ spi_fw = fu_vli_device_spi_read(FU_VLI_DEVICE(self), fu_vli_device_get_offset(FU_VLI_DEVICE(self)), fu_device_get_firmware_size_max(FU_DEVICE(self)), fu_progress_get_child(progress), error); if (spi_fw == NULL) return FALSE; sbuf = g_bytes_get_data(spi_fw, &sbufsz); if (sbufsz != 0x8000) sec_addr = 0x30000; if (!fu_memread_uint16_safe(sbuf, sbufsz, sbufsz - 2, &crc_file, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read file CRC: "); return FALSE; } crc_actual = fu_crc16(sbuf, sbufsz - 2); fu_progress_step_done(progress); /* update fw2 first if fw1 correct */ buf = g_bytes_get_data(fw, &bufsz); if (crc_actual == crc_file) { if (!fu_vli_device_spi_write(FU_VLI_DEVICE(self), sec_addr, buf, bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); if (!fu_vli_device_spi_write(FU_VLI_DEVICE(self), fu_vli_device_get_offset(FU_VLI_DEVICE(self)), buf, bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* else update fw1 first */ } else { if (!fu_vli_device_spi_write(FU_VLI_DEVICE(self), fu_vli_device_get_offset(FU_VLI_DEVICE(self)), buf, bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); if (!fu_vli_device_spi_write(FU_VLI_DEVICE(self), sec_addr, buf, bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_vli_pd_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuVliPdDevice *self = FU_VLI_PD_DEVICE(device); gsize bufsz = 0; guint8 tmp = 0; const guint8 *buf = NULL; g_autoptr(GBytes) fw = NULL; /* binary blob */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* write GPIOs in new mode */ if (!fu_vli_pd_device_write_gpios(self, error)) return FALSE; /* disable write protect in GPIO_3 */ if (!fu_vli_pd_device_read_reg(self, FU_VLI_PD_REGISTER_ADDRESS_GPIO_CONTROL_A, &tmp, error)) return FALSE; if (!fu_vli_pd_device_write_reg(self, FU_VLI_PD_REGISTER_ADDRESS_GPIO_CONTROL_A, tmp | 0x44, error)) return FALSE; /* dual image on VL103 */ if (fu_vli_device_get_kind(FU_VLI_DEVICE(device)) == FU_VLI_DEVICE_KIND_VL103 && fu_device_has_flag(device, FWUPD_DEVICE_FLAG_DUAL_IMAGE)) return fu_vli_pd_device_write_dual_firmware(self, fw, progress, error); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 63, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 37, NULL); /* erase */ if (!fu_vli_device_spi_erase_all(FU_VLI_DEVICE(self), fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to erase all: "); return FALSE; } fu_progress_step_done(progress); /* write in chunks */ buf = g_bytes_get_data(fw, &bufsz); if (!fu_vli_device_spi_write(FU_VLI_DEVICE(self), fu_vli_device_get_offset(FU_VLI_DEVICE(self)), buf, bufsz, fu_progress_get_child(progress), error)) return FALSE; /* success */ fu_progress_step_done(progress); return TRUE; } static gboolean fu_vli_pd_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuVliPdDevice *self = FU_VLI_PD_DEVICE(device); g_autoptr(GError) error_local = NULL; guint8 gpio_control_a = 0; /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in bootloader mode, skipping"); return TRUE; } /* VL103 only updates in ROM mode, check other devices for skips-rom flag */ if (fu_vli_device_get_kind(FU_VLI_DEVICE(device)) != FU_VLI_DEVICE_KIND_VL103 && fu_device_has_private_flag(device, FU_VLI_PD_DEVICE_FLAG_SKIPS_ROM)) { return TRUE; } /* write GPIOs */ if (!fu_vli_pd_device_write_gpios(self, error)) return FALSE; /* disable charging */ if (!fu_vli_pd_device_read_reg(self, FU_VLI_PD_REGISTER_ADDRESS_GPIO_CONTROL_A, &gpio_control_a, error)) return FALSE; gpio_control_a = (gpio_control_a | 0x10) & 0xFE; if (!fu_vli_pd_device_write_reg(self, FU_VLI_PD_REGISTER_ADDRESS_GPIO_CONTROL_A, gpio_control_a, error)) return FALSE; /* VL103 set ROM sig does not work, so use alternate function */ if ((fu_vli_device_get_kind(FU_VLI_DEVICE(device)) != FU_VLI_DEVICE_KIND_VL100) && (fu_vli_device_get_kind(FU_VLI_DEVICE(device)) != FU_VLI_DEVICE_KIND_VL102)) { if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(device)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xc0, 0x0000, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED)) { g_debug("ignoring %s", error_local->message); } else { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to restart device: "); return FALSE; } } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } /* patch APP5 FW bug (2AF2 -> 2AE2) on VL100-App5 and VL102 */ if (fu_vli_device_get_kind(FU_VLI_DEVICE(device)) == FU_VLI_DEVICE_KIND_VL100 || fu_vli_device_get_kind(FU_VLI_DEVICE(device)) == FU_VLI_DEVICE_KIND_VL102) { guint8 proj_legacy = 0; if (!fu_vli_pd_device_read_reg(self, FU_VLI_PD_REGISTER_ADDRESS_PROJ_LEGACY, &proj_legacy, error)) return FALSE; if (proj_legacy != 0x80) { if (!fu_vli_pd_device_write_reg(self, 0x2AE2, 0x1E, error)) return FALSE; if (!fu_vli_pd_device_write_reg(self, 0x2AE3, 0xC3, error)) return FALSE; if (!fu_vli_pd_device_write_reg(self, 0x2AE4, 0x5A, error)) return FALSE; if (!fu_vli_pd_device_write_reg(self, 0x2AE5, 0x87, error)) return FALSE; } } /* set ROM sig */ if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(device)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xa0, 0x0000, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) return FALSE; /* reset from SPI_Code into ROM_Code */ if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(device)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xb0, 0x0000, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED) || g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NOT_SUPPORTED)) { g_debug("ignoring %s", error_local->message); } else { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to restart device: "); return FALSE; } } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_vli_pd_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuVliPdDevice *self = FU_VLI_PD_DEVICE(device); g_autoptr(GError) error_local = NULL; /* Work around a silicon bug: Once the CC-resistor is removed, the * CC-host thinks the device is un-plugged and turn off VBUS (power). * When VL103 is powered-off, VL103 puts a resistor at CC-pin. * The CC-host will think the device is re-plugged and provides VBUS * again. Then, VL103 will be powered on and runs new FW. */ if (fu_vli_device_get_kind(FU_VLI_DEVICE(device)) == FU_VLI_DEVICE_KIND_VL103) { if (!fu_vli_pd_device_write_reg(self, 0x1201, 0xf6, error)) return FALSE; if (!fu_vli_pd_device_write_reg(self, 0x1001, 0xf6, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } /* sanity check */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } /* chip reset command works only for non-VL103 */ if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(device)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xb0, 0x0000, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE) || g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NOT_SUPPORTED) || g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT) || g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED)) { g_debug("ignoring %s", error_local->message); } else { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to restart device: "); return FALSE; } } /* replug */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static void fu_vli_pd_device_kind_changed_cb(FuVliDevice *device, GParamSpec *pspec, gpointer user_data) { if (fu_vli_device_get_kind(device) == FU_VLI_DEVICE_KIND_VL103) { /* wait for USB-C timeout */ fu_device_set_remove_delay(FU_DEVICE(device), 10000); } } static void fu_vli_pd_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 72, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 28, "reload"); } static void fu_vli_pd_device_init(FuVliPdDevice *self) { fu_device_add_icon(FU_DEVICE(self), "usb-hub"); fu_device_add_protocol(FU_DEVICE(self), "com.vli.pd"); fu_device_set_summary(FU_DEVICE(self), "USB power distribution device"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_vli_device_set_spi_auto_detect(FU_VLI_DEVICE(self), FALSE); fu_device_register_private_flag(FU_DEVICE(self), FU_VLI_PD_DEVICE_FLAG_HAS_I2C_PS186, "has-i2c-ps186"); fu_device_register_private_flag(FU_DEVICE(self), FU_VLI_PD_DEVICE_FLAG_SKIPS_ROM, "skips-rom"); /* connect up attach or detach vfuncs when kind is known */ g_signal_connect(FU_VLI_DEVICE(self), "notify::kind", G_CALLBACK(fu_vli_pd_device_kind_changed_cb), NULL); } static void fu_vli_pd_device_class_init(FuVliPdDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); FuVliDeviceClass *klass_vli_device = FU_VLI_DEVICE_CLASS(klass); klass_device->dump_firmware = fu_vli_pd_device_dump_firmware; klass_device->write_firmware = fu_vli_pd_device_write_firmware; klass_device->prepare_firmware = fu_vli_pd_device_prepare_firmware; klass_device->attach = fu_vli_pd_device_attach; klass_device->detach = fu_vli_pd_device_detach; klass_device->setup = fu_vli_pd_device_setup; klass_device->set_progress = fu_vli_pd_device_set_progress; klass_vli_device->spi_chip_erase = fu_vli_pd_device_spi_chip_erase; klass_vli_device->spi_sector_erase = fu_vli_pd_device_spi_sector_erase; klass_vli_device->spi_read_data = fu_vli_pd_device_spi_read_data; klass_vli_device->spi_read_status = fu_vli_pd_device_spi_read_status; klass_vli_device->spi_write_data = fu_vli_pd_device_spi_write_data; klass_vli_device->spi_write_enable = fu_vli_pd_device_spi_write_enable; klass_vli_device->spi_write_status = fu_vli_pd_device_spi_write_status; } fwupd-1.9.16/plugins/vli/fu-vli-pd-device.h000066400000000000000000000010451460375044200204260ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-device.h" #define FU_TYPE_VLI_PD_DEVICE (fu_vli_pd_device_get_type()) G_DECLARE_FINAL_TYPE(FuVliPdDevice, fu_vli_pd_device, FU, VLI_PD_DEVICE, FuVliDevice) #define FU_VLI_PD_REGISTER_ADDRESS_PROJ_ID_HIGH 0X009C #define FU_VLI_PD_REGISTER_ADDRESS_PROJ_ID_LOW 0X009D #define FU_VLI_PD_REGISTER_ADDRESS_PROJ_LEGACY 0X0018 #define FU_VLI_PD_REGISTER_ADDRESS_GPIO_CONTROL_A 0X0003 fwupd-1.9.16/plugins/vli/fu-vli-pd-firmware.c000066400000000000000000000105351460375044200210020ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-pd-common.h" #include "fu-vli-pd-firmware.h" #include "fu-vli-struct.h" struct _FuVliPdFirmware { FuFirmwareClass parent_instance; FuVliDeviceKind device_kind; guint16 vid; guint16 pid; }; G_DEFINE_TYPE(FuVliPdFirmware, fu_vli_pd_firmware, FU_TYPE_FIRMWARE) FuVliDeviceKind fu_vli_pd_firmware_get_kind(FuVliPdFirmware *self) { g_return_val_if_fail(FU_IS_VLI_PD_FIRMWARE(self), 0); return self->device_kind; } static gboolean fu_vli_pd_firmware_validate_header(FuVliPdFirmware *self) { if (self->vid == 0x2109) return TRUE; if (self->vid == 0x17EF) return TRUE; if (self->vid == 0x2D01) return TRUE; if (self->vid == 0x06C4) return TRUE; if (self->vid == 0x0BF8) return TRUE; if (self->vid == 0x208E) return TRUE; return FALSE; } static void fu_vli_pd_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuVliPdFirmware *self = FU_VLI_PD_FIRMWARE(firmware); fu_xmlb_builder_insert_kv(bn, "device_kind", fu_vli_device_kind_to_string(self->device_kind)); fu_xmlb_builder_insert_kx(bn, "vid", self->vid); fu_xmlb_builder_insert_kx(bn, "pid", self->pid); } static gboolean fu_vli_pd_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuVliPdFirmware *self = FU_VLI_PD_FIRMWARE(firmware); gsize bufsz = 0; guint32 fwver; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autofree gchar *fwver_str = NULL; g_autoptr(GByteArray) st = NULL; /* parse */ st = fu_struct_vli_pd_hdr_parse(buf, bufsz, VLI_USBHUB_PD_FLASHMAP_ADDR, error); if (st == NULL) { g_prefix_error(error, "failed to read header: "); return FALSE; } self->vid = fu_struct_vli_pd_hdr_get_vid(st); /* fall back to legacy location */ if (!fu_vli_pd_firmware_validate_header(self)) { g_byte_array_unref(st); st = fu_struct_vli_pd_hdr_parse(buf, bufsz, VLI_USBHUB_PD_FLASHMAP_ADDR_LEGACY, error); if (st == NULL) { g_prefix_error(error, "failed to read header: "); return FALSE; } } self->vid = fu_struct_vli_pd_hdr_get_vid(st); /* urgh, not found */ if (!fu_vli_pd_firmware_validate_header(self)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "header invalid, VID not supported"); return FALSE; } /* guess device kind from fwver */ fwver = fu_struct_vli_pd_hdr_get_fwver(st); self->device_kind = fu_vli_pd_common_guess_device_kind(fwver); if (self->device_kind == FU_VLI_DEVICE_KIND_UNKNOWN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "version invalid, using 0x%x", fwver); return FALSE; } fwver_str = fu_version_from_uint32(fwver, FWUPD_VERSION_FORMAT_QUAD); fu_firmware_set_version(firmware, fwver_str); fu_firmware_set_version_raw(firmware, fwver); /* check size */ if (bufsz != fu_vli_common_device_kind_get_size(self->device_kind)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "size invalid, got 0x%x expected 0x%x", (guint)bufsz, fu_vli_common_device_kind_get_size(self->device_kind)); return FALSE; } /* check CRC */ if ((flags & FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM) == 0) { guint16 crc_actual; guint16 crc_file = 0x0; if (!fu_memread_uint16_safe(buf, bufsz, bufsz - 2, &crc_file, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to read file CRC: "); return FALSE; } crc_actual = fu_crc16(buf, bufsz - 2); if (crc_actual != crc_file) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "CRC invalid, got 0x%x expected 0x%x", crc_file, crc_actual); return FALSE; } } /* success */ return TRUE; } static void fu_vli_pd_firmware_init(FuVliPdFirmware *self) { fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM); } static void fu_vli_pd_firmware_class_init(FuVliPdFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_vli_pd_firmware_parse; klass_firmware->export = fu_vli_pd_firmware_export; } FuFirmware * fu_vli_pd_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_VLI_PD_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/vli/fu-vli-pd-firmware.h000066400000000000000000000007411460375044200210050ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-common.h" #define FU_TYPE_VLI_PD_FIRMWARE (fu_vli_pd_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuVliPdFirmware, fu_vli_pd_firmware, FU, VLI_PD_FIRMWARE, FuFirmware) FuFirmware * fu_vli_pd_firmware_new(void); FuVliDeviceKind fu_vli_pd_firmware_get_kind(FuVliPdFirmware *self); fwupd-1.9.16/plugins/vli/fu-vli-pd-parade-device.c000066400000000000000000000535231460375044200216630ustar00rootroot00000000000000/* * Copyright (C) 2015 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-pd-device.h" #include "fu-vli-pd-parade-device.h" #include "fu-vli-struct.h" struct _FuVliPdParadeDevice { FuDevice parent_instance; FuVliDeviceKind device_kind; guint8 page2; /* base address */ guint8 page7; /* base address */ }; G_DEFINE_TYPE(FuVliPdParadeDevice, fu_vli_pd_parade_device, FU_TYPE_DEVICE) #define FU_VLI_PD_PARADE_I2C_CMD_WRITE 0xa6 #define FU_VLI_PD_PARADE_I2C_CMD_READ 0xa5 static void fu_vli_pd_parade_device_to_string(FuDevice *device, guint idt, GString *str) { FuVliPdParadeDevice *self = FU_VLI_PD_PARADE_DEVICE(device); fu_string_append(str, idt, "DeviceKind", fu_vli_device_kind_to_string(self->device_kind)); fu_string_append_kx(str, idt, "Page2", self->page2); fu_string_append_kx(str, idt, "Page7", self->page7); } static gboolean fu_vli_pd_parade_device_i2c_read(FuVliPdParadeDevice *self, guint8 page2, guint8 reg_offset, /* customers addr offset */ guint8 *buf, gsize bufsz, GError **error) { guint16 value; /* sanity check */ if (bufsz > 0x40) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "request too large"); return FALSE; } /* VL103 FW only Use bits[7:1], so divide by 2 */ value = ((guint16)reg_offset << 8) | (page2 >> 1); if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_VLI_PD_PARADE_I2C_CMD_READ, value, 0x0, buf, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to read 0x%x:0x%x: ", page2, reg_offset); return FALSE; } return TRUE; } static gboolean fu_vli_pd_parade_device_i2c_write(FuVliPdParadeDevice *self, guint8 page2, guint8 reg_offset, /* customers addr offset */ guint8 val, /* only one byte supported */ GError **error) { guint16 value; guint16 index; guint8 buf[2] = {0x0}; /* apparently unused... */ /* VL103 FW only Use bits[7:1], so divide by 2 */ value = ((guint16)reg_offset << 8) | (page2 >> 1); index = (guint16)val << 8; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, FU_VLI_PD_PARADE_I2C_CMD_WRITE, value, index, buf, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to write 0x%x:0x%x: ", page2, reg_offset); return FALSE; } return TRUE; } static gboolean fu_vli_pd_parade_device_start_mcu(FuVliPdParadeDevice *self, GError **error) { if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0xBC, 0x00, error)) { g_prefix_error(error, "failed to start MCU: "); return FALSE; } return TRUE; } static gboolean fu_vli_pd_parade_device_stop_mcu(FuVliPdParadeDevice *self, GError **error) { if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0xBC, 0xC0, error)) { g_prefix_error(error, "failed to stop MCU: "); return FALSE; } if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0xBC, 0x40, error)) { g_prefix_error(error, "failed to stop MCU 2nd: "); return FALSE; } return TRUE; } static gboolean fu_vli_pd_parade_device_set_offset(FuVliPdParadeDevice *self, guint16 addr, GError **error) { if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x8E, addr >> 8, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x8F, addr & 0xff, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_parade_device_read_fw_ver(FuVliPdParadeDevice *self, GError **error) { guint8 buf[0x20] = {0x0}; g_autofree gchar *version_str = NULL; /* stop MCU */ if (!fu_vli_pd_parade_device_stop_mcu(self, error)) return FALSE; if (!fu_vli_pd_parade_device_set_offset(self, 0x0, error)) return FALSE; fu_device_sleep(FU_DEVICE(self), 10); /* ms */ if (!fu_vli_pd_parade_device_i2c_read(self, self->page7, 0x02, buf, 0x1, error)) return FALSE; if (buf[0] != 0x01 && buf[0] != 0x02) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported on this device: buffer was 0x%02x", buf[0]); return FALSE; } g_debug("getting FW%X version", buf[0]); if (!fu_vli_pd_parade_device_set_offset(self, 0x5000 | buf[0], error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_read(self, self->page7, 0x00, buf, sizeof(buf), error)) return FALSE; /* start MCU */ if (!fu_vli_pd_parade_device_start_mcu(self, error)) return FALSE; /* format version triplet */ version_str = g_strdup_printf("%u.%u.%u", buf[0], buf[1], buf[2]); fu_device_set_version(FU_DEVICE(self), version_str); return TRUE; } static gboolean fu_vli_pd_parade_device_set_wp(FuVliPdParadeDevice *self, gboolean val, GError **error) { return fu_vli_pd_parade_device_i2c_write(self, self->page2, 0xB3, val ? 0x10 : 0x00, error); } static gboolean fu_vli_pd_parade_device_write_enable(FuVliPdParadeDevice *self, GError **error) { /* Set_WP_High, SPI_WEN_06, Len_00, Trigger_Write, Set_WP_Low */ if (!fu_vli_pd_parade_device_set_wp(self, TRUE, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x90, 0x06, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x92, 0x00, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x93, 0x05, error)) return FALSE; if (!fu_vli_pd_parade_device_set_wp(self, FALSE, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_parade_device_write_disable(FuVliPdParadeDevice *self, GError **error) { if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0xDA, 0x00, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_parade_device_write_status(FuVliPdParadeDevice *self, guint8 target_status, GError **error) { /* Set_WP_High, SPI_WSTS_01, Target_Status, Len_01, Trigger_Write, Set_WP_Low */ if (!fu_vli_pd_parade_device_set_wp(self, TRUE, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x90, 0x01, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x90, target_status, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x92, 0x01, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x93, 0x05, error)) return FALSE; if (!fu_vli_pd_parade_device_set_wp(self, FALSE, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_parade_device_wait_ready(FuVliPdParadeDevice *self, GError **error) { gboolean ret = FALSE; guint limit = 100; guint8 buf = 0x0; /* wait for SPI ROM */ for (guint wait_cnt1 = 0; wait_cnt1 < limit; wait_cnt1++) { buf = 0xFF; if (!fu_vli_pd_parade_device_i2c_read(self, self->page2, 0x9E, &buf, sizeof(buf), error)) return FALSE; /* busy status: * bit[1,0]:Byte_Program * bit[3,2]:Sector Erase * bit[5,4]:Chip Erase */ if ((buf & 0x0C) == 0) { ret = TRUE; break; } } if (!ret) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to wait for SPI not BUSY"); return FALSE; } /* wait for SPI ROM status clear */ ret = FALSE; for (guint wait_cnt1 = 0; wait_cnt1 < limit; wait_cnt1++) { gboolean ret2 = FALSE; /* SPI_RSTS_05, Len_01, Trigger_Read */ if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x90, 0x05, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x92, 0x00, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x93, 0x01, error)) return FALSE; /* wait for cmd done */ for (guint wait_cnt2 = 0; wait_cnt2 < limit; wait_cnt2++) { buf = 0xFF; if (!fu_vli_pd_parade_device_i2c_read(self, self->page2, 0x93, &buf, sizeof(buf), error)) return FALSE; if ((buf & 0x01) == 0) { ret2 = TRUE; break; } } if (!ret2) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to wait for SPI CMD done"); return FALSE; } /* Wait_SPI_STS_00 */ buf = 0xFF; if (!fu_vli_pd_parade_device_i2c_read(self, self->page2, 0x91, &buf, sizeof(buf), error)) return FALSE; if ((buf & 0x01) == 0) { ret = TRUE; break; } } if (!ret) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to wait for SPI status clear"); return FALSE; } /* success */ return TRUE; } static gboolean fu_vli_pd_parade_device_sector_erase(FuVliPdParadeDevice *self, guint16 addr, GError **error) { /* SPI_SE_20, SPI_Adr_H, SPI_Adr_M, SPI_Adr_L, Len_03, Trigger_Write */ if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x90, 0x20, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x90, addr >> 8, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x90, addr & 0xff, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x90, 0x00, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x92, 0x03, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x93, 0x05, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_parade_device_enable_mapping(FuVliPdParadeDevice *self, GError **error) { if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0xDA, 0xAA, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0xDA, 0x55, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0xDA, 0x50, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0xDA, 0x41, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0xDA, 0x52, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0xDA, 0x44, error)) return FALSE; return TRUE; } static gboolean fu_vli_pd_parade_device_block_erase(FuVliPdParadeDevice *self, guint8 block_idx, GError **error) { /* erase */ for (guint idx = 0x00; idx < 0x100; idx += 0x10) { if (!fu_vli_pd_parade_device_write_enable(self, error)) return FALSE; if (!fu_vli_pd_parade_device_set_wp(self, TRUE, error)) return FALSE; if (!fu_vli_pd_parade_device_sector_erase(self, ((guint16)block_idx << 8) | idx, error)) return FALSE; if (!fu_vli_pd_parade_device_wait_ready(self, error)) return FALSE; if (!fu_vli_pd_parade_device_set_wp(self, FALSE, error)) return FALSE; } /* verify */ for (guint idx = 0; idx < 0x100; idx += 0x10) { guint8 buf[0x20] = {0xff}; if (!fu_vli_pd_parade_device_set_offset(self, (block_idx << 8) | idx, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_read(self, self->page7, 0, buf, 0x20, error)) return FALSE; for (guint idx2 = 0; idx2 < 0x20; idx2++) { if (buf[idx2] != 0xFF) { guint32 addr = (block_idx << 16) + (idx << 8); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Erase failed @0x%x", addr); return FALSE; } } } /* success */ return TRUE; } static gboolean fu_vli_pd_parade_device_block_write(FuVliPdParadeDevice *self, guint8 block_idx, const guint8 *txbuf, GError **error) { for (guint idx = 0; idx < 0x100; idx++) { if (!fu_vli_pd_parade_device_set_offset(self, (block_idx << 8) | idx, error)) return FALSE; for (guint idx2 = 0; idx2 < 0x100; idx2++) { guint32 buf_offset = (idx << 8) + idx2; if (!fu_vli_pd_parade_device_i2c_write(self, self->page7, (guint8)idx2, txbuf[buf_offset], error)) return FALSE; } } /* success */ return TRUE; } static gboolean fu_vli_pd_parade_device_block_read(FuVliPdParadeDevice *self, guint8 block_idx, guint8 *buf, gsize bufsz, GError **error) { for (guint idx = 0; idx < 0x100; idx++) { if (!fu_vli_pd_parade_device_set_offset(self, (block_idx << 8) | idx, error)) return FALSE; for (guint idx2 = 0; idx2 < 0x100; idx2 += 0x20) { guint buf_offset = (idx << 8) + idx2; if (!fu_vli_pd_parade_device_i2c_read(self, self->page7, idx2, buf + buf_offset, 0x20, error)) return FALSE; } } return TRUE; } static gboolean fu_vli_pd_parade_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuVliPdParadeDevice *self = FU_VLI_PD_PARADE_DEVICE(device); FuVliPdDevice *parent = FU_VLI_PD_DEVICE(fu_device_get_parent(device)); guint8 buf[0x20]; guint block_idx_tmp; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GByteArray) buf_verify = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GBytes) fw_verify = NULL; g_autoptr(FuChunk) chk0 = NULL; g_autoptr(FuChunkArray) blocks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 19, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 45, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 36, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); /* simple image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; /* stop MPU and reset SPI */ if (!fu_vli_pd_parade_device_stop_mcu(self, error)) return FALSE; /* 64K block erase */ if (!fu_vli_pd_parade_device_write_enable(self, error)) return FALSE; if (!fu_vli_pd_parade_device_write_status(self, 0x00, error)) return FALSE; if (!fu_vli_pd_parade_device_wait_ready(self, error)) return FALSE; blocks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x10000); for (guint i = 1; i < fu_chunk_array_length(blocks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(blocks, i); if (!fu_vli_pd_parade_device_block_erase(self, fu_chunk_get_idx(chk), error)) return FALSE; fu_progress_set_percentage_full(fu_progress_get_child(progress), i + 1, fu_chunk_array_length(blocks)); } fu_progress_step_done(progress); /* load F/W to SPI ROM */ if (!fu_vli_pd_parade_device_enable_mapping(self, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x82, 0x20, error)) return FALSE; /* Reset_CLT2SPI_Interface */ fu_device_sleep(device, 100); /* ms */ if (!fu_vli_pd_parade_device_i2c_write(self, self->page2, 0x82, 0x00, error)) return FALSE; /* write blocks */ for (guint i = 1; i < fu_chunk_array_length(blocks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(blocks, i); if (!fu_vli_pd_parade_device_block_write(self, fu_chunk_get_idx(chk), fu_chunk_get_data(chk), error)) return FALSE; fu_progress_set_percentage_full(fu_progress_get_child(progress), i + 1, fu_chunk_array_length(blocks)); } if (!fu_vli_pd_parade_device_write_disable(self, error)) return FALSE; fu_progress_step_done(progress); /* add the new boot config into the verify buffer */ buf_verify = g_byte_array_sized_new(g_bytes_get_size(fw)); chk0 = fu_chunk_array_index(blocks, 0); g_byte_array_append(buf_verify, fu_chunk_get_data(chk0), fu_chunk_get_data_sz(chk0)); /* verify SPI ROM, ignoring the boot config */ for (guint i = 1; i < fu_chunk_array_length(blocks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(blocks, i); gsize bufsz = fu_chunk_get_data_sz(chk); g_autofree guint8 *vbuf = g_malloc0(bufsz); if (!fu_vli_pd_parade_device_block_read(self, fu_chunk_get_idx(chk), vbuf, bufsz, error)) return FALSE; g_byte_array_append(buf_verify, vbuf, bufsz); fu_progress_set_percentage_full(fu_progress_get_child(progress), i + 1, fu_chunk_array_length(blocks)); } fw_verify = g_bytes_new(buf_verify->data, buf_verify->len); if (!fu_bytes_compare(fw, fw_verify, error)) return FALSE; fu_progress_step_done(progress); /* save boot config into Block_0 */ if (!fu_vli_pd_parade_device_write_enable(self, error)) return FALSE; if (!fu_vli_pd_parade_device_set_wp(self, TRUE, error)) return FALSE; if (!fu_vli_pd_parade_device_sector_erase(self, 0x0, error)) return FALSE; if (!fu_vli_pd_parade_device_wait_ready(self, error)) return FALSE; if (!fu_vli_pd_parade_device_set_wp(self, FALSE, error)) return FALSE; /* Page_HW_Write_Disable */ if (!fu_vli_pd_parade_device_enable_mapping(self, error)) return FALSE; block_idx_tmp = 1; if (!fu_vli_pd_parade_device_set_offset(self, 0x0, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page7, 0x00, 0x55, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page7, 0x01, 0xAA, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page7, 0x02, (guint8)block_idx_tmp, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_write(self, self->page7, 0x03, (guint8)(0x01 - block_idx_tmp), error)) return FALSE; if (!fu_vli_pd_parade_device_write_disable(self, error)) return FALSE; /* check boot config data */ if (!fu_vli_pd_parade_device_set_offset(self, 0x0, error)) return FALSE; if (!fu_vli_pd_parade_device_i2c_read(self, self->page7, 0, buf, sizeof(buf), error)) return FALSE; if (buf[0] != 0x55 || buf[1] != 0xAA || buf[2] != block_idx_tmp || buf[3] != 0x01 - block_idx_tmp) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "boot config data error"); return FALSE; } /* enable write protection */ if (!fu_vli_pd_parade_device_write_enable(self, error)) return FALSE; if (!fu_vli_pd_parade_device_write_status(self, 0x8C, error)) return FALSE; if (!fu_vli_pd_parade_device_wait_ready(self, error)) return FALSE; if (!fu_vli_pd_parade_device_write_disable(self, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static GBytes * fu_vli_pd_parade_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuVliPdDevice *parent = FU_VLI_PD_DEVICE(fu_device_get_parent(device)); FuVliPdParadeDevice *self = FU_VLI_PD_PARADE_DEVICE(device); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GByteArray) fw = g_byte_array_new(); g_autoptr(GPtrArray) blocks = NULL; /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return NULL; /* stop MPU and reset SPI */ if (!fu_vli_pd_parade_device_stop_mcu(self, error)) return NULL; /* read */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_VERIFY); fu_byte_array_set_size(fw, fu_device_get_firmware_size_max(device), 0x00); blocks = fu_chunk_array_mutable_new(fw->data, fw->len, 0x0, 0x0, 0x10000); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, blocks->len); for (guint i = 0; i < blocks->len; i++) { FuChunk *chk = g_ptr_array_index(blocks, i); if (!fu_vli_pd_parade_device_block_read(self, fu_chunk_get_idx(chk), fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), error)) return NULL; fu_progress_step_done(progress); } return g_bytes_new(fw->data, fw->len); } static gboolean fu_vli_pd_parade_device_probe(FuDevice *device, GError **error) { FuVliPdParadeDevice *self = FU_VLI_PD_PARADE_DEVICE(device); /* get version */ if (!fu_vli_pd_parade_device_read_fw_ver(self, error)) return FALSE; /* use header to populate device info */ fu_device_add_instance_str(device, "I2C", fu_vli_device_kind_to_string(self->device_kind)); return fu_device_build_instance_id(device, error, "USB", "VID", "PID", "I2C", NULL); } static void fu_vli_pd_parade_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_vli_pd_parade_device_init(FuVliPdParadeDevice *self) { self->device_kind = FU_VLI_DEVICE_KIND_PS186; self->page2 = 0x14; self->page7 = 0x1E; fu_device_add_icon(FU_DEVICE(self), "video-display"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_protocol(FU_DEVICE(self), "com.vli.i2c"); fu_device_set_install_duration(FU_DEVICE(self), 15); /* seconds */ fu_device_set_logical_id(FU_DEVICE(self), "PS186"); fu_device_set_summary(FU_DEVICE(self), "DisplayPort 1.4a to HDMI 2.0b protocol converter"); fu_device_set_firmware_size(FU_DEVICE(self), 0x40000); } static void fu_vli_pd_parade_device_class_init(FuVliPdParadeDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_vli_pd_parade_device_to_string; klass_device->probe = fu_vli_pd_parade_device_probe; klass_device->dump_firmware = fu_vli_pd_parade_device_dump_firmware; klass_device->write_firmware = fu_vli_pd_parade_device_write_firmware; klass_device->set_progress = fu_vli_pd_parade_device_set_progress; } FuDevice * fu_vli_pd_parade_device_new(FuVliDevice *parent) { FuVliPdParadeDevice *self = g_object_new(FU_TYPE_VLI_PD_PARADE_DEVICE, "parent", parent, NULL); return FU_DEVICE(self); } fwupd-1.9.16/plugins/vli/fu-vli-pd-parade-device.h000066400000000000000000000007311460375044200216610ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-device.h" #include "fu-vli-pd-common.h" #define FU_TYPE_VLI_PD_PARADE_DEVICE (fu_vli_pd_parade_device_get_type()) G_DECLARE_FINAL_TYPE(FuVliPdParadeDevice, fu_vli_pd_parade_device, FU, VLI_PD_PARADE_DEVICE, FuDevice) FuDevice * fu_vli_pd_parade_device_new(FuVliDevice *parent); fwupd-1.9.16/plugins/vli/fu-vli-plugin.c000066400000000000000000000021701460375044200200570ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-pd-device.h" #include "fu-vli-pd-firmware.h" #include "fu-vli-plugin.h" #include "fu-vli-usbhub-device.h" #include "fu-vli-usbhub-firmware.h" struct _FuVliPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuVliPlugin, fu_vli_plugin, FU_TYPE_PLUGIN) static void fu_vli_plugin_init(FuVliPlugin *self) { } static void fu_vli_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "VliDeviceKind"); fu_context_add_quirk_key(ctx, "VliSpiAutoDetect"); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_VLI_USBHUB_FIRMWARE); fu_plugin_add_firmware_gtype(plugin, NULL, FU_TYPE_VLI_PD_FIRMWARE); fu_plugin_add_device_gtype(plugin, FU_TYPE_VLI_USBHUB_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_VLI_PD_DEVICE); } static void fu_vli_plugin_class_init(FuVliPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_vli_plugin_constructed; } fwupd-1.9.16/plugins/vli/fu-vli-plugin.h000066400000000000000000000003371460375044200200670ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuVliPlugin, fu_vli_plugin, FU, VLI_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/vli/fu-vli-usbhub-common.h000066400000000000000000000026051460375044200213470ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-common.h" #define FU_VLI_USBHUB_HEADER_STRAPPING1_SELFW1 (1 << 1) #define FU_VLI_USBHUB_HEADER_STRAPPING1_76PIN (1 << 2) #define FU_VLI_USBHUB_HEADER_STRAPPING1_B3UP (1 << 3) #define FU_VLI_USBHUB_HEADER_STRAPPING1_LPC (1 << 4) #define FU_VLI_USBHUB_HEADER_STRAPPING1_U1U2 (1 << 5) #define FU_VLI_USBHUB_HEADER_STRAPPING1_BC (1 << 6) #define FU_VLI_USBHUB_HEADER_STRAPPING1_Q4S (1 << 7) #define FU_VLI_USBHUB_HEADER_STRAPPING2_IDXEN (1 << 0) #define FU_VLI_USBHUB_HEADER_STRAPPING2_FWRTY (1 << 1) #define FU_VLI_USBHUB_HEADER_STRAPPING2_SELFW2 (1 << 7) #define VLI_USBHUB_FLASHMAP_ADDR_TO_IDX(addr) (addr / 0x20) #define VLI_USBHUB_FLASHMAP_IDX_TO_ADDR(addr) (addr * 0x20) #define VLI_USBHUB_FLASHMAP_IDX_HD1 0x00 /* factory firmware */ #define VLI_USBHUB_FLASHMAP_IDX_HD2 0x80 /* update firmware */ #define VLI_USBHUB_FLASHMAP_IDX_INVALID 0xff #define VLI_USBHUB_FLASHMAP_ADDR_HD1 0x0 #define VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP 0x1800 #define VLI_USBHUB_FLASHMAP_ADDR_HD2 0x1000 #define VLI_USBHUB_FLASHMAP_ADDR_FW 0x2000 #define VLI_USBHUB_FLASHMAP_ADDR_PD_LEGACY 0x10000 #define VLI_USBHUB_FLASHMAP_ADDR_PD 0x20000 #define VLI_USBHUB_FLASHMAP_ADDR_PD_BACKUP 0x30000 fwupd-1.9.16/plugins/vli/fu-vli-usbhub-device.c000066400000000000000000001351171460375044200213160ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-vli-struct.h" #include "fu-vli-usbhub-common.h" #include "fu-vli-usbhub-device.h" #include "fu-vli-usbhub-firmware.h" #include "fu-vli-usbhub-msp430-device.h" #include "fu-vli-usbhub-pd-device.h" #include "fu-vli-usbhub-rtd21xx-device.h" struct _FuVliUsbhubDevice { FuVliDevice parent_instance; gboolean disable_powersave; guint8 update_protocol; GByteArray *st_hd1; /* factory */ GByteArray *st_hd2; /* update */ }; G_DEFINE_TYPE(FuVliUsbhubDevice, fu_vli_usbhub_device, FU_TYPE_VLI_DEVICE) /** * FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_GPIOB: * * Use GPIO-B reset to reset the device. */ #define FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_GPIOB (1 << 0) /** * FU_VLI_USBHUB_DEVICE_FLAG_USB2: * * Device is USB-2 speed. */ #define FU_VLI_USBHUB_DEVICE_FLAG_USB2 (1 << 1) /** * FU_VLI_USBHUB_DEVICE_FLAG_USB3: * * Device is USB-3 speed. */ #define FU_VLI_USBHUB_DEVICE_FLAG_USB3 (1 << 2) /** * FU_VLI_USBHUB_DEVICE_FLAG_UNLOCK_LEGACY813: * * Device type VL813 needs unlocking with a custom VDR request. */ #define FU_VLI_USBHUB_DEVICE_FLAG_UNLOCK_LEGACY813 (1 << 3) /** * FU_VLI_USBHUB_DEVICE_FLAG_HAS_SHARED_SPI_PD: * * Device shares the SPI device with the PD device. */ #define FU_VLI_USBHUB_DEVICE_FLAG_HAS_SHARED_SPI_PD (1 << 4) /** * FU_VLI_USBHUB_DEVICE_FLAG_HAS_MSP430: * * Device has a MSP430 attached via I²C. */ #define FU_VLI_USBHUB_DEVICE_FLAG_HAS_MSP430 (1 << 5) /** * FU_VLI_USBHUB_DEVICE_FLAG_HAS_RTD21XX: * * Device has a RTD21XX attached via I²C. */ #define FU_VLI_USBHUB_DEVICE_FLAG_HAS_RTD21XX (1 << 6) /** * FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_USB_CABLE: * * Unplug & re-plug USB cable to reset the device. */ #define FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_USB_CABLE (1 << 7) /** * FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_POWER_CORD: * * Unplug & re-plug power cord to reset the device. */ #define FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_POWER_CORD (1 << 8) static void fu_vli_usbhub_device_to_string(FuDevice *device, guint idt, GString *str) { FuVliUsbhubDevice *self = FU_VLI_USBHUB_DEVICE(device); /* parent */ FU_DEVICE_CLASS(fu_vli_usbhub_device_parent_class)->to_string(device, idt, str); fu_string_append_kb(str, idt, "DisablePowersave", self->disable_powersave); fu_string_append_kx(str, idt, "UpdateProtocol", self->update_protocol); if (self->update_protocol >= 0x2) { g_autofree gchar *st_hd1str = fu_struct_vli_usbhub_hdr_to_string(self->st_hd1); fu_string_append(str, idt, "H1Hdr@0x0", st_hd1str); if (fu_struct_vli_usbhub_hdr_get_dev_id(self->st_hd2) != 0xFFFF) { g_autofree gchar *st_hd2str = fu_struct_vli_usbhub_hdr_to_string(self->st_hd2); fu_string_append(str, idt, "H2Hdr@0x1000", st_hd2str); } } } static guint8 fu_vli_usbhub_header_crc8(GByteArray *hdr) { return ~fu_crc8(hdr->data, hdr->len - 1); } static gboolean fu_vli_usbhub_device_vdr_unlock_813(FuVliUsbhubDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0x85, 0x8786, 0x8988, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to UnLock_VL813: "); return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_device_read_reg(FuVliUsbhubDevice *self, guint16 addr, guint8 *buf, GError **error) { if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, addr >> 8, addr & 0xff, 0x0, buf, 0x1, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to read register 0x%x: ", addr); return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_device_write_reg(FuVliUsbhubDevice *self, guint16 addr, guint8 value, GError **error) { if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, addr >> 8, addr & 0xff, (guint16)value, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to write register 0x%x: ", addr); return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_device_spi_read_status(FuVliDevice *self, guint8 *status, GError **error) { guint8 spi_cmd = 0x0; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_READ_STATUS, &spi_cmd, error)) return FALSE; return g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xc1, spi_cmd, 0x0000, status, 0x1, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_usbhub_device_spi_read_data(FuVliDevice *self, guint32 addr, guint8 *buf, gsize bufsz, GError **error) { guint8 spi_cmd = 0x0; guint16 value; guint16 index; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_READ_DATA, &spi_cmd, error)) return FALSE; value = ((addr >> 8) & 0xff00) | spi_cmd; index = ((addr << 8) & 0xff00) | ((addr >> 8) & 0x00ff); return g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xc4, value, index, buf, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_usbhub_device_spi_write_status(FuVliDevice *self, guint8 status, GError **error) { guint8 spi_cmd = 0x0; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_WRITE_STATUS, &spi_cmd, error)) return FALSE; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd1, spi_cmd, 0x0000, &status, 0x1, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } /* Fix_For_GD_&_EN_SPI_Flash */ fu_device_sleep(FU_DEVICE(self), 100); /* ms */ return TRUE; } static gboolean fu_vli_usbhub_device_spi_write_enable(FuVliDevice *self, GError **error) { guint8 spi_cmd = 0x0; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_WRITE_EN, &spi_cmd, error)) return FALSE; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd1, spi_cmd, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to write enable SPI: "); return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_device_spi_chip_erase(FuVliDevice *self, GError **error) { guint8 spi_cmd = 0x0; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_CHIP_ERASE, &spi_cmd, error)) return FALSE; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd1, spi_cmd, 0x0000, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_device_spi_sector_erase(FuVliDevice *self, guint32 addr, GError **error) { guint8 spi_cmd = 0x0; guint16 value; guint16 index; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_SECTOR_ERASE, &spi_cmd, error)) return FALSE; value = ((addr >> 8) & 0xff00) | spi_cmd; index = ((addr << 8) & 0xff00) | ((addr >> 8) & 0x00ff); return g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd4, value, index, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error); } static gboolean fu_vli_usbhub_device_spi_write_data(FuVliDevice *self, guint32 addr, const guint8 *buf, gsize bufsz, GError **error) { guint8 spi_cmd = 0x0; guint16 value; guint16 index; g_autofree guint8 *buf_mut = NULL; if (!fu_cfi_device_get_cmd(fu_vli_device_get_cfi_device(self), FU_CFI_DEVICE_CMD_PAGE_PROG, &spi_cmd, error)) return FALSE; value = ((addr >> 8) & 0xff00) | spi_cmd; index = ((addr << 8) & 0xff00) | ((addr >> 8) & 0x00ff); buf_mut = fu_memdup_safe(buf, bufsz, error); if (buf_mut == NULL) return FALSE; if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(self)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xd4, value, index, buf_mut, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { return FALSE; } /* patch for PUYA flash write data command */ fu_device_sleep(FU_DEVICE(self), 1); /* ms */ return TRUE; } #define VL817_ADDR_GPIO_OUTPUT_ENABLE 0xF6A0 /* 0=input, 1=output */ #define VL817_ADDR_GPIO_SET_OUTPUT_DATA 0xF6A1 /* 0=low, 1=high */ #define VL817_ADDR_GPIO_GET_INPUT_DATA 0xF6A2 /* 0=low, 1=high */ static gboolean fu_vli_usbhub_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuDevice *proxy = fu_device_get_proxy_with_fallback(device); g_autoptr(GError) error_local = NULL; /* the user has to do something */ if (fu_device_has_private_flag(device, FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_USB_CABLE)) { g_autoptr(FwupdRequest) request = fwupd_request_new(); fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); if (!fu_device_emit_request(device, request, progress, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } if (fu_device_has_private_flag(device, FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_POWER_CORD)) { g_autoptr(FwupdRequest) request = fwupd_request_new(); fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REPLUG_POWER); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); if (!fu_device_emit_request(device, request, progress, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } /* some hardware has to toggle a GPIO to reset the entire PCB */ if (fu_vli_device_get_kind(FU_VLI_DEVICE(proxy)) == FU_VLI_DEVICE_KIND_VL817 && fu_device_has_private_flag(device, FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_GPIOB)) { guint8 tmp = 0x0; /* set GPIOB output enable */ g_info("using GPIO reset for %s", fu_device_get_id(device)); if (!fu_vli_usbhub_device_read_reg(FU_VLI_USBHUB_DEVICE(proxy), VL817_ADDR_GPIO_OUTPUT_ENABLE, &tmp, error)) return FALSE; if (!fu_vli_usbhub_device_write_reg(FU_VLI_USBHUB_DEVICE(proxy), VL817_ADDR_GPIO_OUTPUT_ENABLE, tmp | (1 << 1), error)) return FALSE; /* toggle GPIOB to trigger reset */ if (!fu_vli_usbhub_device_read_reg(FU_VLI_USBHUB_DEVICE(proxy), VL817_ADDR_GPIO_SET_OUTPUT_DATA, &tmp, error)) return FALSE; if (!fu_vli_usbhub_device_write_reg(FU_VLI_USBHUB_DEVICE(proxy), VL817_ADDR_GPIO_SET_OUTPUT_DATA, tmp ^ (1 << 1), error)) return FALSE; } else { /* replug, and ignore the device going away */ if (!g_usb_device_control_transfer(fu_usb_device_get_dev(FU_USB_DEVICE(proxy)), G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, 0xf6, 0x0040, 0x0002, NULL, 0x0, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE) || g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT) || g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_FAILED)) { g_debug("ignoring %s", error_local->message); } else { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to restart device: "); return FALSE; } } } /* success */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } /* disable hub sleep states -- not really required by 815~ hubs */ static gboolean fu_vli_usbhub_device_disable_u1u2(FuVliUsbhubDevice *self, GError **error) { guint8 buf = 0x0; /* clear Reg[0xF8A2] bit_3 & bit_7 -- also * clear Total Switch / Flag To Disable FW Auto-Reload Function */ if (!fu_vli_usbhub_device_read_reg(self, 0xf8a2, &buf, error)) return FALSE; buf &= 0x77; if (!fu_vli_usbhub_device_write_reg(self, 0xf8a2, buf, error)) return FALSE; /* clear Reg[0xF832] bit_0 & bit_1 */ if (!fu_vli_usbhub_device_read_reg(self, 0xf832, &buf, error)) return FALSE; buf &= 0xfc; if (!fu_vli_usbhub_device_write_reg(self, 0xf832, buf, error)) return FALSE; /* clear Reg[0xF920] bit_1 & bit_2 */ if (!fu_vli_usbhub_device_read_reg(self, 0xf920, &buf, error)) return FALSE; buf &= 0xf9; if (!fu_vli_usbhub_device_write_reg(self, 0xf920, buf, error)) return FALSE; /* set Reg[0xF836] bit_3 */ if (!fu_vli_usbhub_device_read_reg(self, 0xf836, &buf, error)) return FALSE; buf |= 0x08; if (!fu_vli_usbhub_device_write_reg(self, 0xf836, buf, error)) return FALSE; return TRUE; } static gboolean fu_vli_usbhub_device_guess_kind(FuVliUsbhubDevice *self, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); guint8 b811P812 = 0x0; guint8 pkgtype = 0x0; guint8 chipid1 = 0x0; guint8 chipid2 = 0x0; guint8 chipid12 = 0x0; guint8 chipid22 = 0x0; guint8 chipver = 0x0; guint8 chipver2 = 0x0; gint tPid = g_usb_device_get_pid(usb_device) & 0x0fff; if (!fu_vli_usbhub_device_read_reg(self, 0xf88c, &chipver, error)) { g_prefix_error(error, "Read_ChipVer failed: "); return FALSE; } g_debug("chipver = 0x%02x", chipver); if (!fu_vli_usbhub_device_read_reg(self, 0xf63f, &chipver2, error)) { g_prefix_error(error, "Read_ChipVer2 failed: "); return FALSE; } g_debug("chipver2 = 0x%02x", chipver2); if (!fu_vli_usbhub_device_read_reg(self, 0xf800, &b811P812, error)) { g_prefix_error(error, "Read_811P812 failed: "); return FALSE; } g_debug("b811P812 = 0x%02x", b811P812); if (!fu_vli_usbhub_device_read_reg(self, 0xf88e, &chipid1, error)) { g_prefix_error(error, "Read_ChipID1 failed: "); return FALSE; } g_debug("chipid1 = 0x%02x", chipid1); if (!fu_vli_usbhub_device_read_reg(self, 0xf88f, &chipid2, error)) { g_prefix_error(error, "Read_ChipID2 failed: "); return FALSE; } g_debug("chipid2 = 0x%02x", chipid2); if (!fu_vli_usbhub_device_read_reg(self, 0xf64e, &chipid12, error)) { g_prefix_error(error, "Read_ChipID12 failed: "); return FALSE; } g_debug("chipid12 = 0x%02x", chipid12); if (!fu_vli_usbhub_device_read_reg(self, 0xf64f, &chipid22, error)) { g_prefix_error(error, "Read_ChipID22 failed: "); return FALSE; } g_debug("chipid22 = 0x%02x", chipid22); if (!fu_vli_usbhub_device_read_reg(self, 0xf651, &pkgtype, error)) { g_prefix_error(error, "Read_820Q7Q8 failed: "); return FALSE; } g_debug("pkgtype = 0x%02x", pkgtype); if (chipid2 == 0x35 && chipid1 == 0x07) { fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL210); } else if (chipid2 == 0x35 && chipid1 == 0x18) { if (chipver == 0xF0) { /* packet type determines device kind for VL819-VL822, minus VL820 */ switch ((pkgtype >> 1) & 0x07) { /* VL822Q7 */ case 0x00: fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL822Q7); break; /* VL822Q5 */ case 0x01: fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL822Q5); break; /* VL822Q8 */ case 0x02: fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL822Q8); break; /* VL821Q7 */ case 0x04: fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL821Q7); break; /* VL819Q7 */ case 0x05: fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL819Q7); break; /* VL821Q8 */ case 0x06: fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL821Q8); break; /* VL819Q8 */ case 0x07: fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL819Q8); break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Packet Type match failed: "); return FALSE; } } else { if (pkgtype & (1 << 2)) fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL820Q8); else fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL820Q7); } } else if (chipid2 == 0x35 && chipid1 == 0x31) { fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL815); } else if (chipid2 == 0x35 && chipid1 == 0x38) { fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL817); } else if (chipid2 == 0x35 && chipid1 == 0x90) { fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL817S); } else if (chipid2 == 0x35 && chipid1 == 0x95) { fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL822T); } else if (chipid2 == 0x35 && chipid1 == 0x99) { if (chipver == 0xC0 || chipver == 0xC1) fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL822C0); else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported 99 type"); return FALSE; } } else if (chipid2 == 0x35 && chipid1 == 0x66) { if (chipver <= 0xC0) fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL830); else fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL832); } else if (chipid2 == 0x35 && chipid1 == 0x45) { fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL211); } else if (chipid22 == 0x35 && chipid12 == 0x53) { fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL120); } else if (chipid22 == 0x35 && chipid12 == 0x92) { fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL122); } else if (tPid == 0x810) { fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL810); } else if (tPid == 0x811) { fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL811); } else if ((b811P812 & ((1 << 5) | (1 << 4))) == 0) { if (chipver == 0x10) fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL811PB0); else fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL811PB3); } else if ((b811P812 & ((1 << 5) | (1 << 4))) == (1 << 4)) { fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL812Q4S); } else if ((b811P812 & ((1 << 5) | (1 << 4))) == ((1 << 5) | (1 << 4))) { if (chipver == 0x10) fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL812B0); else fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL812B3); } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "hardware is not supported"); return FALSE; } /* success */ return TRUE; } static gboolean fu_vli_usbhub_device_probe(FuDevice *device, GError **error) { FuVliUsbhubDevice *self = FU_VLI_USBHUB_DEVICE(device); guint16 usbver = fu_usb_device_get_spec(FU_USB_DEVICE(device)); /* quirks now applied... */ if (usbver > 0x0300 || fu_device_has_private_flag(device, FU_VLI_USBHUB_DEVICE_FLAG_USB3)) { fu_device_set_summary(device, "USB 3.x hub"); /* prefer to show the USB 3 device and only fall back to the * USB 2 version as a recovery */ fu_device_set_priority(device, 1); } else if (usbver > 0x0200 || fu_device_has_private_flag(device, FU_VLI_USBHUB_DEVICE_FLAG_USB2)) { fu_device_set_summary(device, "USB 2.x hub"); } else { fu_device_set_summary(device, "USB hub"); } /* only some required */ if (fu_device_has_private_flag(device, FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_USB_CABLE) || fu_device_has_private_flag(device, FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_POWER_CORD)) { fu_device_add_request_flag(FU_DEVICE(self), FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); } return TRUE; } static gboolean fu_vli_usbhub_device_pd_setup(FuVliUsbhubDevice *self, GError **error) { g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error_local = NULL; /* add child */ dev = fu_vli_usbhub_pd_device_new(self); if (!fu_device_probe(dev, error)) return FALSE; if (!fu_device_setup(dev, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_debug("%s", error_local->message); } else { g_warning("cannot create PD device: %s", error_local->message); } return TRUE; } fu_device_add_child(FU_DEVICE(self), dev); return TRUE; } static gboolean fu_vli_usbhub_device_msp430_setup(FuVliUsbhubDevice *self, GError **error) { g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error_local = NULL; /* add child */ dev = fu_vli_usbhub_msp430_device_new(self); if (!fu_device_probe(dev, error)) return FALSE; if (!fu_device_setup(dev, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_debug("%s", error_local->message); } else { g_warning("cannot create MSP430 I²C device: %s", error_local->message); } return TRUE; } fu_device_add_child(FU_DEVICE(self), dev); return TRUE; } static gboolean fu_vli_usbhub_device_rtd21xx_setup(FuVliUsbhubDevice *self, GError **error) { g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error_local = NULL; /* add child */ dev = fu_vli_usbhub_rtd21xx_device_new(self); if (!fu_device_probe(dev, error)) return FALSE; if (!fu_device_setup(dev, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_debug("%s", error_local->message); } else { g_warning("cannot create RTD21XX I²C device: %s", error_local->message); } return TRUE; } fu_device_add_child(FU_DEVICE(self), dev); return TRUE; } static gboolean fu_vli_usbhub_device_ready(FuDevice *device, GError **error) { FuVliUsbhubDevice *self = FU_VLI_USBHUB_DEVICE(device); g_autoptr(GError) error_tmp = NULL; /* FuUsbDevice->ready */ if (!FU_DEVICE_CLASS(fu_vli_usbhub_device_parent_class)->ready(device, error)) return FALSE; /* to expose U3 hub, wait until fw is stable before sending VDR */ fu_device_sleep(FU_DEVICE(self), 100); /* ms */ /* try to read a block of data which will fail for 813-type devices */ if (fu_device_has_private_flag(device, FU_VLI_USBHUB_DEVICE_FLAG_UNLOCK_LEGACY813) && !fu_vli_device_spi_read_block(FU_VLI_DEVICE(self), 0x0, self->st_hd1->data, self->st_hd1->len, &error_tmp)) { g_warning("failed to read, trying to unlock 813: %s", error_tmp->message); if (!fu_vli_usbhub_device_vdr_unlock_813(self, error)) return FALSE; if (!fu_vli_device_spi_read_block(FU_VLI_DEVICE(self), 0x0, self->st_hd1->data, self->st_hd1->len, error)) { g_prefix_error(error, "813 unlock fail: "); return FALSE; } g_debug("813 unlock OK"); /* VL813 & VL210 have same PID (0x0813), and only VL813 can reply */ fu_vli_device_set_kind(FU_VLI_DEVICE(self), FU_VLI_DEVICE_KIND_VL813); } else { if (!fu_vli_usbhub_device_guess_kind(self, error)) return FALSE; } /* read HD1 (factory) header */ if (!fu_vli_device_spi_read_block(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD1, self->st_hd1->data, self->st_hd1->len, error)) { g_prefix_error(error, "failed to read HD1 header: "); return FALSE; } /* detect update protocol from the device ID */ switch (fu_struct_vli_usbhub_hdr_get_dev_id(self->st_hd1)) { /* VL810~VL813 */ case 0x0d12: self->update_protocol = 0x1; self->disable_powersave = TRUE; fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_set_install_duration(FU_DEVICE(self), 10); /* seconds */ break; /* VL817~ */ case 0x0507: case 0x0518: case 0x0538: case 0x0545: case 0x0553: case 0x0590: case 0x0592: case 0x0595: self->update_protocol = 0x2; fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_set_install_duration(FU_DEVICE(self), 15); /* seconds */ break; case 0x0566: self->update_protocol = 0x3; fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_set_install_duration(FU_DEVICE(self), 30); /* seconds */ break; default: g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "hardware is not supported, dev_id=0x%x", (guint)fu_struct_vli_usbhub_hdr_get_dev_id(self->st_hd1)); return FALSE; } /* read HD2 (update) header */ if (self->update_protocol >= 0x2) { if (!fu_vli_device_spi_read_block(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD2, self->st_hd2->data, self->st_hd2->len, error)) { g_prefix_error(error, "failed to read HD2 header: "); return FALSE; } } /* detect the PD child */ if (fu_device_has_private_flag(device, FU_VLI_USBHUB_DEVICE_FLAG_HAS_SHARED_SPI_PD)) { if (!fu_vli_usbhub_device_pd_setup(self, error)) return FALSE; } /* detect the I²C child */ if (fu_usb_device_get_spec(FU_USB_DEVICE(self)) >= 0x0300 && fu_device_has_private_flag(device, FU_VLI_USBHUB_DEVICE_FLAG_HAS_MSP430)) { if (!fu_vli_usbhub_device_msp430_setup(self, error)) return FALSE; } if (fu_device_has_private_flag(device, FU_VLI_USBHUB_DEVICE_FLAG_HAS_RTD21XX)) { if (!fu_vli_usbhub_device_rtd21xx_setup(self, error)) return FALSE; } /* success */ return TRUE; } static FuFirmware * fu_vli_usbhub_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuVliUsbhubDevice *self = FU_VLI_USBHUB_DEVICE(device); FuVliDeviceKind device_kind; g_autoptr(FuFirmware) firmware = fu_vli_usbhub_firmware_new(); /* check is compatible with firmware */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; device_kind = fu_vli_usbhub_firmware_get_device_kind(FU_VLI_USBHUB_FIRMWARE(firmware)); if (fu_vli_device_get_kind(FU_VLI_DEVICE(self)) != device_kind) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware incompatible, got %s, expected %s", fu_vli_device_kind_to_string(device_kind), fu_vli_device_kind_to_string(fu_vli_device_get_kind(FU_VLI_DEVICE(self)))); return NULL; } if (fu_struct_vli_usbhub_hdr_get_dev_id(self->st_hd1) != fu_vli_usbhub_firmware_get_device_id(FU_VLI_USBHUB_FIRMWARE(firmware))) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware incompatible, got 0x%04x, expected 0x%04x", fu_vli_usbhub_firmware_get_device_id(FU_VLI_USBHUB_FIRMWARE(firmware)), (guint)fu_struct_vli_usbhub_hdr_get_dev_id(self->st_hd1)); return NULL; } /* we could check this against flags */ g_info("parsed version: %s", fu_firmware_get_version(firmware)); return g_steal_pointer(&firmware); } static gboolean fu_vli_usbhub_device_update_v1(FuVliUsbhubDevice *self, FuFirmware *firmware, FuProgress *progress, GError **error) { gsize bufsz = 0; const guint8 *buf; g_autoptr(GBytes) fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 20, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80, NULL); /* simple image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* erase */ if (!fu_vli_device_spi_erase_all(FU_VLI_DEVICE(self), fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to erase chip: "); return FALSE; } fu_progress_step_done(progress); /* write in chunks */ buf = g_bytes_get_data(fw, &bufsz); if (!fu_vli_device_spi_write(FU_VLI_DEVICE(self), 0x0, buf, bufsz, fu_progress_get_child(progress), error)) return FALSE; /* success */ fu_progress_step_done(progress); return TRUE; } /* if no header1 or ROM code update, write data directly */ static gboolean fu_vli_usbhub_device_update_v2_recovery(FuVliUsbhubDevice *self, GBytes *fw, FuProgress *progress, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 20, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80, NULL); /* erase */ for (guint32 addr = 0; addr < bufsz; addr += 0x1000) { if (!fu_vli_device_spi_erase_sector(FU_VLI_DEVICE(self), addr, error)) { g_prefix_error(error, "failed to erase sector @0x%x: ", addr); return FALSE; } fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)addr, bufsz); } fu_progress_step_done(progress); /* write in chunks */ if (!fu_vli_device_spi_write(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD1, buf, bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_vli_usbhub_device_hd1_is_valid(GByteArray *hdr) { if (fu_struct_vli_usbhub_hdr_get_prev_ptr(hdr) != VLI_USBHUB_FLASHMAP_IDX_INVALID) return FALSE; if (fu_struct_vli_usbhub_hdr_get_checksum(hdr) != fu_vli_usbhub_header_crc8(hdr)) return FALSE; return TRUE; } static gboolean fu_vli_usbhub_device_hd1_recover(FuVliUsbhubDevice *self, GByteArray *hdr, FuProgress *progress, GError **error) { /* point to HD2, i.e. updated firmware */ if (fu_struct_vli_usbhub_hdr_get_next_ptr(hdr) != VLI_USBHUB_FLASHMAP_IDX_HD2) { fu_struct_vli_usbhub_hdr_set_next_ptr(hdr, VLI_USBHUB_FLASHMAP_IDX_HD2); fu_struct_vli_usbhub_hdr_set_checksum(hdr, fu_vli_usbhub_header_crc8(hdr)); } /* write new header block */ if (!fu_vli_device_spi_erase_sector(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD1, error)) { g_prefix_error(error, "failed to erase header1 sector at 0x%x: ", (guint)VLI_USBHUB_FLASHMAP_ADDR_HD1); return FALSE; } if (!fu_vli_device_spi_write_block(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD1, hdr->data, hdr->len, progress, error)) { g_prefix_error(error, "failed to write header1 block at 0x%x: ", (guint)VLI_USBHUB_FLASHMAP_ADDR_HD1); return FALSE; } /* update the cached copy */ g_byte_array_unref(self->st_hd1); self->st_hd1 = g_byte_array_ref(hdr); return TRUE; } static gboolean fu_vli_usbhub_device_update_v2(FuVliUsbhubDevice *self, FuFirmware *firmware, FuProgress *progress, GError **error) { gsize buf_fwsz = 0; guint32 hd1_fw_sz; guint32 hd2_fw_sz; guint32 hd2_fw_addr; guint32 hd2_fw_offset; const guint8 *buf_fw; g_autoptr(GByteArray) st_hd = NULL; g_autoptr(GBytes) fw = NULL; /* simple image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* root header is valid */ if (fu_vli_usbhub_device_hd1_is_valid(self->st_hd1)) { /* no update has ever been done */ if (fu_struct_vli_usbhub_hdr_get_next_ptr(self->st_hd1) != VLI_USBHUB_FLASHMAP_IDX_HD2) { /* backup HD1 before recovering */ if (!fu_vli_device_spi_erase_sector(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD2, error)) { g_prefix_error(error, "failed to erase sector at header 1: "); return FALSE; } if (!fu_vli_device_spi_write_block(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP, self->st_hd1->data, self->st_hd1->len, progress, error)) { g_prefix_error(error, "failed to write block at header 1: "); return FALSE; } if (!fu_vli_usbhub_device_hd1_recover(self, self->st_hd1, progress, error)) { g_prefix_error(error, "failed to write header: "); return FALSE; } } } else { /* copy the header from the backup zone */ g_info("HD1 was invalid, reading backup"); if (!fu_vli_device_spi_read_block(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP, self->st_hd1->data, self->st_hd1->len, error)) { g_prefix_error(error, "failed to read root header from 0x%x: ", (guint)VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP); return FALSE; } if (!fu_vli_usbhub_device_hd1_is_valid(self->st_hd1)) { g_info("backup header is also invalid, starting recovery"); return fu_vli_usbhub_device_update_v2_recovery(self, fw, progress, error); } if (!fu_vli_usbhub_device_hd1_recover(self, self->st_hd1, progress, error)) { g_prefix_error(error, "failed to get root header in backup zone: "); return FALSE; } } /* align the update fw address to the sector after the factory size */ hd1_fw_sz = fu_struct_vli_usbhub_hdr_get_usb3_fw_sz(self->st_hd1); if (hd1_fw_sz > 0xF000) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "FW1 size abnormal 0x%x", (guint)hd1_fw_sz); return FALSE; } hd2_fw_addr = (hd1_fw_sz + 0xfff) & 0xf000; hd2_fw_addr += VLI_USBHUB_FLASHMAP_ADDR_FW; /* get the size and offset of the update firmware */ buf_fw = g_bytes_get_data(fw, &buf_fwsz); st_hd = fu_struct_vli_usbhub_hdr_parse(buf_fw, buf_fwsz, 0x0, error); if (st_hd == NULL) return FALSE; hd2_fw_sz = fu_struct_vli_usbhub_hdr_get_usb3_fw_sz(st_hd); hd2_fw_offset = fu_struct_vli_usbhub_hdr_get_usb3_fw_addr(st_hd); g_debug("FW2 @0x%x (length 0x%x, offset 0x%x)", hd2_fw_addr, hd2_fw_sz, hd2_fw_offset); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 72, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 20, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 8, "hd2"); /* make space */ if (!fu_vli_device_spi_erase(FU_VLI_DEVICE(self), hd2_fw_addr, hd2_fw_sz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* perform the actual write */ if (!fu_vli_device_spi_write(FU_VLI_DEVICE(self), hd2_fw_addr, buf_fw + hd2_fw_offset, hd2_fw_sz, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write payload: "); return FALSE; } fu_progress_step_done(progress); /* write new HD2 */ fu_struct_vli_usbhub_hdr_set_usb3_fw_addr(st_hd, hd2_fw_addr & 0xFFFF); fu_struct_vli_usbhub_hdr_set_usb3_fw_addr_high(st_hd, hd2_fw_addr >> 16); fu_struct_vli_usbhub_hdr_set_prev_ptr(st_hd, VLI_USBHUB_FLASHMAP_IDX_HD1); fu_struct_vli_usbhub_hdr_set_next_ptr(st_hd, VLI_USBHUB_FLASHMAP_IDX_INVALID); fu_struct_vli_usbhub_hdr_set_checksum(st_hd, fu_vli_usbhub_header_crc8(st_hd)); if (!fu_vli_device_spi_erase_sector(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD2, error)) { g_prefix_error(error, "failed to erase sectors for HD2: "); return FALSE; } if (!fu_vli_device_spi_write_block(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD2, st_hd->data, st_hd->len, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write HD2: "); return FALSE; } fu_progress_step_done(progress); /* success */ g_byte_array_unref(self->st_hd2); self->st_hd2 = g_byte_array_ref(st_hd); return TRUE; } static gboolean fu_vli_usbhub_device_update_v3(FuVliUsbhubDevice *self, FuFirmware *firmware, FuProgress *progress, GError **error) { gsize buf_fwsz = 0; guint32 hd2_fw_sz; guint32 hd2_fw_addr; guint32 hd2_fw_offset; const guint8 *buf_fw; g_autoptr(GByteArray) st_hd = NULL; g_autoptr(GBytes) fw = NULL; /* simple image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* root header is valid */ if (fu_vli_usbhub_device_hd1_is_valid(self->st_hd1)) { /* no update has ever been done */ if (fu_struct_vli_usbhub_hdr_get_next_ptr(self->st_hd1) != VLI_USBHUB_FLASHMAP_IDX_HD2) { /* backup HD1 before recovering */ if (!fu_vli_device_spi_erase_sector(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD2, error)) { g_prefix_error(error, "failed to erase sector at header 1: "); return FALSE; } if (!fu_vli_device_spi_write_block(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP, self->st_hd1->data, self->st_hd1->len, progress, error)) { g_prefix_error(error, "failed to write block at header 1: "); return FALSE; } if (!fu_vli_usbhub_device_hd1_recover(self, self->st_hd1, progress, error)) { g_prefix_error(error, "failed to write header: "); return FALSE; } } } else { /* copy the header from the backup zone */ g_info("HD1 was invalid, reading backup"); if (!fu_vli_device_spi_read_block(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP, self->st_hd1->data, self->st_hd1->len, error)) { g_prefix_error(error, "failed to read root header from 0x%x: ", (guint)VLI_USBHUB_FLASHMAP_ADDR_HD1_BACKUP); return FALSE; } if (!fu_vli_usbhub_device_hd1_is_valid(self->st_hd1)) { g_info("backup header is also invalid, starting recovery"); return fu_vli_usbhub_device_update_v2_recovery(self, fw, progress, error); } if (!fu_vli_usbhub_device_hd1_recover(self, self->st_hd1, progress, error)) { g_prefix_error(error, "failed to get root header in backup zone: "); return FALSE; } } /* use fixed address for update fw */ if (fu_vli_device_get_kind(FU_VLI_DEVICE(self)) == FU_VLI_DEVICE_KIND_VL830) hd2_fw_addr = 0x60000; else hd2_fw_addr = 0x80000; /* get the size and offset of the update firmware */ buf_fw = g_bytes_get_data(fw, &buf_fwsz); st_hd = fu_struct_vli_usbhub_hdr_parse(buf_fw, buf_fwsz, 0x0, error); if (st_hd == NULL) return FALSE; hd2_fw_sz = (fu_struct_vli_usbhub_hdr_get_usb3_fw_sz_high(st_hd) << 16); hd2_fw_sz += fu_struct_vli_usbhub_hdr_get_usb3_fw_sz(st_hd); hd2_fw_offset = (fu_struct_vli_usbhub_hdr_get_usb3_fw_addr_high(st_hd) << 16); hd2_fw_offset += fu_struct_vli_usbhub_hdr_get_usb3_fw_addr(st_hd); g_debug("FW2 @0x%x (length 0x%x, offset 0x%x)", hd2_fw_addr, hd2_fw_sz, hd2_fw_offset); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 72, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 20, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 8, "hd2"); /* make space */ if (!fu_vli_device_spi_erase(FU_VLI_DEVICE(self), hd2_fw_addr, hd2_fw_sz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* perform the actual write */ if (!fu_vli_device_spi_write(FU_VLI_DEVICE(self), hd2_fw_addr, buf_fw + hd2_fw_offset, hd2_fw_sz, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write payload: "); return FALSE; } fu_progress_step_done(progress); /* write new HD2 */ fu_struct_vli_usbhub_hdr_set_usb3_fw_addr(st_hd, hd2_fw_addr & 0xFFFF); fu_struct_vli_usbhub_hdr_set_usb3_fw_addr_high(st_hd, hd2_fw_addr >> 16); fu_struct_vli_usbhub_hdr_set_prev_ptr(st_hd, VLI_USBHUB_FLASHMAP_IDX_HD1); fu_struct_vli_usbhub_hdr_set_next_ptr(st_hd, VLI_USBHUB_FLASHMAP_IDX_INVALID); fu_struct_vli_usbhub_hdr_set_checksum(st_hd, fu_vli_usbhub_header_crc8(st_hd)); if (!fu_vli_device_spi_erase_sector(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD2, error)) { g_prefix_error(error, "failed to erase sectors for HD2: "); return FALSE; } if (!fu_vli_device_spi_write_block(FU_VLI_DEVICE(self), VLI_USBHUB_FLASHMAP_ADDR_HD2, st_hd->data, st_hd->len, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write HD2: "); return FALSE; } fu_progress_step_done(progress); /* success */ g_byte_array_unref(self->st_hd2); self->st_hd2 = g_byte_array_ref(st_hd); return TRUE; } static GBytes * fu_vli_usbhub_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuVliUsbhubDevice *self = FU_VLI_USBHUB_DEVICE(device); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); return fu_vli_device_spi_read(FU_VLI_DEVICE(self), 0x0, fu_device_get_firmware_size_max(device), progress, error); } static gboolean fu_vli_usbhub_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuVliUsbhubDevice *self = FU_VLI_USBHUB_DEVICE(device); /* disable powersaving if required */ if (self->disable_powersave) { if (!fu_vli_usbhub_device_disable_u1u2(self, error)) { g_prefix_error(error, "disabling powersave failed: "); return FALSE; } } /* use correct method */ if (self->update_protocol == 0x1) return fu_vli_usbhub_device_update_v1(self, firmware, progress, error); if (self->update_protocol == 0x2) return fu_vli_usbhub_device_update_v2(self, firmware, progress, error); if (self->update_protocol == 0x3) return fu_vli_usbhub_device_update_v3(self, firmware, progress, error); /* not sure what to do */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "update protocol 0x%x not supported", self->update_protocol); return FALSE; } static void fu_vli_usbhub_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 92, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 7, "reload"); } static void fu_vli_usbhub_device_init(FuVliUsbhubDevice *self) { self->st_hd1 = fu_struct_vli_usbhub_hdr_new(); self->st_hd2 = fu_struct_vli_usbhub_hdr_new(); fu_device_add_icon(FU_DEVICE(self), "usb-hub"); fu_device_add_protocol(FU_DEVICE(self), "com.vli.usbhub"); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FALLBACK); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_AUTO_PARENT_CHILDREN); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_register_private_flag(FU_DEVICE(self), FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_GPIOB, "attach-with-gpiob"); fu_device_register_private_flag(FU_DEVICE(self), FU_VLI_USBHUB_DEVICE_FLAG_USB2, "usb3"); fu_device_register_private_flag(FU_DEVICE(self), FU_VLI_USBHUB_DEVICE_FLAG_USB3, "usb2"); fu_device_register_private_flag(FU_DEVICE(self), FU_VLI_USBHUB_DEVICE_FLAG_UNLOCK_LEGACY813, "unlock-legacy813"); fu_device_register_private_flag(FU_DEVICE(self), FU_VLI_USBHUB_DEVICE_FLAG_HAS_SHARED_SPI_PD, "has-shared-spi-pd"); fu_device_register_private_flag(FU_DEVICE(self), FU_VLI_USBHUB_DEVICE_FLAG_HAS_MSP430, "has-msp430"); fu_device_register_private_flag(FU_DEVICE(self), FU_VLI_USBHUB_DEVICE_FLAG_HAS_RTD21XX, "has-rtd21xx"); fu_device_register_private_flag(FU_DEVICE(self), FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_USB_CABLE, "attach-with-usb"); fu_device_register_private_flag(FU_DEVICE(self), FU_VLI_USBHUB_DEVICE_FLAG_ATTACH_WITH_POWER_CORD, "attach-with-power"); } static void fu_vli_usbhub_device_finalize(GObject *obj) { FuVliUsbhubDevice *self = FU_VLI_USBHUB_DEVICE(obj); g_byte_array_unref(self->st_hd1); g_byte_array_unref(self->st_hd2); G_OBJECT_CLASS(fu_vli_usbhub_device_parent_class)->finalize(obj); } static void fu_vli_usbhub_device_class_init(FuVliUsbhubDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); FuVliDeviceClass *klass_vli_device = FU_VLI_DEVICE_CLASS(klass); object_class->finalize = fu_vli_usbhub_device_finalize; klass_device->probe = fu_vli_usbhub_device_probe; klass_device->dump_firmware = fu_vli_usbhub_device_dump_firmware; klass_device->write_firmware = fu_vli_usbhub_device_write_firmware; klass_device->prepare_firmware = fu_vli_usbhub_device_prepare_firmware; klass_device->attach = fu_vli_usbhub_device_attach; klass_device->to_string = fu_vli_usbhub_device_to_string; klass_device->ready = fu_vli_usbhub_device_ready; klass_device->set_progress = fu_vli_usbhub_device_set_progress; klass_vli_device->spi_chip_erase = fu_vli_usbhub_device_spi_chip_erase; klass_vli_device->spi_sector_erase = fu_vli_usbhub_device_spi_sector_erase; klass_vli_device->spi_read_data = fu_vli_usbhub_device_spi_read_data; klass_vli_device->spi_read_status = fu_vli_usbhub_device_spi_read_status; klass_vli_device->spi_write_data = fu_vli_usbhub_device_spi_write_data; klass_vli_device->spi_write_enable = fu_vli_usbhub_device_spi_write_enable; klass_vli_device->spi_write_status = fu_vli_usbhub_device_spi_write_status; } fwupd-1.9.16/plugins/vli/fu-vli-usbhub-device.h000066400000000000000000000005261460375044200213160ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-device.h" #define FU_TYPE_VLI_USBHUB_DEVICE (fu_vli_usbhub_device_get_type()) G_DECLARE_FINAL_TYPE(FuVliUsbhubDevice, fu_vli_usbhub_device, FU, VLI_USBHUB_DEVICE, FuVliDevice) fwupd-1.9.16/plugins/vli/fu-vli-usbhub-firmware.c000066400000000000000000000223261460375044200216700ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-struct.h" #include "fu-vli-usbhub-firmware.h" struct _FuVliUsbhubFirmware { FuFirmwareClass parent_instance; FuVliDeviceKind device_kind; guint16 dev_id; }; G_DEFINE_TYPE(FuVliUsbhubFirmware, fu_vli_usbhub_firmware, FU_TYPE_FIRMWARE) FuVliDeviceKind fu_vli_usbhub_firmware_get_device_kind(FuVliUsbhubFirmware *self) { g_return_val_if_fail(FU_IS_VLI_USBHUB_FIRMWARE(self), 0); return self->device_kind; } guint16 fu_vli_usbhub_firmware_get_device_id(FuVliUsbhubFirmware *self) { g_return_val_if_fail(FU_IS_VLI_USBHUB_FIRMWARE(self), 0); return self->dev_id; } static void fu_vli_usbhub_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuVliUsbhubFirmware *self = FU_VLI_USBHUB_FIRMWARE(firmware); fu_xmlb_builder_insert_kv(bn, "device_kind", fu_vli_device_kind_to_string(self->device_kind)); } static gboolean fu_vli_usbhub_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuVliUsbhubFirmware *self = FU_VLI_USBHUB_FIRMWARE(firmware); gsize bufsz = 0; guint16 adr_ofs = 0; guint16 version = 0x0; guint32 adr_ofs32 = 0; guint8 tmp = 0x0; guint8 fwtype = 0x0; guint8 strapping1; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GByteArray) st = NULL; /* map into header */ st = fu_struct_vli_usbhub_hdr_parse(buf, bufsz, 0x0, error); if (st == NULL) { g_prefix_error(error, "failed to read header: "); return FALSE; } self->dev_id = fu_struct_vli_usbhub_hdr_get_dev_id(st); strapping1 = fu_struct_vli_usbhub_hdr_get_strapping1(st); /* get firmware versions */ switch (self->dev_id) { case 0x0d12: /* VL81x */ if (!fu_memread_uint16_safe(buf, bufsz, 0x1f4c, &version, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to get version: "); return FALSE; } version |= (strapping1 >> 4) & 0x07; if ((version & 0x0f) == 0x04) { if (!fu_memread_uint8_safe(buf, bufsz, 0x700d, &tmp, error)) { g_prefix_error(error, "failed to get version increment: "); return FALSE; } if (tmp & 0x40) version += 1; } break; case 0x0507: /* VL210 */ if (!fu_memread_uint16_safe(buf, bufsz, 0x8f0c, &version, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to get version: "); return FALSE; } version |= (strapping1 >> 4) & 0x07; if ((version & 0x0f) == 0x04) version += 1; break; case 0x0566: /* U4ID_Address_In_FW_Zone */ if (!fu_memread_uint24_safe(buf, bufsz, 0x3F80, &adr_ofs32, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to get offset addr: "); return FALSE; } if (!fu_memread_uint16_safe(buf, bufsz, adr_ofs32 - 0x20000 + 0x2000 + 4, &version, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to get offset version: "); return FALSE; } version |= (strapping1 >> 4) & 0x07; break; default: /* U3ID_Address_In_FW_Zone */ if (!fu_memread_uint16_safe(buf, bufsz, 0x8000, &adr_ofs, G_BIG_ENDIAN, error)) { g_prefix_error(error, "failed to get offset addr: "); return FALSE; } if (!fu_memread_uint16_safe(buf, bufsz, adr_ofs + 0x2000 + 0x04, /* U3-M? */ &version, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to get offset version: "); return FALSE; } version |= (strapping1 >> 4) & 0x07; } /* version is set */ if (version != 0x0) { g_autofree gchar *version_str = NULL; version_str = fu_version_from_uint16(version, FWUPD_VERSION_FORMAT_BCD); fu_firmware_set_version(firmware, version_str); fu_firmware_set_version_raw(firmware, version); } /* get device type from firmware image */ switch (self->dev_id) { case 0x0d12: { guint16 binver1 = 0x0; guint16 binver2 = 0x0; guint16 usb2_fw_addr = fu_struct_vli_usbhub_hdr_get_usb2_fw_addr(st) + 0x1ff1; guint16 usb3_fw_addr = fu_struct_vli_usbhub_hdr_get_usb3_fw_addr(st) + 0x1ffa; if (!fu_memread_uint16_safe(buf, bufsz, usb2_fw_addr, &binver1, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to get binver1: "); return FALSE; } if (!fu_memread_uint16_safe(buf, bufsz, usb3_fw_addr, &binver2, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to get binver2: "); return FALSE; } /* VL813 == VT3470 */ if ((binver1 == 0xb770 && binver2 == 0xb770) || (binver1 == 0xb870 && binver2 == 0xb870)) { self->device_kind = FU_VLI_DEVICE_KIND_VL813; /* VLQ4S == VT3470 (Q4S) */ } else if (strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_Q4S) { self->device_kind = FU_VLI_DEVICE_KIND_VL812Q4S; /* VL812 == VT3470 (812/813) */ } else if (strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_76PIN) { /* is B3 */ if (strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_B3UP) self->device_kind = FU_VLI_DEVICE_KIND_VL812B3; else self->device_kind = FU_VLI_DEVICE_KIND_VL812B0; /* VL811P == VT3470 */ } else { /* is B3 */ if (strapping1 & FU_VLI_USBHUB_HEADER_STRAPPING1_B3UP) self->device_kind = FU_VLI_DEVICE_KIND_VL811PB3; else self->device_kind = FU_VLI_DEVICE_KIND_VL811PB0; } break; } case 0x0507: /* VL210 == VT3507 */ self->device_kind = FU_VLI_DEVICE_KIND_VL210; break; case 0x0545: /* VL211 == VT3545 */ self->device_kind = FU_VLI_DEVICE_KIND_VL211; break; case 0x0518: /* VL819~VL822 == VT3518 */ if (!fu_memread_uint8_safe(buf, bufsz, 0x8021, &tmp, error)) { g_prefix_error(error, "failed to get 820/822 byte: "); return FALSE; } /* Q5/Q7/Q8 requires searching two addresses for offset value */ if (!fu_memread_uint16_safe(buf, bufsz, 0x8018, &adr_ofs, G_BIG_ENDIAN, error)) { g_prefix_error(error, "failed to get Q7/Q8 offset mapping: "); return FALSE; } /* VL819, VL821, VL822 */ if (tmp == 0xF0) { if (!fu_memread_uint8_safe(buf, bufsz, adr_ofs + 0x2000, &tmp, error)) { g_prefix_error(error, "failed to get offset version: "); return FALSE; } /* VL819 */ if ((tmp == 0x05) || (tmp == 0x07)) fwtype = tmp & 0x7; else fwtype = (tmp & 0x1) << 1 | (tmp & 0x2) << 1 | (tmp & 0x4) >> 2; /* matching Q5/Q7/Q8 */ switch (fwtype) { case 0x00: self->device_kind = FU_VLI_DEVICE_KIND_VL822Q7; break; case 0x01: self->device_kind = FU_VLI_DEVICE_KIND_VL822Q5; break; case 0x02: self->device_kind = FU_VLI_DEVICE_KIND_VL822Q8; break; case 0x04: self->device_kind = FU_VLI_DEVICE_KIND_VL821Q7; break; case 0x05: self->device_kind = FU_VLI_DEVICE_KIND_VL819Q7; break; case 0x06: self->device_kind = FU_VLI_DEVICE_KIND_VL821Q8; break; case 0x07: self->device_kind = FU_VLI_DEVICE_KIND_VL819Q8; break; default: g_prefix_error(error, "failed to match Q5/Q7/Q8 fw type: "); return FALSE; } /* VL820 */ } else if (tmp == 0xC0 || tmp == 0xC1) { self->device_kind = FU_VLI_DEVICE_KIND_VL822C0; } else { if (!fu_memread_uint8_safe(buf, bufsz, 0xf000, &tmp, error)) { g_prefix_error(error, "failed to get Q7/Q8 difference: "); return FALSE; } if (tmp & (1 << 0)) self->device_kind = FU_VLI_DEVICE_KIND_VL820Q8; else self->device_kind = FU_VLI_DEVICE_KIND_VL820Q7; } break; case 0x0595: /* VL822T == VT3595 */ self->device_kind = FU_VLI_DEVICE_KIND_VL822T; break; case 0x0538: /* VL817 == VT3538 */ self->device_kind = FU_VLI_DEVICE_KIND_VL817; break; case 0x0590: /* VL817S == VT3590 */ self->device_kind = FU_VLI_DEVICE_KIND_VL817S; break; case 0x0553: /* VL120 == VT3553 */ self->device_kind = FU_VLI_DEVICE_KIND_VL120; break; case 0x0592: /* VL122 == VT3592 */ self->device_kind = FU_VLI_DEVICE_KIND_VL122; break; case 0x0566: { /* VL830 VL832 = VT3566 */ guint32 binveraddr = 0; guint8 binver = 0; if (!fu_memread_uint24_safe(buf, bufsz, 0x3FBC, &binveraddr, G_LITTLE_ENDIAN, error)) { g_prefix_error(error, "failed to get binveraddr: "); return FALSE; } if (!fu_memread_uint8_safe(buf, bufsz, binveraddr - 0x20000 + 0x2000, &binver, error)) { g_prefix_error(error, "failed to get binver2: "); return FALSE; } /* VL813 == VT3470 */ if (binver <= 0xC0) self->device_kind = FU_VLI_DEVICE_KIND_VL830; else self->device_kind = FU_VLI_DEVICE_KIND_VL832; break; } default: break; } /* device not supported */ if (self->device_kind == FU_VLI_DEVICE_KIND_UNKNOWN) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "device kind unknown"); return FALSE; } /* success */ return TRUE; } static void fu_vli_usbhub_firmware_init(FuVliUsbhubFirmware *self) { } static void fu_vli_usbhub_firmware_class_init(FuVliUsbhubFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->parse = fu_vli_usbhub_firmware_parse; klass_firmware->export = fu_vli_usbhub_firmware_export; } FuFirmware * fu_vli_usbhub_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_VLI_USBHUB_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/vli/fu-vli-usbhub-firmware.h000066400000000000000000000011641460375044200216720ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-usbhub-common.h" #define FU_TYPE_VLI_USBHUB_FIRMWARE (fu_vli_usbhub_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuVliUsbhubFirmware, fu_vli_usbhub_firmware, FU, VLI_USBHUB_FIRMWARE, FuFirmware) FuFirmware * fu_vli_usbhub_firmware_new(void); FuVliDeviceKind fu_vli_usbhub_firmware_get_device_kind(FuVliUsbhubFirmware *self); guint16 fu_vli_usbhub_firmware_get_device_id(FuVliUsbhubFirmware *self); fwupd-1.9.16/plugins/vli/fu-vli-usbhub-i2c-common.c000066400000000000000000000026041460375044200220140ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-usbhub-i2c-common.h" gboolean fu_vli_usbhub_i2c_check_status(FuVliUsbhubI2cStatus status, GError **error) { if (status == FU_VLI_USBHUB_I2C_STATUS_OK) return TRUE; if (status == FU_VLI_USBHUB_I2C_STATUS_HEADER) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Incorrect header value of data frame"); return FALSE; } if (status == FU_VLI_USBHUB_I2C_STATUS_COMMAND) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid command data"); return FALSE; } if (status == FU_VLI_USBHUB_I2C_STATUS_ADDRESS) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Invalid address range"); return FALSE; } if (status == FU_VLI_USBHUB_I2C_STATUS_PACKETSIZE) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Incorrect payload data length"); return FALSE; } if (status == FU_VLI_USBHUB_I2C_STATUS_CHECKSUM) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Incorrect frame data checksum"); return FALSE; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Unknown error [0x%02x]", status); return FALSE; } fwupd-1.9.16/plugins/vli/fu-vli-usbhub-i2c-common.h000066400000000000000000000010621460375044200220160ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include typedef enum { FU_VLI_USBHUB_I2C_STATUS_OK = 0x00, FU_VLI_USBHUB_I2C_STATUS_HEADER = 0x51, FU_VLI_USBHUB_I2C_STATUS_COMMAND = 0x52, FU_VLI_USBHUB_I2C_STATUS_ADDRESS = 0x53, FU_VLI_USBHUB_I2C_STATUS_PACKETSIZE = 0x54, FU_VLI_USBHUB_I2C_STATUS_CHECKSUM = 0x55, } FuVliUsbhubI2cStatus; gboolean fu_vli_usbhub_i2c_check_status(FuVliUsbhubI2cStatus status, GError **error); fwupd-1.9.16/plugins/vli/fu-vli-usbhub-msp430-device.c000066400000000000000000000247711460375044200223450ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-struct.h" #include "fu-vli-usbhub-common.h" #include "fu-vli-usbhub-i2c-common.h" #include "fu-vli-usbhub-msp430-device.h" struct _FuVliUsbhubMsp430Device { FuDevice parent_instance; }; G_DEFINE_TYPE(FuVliUsbhubMsp430Device, fu_vli_usbhub_msp430_device, FU_TYPE_DEVICE) /* Texas Instruments BSL */ #define I2C_ADDR_WRITE 0x18 #define I2C_ADDR_READ 0x19 #define I2C_CMD_WRITE 0x32 #define I2C_CMD_READ_STATUS 0x33 #define I2C_CMD_UPGRADE 0x34 #define I2C_CMD_READ_VERSIONS 0x40 #define I2C_R_VDR 0xa0 /* read vendor command */ #define I2C_W_VDR 0xb0 /* write vendor command */ static gboolean fu_vli_usbhub_device_i2c_read(FuVliUsbhubDevice *self, guint8 cmd, guint8 *buf, gsize bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); guint16 value = ((guint16)I2C_ADDR_WRITE << 8) | cmd; guint16 index = (guint16)I2C_ADDR_READ << 8; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, I2C_R_VDR, value, index, buf, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to read I2C: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "I2cReadData", buf, bufsz); return TRUE; } static gboolean fu_vli_usbhub_device_i2c_read_status(FuVliUsbhubDevice *self, FuVliUsbhubI2cStatus *status, GError **error) { guint8 buf[1] = {0xff}; if (!fu_vli_usbhub_device_i2c_read(self, I2C_CMD_READ_STATUS, buf, sizeof(buf), error)) return FALSE; if (status != NULL) *status = buf[0]; return TRUE; } static gboolean fu_vli_usbhub_device_i2c_write_data(FuVliUsbhubDevice *self, guint8 disable_start_bit, guint8 disable_end_bit, const guint8 *buf, gsize bufsz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); guint16 value = (((guint16)disable_start_bit) << 8) | disable_end_bit; g_autofree guint8 *buf_mut = NULL; fu_dump_raw(G_LOG_DOMAIN, "I2cWriteData", buf, bufsz); buf_mut = fu_memdup_safe(buf, bufsz, error); if (buf_mut == NULL) return FALSE; if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, I2C_W_VDR, value, 0x0, buf_mut, bufsz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to write I2C @0x%x: ", value); return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_msp430_device_setup(FuDevice *device, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); guint8 buf[11] = {0x0}; g_autofree gchar *version = NULL; /* get versions */ if (!fu_vli_usbhub_device_i2c_read(parent, I2C_CMD_READ_VERSIONS, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to read versions: "); return FALSE; } if ((buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0x00) || (buf[0] == 0xff && buf[1] == 0xff && buf[2] == 0xff)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no MSP430 device detected"); return FALSE; } /* set version */ version = g_strdup_printf("%x.%x", buf[0], buf[1]); fu_device_set_version(device, version); return TRUE; } static gboolean fu_vli_usbhub_msp430_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); FuVliUsbhubI2cStatus status = 0xff; g_autoptr(FuDeviceLocker) locker = NULL; const guint8 buf[] = { I2C_ADDR_WRITE, I2C_CMD_UPGRADE, }; /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; if (!fu_vli_usbhub_device_i2c_write_data(parent, 0, 0, buf, sizeof(buf), error)) return FALSE; /* avoid power instability by waiting T1 */ fu_device_sleep_full(device, 1000, progress); /* ms */ /* check the device came back */ if (!fu_vli_usbhub_device_i2c_read_status(parent, &status, error)) { g_prefix_error(error, "device did not come back after detach: "); return FALSE; } return fu_vli_usbhub_i2c_check_status(status, error); } typedef struct { guint8 command; guint8 buf[0x40]; gsize bufsz; guint8 len; } FuVliUsbhubDeviceRequest; static gboolean fu_vli_usbhub_msp430_device_write_firmware_cb(FuDevice *device, gpointer user_data, GError **error) { FuVliUsbhubDeviceRequest *req = (FuVliUsbhubDeviceRequest *)user_data; FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); FuVliUsbhubI2cStatus status = 0xff; fu_device_sleep(device, 5); /* ms */ if (fu_usb_device_get_spec(FU_USB_DEVICE(parent)) >= 0x0300 || req->bufsz <= 32) { if (!fu_vli_usbhub_device_i2c_write_data(parent, 0, 0, req->buf, req->bufsz, error)) return FALSE; } else { /* for U2, hub data buffer <= 32 bytes */ if (!fu_vli_usbhub_device_i2c_write_data(parent, 0, 1, req->buf, 32, error)) return FALSE; if (!fu_vli_usbhub_device_i2c_write_data(parent, 1, 0, req->buf + 32, req->bufsz - 32, error)) return FALSE; } /* end of file, no need to check status */ if (req->len == 0 && req->buf[6] == 0x01 && req->buf[7] == 0xFF) return TRUE; /* read data to check status */ fu_device_sleep(device, 5); /* ms */ if (!fu_vli_usbhub_device_i2c_read_status(parent, &status, error)) return FALSE; return fu_vli_usbhub_i2c_check_status(status, error); } static gboolean fu_vli_usbhub_msp430_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); GPtrArray *records = fu_ihex_firmware_get_records(FU_IHEX_FIRMWARE(firmware)); g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; /* transfer by I²C write, and check status by I²C read */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); fu_progress_set_steps(progress, records->len); for (guint j = 0; j < records->len; j++) { FuIhexFirmwareRecord *rcd = g_ptr_array_index(records, j); FuVliUsbhubDeviceRequest req = {0x0}; const gchar *line = rcd->buf->str; /* length, 16-bit address, type */ req.len = rcd->byte_cnt; if (req.len >= sizeof(req.buf) - 7) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "line too long; buffer size is 0x%x bytes", (guint)sizeof(req.buf)); return FALSE; } /* write each record directly to the hardware */ req.buf[0] = I2C_ADDR_WRITE; req.buf[1] = I2C_CMD_WRITE; req.buf[2] = 0x3a; /* ':' */ req.buf[3] = req.len; if (!fu_firmware_strparse_uint8_safe(line, rcd->buf->len, 3, &req.buf[4], error)) return FALSE; if (!fu_firmware_strparse_uint8_safe(line, rcd->buf->len, 5, &req.buf[5], error)) return FALSE; if (!fu_firmware_strparse_uint8_safe(line, rcd->buf->len, 7, &req.buf[6], error)) return FALSE; for (guint8 i = 0; i < req.len; i++) { if (!fu_firmware_strparse_uint8_safe(line, rcd->buf->len, 9 + (i * 2), &req.buf[7 + i], error)) return FALSE; } if (!fu_firmware_strparse_uint8_safe(line, rcd->buf->len, 9 + (req.len * 2), &req.buf[7 + req.len], error)) return FALSE; req.bufsz = req.len + 8; /* retry this if it fails */ if (!fu_device_retry(device, fu_vli_usbhub_msp430_device_write_firmware_cb, 5, &req, error)) return FALSE; fu_progress_step_done(progress); } /* the device automatically reboots */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* success */ return TRUE; } static gboolean fu_vli_usbhub_msp430_device_probe(FuDevice *device, GError **error) { FuVliDeviceKind device_kind = FU_VLI_DEVICE_KIND_MSP430; FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); fu_device_set_name(device, fu_vli_device_kind_to_string(device_kind)); fu_device_set_physical_id(device, fu_device_get_physical_id(FU_DEVICE(parent))); /* add instance ID */ fu_device_add_instance_str(device, "I2C", fu_vli_device_kind_to_string(device_kind)); return fu_device_build_instance_id(device, error, "USB", "VID", "PID", NULL); } static void fu_vli_usbhub_msp430_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 13, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 85, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_vli_usbhub_msp430_device_init(FuVliUsbhubMsp430Device *self) { fu_device_add_icon(FU_DEVICE(self), "usb-hub"); fu_device_add_protocol(FU_DEVICE(self), "com.vli.i2c"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_set_logical_id(FU_DEVICE(self), "I2C"); fu_device_set_summary(FU_DEVICE(self), "I²C dock management device"); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_IHEX_FIRMWARE); /* the MSP device reboot takes down the entire hub for ~60 seconds */ fu_device_set_remove_delay(FU_DEVICE(self), 120 * 1000); } static void fu_vli_usbhub_msp430_device_class_init(FuVliUsbhubMsp430DeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_vli_usbhub_msp430_device_probe; klass_device->setup = fu_vli_usbhub_msp430_device_setup; klass_device->detach = fu_vli_usbhub_msp430_device_detach; klass_device->write_firmware = fu_vli_usbhub_msp430_device_write_firmware; klass_device->set_progress = fu_vli_usbhub_msp430_device_set_progress; } FuDevice * fu_vli_usbhub_msp430_device_new(FuVliUsbhubDevice *parent) { FuVliUsbhubMsp430Device *self = g_object_new(FU_TYPE_VLI_USBHUB_MSP430_DEVICE, "parent", parent, NULL); return FU_DEVICE(self); } fwupd-1.9.16/plugins/vli/fu-vli-usbhub-msp430-device.h000066400000000000000000000007401460375044200223400ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-usbhub-device.h" #define FU_TYPE_VLI_USBHUB_MSP430_DEVICE (fu_vli_usbhub_msp430_device_get_type()) G_DECLARE_FINAL_TYPE(FuVliUsbhubMsp430Device, fu_vli_usbhub_msp430_device, FU, VLI_USBHUB_MSP430_DEVICE, FuDevice) FuDevice * fu_vli_usbhub_msp430_device_new(FuVliUsbhubDevice *parent); fwupd-1.9.16/plugins/vli/fu-vli-usbhub-pd-device.c000066400000000000000000000235551460375044200217210ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-vli-pd-common.h" #include "fu-vli-pd-firmware.h" #include "fu-vli-struct.h" #include "fu-vli-usbhub-common.h" #include "fu-vli-usbhub-pd-device.h" struct _FuVliUsbhubPdDevice { FuDevice parent_instance; FuVliDeviceKind device_kind; }; G_DEFINE_TYPE(FuVliUsbhubPdDevice, fu_vli_usbhub_pd_device, FU_TYPE_DEVICE) static void fu_vli_usbhub_pd_device_to_string(FuDevice *device, guint idt, GString *str) { FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE(device); fu_string_append(str, idt, "DeviceKind", fu_vli_device_kind_to_string(self->device_kind)); fu_string_append_kx(str, idt, "FwOffset", fu_vli_common_device_kind_get_offset(self->device_kind)); fu_string_append_kx(str, idt, "FwSize", fu_vli_common_device_kind_get_size(self->device_kind)); } static gboolean fu_vli_usbhub_pd_device_setup(FuDevice *device, GError **error) { FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE(device); FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); const gchar *name; guint32 fwver; gsize bufsz = FU_STRUCT_VLI_PD_HDR_SIZE; g_autofree guint8 *buf = g_malloc0(bufsz); g_autoptr(GByteArray) st = NULL; /* legacy location */ if (!fu_vli_device_spi_read_block(FU_VLI_DEVICE(parent), VLI_USBHUB_FLASHMAP_ADDR_PD_LEGACY + VLI_USBHUB_PD_FLASHMAP_ADDR_LEGACY, buf, bufsz, error)) { g_prefix_error(error, "failed to read legacy PD header: "); return FALSE; } st = fu_struct_vli_pd_hdr_parse(buf, bufsz, 0x0, error); if (st == NULL) return FALSE; /* new location */ if (fu_struct_vli_pd_hdr_get_vid(st) != 0x2109) { g_debug("PD VID was 0x%04x trying new location", fu_struct_vli_pd_hdr_get_vid(st)); if (!fu_vli_device_spi_read_block(FU_VLI_DEVICE(parent), VLI_USBHUB_FLASHMAP_ADDR_PD + VLI_USBHUB_PD_FLASHMAP_ADDR, buf, bufsz, error)) { g_prefix_error(error, "failed to read PD header: "); return FALSE; } g_byte_array_unref(st); st = fu_struct_vli_pd_hdr_parse(buf, bufsz, 0x0, error); if (st == NULL) return FALSE; } /* just empty space */ fwver = fu_struct_vli_pd_hdr_get_fwver(st); if (fwver == G_MAXUINT32) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no PD device header found"); return FALSE; } /* get version */ self->device_kind = fu_vli_pd_common_guess_device_kind(fwver); if (self->device_kind == FU_VLI_DEVICE_KIND_UNKNOWN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "PD version invalid [0x%x]", fwver); return FALSE; } name = fu_vli_device_kind_to_string(self->device_kind); fu_device_set_name(device, name); /* use header to populate device info */ fu_device_set_version_u32(device, fwver); /* add standard GUIDs in order of priority */ fu_device_add_instance_u16(device, "VID", fu_struct_vli_pd_hdr_get_vid(st)); fu_device_add_instance_u16(device, "PID", fu_struct_vli_pd_hdr_get_pid(st)); fu_device_add_instance_u8(device, "APP", fwver & 0xff); fu_device_add_instance_str(device, "DEV", name); if (!fu_device_build_instance_id_full(device, FU_DEVICE_INSTANCE_FLAG_QUIRKS, error, "USB", "VID", NULL)) return FALSE; if (!fu_device_build_instance_id(device, error, "USB", "VID", "PID", NULL)) return FALSE; if (!fu_device_build_instance_id(device, error, "USB", "VID", "PID", "DEV", NULL)) return FALSE; if (!fu_device_build_instance_id(device, error, "USB", "VID", "PID", "APP", NULL)) return FALSE; /* these have a backup section */ if (fu_vli_common_device_kind_get_offset(self->device_kind) == VLI_USBHUB_FLASHMAP_ADDR_PD) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); /* success */ return TRUE; } static gboolean fu_vli_usbhub_pd_device_reload(FuDevice *device, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); g_autoptr(FuDeviceLocker) locker = NULL; /* open parent device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; return fu_vli_usbhub_pd_device_setup(device, error); } static FuFirmware * fu_vli_usbhub_pd_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE(device); FuVliDeviceKind device_kind; g_autoptr(FuFirmware) firmware = fu_vli_pd_firmware_new(); /* check is compatible with firmware */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; device_kind = fu_vli_pd_firmware_get_kind(FU_VLI_PD_FIRMWARE(firmware)); if (self->device_kind != device_kind) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware incompatible, got %s, expected %s", fu_vli_device_kind_to_string(device_kind), fu_vli_device_kind_to_string(self->device_kind)); return NULL; } /* we could check this against flags */ g_info("parsed version: %s", fu_firmware_get_version(firmware)); return g_steal_pointer(&firmware); } static GBytes * fu_vli_usbhub_pd_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE(device); g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return NULL; /* read */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ); return fu_vli_device_spi_read(FU_VLI_DEVICE(parent), fu_vli_common_device_kind_get_offset(self->device_kind), fu_device_get_firmware_size_max(device), progress, error); } static gboolean fu_vli_usbhub_pd_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuVliUsbhubPdDevice *self = FU_VLI_USBHUB_PD_DEVICE(device); FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); gsize bufsz = 0; const guint8 *buf; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GBytes) fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 78, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 22, NULL); /* simple image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; /* erase */ buf = g_bytes_get_data(fw, &bufsz); if (!fu_vli_device_spi_erase(FU_VLI_DEVICE(parent), fu_vli_common_device_kind_get_offset(self->device_kind), bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* write */ if (!fu_vli_device_spi_write(FU_VLI_DEVICE(parent), fu_vli_common_device_kind_get_offset(self->device_kind), buf, bufsz, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } /* reboot the parent FuVliUsbhubDevice if we update the FuVliUsbhubPdDevice */ static gboolean fu_vli_usbhub_pd_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuDevice *parent = fu_device_get_parent(device); g_autoptr(FuDeviceLocker) locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; return fu_device_attach_full(parent, progress, error); } static gboolean fu_vli_usbhub_pd_device_probe(FuDevice *device, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); fu_device_set_physical_id(device, fu_device_get_physical_id(FU_DEVICE(parent))); return TRUE; } static void fu_vli_usbhub_pd_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_vli_usbhub_pd_device_init(FuVliUsbhubPdDevice *self) { fu_device_add_icon(FU_DEVICE(self), "usb-hub"); fu_device_add_protocol(FU_DEVICE(self), "com.vli.usbhub"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_set_install_duration(FU_DEVICE(self), 15); /* seconds */ fu_device_set_logical_id(FU_DEVICE(self), "PD"); fu_device_set_summary(FU_DEVICE(self), "USB-C power delivery device"); } static void fu_vli_usbhub_pd_device_class_init(FuVliUsbhubPdDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_vli_usbhub_pd_device_to_string; klass_device->probe = fu_vli_usbhub_pd_device_probe; klass_device->setup = fu_vli_usbhub_pd_device_setup; klass_device->reload = fu_vli_usbhub_pd_device_reload; klass_device->attach = fu_vli_usbhub_pd_device_attach; klass_device->dump_firmware = fu_vli_usbhub_pd_device_dump_firmware; klass_device->write_firmware = fu_vli_usbhub_pd_device_write_firmware; klass_device->prepare_firmware = fu_vli_usbhub_pd_device_prepare_firmware; klass_device->set_progress = fu_vli_usbhub_pd_device_set_progress; } FuDevice * fu_vli_usbhub_pd_device_new(FuVliUsbhubDevice *parent) { FuVliUsbhubPdDevice *self = g_object_new(FU_TYPE_VLI_USBHUB_PD_DEVICE, "parent", parent, NULL); return FU_DEVICE(self); } fwupd-1.9.16/plugins/vli/fu-vli-usbhub-pd-device.h000066400000000000000000000007101460375044200217120ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-usbhub-device.h" #define FU_TYPE_VLI_USBHUB_PD_DEVICE (fu_vli_usbhub_pd_device_get_type()) G_DECLARE_FINAL_TYPE(FuVliUsbhubPdDevice, fu_vli_usbhub_pd_device, FU, VLI_USBHUB_PD_DEVICE, FuDevice) FuDevice * fu_vli_usbhub_pd_device_new(FuVliUsbhubDevice *parent); fwupd-1.9.16/plugins/vli/fu-vli-usbhub-rtd21xx-device.c000066400000000000000000000403031460375044200226200ustar00rootroot00000000000000/* * Copyright (C) 2017 VIA Corporation * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-vli-struct.h" #include "fu-vli-usbhub-rtd21xx-device.h" struct _FuVliUsbhubRtd21xxDevice { FuDevice parent_instance; }; G_DEFINE_TYPE(FuVliUsbhubRtd21xxDevice, fu_vli_usbhub_rtd21xx_device, FU_TYPE_DEVICE) #define I2C_WRITE_REQUEST 0xB2 #define I2C_READ_REQUEST 0xA5 #define I2C_DELAY_AFTER_SEND 5 /* ms */ #define UC_FOREGROUND_TARGET_ADDR 0x3A #define UC_FOREGROUND_STATUS 0x31 #define UC_FOREGROUND_OPCODE 0x33 #define UC_FOREGROUND_ISP_DATA_OPCODE 0x34 #define ISP_DATA_BLOCKSIZE 30 #define ISP_PACKET_SIZE 32 typedef enum { ISP_STATUS_BUSY = 0xBB, /* host must wait for device */ ISP_STATUS_IDLE_SUCCESS = 0x11, /* previous command was OK */ ISP_STATUS_IDLE_FAILURE = 0x12, /* previous command failed */ } IspStatus; typedef enum { ISP_CMD_ENTER_FW_UPDATE = 0x01, ISP_CMD_GET_PROJECT_ID_ADDR = 0x02, ISP_CMD_SYNC_IDENTIFY_CODE = 0x03, ISP_CMD_GET_FW_INFO = 0x04, ISP_CMD_FW_UPDATE_START = 0x05, ISP_CMD_FW_UPDATE_ISP_DONE = 0x06, ISP_CMD_FW_UPDATE_EXIT = 0x07, ISP_CMD_FW_UPDATE_RESET = 0x08, } IspCmd; static gboolean fu_vli_usbhub_device_i2c_write(FuVliUsbhubDevice *self, guint8 target_addr, guint8 sub_addr, guint8 *data, gsize datasz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); gsize bufsz = datasz + 2; g_autofree guint8 *buf = g_malloc0(bufsz); buf[0] = target_addr; buf[1] = sub_addr; if (!fu_memcpy_safe(buf, bufsz, 0x2, /* dst */ data, datasz, 0x0, /* src */ datasz, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "I2cWriteData", buf, datasz + 2); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, I2C_WRITE_REQUEST, 0x0000, 0x0000, buf, datasz + 2, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to write I2C @0x%02x:%02x: ", target_addr, sub_addr); return FALSE; } fu_device_sleep(FU_DEVICE(self), I2C_DELAY_AFTER_SEND); return TRUE; } static gboolean fu_vli_usbhub_device_i2c_read(FuVliUsbhubDevice *self, guint8 target_addr, guint8 sub_addr, guint8 *data, gsize datasz, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self)); if (!g_usb_device_control_transfer(usb_device, G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST, G_USB_DEVICE_REQUEST_TYPE_VENDOR, G_USB_DEVICE_RECIPIENT_DEVICE, I2C_READ_REQUEST, 0x0000, ((guint16)sub_addr << 8) + target_addr, data, datasz, NULL, FU_VLI_DEVICE_TIMEOUT, NULL, error)) { g_prefix_error(error, "failed to read I2C: "); return FALSE; } fu_dump_raw(G_LOG_DOMAIN, "I2cReadData", data, datasz); return TRUE; } static gboolean fu_vli_usbhub_device_rtd21xx_read_status_raw(FuVliUsbhubRtd21xxDevice *self, guint8 *status, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(FU_DEVICE(self))); guint8 buf[] = {0x00}; if (!fu_vli_usbhub_device_i2c_read(parent, UC_FOREGROUND_TARGET_ADDR, UC_FOREGROUND_STATUS, buf, sizeof(buf), error)) return FALSE; if (status != NULL) *status = buf[0]; return TRUE; } static gboolean fu_vli_usbhub_device_rtd21xx_read_status_cb(FuDevice *device, gpointer user_data, GError **error) { FuVliUsbhubRtd21xxDevice *self = FU_VLI_USBHUB_RTD21XX_DEVICE(device); guint8 status = 0xfd; if (!fu_vli_usbhub_device_rtd21xx_read_status_raw(self, &status, error)) return FALSE; if (status == ISP_STATUS_BUSY) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "status was 0x%02x", status); return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_device_rtd21xx_read_status(FuVliUsbhubRtd21xxDevice *self, guint8 *status, GError **error) { return fu_device_retry(FU_DEVICE(self), fu_vli_usbhub_device_rtd21xx_read_status_cb, 4200, status, error); } static gboolean fu_vli_usbhub_rtd21xx_ensure_version_unlocked(FuVliUsbhubRtd21xxDevice *self, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(FU_DEVICE(self))); guint8 buf_rep[7] = {0x00}; guint8 buf_req[] = {ISP_CMD_GET_FW_INFO}; g_autofree gchar *version = NULL; if (!fu_vli_usbhub_device_i2c_write(parent, UC_FOREGROUND_TARGET_ADDR, UC_FOREGROUND_OPCODE, buf_req, sizeof(buf_req), error)) { g_prefix_error(error, "failed to get version number: "); return FALSE; } /* wait for device ready */ fu_device_sleep(FU_DEVICE(self), 300); if (!fu_vli_usbhub_device_i2c_read(parent, UC_FOREGROUND_TARGET_ADDR, 0x00, buf_rep, sizeof(buf_rep), error)) { g_prefix_error(error, "failed to get version number: "); return FALSE; } /* set version */ version = g_strdup_printf("%u.%u", buf_rep[1], buf_rep[2]); fu_device_set_version(FU_DEVICE(self), version); return TRUE; } static gboolean fu_vli_usbhub_rtd21xx_device_setup(FuDevice *device, GError **error) { FuVliUsbhubRtd21xxDevice *self = FU_VLI_USBHUB_RTD21XX_DEVICE(device); g_autoptr(FuDeviceLocker) locker = NULL; /* get version */ locker = fu_device_locker_new_full(device, (FuDeviceLockerFunc)fu_device_detach, (FuDeviceLockerFunc)fu_device_attach, error); if (locker == NULL) return FALSE; if (!fu_vli_usbhub_rtd21xx_ensure_version_unlocked(self, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_vli_usbhub_device_rtd21xx_detach_raw(FuVliUsbhubRtd21xxDevice *self, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(FU_DEVICE(self))); guint8 buf[] = {0x03}; if (!fu_vli_usbhub_device_i2c_write(parent, 0x6A, 0x31, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to detach: "); return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_device_rtd21xx_detach_cb(FuDevice *device, gpointer user_data, GError **error) { FuVliUsbhubRtd21xxDevice *self = FU_VLI_USBHUB_RTD21XX_DEVICE(device); guint8 status = 0xfe; if (!fu_vli_usbhub_device_rtd21xx_detach_raw(self, error)) return FALSE; if (!fu_vli_usbhub_device_rtd21xx_read_status_raw(self, &status, error)) return FALSE; if (status != ISP_STATUS_IDLE_SUCCESS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "detach status was 0x%02x", status); return FALSE; } return TRUE; } static gboolean fu_vli_usbhub_rtd21xx_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; if (!fu_device_retry(device, fu_vli_usbhub_device_rtd21xx_detach_cb, 100, NULL, error)) return FALSE; /* success */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_vli_usbhub_rtd21xx_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); guint8 buf[] = {ISP_CMD_FW_UPDATE_RESET}; g_autoptr(FuDeviceLocker) locker = NULL; /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; if (!fu_vli_usbhub_device_i2c_write(parent, UC_FOREGROUND_TARGET_ADDR, UC_FOREGROUND_OPCODE, buf, sizeof(buf), error)) { g_prefix_error(error, "failed to attach: "); return FALSE; } /* success */ fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } static gboolean fu_vli_usbhub_rtd21xx_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); FuVliUsbhubRtd21xxDevice *self = FU_VLI_USBHUB_RTD21XX_DEVICE(device); const guint8 *fwbuf; gsize fwbufsz = 0; guint32 project_addr; guint8 project_id_count; guint8 read_buf[10] = {0}; guint8 write_buf[ISP_PACKET_SIZE] = {0}; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, "enable-isp"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 10, NULL); /* open device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; /* simple image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; fwbuf = g_bytes_get_data(fw, &fwbufsz); /* enable ISP high priority */ write_buf[0] = ISP_CMD_ENTER_FW_UPDATE; write_buf[1] = 0x01; if (!fu_vli_usbhub_device_i2c_write(parent, UC_FOREGROUND_TARGET_ADDR, UC_FOREGROUND_OPCODE, write_buf, 2, error)) { g_prefix_error(error, "failed to enable ISP: "); return FALSE; } if (!fu_vli_usbhub_device_rtd21xx_read_status(self, NULL, error)) return FALSE; /* get project ID address */ write_buf[0] = ISP_CMD_GET_PROJECT_ID_ADDR; if (!fu_vli_usbhub_device_i2c_write(parent, UC_FOREGROUND_TARGET_ADDR, UC_FOREGROUND_OPCODE, write_buf, 1, error)) { g_prefix_error(error, "failed to get project ID address: "); return FALSE; } /* read back 6 bytes data */ fu_device_sleep(device, I2C_DELAY_AFTER_SEND * 40); if (!fu_vli_usbhub_device_i2c_read(parent, UC_FOREGROUND_TARGET_ADDR, UC_FOREGROUND_STATUS, read_buf, 6, error)) { g_prefix_error(error, "failed to read project ID: "); return FALSE; } if (read_buf[0] != ISP_STATUS_IDLE_SUCCESS) { g_prefix_error(error, "failed project ID with error 0x%02x: ", read_buf[0]); return FALSE; } /* verify project ID */ if (!fu_memread_uint32_safe(read_buf, sizeof(read_buf), 0x1, &project_addr, G_BIG_ENDIAN, error)) return FALSE; project_id_count = read_buf[5]; write_buf[0] = ISP_CMD_SYNC_IDENTIFY_CODE; if (!fu_memcpy_safe(write_buf, sizeof(write_buf), 0x1, /* dst */ fwbuf, fwbufsz, project_addr, /* src */ project_id_count, error)) { g_prefix_error(error, "failed to write project ID from 0x%04x: ", project_addr); return FALSE; } if (!fu_vli_usbhub_device_i2c_write(parent, UC_FOREGROUND_TARGET_ADDR, UC_FOREGROUND_OPCODE, write_buf, project_id_count + 1, error)) { g_prefix_error(error, "failed to send fw update start cmd: "); return FALSE; } if (!fu_vli_usbhub_device_rtd21xx_read_status(self, NULL, error)) return FALSE; /* background FW update start command */ write_buf[0] = ISP_CMD_FW_UPDATE_START; fu_memwrite_uint16(write_buf + 1, ISP_DATA_BLOCKSIZE, G_BIG_ENDIAN); if (!fu_vli_usbhub_device_i2c_write(parent, UC_FOREGROUND_TARGET_ADDR, UC_FOREGROUND_OPCODE, write_buf, 3, error)) { g_prefix_error(error, "failed to send fw update start cmd: "); return FALSE; } fu_progress_step_done(progress); /* send data */ chunks = fu_chunk_array_new_from_bytes(fw, 0x00, ISP_DATA_BLOCKSIZE); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_vli_usbhub_device_rtd21xx_read_status(self, NULL, error)) return FALSE; if (!fu_vli_usbhub_device_i2c_write(parent, UC_FOREGROUND_TARGET_ADDR, UC_FOREGROUND_ISP_DATA_OPCODE, fu_chunk_get_data_out(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to write @0x%04x: ", fu_chunk_get_address(chk)); return FALSE; } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* update finish command */ if (!fu_vli_usbhub_device_rtd21xx_read_status(self, NULL, error)) return FALSE; write_buf[0] = ISP_CMD_FW_UPDATE_ISP_DONE; if (!fu_vli_usbhub_device_i2c_write(parent, UC_FOREGROUND_TARGET_ADDR, UC_FOREGROUND_OPCODE, write_buf, 1, error)) { g_prefix_error(error, "failed update finish cmd: "); return FALSE; } fu_progress_step_done(progress); /* exit background-fw mode */ if (!fu_vli_usbhub_device_rtd21xx_read_status(self, NULL, error)) return FALSE; write_buf[0] = ISP_CMD_FW_UPDATE_EXIT; if (!fu_vli_usbhub_device_i2c_write(parent, UC_FOREGROUND_TARGET_ADDR, UC_FOREGROUND_OPCODE, write_buf, 1, error)) { g_prefix_error(error, "FwUpdate exit: "); return FALSE; } /* the device needs some time to restart with the new firmware before * it can be queried again */ fu_device_sleep_full(device, 20000, progress); /* ms */ /* success */ fu_progress_step_done(progress); return TRUE; } static gboolean fu_vli_usbhub_rtd21xx_device_reload(FuDevice *device, GError **error) { FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); g_autoptr(FuDeviceLocker) locker = NULL; /* open parent device */ locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; return fu_vli_usbhub_rtd21xx_device_setup(device, error); } static gboolean fu_vli_usbhub_rtd21xx_device_probe(FuDevice *device, GError **error) { FuVliDeviceKind device_kind = FU_VLI_DEVICE_KIND_RTD21XX; FuVliUsbhubDevice *parent = FU_VLI_USBHUB_DEVICE(fu_device_get_parent(device)); fu_device_set_name(device, fu_vli_device_kind_to_string(device_kind)); fu_device_set_physical_id(device, fu_device_get_physical_id(FU_DEVICE(parent))); /* add instance ID */ fu_device_add_instance_str(device, "I2C", fu_vli_device_kind_to_string(device_kind)); return fu_device_build_instance_id(device, error, "USB", "VID", "PID", "I2C", NULL); } static void fu_vli_usbhub_rtd21xx_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_vli_usbhub_rtd21xx_device_init(FuVliUsbhubRtd21xxDevice *self) { fu_device_add_icon(FU_DEVICE(self), "video-display"); fu_device_add_protocol(FU_DEVICE(self), "com.vli.i2c"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_set_install_duration(FU_DEVICE(self), 100); /* seconds */ fu_device_set_logical_id(FU_DEVICE(self), "I2C"); fu_device_retry_set_delay(FU_DEVICE(self), 30); /* ms */ } static void fu_vli_usbhub_rtd21xx_device_class_init(FuVliUsbhubRtd21xxDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->probe = fu_vli_usbhub_rtd21xx_device_probe; klass_device->setup = fu_vli_usbhub_rtd21xx_device_setup; klass_device->reload = fu_vli_usbhub_rtd21xx_device_reload; klass_device->attach = fu_vli_usbhub_rtd21xx_device_attach; klass_device->detach = fu_vli_usbhub_rtd21xx_device_detach; klass_device->write_firmware = fu_vli_usbhub_rtd21xx_device_write_firmware; klass_device->set_progress = fu_vli_usbhub_rtd21xx_device_set_progress; } FuDevice * fu_vli_usbhub_rtd21xx_device_new(FuVliUsbhubDevice *parent) { FuVliUsbhubRtd21xxDevice *self = g_object_new(FU_TYPE_VLI_USBHUB_RTD21XX_DEVICE, "parent", parent, NULL); return FU_DEVICE(self); } fwupd-1.9.16/plugins/vli/fu-vli-usbhub-rtd21xx-device.h000066400000000000000000000007461460375044200226340ustar00rootroot00000000000000/* * Copyright (C) 2019 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-vli-usbhub-device.h" #define FU_TYPE_VLI_USBHUB_RTD21XX_DEVICE (fu_vli_usbhub_rtd21xx_device_get_type()) G_DECLARE_FINAL_TYPE(FuVliUsbhubRtd21xxDevice, fu_vli_usbhub_rtd21xx_device, FU, VLI_USBHUB_RTD21XX_DEVICE, FuDevice) FuDevice * fu_vli_usbhub_rtd21xx_device_new(FuVliUsbhubDevice *parent); fwupd-1.9.16/plugins/vli/fu-vli.rs000066400000000000000000000033411460375044200167660ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(Parse)] struct VliPdHdr { fwver: u32be, vid: u16le, pid: u16le, } #[derive(New, Parse, ToString)] struct VliUsbhubHdr { dev_id: u16be, strapping1: u8, strapping2: u8, usb3_fw_addr: u16be, usb3_fw_sz: u16be, usb2_fw_addr: u16be, usb2_fw_sz: u16be, usb3_fw_addr_high: u8, usb3_fw_sz_high: u8, _unknown_0e: [u8; 2], usb2_fw_addr_high: u8, _unknown_11: [u8; 10], inverse_pe41: u8, prev_ptr: u8, // addr / 0x20 next_ptr: u8, // addr / 0x20 variant: u8, checksum: u8, } #[derive(ToString, FromString)] enum VliDeviceKind { Unknown = 0x0, Vl100 = 0x0100, Vl101 = 0x0101, Vl102 = 0x0102, Vl103 = 0x0103, Vl104 = 0x0104, Vl105 = 0x0105, Vl106 = 0x0106, Vl107 = 0x0107, Vl108 = 0x0108, Vl109 = 0x0109, Vl120 = 0x0120, Vl122 = 0x0122, Vl210 = 0x0210, Vl211 = 0x0211, Vl212 = 0x0212, Vl650 = 0x0650, Vl810 = 0x0810, Vl811 = 0x0811, Vl811pb0 = 0x8110, Vl811pb3 = 0x8113, Vl812b0 = 0xa812, Vl812b3 = 0xb812, Vl812q4s = 0xc812, Vl813 = 0x0813, Vl815 = 0x0815, Vl817 = 0x0817, Vl817s = 0xa817, Vl819q7 = 0xa819, // guessed Vl819q8 = 0xb819, // guessed Vl820q7 = 0xa820, Vl820q8 = 0xb820, Vl821q7 = 0xa821, // guessed Vl821q8 = 0xb821, // guessed Vl822q5 = 0x0822, // guessed Vl822q7 = 0xa822, // guessed Vl822q8 = 0xb822, // guessed Vl822t = 0xc822, // guessed Vl822c0 = 0xd822, Vl830 = 0x0830, Vl832 = 0x0832, Msp430 = 0xf430, // guessed Ps186 = 0xf186, // guessed Rtd21xx = 0xff00, // guessed } fwupd-1.9.16/plugins/vli/meson.build000066400000000000000000000026501460375044200173620ustar00rootroot00000000000000if gusb.found() plugins += {meson.current_source_dir().split('/')[-1]: true} cargs = ['-DG_LOG_DOMAIN="FuPluginVliUsbhub"'] plugin_quirks += files([ 'vli-bizlink.quirk', 'vli-dell.quirk', 'vli-fujitsu.quirk', 'vli-goodway.quirk', 'vli-hyper.quirk', 'vli-lenovo.quirk', 'vli-luxshare.quirk', 'vli-samsung.quirk', ]) plugin_builtin_vli = static_library('fu_plugin_vli', rustgen.process('fu-vli.rs'), sources: [ 'fu-vli-plugin.c', 'fu-vli-common.c', 'fu-vli-device.c', 'fu-vli-pd-common.c', 'fu-vli-pd-device.c', 'fu-vli-pd-firmware.c', 'fu-vli-pd-parade-device.c', 'fu-vli-usbhub-device.c', 'fu-vli-usbhub-firmware.c', 'fu-vli-usbhub-i2c-common.c', 'fu-vli-usbhub-msp430-device.c', 'fu-vli-usbhub-pd-device.c', 'fu-vli-usbhub-rtd21xx-device.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) plugin_builtins += plugin_builtin_vli if get_option('tests') e = executable( 'vli-self-test', rustgen.process('fu-vli.rs'), sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_vli, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: cargs, ) test('vli-self-test', e, env: env) # added to installed-tests endif endif fwupd-1.9.16/plugins/vli/vli-bizlink.quirk000066400000000000000000000005741460375044200205320ustar00rootroot00000000000000# BizLink USB-C Cayenne [USB\VID_06C4&PID_C304] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd CounterpartGuid = USB\VID_06C4&PID_C303 [USB\VID_06C4&PID_C303] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_06C4&PID_C304 [USB\VID_06C4&PID_C305] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_06C4&PID_C304 fwupd-1.9.16/plugins/vli/vli-dell.quirk000066400000000000000000000001611460375044200200000ustar00rootroot00000000000000# Dell DA300 [USB\VID_2109&PID_0820&REV_3003] Flags = no-probe [USB\VID_2109&PID_2820&REV_3003] Flags = no-probe fwupd-1.9.16/plugins/vli/vli-fujitsu.quirk000066400000000000000000000015231460375044200205540ustar00rootroot00000000000000# Fujitsu B2711 Hub [USB\VID_0BF8&PID_105D] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 CounterpartGuid = USB\VID_0BF8&PID_105E [USB\VID_0BF8&PID_105E] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_0BF8&PID_105D # Fujitsu P2711 Multi-Hub [USB\VID_0BF8&PID_1051] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd CounterpartGuid = USB\VID_0BF8&PID_1052 [USB\VID_0BF8&PID_1052] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_0BF8&PID_1051 [USB\VID_0BF8&PID_1054] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 ParentGuid = USB\VID_0BF8&PID_1051 CounterpartGuid = USB\VID_0BF8&PID_1055 [USB\VID_0BF8&PID_1055] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_0BF8&PID_1051 # Fujitsu [USB\VID_0BF8&PID_103C] Plugin = vli GType = FuVliPDDevice fwupd-1.9.16/plugins/vli/vli-goodway.quirk000066400000000000000000000003661460375044200205400ustar00rootroot00000000000000# Goodway Minibons [USB\VID_065F&PID_F817] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 CounterpartGuid = USB\VID_065F&PID_F816 [USB\VID_065F&PID_F816] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_065F&PID_F817 fwupd-1.9.16/plugins/vli/vli-hyper.quirk000066400000000000000000000004071460375044200202120ustar00rootroot00000000000000# Hyper USB-C Hub [USB\VID_2D01&PID_0385] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd CounterpartGuid = USB\VID_2D01&PID_0382 [USB\VID_2D01&PID_0382] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_2D01&PID_0385 fwupd-1.9.16/plugins/vli/vli-lenovo.quirk000066400000000000000000000150621460375044200203700ustar00rootroot00000000000000# Lenovo CS18 Ultra Dock [USB\VID_17EF&PID_3070] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 CounterpartGuid = USB\VID_17EF&PID_3071 [USB\VID_17EF&PID_3072&HUB_0006] ParentGuid = USB\VID_17EF&PID_3072&HUB_0002 [USB\VID_17EF&PID_3071] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 [USB\VID_17EF&PID_3071&HUB_0002] ParentGuid = USB\VID_17EF&PID_3071&HUB_0006 # Lenovo CS18 Pro and Basic Dock [USB\VID_17EF&PID_3072] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 CounterpartGuid = USB\VID_17EF&PID_3073 [USB\VID_17EF&PID_3073] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 [USB\VID_17EF&PID_3073&HUB_0006] ParentGuid = USB\VID_17EF&PID_3073&HUB_0002 # Lenovo TR Dock [USB\VID_17EF&PID_307F&HUB_0002] ParentGuid = TBT-01081720 [USB\VID_17EF&PID_307F] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 CounterpartGuid = USB\VID_17EF&PID_3080 [USB\VID_17EF&PID_307F&HUB_0002] Flags = usb3,has-msp430 [USB\VID_17EF&PID_307F&HUB_0006] ParentGuid = USB\VID_17EF&PID_307F&HUB_0002 [USB\VID_17EF&PID_3080] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 [USB\VID_17EF&PID_3080&HUB_06] ParentGuid = USB\VID_17EF&PID_3080&HUB_20 [USB\VID_17EF&PID_3080&HUB_20] ParentGuid = TBT-01081720 # Lenovo CS13 KG Dock [USB\VID_17EF&PID_1010] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 # Lenovo CS13 GD Dock [USB\VID_17EF&PID_1012] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 # Lenovo CS13 MO Dock [USB\VID_17EF&PID_1013] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 # Lenovo USB3 Ultra Dock [USB\VID_17EF&PID_1014] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 [USB\VID_17EF&PID_1015] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 ParentGuid = USB\VID_17EF&PID_1014 # Lenovo USB3 Pro Dock [USB\VID_17EF&PID_1016] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 [USB\VID_17EF&PID_1018] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 ParentGuid = USB\VID_17EF&PID_1016 # Lenovo Workstation D40 [USB\VID_17EF&PID_1033] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 # Lenovo Workstation S40 [USB\VID_17EF&PID_1034] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 # Lenovo Workstation v40 [USB\VID_17EF&PID_1035] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 # Lenovo One Link Plus [USB\VID_17EF&PID_1018] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 [USB\VID_17EF&PID_1019] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,usb3 ParentGuid = USB\VID_17EF&PID_1018 # Lenovo Hybrid dock [USB\VID_17EF&PID_A356] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 CounterpartGuid = USB\VID_17EF&PID_1028 [USB\VID_17EF&PID_1028] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 [USB\VID_17EF&PID_A357] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 ParentGuid = USB\VID_17EF&PID_A356 CounterpartGuid = USB\VID_17EF&PID_1029 [USB\VID_17EF&PID_1029] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_17EF&PID_1028 # Lenovo Travel hub [USB\VID_17EF&PID_7216] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 CounterpartGuid = USB\VID_17EF&PID_7224 [USB\VID_17EF&PID_7224] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_17EF&PID_7216 # Lenovo Travel hub Gen2 [USB\VID_17EF&PID_721D] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd CounterpartGuid = USB\VID_17EF&PID_7225 [USB\VID_17EF&PID_7225] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_17EF&PID_721D [USB\VID_17EF&PID_721C] Plugin = vli GType = FuVliUsbhubDevice Flags = has-rtd21xx ParentGuid = USB\VID_17EF&PID_721D [USB\VID_17EF&PID_721C&I2C_REALTEK] Name = RTD2181S # Lenovo USB-C Mini dock [USB\VID_17EF&PID_3094] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd,attach-with-gpiob CounterpartGuid = USB\VID_17EF&PID_3095 [USB\VID_17EF&PID_3095] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2,attach-with-gpiob ParentGuid = USB\VID_17EF&PID_3094 [USB\VID_17EF&PID_3097] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_17EF&PID_3094 [USB\VID_17EF&PID_3093] Plugin = vli GType = FuVliUsbhubDevice Flags = has-rtd21xx ParentGuid = USB\VID_17EF&PID_3094 [USB\VID_17EF&PID_3093&I2C_REALTEK] Name = RTD2181S [USB\VID_17EF&PID_721C&APP_26] ProxyGuid = USB\VID_17EF&PID_3094 FirmwareSize = 0x8000 # Lenovo Travel Hub 1in3 [USB\VID_17EF&PID_7228] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 CounterpartGuid = USB\VID_17EF&PID_7236 [USB\VID_17EF&PID_7236] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_17EF&PID_7228 # Lenovo USB-C 7-in-1 Hub [USB\VID_17EF&PID_722A] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd CounterpartGuid = USB\VID_17EF&PID_7229 [USB\VID_17EF&PID_7229] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_17EF&PID_722A # Lenovo USB-C to 4 USB-A Hub [USB\VID_17EF&PID_1039] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd,attach-with-gpiob CounterpartGuid = USB\VID_17EF&PID_103A [USB\VID_17EF&PID_103A] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_17EF&PID_1039 # Lenovo Gen2 dock [USB\VID_17EF&PID_A391] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 CounterpartGuid = USB\VID_17EF&PID_A392 [USB\VID_17EF&PID_A392] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_17EF&PID_A391 [USB\VID_17EF&PID_A393] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 ParentGuid = USB\VID_17EF&PID_A391 CounterpartGuid = USB\VID_17EF&PID_A394 [USB\VID_17EF&PID_A394] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_17EF&PID_A392 [USB\VID_17EF&PID_A395] Plugin = vli GType = FuVliUsbhubDevice # Lenovo Hybrid Dock Version 2 [USB\VID_17EF&PID_30D2] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd CounterpartGuid = USB\VID_17EF&PID_30D3 [USB\VID_17EF&PID_30D3] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_17EF&PID_30D2 [USB\VID_17EF&PID_30D4] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 ParentGuid = USB\VID_17EF&PID_30D2 CounterpartGuid = USB\VID_17EF&PID_30D5 [USB\VID_17EF&PID_30D5] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_17EF&PID_30D2 # Lenovo VGA [USB\VID_17EF&PID_7211] Plugin = vli GType = FuVliPdDevice # Lenovo HDMI [USB\VID_17EF&PID_7212] Plugin = vli GType = FuVliPdDevice Flags = dual-image # Lenovo [USB\VID_17EF&PID_7215] Plugin = vli GType = FuVliPdDevice [USB\VID_17EF&PID_7217] Plugin = vli GType = FuVliPdDevice [USB\VID_17EF&PID_7223] Plugin = vli GType = FuVliPdDevice Flags = dual-image [USB\VID_17EF&PID_7247] Plugin = vli GType = FuVliPdDevice fwupd-1.9.16/plugins/vli/vli-luxshare.quirk000066400000000000000000000017541460375044200207240ustar00rootroot00000000000000# Luxshare Quad USB4 Dock [USB\VID_208E&PID_0830] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,attach-with-power CounterpartGuid = USB\VID_208E&PID_2830 [USB\VID_208E&PID_2830] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_208E&PID_0830 [USB\VID_208E&PID_0824] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 ParentGuid = USB\VID_208E&PID_0830 CounterpartGuid = USB\VID_208E&PID_2824 [USB\VID_208E&PID_2824] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_208E&PID_2830 # Luxshare 7-in-1 Hub [USB\VID_208E&PID_0822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3,has-shared-spi-pd CounterpartGuid = USB\VID_208E&PID_2822 [USB\VID_208E&PID_2822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 ParentGuid = USB\VID_208E&PID_0822 # VL105 PD Controller [USB\VID_208E&PID_0105] Plugin = vli GType = FuVliPdDevice Flags = skips-rom VliDeviceKind = vl105 [USB\VID_208E&PID_1105] Plugin = vli GType = FuVliPdDevice VliDeviceKind = vl105 fwupd-1.9.16/plugins/vli/vli-noinst.quirk000066400000000000000000000054561460375044200204060ustar00rootroot00000000000000# 3470_Class [USB\VID_2109&PID_0810] Plugin = vli GType = FuVliUsbhubDevice [USB\VID_2109&PID_0811] Plugin = vli GType = FuVliUsbhubDevice [USB\VID_2109&PID_0812] Plugin = vli GType = FuVliUsbhubDevice [USB\VID_2109&PID_0813] Plugin = vli GType = FuVliUsbhubDevice Flags = needs-unlock-legacy813 [USB\VID_2109&PID_8110] Plugin = vli GType = FuVliUsbhubDevice [USB\VID_2109&PID_8113] Plugin = vli GType = FuVliUsbhubDevice # 3507_Class [USB\VID_2109&PID_0210] Plugin = vli GType = FuVliUsbhubDevice # 3545_Class [USB\VID_2109&PID_0211] Plugin = vli GType = FuVliUsbhubDevice [USB\VID_2109&PID_2211] Plugin = vli GType = FuVliUsbhubDevice # VL817 [USB\VID_2109&PID_0817] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 [USB\VID_2109&PID_2817] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 # VL819 [USB\VID_2109&PID_0819] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 [USB\VID_2109&PID_2819] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 # VL820 [USB\VID_2109&PID_0820] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 [USB\VID_2109&PID_2820] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 # VL822 [USB\VID_2109&PID_0822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb3 [USB\VID_2109&PID_2822] Plugin = vli GType = FuVliUsbhubDevice Flags = usb2 # Generic PD VliDeviceKind = vl100 [USB\VID_2109&PID_0100] Plugin = vli GType = FuVliPdDevice VliDeviceKind = vl100 [USB\VID_2109&PID_0101] Plugin = vli GType = FuVliPdDevice VliDeviceKind = vl101 [USB\VID_2109&PID_0102] Plugin = vli GType = FuVliPdDevice VliDeviceKind = vl102 [USB\VID_2109&PID_0103] Plugin = vli GType = FuVliPdDevice Flags = dual-image VliDeviceKind = vl103 [USB\VID_2109&PID_0104] Plugin = vli GType = FuVliPdDevice VliDeviceKind = vl104 [USB\VID_2109&PID_0105] Plugin = vli GType = FuVliPdDevice VliDeviceKind = vl105 [USB\VID_2109&PID_0106] Plugin = vli GType = FuVliPdDevice VliDeviceKind = vl106 [USB\VID_2109&PID_0107] Plugin = vli GType = FuVliPdDevice VliDeviceKind = vl107 # Generic devices [USB\VID_2109&PID_D101] Plugin = vli GType = FuVliPdDevice [USB\VID_2109&PID_D102] Plugin = vli GType = FuVliPdDevice [USB\VID_2109&PID_8880] Plugin = vli GType = FuVliPdDevice # VL671,U3TT, VT3547 [USB\VID_2109&PID_8881] Plugin = vli GType = FuVliPdDevice [USB\VID_2109&PID_8882] Plugin = vli GType = FuVliPdDevice [USB\VID_2109&PID_8883] Plugin = vli GType = FuVliPdDevice # Realtek [USB\VID_2109&PID_8884] Plugin = vli GType = FuVliPdDevice # VL650, VT3555 [USB\VID_2109&PID_8885] Plugin = vli GType = FuVliPdDevice # MStar, VT3518 [USB\VID_2109&PID_8886] Plugin = vli GType = FuVliPdDevice [USB\VID_2109&PID_8887] Plugin = vli GType = FuVliPdDevice [USB\VID_2109&PID_8889] Plugin = vli GType = FuVliPdDevice # Novatek, VT3538 [USB\VID_2109&PID_888A] Plugin = vli GType = FuVliPdDevice [USB\VID_2109&PID_888B] Plugin = vli GType = FuVliPdDevice fwupd-1.9.16/plugins/vli/vli-samsung.quirk000066400000000000000000000002001460375044200205270ustar00rootroot00000000000000# Samsung [USB\VID_04E8&PID_A025] Plugin = vli GType = FuVliPdDevice [USB\VID_04E8&PID_A048] Plugin = vli GType = FuVliPdDevice fwupd-1.9.16/plugins/wacom-raw/000077500000000000000000000000001460375044200163205ustar00rootroot00000000000000fwupd-1.9.16/plugins/wacom-raw/README.md000066400000000000000000000034551460375044200176060ustar00rootroot00000000000000--- title: Plugin: Wacom RAW --- ## Introduction This plugin updates integrated Wacom AES and EMR devices. They are typically connected using I²C and not USB. ## GUID Generation The HID DeviceInstanceId values are used, e.g. `HIDRAW\VEN_056A&DEV_4875`. To recover panels that have been flashed with the wrong firmware version, the panel may have a parent device with GUID constructed from the EDID, e.g. `DRM\VEN_BOE&DEV_086E`. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in Intel HEX file format. This plugin supports the following protocol ID: * `com.wacom.raw` ## Quirk Use This plugin uses the following plugin-specific quirks: ### WacomI2cFlashBlockSize Block size to transfer firmware. Since: 1.2.4 ### WacomI2cFlashBaseAddr Base address for firmware. Since: 1.2.4 ### WacomI2cFlashSize Maximum size of the firmware zone. Since: 1.2.4 ### Flags:requires-wait-for-replug The device needs to replug into a bootloader mode. Since: 1.9.15 ## Update Behavior The device usually presents in runtime mode, but on detach re-enumerates with a different HIDRAW PID in a bootloader mode. On attach the device re-enumerates back to the runtime mode. For this reason the `REPLUG_MATCH_GUID` internal device flag is used so that the bootloader and runtime modes are treated as the same device. ## Vendor ID Security The vendor ID is set from the udev vendor, in this instance set to `HIDRAW:0x056A` ## External Interface Access This plugin requires ioctl `HIDIOCSFEATURE` access. ## Version Considerations This plugin has been available since fwupd version `1.2.4`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Tatsunosuke Tobita: @flying-elephant fwupd-1.9.16/plugins/wacom-raw/data/000077500000000000000000000000001460375044200172315ustar00rootroot00000000000000fwupd-1.9.16/plugins/wacom-raw/data/hid-recorder.txt000066400000000000000000001054561460375044200223540ustar00rootroot00000000000000# WCOM4875:00 056A:4875 # 0x05, 0x0d, // Usage Page (Digitizers) 0 # 0x09, 0x04, // Usage (Touch Screen) 2 # 0xa1, 0x01, // Collection (Application) 4 # 0x85, 0x0c, // Report ID (12) 6 # 0x95, 0x01, // Report Count (1) 8 # 0x75, 0x08, // Report Size (8) 10 # 0x26, 0xff, 0x00, // Logical Maximum (255) 12 # 0x15, 0x00, // Logical Minimum (0) 15 # 0x81, 0x03, // Input (Cnst,Var,Abs) 17 # 0x09, 0x54, // Usage (Contact Count) 19 # 0x81, 0x02, // Input (Data,Var,Abs) 21 # 0x05, 0x0d, // Usage Page (Digitizers) 23 # 0x09, 0x22, // Usage (Finger) 25 # 0xa1, 0x02, // Collection (Logical) 27 # 0x09, 0x42, // Usage (Tip Switch) 29 # 0x15, 0x00, // Logical Minimum (0) 31 # 0x25, 0x01, // Logical Maximum (1) 33 # 0x75, 0x01, // Report Size (1) 35 # 0x95, 0x01, // Report Count (1) 37 # 0x81, 0x02, // Input (Data,Var,Abs) 39 # 0x81, 0x03, // Input (Cnst,Var,Abs) 41 # 0x09, 0x47, // Usage (Confidence) 43 # 0x81, 0x02, // Input (Data,Var,Abs) 45 # 0x95, 0x05, // Report Count (5) 47 # 0x81, 0x03, // Input (Cnst,Var,Abs) 49 # 0x75, 0x10, // Report Size (16) 51 # 0x09, 0x51, // Usage (Contact Id) 53 # 0x95, 0x01, // Report Count (1) 55 # 0x81, 0x02, // Input (Data,Var,Abs) 57 # 0x05, 0x01, // Usage Page (Generic Desktop) 59 # 0x75, 0x10, // Report Size (16) 61 # 0x95, 0x01, // Report Count (1) 63 # 0x55, 0x0e, // Unit Exponent (-2) 65 # 0x65, 0x11, // Unit (Centimeter,SILinear) 67 # 0x09, 0x30, // Usage (X) 69 # 0x26, 0xc8, 0x35, // Logical Maximum (13768) 71 # 0x35, 0x00, // Physical Minimum (0) 74 # 0x46, 0x72, 0x0d, // Physical Maximum (3442) 76 # 0x81, 0x02, // Input (Data,Var,Abs) 79 # 0x46, 0x90, 0x07, // Physical Maximum (1936) 81 # 0x09, 0x31, // Usage (Y) 84 # 0x26, 0x40, 0x1e, // Logical Maximum (7744) 86 # 0x81, 0x02, // Input (Data,Var,Abs) 89 # 0xc0, // End Collection 91 # 0x05, 0x0d, // Usage Page (Digitizers) 92 # 0x09, 0x22, // Usage (Finger) 94 # 0xa1, 0x02, // Collection (Logical) 96 # 0x09, 0x42, // Usage (Tip Switch) 98 # 0x15, 0x00, // Logical Minimum (0) 100 # 0x25, 0x01, // Logical Maximum (1) 102 # 0x75, 0x01, // Report Size (1) 104 # 0x95, 0x01, // Report Count (1) 106 # 0x81, 0x02, // Input (Data,Var,Abs) 108 # 0x81, 0x03, // Input (Cnst,Var,Abs) 110 # 0x09, 0x47, // Usage (Confidence) 112 # 0x81, 0x02, // Input (Data,Var,Abs) 114 # 0x95, 0x05, // Report Count (5) 116 # 0x81, 0x03, // Input (Cnst,Var,Abs) 118 # 0x75, 0x10, // Report Size (16) 120 # 0x09, 0x51, // Usage (Contact Id) 122 # 0x95, 0x01, // Report Count (1) 124 # 0x81, 0x02, // Input (Data,Var,Abs) 126 # 0x05, 0x01, // Usage Page (Generic Desktop) 128 # 0x75, 0x10, // Report Size (16) 130 # 0x95, 0x01, // Report Count (1) 132 # 0x55, 0x0e, // Unit Exponent (-2) 134 # 0x65, 0x11, // Unit (Centimeter,SILinear) 136 # 0x09, 0x30, // Usage (X) 138 # 0x26, 0xc8, 0x35, // Logical Maximum (13768) 140 # 0x35, 0x00, // Physical Minimum (0) 143 # 0x46, 0x72, 0x0d, // Physical Maximum (3442) 145 # 0x81, 0x02, // Input (Data,Var,Abs) 148 # 0x46, 0x90, 0x07, // Physical Maximum (1936) 150 # 0x09, 0x31, // Usage (Y) 153 # 0x26, 0x40, 0x1e, // Logical Maximum (7744) 155 # 0x81, 0x02, // Input (Data,Var,Abs) 158 # 0xc0, // End Collection 160 # 0x05, 0x0d, // Usage Page (Digitizers) 161 # 0x09, 0x22, // Usage (Finger) 163 # 0xa1, 0x02, // Collection (Logical) 165 # 0x09, 0x42, // Usage (Tip Switch) 167 # 0x15, 0x00, // Logical Minimum (0) 169 # 0x25, 0x01, // Logical Maximum (1) 171 # 0x75, 0x01, // Report Size (1) 173 # 0x95, 0x01, // Report Count (1) 175 # 0x81, 0x02, // Input (Data,Var,Abs) 177 # 0x81, 0x03, // Input (Cnst,Var,Abs) 179 # 0x09, 0x47, // Usage (Confidence) 181 # 0x81, 0x02, // Input (Data,Var,Abs) 183 # 0x95, 0x05, // Report Count (5) 185 # 0x81, 0x03, // Input (Cnst,Var,Abs) 187 # 0x75, 0x10, // Report Size (16) 189 # 0x09, 0x51, // Usage (Contact Id) 191 # 0x95, 0x01, // Report Count (1) 193 # 0x81, 0x02, // Input (Data,Var,Abs) 195 # 0x05, 0x01, // Usage Page (Generic Desktop) 197 # 0x75, 0x10, // Report Size (16) 199 # 0x95, 0x01, // Report Count (1) 201 # 0x55, 0x0e, // Unit Exponent (-2) 203 # 0x65, 0x11, // Unit (Centimeter,SILinear) 205 # 0x09, 0x30, // Usage (X) 207 # 0x26, 0xc8, 0x35, // Logical Maximum (13768) 209 # 0x35, 0x00, // Physical Minimum (0) 212 # 0x46, 0x72, 0x0d, // Physical Maximum (3442) 214 # 0x81, 0x02, // Input (Data,Var,Abs) 217 # 0x46, 0x90, 0x07, // Physical Maximum (1936) 219 # 0x09, 0x31, // Usage (Y) 222 # 0x26, 0x40, 0x1e, // Logical Maximum (7744) 224 # 0x81, 0x02, // Input (Data,Var,Abs) 227 # 0xc0, // End Collection 229 # 0x05, 0x0d, // Usage Page (Digitizers) 230 # 0x09, 0x22, // Usage (Finger) 232 # 0xa1, 0x02, // Collection (Logical) 234 # 0x09, 0x42, // Usage (Tip Switch) 236 # 0x15, 0x00, // Logical Minimum (0) 238 # 0x25, 0x01, // Logical Maximum (1) 240 # 0x75, 0x01, // Report Size (1) 242 # 0x95, 0x01, // Report Count (1) 244 # 0x81, 0x02, // Input (Data,Var,Abs) 246 # 0x81, 0x03, // Input (Cnst,Var,Abs) 248 # 0x09, 0x47, // Usage (Confidence) 250 # 0x81, 0x02, // Input (Data,Var,Abs) 252 # 0x95, 0x05, // Report Count (5) 254 # 0x81, 0x03, // Input (Cnst,Var,Abs) 256 # 0x75, 0x10, // Report Size (16) 258 # 0x09, 0x51, // Usage (Contact Id) 260 # 0x95, 0x01, // Report Count (1) 262 # 0x81, 0x02, // Input (Data,Var,Abs) 264 # 0x05, 0x01, // Usage Page (Generic Desktop) 266 # 0x75, 0x10, // Report Size (16) 268 # 0x95, 0x01, // Report Count (1) 270 # 0x55, 0x0e, // Unit Exponent (-2) 272 # 0x65, 0x11, // Unit (Centimeter,SILinear) 274 # 0x09, 0x30, // Usage (X) 276 # 0x26, 0xc8, 0x35, // Logical Maximum (13768) 278 # 0x35, 0x00, // Physical Minimum (0) 281 # 0x46, 0x72, 0x0d, // Physical Maximum (3442) 283 # 0x81, 0x02, // Input (Data,Var,Abs) 286 # 0x46, 0x90, 0x07, // Physical Maximum (1936) 288 # 0x09, 0x31, // Usage (Y) 291 # 0x26, 0x40, 0x1e, // Logical Maximum (7744) 293 # 0x81, 0x02, // Input (Data,Var,Abs) 296 # 0xc0, // End Collection 298 # 0x05, 0x0d, // Usage Page (Digitizers) 299 # 0x09, 0x22, // Usage (Finger) 301 # 0xa1, 0x02, // Collection (Logical) 303 # 0x09, 0x42, // Usage (Tip Switch) 305 # 0x15, 0x00, // Logical Minimum (0) 307 # 0x25, 0x01, // Logical Maximum (1) 309 # 0x75, 0x01, // Report Size (1) 311 # 0x95, 0x01, // Report Count (1) 313 # 0x81, 0x02, // Input (Data,Var,Abs) 315 # 0x81, 0x03, // Input (Cnst,Var,Abs) 317 # 0x09, 0x47, // Usage (Confidence) 319 # 0x81, 0x02, // Input (Data,Var,Abs) 321 # 0x95, 0x05, // Report Count (5) 323 # 0x81, 0x03, // Input (Cnst,Var,Abs) 325 # 0x75, 0x10, // Report Size (16) 327 # 0x09, 0x51, // Usage (Contact Id) 329 # 0x95, 0x01, // Report Count (1) 331 # 0x81, 0x02, // Input (Data,Var,Abs) 333 # 0x05, 0x01, // Usage Page (Generic Desktop) 335 # 0x75, 0x10, // Report Size (16) 337 # 0x95, 0x01, // Report Count (1) 339 # 0x55, 0x0e, // Unit Exponent (-2) 341 # 0x65, 0x11, // Unit (Centimeter,SILinear) 343 # 0x09, 0x30, // Usage (X) 345 # 0x26, 0xc8, 0x35, // Logical Maximum (13768) 347 # 0x35, 0x00, // Physical Minimum (0) 350 # 0x46, 0x72, 0x0d, // Physical Maximum (3442) 352 # 0x81, 0x02, // Input (Data,Var,Abs) 355 # 0x46, 0x90, 0x07, // Physical Maximum (1936) 357 # 0x09, 0x31, // Usage (Y) 360 # 0x26, 0x40, 0x1e, // Logical Maximum (7744) 362 # 0x81, 0x02, // Input (Data,Var,Abs) 365 # 0xc0, // End Collection 367 # 0x05, 0x0d, // Usage Page (Digitizers) 368 # 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) 370 # 0x75, 0x10, // Report Size (16) 375 # 0x95, 0x01, // Report Count (1) 377 # 0x09, 0x56, // Usage (Scan Time) 379 # 0x81, 0x02, // Input (Data,Var,Abs) 381 # 0x85, 0x0c, // Report ID (12) 383 # 0x09, 0x55, // Usage (Contact Max) 385 # 0x75, 0x08, // Report Size (8) 387 # 0x95, 0x01, // Report Count (1) 389 # 0x26, 0xff, 0x00, // Logical Maximum (255) 391 # 0xb1, 0x02, // Feature (Data,Var,Abs) 394 # 0x85, 0x0a, // Report ID (10) 396 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 398 # 0x09, 0xc5, // Usage (Vendor Usage 0xc5) 401 # 0x96, 0x00, 0x01, // Report Count (256) 403 # 0xb1, 0x02, // Feature (Data,Var,Abs) 406 # 0xc0, // End Collection 408 # 0x06, 0x11, 0xff, // Usage Page (Vendor Usage Page 0xff11) 409 # 0x09, 0x11, // Usage (Vendor Usage 0x11) 412 # 0xa1, 0x01, // Collection (Application) 414 # 0x85, 0x03, // Report ID (3) 416 # 0xa1, 0x02, // Collection (Logical) 418 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 420 # 0x75, 0x08, // Report Size (8) 422 # 0x15, 0x00, // Logical Minimum (0) 424 # 0x26, 0xff, 0x00, // Logical Maximum (255) 426 # 0x95, 0x27, // Report Count (39) 429 # 0x81, 0x02, // Input (Data,Var,Abs) 431 # 0xc0, // End Collection 433 # 0x85, 0x02, // Report ID (2) 434 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 436 # 0x95, 0x01, // Report Count (1) 438 # 0xb1, 0x02, // Feature (Data,Var,Abs) 440 # 0x85, 0x03, // Report ID (3) 442 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 444 # 0x95, 0x3f, // Report Count (63) 446 # 0xb1, 0x02, // Feature (Data,Var,Abs) 448 # 0x85, 0x04, // Report ID (4) 450 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 452 # 0x95, 0x0f, // Report Count (15) 454 # 0xb1, 0x02, // Feature (Data,Var,Abs) 456 # 0x85, 0x07, // Report ID (7) 458 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 460 # 0x96, 0x00, 0x01, // Report Count (256) 462 # 0xb1, 0x02, // Feature (Data,Var,Abs) 465 # 0x85, 0x08, // Report ID (8) 467 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 469 # 0x96, 0x87, 0x00, // Report Count (135) 471 # 0xb1, 0x02, // Feature (Data,Var,Abs) 474 # 0x85, 0x09, // Report ID (9) 476 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 478 # 0x96, 0x3f, 0x00, // Report Count (63) 480 # 0xb1, 0x02, // Feature (Data,Var,Abs) 483 # 0x85, 0x0d, // Report ID (13) 485 # 0x09, 0x00, // Usage (Vendor Usage 0x00) 487 # 0x95, 0x07, // Report Count (7) 489 # 0xb1, 0x02, // Feature (Data,Var,Abs) 491 # 0xc0, // End Collection 493 # 0x05, 0x0d, // Usage Page (Digitizers) 494 # 0x09, 0x0e, // Usage (Device Configuration) 496 # 0xa1, 0x01, // Collection (Application) 498 # 0x85, 0x0e, // Report ID (14) 500 # 0x09, 0x23, // Usage (Device Settings) 502 # 0xa1, 0x02, // Collection (Logical) 504 # 0x09, 0x52, // Usage (Inputmode) 506 # 0x09, 0x53, // Usage (Device Index) 508 # 0x15, 0x00, // Logical Minimum (0) 510 # 0x25, 0x0a, // Logical Maximum (10) 512 # 0x75, 0x08, // Report Size (8) 514 # 0x95, 0x02, // Report Count (2) 516 # 0xb1, 0x02, // Feature (Data,Var,Abs) 518 # 0xc0, // End Collection 520 # 0xc0, // End Collection 521 # 0x05, 0x0d, // Usage Page (Digitizers) 522 # 0x09, 0x02, // Usage (Pen) 524 # 0xa1, 0x01, // Collection (Application) 526 # 0x85, 0x06, // Report ID (6) 528 # 0xa4, // Push 530 # 0x09, 0x20, // Usage (Stylus) 531 # 0xa1, 0x00, // Collection (Physical) 533 # 0x09, 0x42, // Usage (Tip Switch) 535 # 0x09, 0x44, // Usage (Barrel Switch) 537 # 0x09, 0x45, // Usage (Eraser) 539 # 0x09, 0x3c, // Usage (Invert) 541 # 0x09, 0x5a, // Usage (Secondary Barrel Switch) 543 # 0x09, 0x32, // Usage (In Range) 545 # 0x15, 0x00, // Logical Minimum (0) 547 # 0x25, 0x01, // Logical Maximum (1) 549 # 0x75, 0x01, // Report Size (1) 551 # 0x95, 0x06, // Report Count (6) 553 # 0x81, 0x02, // Input (Data,Var,Abs) 555 # 0x95, 0x02, // Report Count (2) 557 # 0x81, 0x03, // Input (Cnst,Var,Abs) 559 # 0x05, 0x01, // Usage Page (Generic Desktop) 561 # 0x09, 0x30, // Usage (X) 563 # 0x27, 0x70, 0x86, 0x00, 0x00, // Logical Maximum (34416) 565 # 0x47, 0x70, 0x86, 0x00, 0x00, // Physical Maximum (34416) 570 # 0x65, 0x11, // Unit (Centimeter,SILinear) 575 # 0x55, 0x0d, // Unit Exponent (-3) 577 # 0x75, 0x10, // Report Size (16) 579 # 0x95, 0x01, // Report Count (1) 581 # 0x81, 0x02, // Input (Data,Var,Abs) 583 # 0x09, 0x31, // Usage (Y) 585 # 0x27, 0x9f, 0x4b, 0x00, 0x00, // Logical Maximum (19359) 587 # 0x47, 0x9f, 0x4b, 0x00, 0x00, // Physical Maximum (19359) 592 # 0x81, 0x02, // Input (Data,Var,Abs) 597 # 0x45, 0x00, // Physical Maximum (0) 599 # 0x65, 0x00, // Unit (None) 601 # 0x55, 0x00, // Unit Exponent (0) 603 # 0x05, 0x0d, // Usage Page (Digitizers) 605 # 0x09, 0x30, // Usage (Tip Pressure) 607 # 0x26, 0xff, 0x0f, // Logical Maximum (4095) 609 # 0x75, 0x10, // Report Size (16) 612 # 0x81, 0x02, // Input (Data,Var,Abs) 614 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 616 # 0x09, 0x5b, // Usage (Vendor Usage 0x5b) 619 # 0x16, 0x00, 0x80, // Logical Minimum (-32768) 621 # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 624 # 0x75, 0x10, // Report Size (16) 627 # 0x81, 0x02, // Input (Data,Var,Abs) 629 # 0x05, 0x0d, // Usage Page (Digitizers) 631 # 0x09, 0x5b, // Usage (Transducer Serial Number) 633 # 0x17, 0x00, 0x00, 0x00, 0x80, // Logical Minimum (-2147483648) 635 # 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 640 # 0x75, 0x20, // Report Size (32) 645 # 0x81, 0x02, // Input (Data,Var,Abs) 647 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 649 # 0x09, 0x00, // Usage (Undefined) 652 # 0x75, 0x08, // Report Size (8) 654 # 0x26, 0xff, 0x00, // Logical Maximum (255) 656 # 0x15, 0x00, // Logical Minimum (0) 659 # 0x81, 0x02, // Input (Data,Var,Abs) 661 # 0x05, 0x0d, // Usage Page (Digitizers) 663 # 0x09, 0x3b, // Usage (Battery Strength) 665 # 0x81, 0x02, // Input (Data,Var,Abs) 667 # 0x65, 0x14, // Unit (Degrees,EngRotation) 669 # 0x55, 0x00, // Unit Exponent (0) 671 # 0x16, 0xa6, 0xff, // Logical Minimum (-90) 673 # 0x26, 0x5a, 0x00, // Logical Maximum (90) 676 # 0x36, 0xa6, 0xff, // Physical Minimum (-90) 679 # 0x46, 0x5a, 0x00, // Physical Maximum (90) 682 # 0x75, 0x08, // Report Size (8) 685 # 0x09, 0x3d, // Usage (X Tilt) 687 # 0x81, 0x02, // Input (Data,Var,Abs) 689 # 0x09, 0x3e, // Usage (Y Tilt) 691 # 0x81, 0x02, // Input (Data,Var,Abs) 693 # 0xc0, // End Collection 695 # 0xb4, // Pop 696 # 0x85, 0x13, // Report ID (19) 697 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 699 # 0x09, 0xc5, // Usage (Vendor Usage 0xc5) 702 # 0x96, 0x00, 0x01, // Report Count (256) 704 # 0xb1, 0x02, // Feature (Data,Var,Abs) 707 # 0xc0, // End Collection 709 # 0x06, 0x11, 0xff, // Usage Page (Vendor Usage Page 0xff11) 710 # 0x09, 0x02, // Usage (Vendor Usage 0x02) 713 # 0xa1, 0x01, // Collection (Application) 715 # 0x85, 0x0b, // Report ID (11) 717 # 0xa4, // Push 719 # 0x09, 0x20, // Usage (Vendor Usage 0x20) 720 # 0xa1, 0x00, // Collection (Physical) 722 # 0x09, 0x42, // Usage (Vendor Usage 0x42) 724 # 0x09, 0x44, // Usage (Vendor Usage 0x44) 726 # 0x09, 0x45, // Usage (Vendor Usage 0x45) 728 # 0x09, 0x3c, // Usage (Vendor Usage 0x3c) 730 # 0x09, 0x5a, // Usage (Vendor Usage 0x5a) 732 # 0x09, 0x32, // Usage (Vendor Usage 0x32) 734 # 0x15, 0x00, // Logical Minimum (0) 736 # 0x25, 0x01, // Logical Maximum (1) 738 # 0x75, 0x01, // Report Size (1) 740 # 0x95, 0x06, // Report Count (6) 742 # 0x81, 0x02, // Input (Data,Var,Abs) 744 # 0x95, 0x02, // Report Count (2) 746 # 0x81, 0x03, // Input (Cnst,Var,Abs) 748 # 0x05, 0x01, // Usage Page (Generic Desktop) 750 # 0x09, 0x30, // Usage (X) 752 # 0x27, 0x70, 0x86, 0x00, 0x00, // Logical Maximum (34416) 754 # 0x47, 0x70, 0x86, 0x00, 0x00, // Physical Maximum (34416) 759 # 0x65, 0x11, // Unit (Centimeter,SILinear) 764 # 0x55, 0x0d, // Unit Exponent (-3) 766 # 0x75, 0x10, // Report Size (16) 768 # 0x95, 0x01, // Report Count (1) 770 # 0x81, 0x02, // Input (Data,Var,Abs) 772 # 0x09, 0x31, // Usage (Y) 774 # 0x27, 0x9f, 0x4b, 0x00, 0x00, // Logical Maximum (19359) 776 # 0x47, 0x9f, 0x4b, 0x00, 0x00, // Physical Maximum (19359) 781 # 0x81, 0x02, // Input (Data,Var,Abs) 786 # 0x45, 0x00, // Physical Maximum (0) 788 # 0x65, 0x00, // Unit (None) 790 # 0x55, 0x00, // Unit Exponent (0) 792 # 0x05, 0x0d, // Usage Page (Digitizers) 794 # 0x09, 0x30, // Usage (Tip Pressure) 796 # 0x26, 0xff, 0x0f, // Logical Maximum (4095) 798 # 0x75, 0x10, // Report Size (16) 801 # 0x81, 0x02, // Input (Data,Var,Abs) 803 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 805 # 0x09, 0x5b, // Usage (Vendor Usage 0x5b) 808 # 0x16, 0x00, 0x80, // Logical Minimum (-32768) 810 # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 813 # 0x75, 0x10, // Report Size (16) 816 # 0x81, 0x02, // Input (Data,Var,Abs) 818 # 0x05, 0x0d, // Usage Page (Digitizers) 820 # 0x09, 0x5b, // Usage (Transducer Serial Number) 822 # 0x17, 0x00, 0x00, 0x00, 0x80, // Logical Minimum (-2147483648) 824 # 0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 829 # 0x75, 0x20, // Report Size (32) 834 # 0x81, 0x02, // Input (Data,Var,Abs) 836 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 838 # 0x09, 0x00, // Usage (Undefined) 841 # 0x75, 0x08, // Report Size (8) 843 # 0x26, 0xff, 0x00, // Logical Maximum (255) 845 # 0x15, 0x00, // Logical Minimum (0) 848 # 0x81, 0x02, // Input (Data,Var,Abs) 850 # 0x05, 0x0d, // Usage Page (Digitizers) 852 # 0x09, 0x3b, // Usage (Battery Strength) 854 # 0x81, 0x02, // Input (Data,Var,Abs) 856 # 0x65, 0x14, // Unit (Degrees,EngRotation) 858 # 0x55, 0x00, // Unit Exponent (0) 860 # 0x16, 0xa6, 0xff, // Logical Minimum (-90) 862 # 0x26, 0x5a, 0x00, // Logical Maximum (90) 865 # 0x36, 0xa6, 0xff, // Physical Minimum (-90) 868 # 0x46, 0x5a, 0x00, // Physical Maximum (90) 871 # 0x75, 0x08, // Report Size (8) 874 # 0x09, 0x3d, // Usage (X Tilt) 876 # 0x81, 0x02, // Input (Data,Var,Abs) 878 # 0x09, 0x3e, // Usage (Y Tilt) 880 # 0x81, 0x02, // Input (Data,Var,Abs) 882 # 0xc0, // End Collection 884 # 0xb4, // Pop 885 # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 886 # 0x75, 0x08, // Report Size (8) 889 # 0x15, 0x00, // Logical Minimum (0) 891 # 0x26, 0xff, 0x00, // Logical Maximum (255) 893 # 0x85, 0x05, // Report ID (5) 896 # 0x09, 0x00, // Usage (Undefined) 898 # 0x95, 0x3a, // Report Count (58) 900 # 0x81, 0x02, // Input (Data,Var,Abs) 902 # 0x85, 0x10, // Report ID (16) 904 # 0x09, 0x00, // Usage (Undefined) 906 # 0x95, 0x14, // Report Count (20) 908 # 0x81, 0x02, // Input (Data,Var,Abs) 910 # 0x85, 0x0f, // Report ID (15) 912 # 0x09, 0x00, // Usage (Undefined) 914 # 0x95, 0x28, // Report Count (40) 916 # 0x81, 0x02, // Input (Data,Var,Abs) 918 # 0x85, 0x0f, // Report ID (15) 920 # 0x09, 0x00, // Usage (Undefined) 922 # 0x95, 0x07, // Report Count (7) 924 # 0xb1, 0x02, // Feature (Data,Var,Abs) 926 # 0x85, 0x11, // Report ID (17) 928 # 0x09, 0x00, // Usage (Undefined) 930 # 0x95, 0x09, // Report Count (9) 932 # 0xb1, 0x02, // Feature (Data,Var,Abs) 934 # 0x85, 0x05, // Report ID (5) 936 # 0x09, 0x00, // Usage (Undefined) 938 # 0x95, 0x08, // Report Count (8) 940 # 0xb1, 0x02, // Feature (Data,Var,Abs) 942 # 0x85, 0x10, // Report ID (16) 944 # 0x09, 0x00, // Usage (Undefined) 946 # 0x96, 0x3f, 0x00, // Report Count (63) 948 # 0xb1, 0x02, // Feature (Data,Var,Abs) 951 # 0x85, 0x0b, // Report ID (11) 953 # 0x09, 0x00, // Usage (Undefined) 955 # 0x96, 0x3f, 0x00, // Report Count (63) 957 # 0xb1, 0x02, // Feature (Data,Var,Abs) 960 # 0xc0, // End Collection 962 # 0x05, 0x01, // Usage Page (Generic Desktop) 963 # 0x09, 0x02, // Usage (Mouse) 965 # 0xa1, 0x01, // Collection (Application) 967 # 0x85, 0x01, // Report ID (1) 969 # 0x09, 0x01, // Usage (Pointer) 971 # 0xa1, 0x00, // Collection (Physical) 973 # 0x05, 0x09, // Usage Page (Button) 975 # 0x19, 0x01, // Usage Minimum (1) 977 # 0x29, 0x02, // Usage Maximum (2) 979 # 0x15, 0x00, // Logical Minimum (0) 981 # 0x25, 0x01, // Logical Maximum (1) 983 # 0x95, 0x02, // Report Count (2) 985 # 0x75, 0x01, // Report Size (1) 987 # 0x81, 0x02, // Input (Data,Var,Abs) 989 # 0x95, 0x01, // Report Count (1) 991 # 0x75, 0x06, // Report Size (6) 993 # 0x81, 0x03, // Input (Cnst,Var,Abs) 995 # 0x05, 0x01, // Usage Page (Generic Desktop) 997 # 0x09, 0x30, // Usage (X) 999 # 0x09, 0x31, // Usage (Y) 1001 # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 1003 # 0x75, 0x10, // Report Size (16) 1006 # 0x95, 0x02, // Report Count (2) 1008 # 0x81, 0x02, // Input (Data,Var,Abs) 1010 # 0xc0, // End Collection 1012 # 0xc0, // End Collection 1013 fwupd-1.9.16/plugins/wacom-raw/fu-wacom-aes-device.c000066400000000000000000000202431460375044200222060ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-wacom-aes-device.h" #include "fu-wacom-common.h" typedef struct __attribute__((packed)) { guint8 report_id; guint8 cmd; guint8 echo; guint32 addr; guint8 size8; guint8 data[128]; } FuWacomRawVerifyResponse; struct _FuWacomAesDevice { FuWacomDevice parent_instance; }; G_DEFINE_TYPE(FuWacomAesDevice, fu_wacom_aes_device, FU_TYPE_WACOM_DEVICE) static gboolean fu_wacom_aes_add_recovery_hwid(FuDevice *device, GError **error) { FuWacomRawRequest cmd = { .report_id = FU_WACOM_RAW_BL_REPORT_ID_SET, .cmd = FU_WACOM_RAW_BL_CMD_VERIFY_FLASH, .echo = 0x01, .addr = FU_WACOM_RAW_BL_START_ADDR, .size8 = FU_WACOM_RAW_BL_BYTES_CHECK / 8, }; FuWacomRawVerifyResponse rsp = {.report_id = FU_WACOM_RAW_BL_REPORT_ID_GET, .size8 = 0x00, .data = {0x00}}; guint16 pid; if (!fu_wacom_device_set_feature(FU_WACOM_DEVICE(device), (guint8 *)&cmd, sizeof(cmd), error)) { g_prefix_error(error, "failed to send: "); return FALSE; } if (!fu_wacom_device_get_feature(FU_WACOM_DEVICE(device), (guint8 *)&rsp, sizeof(rsp), error)) { g_prefix_error(error, "failed to receive: "); return FALSE; } if (rsp.size8 != cmd.size8) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "firmware does not support this feature"); return FALSE; } pid = (rsp.data[7] << 8) + (rsp.data[6]); if ((pid == 0xFFFF) || (pid == 0x0000)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "invalid recovery product ID %04x", pid); return FALSE; } /* add recovery IDs */ fu_device_add_instance_u16(device, "VEN", 0x2D1F); fu_device_add_instance_u16(device, "DEV", pid); if (!fu_device_build_instance_id(device, error, "HIDRAW", "VID", "PID", NULL)) return FALSE; fu_device_add_instance_u16(device, "VEN", 0x056A); return fu_device_build_instance_id(device, error, "HIDRAW", "VID", "PID", NULL); } static gboolean fu_wacom_aes_query_operation_mode(FuWacomAesDevice *self, GError **error) { guint8 buf[FU_WACOM_RAW_FW_REPORT_SZ] = { FU_WACOM_RAW_FW_REPORT_ID, FU_WACOM_RAW_FW_CMD_QUERY_MODE, }; /* 0x00=runtime, 0x02=bootloader */ if (!fu_wacom_device_get_feature(FU_WACOM_DEVICE(self), buf, sizeof(buf), error)) return FALSE; if (buf[1] == 0x00) { fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } if (buf[1] == 0x02) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); return TRUE; } /* unsupported */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to query operation mode, got 0x%x", buf[1]); return FALSE; } static gboolean fu_wacom_aes_device_setup(FuDevice *device, GError **error) { FuWacomAesDevice *self = FU_WACOM_AES_DEVICE(device); g_autoptr(GError) error_local = NULL; /* find out if in bootloader mode already */ if (!fu_wacom_aes_query_operation_mode(self, error)) return FALSE; /* get firmware version */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { fu_device_set_version(device, "0.0"); /* get the recovery PID if supported */ if (!fu_wacom_aes_add_recovery_hwid(device, &error_local)) g_debug("failed to get HwID: %s", error_local->message); } else { guint16 fw_ver; guint8 data[FU_WACOM_RAW_STATUS_REPORT_SZ] = {FU_WACOM_RAW_STATUS_REPORT_ID, 0x0}; g_autofree gchar *version = NULL; if (!fu_wacom_device_get_feature(FU_WACOM_DEVICE(self), data, sizeof(data), error)) return FALSE; if (!fu_memread_uint16_safe(data, sizeof(data), 11, &fw_ver, G_LITTLE_ENDIAN, error)) return FALSE; version = g_strdup_printf("%04x.%02x", fw_ver, data[13]); fu_device_set_version(device, version); } /* success */ return TRUE; } static gboolean fu_wacom_aes_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuWacomDevice *self = FU_WACOM_DEVICE(device); FuWacomRawRequest req = {.report_id = FU_WACOM_RAW_BL_REPORT_ID_TYPE, .cmd = FU_WACOM_RAW_BL_TYPE_FINALIZER, 0x00}; if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } if (!fu_wacom_device_set_feature(self, (const guint8 *)&req, sizeof(req), error)) { g_prefix_error(error, "failed to finalize the device: "); return FALSE; } /* does the device have to replug to bootloader mode */ if (fu_device_has_private_flag(device, FU_WACOM_RAW_DEVICE_FLAG_REQUIRES_WAIT_FOR_REPLUG)) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } else { /* wait for device back to runtime mode */ fu_device_sleep(device, 500); /* ms */ fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } /* success */ return TRUE; } static gboolean fu_wacom_aes_device_erase_all(FuWacomAesDevice *self, FuProgress *progress, GError **error) { FuWacomRawRequest req = {.cmd = FU_WACOM_RAW_BL_CMD_ALL_ERASE, .echo = FU_WACOM_RAW_ECHO_DEFAULT, 0x00}; FuWacomRawResponse rsp = {0x00}; if (!fu_wacom_device_cmd(FU_WACOM_DEVICE(self), &req, &rsp, 2000, /* this takes a long time */ FU_WACOM_DEVICE_CMD_FLAG_POLL_ON_WAITING, error)) { g_prefix_error(error, "failed to send eraseall command: "); return FALSE; } fu_device_sleep_full(FU_DEVICE(self), 2000, progress); return TRUE; } static gboolean fu_wacom_aes_device_write_block(FuWacomAesDevice *self, guint32 idx, guint32 address, const guint8 *data, gsize datasz, GError **error) { gsize blocksz = fu_wacom_device_get_block_sz(FU_WACOM_DEVICE(self)); FuWacomRawRequest req = { .cmd = FU_WACOM_RAW_BL_CMD_WRITE_FLASH, .echo = (guint8)idx + 1, .addr = GUINT32_TO_LE(address), .size8 = datasz / 8, .data = {0x00}, }; FuWacomRawResponse rsp = {0x00}; /* check size */ if (datasz != blocksz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "block size 0x%x != 0x%x untested", (guint)datasz, (guint)blocksz); return FALSE; } if (!fu_memcpy_safe((guint8 *)&req.data, sizeof(req.data), 0x0, /* dst */ data, datasz, 0x0, /* src */ datasz, error)) return FALSE; /* write */ if (!fu_wacom_device_cmd(FU_WACOM_DEVICE(self), &req, &rsp, 1, /* ms */ FU_WACOM_DEVICE_CMD_FLAG_POLL_ON_WAITING, error)) { g_prefix_error(error, "failed to write block %u: ", idx); return FALSE; } return TRUE; } static gboolean fu_wacom_aes_device_write_firmware(FuDevice *device, FuChunkArray *chunks, FuProgress *progress, GError **error) { FuWacomAesDevice *self = FU_WACOM_AES_DEVICE(device); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 28, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 72, NULL); /* erase */ if (!fu_wacom_aes_device_erase_all(self, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* write */ for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_wacom_aes_device_write_block(self, fu_chunk_get_idx(chk), fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); return TRUE; } static void fu_wacom_aes_device_init(FuWacomAesDevice *self) { fu_device_set_name(FU_DEVICE(self), "Wacom AES Device"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); } static void fu_wacom_aes_device_class_init(FuWacomAesDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); FuWacomDeviceClass *klass_wac_device = FU_WACOM_DEVICE_CLASS(klass); klass_device->setup = fu_wacom_aes_device_setup; klass_device->attach = fu_wacom_aes_device_attach; klass_wac_device->write_firmware = fu_wacom_aes_device_write_firmware; } fwupd-1.9.16/plugins/wacom-raw/fu-wacom-aes-device.h000066400000000000000000000004731460375044200222160ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wacom-device.h" #define FU_TYPE_WACOM_AES_DEVICE (fu_wacom_aes_device_get_type()) G_DECLARE_FINAL_TYPE(FuWacomAesDevice, fu_wacom_aes_device, FU, WACOM_AES_DEVICE, FuWacomDevice) fwupd-1.9.16/plugins/wacom-raw/fu-wacom-common.c000066400000000000000000000043571460375044200215010ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wacom-common.h" gboolean fu_wacom_common_check_reply(const FuWacomRawRequest *req, const FuWacomRawResponse *rsp, GError **error) { if (rsp->report_id != FU_WACOM_RAW_BL_REPORT_ID_GET) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "report ID failed, expected 0x%02x, got 0x%02x", (guint)FU_WACOM_RAW_BL_REPORT_ID_GET, req->report_id); return FALSE; } if (req->cmd != rsp->cmd) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cmd failed, expected 0x%02x, got 0x%02x", req->cmd, rsp->cmd); return FALSE; } if (req->echo != rsp->echo) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "echo failed, expected 0x%02x, got 0x%02x", req->echo, rsp->echo); return FALSE; } return TRUE; } gboolean fu_wacom_common_rc_set_error(const FuWacomRawResponse *rsp, GError **error) { if (rsp->resp == FU_WACOM_RAW_RC_OK) return TRUE; if (rsp->resp == FU_WACOM_RAW_RC_BUSY) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "device is busy"); return FALSE; } if (rsp->resp == FU_WACOM_RAW_RC_MCUTYPE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "MCU type does not match"); return FALSE; } if (rsp->resp == FU_WACOM_RAW_RC_PID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "PID does not match"); return FALSE; } if (rsp->resp == FU_WACOM_RAW_RC_CHECKSUM1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum1 does not match"); return FALSE; } if (rsp->resp == FU_WACOM_RAW_RC_CHECKSUM2) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "checksum2 does not match"); return FALSE; } if (rsp->resp == FU_WACOM_RAW_RC_TIMEOUT) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "command timed out"); return FALSE; } g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "unknown error 0x%02x", rsp->resp); return FALSE; } gboolean fu_wacom_common_block_is_empty(const guint8 *data, guint16 datasz) { for (guint16 i = 0; i < datasz; i++) { if (data[i] != 0xff) return FALSE; } return TRUE; } fwupd-1.9.16/plugins/wacom-raw/fu-wacom-common.h000066400000000000000000000041221460375044200214740ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_WACOM_RAW_CMD_RETRIES 1000 #define FU_WACOM_RAW_STATUS_REPORT_ID 0x04 #define FU_WACOM_RAW_STATUS_REPORT_SZ 16 #define FU_WACOM_RAW_FW_REPORT_ID 0x02 #define FU_WACOM_RAW_FW_CMD_QUERY_MODE 0x00 #define FU_WACOM_RAW_FW_CMD_DETACH 0x02 #define FU_WACOM_RAW_FW_REPORT_SZ 2 #define FU_WACOM_RAW_BL_START_ADDR (0x11FF8) #define FU_WACOM_RAW_BL_BYTES_CHECK 8 #define FU_WACOM_RAW_BL_REPORT_ID_TYPE 0x02 #define FU_WACOM_RAW_BL_TYPE_FINALIZER 0x00 #define FU_WACOM_RAW_BL_REPORT_ID_SET 0x07 #define FU_WACOM_RAW_BL_REPORT_ID_GET 0x08 #define FU_WACOM_RAW_BL_CMD_ERASE_FLASH 0x00 #define FU_WACOM_RAW_BL_CMD_WRITE_FLASH 0x01 #define FU_WACOM_RAW_BL_CMD_VERIFY_FLASH 0x02 #define FU_WACOM_RAW_BL_CMD_ATTACH 0x03 #define FU_WACOM_RAW_BL_CMD_GET_BLVER 0x04 #define FU_WACOM_RAW_BL_CMD_GET_MPUTYPE 0x05 #define FU_WACOM_RAW_BL_CMD_CHECK_MODE 0x07 #define FU_WACOM_RAW_BL_CMD_ERASE_DATAMEM 0x0e #define FU_WACOM_RAW_BL_CMD_ALL_ERASE 0x90 #define FU_WACOM_RAW_RC_OK 0x00 #define FU_WACOM_RAW_RC_BUSY 0x80 #define FU_WACOM_RAW_RC_MCUTYPE 0x0c #define FU_WACOM_RAW_RC_PID 0x0d #define FU_WACOM_RAW_RC_CHECKSUM1 0x81 #define FU_WACOM_RAW_RC_CHECKSUM2 0x82 #define FU_WACOM_RAW_RC_TIMEOUT 0x87 #define FU_WACOM_RAW_RC_IN_PROGRESS 0xff #define FU_WACOM_RAW_ECHO_DEFAULT g_random_int_range(0xa0, 0xfe) typedef struct __attribute__((packed)) { guint8 report_id; guint8 cmd; guint8 echo; guint32 addr; guint8 size8; guint8 data[128]; guint8 data_unused[121]; } FuWacomRawRequest; typedef struct __attribute__((packed)) { guint8 report_id; guint8 cmd; guint8 echo; guint8 resp; guint8 data_unused[132]; } FuWacomRawResponse; gboolean fu_wacom_common_rc_set_error(const FuWacomRawResponse *rsp, GError **error); gboolean fu_wacom_common_check_reply(const FuWacomRawRequest *req, const FuWacomRawResponse *rsp, GError **error); gboolean fu_wacom_common_block_is_empty(const guint8 *data, guint16 datasz); fwupd-1.9.16/plugins/wacom-raw/fu-wacom-device.c000066400000000000000000000257471460375044200214560ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-wacom-common.h" #include "fu-wacom-device.h" typedef struct { guint flash_block_size; guint32 flash_base_addr; guint32 flash_size; } FuWacomDevicePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuWacomDevice, fu_wacom_device, FU_TYPE_UDEV_DEVICE) #define GET_PRIVATE(o) (fu_wacom_device_get_instance_private(o)) #define FU_WACOM_DEVICE_IOCTL_TIMEOUT 5000 /* ms */ static void fu_wacom_device_to_string(FuDevice *device, guint idt, GString *str) { FuWacomDevice *self = FU_WACOM_DEVICE(device); FuWacomDevicePrivate *priv = GET_PRIVATE(self); /* FuUdevDevice->to_string */ FU_DEVICE_CLASS(fu_wacom_device_parent_class)->to_string(device, idt, str); fu_string_append_kx(str, idt, "FlashBlockSize", priv->flash_block_size); fu_string_append_kx(str, idt, "FlashBaseAddr", priv->flash_base_addr); fu_string_append_kx(str, idt, "FlashSize", priv->flash_size); } gsize fu_wacom_device_get_block_sz(FuWacomDevice *self) { FuWacomDevicePrivate *priv = GET_PRIVATE(self); return priv->flash_block_size; } gboolean fu_wacom_device_check_mpu(FuWacomDevice *self, GError **error) { FuWacomRawRequest req = {.cmd = FU_WACOM_RAW_BL_CMD_GET_MPUTYPE, .echo = FU_WACOM_RAW_ECHO_DEFAULT, 0x00}; FuWacomRawResponse rsp = {0x00}; if (!fu_wacom_device_cmd(FU_WACOM_DEVICE(self), &req, &rsp, 0, FU_WACOM_DEVICE_CMD_FLAG_NO_ERROR_CHECK, error)) { g_prefix_error(error, "failed to get MPU type: "); return FALSE; } /* W9013 */ if (rsp.resp == 0x2e) { fu_device_add_instance_id_full(FU_DEVICE(self), "WacomEMR_W9013", FU_DEVICE_INSTANCE_FLAG_QUIRKS); return TRUE; } /* W9021 */ if (rsp.resp == 0x45) { fu_device_add_instance_id_full(FU_DEVICE(self), "WacomEMR_W9021", FU_DEVICE_INSTANCE_FLAG_QUIRKS); return TRUE; } /* unsupported */ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "MPU is not W9013 or W9021: 0x%x", rsp.resp); return FALSE; } static gboolean fu_wacom_device_probe(FuDevice *device, GError **error) { /* set the physical ID */ return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); } static gboolean fu_wacom_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuWacomDevice *self = FU_WACOM_DEVICE(device); g_autoptr(GError) error_local = NULL; guint8 buf[FU_WACOM_RAW_FW_REPORT_SZ] = { FU_WACOM_RAW_FW_REPORT_ID, FU_WACOM_RAW_FW_CMD_DETACH, }; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in bootloader mode, skipping"); return TRUE; } if (!fu_wacom_device_set_feature(self, buf, sizeof(buf), &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_INTERNAL)) { g_debug("ignoring: %s", error_local->message); } else { g_propagate_prefixed_error(error, g_steal_pointer(&error_local), "failed to switch to bootloader mode: "); return FALSE; } } /* does the device have to replug to bootloader mode */ if (fu_device_has_private_flag(device, FU_WACOM_RAW_DEVICE_FLAG_REQUIRES_WAIT_FOR_REPLUG)) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } else { fu_device_sleep(device, 300); /* ms */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } return TRUE; } static gboolean fu_wacom_device_check_mode(FuWacomDevice *self, GError **error) { FuWacomRawRequest req = {.cmd = FU_WACOM_RAW_BL_CMD_CHECK_MODE, .echo = FU_WACOM_RAW_ECHO_DEFAULT, 0x00}; FuWacomRawResponse rsp = {0x00}; if (!fu_wacom_device_cmd(self, &req, &rsp, 0, FU_WACOM_DEVICE_CMD_FLAG_NO_ERROR_CHECK, error)) { g_prefix_error(error, "failed to check mode: "); return FALSE; } if (rsp.resp != 0x06) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "check mode failed, mode=0x%02x", rsp.resp); return FALSE; } return TRUE; } static gboolean fu_wacom_device_set_version_bootloader(FuWacomDevice *self, GError **error) { FuWacomRawRequest req = {.cmd = FU_WACOM_RAW_BL_CMD_GET_BLVER, .echo = FU_WACOM_RAW_ECHO_DEFAULT, 0x00}; FuWacomRawResponse rsp = {0x00}; g_autofree gchar *version = NULL; if (!fu_wacom_device_cmd(self, &req, &rsp, 0, FU_WACOM_DEVICE_CMD_FLAG_NO_ERROR_CHECK, error)) { g_prefix_error(error, "failed to get bootloader version: "); return FALSE; } version = g_strdup_printf("%u", rsp.resp); fu_device_set_version_bootloader(FU_DEVICE(self), version); return TRUE; } static gboolean fu_wacom_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuWacomDevice *self = FU_WACOM_DEVICE(device); FuWacomDevicePrivate *priv = GET_PRIVATE(self); FuWacomDeviceClass *klass = FU_WACOM_DEVICE_GET_CLASS(device); g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* use the correct image from the firmware */ g_debug("using element at addr 0x%0x", (guint)fu_firmware_get_addr(firmware)); /* check start address and size */ if (fu_firmware_get_addr(firmware) != priv->flash_base_addr) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "base addr invalid: 0x%05x", (guint)fu_firmware_get_addr(firmware)); return FALSE; } fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; if (g_bytes_get_size(fw) > priv->flash_size) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "size is invalid: 0x%05x", (guint)g_bytes_get_size(fw)); return FALSE; } /* we're in bootloader mode now */ if (!fu_wacom_device_check_mode(self, error)) return FALSE; if (!fu_wacom_device_set_version_bootloader(self, error)) return FALSE; /* flash chunks */ chunks = fu_chunk_array_new_from_bytes(fw, priv->flash_base_addr, priv->flash_block_size); return klass->write_firmware(device, chunks, progress, error); } gboolean fu_wacom_device_set_feature(FuWacomDevice *self, const guint8 *data, guint datasz, GError **error) { fu_dump_raw(G_LOG_DOMAIN, "SetFeature", data, datasz); return fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCSFEATURE(datasz), (guint8 *)data, NULL, FU_WACOM_DEVICE_IOCTL_TIMEOUT, error); } gboolean fu_wacom_device_get_feature(FuWacomDevice *self, guint8 *data, guint datasz, GError **error) { if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGFEATURE(datasz), data, NULL, FU_WACOM_DEVICE_IOCTL_TIMEOUT, error)) return FALSE; fu_dump_raw(G_LOG_DOMAIN, "GetFeature", data, datasz); return TRUE; } gboolean fu_wacom_device_cmd(FuWacomDevice *self, FuWacomRawRequest *req, FuWacomRawResponse *rsp, guint delay_ms, FuWacomDeviceCmdFlags flags, GError **error) { req->report_id = FU_WACOM_RAW_BL_REPORT_ID_SET; if (!fu_wacom_device_set_feature(self, (const guint8 *)req, sizeof(*req), error)) { g_prefix_error(error, "failed to send: "); return FALSE; } fu_device_sleep(FU_DEVICE(self), delay_ms); rsp->report_id = FU_WACOM_RAW_BL_REPORT_ID_GET; if (!fu_wacom_device_get_feature(self, (guint8 *)rsp, sizeof(*rsp), error)) { g_prefix_error(error, "failed to receive: "); return FALSE; } if (flags & FU_WACOM_DEVICE_CMD_FLAG_NO_ERROR_CHECK) return TRUE; if (!fu_wacom_common_check_reply(req, rsp, error)) return FALSE; /* wait for the command to complete */ if (flags & FU_WACOM_DEVICE_CMD_FLAG_POLL_ON_WAITING && rsp->resp != FU_WACOM_RAW_RC_OK) { for (guint i = 0; i < FU_WACOM_RAW_CMD_RETRIES; i++) { fu_device_sleep(FU_DEVICE(self), delay_ms); if (!fu_wacom_device_get_feature(self, (guint8 *)rsp, sizeof(*rsp), error)) return FALSE; if (!fu_wacom_common_check_reply(req, rsp, error)) return FALSE; if (rsp->resp != FU_WACOM_RAW_RC_IN_PROGRESS && rsp->resp != FU_WACOM_RAW_RC_BUSY) break; } } return fu_wacom_common_rc_set_error(rsp, error); } static gboolean fu_wacom_device_set_quirk_kv(FuDevice *device, const gchar *key, const gchar *value, GError **error) { FuWacomDevice *self = FU_WACOM_DEVICE(device); FuWacomDevicePrivate *priv = GET_PRIVATE(self); guint64 tmp = 0; if (g_strcmp0(key, "WacomI2cFlashBlockSize") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXSIZE, error)) return FALSE; priv->flash_block_size = tmp; return TRUE; } if (g_strcmp0(key, "WacomI2cFlashBaseAddr") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; priv->flash_base_addr = tmp; return TRUE; } if (g_strcmp0(key, "WacomI2cFlashSize") == 0) { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) return FALSE; priv->flash_size = tmp; return TRUE; } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported"); return FALSE; } static void fu_wacom_device_replace(FuDevice *device, FuDevice *donor) { g_return_if_fail(FU_IS_WACOM_DEVICE(device)); g_return_if_fail(FU_IS_WACOM_DEVICE(donor)); /* copy private instance data */ if (fu_device_has_private_flag(donor, FU_WACOM_RAW_DEVICE_FLAG_REQUIRES_WAIT_FOR_REPLUG)) { fu_device_add_private_flag(device, FU_WACOM_RAW_DEVICE_FLAG_REQUIRES_WAIT_FOR_REPLUG); } } static void fu_wacom_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 4, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 92, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 4, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_wacom_device_init(FuWacomDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.wacom.raw"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_IHEX_FIRMWARE); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_register_private_flag(FU_DEVICE(self), FU_WACOM_RAW_DEVICE_FLAG_REQUIRES_WAIT_FOR_REPLUG, "requires-wait-for-replug"); } static void fu_wacom_device_class_init(FuWacomDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->to_string = fu_wacom_device_to_string; klass_device->write_firmware = fu_wacom_device_write_firmware; klass_device->detach = fu_wacom_device_detach; klass_device->set_quirk_kv = fu_wacom_device_set_quirk_kv; klass_device->probe = fu_wacom_device_probe; klass_device->set_progress = fu_wacom_device_set_progress; klass_device->replace = fu_wacom_device_replace; } fwupd-1.9.16/plugins/wacom-raw/fu-wacom-device.h000066400000000000000000000025341460375044200214500ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-wacom-common.h" #define FU_TYPE_WACOM_DEVICE (fu_wacom_device_get_type()) G_DECLARE_DERIVABLE_TYPE(FuWacomDevice, fu_wacom_device, FU, WACOM_DEVICE, FuUdevDevice) struct _FuWacomDeviceClass { FuUdevDeviceClass parent_class; gboolean (*write_firmware)(FuDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error); }; typedef enum { FU_WACOM_DEVICE_CMD_FLAG_NONE = 0, FU_WACOM_DEVICE_CMD_FLAG_POLL_ON_WAITING = 1 << 0, FU_WACOM_DEVICE_CMD_FLAG_NO_ERROR_CHECK = 1 << 1, } FuWacomDeviceCmdFlags; #define FU_WACOM_RAW_DEVICE_FLAG_REQUIRES_WAIT_FOR_REPLUG (1 << 0) gboolean fu_wacom_device_set_feature(FuWacomDevice *self, const guint8 *data, guint datasz, GError **error); gboolean fu_wacom_device_get_feature(FuWacomDevice *self, guint8 *data, guint datasz, GError **error); gboolean fu_wacom_device_cmd(FuWacomDevice *self, FuWacomRawRequest *req, FuWacomRawResponse *rsp, guint delay_ms, FuWacomDeviceCmdFlags flags, GError **error); gboolean fu_wacom_device_erase_all(FuWacomDevice *self, GError **error); gboolean fu_wacom_device_check_mpu(FuWacomDevice *self, GError **error); gsize fu_wacom_device_get_block_sz(FuWacomDevice *self); fwupd-1.9.16/plugins/wacom-raw/fu-wacom-emr-device.c000066400000000000000000000173131460375044200222250ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wacom-common.h" #include "fu-wacom-emr-device.h" struct _FuWacomEmrDevice { FuWacomDevice parent_instance; }; G_DEFINE_TYPE(FuWacomEmrDevice, fu_wacom_emr_device, FU_TYPE_WACOM_DEVICE) static gboolean fu_wacom_emr_device_setup(FuDevice *device, GError **error) { FuWacomEmrDevice *self = FU_WACOM_EMR_DEVICE(device); /* check MPU type */ if (!fu_wacom_device_check_mpu(FU_WACOM_DEVICE(self), error)) return FALSE; /* get firmware version */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { fu_device_set_version(device, "0.0"); } else { guint16 fw_ver; guint8 data[19] = {0x03, 0x0}; /* 0x03 is an unknown ReportID */ if (!fu_wacom_device_get_feature(FU_WACOM_DEVICE(self), data, sizeof(data), error)) return FALSE; if (!fu_memread_uint16_safe(data, sizeof(data), 11, &fw_ver, G_LITTLE_ENDIAN, error)) return FALSE; fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER); fu_device_set_version_u32(device, fw_ver); } /* success */ return TRUE; } static guint8 fu_wacom_emr_device_calc_checksum(guint8 init1, const guint8 *buf, gsize bufsz) { return init1 + ~(fu_sum8(buf, bufsz)) + 1; } static gboolean fu_wacom_emr_device_w9013_erase_data(FuWacomEmrDevice *self, GError **error) { FuWacomRawResponse rsp = {0x00}; FuWacomRawRequest req = {.cmd = FU_WACOM_RAW_BL_CMD_ERASE_DATAMEM, .echo = FU_WACOM_RAW_ECHO_DEFAULT, 0x00}; guint8 *buf = (guint8 *)&req.addr; buf[0] = 0x00; /* erased block */ buf[1] = fu_wacom_emr_device_calc_checksum(0x05 + 0x00 + 0x07 + 0x00, (const guint8 *)&req, 4); if (!fu_wacom_device_cmd(FU_WACOM_DEVICE(self), &req, &rsp, 1, /* ms */ FU_WACOM_DEVICE_CMD_FLAG_POLL_ON_WAITING, error)) { g_prefix_error(error, "failed to erase datamem: "); return FALSE; } g_usleep(50); return TRUE; } static gboolean fu_wacom_emr_device_w9013_erase_code(FuWacomEmrDevice *self, guint8 idx, guint8 block_nr, GError **error) { FuWacomRawResponse rsp = {0x00}; FuWacomRawRequest req = {.cmd = FU_WACOM_RAW_BL_CMD_ERASE_FLASH, .echo = idx, 0x00}; guint8 *buf = (guint8 *)&req.addr; buf[0] = block_nr; buf[1] = fu_wacom_emr_device_calc_checksum(0x05 + 0x00 + 0x07 + 0x00, (const guint8 *)&req, 4); if (!fu_wacom_device_cmd(FU_WACOM_DEVICE(self), &req, &rsp, 1, /* ms */ FU_WACOM_DEVICE_CMD_FLAG_POLL_ON_WAITING, error)) { g_prefix_error(error, "failed to erase codemem: "); return FALSE; } g_usleep(50); return TRUE; } static gboolean fu_wacom_device_w9021_erase_all(FuWacomEmrDevice *self, GError **error) { FuWacomRawRequest req = { .cmd = FU_WACOM_RAW_BL_CMD_ALL_ERASE, .echo = 0x01, .addr = 0x00, }; FuWacomRawResponse rsp = {0x00}; if (!fu_wacom_device_cmd(FU_WACOM_DEVICE(self), &req, &rsp, 2000, /* this takes a long time */ FU_WACOM_DEVICE_CMD_FLAG_POLL_ON_WAITING, error)) { g_prefix_error(error, "failed to send eraseall command: "); return FALSE; } if (!fu_wacom_common_rc_set_error(&rsp, error)) { g_prefix_error(error, "failed to erase: "); return FALSE; } g_usleep(50); return TRUE; } static gboolean fu_wacom_emr_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuWacomDevice *self = FU_WACOM_DEVICE(device); FuWacomRawRequest req = {.report_id = FU_WACOM_RAW_BL_REPORT_ID_SET, .cmd = FU_WACOM_RAW_BL_CMD_ATTACH, .echo = FU_WACOM_RAW_ECHO_DEFAULT, 0x00}; if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } if (!fu_wacom_device_set_feature(self, (const guint8 *)&req, sizeof(req), error)) { g_prefix_error(error, "failed to switch to runtime mode: "); return FALSE; } /* does the device have to replug to bootloader mode */ if (fu_device_has_private_flag(device, FU_WACOM_RAW_DEVICE_FLAG_REQUIRES_WAIT_FOR_REPLUG)) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } else { fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); } /* success */ return TRUE; } static gboolean fu_wacom_emr_device_write_block(FuWacomEmrDevice *self, guint32 idx, guint32 address, const guint8 *data, gsize datasz, GError **error) { gsize blocksz = fu_wacom_device_get_block_sz(FU_WACOM_DEVICE(self)); FuWacomRawRequest req = { .cmd = FU_WACOM_RAW_BL_CMD_WRITE_FLASH, .echo = (guint8)idx + 1, .addr = GUINT32_TO_LE(address), .size8 = datasz / 8, .data = {0x00}, }; FuWacomRawResponse rsp = {0x00}; /* check size */ if (datasz > sizeof(req.data)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "data size 0x%x too large for packet", (guint)datasz); return FALSE; } if (datasz != blocksz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "block size 0x%x != 0x%x untested", (guint)datasz, (guint)blocksz); return FALSE; } /* data */ memcpy(&req.data, data, datasz); /* cmd and data checksums */ req.data_unused[0] = fu_wacom_emr_device_calc_checksum(0x05 + 0x00 + 0x4c + 0x00, (const guint8 *)&req, 8); req.data_unused[1] = fu_wacom_emr_device_calc_checksum(0x00, data, datasz); if (!fu_wacom_device_cmd(FU_WACOM_DEVICE(self), &req, &rsp, 1, FU_WACOM_DEVICE_CMD_FLAG_NONE, error)) { g_prefix_error(error, "failed to write at 0x%x: ", address); return FALSE; } return TRUE; } static gboolean fu_wacom_emr_device_write_firmware(FuDevice *device, FuChunkArray *chunks, FuProgress *progress, GError **error) { FuWacomEmrDevice *self = FU_WACOM_EMR_DEVICE(device); guint8 idx = 0; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); /* erase W9013 */ if (fu_device_has_instance_id(device, "WacomEMR_W9013")) { if (!fu_wacom_emr_device_w9013_erase_data(self, error)) return FALSE; for (guint i = 127; i >= 8; i--) { if (!fu_wacom_emr_device_w9013_erase_code(self, idx++, i, error)) return FALSE; } } /* erase W9021 */ if (fu_device_has_instance_id(device, "WacomEMR_W9021")) { if (!fu_wacom_device_w9021_erase_all(self, error)) return FALSE; } fu_progress_step_done(progress); /* write */ for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (fu_wacom_common_block_is_empty(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk))) continue; if (!fu_wacom_emr_device_write_block(self, fu_chunk_get_idx(chk), fu_chunk_get_address(chk), fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) return FALSE; fu_progress_set_percentage_full(fu_progress_get_child(progress), (gsize)i + 1, (gsize)fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); return TRUE; } static void fu_wacom_emr_device_init(FuWacomEmrDevice *self) { fu_device_set_name(FU_DEVICE(self), "Wacom EMR Device"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); } static void fu_wacom_emr_device_class_init(FuWacomEmrDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); FuWacomDeviceClass *klass_wac_device = FU_WACOM_DEVICE_CLASS(klass); klass_device->setup = fu_wacom_emr_device_setup; klass_device->attach = fu_wacom_emr_device_attach; klass_wac_device->write_firmware = fu_wacom_emr_device_write_firmware; } fwupd-1.9.16/plugins/wacom-raw/fu-wacom-emr-device.h000066400000000000000000000004731460375044200222310ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wacom-device.h" #define FU_TYPE_WACOM_EMR_DEVICE (fu_wacom_emr_device_get_type()) G_DECLARE_FINAL_TYPE(FuWacomEmrDevice, fu_wacom_emr_device, FU, WACOM_EMR_DEVICE, FuWacomDevice) fwupd-1.9.16/plugins/wacom-raw/fu-wacom-raw-plugin.c000066400000000000000000000037151460375044200222730ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-wacom-aes-device.h" #include "fu-wacom-common.h" #include "fu-wacom-emr-device.h" #include "fu-wacom-raw-plugin.h" struct _FuWacomRawPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuWacomRawPlugin, fu_wacom_raw_plugin, FU_TYPE_PLUGIN) static void fu_wacom_raw_plugin_device_registered(FuPlugin *plugin, FuDevice *device) { /* is internal DRM device */ if (FU_IS_DRM_DEVICE(device) && fu_device_has_flag(device, FWUPD_DEVICE_FLAG_INTERNAL)) { GPtrArray *devices = fu_plugin_get_devices(plugin); for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); fu_device_add_child(device, device_tmp); } fu_plugin_cache_add(plugin, "drm", device); } } static gboolean fu_wacom_raw_plugin_device_created(FuPlugin *plugin, FuDevice *device, GError **error) { FuDevice *drm_device = fu_plugin_cache_lookup(plugin, "drm"); if (drm_device != NULL) fu_device_add_child(drm_device, device); return TRUE; } static void fu_wacom_raw_plugin_init(FuWacomRawPlugin *self) { } static void fu_wacom_raw_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); FuContext *ctx = fu_plugin_get_context(plugin); fu_context_add_quirk_key(ctx, "WacomI2cFlashBlockSize"); fu_context_add_quirk_key(ctx, "WacomI2cFlashBaseAddr"); fu_context_add_quirk_key(ctx, "WacomI2cFlashSize"); fu_plugin_add_device_gtype(plugin, FU_TYPE_WACOM_AES_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_WACOM_EMR_DEVICE); fu_plugin_add_udev_subsystem(plugin, "hidraw"); } static void fu_wacom_raw_plugin_class_init(FuWacomRawPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_wacom_raw_plugin_constructed; plugin_class->device_created = fu_wacom_raw_plugin_device_created; plugin_class->device_registered = fu_wacom_raw_plugin_device_registered; } fwupd-1.9.16/plugins/wacom-raw/fu-wacom-raw-plugin.h000066400000000000000000000003601460375044200222710ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuWacomRawPlugin, fu_wacom_raw_plugin, FU, WACOM_RAW_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/wacom-raw/meson.build000066400000000000000000000007621460375044200204670ustar00rootroot00000000000000if gudev.found() plugins += {meson.current_source_dir().split('/')[-1]: true} cargs = ['-DG_LOG_DOMAIN="FuPluginWacomRaw"'] plugin_quirks += files('wacom-raw.quirk') plugin_builtins += static_library('fu_plugin_wacom_raw', sources: [ 'fu-wacom-raw-plugin.c', 'fu-wacom-common.c', 'fu-wacom-device.c', 'fu-wacom-aes-device.c', 'fu-wacom-emr-device.c', ], include_directories: plugin_incdirs, c_args: cargs, dependencies: plugin_deps, link_with: plugin_libs, ) endif fwupd-1.9.16/plugins/wacom-raw/wacom-raw.quirk000066400000000000000000000056721460375044200213040ustar00rootroot00000000000000# Devices that do "replug" and thus don't change VID:PID to the bootloader # need to have an extra GUID of WacomAES or WacomEMR added so that the flash # constants are set correctly. # This device has USB I/F [HIDRAW\VEN_056A&DEV_5315] Plugin = wacom_raw Guid[quirk] = WacomAES Flags = requires-wait-for-replug CounterpartGuid = HIDRAW\VEN_056A&DEV_0094 # Lenovo X1 Yoga Gen7 5308 [HIDRAW\VEN_056A&DEV_5308] Plugin = wacom_raw Guid[quirk] = WacomAES # Lenovo X1 Yoga Gen7 5309 [HIDRAW\VEN_056A&DEV_5309] Plugin = wacom_raw Guid[quirk] = WacomAES # Lenovo X1 Yoga Gen7 530A [HIDRAW\VEN_056A&DEV_530A] Plugin = wacom_raw Guid[quirk] = WacomAES # Lenovo X1 Yoga Gen7 530B [HIDRAW\VEN_056A&DEV_530B] Plugin = wacom_raw Guid[quirk] = WacomAES # Lenovo X1 Yoga Gen7 530E [HIDRAW\VEN_056A&DEV_530E] Plugin = wacom_raw Guid[quirk] = WacomAES # Lenovo X1 Yoga Gen7 52B5 [HIDRAW\VEN_056A&DEV_52B5] Plugin = wacom_raw Guid[quirk] = WacomAES # Lenovo X1 Yoga Gen7 52C4 [HIDRAW\VEN_056A&DEV_52C4] Plugin = wacom_raw Guid[quirk] = WacomAES # Dell Chromebook Enterprise 5300 [HIDRAW\VEN_2D1F&DEV_4946] Plugin = wacom_raw Guid[quirk] = WacomAES # Moffet 14-LGD-TPK [HIDRAW\VEN_2D1F&DEV_4970] Plugin = wacom_raw Guid[quirk] = WacomAES Flags = self-recovery # Moffet 14-Sharp-HH [HIDRAW\VEN_2D1F&DEV_4971] Plugin = wacom_raw Guid[quirk] = WacomAES Flags = self-recovery # Moffet 14-Sharp-VIA [HIDRAW\VEN_2D1F&DEV_4972] Plugin = wacom_raw Guid[quirk] = WacomAES Flags = self-recovery # Dell Latitude 5175 [HIDRAW\VEN_056A&DEV_4807] Plugin = wacom_raw Guid[quirk] = WacomAES # Dell XPS 12 9250 [HIDRAW\VEN_056A&DEV_4822] Plugin = wacom_raw Guid[quirk] = WacomAES # Dell Venue 8 Pro 5855 [HIDRAW\VEN_056A&DEV_4824] Plugin = wacom_raw Guid[quirk] = WacomAES # Dell XPS 13 9365 [HIDRAW\VEN_056A&DEV_4831] Plugin = wacom_raw Guid[quirk] = WacomAES # Dell Latitude 5285 [HIDRAW\VEN_056A&DEV_484C] Plugin = wacom_raw Guid[quirk] = WacomAES # Dell Latitude 7390 2-in-1 [HIDRAW\VEN_056A&DEV_4841] Plugin = wacom_raw Guid[quirk] = WacomAES # Dell XPS-15 9575 [HIDRAW\VEN_056A&DEV_4875] Plugin = wacom_raw Guid[quirk] = WacomAES # Dell Latitude 7400 2-in-1 [HIDRAW\VEN_056A&DEV_48C9] Plugin = wacom_raw Guid[quirk] = WacomAES # Dell XPS-15 9570 [HIDRAW\VEN_056A&DEV_488F] Plugin = wacom_raw Guid[quirk] = WacomAES # Dell XPS 13 7390 2-in-1 [HIDRAW\VEN_056A&DEV_48ED] Plugin = wacom_raw Guid[quirk] = WacomAES # AES bootloader mode [HIDRAW\VEN_056A&DEV_0094] Plugin = wacom_raw Guid[quirk] = WacomAES Flags = is-bootloader # EMR bootloader mode [HIDRAW\VEN_056A&DEV_012B] Plugin = wacom_raw Guid[quirk] = WacomEMR Flags = is-bootloader [WacomEMR_W9013] WacomI2cFlashBlockSize = 64 WacomI2cFlashBaseAddr = 0x2000 WacomI2cFlashSize = 0x1e000 [WacomEMR_W9021] WacomI2cFlashBlockSize = 256 WacomI2cFlashBaseAddr = 0x3000 WacomI2cFlashSize = 0x3c000 [WacomEMR] GType = FuWacomEmrDevice [WacomAES] GType = FuWacomAesDevice WacomI2cFlashBlockSize = 128 WacomI2cFlashBaseAddr = 0x8000 WacomI2cFlashSize = 0x28000 fwupd-1.9.16/plugins/wacom-usb/000077500000000000000000000000001460375044200163205ustar00rootroot00000000000000fwupd-1.9.16/plugins/wacom-usb/README.md000066400000000000000000000034451460375044200176050ustar00rootroot00000000000000--- title: Plugin: Wacom USB --- ## Introduction Wacom provides interactive pen displays, pen tablets, and styluses to equip and inspire everyone make the world a more creative place. From 2016 Wacom has been using a HID-based proprietary flashing algorithm which has been documented by support team at Wacom and provided under NDA under the understanding it would be used to build a plugin under a LGPLv2+ license. Wacom devices are actually composite devices, with the main ARM CPU being programmed using a more complicated erase, write, verify algorithm based on a historical update protocol. The "sub-module" devices use a newer protocol, again based on HID, but are handled differently depending on their type. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in the following formats: * Touch module: Intel HEX file format * Bluetooth module: Unknown airoflash file format * EMR module: Plain SREC file format * Main module: SREC file format, with a custom `WACOM` vendor header This plugin supports the following protocol ID: * `com.wacom.usb` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_056A&PID_0378` ## Update Behavior The firmware is deployed when the device is in normal runtime mode, and the device will reset when the new firmware has been written. ## Vendor ID Security The vendor ID is set from the USB vendor, for example set to `USB:0x056A` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.2.2`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Jason Gerecke: @jigpu fwupd-1.9.16/plugins/wacom-usb/data/000077500000000000000000000000001460375044200172315ustar00rootroot00000000000000fwupd-1.9.16/plugins/wacom-usb/data/lsusb.txt000066400000000000000000000037021460375044200211240ustar00rootroot00000000000000Bus 001 Device 023: ID 056a:0378 Wacom Co., Ltd CTL-6100WL [Intuos BT (M)] Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x056a Wacom Co., Ltd idProduct 0x0378 CTL-6100WL [Intuos BT (M)] bcdDevice 1.66 iManufacturer 1 Wacom Co.,Ltd. iProduct 2 Intuos BT M iSerial 3 8BH00U2012294 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0022 bNumInterfaces 1 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.10 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 759 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 Device Status: 0x0000 (Bus Powered) fwupd-1.9.16/plugins/wacom-usb/fu-self-test.c000066400000000000000000000061211460375044200210020ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-wac-common.h" #include "fu-wac-firmware.h" #include "fu-wac-struct.h" static void fu_wac_firmware_parse_func(void) { gboolean ret; g_autofree gchar *fn = NULL; g_autoptr(FuFirmware) firmware = fu_wac_firmware_new(); g_autoptr(FuFirmware) img = NULL; g_autoptr(GBytes) blob_block = NULL; g_autoptr(GBytes) bytes = NULL; g_autoptr(GError) error = NULL; /* parse the test file */ fn = g_test_build_filename(G_TEST_DIST, "tests", "test.wac", NULL); if (!g_file_test(fn, G_FILE_TEST_EXISTS)) { g_test_skip("no data file found"); return; } bytes = fu_bytes_get_contents(fn, &error); g_assert_no_error(error); g_assert_nonnull(bytes); ret = fu_firmware_parse(firmware, bytes, FWUPD_INSTALL_FLAG_NO_SEARCH, &error); g_assert_no_error(error); g_assert_true(ret); /* get image data */ img = fu_firmware_get_image_by_id(firmware, 0, &error); g_assert_no_error(error); g_assert_nonnull(img); /* get block */ blob_block = fu_firmware_write_chunk(img, 0x8008000, 1024, &error); g_assert_no_error(error); g_assert_nonnull(blob_block); fu_wac_buffer_dump("IMG", FU_WAC_REPORT_ID_MODULE, g_bytes_get_data(blob_block, NULL), g_bytes_get_size(blob_block)); } static void fu_wac_firmware_xml_func(void) { gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *csum1 = NULL; g_autofree gchar *csum2 = NULL; g_autofree gchar *xml_out = NULL; g_autofree gchar *xml_src = NULL; g_autoptr(FuFirmware) firmware1 = fu_wac_firmware_new(); g_autoptr(FuFirmware) firmware2 = fu_wac_firmware_new(); g_autoptr(GError) error = NULL; /* build and write */ filename = g_test_build_filename(G_TEST_DIST, "tests", "wacom-usb.builder.xml", NULL); ret = g_file_get_contents(filename, &xml_src, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_firmware_build_from_xml(firmware1, xml_src, &error); g_assert_no_error(error); g_assert_true(ret); csum1 = fu_firmware_get_checksum(firmware1, G_CHECKSUM_SHA1, &error); g_assert_no_error(error); g_assert_cmpstr(csum1, ==, "346f6196449b356777cf241f6edb039d503b88a1"); /* ensure we can round-trip */ xml_out = fu_firmware_export_to_xml(firmware1, FU_FIRMWARE_EXPORT_FLAG_NONE, &error); g_assert_no_error(error); ret = fu_firmware_build_from_xml(firmware2, xml_out, &error); g_assert_no_error(error); g_assert_true(ret); csum2 = fu_firmware_get_checksum(firmware2, G_CHECKSUM_SHA1, &error); g_assert_cmpstr(csum1, ==, csum2); } int main(int argc, char **argv) { (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); g_type_ensure(FU_TYPE_SREC_FIRMWARE); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); /* log everything */ (void)g_setenv("G_MESSAGES_DEBUG", "all", FALSE); /* tests go here */ g_test_add_func("/wac/firmware{parse}", fu_wac_firmware_parse_func); g_test_add_func("/wac/firmware{xml}", fu_wac_firmware_xml_func); return g_test_run(); } fwupd-1.9.16/plugins/wacom-usb/fu-wac-android-device.c000066400000000000000000000014151460375044200225220ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-wac-android-device.h" struct _FuWacAndroidDevice { FuHidDevice parent_instance; }; G_DEFINE_TYPE(FuWacAndroidDevice, fu_wac_android_device, FU_TYPE_HID_DEVICE) static void fu_wac_android_device_init(FuWacAndroidDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.wacom.usb"); fu_device_add_icon(FU_DEVICE(self), "input-tablet"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_inhibit(FU_DEVICE(self), "hw", "Switch into PC mode by holding down the " "two outermost ExpressKeys for 4 seconds"); } static void fu_wac_android_device_class_init(FuWacAndroidDeviceClass *klass) { } fwupd-1.9.16/plugins/wacom-usb/fu-wac-android-device.h000066400000000000000000000004771460375044200225360ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_WAC_ANDROID_DEVICE (fu_wac_android_device_get_type()) G_DECLARE_FINAL_TYPE(FuWacAndroidDevice, fu_wac_android_device, FU, WAC_ANDROID_DEVICE, FuHidDevice) fwupd-1.9.16/plugins/wacom-usb/fu-wac-common.c000066400000000000000000000010071460375044200211320ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fu-wac-common.h" #include "fu-wac-struct.h" void fu_wac_buffer_dump(const gchar *title, guint8 cmd, const guint8 *buf, gsize sz) { g_autofree gchar *tmp = NULL; tmp = g_strdup_printf("%s %s (%" G_GSIZE_FORMAT ")", title, fu_wac_report_id_to_string(cmd), sz); fu_dump_raw(G_LOG_DOMAIN, tmp, buf, sz); } fwupd-1.9.16/plugins/wacom-usb/fu-wac-common.h000066400000000000000000000010531460375044200211400ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_WAC_PACKET_LEN 512 #define FU_WAC_REPORT_ID_COMMAND 0x01 #define FU_WAC_REPORT_ID_STATUS 0x02 #define FU_WAC_REPORT_ID_CONTROL 0x03 #define FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_MAIN 0x07 #define FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_TOUCH 0x07 #define FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_BLUETOOTH 0x16 void fu_wac_buffer_dump(const gchar *title, guint8 cmd, const guint8 *buf, gsize sz); fwupd-1.9.16/plugins/wacom-usb/fu-wac-device.c000066400000000000000000000654131460375044200211140ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wac-common.h" #include "fu-wac-device.h" #include "fu-wac-firmware.h" #include "fu-wac-module-bluetooth-id6.h" #include "fu-wac-module-bluetooth-id9.h" #include "fu-wac-module-bluetooth.h" #include "fu-wac-module-scaler.h" #include "fu-wac-module-touch-id7.h" #include "fu-wac-module-touch.h" #include "fu-wac-struct.h" typedef struct { guint32 start_addr; guint32 block_sz; guint16 write_sz; /* bit 15 is write protection flag */ } FuWacFlashDescriptor; #define FU_WAC_DEVICE_TIMEOUT 5000 /* ms */ struct _FuWacDevice { FuHidDevice parent_instance; GPtrArray *flash_descriptors; GArray *checksums; guint32 status_word; guint16 firmware_index; guint16 loader_ver; guint16 read_data_sz; guint16 write_word_sz; guint16 write_block_sz; /* usb transfer size */ guint16 nr_flash_blocks; guint16 configuration; }; G_DEFINE_TYPE(FuWacDevice, fu_wac_device, FU_TYPE_HID_DEVICE) static gboolean fu_wav_device_flash_descriptor_is_wp(const FuWacFlashDescriptor *fd) { return fd->write_sz & 0x8000; } static void fu_wac_device_flash_descriptor_to_string(FuWacFlashDescriptor *fd, guint idt, GString *str) { fu_string_append_kx(str, idt, "StartAddr", fd->start_addr); fu_string_append_kx(str, idt, "BlockSize", fd->block_sz); fu_string_append_kx(str, idt, "WriteSize", fd->write_sz & ~0x8000); fu_string_append_kb(str, idt, "Protected", fu_wav_device_flash_descriptor_is_wp(fd)); } static void fu_wac_device_to_string(FuDevice *device, guint idt, GString *str) { FuWacDevice *self = FU_WAC_DEVICE(device); g_autofree gchar *status_str = NULL; if (self->firmware_index != 0xffff) { g_autofree gchar *tmp = g_strdup_printf("0x%04x", self->firmware_index); fu_string_append(str, idt, "FwIndex", tmp); } if (self->loader_ver > 0) { g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->loader_ver); fu_string_append(str, idt, "LoaderVer", tmp); } if (self->read_data_sz > 0) { g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->read_data_sz); fu_string_append(str, idt, "ReadDataSize", tmp); } if (self->write_word_sz > 0) { g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->write_word_sz); fu_string_append(str, idt, "WriteWordSize", tmp); } if (self->write_block_sz > 0) { g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->write_block_sz); fu_string_append(str, idt, "WriteBlockSize", tmp); } if (self->nr_flash_blocks > 0) { g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->nr_flash_blocks); fu_string_append(str, idt, "NrFlashBlocks", tmp); } if (self->configuration != 0xffff) { g_autofree gchar *tmp = g_strdup_printf("0x%04x", (guint)self->configuration); fu_string_append(str, idt, "Configuration", tmp); } for (guint i = 0; i < self->flash_descriptors->len; i++) { FuWacFlashDescriptor *fd = g_ptr_array_index(self->flash_descriptors, i); g_autofree gchar *title = g_strdup_printf("FlashDescriptor%02u", i); fu_string_append(str, idt, title, NULL); fu_wac_device_flash_descriptor_to_string(fd, idt + 1, str); } status_str = fu_wac_device_status_to_string(self->status_word); fu_string_append(str, idt, "Status", status_str); } gboolean fu_wac_device_get_feature_report(FuWacDevice *self, guint8 *buf, gsize bufsz, FuHidDeviceFlags flags, GError **error) { guint8 cmd = buf[0]; /* hit hardware */ if (!fu_hid_device_get_report(FU_HID_DEVICE(self), cmd, buf, bufsz, FU_WAC_DEVICE_TIMEOUT, flags | FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; /* check packet */ if (buf[0] != cmd) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "command response was %i expected %i", buf[0], cmd); return FALSE; } return TRUE; } gboolean fu_wac_device_set_feature_report(FuWacDevice *self, guint8 *buf, gsize bufsz, FuHidDeviceFlags flags, GError **error) { return fu_hid_device_set_report(FU_HID_DEVICE(self), buf[0], buf, bufsz, FU_WAC_DEVICE_TIMEOUT, flags | FU_HID_DEVICE_FLAG_IS_FEATURE | FU_HID_DEVICE_FLAG_RETRY_FAILURE, error); } static gboolean fu_wac_device_ensure_flash_descriptors(FuWacDevice *self, GError **error) { gsize sz = (self->nr_flash_blocks * 10) + 1; g_autofree guint8 *buf = NULL; /* already done */ if (self->flash_descriptors->len > 0) return TRUE; /* hit hardware */ buf = g_malloc(sz); memset(buf, 0xff, sz); buf[0] = FU_WAC_REPORT_ID_GET_FLASH_DESCRIPTOR; if (!fu_wac_device_get_feature_report(self, buf, sz, FU_HID_DEVICE_FLAG_NONE, error)) return FALSE; /* parse */ for (guint i = 0; i < self->nr_flash_blocks; i++) { g_autofree FuWacFlashDescriptor *fd = g_new0(FuWacFlashDescriptor, 1); const guint blksz = 0x0A; if (!fu_memread_uint32_safe(buf, sz, (i * blksz) + 1, &fd->start_addr, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint32_safe(buf, sz, (i * blksz) + 5, &fd->block_sz, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_memread_uint16_safe(buf, sz, (i * blksz) + 9, &fd->write_sz, G_LITTLE_ENDIAN, error)) return FALSE; g_ptr_array_add(self->flash_descriptors, g_steal_pointer(&fd)); } if (self->flash_descriptors->len > G_MAXUINT16) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "too many flash descriptors for hardware: 0x%x", self->flash_descriptors->len); return FALSE; } g_info("added %u flash descriptors", self->flash_descriptors->len); return TRUE; } static gboolean fu_wac_device_ensure_status(FuWacDevice *self, GError **error) { g_autofree gchar *str = NULL; guint8 buf[] = {[0] = FU_WAC_REPORT_ID_GET_STATUS, [1 ... 4] = 0xff}; /* hit hardware */ buf[0] = FU_WAC_REPORT_ID_GET_STATUS; if (!fu_wac_device_get_feature_report(self, buf, sizeof(buf), FU_HID_DEVICE_FLAG_NONE, error)) return FALSE; /* parse */ self->status_word = fu_memread_uint32(buf + 1, G_LITTLE_ENDIAN); str = fu_wac_device_status_to_string(self->status_word); g_debug("status now: %s", str); return TRUE; } static gboolean fu_wac_device_ensure_checksums(FuWacDevice *self, GError **error) { gsize sz = (self->nr_flash_blocks * 4) + 5; guint32 updater_version; g_autofree guint8 *buf = g_malloc(sz); /* hit hardware */ memset(buf, 0xff, sz); buf[0] = FU_WAC_REPORT_ID_GET_CHECKSUMS; if (!fu_wac_device_get_feature_report(self, buf, sz, FU_HID_DEVICE_FLAG_NONE, error)) return FALSE; /* parse */ updater_version = fu_memread_uint32(buf + 1, G_LITTLE_ENDIAN); g_info("updater-version: %" G_GUINT32_FORMAT, updater_version); /* get block checksums */ g_array_set_size(self->checksums, 0); for (guint i = 0; i < self->nr_flash_blocks; i++) { guint32 csum = fu_memread_uint32(buf + 5 + (i * 4), G_LITTLE_ENDIAN); g_debug("checksum block %02u: 0x%08x", i, (guint)csum); g_array_append_val(self->checksums, csum); } g_debug("added %u checksums", self->flash_descriptors->len); return TRUE; } static gboolean fu_wac_device_ensure_firmware_index(FuWacDevice *self, GError **error) { guint8 buf[] = {[0] = FU_WAC_REPORT_ID_GET_CURRENT_FIRMWARE_IDX, [1 ... 2] = 0xff}; /* hit hardware */ if (!fu_wac_device_get_feature_report(self, buf, sizeof(buf), FU_HID_DEVICE_FLAG_NONE, error)) return FALSE; /* parse */ self->firmware_index = fu_memread_uint16(buf + 1, G_LITTLE_ENDIAN); return TRUE; } static gboolean fu_wac_device_ensure_parameters(FuWacDevice *self, GError **error) { guint8 buf[] = {[0] = FU_WAC_REPORT_ID_GET_PARAMETERS, [1 ... 12] = 0xff}; /* hit hardware */ if (!fu_wac_device_get_feature_report(self, buf, sizeof(buf), FU_HID_DEVICE_FLAG_NONE, error)) return FALSE; /* parse */ self->loader_ver = fu_memread_uint16(buf + 1, G_LITTLE_ENDIAN); self->read_data_sz = fu_memread_uint16(buf + 3, G_LITTLE_ENDIAN); self->write_word_sz = fu_memread_uint16(buf + 5, G_LITTLE_ENDIAN); self->write_block_sz = fu_memread_uint16(buf + 7, G_LITTLE_ENDIAN); self->nr_flash_blocks = fu_memread_uint16(buf + 9, G_LITTLE_ENDIAN); self->configuration = fu_memread_uint16(buf + 11, G_LITTLE_ENDIAN); return TRUE; } static gboolean fu_wac_device_write_block(FuWacDevice *self, guint32 addr, GBytes *blob, GError **error) { const guint8 *tmp; gsize bufsz = self->write_block_sz + 5; gsize sz = 0; g_autofree guint8 *buf = NULL; /* check size */ tmp = g_bytes_get_data(blob, &sz); if (sz > self->write_block_sz) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "packet was too large at %" G_GSIZE_FORMAT " bytes", sz); return FALSE; } /* build packet */ buf = g_malloc(bufsz); memset(buf, 0xff, bufsz); buf[0] = FU_WAC_REPORT_ID_WRITE_BLOCK; fu_memwrite_uint32(buf + 1, addr, G_LITTLE_ENDIAN); if (sz > 0) { if (!fu_memcpy_safe(buf, bufsz, 0x5, /* dst */ tmp, sz, 0x0, /* src */ sz, error)) return FALSE; } /* hit hardware */ return fu_wac_device_set_feature_report(self, buf, bufsz, FU_HID_DEVICE_FLAG_NONE, error); } static gboolean fu_wac_device_erase_block(FuWacDevice *self, guint32 addr, GError **error) { guint8 buf[] = {[0] = FU_WAC_REPORT_ID_ERASE_BLOCK, [1 ... 4] = 0xff}; /* build packet */ fu_memwrite_uint32(buf + 1, addr, G_LITTLE_ENDIAN); /* hit hardware */ return fu_wac_device_set_feature_report(self, buf, sizeof(buf), FU_HID_DEVICE_FLAG_NONE, error); } gboolean fu_wac_device_update_reset(FuWacDevice *self, GError **error) { guint8 buf[] = {[0] = FU_WAC_REPORT_ID_UPDATE_RESET, [1 ... 4] = 0xff}; /* hit hardware */ return fu_wac_device_set_feature_report(self, buf, sizeof(buf), FU_HID_DEVICE_FLAG_NONE, error); } static gboolean fu_wac_device_set_checksum_of_block(FuWacDevice *self, guint16 block_nr, guint32 checksum, GError **error) { guint8 buf[] = {[0] = FU_WAC_REPORT_ID_SET_CHECKSUM_FOR_BLOCK, [1 ... 6] = 0xff}; /* build packet */ fu_memwrite_uint16(buf + 1, block_nr, G_LITTLE_ENDIAN); fu_memwrite_uint32(buf + 3, checksum, G_LITTLE_ENDIAN); /* hit hardware */ return fu_wac_device_set_feature_report(self, buf, sizeof(buf), FU_HID_DEVICE_FLAG_NONE, error); } static gboolean fu_wac_device_calculate_checksum_of_block(FuWacDevice *self, guint16 block_nr, GError **error) { guint8 buf[] = {[0] = FU_WAC_REPORT_ID_CALCULATE_CHECKSUM_FOR_BLOCK, [1 ... 2] = 0xff}; /* build packet */ fu_memwrite_uint16(buf + 1, block_nr, G_LITTLE_ENDIAN); /* hit hardware */ return fu_wac_device_set_feature_report(self, buf, sizeof(buf), FU_HID_DEVICE_FLAG_NONE, error); } static gboolean fu_wac_device_write_checksum_table(FuWacDevice *self, GError **error) { guint8 buf[] = {[0] = FU_WAC_REPORT_ID_WRITE_CHECKSUM_TABLE, [1 ... 4] = 0xff}; /* hit hardware */ return fu_wac_device_set_feature_report(self, buf, sizeof(buf), FU_HID_DEVICE_FLAG_NONE, error); } gboolean fu_wac_device_switch_to_flash_loader(FuWacDevice *self, GError **error) { guint8 buf[] = {[0] = FU_WAC_REPORT_ID_SWITCH_TO_FLASH_LOADER, [1] = 0x05, [2] = 0x6a}; /* hit hardware */ return fu_wac_device_set_feature_report(self, buf, sizeof(buf), FU_HID_DEVICE_FLAG_NONE, error); } static gboolean fu_wac_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuWacDevice *self = FU_WAC_DEVICE(device); gsize blocks_done = 0; gsize blocks_total = 0; g_autofree guint32 *csum_local = NULL; g_autoptr(FuFirmware) img = NULL; g_autoptr(GHashTable) fd_blobs = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 1, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 95, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 2, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); /* get current selected device */ if (!fu_wac_device_ensure_firmware_index(self, error)) return FALSE; /* use the correct image from the firmware */ img = fu_firmware_get_image_by_idx(firmware, self->firmware_index == 1 ? 1 : 0, error); if (img == NULL) return FALSE; g_debug("using image at addr 0x%0x", (guint)fu_firmware_get_addr(img)); /* get firmware parameters (page sz and transfer sz) */ if (!fu_wac_device_ensure_parameters(self, error)) return FALSE; /* get the current flash descriptors */ if (!fu_wac_device_ensure_flash_descriptors(self, error)) return FALSE; /* get the updater protocol version */ if (!fu_wac_device_ensure_checksums(self, error)) return FALSE; fu_progress_step_done(progress); /* clear all checksums of pages */ for (guint i = 0; i < self->flash_descriptors->len; i++) { FuWacFlashDescriptor *fd = g_ptr_array_index(self->flash_descriptors, i); if (fu_wav_device_flash_descriptor_is_wp(fd)) continue; if (!fu_wac_device_set_checksum_of_block(self, i, 0x0, error)) return FALSE; } fu_progress_step_done(progress); /* get the blobs for each chunk */ fd_blobs = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)g_bytes_unref); for (guint i = 0; i < self->flash_descriptors->len; i++) { FuWacFlashDescriptor *fd = g_ptr_array_index(self->flash_descriptors, i); GBytes *blob_block; g_autoptr(GBytes) blob_tmp = NULL; if (fu_wav_device_flash_descriptor_is_wp(fd)) continue; blob_tmp = fu_firmware_write_chunk(img, fd->start_addr, fd->block_sz, NULL); if (blob_tmp == NULL) break; blob_block = fu_bytes_pad(blob_tmp, fd->block_sz); g_hash_table_insert(fd_blobs, fd, blob_block); } /* checksum actions post-write */ blocks_total = g_hash_table_size(fd_blobs); /* write the data into the flash page */ csum_local = g_new0(guint32, self->flash_descriptors->len); for (guint i = 0; i < self->flash_descriptors->len; i++) { FuWacFlashDescriptor *fd = g_ptr_array_index(self->flash_descriptors, i); GBytes *blob_block; g_autoptr(FuChunkArray) chunks = NULL; /* if page is protected */ if (fu_wav_device_flash_descriptor_is_wp(fd)) continue; /* get data for page */ blob_block = g_hash_table_lookup(fd_blobs, fd); if (blob_block == NULL) break; /* ignore empty blocks */ if (fu_bytes_is_empty(blob_block)) { g_debug("empty block, ignoring"); fu_progress_set_percentage_full(fu_progress_get_child(progress), blocks_done++, blocks_total); continue; } /* erase entire block */ if (!fu_wac_device_erase_block(self, i, error)) return FALSE; /* write block in chunks */ chunks = fu_chunk_array_new_from_bytes(blob_block, fd->start_addr, self->write_block_sz); for (guint j = 0; j < fu_chunk_array_length(chunks); j++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, j); g_autoptr(GBytes) blob_chunk = fu_chunk_get_bytes(chk); if (!fu_wac_device_write_block(self, fu_chunk_get_address(chk), blob_chunk, error)) return FALSE; } /* calculate expected checksum and save to device RAM */ csum_local[i] = GUINT32_TO_LE(fu_sum32w_bytes(blob_block, G_LITTLE_ENDIAN)); g_debug("block checksum %02u: 0x%08x", i, csum_local[i]); if (!fu_wac_device_set_checksum_of_block(self, i, csum_local[i], error)) return FALSE; /* update device progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), blocks_done++, blocks_total); } fu_progress_step_done(progress); /* check at least one block was written */ if (blocks_done == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "empty firmware image or all blocks write-protected"); return FALSE; } /* calculate CRC inside device */ for (guint i = 0; i < self->flash_descriptors->len; i++) { if (!fu_wac_device_calculate_checksum_of_block(self, i, error)) return FALSE; } /* read all CRC of all pages and verify with local CRC */ if (!fu_wac_device_ensure_checksums(self, error)) return FALSE; for (guint i = 0; i < self->flash_descriptors->len; i++) { FuWacFlashDescriptor *fd = g_ptr_array_index(self->flash_descriptors, i); GBytes *blob_block; guint32 csum_rom; /* if page is protected */ if (fu_wav_device_flash_descriptor_is_wp(fd)) continue; /* no more written pages */ blob_block = g_hash_table_lookup(fd_blobs, fd); if (blob_block == NULL) continue; if (fu_bytes_is_empty(blob_block)) continue; /* check checksum matches */ csum_rom = g_array_index(self->checksums, guint32, i); if (csum_rom != csum_local[i]) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed local checksum at block %u, " "got 0x%08x expected 0x%08x", i, (guint)csum_rom, (guint)csum_local[i]); return FALSE; } g_debug("matched checksum at block %u of 0x%08x", i, csum_rom); } fu_progress_step_done(progress); /* store host CRC into flash */ if (!fu_wac_device_write_checksum_table(self, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_wac_device_add_modules_bluetooth(FuWacDevice *self, GError **error) { g_autofree gchar *name = NULL; g_autofree gchar *name_id6 = NULL; g_autoptr(FuWacModule) module = NULL; g_autoptr(FuWacModule) module_id6 = NULL; guint16 fw_ver; /* it can take up to 5s to get the new version after a fw update */ for (guint i = 0; i < 5; i++) { guint8 buf[] = {[0] = FU_WAC_REPORT_ID_GET_FIRMWARE_VERSION_BLUETOOTH, [1 ... 14] = 0xff}; if (!fu_wac_device_get_feature_report(self, buf, sizeof(buf), FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "Failed to get GetFirmwareVersionBluetooth: "); return FALSE; } if (!fu_memread_uint16_safe(buf, sizeof(buf), 1, &fw_ver, G_LITTLE_ENDIAN, error)) return FALSE; if (fw_ver != 0) break; fu_device_sleep(FU_DEVICE(self), 1000); /* ms */ } /* Success! But legacy bluetooth can't tell us which module the device needs. * Initialize both and rely on the firmware update containing the appropriate * package. */ name = g_strdup_printf("%s [Legacy Bluetooth Module]", fu_device_get_name(FU_DEVICE(self))); module = fu_wac_module_bluetooth_new(FU_DEVICE(self)); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module)); fu_device_set_name(FU_DEVICE(module), name); fu_device_set_version_u16(FU_DEVICE(module), fw_ver); name_id6 = g_strdup_printf("%s [Legacy Bluetooth Module (ID6)]", fu_device_get_name(FU_DEVICE(self))); module_id6 = fu_wac_module_bluetooth_id6_new(FU_DEVICE(self)); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module_id6)); fu_device_set_name(FU_DEVICE(module_id6), name_id6); fu_device_set_version_u16(FU_DEVICE(module_id6), fw_ver); return TRUE; } static gboolean fu_wac_device_add_modules_legacy(FuWacDevice *self, GError **error) { g_autoptr(GError) error_bt = NULL; /* optional bluetooth */ if (!fu_wac_device_add_modules_bluetooth(self, &error_bt)) g_debug("no bluetooth hardware: %s", error_bt->message); return TRUE; } static gboolean fu_wac_device_add_modules(FuWacDevice *self, GError **error) { g_autofree gchar *version_bootloader = NULL; guint8 buf[] = {[0] = FU_WAC_REPORT_ID_FW_DESCRIPTOR, [1 ... 31] = 0xff}; guint16 boot_ver; if (!fu_wac_device_get_feature_report(self, buf, sizeof(buf), FU_HID_DEVICE_FLAG_NONE, error)) { g_prefix_error(error, "Failed to get DeviceFirmwareDescriptor: "); return FALSE; } /* verify bootloader is compatible */ if (buf[1] != 0x01) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "bootloader major version not compatible"); return FALSE; } /* verify the number of submodules is possible */ if (buf[3] > (512 - 4) / 4) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "number of submodules is impossible"); return FALSE; } /* bootloader version */ if (!fu_memread_uint16_safe(buf, sizeof(buf), 1, &boot_ver, G_BIG_ENDIAN, error)) return FALSE; version_bootloader = fu_version_from_uint16(boot_ver, FWUPD_VERSION_FORMAT_BCD); fu_device_set_version_bootloader(FU_DEVICE(self), version_bootloader); fu_device_set_version_bootloader_raw(FU_DEVICE(self), boot_ver); /* get versions of each submodule */ for (guint8 i = 0; i < buf[3]; i++) { guint8 fw_type = buf[(i * 4) + 4] & ~0x80; g_autofree gchar *name = NULL; g_autoptr(FuWacModule) module = NULL; guint16 ver; if (!fu_memread_uint16_safe(buf, sizeof(buf), (i * 4) + 5, &ver, G_BIG_ENDIAN, error)) return FALSE; switch (fw_type) { case FU_WAC_MODULE_FW_TYPE_TOUCH: module = fu_wac_module_touch_new(FU_DEVICE(self)); name = g_strdup_printf("%s [Touch Module]", fu_device_get_name(FU_DEVICE(self))); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module)); fu_device_set_name(FU_DEVICE(module), name); fu_device_set_version_u16(FU_DEVICE(module), ver); break; case FU_WAC_MODULE_FW_TYPE_TOUCH_ID7: module = fu_wac_module_touch_id7_new(FU_DEVICE(self)); name = g_strdup_printf("%s [Touch Module]", fu_device_get_name(FU_DEVICE(self))); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module)); fu_device_set_name(FU_DEVICE(module), name); fu_device_set_summary(FU_DEVICE(module), "ID7"); fu_device_set_version_u16(FU_DEVICE(module), ver); break; case FU_WAC_MODULE_FW_TYPE_BLUETOOTH: module = fu_wac_module_bluetooth_new(FU_DEVICE(self)); name = g_strdup_printf("%s [Bluetooth Module]", fu_device_get_name(FU_DEVICE(self))); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module)); fu_device_set_name(FU_DEVICE(module), name); fu_device_set_version_u16(FU_DEVICE(module), ver); break; case FU_WAC_MODULE_FW_TYPE_BLUETOOTH_ID6: module = fu_wac_module_bluetooth_id6_new(FU_DEVICE(self)); name = g_strdup_printf("%s [Bluetooth Module]", fu_device_get_name(FU_DEVICE(self))); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module)); fu_device_set_name(FU_DEVICE(module), name); fu_device_set_summary(FU_DEVICE(module), "ID6"); fu_device_set_version_u16(FU_DEVICE(module), ver); break; case FU_WAC_MODULE_FW_TYPE_SCALER: module = fu_wac_module_scaler_new(FU_DEVICE(self)); name = g_strdup_printf("%s [Scaler Module]", fu_device_get_name(FU_DEVICE(self))); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module)); fu_device_set_name(FU_DEVICE(module), name); fu_device_set_version_u16(FU_DEVICE(module), ver); break; case FU_WAC_MODULE_FW_TYPE_BLUETOOTH_ID9: module = fu_wac_module_bluetooth_id9_new(FU_DEVICE(self)); name = g_strdup_printf("%s [Bluetooth Module]", fu_device_get_name(FU_DEVICE(self))); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(module)); fu_device_set_name(FU_DEVICE(module), name); fu_device_set_summary(FU_DEVICE(module), "ID9"); fu_device_set_version_u16(FU_DEVICE(module), ver); break; case FU_WAC_MODULE_FW_TYPE_MAIN: fu_device_set_version_u16(FU_DEVICE(self), ver); break; default: g_warning("unknown submodule type 0x%0x", fw_type); break; } } return TRUE; } static gboolean fu_wac_device_setup(FuDevice *device, GError **error) { FuWacDevice *self = FU_WAC_DEVICE(device); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_wac_device_parent_class)->setup(device, error)) return FALSE; /* get current status */ if (!fu_wac_device_ensure_status(self, error)) return FALSE; /* get version of each sub-module */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION)) { if (!fu_wac_device_add_modules_legacy(self, error)) return FALSE; } else { if (!fu_wac_device_add_modules(self, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_wac_device_close(FuDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device)); /* reattach wacom.ko */ if (!g_usb_device_release_interface(usb_device, 0x00, /* HID */ G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error)) { g_prefix_error(error, "failed to re-attach interface: "); return FALSE; } /* The hidcore subsystem uses a generic power_supply that has a deferred * work item that will lock the device. When removing the power_supply, * we take the lock, then cancel the work item which needs to take the * lock too. This needs to be fixed in the kernel, but for the moment * this should let the kernel unstick itself. */ fu_device_sleep(device, 20); /* ms */ /* FuUsbDevice->close */ return FU_DEVICE_CLASS(fu_wac_device_parent_class)->close(device, error); } static void fu_wac_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload"); } static void fu_wac_device_init(FuWacDevice *self) { self->flash_descriptors = g_ptr_array_new_with_free_func(g_free); self->checksums = g_array_new(FALSE, FALSE, sizeof(guint32)); self->configuration = 0xffff; self->firmware_index = 0xffff; fu_device_add_protocol(FU_DEVICE(self), "com.wacom.usb"); fu_device_add_icon(FU_DEVICE(self), "input-tablet"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_BCD); fu_device_set_install_duration(FU_DEVICE(self), 10); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_WAC_FIRMWARE); fu_device_retry_set_delay(FU_DEVICE(self), 30); /* ms */ } static void fu_wac_device_finalize(GObject *object) { FuWacDevice *self = FU_WAC_DEVICE(object); g_ptr_array_unref(self->flash_descriptors); g_array_unref(self->checksums); G_OBJECT_CLASS(fu_wac_device_parent_class)->finalize(object); } static void fu_wac_device_class_init(FuWacDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_wac_device_finalize; klass_device->write_firmware = fu_wac_device_write_firmware; klass_device->to_string = fu_wac_device_to_string; klass_device->setup = fu_wac_device_setup; klass_device->close = fu_wac_device_close; klass_device->set_progress = fu_wac_device_set_progress; } fwupd-1.9.16/plugins/wacom-usb/fu-wac-device.h000066400000000000000000000013331460375044200211100ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_WAC_DEVICE (fu_wac_device_get_type()) G_DECLARE_FINAL_TYPE(FuWacDevice, fu_wac_device, FU, WAC_DEVICE, FuHidDevice) gboolean fu_wac_device_get_feature_report(FuWacDevice *self, guint8 *buf, gsize bufsz, FuHidDeviceFlags flags, GError **error); gboolean fu_wac_device_set_feature_report(FuWacDevice *self, guint8 *buf, gsize bufsz, FuHidDeviceFlags flags, GError **error); gboolean fu_wac_device_switch_to_flash_loader(FuWacDevice *self, GError **error); gboolean fu_wac_device_update_reset(FuWacDevice *self, GError **error); fwupd-1.9.16/plugins/wacom-usb/fu-wac-firmware.c000066400000000000000000000254111460375044200214630ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wac-firmware.h" struct _FuWacFirmware { FuFirmware parent_instance; }; G_DEFINE_TYPE(FuWacFirmware, fu_wac_firmware, FU_TYPE_FIRMWARE) #define FU_WAC_FIRMWARE_TOKENS_MAX 100000 /* lines */ #define FU_WAC_FIRMWARE_SECTIONS_MAX 10 typedef struct { guint32 addr; guint32 sz; guint32 prog_start_addr; } FuFirmwareWacHeaderRecord; typedef struct { FuFirmware *firmware; FwupdInstallFlags flags; GPtrArray *header_infos; GString *image_buffer; guint8 images_cnt; } FuWacFirmwareTokenHelper; static gboolean fu_wac_firmware_tokenize_cb(GString *token, guint token_idx, gpointer user_data, GError **error) { FuWacFirmwareTokenHelper *helper = (FuWacFirmwareTokenHelper *)user_data; g_autofree gchar *cmd = NULL; /* sanity check */ if (token_idx > FU_WAC_FIRMWARE_TOKENS_MAX) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "file has too many lines"); return FALSE; } /* remove WIN32 line endings */ g_strdelimit(token->str, "\r\x1a", '\0'); token->len = strlen(token->str); /* ignore blank lines */ cmd = g_strndup(token->str, 2); if (g_strcmp0(cmd, "") == 0) return TRUE; /* Wacom-specific metadata */ if (g_strcmp0(cmd, "WA") == 0) { /* header info record */ if (token->len > 3 && memcmp(token->str + 2, "COM", 3) == 0) { guint8 header_image_cnt = 0; if (token->len != 40) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid header, got %" G_GSIZE_FORMAT " bytes", token->len); return FALSE; } /* sanity check */ if (helper->header_infos->len > FU_WAC_FIRMWARE_SECTIONS_MAX) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "too many metadata sections: %u", helper->header_infos->len); return FALSE; } if (!fu_firmware_strparse_uint4_safe(token->str, token->len, 5, &header_image_cnt, error)) return FALSE; for (guint j = 0; j < header_image_cnt; j++) { g_autofree FuFirmwareWacHeaderRecord *hdr = NULL; hdr = g_new0(FuFirmwareWacHeaderRecord, 1); if (!fu_firmware_strparse_uint32_safe(token->str, token->len, (j * 16) + 6, &hdr->addr, error)) return FALSE; if (!fu_firmware_strparse_uint32_safe(token->str, token->len, (j * 16) + 14, &hdr->sz, error)) return FALSE; g_debug("header_fw%u_addr: 0x%x", j, hdr->addr); g_debug("header_fw%u_sz: 0x%x", j, hdr->sz); g_ptr_array_add(helper->header_infos, g_steal_pointer(&hdr)); } return TRUE; } /* firmware headline record */ if (token->len == 13) { FuFirmwareWacHeaderRecord *hdr; guint8 idx = 0; if (!fu_firmware_strparse_uint4_safe(token->str, token->len, 2, &idx, error)) return FALSE; if (idx == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "headline %u invalid", idx); return FALSE; } if (idx > helper->header_infos->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "headline %u exceeds header count %u", idx, helper->header_infos->len); return FALSE; } if (idx - 1 != helper->images_cnt) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "headline %u is not in sorted order", idx); return FALSE; } hdr = g_ptr_array_index(helper->header_infos, idx - 1); if (!fu_firmware_strparse_uint32_safe(token->str, token->len, 3, &hdr->prog_start_addr, error)) return FALSE; if (hdr->prog_start_addr != hdr->addr) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "programming address 0x%x != " "base address 0x%0x for idx %u", hdr->prog_start_addr, hdr->addr, idx); return FALSE; } g_debug("programing-start-address: 0x%x", hdr->prog_start_addr); return TRUE; } g_debug("unknown Wacom-specific metadata"); return TRUE; } /* start */ if (g_strcmp0(cmd, "S0") == 0) { if (helper->image_buffer->len > 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "duplicate S0 without S7"); return FALSE; } g_string_append_printf(helper->image_buffer, "%s\n", token->str); return TRUE; } /* these are things we want to include in the image */ if (g_strcmp0(cmd, "S1") == 0 || g_strcmp0(cmd, "S2") == 0 || g_strcmp0(cmd, "S3") == 0 || g_strcmp0(cmd, "S5") == 0 || g_strcmp0(cmd, "S7") == 0 || g_strcmp0(cmd, "S8") == 0 || g_strcmp0(cmd, "S9") == 0) { if (helper->image_buffer->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s without S0", cmd); return FALSE; } g_string_append_printf(helper->image_buffer, "%s\n", token->str); } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid SREC command on line %u: %s", token_idx + 1, cmd); return FALSE; } /* end */ if (g_strcmp0(cmd, "S7") == 0) { g_autoptr(GBytes) blob = NULL; g_autoptr(GBytes) fw_srec = NULL; g_autoptr(FuFirmware) firmware_srec = fu_srec_firmware_new(); g_autoptr(FuFirmware) img = fu_firmware_new(); FuFirmwareWacHeaderRecord *hdr; /* get the correct relocated start address */ if (helper->images_cnt >= helper->header_infos->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s without header", cmd); return FALSE; } hdr = g_ptr_array_index(helper->header_infos, helper->images_cnt); if (helper->image_buffer->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "%s with missing image buffer", cmd); return FALSE; } /* parse SREC file and add as image */ blob = g_bytes_new(helper->image_buffer->str, helper->image_buffer->len); fu_srec_firmware_set_addr_min(FU_SREC_FIRMWARE(firmware_srec), hdr->addr); if (!fu_firmware_parse_full(firmware_srec, blob, 0x0, helper->flags | FWUPD_INSTALL_FLAG_NO_SEARCH, error)) return FALSE; fw_srec = fu_firmware_get_bytes(firmware_srec, error); if (fw_srec == NULL) return FALSE; fu_firmware_set_bytes(img, fw_srec); fu_firmware_set_addr(img, fu_firmware_get_addr(firmware_srec)); fu_firmware_set_idx(img, helper->images_cnt); if (!fu_firmware_add_image_full(helper->firmware, img, error)) return FALSE; helper->images_cnt++; /* clear the image buffer */ g_string_set_size(helper->image_buffer, 0); } /* success */ return TRUE; } static gboolean fu_wac_firmware_check_magic(FuFirmware *firmware, GBytes *fw, gsize offset, GError **error) { guint8 magic[5] = "WACOM"; return fu_memcmp_safe(g_bytes_get_data(fw, NULL), g_bytes_get_size(fw), offset, magic, sizeof(magic), 0x0, sizeof(magic), error); } static gboolean fu_wac_firmware_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(fw, &bufsz); g_autoptr(GPtrArray) header_infos = g_ptr_array_new_with_free_func(g_free); g_autoptr(GString) image_buffer = g_string_new(NULL); FuWacFirmwareTokenHelper helper = {.firmware = firmware, .flags = flags, .header_infos = header_infos, .image_buffer = image_buffer, .images_cnt = 0}; /* tokenize */ if (!fu_strsplit_full((const gchar *)buf + offset, bufsz - offset, "\n", fu_wac_firmware_tokenize_cb, &helper, error)) return FALSE; /* verify data is complete */ if (helper.image_buffer->len > 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "truncated data: no S7"); return FALSE; } /* ensure this matched the header */ if (helper.header_infos->len != helper.images_cnt) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "not enough images %u for header count %u", helper.images_cnt, header_infos->len); return FALSE; } /* success */ return TRUE; } static guint8 fu_wac_firmware_calc_checksum(GByteArray *buf) { return fu_sum8(buf->data, buf->len) ^ 0xFF; } static GByteArray * fu_wac_firmware_write(FuFirmware *firmware, GError **error) { g_autoptr(GPtrArray) images = fu_firmware_get_images(firmware); g_autoptr(GString) str = g_string_new(NULL); g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GByteArray) buf_hdr = g_byte_array_new(); /* fw header */ if (images->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no firmware images found"); return NULL; } for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); fu_byte_array_append_uint32(buf_hdr, fu_firmware_get_addr(img), G_BIG_ENDIAN); fu_byte_array_append_uint32(buf_hdr, fu_firmware_get_size(img), G_BIG_ENDIAN); } g_string_append_printf(str, "WACOM%u", images->len); for (guint i = 0; i < buf_hdr->len; i++) g_string_append_printf(str, "%02X", buf_hdr->data[i]); g_string_append_printf(str, "%02X\n", fu_wac_firmware_calc_checksum(buf_hdr)); /* payload */ for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autoptr(GBytes) img_blob = NULL; g_autoptr(GByteArray) buf_img = g_byte_array_new(); /* img header */ g_string_append_printf(str, "WA%u", (guint)fu_firmware_get_idx(img) + 1); fu_byte_array_append_uint32(buf_img, fu_firmware_get_addr(img), G_BIG_ENDIAN); for (guint j = 0; j < buf_img->len; j++) g_string_append_printf(str, "%02X", buf_img->data[j]); g_string_append_printf(str, "%02X\n", fu_wac_firmware_calc_checksum(buf_img)); /* srec */ img_blob = fu_firmware_write(img, error); if (img_blob == NULL) return NULL; g_string_append_len(str, (const gchar *)g_bytes_get_data(img_blob, NULL), g_bytes_get_size(img_blob)); } /* success */ g_byte_array_append(buf, (const guint8 *)str->str, str->len); return g_steal_pointer(&buf); } static void fu_wac_firmware_init(FuWacFirmware *self) { fu_firmware_set_images_max(FU_FIRMWARE(self), 1024); } static void fu_wac_firmware_class_init(FuWacFirmwareClass *klass) { FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); klass_firmware->check_magic = fu_wac_firmware_check_magic; klass_firmware->parse = fu_wac_firmware_parse; klass_firmware->write = fu_wac_firmware_write; } FuFirmware * fu_wac_firmware_new(void) { return FU_FIRMWARE(g_object_new(FU_TYPE_WAC_FIRMWARE, NULL)); } fwupd-1.9.16/plugins/wacom-usb/fu-wac-firmware.h000066400000000000000000000005121460375044200214630ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_WAC_FIRMWARE (fu_wac_firmware_get_type()) G_DECLARE_FINAL_TYPE(FuWacFirmware, fu_wac_firmware, FU, WAC_FIRMWARE, FuFirmware) FuFirmware * fu_wac_firmware_new(void); fwupd-1.9.16/plugins/wacom-usb/fu-wac-module-bluetooth-id6.c000066400000000000000000000130211460375044200236110ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2021 Jason Gerecke * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wac-common.h" #include "fu-wac-device.h" #include "fu-wac-module-bluetooth-id6.h" #include "fu-wac-struct.h" struct _FuWacModuleBluetoothId6 { FuWacModule parent_instance; }; G_DEFINE_TYPE(FuWacModuleBluetoothId6, fu_wac_module_bluetooth_id6, FU_TYPE_WAC_MODULE) #define FU_WAC_MODULE_BLUETOOTH_ID6_CRC8_POLYNOMIAL 0x31 #define FU_WAC_MODULE_BLUETOOTH_ID6_PAYLOAD_SZ 256 #define FU_WAC_MODULE_BLUETOOTH_ID6_START_NORMAL 0x00 #define FU_WAC_MODULE_BLUETOOTH_ID6_START_FULLERASE 0xFE #define FU_WAC_MODULE_BLUETOOTH_ID6_START_TIMEOUT 60000 /* ms */ #define FU_WAC_MODULE_BLUETOOTH_ID6_END_TIMEOUT 60000 /* ms */ static guint8 fu_wac_module_bluetooth_id6_reverse_bits(guint8 value) { guint8 reverse = 0; for (gint i = 0; i < 8; i++) { reverse <<= 1; reverse |= (value & 0x01); value >>= 1; } return reverse; } static guint8 fu_wac_module_bluetooth_id6_calculate_crc(const guint8 *data, gsize sz) { guint8 crc = ~fu_crc8_full(data, sz, 0x00, FU_WAC_MODULE_BLUETOOTH_ID6_CRC8_POLYNOMIAL); return fu_wac_module_bluetooth_id6_reverse_bits(crc); } static gboolean fu_wac_module_bluetooth_id6_write_blob(FuWacModule *self, GBytes *fw, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(fw, 0x0, FU_WAC_MODULE_BLUETOOTH_ID6_PAYLOAD_SZ); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); guint8 buf[FU_WAC_MODULE_BLUETOOTH_ID6_PAYLOAD_SZ + 7] = {0x00, 0x01, 0xFF}; g_autoptr(GBytes) blob_chunk = NULL; /* build data packet */ fu_memwrite_uint32(buf + 0x3, 0x0, G_LITTLE_ENDIAN); /* addr, always zero */ memcpy(buf + 0x7, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); buf[2] = fu_wac_module_bluetooth_id6_calculate_crc( buf + 0x7, FU_WAC_MODULE_BLUETOOTH_ID6_PAYLOAD_SZ); /* include 0xFF for the possibly incomplete last chunk */ blob_chunk = g_bytes_new(buf, sizeof(buf)); g_debug("writing block %u of %u", i, fu_chunk_array_length(chunks) - 1); if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_DATA, blob_chunk, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_DATA_TIMEOUT, error)) { g_prefix_error(error, "failed to write block %u of %u: ", i, fu_chunk_array_length(chunks) - 1); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_wac_module_bluetooth_id6_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuWacModule *self = FU_WAC_MODULE(device); const guint8 buf_start[] = {FU_WAC_MODULE_BLUETOOTH_ID6_START_NORMAL}; g_autoptr(GBytes) blob_start = g_bytes_new_static(buf_start, sizeof(buf_start)); g_autoptr(GBytes) fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 8, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 59, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 33, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) { g_prefix_error(error, "wacom bluetooth-id6 module failed to get bytes: "); return FALSE; } /* start, which will erase the module */ if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_START, blob_start, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_BLUETOOTH_ID6_START_TIMEOUT, error)) { g_prefix_error(error, "wacom bluetooth-id6 module failed to erase: "); return FALSE; } fu_progress_step_done(progress); /* data */ if (!fu_wac_module_bluetooth_id6_write_blob(self, fw, fu_progress_get_child(progress), error)) { g_prefix_error(error, "wacom bluetooth-id6 module failed to write: "); return FALSE; } fu_progress_step_done(progress); /* end */ if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_END, NULL, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_BLUETOOTH_ID6_END_TIMEOUT, error)) { g_prefix_error(error, "wacom bluetooth-id6 module failed to end: "); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_wac_module_bluetooth_id6_init(FuWacModuleBluetoothId6 *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_install_duration(FU_DEVICE(self), 120); fu_device_set_remove_delay(FU_DEVICE(self), 300000); } static void fu_wac_module_bluetooth_id6_class_init(FuWacModuleBluetoothId6Class *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_wac_module_bluetooth_id6_write_firmware; } FuWacModule * fu_wac_module_bluetooth_id6_new(FuDevice *proxy) { FuWacModule *module = NULL; module = g_object_new(FU_TYPE_WAC_MODULE_BLUETOOTH_ID6, "proxy", proxy, "fw-type", FU_WAC_MODULE_FW_TYPE_BLUETOOTH_ID6, NULL); return module; } fwupd-1.9.16/plugins/wacom-usb/fu-wac-module-bluetooth-id6.h000066400000000000000000000007661460375044200236320ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2021 Jason Gerecke * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wac-module.h" #define FU_TYPE_WAC_MODULE_BLUETOOTH_ID6 (fu_wac_module_bluetooth_id6_get_type()) G_DECLARE_FINAL_TYPE(FuWacModuleBluetoothId6, fu_wac_module_bluetooth_id6, FU, WAC_MODULE_BLUETOOTH_ID6, FuWacModule) FuWacModule * fu_wac_module_bluetooth_id6_new(FuDevice *proxy); fwupd-1.9.16/plugins/wacom-usb/fu-wac-module-bluetooth-id9.c000066400000000000000000000221071460375044200236210ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2021-2023 Jason Gerecke * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wac-common.h" #include "fu-wac-device.h" #include "fu-wac-module-bluetooth-id9.h" #include "fu-wac-struct.h" struct _FuWacModuleBluetoothId9 { FuWacModule parent_instance; }; G_DEFINE_TYPE(FuWacModuleBluetoothId9, fu_wac_module_bluetooth_id9, FU_TYPE_WAC_MODULE) #define FU_WAC_MODULE_BLUETOOTH_ID9_PAYLOAD_SZ 256 #define FU_WAC_MODULE_BLUETOOTH_ID9_START_NORMAL 0x00 #define FU_WAC_MODULE_BLUETOOTH_ID9_CMD_NORMAL 0x00 #define FU_WAC_MODULE_BLUETOOTH_ID9_CMD_FULLERASE 0xFE #define FU_WAC_MODULE_BLUETOOTH_ID9_LOADER_RAM 0x02 #define FU_WAC_MODULE_BLUETOOTH_ID9_LOADER_BEGIN 0x03 #define FU_WAC_MODULE_BLUETOOTH_ID9_LOADER_DATA 0x04 #define FU_WAC_MODULE_BLUETOOTH_ID9_CRC_POLYNOMIAL 0xEDB88320 #define FU_WAC_MODULE_BLUETOOTH_ID9_POLL_INTERVAL 5 /* ms */ #define FU_WAC_MODULE_BLUETOOTH_ID9_START_TIMEOUT 75000 /* ms */ #define FU_WAC_MODULE_BLUETOOTH_ID9_DATA_TIMEOUT 10000 /* ms */ #define FU_WAC_MODULE_BLUETOOTH_ID9_END_TIMEOUT 10000 /* ms */ static FuChunk * fu_wac_module_bluetooth_id9_get_startcmd(GBytes *data, gboolean full_erase, GError **error) { guint8 command = full_erase ? FU_WAC_MODULE_BLUETOOTH_ID9_CMD_FULLERASE : FU_WAC_MODULE_BLUETOOTH_ID9_CMD_NORMAL; gsize bufsz = 0; const guint8 *buf = g_bytes_get_data(data, &bufsz); guint32 crc; g_autoptr(GByteArray) loader_cmd = fu_struct_id9_loader_cmd_new(); g_autoptr(GByteArray) spi_cmd = fu_struct_id9_spi_cmd_new(); g_autoptr(GByteArray) unknown_cmd = fu_struct_id9_unknown_cmd_new(); fu_struct_id9_unknown_cmd_set_size(unknown_cmd, bufsz); fu_struct_id9_spi_cmd_set_size(spi_cmd, bufsz + FU_STRUCT_ID9_UNKNOWN_CMD_SIZE); if (!fu_struct_id9_spi_cmd_set_data(spi_cmd, unknown_cmd, error)) return NULL; fu_struct_id9_loader_cmd_set_command(loader_cmd, command); fu_struct_id9_loader_cmd_set_size(loader_cmd, bufsz + FU_STRUCT_ID9_SPI_CMD_SIZE); /* CRC(concat(spi_cmd, *data)) */ crc = fu_crc32_full(spi_cmd->data, FU_STRUCT_ID9_SPI_CMD_SIZE, ~0, FU_WAC_MODULE_BLUETOOTH_ID9_CRC_POLYNOMIAL); crc = fu_crc32_full(buf, bufsz, ~crc, FU_WAC_MODULE_BLUETOOTH_ID9_CRC_POLYNOMIAL); fu_struct_id9_loader_cmd_set_crc(loader_cmd, crc); if (!fu_struct_id9_loader_cmd_set_data(loader_cmd, spi_cmd, error)) return NULL; if (!fu_struct_id9_loader_cmd_validate(loader_cmd->data, loader_cmd->len, 0, error)) return NULL; return fu_chunk_bytes_new(g_bytes_new(loader_cmd->data, loader_cmd->len)); } static gboolean fu_wac_module_bluetooth_id9_write_block(FuWacModule *self, guint8 phase, FuChunk *chunk, FuProgress *progress, GError **error) { g_autoptr(GByteArray) buf = g_byte_array_new(); g_autoptr(GBytes) blob_chunk = NULL; fu_byte_array_append_uint8(buf, phase); g_byte_array_append(buf, fu_chunk_get_data(chunk), fu_chunk_get_data_sz(chunk)); blob_chunk = g_bytes_new(buf->data, buf->len); return fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_DATA, blob_chunk, fu_progress_get_child(progress), FU_WAC_MODULE_BLUETOOTH_ID9_POLL_INTERVAL, FU_WAC_MODULE_BLUETOOTH_ID9_DATA_TIMEOUT, error); } static gboolean fu_wac_module_bluetooth_id9_write_blocks(FuWacModule *self, guint8 phase, GBytes *data, gsize block_len, FuProgress *progress, GError **error) { g_autoptr(FuChunkArray) chunks = fu_chunk_array_new_from_bytes(data, 0, block_len); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); if (!fu_wac_module_bluetooth_id9_write_block(self, phase, chk, progress, error)) return FALSE; fu_progress_step_done(progress); } return TRUE; } static FuFirmware * fu_wac_module_bluetooth_id9_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { const guint8 *blob; gsize blob_len = 0; guint16 loader_len = 0; gsize payload_len = 0; g_autoptr(GBytes) loader_bytes = NULL; g_autoptr(GBytes) payload_bytes = NULL; g_autoptr(FuFirmware) firmware = fu_firmware_new(); g_autoptr(FuFirmware) loader_fw = NULL; g_autoptr(FuFirmware) payload_fw = NULL; /* The firmware file is formatted as a 2 byte "length" field * followed by bytes of loader code. The remainder * payload is firmware data. */ blob = g_bytes_get_data(fw, &blob_len); if (!fu_memread_uint16_safe(blob, blob_len, 0, &loader_len, G_BIG_ENDIAN, error)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid firmware size"); return NULL; } if (loader_len > blob_len - 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "invalid firmware loader size"); return NULL; } loader_bytes = fu_bytes_new_offset(fw, 2, loader_len, error); if (loader_bytes == NULL) return NULL; loader_fw = fu_firmware_new_from_bytes(loader_bytes); fu_firmware_set_id(loader_fw, FU_FIRMWARE_ID_HEADER); fu_firmware_add_image(firmware, loader_fw); payload_len = blob_len - 2 - loader_len; payload_bytes = fu_bytes_new_offset(fw, 2 + loader_len, payload_len, error); if (payload_bytes == NULL) return NULL; payload_fw = fu_firmware_new_from_bytes(payload_bytes); fu_firmware_set_id(payload_fw, FU_FIRMWARE_ID_PAYLOAD); fu_firmware_add_image(firmware, payload_fw); return g_steal_pointer(&firmware); } static gboolean fu_wac_module_bluetooth_id9_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuWacModule *self = FU_WAC_MODULE(device); const guint8 buf_start[] = {FU_WAC_MODULE_BLUETOOTH_ID9_START_NORMAL}; g_autoptr(GBytes) blob_start = g_bytes_new_static(buf_start, sizeof(buf_start)); g_autoptr(GBytes) loader = NULL; g_autoptr(GBytes) payload = NULL; g_autoptr(FuChunk) cmd = NULL; /* get firmware images */ loader = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_HEADER, error); if (loader == NULL) return FALSE; payload = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_PAYLOAD, error); if (payload == NULL) return FALSE; cmd = fu_wac_module_bluetooth_id9_get_startcmd(payload, FALSE, error); if (cmd == NULL) return FALSE; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 22, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 67, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, NULL); /* start */ if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_START, blob_start, fu_progress_get_child(progress), FU_WAC_MODULE_BLUETOOTH_ID9_POLL_INTERVAL, FU_WAC_MODULE_BLUETOOTH_ID9_START_TIMEOUT, error)) return FALSE; fu_progress_step_done(progress); /* transfer flash programmer to device RAM */ if (!fu_wac_module_bluetooth_id9_write_blocks(self, FU_WAC_MODULE_BLUETOOTH_ID9_LOADER_RAM, loader, FU_WAC_MODULE_BLUETOOTH_ID9_PAYLOAD_SZ, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* send "flash start" command to programer */ if (!fu_wac_module_bluetooth_id9_write_block(self, FU_WAC_MODULE_BLUETOOTH_ID9_LOADER_BEGIN, cmd, progress, error)) return FALSE; /* transfer payload for programming */ if (!fu_wac_module_bluetooth_id9_write_blocks(self, FU_WAC_MODULE_BLUETOOTH_ID9_LOADER_DATA, payload, FU_WAC_MODULE_BLUETOOTH_ID9_PAYLOAD_SZ, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* end */ if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_END, NULL, fu_progress_get_child(progress), FU_WAC_MODULE_BLUETOOTH_ID9_POLL_INTERVAL, FU_WAC_MODULE_BLUETOOTH_ID9_END_TIMEOUT, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_wac_module_bluetooth_id9_init(FuWacModuleBluetoothId9 *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_install_duration(FU_DEVICE(self), 15); } static void fu_wac_module_bluetooth_id9_class_init(FuWacModuleBluetoothId9Class *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_wac_module_bluetooth_id9_write_firmware; klass_device->prepare_firmware = fu_wac_module_bluetooth_id9_prepare_firmware; } FuWacModule * fu_wac_module_bluetooth_id9_new(FuDevice *proxy) { FuWacModule *module = NULL; module = g_object_new(FU_TYPE_WAC_MODULE_BLUETOOTH_ID9, "proxy", proxy, "fw-type", FU_WAC_MODULE_FW_TYPE_BLUETOOTH_ID9, NULL); return module; } fwupd-1.9.16/plugins/wacom-usb/fu-wac-module-bluetooth-id9.h000066400000000000000000000007761460375044200236360ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2021-2023 Jason Gerecke * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wac-module.h" #define FU_TYPE_WAC_MODULE_BLUETOOTH_ID9 (fu_wac_module_bluetooth_id9_get_type()) G_DECLARE_FINAL_TYPE(FuWacModuleBluetoothId9, fu_wac_module_bluetooth_id9, FU, WAC_MODULE_BLUETOOTH_ID9, FuWacModule) FuWacModule * fu_wac_module_bluetooth_id9_new(FuDevice *proxy); fwupd-1.9.16/plugins/wacom-usb/fu-wac-module-bluetooth.c000066400000000000000000000152071460375044200231410ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wac-common.h" #include "fu-wac-device.h" #include "fu-wac-module-bluetooth.h" #include "fu-wac-struct.h" struct _FuWacModuleBluetooth { FuWacModule parent_instance; }; G_DEFINE_TYPE(FuWacModuleBluetooth, fu_wac_module_bluetooth, FU_TYPE_WAC_MODULE) #define FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ 256 #define FU_WAC_MODULE_BLUETOOTH_ADDR_USERDATA_START 0x3000 #define FU_WAC_MODULE_BLUETOOTH_ADDR_USERDATA_STOP 0x8000 typedef struct { guint8 preamble[7]; guint32 addr; guint8 crc; guint8 cdata[FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ]; } FuWacModuleBluetoothBlockData; static void fu_wac_module_bluetooth_calculate_crc_byte(guint8 *crc, guint8 data) { guint8 c[8]; guint8 m[8]; guint8 r[8]; /* find out what bits are set */ for (guint i = 0; i < 8; i++) { c[i] = (*crc & (1 << i)) != 0; m[i] = (data & (1 << i)) != 0; } /* do CRC on byte */ r[7] = (c[7] ^ m[4] ^ c[3] ^ m[3] ^ c[4] ^ m[6] ^ c[1] ^ m[0]); r[6] = (c[6] ^ m[5] ^ c[2] ^ m[4] ^ c[3] ^ m[7] ^ c[0] ^ m[1]); r[5] = (c[5] ^ m[6] ^ c[1] ^ m[5] ^ c[2] ^ m[2]); r[4] = (c[4] ^ m[7] ^ c[0] ^ m[6] ^ c[1] ^ m[3]); r[3] = (m[7] ^ m[0] ^ c[7] ^ c[0] ^ m[3] ^ c[4] ^ m[6] ^ c[1]); r[2] = (m[1] ^ c[6] ^ m[0] ^ c[7] ^ m[3] ^ c[4] ^ m[7] ^ c[0] ^ m[6] ^ c[1]); r[1] = (m[2] ^ c[5] ^ m[1] ^ c[6] ^ m[4] ^ c[3] ^ m[7] ^ c[0]); r[0] = (m[3] ^ c[4] ^ m[2] ^ c[5] ^ m[5] ^ c[2]); /* copy back into CRC */ *crc = 0; for (guint i = 0; i < 8; i++) { if (r[i] == 0) continue; *crc |= (1 << i); } } static guint8 fu_wac_module_bluetooth_calculate_crc(const guint8 *data, gsize sz) { guint8 crc = 0; for (gsize i = 0; i < sz; i++) fu_wac_module_bluetooth_calculate_crc_byte(&crc, data[i]); return crc; } static GPtrArray * fu_wac_module_bluetooth_parse_blocks(const guint8 *data, gsize sz, gboolean skip_user_data, GError **error) { const guint8 preamble[] = {0x02, 0x00, 0x0f, 0x06, 0x01, 0x08, 0x01}; GPtrArray *blocks = g_ptr_array_new_with_free_func(g_free); for (guint addr = 0x0; addr < sz; addr += FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ) { g_autofree FuWacModuleBluetoothBlockData *bd = NULL; gsize cdata_sz = FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ; /* user data area */ if (skip_user_data && addr >= FU_WAC_MODULE_BLUETOOTH_ADDR_USERDATA_START && addr < FU_WAC_MODULE_BLUETOOTH_ADDR_USERDATA_STOP) continue; bd = g_new0(FuWacModuleBluetoothBlockData, 1); bd->addr = addr; memcpy(bd->preamble, preamble, sizeof(preamble)); memset(bd->cdata, 0xff, FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ); /* if file is not in multiples of payload size */ if (addr + FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ >= sz) cdata_sz = sz - addr; if (!fu_memcpy_safe(bd->cdata, sizeof(bd->cdata), 0x0, /* dst */ data, sz, addr, /* src */ cdata_sz, error)) return NULL; bd->crc = fu_wac_module_bluetooth_calculate_crc(bd->cdata, FU_WAC_MODULE_BLUETOOTH_PAYLOAD_SZ); g_ptr_array_add(blocks, g_steal_pointer(&bd)); } return blocks; } static gboolean fu_wac_module_bluetooth_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuWacModule *self = FU_WAC_MODULE(device); const guint8 *data; gsize len = 0; const guint8 buf_start[] = {0x00}; g_autoptr(GPtrArray) blocks = NULL; g_autoptr(GBytes) blob_start = g_bytes_new_static(buf_start, 1); g_autoptr(GBytes) fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 20, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 79, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) { g_prefix_error(error, "wacom bluetooth module failed to get bytes: "); return FALSE; } /* build each data packet */ data = g_bytes_get_data(fw, &len); blocks = fu_wac_module_bluetooth_parse_blocks(data, len, TRUE, error); if (blocks == NULL) { g_prefix_error(error, "wacom bluetooth module failed to parse: "); return FALSE; } /* start, which will erase the module */ if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_START, blob_start, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_START_TIMEOUT, error)) { g_prefix_error(error, "wacom bluetooth module failed to erase: "); return FALSE; } fu_progress_step_done(progress); /* data */ for (guint i = 0; i < blocks->len; i++) { FuWacModuleBluetoothBlockData *bd = g_ptr_array_index(blocks, i); guint8 buf[256 + 11]; g_autoptr(GBytes) blob_chunk = NULL; /* build data packet */ memset(buf, 0xff, sizeof(buf)); memcpy(&buf[0], bd->preamble, 7); fu_memwrite_uint24(buf + 0x7, bd->addr, G_LITTLE_ENDIAN); buf[10] = bd->crc; memcpy(&buf[11], bd->cdata, sizeof(bd->cdata)); blob_chunk = g_bytes_new(buf, sizeof(buf)); if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_DATA, blob_chunk, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_DATA_TIMEOUT, error)) { g_prefix_error(error, "wacom bluetooth module failed to write: "); return FALSE; } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), i + 1, blocks->len); } fu_progress_step_done(progress); /* end */ if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_END, NULL, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_END_TIMEOUT, error)) { g_prefix_error(error, "wacom bluetooth module failed to end: "); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_wac_module_bluetooth_init(FuWacModuleBluetooth *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_install_duration(FU_DEVICE(self), 30); } static void fu_wac_module_bluetooth_class_init(FuWacModuleBluetoothClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_wac_module_bluetooth_write_firmware; } FuWacModule * fu_wac_module_bluetooth_new(FuDevice *proxy) { FuWacModule *module = NULL; module = g_object_new(FU_TYPE_WAC_MODULE_BLUETOOTH, "proxy", proxy, "fw-type", FU_WAC_MODULE_FW_TYPE_BLUETOOTH, NULL); return module; } fwupd-1.9.16/plugins/wacom-usb/fu-wac-module-bluetooth.h000066400000000000000000000006441460375044200231450ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wac-module.h" #define FU_TYPE_WAC_MODULE_BLUETOOTH (fu_wac_module_bluetooth_get_type()) G_DECLARE_FINAL_TYPE(FuWacModuleBluetooth, fu_wac_module_bluetooth, FU, WAC_MODULE_BLUETOOTH, FuWacModule) FuWacModule * fu_wac_module_bluetooth_new(FuDevice *proxy); fwupd-1.9.16/plugins/wacom-usb/fu-wac-module-scaler.c000066400000000000000000000120731460375044200224030ustar00rootroot00000000000000/* * Copyright (C) 2022 Aaron Skomra * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wac-common.h" #include "fu-wac-device.h" #include "fu-wac-module-scaler.h" #include "fu-wac-struct.h" struct _FuWacModuleScaler { FuWacModule parent_instance; }; G_DEFINE_TYPE(FuWacModuleScaler, fu_wac_module_scaler, FU_TYPE_WAC_MODULE) #define FU_WAC_MODULE_SCALER_CRC8_POLYNOMIAL 0x07 #define FU_WAC_MODULE_SCALER_PAYLOAD_SZ 256 #define FU_WAC_MODULE_SCALER_START_NORMAL 0x00 #define FU_WAC_MODULE_SCALER_START_FULLERASE 0xFE typedef struct __attribute__((__packed__)) { guint8 addr[3]; guint8 crc; guint8 cdata[FU_WAC_MODULE_SCALER_PAYLOAD_SZ]; } FuWacModuleScalerBlockData; static GPtrArray * fu_wac_module_scaler_parse_blocks(const guint8 *data, gsize sz, GError **error) { GPtrArray *blocks = g_ptr_array_new_with_free_func(g_free); for (guint addr = 0x0; addr < sz; addr += FU_WAC_MODULE_SCALER_PAYLOAD_SZ) { g_autofree FuWacModuleScalerBlockData *bd = NULL; gsize cdata_sz = FU_WAC_MODULE_SCALER_PAYLOAD_SZ; bd = g_new0(FuWacModuleScalerBlockData, 1); fu_memwrite_uint24(bd->addr, addr, G_BIG_ENDIAN); memset(bd->cdata, 0xff, sizeof(bd->cdata)); if (addr + FU_WAC_MODULE_SCALER_PAYLOAD_SZ >= sz) cdata_sz = sz - addr; if (!fu_memcpy_safe(bd->cdata, sizeof(bd->cdata), 0x0, /* dst */ data, sz, addr, /* src */ cdata_sz, error)) return NULL; bd->crc = ~fu_crc8(bd->cdata, sizeof(bd->cdata)); g_ptr_array_add(blocks, g_steal_pointer(&bd)); } return blocks; } static gboolean fu_wac_module_scaler_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuWacModule *self = FU_WAC_MODULE(device); const guint8 *data; gsize len = 0; const guint8 buf_start[] = {FU_WAC_MODULE_SCALER_START_NORMAL}; g_autoptr(GPtrArray) blocks = NULL; g_autoptr(GBytes) blob_start = g_bytes_new_static(buf_start, 1); g_autoptr(GBytes) fw = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 8, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 59, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 33, NULL); /* get default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) { g_prefix_error(error, "wacom scaler module failed to get bytes: "); return FALSE; } /* build each data packet */ data = g_bytes_get_data(fw, &len); blocks = fu_wac_module_scaler_parse_blocks(data, len, error); if (blocks == NULL) { g_prefix_error(error, "wacom scaler module failed to parse blocks: "); return FALSE; } /* start, which will erase the module */ if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_START, blob_start, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_START_TIMEOUT, error)) { g_prefix_error(error, "wacom scaler module failed to erase: "); return FALSE; } fu_progress_step_done(progress); /* data */ for (guint i = 0; i < blocks->len; i++) { FuWacModuleScalerBlockData *bd = g_ptr_array_index(blocks, i); guint8 buf[FU_WAC_MODULE_SCALER_PAYLOAD_SZ + 4]; g_autoptr(GBytes) blob_chunk = NULL; /* build data packet */ memset(buf, 0xff, sizeof(buf)); memcpy(&buf[0], bd->addr, 3); buf[3] = bd->crc; memcpy(&buf[4], bd->cdata, sizeof(bd->cdata)); blob_chunk = g_bytes_new(buf, sizeof(*bd)); if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_DATA, blob_chunk, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_DATA_TIMEOUT, error)) { g_prefix_error(error, "wacom scaler module failed to write: "); return FALSE; } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), i + 1, blocks->len); } fu_progress_step_done(progress); /* end */ if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_END, NULL, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_END_TIMEOUT, error)) { g_prefix_error(error, "wacom scaler module failed to end: "); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_wac_module_scaler_init(FuWacModuleScaler *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_install_duration(FU_DEVICE(self), 120); } static void fu_wac_module_scaler_class_init(FuWacModuleScalerClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_wac_module_scaler_write_firmware; } FuWacModule * fu_wac_module_scaler_new(FuDevice *proxy) { FuWacModule *module = NULL; module = g_object_new(FU_TYPE_WAC_MODULE_SCALER, "proxy", proxy, "fw-type", FU_WAC_MODULE_FW_TYPE_SCALER, NULL); return module; } fwupd-1.9.16/plugins/wacom-usb/fu-wac-module-scaler.h000066400000000000000000000006611460375044200224100ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2021 Jason Gerecke * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wac-module.h" #define FU_TYPE_WAC_MODULE_SCALER (fu_wac_module_scaler_get_type()) G_DECLARE_FINAL_TYPE(FuWacModuleScaler, fu_wac_module_scaler, FU, WAC_MODULE_SCALER, FuWacModule) FuWacModule * fu_wac_module_scaler_new(FuDevice *proxy); fwupd-1.9.16/plugins/wacom-usb/fu-wac-module-touch-id7.c000066400000000000000000000252061460375044200227370ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2023 Joshua Dickens * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wac-device.h" #include "fu-wac-module-touch-id7.h" #include "fu-wac-struct.h" struct _FuWacModuleTouchId7 { FuWacModule parent_instance; }; typedef struct { guint32 op_id; const guint8 *buf; gsize bufsz; gsize offset; } WtaInfo; typedef struct { guint32 header_size; guint16 firmware_number; } WtaFileHeader; typedef struct { guint32 file_name_length; guint32 start_address; guint8 ic_id; guint8 ma_id; guint32 block_count; } WtaRecordHeader; G_DEFINE_TYPE(FuWacModuleTouchId7, fu_wac_module_touch_id7, FU_TYPE_WAC_MODULE) /** * Read and advance past a WTA file header. * * File Header format: * { * u32: Starting symbol for the file (WTA) * u32: Header Size * u8[]: Variable-length padding to bring the header to match Header Size * u16: Number of Firmware * u8[]: Padding/Unnecessary Data * } * */ static gboolean fu_wac_module_touch_id7_read_file_header(WtaFileHeader *header, WtaInfo *info, GError **error) { info->offset += 4; if (!fu_memread_uint32_safe(info->buf, info->bufsz, info->offset, &header->header_size, G_LITTLE_ENDIAN, error)) return FALSE; info->offset += header->header_size - 8; if (!fu_memread_uint16_safe(info->buf, info->bufsz, info->offset, &header->firmware_number, G_LITTLE_ENDIAN, error)) return FALSE; info->offset += 16; return TRUE; } /** * fu_wac_module_touch_id7_read_record_header: * * Read and advance past a WTA record header. * * Header format: * { * u32: Length of filename * char[]: Variable-length null-terminated filename string * u8[]: Variable-length padding to bring filename to a multiple of 4 bytes * u8: Firmware Type * u8[]: 3 Bytes padding to bring Firmware Type to a multiple of 4 bytes * u32: Start address * u32: Segment Size * u8: IC_ID * u8: MA_ID * u8[]: 2 Bytes padding to bring IC_ID/MA_ID to a multiple of 4 bytes * u32: Block Count * } * */ static gboolean fu_wac_module_touch_id7_read_record_header(WtaRecordHeader *header, WtaInfo *info, GError **error) { if (!fu_memread_uint32_safe(info->buf, info->bufsz, info->offset, &header->file_name_length, G_LITTLE_ENDIAN, error)) return FALSE; info->offset += header->file_name_length + 8; if (!fu_memread_uint32_safe(info->buf, info->bufsz, info->offset, &header->start_address, G_LITTLE_ENDIAN, error)) return FALSE; info->offset += 8; if (!fu_memread_uint8_safe(info->buf, info->bufsz, info->offset, &header->ic_id, error)) return FALSE; info->offset += 1; if (!fu_memread_uint8_safe(info->buf, info->bufsz, info->offset, &header->ma_id, error)) return FALSE; info->offset += 3; if (!fu_memread_uint32_safe(info->buf, info->bufsz, info->offset, &header->block_count, G_LITTLE_ENDIAN, error)) return FALSE; info->offset += 4; /* success */ return TRUE; } /** * fu_wac_module_touch_id7_generate_command: * @header: A #WtaRecordHeader * @cmd: The type of command to be sent * @op_id: The operation serial number * @buf: The buffer to write the command into * * Generate a standard touch id7 command preamble. * */ static void fu_wac_module_touch_id7_generate_command(const WtaRecordHeader *header, guint8 cmd, guint16 op_id, guint8 *buf) { buf[0] = cmd; buf[1] = header->ic_id; buf[2] = header->ma_id; fu_memwrite_uint32(&buf[3], op_id, G_LITTLE_ENDIAN); fu_memwrite_uint32(&buf[7], header->start_address, G_LITTLE_ENDIAN); } /** * fu_wac_module_touch_id7_write_block: * * Write the data of a single firmware block to the device. * */ static gboolean fu_wac_module_touch_id7_write_block(FuWacModule *self, WtaInfo *info, FuProgress *progress, WtaRecordHeader *record_hdr, GError **error) { g_autoptr(GPtrArray) chunks = NULL; g_autoptr(GByteArray) st_blk = NULL; /* generate chunks off of the raw block data */ st_blk = fu_struct_wta_block_header_parse(info->buf, info->bufsz, info->offset, error); if (st_blk == NULL) return FALSE; info->offset += FU_STRUCT_WTA_BLOCK_HEADER_SIZE; chunks = fu_chunk_array_new(info->buf + info->offset, fu_struct_wta_block_header_get_block_size(st_blk), fu_struct_wta_block_header_get_block_start(st_blk), 0x0, /* page_sz */ FU_WAC_MODULE_CHUNK_SIZE); /* packet_sz */ /* write data */ for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint8 buf[11 + FU_WAC_MODULE_CHUNK_SIZE]; g_autoptr(GBytes) blob_chunk = NULL; buf[0] = FU_WAC_MODULE_COMMAND_DATA; buf[1] = record_hdr->ic_id; buf[2] = record_hdr->ma_id; fu_memwrite_uint32(&buf[3], info->op_id, G_LITTLE_ENDIAN); fu_memwrite_uint32(&buf[7], fu_chunk_get_address(chk), G_LITTLE_ENDIAN); memcpy(&buf[11], fu_chunk_get_data(chk), FU_WAC_MODULE_CHUNK_SIZE); blob_chunk = g_bytes_new(buf, sizeof(buf)); if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_DATA, blob_chunk, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_DATA_TIMEOUT, error)) { g_prefix_error(error, "failed to write block %u: ", info->op_id); return FALSE; } info->op_id++; /* rough estimate based on file size with some added to handle the extra firmware * record start and end commands */ fu_progress_set_percentage_full(fu_progress_get_child(progress), info->op_id, info->bufsz / FU_WAC_MODULE_CHUNK_SIZE + 10); } /* incrementing data to the next block */ info->offset += fu_struct_wta_block_header_get_block_size(st_blk); return TRUE; } /** * fu_wac_module_touch_id7_write_record: * * Start and end the write process for a single touch id7 firmware record and * the block(s) it contains. * A touch id7 firmware record acts as it's own mini update with the device, * with a start and end command each individual record. * A single touch id7 firmware record can contain one or more blocks that have * the raw data for writing. */ static gboolean fu_wac_module_touch_id7_write_record(FuWacModule *self, WtaInfo *info, FuProgress *progress, GError **error) { WtaRecordHeader record_hdr = {0x0}; g_autoptr(GBytes) blob_start = NULL; g_autoptr(GBytes) blob_end = NULL; guint8 command[11]; if (!fu_wac_module_touch_id7_read_record_header(&record_hdr, info, error)) return FALSE; /* start firmware record command */ fu_wac_module_touch_id7_generate_command(&record_hdr, FU_WAC_MODULE_COMMAND_START, info->op_id, command); blob_start = g_bytes_new(command, sizeof(command)); if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_DATA, blob_start, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_DATA_TIMEOUT, error)) return FALSE; info->op_id++; /* write each block */ for (guint32 i = 0; i < record_hdr.block_count; i++) { if (!fu_wac_module_touch_id7_write_block(self, info, progress, &record_hdr, error)) return FALSE; } /* end firmware record command */ fu_wac_module_touch_id7_generate_command(&record_hdr, FU_WAC_MODULE_COMMAND_END, info->op_id, command); blob_end = g_bytes_new(command, sizeof(command)); if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_DATA, blob_end, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_DATA_TIMEOUT, error)) return FALSE; info->op_id++; return TRUE; } /** * fu_wac_module_touch_id7_write_firmware: * * Start and End the overall update process for touch id7 firmware and the * record(s) it contains. * A touch id7 firmware will usually contain 3 firmware record(s) but could * potentially have less or more. */ static gboolean fu_wac_module_touch_id7_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuWacModule *self = FU_WAC_MODULE(device); g_autoptr(GBytes) blob = NULL; WtaInfo info; WtaFileHeader file_hdr; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 2, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 97, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, NULL); g_debug("using element at addr 0x%0x", (guint)fu_firmware_get_addr(firmware)); blob = fu_firmware_get_bytes(firmware, error); if (blob == NULL) return FALSE; /* start, which will erase the module */ if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_START, NULL, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_START_TIMEOUT, error)) return FALSE; fu_progress_step_done(progress); /* set basic info */ info.offset = 0x0; info.buf = g_bytes_get_data(blob, &info.bufsz); info.op_id = 1; if (!fu_wac_module_touch_id7_read_file_header(&file_hdr, &info, error)) return FALSE; /* write each firmware record */ for (guint i = 0; i < file_hdr.firmware_number; i++) { if (!fu_wac_module_touch_id7_write_record(self, &info, progress, error)) return FALSE; /* increment data to the next firmware record */ info.offset += 14; } fu_progress_step_done(progress); /* end */ if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_END, NULL, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_END_TIMEOUT, error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_wac_module_touch_id7_init(FuWacModuleTouchId7 *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_install_duration(FU_DEVICE(self), 90); } static void fu_wac_module_touch_id7_class_init(FuWacModuleTouchId7Class *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_wac_module_touch_id7_write_firmware; } FuWacModule * fu_wac_module_touch_id7_new(FuDevice *proxy) { FuWacModule *module = NULL; module = g_object_new(FU_TYPE_WAC_MODULE_TOUCH_ID7, "proxy", proxy, "fw-type", FU_WAC_MODULE_FW_TYPE_TOUCH_ID7, NULL); return module; } fwupd-1.9.16/plugins/wacom-usb/fu-wac-module-touch-id7.h000066400000000000000000000010101460375044200227270ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * Copyright (C) 2023 Joshua Dickens * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wac-module.h" #define FU_TYPE_WAC_MODULE_TOUCH_ID7 (fu_wac_module_touch_id7_get_type()) G_DECLARE_FINAL_TYPE(FuWacModuleTouchId7, fu_wac_module_touch_id7, FU, WAC_MODULE_TOUCH_ID7, FuWacModule) #define FU_WAC_MODULE_CHUNK_SIZE 128 FuWacModule * fu_wac_module_touch_id7_new(FuDevice *proxy); fwupd-1.9.16/plugins/wacom-usb/fu-wac-module-touch.c000066400000000000000000000101621460375044200222510ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wac-device.h" #include "fu-wac-module-touch.h" #include "fu-wac-struct.h" struct _FuWacModuleTouch { FuWacModule parent_instance; }; G_DEFINE_TYPE(FuWacModuleTouch, fu_wac_module_touch, FU_TYPE_WAC_MODULE) static gboolean fu_wac_module_touch_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuWacModule *self = FU_WAC_MODULE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 10, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 10, NULL); g_debug("using element at addr 0x%0x", (guint)fu_firmware_get_addr(firmware)); /* build each data packet */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) { g_prefix_error(error, "wacom touch module failed to get bytes: "); return FALSE; } chunks = fu_chunk_array_new_from_bytes(fw, fu_firmware_get_addr(firmware), 128); /* packet_sz */ /* start, which will erase the module */ if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_START, NULL, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_START_TIMEOUT, error)) { g_prefix_error(error, "wacom touch module failed to erase: "); return FALSE; } fu_progress_step_done(progress); /* data */ for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); guint8 buf[128 + 7] = {0xff}; g_autoptr(GBytes) blob_chunk = NULL; /* build G11T data packet */ memset(buf, 0xff, sizeof(buf)); buf[0] = 0x01; /* writing */ buf[1] = fu_chunk_get_idx(chk) + 1; fu_memwrite_uint32(&buf[2], fu_chunk_get_address(chk), G_LITTLE_ENDIAN); buf[6] = 0x10; /* no idea! */ if (!fu_memcpy_safe(buf, sizeof(buf), 0x07, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "wacom touch module failed to memcpy: "); return FALSE; } blob_chunk = g_bytes_new(buf, sizeof(buf)); if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_DATA, blob_chunk, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_DATA_TIMEOUT, error)) { g_prefix_error(error, "failed to write block %u: ", fu_chunk_get_idx(chk)); return FALSE; } /* update progress */ fu_progress_set_percentage_full(fu_progress_get_child(progress), i + 1, fu_chunk_array_length(chunks)); } fu_progress_step_done(progress); /* end */ if (!fu_wac_module_set_feature(self, FU_WAC_MODULE_COMMAND_END, NULL, fu_progress_get_child(progress), FU_WAC_MODULE_POLL_INTERVAL, FU_WAC_MODULE_END_TIMEOUT, error)) { g_prefix_error(error, "wacom touch module failed to end: "); return FALSE; } fu_progress_step_done(progress); /* success */ return TRUE; } static void fu_wac_module_touch_init(FuWacModuleTouch *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_install_duration(FU_DEVICE(self), 30); fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_IHEX_FIRMWARE); } static void fu_wac_module_touch_class_init(FuWacModuleTouchClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_wac_module_touch_write_firmware; } FuWacModule * fu_wac_module_touch_new(FuDevice *proxy) { FuWacModule *module = NULL; module = g_object_new(FU_TYPE_WAC_MODULE_TOUCH, "proxy", proxy, "fw-type", FU_WAC_MODULE_FW_TYPE_TOUCH, NULL); return module; } fwupd-1.9.16/plugins/wacom-usb/fu-wac-module-touch.h000066400000000000000000000005601460375044200222570ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-wac-module.h" #define FU_TYPE_WAC_MODULE_TOUCH (fu_wac_module_touch_get_type()) G_DECLARE_FINAL_TYPE(FuWacModuleTouch, fu_wac_module_touch, FU, WAC_MODULE_TOUCH, FuWacModule) FuWacModule * fu_wac_module_touch_new(FuDevice *proxy); fwupd-1.9.16/plugins/wacom-usb/fu-wac-module.c000066400000000000000000000225201460375044200211320ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-wac-common.h" #include "fu-wac-device.h" #include "fu-wac-module.h" #include "fu-wac-struct.h" typedef struct { guint8 fw_type; guint8 command; guint8 status; } FuWacModulePrivate; G_DEFINE_TYPE_WITH_PRIVATE(FuWacModule, fu_wac_module, FU_TYPE_DEVICE) #define GET_PRIVATE(o) (fu_wac_module_get_instance_private(o)) enum { PROP_0, PROP_FW_TYPE, PROP_LAST }; static void fu_wac_module_to_string(FuDevice *device, guint idt, GString *str) { FuWacModule *self = FU_WAC_MODULE(device); FuWacModulePrivate *priv = GET_PRIVATE(self); fu_string_append(str, idt, "FwType", fu_wac_module_fw_type_to_string(priv->fw_type)); fu_string_append(str, idt, "Status", fu_wac_module_status_to_string(priv->status)); fu_string_append(str, idt, "Command", fu_wac_module_command_to_string(priv->command)); } static gboolean fu_wac_module_refresh(FuWacModule *self, GError **error) { FuWacDevice *parent_device = FU_WAC_DEVICE(fu_device_get_parent(FU_DEVICE(self))); FuWacModulePrivate *priv = GET_PRIVATE(self); guint8 buf[] = {[0] = FU_WAC_REPORT_ID_MODULE, [1 ... FU_WAC_PACKET_LEN - 1] = 0xff}; /* get from hardware */ if (!fu_wac_device_get_feature_report(parent_device, buf, sizeof(buf), FU_HID_DEVICE_FLAG_ALLOW_TRUNC, error)) { g_prefix_error(error, "failed to refresh status: "); return FALSE; } /* check fw type */ if (priv->fw_type != buf[1]) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Submodule GetFeature fw_Type invalid " "got 0x%02x expected 0x%02x", (guint)buf[1], (guint)priv->fw_type); return FALSE; } /* current phase and status */ if (priv->command != buf[2] || priv->status != buf[3]) { priv->command = buf[2]; priv->status = buf[3]; g_debug("command: %s, status: %s", fu_wac_module_command_to_string(priv->command), fu_wac_module_status_to_string(priv->status)); } /* success */ return TRUE; } static gboolean fu_wac_module_refresh_cb(FuDevice *device, gpointer user_data, GError **error) { FuWacModule *self = FU_WAC_MODULE(device); FuWacModulePrivate *priv = GET_PRIVATE(self); g_autoptr(GError) error_local = NULL; if (!fu_wac_module_refresh(self, &error_local)) { if (g_error_matches(error_local, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_NO_DEVICE)) return TRUE; g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* retry not necessary for unrecoverable errors */ if (priv->status != FU_WAC_MODULE_STATUS_BUSY) return TRUE; if (priv->status != FU_WAC_MODULE_STATUS_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "refresh returned status 0x%x [%s]", priv->status, fu_wac_module_status_to_string(priv->status)); return FALSE; } /* success */ return TRUE; } gboolean fu_wac_module_set_feature(FuWacModule *self, guint8 command, GBytes *blob, /* optional */ FuProgress *progress, guint poll_interval, /* ms */ guint busy_timeout, /* ms */ GError **error) { FuWacDevice *parent_device = FU_WAC_DEVICE(fu_device_get_parent(FU_DEVICE(self))); FuWacModulePrivate *priv = GET_PRIVATE(self); const guint8 *data; gsize len = 0; guint delay_ms = fu_device_has_flag(FU_DEVICE(parent_device), FWUPD_DEVICE_FLAG_EMULATED) ? 10 : poll_interval; guint busy_poll_loops = busy_timeout / delay_ms; guint8 buf[] = {[0] = FU_WAC_REPORT_ID_MODULE, [1] = priv->fw_type, [2] = command, [3 ... FU_WAC_PACKET_LEN - 1] = 0xff}; /* sanity check */ g_return_val_if_fail(FU_IS_WAC_MODULE(self), FALSE); g_return_val_if_fail(FU_IS_WAC_DEVICE(parent_device), FALSE); /* verify the size of the blob */ if (blob != NULL) { data = g_bytes_get_data(blob, &len); if (!fu_memcpy_safe(buf, sizeof(buf), 0x03, /* dst */ data, len, 0x0, /* src */ len, error)) { g_prefix_error(error, "Submodule blob larger than buffer: "); return FALSE; } } /* tell the daemon the current status */ switch (command) { case FU_WAC_MODULE_COMMAND_START: fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_ERASE); break; case FU_WAC_MODULE_COMMAND_DATA: fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE); break; case FU_WAC_MODULE_COMMAND_END: fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_VERIFY); break; default: break; } /* send to hardware */ if (!fu_wac_device_set_feature_report(parent_device, buf, sizeof(buf), FU_HID_DEVICE_FLAG_ALLOW_TRUNC, error)) { g_prefix_error(error, "failed to set module feature: "); return FALSE; } /* wait for hardware */ if (busy_poll_loops > 0) { fu_device_sleep(FU_DEVICE(self), delay_ms); /* settle before polling status */ if (!fu_device_retry_full(FU_DEVICE(self), fu_wac_module_refresh_cb, busy_poll_loops, delay_ms, NULL, error)) { g_prefix_error(error, "failed to set feature %s: ", fu_wac_module_command_to_string(command)); return FALSE; } if (priv->status != FU_WAC_MODULE_STATUS_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "refresh returned status 0x%x [%s]", priv->status, fu_wac_module_status_to_string(priv->status)); return FALSE; } } /* success */ return TRUE; } static gboolean fu_wac_module_cleanup(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDevice *parent = fu_device_get_parent(device); g_autoptr(FuDeviceLocker) locker = fu_device_locker_new(parent, error); if (locker == NULL) return FALSE; return fu_device_cleanup(parent, progress, flags, error); } static gchar * fu_wac_module_convert_version(FuDevice *device, guint64 version_raw) { return fu_version_from_uint16(version_raw, fu_device_get_version_format(device)); } static void fu_wac_module_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuWacModule *self = FU_WAC_MODULE(object); FuWacModulePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_FW_TYPE: g_value_set_uint(value, priv->fw_type); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_wac_module_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuWacModule *self = FU_WAC_MODULE(object); FuWacModulePrivate *priv = GET_PRIVATE(self); switch (prop_id) { case PROP_FW_TYPE: priv->fw_type = g_value_get_uint(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_wac_module_init(FuWacModule *self) { fu_device_add_protocol(FU_DEVICE(self), "com.wacom.usb"); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_BCD); fu_device_set_remove_delay(FU_DEVICE(self), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); } static void fu_wac_module_constructed(GObject *object) { FuWacModule *self = FU_WAC_MODULE(object); FuWacModulePrivate *priv = GET_PRIVATE(self); FuDevice *proxy = fu_device_get_proxy(FU_DEVICE(self)); g_autofree gchar *devid = NULL; g_autofree gchar *vendor_id = NULL; /* set vendor ID */ vendor_id = g_strdup_printf("USB:0x%04X", fu_usb_device_get_vid(FU_USB_DEVICE(proxy))); fu_device_add_vendor_id(FU_DEVICE(self), vendor_id); /* set USB physical and logical IDs */ fu_device_set_physical_id(FU_DEVICE(self), fu_device_get_physical_id(proxy)); fu_device_set_logical_id(FU_DEVICE(self), fu_wac_module_fw_type_to_string(priv->fw_type)); /* append the firmware kind to the generated GUID */ devid = g_strdup_printf("USB\\VID_%04X&PID_%04X-%s", fu_usb_device_get_vid(FU_USB_DEVICE(proxy)), fu_usb_device_get_pid(FU_USB_DEVICE(proxy)), fu_wac_module_fw_type_to_string(priv->fw_type)); fu_device_add_instance_id(FU_DEVICE(self), devid); G_OBJECT_CLASS(fu_wac_module_parent_class)->constructed(object); } static void fu_wac_module_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reload"); } static void fu_wac_module_class_init(FuWacModuleClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); /* properties */ object_class->get_property = fu_wac_module_get_property; object_class->set_property = fu_wac_module_set_property; /** * FuWacModule:fw-type: * * The firmware kind. */ pspec = g_param_spec_uint("fw-type", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FW_TYPE, pspec); object_class->constructed = fu_wac_module_constructed; klass_device->to_string = fu_wac_module_to_string; klass_device->cleanup = fu_wac_module_cleanup; klass_device->set_progress = fu_wac_module_set_progress; klass_device->convert_version = fu_wac_module_convert_version; } fwupd-1.9.16/plugins/wacom-usb/fu-wac-module.h000066400000000000000000000013451460375044200211410ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_WAC_MODULE (fu_wac_module_get_type()) G_DECLARE_DERIVABLE_TYPE(FuWacModule, fu_wac_module, FU, WAC_MODULE, FuDevice) struct _FuWacModuleClass { FuDeviceClass parent_class; }; #define FU_WAC_MODULE_POLL_INTERVAL 100 /* ms */ #define FU_WAC_MODULE_START_TIMEOUT 15000 /* ms */ #define FU_WAC_MODULE_DATA_TIMEOUT 10000 /* ms */ #define FU_WAC_MODULE_END_TIMEOUT 10000 /* ms */ gboolean fu_wac_module_set_feature(FuWacModule *self, guint8 command, GBytes *blob, FuProgress *progress, guint poll_interval, guint busy_timeout, GError **error); fwupd-1.9.16/plugins/wacom-usb/fu-wac-plugin.c000066400000000000000000000055241460375044200211500ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-wac-android-device.h" #include "fu-wac-device.h" #include "fu-wac-firmware.h" #include "fu-wac-plugin.h" struct _FuWacPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuWacPlugin, fu_wac_plugin, FU_TYPE_PLUGIN) static gboolean fu_wac_plugin_write_firmware(FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDevice *parent = fu_device_get_parent(device); g_autoptr(FuDeviceLocker) locker = NULL; locker = fu_device_locker_new(parent != NULL ? parent : device, error); if (locker == NULL) return FALSE; return fu_device_write_firmware(device, blob_fw, progress, flags, error); } static gboolean fu_wac_plugin_composite_prepare(FuPlugin *self, GPtrArray *devices, GError **error) { for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); if (FU_IS_WAC_DEVICE(device)) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; g_info("switching main device to flash loader"); if (!fu_wac_device_switch_to_flash_loader(FU_WAC_DEVICE(device), error)) return FALSE; } } return TRUE; } static gboolean fu_wac_plugin_composite_cleanup(FuPlugin *self, GPtrArray *devices, GError **error) { for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); if (FU_IS_WAC_DEVICE(device)) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; g_info("resetting main device"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); if (!fu_wac_device_update_reset(FU_WAC_DEVICE(device), error)) return FALSE; } } return TRUE; } static void fu_wac_plugin_init(FuWacPlugin *self) { } static void fu_wac_plugin_object_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_set_name(plugin, "wacom_usb"); } static void fu_wac_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_set_device_gtype_default(plugin, FU_TYPE_WAC_DEVICE); fu_plugin_add_device_gtype(plugin, FU_TYPE_WAC_ANDROID_DEVICE); fu_plugin_add_firmware_gtype(plugin, "wacom", FU_TYPE_WAC_FIRMWARE); } static void fu_wac_plugin_class_init(FuWacPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->constructed = fu_wac_plugin_object_constructed; plugin_class->constructed = fu_wac_plugin_constructed; plugin_class->write_firmware = fu_wac_plugin_write_firmware; plugin_class->composite_prepare = fu_wac_plugin_composite_prepare; plugin_class->composite_cleanup = fu_wac_plugin_composite_cleanup; } fwupd-1.9.16/plugins/wacom-usb/fu-wac-plugin.h000066400000000000000000000003371460375044200211520ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuWacPlugin, fu_wac_plugin, FU, WAC_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/wacom-usb/fu-wac.rs000066400000000000000000000050341460375044200200520ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(Parse)] struct WtaBlockHeader { block_start: u32le, block_size: u32le, } #[derive(ToString)] enum WacReportId { FwDescriptor = 0xCB, // GET_FEATURE SwitchToFlashLoader = 0xCC, // SET_FEATURE QuitAndReset = 0xCD, // SET_FEATURE ReadBlockData = 0xD1, // GET_FEATURE WriteBlock = 0xD2, // SET_FEATURE EraseBlock = 0xD3, // SET_FEATURE SetReadAddress = 0xD4, // GET_FEATURE GetStatus = 0xD5, // GET_FEATURE UpdateReset = 0xD6, // SET_FEATURE WriteWord = 0xD7, // SET_FEATURE GetParameters = 0xD8, // GET_FEATURE GetFlashDescriptor = 0xD9, // GET_FEATURE GetChecksums = 0xDA, // GET_FEATURE SetChecksumForBlock = 0xDB, // SET_FEATURE CalculateChecksumForBlock = 0xDC, // SET_FEATURE WriteChecksumTable = 0xDE, // SET_FEATURE GetCurrentFirmwareIdx = 0xE2, // GET_FEATURE Module = 0xE4, } #[derive(ToString)] enum WacModuleFwType { Touch = 0x00, Bluetooth = 0x01, EmrCorrection = 0x02, BluetoothHid = 0x03, Scaler = 0x04, BluetoothId6 = 0x06, TouchId7 = 0x07, BluetoothId9 = 0x09, Main = 0x3F, } #[derive(ToString)] enum WacModuleCommand { Start = 0x01, Data = 0x02, End = 0x03, } #[derive(ToString)] enum WacModuleStatus { Ok, Busy, ErrCrc, ErrCmd, ErrHwAccessFail, ErrFlashNoSupport, ErrModeWrong, ErrMpuNoSupport, ErrVersionNoSupport, ErrErase, ErrWrite, ErrExit, Err, ErrInvalidOp, ErrWrongImage, } #[derive(ToBitString)] enum WacDeviceStatus { Unknown = 0, Writing = 1 << 0, Erasing = 1 << 1, ErrorWrite = 1 << 2, ErrorErase = 1 << 3, WriteProtected = 1 << 4, } #[derive(New)] struct Id9UnknownCmd { unknown1: u16be == 0x7050, unknown2: u32be == 0, size: u16be, // Size of payload to be transferred } #[derive(New)] struct Id9SpiCmd { command: u8 == 0x91, start_addr: u32be == 0, size: u16be, // sizeof(data) + size of payload data: Id9UnknownCmd, } #[derive(New, Validate)] struct Id9LoaderCmd { command: u8, size: u16be, // sizeof(data) + size of payload crc: u32be, // CRC(concat(data, payload)) data: Id9SpiCmd, } fwupd-1.9.16/plugins/wacom-usb/meson.build000066400000000000000000000031211460375044200204570ustar00rootroot00000000000000if gusb.found() plugins += {meson.current_source_dir().split('/')[-1]: true} cargs = ['-DG_LOG_DOMAIN="FuPluginWacomUsb"'] plugin_quirks += files('wacom-usb.quirk') plugin_builtin_wac = static_library('fu_plugin_wac', rustgen.process( 'fu-wac.rs', # fuzzing ), sources: [ 'fu-wac-common.c', 'fu-wac-android-device.c', 'fu-wac-device.c', 'fu-wac-firmware.c', # fuzzing 'fu-wac-module.c', 'fu-wac-module-bluetooth.c', 'fu-wac-module-bluetooth-id6.c', 'fu-wac-module-bluetooth-id9.c', 'fu-wac-module-scaler.c', 'fu-wac-module-touch.c', 'fu-wac-module-touch-id7.c', 'fu-wac-plugin.c', ], include_directories: plugin_incdirs, c_args: cargs, dependencies: plugin_deps, link_with: plugin_libs, ) plugin_builtins += plugin_builtin_wac if get_option('tests') install_data(['tests/wacom-usb.builder.xml'], install_dir: join_paths(installed_test_datadir, 'tests')) env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) e = executable( 'wacom-usb-self-test', rustgen.process('fu-wac.rs'), sources: [ 'fu-self-test.c', ], include_directories: plugin_incdirs, dependencies: plugin_deps, link_with: [ plugin_libs, plugin_builtin_wac, ], install: true, install_rpath: libdir_pkg, install_dir: installed_test_bindir, c_args: [ cargs, '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('wacom-usb-self-test', e, env: env) # added to installed-tests endif endif fwupd-1.9.16/plugins/wacom-usb/tests/000077500000000000000000000000001460375044200174625ustar00rootroot00000000000000fwupd-1.9.16/plugins/wacom-usb/tests/wacom-usb.bin000066400000000000000000000003311460375044200220460ustar00rootroot00000000000000WACOM2080080000000000B080400000000000B55 WA10800800077 S0030000FC S3100800800068656C6C6F20776F726C640B S5030001FB S70500000000FA WA208040000F3 S0030000FC S3100804000068656C6C6F20776F726C6487 S5030001FB S70500000000FA fwupd-1.9.16/plugins/wacom-usb/tests/wacom-usb.builder.xml000066400000000000000000000004611460375044200235270ustar00rootroot00000000000000 0x0 0x8008000 aGVsbG8gd29ybGQ= 0x1 0x8040000 aGVsbG8gd29ybGQ= fwupd-1.9.16/plugins/wacom-usb/wacom-usb.quirk000066400000000000000000000055451460375044200213030ustar00rootroot00000000000000 # Intuos Pro medium (2nd-gen USB) [PTH-660] [USB\VID_056A&PID_0357] Plugin = wacom_usb Flags = use-runtime-version # Intuos Pro large (2nd-gen USB) [PTH-860] [USB\VID_056A&PID_0358] Plugin = wacom_usb Flags = use-runtime-version # Intuos S 3rd-gen (USB) [CTL-4100] [USB\VID_056A&PID_0374] Plugin = wacom_usb Flags = use-runtime-version,no-serial-number # Intuos S 3rd-gen (USB) [CTL-4100 - Android Mode] [USB\VID_2D1F&PID_0374] Plugin = wacom_usb GType = FuWacAndroidDevice # Intuos M 3rd-gen (USB) [CTL-6100] [USB\VID_056A&PID_0375] Plugin = wacom_usb Flags = use-runtime-version,no-serial-number # Intuos M 3rd-gen (USB) [CTL-6100 - Android Mode] [USB\VID_2D1F&PID_0375] Plugin = wacom_usb GType = FuWacAndroidDevice # Intuos BT S 3rd-gen (USB) [CTL-4100WL] [USB\VID_056A&PID_0376] Plugin = wacom_usb Flags = use-runtime-version,no-serial-number # Intuos BT S 3rd-gen (USB) [CTL-4100WL - Android Mode] [USB\VID_2D1F&PID_0376] Plugin = wacom_usb GType = FuWacAndroidDevice # Intuos BT M 3rd-gen (USB) [CTL-6100WL] [USB\VID_056A&PID_0378] Plugin = wacom_usb Flags = use-runtime-version,no-serial-number # Intuos BT M 3rd-gen (USB) [CTL-6100WL - Android Mode] [USB\VID_2D1F&PID_0378] Plugin = wacom_usb GType = FuWacAndroidDevice # Intuos Pro Small (2nd-gen USB) [PTH-460] [USB\VID_056A&PID_0392] Plugin = wacom_usb # Intuos BT S 3rd-gen, Rev 2 (USB) [CTL-4100WL] [USB\VID_056A&PID_03C5] Plugin = wacom_usb Flags = use-runtime-version,no-serial-number # Intuos BT S 3rd-gen, Rev 2 (USB) [CTL-4100WL - Android Mode] [USB\VID_2D1F&PID_03C5] Plugin = wacom_usb GType = FuWacAndroidDevice # Intuos BT M 3rd-gen, Rev 2 (USB) [CTL-6100WL] [USB\VID_056A&PID_03C7] Plugin = wacom_usb Flags = use-runtime-version,no-serial-number # Intuos BT M 3rd-gen, Rev 2 (USB) [CTL-6100WL - Android Mode] [USB\VID_2D1F&PID_03C7] Plugin = wacom_usb GType = FuWacAndroidDevice # Intuos Pro Small (2nd-gen USB v2) [PTH-460] [USB\VID_056A&PID_03DC] Plugin = wacom_usb # Cintiq Pro 27 (USB) [DTH271] [USB\VID_056A&PID_03C0] Plugin = wacom_usb # Wacom One 13 (USB) [DTH-134] [USB\VID_056A&PID_03CB] Plugin = wacom_usb # Wacom One 12 (USB) [DTC-121] [USB\VID_056A&PID_03CE] Plugin = wacom_usb # DTH134 (USB) [DTH-134] [USB\VID_056A&PID_03EC] Plugin = wacom_usb # DTC121 (USB) [DTC-121] [USB\VID_056A&PID_03ED] Plugin = wacom_usb # Wacom One Pen tablet small (USB) [CTC4110WL] [USB\VID_0531&PID_0100] Plugin = wacom_usb Flags = no-serial-number # Wacom One Pen tablet small (USB) [CTC4110WL - Android Mode] [USB\VID_0531&PID_0104] Plugin = wacom_usb # Wacom One Pen tablet medium (USB) [CTC6110WL] [USB\VID_0531&PID_0102] Plugin = wacom_usb Flags = no-serial-number # Wacom One Pen tablet medium (USB) [CTC6110WL - Android Mode] [USB\VID_0531&PID_0105] Plugin = wacom_usb # Cintiq Pro 17 (USB) [DTH172] [USB\VID_056A&PID_03C4] Plugin = wacom_usb # Cintiq Pro 22 (USB) [DTH227] [USB\VID_056A&PID_03D0] Plugin = wacom_usb fwupd-1.9.16/plugins/wistron-dock/000077500000000000000000000000001460375044200170465ustar00rootroot00000000000000fwupd-1.9.16/plugins/wistron-dock/README.md000066400000000000000000000026441460375044200203330ustar00rootroot00000000000000--- title: Plugin: Wistron Dock --- ## Introduction Wistron use a generic flashing protocol for dock devices supplied to various OEMs. The protocol is compatible with designs that use Nuvoton, Infineon, and GD MCUs. ## Firmware Format The daemon will decompress the cabinet archive and extract a firmware blob in a zipped file format. The archive must contain exactly one file with each of these extensions: * `.wdfl.sig` * `.wdfl` * `.bin` This plugin supports the following protocol ID: * `com.wistron.dock` ## GUID Generation These devices use the standard USB DeviceInstanceId values, e.g. * `USB\VID_0FB8&PID_0010` Devices also have additional instance IDs which corresponds MCU type, e.g. * `USB\VID_0FB8&PID_0010&MCUID_M4521` ## Update Behavior The device enters DFU mode, then writes the fixed-size WDFL signature and WDFL data, then writes blocks of variable sized data. Finally it clears DFU mode and the user can re-plug the USB-C cable to trigger the update. ## Vendor ID Security The vendor ID is set from the USB vendor, in this example set to `USB:0x0FB8` ## External Interface Access This plugin requires read/write access to `/dev/bus/usb`. ## Version Considerations This plugin has been available since fwupd version `1.8.9`. ## Owners Anyone can submit a pull request to modify this plugin, but the following people should be consulted before making major or functional changes: * Felix Chen: @a999153 fwupd-1.9.16/plugins/wistron-dock/fu-wistron-dock-common.h000066400000000000000000000031301460375044200235350ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_WISTRON_DOCK_CMD_ICP_ENTER 0x81 #define FU_WISTRON_DOCK_CMD_ICP_EXIT 0x82 #define FU_WISTRON_DOCK_CMD_ICP_ADDRESS 0x84 #define FU_WISTRON_DOCK_CMD_ICP_READBLOCK 0x85 #define FU_WISTRON_DOCK_CMD_ICP_WRITEBLOCK 0x86 #define FU_WISTRON_DOCK_CMD_ICP_MCUID 0x87 #define FU_WISTRON_DOCK_CMD_ICP_BBINFO 0x88 /* bb code information */ #define FU_WISTRON_DOCK_CMD_ICP_USERINFO 0x89 /* user code information */ #define FU_WISTRON_DOCK_CMD_ICP_DONE 0x5A #define FU_WISTRON_DOCK_CMD_ICP_ERROR 0xFF #define FU_WISTRON_DOCK_CMD_ICP_EXIT_WDRESET 0x01 /* exit ICP with watch dog reset */ #define FU_WISTRON_DOCK_CMD_DFU_ENTER 0x91 #define FU_WISTRON_DOCK_CMD_DFU_EXIT 0x92 #define FU_WISTRON_DOCK_CMD_DFU_ADDRESS 0x93 #define FU_WISTRON_DOCK_CMD_DFU_READIMG_BLOCK 0x94 #define FU_WISTRON_DOCK_CMD_DFU_WRITEIMG_BLOCK 0x95 #define FU_WISTRON_DOCK_CMD_DFU_VERIFY 0x96 #define FU_WISTRON_DOCK_CMD_DFU_COMPOSITE_VER 0x97 #define FU_WISTRON_DOCK_CMD_DFU_WRITE_WDFL_SIG 0x98 #define FU_WISTRON_DOCK_CMD_DFU_WRITE_WDFL_DATA 0x99 #define FU_WISTRON_DOCK_CMD_DFU_VERIFY_WDFL 0x9A #define FU_WISTRON_DOCK_CMD_DFU_SERINAL_NUMBER 0x9B #define FU_WISTRON_DOCK_CMD_DFU_DONE 0x5A #define FU_WISTRON_DOCK_CMD_DFU_ERROR 0xFF #define FU_WISTRON_DOCK_WDIT_SIZE 512 /* bytes */ #define FU_WISTRON_DOCK_WDIT_TAG_ID 0x4954 /* 'IT' */ #define FU_WISTRON_DOCK_WDFL_SIG_SIZE 256 /* bytes */ #define FU_WISTRON_DOCK_WDFL_DATA_SIZE 1328 /* bytes */ fwupd-1.9.16/plugins/wistron-dock/fu-wistron-dock-device.c000066400000000000000000000631411460375044200235070ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * Copyright (C) 2022 Wistron * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-wistron-dock-common.h" #include "fu-wistron-dock-device.h" #include "fu-wistron-dock-struct.h" struct _FuWistronDockDevice { FuHidDevice parent_instance; guint8 component_idx; guint8 update_phase; guint8 status_code; guint8 imgmode; gchar *icp_bbinfo; gchar *icp_userinfo; guint device_insert_id; }; G_DEFINE_TYPE(FuWistronDockDevice, fu_wistron_dock_device, FU_TYPE_HID_DEVICE) #define FU_WISTRON_DOCK_TRANSFER_BLOCK_SIZE 512 /* bytes */ #define FU_WISTRON_DOCK_TRANSFER_TIMEOUT 5000 /* ms */ #define FU_WISTRON_DOCK_TRANSFER_RETRY_COUNT 5 #define FU_WISTRON_DOCK_TRANSFER_RETRY_DELAY 100 /* ms */ #define FU_WISTRON_DOCK_ID_USB_CONTROL 0x06 /* 7 bytes */ #define FU_WISTRON_DOCK_ID_USB_BLOCK 0x07 /* 512 bytes */ #define FU_WISTRON_DOCK_ID_IMG_CONTROL 0x16 /* 7 bytes */ #define FU_WISTRON_DOCK_ID_DOCK_IMG_DATA 0x17 /* 512 bytes */ #define FU_WISTRON_DOCK_ID_DOCK_WDIT 0x20 /* 512 bytes */ #define FU_WISTRON_DOCK_ID_DOCK_WDFL_SIG 0x21 /* 256 bytes */ #define FU_WISTRON_DOCK_ID_DOCK_WDFL_DATA 0x22 /* 1440 bytes */ #define FU_WISTRON_DOCK_ID_DOCK_SN 0x23 /* 32 bytes */ static void fu_wistron_dock_device_to_string(FuDevice *device, guint idt, GString *str) { FuWistronDockDevice *self = FU_WISTRON_DOCK_DEVICE(device); /* FuHidDevice->to_string */ FU_DEVICE_CLASS(fu_wistron_dock_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "ComponentIdx", fu_wistron_dock_component_idx_to_string(self->component_idx)); fu_string_append(str, idt, "UpdatePhase", fu_wistron_dock_update_phase_to_string(self->update_phase)); fu_string_append(str, idt, "StatusCode", fu_wistron_dock_status_code_to_string(self->status_code)); fu_string_append_kx(str, idt, "ImgMode", self->imgmode); if (self->icp_bbinfo != NULL) fu_string_append(str, idt, "IcpBbInfo", self->icp_bbinfo); if (self->icp_userinfo != NULL) fu_string_append(str, idt, "IcpUserInfo", self->icp_userinfo); } typedef struct { guint8 *cmd; gsize cmdsz; guint8 *buf; gsize bufsz; gboolean check_result; } FuWistronDockHelper; static gboolean fu_wistron_dock_device_control_cb(FuDevice *device, gpointer user_data, GError **error) { FuWistronDockHelper *helper = (FuWistronDockHelper *)user_data; if (!fu_hid_device_set_report(FU_HID_DEVICE(device), helper->cmd[0], /* value */ helper->cmd, helper->cmdsz, FU_WISTRON_DOCK_TRANSFER_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; if (helper->check_result) { if (!fu_hid_device_get_report(FU_HID_DEVICE(device), helper->buf[0], /* value */ helper->buf, helper->bufsz, FU_WISTRON_DOCK_TRANSFER_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; if (helper->buf[7] != FU_WISTRON_DOCK_CMD_ICP_DONE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not icp-done, got 0x%02x", helper->cmd[7]); return FALSE; } } return TRUE; } static gboolean fu_wistron_dock_device_control_write(FuWistronDockDevice *self, guint8 *cmd, gsize cmdsz, gboolean check_result, GError **error) { FuWistronDockHelper helper = {.cmd = cmd, .cmdsz = cmdsz, .buf = cmd, .bufsz = cmdsz, .check_result = check_result}; return fu_device_retry_full(FU_DEVICE(self), fu_wistron_dock_device_control_cb, FU_WISTRON_DOCK_TRANSFER_RETRY_COUNT, FU_WISTRON_DOCK_TRANSFER_RETRY_DELAY, &helper, error); } static gboolean fu_wistron_dock_device_control_read(FuWistronDockDevice *self, guint8 *cmd, gsize cmdsz, guint8 *buf, gsize bufsz, gboolean check_result, GError **error) { FuWistronDockHelper helper = {.cmd = cmd, .cmdsz = cmdsz, .buf = buf, .bufsz = bufsz, .check_result = check_result}; return fu_device_retry_full(FU_DEVICE(self), fu_wistron_dock_device_control_cb, FU_WISTRON_DOCK_TRANSFER_RETRY_COUNT, FU_WISTRON_DOCK_TRANSFER_RETRY_DELAY, &helper, error); } static gboolean fu_wistron_dock_device_data_write_cb(FuDevice *device, gpointer user_data, GError **error) { FuWistronDockHelper *helper = (FuWistronDockHelper *)user_data; if (!fu_hid_device_set_report(FU_HID_DEVICE(device), helper->cmd[0], /* value */ helper->cmd, helper->cmdsz, FU_WISTRON_DOCK_TRANSFER_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(device), helper->buf[0], /* value */ helper->buf, helper->bufsz, FU_WISTRON_DOCK_TRANSFER_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; if (!fu_hid_device_get_report(FU_HID_DEVICE(device), helper->cmd[0], /* value */ helper->cmd, helper->cmdsz, FU_WISTRON_DOCK_TRANSFER_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) return FALSE; if (helper->cmd[7] != FU_WISTRON_DOCK_CMD_ICP_DONE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not icp-done, got 0x%02x", helper->cmd[7]); return FALSE; } return TRUE; } static gboolean fu_wistron_dock_device_data_write(FuWistronDockDevice *self, guint8 *cmd, gsize cmdsz, guint8 *buf, gsize bufsz, GError **error) { FuWistronDockHelper helper = {.cmd = cmd, .cmdsz = cmdsz, .buf = buf, .bufsz = bufsz}; return fu_device_retry_full(FU_DEVICE(self), fu_wistron_dock_device_data_write_cb, FU_WISTRON_DOCK_TRANSFER_RETRY_COUNT, FU_WISTRON_DOCK_TRANSFER_RETRY_DELAY, &helper, error); } static gboolean fu_wistron_dock_device_write_wdfl_sig(FuWistronDockDevice *self, const guint8 *buf, gsize bufsz, GError **error) { guint8 cmd[8] = {FU_WISTRON_DOCK_ID_IMG_CONTROL, FU_WISTRON_DOCK_CMD_DFU_WRITE_WDFL_SIG}; guint8 towrite[FU_WISTRON_DOCK_WDFL_SIG_SIZE + 1] = {FU_WISTRON_DOCK_ID_DOCK_WDFL_SIG}; if (!fu_memcpy_safe(towrite, sizeof(towrite), 0x1, /* dst */ buf, bufsz, 0x0, /* src */ bufsz, error)) return FALSE; return fu_wistron_dock_device_data_write(self, cmd, sizeof(cmd), towrite, sizeof(towrite), error); } static gboolean fu_wistron_dock_device_write_wdfl_data(FuWistronDockDevice *self, const guint8 *buf, gsize bufsz, GError **error) { guint8 cmd[8] = {FU_WISTRON_DOCK_ID_IMG_CONTROL, FU_WISTRON_DOCK_CMD_DFU_WRITE_WDFL_DATA}; guint8 towrite[FU_WISTRON_DOCK_WDFL_DATA_SIZE + 1] = {FU_WISTRON_DOCK_ID_DOCK_WDFL_DATA}; if (!fu_memcpy_safe(towrite, sizeof(towrite), 0x1, /* dst */ buf, bufsz, 0x0, /* src */ bufsz, error)) return FALSE; return fu_wistron_dock_device_data_write(self, cmd, sizeof(cmd), towrite, sizeof(towrite), error); } static gboolean fu_wistron_dock_device_set_img_address(FuWistronDockDevice *self, guint32 addr, GError **error) { guint8 cmd[8] = {FU_WISTRON_DOCK_ID_IMG_CONTROL, FU_WISTRON_DOCK_CMD_DFU_ADDRESS}; fu_memwrite_uint32(cmd + 2, addr, G_BIG_ENDIAN); return fu_wistron_dock_device_control_write(self, cmd, sizeof(cmd), TRUE, error); } static gboolean fu_wistron_dock_device_write_img_data(FuWistronDockDevice *self, const guint8 *buf, gsize bufsz, GError **error) { guint8 cmd[8] = {FU_WISTRON_DOCK_ID_IMG_CONTROL, FU_WISTRON_DOCK_CMD_DFU_WRITEIMG_BLOCK}; guint8 towrite[FU_WISTRON_DOCK_TRANSFER_BLOCK_SIZE + 1] = { FU_WISTRON_DOCK_ID_DOCK_IMG_DATA}; if (!fu_memcpy_safe(towrite, sizeof(towrite), 0x1, /* dst */ buf, bufsz, 0x0, /* src */ bufsz, error)) return FALSE; return fu_wistron_dock_device_data_write(self, cmd, sizeof(cmd), towrite, sizeof(towrite), error); } static gboolean fu_wistron_dock_device_write_blocks(FuWistronDockDevice *self, FuChunkArray *chunks, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, fu_chunk_array_length(chunks)); for (guint i = 0; i < fu_chunk_array_length(chunks); i++) { g_autoptr(FuChunk) chk = fu_chunk_array_index(chunks, i); /* set address */ if (!fu_wistron_dock_device_set_img_address(self, fu_chunk_get_address(chk), error)) { g_prefix_error(error, "failed to set img address 0x%x", fu_chunk_get_address(chk)); return FALSE; } /* write */ if (!fu_wistron_dock_device_write_img_data(self, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed to write img data 0x%x", fu_chunk_get_address(chk)); return FALSE; } /* update progress */ fu_progress_step_done(progress); } /* success */ return TRUE; } static FuFirmware * fu_wistron_dock_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { g_autoptr(FuFirmware) firmware = fu_archive_firmware_new(); g_autoptr(FuFirmware) fw_cbin = NULL; g_autoptr(FuFirmware) fw_new = fu_firmware_new(); g_autoptr(FuFirmware) fw_wdfl = NULL; g_autoptr(FuFirmware) fw_wsig = NULL; /* unzip and get images */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; fw_wsig = fu_archive_firmware_get_image_fnmatch(FU_ARCHIVE_FIRMWARE(firmware), "*.wdfl.sig", error); if (fw_wsig == NULL) return NULL; fw_wdfl = fu_archive_firmware_get_image_fnmatch(FU_ARCHIVE_FIRMWARE(firmware), "*.wdfl", error); if (fw_wdfl == NULL) return NULL; fw_cbin = fu_archive_firmware_get_image_fnmatch(FU_ARCHIVE_FIRMWARE(firmware), "*.bin", error); if (fw_cbin == NULL) return NULL; /* sanity check sizes */ if (fu_firmware_get_size(fw_wsig) < FU_WISTRON_DOCK_WDFL_SIG_SIZE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "WDFL signature size invalid, got 0x%x, expected >= 0x%x", (guint)fu_firmware_get_size(fw_wsig), (guint)FU_WISTRON_DOCK_WDFL_SIG_SIZE); return NULL; } if (fu_firmware_get_size(fw_wdfl) != FU_WISTRON_DOCK_WDFL_DATA_SIZE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "WDFL size invalid, got 0x%x, expected 0x%x", (guint)fu_firmware_get_size(fw_wdfl), (guint)FU_WISTRON_DOCK_WDFL_DATA_SIZE); return NULL; } /* success */ fu_firmware_set_id(fw_wsig, FU_FIRMWARE_ID_SIGNATURE); fu_firmware_add_image(fw_new, fw_wsig); fu_firmware_set_id(fw_wdfl, FU_FIRMWARE_ID_HEADER); fu_firmware_add_image(fw_new, fw_wdfl); fu_firmware_set_id(fw_cbin, FU_FIRMWARE_ID_PAYLOAD); fu_firmware_add_image(fw_new, fw_cbin); return g_steal_pointer(&fw_new); } static gboolean fu_wistron_dock_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuWistronDockDevice *self = FU_WISTRON_DOCK_DEVICE(device); g_autoptr(GBytes) fw_cbin = NULL; g_autoptr(GBytes) fw_wdfl = NULL; g_autoptr(GBytes) fw_wsig = NULL; g_autoptr(FuChunkArray) chunks = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "write-wdfl-signature"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "write-wdfl-data"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write-payload"); /* write WDFL signature */ fw_wsig = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_SIGNATURE, error); if (fw_wsig == NULL) return FALSE; if (!fu_wistron_dock_device_write_wdfl_sig(self, g_bytes_get_data(fw_wsig, NULL), g_bytes_get_size(fw_wsig), error)) { g_prefix_error(error, "failed to write WDFL signature: "); return FALSE; } fu_progress_step_done(progress); /* write WDFL data */ fw_wdfl = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_HEADER, error); if (fw_wdfl == NULL) return FALSE; if (!fu_wistron_dock_device_write_wdfl_data(self, g_bytes_get_data(fw_wdfl, NULL), g_bytes_get_size(fw_wdfl), error)) { g_prefix_error(error, "failed to write WDFL data: "); return FALSE; } fu_progress_step_done(progress); /* write each block */ fw_cbin = fu_firmware_get_image_by_id_bytes(firmware, FU_FIRMWARE_ID_PAYLOAD, error); if (fw_cbin == NULL) return FALSE; chunks = fu_chunk_array_new_from_bytes(fw_cbin, 0x0, FU_WISTRON_DOCK_TRANSFER_BLOCK_SIZE); if (!fu_wistron_dock_device_write_blocks(self, chunks, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to write payload: "); return FALSE; } fu_progress_step_done(progress); /* success! */ return TRUE; } static gboolean fu_wistron_dock_device_ensure_mcuid(FuWistronDockDevice *self, GError **error) { guint8 cmd[8] = {FU_WISTRON_DOCK_ID_USB_CONTROL, FU_WISTRON_DOCK_CMD_ICP_MCUID}; guint8 buf[8] = {FU_WISTRON_DOCK_ID_USB_CONTROL}; g_autofree gchar *tmp = NULL; if (!fu_wistron_dock_device_control_read(self, cmd, sizeof(cmd), buf, sizeof(buf), TRUE, error)) return FALSE; tmp = fu_memstrsafe(buf, sizeof(buf), 2, 5, error); if (tmp == NULL) return FALSE; fu_device_add_instance_str(FU_DEVICE(self), "MCUID", tmp); return fu_device_build_instance_id(FU_DEVICE(self), error, "USB", "VID", "PID", "MCUID", NULL); } static gboolean fu_wistron_dock_device_ensure_bbinfo(FuWistronDockDevice *self, GError **error) { guint8 cmd[8] = {FU_WISTRON_DOCK_ID_USB_CONTROL, FU_WISTRON_DOCK_CMD_ICP_BBINFO}; guint8 buf[8] = {FU_WISTRON_DOCK_ID_USB_CONTROL}; if (!fu_wistron_dock_device_control_read(self, cmd, sizeof(cmd), buf, sizeof(buf), TRUE, error)) return FALSE; g_free(self->icp_bbinfo); self->icp_bbinfo = g_strdup_printf("%u.%u.%u", buf[2], buf[3], buf[4]); return TRUE; } static gboolean fu_wistron_dock_device_ensure_userinfo(FuWistronDockDevice *self, GError **error) { guint8 cmd[8] = {FU_WISTRON_DOCK_ID_USB_CONTROL, FU_WISTRON_DOCK_CMD_ICP_USERINFO}; guint8 buf[8] = {FU_WISTRON_DOCK_ID_USB_CONTROL}; if (!fu_wistron_dock_device_control_read(self, cmd, sizeof(cmd), buf, sizeof(buf), TRUE, error)) return FALSE; g_free(self->icp_userinfo); self->icp_userinfo = g_strdup_printf("%u.%u.%u", buf[2], buf[3], buf[4]); return TRUE; } static gboolean fu_wistron_dock_device_parse_wdit_img(FuWistronDockDevice *self, const guint8 *buf, gsize bufsz, gsize offset, guint8 device_cnt, GError **error) { for (guint j = 0; j < device_cnt; j++) { guint32 version_raw = 0; guint8 status; g_autofree gchar *name = NULL; g_autofree gchar *version0 = NULL; g_autofree gchar *version1 = NULL; g_autofree gchar *version2 = NULL; g_autoptr(GByteArray) st = NULL; /* parse */ st = fu_struct_wistron_dock_wdit_img_parse(buf, bufsz, offset, error); if (st == NULL) return FALSE; /* versions */ version_raw = fu_struct_wistron_dock_wdit_img_get_version_build(st); if (version_raw != 0) version0 = fu_version_from_uint32(version_raw, FWUPD_VERSION_FORMAT_QUAD); version_raw = fu_struct_wistron_dock_wdit_img_get_version1(st); if (version_raw != 0) version1 = fu_version_from_uint32(version_raw, FWUPD_VERSION_FORMAT_QUAD); version_raw = fu_struct_wistron_dock_wdit_img_get_version2(st); if (version_raw != 0) version2 = fu_version_from_uint32(version_raw, FWUPD_VERSION_FORMAT_QUAD); /* name */ name = fu_struct_wistron_dock_wdit_img_get_name(st); status = fu_struct_wistron_dock_wdit_img_get_status(st); g_debug("%s: bld:%s, img1:%s, img2:%s", name, version0, version1, version2); g_debug(" - comp-id:%u, mode:%u, status:%u/%u", fu_struct_wistron_dock_wdit_img_get_comp_id(st), fu_struct_wistron_dock_wdit_img_get_mode(st), (guint)status & 0x0F, (guint)(status & 0xF0) >> 4); offset += st->len; } /* success */ return TRUE; } static gboolean fu_wistron_dock_device_ensure_wdit(FuWistronDockDevice *self, GError **error) { guint8 update_state = 0x0; guint8 buf[FU_WISTRON_DOCK_WDIT_SIZE + 1] = {FU_WISTRON_DOCK_ID_DOCK_WDIT}; g_autoptr(GByteArray) st = NULL; /* get WDIT */ if (!fu_hid_device_get_report(FU_HID_DEVICE(self), buf[0], /* value */ buf, sizeof(buf), FU_WISTRON_DOCK_TRANSFER_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE | FU_HID_DEVICE_FLAG_RETRY_FAILURE | FU_HID_DEVICE_FLAG_ALLOW_TRUNC, error)) return FALSE; /* unpack */ st = fu_struct_wistron_dock_wdit_parse(buf, sizeof(buf), 0x0, error); if (st == NULL) return FALSE; if (fu_struct_wistron_dock_wdit_get_tag_id(st) != FU_WISTRON_DOCK_WDIT_TAG_ID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "WDIT tag invalid, expected 0x%x, got 0x%x", (guint)FU_WISTRON_DOCK_WDIT_TAG_ID, fu_struct_wistron_dock_wdit_get_tag_id(st)); return FALSE; } /* verify VID & PID */ if (fu_struct_wistron_dock_wdit_get_vid(st) != fu_usb_device_get_vid(FU_USB_DEVICE(self)) || fu_struct_wistron_dock_wdit_get_pid(st) != fu_usb_device_get_pid(FU_USB_DEVICE(self))) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "USB VID:PID invalid, expected %04X:%04X, got %04X:%04X", (guint)fu_usb_device_get_vid(FU_USB_DEVICE(self)), (guint)fu_usb_device_get_pid(FU_USB_DEVICE(self)), fu_struct_wistron_dock_wdit_get_vid(st), fu_struct_wistron_dock_wdit_get_pid(st)); return FALSE; } /* image mode */ self->imgmode = fu_struct_wistron_dock_wdit_get_imgmode(st); if (self->imgmode == 0) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); else if (self->imgmode == 1) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); /* update state */ update_state = fu_struct_wistron_dock_wdit_get_update_state(st); self->update_phase = (update_state & 0xF0) >> 4; if (self->update_phase == FU_WISTRON_DOCK_UPDATE_PHASE_DOWNLOAD) fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); else fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); if (fu_wistron_dock_update_phase_to_string(self->update_phase) == NULL) g_warning("unknown update_phase 0x%02x", self->update_phase); self->component_idx = update_state & 0xF; if (fu_wistron_dock_component_idx_to_string(self->component_idx) == NULL) g_warning("unknown component_idx 0x%02x", self->component_idx); /* status code */ self->status_code = fu_struct_wistron_dock_wdit_get_status_code(st); if (fu_wistron_dock_status_code_to_string(self->status_code) == NULL) g_warning("unknown status_code 0x%02x", self->status_code); /* composite version */ fu_device_set_version_u32(FU_DEVICE(self), fu_struct_wistron_dock_wdit_get_composite_version(st)); /* for debugging only */ if (!fu_wistron_dock_device_parse_wdit_img( self, buf, sizeof(buf), st->len + 0x1, MIN(fu_struct_wistron_dock_wdit_get_device_cnt(st), 32), error)) { g_prefix_error(error, "failed to parse imgs: "); return FALSE; } /* adding the MCU while flashing the device, ignore until it comes back in runtime mode */ if (self->update_phase == FU_WISTRON_DOCK_UPDATE_PHASE_DEPLOY && self->status_code == FU_WISTRON_DOCK_STATUS_CODE_UPDATING) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "ignoring device in MCU mode"); return FALSE; } /* success */ return TRUE; } static gboolean fu_wistron_dock_device_setup(FuDevice *device, GError **error) { FuWistronDockDevice *self = FU_WISTRON_DOCK_DEVICE(device); /* FuUsbDevice->setup */ if (!FU_DEVICE_CLASS(fu_wistron_dock_device_parent_class)->setup(device, error)) return FALSE; if (!fu_wistron_dock_device_ensure_mcuid(self, error)) { g_prefix_error(error, "failed to get MCUID: "); return FALSE; } if (!fu_wistron_dock_device_ensure_bbinfo(self, error)) { g_prefix_error(error, "failed to get BBINFO: "); return FALSE; } if (!fu_wistron_dock_device_ensure_userinfo(self, error)) { g_prefix_error(error, "failed to get USERINFO: "); return FALSE; } if (!fu_wistron_dock_device_ensure_wdit(self, error)) { g_prefix_error(error, "failed to get WDIT: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_wistron_dock_device_detach(FuDevice *device, FuProgress *progress, GError **error) { FuWistronDockDevice *self = FU_WISTRON_DOCK_DEVICE(device); guint8 cmd[8] = {FU_WISTRON_DOCK_ID_IMG_CONTROL, FU_WISTRON_DOCK_CMD_DFU_ENTER}; /* sanity check */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in bootloader mode, skipping"); return TRUE; } if (!fu_wistron_dock_device_control_write(self, cmd, sizeof(cmd), FALSE, error)) return FALSE; return fu_wistron_dock_device_ensure_wdit(self, error); } static gboolean fu_wistron_dock_device_insert_cb(gpointer user_data) { FuWistronDockDevice *self = FU_WISTRON_DOCK_DEVICE(user_data); g_autoptr(FwupdRequest) request = fwupd_request_new(); g_autoptr(GError) error_local = NULL; /* interactive request to start the SPI write */ fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_INSERT_USB_CABLE); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); if (!fu_device_emit_request(FU_DEVICE(self), request, NULL, &error_local)) g_warning("%s", error_local->message); /* success */ self->device_insert_id = 0; return G_SOURCE_REMOVE; } static gboolean fu_wistron_dock_device_cleanup(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuWistronDockDevice *self = FU_WISTRON_DOCK_DEVICE(device); /* ensure the timeout has been cleared, even on error */ if (self->device_insert_id != 0) { g_source_remove(self->device_insert_id); self->device_insert_id = 0; } return TRUE; } static gboolean fu_wistron_dock_device_attach(FuDevice *device, FuProgress *progress, GError **error) { FuWistronDockDevice *self = FU_WISTRON_DOCK_DEVICE(device); guint8 cmd[8] = {FU_WISTRON_DOCK_ID_IMG_CONTROL, FU_WISTRON_DOCK_CMD_DFU_EXIT}; g_autoptr(FwupdRequest) request = fwupd_request_new(); /* sanity check */ if (!fu_wistron_dock_device_ensure_wdit(self, error)) return FALSE; if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { g_debug("already in runtime mode, skipping"); return TRUE; } if (!fu_wistron_dock_device_control_write(self, cmd, sizeof(cmd), FALSE, error)) return FALSE; fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); /* the user has to remove the USB cable, wait 15 seconds, then re-insert it */ fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_USB_CABLE); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); if (!fu_device_emit_request(device, request, progress, error)) return FALSE; /* set a timeout, which will trigger as we're waiting for the device -- * no sync sleep is possible as the device will re-enumerate one more time */ fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_BUSY); self->device_insert_id = g_timeout_add_seconds(20, fu_wistron_dock_device_insert_cb, self); /* success */ return TRUE; } static void fu_wistron_dock_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 20, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 75, "reload"); } static void fu_wistron_dock_device_init(FuWistronDockDevice *self) { fu_device_add_protocol(FU_DEVICE(self), "com.wistron.dock"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_QUAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG); fu_device_add_request_flag(FU_DEVICE(self), FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); fu_device_set_remove_delay(FU_DEVICE(self), 5 * 60 * 1000); } static void fu_wistron_dock_device_finalize(GObject *object) { FuWistronDockDevice *self = FU_WISTRON_DOCK_DEVICE(object); if (self->device_insert_id != 0) g_source_remove(self->device_insert_id); g_free(self->icp_bbinfo); g_free(self->icp_userinfo); G_OBJECT_CLASS(fu_wistron_dock_device_parent_class)->finalize(object); } static void fu_wistron_dock_device_class_init(FuWistronDockDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_wistron_dock_device_finalize; klass_device->to_string = fu_wistron_dock_device_to_string; klass_device->prepare_firmware = fu_wistron_dock_device_prepare_firmware; klass_device->write_firmware = fu_wistron_dock_device_write_firmware; klass_device->attach = fu_wistron_dock_device_attach; klass_device->detach = fu_wistron_dock_device_detach; klass_device->setup = fu_wistron_dock_device_setup; klass_device->cleanup = fu_wistron_dock_device_cleanup; klass_device->set_progress = fu_wistron_dock_device_set_progress; } fwupd-1.9.16/plugins/wistron-dock/fu-wistron-dock-device.h000066400000000000000000000005401460375044200235060ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_WISTRON_DOCK_DEVICE (fu_wistron_dock_device_get_type()) G_DECLARE_FINAL_TYPE(FuWistronDockDevice, fu_wistron_dock_device, FU, WISTRON_DOCK_DEVICE, FuHidDevice) fwupd-1.9.16/plugins/wistron-dock/fu-wistron-dock-plugin.c000066400000000000000000000014161460375044200235430ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-wistron-dock-device.h" #include "fu-wistron-dock-plugin.h" struct _FuWistronDockPlugin { FuPlugin parent_instance; }; G_DEFINE_TYPE(FuWistronDockPlugin, fu_wistron_dock_plugin, FU_TYPE_PLUGIN) static void fu_wistron_dock_plugin_init(FuWistronDockPlugin *self) { } static void fu_wistron_dock_plugin_constructed(GObject *obj) { FuPlugin *plugin = FU_PLUGIN(obj); fu_plugin_add_device_gtype(plugin, FU_TYPE_WISTRON_DOCK_DEVICE); } static void fu_wistron_dock_plugin_class_init(FuWistronDockPluginClass *klass) { FuPluginClass *plugin_class = FU_PLUGIN_CLASS(klass); plugin_class->constructed = fu_wistron_dock_plugin_constructed; } fwupd-1.9.16/plugins/wistron-dock/fu-wistron-dock-plugin.h000066400000000000000000000003711460375044200235470ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include G_DECLARE_FINAL_TYPE(FuWistronDockPlugin, fu_wistron_dock_plugin, FU, WISTRON_DOCK_PLUGIN, FuPlugin) fwupd-1.9.16/plugins/wistron-dock/fu-wistron-dock.rs000066400000000000000000000021101460375044200224410ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(ToString)] #[repr(u8)] enum WistronDockStatusCode { Enter = 0x1, Prepare = 0x2, Updating = 0x3, Complete = 0x4, // unplug cable to trigger update } #[derive(Parse)] struct WistronDockWdit { hid_id: u8, tag_id: u16be, vid: u16le, pid: u16le, imgmode: u8, update_state: u8, status_code: WistronDockStatusCode, composite_version: u32be, device_cnt: u8, reserved: u8, } #[derive(ToString)] #[repr(u8)] enum WistronDockComponentIdx { Mcu = 0x0, Pd = 0x1, Audio = 0x2, Usb = 0x3, Mst = 0x4, Spi = 0xA, Dock = 0xF, } #[derive(Parse)] struct WistronDockWditImg { comp_id: WistronDockComponentIdx, mode: u8, // 0=single, 1=dual-s, 2=dual-a status: u8, // 0=unknown, 1=valid, 2=invalid reserved: u8, version_build: u32be, version1: u32be, version2: u32be, name: [char; 32], } #[derive(ToString)] enum WistronDockUpdatePhase { Download = 0x1, Deploy = 0x2, } fwupd-1.9.16/plugins/wistron-dock/meson.build000066400000000000000000000007321460375044200212120ustar00rootroot00000000000000if gusb.found() plugins += {meson.current_source_dir().split('/')[-1]: true} cargs = ['-DG_LOG_DOMAIN="FuPluginWistronDock"'] plugin_quirks += files('wistron-dock.quirk') plugin_builtins += static_library('fu_plugin_wistron_dock', rustgen.process('fu-wistron-dock.rs'), sources: [ 'fu-wistron-dock-device.c', 'fu-wistron-dock-plugin.c', ], include_directories: plugin_incdirs, link_with: plugin_libs, c_args: cargs, dependencies: plugin_deps, ) endif fwupd-1.9.16/plugins/wistron-dock/wistron-dock.quirk000066400000000000000000000002101460375044200225370ustar00rootroot00000000000000# Wistron Travel Dock [USB\VID_0FB8&PID_0010] Plugin = wistron_dock # Wistron USB-C Dock [USB\VID_17EF&PID_30EF] Plugin = wistron_dock fwupd-1.9.16/po/000077500000000000000000000000001460375044200133605ustar00rootroot00000000000000fwupd-1.9.16/po/.gitignore000066400000000000000000000000061460375044200153440ustar00rootroot00000000000000*.pot fwupd-1.9.16/po/LINGUAS000066400000000000000000000002111460375044200143770ustar00rootroot00000000000000af ast ca cs da de en_GB eo es eu fi fr fur gl he hi hr hu id it ja ka kk ko ky lt nl oc pa pl pt pt_BR ru si sk sr sv tr uk zh_CN zh_TW fwupd-1.9.16/po/POTFILES.in000066400000000000000000000011141460375044200151320ustar00rootroot00000000000000data/remotes.d/lvfs.metainfo.xml data/remotes.d/lvfs-testing.metainfo.xml libfwupdplugin/fu-bios-settings.c libfwupdplugin/tests/bios-attrs/dell-xps13-9310/strings.txt policy/org.freedesktop.fwupd.policy.in plugins/dfu/fu-dfu-tool.c plugins/tpm/fu-tpm-eventlog.c plugins/uefi-capsule/fu-uefi-capsule-plugin.c plugins/uefi-capsule/fu-uefi-tool.c plugins/uefi-dbx/fu-dbxtool.c src/fu-console.c src/fu-debug.c src/fu-engine-helper.c src/fu-main.c src/fu-offline.c src/fu-remote-list.c src/fu-security-attr-common.c src/fu-tool.c src/fu-util.c src/fu-util-bios-setting.c src/fu-util-common.c fwupd-1.9.16/po/POTFILES.skip000066400000000000000000000000501460375044200154700ustar00rootroot00000000000000data/org.freedesktop.fwupd.metainfo.xml fwupd-1.9.16/po/af.po000066400000000000000000000164321460375044200143140ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # F Wolff , 2019 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Afrikaans (http://app.transifex.com/freedesktop/fwupd/language/af/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: af\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minuut oor" msgstr[1] "%.0f minute oor" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dag" msgstr[1] "%u dae" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u uur" msgstr[1] "%u ure" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuut" msgstr[1] "%u minute" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u sekonde" msgstr[1] "%u sekondes" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Ouderdom" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "’n Bywerking vereis dat die stelsel herbegin om te voltooi." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Antwoord ja op alle vrae" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Kanselleer" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Gekanselleer" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Verander" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Kontrolesom" #. TRANSLATORS: error message msgid "Command not found" msgstr "Opdrag nie gevind nie" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Pak tans uit…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Beskrywing" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Toestel bygevoeg:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Toestel verander:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Toestel verwyder:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Toestelle wat suksesvol bygewerk is:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Toestelle wat nie korrek bygewerk is nie:" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Klaar!" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Gradeer tans %s af vanaf %s na %s… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Gradeer tans %s af…" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Laai tans af…" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Geaktiveer" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Vee tans uit…" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Lêernaam" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Lêernaamhandtekening" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Gevind" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Ledig…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Installeer tans op %s…" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Sleutelring" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Minder as een minuut oor" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Laai tans…" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metadata-URI" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "Goed" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Wagwoord" msgid "Print the version number" msgstr "Druk die weergawenommer" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioriteit" msgid "Proceed with upload?" msgstr "Gaan voort met oplaai?" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Lees tans…" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Herinstalleer tans %s met %s… " #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Internetverbinding is nodig" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Herbegin nou?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Herbegin tans toestel…" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Skeduleer die installasie vir die volgende herbegin indien moontlik" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Skeduleer tans…" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Wys toestelle wat nie bygewerk kan word nie" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Opsomming" msgid "Target" msgstr "Teiken" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipe" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Onbekend" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Werk nou by?" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Werk tans %s by vanaf %s na %s… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Werk tans %s by…" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Gebruikernaam" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verifieer tans…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Weergawe" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Wag tans…" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Skryf tans…" fwupd-1.9.16/po/ast.po000066400000000000000000000036711460375044200145160ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # enolp , 2017 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Asturian (http://www.transifex.com/freedesktop/fwupd/language/ast/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ast\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Amestóse" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Encaboxóse" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Camudóse" #. TRANSLATORS: this is the encryption method used when writing msgid "Cipher" msgstr "Cifráu" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Alcontróse" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" msgid "Mode" msgstr "Mou" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' #. TRANSLATORS: section header for the release name msgid "Name" msgstr "Nome" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protocolu" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Rexón" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Desanicióse" #. TRANSLATORS: serial number, e.g. '00012345' msgid "Serial" msgstr "Serial" #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Estáu" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Estáu" #. TRANSLATORS: transfer size in bytes msgid "Transfer Size" msgstr "Tamañu de tresferencia" fwupd-1.9.16/po/ca.po000066400000000000000000003722421460375044200143150ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Antoni Bella Pérez , 2017-2023 # Robert Antoni Buj i Gelonch , 2017 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Catalan (http://app.transifex.com/freedesktop/fwupd/language/ca/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ca\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "Manca %.0f minut" msgstr[1] "Manquen %.0f minuts" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "Actualitza el BMC %s" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "%s Actualització de la bateria" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "%s Actualitza el microcodi de la CPU" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "%s Actualització de la càmera" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "Actualització de la configuració %s" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Actualització ME del consumidor %s" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Actualització del controlador %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Actualització ME corporativa de %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Actualització del dispositiu %s" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "Actualitza la pantalla %s" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "Actualització el moll %s" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "Actualització del controlador %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Actualització del controlador incrustat %s" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "%s actualització del lector d'empremtes" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "Actualització de la unitat flaix %s" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "%s Actualització de la GPU" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "%s Actualització de la tauleta gràfica" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "%s Actualització del teclat" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "Actualització ME de %s" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "%s Actualització del ratolí" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "Actualització del controlador de xarxa %s" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "Actualització de l'SSD %s" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "Actualització del controlador d'emmagatzematge %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Actualització del sistema %s" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "%s Actualització del TPM" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Actualització del controlador Thunderbolt %s" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "%s Actualització del ratolí tàctil" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "%s actualització del moll USB" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "%s actualització del receptor USB" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Actualització de %s" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s i tots els dispositius connectats poden no ser usables en actualitzar." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "%s ha aparegut: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "%s ha canviat: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "%s ha desaparegut: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s actualment no es pot actualitzar" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "Mode de fabricació %s" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s haurà d'estar connectat durant tota l'actualització per a evitar danys." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s haurà de romandre connectat a una font d'alimentació durant tota l'actualització per a evitar danys." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "Superposa %s" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s versió %s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "Versió %s" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dia" msgstr[1] "%u dies" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] " %u dispositiu té una actualització de microprogramari disponible." msgstr[1] "%u dispositius tenen una actualització de microprogramari disponible." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u dispositiu no és la millor configuració coneguda." msgstr[1] "%u dispositius no són la millor configuració coneguda." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u hora" msgstr[1] "%u hores" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "Està admès %u dispositiu local" msgstr[1] "Estan admesos %u dispositius locals" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minut" msgstr[1] "%u minuts" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u segon" msgstr[1] "%u segons" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (llindar %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(obsolet)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "Un PCR en el TPM ara té un valor no vàlid" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "Protecció de reinjecció de microprogramari d'AMD" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "Protecció d'escriptura del microprogramari d'AMD" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "Protecció contra la reversió del processador segur d'AMD" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "ARCHIVE FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATFILE]" msgstr "ARXIU MICROPROGRAMARI METAINFO [MICROPROGRAMARI] [METAINFO] [FITXER_JCAT]" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Acció requerida:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Activa els dispositius." #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Activa els dispositius pendents." msgid "Activate the new firmware on the device" msgstr "Activa el microprogramari nou al dispositiu" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Activació de l'actualització del microprogramari" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Activa l'actualització del microprogramari per a" #. TRANSLATORS: command description msgid "Adds devices to watch for future emulation" msgstr "Afegeix els dispositius que es vigilaran per a una emulació futura" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Antiguitat" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Voleu acceptar i habilitar el remot?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Àlies per a «%s»" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "Tots els PCR en el TPM ara són vàlids" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Tots els PCR en el TPM són vàlids" #. TRANSLATORS: an application is preventing system updates msgid "All devices are prevented from update by system inhibit" msgstr "La inhibició del sistema impedeix que s'actualitzin tots els dispositius" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Tots els dispositius del mateix tipus s'actualitzaran al mateix temps" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Permet tornar a la versió anterior del microprogramari" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Permetre tornar a instal·lar les versions existents del microprogramari" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Permet canviar de branca de microprogramari" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Branca alternativa" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "Hi ha una actualització en curs" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Una actualització requereix un reinici per a completar-se." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Una actualització requereix que s'aturi el sistema per a finalitzar." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Respon sí a totes les preguntes" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Aplica les actualitzacions de microprogramari" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Aplica una actualització fins i tot quan no se us aconselli." #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Aplica els fitxers d'actualització." #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "S'està aplicant l'actualització…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Microprogramari aprovat:" msgstr[1] "Microprogramari aprovat:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Demana-m'ho de nou la pròxima vegada?" #. TRANSLATORS: command description msgid "Asks the daemon to quit" msgstr "Demana al dimoni sortir" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Adjunta al mode microprogramari." #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "S'està autenticant..." #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Es requereixen els detalls d'autenticació" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Es requereix autenticació per a desactualitzar el microprogramari en un dispositiu extraïble" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Es requereix autenticació per a desactualitzar el microprogramari en aquesta màquina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to fix a host security issue" msgstr "Cal autenticació per a solucionar un problema de seguretat a l'amfitrió" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Cal autenticació per a modificar les opcions de configuració del BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Es necessita l'autenticació per a modificar un remot configurat emprat per a les actualitzacions del microprogramari" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Es requereix autenticació per a modificar la configuració del dimoni" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "Cal autenticació per a llegir les opcions de configuració del BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Es requereix autenticació per a establir la llista de microprogramari aprovat" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Es requereix autenticació per a signar les dades emprant el certificat del client" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Es requereix autenticació per a canviar a la nova versió del microprogramari" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to undo the fix for a host security issue" msgstr "Cal autenticació per a desfer la solució d'un problema de seguretat a l'amfitrió" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Es requereix autenticació per a desbloquejar un dispositiu" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Es requereix autenticació per a actualitzar el microprogramari en un dispositiu extraïble" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Es requereix autenticació per a actualitzar el microprogramari en aquesta màquina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Es requereix autenticació per a actualitzar les sumes de verificació emmagatzemades pels dispositius" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Informa automàticament" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Voleu pujar-lo automàticament cada vegada?" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS Firmware Updates" msgstr "Actualitzacions de microprogramari del BIOS" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "Protecció contra la reversió del BIOS" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS firmware updates" msgstr "Actualitzacions de microprogramari del BIOS" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "Protecció contra la reversió del BIOS" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "Actualitzacions del BIOS lliurades mitjançant LVFS o Windows Update" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "CONSTRUCTOR_XML NOM_FITXER_DEST" msgid "BYTES" msgstr "BYTES" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Bateria" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Vincula el controlador actual." #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Fitxers del microprogramari bloquejat:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Versió bloquejada" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Microprogramari bloquejat:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Bloqueja la instal·lació d'un microprogramari específic." #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Versió del carregador d'arrencada" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Branca" #. TRANSLATORS: command description msgid "Build a cabinet archive from a firmware blob and XML metadata" msgstr "Construeix un arxiu Cabinet a partir d’un blob de microprogramari i metadades XML" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Construeix un fitxer de microprogramari." #. TRANSLATORS: longer description msgid "CPU Microcode must be updated to mitigate against various information-disclosure security issues." msgstr "El microcodi de la CPU s'ha d'actualitzar per a mitigar diversos problemes de seguretat de divulgació d'informació." #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Cancel·la" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "S'ha cancel·lat" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "No s'ha pogut aplicar perquè l'actualització de la dbx ja s'ha aplicat." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "No s'han pogut aplicar les actualitzacions en els suports en viu" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "S'ha canviat" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Comprova que la suma criptogràfica coincideix amb el microprogramari." #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Suma de verificació" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Tria una branca" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Tria un dispositiu" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Tria un microprogramari" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Tria un llançament" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Trieu l'ESP:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Tria un volum" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Esborra els resultats de l'última actualització." #. TRANSLATORS: error message msgid "Command not found" msgstr "No s'ha trobat cap ordre" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Sostinguda per la comunitat" #. TRANSLATORS: title prefix for the BIOS settings dialog msgid "Configuration Change Suggested" msgstr "Canvi de configuració suggerit" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "La configuració només la pot llegir l'administrador del sistema" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Converteix un fitxer de microprogramari." #. TRANSLATORS: when the update was built msgid "Created" msgstr "Creat" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Crítica" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Està disponible la verificació de la suma criptogràfica" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Valor actual" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Versió actual" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "ID_DISPOSITIU|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Utilitat DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opcions per a la depuració" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "S'està descomprimint..." #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Descripció" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Separa del mode carregador d'arrencada." #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Detalls" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "Cal desviar-se de la millor configuració coneguda?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Etiquetes del dispositiu" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "ID del dispositiu" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "S'ha afegit el dispositiu:" #. TRANSLATORS: the device is already connected msgid "Device already exists" msgstr "El dispositiu ja existeix" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "L'energia de la bateria del dispositiu és massa baixa" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "L'energia de la bateria és massa baixa (%u%%, requereix %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "El dispositiu pot recuperar fallades de flaix" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "El dispositiu no es pot emprar mentre la tapa està tancada" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "S'ha canviat el dispositiu:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "L'emulació del dispositiu no està activada." #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "És obligatori el microprogramari del dispositiu per a comprovar la versió" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "El dispositiu està emulat" #. TRANSLATORS: device cannot be interrupted, for instance taking a phone call msgid "Device is in use" msgstr "El dispositiu està en ús" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "El dispositiu està bloquejat" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "El dispositiu és necessari per a instal·lar totes les versions subministrades" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "El dispositiu és inabastable" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "El dispositiu no és accessible o es troba fora de l'abast" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "El dispositiu es podrà usar durant tota l'actualització" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "El dispositiu està esperant que s'apliqui l'actualització" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "S'ha eliminat el dispositiu:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "El dispositiu requereix que es connecti l'alimentació" #. TRANSLATORS: device does not have a display connected msgid "Device requires a display to be plugged in" msgstr "El dispositiu requereix que hi hagi connectada una pantalla" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "El dispositiu requereix una llicència de programari per a actualitzar" #. TRANSLATORS: longer description msgid "Device software updates are provided for this device." msgstr "Es proporcionen actualitzacions de programari de dispositiu per a aquest dispositiu." #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Etapes d'actualització del dispositiu" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "El dispositiu admet el canvi a una branca diferent de microprogramari" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Mètode d'actualització del dispositiu" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Cal activar l'actualització del dispositiu" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "El dispositiu farà una còpia de seguretat del microprogramari abans d'instal·lar" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "El dispositiu no tornarà a aparèixer un cop finalitzada l'actualització" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Els dispositius que s'han actualitzat correctament:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Els dispositius que no s'han actualitzat correctament:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Dispositius sense actualitzacions de microprogramari disponibles:" #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Dispositius amb la versió més recent del microprogramari disponible:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "No s'ha trobat cap dispositiu amb GUID coincident" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Inhabilitada" msgid "Disabled fwupdate debugging" msgstr "La depuració del «fwupdate» està inhabilitada" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Inhabilita un remot indicat." #. TRANSLATORS: command line option msgid "Display version" msgstr "Mostra la versió" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Distribució" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "No comprovar si hi ha metadades antigues" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "No comprovar si hi ha un historial sense informar" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "No comprovar si la baixada des del remot ha d'estar habilitada" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "No verifiquis ni demanis reiniciar després d'actualitzar" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "No incloure el prefix de domini del registre" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "No incloure el prefix de la marca de temps" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "No realitzar les comprovacions de seguretat al dispositiu" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "No demanis pels dispositius" #. TRANSLATORS: command line option msgid "Do not prompt to fix security issues" msgstr "No demanis resoldre problemes de seguretat" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "No cercar el microprogramari quan s'analitzi" #. TRANSLATORS: warning message shown after update has been scheduled msgid "Do not turn off your computer or remove the AC adaptor while the update is in progress." msgstr "No apagueu l'ordinador ni desendolleu l'adaptador de CA mentre estigui en curs l'actualització." #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "No escriure a la base de dades de l'historial" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Enteneu les conseqüències de canviar la branca del microprogramari?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Voleu inhabilitar aquesta característica per a futures actualitzacions?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "Voleu activar-la ara?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Voleu refrescar ara aquest remot?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Voleu pujar automàticament els informes per a futures actualitzacions?" #. TRANSLATORS: command line option msgid "Don't prompt for authentication (less details may be shown)" msgstr "No demanis autenticació (pot mostrar menys detalls)" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Fet!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Voleu desactualitzar %s des de %s a %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Desactualitza el microprogramari en un dispositiu." #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "S'està desactualitzant %s des de %s a %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "S'està desactualitzant %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Descarrega un fitxer" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "S'està descarregant..." #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Aboca les dades al SMBIOS des d'un fitxer." #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Durada" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "L'ESP especificat no era vàlid" #. TRANSLATORS: longer description msgid "Each system should have tests to ensure firmware security." msgstr "Cada sistema haurà de tenir proves per a assegurar la seguretat del microprogramari." #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "Emula un dispositiu mitjançant un manifest en JSON" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Emulat" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Amfitrió emulat" msgid "Enable" msgstr "Habilita" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Habilita el suport per a l'actualització del microprogramari sobre sistemes compatibles" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Voleu habilitar el remot nou?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Voleu habilitar aquest remot?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Habilitat" msgid "Enabled fwupdate debugging" msgstr "La depuració del «fwupdate» està habilitada" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Activat si hi ha cap coincidència de maquinari" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Habilita un remot indicat." #. TRANSLATORS: longer description msgid "Enabling firmware updates for the BIOS allows fixing security issues." msgstr "Habilitar actualitzacions de microprogramari per al BIOS permet solucionar problemes de seguretat." msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Si habiliteu aquesta funcionalitat, ho fareu sota el vostre propi risc, el qual significa que haureu de posar-vos en contacte amb el fabricant original de l'equip quant a qualsevol problema causat per aquestes actualitzacions. A $OS_RELEASE:BUG_REPORT_URL$, només s'han de presentar els problemes amb el procés d'actualització." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Si habiliteu aquest remot, ho fareu sota el vostre propi risc." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Encriptada" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "RAM encriptada" msgid "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." msgstr "La RAM encriptada fa impossible que es pugui llegir la informació que s'emmagatzema a la memòria del dispositiu si es treu i s'accedeix al xip de memòria." #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Final de la vida" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Enumeració" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Esborra tot l'historial de les actualitzacions de microprogramari." #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "S'està esborrant..." #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Surt després d'un petit retard" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Surt una vegada s'hagi carregat el motor" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Exporta a XML una estructura de fitxers del microprogramari" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Extreu un blob de microprogramari en imatges." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "FITXER" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "FITXER [ID_DISPOSITIU|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "NOM_FITXER" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "NOM_FITXER CERTIFICAT CLAU_PRIVADA" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "NOM_FITXER NOM_ALT_DISPOSITIU|ID_ALT_DISPOSITIU" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "NOM_FITXER NOM_ALT_DISPOSITIU|ID_ALT_DISPOSITIU [NOM_ALT_IMATGE|ID_ALT_IMATGE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "NOM_FITXER ID_DISPOSITIU" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "NOM_FITXER DESPLAÇAMENT DADES [TIPUS_MICROPROGRAMARI]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "NOM_FITXER [ID_DISPOSITIU|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "NOM_FITXER [TIPUS_MICROPROGRAMARI]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "NOM_FITXER_ORIG NOM_FITXER_DEST [TIPUS_MICROPROGRAMARI_ORIG] [TIPUS_MICROPROGRAMARI_DEST]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "NOM_DE_FITXER|SUMA_DE_VERIFICACIÓ_1[,SUMA_DE_VERIFICACIÓ_2][,SUMA_DE_VERIFICACIÓ_3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Ha fallat" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Ha fallat en aplicar l'actualització" #. TRANSLATORS: error message for Windows msgid "Failed to connect to Windows service, please ensure it's running." msgstr "Ha fallat en connectar amb el servei de Windows, assegureu-vos que s'està executant." #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Ha fallat en connectar amb el dimoni" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Ha fallat en obtenir els dispositius pendents" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Ha fallat en instal·lar l'actualització del microprogramari" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Ha fallat en carregar una dbx local" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "No s'han pogut carregar les peculiaritats" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Ha fallat en carregar una dbx del sistema" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Ha fallat en bloquejar" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Ha fallat en analitzar els arguments" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Ha fallat en analitzar el fitxer" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Ha fallat en analitzar les etiquetes per a --filter" msgid "Failed to parse flags for --filter-release" msgstr "Ha fallat en analitzar els indicadors --filter-release" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Ha fallat en analitzar una dbx local" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Ha fallat en tornar a arrencar" #. TRANSLATORS: a feature is something like "can show an image" msgid "Failed to set front-end features" msgstr "Ha fallat en establir les característiques del frontal" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Ha fallat en establir el mode de presentació" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Ha fallat en validar el contingut ESP" #. TRANSLATORS: item is FALSE msgid "False" msgstr "Fals" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Nom del fitxer" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Signatura del nom del fitxer" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Nom del fitxer d'origen" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "El nom del fitxer és obligatori" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filtra amb un conjunt d'etiquetes del dispositiu amb un prefix «~» per a excloure, p. ex., «internal,~needs-reboot»" #. TRANSLATORS: command line option msgid "Filter with a set of release flags using a ~ prefix to exclude, e.g. 'trusted-release,~trusted-metadata'" msgstr "Filtreu amb un conjunt de marques de llançament utilitzant un prefix ~ per a excloure, p. ex. «trusted-release,~trusted-metadata»" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Certificació del microprogramari" #. TRANSLATORS: longer description msgid "Firmware Attestation checks device software using a reference copy, to make sure that it has not been changed." msgstr "La certificació del microprogramari comprova el programari del dispositiu mitjançant una còpia de referència, per a assegurar-se que no ha estat canviat." #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Descriptor de microprogramari del BIOS" #. TRANSLATORS: longer description msgid "Firmware BIOS Descriptor protects device firmware memory from being tampered with." msgstr "El descriptor de microprogramari del BIOS protegeix que la memòria de microprogramari del dispositiu no es mantingui alterada." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Regió de microprogramari del BIOS" #. TRANSLATORS: longer description msgid "Firmware BIOS Region protects device firmware memory from being tampered with." msgstr "La regió de microprogramari del BIOS protegeix que la memòria de microprogramari del dispositiu no es mantingui alterada." #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "URI base del microprogramari" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Servei de D-Bus per a l'actualització de microprogramari" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Dimoni per a l'actualització de microprogramari" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Verificació de l'actualitzador de microprogramari" msgid "Firmware Updater Verification checks that software used for updating has not been tampered with." msgstr "La verificació de l'actualitzador de microprogramari comprova que el programari emprat per a l'actualització no ha estat alterat." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Actualitzacions de microprogramari" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Utilitat per al microprogramari" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Protecció d'escriptura del microprogramari" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Bloca la protecció d'escriptura del microprogramari" #. TRANSLATORS: longer description msgid "Firmware Write Protection protects device firmware memory from being tampered with." msgstr "La protecció d'escriptura del microprogramari protegeix que la memòria de microprogramari del dispositiu sigui manipulada." #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Certificació del microprogramari" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "El microprogramari ja està bloquejat" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "El microprogramari ja no està bloquejat" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Les metadades del microprogramari no s'han actualitzat durant %u dia i podria ser que no estiguin actualitzades." msgstr[1] "Les metadades del microprogramari no s'han actualitzat durant %u dies i podria ser que no estiguin actualitzades." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Actualitzacions del microprogramari" msgid "Firmware updates are not supported on this machine." msgstr "Les actualitzacions de microprogramari no estan admeses en aquesta màquina." msgid "Firmware updates are supported on this machine." msgstr "Les actualitzacions de microprogramari estan admeses en aquesta màquina." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Actualitzacions de microprogramari inhabilitades: executeu «fwupdmgr unlock» per a habilitar-les" #. TRANSLATORS: command description msgid "Fix a specific host security attribute" msgstr "Corregeix un atribut de seguretat específic de l'amfitrió" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fix reverted successfully" msgstr "Solució revertida amb èxit" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fixed successfully" msgstr "S'ha solucionat correctament" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Etiquetes" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Força l'acció relaxant algunes comprovacions del temps d'execució" msgid "Force the action ignoring all warnings" msgstr "Força l'acció ignorant tots els avisos" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "S'ha trobat" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "S'ha detectat un encriptatge de tot el disc" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "Els secrets d'encriptatge de tot el disc es poden invalidar en actualitzar" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "Plataforma fusionada" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Plataforma fusionada" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "El GUID" msgstr[1] "Els GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "GUID|DEVICE-ID" msgstr "GUID|DISPOSITIU" msgid "Get BIOS settings" msgstr "Obtén les opcions de configuració del BIOS" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Obté totes les etiquetes admeses per «fwupd»." #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Obté tots els dispositius que admeten actualitzacions de microprogramari." #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Obté tots els connectors habilitats registrats amb el sistema." #. TRANSLATORS: command description msgid "Get device report metadata" msgstr "Obtén les metadades de l'informe del dispositiu" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Obté la informació sobre un fitxer de microprogramari." #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Obté els remots configurats." #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Obté els atributs de seguretat de l'amfitrió." #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Obtén la llista del microprogramari aprovat" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Obté la llista del microprogramari bloquejat." #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Obté la llista d'actualitzacions per al maquinari connectat." #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Obté els alliberaments per a un dispositiu." #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Obté els resultats de l'última actualització." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "FITXER-HWIDS" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "El maquinari està esperant a ser endollat" #. TRANSLATORS: the release urgency msgid "High" msgstr "Alta" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Esdeveniments de seguretat a l'amfitrió" #. TRANSLATORS: error message for unsupported feature #, c-format msgid "Host Security ID (HSI) is not supported" msgstr "L'ID de seguretat de l'amfitrió (HSI) no està admès" #. TRANSLATORS: success, so say thank you to the user msgid "Host Security ID attributes uploaded successfully, thanks!" msgstr "Els atributs de l'ID de seguretat de l'amfitrió s'han pujat correctament, gràcies!" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "ID de seguretat de l'amfitrió:" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "INHIBIT-ID" msgstr "ID_INHIBEIX" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "Protecció IOMMU" #. TRANSLATORS: longer description msgid "IOMMU Protection prevents connected devices from accessing unauthorized parts of system memory." msgstr "La protecció IOMMU impedeix que els dispositius connectats accedeixin a parts no autoritzades de la memòria del sistema." #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "Està inhabilitada la protecció IOMMU de dispositius" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "Està habilitada la protecció IOMMU de dispositius" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Està ociós..." #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ignora les comprovacions estrictes de SSL quan es baixin els fitxers" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Ignora les falles de la suma de verificació del microprogramari" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Ignora les falles de discrepància del maquinari del microprogramari" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignora les comprovacions de seguretat de validació" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "S'ignoren les comprovacions estrictes de SSL, per a fer-ho automàticament en el futur, exporteu DISABLE_SSL_STRICT en el vostre entorn" #. TRANSLATORS: the inhibit ID is a short string like dbus-123456 #, c-format msgid "Inhibit ID is %s." msgstr "L'ID d'inhibició és %s." #. TRANSLATORS: command description msgid "Inhibit the system to prevent upgrades" msgstr "Inhibeix el sistema per a evitar actualitzacions" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Durada de la instal·lació" #. TRANSLATORS: command description msgid "Install a firmware file in cabinet format on this hardware" msgstr "Instal·la en aquest maquinari un fitxer de microprogramari en el format Cabinet" #. TRANSLATORS: command description msgid "Install a raw firmware blob on a device" msgstr "Instal·la un blob de microprogramari RAW en un dispositiu" #. TRANSLATORS: command description msgid "Install a specific firmware file on all devices that match" msgstr "Instal·la un fitxer de microprogramari específic en tots els dispositius que coincideixin" #. TRANSLATORS: command description msgid "Install a specific firmware on a device, all possible devices will also be installed once the CAB matches" msgstr "Instal·la un microprogramari específic en un dispositiu, tots els dispositius possibles també s'instal·laran una vegada que coincideixi el CAB" msgid "Install old version of signed system firmware" msgstr "Instal·la la versió antiga del microprogramari signat per al sistema" msgid "Install old version of unsigned system firmware" msgstr "Instal·la la versió antiga del microprogramari sense signar per al sistema" msgid "Install signed device firmware" msgstr "Instal·la microprogramari signat per al dispositiu" msgid "Install signed system firmware" msgstr "Instal·la microprogramari signat per al sistema" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Instal·la primer al dispositiu pare" msgid "Install unsigned device firmware" msgstr "Instal·la microprogramari sense signar per al dispositiu" msgid "Install unsigned system firmware" msgstr "Instal·la microprogramari sense signar per al sistema" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Instal·lació del microprogramari..." #. TRANSLATORS: stay on one firmware version unless the new version is #. explicitly #. * specified msgid "Installing a specific release is explicitly required" msgstr "Es requereix explícitament la instal·lació d'una versió específica" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "S'està instal·lant l'actualització de microprogramari..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "S'està instal·lant a %s…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "La instal·lació d'aquesta actualització també pot anul·lar qualsevol garantia del dispositiu." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Sencer" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "BootGuard d'Intel" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "Protegit amb l'ACM per al BootGuard d'Intel" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Protegit amb l'ACM per al BootGuard d'Intel" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Política d'error del BootGuard d'Intel" msgid "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." msgstr "La política d'error del BootGuard d'Intel assegura que el dispositiu no continuarà l'inici si s'ha alterat el seu programari de dispositiu." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Fusible del BootGuard d'Intel" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Fusible OTP del BootGuard d'Intel" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Arrencada verificada per BootGuard d'Intel" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Política d'error del BootGuard d'Intel" #. TRANSLATORS: longer description msgid "Intel BootGuard prevents unauthorized device software from operating when the device is started." msgstr "El BootGuard d'Intel impedeix que el programari de dispositiu sense autorització funcioni quan s'inicia el dispositiu." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Arrencada verificada per al BootGuard d'Intel" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "CET d'Intel actiu" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "CET d'Intel habilitat" #. TRANSLATORS: longer description msgid "Intel Control-Flow Enforcement Technology detects and prevents certain methods for running malicious software on the device." msgstr "La tecnologia de compliment del flux de control d'Intel detecta i impedeix certs mètodes per a executar programari maliciós en el dispositiu." #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS Mitigation" msgstr "Mitigació GDS d'Intel" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS mitigation" msgstr "Mitigació GDS d'Intel" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Mode de fabricació del motor de gestió d'Intel" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Superposa el motor de gestió d'Intel" #. TRANSLATORS: longer description msgid "Intel Management Engine Override disables checks for device software tampering." msgstr "El fet de superposar el motor de gestió d'Intel desactivarà les comprovacions per a la manipulació de programari del dispositiu." #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Versió del motor de gestió d'Intel" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "SMAP d'Intel" #. TRANSLATORS: longer description msgid "Intel Supervisor Mode Access Prevention ensures critical parts of device memory are not accessed by less secure programs." msgstr "La prevenció de l'accés al mode de supervisor d'Intel assegura que les parts crítiques de la memòria del dispositiu no són accedides per programes menys segurs." #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Dispositiu intern" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "No vàlid" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Arguments no vàlids" #. TRANSLATORS: error message msgid "Invalid arguments, expected GUID" msgstr "Arguments no vàlids, GUID esperat" #. TRANSLATOR: This is the error message for #. * incorrect parameter msgid "Invalid arguments, expected an AppStream ID" msgstr "Arguments no vàlids, s'esperava un ID d'AppStream" #. TRANSLATORS: error message msgid "Invalid arguments, expected at least ARCHIVE FIRMWARE METAINFO" msgstr "Arguments no vàlids, com a mínim s'esperaven ARXIU MICROPROGRAMARI METAINFO" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Està desactualitzada" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Està en el mode carregador d'arrencada" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Està actualitzada" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Problema" msgstr[1] "Problemes" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "CLAU,VALOR" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "El nucli ja no està contaminat" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "El nucli està contaminat" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Està inhabilitat el bloqueig de parts del nucli" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Està habilitat el bloqueig de parts del nucli" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr " Anell de claus" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "UBICACIÓ" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Última modificació" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Manca menys d'un minut" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Llicència" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Bloqueig del nucli Linux" #. TRANSLATORS: longer description msgid "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgstr "El mode de bloqueig del nucli Linux evita que els comptes d'administrador (root) accedir i canviar a parts crítiques del programari del sistema." msgid "Linux Kernel Swap temporarily saves information to disk as you work. If the information is not protected, it could be accessed by someone if they obtained the disk." msgstr "L'intercanvi del nucli de Linux desa temporalment informació en el disc mentre treballeu. Si la informació no està protegida, algú podria accedir-hi si obté el disc." #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Verificació del nucli Linux" msgid "Linux Kernel Verification makes sure that critical system software has not been tampered with. Using device drivers which are not provided with the system can prevent this security feature from working correctly." msgstr "La verificació del nucli de Linux s'assegura que el programari crític del sistema no ha estat alterat. L'ús de controladors de dispositiu que no es proporcionen amb el sistema pot evitar que aquesta característica de seguretat funcioni correctament." #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Intercanvi de Linux" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Servei de microprogramari del proveïdor Linux (microprogramari estable)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Servei de microprogramari del proveïdor Linux (microprogramari en proves)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Nucli Linux" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Bloqueig del nucli Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Intercanvi de Linux" #. TRANSLATORS: command description msgid "List EFI variables with a specific GUID" msgstr "Llista les variables EFI amb un GUID específic" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Llista les entrades en la dbx." #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Llista les actualitzacions de microprogramari compatibles" #. TRANSLATORS: command description msgid "List the available firmware GTypes" msgstr "Llista els GTipus de microprogramari disponibles" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Llista els tipus de microprogramari disponibles." #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Llista els fitxers en l'ESP." #. TRANSLATORS: command description msgid "Load device emulation data" msgstr "Carrega les dades d'emulació del dispositiu" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "Carregat des d'un mòdul extern" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "S'està carregant..." #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Blocada" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Baixa" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "Manifest de la clau MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "Manifest de la clau MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "Mode de fabricació MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "Superposa el MEI" msgid "MEI version" msgstr "Versió del MEI" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Habilita manualment connectors específics" #. TRANSLATORS: longer description msgid "Manufacturing Mode is used when the device is manufactured and security features are not yet enabled." msgstr "El mode de fabricació s'empra quan el dispositiu s'està fabricant i les característiques de seguretat encara no estan habilitades." #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Longitud màxima" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Valor màxim" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Mitjana" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Signatura de les metadades" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI de les metadades" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Les metadades es poden obtenir des del servei de microprogramari del proveïdor Linux." #. TRANSLATORS: error message for a user who ran fwupdmgr #. * refresh recently msgid "Metadata is up to date; use --force to refresh again." msgstr "Les metadades estan actualitzades, useu --force per a actualitzar-les de nou." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Versió mínima" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Longitud mínima" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Valor mínim" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "El dimoni i el client no coincideixin, useu %s en el seu lloc" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Modifica un valor de la configuració del dimoni" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifica un remot indicat." msgid "Modify a configured remote" msgstr "Modifica un remot configurat" msgid "Modify daemon configuration" msgstr "Modifica la configuració del dimoni" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Monitora el dimoni per als esdeveniments." #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Munta l'ESP." #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Requereix un reinici després de la instal·lació" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Cal reiniciar" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Requereix aturar després de la instal·lació" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Versió nova" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "No s'ha especificat cap acció!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "No hi ha cap desactualització per a %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "No s'ha trobat cap ID de microprogramari" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "No s'ha trobat cap microprogramari" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "No s'ha detectat cap maquinari amb capacitat per a l'actualització del microprogramari" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "No s'ha trobat cap connector" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "No hi ha cap llançament disponible" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Actualment, no hi ha cap remot habilitat, de manera que no hi ha metadades disponibles." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "No hi ha cap remot disponible" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Dispositius no actualitzables" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "No hi ha cap actualització disponible" msgid "No updates available for remaining devices" msgstr "No hi ha cap actualització disponible per als dispositius restants" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "No s'han aplicat les actualitzacions" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "No aprovada" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "No s'ha trobat" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "No admesa" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "D'acord" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "Bé!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Versió antiga" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Mostra només un únic valor de PCR" #. TRANSLATORS: command line option msgid "Only use peer-to-peer networking when downloading files" msgstr "Usa només la xarxa d'igual a igual per a baixar els fitxers" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Només es permeten actualitzacions de la versió" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Sortida en el format JSON" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Preferència sobre el camí ESP predeterminat" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Firmware" msgstr "Microprogramari des de P2P" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Metadata" msgstr "Metadades des de P2P" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "CAMÍ" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Analitza i mostra els detalls sobre un fitxer de microprogramari." #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "S'està analitzant l'actualització de la dbx..." #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "S'està analitzant la dbx del sistema..." #. TRANSLATORS: remote filename base msgid "Password" msgstr "Contrasenya" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Apedaça un blob de microprogramari amb un desplaçament conegut" msgid "Payload" msgstr "Part útil" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Pendent" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Percentatge completat" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Voleu efectuar l'operació?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Depuració de la plataforma" #. TRANSLATORS: longer description msgid "Platform Debugging allows device security features to be disabled. This should only be used by hardware manufacturers." msgstr "La depuració de la plataforma permet que es desactivin les característiques de seguretat del dispositiu. Això només ho han d'emprar els fabricants de maquinari." #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Depuració de la plataforma" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Abans de continuar, assegureu-vos que teniu la clau de recuperació del volum." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Introduïu un número del 0 al %u: " #. TRANSLATORS: the user isn't reading the question -- please don't translate #. * 'Y' or 'N' as these are hardcoded msgid "Please enter either Y or N: " msgstr "Introduïu S o N:" #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Manquen les dependències del connector" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Valors possibles" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "Protecció DMA durant la prearrencada" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Protecció DMA durant la prearrencada" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "La protecció DMA durant la prearrencada està desactivada" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "La protecció DMA durant la prearrencada està activada" #. TRANSLATORS: longer description msgid "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." msgstr "La protecció de DMA en la prearrencada impedeix que els dispositius accedeixin a la memòria del sistema després de connectar-los amb l'ordinador." #. TRANSLATORS: warning message msgid "Press unlock on the device to continue the update process." msgstr "Premeu desbloqueig en el dispositiu per a continuar amb el procés d'actualització." #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Versió anterior" msgid "Print the version number" msgstr "Imprimeix el número de versió" msgid "Print verbose debug statements" msgstr "Imprimeix les sentències detallades de la depuració" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritat" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Problemes" msgid "Proceed with upload?" msgstr "Voleu continuar amb l'enviament?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Comprovacions de seguretat del processador" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Protecció contra la reversió del processador" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Propietari" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Consulta el suport per a l'actualització del microprogramari" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "ID_REMOT" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "ID_REMOT CLAU VALOR" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Només lectura" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Llegeix un blob de microprogramari des d'un dispositiu." #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Llegeix un nicroprogramari des d'un dispositiu" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Llegeix el microprogramari des del dispositiu a un fitxer." #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Llegeix el microprogramari des d'una partició a un fitxer." #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Llegint des de %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "S'està llegint..." #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "S'està tornant a arrencar..." #. TRANSLATORS: how often we should refresh the metadata msgid "Refresh Interval" msgstr "Interval de refresc" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Refresca les metadades des del servidor remot." #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Voleu reinstal·lar %s a %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Torna a instal·lar el microprogramari actual en el dispositiu." #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Torna a instal·lar el microprogramari a un dispositiu." #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "S'està reinstal·lant %s amb %s... " #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Branca de llançament" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Etiquetes de llançament" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "ID de llançament" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID remot" #. TRANSLATORS: command description msgid "Removes devices to watch for future emulation" msgstr "Elimina els dispositius que es vigilaran per a una emulació futura" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Substitueix les dades en un fitxer de microprogramari existent." #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI de l'informe" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Informat al servidor remot" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Sol·licitud cancel·lada" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "No s'ha trobat el sistema de fitxers «efivarfs» requerit" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "No s'ha trobat el maquinari requerit" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Requereix un carregador d'arrencada" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Requereix connexió a Internet" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Voleu reiniciar ara?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Voleu reiniciar el dimoni per a fer efectiu el canvi?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "S'està reiniciant el dispositiu..." #. TRANSLATORS: command description msgid "Retrieve BIOS settings. If no arguments are passed all settings are returned" msgstr "Es recuperen les opcions de configuració del BIOS. Si no es transmeten arguments, es retornarà tota la configuració" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Retorna tots els ID del maquinari de la màquina." #. TRANSLATORS: ask the user to upload msgid "Review and upload report now?" msgstr "Voleu revisar i penjar ara l'informe?" #. TRANSLATORS: longer description msgid "Rollback Protection prevents device software from being downgraded to an older version that has security problems." msgstr "La protecció contra la reversió evita que el programari del dispositiu torni a una versió anterior que potser conté problemes de seguretat." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Per a més informació, executeu «fwupdmgr get-upgrades»." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr sync` to complete this action." msgstr "Executeu «fwupdmgr sync» per a completar aquesta acció." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Executa la rutina de neteja de la composició del connector en usar install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Executa la rutina de preparació de la composició del connector en usar install-blob" #. TRANSLATORS: command description msgid "Run the post-reboot cleanup action" msgstr "Executa l'acció de neteja posterior al reinici" #. TRANSLATORS: tell a user how to get information #, c-format msgid "Run without '%s' to see" msgstr "Per a veure executa sense «%s»" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "El nucli executat és massa antic" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Sufix d'execució" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "VALOR DE CONFIGURACIÓ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING1 VALUE1 [SETTING2] [VALUE2]" msgstr "CONFIG_1 VALOR_1 [CONFIG_2] [VALOR_2]" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "Descriptor del BIOS per a l'SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "Regió del BIOS per a l'SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "Bloca l'SPI" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "Protecció de repetició per a l'SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "Escriu l'SPI" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "Protecció d'escriptura per a l'SPI" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "SUBSISTEMA CONTROLADOR [ID_DISPOSITIU|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Desa un fitxer que permet la generació dels ID de maquinari" #. TRANSLATORS: command description msgid "Save device emulation data" msgstr "Desa les dades d'emulació del dispositiu" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "Augment escalar" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Planifica la instal·lació per al següent reinici quan sigui possible" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Planificació..." #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Està inhabilitada l'arrencada segura" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Està habilitada l'arrencada segura" msgid "Security hardening for HSI" msgstr "Reforç de la seguretat per a HSI" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Per a més detalls, vegeu %s." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Per a més informació, vegeu %s." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Dispositiu seleccionat" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Volum seleccionat" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Número de sèrie" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "Estableix la configuració «%s» del BIOS a «%s»." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "Estableix una configuració del BIOS" msgid "Set one or more BIOS settings" msgstr "Estableix una o més opcions de configuració del BIOS" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Estableix l'indicador de depuració durant l'actualització" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "Estableix una o més opcions de configuració del BIOS" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Estableix la llista de microprogramari aprovat" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Tipus de configuració" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "La configuració s'aplicarà després de reiniciar el sistema" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Comparteix l'historial de microprogramari amb els desenvolupadors." #. TRANSLATORS: command line option msgid "Show all results" msgstr "Mostra tots els resultats" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Mostra les versions del client i el dimoni" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Mostra informació detallada del dimoni per a un domini concret" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Mostra informació de depuració per a tots els dominis" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Mostra les opcions per a la depuració" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Mostra els dispositius que no són actualitzables" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Mostra informació de depuració addicional." #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Mostra l'historial de les actualitzacions de microprogramari." #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Mostra la versió calculada de la dbx." #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Mostra el registre de depuració del darrer intent d'actualització" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Mostra la informació sobre l'estat de l'actualització del microprogramari" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Voleu aturar-lo ara?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Signa un microprogramari amb una clau nova" msgid "Sign data using the client certificate" msgstr "Signa les dades emprant el certificat del client" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Signa les dades emprant el certificat del client" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Signa les dades enviades amb el certificat del client" msgid "Signature" msgstr "Signatura" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Part útil signada" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Mida" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Alguns dels secrets de la plataforma es poden invalidar en actualitzar aquest microprogramari." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Origen" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Especifiqueu el proveïdor/ID del producte del dispositiu DFU" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Especifica el fitxer de base de dades dbx." msgid "Specify the number of bytes per USB transfer" msgstr "Especifiqueu el nombre de bytes per a la transferència USB" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "Cadena" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Amb èxit" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "S'han activat correctament tots els dispositius" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "El remot s'ha inhabilitat correctament" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "S'han baixat les metadades noves amb èxit:" #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "S'ha habilitat i refrescat el remot amb èxit" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "El remot s'ha habilitat correctament" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "El microprogramari s'ha instal·lat correctament" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "S'ha modificat amb èxit el valor de la configuració" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "El remot ha estat modificat amb èxit" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "S'han refrescat manualment amb èxit les metadades" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "S'han actualitzat correctament les sumes de verificació del dispositiu" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "S'ha enviat %u informe correctament" msgstr[1] "S'han enviat %u informes correctament" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "S'han verificat correctament les sumes de verificació del dispositiu" #. TRANSLATORS: the device showed up in time #, c-format msgid "Successfully waited %.0fms for device" msgstr "S'han esperat amb èxit %.0f per al dispositiu" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Resum" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Admesa" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "CPU admesa" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Admès en el servidor remot" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Suspèn a inactiu" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Suspèn a la RAM" #. TRANSLATORS: longer description msgid "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Suspèn a inactiu permet que el dispositiu s'adormi ràpidament per a estalviar energia. Si bé el dispositiu ha estat suspès, es podria treure físicament la memòria i accedir a la informació." #. TRANSLATORS: longer description msgid "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Suspèn a la RAM permet que el dispositiu s'adormi ràpidament per a estalviar energia. Si bé el dispositiu ha estat suspès, es podria treure físicament la memòria i accedir a la informació." #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Suspensió a inactiu" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Suspensió a la RAM" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Voleu canviar la branca des de %s a %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Canvia la branca de microprogramari en el dispositiu." #. TRANSLATORS: command description msgid "Sync firmware versions to the chosen configuration" msgstr "Sincronitza les versions del microprogramari amb la configuració triada" #. TRANSLATORS: this CLI tool is now preventing system updates msgid "System Update Inhibited" msgstr "S'ha inhibit l'actualització del sistema" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "L'energia del sistema és massa baixa per a realitzar l'actualització" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr " L'energia del sistema és massa baixa per a realitzar l'actualització (%u%%, requereix %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "El sistema requereix una font d'alimentació externa" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEXT" #. TRANSLATORS: longer description msgid "TPM (Trusted Platform Module) is a computer chip that detects when hardware components have been tampered with." msgstr "El TPM (mòdul de plataforma de confiança) és un xip d'ordinador que detecta quan s'han manipulat els components de maquinari." #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "Reconstrucció PCR0 del TPM" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "La reconstrucció PCR0 del TPM no és vàlida" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "La reconstrucció PCR0 del TPM ara és vàlida" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "Configuració de la plataforma TPM" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "Reconstrucció TPM" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "Els PCR buits en el TPM" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM versió 2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Etiqueta" msgstr[1] "Etiquetes" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "Etiquetat per a l'emulació" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Contaminada" msgid "Target" msgstr "Objectiu" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Prova un dispositiu emprant un manifest JSON" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Provada" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "Provada per %s" #. TRANSLATORS: someone we trust has tested this msgid "Tested by trusted vendor" msgstr "Provat per un venedor de confiança" #. TRANSLATORS: longer description msgid "The Intel Management Engine Key Manifest must be valid so that the device firmware can be trusted by the CPU." msgstr "El manifest de la clau del motor de gestió d'Intel ha de ser vàlid perquè la CPU pugui confiar en el microprogramari del dispositiu." #. TRANSLATORS: longer description msgid "The Intel Management Engine controls device components and needs to have a recent version to avoid security issues." msgstr "El motor de gestió d'Intel controla els components del dispositiu i necessita tenir una versió recent per a evitar problemes de seguretat." #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "El LVFS és un servei gratuït que funciona com una entitat legal independent i no té cap vincle amb la $OS_RELEASE:NAME$. És possible que el vostre distribuïdor no hagi verificat cap de les actualitzacions del microprogramari per a la compatibilitat amb el vostre sistema o els dispositius connectats. Tot el microprogramari només és proporcionat pel fabricant original dels equips." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Platform Configuration is used to check whether the device start process has been modified." msgstr "La configuració de la plataforma TPM (mòdul de plataforma de confiança) s'empra per a comprovar si s'ha modificat el procés d'inici del dispositiu." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Reconstruction is used to check whether the device start process has been modified." msgstr "La reconstrucció del TPM (mòdul de plataforma de confiança) s'empra per a comprovar si s'ha modificat el procés d'inici del dispositiu." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "El PCR0 del TPM difereix de la reconstrucció." #. TRANSLATORS: longer description msgid "The UEFI Platform Key is used to determine if device software comes from a trusted source." msgstr "La clau de la plataforma UEFI s'empra per a determinar si el programari del dispositiu prové d'una font de confiança." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "El dimoni ha carregat codi de tercers i ja no és admès pels desenvolupadors originals!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "La versió del dispositiu no coincideix: obtinguda %s, esperada %s" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "El microprogramari des de %s no és proporcionat per %s, el proveïdor de maquinari." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "El rellotge del sistema no s'ha configurat correctament i pot fallar la descàrrega de fitxers." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been re-inserted." msgstr "L'actualització continuarà quan s'hagi tornat a endollar el cable USB del dispositiu." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged and then re-inserted." msgstr "L'actualització continuarà quan el cable USB del dispositiu s'hagi desconnectat i es torni a inserir." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged." msgstr "L'actualització continuarà quan s'hagi desconnectat el cable USB del dispositiu." #. TRANSLATORS: warning message msgid "The update will continue when the device power cable has been removed and re-inserted." msgstr "L'actualització continuarà quan s'hagi retirat i tornat a inserir el cable d'alimentació del dispositiu." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "El proveïdor no ha subministrat les notes de llançament." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Hi ha dispositius amb problemes:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "No hi ha cap fitxer de microprogramari bloquejat." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "No hi ha cap microprogramari aprovat." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "Aquest dispositiu es revertirà a %s quan es realitzi l'ordre %s." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "Aquest microprogramari és proporcionat pels membres de la comunitat LVFS i no és proporcionada (o admesa) pel proveïdor de maquinari original." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Aquest paquet no ha estat validat, i per això podria no funcionar adequadament." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Aquest programa només pot funcionar correctament com a «root»" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Aquest remot conté microprogramari que no està embargat, però encara l'ha de provar el proveïdor del maquinari. Haureu d'assegurar-vos que teniu una manera de desactualitzar manualment el microprogramari si l'actualització del microprogramari no funciona." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "Aquest sistema no admet la configuració del microprogramari" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Aquest sistema té problemes d'execució HSI." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Aquest sistema té un nivell baix de seguretat HSI." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Aquesta eina permet a un administrador aplicar les actualitzacions des de la dbx UEFI." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Aquesta eina permet a un administrador depurar el funcionament d'«UpdateCapsule»." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Aquesta eina permet que un administrador consulti i controli el dimoni «fwupd», el qual li permetrà realitzar accions com instal·lar o degradar la versió de microprogramari." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Aquesta eina permet a un administrador usar els connectors de «fwupd» sense estar instal·lats en el sistema amfitrió." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can add a kernel argument of '%s', but it will only be active after restarting the computer." msgstr "Aquesta eina pot afegir un argument del nucli de «%s», però només estarà actiu després de reiniciar l'ordinador." #. TRANSLATORS: the %1 is a BIOS setting name. #. * %2 and %3 are the values, e.g. "True" or "Windows10" #, c-format msgid "This tool can change the BIOS setting '%s' from '%s' to '%s' automatically, but it will only be active after restarting the computer." msgstr "Aquesta eina pot canviar la configuració del BIOS «%s» des de «%s» a «%s» de forma automàtica, però només estarà activa una vegada es reiniciï l'ordinador." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can change the kernel argument from '%s' to '%s', but it will only be active after restarting the computer." msgstr "Aquesta eina pot canviar l'argument del nucli des de «%s» a «%s», però només estarà actiu després de reiniciar l'ordinador." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Aquesta eina només pot ser usada per l'usuari «root»." #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Aquesta eina llegirà i analitzarà el registre d'esdeveniments TPM des del microprogramari del sistema." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Fallada transitòria" #. TRANSLATORS: item is TRUE msgid "True" msgstr "Cert" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Metadades de confiança" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Part útil de confiança" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipus" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI Bootservice Variables" msgstr "Variables del servei d'arrencada UEFI" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition may not be set up correctly" msgstr "És possible que la partició ESP de la UEFI no estigui configurada correctament" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "No s'ha detectat o configurat la partició ESP de la UEFI" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Utilitat per al microprogramari UEFI" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "Clau de la plataforma UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "Arrencada segura UEFI" #. TRANSLATORS: longer description msgid "UEFI Secure Boot prevents malicious software from being loaded when the device starts." msgstr "L'arrencada segura UEFI evita que es carregui programari maliciós quan s'inicia el dispositiu." #. TRANSLATORS: longer description msgid "UEFI boot service variables should not be readable from runtime mode." msgstr "Les variables del servei d'arrencada UEFI no s'haurien de poder llegir des del mode de temps d'execució." #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI bootservice variables" msgstr "Variables del servei d'arrencada UEFI" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "Les actualitzacions de la càpsula UEFI no estan disponibles o habilitades a la configuració del microprogramari" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "Utilitat dbx UEFI" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "El microprogramari UEFI no es pot actualitzar en el mode BIOS heretat" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "Clau de la plataforma UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "Arrancada segura de la UEFI" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "No s'ha pogut connectar amb el servei" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "No es pot trobar atribut" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Desvincula el controlador actual." #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Microprogramari desbloquejat:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Desbloqueja la instal·lació d'un microprogramari específic." #. TRANSLATORS: command description msgid "Undo the host security attribute fix" msgstr "Desfés la correcció de l'atribut de seguretat de l'amfitrió" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "No està encriptada" #. TRANSLATORS: command description msgid "Uninhibit the system to allow upgrades" msgstr "Desinhibeix el sistema per a permetre actualitzacions" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Desconegut" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Dispositiu desconegut" msgid "Unlock the device to allow access" msgstr "Desbloqueja el dispositiu per a permetre l'accés" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Desblocada" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Desbloqueja el dispositiu per a accedir al microprogramari." #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Desmunta l'ESP." #. TRANSLATORS: message shown after device has been marked for emulation msgid "Unplug and replug the device to continue the update process." msgstr "Desendolleu i torneu a endollar el dispositiu per a continuar amb el procés d'actualització." #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "No estableixis l'indicador de depuració durant l'actualització" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Part útil sense signar" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Versió %s no admesa del dimoni, la versió del client és %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Sense contaminar" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Actualitzable" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Error en actualitzar" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Imatge d'actualització" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Missatge de l'actualització" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Estat en actualitzar" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Que falli en actualitzar és un problema conegut, visiteu aquest URL per a obtenir més informació:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Voleu actualitzar ara?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "L'actualització requereix un reinici" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Actualitza la suma criptogràfica emmagatzemada amb el contingut actual de la ROM." msgid "Update the stored device verification information" msgstr "Actualitza la informació de verificació dels dispositius emmagatzemats" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Actualitza les metadades emmagatzemades amb el contingut actual." #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "Actualitza tots els dispositius especificats a la versió més recent del microprogramari o tots els dispositius sense especificar" msgid "Updating" msgstr "S'està actualitzant" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "S'està actualitzant %s des de %s a %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "S'està actualitzant %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Voleu actualitzar %s des de %s a %s?" #. TRANSLATORS: ask the user to share, %s is something #. * like: "Linux Vendor Firmware Service" #, c-format msgid "Upload these anonymous results to the %s to help other users?" msgstr "Voleu pujar aquests resultats anònims al %s per a ajudar als altres usuaris?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "L'enviament dels informes de microprogramari ajudarà als proveïdors de maquinari a identificar amb rapidesa les actualitzacions fallides i satisfactòries sobre els dispositius reals." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Urgència" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Useu %s per a obtenir ajuda" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Useu CTRL^C per a cancel·lar." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Empreu «fwupdtool --help» per a l'ajuda" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Usa les etiquetes peculiars en instal·lar el microprogramari" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "S'ha notificat a l'usuari" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Nom d'usuari" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Vàlid" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "S'està validant el contingut ESP..." #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variant" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Venedor" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "S'està verificant..." #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Versió" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Versió[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "AVÍS" #. TRANSLATORS: command description msgid "Wait for a device to appear" msgstr "Espereu fins que aparegui un dispositiu" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "S'està esperant…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Mira per a canvis al maquinari." #. TRANSLATORS: check various UEFI and ACPI tables are unchanged after the #. update msgid "Will measure elements of system integrity around an update" msgstr "Mesurarà els elements d'integritat del sistema al voltant d'una actualització" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Escriu el microprogramari des d'un fitxer a dins del dispositiu." #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Escriu el microprogramari des d'un fitxer a dins d'una partició." #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Fitxer d'escriptura:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "S'està escrivint..." #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from a recovery or installation disk, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Hauríeu d'assegurar-vos que us sentiu còmode restaurant la configuració des d'un disc de recuperació o instal·lació, ja que aquest canvi pot provocar que el sistema no arrenqui a Linux o provocar una altra inestabilitat del sistema." #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from the system firmware setup, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Heu d'assegurar-vos que esteu còmode restablint la configuració des de la configuració del microprogramari del sistema, ja que aquest canvi pot fer que el sistema no arrenqui a Linux o que causi alguna altra inestabilitat en el sistema." #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "És possible que el vostre distribuïdor no hagi verificat cap de les actualitzacions del microprogramari per a la compatibilitat amb el vostre sistema o els dispositius connectats." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "El vostre maquinari pot danyar-se amb aquest microprogramari, i la instal·lació d'aquesta versió pot anul·lar qualsevol garantia amb %s." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "El vostre sistema està establert a la BKC de %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[APPSTREAM_ID]" msgstr "[ID_APPSTREAM]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[SUMA_DE_VERIFICACIÓ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[ID_DISPOSITIU|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[ID_DISPOSITIU|GUID] [BRANCA]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[ID_DISPOSITIU|GUID] [VERSIÓ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE]" msgstr "[DISPOSITIU]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[FITXER SIGNATURA_FITXER ID_REMOT]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[NOM_FITXER_1] [NOM_FITXER_2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[RAÓ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[CONFIG_1] [ CONFIG_2]..." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[CONFIG_1] [CONFIG_2] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[FITXER_SMBIOS|FITXER_HWIDS]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "predeterminat" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "Utilitat per al registre d'esdeveniments TPM del fwupd" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "Connectors del «fwupd»" fwupd-1.9.16/po/cs.po000066400000000000000000003716541460375044200143450ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Ascii Wolf , 2017,2019-2020,2022 # Ascii Wolf , 2017 # Marek Černocký , 2016,2018 # Pavel Borecki , 2020-2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Czech (http://app.transifex.com/freedesktop/fwupd/language/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: cs\n" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "Zbývá %.0f minuta" msgstr[1] "Zbývají %.0f minuty" msgstr[2] "Zbývá %.0f minut" msgstr[3] "Zbývají %.0f minuty" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "Aktualizace pro zařízení pro vestavěnou zprávu (BMC) %s" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "Aktualizace pro akumulátor %s" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "Aktualizace mikrokódu pro procesor %s" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "Aktualizace pro kameru %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "Aktualizace nastavení pro %s" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Aktualizace spotřebitelského ME v %s" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Aktualizace pro řadič %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Aktualizace podnikového ME v %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Aktualizace pro zařízení %s" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "Aktualizace pro displej %s" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "Aktualizace doku %s" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "%s aktualizace disku" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Aktualizace vestavěného řadiče (EC) v %s" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "Aktualizace čtečky otisků prstů %s" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "%s aktualizace flash disku" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "%s aktualizace GPU" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "Aktualizace grafického tabletu %s" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "Aktualizace pro klávesnici %s" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "Aktualizace ME v %s" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "Aktualizace pro myš %s" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "Aktualizace síťové karty %s" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "%s aktualizace SSD" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "Aktualizace pro %s řadič úložiště" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Celková aktualizace pro %s" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "Aktualizace pro TPM %s" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Aktualizace pro řadič Thunderbolt v %s" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "Aktualizace pro touchpad %s" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "Aktualizace USB doku %s" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "Aktualizace USB přijímače %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Aktualizace pro %s" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s a k němu připojená zařízení nemusí být v průběhu aktualizace použitelná." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "%s se objevilo: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "%s se změnilo: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "%s zmizelo: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s v tuto chvíli není možné aktualizovat" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "režim pro výrobce v %s" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "Je třeba, aby %s zůstalo v průběhu aktualizace připojené, jinak hrozí poškození." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "Je třeba, aby %s bylo po dobu aktualizace připojené ke zdroji napájení z elektrické sítě, jinak hrozí poškození." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "hw přepnutí %s" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s verze %s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "Verze %s" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u den" msgstr[1] "%u dny" msgstr[2] "%u dnů" msgstr[3] "%u dny" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "Je k dispozici novější firmware pro %u zařízení." msgstr[1] "Jsou k dispozici novější firmware pro %u zařízení." msgstr[2] "Jsou k dispozici novější firmware pro %u zařízení." msgstr[3] "Jsou k dispozici novější firmware pro %u zařízení." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "Firmware na %u zařízení není na verzi, která by byla osvědčená v kombinaci s verzemi firmwarů ostatních zařízení." msgstr[1] "Firmware na %u zařízeních není na verzi, která by byla osvědčená v kombinaci s verzemi firmwarů ostatních zařízení." msgstr[2] "Firmware na %u zařízeních není na verzi, která by byla osvědčená v kombinaci s verzemi firmwarů ostatních zařízení." msgstr[3] "Firmware na %u zařízeních není na verzi, která by byla osvědčená v kombinaci s verzemi firmwarů ostatních zařízení." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u hodina" msgstr[1] "%u hodiny" msgstr[2] "%u hodin" msgstr[3] "%u hodiny" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u zařízení v počítači podporováno" msgstr[1] "%u zařízení v počítači podporovány" msgstr[2] "%u zařízení v počítači podporováno" msgstr[3] "%u zařízení v počítači podporovány" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuta" msgstr[1] "%u minuty" msgstr[2] "%u minut" msgstr[3] "%u minuty" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u sekunda" msgstr[1] "%u sekundy" msgstr[2] "%u sekund" msgstr[3] "%u sekundy" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (dolní hranice %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(zastaralé)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "PCR registr v TPM má nyní neplatnou hodnotu" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "AMD – zamezení znovupoužití dříve použitého" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "AMD – zamezení zápisu firmware" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "AMD zamezení instalace starší verze na procesoru" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "ARCHIVE FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATFILE]" msgstr "ARCHIV FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATSOUBOR]" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Vyžadovaná akce:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Aktivovat zařízení" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Aktivovat čekající zařízení" msgid "Activate the new firmware on the device" msgstr "Aktivovat nový firmware na zařízení" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Aktivace aktualizovaného firmwaru" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Aktivuje se aktualizace firmware pro" #. TRANSLATORS: command description msgid "Adds devices to watch for future emulation" msgstr "Přidá zařízení pro hlídání budoucí emulace" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Stáří" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Odsouhlasit a povolit vzdálený zdroj?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alternativa (alias) pro %s" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "Všechny PCR registry jsou nyní platné" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Všechny PCR registry jsou platné" #. TRANSLATORS: an application is preventing system updates msgid "All devices are prevented from update by system inhibit" msgstr "Aktualizaci veškerých zařízení je bráněno systémovým inhibitorem" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Veškerá zařízení stejného typu budou aktualizována naráz" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Povolit návrat ke starší verzi firmwaru" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Povolit přeinstalaci stávající verze firmwaru" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Umožnit přepnutí varianty firmware" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Alternativní větev" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "Probíhá aktualizace" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Některá z aktualizací vyžaduje pro dokončení restart počítače." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Některá z aktualizací vyžaduje pro dokončení vypnutí počítače." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Na všechny dotazy odpovědět ano" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Provést aktualizace firmwaru" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Provést aktualizaci i když to není doporučeno" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Použít soubory s aktualizací" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Provádění aktualizace…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Schválený firmware:" msgstr[1] "Schválené firmwary:" msgstr[2] "Schválených firmwarů:" msgstr[3] "Schválené firmwary:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Ptát se i příště?" #. TRANSLATORS: command description msgid "Asks the daemon to quit" msgstr "Požádá proces služby aby se ukončila" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Napojit do režimu firmwaru" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Ověřování se…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Je zapotřebí podrobností k ověření se" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "K návratu na starší verzi firmwaru na výměnném zařízení je vyžadováno ověření se" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "K návratu na starší verzi firmwaru na tomto počítači je vyžadováno ověření se" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to fix a host security issue" msgstr "Pro opravu problému se zabezpečením stroje je třeba se ověřit" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Pro změnu BIOS nastavení je třeba se ověřit" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Ke změně nastaveného vzdáleného zdroje, který se používá pro aktualizace firmwarů, je vyžadováno ověření se" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Ke změně nastavení procesu služby je vyžadováno ověření se" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "Pro načtení nastavení BIOS je třeba se ověřit" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Pro nastavení seznamu schválených firmwarů je vyžadováno ověření se" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Pro podepsání dat klientským certifikátem je vyžadováno ověření se" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Pro přepnutí na novou verzi firmwaru je vyžadováno ověření se" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to undo the fix for a host security issue" msgstr "Pro vrácení do stavu před opravou problému se zabezpečením stroje je třeba se ověřit" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Pro odemknutí zařízení je požadováno ověření se" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "K aktualizaci firmwaru na výměnném zařízení je vyžadováno ověření se" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "K aktualizaci firmwaru na tomto počítači je vyžadováno ověření se" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "K aktualizaci uložených kontrolních součtů pro zařízení je vyžadováno ověření se" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Automatické odesílání hlášení" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Pokaždé automaticky odeslat?" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS Firmware Updates" msgstr "Aktualizace BIOS firmwaru" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "Zamezení instalace starší verze BIOS" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS firmware updates" msgstr "Aktualizace BIOS firmwaru" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "Zamezení instalace starší verze BIOS" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "Aktualizace BIOS doručené prostřednictvím LVFS nebo Windows Update" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "XML-PRO-SESTAVENÍ CÍLOVÝ-SOUBOR" msgid "BYTES" msgstr "BAJTŮ" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Akumulátor/baterie" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Napojit nový ovladač jádra" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Blokované soubory s firmware:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Blokovaná verze" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Blokuje se firmware:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Zablokovat instalaci konkrétního firmwaru" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Verze zavaděče firmware" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Větev" #. TRANSLATORS: command description msgid "Build a cabinet archive from a firmware blob and XML metadata" msgstr "Sestavit cab archiv z binárního souboru s firmwarem a XML metadat" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Sestavit soubor s firmware" #. TRANSLATORS: longer description msgid "CPU Microcode must be updated to mitigate against various information-disclosure security issues." msgstr "Aby bylo možné zmírnit dopad různých problémů s úniky různých informací z procesoru, je třeba zaktualizovat jeho mikrokód" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Storno" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Zrušeno" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Tuto aktualizaci dbx už není možné použít, protože už je používána." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Aktualizace není možné provést při provozování systému z instalačního média (live)" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Změněno" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Zkontrolovat zda se kryptografický otisk shoduje s firmware" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Kontrolní součet" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Vyberte variantu" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Vyberte zařízení" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Vyberte firmware" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Vyberte vydán" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Zvolte ESP:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Vyberte svazek" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Smazat výsledky z poslední aktualizace" #. TRANSLATORS: error message msgid "Command not found" msgstr "Příkaz nenalezen" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Podporováno komunitou" #. TRANSLATORS: title prefix for the BIOS settings dialog msgid "Configuration Change Suggested" msgstr "Doporučena změna nastavení" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "Nastavení je čitelné pouze pro správce systému" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Převést soubor s firmware" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Vytvořeno" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Kritická" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Je k dispozici kryptografické ověření otisku dat" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Stávající hodnota" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Stávající verze" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "IDENTIF-ZAŘÍZENÍ|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Nástroj pro práci s DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Předvolby ladění" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Rozbalování…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Popis" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Odpojit do režimu zavaděče" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Podrobnosti" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "Odchýlit se od osvědčené kombinace verzí firmwarů všech zařízení?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Příznaky zařízení" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Identifikátor zařízení" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Přidáno zařízení:" #. TRANSLATORS: the device is already connected msgid "Device already exists" msgstr "Zařízení už existuje" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "Akumulátor/baterie v zařízení je příliš vybitý" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "Akumulátor/baterie v zařízení je příliš vybitý (%u%%, vyžaduje %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Zařízení se dovede vzpamatovat z nezdaru při zápisu firmware" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "Zařízení není možné používat, pokud je víko s displejem přiklopené" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Změněno zařízení:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "Emulace zařízení není zapnutá." #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Je třeba, aby firmware zařízení měl kontrolu verze" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "Zařízení je emulováno" #. TRANSLATORS: device cannot be interrupted, for instance taking a phone call msgid "Device is in use" msgstr "Zařízení je používáno" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Zařízení je uzamčeno" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Zařízení vyžaduje instalaci všech poskytnutých vydání" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Zařízení není dosažitelné" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "Zařízení není dosažitelné nebo se nachází mimo dosah signálu" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Zařízení je možné používat i v průběhu aktualizace" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "Zařízení čeká na instalaci aktualizací" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Odebráno zařízení:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "Zařízení vyžaduje, aby bylo připojeno napájení z elektrické zásuvky" #. TRANSLATORS: device does not have a display connected msgid "Device requires a display to be plugged in" msgstr "Zařízení vyžaduje aby byla připojená obrazovka" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "Aby ho bylo možné zaktualizovat, zařízení vyžaduje softwarovou licenci" #. TRANSLATORS: longer description msgid "Device software updates are provided for this device." msgstr "Pro toto zařízení jsou poskytovány aktualizace softwaru." #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Zařízení podporuje rozfázované aktualizace" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "Zařízení podporuje přepínání mezi různými větvemi firmware" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Metoda aktualizace zařízení" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "U daného zařízení je třeba nejdříve aktivovat režim aktualizace" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Zařízení provede zálohu stávajícího firmware před instalací nového" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Po dokončení aktualizace se zařízení hned znovu neobjeví" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Zařízení, která byla úspěšně aktualizována:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Zařízení, která nebyla úspěšně aktualizována:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Zařízení, pro která nejsou k dispozici aktualizace firmware:" #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Zařízení s nejnovějšími dostupnými verzemi firmware:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "Nenalezena žádná zařízení, která mají odpovídající GUID identifikátory" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Zakázáno" msgid "Disabled fwupdate debugging" msgstr "Vypnuto ladění fwupdate" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Zakázat daný vzdálený zdroj" #. TRANSLATORS: command line option msgid "Display version" msgstr "Zobrazit verzi" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Distribuce" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Nekontrolovat stáří metadat" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Nekontrolovat nenahlášení historie" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Nekontrolovat zda by mělo být zapnuté stahování ze vzdálených zdrojů" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Po aktualizaci nekontrolovat nebo se neptat na restart" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Nepřidávat předponu oblasti zaznamenávání" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Nepřidávat předponu s datem a časem" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Neprovádět přípravné kontroly zařízení" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "Nedotazovat se na zařízení" #. TRANSLATORS: command line option msgid "Do not prompt to fix security issues" msgstr "Nevyzývat k opravení problémů se zabezpečením" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "Při zpracovávání nehledat firmware" #. TRANSLATORS: warning message shown after update has been scheduled msgid "Do not turn off your computer or remove the AC adaptor while the update is in progress." msgstr "V průběhu aktualizace počítač nevypínejte ani ho neodpojujte od napájení ze zásuvky." #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Nezapisovat do databáze historie" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Rozumíte důsledkům změny větve firmwaru?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Chcete pro budoucí aktualizace tuto funkci vypnout?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "Chcete ji zapnout?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Chcete nyní tento vzdálený zdroj znovu načíst?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Chcete pro budoucí aktualizace odesílat hlášení automaticky?" #. TRANSLATORS: command line option msgid "Don't prompt for authentication (less details may be shown)" msgstr "Nepožadovat ověření se (následkem toho může být k dispozici méně podrobností)" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Hotovo!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Vrátit se ke starší verzi u %s a to z %s na %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Vrátit firmware v zařízení na starší verzi" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Vrací se %s z verze %s na starší %s…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Vracení %s na starší verzi…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Stáhnout si soubor" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Stahování…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Vypsat data SMBIOS ze souboru" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Délka instalace" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Určený ESP oddíl není platný" #. TRANSLATORS: longer description msgid "Each system should have tests to ensure firmware security." msgstr "Každý systém by měl mít testy pro zajištění zabezpečení firmware." #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "Emulovat zařízení pomocí JSON manifestu" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Emulováno" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Emulovaný stroj" msgid "Enable" msgstr "Zapnout" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Povolit podporu aktualizace firmwaru na podporovaných systémech" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Povolit nový vzdálený zdroj?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Povolit tento vzdálený zdroj?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Povoleno" msgid "Enabled fwupdate debugging" msgstr "Zapnuto ladění fwupdate" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Zapnuto pokud je odpovídající hardware" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Povolit zadaný vzdálený zdroj" #. TRANSLATORS: longer description msgid "Enabling firmware updates for the BIOS allows fixing security issues." msgstr "Zapnutí aktualizací firmware pro BIOS umožňuje opravovat problémy se zabezpečením." msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Zapnutí této funkce je na vaše vlastní riziko. To znamená, že v případě problémů způsobených aktualizací je třeba, abyste se obrátili přímo na výrobce daného vybavení. Na adresu $OS_RELEASE:BUG_REPORT_URL$ by měly být hlášeny pouze problémy s aktualizačním procesem jako takovým." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Povolení tohoto vzdáleného zdroje je na vaše vlastní riziko." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Šifrováno" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "Šifrovaná RAM" msgid "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." msgstr "Šifrovaná operační paměť znemožňuje čtení informací z paměti zařízení v případě, že je z něj paměťový modul vyjmut a pokoušeno se z něho číst." #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Podpora od výrobce skončila" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Výčet" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Smazat veškerou historii aktualizací firmware" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Mazání…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Skončit po krátké prodlevě" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Skončit po načtení výkonné části" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Exportovat strukturu souboru s firmwarem do XML" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Rozbalit firmware z blobu do obrazů" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "SOUBOR" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "SOUBOR [IDENTIF-ZAŘÍZENÍ|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "SOUBOR" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "SOUBOR CERTIFIKÁT SOUKROMÝ-KLÍČ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "SOUBOR ALTERN-NÁZEV-ZAŘÍZENÍ|ALTERN-IDENTIF-ZAŘÍZENÍ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "SOUBOR ALTERN-NÁZEV-ZAŘÍZENÍ|ALTENRN-IDENTIF-ZAŘÍZENÍ [ALTERN-NÁZEV-OBRAZU|ALTERN-IDENTIF-OBRAZU]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "SOUBOR IDENTIFIKÁTOR-ZAŘÍZENÍ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "SOUBOR POSUN DATA [TYP-FIRMWARE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "SOUBOR [IDENTIFIKÁTOR-ZAŘÍZ|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "SOUBOR [TYP-FIRMWARE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "ZDROJOVÝ-SOUBOR CÍLOVÝ-SOUBOR [ZDROJ-TYP-FIRMWARE] [CÍL-TYP-FIRMWARE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "SOUBOR|KONTROLNÍ-SOUČET1[,KONTROLNÍ-SOUČET2][,KONTROLNÍ-SOUČET3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Nezdařilo se" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Aktualizaci se nepodařilo provést" #. TRANSLATORS: error message for Windows msgid "Failed to connect to Windows service, please ensure it's running." msgstr "Nepodařilo se připojit k Windows službě – zajistěte, že je spuštěná." #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Nepodařilo se spojit s procesem služby" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Nepodařilo se získat čekající zařízení" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Aktualizaci firmware se nepodařilo nainstalovat" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Nepodařilo se načíst místní dbx" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Načtení zvláštních požadavků (quirks) se nezdařilo" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Nepodařilo se načíst systémové dbx" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Nepodařilo se uzamknout" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Zpracování argumentů se nezdařilo" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Soubor se nepodařilo zpracovat" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Nepodařilo se zpracovat příznaky pro --filter" msgid "Failed to parse flags for --filter-release" msgstr "Nepodařilo se zpracovat příznaky pro --filter-release" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Nepodařilo se zpracovat místní dbx" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Nepodařilo se restartovat" #. TRANSLATORS: a feature is something like "can show an image" msgid "Failed to set front-end features" msgstr "Nepodařilo se nastavit funkce nadstavby" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Nepodařilo se nastavit režim startovací obrazovky systému" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Nepodařilo se ověřit obsah ESP oddílu" #. TRANSLATORS: item is FALSE msgid "False" msgstr "Nepravda" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Soubor" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Podpis souboru" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Odkud soubor" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Je zapotřebí zadat název souboru" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filtrovat podle sady příznaků zařízení (s tím, že předpona ~ slouží pro vynechání), např. 'internal,~needs-reboot' (vestavěné, nevyžadující restart)" #. TRANSLATORS: command line option msgid "Filter with a set of release flags using a ~ prefix to exclude, e.g. 'trusted-release,~trusted-metadata'" msgstr "Filtrovat podle sady příznaků vydání (s tím, že předpona ~ slouží pro vynechání), např. 'trusted-release,~trusted-metadata'" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Atestace firmware" #. TRANSLATORS: longer description msgid "Firmware Attestation checks device software using a reference copy, to make sure that it has not been changed." msgstr "Atestace firmware kontroluje software zařízení pomocí referenční kopie, aby bylo jisté, že nebyl změněn." #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "BIOS popisovač firmware" #. TRANSLATORS: longer description msgid "Firmware BIOS Descriptor protects device firmware memory from being tampered with." msgstr "Firmware BIOS Descriptor chrání paměť firmware zařízení proti úpravám." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "BIOS oblast firmware" #. TRANSLATORS: longer description msgid "Firmware BIOS Region protects device firmware memory from being tampered with." msgstr "Firmware BIOS Region chrání paměť firmware zařízení proti úpravám." #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Základ URI pro firmware" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Služba D-Bus pro aktualizaci firmwaru" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Proces služby pro aktualizaci firmwaru" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Ověřování nástroje pro aktualizaci firmware" msgid "Firmware Updater Verification checks that software used for updating has not been tampered with." msgstr "Ověřování nástroje pro aktualizaci firmware kontroluje, že se software, použitým pro aktualizaci, nebylo manipulováno." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Aktualizace firmware" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Nástroj pro práci s firmwary" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Ochrana firmware vůči přepsání" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Zámek ochrany firmware vůči přepsání" #. TRANSLATORS: longer description msgid "Firmware Write Protection protects device firmware memory from being tampered with." msgstr "Zamezení zápisu firmware chrání paměť firmware zařízení proti úpravám." #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Atestace firmware" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Firmware už je blokován" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Firmware ještě není blokován" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Metadata o firmwarech nebyla aktualizována už celý %u den a nemusí být proto aktuální." msgstr[1] "Metadata o firmwarech nebyla aktualizována už celé %u dny a nemusí být proto aktuální." msgstr[2] "Metadata o firmwarech nebyla aktualizována už celých %u dní a nemusí být proto aktuální." msgstr[3] "Metadata o firmwarech nebyla aktualizována už celé %u dny a nemusí být proto aktuální." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Aktualizace firmware" msgid "Firmware updates are not supported on this machine." msgstr "Aktualizace firmwaru nejsou na tomto počítači podporované." msgid "Firmware updates are supported on this machine." msgstr "Aktualizace firmwaru jsou na tomto počítači podporované." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Aktualizace firmware jsou zakázané. Pokud je chcete povolit, spusťte „fwupdmgr unlock“" #. TRANSLATORS: command description msgid "Fix a specific host security attribute" msgstr "Opravit konkrétní atribut zabezpečení stroje" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fix reverted successfully" msgstr "Úspěšně vráceno do stavu před opravou" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fixed successfully" msgstr "Úspěšně opraveno" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Příznaky" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Vynutit akci uvolněním některých kontrol při vykonávání" msgid "Force the action ignoring all warnings" msgstr "Ignorovat veškerá varování a navzdory jim akci vynutit" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Nalezeno" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Zjištěno celodiskové šifrování" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "Při aktualizaci mohou být zneplatněna tajemství pro celodiskové šifrování" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "Platforma s jednorázovým nastavením (fuse)" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Platforma s jednorázovým nastavením (fuse)" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID identifikátory" msgstr[2] "GUID identifikátorů" msgstr[3] "GUID identifikátory" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "GUID|DEVICE-ID" msgstr "GUID|ID-ZARIZENI" msgid "Get BIOS settings" msgstr "Získat nastavení BIOS" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Vypsat všechny příznaky zařízení podporované fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Vypsat všechna zařízení podporující aktualizaci firmwaru" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Vypsat všechny povolené zásuvné moduly registrované v systému" #. TRANSLATORS: command description msgid "Get device report metadata" msgstr "Získat metadata výkazu o zařízení" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Zjistit podrobnosti o souboru s firmwarem" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Zjistit nastavené vzdálené zdroje" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Zjistit atributy zabezpečení hostitele" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Získat seznam schválených firmwarů" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Získat seznam blokovaných firmwarů" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Vypsat seznam aktualizací pro připojený hardware" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Vypsat vydání pro zařízení" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Vypsat výsledky z poslední aktualizace" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "SOUBOR-S-IDENTIF-HARDWARE" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Hardware čeká na opětovné připojení" #. TRANSLATORS: the release urgency msgid "High" msgstr "Vysoká" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Události zabezpečení na hostiteli" #. TRANSLATORS: error message for unsupported feature #, c-format msgid "Host Security ID (HSI) is not supported" msgstr "Identif. zabezpečení hostitele (HSI) není podporován" #. TRANSLATORS: success, so say thank you to the user msgid "Host Security ID attributes uploaded successfully, thanks!" msgstr "Atributy identifikátoru zabezpečení hostitele úspěšně nahrány – děkujeme!" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Identifikátor zabezpečení hostitele:" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "INHIBIT-ID" msgstr "IDENTIF-INHIBITORU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "Ochrana IOMMU" #. TRANSLATORS: longer description msgid "IOMMU Protection prevents connected devices from accessing unauthorized parts of system memory." msgstr "Ochrana IOMMU brání připojeným zařízení v přístupu k částem systémové paměti, ke kterým nejsou pověřené k přístupu." #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "ochrana IOMMU zařízení vypnuta" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "ochrana IOMMU zařízení zapnuta" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Nečinné…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Při stahování souborů ignorovat některé nedostatky SSL certifikátů" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Ignorovat nesprávný kontrolní součet firmware" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Ignorovat neshody firmware s hardware" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignorovat výsledky přípravných kontrol" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Ignorují se striktní SSL kontroly. Pokud chcete, aby se tak příště stalo automaticky, nastavte (a exportujte) proměnnou prostředí DISABLE_SSL_STRICT" #. TRANSLATORS: the inhibit ID is a short string like dbus-123456 #, c-format msgid "Inhibit ID is %s." msgstr "Identif. blokátoru je %s." #. TRANSLATORS: command description msgid "Inhibit the system to prevent upgrades" msgstr "Inhibovat systém a zabránit tak přechodům na novější verze" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Délka instalace" #. TRANSLATORS: command description msgid "Install a firmware file in cabinet format on this hardware" msgstr "Nainstalovat soubor s firmwarem (ve formátu cab) na tento hardware" #. TRANSLATORS: command description msgid "Install a raw firmware blob on a device" msgstr "Nainstalovat na zařízení holý binární soubor s firmwarem" #. TRANSLATORS: command description msgid "Install a specific firmware file on all devices that match" msgstr "Nainstalovat konkrétní soubor s firmware na všech zařízeních, se kterými je ve shodě" #. TRANSLATORS: command description msgid "Install a specific firmware on a device, all possible devices will also be installed once the CAB matches" msgstr "Nainstalovat konkrétní firmware na zařízení, veškerá možná zařízení budou také nainstalována, pokud jim CAB odpovídá" msgid "Install old version of signed system firmware" msgstr "Instalace starší verze podepsaného systémového firmware" msgid "Install old version of unsigned system firmware" msgstr "Instalace starší verze nepodepsaného systémového firmware" msgid "Install signed device firmware" msgstr "Instalace podepsaného firmwaru zařízení" msgid "Install signed system firmware" msgstr "Instalace podepsaného systémového firmwaru" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Nejprve nainstalovat do nadřazeného zařízení" msgid "Install unsigned device firmware" msgstr "Instalace nepodepsaného firmwaru zařízení" msgid "Install unsigned system firmware" msgstr "Instalace nepodepsaného systémového firmwaru" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Instalace firmware…" #. TRANSLATORS: stay on one firmware version unless the new version is #. explicitly #. * specified msgid "Installing a specific release is explicitly required" msgstr "Je výslovně vyžadována instalace konkrétního vydání" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Instaluje se aktualizace firmwaru…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Instalace na %s…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "Instalace této aktualizace také může zneplatnit jakékoli záruky na zařízení." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Celé číslo" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "Chráněno Intel BootGuard ACM" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Chráněno Intel BootGuard ACM" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Co Intel BootGuard udělá v případě chyb" msgid "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." msgstr "Zásada chyb Intel BootGuard zajišťuje, že zařízení nebude pokračovat ve startu, pokud se software zařízení bylo manipulováno." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Jednoráz. trvalé přepnutí (fuse) pro Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Jednorázové trvalé přepnutí (OTP fuse) pro Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Ověřované zavádění s Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Co Intel BootGuard udělá v případě chyb" #. TRANSLATORS: longer description msgid "Intel BootGuard prevents unauthorized device software from operating when the device is started." msgstr "Intel BootGuard brání fungování neověřeného software zařízení, když je zařízení spouštěno." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Ověřované zavádění s Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET aktivní" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET podporováno" #. TRANSLATORS: longer description msgid "Intel Control-Flow Enforcement Technology detects and prevents certain methods for running malicious software on the device." msgstr "Technologie Intel Control-Flow Enforcement (vynucování řízení toku) zjišťuje a brání určitým metodám spouštění škodlivého software na zařízení." #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS Mitigation" msgstr "Zmírnění Intel GDS" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS mitigation" msgstr "Zmírnění Intel GDS" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Intel Management Engine – režim pro výrobce" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Intel Management Engine – přebití" #. TRANSLATORS: longer description msgid "Intel Management Engine Override disables checks for device software tampering." msgstr "Přebití Intel Management Engine vypíná kontroly manipulace se software zařízení." #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Verze Intel Management Engine" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: longer description msgid "Intel Supervisor Mode Access Prevention ensures critical parts of device memory are not accessed by less secure programs." msgstr "Intel Supervisor Mode Access Prevention zajišťuje, že kritické části paměti zařízení nejsou přístupné méně bezpečným programům." #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Vestavěné zařízení" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Neplatné" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Neplatné argumenty" #. TRANSLATORS: error message msgid "Invalid arguments, expected GUID" msgstr "Neplatný argument – očekáváno GUID" #. TRANSLATOR: This is the error message for #. * incorrect parameter msgid "Invalid arguments, expected an AppStream ID" msgstr "Neplatný argument – očekáván AppStream identif." #. TRANSLATORS: error message msgid "Invalid arguments, expected at least ARCHIVE FIRMWARE METAINFO" msgstr "Neplatný argument – očekáváno přinjemenším ARCHIVE FIRMWARE METAINFO" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Je starší verzí" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Je v režimu zavaděče firmware" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Je novější verzí" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Problém" msgstr[1] "Problémy" msgstr[2] "Problémů" msgstr[3] "Problémy" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "KLÍČ,HODNOTA" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "Jádro už není pozměněno" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "Jádro je pozměněno" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Uzamčení jádra vypnuto" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Uzamčení jádra zapnuto" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Klíčenka" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "UMÍSTĚNÍ" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Naposledy změněno" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Zbývá méně než jedna minuta" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licence" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Uzamčení linuxového jádra" #. TRANSLATORS: longer description msgid "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgstr "Uzamčený režim linuxového jádra brání účtům s právy správy (root) v přístupu a změnám kritických částí systémového software." msgid "Linux Kernel Swap temporarily saves information to disk as you work. If the information is not protected, it could be accessed by someone if they obtained the disk." msgstr "Odkládací prostor (swap) linuxového jádra za provozu dočasně ukládá informace na disk. Pokud tyto nejsou chráněny, může se k nim někdo dostat, pokud získá přístup k disku." #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Ověření linuxového jádra" msgid "Linux Kernel Verification makes sure that critical system software has not been tampered with. Using device drivers which are not provided with the system can prevent this security feature from working correctly." msgstr "Ověřování neporučenosti linuxového jádra zajišťuje, že s kritickým systémovým software nebylo manipulováno. Použití ovladačů zařízení, které nejsou poskytovány systémem, mohou této bezpečnostní funkci bránit ve správném fungování." #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Linuxový odkládací prostor" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (stabilní firmware)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (testovací firmware)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Linuxové jádro" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Uzamčení linuxového jádra" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linuxový odkládací prostor" #. TRANSLATORS: command description msgid "List EFI variables with a specific GUID" msgstr "Vypsat EFI proměnné s konkrétním GUID" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Vypsat položky v dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Vypsat podporované aktualizace firmwarů" #. TRANSLATORS: command description msgid "List the available firmware GTypes" msgstr "Vypsat GTypy firmware, které jsou k dispozici" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Vypsat typy firmwaru, které jsou k dispozici" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Vypsat soubory na ESP oddílu" #. TRANSLATORS: command description msgid "Load device emulation data" msgstr "Načíst data emulace zařízení" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "Načteno z externího modulu" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Načítání…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Zamčeno" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Nízká" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "Manifest klíče MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "Manifest klíče MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "režim pro výrobce v MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "hw přepnutí MEI" msgid "MEI version" msgstr "Verze MEI" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Ručně zapnout konkrétní zásuvné moduly" #. TRANSLATORS: longer description msgid "Manufacturing Mode is used when the device is manufactured and security features are not yet enabled." msgstr "Režim pro výrobce je používán při výrobě zařízení a funkce pro zabezpečení ještě nejsou zapnuté." #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Nejdelší platná délka" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Nejvyšší platná hodnota" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Střední" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Podpis metadat" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI pro metadata" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadata lze získat ze služby Linux Vendor Firmware Service." #. TRANSLATORS: error message for a user who ran fwupdmgr #. * refresh recently msgid "Metadata is up to date; use --force to refresh again." msgstr "Metadata jsou aktuální. Pokud je přesto chcete znovu načíst, použijte přepínač --force." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Nejstarší použitelná verze" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Nejkratší platné délka" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Nejnižší platná hodnota" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Nesoulad verzí procesu služby a klienta, namísto toho použijte %s" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Změnit hodnotu v nastavení procesu služby" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Změnit zadaný vzdálený zdroj" msgid "Modify a configured remote" msgstr "Upravit nastavený vzdálený zdroj" msgid "Modify daemon configuration" msgstr "Upravit nastavení procesu služby" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Sledovat události v procesu služby" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Připojit ESP oddíl" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Po instalaci vyžaduje restart" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Vyžaduje restart" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Po instalaci vyžaduje vypnutí" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Nová verze" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Není zadána žádné akce!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Pro %s nejsou k dispozici žádné starší verze" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Nenalezeny žádné identifikátory firmware" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "Nenalezen žádný firmware" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Nezjištěn žádný hardware vybavený pro aktualizaci firmware v něm." #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nebyl nalezen žádný zásuvný modul" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Nejsou k dispozici žádná vydání" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "V tuto chvíli nejsou povolené žádné vzdálené zdroje, takže nejsou k dispozici žádná metadata." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Nejsou k dispozici žádné vzdálené zdroje" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Žádná zařízení, u kterých by bylo možné zaktualizovat" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Nejsou k dispozici žádné aktualizace" msgid "No updates available for remaining devices" msgstr "Pro zbývající zařízení nejsou k dispozici žádné aktualizace" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nebyly provedeny žádné aktualizace" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Neschváleno" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Nenalezeno" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Nepodporováno" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "V pořádku" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "OK!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Stará verze" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Zobrazit pouze jedinou PCR hodnotu" #. TRANSLATORS: command line option msgid "Only use peer-to-peer networking when downloading files" msgstr "Stahovat soubory pouze ze sítě klient-klient (p2p)" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Zařízení umožňuje pouze aktualizace na novější verze" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Výstup ve formátu JSON" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Přepsat výchozí popis umístění na ESP oddílu" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Firmware" msgstr "Firmware z P2P" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Metadata" msgstr "Metadata z P2P" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "POPIS-UMÍSTĚNÍ" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Zpracovat a zobrazit podrobnosti o souboru s firmwarem" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Zpracovávání aktualizace dbx…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Zpracovávání systémového dbx…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Heslo" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Opravit blob firmware na známém posunu" msgid "Payload" msgstr "Obsah" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Čeká" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Dokončeno procent" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Provést operaci" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Ladění platformy" #. TRANSLATORS: longer description msgid "Platform Debugging allows device security features to be disabled. This should only be used by hardware manufacturers." msgstr "Ladění platformy umožňuje vypínat funkce zabezpečení zařízení. Mělo by být používáno pouze výrobci zařízení." #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Ladění platformy" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Než budete pokračovat, zajistěte si, abyste měli k dispozici záchranný klíč pro svazek." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Zadejte prosím číslo od 0 do %u:" #. TRANSLATORS: the user isn't reading the question -- please don't translate #. * 'Y' or 'N' as these are hardcoded msgid "Please enter either Y or N: " msgstr "Zadejte Y (ano) nebo N (ne):" #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Chybí komponenty, na kterých zásuvný modul závisí" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Možné hodnoty" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "Ochrana přímého přístupu do paměti před startem operačního systému" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Ochrana přímého přístupu do paměti před startem operačního systému" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "Ochrana přímého přístupu do paměti před startem operačního systému je vypnuta" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "Ochrana přímého přístupu do paměti před startem operačního systému je zapnuta" #. TRANSLATORS: longer description msgid "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." msgstr "Ochrana přímého přístupu do paměti (DMA) brání zařízení přistupovat k systémové paměti poté co jsou připojena k počítači." #. TRANSLATORS: warning message msgid "Press unlock on the device to continue the update process." msgstr "Aby bylo možné pokračovat v procesu aktualizace, stiskněte na zařízení odemknutí." #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Předchozí verze" msgid "Print the version number" msgstr "Vypsat číslo verze" msgid "Print verbose debug statements" msgstr "Vypsat podrobná ladicí hlášení" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Priorita" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Problémy" msgid "Proceed with upload?" msgstr "Pokračovat v nahrávání?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Kontroly zabezpečení procesoru" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Zamezení instalace starší verze na procesoru" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Uzavřená" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Dotázat se na podporu aktualizace firmwaru" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "IDENTIFIKÁTOR-VZDÁLENÉHO-ZDROJE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "HODNOTA KLÍČE IDENTIF-VZDÁLENÉHO-ZDROJE" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Pouze pro čtení" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Vyčíst blob firmwaru ze zařízení" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Vyčíst firmware ze zařízení" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Vyčíst firmware ze zařízení a zapsat ho do souboru" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Vyčíst firmware z jednoho oddílu do souboru" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Čtení z %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Čtení…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Restartování…" #. TRANSLATORS: how often we should refresh the metadata msgid "Refresh Interval" msgstr "Interval opětovného načítání" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Aktualizovat metadata ze vzdáleného serveru" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Přeinstalovat %s na %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Přeinstalovat stávající firmware na zařízení" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Přeinstalovat firmware na zařízení" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Přeinstalovává se %s na %s…" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Větev vydání" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Příznaky vydání" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "Identif. vydání" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Identif. vzdáleného zdroje" #. TRANSLATORS: command description msgid "Removes devices to watch for future emulation" msgstr "Odebere zařízení pro hlídání budoucí emulace" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Nahradit data ve stávajícím souboru s firmwarem" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI pro hlášení" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Nahlášeno na vzdálený server" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Požadavek zrušen" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "Nebyl nalezen potřebný souborový systém efivarfs" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "Požadovaný hardware nenalezen" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Vyžaduje ruční přepnutí do zavaděče firmware" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Vyžaduje připojení k Internetu" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Restartovat nyní?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Restartovat proces služby, aby se změny uplatnily?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Restartování zařízení…" #. TRANSLATORS: command description msgid "Retrieve BIOS settings. If no arguments are passed all settings are returned" msgstr "Získává nastavení z BIOS. Pokud nejsou uvedeny žádné argumenty, jsou vypsána veškerá nastavení" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Vypsat všechny identifikátory hardwaru počítače" #. TRANSLATORS: ask the user to upload msgid "Review and upload report now?" msgstr "Prohlédnout si a odeslat hlášení nyní?" #. TRANSLATORS: longer description msgid "Rollback Protection prevents device software from being downgraded to an older version that has security problems." msgstr "Zamezení instalace starší verze brání přechodu na starší verzi software zařízení (mohly by obsahovat už známé problémy v zabezpečení)." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Podrobnosti získáte spuštěním `fwupdmgr get-upgrades`." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr sync` to complete this action." msgstr "Pokud chcete tuto akci dokončit, spusťte `fwupdmgr sync`." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Při použití install-blob spustit zásuvný modul rutina vyčištění složeného" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Při použití install-blob spustit zásuvný modul rutina připravení složeného" #. TRANSLATORS: command description msgid "Run the post-reboot cleanup action" msgstr "Spustit akci čištění po restartu" #. TRANSLATORS: tell a user how to get information #, c-format msgid "Run without '%s' to see" msgstr "Pokud chcete jen zobrazit, spusťte bez „%s“" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "Provozované jádro systému je příliš staré" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Přípona běhového prostředí" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "HODNOTA NASTAVENI" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING1 VALUE1 [SETTING2] [VALUE2]" msgstr "NASTAVENÍ1 HODNOTA1 [NASTAVENÍ2] [HODNOTA2]" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "Popisovač SPI BIOS" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "Oblast SPI BIOS" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI uzamčení" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "Ochrana SPI před replay" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI zápis" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "Ochrana zápisu do SPI" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "PODSYSTÉM OVLADAČ [IDENTIF-ZAŘÍZENÍ|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Uložit soubor který umožňuje vytváření identifikátorů hardware" #. TRANSLATORS: command description msgid "Save device emulation data" msgstr "Uložit data emulace zařízení" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "Skalární přírůstek" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Pokud je to možné, naplánovat instalaci na příští restart" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Plánování aktualizace…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Secure Boot vypnut" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Secure Boot zapnut" msgid "Security hardening for HSI" msgstr "Posílení zabezpečení pro HSI" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Podrobnosti viz %s." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Podrobnosti viz %s." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Vybrané zařízení" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Vybraný svazek" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Sériové číslo" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "Nastavit volbu v BIOS „%s“ pomocí „%s." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "Provést nastavení v BIOS" msgid "Set one or more BIOS settings" msgstr "Nastavit jedno nebo více nastavení v BIOS" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Během aktualizace nastavit příznak ladění" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "Nastaví jedno nebo více nastavení v BIOS" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Nastavuje seznam schválených firmwarů" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Typ nastavení" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Nastavení se projeví po restartu stroje" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Sdílet historii firmwaru s vývojáři" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Zobrazit všechny výsledky" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Zobrazit verzi klienta a procesu služby" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Zobrazit podrobnější informace z procesu služby pro konkrétní oblast" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Zapnout vypisování ladicích informací pro všechny oblasti" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Zobrazit předvolby ladění" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Zobrazit zařízení, která nelze aktualizovat" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Zobrazovat doplňující informace pro ladění" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Zobrazit historii aktualizací firmwaru" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Zobrazit vypočtenou verzi dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Zobrazit záznam ladicích informací z posledního pokusu o aktualizaci" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Zobrazit informace o stavu aktualizace firmwaru" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Vypnout nyní?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Podepsat firmware novým klíčem" msgid "Sign data using the client certificate" msgstr "Podepsat data klientským certifikátem" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Podepsat data klientským certifikátem" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Podepsat nahraná data klientským certifikátem" msgid "Signature" msgstr "Podpis" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Podepsaný obsah" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Velikost" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Při aktualizaci tohoto firmwaru mohou být některá tajemství uložená v rámci platformy zneplatněna." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Zdroj" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Zadejte identifikátor(y) výrobce/produktu DFU zařízení" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Zadejte soubor dbx databáze" msgid "Specify the number of bytes per USB transfer" msgstr "Zadejte počet bajtů pro jednotlivé přenosy přes USB sběrnici" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "Řetězec" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Úspěch" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Všechna zařízení úspěšně aktivována" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Vzdálený zdroj úspěšně zakázán" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Nová metadata úspěšně stažena:" #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Vzdálený zdroj úspěšně povolen a znovu načten" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Vzdálený zdroj úspěšně povolen" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmware úspěšně nainstalován" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Hodnota v nastavení úspěšně změněna" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Vzdálený zdroj úspěšně změněn" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metadata úspěšně ručně aktualizována" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Kontrolní součty pro zařízení úspěšně zaktualizovány" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Úspěšně odesláno %u hlášení" msgstr[1] "Úspěšně odeslána %u hlášení" msgstr[2] "Úspěšně odesláno %u hlášení" msgstr[3] "Úspěšně odeslána %u hlášení" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Kontrolní součty pro zařízení úspěšně ověřeny" #. TRANSLATORS: the device showed up in time #, c-format msgid "Successfully waited %.0fms for device" msgstr "Úspěšně počkáno %.0fms na zařízení" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Souhrn" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Podporováno" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "Podporovaný procesor" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Podporováno na vzdáleném serveru" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Uspat do nečinnosti" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Uspat do paměti" #. TRANSLATORS: longer description msgid "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Uspání do nečinnosti umožňuje zařízení rychlé přejít do režimu spánku a šetřit tak energií. V době, kdy je zařízení uspané, je možné z něho fyzicky vyjmout operační paměť a číst z ní informace." #. TRANSLATORS: longer description msgid "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Uspání do paměti umožňuje zařízení rychlé přejít do režimu spánku a šetřit tak energií. V době, kdy je zařízení uspané, je možné z něho fyzicky vyjmout operační paměť a číst z ní informace." #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Uspat do nečinnosti" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Uspat do paměti" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Přepnout větev z %s na %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Přepnout větev firmware na zařízení" #. TRANSLATORS: command description msgid "Sync firmware versions to the chosen configuration" msgstr "Synchronizovat verze firmwarů ke zvolenému nastavení" #. TRANSLATORS: this CLI tool is now preventing system updates msgid "System Update Inhibited" msgstr "Aktualizace systému blokována" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "Akumulátor notebooku není dostatečně nabitý, aby aktualizaci bylo možné provést" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "Akumulátor notebooku není dostatečně nabitý, aby aktualizaci bylo možné provést (%u%%, vyžaduje %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "Počítač vyžaduje externí napájení" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEXT" #. TRANSLATORS: longer description msgid "TPM (Trusted Platform Module) is a computer chip that detects when hardware components have been tampered with." msgstr "TPM (Trusted Platform Module) je počítačový čip, který zjišťuje zda bylo s hardwarovými součástmi manipulováno." #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "Rekonstrukce TPM PCR0" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "Rekonstrukce TPM PCR0 není platná" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "Rekonstrukce TPM PCR0 je nyní platná" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "Nastavení TPM platformy" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "Rekonstrukce TPM" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "Prázdné PCR registry v TPM" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Štítek" msgstr[1] "Štítky" msgstr[2] "Štítků" msgstr[3] "Štítky" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "Označeno k emulaci" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "„Poskrvněno“" msgid "Target" msgstr "Cíl" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Vyzkoušet zařízení pomocí JSON manifestu" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Vyzkoušeno" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "Vyzkoušeno výrobcem %s" #. TRANSLATORS: someone we trust has tested this msgid "Tested by trusted vendor" msgstr "Vyzkoušeno důvěryhodným výrobcem" #. TRANSLATORS: longer description msgid "The Intel Management Engine Key Manifest must be valid so that the device firmware can be trusted by the CPU." msgstr "Je třeba, aby manifest klíče správního engine (ME) byl platný, aby z pohledu procesoru mohl být firmware považován za důvěryhodný." #. TRANSLATORS: longer description msgid "The Intel Management Engine controls device components and needs to have a recent version to avoid security issues." msgstr "Intel Management Engine ovládá součásti zařízení a aby bylo zamezeno problémům se zabezpečením, je třeba mít jeho nejnovější verzi." #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS je bezplatná služba, které funguje jako nezávislý právní subjekt a nemá žádné vazby na systém $OS_RELEASE:NAME$. Kompatibilita aktualizací firmware s připojenými zařízeními, či vámi používaným systémem, nemusí být poskytovatelem vámi používané distribuce ověřována. Veškerý firmware je poskytován pouze přímo od výrobců daného vybavení." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Platform Configuration is used to check whether the device start process has been modified." msgstr "Nastavení TPM (Trusted Platform Module) platformy slouží k ověření, zda nebylo s procesem startu zařízení manipulováno." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Reconstruction is used to check whether the device start process has been modified." msgstr "TPM (Trusted Platform Module) Reconstruction slouží k ověření, zda nebylo s procesem startu zařízení manipulováno." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "PCR0 v TPM se liší od rekonstrukce." #. TRANSLATORS: longer description msgid "The UEFI Platform Key is used to determine if device software comes from a trusted source." msgstr "UEFI Platform Key slouží ke ověřování zda software na zařízení pochází z důvěryhodného zdroje." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "Proces služby načetl spustitelný kód, pocházející od třetí strany. Vývojáři původního procesu služby už proto jeho podporu nemají ve svých rukou!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "Verze zařízení neodpovídá: obdrženo %s, očekáváno %s" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "Firmware z %s nepochází od %s (výrobce daného hardware)." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "Systémové hodiny nemají správný čas – stahování souborů se kvůli tomu nemusí zdařit." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been re-inserted." msgstr "Aktualizace bude pokračovat po odpojení a připojení USB kabelu." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged and then re-inserted." msgstr "Aktualizace bude pokračovat po odpojení a opětovném připojení USB kabelu." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged." msgstr "Aktualizace bude pokračovat po odpojení USB kabelu." #. TRANSLATORS: warning message msgid "The update will continue when the device power cable has been removed and re-inserted." msgstr "Aktualizace bude pokračovat po odpojení a opětovném připojení napájecího kabelu zařízení." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "Výrobce neposkytl žádné poznámky k vydání." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Jsou zde zařízení, která mají problémy:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Nejsou zde žádné blokované soubory s firmware" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Není k dispozici žádný schválený firmware." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "Vykonáním příkazu %s bude toto zařízení vráceno zpět na verzi %s." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "Tento firmware je poskytován členy komunity LVFS a není poskytován (ani podporován) původním výrobcem hardware." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Tento balíček nebyl ověřen – může se stát, že nebude fungovat správně." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Tento program může správně fungovat jen pod uživatelem root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Tento vzdálený zdroj obsahuje firmware, který už sice je možné šířit, ale zatím je u výrobce stále ještě ve stádiu testování. Měli byste si zajistit, že znáte způsob, jak se ručně vrátit ke starší verzi firmwaru, kdyby při aktualizaci došlo k nezdaru." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "Tento systém nepodporuje nastavování firmware" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Tento systém má problémy s běhovým prostředím HSI." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Tento systém má nízkou úroveň zabezpečení HSI." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Tento nástroj správci umožňuje provádět UEFI dbx aktualizace." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Tento nástroj správci umožňuje provádět ladění operace UpdateCapsule." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Tento nástroj správci umožňuje dotazovat a ovládat proces služby fwupd a tím provádět akce jako např. instalaci nebo návrat ke starší verzi firmware." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Tento nástroj správci umožňuje použít zásuvné moduly pro fwupd bez toho, aby se instalovaly na hostitelský systém." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can add a kernel argument of '%s', but it will only be active after restarting the computer." msgstr "Tento nástroj může přidat argument jádra systému „%s“, ale toto se projeví až po restartu počítače." #. TRANSLATORS: the %1 is a BIOS setting name. #. * %2 and %3 are the values, e.g. "True" or "Windows10" #, c-format msgid "This tool can change the BIOS setting '%s' from '%s' to '%s' automatically, but it will only be active after restarting the computer." msgstr "Tento nástroj může změnit nastavení BIOS „%s“ z „%s“ na „%s“ automaticky, ale toto se projeví až po restartu počítače." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can change the kernel argument from '%s' to '%s', but it will only be active after restarting the computer." msgstr "Tento nástroj může změnit argument jádra systému z „%s“ na „%s“, ale toto se projeví až po restartu počítače." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Tento nástroj může používat pouze uživatel s oprávněními na úrovni správce systému (root)" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Tento nástroj vyčte ze systémového firmwaru záznam událostí v TPM a zpracuje ho." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Přechodný nezdar" #. TRANSLATORS: item is TRUE msgid "True" msgstr "Pravda" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Důvěryhodná metadata" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Důvěryhodný obsah" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Typ" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI Bootservice Variables" msgstr "UEFI Bootservice Variables" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition may not be set up correctly" msgstr "Je možné, že UEFI ESP oddíl není správně uspořádán" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "Nezjištěn (či nenastaven) UEFI ESP oddíl" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Nástroj pro práci s UEFI firmwarem" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "Klíč platformy v UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "UEFI Secure Boot" #. TRANSLATORS: longer description msgid "UEFI Secure Boot prevents malicious software from being loaded when the device starts." msgstr "UEFI Secure Boot brání načtení záškodnickému softwaru při spouštění zařízení." #. TRANSLATORS: longer description msgid "UEFI boot service variables should not be readable from runtime mode." msgstr "Proměnné UEFI boot service by neměly být čitelné z běhového režimu." #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI bootservice variables" msgstr "Proměnné UEFI bootservice" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "Nejsou k dispozici (nebo jsou vypnuté v nastavení firmware) aktualizace typu UEFI capsule" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "Nástroj pro UEFI dbx" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "UEFI firmware není možné aktualizovat, pokud je přepnutý do režimu legacy BIOS" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "Klíč platformy v UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "UEFI secure boot" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Nedaří se připojit ke službě" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "Atribut se nedaří nalézt" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Odpojit stávající ovladač" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Odblokovává se firmware:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Odblokovat instalaci konkrétního firmwaru" #. TRANSLATORS: command description msgid "Undo the host security attribute fix" msgstr "Vrátit do stavu před opravou atributu zabezpečení stroje" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Nešifrováno" #. TRANSLATORS: command description msgid "Uninhibit the system to allow upgrades" msgstr "Zrušit inhibování systému a umožnit tak přechody na novější verze" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Neznámo" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Neznámé zařízení" msgid "Unlock the device to allow access" msgstr "Odemknout zařízení a umožnit tak do něj přístup" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Odemčeno" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Odemknout zařízení pro přístup k firmwaru" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Odpojit ESP oddíl" #. TRANSLATORS: message shown after device has been marked for emulation msgid "Unplug and replug the device to continue the update process." msgstr "Aby bylo možné pokračovat v procesu aktualizace, zařízení odpojte a připojte nazpět." #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Během aktualizace zrušit příznak ladění" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Nepodepsaný obsah" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Nepodporovaná verze procesu služby %s, verze klienta je %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "„Neposkvrněno“" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Možné aktualizovat" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Chyba při aktualizaci" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Aktualizovat obraz" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Zpráva z aktualizace" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Stav aktualizace" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Problém s nezdařenou aktualizací je už znám, další informace naleznete na této adrese:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Aktualizovat nyní?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Aktualizace vyžaduje restart" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Aktualizovat uložený kryptografický otisk stávajícím obsahem ROM paměti" msgid "Update the stored device verification information" msgstr "Aktualizovat uložené informace ohledně ověření správnosti pro zařízení" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Aktualizovat uložená metadata stávajícím obsahem" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "Aktualizuje firmware veškerých zadaných zařízení na nejnovější verzi, případně na všech zařízeních, pokud nejsou žádná vyjmenována" msgid "Updating" msgstr "Aktualizuje se" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Aktualizuje se %s z verze %s na %s…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Aktualizace %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Přejít na novější verzi u %s a to z %s na %s?" #. TRANSLATORS: ask the user to share, %s is something #. * like: "Linux Vendor Firmware Service" #, c-format msgid "Upload these anonymous results to the %s to help other users?" msgstr "Nahrát tyto anonymní výsledky na %s a pomoci tak ostatním uživatelům?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Posíláním hlášení o firmwarech pomůžete výrobcům hardwaru rychle rozpoznat nezdařené a úspěšné aktualizace na zařízeních, tak jak jsou provozována v reálných podmínkách." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Naléhavost" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Nápovědu vyvoláte pomocí %s" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Zrušíte stisknutím CTRL+C." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Nápovědu obdržíte spuštěním fwupdtool --help" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Při instalaci firmware použít příznaky pro kompatibilitu (quirk)" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Uživatel byl upozorněn" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Uživatelské jméno" msgid "VID:PID" msgstr "IDENTIF-VÝROBCE:IDENTIF-PRODUKTU" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Platné" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Ověřování obsahu ESP oddílu…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Varianta" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Výrobce" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Ověřování zapsaného…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Verze" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Verze[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "VAROVÁNÍ" #. TRANSLATORS: command description msgid "Wait for a device to appear" msgstr "Počkat až se zařízení objeví" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Čekání…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Sledovat změny hardwaru" #. TRANSLATORS: check various UEFI and ACPI tables are unchanged after the #. update msgid "Will measure elements of system integrity around an update" msgstr "Zkontroluje neporušenost prvků systému po aktualizaci" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Zapsat firmware ze souboru do zařízení" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Zapsat firmware ze souboru do jednoho oddílu" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Zapisování souboru:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Zapisování…" #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from a recovery or installation disk, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Měli byste ověřit že dokážete vrátit nastavení zpět ze záchranného nebo instalačního média, protože tato změna může způsobit že systém nenastartuje do Linuxu nebo způsobí jiné nestability systému." #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from the system firmware setup, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Měli byste ověřit že dokážete vrátit nastavení zpět z rozhraní firmware, protože tato změna může způsobit že systém nenastartuje do Linuxu nebo způsobí jiné nestability systému." #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Kompatibilita aktualizací firmware s připojenými zařízeními, či vámi používaným systémem, nemusí být poskytovatelem vámi používané distribuce ověřována. " #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Použitím tohoto firmware může být hardware poškozen a instalací tohoto vydání může být ztracena záruka od %s." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "Váš systém má osvědčené uspořádání %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[APPSTREAM_ID]" msgstr "[APPSTREAM_IDENTIF]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[KONTROLNÍ-SOUČET]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[IDENTIF-ZAŘÍZENÍ|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[IDENTIF-ZAŘÍZENÍ|GUID] [VĚTEV]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[IDENTIF-ZAŘÍZENÍ|GUID] [VERZE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE]" msgstr "[ZAŘÍZENÍ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[SOUBOR PODPIS_SOUBORU IDENTIF-VZDÁLENÉHO-ZDROJE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[SOUBOR1] [SOUBOR2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[DŮVOD]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[NASTAVENi1] [NASTAVENI2]…" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[NASTAVENÍ1] [NASTAVENÍ2] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SOUBOR-S-SMBIOS|SOUBOR-S-IDENTIF-HARDWARE]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "výchozí" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "fwupd nástroj pro práci se záznamy událostí v TPM" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "zásuvné moduly pro fwupd" fwupd-1.9.16/po/da.po000066400000000000000000002137601460375044200143150ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # scootergrisen, 2019 # scootergrisen, 2019-2021 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Danish (http://app.transifex.com/freedesktop/fwupd/language/da/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: da\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minut tilbage" msgstr[1] "%.0f minutter tilbage" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "%s Opdatering for batteri" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "%s opdatering af CPU-mikrokode" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "%s Opdatering for kamera" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "Opdatering for konfiguration %s" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s ME-opdatering for forbruger" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Opdatering af controller %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s ME-opdatering for virksomhed" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Enhedsopdatering for %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Opdatering af indlejret controller %s" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "%s Opdatering for tastatur" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "ME-opdatering for %s" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "%s Opdatering for mus" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "Opdatering af netværksgrænseflade %s" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "Opdatering af lagercontroller %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Systemopdatering for %s" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "%s Opdatering for TPM" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Opdatering af Thunderbolt-controller %s" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "%s Opdatering for pegeplade" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Opdatering for %s" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s og alle tilsluttede enheder vil måske ikke være anvendelige under opdatering." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s-fabrikstilstand" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s skal være tilsluttet under hele opdateringen, for at undgå skade." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s skal være tilsluttet en strømkilde under hele opdateringen, for at undgå skade." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s-tilsidesættelse" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s-version" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dag" msgstr[1] "%u dage" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u enhed har en tilgængelig firmwareopgradering." msgstr[1] "%u enheder har en tilgængelig firmwareopgradering." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u time" msgstr[1] "%u timer" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u lokal enhed understøttes" msgstr[1] "%u lokale enheder understøttes" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minut" msgstr[1] "%u minutter" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u sekund" msgstr[1] "%u sekunder" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(forældet)" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Handling kræves:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Aktivér enheder" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Aktiverer afventende enheder" msgid "Activate the new firmware on the device" msgstr "Aktivér den nye firmware på enheden" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Aktiverer firmwareopdatering" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Aktiverer firmwareopdatering for" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Alder" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Accepter og aktivér fjernen?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias til %s" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Alle enheder af den samme type opdateres samtidigt" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Tillad nedgradering af firmwareversioner" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Tillad geninstallering af eksisterende firmwareversioner" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Tillad skift af firmwaregren" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "For at fuldføre en opdatering skal systemet genstartes." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "For at fuldføre en opdatering skal systemet lukkes ned." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Svar ja til alle spørgsmål" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Anvend firmwareopdateringer" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Anvend opdatering, selv når det ikke tilrådes" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Anvend opdateringsfiler" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Anvender opdatering …" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Godkendt firmware:" msgstr[1] "Godkendt firmware:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Spørg igen næste gang?" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Tilkobl til firmwaretilstand" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Autentificerer …" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Der kræves autentifikationsdetaljer" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Der kræves autentifikation for at nedgradere firmwaren på en flytbar enhed" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Der kræves autentifikation for at nedgradere firmwaren på maskinen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Der kræves autentifikation for at redigere en konfigureret fjern som bruges til firmwareopdateringer" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Der kræves autentifikation for at redigere dæmonkonfiguration" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Der kræves autentifikation for at indstille listen over godkendt firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Der kræves autentifikation for at underskrive data med klientcertifikatet" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Der kræves autentifikation for at skifte til den nye firmwareversion" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Der kræves autentifikation for at låse enhed op" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Der kræves autentifikation for at opdatere firmwaren på en flytbar enhed" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Der kræves autentifikation for at opdatere firmwaren på maskinen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Der kræves autentifikation for at opdatere de gemte checksumme for enheden" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Automatisk rapportering" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Upload automatisk hver gang?" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BUILDER-XML FILNAVN-DST" msgid "BYTES" msgstr "BYTES" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Bind ny kernedriver" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Blokerede firmwarefiler:" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Blokerer firmware:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Blokerer en bestemt firmware fra at blive installeret" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Opstartsindlæser version" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Gren" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Byg en firmwarefil" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Annuller" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Annulleret" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Kan ikke anvende eftersom dbx-opdatering allerede er blevet anvendt." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Kan ikke anvende opdateringer på livemedier" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Ændret" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Tjekker om den kryptografiske hash passer med firmwaren" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Checksum" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Rydder resultaterne fra den sidste opdatering" #. TRANSLATORS: error message msgid "Command not found" msgstr "Kommandoen blev ikke fundet" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Konverter en firmwarefil" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Oprettet" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Kritisk" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Bekræftelse af kryptografisk hash er tilgængelig" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Nuværende version" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "ENHEDS-ID|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU-redskab" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Fejlsøgningsindstillinger" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Udpakker …" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Beskrivelse" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Frakobl til opstartsindlæsertilstand" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Detaljer" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Enhedsflag" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Enheds-id" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Enhed tilføjet:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Enheden kan gendannes efter mislykkedes flash" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Enhed ændret:" #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Enhedsfirmware kræves for at have et versiontjek" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Enheden er låst" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Enhed kræves for at installere alle leverede udgivelser" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Enheden er utilgængelig" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Enheden kan anvendes under hele opdateringen" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Enhed fjernet:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Enhedstrin-opdateringer" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "Enheden understøtter at der kan skiftes til en anden gren med firmware" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Opdateringsmetode for enhed" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Enhedsopdatering behøver aktivering" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Enheden sikkerhedskopierer firmwaren inden installation" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Enheden vises ikke igen når opdateringen er færdig" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Enheder som det lykkedes at opdatere:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Enheder som ikke blev opdateret korrekt:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Enheder uden nogen tilgængelige firmwareopdateringer: " #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Enheder med den seneste tilgængelige firmwareversion:" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Deaktiveret" msgid "Disabled fwupdate debugging" msgstr "Deaktivér fejlsøgning af fwupdate" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Deaktiverer en angivet fjern" #. TRANSLATORS: command line option msgid "Display version" msgstr "Vis version" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Tjek ikke efter gammel metadata" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Tjek ikke efter historik som ikke er blevet rapporteret" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Tjek ikke om download af fjerne skal aktiveres" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Tjek ikke eller spørg om genstart, efter opdatering" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Medtag ikke præfiks for logdomæne" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Medtag ikke tidsstempelpræfiks" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Udfør ikke sikkerhedstjek af enhed" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Skriv ikke til historikdatabasen" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Forstår du de konsekvenser som er forbundet med ændring af firmwaregrenen?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Vil du deaktivere funktionaliteten for fremtidige opdateringer?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Vil du opdatere fjernen nu?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Vil du uploade rapporter automatisk for fremtidige opdateringer?" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Færdig!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Nedgrader %s fra %s til %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Nedgraderer firmwaren på en enhed" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Nedgraderer %s fra %s til %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Nedgraderer %s …" #. TRANSLATORS: command description msgid "Download a file" msgstr "Download en fil" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Downloader …" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Dump SMBIOS-data fra en fil" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Varighed" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Angivet ESP var ikke gyldig" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Aktivér understøttelse af firmwareopdateringer på systemer som understøtter det" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Aktivér ny fjern?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Aktivér fjernen?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Aktiveret" msgid "Enabled fwupdate debugging" msgstr "Aktivér fejlsøgning af fwupdate" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Aktiverer en angivet fjern" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Aktivering af funktionen sker på egen risiko. Det betydet at du skal kontakte din oprindelige udstyrsproducent vedrørende eventuelle problemer forårsaget af opdateringerne. Det er kun problemer med selv opdateringsprocessen som skal indsende på $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Aktivering af fjernen sker på egen risiko." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Krypteret" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "Krypteret RAM" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Slet al historik over firmwareopdateringer" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Sletter …" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Afslut efter en lille forsinkelse" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Afslut efter motoren er indlæst" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Eksportér en firmwarefilstruktur til XML" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Udpak en firmwareblob til aftryk" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "FIL" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "FIL [ENHEDS-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "FILNAVN" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "FILNAVN CERTIFIKAT PRIVAT-NØGLE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "FILNAVN ENHED-ALT-NAVN|ENHED-ALT-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "FILNAVN ENHED-ALT-NAVN|ENHED-ALT-ID [AFTRYK-ALT-NAVN|AFTRYK-ALT-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "FILNAVN ENHEDS-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "FILNAVN [ENHEDS-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "FILNAVN [FIRMWARETYPE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "FILNAVN-KILDE FILNAVN-DST [FIRMWARETYPE-KILDE] [FIRMWARE-TYPE-DST]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Mislykkedes" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Kunne ikke anvende opdatering" #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Kunne ikke oprette forbindelse til dæmonen" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Kunne ikke hente afventende enheder" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Kunne ikke installere firmwareopdateringen" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Kunne ikke indlæse lokal dbx" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Kunne ikke indlæse quirks" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Kunne ikke indlæse systemets dbx" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Kunne ikke låse" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Kunne ikke fortolke argumenter" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Kunne ikke fortolke fil" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Kunne ikke fortolke flag for --filter" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Kunne ikke fortolke lokal dbx" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Kunne ikke genstarte" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Kunne ikke indstille splash-tilstand" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Kunne ikke validere ESP-indhold" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Filnavn" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Filnavnets underskrift" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Filnavnets kilde" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Filnavn kræves" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filtrer med et sæt enhedsflag med et ~-præfiks for at udelukke, f.eks. 'intern,~behøver-genstart'" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Grund-URI for firmware" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "D-Bus-tjeneste for firmwareopdatering" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Firmwareopdateringsdæmon" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Firmwareredskab" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Firmwareattest" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Firmware er allerede blokeret" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Firmware er ikke allerede blokeret" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Firmwaremetadata er ikke blevet opdateret i %u dag og kan være forældet." msgstr[1] "Firmwaremetadata er ikke blevet opdateret i %u dage og kan være forældet." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Firmwareopdateringer" msgid "Firmware updates are not supported on this machine." msgstr "Maskinen understøtter ikke firmwareopdateringer." msgid "Firmware updates are supported on this machine." msgstr "Maskinen understøtter firmwareopdateringer." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Firmwareopdateringer deaktiveret; kør 'fwupdmgr unlock' for at aktivere" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Flag" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Gennemtving handlingen ved at afslappe visse runtimetjek" msgid "Force the action ignoring all warnings" msgstr "Gennemtving handlingen og ignorer alle advarsler" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Fundet" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID'er" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Hent alle enhedsflag som understøttes af fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Hent alle enheder som understøtter firmwareopdateringer" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Hent alle aktiverede plugins som er registreret med systemet" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Hent detaljer om en firmwarefil" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Henter de konfigurerede fjerne" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Henter værtens sikkerhedsattributter" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Henter listen over godkendt firmware" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Henter listen over blokerede firmware" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Henter listen over opdateringer for tilsluttet hardware" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Henter resultaterne fra en enhed" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Henter resultaterne fra den sidste opdatering" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWID'ER-FIL" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Hardwaren venter på at bliver gentilkoblet" #. TRANSLATORS: the release urgency msgid "High" msgstr "Høj" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Værtens sikkerheds-ID:" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Inaktiv …" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ignorer strikse SSL-tjek ved download af filer" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Ignorer mislykkede checksum for firmware" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Ignorer mislykkedes firmwarehardware som ikke passer sammen" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignorer sikkerhedstjek af bekræftelse" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Ignorerer strikse SSL-tjeks. Eksportér DISABLE_SSL_STRICT i dit miljø for at gøre det automatisk i fremtiden" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Varighed for installation" msgid "Install signed device firmware" msgstr "Installer enhedsfirmware der er underskrevet" msgid "Install signed system firmware" msgstr "Installer systemfirmware der er underskrevet" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Installer først til forælderenhed" msgid "Install unsigned device firmware" msgstr "Installer enhedsfirmware der ikke er underskrevet" msgid "Install unsigned system firmware" msgstr "Installer systemfirmware der ikke er underskrevet" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Installerer firmware …" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Installerer firmwareopdateringer …" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Installerer på %s …" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Intel BootGuard ACM-beskyttet" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Intel BootGuard OTP-fuse" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Intel BootGuard regler om fejl" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Intel BootGuard bekræftet start" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET aktiv" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET-aktiveret" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Intern enhed" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Ugyldig" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Er i opstartsindlæsertilstand" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Problemstilling" msgstr[1] "Problemstillinger" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "NØGLE,VÆRDI" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Nøglering" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "PLACERING" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Sidst redigeret" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Mindre end et minut tilbage" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licens" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (stabilt firmware)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (testning firmware)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Linux-kerne" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Nedlukning af Linux-kerne" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linux-swap" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Vis poster i dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Vis understøttede firmwareopdateringer" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Vis de tilgængelige firmwaretyper" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Viser filer på ESP'en" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Indlæser …" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Låst" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Lav" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "MEI-fabrikstilstand" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "MEI-tilsidesættelse" msgid "MEI version" msgstr "MEI-version" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Aktivér bestemte plugins manuelt" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Mellem" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Underskrift for metadata" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metadata-URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadata kan hentes fra Linux Vendor Firmware Service." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Minimum version" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Dæmon og klient passer ikke sammen, brug %s i stedet" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Rediger en værdi i dæmonkonfiguration" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Redigerer en angivet fjern" msgid "Modify a configured remote" msgstr "Rediger en konfigureret fjern" msgid "Modify daemon configuration" msgstr "Rediger dæmonkonfiguration" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Overvåg dæmonen for hændelser" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Monterer ESP'en" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Genstart efter installation er nødvendig" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Genstart er nødvendig" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Nedlukning efter installation er nødvendig" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Ny version" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Der er ikke angivet nogen handling!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Ingen nedgraderinger til %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Fandt ingen firmware-id'er" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Der blev ikke fundet nogen hardware med firmware som kan opdateres" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Der blev ikke fundet nogen plugins" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Ingen tilgængelige udgivelser" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Der er ikke nogen metadata da der ikke er aktiveret nogen fjerne." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Ingen tilgængelige fjerne" msgid "No updates available for remaining devices" msgstr "Ingen opdateringer tilgængelige for tilbageværende enheder" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Der blev ikke anvendt nogen opdateringer" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Ikke fundet" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Ikke-understøttet" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "OK" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Vis kun én PCR-værdi" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Kun versionsopgraderinger er tilladte" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Output i JSON-format" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Tilsidesæt standard-ESP-stien" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "STI" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Fortolk og vis deltaljer om en firmwarefil" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Fortolker dbx-opdatering …" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Fortolker systemets dbx …" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Adgangskode" msgid "Payload" msgstr "Nyttelast" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Afventer" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Procent fuldført" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Udfør handling?" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Indtast venligst et tal nummer 0 og %u: " #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Plugin-afhængigheder mangler" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Præopstart DMA-beskyttelse" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Forrige version" msgid "Print the version number" msgstr "Udskriv versionsnummer" msgid "Print verbose debug statements" msgstr "Udskriv uddybende fejlsøgningsudsagn" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritet" msgid "Proceed with upload?" msgstr "Fortsæt med upload?" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Proprietær" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Forespørg om understøttelse af firmwareopdatering" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "FJERN-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "FJERN-ID NØGLE VÆRDI" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Læs en firmwareblob fra en enhed" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Læs firmware fra enhed ind i en fil" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Læs firmware fra en partition ind i en fil" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Læser fra %s …" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Læser …" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Genstarter …" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Opdater metadata fra fjernserver" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Geninstaller %s til %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Geninstaller den nuværende firmware på enheden" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Geninstaller firmware på en enhed" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Geninstallerer %s med %s... " #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Udgivelsesgren" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Fjern-id" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Erstat data i en eksisterende firmwarefil" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Rapport-URI" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Rapporteret til fjernserver" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "Krævede efivarfs-filsystem blev ikke fundet" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "Det krævede hardware blev ikke fundet" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Kræver en opstartsindlæser" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Kræver internetforbindelse" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Genstart nu?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Genstart dæmonen så ændringerne kan træde i kraft?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Genstarter enhed …" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Returner alle maskinens hardware-id'er" #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Kør `fwupdmgr get-upgrades` for mere information." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Kør oprydningsrutinen for pluginkomposition når install-blob bruges" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Kør forberedelsesrutinen for pluginkomposition når install-blob bruges" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "Kørende kerne er for gammel" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Suffiks for runtime" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "SPI BIOS-beskriver" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "SPI BIOS-region" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI-lås" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI-skriv" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "UNDERSYSTEM DRIVER [ENHEDS-ID|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Gem en fil der gør det muligt at generere hardware-id'er" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Planlægning af installation ved næste genstart, når det er muligt" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Planlægger …" #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Se %s for mere information." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Valgte enhed" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Valgte diskområde" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Serienummer" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Indstil fejlsøgningsflaget under opdatering" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Indstiller listen over godkendt firmware" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Del historik over firmware med udviklerne" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Vis alle resultater" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Vis klient- og dæmonversioner" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Vis uddybende information for dæmon for et bestemt domæne" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Vis fejlsøgningsinformation for alle domæner" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Vis fejlsøgningsindstillinger" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Vis enheder som ikke kan opdateres" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Vis ekstra fejlsøgningsinformation" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Vis historik over firmwareopdateringer" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Vis den udregnede version af dbx'en" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Vis fejlsøgningsloggen fra den opdatering der blev forsøgt sidst" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Vis informationen om firmwareopdateringsstatus" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Luk ned nu?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Underskriv en firmware med en ny nøgle" msgid "Sign data using the client certificate" msgstr "Underskriv data med klientcertifikat" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Underskriv data med klientcertifikat" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Underskriv den uploadede data med klientcertifikatet" msgid "Signature" msgstr "Underskrift" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Størrelse" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Kilde" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Angiv producent-/produkt-id('er) for DFU-enhed" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Angiv dbx-databasefilen" msgid "Specify the number of bytes per USB transfer" msgstr "Angiv antal bytes pr. USB-overførsel" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Lykkedes" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Det lykkedes at aktivere alle enheder" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Det lykkedes at deaktivere fjern" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Det lykkedes at downloade ny metadata: " #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Det lykkedes at aktivere og opdatere fjernen" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Det lykkedes at aktivere fjern" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Det lykkedes at installere firmware" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Det lykkedes at redigere konfigurationsværdi" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Det lykkedes at redigere fjern" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Det lykkedes at opdatere metadata manuelt" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Opdatering af enhedens tjeksumme lykkedes" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Det lykkedes at uploade %u rapport" msgstr[1] "Det lykkedes at uploade %u rapporter" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Bekræftelse af enhedens tjeksumme lykkedes" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Opsummering" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Understøttet" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Understøttes på fjernserver" #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Suspender-til-inaktiv" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Suspender-til-ram" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Skift gren fra %s til %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Skift firmwaregrenen på enheden" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "Systemet kræver ekstern strømkilde" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEKST" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "TPM PCR0-rekonstruktion" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Tainted" msgid "Target" msgstr "Mål" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Test en enhed med et JSON-manifest" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS'en er en gratis tjeneste der opererer som en selvstændig juridisk enhed og har ingen forbindelse med $OS_RELEASE:NAME$. Din distributør har måske ikke bekræftet nogen af firmwareopdateringerne for kompatibilitet med dit system eller tilsluttede enheder. Al firmware leveres kun af den oprindelige udstyrsproducent." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "TPM PCR0'en er ikke magen til rekonstruktionen." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "Dæmonen har indlæst tredjepartskode og understøttes ikke længere af opstrømsudviklerne!" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "Firmwaren fra %s leveres ikke af hardwareleverandøren %s." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "Systemets ur er ikke blevet indstillet korrekt og download af filer kan mislykkes." #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Der er ikke nogen blokerede firmwarefiler" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Der er ikke nogen godkendt firmware." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Pakken er ikke blevet valideret — den virker måske ikke ordentligt." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Programmet virker måske kun korrekt som root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Fjernen indeholder firmware som der ikke er embargo på, men som stadigvæk testes af hardwareproducenten. Du skal sikre dig at du har en manuel måde til at nedgradere firmwaren på hvis firmwareopdateringen mislykkedes." #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Systemet har problemer med HSI-runtime." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Systemet har et lavt HSI-sikkerhedsniveau." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Værktøjet giver en administrator mulighed for at anvende UEFI-dbx-opdateringer." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Værktøjet giver en administrator mulighed for at fejlfinde UpdateCapsule-handling." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Værktøjet giver en administrator mulighed for at sætte i kø og styre fwupd-dæmonen, hvorved denne kan udføre handlinger såsom at installere eller nedgradere firmware." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Værktøjet giver en administrator mulighed for at bruge fwupd-plugins uden at installere dem på værtssystemet." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Værktøjet kan kun bruges af root-brugeren" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Værktøjet læser og fortolker TPM-hændelsesloggen fra systemfirmwaren." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Forbigående mislykkedes" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Type" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "UEFI ESP-partition ikke registreret eller konfigureret" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI-firmwareredskab" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "UEFI-kapselopdateringer ikke tilgængelige eller aktiveret i firmwareopsætning" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "UEFI-dbx-redskab" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "UEFI-firmware kan ikke opdateres i forældet BIOS-tilstand" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "UEFI-platformsnøgle" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "UEFI sikkeropstart" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Kan ikke oprette forbindelse til tjeneste" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Fjern binding af nuværende driver" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Fjerner blokering af firmware:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Fjerner blokering af en bestemt firmware fra at blive installeret" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Dekrypteret" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Ukendt" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Ukendt enhed" msgid "Unlock the device to allow access" msgstr "Lås enheden op for at tillade adgang" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Låst op" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Låser op for enheden for at få adgang til firmwaren" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Afmonterer ESP'en" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Fjern fejlsøgningsflaget under opdatering" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Ikke-understøttet dæmonversion %s, klientversionen er %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Untainted" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Kan opdateres" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Fejl ved opdatering" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Opdateringsmeddelelse" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Opdateringstilstand" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Opdateringer som mislykkes er et velkendt problem. Besøg URL'en for mere information:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Opdater nu?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Opdatering kræver en genstart" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Opdater den gemte kryptografiske hash med indholdet fra den nuværende ROM" msgid "Update the stored device verification information" msgstr "Opdaterer de gemte informationer om enhedsverifikation" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Opdater det gemte metadata med det nuværende indhold" msgid "Updating" msgstr "Opdaterer" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Opdaterer %s fra %s til %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Opdaterer %s …" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Opgrader %s fra %s til %s?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Upload af firmwarerapporter hjælper hardwareproducenter til hurtigt at identificere opdateringer som fejlede og lykkedes på rigtigt hardware." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Vigtighed" #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Brug fwupdtool --help for hjælp" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Brug quirk-flag ved installation af firmware" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Brugeren er blevet underrettet" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Brugernavn" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Gyldig" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Validerer ESP-indhold …" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variant" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Producent" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verificerer …" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Version" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Venter …" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Hold øje med hardwareændringer" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Skriv firmware fra fil ind i enhed" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Skriv firmware fra fil ind i en partition" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Skriver fil:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Skriver …" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Din distributør har måske ikke verificeret kompatibiliteten af firmwareopdateringerne med dit system eller tilsluttede enheder." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Der er mulighed for at din hardware kan tage skade hvis firmwaren bruges og installation af udgivelsen kan ugyldiggøre garantien med %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[CHECKSUM]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[ENHEDS-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[ENHEDS-ID|GUID] [GREN]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[FIL FILUNDERSKRIFT FJERN-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[FILNAVN1] [FILNAVN2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SMBIOS-FIL|HWID'ER-FIL]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "standard" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "fwupd TPM-hændelseslogredskab" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd-plugins" fwupd-1.9.16/po/de.po000066400000000000000000003572061460375044200143250ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Christian , 2023 # Ettore Atalan , 2018,2021-2023 # Marco Tedaldi , 2015 # Wolfgang Stöggl , 2015 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: German (http://app.transifex.com/freedesktop/fwupd/language/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f Minute verbleibt" msgstr[1] "%.0f Minuten verbleiben" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "%s BMC-Aktualisierung" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "%s Akku-Aktualisierung" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "%s CPU-Microcode-Aktualisierung" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "%s Kameraaktualisierung" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "%s Konfigurationsaktualisierung" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s Aktualisierung der ME für Verbraucher" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s Controller-Aktualisierung" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s Aktualisierung der ME für Unternehmen" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s Geräteaktualisierung" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "%s Bildschirmaktualisierung" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "%s Dock-Aktualisierung" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "%s Laufwerksaktualisierung" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s Eingebetteter-Controller-Aktualisierung" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "%s Fingerabdruckleser-Aktualisierung" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "%s Flash-Laufwerk-Aktualisierung" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "%s GPU-Aktualisierung" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "%s Grafiktablett-Aktualisierung" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "%s Tastaturaktualisierung" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s ME-Aktualisierung" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "%s Mausaktualisierung" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "%s Netzwerkschnittstellenaktualisierung" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "%s SSD-Aktualisierung" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "%s Speicher-Controller-Aktualisierung" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s Systemaktualisierung" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "%s TPM-Aktualisierung" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s Thunderbolt-Controller-Aktualisierung" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "%s Tastfeld-Aktualisierung" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "%s USB-Dock-Aktualisierung" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "%s USB-Empfänger-Aktualisierung" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s Aktualisierung" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s und alle angeschlossenen Geräte sind während der Aktualisierung möglicherweise nicht nutzbar." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "%s erschienen: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "%s geändert: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "%s verschwunden: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s ist derzeit nicht aktualisierbar" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s Herstellungsmodus" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s muss während der gesamten Dauer der Aktualisierung angeschlossen bleiben, um Schäden zu vermeiden." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s muss während der gesamten Dauer der Aktualisierung an eine Stromquelle angeschlossen bleiben, um Schäden zu vermeiden." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s überschreiben" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s Version" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u Tag" msgstr[1] "%u Tage" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "Für %u Gerät ist eine Firmware-Aktualisierung verfügbar." msgstr[1] "Für %u Geräte ist eine Firmware-Aktualisierung verfügbar." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u Gerät ist nicht die beste bekannte Konfiguration." msgstr[1] "%u Geräte sind nicht die beste bekannte Konfiguration." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u Stunde" msgstr[1] "%u Stunden" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u lokales Gerät unterstützt" msgstr[1] "%u lokale Geräte unterstützt" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u Minute" msgstr[1] "%u Minuten" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u Sekunde" msgstr[1] "%u Sekunden" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (Schwellenwert %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(veraltet)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "Ein TPM PCR ist jetzt ein ungültiger Wert" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "AMD-Firmware-Wiederholungsschutz" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "AMD-Firmware-Schreibschutz" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "Rückrollschutz des AMD Sicherheitsprozessors" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "ARCHIVE FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATFILE]" msgstr "ARCHIV FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCAT-DATEI]" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Aktion erforderlich:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Geräte aktivieren" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Ausstehende Geräte aktivieren" msgid "Activate the new firmware on the device" msgstr "Die neue Firmware auf dem Gerät aktivieren" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Firmware-Aktualisierung wird aktiviert" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Firmware-Aktualisierung wird aktiviert für" #. TRANSLATORS: command description msgid "Adds devices to watch for future emulation" msgstr "Fügt Geräte hinzu, die für künftige Emulationen zu beobachten sind" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Alter" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Zustimmen und die Gegenstelle aktivieren?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Verweis auf %s" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "Alle TPM PCRs sind jetzt gültig" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Alle TPM PCRs sind gültig" #. TRANSLATORS: an application is preventing system updates msgid "All devices are prevented from update by system inhibit" msgstr "Alle Geräte werden durch eine Systemblockierung an der Aktualisierung gehindert" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Alle Geräte desselben Typs werden zur gleichen Zeit aktualisiert" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Herabstufung von Firmware-Versionen zulassen" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Neuinstallation vorhandener Firmware-Versionen erlauben" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Wechsel des Firmware-Zweigs erlauben" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Alternativer Zweig" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "Eine Aktualisierung ist in Bearbeitung" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Ein Neustart ist erforderlich, um eine Aktualisierung abzuschließen." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Für eine Aktualisierung muss das System zum Abschluss heruntergefahren werden." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Alle Fragen mit Ja beantworten" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Firmware-Aktualisierungen anwenden" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Aktualisierung auch dann anwenden, wenn dies nicht empfohlen wird" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Aktualisierungsdateien anwenden" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Aktualisierung wird angewendet …" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Genehmigte Firmware:" msgstr[1] "Genehmigte Firmware:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Nächstes Mal wieder nachfragen?" #. TRANSLATORS: command description msgid "Asks the daemon to quit" msgstr "Fordert den Daemon zum Beenden auf" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "An den Firmware-Modus anhängen" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Authentifizierung …" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Authentifizierungsdetails sind erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Für eine Herabstufung der Firmware auf einem entfernbaren Gerät ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Für eine Herabstufung der Firmware auf diesem System ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to fix a host security issue" msgstr "Legitimierung ist erforderlich zum Beheben eines Sicherheitsproblems der Maschine" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Zum Ändern der BIOS-Einstellungen ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Zum Ändern einer konfigurierten Gegenstelle, die für Firmware-Aktualisierungen verwendet wird, ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Legitimierung ist zum Verändern der Daemon-Konfiguration notwendig" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "Zum Lesen der BIOS-Einstellungen ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Zum Festlegen der Liste der zugelassenen Firmware ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Zum Signieren von Daten mit dem Client-Zertifikat ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Für den Wechsel zur neuen Firmware-Version ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to undo the fix for a host security issue" msgstr "Legitimierung ist erforderlich zum Zurücknehmen der Beheben eines Sicherheitsproblems der Maschine" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Legitimation ist zum Entsperren eines Geräts erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Für die Aktualisierung der Firmware auf einem entfernbaren Gerät ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Für die Aktualisierung der Firmware auf diesem System ist eine Authentifizierung erforderlich" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Eine Authentifizierung ist erforderlich, um die gespeicherten Prüfsummen für das Gerät zu aktualisieren" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Automatische Berichterstattung" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Jedes Mal automatisch hochladen?" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS Firmware Updates" msgstr "BIOS Firmware-Aktualisierungen" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "BIOS-Rückrollschutz" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS firmware updates" msgstr "BIOS Firmware-Aktualisierungen" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "BIOS-Rückrollschutz" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "BIOS-Aktualisierungen werden über LVFS oder Windows Update bereitgestellt" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BUILDER-XML DATEINAME-ZIEL" msgid "BYTES" msgstr "BYTES" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Akku" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Neuen Kernel-Treiber einbinden" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Blockierte Firmware-Dateien:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Blockierte Version" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Firmware wird blockiert:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Blockiert die Installation einer bestimmten Firmware" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Bootloader-Version" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Zweig" #. TRANSLATORS: command description msgid "Build a cabinet archive from a firmware blob and XML metadata" msgstr "Cabinet-Archiv aus einem Firmware-Blob und XML-Metadaten erstellen" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Eine Firmware-Datei bauen" #. TRANSLATORS: longer description msgid "CPU Microcode must be updated to mitigate against various information-disclosure security issues." msgstr "Der CPU-Mikrocode muss aktualisiert werden, um verschiedene Sicherheitsprobleme bei der Informationspreisgabe abzumildern." #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Abbrechen" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Abgebrochen" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Eine bereits angewandte dbx-Aktualisierung kann nicht angewendet werden." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Aktualisierungen können nicht auf Live-Medien angewendet werden" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Geändert" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Überprüft, ob der kryptografische Hash mit der Firmware übereinstimmt" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Prüfsumme" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Zweig auswählen" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Gerät auswählen" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Firmware auswählen" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Version auswählen" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Wählen Sie den ESP aus:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Datenträger auswählen" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Bereinigt die Ergebnisse der letzten Aktualisierung" #. TRANSLATORS: error message msgid "Command not found" msgstr "Befehl nicht gefunden" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Von der Gemeinschaft unterstützt" #. TRANSLATORS: title prefix for the BIOS settings dialog msgid "Configuration Change Suggested" msgstr "Vorgeschlagene Konfigurationsänderung" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "Konfiguration ist nur für den Systemadministrator lesbar" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Eine Firmware-Datei konvertieren" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Erstellt" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Kritisch" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Kryptografische Hash-Verifizierung ist verfügbar" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Aktueller Wert" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Aktuelle Version" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "GERÄTEKENNUNG|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU-Dienstprogramm" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Debug Optionen" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Entpacken …" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Beschreibung" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Vom Bootloader-Modus lösen" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Details" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "Von der besten bekannten Konfiguration abweichen?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Geräte-Bitschalter" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Gerätekennung" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Gerät hinzugefügt:" #. TRANSLATORS: the device is already connected msgid "Device already exists" msgstr "Gerät existiert bereits" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "Geräteakku ist zu schwach" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "Geräteakkuleistung ist zu gering (%u%%, erfordert %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Das Gerät kann sich nach Fehlern beim Aufspielen wiederherstellen" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "Gerät kann nicht verwendet werden, wenn der Deckel geschlossen ist" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Gerät geändert:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "Geräteemulation ist nicht aktiviert." #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Versionsprüfung der Geräte-Firmware ist erforderlich" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "Gerät wird emuliert" #. TRANSLATORS: device cannot be interrupted, for instance taking a phone call msgid "Device is in use" msgstr "Gerät ist in Gebrauch" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Gerät ist gesperrt" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Das Gerät muss alle bereitgestellten Versionen nacheinander installieren." #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Gerät ist nicht erreichbar" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "Gerät ist unerreichbar oder außerhalb der Funkreichweite" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Das Gerät ist während der Dauer der Aktualisierung nutzbar" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "Gerät wartet auf die Anwendung der Aktualisierung" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Gerät entfernt:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "Gerät muss an eine Wechselstromquelle angeschlossen werden" #. TRANSLATORS: device does not have a display connected msgid "Device requires a display to be plugged in" msgstr "Gerät benötigt einen angeschlossenen Bildschirm" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "Gerät erfordert eine Softwarelizenz für die Aktualisierung" #. TRANSLATORS: longer description msgid "Device software updates are provided for this device." msgstr "Für dieses Gerät werden Gerätesoftware-Aktualisierungen bereitgestellt." #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Gerät führt Aktualisierungen in Etappen durch" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "Gerät unterstützt den Wechsel zu einem anderen Zweig der Firmware" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Geräteaktualisierungsmethode" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Geräteaktualisierung erfordert Aktivierung" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Das Gerät sichert die Firmware vor der Installation" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Gerät wird nach Abschluss der Aktualisierung nicht wieder angezeigt" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Erfolgreich aktualisierte Geräte:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Nicht korrekt aktualisierte Geräte:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Geräte mit keinen verfügbaren Firmware-Aktualisierungen: " #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Geräte mit der neuesten verfügbaren Firmware-Version:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "Es wurden keine Geräte mit passenden GUIDs gefunden" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Deaktiviert" msgid "Disabled fwupdate debugging" msgstr "fwupdate-Defektlokalisierung deaktivieren" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Deaktiviert eine gegebene Gegenstelle" #. TRANSLATORS: command line option msgid "Display version" msgstr "Version anzeigen" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Distribution" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Nicht auf alte Metadaten prüfen" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Nicht auf nicht erfassten Verlauf prüfen" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Nicht prüfen, ob Gegenstellen zum Herunterladen aktiviert werden sollen" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Nach der Aktualisierung nicht prüfen oder zum Neustart auffordern" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Zeitstempel-Präfix nicht einbeziehen" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Keine Gerätesicherheitsüberprüfungen durchführen" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "Nicht nach Geräten fragen" #. TRANSLATORS: command line option msgid "Do not prompt to fix security issues" msgstr "Nicht zur Behebung von Sicherheitsproblemen auffordern" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "Beim Parsen die Firmware nicht durchsuchen" #. TRANSLATORS: warning message shown after update has been scheduled msgid "Do not turn off your computer or remove the AC adaptor while the update is in progress." msgstr "Schalten Sie Ihren Rechner nicht aus und entfernen Sie nicht den Netzadapter, während die Aktualisierung durchgeführt wird." #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Nicht in die Verlaufsdatenbank schreiben" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Verstehen Sie die Folgen einer Änderung des Firmware-Zweigs?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Möchten Sie diese Funktion für zukünftige Aktualisierungen deaktivieren?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "Möchten Sie sie jetzt aktivieren?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Möchten Sie diese Gegenstelle jetzt auffrischen?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Möchten Sie Berichte für künftige Aktualisierungen automatisch hochladen?" #. TRANSLATORS: command line option msgid "Don't prompt for authentication (less details may be shown)" msgstr "Nicht zur Authentifizierung auffordern (es werden möglicherweise weniger Details angezeigt)" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Fertig." #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "%s von %s auf %s herabstufen?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Stuft die Firmware auf einem Gerät herab" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Herabstufung für %s von %s auf %s wird eingespielt…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "%s wird herabgestuft …" #. TRANSLATORS: command description msgid "Download a file" msgstr "Herunterladen einer Datei" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Herunterladen …" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "SMBIOS-Daten aus einer Datei ausgeben" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Dauer" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Das angegebene ESP war nicht gültig" #. TRANSLATORS: longer description msgid "Each system should have tests to ensure firmware security." msgstr "Jedes System sollte Tests haben, um die Sicherheit der Firmware zu gewährleisten." #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "Ein Gerät mithilfe eines JSON-Manifests emulieren" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Emuliert" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Emulierter Host" msgid "Enable" msgstr "Aktivieren" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Firmware-Aktualisierungsunterstützung auf unterstützten Systemen aktivieren" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Neue Gegenstelle aktivieren?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Diese Gegenstelle aktivieren?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Aktiviert" msgid "Enabled fwupdate debugging" msgstr "fwupdate-Defektlokalisierung aktivieren" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Aktiviert, wenn die Hardware passt" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Aktiviert eine gegebene Gegenstelle" #. TRANSLATORS: longer description msgid "Enabling firmware updates for the BIOS allows fixing security issues." msgstr "Das Aktivieren von Firmware-Aktualisierungen für das BIOS ermöglicht das Beheben von Sicherheitsproblemen." msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Die Aktivierung dieser Funktionalität erfolgt auf eigene Gefahr, d.h. Sie müssen sich bei Problemen, die durch diese Aktualisierungen verursacht werden, an Ihren Erstausrüster wenden. Nur Probleme mit dem Aktualisierungsprozess selbst sollten unter $OS_RELEASE:BUG_REPORT_URL$ eingereicht werden." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Die Aktivierung dieser Gegenstelle erfolgt auf eigene Gefahr." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Verschlüsselt" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "Verschlüsselter RAM" msgid "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." msgstr "Verschlüsselter RAM macht es unmöglich, dass im Gerätespeicher gespeicherte Informationen gelesen werden können, wenn der Speicherchip entfernt und darauf zugegriffen wird." #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Lebensende" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Aufzählung" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Gesamten Firmware-Aktualisierungsverlauf löschen" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Löschen …" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Verlassen nach einer kurzen Verzögerung" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Nach dem Laden der Engine beenden" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Eine Firmware-Dateistruktur nach XML exportieren" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Einen Firmware-Blob in Abbilder extrahieren" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "DATEI" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "DATEI [GERÄTEKENNUNG|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "DATEINAME" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "DATEINAME ZERTIFIKAT PRIVATER-SCHLÜSSEL" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "DATEINAME GERÄT-ALT-NAME|GERÄT-ALT-KENNUNG" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "DATEINAME GERÄT-ALT-NAME|GERÄT-ALT-KENNUNG [ABBILD-ALT-NAME|ABBILD-ALT-KENNUNG]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "DATEINAME GERÄTEKENNUNG" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "DATEINAME ADRESSABSTAND DATEN [FIRMWARE-TYP]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "DATEINAME [GERÄTEKENNUNG|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "DATEINAME [FIRMWARE-TYP]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "DATEINAME-QUELLE DATEINAME-ZIEL [FIRMWARE-TYP-QUELLE] [FIRMWARE-TYP-ZIEL]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "DATEINAME|PRÜFSUMME1[,PRÜFSUMME2][,PRÜFSUMME3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Fehlgeschlagen" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Anwenden der Aktualisierung ist fehlgeschlagen" #. TRANSLATORS: error message for Windows msgid "Failed to connect to Windows service, please ensure it's running." msgstr "Verbindung zum Windows-Dienst fehlgeschlagen, bitte stellen Sie sicher, dass er läuft." #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Verbindung mit dem Daemon ist fehlgeschlagen" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Abrufen ausstehender Geräte ist fehlgeschlagen" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Installieren der Firmware-Aktualisierung ist fehlgeschlagen" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Laden der lokalen dbx ist fehlgeschlagen" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Laden der Macken ist fehlgeschlagen" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Laden der System-dbx ist fehlgeschlagen" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Sperren ist fehlgeschlagen" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Parsen der Argumente ist fehlgeschlagen" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Parsen der Datei ist fehlgeschlagen" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Das Auswerten der Schalter von »--filter« ist fehlgeschlagen" msgid "Failed to parse flags for --filter-release" msgstr "Das Auswerten der Schalter von »--filter-release« ist fehlgeschlagen" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Parsen der lokalen dbx ist fehlgeschlagen" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Neustart ist fehlgeschlagen" #. TRANSLATORS: a feature is something like "can show an image" msgid "Failed to set front-end features" msgstr "Fehler beim Festlegen von Front-End-Funktionen" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Festlegen des Begrüßungsmodus ist fehlgeschlagen" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Validierung des ESP-Inhalts ist fehlgeschlagen" #. TRANSLATORS: item is FALSE msgid "False" msgstr "Falsch" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Dateiname" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Dateinamen-Signatur" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Dateinamen-Quelle" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Dateiname erforderlich" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Firmware-Attestierung" #. TRANSLATORS: longer description msgid "Firmware Attestation checks device software using a reference copy, to make sure that it has not been changed." msgstr "Bei der Firmware-Attestierung wird die Gerätesoftware anhand einer Referenzkopie überprüft, um sicherzustellen, dass sie nicht geändert wurde." #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Firmware-BIOS-Deskriptor" #. TRANSLATORS: longer description msgid "Firmware BIOS Descriptor protects device firmware memory from being tampered with." msgstr "Firmware-BIOS-Deskriptor schützt den Firmware-Speicher des Geräts vor Manipulationen." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Firmware-BIOS-Region" #. TRANSLATORS: longer description msgid "Firmware BIOS Region protects device firmware memory from being tampered with." msgstr "Firmware-BIOS-Region schützt den Firmware-Speicher des Geräts vor Manipulationen." #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Firmware Basis-URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "D-Bus-Dienst für Firmware-Aktualisierung" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Hintergrundprogramm für Firmware-Aktualisierung" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Firmware-Aktualisierungsprogramm-Verifizierung" msgid "Firmware Updater Verification checks that software used for updating has not been tampered with." msgstr "Die Firmware-Aktualisierungsprogramm-Verifizierung prüft, ob die für die Aktualisierung verwendete Software nicht manipuliert wurde." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Firmware-Aktualisierungen" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Firmware-Dienstprogramm" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Firmware-Schreibschutz" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Firmware-Schreibschutz-Sperre" #. TRANSLATORS: longer description msgid "Firmware Write Protection protects device firmware memory from being tampered with." msgstr "Firmware-Schreibschutz schützt den Firmware-Speicher des Geräts vor Manipulationen." #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Firmware-Attestierung" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Firmware ist bereits blockiert" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Firmware ist nicht bereits blockiert." #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Firmware-Metadaten wurden seit %u Tag nicht aktualisiert und sind möglicherweise nicht auf dem neuesten Stand." msgstr[1] "Firmware-Metadaten wurden seit %u Tagen nicht aktualisiert und sind möglicherweise nicht auf dem neuesten Stand." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Firmware-Aktualisierungen" msgid "Firmware updates are not supported on this machine." msgstr "Firmware-Aktualisierungen werden auf diesem System nicht unterstützt." msgid "Firmware updates are supported on this machine." msgstr "Firmware-Aktualisierungen werden auf diesem System unterstützt." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Firmware-Aktualisierungen ist deaktiviert; zum Aktivieren »fwupdmgr unlock« ausführen" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fix reverted successfully" msgstr "Korrektur erfolgreich zurückgenommen" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fixed successfully" msgstr "Erfolgreich korrigiert" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Bitschalter" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Aktion durch Lockerung einiger Laufzeitüberprüfungen erzwingen" msgid "Force the action ignoring all warnings" msgstr "Aktion erzwingen, bei der alle Warnungen ignoriert werden" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Gefunden" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Vollständige Festplattenverschlüsselung erkannt" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "Die Geheimnisse der vollständigen Festplattenverschlüsselung können bei der Aktualisierung ungültig werden" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "Abgesicherte Plattform" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Abgesicherte Plattform" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUIDs" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "GUID|DEVICE-ID" msgstr "GUID|GERÄTEKENNUNG" msgid "Get BIOS settings" msgstr "BIOS-Einstellungen abrufen" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Alle von fwupd unterstützten Geräte-Bitschalter abrufen" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Alle Geräte ermitteln, die Firmware-Aktualisierungen unterstützen" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Alle aktivierten und im System registrierten Plugins ermitteln" #. TRANSLATORS: command description msgid "Get device report metadata" msgstr "Gerätebericht-Metadaten abrufen" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Ermittelt Details über eine Firmware-Datei" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Ruft die konfigurierten Gegenstellen ab" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Ruft die Sicherheitsattribute des Hosts ab" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Holt die Liste der genehmigten Firmware" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Holt die Liste der blockierten Firmware" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Ermittelt die Liste der Aktualisierungen für angeschlossene Hardware" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Ermittelt die Versionen für ein Gerät" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Ermittelt die Ergebnisse der letzten Aktualisierung" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWIDS-DATEI" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Hardware wartet darauf, wieder angeschlossen zu werden" #. TRANSLATORS: the release urgency msgid "High" msgstr "Hoch" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Host-Sicherheitsereignisse" #. TRANSLATORS: error message for unsupported feature #, c-format msgid "Host Security ID (HSI) is not supported" msgstr "Host-Sicherheitskennung (HSI) wird nicht unterstützt" #. TRANSLATORS: success, so say thank you to the user msgid "Host Security ID attributes uploaded successfully, thanks!" msgstr "Die Attribute der Sicherheitskennung des Hosts wurden erfolgreich hochgeladen, danke!" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Host-Sicherheitskennung:" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "INHIBIT-ID" msgstr "BLOCKIERUNGSKENNUNG" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "IOMMU-Schutz" #. TRANSLATORS: longer description msgid "IOMMU Protection prevents connected devices from accessing unauthorized parts of system memory." msgstr "IOMMU-Schutz verhindert, dass angeschlossene Geräte auf nicht autorisierte Teile des Systemspeichers zugreifen." #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "IOMMU-Geräteschutz deaktiviert" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "IOMMU-Geräteschutz aktiviert" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Bereit …" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Strenge SSL-Prüfungen beim Herunterladen von Dateien ignorieren" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Fehlerhafte Firmware-Prüfsummen ignorieren" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Firmware-Hardware-Nichtübereinstimmungsfehler ignorieren" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Validierungssicherheitsüberprüfungen ignorieren" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Strenge SSL-Prüfungen werden ignoriert; um dies in Zukunft automatisch zu tun, exportieren Sie DISABLE_SSL_STRICT in Ihrer Umgebung" #. TRANSLATORS: the inhibit ID is a short string like dbus-123456 #, c-format msgid "Inhibit ID is %s." msgstr "Blockierungskennung lautet %s." #. TRANSLATORS: command description msgid "Inhibit the system to prevent upgrades" msgstr "System blockieren, um Aktualisierungen zu verhindern" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Installationsdauer" #. TRANSLATORS: command description msgid "Install a firmware file in cabinet format on this hardware" msgstr "Eine Firmware-Datei im Cabinet-Format auf dieser Hardware installieren" #. TRANSLATORS: command description msgid "Install a raw firmware blob on a device" msgstr "Rohen Firmware-Blob auf einem Gerät installieren" #. TRANSLATORS: command description msgid "Install a specific firmware file on all devices that match" msgstr "Bestimmte Firmware-Datei auf allen zutreffenden Geräten installieren" msgid "Install old version of signed system firmware" msgstr "Alte Version der signierten System-Firmware installieren" msgid "Install old version of unsigned system firmware" msgstr "Alte Version der nicht-signierten System-Firmware installieren" msgid "Install signed device firmware" msgstr "Signierte Geräte-Firmware installieren" msgid "Install signed system firmware" msgstr "Signierte System-Firmware installieren" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Zuerst auf dem übergeordneten Gerät installieren" msgid "Install unsigned device firmware" msgstr "Nicht-signierte Geräte-Firmware installieren" msgid "Install unsigned system firmware" msgstr "Nicht-signierte System-Firmware installieren" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Firmware wird installiert …" #. TRANSLATORS: stay on one firmware version unless the new version is #. explicitly #. * specified msgid "Installing a specific release is explicitly required" msgstr "Die Installation einer bestimmten Version ist ausdrücklich erforderlich" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Firmware-Aktualisierung wird installiert …" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Wird auf %s installiert …" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "Durch die Installation dieser Aktualisierung kann auch die Gerätegarantie erlöschen." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Ganzzahl" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "Geschützt mit Intel BootGuard ACM" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Intel BootGuard ACM-geschützt" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Intel BootGuard-Fehlerrichtlinie" msgid "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." msgstr "Die Intel BootGuard-Fehlerrichtlinie stellt sicher, dass das Gerät nicht weiter startet, wenn seine Gerätesoftware manipuliert wurde." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Intel BootGuard-Sicherung" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Einmalig programmierbare Intel BootGuard-Sicherung" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Verifizierter Start mit Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Intel BootGuard-Fehlerrichtlinie" #. TRANSLATORS: longer description msgid "Intel BootGuard prevents unauthorized device software from operating when the device is started." msgstr "Intel BootGuard verhindert, dass nicht autorisierte Gerätesoftware beim Start des Geräts ausgeführt wird." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Verifizierter Start mit Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET aktiv" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET aktiviert" #. TRANSLATORS: longer description msgid "Intel Control-Flow Enforcement Technology detects and prevents certain methods for running malicious software on the device." msgstr "»Intel Control-Flow Enforcement Technology« erkennt und verhindert bestimmte Möglichkeiten, bösartige Software auf dem Gerät auszuführen" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS Mitigation" msgstr "Intel GDS-Milderung" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS mitigation" msgstr "Intel GDS-Milderung" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Herstellungsmodus der Intel Management Engine" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Übergehen der »Intel Management Engine«" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Version der Intel Management Engine" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Internes Gerät" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Ungültig" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Ungültige Argumente" #. TRANSLATORS: error message msgid "Invalid arguments, expected GUID" msgstr "Ungültige Argumente, erwartete GUID" #. TRANSLATOR: This is the error message for #. * incorrect parameter msgid "Invalid arguments, expected an AppStream ID" msgstr "Ungültige Argumente. Es wurde eine AppStream-Kennung erwartet." #. TRANSLATORS: error message msgid "Invalid arguments, expected at least ARCHIVE FIRMWARE METAINFO" msgstr "Ungültige Argumente, erwartete wenigstens ARCHIV FIRMWARE METAINFO" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Ist Zurückstufung" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Ist im Bootloader-Modus" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Ist Höherstufung" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Anliegen" msgstr[1] "Anliegen" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "SCHLÜSSEL,WERT" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "Kernel ist nicht mehr verschmutzt" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "Kernel ist verschmutzt" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Kernel-Sperrung deaktiviert" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Kernel-Sperrung aktiviert" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Schlüsselbund" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "ORT" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Zuletzt geändert" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Weniger als eine Minute verbleiben" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Lizenz" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Linux-Kernel-Sperrung" #. TRANSLATORS: longer description msgid "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgstr "Die Abriegelung des Linux-Kernels verhindert, dass Administratorkonten (root) auf kritische Teile der Systemsoftware zugreifen und diese ändern können." msgid "Linux Kernel Swap temporarily saves information to disk as you work. If the information is not protected, it could be accessed by someone if they obtained the disk." msgstr "Die Auslagerung des Linux-Kernels speichert temporär Informationen auf der Festplatte, während Sie arbeiten. Wenn die Informationen nicht geschützt sind, könnte jemand auf sie zugreifen, wenn er die Festplatte erbeutet." #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Linux-Kernel-Verifizierung" msgid "Linux Kernel Verification makes sure that critical system software has not been tampered with. Using device drivers which are not provided with the system can prevent this security feature from working correctly." msgstr "Die Linux-Kernel-Verifizierung stellt sicher, dass kritische Systemsoftware nicht manipuliert wurde. Die Verwendung von Gerätetreibern, die nicht im Lieferumfang des Systems enthalten sind, kann verhindern, dass diese Sicherheitsfunktion korrekt funktioniert." #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Linux-Auslagerung" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (stabile Firmware)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (Test-Firmware)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Linux-Kernel" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Linux-Kernel-Sperrung" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linux-Auslagerung" #. TRANSLATORS: command description msgid "List EFI variables with a specific GUID" msgstr "EFI-Variablen mit einer bestimmten GUID auflisten" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Einträge in dbx auflisten" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Unterstützte Firmware-Aktualisierungen auflisten" #. TRANSLATORS: command description msgid "List the available firmware GTypes" msgstr "Verfügbare Firmware-GTypen auflisten" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Verfügbare Firmware-Typen auflisten" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Listet die Dateien auf dem ESP auf" #. TRANSLATORS: command description msgid "Load device emulation data" msgstr "Geräteemulationsdaten laden" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "Von einem externen Modul geladen" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Laden …" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Gesperrt" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Niedrig" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "MEI-Schlüsselmanifest" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "MEI-Schlüsselmanifest" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "MEI-Herstellungsmodus" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "MEI überschreiben" msgid "MEI version" msgstr "MEI-Version" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Manuelles Aktivieren bestimmter Plugins" #. TRANSLATORS: longer description msgid "Manufacturing Mode is used when the device is manufactured and security features are not yet enabled." msgstr "Der Herstellungsmodus wird verwendet, wenn das Gerät hergestellt wird und die Sicherheitsfunktionen noch nicht aktiviert sind." #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Maximale Länge" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Maximaler Wert" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Mittel" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Metadaten-Signatur" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metadaten-URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadaten können über den Linux Vendor Firmware Service bezogen werden." #. TRANSLATORS: error message for a user who ran fwupdmgr #. * refresh recently msgid "Metadata is up to date; use --force to refresh again." msgstr "Die Metadaten sind auf dem neuesten Stand; verwenden Sie --force, um sie erneut aufzufrischen." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Minimale Version" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Minimale Länge" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Minimaler Wert" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Ändert einen Daemon-Konfigurationswert" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifiziert eine gegebene Gegenstelle" msgid "Modify a configured remote" msgstr "Eine konfigurierte Gegenstelle modifizieren" msgid "Modify daemon configuration" msgstr "Daemon-Konfiguration bearbeiten" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Hintergrundprogramm auf Ereignisse überwachen" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Hängt das ESP ein" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Benötigt einen Neustart nach der Installation" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Benötigt Neustart" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Benötigt ein Herunterfahren nach der Installation" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Neue Version" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Keine Aktion angegeben!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Keine Herabstufungen für %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Keine Firmware-Kennungen gefunden" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "Keine Firmware gefunden" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Es wurde keine Hardware erkannt, deren Firmware aktualisiert werden kann" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Keine Plugins gefunden" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Keine Freigaben verfügbar" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Da derzeit keine Gegenstellen aktiviert sind, sind keine Metadaten verfügbar." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Keine Gegenstelle verfügbar" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Keine aktualisierbaren Geräte" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Keine Aktualisierungen verfügbar" msgid "No updates available for remaining devices" msgstr "Für die verbleibenden Geräte sind keine Aktualisierungen verfügbar" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Es wurden keine Aktualisierungen angewendet" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Nicht genehmigt" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Nicht gefunden" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Nicht unterstützt" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "Ok" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "OK!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Alte Version" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Nur einzelnen PCR-Wert anzeigen" #. TRANSLATORS: command line option msgid "Only use peer-to-peer networking when downloading files" msgstr "Beim Herunterladen von Dateien nur Peer-to-Peer-Netzwerke verwenden" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Nur Versionsaktualisierungen sind erlaubt" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Ausgabe im JSON-Format" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Standard-ESP-Pfad überschreiben" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Firmware" msgstr "P2P-Firmware" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Metadata" msgstr "P2P-Metadaten" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "PFAD" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Details zu einer Firmware-Datei parsen und anzeigen" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "dbx-Aktualisierung wird geparst …" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "System-dbx wird geparst …" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Passwort" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Einen Firmware-Blob an einem bekannten Offset patchen" msgid "Payload" msgstr "Nutzdaten" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Ausstehend" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Prozent abgeschlossen" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Operation durchführen?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Plattform-Debugging" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Fehlerdiagnose der Plattform" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Bitte stellen Sie sicher, dass Sie den Wiederherstellungsschlüssel für den Datenträger haben, bevor Sie fortfahren." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Bitte geben Sie eine Zahl von 0 bis %u ein: " #. TRANSLATORS: the user isn't reading the question -- please don't translate #. * 'Y' or 'N' as these are hardcoded msgid "Please enter either Y or N: " msgstr "Bitte geben Sie entweder »Y« oder »N« ein: " #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Fehlende Plugin-Abhängigkeiten" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Mögliche Werte" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "DMA-Schutz vor dem Booten" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "DMA-Schutz vor dem Booten" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "DMA-Schutz vor dem Booten ist deaktiviert" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "DMA-Schutz vor dem Booten ist aktiviert" #. TRANSLATORS: longer description msgid "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." msgstr "Der DMA-Schutz vor dem Booten verhindert, dass Geräte auf den Systemspeicher zugreifen, nachdem sie an den Rechner angeschlossen wurden." #. TRANSLATORS: warning message msgid "Press unlock on the device to continue the update process." msgstr "Drücken Sie auf dem Gerät auf Entsperren, um den Aktualisierungsvorgang fortzusetzen." #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Vorherige Version" msgid "Print the version number" msgstr "Versionsnummer ausgeben" msgid "Print verbose debug statements" msgstr "Ausführliche Debug-Anweisungen ausgeben" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Priorität" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Probleme" msgid "Proceed with upload?" msgstr "Mit dem Hochladen fortfahren?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Prozessor-Sicherheitsprüfungen" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Prozessor-Rückrollschutz" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Proprietär" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Abfrage der Unterstützung für Firmware-Aktualisierungen" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "GEGENSTELLENKENNUNG" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "GEGENSTELLENKENNUNG SCHLÜSSEL WERT" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Nur lesen" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Firmware-Blob von einem Gerät lesen" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Firmware von einem Gerät lesen" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Firmware von Gerät in Datei schreiben" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Firmware von einzelner Partition in Datei lesen" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Von %s wird gelesen …" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Lesen …" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Wird neu gestartet …" #. TRANSLATORS: how often we should refresh the metadata msgid "Refresh Interval" msgstr "Auffrischungsintervall" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Metadaten von entferntem Server aktualisieren" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "%s auf %s neu installieren?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Aktuelle Firmware auf dem Gerät neu installieren" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Firmware auf einem Gerät neu installieren" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Erneute Installation von %s mit %s …" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Veröffentlichungszweig" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Gegenstellenkennung" #. TRANSLATORS: command description msgid "Removes devices to watch for future emulation" msgstr "Entfernt Geräte, die für zukünftige Emulationen zu beobachten sind" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Daten in einer bestehenden Firmware-Datei ersetzen" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Bericht-URI" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "An den entfernten Server gemeldet" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Anfrage abgebrochen" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "Erforderliches efivarfs-Dateisystem wurde nicht gefunden" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "Erforderliche Hardware wurde nicht gefunden" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Erfordert einen Bootloader" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Erfordert Internetverbindung" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Jetzt neu starten?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Den Daemon neu starten, damit die Änderung wirksam wird?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Gerät wird neu gestartet …" #. TRANSLATORS: command description msgid "Retrieve BIOS settings. If no arguments are passed all settings are returned" msgstr "BIOS-Einstellungen abrufen. Wenn keine Argumente übergeben werden, werden alle Einstellungen zurückgegeben" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Alle Hardware-Kennungen für das System zurückgeben" #. TRANSLATORS: ask the user to upload msgid "Review and upload report now?" msgstr "Bericht jetzt überprüfen und hochladen?" #. TRANSLATORS: longer description msgid "Rollback Protection prevents device software from being downgraded to an older version that has security problems." msgstr "Der Rückrollschutz verhindert, dass die Gerätesoftware auf eine ältere Version heruntergestuft wird, die Sicherheitsprobleme aufweist." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Führen Sie `fwupdmgr get-upgrades` für weitere Informationen aus." #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "Laufender Kernel ist zu alt" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Laufzeit-Suffix" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "EINSTELLUNGSWERT" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING1 VALUE1 [SETTING2] [VALUE2]" msgstr "EINSTELLUNG1 WERT1 [EINSTELLUNG2] [WERT2]" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "SPI-BIOS-Deskriptor" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "SPI BIOS-Region" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI sperren" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "SPI-Wiederholungsschutz" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI schreiben" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "SPI-Schreibschutz" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "SUBSYSTEM TREIBER [GERÄTEKENNUNG|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Eine Datei speichern, die die Erstellung von Hardware-Kennungen ermöglicht" #. TRANSLATORS: command description msgid "Save device emulation data" msgstr "Geräteemulationsdaten speichern" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "Skalares Inkrement" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Installation für den nächsten Neustart planen, falls möglich" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Einplanen …" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Secure Boot deaktiviert" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Secure Boot aktiviert" msgid "Security hardening for HSI" msgstr "Strengere Sicherheit für HSI" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Siehe %s für weitere Details." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Siehe %s für weitere Informationen." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Ausgewähltes Gerät" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Ausgewählter Datenträger" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Seriennummer" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "BIOS-Einstellung »%s« mittels »%s« festlegen." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "Eine BIOS-Einstellung festlegen" msgid "Set one or more BIOS settings" msgstr "Eine oder mehrere BIOS-Einstellungen festlegen" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Den Schalter für Fehlerdiagnose während der Aktualisierung setzen" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "Legt eine oder mehrere BIOS-Einstellungen fest" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Legt die Liste der zugelassenen Firmware fest" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Einstellungstyp" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Einstellungen werden nach einem Neustart des Systems wirksam" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Firmware-Verlauf mit den Entwicklern teilen" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Alle Ergebnisse anzeigen" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Client- und Hintergrundprogramm-Versionen anzeigen" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Ausführliche Daemon-Informationen für eine bestimme Domäne anzeigen" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Informationen zur Fehlerdiagnose für alle Domänen anzeigen" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Debug Optionen anzeigen" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Nicht aktualisierbare Geräte anzeigen" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Zusätzliche Informationen zur Fehlerdiagnose anzeigen" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Verlauf von Firmware-Aktualisierungen anzeigen" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Berechnete Version der dbx anzeigen" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Fehlerprotokoll der letzten versuchten Aktualisierung anzeigen" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Informationen über den Firmware-Aktualisierungsstatus anzeigen" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Jetzt herunterfahren?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Eine Firmware mit einem neuen Schlüssel signieren" msgid "Sign data using the client certificate" msgstr "Daten mit dem Client-Zertifikat signieren" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Daten mit dem Client-Zertifikat signieren" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Die hochgeladenen Daten mit dem Client-Zertifikat signieren" msgid "Signature" msgstr "Signatur" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Signierte Nutzdaten" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Größe" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Einige der Plattformgeheimnisse können durch die Aktualisierung dieser Firmware ungültig werden." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Quelle" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Anbieter-/Produktkennung(en) des DFU-Geräts angeben" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "dbx-Datenbankdatei angeben" msgid "Specify the number of bytes per USB transfer" msgstr "Geben Sie die Anzahl der Bytes pro USB-Übertragung an" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "Zeichenkette" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Erfolg" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Alle Geräte erfolgreich aktiviert" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Erfolgreich deaktivierte Gegenstelle" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Neue Metadaten wurden erfolgreich heruntergeladen: " #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Gegenstelle erfolgreich aktiviert und aufgefrischt" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Erfolgreich aktivierte Gegenstelle " #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Erfolgreich installierte Firmware" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Konfigurationswert erfolgreich geändert" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Erfolgreich modifizierte Gegenstelle" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metadaten erfolgreich manuell aufgefrischt" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Die Geräte-Prüfsummen wurden erfolgreich aktualisiert" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "%u Bericht erfolgreich hochgeladen" msgstr[1] "%u Berichte erfolgreich hochgeladen" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Geräteprüfsummen erfolgreich verifiziert" #. TRANSLATORS: the device showed up in time #, c-format msgid "Successfully waited %.0fms for device" msgstr "Erfolgreich %.0f ms auf das Gerät gewartet" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Zusammenfassung" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Unterstützt" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "Unterstützte CPU" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Unterstützt auf dem entfernten Server" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Im Leerlauf anhalten" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Im RAM anhalten" #. TRANSLATORS: longer description msgid "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Im Leerlauf anhalten ermöglicht es dem Gerät, sich schnell schlafen zu legen, um Strom zu sparen. Während das Gerät angehalten ist, kann sein Speicher physisch entfernt und auf seine Informationen zugegriffen werden." #. TRANSLATORS: longer description msgid "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Im RAM anhalten ermöglicht es dem Gerät, sich schnell schlafen zu legen, um Strom zu sparen. Während das Gerät angehalten ist, kann sein Speicher physisch entfernt und auf seine Informationen zugegriffen werden." #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Im Leerlauf anhalten" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Im RAM anhalten" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Zweig von %s nach %s wechseln?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Den Firmware-Zweig auf dem Gerät wechseln" #. TRANSLATORS: command description msgid "Sync firmware versions to the chosen configuration" msgstr "Firmware-Versionen mit der ausgewählten Konfiguration synchronisieren" #. TRANSLATORS: this CLI tool is now preventing system updates msgid "System Update Inhibited" msgstr "Systemaktualisierung blockiert" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "Systemleistung ist zu gering, um die Aktualisierung durchzuführen" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "Systemleistung ist zu gering, um die Aktualisierung durchzuführen (%u%%, erfordert %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "System benötigt externe Stromquelle" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEXT" #. TRANSLATORS: longer description msgid "TPM (Trusted Platform Module) is a computer chip that detects when hardware components have been tampered with." msgstr "TPM (Trusted Platform Module) ist ein Rechner-Chip, der erkennt, wenn Hardware-Komponenten manipuliert wurden." #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "TPM-PCR0-Rekonstruktion" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "TPM-PCR0-Rekonstruktion ist ungültig" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "TPM-PCR0-Rekonstruktion ist jetzt gültig" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "TPM-Plattformkonfiguration" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "TPM-Rekonstruktion" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "TPM leere PCRs" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Kennzeichen" msgstr[1] "Kennzeichen" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "Zur Emulation markiert" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Verdorben" msgid "Target" msgstr "Ziel" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Ein Gerät mit einem JSON-Manifest testen" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Geprüft" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "Geprüft von %s" #. TRANSLATORS: someone we trust has tested this msgid "Tested by trusted vendor" msgstr "Getestet von einem vertrauenswürdigen Anbieter" #. TRANSLATORS: longer description msgid "The Intel Management Engine Key Manifest must be valid so that the device firmware can be trusted by the CPU." msgstr "Das Schlüsselmanifest der Intel Management Engine muss gültig sein, damit die Geräte-Firmware von der CPU als vertrauenswürdig eingestuft werden kann." #. TRANSLATORS: longer description msgid "The Intel Management Engine controls device components and needs to have a recent version to avoid security issues." msgstr "Die Intel Management Engine steuert Gerätekomponenten und muss auf dem neuesten Stand sein, um Sicherheitsprobleme zu vermeiden." #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "Der LVFS ist ein kostenloser Dienst, der als unabhängige juristische Person arbeitet und keine Verbindung zu $OS_RELEASE:NAME$ hat. Ihr Händler hat möglicherweise keine der Firmware-Aktualisierungen auf Kompatibilität mit Ihrem System oder den angeschlossenen Geräten verifiziert. Die gesamte Firmware wird nur vom Originalhersteller zur Verfügung gestellt." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Platform Configuration is used to check whether the device start process has been modified." msgstr "Die TPM (Trusted Platform Module)-Plattformkonfiguration wird verwendet, um zu überprüfen, ob der Gerätestartprozess geändert wurde." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Reconstruction is used to check whether the device start process has been modified." msgstr "Die TPM (Trusted Platform Module)-Rekonstruktion wird verwendet, um zu überprüfen, ob der Gerätestartprozess geändert wurde." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "Das TPM PCR0 unterscheidet sich von der Rekonstruktion." #. TRANSLATORS: longer description msgid "The UEFI Platform Key is used to determine if device software comes from a trusted source." msgstr "Der UEFI-Plattformschlüssel wird verwendet, um festzustellen, ob die Gerätesoftware aus einer vertrauenswürdigen Quelle stammt." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "Der Daemon hat Code von Drittanbietern geladen und wird von den Upstream-Entwicklern nicht mehr unterstützt!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "Die Geräteversion stimmte nicht überein: %s erhalten, %s erwartet" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "Die Firmware von %s wird nicht von %s, dem Hardwarehersteller, geliefert." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "Die Systemuhr wurde nicht korrekt eingestellt und das Herunterladen von Dateien kann fehlschlagen." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been re-inserted." msgstr "Die Aktualisierung wird fortgesetzt, wenn das USB-Kabel des Geräts wieder eingesteckt wird." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged and then re-inserted." msgstr "Die Aktualisierung wird fortgesetzt, wenn das USB-Kabel des Geräts abgezogen und dann wieder eingesteckt wird." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged." msgstr "Die Aktualisierung wird fortgesetzt, wenn das USB-Kabel des Geräts abgezogen wurde." #. TRANSLATORS: warning message msgid "The update will continue when the device power cable has been removed and re-inserted." msgstr "Die Aktualisierung wird fortgesetzt, wenn das Netzkabel des Geräts entfernt und wieder eingesteckt wird." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "Der Anbieter hat keine Versionshinweise zur Verfügung gestellt." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Es gibt Geräte mit Problemen:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Es gibt keine blockierten Firmware-Dateien" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Es gibt keine genehmigte Firmware." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "Dieses Gerät wird wieder auf %s zurückgesetzt, wenn der Befehl %s ausgeführt wird." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "Diese Firmware wird von Mitgliedern der LVFS-Gemeinschaft zur Verfügung gestellt und wird nicht vom ursprünglichen Hardwareanbieter bereitgestellt (oder unterstützt)." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Dieses Paket wurde nicht validiert und könnte nicht richtig funktionieren." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Dieses Programm funktioniert möglicherweise nur als root korrekt" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Diese Gegenstelle enthält Firmware, die nicht unter Embargo steht, sondern vom Hardwareanbieter noch getestet wird. Sie sollten sicherstellen, dass Sie eine Möglichkeit haben, die Firmware manuell zurückzustufen, falls die Firmware-Aktualisierung fehlschlägt." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "Dieses System unterstützt keine Firmware-Einstellungen" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Dieses System hat HSI-Laufzeitprobleme." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Dieses System hat eine niedrige HSI-Sicherheitsstufe." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Mit diesem Werkzeug kann ein Administrator UEFI dbx-Aktualisierungen anwenden." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Mit diesem Werkzeug kann ein Administrator den fwupd-Daemon abfragen und steuern, sodass er Aktionen wie die Installation oder das Herabstufen von Firmware durchführen kann." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Dieses Werkzeug ermöglicht es einem Administrator, die fwupd-Plugins zu verwenden, ohne auf dem Host-System installiert sein zu müssen." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can add a kernel argument of '%s', but it will only be active after restarting the computer." msgstr "Dieses Werkzeug kann den Kernel-Parameter »%s« hinzufügen, aber dieser wird erst nach einem Neustart des Rechners aktiv." #. TRANSLATORS: the %1 is a BIOS setting name. #. * %2 and %3 are the values, e.g. "True" or "Windows10" #, c-format msgid "This tool can change the BIOS setting '%s' from '%s' to '%s' automatically, but it will only be active after restarting the computer." msgstr "Dieses Werkzeug kann die BIOS-Einstellung »%s« automatisch von »%s« auf »%s« ändern, aber sie wird erst nach einem Neustart des Rechners aktiv." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can change the kernel argument from '%s' to '%s', but it will only be active after restarting the computer." msgstr "Dieses Werkzeug kann den Kernel-Parameter von »%s« auf »%s« ändern, aber dieser wird erst nach einem Neustart des Rechners aktiv." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Dieses Werkzeug kann nur von dem Benutzer root verwendet werden" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Dieses Werkzeug liest und parst das TPM-Ereignisprotokoll aus der System-Firmware." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Vorübergehender Fehler" #. TRANSLATORS: item is TRUE msgid "True" msgstr "Wahr" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Vertrauenswürdige Metadaten" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Vertrauenswürdige Nutzdaten" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Typ" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI Bootservice Variables" msgstr "UEFI-Bootdienst-Variablen" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition may not be set up correctly" msgstr "Die UEFI-ESP-Partition ist möglicherweise nicht korrekt eingerichtet" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "UEFI ESP-Partition nicht erkannt oder konfiguriert" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI-Firmware-Dienstprogramm" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "UEFI-Plattformschlüssel" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "Sicherer UEFI-Boot" #. TRANSLATORS: longer description msgid "UEFI Secure Boot prevents malicious software from being loaded when the device starts." msgstr "UEFI Secure Boot verhindert, dass bösartige Software beim Start des Geräts geladen wird." #. TRANSLATORS: longer description msgid "UEFI boot service variables should not be readable from runtime mode." msgstr "UEFI-Bootdienst-Variablen sollten im Laufzeitmodus nicht lesbar sein." #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI bootservice variables" msgstr "UEFI-Bootdienst-Variablen" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "Aktualisierung der UEFI-Kapsel nicht verfügbar oder in der Firmware-Einrichtung aktiviert" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "UEFI dbx-Dienstprogramm" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "UEFI-Firmware kann im veralteten BIOS-Modus nicht aktualisiert werden" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "UEFI-Plattformschlüssel" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "Sicherer UEFI-Boot" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Es konnte keine Verbindung zum Dienst hergestellt werden" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "Attribut kann nicht gefunden werden" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Aktuellen Treiber entbinden" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Firmware entblocken:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Entblockt die Installation einer bestimmten Firmware" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Entschlüsselt" #. TRANSLATORS: command description msgid "Uninhibit the system to allow upgrades" msgstr "System entblocken, um Aktualisierungen zu ermöglichen" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Unbekannt" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Unbekanntes Gerät" msgid "Unlock the device to allow access" msgstr "Das Gerät entsperren, um Zugriff zu ermöglichen" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Entsperrt" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Entsperrt das Gerät für Zugriff auf die Firmware" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Hängt das ESP aus" #. TRANSLATORS: message shown after device has been marked for emulation msgid "Unplug and replug the device to continue the update process." msgstr "Stecken Sie das Gerät aus und wieder ein, um den Aktualisierungsvorgang fortzusetzen." #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Den Schalter für Fehlerdiagnose während der Aktualisierung ausstellen" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Unsignierte Nutzdaten" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Nicht unterstützte Daemon-Version %s, Client-Version ist %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Unverdorben" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Aktualisierbar" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Aktualisierungsfehler" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Aktualisierungsabbild" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Aktualisierungsmeldung" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Aktualisierungsstatus" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Der Aktualisierungsfehler ist ein bekanntes Problem, besuchen Sie diese URL für weitere Informationen:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Jetzt aktualisieren?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Aktualisierung erfordert einen Neustart" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Den gespeicherten kryptografischen Hash mit dem aktuellen ROM-Inhalt aktualisieren" msgid "Update the stored device verification information" msgstr "Gespeicherte Geräteverifizierungsinformationen aktualisieren" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Die gespeicherten Metadaten mit dem aktuellen Inhalt aktualisieren" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "Aktualisiert alle angegebenen Geräte auf die neueste Firmware-Version, oder alle Geräte, wenn nicht angegeben" msgid "Updating" msgstr "Wird aktualisiert" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Aktualisieren von %s von %s nach %s …" #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "%s wird aktualisiert …" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "%s von %s auf %s aktualisieren?" #. TRANSLATORS: ask the user to share, %s is something #. * like: "Linux Vendor Firmware Service" #, c-format msgid "Upload these anonymous results to the %s to help other users?" msgstr "Diese anonymen Ergebnisse zum %s hochladen, um anderen Nutzern zu helfen?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Das Hochladen von Firmware-Berichten hilft Hardwareherstellern, fehlerhafte und erfolgreiche Aktualisierungen auf realen Geräten schnell zu erkennen." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Dringlichkeit" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Verwenden Sie %s für Hilfe" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Verwenden Sie STRG^C zum Abbrechen." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Verwenden Sie fwupdtool --help für Hilfe" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Macken-Bitschalter bei der Installation von Firmware verwenden" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Benutzer wurde benachrichtigt" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Benutzername" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Gültig" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Validierung der ESP-Inhalte …" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variante" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Anbieter" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Wird verifiziert…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Version" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Version[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "WARNUNG" #. TRANSLATORS: command description msgid "Wait for a device to appear" msgstr "Warten, bis ein Gerät erscheint" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Warten …" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Auf Hardware-Änderungen achten" #. TRANSLATORS: check various UEFI and ACPI tables are unchanged after the #. update msgid "Will measure elements of system integrity around an update" msgstr "Misst Elemente der Systemintegrität im Zusammenhang mit einer Aktualisierung" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Firmware von Datei auf Gerät schreiben" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Firmware aus Datei in einzelne Partition schreiben" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Datei wird geschrieben:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Schreiben …" #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from a recovery or installation disk, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Sie sollten sich vergewissern, dass Sie die Einstellung aus einer Wiederherstellung oder von Installations-Disketten zurückspielen können, weil diese Änderung dazu führen kann, dass das System nicht mehr Linux startet oder andere Systeminstabilitäten verursacht." #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from the system firmware setup, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Sie sollten sich vergewissern, dass Sie die Einstellung aus der System-Firmware-Einrichtung wiederherstellen können, da diese Änderung dazu führen kann, dass das System nicht in Linux startet oder andere Systeminstabilitäten verursacht." #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Ihr Händler hat möglicherweise keine der Firmware-Aktualisierungen auf Kompatibilität mit Ihrem System oder den angeschlossenen Geräten verifiziert." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Ihre Hardware kann durch die Verwendung dieser Firmware beschädigt werden und die Installation dieser Version kann zum Erlöschen jeglicher Garantie mit %s führen." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "Ihr System ist auf die BKC von %s eingestellt." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[APPSTREAM_ID]" msgstr "[APPSTREAM_KENNUNG]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[PRÜFSUMME]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[GERÄTEKENNUNG|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[GERÄTEKENNUNG|GUID] [ZWEIG]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[GERÄTEKENNUNG|GUID] [VERSION]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE]" msgstr "[GERÄT]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[DATEI DATEI_SIG GEGENSTELLENKENNUNG]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[DATEINAME1] [DATEINAME2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[GRUND]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[EINSTELLUNG1] [ EINSTELLUNG2]..." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[EINSTELLUNG1] [EINSTELLUNG2] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SMBIOS-DATEI|HWIDS-DATEI]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "Standard" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "fwupd TPM-Ereignisprotokolldienstprogramm" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd-Plugins" fwupd-1.9.16/po/en_GB.po000066400000000000000000003462171460375044200147070ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Andi Chandler , 2019-2020,2022 # Richard Hughes , 2015,2017-2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: English (United Kingdom) (http://app.transifex.com/freedesktop/fwupd/language/en_GB/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: en_GB\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minute remaining" msgstr[1] "%.0f minutes remaining" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "%s BMC Update" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "%s Battery Update" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "%s CPU Microcode Update" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "%s Camera Update" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "%s Configuration Update" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s Consumer ME Update" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s Controller Update" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s Corporate ME Update" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s Device Update" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "%s Display Update" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "%s Dock Update" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "%s Drive Update" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s Embedded Controller Update" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "%s Fingerprint Reader Update" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "%s Flash Drive Update" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "%s GPU Update" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "%s Graphics Tablet Update" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "%s Keyboard Update" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s ME Update" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "%s Mouse Update" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "%s Network Interface Update" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "%s SSD Update" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "%s Storage Controller Update" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s System Update" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "%s TPM Update" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s Thunderbolt Controller Update" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "%s Touchpad Update" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "%s USB Dock Update" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "%s USB Receiver Update" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s Update" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s and all connected devices may not be usable while updating." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "%s appeared: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "%s changed: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "%s disappeared: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s is not currently updatable" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s manufacturing mode" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s must remain connected for the duration of the update to avoid damage." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s must remain plugged into a power source for the duration of the update to avoid damage." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s override" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s version" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u day" msgstr[1] "%u days" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u device has a firmware upgrade available." msgstr[1] "%u devices have a firmware upgrade available." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u device is not the best known configuration." msgstr[1] "%u devices are not the best known configuration." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u hour" msgstr[1] "%u hours" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u local device supported" msgstr[1] "%u local devices supported" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minute" msgstr[1] "%u minutes" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u second" msgstr[1] "%u seconds" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (threshold %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(obsoleted)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "A TPM PCR is now an invalid value" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "AMD Firmware Replay Protection" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "AMD Firmware Write Protection" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "AMD Secure Processor Rollback Protection" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "ARCHIVE FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATFILE]" msgstr "ARCHIVE FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATFILE]" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Action Required:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Activate devices" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Activate pending devices" msgid "Activate the new firmware on the device" msgstr "Activate the new firmware on the device" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Activating firmware update" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Activating firmware update for" #. TRANSLATORS: command description msgid "Adds devices to watch for future emulation" msgstr "Adds devices to watch for future emulation" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Age" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Agree and enable the remote?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias to %s" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "All TPM PCRs are now valid" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "All TPM PCRs are valid" #. TRANSLATORS: an application is preventing system updates msgid "All devices are prevented from update by system inhibit" msgstr "All devices are prevented from update by system inhibit" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "All devices of the same type will be updated at the same time" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Allow downgrading firmware versions" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Allow reinstalling existing firmware versions" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Allow switching firmware branch" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Alternate branch" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "An update is in progress" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "An update requires a reboot to complete." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "An update requires the system to shutdown to complete." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Answer yes to all questions" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Apply firmware updates" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Apply update even when not advised" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Apply update files" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Applying update…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Approved firmware:" msgstr[1] "Approved firmware:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Ask again next time?" #. TRANSLATORS: command description msgid "Asks the daemon to quit" msgstr "Asks the daemon to quit" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Attach to firmware mode" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Authenticating…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Authentication details are required" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Authentication is required to downgrade the firmware on a removable device" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Authentication is required to downgrade the firmware on this machine" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to fix a host security issue" msgstr "Authentication is required to fix a host security issue" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Authentication is required to modify BIOS settings" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Authentication is required to modify a configured remote used for firmware updates" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Authentication is required to modify daemon configuration" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "Authentication is required to read BIOS settings" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Authentication is required to set the list of approved firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Authentication is required to sign data using the client certificate" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Authentication is required to switch to the new firmware version" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to undo the fix for a host security issue" msgstr "Authentication is required to undo the fix for a host security issue" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Authentication is required to unlock a device" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Authentication is required to update the firmware on a removable device" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Authentication is required to update the firmware on this machine" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Authentication is required to update the stored checksums for the device" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Automatic Reporting" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Automatically upload every time?" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS Firmware Updates" msgstr "BIOS Firmware Updates" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "BIOS Rollback Protection" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS firmware updates" msgstr "BIOS firmware updates" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "BIOS rollback protection" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "BIOS updates delivered via LVFS or Windows Update" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BUILDER-XML FILENAME-DST" msgid "BYTES" msgstr "BYTES" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Battery" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Bind new kernel driver" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Blocked firmware files:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Blocked version" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Blocking firmware:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Blocks a specific firmware from being installed" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Bootloader Version" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Branch" #. TRANSLATORS: command description msgid "Build a cabinet archive from a firmware blob and XML metadata" msgstr "Build a cabinet archive from a firmware blob and XML metadata" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Build a firmware file" #. TRANSLATORS: longer description msgid "CPU Microcode must be updated to mitigate against various information-disclosure security issues." msgstr "CPU Microcode must be updated to mitigate against various information-disclosure security issues." #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Cancel" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Cancelled" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Cannot apply as dbx update has already been applied." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Cannot apply updates on live media" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Changed" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Checks cryptographic hash matches firmware" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Checksum" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Choose branch" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Choose device" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Choose firmware" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Choose release" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Choose the ESP:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Choose volume" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Clears the results from the last update" #. TRANSLATORS: error message msgid "Command not found" msgstr "Command not found" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Community supported" #. TRANSLATORS: title prefix for the BIOS settings dialog msgid "Configuration Change Suggested" msgstr "Configuration Change Suggested" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "Configuration is only readable by the system administrator" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Convert a firmware file" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Created" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Critical" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Cryptographic hash verification is available" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Current Value" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Current version" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "DEVICE-ID|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU Utility" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Debugging Options" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Decompressing…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Description" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Detach to bootloader mode" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Details" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "Deviate from the best known configuration?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Device Flags" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Device ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Device added:" #. TRANSLATORS: the device is already connected msgid "Device already exists" msgstr "Device already exists" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "Device battery power is too low" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "Device battery power is too low (%u%%, requires %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Device can recover flash failures" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "Device cannot be used while the lid is closed" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Device changed:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "Device emulation is not enabled." #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Device firmware is required to have a version check" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "Device is emulated" #. TRANSLATORS: device cannot be interrupted, for instance taking a phone call msgid "Device is in use" msgstr "Device is in use" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Device is locked" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Device is required to install all provided releases" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Device is unreachable" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "Device is unreachable, or out of wireless range" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Device is usable for the duration of the update" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "Device is waiting for the update to be applied" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Device removed:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "Device requires AC power to be connected" #. TRANSLATORS: device does not have a display connected msgid "Device requires a display to be plugged in" msgstr "Device requires a display to be plugged in" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "Device requires a software license to update" #. TRANSLATORS: longer description msgid "Device software updates are provided for this device." msgstr "Device software updates are provided for this device." #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Device stages updates" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "Device supports switching to a different branch of firmware" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Device update method" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Device update needs activation" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Device will backup firmware before installing" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Device will not re-appear after update completes" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Devices that have been updated successfully:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Devices that were not updated correctly:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Devices with no available firmware updates: " #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Devices with the latest available firmware version:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "Did not find any devices with matching GUIDs" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Disabled" msgid "Disabled fwupdate debugging" msgstr "Disabled fwupdate debugging" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Disables a given remote" #. TRANSLATORS: command line option msgid "Display version" msgstr "Display version" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Distribution" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Do not check for old metadata" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Do not check for unreported history" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Do not check if download remotes should be enabled" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Do not check or prompt for reboot after update" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Do not include log domain prefix" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Do not include timestamp prefix" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Do not perform device safety checks" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "Do not prompt for devices" #. TRANSLATORS: command line option msgid "Do not prompt to fix security issues" msgstr "Do not prompt to fix security issues" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "Do not search the firmware when parsing" #. TRANSLATORS: warning message shown after update has been scheduled msgid "Do not turn off your computer or remove the AC adaptor while the update is in progress." msgstr "Do not turn off your computer or remove the AC adaptor while the update is in progress." #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Do not write to the history database" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Do you understand the consequences of changing the firmware branch?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Do you want to disable this feature for future updates?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "Do you want to enable it now?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Do you want to refresh this remote now?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Do you want to upload reports automatically for future updates?" #. TRANSLATORS: command line option msgid "Don't prompt for authentication (less details may be shown)" msgstr "Don't prompt for authentication (less details may be shown)" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Done!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Downgrade %s from %s to %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Downgrades the firmware on a device" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Downgrading %s from %s to %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Downgrading %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Download a file" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Downloading…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Dump SMBIOS data from a file" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Duration" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "ESP specified was not valid" #. TRANSLATORS: longer description msgid "Each system should have tests to ensure firmware security." msgstr "Each system should have tests to ensure firmware security." #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "Emulate a device using a JSON manifest" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Emulated" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Emulated host" msgid "Enable" msgstr "Enable" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Enable firmware update support on supported systems" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Enable new remote?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Enable this remote?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Enabled" msgid "Enabled fwupdate debugging" msgstr "Enabled fwupdate debugging" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Enabled if hardware matches" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Enables a given remote" #. TRANSLATORS: longer description msgid "Enabling firmware updates for the BIOS allows fixing security issues." msgstr "Enabling firmware updates for the BIOS allows fixing security issues." msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Enabling this remote is done at your own risk." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Encrypted" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "Encrypted RAM" msgid "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." msgstr "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "End of life" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Enumeration" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Erase all firmware update history" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Erasing…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Exit after a small delay" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Exit after the engine has loaded" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Export a firmware file structure to XML" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Extract a firmware blob to images" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "FILE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "FILE [DEVICE-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "FILENAME" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "FILENAME CERTIFICATE PRIVATE-KEY" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "FILENAME DEVICE-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "FILENAME OFFSET DATA [FIRMWARE-TYPE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "FILENAME [DEVICE-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "FILENAME [FIRMWARE-TYPE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Failed" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Failed to apply update" #. TRANSLATORS: error message for Windows msgid "Failed to connect to Windows service, please ensure it's running." msgstr "Failed to connect to Windows service, please ensure it's running." #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Failed to connect to daemon" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Failed to get pending devices" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Failed to install firmware update" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Failed to load local dbx" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Failed to load quirks" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Failed to load system dbx" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Failed to lock" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Failed to parse arguments" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Failed to parse file" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Failed to parse flags for --filter" msgid "Failed to parse flags for --filter-release" msgstr "Failed to parse flags for --filter-release" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Failed to parse local dbx" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Failed to reboot" #. TRANSLATORS: a feature is something like "can show an image" msgid "Failed to set front-end features" msgstr "Failed to set front-end features" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Failed to set splash mode" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Failed to validate ESP contents" #. TRANSLATORS: item is FALSE msgid "False" msgstr "False" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Filename" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Filename Signature" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Filename Source" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Filename required" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" #. TRANSLATORS: command line option msgid "Filter with a set of release flags using a ~ prefix to exclude, e.g. 'trusted-release,~trusted-metadata'" msgstr "Filter with a set of release flags using a ~ prefix to exclude, e.g. 'trusted-release,~trusted-metadata'" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Firmware Attestation" #. TRANSLATORS: longer description msgid "Firmware Attestation checks device software using a reference copy, to make sure that it has not been changed." msgstr "Firmware Attestation checks device software using a reference copy, to make sure that it has not been changed." #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Firmware BIOS Descriptor" #. TRANSLATORS: longer description msgid "Firmware BIOS Descriptor protects device firmware memory from being tampered with." msgstr "Firmware BIOS Descriptor protects device firmware memory from being tampered with." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Firmware BIOS Region" #. TRANSLATORS: longer description msgid "Firmware BIOS Region protects device firmware memory from being tampered with." msgstr "Firmware BIOS Region protects device firmware memory from being tampered with." #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Firmware Base URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Firmware Update D-Bus Service" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Firmware Update Daemon" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Firmware Updater Verification" msgid "Firmware Updater Verification checks that software used for updating has not been tampered with." msgstr "Firmware Updater Verification checks that software used for updating has not been tampered with." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Firmware Updates" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Firmware Utility" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Firmware Write Protection" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Firmware Write Protection Lock" #. TRANSLATORS: longer description msgid "Firmware Write Protection protects device firmware memory from being tampered with." msgstr "Firmware Write Protection protects device firmware memory from being tampered with." #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Firmware attestation" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Firmware is already blocked" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Firmware is not already blocked" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Firmware metadata has not been updated for %u day and may not be up to date." msgstr[1] "Firmware metadata has not been updated for %u days and may not be up to date." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Firmware updates" msgid "Firmware updates are not supported on this machine." msgstr "Firmware updates are not supported on this machine." msgid "Firmware updates are supported on this machine." msgstr "Firmware updates are supported on this machine." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Firmware updates disabled; run 'fwupdmgr unlock' to enable" #. TRANSLATORS: command description msgid "Fix a specific host security attribute" msgstr "Fix a specific host security attribute" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fix reverted successfully" msgstr "Fix reverted successfully" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fixed successfully" msgstr "Fixed successfully" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Flags" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Force the action by relaxing some runtime checks" msgid "Force the action ignoring all warnings" msgstr "Force the action ignoring all warnings" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Found" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Full Disk Encryption Detected" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "Full disk encryption secrets may be invalidated when updating" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "Fused Platform" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Fused platform" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUIDs" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "GUID|DEVICE-ID" msgstr "GUID|DEVICE-ID" msgid "Get BIOS settings" msgstr "Get BIOS settings" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Get all device flags supported by fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Get all devices that support firmware updates" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Get all enabled plug-ins registered with the system" #. TRANSLATORS: command description msgid "Get device report metadata" msgstr "Get device report metadata" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Gets details about a firmware file" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Gets the configured remotes" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Gets the host security attributes" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Gets the list of approved firmware" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Gets the list of blocked firmware" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Gets the list of updates for connected hardware" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Gets the releases for a device" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Gets the results from the last update" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWIDS-FILE" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Hardware is waiting to be replugged" #. TRANSLATORS: the release urgency msgid "High" msgstr "High" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Host Security Events" #. TRANSLATORS: error message for unsupported feature #, c-format msgid "Host Security ID (HSI) is not supported" msgstr "Host Security ID (HSI) is not supported" #. TRANSLATORS: success, so say thank you to the user msgid "Host Security ID attributes uploaded successfully, thanks!" msgstr "Host Security ID attributes uploaded successfully, thanks!" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Host Security ID:" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "INHIBIT-ID" msgstr "INHIBIT-ID" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "IOMMU Protection" #. TRANSLATORS: longer description msgid "IOMMU Protection prevents connected devices from accessing unauthorized parts of system memory." msgstr "IOMMU Protection prevents connected devices from accessing unauthorised parts of system memory." #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "IOMMU device protection disabled" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "IOMMU device protection enabled" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Idle…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ignore SSL strict checks when downloading files" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Ignore firmware checksum failures" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Ignore firmware hardware mismatch failures" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignore validation safety checks" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" #. TRANSLATORS: the inhibit ID is a short string like dbus-123456 #, c-format msgid "Inhibit ID is %s." msgstr "Inhibit ID is %s." #. TRANSLATORS: command description msgid "Inhibit the system to prevent upgrades" msgstr "Inhibit the system to prevent upgrades" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Install Duration" #. TRANSLATORS: command description msgid "Install a firmware file in cabinet format on this hardware" msgstr "Install a firmware file in cabinet format on this hardware" #. TRANSLATORS: command description msgid "Install a raw firmware blob on a device" msgstr "Install a raw firmware blob on a device" #. TRANSLATORS: command description msgid "Install a specific firmware file on all devices that match" msgstr "Install a specific firmware file on all devices that match" #. TRANSLATORS: command description msgid "Install a specific firmware on a device, all possible devices will also be installed once the CAB matches" msgstr "Install a specific firmware on a device, all possible devices will also be installed once the CAB matches" msgid "Install old version of signed system firmware" msgstr "Install old version of signed system firmware" msgid "Install old version of unsigned system firmware" msgstr "Install old version of unsigned system firmware" msgid "Install signed device firmware" msgstr "Install signed device firmware" msgid "Install signed system firmware" msgstr "Install signed system firmware" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Install to parent device first" msgid "Install unsigned device firmware" msgstr "Install unsigned device firmware" msgid "Install unsigned system firmware" msgstr "Install unsigned system firmware" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Installing Firmware…" #. TRANSLATORS: stay on one firmware version unless the new version is #. explicitly #. * specified msgid "Installing a specific release is explicitly required" msgstr "Installing a specific release is explicitly required" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Installing firmware update…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Installing on %s…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "Installing this update may also void any device warranty." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Integer" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "Intel BootGuard ACM Protected" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Intel BootGuard ACM protected" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Intel BootGuard Error Policy" msgid "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." msgstr "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Intel BootGuard Fuse" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Intel BootGuard OTP fuse" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Intel BootGuard Verified Boot" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Intel BootGuard error policy" #. TRANSLATORS: longer description msgid "Intel BootGuard prevents unauthorized device software from operating when the device is started." msgstr "Intel BootGuard prevents unauthorised device software from operating when the device is started." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Intel BootGuard verified boot" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET Active" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET Enabled" #. TRANSLATORS: longer description msgid "Intel Control-Flow Enforcement Technology detects and prevents certain methods for running malicious software on the device." msgstr "Intel Control-Flow Enforcement Technology detects and prevents certain methods for running malicious software on the device." #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS Mitigation" msgstr "Intel GDS Mitigation" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS mitigation" msgstr "Intel GDS mitigation" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Intel Management Engine Manufacturing Mode" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Intel Management Engine Override" #. TRANSLATORS: longer description msgid "Intel Management Engine Override disables checks for device software tampering." msgstr "Intel Management Engine Override disables checks for device software tampering." #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Intel Management Engine Version" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: longer description msgid "Intel Supervisor Mode Access Prevention ensures critical parts of device memory are not accessed by less secure programs." msgstr "Intel Supervisor Mode Access Prevention ensures critical parts of device memory are not accessed by less secure programs." #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Internal device" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Invalid" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Invalid arguments" #. TRANSLATORS: error message msgid "Invalid arguments, expected GUID" msgstr "Invalid arguments, expected GUID" #. TRANSLATOR: This is the error message for #. * incorrect parameter msgid "Invalid arguments, expected an AppStream ID" msgstr "Invalid arguments, expected an AppStream ID" #. TRANSLATORS: error message msgid "Invalid arguments, expected at least ARCHIVE FIRMWARE METAINFO" msgstr "Invalid arguments, expected at least ARCHIVE FIRMWARE METAINFO" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Is downgrade" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Is in bootloader mode" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Is upgrade" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Issue" msgstr[1] "Issues" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "KEY,VALUE" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "Kernel is no longer tainted" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "Kernel is tainted" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Kernel lockdown disabled" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Kernel lockdown enabled" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Keyring" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "LOCATION" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Last modified" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Less than one minute remaining" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licence" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Linux Kernel Lockdown" #. TRANSLATORS: longer description msgid "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgstr "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgid "Linux Kernel Swap temporarily saves information to disk as you work. If the information is not protected, it could be accessed by someone if they obtained the disk." msgstr "Linux Kernel Swap temporarily saves information to disk as you work. If the information is not protected, it could be accessed by someone if they obtained the disk." #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Linux Kernel Verification" msgid "Linux Kernel Verification makes sure that critical system software has not been tampered with. Using device drivers which are not provided with the system can prevent this security feature from working correctly." msgstr "Linux Kernel Verification makes sure that critical system software has not been tampered with. Using device drivers which are not provided with the system can prevent this security feature from working correctly." #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Linux Swap" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (stable firmware)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (testing firmware)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Linux kernel" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Linux kernel lockdown" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linux swap" #. TRANSLATORS: command description msgid "List EFI variables with a specific GUID" msgstr "List EFI variables with a specific GUID" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "List entries in dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "List supported firmware updates" #. TRANSLATORS: command description msgid "List the available firmware GTypes" msgstr "List the available firmware GTypes" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "List the available firmware types" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Lists files on the ESP" #. TRANSLATORS: command description msgid "Load device emulation data" msgstr "Load device emulation data" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "Loaded from an external module" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Loading…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Locked" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Low" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "MEI Key Manifest" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "MEI key manifest" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "MEI manufacturing mode" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "MEI override" msgid "MEI version" msgstr "MEI version" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Manually enable specific plug-ins" #. TRANSLATORS: longer description msgid "Manufacturing Mode is used when the device is manufactured and security features are not yet enabled." msgstr "Manufacturing Mode is used when the device is manufactured and security features are not yet enabled." #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Maximum length" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Maximum value" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Medium" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Metadata Signature" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metadata URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadata can be obtained from the Linux Vendor Firmware Service." #. TRANSLATORS: error message for a user who ran fwupdmgr #. * refresh recently msgid "Metadata is up to date; use --force to refresh again." msgstr "Metadata is up to date; use --force to refresh again." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Minimum Version" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Minimum length" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Minimum value" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Mismatched daemon and client, use %s instead" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Modifies a daemon configuration value" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifies a given remote" msgid "Modify a configured remote" msgstr "Modify a configured remote" msgid "Modify daemon configuration" msgstr "Modify daemon configuration" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Monitor the daemon for events" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Mounts the ESP" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Needs a reboot after installation" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Needs reboot" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Needs shutdown after installation" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "New version" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "No action specified!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "No downgrades for %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "No firmware IDs found" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "No firmware found" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "No hardware detected with firmware update capability" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "No plug-ins found" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "No releases available" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "No remotes are currently enabled so no metadata is available." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "No remotes available" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "No updatable devices" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "No updates available" msgid "No updates available for remaining devices" msgstr "No updates available for remaining devices" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "No updates were applied" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Not approved" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Not found" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Not supported" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "OK" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "OK!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Old version" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Only show single PCR value" #. TRANSLATORS: command line option msgid "Only use peer-to-peer networking when downloading files" msgstr "Only use peer-to-peer networking when downloading files" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Only version upgrades are allowed" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Output in JSON format" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Override the default ESP path" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Firmware" msgstr "P2P Firmware" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Metadata" msgstr "P2P Metadata" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "PATH" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Parse and show details about a firmware file" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Parsing dbx update…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Parsing system dbx…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Password" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Patch a firmware blob at a known offset" msgid "Payload" msgstr "Payload" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Pending" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Percentage complete" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Perform operation?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Platform Debugging" #. TRANSLATORS: longer description msgid "Platform Debugging allows device security features to be disabled. This should only be used by hardware manufacturers." msgstr "Platform Debugging allows device security features to be disabled. This should only be used by hardware manufacturers." #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Platform debugging" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Please ensure you have the volume recovery key before continuing." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Please enter a number from 0 to %u: " #. TRANSLATORS: the user isn't reading the question -- please don't translate #. * 'Y' or 'N' as these are hardcoded msgid "Please enter either Y or N: " msgstr "Please enter either Y or N: " #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Plug-in dependencies missing" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Possible Values" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "Pre-boot DMA Protection" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Pre-boot DMA protection" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "Pre-boot DMA protection is disabled" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "Pre-boot DMA protection is enabled" #. TRANSLATORS: longer description msgid "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." msgstr "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." #. TRANSLATORS: warning message msgid "Press unlock on the device to continue the update process." msgstr "Press unlock on the device to continue the update process." #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Previous version" msgid "Print the version number" msgstr "Print the version number" msgid "Print verbose debug statements" msgstr "Print verbose debug statements" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Priority" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Problems" msgid "Proceed with upload?" msgstr "Proceed with upload?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Processor Security Checks" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Processor rollback protection" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Proprietary" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Query for firmware update support" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "REMOTE-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "REMOTE-ID KEY VALUE" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Read Only" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Read a firmware blob from a device" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Read a firmware from a device" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Read firmware from device into a file" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Read firmware from one partition into a file" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Reading from %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Reading…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Rebooting…" #. TRANSLATORS: how often we should refresh the metadata msgid "Refresh Interval" msgstr "Refresh Interval" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Refresh metadata from remote server" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Reinstall %s to %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Reinstall current firmware on the device" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Reinstall firmware on a device" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Reinstalling %s with %s... " #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Release Branch" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Release Flags" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "Release ID" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Remote ID" #. TRANSLATORS: command description msgid "Removes devices to watch for future emulation" msgstr "Removes devices to watch for future emulation" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Replace data in an existing firmware file" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Report URI" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Reported to remote server" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Request cancelled" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "Required efivarfs filesystem was not found" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "Required hardware was not found" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Requires a bootloader" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Requires internet connection" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Restart now?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Restart the daemon to make the change effective?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Restarting device…" #. TRANSLATORS: command description msgid "Retrieve BIOS settings. If no arguments are passed all settings are returned" msgstr "Retrieve BIOS settings. If no arguments are passed all settings are returned" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Return all the hardware IDs for the machine" #. TRANSLATORS: ask the user to upload msgid "Review and upload report now?" msgstr "Review and upload report now?" #. TRANSLATORS: longer description msgid "Rollback Protection prevents device software from being downgraded to an older version that has security problems." msgstr "Rollback Protection prevents device software from being downgraded to an older version that has security problems." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Run `fwupdmgr get-upgrades` for more information." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr sync` to complete this action." msgstr "Run `fwupdmgr sync` to complete this action." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Run the plug-in composite cleanup routine when using install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Run the plug-in composite prepare routine when using install-blob" #. TRANSLATORS: command description msgid "Run the post-reboot cleanup action" msgstr "Run the post-reboot cleanup action" #. TRANSLATORS: tell a user how to get information #, c-format msgid "Run without '%s' to see" msgstr "Run without '%s' to see" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "Running kernel is too old" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Runtime Suffix" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "SETTING VALUE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING1 VALUE1 [SETTING2] [VALUE2]" msgstr "SETTING1 VALUE1 [SETTING2] [VALUE2]" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "SPI BIOS Descriptor" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "SPI BIOS region" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI lock" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "SPI replay protection" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI write" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "SPI write protection" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Save a file that allows generation of hardware IDs" #. TRANSLATORS: command description msgid "Save device emulation data" msgstr "Save device emulation data" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "Scalar Increment" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Schedule installation for next reboot when possible" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Scheduling…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Secure Boot disabled" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Secure Boot enabled" msgid "Security hardening for HSI" msgstr "Security hardening for HSI" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "See %s for more details." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "See %s for more information." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Selected device" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Selected volume" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Serial Number" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "Set BIOS setting '%s' using '%s'." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "Set a BIOS setting" msgid "Set one or more BIOS settings" msgstr "Set one or more BIOS settings" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Set the debugging flag during update" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "Sets one or more BIOS settings" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Sets the list of approved firmware" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Setting type" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Settings will apply after system reboots" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Share firmware history with the developers" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Show all results" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Show client and daemon versions" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Show daemon verbose information for a particular domain" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Show debugging information for all domains" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Show debugging options" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Show devices that are not updatable" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Show extra debugging information" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Show history of firmware updates" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Show the calculated version of the dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Show the debug log from the last attempted update" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Show the information of firmware update status" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Shutdown now?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Sign a firmware with a new key" msgid "Sign data using the client certificate" msgstr "Sign data using the client certificate" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Sign data using the client certificate" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Sign the uploaded data with the client certificate" msgid "Signature" msgstr "Signature" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Signed Payload" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Size" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Some of the platform secrets may be invalidated when updating this firmware." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Source" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Specify Vendor/Product ID(s) of DFU device" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Specify the dbx database file" msgid "Specify the number of bytes per USB transfer" msgstr "Specify the number of bytes per USB transfer" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "String" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Success" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Successfully activated all devices" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Successfully disabled remote" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Successfully downloaded new metadata: " #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Successfully enabled and refreshed remote" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Successfully enabled remote" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Successfully installed firmware" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Successfully modified configuration value" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Successfully modified remote" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Successfully refreshed metadata manually" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Successfully updated device checksums" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Successfully uploaded %u report" msgstr[1] "Successfully uploaded %u reports" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Successfully verified device checksums" #. TRANSLATORS: the device showed up in time #, c-format msgid "Successfully waited %.0fms for device" msgstr "Successfully waited %.0fms for device" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Summary" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Supported" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "Supported CPU" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Supported on remote server" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Suspend To Idle" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Suspend To RAM" #. TRANSLATORS: longer description msgid "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." #. TRANSLATORS: longer description msgid "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Suspend-to-idle" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Suspend-to-ram" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Switch branch from %s to %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Switch the firmware branch on the device" #. TRANSLATORS: command description msgid "Sync firmware versions to the chosen configuration" msgstr "Sync firmware versions to the chosen configuration" #. TRANSLATORS: this CLI tool is now preventing system updates msgid "System Update Inhibited" msgstr "System Update Inhibited" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "System power is too low to perform the update" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "System power is too low to perform the update (%u%%, requires %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "System requires external power source" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEXT" #. TRANSLATORS: longer description msgid "TPM (Trusted Platform Module) is a computer chip that detects when hardware components have been tampered with." msgstr "TPM (Trusted Platform Module) is a computer chip that detects when hardware components have been tampered with." #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "TPM PCR0 reconstruction" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "TPM PCR0 reconstruction is invalid" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "TPM PCR0 reconstruction is now valid" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "TPM Platform Configuration" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "TPM Reconstruction" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "TPM empty PCRs" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Tag" msgstr[1] "Tags" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "Tagged for emulation" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Tainted" msgid "Target" msgstr "Target" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Test a device using a JSON manifest" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Tested" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "Tested by %s" #. TRANSLATORS: someone we trust has tested this msgid "Tested by trusted vendor" msgstr "Tested by trusted vendor" #. TRANSLATORS: longer description msgid "The Intel Management Engine Key Manifest must be valid so that the device firmware can be trusted by the CPU." msgstr "The Intel Management Engine Key Manifest must be valid so that the device firmware can be trusted by the CPU." #. TRANSLATORS: longer description msgid "The Intel Management Engine controls device components and needs to have a recent version to avoid security issues." msgstr "The Intel Management Engine controls device components and needs to have a recent version to avoid security issues." #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Platform Configuration is used to check whether the device start process has been modified." msgstr "The TPM (Trusted Platform Module) Platform Configuration is used to check whether the device start process has been modified." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Reconstruction is used to check whether the device start process has been modified." msgstr "The TPM (Trusted Platform Module) Reconstruction is used to check whether the device start process has been modified." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "The TPM PCR0 differs from reconstruction." #. TRANSLATORS: longer description msgid "The UEFI Platform Key is used to determine if device software comes from a trusted source." msgstr "The UEFI Platform Key is used to determine if device software comes from a trusted source." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "The device version did not match: got %s, expected %s" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "The firmware from %s is not supplied by %s, the hardware vendor." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "The system clock has not been set correctly and downloading files may fail." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been re-inserted." msgstr "The update will continue when the device USB cable has been re-inserted." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged and then re-inserted." msgstr "The update will continue when the device USB cable has been unplugged and then re-inserted." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged." msgstr "The update will continue when the device USB cable has been unplugged." #. TRANSLATORS: warning message msgid "The update will continue when the device power cable has been removed and re-inserted." msgstr "The update will continue when the device power cable has been removed and re-inserted." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "The vendor did not supply any release notes." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "There are devices with issues:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "There are no blocked firmware files" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "There is no approved firmware." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "This device will be reverted back to %s when the %s command is performed." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "This package has not been validated, it may not work properly." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "This program may only work correctly as root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "This system doesn't support firmware settings" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "This system has HSI runtime issues." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "This system has a low HSI security level." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "This tool allows an administrator to apply UEFI dbx updates." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "This tool allows an administrator to debug UpdateCapsule operation." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "This tool allows an administrator to use the fwupd plug-ins without being installed on the host system." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can add a kernel argument of '%s', but it will only be active after restarting the computer." msgstr "This tool can add a kernel argument of '%s', but it will only be active after restarting the computer." #. TRANSLATORS: the %1 is a BIOS setting name. #. * %2 and %3 are the values, e.g. "True" or "Windows10" #, c-format msgid "This tool can change the BIOS setting '%s' from '%s' to '%s' automatically, but it will only be active after restarting the computer." msgstr "This tool can change the BIOS setting '%s' from '%s' to '%s' automatically, but it will only be active after restarting the computer." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can change the kernel argument from '%s' to '%s', but it will only be active after restarting the computer." msgstr "This tool can change the kernel argument from '%s' to '%s', but it will only be active after restarting the computer." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "This tool can only be used by the root user" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "This tool will read and parse the TPM event log from the system firmware." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Transient failure" #. TRANSLATORS: item is TRUE msgid "True" msgstr "True" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Trusted metadata" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Trusted payload" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Type" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI Bootservice Variables" msgstr "UEFI Bootservice Variables" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition may not be set up correctly" msgstr "UEFI ESP partition may not be set up correctly" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "UEFI ESP partition not detected or configured" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI Firmware Utility" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "UEFI Platform Key" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "UEFI Secure Boot" #. TRANSLATORS: longer description msgid "UEFI Secure Boot prevents malicious software from being loaded when the device starts." msgstr "UEFI Secure Boot prevents malicious software from being loaded when the device starts." #. TRANSLATORS: longer description msgid "UEFI boot service variables should not be readable from runtime mode." msgstr "UEFI boot service variables should not be readable from runtime mode." #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI bootservice variables" msgstr "UEFI bootservice variables" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "UEFI capsule updates not available or enabled in firmware setup" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "UEFI dbx Utility" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "UEFI firmware can not be updated in legacy BIOS mode" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "UEFI platform key" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "UEFI secure boot" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Unable to connect to service" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "Unable to find attribute" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Unbind current driver" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Unblocking firmware:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Unblocks a specific firmware from being installed" #. TRANSLATORS: command description msgid "Undo the host security attribute fix" msgstr "Undo the host security attribute fix" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Unencrypted" #. TRANSLATORS: command description msgid "Uninhibit the system to allow upgrades" msgstr "Uninhibit the system to allow upgrades" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Unknown" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Unknown Device" msgid "Unlock the device to allow access" msgstr "Unlock the device to allow access" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Unlocked" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Unlocks the device for firmware access" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Unmounts the ESP" #. TRANSLATORS: message shown after device has been marked for emulation msgid "Unplug and replug the device to continue the update process." msgstr "Unplug and replug the device to continue the update process." #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Unset the debugging flag during update" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Unsigned Payload" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Unsupported daemon version %s, client version is %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Untainted" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Updatable" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Update Error" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Update Image" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Update Message" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Update State" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Update failure is a known issue, visit this URL for more information:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Update now?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Update requires a reboot" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Update the stored cryptographic hash with current ROM contents" msgid "Update the stored device verification information" msgstr "Update the stored device verification information" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Update the stored metadata with current contents" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "Updates all specified devices to latest firmware version, or all devices if unspecified" msgid "Updating" msgstr "Updating" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Updating %s from %s to %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Updating %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Upgrade %s from %s to %s?" #. TRANSLATORS: ask the user to share, %s is something #. * like: "Linux Vendor Firmware Service" #, c-format msgid "Upload these anonymous results to the %s to help other users?" msgstr "Upload these anonymous results to the %s to help other users?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Urgency" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Use %s for help" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Use CTRL^C to cancel." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Use fwupdtool --help for help" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Use quirk flags when installing firmware" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "User has been notified" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Username" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Valid" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Validating ESP contents…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variant" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Vendor" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verifying…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Version" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Version[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "WARNING" #. TRANSLATORS: command description msgid "Wait for a device to appear" msgstr "Wait for a device to appear" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Waiting…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Watch for hardware changes" #. TRANSLATORS: check various UEFI and ACPI tables are unchanged after the #. update msgid "Will measure elements of system integrity around an update" msgstr "Will measure elements of system integrity around an update" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Write firmware from file into device" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Write firmware from file into one partition" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Writing file:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Writing…" #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from a recovery or installation disk, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "You should ensure you are comfortable restoring the setting from a recovery or installation disk, as this change may cause the system to not boot into Linux or cause other system instability." #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from the system firmware setup, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "You should ensure you are comfortable restoring the setting from the system firmware setup, as this change may cause the system to not boot into Linux or cause other system instability." #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "Your system is set up to the BKC of %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[APPSTREAM_ID]" msgstr "[APPSTREAM_ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[CHECKSUM]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[DEVICE-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[DEVICE-ID|GUID] [BRANCH]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[DEVICE-ID|GUID] [VERSION]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE]" msgstr "[DEVICE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[FILE FILE_SIG REMOTE-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[FILENAME1] [FILENAME2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[REASON]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[SETTING1] [ SETTING2]..." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[SETTING1] [SETTING2] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SMBIOS-FILE|HWIDS-FILE]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "default" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "fwupd TPM event log utility" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd plug-ins" fwupd-1.9.16/po/eo.po000066400000000000000000000036461460375044200143340ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # kristjan , 2019 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Esperanto (http://www.transifex.com/freedesktop/fwupd/language/eo/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: eo\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u sekundo" msgstr[1] "%u sekundoj" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Aldonita" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Nuligi" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Nuligita" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Ŝanĝita" #. TRANSLATORS: get interactive prompt msgid "Choose a device:" msgstr "Elektu aparaton:" #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Priskribo" #. success msgid "Done!" msgstr "Farita!" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Trovita" msgid "ID" msgstr "ID" msgid "Mode" msgstr "Reĝimo" #. TRANSLATORS: interface name, e.g. "Flash" #. TRANSLATORS: device name, e.g. 'ColorHug2' msgid "Name" msgstr "Nomo" msgid "OK" msgstr "Bone" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Protokolo" #. TRANSLATORS: these are areas of memory on the chip msgid "Region" msgstr "Regiono" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Forigita" msgid "Target" msgstr "Celo" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Nekonata" fwupd-1.9.16/po/es.po000066400000000000000000003556041460375044200143440ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Adolfo Jayme-Barrientos, 2021 # Adolfo Jayme-Barrientos, 2021 # César Enrique García , 2023 # Francisco Serrador, 2022-2023 # Francisco Serrador , 2022 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Spanish (http://app.transifex.com/freedesktop/fwupd/language/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: es\n" "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minuto restante" msgstr[1] "%.0f minutos restantes" msgstr[2] "%.0f minutos restantes" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "Actualización %s BMC" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "Actualización %s de Batería" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "Actualización %s de Mmicrocódigo CPU" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "Actualización %s de Cámara" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "Actualización %s de Configuración" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Actualización %s ME del Cliente" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Actualización %s del Controlador" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Actualización %s ME de Fabricante" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Actualización %s de Dispositivo" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "Actualización %s de Pantalla" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "Actualización %s de Muelle" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "Actualización %s de Dispositivo" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Actualización %s de Controlador Empotrado" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "Actualización %s del Lector de Huellas" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "Actualización %s de Unidad Flash" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "Actualización de GPU %s" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "%s Tabla de Gráficos de Actualización" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "Actualización %s de Teclado" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "Actualización %s ME" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "Actualización %s del Ratón" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "Actualización %s del Interfaz de Red" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "%s SSD Actualización" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "Actualización %s del Controlador de Almacenaje" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Actualización %s del Sistema" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "Actualización %s de TPM" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Actualización %s del Controlador Thunderbolt" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "Actualización %s de Touchpad" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "%s Muelle de USB Actualización" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "Receptor USB %s Actualizado" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Actualización %s" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s y todos los dispositivos conectados tal vez no están utilizables mientras se actualizan." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "%s ha aparecido: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "%s ha cambiado: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "%s ha desaparecido: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s no es actualizable actualmente" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s modo de fabricante" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s debe permanecer conectado para la duración de la actualización para evitar daño." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s debe permanecer enchufado en un agente de alimentación para la duración de la actualización para evitar daño." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s sobrecarga" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "Versión %s" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u día" msgstr[1] "%u días" msgstr[2] "%u días" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u dispositivo tiene una actualización disponible." msgstr[1] "%u dispositivos tienen una actualización disponible." msgstr[2] "%u dispositivos tienen una actualización disponible." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u dispositivo no es la mejor configuración conocida." msgstr[1] "%u dispositivos no son la mejor configuración conocida" msgstr[2] "%u dispositivos no son la mejor configuración conocida." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u hora" msgstr[1] "%u horas" msgstr[2] "%u horas" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u dispositivo local mantenido" msgstr[1] "%u dispositivos locales mantenidos" msgstr[2] "%u dispositivos locales mantenidos" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuto" msgstr[1] "%u minutos" msgstr[2] "%u minutos" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u segundo" msgstr[1] "%u segundos" msgstr[2] "%u segundos" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (umbral %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(obsoleto)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "Un PCR TRM ahora es un valor no válido" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "Repetición de Protección AMD" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "Escritura Firmware AMD" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "Protección de Reversión del Procesador AMD Seguro" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "ARCHIVE FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATFILE]" msgstr "METAINFO del ARCHIVADOR FIRMWARE [FIRMWARE] [METAINFO] [JCATFILE]" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Operación requerida:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Activar dispositivos" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Activar dispositivos pendientes" msgid "Activate the new firmware on the device" msgstr "Activa el firmware nuevo en el dispositivo" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Activando actualización del firmware" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Activando actualización del firmware para" #. TRANSLATORS: command description msgid "Adds devices to watch for future emulation" msgstr "Añade dispositivos que vigilen para emulación futura." #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Edad" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "¿Acuerda y habilita el remoto?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias a %s" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "Todos los PCR TMP son válidos ahora" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Todos los PCR TMP son válidos" #. TRANSLATORS: an application is preventing system updates msgid "All devices are prevented from update by system inhibit" msgstr "Las actualizaciones de todos los dispositivos están bloqueadas ya que el sistema está inhibido" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Todos los dispositivos del mismo tipo serán actualizados a la vez" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Permitir bajar versiones de firmware" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Permitir reinstalar versiones de firmware existentes" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Permitir intercambiar rama de firmware" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Ramificación alterna" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "Está en progreso una actualización" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Una actualización requiere un rearranque para completarla." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Una actualización requiere que el sistema se apague para completarla." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Responda sí a todas las preguntas" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Aplica actualizaciones del firmware" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Aplica actualización incluso cuando no sea advertido" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Aplicar archivos de actualización" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Aplicando actualización…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Firmware aprobado" msgstr[1] "Firmware aprobado" msgstr[2] "Firmware aprobado" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "¿Quiere solicitar de nuevo la siguiente vez?" #. TRANSLATORS: command description msgid "Asks the daemon to quit" msgstr "Solicita al demonio que salga" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Acoplar al modo firmware" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Autenticando…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Detalles requeridos para la autenticación" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Se requiere autenticación para degradar el firmware a una versión anterior en un dispositivo extraíble" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Se requiere autenticación para devolver el firmware a una versión anterior en esta máquina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Se requiere autenticación para modificar los parámetros del BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Se requiere autenticación para modificar un remoto configurado utilizado para actualizaciones de firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Se requiere autenticación para modificar la configuración del demonio" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "Requiere autenticarse para leer parámetros de BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Se requiere autenticación para establecer el listado de firmware aprobado" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Se requiere autenticación para firmar datos utilizando el certificado del cliente" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Se requiere autenticación para cambiar a la versión de firmware nueva" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "La autenticación es requerida para desbloquear un dispositivo" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Se requiere autenticación para actualizar el firmware en un dispositivo extraíble" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Autenticación es requerida para actualizar el firmware en esta máquina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Se requiere autenticación para actualizar las sumas de verificación almacenadas para el dispositivo" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Informes Automáticos" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "¿Subo automáticamente cada vez?" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "Retroceso de Protección de BIOS" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "Retrocede protección BIOS" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "BIOS actualiza enviados vía LVFS o Windows Update" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BUILDER-XML NOMBREARCHIVO-DST" msgid "BYTES" msgstr "BYTES" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Batería" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Vincula nuevo controlador del kernel" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Archivos de firmware bloqueados:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Versión bloqueada" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Firmware bloqueado:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Bloquea un origen del firmware específico siendo instalado" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Versión del Arranque" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Rama" #. TRANSLATORS: command description msgid "Build a cabinet archive from a firmware blob and XML metadata" msgstr "Construye un archivado gabinete desde un globo de firmware y metadatos XML" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Compila un archivo firmware" #. TRANSLATORS: longer description msgid "CPU Microcode must be updated to mitigate against various information-disclosure security issues." msgstr "El micro-código de CPU debe ser actualizarlo para mitigar varios informes descubiertos en problemas de seguridad." #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Cancelar" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Cancelado" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "No se puede aplicar como actualización dbx ya ha sido aplicado." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "No se puede aplicar actualizaciones en medio vivo" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Cambiado" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Comprueba coincidencias hash criptográficas del firmware" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Sumatorio" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Elija rama" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Elegir dispositivo" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Elija firmware" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Elija la publicación" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Elija el ESP:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Elija volúmen" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Vacía el resultado desde la última actualización" #. TRANSLATORS: error message msgid "Command not found" msgstr "No se encontró la orden" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Comunitariamente mantenido" #. TRANSLATORS: title prefix for the BIOS settings dialog msgid "Configuration Change Suggested" msgstr "Cambio Sugerido de Configuración" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "Configuración solamente leíble por el administrador del sistema" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Convierta un archivo firmware" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Creado" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Crítica" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Hay disponible verificación hash criptográfica" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Valor Actual" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Versión actual" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "DEVICE-ID|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Utilidad DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opciones de depuración" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Descomprimiendo…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Descripción" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Suelta un modo de cargador de arranque" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Detalles" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "¿Desviado desde la configuración mejor conocida?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Indicadores del Dispositivo" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "ID de dispositivo" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Dispositivo añadido:" #. TRANSLATORS: the device is already connected msgid "Device already exists" msgstr "El dispositivo ya existe" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "La carga de batería del dispositivo es muy baja" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "La carga de la batería del dispositivo es muy baja (%u%%, requiere %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "El dispositivo puede recuperar errores de flash" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "Dispositivo no puede ser utilizado mientras la tapa está cerrada" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Dispositivo modificado:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "La emulación de dispositivo no está activada." #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "El dispositivo firmware es necesario para hacer una comprobación de versión" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "El dispositivo está emulado" #. TRANSLATORS: device cannot be interrupted, for instance taking a phone call msgid "Device is in use" msgstr "Dispositivo en uso" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "El dispositivo está bloqueado" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "El dispositivo es requerido para instalar todas las publicaciones proporcionadas" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Dispositivo no alcanzable" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "El dispositivo no es alcanzable, o está fuera del conexión sin cableado" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "El dispositivo es utilizable para la duración de la actualización" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "El dispositivo está esperando a que se actualice para ser aplicado" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Dispositivo retirado:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "El dispositivo requiere electricidad AC para conectarlo" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "El dispositivo requiere una licencia de software para actualizarse" #. TRANSLATORS: longer description msgid "Device software updates are provided for this device." msgstr "Actualizaciones del software del dispositivo son proporcionados para este dispositivo." #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Escenarios de actualizaciones del dispositivo" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "Dispositivo mantiene intercambio a una rama de firmware diferente" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Método de actualización de dispositivo" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Actualización de dispositivo requiere activación" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Dispositivo respaldará el firmware antes de instalar" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Dispositivo no re-aparecerá tras actualización termine" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Dispositivos que han sido actualizados correctamente:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Dispositivos que no fueron actualizados correctamente:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Dispositivos sin actualizaciones del firmware disponible: " #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Dispositivos con la última versión disponible del firmware:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "No encontró ningún dispositivo con los GUID coincidan" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Deshabilitado" msgid "Disabled fwupdate debugging" msgstr "Deshabilitó depuración de fwupdate" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Desactiva un remoto proporcionado" #. TRANSLATORS: command line option msgid "Display version" msgstr "Representar la versión" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Distribución" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "No marcar para metadatos antiguos" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "No marcar para historial no comunicado" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "No marcar si las descargas remotas serían activadas" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "No marque o solicite para rearrancar tras actualizar" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "No incluye prefijo del boletín de dominio" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "No incluye prefijo de hora" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "No realiza comprobaciones seguras del dispositivo" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "No se solicita para dispositivos" #. TRANSLATORS: command line option msgid "Do not prompt to fix security issues" msgstr "No solicitar para reparar problemas de seguridad" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "No buscar el firmware cuando analice la interpretación" #. TRANSLATORS: warning message shown after update has been scheduled msgid "Do not turn off your computer or remove the AC adaptor while the update is in progress." msgstr "No apague su equipo o retire el adaptador CA mientras la actualización está en proceso." #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "No se lee a la base de datos histórica" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "¿Comprende las consecuencias de cambiar la rama del firmware?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "¿No desea deshabilitar esta característica para actualizaciones futuras?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "¿Desea activarlo ahora?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "¿Quiere recargar ahora este remoto?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "¿Desea subir repositorios automáticamente para actualizaciones futuras?" #. TRANSLATORS: command line option msgid "Don't prompt for authentication (less details may be shown)" msgstr "No solicitar autenticación (menor detalle puede ser mostrado)" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Finalizado." #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "¿Degradar %s desde %s a %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Degrada el firmware en un dispositivo" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Degradando %s desde %s a %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Degradando %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Descargar un archivo" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Descargando…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Vuelca datos SMBIOS desde un archivo" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Duración" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "ESP especificado no fue válido" #. TRANSLATORS: longer description msgid "Each system should have tests to ensure firmware security." msgstr "Cada sistema tendría pruebas para verificar la seguridad del firmware." #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "Emula un dispositivo utilizando un manifiesto JSON" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Emulado" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Anfitrión emulado" msgid "Enable" msgstr "Activar" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Activa mantenimiento de actualización de firmware en sistemas con mantenimiento" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "¿Habilitar remoto nuevo?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "¿Habilitar este remoto?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Activado" msgid "Enabled fwupdate debugging" msgstr "Depuración de fwupdate activada" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Habilitado si coincide con el hardware" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Activa un remoto proporcionado" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Active esta funcionalidad bajo su responsabilidad, lo que significa que tiene que contactar con el fabricante del equipo original ante cualquier problema causado por estas actualizaciones. Solo se deben reportar los problemas con el mismo proceso de actualización en $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Habilitar este remoto es realizado a su propio riesgo." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Cifrado" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "RAM cifrada" msgid "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." msgstr "RAM cifrado lo hace imposible para información que esté almacenada en memoria del dispositivo para ser leído si el chip de memoria está extraído y accedido." #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Final de vida" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Enumeración" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Elimina todo el historial de actualización del firmware" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Borrando…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Salir después de una pequeña pausa" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Salir después que sea cargado el motor" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Exporta una estructura del archivo firmware a XML" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Extrae una gota de firmware a imágenes" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "ARCHIVO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "ARCHIVO [ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "NOMBRE-DE-ARCHIVO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "ID-ARCHIVO CERTIFICA LLAVE-PRIVADA" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "NOMBRE-ARCHIVO DISP.-ALT-NOMBRE|DISP.-ALT-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "NOMBRE-ARCHIVO DISP.-ALT-NOMBRE|DISP.-ALT-ID [IMAGEN-ALT-NOMBRE|IMAGEN-ALT-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "ID-NOMBRE ID-DISPOSITIVO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "NOMBRE-ARCHIVO DESPLAZAMIENTO [TIPO-FIRMWARE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "NOMBRE-ARCHIVO [ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "NOMBRE-ARCHIVO [TIPO-FIRMWARE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "NOMRE-ARCHIVO-SRC NOMBRE-ARCHIVO-DST [TIPO-FIRMWARE-SRC] [TIPO-FIRMWARE-DST]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "ID-ARCHIVO|COMPROBANTE1[,COMPROBANTE2][,COMPROBANTE3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Erróneo" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Error al aplicar actualización" #. TRANSLATORS: error message for Windows msgid "Failed to connect to Windows service, please ensure it's running." msgstr "Ha fallado al conectar al servicio Windows, debe asegurar que esté en ejecución." #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Error al conectar al demonio" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Error al obtener dispositivos pendientes" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Error al instalar actualización de firmware" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Erróneo en carga dbx local" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Erróneo en carga dbx quirks" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Erróneo en carga dbx del sistema" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Error al bloquear" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "No se pudieron procesar los argumentos" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "No se pudo procesar el archivo" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Error al interpretar indicadores para --filter" msgid "Failed to parse flags for --filter-release" msgstr "Ha fallado al interpretar indicaciones para --filter-release" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Intérprete erróneo del dbx local" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Error al rearrancar" #. TRANSLATORS: a feature is something like "can show an image" msgid "Failed to set front-end features" msgstr "Erróneo al fijar características de front-end" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Error al fijar el modo de presentación" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Error al validar el contenido de ESP" #. TRANSLATORS: item is FALSE msgid "False" msgstr "Falso" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "ID de archivo" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Firma del ID de archivo" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Nombre del Archivo Origen" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Se requiere el nombre de archivo" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filtro con un conjunto de indicadores del dispositivo utilizando un prefijo ~ a excluir, p. ej. 'internet, ~necesita-rearrancar'" #. TRANSLATORS: command line option msgid "Filter with a set of release flags using a ~ prefix to exclude, e.g. 'trusted-release,~trusted-metadata'" msgstr "Filtrar con un conjunto de indicadores de publicación utilizando un prefijo ~ para excluir, p. ej., 'trusted-release,~trusted-metadata'" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Prueba de Firmware" #. TRANSLATORS: longer description msgid "Firmware Attestation checks device software using a reference copy, to make sure that it has not been changed." msgstr "Garantía de firmware comprueba que el software del dispositivo utiliza una copia de referencia, para asegurar que no haya sido modificado." #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Descriptor BIOS Firmware" #. TRANSLATORS: longer description msgid "Firmware BIOS Descriptor protects device firmware memory from being tampered with." msgstr "Descriptor del Firmware de BIOS protege dispositivo de memoria del firmware con el cual se manipuló." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Región BIOS Firmware" #. TRANSLATORS: longer description msgid "Firmware BIOS Region protects device firmware memory from being tampered with." msgstr "Región Firmware BIOS protege memoria del firmware del dispositivo con el cual se está manipulando." #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "URI de Firmware Base" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Actualizar Firmware de Servicio D-Bus" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Demonio de Actualización de Firmware" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Verificación para Actualizador del Firmware" msgid "Firmware Updater Verification checks that software used for updating has not been tampered with." msgstr "La Verificación de Actualizador del Firmware comprueba que el software utilizado para actualizar no ha sido manipulado." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Actualizaciones del firmware" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Utilidad Firmware" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Protección de Escritura del Firmware" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Bloqueo de Protección de Escritura Firmware" #. TRANSLATORS: longer description msgid "Firmware Write Protection protects device firmware memory from being tampered with." msgstr "Protección de Escritura del Firmware del de la memoria del firmware del dispositivo con el que es condonado. " #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Garantía de firmware" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Firmware ya está bloqueado" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Firmware aún no está bloqueado" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Los metadatos de firmware no han sido actualizados para %u día y puede no estar al día." msgstr[1] "Los metadatos de firmware no han sido actualizados para %u días y puede no estar al día." msgstr[2] "Los metadatos de firmware no han sido actualizados para %u días y puede no estar al día." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Actualizaciones del firmware" msgid "Firmware updates are not supported on this machine." msgstr "Las actualizaciones de firmware no están mantenidos en esta máquina." msgid "Firmware updates are supported on this machine." msgstr "Las actualizaciones de firmware están mantenidas en esta máquina." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Actualizaciones firmware desactivada; ejecute 'fwupdmgr unlock' para habilitar" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Indicadores" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Fuerza la acción relajando algunas comprobaciones en tiempo de ejecución" msgid "Force the action ignoring all warnings" msgstr "Fuerza la acción descartando todas las advertencias" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Encontrado" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Detectado Cifrado de Disco Completo" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "Secretos de cifrado de disco completo tal vez está invalidado cuando modernice" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "Plataforma Fusionada" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Plataforma fusionada" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID" msgstr[2] "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "GUID|DEVICE-ID" msgstr "GUID|ID-DISPOSITIVO" msgid "Get BIOS settings" msgstr "Obtener parámetros de BIOS" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Obtenga todos los indicadores del dispositivo admitido por fwpd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Obtiene todos los dispositivos que mantengan actualizaciones de firmware" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Obtiene todos los complementos registrados activados con el sistema" #. TRANSLATORS: command description msgid "Get device report metadata" msgstr "Obtenga el informe de metadatos del dispositivo" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Obtiene detalles acerca de un archivo firmware" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Obtiene los remotos configurados" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Obtiene los atributos de seguridad del hospedaje" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Obtiene el listado de firmware aprobado" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Obtiene el listado de firmware bloqueado" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Obtiene el listado de actualizaciones para hardware conectado" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Obtiene las publicaciones para un dispositivo" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Obtiene los resultados desde la última actualización" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWIDS-ARCHIVO" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "El hardware está esperando que sea enchufado" #. TRANSLATORS: the release urgency msgid "High" msgstr "Alta" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Eventos de Seguridad de Hospedaje" #. TRANSLATORS: error message for unsupported feature #, c-format msgid "Host Security ID (HSI) is not supported" msgstr "ID de Seguridad de Hospedaje (HSI) no está mantenido" #. TRANSLATORS: success, so say thank you to the user msgid "Host Security ID attributes uploaded successfully, thanks!" msgstr "Atributos de ID de seguridad del anfitrión correctamente subido, ¡gracias!" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "ID de seguridad de hospedaje:" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "INHIBIT-ID" msgstr "INHIBIR-ID" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "Protección de dispositivo IOMMU" #. TRANSLATORS: longer description msgid "IOMMU Protection prevents connected devices from accessing unauthorized parts of system memory." msgstr "Protección IOMMU previene dispositivos conectados desde partes de acceso no autorizadas de memoria del sistema." #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "Protección de dispositivo IOMMU desactivado" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "Protección de dispositivo IOMMU activado" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Inactivo…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Descarta comprobaciones estrictas de SSL cuando descargue archivos" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Hace caso omiso a errores del sumatorio del firmware" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Hace caso omiso a errores del concordancia del hardware" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Hace caso omiso a comprobaciones validación de seguridad" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Descartando comprobaciones estrictas SSL, para hacer esto automáticamente en la futura exportación DISABLE_SSL_STRICT dentro de su entorno" #. TRANSLATORS: the inhibit ID is a short string like dbus-123456 #, c-format msgid "Inhibit ID is %s." msgstr "ID inhibido es %s." #. TRANSLATORS: command description msgid "Inhibit the system to prevent upgrades" msgstr "Inhibe el sistema para bloquear actualizaciones" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Duración de Instalación" #. TRANSLATORS: command description msgid "Install a firmware file in cabinet format on this hardware" msgstr "Instala un archivo firmware en formato de cabina en este hardware" #. TRANSLATORS: command description msgid "Install a raw firmware blob on a device" msgstr "Instale un globo firmware en crudo en un dispositivo" #. TRANSLATORS: command description msgid "Install a specific firmware file on all devices that match" msgstr "Instala un archivo firmware específico en todos los dispositivos que coincidan" #. TRANSLATORS: command description msgid "Install a specific firmware on a device, all possible devices will also be installed once the CAB matches" msgstr "Instala un firmware específico en un dispositivo, todos los dispositivos posibles además serán instalados una vez que coincida el CAB" msgid "Install old version of signed system firmware" msgstr "Instala versión antigua de firmware del sistema firmado" msgid "Install old version of unsigned system firmware" msgstr "Instalar una versión antigua de firmware del sistema no firmado" msgid "Install signed device firmware" msgstr "Instalar firmware del dispositivo firmado" msgid "Install signed system firmware" msgstr "Instalar firmware del sistema firmado" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Instala primero el dispositivo antecedente" msgid "Install unsigned device firmware" msgstr "Instalar firmware del dispositivo no firmado" msgid "Install unsigned system firmware" msgstr "Instalar firmware del sistema sin firmar" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Instalando Firmware…" #. TRANSLATORS: stay on one firmware version unless the new version is #. explicitly #. * specified msgid "Installing a specific release is explicitly required" msgstr "Se requiere una publicación específica para ser instalado" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Instalando actualización del firmware…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Instalando en %s…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "Instalando esta actualización tal vez anule cualquier garantía del dispositivo." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Entero" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "ACM protegido Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "ACM protegido Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Error de normativa Intel BootGuard" msgid "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." msgstr "Normativa Errónea de Intel BootGuard asegura que el dispositivo no continúe iniciar si su software del dispositivo haya sido manipulado." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Intel BootGuard Fuse" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Intel BootGuard OTP fuse" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Arranque verificado Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Error de normativa Intel BootGuard" #. TRANSLATORS: longer description msgid "Intel BootGuard prevents unauthorized device software from operating when the device is started." msgstr "Intel BootGuard impide software de dispositivo no autorizado al operar cuando el dispositivo es arrancado." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Arranque verificado Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET Activo" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET Habilitada" #. TRANSLATORS: longer description msgid "Intel Control-Flow Enforcement Technology detects and prevents certain methods for running malicious software on the device." msgstr "Tecnología de Refuerzo de Control de Flujo Intel detecta y previene ciertos métodos para ejecución de software malicioso en el dispositivo." #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS Mitigation" msgstr "Mitigación de GDS de Intel" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS mitigation" msgstr "Mitigación de GDS de Intel" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Modo de Manufactura del Motor de Gestión Intel" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Modo de Manufactura del Motor de Gestión Intel" #. TRANSLATORS: longer description msgid "Intel Management Engine Override disables checks for device software tampering." msgstr "Descarta el Gestor de Motor Intel desactiva las comprobaciones para manipulación de software del dispositivo." #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Versión del Motor de Gestión Intel" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: longer description msgid "Intel Supervisor Mode Access Prevention ensures critical parts of device memory are not accessed by less secure programs." msgstr "Prevención de Acceso al Modo Supervisor de Intel asegura partes críticas de memoria del dispositivo que no están accedidas por programas menos seguros." #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Dispositivo interno" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "No válida" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Argumentos no válidos" #. TRANSLATORS: error message msgid "Invalid arguments, expected GUID" msgstr "Argumentos no válidos, se esperaba GUID" #. TRANSLATORS: error message msgid "Invalid arguments, expected at least ARCHIVE FIRMWARE METAINFO" msgstr "Argumentos no válidos, esperados al menos ARCHIVE FIRMWARE METAINFO" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Está degradada" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Está dentro del modo cargador de arranque" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Está modernizada" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Informe" msgstr[1] "Informes" msgstr[2] "Informes" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "CLAVE,VALOR" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "El Kernel no está manoseado por más tiempo" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "El Kernel está manoseado" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Bloqueo desactivado del kernel" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Bloqueo activado del kernel" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Anillo" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "LOCALIZACIÓN" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Último modificado" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Falta menos de un minuto" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licencia" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Kernel Linux bloqueado" #. TRANSLATORS: longer description msgid "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgstr "El modo Cerrojo del Kernel Linux previene al administrador (root) las cuentas desde el acceso y modificación de partes críticas del software del sistema." msgid "Linux Kernel Swap temporarily saves information to disk as you work. If the information is not protected, it could be accessed by someone if they obtained the disk." msgstr "El Intercambio del Kernel Linux guarda información temporalmente al disco para que pueda trabajar. Si la información no está protegida, pudo ser accedido por alguno si obtuvieron el disco." #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Verificación de Kernel Linux" msgid "Linux Kernel Verification makes sure that critical system software has not been tampered with. Using device drivers which are not provided with the system can prevent this security feature from working correctly." msgstr "La Verificación del Kernel Linux asegura que el software crítico del sistema no ha sido manoseado. Utilizando controladores del dispositivo los cuales no sean proporcionados con el sistema pueden prevenir esta característica de seguridad desde el trabajo correcto." #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Intercambio (swap) de Linux" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "«Linux Vendor Firmware Service» (firmware estable)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "«Linux Vendor Firmware Service» (firmware de pruebas)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Kernel Linux" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Kernel Linux bloqueado" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Intercambio de Linux" #. TRANSLATORS: command description msgid "List EFI variables with a specific GUID" msgstr "Enumera variables EFI con un GUID específico" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Listado de apuntes en dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Enumera actualizaciones de firmware mantenidas" #. TRANSLATORS: command description msgid "List the available firmware GTypes" msgstr "Enumera los tipos GType del firmware que estén disponibles." #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Enumera los tipos de firmware disponibles" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Enumera archivos en la ESP" #. TRANSLATORS: command description msgid "Load device emulation data" msgstr "Carga los datos de emulación de dispositivo" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "Cargado desde un módulo externo" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Cargando…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Bloqueado" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Baja" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "Declaración de llave MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "Manifestar clave MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "Modo manufacturante MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "Sobrecarga MEI" msgid "MEI version" msgstr "Versión MEI" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Activa manualmente complementos específicos" #. TRANSLATORS: longer description msgid "Manufacturing Mode is used when the device is manufactured and security features are not yet enabled." msgstr "Modo Fabricante se utiliza cuando el dispositivo es fabricado y las características de seguridad aún no están activadas." #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Longitud máxima" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Valor máximo" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Mediana" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Firma Metadatos" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI Metadatos" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadatos pueden obtenerse desde el Servicio de Firmware del Proveedor Linux." #. TRANSLATORS: error message for a user who ran fwupdmgr #. * refresh recently msgid "Metadata is up to date; use --force to refresh again." msgstr "Metadatos están actualizados; utilice --force para actualizarlo de nuevo." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Versión Mínima" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Longitud mínima" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Valor mínimo" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "No coincidió demonio y cliente, utilice %s en su lugar" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Codifica un valor de configuración del demonio" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifica un remoto proporcionado" msgid "Modify a configured remote" msgstr "Modificar un remoto configurado" msgid "Modify daemon configuration" msgstr "Modifica la configuración del demonio" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Monitoriza al demonio para eventos" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Monta el ESP" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Necesita rearrancar tras la instalación" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Requiere reinicio" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Necesita apagar tras la instalación" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Versión nueva" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "No se especificó ninguna acción." #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Ninguna degradación para %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Ningún ID de firmware encontrado" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "Ningún firmware encontrado" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Ningún hardware detectado con capacidad de actualización del firmware" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Ningún complemento encontrado" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Ninguna liberación disponible" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Ningunos remotos están activados actualmente tal que ningún metadato está disponible." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Ningún remoto disponible" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Ningún dispositivo actualizable" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "No hay actualizaciones disponibles" msgid "No updates available for remaining devices" msgstr "No hay actualizaciones disponibles para dispositivos restantes" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Ninguna actualización fue aplicada" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "No aprovado" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "No encontrado" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "No compatible" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "APROBADO" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "¡Perfecto!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Versión anterior" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Solamente muestra el único valor de PCR" #. TRANSLATORS: command line option msgid "Only use peer-to-peer networking when downloading files" msgstr "Solamente utilizar redes pareja a pareja cuando descargue archivos" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Solamente las modernizaciones de versión están permitidas" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Formato de salida en JSON" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Supera la ruta del ESP predeterminado" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Firmware" msgstr "Firmware P2P" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Metadata" msgstr "Metadatos P2P" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "RUTA" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Interpreta y muestra detalles acerca de un archivo de firmware" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Interpretando actualización dbx…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Interpretando sistema dbx…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Contraseña" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Parchea una gota de firmware en un desplazamiento conocido" msgid "Payload" msgstr "Carga" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Pendiente" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Porcentaje completo" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "¿Realizar operación?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Depuración de Plataforma" #. TRANSLATORS: longer description msgid "Platform Debugging allows device security features to be disabled. This should only be used by hardware manufacturers." msgstr "Depuración de plataforma permite características de seguridad del dispositivo que sean desactivadas. Esto solamente serían utilizadas por fabricante del hardware." #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Depuración de plataforma" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Asegure que tenga la llave de recuperación del volumen antes de continuar." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Introduzca un número desde 0 hasta %u: " #. TRANSLATORS: the user isn't reading the question -- please don't translate #. * 'Y' or 'N' as these are hardcoded msgid "Please enter either Y or N: " msgstr "Introduzca o S o N:" #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Dependencias ausentes del complemento" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Valores Posibles" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "Protección DMA de pre-arranque" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Protección DMA de pre-arranque" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "Protección DMA de pre-arranque está desactivada" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "Protección DMA de pre-arranque está activada" #. TRANSLATORS: longer description msgid "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." msgstr "La protección del DMA del pre-arranque previene acceder a dispositivos a la memoria del sistema después de ser conectada al equipo." #. TRANSLATORS: warning message msgid "Press unlock on the device to continue the update process." msgstr "Presione desbloquear en el dispositivo para continuar el proceso de actualización." #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Versión anterior" msgid "Print the version number" msgstr "Escribe el número de la versión" msgid "Print verbose debug statements" msgstr "Escribe la verborrea de mensajes de depuración" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioridad" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Problemas" msgid "Proceed with upload?" msgstr "¿Proceder con subida?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Comprobaciones de Seguridad del Procesador" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Procesador retira protección" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Titular" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Solicita mantenimiento para actualizar firmware" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "ID-REMOTO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "ID-REMOTO CLAVE VALOR" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Solo Lectura" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Lee una gota del firmware desde un dispositivo" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Lea un firmware desde un dispositivo" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Lea firmware desde el dispositivo al archivo" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Lea firmware desde una partición a un archivo" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Leyendo desde %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Leyendo…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Reiniciando…" #. TRANSLATORS: how often we should refresh the metadata msgid "Refresh Interval" msgstr "Intervalo de recarga" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Recarga metadatos desde servidor remoto" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "¿Reinstalar %s a %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Reinstala firmware actual en el dispositivo" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Reinstalar firmware en un dispositivo" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Reinstalando %s con %s... " #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Ramificación de Publicación" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Indicadores de Publicación" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "ID Publicación" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID Remoto" #. TRANSLATORS: command description msgid "Removes devices to watch for future emulation" msgstr "Extrae dispositivos para vigilar en emulación futura." #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Sustituye datos dentro de un archivo firmware existente" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI de Comunicado" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Comunicado para servidor remoto" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Solicitud cancelada" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "Sistema de archivos efivarfs requerido no fue encontrado" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "Hardware requerido no fue encontrado" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Requiere un cargador de arranque" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Requiere conexión a Internet" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "¿Reiniciar ahora?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "¿Reinicio el demonio para crear el cambio efectivo?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Reiniciando dispositivo…" #. TRANSLATORS: command description msgid "Retrieve BIOS settings. If no arguments are passed all settings are returned" msgstr "Obtiene la configuración de la BIOS. Si no se proporciona ningún argumente se devuelve toda la configuración." #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Devuelve todos los ID de hardware para la máquina" #. TRANSLATORS: longer description msgid "Rollback Protection prevents device software from being downgraded to an older version that has security problems." msgstr "Retroceder Protección previene software de dispositivo sea degradado a una versión más antigua que tenga problemas de seguridad." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Ejecute `fwupdmgr get-upgrades` para más información." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Ejecuta el complemento compuesto vacía rutina cuando utiliza gota de instalación" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Ejecute el complemento compuesto de rutina preparada cuando utilice install-blob" #. TRANSLATORS: tell a user how to get information #, c-format msgid "Run without '%s' to see" msgstr "Ejecutar sin '%s' para ver" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "Ejecutando kernel que es demasiado antiguo" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Sufijo de tiempo de ejecución" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "DECLARANDO VALOR" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING1 VALUE1 [SETTING2] [VALUE2]" msgstr "PARÁMETRO1 VALOR1 [PARÁMETRO2] [VALOR2]" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "Descriptor BIOS SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "Región BIOS SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "Candado SPI" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "SPI solicita protección" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "Escritura SPI" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "Protección de escritura SPI" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "CONTROLADOR DE SUBSISTEMA [ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Guarda un archivo que permita generación de los ID hardware" #. TRANSLATORS: command description msgid "Save device emulation data" msgstr "Guarda los datos de emulación de dispositivo" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "Incremento Escalar" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Planifica instalación para siguiente rearranque cuando sea posible" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Planificando…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Arranque Seguro desactivado" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Arranque Seguro activado" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Vea %s para más detalles." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Vea %s para más información." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Dispositivo seleccionado" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Volumen seleccionado" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Número de Serie" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "Fija parámetros de la BIOS '%s' utilizando '%s'." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "Fije un parámetro de la BIOS" msgid "Set one or more BIOS settings" msgstr "Fije uno o más parámetros de BIOS" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Fija el indicador de depuración durante actualización" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "Fija uno o más parámetros de BIOS" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Establece el firmware aprobado del listado" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Tipo paramétrico" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Los parámetros aplicarán tras reiniciar el sistema" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Compartir historial de firmware con los desarrolladores" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Muestra todos los resultados" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Mostrar versiones del cliente y el servicio" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Muestra verborrea de información del demonio para un dominio particular" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Muestra información de depuración para todos los dominios" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Mostrar información extra de depuración" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Muestra dispositivos que no son actualizables" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Mostrar información de depuración adicional" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Muestra historial de actualizaciones del firmware" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Muestra la versión calculada del dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Muestra el boletín de depuración desde la última actualización intentada" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Muestra la información del estado de actualización del firmware" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "¿Apago ahora?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Firma un firmware con una llave nueva" msgid "Sign data using the client certificate" msgstr "Firmar datos utilizando el certificado del cliente" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Firmar datos utilizando el certificado del cliente" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Firma los datos subidos con el certificado del cliente" msgid "Signature" msgstr "Firma" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Carga Firmada" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Tamaño" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Alguno de los secretos de la plataforma tal vez sea invalidada cuando actualice este firmware." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Origen" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Especifique Proveedor/ID (varios) del Producto del dispositivo DFU" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Específicamente el archivo de la base de datos sbx" msgid "Specify the number of bytes per USB transfer" msgstr "Especifique el número de bytes por transferencia USB" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "Cadena" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Correcto" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Se activaron correctamente todos los dispositivos" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Remoto desactivado correctamente" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Descarga correctamente metadatos nuevos: " #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Remoto activado y recargado correctamente" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Remoto activado correctamente" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmware instalado correctamente" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Valor de configuración modificado correctamente" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Remoto modificado correctamente" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metadatos actualizados manualmente correctamente" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Sumatorio de dispositivo actualizado correctamente" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Se ha subido %u parte correctamente" msgstr[1] "Se han subido %u partes correctamente" msgstr[2] "Se han subido %u partes correctamente" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Comprobación correctamente verificada del dispositivo" #. TRANSLATORS: the device showed up in time #, c-format msgid "Successfully waited %.0fms for device" msgstr "Ha esperado correctamente %.0f ms para dispositivo" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Totales" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Mantenido" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "CPU admitida" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Mantenido en servidor remoto" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Suspendido a Descanso" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Suspender a RAM" #. TRANSLATORS: longer description msgid "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Suspensión para Descanso permite que el dispositivo se duerma rápidamente con el fin de guardar energía. Mientras que el dispositivo esté suspendido, su memoria pudo ser retirada físicamente y su información accedida." #. TRANSLATORS: longer description msgid "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Suspensión a RAM permite al dispositivo que rápidamente se vuelva durmiente con el fin de guardar energía. Mientras el dispositivo ha estado suspendido, su memoria pudo ser físicamente retirada y su información accedida." #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Suspendido-a-descanso" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Suspender a RAM" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "¿Ramificación intercambiada desde %s hasta %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Intercambiar rama firmware en el dispositivo" #. TRANSLATORS: this CLI tool is now preventing system updates msgid "System Update Inhibited" msgstr "Actualización de Sistema Inhibido" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "Energía del sistema es demasiado baja para realizar la actualización" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "La carga del sistema es muy baja para realizar una actualización (%u%%, requiere %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "Sistema requiere energía externa" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEXTO" #. TRANSLATORS: longer description msgid "TPM (Trusted Platform Module) is a computer chip that detects when hardware components have been tampered with." msgstr "TPM (Trusted Platform Module) es un chip del equipo que detecta cuando los componentes del hardware han sido manipulados." #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "Reconstrucción PCRO de TPM" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "Reconstrucción PCRO de TPM no es válida" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "Reconstrucción PCRO de TPM ahora es válida" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "Configuración de Plataforma TPM" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "Reconstrucción TPM" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "Los PCR vacíos de TPM" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Etiqueta" msgstr[1] "Etiquetas" msgstr[2] "Etiquetas" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "Etiquetado para emulación" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Manoseado" msgid "Target" msgstr "Objetivo" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Testea un dispositivo utilizando una declaración JSON" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Probado" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "Probado por %s" #. TRANSLATORS: someone we trust has tested this msgid "Tested by trusted vendor" msgstr "Probado por proveedor confiado" #. TRANSLATORS: longer description msgid "The Intel Management Engine Key Manifest must be valid so that the device firmware can be trusted by the CPU." msgstr "El Motor de Gestión Intel Manifiesta Clave debe ser válida tal que el firmware del dispositivo pueda ser confiado por la CPU." #. TRANSLATORS: longer description msgid "The Intel Management Engine controls device components and needs to have a recent version to avoid security issues." msgstr "El Motor de Gestión de Intel controla componentes del dispositivo y necesita tener una versión reciente para evitar problemas de seguridad." #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "El LVFS es un servicio libre que opera como una entidad independiente legal y no tienen contacto con $OS_RELEASE:NAME$. Su distribuidor no está verificado y ninguna de las actualizaciones del firmware para compatibilidad con su sistema o dispositivos conectados. Todo el firmware está distribuido únicamente por el fabricante del equipo original." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Platform Configuration is used to check whether the device start process has been modified." msgstr "La Configuración de la Plataforma TPM (Trusted Platform Module) se utiliza si el dispositivo inicial del proceso ha sido modificado." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Reconstruction is used to check whether the device start process has been modified." msgstr "La Reconstrucción de TPM (Trusted Platform Module) se utiliza para comprobar si el proceso de arranque del dispositivo ha sido modificado." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "El PCR0 TPM difiere desde reconstrucción." #. TRANSLATORS: longer description msgid "The UEFI Platform Key is used to determine if device software comes from a trusted source." msgstr "La Clave de Plataforma UEFI es utilizada para determinar si el software del dispositivo viene desde un origen confiado." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "¡El demonio ha cargado código de 3ª parte y ya no está mantenido por los desarrolladores actuales!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "La versión del dispositivo no coincidió: obtuvo %s, esperaba %s" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "El firmware desde %s no está suministrado por %s, el fabricante del hardware." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "El reloj del sistema no ha sido fijado correctamente y los archivos descargados pueden fallar." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been re-inserted." msgstr "La actualización continuará cuando el dispositivo del cable USB ha sido re-insertado." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged and then re-inserted." msgstr "La actualización continuará cuando el cable USB del dispositivo haya sido desenchufado y después re-insertado." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged." msgstr "La actualización continuará cuando el cable del dispositivo USB haya sido desenchufado." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "El proveedor no admite ninguna nota de publicación." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Hay dispositivos con problemas:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "No hay archivos firmware bloqueados" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "No hay ningún firmware aprobado." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "Este dispositivo será revertido a %s cuando la instrucción %s sea realizada." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "Este firmware se proporcionó por miembros de la comunidad LVFS y no proporcionó (o mantuvo) por el proveedor del hardware original." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Este paquete no ha sido validado, quizá no funciona apropiadamente." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Este programa tal vez solo funciona correctamente como root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Este remoto contiene firmware el cual no está embargado, pero aún está siendo probado por el proveedor de hardware. Debería asegurarse que tenga una manera de degradar el firmware a la versión anterior manualmente si falla la actualización del firmware." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "Este sistema no mantiene parámetros del firmware" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Este sistema tiene problemas en tiempo de ejecución HSI." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Este sistema tiene un nivel de seguridad HSI bajo." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Esta herramienta permite un administrados para aplicar actualizaciones dbx de UEFI." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Esta herramienta permite un administrador depura operación UpdateCapsule." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Esta herramienta permite un administrados para solicitar y controla el demonio fwupd, permitiéndolos entonces realizar acciones tales como instalar o bajar la versión del firmware." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Esta herramienta permite que un administrador utilice los complementos fwupd sin ser instalados en el sistema hospedado." #. TRANSLATORS: the %1 is a BIOS setting name. #. * %2 and %3 are the values, e.g. "True" or "Windows10" #, c-format msgid "This tool can change the BIOS setting '%s' from '%s' to '%s' automatically, but it will only be active after restarting the computer." msgstr "Esta herramienta puede cambiar los parámetros de BIOS «%s» desde «%s» a «%s» automáticamente, pero serán activados únicamente tras rearrancar el equipo." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Esta herramienta solo puede ser utilizado por el usuario root" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Esta herramienta leerá e interpretará boletín de evento TPM desde el firmware del sistema." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Transición errónea" #. TRANSLATORS: item is TRUE msgid "True" msgstr "Cierto" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Metadatos confiados" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Carga confiada" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipo" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI Bootservice Variables" msgstr "Variables del servicio de arranque de UEFI" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition may not be set up correctly" msgstr "La partición ESP de la UEFI puede que no esté correctamente configurada" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "Partición ESP UEFI no detectada o configurada" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Utilidad Firmware de UEFI" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "Llave de Plataforma UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "Arranque Seguro UEFI" #. TRANSLATORS: longer description msgid "UEFI Secure Boot prevents malicious software from being loaded when the device starts." msgstr "UEFI Secure Boot previene software malicioso sea cargado cuando el dispositivo de inicie." #. TRANSLATORS: longer description msgid "UEFI boot service variables should not be readable from runtime mode." msgstr "Variables del servicio de arranque UEFI no sería legible desde modo en tiempo de ejecución." #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI bootservice variables" msgstr "Variables de servicio de arranque UEFI" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "Actualizaciones de cápsulas UEFI no disponible o activada en configuración de firmware" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "Utilidad dbx UEFI" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "Firmware UEFI no puede ser actualizado en modo de BIOS heredada" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "Clave UEFI de la plataforma" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "Arranque seguro UEFI" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Imposible conectar al servicio" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "No es capaz de encontrar un atributo" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Desvincula controlador actual" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Desbloquear firmware:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Desbloquea un firmware específico para que sea instalado" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Descifrado" #. TRANSLATORS: command description msgid "Uninhibit the system to allow upgrades" msgstr "Desinhibe el sistema para permitir actualizaciones" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Desconocido/a" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Dispositivo Desconocido" msgid "Unlock the device to allow access" msgstr "Desbloquea el dispositivo para permitir acceso" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Desbloqueado" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Desbloquea el dispositivo para acceso al firmware" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Desmontajes de ESP" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Suelta el indicador de depuración durante actualización" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Carga No Firmada" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Versión %s no mantenida del demonio, versión de cliente es %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "No manoseado" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Actualizable" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Actualizar Error" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Actualizar Imagen" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Actualizar Mensaje" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Actualizar Estado" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Error de actualización es un problema conocido, visite este URL para más información:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "¿Actualizar ahora?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Actualización requiere un rearranque" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Actualiza el hash criptográfico almacenado con el contenido de la actual ROM" msgid "Update the stored device verification information" msgstr "Actualizar la información de verificación del dispositivo almacenada" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Actualiza los metadatos almacenados con contenido actual" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "Actualiza todos los dispositivos especificados a la última versión del firmware, o todos los dispositivos si no especificados" msgid "Updating" msgstr "Actualizando" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Actualizar %s desde %s a %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Actualizando %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "¿Moderniza %s desde %s hasta %s?" #. TRANSLATORS: ask the user to share, %s is something #. * like: "Linux Vendor Firmware Service" #, c-format msgid "Upload these anonymous results to the %s to help other users?" msgstr "¿Subo estos resultados anónimos al %s para ayudar a otros usuarios?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Subiendo comunicados firmware ayuda proveedores de hardware para identificar rápidamente fallos y actualizaciones correctas en dispositivos reales." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Urgencia" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Utilice %s para ayuda" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Utilice CTRL^C para cancelar." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Emplee fwupdtool --help para ayuda" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Emplee indicadores particulares cuando instale firmware" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "El usuario ha sido notificado" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Nombre del usuario" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Válido" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Validación de contenidos ESP…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variante" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Proveedor" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verificando…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Versión" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Versión" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "AVISO:" #. TRANSLATORS: command description msgid "Wait for a device to appear" msgstr "Espera que aparezca un dispositivo" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Esperando…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Vigía para cambios del hardware" #. TRANSLATORS: check various UEFI and ACPI tables are unchanged after the #. update msgid "Will measure elements of system integrity around an update" msgstr "Medirá elementos del integridad del sistema relativo a una actualización" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Escriba firmware desde el archivo al dispositivo" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Escriba firmware desde el archivo a un dispositivo" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Escribiendo archivo:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Escribiendo…" #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from the system firmware setup, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Aseguraría que sea confortable restaurando los parámetros desde la configuración del firmware del sistema, como este cambio pueda causar que el sistema no arranque en Linux o causar otra inestabilidad del sistema." #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Su distribuidor tal vez no ha verificado ninguna de las actualizaciones de firmware para compatibilidad con su sistema o dispositivos conectados." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Su hardware tal vez está dañado utilizando este firmware, e instalando esta publicación tal vez anula cualquier garantía con %s." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "Su sistema está actualizado a la BKC de %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[COMPROBANTE-SUMATORIO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[ID-DISPOSITIVO|GUID] [RAMIFICACIÓN]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[ID-DISPOSITIVO|GUID] [VERSIÓN]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[ARCHIVO FIRMA_ARCH ID-REMOTO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[ARCHIVO-NOMBRE1] [ARCHIVO-NOMBRE2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[RAZÓN]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[PARÁMETRO1] [PARÁMETRO2]..." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[PARÁMETRO1] [PARÁMETRO2] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[ARCHIVO-SMBIOS|ARCHIVO-HWIDS]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "predeterminado" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "evento TPM de fwupd para utilidad del boletín" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "complementos fwpd" fwupd-1.9.16/po/eu.po000066400000000000000000000236231460375044200143370ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # assar , 2017,2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Basque (http://app.transifex.com/freedesktop/fwupd/language/eu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: eu\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "Minutu%.0f falta da" msgstr[1] "%.0f minutu falta dira" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Onartu eta gaitu urrunekoa?" #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Erantzun bai galdera guztiei" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Onartutako firmwarea:" msgstr[1] "Onartutako firmwarea:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Galdetu berriro hurrengoan?" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Autentifikatzen…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Autentifikazioa behar da daemon-aren konfigurazioa aldatzeko" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Automatikoki eguneratu guztietan?" msgid "BYTES" msgstr "BYTE" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Firmwarea blokeatzen:" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Utzi" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Aldatua" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Aukeratu adarra" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Aukeratu firmwarea" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Aukeratu argitalpena" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Aukeratu bolumena" #. TRANSLATORS: error message msgid "Command not found" msgstr "Ez da komandoa aurkitu" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Deskonprimatzen…" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Gailua gehitu da:" #. TRANSLATORS: the device is already connected msgid "Device already exists" msgstr "Gailua badago lehendik" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Gailua aldatu da:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "Gailu-emulazioa ez dago gaituta." #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Gailua kendu da:" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Gailua eguneratzeko metodoa" #. TRANSLATORS: command line option msgid "Display version" msgstr "Bistaratze-bertsioa" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "Gaitu nahi al duzu?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Urruneko hau orain freskatu nahi al duzu?" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Egina!" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Deskargatzen…" msgid "Enable" msgstr "Gaitu" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Gaitu urruneko berria?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Gaitu urruneko hau?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Gaituta" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Ezabatzen…" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Huts egin du fitxategiaren analisiak" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Firmwarea jadanik blokeatuta dago" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Firmwarea ez dago jadanik blokeatuta" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Aurkitua" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUIDa" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUIDa" msgid "Get BIOS settings" msgstr "Eskuratu BIOSaren ezarpenak" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Inaktibo…" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Firmwarea instalatzen…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Firmwarearen eguneratzea instalatzen..." #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Minutu bat baino gutxiago falta da" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Kargatzen…" msgid "Modify daemon configuration" msgstr "Aldatu daemon-aren konfigurazioa" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Ez da firmwarearen IDrik aurkitu" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "Ez da firmwarerik aurkitu" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Ez dago argitalpenik eskuragarri" #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Ez dago urrunekorik erabilgarri" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Ez dago eguneratu daitekeen gailurik" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Ez dago eguneraketarik erabilgarri" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "Ados" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Gauzatu eragiketa?" msgid "Print the version number" msgstr "Inprimatu bertsio-zenbakia" msgid "Proceed with upload?" msgstr "Jarraitu kargarekin?" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Irakurtzen…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Berrabiarazten…" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Eskaria utzi da" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Gailua berrabiarazten…" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Programatzen…" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Hautatutako bolumena" #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "Ezarri BIOS ezarpen bat" msgid "Set one or more BIOS settings" msgstr "Ezarri BIOS ezarpen bat edo batzuk" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Erakutsi emaitza guztiak" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Erakutsi eguneragarriak ez diren gailuak" msgid "Signature" msgstr "Sinadura" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Gailu guztiak ongi aktibatu dira" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Urrunekoa ongi desgaitu da" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Metadatu berriak ongi deskargatu dira:" #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Urrunekoa ongi gaitu eta freskatu da" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Urrunekoa ongi gaitu da" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmwarea ongi instalatu da" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Konfigurazio-balioa ongi aldatu da" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Urrunekoa ongi aldatu da" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metadatuak ongi freskatu dira eskuz" msgid "Target" msgstr "Helburua" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Ezin da zerbitzuarekin konektatu" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Firmwarea desblokeatzen:" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Ezezaguna" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Eguneratu orain?" msgid "Updating" msgstr "Eguneratzen" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Egiaztatzen…" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "ABISUA" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Itxaroten…" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Idazten…" fwupd-1.9.16/po/fi.po000066400000000000000000003446271460375044200143360ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Jiri Grönroos , 2017-2018,2020-2022 # Kimmo Kujansuu , 2019-2022 # Timo Jyrinki , 2021 # Ville-Pekka Vainio , 2021,2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Finnish (http://app.transifex.com/freedesktop/fwupd/language/fi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fi\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minuutti jäljellä" msgstr[1] "%.0f minuuttia jäljellä" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "BMC:n %s päivitys" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "Laitteen %s akkupäivitys" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "Laitteen %s suorittimen mikrokoodipäivitys" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "Laitteen %s kamerapäivitys" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "Laitteen %s asetusten päivitys" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Laitteen %s kuluttajan ME-päivitys" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "\"%s\"-ohjaimen päivitys" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Laitteen %s yrityksen ME-päivitys" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Laitteen %s laitepäivitys" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "Näytön %s päivitys" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "Telakan %s päivitys" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "Levyn %s päivitys" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Laitteen %s sulautetun ohjaimen päivitys" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "Sormenjälkilukijan %s päivitys" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "Flash-levyn %s päivitys" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "Näytönohjaimen %s päivitys" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "Piirtopöydän %s päivitys" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "Näppäimistön %s päivitys" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "Laitteen %s ME-päivitys" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "Hiiren %s päivitys" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "”%s” -verkkolaitteen päivitys" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "SSD:n %s päivitys" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "”%s” -tallennustilaohjaimen päivitys" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Laitteen %s järjestelmäpäivitys" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "Laitteen %s TPM-päivitys" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Laitteen %s Thunderbolt-ohjaimen päivitys" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "Laitteen %s kosketuslevypäivitys" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "USB-telakan %s päivitys" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "USB-vastaanottimen %s päivitys" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Laitteen %s päivitys" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s ja kaikki kytketyt laitteet eivät välttämättä ole käyttökelpoisia päivityksen aikana." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "%s näkyy: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "%s muutettu: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "%s kadonnut: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s ei ole tällä hetkellä päivitettävissä" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s-valmistustila (manufacturing mode)" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s on oltava kytkettynä päivityksen ajaksi vaurioiden välttämiseksi." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s on oltava kytkettynä virtalähteeseen päivityksen ajaksi vaurioiden välttämiseksi." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s ohita" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s-versio" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u päivä" msgstr[1] "%u päivää" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u laitteessa on saatavilla firmware -päivitys." msgstr[1] "%u laitteelle on saatavilla laiteohjelmistopäivitys." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u laitteet eivät ole tunnetuin kokoonpano." msgstr[1] "%u laitteet eivät ole tunnetuin kokoonpano." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u tunti" msgstr[1] "%u tuntia" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%upaikallisia laitteita tuettu" msgstr[1] "%u tuettua paikallista laitetta" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuutti" msgstr[1] "%u minuuttia" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u sekunti" msgstr[1] "%u sekuntia" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (tarvitaan %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(vanhentunut)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "TPM PCR on nyt virheellisellä arvolla" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "AMD:n laiteohjelmiston kirjoitussuojaus" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "AMD-suorittimen versiopalautuksen suojaus" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "ARCHIVE FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATFILE]" msgstr "ARKISTO LAITEOHJELMISTO METATIETO [LAITEOHJELMISTO] [METATIETO] [JCAT-TIEDOSTO]" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Toimia vaaditaan:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Aktivoi laitteet" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Aktivoi odottavat laitteet" msgid "Activate the new firmware on the device" msgstr "Aktivoi laitteessa oleva uusi laiteohjelmisto" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Laiteohjelmistopäivityksen aktivointi" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Käynnistetään laiteohjelmiston päivitys" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Ikä" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Hyväksytäänkö ja otetaanko lähde käyttöön?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Toinen nimi komennolle %s" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "Kaikki TPM PCR:t ovat nyt kelvollisia" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Kaikki TPM PCR:t ovat kelvollisia" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Kaikki samantyyppiset laitteet päivitetään samaan aikaan" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Salli laiteohjelmistoversioiden alentaminen" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Salli laiteohjelmiston nykyisten versioiden asentaminen uudelleen" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Salli laiteohjelmiston haaran vaihtaminen" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Vaihtoehtoinen haara" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "Päivitys on kesken" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Päivitys vaatii uudelleenkäynnistyksen." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Päivitys edellyttää järjestelmän sammuttamista." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Vastaa kaikkiin kysymyksiin kyllä" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Toteuta laiteohjelmistopäivitykset" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Asenna päivitys myös silloin, kun sitä ei suositella" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Asenna päivitystiedostot" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Asennetaan päivitystä..." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Hyväksytty laiteohjelmisto:" msgstr[1] "Hyväksytty laiteohjelmisto:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Kysytäänkö uudestaan ensi kerralla?" #. TRANSLATORS: command description msgid "Asks the daemon to quit" msgstr "Pyydä taustaprosessia lopettamaan" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Liity laiteohjelmistotilaan" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Tunnistaudutaan…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Todennus vaaditaan" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Erillisen laitteen laiteohjelmiston version alentaminen vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Tämän järjestelmän laiteohjelmiston version alentaminen vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "BIOS-asetusten muuttaminen vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Laiteohjelmistojen päivityslähteen asetusten muokkaaminen vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Taustaprosessin asetusten muokkaaminen vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "BIOS-asetusten lukeminen vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Hyväksyttyjen laiteohjelmistojen luettelon tallentaminen vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Tietojen allekirjoittaminen asiakaan sertifikaatin avulla vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Uuden laiteohjelmistoversion käyttöönotto vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Laitteen lukituksen avaaminen vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Erillisen laitteen laiteohjelmiston päivittäminen vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Tämän järjestelmän laiteohjelmiston päivittäminen vaatii tunnistautumisen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Tallennettujen tarkistussummien päivitys vaatii tunnistautumisen" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Automaattinen raportointi" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Lähetetäänkö automaattisesti joka kerta?" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "BIOS-versiopalautuksen suojaus" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "BIOS-versiopalautuksen suojaus" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "BIOS-päivitykset toimitetaan LVFS:n tai Windows Updaten kautta" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "LUONTI-XML TIEDOSTONIMI-KOHDE" msgid "BYTES" msgstr "TAVUA" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Akku" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Kytke uusi ytimen ajuri" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Estetyt laiteohjelmistotiedostot:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Estetty versio" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Laiteohjelmiston estäminen:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Estää tietyn laiteohjelmiston asentamisen" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Käynnistyslataimen versio" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Haara" #. TRANSLATORS: command description msgid "Build a cabinet archive from a firmware blob and XML metadata" msgstr "Rakenna cabinet-arkisto laiteohjelmisto-blobista ja XML-metatiedoista" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Luo laiteohjelmistotiedosto" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Peru" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Peruttu" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Ei voida asentaa, koska dbx-päivitys on jo asennettu." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Päivityksiä ei voi asentaa live-medialla" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Muutettu" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Tarkistaa, että kryptografinen tiiviste vastaa laiteohjelmistoa" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Tarkistussumma" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Valitse haara" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Valitse laite" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Valitse laiteohjelmisto" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Valitse julkaisu" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Valitse ESP:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Valitse asema" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Tyhjennä viimeisimmän päivityksen tulokset" #. TRANSLATORS: error message msgid "Command not found" msgstr "Komentoa ei löytynyt" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Yhteisön tukema" #. TRANSLATORS: title prefix for the BIOS settings dialog msgid "Configuration Change Suggested" msgstr "Ehdotetaan asetusmuutosta" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "Vain pääkäyttäjä voi lukea asetuksia" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Muunna laiteohjelmistotiedosto" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Luotu" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Kriittinen" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Kryptografisen tiivisteen tarkistus on käytettävissä" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Nykyinen arvo" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Nykyinen versio" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "LAITETUNNUS|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU-apuohjelma" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Vianjäljitysvalinnat" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Puretaan…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Kuvaus" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Irrota käynnistyslataimen tilaan" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Tiedot" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "Poiketaanko parhaasta tunnetusta konfiguraatiosta?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Laitteen liput" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Laitetunnus" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Laite lisätty:" #. TRANSLATORS: the device is already connected msgid "Device already exists" msgstr "Laite on jo olemassa." #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "Laitteen akun lataus on liian vähissä" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "Laitteen akussa on liian vähän virtaa (%u%%, tarvitaan %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Laite voi palautua asennuksen virheistä" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "Laitetta ei voi käyttää kun kansi on suljettu" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Laite muutettu:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "Laite-emulointi ei ole käytössä" #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Laiteohjelmisto vaatii version tarkistamista" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "Laite on emuloitu" #. TRANSLATORS: device cannot be interrupted, for instance taking a phone call msgid "Device is in use" msgstr "Laite on käytössä" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Laite on lukittu" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Laite edellyttää asentamaan kaikki toimitetut versiot" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Laite ei ole tavoitettavissa" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "Laitteeseen ei saada yhteyttä tai se on kantaman ulkopuolella" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Laitetta voidaan käyttää päivityksen aikana" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "Laite odottaa päivityksen toteuttamista" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Laite poistettu:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "Laite vaatii verkkovirran olevan kytketty" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "Laitteen päivitys tarvitsee ohjelmistolisenssin" #. TRANSLATORS: longer description msgid "Device software updates are provided for this device." msgstr "Tälle laitteelle tarjotaan ohjelmistopäivityksiä" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Laite valmistelee päivitykset" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "Laite tukee vaihtamista toiseen laiteohjelmiston haaraan" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Laitteen päivitysmenetelmä" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Laitteen päivitys tarvitsee aktivointia" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Laite varmuuskopioi laiteohjelmiston ennen asennusta" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Laitetta ei näytetä uudelleen päivityksen päätyttyä" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Onnistuneesti päivitetyt laitteet:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Laitteet, joita ei päivitetty oikein:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Laitteet, joille ei ole saatavilla laiteohjelmiston päivityksiä:" #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Laitteet, joille on asennettu uusin versio laiteohjelmistosta:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "Laitteita ei löytynyt vastaavilla GUID-tunnuksilla" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Ei käytössä" msgid "Disabled fwupdate debugging" msgstr "fwupdaten vianjäljitys ei ole käytössä" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Poista annettu lähde" #. TRANSLATORS: command line option msgid "Display version" msgstr "Näytä versio" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Jakelu" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Älä tarkista metatietojen vanhentumista" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Älä tarkista ilmoittamattomien historiaa" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Älä tarkista, pitäisikö verkkolähteitä ottaa käyttöön" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Älä pyydä tai tarkista uudelleenkäynnistystä päivityksen jälkeen" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Älä sisällytä lokiin toimialueen etuliitettä" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Älä sisällytä aikaleiman etuliitettä" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Älä suorita laitteen turvallisuustarkastuksia" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "Älä pyydä laitteita" #. TRANSLATORS: command line option msgid "Do not prompt to fix security issues" msgstr "Älä pyydä turvallisuusongelmien korjausta" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "Älä etsi laiteohjelmistoja samalla kun jäsennetään" #. TRANSLATORS: warning message shown after update has been scheduled msgid "Do not turn off your computer or remove the AC adaptor while the update is in progress." msgstr "Älä sammuta tietokonettasi äläkä irrota virtajohtoa kun päivitys on kesken." #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Älä kirjoita historiatietokantaan" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Ymmärrätkö laiteohjelmiston haaran vaihtamisen seuraukset?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Haluatko poistaa tämän ominaisuuden käytöstä tulevia päivityksiä varten?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "Haluatko ottaa sen käyttöön nyt?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Haluatko päivittää tämän lähteen nyt?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Haluatko lähettää raportteja automaattisesti tulevia päivityksiä varten?" #. TRANSLATORS: command line option msgid "Don't prompt for authentication (less details may be shown)" msgstr "Älä pyydä todennusta (saatetaan näyttää vähemmän tietoja)" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Valmis!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Alennetaanko laitteen %s laiteohjelmisto versiosta %s versioon %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Alentaa laitteen laiteohjelmistoa" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Alennetaan laitteen %s ohjelmisto versiosta %s versioon %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Alennetaan laitetta %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Lataa tiedosto" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Ladataan…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Pura SMBIOS-tiedot tiedostosta" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Kesto" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Annettu ESP-polku on virheellinen" #. TRANSLATORS: longer description msgid "Each system should have tests to ensure firmware security." msgstr "Jokaiselle järjestelmälle pitäisi olla testejä, jotka varmistavat laiteohjelmiston turvallisuuden." #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "Emuloi laitetta käyttäen JSON-manifestia" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Emuloitu" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Emuloitu kone" msgid "Enable" msgstr "Käytä" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Ota laiteohjelmiston päivitystuki käyttöön tuetuissa järjestelmissä" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Otetaanko uusi lähde käyttöön?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Otetaanko tämä lähde käyttöön?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Käytössä" msgid "Enabled fwupdate debugging" msgstr "fwupdaten vianjäljitys on käytössä" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Käytössä, jos laitteisto vastaa" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Ota käyttöön annettu lähde" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Tämän toiminnon käyttöönotto tapahtuu omalla vastuullasi, joten sinun on otettava yhteyttä alkuperäiseen laitevalmistajaan näiden päivitysten aiheuttamista ongelmista. Vain päivitysprosessiin liittyvät ongelmat pitäisi ilmoittaa osoitteeseen $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Tämän lähteen käyttöönotto tapahtuu omalla vastuullasi." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Salattu" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "Salattu RAM-muisti" msgid "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." msgstr "Kun keskusmuisti on salatttu, laitteen muistiin tallennettua tietoa ei voi lukea, vaikka muistisiru poistettaisiin ja sitä käytettäisiin." #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Käyttöaika loppu" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Enumeraatio" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Poista kaikki laiteohjelmiston päivityshistoria" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Poistetaan…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Poistu pienen viiveen jälkeen" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Poistu kun moottori on ladattu" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Vie laiteohjelmiston tiedostorakenne XML-muotoon" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Pura laiteohjelmiston \"blob\" levykuviksi" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "TIEDOSTO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "TIEDOSTO [LAITETUNNUS|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "TIEDOSTONIMI" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "TIEDOSTONIMI SERTIFIKAATTI YKSITYINEN AVAIN" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "TIEDOSTONIMI LAITTEEN-VAIHTOEHT-NIMI|LAITTEEN-VAIHTOEHT-TUNNUS" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "TIEDOSTONIMI LAITTEEN-VAIHTOEHT-NIMI|LAITTEEN-VAIHTOEHT-TUNNUS [LEVYKUVAN-VAIHTOEHT-NIMI|LEVYKUVAN-VAIHTOEH-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "TIEDOSTONIMI LAITETUNNUS" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "TIEDOSTONIMI SIIRTYMÄ DATA [LAITEOHJELMISTON-TYYPPI]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "TIEDOSTONIMI [LAITETUNNUS|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "TIEDOSTONIMI [LAITEOHJELMISTON-TYYPPI]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "TIEDOSTONIMI-LÄHDE TIEDOSTONIMI-KOHDE [LAITEOHJELMISTON-TYYPPI-LÄHDE] [LAITEOHJELMISTON-TYYPPI-KOHDE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Epäonnistui" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Päivityksen asentaminen epäonnistui" #. TRANSLATORS: error message for Windows msgid "Failed to connect to Windows service, please ensure it's running." msgstr "Windows-palveluun yhdistäminen epäonnistui. Varmista, että se on käynnissä." #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Yhteys taustaprosessiin epäonnistui" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Vireillä olevien laitteiden haku epäonnistui" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Laiteohjelmiston päivityksen asennus epäonnistui" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Paikallisen dbx-tiedoston lataus epäonnistui" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Erikoisominaisuuksien (quirks) lataaminen epäonnistui" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Järjestelmän dbx-tiedoston lataus epäonnistui" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Lukitseminen epäonnistui" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Parametrien jäsentäminen epäonnistui" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Tiedoston jäsentäminen epäonnistui" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "--filter-lippujen jäsentäminen ei onnistunut" msgid "Failed to parse flags for --filter-release" msgstr "--filter-release-lippujen jäsentäminen ei onnistunut" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Paikallisen dbx-tiedoston lukeminen epäonnistui" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Uudelleenkäynnistys epäonnistui" #. TRANSLATORS: a feature is something like "can show an image" msgid "Failed to set front-end features" msgstr "Käyttöliittymän ominaisuuksien asettaminen epäonnistui" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Splash-tilan asettaminen epäonnistui" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "ESP:n sisällön vahvistaminen epäonnistui" #. TRANSLATORS: item is FALSE msgid "False" msgstr "Epätosi" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Tiedostonimi" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Tiedoston allekirjoitus" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Tiedostonimen lähde" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Tiedostonimi vaaditaan" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Suodata käyttäen laitelippuja. ~ poissulkee, esimerkiksi ”internal,~needs-reboot”" #. TRANSLATORS: command line option msgid "Filter with a set of release flags using a ~ prefix to exclude, e.g. 'trusted-release,~trusted-metadata'" msgstr "Suodata käyttäen julkaisulippuja. ~ poissulkee, esimerkiksi ”trusted-release,~trusted-metadata”" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Laiteohjelmiston vahvistaminen" #. TRANSLATORS: longer description msgid "Firmware Attestation checks device software using a reference copy, to make sure that it has not been changed." msgstr "Laiteohjelmiston vahvistaminen tarkastaa, että laiteohjelmisto käyttää suositeltua versiota, jotta voidaan olla varmoja että ohjelmistoa ei ole muokattu." #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Laiteohjelmiston BIOS-tunnus" #. TRANSLATORS: longer description msgid "Firmware BIOS Descriptor protects device firmware memory from being tampered with." msgstr "Laiteohjelmiston BIOS-kuvain estää laiteohjelmiston muistin peukaloimisen." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Laiteohjelmiston BIOS-alue" #. TRANSLATORS: longer description msgid "Firmware BIOS Region protects device firmware memory from being tampered with." msgstr "Laiteohjelmiston BIOS-alue estää laiteohjelmiston muistin peukaloimisen." #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Laiteohjelmistojen perus-URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Laiteohjelmistopäivityksen D-Bus-palvelu" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Laiteohjelmistopäivityksen taustaprosessi" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Laiteohjelmistopäivittimen varmennus" msgid "Firmware Updater Verification checks that software used for updating has not been tampered with." msgstr "Laiteohjelmistopäivittimen varmennus varmentaa, että päivittämiseen käytettyä ohjelmistoa ei ole peukaloitu." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Laiteohjelmistopäivitykset" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Laiteohjelmistotyökalu" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Laiteohjelmiston kirjoitussuojaus" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Laiteohjelman kirjoitussuojauksen lukko" #. TRANSLATORS: longer description msgid "Firmware Write Protection protects device firmware memory from being tampered with." msgstr "Laiteohjelmiston kirjoitussuojaus estää laiteohjelmiston muistin peukaloimisen." #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Laiteohjelmiston vahvistaminen" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Laiteohjelmisto on jo estetty" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Laiteohjelmistoa ei ole jo estetty" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Laiteohjelmiston metatietoja ei ole päivitetty %upäivään ja ne eivät välttämättä ole ajan tasalla." msgstr[1] "Laiteohjelmiston metatietoja ei ole päivitetty %u päivään ja ne eivät välttämättä ole ajan tasalla." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Laiteohjelmistopäivitykset" msgid "Firmware updates are not supported on this machine." msgstr "Tämä laite ei tue laiteohjelmistopäivityksiä" msgid "Firmware updates are supported on this machine." msgstr "Tämä laite tukee laiteohjelmistopäivityksiä" #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Laiteohjelmiston päivitykset poistettu käytöstä; suorita 'fwupdmgr unlock' ottaaksesi ne käyttöön" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Liput" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Pakota toiminta helpottamalla joitakin ajonaikaisia tarkistuksia" msgid "Force the action ignoring all warnings" msgstr "Pakota toimenpide, älä huomioi varoituksia" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Löydetty" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Levyn salaus havaittu" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "Levyn salaus voi mitätöityä päivityksen aikana" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "Alustassa on sulake" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Alustassa on sulake" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUIDs" msgstr[1] "GUID:t" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "GUID|DEVICE-ID" msgstr "GUID|LAITETUNNUS" msgid "Get BIOS settings" msgstr "Hae BIOS-asetukset" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Hae kaikki fwupd:n tukemat laiteliput" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Hae kaikki laiteohjelmistopäivityksiä tukevat laitteet" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Hae kaikki järjestelmään rekisteröidyt laajennukset" #. TRANSLATORS: command description msgid "Get device report metadata" msgstr "Hae laitteistoraportin metatiedot" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Hae tietoja laiteohjelmistotiedostosta" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Hae konfiguroidut lähteet" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Hae koneen tietoturva-attribuutit" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Hae hyväksytyn laiteohjelmiston luettelo" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Hakee estettyjen laiteohjelmistojen luettelon" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Hae luettelo liitettyjen laitteiden päivityksistä" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Hakee laitteen julkaisut" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Hakee viimeisimmän päivityksen tulokset" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWIDS-TIEDOSTO" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Laitteisto odottaa uudelleenkytkemistä" #. TRANSLATORS: the release urgency msgid "High" msgstr "Korkea" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Koneen suojaustapahtumat" #. TRANSLATORS: error message for unsupported feature #, c-format msgid "Host Security ID (HSI) is not supported" msgstr "Host Security ID (HSI) ei ole tuettu" #. TRANSLATORS: success, so say thank you to the user msgid "Host Security ID attributes uploaded successfully, thanks!" msgstr "Koneen tietoturvatunnisteen (HSI) attribuuttien lähetys onnistui. Kiitos!" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Koneen tietoturvatunniste (Host Security ID):" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "INHIBIT-ID" msgstr "ESTON-TUNNISTE" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "IOMMU-suojaus" #. TRANSLATORS: longer description msgid "IOMMU Protection prevents connected devices from accessing unauthorized parts of system memory." msgstr "IOMMU:n suojaus estää liitettyjä laitteita käyttämästä järjestelmämuistin yksityisiä osia." #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "IOMMU-laitteen suojaus poistettu" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "IOMMU-laitteen suojaus käytössä" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Jouten…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ohita tiukat SSL-tarkistukset tiedostoja ladattaessa" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Ohita laiteohjelmiston tarkistussumman virheet" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Ohita laitteiden yhteensopimattomuuden virheet" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ohita turvatarkastukset" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Ohitetaan tiukat SSL-tarkistukset. Jos haluat tehdä tämän jatkossa automaattisesti, ota käyttöön DISABLE_SSL_STRICT-ympäristömuuttuja" #. TRANSLATORS: the inhibit ID is a short string like dbus-123456 #, c-format msgid "Inhibit ID is %s." msgstr "Eston tunniste on %s." #. TRANSLATORS: command description msgid "Inhibit the system to prevent upgrades" msgstr "Ota käyttöön päivitysten estäminen" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Asennuksen kesto" #. TRANSLATORS: command description msgid "Install a firmware file in cabinet format on this hardware" msgstr "Asenna cabinet-muotoinen laiteohjelmisto tähän laitteistoon" #. TRANSLATORS: command description msgid "Install a raw firmware blob on a device" msgstr "Asenna laiteohjelmisto (\"blob\") laitteeseen" #. TRANSLATORS: command description msgid "Install a specific firmware file on all devices that match" msgstr "Asenna tietty laiteohjelmistotiedosto kaikkiin laitteisiin, joihin se sopii" #. TRANSLATORS: command description msgid "Install a specific firmware on a device, all possible devices will also be installed once the CAB matches" msgstr "Asenna tietty laiteohjelmisto laitteeseen. Asennetaan kaikkiin mahdollisiin laitteisiin, kun CAB täsmää." msgid "Install old version of signed system firmware" msgstr "Asenna järjestelmän laiteohjelmiston allekirjoitettu vanha versio" msgid "Install old version of unsigned system firmware" msgstr "Asenna järjestelmän laiteohjelmiston allekirjoittamaton vanha versio" msgid "Install signed device firmware" msgstr "Asenna allekirjoitettu laiteohjelmisto" msgid "Install signed system firmware" msgstr "Asenna allekirjoitettu järjestelmän laiteohjelmisto" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Asenna ensin päälaitteelle" msgid "Install unsigned device firmware" msgstr "Asenna allekirjoittamaton laiteohjelmisto" msgid "Install unsigned system firmware" msgstr "Asenna allekirjoittamaton järjestelmän laiteohjelmisto" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Asennetaan laiteohjelmistoa…" #. TRANSLATORS: stay on one firmware version unless the new version is #. explicitly #. * specified msgid "Installing a specific release is explicitly required" msgstr "Juuri tietyn julkaisuversion asentaminen vaaditaan" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Asennetaan laiteohjelmistopäivitystä…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Asentaa laitteelle %s…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "Tämän päivityksen asentaminen saattaa kumota laitteen takuun." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Kokonaisluku" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "Intel BootGuard ACM -suojattu" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Intel BootGuard ACM -suojattu" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Intel BootGuard -virhekäytäntö" msgid "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." msgstr "Intel BootGuardin virhekäytäntö varmistaa, että laitteen käynnistys ei jatku, jos sen laiteohjelmistoa on peukaloitu." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Intel BootGuard -sulake" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Intel BootGuardin OTP-sulake" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Intel BootGuardilla vahvistettu käynnistys" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Intel BootGuard -virhekäytäntö" #. TRANSLATORS: longer description msgid "Intel BootGuard prevents unauthorized device software from operating when the device is started." msgstr "Intel BootGuard estää luvattoman laiteohjelmiston toiminnan, kun laite käynnistyy." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Intel BootGuardilla vahvistettu käynnistys" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET aktiivinen" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET käytössä" #. TRANSLATORS: longer description msgid "Intel Control-Flow Enforcement Technology detects and prevents certain methods for running malicious software on the device." msgstr "Intelin toiminnonkulun valvontateknologia tunnistaa ja estää tiettyjä menetelmiä, joiden avulla laitteella voisi suorittaa haittaohjelmia." #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Intel Management Engine -valmistustila" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Intel Management Enginen ohitus" #. TRANSLATORS: longer description msgid "Intel Management Engine Override disables checks for device software tampering." msgstr "Intel Management Enginen ohitus ottaa laiteohjelmiston peukaloinnin tunnistuksen pois käytöstä." #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Intel Management Enginen versio" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: longer description msgid "Intel Supervisor Mode Access Prevention ensures critical parts of device memory are not accessed by less secure programs." msgstr "Intelin pääkäyttäjätilan pääsynesto varmistaa, että turvattomat ohjelmat eivät pysty käyttämään laitteen muistin kriittisiä alueita." #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Sisäinen laite" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Virheellinen" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Virheelliset argumentit" #. TRANSLATORS: error message msgid "Invalid arguments, expected GUID" msgstr "Virheellinen parametri, odotettiin GUID:tä" #. TRANSLATORS: error message msgid "Invalid arguments, expected at least ARCHIVE FIRMWARE METAINFO" msgstr "Virheellinen parametri. Odotettiin ainakin ARKISTO LAITEOHJELMISTO METATIEDOT." #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "On vanhempi" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "On käynnistyslataintilassa" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "On päivitys" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Kysymykset" msgstr[1] "Ongelmat" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "AVAIN,ARVO" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "Ydin ei ole enää saastunut" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "Ydin on saastunut" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Ytimen lukitus on poistettu käytöstä" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Ytimen lukitus on käytössä" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Avainnippu" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "SIJAINTI" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Viimeksi muokattu" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Jäljellä on alle minuutti" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Lisenssi" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Linux-ytimen lukitus" #. TRANSLATORS: longer description msgid "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgstr "Linux-ytimen lukitustila estää pääkäyttäjiä (root) lukemasta ja muokkaamasta järjestelmäohjelmistojen kriittisiä osia." msgid "Linux Kernel Swap temporarily saves information to disk as you work. If the information is not protected, it could be accessed by someone if they obtained the disk." msgstr "Linux-ytimen näennäismuisti (swap) tallentaa tietoja käytön aikana väliaikaisesti levylle. Jos näitä tietoja ei suojata, levyn käsiinsä saava taho pystyy lukemaan ne." #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Linux-ytimen varmennus" msgid "Linux Kernel Verification makes sure that critical system software has not been tampered with. Using device drivers which are not provided with the system can prevent this security feature from working correctly." msgstr "Linux-ytimen varmennus varmentaa, että kriittistä järjestelmäohjelmistoa ei ole peukaloitu. Sellaisten laiteajurien käyttö, joita ei toimiteta järjestelmän mukana, saattaa estää tämän turvallisuusominaisuuden oikeanlaisen toiminnan." #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Linuxin näennäismuisti (swap)" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux-laitetoimittajien laiteohjelmistopalvelu (vakaat laiteohjelmistot)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux-laitetoimittajien laiteohjelmistopalvelu (testattavat laiteohjelmistot)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Linux-ydin" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Linux-ytimen lukitus" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linuxin näennäismuisti (swap)" #. TRANSLATORS: command description msgid "List EFI variables with a specific GUID" msgstr "Luetteloi tietyn GUID:n EFI-muuttujat" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Listaa dbx:n merkinnät" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Luettelo tuetuista laiteohjelmistopäivityksistä" #. TRANSLATORS: command description msgid "List the available firmware GTypes" msgstr "Luettelo käytettävissä olevista GTypeseistä" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Luettelo käytettävissä olevista laiteohjelmistotyypeistä" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Luetteloi ESP:n tiedostot" #. TRANSLATORS: command description msgid "Load device emulation data" msgstr "Lataa laitteen emulointitiedot" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "Ladattu ulkoisesta moduulista" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Ladataan…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Lukittu" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Matala" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "MEI-avainmanifesti" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "MEI-avainmanifesti" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "MEI-valmistustila (manufacturing mode)" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "MEI-ohitus" msgid "MEI version" msgstr "MEI-versio" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Ota manuaalisesti tietyt laajennukset käyttöön" #. TRANSLATORS: longer description msgid "Manufacturing Mode is used when the device is manufactured and security features are not yet enabled." msgstr "Valmistustilaa käytetään laitteen valmistuksen aikana, kun turvallisuusominaisuudet eivät ole vielä käytössä." #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Enimmäispituus" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Enimmäisarvo" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Keskitaso" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Metatietojen allekirjoitus" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metatietojen URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metatiedot voidaan hankkia Linux Vendor Firmware -palvelusta." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Vähimmäisversio" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Vähimmäispituus" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Vähimmäisarvo" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Taustaprosessi ja asiakasohjelma eivät vastaa toisiaan, käytä sen sijaan %s" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Muokkaa taustaprosessin asetusarvoa" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Muokkaa annettua lähdettä" msgid "Modify a configured remote" msgstr "Muokkaa lähteen asetuksia" msgid "Modify daemon configuration" msgstr "Muokkaa taustaprosessin asetuksia" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Seuraa tapahtumien taustaprosessia" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Liitä ESP" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Tarvitsee uudelleenkäynnistyksen asennuksen jälkeen" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Uudelleenkäynnistys tarvitaan" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Tarvitsee sammutuksen asennuksen jälkeen" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Uusi versio" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Toimintoa ei ole määritetty!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Ei versioalennusta laitteelle %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Laiteohjelmiston tunnuksia ei löytynyt" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "Laiteohjelmistoa ei löytynyt" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Ei löytynyt laitteita, joille voisi asentaa laiteohjelmistopäivityksiä" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Liitännäisiä ei löytynyt" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Ei julkaisuja saatavilla" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Mitään lähteitä ei ole tällä hetkellä käytössä, joten metatietoja ei ole saatavilla." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Lähteitä ei saatavilla" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Ei päivitettäviä laitteita" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Päivityksiä ei ole saatavilla" msgid "No updates available for remaining devices" msgstr "Muille laitteille ei ole saatavilla päivityksiä" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Päivityksiä ei toteutettu" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Hylätty" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Ei löydy" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Ei tueta" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "OK" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "OK!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Vanha versio" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Näytä vain yksi PCR-arvo" #. TRANSLATORS: command line option msgid "Only use peer-to-peer networking when downloading files" msgstr "Käytä vain vertaisverkkoa tiedostojen lataamiseen" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Vain versioiden päivitykset ovat sallittuja" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Tuloste JSON-muodossa" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Ohita oletusarvoinen ESP-polku" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "POLKU" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Jäsennä ja näytä tiedot laiteohjelmistotiedostosta" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Jäsennetään dbx-päivitystä..." #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Jäsennetään järjestelmän dbx:ää..." #. TRANSLATORS: remote filename base msgid "Password" msgstr "Salasana" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Korjaa laiteohjelmiston blob tunnetulla vastineella" msgid "Payload" msgstr "Tietosisältö" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Odottaa" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Valmis prosenteissa" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Suoritetaanko toiminto?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Alustan virheenjäljitys" #. TRANSLATORS: longer description msgid "Platform Debugging allows device security features to be disabled. This should only be used by hardware manufacturers." msgstr "Alustan virheenjäljityksen avulla voidaan ottaa laitteen turvallisuusominaisuudet pois käytöstä. Vain laitevalmistajien tulisi käyttää tätä ominaisuutta." #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Alustan virheenjäljitys" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Varmista, että sinulla on aseman palautusavain ennen kuin jatkat." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Anna numero 0 -%u" #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Laajennuksen riippuvuudet puuttuvat" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Mahdolliset arvot" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "Käynnistystä edeltävä DMA-suojaus" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Käynnistystä edeltävä DMA-suojaus" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "Käynnistystä edeltävä DMA-suojaus on poistettu käytöstä" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "Käynnistystä edeltävä DMA-suojaus on käytössä" #. TRANSLATORS: longer description msgid "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." msgstr "Käynnistystä edeltävä DMA-suojaus estää laitteita käyttämästä järjestelmän muistia, kun ne kytketään tietokoneeseen." #. TRANSLATORS: warning message msgid "Press unlock on the device to continue the update process." msgstr "Paina laitteen lukituksen avauspainiketta, jotta päivitys voi jatkua." #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Aiempi versio" msgid "Print the version number" msgstr "Tulosta versionumero" msgid "Print verbose debug statements" msgstr "Tulosta vianjäljitystiedot" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioriteetti" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Ongelmat" msgid "Proceed with upload?" msgstr "Jatketaanko lähettämistä?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Suorittimen turvallisuustarkastukset" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Suorittimen versiopalautuksen suojaus" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Suljettu" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Kysely laiteohjelmistopäivityksen tuesta" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "LÄHDETUNNUS" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "LÄHDETUNNUS AVAIN ARVO" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Vain luku" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Lue laiteohjelmiston \"blob\" laitteesta" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Lue laiteohjelmisto laitteelta" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Lue laiteohjelmisto laitteesta tiedostoon" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Lue laiteohjelmisto osiolta tiedostoon" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Lukee laitteelta %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Luetaan…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Käynnistetään uudelleen..." #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Päivitä metatiedot etäpalvelimelta" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Asennetaanko laitteeseen %s uudelleen laiteohjelmiston versio%s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Asenna nykyinen laiteohjelmisto laitteeseen uudelleen" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Asenna laiteohjelmisto uudelleen laitteelle" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Asennetaan laitteelle %s uudelleen versio %s..." #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Julkaisuhaara" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Julkaisuliput" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "Julkaisutunnus" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Etätunnus" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Vaihda tiedot olemassa olevaan laiteohjelmistotiedostoon" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Ilmoitus-URI" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Raportoitu etäpalvelimelle" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Pyyntö peruttu" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "Vaadittua efivarfs-tiedostojärjestelmää ei löytynyt" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "Vaadittavaa laitteistoa ei löytynyt" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Vaatii käynnistyslataimen" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Vaatii internetyhteyden" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Käynnistetäänkö uudelleen nyt?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Käynnistetäänkö taustaprosessi uudelleen, jotta muutos tulee voimaan?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Käynnistetään laite uudelleen…" #. TRANSLATORS: command description msgid "Retrieve BIOS settings. If no arguments are passed all settings are returned" msgstr "Nouda BIOS-asetuksia. Jos parametreja ei anneta, noudetaan kaikki asetukset." #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Palauta kaikki koneen laitetunnukset" #. TRANSLATORS: longer description msgid "Rollback Protection prevents device software from being downgraded to an older version that has security problems." msgstr "Versiopalautuksen suojaus estää laiteohjelmiston version varhentamisen vanhempaan versioon, jossa on turvallisuusongelmia." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Suorita ”fwupdmgr get-upgrades” saadaksesi lisätietoja." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Suorita liitännäisen yhteinen siivousrutiini, kun käytetään komentoa install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Suorita liitännäisen yhteinen valmistelurutiini, kun käytetään komentoa install-blob" #. TRANSLATORS: tell a user how to get information #, c-format msgid "Run without '%s' to see" msgstr "Suorita ilman valitsinta ”%s” nähdäksesi" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "Ajossa oleva ydin on liian vanha" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Ajonaikainen pääte" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "ASETUS ARVO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING1 VALUE1 [SETTING2] [VALUE2]" msgstr "ASETUS1 ARVO1 [ASETUS2] [ARVO2]" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "SPI-BIOS-tunnus" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "SPI-BIOS-alue" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI-lukko" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI-kirjoitus" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "SPI-kirjoitussuojaus" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "ALIJÄRJESTELMÄ AJURI [LAITETUNNUS|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Tallenna tiedosto, joka mahdollistaa laitetunnusten luomisen" #. TRANSLATORS: command description msgid "Save device emulation data" msgstr "Tallenna laitteen emulointitiedot" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "Lukuarvon kasvattaminen" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Ajasta asennus uudelleenkäynnistykselle mahdollisuuksien mukaan" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Ajoitetaan…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Turvakäynnistys (secure boot) pois käytöstä" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Turvakäynnistys (secure boot) käytössä" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Lisätietoja sivulla %s" #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Katso %s saadaksesi lisätietoja." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Valittu laite" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Valittu asema" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Sarjanumero" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "Asetettiin BIOS-asetus ”%s” arvoon ”%s”." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "Aseta BIOS-asetus" msgid "Set one or more BIOS settings" msgstr "Aseta yksi tai useampi BIOS-asetus" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Aseta virheenkorjauksen lippu päivityksen aikana" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "Asettaa yhden tai useamman BIOS-asetuksen" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Tallentaa hyväksyttyjen laiteohjelmistojen luettelon" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Asetuksen tyyppi" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Asetukset tulevat voimaan, kun järjestelmä on käynnistetty uudelleen" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Jaa laiteohjelmiston historia kehittäjien kanssa" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Näytä kaikki tulokset" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Näytä asiakas- ja taustaprosessin versiot" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Näytä taustaprosessin täydelliset tiedot tietylle verkkotunnukselle" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Näytä kaikkien verkkotunnusten virheenkorjaustiedot" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Näytä vianjäljitysvalinnat" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Näytä laitteet, joita ei voi päivittää" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Näytä ylimääräiset virheenkorjaustiedot" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Näytä laiteohjelmistopäivitysten historia" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Näytä dbx:n laskettu versio" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Näytä virheenjäljitysloki viimeisestä päivitysyrityksestä" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Näytä tiedot laiteohjelmiston päivitystilasta" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Sammutetaanko nyt?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Allekirjoita laiteohjelmisto uudella avaimella" msgid "Sign data using the client certificate" msgstr "Allekirjoita tietoja käyttämällä asiakkaan sertifikaattia" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Allekirjoita tietoja käyttämällä asiakkaan sertifikaattia" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Allekirjoita lähetetyt tiedot asiakkaan sertifikaatilla" msgid "Signature" msgstr "Allekirjoitus" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Allekirjoitettu hyötykuorma" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Koko" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Jotkut alustasalaukset voidaan mitätöidä tätä laiteohjelmistoa päivitettäessä." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Lähde" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Määritä DFU-laitteen toimittaja- tai tuotetunnus" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Määritä dbx-tietokantatiedosto" msgid "Specify the number of bytes per USB transfer" msgstr "Määritä tavujen määrä USB-siirtoa kohti" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "Merkkijono" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Onnistui" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Kaikkien laitteiden aktivointi onnistui" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Lähteen poistaminen onnistui" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Uusien metatietojen lataus onnistui: " #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Lähteen käyttöönotto ja päivitys onnistui" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Lähteen käyttöönotto onnistui" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Laiteohjelmiston asennus onnistui" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Asetusarvon muokkaaminen onnistui" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Lähteen muokkaus onnistui" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metatietojen manuaalinen päivitys onnistui" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Laitteiden tarkistussummien päivitys onnistui" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Raporttien lataus %u onnistui" msgstr[1] "%u raportin lähetys onnistui" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Laitteiden tarkistussummat vahvistettu onnistuneesti" #. TRANSLATORS: the device showed up in time #, c-format msgid "Successfully waited %.0fms for device" msgstr "Odotettiin laitetta %.0fms ja se ilmestyi" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Yhteenveto" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Tuettu" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "Tuettu suoritin" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Tuettu etäpalvelimella" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Keskeytä tyhjäkäynnille" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Keskeytä muistiin" #. TRANSLATORS: longer description msgid "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Keskeyttämällä tyhjäkäynnille laite voi mennä nopeasti unitilaan virran säästämiseksi. Kun laite on unitilassa, sen muisti voidaan fyysisesti irrottaa ja muistin sisältämät tiedot voidaan lukea." #. TRANSLATORS: longer description msgid "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Keskeyttämällä muistiin laite voi mennä nopeasti unitilaan virran säästämiseksi. Kun laite on unitilassa, sen muisti voidaan fyysisesti irrottaa ja muistin sisältämät tiedot voidaan lukea." #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Keskeytä tyhjäkäynnille" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Keskeytä muistiin" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Vaihdatko haaran %s haaraksi %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Vaihda laitteen laiteohjelmiston haaraa" #. TRANSLATORS: this CLI tool is now preventing system updates msgid "System Update Inhibited" msgstr "Järjestelmäpäivitys on estetty" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "Järjestelmän akun varaus ei riitä päivityksen toteuttamiseen" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "Järjestelmän akussa on liian vähän virtaa päivityksen suorittamiseen (%u%%, tarvitaan %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "Järjestelmä vaatii ulkoisen virtalähteen" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEKSTI" #. TRANSLATORS: longer description msgid "TPM (Trusted Platform Module) is a computer chip that detects when hardware components have been tampered with." msgstr "TPM (Trusted Platform Module) on tietokonesiru, joka tunnistaa, jos laitteistokomponentteja on peukaloitu." #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "TPM PCR0 -jälleenrakennus" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "TPM PCR0 -jälleenrakennus on virheellinen" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "TPM PCR0 -jälleenrakennus on nyt kelvollinen" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "TPM-alustan asetukset" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "TPM:n jälleenrakennus" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "TPM tyhjät PCR:t" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Tunniste" msgstr[1] "Tunnisteet" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "Merkitty emuloitavaksi" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Ydin on tainted-tilassa" msgid "Target" msgstr "Kohde" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Testaa laitetta käyttäen JSON-manifestia" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Testattu" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "Valmistajan %s testaama" #. TRANSLATORS: someone we trust has tested this msgid "Tested by trusted vendor" msgstr "Luotettavan valmistajan testaama" #. TRANSLATORS: longer description msgid "The Intel Management Engine Key Manifest must be valid so that the device firmware can be trusted by the CPU." msgstr "Intel Management Engine -avainmanifestin täytyy olla kelvollinen, jotta suoritin voi luottaa laiteohjelmistoon." #. TRANSLATORS: longer description msgid "The Intel Management Engine controls device components and needs to have a recent version to avoid security issues." msgstr "Intel Management Engine hallitsee laitteistokomponentteja. Sen versio on oltava tuore, jotta vältytään turvallisuusongelmilta." #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS on ilmainen palvelu, joka toimii itsenäisenä oikeushenkilönä eikä sillä ole yhteyttä jakelijaan $OS_RELEASE:NAME$. Jakelijasi ei ehkä ole varmistanut laiteohjelmistopäivitysten yhteensopivuutta järjestelmän tai liitettyjen laitteiden kanssa. Kaikki laiteohjelmistot tulevat alkuperäisiltä laitevalmistajilta." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Platform Configuration is used to check whether the device start process has been modified." msgstr "TPM-alustan (Trusted Platform Module) asetusten avulla tarkastetaan, onko laitteen käynnistysprosessia muutettu." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Reconstruction is used to check whether the device start process has been modified." msgstr "TPM:n (Trusted Platform Module) jälleenrakennuksen avulla tarkastetaan, onko laitteen käynnistysprosessia muutettu." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "TPM PCR0 eroaa uudelleenrakennetusta." #. TRANSLATORS: longer description msgid "The UEFI Platform Key is used to determine if device software comes from a trusted source." msgstr "UEFI:n alusta-avaimen perusteella tiedetään, tuleeko laiteohjelmisto luotettavasta lähteestä." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "Palvelu on ladannut 3. osapuolen koodia, eivätkä kehittäjät enää jatkossa tue sitä!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "Laitteen versio ei täsmää: sain %s, odotettu %s" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "Laiteohjelmiston on toimittanut %s eikä laitteen toimittaja %s." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "Järjestelmän kelloa ei ole asetettu oikein ja tiedostojen lataaminen saattaa epäonnistua." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been re-inserted." msgstr "Päivitys jatkuu, kun laitteen USB-kaapeli on kiinnitetty uudestaan." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged and then re-inserted." msgstr "Päivitys jatkuu, kun laitteen USB-kaapeli on irrotettu ja kiinnitetty uudestaan." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged." msgstr "Päivitys jatkuu, kun laitteen USB-kaapeli on irrotettu." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "Valmistaja ei toimittanut julkaisutietoja." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Seuraavissa laitteissa on ongelmia:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Estettyjä laiteohjelmistotiedostoja ei ole" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Hyväksyttyä laiteohjelmistoa ei ole." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "Laite palautetaan versioon %s, kun komento %s suoritetaan." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "Tämä laiteohjelmisto on tarjolla LVFS-yhteisöjäsenten toimesta, sitä ei ole tarjottu (tai tuettu) alkuperäisen laitevalmistajan toimesta." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Tätä pakettia ei ole vahvistettu, se ei välttämättä toimi oikein." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Tämä ohjelma toimii oikein vain root-käyttäjän oikeuksin" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Tämä lähde sisältää laiteohjelmiston, joka ei ole vientikiellossa, mutta jota laitteistotoimittaja testaa edelleen. Varmista, että voit päivittää laiteohjelmiston takaisin vanhempaan versioon manuaalisesti, jos päivitys epäonnistuu." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "Tämä järjestelmä ei tue laiteohjelmiston asetuksia" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Järjestelmässä on ajonaikainen HSI-ongelma." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Tämän järjestelmän HSI-tietoturvataso on alhainen." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Tämän työkalun avulla järjestelmänvalvoja voi asentaa UEFI-dbx-päivityksiä." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Tämän työkalun avulla järjestelmänvalvoja voi jäljittää UpdateCapsulen toimintaa." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Tämän työkalun avulla järjestelmänvalvoja voi kysellä ja hallita fwupd-palvelua, jolloin voi suorittaa toimintoja, kuten laiteohjelmiston asennus tai päivittäminen." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Tämän työkalun avulla järjestelmänvalvoja voi käyttää fwupd-laajennuksia asentamatta niitä järjestelmään." #. TRANSLATORS: the %1 is a BIOS setting name. #. * %2 and %3 are the values, e.g. "True" or "Windows10" #, c-format msgid "This tool can change the BIOS setting '%s' from '%s' to '%s' automatically, but it will only be active after restarting the computer." msgstr "Tämä työkalu voi vaihtaa BIOS-asetuksen ”%s” automaattisesti arvosta ”%s” arvoon ”%s”, mutta muutos tulee voimaan vasta uudelleenkäynnistyksen jälkeen." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Tätä työkalua voi käyttää vain root-käyttäjä" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Tämä työkalu lukee ja jäsentää TPM-tapahtumalokin järjestelmän laiteohjelmistosta." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Ohimenevä häiriö" #. TRANSLATORS: item is TRUE msgid "True" msgstr "Tosi" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Luotettu metatieto" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Luotettu lataus" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tyyppi" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI Bootservice Variables" msgstr "UEFI-käynnistyspalvelun muuttujat" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition may not be set up correctly" msgstr "UEFIn ESP-osion asetuksia ei ole ehkä tehty oikein" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "UEFIn ESP-osiota ei havaittu tai määritetty" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI-laiteohjelmistoapuohjelma" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "UEFI:n alusta-avain" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "UEFI-turvakäynnistys (secure boot)" #. TRANSLATORS: longer description msgid "UEFI Secure Boot prevents malicious software from being loaded when the device starts." msgstr "UEFI-turvakäynnistys (secure boot) estää haittaohjelmien lataamisen laitteen käynnistyessä." #. TRANSLATORS: longer description msgid "UEFI boot service variables should not be readable from runtime mode." msgstr "UEFI-käynnistyspalvelun muuttujia ei pitäisi pystyä lukemaan ajon aikana." #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI bootservice variables" msgstr "UEFI-käynnistyspalvelun muuttujat" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "UEFI-kapselipäivitykset eivät ole saatavilla tai niitä ei ole otettu käyttöön laiteohjelmiston asetuksissa" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "UEFI-dbx-apuohjelma" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "UEFI-laiteohjelmiston päivitys ei onnistu vanhassa BIOS-tilassa" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "UEFI:n alusta-avain" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "UEFI-turvakäynnistys (secure boot)" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Yhteys palveluun ei onnistu" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "Asetusta ei löydy" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Irrota nykyinen ajuri" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Laiteohjelmiston eston poistaminen:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Poistaa asennuseston laiteohjelmistosta" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Salaamaton" #. TRANSLATORS: command description msgid "Uninhibit the system to allow upgrades" msgstr "Poista päivitysten esto käytöstä" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Tuntematon" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Tuntematon laite" msgid "Unlock the device to allow access" msgstr "Avaa laitteen lukitus salliaksesi käytön" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Auki" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Avaa laitteen lukituksen, jotta laiteohjelmistoon on pääsy" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Irrota ESP" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Poista virheenkorjauksen lippu päivityksen aikana" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Allekirjoittamaton hyötykuorma" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Taustaprosessin versiota %s ei tueta, asiakasohjelman versio on %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Ydin ei ole tainted-tilassa" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Päivitettävissä" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Päivitysvirhe" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Päivityskuva" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Päivityksen viesti" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Päivityksen tila" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Päivityksen epäonnistuminen on tunnettu ongelma. Saat lisätietoja tästä URL-osoitteesta:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Päivitetäänkö nyt?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Päivitys edellyttää uudelleenkäynnistyksen" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Päivitä tallennettu kryptografinen tiiviste nykyisellä ROM-sisällöllä" msgid "Update the stored device verification information" msgstr "Päivitä laitteen tallennetut vahvistustiedot" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Päivitä tallennetut metatiedot nykyisellä sisällöllä" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "Päivittää kaikki määritetyt laitteet uusimpaan laiteohjelmistoversioon - tai kaikki laitteet, jos määritystä ei ole annettu" msgid "Updating" msgstr "Päivittää" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Päivitetään laitteen %s ohjelmisto versiosta %s versioon %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Päivittää laitetta %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Päivitetäänkö laitteen %s laiteohjelmisto versiosta %s versioon %s?" #. TRANSLATORS: ask the user to share, %s is something #. * like: "Linux Vendor Firmware Service" #, c-format msgid "Upload these anonymous results to the %s to help other users?" msgstr "Lähetetäänkö nämä anonyymit tulokset palveluun %s, jotta niiden avulla voidaan auttaa muita käyttäjiä?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Laiteohjelmistoraporttien lähettäminen auttaa laitteistotoimittajia tunnistamaan nopeasti virheelliset ja onnistuneet päivitykset todellisissa laitteissa." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Kiireellisyys" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Ohje: %s" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Peru painamalla CTRL^C." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Suorita fwupdtool --help nähdäksesi ohjeet" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Käytä erikoisominaisuuslippuja (quirk flags) asennettaessa laiteohjelmistoa" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Käyttäjälle on ilmoitettu" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Käyttäjätunnus" msgid "VID:PID" msgstr "VID:PID (toimittajatunnus:tuotetunnus)" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Kunnossa" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Vahvistetaan ESP:n sisältöä..." #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Muunnelma" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Toimittaja" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Vahvistetaan…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Versio" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Versio[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "VAROITUS" #. TRANSLATORS: command description msgid "Wait for a device to appear" msgstr "Odota että laite ilmestyy" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Odotetaan…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Seuraa laitteiston muutoksia" #. TRANSLATORS: check various UEFI and ACPI tables are unchanged after the #. update msgid "Will measure elements of system integrity around an update" msgstr "Tarkistaa järjestelmän eheyden yksityiskohtia päivityksen ympärillä" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Kirjoita laiteohjelmisto tiedostosta laitteeseen" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Kirjoita laiteohjelmisto tiedostosta osioon" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Kirjoitetaan tiedosto:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Kirjoitetaan…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Jakelijasi ei ehkä ole varmistanut laiteohjelmistopäivitysten yhteensopivuutta järjestelmän tai liitettyjen laitteiden kanssa." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Laitteistosi saattaa vaurioitua tämän laiteohjelmiston käytöllä. Asentaminen voi mitätöidä toimittajan %s takuun." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "Järjestelmäsi on asetettu BKC:hen (paras tunnettu konfiguraatio) %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[TARKISTESUMMA]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[LAITETUNNUS|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[LAITETUNNUS|GUID] [HAARA]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[LAITETUNNUS|GUID] [VERSIO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[TIEDOSTO TIEDOSTON_ALLEKIRJOITUS LÄHDETUNNUS]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[TIEDOSTONIMI1] [TIEDOSTONIMI2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[SYY]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[ASETUS1] [ ASETUS2]..." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[ASETUS1] [ASETUS2] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SMBIOS-TIEDOSTO|HWIDS-TIEDOSTO]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "oletus" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "fwupd:n TPM-tapahtumalokin apuohjelma" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd-liitännäiset" fwupd-1.9.16/po/fr.po000066400000000000000000001000551460375044200143300ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Claude Paroz , 2021 # Corentin Noël , 2020 # Franck , 2015 # Julien Humbert , 2020-2021 # Yolopix ​, 2022 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: French (http://app.transifex.com/freedesktop/fwupd/language/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fr\n" "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minute restante" msgstr[1] "%.0f minutes restantes" msgstr[2] "%.0f minutes restantes" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Mise à jour de %s" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "Version %s" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u jour" msgstr[1] "%u jours" msgstr[2] "%u jours" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "Une mise à jour de micrologiciel est disponible pour %u appareil." msgstr[1] "Une mise à jour de micrologiciel est disponible pour %u appareils." msgstr[2] "Une mise à jour de micrologiciel est disponible pour %u appareils." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u heure" msgstr[1] "%u heures" msgstr[2] "%u heures" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minute" msgstr[1] "%u minutes" msgstr[2] "%u minutes" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u seconde" msgstr[1] "%u secondes" msgstr[2] "%u secondes" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(obsolète)" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Activation de la mise à jour du micrologiciel" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Activation de la mise à jour de micrologiciel pour" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Âge" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias de %s" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Une mise à jour nécessite un redémarrage pour se terminer." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Une mise à jour nécessite que le système soit éteint pour se terminer." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Répondre oui à toutes les questions" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Appliquer les mises à jour de micrologiciels" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Appliquer la mise à jour même quand ce n'est pas conseillé" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Appliquer les fichiers de mise à jour" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Application de la mise à jour…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Micrologiciel approuvé :" msgstr[1] "Micrologiciels approuvés :" msgstr[2] "Micrologiciels approuvés :" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Demander à nouveau la prochaine fois ?" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Authentification…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Une authentification est nécessaire pour définir la liste des micrologiciels approuvés" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Une authentification est nécessaire pour signer les données en utilisant le certificat du client" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Authentification requise pour déverrouiller un périphérique" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Authentification requise pour mettre à jour le micrologiciel sur un périphérique amovible" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Une authentification est nécessaire pour mettre à jour le micrologiciel sur cette machine" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Rapports automatiques" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Envoyer automatiquement à chaque fois ?" msgid "BYTES" msgstr "OCTETS" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Version du chargeur d’amorçage" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Branche" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Annuler" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Annulé" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Impossible d'appliquer car la mise à jour dbx a déjà été appliquée." #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Modifié" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Vérifie que l'empreinte cryptographique correspond au micrologiciel" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Somme de contrôle" #. TRANSLATORS: error message msgid "Command not found" msgstr "Commande non trouvée" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Critique" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Version actuelle" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Options de débogage" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Décompression…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Description" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Détails" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Drapeaux de périphérique" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Périphérique ajouté :" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Périphérique modifié :" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Le périphérique est verrouillé" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Périphérique retiré :" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Méthode de mise à jour du périphérique" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Désactivé" msgid "Disabled fwupdate debugging" msgstr "Débogage fwupdate désactivé" #. TRANSLATORS: command line option msgid "Display version" msgstr "Afficher la version" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Ne pas vérifier d'anciennes métadonnées" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Ne pas écrire dans la base de données de l'historique" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Terminé !" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Rétrogradation de %s de %s en %s" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Téléchargement…" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Durée" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Activé" msgid "Enabled fwupdate debugging" msgstr "Débogage fwupdate activé" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "L'activation de cette fonctionnalité est à vos propres risques, ce qui signifie que vous devrez contacter le fabricant d'origine de votre équipement au sujet de tout problème éventuel causé par ces mises à jour. Seuls les problèmes liés au processus de mise à jour doivent être signalés à $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Chiffré" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "RAM chiffrée" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Effacer tout l'historique de mise à jour du micrologiciel" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Effacement…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Quitter après un bref délai" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Quitter après le chargement du moteur" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "FICHIER" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Échec" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Échec d'application de la mise à jour" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Impossible d'obtenir les périphériques en attente" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Échec d'installation de la mise à jour du micrologiciel" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Échec de chargement du dbx local" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Échec de chargement du dbx système" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Échec de verrouillage" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Echec de l'analyse des paramètres" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Échec d'analyse du dbx local" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Échec du redémarrage" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Échec de validation des contenus ESP" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Nom de fichier" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Signature de nom de fichier" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Source de nom de fichier" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Nom de fichier obligatoire" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "URI de base du micrologiciel" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Service D-Bus de mise à jour des micrologiciels" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Service de mise à jour de micrologiciel" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Attestation de micrologiciel" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Le micrologiciel est déjà bloqué" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Le micrologiciel n'est pas déjà bloqué" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Mises à jour de micrologiciels" msgid "Firmware updates are not supported on this machine." msgstr "Les mises à jour de micrologiciels ne sont pas prises en charge sur cette machine." msgid "Firmware updates are supported on this machine." msgstr "Les mises à jour de micrologiciels sont prises en charge sur cette machine." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Les mises à jour de micrologiciel sont désactivées ; exécutez «fwupdmgr unlock» pour les activer" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Drapeaux" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Trouvé" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Chiffrement complet du disque détecté" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUIDs" msgstr[2] "GUIDs" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Obtenir la liste des périphériques supportant les mises à jour de micrologiciel" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Obtenir les détails d'un fichier de micrologiciel" #. TRANSLATORS: the release urgency msgid "High" msgstr "Haute" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Identifiant de sécurité de l'hôte :" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "En attente…" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Durée d'installation" msgid "Install signed device firmware" msgstr "Installer le micrologiciel signé du périphérique" msgid "Install signed system firmware" msgstr "Installer le micrologiciel signé du système" msgid "Install unsigned system firmware" msgstr "Installer le micrologiciel non signé du système" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Installation de micrologiciel…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Installation de la mise à jour du micrologiciel..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Installation sur %s…" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET actif" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET activé" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Périphérique interne" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Invalide" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "CLÉ,VALEUR" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Trousseau de clés" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Dernière modification" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Moins d’une minute restante" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licence" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Noyau Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Espace d'échange (swap) Linux" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Afficher la liste des entrées de la base dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Liste les mises à jour de micrologiciel supportées" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Chargement…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Verrouillé" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Faible" msgid "MEI version" msgstr "Version MEI" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Moyenne" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Signature de métadonnées" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI de métadonnées" #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Version minimum" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Nouvelle version" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Aucune action indiquée !" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Aucune rétrogradation pour %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Aucun identifiant de micrologiciel trouvé" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Aucun matériel ayant des capacités de mise à jour du micrologiciel n'a été détecté" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Aucun greffon trouvé" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Aucune mise à jour n'a été appliquée" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Non trouvé" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Non pris en charge" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "OK" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "CHEMIN" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Analyse de la mise à jour dbx…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Analyse de la base dbx système…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Mot de passe" msgid "Payload" msgstr "Charge utile" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Effectuer l'opération ?" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Veuillez saisir un nombre de 0 à %u :" #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Dépendances de greffons manquantes" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Version précédente" msgid "Print the version number" msgstr "Afficher le numéro de version" msgid "Print verbose debug statements" msgstr "Afficher les instructions de débogage verbeuses" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Priorité" msgid "Proceed with upload?" msgstr "Continuer avec le téléversement ?" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Propriétaire" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Lecture depuis %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Lecture…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Redémarrage…" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Réinstaller le micrologiciel sur un périphérique" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Réinstallation de %s en %s" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI des rapports" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "Système de fichier efivarfs requis non trouvé" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "Le matériel requis n'a pas été trouvé" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Redémarrer maintenant ?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Redémarrer le service pour rendre la modification effective ?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Redémarrage du périphérique…" #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Exécutez «fwupdmgr get-upgrades» pour plus d'informations." #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Planification..." #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Démarrage sécurisé désactivé" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Démarrage sécurisé activé" #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Voir %s pour plus d'informations." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Périphérique sélectionné" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Volume sélectionné" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Numéro de série" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Définir la liste des micrologiciels approuvés" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Partager l'historique du micrologiciel avec les développeurs" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Afficher tous les résultats" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Montrer les options de débogage" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Montre des informations de débogage complémentaires" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Afficher la version calculée de la base dbx" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Éteindre maintenant ?" msgid "Sign data using the client certificate" msgstr "Signer les données en utilisant le certificat du client" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Signer les données en utilisant le certificat du client" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Signer les données envoyées avec le certificat du client" msgid "Signature" msgstr "Signature" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Taille" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Source" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Indiquer le fichier de base de données dbx" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Tous les périphériques ont été activés avec succès" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "La valeur de configuration a été modifiée avec succès" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Succès de l'envoi de %u rapport" msgstr[1] "Succès de l'envoi de %u rapports" msgstr[2] "Succès de l'envoi de %u rapports" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Résumé" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Pris en charge" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "Le système nécessite une source d'alimentation externe" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEXTE" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" msgid "Target" msgstr "Cible" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS est un service libre qui opère en tant qu'entité légale indépendante et n'est aucunement connecté à $OS_RELEASE:NAME$. Votre distributeur n'a pas forcément vérifié la compatibilité des mises à jour de micrologiciel avec votre système ou avec les appareils connectés. Tous les micrologiciels ne sont fournis que par le fabricant original de votre équipement." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Il n'y a aucun micrologiciel approuvé." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Ce paquet n'a pas été validé, il peut ne pas fonctionner correctement." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Cet outil permet à un administrateur d'appliquer les mises à jour dbx UEFI." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Cet outil permet à un administrateur de déboguer l'opération UpdateCapsule." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Cet outil ne peut être utilisé que par l'utilisateur root" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Type" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "Partition ESP UEFI non détectée ou non configurée" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Utilitaire de micrologiciel UEFI" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "Les mises à jour de capsule UEFI ne sont pas disponibles ou pas activées dans la configuration du micrologiciel" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "Utilitaire dbx UEFI" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "Clé de plate-forme UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "Démarrage sécurisé UEFI" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Impossible de se connecter au service" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Déchiffré" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Inconnu" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Déverrouillé" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Déverrouille le périphérique pour l'accès au micrologiciel" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Mise à jour possible" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Erreur de mise à jour" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Message de mise à jour" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "État de mise à jour" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Cet échec de mise à jour est un problème connu, visitez cette URL pour plus d'informations :" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Mettre à jour maintenant ?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "La mise à jour nécessite un redémarrage" msgid "Updating" msgstr "Mise à jour" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Mise à jour de %s de %s en %s" #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Mise à jour de %s…" #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Urgence" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Nom d’utilisateur" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Valide" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Validation des contenus ESP…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variante" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Fournisseur" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Vérification…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Version" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "En attente…" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Écriture du fichier :" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Écriture…" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[SOMME DE CONTRÔLE]" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "Greffons fwupd" fwupd-1.9.16/po/fur.po000066400000000000000000001710671460375044200145300ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Fabio Tomat , 2017-2018,2020,2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Friulian (http://app.transifex.com/freedesktop/fwupd/language/fur/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fur\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "Al mancjie %.0f minût" msgstr[1] "A mancjin %.0f minûts" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Inzornament dispositîf %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Inzornament di %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "Pal moment nol è pussibil inzornâ %s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "Modalitât costrutôr %s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "Override %s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "Version di %s" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u dispositîf al à un inzornament firmware disponibil." msgstr[1] "%u dispositîfs a àn un inzornament firmware disponibil." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u dispositîf no e la miôr configurazion cognossude." msgstr[1] "%u dispositîfs no son la miôr configurazion cognossude." #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u dispositîf locâl supuartât" msgstr[1] "%u dispositîfs locâi supuartâts" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "Protezion de scriture dal firmware AMD" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Ative dispositîfs" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Ative dispositîfs in spiete" msgid "Activate the new firmware on the device" msgstr "Ative il gnûf firmware sul dispositîf" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Ativazion inzornament firmware" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Ativazion dal inzornament firmware par" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Etât" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Acetâ e abilitâ la sorzint esterne?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias a %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Permet di tornâ indaûr aes versions di firmware precedentis" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Permet di tornâ a instalâ lis versions dal firmware esistentis" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Permet di cambiâ ram dal firmware" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Un inzornament al à bisugne che si torni a inviâ il computer par finî." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Un inzornament, par completâsi, al à bisugne che si distudedi il sisteme." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Rispuint di sì a dutis lis domandis" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Apliche i inzornaments firmware" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Apliche l'inzornament ancje cuant che nol è conseât" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Apliche i files di inzornament" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Daûr a aplicâ l'inzornament…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Firmware aprovât:" msgstr[1] "Firmware aprovâts:" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Zonte ae modalitât firmware" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Daûr a autenticâ…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "I detais de autenticazion a son necessaris" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "La autenticazion e je necessarie par tornâ indaûr ae version precedente dal firmware suntun dispositîf estraibil " #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "La autenticazion e je necessarie par tornâ indaûr ae version precedente dal firmware su cheste machine" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "La autenticazion e je necessarie par modificâ lis impostazions dal BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "La autenticazion e je necessarie par modificâ une sorzint esterne configurade, doprade pai inzornaments dal firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "La autenticazion e je necessarie par modificâ la configurazion dal demoni" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "La autenticazion e je necessarie par lei lis impostazions BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "La autenticazion e je necessarie par stabilî la liste dai firmware aprovâts" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "La autenticazion e je necessarie par firmâ i dâts doprant il certificât dal client" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "La autenticazion e je necessarie par passâ ae gnove version dal firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "La autenticazion e je necessarie par sblocâ un dispositîf" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "La autenticazion e je necessarie par inzornâ il firmware suntun dispositîf estraibil" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "La autenticazion e je necessarie par inzornâ il firmware su cheste machine" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "La autenticazion e je necessarie par inzornâ i checksum archiviâts pal dispositîf" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "Inzornaments BIOS dâts fûr vie LVFS o Windows Update" msgid "BYTES" msgstr "BYTES" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Files di firmware blocâts:" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Firmware blocât:" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Compile un file di firmware" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Anule" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Anulât" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Impussibil aplicâ viodût che l'inzornament dbx al è za stât aplicât." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Impussibil aplicâ i inzornaments su supuarts “live”" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Modificât" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Checksum" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Sielç l'ESP:" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Al nete i risultâts dal ultin inzornament" #. TRANSLATORS: error message msgid "Command not found" msgstr "Comant no cjatât" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Convertìs un file di firmware" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Utilitât DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opzions di debug" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Daûr a decomprimi…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Descrizion" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Variabilis/flags dispositîf" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "ID dispositîf" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Dispositîf zontât:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Dispositîf modificât:" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "Il dispositîf al è emulât" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Il dispositîf al è blocât" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Impussibil rivâ al dispositîf" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Dispositîf gjavât:" #. TRANSLATORS: longer description msgid "Device software updates are provided for this device." msgstr "Su chest dispositîf a son dâts fûr inzornaments." #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Il dispositîf al apliche i inzornaments a tapis" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Metodi di inzornament dal dispositîf" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Dispositîfs che a son stâts inzornâts cun sucès:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Dispositîfs che no son stâts inzornâts ben:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Dispositîfs cence inzornaments firmware disponibii:" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Disabilitât" msgid "Disabled fwupdate debugging" msgstr "Disabilite il debug di fwupdate" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Al disabilite une sorzint esterne indicade" #. TRANSLATORS: command line option msgid "Display version" msgstr "Visualize la version" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Distribuzion" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "No sta controlâ se lis sorzint esternis dal discjariament a àn di sei abilitadis" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "No sta includi il prefìs il domini dal regjistri" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "No sta includi il prefìs date/ore" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "No sta eseguî i controi di sigurece dal dispositîf" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "No sta domandâ dispositîfs" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Desideristu inzornâ cheste sorzint esterne cumò?" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Fat!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Puartâ indaûr %s de version %s ae %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Al torne indaûr ae version precedente dal firmware suntun dispositîf" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Daûr a tornâ indaûr ae version precedente di %s de %s ae %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Daûr a degradâ di version %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Discjame un file" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Daûr a discjariâ…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Scrîf jù i dâts SMBIOS di un file" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Durade" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "L'ESP specificât nol jere valit" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Host emulât" msgid "Enable" msgstr "Abilite" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Abilite il supuart dal inzornament dal firmware sui sistemis supuartâts" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Abilitâ gnove sorzint esterne?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Abilitâ cheste sorzint esterne?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Abilitât" msgid "Enabled fwupdate debugging" msgstr "Abilite il debug di fwupdate" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Abilitât se l'hardware al corispuint" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Al abilite une sorzint esterne indicade" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Si abilite cheste funzionalitât a propri pericul, che al significhe che, par ogni probleme causât di chescj inzornaments, si à di contatâ il produtôr origjinâl dal imprest. Dome i problemis che si àn cul sôl procès di inzornament a àn di sei inviâts a $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "La abilitazion di cheste sorzint esterne e ven fate a to pericul." #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "RAM cifrade" msgid "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." msgstr "La RAM cifrade e fâs in mût che al sedi impussibil lei lis informazions archiviadis te memorie dal dispositîf, se il banc di memorie al ven gjavât e doprât." #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Scancele dute la cronologjie dai inzornaments dal firmware" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Daûr a scancelâ…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Jes dopo un piçul ritart" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Jes dopo che il motôr al à cjariât" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "NONFILE" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Impussibil aplicâ l'inzornament" #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "No si è rivâts a conetisi al demoni" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "No si è rivâts a otignî i dispositîfs in spiete" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "No si è rivâts a instalâ l'inzornament dal firmware" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Impussibil cjariâ il dbx locâl" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Impussibil cjariâ lis soluzions alternativis" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Impussibil cjariâ il dbx di sisteme" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr " Impussibil blocâ" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "No si è rivâts a analizâ i argoments" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "No si è rivâts a analizâ il file" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Impussibil analizâ lis variabilis/flags par --filter" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Impussibil analizâ il dbx locâl" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr " No si è rivâts a tornâ a inviâ il sisteme" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "No si è rivâts a stabilî la modalitât splash" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Impussibil convalidâ i contignûts ESP" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Non file" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Firme non file" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Sorzint dal non di file" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Non di file necessari" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Attestazion firmware" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Descritôr BIOS dal firmware" #. TRANSLATORS: longer description msgid "Firmware BIOS Descriptor protects device firmware memory from being tampered with." msgstr "Il Descritôr BIOS dal firmware al protêç des modifichis la memorie firmware dal dispositîf." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Regjon BIOS dal firmware" #. TRANSLATORS: longer description msgid "Firmware BIOS Region protects device firmware memory from being tampered with." msgstr "La Regjon BIOS dal firmware e protêç des modifichis la memorie firmware dal dispositîf ." #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "URI de base dal firmware" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Servizi D-Bus inzornament firmware" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Demoni di inzornament firmware" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Verifiche dal inzornadôr dal firmware" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Inzornaments firmware" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Utilitât firmware" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Protezion di scriture dal firmware" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Bloc di protezion de scriture dal firmware" #. TRANSLATORS: longer description msgid "Firmware Write Protection protects device firmware memory from being tampered with." msgstr "La protezion de scriture dal firmware e protêç des modifichis la memorie dal firmware dai dispositîfs." #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Attestazion firmware" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "I metadâts dal firmware no son stâts inzornâts par %u zornade e a podaressin jessi vielis." msgstr[1] "I metadâts dal firmware no son stâts inzornâts par %u dîs e a podaressin jessi vielis." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Inzornaments firmware" msgid "Firmware updates are not supported on this machine." msgstr "Su cheste machine no son supuartâts i inzornaments firmware." msgid "Firmware updates are supported on this machine." msgstr "Su cheste machine a son supuartâts i inzornaments firmware." #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Sfuarce la azion smolant cualchi control in timp di esecuzion" msgid "Force the action ignoring all warnings" msgstr "Sfuarce la azion ignorant ducj i avertiments" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Cjatât" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUIDs" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" msgid "Get BIOS settings" msgstr "Oten impostazions BIOS" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Oten dutis lis variabilis/flags supuartadis di fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Oten ducj i dispositîfs che a supuartin i inzornaments dal firmware" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Al oten detais su un file di firmware" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Al oten lis sorzint esternis configuradis" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Al oten la liste dai firmware aprovâts" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Al oten la liste dai firmware blocâts" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Al oten la liste di inzornaments pal hardware tacât" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Al oten lis publicazions par un dispositîf" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Al oten i risultâts dal ultin inzornament" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "Protezion IOMMU" #. TRANSLATORS: longer description msgid "IOMMU Protection prevents connected devices from accessing unauthorized parts of system memory." msgstr "La Protezion IOMMU e impedìs ai dispositîfs colegâts di acedi a parts no autorizadis de memorie dal sisteme." #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "In polse…" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignore la covalide dai controi di sigurece" msgid "Install old version of signed system firmware" msgstr "Instale la version vecje dal firmware di sisteme firmât" msgid "Install old version of unsigned system firmware" msgstr "Instale la version vecje dal firmware di sisteme cence firme" msgid "Install signed device firmware" msgstr "Instasle firmware di dispositîf firmât" msgid "Install signed system firmware" msgstr "Instale firmware di sisteme firmât" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Instale prime sul dispositîf gjenitôr" msgid "Install unsigned device firmware" msgstr "Instale firmware di dispositîf cence firme" msgid "Install unsigned system firmware" msgstr "Instale firmware di sisteme cence firme" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Daûr a instalâ il firmware…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Daûr a instalâ l'inzornament dal firmware…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Daûr a instalâ su %s…" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "ACM protet di Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "ACM protet di Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Politiche sui erôrs di Intel BootGuard" msgid "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." msgstr "La Politiche sui erôrs di Intel BootGuard e garantìs che il dispositîf nol continui a inviâsi se il software dal dispositîf al è stât modificât." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Fusibil OTP di Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Inviament verificât di Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Politiche di erôr di Intel BootGuard" #. TRANSLATORS: longer description msgid "Intel BootGuard prevents unauthorized device software from operating when the device is started." msgstr "Intel BootGuard al impedìs al software di dispositîfs no-autorizâts dal operâ cuant che il dispositîf al è inviât." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Inviament verificât di Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET atîf" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET abilitât" #. TRANSLATORS: longer description msgid "Intel Control-Flow Enforcement Technology detects and prevents certain methods for running malicious software on the device." msgstr "Intel Control-Flow Enforcement Technology al rileve e al impedìs cierts metodis di esecuzion di software malevul sul dispositîf." #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Modalitât di produzion dal Intel Management Engine" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Intel Management Engine Override" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Version dal Intel Management Engine" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: longer description msgid "Intel Supervisor Mode Access Prevention ensures critical parts of device memory are not accessed by less secure programs." msgstr "Intel Supervisor Mode Access Prevention al garantìs che i programs mancul sigûrs no podedin doprâ ciertis parts critichis de memorie dal dispositîf." #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Dispositîf interni" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Argoments no valits" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Al è pussibil puartâ indaûr di version" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Puarteclâfs" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Al mancje mancul di un minût" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Lockdown dal kernel Linux" #. TRANSLATORS: longer description msgid "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgstr "La modalitât Lokdown dal Kernel Linux e impedìs ai accounts dai aministradôrs (root) di acedi e cambiâ parts critichis dal software di sisteme." msgid "Linux Kernel Swap temporarily saves information to disk as you work. If the information is not protected, it could be accessed by someone if they obtained the disk." msgstr "Il Swap dal kernel Linux, vâl a dî la memorie di scambi, e salve in mût temporani informazions su disc intant che tu lavoris. Se lis informazions no son protetis, cualchidun al podarès acedi a chestis informazions se al oten il disc." #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Verifiche dal kernel Linux" msgid "Linux Kernel Verification makes sure that critical system software has not been tampered with. Using device drivers which are not provided with the system can prevent this security feature from working correctly." msgstr "La verifiche dal kernel Linux e garantìs che il software critic di sisteme nol sedi modificât. Se a vegnin doprâts dirvers di dispositîf che no son stâts dâts fûr cul sisteme, al è pussibil impedî di lavorâ ben a cheste funzionalitât di sigurece." #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Swap di Linux" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Servizi Firmware dal vendidôr di Linux (firmware stabil)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Servizi Firmware dal vendidôr di Linux (firmware di prove)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Kernel Linux" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Lockdown dal kernel Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Swap di Linux" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Vôs de liste in dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Liste dai inzornaments di firmware supuartâts" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Liste i gjenars di firmware disponibii" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Daûr a cjariâ…" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "Modalitât costrutôr MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "Override MEI" msgid "MEI version" msgstr "Version MEI" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Firme metadâts" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI metadata" #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Version minime" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Al modifiche un valôr di configurazion dal demoni" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Al modifiche une sorzint esterne indicade" msgid "Modify a configured remote" msgstr "Modifiche une sorzint esterne configurade" msgid "Modify daemon configuration" msgstr "Modifiche la configurazion dal demoni" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Monitore il demoni pai events" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr " Al necessite di tornâ a inviâ il sisteme" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Gnove version" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Nissune azion specificade!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Nissune degradazion di version par %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Nissun ID di firmware cjatât" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Nissun hardware rilevât un funzionalitâts di inzornament dal firmware" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nissun plugin cjatât" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Nissune publicazion disponibile" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "In chest moment no je abilitade nissune sorzint esterne duncje nissun metadât al è disponibil." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Nissune sorzint esterne disponibile" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Nissun dispositîf che si pues inzornâ" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Nissun inzornament disponibil" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nol è stât aplicât nissun inzornament" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "No cjatât" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "Va ben" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "Va ben!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Version vecje" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Mostre dome il valôr PCR" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Passe parsore al valôr dal percors ESP predefinît" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "PERCORS" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Analize e mostre i detais in merit a un file di firmware" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Daûr a analizâ l'inzornament di dbx…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Daûr a analizâ il dbx di sisteme…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Password" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Percentuâl di completament" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Debug de plateforme" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Debug de plateforme" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Inserìs un numar di 0 a %u: " #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "Protezion DMA di pre-inviament" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Protezion DMA pre-inviament" #. TRANSLATORS: longer description msgid "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." msgstr "La protezion DMA di pre-inviament e impedìs ai dispositîfs di acedi ae memorie di sisteme dopo che si son colegâts al computer." msgid "Print the version number" msgstr "Stampe il numar de version" msgid "Print verbose debug statements" msgstr "Stampe lis declarazions di debug in maniere prolisse" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritât" msgid "Proceed with upload?" msgstr "Procedi cul inviament?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Controi di sigurece dal processôr" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Interogazion pal supuart dai inzornaments firmware" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Lei di un dispositîf un file binari di firmware" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Lei di un dispositîf un firmware" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Lei il firmware dal dispositîf intun file" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Lei il firmware di une partizion intun file" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Daûr a lei di %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Daûr a lei…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Daûr a tornâ a inviâ il sisteme…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Inzorne i metadâts dal servidôr esterni" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Tornâ a instalâ %s %s? " #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Torne instale il firmware atuâl sul dispositîf" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Torne instale un firmware suntun dispositîf" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Daûr a tornâ a instalâ %s cun %s... " #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "ID publicazion" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID esterni" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Sostituìs i dâts intun file di firmware esistent" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI segnalazion" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Segnalât su servidôr esterni" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Richieste anulade" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Al à bisugne de conession a internet" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Tornâ a inviâ cumò?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Tornâ a inviâ il demoni par rindi efetive la modifiche?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Daûr a tornâ a inviâ il dispositîf…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Torne ducj i ID dal hardware pe machine" #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Eseguìs `fwupdmgr get-upgrades` par vê plui informazions." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "Descritôr dal BIOS SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "Regjon BIOS SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "Bloc SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "Scriture SPI" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "Protezion de scriture SPI" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Planifiche la instalazion pe volte sucessive che si torne a inviâ cuant che al è pussibil" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Daûr a planificâ…" #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Viôt %s par vê plui informazions." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Dispositîf selezionât" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Volum selezionât" msgid "Set one or more BIOS settings" msgstr "Configure une o plui impostazion BIOS" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Stabilìs la opzion di debug dilunc l'inzornament" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Al stabilìs la liste dai firmware aprovâts" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Lis impostazions a vignaran aplicadis daspò che il sisteme si tornarà a inviâ" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Condivît la conologjie dai firmware cui svilupadôrs" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Mostre versions di client e demoni" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Mostre lis informazions prolissis dal demoni par un domini particolâr" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Mostre lis informazions di debug par ducj i dominis" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Mostre opzions di debug" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Mostre i dispositîfs che no si puedin inzornâ" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Mostre informazions di debug adizionâls" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Mostre la cronologjie dai inzoronaments dal firmware" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Mostre la version calcolade dal dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Mostre il regjistri dal debug dal ultin tentatîf di inzornament" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Mostre lis informazions sul stât dal inzornament dal firmware" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Distudâ cumò?" msgid "Sign data using the client certificate" msgstr "Firme i dâts doprant il certificât dal client" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Firme i dâts doprant il certificât dal client" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Firme i dâts cjamâts in rêt cul certificât dal client" msgid "Signature" msgstr "Firme" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Specifiche Vendidôr/ID prodot dal dispositîf DFU" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Specifiche il file de base di dâts dal dbx" msgid "Specify the number of bytes per USB transfer" msgstr "Specifiche il numar di byte par trasferiment USB" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "Stringhe" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Ducj i dispositîfs a son stâts ativâts cun sucès" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Sorzint esterne disabilitade cun sucès" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Gnûf metadât discjariât cun sucès:" #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Sorzint esterne inzornade e abilitade cun sucès" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Sorzint esterne abilitade cun sucès" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmware instalât cun sucès" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Valôr di configurazion modificât cun sucès" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Sorzint modificade cun sucès" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metadât inzornât a man cun sucès" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Sumis di control dai dispositîfs inzornadis cun sucès" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "%u rapuart inviât cun sucès" msgstr[1] "%u rapuarts inviâts cun sucès" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Sumis di control dai dispositîfs verificadis cun sucès" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Sintesi" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "CPU supuartade" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Supuartât su servidôr esterni" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Sospension su inativitât" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Sospension su RAM" #. TRANSLATORS: longer description msgid "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Sospension su inativitât al permet al dispositîf di lâ in polse subite, cussì di sparagnâ energjie. Intant che il dispositîf al è sospindût, al è pussibil gjavâ in mût fisic il banc di memorie, e chest doprât par acedi aes informazions." #. TRANSLATORS: longer description msgid "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "La sospension su RAM e permet al dispositîf di lâ in polse subite, cussì di sparagnâ energjie. Intant che il dispositîf al è sospindût, al è pussibil gjavâ in mût fisic il banc di memorie, e chest doprât par acedi aes informazions." #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Suspend-to-idle" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Suspend-to-ram" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Cambiâ ram di %s a %s? " #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Cambie il ram dal firmware sul dispositîf" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "Ricostruzion di PCR0 dal TPM" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "Configurazion de plateforme TPM" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "Ricostruzion dal TPM" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "PCRs vueits di TPM" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Etichete" msgstr[1] "Etichetis" msgid "Target" msgstr "Destinazion" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "Il LVFS — Servizi firmware dal vendidôr di linux — al è un servizi gratuit che al opere come entitât legâl indipendente e no à conession cun $OS_RELEASE:NAME$. Il to distributôr al podarès no vê verificât la compatibilitât di nissun dai inzornaments firmware cul vuestri sisteme o cui dispositîfs tacâts. Ducj i firmware a son furnîts dome dal produtôr origjinâl dal imprest." #. TRANSLATORS: longer description msgid "The UEFI Platform Key is used to determine if device software comes from a trusted source." msgstr "La clâf de plateforme UEFI e ven doprade par determinâ se il software dal dispositîf al rive di une sorzint afidabile." #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "No'nd è nissun file di firmware blocât" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "No'nd è nissun firmware aprovât." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Chest program al pues lavorâ in maniere juste dome come root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Cheste sorzint esterne e conten firmware che no son sot di embargo, ma a son ancjemò in prove dal vendidôr dal hardware. Tu varessis di sigurâti di vê une maniere par puartâ indaûr a man il firmware ae version precedente, se l'inzornament al falìs." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "Chest sisteme nol supuarte lis configurazions dai firmwares" #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Chest imprest al permet a un aministradôr di aplicâ i inzornaments dbx UEFI." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Chest imprest al permet a un aministradôr di fâ il debug de operazion UpdateCapsule ." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Chest strument al pues jessi doprât dome dal utent root" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Chest strument al leiarà e al analizarà il regjistri events TPM dal firmware di sisteme." #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Gjenar" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Utilitât Firmware UEFI" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "Clâf de plateforme UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "Inviament sigûr di UEFI" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "Utilitât dbx di UEFI" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "Clâf de plateforme UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "Inviament sigûr di UEFI" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Impussibil conetisi al servizi" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Firmware sblocât:" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "No cognossût" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Dispositîf no cognossût" msgid "Unlock the device to allow access" msgstr "Sbloche il dispositîf par permeti l'acès" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Al sbloche il dispositîf pal acès al firmware" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Gjave la opzion di debug dilunc l'inzornament" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Version %s dal demoni no supuartade, la version dal client e je la %s" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Si pues inzornâ" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Erôr inzornament" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Imagjin di inzornament" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Stât inzornament" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Il faliment dal inzornament al è un probleme cognossût, visite chest URL par vê plui informazions:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Inzornâ cumò?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Pal inzornament al è necessari tornâ a inviâ il computer" msgid "Update the stored device verification information" msgstr "Inzorne lis informazions di verifiche dal dispositîf archiviadis" msgid "Updating" msgstr "Daûr a inzornâ" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Daûr a inzornâ %s di %s a %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Daûr a inzornâ %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Inzornâ %s de version %s ae %s?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Inviâ i rapuarts dal firmware al jude i vendidôrs di hardware a identificâ subite i inzornaments bogns e falimentârs sui dispositîfs reâi." #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Dopre variabilis/flags di soluzions alternativis cuant che si instale il firmware" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Non utent" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Daûr a convalidâ i contignûts ESP…" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Daûr a verificâ…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Version" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Version[fwupd]" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "In spiete…" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Scrîf il firmware dal file intal dispositîf" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Scrîf il firmware dal file intune partizion" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Daûr a scrivi il file:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Daûr a scrivi…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Il to distributôr al podarès no vê verificât nissun dai inzornaments firmware pe compatibilitât cui dispositîfs tacâts o cul to sisteme." #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "Utilitât di regjistrazion events TPM di fwupd" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "plugins di fwupd" fwupd-1.9.16/po/gl.po000066400000000000000000000733571460375044200143410ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Fran Diéguez , 2020 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Galician (http://app.transifex.com/freedesktop/fwupd/language/gl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: gl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] " Falta %.0f minuto" msgstr[1] " Faltan %.0f minutos" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "Modo de fabricación %s" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Activar os dispositivos pendentes" msgid "Activate the new firmware on the device" msgstr "Activar o novo firmware no dispositivo" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Aceptar e activar o remoto?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias a %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Permitir a desactualización de versións de firmware" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Permitir a reinstalación de versións de firmware existentes" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Aplicar actualizacións de firmware" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Aplicar actualización incluso cando non se avise" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Aplicar ficheiros de actualización" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Aplicando actualización…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Firmware aprovados:" msgstr[1] "Firmware aprovado:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Preguntar de novo a seguinte vez?" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Anexarse ao modo de firmware" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Autenticando…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Requírese autenticación para desactualizar o firmware nun dispositivo extraíbel" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Requírese autenticación para desactualizar o firmware nesta máquina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Requírese autenticación para modificar a configuración do remoto usado para actualizar firmwares" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Requírese autenticación para modificar a configuración do demonio" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Requírese autenticación para estabelecer a lista do firmware aprovado" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Requírese autenticación para asinar os datos usando o certificado do cliente" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Requírese autenticación para trocar a unha nova versión do firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Requírese autenticación para desbloquear un dispositivo" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Requírese autenticación para actualizar o firmware nun dispositivo extraíbel" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Requírese autenticación para actualizar o firmware nesta máquina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Requírese autenticación para actualizar as sumas de verificación para o dispositivo" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Cancelar" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Cancelado" #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Non foi posíbel aplicar as actualizacións no soporte multimedia" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Cambiado" #. TRANSLATORS: error message msgid "Command not found" msgstr "Orde non atopada" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Converter un ficheiro de firmware" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Utilidad DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opcións de depuración" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Descomprimindo…" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Desanexarse ao modo de firmware" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Dispositivo engadido:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Dispositivo cambiado" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Dispositivo retirado:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Dispositivos que foron actualizados con éxito:" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Desactivado" msgid "Disabled fwupdate debugging" msgstr "Desactivar a depuración de fwupdate" #. TRANSLATORS: command line option msgid "Display version" msgstr "Mostrar versión" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Non incluír o dominio do rexistro" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Non incluír o prefixo de marca de tempo" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Non levar a cabo comprobacións de dispositivo" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Feito!" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Desactualizando %s desde %s a %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Desactualizando %s" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Descargando…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Volcar os datos da SMBIOS desde un ficheiro " #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "O ESP especificado non é válido" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Activar a compatibilidade de actualización de firmware nos sistemas admitidos" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Desexa activar este remoto?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Activad" msgid "Enabled fwupdate debugging" msgstr "Activar a depuración de fwupdate" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Active esta funcionalidade baixo o seu risco, o que significa que ten que contactar co seu fabricante de equipamento orixinal se ten calquera problema con estas actualizacións. Só os problemas co proceso de actualización en si deberían enviarse en $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Cifrad" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "RAM cifrada" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Borrando…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Saír despois dun pequeno atraso" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Saír despois de que se cargue o motor" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Fallido" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Produciuse un fallo ao aplicar a actualización" #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Produciuse un fallo ao conectarse ao demoni" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Produciuse un fallo ao obter a lista de dispositivos pendentes" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Produciuse un fallo ao instalar a actualización do firmware" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Produciuse un fallo ao cargar o dbx local" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Produciuse un fallo ao cargar o dbx do sistema" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Produciuse un fallo ao analizar os argumentos" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Produciuse un fallo ao analizar o ficheiro" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Produciuse un fallo ao analizar as bandeiras para --filter" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Produciuse un fallo ao analizar a dbx local" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Produciuse un fallo ao reiniciar" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Produciuse un fallo ao modo splash" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Produciuse un fallo ao validar os contidos do ESP" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Requírese un nome de ficheiro" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Servizo D-Bus da Actualización do Firmware" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Demonio de Actualización de Firmware" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Utilidade de Firmware" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Actualizacións de firmware" msgid "Firmware updates are not supported on this machine." msgstr "Esta máquina non admite as actualizacións de firmware." msgid "Firmware updates are supported on this machine." msgstr "Esta máquina admite as actualizacións de firmware." msgid "Force the action ignoring all warnings" msgstr "Forzar a acción ignorando todas as advertencias" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Atopado" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Obter todas as bandeiras de dispositivos admitidos por fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Obtén todos os dispositivos que admiten actualizacións de firmware" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Obter todos os engadidos activos rexistrados no sistema" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Obtén a información sobre o ficheiro de firmware" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Obten os remotos configurados" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Obtén os atributos de seguranza do equipo" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Obtén a lista de actualizacións para o hardware conectado" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "ID de seguranza do equipo:" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Ocioso…" msgid "Install signed device firmware" msgstr "Instalar firmware de dispositivo asinado" msgid "Install signed system firmware" msgstr "Instalar firmware do sistema asinado" msgid "Install unsigned device firmware" msgstr "Instalar firmware de dispositivo non asinado" msgid "Install unsigned system firmware" msgstr "Instalar firmware do sistema non asinado" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Instalando Firmware…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Instalando actualización do firmware…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Instalando en %s…" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "ACM protexido Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Política de erro de Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Arrinque verificado de Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET activo" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET activado" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "SMAP de Intel" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Non válido" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Falta menos dun minuto" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Servizo de Linux Vendor Firmware (firmware estábel)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Servizo de Linux Vendor Firmware (firmware de probas)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Núcleo de Linux" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Bloqueo de kernel de Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Swap de Linux" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Mostrar as entradas no dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Mostrar actualizacións de firmware compatíbeis" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Lista todos os tipos de firmware dispoñíbeis" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Cargando…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Bloqueado" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "Modo de fabricación MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "Omitir MEI" msgid "MEI version" msgstr "Versión do MEI" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Activar manualmente engadidos específicos" msgid "Modify a configured remote" msgstr "Modificar un remoto configuración" msgid "Modify daemon configuration" msgstr "Modificación configuración do demonio" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Monitorizar eventos no demonio" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Non se require ningunha acción!" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Non se atoparon IDs de firmware" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Non se atoparon engadidos" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Non hai publicacións dispoñíbeis" #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Non hai remotos dispoñíbeis" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Non se aplicou ningunha actualización" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Non atopado" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Non compatíbel" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "OK" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Mostrar só un valor de PCR" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Sobrescribir a ruta predefinida do ESP" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Analixar e mostrar a información dun ficheiro de firmware" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Analizando a dbx de actualizacións…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Analizando o dbx do sistema…" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Porcentaxe completado" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Protección de DMA pre-arrinque" msgid "Print the version number" msgstr "Imprime o número de versión" msgid "Print verbose debug statements" msgstr "Imprime as sentencias de depuración verbosas" msgid "Proceed with upload?" msgstr "Desexa seguir coa subida?" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Consultar compatibilidade da actualización do firmware" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Ler un blob de firmware desde un dispositivo" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Ler firmware desde o dispositivo nun ficheiro" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Ler firmware desde unha partición nun ficheiro" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Lendo de %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Lendo…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Reiniciando…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Actualiza os metadatos desde un servidor remoto" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Reinstalar firmware nun dispositivo" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Reinstalando %s con %s... " #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Substituír datos nun ficheiro de firmware existente" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Require conexión a internet" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Desexa reiniciar o demonio para facer o cambio efectivo?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Reiniciando dispositivo…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Devolve todos os IDs do hardware da máquina" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "Rexión da BIOS Do SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "Bloqueo do SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "Escritura do SPI" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Planificando…" #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Seleccionar un dispositivo" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Volume seleccionado" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Establecer a bandeira de depuración durante a actualización" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Estabelece a lista do firmware aprobado" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Mostrar as versións de cliente e demonio" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Mostrar opcións de depuración" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Mostrar os dispositivos que non son actualizábeis" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Mostrar información de depuración adicional" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Mostrar o historial das actualizacións de firmware" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Mostrar a versión calculada do dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Mostrar rexistro de depuración desde o último intento de actualización" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Mostrar a información de estado da actualización do firmware" msgid "Sign data using the client certificate" msgstr "Asinar os datos usando o certificafo do cliente" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Asinar os datos usando o certificafo do cliente" msgid "Signature" msgstr "Sinatura" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Especifique os IDs do Fabricante/Produto do dispositivo DFU" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Especificar o ficheiro de base de datos dbx" msgid "Specify the number of bytes per USB transfer" msgstr "Especifique o número de bytes por transferenvia USB" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Remoto desactivado correctamente" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Remoto activado correctamente" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Instalación do firmware exitosa" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Remoto modficado correctamente" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Comprobáronse correctamente as sumas de verificación do dispositivo" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Compatíbel" #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Suspend-a-ocioso" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Suspender-a-ram" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "Reconstrución do PCR0 de TPM" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" msgid "Target" msgstr "Obxectivo" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "O LVFS é o servizo que opera como unha entidade legal independente e non ten ligazón con $OS_RELEASE:NAME$. O seu distribuidor podería non ter que comprobar se as actualizacións de firmware teñen compatibilidade co seu sistema ou dispositivos conectados. Todos os firmware son fornecidos por fabricantes de equipamento orixinal." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Este programa podería funcionar correctamente só como root" #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Esta ferrametne só pode ser usada por un usuario root" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Utilidade de firmware de UEFI" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "Utilidade UEFI dbx" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "Arrinque seguro de UEFI" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Descifrado" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Descoñecido" msgid "Unlock the device to allow access" msgstr "Desbloquear o dispositivo para permitir o acceso" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Desbloqueado" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Quitar a bandeira de depuración durante a actualización" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Actualizar agora" msgid "Update the stored device verification information" msgstr "Actualizar a información de verificación almacenada no dispositivo" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Actualizaar os metadatos almacenados cos contidos actuais" msgid "Updating" msgstr "Actualizando" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Actualizando %s desde %s a %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Actualizando %s…" #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Usar fwupdtool --help para obter axuda" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Válido" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Validando os contidos do ESP…" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verificando…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Versión" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Agardando…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Facer seguimento dos cambios de hardware" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Escribir firmware desde un ficheiro nun dispositivo" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Escribir firmware desde un ficheiro nunha partición" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Escribindo…" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "Utilidade de rexistro de eventos de TPM de fwupd" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "Engadidos de fwupd" fwupd-1.9.16/po/he.po000066400000000000000000001732341460375044200143260ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # dhead666 , 2015 # gk , 2015 # 63f334ffc0709ba0fc2361b80bf3c0f0_00ffd1e , 2021 # Yaron Shahrabani , 2021,2023 # Yosef Or Boczko , 2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Hebrew (http://app.transifex.com/freedesktop/fwupd/language/he/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: he\n" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "נותרה דקה %.0f" msgstr[1] "נותרו %.0f דקות" msgstr[2] "נותרו %.0f דקות" msgstr[3] "נותרו %.0f דקות" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "עדכון סוללה %s" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "עדכון מצלמה %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "עדכון הגדרות %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "עדכון התקן %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "עדכון בקר משובץ של %s" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "עדכון מקלדת %s" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "עדכון עכבר %s" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "עדכון מנשק רשת %s" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "עדכון בקר אחסון %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "עדכון מערכת %s" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "עדכון TPM %s" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "עדכון משטח מגע %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "עדכון %s" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s וכל ההתקנים המחוברים עשויים לא להיות שמישים בעת העדכון." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "מצב ייצור של %s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "גרסת %s" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "יום %u" msgstr[1] "יומיים (%u)" msgstr[2] "%u ימים" msgstr[3] "%u ימים" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "שעה %u" msgstr[1] "שעתיים (%u)" msgstr[2] "%u שעות" msgstr[3] "%u שעות" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "יש תמיכה בהתקן מקומי %u" msgstr[1] "יש תמיכה ב־%u התקנים מקומיים" msgstr[2] "יש תמיכה ב־%u התקנים מקומיים" msgstr[3] "יש תמיכה ב־%u התקנים מקומיים" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "דקה %u" msgstr[1] "%u דקות" msgstr[2] "%u דקות" msgstr[3] "%u דקות" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "שנייה %u" msgstr[1] "%u שניות" msgstr[2] "%u שניות" msgstr[3] "%u שניות" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(לא תקף עוד)" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "פעולה נדרשת:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "הפעלת התקנים" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "ההתקנים הממתינים מופעלים" msgid "Activate the new firmware on the device" msgstr "הפעלת קושחה חדשה בהתקן" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "עדכון הקושחה מופעל" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "עדכון הקושחה מופעל עבור" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "גיל" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "להסכים ולהפעיל את המרוחק?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "כינוי עבור %s" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "כל ההתקנים מאותו הסוג יעודכנו באותו הזמן" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "לאפשר שנמוך גרסאות קושחה" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "לאפשר להתקין מחדש גרסאות קושחה קיימות" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "לאפשר מעבר ענפי קושחה" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "נדרשת הפעלה מחדש להשלמת עדכון." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "נדרש כיבוי המערכת כדי להשלים עדכון." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "לענות כן על כל השאלות" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "החלת עדכוני קושחה" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "החלת עדכון אפילו אם לא מומלץ" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "החלת קובצי עדכון" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "העדכון חל…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "קושחה מאומתת:" msgstr[1] "קושחה מאומתת:" msgstr[2] "קושחה מאומתת:" msgstr[3] "קושחה מאומתת:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "לשאול שוב בפעם הבאה?" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "הצמדה למצב קושחה" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "מתבצע אימות…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "פרטי אימות נחוצים" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "נדרש אימות כדי לשנמך חומרה על התקן נשלף" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "נדרש אימות כדי לשנמך את הקושחה במכונה הזאת" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to fix a host security issue" msgstr "נדרש אימות כדי לתקן תקלות אבטחה במארח" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "נדרש אימות כדי לשנות הגדרות BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "נדרש אימות כדי לשנות מרוחק מוגדר לעדכוני קושחה" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "נדרש אימות לשינוי הגדרות הסוכן" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "נדרשת אימות על מנת לקרוא הגדרות BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "נדרש אימות כדי להגדיר את רשימת הקושחות המותרות" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "נדרש אימות כדי לחתום נתונים באמצעות אישור לקוח" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "נדרש אימות כדי לעבור לגרסת הקושחה החדשה" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to undo the fix for a host security issue" msgstr "נדרש אימות כדי לבטל את התיקון לתקלת אבטחת המארח" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "נדרש אימות כדי לשחרר התקן" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "נדרש אימות כדי לעדכן חומרה על התקן נשלף" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "אימות משתמש נדרש לעדכון קושחה מערכת זו" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "נדרש אימות כדי לעדכן את סכומי הביקורת עבור ההתקן" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "דיווח אוטומטי" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "להעלות אוטומטית בכל פעם?" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "עדכוני ה־BIOS מועברים דרך LVFS או Windows Update" msgid "BYTES" msgstr "בתים" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "איגוד למנהל התקן ליבה חדש" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "קובצי קושחה חסומים:" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "קושחה חוסמת:" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "גרסת מנהל טעינה" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "ענף" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "בניית קובץ קושחה" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "ביטול" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "בוטל" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "לא ניתן להחיל כיוון שכבר חל עדכון dbx." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "לא ניתן להחיל עדכונים על מדיה להפעלה חיה" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "השתנה" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "בודק שהגיבוב הקריפטוגרפי תואם לקושחה" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "סכום ביקורת" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "נא לבחור את ה־ESP:" #. TRANSLATORS: error message msgid "Command not found" msgstr "פקודה לא נמצאה" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "המרת קובץ קושחה" #. TRANSLATORS: when the update was built msgid "Created" msgstr "מועד יצירה" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "קריטי" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "גרסה נוכחית" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "כלי עדכון קושחת התקן" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "אפשרויות ניפוי שגיאות" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "מתבצעת פריסה…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "תיאור" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "ניתוק למצב מנהל טעינה" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "פרטים" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "דגלוני התקן" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "מזהה התקן" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "נוסף התקן:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "השתנה התקן:" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "ההתקן נעול" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "ההתקן אינו נגיש" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "הוסר התקנים:" #. TRANSLATORS: command line option msgid "Device update method" msgstr "שיטת עדכון התקן" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "עדכון ההתקן דורש הפעלה" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "התקנים שעודכנו כראוי:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "התקנים שלא עודכנו כראוי:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "התקנים ללא עדכוני קושחה זמינים:" #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "התקנים עם גרסת הקושחה העדכנית ביותר שזמינה:" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "מושבת" msgid "Disabled fwupdate debugging" msgstr "ניפוי תקלות ב־fwupdate מושבת" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "משבית מרוחק מסוים" #. TRANSLATORS: command line option msgid "Display version" msgstr "הצגת גרסה" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "לא לבדוק נתוני על ישנים" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "לא לבדוק היסטוריה לא מדווחת" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "לא לבדוק אם המרוחקים שהתקבלו אמורים להיות מופעלים" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "לא לבדוק או לבקש הפעלה מחדש לאחר עדכון" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "לא לכלול קידומת חותמת זמן" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "לא לבצע בדיקות בטיחות על ההתקן" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "לא לכתוב למסד הנתונים של ההיסטוריה" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "האם השלכות מעבר ענף הקושחה ברורות לך?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "להשבתי את היכולת הזאת לעדכונים עתידיים?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "לרענן את המרוחק הזה כעת?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "להעלות את הדוחות אוטומטית לעדכונים עתידיים?" #. TRANSLATORS: success #. success msgid "Done!" msgstr "הסתיים!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "לשנמך את %s מגרסה %s אל %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "משנמך קושחה על התקן" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "משנמך גרסת %s מ־%s ל־%s..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "%s משונמך…" #. TRANSLATORS: command description msgid "Download a file" msgstr "הורדת קובץ" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "מתבצעת הורדה…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "משיכת נתוני SMBIOS מקובץ" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "משך" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "ה־ESP שצוין לא היה תקף" msgid "Enable" msgstr "הפעלה" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "יש להפעיל תמיכה בעדכוני קושחה במערכות נתמכות" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "להפעיל מרוחק חדש?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "להפעיל את המרוחק הזה?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "מופעל" msgid "Enabled fwupdate debugging" msgstr "ניפוי תקלות ב־fwupdate פעיל" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "מפעיל מרוחק מסוים" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "הפעלת יכולת זאת היא על אחריותך בלבד, כלומר שעליך ליצור קשר עם ספק הציוד המקורי שלך בנוגע לתקלות שנגרמות על ידי העדכונים האלה. רק תקלות בתהליך העדכון עצמו אמורות להיות מתועדות אצל $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "הפעלת מרוחק זה היא על אחריותך בלבד." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "מוצפן" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "זיכרון מוצפן" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "למחוק את כל היסטוריית עדכוני הקושחה" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "מתבצעת מחיקה…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "יציאה לאחר השהייה קצרה" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "יציאה לאחר טעינת מנוע התכנה" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "ייצוא מבנה קובצי הקושחה ל־XML" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "חילוץ מקטע בינרי מהקושחה לקובצי דמות" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "שם-קובץ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "שם-קובץ שם-חלופי-להתקן|מזהה-חלופי-להתקן" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "שם-קובץ שם-חלופי-להתקן|מזהה-חלופי-להתקן [שם-חלופי-לדמות|מזהה-חלופי-לדמות]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "נכשל" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "החלת העדכון נכשלה" #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "החיבור לסוכן נכשל" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "קבלת ההתקנים הממתינים נכשלה" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "התקנת עדכון הקושחה נכשלה" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "טעינת ה־dbx המקומי נכשלה" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "טעינת התיקונים תואמי ההתקן נכשלה" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "טעינת ה־dbx של המערכת נכשלה" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "הנעילה נכשלה" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr " פענוח הארגומנטים נכשל" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "ניתוח הקובץ נכשל" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "פענוח הדגלונים עבור ‎--filter נכשל" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "פענוח ה־dbx המקומי נכשל" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "הפעלה מחדש נכשלה" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "הגדרת מצב מסך הכניסה נכשלה" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "אימות תוכן ה־ESP נכשל" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "שם קובץ" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "חתימת שם קובץ" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "מקור שם קובץ" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "נדרש שם קובץ" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "כתובת בסיס קושחה" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "שירות D-Bus עדכון קושחה" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "שדון עדכון קושחה" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "כלי קושחה" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "הקושחה כבר חסומה" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "הקושחה אינה חסומה כבר" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "עדכוני קושחה" msgid "Firmware updates are not supported on this machine." msgstr "במערכת הזאת אין תמיכה בעדכוני קושחה." msgid "Firmware updates are supported on this machine." msgstr "במערכת הזאת יש תמיכה בעדכוני קושחה." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "עדכוני קושחה מושבתים, נא להריץ את ‚fwupdmgr unlock’ כדי להפעיל." #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "דגלים" msgid "Force the action ignoring all warnings" msgstr "אילוץ הפעולה תוך התעלמות מכל האזהרות" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "נמצא" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "מזהה-קבוצה" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "מזהה-קבוצה" msgid "Get BIOS settings" msgstr "משיכת הגדרות ה־BIOS" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "מקבל את כל דגלוני ההתקנים שנתמכים על ידי fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "מציג כל המכשירים התומכים בעדכוני קושחה" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "קבלת כל התוספים הפעילים שנרשמו במערכת" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "מציג פרטים אודות קובץ קושחה" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "מושכת את המרוחקים המוגדרים" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "מושך את מאפייני האבטחה של המארח" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "מקבל את רשימת הקושחות שעברו אישור" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "מקבל את רשימת הקושחות החסומות" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "מקבל את רשימת העדכונים לחומרה שמחוברת" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "מקבל את התוצאות מהעדכון האחרון" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "החומרה ממתינה לחיבור מחדש" #. TRANSLATORS: the release urgency msgid "High" msgstr "גבוה" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "מזהה אבטחת מארח:" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "בהמתנה…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "התעלמות מבדיקות מחמירות של SSL בעת הורדת קבצים" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "התעלמות מכשלונות סיכום ביקורת של קושחות" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "התעלמות מכשלי התאמה בין קושחה לחומרה" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "התעלמות מאימות בדיקות בטיחות" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "משך התקנה" msgid "Install old version of signed system firmware" msgstr "התקנת גרסה ישנה של קושחת מערכת חתומה" msgid "Install old version of unsigned system firmware" msgstr "התקנת גרסה ישנה של קושחת מערכת שלא נחתמה" msgid "Install signed device firmware" msgstr "התקנת קושחת התקן חתומה" msgid "Install signed system firmware" msgstr "התקנת קושחת מערכת חתומה" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "יש להתקין להתקן הורה תחילה" msgid "Install unsigned device firmware" msgstr "התקנת קושחת התקן שלא נחתמה" msgid "Install unsigned system firmware" msgstr "התקנת קושחת מערכת שלא נחתמה" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "קושחה מותקנת…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "עדכון הקושחה מותקן…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "מותקן על %s…" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "BootGuard של אינטל" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "מדיניות שגיאות של BootGuard של אינטל" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "CET של אינטל פעיל" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "CET של אינטל מאופשר" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "SMAP של אינטל" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "התקן פנימי" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "שגוי" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "במצב מנהל טעינה" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "תקלה" msgstr[1] "תקלות" msgstr[2] "תקלות" msgstr[3] "תקלות" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "מחזיק מפתחות" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "שינוי אחרון" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "נותרה פחות מדקה" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "רישיון" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "שירות קושחת יצרנים ללינוקס (קושחה יציבה)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "שירות קושחת יצרנים ללינוקס (קושחה ניסיונית)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "הליבה של לינוקס" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "אזור החלפה של לינוקס" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "הצגת רשומות ב־dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "הצגת עדכוני הקושחה הנתמכים" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "הצגת סוגי הקושחות הזמינים" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "מציג קבצים ב־ESP" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "בטעינה…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "נעול" #. TRANSLATORS: the release urgency msgid "Low" msgstr "נמוך" msgid "MEI version" msgstr "גרסת MEI" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "להפעיל ידנית תוספים מסוימים" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "בינוני" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "חתימת נתוני על" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "כתובת נתוני על" #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "גרסה מזערית" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "משנה מרוחק נתון" msgid "Modify a configured remote" msgstr "שינוי מרוחק מוגדר" msgid "Modify daemon configuration" msgstr "שינוי הגדרות סוכן" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "מעקב אחר הסוכן לאיתור אירועים" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "מעגן את ה־ESP" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "דורש הפעלה מחדש לאחר ההתקנה" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "דורש כיבוי לאחר ההתקנה" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "גרסה חדשה" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "לא צוינה פעולה!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "אין שנמוכים עבור %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "לא נמצאו מזהי קושחה" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "לא אותרה חומרה בעלת יכולת עדכון קושחה" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "לא נמצאו תוספים" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "אין מהדורות זמינות" #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "אין מרוחקים זמינים" msgid "No updates available for remaining devices" msgstr "אין עדכונים זמינים להתקנים שנותרו" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "לא הוחלו עדכונים" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "לא נמצא" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "לא נתמך" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "אישור" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "הצגת ערך PCR יחיד" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "מותר עדכוני גרסאות בלבד" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "פלט במבנה JSON" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "מעקף נתיב ה־ESP כברירת מחדל" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "נתיב" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "פענוח והצגת פרטים על קובץ קושחה" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "עדכון ה־dbx מפוענח…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "ה־dbx של המערכת מפוענח…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "סיסמה" msgid "Payload" msgstr "מטען" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "אחוזים שהושלמו" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "לבצע פעולה?" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "נא למלא מספר מ־0 עד %u:" #. TRANSLATORS: the user isn't reading the question -- please don't translate #. * 'Y' or 'N' as these are hardcoded msgid "Please enter either Y or N: " msgstr "נא להקליד Y או N:" #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "תלויות התוסף חסרות" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "גרסה קודמת" msgid "Print the version number" msgstr "הצגת מספר הגרסה" msgid "Print verbose debug statements" msgstr "הצגת מפרט ניפוי תקלות" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "עדיפות" msgid "Proceed with upload?" msgstr "להמשיך בהעלאה?" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "קנייני" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "תשאול תמיכה בעדכון קושחה" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "קריאת מקטע בינרי של קושחה מהתקן" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "קריאת קושחה מהתקן לקובץ" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "קריאת קושחה ממחיצה אחת לקובץ" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "נקרא מתוך %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "מתבצעת קריאה…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "מתבצעת הפעלה מחדש…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "רענון נתוני העל משרת מרוחק" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "להתקין את %s אל %s מחדש?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "התקנת הקושחה הנוכחית על ההתקן מחדש" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "התקנת קושחה על התקן מחדש" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "מתקין מחדש %s עם %s..." #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "ענף הפצה" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "מזהה מרוחק" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "החלפת נתונים בקובץ קושחה קיים" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "כתובת לדיווח" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "דווח לשרת המרוחק" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "הבקשה בוטלה" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "מערכת קבצים efivarfs נחוצה לא נמצאה" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "החומרה הנדרשת לא נמצאה" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "דורש מנהל טעינה" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "דורש חיבור לאינטרנט" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "להפעיל כעת מחדש?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "להפעיל את הסוכן מחדש כדי שהשינויים ייכנסו לתוקף?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "ההתקן מופעל מחדש…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "החזרת כל מזהי החומרה למכונה" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "הליבה הפעילה ישנה מדי" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "סיומת סביבת הרצה" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "נעילת SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "כתיבת SPI" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "מתבצע תזמון…" msgid "Security hardening for HSI" msgstr "הקשחת אבטחת מידע ל־HSI" #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "ניתן לפנות אל %s למידע נוסף." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "התקן נבחר" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "כרך נבחר" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "מספר סידורי" msgid "Set one or more BIOS settings" msgstr "שינוי הגדרה אחת או יותר ב־BIOS" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "הגדרת דגלון ניפוי התקלות במהלך עדכון" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "הגדרת רשימת הקושחות המותרות" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "ההגדרות תוחלנה לאחר הפעלת המערכת מחדש" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "שיתוף היסטוריית הקושחה עם המפתחים" #. TRANSLATORS: command line option msgid "Show all results" msgstr "הצגת כל התוצאות" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "הצגת גרסאות לקוח וסוכן" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "להציג פרטי ניפוי תקלות לכל שמות התחום" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "הצג אפשרויות ניפוי שגיאות" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "להציג התקנים שלא ניתן לעדכן" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "הצגת מידע ניפוי שגיאות מורחב" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "הצגת היסטוריית עדכוני קושחה" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "הצגת הגרסה המחושבת של ה־dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "הצגת תיעוד ניפוי השגיאות מניסיון העדכון האחרון" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "להציג את המידע על מצב עדכון הקושחה" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "לכבות כעת?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "לחתום על חומרה עם מפתח חדש" msgid "Sign data using the client certificate" msgstr "לחתום על הנתונים בעזרת אישור הלקוח" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "לחתום על הנתונים בעזרת אישור הלקוח" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "לחתום על הנתונים שנשלחים עם אישור הלקוח" msgid "Signature" msgstr "חתימה" #. TRANSLATORS: file size of the download msgid "Size" msgstr "גודל" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "מקור" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "ציון מזהה יצרן/מוצר של התקן DFU" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "ציון קובץ מסד הנתוני מסוג dbx" msgid "Specify the number of bytes per USB transfer" msgstr "ציון מספר הבתים להעברה בודדת דרך USB" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "כל ההתקנים הופעלו בהצלחה" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "נתוני על חדשים התקבלו בהצלחה:" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "המרוחק הופעל בהצלחה" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "הקושחה הותקנה בהצלחה" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "ערך התצורה נערך בהצלחה" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "המרוחק נערך בהצלחה" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "נתוני העל התרעננו ידנית בהצלחה" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "תקציר" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "נתמך" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "נתמך בשרת המרוחק" #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "השהיה למצב המתנה" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "השהיה לזיכרון" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "לעבור ענף מ־%s אל %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "החלפת ענף הקושחה בהתקן" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "המערכת דורשת מקור חשמל חיצוני" msgid "Target" msgstr "יעד" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "בדיקת התקן עם מניפסט JSON" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "‏LVFS הוא שירות חינמי שמתפקד כיישות חוקית בלתי תלוי ואין לה שום קשר עם $OS_RELEASE:NAME$. יתכן כי המפיץ שלך לא אימת ברמת תאימות אף אחד מעדכוני הקושחה מול המערכת שלך או ההתקנים המחוברים אליה. כל הקושחה מסופקת אך ורק על ידי יצרני הציוד המקוריים." #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "הקושחה הזאת מבית %s אינה מסופקת על ידי %s, יצרן החומרה" #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "שעון המערכת אינו מכוון והורדת קבצים עלולה להיכשל." #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "אין קובצי קושחה חסומים" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "אין קושחה שעברה אימות." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "החבילה לא עברה אימות, יכול להיות שלא תעבוד כראוי." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "כנראה שתוכנית זו תעבוד כראוי רק עם משתמש על (root)" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "צד מרוחק זה כולל קושחה שלא נפסלה אך עדיין נבדקת על ידי ספק החומרה. עליך לוודא שיש לך דרך ידנית לשנמך את הקושחה במקרה שעדכון הקושחה נכשל." #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "למערכת יש תקלות בסביבת הרצת ה־HSI." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "רמת אבטחת ה־HSI של המערכת נמוכה." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "כלי זה מאפשר להנהלה להחיל עדכוני dbx ב־UEFI." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "כלי זה מאפשר למנהלי המערכת לתשאל ולשלוט בסוכן fwupd, הוא מאפשר להם לבצע פעולות כגון התקנת או שנמוך קושחה." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "כלי זה מאפשר להנהלה להשתמש בתוספי fwupd מבלי שיותקנו על המערכת המארחת." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "בכלי הזה יכול להשתמש רק משתמש העל" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "כלי זה יקרא ויפענח את יומן האירועים של ה־TPM מקושחת המערכת." #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "סוג" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "מחיצת ESP של UEFI לא זוהתה או הוגדרה" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "כלי קושחת UEFI" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "עדכוני כמוסה של UEFI אינם זמינים או מופעלים בתצורת הקושחה" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "כלי dbx ל־UEFI" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "אי אפשר לעדכן קושחת UEFI במצב BIOS מיושן" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "מפתח פלטפורמה ב־UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "טעינה מאובטחת של UEFI" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "לא ניתן להתחבר לשירות" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "ביטול איגוד מנהל ההתקן הנוכחי" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "קושחה לא חוסמת:" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "לא מוצפן" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "לא ידוע" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "התקן לא ידוע" msgid "Unlock the device to allow access" msgstr "יש לשחרר את ההתקן כדי לאפשר גישה" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "משוחרר" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "משחרר את חסימת ההתקן לגישת קושחה" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "מנתק את ה־ESP" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "ביטול הגדרת דגלון ניפוי התקלות במהלך עדכון" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "גרסת הסוכן %s אינה נתמכת, גרסת הלקוח היא %s" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "ניתן לעדכון" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "שגיאת עדכון" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "הודעת עדכון" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "מצב עדכון" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "כשל בעדכון זאת תקלה מוכרת, יש לבקר בכתובת למידע נוסף:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "לעדכן עכשיו?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "העדכון דורש הפעלה מחדש" msgid "Update the stored device verification information" msgstr "עדכון פרטי אימות ההתקן המאוחסנים" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "עדכון נתוני העל המאוחסנים בתכנים נוכחיים" msgid "Updating" msgstr "מתבצע עדכון" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "מעדכן %s מ־%s ל־%s..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "%s מתעדכן…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "לשדרג את %s מגרסה %s אל %s?" #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "דחיפות" #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "יש להשתמש ב־fwupdtool --help לקבלת עזרה" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "נשלחה הודעה למשתמש" #. TRANSLATORS: remote filename base msgid "Username" msgstr "שם משתמש" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "תקף" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "תכני ה־ESP עוברים אימות…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "הגוון" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "ספק" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "מתבצע אימות…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "גרסה" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "אזהרה" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "בהמתנה…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "מעקב אחר שינויים בחומרה" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "כתיבת קושחה מקובץ להתקן" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "כתיבת קושחה מקובץ למחיצה אחת" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "קובץ נכתב:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "מתבצעת כתיבה…" #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "יכול להיות שהחומרה שלך תיפגע עקב השימוש בקושחה הזאת והתקנת המהדורה הזאת עשויה לפגוע באחריות עם %s." #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "בררת מחדל" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "כלי לתיעוד אירועי TPM של fwupd" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "תוספים של fwupd" fwupd-1.9.16/po/hi.po000066400000000000000000000075051460375044200143270ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Prashant Gupta , 2015 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Hindi (http://www.transifex.com/freedesktop/fwupd/language/hi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: hi\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "%s का उपनाम " #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "फर्मवेयर अपडेट के लिए प्रमाणीकरण चाहिए " #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "डिबगिंग के विकल्प " #. success msgid "Done!" msgstr "हो गया !" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "%s की %s से %s तक अधोगति हो रही है " #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "थोड़ी देरी के बाद बहार जाएँ " #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "इंजन के लोड हो जाने पर बहार जाएँ " #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "आर्गुमेंट पार्स करने में असफल " #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "फर्मवेयर अपडेट डी-बस सेवा " #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "फर्मवेयर अपडेट का समर्थन करने वाली सभी युक्तियाँ प्राप्त करें " #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "फर्मवेयर फाइल की अधिक जानकारी प्राप्त करें " #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "फर्मवेयर फाइल को इस हार्डवेयर पर स्थापित करें " #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "अपडेट की क्षमता वाला हार्डवेयर उपलब्ध नहीं " #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "%s को %s से दोबारा स्थापित करा जा रहा है " #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "डिबगिंग के विकल्प दिखाए " #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "डिबगिंग की अतिरिक्त जानकारी दिखाएँ " #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "%s को %s से %s तक अपडेट करा जा रहा है " fwupd-1.9.16/po/hr.po000066400000000000000000003315401460375044200143370ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # FIRST AUTHOR , 2016 # gogo , 2016 # Milo Ivir , 2020-2021 # Milo Ivir , 2023 # Milo Ivir , 2021 # gogo , 2016-2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Croatian (http://app.transifex.com/freedesktop/fwupd/language/hr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: hr\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minuta preostala" msgstr[1] "%.0f minute preostale" msgstr[2] "%.0f minuta preostalo" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "%s BMC nadopuna" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "%s nadopuna baterije" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "%s nadopuna CPU mikrokôda" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "%s nadopuna kamere" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "%s nadopuna podešavanja" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s nadopuna potrošačkog pogona upravljanja (ME)" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s nadopuna upravljača" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s nadopuna korporativnog pogona upravljanja (ME)" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s nadopuna uređaja" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "%s nadopuna zaslona" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "%s nadopuna doka" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "%s nadopuna diska" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s nadopuna ugrađenog upravljača" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "%s nadopuna čitača otisaka prsta" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "%s nadopuna flash memorije" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "%s GPU nadopuna" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "%s nadopuna grafičkog tableta" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "%s nadopuna tipkovnice" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s nadopuna pogona upravljanja (ME)" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "%s nadopuna miša" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "%s nadopuna mrežnog sučelja" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "%s nadopuna SSD diska" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "%s nadopuna kontrolera pohrane" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s nadopuna sustava" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "%s TPM nadopuna" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s nadopuna Thunderbolt kontrolera" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "%s touchpad nadopuna" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "%s nadopuna USB doka" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "%s nadopuna USB prijemnika" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s nadopuna" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s i svi spojeni uređaji možda neće biti upotrebljivi tijekom nadopune." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "%s pojavljen: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "%s promijenjen: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "%s nestao: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s trenutno nije nadopunjiv" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s način rada za proizvođače" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s mora ostati spojen tijekom trajanja nadopune kako bi se izbjeglo oštećenje." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s mora ostati spojen s izvorom energije tijekom trajanja nadopune kako bi se izbjeglo oštećenje." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s zaobilaženje" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s inačica" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dan" msgstr[1] "%u dana" msgstr[2] "%u dana" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u uređaj ima dostupnu nadopunu firmvera." msgstr[1] "%u uređaja imaju dostupnu nadopunu firmvera." msgstr[2] "%u uređaja ima dostupnu nadopunu firmvera." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u uređaj nema najbolje poznato podešavanje." msgstr[1] "%u uređaja nema najbolje poznato podešavanje." msgstr[2] "%u uređaja nema najbolje poznato podešavanje." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u sat" msgstr[1] "%u sata" msgstr[2] "%u sati" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u lokalni uređaj je podržan" msgstr[1] "%u lokalna uređaja je podržano" msgstr[2] "%u lokalnih uređaja je podržano" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuta" msgstr[1] "%u minute" msgstr[2] "%u minuta" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u sekunda" msgstr[1] "%u sekunde" msgstr[2] "%u sekundi" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (najmanja inačica %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(zastarjelo)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "TPM PCR je sada nevaljana vrijednost" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "AMD firmver zaštita ponavljanja" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "AMD firmver zaštita zpisivanja" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "Zaštita od vraćanja na stariju inačicu AMD sigurnog procesora" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Radnja je potrebna:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Aktiviraj uređaj" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Aktiviraj uređaje na čekanju" msgid "Activate the new firmware on the device" msgstr "Aktiviraj novi firmver na uređaju" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Aktivacija nadopune firmvera" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Aktivacija nadopune firmvera za" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Dob" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Slažete li se s omogućavanjem udaljene lokacije?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Zamjena za %s" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "Svi TPM PCR-ovi su sada valjani" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Svi TPM PCR-ovi su valjani" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Svi uređaji iste vrste će biti nadopunjeni u isto vrijeme" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Dopusti vraćanje starije inačice firmvera" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Dopusti ponovnu instalaciju firmvera postojeće inačice" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Dopusti prebacivanje između firmver grana" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Alternativni ogranak" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "Sprovodi se aktualiziranje" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Nadopuna zahtijeva ponovno pokretanje za završetak." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Nadopuna zahtijeva potpuno isključivanje sustava." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Odgovori 'da' na sva pitanja" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Primijeni nadopune firmvera" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Primijeni nadopunu iako nije preporučljivo" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Primijeni nadopunu datoteka" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Primjena nadopuna…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Odobreni firmver:" msgstr[1] "Odobreni firmveri:" msgstr[2] "Odobreni firmveri:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Upitaj ponovno sljedeći put?" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Prebaci u način firmvera" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Ovjeravanje…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Potrebne su pojedinosti ovjere" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Potrebna je ovjera za vraćanje starije inačice firmvera na uklonjivom uređaju" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Potrebna je ovjera za vraćanje starije inačicu firmvera na ovom računalu" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Potrebna je ovjera za promjenu BIOS postavki" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Potrebna je ovjera za promjenu udaljene lokacije koja se koristi za nadopunu firmvera" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Potrebna je ovjera za promjenu podešavanja pozadinskog programa" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "Potrebna je ovjera za čitanje BIOS postavki" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Potrebna je ovjera za postavljanje popisa odobrenih firmvera" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Potrebna je ovjera za potpisivanje podataka vjerodajnicom klijenta" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Potrebna je ovjera za prebacivanje na novu inačicu firmvera" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Potrebna je ovjera za otključavanje uređaja" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Potrebna je ovjera za nadopunu firmvera na uklonjivom uređaju" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Potrebna je ovjera za nadopunu firmvera na ovom računalu" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Potrebna je ovjera za nadopunu spremljenog kontrolnog zbroja uređaja" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Automatsko izvještavanje" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Automatski pošalji svaki put?" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "Zaštita od vraćanja na stariju inačicu BIOS-a" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "Zaštita od vraćanja na stariju inačicu BIOS-a" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "BIOS nadopune isporučene putem LVFS-a ili Windows ažuriranja" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "IZGRADITELJ-XML NAZIV DATOTEKE-DST" msgid "BYTES" msgstr "BAJTA" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Baterija" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Poveži novi kernel upravljački program" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Datoteke blokiranog firmvera:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Blokirana inačica" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Blokiranje firmvera:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Blokira instalaciju određenog firmvera" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Inačica učitača pokretanja" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Ogranak" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Izgradi datoteku firmvera" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Odustani" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Prekinuto" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Nemoguća primjena kao dbx nadopune jer je već primijenjeno." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Nemoguća primjena na live medij" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Promijenjeno" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Provjeri podudarnost kriptografske jedinstvene vrijednosti firmvera" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Kontrolni zbroj" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Odaberi granu" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Odaberi uređaj" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Odaberi firmver" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Odaberi izdanje" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Odaberi ESP:" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Uklanja rezultate posljednje nadopune" #. TRANSLATORS: error message msgid "Command not found" msgstr "Naredba nije pronađena" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Podržano od strane zajednice" #. TRANSLATORS: title prefix for the BIOS settings dialog msgid "Configuration Change Suggested" msgstr "Predložena promjena podešavanja" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "Podešavanje može čitati samo administrator sustava" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Pretvori datoteku firmvera" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Stvoreno" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Kritična" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Provjera kriptografske jedinstvene vrijednosti je dostupna" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Trenutna vrijednost" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Trenutna inačica" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "UREĐAJ-ID|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU pomagalo" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Mogućnosti otklanjanja greške" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Raspakiravanje…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Opis" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Prebaci u način učitača pokretanja" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Pojedinosti" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "Odustani od najbolje poznatog podešavanja?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Oznaka uređaja" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "ID uređaja" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Uređaj dodan:" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "Energija baterije uređaja je preniska" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "energija baterije uređaja je preniska (%u%%, zahtijeva %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Uređaj se može oporaviti od neuspjelog zapisivanja frimvera" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "Uređaj se ne može koristiti dok je poklopac zaslona zatvoren" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Uređaj promijenjen:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "Emulacija uređaja nije aktivirana." #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Potreban je firmver uređaja za provjeru inačice" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "Uređaj je emuliran" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Uređaj je zaključan" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Uređaj je potreban za instalaciju svih dostupnih izdanja" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Uređaj je nedostupan" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "Uređaj je nedostupan ili je izvan dometa bežične mreže" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Uređaj se može koristiti tijekom trajanja nadopune" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "Uređaj čeka na primjenu nadopune" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Uređaj uklonjen:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "Uređaj zahtijeva vanjski izvor energije" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "Uređaj zahtijeva softversku licencu za nadopunu" #. TRANSLATORS: longer description msgid "Device software updates are provided for this device." msgstr "Nadopune softvera uređaja su isporučene za ovaj uređaj." #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Nadopune uređaja u fazama" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "Uređaj podržava prebacivanje na drugačiji ogranak firmvera" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Način nadopune frimvera" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Nadopuni uređaja je potrebna aktivacija" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Uređaj će sigurnosno kopirati firmver prije instalacije" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Uređaj se neće ponovno pojaviti nakon završetka nadopune" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Uređaji koji su uspješno nadopunjeni:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Uređaji koji nisu ispravno nadopunjeni:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Uređaji bez dostupnih nadopuna firmvera: " #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Uređaji s najnovijom dostupnom inačicom firmvera:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "Nije pronađen niti jedan uređaj s podudarajućim GUID-ovima" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Onemogućeno" msgid "Disabled fwupdate debugging" msgstr "Onemogući fwupdate otklanjanje grešaka" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Onemogućuje zadane udaljene lokacije" #. TRANSLATORS: command line option msgid "Display version" msgstr "Prikaži inačicu" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Distribucija" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Ne provjeravaj stare metapodatke" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Ne provjeravaj neprijavljenu povijest" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Ne provjeravaj trebaju li preuzete udaljene lokacije biti omogućene" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Ne provjeravaj ili upitaj za ponovno pokretanje nakon nadopune" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Ne uključuj prefiks domene zapisa" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Ne uključuj prefiks vremena" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Nemoj izvoditi provjere sigurnosti uređaja" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "Ne upitaj za uređaje" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "Ne pretražuj firmver pri obradi" #. TRANSLATORS: warning message shown after update has been scheduled msgid "Do not turn off your computer or remove the AC adaptor while the update is in progress." msgstr "Ne isključujte svoje računalo ili AC adapter tijekom procesa nadopune." #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Ne zapisuj bazu podataka povijesti" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Razumijete li posljedice promjene ogranka firmvera?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Želite li onemogućiti ovu značajku za buduće nadopune?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "Želiš li je sada aktivirati?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Želite li odmah provjeriti ovu udaljenu lokaciju?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Želite li poslati izvještaje automatski za buduće nadopune?" #. TRANSLATORS: command line option msgid "Don't prompt for authentication (less details may be shown)" msgstr "Ne pitaj za ovjeru (može biti prikazano manje pojedinosti)" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Završeno!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Vrati %s na stariju inačicu sa %s na %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Vraća stariju inačicu firmvera na uređaju" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Vraćanje %s s inačice %s na inačicu %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Vraćanje %s na stariju inačicu…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Preuzmi datoteku" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Preuzimanje…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Ispiši opširnije podatke SMBIOS-a iz datoteke" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Trajanje" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Određeni ESP nije ispravan" #. TRANSLATORS: longer description msgid "Each system should have tests to ensure firmware security." msgstr "Svaki uređaj bi se trebao testirati firmver, kako bi se osigurala sigurnost firmvera." #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "Emuliraj uređaj koristeći JSON manifest" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Emulirano" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Emulirani poslužitelj" msgid "Enable" msgstr "Omogući" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Omogući podršku nadopune firmvera na podržanim sustavima" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Omogući udaljenu lokaciju?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Omogući ovu udaljenu lokaciju?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Omogućeno" msgid "Enabled fwupdate debugging" msgstr "Omogući fwupdate otklanjanje grešaka" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Omogućeno ako se hardver podudara" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Omogućuje zadane udaljene lokacije" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Omogućujete ovu funkcionalnost na vlastiti rizik, što znači da morate kontaktirati svog izvornog proizvođača opreme u vezi problema uzrokovanih tim nadopunama. Samo probleme sa samim postupkom nadopune treba prijaviti na $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Ovu udaljenu lokaciju omogućavate na vlastiti rizik." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Šifrirano" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "Šifrirani RAM" msgid "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." msgstr "Šifrirani RAM čini nemogućim čitanje informacija spremljenih u memoriji uređaja ako je memorijski čip uklonjen i može mu se pristupiti." #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Kraj podrške" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Nabrajanje" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Obriši svu povijest nadopune firmvera" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Brisanje…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Izađi nakon kratke odgode" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Izađi nakon učitavanja pogona" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Izvezi strukturu datoteke frimvera u XML" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Izdvoji blob firmvera u slike" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "DATOTEKA" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "DATOTEKA [UREĐAJ-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "NAZIV DATOTEKE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "PRIVATNI-KLJUČ NAZIV DATOTEKE VJERODAJNICE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "NAZIV DATOTEKE UREĐAJA-ALT-NAZIV|UREĐAJ-ALT-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "NAZIV DATOTEKE UREĐAJA-ALT-NAZIV|UREĐAJ-ALT-ID [SLIKA-ALT-NAZIV|SLIKA-ALT-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "NAZIV DATOTEKE ID-UREĐAJA" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "NAZIV DATOTEKE POMAKA PODATAKA [FIRMVER-VRSTA]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "NAZIV DATOTEKA [UREĐAJ-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "NAZIV DATOTEKE [FIRMVER-VRSTA]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "NAZIV DATOTEKE-SRC NAZIV DATOTEKE-DST [FIRMVER-VRSTA-SRC] [FIRMVER-VRSTA-DST]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "NAZIV DATOTEKE|KONTROLNI ZBROJ1[,KONTROLNI ZBROJ2][,KONTROLNI ZBROJ3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Neuspjelo" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Neuspjela primjena nadopune" #. TRANSLATORS: error message for Windows msgid "Failed to connect to Windows service, please ensure it's running." msgstr "Neuspjelo povezivanje s Windows uslugom, provjerite je li pokrenuta." #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Neuspjelo povezivanje s pozadinskim programom" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Neuspjeli prikaz uređaja na čekanju" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Neuspjela instalacija nadopune firmvera" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Neuspjelo učitavanje lokalnog dbx-a" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Neuspjelo učitavanje okolnosti uređaja" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Neuspjelo učitavanje dbx-a sustava" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Neuspjelo zaključavanje" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Neuspjela obrada argumenata" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Neuspjela obrada datoteke" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Neuspjela obrada oznake za --filter" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Neuspjela obrada lokalnog dbx-a" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Neuspjelo ponovno pokretanje" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Neuspjelo postavljanje splash načina" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Neuspjela provjera ESP sadržaja" #. TRANSLATORS: item is FALSE msgid "False" msgstr "Laž" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Naziv datoteke" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Potpis naziva datoteke" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Izvor naziva datoteke" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Potreban je naziv datoteke" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filtar sa skupom oznaka uređaja koji koristi ~ prefiks za izuzimanje, npr. 'internal,~needs-reboot'" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Frimver provjera" #. TRANSLATORS: longer description msgid "Firmware Attestation checks device software using a reference copy, to make sure that it has not been changed." msgstr "Frimver provjera provjerava softver uređaja koristeći izvornu kopiju, kako bi se osiguralo da nije mijenjan." #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Frimver BIOS opisnik" #. TRANSLATORS: longer description msgid "Firmware BIOS Descriptor protects device firmware memory from being tampered with." msgstr "Frimver BIOS opisnik štiti memoriju frimvera uređaja od neovlaštenog mijenjanja." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Firmver BIOS područje" #. TRANSLATORS: longer description msgid "Firmware BIOS Region protects device firmware memory from being tampered with." msgstr "Firmver BIOS područje štiti memoriju firmvera uređaja od nevlaštenog mijenjanja." #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Osnovni URI firmvera" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Firmver nadopuna D-Bus usluge" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Pozadinski program nadopune firmvera" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Provjera firmver nadopunitelja" msgid "Firmware Updater Verification checks that software used for updating has not been tampered with." msgstr "Provjera firmver nadopunitelja provjerava je li softver za nadopunu neovlašteno mijenjan." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Nadopune frimvera" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Firmver pomagalo" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Firmver zaštita zapisivanja" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Frimver zaključana zaštita zapisivanja" #. TRANSLATORS: longer description msgid "Firmware Write Protection protects device firmware memory from being tampered with." msgstr "Firmver zaštita zapisivanja štiti memoriju firmvera uređaja od nevlaštenog mijenjanja." #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Frimver provjera" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Firmver je već blokiran" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Firmver još nije blokiran" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Metapodaci firmvera nisu nadopunjeni %u dan i možda nisu najnoviji." msgstr[1] "Metapodaci firmvera nisu nadopunjeni %u dana i možda nisu najnoviji." msgstr[2] "Metapodaci firmvera nisu nadopunjeni %u dana i možda nisu najnoviji." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Nadopune frimvera" msgid "Firmware updates are not supported on this machine." msgstr "Nadopuna firmvera nije podržana na ovom računalu." msgid "Firmware updates are supported on this machine." msgstr "Nadopuna firmvera je podržana na ovom računalu." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Nadopune firmvera su onemogućene ; pokrenite 'fwupdmgr unlock' za omogućavanje" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Oznake" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Prisili radnju zanemarujući neke provjere vremena pokretanja" msgid "Force the action ignoring all warnings" msgstr "Prisili radnju zanemarujući sva upozorenja" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Pronađeno" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Potpuno šifriranje diska je otkriveno" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "Tajne potpunog šifriranja diska mogu biti poništene pri nadopuni" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "Pregorjela platforma" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Pregorjela platforma" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID" msgstr[2] "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" msgid "Get BIOS settings" msgstr "Prikaži BIOS postavke" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Prikaži sve oznake uređaja koje podržava fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Prikaži sve uređaje koji podržavaju nadopunu firmvera" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Prikaži sve omogućene priključke registrirane sa sustavom" #. TRANSLATORS: command description msgid "Get device report metadata" msgstr "Preuzmi metapodatke izvještaja uređaja" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Prikaži pojedinosti datoteke firmvera" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Prikazuje udaljena podešavanja" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Prikaži sigurnosne značajke poslužitelja" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Preuzima popis odobrenih firmvera" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Preuzima popis blokiranih firmvera" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Prikaži popis nadopuna za povezani hardver" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Prikazuje izdanja za uređaj" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Prikaži rezultate posljednje nadopune" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWIDS-DATOTEKA" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Hardver čeka da ponovno bude spojen" #. TRANSLATORS: the release urgency msgid "High" msgstr "Visoka" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Događaji sigurnosti računala" #. TRANSLATORS: error message for unsupported feature #, c-format msgid "Host Security ID (HSI) is not supported" msgstr "ID sigurnosti sustava (HSI) nije podržan" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Sigurnosni ID poslužitelja:" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "IOMMU zaštita" #. TRANSLATORS: longer description msgid "IOMMU Protection prevents connected devices from accessing unauthorized parts of system memory." msgstr "IOMMU zaštita sprječava pristup povezanih urađaja neovlaštenim dijelovima memorije." #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "Onemogućena zaštita IOMMU uređaja" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "Omogućena zaštita IOMMU uređaja" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Mirovanje…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Zanemari SSL ograničene provjere pri preuzimanju datoteka" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Zanemari neuspjele kontrolne zbrojeve firmvera" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Zanemari neuspjela poklapanja hardvera firmvera" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Zanemari provjeru sigurnosti uređaja" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Zanemaruju se stroge SSL provjere, kako bi se ovo ubuduće automatski izvezlo DISABLE_SSL_STRICT u svojem okruženju" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Trajanje instalacije" #. TRANSLATORS: command description msgid "Install a specific firmware on a device, all possible devices will also be installed once the CAB matches" msgstr "Instaliraj određeni firmver na uređaj, svi mogući uređaji će biti instalirani jednom kada se CAB podudara" msgid "Install old version of signed system firmware" msgstr "Instaliraj staru inačicu potpisanog frimvera sustava" msgid "Install old version of unsigned system firmware" msgstr "Instaliraj staru inačicu nepotpisanog frimvera sustava" msgid "Install signed device firmware" msgstr "Instaliraj firmver potpisan uređajem" msgid "Install signed system firmware" msgstr "Instaliraj firmver potpisan sustavom" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Instaliraj prvo u nadređeni uređaj" msgid "Install unsigned device firmware" msgstr "Instaliraj firmver nepotpisan uređajem" msgid "Install unsigned system firmware" msgstr "Instaliraj firmver nepotpisan sustavom" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Instalacija firmvera…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Instalacija nadopune firmvera…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Instaliram na %s…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "Instaliranje ove nadopune može poništiti svako jamstvo uređaja." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Cijeli broj" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "Intel BootGuard ACM zaštićen" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Intel BootGuard ACM zaštićen" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Intel BootGuard pravilo greške" msgid "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." msgstr "Intel BootGuard pravilo greške osigurava da se uređaj ne nastavlja pokretati ako je njegov softver uređaja neovlašteno mijenjan." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Intel BootGuard OTP osigurač" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Intel BootGuard OTP osigurač" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Intel BootGuard provjereno pokretanje" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Intel BootGuard pravilo greške" #. TRANSLATORS: longer description msgid "Intel BootGuard prevents unauthorized device software from operating when the device is started." msgstr "Intel BootGuard sprječava pristup neovlaštenog softvera uređaja iz operativnog sustava kada se uređaj pokrene." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Intel BootGuard provjereno pokretanje" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET aktivan" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET omogućen" #. TRANSLATORS: longer description msgid "Intel Control-Flow Enforcement Technology detects and prevents certain methods for running malicious software on the device." msgstr "Intel Control-Flow Enforcement Technology otkriva i sprječava određene načine pokretanja zlonamjernog softvera na uređaju." #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Način rada Intel pogona upravljanja" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Zaobilaženje Intel pogona upravljanja" #. TRANSLATORS: longer description msgid "Intel Management Engine Override disables checks for device software tampering." msgstr "Zaobilaženje Intel pogona upravljanja (Intel Management Engine) onemogućava provjere za neovlaštni softver uređaja." #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Inačica Intel pogona upravljanja" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: longer description msgid "Intel Supervisor Mode Access Prevention ensures critical parts of device memory are not accessed by less secure programs." msgstr "Intel Supervisor Mode Access Prevention osigurava da manje sigurni programi ne pristupaju ključnim dijelovima memorije uređaja." #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Unutrašnji uređaj" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Nevaljano" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Nevaljani argumenti" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Je nadogradnja na stariju inačicu" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "U načinu učitača pokretanja je" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Je nadogradnja" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Problem" msgstr[1] "Problemi" msgstr[2] "Problemi" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "KLJUČ,VRIJEDNOST" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "Kernel više nije kontaminiran" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "Kernel je kontaminiran" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Zaključavanje kernela onemogućeno" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Zaključavanje kernela omogućeno" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Skup ključeva" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "LOKACIJA" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Posljednja promjena" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Preostalo je manje od minute" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licenca" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Linux kernel zaključavanje" #. TRANSLATORS: longer description msgid "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgstr "Linux Kernel zaključavanje sprječava administratorske (korijenske) račune da pristupe i mijenjaju ključne dijelove softvera sustava." msgid "Linux Kernel Swap temporarily saves information to disk as you work. If the information is not protected, it could be accessed by someone if they obtained the disk." msgstr "Linux Kernel Swap privremeno sprema informacije na disk dok radite. Ako informacije nisu zaštićene, netko im može pristupiti ako je u posjedu diska." #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Linux Kernel provjera" msgid "Linux Kernel Verification makes sure that critical system software has not been tampered with. Using device drivers which are not provided with the system can prevent this security feature from working correctly." msgstr "Linux Kernel provjera osigurava da ključni softver sustava nije mijenjan. Korištenje upravljačkih progrma uređaja koji se ne isporučuju sustavom može sprječiti ispravan rad ove značajke." #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Linux swap" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Firmver Usluga Linux Proizvođača (LVFS) (stabilni firmver)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Firmver Usluga Linux Proizvođača (LVFS) (testni firmver)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Linux kernel" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Linux kernel zaključavanje" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linux swap" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Prikaži unose u dbx-u" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Prikaži nadopune podržanih firmvera" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Prikaži dostupne vrste firmvera" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Prikazuje datoteke na ESP-u" #. TRANSLATORS: command description msgid "Load device emulation data" msgstr "Učitaj emulirane podatke uređaja" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "Učitano iz vanjskog modula" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Učitavanje…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Zaključano" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Niska" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "Manifest MEI ključa" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "Manifest MEI ključa" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "MEI način rada za proizvođače" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "MEI zaobilaženje" msgid "MEI version" msgstr "MEI inačica" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Ručno omogući određene priključke" #. TRANSLATORS: longer description msgid "Manufacturing Mode is used when the device is manufactured and security features are not yet enabled." msgstr "Način rada za proizvođače se koristi kada je uređaj proizveden i sigurnosne značajke još nisu omogućene." #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Najveća duljina" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Najviša vrijednost" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Srednja" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Metapodaci potpisa" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI metapodataka" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metapodaci se mogu dobiti od Firmver Usluge Linux Proizvođača (LVFS)." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Najmanja inačica" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Najmanja duljina" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Najmanja vrijednost" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Pozadinski program i klijent se ne podudaraju, umjesto koristite %s" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Mijenja vrijednost podešavanja pozadinskog programa" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Promjena zadane udaljene lokacije" msgid "Modify a configured remote" msgstr "Promijeni zadanu udaljenu lokaciju" msgid "Modify daemon configuration" msgstr "Prilagodi podešavanje pozadinskog programa" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Nadgledaj događaje pozadinskim programom" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Montira ESP" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Potrebno je ponovno pokretanje nakon instalacije" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Zahtijeva ponovno pokretanje" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Potrebno je isključivanje nakon instalacije" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Nova inačica" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Nema zadane radnje!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Nema starijih inačica za %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Nema pronađenog ID firmvera" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Nema otkrivenog hardvera s mogućnosti nadopune firmvera" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nema pronađenih priključaka" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Nema dostupnih izdanja" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Trenutno nema omogućenih udaljenih lokacija stoga nema dostupnih metapodataka." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Nema dostupnih udaljenih lokacija" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Nema nadopunjivih uređaja" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Nema dostupnih nadopuna" msgid "No updates available for remaining devices" msgstr "Nema dostupnih nadopuna za preostale uređaje" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nema primijenjenih nadopuna" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Nije odobreno" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Nije pronađeno" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Nije podržano" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "U redu" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "U redu!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Stara inačica" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Prikaži samo jednu PRC vrijednost" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Samo nadogradnje inačice su dopuštene" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Izlaz u JSON formatu" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Zaobiđi zadanu ESP putanju" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "PUTANJA" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Obradi i prikaži pojedinosti datoteke firmvera" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Obrada dbx nadopune…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Obrada dbx-a sustava…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Lozinka" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Zakrpaj datoteku firmvera na poznati pomak" msgid "Payload" msgstr "Sadržaj prijenosa" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Na čekanju" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Postotak završetka" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Obavi radnju?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Otklanjanje grešaka platforme" #. TRANSLATORS: longer description msgid "Platform Debugging allows device security features to be disabled. This should only be used by hardware manufacturers." msgstr "Otklanjanje grešaka platforme dopušta onemogućavanje sigurnosnih značajki uređaja. Ovo bi trebali koristit samo proizvođači hardvera." #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Otklanjanje grešaka platforme" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Pobrinite se da imate ključ oporavka uređaja prije nastavka." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Odaberite broj od 0 do %u: " #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Zavisnosti priključka nedostaju" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Moguće vrijednosti" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "DMA zaštita predpokretanja" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "DMA zaštita predpokretanja" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "DMA zaštita prije pokretanja je onemogućena" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "DMA zaštita prije pokretanja je omogućena" #. TRANSLATORS: longer description msgid "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." msgstr "DMA zaštita predpokretanja sprječava pristup uređaja memoriji sustava nakon što se povežu s računalom. " #. TRANSLATORS: warning message msgid "Press unlock on the device to continue the update process." msgstr "Pritisnite otključavanje na uređaju za nastavak procesa nadopune." #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Prijašnja inačica" msgid "Print the version number" msgstr "Prikaži broj inačice" msgid "Print verbose debug statements" msgstr "Zapisuj opširniji izvještaj otklanjanja grešaka" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritet" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Problemi" msgid "Proceed with upload?" msgstr "Nastavi sa slanjem?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Sigurnosna provjera procesora" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Zaštita od vraćanja na stariju inačicu procesora" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Vlasnički" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Zatraži podršku nadopune firmvera" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "UDALJENI-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "VRIJEDNOST KLJUČA ID-UDALJENE_LOKACIJE" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Dozvola čitanja" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Očitaj blob firmvera s uređaja" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Očitaj firmver s uređaja" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Očitaj firmver iz uređaja u datoteku" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Očitaj firmver iz jedne particije u datoteku" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Čitanje iz %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Čitanje…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Ponovno pokretanje…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Osvježi metapodatke s udaljenog poslužitelja" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Ponovno instaliraj %s na %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Ponovno instaliraj trenutni firmver na uređaj" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Ponovno instaliraj firmver na uređaj" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Ponovna instalacija %s inačice %s... " #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Ogranak izdanja" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Oznake izdanja" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "ID izdanja" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Udaljeni ID" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Zamijeni podatke u postojećoj datoteci firmvera" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI izvještaja" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Prijavljeno na udaljenom poslužitelju" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Zahtjev je prekinut" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "Potrebni efivarfs datotečni sustav nije pronađen" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "Potreban hardver nije pronađen" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Zahtijeva učitača pokretanja" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Potreban je pristup internetu" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Ponovno pokreni odmah?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Ponovno pokreni pozadinski program kako bi se promjene primijenile?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Ponovno pokretanje uređaja…" #. TRANSLATORS: command description msgid "Retrieve BIOS settings. If no arguments are passed all settings are returned" msgstr "Preuzmi BIOS postavke. Ako se argumenati ne proslijede, postavke su nepromijenjene" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Vrati sve ID-ove hardvera za uređaj" #. TRANSLATORS: longer description msgid "Rollback Protection prevents device software from being downgraded to an older version that has security problems." msgstr "Zaštita od vraćanja na stariju inačicu sprječava softver uređaja da ga vrati na stariju inačicu koja ima sigurnosne probleme." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Pokrenite `fwupdmgr get-upgrades` za više informacija." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Pokreni rutinu čišćenja sastavljanja priključka kada se koristi install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Pokreni rutinu pripreme sastavljanja priključka kada se koristi install-blob" #. TRANSLATORS: tell a user how to get information #, c-format msgid "Run without '%s' to see" msgstr "Pokrenite bez '%s' za prikaz" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "Pokrenuti kernel je prestar" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Sufiks vremenskog izvršavanja" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "VRIJEDNOST POSTAVKE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING1 VALUE1 [SETTING2] [VALUE2]" msgstr "POSTAVKA1 VRIJEDNOST1 [POSTAVKA2] [VRIJEDNOST2]" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "SPI BIOS opisnik" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "SPI BIOS područje" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI zaključavanje" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "Zaštita od SPI ponavljanja" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI zapisivanje" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "Zaštita od SPI zapisivanja" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "UPRAVLJAČKI PROGRAM PODSUSTAVA [UREĐAJ-ID|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Spremi datoteku koja omogućuje stvaranje ID-ova hardvera" #. TRANSLATORS: command description msgid "Save device emulation data" msgstr "Spremi emulirane podatke uređaja" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "Skalarni umnožak" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Zakaži instalaciju pri sljedećem pokretanju kada je moguće" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Zakazivanje…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Sigurno pokretanje (SecureBoot) onemogućeno" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Sigurno pokretanje (SecureBoot) omogućeno" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Pogledajte %s za više informacija." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Pogledajte %s za više informacija." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Odabrani uređaj" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Odabrani uređaj" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Serijski broj" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "Postavi BIOS postavku '%s' koristeći '%s'." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "Postavi BIOS postavku" msgid "Set one or more BIOS settings" msgstr "Postavi jednu ili više BIOS postavki" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Postavi oznaku otklanjanja grešaka tijekom nadopune" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "Postavlja jednu ili više BIOS postavki" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Postavlja popis odobrenih firmvera" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Vrsta postavke" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Postavke će se primijeniti nakon ponovnog pokretanja sustava" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Podijeli povijest firmvera sa razvijateljima" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Prikaži sve rezultate" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Prikaži inačicu klijenta i pozadinskog programa" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Prikaži dodatne informacije pozadinskog programa za određenu domenu" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Prikaži informacije otklanjanja greške za sve domene" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Prikaži mogućnosti otklanjanja greške" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Prikaži uređaje koji se ne mogu nadopuniti" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Prikaži dodatne informacije otklanjanja grešaka" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Prikaži povijest nadopune metapodataka" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Prikaži predviđenu inčicu DBX-a" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Prikaži zapis otklanjanja grešaka posljednjeg pokušaja nadopune" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Prikaži informacije stanja nadopune firmvera" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Odmah isključi?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Potpiši frimver s novim ključem" msgid "Sign data using the client certificate" msgstr "Potpiši podatke koristeći vjerodajnicu klijenta" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Potpiši podatke koristeći vjerodajnicu klijenta" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Potpiši poslane podatke s vjerodajnicom klijenta" msgid "Signature" msgstr "Potpis" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Potpisani sadržaj prijenosa" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Veličina" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Određene tajne platforme mogu biti poništene tijekom nadopune ovog firmvera." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Izvor" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Odredi ID-ove Proizvođača/Proizvoda DFU uređaja" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Odredi datoteku dbx baze podataka" msgid "Specify the number of bytes per USB transfer" msgstr "Odredi broj bajtova po USB prijenosu" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "Izraz" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Uspješno" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Svi uređaji su uspješno aktivirani" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Udaljena lokacija je uspješno onemogućena" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Novi metapodaci su uspješno preuzeti: " #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Udaljena lokacija je uspješno omogućena i provjerena" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Udaljena lokacija je uspješno omogućena" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmver je uspješno instaliran" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Vrijednost podešavanja je uspješno promijenjenja" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Udaljena lokacija je uspješno promijenjena" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metapodaci su ručno uspješno osvježni" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Kontrolni zbroj uređaja je uspješno nadopunjen" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Uspješno poslan %u izvještaj" msgstr[1] "Uspješno poslana %u izvještaja" msgstr[2] "Uspješno poslano %u izvještaja" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Kontrolni zbroj uređaja je uspješno provjeren" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Sažetak" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Podržano" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "Podržan je CPU" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Podržano na udaljenom poslužitelju" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Suspendiraj u mirovanje" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Suspendiraj u RAM" #. TRANSLATORS: longer description msgid "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Suspendiraj u mirovanje omogućuje uređaju brz odlazak na spavanje u svrhu štednje energije. Dok je uređaj suspendiran, njegova se memorija može fizički ukloniti i na taj način pristupiti informacijama." #. TRANSLATORS: longer description msgid "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Suspendiraj u RAM omogućuje uređaju brz odlazak na spavanje u svrhu štednje energije. Dok je uređaj suspendiran, njegova se memorija može fizički ukloniti i na taj način pristupiti informacijama." #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Suspendiraj-u-mirovanje" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Suspendiraj-u-ram" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Prebaci ogranak sa %s na %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Zamijeni ogranak firmvera na uređaju" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "Energija sustava je preniska za obavljanje nadopune" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "energija sustava je preniska za obavljanje nadopune (%u%%, zahtijeva %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "Sustav zahtijeva vanjski izvor energije" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEKST" #. TRANSLATORS: longer description msgid "TPM (Trusted Platform Module) is a computer chip that detects when hardware components have been tampered with." msgstr "TPM (Pouzdani Modul Platforme) je računalni čip koji otkriva neovlaštene promjene hardverskih komponenta." #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "TPM PCR0 rekonstrukcija" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "TPM PCR0 rekonstrukcija je nevaljana" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "TPM PCR0 rekonstrukcija je sada valjana" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "Podešavanje TPM platforme" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "TPM rekonstrukcija" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "TPM prazani PCR-ovi" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Oznaka" msgstr[1] "Oznake" msgstr[2] "Oznake" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "Označeno za emulaciju" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Pokvareno" msgid "Target" msgstr "Odredište" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Testiraj uređaj koristeći JSON manifest" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Testirano" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "Testirao %s" #. TRANSLATORS: longer description msgid "The Intel Management Engine Key Manifest must be valid so that the device firmware can be trusted by the CPU." msgstr "Manifest ključa Intel pogon upravljanja (Intel Management Engine) mora biti valjan tako da firmver uređaja može vjerovati CPU." #. TRANSLATORS: longer description msgid "The Intel Management Engine controls device components and needs to have a recent version to avoid security issues." msgstr "Intel pogon upravljanja (Intel Management Engine) upravlja komponentama uređaja i mora biti najnovije inačice kako bi se izbjegli sigurnosni problemi." #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS (Firmver Usluga Linux Proizvođača) je besplatna usluga koja djeluje kao neovisna pravna osoba i nema veze sa $OS_RELEASE:NAME$. Vaš distributer možda nije provjerio nadopune firmvera za kompatibilnost s vašim sustavom ili priključenim uređajima. Svi firmveri su pružani od strane izvornih proizvođača opreme." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Platform Configuration is used to check whether the device start process has been modified." msgstr "Podešavanje TPM (Pouzdani Modul Platforme) platforme se koristi za provjeru je li proces pokretanja uređaja mijenjan." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Reconstruction is used to check whether the device start process has been modified." msgstr "TPM (Pouzdani Modul Platforme) rekonstrukcija se koristi za provjeru je li proces pokretanja uređaja mijenjan." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "TPM PCR0 razlikuje se od rekonstrukcije." #. TRANSLATORS: longer description msgid "The UEFI Platform Key is used to determine if device software comes from a trusted source." msgstr "UEFI ključ platforme se koristi za otkrivanje dolazi li softver uređaja iz pouzdanih izvora." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "Pozadinski program je učitao kȏd treće strane i više nije podržan od upstream razvijatelja!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "Inačica uređaja se ne podudara: dobivena %s, očekivana %s" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "%s firmver nije isporučen od strane %s dobavljača hardvera." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "Sat sustava nije pravilno postavljen i preuzimanje datoteka možda ne uspije." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been re-inserted." msgstr "Nadopuna će se nastaviti kada se kabel USB uređaja ponovno poveže." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged and then re-inserted." msgstr "Nadopuna će se nastaviti kada se kabel USB uređaja odspoji i ponvno priključi." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged." msgstr "Nadopuna će se nastaviti kada se kabel USB uređaja odspoji." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "Proizvođač nije objavio nikakve bilješke izdanja." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Postoje uređaji s problemima:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Nema blokiranih firmvera" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Ne postoji odobreni firmver." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "Ovaj uređaj će biti vraćen natrag na %s kada se %s naredba pokrene." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "Ovaj firmver je omogućen od strane LVFS članova zajednice i nije omogućen (ili podržan) od strane izvornog proizvođača hardvera." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Ovaj paket još nije provjeren, možda neće ispravno raditi." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Ovaj program možda radi ispravno samo ako je pokrenut kao korijenski korisnik" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Ova udaljena lokacija sadrži firmver koji nije zabranjen, ali se još uvijek testira od strane proizvođača hardvera. Provjerite da imate način na ručno vraćanje starije inačice firmvera ako nadopuna firmvera ne uspije." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "Ovaj sustav ne podržava postavke firmvera" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Ovaj sustav ima HSI probleme s vremenskim izvršavanjem." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Ovaj sustav ima nisku HSI sigurnosnu razinu." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Ovaj alat omogućuje administratoru primjenu UEFI dbx nadopuna." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Ovaj alat dopušta administratoru otklanjanje grešaka u UpdateCapsule radnji." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Ovaj alat dopušta administratoru postavljanje upita i upravljanje fwupd pozadinskim programom, što omogućuje obavljanje radnji poput instalacije i vraćanje starije inačice frimvera." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Ovaj alat dopušta administratoru korištenje fwupd priključaka bez instalacije na sustavu." #. TRANSLATORS: the %1 is a BIOS setting name. #. * %2 and %3 are the values, e.g. "True" or "Windows10" #, c-format msgid "This tool can change the BIOS setting '%s' from '%s' to '%s' automatically, but it will only be active after restarting the computer." msgstr "Ovaj alat može promijeniti BIOS postavku '%s' iz '%s' u '%s' automatski, ali će biti aktivno nakon ponovnog pokretanja računala." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Ovaj alat može koristiti samo korijenski korisnik" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Ovo pomagalo će očitati i obraditi TPM zapis događaja iz frimvera sustava." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Privremeni kvar" #. TRANSLATORS: item is TRUE msgid "True" msgstr "Istina" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Pouzdani metapodaci" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Pouzdan sadržaj prijenosa" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Vrsta" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "UEFI ESP particija nije otkrivena ili podešena" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI firmver pomagalo" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "UEFI ključ platforme" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "UEFI sigurno pokretanje (SecureBoot)" #. TRANSLATORS: longer description msgid "UEFI Secure Boot prevents malicious software from being loaded when the device starts." msgstr "UEFI sigurno pokretanje (SecureBoot) sprječava učitavanje zlonamjernog softvera pri pokretanju uređaja." #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "Nadopune UEFI kapsule nisu dostupne ili omogućene u frimver postavljanju" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "UEFI dbx pomagalo" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "UEFI firmver ne može se nadopuniti u zastarjelom BIOS načinu" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "UEFI ključ platforme" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "UEFI sigurno pokretanje (SecureBoot)" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Neuspjelo povezivanje s udaljenom uslugom" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "Nemoguć pronalazak svojstva" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Prekini povezivanje s trenutnim upravljačkim programom" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Neblokiranje firmvera:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Deblokira instalaciju određenog firmvera" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Nije šifrirano" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Nepoznat" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Nepoznat uređaj" msgid "Unlock the device to allow access" msgstr "Otključaj uređaj za dopuštenje pristupa" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Otključano" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Otključava uređaj za pristup firmveru" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Demontira ESP" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Ukloni oznaku otklanjanja grešaka tijekom nadopune" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Nepotpisani sadržaj prijenosa" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Nepodržana inačica pozadinskog programa %s, inačica klijenta je %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Ispravno" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Nadopunjivo" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Greška nadopune" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Slika nadopune" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Poruka nadopune" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Stanje nadopune" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Neuspješna nadopuna je poznat problem, posjetite ovaj URL za više informacija:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Nadopuni odmah?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Nadopuna zahtijeva ponovno pokretanje" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Nadopuni spremljenu kriptografsku jedinstvenu vrijednost s trenutnim sadržajem ROM-a" msgid "Update the stored device verification information" msgstr "Nadopuni spremljenu informaciju provjere uređaja" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Nadopuni pohranjene metapodatke s trenutnim sadržajem" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "Nadopuni sve navedene uređaje na najnovije inačice firmvera, ili sve uređaje ako nisu navedeni" msgid "Updating" msgstr "Nadopunjivanje" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Nadopuna %s s inačice %s na inačicu %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Nadopunjujem %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Nadogradi %s sa %s na %s?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Slanje izvještaja firmvera pomaže proizvođačima hardvera brzo otkrivanje nedostatka i brzu nadopunu na stvarnim uređajima." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Hitnost" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Koristi %s za pomoć" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Koristi CTRL^C za prekidanje." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Koristite fwupdtool --help za prikaz pomoći" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Koristi oznake okolnosti računala tijekom instalacije firmvera" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Korisnik je obaviješten" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Korisničko ime" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Valjano" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Provjera ESP sadržaja…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Varijanta" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Proizvođač" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Provjeravanje…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Inačica" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Inačica[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "UPOZORENJE" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Čekanje…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Nadgledaj promjene hardvera" #. TRANSLATORS: check various UEFI and ACPI tables are unchanged after the #. update msgid "Will measure elements of system integrity around an update" msgstr "Mjerit će elemente integriteta sustava oko nadopune" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Zapiši firmver iz datoteke u uređaj" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Zapiši firmver iz datoteke u jednu particiju uređaja" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Zapisivanje datoteke:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Zapisivanje…" #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from the system firmware setup, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Trebali bi osigurati da možete vratiti postavke iz podešavanja firmvera sustava, pošto ova promjena može uzrokovati nemogućnost pokretanja u Linux sustav ili uzrokovati druge nestabilnosti sustava." #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Vaš distributer možda nije provjerio nadopune firmvera za kompatibilnost s vašim sustavom ili priključenim uređajima." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Vaš hardver se može oštetiti upotrebom ovog firmvera, instalacija ovog izdanja može poništiti jamstvo sa %s." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "Vaš sustav je postavljen na BKC od %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[KONTROLNI ZBROJ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[UREĐAJ-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[UREĐAJ-ID|GUID] [OGRANAK]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[ID|GUID-UREĐAJA] [INAČICA]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[DATOTEKA POTPIS_DATOTEKE ID-UDALJENE_LOKACIJE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[NAZIV DATOTEKE1] [NAZIV DATOTEKE2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[RAZLOG]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[POSTAVKA1] [POSTAVKAG2]..." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[POSTAVKA1] [POSTAVKA2] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SMBIOS-DATOTEKA|HWIDS-DATOTEKA]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "zadano" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "fwupd TPM pomagalo zapisa događaja" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd priključci" fwupd-1.9.16/po/hu.po000066400000000000000000003664041460375044200143510ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Balázs Meskó , 2017-2019 # Balázs Meskó , 2017-2019 # Balázs Úr , 2015-2018,2023 # Balázs Úr, 2015-2018 # Gabor Kelemen , 2016 # Gábor Kelemen , 2016 # kelemeng , 2016 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Hungarian (http://app.transifex.com/freedesktop/fwupd/language/hu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: hu\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f perc van hátra" msgstr[1] "%.0f perc van hátra" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "%s alaplapmenedzsmentvezérlő-frissítés" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "%s akkumulátorfrissítés" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "%s CPU-mikrokódfrissítés" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "%s kamerafrissítés" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "%s konfigurációs frissítés" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s fogyasztói menedzsmentmotor-frissítés" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s vezérlőfrissítés" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s vállalati menedzsmentmotor-frissítés" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s eszközfrissítés" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "%s kijelzőfrissítés" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "%s dokkolófrissítés" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "%s meghajtófrissítés" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s beágyazottvezérlő-frissítés" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "%s ujjlenyomatolvasó-frissítés" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "%s flashmeghajtó-frissítés" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "%s GPU-frissítés" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "%s rajztáblafrissítés" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "%s billentyűzetfrissítés" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s menedzsmentmotor-frissítés" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "%s egérfrissítés" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "%s hálózaticsatoló-frissítés" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "%s SSD-frissítés" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "%s tárolóvezérlő-frissítés" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s rendszerfrissítés" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "%s TPM-frissítés" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s Thunderbolt-vezérlőfrissítés" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "%s érintőtábla-frissítés" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "%s USB-dokkolófrissítés" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "%s USB-vevőfrissítés" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s frissítés" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "A(z) %s és az összes kapcsolódó eszköz használhatatlan lehet a frissítés alatt." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "A(z) %s megjelent: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "A(z) %s megváltozott: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "A(z) %s eltűnt: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "A(z) %s jelenleg nem frissíthető" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s gyártói mód" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "A(z) %s eszköznek kapcsolódva kell maradnia a frissítés alatt a károsodások elkerüléséhez." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "A(z) %s eszköznek kapcsolódva kell maradnia az áramforráshoz a frissítés alatt a károsodások elkerüléséhez." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s felülbírálás" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s verzió" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u nap" msgstr[1] "%u nap" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u eszköz rendelkezik elérhető belsővezérlőprogram-frissítéssel." msgstr[1] "%u eszköz rendelkezik elérhető belsővezérlőprogram-frissítéssel." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u eszköz nem a legismertebb konfiguráció." msgstr[1] "%u eszköz nem a legismertebb konfiguráció." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u óra" msgstr[1] "%u óra" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u helyi eszköz támogatott" msgstr[1] "%u helyi eszköz támogatott" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u perc" msgstr[1] "%u perc" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u másodperc" msgstr[1] "%u másodperc" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (küszöbszint: %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(elavult)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "Egy TPM PCR most érvénytelen érték" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "AMD belső vezérlőprogram visszajátszási védelem" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "AMD belső vezérlőprogram írásvédelme" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "AMD biztonságos processzor visszaállítási védelem" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "ARCHIVE FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATFILE]" msgstr "ARCHÍVUM BELSŐ-VEZÉRLŐPROGRAM METAINFORMÁCIÓK [BELSŐ-VEZÉRLŐPROGRAM] [METAINFORMÁCIÓK] [JCAT-FÁJL]" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Művelet szükséges:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Eszközök aktiválása" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Függőben lévő eszközök aktiválása" msgid "Activate the new firmware on the device" msgstr "Az új belső vezérlőprogram aktiválása az eszközön" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Belsővezérlőprogram-frissítés aktiválása" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Belsővezérlőprogram-frissítés aktiválása ennél:" #. TRANSLATORS: command description msgid "Adds devices to watch for future emulation" msgstr "Hozzáadja a jövőbeni emulációhoz megfigyelendő eszközöket" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Életkor" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Beleegyezik és engedélyezi a távoli tárolót?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Álnév ehhez: %s" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "Most már az összes TPM PCR érvényes" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Az összes TPM PCR érvényes" #. TRANSLATORS: an application is preventing system updates msgid "All devices are prevented from update by system inhibit" msgstr "Az összes eszköz frissítését megakadályozza a rendszer gátlása" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Az összes azonos típusú eszköz egyszerre lesz frissítve" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Régebbi belsővezérlőprogram-verziók telepítésének lehetővé tétele" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "A meglévő belsővezérlőprogram-verziók újratelepítésének lehetővé tétele" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Belsővezérlőprogram-ág váltásának lehetővé tétele" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Alternatív ág" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "Egy frissítés folyamatban van" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Egy frissítés újraindítást igényel a befejezéshez." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Egy frissítés a rendszer leállítását igényli a befejezéshez." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Igen válaszolása az összes kérdésre" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Belsővezérlőprogram-frissítések alkalmazása" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Frissítés alkalmazása akkor is, ha nem ajánlott" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Frissítési fájlok alkalmazása" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Frissítés alkalmazása…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Jóváhagyott belső vezérlőprogram:" msgstr[1] "Jóváhagyott belső vezérlőprogramok:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Kérdezze újra legközelebb?" #. TRANSLATORS: command description msgid "Asks the daemon to quit" msgstr "Arra kéri a démont, hogy lépjen ki" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Csatlakoztatás a belső vezérlőprogram módhoz" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Hitelesítés…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Hitelesítési részletek szükségesek" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Hitelesítés szükséges a belső vezérlőprogram régebbi verziójának egy cserélhető eszközön történő telepítéséhez" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Hitelesítés szükséges a belső vezérlőprogram régebbi verziójának ezen a gépen történő telepítéséhez" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Hitelesítés szükséges a BIOS-beállítások módosításához" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Hitelesítés szükséges a belső vezérlőprogram frissítéseihez beállított távoli tároló módosításához" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Hitelesítés szükséges a démon konfigurációjának módosításához" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "Hitelesítés szükséges a BIOS-beállítások olvasásához" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Hitelesítés szükséges a jóváhagyott belső vezérlőprogramok listájának beállításához" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Hitelesítés szükséges az adatok ügyféltanúsítvány használatával történő aláírásához" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Hitelesítés szükséges a belső vezérlőprogram új verziójára váltáshoz" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Hitelesítés szükséges az eszköz feloldásához" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Hitelesítés szükséges a belső vezérlőprogram egy cserélhető eszközön történő frissítéséhez" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Hitelesítés szükséges a belső vezérlőprogram ezen a gépen történő frissítéséhez" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Hitelesítés szükséges az eszköz tárolt ellenőrzőösszegeinek frissítéséhez" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Automatikus jelentés" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Automatikusan feltöltse minden alkalommal?" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "BIOS visszaállítási védelem" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "BIOS visszaállítási védelem" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "LVFS-en vagy Windows Update-en keresztül szállított BIOS-frissítések" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "ÖSSZEÁLLÍTÓ-XML FÁJLNÉV-CÉL" msgid "BYTES" msgstr "BÁJTOK" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Akkumulátor" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Új rendszermag-illesztőprogram kötése" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Tiltott belsővezérlőprogram-fájlok:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Tiltott verzió" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Belső vezérlőprogram tiltása:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Megtiltja egy bizonyos belső vezérlőprogram telepítését" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Rendszerbetöltő verziója" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Ág" #. TRANSLATORS: command description msgid "Build a cabinet archive from a firmware blob and XML metadata" msgstr "Szekrényarchívum összeállítása belsővezérlőprogram-bloból és XML-metaadatokból" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Belsővezérlőprogram-fájl összeállítása" #. TRANSLATORS: longer description msgid "CPU Microcode must be updated to mitigate against various information-disclosure security issues." msgstr "A CPU-mikrokódot frissíteni kell a különböző információfeltárási biztonsági problémák kárenyhítéséhez." #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Mégse" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Megszakítva" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Nem lehet alkalmazni, mivel a dbx-frissítés már alkalmazva lett." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Nem lehet alkalmazni a frissítéseket élő médián" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Megváltoztatva" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Ellenőrzi, hogy a kriptográfiai kivonat egyezik-e a belső vezérlőprogrammal" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Ellenőrzőösszeg" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Ág kiválasztása" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Eszköz kiválasztása" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Belső vezérlőprogram kiválasztása" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Kiadás kiválasztása" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Az ESP kiválasztása:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Kötet kiválasztása" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Törli a legutóbbi frissítésből származó eredményeket" #. TRANSLATORS: error message msgid "Command not found" msgstr "A parancs nem található" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Közösség által támogatott" #. TRANSLATORS: title prefix for the BIOS settings dialog msgid "Configuration Change Suggested" msgstr "Konfigurációváltoztatás javasolva" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "A beállítások csak a rendszergazda számára olvashatók" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Belsővezérlőprogram-fájl átalakítása" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Létrehozva" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Kritikus" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Kriptográfiaikivonat-ellenőrzés érhető el" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Jelenlegi érték" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Jelenlegi verzió" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "ESZKÖZAZONOSÍTÓ|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU-segédprogram" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Hibakeresési kapcsolók" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Kibontás…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Leírás" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Leválasztás a rendszerbetöltő módhoz" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Részletek" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "Eltér a legismertebb konfigurációtól?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Eszközjelzők" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Eszközazonosító" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Eszköz hozzáadva:" #. TRANSLATORS: the device is already connected msgid "Device already exists" msgstr "Az eszköz már létezik" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "Az eszköz akkumulátorszintje túl alacsony" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "Az eszköz akkumulátorszintje túl alacsony (%u%%, de %u%% szükséges)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Az eszköz képes helyreállítani a beírási hibákat" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "Az eszköz nem használható, amíg a fedél le van csukva" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Eszköz megváltoztatva:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "Az eszközemuláció nincs engedélyezve." #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Az eszköz belső vezérlőprogramja szükséges a verzió-ellenőrzéshez" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "Az eszköz emulált" #. TRANSLATORS: device cannot be interrupted, for instance taking a phone call msgid "Device is in use" msgstr "Az eszköz használatban van" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Az eszköz zárolva van" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Eszköz szükséges az összes szolgáltatott kiadás telepítéséhez" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Az eszköz elérhetetlen" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "Az eszköz elérhetetlen vagy a vezeték nélküli hatótávolságon kívül van" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Az eszköz használható a frissítés ideje alatt" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "Az eszköz a frissítés alkalmazására vár" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Eszköz eltávolítva:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "Az eszközhöz elektromos áram csatlakoztatása szükséges" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "Az eszköz szoftverlicencet igényel a frissítéshez" #. TRANSLATORS: longer description msgid "Device software updates are provided for this device." msgstr "Eszközszoftver-frissítések vannak szolgáltatva ehhez az eszközhöz." #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Az eszköz szakaszosan frissít" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "Az eszköz támogatja a belső vezérlőprogram különböző ágainak váltását" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Az eszköz frissítési módszere" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Az eszközfrissítés aktiválást igényel" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Az eszköz biztonsági mentést fog készíteni a belső vezérlőprogramról a telepítés előtt" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Az eszköz nem fog újra megjelenni a frissítés befejezése után" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Eszközök, amelyek sikeresen frissítve lettek:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Eszközök, amelyek nem lettek helyesen frissítve:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Nem elérhető belsővezérlőprogram-frissítésekkel rendelkező eszközök: " #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "A legújabb elérhető belsővezérlőprogram-verzióval rendelkező eszközök:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "Nem találhatók illeszkedő csoportazonosítókkal rendelkező eszközök" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Letiltva" msgid "Disabled fwupdate debugging" msgstr "Az fwupdate hibakeresése letiltva" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Letiltja az adott távoli tárolót" #. TRANSLATORS: command line option msgid "Display version" msgstr "Verzió megjelenítése" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Disztribúció" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Ne ellenőrizze a régi metaadatokat" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Ne ellenőrizze a nem jelentett előzményeket" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Ne ellenőrizze, ha a távoli tárolók letöltését engedélyezni kell" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Ne ellenőrizzen vagy kérjen újraindítást a frissítés után" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Ne tartalmazza a naplózási tartomány előtagot" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Ne tartalmazza az időbélyeg előtagot" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Ne végezzen eszközbiztonsági ellenőrzéseket" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "Ne kérjen az eszközöknél" #. TRANSLATORS: command line option msgid "Do not prompt to fix security issues" msgstr "Ne kérdezzen rá a biztonsági hibák javítására" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "Ne keresse a belső vezérlőprogramot a feldolgozáskor" #. TRANSLATORS: warning message shown after update has been scheduled msgid "Do not turn off your computer or remove the AC adaptor while the update is in progress." msgstr "Ne kapcsolja ki a számítógépet vagy ne távolítsa el a hálózati csatlakozót a frissítési folyamat közben." #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Ne írjon az előzmények adatbázisába" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Megértette a belső vezérlőprogram ágának megváltoztatásával járó következményeket?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Szeretné letiltani ezt a funkciót a jövőbeni frissítéseknél?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "Szeretné most engedélyezni?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Szeretné most frissíteni ezt a távoli tárolót?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Szeretné automatikusan feltölteni a jelentéseket a jövőbeni frissítéseknél?" #. TRANSLATORS: command line option msgid "Don't prompt for authentication (less details may be shown)" msgstr "Ne kérdezzen rá a hitelesítésre (kevesebb részlet jelenhet meg)" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Kész!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Telepíti a(z) %s eszközön a régebbi verziót: %s → %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "A belső vezérlőprogram régebbi verzióját telepíti egy eszközön" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "%s régebbi verziójának telepítése %s verzióról %s verzióra… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "%s régebbi verziójának telepítése…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Fájl letöltése" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Letöltés…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "SMBIOS adatok kiírása egy fájlból" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Időtartam" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "A megadott ESP nem volt érvényes" #. TRANSLATORS: longer description msgid "Each system should have tests to ensure firmware security." msgstr "Minden rendszernek rendelkeznie kell tesztekkel a belső vezérlőprogram biztonságának biztosításához." #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "Eszköz emulálása JSON-nyilvántartás használatával" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Emulált" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Emulált kiszolgáló" msgid "Enable" msgstr "Engedélyezés" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Belső vezérlőprogram frissítési támogatás engedélyezése a támogatott rendszereken" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Engedélyezi az új távoli tárolót?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Engedélyezi ezt a távoli tárolót?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Engedélyezve" msgid "Enabled fwupdate debugging" msgstr "Az fwupdate hibakeresése engedélyezve" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Engedélyezve, ha a hardver egyezik" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Engedélyezi az adott távoli tárolót" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Csak a saját felelősségére engedélyezze ezt a funkciót, ami azt jelenti, hogy az eredeti termék gyártójával kell kapcsolatba lépnie, ha problémát okoz a frissítés. Csak a frissítési folyamattal kapcsolatos problémákat jelentse itt be: $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Ennek a távoli tárolónak az engedélyezése saját felelősségére történik." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Titkosított" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "Titkosított memória" msgid "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." msgstr "A titkosított memória lehetetlenné teszi, hogy az eszköz memóriájában tárolt információk olvashatók legyenek, ha a memória-áramkört eltávolítják és hozzáférnek." #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Életciklus vége" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Felsorolás" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Az összes belsővezérlőprogram-frissítési előzmény törlése" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Törlés…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Kilépés egy kis késleltetés után" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Kilépés a motor betöltődése után" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Belsővezérlőprogram-fájl szerkezetének exportálása XML-be" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Belsővezérlőprogram-blob kibontása lemezképekbe" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "FÁJL" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "FÁJL [ESZKÖZAZONOSÍTÓ|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "FÁJLNÉV" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "FÁJLNÉV TANÚSÍTVÁNY SZEMÉLYES-KULCS" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "FÁJLNÉV ESZKÖZ-ALTERNATÍV-NÉV|ESZKÖZ-ALTERNATÍV-AZONOSÍTÓ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "FÁJLNÉV ESZKÖZ-ALTERNATÍV-NÉV|ESZKÖZ-ALTERNATÍV-AZONOSÍTÓ [LEMEZKÉP-ALTERNATÍV-NÉV|LEMEZKÉP-ALTERNATÍV-AZONOSÍTÓ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "FÁJLNÉV ESZKÖZAZONOSÍTÓ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "FÁJLNÉV ELTOLÁS ADATOK [BELSŐVEZÉRLŐPROGRAM-TÍPUS]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "FÁJLNÉV [ESZKÖZAZONOSÍTÓ|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "FÁJLNÉV [BELSŐVEZÉRLŐPROGRAM-TÍPUS]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "FÁJLNÉV-FORRÁS FÁJLNÉV-CÉL [BELSŐVEZÉRLŐPROGRAM-TÍPUS-FORRÁS] [BELSŐVEZÉRLŐPROGRAM-TÍPUS-CÉL]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "FÁJLNÉV|ELLENŐRZŐÖSSZEG1[,ELLENŐRZŐÖSSZEG2][,ELLENŐRZŐÖSSZEG3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Sikertelen" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Nem sikerült a frissítés alkalmazása" #. TRANSLATORS: error message for Windows msgid "Failed to connect to Windows service, please ensure it's running." msgstr "Nem sikerült kapcsolódni a Windows szolgáltatáshoz, győződjön meg arról, hogy fut-e." #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Nem sikerült a démonhoz kapcsolódni" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Nem sikerült a függőben lévő eszközök lekérése" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Nem sikerült a belső vezérlőprogram frissítésének telepítése" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Nem sikerült a helyi dbx betöltése" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Nem sikerült a kerülőmegoldások betöltése" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Nem sikerült a rendszer dbx betöltése" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Nem sikerült a zárolás" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Nem sikerült az argumentumok feldolgozása" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Nem sikerült a fájl feldolgozása" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Nem sikerült a --filter jelzőinek feldolgozása" msgid "Failed to parse flags for --filter-release" msgstr "Nem sikerült a --filter-release jelzőinek feldolgozása" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Nem sikerült a helyi dbx feldolgozása" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Nem sikerült az újraindítás" #. TRANSLATORS: a feature is something like "can show an image" msgid "Failed to set front-end features" msgstr "Nem sikerült beállítani az előtétprogram funkcióit" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Nem sikerült beállítani az indítóképernyő módot" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Nem sikerült az ESP-tartalom ellenőrzése" #. TRANSLATORS: item is FALSE msgid "False" msgstr "Hamis" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Fájlnév" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Fájlnév aláírása" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Fájlnév forrása" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Fájlnév szükséges" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Szűrés eszközjelzők megadásával, használja a ~ előtagot a kihagyáshoz, például „internal,~needs-reboot”" #. TRANSLATORS: command line option msgid "Filter with a set of release flags using a ~ prefix to exclude, e.g. 'trusted-release,~trusted-metadata'" msgstr "Szűrés kiadásjelzők megadásával, használja a ~ előtagot a kihagyáshoz, például „trusted-release,~trusted-metadata”" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Belső vezérlőprogram tanúsítás" #. TRANSLATORS: longer description msgid "Firmware Attestation checks device software using a reference copy, to make sure that it has not been changed." msgstr "A belső vezérlőprogram tanúsítása egy hivatkozási másolat használatával ellenőrzi eszközszoftvert annak biztosításához, hogy azt nem változtatták meg." #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Belső vezérlőprogram BIOS-leírója" #. TRANSLATORS: longer description msgid "Firmware BIOS Descriptor protects device firmware memory from being tampered with." msgstr "A belső vezérlőprogram BIOS-leírója megvédi az eszköz belső vezérlőprogramjának memóriáját a manipulációtól." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Belső vezérlőprogram BIOS-ának területe" #. TRANSLATORS: longer description msgid "Firmware BIOS Region protects device firmware memory from being tampered with." msgstr "A belső vezérlőprogram BIOS-területe megvédi az eszköz belső vezérlőprogramjának memóriáját a manipulációtól." #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Belső vezérlőprogram alap URI-ja" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Belsővezérlőprogram-frissítési D-busz-szolgáltatás" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Belsővezérlőprogram-frissítési démon" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Belső vezérlőprogram frissítőjének ellenőrzése" msgid "Firmware Updater Verification checks that software used for updating has not been tampered with." msgstr "A belső vezérlőprogram frissítőjének ellenőrzése azt ellenőrzi, hogy a frissítéshez használt szoftvert nem manipulálták-e." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Belső vezérlőprogram frissítések" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Belső vezérlőprogram segédprogram" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Belső vezérlőprogram írásvédelme" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Belső vezérlőprogram írásvédelmének zárolása" #. TRANSLATORS: longer description msgid "Firmware Write Protection protects device firmware memory from being tampered with." msgstr "A belső vezérlőprogram írásvédelme megvédi az eszköz belső vezérlőprogramjának memóriáját a manipulációtól." #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Belső vezérlőprogram tanúsítás" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "A belső vezérlőprogram már tiltva van" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "A belső vezérlőprogram még nincs tiltva" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "A belső vezérlőprogram metaadatai %u napja nem lettek frissítve, és elavultak lehetnek." msgstr[1] "A belső vezérlőprogram metaadatai %u napja nem lettek frissítve, és elavultak lehetnek." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Belső vezérlőprogram frissítések" msgid "Firmware updates are not supported on this machine." msgstr "A belsővezérlőprogram-frissítések nem támogatottak ezen a gépen." msgid "Firmware updates are supported on this machine." msgstr "A belsővezérlőprogram-frissítések támogatottak ezen a gépen." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "A belsővezérlőprogram-frissítések le vannak tiltva, futtassa az „fwupdmgr unlock” parancsot az engedélyezéséhez" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Jelzők" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "A művelet kényszerítése néhány futásidejű ellenőrzés lazításával" msgid "Force the action ignoring all warnings" msgstr "A művelet kényszerítése, az összes figyelmeztetés mellőzése" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Megtalált" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Teljes lemeztitkosítás észlelhető" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "A teljes lemeztitkosítás titkai érvénytelenné válhatnak a frissítésekor" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "Biztosított platform" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Biztosított platform" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID-k" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "GUID|DEVICE-ID" msgstr "GUID|ESZKÖZAZONOSÍTÓ" msgid "Get BIOS settings" msgstr "BIOS-beállítások lekérése" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Az fwupd által támogatott összes eszközjelző lekérése" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Az összes eszköz lekérése, amelyek támogatják a belsővezérlőprogram-frissítéseket" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "A rendszeren regisztrált összes engedélyezett bővítmény lekérése" #. TRANSLATORS: command description msgid "Get device report metadata" msgstr "Eszközjelentés metaadatainak lekérése" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Részleteket kér le egy belső vezérlőprogram fájljáról" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Lekéri a beállított távoli tárolókat" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Lekéri a kiszolgáló biztonsági attribútumait" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Lekéri a jóváhagyott belső vezérlőprogramok listáját" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Lekéri a tiltott belső vezérlőprogramok listáját" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Lekéri a frissítések listáját a csatlakoztatott hardverhez" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Lekéri az eszközhöz tartozó kiadásokat" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Lekéri a legutóbbi frissítésből származó eredményeket" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWIDS-FÁJL" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "A hardver újracsatlakoztatásra vár" #. TRANSLATORS: the release urgency msgid "High" msgstr "Magas" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "A gép biztonsági eseményei" #. TRANSLATORS: error message for unsupported feature #, c-format msgid "Host Security ID (HSI) is not supported" msgstr "A kiszolgálóbiztonsági azonosító (HSI) nem támogatott" #. TRANSLATORS: success, so say thank you to the user msgid "Host Security ID attributes uploaded successfully, thanks!" msgstr "A kiszolgálóbiztonsági azonosító attribútumai sikeresen feltöltve, köszönjük!" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Kiszolgálóbiztonsági azonosító:" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "INHIBIT-ID" msgstr "GÁTLÓ-AZONOSÍTÓ" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "IOMMU védelem" #. TRANSLATORS: longer description msgid "IOMMU Protection prevents connected devices from accessing unauthorized parts of system memory." msgstr "Az IOMMU védelem megakadályozza a csatlakoztatott eszközöket abban, hogy hozzáférjenek a rendszermemória jogosulatlan részeihez." #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "Az IOMMU eszközvédelem letiltva" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "Az IOMMU eszközvédelem engedélyezve" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Üresjárat…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "A szigorú SSL-ellenőrzések mellőzése a fájlok letöltésekor" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Belső vezérlőprogram ellenőrzőösszeg-hibáinak figyelmen kívül hagyása" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Belső vezérlőprogram hardvereltérési hibáinak figyelmen kívül hagyása" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Biztonsági ellenőrzések mellőzése" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Szigorú SSL-ellenőrzések mellőzése, ennek a jövőbeni automatikus használatához exportálja az DISABLE_SSL_STRICT változót a környezetébe" #. TRANSLATORS: the inhibit ID is a short string like dbus-123456 #, c-format msgid "Inhibit ID is %s." msgstr "A gátló azonosító: %s." #. TRANSLATORS: command description msgid "Inhibit the system to prevent upgrades" msgstr "A rendszer gátlása a frissítések megakadályozásához" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Telepítés időtartama" #. TRANSLATORS: command description msgid "Install a firmware file in cabinet format on this hardware" msgstr "Egy belsővezérlőprogram-fájl telepítése szekrényformátumban ezen az eszközön" #. TRANSLATORS: command description msgid "Install a raw firmware blob on a device" msgstr "Egy nyers belsővezérlőprogram-blob telepítése egy eszközön" #. TRANSLATORS: command description msgid "Install a specific firmware file on all devices that match" msgstr "Egy bizonyos belsővezérlőprogram-fájl telepítése az összes egyező eszközön" #. TRANSLATORS: command description msgid "Install a specific firmware on a device, all possible devices will also be installed once the CAB matches" msgstr "Egy bizonyos belső vezérlőprogram telepítése egy eszközön, az összes lehetséges eszköz is telepítve lesz, amikor a CAB egyezik" msgid "Install old version of signed system firmware" msgstr "Aláírt rendszer belső vezérprogram régi verziójának telepítése" msgid "Install old version of unsigned system firmware" msgstr "Nem aláírt rendszer belső vezérlőprogram régi verziójának telepítése" msgid "Install signed device firmware" msgstr "Aláírt eszköz belső vezérlőprogram telepítése" msgid "Install signed system firmware" msgstr "Aláírt rendszer belső vezérlőprogram telepítése" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Telepítés először a szülő eszközre" msgid "Install unsigned device firmware" msgstr "Nem aláírt eszköz belső vezérlőprogram telepítése" msgid "Install unsigned system firmware" msgstr "Nem aláírt rendszer belső vezérlőprogram telepítése" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Belső vezérlőprogram telepítése…" #. TRANSLATORS: stay on one firmware version unless the new version is #. explicitly #. * specified msgid "Installing a specific release is explicitly required" msgstr "Egy bizonyos kiadás telepítése kifejezetten szükséges" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Belső vezérlőprogram frissítésének telepítése…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Telepítés a(z) %s eszközön…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "A frissítés telepítése az eszköz garanciáját is érvénytelenítheti." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Egész szám" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "Intel BootGuard ACM védett" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Intel BootGuard ACM védett" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Intel BootGuard hibairányelv" msgid "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." msgstr "Az Intel BootGuard hibairányelv azt biztosítja, hogy az eszköz nem folytatja az indulást, ha az eszközszoftverét manipulálták." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Intel BootGuard biztosíték" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Intel BootGuard egyszer programozható biztosíték" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Intel BootGuard által ellenőrzött rendszerindítás" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Intel BootGuard hibairányelv" #. TRANSLATORS: longer description msgid "Intel BootGuard prevents unauthorized device software from operating when the device is started." msgstr "Az Intel BootGuard megakadályozza a hitelesítetlen eszközszoftvereket abban, hogy az eszköz indulásakor működjenek." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Intel BootGuard által ellenőrzött rendszerindítás" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET aktív" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET engedélyezve" #. TRANSLATORS: longer description msgid "Intel Control-Flow Enforcement Technology detects and prevents certain methods for running malicious software on the device." msgstr "Az Intel vezérlőáramlás-kényszerítési technológia felismer és megakadályoz bizonyos metódusokat abban, hogy kártékony szoftvert futtassanak az eszközön." #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS Mitigation" msgstr "Intel GDS kárenyhítés" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS mitigation" msgstr "Intel GDS kárenyhítés" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Intel menedzsmentmotor gyártói mód" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Intel menedzsmentmotor felülbírálás" #. TRANSLATORS: longer description msgid "Intel Management Engine Override disables checks for device software tampering." msgstr "Az Intel menedzsmentmotor felülbírálás letiltja az eszközszoftver manipulálásának ellenőrzéseit." #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Intel menedzsmentmotor verziója" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: longer description msgid "Intel Supervisor Mode Access Prevention ensures critical parts of device memory are not accessed by less secure programs." msgstr "Az Intel felügyelői mód hozzáférés-megakadályozása azt biztosítja, hogy az eszközmemória kritikus részeit ne érjék el a kevésbé biztonságos programok." #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Belső eszköz" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Érvénytelen" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Érvénytelen argumentumok" #. TRANSLATORS: error message msgid "Invalid arguments, expected GUID" msgstr "Érvénytelen argumentumok, GUID az elvárt" #. TRANSLATORS: error message msgid "Invalid arguments, expected at least ARCHIVE FIRMWARE METAINFO" msgstr "Érvénytelen argumentumok, legalább ARCHÍVUM BELSŐ-VEZÉRLŐPROGRAM METAINFORMÁCIÓK az elvárt" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Régebbi verzió" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Rendszerbetöltő módban van" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Frissítés" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Probléma" msgstr[1] "Problémák" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "KULCS,ÉRTÉK" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "A rendszermag többé nem fertőzött" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "A rendszermag fertőzött" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "A rendszermag lezárása letiltva" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "A rendszermag lezárása engedélyezve" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Kulcstartó" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "HELY" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Utoljára módosítva" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Kevesebb mint egy perc van hátra" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licenc" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Linux rendszermag zárlat" #. TRANSLATORS: longer description msgid "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgstr "A Linux rendszermag zárlat mód megakadályozza a rendszergazda (root) fiókokat abban, hogy hozzáférjenek és megváltoztassák a rendszerszoftver kritikus részeit." msgid "Linux Kernel Swap temporarily saves information to disk as you work. If the information is not protected, it could be accessed by someone if they obtained the disk." msgstr "A Linux rendszermag cserehely átmenetileg elmenti az információkat a lemezre a munkavégzés során. Ha az információk nincsenek védve, akkor azok elérhetők másoknak is, ha megszerzik a lemezt." #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Linux rendszermag ellenőrzés" msgid "Linux Kernel Verification makes sure that critical system software has not been tampered with. Using device drivers which are not provided with the system can prevent this security feature from working correctly." msgstr "A Linux rendszermag ellenőrzés azt biztosítja, hogy a kritikus rendszerszoftvert ne manipulálják. A nem a rendszerrel együtt szolgáltatott eszköz-illesztőprogramok használata megakadályozhatja ennek a biztonsági funkciónak a megfelelő működését." #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Linux cserehely" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux gyártói belső vezérlőprogram szolgáltatás (stabil belső vezérlőprogram)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux gyártói belső vezérlőprogram szolgáltatás (teszt belső vezérlőprogram)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Linux rendszermag" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Linux rendszermag zárlat" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linux cserehely" #. TRANSLATORS: command description msgid "List EFI variables with a specific GUID" msgstr "Egy bizonyos csoportazonosítóval rendelkező EFI-változók felsorolása" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "A dbx-ben lévő bejegyzések felsorolása" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "A támogatott belsővezérlőprogram-frissítések felsorolása" #. TRANSLATORS: command description msgid "List the available firmware GTypes" msgstr "Az elérhető belsővezérlőprogram-G-típusok felsorolása" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Az elérhető belsővezérlőprogram-típusok felsorolása" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Felsorolja az ESP-n lévő fájlokat" #. TRANSLATORS: command description msgid "Load device emulation data" msgstr "Eszközemulációs adatok betöltése" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "Betöltve egy külső modulból" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Betöltés…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Zárolt" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Alacsony" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "MEI kulcsnyilvántartás" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "MEI kulcsnyilvántartás" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "MEI gyártói mód" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "MEI felülbírálás" msgid "MEI version" msgstr "MEI verzió" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Bizonyos bővítmények kézi engedélyezése" #. TRANSLATORS: longer description msgid "Manufacturing Mode is used when the device is manufactured and security features are not yet enabled." msgstr "A gyártói módot akkor használják, amikor az eszköz gyártása folyamatban van, és a biztonsági funkciók még nincsenek engedélyezve." #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Legnagyobb hossz" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Legnagyobb érték" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Közepes" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Metaadatok aláírása" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metaadatok URI-ja" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "A metaadatok nem szerezhetőek be a Linux gyártói belső vezérlőprogram szolgáltatásból." #. TRANSLATORS: error message for a user who ran fwupdmgr #. * refresh recently msgid "Metadata is up to date; use --force to refresh again." msgstr "A metaadatok naprakészek, használja a --force kapcsolót az újbóli frissítéshez." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Legkisebb verzió" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Legkisebb hossz" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Legkisebb érték" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Eltérő démon és kliens, inkább ezt használja: %s" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Módosít egy démonbeállítási értéket" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Módosítja a megadott távoli tárolót" msgid "Modify a configured remote" msgstr "A beállított távoli tároló módosítása" msgid "Modify daemon configuration" msgstr "Démon konfigurációjának módosítása" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "A démon eseményeinek figyelése" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Csatolja az ESP-t" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Újraindítást igényel a telepítés után" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Újraindítást igényel" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Leállítást igényel a telepítés után" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Új verzió" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Nincs művelet megadva!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Nincsenek régebbi verziók a(z) %s eszközhöz" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Nem találhatók belsővezérlőprogram-azonosítók" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "Nem található belső vezérlőprogram" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Nem észlelhető belsővezérlőprogram-frissítési képességgel rendelkező hardver" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nem találhatók bővítmények" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Nincsenek elérhető kiadások" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Jelenleg nincsenek engedélyezett távoli tárolók, így nem érhetőek el metaadatok." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Nincsenek elérhető távoli tárolók" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Nincsenek frissíthető eszközök" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Nincsenek elérhető frissítések" msgid "No updates available for remaining devices" msgstr "Nem érhetők el frissítések a hátralévő eszközökhöz" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nem lett frissítés alkalmazva" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Nincs jóváhagyva" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Nem található" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Nem támogatott" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "OK" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "OK!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Régi verzió" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Csak önálló PCR-értékek megjelenítése" #. TRANSLATORS: command line option msgid "Only use peer-to-peer networking when downloading files" msgstr "Csak egyenrangú hálózatkezelés használata a fájlok letöltésekor" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Csak verziófrissítések engedélyezettek" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Kiment JSON-formátumban" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Az alapértelmezett ESP-útvonal felülbírálása" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Firmware" msgstr "Egyenrangú belső vezérlőprogram" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Metadata" msgstr "Egyenrangú metaadatok" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "ÚTVONAL" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Belsővezérlőprogram-fájl részleteinek feldolgozása és megjelenítése" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "A dbx-frissítés feldolgozása…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "A rendszer dbx feldolgozása…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Jelszó" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Belsővezérlőprogram-blob befoltozása egy ismert eltolásnál" msgid "Payload" msgstr "Hasznos teher" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Függőben" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Százalék kész" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Végrehajtja a műveletet?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Platform hibakeresése" #. TRANSLATORS: longer description msgid "Platform Debugging allows device security features to be disabled. This should only be used by hardware manufacturers." msgstr "A platform hibakeresése lehetővé teszi az eszköz biztonsági funkcióinak letiltását. Ezt csak a hardvergyártóknak kellene használniuk." #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Platform hibakeresése" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "A folytatás előtt győződjön meg arról, hogy rendelkezik-e a kötet helyreállítási kulcsával." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Adjon meg egy számot 0 és %u között: " #. TRANSLATORS: the user isn't reading the question -- please don't translate #. * 'Y' or 'N' as these are hardcoded msgid "Please enter either Y or N: " msgstr "Adjon meg Y (igen) vagy N (nem) értéket: " #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "A bővítmény függőségei hiányoznak" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Lehetséges értékek" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "Rendszerindítás előtti közvetlen memória hozzáférés elleni védelem" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Rendszerindítás előtti közvetlen memória hozzáférés elleni védelem" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "A rendszerindítás előtti közvetlen memória hozzáférés elleni védelem letiltva" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "A rendszerindítás előtti közvetlen memória hozzáférés elleni védelem engedélyezve" #. TRANSLATORS: longer description msgid "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." msgstr "A rendszerindítás előtti közvetlen memória hozzáférés elleni védelem megakadályozza az eszközöket abban, hogy hozzáférjenek a rendszermemóriához, miután kapcsolódtak a számítógéphez." #. TRANSLATORS: warning message msgid "Press unlock on the device to continue the update process." msgstr "Nyomja meg a feloldást a készüléken a frissítési folyamat folytatásához." #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Előző verzió" msgid "Print the version number" msgstr "A verziószám kiírása" msgid "Print verbose debug statements" msgstr "Részletes hibakeresési utasítások kiírása" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritás" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Problémák" msgid "Proceed with upload?" msgstr "Folytatja a feltöltést?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Processzor biztonsági ellenőrzései" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Processzor visszaállítási védelem" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Tulajdonosi" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Belső vezérlőprogram frissítési támogatás lekérdezése" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "TÁVOLITÁROLÓ-AZONOSÍTÓ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "TÁVOLITÁROLÓ-AZONOSÍTÓ KULCS ÉRTÉK" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Csak olvasható" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Belsővezérlőprogram-blob beolvasása egy eszközről" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Belső vezérlőprogram beolvasása egy eszközről" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Belső vezérlőprogram beolvasása egy eszközről egy fájlba" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Belső vezérlőprogram beolvasása egy partícióról egy fájlba" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Olvasás a(z) %s eszközről…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Olvasás…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Újraindítás…" #. TRANSLATORS: how often we should refresh the metadata msgid "Refresh Interval" msgstr "Frissítési időköz" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Metaadatok frissítése a távoli kiszolgálóról" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Újratelepíti a(z) %s eszközt ezzel: %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "A jelenlegi belső vezérlőprogram újratelepítése az eszközön" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Belső vezérlőprogram újratelepítése egy eszközön" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "%s újratelepítése ezzel: %s… " #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Kiadási ág" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Kiadási jelzők" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "Kiadási azonosító" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Távoli azonosító" #. TRANSLATORS: command description msgid "Removes devices to watch for future emulation" msgstr "Eltávolítja a jövőbeni emulációhoz megfigyelendő eszközöket" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Adatok cseréje egy meglévő belső vezérlőprogram fájlban" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Jelentési URI" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Jelentve a távoli kiszolgálónak" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Kérés megszakítva" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "A szükséges efivarfs fájlrendszer nem található" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "A szükséges hardver nem található" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Rendszerbetöltőt igényel" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Internetkapcsolat szükséges" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Újraindítja most?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Újraindítja a démont a változtatás életbe léptetéséhez?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Eszköz újraindítása…" #. TRANSLATORS: command description msgid "Retrieve BIOS settings. If no arguments are passed all settings are returned" msgstr "BIOS-beállítások lekérése. Ha nincsenek argumentumok átadva, akkor az összes beállítás vissza lesz adva" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "A gép összes hardverazonosítójának visszaadása" #. TRANSLATORS: longer description msgid "Rollback Protection prevents device software from being downgraded to an older version that has security problems." msgstr "A visszaállítási védelem megakadályozza, hogy az eszközszoftvert visszaállítsák egy olyan régebbi verzióra, amely biztonsági problémákkal rendelkezik." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Futtassa az „fwupdmgr get-upgrades” parancsot a további információkért." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "A bővítmény összetett tisztítási rutinjának futtatása az install-blob használatakor" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "A bővítmény összetett előkészítési rutinjának futtatása az install-blob használatakor" #. TRANSLATORS: tell a user how to get information #, c-format msgid "Run without '%s' to see" msgstr "Futtassa „%s” nélkül a megtekintéshez" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "A futó rendszermag túl régi" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Futásidejű utótag" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "BEÁLLÍTÁS ÉRTÉK" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING1 VALUE1 [SETTING2] [VALUE2]" msgstr "BEÁLLÍTÁS1 ÉRTÉK1 [BEÁLLÍTÁS2] [ÉRTÉK2]" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "SPI BIOS-leírója" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "SPI BIOS-területe" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI zárolása" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "SPI visszajátszási védelem" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI írása" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "SPI írásvédelem" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "ALRENDSZER ILLESZTŐPROGRAM [ESZKÖZAZONOSÍTÓ|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "A hardverazonosítók előállítását lehetővé tevő fájl mentése" #. TRANSLATORS: command description msgid "Save device emulation data" msgstr "Eszközemulációs adatok mentése" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "Skála növekmény" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Telepítés ütemezése a következő újraindításkor, ha lehetséges" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Ütemezés…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "A biztonságos rendszerindítás letiltva" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "A biztonságos rendszerindítás engedélyezve" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Nézze meg a(z) %s szócikket a további részletekért." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "További információkért nézze meg a(z) %s webhelyet." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Kiválasztott eszköz" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Kiválasztott kötet" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Sorozatszám" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "A(z) „%s” BIOS-beállítás megadása „%s” értékre." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "BIOS-beállítás megadása" msgid "Set one or more BIOS settings" msgstr "Egy vagy több BIOS-beállítás beállítása" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "A hibakeresési jelző beállítása a frissítés során" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "Beállít egy vagy több BIOS-beállítást" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Beállítja a jóváhagyott belső vezérlőprogramok listáját" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Beállítás típusa" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "A beállítások a rendszer újraindítása után lesznek alkalmazva" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Belső vezérlőprogram előzményeinek megosztása a fejlesztőkkel" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Összes eredmény megjelenítése" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Ügyfél és démon verzióinak megjelenítése" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "A démon részletes információinak megjelenítése egy adott tartományhoz" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Hibakeresési információk megjelenítése az összes tartományhoz" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Hibakeresési kapcsolók megjelenítése" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Nem frissíthető eszközök megjelenítése" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "További hibakeresési információk megjelenítése" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Belsővezérlőprogram-frissítések előzményeinek megjelenítése" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "A dbx számított verziójának megjelenítése" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Az utolsó frissítési kísérletből származó hibakeresési napló megjelenítése" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "A belső vezérlőprogram frissítési állapot információinak megjelenítése" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Leállítja most?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Belső vezérlőprogram aláírása egy új kulccsal" msgid "Sign data using the client certificate" msgstr "Adatok aláírása az ügyféltanúsítvány használatával" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Adatok aláírása az ügyféltanúsítvány használatával" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Feltöltött adatok aláírása az ügyféltanúsítvánnyal" msgid "Signature" msgstr "Aláírás" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Aláírt hasznos teher" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Méret" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Egyes platformtitkok érvénytelenné válhatnak ennek a belső vezérlőprogramnak a frissítésekor." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Forrás" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "A DFU eszköz gyártó- vagy termékazonosítóinak megadása" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "A dbx-adatbázisfájl megadása" msgid "Specify the number of bytes per USB transfer" msgstr "Az USB-átvitelenkénti bájtok számának megadása" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "Karakterlánc" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Sikeres" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Az összes eszköz sikeresen aktiválva" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "A távoli tároló sikeresen letiltva" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Az új metaadatok sikeresen letöltve: " #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "A távoli tároló sikeresen engedélyezve és frissítve" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "A távoli tároló sikeresen engedélyezve" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "A belső vezérlőprogram sikeresen telepítve" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "A konfigurációérték sikeresen módosítva" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "A távoli tároló sikeresen módosítva" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "A metaadatok sikeresen frissítve kézileg" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Az eszköz-ellenőrzőösszegek sikeresen frissítve" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "%u jelentés sikeresen feltöltve" msgstr[1] "%u jelentés sikeresen feltöltve" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Az eszköz-ellenőrzőösszegek sikeresen ellenőrizve" #. TRANSLATORS: the device showed up in time #, c-format msgid "Successfully waited %.0fms for device" msgstr "Sikeresen várakozva %.0f ezredmásodpercet az eszközhöz" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Összegzés" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Támogatott" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "Támogatott CPU" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Távoli kiszolgálón támogatott" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Felfüggesztés üresjáratba" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Felfüggesztés a memóriába" #. TRANSLATORS: longer description msgid "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Az üresjáratba való felfüggesztés lehetővé teszi az eszköz számára, hogy gyorsan alvásba kerüljön az energia megtakarításához. Amíg az eszköz fel van függesztve, a memóriája fizikailag eltávolítható, és az információk hozzáférhetők." #. TRANSLATORS: longer description msgid "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "A memóriába való felfüggesztés lehetővé teszi az eszköz számára, hogy gyorsan alvásba kerüljön az energia megtakarításához. Amíg az eszköz fel van függesztve, a memóriája fizikailag eltávolítható, és az információk hozzáférhetők." #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Felfüggesztés üresjáratba" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Felfüggesztés a memóriába" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Átváltja a(z) %s ágat %s ágra?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "A belsővezérlőprogram-ág váltása az eszközön" #. TRANSLATORS: this CLI tool is now preventing system updates msgid "System Update Inhibited" msgstr "Rendszerfrissítés meggátolva" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "A rendszer akkumulátorszintje túl alacsony a frissítés végrehajtásához" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "A rendszer akkumulátorszintje túl alacsony a frissítés végrehajtásához (%u%%, de %u%% szükséges)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "A rendszer külső energiaforrást igényel" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "SZÖVEG" #. TRANSLATORS: longer description msgid "TPM (Trusted Platform Module) is a computer chip that detects when hardware components have been tampered with." msgstr "A TPM (Trusted Platform Module – megbízható platform modul) egy számítógépes integrált áramkör, amely felismeri, ha a hardverösszetevőket manipulálták." #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "TPM PCR0 újjáépítés" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "A TPM PCR0 újjáépítés érvénytelen" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "A TPM PCR0 újjáépítés most már érvényes" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "TPM platformbeállítás" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "TPM újjáépítés" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "TPM üres PCR-ek" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM 2.0-s verzió" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Címke" msgstr[1] "Címkék" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "Megjelölve emulációhoz" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Fertőzött" msgid "Target" msgstr "Cél" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Eszköz tesztelése JSON-nyilvántartás használatával" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Tesztelt" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "%s által tesztelt" #. TRANSLATORS: someone we trust has tested this msgid "Tested by trusted vendor" msgstr "Megbízható gyártó által tesztelt" #. TRANSLATORS: longer description msgid "The Intel Management Engine Key Manifest must be valid so that the device firmware can be trusted by the CPU." msgstr "Az Intel menedzsmentmotor kulcsnyilvántartásának érvényesnek kell lennie ahhoz, hogy az eszköz belső vezérlőprogramjában megbízhasson a processzor." #. TRANSLATORS: longer description msgid "The Intel Management Engine controls device components and needs to have a recent version to avoid security issues." msgstr "Az Intel menedzsmentmotor vezérli az eszközösszetevőket, és a legújabb verzióval kell rendelkeznie a biztonsági problémák elkerüléséhez." #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "Az LVFS egy ingyenes szolgáltatás, amely független jogi entitásként működik, és nincs kapcsolata a(z) $OS_RELEASE:NAME$ operációs rendszerrel. A disztribúció szállítója nem biztos, hogy ellenőrizte kompatibilitási szempontból a belsővezérlőprogram-frissítéseket. Mindent belső vezérlőprogramot csak az eredeti termék gyártója biztosít." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Platform Configuration is used to check whether the device start process has been modified." msgstr "A TPM (Trusted Platform Module – megbízható platform modul) platform konfigurációját annak ellenőrzéséhez használják, hogy az eszköz indulási folyamatát módosították-e." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Reconstruction is used to check whether the device start process has been modified." msgstr "A TPM (Trusted Platform Module – megbízható platform modul) újjáépítést annak ellenőrzéséhez használják, hogy az eszköz indulási folyamatát módosították-e." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "A TPM PCR0 eltér az újjáépítéstől." #. TRANSLATORS: longer description msgid "The UEFI Platform Key is used to determine if device software comes from a trusted source." msgstr "Az UEFI platformkulcsot annak meghatározásához használják, hogy az eszközszoftver megbízható forrásból származik-e." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "A démon harmadik féltől származó kódot töltött be, és azokat a távoli tároló fejlesztői többé már nem támogatják!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "Az eszköz verziója nem egyezett: %s található, %s az elvárt" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "A(z) %s gyártótól származó belső vezérlőprogramot nem a(z) %s, a hardver gyártója biztosította." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "A rendszer órája nem lett helyesen beállítva, és a fájlok letöltése meghiúsulhat." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been re-inserted." msgstr "A frissítés akkor fog folytatódni, ha a készülék USB-kábele újra bedugásra kerül." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged and then re-inserted." msgstr "A frissítés akkor fog folytatódni, ha a készülék USB-kábele kihúzásra, majd újra bedugásra kerül." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged." msgstr "A frissítés akkor fog folytatódni, ha a készülék USB-kábele kihúzásra kerül." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "A gyártó nem adott ki kiadási megjegyzéseket." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Problémákkal rendelkező eszközök vannak:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Nincsenek tiltott belsővezérlőprogram-fájlok." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Nincs jóváhagyott belső vezérlőprogram." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "Az eszköz vissza lesz állítva a(z) %s verzióra, ha a(z) %s parancs végrehajtásra kerül." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "Ezt a belső vezérlőprogramot az LVFS közösség tagjai szolgáltatják, és nem az eredeti hardvergyártó biztosítja (vagy támogatja)." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Ezt a csomagot nem ellenőrizték, ezért előfordulhat, hogy nem működik megfelelően." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Lehet, hogy ez a program csak rendszergazdaként működik megfelelően" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Ez a távoli tároló olyan belső vezérlőprogramot tartalmaz, amelyre nem vonatkozik embargó, de még teszteli a hardver gyártója. Érdemes meggyőződnie arról, hogy van-e mód a belső vezérlőprogram régebbi verziójának kézi telepítésére, ha a belső vezérlőprogram frissítése nem sikerül." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "Ez a rendszer nem támogatja a belső vezérlőprogram beállításokat" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Ez a rendszer HSI futásidejű problémákkal rendelkezik." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Ez a rendszer alacsony HSI biztonsági szinttel rendelkezik." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Ez az eszköz lehetővé teszi a rendszergazdáknak az UEFI dbx-frissítések alkalmazását." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Ez az eszköz lehetővé teszi a rendszergazdáknak az UpdateCapsule művelet hibakeresését." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Ez az eszköz lehetővé teszi a rendszergazdáknak az fwupd démon lekérdezését és vezérlését, lehetővé téve számukra olyan műveletek végrehajtását, mint a belső vezérlőprogram telepítése vagy régebbi verziójának telepítése." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Ez az eszköz lehetővé teszi a rendszergazdáknak az fwupd bővítményeinek használatát anélkül, hogy telepítve lennének a gazdarendszeren." #. TRANSLATORS: the %1 is a BIOS setting name. #. * %2 and %3 are the values, e.g. "True" or "Windows10" #, c-format msgid "This tool can change the BIOS setting '%s' from '%s' to '%s' automatically, but it will only be active after restarting the computer." msgstr "Ez az eszköz képes automatikusan megváltoztatni a(z) „%s” BIOS-beállítást „%s” értékről „%s” értékre, de csak a számítógép újraindítása után lesz aktív." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Ezt az eszközt csak a root felhasználó használhatja" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Ez az eszköz beolvassa és feldolgozza a TPM-eseménynaplót a rendszer belső vezérlőprogramból." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Átmeneti hiba" #. TRANSLATORS: item is TRUE msgid "True" msgstr "Igaz" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Megbízható metaadatok" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Megbízható hasznos teher" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Típus" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI Bootservice Variables" msgstr "UEFI rendszerindítási szolgáltatás változói" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition may not be set up correctly" msgstr "Az UEFI ESP-partíció talán nincs megfelelően beállítva" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "Az UEFI ESP-partíció nincs felismerve vagy beállítva" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI belső vezérlőprogram segédprogram" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "UEFI platformkulcs" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "UEFI biztonságos rendszerindítás" #. TRANSLATORS: longer description msgid "UEFI Secure Boot prevents malicious software from being loaded when the device starts." msgstr "Az UEFI biztonságos rendszerindítás megakadályozza, hogy rosszindulatú szoftverek töltődjenek be az eszköz indulásakor." #. TRANSLATORS: longer description msgid "UEFI boot service variables should not be readable from runtime mode." msgstr "Az UEFI rendszerindítási szolgáltatás változói nem lehetnek olvashatók futásidejű módból." #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI bootservice variables" msgstr "UEFI rendszerindítási szolgáltatás változói" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "Az UEFI kapszulafrissítések nem érhetők el vagy nincsenek engedélyezve a belső vezérlőprogram beállításában" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "UEFI dbx segédprogram" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "Az UEFI belső vezérlőprogram nem frissíthető örökölt BIOS módban" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "UEFI platformkulcs" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "UEFI biztonságos rendszerindítás" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Nem lehet kapcsolódni a szolgáltatáshoz" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "Nem található attribútum" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Jelenlegi illesztőprogram leválasztása" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Belső vezérlőprogram tiltásának feloldása:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Megszünteti egy bizonyos belső vezérlőprogram telepítésének tiltását" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Titkosítatlan" #. TRANSLATORS: command description msgid "Uninhibit the system to allow upgrades" msgstr "A rendszer gátlásának megszüntetése a frissítések lehetővé tételéhez" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Ismeretlen" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Ismeretlen eszköz" msgid "Unlock the device to allow access" msgstr "Az eszköz feloldása a hozzáférés engedélyezéséhez" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Feloldott" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Feloldja az eszközt a belső vezérlőprogram eléréséhez" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Leválasztja az ESP-t" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "A hibakeresési jelző kikapcsolása a frissítés során" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Aláíratlan hasznos teher" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Nem támogatott démonverzió: %s, az ügyfélverzió: %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Nem fertőzött" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Frissíthető" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Frissítési hiba" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Frissítési lemezkép" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Frissítési üzenet" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Frissítés állapota" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "A feltöltési hiba ismert probléma, további információkért látogassa meg ezt az URL-t:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Frissíti most?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "A frissítés újraindítást igényel" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "A tárolt kriptográfiai kivonat frissítése a jelenlegi ROM tartalmával" msgid "Update the stored device verification information" msgstr "A tárolt eszközellenőrzési információk frissítése" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "A tárolt metaadatok frissítése a jelenlegi tartalommal" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "Frissíti az összes megadott eszközt a legújabb belsővezérlőprogram-verzióra, vagy az összes eszközt, ha nincs megadva" msgid "Updating" msgstr "Frissítés" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "%s frissítése %s verzióról %s verzióra… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "%s frissítése…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Frissíti a(z) %s eszközön lévő verziót: %s → %s?" #. TRANSLATORS: ask the user to share, %s is something #. * like: "Linux Vendor Firmware Service" #, c-format msgid "Upload these anonymous results to the %s to help other users?" msgstr "Feltölti ezeket a névtelen eredményeket a(z) %s szolgáltatásba, hogy segítsen más felhasználóknak?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "A belső vezérlőprogram jelentéseinek feltöltése segít a hardvergyártóknak, hogy gyorsan azonosítsák a hibás és sikeres frissítéseket valós eszközökön." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Sürgősség" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Használja a(z) „%s” parancsot a súgóért" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Használja a CTRL^C billentyűkombinációt a megszakításhoz." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Használja az „fwupdtool --help” parancsot a súgóért" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Kerülőmegoldás jelzők használata a belső vezérlőprogram telepítésekor" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "A felhasználó értesítve" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Felhasználónév" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Érvényes" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Az ESP-tartalom ellenőrzése…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Változat" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Gyártó" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Ellenőrzés…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Verzió" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Verzió[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "FIGYELMEZTETÉS" #. TRANSLATORS: command description msgid "Wait for a device to appear" msgstr "Várakozás egy eszköz megjelenésére" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Várakozás…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Hardverváltozások figyelése" #. TRANSLATORS: check various UEFI and ACPI tables are unchanged after the #. update msgid "Will measure elements of system integrity around an update" msgstr "Mérni fogja a rendszer integritásának elemeit egy frissítés körül" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Belső vezérlőprogram írása egy fájlból egy eszközre" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Belső vezérlőprogram írása egy fájlból egy partícióra" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Fájl írása:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Írás…" #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from the system firmware setup, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Győződjön meg arról, hogy kényelmesen vissza tudja állítani a beállításokat a rendszer belső vezérlőprogramjának beállításaiból, mivel ez a változtatás azt eredményezheti, hogy a rendszer nem tudja elindítani a Linuxot, vagy más módon teheti instabillá a rendszert." #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "A disztribúció szállítója nem biztos, hogy ellenőrizte kompatibilitási szempontból a belsővezérlőprogram-frissítéseket a rendszerével és a kapcsolódó eszközeivel." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "A hardver megsérülhet ennek a belső vezérlőprogramnak a használatával, és a kiadás telepítése érvénytelenítheti a(z) %s által vállalt garanciát." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "A rendszere a(z) %s BKC-re van beállítva." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[ELLENŐRZŐÖSSZEG]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[ESZKÖZAZONOSÍTÓ|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[ESZKÖZAZONOSÍTÓ|GUID] [ÁG]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[ESZKÖZAZONOSÍTÓ|GUID] [VERZIÓ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[FÁJL FÁJL-ALÁÍRÁS TÁVOLITÁROLÓ-AZONOSÍTÓ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[FÁJLNÉV1] [FÁJLNÉV2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[INDOK]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[1. BEÁLLÍTÁS] [ 2. BEÁLLÍTÁS]…" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[BEÁLLÍTÁS1] [BEÁLLÍTÁS2] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SMBIOS-FÁJL|HWIDS-FÁJL]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "alapértelmezett" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "fwupd TPM-eseménynapló segédprogram" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd bővítmények" fwupd-1.9.16/po/id.po000066400000000000000000003105641460375044200143250ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Andika Triwidada , 2017-2019,2021 # Andika Triwidada , 2021 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Indonesian (http://app.transifex.com/freedesktop/fwupd/language/id/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: id\n" "Plural-Forms: nplurals=1; plural=0;\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f menit tersisa" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "Pemutakhiran MBC %s" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "Pembaruan Baterai %s" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "Pembaruan Microcode CPU %s" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "Pembaruan Kamera %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "Pembaruan Konfigurasi %s" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Pembaruan ME Konsumen %s" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Pembaruan Pengontrol %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Pembaruan ME Korporat %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Pembaruan Perangkat %s" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "Pemutakhiran Tampilan %s" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "Pemutakhiran Dok %s" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "Pemutakhiran Drive %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Pembaruan Pengontrol Tertanam %s" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "Pemutakhiran Pembaca Sidik Jari %s" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "Pemutakhiran Flash Disk %s" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "Pemutakhiran GPU %s" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "Pemutakhiran Tablet Grafis %s" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "Pembaruan Papan Ketik %s" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "Pembaruan ME %s" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "Pembaruan Tetikus %s" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "Pembaruan Antar Muka Jaringan %s" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "Pemutakhiran SSD %s" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "Pembaruan Pengontrol Penyimpanan %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Pembaruan Sistem %s" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "Pembaruan TPM %s" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Pembaruan Pengontrol Thunderbolt %s" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "Pembaruan Touchpad %s" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "Pemutakhiran Dok USB %s" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "Pemutakhiran Penerima USB %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Pembaruan %s" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s dan semua perangkat yang terhubung mungkin tidak dapat digunakan saat memperbarui." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "%s muncul: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "%s berubah: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "%s menghilang: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s saat ini tidak dapat diperbarui" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s mode manufaktur" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s harus tetap terhubung selama pembaruan untuk menghindari kerusakan." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s harus tetap terhubung ke sumber tenaga selama pembaruan untuk menghindari kerusakan." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s menimpa" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "versi %s" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u hari" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u perangkat memiliki pembaruan firmware." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u perangkat bukanlah konfigurasi yang paling dikenal." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u jam" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u perangkat lokal didukung" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u menit" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u detik" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (ambang batas %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(usang)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "Suatu PCR TPM sekarang adalah nilai yang tidak valid" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "Perlindungan Replay Firmware AMD" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "Perlindungan Tulis Firmware AMD" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "Perlindungan Rollback Prosesor Aman AMD" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Tindakan Diperlukan:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Mengaktifkan perangkat" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Aktifkan perangkat yang tertunda" msgid "Activate the new firmware on the device" msgstr "Aktifkan firmware baru pada perangkat" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Mengaktifkan pemutakhiran firmware" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Mengaktifkan pembaruan firmware untuk" #. TRANSLATORS: command description msgid "Adds devices to watch for future emulation" msgstr "Menambahkan perangkat yang akan diawasi untuk emulasi di masa mendatang" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Usia" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Setuju dan aktifkan remote?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias ke %s" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "Semua TPM PCR sekarang valid" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Semua PCR TPM valid" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Semua perangkat dengan tipe yang sama akan diperbarui pada saat yang sama" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Izinkan penuruntingkatan versi firmware" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Izinkan menginstal ulang versi firmware yang ada" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Perbolehkan beralih cabang firmware" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Branch alternatif" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "Pembaruan sedang berlangsung" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Suatu pembaruan memerlukan boot ulang agar lengkap." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Pembaruan membutuhkan sistem untuk dimatikan untuk menyelesaikan." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Menjawab ya untuk semua pertanyaan" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Terapkan pembaruan firmware" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Terapkan pembaruan bahkan ketika tidak disarankan" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Menerapkan berkas pembaruan" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Menerapkan pembaruan…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Firmware yang disetujui:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Tanya lagi lain kali?" #. TRANSLATORS: command description msgid "Asks the daemon to quit" msgstr "Meminta daemon untuk berhenti" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Cantolkan ke mode firmware" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Mengautentikasi…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Detail otentikasi diperlukan" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Autentikasi diperlukan untuk menuruntingkatkan firmware pada perangkat lepas pasang" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Autentikasi diperlukan untuk menuruntingkatkan firmware pada mesin ini" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Autentikasi diperlukan untuk mengubah pengaturan BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Autentikasi diperlukan untuk mengubah sebuah remote yang ditata yang dipakai untuk pembaruan firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Autentikasi diperlukan untuk mengubah konfigurasi daemon" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "Autentikasi diperlukan untuk membaca pengaturan BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Autentikasi diperlukan untuk mengatur daftar firmware yang disetujui" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Autentikasi diperlukan untuk menandatangani data menggunakan sertifikat klien" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Autentikasi diperlukan untuk beralih ke versi firmware baru" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Autentikasi diperlukan untuk membuka kunci suatu perangkat" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Autentikasi diperlukan untuk memutakhirkan firmware pada perangkat lepas pasang" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Autentikasi diperlukan untuk memutakhirkan firmware pada mesin ini" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Autentikasi diperlukan untuk memutakhirkan checksum tersimpan bagi perangkat" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Pelaporan Otomatis" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Unggah secara otomatis setiap kali?" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "Perlindungan Rollback BIOS" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "Perlindungan rollback BIOS" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "Pembaruan BIOS dikirimkan melalui LVFS atau Pembaruan Windows" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BUILDER-XML NAMABERKAS-DST" msgid "BYTES" msgstr "BYTE" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Baterai" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Mengikat driver kernel baru" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Berkas firmware yang diblokir:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Versi yang diblokir" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Memblokir firmware:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Memblokir firmware tertentu agar tidak diinstal" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Versi Bootloader" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Cabang" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Build berkas firmware" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Batal" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Dibatalkan" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Tidak dapat diterapkan karena pembaruan dbx telah diterapkan." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Tidak dapat menerapkan pembaruan di media live" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Diubah" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Memeriksa apakah hash kriptografis cocok dengan firmware" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Checksum" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Pilih branch" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Pilih perangkat" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Pilih firmware" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Pilih rilis" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Pilih ESP:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Pilih volume" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Membersihkan hasil dari pemutakhiran terakhir" #. TRANSLATORS: error message msgid "Command not found" msgstr "Perintah tidak ditemukan" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Didukung komunitas" #. TRANSLATORS: title prefix for the BIOS settings dialog msgid "Configuration Change Suggested" msgstr "Perubahan Konfigurasi Disarankan" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "Konfigurasi hanya dapat dibaca oleh administrator sistem" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Mengonversi berkas firmware" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Dibuat" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Kritis" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Verifikasi hash kriptografis tersedia" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Nilai Sekarang" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Versi saat ini" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "ID-PERANTI|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Utilitas DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opsi Pengawakutuan" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Mendekompresi…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Deskripsi" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Lepaskan ke mode bootloader" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Rincian" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "Menyimpang dari konfigurasi yang paling dikenal?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Flag Perangkat" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "ID Perangkat" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Perangkat ditambahkan:" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "Daya baterai perangkat terlalu lemah" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "Daya baterai perangkat terlalu lemah (%u%%, membutuhkan %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Perangkat dapat memulihkan kegagalan flash" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "Perangkat tidak dapat digunakan saat lid tertutup" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Perangkat diubah:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "Emulasi perangkat tidak diaktifkan." #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Firmware perangkat harus memiliki pemeriksaan versi" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "Perangkat diemulasi" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Perangkat terkunci" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Perangkat perlu menginstal semua rilis yang disediakan" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Perangkat tidak terjangkau" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "Perangkat tidak dapat dijangkau, atau di luar jangkauan nirkabel" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Perangkat dapat digunakan selama durasi pembaruan" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "Perangkat sedang menunggu pembaruan diterapkan" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Perangkat dilepas:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "Perangkat membutuhkan daya AC tersambung" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "Perangkat memerlukan lisensi perangkat lunak untuk memperbarui" #. TRANSLATORS: longer description msgid "Device software updates are provided for this device." msgstr "Pembaruan perangkat lunak perangkat disediakan untuk perangkat ini." #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Pembaruan tahap perangkat" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "Perangkat mendukung beralih ke cabang firmware yang berbeda" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Metode pembaruan perangkat" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Pembaruan perangkat membutuhkan aktivasi" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Perangkat akan mencadangkan firmware sebelum menginstal" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Perangkat tidak akan muncul kembali setelah pembaruan selesai" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Peranti yang sukses diperbarui:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Peranti yang tidak diperbarui dengan benar:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Perangkat tanpa pembaruan firmware yang tersedia: " #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Perangkat dengan versi firmware terbaru yang tersedia:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "Tidak menemukan perangkat dengan GUID yang cocok" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Dinonaktifkan" msgid "Disabled fwupdate debugging" msgstr "Dinonaktifkan fwupdate debugging" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Menonaktifkan remote yang diberikan" #. TRANSLATORS: command line option msgid "Display version" msgstr "Tampilkan versi" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Distribusi" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Jangan periksa untuk metadata lama" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Jangan periksa untuk riwayat yang tak dilaporkan" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Jangan periksa apakah remote unduhan harus diaktifkan" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Jangan memeriksa atau meminta reboot setelah pembaruan" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Jangan sertakan awalan domain log" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Jangan sertakan awalan stempel waktu" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Jangan melakukan pemeriksaan keamanan perangkat" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "Jangan mencari firmware saat mengurai" #. TRANSLATORS: warning message shown after update has been scheduled msgid "Do not turn off your computer or remove the AC adaptor while the update is in progress." msgstr "Jangan matikan komputer Anda atau lepaskan adaptor AC saat pembaruan sedang berlangsung." #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Jangan menulis ke basis data riwayat" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Apakah Anda memahami konsekuensi dari mengubah cabang firmware?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Apakah Anda ingin menonaktifkan fitur ini untuk pembaruan di masa mendatang?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "Apakah Anda ingin mengaktifkannya sekarang?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Apakah Anda ingin menyegarkan remote ini sekarang?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Apakah Anda ingin mengunggah laporan secara otomatis untuk pembaruan di masa mendatang?" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Selesai!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Menuruntingkatkan %s dari %s ke %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Menuruntingkatkan firmware pada suatu peranti" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Menuruntingkatkan %s dari %s ke %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Menuruntingkatkan %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Unduh suatu berkas" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Mengunduh…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Curahkan data SMBIOS dari suatu berkas" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Durasi" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "ESP yang ditentukan tidak valid" #. TRANSLATORS: longer description msgid "Each system should have tests to ensure firmware security." msgstr "Setiap sistem harus memiliki tes untuk memastikan keamanan firmware." #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "Mengemulasi perangkat menggunakan manifes JSON" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Diemulasi" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Host yang diemulasi" msgid "Enable" msgstr "Fungsikan" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Aktifkan dukungan pembaruan firmware pada sistem yang didukung" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Aktifkan remote baru?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Aktifkan remote ini?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Difungsikan" msgid "Enabled fwupdate debugging" msgstr "Diaktifkan debugging fwupdate" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Diaktifkan jika perangkat keras cocok" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Mengaktifkan remote yang diberikan" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Mengaktifkan fungsi ini dilakukan dengan risiko Anda sendiri, yang berarti Anda harus menghubungi produsen peralatan asli Anda mengenai masalah yang disebabkan oleh pembaruan ini. Hanya masalah dengan proses pembaruan itu sendiri yang harus diajukan pada $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Mengaktifkan remote ini dilakukan dengan risiko Anda sendiri." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Terenkripsi" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "RAM terenkripsi" msgid "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." msgstr "RAM terenkripsi membuat informasi yang disimpan dalam memori perangkat tidak mungkin dibaca jika chip memori dilepas dan diakses." #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Akhir hidup" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Enumerasi" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Menghapus semua riwayat pembaruan firmware" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Menghapus…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Keluar setelah tundaan sejenak" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Keluar setelah mesin telah dimuat" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Mengekspor struktur berkas firmware ke XML" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Mengekstrak blob firmware ke image" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "BERKAS" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "BERKAS [ID-PERANTI|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "NAMABERKAS" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "NAMABERKAS SERTIFIKAT KUNCI-PRIVAT" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "NAMABERKAS NAMA-ALT-PERANGKAT|ID-ALT-PERANGKAT" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "NAMABERKAS NAMA-ALT-PERANGKAT|ID-ALT-PERANGKAT [NAMA-ALT-IMAGE|ID-ALT-IMAGE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "NAMABERKAS ID-PERANTI" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "DATA OFSET NAMA-BERKAS [TIPE-FIRMWARE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "NAMABERKAS [ID-PERANTI|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "NAMABERKAS [TIPE-FIRMWARE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "NAMABERKAS-SRC NAMABERKAS-DST [TIPE-FIRMWARE-SRC] [TIPE-FIRMWARE-DST]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "NAMABERKAS|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Gagal" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Gagal menerapkan pembaruan" #. TRANSLATORS: error message for Windows msgid "Failed to connect to Windows service, please ensure it's running." msgstr "Gagal terhubung ke layanan Windows, pastikan itu berjalan." #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Gagal menyambung ke daemon" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Gagal mendapatkan perangkat yang tertunda" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Gagal menginstal pembaruan firmware" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Gagal memuat dbx lokal" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Gagal memuat quirk" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Gagal memuat dbx sistem" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Gagal mengunci" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Gagal mengurai argumen" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Gagal mengurai berkas" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Gagal mengurai flag untuk --filter" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Gagal mengurai dbx lokal" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Gagal reboot" #. TRANSLATORS: a feature is something like "can show an image" msgid "Failed to set front-end features" msgstr "Gagal mengatur fitur front-end" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Gagal mengatur mode splash" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Gagal memvalidasi konten ESP" #. TRANSLATORS: item is FALSE msgid "False" msgstr "Salah" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Nama Berkas" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Tanda Tangan Nama Berkas" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Sumber Nama Berkas" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Nama berkas diperlukan" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Memfilter dengan suatu set flag perangkat menggunakan awalan ~ untuk mengecualikan, mis. 'internal, ~needs-reboot'" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Deskriptor BIOS Firmware" #. TRANSLATORS: longer description msgid "Firmware BIOS Descriptor protects device firmware memory from being tampered with." msgstr "Deskriptor BIOS Firmware melindungi memori firmware perangkat agar tidak dirusak." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Wilayah BIOS Firmware" #. TRANSLATORS: longer description msgid "Firmware BIOS Region protects device firmware memory from being tampered with." msgstr "Wilayah BIOS Firmware melindungi memori firmware perangkat agar tidak dirusak." #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "URI Basis Firmware" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Layanan D-Bus Pemutakhiran Firmware" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Daemon Pemutakhiran Firmware" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Verifikasi Pemutakhir Firmware" msgid "Firmware Updater Verification checks that software used for updating has not been tampered with." msgstr "Verifikasi Pembaruan Firmware memeriksa bahwa perangkat lunak yang digunakan untuk memperbarui belum dirusak." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Pemutakhiran Firmware" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Utilitas Firmware" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Perlindungan Tulis Firmware" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Kunci Perlindungan Tulis Firmware" #. TRANSLATORS: longer description msgid "Firmware Write Protection protects device firmware memory from being tampered with." msgstr "Perlindungan Tulis Firmware melindungi memori firmware perangkat agar tidak dirusak." #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Pengesahan firmware" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Firmware sudah diblokir" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Firmware belum diblokir" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Metadata firmware belum diperbarui selama %uhari dan mungkin tidak mutakhir." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Pemutakhiran firmware" msgid "Firmware updates are not supported on this machine." msgstr "Pembaruan firmware tidak didukung pada mesin ini." msgid "Firmware updates are supported on this machine." msgstr "Pembaruan firmware didukung pada mesin ini." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Pembaruan firmware dinonaktifkan; jalankan 'fwupdmgr unlock' untuk mengaktifkan" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Flag" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Paksa tindakan dengan merelaksasi beberapa pemeriksaan runtime" msgid "Force the action ignoring all warnings" msgstr "Paksa tindakan mengabaikan semua peringatan" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Ditemukan" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Enkripsi Disk Lengkap Terdeteksi" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "Rahasia enkripsi disk lengkap mungkin jadi tidak valid saat memperbarui" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Platform yang memakai fuse" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" msgid "Get BIOS settings" msgstr "Dapatkan pengaturan BIOS" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Dapatkan semua flag perangkat yang didukung oleh fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Dapatkan semua perangkat yang mendukung pemutakhiran firmware" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Dapatkan semua plugin yang diaktifkan yang terdaftar dengan sistem" #. TRANSLATORS: command description msgid "Get device report metadata" msgstr "Mendapatkan metadata laporan perangkat" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Dapatkan rincian tentang suatu berkas firmware" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Dapatkan remote-remote yang terkonfigurasi" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Mendapatkan atribut keamanan host" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Dapatkan daftar firmware yang disetujui" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Mendapatkan daftar firmware yang diblokir" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Dapatkan daftar pemutakhiran bagi perangkat keras yang tersambung" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Mendapatkan rilis-rilis bagi sebuah peranti" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Mendapatkan hasil dari pemutakhiran terakhir" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "BERKAS-HWIDS" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Perangkat keras sedang menunggu untuk ditancapkan kembali" #. TRANSLATORS: the release urgency msgid "High" msgstr "Tinggi" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Kejadian Keamanan Host" #. TRANSLATORS: success, so say thank you to the user msgid "Host Security ID attributes uploaded successfully, thanks!" msgstr "Atribut ID Keamanan Host berhasil diunggah, terima kasih!" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "ID Keamanan Host:" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "INHIBIT-ID" msgstr "INHIBIT-ID" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "Perlindungan IOMMU" #. TRANSLATORS: longer description msgid "IOMMU Protection prevents connected devices from accessing unauthorized parts of system memory." msgstr "Perlindungan IOMMU mencegah perangkat yang terhubung mengakses bagian memori sistem yang tidak diotorisasi." #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "Proteksi perangkat IOMMU dinonaktifkan" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "Proteksi perangkat IOMMU diaktifkan" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Menganggur…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Abaikan pemeriksaan ketat SSL saat mengunduh berkas" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Abaikan kegagalan checksum firmware" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Abaikan kegagalan ketidakcocokan perangkat keras firmware" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Mengbaikan pemeriksaan keamanan validasi" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Mengabaikan pemeriksaan ketat SSL, untuk melakukan ini secara otomatis di masa depan, eksporlah DISABLE_SSL_STRICT di lingkungan Anda" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Durasi Instalasi" msgid "Install old version of signed system firmware" msgstr "Menginstal versi lama firmware sistem yang ditandatangani" msgid "Install old version of unsigned system firmware" msgstr "Menginstal versi lama firmware sistem yang tidak ditandatangani" msgid "Install signed device firmware" msgstr "Pasang firmware perangkat yang ditandatangani" msgid "Install signed system firmware" msgstr "Pasang firmware sistem yang ditandatangani" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Instal ke perangkat induk terlebih dahulu" msgid "Install unsigned device firmware" msgstr "Pasang firmware perangkat yang tak ditandatangani" msgid "Install unsigned system firmware" msgstr "Pasang firmware sistem yang tak ditandatangani" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Menginstal Firmware…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Sedang memasang pembaruan firmware…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Memasang pada %s…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "Memasang pembaruan ini juga dapat membatalkan garansi perangkat apa pun." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Integer" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "Intel BootGuard Terproteksi ACM" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Intel BootGuard terproteksi ACM" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Kebijakan Kesalahan Intel BootGuard" msgid "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." msgstr "Kebijakan Kesalahan Intel BootGuard memastikan perangkat tidak terus dimulai jika perangkat lunak perangkatnya telah dirusak." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Fuse Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Intel BootGuard OTP fuse" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Boot Terverifikasi Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Kebijakan kesalahan Intel BootGuard" #. TRANSLATORS: longer description msgid "Intel BootGuard prevents unauthorized device software from operating when the device is started." msgstr "Intel BootGuard mencegah perangkat lunak perangkat yang tidak sah beroperasi saat perangkat dimulai." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Boot terverifikasi Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET Aktif" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET Diaktifkan" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Mode Manufaktur Intel Management Engine" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Versi Intel Management Engine" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Perangkat internal" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Tak valid" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Argumen tidak valid" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Adalah turun tingkat" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Sedang dalam mode bootloader" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Adalah peningkatan" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Masalah" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "KUNCI,NILAI" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "Kernel tidak lagi tercemar" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "Kernel tercemar" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Penguncian kernel dinonaktifkan" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Penguncian kernel diaktifkan" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Keyring" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "LOKASI" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Modifikasi terakhir" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Kurang dari satu menit tersisa" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Lisensi" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Penguncian Kernel Linux" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Verifikasi Kernel Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Swap Linux" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Layanan Firmware Vendor Linux (firmware stabil)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Layanan Firmware Vendor Linux (firmware uji)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Kernel Linux" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Penguncian kernel Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linux swap" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Membuat daftar entri di dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Daftar pembaruan firmware yang didukung" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Daftar tipe firmware yang tersedia" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Daftar berkas di ESP" #. TRANSLATORS: command description msgid "Load device emulation data" msgstr "Muat data emulasi perangkat" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "Dimuat dari modul eksternal" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Memuat…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Terkunci" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Rendah" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "Manifes Kunci MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "Manifes kunci MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "Mode manufaktur MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "MEI menimpa" msgid "MEI version" msgstr "Versi MEI" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Aktifkan plugin tertentu secara manual" #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Panjang maksimum" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Nilai maksimum" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Sedang" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Tanda Tangan Metadata" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI Metadata" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadata dapat diperoleh dari Layanan Firmware Vendor Linux." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Versi Minimum" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Panjang minimum" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Nilai minimum" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Daemon dan klien tidak cocok, gunakan %s sebagai gantinya" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Memodifikasi nilai konfigurasi daemon" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Mengubah suatu remote yang diberikan" msgid "Modify a configured remote" msgstr "Ubah suatu remote yang ditata" msgid "Modify daemon configuration" msgstr "Ubah konfigurasi daemon" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Pantau daemon untuk kejadian" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Kait ESP" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Membutuhkan reboot setelah instalasi" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Perlu reboot" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Perlu dimatikan setelah instalasi" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Versi baru" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Tidak ada tindakan yang ditentukan!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Tidak ada penuruntingkatan untuk %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Tidak ada ID firmware yang ditemukan" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Tidak terdeteksi perangkat keras dengan kapabilitas pemutakhiran firmware" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Tidak ada plugin yang ditemukan" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Tidak ada rilis yang tersedia" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Saat ini tidak ada remote yang diaktifkan sehingga metadata tidak tersedia." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Tidak ada remote yang tersedia" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Tidak ada perangkat yang dapat diperbarui" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Tidak ada pembaruan yang tersedia" msgid "No updates available for remaining devices" msgstr "Tidak ada pembaruan yang tersedia untuk perangkat yang tersisa" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Tidak ada pembaruan yang diterapkan" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Tidak disetujui" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Tidak ditemukan" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Tak didukung" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "OK" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "OK!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Versi lama" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Hanya tampilkan nilai PCR tunggal" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Hanya peningkatan versi yang diizinkan" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Keluaran dalam format JSON" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Timpa path ESP default" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "PATH" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Uraikan dan tampilkan detail tentang berkas firmware" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Mengurai pembaruan dbx…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Mengurai dbx sistem…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Kata Sandi" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Mem-patch blob firmware pada ofset yang diketahui" msgid "Payload" msgstr "Payload" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Tertunda" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Persentase selesai" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Lakukan operasi?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Pengawakutuan Platform" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Pengawakutuan platform" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Pastikan Anda memiliki kunci pemulihan volume sebelum melanjutkan." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Silakan masukkan angka dari 0 hingga %u: " #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Dependensi plugin kurang" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Nilai yang Mungkin" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "Perlindungan DMA Pra-Boot" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Perlindungan DMA pra-boot" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "Perlindungan DMA pra-boot dinonaktifkan" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "Perlindungan DMA pra-boot diaktifkan" #. TRANSLATORS: longer description msgid "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." msgstr "Perlindungan DMA pra-boot mencegah perangkat mengakses memori sistem setelah terhubung ke komputer." #. TRANSLATORS: warning message msgid "Press unlock on the device to continue the update process." msgstr "Tekan buka kunci pada perangkat untuk melanjutkan proses pembaruan." #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Versi sebelumnya" msgid "Print the version number" msgstr "Cetak nomor versi" msgid "Print verbose debug statements" msgstr "Cetak pernyataan awakutu rinci" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritas" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Masalah" msgid "Proceed with upload?" msgstr "Lanjutkan mengunggah?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Pemeriksaan Keamanan Prosesor" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Perlindungan rollback prosesor" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Proprietari" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Menanyakan dukungan pembaruan firmware" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "ID-REMOTE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "ID-REMOTE KUNCI NILAI" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Hanya Baca" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Baca blob firmware dari perangkat" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Membaca firmware dari perangkat" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Baca firmware dari perangkat ke dalam suatu berkas" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Baca firmware dari satu partisi ke dalam suatu berkas" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Membaca dari %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Membaca…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Reboot…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Segarkan metadata dari server remote" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Pasang ulang %s ke %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Menginstal ulang firmware saat ini di perangkat" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Menginstal ulang firmware pada perangkat" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Memasang ulang %s dengan %s... " #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Cabang Rilis" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Flag Rilis" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "ID Rilis" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID Remote" #. TRANSLATORS: command description msgid "Removes devices to watch for future emulation" msgstr "Menghapus perangkat yang akan diawasi untuk emulasi di masa mendatang" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Gantikan data dalam suatu berkas firmware yang telah ada" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI Lapor" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Dilaporkan ke server remote" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Permintaan dibatalkan" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "Sistem berkas efivarfs yang diperlukan tidak ditemukan" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "Perangkat keras yang diperlukan tidak ditemukan" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Membutuhkan bootloader" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Memerlukan koneksi internet" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Mulai ulang sekarang?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Mulai ulang daemon untuk membuat perubahan itu efektif?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Memulai ulang perangkat…" #. TRANSLATORS: command description msgid "Retrieve BIOS settings. If no arguments are passed all settings are returned" msgstr "Ambil pengaturan BIOS. Jika tidak ada argumen yang diteruskan, semua pengaturan dikembalikan" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Memberikan kembalian semua ID perangkat keras bagi mesin" #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Jalankan `fwupdmgr get-upgrade` untuk informasi lebih lanjut." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Menjalankan rutin pembersihan komposit plugin saat menggunakan install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Menjalankan rutin persiapan komposit saat menggunakan install-blob" #. TRANSLATORS: tell a user how to get information #, c-format msgid "Run without '%s' to see" msgstr "Jalankan tanpa '%s' untuk melihat" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "Kernel yang sedang berjalan terlalu tua" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Akhiran Runtime" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "NILAI PENGATURAN" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "Deskriptor BIOS SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "Wilayah BIOS SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI kunci" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "Perlindungan replay SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI tulis" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "Proteksi penulisan SPI" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "SUBSISTEM DRIVER [ID-PERANTI|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Menyimpan berkas yang memungkinkan pembuatan ID perangkat keras" #. TRANSLATORS: command description msgid "Save device emulation data" msgstr "Simpan data emulasi perangkat" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Menjadwalkan instalasi untuk boot ulang selanjutnya bila mungkin" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Menjadwalkan…" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Secure Boot diaktifkan" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Lihat %s untuk lebih jelasnya." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Lihat %s untuk informasi lebih lanjut." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Perangkat yang dipilih" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Volume yang dipilih" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Nomor Seri" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "Setel pengaturan BIOS '%s' menggunakan '%s'." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "Atur pengaturan BIOS" msgid "Set one or more BIOS settings" msgstr "Atur satu atau beberapa pengaturan BIOS" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Atur flag debugging selama pembaruan" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "Mengatur satu atau beberapa pengaturan BIOS" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Setel daftar firmware yang disetujui" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Jenis pengaturan" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Pengaturan akan berlaku setelah reboot sistem" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Membagikan riwayat firmware dengan para pengembang" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Tampilkan semua hasil" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Tampilkan versi daemon dan klien" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Tampilkan informasi daemon rinci untuk domain tertentu" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Tampilkan informasi debug untuk semua domain" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Tampilkan opsi debugging" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Tampilkan peranti yang tidak dapat dimutakhirkan" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Tampilkan informasi pengawakutuan ekstra" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Tampilkan riwayat pembaruan firmware" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Memperlihatkan versi terhitung dari dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Tampilkan log debug dari pembaruan yang terakhir kali dicoba" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Tampilkan informasi status pembaruan firmware" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Matikan sekarang?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Menandatangani firmware dengan kunci baru" msgid "Sign data using the client certificate" msgstr "Tanda tangani data menggunakan sertifikat klien" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Tanda tangani data menggunakan sertifikat klien" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Menanda tangani data yang diunggah dengan sertifikat klien" msgid "Signature" msgstr "Tanda Tangan" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Payload yang Ditandatangani" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Ukuran" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Beberapa rahasia platform mungkin menjadi tidak valid saat memperbarui firmware ini." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Sumber" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Tentukan ID Produk/Vendor perangkat DFU" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Menentukan berkas basis data dbx" msgid "Specify the number of bytes per USB transfer" msgstr "Tentukan jumlah byte per transfer USB" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "String" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Sukses" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Berhasil mengaktifkan semua perangkat" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Remote yang berhasil dinonaktifkan" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Metadata baru berhasil diunduh: " #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Berhasil mengaktifkan dan menyegarkan remote" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Remote yang berhasil diaktifkan" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmware berhasil diinstal" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Nilai konfigurasi yang berhasil dimodifikasi" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Remote yang berhasil dimodifikasi" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metadata berhasil disegarkan secara manual" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Checksum perangkat berhasil diperbarui" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Berhasil mengunggah %u laporan" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Checksum perangkat berhasil diverifikasi" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Ringkasan" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Didukung" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "CPU yang didukung" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Didukung di server remote" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Suspensi Ke Menganggur" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Suspensi Ke RAM" #. TRANSLATORS: longer description msgid "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Sunpensi ke Menganggur memungkinkan perangkat untuk tidur dengan cepat agar menghemat daya. Meskipun perangkat telah ditangguhkan, memorinya dapat dicabut secara fisik dan informasinya diakses." #. TRANSLATORS: longer description msgid "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Suspensi ke RAM memungkinkan perangkat untuk tidur dengan cepat agar menghemat daya. Meskipun perangkat telah disuspensi, memorinya dapat dicabut secara fisik dan informasinya diakses." #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Suspend-to-idle" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Suspend-to-ram" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Beralih cabang dari %s ke %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Beralih cabang firmware pada perangkat" #. TRANSLATORS: this CLI tool is now preventing system updates msgid "System Update Inhibited" msgstr "Pemutakhiran Sistem Dicegah" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "Daya sistem terlalu lemah untuk melakukan pemutakhiran" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "Daya sistem terlalu lemah untuk melakukan pemutakhiran (%u%%, membutuhkan %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "Sistem membutuhkan sumber daya eksternal" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEKS" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "Rekonstruksi TPM PCR0" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "Rekonstruksi PCR0 TPM tidak valid" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "Rekonstruksi PCR0 TPM kini valid" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "Konfigurasi Platform TPM" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "Rekonstruksi TPM" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "PCR kosong TPM" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Tag" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "Di-tag untuk emulasi" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Ternoda" msgid "Target" msgstr "Target" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Menguji perangkat menggunakan manifest JSON" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Diuji" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "Diuji oleh %s" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS adalah layanan gratis yang beroperasi sebagai badan hukum independen dan tidak memiliki koneksi dengan $OS_RELEASE:NAME$. Distributor Anda mungkin belum memverifikasi pembaruan firmware untuk kompatibilitas dengan sistem Anda atau perangkat yang terhubung. Semua firmware hanya disediakan oleh pabrikan peralatan asli." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "TPM PCR0 berbeda dengan rekonstruksi." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "Daemon telah memuat kode pihak ke-3 dan tidak lagi didukung oleh pengembang hulu!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "Versi perangkat tidak cocok: mendapat %s, diharapkan %s" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "Firmware dari %s tidak disediakan oleh %s, vendor perangkat keras." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "Jam sistem belum diatur dengan benar dan mengunduh berkas mungkin gagal." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been re-inserted." msgstr "Pembaruan akan berlanjut ketika kabel USB perangkat telah ditancapkan kembali." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged and then re-inserted." msgstr "Pembaruan akan berlanjut ketika kabel USB perangkat telah dicabut dan kemudian ditancapkan kembali." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged." msgstr "Pembaruan akan berlanjut ketika kabel USB perangkat telah dicabut." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "Vendor tidak memberikan catatan rilis apa pun." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Ada perangkat yang bermasalah:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Tidak ada berkas firmware yang diblokir" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Tidak ada firmware yang disetujui." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "Firmware ini disediakan oleh anggota komunitas LVFS dan tidak disediakan (atau didukung) oleh vendor perangkat keras asli." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Paket ini belum divalidasi, mungkin tidak berfungsi dengan baik." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Program ini hanya dapat berfungsi dengan benar sebagai root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Remote ini berisi firmware yang tidak diembargo, tetapi masih sedang diuji oleh vendor perangkat keras. Anda harus memastikan Anda memiliki cara untuk menurunkan versi firmware secara manual jika pembaruan firmware gagal." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "Sistem ini tidak mendukung pengaturan firmware" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Sistem ini memiliki masalah runtime HSI." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Sistem ini memiliki tingkat keamanan HSI yang rendah." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Alat ini memungkinkan administrator untuk menerapkan pembaruan UEFI dbx." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Alat ini memungkinkan administrator untuk men-debug operasi UpdateCapsule." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Alat ini memungkinkan administrator untuk kuiri dan mengontrol daemon fwupd, yang memungkinkan mereka untuk melakukan tindakan seperti menginstal atau menurun-tingkatkan firmware." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Alat ini memungkinkan administrator untuk menggunakan plugin fwupd tanpa diinstal pada sistem host." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Alat ini hanya bisa dipakai oleh pengguna root" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Alat ini akan membaca dan mengurai log kejadian TPM dari firmware sistem." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Kegagalan transien" #. TRANSLATORS: item is TRUE msgid "True" msgstr "Benar" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Metadata tepercaya" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Payload tepercaya" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipe" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "Partisi UEFI ESP tidak terdeteksi atau dikonfigurasi" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Utilitas Firmware UEFI" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "Kunci Platform UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "Secure Boot UEFI" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "Pembaruan kapsul UEFI tidak tersedia atau diaktifkan dalam penyiapan firmware" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "Utilitas dbx UEFI" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "Firmware UEFI tidak dapat diperbarui dalam mode BIOS warisan" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "Kunci platform UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "Boot aman UEFI" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Tidak dapat terhubung ke layanan" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "Tidak dapat menemukan atribut" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Lepas ikatan driver saat ini" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Membuka blokir firmware:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Membuka blokir firmware tertentu agar tidak diinstal" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Tidak terenkripsi" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Tidak diketahui" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Perangkat Tidak Dikenal" msgid "Unlock the device to allow access" msgstr "Buka kunci perangkat untuk mengizinkan akses" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Tak terkunci" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Membuka kunci perangkat bagi akses firmware" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Lepas kait ESP" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Batalkan flag debugging selama pembaruan" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Payload yang Tidak Ditandatangani" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Versi daemon tidak didukung %s, versi klien adalah %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Tidak ternoda" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Dapat diperbarui" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Kesalahan Pembaruan" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Perbarui Image" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Pesan Pembaruan" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Keadaan Pembaruan" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Kegagalan pembaruan adalah masalah yang telah diketahui, kunjungi URL ini untuk informasi lebih lanjut:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Perbarui sekarang?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Pembaruan membutuhkan reboot" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Memperbarui hash kriptografi yang disimpan dengan konten ROM saat ini" msgid "Update the stored device verification information" msgstr "Mutakhirkan informasi verifikasi perangkat yang tersimpan" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Perbarui metadata yang disimpan dengan konten saat ini" msgid "Updating" msgstr "Memutakhirkan" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Memutakhirkan %s dari %s ke %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Memutakhirkan %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Meningkatkan %s dari %s ke %s?" #. TRANSLATORS: ask the user to share, %s is something #. * like: "Linux Vendor Firmware Service" #, c-format msgid "Upload these anonymous results to the %s to help other users?" msgstr "Unggah hasil anonim ini ke %s untuk membantu pengguna lain?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Mengunggah laporan firmware membantu vendor perangkat keras untuk dengan cepat mengidentifikasi pembaruan yang gagal dan berhasil pada perangkat nyata." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Urgensi" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Gunakan %s untuk bantuan" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Gunakan CTRL^C untuk membatalkan." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Gunakan fwupdtool --help untuk bantuan" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Gunakan flag quirk ketika menginstal firmware" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Pengguna telah diberitahu" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Nama Pengguna" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Valid" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Memvalidasi konten ESP…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Varian" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Vendor" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verifikasi…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Versi" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Versi[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "PERINGATAN" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Menunggu…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Awasi perubahan perangkat keras" #. TRANSLATORS: check various UEFI and ACPI tables are unchanged after the #. update msgid "Will measure elements of system integrity around an update" msgstr "Akan mengukur elemen integritas sistem seputar pembaruan" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Tulis firmware dari berkas ke dalam perangkat" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Tulis firmware dari berkas ke dalam suatu partisi" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Menulis berkas:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Menulis…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Distributor Anda mungkin belum memverifikasi pembaruan firmware untuk kompatibilitas dengan sistem Anda atau perangkat yang terhubung." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Perangkat keras Anda mungkin rusak menggunakan firmware ini, dan menginstal rilis ini dapat membatalkan garansi apa pun dengan %s." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "Sistem Anda disiapkan sebagai BKC dari %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[CHECKSUM]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[ID-PERANTI|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[ID-PERANTI|GUID] [CABANG]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[ID-PERANGKAT|GUID] [VERSI]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[BERKAS TTD_BERKAS ID-REMOTE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[NAMABERKAS1] [NAMABERKAS2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[ALASAN]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[PENGATURAN1] [ PENGATURAN2]..." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[PENGATURAN1] [PENGATURAN2] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[BERKAS-SMBIOS|BERKAS-HWIDS]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "baku" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "Utilitas log kejadian TPM fwupd" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "plugin fwupd" fwupd-1.9.16/po/it.po000066400000000000000000003071161460375044200143440ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Gianvito Cavasoli , 2016 # Milo Casagrande , 2017-2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Italian (http://app.transifex.com/freedesktop/fwupd/language/it/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: it\n" "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "Manca %.0f minuto" msgstr[1] "Mancano %.0f minuti" msgstr[2] "Mancano %.0f minuti" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "Aggiornamento BMC %s" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "Aggiornamento batteria %s" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "Aggiornamento microcode CPU %s" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "Aggiornamento fotocamera %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "Aggiornamento configurazione %s" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Aggiornamento Consumer ME di %s" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Aggiornamento unità di controllo %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Aggiornamento Coporate ME di %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Aggiornamento dispositivo %s" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "Aggiornamento schermo %s" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "Aggiornamento dock %s" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "Aggiornamento unità %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Aggiornamento unità di controllo integrata di %s" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "Aggiornamento lettore impronte %s" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "Aggiornamento unità dati %s" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "Aggiornamento GPU %s" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "Aggiornamento tavoletta grafica %s" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "Aggiornamento tastiera %s" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "Aggiornamento ME di %s" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "Aggiornamento mouse %s" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "Aggiornamento interfaccia di rete %s" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "Aggiornamento SSD %s" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "Aggiornamento controller di archiviazione %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Aggiornamento sistema %s" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "Aggiornamento TPM %s" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Aggiornamento unità di controllo Thunderbolt %s" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "Aggiornamento touchpad %s" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "Aggiornamento dock USB %s" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "Aggiornamento ricevitore USB %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Aggiornamento di %s" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s e tutti i dispositivi collegati potrebbero non essere utilizzabili durante l'aggiornamento." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "%s apparso: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "%s è cambiato: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "%s scomparso: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s non può essere aggiornato" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "Modalità costruttore %s" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s deve rimanere collegato durante l'aggiornamento per evitare possibili danni." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s deve rimanere collegato alla rete elettrica durante l'aggiornamento per evitare possibili danni." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "Override %s" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "Versione %s" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u giorno" msgstr[1] "%u giorni" msgstr[2] "%u giorni" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u dispositivo ha un aggiornamento firmware disponibile." msgstr[1] "%u dispositivi hanno un aggiornamento firmware disponibile." msgstr[2] "%u dispositivi hanno un aggiornamento firmware disponibile." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u dispositivo non dispone della configurazione migliore." msgstr[1] "%u dispositivi non dispongono della configurazione migliore." msgstr[2] "%u dispositivi non dispongono della configurazione migliore." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u ora" msgstr[1] "%u ore" msgstr[2] "%u ore" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u dispositivo locale supportato" msgstr[1] "%u dispositivi locali supportati" msgstr[2] "%u dispositivi locali supportati" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuto" msgstr[1] "%u minuti" msgstr[2] "%u minuti" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u secondo" msgstr[1] "%u secondi" msgstr[2] "%u secondi" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (soglia %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(obsoleto)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "Un PCR TPM è ora un valore non valido" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "Protezione replay firmware AMD" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "Protezione scrittura firmware AMD" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "Protezione rollback sicuro del processore AMD" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Azione richiesta:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Attiva dispositivi" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Attiva i dispositivi in attesa" msgid "Activate the new firmware on the device" msgstr "Attiva il nuovo firmware sul dispositivo" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Attivazione aggiornamento firmware" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Attivazione aggiornamento firmware per" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Età" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Accettare e abilitare il remoto?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias di %s" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "Tutti i PCR TPM non sono validi" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Tutti i PCR TPM sono validi" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Tutti i dispositivi dello stesso tipo saranno aggiornati allo stesso tempo" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Consente di tornare alle precedenti versioni del firmware" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Consente la re-installazione di versioni esistenti del firmware" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Consente il cambio del ramo firmware" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Ramo alternativo" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "Aggiornamento in corso" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Per essere completato, un aggiornamento richiede un riavvio." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Per essere completato, un aggiornamento richiede lo spegnimento del sistema." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Risponde affermativamente a tutte le domande" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Applica aggiornamenti firmware" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Applica aggiornamento anche se non consigliato" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Applica file di aggiornamento" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Applicazione aggiornamento…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Firmware approvato:" msgstr[1] "Firmware approvati:" msgstr[2] "Firmware approvati:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Chiedere ancora la prossima volta?" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Collega in modalità firmware" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Autenticazione…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Sono richiesti i dettagli di autenticazione" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "È richiesto autenticarsi per tornare al precedente firmware su un dispositivo rimovibile" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "È richiesto autenticarsi tornare al precedente firmware su questa macchina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "È richiesto autenticarsi per modificare le impostazioni BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "È richiesto autenticarsi per modificare un remoto configurato utilizzato per aggiornamenti firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "È richiesto autenticarsi per modificare la configurazione del demone" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "È richiesto autenticarsi per leggere le impostazioni BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "È richiesto autenticarsi per impostare l'elenco dei firmware approvati" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "È richiesto autenticarsi per firmare i dati utilizzando il certificato del client" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "È richiesto autenticarsi per passare alla nuova versione del firmware " #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "È richiesto autenticarsi per sbloccare un dispositivo" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "È richiesto autenticarsi per aggiornare il firmware su un dispositivo rimovibile" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "È richiesto autenticarsi per aggiornare il firmware su questa macchina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "È richiesto autenticarsi per aggiornare il codice di controllo del dispositivo salvato" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Rapporti automatici" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Caricare automaticamente ogni volta?" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "Protezione rollback BIOS" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "Protezione rollback BIOS" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "Aggiornamenti BIOS forniti tramite LVFS o Windows Update" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BUILDER-XML NOMEFILE-DST" msgid "BYTES" msgstr "BYTE" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Batteria" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Vincola in nuovo driver kernel" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "File di firmware bloccati:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Versione bloccata" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Firmware bloccato:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Blocca un firmware specifico così da non installarlo" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Versione bootloader" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Ramo" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Compila una file firmware" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Annulla" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Annullato" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Impossibile applicare poiché l'aggiornamento dbx è già stato applicato." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Impossibile applicare gli aggiornamenti su supporti live" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Modificato" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Verifica che l'hash crittografico corrisponda col firmware" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Codice di controllo" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Scegliere un ramo" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Scegliere un dispositivo" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Scegliere un firmware" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Scegliere un rilascio" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Scegliere ESP:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Scegliere un volume" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Pulisce i risultati dell'ultimo aggiornamento" #. TRANSLATORS: error message msgid "Command not found" msgstr "Comando non trovato" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Supportata dalla comunità" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "La configurazione può essere letta solamente dall'amministratore" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Converte un file firmware" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Creato" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Critica" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "È disponibile la verifica dell'hash crittografico" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Valore attuale" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Versione attuale" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "ID-DISPOSITIVO|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Strumento DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opzioni di debug" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Estrazione…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Descrizione" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Scollega in modalità bootloader" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Dettagli" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "Allontanarsi dalla migliore configurazione nota?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Flag dispositivo" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "ID dispositivo" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Dispositivo aggiunto:" #. TRANSLATORS: the device is already connected msgid "Device already exists" msgstr "Il dispositivo esiste già" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "La batteria del dispositivo è troppo bassa" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "La carica della batteria del dispositivo è troppo bassa (%u%%, richiesto %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Il dispositivo è in grado di correggere problemi di flash" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "Il dispositivo non può essere utilizzato mentre il coperchio è chiuso" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Dispositivo modificato:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "Emulazione dispositivo non abilitata." #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "È richiesto il firmware dispositivo per eseguire un controllo della versione" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "Il dispositivo è emulato" #. TRANSLATORS: device cannot be interrupted, for instance taking a phone call msgid "Device is in use" msgstr "Il dispositivo è in uso" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Il dispositivo è bloccato" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "È richiesto un dispositivo per installare tutti i rilasci forniti" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Il dispositivo non è raggiungibile" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "Il dispositivo non è raggiungibile o fuori portata" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Il dispositivo è utilizzabile per la durata dell'aggiornamento" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "Il dispositivo è in attesa del completamento dell'aggiornamento" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Dispositivo rimosso:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "Il dispositivo richiede di essere collegato all'alimentazione elettrica" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "Il dispositivo necessita di una licenza software per aggiornare" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Il dispositivo applica gli aggiornamenti a fasi" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "Il dispositivo supporta il passaggio a un ramo diverso del firmware" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Metodo di aggiornamento del dispositivo" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "L'aggiornamento del dispositivo richiede l'attivazione" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Il dispositivo eseguirà un backup del firmware prima dell'installazione" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Il dispositivo non comparirà nuovamente dopo l'aggiornamento" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Dispositivi aggiornati con successo:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Dispositivi non aggiornati correttamente:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Dispositivi senza aggiornamenti firmware:" #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Dispositivi con l'ultima versione disponibile del firmware:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "Nessun dispositivo trovato con GUID corrispondenti" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Disabilitato" msgid "Disabled fwupdate debugging" msgstr "Debug fwupdate disabilitato" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Disabilita un remoto dato" #. TRANSLATORS: command line option msgid "Display version" msgstr "Visualizza la versione" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Distribuzione" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Non controlla i metadati vecchi" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Non controlla la cronologia non segnalata " #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Non controlla se lo scaricamento dai remoti deve essere abilitato" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Non controlla o chiede se è necessario riavviare dopo un aggiornamento" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Non include il prefisso del dominio" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Non include il prefisso della marcatura temporale" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Non esegue i controlli di sicurezza del dispositivo" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "Non chiede i dispositivi" #. TRANSLATORS: command line option msgid "Do not prompt to fix security issues" msgstr "Non richiede di risolvere problemi di sicurezza" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "Non cerca il firmware durante l'analisi" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Non scrive la cronologia" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Le conseguenze del cambio di ramo del firmware sono state comprese?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Disabilitare questa funzionalità per i prossimi aggiornamenti?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "Abilitarlo ora?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Aggiornare questo remoto ora?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Caricare automaticamente i resoconti per i prossimi aggiornamenti?" #. TRANSLATORS: command line option msgid "Don't prompt for authentication (less details may be shown)" msgstr "Non chiede autenticazione (potrebbero essere mostrati meno dettagli)" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Fatto." #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Arretrare %s da %s a %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Torna a una vecchia versione del firmware su un dispositivo" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Arretramento di %s da %s a %s..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Arretramento di %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Scarica un file" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Scaricamento…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Scarica i dati SMBIOS da un file" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Durata" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "ESP specificata non era valida" #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "Emula un dispositivo usando un manifesto JSON" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Emulato" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Host emulato" msgid "Enable" msgstr "Abilita" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Abilita il supporto all'aggiornamento firmware sui sistemi compatibili" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Abilitare il nuovo remoto?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Abilitare questo remoto?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Abilitato" msgid "Enabled fwupdate debugging" msgstr "Debug fwupdate abilitato" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Abilitato se corrisponde all'hardware" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Abilita un remoto dato" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Abilitare questa funzionalità a proprio rischio: in caso di problemi con gli aggiornamenti sarà necessario contattare l'OEM. Solamente i problemi legati al processo di aggiornamento possono essere inviati a $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Abilitare questo remoto a proprio rischio." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Cifrato" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "RAM cifrata" #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Fine vita" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Enumerazione" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Elimina tutta la cronologia degli aggiornamenti firmware" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Eliminazione…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Esce dopo una breve attesa" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Esce dopo che il motore è stato caricato" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Esporta la struttura di un file firmware in XML" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Estrae un blob firmware in immagini" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "FILE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "FILE [ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "NOMEFILE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "NOMEFILE CERTIFICATO CHIAVE-PRIVATA" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "NOMEFILE NOME-ALT-DISPOSITIVO|ID-ALT-DISPOSITIVO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "NOMEFILE NOME-ALT-DISPOSITIVO|ID-ALT-DISPOSITIVO [NOME-ALT-IMMAGINE|ID-ALT-IMMAGINE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "NOMEFILE ID-DISPOSITIVO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "FILENAME OFFSET DATA [TIPO-FIRMWARE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "NOMEFILE [ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "NOMEFILE [TIPO-FIRMWARE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "NOMEFILE-SRC NOMEFILE-DST [TIPO-FIRMWARE-SRC] [TIPO-FIRMWARE-DST]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "NOMEFILE|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Non riuscito" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Applicazione aggiornamento non riuscita" #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Connessione al demone non riuscita" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Recupero dei dispositivi in attesa non riuscito" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Installazione aggiornamento firmware non riuscita" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Caricamento dbx locale non riuscito" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Caricamento stranezze non riuscito" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Caricamento dbx di sistema non riuscito" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Blocco non riuscito" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Analisi degli argomenti non riuscita" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Lettura del file non riuscita" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Analisi del flag --filter non riuscita" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Lettura dbx locale non riuscita" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Riavvio non riuscito" #. TRANSLATORS: a feature is something like "can show an image" msgid "Failed to set front-end features" msgstr "Impostazione delle funzionalità del front-end non riuscita" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Impostazione della modalità splash non riuscita" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Verifica contenuti ESP non riuscita" #. TRANSLATORS: item is FALSE msgid "False" msgstr "Falso" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Nome file" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Firma nome file" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Sorgente nome file" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Nome file richiesto" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filtra tramite un insieme di flag utilizzando ~ per escludere, per esempio «internal, ~needs-reboot»" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Convalida firmware" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Descrittore BIOS firmware" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Regione BIOS firmware" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "URI di base del firmware" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Servizio D-Bus di aggiornamento firmware" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Demone di aggiornamento firmware" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Verifica aggiornamento firmware" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Aggiornamenti firmware" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Strumento gestione firmware" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Protezione scrittura firmware" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Blocco protezione scrittura firmware" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Convalida firmware" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Il firmware è già bloccato" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Il firmware non è bloccato" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "I metadati del firmware non sono stati controllati per %u giorno e potrebbero non essere aggiornati." msgstr[1] "I metadati del firmware non sono stati controllati per %u giorni e potrebbero non essere aggiornati." msgstr[2] "I metadati del firmware non sono stati controllati per %u giorni e potrebbero non essere aggiornati." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Aggiornamenti firmware" msgid "Firmware updates are not supported on this machine." msgstr "Gli aggiornamenti firmware non sono supportati su questo dispositivo." msgid "Firmware updates are supported on this machine." msgstr "Gli aggiornamenti firmware sono supportati su questo dispositivo." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Aggiornamenti firmware disabilitati; eseguire «fwupdmgr unlock» per abilitarli" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Flag" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Forza l'azione riducendo alcuni controlli runtime" msgid "Force the action ignoring all warnings" msgstr "Forza l'azione ignorando gli avvisi" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Trovato" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Rilevata cifratura del disco" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "I segreti di cifratura del disco potrebbero essere invalidati durante l'aggiornamento" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "Piattaforma saldata" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Piattaforma saldata" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID" msgstr[2] "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "GUID|DEVICE-ID" msgstr "GUID|ID-DISPOSITIVO" msgid "Get BIOS settings" msgstr "Recupero impostazioni BIOS" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Ottiene tutti i flag dispositivo supportati da fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Ottiene tutti i dispositivi che supportano gli aggiornamenti del firmware" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Recupera tutti i plugin registrati nel sistema" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Ottiene le informazioni su un file di firmware" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Ottiene i remoti configurati" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Recupera gli attributi di sicurezza dell'host" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Recupera l'elenco dei firmware approvati" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Recupera l'elenco del firmware bloccato" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Ottiene l'elenco degli aggiornamenti per l'hardware connesso" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Ottiene i rilasci di un dispositivo" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Ottiene i risultati dell'ultimo aggiornamento" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "FILE-HWIDS" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "L'hardware è in attesa di essere ricollegato" #. TRANSLATORS: the release urgency msgid "High" msgstr "Elevata" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Eventi sicurezza host" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "ID sicurezza host:" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "Protezione IOMMU" #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "Protezione dispositivo IOMMU disabilitata" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "Protezione dispositivo IOMMU abilitata" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Inattivo…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ignora controlli SSL nello scaricare i file" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Ignora i checksum del firmware non riusciti" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Ignora corrispondenze errate firmware hardware" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignora i controlli di sicurezza di validazione" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Controlli SSL ignorati, per fare ciò automaticamente in futuro, esportare DISABLE_SSL_STRICT nel proprio ambiente" #. TRANSLATORS: the inhibit ID is a short string like dbus-123456 #, c-format msgid "Inhibit ID is %s." msgstr "L'ID d'inibizione è %s" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Tempo di installazione" #. TRANSLATORS: command description msgid "Install a firmware file in cabinet format on this hardware" msgstr "Installa un file di firmware in formato cabinet su questo hardware" #. TRANSLATORS: command description msgid "Install a specific firmware file on all devices that match" msgstr "Installa un file di firmware specifico su tutti i dispositivi corrispondenti" msgid "Install old version of signed system firmware" msgstr "Installa versione precedente del firmware firmato di sistema" msgid "Install old version of unsigned system firmware" msgstr "Installa versione precedente del firmware non firmato di sistema" msgid "Install signed device firmware" msgstr "Installa firmware firmato del dispositivo" msgid "Install signed system firmware" msgstr "Installa firmware firmato di sistema" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Installare sul dispositivo genitore prima" msgid "Install unsigned device firmware" msgstr "Installa firmware non firmato del dispositivo" msgid "Install unsigned system firmware" msgstr "Installa firmware non firmato di sistema" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Installazione firmware…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Installazione aggiornamento firmware…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Installazione su %s…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "L'installazione dell'aggiornamento potrebbe annullare la garanzia del dispositivo." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Intero" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "ACM protetto Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "ACM protetto da Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Regole di errore Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Fusibile Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Fusibile OTP Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Avvio verificato Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Regole di errore Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Avvio verificato da Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET attivo" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET abilitato" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS Mitigation" msgstr "Mitigazione GDS Intel" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS mitigation" msgstr "Mitigazione GDS Intel" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Modalità costruttore Intel Management Engine" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Disabilitazione Intel Management Engine" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Versione Intel Management Engine" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Dispositivo interno" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Non valido" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Parametri non validi" #. TRANSLATORS: error message msgid "Invalid arguments, expected GUID" msgstr "Parametro non valido, atteso GUID" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "È retrocessione" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "È in modalità bootloader" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "È aggiornamento" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Problema" msgstr[1] "Problemi" msgstr[2] "Problemi" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "CHIAVE,VALORE" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "Il kernel è di nuovo integro" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "Il kernel non è integro" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Lockdown kernel disabilitato" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Lockdown kernel abilitato" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Portachiavi" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "POSIZIONE" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Ultima modifica" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Manca meno di un minuto" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licenza" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Lockdown kernel Linux" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Verifica kernel Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Swap Linux" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (firmware stabile)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (firmware in prova)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Kernel Linux" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Lockdown kernel Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Swap Linux" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Elenca le voci nel dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Elenca gli aggiornamenti firmware supportati" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Elenca tutti i tipi di firmware disponibili" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Elenca i file nell'ESP" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "Caricato da un module esterno" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Caricamento…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Bloccato" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Bassa" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "Manifesto chiave MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "Manifesto chiave MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "Modalità costruttore MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "Override MEI" msgid "MEI version" msgstr "Versione MEI" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Abilita manualmente i plugin selezionati" #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Lunghezza massima" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Valore massimo" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Media" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Firma metadati" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI metadati" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadati possono essere scaricati da Linux Vendor Firmware Service." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Versione minima" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Lunghezza minima" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Valore minimo" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Versioni di demone e client non corrispondenti, usare %s" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Modifica il valore della configurazione del demone" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifica un remoto" msgid "Modify a configured remote" msgstr "Modifica un remoto configurato" msgid "Modify daemon configuration" msgstr "Modifica la configurazione del demone" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Controlla il demone per gli eventi" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Monta l'ESP" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Necessita un riavvio dopo l'installazione" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Richiesto riavvio" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Necessita l'arresto dopo l'installazione" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Nuova versione" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Nessuna azione specificata." #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Nessuna versione precedente per %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Nessun ID firmware trovato" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "Nessun firmware trovato" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Non è stato rilevato nessun hardware con capacità di aggiornamento del firmware" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Non è stato trovato alcun plugin" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Nessuna versione disponibile" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Non è abilitato alcun remoto e non sono disponibili metadati." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Nessun server disponibile" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Nessun dispositivo aggiornabile" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Nessun aggiornamento disponibile" msgid "No updates available for remaining devices" msgstr "Nessun aggiornamento disponibile per i dispositivi rimanenti" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Non è stato applicato alcun aggiornamento" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Non approvata" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Non trovato" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Non supportato" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "Fatto" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "Ok" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Versione precedente" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Mostra solo il valore PCR" #. TRANSLATORS: command line option msgid "Only use peer-to-peer networking when downloading files" msgstr "Usa solo reti peer-to-peer per scaricare i file" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Sono consentiti solo avanzamenti di versione" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Output in formato JSON" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Sovrascrive il percorso ESP predefinito" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Firmware" msgstr "Firmware P2P" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Metadata" msgstr "Metadati P2P" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "PERCORSO" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Legge e mostra i dettagli riguardo a un file firmware" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Lettura aggiornamento dbx…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Lettura dbx di sistema…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Password" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Esegue una patch di un blob firmware a un offset noto" msgid "Payload" msgstr "Carico" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "In attesa" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Percentuale completamento" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Eseguire l'operazione?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Debug piattaforma" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Debug piattaforma" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Assicurarsi di avere la chiave di ripristino prima di continuare." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Inserire un numero tra 0 e %u:" #. TRANSLATORS: the user isn't reading the question -- please don't translate #. * 'Y' or 'N' as these are hardcoded msgid "Please enter either Y or N: " msgstr "Digitare Y o N:" #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Dipendenze plugin mancanti" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Valori possibili" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "Protezione DMA pre-boot" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Protezione DMA pre-boot" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "La protezione pre-boot DMA non è abilitata" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "La protezione pre-boot DMA è abilitata" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Versione precedente" msgid "Print the version number" msgstr "Stampa il numero di versione" msgid "Print verbose debug statements" msgstr "Stampa messaggi di debug prolissi" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Priorità" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Errori" msgid "Proceed with upload?" msgstr "Procedere con il caricamento?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Controlli di sicurezza del processore" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Protezione rollback del processore" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Proprietaria" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Interroga il supporto per gli aggiornamenti firmware" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "ID-REMOTO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "ID-REMOTO CHIAVE VALORE" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Sola lettura" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Legge un blob firmware da un dispositivo" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Legge un firmware da un dispositivo" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Legge il firmware dal dispositivo in un file" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Legge il firmware da una partizione in un file" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Lettura da %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Lettura…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Riavvio…" #. TRANSLATORS: how often we should refresh the metadata msgid "Refresh Interval" msgstr "Intervallo aggiornamento" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Ricarica i metadati dal server remoto" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Reinstallare %s con %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Installa nuovamente il firmware attuale sul dispositivo" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Installa nuovamente il firmware su un dispositivo" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Reinstallazione di %s con %s..." #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Ramo di rilascio" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Flag di rilascio" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "ID release" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID remoto" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Sostituisce i dati su un file di firmware esistente" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI del rapporto" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Segnalato al server remoto" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Richiesta annullata" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "Il file system richiesto efivarfs non è stato trovato" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "L'hardware richiesto non è stato trovato" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Richiede un bootloader" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Richiede una connessione a Internet" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Riavviare ora?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Riavviare il demone per applicare le modifiche?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Riavvio del dispositivo…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Fornisce tutti gli ID hardware per un dispositivo" #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Per maggiori informazioni eseguire «fwupdmgr get-upgrades»." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Esegue la procedura di pulizia del plugin quando si utilizza install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Esegue la procedura di preparazione del plugin quando si utilizza install-blob" #. TRANSLATORS: tell a user how to get information #, c-format msgid "Run without '%s' to see" msgstr "Eseguire senza «%s» per vedere" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "La versione del kernel in esecuzione è troppo vecchia" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Suffisso di runtime" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "IMPOSTAZIONE VALORE" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "Descrittore BIOS SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "Regione BIOS SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "Blocco SPI" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "Protezione replay SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "Scrittura SPI" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "Protezione scrittura SPI" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "SOTTOSISTEMA DRIVER [ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Salva una file che consente di generare ID hardware" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "Incremento scalare" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Pianifica l'installazione al prossimo riavvio quando è possibile" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Pianificazione…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Secure boot disabilitato" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Secure boot abilitato" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Per maggiori informazioni, consultare %s." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Per maggiori informazioni, consultare %s." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Dispositivo selezionato" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Volume selezionato" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Numero di serie" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "Configura l'impostazione BIOS «%s» utilizzando «%s»." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "Imposta un parametro del BIOS" msgid "Set one or more BIOS settings" msgstr "Configura una o più impostazioni BIOS" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Imposta il flag di debug durante l'aggiornamento" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "Configura una o più impostazioni BIOS" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Imposta l'elenco dei firmware approvati" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Tipo di impostazione" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Le impostazioni saranno applicate dopo il riavvio" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Condivide la cronologia del firmware con gli sviluppatori" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Mostra tutti i risultati" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Mostra la versione del client e del demone" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Mostra informazioni prolisse del demone per un dominio" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Mostra informazioni di debug per tutti i domini" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Mostra le opzioni di debug" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Mostra dispositivi non aggiornabili" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Mostra maggiori informazioni di debug" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Mostra la cronologia degli aggiornamenti firmware" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Mostra la versione calcolata del dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Mostra debug dell'ultimo tentativo di aggiornamento" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Mostra informazioni sullo stato degli aggiornamenti firmware" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Spegnere ora?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Firma un firmware con una chiave nuova" msgid "Sign data using the client certificate" msgstr "Firma i dati col certificato del client" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Firma i dati col certificato del client" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Firma i dati caricati col certificato del client" msgid "Signature" msgstr "Firma" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Dati firmati" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Dimensione" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Alcuni dei segreti della piattaforma potrebbero essere invalidati durante l'aggiornamento del firmware" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Sorgente" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Specifica vendor/ID prodotto di un dispositivo DFU" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Indica il file del database dbx" msgid "Specify the number of bytes per USB transfer" msgstr "Specifica il numero di byte per trasferimento USB" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "Stringa" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Completato" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Attivazione di tutti i dispositivi avvenuta con successo" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Remoto disabilitato con successo" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Nuovi metadati scaricati con successo:" #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Remoto abilitato e aggiornato con successo" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Remoto abilitato con successo" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmware installato con successo" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Valore della configurazione modificato con successo" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Remoto modificato con successo" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metadati aggiornati manualmente con successo" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Codici di controllo del dispositivo aggiornati con successo" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "%u rapporto caricato con successo" msgstr[1] "%u rapporti caricati con successo" msgstr[2] "%u rapporti caricati con successo" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Codici di controllo del dispositivo verificati con successo" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Riepilogo" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Supportato" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "CPU supportata" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Supportato sul server remoto" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Sospensione" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Sospensione in RAM" #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Suspend-to-idle" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Suspend-to-ram" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Passare dal ramo %s a %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Cambia il ramo del firmware sul dispositivo" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "Il sistema richiede una sorgente elettrica esterna" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TESTO" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "Ricostruzione PCR0 TPM" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "La ricostruzione PCR0 TPM non è valida" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "La ricostruzione PCR0 TPM è ora valida" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "Configurazione piattaforma TPM" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "Ricostruzione TPM" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "PCR TPM vuoti" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Tag" msgstr[1] "Tag" msgstr[2] "Tag" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "Marcato per l'emulazione" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Non integro" msgid "Target" msgstr "Obiettivo" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Verifica un dispositivo usando un manifesto JSON" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Testato" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "Testato da %s" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS è un servizio gratuito che opera come entità legale indipendente e non ha alcun legame con $OS_RELEASE:NAME$. Il distributore potrebbe non aver verificato la compatibilità degli aggiornamenti firmware col proprio sistema o con i propri dispositivi collegati. Il firmware viene fornito solamente dall'OEM." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "TPM PCR0 è diverso dalla ricostruzione." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "Il demone ha caricato codice di terze parti e non è più supportato dagli sviluppatori." #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "La versione del dispositivo non corrisponde: trovato %s, atteso %s" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "Il firmware su %s non è fornito da %s, il fornitore dell'hardware." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "L'orologio di sistema non è stato impostato correttamente e lo scaricamento di file potrebbe non riuscire." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "Il produttore non ha fornito note di rilascio." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Dispositivi con problemi:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Non ci sono file di firmware bloccati" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Non ci sono firmware approvati." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "Questo dispositivo verrà retrocesso alla versione %s all'esecuzione del comando %s. " #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "Questo firmware è fornito dalla comunità LVFS e non viene fornito o supportato dal produttore originale dell'hardware." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Questo pacchetto non è stato verificato, potrebbe non funzionare correttamente." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Questo programma può funzionare correttamente solo come utente root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Questo remoto contiene firmware che non è bloccato, ma è ancora in fase di verifica dal produttore hardware. Assicurarsi di poter ripristinare, manualmente o con altre procedure, il vecchio firmware nel caso in cui l'aggiornamento non riuscisse." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "Questo sistema non supporta le configurazioni del firmware" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Questo sistema presenta dei problemi di runtime HSI." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Questo sistema ha un livello di sicurezza HSI basso." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Questo strumento consente a un amministratore di applicare aggiornamenti UEFI dbx." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Questo strumento consente a un amministratore di eseguire debug sulle operazioni UpdateCapsule." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Questo strumento consente a un amministratore di inviare richieste e controllare il demone fwupd per eseguire azioni come l'installazione o la retrocessione del firmware." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Questo strumento consente a un amministratore di utilizzare i plugin fwupd senza che siano installati sul sistema host." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Questo strumento può essere usato solamente dall'utente root" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Questo strumento legge e analizza il registro eventi TPM dal firmware di sistema." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Errore transitorio" #. TRANSLATORS: item is TRUE msgid "True" msgstr "Vero" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Metadati verificati" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Dati verificati" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipo" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI Bootservice Variables" msgstr "Variabili bootservice UEFI" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "Partizione ESP UEFI non rilavata o non configurata" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Strumento firmware UEFI" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "Chiave piattaforma UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "Avvio sicuro UEFI" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI bootservice variables" msgstr "Variabili bootservice UEFI" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "Aggiornamenti capsula UEFI non disponibili o non abilitati nella configurazione firmware" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "Strumento EUFI dbx" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "Impossibile aggiornare il firmware UEFI in modalità BIOS legacy" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "Chiave piattaforma UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "Avvio sicuro UEFI" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Impossibile collegarsi al servizio" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "Impossibile trovare l'attributo" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Svincola il driver attuale" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Sblocco del firmware:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Sblocca un firmware specifico dal non essere installato" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Non cifrato" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Sconosciuto" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Dispositivo sconosciuto" msgid "Unlock the device to allow access" msgstr "Sblocco del dispositivo per consentire l'accesso" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Sbloccato" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Sblocca il dispositivo per accedere al firmware" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Smonta l'ESP" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Ripristina il flag di debug durante l'aggiornamento" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Dati non firmati" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Demone versione %s non supportato, la versione del client è %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Integro" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Aggiornabile" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Errore aggiornamento" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Aggiorna immagine" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Messaggio aggiornamento" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Stato aggiornamento" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Questo è un problema noto, consultare il seguente URL per maggiori informazioni:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Aggiornare ora?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "L'aggiornamento richiede il riavvio" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Aggiorna l'hash crittografico archiviato con il contenuto della ROM" msgid "Update the stored device verification information" msgstr "Aggiornamento delle informazioni di verifica del dispositivo salvate" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Aggiorna i metadati salvati con il contenuto attuale" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "Aggiorna tutti i dispositivi specificati all'ultima versione firmware o tutti i dispositivi se non specificato" msgid "Updating" msgstr "Aggiornamento in corso" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Aggiornamento di %s da %s a %s..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Aggiornamento di %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Aggiornare %s da %s a %s?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Inviare resoconti sul firmware aiuta gli sviluppatori a identificare velocemente aggiornamenti eseguiti con successo o non riusciti su dispositivi reali." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Urgenza" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Usare %s per l'aiuto" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Usare CTRL^C per annullare." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Usare «fwupdtool --help» per maggiori informazioni" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Usa flag quirk nell'installare il firmware" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Notifica inviata all'utente" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Nome utente" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Valido" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Verifica contenuti ESP…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variante" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Fornitore" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verifica…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Versione" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Versione[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "AVVERTENZA" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Attesa…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Controlla le modifiche hardware" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Scrive il firmware dal file nel dispositivo" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Scrive il firmware dal file in una partizione" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Scrittura file:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Scrittura…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "La propria distribuzione potrebbe non aver verificato la compatibilità degli aggiornamenti firmware col proprio sistema o col dispositivo collegato." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Utilizzando questo firmware, l'hardware potrebbe danneggiarsi; inoltre, l'installazione di questa versione potrebbe annullare la garanzia con %s." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "Il sistema è impostato per il BKC di %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[CHECKSUM]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[ID-DISPOSITIVO|GUID] [BRANCH]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[ID|GUID-DISPOSITIVO] [VERSIONE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[FILE SIG_FILE ID-REMOTO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[NOMEFILE1] [NOMEFILE2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[MOTIVO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[IMPOSTAZIONE1] [ IMPOSTAZIONE2]..." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[FILE-SMBIOS|FILE-HWIDS]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "predefinito" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "Strumento registro eventi TPM fwupd" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "Plugin fwupd" fwupd-1.9.16/po/its/000077500000000000000000000000001460375044200141575ustar00rootroot00000000000000fwupd-1.9.16/po/its/appdata.its000066400000000000000000000013501460375044200163110ustar00rootroot00000000000000 fwupd-1.9.16/po/its/appdata.loc000066400000000000000000000005121460375044200162660ustar00rootroot00000000000000 fwupd-1.9.16/po/ja.po000066400000000000000000000221341460375044200143140ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Green , 2021 # Nobuhiro Iwamatsu , 2021 # Takuro Onoue , 2021 # YOSHIDUMI, Rentaro, 2021 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Japanese (http://app.transifex.com/freedesktop/fwupd/language/ja/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ja\n" "Plural-Forms: nplurals=1; plural=0;\n" msgid "Activate the new firmware on the device" msgstr "機器上で新しいファームウェアを実行可能にする" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "%sの別名" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "外付け機器でファームウェアをダウングレードするには認証が必要です" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "この機器のファームウェアをダウングレードするには認証が必要です。" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "ファームウェア更新用の遠隔構成を変更するには認証が必要です" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "デーモン構成の変更には認証が必要です" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "認証済みファームウェアの一覧を設定するには認証が必要です" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "クライアント証明書を用いてデータに署名するには認証が必要です" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "新版のファームウェアに切り替えるには認証が必要です" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "機器を開錠するには認証が必要です" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "外付け機器でファームウェアを更新するには認証が必要です" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "当機でファームウェアを更新するには認証が必要です" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "保存された機器の検査合計を更新するには認証が必要です" msgid "BYTES" msgstr "バイト数" #. TRANSLATORS: error message msgid "Command not found" msgstr "コマンドが見付かりません" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU ユーティリティ" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "この機能を有効にすることは、お客様自身の責任で行ってください。つまり、これらの更新によって発生した問題については、製造元のメーカーに連絡する必要があります。更新プロセス自体に関する問題だけを $OS_RELEASE:BUG_REPORT_URL$ へ提出してください。" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "ファイル名" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "ファイル名 機器の代替名|機器の代替ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "ファイル名 機器の代替名|機器の代替ID [イメージの代替名|イメージの代替ID]" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "引数の解析に失敗しました" msgid "Force the action ignoring all warnings" msgstr "警告を全て無視して処理を強行" msgid "Install signed device firmware" msgstr "署名済みデバイスファームウェアを導入する" msgid "Install signed system firmware" msgstr "署名済みシステムファームウェアを導入する" msgid "Install unsigned device firmware" msgstr "署名のないデバイスファームウェアを導入する" msgid "Install unsigned system firmware" msgstr "署名のないシステムファームウェアを導入する" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux ベンダーファームウェアサービス (安定版ファームウェア)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux ベンダーファームウェアサービス (試験版ファームウェア)" msgid "Modify a configured remote" msgstr "遠隔構成を変更する" msgid "Modify daemon configuration" msgstr "デーモン構成を変更する" msgid "Print the version number" msgstr "版数を表示する" msgid "Print verbose debug statements" msgstr "冗長な診断明細を表示する" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "ファームウェアを機器からファイルに読み込む" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "ファームウェアをひとつの領域からファイルに読み込む" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "既存のファームウェアファイル内のデータを置き換えます" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "認証済みファームウェアの一覧を設定する" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "追加のデバッグ情報を表示します" msgid "Sign data using the client certificate" msgstr "クライアント証明書を用いてデータに署名する" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "クライアント証明書を用いてデータに署名する" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "DFU機器のベンダーまたは製品IDを指定してください" msgid "Specify the number of bytes per USB transfer" msgstr "USB伝送器ごとのバイト数を指定する" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS は独立した法人として機能する無料のサービスであり、$OS_RELEASE:NAME$ とは関係ありません。ディストリビューターは、システムまたは接続されているデバイスとの互換性について、ファームウェアの更新を確認していない可能性があります。すべてのファームウェアは、元の機器の製造元からのみ提供されています。" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "この遠隔側ソースには、輸出禁止ではないファームウェアが含まれていますが、ハードウェアベンダーによって未だテスト中です。ファームウェアの更新に失敗した場合は、ファームウェアを手動でダウングレードする方法を確保しておく必要があります。" msgid "Unlock the device to allow access" msgstr "アクセスを許可するために機器を開錠してください" msgid "Update the stored device verification information" msgstr "保存された機器の検証情報を更新する" msgid "VID:PID" msgstr "ベンダーID:製品ID" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "ファームウェアをファイルから機器に書き込む" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "ファームウェアをファイルから 1 つの領域に書き込む" fwupd-1.9.16/po/ka.po000066400000000000000000001517631460375044200143300ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Georgian (http://app.transifex.com/freedesktop/fwupd/language/ka/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ka\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "დარჩენილია %.0f წუთი" msgstr[1] "დარჩენილია %.0f წუთი" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "%s BMC -ის განახლება" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "%s ელემენტის განახლება" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "%s CPU მიკროკოდის განახლება" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "%s კამერის განახლება" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "%s კონფიგურაციის განახლება" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s მომხმარებლის ME-ის განახლება" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s კონტროლერის განახლება" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s კორპორატიული ME-ის განახლება" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s მოწყობილობის განახლება" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "%s ეკრანის განახლება" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "%s დოკის განახლება" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "%s დისკის განახლება" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s ჩადგმული კონტროლერის განახლება" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "%s Flash დისკის განახლება" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "%s GPU -ის განახლება" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "%s კლავიატურის განახლება" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s ME-ის განახლება" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "%s თაგუნას განახლება" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "%s ქსელის ინტერფეისის განახლება" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "%s SSD -ის განახლება" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "%s საცავის კონტროლერის განახლება" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s სისტემის განახლება" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "%s TPM განახლება" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s Thunderbolt კონტროლერის განახლება" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "%s თაჩპედის განახლება" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "%s USB დოკის განახლება" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "%s USB მიმღების განახლება" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s განახლება" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s ამჟამად განახლებადი არაა" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s დამზადების რეჟიმი" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s გადაფარვა" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s ვერსია" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u დღე" msgstr[1] "%u დღე" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u საათი" msgstr[1] "%u საათი" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minute" msgstr[1] "%u minutes" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u წამი" msgstr[1] "%u წამი" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(მოძველებული)" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "AMD-ის მიკროკოდის თავიდან ჩაწერისგან დაცვა" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "AMD-ის მიკროკოდში ჩაწერისგან დაცვა" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "საჭირო ქმედება:" msgid "Activate the new firmware on the device" msgstr "მოწყობილობაზე ახალი მიკროკოდის აქტივაცია" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "მიკროკოდის განახლების აქტივაცია" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "ასაკი" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "%s-ის მეტსახელი" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "ერთი ტიპის მოწყობილობების ერთდროულად განახლდება" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "ალტერნატიული ბრენჩი" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "მიკროკოდის განახლებების გადატარება" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "განახლების ფალების გადატარება" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "განახლების გადატარება…" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "ავთენტიკაცია…" msgid "BYTES" msgstr "BYTES" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "ელემენტი" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "დაბლოკილი ვერსია" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "ჩამტვირთავი კოდის ვერსია" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "ბრენჩი" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "გაუქმება" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "შეწყვეტილია" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "შეცვლილია" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "საკონტროლო რიცხვი" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "აირჩიეთ ბრენჩი" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "აირჩიეთ მოწყობილობა" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "აირჩიეთ მიკროკოდი" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "აირჩეთ ESP:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "აირჩიეთ ტომი" #. TRANSLATORS: error message msgid "Command not found" msgstr "ბრძანება ვერ ვიპოვე" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "საზგადოების მიერ მხარდაჭერილი" #. TRANSLATORS: when the update was built msgid "Created" msgstr "შექმნილია" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "კრიტიკული" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "ხელმისაწვდომია ჰეშის კრიპტოგრაფიული შემოწმება" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "მიმდინარე მნიშვნელობა" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "მიმდინარე ვერსია" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "DEVICE-ID|GUID" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "გამართვის პარამეტრები" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "გაშლა…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "აღწერილობა" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "დეტალები" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "მოწყობილობის ალმები" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "მოწყობილობის ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "მოწყობილობა დაემატა:" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "მოწყობილობის ელემენტის სიმძლავრე მეტისმეტად დაბალია" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "მოწყობილობის ელემენტის სიმძლავრე მეტისმეტად დაბალია(%u%%, საჭიროა %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "მოწყობილობას შეუძლია ფლეშის ავარიის გადატანა" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "მოწყობილობას ვერ გამოიყენებთ, თუ ის დახურულია" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "მოწყობილობა შეიცვალა:" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "მოწყობილობა ჩაკეტილია" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "მოწყობილობა მიუწვდომელია" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "მოწყობილობა მიუწვდომელია ან გასულია უსადენო ინტერნეტის წვდომის არეალიდან" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "მოწყობილობა განახლების დროსაც გამოყენებადია" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "მოწყობილობა განახლების დასრულებას ელოდება" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "მოწყობილობა წაშლილია:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "მოწყობილობას ესაჭიროება 220V კვება" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "მოწყობილობის დონის განახლება" #. TRANSLATORS: command line option msgid "Device update method" msgstr "მოწყობილობის განახლების მეთოდი" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "მოწყობილობის განახლებას აქტივაცია ესაჭიროება" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "განახლების დასრულების შემდეგ მოწყობილობა აღარ გამოჩნდება" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "გამორთულია" #. TRANSLATORS: command line option msgid "Display version" msgstr "ვერსის ჩვენება" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "გავრცელება" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "გნებავთ ეს დაშორებული ახლა განაახლოთ?" #. TRANSLATORS: success #. success msgid "Done!" msgstr "მზადაა!" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "გადმოწერა…" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "ხანგრძლოვობა" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "მითითებული ESP არასწორია" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "ემულირებული" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "ემულირებული ჰოსტი" msgid "Enable" msgstr "ჩართვა" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "მხარდაჭერილ სისტემებზე მიკროკოდის განახლების ჩართვა" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "ჩართულია" #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "დაშიფრული" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "დაშიფრული RAM" #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "ძალიან ძველი" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "წაშლა…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "გამოსვლამდე მცირე დაყოვნება" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "გამოსვლა ძრავის ჩატვირთვის შემდეგ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "FILE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "ფაილის სახელი" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "FILENAME CERTIFICATE PRIVATE-KEY" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "FILENAME DEVICE-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "FILENAME OFFSET DATA [FIRMWARE-TYPE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "FILENAME [DEVICE-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "FILENAME [FIRMWARE-TYPE]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "შეცდომა" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "არგუმენტების დამუშავების შეცდომა" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "ფაილის დამუშავების შეცდომა" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "გადატვირთვის შეცდომა" #. TRANSLATORS: item is FALSE msgid "False" msgstr "მცდარი" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "ფაილის სახელი" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "ფაილის სახელის ხელმოწერა" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "ფაილის სახელის წყარო" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "ფაილის სახელი აუცილებელია" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "მიკროკოდის ატესტაცია" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "მიკროკოდის BIOS-ის დესკრიპტორი" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "მიკროკოდის BIOS-ის რეგიონი" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "მიკროკოდის გამნახლებლის შემოწმება" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "მიკროკოდის განახლებები" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "მიკროკოდის ჩაწერის დაცვა" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "მიკროკოდის ჩაწერის დაცვის დაბლოკვა" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "მიკროკოდის ატესტაცია" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "მიკროკოდის განახლებები" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "ალმები" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "ნაპოვნი" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUIDs" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWIDS-FILE" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "აპარატურა თავიდან შეერთებას ელოდება" #. TRANSLATORS: the release urgency msgid "High" msgstr "მაღალი" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "ჰოსტის უსაფრთხოების ID:" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "IOMMU უსაფრთხოება" #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "IOMMU მოწყობილობის უსაფრთხოება გამორთულია" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "IOMMU მოწყობილობის უსაფრთხოება ჩართულია" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "უქმე…" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "დადასტურების შემოწმების ტესტების იგნორი" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "დაყენების დრო" msgid "Install old version of unsigned system firmware" msgstr "მოწყობილობის ხელმოუწერელი მიკროკოდის ძველი ვერსიის დაყენება" msgid "Install signed device firmware" msgstr "მოწყობილობის ხელმოწერილი მიკროკოდის დაყენება" msgid "Install signed system firmware" msgstr "მოწყობილობის ხელმოწერილი მიკროკოდის დაყენება" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "ჯერ მშობელ მოწყობილობაზე დაყენება" msgid "Install unsigned device firmware" msgstr "მოწყობილობის ხელმოუწერელი მიკროკოდის დაყენება" msgid "Install unsigned system firmware" msgstr "მოწყობილობის ხელმოუწერელი მიკროკოდის დაყენება" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "მიკროკოდის დაყენება…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "მიკროკოდის განახლების დაყენება…" #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "მთელი რიცხვი" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "Intel BootGuard ACM-ით დაცული" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Intel BootGuard ACM-ით დაცული" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Intel BootGuard-ის შეცდომების პოლიტიკა" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Intel BootGuard-ის პატრუქი" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Intel BootGuard-ის OTP პატრუქი" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Intel BootGuard-ით შემოწმებული ჩატვირთვა" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Intel BootGuard-ის შეცდომების პოლიტიკა" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Intel BootGuard-ით შემოწმებული ჩატვირთვა" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET ჩართულია" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET ჩართულია" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Intel Management Engine -ის მწარმოებლის რეჟიმი" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Intel Management Engine -ის გადაფარვა" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Intel Management Engine -ის ვერსია" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "შიდა მოწყობილობა" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "არასწორი" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "არასწორი არგუმენტები" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "ვერსია ჩამოიწევა" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "ჩამტვირთავი კოდის რეჟიმშია" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "გაუმჯობესდება" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Პრობლემა" msgstr[1] "საკითხები" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "ბრელოკი" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "მდებარეობა" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "ბოლო ცვლილება" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "ლიცენზია" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Linux-ის ბირთვი დაბლოკილია" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Linux-ის ბირთვის შემოწმება" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Linux Swap" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Linux-ის ბირთვი" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Linux-ის ბირთვი დაბლოკილია" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linux Swap" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "მიკროკოდის მხარდაჭერილი განახლებების სია" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "ჩატვირთვა…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "დაბლოკილი" #. TRANSLATORS: the release urgency msgid "Low" msgstr "დაბალი" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "MEI მწარმოებლის რეჟიმი" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "MEI გადაფარვა" msgid "MEI version" msgstr "MEI-ის ვერსია" #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "მაქსიმალური სიგრძე" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "მაქსიმალური მნიშვნელობა" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "საშუალო" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "მეტამონაცემების ხელმოწერა" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "მეტამონაცემების URI" #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "მინიმალური ვერსია" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "მინიმალური სიგრძე" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "მინიმალური მნიშვნელობა" msgid "Modify daemon configuration" msgstr "დემონის კონფიგურაციის შეცვლა" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "დაყენების შემდეგ საჭიროებს გადატვირთვას" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "საჭიროებს გადატვირთვას" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "დაყენების შემდეგ საჭიროებს გამორთვას" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "ახალი ვერსია" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "ქმედება მითითებული არაა!" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "მიკროკოდის ID-ები ვერ ვიპოვე" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "დამატებები ნაპოვნი არაა" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "განახლებადი მოწყობილობები ვერ ვიპოვე" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "განახლებების ვერ ვიპოვე" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "ნებადაურთველი" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "ნაპოვნი არაა" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "მხარდაუჭერელია" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "დიახ" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "დიახ!" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "დაშვებულია მხოლოდ ვერსიების განახლება" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "ნაგულისხმები ESP-ის ბილიკის გადაფარვა" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "ბილიკი" #. TRANSLATORS: remote filename base msgid "Password" msgstr "პაროლი" msgid "Payload" msgstr "შემცველობა" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "დარჩენილი" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "პროცენტები დასრულებულია" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "შევასრულო ოპერაცია?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "პლატფორმის გამართვა" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "გაგრძელების წინ დარწმუნდით, რომ ტომის აღდგენის გასაღები ნამდვილად გაქვთ." #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "შესაძლო მნიშვნელობები" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "ჩატვირთვამდელი DMA-ის დაცვა" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "წინა ვერსია" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "პრიორიტეტი" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "პრობლემები" msgid "Proceed with upload?" msgstr "დავიწყო ატვირთვა?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "პროცესორის უსაფრთხოების შემოწმება" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "დახურული კოდი" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "REMOTE-ID" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "მხოლოდ კითხვისთვის" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "წაკითხვა…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "გადატვირთვა…" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "რელიზის ბრენჩი" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "რელიზის ალმები" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "რელიზის ID" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "დაშორებულის ID" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "ანგარიშის URI" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "მოთხოვნა გაუქმებულია" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "საჭიროებს ჩამტვირთავ კოდს" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "გადავტვირთო ახლა?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "მოწყობილობის გადატვირთვა…" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "SPI BIOS-ის დესკრიპტორი" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "SPI BIOS-ის რეგიონი" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI ბლოკი" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI ჩაწერა" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "რიგში ჩაყენება…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "დაცული ჩატვირთვა გამორთულია" #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "არჩეული მოწყობილობა" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "არჩეული ტომი" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "სერიული ნომერი" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "პარამეტრები მხოლოდ სისტემის გადატვირთვის შემდეგ გადატარდება" #. TRANSLATORS: command line option msgid "Show all results" msgstr "ყველა შედეგის ჩვენება" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "კლიენტისა და დემონის ვერსიების ჩვენება" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "გამართვის ინფორმაციის ჩვენება ყველა დომენისთვის" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "გამართვის პარამეტრების ჩვენება" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "გამართვის დამატებითი ინფორმაციის ჩვენება" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "გამოვრთო ახლა?" msgid "Signature" msgstr "ხელმოწერა" #. TRANSLATORS: file size of the download msgid "Size" msgstr "ზომა" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "ამ მიკროკოდის განახლებისას შეილება პლატფორმის ზოგიერი საიდუმლო დაიკარგოს." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "წყარო" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "სტრიქონი" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "წარმატება" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "დაშორებული წარმატებით გამოირთო" #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "დაშორებული წარმატებით ჩაირთო და განახლდა" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "დაშორებული წარმატებით ჩაირთო" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "დაშორებული წარმატებით შეიცვალა" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "შეჯამება" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "მხარდაჭერილი" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "მხარდაჭერილი CPU" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "უქმობამდე შეჩერება" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "RAM-ში შეჩერება" #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "შეჩერება" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "RAM-ში შეჩერება" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "სისტემის კვების სიმძლავრე განახლებისთვის მეტისმეტად დაბალია" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "სისტემის კვების სიმძლავრე განახლებისთვის მეტისმეტად დაბალია(%u%%, საჭიროა %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "სისტემას კვების გარე წყარო ესაჭიროება" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "ტექსტი" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "TPM პლატფორმის მორგება" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "TPM -ის რემონტი" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "იარლიყი" msgstr[1] "ჭდეები" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "მონიშნული ემულაციისთვის" msgid "Target" msgstr "სამიზნე წერტილი" #. TRANSLATORS: item is TRUE msgid "True" msgstr "ჭეშმარიტი" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "სანდო მეტამონაცემები" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "სანდო დატვირთვა" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "ტიპით" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI მიკროკოდის ხელსაწყო" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "UEFI პლატფორმის გასაღები" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "UEFI უსაფრთხო ჩატვირთვა" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "UEFI პლატფორმის გასაღები" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "UEFI უსაფრთხო ჩატვირთვა" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "ატრიბუტი ვერ ვიპოვე" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "დაუშიფრავი" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "უცნობია" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "უცნობი მოწყობილობა" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "განბლოკილი" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "განახლებისას გამართვის ალმის მოხსნა" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "განახლებადი" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "განახლების შეცდომა" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "ასლის განახლება" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "განახლების შეტყობინება" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "განახლების მდგომარეობა" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "განვაახლო ახლა?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "განახლება გადატვირთვას საჭიროებს" msgid "Updating" msgstr "განახლება" #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "%s-ის განახლება…" #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "სასწრაფოობა" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "მომხმარებელი გაფრთხილებულია" #. TRANSLATORS: remote filename base msgid "Username" msgstr "მომხმარებელი" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "სწორი" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "ვარიანტი" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "მომწოდებელი" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "შემოწმება…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "ვერსია" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "გაფრთხილება" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "მოლოდინი…" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "ფაილის ჩაწერა:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "ჩაწერა…" #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "ამ მიკროკოდით თქვენი აპარატურა შეიძლება დაზიანდეს. ამავე დროს ამ რელიზის დაყენებამ შეიძლება %s-ზე გარანტია მოგიხსნათ." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[DEVICE-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SMBIOS-FILE|HWIDS-FILE]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "ნაგულისხმები" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd დამატებები" fwupd-1.9.16/po/kk.po000066400000000000000000000024751460375044200143350ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Baurzhan Muftakhidinov , 2017 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Kazakh (http://www.transifex.com/freedesktop/fwupd/language/kk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: kk\n" "Plural-Forms: nplurals=1; plural=0;\n" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Қосылған" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Бас тартылған" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Өзгертілген" #. TRANSLATORS: read from device to host msgid "Erasing" msgstr "Өшірілуде" #. TRANSLATORS: read from device to host msgid "Reading" msgstr "Оқылуда" #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Өшірілген" #. TRANSLATORS: read from device to host msgid "Verifying" msgstr "Тексерілуде" #. TRANSLATORS: write from host to device msgid "Writing" msgstr "Жазылуда" fwupd-1.9.16/po/ko.po000066400000000000000000003525461460375044200143500ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Seong-ho Cho , 2017,2019,2021-2023 # Shinjo Park , 2018-2021,2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Korean (http://app.transifex.com/freedesktop/fwupd/language/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ko\n" "Plural-Forms: nplurals=1; plural=0;\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f분 남음" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "%s BMC 업데이트" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "%s 배터리 업데이트" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "%s CPU 마이크로코드 업데이트" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "%s 카메라 업데이트" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "%s 설정 업데이트" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s 소비자용 ME 업데이트" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s 컨트롤러 업데이트" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s 기업용 ME 업데이트" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s 장치 업데이트" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "%s 디스플레이 장치 업데이트" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "%s 도크 업데이트" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "%s 드라이브 업데이트" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s 임베디드 컨트롤러 업데이트" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "%s 지문 스캐너 업데이트" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "%s 플래시 드라이브 업데이트" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "%s GPU 장치 업데이트" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "%s 그래픽 태블릿 업데이트" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "%s 키보드 업데이트" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s ME 업데이트" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "%s 마우스 업데이트" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "%s 네트워크 인터페이스 업데이트" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "%s SSD 장치 업데이트" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "%s 저장 장치 컨트롤러 업데이트" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s 시스템 업데이트" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "%s TPM 업데이트" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s Thunderbolt 컨트롤러 업데이트" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "%s 터치패드 업데이트" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "%s USB 도크 업데이트" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "%s USB 수신 장치 업데이트" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s 업데이트" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "업데이트 중에는 %s 및 연결된 모든 장치를 사용할 수 없을 수도 있습니다." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "%s 나타남: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "%s 바뀜: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "%s 사라짐: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "현재 %s을(를) 업데이트할 수 없습니다" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s 제조 모드" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "장치 손상을 방지하려면 업데이트 중 %s의 연결을 계속 유지해야 합니다." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "장치 손상을 방지하려면 업데이트 중 %s을(를) 전원에 계속 연결해 두어야 합니다." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s 재정의" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s 버전" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u일" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "장치 %u개의 펌웨어를 업데이트할 수 있습니다." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "장치 %u개가 알려진 최적의 설정 상태가 아닙니다." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u시간" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "로컬 장치 %u개를 지원함" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u분" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u초" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (임계율 %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(사용되지 않음)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "TPM PCR에서 이제 올바르지 않은 값을 가졌습니다" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "AMD 펌웨어 재생 보호" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "AMD 펌웨어 기록 보호" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "AMD 보안 프로세서 롤백 보호" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "ARCHIVE FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATFILE]" msgstr "<아카이브> <펌웨어> <메타정보> [<펌웨어>] [<메타정보>] []" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "동작이 필요합니다:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "장치 활성화" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "대기 중인 장치 활성화" msgid "Activate the new firmware on the device" msgstr "장치에 새 펌웨어 활성화" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "펌웨어 업데이트 활성화 중" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "다음 장치의 펌웨어 업데이트 활성화 중:" #. TRANSLATORS: command description msgid "Adds devices to watch for future emulation" msgstr "향후 에뮬레이션을 감시할 장치를 추가합니다" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "경과 기간" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "동의하며 원격 저장소를 활성화하시겠습니까?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "%s의 별칭" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "모든 TPM PCR이 이제 올바릅니다" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "모든 TPM PCR이 올바릅니다" #. TRANSLATORS: an application is preventing system updates msgid "All devices are prevented from update by system inhibit" msgstr "시스템 동작 억제로 모든 장치 업데이트를 막았습니다" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "동일한 모든 형식의 장치를 동시 업데이트합니다" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "펌웨어 다운그레이드 허용" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "기존 펌웨어 버전 재설치 허용" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "펌웨어 브랜치 전환 허용" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "대체 브랜치" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "업데이트를 진행하고 있습니다" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "업데이트를 완료하려면 다시 시작해야 합니다." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "업데이트를 완료하려면 시스템을 종료해야 합니다." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "모든 질문에 예로 답하기" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "펌웨어 업데이트 적용" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "필요하지 않은 경우에도 업데이트 적용" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "업데이트 파일 적용" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "업데이트 적용 중..." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "허용된 펌웨어:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "다음에도 다시 확인하시겠습니까?" #. TRANSLATORS: command description msgid "Asks the daemon to quit" msgstr "데몬 끝내기를 확인합니다" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "펌웨어 모드에 연결" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "인증 중…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "인증 세부 절차가 필요합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "이동식 장치의 펌웨어를 이전 버전으로 되돌리려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "이 머신에 이전 버전의 펌웨어를 설치하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "BIOS 설정 값을 수정하려면 인증이 필요합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "펌웨어 업데이트에 사용할 원격 설정을 수정하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "데몬 설정을 수정하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "BIOS 설정 값을 읽으려면 인증이 필요합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "허용된 펌웨어 목록을 설정하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "클라이언트 인증서로 데이터를 서명하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "새 펌웨어 버전으로 전환하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "장치 잠금을 해제하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "이동식 장치의 펌웨어를 업데이트하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "이 머신의 펌웨어를 업데이트하려면 인증해야 합니다" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "저장된 체크섬을 업데이트하려면 인증해야 합니다" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "자동 보고" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "매번 자동으로 업로드하시겠습니까?" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "BIOS 롤백 보호" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "BIOS 롤백 보호" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "LVFS 또는 윈도우 업데이트에서 BIOS 업데이트를 받아왔습니다" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BUILDER-XML FILENAME-DST" msgid "BYTES" msgstr "BYTES" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "배터리" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "새 커널 드라이버 바인드" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "차단된 펌웨어 파일:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "막힌 버전" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "차단할 펌웨어:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "지정한 펌웨어 설치 방지" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "부트로더 버전" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "브랜치" #. TRANSLATORS: command description msgid "Build a cabinet archive from a firmware blob and XML metadata" msgstr "펌웨어 블롭과 XML 메타데이터를 활용하여 캐비닛 아카이브를 만듭니다" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "펌웨어 파일 빌드" #. TRANSLATORS: longer description msgid "CPU Microcode must be updated to mitigate against various information-disclosure security issues." msgstr "다양한 정보 유출 보안 문제를 해결하려면 CPU 마이크로코드를 업데이트해야 합니다." #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "취소" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "취소함" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "dbx 업데이트가 이미 적용되었기 때문에 적용할 수 없습니다." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "라이브 미디어에서 실행 중일 때는 업데이트를 적용할 수 없음" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "바뀜" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "암호화 해시가 펌웨어와 일치하는지 확인" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "체크섬" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "브랜치를 선택하십시오" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "장치를 선택하십시오" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "펌웨어를 선택하십시오" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "릴리스를 선택하십시오" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "ESP 선택:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "볼륨을 선택하십시오" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "최근 업데이트 결과 지우기" #. TRANSLATORS: error message msgid "Command not found" msgstr "명령을 찾을 수 없습니다" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "커뮤니티 지원" #. TRANSLATORS: title prefix for the BIOS settings dialog msgid "Configuration Change Suggested" msgstr "설정 변경 제안" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "설정은 시스템 관리자만 확인할 수 있습니다" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "펌웨어 파일 변환" #. TRANSLATORS: when the update was built msgid "Created" msgstr "생성됨" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "매우 높음" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "암호화 해시 검사 사용 가능" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "현재값" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "현재 버전" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "DEVICE-ID|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU 유틸리티" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "디버깅 옵션" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "압축 해제 중…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "설명" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "부트로더 모드로 전환" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "자세한 정보" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "최적의 설정에서 벗어납니까?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "장치 플래그" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "장치 ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "장치 추가됨:" #. TRANSLATORS: the device is already connected msgid "Device already exists" msgstr "장치가 이미 있습니다" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "배터리 전원 여분이 매우 부족합니다" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "장치 배터리 전원이 매우 부족합니다 (%u%%, 필요 전원 백분율 %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "장치 업데이트 실패 시 복구 가능" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "덮개가 닫혀있는 동안 사용할 수 없습니다" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "장치 상태 바뀜:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "장치 에뮬레이션을 활용할 수 없습니다." #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "장치 펌웨어에서 버전 확인을 지원해야 함" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "에뮬레이션 장치" #. TRANSLATORS: device cannot be interrupted, for instance taking a phone call msgid "Device is in use" msgstr "장치를 사용하고 있습니다" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "장치가 잠김" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "장치에 제공된 모든 릴리스를 설치해야 함" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "장치에 접근할 수 없습니다" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "장치가 끊어졌거나, 무선 범위를 벗어났습니다" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "업데이트 중 장치를 사용할 수 있음" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "업데이트 적용을 기다리고 있습니다" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "장치 제거됨:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "교류 전원을 연결해야합니다" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "장치 업데이트시 프로그램 라이선스가 필요합니다" #. TRANSLATORS: longer description msgid "Device software updates are provided for this device." msgstr "이 장치에 대한 장치 프로그램 업데이트가 있습니다." #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "안전한 업데이트 지원" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "장치에서 펌웨어 브랜치 전환을 지원함" #. TRANSLATORS: command line option msgid "Device update method" msgstr "장치 업데이트 방식" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "장치 업데이트 활성화 필요" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "장치에 펌웨어를 설치하기 전에 백업을 수행함" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "업데이트 후 장치가 다시 표시되지 않음" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "성공적으로 업데이트된 장치:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "올바르게 업데이트되지 않은 장치:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "펌웨어 업데이트를 사용할 수 없는 장치:" #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "최신 펌웨어가 설치된 장치:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "일치하는 GUID 장치를 찾지 못했습니다" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "비활성화됨" msgid "Disabled fwupdate debugging" msgstr "fwupdate 디버깅 비활성화" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "지정한 원격 저장소 비활성화" #. TRANSLATORS: command line option msgid "Display version" msgstr "버전 표시" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "배포판" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "오래된 메타데이터 검사하지 않기" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "보고되지 않은 과거 기록 검사하지 않기" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "원격 저장소에서 다운로드 활성화 여부를 검사하지 않기" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "업데이트 후 다시 시작 묻거나 검사하지 않기" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "로그 도메인 접두사를 포함하지 않기" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "timestamp 접두사를 포함하지 않기" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "장치 안정성 검사를 실행하지 않음" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "장치에 대해 물어보지 않음" #. TRANSLATORS: command line option msgid "Do not prompt to fix security issues" msgstr "보안 문제 해결 묻지 않기" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "해석시 펌웨어를 검색하지 않음" #. TRANSLATORS: warning message shown after update has been scheduled msgid "Do not turn off your computer or remove the AC adaptor while the update is in progress." msgstr "업데이트를 진행하는 동안 컴퓨터를 끄거나 교류 어댑터를 뽑지 마십시오." #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "과거 기록 데이터베이스에 기록하지 않기" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "펌웨어 브랜치 변경 조건을 이해했습니까?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "다음에 업데이트할 때 이 기능을 비활성화하시겠습니까?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "지금 사용하시겠습니까?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "원격 저장소를 새로 고치시겠습니까?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "다음에 업데이트할 때 보고서를 자동으로 업로드하시겠습니까?" #. TRANSLATORS: command line option msgid "Don't prompt for authentication (less details may be shown)" msgstr "인증을 확인하지 않음(덜 자세하게 나타냄)" #. TRANSLATORS: success #. success msgid "Done!" msgstr "완료!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "%s을(를) %s에서 %s(으)로 다운그레이드하시겠습니까?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "장치 펌웨어 다운그레이드" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "%s을(를) %s에서 %s(으)로 다운그레이드 중..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "%s 다운그레이드 중..." #. TRANSLATORS: command description msgid "Download a file" msgstr "파일 다운로드" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "다운로드 중…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "파일에 저장된 SMBIOS 데이터 덤프 출력" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "예상 시간" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "지정한 ESP가 올바르지 않습니다" #. TRANSLATORS: longer description msgid "Each system should have tests to ensure firmware security." msgstr "각 시스템에 대해 펌웨어 보안을 확인 시험을 수행해야 합니다." #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "JSON 매니페스트로 장치 에뮬레이팅합니다" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "에뮬레이팅함" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "에뮬레이팅 호스트" msgid "Enable" msgstr "활성" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "지원하는 시스템의 펌웨어 업데이트 지원 활성화" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "새 원격 저장소를 활성화하시겠습니까?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "이 원격 저장소를 활성화하시겠습니까?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "활성화됨" msgid "Enabled fwupdate debugging" msgstr "fwupdate 디버깅 활성화" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "하드웨어가 일치하면 활성" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "지정한 원격 저장소 활성화" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "이 기능의 사용은 본인의 책임이며, 업데이트로 인해서 생긴 문제는 원 장치 제조사에 직접 보고해야 합니다. 업데이트 진행 과정 자체의 문제는 $OS_RELEASE:BUG_REPORT_URL$(으)로 보고해 주십시오." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "이 원격 저장소를 설정하는 것은 본인의 책임입니다." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "암호화됨" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "암호화된 RAM" msgid "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." msgstr "암호화 RAM 정책에서 메모리 칩을 제거했거나 접근할 때 읽어야 할 장치 메모리에 정보를 저장할 수 없도록 합니다." #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "지원 만료" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "열거값" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "모든 펌웨어 업데이트 기록 지우기" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "지우는 중…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "짧은 대기 시간 경과 후 나가기" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "엔진을 불러온 후 나가기" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "펌웨어 파일 구조를 XML로 내보내기" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "펌웨어 바이너리 파일에서 이미지 추출" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "FILE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "FILE [DEVICE-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "FILENAME" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "FILENAME CERTIFICATE PRIVATE-KEY" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "FILENAME DEVICE-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "<파일이름> <오프셋> <데이터> [<펌웨어형식>]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "FILENAME [DEVICE-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "FILENAME [FIRMWARE-TYPE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "<파일이름>|<체크섬1>[,<체크섬2>][,<체크섬3>]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "실패함" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "업데이트를 적용할 수 없음" #. TRANSLATORS: error message for Windows msgid "Failed to connect to Windows service, please ensure it's running." msgstr "윈도우 서비스 연결 실패. 실행중인지 확인하십시오." #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "데몬에 연결할 수 없음" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "대기 중인 장치를 가져올 수 없음" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "펌웨어 업데이트를 설치할 수 없음" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "로컬 dbx를 불러올 수 없음" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "특이 사항을 불러올 수 없음" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "시스템 dbx를 불러올 수 없음" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "잠글 수 없음" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "인자 해석에 실패했습니다" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "파일을 해석할 수 없음" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "--filter에 전달한 플래그 해석에 실패함" msgid "Failed to parse flags for --filter-release" msgstr "--filter-release에 전달한 플래그 해석에 실패함" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "로컬 dbx를 해석할 수 없음" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "다시 시작할 수 없음" #. TRANSLATORS: a feature is something like "can show an image" msgid "Failed to set front-end features" msgstr "프론트엔드 기능 설정에 실패했습니다" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "스플래시 모드를 설정할 수 없음" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "ESP 내용을 검사할 수 없음" #. TRANSLATORS: item is FALSE msgid "False" msgstr "거짓" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "파일 이름" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "파일 이름 서명" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "파일 이름 원본" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "파일 이름이 필요함" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "플래그로 장치를 필터하며, ~ 기호를 앞에 붙이면 제외할 수 있습니다. 예: 'internal,~needs-reboot'" #. TRANSLATORS: command line option msgid "Filter with a set of release flags using a ~ prefix to exclude, e.g. 'trusted-release,~trusted-metadata'" msgstr "플래그로 릴리스를 필터하며, ~ 기호를 앞에 붙이면 제외할 수 있습니다. 예: 'trusted-release,~trusted-metadata'" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "펌웨어 검증" #. TRANSLATORS: longer description msgid "Firmware Attestation checks device software using a reference copy, to make sure that it has not been changed." msgstr "펌웨어 증명 정책에서는 장치 프로그램의 변경 여부 확인 목적으로 참조 복사를 사용하는지 검사합니다." #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "펌웨어 BIOS 서술자" #. TRANSLATORS: longer description msgid "Firmware BIOS Descriptor protects device firmware memory from being tampered with." msgstr "장치 펌웨어 서술자에서 장치 펌웨어 메모리에 펌웨어를 임의로 변경하는 행위를 막습니다." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "펌웨어 BIOS 영역" #. TRANSLATORS: longer description msgid "Firmware BIOS Region protects device firmware memory from being tampered with." msgstr "펌웨어 BIOS 영역에서 장치 펌웨어 메모리에 펌웨어를 임의로 변경하는 행위를 막습니다" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "펌웨어 기본 URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "펌웨어 업데이트 D-Bus 서비스" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "펌웨어 업데이트 데몬" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "펌웨어 업데이터 검증" msgid "Firmware Updater Verification checks that software used for updating has not been tampered with." msgstr "펌웨어 업데이터 검증에서 업데이트에 사용하는 프로그램을 임의로 바꾸지 않았는지 확인합니다." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "펌웨어 업데이트" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "펌웨어 유틸리티" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "펌웨어 기록 보호" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "펌웨어 기록 보호 잠금" #. TRANSLATORS: longer description msgid "Firmware Write Protection protects device firmware memory from being tampered with." msgstr "펌웨어 기록 보호 정책에서 장치 펌웨어 메모리에 펌웨어를 임의로 변경하는 행위를 막습니다" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "펌웨어 검증" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "펌웨어가 이미 차단됨" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "펌웨어가 이미 차단되어 있지 않음" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "펌웨어 메타데이터가 %u일 동안 업데이트되지 않았으므로 최신 정보가 누락되었을 수도 있습니다." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "펌웨어 업데이트" msgid "Firmware updates are not supported on this machine." msgstr "이 머신에서 펌웨어 업데이트를 지원하지 않습니다." msgid "Firmware updates are supported on this machine." msgstr "이 머신에서 펌웨어 업데이트를 지원합니다." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "펌웨어 업그레이드 비활성화됨. 활성화하려면 'fwupdmgr unlock' 명령을 실행하십시오" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "플래그" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "런타임 체크를 비활성화하여 강제로 작업 실행" msgid "Force the action ignoring all warnings" msgstr "모든 경고를 무시하고 작업 강제 진행" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "찾음" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "전체 디스크 암호화를 감지했습니다" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "업데이트를 수행하면 전체 디스크 암호화 비밀키를 무효화할 수 있습니다" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "퓨징 플랫폼" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "퓨징한 플랫폼" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "GUID|DEVICE-ID" msgstr "" msgid "Get BIOS settings" msgstr "BIOS 설정 가져오기" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "fwupd를 지원하는 모든 장치 정보 가져오기" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "펌웨어 업데이트를 지원하는 모든 장치 정보 가져오기" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "시스템에 등록된 모든 활성 플러그인 가져오기" #. TRANSLATORS: command description msgid "Get device report metadata" msgstr "장치 보고서 메타데이터 가져오기" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "펌웨어 파일 세부 정보 가져오기" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "원격 설정 정보 가져오기" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "호스트 보안 속성 가져오기" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "허용된 펌웨어 목록 가져오기" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "설치 방지된 펌웨어 목록 가져오기" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "연결한 하드웨어의 업데이트 목록을 가져옵니다" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "장치 펌웨어 릴리스 가져오기" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "최근 업데이트 결과 가져오기" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWIDS-FILE" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "하드웨어를 다시 연결해야 함" #. TRANSLATORS: the release urgency msgid "High" msgstr "높음" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "호스트 보안 이벤트" #. TRANSLATORS: error message for unsupported feature #, c-format msgid "Host Security ID (HSI) is not supported" msgstr "호스트 보안 ID(HSI)를 지원하지 않습니다" #. TRANSLATORS: success, so say thank you to the user msgid "Host Security ID attributes uploaded successfully, thanks!" msgstr "호스트 보안 ID 속성을 제대로 올렸습니다. 감사합니다!" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "호스트 보안 ID:" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "INHIBIT-ID" msgstr "INHIBIT-ID" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "IOMMU 보호" #. TRANSLATORS: longer description msgid "IOMMU Protection prevents connected devices from accessing unauthorized parts of system memory." msgstr "IOMMU 보호 정책에서는 연결한 장치의 시스템 메모리의 비허가 부분 접근을 막습니다." #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "IOMMU 장치 보호 해제함" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "IOMMU 장치 보호 설정함" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "대기 중…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "파일을 다운로드할 때 SSL 엄격한 검사 건너뛰기" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "펌웨어 체크섬 오류 무시" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "펌웨어 하드웨어 일치 오류 무시" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "장치 안정성 검사 무시" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "SSL 엄격한 검사 무시, 차후에 자동으로 적용하려면 DISABLE_SSL_STRICT 환경 변수를 지정해야 함" #. TRANSLATORS: the inhibit ID is a short string like dbus-123456 #, c-format msgid "Inhibit ID is %s." msgstr "억제 ID는 %s입니다." #. TRANSLATORS: command description msgid "Inhibit the system to prevent upgrades" msgstr "업그레이드를 막도록 시스템 동작을 억제합니다" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "설치 예상 시간" #. TRANSLATORS: command description msgid "Install a firmware file in cabinet format on this hardware" msgstr "이 하드웨어에 캐비닛 형식의 펌웨어 파일 설치" #. TRANSLATORS: command description msgid "Install a raw firmware blob on a device" msgstr "장치에 원시 펌웨어 블롭 설치" #. TRANSLATORS: command description msgid "Install a specific firmware file on all devices that match" msgstr "일치하는 모든 장치에 지정한 펌웨어 파일 설치" #. TRANSLATORS: command description msgid "Install a specific firmware on a device, all possible devices will also be installed once the CAB matches" msgstr "장치에 지정 펌웨어를 설치합니다. CAB이 일치하는 모든 가능한 장치에도 설치합니다" msgid "Install old version of signed system firmware" msgstr "오래된 버전의 서명한 시스템 펌웨어 설치" msgid "Install old version of unsigned system firmware" msgstr "오래된 버전의 서명하지 않은 시스템 펌웨어 설치" msgid "Install signed device firmware" msgstr "서명된 장치 펌웨어 설치" msgid "Install signed system firmware" msgstr "서명된 시스템 펌웨어 설치" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "부모 장치에 먼저 설치" msgid "Install unsigned device firmware" msgstr "서명되지 않은 장치 펌웨어 설치" msgid "Install unsigned system firmware" msgstr "서명되지 않은 시스템 펌웨어 설치" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "펌웨어 설치 중..." #. TRANSLATORS: stay on one firmware version unless the new version is #. explicitly #. * specified msgid "Installing a specific release is explicitly required" msgstr "특정한 릴리스 설치를 명시적으로 지정해야 함" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "펌웨어 업데이트 설치 중…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "%s에 설치 중..." #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "이 업데이트를 설치하면 장치 보증이 깨질 수 있습니다." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "정수값" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "인텔 BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "인텔 BootGuard ACM 보호" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "인텔 BootGuard ACM 보호됨" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "인텔 BootGuard 오류 정책" msgid "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." msgstr "인텔 BootGuard 오류 정책은 장치 소프트웨어가 임의로 변경되었을 때 장치 시작 작업이 중단되도록 합니다." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "인텔 BootGuard 퓨즈" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "인텔 BootGuard OTP 퓨즈" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "인텔 BootGuard 검증된 부트" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "인텔 BootGuard 오류 정책" #. TRANSLATORS: longer description msgid "Intel BootGuard prevents unauthorized device software from operating when the device is started." msgstr "인텔 BootGuard에서 장치 동작을 시작할 때 인증하지 않은 장치 프로그램의 동작을 막습니다." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "인텔 BootGuard 검증된 부트" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "인텔 CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "인텔 CET 활성" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "인텔 CET 활성화됨" #. TRANSLATORS: longer description msgid "Intel Control-Flow Enforcement Technology detects and prevents certain methods for running malicious software on the device." msgstr "인텔 CET(제어흐름 강제 기술)은 장치에서 악성 프로그램을 감지하고 일부 실행 방법을 차단합니다." #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS Mitigation" msgstr "인텔 GDS 완화" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS mitigation" msgstr "인텔 GDS 완화" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "인텔 관리 엔진 제조사 모드" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "인텔 관리 엔진 무시" #. TRANSLATORS: longer description msgid "Intel Management Engine Override disables checks for device software tampering." msgstr "인텔 관리 엔진 오버라이드는 장치 프로그램의 부당 변경 검사를 끕니다." #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "인텔 관리 엔진 버전" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "인텔 SMAP" #. TRANSLATORS: longer description msgid "Intel Supervisor Mode Access Prevention ensures critical parts of device memory are not accessed by less secure programs." msgstr "인텔 SMAP(수퍼바이저 모드 접근 방지 정책)은 안전하지 않은 프로그램에서 장치 메모리의 중요한 부분에 접근하는 것을 방지합니다." #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "내장 장치" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "올바르지 않음" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "부적절한 인자 값" #. TRANSLATORS: error message msgid "Invalid arguments, expected GUID" msgstr "잘못된 인자, GUID를 예상함" #. TRANSLATORS: error message msgid "Invalid arguments, expected at least ARCHIVE FIRMWARE METAINFO" msgstr "부적절한 인자. 최소한 <아카이브> <펌웨어> <메타정보> 가 필요합니다" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "다운그레이드 버전" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "현재 부트로더 모드에 있음" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "업그레이드 버전" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "문제점" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "KEY,VALUE" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "커널에 더이상 문제 요소가 없음" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "커널에 문제 요소 발생" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "커널 잠금 해제함" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "커널 잠금 설정함" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "키 모음" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "<위치>" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "마지막으로 수정한 날짜" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "1분 미만 남음" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "라이선스" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "리눅스 커널 락다운" #. TRANSLATORS: longer description msgid "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgstr "리눅스 커널 잠금 모드에서는 관리자(루트) 계정이 시스템 프로그램의 중요한 부분을 접근하거나 바꾸는 행위를 막습니다." msgid "Linux Kernel Swap temporarily saves information to disk as you work. If the information is not protected, it could be accessed by someone if they obtained the disk." msgstr "리눅스 커널 스왑 기능에서는 작업한 결과 정보를 임시로 디스크에 저장합니다. 정보를 보호하는 상태가 아니라면 디스크에 접근한 누군가가 해당 정보에 접근할 수 있습니다." #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "리눅스 커널 검증" msgid "Linux Kernel Verification makes sure that critical system software has not been tampered with. Using device drivers which are not provided with the system can prevent this security feature from working correctly." msgstr "리눅스 커널 검증 과정에서 중요한 시스템 프로그램이 임의로 변경하지 않았는지 확인합니다. 시스템에서 제공하지 않는 장치 드라이버를 사용하면 이 보안 기능이 올바르게 동작하지 못하게 막을 수 있습니다." #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "리눅스 스왑" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "리눅스 제조사 펌웨어 서비스(안정 버전 펌웨어)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "리눅스 제조사 펌웨어 서비스(테스트 버전 펌웨어)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "리눅스 커널" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "리눅스 커널 Lockdown" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "리눅스 스왑" #. TRANSLATORS: command description msgid "List EFI variables with a specific GUID" msgstr "지정한 GUID를 사용하는 EFI 변수 표시" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "dbx의 항목 표시" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "지원하는 펌웨어 업데이트 목록 표시" #. TRANSLATORS: command description msgid "List the available firmware GTypes" msgstr "사용 가능한 펌웨어 GType 목록 표시" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "사용 가능한 펌웨어 종류 표시" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "ESP에 저장된 파일 표시" #. TRANSLATORS: command description msgid "Load device emulation data" msgstr "장치 에뮬레이션 데이터를 불러옵니다" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "외부 모듈에서 불러옴" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "불러오는 중…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "잠김" #. TRANSLATORS: the release urgency msgid "Low" msgstr "낮음" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "MEI 키 매니페스트" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "MEI 키 매니페스트" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "MEI 제조 모드" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "MEI 재정의" msgid "MEI version" msgstr "MEI 버전" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "지정한 플러그인을 수동으로 활성화" #. TRANSLATORS: longer description msgid "Manufacturing Mode is used when the device is manufactured and security features are not yet enabled." msgstr "제조사 모드는 장치를 제조한 후 보안 기능을 아직 켜지 않았을 때 사용합니다." #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "최대 길이" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "최대값" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "중간" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "메타데이터 서명" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "메타데이터 URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "리눅스 제조사 펌웨어 서비스에서 메타데이터를 가져올 수 있습니다." #. TRANSLATORS: error message for a user who ran fwupdmgr #. * refresh recently msgid "Metadata is up to date; use --force to refresh again." msgstr "메타데이터가 최신 상태입니다. 다시 새로 고치려면 --force를 사용하십시오." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "최소 버전" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "최소 길이" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "최소값" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "데몬과 클라이언트가 일치하지 않음, %s을(를) 대신 사용함" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "데몬 설정값 수정" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "지정한 원격 저장소 수정" msgid "Modify a configured remote" msgstr "원격 설정 수정" msgid "Modify daemon configuration" msgstr "데몬 설정 수정" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "데몬 이벤트 감시" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "ESP 마운트" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "설치 후 다시 시작 필요함" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "다시 시작 필요" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "설치 후 종료 필요함" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "새 버전" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "동작을 지정하지 않았습니다!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "%s의 다운그레이드 없음" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "펌웨어 ID를 찾을 수 없음" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "펌웨어가 없습니다" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "펌웨어를 업데이트할 수 있는 하드웨어가 없음" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "플러그인을 찾을 수 없음" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "사용 가능한 릴리스 없음" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "원격 저장소가 설정되지 않았으므로 메타데이터를 사용할 수 없습니다." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "원격 저장소 없음" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "업데이트할 수 있는 장치가 없습니다" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "업데이트가 없습니다" msgid "No updates available for remaining devices" msgstr "남은 장치의 업데이트가 없습니다" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "적용된 업데이트 없음" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "승인 안함" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "찾을 수 없음" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "지원하지 않음" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "확인" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "확인!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "이전 버전" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "단일 PCR 값만 표시" #. TRANSLATORS: command line option msgid "Only use peer-to-peer networking when downloading files" msgstr "파일을 다운로드할 때 피어투피어 네트워크만 사용" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "버전 업그레이드만 가능합니다" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "JSON 형식으로 출력" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "기본 ESP 경로 재정의" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "PATH" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "펌웨어 파일을 처리하고 세부 정보 표시" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "dbx 업데이트 해석 중..." #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "시스템 dbx 해석 중..." #. TRANSLATORS: remote filename base msgid "Password" msgstr "암호" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "알려진 오프셋에 펌웨어 블롭 패치" msgid "Payload" msgstr "페이로드" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "대기 중" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "진행률" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "작업을 수행하시겠습니까?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "플랫폼 디버깅" #. TRANSLATORS: longer description msgid "Platform Debugging allows device security features to be disabled. This should only be used by hardware manufacturers." msgstr "플랫폼 디버깅에서 장치 보안 기능 비활성화를 허용합니다. 이 기능은 하드웨어 제조사에서만 사용해야 합니다." #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "플랫폼 디버깅" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "계속하시 전 볼륨 복원 키가 있는지 확인하십시오." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "0에서 %u까지의 숫자 중 하나를 입력하십시오:" #. TRANSLATORS: the user isn't reading the question -- please don't translate #. * 'Y' or 'N' as these are hardcoded msgid "Please enter either Y or N: " msgstr "Y나 N으로 입력하십시오:" #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "플러그인 의존성을 찾을 수 없음" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "가능한 값" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "부팅 전 DMA 보호" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "부트 전 DMA 보호" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "부팅 전 DMA 보호를 해제했습니다" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "부팅 전 DMA 보호를 설정했습니다" #. TRANSLATORS: longer description msgid "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." msgstr "부팅 전 DMA 보호 정책에서 컴퓨터에 연결한 장치에서의 시스템 메모리 접근을 막습니다." #. TRANSLATORS: warning message msgid "Press unlock on the device to continue the update process." msgstr "업데이트 과정을 계속하려면 장치 잠금 해제를 누르십시오." #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "이전 버전" msgid "Print the version number" msgstr "버전 번호 표시" msgid "Print verbose debug statements" msgstr "자세한 디버그 정보 표시" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "우선 순위" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "문제" msgid "Proceed with upload?" msgstr "업로드를 진행하시겠습니까?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "프로세서 보안 검사" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "프로세서 롤백 보호" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "독점적" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "펌웨어 업데이트 지원 조회" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "REMOTE-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "REMOTE-ID KEY VALUE" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "읽기 전용" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "장치에서 펌웨어 바이너리 읽기" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "장치에서 펌웨어 읽기" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "장치 펌웨어를 읽어 파일에 기록" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "파티션에서 펌웨어를 읽어 파일에 기록" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "%s에서 읽는 중..." #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "읽는 중…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "다시 시작하는 중..." #. TRANSLATORS: how often we should refresh the metadata msgid "Refresh Interval" msgstr "새로 고침 간격" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "원격 서버에서 메타데이터 새로 고침" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "%s에 %s을(를) 다시 설치하시겠습니까?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "장치에 현재 펌웨어 다시 설치" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "장치에 펌웨어 다시 설치" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "%s에 %s 다시 설치하는 중..." #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "릴리스 브랜치" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "출시 플래그" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "출시 ID" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "원격 ID" #. TRANSLATORS: command description msgid "Removes devices to watch for future emulation" msgstr "향후 에뮬레이션을 감시할 장치를 제거합니다" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "기존 펌웨어 파일의 데이터 교체" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "보고서 URI" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "원격 서버에 보고됨" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "요청을 취소했습니다" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "필요한 efivarfs 파일 시스템을 찾을 수 없음" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "필요한 하드웨어를 찾을 수 없음" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "부트로더 모드 진입 필요함" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "인터넷 연결 필요" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "지금 다시 시작하시겠습니까?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "데몬을 다시 시작하여 변경 사항을 적용하시겠습니까?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "장치 다시 시작하는 중…" #. TRANSLATORS: command description msgid "Retrieve BIOS settings. If no arguments are passed all settings are returned" msgstr "BIOS 설정을 가져옵니다. 전달하는 인자 값이 없으면 모든 설정 값을 반환합니다" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "머신의 모든 하드웨어 ID 반환" #. TRANSLATORS: longer description msgid "Rollback Protection prevents device software from being downgraded to an older version that has security problems." msgstr "롤백 보호 정책에서 보안 문제를 야기하는 장치 프로그램의 이전 버전 다운 그레이드를 막습니다." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "더 많은 정보를 보려면 `fwupdmgr get-upgrades` 명령을 실행하십시오." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "install-blob 사용 시 플러그인 정리 루틴 실행" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "install-blob 사용 시 플러그인 준비 루틴 실행" #. TRANSLATORS: tell a user how to get information #, c-format msgid "Run without '%s' to see" msgstr "확인하려면 '%s'을(를) 빼고 실행하십시오" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "실행 중인 커널이 너무 오래되었습니다" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "런타임 접미사" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "<설정> <값>" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING1 VALUE1 [SETTING2] [VALUE2]" msgstr "<설정1> <값1> [<설정2>] [<값2>]" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "SPI BIOS 설명자" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "SPI BIOS 영역" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI 잠금" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "SPI 리플레이 보호" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI 기록" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "SPI 기록 보호" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "하드웨어 ID를 생성할 수 있는 파일 저장" #. TRANSLATORS: command description msgid "Save device emulation data" msgstr "장치 에뮬레이션 데이터를 저장합니다" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "스칼라 증가값" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "가능하다면 다음에 다시 시작할 때 설치 예약" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "작업 계획 중…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "보안 부팅 비활성" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "보안 부팅 활성" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "자세한 정보는 %s 사이트를 참조하십시오." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "자세한 정보는 %s 사이트를 참조하십시오." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "선택한 장치" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "선택한 볼륨" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "일련 번호" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "'%s' BIOS 설정을 '%s'(으)로 설정합니다." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "BIOS 설정 값을 설정" msgid "Set one or more BIOS settings" msgstr "하나 이상의 BIOS 설정 값 설정" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "업데이트 중 디버깅 플래그 설정" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "하나 이상의 BIOS 설정 값을 설정합니다" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "허용된 펌웨어 목록 설정" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "설정 데이터형" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "시스템을 다시 부팅하고 나면 설정을 반영합니다" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "개발사와 펌웨어 업데이트 기록 공유하기" #. TRANSLATORS: command line option msgid "Show all results" msgstr "모든 결과 표시" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "클라이언트와 데몬 버전 표시" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "지정한 도메인의 자세한 데몬 정보 표시" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "모든 도메인의 디버깅 정보 표시" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "디버깅 옵션을 표시합니다" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "업데이트할 수 없는 장치 표시" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "추가 디버깅 정보 표시" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "펌웨어 업데이트 기록 보기" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "dbx의 계산된 버전 표시" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "마지막으로 시도한 업데이트의 디버그 정보 표시" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "펌웨어 업데이트 상태 표시" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "지금 종료하시겠습니까?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "새 키로 펌웨어 서명" msgid "Sign data using the client certificate" msgstr "클라이언트 인증서로 데이터 서명" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "클라이언트 인증서로 데이터 서명" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "클라이언트 인증서로 업로드된 데이터 서명" msgid "Signature" msgstr "서명" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "서명한 페이로드" #. TRANSLATORS: file size of the download msgid "Size" msgstr "크기" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "이 펌웨어를 업데이트하면 장치 비밀키 일부가 훼손될 수 있습니다." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "원본" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "DFU 장치의 제조사/제품 ID 지정" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "dbx 데이터베이스 파일 지정" msgid "Specify the number of bytes per USB transfer" msgstr "USB 전송 당 바이트 수 지정" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "문자열" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "성공" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "모든 장치를 활성화함" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "원격 저장소를 비활성화함" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "새 메타데이터를 다운로드함:" #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "원격 저장소를 활성화하고 새로 고침" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "원격 저장소를 활성화함" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "펌웨어 설치 성공" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "설정을 수정함" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "원격 저장소를 수정함" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "메타데이터를 수동으로 업데이트함" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "장치 체크섬을 업데이트함" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "보고서 %u개 업로드함" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "장치 체크섬을 검증함" #. TRANSLATORS: the device showed up in time #, c-format msgid "Successfully waited %.0fms for device" msgstr "%.0f밀리초동안 장치 응답 대기에 성공했습니다" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "요약" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "지원함" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "지원 CPU" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "원격 서버에서 지원함" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "최대 절전" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "RAM 대기" #. TRANSLATORS: longer description msgid "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "대기 상태는 장치를 빨리 전원 절약 대기 모드로 진입하게 합니다. 장치가 대기 상태에 들어가면, 메모리를 물리적으로 제거하고 정보에 접근할 수 있습니다." #. TRANSLATORS: longer description msgid "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "RAM 대기 상태는 장치를 빨리 전원 절약 대기모드로 진입하게 합니다. 장치가 대기 상태에 들어가면, 메모리를 물리적으로 제거하고 정보에 접근할 수 있습니다." #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Idle 절전" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "RAM 절전" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "브랜치를 %s에서 %s(으)로 전환하시겠습니까?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "장치의 펌웨어 브랜치 전환" #. TRANSLATORS: this CLI tool is now preventing system updates msgid "System Update Inhibited" msgstr "시스템 업데이트 동작을 억제했습니다" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "시스템 전원이 매우 부족하여 업데이트를 수행할 수 없습니다" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "시스템 전원이 매우 부족하여 업데이트를 수행할 수 없습니다 (%u%%, 필요 전원 백분율 %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "시스템에 외부 전원을 연결해야 함" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEXT" #. TRANSLATORS: longer description msgid "TPM (Trusted Platform Module) is a computer chip that detects when hardware components have been tampered with." msgstr "TPM(신뢰 플랫폼 모듈)은 하드웨어 부품의 임의 변경 여부를 감지하는 컴퓨터 칩입니다." #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "TPM PCR0 재구축" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "TPM PCR0 재구성 상태가 올바르지 않습니다" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "TPM PCR0 재구성 상태가 이제 올바릅니다" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "TPM 플랫폼 설정" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "TPM 재구성" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "TPM이 PCR을 비웁니다" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "태그" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "에뮬레이팅 목적으로 태깅함" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "오염됨" msgid "Target" msgstr "대상" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "JSON 매니페스트 장치를 시험합니다" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "시험완료" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "%s이(가) 시험함" #. TRANSLATORS: someone we trust has tested this msgid "Tested by trusted vendor" msgstr "신뢰하는 제조사에서 시험했습니다" #. TRANSLATORS: longer description msgid "The Intel Management Engine Key Manifest must be valid so that the device firmware can be trusted by the CPU." msgstr "인텔 관리 엔진 키 매니페스트가 올바르게 설정되어 있어야 CPU에서 장치 펌웨어를 신뢰할 수 있습니다." #. TRANSLATORS: longer description msgid "The Intel Management Engine controls device components and needs to have a recent version to avoid security issues." msgstr "인텔 관리 엔진은 장치 구성 요소를 관리하며 보안 문제를 막도록 최신 버전을 설치해야 합니다." #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS는 $OS_RELEASE:NAME$와(과) 별개로 운영되는 독립된 법적 단체에서 운영하는 무료 서비스입니다. 배포판 개발사에서 펌웨어 업데이트와 시스템 및 연결한 장치의 호환성을 검증한다는 보장이 없습니다. 모든 펌웨어는 장치 제조사(OEM)에서 직접 제공합니다." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Platform Configuration is used to check whether the device start process has been modified." msgstr "TPM(신뢰 플랫폼 모듈) 플랫폼 구성 정책은 장치가 상태가 바뀐 프로세스를 시작하는지 여부를 확인할 때 사용합니다." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Reconstruction is used to check whether the device start process has been modified." msgstr "TPM(신뢰 플랫폼 모듈) 재구성 정책은 장치가 상태가 바뀐 프로세스를 시작하는지 여부를 확인할 때 사용합니다." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "TPM PCR0이 재구축한 값과 다릅니다." #. TRANSLATORS: longer description msgid "The UEFI Platform Key is used to determine if device software comes from a trusted source." msgstr "UEFI 플랫폼 키는 장치 프로그램이 신뢰하는 공급원에서 제공하는지 여부를 확인할 때 사용합니다." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "데몬에서 제3자 코드를 불러왔으며 업스트림 개발자는 더 이상 지원하지 않습니다!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "장치 버전이 일치하지 않습니다: %s이지만, %s이(가) 필요함" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "%s의 펌웨어는 하드웨어 제조사 %s에서 제공하지 않았습니다." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "시스템 시계가 올바르게 설정되어 있지 않습니다. 파일 다운로드가 실패할 수도 있습니다." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been re-inserted." msgstr "장치 USB 케이블을 뽑고난 후 다시 연결하면 업데이트를 계속합니다." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged and then re-inserted." msgstr "장치 USB 케이블을 뽑고난 후 다시 연결하면 업데이트를 계속합니다." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged." msgstr "장치 USB 케이블을 뽑으면 업데이트를 계속합니다." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "제조사에서 출시 기록을 제공하지 않았습니다." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "장치에 문제가 있습니다:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "차단된 펌웨어 파일 없음" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "허용된 펌웨어가 없습니다." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "이 장치는 %s(으)로 %s 명령을 실행하면 되돌립니다." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "이 펌웨어는 LVFS 커뮤니티 구성원이 제공하며, 원 하드웨어 제조사에서 제공(또는 지원)하지 않습니다." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "이 패키지는 검증되지 않았습니다. 올바르게 작동하지 않을 수도 있습니다." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "이 프로그램은 루트 권한으로만 올바르게 작동할 수도 있습니다" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "이 원격 저장소에서는 하드웨어 제조사에서 검증 단계를 진행 중인 펌웨어를 배포합니다. 펌웨어 업데이트 도중 및 이후 문제가 발생했을 경우를 대비하여 직접 펌웨어를 다운그레이드할 방편을 확보하기를 추천합니다." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "이 시스템에서는 펌웨어 설정을 지원하지 않습니다" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "시스템에 HSI 런타임 문제가 있습니다." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "시스템에서 HSI 보안 등급 낮음을 사용 중입니다." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "이 도구를 사용하면 시스템 관리자가 UEFI dbx 업데이트를 적용할 수 있습니다." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "이 도구를 사용하면 시스템 관리자가 UpdateCapsule 작업을 디버그할 수 있습니다." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "이 도구를 사용하면 시스템 관리자가 펌웨어 설치 및 다운그레이드 등 동작을 수행하도록 fwupd 데몬에 질의하고 제어할 수 있습니다." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "이 도구를 사용하면 시스템 관리자가 fwupd 플러그인을 호스트 시스템에 설치하지 않고 사용할 수 있습니다." #. TRANSLATORS: the %1 is a BIOS setting name. #. * %2 and %3 are the values, e.g. "True" or "Windows10" #, c-format msgid "This tool can change the BIOS setting '%s' from '%s' to '%s' automatically, but it will only be active after restarting the computer." msgstr "이 도구에서 현재 '%s' BIOS 설정을 '%s'에서 '%s'(으)로 자동으로 바꿀 수 있습니다만, 컴퓨터를 다시 시작해야 합니다." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "이 도구는 루트로만 사용할 수 있습니다" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "이 도구는 시스템 펌웨어에서 TPM 이벤트 로그를 읽어서 해석합니다." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "일시적 실패" #. TRANSLATORS: item is TRUE msgid "True" msgstr "참" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "신뢰하는 메타데이터" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "신뢰하는 페이로드" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "형식" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI Bootservice Variables" msgstr "UEFI 부트 서비스 변수" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition may not be set up correctly" msgstr "UEFI ESP 파티션이 올바르게 설정되지 않았을 수도 있음" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "UEFI ESP 파티션이 감지되지 않았거나 설정되지 않았음" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI 펌웨어 유틸리티" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "UEFI 플랫폼 키" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "UEFI 보안 부트" #. TRANSLATORS: longer description msgid "UEFI Secure Boot prevents malicious software from being loaded when the device starts." msgstr "UEFI 보안 부팅은 장치를 시작할 때 악의적인 프로그램을 불러오지 못하게 막아줍니다." #. TRANSLATORS: longer description msgid "UEFI boot service variables should not be readable from runtime mode." msgstr "UEFI 부트 서비스 변수는 런타임 모드에서 읽어올 수 없어야 합니다." #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI bootservice variables" msgstr "UEFI 부트 서비스 변수" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "UEFI 캡슐 업데이트를 사용할 수 없거나 펌웨어 설정에서 비활성화됨" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "UEFI dbx 유틸리티" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "레거시 BIOS 모드에서 UEFI 펌웨어를 업데이트할 수 없음" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "UEFI 플랫폼 키" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "UEFI 보안 부트" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "서비스에 연결할 수 없음" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "속성 항목을 찾을 수 없습니다" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "현재 드라이버 바인드 해제" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "차단 해제할 펌웨어:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "지정한 펌웨어 설치 방지 해제" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "암호화되지 않음" #. TRANSLATORS: command description msgid "Uninhibit the system to allow upgrades" msgstr "업그레이드를 허용하도록 시스템 억제를 해제합니다" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "알 수 없음" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "알 수 없는 장치" msgid "Unlock the device to allow access" msgstr "접근을 허용하려면 장치 잠금을 해제하십시오" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "잠금 해제됨" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "펌웨어에 접근할 수 있도록 장치 잠금을 해제" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "ESP 마운트 해제" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "업데이트 중 디버깅 플래그 설정 해제" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "서명하지 않은 페이로드" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "지원하지 않는 데몬 버전 %s, 클라이언트 버전 %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "오염되지 않음" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "업데이트 가능" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "업데이트 오류" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "업데이트 이미지" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "업데이트 메시지" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "업데이트 상태" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "알 수 없는 이유로 업데이트가 실패했습니다. 더 많은 정보를 보려면 다음 URL을 참조하십시오:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "지금 업데이트하시겠습니까?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "업데이트 시 다시 시작 필요함" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "현재 ROM 내용으로 저장된 암호화 해시를 업데이트" msgid "Update the stored device verification information" msgstr "저장된 장치 검증 정보 업데이트" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "저장된 메타데이터를 현재 내용으로 업데이트" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "모든 지정 장치를 최신 펌웨어 버전으로 업데이트합니다. 지정하지 않으면 모든 장치를 업데이트합니다." msgid "Updating" msgstr "업데이트" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "%s을(를) %s에서 %s(으)로 업데이트 중..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "%s 업데이트 중..." #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "%s을(를) %s에서 %s(으)로 업그레이드하시겠습니까?" #. TRANSLATORS: ask the user to share, %s is something #. * like: "Linux Vendor Firmware Service" #, c-format msgid "Upload these anonymous results to the %s to help other users?" msgstr "다른 사용자에게 도움을 줄 익명 결과를 %s에 업로드하시겠습니까?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "펌웨어 보고서를 업로드하면 하드웨어 제작사에서 실제 장치에 업데이트를 적용했을 때 성공 및 실패 여부를 빠르게 알 수 있습니다." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "중요도" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "도움말을 보려면 %s 명령을 사용하십시오" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "취소하려면 CTRL^C를 누르십시오." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "도움말을 보려면 fwupdtool --help를 실행하십시오" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "펌웨어를 설치할 때 quirk 플래그 사용" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "사용자에게 알림이 표시됨" #. TRANSLATORS: remote filename base msgid "Username" msgstr "사용자 이름" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "올바름" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "ESP 내용 검사 중..." #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "파생형" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "제조사" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "검증 중…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "버전" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "버전[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "경고" #. TRANSLATORS: command description msgid "Wait for a device to appear" msgstr "장치 출현을 기다립니다" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "기다리는 중…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "하드웨어 변경 사항 감시" #. TRANSLATORS: check various UEFI and ACPI tables are unchanged after the #. update msgid "Will measure elements of system integrity around an update" msgstr "업데이트 전후 시스템 무결성 요소를 확인합니다" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "파일의 펌웨어를 장치에 기록" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "파일의 펌웨어를 파티션 하나에 기록" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "파일에 기록 중:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "쓰는 중…" #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from the system firmware setup, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "리눅스로 부팅할 수 없거나 시스템의 불안정성을 야기할 수 있으므로 시스템 펌웨어 설정을 쉽게 되돌릴 수 있는지 확인해야 합니다." #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "배포판 개발사에서 펌웨어 업데이트와 시스템 및 연결된 장치간의 호환성을 검증한다는 보장이 없습니다." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "이 펌웨어를 사용했을 때 하드웨어가 손상될 수 있으며, 이 릴리스를 설치하면 %s의 제품 보증을 무효화할 수도 있습니다." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "시스템에서 %s의 BKC를 설정했습니다." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[CHECKSUM]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[DEVICE-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[DEVICE-ID|GUID] [BRANCH]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[<장치-ID>|] [<버전>]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[FILE FILE_SIG REMOTE-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[<파일이름1>] [<파일이름2>]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[<이유>]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[<설정1>] [<설정2>]..." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[<설정1>] [<설정2>] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SMBIOS-FILE|HWIDS-FILE]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "기본값" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "fwupd TPM 이벤트 로그 유틸리티" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd 플러그인" fwupd-1.9.16/po/ky.po000066400000000000000000000032341460375044200143450ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Ilyas Bakirov , 2018 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Kyrgyz (http://www.transifex.com/freedesktop/fwupd/language/ky/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ky\n" "Plural-Forms: nplurals=1; plural=0;\n" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Кошулду" #. TRANSLATORS: chip ID, e.g. "0x58200204" msgid "Chip ID" msgstr "Чиптин ID'си" #. TRANSLATORS: detected a DFU device msgid "Found" msgstr "Табылды" #. TRANSLATORS: Appstream ID for the hardware type msgid "ID" msgstr "ID" #. show message in progressbar #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing %s" msgstr "Орнотулууда: %s" msgid "Mode" msgstr "Режими" #. TRANSLATORS: DFU protocol version, e.g. 1.1 msgid "Protocol" msgstr "Протокол" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Окуулууда..." #. TRANSLATORS: device state, i.e. appIDLE msgid "State" msgstr "Абалы" #. TRANSLATORS: probably not run as root... #. TRANSLATORS: device has failed to report status #. TRANSLATORS: device status, e.g. "OK" msgid "Status" msgstr "Статус" #. TRANSLATORS: currect daemon status is unknown msgid "Unknown" msgstr "Белгисиз" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Жазылууда..." msgid "fwupd" msgstr "fwupd" fwupd-1.9.16/po/lt.po000066400000000000000000001366251460375044200143540ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Moo, 2019,2023 # Moo, 2020-2021 # Moo, 2023 # Rimas Kudelis , 2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Lithuanian (http://app.transifex.com/freedesktop/fwupd/language/lt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: lt\n" "Plural-Forms: nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "Liko %.0f minutė" msgstr[1] "Liko %.0f minutės" msgstr[2] "Liko %.0f minučių" msgstr[3] "Liko %.0f minutės" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "„%s“ akumuliatoriaus naujinimas" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "„%s“ kameros naujinimas" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "„%s“ konfigūracijos naujinimas" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "„%s“ valdiklio naujinimas" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "„%s“ įrenginio naujinimas" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "„%s“ įtaisytojo valdiklio naujinimas" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "„%s“ klaviatūros naujinimas" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "„%s“ ME naujinimas" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "„%s“ pelės naujinimas" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "„%s“ sistemos naujinimas" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "„%s“ jutiklinio kilimėlio naujinimas" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "„%s“ naujinimas" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s versija" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u diena" msgstr[1] "%u dienos" msgstr[2] "%u dienų" msgstr[3] "%u diena" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u įrenginiui yra prieinamas programinės aparatinės įrangos naujinimas." msgstr[1] "%u įrenginiams yra prieinamas programinės aparatinės įrangos naujinimas." msgstr[2] "%u įrenginių yra prieinamas programinės aparatinės įrangos naujinimas." msgstr[3] "%u įrenginiui yra prieinamas programinės aparatinės įrangos naujinimas." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u valanda" msgstr[1] "%u valandos" msgstr[2] "%u valandų" msgstr[3] "%u valanda" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "Yra palaikomas %u vietinis įrenginys" msgstr[1] "Yra palaikomi %u vietiniai įrenginiai" msgstr[2] "Yra palaikoma %u vietinių įrenginių" msgstr[3] "Yra palaikomas %u vietinis įrenginys" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minutė" msgstr[1] "%u minutės" msgstr[2] "%u minučių" msgstr[3] "%u minutė" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u sekundė" msgstr[1] "%u sekundės" msgstr[2] "%u sekundžių" msgstr[3] "%u sekundė" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Reikalingas veiksmas:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Aktyvuoti įrenginius" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Aktyvuoti laukiančius įrenginius" msgid "Activate the new firmware on the device" msgstr "Aktyvinti naująją aparatinę programinę įrangą įrenginyje" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Aktyvinamas aparatinės programinės įrangos naujinimas" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Aktyvuojama aparatinė programinė įranga, skirta" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Amžius" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Sutikti ir įjungti šią nuotolinę saugyklą?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alternatyvus komandos „%s“ vardas" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Leisti sendinti aparatinės programinės įrangos versijas" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Leisti iš naujo įdiegti esamas aparatinės programinės įrangos versijas" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Naujinimo užbaigimui, reikia paleisti sistemą iš naujo." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Naujinimo užbaigimui, reikia išjungti sistemą." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Atsakyti taip į visus klausimus" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Taikyti aparatinės programinės įrangos naujinimus" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Taikyti naujinimą, netgi kai nepatartina" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Taikyti naujinimo failus" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Taikomas naujinimas…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Patvirtinta programinė aparatinė įranga:" msgstr[1] "Patvirtinta programinė aparatinė įranga:" msgstr[2] "Patvirtinta programinė aparatinė įranga:" msgstr[3] "Patvirtinta programinė aparatinė įranga:" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Pridėti į aparatinės programinės įrangos veikseną" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Nustatoma tapatybė…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Norint keičiamajame įrenginyje sendinti aparatinę programinę įrangą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Norint šiame įrenginyje sendinti aparatinę programinę įrangą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Norint modifikuoti aparatinės programinės įrangos naujinimams naudojamą sukonfigūruotą saugyklą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Norint modifikuoti tarnybos konfigūraciją, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Norint nustatyti patvirtintos aparatinės programinės įrangos sąrašą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Norint pasirašyti duomenis naudojant kliento liudijimą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Norint perjungti į naują aparatinės programinės įrangos versiją, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Norint atrakinti įrenginį, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Norint keičiamajame įrenginyje atnaujinti aparatinę programinę įrangą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Norint šiame įrenginyje atnaujinti aparatinę programinę įrangą, reikalingas tapatybės nustatymas" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Norint atnaujinti saugomas įrenginio kontrolines sumas, reikalingas tapatybės nustatymas" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "BIOS sistemos naujinimai iš LVFS arba „Windows Update“" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Susieti naują branduolio tvarkyklę" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Atsisakyti" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Atsisakyta" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Negalima taikyti, nes dbx naujinimas jau yra pritaikytas." #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Pakeistas" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Kontrolinė suma" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Išvalo rezultatus iš paskutinio naujinimo" #. TRANSLATORS: error message msgid "Command not found" msgstr "Komanda nerasta" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Konvertuoti aparatinės programinės įrangos failą" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Sukurtas" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Dabartinė versija" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "ĮRENGINIO-ID|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "ĮAPĮA paslaugų programa" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Derinimo parametrai" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Išskleidžiama…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Aprašas" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Atskirti į pradinio įkėliklio veikseną" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Įrenginio požymiai" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Įrenginio ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Pridėtas įrenginys:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Pakeistas įrenginys:" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Įrenginys yra užrakintas" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Pašalintas įrenginys:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Įrenginiai, kurie buvo sėkmingai atnaujinti:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Įrenginiai, kurių nepavyko atnaujinti:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Įrenginiai, neturintys prieinamų aparatinės programinės įrangos naujinimų: " #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Išjungtas" msgid "Disabled fwupdate debugging" msgstr "Išjungtas „fwupdate“ derinimas" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Išjungia nurodytą nuotolinę saugyklą" #. TRANSLATORS: command line option msgid "Display version" msgstr "Rodyti versiją" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Netikrinti ar yra senų metaduomenų" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Netikrinti ar yra istorijos apie kurią nepranešta" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Nerašyti į istorijos duomenų bazę" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Atlikta!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Sendinti %s iš %s į %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Sendina aparatinę programinę įrangą įrenginyje" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Sendinama %s iš %s į %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Sendinamas %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Atsisiųsti failą" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Atsisiunčiama…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Iškloti SMBIOS duomenis iš failo" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Trukmė" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Nurodytas ESS (angl. ESP) nebuvo teisingas" msgid "Enable" msgstr "Įjungti" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Įjungti aparatinės programinės įrangos naujinimo palaikymą palaikomose sistemose" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Įjungti šią nuotolinę saugyklą?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Įjungta" msgid "Enabled fwupdate debugging" msgstr "Įjungtas „fwupdate“ derinimas" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Įjungia nurodytą nuotolinę saugyklą" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Įjungdami šį funkcionalumą, su juo susijusią riziką prisiimate sau. Apie bet kokias šių naujinimų sukeltas problemas turėtumėte pranešti tiesiogiai atitinkamam aparatinės įrangos gamintojui. Tik problemas, susijusias su pačiu naujinimo procesu, derėtų registruoti adresu $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Šios nuotolinės saugyklos įjungimas yra jūsų pačių rizika." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Šifruotas" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Pašalinti visą aparatinės programinės įrangos naujinimų istoriją" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Ištrinama…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Išeiti po nedidelės delsos" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Išeiti, įkėlus modulį" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Eksportuoti aparatinės programinės įrangos failo struktūrą į XML" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "FAILAS" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "FAILAS [ĮRENGINIO-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "FAILO-VARDAS" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "FAILO-VARDAS LIUDIJIMAS PRIVATUSIS-RAKTAS" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "FAILO-VARDAS ĮRENGINIO-ALT-VARDAS|ĮRENGINIO-ALT-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "FAILO-VARDAS ĮRENGINIO-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "FAILO-VARDAS [ĮRENGINIO-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "FAILO-VARDAS [APARATINĖS-PROGRAMINĖS-ĮRANGOS-TIPAS]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Patyrė nesėkmę" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Nepavyko pritaikyti naujinimo" #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Nepavyko prisijungti prie tarnybos" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Nepavyko gauti laukiančių įrenginių" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Nepavyko įdiegti aparatinės programinės įrangos naujinimo" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Nepavyko įkelti vietinio dbx" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Nepavyko įkelti gudrybių" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Nepavyko įkelti sisteminio dbx" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Nepavyko užrakinti" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Nepavyko išanalizuoti argumentų" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Nepavyko išanalizuoti failo" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Nepavyko paleisti iš naujo" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Nepavyko nustatyti prisistatymo lango veikseną" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Nepavyko patikrinti ESP turinio" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Failo vardas" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Failo vardo parašas" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Reikalingas failo vardas" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Pagrindinis aparatinės programinės įrangos URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Aparatinės programinės įrangos naujinimo „D-Bus“ tarnyba" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Aparatinės programinės įrangos naujinimo tarnyba" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Aparatinės programinės įrangos naujinimai" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Aparatinės programinės įrangos paslaugų programa" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Aparatinė programinė įranga jau užblokuota" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Aparatinės programinės įrangos naujinimai" msgid "Firmware updates are not supported on this machine." msgstr "Šiame kompiuteryje aparatinės programinės įrangos naujinimai yra negalimi." msgid "Firmware updates are supported on this machine." msgstr "Šiame kompiuteryje aparatinės programinės įrangos naujinimai yra galimi." #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Požymiai" msgid "Force the action ignoring all warnings" msgstr "Priverstinai atlikti veiksmą nepaisant visų įspėjimų" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Rastas" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Gauti visus „fwupd“ palaikomus įrenginio požymius (gaireles)" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Gauti visus įrenginius, kurie palaiko aparatinės programinės įrangos naujinimus" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Gauti visus įjungtus sistemoje registruotus įskiepius" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Gauna išsamesnę informaciją apie aparatinės programinės įrangos failą" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Gauna sukonfigūruotas nuotolines saugyklas" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Gauna naujinimų sąrašą prijungtai aparatinei įrangai" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Gauna laidas įrenginiui" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Gauna rezultatus iš paskutinio naujinimo" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Neveiklus…" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Nepaisyti aparatinės programinės įrangos kontrolinių sumų neatitikimų" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Įdiegimo trukmė" msgid "Install old version of signed system firmware" msgstr "Įdiegti seną pasirašytos sistemos aparatinės programinės įrangos versiją" msgid "Install old version of unsigned system firmware" msgstr "Įdiegti seną nepasirašytos sistemos aparatinės programinės įrangos versiją" msgid "Install signed device firmware" msgstr "Įdiegti pasirašytą įrenginio aparatinę programinę įrangą" msgid "Install signed system firmware" msgstr "Įdiegti pasirašytą sistemos aparatinę programinę įrangą" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Iš pradžių, įdiegti į viršesnį įrenginį" msgid "Install unsigned device firmware" msgstr "Įdiegti nepasirašytą įrenginio aparatinę programinę įrangą" msgid "Install unsigned system firmware" msgstr "Įdiegti nepasirašytą sistemos aparatinę programinę įrangą" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Diegiama aparatinė programinė įranga…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Diegiamas aparatinės programinės įrangos naujinimas…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Įdiegiama ties %s…" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Vidinis įrenginys" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "RAKTAS,REIKŠMĖ" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Raktinė" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "VIETA" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Liko mažiau kaip minutė" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licencija" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "„Linux Vendor Firmware Service“ (stabili aparatinė programinė įranga)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "„Linux Vendor Firmware Service“ (testuojama aparatinė programinė įranga)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Linux branduolys" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Išvardyti dbx esančius įrašus" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Išvardyti prieinamus aparatinės programinės įrangos naujinimus" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Išvardyti prieinamus aparatinės programinės įrangos tipus" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Išvardija ESP esančius failus" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Įkeliama…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Užrakintas" msgid "MEI version" msgstr "MEI versija" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Metaduomenų parašas" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metaduomenų URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metaduomenys gali būti gauti iš „Linux Vendor Firmware Service“ paslaugos." #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifikuoja nurodytą nuotolinę saugyklą" msgid "Modify a configured remote" msgstr "Modifikuoti sukonfigūruotą nuotolinę saugyklą" msgid "Modify daemon configuration" msgstr "Modifikuoti tarnybos konfigūraciją" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Stebėti tarnybą, ar yra įvykių" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Prijungia ESP" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Reikia paleisti iš naujo" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Nauja versija" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Nenurodytas joks veiksmas!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Nėra sendinimų, skirtų %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Nerasta jokių aparatinės programinės įrangos ID" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Neaptikta jokios aparatinės įrangos su aparatinės programinės įrangos naujinimo galimybėmis" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nerasta jokių įskiepių" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Nėra prieinamų laidų" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Šiuo metu nėra įjungtos jokios nuotolinės saugyklos, taigi, nėra prieinami jokie metaduomenys." #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nebuvo pritaikyti jokie naujinimai" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Nerastas" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Nepalaikomas" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "Gerai" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Rodyti tik vieną PCR reikšmę" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Išvestis JSON formatu" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Nustelbti numatytąjį ESS (angl. ESP) kelią" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "KELIAS" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Slaptažodis" msgid "Payload" msgstr "Naudingoji apkrova" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Užbaigta procentinė dalis" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Atlikti operaciją?" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Įveskite skaičių nuo 0 iki %u: " #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Ankstesnė versija" msgid "Print the version number" msgstr "Parodyti versijos numerį" msgid "Print verbose debug statements" msgstr "Parodyti išsamius derinimo sakinius" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Pirmenybė" msgid "Proceed with upload?" msgstr "Tęsti išsiuntimą?" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Nuosavybinė" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Užklausti aparatinės programinės įrangos naujinimų palaikymo" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Nuskaityti aparatinę programinę įrangą iš įrenginio į failą" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Skaityti aparatinę programinę įrangą iš vieno skaidinio į failą" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Skaitoma iš %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Skaitoma…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Paleidžiama iš naujo…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Iš naujo įkelti metaduomenis iš nuotolinio serverio" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Įdiegti iš naujo %s į %s?" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Iš naujo įdiegti įrenginyje aparatinę programinę įrangą" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Iš naujo įdiegiama %s su %s... " #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Nuotolinės saugyklos ID" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Pakeisti duomenis esamame aparatinės programinės įrangos faile" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Ataskaitų URI" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Reikalauja interneto ryšio" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Paleisti iš naujo dabar?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Paleisti tarnybą iš naujo, kad pakeitimas įsigaliotų?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Įrenginys paleidžiamas iš naujo…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Grąžinti visus kompiuterio aparatinės įrangos ID" #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Išsamesnei informacijai, paleiskite „fwupdmgr get-upgrades“." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Paleisti sudėtinę įskiepio išvalymo programą, naudojant install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Paleisti sudėtinę įskiepio paruošimo programą, naudojant install-blob" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Kai įmanoma, suplanuoti įdiegimą kitam paleidimui iš naujo" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Suplanuojama…" #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Išsamesnės informacijos ieškokite adresu %s." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Pasirinktas įrenginys" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Naujinimo metu nustatyti derinimo požymį" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Nustato patvirtintą aparatinės programinės įrangos sąrašą" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Nuostatos bus pritaikytos perleidus sistemą" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Bendrinti aparatinės programinės įrangos istoriją su plėtotojais" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Rodyti visus rezultatus" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Rodyti kliento ir tarnybos versijas" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Rodyti derinimo parametrus" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Rodyti negalimus naujinti įrenginius" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Rodyti papildomą derinimo informaciją" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Rodyti aparatinės programinės įrangos naujinimų istoriją" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Rodyti apskaičiuotą dbx versiją" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Rodyti derinimo žurnalą iš paskutinio bandyto naujinimo" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Rodyti aparatinės programinės įrangos naujinimo būseną" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Išjungti dabar?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Pasirašyti aparatinę programinę įrangą nauju raktu" msgid "Sign data using the client certificate" msgstr "Pasirašyti duomenis, naudojant kliento liudijimą" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Pasirašyti duomenis, naudojant kliento liudijimą" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Pasirašyti išsiunčiamus duomenis naudojant kliento liudijimą" msgid "Signature" msgstr "Parašas" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Dydis" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Nurodyti ĮAPĮA įrenginio gamintojo ir/ar produkto ID" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Nurodyti dbx duomenų bazės failą" msgid "Specify the number of bytes per USB transfer" msgstr "Nurodyti baitų skaičių tenkantį vienam USB persiuntimui" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Pavyko" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Sėkmingai aktyvuoti visi įrenginiai" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Sėkmingai atsisiųsti nauji metaduomenys: " #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Aparatinė programinė įranga sėkmingai įdiegta" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Sėkmingai atnaujintos įrenginių kontrolinės sumos" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Santrauka" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Palaikomas" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Palaikomas nuotoliniame serveryje" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "Sistema reikalauja išorinio maitinimo šaltinio" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEKSTAS" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" msgid "Target" msgstr "Paskirtis" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS – tai nemokama paslauga, teikiama nepriklausomo juridinio asmens ir nesusijusi su „$OS_RELEASE:NAME$“. Jūsų OS platintojas gali nebūti patikrinęs šių aparatinės programinės įrangos naujinimų suderinamumo su jūsų sistema ar prijungtais įrenginiais. Visa aparatinė programinė įranga LVFS pateikiama tik aparatinės įrangos gamintojų." #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Nėra jokios patvirtintos aparatinės programinės įrangos." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Šis paketas nebuvo patvirtintas, jis gali tinkamai neveikti." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Ši programa gali tinkamai veikti tik pagrindinio naudotojo teisėmis" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Šioje nuotolinėje saugykloje laikoma aparatinė programinė įranga, kurios testavimo aparatinės įrangos gamintojai dar neužbaigė. Jei naudosite ją, įsitikinkite, jog nepavykus naujinimui ar prireikus dėl kitų priežasčių, turėsite galimybę sugrąžinti ankstesnę aparatinės programinės įrangos versiją." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Šis įrankis leidžia administratoriui taikyti UEFI dbx naujinimus." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Šį įrankį gali naudoti tik pagrindinis naudotojas" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipas" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI aparatinės programinės įrangos paslaugų programa" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "UEFI dbx paslaugų programa" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Atsieti dabartinę tvarkyklę" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Nešifruotas" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Nežinoma" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Nežinomas įrenginys" msgid "Unlock the device to allow access" msgstr "Atrakinti įrenginį, kad būtų leista prieiga" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Atrakintas" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Atrakina įrenginį aparatinės programinės įrangos prieigai" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Atjungia ESP" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Naujinimo metu pašalinti derinimo požymį" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Nepalaikoma tarnybos versija %s, kliento programos versija yra %s" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Galimas naujinti" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Naujinimo klaida" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Naujinimo pranešimas" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Naujinimo būsena" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Naujinimo nesėkmė yra žinoma problema, išsamesnei informacijai apsilankykite šiame URL:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Naujinti dabar?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Naujinimas reikalauja paleidimo iš naujo" msgid "Update the stored device verification information" msgstr "Naujinti saugomą įrenginio patikrinimo informaciją" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Naujinti saugomus metaduomenis esamu turiniu" msgid "Updating" msgstr "Naujinama" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Naujinama „%s“ iš %s į %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Naujinama %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Naujinti %s iš %s į %s?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Aparatinės programinės įrangos ataskaitų išsiuntimas padeda aparatinės įrangos tiekėjams greitai atpažinti nesėkmingus bei sėkmingus naujinimus tikruose įrenginiuose." #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Naudotojui buvo pranešta" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Naudotojo vardas" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Tikrinamas ESP turinys…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variantas" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Patikrinama…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Versija" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "ĮSPĖJIMAS" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Laukiama…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Stebėti aparatinės įrangos pakeitimus" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Rašyti aparatinę programinę įrangą iš failo į įrenginį" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Rašyti aparatinę programinę įrangą iš failo į vieną skaidinį" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Rašomas failas:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Rašoma…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Gali būti, kad jūsų platintojas nėra patvirtinęs jokių aparatinės programinės įrangos naujinimų suderinamumo su jūsų sistema ar prijungtais įrenginiais." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[KONTROLINĖ-SUMA]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[ĮRENGINIO-ID|GUID]" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "„fwupd“ TPM įvykių žurnalo paslaugų programa" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd įskiepiai" fwupd-1.9.16/po/meson.build000066400000000000000000000003561460375044200155260ustar00rootroot00000000000000i18n.gettext(meson.project_name(), preset: 'glib', args: [ '--default-domain=' + meson.project_name(), ] ) run_target('fix-translations', command: [ fix_translations, join_paths(meson.project_source_root(), 'po') ] ) fwupd-1.9.16/po/nl.po000066400000000000000000001441561460375044200143440ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Philip Goto , 2023 # Richard E. van der Luit , 2017,2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Dutch (http://app.transifex.com/freedesktop/fwupd/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minuut resterend" msgstr[1] "%.0f minuten resterend" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "BMC-update voor %s" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "Batterij-update voor %s" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "CPU-microcode-update voor %s" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "Camera-update voor %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "Configuratie-update voor %s" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Gebruikers-ME-update voor %s" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Controller-update voor %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Bedrijfs-ME-update voor %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Apparaat-update voor %s" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "Scherm-update voor %s" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "Dock-update voor %s" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "Schijf-update voor %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Geïntegreerde controller-update voor %s" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "Fingerafdruklezer-update voor %s" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "Flashopslag-update voor %s" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "GPU-update voor %s" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "Tekentablet-update voor %s" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "Toetsenbord-update voor %s" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "ME-update voor %s" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "Muis-update voor %s" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "Netwerkinterface-update voor %s" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "SSD-update voor %s" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "Opslag-controller-update voor %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Systeem-update voor %s" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "TPM-update voor %s" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Thunderbolt-controller-update voor %s" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "Touchpad-update voor %s" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "USB-dock-update voor %s" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "USB-ontvanger-update voor %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Update voor %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s kan momenteel niet worden bijgewerkt" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s-productiemodus" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s-overschrijving" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s-versie" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dag" msgstr[1] "%u dagen" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u uur" msgstr[1] "%u uur" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuut" msgstr[1] "%u minuten" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u seconde" msgstr[1] "%u seconden" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (drempelwaarde: %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(verouderd)" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Actie vereist:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Apparaten activeren" msgid "Activate the new firmware on the device" msgstr "De nieuwe firmware op het apparaat activeren" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Firmware-update activeren" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Leeftijd" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias voor %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Downgraden van oude firmware-versies toestaan" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Herinstalleren van bestaande firmware-versies toestaan" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Wisselen van firmware-tak toestaan" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Alternatieve tak" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "Een update is bezig" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Een update vereist een herstart om te voltooien." #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Firmware-updates toepassen" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Updatebestanden toepassen" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Update toepassen…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Goedgekeurde firmware:" msgstr[1] "Goedgekeurde firmware:" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Aan firmware-modus vastmaken" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Authenticeren…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Om de firmware op een verwijderbaar apparaat te downgraden moet u toestemming verlenen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Om de firmware op deze computer te downgraden moet u toestemming verlenen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Authenticatie is vereist om BIOS-instellingen aan te passen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Authenticatie is vereist om daemon-configuratie aan te passen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "Authenticatie is vereist om BIOS-instellingen te lezen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Authenticatie is vereist om naar de nieuwe firmwareversie om te schakelen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Om een apparaat te ontgrendelen moet u toestemming verlenen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Om de firmware op een verwijderbaar apparaat bij te werken moet u toestemming verlenen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Om de firmware op deze computer bij te werken moet u toestemming verlenen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Om de opgeslagen controlesommen op het apparaat bij te werken moet u toestemming verlenen" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Automatisch elke keer uploaden?" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "BIOS-terugrolbescherming" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "BIOS-updates geleverd via LVFS of Windows Update" msgid "BYTES" msgstr "BYTES" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Batterij" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Geblokkeerde versie" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Opstartladerversie" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Tak" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Annuleren" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Geannuleerd" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Gewijzigd" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Controlesom" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Tak selecteren" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Apparaat kiezen" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Firmware kiezen" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Uitgave kiezen" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Kies de ESP:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Volume selecteren" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Wist de resultaten van de laatste update" #. TRANSLATORS: error message msgid "Command not found" msgstr "De opdracht kon niet worden gevonden" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Ondersteund door gemeenschap" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Aanmaakmoment" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Kritiek" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Huidige waarde" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Huidige versie" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "APPARAAT-ID|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU-hulpmiddel" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Foutopsporingsopties" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Uitpakken..." #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Omschrijving" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Details" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Apparaat-flags" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Apparaat-ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Apparaat toegevoegd:" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "Batterij van apparaat is te leeg" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Apparaat gewijzigd:" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "Apparaat is geëmuleerd" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Apparaat is vergrendeld" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Apparaat is onbereikbaar" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Apparaat verwijderd:" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Updatemethode voor apparaat" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Apparaten zonder beschikbare firmware-updates:" #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Apparaten met de nieuwste beschikbare firmware-versie:" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Uitgeschakeld" #. TRANSLATORS: command line option msgid "Display version" msgstr "Versie tonen" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Distributie" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Afgerond!" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Bezig met downgraden van %s van %s naar %s..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "%s downgraden…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Bestand downloaden" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Downloaden…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "SMBIOS-gegevens vanuit een bestand dumpen" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Duur" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Opgegeven ESP is niet geldig" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Geëmuleerd" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Geëmuleerde host" msgid "Enable" msgstr "Inschakelen" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Nieuwe remote inschakelen?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Deze remote inschakelen?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Ingeschakeld" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Ingeschakeld als hardware overeenkomt" #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Versleuteld" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "Versleuteld geheugen" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Opsomming" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Alle firmware-updategeschiedenis wissen" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Wissen…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Afsluiten na een korte vertraging" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Afsluiten nadat de engine geladen is" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "BESTAND" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "BESTAND [APPARAAT-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "BESTANDSNAAM" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "BESTANDSNAAM APPARAAT-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "BESTANDSNAAM [APPARAAT-ID|GUID]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Mislukt" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Update toepassen mislukt" #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Verbinden met daemon mislukt" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Firmware-update installeren mislukt" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Eigenaardigheden laden mislukt" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Vergrendelen mislukt" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Het doorvoeren van argumenten is mislukt" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Bestand parsen mislukt" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Flags voor ‘--filter’ parsen mislukt" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Herstarten mislukt" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Splash-modus instellen mislukt" #. TRANSLATORS: item is FALSE msgid "False" msgstr "Onwaar" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Bestandsnaam" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Bestandsnaam vereist" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Firmware-attestatie" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Firmware-BIOS-descriptor" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Firmware-BIOS-regio" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Firmware Update D-Bus-dienst" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Firmware Update Daemon" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Firmware-updater-verificatie" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Firmware-updates" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Firmware-hulpmiddel" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Firmware-schrijfbescherming" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Firmware-schrijfbeschermingsvergrendeling" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Firmware-attestatie" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Firmware-updates" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Flags" msgid "Force the action ignoring all warnings" msgstr "De actie forceren en alle waarschuwingen negeren" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Gevonden" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Volledige schijfversleuteling gedetecteerd" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "Zekeringsplatform" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID's" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" msgid "Get BIOS settings" msgstr "BIOS-instellingen verkrijgen" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Alle apparaat-flags ondersteund door fwupd verkrijgen" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Alle apparaten verkrijgen die firmware-updates ondersteunen" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Alle ingeschakelde plug-ins geregistreerd op het systeem verkrijgen" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Verkrijgt details over een firmware-bestand" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Verkrijgt een lijst van updates voor verbonden hardware" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Verkrijgt de resultaten van de laatste update" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWIDS-BESTAND" #. TRANSLATORS: the release urgency msgid "High" msgstr "Hoog" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Host-beveiligingsgebeurtenissen" #. TRANSLATORS: error message for unsupported feature #, c-format msgid "Host Security ID (HSI) is not supported" msgstr "Host-beveiligings-ID (HSI) wordt niet ondersteund" #. TRANSLATORS: success, so say thank you to the user msgid "Host Security ID attributes uploaded successfully, thanks!" msgstr "Kenmerken van host-beveiligings-ID succesvol geüpload, bedankt!" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Host-beveiligings-ID:" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "IOMMU-apparaatbeveiliging uitgeschakeld" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "IOMMU-apparaatbeveiliging ingeschakeld" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Slaapt..." #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Installatietijd" msgid "Install old version of signed system firmware" msgstr "Oude versie van ondertekende systeemfirmware installeren" msgid "Install old version of unsigned system firmware" msgstr "Oude versie van niet-ondertekende systeemfirmware installeren" msgid "Install signed device firmware" msgstr "Ondertekende apparaatfirmware installeren" msgid "Install signed system firmware" msgstr "Ondertekende systeemfirmware installeren" msgid "Install unsigned device firmware" msgstr "Niet-ondertekende apparaatfirmware installeren" msgid "Install unsigned system firmware" msgstr "Niet-ondertekende systeemfirmware installeren" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Firmware installeren…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Firmware-update installeren…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Installeren op %s…" #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Geheel getal" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Intern apparaat" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Ongeldig" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Ongeldige argumenten" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Is downgrade" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Is upgrade" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Probleem" msgstr[1] "Problemen" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "SLEUTEL,WAARDE" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "Kernel is niet langer aangetast" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "Kernel is aangetast" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Kernel-lockdown uitgeschakeld" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Kernel-lockdown ingeschakeld" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Sleutelbos" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "LOCATIE" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Laatst bewerkt" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Minder dan één minuut resterend" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licentie" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (stabiele firmware)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (test-firmware)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Linux-kernel" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Linux-kernellockdown" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linux-wisselgeheugen" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Ondersteunde firmware-updates opsommen" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Laden..." #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Vergrendeld" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Laag" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "MEI-sleutelmanifest" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "MEI-productiemodus" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "MEI-overschrijving" msgid "MEI version" msgstr "MEI-versie" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Handmatig specifieke plug-ins inschakelen" #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Maximumlengte" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Maximumwaarde" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Gemiddeld" #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Minimumversie" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Minimumlengte" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Minimumwaarde" msgid "Modify a configured remote" msgstr "Geconfigureerde remote aanpassen" msgid "Modify daemon configuration" msgstr "Daemon-configuratie aanpassen" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "De achtergrondservice controleren op gebeurtenissen" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Vereist een herstart na installatie" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Vereist herstart" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Vereist afsluiten na installatie" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Nieuwe versie" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Geen actie opgegeven!" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Geen firmware-ID's gevonden" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Geen hardware aangetroffen die in staat is firmware bij te werken" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Geen plug-ins gevonden" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Geen uitgaven beschikbaar" #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Geen remotes beschikbaar" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Geen apparaten die kunnen worden bijgewerkt" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Geen updates beschikbaar" msgid "No updates available for remaining devices" msgstr "Geen updates beschikbaar voor resterende apparaten" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Er zijn geen updates toegepast" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Niet goedgekeurd" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Niet gevonden" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Niet ondersteund" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "Oké" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "OK!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Oude versie" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Uitvoer in JSON-formaat" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "PAD" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "dbx-update parsen…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Systeem-dbx parsen…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Wachtwoord" msgid "Payload" msgstr "Inhoud" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Afwachtend" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Percentage voltooid" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Handeling uitvoeren?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Platform-foutopsporing" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Platform-foutopsporing" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Voer een getal in tussen 0 en %u:" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Mogelijke waarden" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Vorige versie" msgid "Print the version number" msgstr "Versienummer afdrukken" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioriteit" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Problemen" msgid "Proceed with upload?" msgstr "Doorgaan met uploaden?" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Processor-terugrolbescherming" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Propriëtair" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "REMOTE-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "REMOTE-ID SLEUTEL WAARDE" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Alleen-lezen" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Firmware van het apparaat uitlezen naar een bestand" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Firmware van één partitie uitlezen naar een bestand" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Lezen van %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Lezen…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Herstarten…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Metadata verversen vanuit externe server" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Firmware op een apparaat herinstalleren" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Bezig met herinstalleren van %s met %s..." #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Uitgavetak" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Uitgave-flags" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Gegevens vervangen in een bestaand firmwarebestand" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Gemeld aan externe server" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Verzoek geannuleerd" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "Vereiste hardware is niet gevonden" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Vereist een opstartlader" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Vereist internetverbinding" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Nu herstarten?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Herstarten apparaat..." #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Runtime-achtervoegsel" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "INSTELLING WAARDE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING1 VALUE1 [SETTING2] [VALUE2]" msgstr "INSTELLING1 WAARDE1 [INSTELLING2] [WAARDE2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "SUBSYSTEEM STUURPROGRAMMA [APPARAAT-ID|GUID]" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "De installatie inplannen voor de volgende herstart, indien mogelijk" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Inplannen..." #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Veilig opstarten uitgeschakeld" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Veilig opstarten ingeschakeld" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Zie %s voor meer details." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Zie %s voor meer informatie." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Geselecteerd apparaat" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Geselecteerd volume" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Serienummer" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "BIOS-instelling ‘%s’ ingesteld op ‘%s’" msgid "Set one or more BIOS settings" msgstr "Eén of meerdere BIOS-instellingen aanpassen" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Stelt de lijst met goedgekeurde firmware in" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Instellingstype" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Instellingen worden toegepast na herstarten van het systeem" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Alle resultaten tonen" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Client- en daemon-versies tonen" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Foutopsporingsopties weergeven" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Apparaten tonen die niet kunnen worden bijgewerkt" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Extra foutopsporingsinformatie weergeven" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Geschiedenis van firmware-updates tonen" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Nu afsluiten?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Firmware met een nieuwe sleutel ondertekenen" msgid "Signature" msgstr "Handtekening" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Ondertekende inhoud" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Grootte" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Bron" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "Tekenreeks" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Voltooid" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmware succesvol geïnstalleerd" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "%u rapport succesvol geüpload" msgstr[1] "%u rapporten succesvol geüpload" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Apparaat-controlesommen succesvol geverifieerd" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Samenvatting" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Ondersteund" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "Ondersteunde CPU" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Ondersteund op externe server" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Tak wisselen van %s naar %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Firmware-tak van het apparaat wisselen" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "Systeem vereist externe voedingsbron" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEKST" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Aangetast" msgid "Target" msgstr "Doel" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Getest" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "Getest door %s" #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Er zijn apparaten met problemen:" #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "Dit systeem ondersteunt firmware-instellingen niet" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Dit systeem heeft HSI-runtimeproblemen." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Dit systeem heeft een laag HSI-beveiligingsniveau." #. TRANSLATORS: item is TRUE msgid "True" msgstr "Waar" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Vertrouwde metagegevens" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Vertrouwde inhoud" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Type" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI-firmwaregereedschap" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "Kan eigenschap niet vinden" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Niet-versleuteld" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Onbekend" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Onbekend apparaat" msgid "Unlock the device to allow access" msgstr "Ontgrendel het apparaat om toegang te verlenen" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Ontgrendeld" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Ontgrendelt het apparaat voor firmware-toegang" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Niet-ondertekende inhoud" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Onaangetast" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Kan worden bijgewerkt" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Updatefout" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Update-afbeelding" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Update-bericht" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Update-toestand" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Nu bijwerken?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Update vereist een herstart" msgid "Update the stored device verification information" msgstr "Opgeslagen apparaatverificatie-informatie bijwerken" msgid "Updating" msgstr "Bijwerken" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Bezig met bijwerken van %s van %s naar %s..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "%s bijwerken…" #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Urgentie" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Gebruik %s voor hulp" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Gebruik Ctrl-C om te annuleren" #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Gebruik ‘fwupdtool --help’ voor hulp" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Gebruikersnaam" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Geldig" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "ESP-inhoud valideren…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variant" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Leverancier" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Valideren..." #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Versie" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Versie[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "WAARSCHUWING" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Wachten…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Hardware-wijzigingen bijhouden" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Firmware van een bestand naar een apparaat schrijven" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Firmware van een bestand naar één partitie schrijven" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Bestand schrijven:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Schrijven..." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[CONTROLESOM]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[APPARAAT-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[APPARAAT-ID|GUID] [TAK]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[APPARAAT-ID|GUID] [VERSIE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[BESTANDSNAAM1][BESTANDSNAAM2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[REDEN]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[INSTELLING1] [INSTELLING2]…" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[INSTELLING1] [INSTELLING2] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SMBIOS-BESTAND|HWIDS-BESTAND]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "standaard" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd-plug-ins" fwupd-1.9.16/po/oc.po000066400000000000000000000077441460375044200143350ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Cédric Valmary , 2016 # Cédric Valmary , 2016 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Occitan (post 1500) (http://www.transifex.com/freedesktop/fwupd/language/oc/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: oc\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #. TRANSLATORS: this is when a device is hotplugged msgid "Added" msgstr "Apondut" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Aliàs de %s" #. TRANSLATORS: this is when a device ctrl+c's a watch msgid "Cancelled" msgstr "Anullat" #. TRANSLATORS: this is when a device is hotplugged #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Cambiat" #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Soma de contraròtle" #. TRANSLATORS: error message msgid "Command not found" msgstr "Comanda pas trobada" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opcions de desbugatge" #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Descripcion" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Acabat !" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Retrogradacion de %s de %s en %s " #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Quitar aprèp un brèu relambi" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Quitar aprèp lo cargament del motor" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Fracàs de l'analisi dels paramètres" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Servici D-Bus de mesa a jorn dels micrologicials" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Trobat" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Obténer la lista dels periferics que supòrtan las mesas a jorn de micrologicial" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Obténer los detalhs d'un fichièr de micrologicial" #. TRANSLATORS: command description msgid "Install a firmware file on this hardware" msgstr "Installar un fichièr de micrologicial sus aqueste material" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Cap de material amb de capacitats de mesa a jorn del micrologicial es pas estat detectat" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "D'acòrdi" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Reïnstallacion de %s en %s " #. TRANSLATORS: this is when a device is hotplugged msgid "Removed" msgstr "Suprimit" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Mostrar las opcions de desbugatge" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Mòstra d'informacions de desbugatge complementàrias" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Mesa a jorn de %s de %s en %s " #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Version" fwupd-1.9.16/po/pa.po000066400000000000000000000261461460375044200143310ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # A S Alam, 2021 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Panjabi (Punjabi) (http://app.transifex.com/freedesktop/fwupd/language/pa/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pa\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "%s ਸੰਰਚਨਾ ਅੱਪਡੇਟ" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s ਡਿਵਾਈਸ ਅੱਪਡੇਟ" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s ਸਿਸਟਮ ਅੱਪਡੇਟ" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s ਅੱਪਡੇਟ" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u ਮਿੰਟ" msgstr[1] "%uਮਿੰਟ" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u ਸਕਿੰਟ" msgstr[1] "%u ਸਕਿੰਟ" msgid "Activate the new firmware on the device" msgstr "ਡਿਵਾਈਸ ਉੱਤੇ ਨਵਾਂ ਫਿਰਮਵੇਅਰ ਸਰਗਰਮ ਕਰੋ" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "ਫਿਰਮਵੇਅਰ ਅੱਪਡੇਟ ਲਾਗੂ ਕਰੋ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "ਹਟਾਉਣਯੋਗ ਡਿਵਾਈਸ ਉੱਤੇ ਫਿਰਮਵੇਅਰ ਡਾਊਨਗਰੇਡ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "ਇਸ ਮਸ਼ੀਨ ਉੱਤੇ ਫਿਰਮਵੇਅਰ ਨੂੰ ਡਾਊਨਗਰੇਡ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "ਡਿਵਾਈਸ ਨੂੰ ਅਣ-ਲਾਕ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "ਹਟਾਉਣਯੋਗ ਡਿਵਾਈਸ ਉੱਤੇ ਫਿਰਮਵੇਅਰ ਅੱਪਡੇਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "ਇਸ ਮਸ਼ੀਨ ਉੱਤੇ ਫਿਰਮਵੇਅਰ ਅੱਪਡੇਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ" msgid "BYTES" msgstr "ਬਾਈਟ" #. TRANSLATORS: error message msgid "Command not found" msgstr "ਕਮਾਂਡ ਨਹੀਂ ਲੱਭੀ" #. TRANSLATORS: when the update was built msgid "Created" msgstr "ਬਣਾਇਆ" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "ਗੰਭੀਰ" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "ਵਰਣਨ" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "ਵੇਰਵੇ" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "ਡਿਵਾਈਸ ID" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "ਲੱਗਣਾ ਸਮਾਂ" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "ਇਹ ਸਹੂਲਤ ਨੂੰ ਆਪਣੇ ਖਤਰੇ ਉੱਤੇ ਹੀ ਸਮਰੱਥ ਕਰੋ, ਜਿਸ ਦਾ ਅਰਥ ਹੋਵੇਗਾ ਕਿ ਇਹਨਾਂ ਅੱਪਡੇਟਾਂ ਨਾਲ ਆਈ ਕਿਸੇ ਵੀ ਸਮੱਸਿਆ ਵਾਸਤੇ ਤੁਸੀਂ ਅਸਲ ਡਿਵਾਈਸ ਨਿਰਮਾਤਾ ਨਾਲ ਸੰਪਰਕ ਕਰੋਗੇ। ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਕਾਰਵਾਈ ਸੰਬੰਧੀ ਕਿਸੇ ਵੀ ਸਮੱਸਿਆ ਬਾਰੇ $OS_RELEASE:BUG_REPORT_URL$ ਉੱਤੇ ਰਿਪੋਰਟ ਕਰੋ।" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "ਫਾਇਲ-ਨਾਂ" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "ਫਾਇਲ-ਦਾ-ਨਾਂ" #. TRANSLATORS: the release urgency msgid "High" msgstr "ਵੱਧ" msgid "Install old version of signed system firmware" msgstr "ਸਾਈਨ ਕੀਤੇ ਸਿਸਟਮ ਫਿਰਮਵੇਅਰ ਦਾ ਪੁਰਾਣਾ ਵਰਜ਼ਨ ਇੰਸਟਾਲ ਕਰੋ" msgid "Install old version of unsigned system firmware" msgstr "ਬਿਨ-ਸਾਈਨ ਕੀਤੇ ਸਿਸਟਮ ਫਿਰਮਵੇਅਰ ਦਾ ਪੁਰਾਣਾ ਵਰਜ਼ਨ ਇੰਸਟਾਲ ਕਰੋ" msgid "Install signed device firmware" msgstr "ਸਾਈਨ ਕੀਤਾ ਡਿਵਾਈਸ ਫਿਰਮਵੇਅਰ ਇੰਸਟਾਲ ਕਰੋ" msgid "Install signed system firmware" msgstr "ਸਾਈਨ ਕੀਤਾ ਸਿਸਟਮ ਫਿਰਮਵੇਅਰ ਇੰਸਟਾਲ ਕਰੋ" msgid "Install unsigned device firmware" msgstr "ਬਿਨ-ਸਾਈਨ ਕੀਤਾ ਡਿਵਾਈਸ ਫਿਰਮਵੇਅਰ ਇੰਸਟਾਲ ਕਰੋ" msgid "Install unsigned system firmware" msgstr "ਬਿਨਾਂ-ਸਾਈਨ ਕੀਤਾ ਸਿਸਟਮ ਫਿਰਮਵੇਅਰ ਇੰਸਟਾਲ ਕਰੋ" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "ਫਿਰਮਵੇਅਰ ਅੱਪਡੇਟ ਇੰਸਟਾਲ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "ਮਸਲਾ" msgstr[1] "ਮਸਲੇ" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "ਲਸੰਸ" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "ਲੀਨਕਸ ਵੇਂਡਰ ਫਿਰਮਵੇਅਰ ਸਰਵਿਸ (ਸਟੇਬਲ ਫਿਰਮਵੇਅਰ)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "ਲੀਨਕਸ ਵੇਂਡਰ ਫਿਰਮਵੇਅਰ ਸਰਵਿਸ (ਟੈਸਟਿੰਗ ਫਿਰਮਵੇਅਰ)" #. TRANSLATORS: the release urgency msgid "Low" msgstr "ਘੱਟ" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "ਠੀਕ-ਠਾਕ" msgid "Modify daemon configuration" msgstr "ਡੈਮਨ ਸੰਰਚਨਾ ਸੋਧੋ" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "ਮੁੜ-ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "ਨਵਾਂ ਵਰਜ਼ਨ" #. TRANSLATORS: remote filename base msgid "Password" msgstr "ਪਾਸਵਰਡ" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "ਪ੍ਰੋਪ੍ਰੇਟਰੀ" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "ਡਿਵਾਈਸ ਲਈ ਫਿਰਮਵੇਅਰ ਨੂੰ ਫਾਇਲ ਤੋਂ ਪੜ੍ਹੋ" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "ਹੁਣੇ ਮੁੜ-ਚਾਲੂ ਕਰਨਾ ਹੈ?" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "ਸੁਰੱਖਿਅਤ ਬੂਟ ਅਸਮਰੱਥ ਹੈ" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "ਸੁਰੱਖਿਅਤ ਬੂਟ ਸਮਰੱਥ ਹੈ" #. TRANSLATORS: file size of the download msgid "Size" msgstr "ਆਕਾਰ" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "ਸਰੋਤ" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "ਸਫ਼ਲ" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "ਸਾਰ" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "ਸਿਸਟਮ ਨੂੰ ਬਾਹਰੀ ਪਾਵਰ ਸਰੋਤ ਚਾਹੀਦਾ ਹੈ" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "ਟੈਗ" msgstr[1] "ਟੈਗ" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS ਮੁਫ਼ਤ ਸਰਵਿਸ ਹੈ, ਜੋ ਕਿ ਆਜ਼ਾਦ ਕਨੂੰਨੀ ਸੰਸਥਾ ਵਜੋਂ ਕੰਮ ਕਰਦੀ ਹੈ ਅਤੇ $OS_RELEASE:NAME$ ਨਾਲ ਕੋਈ ਸੰਬੰਧ ਨਹੀਂ ਹੈ। ਤੁਹਾਡੇ ਡਿਸਟਰੀਬਿਊਟਰ ਨੇ ਤੁਹਾਡੇ ਸਿਸਟਮ ਜਾਂ ਕਨੈਕਟ ਹੋਏ ਡਿਵਾਈਸਾਂ ਲਈ ਅਨੁਕੂਲਤਾ ਵਾਸਤੇ ਫਿਰਮਵੇਅਰ ਅੱਪਡੇਟਾਂ ਨੂੰ ਤਸਦੀਕ ਨਹੀਂ ਕੀਤੇ ਹਨ। ਸਾਰੇ ਫਿਰਮਵੇਅਰ ਅਸਲ ਡਿਵਾਈਸ ਨਿਮਰਾਤਾਵਾਂ ਵਲੋਂ ਹੀ ਦਿੱਤੇ ਜਾ ਗਏ ਹਨ।" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "ਕਿਸਮ" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI ਫਿਰਮਵੇਅਰ ਸਹੂਲਤ" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "ਅਣਪਛਾਤਾ ਡਿਵਾਈਸ" msgid "Unlock the device to allow access" msgstr "ਪਹੁੰਚ ਦੀ ਇਜਾਜ਼ਤ ਦੇਣ ਲਈ ਡਿਵਾਈਸ ਅਣ-ਲਾਕ ਕਰੋ" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "ਅੱਪਡੇਟ ਲਈ ਮੁੜ-ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ" #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "ਜ਼ਰੂਰਤ" #. TRANSLATORS: remote filename base msgid "Username" msgstr "ਵਰਤੋਂਕਾਰ-ਨਾਂ" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "ਵੇਂਡਰ" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "ਵਰਜ਼ਨ" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "ਮੂਲ" fwupd-1.9.16/po/pl.po000066400000000000000000003530471460375044200143470ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Piotr Drąg , 2015-2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Polish (http://app.transifex.com/freedesktop/fwupd/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pl\n" "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "Pozostała %.0f minuta" msgstr[1] "Pozostały %.0f minuty" msgstr[2] "Pozostało %.0f minut" msgstr[3] "Pozostało %.0f minut" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "Aktualizacja BMC %s" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "Aktualizacja akumulatora %s" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "Aktualizacja mikrokodu procesora %s" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "Aktualizacja aparatu %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "Aktualizacja konfiguracji %s" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Aktualizacja podsystemu ME użytkownika końcowego urządzenia %s" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Aktualizacja kontrolera %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Aktualizacja firmowego podsystemu ME urządzenia %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Aktualizacja urządzenia %s" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "Aktualizacja ekranu %s" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "Aktualizacja stacji dokującej %s" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "Aktualizacja dysku %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Aktualizacja wbudowanego kontrolera %s" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "Aktualizacja czytnika odcisków palców %s" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "Aktualizacja dysku półprzewodnikowego %s" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "Aktualizacja karty graficznej %s" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "Aktualizacja tabletu graficznego %s" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "Aktualizacja klawiatury %s" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "Aktualizacja podsystemu ME urządzenia %s" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "Aktualizacja myszy %s" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "Aktualizacja interfejsu sieciowego %s" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "Aktualizacja dysku SSD %s" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "Aktualizacja kontrolera pamięci masowej %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Aktualizacja komputera %s" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "Aktualizacja TPM %s" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Aktualizacja kontrolera Thunderbolt %s" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "Aktualizacja panelu dotykowego %s" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "Aktualizacja replikatora portów USB %s" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "Aktualizacja odbiornika USB %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Aktualizacja urządzenia %s" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s i wszystkie podłączone urządzenia nie będą mogły być używane podczas aktualizacji." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "„%s” pojawiło się: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "„%s” zmieniło się: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "„%s” zniknęło: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "Nie można obecnie zaktualizować urządzenia %s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "Tryb serwisowy %s" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "Urządzenie %s musi być podłączone podczas trwania aktualizacji, aby uniknąć uszkodzenia." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "Urządzenie %s musi być podłączone do prądu podczas trwania aktualizacji, aby uniknąć uszkodzenia." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "Obejście %s" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "Wersja %s" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dzień" msgstr[1] "%u dni" msgstr[2] "%u dni" msgstr[3] "%u dni" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u urządzenie ma dostępną aktualizację oprogramowania sprzętowego." msgstr[1] "%u urządzenia mają dostępną aktualizację oprogramowania sprzętowego." msgstr[2] "%u urządzeń ma dostępną aktualizację oprogramowania sprzętowego." msgstr[3] "%u urządzeń ma dostępną aktualizację oprogramowania sprzętowego." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u urządzenie nie ma najlepszej znanej konfiguracji." msgstr[1] "%u urządzenia nie mają najlepszej znanej konfiguracji." msgstr[2] "%u urządzeń nie ma najlepszej znanej konfiguracji." msgstr[3] "%u urządzeń nie ma najlepszej znanej konfiguracji." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u godzina" msgstr[1] "%u godziny" msgstr[2] "%u godzin" msgstr[3] "%u godzin" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "Obsługiwane jest %u lokalne urządzenie" msgstr[1] "Obsługiwane są %u lokalne urządzenia" msgstr[2] "Obsługiwanych jest %u lokalnych urządzeń" msgstr[3] "Obsługiwanych jest %u lokalnych urządzeń" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuta" msgstr[1] "%u minuty" msgstr[2] "%u minut" msgstr[3] "%u minut" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u sekunda" msgstr[1] "%u sekundy" msgstr[2] "%u sekund" msgstr[3] "%u sekund" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (próg to %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(przestarzałe)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "PCR TPM ma teraz nieprawidłową wartość" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "Ochrona przed odtwarzaniem oprogramowania sprzętowego AMD" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "Ochrona przed zapisem oprogramowania sprzętowego AMD" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "Ochrona procesora zabezpieczeń AMD przed wycofaniem" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "ARCHIVE FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATFILE]" msgstr "ARCHIWUM OPROGRAMOWANIE-SPRZĘTOWE METAINFO [OPROGRAMOWANIE-SPRZĘTOWE] [METAINFO] [PLIK-JCAT]" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Wymagane działanie:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Aktywuje urządzenia" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Aktywuje oczekujące urządzenia" msgid "Activate the new firmware on the device" msgstr "Aktywacja nowego oprogramowania sprzętowego na urządzeniu" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Aktywowanie aktualizacji oprogramowania sprzętowego" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Aktywowanie aktualizacji oprogramowania sprzętowego dla" #. TRANSLATORS: command description msgid "Adds devices to watch for future emulation" msgstr "Dodaje urządzenia do obserwowania do emulacji w przyszłości" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Wiek" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Wyrazić zgodę i włączyć repozytorium?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias do „%s”" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "Wszystkie PCR TPM są teraz prawidłowe" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Wszystkie PCR TPM są prawidłowe" #. TRANSLATORS: an application is preventing system updates msgid "All devices are prevented from update by system inhibit" msgstr "Aktualizacja wszystkich urządzeń jest uniemożliwiana przez wstrzymanie systemu" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Wszystkie urządzenia tego samego typu zostaną zaktualizowane w tym samym czasie" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Umożliwia instalowanie poprzednich wersji oprogramowania sprzętowego" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Umożliwia ponowne instalowanie istniejących wersji oprogramowania sprzętowego" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Umożliwia przełączanie gałęzi oprogramowania sprzętowego" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Alternatywna gałąź" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "Trwa aktualizacja" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Ukończenie aktualizacji wymaga ponownego uruchomienia." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Ukończenie aktualizacji wymaga wyłączenia komputera." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Odpowiada tak na wszystkie pytania" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Zastosowuje aktualizacje oprogramowania sprzętowego" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Zastosowuje aktualizację nawet, jeśli nie jest zalecana" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Zastosowuje pliki aktualizacji" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Zastosowywanie aktualizacji…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Zatwierdzone oprogramowanie sprzętowe:" msgstr[1] "Zatwierdzone oprogramowanie sprzętowe:" msgstr[2] "Zatwierdzone oprogramowanie sprzętowe:" msgstr[3] "Zatwierdzone oprogramowanie sprzętowe:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Zapytać ponownie następnym razem?" #. TRANSLATORS: command description msgid "Asks the daemon to quit" msgstr "Prosi usługę o zakończenie działania" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Podłącza do trybu oprogramowania sprzętowego" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Uwierzytelnianie…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Wymagane są informacje o uwierzytelnieniu" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Wymagane jest uwierzytelnienie, aby zainstalować poprzednią wersję oprogramowania sprzętowego urządzenia wymiennego" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Wymagane jest uwierzytelnienie, aby zainstalować poprzednią wersję oprogramowania sprzętowego tego komputera" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to fix a host security issue" msgstr "Wymagane jest uwierzytelnienie, aby naprawić problem z zabezpieczeniami komputera" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Wymagane jest uwierzytelnienie, aby zmodyfikować ustawienia BIOS-u" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Wymagane jest uwierzytelnienie, aby zmodyfikować skonfigurowane repozytorium używane do aktualizacji oprogramowania sprzętowego" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Wymagane jest uwierzytelnienie, aby zmodyfikować konfigurację usługi" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "Wymagane jest uwierzytelnienie, aby odczytać ustawienia BIOS-u" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Wymagane jest uwierzytelnienie, aby ustawić listę zatwierdzonego oprogramowania sprzętowego" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Wymagane jest uwierzytelnienie, aby podpisać dane za pomocą certyfikatu klienta" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Wymagane jest uwierzytelnienie, aby przełączyć na nową wersję oprogramowania sprzętowego" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to undo the fix for a host security issue" msgstr "Wymagane jest uwierzytelnienie, aby wycofać poprawkę problemu z zabezpieczeniami komputera" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Wymagane jest uwierzytelnienie, aby odblokować urządzenie" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Wymagane jest uwierzytelnienie, aby zaktualizować oprogramowanie sprzętowe wymiennego urządzenia" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Wymagane jest uwierzytelnienie, aby zaktualizować oprogramowanie sprzętowe tego komputera" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Wymagane jest uwierzytelnienie, aby zaktualizować przechowywane sumy kontrolne dla urządzenia" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Automatyczne zgłaszanie" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Automatycznie wysyłać za każdym razem?" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS Firmware Updates" msgstr "Aktualizacje oprogramowania sprzętowego BIOS-u" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "Ochrona BIOS-u przed wycofaniem" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS firmware updates" msgstr "Aktualizacje oprogramowania sprzętowego BIOS-u" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "Ochrona BIOS-u przed wycofaniem" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "Aktualizacje BIOS-u dostarczane przez usługę LVFS lub Windows Update" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BUILDER-XML NAZWA-PLIKU-DOCELOWEGO" msgid "BYTES" msgstr "BAJTY" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Akumulator" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Dowiązuje nowy sterownik jądra" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Zablokowane pliki oprogramowania sprzętowego:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Zablokowana wersja" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Blokowanie oprogramowania sprzętowego:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Blokuje możliwość instalacji podanego oprogramowania sprzętowego" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Wersja programu startowego" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Gałąź" #. TRANSLATORS: command description msgid "Build a cabinet archive from a firmware blob and XML metadata" msgstr "Buduje archiwum CAB z zamkniętego oprogramowania sprzętowego i metadanych XML" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Buduje plik oprogramowania sprzętowego" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Anuluj" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Anulowano" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Nie można zastosować, ponieważ aktualizacja bazy dbx została już zastosowana." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Nie można stosować aktualizacji na nośnikach typu Live" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Zmieniono" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Sprawdza, czy kryptograficzna suma kontrolna pasuje do oprogramowania sprzętowego" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Suma kontrolna" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Wybór gałęzi" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Wybór urządzenia" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Wybór oprogramowania sprzętowego" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Wybór wydania" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Wybór ESP:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Wybór woluminu" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Usuwa wyniki z ostatniej aktualizacji" #. TRANSLATORS: error message msgid "Command not found" msgstr "Nie odnaleziono polecenia" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Wspierane przez społeczność" #. TRANSLATORS: title prefix for the BIOS settings dialog msgid "Configuration Change Suggested" msgstr "Proponowana zmiana konfiguracji" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "Tylko administrator komputera może odczytywać konfigurację" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Konwertuje plik oprogramowania sprzętowego" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Utworzono" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Krytyczna" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Dostępne jest sprawdzenie poprawności kryptograficznej sumy kontrolnej" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Obecna wartość" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Obecna wersja" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "IDENTYFIKATOR-URZĄDZENIA|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Narzędzie DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opcje debugowania" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Dekompresowanie…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Opis" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Odłącza do trybu programu startowego" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Informacje" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "Odejść od najlepszej znanej konfiguracji?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Flagi urządzenia" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Identyfikator urządzenia" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Dodano urządzenie:" #. TRANSLATORS: the device is already connected msgid "Device already exists" msgstr "Urządzenie już istnieje" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "Poziom naładowania akumulatora urządzenia jest za niski" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "Poziom naładowania akumulatora urządzenia jest za niski (%u%%, wymaga %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Urządzenie może przywrócić się po niepowodzeniu wgrywania" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "Nie można używać urządzenia, kiedy pokrywa jest zamknięta" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Zmieniono urządzenie:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "Emulacja urządzenia nie jest włączona." #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Oprogramowanie sprzętowe urządzenia musi mieć test wersji" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "Urządzenie jest emulowane" #. TRANSLATORS: device cannot be interrupted, for instance taking a phone call msgid "Device is in use" msgstr "Urządzenie jest używane" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Urządzenie jest zablokowane" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Urządzenie musi zainstalować wszystkie podane wydania" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Nie można komunikować się z urządzeniem" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "Nie można komunikować się z urządzeniem lub jest poza zasięgiem" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Urządzenie może być używane podczas trwania aktualizacji" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "Urządzenie oczekuje na zastosowanie aktualizacji" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Usunięto urządzenie:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "Urządzenie wymaga podłączenia do prądu" #. TRANSLATORS: device does not have a display connected msgid "Device requires a display to be plugged in" msgstr "Urządzenie wymaga, aby ekran był podłączony" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "Urządzenie wymaga licencji na oprogramowanie, aby zaktualizować" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Urządzenie przygotowuje aktualizacje" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "Urządzenie obsługuje przełączanie na inną gałąź oprogramowania sprzętowego" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Metoda aktualizacji urządzenia" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Aktualizacja urządzenia wymaga aktywacji" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Urządzenie wykona kopię zapasową oprogramowania sprzętowego przed instalacją" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Urządzenie nie pojawi się z powrotem po ukończeniu aktualizacji" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Pomyślnie zaktualizowane urządzenia:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Urządzenia, które nie zostały poprawnie zaktualizowane:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Urządzenia bez dostępnych aktualizacji oprogramowania sprzętowego: " #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Urządzenia z najnowszą dostępną wersją oprogramowania sprzętowego:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "Nie odnaleziono żadnych urządzeń o pasujących GUID" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Wyłączone" msgid "Disabled fwupdate debugging" msgstr "Wyłączono debugowanie fwupdate" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Wyłącza podane repozytorium" #. TRANSLATORS: command line option msgid "Display version" msgstr "Wyświetla wersję" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Dystrybucja" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Bez sprawdzania przestarzałych metadanych" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Bez sprawdzania niezgłoszonej historii" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Bez sprawdzania, czy repozytoria pobierania mają być włączone" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Bez sprawdzania ani pytania o ponowne uruchomienie po aktualizacji" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Bez dołączania przedrostka domeny dziennika" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Bez dołączania przedrostka czasu" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Bez wykonywania testów bezpieczeństwa urządzenia" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "Bez pytania o urządzenia" #. TRANSLATORS: command line option msgid "Do not prompt to fix security issues" msgstr "Bez pytania o naprawienie problemów zabezpieczeń" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "Bez wyszukiwania oprogramowania sprzętowego podczas przetwarzania" #. TRANSLATORS: warning message shown after update has been scheduled msgid "Do not turn off your computer or remove the AC adaptor while the update is in progress." msgstr "Nie należy wyłączać komputera ani odłączać zasilacza w czasie trwania aktualizacji." #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Bez zapisywania do bazy danych historii" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Czy rozumiesz konsekwencje zmiany gałęzi oprogramowania sprzętowego?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Wyłączyć tę funkcję dla przyszłych aktualizacji?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "Włączyć ją teraz?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Odświeżyć to repozytorium teraz?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Automatycznie wysyłać zgłoszenia dla przyszłych aktualizacji?" #. TRANSLATORS: command line option msgid "Don't prompt for authentication (less details may be shown)" msgstr "Bez pytania o uwierzytelnienie (może być wyświetlanych mniej informacji)" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Gotowe." #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Zainstalować poprzednią wersję urządzenia %s z %s do %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Instaluje poprzednią wersję oprogramowania sprzętowego urządzenia" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Instalowanie poprzedniej wersji %s z %s do %s… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Instalowanie poprzedniej wersji urządzenia %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Pobiera plik" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Pobieranie…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Zrzuca dane SMBIOS z pliku" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Czas trwania" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Podana partycja ESP nie jest prawidłowa" #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "Emuluje urządzenie za pomocą manifestu JSON" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Emulowane" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Emulowany komputer" msgid "Enable" msgstr "Włącz" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Włącza obsługę aktualizacji oprogramowania sprzętowego na obsługiwanych systemach" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Włączyć nowe repozytorium?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Włączyć to repozytorium?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Włączone" msgid "Enabled fwupdate debugging" msgstr "Włączono debugowanie fwupdate" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Włączone, jeśli sprzęt pasuje" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Włącza podane repozytorium" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Włączenie tej funkcjonalności jest wykonywane na własne ryzyko, co oznacza, że należy skontaktować się z oryginalnym producentem sprzętu w sprawie ewentualnych problemów spowodowanych przez te aktualizacje. Tylko problemy z samym procesem aktualizacji powinny być zgłaszane pod adresem $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Włączenie tego repozytorium wykonywane jest na własne ryzyko." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Zaszyfrowane" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "Zaszyfrowana pamięć RAM" #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Niewspierane" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Wyliczenie" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Usuwa całą historię aktualizacji oprogramowania sprzętowego" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Usuwanie zawartości…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Kończy działanie po małym opóźnieniu" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Kończy działanie po wczytaniu mechanizmu" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Eksportuje strukturę pliku oprogramowania sprzętowego do pliku XML" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Rozpakowuje zamknięte oprogramowanie sprzętowe do obrazów" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "PLIK" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "PLIK [IDENTYFIKATOR-URZĄDZENIA|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "NAZWA-PLIKU" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "NAZWA-PLIKU CERTYFIKAT KLUCZ-PRYWATNY" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "NAZWA-PLIKU ALTERNATYWNA-NAZWA-URZĄDZENIA|ALTERNATYWNY-IDENTYFIKATOR-URZĄDZENIA" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "NAZWA-PLIKU ALTERNATYWNA-NAZWA-URZĄDZENIA|ALTERNATYWNY-IDENTYFIKATOR-URZĄDZENIA [ALTERNATYWNA-NAZWA-OBRAZU|ALTERNATYWNY-IDENTYFIKATOR-OBRAZU]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "NAZWA-PLIKU IDENTYFIKATOR-URZĄDZENIA" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "NAZWA-PLIKU WYRÓWNANIE DANE [TYP-OPROGRAMOWANIA-SPRZĘTOWEGO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "NAZWA-PLIKU [IDENTYFIKATOR-URZĄDZENIA|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "NAZWA-PLIKU [TYP-OPROGRAMOWANIA-SPRZĘTOWEGO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "NAZWA-PLIKU-ŹRÓDŁOWEGO NAZWA-PLIKU-DOCELOWEGO [TYP-ŹRÓDŁOWEGO-OPROGRAMOWANIA-SPRZĘTOWEGO] [TYP-DOCELOWEGO-OPROGRAMOWANIA-SPRZĘTOWEGO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "NAZWA-PLIKU|SUMA-KONTROLNA1[,SUMA-KONTROLNA2][,SUMA-KONTROLNA3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Nie powiodło się" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Zastosowanie aktualizacji się nie powiodło" #. TRANSLATORS: error message for Windows msgid "Failed to connect to Windows service, please ensure it's running." msgstr "Połączenie z usługą systemu Windows się nie powiodło, proszę się upewnić, że ona działa." #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Połączenie z usługą się nie powiodło" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Uzyskanie oczekujących urządzeń się nie powiodło" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Zainstalowanie aktualizacji oprogramowania sprzętowego się nie powiodło" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Wczytanie lokalnej bazy dbx się nie powiodło" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Wczytanie poprawek się nie powiodło" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Wczytanie systemowej bazy dbx się nie powiodło" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Zablokowanie się nie powiodło" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Przetworzenie parametrów się nie powiodło" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Przetworzenie pliku się nie powiodło" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Przetworzenie flag dla opcji --filter się nie powiodło" msgid "Failed to parse flags for --filter-release" msgstr "Przetworzenie flag dla opcji --filter-release się nie powiodło" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Przetworzenie lokalnej bazy dbx się nie powiodło" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Ponowne uruchomienie się nie powiodło" #. TRANSLATORS: a feature is something like "can show an image" msgid "Failed to set front-end features" msgstr "Ustawienie funkcji interfejsu się nie powiodło" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Ustawienie trybu ekranu wczytywania się nie powiodło" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Sprawdzenie poprawności zawartości ESP się nie powiodło" #. TRANSLATORS: item is FALSE msgid "False" msgstr "Fałsz" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Nazwa pliku" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Podpis nazwy pliku" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Źródło nazwy pliku" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Wymagana jest nazwa pliku" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filtruje za pomocą zestawu flag urządzeń, przedrostek ~ wyklucza, np. „internal,~needs-reboot”" #. TRANSLATORS: command line option msgid "Filter with a set of release flags using a ~ prefix to exclude, e.g. 'trusted-release,~trusted-metadata'" msgstr "Filtruje za pomocą zestawu flag wydań, przedrostek ~ wyklucza, np. „trusted-release,~trusted-metadata”" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Zaświadczenie oprogramowania sprzętowego" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Deskryptor BIOS-u oprogramowania sprzętowego" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Region BIOS-u oprogramowania sprzętowego" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Podstawowy adres URI oprogramowania sprzętowego" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Usługa D-Bus aktualizacji oprogramowania sprzętowego" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Usługa aktualizacji oprogramowania sprzętowego" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Weryfikacja aktualizatora oprogramowania sprzętowego" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Aktualizacje oprogramowania sprzętowego" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Narzędzie oprogramowania sprzętowego" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Ochrona przed zapisem oprogramowania sprzętowego" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Blokada ochrony przed zapisem oprogramowania sprzętowego" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Zaświadczenie oprogramowania sprzętowego" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Oprogramowanie sprzętowe jest już zablokowane" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Oprogramowanie nie jest już zablokowane" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Metadane oprogramowania sprzętowego nie zostały zaktualizowane przez %u dzień i mogą nie być aktualne." msgstr[1] "Metadane oprogramowania sprzętowego nie zostały zaktualizowane przez %u dni i mogą nie być aktualne." msgstr[2] "Metadane oprogramowania sprzętowego nie zostały zaktualizowane przez %u dni i mogą nie być aktualne." msgstr[3] "Metadane oprogramowania sprzętowego nie zostały zaktualizowane przez %u dni i mogą nie być aktualne." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Aktualizacje oprogramowania sprzętowego" msgid "Firmware updates are not supported on this machine." msgstr "Aktualizacje oprogramowania sprzętowego nie są obsługiwane na tym komputerze." msgid "Firmware updates are supported on this machine." msgstr "Aktualizacje oprogramowania sprzętowego są obsługiwane na tym komputerze." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Aktualizacje oprogramowania sprzętowego są wyłączone; wykonanie polecenia „fwupdmgr unlock” je włączy" #. TRANSLATORS: command description msgid "Fix a specific host security attribute" msgstr "Naprawia podany atrybut zabezpieczeń komputera" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fix reverted successfully" msgstr "Pomyślnie wycofano poprawkę" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fixed successfully" msgstr "Pomyślnie naprawiono" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Flagi" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Wymusza działanie przez rozluźnienie części testów uruchamiania" msgid "Force the action ignoring all warnings" msgstr "Wymusza działanie ignorując wszystkie ostrzeżenia" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Odnaleziono" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Wykryto pełne szyfrowanie dysku" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "Hasła pełnego szyfrowania dysku mogą zostać unieważnione podczas aktualizacji" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "Platforma z bezpiecznikiem" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Platforma z bezpiecznikiem" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID" msgstr[2] "GUID" msgstr[3] "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "GUID|DEVICE-ID" msgstr "GUID|IDENTYFIKATOR-URZĄDZENIA" msgid "Get BIOS settings" msgstr "Uzyskanie ustawień BIOS-u" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Uzyskuje wszystkie flagi urządzeń obsługiwane przez usługę fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Uzyskuje wszystkie urządzenia obsługujące aktualizacje oprogramowania sprzętowego" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Uzyskuje wszystkie włączone wtyczki zarejestrowane na komputerze" #. TRANSLATORS: command description msgid "Get device report metadata" msgstr "Uzyskuje metadane zgłoszenia urządzenia" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Uzyskuje informacje o pliku oprogramowania sprzętowego" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Uzyskuje skonfigurowane repozytoria" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Uzyskuje atrybuty zabezpieczeń komputera" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Uzyskuje listę zatwierdzonego oprogramowania sprzętowego" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Uzyskuje listę zablokowanego oprogramowania sprzętowego" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Uzyskuje listę aktualizacji dla podłączonego sprzętu" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Uzyskuje wydania dla urządzenia" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Uzyskuje wyniki z ostatniej aktualizacji" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "PLIK-HWIDS" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Sprzęt czeka na ponowne podłączenie" #. TRANSLATORS: the release urgency msgid "High" msgstr "Wysoka" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Zdarzenia zabezpieczeń komputera" #. TRANSLATORS: error message for unsupported feature #, c-format msgid "Host Security ID (HSI) is not supported" msgstr "Identyfikator zabezpieczeń komputera (HSI) jest nieobsługiwany" #. TRANSLATORS: success, so say thank you to the user msgid "Host Security ID attributes uploaded successfully, thanks!" msgstr "Pomyślnie wysłano atrybuty identyfikatora zabezpieczeń komputera." #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Identyfikator zabezpieczeń komputera:" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "INHIBIT-ID" msgstr "IDENTYFIKATOR-WSTRZYMANIA" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "Ochrona IOMMU" #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "Wyłączono ochronę urządzenia IOMMU" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "Włączono ochronę urządzenia IOMMU" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Bezczynne…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ignoruje ścisłe testy SSL podczas pobierania plików" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Ignoruje niepowodzenia sum kontrolnych oprogramowania sprzętowego" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Ignoruje niepowodzenia zgodności sprzętu z oprogramowaniem sprzętowym" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignoruje testy bezpieczeństwa i sprawdzanie poprawności" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Ignorowanie ścisłych testów SSL, aby robić to automatycznie w przyszłości, należy wyeksportować zmienną DISABLE_SSL_STRICT w środowisku" #. TRANSLATORS: the inhibit ID is a short string like dbus-123456 #, c-format msgid "Inhibit ID is %s." msgstr "Identyfikator wstrzymania to %s." #. TRANSLATORS: command description msgid "Inhibit the system to prevent upgrades" msgstr "Wstrzymuje system, aby uniemożliwić aktualizacje" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Czas trwania instalacji" #. TRANSLATORS: command description msgid "Install a firmware file in cabinet format on this hardware" msgstr "Instaluje plik oprogramowania sprzętowego w formacie CAB na tym sprzęcie" #. TRANSLATORS: command description msgid "Install a raw firmware blob on a device" msgstr "Instaluje surowe zamknięte oprogramowanie sprzętowe na urządzeniu" #. TRANSLATORS: command description msgid "Install a specific firmware file on all devices that match" msgstr "Instaluje podany plik oprogramowania sprzętowego na wszystkich pasujących urządzeniach" #. TRANSLATORS: command description msgid "Install a specific firmware on a device, all possible devices will also be installed once the CAB matches" msgstr "Instaluje podane oprogramowanie sprzętowe na urządzeniu, wszystkie możliwe urządzenia także zostaną zainstalowane, jeśli CAB pasuje" msgid "Install old version of signed system firmware" msgstr "Instalacja poprzedniej wersji podpisanego oprogramowania sprzętowego komputera" msgid "Install old version of unsigned system firmware" msgstr "Instalacja poprzedniej wersji niepodpisanego oprogramowania sprzętowego komputera" msgid "Install signed device firmware" msgstr "Instalacja podpisanego oprogramowania sprzętowego urządzenia" msgid "Install signed system firmware" msgstr "Instalacja podpisanego oprogramowania sprzętowego komputera" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Instaluje najpierw na urządzeniu nadrzędnym" msgid "Install unsigned device firmware" msgstr "Instalacja niepodpisanego oprogramowania sprzętowego urządzenia" msgid "Install unsigned system firmware" msgstr "Instalacja niepodpisanego oprogramowania sprzętowego komputera" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Instalowanie oprogramowania sprzętowego…" #. TRANSLATORS: stay on one firmware version unless the new version is #. explicitly #. * specified msgid "Installing a specific release is explicitly required" msgstr "Wymagane jest instalowanie konkretnego wydania" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Instalowanie aktualizacji oprogramowania sprzętowego…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Instalowanie na urządzeniu %s…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "Zainstalowanie tej aktualizacji może także spowodować utratę wszelkiej gwarancji na urządzenie." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Liczba stała" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "ACM chronione przez Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "ACM chronione przez Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Zasady postępowania Intel BootGuard w przypadku błędów" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Bezpiecznik Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Bezpiecznik OTP Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Uruchamianie zweryfikowane przez Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Zasady postępowania Intel BootGuard w przypadku błędów" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Uruchamianie zweryfikowane przez Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET jest aktywne" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET jest włączone" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS Mitigation" msgstr "Załatanie dziury GDS procesorów Intel" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS mitigation" msgstr "Załatanie dziury GDS procesorów Intel" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Tryb serwisowy Intel Management Engine" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Obejście Intel Management Engine" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Wersja Intel Management Engine" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Wewnętrzne urządzenie" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Nieprawidłowe" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Nieprawidłowe parametry" #. TRANSLATORS: error message msgid "Invalid arguments, expected GUID" msgstr "Nieprawidłowe parametry, oczekiwano GUID" #. TRANSLATOR: This is the error message for #. * incorrect parameter msgid "Invalid arguments, expected an AppStream ID" msgstr "Nieprawidłowe parametry, oczekiwano identyfikatora AppStream" #. TRANSLATORS: error message msgid "Invalid arguments, expected at least ARCHIVE FIRMWARE METAINFO" msgstr "Nieprawidłowe parametry, oczekiwano co najmniej ARCHIWUM OPROGRAMOWANIE-SPRZĘTOWE METAINFO" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Jest instalacją poprzedniej wersji" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Jest w trybie programu startowego" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Jest aktualizacją" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Błąd" msgstr[1] "Błędy" msgstr[2] "Błędy" msgstr[3] "Błędy" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "KLUCZ,WARTOŚĆ" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "Jądro nie jest już skażone" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "Jądro jest skażone" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Wyłączono blokadę jądra" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Włączono blokadę jądra" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Baza kluczy" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "POŁOŻENIE" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Ostatnia modyfikacja" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Pozostała mniej niż jedna minuta" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licencja" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Blokada jądra Linux" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Weryfikacja jądra Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Obszar wymiany systemu Linux" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (stabilne oprogramowanie sprzętowe)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (testowe oprogramowanie sprzętowe)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Jądro Linux" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Blokada jądra Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Obszar wymiany systemu Linux" #. TRANSLATORS: command description msgid "List EFI variables with a specific GUID" msgstr "Wyświetla listę zmiennych EFI o podanym GUID" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Wyświetla listę wpisów w bazie dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Wyświetla listę obsługiwanych aktualizacji oprogramowania sprzętowego" #. TRANSLATORS: command description msgid "List the available firmware GTypes" msgstr "Wyświetla listę GType dostępnego oprogramowania sprzętowego" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Wyświetla listę dostępnych typów oprogramowania sprzętowego" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Wyświetla listę plików na ESP" #. TRANSLATORS: command description msgid "Load device emulation data" msgstr "Wczytuje dane emulacji urządzenia" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "Wczytane z zewnętrznego modułu" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Wczytywanie…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Zablokowane" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Niska" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "Manifest klucza MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "Manifest klucza MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "Tryb serwisowy MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "Obejście MEI" msgid "MEI version" msgstr "Wersja MEI" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Ręcznie włącza podane wtyczki" #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Maksymalna długość" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Maksymalna wartość" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Średnia" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Podpis metadanych" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Adres URI metadanych" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadane można uzyskać z serwisu Linux Vendor Firmware Service." #. TRANSLATORS: error message for a user who ran fwupdmgr #. * refresh recently msgid "Metadata is up to date; use --force to refresh again." msgstr "Metadane są aktualne, opcja --force odświeży je ponownie." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Minimalna wersja" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Minimalna długość" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Minimalna wartość" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Niezgodna usługa i klient, proszę użyć %s zamiast tego" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Modyfikuje wartość konfiguracji usługi" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modyfikuje podane repozytorium" msgid "Modify a configured remote" msgstr "Modyfikacja skonfigurowanego repozytorium" msgid "Modify daemon configuration" msgstr "Modyfikacja konfiguracji usługi" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Monitoruje zdarzenia usługi" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Montuje MSP" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Wymaga ponownego uruchomienia po instalacji" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Wymaga ponownego uruchomienia" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Wymaga wyłączenia komputera po instalacji" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Nowa wersja" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Nie podano działania." #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Brak poprzednich wersji dla urządzenia %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Nie odnaleziono identyfikatorów oprogramowania sprzętowego" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "Nie odnaleziono oprogramowania sprzętowego" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Nie wykryto sprzętu z możliwością aktualizacji jego oprogramowania" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nie odnaleziono wtyczek" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Brak dostępnych wydań" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Żadne repozytoria nie są obecnie włączone, więc żadne metadane nie są dostępne." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Brak dostępnych repozytoriów" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Brak urządzeń, które można aktualizować" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Brak dostępnych aktualizacji" msgid "No updates available for remaining devices" msgstr "Brak dostępnych aktualizacji dla pozostałych urządzeń" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nie zastosowano żadnych aktualizacji" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Niezatwierdzone" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Nie odnaleziono" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Nieobsługiwane" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "OK" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "OK!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Poprzednia wersja" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Wyświetla tylko jedną wartość PCR" #. TRANSLATORS: command line option msgid "Only use peer-to-peer networking when downloading files" msgstr "Używa tylko sieci P2P podczas pobierania plików" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Dozwolone są tylko aktualizacje wersji" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Wyjście w formacie JSON" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Zastępuje domyślną ścieżkę ESP" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Firmware" msgstr "Oprogramowanie sprzętowe P2P" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Metadata" msgstr "Metadane P2P" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "ŚCIEŻKA" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Przetwarza i wyświetla informacje o pliku oprogramowania sprzętowego" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Przetwarzanie aktualizacji bazy dbx…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Przetwarzanie systemowej bazy dbx…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Hasło" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Łata zamknięte oprogramowanie sprzętowe pod znanym wyrównaniem" msgid "Payload" msgstr "Dane" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Oczekujące" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Procent ukończenia" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Wykonać działanie?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Debugowanie platformy" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Debugowanie platformy" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Przed kontynuacją proszę się upewnić, że posiadany jest klucz odzyskiwania woluminu." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Proszę podać liczbę od 0 do %u: " #. TRANSLATORS: the user isn't reading the question -- please don't translate #. * 'Y' or 'N' as these are hardcoded msgid "Please enter either Y or N: " msgstr "Proszę nacisnąć Y lub N:" #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Brak zależności wtyczki" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Możliwe wartości" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "Ochrona DMA przed uruchomieniem" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Ochrona DMA przed uruchomieniem" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "Ochrona DMA przed uruchomieniem jest wyłączona" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "Ochrona DMA przed uruchomieniem jest włączona" #. TRANSLATORS: warning message msgid "Press unlock on the device to continue the update process." msgstr "Proszę nacisnąć „Odblokuj” na urządzeniu, aby kontynuować aktualizację." #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Poprzednia wersja" msgid "Print the version number" msgstr "Wyświetla numer wersji" msgid "Print verbose debug statements" msgstr "Wyświetla więcej komunikatów debugowania" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Priorytet" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Problemy" msgid "Proceed with upload?" msgstr "Kontynuować wysyłanie?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Testy zabezpieczeń procesora" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Ochrona procesora przed wycofaniem" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Własnościowe" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Odpytuje obsługę aktualizacji oprogramowania sprzętowego" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "IDENTYFIKATOR-REPOZYTORIUM" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "IDENTYFIKATOR-REPOZYTORIUM KLUCZ WARTOŚĆ" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Tylko do odczytu" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Odczytuje zamknięte oprogramowanie sprzętowe z urządzenia" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Odczytuje oprogramowanie sprzętowe z urządzenia" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Odczytuje oprogramowanie sprzętowe z urządzenia do pliku" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Odczytuje oprogramowanie sprzętowe z jednej partycji do pliku" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Odczytywanie z urządzenia %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Odczytywanie…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Ponowne uruchamianie…" #. TRANSLATORS: how often we should refresh the metadata msgid "Refresh Interval" msgstr "Czas między odświeżeniami" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Odświeża metadane ze zdalnego serwera" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Ponownie zainstalować urządzenie %s do wersji %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Ponownie instaluje obecne oprogramowanie sprzętowe na urządzeniu" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Ponownie instaluje oprogramowanie sprzętowe na urządzeniu" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Ponowne instalowanie %s za pomocą %s… " #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Gałąź wydania" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Flagi wydania" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "Identyfikator wydania" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Identyfikator repozytorium" #. TRANSLATORS: command description msgid "Removes devices to watch for future emulation" msgstr "Usuwa urządzenia do obserwowania do emulacji w przyszłości" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Zastępuje dane w istniejącym pliku oprogramowania sprzętowego" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Adres URI zgłoszenia" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Zgłoszone do zdalnego serwera" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Anulowano żądanie" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "Nie odnaleziono wymaganego systemu plików „efivarfs”" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "Nie odnaleziono wymaganego oprogramowania sprzętowego" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Wymaga programu startowego" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Wymaga połączenia z Internetem" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Uruchomić ponownie?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Uruchomić usługę ponownie, aby uwzględnić zmianę?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Ponowne uruchamianie urządzenia…" #. TRANSLATORS: command description msgid "Retrieve BIOS settings. If no arguments are passed all settings are returned" msgstr "Pobiera ustawienia BIOS-u. Jeśli żadne parametry nie zostaną przekazane, zwracane są wszystkie ustawienia" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Zwraca wszystkie identyfikatory sprzętu dla komputera" #. TRANSLATORS: ask the user to upload msgid "Review and upload report now?" msgstr "Przejrzeć i wysłać zgłoszenie?" #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Polecenie „fwupdmgr get-upgrades” wyświetli więcej informacji." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr sync` to complete this action." msgstr "Polecenie „fwupdmgr sync” dokończy to działanie." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Wykonuje procedurę czyszczenia składania wtyczki podczas używania „install-blob”" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Wykonuje procedurę przygotowania składania wtyczki podczas używania „install-blob”" #. TRANSLATORS: command description msgid "Run the post-reboot cleanup action" msgstr "Wykonuje działanie czyszczenia po ponownym uruchomieniu" #. TRANSLATORS: tell a user how to get information #, c-format msgid "Run without '%s' to see" msgstr "Wykonanie bez „%s” wyświetli" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "Działające jądro jest za stare" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Przyrostek uruchamiania" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "USTAWIENIE WARTOŚĆ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING1 VALUE1 [SETTING2] [VALUE2]" msgstr "USTAWIENIE1 WARTOŚĆ1 [USTAWIENIE2] [WARTOŚĆ2]" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "Deskryptor BIOS-u SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "Region BIOS-u SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "Blokada SPI" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "Ochrona przed odtwarzaniem SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "Zapis SPI" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "Ochrona przed zapisem SPI" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "PODSYSTEM STEROWNIK [IDENTYFIKATOR-URZĄDZENIA|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Zapisuje plik umożliwiający utworzenie identyfikatorów sprzętu" #. TRANSLATORS: command description msgid "Save device emulation data" msgstr "Zapisuje dane emulacji urządzenia" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "Przyrost skalarny" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Planuje instalację podczas następnego ponownego uruchomienia" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Planowanie…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Wyłączono Secure Boot" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Włączono Secure Boot" msgid "Security hardening for HSI" msgstr "Wzmacnianie zabezpieczeń dla HSI" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "%s zawiera więcej informacji." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "%s zawiera więcej informacji." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Wybrane urządzenie" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Wybrany wolumin" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Numer seryjny" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "Ustawienie ustawienia BIOS-u „%s” na „%s”." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "Ustawia ustawienie BIOS-u" msgid "Set one or more BIOS settings" msgstr "Ustawienie co najmniej jednego ustawienia BIOS-u" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Ustawia flagę debugowania podczas aktualizacji" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "Ustawia co najmniej jedno ustawienie BIOS-u" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Ustawienie listy zatwierdzonego oprogramowania sprzętowego" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Typ ustawienia" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Ustawienia zostaną zastosowane po ponownym uruchomieniu komputera" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Udostępnia historię oprogramowania sprzętowego programistom" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Wyświetla wszystkie wyniki" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Wyświetla wersje klienta i usługi" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Wyświetla więcej informacji o usłudze dla konkretnej domeny" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Wyświetla informacje o debugowaniu dla wszystkich domen" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Wyświetla opcje debugowania" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Wyświetla urządzenia, których nie można aktualizować" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Wyświetla dodatkowe informacje o debugowaniu" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Wyświetla historię aktualizacji oprogramowania sprzętowego" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Wyświetla obliczoną wersję bazy dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Wyświetla dziennik debugowania z ostatniej aktualizacji" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Wyświetla informacje o stanie aktualizacji oprogramowania sprzętowego" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Wyłączyć teraz?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Podpisuje oprogramowanie sprzętowe nowym kluczem" msgid "Sign data using the client certificate" msgstr "Podpisanie danych za pomocą certyfikatu klienta" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Podpisanie danych za pomocą certyfikatu klienta" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Podpisuje wysłane dane za pomocą certyfikatu klienta" msgid "Signature" msgstr "Podpis" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Podpisane dane" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Rozmiar" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Część haseł platformy może zostać unieważnionych podczas aktualizowania tego oprogramowania sprzętowego." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Źródło" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Podaje identyfikatory dostawcy/produktu urządzenia DFU" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Podaje plik bazy dbx" msgid "Specify the number of bytes per USB transfer" msgstr "Podaje liczbę bajtów na przesyłanie USB" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "Ciąg" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Powodzenie" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Pomyślnie aktywowano wszystkie urządzenia" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Pomyślnie wyłączono repozytorium" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Pomyślnie pobrano nowe metadane: " #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Pomyślnie włączono i odświeżono repozytorium" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Pomyślnie włączono repozytorium" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Pomyślnie zainstalowano oprogramowanie sprzętowe" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Pomyślnie zmodyfikowano wartość konfiguracji" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Pomyślnie zmodyfikowano repozytorium" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Pomyślnie ręcznie odświeżono metadane" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Pomyślnie zaktualizowano sumy kontrolne urządzenia" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Pomyślnie wysłano %u zgłoszenie" msgstr[1] "Pomyślnie wysłano %u zgłoszenia" msgstr[2] "Pomyślnie wysłano %u zgłoszeń" msgstr[3] "Pomyślnie wysłano %u zgłoszeń" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Pomyślnie sprawdzono poprawność sum kontrolnych urządzenia" #. TRANSLATORS: the device showed up in time #, c-format msgid "Successfully waited %.0fms for device" msgstr "Pomyślnie odczekano %.0f ms na urządzenie" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Podsumowanie" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Obsługiwane" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "Obsługiwany procesor" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Obsługiwane na zdalnym serwerze" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Uśpienie do trybu bezczynności" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Uśpienie do pamięci RAM" #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Uśpienie do trybu bezczynności" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Uśpienie do pamięci RAM" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Przełączyć gałąź z %s na %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Przełącza gałąź oprogramowania sprzętowego na urządzeniu" #. TRANSLATORS: command description msgid "Sync firmware versions to the chosen configuration" msgstr "Synchronizuje wersje oprogramowania sprzętowego do wybranej konfiguracji" #. TRANSLATORS: this CLI tool is now preventing system updates msgid "System Update Inhibited" msgstr "Aktualizacja systemu jest wstrzymana" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "Poziom naładowania komputera jest za niski, aby wykonać aktualizację" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "Poziom naładowania komputera jest za niski, aby wykonać aktualizację (%u%%, wymaga %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "Komputer wymaga zewnętrznego źródła zasilania" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEKST" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "Rekonstrukcja PCR0 TPM" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "Rekonstrukcja PCR0 TPM jest nieprawidłowa" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "Rekonstrukcja PCR0 TPM jest teraz prawidłowa" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "Konfiguracja platformy TPM" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "Rekonstrukcja TPM" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "Puste PCR TPM" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Etykieta" msgstr[1] "Etykiety" msgstr[2] "Etykiety" msgstr[3] "Etykiety" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "Oznaczone do emulacji" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Skażone" msgid "Target" msgstr "Cel" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Testuje urządzenie za pomocą manifestu JSON" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Przetestowane" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "Przetestowane przez: %s" #. TRANSLATORS: someone we trust has tested this msgid "Tested by trusted vendor" msgstr "Przetestowane przez zaufanego dostawcę" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS to wolny serwis działający jako niezależny podmiot prawny niemający związków z systemem $OS_RELEASE:NAME$. Dystrybutor używanego systemu mógł nie zweryfikować żadnych aktualizacji oprogramowania sprzętowego pod kątem zgodności z używanym komputerem lub podłączonymi urządzeniami. Każde oprogramowanie sprzętowe jest dostarczane wyłącznie przez oryginalnego producenta sprzętu." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "PCR0 TPM różni się od rekonstrukcji." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "Usługa wczytała kod firmy trzeciej i nie jest już wspierana przez jej programistów!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "Wersja urządzenia się nie zgadza: otrzymano %s, oczekiwano %s" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "Oprogramowanie sprzętowe od firmy %s nie jest dostarczane przez firmę %s, dostawcę sprzętu." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "Zegar komputera nie jest poprawnie ustawiony i pobieranie plików może się nie powieść." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been re-inserted." msgstr "Aktualizacja zostanie kontynuowana po ponownym podłączeniu kabla USB urządzenia." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged and then re-inserted." msgstr "Aktualizacja zostanie kontynuowana po odłączeniu i ponownym podłączeniu kabla USB urządzenia." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged." msgstr "Aktualizacja zostanie kontynuowana po odłączeniu kabla USB urządzenia." #. TRANSLATORS: warning message msgid "The update will continue when the device power cable has been removed and re-inserted." msgstr "Aktualizacja zostanie kontynuowana po odłączeniu i ponownym podłączeniu kabla zasilającego urządzenia." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "Dostawca nie dostarczył żadnych informacji o wydaniu." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Urządzenia z problemami:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Nie ma zablokowanych plików oprogramowania sprzętowego" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Nie ma zatwierdzonego oprogramowania sprzętowego." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "To urządzenie zostanie przywrócone do wersji %s po wykonaniu polecenia %s." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "To oprogramowanie sprzętowe jest dostarczane przez członków społeczności LVFS i nie jest dostarczane (ani wspierane) przez oryginalnego dostawcę sprzętu." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Ten pakiet nie został zweryfikowany, może nie działać poprawnie." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Ten program może działać poprawnie tylko jako root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "To repozytorium zawiera oprogramowanie sprzętowe nieobjęte embargo, ale nadal testowane przez dostawcę sprzętu. Należy upewnić się, że istnieje możliwość ręcznego zainstalowania poprzedniej wersji oprogramowania sprzętowego w razie niepowodzenia aktualizacji." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "Ten komputer nie obsługuje ustawień oprogramowania sprzętowego" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Ten komputer ma problemy HSI uruchamiania." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Ten komputer ma niski poziom zabezpieczeń HSI." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "To narzędzie umożliwia administratorowi zastosowywanie aktualizacji bazy dbx dla UEFI." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "To narzędzie umożliwia administratorowi debugowanie działania UpdateCapsule." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "To narzędzie umożliwia administratorowi odpytywanie i sterowanie usługą fwupd, umożliwiając wykonywanie takich działań, jak instalowanie lub instalowanie poprzedniej wersji oprogramowania sprzętowego." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "To narzędzie umożliwia administratorowi używanie wtyczek fwupd bez ich instalacji na komputerze." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can add a kernel argument of '%s', but it will only be active after restarting the computer." msgstr "To narzędzie może dodać parametr jądra „%s”, ale zmiana będzie aktywna dopiero po ponownym uruchomieniu komputera." #. TRANSLATORS: the %1 is a BIOS setting name. #. * %2 and %3 are the values, e.g. "True" or "Windows10" #, c-format msgid "This tool can change the BIOS setting '%s' from '%s' to '%s' automatically, but it will only be active after restarting the computer." msgstr "To narzędzie może automatycznie zmienić ustawienie BIOS-u „%s” z „%s” na „%s”, ale zmiana będzie aktywna dopiero po ponownym uruchomieniu komputera." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can change the kernel argument from '%s' to '%s', but it will only be active after restarting the computer." msgstr "To narzędzie może zmienić parametr jądra z „%s” na „%s”, ale zmiana będzie aktywna dopiero po ponownym uruchomieniu komputera." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "To narzędzie może być używane tylko przez użytkownika root" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "To narzędzie odczyta i przetworzy dziennik zdarzeń TPM z oprogramowania sprzętowego komputera." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Przejściowe niepowodzenie" #. TRANSLATORS: item is TRUE msgid "True" msgstr "Prawda" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Zweryfikowane metadane" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Zweryfikowane dane" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Typ" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI Bootservice Variables" msgstr "Zmienne usługi uruchamiania UEFI" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition may not be set up correctly" msgstr "Partycja ESP UEFI może nie być poprawnie skonfigurowana" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "Nie wykryto lub nie skonfigurowano partycji ESP UEFI" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Narzędzie oprogramowania sprzętowego UEFI" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "Klucz platformy UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "Zabezpieczone uruchamianie UEFI" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI bootservice variables" msgstr "Zmienne usługi uruchamiania UEFI" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "Aktualizacje kapsułowe UEFI są niedostępne lub wyłączone w konfiguracji oprogramowania sprzętowego" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "Narzędzie bazy dbx dla UEFI" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "Nie można aktualizować oprogramowania sprzętowego UEFI w trybie przestarzałego BIOS-u" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "Klucz platformy UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "Zabezpieczone uruchamianie UEFI" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Nie można połączyć się z usługą" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "Nie można odnaleźć atrybutu" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Odwiązuje bieżący sterownik" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Odblokowywanie oprogramowania sprzętowego:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Odblokowuje możliwość instalacji podanego oprogramowania sprzętowego" #. TRANSLATORS: command description msgid "Undo the host security attribute fix" msgstr "Wycofuje poprawkę atrybutu zabezpieczeń komputera" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Niezaszyfrowane" #. TRANSLATORS: command description msgid "Uninhibit the system to allow upgrades" msgstr "Kończy wstrzymanie systemu, aby zezwolić na aktualizacje" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Nieznane" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Nieznane urządzenie" msgid "Unlock the device to allow access" msgstr "Odblokowanie urządzenia" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Odblokowane" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Odblokowuje urządzenie" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Odmontowuje ESP" #. TRANSLATORS: message shown after device has been marked for emulation msgid "Unplug and replug the device to continue the update process." msgstr "Proszę odłączyć i ponownie podłączyć urządzenie, aby kontynuować aktualizację." #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Usuwa flagę debugowania podczas aktualizacji" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Niepodpisane dane" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Nieobsługiwana wersja usługi %s, wersja klienta to %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Nieskażone" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Można aktualizować" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Błąd aktualizacji" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Ilustracja aktualizacji" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Komunikat aktualizacji" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Stan aktualizacji" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Niepowodzenie aktualizacji to znany problem, pod tym adresem dostępnych jest więcej informacji:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Zaktualizować teraz?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Aktualizacja wymaga ponownego uruchomienia" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Aktualizuje przechowywaną kryptograficzną sumę kontrolną bieżącą zawartością pamięci ROM" msgid "Update the stored device verification information" msgstr "Aktualizacja przechowywanych informacji o poprawności urządzenia" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Aktualizuje przechowywane metadane obecną zawartością" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "Aktualizuje wszystkie podane urządzenia do najnowszej wersji oprogramowania sprzętowego, lub wszystkie urządzenia, jeśli nie podano żadnych" msgid "Updating" msgstr "Aktualizowanie" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Aktualizowanie %s z wersji %s do %s… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Aktualizowanie urządzenia %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Zaktualizować urządzenie %s z wersji %s na %s?" #. TRANSLATORS: ask the user to share, %s is something #. * like: "Linux Vendor Firmware Service" #, c-format msgid "Upload these anonymous results to the %s to help other users?" msgstr "Wysłać te anonimowe wyniki do serwisu %s, aby pomóc innym użytkownikom?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Wysyłanie zgłoszeń o oprogramowaniu sprzętowym pomaga dostawcom sprzętu szybko identyfikować nieudane i udane aktualizacje na prawdziwych urządzeniach." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Pilność" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Polecenie %s wyświetli pomoc" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Naciśnięcie CTRL^C anuluje." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Polecenie fwupdtool --help wyświetli pomoc" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Używa flag poprawek podczas instalowania oprogramowania sprzętowego" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Użytkownik został powiadomiony" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Nazwa użytkownika" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Prawidłowe" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Sprawdzanie poprawności zawartości ESP…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Wariant" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Dostawca" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Sprawdzanie poprawności…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Wersja" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Wersja[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "OSTRZEŻENIE" #. TRANSLATORS: command description msgid "Wait for a device to appear" msgstr "Odczekuje na pojawienie się urządzenia" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Oczekiwanie…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Obserwuje zmiany sprzętu" #. TRANSLATORS: check various UEFI and ACPI tables are unchanged after the #. update msgid "Will measure elements of system integrity around an update" msgstr "Dokona pomiaru elementów spójności komputera wokół aktualizacji" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Zapisuje oprogramowanie sprzętowe z pliku na urządzenie" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Zapisuje oprogramowanie sprzętowe z pliku na jedną partycję" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Zapisywanie pliku:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Zapisywanie…" #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from a recovery or installation disk, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Należy się upewnić, że użytkownik jest w stanie przywrócić ustawienia z dysku przywracania lub instalacji, ponieważ ta zmiana może spowodować, że komputer nie będzie mógł uruchomić systemu Linux lub spowodować inny rodzaj niestabilności komputera." #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from the system firmware setup, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Należy się upewnić, że użytkownik jest w stanie przywrócić ustawienia w konfiguracji oprogramowania sprzętowego komputera, ponieważ ta zmiana może spowodować, że komputer nie będzie mógł uruchomić systemu Linux lub spowodować inny rodzaj niestabilności komputera." #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Używana dystrybucja mogła nie sprawdzić zgodności aktualizacji oprogramowania sprzętowego z komputerem i podłączonymi urządzeniami." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Sprzęt może zostać uszkodzony przez użycie tego oprogramowania sprzętowego, a zainstalowanie tego wydania może spowodować utratę gwarancji od firmy %s." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "Komputer jest skonfigurowany według BKC %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[APPSTREAM_ID]" msgstr "[IDENTYFIKATOR_APPSTREAM]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[SUMA-KONTROLNA]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[IDENTYFIKATOR-URZĄDZENIA|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[IDENTYFIKATOR-URZĄDZENIA|GUID] [GAŁĄŹ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[IDENTYFIKATOR-URZĄDZENIA|GUID] [WERSJA]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE]" msgstr "[URZĄDZENIE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[PLIK PODPIS_PLIKU IDENTYFIKATOR-REPOZYTORIUM]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[NAZWA-PLIKU1] [NAZWA-PLIKU2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[POWÓD]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[USTAWIENIE1] [USTAWIENIE2]…" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[USTAWIENIE1] [USTAWIENIE2] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[PLIK-SMBIOS|PLIK-HWIDS]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "domyślna" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "Narzędzie dziennika zdarzeń TPM usługi fwupd" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "Wtyczki usługi fwupd" fwupd-1.9.16/po/pt.po000066400000000000000000002262431460375044200143540ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Hugo Carvalho , 2021 # Luis Filipe Teixeira , 2023 # Peter J. Mello , 2020 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Portuguese (http://app.transifex.com/freedesktop/fwupd/language/pt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pt\n" "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minuto restante" msgstr[1] "%.0f minutos restantes" msgstr[2] "%.0f minutos restantes" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "Atualização da bateria para %s" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "Atualização de microcódigo de CPU para %s" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "Atualização de câmera para %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "Atualização da configuração de %s" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Atualização consumidor-final de ME para %s" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Atualização de controlador para %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Atualização corporativa de ME para %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Atualização do dispositivo %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Atualização do controlador embarcado %s" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "Atualização do teclado para %s" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "Atualização de ME para %s" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "Atualização do rato para %s" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "Atualização da interface de rede %s" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "Atualização do controlador de armazenamento %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Atualização do sistema %s" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "Atualização de TPM para %s" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Atualização de controlador Thunderbolt para %s" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "Atualização do touchpad para %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Atualização de %s" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s e todos os dispositivos conectados não podem ser usados durante a atualização." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "Modo de fabricação de %s" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s deve permanecer conectado durante a atualização para evitar danos." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s deve permanecer conectado a uma fonte de energia durante a atualização para evitar danos." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "substituição de %s" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "Versão %s" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dia" msgstr[1] "%u dias" msgstr[2] "%u dias" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] " %u dispositivo tem uma atualização de firmware disponível." msgstr[1] "%u dispositivos têm uma atualização de firmware disponível." msgstr[2] "%u dispositivos têm uma atualização de firmware disponível." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u hora" msgstr[1] "%u horas" msgstr[2] "%u horas" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u dispositivo local suportado" msgstr[1] "%u dispositivos locais suportados" msgstr[2] "%u dispositivos locais suportados" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuto" msgstr[1] "%u minutos" msgstr[2] "%u minutos" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u segundo" msgstr[1] "%u segundos" msgstr[2] "%u segundos" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(obsoleto)" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Ação necessária:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Ativa dispositivos" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Ativa dispositivos pendentes" msgid "Activate the new firmware on the device" msgstr "Ativar o novo firmware no dispositivo" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Ativando atualização de firmware" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Ativando atualização de firmware para" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Idade" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Aceitar e ativar o remoto?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Atalho para %s" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Todos os dispositivos do mesmo tipo serão atualizados ao mesmo tempo" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Permite fazer downgrade de versões de firmware" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Permite reinstalar versões de firmware existentes" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Permite alternar a ramificação do firmware" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Uma atualização requer uma reinicialização para ser concluída." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Uma atualização requer que o sistema seja desligado por completo." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Responde sim para todas as perguntas" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Aplica atualizações de firmware" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Aplica a atualização mesmo quando não recomendado" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Aplica ficheiros de atualização" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Aplicando atualização…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Firmware aprovado:" msgstr[1] "Firmwares aprovados:" msgstr[2] "Firmwares aprovados:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Perguntar novamente da próxima vez?" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Modo anexar ao firmware" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "A autenticar…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Os detalhes de autenticação são necessários" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Autenticação é necessária para fazer downgrade da versão do firmware em um dispositivo removível" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Autenticação é necessária para fazer downgrade da versão do firmware nesta máquina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "A autenticação é necessária para modificar as definições do BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Autenticação é necessária para modificar um remoto configurado usado para atualizações de firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Autenticação é necessária para modificar uma configuração de daemon" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "A autenticação é necessária para ler as definições do BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Autenticação é necessária para definir a lista de firmwares aprovados" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Autenticação é necessária para assinar dados usando o certificado do cliente" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Autenticação é necessária para alternar para nova versão de firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Autenticação é necessária para desbloquear um dispositivo" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Autenticação é necessária para atualizar o firmware em dispositivo removível" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Autenticação é necessária para atualizar o firmware nesta máquina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Autenticação é necessária para atualizar as somas de verificação armazenadas para o dispositivo" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Relatórios automáticos" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Enviar automaticamente toda vez?" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "Atualizações do BIOS fornecidas via LVFS ou Windows Update" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "XML-CONSTRUTOR NOME-DE-FICHEIRO-DEST" msgid "BYTES" msgstr "BYTES" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Vincula novo driver de kernel" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Arquivos de firmware bloqueados:" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Bloqueando firmware:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Impede que um firmware específico seja instalado" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Versão do gestor de arranque" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Ramificação" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Compila um ficheiro de firmware" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Cancelar" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Cancelado" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Não foi possível aplicar, pois a atualização de dbx já foi aplicada." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Não é possível aplicar atualizações em uma mídia de instalação" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Alterado" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Verifica se o hash criptográfico corresponde ao firmware" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Soma de verificação" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Selecione o ESP:" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Limpa os resultados da última atualização" #. TRANSLATORS: error message msgid "Command not found" msgstr "Comando não encontrado" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Converte um ficheiro de firmware" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Criado" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Crítica" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "A verificação criptográfica de hash está disponível" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Versão atual" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "ID-DISPOSITIVO|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Utilitário DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opções de depuração" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "A descomprimir…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Descrição" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Modo desanexar ao gestor de arranque" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Detalhes" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Opções do dispositivo" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "ID do dispositivo" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Dispositivo adicionado:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "O dispositivo pode recuperar falhas de flashing" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Dispositivo modificado:" #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Um firmware de dispositivo é necessário para ter uma verificação de versão" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "O dispositivo está bloqueado" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Um dispositivo é necessário para instalar todos os lançamentos fornecidos" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "O dispositivo está inalcançável" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "O dispositivo pode ser usado durante a atualização" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Dispositivo removido:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Atualizações de estágios do dispositivo" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "O dispositivo tem suporte para alternar para uma ramificação diferente do firmware" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Método de atualização do dispositivo" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "A atualização do dispositivo precisa de ativação" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "O dispositivo fará backup do firmware antes de instalar" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "O dispositivo não aparecerá novamente após a atualização ser concluída" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Dispositivos que foram atualizados com sucesso:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Dispositivos que não foram atualizados com sucesso:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Dispositivos com nenhuma atualização de firmware disponível: " #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Dispositivos com a versão mais recente do firmware disponível:" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Desabilitado" msgid "Disabled fwupdate debugging" msgstr "Depuração de fwupdate desabilitada" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Desabilita um remoto dado" #. TRANSLATORS: command line option msgid "Display version" msgstr "Exibe a versão" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Não verifica por metadados antigos" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Não verifica por histórico não relatado" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Não verifica se remotos para transferência devem estar ativados" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Não verifica nem solicita configuração para reiniciar após atualizar" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Não inclui prefixo de domínio de log" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Não inclui prefixo com marca de tempo" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Não realiza verificações de segurança de dispositivo" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Não escreve no banco de dados de histórico" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Entende as consequências de trocar a ramificação do firmware?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Deseja desativar este recurso para atualizações futuras?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Deseja atualizar este remoto agora?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Deseja enviar relatórios automaticamente para atualizações futuras?" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Feito!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Fazer downgrade %s de %s para %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Faz downgrade da versão do firmware em um dispositivo" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "A reverter de %s de %s para %s… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "A reverter %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Transferir um ficheiro" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "A transferir…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Despeja dados SMBIOS a partir de um ficheiro" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Duração" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "A ESP especificada não era válida" msgid "Enable" msgstr "Ativar" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Ativa suporte a atualização de firmware em sistemas suportados" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Ativar novo remoto?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Ativar esse remoto?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Ativado" msgid "Enabled fwupdate debugging" msgstr "Depuração de fwupdate habilitada" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Ativa um remoto dado" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "A ativação desta funcionalidade é feita por sua conta e risco, o que significa que precisa de entrar em contato com o fabricante do equipamento original sobre quaisquer problemas causados por estas atualizações. Apenas problemas com o processo de atualização devem ser relatados em $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "A ativação deste remoto é feito a seu próprio custo e risco." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Criptografado" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "RAM criptografada" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Apaga todo histórico de atualização de firmware" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "A apagar…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Sair após pequeno atraso" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Sair após o carregamento do motor" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Exporta a estrutura de ficheiro de um firmware para XML" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Extrai um blob de firmware para imagens" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "FICHEIRO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "FICHEIRO [ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "NOMEDOFICHEIRO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "NOME-DE-FICHEIRO CERTIFICADO CHAVE-PRIVADA" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "NOME-DE-FICHEIRO NOME-ALT-DISPOSITIVO|ID-ALT-DISPOSITIVO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "NOME-DE-FICHEIRO NOME-ALT-DISPOSITIVO|ID-ALT-DISPOSITIVO [NOME-ALT-IMAGEM|ID-ALT-IMAGEM]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "NOME-DE-FICHEIRO ID-DISPOSITIVO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "NOME-DE-FICHEIRO [ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "NOME-DE-FICHEIRO [TIPO-FIRMWARE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "NOME-DE-FICHEIRO-ORIG NOME-DE-FICHEIRO-DEST [TIPO-FIRMWARE-ORIG] [TIPO-FIRMWARE-DEST]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Falhou" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Falha ao aplicar a atualização" #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Falha ao se ligar ao daemon" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Falha ao obter dispositivos pendentes" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Falha ao instalar a atualização de firmware" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Falha ao carregar o dbx local" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Falha ao carregar peculiaridades" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Falha ao carregar o dbx do sistema" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Falha ao bloquear" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Falha ao interpretar argumentos" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Falha ao analisar o ficheiro" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Falha ao analisar opções para --filter" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Falha ao analisar o dbx local" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Falha ao reiniciar" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Falha ao definir o modo splash" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Falha ao validar o conteúdo da ESP" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Nome de ficheiro" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Assinatura de nome de ficheiro" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Fonte do nome do ficheiro" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Nome de ficheiro necessário" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filtra com um conjunto de opções de dispositivo usando um prefixo ~ para excluir, p.ex. \"internal,~needs-reboot\"" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "URI base de firmware" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Serviço D-Bus de Atualização de Firmware" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Daemon de Atualização de Firmware" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Utilitário de Firmware" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Atestação de firmware" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "O firmware já está bloqueado" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "O firmware ainda não está bloqueado" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Os metadados do firmware não foram atualizados a %u dia e podem não estar atualizados." msgstr[1] "Os metadados do firmware não foram atualizados a %u dias e podem não estar atualizados." msgstr[2] "Os metadados do firmware não foram atualizados a %u dias e podem não estar atualizados." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Atualizações de firmware" msgid "Firmware updates are not supported on this machine." msgstr "Não há suporte a atualizações de firmware nessa máquina." msgid "Firmware updates are supported on this machine." msgstr "Há suporte a atualizações de firmware nesta máquina." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Atualizações de firmware desabilitadas; execute \"fwupdmgr unlock\" para habilitar" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Opções" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Força a ação relaxando alguns verificações em tempo de execução" msgid "Force the action ignoring all warnings" msgstr "Força a ação ignorando todos avisos" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Encontrado" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Encriptação completa do disco detetada" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUIDs" msgstr[2] "GUIDs" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" msgid "Get BIOS settings" msgstr "Obter definições do BIOS" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Obtém todas as opções de dispositivo suportadas pelo fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Obtém todos os dispositivos que oferecem suporte às atualizações de firmware" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Obtém todos os plugins registados com o sistema" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Obtém detalhes sobre um ficheiro de firmware" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Obtém os remotos configurados" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Obtém os atributos de segurança do host" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Obtém a lista de firmware aprovado" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Obtém a lista de firmwares bloqueados" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Obtém a lista de atualizações para os hardwares conectados" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Obtém os lançamentos para um dispositivo" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Obtém os resultados da última atualização" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "FICHEIRO-HWIDS" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "O hardware está aguardando para ser reconectado" #. TRANSLATORS: the release urgency msgid "High" msgstr "Alta" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "ID de segurança do host:" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Inativo…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ignora verificações estritas de SSL ao baixar ficheiros" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Ignora falhas de soma de verificação de firmware" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Ignora falhas de incompatibilidade de hardware de firmware" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignorar verificações de segurança de validação" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Ignorando as verificações estritas de SSL; para fazer isso automaticamente no futuro, exporte DISABLE_SSL_STRICT no seu ambiente" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Duração de instalação" msgid "Install old version of signed system firmware" msgstr "Instalar a versão antiga do firmware do sistema assinado" msgid "Install old version of unsigned system firmware" msgstr "Instalar a versão antiga do firmware do sistema não assinado" msgid "Install signed device firmware" msgstr "Instalar firmware assinado no dispositivo" msgid "Install signed system firmware" msgstr "Instalar firmware assinado no sistema" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Instalar primeiro no dispositivo principal" msgid "Install unsigned device firmware" msgstr "Instalar firmware não assinado no dispositivo" msgid "Install unsigned system firmware" msgstr "Instalar firmware não assinado no sistema" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Instalando o firmware…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Instalando atualização de firmware…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Instalando em %s…" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "ACM protegido com Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Fuse programável (OTP) do Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Política de erro do Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Inicialização verificada com Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET ativo" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Ativado para Intel CET" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Dispositivo interno" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Inválido" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Está no modo gestor de arranque" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Problema" msgstr[1] "Problemas" msgstr[2] "Problemas" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "CHAVE,VALOR" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Chaveiro" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "LOCAL" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Última modificação" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Menos que um minuto restante" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licença" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (firmware estável)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (firmware de teste)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Kernel Linux" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Confinamento do kernel Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Swap do Linux" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Lista entradas no dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Lista atualizações de firmware suportadas" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Lista os tipos de firmware disponível" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Lista ficheiros na ESP" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "A carregar…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Bloqueado" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Baixa" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "Modo de fabricação do MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "Substituição de MEI" msgid "MEI version" msgstr "Versão MEI" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Ativa manualmente plugins específicos" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Média" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Assinatura de metadados" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI de metadados" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadados podem ser obtidos do Linux Vendor Firmware Service." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Versão mínima" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Daemon e cliente incompatíveis, use %s" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Modifica um valor de configuração do daemon" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifica um remoto dado" msgid "Modify a configured remote" msgstr "Modificar um remoto configurado" msgid "Modify daemon configuration" msgstr "Modificar a configuração do daemon" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Monitora o daemon por eventos" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Monta a ESP" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Precisa de uma reinício após a instalação" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Precisa reiniciar" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Precisa de desligamento após a instalação" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Nova versão" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Nenhuma ação especificada!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Nenhum downgrade para %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Nenhum ID de firmware encontrado" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Nenhum periférico com capacidade de atualização de firmware foi detectado" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nenhum plugin encontrado" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Nenhum lançamento disponível" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Nenhum remoto está atualmente ativado, então nenhum metadado está disponível." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Nenhum remoto disponível" msgid "No updates available for remaining devices" msgstr "Nenhuma atualização disponível para os dispositivos restantes" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nenhuma atualização foi aplicada" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Não encontrado" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Não suportado" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "OK" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "OK!" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Mostra apenas valor único de PCR" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Apenas atualizações de versão são permitidas" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Saída em formato JSON" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Substitui o caminho padrão da ESP" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "CAMINHO" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Analisa e mostra detalhes sobre um ficheiro de firmware" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Analisando a atualização de dbx…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Analisando o dbx do sistema…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Senha" msgid "Payload" msgstr "Carga" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Pendente" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Percentagem concluída" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Efetuar a operação?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Depuração de plataforma" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Por favor, insira um número de 0 a %u: " #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Dependências de plugin ausentes" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Proteção de DMA pré-inicialização" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Versão anterior" msgid "Print the version number" msgstr "Imprime o número de versão" msgid "Print verbose debug statements" msgstr "Imprime instruções de depuração verbosas" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioridade" msgid "Proceed with upload?" msgstr "Prosseguir com o envio?" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Privativa" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Consulta por suporte a atualização de firmware" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "ID-REMOTO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "ID-REMOTO CHAVE VALOR" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Lê um blob de firmware de um dispositivo" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Lê o firmware do dispositivo para um ficheiro" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Lê o firmware de uma partição para um ficheiro" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Lendo de %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "A ler…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "A reiniciar…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Renova metadados do servidor remoto" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Reinstalar %s para %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Reinstala o firmware atual no dispositivo" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Reinstala firmware em um dispositivo" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "A reinstalar %s com %s… " #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Ramificação de lançamento" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "ID da versão" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID remoto" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Substitui os dados em um ficheiro de firmware existente" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI do relatório" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Relatado ao servidor remoto" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Pedido cancelado" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "O sistema de ficheiros efivarfs necessário não foi encontrado" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "O hardware necessário não foi encontrado" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Requer um gestor de arranque" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Requer ligação à Internet" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Reiniciar agora?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Reiniciar o daemon para tornar a mudança efetiva?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "A reiniciar o dispositivo…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Retorna todos os IDs de hardware para a máquina" #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Execute `fwupdmgr get-upgrades` para mais informações." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Executa a rotina de limpeza da composição de plugin ao usar install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Executa a rotina de preparação da composição de plugin ao usar install-blob" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "O kernel em uso é muito antigo" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Sufixo de tempo de execução" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "Descritor de BIOS do SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "Região BIOS do SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "Bloqueio de SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "Escrita de SPI" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "SUBSISTEMA DRIVER [ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Guardar um ficheiro que permite a geração de IDs de hardware" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Agenda instalação para próxima reinicialização quando possível" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "A agendar…" #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Veja %s para mais informações." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Dispositivo selecionado" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Volume selecionado" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Número de série" msgid "Set one or more BIOS settings" msgstr "Definir uma ou mais definições do BIOS" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Define a opção de depuração durante atualização" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Define a lista de firmwares aprovados" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "As definições serão aplicadas após a reinicialização do sistema" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Compartilha histórico de firmware com os desenvolvedores" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Mostra todos os resultados" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Mostra as versões do cliente e do daemon" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Mostrar informações detalhadas do daemon para um domínio em particular" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Mostra informações de depuração para todos domínios" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Mostrar opções de depuração" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Mostra dispositivos que não são atualizáveis" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Mostra informações adicionais de depuração" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Mostra histórico de atualizações de firmware" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Mostra a versão calculada do dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Mostra o registo de depuração da tentativa mais recente de atualização" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Mostra as informações do status de atualização de firmware" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Desligar agora?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Assina um firmware com uma nova chave" msgid "Sign data using the client certificate" msgstr "Assina dados usando o certificado cliente" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Assina dados usando o certificado cliente" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Assina os dados enviados com o certificado cliente" msgid "Signature" msgstr "Assinatura" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Tamanho" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Fonte" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Especifica ID(s) de Fornecedor/Produto de dispositivo DFU" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Especifica o ficheiro da base de dados dbx" msgid "Specify the number of bytes per USB transfer" msgstr "Especifica o número de bytes por transferência USB" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Sucesso" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Todos os dispositivos ativados com sucesso" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Remoto desabilitado com sucesso" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Transferidos com sucesso novos metadados: " #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Remoto ativado e atualizado com sucesso" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Remoto ativado com sucesso" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmware instalado com sucesso" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Valor da configuração modificada com sucesso" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Remoto modificado com sucesso" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metadados atualizados manualmente com sucesso" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Somas de verificação de dispositivo atualizadas com sucesso" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Enviado com sucesso %u relatório" msgstr[1] "Enviados com sucesso %u relatórios" msgstr[2] "Enviados com sucesso %u relatórios" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Somas de verificação de dispositivo verificadas com sucesso" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Resumo" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Suportado" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "CPU suportado" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Suporte no servidor remoto" #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Suspensão para inativo" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Suspensão para RAM" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Trocar ramificação de %s para %s ?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Alterna a ramificação do firmware no dispositivo" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "O sistema exige uma fonte de alimentação externa" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEXTO" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "Reconstrução de PCR0 de TPM" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "PCRs vazios de TPM" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Etiqueta" msgstr[1] "Etiquetas" msgstr[2] "Etiquetas" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Contaminado" msgid "Target" msgstr "Destino" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Testa um dispositivo usando um manifesto JSON" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "O LVFS é um serviço livre que opera como uma entidade legal independente e tem nenhuma ligação com $OS_RELEASE:NAME$. O seu distribuidor pode não ter verificado alguma das atualizações de firmware por compatibilidade com O seu sistema ou dispositivos ligados. Todo o firmware é fornecido apenas pelo fabricante do equipamento original." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "O TPM PCR0 diverge da reconstrução." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "O daemon carregou código de terceiros e não é mais suportado pelos desenvolvedores upstream!" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "O firmware de %s não é fornecido por %s, o fornecedor de hardware." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "O relógio do sistema não foi definido corretamente e baixar ficheiros pode resultar em falha." #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Não há ficheiros de firmware bloqueados" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Nenhum firmware aprovado." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Este pacote não foi validado, pode não funcionar corretamente." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Esse programa só pode funcionar corretamente como root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Este remoto contém firmware que não está embargado, mas ainda está a ser testado pelo fornecedor do hardware. Deve garantir que tem uma maneira de reverter manualmente do firmware se a atualização do firmware falhar." #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Este sistema possui problemas de tempo de execução de HSI." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Este sistema possui um nível de segurança de HSI baixo." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Esta ferramenta permite que um administrador aplique atualizações de dbx UEFI." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Esta ferramenta permite que um administrador depure da operação UpdateCapsule." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Esta ferramenta permite que um administrador consulte e controle o daemon fwupd, permitindo que ele execute ações como instalar ou fazer downgrade do firmware." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Esta ferramenta permite que um administrador use os plugins do fwupd sem estarem instalados no sistema host." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Esta ferramenta só pode ser usada pelo utilizador root" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Esta ferramenta vai ler e analisar o log de evento de TPM do firmware do sistema." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Falha transitória" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipo" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "Partição UEFI ESP não detectada ou configurada" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Utilitário de Firmware UEFI" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI bootservice variables" msgstr "Variáveis do serviço de arranque UEFI" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "Atualizações de cápsula UEFI não disponíveis ou habilitadas na configuração do firmware" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "Utilitário de dbx UEFI" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "Firmware UEFI não pode ser atualizado no modo BIOS legado" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "Chave de plataforma UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "Secure boot de UEFI" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Não foi possível conectar ao serviço" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Desvincula o driver atual" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Desbloqueando firmware:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Retira impedimento de um firmware específico ser instalado" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Descriptografado" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Desconhecido" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Dispositivo desconhecido" msgid "Unlock the device to allow access" msgstr "Desbloquear o dispositivo para permitir acesso" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Desbloqueado" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Desbloqueia o dispositivo para acesso do firmware" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Desmonta a ESP" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Desativa a opção de depuração durante atualização" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Sem suporte ao daemon na versão %s, a versão do cliente é %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Descontaminado" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Atualizável" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Erro de atualização" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Mensagem de atualização" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Estado da atualização" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "A falha de atualização é um problema conhecido, visite esta URL para mais informações:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Atualizar agora?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "A atualização requer um reinício" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Atualiza o hash criptográfico armazenado com o atual conteúdo da ROM" msgid "Update the stored device verification information" msgstr "Atualizar as informações de verificação armazenadas do dispositivo" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Atualiza os metadados armazenados com o conteúdo atual" msgid "Updating" msgstr "Atualizando" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "A atualizar %s de %s para %s… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Atualizando %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Atualizar %s de %s para %s?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "O envio de relatórios de firmware ajuda os fornecedores de hardware a identificar rapidamente atualizações com falha e êxito em dispositivos." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Urgência" #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Use fwupdtool --help para ajuda" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Usa opções de peculiaridades ao instalar firmware" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "O utilizador foi notificado" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Nome de utilizador" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Válido" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Validando conteúdo da ESP…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variação" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Fornecedor" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "A verificar…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Versão" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "ATENÇÃO" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "A aguardar…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Monitora alterações no hardware" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Escreve um firmware do ficheiro para o dispositivo" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Escreve um firmware do ficheiro para uma partição" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "A gravar ficheiro:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "A gravar…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "O seu distribuidor pode não ter verificado nenhuma das atualizações de firmware por compatibilidade com seu sistema ou dispositivos ligados." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "O seu hardware pode ser danificado usando este firmware e instalar esta versão pode anular qualquer garantia com %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[SOMA-VERIFICAÇÃO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[ID-DISPOSITIVO|GUID] [RAMIFICAÇÃO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[FICHEIRO FICHEIRO-ASSINATURA ID-REMOTO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[NOME-DE-FICHEIRO1] [NOME-DE-FICHEIRO2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[FICHEIRO-SMBIOS|FICHEIRO-HWIDS]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "padrão" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "Utilitário de registo de eventos TPM do fwupd" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "Plugins do fwupd" fwupd-1.9.16/po/pt_BR.po000066400000000000000000002570411460375044200147370ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Derek W. Stavis , 2015 # Derek W. Stavis , 2016 # Derek W. Stavis , 2015-2016 # Rafael Fontenelle , 2017-2018 # Rafael Fontenelle , 2015-2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Portuguese (Brazil) (http://app.transifex.com/freedesktop/fwupd/language/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pt_BR\n" "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minuto restante" msgstr[1] "%.0f minutos restantes" msgstr[2] "%.0f minutos restantes" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "Atualização de BMC para %s" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "Atualização da bateria para %s" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "Atualização de microcódigo de CPU para %s" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "Atualização de câmera para %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "Atualização da configuração de %s " #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Atualização consumidor-final de ME para %s " #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Atualização de controlador para %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Atualização corporativa de ME para %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Atualização do dispositivo %s" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "Atualização de tela para %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Atualização do controlador embarcado %s" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "Atualização do teclado para %s" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "Atualização de ME para %s" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "Atualização do mouse para %s" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "Atualização da interface de rede %s" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "Atualização do controlador de armazenamento %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Atualização do sistema %s" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "Atualização de TPM para %s" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Atualização de controlador Thunderbolt para %s" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "Atualização do touchpad para %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Atualização de %s" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s e todos os dispositivos conectados não podem ser usados durante a atualização." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "%s apareceu: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "%s alterado: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "%s desapareceu: %s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "Modo de fabricação de %s" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s deve permanecer conectado durante a atualização para evitar danos." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s deve permanecer conectado a uma fonte de energia durante a atualização para evitar danos." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "substituição de %s" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "versão %s " #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dia" msgstr[1] "%u dias" msgstr[2] "%u dias" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] " %u dispositivo tem uma atualização de firmware disponível." msgstr[1] "%u dispositivos têm uma atualização de firmware disponível." msgstr[2] "%u dispositivos têm uma atualização de firmware disponível." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u dispositivo não é a configuração mais conhecida." msgstr[1] "%u dispositivos não são a configuração mais conhecida." msgstr[2] "%u dispositivos não são a configuração mais conhecida." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u hora" msgstr[1] "%u horas" msgstr[2] "%u horas" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u dispositivo local suportado" msgstr[1] "%u dispositivos locais suportados" msgstr[2] "%u dispositivos locais suportados" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minuto" msgstr[1] "%u minutos" msgstr[2] "%u minutos" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u segundo" msgstr[1] "%u segundos" msgstr[2] "%u segundos" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(obsoleto)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "Um PCR de TPM é agora um valor inválido" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Ação necessária:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Habilita dispositivos" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Ativa dispositivos pendentes" msgid "Activate the new firmware on the device" msgstr "Ativar o novo firmware no dispositivo" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Ativando atualização de firmware" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Ativando atualização de firmware para" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Idade" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Concordar e habilitar o remoto?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Atalho para %s" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "Todos PCRs de TPM são agora válidos" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Todos PCRs de TPM são válidos" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Todos os dispositivos do mesmo tipo serão atualizados ao mesmo tempo" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Permite fazer downgrade de versões de firmware" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Permite reinstalar versões de firmware existentes" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Permite alternar a ramificação do firmware" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Ramificação alternativa" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Uma atualização requer uma reinicialização para ser concluída." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Uma atualização requer que o sistema seja desligado por completo." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Responde sim para todas as perguntas" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Aplica atualizações de firmware" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Aplica a atualização mesmo quando não recomendado" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Aplica arquivos de atualização" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Aplicando atualização…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Firmware aprovado:" msgstr[1] "Firmwares aprovados:" msgstr[2] "Firmwares aprovados:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Perguntar novamente da próxima vez?" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Modo anexar ao firmware" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Autenticando…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Os detalhes de autenticação são necessários" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Autenticação é necessária para desatualizar a versão do firmware em um dispositivo removível" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Autenticação é necessária para desatualizar a versão do firmware nesta máquina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Autenticação é necessária para modificar as configurações da BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Autenticação é necessária para modificar um remoto configurado usado para atualizações de firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Autenticação é necessária para modificar uma configuração de daemon" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "Autenticação é necessária para ler as configurações da BIOS" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Autenticação é necessária para definir a lista de firmwares aprovados" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Autenticação é necessária para assinar dados usando o certificado do cliente" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Autenticação é necessária para alternar para nova versão de firmware" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Autenticação é necessária para desbloquear um dispositivo" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Autenticação é necessária para atualizar o firmware em dispositivo removível" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Autenticação é necessária para atualizar o firmware nesta máquina" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Autenticação é necessária para atualizar as somas de verificação armazenadas para o dispositivo" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Relatórios automáticos" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Enviar automaticamente toda vez?" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "Proteção contra reversão da BIOS" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "Atualizações do BIOS entregues via LVFS ou Windows Update" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "XML-CONSTRUTOR NOME-DE-ARQUIVO-DEST" msgid "BYTES" msgstr "BYTES" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Vincula novo driver de kernel" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Arquivos de firmware bloqueados:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Versão bloqueada" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Bloqueando firmware:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Impede que um firmware específico seja instalado" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Versão do gerenciador de boot" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Ramificação" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Compila um arquivo de firmware" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Cancelar" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Cancelado" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Não foi possível aplicar, pois a atualização de dbx já foi aplicada." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Não é possível aplicar atualizações em uma mídia de instalação" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Alterado" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Verifica se o hash criptográfico corresponde ao firmware" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Soma de verificação" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Escolha a ESP:" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Limpa os resultados da última atualização" #. TRANSLATORS: error message msgid "Command not found" msgstr "Comando não encontrado" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Suporte mantido pela comunidade" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Converte um arquivo de firmware" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Criada" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Crítica" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "A verificação criptográfica de hash está disponível" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Versão atual" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "ID-DISPOSITIVO|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Utilitário DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Opções de depuração" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Descompactando…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Descrição" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Modo desanexar ao gerenciador de boot" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Detalhes" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "Desviar da configuração mais conhecida?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Opções do dispositivo" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "ID do dispositivo" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Dispositivo adicionado:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "O dispositivo pode recuperar falhas de flashing" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Dispositivo modificado:" #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Um firmware de dispositivo é necessário para ter uma verificação de versão" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "O dispositivo está bloqueado" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Um dispositivo é necessário para instalar todos os lançamentos fornecidos" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "O dispositivo está inalcançável" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "O dispositivo pode ser usado durante a atualização" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Dispositivo removido:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Atualizações de estágios do dispositivo" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "O dispositivo tem suporte para alternar para uma ramificação diferente do firmware" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Método de atualização do dispositivo" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "A atualização do dispositivo precisa de ativação" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "O dispositivo fará backup do firmware antes de instalar" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "O dispositivo não aparecerá novamente após a atualização ser concluída" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Dispositivos que foram atualizados com sucesso:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Dispositivos que não foram atualizados com sucesso:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Dispositivos com nenhuma atualização de firmware disponível:" #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Dispositivos com a versão mais recente do firmware disponível:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "Não foi encontrado qualquer dispositivo com GUID correspondente" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Desabilitado" msgid "Disabled fwupdate debugging" msgstr "Depuração de fwupdate desabilitada" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Desabilita um remoto dado" #. TRANSLATORS: command line option msgid "Display version" msgstr "Exibe a versão" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Não verifica por metadados antigos" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Não verifica por histórico não relatado" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Não verifica se remotos para dowload devem estar habilitados" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Não verifica nem solicita configuração para reiniciar após atualizar" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Não inclui prefixo de domínio de log" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Não inclui prefixo com marca de tempo" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Não realiza verificações de segurança de dispositivo" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "Não solicita dispositivos" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Não escreve no banco de dados de histórico" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Você entende as consequências de trocar a ramificação do firmware?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Você deseja desabilitar este recurso para atualizações futuras?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Você deseja atualizar este remoto agora?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Você deseja enviar relatórios automaticamente para atualizações futuras?" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Feito!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Desatualizar %s de %s para %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Desatualiza a versão do firmware em um dispositivo" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Fazendo downgrade de %s de %s para %s… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Fazendo downgrade %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Baixa um arquivo" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Baixando…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Despeja dados SMBIOS a partir de um arquivo" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Duração" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "A ESP especificada não era válida" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Host emulado" msgid "Enable" msgstr "Habilitar" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Habilita suporte a atualização de firmware em sistemas suportados" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Habilitar novo remoto?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Habilitar esse remoto?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Habilitado" msgid "Enabled fwupdate debugging" msgstr "Depuração de fwupdate habilitada" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Habilitado se o hardware corresponder" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Habilita um remoto dado" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "A habilitação dessa funcionalidade é feita por sua conta e risco, o que significa que você precisa entrar em contato com o fabricante do equipamento original sobre quaisquer problemas causados por essas atualizações. Somente problemas com o processo de atualização devem ser relatados em $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "A habilitação deste remoto é feito a seu próprio custo e risco." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Criptografado" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "RAM criptografada" #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Fim de vida" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Apaga todo histórico de atualização de firmware" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Apagando…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Sair após pequeno atraso" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Sair após o carregamento do motor" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Exporta a estrutura de arquivo de um firmware para XML" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Extrai um blob de firmware para imagens" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "ARQUIVO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "ARQUIVO [ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "NOME-DE-ARQUIVO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "NOME-DE-ARQUIVO CERTIFICADO CHAVE-PRIVADA" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "NOME-DE-ARQUIVO NOME-ALT-DISPOSITIVO|ID-ALT-DISPOSITIVO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "NOME-DE-ARQUIVO NOME-ALT-DISPOSITIVO|ID-ALT-DISPOSITIVO [NOME-ALT-IMAGEM|ID-ALT-IMAGEM]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "NOME-DE-ARQUIVO ID-DISPOSITIVO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "NOME-DE-ARQUIVO DESLOCAMENTO DADOS [TIPO-DO-FIRMWARE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "NOME-DE-ARQUIVO [ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "NOME-DE-ARQUIVO [TIPO-FIRMWARE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "NOME-DE-ARQUIVO-ORIG NOME-DE-ARQUIVO-DEST [TIPO-FIRMWARE-ORIG] [TIPO-FIRMWARE-DEST]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "NOME-DE-ARQUIVO|SOMA-VERIFICAÇÃO1[,SOMA-VERIFICAÇÃO2][,SOMA-VERIFICAÇÃO3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Falhou" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Falha ao aplicar a atualização" #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Falha ao se conectar ao daemon" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Falha ao obter dispositivos pendentes" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Falha ao instalar a atualização de firmware" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Falha ao carregar o dbx local" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Falha ao carregar peculiaridades" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Falha ao carregar o dbx do sistema" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Falha ao bloquear" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Falha ao interpretar argumentos" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Falha ao analisar o arquivo" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Falha ao analisar opções para --filter" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Falha ao analisar o dbx local" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Falha ao reinicializar" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Falha ao definir o modo splash" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Falha ao validar o conteúdo da ESP" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Nome de arquivo" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Assinatura de nome de arquivo" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Fonte do nome do arquivo" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Nome de arquivo necessário" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filtra com um conjunto de opções de dispositivo usando um prefixo ~ para excluir, p.ex. \"internal,~needs-reboot\"" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Descritor da BIOS do firmware" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Região da BIOS do firmware" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "URI base de firmware" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Serviço D-Bus de Atualização de Firmware" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Daemon de Atualização de Firmware" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Utilitário de Firmware" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Proteção contra gravação de firmware" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Bloqueio de proteção contra gravação de firmware" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Atestação de firmware" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "O firmware já está bloqueado" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "O firmware ainda não está bloqueado" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Os metadados do firmware não foram atualizados a %u dia e podem não estar atualizados." msgstr[1] "Os metadados do firmware não foram atualizados a %u dias e podem não estar atualizados." msgstr[2] "Os metadados do firmware não foram atualizados a %u dias e podem não estar atualizados." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Atualizações de firmware" msgid "Firmware updates are not supported on this machine." msgstr "Não há suporte a atualizações de firmware nessa máquina." msgid "Firmware updates are supported on this machine." msgstr "Há suporte a atualizações de firmware nesta máquina." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Atualizações de firmware desabilitadas; execute \"fwupdmgr unlock\" para habilitar" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Opções" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Força a ação relaxando alguns verificações em tempo de execução" msgid "Force the action ignoring all warnings" msgstr "Força a ação ignorando todos avisos" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Encontrado" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Criptografia de disco completo detectada" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "Os segredos de criptografia de disco completa podem ser invalidados ao atualizar" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Plataforma fundida" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUIDs" msgstr[2] "GUIDs" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" msgid "Get BIOS settings" msgstr "Obter as configurações da BIOS" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Obtém todas as opções de dispositivo suportadas pelo fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Obtém todos os dispositivos que oferecem suporte às atualizações de firmware" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Obtém todos os plugins registrados com o sistema" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Obtém detalhes sobre um arquivo de firmware" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Obtém os remotos configurados" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Obtém os atributos de segurança do host" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Obtém a lista de firmware aprovado" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Obtém a lista de firmwares bloqueados" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Obtém a lista de atualizações para os hardwares conectados" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Obtém os lançamentos para um dispositivo" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Obtém os resultados da última atualização" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "ARQUIVO-HWIDS" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "O hardware está aguardando para ser reconectado" #. TRANSLATORS: the release urgency msgid "High" msgstr "Alta" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Eventos de segurança do host" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "ID de segurança do host:" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "Proteção de IOMMU" #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "Proteção de dispositivo IOMMU desabilitada" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "Proteção de dispositivo IOMMU habilitada" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Ocioso…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ignora verificações estritas de SSL ao baixar arquivos" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Ignora falhas de soma de verificação de firmware" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Ignora falhas de incompatibilidade de hardware de firmware" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignorar verificações de segurança de validação" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Ignorando as verificações estritas de SSL; para fazer isso automaticamente no futuro, exporte DISABLE_SSL_STRICT em seu ambiente" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Duração de instalação" msgid "Install old version of signed system firmware" msgstr "Instalar versão antiga do firmware do sistema assinado" msgid "Install old version of unsigned system firmware" msgstr "Instalar a versão antiga do firmware do sistema não assinado" msgid "Install signed device firmware" msgstr "Instalar firmware assinado no dispositivo" msgid "Install signed system firmware" msgstr "Instalar firmware assinado no sistema" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Instalar primeiro no dispositivo pai" msgid "Install unsigned device firmware" msgstr "Instalar firmware não assinado no dispositivo" msgid "Install unsigned system firmware" msgstr "Instalar firmware não assinado no sistema" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Instalando o firmware…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Instalando atualização de firmware…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Instalando em %s…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "A instalação desta atualização também pode anular qualquer garantia do dispositivo." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "ACM protegido com Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "ACM protegido com Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Política de erros da Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Fuse de Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Fuse programável (OTP) do Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Inicialização verificada por Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Política de erro do Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Inicialização verificada com Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET ativo" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Habilitado para Intel CET" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Dispositivo interno" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Inválido" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "É desatualização" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Está no modo gerenciador de boot" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "É atualização" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Problema" msgstr[1] "Problemas" msgstr[2] "Problemas" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "CHAVE,VALOR" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "Kernel não está mais contaminado" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "Kernel está contaminado" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Lockdown do kernel desabilitado" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Lockdown do kernel habilitado" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Chaveiro" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "LOCAL" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Última modificação" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Menos que um minuto restante" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licença" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Lockdown do kernel Linux" #. TRANSLATORS: longer description msgid "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgstr "O modo Linux Kernel Lockdown impede que contas de administrador (root) acessem e alterem partes críticas do software do sistema." #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Verificação do kernel Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Swap Linux" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (firmware estável)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (firmware de teste)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Kernel Linux" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Lockdown do kernel Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Swap do Linux" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Lista entradas no dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Lista atualizações de firmware suportadas" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Lista os tipos de firmware disponível" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Lista arquivos na ESP" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Carregando…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Bloqueado" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Baixa" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "Manifesto de chave de MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "Modo de fabricação do MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "substituição de MEI" msgid "MEI version" msgstr "versão MEI" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Habilita manualmente plugins específicos" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Média" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Assinatura de metadados" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI de metadados" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadados podem ser obtidos do Linux Vendor Firmware Service." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Versão mínima" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Daemon e cliente incompatíveis, use %s" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Modifica um valor de configuração do daemon" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifica um remoto dado" msgid "Modify a configured remote" msgstr "Modificar um remoto configurado" msgid "Modify daemon configuration" msgstr "Modificar a configuração do daemon" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Monitora o daemon por eventos" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Monta a ESP" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Precisa de uma reinicialização após a instalação" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Precisa reiniciar" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Precisa de desligamento após a instalação" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Nova versão" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Nenhuma ação especificada!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Nenhuma desatualização para %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Nenhum ID de firmware encontrado" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Nenhum periférico com capacidade de atualização de firmware foi detectado" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Nenhum plugin encontrado" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Nenhum lançamento disponível" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Nenhum remoto está atualmente habilitado, então nenhum metadado está disponível." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Nenhum remoto disponível" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Nenhum dispositivo atualizável" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Nenhuma atualização disponível" msgid "No updates available for remaining devices" msgstr "Nenhuma atualização disponível para os dispositivos restantes" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Nenhuma atualização foi aplicada" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Não aprovada" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Não encontrado" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Não suportado" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "OK" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "OK!" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Mostra apenas valor único de PCR" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Apenas atualizações de versão são permitidas" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Saída em formato JSON" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Substitui o caminho padrão da ESP" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "CAMINHO" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Analisa e mostra detalhes sobre um arquivo de firmware" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Analisando a atualização de dbx…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Analisando o dbx do sistema…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Senha" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Corrige um blob de firmware em um deslocamento conhecido" msgid "Payload" msgstr "Carga" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Pendente" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Percentagem concluída" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Efetuar a operação?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Depuração de plataforma" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Depuração da plataforma" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Certifique-se de ter a chave de recuperação de volume antes de continuar." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Por favor, insira um número de 0 a %u: " #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Dependências de plugin ausentes" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "Proteção de DMA pré-inicialização" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Proteção de DMA pré-inicialização" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "Proteção de DMA pré-inicialização está desabilitada" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "Proteção de DMA pré-inicialização está habilitada" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Versão anterior" msgid "Print the version number" msgstr "Imprime o número de versão" msgid "Print verbose debug statements" msgstr "Imprime instruções de depuração verbosas" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioridade" msgid "Proceed with upload?" msgstr "Prosseguir com o envio?" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Proteção contra reversão do processador" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Privativa" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Consulta por suporte a atualização de firmware" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "ID-REMOTO" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "ID-REMOTO CHAVE VALOR" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Lê um blob de firmware de um dispositivo" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Lê o firmware do dispositivo para um arquivo" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Lê o firmware de uma partição para um arquivo" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Lendo de %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Lendo…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Reinicializando…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Renova metadados do servidor remoto" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Reinstalar %s para %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Reinstala o firmware atual no dispositivo" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Reinstala firmware em um dispositivo" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Reinstalando %s com %s… " #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Ramificação de lançamento" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Opções da versão" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "ID Versão" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "ID remoto" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Substitui os dados em um arquivo de firmware existente" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI do relatório" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Relatado ao servidor remoto" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Solicitação canceladas" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "O sistema de arquivos efivarfs necessário não foi encontrado" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "O hardware necessário não foi encontrado" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Requer um gerenciador de boot" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Requer conexão com a Internet" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Reiniciar agora?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Reiniciar o daemon para tornar a mudança efetiva?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Reiniciando dispositivo…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Retorna todos os IDs de hardware para a máquina" #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Execute `fwupdmgr get-upgrades` para mais informações." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Executa a rotina de limpeza da composição de plugin ao usar install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Executa a rotina de preparação da composição de plugin ao usar install-blob" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "O kernel em uso é muito antigo" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Sufixo de tempo de execução" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "Descritor de BIOS do SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "Região BIOS do SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "Bloqueio de SPI" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "Proteção contra repetição de SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "Escrita de SPI" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "Proteção contra escrita de SPI" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "SUBSISTEMA DRIVER [ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Salva um arquivo que permite a geração de IDs de hardware" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Agenda instalação para próxima reinicialização quando possível" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Agendando…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Inicialização segura desabilitada" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Inicialização segura habilitada" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Veja %s para mais detalhes." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Veja %s para mais informações." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Dispositivo selecionado" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Volume selecionado" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Número de série" msgid "Set one or more BIOS settings" msgstr "Defina as configurações de uma ou mais BIOS" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Define a opção de depuração durante atualização" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Define a lista de firmwares aprovados" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "As configurações serão aplicadas após a reinicialização do sistema" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Compartilha histórico de firmware com os desenvolvedores" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Mostra todos os resultados" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Mostra as versões do cliente e do daemon" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Mostrar informações detalhadas do daemon para um domínio em particular" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Mostra informações de depuração para todos domínios" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Mostrar opções de depuração" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Mostra dispositivos que não são atualizáveis" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Mostra informações adicionais de depuração" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Mostra histórico de atualizações de firmware" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Mostra a versão calculada do dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Mostra o registro log de depuração da tentativa mais recente de atualização" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Mostra as informações do status de atualização de firmware" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Desligar agora?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Assina um firmware com uma nova chave" msgid "Sign data using the client certificate" msgstr "Assina dados usando o certificado cliente" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Assina dados usando o certificado cliente" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Assina os dados enviados com o certificado cliente" msgid "Signature" msgstr "Assinatura" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Carga útil assinada" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Tamanho" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Alguns dos segredos da plataforma podem ser invalidados ao atualizar este firmware." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Fonte" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Especifica ID(s) de Fornecedor/Produto de dispositivo DFU" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Especifica o arquivo de banco de dados dbx" msgid "Specify the number of bytes per USB transfer" msgstr "Especifica o número de bytes por transferência USB" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Sucesso" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Todos os dispositivos ativados com sucesso" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Remoto desabilitado com sucesso" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Baixados com sucesso novos metadados:" #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Remoto habilitado e atualizado com sucesso" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Remoto habilitado com sucesso" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Firmware instalado com sucesso" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Valor da configuração modificada com sucesso" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Remoto modificado com sucesso" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Metadados atualizados manualmente com sucesso" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Somas de verificação de dispositivo atualizadas com sucesso" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Enviado com sucesso %u relatório" msgstr[1] "Enviados com sucesso %u relatórios" msgstr[2] "Enviados com sucesso %u relatórios" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Somas de verificação de dispositivo verificadas com sucesso" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Resumo" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Suportado" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "CPU suportada" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Suporte no servidor remoto" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Suspender para a RAM" #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Suspensão para inativo" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Suspensão para RAM" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Trocar ramificação de %s para %s ?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Alterna a ramificação do firmware no dispositivo" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "O sistema exige uma fonte de alimentação externa" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEXTO" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "Reconstrução de PCR0 de TPM" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "Reconstrução PCR0 de TPM é inválido" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "Reconstrução PCR0 de TPM é agora válido" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "PCRs vazios de TMP" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Tag" msgstr[1] "Tags" msgstr[2] "Tags" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Contaminado" msgid "Target" msgstr "Alvo" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Testa um dispositivo usando um manifesto JSON" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "O LVFS é um serviço livre que opera como uma entidade legal independente e tem nenhuma conexão com $OS_RELEASE:NAME$. Seu distribuidor pode não ter verificado alguma das atualizações de firmware por compatibilidade com seu sistema ou dispositivos conectados. Todo firmware é fornecido apenas pelo fabricante do equipamento original." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "O TPM PCR0 diverge da reconstrução." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "O daemon carregou código de terceiros e não é mais suportado pelos desenvolvedores upstream!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "A versão do dispositivo não corresponde: obteve %s, esperava %s" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "O firmware de %s não é fornecido por %s, o fornecedor de hardware." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "O relógio do sistema não foi definido corretamente e baixar arquivos pode resultar em falha." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "O fornecedor não forneceu nenhuma nota de lançamento." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Existem dispositivos com problemas:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Não há arquivos de firmware bloqueados" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Nenhum firmware aprovado." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "Este dispositivo será revertido para %s quando o comando %s for executado." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "Este firmware é fornecido pelos membros da comunidade LVFS e não é fornecido (ou suportado) pelo fornecedor de hardware original." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Este pacote não foi validado, ele pode não funcionar corretamente." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Esse programa só pode funcionar corretamente como root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Este remoto contém firmware que não está embargado, mas ainda está sendo testado pelo fornecedor do hardware. Você deve garantir que você tenha uma maneira de desatualizar manualmente o firmware se a atualização do firmware falhar." #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Este sistema possui problemas de tempo de execução de HSI." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Este sistema possui um nível de segurança de HSI baixo." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Esta ferramenta permite que um administrador aplique atualizações de dbx UEFI." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Esta ferramenta permite que um administrador depure da operação UpdateCapsule." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Esta ferramenta permite que um administrador consulte e controle o daemon fwupd, permitindo que ele execute ações como instalar ou fazer downgrade do firmware." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Esta ferramenta permite que um administrador use os plugins do fwupd sem estarem instalados no sistema host" #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Esta ferramenta só pode ser usada pelo usuário root" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Esta ferramenta vai ler e analisar o log de evento de TPM do firmware do sistema." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Falha transitória" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Metadados confiável" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Carga útil confiável" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tipo" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "Partição UEFI ESP não detectada ou configurada" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Utilitário de Firmware UEFI" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "Atualizações de cápsula UEFI não disponíveis ou habilitadas na configuração do firmware" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "Utilitário de dbx UEFI" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "Firmware UEFI não pode ser atualizado no modo BIOS legado" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "Chave de plataforma UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "Secure boot de UEFI" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Não foi possível conectar ao serviço" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Desvincula o driver atual" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Desbloqueando firmware:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Retira impedimento de um firmware específico ser instalado" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Descriptografado" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Desconhecido" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Dispositivo desconhecido" msgid "Unlock the device to allow access" msgstr "Desbloquear o dispositivo para permitir acesso" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Desbloqueado" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Desbloqueia o dispositivo para acesso do firmware" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Desmonta a ESP" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Desativa a opção de depuração durante atualização" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Carga útil não assinada" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Sem suporte ao daemon na versão %s, a versão do cliente é %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Descontaminado" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Atualizável" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Erro de atualização" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Atualizar imagem" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Mensagem de atualização" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Estado da atualização" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "A falha de atualização é um problema conhecido, visite essa URL para mais informações:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Atualizar agora?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "A atualização requer uma reinicialização" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Atualiza o hash criptográfico armazenado com o atual conteúdo da ROM" msgid "Update the stored device verification information" msgstr "Atualizar as informações de verificação armazenadas do dispositivo" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Atualiza os metadados armazenados com o conteúdo atual" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "Atualiza todos os dispositivos especificados para a versão de firmware mais recente ou todos os dispositivos se não especificados" msgid "Updating" msgstr "Atualizando" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Atualizando %s de %s para %s… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Atualizando %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Atualizar %s de %s para %s?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "O envio de relatórios de firmware ajuda os fornecedores de hardware a identificar rapidamente atualizações com falha e êxito em dispositivos reais." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Urgência" #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Use fwupdtool --help para ajuda" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Usa opções de peculiaridades ao instalar firmware" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "O usuário foi notificado" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Nome de usuário" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Válido" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Validando conteúdo da ESP…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variação" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Fornecedor" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verificando…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Versão" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "AVISO" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Aguardando…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Monitora alterações no hardware" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Escreve um firmware do arquivo para o dispositivo" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Escreve um firmware do arquivo para uma partição" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Escrevendo arquivo:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Escrevendo…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Seu distribuidor pode não ter verificado nenhuma das atualizações de firmware por compatibilidade com seu sistema ou dispositivos conectados." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Seu hardware pode ser danificado usando este firmware e instalar esta versão pode anular qualquer garantia com %s." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "Seu sistema está configurado para o BKC de %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[SOMA-VERIFICAÇÃO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[ID-DISPOSITIVO|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[ID-DISPOSITIVO|GUID] [RAMIFICAÇÃO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[ID-DISPOSITIVO|GUID] [VERSÃO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[ARQUIVO ARQUIVO-ASSINATURA ID-REMOTO]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[NOME-DE-ARQUIVO1] [NOME-DE-ARQUIVO2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[ARQUIVO-SMBIOS|ARQUIVO-HWIDS]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "padrão" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "Utilitário de registro de eventos TPM do fwupd" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "Plugins do fwupd" fwupd-1.9.16/po/ru.po000066400000000000000000002175301460375044200143560ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Igor , 2017 # Serge Vylekzhanin , 2015-2019 # Sergej A. , 2022-2023 # Sergej A. , 2022 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Russian (http://app.transifex.com/freedesktop/fwupd/language/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "Осталась %.0f минута" msgstr[1] "Осталось %.0f минуты" msgstr[2] "Осталось %.0f минут" msgstr[3] "Осталось %.0f минут" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "Обновление аккумулятора %s" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "Обновление камеры %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "Обновление конфигурации %s" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Обновление подсистемы Consumer МЕ устройства %s" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Обновление контроллера %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Обновление подсистемы Corporate МЕ устройства %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Обновление устройства %s" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "Обновление дисплея %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Обновление встроенного контроллера %s" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "Обновление клавиатуры %s" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "Обновление подсистемы МЕ устройства %s" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "Обновление мыши %s" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "Обновление сетевого интерфейса %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Обновление системы %s" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "Обновление сенсорной панели %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Обновление устройства %s" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s версия" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u день" msgstr[1] "%u дня" msgstr[2] "%u дней" msgstr[3] "%u дней" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u device has a firmware upgrade available." msgstr[1] "%u устройства имеют доступное обновление прошивки." msgstr[2] "%u устройств имеют доступное обновление прошивки." msgstr[3] "%u устройство имеет доступное обновление прошивки." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u устройство с не самой лучшей известной конфигурацией." msgstr[1] "%u устройства с не самой лучшей известной конфигурацией." msgstr[2] "%u устройств с не самой лучшей известной конфигурацией." msgstr[3] "%u устройств с не самой лучшей известной конфигурацией." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u час" msgstr[1] "%u часа" msgstr[2] "%u часов" msgstr[3] "%u часов" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u минута" msgstr[1] "%u минуты" msgstr[2] "%u минут" msgstr[3] "%u минут" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u секунда" msgstr[1] "%u секунды" msgstr[2] "%u секунд" msgstr[3] "%u секунд" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (пороговое значение - %u%%)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "PCR TPM теперь имеет недействительное значение" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Необходимое действие:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Активировать устройства" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Задействовать ожидающие устройства" msgid "Activate the new firmware on the device" msgstr "Активировать новую прошивку на устройстве" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Активация обновления прошивки" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Активация обновления прошивки для" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Возраст" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Согласиться и активировать репозиторий?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Псевдоним %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Разрешить понижение версий прошивок" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Разрешить переустановку существующих версий прошивки" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Разрешить переключение веток прошивки" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Альтернативная ветка" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Завершение обновления требует перезагрузки." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Завершение обновления требует выключения системы." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Ответить да на все вопросы" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Применить обновления прошивки" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Применить обновление, даже если это не является рекомендованным" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Применить файлы обновления" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Применение обновления…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Одобренная прошивка:" msgstr[1] "Одобренная прошивка:" msgstr[2] "Одобренная прошивка:" msgstr[3] "Одобренная прошивка:" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Подключить в режим прошивки" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Аутентификация…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Для понижения версии прошивки на съёмном устройстве требуется аутентификация" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Для понижения версии прошивки на этой машине требуется аутентификация" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Для изменения настроек BIOS требуется аутентификация" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Для модификации настроенного репозитория, используемого для обновления прошивки, требуется аутентификация" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Для изменения настроек фоновой службы требуется аутентификация" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Для установки списка одобренных прошивок требуется аутентификация" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Для подписи данных с использованием сертификата клиента требуется аутентификация" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Для переключения на новую версию прошивки требуется аутентификация" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Для разблокировки устройства требуется аутентификация" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Для обновления прошивки на съёмном устройстве требуется аутентификация" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Для обновления прошивки на этой машине требуется аутентификация" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Для обновления хранимых контрольных сумм устройства требуется аутентификация" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "Обновления BIOS доставляются через LVFS или Центр обновления Windows." msgid "BYTES" msgstr "БАЙТЫ" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Аккумулятор" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Привязать новый драйвер ядра" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Блокированные файлы прошивки:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Заблокированная версия" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Блокированная прошивка:" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Версия загрузчика" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Ветка" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Собрать файл прошивки" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Отменить" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Отменено" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Не удалось применить, поскольку обновление dbx уже применено." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Не удалось применить обновления с портативного носителя" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Изменено" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Контрольная сумма" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Очистить результаты c последнего обновления" #. TRANSLATORS: error message msgid "Command not found" msgstr "Команда не найдена" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Поддерживается сообществом" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Преобразовать файл прошивки" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Создано" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Критичный" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Текущая версия" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Средство работы с DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Параметры отладки" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Распаковка…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Описание" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Отключить в режим загрузчика" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Подробнее" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Флаги устройства" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Идентификатор устройства" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Добавлено устройство:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Заменено устройство:" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Устройство заблокировано" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Устройство недоступно" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Удалено устройство:" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "Устройство поддерживает переключение на другую ветку прошивки" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Способ обновления устройства" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Устройства, которые были успешно обновлены:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Устройства, которые не были правильно обновлены:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Устройства без доступных обновлений прошивки:" #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Устройства с последней доступной версией прошивки:" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Не активировано" msgid "Disabled fwupdate debugging" msgstr "Отладка fwupdate деактивирована" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Деактивировать данный репозиторий" #. TRANSLATORS: command line option msgid "Display version" msgstr "Показать версию" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Не проверять старые метаданные" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Не проверять незарегистрированную историю" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Не проверять, включены ли репозитории" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Не включать префикс домена журнала" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Не включать префикс метки времени" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Не сохранять в базу данных истории" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Понимаете ли вы последствия изменения ветки прошивки?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Хотите отключить эту функцию для будущих обновлений?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Хотите сейчас обновить удалённый репозиторий?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Хотите автоматически загружать отчёты для будущих обновлений?" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Готово!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Понизить версию прошивки на устройстве" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Понижение версии прошивки устройства %s с %s на %s…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Понижение версии прошивки устройства %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Скачать файл" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Получение..." #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Записать данные SMBIOS из файла" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Продолжительность" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Указанный раздел ESP недействителен" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Активировать поддержку обновлений прошивки в поддерживаемых системах." #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Включить новый удалённый репозиторий?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Активировать этот репозиторий?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Активировано" msgid "Enabled fwupdate debugging" msgstr "Отладка fwupdate активирована" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Активировать данный репозиторий" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Активирование этого функционала осуществляется на ваш страх и риск. Это означает, что вам следует обратиться к производителю оборудования относительно любых проблем, вызванных этими обновлениями. Только проблемы с фактическим процессом обновления следует сообщать в $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Активация этого репозитория осуществляется на свой страх и риск." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Зашифровано" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "Шифрование RAM" #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Окончание поддержки" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Стереть всю историю обновлений прошивки" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Стирание…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Выйти после небольшой задержки" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Выйти после загрузки движка" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Экспорт структуры файла прошивки в XML" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "ФАЙЛ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "ИМЯ_ФАЙЛА" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Не удалось" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Не удалось применить обновление" #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Не удалось подключиться к фоновой службе" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Не удалось получить список ожидающих устройств" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Не удалось установить обновление прошивки" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Не удалось загрузить локальную dbx" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Не удалось загрузить странности" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Не удалось загрузить системную dbx" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Не удалось заблокировать" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Не удалось разобрать аргументы" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Не удалось проанализировать файл" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Не удалось разобрать флаги для «--filter»" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Не удалось обработать локальную dbx" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Не удалось перезагрузить" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Не удалось установить режим заставки" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Не удалось проверить содержимое ESP" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Имя файла" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Подпись имени файла" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Имя файла источника" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Требуется имя файла" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Базовый URI прошивки" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "D-Bus служба обновления прошивки" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Фоновая служба обновления прошивки" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Средство работы с прошивками" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Прошивка уже заблокирована" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Прошивка еще не заблокирована" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Метаданные прошивки не обновлялись в течение %u дня и могут быть устаревшими." msgstr[1] "Метаданные прошивки не обновлялись в течение %u дней и могут быть устаревшими." msgstr[2] "Метаданные прошивки не обновлялись в течение %u дней и могут быть устаревшими." msgstr[3] "Метаданные прошивки не обновлялись в течение %u дней и могут быть устаревшими." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Обновления прошивки" msgid "Firmware updates are not supported on this machine." msgstr "Обновления прошивки не поддерживаются на этой машине." msgid "Firmware updates are supported on this machine." msgstr "Обновления прошивки поддерживаются на этой машине." #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Флаги" msgid "Force the action ignoring all warnings" msgstr "Выполнить действие, игнорируя все предупреждения" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Найдено" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "Ключи шифрования всего диска могут утратить силу при обновлении" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID" msgstr[2] "GUID" msgstr[3] "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Получить все флаги устройств, поддерживаемые fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Получить все устройства, которые поддерживают обновления прошивки" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Получить все активированные плагины, зарегистрированные в системе" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Получить сведения о файле прошивки" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Получить настроенные репозитории" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Получить список обновлений для подключенного оборудования" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Получить релизы для устройства" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Получить результаты с последнего обновления" #. TRANSLATORS: the release urgency msgid "High" msgstr "Высокий" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Бездействие…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Игнорировать строгие проверки SSL при загрузке файлов" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Продолжительность установки" msgid "Install old version of signed system firmware" msgstr "Установить старую версию подписанной системной прошивки" msgid "Install old version of unsigned system firmware" msgstr "Установить старую версию неподписанной системной прошивки" msgid "Install signed device firmware" msgstr "Установить подписанную прошивку устройства" msgid "Install signed system firmware" msgstr "Установить подписанную системную прошивку" msgid "Install unsigned device firmware" msgstr "Установить неподписанную прошивку устройства" msgid "Install unsigned system firmware" msgstr "Установить неподписанную системную прошивку" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Установка прошивки…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Установка обновления прошивки…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Установка на устройство %s…" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Intel BootGuard защищено ACM" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Политика ошибок Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Проверенная загрузка Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET активна" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET включена" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Внутреннее устройство" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Недопустимые аргументы" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Более старая" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Обновление" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Проблема" msgstr[1] "Проблемы" msgstr[2] "Проблем" msgstr[3] "Проблема" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Хранилище ключей" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "РАСПОЛОЖЕНИЕ" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Последнее изменение" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Осталось меньше минуты" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Лицензия" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (стабильная прошивка)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (тестовая прошивка)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Ядро линукс" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Ядро линукс с функцией lockdown" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Список записей в dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Вывести список поддерживаемых обновлений прошивки" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Вывести список файлов на ESP" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Загрузка…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Заблокировано" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Низкий" msgid "MEI version" msgstr "Версия MEI" #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Максимальная длина" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Максимальное значение" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Средний" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI метаданных" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Метаданные можно получить в Linux Vendor Firmware Service." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Минимальная версия" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Минимальная длина" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Минимальное значение" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Несовместимые фоновая служба и клиент, используйте %s" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Модифицировать данный репозиторий" msgid "Modify a configured remote" msgstr "Изменить настроенный репозиторий" msgid "Modify daemon configuration" msgstr "Изменить настройки фоновой службы" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Следить за событиями в фоновой службе" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Монтировать ESP" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Требуется перезагрузка" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Новая версия" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Не определено никаких действий." #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Идентификаторы прошивок не найдены" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Не обнаружено оборудования с возможностью обновления прошивки" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Плагины не найдены" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Нет доступных выпусков" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "В настоящее время урепозитории не активированы, поэтому метаданные недоступны." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Нет доступных репозиториев" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Нет пригодных для обновления устройств" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Нет доступных обновлений" msgid "No updates available for remaining devices" msgstr "Нет доступных обновлений для остальных устройств" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Обновления не были применены" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Не одобренная" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Не найдено" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Не поддерживается" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "ОК" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "OK!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Старая версия" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Показывать только одно значение PCR" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Вывод в формате JSON" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Переопределить путь ESP по умолчанию" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "ПУТЬ" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Разобрать и показать детали о файле прошивки" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Обработка обновления dbx…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Обработка системной dbx…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Пароль" msgid "Payload" msgstr "Полезная нагрузка" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "В очереди" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Процент завершения" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Выполнить операцию?" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Прежде чем продолжить, убедитесь, что у вас есть ключ восстановления тома." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Пожалуйста, введите число от 0 до %u:" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Возможные значения" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Предзагрузочная защита DMA" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Предыдущая версия" msgid "Print the version number" msgstr "Напечатать номер версии" msgid "Print verbose debug statements" msgstr "Напечатать подробные отладочные отчёты" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Приоритет" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Проблемы" msgid "Proceed with upload?" msgstr "Продолжить загрузку?" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Проприетарная" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Запросить поддержку обновления прошивки" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Только чтение" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Считать прошивку из устройства в файл" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Считать прошивку из одного раздела в файл" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Чтение из %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Чтение…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Перезагрузка…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Обновить метаданные с удаленного сервера" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Переустановить прошивку на устройстве" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Переустановка %s с %s…" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Ветка выпуска" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Флаги выпуска" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "ID выпуска" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Идентификатор репозитория" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Заменить данные в существующем файле прошивки" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI для отчета" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Запрос отменен" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Требуется подключение к сети Интернет" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Перезагрузить сейчас?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Перезапустить фоновую службу, чтобы изменения вступили в силу?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Перезапуск устройства…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Показать идентификаторы всех устройств на машине" #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Запустите «fwupdmgr get-upgrades» для получения дополнительной информации." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Запустить процедуру очистки составного плагина, используя двоичную установку" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Запустить процедуру подготовки составного плагина, используя двоичную установку" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "Дескриптор SPI BIOS" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "Регион SPI BIOS" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "Блокировка SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "Запись SPI" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "Скалярное приращение" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Запланировать установку на следующую перезагрузку, если это возможно" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Планировка…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Secure Boot отключен" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Secure Boot включен" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Подробнее смотрите %s." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Подробнее смотрите %s." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Выбранное устройство" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Выбранный том" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Серийный номер" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Установить флаг отладки во время обновления" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Установить список одобренных прошивок" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Настройки вступят в силу после перезагрузки системы" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Поделиться историей прошивки с разработчиками" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Показать все результаты" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Показать версии клиента и фоновой службы" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Показать подробную информацию о фоновой службе для определенного домена" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Показать отладочную информацию для всех доменов" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Показать параметры отладки" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Показать устройства, обновление для которых невозможно" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Показать дополнительную отладочную информацию" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Показать историю обновлений прошивки" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Показать рассчитанную версию dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Показать журнал отладки с последней попытки обновления" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Показать информацию о состоянии обновления прошивки" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Выключить сейчас?" msgid "Sign data using the client certificate" msgstr "Подписать данные с использованием сертификата клиента" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Подписать данные с использованием сертификата клиента" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Подписать загруженные данные с использованием сертификата клиента" msgid "Signature" msgstr "Подпись" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Подписанное содержимое" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Размер" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "При обновлении этой прошивки некоторые ключи платформы могут утратить силу." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Источник" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Определить идентификатор(ы) поставщика / продукта устройства DFU" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Указать файл базы данных dbx" msgid "Specify the number of bytes per USB transfer" msgstr "Определить количество байтов на передачу USB" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Успешно" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Все устройства успешно активированы" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Удалённый репозиторий успешно отключен" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Успешно загружены новые метаданные:" #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Удалённый репозиторий успешно подключен и обновлён" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Удалённый репозиторий успешно подключен" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Прошивка успешно установлена" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Успешно изменено значение в настройках" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Успешно изменено удалённое хранилище" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Метаданные успешно обновлены вручную" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Контрольные суммы устройства успешно обновлены" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Успешно загружен %u отчёт" msgstr[1] "Успешно загружено %u отчёта" msgstr[2] "Успешно загружено %u отчётов" msgstr[3] "Успешно загружено %u отчётов" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Контрольные суммы устройства успешно проверены" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Сводка" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Поддерживается" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "Поддерживаемый CPU" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Переключиться с ветки %s на %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Переключить ветку прошивки на устройстве" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "Реконстуркция PCR0 TPM" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "Реконструкция PCR0 TPM недействительна" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "Пустые PCR TPM" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Метка" msgstr[1] "Метки" msgstr[2] "Меток" msgstr[3] "Метки" msgid "Target" msgstr "Цель" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS — это бесплатный сервис, действующий как независимое юридическое лицо, которое не имеет связи с системой $OS_RELEASE:NAME$. Ваш распространитель системы может не проверять какие-либо обновления прошивки на совместимость с системой или подключенными устройствами. Каждая прошивка поставляется только производителем оригинального оборудования." #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Нет заблокированных файлов прошивки" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Одобренной прошивки нет." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Эта программа может корректно работать только как root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Этот репозиторий содержит прошивки, которое не запрещены, но все еще тестируются производителем оборудования. Вы должны убедиться, что у вас есть способ вручную понизить версию прошивки устройства в случае сбоя при обновлении." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Этот инструмент позволяет администратору применять обновления UEFI dbx." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Этот инструмент позволяет администратору выполнять диагностику операции UpdateCapsule." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Этот инструмент может использовать только пользователь root" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Этот инструмент читает и обрабатывает журнал событий TPM, который заполняется системными прошивками." #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Надёжные метаданные" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Тип" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Средство для прошивки UEFI" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "Утилита для UEFI dbx" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "Ключ платформы UEFI" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "Не удалось найти атрибут" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Отвязать тукущий драйвер" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Разблокированная прошивка:" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Не зашифровано" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Неизвестно" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Неизвестное устройство" msgid "Unlock the device to allow access" msgstr "Разблокировать устройство для получения доступа" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Разблокировано" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Разблокировать устройство для доступа к прошивке" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Размонтировать ESP" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Снять флаг отладки во время обновления" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Неподписанное содержимое" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Неподдерживаемая фоновая служба версии %s, клиент версии %s" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Ошибка обновления" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Изображение обновления" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Сообщение об обновлении" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Обновить статус" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Ошибка обновления — известная проблема, посетите этот URL для получения дополнительной информации:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Обновить сейчас?" msgid "Update the stored device verification information" msgstr "Обновить хранимую проверочную информацию устройства" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Обновить сохраненные метаданные с текущим содержимым" msgid "Updating" msgstr "Обновление" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Обновление %s с %s на %s…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Обновление устройства %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Обновить %s с %s до %s?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Загрузка отчетов о прошивке помогает поставщикам оборудования быстро определять неудачные и успешные обновления на реальных устройствах." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Используйте «fwupdtool --help» для получения справки" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Использовать вариативные флажки при установке прошивки" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Имя пользователя" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Проверка содержимого ESP…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Вариант" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Производитель" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Проверка…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Версия" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Версия[fwupd]" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Ожидание…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Следить за аппаратными изменениями" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Записать прошивку из файла на устройство" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Записать прошивку из файла на один раздел" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Запись файла:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Запись…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Ваш распространитель системы может не проверять какие-либо обновления прошивки на совместимость с системой или подключенными устройствами." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[ID-УСТРОЙСТВА|GUID] [ВЕТКА]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "по умолчанию" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "Утилита для работы с записями журнала событий fwupd TPM" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "плагины fwupd" fwupd-1.9.16/po/si.po000066400000000000000000003363561460375044200143530ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # HelaBasa Group , 2021-2022 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Sinhala (http://app.transifex.com/freedesktop/fwupd/language/si/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: si\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "විනාඩි %.0f ක් ඉතිරියි" msgstr[1] "විනාඩි %.0f ක් ඉතිරියි" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "%s BMC යාවත්කාලීන කිරීම" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "%s බැටරි යාවත්කාලීන කිරීම" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "%s CPU මයික්‍රොකෝඩ් යාවත්කාලීන කිරීම" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "%s කැමරා යාවත්කාලීන කිරීම" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "%s වින්‍යාස යාවත්කාලීන කිරීම" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s පාරිභෝගික ME යාවත්කාලීන කිරීම" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s පාලක යාවත්කාලීන කිරීම" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s ආයතනික ME යාවත්කාලීන කිරීම" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s උපාංග යාවත්කාලීන කිරීම" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "%s සංදර්ශක යාවත්කාලීන කිරීම" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s Embedded Controller Update" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "%s යතුරුපුවරු යාවත්කාලීන කිරීම" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s ME යාවත්කාලීන කිරීම" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "%s මූසික යාවත්කාලීන කිරීම" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "%s ජාල අතුරුමුහුණත යාවත්කාලීන කිරීම" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "%s ගබඩා පාලක යාවත්කාලීන කිරීම" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s පද්ධති යාවත්කාලීන කිරීම" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "%s TPM යාවත්කාලීන" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s Thunderbolt Controller යාවත්කාලීන කිරීම" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "%s Touchpad යාවත්කාලීන කිරීම" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "%s USB ග්‍රාහකයා යාවත්කාලීන කිරීම" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s යාවත්කාලීන කරන්න" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s සහ සියලුම සම්බන්ධිත උපාංග යාවත්කාලීන කිරීමේදී භාවිත කළ නොහැක." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "%s පෙනී සිටියේය: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "%s වෙනස් විය: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "%s අතුරුදහන් විය: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s දැනට යාවත්කාලීන කළ නොහැක" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s නිෂ්පාදන මාදිලිය" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "හානිය වළක්වා ගැනීම සඳහා යාවත්කාලීන කාලය සඳහා %s සම්බන්ධව තිබිය යුතුය." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "හානිය වළක්වා ගැනීම සඳහා යාවත්කාලීන කාලසීමාව සඳහා %s බල ප්‍රභවයකට සම්බන්ධ කළ යුතුය." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s අභිබවා යයි" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "අනුවාදය %s" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "දින %u යි" msgstr[1] "දින %u යි" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u උපාංගයේ ස්ථිරාංග උත්ශ්‍රේණිගත කිරීමක් ඇත." msgstr[1] "%u උපාංග සඳහා ස්ථිරාංග උත්ශ්‍රේණිගත කිරීමක් ඇත." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u උපාංගය හොඳම දන්නා වින්‍යාසය නොවේ." msgstr[1] "%u උපාංග හොඳම දන්නා වින්‍යාසය නොවේ." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "පැය %u යි" msgstr[1] "පැය %u යි" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u දේශීය උපාංග සඳහා සහය දක්වයි" msgstr[1] "%u දේශීය උපාංග සඳහා සහය දක්වයි" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "විනාඩි %u" msgstr[1] "විනාඩි %u" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "තත්පර %u" msgstr[1] "තත්පර %u" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (ඉන්පසුව %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(යල් පැන ගිය)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "TPM PCR දැන් වලංගු නොවන අගයකි" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "අවශ්‍ය ක්‍රියාමාර්ග:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "උපාංග සක්රිය කරන්න" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "පොරොත්තු උපාංග සක්‍රිය කරන්න" msgid "Activate the new firmware on the device" msgstr "උපාංගයේ නව ස්ථිරාංග සක්රිය කරන්න" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "ස්ථිරාංග යාවත්කාලීන කිරීම සක්රිය කිරීම" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "සඳහා ස්ථිරාංග යාවත්කාලීන කිරීම සක්රිය කිරීම" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "වයස" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "එකඟ වී දුරස්ථ පාලකය සබල කරන්නද?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "%s ට අපනාමය" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "සියලුම TPM PCRs දැන් වලංගු වේ" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "සියලුම TPM PCR වලංගු වේ" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "එකම වර්ගයේ සියලුම උපාංග එකම අවස්ථාවේදීම යාවත්කාලීන වේ" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "ස්ථිරාංග අනුවාද පහත හෙලීමට ඉඩ දෙන්න" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "පවතින ස්ථිරාංග අනුවාද නැවත ස්ථාපනය කිරීමට ඉඩ දෙන්න" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "ස්ථිරාංග ශාඛාව මාරු කිරීමට ඉඩ දෙන්න" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "විකල්ප ශාඛාව" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "යාවත්කාලීනයක් සම්පූර්ණ කිරීමට නැවත පණගැන්වීමක් අවශ්‍ය වේ." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "යාවත්කාලීනයක් සම්පූර්ණ කිරීමට පද්ධතිය වසා දැමීම අවශ්‍ය වේ." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "සියලුම ප්‍රශ්න වලට ඔව් පිළිතුරු දෙන්න" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "ස්ථිරාංගයේ යාවත්කාල යොදන්න" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "උපදෙස් නොදෙන විට පවා යාවත්කාලීන කරන්න" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "යාවත්කාල ගොනු යොදන්න" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "යාවත්කාලය යොදමින්…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "අනුමත ස්ථිරාංග:" msgstr[1] "අනුමත ස්ථිරාංග:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "ඊළඟ වතාවේ නැවත අසන්න?" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "ස්ථිරාංග මාදිලියට අමුණන්න" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "…සත්‍යාපනය කිරීම" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "සත්‍යාපන විස්තර අවශ්‍යයි" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "ඉවත් කළ හැකි උපාංගයක ස්ථිරාංග පහත හෙලීමට සත්‍යාපනය අවශ්‍ය වේ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "මෙම යන්ත්‍රයේ ස්ථිරාංග පහත හෙලීමට සත්‍යාපනය අවශ්‍ය වේ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "ස්ථිරාංග යාවත්කාලීන කිරීම් සඳහා භාවිතා කරන වින්‍යාස කළ දුරස්ථ පාලකයක් වෙනස් කිරීමට සත්‍යාපනය අවශ්‍ය වේ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "ඩීමන් වින්‍යාසය වෙනස් කිරීමට සත්‍යාපනය අවශ්‍ය වේ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "අනුමත ස්ථිරාංග ලැයිස්තුව සැකසීමට සත්‍යාපනය අවශ්‍ය වේ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "සේවාදායක සහතිකය භාවිතයෙන් දත්ත අත්සන් කිරීමට සත්‍යාපනය අවශ්‍ය වේ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "නව ස්ථිරාංග අනුවාදය වෙත මාරු වීමට සත්‍යාපනය අවශ්‍ය වේ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "උපාංගයක් අගුලු හැරීමට සත්‍යාපනය අවශ්‍ය වේ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "ඉවත් කළ හැකි උපාංගයක ස්ථිරාංග යාවත්කාලීන කිරීමට සත්‍යාපනය අවශ්‍ය වේ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "මෙම යන්ත්‍රයේ ස්ථිරාංග යාවත්කාලීන කිරීමට සත්‍යාපනය අවශ්‍ය වේ" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "උපාංගය සඳහා ගබඩා කර ඇති චෙක්සම් යාවත්කාලීන කිරීමට සත්‍යාපනය අවශ්‍ය වේ" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "ස්වයංක්‍රීය වාර්තාකරණය" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "සෑම විටම ස්වයංක්‍රීයව උඩුගත කරන්නද?" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BUILDER-XML ගොනු නාමය-DST" msgid "BYTES" msgstr "බයිට" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "බැටරි" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "නව කර්නල් ධාවකය බැඳන්න" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "අවහිර කළ ස්ථිරාංග ගොනු:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "අවහිර කළ අනුවාදය" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "ස්ථිරාංග අවහිර කිරීම:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "නිශ්චිත ස්ථිරාංග ස්ථාපනය කිරීම අවහිර කරයි" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Bootloader අනුවාදය" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "ශාඛාව" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "ස්ථිරාංග ගොනුවක් සාදන්න" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "අවලංගු කරන්න" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "අවලංගු කෙරිණි" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "dbx යාවත්කාලීනය දැනටමත් යොදවා ඇති බැවින් අයදුම් කළ නොහැක." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "සජීවී මාධ්‍යවල යාවත්කාලීන යෙදිය නොහැක" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "වෙනස් විය" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "ගුප්ත ලේඛන හැෂ් ගැලපීම් ස්ථිරාංග පරීක්ෂා කරයි" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "චෙක්සම්" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "අවසාන යාවත්කාලීනයෙන් ප්‍රතිඵල හිස් කරයි" #. TRANSLATORS: error message msgid "Command not found" msgstr "විධානය සොයාගත නොහැකි" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "ප්‍රජාවගේ සහාය" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "ස්ථිරාංග ගොනුවක් පරිවර්තනය කරන්න" #. TRANSLATORS: when the update was built msgid "Created" msgstr "නිර්මාණය කළා" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "විවේචනාත්මක" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "ගුප්ත ලේඛන හැෂ් සත්‍යාපනය ලබා ගත හැකිය" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "වත්මන් අනුවාදය" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "DEVICE-ID|මාර්ගෝපදේශය" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU උපයෝගිතා" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "දෝශ නිරාකරණ විකල්ප" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "විසංයෝජනය…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "විස්තර" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "ඇරඹුම් කාරක මාදිලියට වෙන් කරන්න" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "විස්තර" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "හොඳින්ම දන්නා වින්‍යාසයෙන් බැහැර වන්නද?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "උපාංග කොඩි" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "උපාංග හැඳුනුම්පත" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "උපාංගය එකතු කළේ:" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "උපාංගයේ බැටරි බලය ඉතා අඩුය" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "උපාංග බැටරි බලය ඉතා අඩුයි (%u%%, %u%% අවශ්‍යයි)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "උපාංගයට ෆ්ලෑෂ් අසමත්වීම් නැවත ලබා ගත හැක" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "පියන වසා ඇති විට උපාංගය භාවිතා කළ නොහැක" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "උපාංගය වෙනස් විය:" #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "අනුවාද පරීක්ෂාවක් කිරීමට උපාංග ස්ථිරාංග අවශ්‍ය වේ" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "උපාංගය අගුලු දමා ඇත" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "සපයා ඇති සියලුම නිකුතු ස්ථාපනය කිරීමට උපාංගය අවශ්‍ය වේ" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "උපාංගය ළඟා විය නොහැක" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "උපාංගය වෙත ළඟා විය නොහැක, නැතහොත් රැහැන් රහිත පරාසයෙන් පිටත" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "යාවත්කාලීන කාලය සඳහා උපාංගය භාවිතා කළ හැකිය" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "උපාංගය යාවත්කාලීනය යෙදෙන තෙක් බලා සිටී" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "උපාංගය ඉවත් කරන ලදී:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "උපාංගයට සම්බන්ධ වීමට AC බලය අවශ්‍යයි" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "උපාංග අදියර යාවත්කාලීන කිරීම්" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "ස්ථිරාංගයේ වෙනත් ශාඛාවකට මාරු වීමට උපාංගය සහාය දක්වයි" #. TRANSLATORS: command line option msgid "Device update method" msgstr "උපාංග යාවත්කාලීන ක්‍රමය" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "උපාංග යාවත්කාලීන කිරීම සක්‍රිය කිරීම අවශ්‍යයි" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "උපාංගය ස්ථාපනය කිරීමට පෙර ස්ථිරාංග උපස්ථ කරයි" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "යාවත්කාලීනය සම්පූර්ණ වූ පසු උපාංගය නැවත දිස් නොවනු ඇත" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "සාර්ථකව යාවත්කාලීන කරන ලද උපාංග:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "නිවැරදිව යාවත්කාලීන නොකළ උපාංග:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "පවතින ස්ථිරාංග යාවත්කාලීන නොමැති උපාංග: " #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "පවතින නවතම ස්ථිරාංග අනුවාදය සහිත උපාංග:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "ගැළපෙන GUID සහිත උපාංග කිසිවක් සොයා ගත්තේ නැත" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "අබල කර ඇත" msgid "Disabled fwupdate debugging" msgstr "fwupdate නිදොස්කරණය අක්‍රීය කර ඇත" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "ලබා දී ඇති දුරස්ථ පාලකයක් අබල කරයි" #. TRANSLATORS: command line option msgid "Display version" msgstr "සංදර්ශක අනුවාදය" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "පැරණි පාරදත්ත සඳහා පරීක්ෂා නොකරන්න" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "වාර්තා නොකළ ඉතිහාසය පරීක්ෂා නොකරන්න" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "බාගැනීම් දුරස්ථ සක්‍රීය කළ යුතුදැයි පරීක්ෂා නොකරන්න" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "යාවත්කාලීන කිරීමෙන් පසු නැවත පණගැන්වීම සඳහා පරීක්ෂා නොකරන්න" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "ලොග් වසම් උපසර්ගය ඇතුළත් නොකරන්න" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "වේලා මුද්දර උපසර්ගය ඇතුළත් නොකරන්න" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "උපාංග ආරක්ෂණ පරීක්ෂාවන් සිදු නොකරන්න" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "උපාංග සඳහා විමසන්න එපා" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "ඉතිහාස දත්ත ගබඩාවට ලියන්න එපා" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "ස්ථිරාංග ශාඛාව වෙනස් කිරීමේ ප්රතිවිපාක ඔබට තේරෙනවාද?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "ඔබට අනාගත යාවත්කාලීන සඳහා මෙම විශේෂාංගය අබල කිරීමට අවශ්‍යද?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "ඔබට දැන් මෙම දුරස්ථ පාලකය නැවුම් කිරීමට අවශ්‍යද?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "ඔබට අනාගත යාවත්කාලීන සඳහා වාර්තා ස්වයංක්‍රීයව උඩුගත කිරීමට අවශ්‍යද?" #. TRANSLATORS: success #. success msgid "Done!" msgstr "අහවරයි!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "%s %s සිට %sදක්වා පහත් කරන්නද?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "උපාංගයක ස්ථිරාංග පහත හෙළයි" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "%s %s සිට %sදක්වා පහත හෙලීම ... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "පහත හෙලීම %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "ගොනුවක් බාගන්න" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "බාගත වෙමින්…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "ගොනුවකින් SMBIOS දත්ත ඩම්ප් කරන්න" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "කාල සීමාව" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "නිශ්චිතව දක්වා ඇති ESP වලංගු නැත" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "සහාය දක්වන පද්ධති මත ස්ථිරාංග යාවත්කාලීන සහාය සබල කරන්න" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "නව දුරස්ථ පාලකය සබල කරන්නද?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "මෙම දුරස්ථ පාලකය සබල කරන්නද?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "සබල කර ඇත" msgid "Enabled fwupdate debugging" msgstr "fwupdate නිදොස්කරණය සබල කර ඇත" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "දෘඪාංග ගැලපෙන්නේ නම් සබල කර ඇත" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "දී ඇති දුරස්ථ පාලකයක් සබල කරයි" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "මෙම ක්‍රියාකාරීත්වය සක්‍රීය කිරීම ඔබගේම අවදානමකින් සිදු කෙරේ, එයින් අදහස් වන්නේ මෙම යාවත්කාලීනයන් නිසා ඇති වන ගැටළු සම්බන්ධයෙන් ඔබ ඔබේ මුල් උපකරණ නිෂ්පාදකයා හා සම්බන්ධ විය යුතු බවයි. $OS_RELEASE:BUG_REPORT_URL$ හි ගොනු කළ යුත්තේ යාවත්කාලීන ක්‍රියාවලියේ ඇති ගැටලු පමණි." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "මෙම දුරස්ථ පාලකය සබල කිරීම ඔබගේම අවදානමකින් සිදු කෙරේ." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "සංකේතිතයි" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "සංකේතනය කළ RAM" #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "ජීවිතයේ අවසානය" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "සියලුම ස්ථිරාංග යාවත්කාලීන ඉතිහාසය මකන්න" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "මැකෙමින්…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "කුඩා ප්‍රමාදයකින් පසු පිටවන්න" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "එන්ජිම පූරණය වූ පසු පිටවන්න" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "ස්ථිරාංග ගොනු ව්‍යුහයක් XML වෙත අපනයනය කරන්න" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "පින්තූර වෙත ස්ථිරාංග බ්ලොබ් උපුටා ගන්න" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "ගොනුව" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "ගොනුව [උපාංගය-ID|මාර්ගෝපදේශය]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "ගොනුවේ නම" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "ගොනු නාම සහතිකය පුද්ගලික යතුර" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "FILENAME උපාංගය-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "FILENAME OFFSET දත්ත [FIRMWARE-TYPE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "ගොනු නාමය [DEVICE-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "FILENAME [FIRMWARE-TYPE]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "අසමත් විය" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "යාවත්කාලය යෙදීමට අසමත් විය" #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "ඩීමන් වෙත සම්බන්ධ වීමට අසමත් විය" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "පොරොත්තු උපාංග ලබා ගැනීමට අසමත් විය" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "ස්ථිරාංග යාවත්කාලීන ස්ථාපනය කිරීමට අසමත් විය" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "දේශීය dbx පූරණය කිරීමට අසමත් විය" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "විචක්ෂණ පූරණය කිරීමට අසමත් විය" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "පද්ධතිය dbx පූරණය කිරීමට අසමත් විය" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "අගුළු ලෑමට අසමත් විය" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "තර්ක විග්‍රහ කිරීමට අසමත් විය" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "ගොනුව විග්‍රහ කිරීමට අසමත් විය" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "--ෆිල්ටර් සඳහා කොඩි විග්‍රහ කිරීමට අසමත් විය" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "දේශීය dbx විග්‍රහ කිරීමට අසමත් විය" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "යළි ඇරඹීමට අසමත් විය" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "විදින මාදිලිය සැකසීමට අසමත් විය" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "ESP අන්තර්ගතයන් වලංගු කිරීමට අසමත් විය" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "ගොනුවේ නම" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "ගොනු නාමය අත්සන" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "ගොනු නාමය මූලාශ්රය" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "ගොනුවේ නම අවශ්‍යයි" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "බැහැර කිරීමට ~ උපසර්ගයක් භාවිතා කරමින් උපාංග ධජ කට්ටලයක් සමඟ පෙරහන් කරන්න, උදා 'අභ්‍යන්තර,~needs-reboot'" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "ස්ථිරාංග පදනම URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "ස්ථිරාංග යාවත්කාලීන D-බස් සේවාව" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "ස්ථිරාංග යාවත්කාලීන Daemon" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "ස්ථිරාංග උපයෝගිතා" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "ස්ථිරාංග සහතික කිරීම" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "ස්ථිරාංග දැනටමත් අවහිර කර ඇත" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "ස්ථිරාංග දැනටමත් අවහිර කර නැත" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "ස්ථිරාංග පාරදත්ත දින %u ක් සඳහා යාවත්කාලීන කර නොමැති අතර යාවත්කාලීන නොවිය හැක." msgstr[1] "ස්ථිරාංග පාරදත්ත දින %u ක් සඳහා යාවත්කාලීන කර නොමැති අතර යාවත්කාලීන නොවිය හැක." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "ස්ථිරාංග යාවත්කාල" msgid "Firmware updates are not supported on this machine." msgstr "මෙම යන්ත්‍රයේ ස්ථිරාංග යාවත්කාලීන කිරීම් සඳහා සහය නොදක්වයි." msgid "Firmware updates are supported on this machine." msgstr "මෙම යන්ත්‍රයේ ස්ථිරාංග යාවත්කාලීන කිරීම් සඳහා සහය දක්වයි." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "ස්ථිරාංග යාවත්කාලීන කිරීම් අක්‍රීය කර ඇත; සබල කිරීමට 'fwupdmgr unlock' ධාවනය කරන්න" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "කොඩි" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "සමහර ධාවන කාල චෙක්පත් ලිහිල් කිරීමෙන් ක්‍රියාව බල කරන්න" msgid "Force the action ignoring all warnings" msgstr "සියලුම අනතුරු ඇඟවීම් නොසලකා හරිමින් ක්‍රියාව බල කරන්න" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "හමු විය" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "සම්පූර්ණ තැටි සංකේතනය අනාවරණය විය" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "යාවත්කාලීන කිරීමේදී සම්පූර්ණ තැටි සංකේතාංකන රහස් අවලංගු විය හැක" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "විලයන වේදිකාව" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "මාර්ගෝපදේශය" msgstr[1] "GUIDs" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "මාර්ගෝපදේශය" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "මාර්ගෝපදේශය" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "fwupd මගින් සහය දක්වන සියලුම උපාංග කොඩි ලබා ගන්න" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "ස්ථිරාංග යාවත්කාලීන කිරීම් සඳහා සහය දක්වන සියලුම උපාංග ලබා ගන්න" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "සියලුම සක්‍රීය ප්ලගීන පද්ධතිය සමඟ ලියාපදිංචි කර ගන්න" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "ස්ථිරාංග ගොනුවක් පිළිබඳ විස්තර ලබා ගනී" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "වින්‍යාසගත දුරස්ථයන් ලබා ගනී" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "සත්කාරක ආරක්ෂක ගුණාංග ලබා ගනී" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "අනුමත ස්ථිරාංග ලැයිස්තුව ලබා ගනී" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "අවහිර කළ ස්ථිරාංග ලැයිස්තුව ලබා ගනී" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "සම්බන්ධිත දෘඩාංග සඳහා යාවත්කාලීන ලැයිස්තුව ලබා ගනී" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "උපාංගයක් සඳහා නිකුත් කිරීම් ලබා ගනී" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "අවසාන යාවත්කාලීනයෙන් ප්‍රතිඵල ලබා ගනී" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWIDS-FILE" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "දෘඪාංග නැවත පිරවීමට බලා සිටී" #. TRANSLATORS: the release urgency msgid "High" msgstr "ඉහළ" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "සත්කාරක ආරක්ෂක සිදුවීම්" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "සත්කාරක ආරක්ෂක ID:" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "IOMMU උපාංග ආරක්ෂණය අබල කර ඇත" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "IOMMU උපාංග ආරක්ෂණය සබල කර ඇත" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "නිෂ්ක්‍රීය…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "ගොනු බාගත කිරීමේදී SSL දැඩි චෙක්පත් නොසලකා හරින්න" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "ස්ථිරාංග චෙක්සම් අසමත්වීම් නොසලකා හරින්න" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "ස්ථිරාංග දෘඪාංග නොගැලපීම අසාර්ථක වීම නොසලකා හරින්න" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "වලංගු කිරීමේ ආරක්ෂණ පරීක්ෂාවන් නොසලකා හරින්න" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "SSL දැඩි චෙක්පත් නොසලකා හැරීම, අනාගතයේදී මෙය ස්වයංක්‍රීයව සිදු කිරීම සඳහා ඔබේ පරිසරය තුළ DISABLE_SSL_STRICT අපනයනය කරන්න" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "ස්ථාපන කාලය" msgid "Install old version of signed system firmware" msgstr "අත්සන් කළ පද්ධති ස්ථිරාංගයේ පැරණි අනුවාදය ස්ථාපනය කරන්න" msgid "Install old version of unsigned system firmware" msgstr "අත්සන් නොකළ පද්ධති ස්ථිරාංගයේ පැරණි අනුවාදය ස්ථාපනය කරන්න" msgid "Install signed device firmware" msgstr "අත්සන් කරන ලද උපාංග ස්ථිරාංග ස්ථාපනය කරන්න" msgid "Install signed system firmware" msgstr "අත්සන් කළ පද්ධති ස්ථිරාංග ස්ථාපනය කරන්න" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "පළමුව මාපිය උපාංගයට ස්ථාපනය කරන්න" msgid "Install unsigned device firmware" msgstr "අත්සන් නොකළ උපාංග ස්ථිරාංග ස්ථාපනය කරන්න" msgid "Install unsigned system firmware" msgstr "අත්සන් නොකළ පද්ධති ස්ථිරාංග ස්ථාපනය කරන්න" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "ස්ථිරාංගය ස්ථාපනය වෙමින්…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "ස්ථිරාංගයේ අනුවාදය ස්ථාපනය වෙමින්…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "…මත %sකිරීම" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "මෙම යාවත්කාලීනය ස්ථාපනය කිරීමෙන් ඕනෑම උපාංග වගකීමක් ද අවලංගු විය හැක." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Intel BootGuard ACM ආරක්ෂිතයි" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Intel BootGuard OTP ෆියුස්" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Intel BootGuard දෝෂ ප්‍රතිපත්තිය" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Intel BootGuard සත්‍යාපනය කළ ඇරඹුම" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET Active" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET සබල කර ඇත" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "අභ්යන්තර උපාංගය" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "වලංගු නොවේ" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "පහත හෙලනු ලැබේ" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "බූට්ලෝඩර් මාදිලියේ ඇත" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "වැඩිදියුණු වේ" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "නිකුත් කිරීම" msgstr[1] "ගැටලු" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "යතුර, අගය" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "කර්නලය තවදුරටත් අපිරිසිදු නොවේ" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "කර්නලය අපිරිසිදු වේ" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "කර්නලය අගුලු දැමීම අබල කර ඇත" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "කර්නල් අගුලු දැමීම සබල කර ඇත" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "ප්රධාන මුදුව" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "ස්ථානය" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "අවසන් වරට වෙනස් කරන ලදී" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "මිනිත්තුවකට වඩා අඩු කාලයක් ඉතිරිව ඇත" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "බලපත්රය" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (ස්ථාවර ස්ථිරාංග)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (ස්ථිරාංග පරීක්ෂා කිරීම)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "ලිනක්ස් කර්නලය" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "ලිනක්ස් කර්නලය අගුලු දැමීම" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "ලිනක්ස් හුවමාරුව" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "dbx හි ඇතුළත් කිරීම් ලැයිස්තුගත කරන්න" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "සහාය දක්වන ස්ථිරාංග යාවත්කාලීන ලැයිස්තුගත කරන්න" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "පවතින ස්ථිරාංග වර්ග ලැයිස්තුගත කරන්න" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "ESP මත ගොනු ලැයිස්තුගත කරයි" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "පූරණය වෙමින්…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "අගුළු ලා ඇත" #. TRANSLATORS: the release urgency msgid "Low" msgstr "අඩු" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "MEI නිෂ්පාදන මාදිලිය" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "MEI අභිබවා යයි" msgid "MEI version" msgstr "MEI අනුවාදය" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "විශේෂිත ප්ලගීන අතින් සක්‍රීය කරන්න" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "මධ්යම" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "පාරදත්ත අත්සන" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "පාරදත්ත URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "පාරදත්ත Linux Vendor Firmware Service වෙතින් ලබාගත හැක." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "අවම අනුවාදය" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "නොගැලපෙන ඩීමන් සහ සේවාදායකයා, ඒ වෙනුවට %s භාවිතා කරන්න" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "ඩීමන් වින්‍යාස අගයක් වෙනස් කරයි" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "දී ඇති දුරස්ථ පාලකයක් වෙනස් කරයි" msgid "Modify a configured remote" msgstr "වින්‍යාසගත දුරස්ථ පාලකයක් වෙනස් කරන්න" msgid "Modify daemon configuration" msgstr "ඩීමන් වින්‍යාසය වෙනස් කරන්න" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "සිදුවීම් සඳහා ඩීමන් නිරීක්ෂණය කරන්න" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "ESP සවි කරයි" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "ස්ථාපනය කිරීමෙන් පසු නැවත ආරම්භ කිරීම අවශ්ය වේ" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "නැවත පණගැන්වීම අවශ්‍යයි" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "ස්ථාපනය කිරීමෙන් පසු වසා දැමීම අවශ්ය වේ" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "නව අනුවාදය" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "ක්‍රියාවක් සඳහන් කර නැත!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "%sසඳහා පහත් කිරීම් නොමැත" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "ස්ථිරාංග හැඳුනුම් කිසිවක් හමු නොවීය" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "ස්ථිරාංග යාවත්කාලීන කිරීමේ හැකියාව ඇති දෘඪාංග අනාවරණය කර ගෙන නොමැත" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "ප්ලගින කිසිවක් හමු නොවීය" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "නිකුත් කිරීම් නොමැත" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "කිසිදු දුරස්ථ පාලකයක් දැනට සබල කර නොමැති නිසා පාරදත්ත නොමැත." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "දුරස්ථ නැත" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "යාවත්කාලීන කළ හැකි උපාංග නොමැත" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "යාවත්කාලීන නොමැත" msgid "No updates available for remaining devices" msgstr "ඉතිරි උපාංග සඳහා යාවත්කාලීන නොමැත" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "යාවත්කාලීන කිසිවක් යොදන ලදී" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "අනුමත කර නැත" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "හමු නොවිණි" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "සහාය නොදක්වයි" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "හරි" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "හරි!" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "තනි PCR අගය පමණක් පෙන්වන්න" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "අනුවාද උත්ශ්‍රේණි කිරීමට පමණක් අවසර ඇත" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "JSON ආකෘතියෙන් ප්‍රතිදානය" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "පෙරනිමි ESP මාර්ගය අභිබවා යන්න" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "මාර්ගය" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "ස්ථිරාංග ගොනුවක් පිළිබඳ විස්තර විග්‍රහ කර පෙන්වන්න" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "dbx යාවත්කාලීන…විග්‍රහ කිරීම" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "විග්‍රහ කිරීමේ පද්ධතිය dbx…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "මුරපදය" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "දන්නා ඕෆ්සෙට් එකක ස්ථිරාංග බ්ලොබ් එකක් පැච් කරන්න" msgid "Payload" msgstr "ගෙවීම" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "පොරොත්තුවෙන්" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "ප්‍රතිශතය සම්පූර්ණයි" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "මෙහෙයුම සිදු කරන්නද?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "වේදිකා නිදොස්කරණය" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "කරුණාකර දිගටම කරගෙන යාමට පෙර ඔබ සතුව ශබ්ද ප්‍රතිසාධන යතුර ඇති බවට සහතික වන්න." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "කරුණාකර 0 සිට %uදක්වා අංකයක් ඇතුළු කරන්න: " #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "ප්ලගින පරායත්තතා අතුරුදහන්" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "පෙර-ආරම්භක DMA ආරක්ෂාව" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "පෙර-ආරම්භක DMA ආරක්ෂාව අක්‍රීය කර ඇත" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "පෙර-ආරම්භක DMA ආරක්ෂාව සබල කර ඇත" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "පෙර අනුවාදය" msgid "Print the version number" msgstr "අනුවාද අංකය මුද්‍රණය කරන්න" msgid "Print verbose debug statements" msgstr "වාචික දෝශ නිරාකරණ ප්‍රකාශ මුද්‍රණය කරන්න" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "ප්රමුඛත්වය" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "ගැටලු" msgid "Proceed with upload?" msgstr "උඩුගත කිරීම සමඟ ඉදිරියට යන්නද?" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "හිමිකාර" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "ස්ථිරාංග යාවත්කාලීන සහාය සඳහා විමසුම" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "දුරස්ථ-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "දුරස්ථ-ID යතුරු අගය" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "උපාංගයකින් ස්ථිරාංග බ්ලොබ් කියවන්න" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "උපාංගයකින් ස්ථිරාංග කියවන්න" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "උපාංගයෙන් ස්ථිරාංග ගොනුවකට කියවන්න" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "ස්ථිරාංග එක් කොටසකින් ගොනුවකට කියවන්න" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "%s වෙතින් කියැවෙමින්…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "කියවෙමින්…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "නැවත ඇරඹෙමින්…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "දුරස්ථ සේවාදායකයෙන් පාර-දත්ත නැවුම් කරන්න" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "%s සිට %sදක්වා නැවත ස්ථාපනය කරන්නද?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "උපාංගයේ වත්මන් ස්ථිරාංග නැවත ස්ථාපනය කරන්න" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "උපාංගයක ස්ථිරාංග නැවත ස්ථාපනය කරන්න" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "%s සමඟ %s යළි ස්ථාපනය වෙමින්... " #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "නිදහස් ශාඛාව" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "කොඩි නිකුත් කරන්න" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "නිදහස් හැඳුනුම්පත" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "දුරස්ථ හැඳුනුම්පත" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "පවතින ස්ථිරාංග ගොනුවක දත්ත ප්‍රතිස්ථාපනය කරන්න" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI වාර්තා කරන්න" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "දුරස්ථ සේවාදායකය වෙත වාර්තා කරන ලදී" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "අවශ්‍ය efivarfs ගොනු පද්ධතිය සොයාගත නොහැකි විය" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "අවශ්‍ය දෘඩාංග හමු නොවීය" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "ඇරඹුම් කාරකයක් අවශ්‍ය වේ" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "අන්තර්ජාල සම්බන්ධතාව අවශ්‍යයි" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "දැන් නැවත ආරම්භ කරන්නද?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "වෙනස් කිරීම ඵලදායී කිරීමට ඩීමන් නැවත ආරම්භ කරන්නද?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "උපාංගය යළි ඇරඹෙමින්…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "යන්ත්‍රය සඳහා සියලුම දෘඪාංග හැඳුනුම්පත් ආපසු දෙන්න" #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "වැඩි විස්තර සඳහා `fwupdmgr get-upgrades` ධාවනය කරන්න." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Install-blob භාවිතා කරන විට ප්ලගින සංයුක්ත පිරිසිදු කිරීමේ පුරුද්ද ක්‍රියාත්මක කරන්න" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "ස්ථාපනය-බ්ලොබ් භාවිතා කරන විට ප්ලගින සංයුක්ත සූදානම් කිරීමේ දින චර්යාව ධාවනය කරන්න" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "ධාවන කර්නලය පරණ වැඩියි" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "ධාවන කාල උපසර්ගය" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "SPI BIOS විස්තරකය" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "SPI BIOS කලාපය" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI අගුල" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "SPI නැවත ධාවනය ආරක්ෂාව" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI ලියන්න" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "SPI ලිවීමේ ආරක්ෂාව" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "උප පද්ධති ධාවකය [උපාංගය-ID|මාර්ගෝපදේශය]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "දෘඪාංග හැඳුනුම්පත් ජනනය කිරීමට ඉඩ දෙන ගොනුවක් සුරකින්න" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "හැකි විට ඊළඟ නැවත පණගැන්වීම සඳහා ස්ථාපනය කාලසටහන්ගත කරන්න" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "උපලේඛනගත කිරීම…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "ආරක්ෂිත ඇරඹුම් අක්‍රීය කර ඇත" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "ආරක්ෂිත ඇරඹුම් සක්රිය කර ඇත" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "වැඩි විස්තර සඳහා %s බලන්න." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "තව තොරතුරු සඳහා %s බලන්න." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "තෝරාගත් උපාංගය" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "තෝරාගත් පරිමාව" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "අන්රක්රමික අංකය" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "යාවත්කාලීන කිරීමේදී දෝශ නිරාකරණ ධජය සකසන්න" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "අනුමත ස්ථිරාංග ලැයිස්තුව සකසයි" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "ස්ථිරාංග ඉතිහාසය සංවර්ධකයින් සමඟ බෙදා ගන්න" #. TRANSLATORS: command line option msgid "Show all results" msgstr "සියළුම ප්‍රතිඵල පෙන්වන්න" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "සේවාදායක සහ ඩීමන් අනුවාද පෙන්වන්න" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "විශේෂිත වසමක් සඳහා ඩේමන් වාචික තොරතුරු පෙන්වන්න" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "සියලුම වසම් සඳහා නිදොස් කිරීමේ තොරතුරු පෙන්වන්න" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "නිදොස් කිරීමේ විකල්ප පෙන්වන්න" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "යාවත්කාලීන කළ නොහැකි උපාංග පෙන්වන්න" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "අමතර දෝශ නිරාකරණ තොරතුරු පෙන්වන්න" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "ස්ථිරාංග යාවත්කාලීන ඉතිහාසය පෙන්වන්න" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "dbx හි ගණනය කළ අනුවාදය පෙන්වන්න" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "අවසන් වරට උත්සාහ කළ යාවත්කාලීනයේ දෝශ නිරාකරණ ලොගය පෙන්වන්න" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "ස්ථිරාංග යාවත්කාලීන තත්ත්වය පිළිබඳ තොරතුරු පෙන්වන්න" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "දැන් වසා දමන්නද?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "නව යතුරක් සමඟ ස්ථිරාංගයක් අත්සන් කරන්න" msgid "Sign data using the client certificate" msgstr "සේවාදායක සහතිකය භාවිතයෙන් දත්ත අත්සන් කරන්න" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "සේවාදායක සහතිකය භාවිතයෙන් දත්ත අත්සන් කරන්න" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "උඩුගත කළ දත්ත සේවාදායක සහතිකය සමඟ අත්සන් කරන්න" msgid "Signature" msgstr "අත්සන" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "අත්සන් කළ පේලෝඩ්" #. TRANSLATORS: file size of the download msgid "Size" msgstr "ප්රමාණය" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "මෙම ස්ථිරාංග යාවත්කාලීන කිරීමේදී සමහර වේදිකා රහස් අවලංගු විය හැක." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "මූලාශ්රය" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "DFU උපාංගයේ විකුණුම්කරු/නිෂ්පාදන ID(s) සඳහන් කරන්න" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "dbx දත්ත සමුදා ගොනුව සඳහන් කරන්න" msgid "Specify the number of bytes per USB transfer" msgstr "USB හුවමාරුවකට බයිට් ගණන සඳහන් කරන්න" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "සාර්ථකත්වය" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "සියලුම උපාංග සාර්ථකව සක්‍රිය කර ඇත" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "දුරස්ථ පාලකය සාර්ථකව අබල කරන ලදී" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "නව පාරදත්ත සාර්ථකව බාගන්නා ලදී: " #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "දුරස්ථ පාලකය සාර්ථකව සබල කර නැවුම් කරන ලදී" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "දුරස්ථ පාලකය සාර්ථකව සබල කර ඇත" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "ස්ථිරාංග සාර්ථකව ස්ථාපනය කර ඇත" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "වින්‍යාස අගය සාර්ථකව වෙනස් කරන ලදී" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "දුරස්ථ පාලකය සාර්ථකව වෙනස් කරන ලදී" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "පාරදත්ත අතින් සාර්ථකව නැවුම් කරන ලදී" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "උපාංග චෙක්සම් සාර්ථකව යාවත්කාලීන කරන ලදී" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "වාර්තාව %u ක් සාර්ථකව උඩුගත කරන ලදී" msgstr[1] "වාර්තා %u ක් සාර්ථකව උඩුගත කරන ලදී" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "උපාංග චෙක්සම් සාර්ථකව සත්‍යාපනය කරන ලදී" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "සාරාංශය" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "සහාය දක්වයි" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "සහය දක්වන CPU" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "දුරස්ථ සේවාදායකයේ සහය දක්වයි" #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "අත්හිටුවීම-නිෂ්ක්රීය කිරීම" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "සස්පෙන්ඩ්-ට-රැම්" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "ශාඛාව %s සිට %sදක්වා මාරු කරන්නද?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "උපාංගයේ ස්ථිරාංග ශාඛාව මාරු කරන්න" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "යාවත්කාලීන කිරීම සිදු කිරීමට පද්ධතියේ බලය ඉතා අඩුය" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "යාවත්කාලීන කිරීම සිදු කිරීමට පද්ධතියේ බලය ඉතා අඩුය (%u%%, අවශ්‍ය වන්නේ %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "පද්ධතියට බාහිර බලශක්ති ප්රභවයක් අවශ්ය වේ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "පෙළ" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "TPM PCR0 ප්‍රතිසංස්කරණය" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "TPM PCR0 ප්‍රතිනිර්මාණය වලංගු නැත" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "TPM PCR0 ප්‍රතිනිර්මාණය දැන් වලංගුයි" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "TPM හිස් PCRs" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM අනු:2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "ටැග් කරන්න" msgstr[1] "ටැග්" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "කිලිටි වෙලා" msgid "Target" msgstr "ඉලක්කය" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "JSON මැනිෆෙස්ටයක් භාවිතයෙන් උපාංගයක් පරීක්ෂා කරන්න" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS යනු ස්වාධීන නෛතික ආයතනයක් ලෙස ක්‍රියාත්මක වන නිදහස් සේවාවක් වන අතර $OS_RELEASE:NAME$ සමඟ කිසිදු සම්බන්ධයක් නොමැත. ඔබේ බෙදාහරින්නා ඔබේ පද්ධතිය හෝ සම්බන්ධිත උපාංග සමඟ ගැළපීම සඳහා ස්ථිරාංග යාවත්කාලීන කිසිවක් සත්‍යාපනය කර නොතිබිය හැකිය. සියලුම ස්ථිරාංග සපයනු ලබන්නේ මුල් උපකරණ නිෂ්පාදකයා විසින් පමණි." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "TPM PCR0 ප්‍රතිසංස්කරණයෙන් වෙනස් වේ." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "ඩීමන් 3වන පාර්ශ්ව කේතය පූරණය කර ඇති අතර උඩුගං බලා සංවර්ධකයින් විසින් තවදුරටත් සහාය නොදක්වයි!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "උපාංග අනුවාදය නොගැලපේ: ලැබුණේ %s, අපේක්ෂිත %s" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "%s සිට ස්ථිරාංග සපයනු ලබන්නේ දෘඪාංග වෙළෙන්දා වන %sවිසිනි." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "පද්ධති ඔරලෝසුව නිවැරදිව සකසා නොමැති අතර ගොනු බාගත කිරීම අසාර්ථක විය හැක." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "වෙළෙන්දා නිකුත් කිරීමේ සටහන් කිසිවක් සපයා නැත." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "ගැටළු සහිත උපාංග තිබේ:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "අවහිර කළ ස්ථිරාංග ගොනු නොමැත" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "අනුමත ස්ථිරාංග නොමැත." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "%s විධානය ක්‍රියාත්මක කරන විට මෙම උපාංගය නැවත %s වෙත ප්‍රතිවර්තනය වේ." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "මෙම ස්ථිරාංග සපයනු ලබන්නේ LVFS ප්‍රජා සාමාජිකයින් විසින් වන අතර මුල් දෘඪාංග වෙළෙන්දා විසින් සපයනු නොලැබේ (හෝ සහාය නොදක්වයි)." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "මෙම පැකේජය වලංගු කර නැත, එය නිවැරදිව ක්‍රියා නොකරනු ඇත." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "මෙම වැඩසටහන නිවැරදිව ක්‍රියා කළ හැක්කේ root ලෙස පමණි" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "මෙම දුරස්ථ පාලකයේ සම්බාධක නොමැති ස්ථිරාංග අඩංගු නමුත් දෘඪාංග වෙළෙන්දා විසින් තවමත් පරීක්ෂා කරනු ලැබේ. ස්ථිරාංග යාවත්කාලීන කිරීම අසාර්ථක වුවහොත් ස්ථිරාංග හස්තීයව පහත හෙළීමට ඔබට ක්‍රමයක් ඇති බව ඔබ සහතික විය යුතුය." #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "මෙම පද්ධතියට HSI ධාවන කාල ගැටළු ඇත." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "මෙම පද්ධතියට අඩු HSI ආරක්ෂක මට්ටමක් ඇත." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "මෙම මෙවලම පරිපාලකයෙකුට UEFI dbx යාවත්කාලීන යෙදීමට ඉඩ සලසයි." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "මෙම මෙවලම පරිපාලකයෙකුට UpdateCapsule මෙහෙයුම නිදොස් කිරීමට ඉඩ දෙයි." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "මෙම මෙවලම පරිපාලකයෙකුට fwupd daemon විමසා පාලනය කිරීමට ඉඩ සලසයි, ස්ථිරාංග ස්ථාපනය කිරීම හෝ පහත හෙලීම වැනි ක්‍රියා සිදු කිරීමට ඔවුන්ට ඉඩ සලසයි." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "මෙම මෙවලම පරිපාලකයෙකුට ධාරක පද්ධතියේ ස්ථාපනය නොකර fwupd ප්ලගීන භාවිතා කිරීමට ඉඩ සලසයි." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "මෙම මෙවලම භාවිතා කළ හැක්කේ root පරිශීලකයාට පමණි" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "මෙම මෙවලම පද්ධති ස්ථිරාංගයෙන් TPM සිදුවීම් ලොගය කියවා විග්‍රහ කරයි." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "තාවකාලික අසාර්ථකත්වය" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "විශ්වාසදායක පාරදත්ත" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "විශ්වාසනීය ගෙවීම" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "ටයිප් කරන්න" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "UEFI ESP කොටස අනාවරණය කර හෝ වින්‍යාස කර නොමැත" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI ස්ථිරාංග උපයෝගිතා" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "UEFI කැප්සියුල යාවත්කාලීන ලබා ගත නොහැක හෝ ස්ථිරාංග සැකසුම තුළ සබල කර ඇත" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "UEFI dbx උපයෝගිතා" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "UEFI ස්ථිරාංග පැරණි BIOS ආකාරයෙන් යාවත්කාලීන කළ නොහැක" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "UEFI වේදිකා යතුර" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "UEFI ආරක්ෂිත ඇරඹුම" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "සේවාවට සම්බන්ධ විය නොහැක" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "වත්මන් ධාවකය ඉවත් කරන්න" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "ස්ථිරාංග අවහිර කිරීම ඉවත් කිරීම:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "නිශ්චිත ස්ථිරාංගයක් ස්ථාපනය කිරීමෙන් අවහිර කිරීම ඉවත් කරයි" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "විසංකේතිතයි" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "නොදනී" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "හඳුනානොගත් උපකරණය" msgid "Unlock the device to allow access" msgstr "ප්‍රවේශයට ඉඩ දීමට උපාංගය අගුළු හරින්න" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "අගුළු හැර ඇත" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "ස්ථිරාංග ප්‍රවේශය සඳහා උපාංගය අගුළු හරියි" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "ESP ගලවයි" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "යාවත්කාලීන කිරීමේදී දෝශ නිරාකරණ ධජය නොසකසන්න" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "අත්සන් නොකළ ගෙවීම" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "සහාය නොදක්වන ඩීමන් අනුවාදය %s, සේවාදායක අනුවාදය %sවේ" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "අපිරිසිදු" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "යාවත්කාලීන කළ හැකි" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "යාවත්කාලීන දෝෂයකි" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "රූපය යාවත්කාලීන කරන්න" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "පණිවිඩය යාවත්කාලීන කරන්න" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "තත්වය යාවත්කාලීන කරන්න" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "යාවත්කාලීන අසාර්ථක වීම දන්නා ගැටළුවකි, වැඩිදුර තොරතුරු සඳහා මෙම URL වෙත පිවිසෙන්න:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "දැන් යාවත්කාලීන කරන්න?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "යාවත්කාලීන කිරීමට නැවත පණගැන්වීමක් අවශ්‍ය වේ" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "ගබඩා කර ඇති ගුප්ත ලේඛන හැෂ් වත්මන් ROM අන්තර්ගතය සමඟ යාවත්කාලීන කරන්න" msgid "Update the stored device verification information" msgstr "ගබඩා කර ඇති උපාංග සත්‍යාපන තොරතුරු යාවත්කාලීන කරන්න" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "ගබඩා කර ඇති පාරදත්ත වත්මන් අන්තර්ගතය සමඟ යාවත්කාලීන කරන්න" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "සියලුම නිශ්චිත උපාංග නවතම ස්ථිරාංග අනුවාදය වෙත යාවත්කාලීන කරයි, හෝ නිශ්චිතව දක්වා නොමැති නම් සියලුම උපාංග" msgid "Updating" msgstr "යාවත්කාලීන කිරීම" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "%s %s සිට %sදක්වා යාවත්කාලීන කරමින් ... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "%s යාවත්කාල වෙමින්…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "%s %s සිට %sදක්වා උත්ශ්‍රේණි කරන්නද?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "ස්ථිරාංග වාර්තා උඩුගත කිරීම සැබෑ උපාංගවල අසාර්ථක සහ සාර්ථක යාවත්කාලීන ඉක්මනින් හඳුනා ගැනීමට දෘඪාංග වෙළෙන්දන්ට උපකාර කරයි." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "හදිසිය" #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "උදව් සඳහා fwupdtool --help භාවිතා කරන්න" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "ස්ථිරාංග ස්ථාපනය කිරීමේදී quirk flags භාවිතා කරන්න" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "පරිශීලකයාට දැනුම් දී ඇත" #. TRANSLATORS: remote filename base msgid "Username" msgstr "පරිශීලක නාමය" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "වලංගුයි" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "ESP අන්තර්ගතයන් වලංගු කිරීම…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "ප්රභේදය" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "වෙළෙන්දා" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "සත්‍යාපනය…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "අනුවාදය" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "රැඳෙමින්…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "දෘඪාංග වෙනස්කම් සඳහා නරඹන්න" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "ගොනුවේ සිට උපාංගයට ස්ථිරාංග ලියන්න" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "ගොනුවකින් ස්ථිරාංග එක් කොටසකට ලියන්න" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "ලිපිගොනු ලිවීම:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "ලියවෙමින්…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "ඔබේ බෙදාහරින්නා ඔබේ පද්ධතිය හෝ සම්බන්ධිත උපාංග සමඟ ගැළපීම සඳහා ස්ථිරාංග යාවත්කාලීන කිසිවක් සත්‍යාපනය කර නොතිබිය හැකිය." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "මෙම ස්ථිරාංග භාවිතයෙන් ඔබේ දෘඪාංගයට හානි සිදු විය හැකි අතර, මෙම නිකුතුව ස්ථාපනය කිරීමෙන් %sසමඟ ඇති ඕනෑම වගකීමක් අවලංගු විය හැක." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "ඔබේ පද්ධතිය %sහි BKC ලෙස සකසා ඇත." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[CHECKSUM]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[DEVICE-ID|මාර්ගෝපදේශය]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[DEVICE-ID|GUID] [BRANCH]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[DEVICE-ID|GUID] [VERSION]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[FILE FILE_SIG දුරස්ථ-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[FILENAME1] [FILENAME2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SMBIOS-FILE|HWIDS-FILE]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "පෙරනිමිය" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "fwupd TPM සිදුවීම් ලොග් උපයෝගීතාව" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd ප්ලගීන" fwupd-1.9.16/po/sk.po000066400000000000000000000166771460375044200143560ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Dušan Kazik , 2015-2017 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Slovak (http://app.transifex.com/freedesktop/fwupd/language/sk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sk\n" "Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Prezývka príkazu %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Umožní zníženie verzií firmvéru" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Vyžaduje sa overenie totožnosti na prechod na staršiu verziu firmvéru vymeniteľného zariadenia" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Vyžaduje sa overenie totožnosti na prechod na staršiu verziu firmvéru v tomto počítači" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Na odomknutie zariadenia sa vyžaduje overenie totožnosti" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Vyžaduje sa overenie totožnosti na aktualizovanie firmvéru vymeniteľného zariadenia " #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Na aktualizovanie firmvéru v tomto počítači je potrebné overenie totožnosti" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Zrušené" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Zmenené" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Kontrolný medzisúčet" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Vymaže výsledky z poslednej aktualizácie" #. TRANSLATORS: error message msgid "Command not found" msgstr "Príkaz nenájdený" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Nástroj pre DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Voľby ladenia" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Popis" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Pridané zariadenie:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Zmenené zariadenie:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Odstránené zariadenie:" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Hotovo!" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Vracia sa %s z verzie %s na verziu %s... " #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Skončí po krátkom oneskorení" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Skončí po načítaní jadra" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Zlyhalo analyzovanie parametrov" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Služba zbernice D-Bus na aktualizovanie firmvéru" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Démon aktualizácie firmvéru" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Nástroj pre firmvéry" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Nájdené" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Získa všetky zariadenia, ktoré podporujú aktualizovanie firmvéru" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Získa podrobnosti o súbore firmvéru" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Získa zoznam aktualizácií pre pripojený hardvér" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Získa výsledky z poslednej aktualizácie" msgid "Install signed device firmware" msgstr "Nainštaluje podpísaný firmvér zariadenia" msgid "Install signed system firmware" msgstr "Nainštaluje podpísaný firmvér systému" msgid "Install unsigned device firmware" msgstr "Nainštaluje nepodpísaný firmvér zariadenia" msgid "Install unsigned system firmware" msgstr "Nainštaluje nepodpísaný firmvér systému" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Sleduje démona kvôli udalostiam" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Nezistil sa žiadny hardvér s možnosťou aktualizácie firmvéru" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "OK" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Prečíta firmvér zo zariadenia do súboru" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Prečíta firmvér z jedného oddielu do súboru" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Obnoví metaúdaje zo vzdialeného servera" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Preinštalováva sa %s verziou %s... " #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Zobrazí voľby ladenia" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Zobrazovať dodatočné ladiace informácie" msgid "Unlock the device to allow access" msgstr "Odomknúť zariadenie na umožnenie prístupu" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Odomkne zariadenie pre prístup k firmvéru" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Aktualizuje sa %s z verzie %s na verziu %s... " #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Verzia" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Zapíše firmvér zo súboru do zariadenia" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Zapíše firmvér zo súboru do jedného oddielu" fwupd-1.9.16/po/sr.po000066400000000000000000000414701460375044200143520ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Miloš Popović , 2016 # Марко М. Костић (Marko M. Kostić) , 2015-2018 # Мирослав Николић , 2017 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Serbian (http://app.transifex.com/freedesktop/fwupd/language/sr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sr\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Старост" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Алијас на %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Дозволи уназађивање издања фирмвера" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Потребно је поново покретање да би се исправка применила." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Одговори са да на сва питања" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Идентификујем…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Потребна је пријава за уназађивање фирмвера на преносивом уређају" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Потребна је пријава за уназађивање фирмвера на овој машини" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Идентификовање је потребно за измену подешеног удаљеног сервера који се користи за ажурирања фирмвера" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Потребна је пријава за откључавање уређаја" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Потребна је пријава за ажурирање фирмвера на преносивом уређају" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Потребна је пријава за ажурирање фирмвера на овој машини" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Потребна је пријава за ажурирање причуваних сума провере за уређај" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Отказао" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Променио" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Чек-сума" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Чисти резултате последњег ажурирања" #. TRANSLATORS: error message msgid "Command not found" msgstr "Наредба није пронађена" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "ДФУ алатка" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Опције отклањања проблема" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Распакујем…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Опис" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Додат је уређај:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Промењен је уређај:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Уклоњен је уређај:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Уређаји који су ажурирани исправно:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Уређаји који нису ажурирани исправно:" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Не проверавај старе метаподатке" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Не проверавај непослати историјат" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Урађено!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Уназађује фирмвер на уређају" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Уназађујем %s са %s на %s..." #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Преузимам…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Ишчитај SMBIOS податке из датотеке" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Омогућено" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Обриши сав историјат ажурирања фирмвера" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Бришем…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Изађи након малог застоја" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Изађи након учитавања мотора" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Нисам могао да учитам ћефове" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Не могу да обрадим аргументе" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Назив датотеке" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Потпис назива датотеке" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Основни URI фирмвера" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Д-Бус услуга ажурирања фирмвера" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Демон за ажурирање фирмвера" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Алатка за фирмвер" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Метаподаци фирмвера нису ажурирани %u дан и можда су застарели." msgstr[1] "Метаподаци фирмвера нису ажурирани %u дана и можда су застарели." msgstr[2] "Метаподаци фирмвера нису ажурирани %u дана и можда су застарели." #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Нашао" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "ГУИД" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "ГУИД" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Добави све уређаје који подржавају ажурирање фирмвера" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Добави појединости о датотеци са фирмвером" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Добавља подешена удаљена места" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Добави списак свих ажурирања за повезани уређај" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Добавља издања за уређај" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Добавља резултате последњег ажурирања" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Мирујем…" msgid "Install signed device firmware" msgstr "Инсталирајте потписани фирмвер за уређаје" msgid "Install signed system firmware" msgstr "Инсталирајте потписани системски фирмвер" msgid "Install unsigned device firmware" msgstr "Инсталирајте непотписани фирмвер за уређаје" msgid "Install unsigned system firmware" msgstr "Инсталирајте непотписани системски фирмвер" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Инсталирам ажурирање фирмвера…" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Привезак" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Учитавам…" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "URI метаподатака" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Мења дати удаљени сервер" msgid "Modify a configured remote" msgstr "Измени подешени удаљени сервер" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Прати демона за догађајима" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Нема хардвера којем се може ажурирати фирмвер" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "У реду" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Лозинка" msgid "Payload" msgstr "Товар" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Важност" msgid "Proceed with upload?" msgstr "Наставити са отпремањем?" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Исчитај фирмвер са уређаја у датотеку" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Исчитај фирмвер са једне партиције у датотеку" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Читам…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Освежава метаподатке са удаљеног сервера" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Поново инсталирам %s са %s..." #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Удаљени ИБ" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Замењује податке у постојећој датотеци фирмвера" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "URI извештаја" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Захтева везу са интернетом" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Поново покренути сада?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Поново покрећем уређај…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Враћа све ИБ-јеве хардвера на машини" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Заказује инсталирање за следеће подизање система када је могуће" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Заказујем…" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Подели историјат фирмвера са програмерима" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Прикажи издања клијента и демона" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Прикажи опције за отклањање проблема" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Прикажи додатне податке за отклањање проблема" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Прикажи историјат ажурирања фирмвера" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Сажетак" msgid "Target" msgstr "Мета" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Врста" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Непознато" msgid "Unlock the device to allow access" msgstr "Откључајте уређај да бисте дозволили приступ" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Откључава уређај за приступ фирмверу" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Узрок неуспеха ажурирања је познат, погледајте ову адресу за више података:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Ажурирати сада?" msgid "Update the stored device verification information" msgstr "Ажурирајте причуване податке потврђивања уређаја" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Ажурирам %s са %s на %s..." #. TRANSLATORS: remote filename base msgid "Username" msgstr "Корисничко име" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Проверавам…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Издање" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Чекам…" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Упиши фирмвер из датотеке у уређај" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Упиши фирмвер из датотеке у једну партицију" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Пишем…" fwupd-1.9.16/po/sv.po000066400000000000000000003607131460375044200143620ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Anders Jonsson , 2017,2019-2023 # Andreas Henriksson , 2017 # Josef Andersson , 2015,2017-2018 # Josef Andersson , 2015,2017 # Luna Jernberg , 2020-2021 # Sebastian Rasmussen , 2018-2020 # Sebastian Rasmussen , 2018 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Swedish (http://app.transifex.com/freedesktop/fwupd/language/sv/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sv\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f minut kvarstår" msgstr[1] "%.0f minuter kvarstår" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "%s BMC-uppdatering" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "%s-batteriuppdatering" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "%s CPU-mikrokodsuppdatering" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "%s-kamerauppdatering" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "%s-konfigurationsuppdatering" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s Consumer ME-uppdatering" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s-styrenhetsuppdatering" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s Corporate ME-uppdatering" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s-enhetsuppdatering" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "%s-skärmuppdatering" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "%s-dockuppdatering" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "%s-enhetsuppdatering" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s inbäddad styrenhetsuppdatering" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "%s-fingeravtrycksläsaruppdatering" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "%s-flashenhetsuppdatering" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "%s GPU-uppdatering" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "%s-ritplatteuppdatering" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "%s-tangentbordsuppdatering" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s ME-uppdatering" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "%s-musuppdatering" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "%s-nätverksgränssnittsuppdatering" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "%s SSD-uppdatering" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "%s-lagringsstyrenhetsuppdatering" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s-systemuppdatering" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "%s TPM-uppdatering" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s Thunderbolt-styrenhetsuppdatering" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "%s-pekplatteuppdatering" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "%s USB-dockuppdatering" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "%s USB-mottagaruppdatering" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s-uppdatering" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s och alla anslutna enheter kanske inte går att använda under uppdatering." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "%s dök upp: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "%s ändrad: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "%s försvann: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s går för tillfället inte uppdatera" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s-tillverkningsläge" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "%s måste förbli anslutna under tiden som uppdateringen pågår för att undvika skador." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s måste förbli ansluten till en strömkälla under tiden som uppdateringen pågår för att undvika skada." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s-åsidosättning" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s-version" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u dag" msgstr[1] "%u dagar" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u enhet har en uppgradering för fast programvara tillgänglig." msgstr[1] "%u enheter har en uppgradering för fast programvara tillgänglig." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%u enhet har inte den mest kända konfigurationen." msgstr[1] "%u enheter har inte den mest kända konfigurationen." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u timme" msgstr[1] "%u timmar" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u lokal enhet stöds" msgstr[1] "%u lokala enheter stöds" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u minut" msgstr[1] "%u minuter" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u sekund" msgstr[1] "%u sekunder" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (tröskelvärde %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(föråldrad)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "Ett TPM PCR har nu ett ogiltigt värde" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "Återuppspelningsskydd för AMD:s fasta programvara" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "Skrivskydd för AMD:s fasta programvara" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "Säkert AMD-tillbakarullningsskydd för processorn" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "ARCHIVE FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATFILE]" msgstr "ARKIV FAST_PROGRAMVARA METAINFO [FAST_PROGRAMVARA] [METAINFO] [JCAT-FIL]" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Åtgärd krävs:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Aktivera enheter" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Aktivera väntande enheter" msgid "Activate the new firmware on the device" msgstr "Aktiverar den nya fasta programvaran på enheten" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Aktiverar uppdatering av fast programvara" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Aktiverar uppdatering av fast programvara för" #. TRANSLATORS: command description msgid "Adds devices to watch for future emulation" msgstr "Lägger till enheter att bevaka för framtida emulering" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Ålder" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Samtyck och aktivera fjärrkällan?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Alias för %s" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "Alla TPM PCR är nu giltiga" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Alla TPM PCR är giltiga" #. TRANSLATORS: an application is preventing system updates msgid "All devices are prevented from update by system inhibit" msgstr "Alla enheter förhindras att uppdatera av systemhindrande" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Alla enheter av samma typ kommer uppdateras samtidigt" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Tillåt att nedgradera versioner av fast programvara" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Tillåt ominstallation av befintliga versioner av fast programvara" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Tillåt att byta gren för fast programvara" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Alternativ gren" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "En uppdatering pågår" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "En uppdatering kräver en omstart för att slutföras." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "En uppdatering kräver att systemet stängs ned för att slutföras." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Svara ja på alla frågor" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Tillämpa uppdateringar för fast programvara" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Tillämpa uppdatering även när det inte rekommenderas" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Tillämpa uppdateringsfiler" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Tillämpar uppdatering…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Godkänd fast programvara:" msgstr[1] "Godkänd fast programvara:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Fråga igen nästa gång?" #. TRANSLATORS: command description msgid "Asks the daemon to quit" msgstr "Ber demonen att avsluta" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Fäst till fast programvaruläge" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Autentiserar…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Autentiseringsdetaljer krävs" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Autentisering krävs för att nedgradera den fasta programvaran för en flyttbar enhet" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Autentisering krävs för att nedgradera den fasta programvaran på denna maskin" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to fix a host security issue" msgstr "Autentisering krävs för att fixa ett säkerhetsproblem på värden" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Autentisering krävs för att ändra BIOS-inställningar" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Autentisering krävs för att ändra en konfigurerad fjärrkälla som används för uppdateringar av fast programvara" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Autentisering krävs för att modifiera demonkonfiguration" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "Autentisering krävs för att läsa BIOS-inställningar" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Autentisering krävs för att sätta listan över godkänd fast programvara" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Autentisering krävs för att signera data med klientcertifikatet" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Autentisering krävs för att växla till den nya fasta programvaruversionen" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to undo the fix for a host security issue" msgstr "Autentisering krävs för att återställa fixen för ett säkerhetsproblem på värden" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Autentisering krävs för att låsa upp en enhet" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Autentisering krävs för att uppdatera den fasta programvaran på en flyttbar enhet" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Autentisering krävs för att uppdatera den fasta programvaran för denna maskin" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Autentisering krävs för att uppdatera lagrade kontrollsummor för enheten" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Automatisk rapportering" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Skicka automatiskt varje gång?" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS Firmware Updates" msgstr "Uppdateringar för fast BIOS-programvara" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "BIOS-tillbakarullningsskydd" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS firmware updates" msgstr "Uppdateringar för fast BIOS-programvara" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "BIOS-tillbakarullningsskydd" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "BIOS-uppdateringar levererade via LVFS eller Windows Update" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "BYGGAR-XML FILNAMN-MÅL" msgid "BYTES" msgstr "BYTE" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Batteri" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Bind ny kärndrivrutin" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Blockerade fast programvarufiler:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Blockerad version" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Blockera fast programvara:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Blockerar en specifik fast programvara från att installeras" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Starthanterarversion" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Gren" #. TRANSLATORS: command description msgid "Build a cabinet archive from a firmware blob and XML metadata" msgstr "Bygg ett kabinettarkiv från en fast programvaru-blob och XML-metadata" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Bygg en fast programvarufil" #. TRANSLATORS: longer description msgid "CPU Microcode must be updated to mitigate against various information-disclosure security issues." msgstr "CPU-mikrokod måste uppdateras för att förmildra mot diverse informationsavslöjande säkerhetsproblem." #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Avbryt" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Avbruten" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Kan inte tillämpa eftersom dbx-uppdatering redan har tillämpats." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Kan inte tillämpa uppdateringar på live-media" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Ändrad" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Kontrollerar att kryptografisk hash matchar fast programvara" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Kontrollsumma" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Välj gren" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Välj enhet" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Välj fast programvara" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Välj utgåva" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Välj ESP:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Välj volym" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Rensar resultaten från senaste uppdateringen" #. TRANSLATORS: error message msgid "Command not found" msgstr "Kommandot hittades inte" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Stöds av gemenskapen" #. TRANSLATORS: title prefix for the BIOS settings dialog msgid "Configuration Change Suggested" msgstr "Föreslagen ändring av konfiguration" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "Konfigurationen är endast läsbar av systemadministratören" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Konvertera en fast programvarufil" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Skapad" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Kritisk" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Kryptografisk hashverifiering är tillgänglig" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Aktuellt värde" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Aktuell version" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "ENHETS-ID|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU-verktyg" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Felsökningsalternativ" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Dekomprimerar…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Beskrivning" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Koppla från till starthanterarläge" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Detaljer" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "Avvik från den mest kända konfigurationen?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Enhetsflaggor" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Enhets-ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Enhet tillagd:" #. TRANSLATORS: the device is already connected msgid "Device already exists" msgstr "Enheten finns redan" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "Enheten har för lite batteri" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "Enheten har för lite batteri (%u%%, behöver %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Enhet kan återhämta sig från flashningsfel" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "Enheten kan inte användas medan locket är stängt" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Enhet ändrad:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "Enhetsemulering är inte aktiverad." #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Fast programvara för enhet krävs för att ha en versionskontroll" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "Enheten är emulerad" #. TRANSLATORS: device cannot be interrupted, for instance taking a phone call msgid "Device is in use" msgstr "Enheten används" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Enhet är låst" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Enhet krävs för att installera alla tillgängliga utgåvor" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Enheten kan inte nås" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "Enheten kan inte nås, eller är utanför den trådlösa räckvidden" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Enhet går använda under tiden som uppdateringen pågår" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "Enheten väntar på att uppdateringen ska tillämpas" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Enhet borttagen:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "Enheten behöver vara ansluten till nätspänning" #. TRANSLATORS: device does not have a display connected msgid "Device requires a display to be plugged in" msgstr "Enheten kräver att en skärm är inpluggad" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "Enheten kräver en programvarulicens för att uppdateras" #. TRANSLATORS: longer description msgid "Device software updates are provided for this device." msgstr "Enhetsprogramuppdateringar tillhandahålls för denna enhet." #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Enhet genomför uppdatering i steg" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "Enhet stöder byte till en annan gren av fast programvara" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Uppdateringsmetod för enhet" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Enhetsuppdatering kräver aktivering" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Enheten kommer säkerhetskopiera fast programvara innan den installeras" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Enhet kommer inte att dyka upp igen efter att uppdateringen färdigställs" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Enheter som har uppdaterats:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Enheter som inte uppdaterades korrekt:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Enheter utan tillgängliga uppdateringar för fast programvara: " #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Enheter med den senaste tillgängliga uppdateringen för fast programvara:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "Hittade inga enheter med matchande GUID" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Inaktiverad" msgid "Disabled fwupdate debugging" msgstr "Inaktiverade fwupdate-felsökning" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Inaktiverar en given fjärrkälla" #. TRANSLATORS: command line option msgid "Display version" msgstr "Visa version" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Distribution" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Kontrollera inte gammal metadata" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Kontrollera inte ej rapporterad historik" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Kontrollera inte om fjärrkällor för hämtning ska aktiveras" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Varken kontrollera eller fråga om omstart efter uppdatering" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Inkludera inte loggdomänsprefix" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Inkludera inte tidsstämpelprefix" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Utför inte säkerhetskontroller för enheter" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "Fråga inte om enheter" #. TRANSLATORS: command line option msgid "Do not prompt to fix security issues" msgstr "Fråga inte om att fixa säkerhetsproblem" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "Sök inte den fasta programvaran under tolkning" #. TRANSLATORS: warning message shown after update has been scheduled msgid "Do not turn off your computer or remove the AC adaptor while the update is in progress." msgstr "Slå inte av din dator och ta inte bort nätadaptern medan uppdateringen pågår." #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Skriv inte till historikdatabasen" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Förstår du konsekvenserna av att byta gren av fast programvara?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Vill du inaktivera den här funktionen för framtida uppdateringar?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "Vill du aktivera den nu?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Vill du uppdatera den här fjärrkällan nu?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Vill du skicka rapporter automatiskt för framtida uppdateringar?" #. TRANSLATORS: command line option msgid "Don't prompt for authentication (less details may be shown)" msgstr "Fråga inte om autentisering (mindre detaljer kan visas)" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Klar!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Nedgradera %s från %s till %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Nedgradera fast programvara på en enhet" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Nedgraderar %s från %s till %s… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Nedgraderar %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Hämta en fil" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Hämtar…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Dumpa SMBIOS-data från en fil" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Tid" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Angiven ESP var inte giltig" #. TRANSLATORS: longer description msgid "Each system should have tests to ensure firmware security." msgstr "Varje system bör ha test för att säkerställa den fasta programvarans säkerhet." #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "Emulera en enhet med ett JSON-manifest" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Emulerad" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Emulerad värd" msgid "Enable" msgstr "Aktivera" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Aktivera stöd för uppdatering av fast programvara på system som stöds" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Aktivera ny fjärrkälla?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Aktivera denna fjärrkälla?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Aktiverad" msgid "Enabled fwupdate debugging" msgstr "Aktiverade fwupdate-felsökning" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Aktiverad om hårdvaran matchar" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Aktiverar en given fjärrkälla" #. TRANSLATORS: longer description msgid "Enabling firmware updates for the BIOS allows fixing security issues." msgstr "Aktivering av fasta programvaruuppdateringar för BIOS möjliggör fixande av säkerhetsproblem." msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Att aktivera denna funktionalitet görs på egen risk, vilket betyder att du måste kontakta den ursprungliga tillverkaren av din utrustning om problem som orsakas av dessa uppdateringar. Endast problem med själva uppdateringsprocessen ska rapporteras på $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Att aktivera denna fjärrkälla görs på egen risk." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Krypterad" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "Krypterat RAM" msgid "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." msgstr "Krypterad RAM gör det omöjligt för information som är lagrad på enhetsminnet att läsas om minneschipet tas bort och koms åt." #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Har nått slutet på sin livstid" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Uppräkning" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Ta bort all historik för fast programvara" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Raderar…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Avsluta efter en kort fördröjning" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Avsluta efter att motorn har lästs in" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Exportera en fast programvarufilstruktur till XML" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Extrahera en fast programvaru-blob till avbilder" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "FIL" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "FIL [ENHETS-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "FILNAMN" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "FILNAMN CERTIFIKAT PRIVAT-NYCKEL" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "FILNAMN ENHET-ALT-NAMN|ENHET-ALT-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "FILNAMN ENHET-ALT-NAMN|ENHET-ALT-ID [AVBILD-ALT-NAMN|AVBILD-ALT-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "FILNAMN ENHETS-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "FILNAMN AVSTÅND DATA [FAST-PROGRAMVARUTYP]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "FILNAMN [ENHETS-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "FILNAMN [FAST-PROGRAMVARUTYP]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "FILNAMN-KÄL FILNAMN-MÅL [FAST-PROGRAMVARUTYP-KÄL] [FAST-PROGRAMVARUTYP-MÅL]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "FILNAMN|KONTROLLSUMMA1[,KONTROLLSUMMA2][,KONTROLLSUMMA3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Misslyckades" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Misslyckades med att tillämpa uppdatering" #. TRANSLATORS: error message for Windows msgid "Failed to connect to Windows service, please ensure it's running." msgstr "Misslyckades med att ansluta till Windows-tjänst, säkerställ att den körs." #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Misslyckades med att ansluta till demon" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Misslyckades med att hämta väntande enheter" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Misslyckades med att installera uppdatering av fast programvara" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Misslyckades med att läsa in lokal dbx" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Misslyckades med att läsa in speciallösning" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Misslyckades med att läsa in system-dbx" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Misslyckades med att låsa" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Misslyckades med att tolka argument" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Misslyckades med att tolka fil" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Misslyckades med att tolka --filter-flaggor" msgid "Failed to parse flags for --filter-release" msgstr "Misslyckades med att tolka flaggor för --filter-release" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Misslyckades med att tolka lokal dbx" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Misslyckades med att starta om" #. TRANSLATORS: a feature is something like "can show an image" msgid "Failed to set front-end features" msgstr "Misslyckades med att sätta funktioner för framände" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Misslyckades med att sätta uppstartsläge" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Misslyckades med att validera ESP-innehåll" #. TRANSLATORS: item is FALSE msgid "False" msgstr "Falskt" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Filnamn" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Filnamnssignatur" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Filnamnskälla" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Filnamn krävs" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Filtrera via en uppsättning av enhetsflaggor genom att använda ett ~-prefix för att exkludera, t.ex. ”internal,~needs-reboot”" #. TRANSLATORS: command line option msgid "Filter with a set of release flags using a ~ prefix to exclude, e.g. 'trusted-release,~trusted-metadata'" msgstr "Filtrera med en uppsättning utgåveflaggor och prefixet ~ för att exkludera, t.ex. 'trusted-release,~trusted-metadata'" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Attestering av fast programvara" #. TRANSLATORS: longer description msgid "Firmware Attestation checks device software using a reference copy, to make sure that it has not been changed." msgstr "Attestering av fast programvara kontrollerar enhetsprogramvara med en referenskopia för att se att den inte har ändrats." #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "BIOS-beskrivare för fast programvara" #. TRANSLATORS: longer description msgid "Firmware BIOS Descriptor protects device firmware memory from being tampered with." msgstr "BIOS-beskrivare för fast programvara skyddar enhetens minne för fast programvara från att mixtras med." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "BIOS-region för fast programvara" #. TRANSLATORS: longer description msgid "Firmware BIOS Region protects device firmware memory from being tampered with." msgstr "BIOS-region för fast programvara skyddar enhetens minne för fast programvara från att mixtras med." #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Fast programvara bas-URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "D-Bus-tjänst för uppdatering av fast programvara" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Uppdateringsdemon för fast programvara" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Verifiering av uppdateraren av fast programvara" msgid "Firmware Updater Verification checks that software used for updating has not been tampered with." msgstr "Verifiering av uppdateraren av fast programvara kontrollerar att programvaran som används för uppdatering inte har mixtrats med." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Uppdateringar för fast programvara" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Fast programvaruverktyg" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Skrivskydd för fast programvara" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Skrivskyddslås för fast programvara" #. TRANSLATORS: longer description msgid "Firmware Write Protection protects device firmware memory from being tampered with." msgstr "Skrivskydd för fast programvara skyddar enhetens minne för fast programvara från att mixtras med." #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Attestering av fast programvara" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Fast programvara är redan blockerad" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Fast programvara är inte redan blockerad" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Metadata för fast programvara har inte uppdaterats på %u dag och kan vara inaktuell." msgstr[1] "Metadata för fast programvara har inte uppdaterats på %u dagar och kan vara inaktuell." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Uppdateringar för fast programvara" msgid "Firmware updates are not supported on this machine." msgstr "Uppdateringar av fast programvara stöds inte på denna maskin." msgid "Firmware updates are supported on this machine." msgstr "Uppdateringar av fast programvara stöds på denna maskin." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Uppdateringar för fast programvara är inaktiverade; kör ”fwupdmgr unlock” för att aktivera" #. TRANSLATORS: command description msgid "Fix a specific host security attribute" msgstr "Fixa ett specifikt säkerhetsattribut på värden" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fix reverted successfully" msgstr "Fixen återställdes" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fixed successfully" msgstr "Fixades" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Flaggor" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Tvinga åtgärden genom att lätta på några kontroller vid körning" msgid "Force the action ignoring all warnings" msgstr "Tvinga åtgärden, ignorera alla varningar" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Hittad" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Full diskkryptering upptäckt" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "Krypteringshemligheter för hel disk kan göras ogiltiga vid uppdatering" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "Plattform med säkring" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Plattform med säkring" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "GUID|DEVICE-ID" msgstr "GUID|ENHETS-ID" msgid "Get BIOS settings" msgstr "Erhåll BIOS-inställningar" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Hämta alla enhetsflaggor som stöds av fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Hämta alla enheter som stödjer uppdateringar av fast programvara" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Erhåll alla aktiverade insticksmoduler som är registrerade i systemet" #. TRANSLATORS: command description msgid "Get device report metadata" msgstr "Hämta rapportmetadata för enhet" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Hämtar detaljer om en fast programvarufil" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Ger de konfigurerade fjärrkällorna" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Hämtar värdens säkerhetsattribut" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Hämtar listan över godkänd fast programvara" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Hämtar listan över blockerad fast programvara" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Hämtar listan över uppdateringar för ansluten hårdvara" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Erhåll utgåvan för en enhet" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Hämtar resultaten från senaste uppdateringen" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWIDS-FIL" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Hårdvara väntar på att bli utdragen/återinsatt" #. TRANSLATORS: the release urgency msgid "High" msgstr "Hög" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Säkerhetshändelser för värd" #. TRANSLATORS: error message for unsupported feature #, c-format msgid "Host Security ID (HSI) is not supported" msgstr "Värdsäkerhets-ID (HSI) stöds inte" #. TRANSLATORS: success, so say thank you to the user msgid "Host Security ID attributes uploaded successfully, thanks!" msgstr "ID-attribut för värdsäkerhet skickades, tack!" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Säkerhets-ID för värd:" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "INHIBIT-ID" msgstr "HINDRANDE-ID" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "IOMMU-skydd" #. TRANSLATORS: longer description msgid "IOMMU Protection prevents connected devices from accessing unauthorized parts of system memory." msgstr "IOMMU-skydd förhindrar anslutna enheter från att komma åt ej auktoriserade delar av systemminnet." #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "IOMMU-enhetsskydd inaktiverat" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "IOMMU-enhetsskydd aktiverat" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Inaktiv…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ignorera strikta SSL-kontroller vid hämtning av filer" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Ignorera fel i kontrollsumman för fast programvara" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Ignorera fel i matchning av hårdvara för fast programvara" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ignorera säkerhetskontroller för giltighet" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Ignorerar strikta SSL-kontroller, för att göra detta automatiskt i framtiden exportera DISABLE_SSL_STRICT i din miljö" #. TRANSLATORS: the inhibit ID is a short string like dbus-123456 #, c-format msgid "Inhibit ID is %s." msgstr "Hindrande-ID är %s." #. TRANSLATORS: command description msgid "Inhibit the system to prevent upgrades" msgstr "Hindra systemet så uppgraderingar förhindras" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Installationstid" #. TRANSLATORS: command description msgid "Install a firmware file in cabinet format on this hardware" msgstr "Installera en fast programvarufil i kabinettformat på denna hårdvara" #. TRANSLATORS: command description msgid "Install a raw firmware blob on a device" msgstr "Installera en rå fast programvaru-blob på en enhet" #. TRANSLATORS: command description msgid "Install a specific firmware file on all devices that match" msgstr "Installera en specifik fast programvara på alla enheter som matchar" #. TRANSLATORS: command description msgid "Install a specific firmware on a device, all possible devices will also be installed once the CAB matches" msgstr "Installera specifik fast programvara på en enhet, alla möjliga enheter kommer också installeras när CAB-arkivet matchar" msgid "Install old version of signed system firmware" msgstr "Installera en gammal version av signerad fast programvara för systemet" msgid "Install old version of unsigned system firmware" msgstr "Installera en gammal version av osignerad fast programvara för systemet" msgid "Install signed device firmware" msgstr "Installera signerad fast programvara för enhet" msgid "Install signed system firmware" msgstr "Installera signerad fast programvara för systemet" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Installera först på föräldraenhet" msgid "Install unsigned device firmware" msgstr "Installera osignerad fast programvara för enhet" msgid "Install unsigned system firmware" msgstr "Installera osignerad fast programvara för systemet" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Installerar fast programvara…" #. TRANSLATORS: stay on one firmware version unless the new version is #. explicitly #. * specified msgid "Installing a specific release is explicitly required" msgstr "Det krävs att explicit installera en specifik utgåva" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Installerar uppdatering för fast programvara…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Installerar på %s…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "Installation av denna uppdatering kan också ogiltigförklara eventuell garanti för enheten." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Heltal" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "Intel BootGuard ACM-skyddad" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Intel BootGuard ACM-skyddad" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Intel BootGuards felpolicy" msgid "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." msgstr "Intel BootGuards felpolicy säkerställer att enheten inte fortsätter starta om dess enhetsprogramvara har mixtrats med." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Intel BootGuard-säkring" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Intel BootGuard OTP-säkring" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Intel BootGuard verifierad uppstart" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Intel BootGuard-felpolicy" #. TRANSLATORS: longer description msgid "Intel BootGuard prevents unauthorized device software from operating when the device is started." msgstr "Intel BootGuard förhindrar ej auktoriserad enhetsprogramvara från att köras då enheten startas." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Intel BootGuard verifierad uppstart" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET aktiv" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET stöds" #. TRANSLATORS: longer description msgid "Intel Control-Flow Enforcement Technology detects and prevents certain methods for running malicious software on the device." msgstr "Intel Control-Flow Enforcement Technology upptäcker och förhindrar vissa metoder för att köra skadlig programvara på enheten." #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS Mitigation" msgstr "Intel GDS-förmildrande" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS mitigation" msgstr "Intel GDS-förmildrande" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Intel Management Engine-tillverkningsläge" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Intel Management Engine-åsidosättning" #. TRANSLATORS: longer description msgid "Intel Management Engine Override disables checks for device software tampering." msgstr "Intel Management Engine-åsidosättning inaktiverar kontroller om enhetens programvara mixtrats med." #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Intel Management Engine-version" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: longer description msgid "Intel Supervisor Mode Access Prevention ensures critical parts of device memory are not accessed by less secure programs." msgstr "Intel Supervisor Mode Access Prevention säkerställer att kritiska delar av enhetsminnet inte koms åt av mindre säkra program." #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Intern enhet" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Ogiltig" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Ogiltiga argument" #. TRANSLATORS: error message msgid "Invalid arguments, expected GUID" msgstr "Ogiltiga argument, GUID förväntades" #. TRANSLATOR: This is the error message for #. * incorrect parameter msgid "Invalid arguments, expected an AppStream ID" msgstr "Ogiltiga argument, ett AppStream-ID förväntades" #. TRANSLATORS: error message msgid "Invalid arguments, expected at least ARCHIVE FIRMWARE METAINFO" msgstr "Ogiltiga argument, förväntade åtminstone ARKIV FAST_PROGRAMVARA METAINFO" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Är nedgradering" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Är i starthanterarläge" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Är uppgradering" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Problem" msgstr[1] "Problem" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "NYCKEL,VÄRDE" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "Kärnan är ej längre befläckad" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "Kärnan är befläckad" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Lockdown för Linux-kärnan inaktiverad" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Lockdown för Linux-kärnan aktiverad" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Nyckelring" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "PLATS" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Senast ändrad" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Mindre än en minut kvarstår" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Licens" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Lockdown för Linux-kärnan" #. TRANSLATORS: longer description msgid "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgstr "Lockdown-läge för Linux-kärnan förhindrar administratörskonton (root) från att komma åt och ändra kritiska delar av systemprogramvara." msgid "Linux Kernel Swap temporarily saves information to disk as you work. If the information is not protected, it could be accessed by someone if they obtained the disk." msgstr "Linux-kärnans växlingsutrymme sparar tillfälligt information på disk under tiden du arbetar. Om informationen inte är skyddad skulle det vara möjligt för någon att komma åt den om de fick tag på disken." #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Verifiering av Linux-kärnan" msgid "Linux Kernel Verification makes sure that critical system software has not been tampered with. Using device drivers which are not provided with the system can prevent this security feature from working correctly." msgstr "Verifiering av Linux-kärnan säkerställer att kritisk systemprogramvara inte har mixtrats med. Att använda enhetsdrivrutiner som inte tillhandahålls med systemet kan förhindra denna säkerhetsfunktion att fungera som den ska." #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Linux-växlingsutrymme" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Vendor Firmware Service (stabil fast programvara)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Vendor Firmware Service (fast programvara för testning)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Linux-kärna" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Lockdown för Linux-kärnan" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linux-växlingsutrymme" #. TRANSLATORS: command description msgid "List EFI variables with a specific GUID" msgstr "Lista EFI-variabler med ett specifikt GUID" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Lista poster i dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Lista uppdateringar för fast programvara som stöds" #. TRANSLATORS: command description msgid "List the available firmware GTypes" msgstr "Lista tillgängliga GType-typer för fast programvara" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Lista de tillgängliga typerna av fast programvara" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Listar filer på ESP" #. TRANSLATORS: command description msgid "Load device emulation data" msgstr "Läs in enhetens emuleringsdata" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "Inläst från en extern modul" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Läser in…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Låst" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Låg" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "MEI-nyckelmanifest" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "MEI-nyckelmanifest" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "MEI-tillverkningsläge" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "MEI-åsidosättning" msgid "MEI version" msgstr "MEI-version" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Aktivera manuellt specifika insticksmoduler" #. TRANSLATORS: longer description msgid "Manufacturing Mode is used when the device is manufactured and security features are not yet enabled." msgstr "Tillverkningsläge används när enheten tillverkas och säkerhetsfunktioner ännu inte aktiverats." #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Största längd" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Högsta värde" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Medel" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Metadatasignatur" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Metadata-URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Metadata kan hämtas från Linux Vendor Firmware Service." #. TRANSLATORS: error message for a user who ran fwupdmgr #. * refresh recently msgid "Metadata is up to date; use --force to refresh again." msgstr "Metadata är uppdaterade; använd --force för att uppdatera igen." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Minimiversion" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Minsta längd" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Lägsta värde" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Demon och klient stämmer inte, använd %s istället" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Modifierar ett konfigurationsvärde för demonen" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Modifierar en given fjärrkälla" msgid "Modify a configured remote" msgstr "Modifiera en konfigurerad fjärrkälla" msgid "Modify daemon configuration" msgstr "Modifiera demonkonfiguration" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Övervaka demonen för händelser" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Monterar ESP" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Kräver en omstart efter installation" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Kräver omstart" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Kräver en nedstängning efter installation" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Ny version" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Ingen åtgärd angiven!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Inga nedgraderingar för %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Inget fast programvaru-ID hittat" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "Ingen fast programvara hittades" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Ingen uppdateringsbar hårdvara upptäcktes" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Inga insticksmoduler hittades" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Inga utgåvor tillgängliga" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Inga fjärrkällor är aktiverade för närvarande, så ingen metadata är tillgänglig." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Inga fjärrkällor tillgängliga" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Inga enheter går att uppdatera" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Inga uppdateringar tillgängliga" msgid "No updates available for remaining devices" msgstr "Inga uppdateringar tillgängliga för kvarvarande enheter" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Inga uppdateringar applicerades" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Inte godkänd" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Hittades inte" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Stöds inte" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "OK" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "OK!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Gammal version" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Visa endast enkelt PCR-värde" #. TRANSLATORS: command line option msgid "Only use peer-to-peer networking when downloading files" msgstr "Använd endast P2P-nätverk vid hämtning av filer" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Endast versionsuppgraderingar är tillåtna" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Utmatning i JSON-format" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Åsidosätt standardsökväg för ESP" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Firmware" msgstr "Fast programvara från P2P" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Metadata" msgstr "P2P-metadata" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "SÖKVÄG" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Tolka och visa detaljer om en fast programvarufil" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Tolkar dbx-uppdatering…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Tolkar system-dbx…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Lösenord" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Patcha en fast programvaru-blob på ett känt avstånd" msgid "Payload" msgstr "Nyttolast" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "Väntande" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Andel färdigställt" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Utför operationen?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Plattformsfelsökning" #. TRANSLATORS: longer description msgid "Platform Debugging allows device security features to be disabled. This should only be used by hardware manufacturers." msgstr "Plattformsfelsökning tillåter att enhetens säkerhetsfunktioner inaktiveras. Detta bör endast göras av hårdvarutillverkare." #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Plattformsfelsökning" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Se till att du har volymåterställningsnyckeln innan du fortsätter." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Ange en siffra mellan 0 och %u: " #. TRANSLATORS: the user isn't reading the question -- please don't translate #. * 'Y' or 'N' as these are hardcoded msgid "Please enter either Y or N: " msgstr "Ange antingen Y eller N:" #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Beroenden för insticksmodul saknas" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Möjliga värden" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "DMA-skydd före uppstart" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "DMA-skydd före uppstart" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "DMA-skydd före uppstart är inaktiverat" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "DMA-skydd före uppstart är aktiverat" #. TRANSLATORS: longer description msgid "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." msgstr "DMA-skydd före uppstart förhindrar enheter från att komma åt systemminne efter att de anslutits till datorn." #. TRANSLATORS: warning message msgid "Press unlock on the device to continue the update process." msgstr "Tryck lås upp på enheten för att fortsätta uppdateringsprocessen." #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Föregående version" msgid "Print the version number" msgstr "Skriv ut versionsnumret" msgid "Print verbose debug statements" msgstr "Skriv ut utförliga felsökningssatser" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Prioritet" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Problem" msgid "Proceed with upload?" msgstr "Fortsätt med att skicka upp?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Processorsäkerhetskontroller" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Processortillbakarullningsskydd" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Proprietär" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Fråga efter stöd för uppdatering av fast programvara" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "FJÄRR-ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "FJÄRR-ID NYCKEL VÄRDE" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Skrivskyddad" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Läs en fast programvaru-blob från en enhet" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Läs om fast programvara från en enhet" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Läs fast programvara från enhet till fil" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Läs fast programvara från en partition till fil" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Läser från %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Läser…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Startar om…" #. TRANSLATORS: how often we should refresh the metadata msgid "Refresh Interval" msgstr "Uppdateringsintervall" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Uppdatera metadata från fjärrserver" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Återinstallera %s till %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Återinstallera aktuell fast programvara på enheten" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Installera om fast programvara på en enhet" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Återinstallerar %s med %s… " #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Utgåvegren" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Utgåveflaggor" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "Utgåva-ID" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Fjärrkälla-ID" #. TRANSLATORS: command description msgid "Removes devices to watch for future emulation" msgstr "Tar bort enheter att bevaka för framtida emulering" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Ersätt data i en befintlig fast programvarufil" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Rapport-URI" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Rapporterat till fjärrserver" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Begäran avbruten" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "Nödvändigt efivarfs-filsystem hittades inte" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "Hårdvara som krävs hittades inte" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Kräver en starthanterare" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Kräver internetanslutning" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Starta om nu?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Starta om demonen för att göra så att ändringarna får effekt?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Startar om enhet…" #. TRANSLATORS: command description msgid "Retrieve BIOS settings. If no arguments are passed all settings are returned" msgstr "Erhåll BIOS-inställningar. Om inga argument skickas med returneras alla inställningar" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Returnera alla hårdvaru-ID:n för maskinen" #. TRANSLATORS: ask the user to upload msgid "Review and upload report now?" msgstr "Granska och skicka rapport nu?" #. TRANSLATORS: longer description msgid "Rollback Protection prevents device software from being downgraded to an older version that has security problems." msgstr "Tillbakarullningsskydd förhindrar enhetsprogramvara från att nedgraderas till en äldre version som har säkerhetsproblem." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Kör ”fwupdmgr get-upgrades” för vidare information." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr sync` to complete this action." msgstr "Kör `fwupdmgr sync` för att slutföra denna åtgärd." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Kör uppstädningssammansättningsrutin för insticksmodulen när install-blob används" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Kör förberedelsesammansättningsrutin för insticksmodulen när install-blob används" #. TRANSLATORS: command description msgid "Run the post-reboot cleanup action" msgstr "Kör upprensningsåtgärden efter uppstart" #. TRANSLATORS: tell a user how to get information #, c-format msgid "Run without '%s' to see" msgstr "Kör utan ”%s” för att se" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "Kärnan som körs är för gammal" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Exekveringssuffix" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "INSTÄLLNING VÄRDE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING1 VALUE1 [SETTING2] [VALUE2]" msgstr "INSTÄLLNING1 VÄRDE1 [INSTÄLLNING2] [VÄRDE2]" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "SPI BIOS-beskrivare" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "SPI BIOS-region" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI lås" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "SPI-återuppspelningsskydd" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI skriv" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "SPI-skrivskydd" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "UNDERSYSTEM DRIVRUTIN [ENHETS-ID|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Spara en fil som möjliggör generering av hårdvaru-ID:n" #. TRANSLATORS: command description msgid "Save device emulation data" msgstr "Spara enhetens emuleringsdata" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "Skalär ökning" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Schemalägg om möjligt installationen till nästa uppstart" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Schemalägger…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Secure Boot inaktiverad" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Secure Boot aktiverad" msgid "Security hardening for HSI" msgstr "Säkerhetsskärpning för HSI" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Se %s för mer detaljer." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Se %s för mer information." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Vald enhet" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Vald volym" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Serienummer" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "Ställde in BIOS-inställningen ”%s” till ”%s”." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "Ställ in en BIOS-inställning" msgid "Set one or more BIOS settings" msgstr "Ställ in en eller flera BIOS-inställningar" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Ställ in felsökningsflaggan under uppdatering" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "Ställer in en eller flera BIOS-inställningar" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Sätter listan av godkänd fast programvara" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Inställningstyp" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Inställningar kommer börja gälla efter systemet startats om" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Dela historik för fast programvara med utvecklarna" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Visa alla resultat" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Visa klient- och demon-version" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Visa utförlig information från demonen för en specifik domän" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Visa felsökningsinformation för alla domäner" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Visa felsökningsalternativ" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Visa enheter som inte kan uppdateras" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Visa extra felsökningsinformation" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Visa historik över uppdateringar för fast programvara" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Visa den beräknade versionen av dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Visa felsökningsloggen från det senaste uppdateringsförsöket" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Visa information om uppdateringsstatus för fast programvara" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Stäng ner nu?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Signera en fast programvara med en ny nyckel" msgid "Sign data using the client certificate" msgstr "Signera data med klientcertifikatet" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Signera data med klientcertifikatet" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Signera skickade data med klientcertifikatet" msgid "Signature" msgstr "Signatur" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Signerad nyttolast" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Storlek" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Några av plattformshemligheterna kan göras ogiltiga när denna fasta programvara uppdateras." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Källa" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Ange Tillverkar-/Produkt-ID för DFU-enhet" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Ange dbx-databasfilen" msgid "Specify the number of bytes per USB transfer" msgstr "Ange antalet byte per USB-överföring" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "Sträng" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Slutfördes" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Aktiverade framgångsrikt alla enheter" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Inaktiverade framgångsrikt fjärrkälla" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Hämtade framgångsrikt ny metadata: " #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Aktiverade och uppdaterade framgångsrikt fjärrkälla" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Aktiverade framgångsrikt fjärrkälla" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Installerade framgångsrikt fast programvara" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Modifierade framgångsrikt konfigurationsvärde" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Modifierade framgångsrikt fjärrkälla" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Uppdaterade framgångsrikt metadata manuellt" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Uppdaterade framgångsrikt enhetskontrollsummor" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Skickade framgångsrikt %u rapport" msgstr[1] "Skickade framgångsrikt %u rapporter" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Verifierade framgångsrikt enhetskontrollsummor" #. TRANSLATORS: the device showed up in time #, c-format msgid "Successfully waited %.0fms for device" msgstr "Väntade %.0fms på enhet" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Sammanfattning" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Stöds" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "CPU som stöds" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Stöds på fjärrserver" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Gå till inaktivt läge" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Gå till vänteläge i RAM" #. TRANSLATORS: longer description msgid "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Gå till inaktivt läge låter enheten snabbt sova för att spara ström. När enheten är i vänteläge skulle dess minne fysiskt kunna plockas bort och dess information kommas åt." #. TRANSLATORS: longer description msgid "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Gå till vänteläge i RAM låter enheten snabbt sova för att spara ström. När enheten är i vänteläge skulle dess minne fysiskt kunna plockas bort och dess information kommas åt." #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Gå-till-inaktivt-läge" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Gå-till-vänteläge-i-ram" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Byt gren från %s till %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Byt gren för fast programvara på enheten" #. TRANSLATORS: command description msgid "Sync firmware versions to the chosen configuration" msgstr "Synkronisera versioner för fast programvara till den valda konfigurationen" #. TRANSLATORS: this CLI tool is now preventing system updates msgid "System Update Inhibited" msgstr "Systemuppdatering hindrad." #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "Systemet har för lite batteri för att utföra uppdateringen" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "Systemet har för lite batteri för att utföra uppdateringen (%u%%, behöver %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "Systemet kräver extern strömkälla" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "TEXT" #. TRANSLATORS: longer description msgid "TPM (Trusted Platform Module) is a computer chip that detects when hardware components have been tampered with." msgstr "TPM (Trusted Platform Module) är ett datorchip som upptäcker när hårdvarukomponenter har mixtrats med." #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "TPM PCR0-rekonstruktion" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "TPM PCR0-rekonstruktion är ogiltig" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "TPM PCR0-rekonstruktion är nu giltig" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "TPM-plattformskonfiguration" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "TPM-rekonstruktion" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "TPM har tomma PCR" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Tag" msgstr[1] "Taggar" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "Taggad för emulering" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Befläckad" msgid "Target" msgstr "Mål" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Testa en enhet med ett JSON-manifest" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Testad" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "Testad av %s" #. TRANSLATORS: someone we trust has tested this msgid "Tested by trusted vendor" msgstr "Testad av en betrodd leverantör" #. TRANSLATORS: longer description msgid "The Intel Management Engine Key Manifest must be valid so that the device firmware can be trusted by the CPU." msgstr "Nyckelmanifestet för Intel Management Engine måste vara giltigt så att enhetens fasta programvara kan vara betrodd av processorn." #. TRANSLATORS: longer description msgid "The Intel Management Engine controls device components and needs to have a recent version to avoid security issues." msgstr "Intel Management Engine kontrollerar enhetskomponenter och behöver ha en tillräckligt ny version för att undvika säkerhetsproblem." #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS är en fri tjänst som fungerar som en oberoende juridisk person och har ingen koppling till $OS_RELEASE:NAME$. Din distributör kanske inte har bekräftat att någon av uppdateringarna för fast programvara är kompatibla med ditt system eller anslutna enheter. All fast programvara tillhandahålls endast av den ursprungliga tillverkaren av utrustning." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Platform Configuration is used to check whether the device start process has been modified." msgstr "Plattformskonfigurationen för TPM (Trusted Platform Module) används för att kontrollera om enhetens startprocess har ändrats." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Reconstruction is used to check whether the device start process has been modified." msgstr "Rekonstruktionen för TPM (Trusted Platform Module) används för att kontrollera om enhetens startprocess har ändrats." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "TPM PCR0 skiljer sig från rekonstruktion." #. TRANSLATORS: longer description msgid "The UEFI Platform Key is used to determine if device software comes from a trusted source." msgstr "UEFI-plattformsnyckeln används för att avgöra om enhetsprogramvara kommer från en betrodd källa." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "Demonen har läst in tredjepartskod och stöds inte längre av uppströmsutvecklarna!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "Enhetsversionen matchade inte: fick %s, %s förväntades" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "Fast programvara från %s levereras inte av %s, hårdvarutillverkaren." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "Systemklockan har inte ställts in korrekt och hämtning av filer kan misslyckas." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been re-inserted." msgstr "Uppdateringen kommer fortsätta när enhetens USB-kabel har stoppats in igen." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged and then re-inserted." msgstr "Uppdateringen kommer fortsätta när enhetens USB-kabel har kopplats från och stoppats in igen." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged." msgstr "Uppdateringen kommer fortsätta när enhetens USB-kabel har kopplats från." #. TRANSLATORS: warning message msgid "The update will continue when the device power cable has been removed and re-inserted." msgstr "Uppdateringen kommer fortsätta när enhetens strömkabel har dragits ut och stoppats in igen." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "Tillverkaren tillhandahöll inte några kommentarer till utgåvan." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Det finns enheter med problem:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Det finns inga blockerade fast programvarufiler" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Det finns ingen godkänd fast programvara." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "Denna enhet kommer att återställas till %s när kommandot %s körs." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "Denna fasta programvara tillhandahålls av medlemmar av LVFS-gemenskapen och varken tillhandahålls (eller stöds) av den ursprungliga hårdvarutillverkaren." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Detta paket har inte validerats, det kanske inte fungerar korrekt." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Detta program kommer endast fungera korrekt som root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Denna fjärrkälla innehåller fast programvara som inte är under embargo, men fortfarande testas av hårdvarutillverkare. Du bör säkerställa att du har ett sätt att manuellt nedgradera den fasta programvaran om uppdateringen misslyckas." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "Detta system stöder inte inställningar för fast programvara" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Detta system har problem med HSI-exekvering." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Detta system har en låg HSI-säkerhetsnivå." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "Detta verktyg gör det möjligt för en administratör att tillämpa UEFI dbx-uppdateringar." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "Detta verktyg gör det möjligt för en administratör att felsöka en UpdateCapsule-operation." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Detta verktyg låter en administratör ställa frågor till och styra fwupd-demonen, vilket låter dem utföra åtgärder som att installera eller nedgradera fast programvara." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Detta verktyg tillåter en administratör att använda fwupd-insticksmoduler utan att dessa är installerade på värdsystemet." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can add a kernel argument of '%s', but it will only be active after restarting the computer." msgstr "Detta verktyg kan lägga till ett kärnargument ”%s”, men det kommer aktiveras först efter att datorn startats om." #. TRANSLATORS: the %1 is a BIOS setting name. #. * %2 and %3 are the values, e.g. "True" or "Windows10" #, c-format msgid "This tool can change the BIOS setting '%s' from '%s' to '%s' automatically, but it will only be active after restarting the computer." msgstr "Detta verktyg kan automatiskt ändra BIOS-inställningen ”%s” från ”%s” till ”%s”, men den kommer aktiveras först efter att datorn startats om." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can change the kernel argument from '%s' to '%s', but it will only be active after restarting the computer." msgstr "Detta verktyg kan ändra kärnargumentet från ”%s” till ”%s”, men det kommer aktiveras först efter att datorn startats om." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Detta verktyg kan endast användas av administratörsanvändaren" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Detta verktyg läser och analyserar TPM-händelseloggen från systemets fasta programvara." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Tillfälligt misslyckande" #. TRANSLATORS: item is TRUE msgid "True" msgstr "Sant" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Betrodda metadata" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Betrodd nyttolast" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Typ" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI Bootservice Variables" msgstr "UEFI-uppstartstjänstvariabler" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition may not be set up correctly" msgstr "UEFI ESP-partition kanske inte konfigurerats korrekt" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "UEFI ESP-partition upptäcktes inte eller är inte konfigurerad" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Verktyg för fast UEFI-programvara" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "UEFI-plattformsnyckel" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "UEFI säkerhetsstart" #. TRANSLATORS: longer description msgid "UEFI Secure Boot prevents malicious software from being loaded when the device starts." msgstr "UEFI säkerhetsstart förhindrar skadlig programvara från att läsas in när enheten startas." #. TRANSLATORS: longer description msgid "UEFI boot service variables should not be readable from runtime mode." msgstr "UEFI-uppstartstjänstvariabler får inte vara läsbara från körtidsläge." #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI bootservice variables" msgstr "UEFI-uppstartstjänstvariabler" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "UEFI-kapseluppdateringar är inte tillgängliga eller aktiverade i konfiguration för fast programvara" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "UEFI dbx-verktyg" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "Fast UEFI-programvara kan inte uppdateras i äldre BIOS-läge" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "UEFI-plattformsnyckel" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "UEFI säkerhetsstart" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Kunde inte ansluta till tjänst" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "Kunde inte hitta attribut" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Lösgör aktuell drivrutin" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Avblockera fast programvara:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Avblockerar en specifik fast programvara från att installeras" #. TRANSLATORS: command description msgid "Undo the host security attribute fix" msgstr "Ångra säkerhetsattributfixen på värden" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Okrypterad" #. TRANSLATORS: command description msgid "Uninhibit the system to allow upgrades" msgstr "Släpp systemets hindrande så uppgraderingar tillåts" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Okänd" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Okänd enhet" msgid "Unlock the device to allow access" msgstr "Lås upp enheten för att tillåta åtkomst" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Upplåst" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Låser upp enheten för fast programvaruåtkomst" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Avmonterar ESP" #. TRANSLATORS: message shown after device has been marked for emulation msgid "Unplug and replug the device to continue the update process." msgstr "Koppla ur och koppla in enheten igen för att fortsätta uppdateringsprocessen." #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Ta bort felsökningsflaggan under uppdatering" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Osignerad nyttolast" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Demonversion %s stöds inte, klientversionen är %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Obefläckad" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Uppdateringsbar" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Uppdateringsfel" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Uppdatera avbild" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Uppdateringsmeddelande" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Uppdateringstillstånd" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Misslyckad uppdatering är ett känt fel, besök denna URL för mer information:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Uppdatera nu?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Uppdatering kräver en omstart" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Uppdatera den lagrade kryptografiska hashen med aktuellt ROM-innehåll" msgid "Update the stored device verification information" msgstr "Uppdatera den lagrade enhetens verifikationsinformation" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Uppdatera lagrad metadata med aktuellt innehåll" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "Uppdaterar alla angivna enheter till senaste version av fast programvara, eller alla enheter om ej angivet" msgid "Updating" msgstr "Uppdaterar" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Uppdaterar %s från %s till %s… " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Uppdaterar %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Uppgradera %s från %s till %s?" #. TRANSLATORS: ask the user to share, %s is something #. * like: "Linux Vendor Firmware Service" #, c-format msgid "Upload these anonymous results to the %s to help other users?" msgstr "Skicka dessa anonyma resultat till %s för att hjälpa andra användare?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Att skicka upp rapporter för fast programvara hjälper hårdvarutillverkare att snabbt identifiera trasiga och fungerande uppdateringar på riktiga enheter." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Viktighet" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Använd %s för hjälp" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Använd CTRL^C för att avbryta." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Använd fwupdtool --help för hjälp" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Använd specialflaggor vid installation av fast programvara" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Användare har aviserats" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Användarnamn" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Giltig" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Validerar ESP-innehåll…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Variant" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Tillverkare" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Verifierar…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Version" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Version[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "VARNING" #. TRANSLATORS: command description msgid "Wait for a device to appear" msgstr "Vänta på att en enhet ska dyka upp" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Väntar…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Övervaka hårdvaruändringar" #. TRANSLATORS: check various UEFI and ACPI tables are unchanged after the #. update msgid "Will measure elements of system integrity around an update" msgstr "Kommer mäta element av systemintegritet vid en uppdatering" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Skriv fast programvara från fil till enhet" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Skriv fast programvara från fil till partition" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Skriver fil:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Skriver…" #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from a recovery or installation disk, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Du bör säkerställa att du är bekväm med att återställa inställningen från en återställnings- eller installationsdisk, då denna ändring kan få systemet att inte kunna starta Linux eller orsaka annan systeminstabilitet." #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from the system firmware setup, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Du bör säkerställa att du är bekväm med att återställa inställningen från systemets konfiguration för fast programvara, då denna ändring kan få systemet att inte kunna starta Linux eller orsaka annan systeminstabilitet." #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Din distributör kanske inte har verifierat någon av uppdateringarna av fast programvara för kompatibilitet med ditt system eller anslutna enheter." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Din hårdvara kan skadas med den här fasta programvaran och att installera denna utgåva kan ogiltigförklara all garanti hos %s." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "Ditt system är inställt till BKC för %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[APPSTREAM_ID]" msgstr "[APPSTREAM-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[KONTROLLSUMMA]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[ENHETS-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[ENHETS-ID|GUID] [GREN]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[ENHETS-ID|GUID] [VERSION]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE]" msgstr "[ENHET]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[FIL FILSIG FJÄRR-ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[FILNAMN1] [FILNAMN2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[ORSAK]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[INSTÄLLNING1] [ INSTÄLLNING2]…" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[INSTÄLLNING1] [INSTÄLLNING2] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SMBIOS-FIL|HWIDS-FIL]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "standard" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "fwupd TPM-händelseloggverktyg" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd-insticksmoduler" fwupd-1.9.16/po/test-deps000077500000000000000000000030061460375044200152150ustar00rootroot00000000000000#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-2.1+ """ Check dependencies needed for rasterization """ import sys import os err = [] try: import gi except ImportError: err.append("missing dependency python gobject introspection (python3-gi)") try: gi.require_version("Pango", "1.0") from gi.repository import Pango except NameError: pass except ValueError: err.append("missing pango gobject introspection library") try: gi.require_version("PangoCairo", "1.0") from gi.repository import PangoCairo except NameError: pass except ValueError: err.append("missing pangocairo gobject introspection library") try: gi.require_version("cairo", "1.0") from gi.repository import cairo except NameError: pass except ValueError: err.append("missing cairo gobject introspection library") try: import cairo except NameError: pass except ImportError: err.append("missing dependency python cairo (python3-cairo)") # check that LINUGAS lists every language with a .po file try: linguas_fn = sys.argv[1] except IndexError: linguas_fn = open("po/LINGUAS") with open(linguas_fn) as f: langs = f.read().splitlines() for root, dirs, files in os.walk("po"): for file in files: if not file.endswith(".po"): continue l = file.split(".po") if len(l) > 1 and not l[0] in langs: err = 1 err.append("missing translations for %s" % l[0]) for msg in err: print(f"Error: {msg}", file=sys.stderr) sys.exit(len(err)) fwupd-1.9.16/po/tr.po000066400000000000000000001432331460375044200143530ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Emin Tufan Çetin , 2020 # Muhammet Kara , 2016 # Sabri Ünal , 2019-2020,2022-2023 # Sabri Ünal , 2020 # Serdar Sağlam , 2020 # yunus kaba , 2021 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Turkish (http://app.transifex.com/freedesktop/fwupd/language/tr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: tr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "%.0f dakika kaldı" msgstr[1] "%.0f dakika kaldı" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s Tüketici ME Güncellemesi" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s Denetleyici Güncellemesi" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s Kurumsal ME Güncellemesi" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s Aygıt Güncellemesi" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s Gömülü Denetleyici Güncellemesi" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s ME Güncellemesi" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s Sistem Güncellemesi" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s Thunderbolt Denetleyici Güncellemesi" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s Güncellemesi" #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "%s, hasar oluşmaması için güncelleme süresince güç kaynağına bağlı kalmalıdır." #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s sürümü" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u gün" msgstr[1] "%u gün" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u aygıtın donanım yazılımı yükseltmesi var." msgstr[1] "%u aygıtın donanım yazılımı yükseltmesi var." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u saat" msgstr[1] "%u saat" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u yerel cihaz destekleniyor" msgstr[1] "%u yerel aygıt destekleniyor" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u dakika" msgstr[1] "%u dakika" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u saniye" msgstr[1] "%u saniye" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Etkin aygıtlar" msgid "Activate the new firmware on the device" msgstr "Aygıttaki yeni donanım yazılımını etkinleştir" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Donanım yazılımı güncellemesini etkinleştir" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Yaş" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Takma ad %s" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Donanım yazılımı sürümünün düşürülmesine izin ver" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Varolan donanım yazılımı sürümlerinin yeniden kurulumuna izin ver" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Diğer kollara geçiş için izin verin" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Güncelleme işlemi için yeniden başlatma gerekir." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Güncelleme işlemi için sistemin kapanması gerekir." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Tüm sorulara evet yanıtı ver" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Donanım yazılımı güncellemelerini uygula" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Güncelleme dosyalarını uygula" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Onaylı ürün yazılımı:" msgstr[1] "Onaylı donanım yazılımı:" #. TRANSLATORS: command description msgid "Asks the daemon to quit" msgstr "Art alan hizmetinden çıkmasını ister" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Kimlik doğrulanıyor…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Çıkarılabilir aygıt üzerindeki donanım yazılımının sürümünü düşürmek için kimlik doğrulama gerekir" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Bu makine üzerindeki donanım yazılımının sürümünü düşürmek için kimlik doğrulama gerekir" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Donanım yazılımı güncellemeleri için kullanılan uzak yapılandırmayı değiştirmek için kimlik doğrulama gerekir" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Art alan hizmeti yapılandırmasını değiştirmek için kimlik doğrulama gerekir" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Onaylı donanım yazılımı listesini ayarlamak için kimlik doğrulama gerekir" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "İstemci sertifikasını kullanarak veri imzası için kimlik doğrulama gerekir" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Bu makine üzerindeki donanım yazılımını güncellemek için kimlik doğrulama gerekir" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Bir aygıtın kilidini açmak için kimlik doğrulama gerekir" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Çıkarılabilir aygıt üzerindeki donanım yazılımını güncellemek için kimlik doğrulama gerekir" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Bu makine üzerindeki donanım yazılımını güncellemek için kimlik doğrulama gerekir" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Aygıtın saklanan sağlama toplamlarını güncellemek için kimlik doğrulama gerekir" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Otomatik Raporla" msgid "BYTES" msgstr "BYTES" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Belirli bir donanım yazılımının kurulmasını engeller" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Önyükleyici Sürümü" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "İptal" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "İptal Edildi" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Değişti" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Sağlama Toplamı" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Dal seç" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Aygıt seç" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Donanım yazılımı seç" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Bölüm seç" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Son güncellemenin sonuçlarını temizler" #. TRANSLATORS: error message msgid "Command not found" msgstr "Komut bulunamadı" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Kritik" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Var olan sürüm" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU Yardımcı Programı" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Hata Ayıklama Seçenekleri" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Açılıyor…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Açıklama" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Ayrıntılar" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Aygıt Bayrakları" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Aygıt Kimliği" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Aygıt eklendi:" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "Aygıtın pil gücü çok düşük" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Aygıt değişti:" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Aygıt kilitli" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Aygıt sağlanan tüm sürümlerin kurulumunu gerektiriyor" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Aygıta erişilemiyor" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Aygıt çıkarıldı:" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Aygıt güncelleme yöntemi" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Aygıt güncellemesinin etkinleştirilmesi gerekiyor" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Aygıt, kurulumdan önce donanım yazılımını yedekleyecek" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Donanım yazılımı güncellemesi olmayan aygıtlar:" #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Var olan en son donanım yazılımı sürümüne sahip aygıtlar:" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Devredışı" msgid "Disabled fwupdate debugging" msgstr "fwupdate hata ayıklama devre dışı bırakıldı" #. TRANSLATORS: command line option msgid "Display version" msgstr "Ekran sürümü" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Eski üst verileri kontrol etme" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Bildirilmeyen geçmişi kontrol etme" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Günlük etki alanı öneki eklemeyin" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Zaman damgası öneki eklemeyin" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Aygıt güvenlik denetimlerini yapma" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Geçmişi veri tabanına yazma" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Tamamlandı!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Bir aygıttaki üretici yazılımı sürümünü düşürür" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr " %s, %s sürümünden %s sürümüne düşürülüyor... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "%s sürümü düşürülüyor…" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "İndiriliyor…" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Süre" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Belirtilen ESP geçerli değil" msgid "Enable" msgstr "Etkinleştir" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Desteklenen sistemlerde donanım yazılımı güncelleme desteğini etkinleştir" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Bu uzaktan kumanda etkinleştirilsin mi?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Etkinleştirildi" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Bu işlevselliğin etkinleştirilmesi kendi sorumluluğunuzdadır, bu güncellemelerin neden olduğu sorunlar için orijinal ekipman üreticinize başvurmanız gerektiği anlamına gelir. Yalnızca güncelleme işleminin kendisiyle ilgili sorunlar şu adresten yapılmalıdır $OS_RELEASE:BUG_REPORT_URL$." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Bu uzaktan kumandayı etkinleştirmek kendi sorumluluğunuzdadır." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Şifrelenmiş" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Tüm donanım yazılımı güncelleme geçmişini sil" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Siliniyor…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Küçük bir gecikme sonrası çık" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "FILE" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "FILENAME" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "DOSYAADI AYGIT-KİMLİĞİ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "DOSYAADI [AYGIT-KİMLİĞİ|GUID]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Başarısız" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Güncelleme uygulanamadı" #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Art alan hizmetine bağlanamadı" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Bekleyen aygıtlar alınamadı" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Donanım yazılımı güncellemesi kurulamadı" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Yerel dbx yüklenemedi" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Tuhaflıklar yüklenemedi" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Sistem dbx yüklenemedi" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Kilitleme başarısız." #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Bağımsız değişkenler ayrıştırılamadı" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Dosya ayrıştırılamadı" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Yerel dbx ayrıştırılamadı" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Yeniden başlatılamadı" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Sıçrama kipi ayarlanamadı" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Dosya adı" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Dosya Adı İmzası" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Dosya adı gerekli" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Donanım Yazılımı Temel URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Donanım Yazılımı Güncelleme D-Bus Hizmeti" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Donanım Yazılımı Güncelleme Art Alan Hizmeti" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Donanım Yazılımı Güncellemeleri" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Donanım Yazılımı Yardımcı Programı" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Donanım yazılımı güncellemeleri" msgid "Firmware updates are not supported on this machine." msgstr "Donanım yazılımı güncellemeleri bu makinede desteklenmiyor." msgid "Firmware updates are supported on this machine." msgstr "Donanım yazılımı güncellemeleri bu makinede destekleniyor." #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Bayraklar" msgid "Force the action ignoring all warnings" msgstr "Eylemi tüm uyarıları görmezden gelmeye zorla" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Bulundu" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUIDs" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" msgid "Get BIOS settings" msgstr "BIOS ayarlarını getir" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "fwupd tarafından desteklenen tüm aygıt bayraklarını getir" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Etkinleştirilmiş tüm eklentileri sisteme kaydettir" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Donanım yazılımı dosyasıyla ilgili ayrıntıları al" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Bağlı donanım için güncelleme listesini al" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Bir aygıt için sürümleri alır" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Son güncellemeden sonuçları alır" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWIDS-FILE" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Donanım yeniden takılmayı bekliyor" #. TRANSLATORS: the release urgency msgid "High" msgstr "Yüksek" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Host Güvenlik Kimliği:" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Boşta…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Dosyaları indirirken katı SSL kontrollerini yoksay" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Kurulum Süresi" #. TRANSLATORS: command description msgid "Install a specific firmware on a device, all possible devices will also be installed once the CAB matches" msgstr "Bir aygıta belirli bir donanım yazılımı kur, CAB eşleştiğinde olası tüm aygıtlar da kurulacak" msgid "Install old version of signed system firmware" msgstr "İmzalı sistem donanım yazılımının eski sürümünü kur" msgid "Install old version of unsigned system firmware" msgstr "İmzasız sistem donanım yazılımının eski sürümünü kur" msgid "Install signed device firmware" msgstr "İmzalı aygıt donanım yazılımını kur" msgid "Install signed system firmware" msgstr "İmzalı sistem donanım yazılımını kur" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Önce ana aygıta kurun" msgid "Install unsigned device firmware" msgstr "İmzasız aygıt donanım yazılımını kur" msgid "Install unsigned system firmware" msgstr "İmzasız sistem donanım yazılımını kur" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Donanım Yazılımı Kuruluyor…" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Donanım yazılımını güncellemesi kuruluyor…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Kuruluyor %s…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "Bu güncellemenin kurulması, herhangi bir aygıt garantisini de geçersiz kılabilir." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET Etkin" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET Etkinleştirildi" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Dahili aygıt" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Geçersiz" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Yükletme" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Sorunlar" msgstr[1] "Sorunlar" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "ANAHTAR,DEĞER" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Anahtarlık" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Son değiştirilme" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Bir dakikadan daha az kaldı" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Lisans" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Linux Takas" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux Tedarikçi Donanım Yazılımı Hizmeti (kararlı donanım yazılımı)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux Tedarikçi Donanım Yazılımı Hizmeti (test donanım yazılımı)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Linux kernel" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Linux kernel lockdown" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linux swap" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "dbx içindeki girdileri listele" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Desteklenen donanım yazılımı güncellemelerini listele" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Kullanılabilir donanım yazılımı türlerini listele" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Yükleniyor…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Kilitli" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Düşük" msgid "MEI version" msgstr "MEI sürümü" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Orta" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Üst Veri İmzası" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Üst veri URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Üst veri Linux Tedarikçi Donanım Yazılımı Hizmetinden edinilebilir" #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Asgari Sürüm" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Art alan hizmeti ile istemci uyuşmadı, bunun yerine %s kullanın" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Bir art alan hizmeti yapılandırma değerini değiştirir" msgid "Modify a configured remote" msgstr "Uzak yapılandırmayı değiştir" msgid "Modify daemon configuration" msgstr "Art alan hizmeti yapılandırmasını değiştir" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Olaylar için art alan hizmetini gözetle" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Kurulumdan sonra yeniden başlatma gerekiyor" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Kurulumdan sonra kapatma gerekiyor" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Yeni sürüm" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Eylem belirtilmedi!" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Donanım yazılımı kimlik bilgisi bulunamadı" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "Donanım yazılımı bulunamadı" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Donanım yazılımı güncelleme yeteneğine sahip donanım saptanamadı" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Eklenti bulunamadı" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Sürüm bulunamadı" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Şu anda uzaktan kumanda etkin değil, bu nedenle üst veri yok." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Uzaktan kumanda bulunamadı" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Güncellenebilir aygıt yok" msgid "No updates available for remaining devices" msgstr "Kalan aygıtlar için güncelleme yok" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Güncelleme uygulanmadı" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Bulunamadı" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Desteklenmiyor" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "Tamam" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Yalnızca tek PCR değerini göster" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Sadece sürüm yükseltmelerine izin veriliyor" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "JSON formatında dışarı aktar" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Öntanımlı ESP yolunu geçersiz kıl" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "YOL" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Donanım yazılımı dosyası hakkındaki ayrıntıları ayrıştır ve göster" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "dbx güncellemesi ayrıştırılıyor…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Sistem dbx ayrıştırılıyor…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Parola" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Yüzde tamamlandı" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Önceki sürüm" msgid "Print the version number" msgstr "Sürüm numarasını yazdır" msgid "Print verbose debug statements" msgstr "Ayrıntılı hata ayıklama ifadelerini yazdır" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Öncelik" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Sahipli" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Yazılım güncelleme desteği için sorgu" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "REMOTE-ID" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Donanım yazılımını aygıttan dosyaya oku" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Donanım yazılımını bölümden dosyaya oku" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Şuradan okunuyor %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Okunuyor…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Yeniden Başlatılıyor…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Üst veriyi uzak sunucudan yenile" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr " %s %s yeniden kurulsun mu?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Var olan donanım yazılımını aygıta yeniden kur" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Donanım yazılımını aygıta yeniden kur" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr " %s %s yeniden kuruluyor..." #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Uzak Kimlik" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Varolan bir üretici yazılımı dosyasındaki verileri değiştir" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Rapor URI" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Uzak sunucuya bildirildi" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "İstek iptal edildi" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "İnternet bağlantısı gerektirir" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Yeniden başlatılsın mı?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Değişikliklerin etkili olması için art alan hizmeti yeniden başlatılsın mı?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Aygıt yeniden başlatılıyor…" #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Daha çok bilgi için `fwupdmgr get-upgrades` çalıştırın." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "install-blob kullanırken eklenti bileşik temizleme yordamını çalıştır" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "install-blob kullanırken eklenti bileşik hazırlama yordamını çalıştır" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI kilidi" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "ALTSİSTEM SÜRÜCÜ [AYGIT-KİMLİĞİ|GUID]" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Mümkün olduğunda sonraki yeniden başlatma için kurulumu zamanla" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Zamanlanıyor…" #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Daha fazlası için tıklayınız. %s " #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Seçili aygıt" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Bölüm seçildi" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Seri Numarası" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Güncelleme sırasında hata ayıklama bayrağını ayarla" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Onaylı donanım yazılımı listesini ayarlar" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Donanım yazılım geçmişini geliştiricilerle paylaş" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Sonuçları göster" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "İstemci ve art alan hizmeti sürümlerini göster" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Belirli bir alan için art alan hizmeti ayrıntılı bilgilerini göster" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Tüm alan adları için hata ayıklama bilgilerini göster" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Hata ayıklama seçeneklerini göster" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Güncellenemeyen aygıtları göster" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Ek hata ayıklama bilgilerini göster" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Donanım yazılımı güncellemelerinin geçmişini göster" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Son denenen güncellemedeki hata ayıklama günlüğünü göster" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Donanım yazılımı güncelleme durumu bilgilerini göster" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Kapatılsın mı?" msgid "Sign data using the client certificate" msgstr "İstemci sertifikasını kullanarak veri imzala" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "İstemci sertifikasını kullanarak veri imzala" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Karşıya yüklenen verileri istemci sertifikasıyla imzala" msgid "Signature" msgstr "İmza" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Boyut" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Kaynak" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "DFU aygıtının Satıcısını/Ürün Kimliğini Belirle" msgid "Specify the number of bytes per USB transfer" msgstr "USB aktarımı başına bayt sayısını belirt" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Tüm aygıtlar başarıyla etkinleştirildi" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Yeni üst veri başarıyla indirildi:" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Donanım yazılımı başarıyla kuruldu" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Yapılandırma değeri başarıyla değiştirildi" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Üst veri başarıyla yenilendi" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Aygıt sağlama toplamları başarıyla güncellendi" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Aygıt sağlaması toplamı başarıyla doğrulandı" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Özet" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Destekleniyor" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "Desteklenen İşlemci" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Uzak sunucuda desteklenir" #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Suspend-to-idle" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Suspend-to-ram" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "METİN" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "TPM PCR0 reconstruction" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" msgid "Target" msgstr "Hedef" #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "Art alan hizmeti 3. taraf kodu yükledi ve artık upstream geliştiricileri tarafından desteklenmiyor!" #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Sorunlu aygıtlar var:" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Onaylı donanım yazılımı yok." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Bu program yalnızca kök erişimi ile düzgün çalışabilir" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "Bu yazılım ambargo edilmemiş, ancak donanım satıcısı tarafından test edilmekte olan bir donanım yazılımı içerir. Donanım yazılımı güncellemesi başarısız olursa, donanım yazılımını elle düşürmenin bir yoluna sahip olmanız gerekir." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "Bu araç, yöneticinin fwupd art alan hizmetini sorgulamasına ve denetlemesine izin vererek, yöneticinin donanım yazılımı kurulması ya da düşürülmesi gibi eylemleri gerçekleştirmesini sağlar." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "Bu araç, bir yöneticinin ana sisteme kurulmadan fwupd eklentilerini kullanmasına izin verir." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Bu araç yalnızca kök kullanıcı tarafından kullanılabilir" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Tür" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI Donanım Yazılımı Yardımcı Programı" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "UEFI Platform Anahtarı" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "UEFI dbx Utility" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "UEFI platform key" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "UEFI secure boot" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Belirli bir donanım yazılımının kurulması engelini kaldırır" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Şifrelenmemiş" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Bilinmeyen" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Bilinmeyen Aygıt" msgid "Unlock the device to allow access" msgstr "Erişime izin vermek için aygıtın kilidini açın" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Serbest" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Aygıt yazılımı erişimi için aygıt kilidini açar" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Güncelleme sırasında hata ayıklama bayrağını ayarlama" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Desteklenmeyen art alan hizmeti sürümü %s, istemci sürümü %s" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Güncellenebilir" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Güncelleme Hatası" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Güncelleme İletisi" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Güncelleme Durumu" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Güncelleme hatası bilinen bir sorundur, daha fazla bilgi için bu URL'yi ziyaret edin:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Şimdi güncellensin mi?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Güncelleme için yeniden başlatma gerekli" msgid "Update the stored device verification information" msgstr "Saklanan aygıt doğrulama bilgilerini güncelle" #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Güncelleniyor %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr " %s, %s sürümünden %s sürümüne yükseltilsin mi?" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Donanım yazılımı kurulurken quirk bayraklarını kullan" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Kullanıcı bilgilendirildi" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Kullanıcı adı" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Geçerli" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "ESP içerikleri doğrulanıyor…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Türev" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Üretici" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Doğrulanıyor…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Sürüm" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "UYARI" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Bekliyor…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Donanım değişikliklerini izle" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Donanım yazılımını dosyadan aygıta yaz" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Donanım yazılımını dosyadan bölüme yaz" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Dosyaya yazılıyor:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Yazılıyor…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Hizmet sağlayıcınız, sisteminizle veya bağlı aygıtlarla uyumluluk için herhangi bir donanım yazılımı güncellemesini doğrulamamış olabilir." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Donanımınız bu donanım yazılımı kullanılarak zarar görebilir ve bu sürümün kurulması, %sile ilgili tüm garantileri geçersiz kılabilir." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[DEVICE-ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[AYGIT-KİMLİĞİ|GUID] [DAL]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[AYGIT-KİMLİĞİ|GUID] [SÜRÜM]" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "fwupd TPM olay günlüğü yardımcı aracı" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd eklentileri" fwupd-1.9.16/po/uk.po000066400000000000000000004547351460375044200143610ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Yuri Chornoivan , 2015-2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Ukrainian (http://app.transifex.com/freedesktop/fwupd/language/uk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: uk\n" "Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "Лишилася %.0f хвилина" msgstr[1] "Лишилося %.0f хвилини" msgstr[2] "Лишилося %.0f хвилин" msgstr[3] "Лишилася %.0f хвилина" #. TRANSLATORS: BMC refers to baseboard management controller which #. * is the device that updates all the other firmware on the system #, c-format msgid "%s BMC Update" msgstr "Оновлення BMC %s" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "Оновлення для акумуляторів %s" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "Оновлення мікропрограми процесора %s" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "Оновлення для камери %s" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "Оновлення налаштувань %s" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "Оновлення %s Consumer ME" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "Оновлення для контролера %s" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "Оновлення %s Corporate ME" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "Оновлення для пристрою %s" #. TRANSLATORS: Video Display refers to the laptop internal display or #. * external monitor #, c-format msgid "%s Display Update" msgstr "Оновлення дисплея %s" #. TRANSLATORS: Dock refers to the port replicator hardware laptops are #. * cradled in, or lowered onto #, c-format msgid "%s Dock Update" msgstr "Оновлення станції %s" #. TRANSLATORS: drive refers to a storage device, e.g. SATA disk #, c-format msgid "%s Drive Update" msgstr "Оновлення для диска %s" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "Оновлення для вбудованого контролера %s" #. TRANSLATORS: a device that can read your fingerprint pattern #, c-format msgid "%s Fingerprint Reader Update" msgstr "Оновлення зчитувача відбитків %s" #. TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC #, c-format msgid "%s Flash Drive Update" msgstr "Оновлення для флеш-диска %s" #. TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. #. * the "video card" #, c-format msgid "%s GPU Update" msgstr "Оновлення для відеокартки %s" #. TRANSLATORS: a large pressure-sensitive drawing area typically used #. * by artists and digital artists #, c-format msgid "%s Graphics Tablet Update" msgstr "Оновлення графічного планшета %s" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "Оновлення для клавіатури %s" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "Оновлення ME %s" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "Оновлення для миші %s" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "Оновлення інтерфейсу мережі %s" #. TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating #. * SATA or NVMe disk #, c-format msgid "%s SSD Update" msgstr "Оновлення для SSD %s" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "Оновлення контролера сховища даних %s" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "Оновлення системи %s" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "Оновлення для TPM %s" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "Оновлення контролера Thunderbolt %s" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "Оновлення для сенсорної панелі %s" #. TRANSLATORS: Dock refers to the port replicator device connected #. * by plugging in a USB cable -- which may or may not also provide power #, c-format msgid "%s USB Dock Update" msgstr "Оновлення станції USB %s" #. TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth #. * device that stays in the USB port so the wireless peripheral works #, c-format msgid "%s USB Receiver Update" msgstr "Оновлення USB-приймача %s" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "Оновлення для %s" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "%s і усі з'єднані пристрою можуть бути недоступними протягом оновлення." #. TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s appeared: %s" msgstr "З'явився %s: %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform #. key". #. * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" #, c-format msgid "%s changed: %s → %s" msgstr "Змінено %s: %s → %s" #. TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS #. region". #. %2 refers to a result value, e.g. "Invalid" #, c-format msgid "%s disappeared: %s" msgstr "Зник %s: %s" #. TRANSLATORS: the device has a reason it can't update, e.g. laptop lid #. closed #, c-format msgid "%s is not currently updatable" msgstr "%s зараз непридатний до оновлення" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "Режим виробництва %s" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "Для уникнення пошкоджень %s має лишатися з'єднаним із комп'ютером протягом оновлення." #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "Для уникнення пошкоджень %s має лишатися з'єднаним із джерелом живлення протягом оновлення." #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "Перевизначення %s" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s, версія %s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "Версія %s" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u день" msgstr[1] "%u дні" msgstr[2] "%u днів" msgstr[3] "%u день" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "Для %u пристрою випущено оновлення мікропрограми." msgstr[1] "Для %u пристроїв випущено оновлення мікропрограм." msgstr[2] "Для %u пристроїв випущено оновлення мікропрограм." msgstr[3] "Для %u пристрою випущено оновлення мікропрограм." #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device is not the best known configuration." msgid_plural "%u devices are not the best known configuration." msgstr[0] "%uпристрій не є найкращою відомою конфігурацією." msgstr[1] "%u пристрої не є найкращою відомою конфігурацією." msgstr[2] "%u пристрої не є найкращою відомою конфігурацією." msgstr[3] "%u пристрій не є найкращою відомою конфігурацією." #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u година" msgstr[1] "%u години" msgstr[2] "%u годин" msgstr[3] "%u година" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "Передбачено підтримку %u локального пристрою" msgstr[1] "Передбачено підтримку %u локальних пристроїв" msgstr[2] "Передбачено підтримку %u локальних пристроїв" msgstr[3] "Передбачено підтримку %u локального пристрою" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u хвилина" msgstr[1] "%u хвилини" msgstr[2] "%u хвилин" msgstr[3] "%u хвилина" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u секунда" msgstr[1] "%u секунди" msgstr[2] "%u секунд" msgstr[3] "%u секунда" #. TRANSLATORS: first percentage is current value, 2nd percentage is the #. * lowest limit the firmware update is allowed for the update to happen #, c-format msgid "%u%% (threshold %u%%)" msgstr "%u%% (порогове значення — %u%%)" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(є застарілим)" #. TRANSLATORS: HSI event title msgid "A TPM PCR is now an invalid value" msgstr "PCR TPM тепер має некоректне значення" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "AMD Firmware Replay Protection" msgstr "Захист відтворення мікропрограми AMD" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "AMD Firmware Write Protection" msgstr "Захист від запису мікропрограми AMD" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "AMD Secure Processor Rollback Protection" msgstr "Захист від відновлення попередніх станів процесора AMD" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "ARCHIVE FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATFILE]" msgstr "АРХІВ МІКРОПРОГРАМА МЕТАІНФО [МІКРОПРОГРАМА] [МЕТАІНФО] [ФАЙЛJCAT]" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "Потрібна дія:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "Задіяти пристрої" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "Задіяти пристрої у черзі очікування" msgid "Activate the new firmware on the device" msgstr "Активація нової мікропрограми на пристрої" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "Активуємо оновлення мікропрограми" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "Активуємо оновлення мікропрограми для" #. TRANSLATORS: command description msgid "Adds devices to watch for future emulation" msgstr "Додає пристрої для спостереження щодо майбутньої емуляції" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "Вік" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "Згодні, увімкнути віддалене сховище?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "Інша назва %s" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are now valid" msgstr "Тепер усі PCR TPM є коректними" #. TRANSLATORS: HSI event title msgid "All TPM PCRs are valid" msgstr "Усі PCR TPM є коректними" #. TRANSLATORS: an application is preventing system updates msgid "All devices are prevented from update by system inhibit" msgstr "Оновлення на усіх пристроях заборонено системою" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "Усі пристрої одного типу буде оновлено одночасно" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "Дозволити зниження версій мікропрограми" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "Дозволити перевстановлення наявних версій мікропрограми" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "Дозволити перемикання гілок мікропрограми" #. TRANSLATORS: is not the main firmware stream msgid "Alternate branch" msgstr "Альтернативна гілка" #. TRANSLATORS: another application is updating the device already msgid "An update is in progress" msgstr "Виконуємо оновлення" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "Для завершення оновлення слід перезавантажити систему." #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "Для завершення оновлення систему слід вимкнути." #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "Відповідати «так» на усі питання" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "Застосувати оновлення мікропрограми" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "Застосувати оновлення, навіть якщо воно не є рекомендованим" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "Застосувати файли оновлень" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "Застосовуємо оновлення…" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "Схвалена мікропрограма:" msgstr[1] "Схвалені мікропрограми:" msgstr[2] "Схвалені мікропрограми:" msgstr[3] "Схвалена мікропрограма:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "Питати знову наступного разу?" #. TRANSLATORS: command description msgid "Asks the daemon to quit" msgstr "Надсилає фоновій службі запит щодо виходу" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "Долучитися до режиму мікропрограми" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "Проходимо розпізнавання…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "Потрібні дані для розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "Для встановлення застарілої версії мікропрограми на портативний пристрій слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "Для встановлення застарілої версії мікропрограми на цей комп’ютер слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to fix a host security issue" msgstr "Для виправлення проблеми із захистом слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify BIOS settings" msgstr "Для внесення змін до атрибутів BIOS слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "Для внесення змін до записів налаштованих віддалених пристроїв, які використовуються для оновлення мікропрограм, слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "Для внесення змін до налаштувань фонової служби слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to read BIOS settings" msgstr "Для читання параметрів BIOS слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "Щоб встановити список схвалених мікропрограм, вам слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "Щоб підписати дані за допомогою клієнтського сертифіката, вам слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "Щоб перемкнутися на нову версію мікропрограми, вам слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to undo the fix for a host security issue" msgstr "Для скасування виправлення проблем із захистом вузла слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "Щоб розблокувати пристрій, слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "Для оновлення мікропрограми на портативному пристрої слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "Щоб отримати доступ до оновлення мікропрограми цього комп’ютера, вам слід пройти розпізнавання" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "Щоб отримати доступ до оновлення збережених контрольних сум для пристрою, вам слід пройти розпізнавання" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "Автоматичне звітування" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "Автоматично вивантажувати кожного разу?" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS Firmware Updates" msgstr "Оновлення мікропрограми BIOS" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS Rollback Protection" msgstr "Захист від відновлення попередніх станів BIOS" #. TRANSLATORS: Title: Whether BIOS Firmware updates is enabled msgid "BIOS firmware updates" msgstr "Оновлення мікропрограми BIOS" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "BIOS rollback protection" msgstr "Захист від відновлення попередніх станів BIOS" #. TRANSLATORS: description of a BIOS setting msgid "BIOS updates delivered via LVFS or Windows Update" msgstr "Оновлення BIOS, які надано за допомогою LVFS або Windows Update" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "XML-ЗБИРАННЯ НАЗВА-ФАЙЛА-ДЖ" msgid "BYTES" msgstr "БАЙТИ" #. TRANSLATORS: refers to the battery inside the peripheral device msgid "Battery" msgstr "Акумулятор" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "Пов'язати новий драйвер ядра" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "Заблоковані файли мікропрограм:" #. TRANSLATORS: version cannot be installed due to policy msgid "Blocked version" msgstr "Заблокована версія" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "Блокуємо мікропрограму:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "Блокує встановлення певної мікропрограми" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "Версія завантажувача" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "Гілка" #. TRANSLATORS: command description msgid "Build a cabinet archive from a firmware blob and XML metadata" msgstr "Зібрати архів cab з бінарної мікропрограми та метаданих XML" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "Зібрати файл мікропрограми" #. TRANSLATORS: longer description msgid "CPU Microcode must be updated to mitigate against various information-disclosure security issues." msgstr "Для боротьби з різноманітними проблемами розкриття даних має бути оновлено мікрокод процесора." #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "Скасувати" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "Скасовано" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "Не вдалося застосувати, оскільки оновлення dbx вже застосовано." #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "Не можна застосовувати оновлення до портативного носія" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "Змінено" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "Перевірити відповідність криптографічних хешів мікропрограми" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "Контрольна сума" #. TRANSLATORS: get interactive prompt, where branch is the #. * supplier of the firmware, e.g. "non-free" or "free" msgid "Choose branch" msgstr "Виберіть гілку" #. TRANSLATORS: get interactive prompt msgid "Choose device" msgstr "Виберіть пристрій" #. TRANSLATORS: get interactive prompt msgid "Choose firmware" msgstr "Виберіть мікропрограму" #. TRANSLATORS: get interactive prompt msgid "Choose release" msgstr "Виберіть випуск" #. TRANSLATORS: get interactive prompt msgid "Choose the ESP:" msgstr "Виберіть ESP:" #. TRANSLATORS: get interactive prompt msgid "Choose volume" msgstr "Виберіть том" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "Вилучає результати останнього оновлення" #. TRANSLATORS: error message msgid "Command not found" msgstr "Такої команди не знайдено" #. TRANSLATORS: is not supported by the vendor msgid "Community supported" msgstr "Підтримка спільноти" #. TRANSLATORS: title prefix for the BIOS settings dialog msgid "Configuration Change Suggested" msgstr "Запропоновано зміни у налаштуваннях" #. TRANSLATORS: no peeking msgid "Configuration is only readable by the system administrator" msgstr "Налаштування можна читати лише від імені адміністратора системи" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "Перетворити файл мікропрограми" #. TRANSLATORS: when the update was built msgid "Created" msgstr "Створено" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "Критичний" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "Доступна перевірка криптографічної хеш-суми" #. TRANSLATORS: current value of a BIOS setting msgid "Current Value" msgstr "Поточне значення" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "Поточна версія" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "ІД_ПРИСТРОЮ|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "Засіб роботи з DFU" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "Параметри діагностики" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "Розпаковування…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "Опис" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "Від'єднатися до режиму завантажувача" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "Подробиці" #. TRANSLATORS: the best known configuration is a set of software that we know #. works #. * well together. In the OEM and ODM industries it is often called a BKC msgid "Deviate from the best known configuration?" msgstr "Вилучити з найкращої відомої конфігурації?" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "Прапорці пристрою" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "Ід. пристрою" #. TRANSLATORS: description of the device requests msgid "Device Requests" msgstr "Запити щодо пристрою" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "Додано пристрій:" #. TRANSLATORS: the device is already connected msgid "Device already exists" msgstr "Пристрій вже існує" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse msgid "Device battery power is too low" msgstr "Рівень заряду акумулятора пристрою є надто низьким" #. TRANSLATORS: for example the batteries *inside* the Bluetooth mouse #, c-format msgid "Device battery power is too low (%u%%, requires %u%%)" msgstr "Рівень заряду акумулятора пристрою є надто низьким (%u%%, має бути принаймні %u%%)" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "Пристрій може відновлюватися при невдалих оновленнях" #. TRANSLATORS: lid means "laptop top cover" msgid "Device cannot be used while the lid is closed" msgstr "Пристроєм не можна користуватися. доки закрито кришку" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "Змінено пристрій:" #. ability to load emulated devices is opt-in msgid "Device emulation is not enabled." msgstr "Емуляцію пристрою не увімкнено." #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "Для перевірки версії потрібна мікропрограма пристрою" #. TRANSLATORS: emulated means we are pretending to be a different model msgid "Device is emulated" msgstr "Емульований пристрій" #. TRANSLATORS: device cannot be interrupted, for instance taking a phone call msgid "Device is in use" msgstr "Пристрій використано сторонніми засобами" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "Пристрій заблоковано" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "Для встановлення усіх наданих випусків потрібен пристрій" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "Пристрій є недоступним" #. TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode msgid "Device is unreachable, or out of wireless range" msgstr "Пристрій є недоступним або перебуває поза досяжністю бездротового зв'язку" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "Пристроєм можна користуватися протягом оновлення" #. TRANSLATORS: usually this is when we're waiting for a reboot msgid "Device is waiting for the update to be applied" msgstr "Пристрій очікує на застосування оновлення" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "Вилучено пристрій:" #. TRANSLATORS: as in, wired mains power for a laptop msgid "Device requires AC power to be connected" msgstr "Пристрій потребує з'єднання із джерелом змінного струму" #. TRANSLATORS: device does not have a display connected msgid "Device requires a display to be plugged in" msgstr "Пристрій вимагає з'єднання з комп'ютером дисплея" #. TRANSLATORS: The device cannot be updated due to missing vendor's license." msgid "Device requires a software license to update" msgstr "Пристрій потребує оновлення ліцензування програмного забезпечення" #. TRANSLATORS: longer description msgid "Device software updates are provided for this device." msgstr "Для цього пристрою надаватимуться оновлення програмного забезпечення." #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "Пристрій із покроковим оновленням" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "У пристрої передбачено підтримку перемикання на іншу гілку мікропрограми" #. TRANSLATORS: command line option msgid "Device update method" msgstr "Спосіб оновлення для пристрою" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "Оновлення пристрою потребує активації" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "Пристрій створить резервну копію мікропрограми перед встановленням" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "Пристрій не з'явиться у списку пристроїв після завершення оновлення" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "Пристрої, для яких вдалося успішно оновити дані:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "Пристрої, для яких не вдалося оновити дані належним чином:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "Пристрої, для яких немає оновлень мікропрограми:" #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "Пристрої із найсвіжішою доступною версією мікропрограми:" #. TRANSLATORS: this is for the device tests msgid "Did not find any devices with matching GUIDs" msgstr "Не вдалося знайти жодного пристрою із відповідними GUID" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "Вимкнено" msgid "Disabled fwupdate debugging" msgstr "Вимкнено діагностику fwupdate" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "Вимикає вказане віддалене сховище" #. TRANSLATORS: command line option msgid "Display version" msgstr "Показати версію" #. TRANSLATORS: the OS the release was tested on msgid "Distribution" msgstr "Дистрибутив" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "Не перевіряти, чи є застарілі метадані" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "Не перевіряти, чи є ненадіслані звіти у журналі" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "Не перевіряти, чи має бути увімкнено віддалені джерела отримання даних" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "Не перевіряти потребу і не пропонувати перезавантажити систему після оновлення" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "Не включати префікс домену журналу" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "Не включати префікс часової позначки" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "Не виконувати перевірок безпечності для пристрою" #. TRANSLATORS: command line option msgid "Do not prompt for devices" msgstr "Не запитувати про пристрої" #. TRANSLATORS: command line option msgid "Do not prompt to fix security issues" msgstr "Не питати про дії щодо проблем із захистом" #. TRANSLATORS: command line option msgid "Do not search the firmware when parsing" msgstr "Не шукати мікропрограму при обробці" #. TRANSLATORS: warning message shown after update has been scheduled msgid "Do not turn off your computer or remove the AC adaptor while the update is in progress." msgstr "Не вимикайте ваш комп'ютер і не виймайте адаптер мережевого живлення, доки оновлення не буде завершено." #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "Не записувати дані до бази даних журналу" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "Чи розумієте ви наслідки зміни гілки мікропрограми?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "Хочете вимкнути цю можливість для наступних оновлень?" #. TRANSLATORS: we can do this live msgid "Do you want to enable it now?" msgstr "Хочете увімкнути її зараз?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "Хочете освіжити це віддалене сховище зараз?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "Хочете вивантажувати звіти автоматично для наступних оновлень?" #. TRANSLATORS: command line option msgid "Don't prompt for authentication (less details may be shown)" msgstr "Не просити про розпізнавання (буде показано менше подробиць)" #. TRANSLATORS: success #. success msgid "Done!" msgstr "Виконано!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "Знизити версію %s з %s до %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "Знижує версію мікропрограми на пристрої" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "Знижуємо версію %s з %s до %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "Знижуємо версію %s…" #. TRANSLATORS: command description msgid "Download a file" msgstr "Отримати файл" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "Отримуємо дані…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "Записати дані SMBIOS з файла" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "Тривалість" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "Вказаний ESP є некоректним" #. TRANSLATORS: longer description msgid "Each system should have tests to ensure firmware security." msgstr "У кожній системі мають бути перевірки, які забезпечують захист мікропрограми." #. TRANSLATORS: command description msgid "Emulate a device using a JSON manifest" msgstr "Емулювати пристрій за допомогою маніфеста JSON" #. TRANSLATORS: this device is not actually real msgid "Emulated" msgstr "Емульовано" #. TRANSLATORS: Title: if we are emulating a different host msgid "Emulated host" msgstr "Емульований вузол" msgid "Enable" msgstr "Увімкнути" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "Увімкнути підтримку оновлення мікропрограми на підтримуваних системах" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "Увімкнути нове віддалене сховище?" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "Увімкнути це віддалене сховище?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "Увімкнено" msgid "Enabled fwupdate debugging" msgstr "Увімкнено діагностику fwupdate" #. TRANSLATORS: Plugin is active only if hardware is found msgid "Enabled if hardware matches" msgstr "Увімкнено, якщо обладнання є відповідним" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "Вмикає вказане віддалене сховище" #. TRANSLATORS: longer description msgid "Enabling firmware updates for the BIOS allows fixing security issues." msgstr "Вмикання оновлень мікропрограми для BIOS уможливлює виправлення проблем із захистом." msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "Наслідки вмикання цієї можливості покладаються на вас. Це означає, що усі проблеми, пов'язані із цими оновленнями, ви маєте вирішувати із виробником обладнання. Повідомляти розробникам дистрибутива за адресою $OS_RELEASE:BUG_REPORT_URL$ слід лише про помилки у процесі оновлення." #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "Вмикання цього віддаленого сховища виконано під вашу відповідальність." #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "Зашировано" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "Шифрована пам'ять" msgid "Encrypted RAM makes it impossible for information that is stored in device memory to be read if the memory chip is removed and accessed." msgstr "Шифрування оперативної пам'яті робить неможливим читання інформації, яка зберігається у пам'яті пристрою, якщо чіп пам'яті вилучено з пристрою і піддано обробці з метою отримання доступу до даних." #. TRANSLATORS: the vendor is no longer supporting the device msgid "End of life" msgstr "Кінець строку дії" #. TRANSLATORS: The BIOS setting can only be changed to fixed values msgid "Enumeration" msgstr "Перелік" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "Витерти увесь журнал оновлень мікропрограми" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "Витираємо…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "Завершити роботу з невеличкою затримкою" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "Завершити роботу після завантаження рушія" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "Експортувати структуру файла мікропрограми до XML" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "Видобувати бінарну мікропрограму до образів" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "ФАЙЛ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "ФАЙЛ [ІД_ПРИСТРОЮ|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "НАЗВА_ФАЙЛА" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "НАЗВА-ФАЙЛА СЕРТИФІКАТ ЗАКРИТИЙ-КЛЮЧ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "НАЗВА_ФАЙЛА АЛЬТ_НАЗВА_ПРИСТРОЮ|АЛЬТ_ІД_ПРИСТРОЮ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "НАЗВА_ФАЙЛА АЛЬТ_НАЗВА_ПРИСТРОЮ|АЛЬТ_ІД_ПРИСТРОЮ [АЛЬТ_НАЗВА_ОБРАЗУ|АЛЬТ_ІД_ОБРАЗУ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "НАЗВА_ФАЙЛА ІД_ПРИСТРОЮ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME OFFSET DATA [FIRMWARE-TYPE]" msgstr "НАЗВА-ФАЙЛА ВІДСТУП ДАНІ [ТИП-МІКРОПРОГРАМИ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "НАЗВА_ФАЙЛА [ІД_ПРИСТРОЮ|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "НАЗВА-ФАЙЛА [ТИП-МІКРОПРОГРАМИ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "НАЗВА_ФАЙЛА_ДЖ НАЗВА_ФАЙЛА_ПРИЗН [ТИП-МІКРОПРОГРАМИ-ДЖ] [ТИП-МІКРОПРОГРАМИ-ПРИЗН]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]" msgstr "НАЗВА_ФАЙЛА|КОНТР_СУМА1[,КОНТР_СУМА2][,КОНТР_СУМА3]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "Помилка" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "Не вдалося застосувати оновлення" #. TRANSLATORS: error message for Windows msgid "Failed to connect to Windows service, please ensure it's running." msgstr "Не вдалося встановити з'єднання зі службою Windows. Будь ласка, переконайтеся, що її запущено." #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "Не вдалося встановити з'єднання із фоновою службою" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "Не вдалося отримати список пристроїв у черзі очікування" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "Не вдалося встановити оновлення мікропрограми" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "Не вдалося завантажити локальний dbx" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "Не вдалося завантажити коригування" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "Не вдалося завантажити системний dbx" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "Не вдалося заблокувати" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "Не вдалося обробити аргументи" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "Не вдалося обробити файл" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "Не вдалося обробити прапорці до --filter" msgid "Failed to parse flags for --filter-release" msgstr "Не вдалося обробити прапорці до --filter-release" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "Не вдалося обробити локальний dbx" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "Не вдалося перезавантажити" #. TRANSLATORS: a feature is something like "can show an image" msgid "Failed to set front-end features" msgstr "Не вдалося встановити можливості оболонки" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "Не вдалося встановити режим вітання" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "Не вдалося встановити чинність даних ESP" #. TRANSLATORS: item is FALSE msgid "False" msgstr "Ні" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "Назва файла" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "Підпис назви файла" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "Джерело файла" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "Слід вказати назву файла" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "Фільтрувати за набором прапорців пристроїв за допомогою префікса виключення ~, наприклад «internal,~needs-reboot»" #. TRANSLATORS: command line option msgid "Filter with a set of release flags using a ~ prefix to exclude, e.g. 'trusted-release,~trusted-metadata'" msgstr "Фільтрувати за набором прапорців випуску з використанням префікса ~ для виключення, приклад: 'trusted-release,~trusted-metadata'" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware Attestation" msgstr "Атестація мікропрограми" #. TRANSLATORS: longer description msgid "Firmware Attestation checks device software using a reference copy, to make sure that it has not been changed." msgstr "Атестація мікропрограми перевіряє програмне забезпечення пристрою за еталонною копією, щоб переконатися, що до нього не було внесено змін." #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware BIOS Descriptor" msgstr "Дескриптор BIOS мікропрограми" #. TRANSLATORS: longer description msgid "Firmware BIOS Descriptor protects device firmware memory from being tampered with." msgstr "Захист lдескриптора BIOS захищає пам'ять мікропрограми пристрою від внесення змін." #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "Firmware BIOS Region" msgstr "Регіон BIOS мікропрограми" #. TRANSLATORS: longer description msgid "Firmware BIOS Region protects device firmware memory from being tampered with." msgstr "Захист регіону BIOS захищає пам'ять мікропрограми пристрою від внесення змін." #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "Основна адреса мікропрограми" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "Служба D-Bus оновлення мікропрограми" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "Служба оновлення мікропрограми" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "Firmware Updater Verification" msgstr "Перевірка засобу оновлення мікропрограми" msgid "Firmware Updater Verification checks that software used for updating has not been tampered with." msgstr "Перевірка засобу оновлення мікропрограми перевіряє, чи не було внесено зміни до програмного забезпечення для оновлення мікропрограми." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware Updates" msgstr "Оновлення мікропрограми" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "Засіб роботи з мікропрограмами" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection" msgstr "Захист мікропрограми від запису" #. TRANSLATORS: Title: firmware refers to the flash chip in the computer msgid "Firmware Write Protection Lock" msgstr "Блокування захисту від запису мікропрограми" #. TRANSLATORS: longer description msgid "Firmware Write Protection protects device firmware memory from being tampered with." msgstr "Захист від запису мікропрограми захищає пам'ять мікропрограми пристрою від внесення змін." #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "Засвідчення мікропрограми" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "Мікропрограму вже заблоковано" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "Мікропрограму ще не заблоковано" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "Метадані мікропрограми не оновлювалися %u день, можливо, вони вже не є актуальними." msgstr[1] "Метадані мікропрограми не оновлювалися %u дні, можливо, вони вже не є актуальними." msgstr[2] "Метадані мікропрограми не оновлювалися %u днів, можливо, вони вже не є актуальними." msgstr[3] "Метадані мікропрограми не оновлювалися %u днів, можливо, вони вже не є актуальними." #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "Оновлення мікропрограми" msgid "Firmware updates are not supported on this machine." msgstr "На цьому комп'ютері не передбачено підтримки оновлення мікропрограми." msgid "Firmware updates are supported on this machine." msgstr "На цьому комп'ютері передбачено підтримку оновлень мікропрограми." #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "Оновлення мікропрограми вимкнено; віддайте команду 'fwupdmgr unlock', щоб їх увімкнути" #. TRANSLATORS: command description msgid "Fix a specific host security attribute" msgstr "Виправити певний атрибут захисту вузла" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fix reverted successfully" msgstr "Виправлення успішно скасовано" #. TRANSLATORS: we've fixed a security problem on the machine msgid "Fixed successfully" msgstr "Успішно виправлено" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "Прапорці" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "Виконати дію примусово шляхом послаблення деяких перевірок під час виконання" msgid "Force the action ignoring all warnings" msgstr "Виконати дію примусово, ігноруючи усі попередження" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "Знайдено" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "Виявлено повне шифрування диска" #. TRANSLATORS: we might ask the user the recovery key when next booting #. Windows msgid "Full disk encryption secrets may be invalidated when updating" msgstr "Ключі шифрування усього диска можуть втрати чинність при оновленні" #. TRANSLATORS: Title: if the part has been fused msgid "Fused Platform" msgstr "Злита платформа" #. TRANSLATORS: Title: if the part has been fused msgid "Fused platform" msgstr "Злита платформа" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" msgstr[1] "GUID" msgstr[2] "GUID" msgstr[3] "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "GUID|DEVICE-ID" msgstr "GUID|ІД-ПРИСТРОЮ" msgid "Get BIOS settings" msgstr "Отримання параметрів BIOS" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "Отримати усі прапорці пристроїв, підтримку яких передбачено у fwupd" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "Отримати список усіх пристроїв, у яких передбачено оновлення мікропрограми" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "Отримати список усіх додатків, які зареєстровано у системі" #. TRANSLATORS: command description msgid "Get device report metadata" msgstr "Отримати метадані звіту щодо пристрою" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "Отримати параметри файла мікропрограми" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "Отримує налаштовані віддалені пристрої" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "Отримує атрибути захисту основної системи" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "Отримує список схвалених мікропрограм" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "Отримує список заблокованих мікропрограм" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "Отримує список оновлень для з’єднаного обладнання" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "Отримує випуски для пристрою" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "Отримує результати з останнього оновлення" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "ФАЙЛ-HWIDS" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "Обладнання очікує на повторне з'єднання" #. TRANSLATORS: the release urgency msgid "High" msgstr "Високий" #. TRANSLATORS: title for host security events msgid "Host Security Events" msgstr "Події захисту вузла" #. TRANSLATORS: error message for unsupported feature #, c-format msgid "Host Security ID (HSI) is not supported" msgstr "Підтримки Host Security ID (HSI) не передбачено" #. TRANSLATORS: success, so say thank you to the user msgid "Host Security ID attributes uploaded successfully, thanks!" msgstr "Ідентифікатор захисту вузла успішно оновлено, дякуємо!" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "Ід. захисту основної системи:" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "INHIBIT-ID" msgstr "ІД-ЗАБОРОНИ" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU Protection" msgstr "Захист IOMMU" #. TRANSLATORS: longer description msgid "IOMMU Protection prevents connected devices from accessing unauthorized parts of system memory." msgstr "Захист IOMMU забороняє з'єднаним пристроям доступ до неуповноважених частин пам'яті системи." #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "Вимкнено захист пристрою IOMMU" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "Увімкнено захист пристрою IOMMU" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "Бездіяльність…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "Ігнорувати результати строгої перевірки SSL під час отримання файлів" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "Ігнорувати помилки при перевірці контрольної суми мікропрограми" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "Ігнорувати помилки, пов'язані із невідповідністю обладнання мікропрограмі" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "Ігнорувати перевірки безпечності" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "Ігноруємо строгі перевірки SSL. Щоб зробити ігнорування у майбутньому автоматичним, експортуйте до вашого середовища змінну DISABLE_SSL_STRICT" #. TRANSLATORS: show the user a generic image that can be themed msgid "Image" msgstr "Зображення" #. TRANSLATORS: show the user a random image from the internet msgid "Image (custom)" msgstr "Зображення (нетипове)" #. TRANSLATORS: the inhibit ID is a short string like dbus-123456 #, c-format msgid "Inhibit ID is %s." msgstr "Ідентифікатор заборони – %s." #. TRANSLATORS: command description msgid "Inhibit the system to prevent upgrades" msgstr "Заборонити системі запобігати оновленням" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "Тривалість встановлення" #. TRANSLATORS: command description msgid "Install a firmware file in cabinet format on this hardware" msgstr "Встановити файл мікропрограми у форматі cab на це обладнання" #. TRANSLATORS: command description msgid "Install a raw firmware blob on a device" msgstr "Встановити просту бінарну мікропрограму на пристрій" #. TRANSLATORS: command description msgid "Install a specific firmware file on all devices that match" msgstr "Встановити певний файл мікропрограми на усі відповідні пристрої" #. TRANSLATORS: command description msgid "Install a specific firmware on a device, all possible devices will also be installed once the CAB matches" msgstr "Встановити вказану мікропрограму на пристрій. Буде також встановлено на усі можливі пристрої, щойно буде встановлено відповідність CAB" msgid "Install old version of signed system firmware" msgstr "Встановити стару версію підписаної мікропрограми системи" msgid "Install old version of unsigned system firmware" msgstr "Встановити стару версію непідписаної мікропрограми системи" msgid "Install signed device firmware" msgstr "Встановити підписану мікропрограму пристрою" msgid "Install signed system firmware" msgstr "Встановити підписану мікропрограму системи" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "Встановити спочатку на батьківському пристрої" msgid "Install unsigned device firmware" msgstr "Встановити непідписану мікропрограму пристрою" msgid "Install unsigned system firmware" msgstr "Встановити непідписану мікропрограму системи" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "Встановлюємо мікропрограму…" #. TRANSLATORS: stay on one firmware version unless the new version is #. explicitly #. * specified msgid "Installing a specific release is explicitly required" msgstr "Явно вказано встановлення вказаного випуску" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "Встановлюємо оновлення мікропрограми…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "Встановлюємо на %s…" #. TRANSLATORS: if it breaks, you get to keep both pieces msgid "Installing this update may also void any device warranty." msgstr "Встановлення цього оновлення може призвести до припинення будь-яких гарантійних зобов'язань щодо пристрою." #. TRANSLATORS: The BIOS setting only accepts integers in a fixed range msgid "Integer" msgstr "Ціле число" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM Protected" msgstr "Захист ACM Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Intel BootGuard захищено ACM" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard Error Policy" msgstr "Правила обробки помилок Intel BootGuard" msgid "Intel BootGuard Error Policy ensures the device does not continue to start if its device software has been tampered with." msgstr "Правила обробки помилок Intel BootGuard запобігають продовженню запуску пристрою, якщо до програмного забезпечення пристрою було внесено зміни." #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard Fuse" msgstr "Intel BootGuard Fuse" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "OTP FUSE Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard Verified Boot" msgstr "Перевірене завантаження Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Правила обробки помилок Intel BootGuard" #. TRANSLATORS: longer description msgid "Intel BootGuard prevents unauthorized device software from operating when the device is started." msgstr "Intel BootGuard запобігає роботі неуповноваженого програмного забезпечення на пристрої після його запуску." #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Перевірене завантаження Intel BootGuard" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology msgid "Intel CET" msgstr "Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Активна Intel CET" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Увімкнено Intel CET" #. TRANSLATORS: longer description msgid "Intel Control-Flow Enforcement Technology detects and prevents certain methods for running malicious software on the device." msgstr "Технологія Intel Control-Flow Enforcement виявляє і запобігає певним методам запуску зловмисниками програмного забезпечення на пристрої." #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS Mitigation" msgstr "Обхід Intel GDS" #. TRANSLATORS: Title: GDS is where the CPU leaks information msgid "Intel GDS mitigation" msgstr "Обхід Intel GDS" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Manufacturing Mode" msgstr "Режим виробництва Intel Management Engine" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is #. enabled #. * with a jumper -- luckily it is probably not accessible to end users on #. consumer #. * boards msgid "Intel Management Engine Override" msgstr "Перевизначення Intel Management Engine" #. TRANSLATORS: longer description msgid "Intel Management Engine Override disables checks for device software tampering." msgstr "Перевизначення Intel Management Engine вимикає перевірки щодо внесення змін до програмного забезпечення пристрою." #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "Intel Management Engine Version" msgstr "Версія Intel Management Engine" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: longer description msgid "Intel Supervisor Mode Access Prevention ensures critical parts of device memory are not accessed by less secure programs." msgstr "Intel Supervisor Mode Access Prevention забороняє малобезпечним програмам доступ до пам'яті пристрою." #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "Внутрішній пристрій" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "Некоректна" #. TRANSLATORS: error message msgid "Invalid arguments" msgstr "Некоректні аргументи" #. TRANSLATORS: error message msgid "Invalid arguments, expected GUID" msgstr "Некоректні аргументи, мало бути вказано GUID" #. TRANSLATOR: This is the error message for #. * incorrect parameter msgid "Invalid arguments, expected an AppStream ID" msgstr "Некоректні аргументи, мало бути вказано ідентифікатор AppStream" #. TRANSLATORS: error message msgid "Invalid arguments, expected at least ARCHIVE FIRMWARE METAINFO" msgstr "Некоректні аргументи. Мало бути вказано принаймні АРХІВ МІКРОПРОГРАМА МЕТАІНФО" #. TRANSLATORS: version is older msgid "Is downgrade" msgstr "Є старішою версією" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "Перебуває у режимі завантажувача" #. TRANSLATORS: version is newer msgid "Is upgrade" msgstr "Є оновленням" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "Вада" msgstr[1] "Вади" msgstr[2] "Вади" msgstr[3] "Вада" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "КЛЮЧ,ЗНАЧЕННЯ" #. TRANSLATORS: HSI event title msgid "Kernel is no longer tainted" msgstr "Ядро більше не використовує сторонні модулі" #. TRANSLATORS: HSI event title msgid "Kernel is tainted" msgstr "Ядро використовує сторонні модулі" #. TRANSLATORS: HSI event title msgid "Kernel lockdown disabled" msgstr "Вимкнено блокування ядра" #. TRANSLATORS: HSI event title msgid "Kernel lockdown enabled" msgstr "Увімкнено блокування ядра" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "Сховище ключів" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "МІСЦЕ" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "Востаннє змінено" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "Лишилося менше за хвилину" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "Ліцензування" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux Kernel Lockdown" msgstr "Блокування ядра Linux" #. TRANSLATORS: longer description msgid "Linux Kernel Lockdown mode prevents administrator (root) accounts from accessing and changing critical parts of system software." msgstr "Режим блокування ядра Linux забороняє адміністративним обліковим записам (root) доступ до критичних частин системи і внесення до них змін." msgid "Linux Kernel Swap temporarily saves information to disk as you work. If the information is not protected, it could be accessed by someone if they obtained the disk." msgstr "Резервна пам'ять ядра Linux зберігає на диску дані під час роботи системи. Якщо дані не захищено, хтось, хто отримає диск, може отримати до них доступ." #. TRANSLATORS: Title: if it's tainted or not msgid "Linux Kernel Verification" msgstr "Перевірка ядра Linux" msgid "Linux Kernel Verification makes sure that critical system software has not been tampered with. Using device drivers which are not provided with the system can prevent this security feature from working correctly." msgstr "Перевірка ядра Linux захищає критичне програмне забезпечення системи від внесення змін. Використання драйверів пристроїв, які не є частиною системи, може заважати належній роботі цієї можливості захисту." #. TRANSLATORS: Title: swap space or swap partition msgid "Linux Swap" msgstr "Резервна пам'ять Linux" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Служба надання мікропрограм для Linux від виробника (стабільна мікропрограма)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Служба надання мікропрограм для Linux від виробника (тестова мікропрограма)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Ядро Linux" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Блокування ядра Linux" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Рез. пам'ять Linux" #. TRANSLATORS: command description msgid "List EFI variables with a specific GUID" msgstr "Вивести список змінних EFI із певним GUID" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "Вивести список записів у dbx" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "Показати список підтримуваних оновлень мікропрограми" #. TRANSLATORS: command description msgid "List the available firmware GTypes" msgstr "Вивести список доступних GType мікропрограми" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "Вивести список доступних типів мікропрограм" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "Виводить список файлів у ESP" #. TRANSLATORS: command description msgid "Load device emulation data" msgstr "Завантажити дані емуляції пристрою" #. TRANSLATORS: the plugin was created from a .so object, and was not built-in msgid "Loaded from an external module" msgstr "Завантажено із зовнішнього модуля" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "Завантаження…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "Заблоковано" #. TRANSLATORS: the release urgency msgid "Low" msgstr "Низький" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refers #. * to the private/public key used to secure loading of firmware msgid "MEI Key Manifest" msgstr "Маніфест ключа MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and key refer #. * to the private/public key used to secure loading of firmware msgid "MEI key manifest" msgstr "Маніфест ключа MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "Режим виробництва MEI" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "Перевизначення MEI" msgid "MEI version" msgstr "Версія MEI" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "Увімкнути певні додатки вручну" #. TRANSLATORS: longer description msgid "Manufacturing Mode is used when the device is manufactured and security features are not yet enabled." msgstr "Режим виробництва використовують після вироблення пристрою, коли можливості захисту ще не увімкнено." #. TRANSLATORS: Longest valid string for BIOS setting msgid "Maximum length" msgstr "Максимальна довжина" #. TRANSLATORS: Highest valid integer for BIOS setting msgid "Maximum value" msgstr "Максимальне значення" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "Середній" #. TRANSLATORS: ask the user to do a simple task which should be translated msgid "Message" msgstr "Повідомлення" #. TRANSLATORS: ask the user a question, and it will not be translated msgid "Message (custom)" msgstr "Повідомлення (нетипове)" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "Підпис метаданих" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "Адреса метаданих" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "Метадані можна отримати від служби надання мікропрограм для Linux від виробника." #. TRANSLATORS: error message for a user who ran fwupdmgr #. * refresh recently msgid "Metadata is up to date; use --force to refresh again." msgstr "Метадані є актуальними; скористайтеся --force для освіження відомостей." #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "Мінімальна версія" #. TRANSLATORS: Shortest valid string for BIOS setting msgid "Minimum length" msgstr "Мінімальна довжина" #. TRANSLATORS: Lowest valid integer for BIOS setting msgid "Minimum value" msgstr "Мінімальне значення" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "Невідповідність фонової служби і клієнта, скористайтеся краще %s" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "Змінює значення налаштувань фонової служби" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "Змінює вказаний запис віддаленого пристрою" msgid "Modify a configured remote" msgstr "Зміна налаштованих віддалених пристроїв" msgid "Modify daemon configuration" msgstr "Зміна налаштувань фонової служби" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "Стежити за подіями у фоновій службі" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "Монтує ESP" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "Потребує перезавантаження після встановлення" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "Потребує перезавантаження" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "Потребує вимикання після встановлення" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "Нова версія" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "Не вказано дії!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "Немає знижень версії для %s" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "Не знайдено ідентифікаторів мікропрограм" #. TRANSLATORS: nothing found msgid "No firmware found" msgstr "Не знайдено мікропрограми" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "Не виявлено обладнання із передбаченою можливістю оновлення мікропрограми" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "Додатків не знайдено" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "Немає доступних випусків" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "Зараз не увімкнено жодного віддаленого сховища, отже, метадані недоступні." #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "Немає доступних віддалених сховищ" #. TRANSLATORS: this is an error string msgid "No updatable devices" msgstr "Немає придатних до оновлення пристроїв" #. TRANSLATORS: this is an error string msgid "No updates available" msgstr "Немає доступних оновлень" msgid "No updates available for remaining devices" msgstr "Для решти пристроїв немає доступних оновлень" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "Не застосовано жодного оновлення" #. TRANSLATORS: version cannot be installed due to policy msgid "Not approved" msgstr "Не затверджено" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "Не знайдено" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "Немає підтримки" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "Гаразд" #. TRANSLATORS: this is for the device tests msgid "OK!" msgstr "Гаразд!" #. TRANSLATORS: the firmware old version msgid "Old version" msgstr "Стара версія" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "Показувати лише одне значення PCR" #. TRANSLATORS: command line option msgid "Only use peer-to-peer networking when downloading files" msgstr "Використовувати лише рівнорангові з'єднання мережі при отриманні файлів" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "Дозволено лише оновлення версії" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "Виведені дані у форматі JSON" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "Перевизначити типовий шлях до ESP" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Firmware" msgstr "Мікропрограма P2P" #. TRANSLATORS: if we can get metadata from peer-to-peer clients msgid "P2P Metadata" msgstr "Метадані P2P" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "ШЛЯХ" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "Обробити і показати параметри файла мікропрограми" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "Обробляємо оновлення dbx…" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "Обробляємо системний dbx…" #. TRANSLATORS: remote filename base msgid "Password" msgstr "Пароль" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "Накласти латку на бінарний файл мікропрограми за відомим відступом" msgid "Payload" msgstr "Вміст" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "У черзі" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "Відсоток виконання" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "Виконати дію?" #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform Debugging" msgstr "Діагностика платформи" #. TRANSLATORS: longer description msgid "Platform Debugging allows device security features to be disabled. This should only be used by hardware manufacturers." msgstr "Діагностика платформи надає змогу вимикати можливості захисту пристрою. Цією можливістю мають користуватися лише виробники обладнання." #. TRANSLATORS: Title: Allows debugging of parts using proprietary hardware msgid "Platform debugging" msgstr "Діагностика платформи" #. TRANSLATORS: 'recovery key' here refers to a code, rather than a physical #. metal thing msgid "Please ensure you have the volume recovery key before continuing." msgstr "Будь ласка, переконайтеся, що у вас є ключ відновлення тому, перш ніж продовжувати обробку." #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "Будь ласка, введіть число від 0 до %u: " #. TRANSLATORS: the user isn't reading the question -- please don't translate #. * 'Y' or 'N' as these are hardcoded msgid "Please enter either Y or N: " msgstr "Будь ласка, введіть Y або N: " #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "Не встановлено залежності додатка" #. TRANSLATORS: Possible values for a bios setting msgid "Possible Values" msgstr "Можливі значення" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA Protection" msgstr "Захист DMA до завантаження" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "Захист DMA до завантаження" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is disabled" msgstr "Захист DMA перед завантаженням вимкнено" #. TRANSLATORS: HSI event title msgid "Pre-boot DMA protection is enabled" msgstr "Захист DMA перед завантаженням увімкнено" #. TRANSLATORS: longer description msgid "Pre-boot DMA protection prevents devices from accessing system memory after being connected to the computer." msgstr "Захист DMA перед завантаженням запобігає доступу з боку пристроїв до системної пам'яті після встановлення з'єднання пристроїв з комп'ютером." #. TRANSLATORS: warning message msgid "Press unlock on the device to continue the update process." msgstr "Будь ласка, розблокуйте пристрій, щоб продовжити процедуру оновлення." #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "Попередня версія" msgid "Print the version number" msgstr "Вивести номер версії" msgid "Print verbose debug statements" msgstr "Вивести докладні діагностичні повідомлення" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "Пріоритетність" #. TRANSLATORS: reasons the device is not updatable msgid "Problems" msgstr "Проблеми" msgid "Proceed with upload?" msgstr "Продовжити вивантаження?" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Processor Security Checks" msgstr "Перевірки захисту процесора" #. TRANSLATORS: Title: if firmware enforces rollback protection msgid "Processor rollback protection" msgstr "Захист від відновлення попередніх станів процесора" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "Пропрієтарна" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "Опитати щодо підтримки оновлення мікропрограми" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "ВІДДАЛ-ІД" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "ВІДДАЛ-ІД КЛЮЧ ЗНАЧЕННЯ" #. TRANSLATORS: BIOS setting is read only msgid "Read Only" msgstr "Лише читання" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "Прочитати бінарну мікропрограму з пристрою" #. TRANSLATORS: command description msgid "Read a firmware from a device" msgstr "Прочитати мікропрограму з пристрою" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "Прочитати мікропрограму з пристрою до файла" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "Прочитати мікропрограму з одного розділу до файла" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "Читаємо з %s…" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "Читаємо…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "Перезавантажуємо…" #. TRANSLATORS: how often we should refresh the metadata msgid "Refresh Interval" msgstr "Інтервал оновлення" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "Оновити метадані з віддаленого сервера" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "Повторно встановити %s до %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "Повторно встановити мікропрограму на пристрій" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "Повторно встановити мікропрограму на пристрій" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "Повторно встановлюємо %s з номером версії %s... " #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "Гілка випусків" #. TRANSLATORS: release attributes msgid "Release Flags" msgstr "Прапорці випуску" #. TRANSLATORS: the exact component on the server msgid "Release ID" msgstr "Ідентифікатор випуску" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "Віддалений ідентифікатор" #. TRANSLATORS: command description msgid "Removes devices to watch for future emulation" msgstr "Вилучає пристрої зі спостереження щодо майбутньої емуляції" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "Замінити дані у наявному файлі мікропрограми" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "Адреса звіту" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "Повідомлено на віддаленому сервері" #. TRANSLATORS: we asked the user to choose an option and they declined msgid "Request canceled" msgstr "Запит скасовано" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "Не знайдено відповідної файлової системи efivarfs" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "Не знайдено відповідного обладнання" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "Потребує завантажувача" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "Потребує з'єднання із інтернетом" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "Перезавантажити зараз?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "Перезапустити фонову службу, щоб зміни набули чинності?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "Перезапускаємо пристрій…" #. TRANSLATORS: command description msgid "Retrieve BIOS settings. If no arguments are passed all settings are returned" msgstr "Отримати параметри BIOS. Якщо аргументів не передано, буде повернуто усі параметри" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "Повернути усі ідентифікатори апаратного забезпечення комп’ютера" #. TRANSLATORS: ask the user to upload msgid "Review and upload report now?" msgstr "Переглянути і вивантажити звіт зараз?" #. TRANSLATORS: longer description msgid "Rollback Protection prevents device software from being downgraded to an older version that has security problems." msgstr "Захист від відновлення попередніх версій запобігає зниженню версій програмного забезпечення до тих, у яких виявлено проблеми із захистом." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "Віддайте команду «fwupdmgr get-upgrades», щоб дізнатися більше." #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr sync` to complete this action." msgstr "Віддайте команду «fwupdmgr sync», щоб завершити цю дію." #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "Запустити процедуру чищення композиції додатків при використанні install-blob" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "Запустити процедуру приготування композиції додатків при використанні install-blob" #. TRANSLATORS: command description msgid "Run the post-reboot cleanup action" msgstr "Виконати дію з чищення після перезавантаження" #. TRANSLATORS: tell a user how to get information #, c-format msgid "Run without '%s' to see" msgstr "Запустіть без «%s», щоб переглянути" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "Поточне ядро є надто застарілим" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "Динамічний суфікс" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING VALUE" msgstr "ПАРАМЕТР ЗНАЧЕННЯ" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SETTING1 VALUE1 [SETTING2] [VALUE2]" msgstr "ПАРАМЕТР1 ЗНАЧЕННЯ1 [ПАРАМЕТР2] [ЗНАЧЕННЯ2]" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "Дескриптор SPI BIOS" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "Регіон BIOS SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "Блокування SPI" #. TRANSLATORS: Title: if hardware enforces control of SPI replays msgid "SPI replay protection" msgstr "Захист від відтворення SPI" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "Запис SPI" #. TRANSLATORS: Title: if hardware enforces control of SPI writes msgid "SPI write protection" msgstr "Захист від запису SPI" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "ПІДСИСТЕМА ДРАЙВЕР [ІД_ПРИСТРОЮ|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "Зберегти файл, за допомогою якого можна буде створити апаратні ідентифікатори" #. TRANSLATORS: command description msgid "Save device emulation data" msgstr "Зберегти дані емуляції пристрою" #. TRANSLATORS: Scalar increment for integer BIOS setting msgid "Scalar Increment" msgstr "Скалярний крок" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "Якщо можливо, запланувати встановлення на наступне перезавантаження" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "Плануємо…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "Secure Boot вимкнено" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "Увімкнено Secure Boot" msgid "Security hardening for HSI" msgstr "Зміцнення захисту для HSI" #. TRANSLATORS: the %1 is a URL to a wiki page #, c-format msgid "See %s for more details." msgstr "Див. %s, щоб дізнатися більше." #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "Див. %s, щоб дізнатися більше." #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "Вибрано пристрій" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "Вибраний том" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "Серійний номер" #. TRANSLATORS: Configured a BIOS setting to a value #, c-format msgid "Set BIOS setting '%s' using '%s'." msgstr "Встановити параметр BIOS «%s» за допомогою «%s»." #. TRANSLATORS: command description msgid "Set a BIOS setting" msgstr "Встановити параметр BIOS" msgid "Set one or more BIOS settings" msgstr "Встановити один або декілька параметрів BIOS" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "Встановити під час оновлення прапорець діагностики" #. TRANSLATORS: command description msgid "Sets one or more BIOS settings" msgstr "Встановити один або декілька параметрів BIOS" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "Встановлення списку схвалених мікропрограм" #. TRANSLATORS: type of BIOS setting msgid "Setting type" msgstr "Тип параметра" #. TRANSLATORS: description of a BIOS setting msgid "Settings will apply after system reboots" msgstr "Параметри буде застосовано після перезавантаження системи" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "Поділитися журналом оновлень із розробниками" #. TRANSLATORS: command line option msgid "Show all results" msgstr "Показати усі результати" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "Вивести дані щодо версій клієнат і фонової служби" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "Показати докладні дані фонової служби для певного домену" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "Показати діагностичні дані для усіх доменів" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "Показувати параметри діагностики" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "Показати пристрої, оновлення для яких неможливе" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "Показати додаткові діагностичні дані" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "Показати журнал оновлень мікропрограми" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "Вивести обчислену версію dbx" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "Показати діагностичний журнал щодо останньої спроби оновлення" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "Показати дані щодо стану оновлення мікропрограми" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "Вимкнути зараз?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "Підписати мікропрограму новим ключем" msgid "Sign data using the client certificate" msgstr "Підписування даних клієнтським сертифікатом" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "Підписування даних клієнтським сертифікатом" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "Підписати вивантажені дані клієнтським сертифікатом" msgid "Signature" msgstr "Підпис" #. TRANSLATORS: firmware is verified on-device the payload using strong crypto msgid "Signed Payload" msgstr "Підписаний вміст" #. TRANSLATORS: file size of the download msgid "Size" msgstr "Розмір" #. TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM msgid "Some of the platform secrets may be invalidated when updating this firmware." msgstr "Якщо оновити цю мікропрограму, деякі ключі платформи можуть втратити чинність." #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "Джерело" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "Вказати ідентифікатори виробника/продукту пристрою DFU" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "Вказати файл бази даних dbx" msgid "Specify the number of bytes per USB transfer" msgstr "Вказати кількість байтів на один пакет передавання даних USB" #. TRANSLATORS: The BIOS setting accepts strings msgid "String" msgstr "Рядок" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "Успіх" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "Успішно активовано усі пристрої" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "Успішно вимкнено запис віддаленого сховища" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "Успішно отримано нові метадані:" #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "Успішно увімкнено і освіжено віддалене сховище" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "Успішно увімкнено запис віддаленого сховища" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "Мікропрограму успішно встановлено" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "Успішно змінено значення у налаштуваннях" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "Успішно змінено віддалене сховище" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "Метадані успішно оновлено вручну" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "Успішно оновлено контрольні суми пристрою" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "Успішно вивантажено %u звіт" msgstr[1] "Успішно вивантажено %u звіти" msgstr[2] "Успішно вивантажено %u звітів" msgstr[3] "Успішно вивантажено %u звіт" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "Успішно перевірено контрольні суми пристрою" #. TRANSLATORS: the device showed up in time #, c-format msgid "Successfully waited %.0fms for device" msgstr "Успішне очікування %.0fмс на пристрій" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "Резюме" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "Є підтримка" #. TRANSLATORS: Title: if fwupd supports HSI on this chip msgid "Supported CPU" msgstr "Підтримувані процесори" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "Підтримуваний на віддаленому сервері" #. TRANSLATORS: Title: a better sleep state msgid "Suspend To Idle" msgstr "Присипляння бездіяльності" #. TRANSLATORS: Title: sleep state msgid "Suspend To RAM" msgstr "Призупинення до RAM" #. TRANSLATORS: longer description msgid "Suspend to Idle allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Призупинення до стану бездіяльності надає змогу пристрою швидко перейти у стан сну для заощадження електроенергії. Доки пристрій перебуває у стані призупинення, його пам'ять може бути фізично видалено із отриманням доступу до даних у пам'яті." #. TRANSLATORS: longer description msgid "Suspend to RAM allows the device to quickly go to sleep in order to save power. While the device has been suspended, its memory could be physically removed and its information accessed." msgstr "Призупинення із записом до оперативної пам'яті надає змогу пристрою швидко перейти у стан сну для заощадження електроенергії. Доки пристрій перебуває у стані призупинення, його пам'ять може бути фізично видалено із отриманням доступу до даних у пам'яті." #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "Присипляння бездіяльності" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "Присипляння до пам'яті" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "Перемкнутися з гілки %s на %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "Перемкнути гілку мікропрограми на пристрої" #. TRANSLATORS: command description msgid "Sync firmware versions to the chosen configuration" msgstr "Синхронізувати версії мікропрограм із вибраною конфігурацією" #. TRANSLATORS: this CLI tool is now preventing system updates msgid "System Update Inhibited" msgstr "Оновлення системи заборонено" #. TRANSLATORS: as in laptop battery power msgid "System power is too low to perform the update" msgstr "Живлення системи на надто низькому рівні для виконання оновлення" #. TRANSLATORS: as in laptop battery power #, c-format msgid "System power is too low to perform the update (%u%%, requires %u%%)" msgstr "Живлення системи на надто низькому рівні для виконання оновлення (%u%%, має бути принаймні %u%%)" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "Система потребує зовнішнього джерела живлення" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "ТЕКСТ" #. TRANSLATORS: longer description msgid "TPM (Trusted Platform Module) is a computer chip that detects when hardware components have been tampered with." msgstr "TPM (Trusted Platform Module або довірений модуль платформи) є комп'ютерним чіпом, який виявляє зміни в апаратних компонентах." #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "Відновлення PCR0 TPM" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is invalid" msgstr "Відновлення PCR0 TPM є некоректним" #. TRANSLATORS: HSI event title msgid "TPM PCR0 reconstruction is now valid" msgstr "Відновлення PCR0 TPM тепер є коректним" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM Platform Configuration" msgstr "Налаштування платформи TPM" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM Reconstruction" msgstr "Відновлення TPM" #. TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be #. empty msgid "TPM empty PCRs" msgstr "Порожні PCR TPM" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM 2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "Мітка" msgstr[1] "Мітки" msgstr[2] "Мітки" msgstr[3] "Мітки" #. TRANSLATORS: we're saving all USB events for emulation msgid "Tagged for emulation" msgstr "Позначено для емуляції" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "Нечисте" msgid "Target" msgstr "Ціль" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "Перевірити пристрій за допомогою маніфесту JSON" #. TRANSLATORS: when the release was tested msgid "Tested" msgstr "Перевірено" #. TRANSLATORS: the %s is a vendor name, e.g. Lenovo #, c-format msgid "Tested by %s" msgstr "Перевірено %s" #. TRANSLATORS: someone we trust has tested this msgid "Tested by trusted vendor" msgstr "Перевірено надійним постачальником" #. TRANSLATORS: longer description msgid "The Intel Management Engine Key Manifest must be valid so that the device firmware can be trusted by the CPU." msgstr "Щоб процесор вважав мікропрограму пристрою надійною, має бути чинним маніфест ключа Intel Management Engine." #. TRANSLATORS: longer description msgid "The Intel Management Engine controls device components and needs to have a recent version to avoid security issues." msgstr "Intel Management Engine керує компонентами пристроїв і потребує оновлення до найсвіжішої версії, щоб уникнути проблем із захистом." #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS є безкоштовною службою, яка працює як незалежна юридична одиниця і не має зв'язку з $OS_RELEASE:NAME$. Розробники вашої операційної системи, можливо, не перевіряли жодні з цих оновлень на сумісність із системою або з'єднаними із комп'ютером пристроями. Усі мікропрограми надаються лише самими виробниками обладнання." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Platform Configuration is used to check whether the device start process has been modified." msgstr "Налаштування платформи TPM (Trusted Platform Module або довірений модуль платформи) використовують для перевірки, чи не було внесено змін до процедури запуску пристрою." #. TRANSLATORS: longer description msgid "The TPM (Trusted Platform Module) Reconstruction is used to check whether the device start process has been modified." msgstr "Відтворення TPM (Trusted Platform Module або довірений модуль платформи) використовують для перевірки, чи не було внесено змін до процедури запуску пристрою." #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "PCR0 TPM відрізняється від реконструкції." #. TRANSLATORS: longer description msgid "The UEFI Platform Key is used to determine if device software comes from a trusted source." msgstr "Ключ платформи UEFI використовують для визначення, чи походить програмне забезпечення пристрою із надійного джерела." #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "Ця фонова служба завантажила сторонній код — її підтримка більше не здійснюється розробниками основної гілки програми!" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "Невідповідна версія пристрою: маємо %s, мало бути %s" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "Мікропрограма з %s не постачається %s, постачальником обладнання." #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "Годинник системи налаштовано неправильно — це може зашкодити отриманню файлів." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been re-inserted." msgstr "Оновлення буде продовжено після того, як кабель USB буде повторно вставлено до пристрою" #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged and then re-inserted." msgstr "Оновлення буде продовжено після того, як кабель USB буде вийнято з пристрою і вставлено знову." #. TRANSLATORS: warning message shown after update has been scheduled msgid "The update will continue when the device USB cable has been unplugged." msgstr "Оновлення буде продовжено після того, як кабель USB буде вийнято з пристрою." #. TRANSLATORS: warning message msgid "The update will continue when the device power cable has been removed and re-inserted." msgstr "Оновлення буде продовжено після того, як кабель живлення буде вийнято з пристрою і вставлено знову." #. TRANSLATORS: naughty vendor msgid "The vendor did not supply any release notes." msgstr "Виробником не надано ніяких нотаток щодо випуску." #. TRANSLATORS: now list devices with unfixed high-priority issues msgid "There are devices with issues:" msgstr "Маємо пристрої із проблемами:" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "Заблокованих файлів мікропрограм немає" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "Немає схвалених мікропрограм." #. TRANSLATORS: %1 is the current device version number, and %2 is the #. command name, e.g. `fwupdmgr sync` #, c-format msgid "This device will be reverted back to %s when the %s command is performed." msgstr "Цей пристрій буде повернуто до %s у результаті виконання команди %s." #. TRANSLATORS: the vendor did not upload this msgid "This firmware is provided by LVFS community members and is not provided (or supported) by the original hardware vendor." msgstr "Цю мікропрограму створено учасниками спільноти LVFS. Її не було надано початковим виробником обладнання. Виробник також не здійснюватиме підтримки цієї мікропрограми." #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "Цей пакунок не було перевірено. Його належну роботу не можна гарантувати." #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "Програма зможе працювати належними чином лише від імені користувача root" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "У цьому сховищі міститься мікропрограма, встановлювати яку не заборонено, але яка усе ще перебуває у процесі тестування виробником обладнання. Вам слід переконатися, що ви зможете встановити стабільну версію мікропрограми, якщо процедура оновлення зазнає невдачі." #. TRANSLATORS: error message msgid "This system doesn't support firmware settings" msgstr "У цій системі не передбачено підтримки параметрів мікропрограми" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "Ця система має вади у роботі HSI." #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "Ця система має низький рівень захисту HSI." #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "За допомогою цієї програми адміністратор може застосувати оновлення до dbx UEFI." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "За допомогою цього інструмента адміністратор може виконувати діагностику операції UpdateCapsule." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "За допомогою цього інструмента адміністратор може опитувати фонову службу fwupd і керувати нею. Це уможливлює виконання таких дій, як встановлення мікропрограм або зниження версії мікропрограм." #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "За допомогою цього інструмента адміністратор може скористатися додатками fwupd без встановлення їх до основної системи." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can add a kernel argument of '%s', but it will only be active after restarting the computer." msgstr "Цей інструмент може додавати аргумент ядра «%s», але нове значення буде задіяно лише після перезапуску комп'ютера." #. TRANSLATORS: the %1 is a BIOS setting name. #. * %2 and %3 are the values, e.g. "True" or "Windows10" #, c-format msgid "This tool can change the BIOS setting '%s' from '%s' to '%s' automatically, but it will only be active after restarting the computer." msgstr "Цей інструмент здатен змінити параметр BIOS «%s» з «%s» на «%s» автоматично, але нове значення буде задіяно лише після перезапуску комп'ютера." #. TRANSLATORS: the %1 is a kernel command line key=value #, c-format msgid "This tool can change the kernel argument from '%s' to '%s', but it will only be active after restarting the computer." msgstr "Цей інструмент може змінювати аргумент ядра з «%s» на «%s», але нове значення буде задіяно лише після перезапуску комп'ютера." #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "Цим інструментом може користуватися лише root" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "Цей інструмент читає і обробляє журнал подій TPM, який заповнюється мікропрограмами системи." #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "Проміжна помилка" #. TRANSLATORS: item is TRUE msgid "True" msgstr "Так" #. TRANSLATORS: We verified the metadata against the server msgid "Trusted metadata" msgstr "Надійні метадані" #. TRANSLATORS: We verified the payload against the server msgid "Trusted payload" msgstr "Надійні дані" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "Тип" #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI Bootservice Variables" msgstr "Змінні служби завантаження UEFI" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition may not be set up correctly" msgstr "Можливо, неправильно налаштовано розділ ESP UEFI" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "Розділ ESP UEFI не виявлено або не налаштовано" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "Засіб роботи із мікропрограмами UEFI" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI Platform Key" msgstr "Ключ платформи UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI Secure Boot" msgstr "Secure Boot UEFI" #. TRANSLATORS: longer description msgid "UEFI Secure Boot prevents malicious software from being loaded when the device starts." msgstr "Secure Boot UEFI запобігає завантаженню програмного забезпечення зловмисників при запуску пристрою." #. TRANSLATORS: longer description msgid "UEFI boot service variables should not be readable from runtime mode." msgstr "Змінні служби завантаження UEFI повинні бути недоступним для читання для режиму виконання програм." #. TRANSLATORS: Title: Bootservice is when only readable from early-boot msgid "UEFI bootservice variables" msgstr "Змінні служби завантаження UEFI" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "Капсульні оновлення UEFI є недоступними або їх не увімкнено у налаштуваннях мікропрограми" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "Засіб роботи з dbx UEFI" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "У режимі сумісності із застарілим BIOS не можна оновити мікропрограму UEFI" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "Ключ платформи UEFI" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "UEFI secure boot" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "Не вдалося встановити з'єднання зі службою" #. TRANSLATORS: error message msgid "Unable to find attribute" msgstr "Не вдалося знайти атрибут" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "Відв'язати поточний драйвер" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "Розблокуємо мікропрограму:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "Розблоковує встановлення певної мікропрограми" #. TRANSLATORS: command description msgid "Undo the host security attribute fix" msgstr "Скасувати виправлення атрибута захисту вузла" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "Розшифровано" #. TRANSLATORS: command description msgid "Uninhibit the system to allow upgrades" msgstr "Скасувати заборону системі дозволяти оновлення" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "Невідомий" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "Невідомий пристрій" msgid "Unlock the device to allow access" msgstr "Розблокування пристрою для отримання доступу" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "Розблоковано" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "Розблоковує пристрій для доступу до мікропрограми" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "Демонтує ESP" #. TRANSLATORS: message shown after device has been marked for emulation msgid "Unplug and replug the device to continue the update process." msgstr "Від'єднайте і повторно з'єднайте пристрій, щоб продовжити процедуру оновлення." #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "Зняти під час оновлення прапорець діагностики" #. TRANSLATORS: firmware payload is unsigned and it is possible to modify it msgid "Unsigned Payload" msgstr "Непідписаний вміст" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "Непідтримувана версія фонової служби %s, версія клієнта — %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "Очищене" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "Придатний до оновлення" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "Помилка оновлення" #. TRANSLATORS: helpful image for the update msgid "Update Image" msgstr "Зображення оновлення" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "Повідомлення щодо оновлення" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "Стан оновлення" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "Нам відомо про помилку під час оновлення. Будь ласка, відвідайте цю адресу, щоб дізнатися більше:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "Оновити зараз?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "Оновлення потребує перезавантаження" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "Оновити збережений криптографічний хеш на основі поточних даних ROM" msgid "Update the stored device verification information" msgstr "Оновлення збережених даних щодо верифікації пристрою" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "Оновити збережені метадані поточними даними" #. TRANSLATORS: command description msgid "Updates all specified devices to latest firmware version, or all devices if unspecified" msgstr "Оновлює усі вказані пристрої до найсвіжішої версії мікропрограми. Якщо пристрої не вказано, оновлює усі пристрої." msgid "Updating" msgstr "Оновлення" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "Оновлюємо %s з %s до %s... " #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "Оновлюємо %s…" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "Оновити %s з %s до %s?" #. TRANSLATORS: ask the user to share, %s is something #. * like: "Linux Vendor Firmware Service" #, c-format msgid "Upload these anonymous results to the %s to help other users?" msgstr "Вивантажити ці анонімні результати до %s, щоб допомогти іншим користувачам?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "Вивантаження звітів щодо мікропрограм допомагає виробникам обладнання швидко виявляти проблеми та дізнаватися про успішне оновлення на реальних пристроях." #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "Рівень важливості" #. TRANSLATORS: explain how to get help, #. * where $1 is something like 'fwupdmgr --help' #, c-format msgid "Use %s for help" msgstr "Скористайтеся командою %s, щоб дізнатися більше" #. TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the #. program msgid "Use CTRL^C to cancel." msgstr "Скористайтеся CTRL^C, щоб скасувати." #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "Скористайтеся fwupdtool --help, щоб дізнатися більше" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "Використовувати варіативні прапорці при встановленні мікропрограми" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "Сповіщено користувача" #. TRANSLATORS: remote filename base msgid "Username" msgstr "Користувач" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "Коректна" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "Перевіряємо дані ESP…" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "Варіант" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "Виробник" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "Перевіряємо…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "Версія" #. TRANSLATORS: the fwupd version the release was tested on msgid "Version[fwupd]" msgstr "Версія[fwupd]" #. TRANSLATORS: this is a prefix on the console msgid "WARNING" msgstr "УВАГА" #. TRANSLATORS: command description msgid "Wait for a device to appear" msgstr "Очікувати на появу пристрою" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "Очікуємо…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "Спостерігати за змінами у обладнанні" #. TRANSLATORS: check various UEFI and ACPI tables are unchanged after the #. update msgid "Will measure elements of system integrity around an update" msgstr "Виконає вимірювання елементів цілісності системи при оновленні" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "Записати мікропрограму з файла на пристрій" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "Записати мікропрограму з файла на один розділ" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "Записуємо файл:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "Записуємо…" #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from a recovery or installation disk, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Ви маєте переконатися, що зможете відновити конфігурацію за допомогою відновлювального диска або диска для встановлення, оскільки ця зміна може спричинити до неможливості завантаження системи Linux або спричинити інші нестабільності у роботі системи." #. TRANSLATORS: the user has to manually recover; we can't do it msgid "You should ensure you are comfortable restoring the setting from the system firmware setup, as this change may cause the system to not boot into Linux or cause other system instability." msgstr "Ви маєте переконатися, що зможете відновити параметр із налаштувань системної мікропрограми, оскільки ця зміна може спричинити до неможливості завантаження системи Linux або спричинити інші нестабільності у роботі системи." #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "Виробник вашого дистрибутива може не перевіряти усі оновлення мікропрограми на сумісність із вашою системою або з’єднаними із нею пристроями." #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "Ваше обладнання може бути пошкоджено через використання цієї мікропрограми. Встановлення цього випуску може призвести до відмови %s від гарантійних зобов'язань." #. TRANSLATORS: BKC is the industry name for the best known configuration and #. is a set #. * of firmware that works together #, c-format msgid "Your system is set up to the BKC of %s." msgstr "Вашу систему налаштовано на найкращу відому конфігурацію %s." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[APPSTREAM_ID]" msgstr "[ІДЕНТИФІКАТОР_APPSTREAM]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[КОНТРОЛЬНА_СУМА]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[ІД_ПРИСТРОЮ|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[ІД_ПРИСТРОЮ|GUID] [ГІЛКА]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [VERSION]" msgstr "[ІД_ПРИСТРОЮ|GUID] [ВЕРСІЯ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE]" msgstr "[ПРИСТРІЙ]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[ФАЙЛ ПІДПИС_ФАЙЛА ВІДДАЛ-ІД]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[НАЗВАФАЙЛА1] [НАЗВАФАЙЛА2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[REASON]" msgstr "[ПРИЧИНА]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [ SETTING2]..." msgstr "[ПАРАМЕТР1] [ПАРАМЕТР2]..." #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SETTING1] [SETTING2] [--no-authenticate]" msgstr "[ПАРАМЕТР1] [ПАРАМЕТР2] [--no-authenticate]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[ФАЙЛ-SMBIOS|ФАЙЛ-HWIDS]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "типова" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "Допоміжна програма для роботи із записами подій журналу TPM fwupd" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "Додатки fwupd" fwupd-1.9.16/po/zh_CN.po000066400000000000000000002066661460375044200147410ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Boyuan Yang <073plan@gmail.com>, 2017,2020,2022 # Dingzhong Chen , 2020-2021 # Dingzhong Chen , 2016-2019 # Mingcong Bai , 2017-2018 # Mingye Wang , 2016 # Mingye Wang , 2015 # Richard Hughes , 2023 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Chinese (China) (http://app.transifex.com/freedesktop/fwupd/language/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: zh_CN\n" "Plural-Forms: nplurals=1; plural=0;\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "还剩 %.0f 分钟" #. TRANSLATORS: battery refers to the system power source #, c-format msgid "%s Battery Update" msgstr "%s 电池更新" #. TRANSLATORS: the CPU microcode is firmware loaded onto the CPU #. * at system bootup #, c-format msgid "%s CPU Microcode Update" msgstr "%s CPU 微码更新" #. TRANSLATORS: camera can refer to the laptop internal #. * camera in the bezel or external USB webcam #, c-format msgid "%s Camera Update" msgstr "%s 摄像头更新" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Secure Boot` #, c-format msgid "%s Configuration Update" msgstr "%s 配置更新" #. TRANSLATORS: ME stands for Management Engine, where #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Consumer ME Update" msgstr "%s 消费者 ME 更新" #. TRANSLATORS: the controller is a device that has other devices #. * plugged into it, for example ThunderBolt, FireWire or USB, #. * the first %s is the device name, e.g. 'Intel ThunderBolt` #, c-format msgid "%s Controller Update" msgstr "%s 控制器更新" #. TRANSLATORS: ME stands for Management Engine (with Intel AMT), #. * where the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Corporate ME Update" msgstr "%s 企业 ME 更新" #. TRANSLATORS: a specific part of hardware, #. * the first %s is the device name, e.g. 'Unifying Receiver` #, c-format msgid "%s Device Update" msgstr "%s 设备更新" #. TRANSLATORS: the EC is typically the keyboard controller chip, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Embedded Controller Update" msgstr "%s 嵌入式控制器更新" #. TRANSLATORS: Keyboard refers to an input device for typing #, c-format msgid "%s Keyboard Update" msgstr "%s 键盘更新" #. TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s ME Update" msgstr "%s 管理引擎(ME)更新" #. TRANSLATORS: Mouse refers to a handheld input device #, c-format msgid "%s Mouse Update" msgstr "%s 鼠标更新" #. TRANSLATORS: Network Interface refers to the physical #. * PCI card, not the logical wired connection #, c-format msgid "%s Network Interface Update" msgstr "%s 网络接口更新" #. TRANSLATORS: Storage Controller is typically a RAID or SAS adapter #, c-format msgid "%s Storage Controller Update" msgstr "%s 存储控制器更新" #. TRANSLATORS: the entire system, e.g. all internal devices, #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s System Update" msgstr "%s 系统更新" #. TRANSLATORS: TPM refers to a Trusted Platform Module #, c-format msgid "%s TPM Update" msgstr "%s TPM 更新" #. TRANSLATORS: the Thunderbolt controller is a device that #. * has other high speed Thunderbolt devices plugged into it; #. * the first %s is the system name, e.g. 'ThinkPad P50` #, c-format msgid "%s Thunderbolt Controller Update" msgstr "%s Thunderbolt 控制器更新" #. TRANSLATORS: TouchPad refers to a flat input device #, c-format msgid "%s Touchpad Update" msgstr "%s 触摸板更新" #. TRANSLATORS: this is the fallback where we don't know if the release #. * is updating the system, the device, or a device class, or something else #. -- #. * the first %s is the device name, e.g. 'ThinkPad P50` #, c-format msgid "%s Update" msgstr "%s 更新" #. TRANSLATORS: warn the user before updating, %1 is a device name #, c-format msgid "%s and all connected devices may not be usable while updating." msgstr "更新期间 %s 和所有连接的设备可能无法使用。" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s manufacturing mode" msgstr "%s 生产模式" #. TRANSLATORS: warn the user before #. * updating, %1 is a device name #, c-format msgid "%s must remain connected for the duration of the update to avoid damage." msgstr "更新期间必须保持 %s 的连接以避免损坏。" #. TRANSLATORS: warn the user before updating, %1 is a machine #. * name #, c-format msgid "%s must remain plugged into a power source for the duration of the update to avoid damage." msgstr "更新期间必须保持 %s 接入电源以避免损坏。" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s override" msgstr "%s 覆写" #. TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number #, c-format msgid "%s v%s" msgstr "%s v%s" #. TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT #, c-format msgid "%s version" msgstr "%s 版本" #. TRANSLATORS: duration in days! #, c-format msgid "%u day" msgid_plural "%u days" msgstr[0] "%u 天" #. TRANSLATORS: this is shown in the MOTD #, c-format msgid "%u device has a firmware upgrade available." msgid_plural "%u devices have a firmware upgrade available." msgstr[0] "%u 个设备有可升级的固件。" #. TRANSLATORS: duration in minutes #, c-format msgid "%u hour" msgid_plural "%u hours" msgstr[0] "%u 小时" #. TRANSLATORS: how many local devices can expect updates now #, c-format msgid "%u local device supported" msgid_plural "%u local devices supported" msgstr[0] "%u 个本地设备受支持" #. TRANSLATORS: duration in minutes #, c-format msgid "%u minute" msgid_plural "%u minutes" msgstr[0] "%u 分钟" #. TRANSLATORS: duration in seconds #, c-format msgid "%u second" msgid_plural "%u seconds" msgstr[0] "%u 秒" #. TRANSLATORS: this is shown as a suffix for obsoleted tests msgid "(obsoleted)" msgstr "(已过时)" #. TRANSLATORS: the user needs to do something, e.g. remove the device msgid "Action Required:" msgstr "所需操作:" #. TRANSLATORS: command description msgid "Activate devices" msgstr "激活设备" #. TRANSLATORS: command description msgid "Activate pending devices" msgstr "激活待处理的设备" msgid "Activate the new firmware on the device" msgstr "激活设备上的新固件" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update" msgstr "正在激活固件更新" #. TRANSLATORS: shown when shutting down to switch to the new version msgid "Activating firmware update for" msgstr "正在激活固件更新给" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "已发布时间" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "同意并启用此远程源吗?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "%s 的别名" #. TRANSLATORS: on some systems certain devices have to have matching #. versions, #. * e.g. the EFI driver for a given network card cannot be different msgid "All devices of the same type will be updated at the same time" msgstr "所有相同类型的设备会在相同一时间更新" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "允许降级固件版本" #. TRANSLATORS: command line option msgid "Allow reinstalling existing firmware versions" msgstr "允许重新安装现有的固件版本" #. TRANSLATORS: command line option msgid "Allow switching firmware branch" msgstr "允许切换固件分支" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "更新需要重启设备才能完成。" #. TRANSLATORS: explain why msgid "An update requires the system to shutdown to complete." msgstr "更新需要系统关机才能完成。" #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "一律用“是”回答问题" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "应用固件更新" #. TRANSLATORS: command line option msgid "Apply update even when not advised" msgstr "应用更新,即使不建议" #. TRANSLATORS: command line option msgid "Apply update files" msgstr "应用更新文件" #. TRANSLATORS: actually sending the update to the hardware msgid "Applying update…" msgstr "正在应用更新……" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "Approved firmware:" msgid_plural "Approved firmware:" msgstr[0] "批准的固件:" #. TRANSLATORS: stop nagging the user msgid "Ask again next time?" msgstr "下次再次询问?" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "附加到固件模式" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "正在认证…" #. TRANSLATORS: user needs to run a command msgid "Authentication details are required" msgstr "需要身份验证详细信息" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "需要认证:在可移动设备上降级固件" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "需要认证:在此机器上降级固件" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "需要认证:修改用于固件更新的已配置远程源" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify daemon configuration" msgstr "需要认证:修改守护进程配置" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to set the list of approved firmware" msgstr "需要认证:设定批准固件的列表" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to sign data using the client certificate" msgstr "需要认证:使用客户端证书签名数据" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to switch to the new firmware version" msgstr "需要认证:切换到新固件版本" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "需要认证:解锁设备" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "需要认证:在可移动设备上升级固件" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "需要认证:在此机器上升级固件" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "需要认证:为设备更新存储的校验和" #. TRANSLATORS: Boolean value to automatically send reports msgid "Automatic Reporting" msgstr "自动报告" #. TRANSLATORS: can we JFDI? msgid "Automatically upload every time?" msgstr "每次都自动上传?" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "BUILDER-XML FILENAME-DST" msgstr "生成器XML 目标文件名" msgid "BYTES" msgstr "字节" #. TRANSLATORS: command description msgid "Bind new kernel driver" msgstr "绑定新内核驱动" #. TRANSLATORS: there follows a list of hashes msgid "Blocked firmware files:" msgstr "受阻的固件文件:" #. TRANSLATORS: we will not offer this firmware to the user msgid "Blocking firmware:" msgstr "阻止的固件:" #. TRANSLATORS: command description msgid "Blocks a specific firmware from being installed" msgstr "阻止指定的固件被安装" #. TRANSLATORS: firmware version of bootloader msgid "Bootloader Version" msgstr "引导程序版本" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Branch" msgstr "分支" #. TRANSLATORS: command description msgid "Build a firmware file" msgstr "生成固件文件" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "取消" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "已取消" #. TRANSLATORS: same or newer update already applied msgid "Cannot apply as dbx update has already been applied." msgstr "无法应用 dbx 更新因其更新已应用。" #. TRANSLATORS: the user is using a LiveCD or LiveUSB install disk msgid "Cannot apply updates on live media" msgstr "无法在 Live 系统介质上应用更新" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "已变更" #. TRANSLATORS: command description msgid "Checks cryptographic hash matches firmware" msgstr "检查加密哈希与固件是否匹配" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "校验和" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "清除从最后一次更新获取的结果" #. TRANSLATORS: error message msgid "Command not found" msgstr "未找到命令" #. TRANSLATORS: command description msgid "Convert a firmware file" msgstr "转换固件文件" #. TRANSLATORS: when the update was built msgid "Created" msgstr "创建日期" #. TRANSLATORS: the release urgency msgid "Critical" msgstr "关键" #. TRANSLATORS: Device supports some form of checksum verification msgid "Cryptographic hash verification is available" msgstr "支持加密哈希验证" #. TRANSLATORS: version number of current firmware msgid "Current version" msgstr "当前版本" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "DEVICE-ID|GUID" msgstr "设备ID|GUID" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "固件更新实用程序" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "调试选项" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "正在解压缩…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "描述" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "拆离到引导加载器模式" #. TRANSLATORS: more details about the update link msgid "Details" msgstr "详情" #. TRANSLATORS: description of device ability msgid "Device Flags" msgstr "设备标志" #. TRANSLATORS: ID for hardware, typically a SHA1 sum msgid "Device ID" msgstr "设备 ID" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "已添加设备:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device can recover flash failures" msgstr "设备可从刷机失败恢复" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "已更改设备:" #. TRANSLATORS: a version check is required for all firmware msgid "Device firmware is required to have a version check" msgstr "设备固件需要版本检查" #. TRANSLATORS: Is locked and can be unlocked msgid "Device is locked" msgstr "设备已锁定" #. TRANSLATORS: the device cannot update from A->C and has to go A->B->C msgid "Device is required to install all provided releases" msgstr "设备需要安装所有提供的版本" #. TRANSLATORS: currently unreachable, perhaps because it is in a lower power #. state #. * or is out of wireless range msgid "Device is unreachable" msgstr "设备不可访问" #. TRANSLATORS: Device remains usable during update msgid "Device is usable for the duration of the update" msgstr "设备在更新期间可使用" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "已移除设备:" #. TRANSLATORS: Device supports a safety mechanism for flashing msgid "Device stages updates" msgstr "设备阶段更新" #. TRANSLATORS: there is more than one supplier of the firmware msgid "Device supports switching to a different branch of firmware" msgstr "设备支持切换到不同的固件分支" #. TRANSLATORS: command line option msgid "Device update method" msgstr "设备更新方法" #. TRANSLATORS: Device update needs to be separately activated msgid "Device update needs activation" msgstr "设备更新需要激活" #. TRANSLATORS: save the old firmware to disk before installing the new one msgid "Device will backup firmware before installing" msgstr "设备将在安装前备份固件" #. TRANSLATORS: Device will not return after update completes msgid "Device will not re-appear after update completes" msgstr "更新完后设备不会再显示" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "成功更新的设备:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "未正确更新的设备:" #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no device #. * upgrade available due to missing on LVFS #. TRANSLATORS: message letting the user know no #. * device upgrade available due to missing on LVFS msgid "Devices with no available firmware updates: " msgstr "无固件更新可用的设备:" #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available #. TRANSLATORS: message letting the user know no device upgrade available #. TRANSLATORS: message letting the user know no device upgrade #. * available msgid "Devices with the latest available firmware version:" msgstr "固件已是最新的设备:" #. TRANSLATORS: Plugin is inactive and not used #. TRANSLATORS: Suffix: the HSI result msgid "Disabled" msgstr "已禁用" msgid "Disabled fwupdate debugging" msgstr "禁用 fwupdate 调试" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "禁用给定的远程源" #. TRANSLATORS: command line option msgid "Display version" msgstr "显示版本" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "不要查找老的元数据" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "不要查找未报告历史" #. TRANSLATORS: command line option msgid "Do not check if download remotes should be enabled" msgstr "不要检查下载远程是否已启用" #. TRANSLATORS: command line option msgid "Do not check or prompt for reboot after update" msgstr "更新后不要检查或提示是否要重启" #. TRANSLATORS: turn on all debugging msgid "Do not include log domain prefix" msgstr "不包含日志域前缀" #. TRANSLATORS: turn on all debugging msgid "Do not include timestamp prefix" msgstr "不包含时间戳前缀" #. TRANSLATORS: command line option msgid "Do not perform device safety checks" msgstr "不要执行设备安全检查" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "不要写入历史数据库" #. TRANSLATORS: should the branch be changed msgid "Do you understand the consequences of changing the firmware branch?" msgstr "你清楚更改固件分支的后果吗?" #. TRANSLATORS: offer to disable this nag msgid "Do you want to disable this feature for future updates?" msgstr "你要在将来的更新中禁用此功能吗?" #. TRANSLATORS: ask if we can update the metadata msgid "Do you want to refresh this remote now?" msgstr "你要现在刷新这个远程库吗?" #. TRANSLATORS: offer to stop asking the question msgid "Do you want to upload reports automatically for future updates?" msgstr "你要将来的更新中自动上传报告吗?" #. TRANSLATORS: success #. success msgid "Done!" msgstr "完成!" #. TRANSLATORS: message letting the user know an downgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Downgrade %s from %s to %s?" msgstr "降级 %s,从 %s 到 %s?" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "降级设备上的固件" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "正在降级 %s,从 %s 到 %s…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Downgrading %s…" msgstr "正在下载 %s……" #. TRANSLATORS: command description msgid "Download a file" msgstr "下载文件" #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "正在下载..." #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "从文件转储 SMBIOS 数据" #. TRANSLATORS: length of time the update takes to apply msgid "Duration" msgstr "时长" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "指定的 ESP 无效" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "在所支持的系统上启用固件更新的支持" #. TRANSLATORS: a remote here is like a 'repo' or software source msgid "Enable new remote?" msgstr "£启用新的远程库" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "要启用此远程源吗?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "已启用" msgid "Enabled fwupdate debugging" msgstr "启用 fwupdate 调试" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "启用给定的远程源" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "启用此功能的风险自负,这意味着你必须就这些更新引起的任何问题与原始设备制造商联系。只有更新过程本身的问题可以提交到 $OS_RELEASE:BUG_REPORT_URL$。" #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "启用此远程源后果自负。" #. TRANSLATORS: Suffix: the HSI result msgid "Encrypted" msgstr "已加密" #. TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME msgid "Encrypted RAM" msgstr "加密内存" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "清除所有固件更新历史" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "正在擦除…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "在短暂的延迟后退出" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "在引擎加载后退出" #. TRANSLATORS: command description msgid "Export a firmware file structure to XML" msgstr "导出 XML 结构的固件文件" #. TRANSLATORS: command description msgid "Extract a firmware blob to images" msgstr "提取固件比特块到映像" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE" msgstr "文件" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILE [DEVICE-ID|GUID]" msgstr "文件 [设备ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME" msgstr "文件名" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME CERTIFICATE PRIVATE-KEY" msgstr "文件名称 证书 私钥" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID" msgstr "文件名 设备替代名称|设备替代ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ALT-NAME|DEVICE-ALT-ID [IMAGE-ALT-NAME|IMAGE-ALT-ID]" msgstr "文件名 设备替代名称|设备替代ID [映像替代名称|映像替代ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME DEVICE-ID" msgstr "文件名 设备ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [DEVICE-ID|GUID]" msgstr "文件名 [设备ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME [FIRMWARE-TYPE]" msgstr "文件名 [固件类型]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]" msgstr "源文件名 目标文件名 [源固件类型] [目标固件类型]" #. TRANSLATORS: the update state of the specific device msgid "Failed" msgstr "已失败" #. TRANSLATORS: dbx file failed to be applied as an update msgid "Failed to apply update" msgstr "应用更新失败" #. TRANSLATORS: we could not talk to the fwupd daemon #. TRANSLATORS: could not contact the fwupd service over D-Bus msgid "Failed to connect to daemon" msgstr "连接到守护进程失败" #. TRANSLATORS: we could not get the devices to update offline msgid "Failed to get pending devices" msgstr "获取待处理的设备失败" #. TRANSLATORS: we could not install for some reason msgid "Failed to install firmware update" msgstr "安装固件更新失败" #. TRANSLATORS: could not read existing system data #. TRANSLATORS: could not read file msgid "Failed to load local dbx" msgstr "加载本地 dbx 失败" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "载入特定问题失败" #. TRANSLATORS: could not read existing system data msgid "Failed to load system dbx" msgstr "加载系统 dbx 失败" #. TRANSLATORS: another fwupdtool instance is already running msgid "Failed to lock" msgstr "锁定失败" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "未能解析参数" #. TRANSLATORS: failed to read measurements file msgid "Failed to parse file" msgstr "解析文件失败" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse flags for --filter" msgstr "无法解析 --filter 的标志" #. TRANSLATORS: could not parse file msgid "Failed to parse local dbx" msgstr "解析本地 dbx 失败" #. TRANSLATORS: we could not reboot for some reason msgid "Failed to reboot" msgstr "重启失败" #. TRANSLATORS: we could not talk to plymouth msgid "Failed to set splash mode" msgstr "设定启动屏幕模式失败" #. TRANSLATORS: something with a blocked hash exists #. * in the users ESP -- which would be bad! msgid "Failed to validate ESP contents" msgstr "验证 ESP 内容失败" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "文件名" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "文件名签名" #. TRANSLATORS: full path of the remote.conf file msgid "Filename Source" msgstr "文件来源" #. TRANSLATORS: user did not include a filename parameter msgid "Filename required" msgstr "需要文件名" #. TRANSLATORS: command line option msgid "Filter with a set of device flags using a ~ prefix to exclude, e.g. 'internal,~needs-reboot'" msgstr "筛选器为一组设备标志,使用 ~ 前缀来排除设备,示例 \"internal,~needs-reboot\"" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "固件库 URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "固件更新 D-Bus 服务" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "固件更新守护程序" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "固件实用程序" #. TRANSLATORS: Title: if we can verify the firmware checksums msgid "Firmware attestation" msgstr "固件证明" #. TRANSLATORS: user selected something not possible msgid "Firmware is already blocked" msgstr "固件已被阻止" #. TRANSLATORS: user selected something not possible msgid "Firmware is not already blocked" msgstr "固件已经不受阻" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "固件元数据已有 %u 天未更新,数据可能不是最新的。" #. TRANSLATORS: Title: if firmware updates are available msgid "Firmware updates" msgstr "固件更新" msgid "Firmware updates are not supported on this machine." msgstr "此机器不支持固件更新。" msgid "Firmware updates are supported on this machine." msgstr "此机器支持固件更新。" #. TRANSLATORS: user needs to run a command msgid "Firmware updates disabled; run 'fwupdmgr unlock' to enable" msgstr "固件更新已禁用;运行“fwupdmgr unlock”以启用" #. TRANSLATORS: description of plugin state, e.g. disabled msgid "Flags" msgstr "标志" #. TRANSLATORS: command line option msgid "Force the action by relaxing some runtime checks" msgstr "通过放宽一些运行时检查来强制操作" msgid "Force the action ignoring all warnings" msgstr "强制操作忽略所有警告" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "找到" #. TRANSLATORS: title text, shown as a warning msgid "Full Disk Encryption Detected" msgstr "探测到全盘加密" #. TRANSLATORS: global ID common to all similar hardware msgid "GUID" msgid_plural "GUIDs" msgstr[0] "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all device flags supported by fwupd" msgstr "获取 fwupd 支持的全部设备标志" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "获得所有支持更新固件的硬件列表" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "获取所有在系统注册的启用插件" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "获取有关某固件文件的详细信息" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "获取已配置的远程源" #. TRANSLATORS: command description msgid "Gets the host security attributes" msgstr "获取主机安全属性" #. TRANSLATORS: firmware approved by the admin msgid "Gets the list of approved firmware" msgstr "获取已批准固件的列表" #. TRANSLATORS: command description msgid "Gets the list of blocked firmware" msgstr "获取受阻固件的列表" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "获取已连接硬件的可用更新列表" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "获取用于设备的发行版本" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "从最后一次更新中获取结果" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "HWIDS-FILE" msgstr "HWIDS文件" #. TRANSLATORS: the hardware is waiting to be replugged msgid "Hardware is waiting to be replugged" msgstr "硬件正等待重新插入" #. TRANSLATORS: the release urgency msgid "High" msgstr "高" #. TRANSLATORS: this is a string like 'HSI:2-U' msgid "Host Security ID:" msgstr "主机安全 ID" #. TRANSLATORS: Title: #. * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit msgid "IOMMU" msgstr "IOMMU" #. TRANSLATORS: HSI event title msgid "IOMMU device protection disabled" msgstr "IOMMU 设备保护已禁用" #. TRANSLATORS: HSI event title msgid "IOMMU device protection enabled" msgstr "IOMMU 设备保护已启用" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "空闲…" #. TRANSLATORS: command line option msgid "Ignore SSL strict checks when downloading files" msgstr "下载文件时忽略 SSL 严格检查" #. TRANSLATORS: command line option msgid "Ignore firmware checksum failures" msgstr "忽略固件校验和错误" #. TRANSLATORS: command line option msgid "Ignore firmware hardware mismatch failures" msgstr "忽略固件硬件不匹配错误" #. TRANSLATORS: Ignore validation safety checks when flashing this device msgid "Ignore validation safety checks" msgstr "忽略验证安全检查" #. TRANSLATORS: try to help msgid "Ignoring SSL strict checks, to do this automatically in the future export DISABLE_SSL_STRICT in your environment" msgstr "正在忽略 SSL 严格检查,要在之后自动应用这一选项请在你的环境里 export DISABLE_SSL_STRICT" #. TRANSLATORS: length of time the update takes to apply msgid "Install Duration" msgstr "安装时长" msgid "Install old version of signed system firmware" msgstr "安装旧版本的已签名系统固件" msgid "Install old version of unsigned system firmware" msgstr "安装旧版本的未签名系统固件" msgid "Install signed device firmware" msgstr "安装已签名的设备固件" msgid "Install signed system firmware" msgstr "安装已签名的系统固件" #. TRANSLATORS: Install composite firmware on the parent before the child msgid "Install to parent device first" msgstr "首先安装到上级设备" msgid "Install unsigned device firmware" msgstr "安装未签名的设备固件" msgid "Install unsigned system firmware" msgstr "安装未签名的系统固件" #. TRANSLATORS: console message when no Plymouth is installed msgid "Installing Firmware…" msgstr "正在安装固件……" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "正在安装固件更新..." #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "正在安装到 %s……" #. TRANSLATORS: Title: BootGuard is a trademark from Intel msgid "Intel BootGuard" msgstr "Intel BootGuard" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * ACM means to verify the integrity of Initial Boot Block msgid "Intel BootGuard ACM protected" msgstr "Intel BootGuard ACM 保护" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * OTP = one time programmable msgid "Intel BootGuard OTP fuse" msgstr "Intel BootGuard OTP 保险丝" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * error policy is what to do on failure msgid "Intel BootGuard error policy" msgstr "Intel BootGuard 错误策略" #. TRANSLATORS: Title: BootGuard is a trademark from Intel, #. * verified boot refers to the way the boot process is verified msgid "Intel BootGuard verified boot" msgstr "Intel BootGuard 验任启动" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * active means being used by the OS msgid "Intel CET Active" msgstr "Intel CET 激活" #. TRANSLATORS: Title: CET = Control-flow Enforcement Technology, #. * enabled means supported by the processor msgid "Intel CET Enabled" msgstr "Intel CET 支持" #. TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention msgid "Intel SMAP" msgstr "Intel SMAP" #. TRANSLATORS: Device cannot be removed easily msgid "Internal device" msgstr "内部设备" #. TRANSLATORS: Suffix: the HSI result msgid "Invalid" msgstr "无效" #. TRANSLATORS: Is currently in bootloader mode msgid "Is in bootloader mode" msgstr "处于引导程序模式" #. TRANSLATORS: issue fixed with the release, e.g. CVE msgid "Issue" msgid_plural "Issues" msgstr[0] "问题" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "KEY,VALUE" msgstr "键,值" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "密钥环" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "LOCATION" msgstr "位置" #. TRANSLATORS: the original time/date the device was modified msgid "Last modified" msgstr "最后修改" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "还剩不到一分钟" #. TRANSLATORS: e.g. GPLv2+, Proprietary etc msgid "License" msgstr "许可证" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux 供应商固件服务(稳定固件)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux 供应商固件服务(测试固件)" #. TRANSLATORS: Title: if it's tainted or not msgid "Linux kernel" msgstr "Linux 内核" #. TRANSLATORS: Title: lockdown is a security mode of the kernel msgid "Linux kernel lockdown" msgstr "Linux 内核锁定" #. TRANSLATORS: Title: swap space or swap partition msgid "Linux swap" msgstr "Linux 交换区" #. TRANSLATORS: command line option msgid "List entries in dbx" msgstr "列出 dbx 中的条目" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "列出所支持固件的更新" #. TRANSLATORS: command description msgid "List the available firmware types" msgstr "列出可用的固件类型" #. TRANSLATORS: command description msgid "Lists files on the ESP" msgstr "列出 ESP 分区中的文件" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "正在加载…" #. TRANSLATORS: Suffix: the HSI result msgid "Locked" msgstr "已锁定" #. TRANSLATORS: the release urgency msgid "Low" msgstr "低" #. TRANSLATORS: Title: MEI = Intel Management Engine msgid "MEI manufacturing mode" msgstr "MEI 生产模式" #. TRANSLATORS: Title: MEI = Intel Management Engine, and the #. * "override" is the physical PIN that can be driven to #. * logic high -- luckily it is probably not accessible to #. * end users on consumer boards msgid "MEI override" msgstr "MEI 覆写" msgid "MEI version" msgstr "MEI 版本" #. TRANSLATORS: command line option msgid "Manually enable specific plugins" msgstr "手动启用指定插件" #. TRANSLATORS: the release urgency msgid "Medium" msgstr "中" #. TRANSLATORS: remote URI msgid "Metadata Signature" msgstr "元数据签名" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "元数据 URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "可从 Linux 供应商固件服务获取元数据。" #. TRANSLATORS: smallest version number installable on device msgid "Minimum Version" msgstr "最小版本" #. TRANSLATORS: error message #, c-format msgid "Mismatched daemon and client, use %s instead" msgstr "守护进程与客户端不匹配,使用 %s 来代替" #. TRANSLATORS: sets something in the daemon configuration file msgid "Modifies a daemon configuration value" msgstr "修改守护进程的配置值" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "修改给定的远程源" msgid "Modify a configured remote" msgstr "修改已配置的远程源" msgid "Modify daemon configuration" msgstr "修改守护进程配置" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "监视守护程序里的事件" #. TRANSLATORS: command description msgid "Mounts the ESP" msgstr "挂载 ESP 分区" #. TRANSLATORS: Requires a reboot to apply firmware or to reload hardware msgid "Needs a reboot after installation" msgstr "安装完后需要重启" #. TRANSLATORS: the update state of the specific device msgid "Needs reboot" msgstr "需要重启" #. TRANSLATORS: Requires system shutdown to apply firmware msgid "Needs shutdown after installation" msgstr "安装完后需要关机" #. TRANSLATORS: version number of new firmware msgid "New version" msgstr "新版本" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "未指定操作!" #. TRANSLATORS: message letting the user know no device downgrade available #. * %1 is the device name #, c-format msgid "No downgrades for %s" msgstr "%s 无可用降级" #. TRANSLATORS: nothing found msgid "No firmware IDs found" msgstr "未找到固件 ID" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "没有检测到支持更新固件的硬件" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "未找到插件" #. TRANSLATORS: no repositories to download from msgid "No releases available" msgstr "无可用发布版本" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "当前尚未启用任何远程源,因此无可用元数据。" #. TRANSLATORS: no repositories to download from msgid "No remotes available" msgstr "无可用远程源" msgid "No updates available for remaining devices" msgstr "其余设备没有可用更新" #. TRANSLATORS: nothing was updated offline msgid "No updates were applied" msgstr "没有应用任何更新" #. TRANSLATORS: Suffix: the HSI result msgid "Not found" msgstr "未找到" #. TRANSLATORS: Suffix: the HSI result msgid "Not supported" msgstr "未支持" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "确定" #. TRANSLATORS: command line option msgid "Only show single PCR value" msgstr "只显示单 PCR 值" #. TRANSLATORS: some devices can only be updated to a new semver and cannot #. * be downgraded or reinstalled with the existing version msgid "Only version upgrades are allowed" msgstr "只允许版本升级" #. TRANSLATORS: command line option msgid "Output in JSON format" msgstr "以 JSON 格式输出" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "覆盖默认的 ESP 路径" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "PATH" msgstr "路径" #. TRANSLATORS: command description msgid "Parse and show details about a firmware file" msgstr "解析并显示固件文件的详细信息" #. TRANSLATORS: reading new dbx from the update msgid "Parsing dbx update…" msgstr "正在解析 dbx 更新……" #. TRANSLATORS: reading existing dbx from the system msgid "Parsing system dbx…" msgstr "正在解析系统 dbx……" #. TRANSLATORS: remote filename base msgid "Password" msgstr "密码" #. TRANSLATORS: command description msgid "Patch a firmware blob at a known offset" msgstr "在已知偏移量处对固件二进制文件打补丁" msgid "Payload" msgstr "载荷" #. TRANSLATORS: the update state of the specific device msgid "Pending" msgstr "待处理" #. TRANSLATORS: console message when not using plymouth msgid "Percentage complete" msgstr "完成百分比" #. TRANSLATORS: prompt to apply the update msgid "Perform operation?" msgstr "执行操作?" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "请输入一个 0 到 %u 之间的数字:" #. TRANSLATORS: Failed to open plugin, hey Arch users msgid "Plugin dependencies missing" msgstr "插件依赖缺失" #. TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack msgid "Pre-boot DMA protection" msgstr "预启动 DMA 保护" #. TRANSLATORS: version number of previous firmware msgid "Previous version" msgstr "上个版本" msgid "Print the version number" msgstr "打印版本号" msgid "Print verbose debug statements" msgstr "打印详细调试语句" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "优先级" msgid "Proceed with upload?" msgstr "确定要上传吗?" #. TRANSLATORS: a non-free software license msgid "Proprietary" msgstr "私有" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "查询固件更新的支持" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID" msgstr "远程ID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "REMOTE-ID KEY VALUE" msgstr "远程ID 键 值" #. TRANSLATORS: command description msgid "Read a firmware blob from a device" msgstr "从设备读取固件比特块(blob)" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "将来自设备的固件读入文件" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "将来自分区的固件读入文件" #. TRANSLATORS: %1 is a device name #, c-format msgid "Reading from %s…" msgstr "正在读取 %s……" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "正在读取…" #. TRANSLATORS: console message when not using plymouth msgid "Rebooting…" msgstr "正在重启……" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "刷新来自远程服务器的元数据" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 is a version string #, c-format msgid "Reinstall %s to %s?" msgstr "重新安装 %s 到 %s?" #. TRANSLATORS: command description msgid "Reinstall current firmware on the device" msgstr "在设备上重新安装当前的固件" #. TRANSLATORS: command description msgid "Reinstall firmware on a device" msgstr "重新安装设备上的固件" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "正在重新安装 %s,使用 %s…" #. TRANSLATORS: the stream of firmware, e.g. nonfree or open-source msgid "Release Branch" msgstr "发行分支" #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "远程源 ID" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "用已有的固件文件替换数据" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "报告 URI" #. TRANSLATORS: Has been reported to a metadata server msgid "Reported to remote server" msgstr "已报告到远程服务器" #. TRANSLATORS: the user is using Gentoo/Arch and has screwed something up msgid "Required efivarfs filesystem was not found" msgstr "所需 efivarfs 文件系统未找到" #. TRANSLATORS: not required for this system msgid "Required hardware was not found" msgstr "所需设备未找到" #. TRANSLATORS: Requires a bootloader mode to be manually enabled by the user msgid "Requires a bootloader" msgstr "需要引导程序" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "需要互联网连接" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "要现在重启设备吗?" #. TRANSLATORS: changes only take effect on restart msgid "Restart the daemon to make the change effective?" msgstr "重启守护进程以让更改生效?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "正在重启设备…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "返回机器的所有硬件 ID" #. TRANSLATORS: this is shown in the MOTD msgid "Run `fwupdmgr get-upgrades` for more information." msgstr "运行 \"fwupdmgr get-upgrades\" 获取更多信息。" #. TRANSLATORS: command line option msgid "Run the plugin composite cleanup routine when using install-blob" msgstr "当使用安装比特块时运行插件组合清理例程" #. TRANSLATORS: command line option msgid "Run the plugin composite prepare routine when using install-blob" msgstr "当使用安装比特块时运行插件组合准备例程" #. TRANSLATORS: The kernel does not support this plugin msgid "Running kernel is too old" msgstr "运行的内核太老旧" #. TRANSLATORS: this is the HSI suffix msgid "Runtime Suffix" msgstr "运行时后缀" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS Descriptor" msgstr "SPI BIOS 描述符" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI BIOS region" msgstr "SPI BIOS 区域" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI lock" msgstr "SPI 锁" #. TRANSLATORS: Title: SPI refers to the flash chip in the computer msgid "SPI write" msgstr "SPI 写入" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "SUBSYSTEM DRIVER [DEVICE-ID|GUID]" msgstr "子系统 驱动 [设备ID|GUID]" #. TRANSLATORS: command description msgid "Save a file that allows generation of hardware IDs" msgstr "保存允许生成硬件 ID 的文件" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "如有可能,安排安装到下次重启" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "正在计划…" #. TRANSLATORS: HSI event title msgid "Secure Boot disabled" msgstr "安全启动已禁用" #. TRANSLATORS: HSI event title msgid "Secure Boot enabled" msgstr "安全启动已启用" #. TRANSLATORS: %s is a link to a website #, c-format msgid "See %s for more information." msgstr "查看 %s 获取更多信息。" #. TRANSLATORS: device has been chosen by the daemon for the user msgid "Selected device" msgstr "所选设备" #. TRANSLATORS: Volume has been chosen by the user msgid "Selected volume" msgstr "所选卷" #. TRANSLATORS: serial number of hardware msgid "Serial Number" msgstr "序列号" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "更新期间设定调试标志" #. TRANSLATORS: firmware approved by the admin msgid "Sets the list of approved firmware" msgstr "设定批准固件的列表" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "与开发者分享固件历史" #. TRANSLATORS: command line option msgid "Show all results" msgstr "显示所有结果" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "显示客户端及守护程序版本" #. TRANSLATORS: this is for daemon development msgid "Show daemon verbose information for a particular domain" msgstr "显示特定域的守护进程详细信息" #. TRANSLATORS: turn on all debugging msgid "Show debugging information for all domains" msgstr "显示所有域的调试信息" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "显示调试选项" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "显示不可更新的设备" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "显示额外调试信息" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "显示固件更新历史" #. TRANSLATORS: command line option msgid "Show the calculated version of the dbx" msgstr "显示 dbx 的计算版本" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "显示上次尝试更新的调试日志" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "显示固件更新状态的信息" #. TRANSLATORS: shutdown to apply the update msgid "Shutdown now?" msgstr "现在关机?" #. TRANSLATORS: command description msgid "Sign a firmware with a new key" msgstr "用新密钥签名固件" msgid "Sign data using the client certificate" msgstr "使用客户端证书签名数据" #. TRANSLATORS: command description msgctxt "command-description" msgid "Sign data using the client certificate" msgstr "使用客户端证书签名数据" #. TRANSLATORS: command line option msgid "Sign the uploaded data with the client certificate" msgstr "以客户端证书签名上传的数据" msgid "Signature" msgstr "签名" #. TRANSLATORS: file size of the download msgid "Size" msgstr "大小" #. TRANSLATORS: source (as in code) link msgid "Source" msgstr "来源" msgid "Specify Vendor/Product ID(s) of DFU device" msgstr "指定 DFU 设备的供应商/产品 ID" #. TRANSLATORS: command line option msgid "Specify the dbx database file" msgstr "指定 dbx 数据库文件" msgid "Specify the number of bytes per USB transfer" msgstr "指定每次 USB 传输的字节数" #. TRANSLATORS: the update state of the specific device msgid "Success" msgstr "成功" #. TRANSLATORS: success message -- where activation is making the new #. * firmware take effect, usually after updating offline msgid "Successfully activated all devices" msgstr "激活所有设备成功" #. TRANSLATORS: success message msgid "Successfully disabled remote" msgstr "禁用远程源成功" #. TRANSLATORS: success message -- where 'metadata' is information #. * about available firmware on the remote server msgid "Successfully downloaded new metadata: " msgstr "已成功下载新元数据:" #. TRANSLATORS: success message msgid "Successfully enabled and refreshed remote" msgstr "已成功启用和刷新远程库" #. TRANSLATORS: success message msgid "Successfully enabled remote" msgstr "启用远程源成功" #. TRANSLATORS: success message msgid "Successfully installed firmware" msgstr "安装固件成功" #. TRANSLATORS: success message -- a per-system setting value msgid "Successfully modified configuration value" msgstr "修改配置值成功" #. TRANSLATORS: success message for a per-remote setting change msgid "Successfully modified remote" msgstr "修改远程源成功" #. TRANSLATORS: success message -- the user can do this by-hand too msgid "Successfully refreshed metadata manually" msgstr "手动刷新元数据成功" #. TRANSLATORS: success message when user refreshes device checksums msgid "Successfully updated device checksums" msgstr "更新设备校验和成功" #. TRANSLATORS: success message -- where the user has uploaded #. * success and/or failure reports to the remote server #, c-format msgid "Successfully uploaded %u report" msgid_plural "Successfully uploaded %u reports" msgstr[0] "上传 %u 篇报告成功" #. TRANSLATORS: success message when user verified device checksums msgid "Successfully verified device checksums" msgstr "验证设备校验和成功" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "概览" #. TRANSLATORS: Suffix: the HSI result msgid "Supported" msgstr "已支持" #. TRANSLATORS: Is found in current metadata msgid "Supported on remote server" msgstr "支持于远程服务器" #. TRANSLATORS: Title: a better sleep state msgid "Suspend-to-idle" msgstr "挂起到空闲" #. TRANSLATORS: Title: sleep state msgid "Suspend-to-ram" msgstr "挂起到内存" #. TRANSLATORS: show and ask user to confirm -- #. * %1 is the old branch name, %2 is the new branch name #, c-format msgid "Switch branch from %s to %s?" msgstr "切换分支,从 %s 到 %s?" #. TRANSLATORS: command description msgid "Switch the firmware branch on the device" msgstr "切换设备的固件分支" #. TRANSLATORS: Must be plugged into an outlet msgid "System requires external power source" msgstr "系统需要外接电源" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "TEXT" msgstr "文本" #. TRANSLATORS: Title: the PCR is rebuilt from the TPM event log msgid "TPM PCR0 reconstruction" msgstr "TPM PCR0 重建" #. TRANSLATORS: Title: TPM = Trusted Platform Module msgid "TPM v2.0" msgstr "TPM v2.0" #. TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 msgid "Tag" msgid_plural "Tags" msgstr[0] "标签" #. TRANSLATORS: Suffix: the HSI result msgid "Tainted" msgstr "受污染" msgid "Target" msgstr "目标" #. TRANSLATORS: command description msgid "Test a device using a JSON manifest" msgstr "使用 JSON 清单测试设备" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS 是个免费服务,以独立法律实体运作,与 $OS_RELEASE:NAME$ 无关。你的发行商可能未对固件更新与你的系统或连接的设备兼容性做过任何验证。所有固件都由原始设备制造商提供。" #. TRANSLATORS: this is more background on a security measurement problem msgid "The TPM PCR0 differs from reconstruction." msgstr "TPM PCR0 跟重建的不同。" #. TRANSLATORS: the user is SOL for support... msgid "The daemon has loaded 3rd party code and is no longer supported by the upstream developers!" msgstr "守护进程已经加载了第三方代码,并且上游开发者不再支持。" #. TRANSLATORS: this is for the device tests, %1 is the device #. * version, %2 is what we expected #, c-format msgid "The device version did not match: got %s, expected %s" msgstr "设备版本不匹配:得到 %s,期望 %s" #. TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name #, c-format msgid "The firmware from %s is not supplied by %s, the hardware vendor." msgstr "来源 %s 的固件并非由硬件供应商 %s 提供。" #. TRANSLATORS: try to help msgid "The system clock has not been set correctly and downloading files may fail." msgstr "系统时钟没有设置正确,文件下载可能会失败。" #. TRANSLATORS: nothing to show msgid "There are no blocked firmware files" msgstr "没有受阻的固件文件" #. TRANSLATORS: approved firmware has been checked by #. * the domain administrator msgid "There is no approved firmware." msgstr "没有批准的固件。" #. TRANSLATORS: unsupported build of the package msgid "This package has not been validated, it may not work properly." msgstr "此软件包未经验证,可能无法正常工作。" #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "此程序只能以 root 身份正常运行" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "此远程源包含未禁止的固件,但其仍经过硬件供应商测试。你应该确保如果固件更新失败时你能够降级固件。" #. TRANSLATORS: this is instructions on how to improve the HSI suffix msgid "This system has HSI runtime issues." msgstr "该系统的 HSI 运行时有问题。" #. TRANSLATORS: this is instructions on how to improve the HSI security level msgid "This system has a low HSI security level." msgstr "该系统的 HSI 安全级别较低。" #. TRANSLATORS: description of dbxtool msgid "This tool allows an administrator to apply UEFI dbx updates." msgstr "此工具允许管理员应用 UEFI dbx 更新。" #. TRANSLATORS: CLI description msgid "This tool allows an administrator to debug UpdateCapsule operation." msgstr "此工具允许管理员调试 UpdateCapsule 操作。" #. TRANSLATORS: CLI description msgid "This tool allows an administrator to query and control the fwupd daemon, allowing them to perform actions such as installing or downgrading firmware." msgstr "此工具允许管理员查询和控制 fwupd 守护进程,让其执行如安装或者降级固件等操作。" #. TRANSLATORS: CLI description msgid "This tool allows an administrator to use the fwupd plugins without being installed on the host system." msgstr "此工具可以让管理员不用安装 fwupd 插件到主机系统上就可以使用它们。" #. TRANSLATORS: the user needs to stop playing with stuff msgid "This tool can only be used by the root user" msgstr "此工具只能由 root 用户使用" #. TRANSLATORS: CLI description msgid "This tool will read and parse the TPM event log from the system firmware." msgstr "此工具将读取和解析系统固件中的 TPM 事件日志。" #. TRANSLATORS: the update state of the specific device msgid "Transient failure" msgstr "暂时失败" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "类型" #. TRANSLATORS: partition refers to something on disk, again, hey Arch users msgid "UEFI ESP partition not detected or configured" msgstr "UEFI ESP 分区未检测到或未配置" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "固件 UEFI 实用工具" #. TRANSLATORS: capsule updates are an optional BIOS feature msgid "UEFI capsule updates not available or enabled in firmware setup" msgstr "UEFI 胶囊更新在固件设置中不可用或未启用" #. TRANSLATORS: program name msgid "UEFI dbx Utility" msgstr "UEFI dbx 实用工具" #. TRANSLATORS: system is not booted in UEFI mode msgid "UEFI firmware can not be updated in legacy BIOS mode" msgstr "UEFI 固件无法在传统 BIOS 模式下更新" #. TRANSLATORS: Title: PK is the 'platform key' for the machine msgid "UEFI platform key" msgstr "UEFI 平台密钥" #. TRANSLATORS: Title: SB is a way of locking down UEFI msgid "UEFI secure boot" msgstr "UEFI 安全引导" #. TRANSLATORS: error message msgid "Unable to connect to service" msgstr "无法连接到服务" #. TRANSLATORS: command description msgid "Unbind current driver" msgstr "解除当前驱动的绑定" #. TRANSLATORS: we will now offer this firmware to the user msgid "Unblocking firmware:" msgstr "正在解除阻止固件:" #. TRANSLATORS: command description msgid "Unblocks a specific firmware from being installed" msgstr "解除阻止指定的固件被安装" #. TRANSLATORS: Suffix: the HSI result msgid "Unencrypted" msgstr "未加密" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "未知" #. TRANSLATORS: Name of hardware msgid "Unknown Device" msgstr "未知设备" msgid "Unlock the device to allow access" msgstr "解锁设备以允许访问" #. TRANSLATORS: Suffix: the HSI result msgid "Unlocked" msgstr "已解锁" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "为固件访问解锁设备" #. TRANSLATORS: command description msgid "Unmounts the ESP" msgstr "卸载 ESP 分区" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "更新期间取消设定调试标志" #. TRANSLATORS: error message #, c-format msgid "Unsupported daemon version %s, client version is %s" msgstr "未受支持的守护进程版本 %s,客户端版本为 %s" #. TRANSLATORS: Suffix: the HSI result msgid "Untainted" msgstr "无污染" #. TRANSLATORS: Device is updatable in this or any other mode msgid "Updatable" msgstr "可更新" #. TRANSLATORS: error message from last update attempt msgid "Update Error" msgstr "更新错误" #. TRANSLATORS: helpful messages from last update #. TRANSLATORS: helpful messages for the update msgid "Update Message" msgstr "更新消息" #. TRANSLATORS: hardware state, e.g. "pending" msgid "Update State" msgstr "更新状态" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "更新失败为已知问题,请访问此 URL 以获取详情:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "现在更新吗?" #. TRANSLATORS: Update can only be done from offline mode msgid "Update requires a reboot" msgstr "更新需要重启" #. TRANSLATORS: command description msgid "Update the stored cryptographic hash with current ROM contents" msgstr "以当前 ROM 内容更新存储的加密哈希" msgid "Update the stored device verification information" msgstr "更新存储的设备验证信息" #. TRANSLATORS: command description msgid "Update the stored metadata with current contents" msgstr "使用当前内容更新存储的元数据" msgid "Updating" msgstr "正在更新" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "正在更新 %s,从 %s 到 %s…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Updating %s…" msgstr "正在更新 %s……" #. TRANSLATORS: message letting the user know an upgrade is available #. * %1 is the device name and %2 and %3 are version strings #, c-format msgid "Upgrade %s from %s to %s?" msgstr "升级 %s,从 %s 到 %s?" #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "上传固件报告可帮助硬件供应商尽快确定真实设备上更新的成功及失败案例。" #. TRANSLATORS: how important the release is msgid "Urgency" msgstr "紧急性" #. TRANSLATORS: explain how to get help msgid "Use fwupdtool --help for help" msgstr "使用 fwupdtool --help 获取帮助信息" #. TRANSLATORS: command line option msgid "Use quirk flags when installing firmware" msgstr "安装固件时使用 quirk 标志" #. TRANSLATORS: User has been notified msgid "User has been notified" msgstr "已通知用户" #. TRANSLATORS: remote filename base msgid "Username" msgstr "用户名" msgid "VID:PID" msgstr "VID:PID" #. TRANSLATORS: Suffix: the HSI result msgid "Valid" msgstr "有效" #. TRANSLATORS: ESP refers to the EFI System Partition msgid "Validating ESP contents…" msgstr "正在验证 ESP 内容……" #. TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') msgid "Variant" msgstr "变型" #. TRANSLATORS: manufacturer of hardware msgid "Vendor" msgstr "供应商" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "正在验证…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "版本" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "等待中..." #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "监视硬件更改" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "将来自文件的固件写入设备" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "将来自文件的固件写入分区" #. TRANSLATORS: decompressing images from a container firmware msgid "Writing file:" msgstr "写入文件:" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "正在写入…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "您的发行商可能尚未认证任何固件的系统及设备兼容性。" #. TRANSLATORS: %1 is the device vendor name #, c-format msgid "Your hardware may be damaged using this firmware, and installing this release may void any warranty with %s." msgstr "你的硬件使用此固件可能会损坏,而且安装此版本固件可能失去 %s 的保修。" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[CHECKSUM]" msgstr "[校验和]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID]" msgstr "[设备ID|GUID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[DEVICE-ID|GUID] [BRANCH]" msgstr "[设备ID|GUID] [分支]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILE FILE_SIG REMOTE-ID]" msgstr "[文件 文件签名 远程ID]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[FILENAME1] [FILENAME2]" msgstr "[文件名1] [文件名2]" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgid "[SMBIOS-FILE|HWIDS-FILE]" msgstr "[SMBIOS文件|HWIDS文件]" #. TRANSLATORS: this is the default branch name when unset msgid "default" msgstr "默认" #. TRANSLATORS: program name msgid "fwupd TPM event log utility" msgstr "fwupd TPM 事件日志工具" #. TRANSLATORS: Title: if the fwupd plugins are all present and correct msgid "fwupd plugins" msgstr "fwupd 插件" fwupd-1.9.16/po/zh_TW.po000066400000000000000000000502751460375044200147640ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the fwupd package. # # Translators: # Cheng-Chia Tseng , 2017-2018 msgid "" msgstr "" "Project-Id-Version: fwupd\n" "Report-Msgid-Bugs-To: \n" "Language-Team: Chinese (Taiwan) (http://app.transifex.com/freedesktop/fwupd/language/zh_TW/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: zh_TW\n" "Plural-Forms: nplurals=1; plural=0;\n" #. TRANSLATORS: more than a minute #, c-format msgid "%.0f minute remaining" msgid_plural "%.0f minutes remaining" msgstr[0] "剩下 %.0f 分鐘" #. TRANSLATORS: the age of the metadata msgid "Age" msgstr "年紀" #. TRANSLATORS: should the remote still be enabled msgid "Agree and enable the remote?" msgstr "同意並且啟用遠端站點?" #. TRANSLATORS: this is a command alias, e.g. 'get-devices' #, c-format msgid "Alias to %s" msgstr "%s 的別名" #. TRANSLATORS: command line option msgid "Allow downgrading firmware versions" msgstr "允許降級韌體版本" #. TRANSLATORS: explain why we want to reboot msgid "An update requires a reboot to complete." msgstr "有更新必須重新開機才能完成。" #. TRANSLATORS: command line option msgid "Answer yes to all questions" msgstr "全部的問題都回答是" #. TRANSLATORS: command line option msgid "Apply firmware updates" msgstr "套用韌體更新" #. TRANSLATORS: command description msgid "Attach to firmware mode" msgstr "連接至韌體模式" #. TRANSLATORS: waiting for user to authenticate msgid "Authenticating…" msgstr "核對中…" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on a removable device" msgstr "必須先通過身份核對才能降級可移除裝置上的韌體" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to downgrade the firmware on this machine" msgstr "必須先通過身份核對才能降級這臺機器上的韌體" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to modify a configured remote used for firmware updates" msgstr "必須通過身份核對才能修改設定作韌體更新的遠端站點" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to unlock a device" msgstr "必須先通過身份核對才能解鎖裝置" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on a removable device" msgstr "必須先通過身份核對才能更新可移除裝置上的韌體" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the firmware on this machine" msgstr "必須先通過身份核對才能更新這臺機器上的韌體" #. TRANSLATORS: this is the PolicyKit modal dialog msgid "Authentication is required to update the stored checksums for the device" msgstr "必須先通過身份核對才能更新裝置的儲存校驗計算碼" #. TRANSLATORS: this is to abort the interactive prompt msgid "Cancel" msgstr "取消" #. TRANSLATORS: this is when a device ctrl+c's a watch #. TRANSLATORS: this is from ctrl+c msgid "Cancelled" msgstr "已取消" #. TRANSLATORS: this is when the daemon state changes msgid "Changed" msgstr "已變更" #. TRANSLATORS: hash to that exact firmware archive #. TRANSLATORS: remote checksum msgid "Checksum" msgstr "校驗計算碼" #. TRANSLATORS: command description msgid "Clears the results from the last update" msgstr "清除上次更新的結果" #. TRANSLATORS: error message msgid "Command not found" msgstr "找不到指令" #. TRANSLATORS: DFU stands for device firmware update msgid "DFU Utility" msgstr "DFU 公用程式" #. TRANSLATORS: for the --verbose arg msgid "Debugging Options" msgstr "除錯選項" #. TRANSLATORS: decompressing the firmware file msgid "Decompressing…" msgstr "解壓縮中…" #. TRANSLATORS: description of BIOS setting #. TRANSLATORS: multiline description of device msgid "Description" msgstr "描述說明" #. TRANSLATORS: command description msgid "Detach to bootloader mode" msgstr "斷開至開機載入器模式" #. TRANSLATORS: this is when a device is hotplugged msgid "Device added:" msgstr "裝置已加入:" #. TRANSLATORS: this is when a device has been updated msgid "Device changed:" msgstr "裝置已變更:" #. TRANSLATORS: this is when a device is hotplugged msgid "Device removed:" msgstr "裝置已移除:" #. TRANSLATORS: a list of successful updates msgid "Devices that have been updated successfully:" msgstr "成功更新的裝置:" #. TRANSLATORS: a list of failed updates msgid "Devices that were not updated correctly:" msgstr "無法正確更新的裝置:" msgid "Disabled fwupdate debugging" msgstr "已停用 fwupdate 除錯" #. TRANSLATORS: command description msgid "Disables a given remote" msgstr "停用指定的遠端站點" #. TRANSLATORS: command line option msgid "Display version" msgstr "顯示版本" #. TRANSLATORS: command line option msgid "Do not check for old metadata" msgstr "不要檢查中介資料是否老舊" #. TRANSLATORS: command line option msgid "Do not check for unreported history" msgstr "不要檢查是否有尚未報告的歷史" #. TRANSLATORS: command line option msgid "Do not write to the history database" msgstr "不要寫入歷史資料庫中" #. TRANSLATORS: success #. success msgid "Done!" msgstr "完成!" #. TRANSLATORS: command description msgid "Downgrades the firmware on a device" msgstr "降級裝置的韌體" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Downgrading %s from %s to %s... " msgstr "正將 %s 從 %s 版降級至 %s 版... " #. TRANSLATORS: downloading from a remote server msgid "Downloading…" msgstr "下載中…" #. TRANSLATORS: command description msgid "Dump SMBIOS data from a file" msgstr "從檔案傾印 SMBIOUS 資料" #. TRANSLATORS: ESP is EFI System Partition msgid "ESP specified was not valid" msgstr "指定的 ESP 無效" #. TRANSLATORS: command line option msgid "Enable firmware update support on supported systems" msgstr "對支援的系統啟用韌體更新支援" #. TRANSLATORS: Turn on the remote msgid "Enable this remote?" msgstr "啟用此遠端站點?" #. TRANSLATORS: Plugin is active and in use #. TRANSLATORS: if the remote is enabled #. TRANSLATORS: Suffix: the HSI result msgid "Enabled" msgstr "啟用" msgid "Enabled fwupdate debugging" msgstr "已啟用 fwupdate 除錯" #. TRANSLATORS: command description msgid "Enables a given remote" msgstr "啟用指定的遠端站點" msgid "Enabling this functionality is done at your own risk, which means you have to contact your original equipment manufacturer regarding any problems caused by these updates. Only problems with the update process itself should be filed at $OS_RELEASE:BUG_REPORT_URL$." msgstr "啟用此功能必須自負風險。這代表若您遭遇到這些更新導致的任何問題,您必須向原始設備供應商聯絡並反應。只有在您遇到更新過程本身的問題時,才是向 $OS_RELEASE:BUG_REPORT_URL$ 回報。" #. TRANSLATORS: show the user a warning msgid "Enabling this remote is done at your own risk." msgstr "若要啟用此遠端站點,請自負風險。" #. TRANSLATORS: command description msgid "Erase all firmware update history" msgstr "抹除所有韌體更新歷史" #. TRANSLATORS: erasing contents of the flash chips msgid "Erasing…" msgstr "抹除中…" #. TRANSLATORS: exit after we've started up, used for user profiling msgid "Exit after a small delay" msgstr "經過一段短暫延遲後便離開" #. TRANSLATORS: exit straight away, used for automatic profiling msgid "Exit after the engine has loaded" msgstr "引擎載入後離開" #. TRANSLATORS: quirks are device-specific workarounds msgid "Failed to load quirks" msgstr "無法載入奇技淫巧" #. TRANSLATORS: the user didn't read the man page msgid "Failed to parse arguments" msgstr "無法解析引數" #. TRANSLATORS: filename of the local file msgid "Filename" msgstr "檔名" #. TRANSLATORS: filename of the local file msgid "Filename Signature" msgstr "檔名簽章" #. TRANSLATORS: remote URI msgid "Firmware Base URI" msgstr "韌體基礎 URI" #. TRANSLATORS: program summary msgid "Firmware Update D-Bus Service" msgstr "韌體更新 D-Bus 服務" #. TRANSLATORS: program name msgid "Firmware Update Daemon" msgstr "韌體更新幕後程式" #. TRANSLATORS: program name msgid "Firmware Utility" msgstr "韌體公用程式" #. TRANSLATORS: the metadata is very out of date; %u is a number > 1 #, c-format msgid "Firmware metadata has not been updated for %u day and may not be up to date." msgid_plural "Firmware metadata has not been updated for %u days and may not be up to date." msgstr[0] "韌體中介資料已有 %u 天未更新,可能不是最新狀態。" msgid "Firmware updates are not supported on this machine." msgstr "此機器沒有韌體更新支援。" msgid "Firmware updates are supported on this machine." msgstr "此機器有韌體更新支援。" #. TRANSLATORS: Suffix: the HSI result msgid "Found" msgstr "找到" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "A single GUID" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command argument: uppercase, spaces->dashes msgctxt "command-argument" msgid "GUID" msgstr "GUID" #. TRANSLATORS: command description msgid "Get all devices that support firmware updates" msgstr "取得所有支援韌體更新的裝置" #. TRANSLATORS: command description msgid "Get all enabled plugins registered with the system" msgstr "取得所有系統中註冊的啟用插件" #. TRANSLATORS: command description msgid "Gets details about a firmware file" msgstr "取得韌體檔案的相關細節" #. TRANSLATORS: command description msgid "Gets the configured remotes" msgstr "取得設定的遠端站點" #. TRANSLATORS: command description msgid "Gets the list of updates for connected hardware" msgstr "取得連接硬體的更新清單" #. TRANSLATORS: command description msgid "Gets the releases for a device" msgstr "取得裝置的發行版本" #. TRANSLATORS: command description msgid "Gets the results from the last update" msgstr "取得上次更新的結果" #. TRANSLATORS: daemon is inactive msgid "Idle…" msgstr "閒置…" msgid "Install signed device firmware" msgstr "安裝已簽署的裝置韌體" msgid "Install signed system firmware" msgstr "安裝已簽署的系統韌體" msgid "Install unsigned device firmware" msgstr "安裝未簽署的裝置韌體" msgid "Install unsigned system firmware" msgstr "安裝未簽署的系統韌體" #. TRANSLATORS: this is shown when updating the firmware after the reboot msgid "Installing firmware update…" msgstr "安裝韌體更新中…" #. TRANSLATORS: %1 is a device name #, c-format msgid "Installing on %s…" msgstr "正安裝到 %s…" #. TRANSLATORS: keyring type, e.g. GPG or PKCS7 msgid "Keyring" msgstr "鑰匙圈" #. TRANSLATORS: time remaining for completing firmware flash msgid "Less than one minute remaining" msgstr "剩不到一分鐘" msgid "Linux Vendor Firmware Service (stable firmware)" msgstr "Linux 廠商韌體服務(穩定版韌體)" msgid "Linux Vendor Firmware Service (testing firmware)" msgstr "Linux 廠商韌體服務(測試中韌體)" #. TRANSLATORS: command line option msgid "List supported firmware updates" msgstr "列出支援的韌體更新" #. TRANSLATORS: parsing the firmware information msgid "Loading…" msgstr "載入中…" #. TRANSLATORS: remote URI msgid "Metadata URI" msgstr "中介資料 URI" #. TRANSLATORS: explain why no metadata available msgid "Metadata can be obtained from the Linux Vendor Firmware Service." msgstr "中介資料可從 Linux 廠商韌體服務取得。" #. TRANSLATORS: command description msgid "Modifies a given remote" msgstr "修改指定的遠端站點" msgid "Modify a configured remote" msgstr "修改設定的遠端站點" #. TRANSLATORS: command description msgid "Monitor the daemon for events" msgstr "監控幕後程式是否有活動" #. TRANSLATORS: user did not tell the tool what to do msgid "No action specified!" msgstr "未指定動作!" #. TRANSLATORS: nothing attached that can be upgraded msgid "No hardware detected with firmware update capability" msgstr "未偵測到具備韌體更新能力的硬體" #. TRANSLATORS: nothing found msgid "No plugins found" msgstr "找不到插件" #. TRANSLATORS: explain why no metadata available msgid "No remotes are currently enabled so no metadata is available." msgstr "目前沒有啟用的遠端站點,因而沒有中介資料可用。" #. TRANSLATORS: Suffix: the HSI result msgid "OK" msgstr "確定" #. TRANSLATORS: command line option msgid "Override the default ESP path" msgstr "凌駕預設 ESP 路徑" #. TRANSLATORS: remote filename base msgid "Password" msgstr "密碼" msgid "Payload" msgstr "酬載" #. TRANSLATORS: the user isn't reading the question #, c-format msgid "Please enter a number from 0 to %u: " msgstr "請輸入 0 到 %u 之間的數字:" #. TRANSLATORS: the numeric priority msgid "Priority" msgstr "優先等級" msgid "Proceed with upload?" msgstr "繼續上傳?" #. TRANSLATORS: command line option msgid "Query for firmware update support" msgstr "查詢韌體更新支援" #. TRANSLATORS: command description msgid "Read firmware from device into a file" msgstr "將裝置的韌體讀取為檔案" #. TRANSLATORS: command description msgid "Read firmware from one partition into a file" msgstr "從一分割區將韌體讀取為檔案" #. TRANSLATORS: reading from the flash chips msgid "Reading…" msgstr "讀取中…" #. TRANSLATORS: command description msgid "Refresh metadata from remote server" msgstr "重整遠端伺服器的中介資料" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second is a version number #. * e.g. "1.2.3" #, c-format msgid "Reinstalling %s with %s... " msgstr "正將 %s 重新安裝為 %s 版... " #. TRANSLATORS: the server the file is coming from #. TRANSLATORS: remote identifier, e.g. lvfs-testing msgid "Remote ID" msgstr "遠端 ID" #. TRANSLATORS: command description msgid "Replace data in an existing firmware file" msgstr "取代既有韌體檔案中的資料" #. TRANSLATORS: URI to send success/failure reports msgid "Report URI" msgstr "報告 URI" #. TRANSLATORS: metadata is downloaded msgid "Requires internet connection" msgstr "必須有網際網路連線" #. TRANSLATORS: reboot to apply the update msgid "Restart now?" msgstr "是否立刻重新啟動?" #. TRANSLATORS: restarting the device to pick up new F/W msgid "Restarting device…" msgstr "重新啓動裝置中…" #. TRANSLATORS: command description msgid "Return all the hardware IDs for the machine" msgstr "回傳所有機器的硬體 ID" #. TRANSLATORS: command line option msgid "Schedule installation for next reboot when possible" msgstr "安排下次重新開機時若可行便安裝" #. TRANSLATORS: scheduling an update to be done on the next boot msgid "Scheduling…" msgstr "排程中…" #. TRANSLATORS: command line option msgid "Set the debugging flag during update" msgstr "在更新期間設定除錯旗標" #. TRANSLATORS: command description msgid "Share firmware history with the developers" msgstr "和開發者分享韌體歷史" #. TRANSLATORS: command line option msgid "Show client and daemon versions" msgstr "顯示客戶端與幕後程式版本" #. TRANSLATORS: for the --verbose arg msgid "Show debugging options" msgstr "顯示除錯選項" #. TRANSLATORS: command line option msgid "Show devices that are not updatable" msgstr "顯示不可更新的裝置" #. TRANSLATORS: command line option msgid "Show extra debugging information" msgstr "顯示額外除錯資訊" #. TRANSLATORS: command description msgid "Show history of firmware updates" msgstr "顯示韌體更新的歷史" #. TRANSLATORS: command line option msgid "Show the debug log from the last attempted update" msgstr "顯示從上次試圖更新起的除錯紀錄" #. TRANSLATORS: command line option msgid "Show the information of firmware update status" msgstr "顯示韌體更新狀態的資訊" #. TRANSLATORS: one line summary of device msgid "Summary" msgstr "摘要" msgid "Target" msgstr "目標" #. TRANSLATORS: do not translate the variables marked using $ msgid "The LVFS is a free service that operates as an independent legal entity and has no connection with $OS_RELEASE:NAME$. Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices. All firmware is provided only by the original equipment manufacturer." msgstr "LVFS(Linux 廠商韌體服務,Linux Vendor Firmware Service)是由獨立法人運作的免費服務,而與 $OS_RELEASE:NAME$ 沒有關聯。您的系統散布商可能尚未驗證過任何韌體更新與您系統間或連接裝置上的相容性。所有本服務中的韌體僅由原始設備製造商提供。" #. TRANSLATORS: we're poking around as a power user msgid "This program may only work correctly as root" msgstr "此程式僅有 root 身份才能正常運作" msgid "This remote contains firmware which is not embargoed, but is still being tested by the hardware vendor. You should ensure you have a way to manually downgrade the firmware if the firmware update fails." msgstr "這個遠端站點包含未列入禁運,但仍處於硬體廠商測試階段的韌體。您應該確保自己在韌體更新失敗時有方法能夠手動降級韌體。" #. TRANSLATORS: remote type, e.g. remote or local msgid "Type" msgstr "類型" #. TRANSLATORS: program name msgid "UEFI Firmware Utility" msgstr "UEFI 韌體公用程式" #. TRANSLATORS: current daemon status is unknown #. TRANSLATORS: we don't know the license of the update #. TRANSLATORS: unknown release urgency #. TRANSLATORS: Suffix: the fallback HSI result msgid "Unknown" msgstr "未知" msgid "Unlock the device to allow access" msgstr "解鎖裝置以允許存取" #. TRANSLATORS: command description msgid "Unlocks the device for firmware access" msgstr "解鎖裝置以供韌體存取" #. TRANSLATORS: command line option msgid "Unset the debugging flag during update" msgstr "取消更新期間的除錯旗標設定" #. TRANSLATORS: the server sent the user a small message msgid "Update failure is a known issue, visit this URL for more information:" msgstr "更新失敗是已知議題,請造訪此 URL 瞭解更多資訊:" #. TRANSLATORS: ask if we can update metadata msgid "Update now?" msgstr "是否立刻更更新?" msgid "Update the stored device verification information" msgstr "更新儲存的裝置核驗資訊" #. TRANSLATORS: the first replacement is a display name #. * e.g. "ColorHugALS" and the second and third are #. * version numbers e.g. "1.2.3" #, c-format msgid "Updating %s from %s to %s... " msgstr "正將 %s 從 %s 版升級至 %s 版... " #. TRANSLATORS: explain why we want to upload msgid "Uploading firmware reports helps hardware vendors to quickly identify failing and successful updates on real devices." msgstr "上傳韌體報告可以協助硬體廠商快速辨識出更新作業在真實裝置上是失敗還成功。" #. TRANSLATORS: remote filename base msgid "Username" msgstr "使用者名稱" #. TRANSLATORS: verifying we wrote the firmware correctly msgid "Verifying…" msgstr "核驗中…" #. TRANSLATORS: the detected version number of the dbx msgid "Version" msgstr "版本" #. TRANSLATORS: waiting for device to do something msgid "Waiting…" msgstr "等候中…" #. TRANSLATORS: command description msgid "Watch for hardware changes" msgstr "監看硬體是否有變更" #. TRANSLATORS: command description msgid "Write firmware from file into device" msgstr "從檔案將韌體寫入裝置" #. TRANSLATORS: command description msgid "Write firmware from file into one partition" msgstr "從檔案將韌體寫入一分割區" #. TRANSLATORS: writing to the flash chips msgid "Writing…" msgstr "寫入中…" #. TRANSLATORS: show the user a warning msgid "Your distributor may not have verified any of the firmware updates for compatibility with your system or connected devices." msgstr "您的系統散布商可能尚未驗證過任何韌體更新與您系統間或連接裝置上的相容性。" fwupd-1.9.16/policy/000077500000000000000000000000001460375044200142415ustar00rootroot00000000000000fwupd-1.9.16/policy/its/000077500000000000000000000000001460375044200150405ustar00rootroot00000000000000fwupd-1.9.16/policy/its/polkit.its000066400000000000000000000004651460375044200170700ustar00rootroot00000000000000 fwupd-1.9.16/policy/its/polkit.loc000066400000000000000000000003031460375044200170350ustar00rootroot00000000000000 fwupd-1.9.16/policy/meson.build000066400000000000000000000015421460375044200164050ustar00rootroot00000000000000install_data('org.freedesktop.fwupd.rules', install_dir: join_paths(datadir, 'polkit-1', 'rules.d')) #newer polkit has the ITS rules included if polkit.version().version_compare('>0.113') i18n.merge_file( input: 'org.freedesktop.fwupd.policy.in', output: 'org.freedesktop.fwupd.policy', install: true, install_dir: join_paths(datadir, 'polkit-1', 'actions') , type: 'xml', po_dir: join_paths(meson.project_source_root(), 'po') ) #older polkit is missing ITS rules and will fail else i18n.merge_file( input: 'org.freedesktop.fwupd.policy.in', output: 'org.freedesktop.fwupd.policy', install: true, install_dir: join_paths(datadir, 'polkit-1', 'actions') , type: 'xml', data_dirs: join_paths(meson.project_source_root(), 'policy'), po_dir: join_paths(meson.project_source_root(), 'po') ) endif fwupd-1.9.16/policy/org.freedesktop.fwupd-unsafe.rules000066400000000000000000000002731460375044200230230ustar00rootroot00000000000000polkit.addRule(function(action, subject) { if (action.id.startsWith("org.freedesktop.fwupd.") && subject.isInGroup("wheel")) { return polkit.Result.YES; } }); fwupd-1.9.16/policy/org.freedesktop.fwupd.policy.in000066400000000000000000000227661460375044200223310ustar00rootroot00000000000000 System firmware update https://github.com/fwupd/fwupd application-x-firmware Install signed system firmware Authentication is required to update the firmware on this machine auth_admin no yes Install unsigned system firmware Authentication is required to update the firmware on this machine auth_admin no auth_admin_keep org.freedesktop.fwupd.update-internal-trusted Install old version of signed system firmware Authentication is required to downgrade the firmware on this machine auth_admin no auth_admin_keep org.freedesktop.fwupd.update-internal Install old version of unsigned system firmware Authentication is required to downgrade the firmware on this machine auth_admin no auth_admin_keep org.freedesktop.fwupd.downgrade-internal-trusted Install signed device firmware Authentication is required to update the firmware on a removable device auth_admin no yes Install unsigned device firmware Authentication is required to update the firmware on a removable device auth_admin no auth_admin_keep org.freedesktop.fwupd.update-hotplug-trusted Install signed device firmware Authentication is required to downgrade the firmware on a removable device auth_admin no auth_admin_keep Install unsigned device firmware Authentication is required to downgrade the firmware on a removable device auth_admin no auth_admin_keep org.freedesktop.fwupd.downgrade-hotplug-trusted Unlock the device to allow access Authentication is required to unlock a device auth_admin no auth_admin_keep Modify daemon configuration Authentication is required to modify daemon configuration auth_admin no auth_admin_keep org.freedesktop.fwupd.modify-remote Activate the new firmware on the device Authentication is required to switch to the new firmware version auth_admin no auth_admin_keep Update the stored device verification information Authentication is required to update the stored checksums for the device auth_admin no auth_admin_keep Modify a configured remote Authentication is required to modify a configured remote used for firmware updates auth_admin no auth_admin_keep Sets the list of approved firmware Authentication is required to set the list of approved firmware auth_admin no auth_admin_keep Sign data using the client certificate Authentication is required to sign data using the client certificate auth_admin no auth_admin_keep Get BIOS settings Authentication is required to read BIOS settings auth_admin no auth_admin_keep Set one or more BIOS settings Authentication is required to modify BIOS settings auth_admin no auth_admin org.freedesktop.fwupd.get-bios-settings Security hardening for HSI Authentication is required to fix a host security issue auth_admin no auth_admin Security hardening for HSI Authentication is required to undo the fix for a host security issue auth_admin no auth_admin fwupd-1.9.16/policy/org.freedesktop.fwupd.rules000066400000000000000000000003741460375044200215460ustar00rootroot00000000000000polkit.addRule(function(action, subject) { if (action.id == "org.freedesktop.fwupd.update-internal" && subject.active == true && subject.local == true && subject.isInGroup("wheel")) { return polkit.Result.YES; } }); fwupd-1.9.16/src/000077500000000000000000000000001460375044200135315ustar00rootroot00000000000000fwupd-1.9.16/src/fu-bluez-backend.c000066400000000000000000000152461460375044200170230ustar00rootroot00000000000000/* * Copyright (C) 2021 Ricardo Cañuelo * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuBackend" #include "config.h" #include "fu-bluez-backend.h" #include "fu-bluez-device.h" struct _FuBluezBackend { FuBackend parent_instance; GDBusObjectManager *object_manager; }; G_DEFINE_TYPE(FuBluezBackend, fu_bluez_backend, FU_TYPE_BACKEND) #define FU_BLUEZ_BACKEND_TIMEOUT 1500 /* ms */ static void fu_bluez_backend_object_properties_changed(FuBluezBackend *self, GDBusProxy *proxy) { const gchar *path = g_dbus_proxy_get_object_path(proxy); gboolean suitable; FuDevice *device_tmp; g_autoptr(FuBluezDevice) dev = NULL; g_autoptr(GVariant) val_connected = NULL; g_autoptr(GVariant) val_paired = NULL; /* device is suitable */ val_connected = g_dbus_proxy_get_cached_property(proxy, "Connected"); if (val_connected == NULL) return; val_paired = g_dbus_proxy_get_cached_property(proxy, "Paired"); if (val_paired == NULL) return; suitable = g_variant_get_boolean(val_connected) && g_variant_get_boolean(val_paired); /* is this an existing device we've previously added */ device_tmp = fu_backend_lookup_by_id(FU_BACKEND(self), path); if (device_tmp != NULL) { if (suitable) { g_debug("ignoring suitable changed BlueZ device: %s", path); return; } g_debug("removing unsuitable BlueZ device: %s", path); fu_backend_device_removed(FU_BACKEND(self), device_tmp); return; } /* not paired and connected */ if (!suitable) return; /* create device */ dev = g_object_new(FU_TYPE_BLUEZ_DEVICE, "backend-id", path, "object-manager", self->object_manager, "proxy", proxy, NULL); g_info("adding suitable BlueZ device: %s", path); fu_backend_device_added(FU_BACKEND(self), FU_DEVICE(dev)); } static void fu_bluez_backend_object_properties_changed_cb(GDBusProxy *proxy, GVariant *changed_properties, GStrv invalidated_properties, FuBluezBackend *self) { fu_bluez_backend_object_properties_changed(self, proxy); } static void fu_bluez_backend_object_added(FuBluezBackend *self, GDBusObject *object) { g_autoptr(GDBusInterface) iface = NULL; iface = g_dbus_object_get_interface(object, "org.bluez.Device1"); if (iface == NULL) return; g_signal_connect(G_DBUS_INTERFACE(iface), "g-properties-changed", G_CALLBACK(fu_bluez_backend_object_properties_changed_cb), self); fu_bluez_backend_object_properties_changed(self, G_DBUS_PROXY(iface)); } static void fu_bluez_backend_object_added_cb(GDBusObjectManager *manager, GDBusObject *object, FuBluezBackend *self) { fu_bluez_backend_object_added(self, object); } static void fu_bluez_backend_object_removed_cb(GDBusObjectManager *manager, GDBusObject *object, FuBluezBackend *self) { const gchar *path = g_dbus_object_get_object_path(object); FuDevice *device_tmp; device_tmp = fu_backend_lookup_by_id(FU_BACKEND(self), path); if (device_tmp == NULL) return; g_info("removing BlueZ device: %s", path); fu_backend_device_removed(FU_BACKEND(self), device_tmp); } typedef struct { GDBusObjectManager *object_manager; GMainLoop *loop; GError **error; GCancellable *cancellable; guint timeout_id; } FuBluezBackendHelper; static void fu_bluez_backend_helper_free(FuBluezBackendHelper *helper) { if (helper->object_manager != NULL) g_object_unref(helper->object_manager); if (helper->timeout_id != 0) g_source_remove(helper->timeout_id); g_cancellable_cancel(helper->cancellable); g_main_loop_unref(helper->loop); g_free(helper); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuBluezBackendHelper, fu_bluez_backend_helper_free) static void fu_bluez_backend_connect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { FuBluezBackendHelper *helper = (FuBluezBackendHelper *)user_data; helper->object_manager = g_dbus_object_manager_client_new_for_bus_finish(res, helper->error); g_main_loop_quit(helper->loop); } static gboolean fu_bluez_backend_timeout_cb(gpointer user_data) { FuBluezBackendHelper *helper = (FuBluezBackendHelper *)user_data; g_cancellable_cancel(helper->cancellable); helper->timeout_id = 0; return G_SOURCE_REMOVE; } static gboolean fu_bluez_backend_setup(FuBackend *backend, FuProgress *progress, GError **error) { FuBluezBackend *self = FU_BLUEZ_BACKEND(backend); g_autoptr(FuBluezBackendHelper) helper = g_new0(FuBluezBackendHelper, 1); /* in some circumstances the bluez daemon will just hang... do not wait * forever and make fwupd startup also fail */ helper->error = error; helper->loop = g_main_loop_new(NULL, FALSE); helper->cancellable = g_cancellable_new(); helper->timeout_id = g_timeout_add(FU_BLUEZ_BACKEND_TIMEOUT, fu_bluez_backend_timeout_cb, helper); g_dbus_object_manager_client_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, "org.bluez", "/", NULL, NULL, NULL, helper->cancellable, fu_bluez_backend_connect_cb, helper); g_main_loop_run(helper->loop); if (helper->object_manager == NULL) return FALSE; self->object_manager = g_steal_pointer(&helper->object_manager); g_signal_connect(G_DBUS_OBJECT_MANAGER(self->object_manager), "object-added", G_CALLBACK(fu_bluez_backend_object_added_cb), self); g_signal_connect(G_DBUS_OBJECT_MANAGER(self->object_manager), "object-removed", G_CALLBACK(fu_bluez_backend_object_removed_cb), self); return TRUE; } static gboolean fu_bluez_backend_coldplug(FuBackend *backend, FuProgress *progress, GError **error) { FuBluezBackend *self = FU_BLUEZ_BACKEND(backend); g_autolist(GDBusObject) objects = NULL; /* failed to set up */ if (self->object_manager == NULL) return TRUE; objects = g_dbus_object_manager_get_objects(self->object_manager); for (GList *l = objects; l != NULL; l = l->next) { GDBusObject *object = G_DBUS_OBJECT(l->data); fu_bluez_backend_object_added(self, object); } return TRUE; } static void fu_bluez_backend_finalize(GObject *object) { FuBluezBackend *self = FU_BLUEZ_BACKEND(object); if (self->object_manager != NULL) g_object_unref(self->object_manager); G_OBJECT_CLASS(fu_bluez_backend_parent_class)->finalize(object); } static void fu_bluez_backend_init(FuBluezBackend *self) { } static void fu_bluez_backend_class_init(FuBluezBackendClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuBackendClass *klass_backend = FU_BACKEND_CLASS(klass); object_class->finalize = fu_bluez_backend_finalize; klass_backend->setup = fu_bluez_backend_setup; klass_backend->coldplug = fu_bluez_backend_coldplug; } FuBackend * fu_bluez_backend_new(FuContext *ctx) { return FU_BACKEND( g_object_new(FU_TYPE_BLUEZ_BACKEND, "name", "bluez", "context", ctx, NULL)); } fwupd-1.9.16/src/fu-bluez-backend.h000066400000000000000000000004621460375044200170220ustar00rootroot00000000000000/* * Copyright (C) 2021 * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-backend.h" #define FU_TYPE_BLUEZ_BACKEND (fu_bluez_backend_get_type()) G_DECLARE_FINAL_TYPE(FuBluezBackend, fu_bluez_backend, FU, BLUEZ_BACKEND, FuBackend) FuBackend * fu_bluez_backend_new(FuContext *ctx); fwupd-1.9.16/src/fu-cabinet-common.c000066400000000000000000000017051460375044200172030ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCommonCab" #include "config.h" #include "fu-cabinet-common.h" #include "fu-cabinet.h" /** * fu_cabinet_build_silo: (skip): * @blob: A readable blob * @size_max: the maximum size of the archive * @error: A #FuEndianType, e.g. %G_LITTLE_ENDIAN * * Create an AppStream silo from a cabinet archive. * * Returns: (transfer full): a #XbSilo, or %NULL on error * * Since: 1.2.0 **/ XbSilo * fu_cabinet_build_silo(GBytes *blob, guint64 size_max, GError **error) { g_autoptr(FuCabinet) cabinet = fu_cabinet_new(); g_return_val_if_fail(blob != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); fu_firmware_set_size_max(FU_FIRMWARE(cabinet), size_max); if (!fu_firmware_parse(FU_FIRMWARE(cabinet), blob, FWUPD_INSTALL_FLAG_NONE, error)) return NULL; return fu_cabinet_get_silo(cabinet, error); } fwupd-1.9.16/src/fu-cabinet-common.h000066400000000000000000000003671460375044200172130ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include XbSilo * fu_cabinet_build_silo(GBytes *blob, guint64 size_max, GError **error) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.9.16/src/fu-cabinet.c000066400000000000000000000651271460375044200157250ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuCabinet" #include "config.h" #include "fwupd-common.h" #include "fwupd-enums.h" #include "fwupd-error.h" #include "fu-cab-image.h" #include "fu-cabinet.h" #include "fu-common.h" #include "fu-string.h" /** * FuCabinet: * * Cabinet archive parser and writer. * * See also: [class@FuArchive] */ struct _FuCabinet { FuCabFirmware parent_instance; gchar *container_checksum; gchar *container_checksum_alt; XbBuilder *builder; XbSilo *silo; JcatContext *jcat_context; JcatFile *jcat_file; }; G_DEFINE_TYPE(FuCabinet, fu_cabinet, FU_TYPE_CAB_FIRMWARE) /** * fu_cabinet_set_jcat_context: (skip): * @self: a #FuCabinet * @jcat_context: (nullable): a Jcat context * * Sets the Jcat context, which is used for setting the trust flags on the * each release in the archive. * * Since: 1.4.0 **/ void fu_cabinet_set_jcat_context(FuCabinet *self, JcatContext *jcat_context) { g_return_if_fail(FU_IS_CABINET(self)); g_return_if_fail(JCAT_IS_CONTEXT(jcat_context)); g_set_object(&self->jcat_context, jcat_context); } /** * fu_cabinet_get_silo: (skip): * @self: a #FuCabinet * @error: (nullable): optional return location for an error * * Gets the silo that represents the superset metadata of all the metainfo files * found in the archive. * * Returns: (transfer full): a #XbSilo, or %NULL if the archive has not been parsed * * Since: 1.4.0 **/ XbSilo * fu_cabinet_get_silo(FuCabinet *self, GError **error) { g_return_val_if_fail(FU_IS_CABINET(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); if (self->silo == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, "no silo"); return NULL; } return g_object_ref(self->silo); } /** * fu_cabinet_add_file: * @self: a #FuCabinet * @basename: filename * @data: file data * * Adds a file to the silo. * * Since: 1.6.0 **/ void fu_cabinet_add_file(FuCabinet *self, const gchar *basename, GBytes *data) { g_autoptr(FuCabImage) img = fu_cab_image_new(); g_return_if_fail(FU_IS_CABINET(self)); g_return_if_fail(basename != NULL); g_return_if_fail(data != NULL); fu_firmware_set_bytes(FU_FIRMWARE(img), data); fu_firmware_set_id(FU_FIRMWARE(img), basename); fu_firmware_add_image(FU_FIRMWARE(self), FU_FIRMWARE(img)); } /** * fu_cabinet_get_file: * @self: a #FuCabinet * @basename: filename * @error: (nullable): optional return location for an error * * Gets a file from the archive. * * Returns: (transfer full): a #GBytes, or %NULL if the file does not exist * * Since: 1.6.0 **/ GBytes * fu_cabinet_get_file(FuCabinet *self, const gchar *basename, GError **error) { g_autoptr(FuFirmware) img = NULL; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_CABINET(self), NULL); g_return_val_if_fail(basename != NULL, NULL); img = fu_firmware_get_image_by_id(FU_FIRMWARE(self), basename, &error_local); if (img == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, error_local->message); return NULL; } return fu_firmware_get_bytes(img, error); } /* sets the firmware and signature blobs on XbNode */ static gboolean fu_cabinet_parse_release(FuCabinet *self, XbNode *release, GError **error) { const gchar *csum_filename = NULL; g_autofree gchar *basename = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(XbNode) artifact = NULL; g_autoptr(XbNode) csum_tmp = NULL; g_autoptr(XbNode) metadata_trust = NULL; g_autoptr(XbNode) nsize = NULL; g_autoptr(JcatItem) item = NULL; g_autoptr(GBytes) release_flags_blob = NULL; FwupdReleaseFlags release_flags = FWUPD_RELEASE_FLAG_NONE; /* we set this with XbBuilderSource before the silo was created */ metadata_trust = xb_node_query_first(release, "../../info/metadata_trust", NULL); if (metadata_trust != NULL) release_flags |= FWUPD_RELEASE_FLAG_TRUSTED_METADATA; /* look for source artifact first */ artifact = xb_node_query_first(release, "artifacts/artifact[@type='source']", NULL); if (artifact != NULL) { csum_filename = xb_node_query_text(artifact, "filename", NULL); csum_tmp = xb_node_query_first(artifact, "checksum[@type='sha256']", NULL); if (csum_tmp == NULL) csum_tmp = xb_node_query_first(artifact, "checksum", NULL); } else { csum_tmp = xb_node_query_first(release, "checksum[@target='content']", NULL); if (csum_tmp != NULL) csum_filename = xb_node_get_attr(csum_tmp, "filename"); } /* if this isn't true, a firmware needs to set in the metainfo.xml file * something like: */ if (csum_filename == NULL) csum_filename = "firmware.bin"; /* get the main firmware file */ basename = g_path_get_basename(csum_filename); blob = fu_cabinet_get_file(self, basename, error); if (blob == NULL) return FALSE; /* set the blob */ xb_node_set_data(release, "fwupd::FirmwareBlob", blob); /* set as metadata if unset, but error if specified and incorrect */ nsize = xb_node_query_first(release, "size[@type='installed']", NULL); if (nsize != NULL) { guint64 size = 0; if (!fu_strtoull(xb_node_get_text(nsize), &size, 0, G_MAXSIZE, error)) return FALSE; if (size != g_bytes_get_size(blob)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "contents size invalid, expected " "%" G_GSIZE_FORMAT ", got %" G_GUINT64_FORMAT, g_bytes_get_size(blob), size); return FALSE; } } else { guint64 size = g_bytes_get_size(blob); g_autoptr(GBytes) blob_sz = g_bytes_new(&size, sizeof(guint64)); xb_node_set_data(release, "fwupd::ReleaseSize", blob_sz); } /* set if unspecified, but error out if specified and incorrect */ if (csum_tmp != NULL && xb_node_get_text(csum_tmp) != NULL) { const gchar *checksum_old = xb_node_get_text(csum_tmp); GChecksumType checksum_type = fwupd_checksum_guess_kind(checksum_old); g_autofree gchar *checksum = NULL; checksum = g_compute_checksum_for_bytes(checksum_type, blob); if (g_strcmp0(checksum, checksum_old) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "contents checksum invalid, expected %s, got %s", checksum, xb_node_get_text(csum_tmp)); return FALSE; } } /* find out if the payload is signed, falling back to detached */ item = jcat_file_get_item_by_id(self->jcat_file, basename, NULL); if (item != NULL) { g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) results = NULL; results = jcat_context_verify_item(self->jcat_context, blob, item, JCAT_VERIFY_FLAG_REQUIRE_CHECKSUM | JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE, &error_local); if (results == NULL) { g_info("failed to verify payload %s: %s", basename, error_local->message); } else { g_info("verified payload %s: %u", basename, results->len); release_flags |= FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD; } /* legacy GPG detached signature */ } else { g_autofree gchar *basename_sig = NULL; g_autoptr(FuFirmware) img_sig = NULL; basename_sig = g_strdup_printf("%s.asc", basename); img_sig = fu_firmware_get_image_by_id(FU_FIRMWARE(FU_CAB_FIRMWARE(self)), basename_sig, NULL); if (img_sig != NULL) { g_autoptr(JcatResult) jcat_result = NULL; g_autoptr(JcatBlob) jcat_blob = NULL; g_autoptr(GBytes) data_sig = NULL; g_autoptr(GError) error_local = NULL; data_sig = fu_firmware_get_bytes(img_sig, error); if (data_sig == NULL) return FALSE; jcat_blob = jcat_blob_new(JCAT_BLOB_KIND_GPG, data_sig); jcat_result = jcat_context_verify_blob(self->jcat_context, blob, jcat_blob, JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE, &error_local); if (jcat_result == NULL) { g_info("failed to verify payload %s using detached: %s", basename, error_local->message); } else { g_info("verified payload %s using detached", basename); release_flags |= FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD; } } } /* this means we can get the data from fu_keyring_get_release_flags */ release_flags_blob = g_bytes_new(&release_flags, sizeof(release_flags)); xb_node_set_data(release, "fwupd::ReleaseFlags", release_flags_blob); /* success */ return TRUE; } static gint fu_cabinet_sort_cb(XbBuilderNode *bn1, XbBuilderNode *bn2, gpointer user_data) { guint64 prio1 = xb_builder_node_get_attr_as_uint(bn1, "priority"); guint64 prio2 = xb_builder_node_get_attr_as_uint(bn2, "priority"); if (prio1 > prio2) return -1; if (prio1 < prio2) return 1; return 0; } static gboolean fu_cabinet_sort_priority_cb(XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { xb_builder_node_sort_children(bn, fu_cabinet_sort_cb, user_data); return TRUE; } static XbBuilderNode * _xb_builder_node_get_child_by_element_attr(XbBuilderNode *bn, const gchar *element, const gchar *attr_name, const gchar *attr_value, const gchar *attr2_name, const gchar *attr2_value) { GPtrArray *bcs = xb_builder_node_get_children(bn); for (guint i = 0; i < bcs->len; i++) { XbBuilderNode *bc = g_ptr_array_index(bcs, i); if (g_strcmp0(xb_builder_node_get_element(bc), element) != 0) continue; if (g_strcmp0(xb_builder_node_get_attr(bc, attr_name), attr_value) != 0) continue; if (g_strcmp0(xb_builder_node_get_attr(bc, attr2_name), attr2_value) == 0) return g_object_ref(bc); } return NULL; } static void fu_cabinet_ensure_container_checksum(XbBuilderNode *bn, const gchar *type, const gchar *checksum) { g_autoptr(XbBuilderNode) csum = NULL; /* verify it exists */ csum = _xb_builder_node_get_child_by_element_attr(bn, "checksum", "type", type, "target", "container"); if (csum == NULL) { csum = xb_builder_node_insert(bn, "checksum", "type", type, "target", "container", NULL); } /* verify it is correct */ if (g_strcmp0(xb_builder_node_get_text(csum), checksum) != 0) { if (xb_builder_node_get_text(csum) != NULL) { g_warning("invalid container checksum %s, fixing up to %s", xb_builder_node_get_text(csum), checksum); } xb_builder_node_set_text(csum, checksum, -1); } } static gboolean fu_cabinet_ensure_container_checksum_cb(XbBuilderFixup *builder_fixup, XbBuilderNode *bn, gpointer user_data, GError **error) { FuCabinet *self = FU_CABINET(user_data); /* not us */ if (g_strcmp0(xb_builder_node_get_element(bn), "release") != 0) return TRUE; fu_cabinet_ensure_container_checksum(bn, "sha1", self->container_checksum); fu_cabinet_ensure_container_checksum(bn, "sha256", self->container_checksum_alt); return TRUE; } static void fu_cabinet_fixup_checksum_children(XbBuilderNode *bn, const gchar *element, const gchar *attr_name, const gchar *attr_value) { GPtrArray *bcs = xb_builder_node_get_children(bn); for (guint i = 0; i < bcs->len; i++) { XbBuilderNode *bc = g_ptr_array_index(bcs, i); if (g_strcmp0(xb_builder_node_get_element(bc), element) != 0) continue; if (attr_value == NULL || g_strcmp0(xb_builder_node_get_attr(bc, attr_name), attr_value) == 0) { const gchar *tmp = xb_builder_node_get_text(bc); if (tmp != NULL) { g_autofree gchar *lowercase = g_ascii_strdown(tmp, -1); xb_builder_node_set_text(bc, lowercase, -1); } } } } static gboolean fu_cabinet_set_lowercase_checksum_cb(XbBuilderFixup *builder_fixup, XbBuilderNode *bn, gpointer user_data, GError **error) { if (g_strcmp0(xb_builder_node_get_element(bn), "artifact") == 0) /* don't care whether it's sha256, sha1 or something else so don't check for * specific value */ fu_cabinet_fixup_checksum_children(bn, "checksum", "type", NULL); else if (g_strcmp0(xb_builder_node_get_element(bn), "release") == 0) fu_cabinet_fixup_checksum_children(bn, "checksum", "target", "content"); return TRUE; } static gboolean fu_cabinet_fixup_strip_inner_text_cb(XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { if (xb_builder_node_get_first_child(bn) == NULL) xb_builder_node_add_flag(bn, XB_BUILDER_NODE_FLAG_STRIP_TEXT); return TRUE; } /* adds each image to the silo */ static gboolean fu_cabinet_build_silo_file(FuCabinet *self, FuFirmware *img, FwupdReleaseFlags release_flags, GError **error) { g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbBuilderNode) bn_info = xb_builder_node_new("info"); /* indicate the metainfo file was signed */ if (release_flags & FWUPD_RELEASE_FLAG_TRUSTED_METADATA) xb_builder_node_insert_text(bn_info, "metadata_trust", NULL, NULL); xb_builder_node_insert_text(bn_info, "filename", fu_firmware_get_id(img), NULL); xb_builder_source_set_info(source, bn_info); /* rewrite to be under a components root */ xb_builder_source_set_prefix(source, "components"); /* parse file */ blob = fu_firmware_get_bytes(img, error); if (blob == NULL) return FALSE; if (!xb_builder_source_load_bytes(source, blob, XB_BUILDER_SOURCE_FLAG_NONE, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "could not parse MetaInfo XML: %s", error_local->message); return FALSE; } xb_builder_import_source(self->builder, source); /* success */ return TRUE; } static gboolean fu_cabinet_build_silo_metainfo(FuCabinet *self, FuFirmware *img, GError **error) { FwupdReleaseFlags release_flags = FWUPD_RELEASE_FLAG_NONE; const gchar *fn = fu_firmware_get_id(img); g_autoptr(JcatItem) item = NULL; /* validate against the Jcat file */ item = jcat_file_get_item_by_id(self->jcat_file, fn, NULL); if (item == NULL) { g_info("failed to verify %s: no JcatItem", fn); } else if (self->jcat_context != NULL) { g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(GBytes) blob = NULL; blob = fu_firmware_get_bytes(img, error); if (blob == NULL) return FALSE; results = jcat_context_verify_item(self->jcat_context, blob, item, JCAT_VERIFY_FLAG_REQUIRE_CHECKSUM | JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE, &error_local); if (results == NULL) { g_info("failed to verify %s: %s", fn, error_local->message); } else { g_info("verified metadata %s: %u", fn, results->len); release_flags |= FWUPD_RELEASE_FLAG_TRUSTED_METADATA; } } /* actually parse the XML now */ g_info("processing file: %s", fn); if (!fu_cabinet_build_silo_file(self, img, release_flags, error)) { g_prefix_error(error, "%s could not be loaded: ", fn); return FALSE; } /* success */ return TRUE; } /* load the firmware.jcat files if included */ static gboolean fu_cabinet_build_jcat_folder(FuCabinet *self, FuFirmware *img, GError **error) { const gchar *fn = fu_firmware_get_id(img); if (fn == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no extraction name set"); return FALSE; } if (g_str_has_suffix(fn, ".jcat")) { g_autoptr(GBytes) data_jcat = NULL; g_autoptr(GInputStream) istream = NULL; data_jcat = fu_firmware_get_bytes(img, error); if (data_jcat == NULL) return FALSE; istream = g_memory_input_stream_new_from_bytes(data_jcat); if (!jcat_file_import_stream(self->jcat_file, istream, JCAT_IMPORT_FLAG_NONE, NULL, error)) return FALSE; } return TRUE; } /* adds each image to the silo */ static gboolean fu_cabinet_build_silo_folder(FuCabinet *self, FuFirmware *img, GError **error) { const gchar *fn = fu_firmware_get_id(img); if (fn == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no extraction name set"); return FALSE; } if (g_str_has_suffix(fn, ".metainfo.xml")) { if (!fu_cabinet_build_silo_metainfo(self, img, error)) return FALSE; } return TRUE; } static gboolean fu_cabinet_build_silo(FuCabinet *self, GError **error) { g_autoptr(GPtrArray) imgs = NULL; g_autoptr(XbBuilderFixup) fixup1 = NULL; g_autoptr(XbBuilderFixup) fixup2 = NULL; g_autoptr(XbBuilderFixup) fixup3 = NULL; g_autoptr(XbBuilderFixup) fixup4 = NULL; /* verbose profiling */ if (g_getenv("FWUPD_XMLB_VERBOSE") != NULL) { xb_builder_set_profile_flags(self->builder, XB_SILO_PROFILE_FLAG_XPATH | XB_SILO_PROFILE_FLAG_DEBUG); } /* load Jcat */ imgs = fu_firmware_get_images(FU_FIRMWARE(FU_CAB_FIRMWARE(self))); if (self->jcat_context != NULL) { for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); if (!fu_cabinet_build_jcat_folder(self, img, error)) return FALSE; } } /* adds each metainfo file to the silo */ for (guint i = 0; i < imgs->len; i++) { FuFirmware *img = g_ptr_array_index(imgs, i); if (!fu_cabinet_build_silo_folder(self, img, error)) return FALSE; } /* sort the components by priority */ fixup1 = xb_builder_fixup_new("OrderByPriority", fu_cabinet_sort_priority_cb, NULL, NULL); xb_builder_fixup_set_max_depth(fixup1, 0); xb_builder_add_fixup(self->builder, fixup1); /* ensure the container checksum is always set */ fixup2 = xb_builder_fixup_new("EnsureContainerChecksum", fu_cabinet_ensure_container_checksum_cb, self, NULL); xb_builder_add_fixup(self->builder, fixup2); fixup3 = xb_builder_fixup_new("LowerCaseCheckSum", fu_cabinet_set_lowercase_checksum_cb, self, NULL); xb_builder_add_fixup(self->builder, fixup3); /* strip inner nodes without children */ fixup4 = xb_builder_fixup_new("TextStripInner", fu_cabinet_fixup_strip_inner_text_cb, self, NULL); xb_builder_add_fixup(self->builder, fixup4); /* did we get any valid files */ self->silo = xb_builder_compile(self->builder, XB_BUILDER_COMPILE_FLAG_SINGLE_ROOT, NULL, error); if (self->silo == NULL) return FALSE; /* success */ return TRUE; } static gboolean fu_cabinet_sign_filename(FuCabinet *self, const gchar *filename, JcatEngine *jcat_engine, JcatFile *jcat_file, GBytes *cert, GBytes *privkey, GError **error) { g_autoptr(GBytes) source_blob = NULL; g_autoptr(JcatBlob) jcat_blob = NULL; g_autoptr(JcatItem) jcat_item = NULL; /* sign the file using the engine */ source_blob = fu_cabinet_get_file(self, filename, error); if (source_blob == NULL) return FALSE; jcat_item = jcat_file_get_item_by_id(jcat_file, filename, NULL); if (jcat_item == NULL) { jcat_item = jcat_item_new(filename); jcat_file_add_item(jcat_file, jcat_item); } jcat_blob = jcat_engine_pubkey_sign(jcat_engine, source_blob, cert, privkey, JCAT_SIGN_FLAG_ADD_TIMESTAMP | JCAT_SIGN_FLAG_ADD_CERT, error); if (jcat_blob == NULL) return FALSE; jcat_item_add_blob(jcat_item, jcat_blob); return TRUE; } static gboolean fu_cabinet_sign_enumerate_metainfo(FuCabinet *self, GPtrArray *files, GError **error) { g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) nodes = NULL; g_autoptr(XbSilo) silo = NULL; /* get all the firmware referenced by the metainfo files */ silo = fu_cabinet_get_silo(self, error); if (silo == NULL) return FALSE; nodes = xb_silo_query(silo, "components/component[@type='firmware']/info/filename", 0, &error_local); if (nodes == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT) || g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_debug("ignoring: %s", error_local->message); g_ptr_array_add(files, g_strdup("firmware.metainfo.xml")); } else { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } else { for (guint i = 0; i < nodes->len; i++) { XbNode *n = g_ptr_array_index(nodes, i); g_debug("adding: %s", xb_node_get_text(n)); g_ptr_array_add(files, g_strdup(xb_node_get_text(n))); } } /* success */ return TRUE; } static gboolean fu_cabinet_sign_enumerate_firmware(FuCabinet *self, GPtrArray *files, GError **error) { g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) nodes = NULL; g_autoptr(XbSilo) silo = NULL; silo = fu_cabinet_get_silo(self, error); if (silo == NULL) return FALSE; nodes = xb_silo_query(silo, "components/component[@type='firmware']/releases/" "release/checksum[@target='content']", 0, &error_local); if (nodes == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT) || g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_debug("ignoring: %s", error_local->message); g_ptr_array_add(files, g_strdup("firmware.bin")); } else { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } else { for (guint i = 0; i < nodes->len; i++) { XbNode *n = g_ptr_array_index(nodes, i); g_debug("adding: %s", xb_node_get_attr(n, "filename")); g_ptr_array_add(files, g_strdup(xb_node_get_attr(n, "filename"))); } } /* success */ return TRUE; } /** * fu_cabinet_sign: * @self: a #FuCabinet * @cert: a PCKS#7 certificate * @privkey: a private key * @flags: signing flags, e.g. %FU_CABINET_SIGN_FLAG_NONE * @error: (nullable): optional return location for an error * * Sign the cabinet archive using JCat. * * Returns: %TRUE for success * * Since: 1.6.0 **/ gboolean fu_cabinet_sign(FuCabinet *self, GBytes *cert, GBytes *privkey, FuCabinetSignFlags flags, GError **error) { g_autoptr(GBytes) new_bytes = NULL; g_autoptr(GBytes) old_bytes = NULL; g_autoptr(GOutputStream) ostr = NULL; g_autoptr(GPtrArray) filenames = g_ptr_array_new_with_free_func(g_free); g_autoptr(JcatContext) jcat_context = jcat_context_new(); g_autoptr(JcatEngine) jcat_engine = NULL; g_autoptr(JcatFile) jcat_file = jcat_file_new(); /* load existing .jcat file if it exists */ old_bytes = fu_cabinet_get_file(self, "firmware.jcat", NULL); if (old_bytes != NULL) { g_autoptr(GInputStream) istr = NULL; istr = g_memory_input_stream_new_from_bytes(old_bytes); if (!jcat_file_import_stream(jcat_file, istr, JCAT_IMPORT_FLAG_NONE, NULL, error)) return FALSE; } /* get all the metainfo.xml and firmware.bin files */ if (!fu_cabinet_sign_enumerate_metainfo(self, filenames, error)) return FALSE; if (!fu_cabinet_sign_enumerate_firmware(self, filenames, error)) return FALSE; /* sign all the files */ jcat_engine = jcat_context_get_engine(jcat_context, JCAT_BLOB_KIND_PKCS7, error); if (jcat_engine == NULL) return FALSE; for (guint i = 0; i < filenames->len; i++) { const gchar *filename = g_ptr_array_index(filenames, i); if (!fu_cabinet_sign_filename(self, filename, jcat_engine, jcat_file, cert, privkey, error)) return FALSE; } /* export new JCat file and add it to the archive */ ostr = g_memory_output_stream_new_resizable(); if (!jcat_file_export_stream(jcat_file, ostr, JCAT_EXPORT_FLAG_NONE, NULL, error)) return FALSE; new_bytes = g_memory_output_stream_steal_as_bytes(G_MEMORY_OUTPUT_STREAM(ostr)); fu_cabinet_add_file(self, "firmware.jcat", new_bytes); return TRUE; } static gboolean fu_cabinet_parse(FuFirmware *firmware, GBytes *fw, gsize offset, FwupdInstallFlags flags, GError **error) { FuCabinet *self = FU_CABINET(firmware); g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) components = NULL; g_autoptr(XbQuery) query = NULL; g_return_val_if_fail(FU_IS_CABINET(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); g_return_val_if_fail(self->silo == NULL, FALSE); /* decompress and calculate container hashes */ if (fw != NULL) { if (!FU_FIRMWARE_CLASS(fu_cabinet_parent_class) ->parse(firmware, fw, offset, flags, error)) return FALSE; self->container_checksum = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, fw); self->container_checksum_alt = g_compute_checksum_for_bytes(G_CHECKSUM_SHA256, fw); } /* build xmlb silo */ if (!fu_cabinet_build_silo(self, error)) return FALSE; /* sanity check */ components = xb_silo_query(self->silo, "components/component", 0, &error_local); if (components == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "archive contained no valid metadata: %s", error_local->message); return FALSE; } /* prepare query */ query = xb_query_new_full(self->silo, "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, error); if (query == NULL) return FALSE; /* process each listed release */ for (guint i = 0; i < components->len; i++) { XbNode *component = g_ptr_array_index(components, i); g_autoptr(GPtrArray) releases = NULL; if (g_strcmp0(xb_node_get_attr(component, "type"), "generic") == 0) continue; releases = xb_node_query_full(component, query, &error_local); if (releases == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no releases in metainfo file: %s", error_local->message); return FALSE; } for (guint j = 0; j < releases->len; j++) { XbNode *rel = g_ptr_array_index(releases, j); g_info("processing release: %s", xb_node_get_attr(rel, "version")); if (!fu_cabinet_parse_release(self, rel, error)) return FALSE; } } /* success */ return TRUE; } static void fu_cabinet_init(FuCabinet *self) { fu_cab_firmware_set_only_basename(FU_CAB_FIRMWARE(self), TRUE); fu_firmware_set_size_max(FU_FIRMWARE(self), 1024 * 1024 * 100); self->builder = xb_builder_new(); self->jcat_file = jcat_file_new(); self->jcat_context = jcat_context_new(); #if LIBJCAT_CHECK_VERSION(0, 1, 13) jcat_context_blob_kind_allow(self->jcat_context, JCAT_BLOB_KIND_SHA256); jcat_context_blob_kind_allow(self->jcat_context, JCAT_BLOB_KIND_SHA512); jcat_context_blob_kind_allow(self->jcat_context, JCAT_BLOB_KIND_PKCS7); jcat_context_blob_kind_allow(self->jcat_context, JCAT_BLOB_KIND_GPG); #endif } static void fu_cabinet_finalize(GObject *obj) { FuCabinet *self = FU_CABINET(obj); if (self->silo != NULL) g_object_unref(self->silo); if (self->builder != NULL) g_object_unref(self->builder); g_free(self->container_checksum); g_free(self->container_checksum_alt); g_object_unref(self->jcat_context); g_object_unref(self->jcat_file); G_OBJECT_CLASS(fu_cabinet_parent_class)->finalize(obj); } static void fu_cabinet_class_init(FuCabinetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS(klass); object_class->finalize = fu_cabinet_finalize; klass_firmware->parse = fu_cabinet_parse; } /** * fu_cabinet_new: * * Returns: a #FuCabinet * * Since: 1.4.0 **/ FuCabinet * fu_cabinet_new(void) { return g_object_new(FU_TYPE_CABINET, NULL); } fwupd-1.9.16/src/fu-cabinet.h000066400000000000000000000020221460375044200157130ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-cab-firmware.h" #define FU_TYPE_CABINET (fu_cabinet_get_type()) G_DECLARE_FINAL_TYPE(FuCabinet, fu_cabinet, FU, CABINET, FuCabFirmware) /** * FuCabinetSignFlags: * @FU_CABINET_SIGN_FLAG_NONE: No flags set * * The flags to use when signing the archive. **/ typedef enum { FU_CABINET_SIGN_FLAG_NONE = 0, /*< private >*/ FU_CABINET_SIGN_FLAG_LAST } FuCabinetSignFlags; FuCabinet * fu_cabinet_new(void); void fu_cabinet_set_jcat_context(FuCabinet *self, JcatContext *jcat_context); gboolean fu_cabinet_sign(FuCabinet *self, GBytes *cert, GBytes *privkey, FuCabinetSignFlags flags, GError **error) G_GNUC_WARN_UNUSED_RESULT; void fu_cabinet_add_file(FuCabinet *self, const gchar *basename, GBytes *data); GBytes * fu_cabinet_get_file(FuCabinet *self, const gchar *basename, GError **error); XbSilo * fu_cabinet_get_silo(FuCabinet *self, GError **error); fwupd-1.9.16/src/fu-client-list.c000066400000000000000000000132221460375044200165340ustar00rootroot00000000000000/* * Copyright (C) 2024 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuClientList" #include "config.h" #include "fu-client-list.h" struct _FuClientList { GObject parent_instance; GPtrArray *array; /* (element-type FuClientListItem) */ GDBusConnection *connection; /* nullable */ }; typedef struct { FuClientList *self; /* no-ref */ FuClient *client; /* ref */ guint watcher_id; } FuClientListItem; G_DEFINE_TYPE(FuClientList, fu_client_list, G_TYPE_OBJECT) enum { PROP_0, PROP_CONNECTION, PROP_LAST }; enum { SIGNAL_ADDED, SIGNAL_REMOVED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; static void fu_client_list_emit_added(FuClientList *self, FuClient *client) { g_debug("client %s added", fu_client_get_sender(client)); g_signal_emit(self, signals[SIGNAL_ADDED], 0, client); } static void fu_client_list_emit_removed(FuClientList *self, FuClient *client) { g_debug("client %s removed", fu_client_get_sender(client)); g_signal_emit(self, signals[SIGNAL_REMOVED], 0, client); } static void fu_client_list_sender_name_vanished_cb(GDBusConnection *connection, const gchar *name, gpointer user_data) { FuClientListItem *item = (FuClientListItem *)user_data; FuClientList *self = FU_CLIENT_LIST(item->self); g_autoptr(FuClient) client = g_object_ref(item->client); fu_client_remove_flag(client, FU_CLIENT_FLAG_ACTIVE); g_ptr_array_remove(self->array, item); fu_client_list_emit_removed(self, client); } FuClient * fu_client_list_register(FuClientList *self, const gchar *sender) { FuClient *client; FuClientListItem *item; g_return_val_if_fail(FU_IS_CLIENT_LIST(self), NULL); /* already exists */ client = fu_client_list_get_by_sender(self, sender); if (client != NULL) return client; /* create and watch */ item = g_new0(FuClientListItem, 1); item->self = self; item->client = fu_client_new(sender); if (self->connection != NULL && sender != NULL) { item->watcher_id = g_bus_watch_name_on_connection(self->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE, NULL, fu_client_list_sender_name_vanished_cb, item, NULL); } g_ptr_array_add(self->array, item); /* success */ fu_client_list_emit_added(self, item->client); return g_object_ref(item->client); } GPtrArray * fu_client_list_get_all(FuClientList *self) { g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_return_val_if_fail(FU_IS_CLIENT_LIST(self), NULL); for (guint i = 0; i < self->array->len; i++) { FuClientListItem *item = g_ptr_array_index(self->array, i); g_ptr_array_add(array, g_object_ref(item->client)); } return g_steal_pointer(&array); } FuClient * fu_client_list_get_by_sender(FuClientList *self, const gchar *sender) { g_return_val_if_fail(FU_IS_CLIENT_LIST(self), NULL); for (guint i = 0; i < self->array->len; i++) { FuClientListItem *item = g_ptr_array_index(self->array, i); if (g_strcmp0(sender, fu_client_get_sender(item->client)) == 0) return g_object_ref(item->client); } return NULL; } static void fu_client_list_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuClientList *self = FU_CLIENT_LIST(object); switch (prop_id) { case PROP_CONNECTION: g_value_set_object(value, self->connection); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_client_list_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuClientList *self = FU_CLIENT_LIST(object); switch (prop_id) { case PROP_CONNECTION: self->connection = g_value_dup_object(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_client_list_item_free(FuClientListItem *item) { if (item->watcher_id > 0) g_bus_unwatch_name(item->watcher_id); g_object_unref(item->client); g_free(item); } static void fu_client_list_init(FuClientList *self) { self->array = g_ptr_array_new_with_free_func((GDestroyNotify)fu_client_list_item_free); } static void fu_client_list_finalize(GObject *obj) { FuClientList *self = FU_CLIENT_LIST(obj); g_ptr_array_unref(self->array); if (self->connection != NULL) g_object_unref(self->connection); G_OBJECT_CLASS(fu_client_list_parent_class)->finalize(obj); } static void fu_client_list_class_init(FuClientListClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->get_property = fu_client_list_get_property; object_class->set_property = fu_client_list_set_property; object_class->finalize = fu_client_list_finalize; pspec = g_param_spec_object("connection", NULL, NULL, G_TYPE_DBUS_CONNECTION, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_CONNECTION, pspec); signals[SIGNAL_ADDED] = g_signal_new("added", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, FU_TYPE_CLIENT); signals[SIGNAL_REMOVED] = g_signal_new("removed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, FU_TYPE_CLIENT); } FuClientList * fu_client_list_new(GDBusConnection *connection) { FuClientList *self; g_return_val_if_fail(connection == NULL || G_IS_DBUS_CONNECTION(connection), NULL); self = g_object_new(FU_TYPE_CLIENT_LIST, "connection", connection, NULL); return FU_CLIENT_LIST(self); } fwupd-1.9.16/src/fu-client-list.h000066400000000000000000000011111460375044200165330ustar00rootroot00000000000000/* * Copyright (C) 2024 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-client.h" #define FU_TYPE_CLIENT_LIST (fu_client_list_get_type()) G_DECLARE_FINAL_TYPE(FuClientList, fu_client_list, FU, CLIENT_LIST, GObject) FuClientList * fu_client_list_new(GDBusConnection *connection); GPtrArray * fu_client_list_get_all(FuClientList *self); FuClient * fu_client_list_register(FuClientList *self, const gchar *sender); FuClient * fu_client_list_get_by_sender(FuClientList *self, const gchar *sender); fwupd-1.9.16/src/fu-client.c000066400000000000000000000101071460375044200155620ustar00rootroot00000000000000/* * Copyright (C) 2024 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuClient" #include "config.h" #include "fu-client.h" struct _FuClient { GObject parent_instance; gchar *sender; GHashTable *hints; /* str:str */ FwupdFeatureFlags feature_flags; FuClientFlag flags; }; G_DEFINE_TYPE(FuClient, fu_client, G_TYPE_OBJECT) enum { PROP_0, PROP_SENDER, PROP_FLAGS, PROP_LAST }; void fu_client_set_feature_flags(FuClient *self, FwupdFeatureFlags feature_flags) { g_return_if_fail(FU_IS_CLIENT(self)); self->feature_flags = feature_flags; } FwupdFeatureFlags fu_client_get_feature_flags(FuClient *self) { g_return_val_if_fail(FU_IS_CLIENT(self), FWUPD_FEATURE_FLAG_NONE); return self->feature_flags; } const gchar * fu_client_get_sender(FuClient *self) { g_return_val_if_fail(FU_IS_CLIENT(self), NULL); return self->sender; } const gchar * fu_client_lookup_hint(FuClient *self, const gchar *key) { g_return_val_if_fail(FU_IS_CLIENT(self), NULL); g_return_val_if_fail(key != NULL, NULL); return g_hash_table_lookup(self->hints, key); } void fu_client_insert_hint(FuClient *self, const gchar *key, const gchar *value) { g_return_if_fail(FU_IS_CLIENT(self)); g_return_if_fail(key != NULL); g_return_if_fail(value != NULL); g_hash_table_insert(self->hints, g_strdup(key), g_strdup(value)); } static void fu_client_add_flag(FuClient *self, FuClientFlag flag) { g_return_if_fail(FU_IS_CLIENT(self)); g_return_if_fail(flag != FU_CLIENT_FLAG_NONE); if ((self->flags & flag) > 0) return; self->flags |= flag; g_object_notify(G_OBJECT(self), "flags"); } void fu_client_remove_flag(FuClient *self, FuClientFlag flag) { g_return_if_fail(FU_IS_CLIENT(self)); g_return_if_fail(flag != FU_CLIENT_FLAG_NONE); if ((self->flags & flag) == 0) return; self->flags &= ~flag; g_object_notify(G_OBJECT(self), "flags"); } gboolean fu_client_has_flag(FuClient *self, FuClientFlag flag) { g_return_val_if_fail(FU_IS_CLIENT(self), FALSE); g_return_val_if_fail(flag != FU_CLIENT_FLAG_NONE, FALSE); return (self->flags & flag) > 0; } static void fu_client_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuClient *self = FU_CLIENT(object); switch (prop_id) { case PROP_SENDER: g_value_set_string(value, self->sender); break; case PROP_FLAGS: g_value_set_uint64(value, self->flags); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_client_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuClient *self = FU_CLIENT(object); switch (prop_id) { case PROP_SENDER: self->sender = g_value_dup_string(value); break; case PROP_FLAGS: fu_client_add_flag(self, (FuClientFlag)g_value_get_uint64(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_client_init(FuClient *self) { self->hints = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); } static void fu_client_finalize(GObject *obj) { FuClient *self = FU_CLIENT(obj); g_free(self->sender); g_hash_table_unref(self->hints); G_OBJECT_CLASS(fu_client_parent_class)->finalize(obj); } static void fu_client_class_init(FuClientClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GParamSpec *pspec; object_class->finalize = fu_client_finalize; object_class->get_property = fu_client_get_property; object_class->set_property = fu_client_set_property; pspec = g_param_spec_string("sender", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_SENDER, pspec); pspec = g_param_spec_uint64("flags", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_FLAGS, pspec); } FuClient * fu_client_new(const gchar *sender) { return FU_CLIENT(g_object_new(FU_TYPE_CLIENT, "sender", sender, "flags", (guint64)FU_CLIENT_FLAG_ACTIVE, NULL)); } fwupd-1.9.16/src/fu-client.h000066400000000000000000000014561460375044200155760ustar00rootroot00000000000000/* * Copyright (C) 2024 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-engine-struct.h" #define FU_TYPE_CLIENT (fu_client_get_type()) G_DECLARE_FINAL_TYPE(FuClient, fu_client, FU, CLIENT, GObject) FuClient * fu_client_new(const gchar *sender); const gchar * fu_client_get_sender(FuClient *self); const gchar * fu_client_lookup_hint(FuClient *self, const gchar *key); void fu_client_insert_hint(FuClient *self, const gchar *key, const gchar *value); void fu_client_set_feature_flags(FuClient *self, FwupdFeatureFlags feature_flags); FwupdFeatureFlags fu_client_get_feature_flags(FuClient *self); void fu_client_remove_flag(FuClient *self, FuClientFlag flag); gboolean fu_client_has_flag(FuClient *self, FuClientFlag flag); fwupd-1.9.16/src/fu-console.c000066400000000000000000000452071460375044200157570ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuProgressBar" #include "config.h" #include #include #include "fu-console.h" #ifdef _WIN32 #include #include #endif struct _FuConsole { GObject parent_instance; GMainContext *main_ctx; FwupdStatus status; gboolean spinner_count_up; /* width in visible chars */ guint spinner_idx; /* width in visible chars */ guint length_percentage; /* width in visible chars */ guint length_status; /* width in visible chars */ guint percentage; GSource *timer_source; gint64 last_animated; /* monotonic */ GTimer *time_elapsed; gdouble last_estimate; gboolean interactive; gboolean contents_to_clear; }; G_DEFINE_TYPE(FuConsole, fu_console, G_TYPE_OBJECT) gboolean fu_console_setup(FuConsole *self, GError **error) { #ifdef _WIN32 HANDLE hOut; DWORD dwMode = 0; /* enable VT sequences */ hOut = GetStdHandle(STD_OUTPUT_HANDLE); if (hOut == INVALID_HANDLE_VALUE) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to get stdout [%u]", (guint)GetLastError()); return FALSE; } if (!GetConsoleMode(hOut, &dwMode)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to get mode [%u]", (guint)GetLastError()); return FALSE; } dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; if (!SetConsoleMode(hOut, dwMode)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to set mode [%u]", (guint)GetLastError()); return FALSE; } if (!SetConsoleOutputCP(CP_UTF8)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to set output UTF-8 [%u]", (guint)GetLastError()); return FALSE; } if (!SetConsoleCP(CP_UTF8)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "failed to set UTF-8 [%u]", (guint)GetLastError()); return FALSE; } #else if (isatty(fileno(stdout)) == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "not a TTY"); return FALSE; } #endif /* success */ return TRUE; } static void fu_console_erase_line(FuConsole *self) { if (!self->interactive) return; g_print("\033[G"); } static void fu_console_reset_line(FuConsole *self) { if (self->contents_to_clear) { fu_console_erase_line(self); g_print("\n"); self->contents_to_clear = FALSE; } } void fu_console_print_kv(FuConsole *self, const gchar *title, const gchar *msg) { gsize title_len; g_auto(GStrv) lines = NULL; if (msg == NULL) return; fu_console_reset_line(self); g_print("%s:", title); /* pad */ title_len = fu_strwidth(title) + 1; lines = g_strsplit(msg, "\n", -1); for (guint j = 0; lines[j] != NULL; j++) { for (gsize i = title_len; i < 25; i++) g_print(" "); g_print("%s\n", lines[j]); title_len = 0; } } guint fu_console_input_uint(FuConsole *self, guint maxnum, const gchar *format, ...) { gint retval; guint answer = 0; va_list args; g_autofree gchar *tmp = NULL; va_start(args, format); tmp = g_strdup_vprintf(format, args); va_end(args); fu_console_print_full(self, FU_CONSOLE_PRINT_FLAG_NONE, "%s [0-%u]: ", tmp, maxnum); do { char buffer[64]; /* swallow the \n at end of line too */ if (!fgets(buffer, sizeof(buffer), stdin)) break; if (strlen(buffer) == sizeof(buffer) - 1) continue; /* get a number */ retval = sscanf(buffer, "%u", &answer); /* positive */ if (retval == 1 && answer <= maxnum) break; fu_console_print_full(self, FU_CONSOLE_PRINT_FLAG_NONE, /* TRANSLATORS: the user isn't reading the question */ _("Please enter a number from 0 to %u: "), maxnum); } while (TRUE); return answer; } gboolean fu_console_input_bool(FuConsole *self, gboolean def, const gchar *format, ...) { va_list args; g_autofree gchar *tmp = NULL; va_start(args, format); tmp = g_strdup_vprintf(format, args); va_end(args); fu_console_print_full(self, FU_CONSOLE_PRINT_FLAG_NONE, "%s [%s]: ", tmp, def ? "Y|n" : "y|N"); do { char buffer[4]; if (!fgets(buffer, sizeof(buffer), stdin)) continue; if (strlen(buffer) == sizeof(buffer) - 1) continue; if (g_strcmp0(buffer, "\n") == 0) return def; buffer[0] = g_ascii_toupper(buffer[0]); if (g_strcmp0(buffer, "Y\n") == 0) return TRUE; if (g_strcmp0(buffer, "N\n") == 0) return FALSE; /* TRANSLATORS: the user isn't reading the question -- please don't translate * 'Y' or 'N' as these are hardcoded */ fu_console_print_literal(self, _("Please enter either Y or N: ")); } while (TRUE); return FALSE; } static GPtrArray * fu_console_strsplit_words(const gchar *text, guint line_len) { g_auto(GStrv) tokens = NULL; g_autoptr(GPtrArray) lines = g_ptr_array_new_with_free_func(g_free); g_autoptr(GString) curline = g_string_new(NULL); /* sanity check */ if (text == NULL || text[0] == '\0') return NULL; if (line_len == 0) return NULL; /* tokenize the string */ tokens = g_strsplit(text, " ", -1); for (guint i = 0; tokens[i] != NULL; i++) { /* current line plus new token is okay */ if (curline->len + fu_strwidth(tokens[i]) < line_len) { g_string_append_printf(curline, "%s ", tokens[i]); continue; } /* too long, so remove space, add newline and dump */ if (curline->len > 0) g_string_truncate(curline, curline->len - 1); g_ptr_array_add(lines, g_strdup(curline->str)); g_string_truncate(curline, 0); g_string_append_printf(curline, "%s ", tokens[i]); } /* any incomplete line? */ if (curline->len > 0) { g_string_truncate(curline, curline->len - 1); g_ptr_array_add(lines, g_strdup(curline->str)); } return g_steal_pointer(&lines); } static void fu_console_box_line(const gchar *start, const gchar *text, const gchar *end, const gchar *padding, guint width) { guint offset = 0; if (start != NULL) { offset += fu_strwidth(start); g_print("%s", start); } if (text != NULL) { offset += fu_strwidth(text); g_print("%s", text); } if (end != NULL) offset += fu_strwidth(end); for (guint i = offset; i < width; i++) g_print("%s", padding); if (end != NULL) g_print("%s\n", end); } void fu_console_line(FuConsole *self, guint width) { g_autoptr(GString) str = g_string_new_len(NULL, width); for (guint i = 0; i < width; i++) g_string_append(str, "─"); fu_console_print_literal(self, str->str); } void fu_console_box(FuConsole *self, const gchar *title, const gchar *body, guint width) { /* nothing to do */ if (title == NULL && body == NULL) return; /* header */ fu_console_reset_line(self); fu_console_box_line("╔", NULL, "╗", "═", width); /* optional title */ if (title != NULL) { g_autoptr(GPtrArray) lines = fu_console_strsplit_words(title, width - 4); for (guint j = 0; j < lines->len; j++) { const gchar *line = g_ptr_array_index(lines, j); fu_console_box_line("║ ", line, " ║", " ", width); } } /* join */ if (title != NULL && body != NULL) fu_console_box_line("╠", NULL, "╣", "═", width); /* optional body */ if (body != NULL) { gboolean has_nonempty = FALSE; g_auto(GStrv) split = g_strsplit(body, "\n", -1); for (guint i = 0; split[i] != NULL; i++) { g_autoptr(GPtrArray) lines = fu_console_strsplit_words(split[i], width - 4); if (lines == NULL) { if (has_nonempty) { fu_console_box_line("║ ", NULL, " ║", " ", width); has_nonempty = FALSE; } continue; } for (guint j = 0; j < lines->len; j++) { const gchar *line = g_ptr_array_index(lines, j); fu_console_box_line("║ ", line, " ║", " ", width); } has_nonempty = TRUE; } } /* footer */ fu_console_box_line("╚", NULL, "╝", "═", width); } static const gchar * fu_console_status_to_string(FwupdStatus status) { switch (status) { case FWUPD_STATUS_IDLE: /* TRANSLATORS: daemon is inactive */ return _("Idle…"); break; case FWUPD_STATUS_DECOMPRESSING: /* TRANSLATORS: decompressing the firmware file */ return _("Decompressing…"); break; case FWUPD_STATUS_LOADING: /* TRANSLATORS: parsing the firmware information */ return _("Loading…"); break; case FWUPD_STATUS_DEVICE_RESTART: /* TRANSLATORS: restarting the device to pick up new F/W */ return _("Restarting device…"); break; case FWUPD_STATUS_DEVICE_READ: /* TRANSLATORS: reading from the flash chips */ return _("Reading…"); break; case FWUPD_STATUS_DEVICE_WRITE: /* TRANSLATORS: writing to the flash chips */ return _("Writing…"); break; case FWUPD_STATUS_DEVICE_ERASE: /* TRANSLATORS: erasing contents of the flash chips */ return _("Erasing…"); break; case FWUPD_STATUS_DEVICE_VERIFY: /* TRANSLATORS: verifying we wrote the firmware correctly */ return _("Verifying…"); break; case FWUPD_STATUS_SCHEDULING: /* TRANSLATORS: scheduling an update to be done on the next boot */ return _("Scheduling…"); break; case FWUPD_STATUS_DOWNLOADING: /* TRANSLATORS: downloading from a remote server */ return _("Downloading…"); break; case FWUPD_STATUS_WAITING_FOR_AUTH: /* TRANSLATORS: waiting for user to authenticate */ return _("Authenticating…"); break; case FWUPD_STATUS_DEVICE_BUSY: case FWUPD_STATUS_WAITING_FOR_USER: /* TRANSLATORS: waiting for device to do something */ return _("Waiting…"); break; default: break; } /* TRANSLATORS: current daemon status is unknown */ return _("Unknown"); } static gboolean _fu_status_is_predictable(FwupdStatus status) { if (status == FWUPD_STATUS_DEVICE_ERASE) return TRUE; if (status == FWUPD_STATUS_DEVICE_VERIFY) return TRUE; if (status == FWUPD_STATUS_DEVICE_READ) return TRUE; if (status == FWUPD_STATUS_DEVICE_WRITE) return TRUE; if (status == FWUPD_STATUS_DOWNLOADING) return TRUE; return FALSE; } static gboolean fu_console_estimate_ready(FuConsole *self, guint percentage) { gdouble old; gdouble elapsed; /* now invalid */ if (percentage == 0 || percentage == 100) { g_timer_start(self->time_elapsed); self->last_estimate = 0; return FALSE; } /* allow-list things that make sense... */ if (!_fu_status_is_predictable(self->status)) return FALSE; old = self->last_estimate; elapsed = g_timer_elapsed(self->time_elapsed, NULL); self->last_estimate = elapsed / percentage * (100 - percentage); /* estimate is ready if we have decreased */ return old > self->last_estimate; } static gchar * fu_console_time_remaining_str(FuConsole *self) { /* less than 5 seconds remaining */ if (self->last_estimate < 5) return NULL; /* less than 60 seconds remaining */ if (self->last_estimate < 60) { /* TRANSLATORS: time remaining for completing firmware flash */ return g_strdup(_("Less than one minute remaining")); } return g_strdup_printf( /* TRANSLATORS: more than a minute */ ngettext("%.0f minute remaining", "%.0f minutes remaining", self->last_estimate / 60), self->last_estimate / 60); } static void fu_console_refresh(FuConsole *self) { const gchar *title; guint i; g_autoptr(GString) str = g_string_new(NULL); /* sanity check */ if (self->status == FWUPD_STATUS_IDLE || self->status == FWUPD_STATUS_UNKNOWN) return; /* erase previous line */ fu_console_erase_line(self); /* add status */ title = fu_console_status_to_string(self->status); g_string_append(str, title); for (i = fu_strwidth(str->str); i < self->length_status; i++) g_string_append_c(str, ' '); /* add console */ g_string_append(str, "["); if (self->percentage > 0) { for (i = 0; i < (self->length_percentage - 1) * self->percentage / 100; i++) g_string_append_c(str, '*'); for (i = i + 1; i < self->length_percentage; i++) g_string_append_c(str, ' '); } else { const gchar chars[] = { '-', '\\', '|', '/', }; for (i = 0; i < self->spinner_idx; i++) g_string_append_c(str, ' '); g_string_append_c(str, chars[i / 4 % G_N_ELEMENTS(chars)]); for (i = i + 1; i < self->length_percentage - 1; i++) g_string_append_c(str, ' '); } g_string_append_c(str, ']'); /* once we have good data show an estimate of time remaining */ if (fu_console_estimate_ready(self, self->percentage)) { g_autofree gchar *remaining = fu_console_time_remaining_str(self); if (remaining != NULL) g_string_append_printf(str, " %s…", remaining); } /* dump to screen */ g_print("%s", str->str); self->contents_to_clear = TRUE; } /** * fu_console_print_full: * @self: a #FuConsole * @flags; a #FuConsolePrintFlags, e.g. %FU_CONSOLE_PRINT_FLAG_STDERR * @text: string * * Clears the console, and prints the text. **/ void fu_console_print_full(FuConsole *self, FuConsolePrintFlags flags, const gchar *format, ...) { va_list args; g_autoptr(GString) str = g_string_new(NULL); va_start(args, format); g_string_append_vprintf(str, format, args); va_end(args); if (flags & FU_CONSOLE_PRINT_FLAG_WARNING) { /* TRANSLATORS: this is a prefix on the console */ g_autofree gchar *fmt = fu_console_color_format(_("WARNING"), FU_CONSOLE_COLOR_RED); g_string_prepend(str, ": "); g_string_prepend(str, fmt); flags |= FU_CONSOLE_PRINT_FLAG_STDERR; } fu_console_reset_line(self); if (flags & FU_CONSOLE_PRINT_FLAG_STDERR) { g_printerr("%s", str->str); } else { g_print("%s", str->str); } } void fu_console_print_literal(FuConsole *self, const gchar *text) { fu_console_reset_line(self); g_print("%s\n", text); } /** * fu_console_print: * @self: a #FuConsole * @text: string * * Clears the console, prints the text and prints a newline. **/ void fu_console_print(FuConsole *self, const gchar *format, ...) { va_list args; g_autofree gchar *tmp = NULL; va_start(args, format); tmp = g_strdup_vprintf(format, args); va_end(args); fu_console_print_literal(self, tmp); } /** * fu_console_set_progress_title: * @self: A #FuConsole * @title: A string * * Sets console title **/ void fu_console_set_progress_title(FuConsole *self, const gchar *title) { if (!self->interactive) return; fu_console_erase_line(self); g_print("%s\n", title); fu_console_refresh(self); } /** * fu_console_set_main_context: * @self: A #FuConsole * @main_ctx: (nullable): main context * * Sets console main context to use for animations. **/ void fu_console_set_main_context(FuConsole *self, GMainContext *main_ctx) { self->main_ctx = g_main_context_ref(main_ctx); } static void fu_console_spin_inc(FuConsole *self) { /* reset */ self->last_animated = g_get_monotonic_time(); /* up to down */ if (self->spinner_count_up) { if (++self->spinner_idx > self->length_percentage - 3) self->spinner_count_up = FALSE; } else { if (--self->spinner_idx == 0) self->spinner_count_up = TRUE; } } static gboolean fu_console_spin_cb(gpointer user_data) { FuConsole *self = FU_CONSOLE(user_data); /* move the spinner index up to down */ fu_console_spin_inc(self); /* update the terminal */ fu_console_refresh(self); return G_SOURCE_CONTINUE; } static void fu_console_spin_end(FuConsole *self) { if (self->timer_source != NULL) { g_source_destroy(self->timer_source); self->timer_source = NULL; /* reset when the spinner has been stopped */ g_timer_start(self->time_elapsed); } /* go back to the start when we next go into unknown percentage mode */ self->spinner_idx = 0; self->spinner_count_up = TRUE; } static void fu_console_spin_start(FuConsole *self) { if (self->timer_source != NULL) g_source_destroy(self->timer_source); self->timer_source = g_timeout_source_new(40); g_source_set_callback(self->timer_source, fu_console_spin_cb, self, NULL); g_source_attach(self->timer_source, self->main_ctx); } /** * fu_console_set_progress: * @self: A #FuConsole * @status: A #FwupdStatus * @percentage: unsigned integer * * Refreshes the progress bar with the new percentage and status. **/ void fu_console_set_progress(FuConsole *self, FwupdStatus status, guint percentage) { g_return_if_fail(FU_IS_CONSOLE(self)); /* not useful */ if (status == FWUPD_STATUS_UNKNOWN) return; /* ignore duplicates */ if (self->status == status && self->percentage == percentage) return; /* cache */ self->status = status; self->percentage = percentage; /* dumb */ if (!self->interactive) { g_printerr("%s: %u%%\n", fu_console_status_to_string(status), percentage); return; } /* if the main loop isn't spinning and we've not had a chance to * execute the callback just do the refresh now manually */ if (percentage == 0 && status != FWUPD_STATUS_IDLE && self->status != FWUPD_STATUS_UNKNOWN) { if ((g_get_monotonic_time() - self->last_animated) / 1000 > 40) { fu_console_spin_inc(self); fu_console_refresh(self); } } /* enable or disable the spinner timeout */ if (percentage > 0) { fu_console_spin_end(self); } else { fu_console_spin_start(self); } /* update the terminal */ fu_console_refresh(self); } /** * fu_console_set_interactive: * @self: A #FuConsole * @interactive: #gboolean * * Marks the console as interactive or not **/ void fu_console_set_interactive(FuConsole *self, gboolean interactive) { g_return_if_fail(FU_IS_CONSOLE(self)); self->interactive = interactive; } /** * fu_console_set_status_length: * @self: A #FuConsole * @len: unsigned integer * * Sets the width of the progressbar status, which must be greater that 3. **/ void fu_console_set_status_length(FuConsole *self, guint len) { g_return_if_fail(FU_IS_CONSOLE(self)); g_return_if_fail(len > 3); self->length_status = len; } /** * fu_console_set_percentage_length: * @self: A #FuConsole * @len: unsigned integer * * Sets the width of the progressbar percentage, which must be greater that 3. **/ void fu_console_set_percentage_length(FuConsole *self, guint len) { g_return_if_fail(FU_IS_CONSOLE(self)); g_return_if_fail(len > 3); self->length_percentage = len; } void fu_console_beep(FuConsole *self, guint count) { for (guint i = 0; i < count; i++) { if (i > 0) g_usleep(250000); g_print("\007"); } } static void fu_console_init(FuConsole *self) { self->length_percentage = 40; self->length_status = 25; self->spinner_count_up = TRUE; self->time_elapsed = g_timer_new(); self->interactive = TRUE; } static void fu_console_finalize(GObject *obj) { FuConsole *self = FU_CONSOLE(obj); fu_console_reset_line(self); if (self->timer_source != 0) g_source_destroy(self->timer_source); if (self->main_ctx != NULL) g_main_context_unref(self->main_ctx); g_timer_destroy(self->time_elapsed); G_OBJECT_CLASS(fu_console_parent_class)->finalize(obj); } static void fu_console_class_init(FuConsoleClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_console_finalize; } /** * fu_console_new: * * Creates a new #FuConsole **/ FuConsole * fu_console_new(void) { FuConsole *self; self = g_object_new(FU_TYPE_CONSOLE, NULL); return FU_CONSOLE(self); } fwupd-1.9.16/src/fu-console.h000066400000000000000000000040201460375044200157500ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_CONSOLE (fu_console_get_type()) G_DECLARE_FINAL_TYPE(FuConsole, fu_console, FU, CONSOLE, GObject) typedef enum { FU_CONSOLE_COLOR_BLACK = 30, FU_CONSOLE_COLOR_RED = 31, FU_CONSOLE_COLOR_GREEN = 32, FU_CONSOLE_COLOR_YELLOW = 33, FU_CONSOLE_COLOR_BLUE = 34, FU_CONSOLE_COLOR_MAGENTA = 35, FU_CONSOLE_COLOR_CYAN = 36, FU_CONSOLE_COLOR_WHITE = 37, } FuConsoleColor; typedef enum { FU_CONSOLE_PRINT_FLAG_NONE = 0, FU_CONSOLE_PRINT_FLAG_STDERR = 1 << 0, FU_CONSOLE_PRINT_FLAG_WARNING = 1 << 1, } FuConsolePrintFlags; gchar * fu_console_color_format(const gchar *text, FuConsoleColor fg_color); FuConsole * fu_console_new(void); gboolean fu_console_setup(FuConsole *self, GError **error); guint fu_console_input_uint(FuConsole *self, guint maxnum, const gchar *format, ...) G_GNUC_PRINTF(3, 4); gboolean fu_console_input_bool(FuConsole *self, gboolean def, const gchar *format, ...) G_GNUC_PRINTF(3, 4); void fu_console_print_full(FuConsole *self, FuConsolePrintFlags flags, const gchar *format, ...) G_GNUC_PRINTF(3, 4); void fu_console_print(FuConsole *self, const gchar *format, ...) G_GNUC_PRINTF(2, 3); void fu_console_print_literal(FuConsole *self, const gchar *text); void fu_console_print_kv(FuConsole *self, const gchar *title, const gchar *msg); void fu_console_line(FuConsole *self, guint width); void fu_console_box(FuConsole *self, const gchar *title, const gchar *body, guint width); void fu_console_beep(FuConsole *self, guint count); void fu_console_set_progress(FuConsole *self, FwupdStatus status, guint percentage); void fu_console_set_status_length(FuConsole *self, guint len); void fu_console_set_percentage_length(FuConsole *self, guint len); void fu_console_set_progress_title(FuConsole *self, const gchar *title); void fu_console_set_interactive(FuConsole *self, gboolean interactive); void fu_console_set_main_context(FuConsole *self, GMainContext *main_ctx); fwupd-1.9.16/src/fu-daemon.c000066400000000000000000002315401460375044200155550ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include "config.h" #include #ifdef HAVE_GIO_UNIX #include #endif #include #include #include "fwupd-bios-setting-private.h" #include "fwupd-device-private.h" #include "fwupd-enums-private.h" #include "fwupd-plugin-private.h" #include "fwupd-release-private.h" #include "fwupd-remote-private.h" #include "fwupd-request-private.h" #include "fwupd-security-attr-private.h" #include "fu-bios-settings-private.h" #include "fu-client-list.h" #include "fu-context-private.h" #include "fu-daemon.h" #include "fu-device-private.h" #include "fu-engine-helper.h" #include "fu-engine-requirements.h" #include "fu-engine.h" #include "fu-polkit-authority.h" #include "fu-release.h" #include "fu-security-attrs-private.h" static void fu_daemon_finalize(GObject *obj); struct _FuDaemon { GObject parent_instance; GDBusConnection *connection; GDBusNodeInfo *introspection_daemon; GDBusProxy *proxy_uid; GMainLoop *loop; FuClientList *client_list; guint32 clients_inhibit_id; FuPolkitAuthority *authority; FwupdStatus status; /* last emitted */ guint percentage; /* last emitted */ guint owner_id; guint process_quit_id; FuEngine *engine; gboolean update_in_progress; gboolean pending_stop; FuDaemonMachineKind machine_kind; GPtrArray *system_inhibits; }; G_DEFINE_TYPE(FuDaemon, fu_daemon, G_TYPE_OBJECT) void fu_daemon_start(FuDaemon *self) { g_return_if_fail(FU_IS_DAEMON(self)); g_main_loop_run(self->loop); } void fu_daemon_stop(FuDaemon *self) { g_return_if_fail(FU_IS_DAEMON(self)); if (self->update_in_progress) { self->pending_stop = TRUE; return; } g_main_loop_quit(self->loop); } static void fu_daemon_engine_changed_cb(FuEngine *engine, FuDaemon *self) { /* not yet connected */ if (self->connection == NULL) return; g_dbus_connection_emit_signal(self->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "Changed", NULL, NULL); } static void fu_daemon_engine_device_added_cb(FuEngine *engine, FuDevice *device, FuDaemon *self) { GVariant *val; /* not yet connected */ if (self->connection == NULL) return; val = fwupd_device_to_variant(FWUPD_DEVICE(device)); g_dbus_connection_emit_signal(self->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "DeviceAdded", g_variant_new_tuple(&val, 1), NULL); } static void fu_daemon_engine_device_removed_cb(FuEngine *engine, FuDevice *device, FuDaemon *self) { GVariant *val; /* not yet connected */ if (self->connection == NULL) return; val = fwupd_device_to_variant(FWUPD_DEVICE(device)); g_dbus_connection_emit_signal(self->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "DeviceRemoved", g_variant_new_tuple(&val, 1), NULL); } static void fu_daemon_engine_device_changed_cb(FuEngine *engine, FuDevice *device, FuDaemon *self) { GVariant *val; /* not yet connected */ if (self->connection == NULL) return; val = fwupd_device_to_variant(FWUPD_DEVICE(device)); g_dbus_connection_emit_signal(self->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "DeviceChanged", g_variant_new_tuple(&val, 1), NULL); } static void fu_daemon_engine_device_request_cb(FuEngine *engine, FwupdRequest *request, FuDaemon *self) { GVariant *val; /* not yet connected */ if (self->connection == NULL) return; val = fwupd_request_to_variant(FWUPD_REQUEST(request)); g_dbus_connection_emit_signal(self->connection, NULL, FWUPD_DBUS_PATH, FWUPD_DBUS_INTERFACE, "DeviceRequest", g_variant_new_tuple(&val, 1), NULL); } static void fu_daemon_emit_property_changed(FuDaemon *self, const gchar *property_name, GVariant *property_value) { GVariantBuilder builder; GVariantBuilder invalidated_builder; /* not yet connected */ if (self->connection == NULL) { g_variant_unref(g_variant_ref_sink(property_value)); return; } /* build the dict */ g_variant_builder_init(&invalidated_builder, G_VARIANT_TYPE("as")); g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); g_variant_builder_add(&builder, "{sv}", property_name, property_value); g_dbus_connection_emit_signal( self->connection, NULL, FWUPD_DBUS_PATH, "org.freedesktop.DBus.Properties", "PropertiesChanged", g_variant_new("(sa{sv}as)", FWUPD_DBUS_INTERFACE, &builder, &invalidated_builder), NULL); g_variant_builder_clear(&builder); g_variant_builder_clear(&invalidated_builder); } static void fu_daemon_set_status(FuDaemon *self, FwupdStatus status) { /* sanity check */ if (self->status == status) return; self->status = status; g_debug("Emitting PropertyChanged('Status'='%s')", fwupd_status_to_string(status)); fu_daemon_emit_property_changed(self, "Status", g_variant_new_uint32(status)); } static void fu_daemon_engine_status_changed_cb(FuEngine *engine, FwupdStatus status, FuDaemon *self) { fu_daemon_set_status(self, status); /* engine has gone idle */ if (status == FWUPD_STATUS_SHUTDOWN) g_main_loop_quit(self->loop); } static FuEngineRequest * fu_daemon_create_request(FuDaemon *self, const gchar *sender, GError **error) { FwupdDeviceFlags device_flags = FWUPD_DEVICE_FLAG_NONE; guint calling_uid = 0; g_autoptr(FuClient) client = NULL; g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(GVariant) value = NULL; /* if using FWUPD_DBUS_SOCKET... */ if (sender == NULL) { fu_engine_request_set_device_flags(request, FWUPD_DEVICE_FLAG_TRUSTED); return g_steal_pointer(&request); } /* did the client set the list of supported features or any hints */ client = fu_client_list_get_by_sender(self->client_list, sender); if (client != NULL) { const gchar *locale = fu_client_lookup_hint(client, "locale"); if (locale != NULL) fu_engine_request_set_locale(request, locale); fu_engine_request_set_feature_flags(request, fu_client_get_feature_flags(client)); } /* are we root and therefore trusted? */ value = g_dbus_proxy_call_sync(self->proxy_uid, "GetConnectionUnixUser", g_variant_new("(s)", sender), G_DBUS_CALL_FLAGS_NONE, 2000, NULL, error); if (value == NULL) { g_prefix_error(error, "failed to read user id of caller: "); return NULL; } g_variant_get(value, "(u)", &calling_uid); if (fu_engine_is_uid_trusted(self->engine, calling_uid)) device_flags |= FWUPD_DEVICE_FLAG_TRUSTED; fu_engine_request_set_device_flags(request, device_flags); /* success */ return g_steal_pointer(&request); } static GVariant * fu_daemon_device_array_to_variant(FuDaemon *self, FuEngineRequest *request, GPtrArray *devices, GError **error) { GVariantBuilder builder; FwupdDeviceFlags flags = fu_engine_request_get_device_flags(request); g_return_val_if_fail(devices->len > 0, NULL); g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); /* override when required */ if (fu_engine_config_get_show_device_private(fu_engine_get_config(self->engine))) flags |= FWUPD_DEVICE_FLAG_TRUSTED; for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); GVariant *tmp = fwupd_device_to_variant_full(FWUPD_DEVICE(device), flags); g_variant_builder_add_value(&builder, tmp); } return g_variant_new("(aa{sv})", &builder); } static GVariant * fu_daemon_plugin_array_to_variant(GPtrArray *plugins) { GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); for (guint i = 0; i < plugins->len; i++) { FuDevice *plugin = g_ptr_array_index(plugins, i); GVariant *tmp = fwupd_plugin_to_variant(FWUPD_PLUGIN(plugin)); g_variant_builder_add_value(&builder, tmp); } return g_variant_new("(aa{sv})", &builder); } static GVariant * fu_daemon_release_array_to_variant(GPtrArray *results) { GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); for (guint i = 0; i < results->len; i++) { FwupdRelease *rel = g_ptr_array_index(results, i); GVariant *tmp = fwupd_release_to_variant(rel); g_variant_builder_add_value(&builder, tmp); } return g_variant_new("(aa{sv})", &builder); } static GVariant * fu_daemon_remote_array_to_variant(GPtrArray *remotes) { GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index(remotes, i); GVariant *tmp = fwupd_remote_to_variant(remote); g_variant_builder_add_value(&builder, tmp); } return g_variant_new("(aa{sv})", &builder); } #ifdef HAVE_GIO_UNIX static GVariant * fu_daemon_result_array_to_variant(GPtrArray *results) { GVariantBuilder builder; g_return_val_if_fail(results->len > 0, NULL); g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); for (guint i = 0; i < results->len; i++) { FwupdDevice *result = g_ptr_array_index(results, i); GVariant *tmp = fwupd_device_to_variant(result); g_variant_builder_add_value(&builder, tmp); } return g_variant_new("(aa{sv})", &builder); } #endif /* HAVE_GIO_UNIX */ typedef struct { GDBusMethodInvocation *invocation; FuEngineRequest *request; FuProgress *progress; FuClient *client; glong client_sender_changed_id; GPtrArray *releases; GPtrArray *action_ids; GPtrArray *checksums; GPtrArray *errors; guint64 flags; GBytes *blob_cab; FuDaemon *self; gchar *device_id; gchar *remote_id; gchar *key; gchar *value; XbSilo *silo; GHashTable *bios_settings; /* str:str */ gboolean is_fix; } FuMainAuthHelper; static void fu_daemon_auth_helper_free(FuMainAuthHelper *helper) { /* always return to IDLE even in event of an auth error */ fu_daemon_set_status(helper->self, FWUPD_STATUS_IDLE); if (helper->blob_cab != NULL) g_bytes_unref(helper->blob_cab); if (helper->silo != NULL) g_object_unref(helper->silo); if (helper->request != NULL) g_object_unref(helper->request); if (helper->progress != NULL) g_object_unref(helper->progress); if (helper->releases != NULL) g_ptr_array_unref(helper->releases); if (helper->action_ids != NULL) g_ptr_array_unref(helper->action_ids); if (helper->checksums != NULL) g_ptr_array_unref(helper->checksums); if (helper->errors != NULL) g_ptr_array_unref(helper->errors); if (helper->client_sender_changed_id > 0) g_signal_handler_disconnect(helper->client, helper->client_sender_changed_id); if (helper->client != NULL) g_object_unref(helper->client); g_free(helper->device_id); g_free(helper->remote_id); g_free(helper->key); g_free(helper->value); g_object_unref(helper->invocation); if (helper->bios_settings != NULL) g_hash_table_unref(helper->bios_settings); g_free(helper); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuMainAuthHelper, fu_daemon_auth_helper_free) #pragma clang diagnostic pop static void fu_daemon_authorize_unlock_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; /* get result */ if (!fu_polkit_authority_check_finish(FU_POLKIT_AUTHORITY(source), res, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* authenticated */ if (!fu_engine_unlock(helper->self->engine, helper->device_id, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value(helper->invocation, NULL); } static void fu_daemon_authorize_get_bios_settings_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; g_autoptr(FuBiosSettings) attrs = NULL; FuContext *ctx; GVariant *val = NULL; /* get result */ if (!fu_polkit_authority_check_finish(FU_POLKIT_AUTHORITY(source), res, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* authenticated */ ctx = fu_engine_get_context(helper->self->engine); attrs = fu_context_get_bios_settings(ctx); val = fu_bios_settings_to_variant(attrs, TRUE); g_dbus_method_invocation_return_value(helper->invocation, val); } static void fu_daemon_authorize_set_bios_settings_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; /* get result */ if (!fu_polkit_authority_check_finish(FU_POLKIT_AUTHORITY(source), res, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* authenticated */ if (!fu_engine_modify_bios_settings(helper->self->engine, helper->bios_settings, FALSE, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value(helper->invocation, NULL); } static void fu_daemon_authorize_set_approved_firmware_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; /* get result */ if (!fu_polkit_authority_check_finish(FU_POLKIT_AUTHORITY(source), res, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* success */ for (guint i = 0; i < helper->checksums->len; i++) { const gchar *csum = g_ptr_array_index(helper->checksums, i); fu_engine_add_approved_firmware(helper->self->engine, csum); } g_dbus_method_invocation_return_value(helper->invocation, NULL); } static void fu_daemon_authorize_set_blocked_firmware_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; /* get result */ if (!fu_polkit_authority_check_finish(FU_POLKIT_AUTHORITY(source), res, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* success */ if (!fu_engine_set_blocked_firmware(helper->self->engine, helper->checksums, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } g_dbus_method_invocation_return_value(helper->invocation, NULL); } static void fu_daemon_authorize_fix_host_security_attr_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; /* get result */ if (!fu_polkit_authority_check_finish(FU_POLKIT_AUTHORITY(source), res, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* success */ if (!fu_engine_fix_host_security_attr(helper->self->engine, helper->key, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } g_dbus_method_invocation_return_value(helper->invocation, NULL); } static void fu_daemon_authorize_undo_host_security_attr_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; /* get result */ if (!fu_polkit_authority_check_finish(FU_POLKIT_AUTHORITY(source), res, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* success */ if (!fu_engine_undo_host_security_attr(helper->self->engine, helper->key, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } g_dbus_method_invocation_return_value(helper->invocation, NULL); } static void fu_daemon_authorize_self_sign_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autofree gchar *sig = NULL; g_autoptr(GError) error = NULL; /* get result */ if (!fu_polkit_authority_check_finish(FU_POLKIT_AUTHORITY(source), res, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* authenticated */ sig = fu_engine_self_sign(helper->self->engine, helper->value, helper->flags, &error); if (sig == NULL) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value(helper->invocation, g_variant_new("(s)", sig)); } static void fu_daemon_modify_config_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; /* get result */ if (!fu_polkit_authority_check_finish(FU_POLKIT_AUTHORITY(source), res, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } if (!fu_engine_modify_config(helper->self->engine, helper->key, helper->value, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value(helper->invocation, NULL); } static void fu_daemon_progress_percentage_changed_cb(FuProgress *progress, guint percentage, FuDaemon *self) { /* sanity check */ if (self->percentage == percentage) return; self->percentage = percentage; g_debug("Emitting PropertyChanged('Percentage'='%u%%')", percentage); fu_daemon_emit_property_changed(self, "Percentage", g_variant_new_uint32(percentage)); } static void fu_daemon_progress_status_changed_cb(FuProgress *progress, FwupdStatus status, FuDaemon *self) { fu_daemon_set_status(self, status); } static void fu_daemon_authorize_activate_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* get result */ if (!fu_polkit_authority_check_finish(FU_POLKIT_AUTHORITY(source), res, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* progress */ fu_progress_set_profile(progress, g_getenv("FWUPD_VERBOSE") != NULL); g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_daemon_progress_percentage_changed_cb), helper->self); g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(fu_daemon_progress_status_changed_cb), helper->self); /* authenticated */ if (!fu_engine_activate(helper->self->engine, helper->device_id, progress, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value(helper->invocation, NULL); } static void fu_daemon_authorize_verify_update_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); /* get result */ if (!fu_polkit_authority_check_finish(FU_POLKIT_AUTHORITY(source), res, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* progress */ fu_progress_set_profile(progress, g_getenv("FWUPD_VERBOSE") != NULL); g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_daemon_progress_percentage_changed_cb), helper->self); g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(fu_daemon_progress_status_changed_cb), helper->self); /* authenticated */ if (!fu_engine_verify_update(helper->self->engine, helper->device_id, progress, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value(helper->invocation, NULL); } static void fu_daemon_authorize_modify_remote_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; /* get result */ if (!fu_polkit_authority_check_finish(FU_POLKIT_AUTHORITY(source), res, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* authenticated */ if (!fu_engine_modify_remote(helper->self->engine, helper->remote_id, helper->key, helper->value, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value(helper->invocation, NULL); } #ifdef HAVE_GIO_UNIX static void fu_daemon_authorize_install_queue(FuMainAuthHelper *helper); static void fu_daemon_authorize_install_cb(GObject *source, GAsyncResult *res, gpointer user_data) { g_autoptr(FuMainAuthHelper) helper = (FuMainAuthHelper *)user_data; g_autoptr(GError) error = NULL; /* get result */ if (!fu_polkit_authority_check_finish(FU_POLKIT_AUTHORITY(source), res, &error)) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* do the next authentication action ID */ fu_daemon_authorize_install_queue(g_steal_pointer(&helper)); } static void fu_daemon_authorize_install_queue(FuMainAuthHelper *helper_ref) { FuDaemon *self = helper_ref->self; g_autoptr(FuMainAuthHelper) helper = helper_ref; g_autoptr(GError) error = NULL; gboolean ret; /* still more things to to authenticate */ if (helper->action_ids->len > 0) { FuPolkitAuthorityCheckFlags auth_flags = FU_POLKIT_AUTHORITY_CHECK_FLAG_ALLOW_USER_INTERACTION; g_autofree gchar *action_id = g_strdup(g_ptr_array_index(helper->action_ids, 0)); g_autofree gchar *sender = g_strdup(fu_client_get_sender(helper->client)); g_ptr_array_remove_index(helper->action_ids, 0); if (fu_engine_request_has_device_flag(helper->request, FWUPD_DEVICE_FLAG_TRUSTED)) auth_flags |= FU_POLKIT_AUTHORITY_CHECK_FLAG_USER_IS_TRUSTED; fu_polkit_authority_check(self->authority, sender, action_id, auth_flags, NULL, fu_daemon_authorize_install_cb, g_steal_pointer(&helper)); return; } /* all authenticated, so install all the things */ fu_progress_set_profile(helper->progress, g_getenv("FWUPD_VERBOSE") != NULL); g_signal_connect(FU_PROGRESS(helper->progress), "percentage-changed", G_CALLBACK(fu_daemon_progress_percentage_changed_cb), helper->self); g_signal_connect(FU_PROGRESS(helper->progress), "status-changed", G_CALLBACK(fu_daemon_progress_status_changed_cb), helper->self); /* all authenticated, so install all the things */ self->update_in_progress = TRUE; ret = fu_engine_install_releases(helper->self->engine, helper->request, helper->releases, helper->blob_cab, helper->progress, helper->flags, &error); self->update_in_progress = FALSE; if (self->pending_stop) g_main_loop_quit(self->loop); if (!ret) { g_dbus_method_invocation_return_gerror(helper->invocation, error); return; } /* success */ g_dbus_method_invocation_return_value(helper->invocation, NULL); } #endif /* HAVE_GIO_UNIX */ #ifdef HAVE_GIO_UNIX static gint fu_daemon_release_sort_cb(gconstpointer a, gconstpointer b) { FuRelease *release1 = *((FuRelease **)a); FuRelease *release2 = *((FuRelease **)b); return fu_release_compare(release1, release2); } static gboolean fu_daemon_install_with_helper_device(FuMainAuthHelper *helper, XbNode *component, FuDevice *device, GError **error) { FuDaemon *self = helper->self; g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) releases = NULL; /* is this component valid for the device */ fu_release_set_device(release, device); fu_release_set_request(release, helper->request); if (helper->remote_id != NULL) { fu_release_set_remote( release, fu_engine_get_remote_by_id(self->engine, helper->remote_id, NULL)); } if (!fu_release_load(release, component, NULL, helper->flags | FWUPD_INSTALL_FLAG_FORCE, &error_local)) { g_ptr_array_add(helper->errors, g_steal_pointer(&error_local)); return TRUE; } if (!fu_engine_requirements_check(self->engine, release, helper->flags | FWUPD_INSTALL_FLAG_FORCE, &error_local)) { if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_debug("first pass requirement on %s:%s failed: %s", fu_device_get_id(device), xb_node_query_text(component, "id", NULL), error_local->message); } g_ptr_array_add(helper->errors, g_steal_pointer(&error_local)); return TRUE; } /* sync update message from CAB */ fu_device_incorporate_from_component(device, component); /* install each intermediate release */ releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES)) { g_autoptr(GPtrArray) rels = NULL; g_autoptr(XbQuery) query = NULL; /* we get this one "for free" */ g_ptr_array_add(releases, g_object_ref(release)); query = xb_query_new_full(xb_node_get_silo(component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, error); if (query == NULL) return FALSE; rels = xb_node_query_full(component, query, NULL); /* add all but the first entry */ for (guint i = 1; i < rels->len; i++) { XbNode *rel = g_ptr_array_index(rels, i); g_autoptr(FuRelease) release2 = fu_release_new(); g_autoptr(GError) error_loop = NULL; fu_release_set_device(release2, device); fu_release_set_request(release2, helper->request); if (!fu_release_load(release2, component, rel, helper->flags, &error_loop)) { g_ptr_array_add(helper->errors, g_steal_pointer(&error_loop)); continue; } g_ptr_array_add(releases, g_object_ref(release2)); } } else { g_ptr_array_add(releases, g_object_ref(release)); } /* make a second pass */ for (guint i = 0; i < releases->len; i++) { FuRelease *release_tmp = g_ptr_array_index(releases, i); if (!fu_engine_requirements_check(self->engine, release_tmp, helper->flags, &error_local)) { g_debug("second pass requirement on %s:%s failed: %s", fu_device_get_id(device), xb_node_query_text(component, "id", NULL), error_local->message); g_ptr_array_add(helper->errors, g_steal_pointer(&error_local)); continue; } if (!fu_engine_check_trust(self->engine, release_tmp, &error_local)) { g_ptr_array_add(helper->errors, g_steal_pointer(&error_local)); continue; } /* get the action IDs for the valid device */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { const gchar *action_id = fu_release_get_action_id(release_tmp); if (!g_ptr_array_find(helper->action_ids, action_id, NULL)) g_ptr_array_add(helper->action_ids, g_strdup(action_id)); } g_ptr_array_add(helper->releases, g_object_ref(release_tmp)); } /* success */ return TRUE; } static gboolean fu_daemon_install_with_helper(FuMainAuthHelper *helper_ref, GError **error) { FuDaemon *self = helper_ref->self; g_autoptr(FuMainAuthHelper) helper = helper_ref; g_autoptr(GPtrArray) components = NULL; g_autoptr(GPtrArray) devices_possible = NULL; /* get a list of devices that in some way match the device_id */ if (g_strcmp0(helper->device_id, FWUPD_DEVICE_ID_ANY) == 0) { devices_possible = fu_engine_get_devices(self->engine, error); if (devices_possible == NULL) return FALSE; } else { g_autoptr(FuDevice) device = NULL; device = fu_engine_get_device(self->engine, helper->device_id, error); if (device == NULL) return FALSE; devices_possible = fu_engine_get_devices_by_composite_id(self->engine, fu_device_get_composite_id(device), error); if (devices_possible == NULL) return FALSE; } /* parse silo */ helper->silo = fu_engine_get_silo_from_blob(self->engine, helper->blob_cab, error); if (helper->silo == NULL) return FALSE; /* for each component in the silo */ components = xb_silo_query(helper->silo, "components/component[@type='firmware']", 0, error); if (components == NULL) return FALSE; helper->action_ids = g_ptr_array_new_with_free_func(g_free); helper->releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); helper->errors = g_ptr_array_new_with_free_func((GDestroyNotify)g_error_free); helper->remote_id = fu_engine_get_remote_id_for_blob(self->engine, helper->blob_cab); /* do any devices pass the requirements */ for (guint i = 0; i < components->len; i++) { XbNode *component = g_ptr_array_index(components, i); for (guint j = 0; j < devices_possible->len; j++) { FuDevice *device = g_ptr_array_index(devices_possible, j); g_debug("testing device %u [%s] with component %u", j, fu_device_get_id(device), i); if (!fu_daemon_install_with_helper_device(helper, component, device, error)) return FALSE; } } /* order the install tasks by the device priority */ g_ptr_array_sort(helper->releases, fu_daemon_release_sort_cb); /* nothing suitable */ if (helper->releases->len == 0) { GError *error_tmp = fu_engine_error_array_get_best(helper->errors); g_propagate_error(error, error_tmp); return FALSE; } /* authenticate all things in the action_ids */ fu_daemon_set_status(self, FWUPD_STATUS_WAITING_FOR_AUTH); fu_daemon_authorize_install_queue(g_steal_pointer(&helper)); return TRUE; } #endif /* HAVE_GIO_UNIX */ static gboolean fu_daemon_device_id_valid(const gchar *device_id, GError **error) { if (g_strcmp0(device_id, FWUPD_DEVICE_ID_ANY) == 0) return TRUE; if (device_id != NULL && strlen(device_id) >= 4) return TRUE; g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid device ID: %s", device_id); return FALSE; } static gboolean fu_daemon_schedule_process_quit_cb(gpointer user_data) { FuDaemon *self = FU_DAEMON(user_data); g_info("daemon asked to quit, shutting down"); self->process_quit_id = 0; g_main_loop_quit(self->loop); return G_SOURCE_REMOVE; } static void fu_daemon_schedule_process_quit(FuDaemon *self) { /* busy? */ if (self->update_in_progress) { g_warning("asked to quit during a firmware update, ignoring"); return; } /* allow the daemon to respond to the request, then quit */ if (self->process_quit_id != 0) g_source_remove(self->process_quit_id); self->process_quit_id = g_idle_add(fu_daemon_schedule_process_quit_cb, self); } typedef struct { gchar *id; gchar *sender; guint watcher_id; } FuDaemonSystemInhibit; static void fu_daemon_system_inhibit_free(FuDaemonSystemInhibit *inhibit) { g_bus_unwatch_name(inhibit->watcher_id); g_free(inhibit->id); g_free(inhibit->sender); g_free(inhibit); } static void fu_daemon_ensure_system_inhibit(FuDaemon *self) { FuContext *ctx = fu_engine_get_context(self->engine); if (self->system_inhibits->len > 0) { fu_context_add_flag(ctx, FU_CONTEXT_FLAG_SYSTEM_INHIBIT); return; } fu_context_remove_flag(ctx, FU_CONTEXT_FLAG_SYSTEM_INHIBIT); } static void fu_daemon_inhibit_name_vanished_cb(GDBusConnection *connection, const gchar *name, gpointer user_data) { FuDaemon *self = FU_DAEMON(user_data); for (guint i = 0; i < self->system_inhibits->len; i++) { FuDaemonSystemInhibit *inhibit = g_ptr_array_index(self->system_inhibits, i); if (g_strcmp0(inhibit->sender, name) == 0) { g_debug("removing %s as %s vanished without calling Uninhibit", inhibit->id, name); g_ptr_array_remove_index(self->system_inhibits, i); fu_daemon_ensure_system_inhibit(self); break; } } } #ifdef HAVE_GIO_UNIX static void fu_daemon_client_flags_notify_cb(FuClient *client, GParamSpec *pspec, FuMainAuthHelper *helper) { if (!fu_client_has_flag(client, FU_CLIENT_FLAG_ACTIVE)) { g_info("%s vanished before completion of install on %s", fu_client_get_sender(client), helper->device_id); fu_progress_add_flag(helper->progress, FU_PROGRESS_FLAG_NO_SENDER); } } #endif static void fu_daemon_daemon_method_call(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data) { FuPolkitAuthorityCheckFlags auth_flags = FU_POLKIT_AUTHORITY_CHECK_FLAG_ALLOW_USER_INTERACTION; FuDaemon *self = FU_DAEMON(user_data); GVariant *val = NULL; g_autoptr(FuEngineRequest) request = NULL; g_autoptr(GError) error = NULL; /* build request */ request = fu_daemon_create_request(self, sender, &error); if (request == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } if (fu_engine_request_has_device_flag(request, FWUPD_DEVICE_FLAG_TRUSTED)) auth_flags |= FU_POLKIT_AUTHORITY_CHECK_FLAG_USER_IS_TRUSTED; /* activity */ fu_engine_idle_reset(self->engine); if (g_strcmp0(method_name, "GetDevices") == 0) { g_autoptr(GPtrArray) devices = NULL; g_debug("Called %s()", method_name); devices = fu_engine_get_devices(self->engine, &error); if (devices == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_daemon_device_array_to_variant(self, request, devices, &error); if (val == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, val); return; } if (g_strcmp0(method_name, "GetPlugins") == 0) { g_debug("Called %s()", method_name); val = fu_daemon_plugin_array_to_variant(fu_engine_get_plugins(self->engine)); g_dbus_method_invocation_return_value(invocation, val); return; } if (g_strcmp0(method_name, "GetReleases") == 0) { const gchar *device_id; g_autoptr(GPtrArray) releases = NULL; g_variant_get(parameters, "(&s)", &device_id); g_debug("Called %s(%s)", method_name, device_id); if (!fu_daemon_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } releases = fu_engine_get_releases(self->engine, request, device_id, &error); if (releases == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_daemon_release_array_to_variant(releases); g_dbus_method_invocation_return_value(invocation, val); return; } if (g_strcmp0(method_name, "GetApprovedFirmware") == 0) { GVariantBuilder builder; GPtrArray *checksums = fu_engine_get_approved_firmware(self->engine); g_variant_builder_init(&builder, G_VARIANT_TYPE("as")); for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index(checksums, i); g_variant_builder_add_value(&builder, g_variant_new_string(checksum)); } val = g_variant_builder_end(&builder); g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&val, 1)); return; } if (g_strcmp0(method_name, "GetBlockedFirmware") == 0) { GVariantBuilder builder; GPtrArray *checksums = fu_engine_get_blocked_firmware(self->engine); g_variant_builder_init(&builder, G_VARIANT_TYPE("as")); for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index(checksums, i); g_variant_builder_add_value(&builder, g_variant_new_string(checksum)); } val = g_variant_builder_end(&builder); g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&val, 1)); return; } if (g_strcmp0(method_name, "GetReportMetadata") == 0) { GHashTableIter iter; GVariantBuilder builder; const gchar *key; const gchar *value; g_autoptr(GHashTable) metadata = NULL; metadata = fu_engine_get_report_metadata(self->engine, &error); if (metadata == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}")); g_hash_table_iter_init(&iter, metadata); while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) { g_variant_builder_add_value(&builder, g_variant_new("{ss}", key, value)); } val = g_variant_builder_end(&builder); g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&val, 1)); return; } if (g_strcmp0(method_name, "SetApprovedFirmware") == 0) { g_autofree gchar *checksums_str = NULL; g_auto(GStrv) checksums = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_variant_get(parameters, "(^as)", &checksums); checksums_str = g_strjoinv(",", checksums); g_debug("Called %s(%s)", method_name, checksums_str); /* authenticate */ fu_daemon_set_status(self, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0(FuMainAuthHelper, 1); helper->self = self; helper->flags = FWUPD_INSTALL_FLAG_NO_SEARCH; helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); helper->checksums = g_ptr_array_new_with_free_func(g_free); for (guint i = 0; checksums[i] != NULL; i++) g_ptr_array_add(helper->checksums, g_strdup(checksums[i])); fu_polkit_authority_check(self->authority, sender, "org.freedesktop.fwupd.set-approved-firmware", auth_flags, NULL, fu_daemon_authorize_set_approved_firmware_cb, g_steal_pointer(&helper)); return; } if (g_strcmp0(method_name, "SetBlockedFirmware") == 0) { g_autofree gchar *checksums_str = NULL; g_auto(GStrv) checksums = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_variant_get(parameters, "(^as)", &checksums); checksums_str = g_strjoinv(",", checksums); g_debug("Called %s(%s)", method_name, checksums_str); /* authenticate */ fu_daemon_set_status(self, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0(FuMainAuthHelper, 1); helper->self = self; helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); helper->checksums = g_ptr_array_new_with_free_func(g_free); for (guint i = 0; checksums[i] != NULL; i++) g_ptr_array_add(helper->checksums, g_strdup(checksums[i])); fu_polkit_authority_check(self->authority, sender, "org.freedesktop.fwupd.set-approved-firmware", auth_flags, NULL, fu_daemon_authorize_set_blocked_firmware_cb, g_steal_pointer(&helper)); return; } if (g_strcmp0(method_name, "Quit") == 0) { if (!fu_engine_request_has_device_flag(request, FWUPD_DEVICE_FLAG_TRUSTED)) { g_dbus_method_invocation_return_error_literal(invocation, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "Permission denied"); return; } fu_daemon_schedule_process_quit(self); g_dbus_method_invocation_return_value(invocation, NULL); return; } if (g_strcmp0(method_name, "SelfSign") == 0) { GVariant *prop_value; const gchar *prop_key; g_autofree gchar *value = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_autoptr(GVariantIter) iter = NULL; g_variant_get(parameters, "(sa{sv})", &value, &iter); g_debug("Called %s(%s)", method_name, value); /* get flags */ helper = g_new0(FuMainAuthHelper, 1); while (g_variant_iter_next(iter, "{&sv}", &prop_key, &prop_value)) { g_debug("got option %s", prop_key); if (g_strcmp0(prop_key, "add-timestamp") == 0 && g_variant_get_boolean(prop_value) == TRUE) helper->flags |= JCAT_SIGN_FLAG_ADD_TIMESTAMP; if (g_strcmp0(prop_key, "add-cert") == 0 && g_variant_get_boolean(prop_value) == TRUE) helper->flags |= JCAT_SIGN_FLAG_ADD_CERT; g_variant_unref(prop_value); } /* authenticate */ fu_daemon_set_status(self, FWUPD_STATUS_WAITING_FOR_AUTH); helper->self = self; helper->value = g_steal_pointer(&value); helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); fu_polkit_authority_check(self->authority, sender, "org.freedesktop.fwupd.self-sign", auth_flags, NULL, fu_daemon_authorize_self_sign_cb, g_steal_pointer(&helper)); return; } if (g_strcmp0(method_name, "GetDowngrades") == 0) { const gchar *device_id; g_autoptr(GPtrArray) releases = NULL; g_variant_get(parameters, "(&s)", &device_id); g_debug("Called %s(%s)", method_name, device_id); if (!fu_daemon_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } releases = fu_engine_get_downgrades(self->engine, request, device_id, &error); if (releases == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_daemon_release_array_to_variant(releases); g_dbus_method_invocation_return_value(invocation, val); return; } if (g_strcmp0(method_name, "GetUpgrades") == 0) { const gchar *device_id; g_autoptr(GPtrArray) releases = NULL; g_variant_get(parameters, "(&s)", &device_id); g_debug("Called %s(%s)", method_name, device_id); if (!fu_daemon_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } releases = fu_engine_get_upgrades(self->engine, request, device_id, &error); if (releases == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_daemon_release_array_to_variant(releases); g_dbus_method_invocation_return_value(invocation, val); return; } if (g_strcmp0(method_name, "GetRemotes") == 0) { g_autoptr(GPtrArray) remotes = NULL; g_debug("Called %s()", method_name); remotes = fu_engine_get_remotes(self->engine, &error); if (remotes == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_daemon_remote_array_to_variant(remotes); g_dbus_method_invocation_return_value(invocation, val); return; } if (g_strcmp0(method_name, "GetHistory") == 0) { g_autoptr(GPtrArray) devices = NULL; g_debug("Called %s()", method_name); devices = fu_engine_get_history(self->engine, &error); if (devices == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_daemon_device_array_to_variant(self, request, devices, &error); if (val == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, val); return; } if (g_strcmp0(method_name, "GetHostSecurityAttrs") == 0) { #ifdef HAVE_HSI g_autoptr(FuSecurityAttrs) attrs = NULL; #endif g_debug("Called %s()", method_name); #ifndef HAVE_HSI g_dbus_method_invocation_return_error_literal(invocation, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "HSI support not enabled"); #else if (self->machine_kind != FU_DAEMON_MACHINE_KIND_PHYSICAL) { g_dbus_method_invocation_return_error_literal( invocation, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "HSI unavailable for hypervisor"); return; } attrs = fu_engine_get_host_security_attrs(self->engine); val = fu_security_attrs_to_variant(attrs); g_dbus_method_invocation_return_value(invocation, val); #endif return; } if (g_strcmp0(method_name, "GetHostSecurityEvents") == 0) { guint limit = 0; #ifdef HAVE_HSI g_autoptr(FuSecurityAttrs) attrs = NULL; #endif g_variant_get(parameters, "(u)", &limit); g_debug("Called %s(%u)", method_name, limit); #ifndef HAVE_HSI g_dbus_method_invocation_return_error_literal(invocation, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "HSI support not enabled"); #else attrs = fu_engine_get_host_security_events(self->engine, limit, &error); if (attrs == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_security_attrs_to_variant(attrs); g_dbus_method_invocation_return_value(invocation, val); #endif return; } if (g_strcmp0(method_name, "ClearResults") == 0) { const gchar *device_id; g_variant_get(parameters, "(&s)", &device_id); g_debug("Called %s(%s)", method_name, device_id); if (!fu_engine_clear_results(self->engine, device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, NULL); return; } if (g_strcmp0(method_name, "EmulationLoad") == 0) { g_autoptr(GBytes) data = NULL; g_debug("Called %s()", method_name); /* load data into engine */ data = g_variant_get_data_as_bytes(g_variant_get_child_value(parameters, 0)); if (!fu_engine_emulation_load(self->engine, data, &error)) { g_dbus_method_invocation_return_error(invocation, error->domain, error->code, "failed to load emulation data: %s", error->message); return; } /* success */ g_dbus_method_invocation_return_value(invocation, NULL); return; } if (g_strcmp0(method_name, "EmulationSave") == 0) { g_autoptr(GBytes) data = NULL; g_debug("Called %s()", method_name); /* save data from engine */ data = fu_engine_emulation_save(self->engine, &error); if (data == NULL) { g_dbus_method_invocation_return_error(invocation, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to save emulation data: %s", error->message); return; } val = g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, data, FALSE); g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&val, 1)); return; } if (g_strcmp0(method_name, "ModifyDevice") == 0) { const gchar *device_id; const gchar *key = NULL; const gchar *value = NULL; /* check the id exists */ g_variant_get(parameters, "(&s&s&s)", &device_id, &key, &value); g_debug("Called %s(%s,%s=%s)", method_name, device_id, key, value); if (!fu_engine_modify_device(self->engine, device_id, key, value, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, NULL); return; } if (g_strcmp0(method_name, "GetResults") == 0) { const gchar *device_id = NULL; g_autoptr(FwupdDevice) result = NULL; g_variant_get(parameters, "(&s)", &device_id); g_debug("Called %s(%s)", method_name, device_id); if (!fu_daemon_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } result = fu_engine_get_results(self->engine, device_id, &error); if (result == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fwupd_device_to_variant(result); g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&val, 1)); return; } if (g_strcmp0(method_name, "UpdateMetadata") == 0) { #ifdef HAVE_GIO_UNIX GDBusMessage *message; GUnixFDList *fd_list; const gchar *remote_id = NULL; gint fd_data; gint fd_sig; g_variant_get(parameters, "(&shh)", &remote_id, &fd_data, &fd_sig); g_debug("Called %s(%s,%i,%i)", method_name, remote_id, fd_data, fd_sig); /* update the metadata store */ message = g_dbus_method_invocation_get_message(invocation); fd_list = g_dbus_message_get_unix_fd_list(message); if (fd_list == NULL || g_unix_fd_list_get_length(fd_list) != 2) { g_set_error(&error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid handle"); g_dbus_method_invocation_return_gerror(invocation, error); return; } fd_data = g_unix_fd_list_get(fd_list, 0, &error); if (fd_data < 0) { g_dbus_method_invocation_return_gerror(invocation, error); return; } fd_sig = g_unix_fd_list_get(fd_list, 1, &error); if (fd_sig < 0) { g_dbus_method_invocation_return_gerror(invocation, error); return; } /* store new metadata (will close the fds when done) */ if (!fu_engine_update_metadata(self->engine, remote_id, fd_data, fd_sig, &error)) { g_prefix_error(&error, "Failed to update metadata for %s: ", remote_id); g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, NULL); #else g_set_error(&error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unsupported feature"); g_dbus_method_invocation_return_gerror(invocation, error); #endif /* HAVE_GIO_UNIX */ return; } if (g_strcmp0(method_name, "Unlock") == 0) { const gchar *device_id = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_variant_get(parameters, "(&s)", &device_id); g_debug("Called %s(%s)", method_name, device_id); if (!fu_daemon_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } /* authenticate */ fu_daemon_set_status(self, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0(FuMainAuthHelper, 1); helper->self = self; helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); helper->device_id = g_strdup(device_id); fu_polkit_authority_check(self->authority, sender, "org.freedesktop.fwupd.device-unlock", auth_flags, NULL, fu_daemon_authorize_unlock_cb, g_steal_pointer(&helper)); return; } if (g_strcmp0(method_name, "Activate") == 0) { const gchar *device_id = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_variant_get(parameters, "(&s)", &device_id); g_debug("Called %s(%s)", method_name, device_id); if (!fu_daemon_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } /* authenticate */ fu_daemon_set_status(self, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0(FuMainAuthHelper, 1); helper->self = self; helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); helper->device_id = g_strdup(device_id); fu_polkit_authority_check(self->authority, sender, "org.freedesktop.fwupd.device-activate", auth_flags, NULL, fu_daemon_authorize_activate_cb, g_steal_pointer(&helper)); return; } if (g_strcmp0(method_name, "ModifyConfig") == 0) { g_autofree gchar *key = NULL; g_autofree gchar *value = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_variant_get(parameters, "(ss)", &key, &value); g_debug("Called %s(%s=%s)", method_name, key, value); /* authenticate */ helper = g_new0(FuMainAuthHelper, 1); helper->self = self; helper->key = g_steal_pointer(&key); helper->value = g_steal_pointer(&value); helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); fu_polkit_authority_check(self->authority, sender, "org.freedesktop.fwupd.modify-config", auth_flags, NULL, fu_daemon_modify_config_cb, g_steal_pointer(&helper)); return; } if (g_strcmp0(method_name, "ModifyRemote") == 0) { const gchar *remote_id = NULL; const gchar *key = NULL; const gchar *value = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; /* check the id exists */ g_variant_get(parameters, "(&s&s&s)", &remote_id, &key, &value); g_debug("Called %s(%s,%s=%s)", method_name, remote_id, key, value); /* create helper object */ helper = g_new0(FuMainAuthHelper, 1); helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); helper->remote_id = g_strdup(remote_id); helper->key = g_strdup(key); helper->value = g_strdup(value); helper->self = self; /* authenticate */ fu_daemon_set_status(self, FWUPD_STATUS_WAITING_FOR_AUTH); fu_polkit_authority_check(self->authority, sender, "org.freedesktop.fwupd.modify-remote", auth_flags, NULL, fu_daemon_authorize_modify_remote_cb, g_steal_pointer(&helper)); return; } if (g_strcmp0(method_name, "VerifyUpdate") == 0) { const gchar *device_id = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; /* check the id exists */ g_variant_get(parameters, "(&s)", &device_id); g_debug("Called %s(%s)", method_name, device_id); if (!fu_daemon_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } /* create helper object */ helper = g_new0(FuMainAuthHelper, 1); helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); helper->device_id = g_strdup(device_id); helper->self = self; /* authenticate */ fu_daemon_set_status(self, FWUPD_STATUS_WAITING_FOR_AUTH); fu_polkit_authority_check(self->authority, sender, "org.freedesktop.fwupd.verify-update", auth_flags, NULL, fu_daemon_authorize_verify_update_cb, g_steal_pointer(&helper)); return; } if (g_strcmp0(method_name, "Verify") == 0) { const gchar *device_id = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_variant_get(parameters, "(&s)", &device_id); g_debug("Called %s(%s)", method_name, device_id); if (!fu_daemon_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } /* progress */ fu_progress_set_profile(progress, g_getenv("FWUPD_VERBOSE") != NULL); g_signal_connect(FU_PROGRESS(progress), "percentage-changed", G_CALLBACK(fu_daemon_progress_percentage_changed_cb), self); g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(fu_daemon_progress_status_changed_cb), self); if (!fu_engine_verify(self->engine, device_id, progress, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } g_dbus_method_invocation_return_value(invocation, NULL); return; } if (g_strcmp0(method_name, "SetFeatureFlags") == 0) { guint64 feature_flags_u64 = 0; g_autoptr(FuClient) client = NULL; g_variant_get(parameters, "(t)", &feature_flags_u64); g_debug("Called %s(%" G_GUINT64_FORMAT ")", method_name, feature_flags_u64); /* old flags for the same sender will be automatically destroyed */ client = fu_client_list_register(self->client_list, sender); fu_client_set_feature_flags(client, feature_flags_u64); g_dbus_method_invocation_return_value(invocation, NULL); return; } if (g_strcmp0(method_name, "SetHints") == 0) { const gchar *prop_key; const gchar *prop_value; g_autoptr(FuClient) client = NULL; g_autoptr(GVariantIter) iter = NULL; g_variant_get(parameters, "(a{ss})", &iter); g_debug("Called %s()", method_name); client = fu_client_list_register(self->client_list, sender); while (g_variant_iter_next(iter, "{&s&s}", &prop_key, &prop_value)) { g_debug("got hint %s=%s", prop_key, prop_value); fu_client_insert_hint(client, prop_key, prop_value); } g_dbus_method_invocation_return_value(invocation, NULL); return; } if (g_strcmp0(method_name, "Inhibit") == 0) { FuDaemonSystemInhibit *inhibit; const gchar *reason = NULL; g_variant_get(parameters, "(&s)", &reason); g_debug("Called %s(%s)", method_name, reason); /* watch */ inhibit = g_new0(FuDaemonSystemInhibit, 1); inhibit->sender = g_strdup(sender); inhibit->id = g_strdup_printf("dbus-%i", g_random_int_range(1, G_MAXINT - 1)); inhibit->watcher_id = g_bus_watch_name_on_connection(self->connection, sender, G_BUS_NAME_WATCHER_FLAGS_NONE, NULL, fu_daemon_inhibit_name_vanished_cb, self, NULL); g_ptr_array_add(self->system_inhibits, inhibit); fu_daemon_ensure_system_inhibit(self); g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", inhibit->id)); return; } if (g_strcmp0(method_name, "Uninhibit") == 0) { const gchar *inhibit_id = NULL; gboolean found = FALSE; g_variant_get(parameters, "(&s)", &inhibit_id); g_debug("Called %s(%s)", method_name, inhibit_id); /* find by id, then uninhibit device */ for (guint i = 0; i < self->system_inhibits->len; i++) { FuDaemonSystemInhibit *inhibit = g_ptr_array_index(self->system_inhibits, i); if (g_strcmp0(inhibit->id, inhibit_id) == 0) { g_ptr_array_remove_index(self->system_inhibits, i); fu_daemon_ensure_system_inhibit(self); found = TRUE; break; } } if (!found) { g_dbus_method_invocation_return_error_literal(invocation, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "Cannot find inhibit ID"); return; } g_dbus_method_invocation_return_value(invocation, NULL); return; } if (g_strcmp0(method_name, "Install") == 0) { #ifdef HAVE_GIO_UNIX GVariant *prop_value; const gchar *device_id = NULL; const gchar *prop_key; gint32 fd_handle = 0; gint fd; guint64 archive_size_max; GDBusMessage *message; GUnixFDList *fd_list; g_autoptr(FuMainAuthHelper) helper = NULL; g_autoptr(GVariantIter) iter = NULL; /* check the id exists */ g_variant_get(parameters, "(&sha{sv})", &device_id, &fd_handle, &iter); g_debug("Called %s(%s,%i)", method_name, device_id, fd_handle); if (!fu_daemon_device_id_valid(device_id, &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } /* create helper object */ helper = g_new0(FuMainAuthHelper, 1); helper->request = g_steal_pointer(&request); helper->progress = fu_progress_new(G_STRLOC); helper->invocation = g_object_ref(invocation); helper->device_id = g_strdup(device_id); helper->self = self; /* get flags */ while (g_variant_iter_next(iter, "{&sv}", &prop_key, &prop_value)) { g_debug("got option %s", prop_key); if (g_strcmp0(prop_key, "offline") == 0 && g_variant_get_boolean(prop_value) == TRUE) helper->flags |= FWUPD_INSTALL_FLAG_OFFLINE; if (g_strcmp0(prop_key, "allow-older") == 0 && g_variant_get_boolean(prop_value) == TRUE) helper->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; if (g_strcmp0(prop_key, "allow-reinstall") == 0 && g_variant_get_boolean(prop_value) == TRUE) helper->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; if (g_strcmp0(prop_key, "allow-branch-switch") == 0 && g_variant_get_boolean(prop_value) == TRUE) helper->flags |= FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH; if (g_strcmp0(prop_key, "force") == 0 && g_variant_get_boolean(prop_value) == TRUE) helper->flags |= FWUPD_INSTALL_FLAG_FORCE; if (g_strcmp0(prop_key, "ignore-power") == 0 && g_variant_get_boolean(prop_value) == TRUE) helper->flags |= FWUPD_INSTALL_FLAG_IGNORE_POWER; if (g_strcmp0(prop_key, "no-history") == 0 && g_variant_get_boolean(prop_value) == TRUE) helper->flags |= FWUPD_INSTALL_FLAG_NO_HISTORY; g_variant_unref(prop_value); } /* get the fd */ message = g_dbus_method_invocation_get_message(invocation); fd_list = g_dbus_message_get_unix_fd_list(message); if (fd_list == NULL || g_unix_fd_list_get_length(fd_list) != 1) { g_set_error(&error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid handle"); g_dbus_method_invocation_return_gerror(invocation, error); return; } fd = g_unix_fd_list_get(fd_list, 0, &error); if (fd < 0) { g_dbus_method_invocation_return_gerror(invocation, error); return; } /* parse the cab file before authenticating so we can work out * what action ID to use, for instance, if this is trusted -- * this will also close the fd when done */ archive_size_max = fu_engine_config_get_archive_size_max(fu_engine_get_config(self->engine)); helper->blob_cab = fu_bytes_get_contents_fd(fd, archive_size_max, &error); if (helper->blob_cab == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } /* install all the things in the store */ helper->client = fu_client_list_register(self->client_list, sender); helper->client_sender_changed_id = g_signal_connect(FU_CLIENT(helper->client), "notify::flags", G_CALLBACK(fu_daemon_client_flags_notify_cb), helper); if (!fu_daemon_install_with_helper(g_steal_pointer(&helper), &error)) { g_dbus_method_invocation_return_gerror(invocation, error); return; } #else g_set_error(&error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unsupported feature"); g_dbus_method_invocation_return_gerror(invocation, error); #endif /* HAVE_GIO_UNIX */ /* async return */ return; } if (g_strcmp0(method_name, "GetDetails") == 0) { #ifdef HAVE_GIO_UNIX GDBusMessage *message; GUnixFDList *fd_list; gint32 fd_handle = 0; gint fd; g_autoptr(GPtrArray) results = NULL; /* get parameters */ g_variant_get(parameters, "(h)", &fd_handle); g_debug("Called %s(%i)", method_name, fd_handle); /* get the fd */ message = g_dbus_method_invocation_get_message(invocation); fd_list = g_dbus_message_get_unix_fd_list(message); if (fd_list == NULL || g_unix_fd_list_get_length(fd_list) != 1) { g_set_error(&error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid handle"); g_dbus_method_invocation_return_gerror(invocation, error); return; } fd = g_unix_fd_list_get(fd_list, 0, &error); if (fd < 0) { g_dbus_method_invocation_return_gerror(invocation, error); return; } /* get details about the file (will close the fd when done) */ results = fu_engine_get_details(self->engine, request, fd, &error); if (results == NULL) { g_dbus_method_invocation_return_gerror(invocation, error); return; } val = fu_daemon_result_array_to_variant(results); g_dbus_method_invocation_return_value(invocation, val); #else g_set_error(&error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "unsupported feature"); g_dbus_method_invocation_return_gerror(invocation, error); #endif /* HAVE_GIO_UNIX */ return; } if (g_strcmp0(method_name, "GetBiosSettings") == 0) { gboolean authenticate = fu_engine_request_get_feature_flags(request) & FWUPD_FEATURE_FLAG_ALLOW_AUTHENTICATION; g_debug("Called %s", method_name); if (!authenticate) { /* if we cannot authenticate and the peer is not * inherently trusted, only return a non-sensitive * subset of the settings */ g_autoptr(FuBiosSettings) attrs = fu_context_get_bios_settings(fu_engine_get_context(self->engine)); val = fu_bios_settings_to_variant( attrs, fu_engine_request_get_device_flags(request) & FWUPD_DEVICE_FLAG_TRUSTED); g_dbus_method_invocation_return_value(invocation, val); } else { g_autoptr(FuMainAuthHelper) helper = NULL; /* authenticate */ fu_daemon_set_status(self, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0(FuMainAuthHelper, 1); helper->self = self; helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); fu_polkit_authority_check(self->authority, sender, "org.freedesktop.fwupd.get-bios-settings", auth_flags, NULL, fu_daemon_authorize_get_bios_settings_cb, g_steal_pointer(&helper)); } return; } if (g_strcmp0(method_name, "SetBiosSettings") == 0) { g_autoptr(FuMainAuthHelper) helper = NULL; const gchar *key; const gchar *value; g_autoptr(GVariantIter) iter = NULL; g_variant_get(parameters, "(a{ss})", &iter); g_debug("Called %s()", method_name); /* authenticate */ fu_daemon_set_status(self, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0(FuMainAuthHelper, 1); helper->self = self; helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); helper->bios_settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); while (g_variant_iter_next(iter, "{&s&s}", &key, &value)) { g_debug("got setting %s=%s", key, value); g_hash_table_insert(helper->bios_settings, g_strdup(key), g_strdup(value)); } fu_polkit_authority_check(self->authority, sender, "org.freedesktop.fwupd.set-bios-settings", auth_flags, NULL, fu_daemon_authorize_set_bios_settings_cb, g_steal_pointer(&helper)); return; } if (g_strcmp0(method_name, "FixHostSecurityAttr") == 0) { const gchar *appstream_id = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_variant_get(parameters, "(&s)", &appstream_id); g_debug("Called %s(%s)", method_name, appstream_id); /* authenticate */ fu_daemon_set_status(self, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0(FuMainAuthHelper, 1); helper->self = self; helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); helper->key = g_strdup(appstream_id); helper->is_fix = TRUE; fu_polkit_authority_check(self->authority, sender, "org.freedesktop.fwupd.fix-host-security-attr", auth_flags, NULL, fu_daemon_authorize_fix_host_security_attr_cb, g_steal_pointer(&helper)); return; } if (g_strcmp0(method_name, "UndoHostSecurityAttr") == 0) { const gchar *appstream_id = NULL; g_autoptr(FuMainAuthHelper) helper = NULL; g_variant_get(parameters, "(&s)", &appstream_id); g_debug("Called %s(%s)", method_name, appstream_id); /* authenticate */ fu_daemon_set_status(self, FWUPD_STATUS_WAITING_FOR_AUTH); helper = g_new0(FuMainAuthHelper, 1); helper->self = self; helper->request = g_steal_pointer(&request); helper->invocation = g_object_ref(invocation); helper->key = g_strdup(appstream_id); helper->is_fix = FALSE; fu_polkit_authority_check(self->authority, sender, "org.freedesktop.fwupd.undo-host-security-attr", auth_flags, NULL, fu_daemon_authorize_undo_host_security_attr_cb, g_steal_pointer(&helper)); return; } g_set_error(&error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "no such method %s", method_name); g_dbus_method_invocation_return_gerror(invocation, error); } static GVariant * fu_daemon_daemon_get_property(GDBusConnection *connection_, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, gpointer user_data) { FuDaemon *self = FU_DAEMON(user_data); /* activity */ fu_engine_idle_reset(self->engine); if (g_strcmp0(property_name, "DaemonVersion") == 0) return g_variant_new_string(SOURCE_VERSION); if (g_strcmp0(property_name, "HostBkc") == 0) return g_variant_new_string(fu_engine_get_host_bkc(self->engine)); if (g_strcmp0(property_name, "Tainted") == 0) return g_variant_new_boolean(FALSE); if (g_strcmp0(property_name, "Status") == 0) return g_variant_new_uint32(self->status); if (g_strcmp0(property_name, "Percentage") == 0) return g_variant_new_uint32(self->percentage); if (g_strcmp0(property_name, FWUPD_RESULT_KEY_BATTERY_LEVEL) == 0) { FuContext *ctx = fu_engine_get_context(self->engine); return g_variant_new_uint32(fu_context_get_battery_level(ctx)); } if (g_strcmp0(property_name, FWUPD_RESULT_KEY_BATTERY_THRESHOLD) == 0) { FuContext *ctx = fu_engine_get_context(self->engine); return g_variant_new_uint32(fu_context_get_battery_threshold(ctx)); } if (g_strcmp0(property_name, "HostVendor") == 0) return g_variant_new_string(fu_engine_get_host_vendor(self->engine)); if (g_strcmp0(property_name, "HostProduct") == 0) return g_variant_new_string(fu_engine_get_host_product(self->engine)); if (g_strcmp0(property_name, "HostMachineId") == 0) { const gchar *tmp = fu_engine_get_host_machine_id(self->engine); if (tmp == NULL) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "failed to get daemon property %s", property_name); return NULL; } return g_variant_new_string(tmp); } if (g_strcmp0(property_name, "HostSecurityId") == 0) { const gchar *tmp = fu_engine_get_host_security_id(self->engine); if (tmp == NULL) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "failed to get daemon property %s", property_name); return NULL; } return g_variant_new_string(tmp); } if (g_strcmp0(property_name, "Interactive") == 0) return g_variant_new_boolean(isatty(fileno(stdout)) != 0); if (g_strcmp0(property_name, "OnlyTrusted") == 0) { return g_variant_new_boolean( fu_engine_config_get_only_trusted(fu_engine_get_config(self->engine))); } /* return an error */ g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY, "failed to get daemon property %s", property_name); return NULL; } static void fu_daemon_register_object(FuDaemon *self) { guint registration_id; static const GDBusInterfaceVTable interface_vtable = {fu_daemon_daemon_method_call, fu_daemon_daemon_get_property, NULL}; registration_id = g_dbus_connection_register_object(self->connection, FWUPD_DBUS_PATH, self->introspection_daemon->interfaces[0], &interface_vtable, self, /* user_data */ NULL, /* user_data_free_func */ NULL); /* GError** */ g_assert(registration_id > 0); } static void fu_daemon_client_list_ensure_inhibit(FuDaemon *self) { g_autoptr(GPtrArray) clients = fu_client_list_get_all(self->client_list); g_debug("connected clients: %u", clients->len); if (clients->len > 0 && self->clients_inhibit_id == 0) { self->clients_inhibit_id = fu_engine_idle_inhibit(self->engine, FU_IDLE_INHIBIT_TIMEOUT, "connected-clients"); } else if (clients->len == 0 && self->clients_inhibit_id != 0) { fu_engine_idle_uninhibit(self->engine, self->clients_inhibit_id); self->clients_inhibit_id = 0; } } static void fu_daemon_client_list_added_cb(FuClientList *client_list, FuClient *client, gpointer user_data) { FuDaemon *self = FU_DAEMON(user_data); fu_daemon_client_list_ensure_inhibit(self); } static void fu_daemon_client_list_removed_cb(FuClientList *client_list, FuClient *client, gpointer user_data) { FuDaemon *self = FU_DAEMON(user_data); fu_daemon_client_list_ensure_inhibit(self); } static void fu_daemon_set_connection(FuDaemon *self, GDBusConnection *connection) { g_set_object(&self->connection, connection); if (connection != NULL) { g_autoptr(FuClientList) client_list = fu_client_list_new(connection); g_signal_connect(client_list, "added", G_CALLBACK(fu_daemon_client_list_added_cb), self); g_signal_connect(client_list, "removed", G_CALLBACK(fu_daemon_client_list_removed_cb), self); g_set_object(&self->client_list, client_list); } } static void fu_daemon_dbus_bus_acquired_cb(GDBusConnection *connection, const gchar *name, gpointer user_data) { FuDaemon *self = FU_DAEMON(user_data); g_autoptr(GError) error = NULL; fu_daemon_set_connection(self, connection); fu_daemon_register_object(self); /* connect to D-Bus directly */ self->proxy_uid = g_dbus_proxy_new_sync(self->connection, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, NULL, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", NULL, &error); if (self->proxy_uid == NULL) { g_warning("cannot connect to DBus: %s", error->message); return; } } static void fu_daemon_dbus_name_acquired_cb(GDBusConnection *connection, const gchar *name, gpointer user_data) { g_debug("acquired name: %s", name); } static void fu_daemon_dbus_name_lost_cb(GDBusConnection *connection, const gchar *name, gpointer user_data) { FuDaemon *self = FU_DAEMON(user_data); if (self->update_in_progress) { g_warning("name lost during a firmware update, ignoring"); return; } g_warning("another service has claimed the dbus name %s", name); g_main_loop_quit(self->loop); } static void fu_daemon_dbus_connection_closed_cb(GDBusConnection *connection, gboolean remote_peer_vanished, GError *error, gpointer user_data) { if (remote_peer_vanished) g_info("client connection closed: %s", error != NULL ? error->message : "unknown"); } static gboolean fu_daemon_dbus_new_connection_cb(GDBusServer *server, GDBusConnection *connection, gpointer user_data) { FuDaemon *self = FU_DAEMON(user_data); fu_daemon_set_connection(self, connection); g_signal_connect(connection, "closed", G_CALLBACK(fu_daemon_dbus_connection_closed_cb), self); fu_daemon_register_object(self); return TRUE; } static GDBusNodeInfo * fu_daemon_load_introspection(const gchar *filename, GError **error) { g_autoptr(GBytes) data = NULL; g_autofree gchar *path = NULL; /* lookup data */ path = g_build_filename("/org/freedesktop/fwupd", filename, NULL); data = g_resources_lookup_data(path, G_RESOURCE_LOOKUP_FLAGS_NONE, error); if (data == NULL) return NULL; /* build introspection from XML */ return g_dbus_node_info_new_for_xml(g_bytes_get_data(data, NULL), error); } void fu_daemon_set_machine_kind(FuDaemon *self, FuDaemonMachineKind machine_kind) { g_return_if_fail(FU_IS_DAEMON(self)); self->machine_kind = machine_kind; } gboolean fu_daemon_setup(FuDaemon *self, const gchar *socket_address, GError **error) { const gchar *machine_kind = g_getenv("FWUPD_MACHINE_KIND"); guint timer_max_ms; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GTimer) timer = g_timer_new(); g_return_val_if_fail(FU_IS_DAEMON(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_profile(progress, g_getenv("FWUPD_VERBOSE") != NULL); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 99, "load-engine"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "load-introspection"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "load-authority"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "own-name"); /* allow overriding for development */ if (machine_kind != NULL) { self->machine_kind = fu_daemon_machine_kind_from_string(machine_kind); if (self->machine_kind == FU_DAEMON_MACHINE_KIND_UNKNOWN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid machine kind specified: %s", machine_kind); return FALSE; } } /* load engine */ self->engine = fu_engine_new(ctx); g_signal_connect(FU_ENGINE(self->engine), "changed", G_CALLBACK(fu_daemon_engine_changed_cb), self); g_signal_connect(FU_ENGINE(self->engine), "device-added", G_CALLBACK(fu_daemon_engine_device_added_cb), self); g_signal_connect(FU_ENGINE(self->engine), "device-removed", G_CALLBACK(fu_daemon_engine_device_removed_cb), self); g_signal_connect(FU_ENGINE(self->engine), "device-changed", G_CALLBACK(fu_daemon_engine_device_changed_cb), self); g_signal_connect(FU_ENGINE(self->engine), "device-request", G_CALLBACK(fu_daemon_engine_device_request_cb), self); g_signal_connect(FU_ENGINE(self->engine), "status-changed", G_CALLBACK(fu_daemon_engine_status_changed_cb), self); if (!fu_engine_load(self->engine, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_HWINFO | FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to load engine: "); return FALSE; } fu_progress_step_done(progress); /* load introspection from file */ self->introspection_daemon = fu_daemon_load_introspection(FWUPD_DBUS_INTERFACE ".xml", error); if (self->introspection_daemon == NULL) { g_prefix_error(error, "failed to load introspection: "); return FALSE; } fu_progress_step_done(progress); /* get authority */ self->authority = fu_polkit_authority_new(); if (!fu_polkit_authority_load(self->authority, error)) return FALSE; fu_progress_step_done(progress); /* own the object */ if (socket_address != NULL) { g_autofree gchar *guid = g_dbus_generate_guid(); g_autoptr(GDBusServer) server = NULL; server = g_dbus_server_new_sync(socket_address, G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS, guid, NULL, NULL, error); if (server == NULL) { g_prefix_error(error, "failed to create D-Bus server: "); return FALSE; } g_message("using socket address: %s", g_dbus_server_get_client_address(server)); g_dbus_server_start(server); g_signal_connect(server, "new-connection", G_CALLBACK(fu_daemon_dbus_new_connection_cb), self); } else { self->owner_id = g_bus_own_name(G_BUS_TYPE_SYSTEM, FWUPD_DBUS_SERVICE, G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | G_BUS_NAME_OWNER_FLAGS_REPLACE, fu_daemon_dbus_bus_acquired_cb, fu_daemon_dbus_name_acquired_cb, fu_daemon_dbus_name_lost_cb, self, NULL); } fu_progress_step_done(progress); /* how did we do */ timer_max_ms = fu_config_get_value_u64(FU_CONFIG(fu_engine_get_config(self->engine)), "fwupd", "IdleInhibitStartupThreshold"); if (timer_max_ms > 0) { guint timer_ms = g_timer_elapsed(timer, NULL) * 1000.f; if (timer_ms > timer_max_ms) { g_autofree gchar *reason = g_strdup_printf("daemon-startup-%ums-max-%ums", timer_ms, timer_max_ms); fu_engine_idle_inhibit(self->engine, FU_IDLE_INHIBIT_TIMEOUT, reason); } } /* a good place to do the traceback */ if (fu_progress_get_profile(progress)) { g_autofree gchar *str = fu_progress_traceback(progress); if (str != NULL) g_print("\n%s\n", str); } /* success */ return TRUE; } static void fu_daemon_init(FuDaemon *self) { self->status = FWUPD_STATUS_IDLE; self->loop = g_main_loop_new(NULL, FALSE); self->system_inhibits = g_ptr_array_new_with_free_func((GDestroyNotify)fu_daemon_system_inhibit_free); } static void fu_daemon_finalize(GObject *obj) { FuDaemon *self = FU_DAEMON(obj); g_ptr_array_unref(self->system_inhibits); if (self->client_list != NULL) g_object_unref(self->client_list); if (self->process_quit_id != 0) g_source_remove(self->process_quit_id); if (self->loop != NULL) g_main_loop_unref(self->loop); if (self->owner_id > 0) g_bus_unown_name(self->owner_id); if (self->proxy_uid != NULL) g_object_unref(self->proxy_uid); if (self->engine != NULL) g_object_unref(self->engine); if (self->connection != NULL) g_object_unref(self->connection); if (self->authority != NULL) g_object_unref(self->authority); if (self->introspection_daemon != NULL) g_dbus_node_info_unref(self->introspection_daemon); G_OBJECT_CLASS(fu_daemon_parent_class)->finalize(obj); } static void fu_daemon_class_init(FuDaemonClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_daemon_finalize; } FuDaemon * fu_daemon_new(void) { FuDaemon *self; self = g_object_new(FU_TYPE_DAEMON, NULL); return FU_DAEMON(self); } fwupd-1.9.16/src/fu-daemon.h000066400000000000000000000010341460375044200155530ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-daemon-struct.h" #define FU_TYPE_DAEMON (fu_daemon_get_type()) G_DECLARE_FINAL_TYPE(FuDaemon, fu_daemon, FU, DAEMON, GObject) FuDaemon * fu_daemon_new(void); gboolean fu_daemon_setup(FuDaemon *self, const gchar *socket_address, GError **error); void fu_daemon_start(FuDaemon *self); void fu_daemon_stop(FuDaemon *self); void fu_daemon_set_machine_kind(FuDaemon *self, FuDaemonMachineKind machine_kind); fwupd-1.9.16/src/fu-daemon.rs000066400000000000000000000003121460375044200157460ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(FromString)] enum DaemonMachineKind { Unknown, Physical, Virtual, Container, } fwupd-1.9.16/src/fu-debug.c000066400000000000000000000205031460375044200153730ustar00rootroot00000000000000/* * Copyright (C) 2010 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDebug" #include "config.h" #include #include #include #include #include #ifdef _WIN32 #include #include #endif typedef struct { GOptionGroup *group; GLogLevelFlags log_level; gboolean console; gboolean no_timestamp; gboolean no_domain; gchar **daemon_verbose; #ifdef _WIN32 HANDLE event_source; #endif } FuDebug; static const gchar * fu_debug_log_level_to_string(GLogLevelFlags log_level) { if (log_level == G_LOG_LEVEL_ERROR) return "error"; if (log_level == G_LOG_LEVEL_CRITICAL) return "critical"; if (log_level == G_LOG_LEVEL_WARNING) return "warning"; if (log_level == G_LOG_LEVEL_MESSAGE) return "message"; if (log_level == G_LOG_LEVEL_INFO) return "info"; if (log_level == G_LOG_LEVEL_DEBUG) return "debug"; return NULL; } static void fu_debug_free(FuDebug *self) { g_option_group_set_parse_hooks(self->group, NULL, NULL); g_option_group_unref(self->group); g_strfreev(self->daemon_verbose); #ifdef _WIN32 DeregisterEventSource(self->event_source); #endif g_free(self); } static gboolean fu_debug_filter_cb(FuDebug *self, const gchar *log_domain, GLogLevelFlags log_level) { /* trivial */ if (log_level <= self->log_level) return TRUE; /* filter on domain */ if (self->daemon_verbose != NULL && log_domain != NULL) return g_strv_contains((const char *const *)self->daemon_verbose, log_domain); /* nope */ return FALSE; } #ifdef _WIN32 static void fu_debug_handler_win32(FuDebug *self, GLogLevelFlags log_level, const gchar *msg) { WORD ev_type = 0x0; /* nothing to do */ if (self->event_source == NULL) return; /* map levels */ switch (log_level) { case G_LOG_LEVEL_INFO: case G_LOG_LEVEL_MESSAGE: ev_type = EVENTLOG_INFORMATION_TYPE; break; case G_LOG_LEVEL_WARNING: ev_type = EVENTLOG_WARNING_TYPE; break; case G_LOG_LEVEL_ERROR: case G_LOG_LEVEL_CRITICAL: ev_type = EVENTLOG_ERROR_TYPE; break; default: return; break; } /* add to log */ ReportEventA(self->event_source, ev_type, FWUPD_CATEGORY_GENERIC, FWUPD_MESSAGE_GENERIC, NULL, 1, 0, (const char **)&msg, NULL); } #endif static void fu_debug_handler_cb(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { FuDebug *self = (FuDebug *)user_data; g_autofree gchar *timestamp = NULL; g_autofree gchar *message_safe = NULL; g_autoptr(GString) domain = NULL; /* should ignore */ if (!fu_debug_filter_cb(self, log_domain, log_level)) return; /* make sure passwords never appear in logs */ message_safe = fu_strpassmask(message); #ifdef _WIN32 /* use Windows event log */ fu_debug_handler_win32(self, log_level, message_safe); #endif /* time header */ if (!self->no_timestamp) { g_autoptr(GDateTime) dt = g_date_time_new_now_utc(); timestamp = g_strdup_printf("%02i:%02i:%02i.%03i", g_date_time_get_hour(dt), g_date_time_get_minute(dt), g_date_time_get_second(dt), g_date_time_get_microsecond(dt) / 1000); } /* pad out domain */ if (!self->no_domain) { /* each file should have set this */ if (log_domain == NULL) log_domain = "FIXME"; domain = g_string_new(log_domain); for (gsize i = domain->len; i < 20; i++) g_string_append(domain, " "); } /* to file */ if (!self->console) { g_autofree gchar *ascii_message = g_str_to_ascii(message_safe, NULL); if (timestamp != NULL) g_printerr("%s ", timestamp); if (domain != NULL) g_printerr("%s ", domain->str); g_printerr("%s\n", ascii_message); return; } /* plain output */ if (g_getenv("NO_COLOR") != NULL) { if (timestamp != NULL) g_printerr("%s ", timestamp); if (domain != NULL) g_printerr("%s ", domain->str); g_printerr("%s\n", message_safe); return; } /* to screen */ switch (log_level) { case G_LOG_LEVEL_ERROR: case G_LOG_LEVEL_CRITICAL: case G_LOG_LEVEL_WARNING: /* critical in red */ if (timestamp != NULL) g_printerr("%c[%dm%s ", 0x1B, 32, timestamp); if (domain != NULL) g_printerr("%s ", domain->str); g_printerr("%c[%dm%s\n%c[%dm", 0x1B, 31, message_safe, 0x1B, 0); break; default: /* debug in blue */ if (timestamp != NULL) g_printerr("%c[%dm%s ", 0x1B, 32, timestamp); if (domain != NULL) g_printerr("%s ", domain->str); g_printerr("%c[%dm%s\n%c[%dm", 0x1B, 34, message_safe, 0x1B, 0); break; } } static gboolean fu_debug_verbose_arg_cb(const gchar *option_name, const gchar *value, gpointer user_data, GError **error) { FuDebug *self = (FuDebug *)user_data; if (self->log_level == G_LOG_LEVEL_MESSAGE) { self->log_level = G_LOG_LEVEL_INFO; return TRUE; } if (self->log_level == G_LOG_LEVEL_INFO) { self->log_level = G_LOG_LEVEL_DEBUG; return TRUE; } g_set_error_literal(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, "No further debug level supported"); return FALSE; } static gboolean fu_debug_pre_parse_hook(GOptionContext *context, GOptionGroup *group, gpointer data, GError **error) { FuDebug *self = (FuDebug *)data; const GOptionEntry entries[] = { {"verbose", 'v', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, (GOptionArgFunc)fu_debug_verbose_arg_cb, /* TRANSLATORS: turn on all debugging */ N_("Show debugging information for all domains"), NULL}, {"no-timestamp", '\0', 0, G_OPTION_ARG_NONE, &self->no_timestamp, /* TRANSLATORS: turn on all debugging */ N_("Do not include timestamp prefix"), NULL}, {"no-domain", '\0', 0, G_OPTION_ARG_NONE, &self->no_domain, /* TRANSLATORS: turn on all debugging */ N_("Do not include log domain prefix"), NULL}, {"daemon-verbose", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &self->daemon_verbose, /* TRANSLATORS: this is for daemon development */ N_("Show daemon verbose information for a particular domain"), "DOMAIN"}, {NULL}}; /* set from FuConfig */ if (g_strcmp0(g_getenv("FWUPD_VERBOSE"), "*") == 0) self->log_level = G_LOG_LEVEL_DEBUG; g_option_group_add_entries(group, entries); return TRUE; } static gboolean fu_debug_post_parse_hook(GOptionContext *context, GOptionGroup *group, gpointer data, GError **error) { FuDebug *self = (FuDebug *)data; /* for compat */ if (self->log_level == G_LOG_LEVEL_DEBUG) (void)g_setenv("FWUPD_VERBOSE", "1", TRUE); /* redirect all domains */ g_log_set_default_handler(fu_debug_handler_cb, self); /* are we on an actual TTY? */ self->console = (isatty(fileno(stderr)) == 1); g_info("verbose to %s (on console %i)", fu_debug_log_level_to_string(self->log_level), self->console); return TRUE; } #ifdef _WIN32 static void fu_debug_setup_event_source(FuDebug *self) { HKEY key; gchar msgfile[MAX_PATH]; DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE; if (RegCreateKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\" "EventLog\\Application\\fwupd", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &key, NULL) != ERROR_SUCCESS) { g_warning("RegCreateKeyExA failed [%u]", (guint)GetLastError()); return; } GetModuleFileNameA(NULL, msgfile, MAX_PATH); RegSetValueExA(key, "EventMessageFile", 0, REG_EXPAND_SZ, (BYTE *)msgfile, strlen(msgfile) + 1); RegSetValueExA(key, "TypesSupported", 0, REG_DWORD, (BYTE *)&dwData, sizeof(dwData)); RegCloseKey(key); /* good to go */ self->event_source = RegisterEventSourceA(NULL, "fwupd"); } #endif /*(transfer): full */ GOptionGroup * fu_debug_get_option_group(void) { FuDebug *self = g_new0(FuDebug, 1); self->log_level = G_LOG_LEVEL_MESSAGE; self->group = g_option_group_new("debug", /* TRANSLATORS: for the --verbose arg */ _("Debugging Options"), /* TRANSLATORS: for the --verbose arg */ _("Show debugging options"), self, (GDestroyNotify)fu_debug_free); g_option_group_set_parse_hooks(self->group, fu_debug_pre_parse_hook, fu_debug_post_parse_hook); #ifdef _WIN32 fu_debug_setup_event_source(self); #endif return g_option_group_ref(self->group); } fwupd-1.9.16/src/fu-debug.h000066400000000000000000000002751460375044200154040ustar00rootroot00000000000000/* * Copyright (C) 2010 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include GOptionGroup * fu_debug_get_option_group(void); fwupd-1.9.16/src/fu-device-list.c000066400000000000000000001051151460375044200165200ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuDeviceList" #include "config.h" #include #include "fu-device-list.h" #include "fu-device-private.h" #include "fu-engine.h" /** * FuDeviceList: * * This list of devices provides a way to find a device using either the * device-id or a GUID. * * The device list will emit ::added and ::removed signals when the device list * has been changed. If the #FuDevice has changed during a device replug then * the ::changed signal will be emitted instead of ::added and then ::removed. * * See also: [class@FuDevice] */ static void fu_device_list_finalize(GObject *obj); struct _FuDeviceList { GObject parent_instance; GPtrArray *devices; /* of FuDeviceItem */ GRWLock devices_mutex; }; enum { SIGNAL_ADDED, SIGNAL_REMOVED, SIGNAL_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; typedef struct { FuDevice *device; FuDevice *device_old; FuDeviceList *self; /* no ref */ guint remove_id; } FuDeviceItem; G_DEFINE_TYPE(FuDeviceList, fu_device_list, G_TYPE_OBJECT) static void fu_device_list_emit_device_added(FuDeviceList *self, FuDevice *device) { g_info("::added %s [%s]", fu_device_get_id(device), fu_device_get_name(device)); g_signal_emit(self, signals[SIGNAL_ADDED], 0, device); } static void fu_device_list_emit_device_removed(FuDeviceList *self, FuDevice *device) { g_info("::removed %s [%s]", fu_device_get_id(device), fu_device_get_name(device)); g_signal_emit(self, signals[SIGNAL_REMOVED], 0, device); } static void fu_device_list_emit_device_changed(FuDeviceList *self, FuDevice *device) { g_info("::changed %s [%s]", fu_device_get_id(device), fu_device_get_name(device)); g_signal_emit(self, signals[SIGNAL_CHANGED], 0, device); } static gchar * fu_device_list_to_string(FuDeviceList *self) { GString *str = g_string_new(NULL); g_rw_lock_reader_lock(&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); gboolean wfr; g_string_append_printf(str, "%u [%p] %s\n", i, item, item->remove_id != 0 ? "IN_TIMEOUT" : ""); wfr = fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); g_string_append_printf(str, "new: %s [%p] %s\n", fu_device_get_id(item->device), item->device, wfr ? "WAIT_FOR_REPLUG" : ""); if (item->device_old != NULL) { wfr = fu_device_has_flag(item->device_old, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); g_string_append_printf(str, "old: %s [%p] %s\n", fu_device_get_id(item->device_old), item->device_old, wfr ? "WAIT_FOR_REPLUG" : ""); } } g_rw_lock_reader_unlock(&self->devices_mutex); return g_string_free(str, FALSE); } /* we cannot use fu_device_get_children() as this will not find "parent-only" * logical relationships added using fu_device_add_parent_guid() */ static GPtrArray * fu_device_list_get_children(FuDeviceList *self, FuDevice *device) { GPtrArray *devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_rw_lock_reader_lock(&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (device == fu_device_get_parent(item->device)) g_ptr_array_add(devices, g_object_ref(item->device)); } g_rw_lock_reader_unlock(&self->devices_mutex); return devices; } static void fu_device_list_depsolve_order_full(FuDeviceList *self, FuDevice *device, guint depth) { g_autoptr(GPtrArray) children = NULL; /* ourself */ fu_device_set_order(device, depth); /* optional children */ children = fu_device_list_get_children(self, device); for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index(children, i); if (fu_device_has_flag(child, FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST)) { fu_device_list_depsolve_order_full(self, child, depth + 1); } else { fu_device_list_depsolve_order_full(self, child, depth - 1); } } } /** * fu_device_list_depsolve_order: * @self: a device list * @device: a device * * Sets the device order using the logical parent->child relationships -- by default * the child is updated first, unless the device has set flag * %FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST. * * Since: 1.5.0 **/ void fu_device_list_depsolve_order(FuDeviceList *self, FuDevice *device) { g_autoptr(FuDevice) root = fu_device_get_root(device); if (fu_device_has_internal_flag(root, FU_DEVICE_INTERNAL_FLAG_EXPLICIT_ORDER)) return; fu_device_list_depsolve_order_full(self, root, 0); } /** * fu_device_list_get_all: * @self: a device list * * Returns all the devices that have been added to the device list. * This includes devices that are no longer active, for instance where a * different plugin has taken over responsibility of the #FuDevice. * * Returns: (transfer container) (element-type FuDevice): the devices * * Since: 1.0.2 **/ GPtrArray * fu_device_list_get_all(FuDeviceList *self) { GPtrArray *devices; g_return_val_if_fail(FU_IS_DEVICE_LIST(self), NULL); devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_rw_lock_reader_lock(&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); g_ptr_array_add(devices, g_object_ref(item->device)); } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (item->device_old == NULL) continue; g_ptr_array_add(devices, g_object_ref(item->device_old)); } g_rw_lock_reader_unlock(&self->devices_mutex); return devices; } /** * fu_device_list_get_active: * @self: a device list * * Returns all the active devices that have been added to the device list. * An active device is defined as a device that is currently connected and has * is owned by a plugin. * * Returns: (transfer container) (element-type FuDevice): the devices * * Since: 1.0.2 **/ GPtrArray * fu_device_list_get_active(FuDeviceList *self) { GPtrArray *devices; g_return_val_if_fail(FU_IS_DEVICE_LIST(self), NULL); devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_rw_lock_reader_lock(&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (fu_device_has_internal_flag(item->device, FU_DEVICE_INTERNAL_FLAG_UNCONNECTED)) continue; if (fu_device_has_inhibit(item->device, "hidden")) continue; g_ptr_array_add(devices, g_object_ref(item->device)); } g_rw_lock_reader_unlock(&self->devices_mutex); return devices; } static FuDeviceItem * fu_device_list_find_by_device(FuDeviceList *self, FuDevice *device) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&self->devices_mutex); g_return_val_if_fail(locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (item->device == device) return item; } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (item->device_old == device) return item; } return NULL; } static FuDeviceItem * fu_device_list_find_by_guid(FuDeviceList *self, const gchar *guid) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&self->devices_mutex); g_return_val_if_fail(locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (fu_device_has_guid(item->device, guid)) return item; } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (item->device_old == NULL) continue; if (fu_device_has_guid(item->device_old, guid)) return item; } return NULL; } static FuDeviceItem * fu_device_list_find_by_connection(FuDeviceList *self, const gchar *physical_id, const gchar *logical_id) { g_autoptr(GRWLockReaderLocker) locker = NULL; if (physical_id == NULL) return NULL; locker = g_rw_lock_reader_locker_new(&self->devices_mutex); g_return_val_if_fail(locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index(self->devices, i); FuDevice *device = item_tmp->device; if (device != NULL && g_strcmp0(fu_device_get_physical_id(device), physical_id) == 0 && g_strcmp0(fu_device_get_logical_id(device), logical_id) == 0) return item_tmp; } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index(self->devices, i); FuDevice *device = item_tmp->device_old; if (device != NULL && g_strcmp0(fu_device_get_physical_id(device), physical_id) == 0 && g_strcmp0(fu_device_get_logical_id(device), logical_id) == 0) return item_tmp; } return NULL; } static FuDeviceItem * fu_device_list_find_by_id(FuDeviceList *self, const gchar *device_id, gboolean *multiple_matches) { FuDeviceItem *item = NULL; gsize device_id_len; /* sanity check */ if (device_id == NULL) { g_critical("device ID was NULL"); return NULL; } /* support abbreviated hashes */ device_id_len = strlen(device_id); g_rw_lock_reader_lock(&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index(self->devices, i); const gchar *ids[] = {fu_device_get_id(item_tmp->device), fu_device_get_equivalent_id(item_tmp->device), NULL}; for (guint j = 0; ids[j] != NULL; j++) { if (strncmp(ids[j], device_id, device_id_len) == 0) { if (item != NULL && multiple_matches != NULL) *multiple_matches = TRUE; item = item_tmp; } } } g_rw_lock_reader_unlock(&self->devices_mutex); if (item != NULL) return item; /* only search old devices if we didn't find the active device */ g_rw_lock_reader_lock(&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index(self->devices, i); const gchar *ids[3] = {NULL}; if (item_tmp->device_old == NULL) continue; ids[0] = fu_device_get_id(item_tmp->device_old); ids[1] = fu_device_get_equivalent_id(item_tmp->device_old); for (guint j = 0; ids[j] != NULL; j++) { if (strncmp(ids[j], device_id, device_id_len) == 0) { if (item != NULL && multiple_matches != NULL) *multiple_matches = TRUE; item = item_tmp; } } } g_rw_lock_reader_unlock(&self->devices_mutex); return item; } /** * fu_device_list_get_old: * @self: a device list * @device: a device * * Returns the old device associated with the currently active device. * * Returns: (transfer full): the device, or %NULL if not found * * Since: 1.0.3 **/ FuDevice * fu_device_list_get_old(FuDeviceList *self, FuDevice *device) { FuDeviceItem *item = fu_device_list_find_by_device(self, device); if (item == NULL) return NULL; if (item->device_old == NULL) return NULL; return g_object_ref(item->device_old); } static FuDeviceItem * fu_device_list_get_by_guids_removed(FuDeviceList *self, GPtrArray *guids) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&self->devices_mutex); g_return_val_if_fail(locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (item->remove_id == 0) continue; for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index(guids, j); if (fu_device_has_guid(item->device, guid)) return item; } } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (item->device_old == NULL) continue; if (item->remove_id == 0) continue; for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index(guids, j); if (fu_device_has_guid(item->device_old, guid)) return item; } } return NULL; } static gboolean fu_device_list_device_delayed_remove_cb(gpointer user_data) { FuDeviceItem *item = (FuDeviceItem *)user_data; FuDeviceList *self = FU_DEVICE_LIST(item->self); /* no longer valid */ item->remove_id = 0; /* remove any children associated with device */ if (!fu_device_has_internal_flag(item->device, FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE_CHILDREN)) { GPtrArray *children = fu_device_get_children(item->device); for (guint j = 0; j < children->len; j++) { FuDevice *child = g_ptr_array_index(children, j); FuDeviceItem *child_item; child_item = fu_device_list_find_by_id(self, fu_device_get_id(child), NULL); if (child_item == NULL) { g_info("device %s not found", fu_device_get_id(child)); continue; } fu_device_list_emit_device_removed(self, child); g_rw_lock_writer_lock(&self->devices_mutex); g_ptr_array_remove(self->devices, child_item); g_rw_lock_writer_unlock(&self->devices_mutex); } } /* just remove now */ g_info("doing delayed removal"); fu_device_list_emit_device_removed(self, item->device); g_rw_lock_writer_lock(&self->devices_mutex); g_ptr_array_remove(self->devices, item); g_rw_lock_writer_unlock(&self->devices_mutex); return G_SOURCE_REMOVE; } static void fu_device_list_remove_with_delay(FuDeviceItem *item) { /* give the hardware time to re-enumerate or the user time to * re-insert the device with a magic button pressed */ g_info("waiting %ums for %s device removal", fu_device_get_remove_delay(item->device), fu_device_get_name(item->device)); item->remove_id = g_timeout_add(fu_device_get_remove_delay(item->device), fu_device_list_device_delayed_remove_cb, item); } static gboolean fu_device_list_should_remove_with_delay(FuDevice *device) { if (fu_device_get_remove_delay(device) == 0) return FALSE; if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) return FALSE; return TRUE; } /** * fu_device_list_remove: * @self: a device list * @device: a device * * Removes a specific device from the list if it exists. * * If the @device has a remove-delay set then a timeout will be started. If * the exact same #FuDevice is added to the list with fu_device_list_add() * within the timeout then only a ::changed signal will be emitted. * * If there is no remove-delay set, the ::removed signal will be emitted * straight away. * * Since: 1.0.2 **/ void fu_device_list_remove(FuDeviceList *self, FuDevice *device) { FuDeviceItem *item; g_return_if_fail(FU_IS_DEVICE_LIST(self)); g_return_if_fail(FU_IS_DEVICE(device)); /* check the device already exists */ item = fu_device_list_find_by_id(self, fu_device_get_id(device), NULL); if (item == NULL) { g_info("device %s not found", fu_device_get_id(device)); return; } /* we can't do anything with an unconnected device */ fu_device_add_internal_flag(item->device, FU_DEVICE_INTERNAL_FLAG_UNCONNECTED); /* ensure never fired if the remove delay is changed */ if (item->remove_id > 0) { g_source_remove(item->remove_id); item->remove_id = 0; } /* delay the removal and check for replug */ if (fu_device_list_should_remove_with_delay(item->device)) { fu_device_list_remove_with_delay(item); return; } /* remove any children associated with device */ if (!fu_device_has_internal_flag(item->device, FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE_CHILDREN)) { GPtrArray *children = fu_device_get_children(device); for (guint j = 0; j < children->len; j++) { FuDevice *child = g_ptr_array_index(children, j); FuDeviceItem *child_item; child_item = fu_device_list_find_by_id(self, fu_device_get_id(child), NULL); if (child_item == NULL) { g_info("device %s not found", fu_device_get_id(child)); continue; } fu_device_list_emit_device_removed(self, child); g_rw_lock_writer_lock(&self->devices_mutex); g_ptr_array_remove(self->devices, child_item); g_rw_lock_writer_unlock(&self->devices_mutex); } } /* remove right now */ fu_device_list_emit_device_removed(self, item->device); g_rw_lock_writer_lock(&self->devices_mutex); g_ptr_array_remove(self->devices, item); g_rw_lock_writer_unlock(&self->devices_mutex); } static void fu_device_list_add_missing_guids(FuDevice *device_new, FuDevice *device_old) { GPtrArray *guids_old = fu_device_get_guids(device_old); for (guint i = 0; i < guids_old->len; i++) { const gchar *guid_tmp = g_ptr_array_index(guids_old, i); if (!fu_device_has_guid(device_new, guid_tmp)) { if (fu_device_has_flag(device_new, FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS)) { g_info("adding GUID %s to device", guid_tmp); fu_device_add_counterpart_guid(device_new, guid_tmp); } else { g_info("not adding GUID %s to device, use " "FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS if required", guid_tmp); } } } } static void fu_device_list_item_finalized_cb(gpointer data, GObject *where_the_object_was) { FuDeviceItem *item = (FuDeviceItem *)data; FuDeviceList *self = FU_DEVICE_LIST(item->self); g_critical("FuDevice %p was finalized without being removed from " "FuDeviceList, removing item!", where_the_object_was); g_rw_lock_writer_lock(&self->devices_mutex); g_ptr_array_remove(self->devices, item); g_rw_lock_writer_unlock(&self->devices_mutex); } /* this should never be required, and yet here we are */ static void fu_device_list_item_set_device(FuDeviceItem *item, FuDevice *device) { if (item->device != NULL) { g_object_weak_unref(G_OBJECT(item->device), fu_device_list_item_finalized_cb, item); } if (device != NULL) { g_object_weak_ref(G_OBJECT(device), fu_device_list_item_finalized_cb, item); } g_set_object(&item->device, device); } static void fu_device_list_clear_wait_for_replug(FuDeviceList *self, FuDeviceItem *item) { g_autofree gchar *str = NULL; /* clear timeout if scheduled */ if (item->remove_id != 0) { g_source_remove(item->remove_id); item->remove_id = 0; } /* remove flag on both old and new devices */ if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) { g_info("%s device came back, clearing flag", fu_device_get_id(item->device)); fu_device_remove_flag(item->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } if (item->device_old != NULL) { if (fu_device_has_flag(item->device_old, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) { g_info("%s old device came back, clearing flag", fu_device_get_id(item->device_old)); fu_device_remove_flag(item->device_old, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } } fu_device_remove_internal_flag(item->device, FU_DEVICE_INTERNAL_FLAG_UNCONNECTED); /* debug */ str = fu_device_list_to_string(self); g_debug("\n%s", str); } static void fu_device_incorporate_problem_update_in_progress(FuDevice *self, FuDevice *donor) { if (fu_device_has_problem(donor, FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS)) { g_info("moving inhibit update-in-progress to active device"); fu_device_add_problem(self, FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS); fu_device_remove_problem(donor, FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS); } } static void fu_device_incorporate_update_state(FuDevice *self, FuDevice *donor) { if (fu_device_get_update_error(donor) != NULL && fu_device_get_update_error(self) == NULL) { const gchar *update_error = fu_device_get_update_error(donor); g_info("copying update error %s to new device", update_error); fu_device_set_update_error(self, update_error); } if (fu_device_get_update_state(donor) != FWUPD_UPDATE_STATE_UNKNOWN && fu_device_get_update_state(self) == FWUPD_UPDATE_STATE_UNKNOWN) { FwupdUpdateState update_state = fu_device_get_update_state(donor); g_info("copying update state %s to new device", fwupd_update_state_to_string(update_state)); fu_device_set_update_state(self, update_state); } } static void fu_device_list_replace(FuDeviceList *self, FuDeviceItem *item, FuDevice *device) { GPtrArray *vendor_ids; g_autofree gchar *str = NULL; /* run the optional device-specific subclass */ fu_device_replace(device, item->device); /* copy over any GUIDs that used to exist */ fu_device_list_add_missing_guids(device, item->device); /* enforce the vendor ID if specified */ vendor_ids = fu_device_get_vendor_ids(item->device); for (guint i = 0; i < vendor_ids->len; i++) { const gchar *vendor_id = g_ptr_array_index(vendor_ids, i); g_info("copying old vendor ID %s to new device", vendor_id); fu_device_add_vendor_id(device, vendor_id); } /* copy inhibit */ fu_device_incorporate_problem_update_in_progress(item->device, device); /* copy over the version strings if not set */ if (fu_device_get_version(item->device) != NULL && fu_device_get_version(device) == NULL) { const gchar *version = fu_device_get_version(item->device); g_info("copying old version %s to new device", version); fu_device_set_version_format(device, fu_device_get_version_format(item->device)); fu_device_set_version(device, version); } /* always use the runtime version */ if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION) && fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER)) { const gchar *version = fu_device_get_version(item->device); g_info("forcing runtime version %s to new device", version); fu_device_set_version_format(device, fu_device_get_version_format(item->device)); fu_device_set_version(device, version); } /* allow another plugin to handle the write too */ if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED)) { g_debug("copying another-write-required to new device"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); } /* seems like a sane assumption if we've tagged the runtime mode as signed */ if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD)) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD)) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_EMULATION_TAG)) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG); /* device won't come back in right mode */ if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) { g_info("copying will-disappear to new device"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR); } /* copy the parent if not already set */ if (fu_device_get_parent(item->device) != NULL && fu_device_get_parent(item->device) != device && fu_device_get_parent(device) != item->device && fu_device_get_parent(device) == NULL) { FuDevice *parent = fu_device_get_parent(item->device); g_info("copying parent %s to new device", fu_device_get_id(parent)); fu_device_set_parent(device, parent); } /* copy the update state if known */ fu_device_incorporate_update_state(item->device, device); /* assign the new device */ g_set_object(&item->device_old, item->device); fu_device_list_item_set_device(item, device); fu_device_list_emit_device_changed(self, device); /* debug */ str = fu_device_list_to_string(self); g_debug("\n%s", str); /* we were waiting for this... */ fu_device_list_clear_wait_for_replug(self, item); } /** * fu_device_list_add: * @self: a device list * @device: a device * * Adds a specific device to the device list if not already present. * * If the @device (or a compatible @device) has been previously removed within * the remove-timeout then only the ::changed signal will be emitted on calling * this function. Otherwise the ::added signal will be emitted straight away. * * Compatible devices are defined as #FuDevice objects that share at least one * device GUID. If a compatible device is matched then the vendor ID and * version will be copied to the new object if they are not already set. * * Any GUIDs present on the old device and not on the new device will be * inherited and do not have to be copied over by plugins manually. * * Returns: (transfer none): a device, or %NULL if not found * * Since: 1.0.2 **/ void fu_device_list_add(FuDeviceList *self, FuDevice *device) { FuDeviceItem *item; g_return_if_fail(FU_IS_DEVICE_LIST(self)); g_return_if_fail(FU_IS_DEVICE(device)); /* is the device waiting to be replugged? */ item = fu_device_list_find_by_id(self, fu_device_get_id(device), NULL); if (item != NULL) { /* literally the same object */ if (device == item->device) { g_info("found existing device %s", fu_device_get_id(device)); fu_device_list_clear_wait_for_replug(self, item); fu_device_list_emit_device_changed(self, device); return; } /* the old device again */ if (item->device_old != NULL && device == item->device_old) { g_info("found old device %s, swapping", fu_device_get_id(device)); fu_device_remove_internal_flag(item->device, FU_DEVICE_INTERNAL_FLAG_UNCONNECTED); fu_device_incorporate_problem_update_in_progress(device, item->device); fu_device_incorporate_update_state(device, item->device); g_set_object(&item->device_old, item->device); fu_device_list_item_set_device(item, device); fu_device_list_clear_wait_for_replug(self, item); fu_device_list_emit_device_changed(self, device); return; } /* same ID, different object */ g_info("found existing device %s, reusing item", fu_device_get_id(item->device)); fu_device_list_replace(self, item, device); fu_device_remove_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_UNCONNECTED); return; } /* verify a device with same connection does not already exist */ item = fu_device_list_find_by_connection(self, fu_device_get_physical_id(device), fu_device_get_logical_id(device)); if (item != NULL && item->remove_id != 0) { g_info("found physical device %s recently removed, reusing " "item from plugin %s for plugin %s", fu_device_get_id(item->device), fu_device_get_plugin(item->device), fu_device_get_plugin(device)); fu_device_list_replace(self, item, device); fu_device_remove_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_UNCONNECTED); return; } /* verify a compatible device does not already exist */ item = fu_device_list_get_by_guids_removed(self, fu_device_get_guids(device)); if (item != NULL) { if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID)) { g_info("found compatible device %s recently removed, reusing " "item from plugin %s for plugin %s", fu_device_get_id(item->device), fu_device_get_plugin(item->device), fu_device_get_plugin(device)); fu_device_list_replace(self, item, device); fu_device_remove_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_UNCONNECTED); return; } g_info("not adding matching %s for device add, use " "FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID if required", fu_device_get_id(item->device)); } /* add helper */ item = g_new0(FuDeviceItem, 1); item->self = self; /* no ref */ fu_device_list_item_set_device(item, device); g_rw_lock_writer_lock(&self->devices_mutex); g_ptr_array_add(self->devices, item); g_rw_lock_writer_unlock(&self->devices_mutex); fu_device_list_emit_device_added(self, device); } /** * fu_device_list_get_by_guid: * @self: a device list * @guid: a device GUID * @error: (nullable): optional return location for an error * * Finds a specific device that has the matching GUID. * * Returns: (transfer full): a device, or %NULL if not found * * Since: 1.0.2 **/ FuDevice * fu_device_list_get_by_guid(FuDeviceList *self, const gchar *guid, GError **error) { FuDeviceItem *item; g_return_val_if_fail(FU_IS_DEVICE_LIST(self), NULL); g_return_val_if_fail(guid != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); item = fu_device_list_find_by_guid(self, guid); if (item != NULL) return g_object_ref(item->device); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "GUID %s was not found", guid); return NULL; } static GPtrArray * fu_device_list_get_wait_for_replug(FuDeviceList *self) { GPtrArray *devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index(self->devices, i); if (fu_device_has_flag(item_tmp->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG) && !fu_device_has_flag(item_tmp->device, FWUPD_DEVICE_FLAG_EMULATED)) g_ptr_array_add(devices, g_object_ref(item_tmp->device)); } return devices; } /** * fu_device_list_wait_for_replug: * @self: a device list * @error: (nullable): optional return location for an error * * Waits for all the devices with %FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG to replug. * * If the device does not exist this function returns without an error. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_list_wait_for_replug(FuDeviceList *self, GError **error) { guint remove_delay = 0; g_autoptr(GTimer) timer = g_timer_new(); g_autoptr(GPtrArray) devices_wfr1 = NULL; g_autoptr(GPtrArray) devices_wfr2 = NULL; g_return_val_if_fail(FU_IS_DEVICE_LIST(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not required, or possibly literally just happened */ devices_wfr1 = fu_device_list_get_wait_for_replug(self); if (devices_wfr1->len == 0) { g_info("no replug or re-enumerate required"); return TRUE; } /* use the maximum of all the devices */ for (guint i = 0; i < devices_wfr1->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices_wfr1, i); if (fu_device_get_remove_delay(device_tmp) > remove_delay) remove_delay = fu_device_get_remove_delay(device_tmp); } /* plugin did not specify */ if (remove_delay == 0) { remove_delay = FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE; g_warning("plugin did not specify a remove delay, " "so guessing we should wait %ums for replug", remove_delay); } else { g_info("waiting %ums for replug", remove_delay); } /* time to unplug and then re-plug */ do { g_autoptr(GPtrArray) devices_wfr_tmp = NULL; g_usleep(1000); g_main_context_iteration(NULL, FALSE); devices_wfr_tmp = fu_device_list_get_wait_for_replug(self); if (devices_wfr_tmp->len == 0) break; } while (g_timer_elapsed(timer, NULL) * 1000.f < remove_delay); /* check that no other devices are still waiting for replug */ devices_wfr2 = fu_device_list_get_wait_for_replug(self); if (devices_wfr2->len > 0) { g_autoptr(GPtrArray) device_ids = g_ptr_array_new_with_free_func(g_free); g_autofree gchar *device_ids_str = NULL; g_autofree gchar *str = NULL; /* dump to console */ str = fu_device_list_to_string(self); g_debug("\n%s", str); /* unset and build error string */ for (guint i = 0; i < devices_wfr2->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices_wfr2, i); fu_device_remove_flag(device_tmp, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); g_ptr_array_add(device_ids, g_strdup(fu_device_get_id(device_tmp))); } device_ids_str = fu_strjoin(",", device_ids); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device %s did not come back", device_ids_str); return FALSE; } /* the loop was quit without the timer */ g_info("waited for replug"); return TRUE; } /** * fu_device_list_get_by_id: * @self: a device list * @device_id: a device ID, typically a SHA1 hash * @error: (nullable): optional return location for an error * * Finds a specific device using the ID string. This function also supports * using abbreviated hashes. * * Returns: (transfer full): a device, or %NULL if not found * * Since: 1.0.2 **/ FuDevice * fu_device_list_get_by_id(FuDeviceList *self, const gchar *device_id, GError **error) { FuDeviceItem *item; gboolean multiple_matches = FALSE; g_return_val_if_fail(FU_IS_DEVICE_LIST(self), NULL); g_return_val_if_fail(device_id != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* multiple things matched */ item = fu_device_list_find_by_id(self, device_id, &multiple_matches); if (multiple_matches) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device ID %s was not unique", device_id); return NULL; } /* nothing at all matched */ if (item == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device ID %s was not found", device_id); return NULL; } /* something found */ return g_object_ref(item->device); } static void fu_device_list_item_free(FuDeviceItem *item) { if (item->remove_id != 0) g_source_remove(item->remove_id); if (item->device_old != NULL) g_object_unref(item->device_old); fu_device_list_item_set_device(item, NULL); g_free(item); } static void fu_device_list_class_init(FuDeviceListClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_device_list_finalize; /** * FuDeviceList::added: * @self: the #FuDeviceList instance that emitted the signal * @device: the #FuDevice * * The ::added signal is emitted when a device has been added to the list. **/ signals[SIGNAL_ADDED] = g_signal_new("added", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuDeviceList::removed: * @self: the #FuDeviceList instance that emitted the signal * @device: the #FuDevice * * The ::removed signal is emitted when a device has been removed from the list. **/ signals[SIGNAL_REMOVED] = g_signal_new("removed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuDeviceList::changed: * @self: the #FuDeviceList instance that emitted the signal * @device: the #FuDevice * * The ::changed signal is emitted when a device has changed. **/ signals[SIGNAL_CHANGED] = g_signal_new("changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); } static void fu_device_list_init(FuDeviceList *self) { self->devices = g_ptr_array_new_with_free_func((GDestroyNotify)fu_device_list_item_free); g_rw_lock_init(&self->devices_mutex); } static void fu_device_list_finalize(GObject *obj) { FuDeviceList *self = FU_DEVICE_LIST(obj); g_rw_lock_clear(&self->devices_mutex); g_ptr_array_unref(self->devices); G_OBJECT_CLASS(fu_device_list_parent_class)->finalize(obj); } /** * fu_device_list_new: * * Creates a new device list. * * Returns: (transfer full): a device list * * Since: 1.0.2 **/ FuDeviceList * fu_device_list_new(void) { FuDeviceList *self; self = g_object_new(FU_TYPE_DEVICE_LIST, NULL); return FU_DEVICE_LIST(self); } fwupd-1.9.16/src/fu-device-list.h000066400000000000000000000017261460375044200165300ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_DEVICE_LIST (fu_device_list_get_type()) G_DECLARE_FINAL_TYPE(FuDeviceList, fu_device_list, FU, DEVICE_LIST, GObject) FuDeviceList * fu_device_list_new(void); void fu_device_list_add(FuDeviceList *self, FuDevice *device); void fu_device_list_remove(FuDeviceList *self, FuDevice *device); GPtrArray * fu_device_list_get_all(FuDeviceList *self); GPtrArray * fu_device_list_get_active(FuDeviceList *self); FuDevice * fu_device_list_get_old(FuDeviceList *self, FuDevice *device); FuDevice * fu_device_list_get_by_id(FuDeviceList *self, const gchar *device_id, GError **error); FuDevice * fu_device_list_get_by_guid(FuDeviceList *self, const gchar *guid, GError **error); gboolean fu_device_list_wait_for_replug(FuDeviceList *self, GError **error); void fu_device_list_depsolve_order(FuDeviceList *self, FuDevice *device); fwupd-1.9.16/src/fu-engine-config.c000066400000000000000000000346721460375044200170310ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEngineConfig" #include "config.h" #include #include "fu-engine-config.h" struct _FuEngineConfig { FuConfig parent_instance; GHashTable *os_release; /* (element-type utf-8 utf-8) */ GPtrArray *disabled_devices; /* (element-type utf-8) */ GPtrArray *disabled_plugins; /* (element-type utf-8) */ GPtrArray *approved_firmware; /* (element-type utf-8) */ GPtrArray *blocked_firmware; /* (element-type utf-8) */ GPtrArray *uri_schemes; /* (element-type utf-8) */ GPtrArray *trusted_reports; /* (element-type FwupdReport) */ GArray *trusted_uids; /* (element-type guint64) */ gchar *host_bkc; gchar *esp_location; gboolean allow_emulation; }; G_DEFINE_TYPE(FuEngineConfig, fu_engine_config, FU_TYPE_CONFIG) static gboolean fu_engine_config_report_from_flags(FwupdReport *report, const gchar *flags_str, GError **error) { g_auto(GStrv) flags_strv = g_strsplit(flags_str, ",", -1); for (guint i = 0; flags_strv[i] != NULL; i++) { FwupdReportFlags flag = fwupd_report_flag_from_string(flags_strv[i]); if (flag == FWUPD_REPORT_FLAG_UNKNOWN) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "report flag '%s' unknown", flags_strv[i]); return FALSE; } fwupd_report_add_flag(report, flag); } return TRUE; } static FwupdReport * fu_engine_config_report_from_spec(FuEngineConfig *self, const gchar *report_spec, GError **error) { g_auto(GStrv) parts = g_strsplit(report_spec, "&", -1); g_autoptr(FwupdReport) report = fwupd_report_new(); for (guint i = 0; parts[i] != NULL; i++) { const gchar *value = NULL; g_auto(GStrv) kv = g_strsplit(parts[i], "=", 2); if (g_strv_length(kv) != 2) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to parse report specifier key=value %s", parts[i]); return NULL; } if (self->os_release != NULL && g_str_has_prefix(kv[1], "$")) value = g_hash_table_lookup(self->os_release, kv[1] + 1); if (value == NULL) value = kv[1]; if (g_strcmp0(kv[0], "VendorId") == 0) { guint64 tmp = 0; if (g_strcmp0(value, "$OEM") == 0) { fwupd_report_add_flag(report, FWUPD_REPORT_FLAG_FROM_OEM); } else { if (!fu_strtoull(value, &tmp, 0, G_MAXUINT32, error)) { g_prefix_error(error, "failed to parse '%s': ", value); return NULL; } fwupd_report_set_vendor_id(report, tmp); } } else if (g_strcmp0(kv[0], "DistroId") == 0) { fwupd_report_set_distro_id(report, value); } else if (g_strcmp0(kv[0], "DistroVariant") == 0) { fwupd_report_set_distro_variant(report, value); } else if (g_strcmp0(kv[0], "DistroVersion") == 0) { fwupd_report_set_distro_version(report, value); } else if (g_strcmp0(kv[0], "RemoteId") == 0) { fwupd_report_set_remote_id(report, value); } else if (g_strcmp0(kv[0], "Flags") == 0) { if (!fu_engine_config_report_from_flags(report, value, error)) return NULL; } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to parse report specifier key %s", kv[0]); return NULL; } } /* success */ return g_steal_pointer(&report); } static void fu_engine_config_reload(FuEngineConfig *self) { g_auto(GStrv) approved_firmware = NULL; g_auto(GStrv) blocked_firmware = NULL; g_auto(GStrv) devices = NULL; g_auto(GStrv) plugins = NULL; g_auto(GStrv) report_specs = NULL; g_auto(GStrv) uids = NULL; g_auto(GStrv) uri_schemes = NULL; g_autofree gchar *domains = NULL; g_autofree gchar *host_bkc = NULL; g_autofree gchar *esp_location = NULL; /* get disabled devices */ g_ptr_array_set_size(self->disabled_devices, 0); devices = fu_config_get_value_strv(FU_CONFIG(self), "fwupd", "DisabledDevices"); if (devices != NULL) { for (guint i = 0; devices[i] != NULL; i++) g_ptr_array_add(self->disabled_devices, g_strdup(devices[i])); } /* get disabled plugins */ g_ptr_array_set_size(self->disabled_plugins, 0); plugins = fu_config_get_value_strv(FU_CONFIG(self), "fwupd", "DisabledPlugins"); if (plugins != NULL) { for (guint i = 0; plugins[i] != NULL; i++) { g_autofree gchar *plugin_name = fu_strstrip(plugins[i]); if (plugin_name == NULL || plugin_name[0] == '\0') continue; g_strdelimit(plugin_name, "-", '_'); g_ptr_array_add(self->disabled_plugins, g_steal_pointer(&plugin_name)); } } /* get approved firmware */ g_ptr_array_set_size(self->approved_firmware, 0); approved_firmware = fu_config_get_value_strv(FU_CONFIG(self), "fwupd", "ApprovedFirmware"); if (approved_firmware != NULL) { for (guint i = 0; approved_firmware[i] != NULL; i++) g_ptr_array_add(self->approved_firmware, g_strdup(approved_firmware[i])); } /* get blocked firmware */ g_ptr_array_set_size(self->blocked_firmware, 0); blocked_firmware = fu_config_get_value_strv(FU_CONFIG(self), "fwupd", "BlockedFirmware"); if (blocked_firmware != NULL) { for (guint i = 0; blocked_firmware[i] != NULL; i++) g_ptr_array_add(self->blocked_firmware, g_strdup(blocked_firmware[i])); } /* get download schemes */ g_ptr_array_set_size(self->uri_schemes, 0); uri_schemes = fu_config_get_value_strv(FU_CONFIG(self), "fwupd", "UriSchemes"); if (uri_schemes != NULL) { for (guint i = 0; uri_schemes[i] != NULL; i++) g_ptr_array_add(self->uri_schemes, g_strdup(uri_schemes[i])); } /* get the domains to run in verbose */ domains = fu_config_get_value(FU_CONFIG(self), "fwupd", "VerboseDomains"); if (domains != NULL && domains[0] != '\0') (void)g_setenv("FWUPD_VERBOSE", domains, TRUE); /* fetch host best known configuration */ host_bkc = fu_config_get_value(FU_CONFIG(self), "fwupd", "HostBkc"); if (host_bkc != NULL && host_bkc[0] != '\0') self->host_bkc = g_steal_pointer(&host_bkc); /* fetch hardcoded ESP mountpoint */ esp_location = fu_config_get_value(FU_CONFIG(self), "fwupd", "EspLocation"); if (esp_location != NULL && esp_location[0] != '\0') self->esp_location = g_steal_pointer(&esp_location); /* get trusted uids */ g_array_set_size(self->trusted_uids, 0); uids = fu_config_get_value_strv(FU_CONFIG(self), "fwupd", "TrustedUids"); if (uids != NULL) { for (guint i = 0; uids[i] != NULL; i++) { guint64 val = 0; g_autoptr(GError) error_local = NULL; if (!fu_strtoull(uids[i], &val, 0, G_MAXUINT64, &error_local)) { g_warning("failed to parse UID '%s': %s", uids[i], error_local->message); continue; } g_array_append_val(self->trusted_uids, val); } } /* get trusted reports */ g_ptr_array_set_size(self->trusted_reports, 0); report_specs = fu_config_get_value_strv(FU_CONFIG(self), "fwupd", "TrustedReports"); if (report_specs != NULL) { for (guint i = 0; report_specs[i] != NULL; i++) { g_autoptr(GError) error_local = NULL; FwupdReport *report = fu_engine_config_report_from_spec(self, report_specs[i], &error_local); if (report == NULL) { g_warning("failed to parse %s: %s", report_specs[i], error_local->message); continue; } g_ptr_array_add(self->trusted_reports, report); } } } static void fu_engine_config_changed_cb(FuEngineConfig *config, gpointer user_data) { FuEngineConfig *self = FU_ENGINE_CONFIG(config); fu_engine_config_reload(self); } guint fu_engine_config_get_idle_timeout(FuEngineConfig *self) { return fu_config_get_value_u64(FU_CONFIG(self), "fwupd", "IdleTimeout"); } GPtrArray * fu_engine_config_get_disabled_devices(FuEngineConfig *self) { g_return_val_if_fail(FU_IS_ENGINE_CONFIG(self), NULL); return self->disabled_devices; } GArray * fu_engine_config_get_trusted_uids(FuEngineConfig *self) { g_return_val_if_fail(FU_IS_ENGINE_CONFIG(self), NULL); return self->trusted_uids; } GPtrArray * fu_engine_config_get_trusted_reports(FuEngineConfig *self) { g_return_val_if_fail(FU_IS_CONFIG(self), NULL); return self->trusted_reports; } GPtrArray * fu_engine_config_get_blocked_firmware(FuEngineConfig *self) { g_return_val_if_fail(FU_IS_ENGINE_CONFIG(self), NULL); return self->blocked_firmware; } guint fu_engine_config_get_uri_scheme_prio(FuEngineConfig *self, const gchar *scheme) { guint idx = 0; if (!g_ptr_array_find_with_equal_func(self->uri_schemes, scheme, g_str_equal, &idx)) return G_MAXUINT; return idx; } guint64 fu_engine_config_get_archive_size_max(FuEngineConfig *self) { return fu_config_get_value_u64(FU_CONFIG(self), "fwupd", "ArchiveSizeMax"); } GPtrArray * fu_engine_config_get_disabled_plugins(FuEngineConfig *self) { g_return_val_if_fail(FU_IS_ENGINE_CONFIG(self), NULL); return self->disabled_plugins; } GPtrArray * fu_engine_config_get_approved_firmware(FuEngineConfig *self) { g_return_val_if_fail(FU_IS_ENGINE_CONFIG(self), NULL); return self->approved_firmware; } gboolean fu_engine_config_get_update_motd(FuEngineConfig *self) { return fu_config_get_value_bool(FU_CONFIG(self), "fwupd", "UpdateMotd"); } gboolean fu_engine_config_get_ignore_power(FuEngineConfig *self) { return fu_config_get_value_bool(FU_CONFIG(self), "fwupd", "IgnorePower"); } gboolean fu_engine_config_get_only_trusted(FuEngineConfig *self) { return fu_config_get_value_bool(FU_CONFIG(self), "fwupd", "OnlyTrusted"); } gboolean fu_engine_config_get_show_device_private(FuEngineConfig *self) { return fu_config_get_value_bool(FU_CONFIG(self), "fwupd", "ShowDevicePrivate"); } gboolean fu_engine_config_get_test_devices(FuEngineConfig *self) { return fu_config_get_value_bool(FU_CONFIG(self), "fwupd", "TestDevices"); } gboolean fu_engine_config_get_allow_emulation(FuEngineConfig *self) { return fu_config_get_value_bool(FU_CONFIG(self), "fwupd", "AllowEmulation"); } gboolean fu_engine_config_get_release_dedupe(FuEngineConfig *self) { return fu_config_get_value_bool(FU_CONFIG(self), "fwupd", "ReleaseDedupe"); } FuReleasePriority fu_engine_config_get_release_priority(FuEngineConfig *self) { g_autofree gchar *tmp = fu_config_get_value(FU_CONFIG(self), "fwupd", "ReleasePriority"); return fu_release_priority_from_string(tmp); } FuP2pPolicy fu_engine_config_get_p2p_policy(FuEngineConfig *self) { FuP2pPolicy p2p_policy = FU_P2P_POLICY_NOTHING; g_autofree gchar *tmp = fu_config_get_value(FU_CONFIG(self), "fwupd", "P2pPolicy"); g_auto(GStrv) split = g_strsplit(tmp, ",", -1); for (guint i = 0; split[i] != NULL; i++) p2p_policy |= fu_p2p_policy_from_string(split[i]); return p2p_policy; } gboolean fu_engine_config_get_enumerate_all_devices(FuEngineConfig *self) { return fu_config_get_value_bool(FU_CONFIG(self), "fwupd", "EnumerateAllDevices"); } const gchar * fu_engine_config_get_host_bkc(FuEngineConfig *self) { g_return_val_if_fail(FU_IS_ENGINE_CONFIG(self), NULL); return self->host_bkc; } const gchar * fu_engine_config_get_esp_location(FuEngineConfig *self) { g_return_val_if_fail(FU_IS_ENGINE_CONFIG(self), NULL); return self->esp_location; } static gchar * fu_engine_config_archive_size_max_default(void) { guint64 memory_size = fu_common_get_memory_size(); guint64 archive_size_max = memory_size > 0 ? MIN(memory_size / 4, G_MAXUINT32) : 512 * 0x100000; return g_strdup_printf("%" G_GUINT64_FORMAT, archive_size_max); } static void fu_engine_set_config_default(FuEngineConfig *self, const gchar *key, const gchar *value) { fu_config_set_default(FU_CONFIG(self), "fwupd", key, value); } static void fu_engine_config_init(FuEngineConfig *self) { g_autofree gchar *archive_size_max_default = fu_engine_config_archive_size_max_default(); self->disabled_devices = g_ptr_array_new_with_free_func(g_free); self->disabled_plugins = g_ptr_array_new_with_free_func(g_free); self->approved_firmware = g_ptr_array_new_with_free_func(g_free); self->blocked_firmware = g_ptr_array_new_with_free_func(g_free); self->trusted_uids = g_array_new(FALSE, FALSE, sizeof(guint64)); self->trusted_reports = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); self->uri_schemes = g_ptr_array_new_with_free_func(g_free); g_signal_connect(self, "loaded", G_CALLBACK(fu_engine_config_changed_cb), NULL); g_signal_connect(self, "changed", G_CALLBACK(fu_engine_config_changed_cb), NULL); /* optionally used for substitutions */ self->os_release = fwupd_get_os_release(NULL); /* defaults changed here will also be reflected in the fwupd.conf man page */ fu_engine_set_config_default(self, "AllowEmulation", "false"); fu_engine_set_config_default(self, "ApprovedFirmware", NULL); fu_engine_set_config_default(self, "ArchiveSizeMax", archive_size_max_default); fu_engine_set_config_default(self, "BlockedFirmware", NULL); fu_engine_set_config_default(self, "DisabledDevices", NULL); fu_engine_set_config_default(self, "DisabledPlugins", ""); fu_engine_set_config_default(self, "EnumerateAllDevices", "false"); fu_engine_set_config_default(self, "EspLocation", NULL); fu_engine_set_config_default(self, "HostBkc", NULL); fu_engine_set_config_default(self, "IdleTimeout", "300"); /* s */ fu_engine_set_config_default(self, "IdleInhibitStartupThreshold", "500"); /* ms */ fu_engine_set_config_default(self, "IgnorePower", "false"); fu_engine_set_config_default(self, "OnlyTrusted", "true"); fu_engine_set_config_default(self, "P2pPolicy", FU_DEFAULT_P2P_POLICY); fu_engine_set_config_default(self, "ReleaseDedupe", "true"); fu_engine_set_config_default(self, "ReleasePriority", "local"); fu_engine_set_config_default(self, "ShowDevicePrivate", "true"); fu_engine_set_config_default(self, "TestDevices", "false"); fu_engine_set_config_default(self, "TrustedReports", "VendorId=$OEM"); fu_engine_set_config_default(self, "TrustedUids", NULL); fu_engine_set_config_default(self, "UpdateMotd", "true"); fu_engine_set_config_default(self, "UriSchemes", "file;https;http;ipfs"); fu_engine_set_config_default(self, "VerboseDomains", NULL); } static void fu_engine_config_finalize(GObject *obj) { FuEngineConfig *self = FU_ENGINE_CONFIG(obj); if (self->os_release != NULL) g_hash_table_unref(self->os_release); g_ptr_array_unref(self->disabled_devices); g_ptr_array_unref(self->disabled_plugins); g_ptr_array_unref(self->approved_firmware); g_ptr_array_unref(self->blocked_firmware); g_ptr_array_unref(self->uri_schemes); g_ptr_array_unref(self->trusted_reports); g_array_unref(self->trusted_uids); g_free(self->host_bkc); g_free(self->esp_location); G_OBJECT_CLASS(fu_engine_config_parent_class)->finalize(obj); } static void fu_engine_config_class_init(FuEngineConfigClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_engine_config_finalize; } FuEngineConfig * fu_engine_config_new(void) { return FU_ENGINE_CONFIG(g_object_new(FU_TYPE_ENGINE_CONFIG, NULL)); } fwupd-1.9.16/src/fu-engine-config.h000066400000000000000000000034731460375044200170310ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-engine-struct.h" #define FU_TYPE_ENGINE_CONFIG (fu_engine_config_get_type()) G_DECLARE_FINAL_TYPE(FuEngineConfig, fu_engine_config, FU, ENGINE_CONFIG, FuConfig) FuEngineConfig * fu_engine_config_new(void); guint64 fu_engine_config_get_archive_size_max(FuEngineConfig *self); guint fu_engine_config_get_idle_timeout(FuEngineConfig *self); GPtrArray * fu_engine_config_get_disabled_devices(FuEngineConfig *self); GPtrArray * fu_engine_config_get_disabled_plugins(FuEngineConfig *self); GArray * fu_engine_config_get_trusted_uids(FuEngineConfig *self); GPtrArray * fu_engine_config_get_trusted_reports(FuEngineConfig *self); GPtrArray * fu_engine_config_get_approved_firmware(FuEngineConfig *self); GPtrArray * fu_engine_config_get_blocked_firmware(FuEngineConfig *self); guint fu_engine_config_get_uri_scheme_prio(FuEngineConfig *self, const gchar *scheme); gboolean fu_engine_config_get_update_motd(FuEngineConfig *self); gboolean fu_engine_config_get_enumerate_all_devices(FuEngineConfig *self); gboolean fu_engine_config_get_ignore_power(FuEngineConfig *self); gboolean fu_engine_config_get_only_trusted(FuEngineConfig *self); gboolean fu_engine_config_get_show_device_private(FuEngineConfig *self); gboolean fu_engine_config_get_test_devices(FuEngineConfig *self); gboolean fu_engine_config_get_allow_emulation(FuEngineConfig *self); gboolean fu_engine_config_get_release_dedupe(FuEngineConfig *self); FuReleasePriority fu_engine_config_get_release_priority(FuEngineConfig *self); FuP2pPolicy fu_engine_config_get_p2p_policy(FuEngineConfig *self); const gchar * fu_engine_config_get_host_bkc(FuEngineConfig *self); const gchar * fu_engine_config_get_esp_location(FuEngineConfig *self); fwupd-1.9.16/src/fu-engine-helper.c000066400000000000000000000311641460375044200170340ustar00rootroot00000000000000/* * Copyright (C) 2020 Mario Limonciello * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEngine" #include "config.h" #include #include #include "fwupd-device-private.h" #include "fu-engine-helper.h" #include "fu-engine.h" static FwupdRelease * fu_engine_get_release_with_tag(FuEngine *self, FuEngineRequest *request, FwupdDevice *dev, const gchar *host_bkc, GError **error) { g_autoptr(GPtrArray) rels = NULL; g_auto(GStrv) host_bkcs = g_strsplit(host_bkc, ",", -1); /* find the newest release that matches */ rels = fu_engine_get_releases(self, request, fwupd_device_get_id(dev), error); if (rels == NULL) return NULL; for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel = g_ptr_array_index(rels, i); for (guint j = 0; host_bkcs[j] != NULL; j++) { if (fwupd_release_has_tag(rel, host_bkcs[j])) return g_object_ref(rel); } } /* no match */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no matching releases for device"); return NULL; } gboolean fu_engine_update_motd(FuEngine *self, GError **error) { const gchar *host_bkc = fu_engine_get_host_bkc(self); guint upgrade_count = 0; guint sync_count = 0; g_autoptr(FuEngineRequest) request = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GString) str = g_string_new(NULL); g_autofree gchar *target = NULL; /* a subset of what fwupdmgr can do */ request = fu_engine_request_new(); fu_engine_request_set_feature_flags(request, FWUPD_FEATURE_FLAG_DETACH_ACTION | FWUPD_FEATURE_FLAG_UPDATE_ACTION); /* get devices from daemon, we even want to know if it's nothing */ devices = fu_engine_get_devices(self, NULL); if (devices != NULL) { for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); g_autoptr(GPtrArray) rels = NULL; /* get the releases for this device */ if (!fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)) continue; rels = fu_engine_get_upgrades(self, request, fwupd_device_get_id(dev), NULL); if (rels == NULL) continue; upgrade_count++; } if (host_bkc != NULL) { for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); g_autoptr(FwupdRelease) rel = NULL; if (!fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)) continue; rel = fu_engine_get_release_with_tag(self, request, dev, host_bkc, NULL); if (rel == NULL) continue; if (g_strcmp0(fwupd_device_get_version(dev), fwupd_release_get_version(rel)) != 0) sync_count++; } } } /* If running under systemd unit, use the directory as a base */ if (g_getenv("RUNTIME_DIRECTORY") != NULL) { target = g_build_filename(g_getenv("RUNTIME_DIRECTORY"), MOTD_FILE, NULL); /* otherwise use the cache directory */ } else { g_autofree gchar *directory = fu_path_from_kind(FU_PATH_KIND_CACHEDIR_PKG); target = g_build_filename(directory, MOTD_DIR, MOTD_FILE, NULL); } /* create the directory and file, even if zero devices; we want an empty file then */ if (!fu_path_mkdir_parent(target, error)) return FALSE; /* nag about syncing or updating, but never both */ if (sync_count > 0) { g_string_append(str, "\n"); g_string_append_printf(str, /* TRANSLATORS: this is shown in the MOTD */ ngettext("%u device is not the best known configuration.", "%u devices are not the best known configuration.", sync_count), sync_count); g_string_append_printf(str, "\n%s\n\n", /* TRANSLATORS: this is shown in the MOTD */ _("Run `fwupdmgr sync` to complete this action.")); } else if (upgrade_count > 0) { g_string_append(str, "\n"); g_string_append_printf(str, /* TRANSLATORS: this is shown in the MOTD */ ngettext("%u device has a firmware upgrade available.", "%u devices have a firmware upgrade available.", upgrade_count), upgrade_count); g_string_append_printf(str, "\n%s\n\n", /* TRANSLATORS: this is shown in the MOTD */ _("Run `fwupdmgr get-upgrades` for more information.")); } /* success, with an empty file if nothing to say */ g_debug("writing motd target %s", target); return g_file_set_contents(target, str->str, str->len, error); } gboolean fu_engine_update_devices_file(FuEngine *self, GError **error) { FwupdDeviceFlags flags = FWUPD_DEVICE_FLAG_NONE; gsize len; g_autoptr(JsonBuilder) builder = NULL; g_autoptr(JsonGenerator) generator = NULL; g_autoptr(JsonNode) root = NULL; g_autoptr(GPtrArray) devices = NULL; g_autofree gchar *data = NULL; g_autofree gchar *directory = NULL; g_autofree gchar *target = NULL; if (fu_engine_config_get_show_device_private(fu_engine_get_config(self))) flags |= FWUPD_DEVICE_FLAG_TRUSTED; builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Devices"); json_builder_begin_array(builder); devices = fu_engine_get_devices(self, NULL); if (devices != NULL) { for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); json_builder_begin_object(builder); fwupd_device_to_json_full(dev, builder, flags); json_builder_end_object(builder); } } json_builder_end_array(builder); json_builder_end_object(builder); root = json_builder_get_root(builder); generator = json_generator_new(); json_generator_set_pretty(generator, TRUE); json_generator_set_root(generator, root); data = json_generator_to_data(generator, &len); if (data == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to convert to JSON string"); return FALSE; } directory = fu_path_from_kind(FU_PATH_KIND_CACHEDIR_PKG); target = g_build_filename(directory, "devices.json", NULL); return g_file_set_contents(target, data, (gssize)len, error); } static void fu_engine_integrity_add_measurement(GHashTable *self, const gchar *id, GBytes *blob) { g_autofree gchar *csum = g_compute_checksum_for_bytes(G_CHECKSUM_SHA256, blob); g_hash_table_insert(self, g_strdup(id), g_steal_pointer(&csum)); } static void fu_engine_integrity_measure_acpi(GHashTable *self) { g_autofree gchar *path = fu_path_from_kind(FU_PATH_KIND_ACPI_TABLES); const gchar *tables[] = {"SLIC", "MSDM", "TPM2", NULL}; for (guint i = 0; tables[i] != NULL; i++) { g_autofree gchar *fn = g_build_filename(path, tables[i], NULL); g_autoptr(GBytes) blob = NULL; blob = fu_bytes_get_contents(fn, NULL); if (blob != NULL && g_bytes_get_size(blob) > 0) { g_autofree gchar *id = g_strdup_printf("ACPI:%s", tables[i]); fu_engine_integrity_add_measurement(self, id, blob); } } } static void fu_engine_integrity_measure_uefi(GHashTable *self) { struct { const gchar *guid; const gchar *name; } keys[] = {{FU_EFIVAR_GUID_EFI_GLOBAL, "BootOrder"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "BootCurrent"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "KEK"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "KEKDefault"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "OsIndications"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "OsIndicationsSupported"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "PK"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "PKDefault"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "SecureBoot"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "SetupMode"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "SignatureSupport"}, {FU_EFIVAR_GUID_EFI_GLOBAL, "VendorKeys"}, {FU_EFIVAR_GUID_SECURITY_DATABASE, "db"}, {FU_EFIVAR_GUID_SECURITY_DATABASE, "dbDefault"}, {FU_EFIVAR_GUID_SECURITY_DATABASE, "dbx"}, {FU_EFIVAR_GUID_SECURITY_DATABASE, "dbxDefault"}, {NULL, NULL}}; /* important keys */ for (guint i = 0; keys[i].guid != NULL; i++) { g_autoptr(GBytes) blob = fu_efivar_get_data_bytes(keys[i].guid, keys[i].name, NULL, NULL); if (blob != NULL) { g_autofree gchar *id = g_strdup_printf("UEFI:%s", keys[i].name); fu_engine_integrity_add_measurement(self, id, blob); } } /* Boot#### */ for (guint i = 0; i < 0xFF; i++) { g_autofree gchar *name = g_strdup_printf("Boot%04X", i); g_autoptr(GBytes) blob = fu_efivar_get_data_bytes(FU_EFIVAR_GUID_EFI_GLOBAL, name, NULL, NULL); if (blob != NULL && g_bytes_get_size(blob) > 0) { const guint8 needle[] = "f\0w\0u\0p\0d"; g_autofree gchar *id = g_strdup_printf("UEFI:%s", name); if (fu_memmem_safe(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), needle, sizeof(needle), NULL, NULL)) { g_debug("skipping %s as fwupd found", id); continue; } fu_engine_integrity_add_measurement(self, id, blob); } } } GHashTable * fu_engine_integrity_new(GError **error) { g_autoptr(GHashTable) self = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_return_val_if_fail(error == NULL || *error == NULL, NULL); fu_engine_integrity_measure_uefi(self); fu_engine_integrity_measure_acpi(self); /* nothing of use */ if (g_hash_table_size(self) == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no measurements"); return NULL; } /* success */ return g_steal_pointer(&self); } gchar * fu_engine_integrity_to_string(GHashTable *self) { GHashTableIter iter; gpointer key, value; g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free); g_return_val_if_fail(self != NULL, NULL); /* sanity check */ if (g_hash_table_size(self) == 0) return NULL; /* build into KV array */ g_hash_table_iter_init(&iter, self); while (g_hash_table_iter_next(&iter, &key, &value)) { g_ptr_array_add(array, g_strdup_printf("%s=%s", (const gchar *)key, (const gchar *)value)); } return fu_strjoin("\n", array); } static const GError * fu_engine_error_array_find(GPtrArray *errors, FwupdError error_code) { for (guint j = 0; j < errors->len; j++) { const GError *error = g_ptr_array_index(errors, j); if (g_error_matches(error, FWUPD_ERROR, error_code)) return error; } return NULL; } static guint fu_engine_error_array_count(GPtrArray *errors, FwupdError error_code) { guint cnt = 0; for (guint j = 0; j < errors->len; j++) { const GError *error = g_ptr_array_index(errors, j); if (g_error_matches(error, FWUPD_ERROR, error_code)) cnt++; } return cnt; } static gboolean fu_engine_error_array_matches_any(GPtrArray *errors, FwupdError *error_codes) { for (guint j = 0; j < errors->len; j++) { const GError *error = g_ptr_array_index(errors, j); gboolean matches_any = FALSE; for (guint i = 0; error_codes[i] != FWUPD_ERROR_LAST; i++) { if (g_error_matches(error, FWUPD_ERROR, error_codes[i])) { matches_any = TRUE; break; } } if (!matches_any) return FALSE; } return TRUE; } /** * fu_engine_error_array_get_best: * @errors: (element-type GError): array of errors * * Finds the 'best' error to show the user from a array of errors, creating a * completely bespoke error where required. * * Returns: (transfer full): a #GError, never %NULL **/ GError * fu_engine_error_array_get_best(GPtrArray *errors) { FwupdError err_prio[] = {FWUPD_ERROR_INVALID_FILE, FWUPD_ERROR_VERSION_SAME, FWUPD_ERROR_VERSION_NEWER, FWUPD_ERROR_NOT_SUPPORTED, FWUPD_ERROR_INTERNAL, FWUPD_ERROR_NOT_FOUND, FWUPD_ERROR_LAST}; FwupdError err_all_uptodate[] = {FWUPD_ERROR_VERSION_SAME, FWUPD_ERROR_NOT_FOUND, FWUPD_ERROR_NOT_SUPPORTED, FWUPD_ERROR_LAST}; FwupdError err_all_newer[] = {FWUPD_ERROR_VERSION_NEWER, FWUPD_ERROR_VERSION_SAME, FWUPD_ERROR_NOT_FOUND, FWUPD_ERROR_NOT_SUPPORTED, FWUPD_ERROR_LAST}; /* are all the errors either GUID-not-matched or version-same? */ if (fu_engine_error_array_count(errors, FWUPD_ERROR_VERSION_SAME) > 1 && fu_engine_error_array_matches_any(errors, err_all_uptodate)) { return g_error_new(FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "All updatable firmware is already installed"); } /* are all the errors either GUID-not-matched or version same or newer? */ if (fu_engine_error_array_count(errors, FWUPD_ERROR_VERSION_NEWER) > 1 && fu_engine_error_array_matches_any(errors, err_all_newer)) { return g_error_new(FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "All updatable devices already have newer versions"); } /* get the most important single error */ for (guint i = 0; err_prio[i] != FWUPD_ERROR_LAST; i++) { const GError *error_tmp = fu_engine_error_array_find(errors, err_prio[i]); if (error_tmp != NULL) return g_error_copy(error_tmp); } /* fall back to something */ return g_error_new(FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No supported devices found"); } fwupd-1.9.16/src/fu-engine-helper.h000066400000000000000000000010131460375044200170270ustar00rootroot00000000000000/* * Copyright (C) 2020 Mario Limonciello * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-engine.h" gboolean fu_engine_update_motd(FuEngine *self, GError **error); gboolean fu_engine_update_devices_file(FuEngine *self, GError **error); GHashTable * fu_engine_integrity_new(GError **error); gchar * fu_engine_integrity_to_string(GHashTable *self); GError * fu_engine_error_array_get_best(GPtrArray *errors); fwupd-1.9.16/src/fu-engine-request.c000066400000000000000000000072001460375044200172370ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEngine" #include "config.h" #include "fu-engine-request.h" struct _FuEngineRequest { GObject parent_instance; FuEngineRequestFlag flags; FwupdFeatureFlags feature_flags; FwupdDeviceFlags device_flags; gchar *locale; }; G_DEFINE_TYPE(FuEngineRequest, fu_engine_request, G_TYPE_OBJECT) void fu_engine_request_add_string(FuEngineRequest *self, guint idt, GString *str) { g_return_if_fail(FU_IS_ENGINE_REQUEST(self)); if (self->flags != FU_ENGINE_REQUEST_FLAG_NONE) { g_autofree gchar *flags = fu_engine_request_flag_to_string(self->flags); fu_string_append(str, idt, "Flags", flags); } fu_string_append_kx(str, idt, "FeatureFlags", self->feature_flags); fu_string_append_kx(str, idt, "DeviceFlags", self->device_flags); if (self->locale != NULL) fu_string_append(str, idt, "Locale", self->locale); } FwupdFeatureFlags fu_engine_request_get_feature_flags(FuEngineRequest *self) { g_return_val_if_fail(FU_IS_ENGINE_REQUEST(self), FALSE); return self->feature_flags; } const gchar * fu_engine_request_get_locale(FuEngineRequest *self) { g_return_val_if_fail(FU_IS_ENGINE_REQUEST(self), NULL); return self->locale; } void fu_engine_request_add_flag(FuEngineRequest *self, FuEngineRequestFlag flag) { g_return_if_fail(FU_IS_ENGINE_REQUEST(self)); self->flags |= flag; } gboolean fu_engine_request_has_flag(FuEngineRequest *self, FuEngineRequestFlag flag) { g_return_val_if_fail(FU_IS_ENGINE_REQUEST(self), FU_ENGINE_REQUEST_FLAG_NONE); return (self->flags & flag) > 0; } void fu_engine_request_set_feature_flags(FuEngineRequest *self, FwupdFeatureFlags feature_flags) { g_return_if_fail(FU_IS_ENGINE_REQUEST(self)); self->feature_flags = feature_flags; } void fu_engine_request_set_locale(FuEngineRequest *self, const gchar *locale) { g_return_if_fail(FU_IS_ENGINE_REQUEST(self)); /* not changed */ if (g_strcmp0(self->locale, locale) == 0) return; g_free(self->locale); self->locale = g_strdup(locale); /* remove the UTF8 suffix as it is not present in the XML */ if (self->locale != NULL) g_strdelimit(self->locale, ".", '\0'); } gboolean fu_engine_request_has_feature_flag(FuEngineRequest *self, FwupdFeatureFlags feature_flag) { g_return_val_if_fail(FU_IS_ENGINE_REQUEST(self), FALSE); return (self->feature_flags & feature_flag) > 0; } FwupdDeviceFlags fu_engine_request_get_device_flags(FuEngineRequest *self) { g_return_val_if_fail(FU_IS_ENGINE_REQUEST(self), FALSE); return self->device_flags; } void fu_engine_request_set_device_flags(FuEngineRequest *self, FwupdDeviceFlags device_flags) { g_return_if_fail(FU_IS_ENGINE_REQUEST(self)); self->device_flags = device_flags; } gboolean fu_engine_request_has_device_flag(FuEngineRequest *self, FwupdDeviceFlags device_flag) { g_return_val_if_fail(FU_IS_ENGINE_REQUEST(self), FALSE); return (self->device_flags & device_flag) > 0; } static void fu_engine_request_init(FuEngineRequest *self) { self->flags = FU_ENGINE_REQUEST_FLAG_NONE; self->device_flags = FWUPD_DEVICE_FLAG_NONE; self->feature_flags = FWUPD_FEATURE_FLAG_NONE; } static void fu_engine_request_finalize(GObject *obj) { FuEngineRequest *self = FU_ENGINE_REQUEST(obj); g_free(self->locale); G_OBJECT_CLASS(fu_engine_request_parent_class)->finalize(obj); } static void fu_engine_request_class_init(FuEngineRequestClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_engine_request_finalize; } FuEngineRequest * fu_engine_request_new(void) { FuEngineRequest *self; self = g_object_new(FU_TYPE_ENGINE_REQUEST, NULL); return FU_ENGINE_REQUEST(self); } fwupd-1.9.16/src/fu-engine-request.h000066400000000000000000000024441460375044200172510ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-engine-struct.h" #define FU_TYPE_ENGINE_REQUEST (fu_engine_request_get_type()) G_DECLARE_FINAL_TYPE(FuEngineRequest, fu_engine_request, FU, ENGINE_REQUEST, GObject) FuEngineRequest * fu_engine_request_new(void); void fu_engine_request_add_string(FuEngineRequest *self, guint idt, GString *str); void fu_engine_request_add_flag(FuEngineRequest *self, FuEngineRequestFlag flag); gboolean fu_engine_request_has_flag(FuEngineRequest *self, FuEngineRequestFlag flag); FwupdFeatureFlags fu_engine_request_get_feature_flags(FuEngineRequest *self); void fu_engine_request_set_feature_flags(FuEngineRequest *self, FwupdFeatureFlags feature_flags); const gchar * fu_engine_request_get_locale(FuEngineRequest *self); void fu_engine_request_set_locale(FuEngineRequest *self, const gchar *locale); gboolean fu_engine_request_has_feature_flag(FuEngineRequest *self, FwupdFeatureFlags feature_flag); gboolean fu_engine_request_has_device_flag(FuEngineRequest *self, FwupdDeviceFlags device_flag); FwupdDeviceFlags fu_engine_request_get_device_flags(FuEngineRequest *self); void fu_engine_request_set_device_flags(FuEngineRequest *self, FwupdDeviceFlags device_flags); fwupd-1.9.16/src/fu-engine-requirements.c000066400000000000000000000606461460375044200203070ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEngine" #include "config.h" #include "fu-engine-requirements.h" static gboolean fu_engine_requirements_require_vercmp(XbNode *req, const gchar *version, FwupdVersionFormat fmt, GError **error) { gboolean ret = FALSE; gint rc = 0; const gchar *tmp = xb_node_get_attr(req, "compare"); const gchar *version_req = xb_node_get_attr(req, "version"); if (g_strcmp0(tmp, "eq") == 0) { rc = fu_version_compare(version, version_req, fmt); ret = rc == 0; } else if (g_strcmp0(tmp, "ne") == 0) { rc = fu_version_compare(version, version_req, fmt); ret = rc != 0; } else if (g_strcmp0(tmp, "lt") == 0) { rc = fu_version_compare(version, version_req, fmt); ret = rc < 0; } else if (g_strcmp0(tmp, "gt") == 0) { rc = fu_version_compare(version, version_req, fmt); ret = rc > 0; } else if (g_strcmp0(tmp, "le") == 0) { rc = fu_version_compare(version, version_req, fmt); ret = rc <= 0; } else if (g_strcmp0(tmp, "ge") == 0) { rc = fu_version_compare(version, version_req, fmt); ret = rc >= 0; } else if (g_strcmp0(tmp, "glob") == 0) { ret = g_pattern_match_simple(version_req, version); } else if (g_strcmp0(tmp, "regex") == 0) { ret = g_regex_match_simple(version_req, version, 0, 0); } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to compare [%s] and [%s]", version_req, version); return FALSE; } /* set error */ if (!ret) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed predicate [%s %s %s]", version_req, tmp, version); } return ret; } static gboolean fu_engine_requirements_check_not_child(FuEngine *self, XbNode *req, FuDevice *device, GError **error) { GPtrArray *children = fu_device_get_children(device); /* only supported */ if (g_strcmp0(xb_node_get_element(req), "firmware") != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot handle not-child %s requirement", xb_node_get_element(req)); return FALSE; } /* check each child */ for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index(children, i); const gchar *version = fu_device_get_version(child); if (version == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no version provided by %s, child of %s", fu_device_get_name(child), fu_device_get_name(device)); return FALSE; } if (fu_engine_requirements_require_vercmp(req, version, fu_device_get_version_format(child), NULL)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not compatible with child device version %s", version); return FALSE; } } return TRUE; } static gboolean fu_engine_requirements_check_vendor_id(FuEngine *self, XbNode *req, FuDevice *device, GError **error) { GPtrArray *vendor_ids; const gchar *vendor_ids_metadata; g_autofree gchar *vendor_ids_device = NULL; /* devices without vendor IDs should not exist! */ vendor_ids = fu_device_get_vendor_ids(device); if (vendor_ids->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device [%s] has no vendor ID", fu_device_get_id(device)); return FALSE; } /* metadata with empty vendor IDs should not exist! */ vendor_ids_metadata = xb_node_get_attr(req, "version"); if (vendor_ids_metadata == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "metadata has no vendor ID"); return FALSE; } /* it is always safe to use a regex, even for simple strings */ vendor_ids_device = fu_strjoin("|", vendor_ids); if (!g_regex_match_simple(vendor_ids_metadata, vendor_ids_device, 0, 0)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with vendor %s: got %s", vendor_ids_device, vendor_ids_metadata); return FALSE; } /* success */ return TRUE; } static gboolean fu_device_has_guids_any(FuDevice *self, gchar **guids) { g_return_val_if_fail(FU_IS_DEVICE(self), FALSE); g_return_val_if_fail(guids != NULL, FALSE); for (guint i = 0; guids[i] != NULL; i++) { if (fu_device_has_guid(self, guids[i])) return TRUE; } return FALSE; } static gboolean fu_engine_requirements_check_firmware(FuEngine *self, XbNode *req, FuDevice *device, const gchar *fwupd_version, FwupdInstallFlags flags, GError **error) { const gchar *version; const gchar *depth_str; gint64 depth = G_MAXINT64; g_autoptr(FuDevice) device_actual = g_object_ref(device); g_autoptr(GError) error_local = NULL; g_auto(GStrv) guids = NULL; /* look at the parent device */ depth_str = xb_node_get_attr(req, "depth"); if (depth_str != NULL) { if (!fu_strtoll(depth_str, &depth, -1, 10, error)) return FALSE; for (gint64 i = 0; i < depth; i++) { FuDevice *device_tmp = fu_device_get_parent(device_actual); if (device_tmp == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No parent device for %s " "(%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT ")", fu_device_get_name(device_actual), i, depth); return FALSE; } g_set_object(&device_actual, device_tmp); } } /* check fwupd version requirement */ if (depth < 0) { if (fu_version_compare(fwupd_version, "1.9.7", FWUPD_VERSION_FORMAT_UNKNOWN) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "requirement 'child firmware' also needs %s >= 1.9.7", FWUPD_DBUS_SERVICE); return FALSE; } } else if (depth == 0) { if (fu_version_compare(fwupd_version, "1.6.1", FWUPD_VERSION_FORMAT_UNKNOWN) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "requirement 'sibling firmware' also needs %s >= 1.6.1", FWUPD_DBUS_SERVICE); return FALSE; } } else if (depth == 1) { if (fu_version_compare(fwupd_version, "1.3.4", FWUPD_VERSION_FORMAT_UNKNOWN) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "requirement 'parent firmware' also needs %s >= 1.3.4", FWUPD_DBUS_SERVICE); return FALSE; } } /* old firmware version */ if (xb_node_get_text(req) == NULL) { version = fu_device_get_version(device_actual); if (!fu_engine_requirements_require_vercmp( req, version, fu_device_get_version_format(device_actual), &error_local)) { if (g_strcmp0(xb_node_get_attr(req, "compare"), "ge") == 0) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with firmware version %s, requires >= %s", version, xb_node_get_attr(req, "version")); } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with firmware version: %s", error_local->message); } return FALSE; } return TRUE; } /* bootloader version */ if (g_strcmp0(xb_node_get_text(req), "bootloader") == 0) { version = fu_device_get_version_bootloader(device_actual); if (!fu_engine_requirements_require_vercmp( req, version, fu_device_get_version_format(device_actual), &error_local)) { if (g_strcmp0(xb_node_get_attr(req, "compare"), "ge") == 0) { g_set_error( error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not compatible with bootloader version %s, requires >= %s", version, xb_node_get_attr(req, "version")); } else { g_debug("Bootloader is not compatible: %s", error_local->message); g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Bootloader is not compatible"); } return FALSE; } return TRUE; } /* vendor ID */ if (g_strcmp0(xb_node_get_text(req), "vendor-id") == 0) { if (flags & FWUPD_INSTALL_FLAG_IGNORE_VID_PID) return TRUE; return fu_engine_requirements_check_vendor_id(self, req, device_actual, error); } /* child version */ if (g_strcmp0(xb_node_get_text(req), "not-child") == 0) return fu_engine_requirements_check_not_child(self, req, device_actual, error); /* another device, specified by GUID|GUID|GUID */ guids = g_strsplit(xb_node_get_text(req), "|", -1); for (guint i = 0; guids[i] != NULL; i++) { if (!fwupd_guid_is_valid(guids[i])) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%s is not a valid GUID", guids[i]); return FALSE; } } /* find if any of the other devices exists */ if (depth == G_MAXINT64) { g_autoptr(FuDevice) device_tmp = NULL; for (guint i = 0; guids[i] != NULL; i++) { g_autoptr(GPtrArray) devices = fu_engine_get_devices_by_guid(self, guids[i], NULL); if (devices != NULL && devices->len > 0) { device_tmp = g_object_ref(g_ptr_array_index(devices, 0)); break; } } if (device_tmp == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No other device %s found", xb_node_get_text(req)); return FALSE; } g_set_object(&device_actual, device_tmp); } else if (depth == -1) { GPtrArray *children; FuDevice *child = NULL; /* look for a child */ children = fu_device_get_children(device); for (guint i = 0; i < children->len; i++) { child = g_ptr_array_index(children, i); if (fu_device_has_guids_any(child, guids)) break; child = NULL; } if (child == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No child found with GUID of %s", xb_node_get_text(req)); return FALSE; } g_set_object(&device_actual, child); /* look for a sibling */ } else if (depth == 0) { FuDevice *child = NULL; FuDevice *parent = fu_device_get_parent(device_actual); GPtrArray *children; /* no parent, so look for GUIDs on this device */ if (parent == NULL) { if (!fu_device_has_guids_any(device_actual, guids)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No GUID of %s on device %s", xb_node_get_text(req), fu_device_get_name(device_actual)); return FALSE; } return TRUE; } children = fu_device_get_children(parent); for (guint i = 0; i < children->len; i++) { child = g_ptr_array_index(children, i); if (fu_device_has_guids_any(child, guids)) break; child = NULL; } if (child == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No sibling found with GUID of %s", xb_node_get_text(req)); return FALSE; } g_set_object(&device_actual, child); /* verify the parent device has the GUID */ } else { if (!fu_device_has_guids_any(device_actual, guids)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No GUID of %s on parent device %s", xb_node_get_text(req), fu_device_get_name(device_actual)); return FALSE; } } /* check fwupd version requirement */ if (depth == G_MAXINT64 && fu_device_get_version(device_actual) != NULL) { if (fu_version_compare(fwupd_version, "1.1.0", FWUPD_VERSION_FORMAT_UNKNOWN) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "requirement 'firmware with version' also needs %s >= 1.1.0", FWUPD_DBUS_SERVICE); return FALSE; } } if (depth == G_MAXINT64 && fu_device_get_version(device_actual) == NULL) { if (fu_version_compare(fwupd_version, "1.2.11", FWUPD_VERSION_FORMAT_UNKNOWN) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "requirement 'firmware no version' also needs %s >= 1.2.11", FWUPD_DBUS_SERVICE); return FALSE; } } /* get the version of the other device */ version = fu_device_get_version(device_actual); if (version != NULL && xb_node_get_attr(req, "compare") != NULL && !fu_engine_requirements_require_vercmp(req, version, fu_device_get_version_format(device_actual), &error_local)) { if (g_strcmp0(xb_node_get_attr(req, "compare"), "ge") == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with %s version %s, requires >= %s", fu_device_get_name(device_actual), version, xb_node_get_attr(req, "version")); } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with %s: %s", fu_device_get_name(device_actual), error_local->message); } return FALSE; } /* success */ return TRUE; } static gboolean fu_engine_requirements_check_id(FuEngine *self, XbNode *req, GError **error) { FuContext *ctx = fu_engine_get_context(self); g_autoptr(GError) error_local = NULL; const gchar *version; /* sanity check */ if (xb_node_get_text(req) == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no requirement value supplied"); return FALSE; } version = fu_context_get_runtime_version(ctx, xb_node_get_text(req)); if (version == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no version available for %s", xb_node_get_text(req)); return FALSE; } if (!fu_engine_requirements_require_vercmp(req, version, FWUPD_VERSION_FORMAT_UNKNOWN, &error_local)) { if (g_strcmp0(xb_node_get_attr(req, "compare"), "ge") == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with %s version %s, requires >= %s", xb_node_get_text(req), version, xb_node_get_attr(req, "version")); } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Not compatible with %s version: %s", xb_node_get_text(req), error_local->message); } return FALSE; } g_debug("requirement %s %s %s -> %s passed", xb_node_get_attr(req, "version"), xb_node_get_attr(req, "compare"), version, xb_node_get_text(req)); return TRUE; } static gboolean fu_engine_requirements_check_hardware(FuEngine *self, XbNode *req, const gchar *fwupd_version, GError **error) { FuContext *ctx = fu_engine_get_context(self); const gchar *fwupd_required = "1.0.1"; g_auto(GStrv) hwid_split = NULL; /* sanity check */ if (xb_node_get_text(req) == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no requirement value supplied"); return FALSE; } /* check fwupd version requirement */ if (g_strstr_len(xb_node_get_text(req), -1, "|") != NULL) fwupd_required = "1.0.8"; if (fu_version_compare(fwupd_version, fwupd_required, FWUPD_VERSION_FORMAT_UNKNOWN) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "requirement 'hardware' also needs %s >= %s", FWUPD_DBUS_SERVICE, fwupd_required); return FALSE; } /* split and treat as OR */ hwid_split = g_strsplit(xb_node_get_text(req), "|", -1); for (guint i = 0; hwid_split[i] != NULL; i++) { if (fu_context_has_hwid_guid(ctx, hwid_split[i])) { g_debug("HWID provided %s", hwid_split[i]); return TRUE; } } /* nothing matched */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no HWIDs matched %s", xb_node_get_text(req)); return FALSE; } static gboolean fu_engine_requirements_check_not_hardware(FuEngine *self, XbNode *req, const gchar *fwupd_version, GError **error) { FuContext *ctx = fu_engine_get_context(self); g_auto(GStrv) hwid_split = NULL; /* check fwupd version requirement */ if (fu_version_compare(fwupd_version, "1.9.10", FWUPD_VERSION_FORMAT_UNKNOWN) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "requirement 'not_hardware' also needs %s >= 1.9.10", FWUPD_DBUS_SERVICE); return FALSE; } /* sanity check */ if (xb_node_get_text(req) == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no requirement value supplied"); return FALSE; } /* split and treat as OR */ hwid_split = g_strsplit(xb_node_get_text(req), "|", -1); for (guint i = 0; hwid_split[i] != NULL; i++) { if (fu_context_has_hwid_guid(ctx, hwid_split[i])) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "%s HWIDs matched", hwid_split[i]); return FALSE; } } /* nothing matched */ return TRUE; } static gboolean fu_engine_requirements_check_client(FuEngine *self, FuEngineRequest *request, XbNode *req, const gchar *fwupd_version, GError **error) { FwupdFeatureFlags flags; g_auto(GStrv) feature_split = NULL; /* sanity check */ if (xb_node_get_text(req) == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no requirement value supplied"); return FALSE; } /* check fwupd version requirement */ if (fu_version_compare(fwupd_version, "1.4.5", FWUPD_VERSION_FORMAT_UNKNOWN) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "requirement 'client' also needs %s >= 1.4.5", FWUPD_DBUS_SERVICE); return FALSE; } /* split and treat as AND */ feature_split = g_strsplit(xb_node_get_text(req), "|", -1); flags = fu_engine_request_get_feature_flags(request); for (guint i = 0; feature_split[i] != NULL; i++) { FwupdFeatureFlags flag = fwupd_feature_flag_from_string(feature_split[i]); /* not recognized */ if (flag == FWUPD_FEATURE_FLAG_LAST) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "client requirement %s unknown", feature_split[i]); return FALSE; } /* not supported */ if ((flags & flag) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "client requirement %s not supported", feature_split[i]); return FALSE; } } /* success */ return TRUE; } static gboolean fu_engine_requirements_check_hard(FuEngine *self, FuRelease *release, XbNode *req, const gchar *fwupd_version, FwupdInstallFlags flags, GError **error) { FuContext *ctx = fu_engine_get_context(self); FuDevice *device = fu_release_get_device(release); FuEngineRequest *request = fu_release_get_request(release); /* ensure component requirement */ if (g_strcmp0(xb_node_get_element(req), "id") == 0) return fu_engine_requirements_check_id(self, req, error); /* ensure firmware requirement */ if (g_strcmp0(xb_node_get_element(req), "firmware") == 0) { if (device == NULL) return TRUE; return fu_engine_requirements_check_firmware(self, req, device, fwupd_version, flags, error); } /* ensure hardware requirement */ if (g_strcmp0(xb_node_get_element(req), "hardware") == 0) { if (!fu_context_has_flag(ctx, FU_CONTEXT_FLAG_LOADED_HWINFO)) return TRUE; return fu_engine_requirements_check_hardware(self, req, fwupd_version, error); } if (g_strcmp0(xb_node_get_element(req), "not_hardware") == 0) { if (!fu_context_has_flag(ctx, FU_CONTEXT_FLAG_LOADED_HWINFO)) return TRUE; return fu_engine_requirements_check_not_hardware(self, req, fwupd_version, error); } /* ensure client requirement */ if (g_strcmp0(xb_node_get_element(req), "client") == 0) { return fu_engine_requirements_check_client(self, request, req, fwupd_version, error); } /* not supported */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "cannot handle requirement type %s", xb_node_get_element(req)); return FALSE; } static gboolean fu_engine_requirements_check_soft(FuEngine *self, FuRelease *release, XbNode *req, const gchar *fwupd_version, FwupdInstallFlags flags, GError **error) { g_autoptr(GError) error_local = NULL; if (!fu_engine_requirements_check_hard(self, release, req, fwupd_version, flags, &error_local)) { if (flags & FWUPD_INSTALL_FLAG_FORCE) { g_info("ignoring soft-requirement due to --force: %s", error_local->message); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } return TRUE; } static gboolean fu_engine_requirements_is_specific_req(XbNode *req) { if (g_strcmp0(xb_node_get_element(req), "firmware") == 0 && xb_node_get_attr(req, "depth") != NULL) return TRUE; if (g_strcmp0(xb_node_get_element(req), "hardware") == 0) return TRUE; return FALSE; } static gchar * fu_engine_requirements_get_newest_fwupd_version(FuEngine *self, FuRelease *release, GError **error) { const gchar *newest_version = "1.0.0"; GPtrArray *reqs = fu_release_get_hard_reqs(release); /* trivial case */ if (reqs == NULL) return g_strdup(newest_version); /* find the newest fwupd requirement */ for (guint i = 0; i < reqs->len; i++) { XbNode *req = g_ptr_array_index(reqs, i); if (g_strcmp0(xb_node_get_text(req), FWUPD_DBUS_SERVICE) == 0 && g_strcmp0(xb_node_get_attr(req, "compare"), "ge") == 0) { const gchar *version = xb_node_get_attr(req, "version"); if (version == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no version provided for requirement %s", xb_node_get_text(req)); return NULL; } /* is this unique, or newer than what we have */ if (newest_version == NULL || fu_version_compare(version, newest_version, FWUPD_VERSION_FORMAT_UNKNOWN) > 0) { newest_version = version; } } } return g_strdup(newest_version); } gboolean fu_engine_requirements_check(FuEngine *self, FuRelease *release, FwupdInstallFlags flags, GError **error) { FuDevice *device = fu_release_get_device(release); GPtrArray *reqs; gboolean has_hardware_req = FALSE; gboolean has_not_hardware_req = FALSE; gboolean has_specific_requirement = FALSE; g_autofree gchar *fwupd_version = NULL; /* get the newest fwupd version requirement */ fwupd_version = fu_engine_requirements_get_newest_fwupd_version(self, release, error); if (fwupd_version == NULL) return FALSE; /* hard requirements */ reqs = fu_release_get_hard_reqs(release); if (reqs != NULL) { for (guint i = 0; i < reqs->len; i++) { XbNode *req = g_ptr_array_index(reqs, i); if (!fu_engine_requirements_check_hard(self, release, req, fwupd_version, flags, error)) return FALSE; if (fu_engine_requirements_is_specific_req(req)) has_specific_requirement = TRUE; if (g_strcmp0(xb_node_get_element(req), "hardware") == 0) has_hardware_req = TRUE; else if (g_strcmp0(xb_node_get_element(req), "not_hardware") == 0) has_not_hardware_req = TRUE; } } /* it does not make sense to allowlist and denylist at the same time */ if (has_hardware_req && has_not_hardware_req) { g_set_error_literal( error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "using hardware and not_hardware at the same time is not supported"); return FALSE; } /* if a device uses a generic ID (i.e. not matching the OEM) then check to make sure the * firmware is specific enough, e.g. by using a CHID or depth requirement */ if (device != NULL && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED) && fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_ENFORCE_REQUIRES) && !has_specific_requirement) { #ifdef SUPPORTED_BUILD g_set_error_literal( error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "generic GUID requires a CHID, child, parent or sibling requirement"); return FALSE; #else if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "generic GUID requires --force, a CHID, child, parent " "or sibling requirement"); return FALSE; } g_info("ignoring enforce-requires requirement due to --force"); #endif } /* soft requirements */ reqs = fu_release_get_soft_reqs(release); if (reqs != NULL) { for (guint i = 0; i < reqs->len; i++) { XbNode *req = g_ptr_array_index(reqs, i); if (!fu_engine_requirements_check_soft(self, release, req, fwupd_version, flags, error)) return FALSE; } } /* success */ return TRUE; } fwupd-1.9.16/src/fu-engine-requirements.h000066400000000000000000000005221460375044200202770ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-engine.h" #include "fu-release.h" gboolean fu_engine_requirements_check(FuEngine *engine, FuRelease *release, FwupdInstallFlags flags, GError **error); fwupd-1.9.16/src/fu-engine.c000066400000000000000000010534761460375044200155720ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuEngine" #include "config.h" #include #ifdef HAVE_GIO_UNIX #include #endif #ifdef HAVE_PASSIM #include #endif #include #include #ifdef HAVE_UTSNAME_H #include #endif #include #ifdef _WIN32 #include #include #include #endif #include #include "fwupd-bios-setting-private.h" #include "fwupd-common-private.h" #include "fwupd-device-private.h" #include "fwupd-enums-private.h" #include "fwupd-release-private.h" #include "fwupd-remote-private.h" #include "fwupd-resources.h" #include "fwupd-security-attr-private.h" #include "fu-backend-private.h" #include "fu-bios-settings-private.h" #include "fu-cabinet.h" #include "fu-config-private.h" #include "fu-context-private.h" #include "fu-coswid-firmware.h" #include "fu-debug.h" #include "fu-device-list.h" #include "fu-device-private.h" #include "fu-device-progress.h" #include "fu-engine-helper.h" #include "fu-engine-request.h" #include "fu-engine-requirements.h" #include "fu-engine.h" #include "fu-history.h" #include "fu-idle.h" #include "fu-plugin-builtin.h" #include "fu-plugin-list.h" #include "fu-plugin-private.h" #include "fu-release.h" #include "fu-remote-list.h" #include "fu-security-attr-common.h" #include "fu-security-attrs-private.h" #include "fu-udev-device-private.h" #include "fu-usb-device-fw-ds20.h" #include "fu-usb-device-ms-ds20.h" #include "fu-usb-device-private.h" #include "fu-version.h" #ifdef HAVE_GUDEV #include "fu-udev-backend.h" #endif #ifdef HAVE_GUSB #include "fu-usb-backend.h" #endif #ifdef HAVE_BLUEZ #include "fu-bluez-backend.h" #endif /* only needed until we hard depend on jcat 0.1.3 */ #include #ifdef HAVE_SYSTEMD #include "fu-systemd.h" #endif #define MINIMUM_BATTERY_PERCENTAGE_FALLBACK 10 #define FU_ENGINE_UPDATE_MOTD_DELAY 5 /* s */ #define FU_ENGINE_MAX_METADATA_SIZE 0x2000000 /* 32MB */ #define FU_ENGINE_MAX_SIGNATURE_SIZE 0x100000 /* 1MB */ static void fu_engine_constructed(GObject *obj); static void fu_engine_finalize(GObject *obj); static void fu_engine_ensure_security_attrs(FuEngine *self); static gboolean fu_engine_backends_save_phase(FuEngine *self, GError **error); static gboolean fu_engine_emulation_load_phase(FuEngine *self, GError **error); struct _FuEngine { GObject parent_instance; GPtrArray *backends; FuEngineConfig *config; FuRemoteList *remote_list; FuDeviceList *device_list; gboolean only_trusted; gboolean write_history; gboolean host_emulation; guint percentage; FuHistory *history; FuIdle *idle; XbSilo *silo; XbQuery *query_component_by_guid; XbQuery *query_container_checksum1; XbQuery *query_container_checksum2; XbQuery *query_tag_by_guid_version; guint coldplug_id; FuPluginList *plugin_list; GPtrArray *plugin_filter; FuContext *ctx; GHashTable *approved_firmware; /* (nullable) */ GHashTable *blocked_firmware; /* (nullable) */ GHashTable *emulation_phases; /* (element-type int utf8) */ GHashTable *emulation_backend_ids; /* (element-type str int) */ GHashTable *device_changed_allowlist; /* (element-type str int) */ gchar *host_machine_id; JcatContext *jcat_context; gboolean loaded; gchar *host_security_id; FuSecurityAttrs *host_security_attrs; GPtrArray *local_monitors; /* (element-type GFileMonitor) */ GMainLoop *acquiesce_loop; guint acquiesce_id; guint acquiesce_delay; guint update_motd_id; FuEngineInstallPhase install_phase; #ifdef HAVE_PASSIM PassimClient *passim_client; #endif }; enum { PROP_0, PROP_CONTEXT, PROP_LAST }; enum { SIGNAL_CHANGED, SIGNAL_DEVICE_ADDED, SIGNAL_DEVICE_REMOVED, SIGNAL_DEVICE_CHANGED, SIGNAL_DEVICE_REQUEST, SIGNAL_STATUS_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; G_DEFINE_TYPE(FuEngine, fu_engine, G_TYPE_OBJECT) static gboolean fu_engine_update_motd_timeout_cb(gpointer user_data) { FuEngine *self = FU_ENGINE(user_data); g_autoptr(GError) error_local = NULL; /* busy */ if (fu_idle_has_inhibit(self->idle, FU_IDLE_INHIBIT_SIGNALS)) return G_SOURCE_CONTINUE; /* update now */ if (!fu_engine_update_motd(self, &error_local)) g_info("failed to update MOTD: %s", error_local->message); self->update_motd_id = 0; return G_SOURCE_REMOVE; } static void fu_engine_update_motd_reset(FuEngine *self) { g_info("resetting update motd timeout"); if (self->update_motd_id != 0) g_source_remove(self->update_motd_id); self->update_motd_id = g_timeout_add_seconds(FU_ENGINE_UPDATE_MOTD_DELAY, fu_engine_update_motd_timeout_cb, self); } static void fu_engine_emit_changed(FuEngine *self) { g_autoptr(GError) error = NULL; /* do nothing */ if (!self->loaded) return; g_signal_emit(self, signals[SIGNAL_CHANGED], 0); fu_engine_idle_reset(self); /* update the motd */ if (fu_engine_config_get_update_motd(self->config)) fu_engine_update_motd_reset(self); /* update the list of devices */ if (!fu_engine_update_devices_file(self, &error)) g_info("failed to update list of devices: %s", error->message); } static void fu_engine_emit_device_changed_safe(FuEngine *self, FuDevice *device) { /* do nothing */ if (!self->loaded) return; /* invalidate host security attributes */ g_clear_pointer(&self->host_security_id, g_free); g_signal_emit(self, signals[SIGNAL_DEVICE_CHANGED], 0, device); } /* get the latest version of the device */ static void fu_engine_emit_device_changed(FuEngine *self, const gchar *device_id) { g_autoptr(FuDevice) device = NULL; g_autoptr(GError) error = NULL; /* get the latest version of this */ device = fu_device_list_get_by_id(self->device_list, device_id, &error); if (device == NULL) { g_warning("cannot emit device-changed: %s", error->message); return; } fu_engine_emit_device_changed_safe(self, device); } FuContext * fu_engine_get_context(FuEngine *self) { return self->ctx; } static void fu_engine_set_status(FuEngine *self, FwupdStatus status) { /* emit changed */ g_signal_emit(self, signals[SIGNAL_STATUS_CHANGED], 0, status); } static void fu_engine_generic_notify_cb(FuDevice *device, GParamSpec *pspec, FuEngine *self) { if (fu_idle_has_inhibit(self->idle, FU_IDLE_INHIBIT_SIGNALS) && !g_hash_table_contains(self->device_changed_allowlist, fu_device_get_id(device))) { g_debug("suppressing notification from %s as transaction is in progress", fu_device_get_id(device)); return; } fu_engine_emit_device_changed(self, fu_device_get_id(device)); } static void fu_engine_history_notify_cb(FuDevice *device, GParamSpec *pspec, FuEngine *self) { if (self->write_history) { g_autoptr(GError) error_local = NULL; if (!fu_history_modify_device(self->history, device, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_debug("ignoring: %s", error_local->message); } else { g_warning("failed to record history for %s: %s", fu_device_get_id(device), error_local->message); } } } fu_engine_emit_device_changed(self, fu_device_get_id(device)); } static void fu_engine_device_request_cb(FuDevice *device, FwupdRequest *request, FuEngine *self) { g_info("Emitting DeviceRequest('Message'='%s')", fwupd_request_get_message(request)); g_signal_emit(self, signals[SIGNAL_DEVICE_REQUEST], 0, request); } static void fu_engine_set_install_phase(FuEngine *self, FuEngineInstallPhase install_phase) { g_info("install phase now %s", fu_engine_install_phase_to_string(install_phase)); self->install_phase = install_phase; } static void fu_engine_watch_device(FuEngine *self, FuDevice *device) { g_autoptr(FuDevice) device_old = fu_device_list_get_old(self->device_list, device); if (device_old != NULL) { g_signal_handlers_disconnect_by_func(device_old, fu_engine_generic_notify_cb, self); g_signal_handlers_disconnect_by_func(device_old, fu_engine_history_notify_cb, self); g_signal_handlers_disconnect_by_func(device_old, fu_engine_device_request_cb, self); } g_signal_connect(FU_DEVICE(device), "notify::flags", G_CALLBACK(fu_engine_generic_notify_cb), self); g_signal_connect(FU_DEVICE(device), "notify::problems", G_CALLBACK(fu_engine_generic_notify_cb), self); g_signal_connect(FU_DEVICE(device), "notify::update-message", G_CALLBACK(fu_engine_generic_notify_cb), self); g_signal_connect(FU_DEVICE(device), "notify::update-image", G_CALLBACK(fu_engine_generic_notify_cb), self); g_signal_connect(FU_DEVICE(device), "notify::update-state", G_CALLBACK(fu_engine_history_notify_cb), self); g_signal_connect(FU_DEVICE(device), "notify::update-error", G_CALLBACK(fu_engine_history_notify_cb), self); g_signal_connect(FU_DEVICE(device), "request", G_CALLBACK(fu_engine_device_request_cb), self); } static void fu_engine_ensure_device_power_inhibit(FuEngine *self, FuDevice *device) { if (fu_engine_config_get_ignore_power(self->config)) return; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REQUIRE_AC) && !fu_power_state_is_ac(fu_context_get_power_state(self->ctx))) { fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_REQUIRE_AC_POWER); } else { fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_REQUIRE_AC_POWER); } if (!fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_IGNORE_SYSTEM_POWER) && fu_context_get_battery_level(self->ctx) != FWUPD_BATTERY_LEVEL_INVALID && fu_context_get_battery_threshold(self->ctx) != FWUPD_BATTERY_LEVEL_INVALID && fu_context_get_battery_level(self->ctx) < fu_context_get_battery_threshold(self->ctx)) { fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW); } else { fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW); } } static void fu_engine_ensure_device_lid_inhibit(FuEngine *self, FuDevice *device) { if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_NO_LID_CLOSED) && fu_context_get_lid_state(self->ctx) == FU_LID_STATE_CLOSED) { fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_LID_IS_CLOSED); return; } fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_LID_IS_CLOSED); } static void fu_engine_ensure_device_display_required_inhibit(FuEngine *self, FuDevice *device) { if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_DISPLAY_REQUIRED) && fu_context_get_display_state(self->ctx) == FU_DISPLAY_STATE_DISCONNECTED) { fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_DISPLAY_REQUIRED); return; } fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_DISPLAY_REQUIRED); } static void fu_engine_ensure_device_system_inhibit(FuEngine *self, FuDevice *device) { if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SYSTEM_INHIBIT)) { fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT); return; } fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT); } static gboolean fu_engine_acquiesce_timeout_cb(gpointer user_data) { FuEngine *self = FU_ENGINE(user_data); g_info("system acquiesced after %ums", self->acquiesce_delay); g_main_loop_quit(self->acquiesce_loop); self->acquiesce_id = 0; return G_SOURCE_REMOVE; } static void fu_engine_acquiesce_reset(FuEngine *self) { if (!g_main_loop_is_running(self->acquiesce_loop)) return; g_info("resetting system acquiesce timeout"); if (self->acquiesce_id != 0) g_source_remove(self->acquiesce_id); self->acquiesce_id = g_timeout_add(self->acquiesce_delay, fu_engine_acquiesce_timeout_cb, self); } static void fu_engine_wait_for_acquiesce(FuEngine *self, guint acquiesce_delay) { if (acquiesce_delay == 0) return; self->acquiesce_delay = acquiesce_delay; self->acquiesce_id = g_timeout_add(acquiesce_delay, fu_engine_acquiesce_timeout_cb, self); g_main_loop_run(self->acquiesce_loop); } static void fu_engine_device_added_cb(FuDeviceList *device_list, FuDevice *device, FuEngine *self) { fu_engine_watch_device(self, device); fu_engine_ensure_device_power_inhibit(self, device); fu_engine_ensure_device_lid_inhibit(self, device); fu_engine_ensure_device_display_required_inhibit(self, device); fu_engine_ensure_device_system_inhibit(self, device); fu_engine_acquiesce_reset(self); g_signal_emit(self, signals[SIGNAL_DEVICE_ADDED], 0, device); } static void fu_engine_device_runner_device_removed(FuEngine *self, FuDevice *device) { GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); fu_plugin_runner_device_removed(plugin_tmp, device); } } static void fu_engine_device_removed_cb(FuDeviceList *device_list, FuDevice *device, FuEngine *self) { fu_engine_device_runner_device_removed(self, device); fu_engine_acquiesce_reset(self); g_signal_handlers_disconnect_by_data(device, self); g_signal_emit(self, signals[SIGNAL_DEVICE_REMOVED], 0, device); } static void fu_engine_device_changed_cb(FuDeviceList *device_list, FuDevice *device, FuEngine *self) { fu_engine_watch_device(self, device); fu_engine_emit_device_changed(self, fu_device_get_id(device)); fu_engine_acquiesce_reset(self); } static gchar * fu_engine_request_get_localized_xpath(FuEngineRequest *request, const gchar *element) { GString *xpath = g_string_new(element); const gchar *locale = NULL; /* optional; not set in tests */ if (request != NULL) locale = fu_engine_request_get_locale(request); /* prefer the users locale if set */ if (locale != NULL) { g_autofree gchar *xpath_locale = NULL; xpath_locale = g_strdup_printf("%s[@xml:lang='%s']|", element, locale); g_string_prepend(xpath, xpath_locale); } return g_string_free(xpath, FALSE); } /* add any client-side BKC tags */ static gboolean fu_engine_add_local_release_metadata(FuEngine *self, FuRelease *release, GError **error) { FuDevice *dev = fu_release_get_device(release); GPtrArray *guids; /* no device matched */ if (dev == NULL) return TRUE; /* not set up */ if (self->query_tag_by_guid_version == NULL) return TRUE; /* use prepared query for each GUID */ guids = fu_device_get_guids(dev); for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index(guids, i); g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) tags = NULL; g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); /* bind GUID and then query */ xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, guid, NULL); xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 1, fu_release_get_version(release), NULL); tags = xb_silo_query_with_context(self->silo, self->query_tag_by_guid_version, &context, &error_local); if (tags == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) || g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) continue; g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } for (guint j = 0; j < tags->len; j++) { XbNode *tag = g_ptr_array_index(tags, j); fu_release_add_tag(release, xb_node_get_text(tag)); } } /* success */ return TRUE; } /* private, for self tests */ void fu_engine_add_remote(FuEngine *self, FwupdRemote *remote) { g_return_if_fail(FU_IS_ENGINE(self)); g_return_if_fail(FWUPD_IS_REMOTE(remote)); fu_remote_list_add_remote(self->remote_list, remote); } static void fu_engine_release_remote_id_changed_cb(FuRelease *release, GParamSpec *pspec, FuEngine *self) { FwupdRemote *remote; const gchar *remote_id = fwupd_release_get_remote_id(FWUPD_RELEASE(release)); if (remote_id == NULL) return; remote = fu_remote_list_get_by_id(self->remote_list, remote_id); if (remote == NULL) { g_warning("no remote found for %s", remote_id); return; } fu_release_set_remote(release, remote); } static gboolean fu_engine_compare_report_trusted(FwupdReport *report_trusted, FwupdReport *report) { if (fwupd_report_has_flag(report_trusted, FWUPD_REPORT_FLAG_FROM_OEM) && !fwupd_report_has_flag(report, FWUPD_REPORT_FLAG_FROM_OEM)) return FALSE; if (fwupd_report_has_flag(report_trusted, FWUPD_REPORT_FLAG_IS_UPGRADE) && !fwupd_report_has_flag(report, FWUPD_REPORT_FLAG_IS_UPGRADE)) return FALSE; if (fwupd_report_get_vendor_id(report_trusted) != 0) { if (fwupd_report_get_vendor_id(report_trusted) != fwupd_report_get_vendor_id(report)) return FALSE; } if (fwupd_report_get_distro_id(report_trusted) != NULL) { if (g_strcmp0(fwupd_report_get_distro_id(report_trusted), fwupd_report_get_distro_id(report)) != 0) return FALSE; } if (fwupd_report_get_distro_version(report_trusted) != NULL) { if (g_strcmp0(fwupd_report_get_distro_version(report_trusted), fwupd_report_get_distro_version(report)) != 0) return FALSE; } if (fwupd_report_get_distro_variant(report_trusted) != NULL) { if (g_strcmp0(fwupd_report_get_distro_variant(report_trusted), fwupd_report_get_distro_variant(report)) != 0) return FALSE; } if (fwupd_report_get_remote_id(report_trusted) != NULL) { if (g_strcmp0(fwupd_report_get_remote_id(report_trusted), fwupd_report_get_remote_id(report)) != 0) return FALSE; } return TRUE; } static void fu_engine_add_trusted_report(FuEngine *self, FuRelease *release) { GPtrArray *reports = fu_release_get_reports(release); GPtrArray *trusted_reports = fu_engine_config_get_trusted_reports(self->config); for (guint i = 0; i < reports->len; i++) { FwupdReport *report = g_ptr_array_index(reports, i); for (guint j = 0; j < trusted_reports->len; j++) { FwupdReport *trusted_report = g_ptr_array_index(trusted_reports, j); if (fu_engine_compare_report_trusted(trusted_report, report)) { g_autofree gchar *str = fwupd_report_to_string(trusted_report); g_debug("add trusted-report to %s:%s as trusted: %s", fu_release_get_appstream_id(release), fu_release_get_version(release), str); fu_release_add_flag(release, FWUPD_RELEASE_FLAG_TRUSTED_REPORT); return; } } } } static gboolean fu_engine_load_release(FuEngine *self, FuRelease *release, XbNode *component, XbNode *rel, FwupdInstallFlags install_flags, GError **error) { /* load release from XML */ fu_release_set_config(release, self->config); /* set the FwupdRemote when the remote ID is set */ g_signal_connect(FU_RELEASE(release), "notify::remote-id", G_CALLBACK(fu_engine_release_remote_id_changed_cb), self); /* requirements we can check without the daemon */ if (!fu_release_load(release, component, rel, install_flags, error)) return FALSE; /* additional requirements */ if (!fu_engine_requirements_check(self, release, install_flags, error)) return FALSE; /* add any client-side BKC tags */ if (!fu_engine_add_local_release_metadata(self, release, error)) return FALSE; /* add the trusted report metadata if appropriate */ fu_engine_add_trusted_report(self, release); /* success */ return TRUE; } /* finds the remote-id for the first firmware in the silo that matches this * container checksum */ static XbNode * fu_engine_get_component_for_checksum(FuEngine *self, const gchar *csum) { g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, csum, NULL); if (self->query_container_checksum1 != NULL) { g_autoptr(XbNode) component = xb_silo_query_first_with_context(self->silo, self->query_container_checksum1, &context, NULL); if (component != NULL) return g_steal_pointer(&component); } if (self->query_container_checksum2 != NULL) { g_autoptr(XbNode) component = xb_silo_query_first_with_context(self->silo, self->query_container_checksum2, &context, NULL); if (component != NULL) return g_steal_pointer(&component); } /* failed */ return NULL; } /* does this exist in any enabled remote */ gchar * fu_engine_get_remote_id_for_blob(FuEngine *self, GBytes *blob) { GChecksumType checksum_types[] = {G_CHECKSUM_SHA256, G_CHECKSUM_SHA1, 0}; g_return_val_if_fail(FU_IS_ENGINE(self), NULL); g_return_val_if_fail(blob != NULL, NULL); for (guint i = 0; checksum_types[i] != 0; i++) { g_autofree gchar *csum = g_compute_checksum_for_bytes(checksum_types[i], blob); g_autoptr(XbNode) component = fu_engine_get_component_for_checksum(self, csum); if (component != NULL) { const gchar *remote_id = xb_node_query_text(component, "../custom/value[@key='fwupd::RemoteId']", NULL); if (remote_id != NULL) return g_strdup(remote_id); } } return NULL; } /** * fu_engine_unlock: * @self: a #FuEngine * @device_id: a device ID * @error: (nullable): optional return location for an error * * Unlocks a device. * * Returns: %TRUE for success **/ gboolean fu_engine_unlock(FuEngine *self, const gchar *device_id, GError **error) { FuPlugin *plugin; g_autoptr(FuDevice) device = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* check the device exists */ device = fu_device_list_get_by_id(self->device_list, device_id, error); if (device == NULL) return FALSE; /* get the plugin */ plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); if (plugin == NULL) return FALSE; /* run the correct plugin that added this */ if (!fu_plugin_runner_unlock(plugin, device, error)) return FALSE; /* make the UI update */ fu_engine_emit_device_changed_safe(self, device); fu_engine_emit_changed(self); return TRUE; } gboolean fu_engine_reset_config(FuEngine *self, const gchar *section, GError **error) { /* reset, effective next reboot */ return fu_config_reset_defaults(FU_CONFIG(self->config), section, error); } gboolean fu_engine_modify_config(FuEngine *self, const gchar *key, const gchar *value, GError **error) { g_auto(GStrv) section_key = NULL; const gchar *keys[] = {"ArchiveSizeMax", "AllowEmulation", "ApprovedFirmware", "BlockedFirmware", "DisabledDevices", "DisabledPlugins", "EnumerateAllDevices", "EspLocation", "HostBkc", "IdleTimeout", "IgnorePower", "OnlyTrusted", "P2pPolicy", "ReleaseDedupe", "ReleasePriority", "ShowDevicePrivate", "TestDevices", "TrustedReports", "TrustedUids", "UpdateMotd", "UriSchemes", "VerboseDomains", "test:AnotherWriteRequired", "test:CompositeChild", "test:DecompressDelay", "test:NeedsActivation", "test:NeedsReboot", "test:RegistrationSupported", "test:RequestDelay", "test:RequestSupported", "test:VerifyDelay", "test:WriteDelay", "test:WriteSupported", NULL}; g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(value != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* check keys are valid */ if (!g_strv_contains(keys, key)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "key %s not supported", key); return FALSE; } /* plugin specified */ section_key = g_strsplit(key, ":", 2); if (g_strv_length(section_key) == 2) { return fu_config_set_value(FU_CONFIG(self->config), section_key[0], section_key[1], value, error); } /* modify, effective next reboot */ return fu_config_set_value(FU_CONFIG(self->config), "fwupd", key, value, error); } /** * fu_engine_modify_remote: * @self: a #FuEngine * @remote_id: a remote ID * @key: the key, e.g. `Enabled` * @value: the key, e.g. `true` * @error: (nullable): optional return location for an error * * Updates the verification silo entry for a specific device. * * Returns: %TRUE for success **/ gboolean fu_engine_modify_remote(FuEngine *self, const gchar *remote_id, const gchar *key, const gchar *value, GError **error) { const gchar *keys[] = { "ApprovalRequired", "AutomaticReports", "AutomaticSecurityReports", "Enabled", "FirmwareBaseURI", "MetadataURI", "ReportURI", "SecurityReportURI", "Username", "Password", NULL, }; /* check keys are valid */ if (!g_strv_contains(keys, key)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "key %s not supported", key); return FALSE; } return fu_remote_list_set_key_value(self->remote_list, remote_id, key, value, error); } static gboolean fu_engine_modify_single_bios_setting(FuEngine *self, const gchar *key, const gchar *value, gboolean force_ro, GError **error) { FwupdBiosSetting *attr = fu_context_get_bios_setting(self->ctx, key); if (attr == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "attribute not found"); return FALSE; } if (!fwupd_bios_setting_write_value(attr, value, error)) return FALSE; if (force_ro) fwupd_bios_setting_set_read_only(attr, TRUE); return TRUE; } /** * fu_engine_modify_bios_settings: * @self: a #FuEngine * @settings: Hashtable of settings/values to configure * @force_ro: a #gboolean indicating if BIOS settings should also be made read-only * @error: (nullable): optional return location for an error * * Use the kernel API to set one or more BIOS settings. * * Returns: %TRUE for success **/ gboolean fu_engine_modify_bios_settings(FuEngine *self, GHashTable *settings, gboolean force_ro, GError **error) { g_autoptr(FuBiosSettings) bios_settings = fu_context_get_bios_settings(self->ctx); gboolean changed = FALSE; GHashTableIter iter; gpointer key, value; g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(settings != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); g_hash_table_iter_init(&iter, settings); while (g_hash_table_iter_next(&iter, &key, &value)) { g_autoptr(GError) error_local = NULL; if (value == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "attribute %s missing value", (const gchar *)key); return FALSE; } if (!fu_engine_modify_single_bios_setting(self, key, value, force_ro, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_debug("%s", error_local->message); continue; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } changed = TRUE; } if (!changed) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "no BIOS settings needed to be changed"); return FALSE; } if (!fu_bios_settings_get_pending_reboot(bios_settings, &changed, error)) return FALSE; g_info("pending_reboot is now %d", changed); return TRUE; } static void fu_engine_check_context_flag_save_events(FuEngine *self) { if (g_hash_table_size(self->emulation_backend_ids) > 0 && fu_engine_config_get_allow_emulation(self->config)) { fu_context_add_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS); } else { fu_context_remove_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS); } } static gboolean fu_engine_remove_device_flag(FuEngine *self, const gchar *device_id, FwupdDeviceFlags flag, GError **error) { FuDevice *proxy; g_autoptr(FuDevice) device = NULL; if (flag == FWUPD_DEVICE_FLAG_NOTIFIED) { device = fu_history_get_device_by_id(self->history, device_id, error); if (device == NULL) return FALSE; fu_device_remove_flag(device, flag); return fu_history_modify_device(self->history, device, error); } if (flag == FWUPD_DEVICE_FLAG_EMULATION_TAG) { device = fu_device_list_get_by_id(self->device_list, device_id, error); if (device == NULL) return FALSE; proxy = fu_device_get_proxy(device); if (proxy != NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device %s uses a proxy, remove the flag on %s instead", fu_device_get_id(device), fu_device_get_id(proxy)); return FALSE; } g_hash_table_remove(self->emulation_backend_ids, fu_device_get_backend_id(device)); return TRUE; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flag cannot be removed from client"); return FALSE; } static void fu_engine_emit_device_request_replug_and_install(FuEngine *self, FuDevice *device) { g_autoptr(FwupdRequest) request = fwupd_request_new(); fwupd_request_set_id(request, FWUPD_REQUEST_ID_REPLUG_INSTALL); fwupd_request_set_device_id(request, fu_device_get_id(device)); fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_IMMEDIATE); fwupd_request_add_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); fwupd_request_set_message(request, "Unplug and replug the device, then install the firmware."); g_signal_emit(self, signals[SIGNAL_DEVICE_REQUEST], 0, request); } static gboolean fu_engine_add_device_flag(FuEngine *self, const gchar *device_id, FwupdDeviceFlags flag, GError **error) { FuDevice *proxy; g_autoptr(FuDevice) device = NULL; if (flag == FWUPD_DEVICE_FLAG_REPORTED || flag == FWUPD_DEVICE_FLAG_NOTIFIED) { device = fu_history_get_device_by_id(self->history, device_id, error); if (device == NULL) return FALSE; fu_device_add_flag(device, flag); return fu_history_modify_device(self->history, device, error); } if (flag == FWUPD_DEVICE_FLAG_EMULATION_TAG) { device = fu_device_list_get_by_id(self->device_list, device_id, error); if (device == NULL) return FALSE; proxy = fu_device_get_proxy(device); if (proxy != NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device %s uses a proxy, set the flag on %s instead", fu_device_get_id(device), fu_device_get_id(proxy)); return FALSE; } g_hash_table_insert(self->emulation_backend_ids, g_strdup(fu_device_get_backend_id(device)), GUINT_TO_POINTER(1)); fu_engine_emit_device_request_replug_and_install(self, device); return TRUE; } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "flag cannot be added from client"); return FALSE; } static gboolean fu_engine_modify_device_flags(FuEngine *self, const gchar *device_id, const gchar *value, GError **error) { /* add or remove a subset of device flags */ if (g_str_has_prefix(value, "~")) { return fu_engine_remove_device_flag(self, device_id, fwupd_device_flag_from_string(value + 1), error); } return fu_engine_add_device_flag(self, device_id, fwupd_device_flag_from_string(value), error); } /** * fu_engine_modify_device: * @self: a #FuEngine * @device_id: a device ID * @key: the key, e.g. `Flags` * @value: the key, e.g. `reported` * @error: (nullable): optional return location for an error * * Sets the reported flag for a specific device. This ensures that other * front-end clients for fwupd do not report the same event. * * Returns: %TRUE for success **/ gboolean fu_engine_modify_device(FuEngine *self, const gchar *device_id, const gchar *key, const gchar *value, GError **error) { g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(value != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (g_strcmp0(key, "Flags") == 0) return fu_engine_modify_device_flags(self, device_id, value, error); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "key %s not supported", key); return FALSE; } static const gchar * fu_engine_checksum_type_to_string(GChecksumType checksum_type) { if (checksum_type == G_CHECKSUM_SHA1) return "sha1"; if (checksum_type == G_CHECKSUM_SHA256) return "sha256"; if (checksum_type == G_CHECKSUM_SHA512) return "sha512"; return "sha1"; } /** * fu_engine_verify_update: * @self: a #FuEngine * @device_id: a device ID * @error: (nullable): optional return location for an error * * Updates the verification silo entry for a specific device. * * Returns: %TRUE for success **/ gboolean fu_engine_verify_update(FuEngine *self, const gchar *device_id, FuProgress *progress, GError **error) { FuPlugin *plugin; GPtrArray *checksums; GPtrArray *guids; g_autofree gchar *fn = NULL; g_autofree gchar *localstatedir = NULL; g_autoptr(FuDevice) device = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderNode) component = NULL; g_autoptr(XbBuilderNode) provides = NULL; g_autoptr(XbBuilderNode) release = NULL; g_autoptr(XbBuilderNode) releases = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuDeviceProgress) device_progress = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* check the devices still exists */ device = fu_device_list_get_by_id(self->device_list, device_id, error); if (device == NULL) return FALSE; device_progress = fu_device_progress_new(device, progress); g_assert(device_progress != NULL); /* get the plugin */ plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); if (plugin == NULL) return FALSE; /* get the checksum */ checksums = fu_device_get_checksums(device); if (checksums->len == 0) { if (!fu_plugin_runner_verify(plugin, device, progress, FU_PLUGIN_VERIFY_FLAG_NONE, error)) return FALSE; fu_engine_emit_device_changed_safe(self, device); } /* we got nothing */ if (checksums->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device verification not supported"); return FALSE; } /* build XML */ component = xb_builder_node_insert(NULL, "component", "type", "firmware", NULL); provides = xb_builder_node_insert(component, "provides", NULL); guids = fu_device_get_guids(device); for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index(guids, i); g_autoptr(XbBuilderNode) provide = NULL; provide = xb_builder_node_insert(provides, "firmware", "type", "flashed", NULL); xb_builder_node_set_text(provide, guid, -1); } releases = xb_builder_node_insert(component, "releases", NULL); release = xb_builder_node_insert(releases, "release", "version", fu_device_get_version(device), NULL); for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index(checksums, i); GChecksumType kind = fwupd_checksum_guess_kind(checksum); g_autoptr(XbBuilderNode) csum = NULL; csum = xb_builder_node_insert(release, "checksum", "type", fu_engine_checksum_type_to_string(kind), "target", "content", NULL); xb_builder_node_set_text(csum, checksum, -1); } xb_builder_import_node(builder, component); /* save silo */ localstatedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); fn = g_strdup_printf("%s/verify/%s.xml", localstatedir, device_id); if (!fu_path_mkdir_parent(fn, error)) return FALSE; file = g_file_new_for_path(fn); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; if (!xb_silo_export_file(silo, file, XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE, NULL, error)) return FALSE; /* success */ return TRUE; } static XbNode * fu_engine_get_component_by_guid(FuEngine *self, const gchar *guid) { g_autoptr(GError) error_local = NULL; g_autoptr(XbNode) component = NULL; g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); /* no components in silo */ if (self->query_component_by_guid == NULL) return NULL; xb_query_context_set_flags(&context, XB_QUERY_FLAG_USE_INDEXES); xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, guid, NULL); component = xb_silo_query_first_with_context(self->silo, self->query_component_by_guid, &context, &error_local); if (component == NULL) { if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && !g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) g_warning("ignoring: %s", error_local->message); return NULL; } return g_object_ref(component); } XbNode * fu_engine_get_component_by_guids(FuEngine *self, FuDevice *device) { GPtrArray *guids = fu_device_get_guids(device); XbNode *component = NULL; for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index(guids, i); component = fu_engine_get_component_by_guid(self, guid); if (component != NULL) break; } return component; } static XbNode * fu_engine_verify_from_local_metadata(FuEngine *self, FuDevice *device, GError **error) { g_autofree gchar *fn = NULL; g_autofree gchar *localstatedir = NULL; g_autofree gchar *xpath = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbNode) release = NULL; g_autoptr(XbSilo) silo = NULL; localstatedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); fn = g_strdup_printf("%s/verify/%s.xml", localstatedir, fu_device_get_id(device)); file = g_file_new_for_path(fn); if (!g_file_query_exists(file, NULL)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find %s", fn); return NULL; } if (!xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, error)) return NULL; xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return NULL; xpath = g_strdup_printf("component/releases/release[@version='%s']", fu_device_get_version(device)); release = xb_silo_query_first(silo, xpath, error); if (release == NULL) return NULL; /* silo has to have same lifecycle as node */ g_object_set_data_full(G_OBJECT(release), "XbSilo", g_steal_pointer(&silo), (GDestroyNotify)g_object_unref); return g_steal_pointer(&release); } static XbNode * fu_engine_verify_from_system_metadata(FuEngine *self, FuDevice *device, GError **error) { FwupdVersionFormat fmt = fu_device_get_version_format(device); GPtrArray *guids = fu_device_get_guids(device); g_autoptr(XbQuery) query = NULL; /* prepare query with bound GUID parameter */ query = xb_query_new_full(self->silo, "components/component[@type='firmware']/" "provides/firmware[@type='flashed'][text()=?]/" "../../releases/release", XB_QUERY_FLAG_OPTIMIZE | XB_QUERY_FLAG_USE_INDEXES, error); if (query == NULL) return NULL; /* use prepared query for each GUID */ for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index(guids, i); g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) releases = NULL; g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); /* bind GUID and then query */ xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, guid, NULL); releases = xb_silo_query_with_context(self->silo, query, &context, &error_local); if (releases == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) || g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { g_debug("could not find %s: %s", guid, error_local->message); continue; } g_propagate_error(error, g_steal_pointer(&error_local)); return NULL; } for (guint j = 0; j < releases->len; j++) { XbNode *rel = g_ptr_array_index(releases, j); const gchar *rel_ver = xb_node_get_attr(rel, "version"); g_autofree gchar *tmp_ver = fu_version_parse_from_format(rel_ver, fmt); if (fu_version_compare(tmp_ver, fu_device_get_version(device), fmt) == 0) return g_object_ref(rel); } } /* not found */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "failed to find release"); return NULL; } /** * fu_engine_verify: * @self: a #FuEngine * @device_id: a device ID * @error: (nullable): optional return location for an error * * Verifies a device firmware checksum using the verification silo entry. * * Returns: %TRUE for success **/ gboolean fu_engine_verify(FuEngine *self, const gchar *device_id, FuProgress *progress, GError **error) { FuPlugin *plugin; GPtrArray *checksums; g_autoptr(FuDevice) device = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GString) xpath_csum = g_string_new(NULL); g_autoptr(XbNode) csum = NULL; g_autoptr(XbNode) release = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* check the id exists */ device = fu_device_list_get_by_id(self->device_list, device_id, error); if (device == NULL) return FALSE; /* get the plugin */ plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); if (plugin == NULL) return FALSE; /* update the device firmware hashes if possible */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE)) { if (!fu_plugin_runner_verify(plugin, device, progress, FU_PLUGIN_VERIFY_FLAG_NONE, error)) return FALSE; } /* find component in local metadata */ release = fu_engine_verify_from_local_metadata(self, device, &error_local); if (release == NULL) { if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && !g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } /* try again with the system metadata */ if (release == NULL) { g_autoptr(GError) error_system = NULL; release = fu_engine_verify_from_system_metadata(self, device, &error_system); if (release == NULL) { if (!g_error_matches(error_system, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && !g_error_matches(error_system, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { g_propagate_error(error, g_steal_pointer(&error_system)); return FALSE; } } } if (release == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No release found for version %s", fu_device_get_version(device)); return FALSE; } /* get the matching checksum */ checksums = fu_device_get_checksums(device); if (checksums->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No device checksums for %s", fu_device_get_version(device)); return FALSE; } /* do any of the checksums in the release match any in the device */ for (guint j = 0; j < checksums->len; j++) { const gchar *hash_tmp = g_ptr_array_index(checksums, j); xb_string_append_union(xpath_csum, "checksum[@target='device'][text()='%s']", hash_tmp); xb_string_append_union(xpath_csum, "checksum[@target='content'][text()='%s']", hash_tmp); } csum = xb_node_query_first(release, xpath_csum->str, NULL); if (csum == NULL) { g_autofree gchar *checksums_device = fu_strjoin("|", checksums); g_autoptr(GString) checksums_metadata = g_string_new(NULL); g_autoptr(GPtrArray) csums = NULL; g_autoptr(GString) xpath = g_string_new(NULL); /* get all checksums to display a useful error */ xb_string_append_union(xpath, "checksum[@target='device']"); if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE)) xb_string_append_union(xpath, "checksum[@target='content']"); csums = xb_node_query(release, xpath->str, 0, NULL); if (csums == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No stored checksums for %s", fu_device_get_version(device)); return FALSE; } for (guint i = 0; i < csums->len; i++) { XbNode *csum_tmp = g_ptr_array_index(csums, i); xb_string_append_union(checksums_metadata, "%s", xb_node_get_text(csum_tmp)); } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "For %s %s expected %s, got %s", fu_device_get_name(device), fu_device_get_version(device), checksums_metadata->str, checksums_device); return FALSE; } /* success */ return TRUE; } gboolean fu_engine_check_trust(FuEngine *self, FuRelease *release, GError **error) { g_autofree gchar *str = fu_release_to_string(release); g_debug("checking trust of %s", str); if (fu_engine_config_get_only_trusted(self->config) && !fu_release_has_flag(release, FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD)) { g_autofree gchar *sysconfdir = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR_PKG); g_autofree gchar *fn = g_build_filename(sysconfdir, "fwupd.conf", NULL); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "firmware signature missing or not trusted; " "set OnlyTrusted=false in %s ONLY if you are a firmware developer", fn); return FALSE; } return TRUE; } void fu_engine_idle_reset(FuEngine *self) { fu_idle_reset(self->idle); } guint32 fu_engine_idle_inhibit(FuEngine *self, FuIdleInhibit inhibit, const gchar *reason) { return fu_idle_inhibit(self->idle, inhibit, reason); } void fu_engine_idle_uninhibit(FuEngine *self, guint32 token) { fu_idle_uninhibit(self->idle, token); } static gchar * fu_engine_get_boot_time(void) { g_autofree gchar *buf = NULL; g_auto(GStrv) lines = NULL; if (!g_file_get_contents("/proc/stat", &buf, NULL, NULL)) return NULL; lines = g_strsplit(buf, "\n", -1); for (guint i = 0; lines[i] != NULL; i++) { if (g_str_has_prefix(lines[i], "btime ")) return g_strdup(lines[i] + 6); } return NULL; } static FuDevice * fu_engine_get_cpu_device(FuEngine *self) { g_autoptr(GPtrArray) devices = fu_device_list_get_active(self->device_list); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_HOST_CPU)) return g_object_ref(device); } return NULL; } static void fu_engine_get_report_metadata_cpu_device(FuEngine *self, GHashTable *hash) { g_autoptr(FuDevice) device = NULL; device = fu_engine_get_cpu_device(self); if (device == NULL) { g_info("failed to find CPU device"); return; } if (fu_device_get_vendor(device) == NULL || fu_device_get_name(device) == NULL) { g_info("not enough data to include CpuModel"); return; } g_hash_table_insert( hash, g_strdup("CpuModel"), g_strdup_printf("%s %s", fu_device_get_vendor(device), fu_device_get_name(device))); } static gboolean fu_engine_get_report_metadata_os_release(GHashTable *hash, GError **error) { g_autoptr(GHashTable) os_release = NULL; struct { const gchar *key; const gchar *val; } distro_kv[] = {{"ID", FWUPD_RESULT_KEY_DISTRO_ID}, {"NAME", "DistroName"}, {"PRETTY_NAME", "DistroPrettyName"}, {"VERSION_ID", FWUPD_RESULT_KEY_DISTRO_VERSION}, {"VARIANT_ID", FWUPD_RESULT_KEY_DISTRO_VARIANT}, {NULL, NULL}}; /* get all required os-release keys */ os_release = fwupd_get_os_release(error); if (os_release == NULL) return FALSE; for (guint i = 0; distro_kv[i].key != NULL; i++) { const gchar *tmp = g_hash_table_lookup(os_release, distro_kv[i].key); if (tmp != NULL) { g_hash_table_insert(hash, g_strdup(distro_kv[i].val), g_strdup(tmp)); } } return TRUE; } static gboolean fu_engine_get_report_metadata_lsb_release(GHashTable *hash, GError **error) { const gchar *fn = "/etc/lsb-release"; g_autoptr(GHashTable) os_release = NULL; struct { const gchar *key; const gchar *val; } distro_kv[] = {{"CHROMEOS_RELEASE_TRACK", "DistroReleaseTrack"}, {"CHROMEOS_RELEASE_BOARD", "DistroReleaseBoard"}, {NULL, NULL}}; if (!g_file_test(fn, G_FILE_TEST_EXISTS)) return TRUE; os_release = fwupd_get_os_release_full(fn, error); if (os_release == NULL) return FALSE; for (guint i = 0; distro_kv[i].key != NULL; i++) { const gchar *tmp = g_hash_table_lookup(os_release, distro_kv[i].key); if (tmp != NULL) g_hash_table_insert(hash, g_strdup(distro_kv[i].val), g_strdup(tmp)); } return TRUE; } static gboolean fu_engine_get_report_metadata_kernel_cmdline(GHashTable *hash, GError **error) { g_autofree gchar *cmdline = NULL; cmdline = fu_common_get_kernel_cmdline(error); if (cmdline == NULL) return FALSE; if (cmdline[0] != '\0') g_hash_table_insert(hash, g_strdup("KernelCmdline"), g_steal_pointer(&cmdline)); return TRUE; } static void fu_engine_add_report_metadata_bool(GHashTable *hash, const gchar *key, gboolean value) { g_hash_table_insert(hash, g_strdup(key), g_strdup(value ? "True" : "False")); } GHashTable * fu_engine_get_report_metadata(FuEngine *self, GError **error) { GHashTable *compile_versions = fu_context_get_compile_versions(self->ctx); GHashTable *runtime_versions = fu_context_get_runtime_versions(self->ctx); const gchar *tmp; gchar *btime; #ifdef HAVE_UTSNAME_H struct utsname name_tmp; #endif g_autoptr(GHashTable) hash = NULL; g_autoptr(GList) compile_keys = g_hash_table_get_keys(compile_versions); g_autoptr(GList) runtime_keys = g_hash_table_get_keys(runtime_versions); /* convert all the runtime and compile-time versions */ hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); for (GList *l = compile_keys; l != NULL; l = l->next) { const gchar *id = l->data; const gchar *version = g_hash_table_lookup(compile_versions, id); g_hash_table_insert(hash, g_strdup_printf("CompileVersion(%s)", id), g_strdup(version)); } for (GList *l = runtime_keys; l != NULL; l = l->next) { const gchar *id = l->data; const gchar *version = g_hash_table_lookup(runtime_versions, id); g_hash_table_insert(hash, g_strdup_printf("RuntimeVersion(%s)", id), g_strdup(version)); } fu_engine_get_report_metadata_cpu_device(self, hash); if (!fu_engine_get_report_metadata_os_release(hash, error)) return NULL; if (!fu_engine_get_report_metadata_lsb_release(hash, error)) return NULL; if (!fu_engine_get_report_metadata_kernel_cmdline(hash, error)) return NULL; /* these affect the report credibility */ #ifdef SUPPORTED_BUILD fu_engine_add_report_metadata_bool(hash, "FwupdSupported", TRUE); #else fu_engine_add_report_metadata_bool(hash, "FwupdSupported", FALSE); #endif /* find out what BKC is being targeted to understand "odd" upgrade paths */ tmp = fu_engine_config_get_host_bkc(self->config); if (tmp != NULL) g_hash_table_insert(hash, g_strdup("HostBkc"), g_strdup(tmp)); /* DMI data */ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_LOADED_HWINFO)) { struct { const gchar *hwid; const gchar *name; } keys[] = {{FU_HWIDS_KEY_BASEBOARD_MANUFACTURER, "HostBaseboardManufacturer"}, {FU_HWIDS_KEY_BASEBOARD_PRODUCT, "HostBaseboardProduct"}, {FU_HWIDS_KEY_BIOS_MAJOR_RELEASE, "HostBiosMajorRelease"}, {FU_HWIDS_KEY_BIOS_MINOR_RELEASE, "HostBiosMinorRelease"}, {FU_HWIDS_KEY_BIOS_VENDOR, "HostBiosVendor"}, {FU_HWIDS_KEY_BIOS_VERSION, "HostBiosVersion"}, {FU_HWIDS_KEY_FIRMWARE_MAJOR_RELEASE, "HostFirmwareMajorRelease"}, {FU_HWIDS_KEY_FIRMWARE_MINOR_RELEASE, "HostFirmwareMinorRelease"}, {FU_HWIDS_KEY_ENCLOSURE_KIND, "HostEnclosureKind"}, {FU_HWIDS_KEY_FAMILY, "HostFamily"}, {FU_HWIDS_KEY_MANUFACTURER, "HostVendor"}, {FU_HWIDS_KEY_PRODUCT_NAME, "HostProduct"}, {FU_HWIDS_KEY_PRODUCT_SKU, "HostSku"}, {NULL, NULL}}; for (guint i = 0; keys[i].hwid != NULL; i++) { tmp = fu_context_get_hwid_value(self->ctx, keys[i].hwid); if (tmp != NULL) g_hash_table_insert(hash, g_strdup(keys[i].name), g_strdup(tmp)); } } /* kernel version is often important for debugging failures */ #ifdef HAVE_UTSNAME_H memset(&name_tmp, 0, sizeof(struct utsname)); if (uname(&name_tmp) >= 0) { g_hash_table_insert(hash, g_strdup("CpuArchitecture"), g_strdup(name_tmp.machine)); g_hash_table_insert(hash, g_strdup("KernelName"), g_strdup(name_tmp.sysname)); g_hash_table_insert(hash, g_strdup("KernelVersion"), g_strdup(name_tmp.release)); } #endif /* add the kernel boot time so we can detect a reboot */ btime = fu_engine_get_boot_time(); if (btime != NULL) g_hash_table_insert(hash, g_strdup("BootTime"), btime); /* add context information */ g_hash_table_insert( hash, g_strdup("PowerState"), g_strdup(fu_power_state_to_string(fu_context_get_power_state(self->ctx)))); g_hash_table_insert( hash, g_strdup("DisplayState"), g_strdup(fu_display_state_to_string(fu_context_get_display_state(self->ctx)))); g_hash_table_insert(hash, g_strdup("LidState"), g_strdup(fu_lid_state_to_string(fu_context_get_lid_state(self->ctx)))); g_hash_table_insert(hash, g_strdup("BatteryLevel"), g_strdup_printf("%u", fu_context_get_battery_level(self->ctx))); g_hash_table_insert(hash, g_strdup("BatteryThreshold"), g_strdup_printf("%u", fu_context_get_battery_threshold(self->ctx))); return g_steal_pointer(&hash); } /** * fu_engine_composite_prepare: * @self: a #FuEngine * @devices: (element-type #FuDevice): devices that will be updated * @error: (nullable): optional return location for an error * * Calls into the plugin loader, informing each plugin of the pending upgrade(s). * * Any failure in any plugin will abort all of the actions before they are started. * * Returns: %TRUE for success **/ gboolean fu_engine_composite_prepare(FuEngine *self, GPtrArray *devices, GError **error) { GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); gboolean any_emulated = FALSE; /* we are emulating a device */ for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) any_emulated = TRUE; } if (any_emulated) { if (!fu_engine_emulation_load_phase(self, error)) return FALSE; } for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); if (!fu_plugin_runner_composite_prepare(plugin_tmp, devices, error)) return FALSE; } /* save to emulated phase */ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && !any_emulated) { if (!fu_engine_backends_save_phase(self, error)) return FALSE; } /* wait for any device to disconnect and reconnect */ if (!fu_device_list_wait_for_replug(self->device_list, error)) { g_prefix_error(error, "failed to wait for composite prepare: "); return FALSE; } /* success */ return TRUE; } /** * fu_engine_composite_cleanup: * @self: a #FuEngine * @devices: (element-type #FuDevice): devices that will be updated * @error: (nullable): optional return location for an error * * Calls into the plugin loader, informing each plugin of the pending upgrade(s). * * Returns: %TRUE for success **/ gboolean fu_engine_composite_cleanup(FuEngine *self, GPtrArray *devices, GError **error) { GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); gboolean any_emulated = FALSE; /* we are emulating a device */ for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) any_emulated = TRUE; } if (any_emulated) { if (!fu_engine_emulation_load_phase(self, error)) return FALSE; } for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); if (!fu_plugin_runner_composite_cleanup(plugin_tmp, devices, error)) return FALSE; } /* save to emulated phase */ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && !any_emulated) { if (!fu_engine_backends_save_phase(self, error)) return FALSE; } /* wait for any device to disconnect and reconnect */ if (!fu_device_list_wait_for_replug(self->device_list, error)) { g_prefix_error(error, "failed to wait for composite cleanup: "); return FALSE; } /* success */ return TRUE; } static gint fu_engine_sort_release_device_order_release_version_cb(gconstpointer a, gconstpointer b) { FuRelease *na = *((FuRelease **)a); FuRelease *nb = *((FuRelease **)b); return fu_release_compare(na, nb); } static gboolean fu_engine_install_release_version_check(FuEngine *self, FuRelease *release, FuDevice *device, GError **error) { FwupdVersionFormat fmt = fu_device_get_version_format(device); const gchar *version_rel = fu_release_get_version(release); const gchar *version_old = fu_release_get_device_version_old(release); if (version_rel != NULL && fu_version_compare(version_old, version_rel, fmt) != 0 && fu_version_compare(version_old, fu_device_get_version(device), fmt) == 0 && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { fu_device_set_update_state(device, FWUPD_UPDATE_STATE_FAILED); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "device version not updated on success, %s != %s", version_rel, fu_device_get_version(device)); return FALSE; } /* success */ return TRUE; } /** * fu_engine_install_releases: * @self: a #FuEngine * @request: a #FuEngineRequest * @releases: (element-type FuRelease): a device * @blob_cab: the #GBytes of the .cab file * @flags: install flags, e.g. %FWUPD_DEVICE_FLAG_UPDATABLE * @error: (nullable): optional return location for an error * * Installs a specific firmware file on one or more install tasks. * * By this point all the requirements and tests should have been done in * fu_engine_requirements_check() so this should not fail before running * the plugin loader. * * Returns: %TRUE for success **/ gboolean fu_engine_install_releases(FuEngine *self, FuEngineRequest *request, GPtrArray *releases, GBytes *blob_cab, FuProgress *progress, FwupdInstallFlags flags, GError **error) { g_autoptr(FuIdleLocker) locker = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices_new = NULL; /* do not allow auto-shutdown during this time */ locker = fu_idle_locker_new(self->idle, FU_IDLE_INHIBIT_TIMEOUT | FU_IDLE_INHIBIT_SIGNALS, "update"); g_assert(locker != NULL); /* use an allow-list for device-changed signals -- only allow any of the composite update * devices to emit signals for the duration of the install */ for (guint i = 0; i < releases->len; i++) { FuRelease *release = g_ptr_array_index(releases, i); FuDevice *device = fu_release_get_device(release); g_hash_table_insert(self->device_changed_allowlist, g_strdup(fu_device_get_id(device)), GUINT_TO_POINTER(1)); } /* install these in the right order */ g_ptr_array_sort(releases, fu_engine_sort_release_device_order_release_version_cb); /* notify the plugins about the composite action */ devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < releases->len; i++) { FuRelease *release = g_ptr_array_index(releases, i); FuDevice *device = fu_release_get_device(release); const gchar *logical_id = fu_device_get_logical_id(device); g_info("composite update %u: %s %s->%s (%s, order:%i: priority:%u)", i + 1, fu_device_get_id(device), fu_device_get_version(device), fu_release_get_version(release), logical_id != NULL ? logical_id : "n/a", fu_device_get_order(device), (guint)fu_release_get_priority(release)); g_ptr_array_add(devices, g_object_ref(device)); } fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_COMPOSITE_PREPARE); if (!fu_engine_composite_prepare(self, devices, error)) { g_prefix_error(error, "failed to prepare composite action: "); return FALSE; } /* all authenticated, so install all the things */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, releases->len); for (guint i = 0; i < releases->len; i++) { FuRelease *release = g_ptr_array_index(releases, i); if (!fu_engine_install_release(self, release, blob_cab, fu_progress_get_child(progress), flags, error)) { g_autoptr(GError) error_local = NULL; if (!fu_engine_composite_cleanup(self, devices, &error_local)) { g_warning("failed to cleanup failed composite action: %s", error_local->message); } return FALSE; } fu_progress_step_done(progress); } /* set all the device statuses back to unknown */ for (guint i = 0; i < releases->len; i++) { FuRelease *release = g_ptr_array_index(releases, i); FuDevice *device = fu_release_get_device(release); fwupd_device_set_status(FWUPD_DEVICE(device), FWUPD_STATUS_UNKNOWN); } /* get a new list of devices in case they replugged */ devices_new = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < devices->len; i++) { FuDevice *device; g_autoptr(FuDevice) device_new = NULL; g_autoptr(GError) error_local = NULL; device = g_ptr_array_index(devices, i); device_new = fu_device_list_get_by_id(self->device_list, fu_device_get_id(device), &error_local); if (device_new == NULL) { g_info("failed to find new device: %s", error_local->message); continue; } g_ptr_array_add(devices_new, g_steal_pointer(&device_new)); } /* notify the plugins about the composite action */ fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_COMPOSITE_CLEANUP); if (!fu_engine_composite_cleanup(self, devices_new, error)) { g_prefix_error(error, "failed to cleanup composite action: "); return FALSE; } /* for online updates, verify the version changed if not a re-install */ for (guint i = 0; i < releases->len; i++) { FuRelease *release = g_ptr_array_index(releases, i); FuDevice *device = fu_release_get_device(release); g_autoptr(FuDevice) device_new = NULL; g_autoptr(GError) error_local = NULL; device_new = fu_device_list_get_by_id(self->device_list, fu_device_get_id(device), &error_local); if (device_new == NULL) { g_info("failed to find new device: %s", error_local->message); continue; } if (!fu_engine_install_release_version_check(self, release, device_new, error)) return FALSE; } /* allow capturing setup again */ fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_SETUP); /* make the UI update */ fu_engine_emit_changed(self); return TRUE; } static void fu_engine_update_release_integrity(FuEngine *self, FwupdRelease *release, const gchar *key) { g_autoptr(GHashTable) integrity = fu_engine_integrity_new(NULL); if (integrity != NULL) { g_autofree gchar *str = fu_engine_integrity_to_string(integrity); fwupd_release_add_metadata_item(FWUPD_RELEASE(release), key, str); } } static gboolean fu_engine_add_release_metadata(FuEngine *self, FuRelease *release, GError **error) { g_autoptr(GHashTable) metadata_device = NULL; g_autoptr(GHashTable) metadata_hash = NULL; /* build the version metadata */ metadata_hash = fu_engine_get_report_metadata(self, error); if (metadata_hash == NULL) return FALSE; fu_release_add_metadata(release, metadata_hash); metadata_device = fu_device_report_metadata_pre(fu_release_get_device(release)); if (metadata_device != NULL) fu_release_add_metadata(release, metadata_device); return TRUE; } static gboolean fu_engine_add_release_plugin_metadata(FuEngine *self, FuRelease *release, FuPlugin *plugin, GError **error) { GPtrArray *metadata_sources; /* build the version metadata */ if (fu_plugin_get_report_metadata(plugin) != NULL) fu_release_add_metadata(release, fu_plugin_get_report_metadata(plugin)); /* allow other plugins to contribute metadata too */ metadata_sources = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_METADATA_SOURCE); if (metadata_sources != NULL) { for (guint i = 0; i < metadata_sources->len; i++) { FuPlugin *plugin_tmp; const gchar *plugin_name = g_ptr_array_index(metadata_sources, i); g_autoptr(GError) error_local = NULL; plugin_tmp = fu_plugin_list_find_by_name(self->plugin_list, plugin_name, &error_local); if (plugin_tmp == NULL) { g_debug("could not add metadata for %s: %s", plugin_name, error_local->message); continue; } if (fu_plugin_get_report_metadata(plugin_tmp) != NULL) { fwupd_release_add_metadata( FWUPD_RELEASE(release), fu_plugin_get_report_metadata(plugin_tmp)); } } } /* measure the "old" system state */ if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY)) { fu_engine_update_release_integrity(self, FWUPD_RELEASE(release), "SystemIntegrityOld"); } return TRUE; } static gboolean fu_engine_is_running_offline(FuEngine *self) { #ifdef HAVE_SYSTEMD g_autofree gchar *default_target = NULL; g_autoptr(GError) error = NULL; default_target = fu_systemd_get_default_target(&error); if (default_target == NULL) { g_warning("failed to get default.target: %s", error->message); return FALSE; } return g_strcmp0(default_target, "system-update.target") == 0; #else return FALSE; #endif } #ifdef HAVE_GIO_UNIX static gchar * fu_realpath(const gchar *filename, GError **error) { char full_tmp[PATH_MAX]; g_return_val_if_fail(filename != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); #ifdef HAVE_REALPATH if (realpath(filename, full_tmp) == NULL) { #else if (_fullpath(full_tmp, filename, sizeof(full_tmp)) == NULL) { #endif g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot resolve path: %s", g_strerror(errno)); return NULL; } if (!g_file_test(full_tmp, G_FILE_TEST_EXISTS)) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "cannot find path: %s", full_tmp); return NULL; } return g_strdup(full_tmp); } #endif static gboolean fu_engine_offline_setup(GError **error) { #ifdef HAVE_GIO_UNIX gint rc; g_autofree gchar *filename = NULL; g_autofree gchar *symlink_target = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); g_autofree gchar *trigger = fu_path_from_kind(FU_PATH_KIND_OFFLINE_TRIGGER); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* does already exist */ filename = fu_realpath(trigger, NULL); if (g_strcmp0(filename, symlink_target) == 0) { g_info("%s already points to %s, skipping creation", trigger, symlink_target); return TRUE; } /* create symlink for the systemd-system-update-generator */ rc = symlink(symlink_target, trigger); if (rc < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to create symlink %s to %s: %s", trigger, symlink_target, g_strerror(errno)); return FALSE; } return TRUE; #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as not available"); return FALSE; #endif } static gboolean fu_engine_offline_invalidate(GError **error) { g_autofree gchar *trigger = fu_path_from_kind(FU_PATH_KIND_OFFLINE_TRIGGER); g_autoptr(GError) error_local = NULL; g_autoptr(GFile) file1 = NULL; g_return_val_if_fail(error == NULL || *error == NULL, FALSE); file1 = g_file_new_for_path(trigger); if (!g_file_query_exists(file1, NULL)) return TRUE; if (!g_file_delete(file1, NULL, &error_local)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot delete %s: %s", trigger, error_local->message); return FALSE; } return TRUE; } /** * fu_engine_schedule_update: * @self: a #FuEngine * @device: a device * @release: a release * @blob_cab: a data blob * @flags: install flags * @error: (nullable): optional return location for an error * * Schedule an offline update for the device * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.3.5 **/ gboolean fu_engine_schedule_update(FuEngine *self, FuDevice *device, FwupdRelease *release, GBytes *blob_cab, FwupdInstallFlags flags, GError **error) { gchar tmpname[] = {"XXXXXX.cab"}; g_autofree gchar *dirname = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuHistory) history = NULL; g_autoptr(GFile) file = NULL; #ifndef HAVE_FWUPDOFFLINE /* sanity check */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as compiled without offline support"); return FALSE; #endif /* id already exists */ history = fu_history_new(); if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_autoptr(FuDevice) res_tmp = NULL; res_tmp = fu_history_get_device_by_id(history, fu_device_get_id(device), NULL); if (res_tmp != NULL && fu_device_get_update_state(res_tmp) == FWUPD_UPDATE_STATE_PENDING) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_ALREADY_PENDING, "%s is already scheduled to be updated", fu_device_get_id(device)); return FALSE; } } /* create directory */ dirname = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); file = g_file_new_for_path(dirname); if (!g_file_query_exists(file, NULL)) { if (!g_file_make_directory_with_parents(file, NULL, error)) return FALSE; } /* get a random filename */ for (guint i = 0; i < 6; i++) tmpname[i] = (gchar)g_random_int_range('A', 'Z'); filename = g_build_filename(dirname, tmpname, NULL); /* just copy to the temp file */ if (!g_file_set_contents(filename, g_bytes_get_data(blob_cab, NULL), (gssize)g_bytes_get_size(blob_cab), error)) return FALSE; /* schedule for next boot */ g_info("schedule %s to be installed to %s on next boot", filename, fu_device_get_id(device)); fwupd_release_set_filename(release, filename); /* add to database */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); fu_device_set_update_state(device, FWUPD_UPDATE_STATE_PENDING); if (!fu_history_add_device(history, device, release, error)) return FALSE; /* next boot we run offline */ return fu_engine_offline_setup(error); } static gboolean fu_engine_save_into_backup_remote(FuEngine *self, GBytes *fw, GError **error) { FwupdRemote *remote_tmp = fu_remote_list_get_by_id(self->remote_list, "backup"); g_autofree gchar *localstatepkg = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); g_autofree gchar *backupdir = g_build_filename(localstatepkg, "backup", NULL); g_autofree gchar *backupdir_uri = g_strdup_printf("file://%s", backupdir); g_autofree gchar *remotes_path = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_REMOTES); g_autofree gchar *remotes_fn = g_build_filename(remotes_path, "backup.conf", NULL); g_autofree gchar *archive_checksum = g_compute_checksum_for_bytes(G_CHECKSUM_SHA256, fw); g_autofree gchar *archive_basename = g_strdup_printf("%s.cab", archive_checksum); g_autofree gchar *archive_fn = g_build_filename(backupdir, archive_basename, NULL); g_autoptr(FwupdRemote) remote = fwupd_remote_new(); /* save archive if required */ if (!g_file_test(archive_fn, G_FILE_TEST_EXISTS)) { g_info("saving archive to %s", archive_fn); if (!fu_bytes_set_contents(archive_fn, fw, error)) return FALSE; } /* already exists as an enabled remote */ if (remote_tmp != NULL && fwupd_remote_has_flag(remote_tmp, FWUPD_REMOTE_FLAG_ENABLED)) return TRUE; /* just enable */ if (remote_tmp != NULL) { g_info("enabling remote %s", fwupd_remote_get_id(remote_tmp)); fwupd_remote_add_flag(remote_tmp, FWUPD_REMOTE_FLAG_ENABLED); return fwupd_remote_save_to_filename(remote_tmp, remotes_fn, NULL, error); } /* create a new remote we can use for re-installing */ g_info("creating new backup remote"); fwupd_remote_add_flag(remote, FWUPD_REMOTE_FLAG_ENABLED); fwupd_remote_set_keyring_kind(remote, FWUPD_KEYRING_KIND_NONE); fwupd_remote_set_title(remote, "Backup"); fwupd_remote_set_metadata_uri(remote, backupdir_uri); return fwupd_remote_save_to_filename(remote, remotes_fn, NULL, error); } /** * fu_engine_install_release: * @self: a #FuEngine * @release: a #FuRelease * @blob_cab: the #GBytes of the .cab file * @progress: a #FuProgress * @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_ALLOW_OLDER * @error: (nullable): optional return location for an error * * Installs a specific release on a device. * * By this point all the requirements and tests should have been done in * fu_engine_requirements_check() so this should not fail before running * the plugin loader. * * Returns: %TRUE for success **/ gboolean fu_engine_install_release(FuEngine *self, FuRelease *release, GBytes *blob_cab, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuDevice *device_orig = fu_release_get_device(release); FuEngineRequest *request = fu_release_get_request(release); FuPlugin *plugin; FwupdFeatureFlags feature_flags = FWUPD_FEATURE_FLAG_NONE; GBytes *blob_fw; const gchar *tmp; g_autoptr(FuDevice) device = NULL; g_autoptr(FuDevice) device_tmp = NULL; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(FU_IS_RELEASE(release), FALSE); g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE); g_return_val_if_fail(blob_cab != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* optional for tests */ if (request != NULL) feature_flags = fu_engine_request_get_feature_flags(request); /* add the checksum of the container blob if not already set */ if (fwupd_release_get_checksums(FWUPD_RELEASE(release))->len == 0) { GChecksumType checksum_types[] = {G_CHECKSUM_SHA256, G_CHECKSUM_SHA1, 0}; for (guint i = 0; checksum_types[i] != 0; i++) { g_autofree gchar *checksum = g_compute_checksum_for_bytes(checksum_types[i], blob_cab); fwupd_release_add_checksum(FWUPD_RELEASE(release), checksum); } } /* not in bootloader mode */ device = g_object_ref(fu_release_get_device(release)); if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { /* both optional; the plugin can specify a fallback */ tmp = fwupd_release_get_detach_caption(FWUPD_RELEASE(release)); if (tmp != NULL) fu_device_set_update_message(device, tmp); tmp = fwupd_release_get_detach_image(FWUPD_RELEASE(release)); if (tmp != NULL) fu_device_set_update_image(device, tmp); } /* save to persistent storage so that the device can recover without a network */ if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_SAVE_INTO_BACKUP_REMOTE)) { if (!fu_engine_save_into_backup_remote(self, blob_cab, error)) return FALSE; } /* schedule this for the next reboot if not in system-update.target, * but first check if allowed on battery power */ if ((flags & FWUPD_INSTALL_FLAG_OFFLINE) > 0 && !fu_engine_is_running_offline(self)) { FuPlugin *plugin_tmp = fu_plugin_list_find_by_name(self->plugin_list, "upower", NULL); if (!fu_engine_add_release_metadata(self, release, error)) return FALSE; if (plugin_tmp != NULL) { if (!fu_plugin_runner_prepare(plugin_tmp, device, progress, flags, error)) return FALSE; if (!fu_engine_add_release_plugin_metadata(self, release, plugin_tmp, error)) return FALSE; } fu_progress_set_status(progress, FWUPD_STATUS_SCHEDULING); return fu_engine_schedule_update(self, device, FWUPD_RELEASE(release), blob_cab, flags, error); } /* set this for the callback */ self->write_history = (flags & FWUPD_INSTALL_FLAG_NO_HISTORY) == 0; /* get per-release firmware blob */ blob_fw = fu_release_get_fw_blob(release); if (blob_fw == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to get firmware blob from release"); return FALSE; } /* get the plugin */ plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); if (plugin == NULL) return FALSE; /* add device to database */ if ((flags & FWUPD_INSTALL_FLAG_NO_HISTORY) == 0) { if (!fu_engine_add_release_metadata(self, release, error)) return FALSE; if (!fu_engine_add_release_plugin_metadata(self, release, plugin, error)) return FALSE; if (!fu_history_add_device(self->history, device, FWUPD_RELEASE(release), error)) return FALSE; } /* install firmware blob */ if (!fu_engine_install_blob(self, device, blob_fw, progress, flags, feature_flags, &error_local)) { FwupdUpdateState state = fu_device_get_update_state(device); if (state != FWUPD_UPDATE_STATE_FAILED && state != FWUPD_UPDATE_STATE_FAILED_TRANSIENT) fu_device_set_update_state(device_orig, FWUPD_UPDATE_STATE_FAILED); else fu_device_set_update_state(device_orig, state); fu_device_set_update_error(device_orig, error_local->message); g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* the device may have changed */ device_tmp = fu_device_list_get_by_id(self->device_list, fu_device_get_id(device), error); if (device_tmp == NULL) { g_prefix_error(error, "failed to get device after install: "); return FALSE; } g_set_object(&device, device_tmp); /* update state (which updates the database if required) */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT) || fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) { fu_device_set_update_state(device_orig, FWUPD_UPDATE_STATE_NEEDS_REBOOT); return TRUE; } /* mark success unless needs a reboot */ if (fu_device_get_update_state(device) != FWUPD_UPDATE_STATE_NEEDS_REBOOT) fu_device_set_update_state(device, FWUPD_UPDATE_STATE_SUCCESS); /* wait for the system to acquiesce if required */ if (fu_device_get_acquiesce_delay(device_orig) > 0 && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_BUSY); fu_engine_wait_for_acquiesce(self, fu_device_get_acquiesce_delay(device_orig)); } #ifdef HAVE_PASSIM /* send to passimd, if enabled and running */ if (passim_client_get_version(self->passim_client) != NULL && fu_engine_config_get_p2p_policy(self->config) & FU_P2P_POLICY_FIRMWARE) { g_autofree gchar *basename = g_path_get_basename(fu_release_get_filename(release)); g_autoptr(GError) error_passim = NULL; g_autoptr(PassimItem) passim_item = passim_item_new(); if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)) passim_item_add_flag(passim_item, PASSIM_ITEM_FLAG_NEXT_REBOOT); passim_item_set_max_age(passim_item, 30 * 24 * 60 * 60); passim_item_set_share_limit(passim_item, 50); passim_item_set_basename(passim_item, basename); passim_item_set_bytes(passim_item, blob_cab); if (!passim_client_publish(self->passim_client, passim_item, &error_passim)) { if (!g_error_matches(error_passim, G_IO_ERROR, G_IO_ERROR_EXISTS)) { g_warning("failed to publish firmware to Passim: %s", error_passim->message); } } else { g_debug("published %s to Passim", passim_item_get_hash(passim_item)); } } #endif /* success */ return TRUE; } /** * fu_engine_get_plugins: * @self: a #FuPluginList * * Gets all the plugins that have been added. * * Returns: (transfer none) (element-type FuPlugin): the plugins * * Since: 1.0.8 **/ GPtrArray * fu_engine_get_plugins(FuEngine *self) { g_return_val_if_fail(FU_IS_ENGINE(self), NULL); return fu_plugin_list_get_all(self->plugin_list); } /** * fu_engine_get_plugin_by_name: * @self: a #FuPluginList * @name: a plugin name, e.g. `dfu` * @error: (nullable): optional return location for an error * * Gets a specific plugin. * * Returns: (transfer none): a plugin, or %NULL * * Since: 1.9.6 **/ FuPlugin * fu_engine_get_plugin_by_name(FuEngine *self, const gchar *name, GError **error) { g_return_val_if_fail(FU_IS_ENGINE(self), NULL); return fu_plugin_list_find_by_name(self->plugin_list, name, error); } static gboolean fu_engine_emulation_load_json(FuEngine *self, const gchar *json, GError **error) { JsonNode *root; g_autoptr(JsonParser) parser = json_parser_new(); /* parse */ if (!json_parser_load_from_data(parser, json, -1, error)) return FALSE; root = json_parser_get_root(parser); /* load into all backends */ for (guint i = 0; i < self->backends->len; i++) { FuBackend *backend = g_ptr_array_index(self->backends, i); if (!fu_backend_load(backend, json_node_get_object(root), FU_USB_DEVICE_EMULATION_TAG, FU_BACKEND_LOAD_FLAG_NONE, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_engine_emulation_load_phase(FuEngine *self, GError **error) { const gchar *json = g_hash_table_lookup(self->emulation_phases, GINT_TO_POINTER(self->install_phase)); if (json == NULL) return TRUE; g_info("loading phase %s: %s", fu_engine_install_phase_to_string(self->install_phase), json); return fu_engine_emulation_load_json(self, json, error); } gboolean fu_engine_emulation_load(FuEngine *self, GBytes *data, GError **error) { gboolean got_json = FALSE; g_autoptr(FuArchive) archive = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(data != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not supported */ if (!fu_engine_config_get_allow_emulation(self->config)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "emulation is not allowed from config"); return FALSE; } /* unload any existing devices */ if (!fu_engine_emulation_load_json(self, "{\"UsbDevices\":[]}", error)) return FALSE; /* load archive */ archive = fu_archive_new(data, FU_ARCHIVE_FLAG_NONE, error); if (archive == NULL) return FALSE; /* load JSON files from archive */ g_hash_table_remove_all(self->emulation_phases); for (guint phase = FU_ENGINE_INSTALL_PHASE_SETUP; phase < FU_ENGINE_INSTALL_PHASE_LAST; phase++) { g_autofree gchar *fn = g_strdup_printf("%s.json", fu_engine_install_phase_to_string(phase)); g_autofree gchar *json_safe = NULL; g_autoptr(GBytes) blob = NULL; /* not found */ blob = fu_archive_lookup_by_fn(archive, fn, NULL); if (blob == NULL) continue; json_safe = g_strndup(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob)); got_json = TRUE; g_info("got emulation for phase %s", fu_engine_install_phase_to_string(phase)); if (phase == FU_ENGINE_INSTALL_PHASE_SETUP) { if (!fu_engine_emulation_load_json(self, json_safe, error)) return FALSE; } else { g_hash_table_insert(self->emulation_phases, GINT_TO_POINTER(phase), g_steal_pointer(&json_safe)); } } if (!got_json) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no emulation data found in archive"); return FALSE; } /* success */ return TRUE; } GBytes * fu_engine_emulation_save(FuEngine *self, GError **error) { gboolean got_json = FALSE; g_autoptr(GByteArray) buf = NULL; g_autoptr(FuArchive) archive = fu_archive_new(NULL, FU_ARCHIVE_FLAG_NONE, NULL); g_return_val_if_fail(FU_IS_ENGINE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* not supported */ if (!fu_engine_config_get_allow_emulation(self->config)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "emulation is not allowed from config"); return NULL; } /* sanity check */ for (guint phase = FU_ENGINE_INSTALL_PHASE_SETUP; phase < FU_ENGINE_INSTALL_PHASE_LAST; phase++) { const gchar *json = g_hash_table_lookup(self->emulation_phases, GINT_TO_POINTER(phase)); g_autofree gchar *fn = g_strdup_printf("%s.json", fu_engine_install_phase_to_string(phase)); g_autoptr(GBytes) blob = NULL; /* nothing set */ if (json == NULL) continue; got_json = TRUE; blob = g_bytes_new_static(json, strlen(json)); fu_archive_add_entry(archive, fn, blob); } if (!got_json) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no emulation data, perhaps no devices have been added?"); return NULL; } /* write */ buf = fu_archive_write(archive, FU_ARCHIVE_FORMAT_ZIP, FU_ARCHIVE_COMPRESSION_GZIP, error); if (buf == NULL) return NULL; /* success */ g_hash_table_remove_all(self->emulation_phases); return g_bytes_new(buf->data, buf->len); } static gboolean fu_engine_backends_save_phase(FuEngine *self, GError **error) { const gchar *data_old; g_autofree gchar *data_new = NULL; g_autofree gchar *data_new_safe = NULL; g_autoptr(JsonBuilder) json_builder = json_builder_new(); g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonNode) json_root = NULL; /* all devices in all backends */ for (guint i = 0; i < self->backends->len; i++) { FuBackend *backend = g_ptr_array_index(self->backends, i); if (!fu_backend_save(backend, json_builder, FU_USB_DEVICE_EMULATION_TAG, FU_BACKEND_SAVE_FLAG_NONE, error)) return FALSE; } json_root = json_builder_get_root(json_builder); json_generator = json_generator_new(); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); data_old = g_hash_table_lookup(self->emulation_phases, GINT_TO_POINTER(self->install_phase)); data_new = json_generator_to_data(json_generator, NULL); if (g_strcmp0(data_new, "") == 0) { g_info("no data for phase %s", fu_engine_install_phase_to_string(self->install_phase)); return TRUE; } if (g_strcmp0(data_old, data_new) == 0) { g_info("JSON unchanged for phase %s", fu_engine_install_phase_to_string(self->install_phase)); return TRUE; } data_new_safe = g_strndup(data_new, 8000); g_info("JSON %s for phase %s: %s...", data_old == NULL ? "added" : "changed", fu_engine_install_phase_to_string(self->install_phase), data_new_safe); g_hash_table_insert(self->emulation_phases, GINT_TO_POINTER(self->install_phase), g_steal_pointer(&data_new)); /* success */ return TRUE; } /** * fu_engine_get_device: * @self: a #FuEngine * @device_id: a device ID * @error: (nullable): optional return location for an error * * Gets a specific device, optionally loading an emulated phase. * * Returns: (transfer full): a device, or %NULL if not found **/ FuDevice * fu_engine_get_device(FuEngine *self, const gchar *device_id, GError **error) { g_autoptr(FuDevice) device = NULL; /* we are emulating a device */ if (self->install_phase != FU_ENGINE_INSTALL_PHASE_SETUP) { g_autoptr(FuDevice) device_old = NULL; device_old = fu_device_list_get_by_id(self->device_list, device_id, NULL); if (device_old != NULL && fu_device_has_flag(device_old, FWUPD_DEVICE_FLAG_EMULATED)) { if (!fu_engine_emulation_load_phase(self, error)) return NULL; } } /* wait for any device to disconnect and reconnect */ if (!fu_device_list_wait_for_replug(self->device_list, error)) { g_prefix_error(error, "failed to wait for device: "); return NULL; } /* get the new device */ device = fu_device_list_get_by_id(self->device_list, device_id, error); if (device == NULL) return NULL; /* success */ return g_steal_pointer(&device); } /* same as FuDevice->prepare, but with the device open */ static gboolean fu_engine_device_prepare(FuEngine *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = fu_device_locker_new(device, error); if (locker == NULL) { g_prefix_error(error, "failed to open device for prepare: "); return FALSE; } /* check battery level is sane */ if (fu_device_get_battery_level(device) > 0 && fu_device_get_battery_level(device) < fu_device_get_battery_threshold(device)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW, "battery level is too low: %u%%", fu_device_get_battery_level(device)); return FALSE; } return fu_device_prepare(device, progress, flags, error); } /* same as FuDevice->cleanup, but with the device open */ static gboolean fu_engine_device_cleanup(FuEngine *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) { g_info("skipping device cleanup due to will-disappear flag"); return TRUE; } locker = fu_device_locker_new(device, error); if (locker == NULL) { g_prefix_error(error, "failed to open device for cleanup: "); return FALSE; } return fu_device_cleanup(device, progress, flags, error); } static gboolean fu_engine_device_check_power(FuEngine *self, FuDevice *device, FwupdInstallFlags flags, GError **error) { if (flags & FWUPD_INSTALL_FLAG_IGNORE_POWER) { g_autofree gchar *configdir = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR_PKG); g_autofree gchar *configfile = g_build_filename(configdir, "fwupd.conf", NULL); g_warning("Ignoring deprecated flag provided by client " "'FWUPD_INSTALL_FLAG_IGNORE_POWER'. To ignore power levels, modify %s", configfile); } if (fu_engine_config_get_ignore_power(self->config)) return TRUE; /* not charging */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REQUIRE_AC) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED) && !fu_power_state_is_ac(fu_context_get_power_state(self->ctx))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_AC_POWER_REQUIRED, "Cannot install update " "when not on AC power unless forced"); return FALSE; } /* not enough just in case */ if (!fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_IGNORE_SYSTEM_POWER) && fu_context_get_battery_level(self->ctx) != FWUPD_BATTERY_LEVEL_INVALID && fu_context_get_battery_threshold(self->ctx) != FWUPD_BATTERY_LEVEL_INVALID && fu_context_get_battery_level(self->ctx) < fu_context_get_battery_threshold(self->ctx)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW, "Cannot install update when system battery " "is not at least %u%% unless forced", fu_context_get_battery_threshold(self->ctx)); return FALSE; } /* success */ return TRUE; } static gboolean fu_engine_prepare(FuEngine *self, const gchar *device_id, FuProgress *progress, FwupdInstallFlags flags, GError **error) { GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); g_autofree gchar *str = NULL; g_autoptr(FuDevice) device = NULL; /* the device and plugin both may have changed */ device = fu_engine_get_device(self, device_id, error); if (device == NULL) { g_prefix_error(error, "failed to get device before update prepare: "); return FALSE; } fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS); if (!fu_engine_device_check_power(self, device, flags, error)) return FALSE; str = fu_device_to_string(device); g_info("prepare -> %s", str); if (!fu_engine_device_prepare(self, device, progress, flags, error)) return FALSE; for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); if (!fu_plugin_runner_prepare(plugin_tmp, device, progress, flags, error)) return FALSE; } /* save to emulated phase */ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { if (!fu_engine_backends_save_phase(self, error)) return FALSE; } /* wait for any device to disconnect and reconnect */ if (!fu_device_list_wait_for_replug(self->device_list, error)) { g_prefix_error(error, "failed to wait for prepare replug: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_engine_cleanup(FuEngine *self, const gchar *device_id, FuProgress *progress, FwupdInstallFlags flags, GError **error) { GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); g_autofree gchar *str = NULL; g_autoptr(FuDevice) device = NULL; /* the device and plugin both may have changed */ device = fu_engine_get_device(self, device_id, error); if (device == NULL) { g_prefix_error(error, "failed to get device before update cleanup: "); return FALSE; } fu_device_remove_problem(device, FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS); str = fu_device_to_string(device); g_info("cleanup -> %s", str); if (!fu_engine_device_cleanup(self, device, progress, flags, error)) return FALSE; for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); if (!fu_plugin_runner_cleanup(plugin_tmp, device, progress, flags, error)) return FALSE; } /* save to emulated phase */ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { if (!fu_engine_backends_save_phase(self, error)) return FALSE; } /* wait for any device to disconnect and reconnect */ if (!fu_device_list_wait_for_replug(self->device_list, error)) { g_prefix_error(error, "failed to wait for cleanup replug: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_engine_detach(FuEngine *self, const gchar *device_id, FuProgress *progress, FwupdFeatureFlags feature_flags, GError **error) { FuPlugin *plugin; g_autofree gchar *str = NULL; g_autoptr(FuDevice) device = NULL; g_autoptr(FuDeviceLocker) poll_locker = NULL; /* the device and plugin both may have changed */ device = fu_engine_get_device(self, device_id, error); if (device == NULL) { g_prefix_error(error, "failed to get device before update detach: "); return FALSE; } /* pause the polling */ poll_locker = fu_device_poll_locker_new(device, error); if (poll_locker == NULL) return FALSE; str = fu_device_to_string(device); g_info("detach -> %s", str); plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); if (plugin == NULL) return FALSE; if (!fu_plugin_runner_detach(plugin, device, progress, error)) return FALSE; /* support older clients without the ability to do immediate requests */ if ((feature_flags & FWUPD_FEATURE_FLAG_REQUESTS) == 0 && fu_device_get_request_cnt(device, FWUPD_REQUEST_KIND_IMMEDIATE) > 0) { /* fallback to something sane */ if (fu_device_get_update_message(device) == NULL) { g_autofree gchar *tmp = NULL; tmp = g_strdup_printf("Device %s needs to manually be put in update mode", fu_device_get_name(device)); fu_device_set_update_message(device, tmp); } /* abort and require client to re-submit */ fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NEEDS_USER_ACTION, fu_device_get_update_message(device)); return FALSE; } /* save to emulated phase */ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { if (!fu_engine_backends_save_phase(self, error)) return FALSE; } /* wait for any device to disconnect and reconnect */ if (!fu_device_list_wait_for_replug(self->device_list, error)) { g_prefix_error(error, "failed to wait for detach replug: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_engine_attach(FuEngine *self, const gchar *device_id, FuProgress *progress, GError **error) { FuPlugin *plugin; g_autofree gchar *str = NULL; g_autoptr(FuDevice) device = NULL; g_autoptr(FuDeviceLocker) poll_locker = NULL; /* the device and plugin both may have changed */ device = fu_engine_get_device(self, device_id, error); if (device == NULL) { g_prefix_error(error, "failed to get device before update attach: "); return FALSE; } str = fu_device_to_string(device); g_info("attach -> %s", str); plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); if (plugin == NULL) return FALSE; /* pause the polling */ poll_locker = fu_device_poll_locker_new(device, error); if (poll_locker == NULL) return FALSE; if (!fu_plugin_runner_attach(plugin, device, progress, error)) return FALSE; /* save to emulated phase */ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { if (!fu_engine_backends_save_phase(self, error)) return FALSE; } /* wait for any device to disconnect and reconnect */ if (!fu_device_list_wait_for_replug(self->device_list, error)) { g_prefix_error(error, "failed to wait for attach replug: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_engine_set_progress(FuEngine *self, const gchar *device_id, FuProgress *progress, GError **error) { g_autoptr(FuDevice) device = NULL; /* the device and plugin both may have changed */ device = fu_engine_get_device(self, device_id, error); if (device == NULL) { g_prefix_error(error, "failed to get device before setting progress: "); return FALSE; } fu_device_set_progress(device, progress); return TRUE; } gboolean fu_engine_activate(FuEngine *self, const gchar *device_id, FuProgress *progress, GError **error) { FuPlugin *plugin; g_autofree gchar *str = NULL; g_autoptr(FuDevice) device = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* check the device exists */ device = fu_device_list_get_by_id(self->device_list, device_id, error); if (device == NULL) return FALSE; str = fu_device_to_string(device); g_info("activate -> %s", str); plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); if (plugin == NULL) return FALSE; if (!fu_plugin_runner_activate(plugin, device, progress, error)) return FALSE; fu_engine_emit_device_changed_safe(self, device); fu_engine_emit_changed(self); return TRUE; } static gboolean fu_engine_reload(FuEngine *self, const gchar *device_id, GError **error) { FuPlugin *plugin; g_autofree gchar *str = NULL; g_autoptr(FuDevice) device = NULL; /* the device and plugin both may have changed */ device = fu_engine_get_device(self, device_id, error); if (device == NULL) { g_prefix_error(error, "failed to get device before update reload: "); return FALSE; } str = fu_device_to_string(device); g_info("reload -> %s", str); plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); if (plugin == NULL) return FALSE; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) { g_info("skipping reload due to will-disappear flag"); return TRUE; } if (!fu_plugin_runner_reload(plugin, device, error)) { g_prefix_error(error, "failed to reload device: "); return FALSE; } /* save to emulated phase */ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { if (!fu_engine_backends_save_phase(self, error)) return FALSE; } /* wait for any device to disconnect and reconnect */ if (!fu_device_list_wait_for_replug(self->device_list, error)) { g_prefix_error(error, "failed to wait for reload replug: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_engine_write_firmware(FuEngine *self, const gchar *device_id, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuPlugin *plugin; g_autofree gchar *str = NULL; g_autoptr(FuDevice) device = NULL; g_autoptr(FuDeviceLocker) poll_locker = NULL; g_autoptr(GError) error_write = NULL; /* cancel the pending action */ if (!fu_engine_offline_invalidate(error)) return FALSE; /* the device and plugin both may have changed */ device = fu_engine_get_device(self, device_id, error); if (device == NULL) { g_prefix_error(error, "failed to get device before update: "); return FALSE; } /* pause the polling */ poll_locker = fu_device_poll_locker_new(device, error); if (poll_locker == NULL) return FALSE; str = fu_device_to_string(device); g_info("update -> %s", str); plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); if (plugin == NULL) return FALSE; if (!fu_plugin_runner_write_firmware(plugin, device, blob_fw, progress, flags, &error_write)) { g_autoptr(GError) error_attach = NULL; g_autoptr(GError) error_cleanup = NULL; if (g_error_matches(error_write, FWUPD_ERROR, FWUPD_ERROR_AC_POWER_REQUIRED) || g_error_matches(error_write, FWUPD_ERROR, FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW) || g_error_matches(error_write, FWUPD_ERROR, FWUPD_ERROR_NEEDS_USER_ACTION) || g_error_matches(error_write, FWUPD_ERROR, FWUPD_ERROR_BROKEN_SYSTEM)) { fu_device_set_update_state(device, FWUPD_UPDATE_STATE_FAILED_TRANSIENT); } else { fu_device_set_update_state(device, FWUPD_UPDATE_STATE_FAILED); } /* attach back into runtime then cleanup */ fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_ATTACH); fu_progress_reset(progress); if (!fu_plugin_runner_attach(plugin, device, progress, &error_attach)) { g_warning("failed to attach device after failed update: %s", error_attach->message); } fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_CLEANUP); fu_progress_reset(progress); if (!fu_engine_cleanup(self, device_id, progress, flags, &error_cleanup)) { g_warning("failed to update-cleanup after failed update: %s", error_cleanup->message); } g_propagate_error(error, g_steal_pointer(&error_write)); return FALSE; } /* save to emulated phase */ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { if (!fu_engine_backends_save_phase(self, error)) return FALSE; } /* wait for any device to disconnect and reconnect */ if (!fu_device_list_wait_for_replug(self->device_list, error)) { g_prefix_error(error, "failed to wait for write-firmware replug: "); return FALSE; } /* success */ return TRUE; } GBytes * fu_engine_firmware_dump(FuEngine *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuDeviceLocker) poll_locker = NULL; /* pause the polling */ poll_locker = fu_device_poll_locker_new(device, error); if (poll_locker == NULL) return NULL; /* open, read, close */ locker = fu_device_locker_new(device, error); if (locker == NULL) { g_prefix_error(error, "failed to open device for firmware read: "); return NULL; } return fu_device_dump_firmware(device, progress, error); } FuFirmware * fu_engine_firmware_read(FuEngine *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuDeviceLocker) poll_locker = NULL; /* pause the polling */ poll_locker = fu_device_poll_locker_new(device, error); if (poll_locker == NULL) return NULL; /* open, read, close */ locker = fu_device_locker_new(device, error); if (locker == NULL) { g_prefix_error(error, "failed to open device for firmware read: "); return NULL; } return fu_device_read_firmware(device, progress, error); } gboolean fu_engine_install_blob(FuEngine *self, FuDevice *device, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, FwupdFeatureFlags feature_flags, GError **error) { guint retries = 0; g_autofree gchar *device_id = NULL; g_autofree gchar *filename_to_delete = NULL; g_autoptr(GTimer) timer = g_timer_new(); g_autoptr(FuDeviceProgress) device_progress = fu_device_progress_new(device, progress); g_assert(device_progress != NULL); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "prepare"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, NULL); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "cleanup"); /* test the firmware is not an empty blob */ if (g_bytes_get_size(blob_fw) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Firmware is invalid as has zero size"); return FALSE; } /* mark this as modified even if we actually fail to do the update */ fu_device_set_modified(device, (guint64)g_get_real_time() / G_USEC_PER_SEC); /* signal to all the plugins the update is about to happen */ device_id = g_strdup(fu_device_get_id(device)); fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_PREPARE); if (!fu_engine_prepare(self, device_id, fu_progress_get_child(progress), flags, error)) return FALSE; fu_progress_step_done(progress); /* we saved this so we could do the offline update */ if (fu_device_get_update_state(device) == FWUPD_UPDATE_STATE_PENDING) { g_autoptr(FuDevice) device_pending = fu_history_get_device_by_id(self->history, device_id, NULL); if (device_pending != NULL) { FwupdRelease *release = fu_device_get_release_default(device_pending); filename_to_delete = g_strdup(fwupd_release_get_filename(release)); } } /* plugins can set FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED to run again, but they * must return TRUE rather than an error */ do { g_autoptr(FuDevice) device_tmp = NULL; FuProgress *progress_local = fu_progress_get_child(progress); /* check for a loop */ if (++retries > 5) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "aborting device write loop, limit 5"); return FALSE; } /* progress */ if (!fu_engine_set_progress(self, device_id, progress_local, error)) return FALSE; if (fu_progress_get_steps(progress_local) == 0) { fu_progress_set_id(progress_local, G_STRLOC); fu_progress_add_flag(progress_local, FU_PROGRESS_FLAG_GUESSED); fu_progress_add_step(progress_local, FWUPD_STATUS_DEVICE_RESTART, 2, NULL); fu_progress_add_step(progress_local, FWUPD_STATUS_DEVICE_WRITE, 94, NULL); fu_progress_add_step(progress_local, FWUPD_STATUS_DEVICE_RESTART, 2, NULL); fu_progress_add_step(progress_local, FWUPD_STATUS_DEVICE_BUSY, 2, NULL); } else if (fu_progress_get_steps(progress_local) != 4) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "FuDevice->set_progress did not set " "detach,write,attach,reload steps"); return FALSE; } /* detach to bootloader mode */ fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_DETACH); if (!fu_engine_detach(self, device_id, fu_progress_get_child(progress_local), feature_flags, error)) return FALSE; fu_progress_step_done(progress_local); /* install */ fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_INSTALL); if (!fu_engine_write_firmware(self, device_id, blob_fw, fu_progress_get_child(progress_local), flags, error)) return FALSE; fu_progress_step_done(progress_local); /* attach into runtime mode */ fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_ATTACH); if (!fu_engine_attach(self, device_id, fu_progress_get_child(progress_local), error)) return FALSE; fu_progress_step_done(progress_local); /* get the new version number */ fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_RELOAD); if (!fu_engine_reload(self, device_id, error)) return FALSE; fu_progress_step_done(progress_local); /* the device and plugin both may have changed */ device_tmp = fu_engine_get_device(self, device_id, error); if (device_tmp == NULL) { g_prefix_error(error, "failed to get device after install blob: "); return FALSE; } if (!fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED)) break; /* don't rely on a plugin clearing this */ fu_device_remove_flag(device_tmp, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); fu_progress_reset(progress_local); } while (TRUE); fu_progress_step_done(progress); /* delete offline-update cab archive */ if (filename_to_delete != NULL) { g_autoptr(GFile) file = g_file_new_for_path(filename_to_delete); if (!g_file_delete(file, NULL, error)) { g_prefix_error(error, "failed to delete %s: ", filename_to_delete); return FALSE; } } /* update history database */ fu_device_set_update_state(device, FWUPD_UPDATE_STATE_SUCCESS); fu_device_set_install_duration(device, g_timer_elapsed(timer, NULL)); if ((flags & FWUPD_INSTALL_FLAG_NO_HISTORY) == 0) { if (!fu_history_modify_device(self->history, device, error)) { g_prefix_error(error, "failed to set success: "); return FALSE; } } /* signal to all the plugins the update has happened */ fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_CLEANUP); if (!fu_engine_cleanup(self, device_id, fu_progress_get_child(progress), flags, error)) return FALSE; fu_progress_step_done(progress); /* make the UI update */ fu_engine_emit_device_changed(self, device_id); g_info("Updating %s took %f seconds", fu_device_get_name(device), g_timer_elapsed(timer, NULL)); return TRUE; } static FuDevice * fu_engine_get_item_by_id_fallback_history(FuEngine *self, const gchar *id, GError **error) { g_autoptr(GPtrArray) devices = NULL; /* not a wildcard */ if (g_strcmp0(id, FWUPD_DEVICE_ID_ANY) != 0) { g_autoptr(FuDevice) dev = NULL; g_autoptr(GError) error_local = NULL; /* get this one device */ dev = fu_history_get_device_by_id(self->history, id, &error_local); if (dev == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Failed to find %s in history database: %s", id, error_local->message); return NULL; } /* only useful */ if (fu_device_get_update_state(dev) == FWUPD_UPDATE_STATE_SUCCESS || fu_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED_TRANSIENT || fu_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED) { return g_steal_pointer(&dev); } /* nothing in database */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Device %s has no results to report", fu_device_get_id(dev)); return NULL; } /* allow '*' for any */ devices = fu_history_get_devices(self->history, error); if (devices == NULL) return NULL; for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index(devices, i); if (fu_device_get_update_state(dev) == FWUPD_UPDATE_STATE_SUCCESS || fu_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED_TRANSIENT || fu_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED) return g_object_ref(dev); } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Failed to find any useful results to report"); return NULL; } static gboolean fu_engine_create_silo_index(FuEngine *self, GError **error) { g_autoptr(GPtrArray) components = NULL; g_autoptr(GError) error_container_checksum1 = NULL; g_autoptr(GError) error_container_checksum2 = NULL; g_autoptr(GError) error_tag_by_guid_version = NULL; /* print what we've got */ components = xb_silo_query(self->silo, "components/component[@type='firmware']", 0, NULL); if (components == NULL) return TRUE; g_info("%u components now in silo", components->len); /* clear old prepared queries */ g_clear_object(&self->query_component_by_guid); g_clear_object(&self->query_container_checksum1); g_clear_object(&self->query_container_checksum2); g_clear_object(&self->query_tag_by_guid_version); /* build the index */ if (!xb_silo_query_build_index(self->silo, "components/component", "type", error)) return FALSE; if (!xb_silo_query_build_index(self->silo, "components/component[@type='firmware']/provides/firmware", "type", error)) return FALSE; if (!xb_silo_query_build_index(self->silo, "components/component/provides/firmware", NULL, error)) return FALSE; if (!xb_silo_query_build_index(self->silo, "components/component[@type='firmware']/tags/tag", "namespace", error)) return FALSE; /* create prepared queries to save time later */ self->query_component_by_guid = xb_query_new_full(self->silo, "components/component/provides/firmware[@type=$'flashed'][text()=?]/" "../..", XB_QUERY_FLAG_OPTIMIZE, error); if (self->query_component_by_guid == NULL) { g_prefix_error(error, "failed to prepare query: "); return FALSE; } /* old-style and new-style */ self->query_container_checksum1 = xb_query_new_full(self->silo, "components/component[@type='firmware']/releases/release/" "checksum[@target='container'][text()=?]/../../..", XB_QUERY_FLAG_OPTIMIZE, &error_container_checksum1); if (self->query_container_checksum1 == NULL) g_debug("ignoring prepared query: %s", error_container_checksum1->message); self->query_container_checksum2 = xb_query_new_full(self->silo, "components/component[@type='firmware']/releases/release/" "artifacts/artifact[@type='binary']/checksum[text()=?]/../../" "../../..", XB_QUERY_FLAG_OPTIMIZE, &error_container_checksum2); if (self->query_container_checksum2 == NULL) g_debug("ignoring prepared query: %s", error_container_checksum2->message); /* prepare tag query with bound GUID parameter */ self->query_tag_by_guid_version = xb_query_new_full(self->silo, "local/components/component[@merge='append']/provides/" "firmware[text()=?]/../../releases/release[@version=?]/../../" "tags/tag", XB_QUERY_FLAG_OPTIMIZE, &error_tag_by_guid_version); if (self->query_tag_by_guid_version == NULL) g_debug("ignoring prepared query: %s", error_tag_by_guid_version->message); /* success */ return TRUE; } /* for the self tests */ void fu_engine_set_silo(FuEngine *self, XbSilo *silo) { g_autoptr(GError) error_local = NULL; g_return_if_fail(FU_IS_ENGINE(self)); g_return_if_fail(XB_IS_SILO(silo)); g_set_object(&self->silo, silo); if (!fu_engine_create_silo_index(self, &error_local)) g_warning("failed to create indexes: %s", error_local->message); } static gboolean fu_engine_appstream_upgrade_cb(XbBuilderFixup *self, XbBuilderNode *bn, gpointer user_data, GError **error) { if (g_strcmp0(xb_builder_node_get_element(bn), "metadata") == 0) xb_builder_node_set_element(bn, "custom"); return TRUE; } static GInputStream * fu_engine_builder_cabinet_adapter_cb(XbBuilderSource *source, XbBuilderSourceCtx *ctx, gpointer user_data, GCancellable *cancellable, GError **error) { FuEngine *self = FU_ENGINE(user_data); g_autoptr(GBytes) blob = NULL; g_autoptr(XbSilo) silo = NULL; g_autofree gchar *xml = NULL; /* convert the CAB into metadata XML */ blob = xb_builder_source_ctx_get_bytes(ctx, cancellable, error); if (blob == NULL) return NULL; silo = fu_engine_get_silo_from_blob(self, blob, error); if (silo == NULL) return NULL; xml = xb_silo_export(silo, XB_NODE_EXPORT_FLAG_NONE, error); if (xml == NULL) return NULL; return g_memory_input_stream_new_from_data(g_steal_pointer(&xml), -1, g_free); } static XbBuilderSource * fu_engine_create_metadata_builder_source(FuEngine *self, const gchar *fn, GError **error) { g_autoptr(GFile) file = g_file_new_for_path(fn); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_info("using %s as metadata source", fn); xb_builder_source_add_simple_adapter(source, "application/vnd.ms-cab-compressed," "com.microsoft.cab," ".cab," "application/octet-stream", fu_engine_builder_cabinet_adapter_cb, self, NULL); if (!xb_builder_source_load_file(source, file, #if LIBJCAT_CHECK_VERSION(0, 2, 0) XB_BUILDER_SOURCE_FLAG_WATCH_FILE | XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY, #else XB_BUILDER_SOURCE_FLAG_WATCH_FILE, #endif NULL, error)) return NULL; return g_steal_pointer(&source); } static gboolean fu_engine_create_metadata(FuEngine *self, XbBuilder *builder, FwupdRemote *remote, GError **error) { g_autoptr(GPtrArray) files = NULL; const gchar *path; /* find all files in directory */ path = fwupd_remote_get_filename_cache(remote); if (path == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no filename cache for %s", fwupd_remote_get_id(remote)); return FALSE; } files = fu_path_get_files(path, error); if (files == NULL) return FALSE; /* add each source */ for (guint i = 0; i < files->len; i++) { g_autoptr(XbBuilderNode) custom = NULL; g_autoptr(XbBuilderSource) source = NULL; g_autoptr(GError) error_local = NULL; const gchar *fn = g_ptr_array_index(files, i); g_autofree gchar *fn_lowercase = g_ascii_strdown(fn, -1); /* check is cab file */ if (!g_str_has_suffix(fn_lowercase, ".cab")) { g_info("ignoring: %s", fn); continue; } /* build source for file */ source = fu_engine_create_metadata_builder_source(self, fn, &error_local); if (source == NULL) { g_warning("failed to create builder source: %s", error_local->message); continue; } /* add metadata */ custom = xb_builder_node_new("custom"); xb_builder_node_insert_text(custom, "value", fn, "key", "fwupd::FilenameCache", NULL); xb_builder_node_insert_text(custom, "value", fwupd_remote_get_id(remote), "key", "fwupd::RemoteId", NULL); xb_builder_source_set_info(source, custom); xb_builder_import_source(builder, source); } return TRUE; } static void fu_engine_ensure_device_supported(FuEngine *self, FuDevice *device) { gboolean is_supported = FALSE; gboolean update_pending = FALSE; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(FuEngineRequest) request = NULL; /* all flags set */ request = fu_engine_request_new(); fu_engine_request_add_flag(request, FU_ENGINE_REQUEST_FLAG_NO_REQUIREMENTS); fu_engine_request_add_flag(request, FU_ENGINE_REQUEST_FLAG_ANY_RELEASE); fu_engine_request_set_feature_flags(request, ~0); /* get all releases that pass the requirements */ releases = fu_engine_get_releases_for_device(self, request, device, &error); if (releases == NULL) { if (!g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO) && !g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_warning("failed to get releases for %s: %s", fu_device_get_name(device), error->message); } } else { if (releases->len > 0) is_supported = TRUE; for (guint i = 0; i < releases->len; i++) { FuRelease *release = FU_RELEASE(g_ptr_array_index(releases, i)); if (fu_release_has_flag(release, FWUPD_RELEASE_FLAG_IS_UPGRADE)) { update_pending = TRUE; break; } } if (update_pending) { fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_UPDATE_PENDING); } else { fu_device_remove_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_UPDATE_PENDING); } } /* was supported, now unsupported */ if (!is_supported) { if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)) { fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED); fu_engine_emit_device_changed_safe(self, device); } return; } /* was unsupported, now supported */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)) { fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED); fu_engine_emit_device_changed_safe(self, device); } } static void fu_engine_md_refresh_devices(FuEngine *self) { g_autoptr(GPtrArray) devices = fu_device_list_get_active(self->device_list); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); g_autoptr(XbNode) component = fu_engine_get_component_by_guids(self, device); /* set or clear the SUPPORTED flag */ fu_engine_ensure_device_supported(self, device); /* fixup the name and format as needed */ if (component != NULL && !fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_ONLY_CHECKSUM)) fu_device_ensure_from_component(device, component); } } static gboolean fu_engine_load_metadata_store_local(FuEngine *self, XbBuilder *builder, FuPathKind path_kind, GError **error) { g_autofree gchar *fn = fu_path_from_kind(path_kind); g_autofree gchar *metadata_path = g_build_filename(fn, "local.d", NULL); g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) metadata_fns = NULL; metadata_fns = fu_path_glob(metadata_path, "*.xml", &error_local); if (metadata_fns == NULL) { g_info("ignoring: %s", error_local->message); return TRUE; } for (guint i = 0; i < metadata_fns->len; i++) { const gchar *path = g_ptr_array_index(metadata_fns, i); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(GFile) file = g_file_new_for_path(path); g_info("loading local metadata: %s", path); if (!xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, error)) return FALSE; xb_builder_source_set_prefix(source, "local"); xb_builder_import_source(builder, source); } /* success */ return TRUE; } static gboolean fu_engine_load_metadata_store(FuEngine *self, FuEngineLoadFlags flags, GError **error) { GPtrArray *remotes; XbBuilderCompileFlags compile_flags = XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID; g_autoptr(GFile) xmlb = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); /* clear existing silo */ g_clear_object(&self->silo); /* invalidate the cache if the fwupd version changes */ xb_builder_append_guid(builder, SOURCE_VERSION); /* verbose profiling */ if (g_getenv("FWUPD_XMLB_VERBOSE") != NULL) { xb_builder_set_profile_flags(builder, XB_SILO_PROFILE_FLAG_XPATH | XB_SILO_PROFILE_FLAG_DEBUG); } /* load each enabled metadata file */ remotes = fu_remote_list_get_all(self->remote_list); for (guint i = 0; i < remotes->len; i++) { const gchar *path = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilderFixup) fixup = NULL; g_autoptr(XbBuilderNode) custom = NULL; g_autoptr(XbBuilderSource) source = xb_builder_source_new(); FwupdRemote *remote = g_ptr_array_index(remotes, i); if (!fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)) continue; path = fwupd_remote_get_filename_cache(remote); if (!g_file_test(path, G_FILE_TEST_EXISTS)) continue; /* generate all metadata on demand */ if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DIRECTORY) { g_info("loading metadata for remote '%s'", fwupd_remote_get_id(remote)); if (!fu_engine_create_metadata(self, builder, remote, &error_local)) { g_warning("failed to generate remote %s: %s", fwupd_remote_get_id(remote), error_local->message); } continue; } /* save the remote-id in the custom metadata space */ file = g_file_new_for_path(path); if (!xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error_local)) { g_warning("failed to load remote %s: %s", fwupd_remote_get_id(remote), error_local->message); continue; } /* fix up any legacy installed files */ fixup = xb_builder_fixup_new("AppStreamUpgrade", fu_engine_appstream_upgrade_cb, self, NULL); xb_builder_fixup_set_max_depth(fixup, 3); xb_builder_source_add_fixup(source, fixup); /* add metadata */ custom = xb_builder_node_new("custom"); xb_builder_node_insert_text(custom, "value", path, "key", "fwupd::FilenameCache", NULL); xb_builder_node_insert_text(custom, "value", fwupd_remote_get_id(remote), "key", "fwupd::RemoteId", NULL); xb_builder_source_set_info(source, custom); /* we need to watch for changes? */ xb_builder_import_source(builder, source); } /* add any client-side data, e.g. BKC tags */ if (!fu_engine_load_metadata_store_local(self, builder, FU_PATH_KIND_LOCALSTATEDIR_PKG, error)) return FALSE; if (!fu_engine_load_metadata_store_local(self, builder, FU_PATH_KIND_DATADIR_PKG, error)) return FALSE; /* on a read-only filesystem don't care about the cache GUID */ if (flags & FU_ENGINE_LOAD_FLAG_READONLY) compile_flags |= XB_BUILDER_COMPILE_FLAG_IGNORE_GUID; /* ensure silo is up to date */ if (flags & FU_ENGINE_LOAD_FLAG_NO_CACHE) { g_autoptr(GFileIOStream) iostr = NULL; xmlb = g_file_new_tmp(NULL, &iostr, error); if (xmlb == NULL) return FALSE; } else { g_autofree gchar *cachedirpkg = fu_path_from_kind(FU_PATH_KIND_CACHEDIR_PKG); g_autofree gchar *xmlbfn = g_build_filename(cachedirpkg, "metadata.xmlb", NULL); xmlb = g_file_new_for_path(xmlbfn); } self->silo = xb_builder_ensure(builder, xmlb, compile_flags, NULL, error); if (self->silo == NULL) { g_prefix_error(error, "cannot create metadata.xmlb: "); return FALSE; } /* success */ return fu_engine_create_silo_index(self, error); } static void fu_engine_remote_list_ensure_p2p_policy_remote(FuEngine *self, FwupdRemote *remote) { if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DOWNLOAD) { FuP2pPolicy p2p_policy = fu_engine_config_get_p2p_policy(self->config); if (p2p_policy & FU_P2P_POLICY_METADATA) fwupd_remote_add_flag(remote, FWUPD_REMOTE_FLAG_ALLOW_P2P_METADATA); else fwupd_remote_remove_flag(remote, FWUPD_REMOTE_FLAG_ALLOW_P2P_METADATA); if (p2p_policy & FU_P2P_POLICY_FIRMWARE) fwupd_remote_add_flag(remote, FWUPD_REMOTE_FLAG_ALLOW_P2P_FIRMWARE); else fwupd_remote_remove_flag(remote, FWUPD_REMOTE_FLAG_ALLOW_P2P_FIRMWARE); } } static void fu_engine_config_changed_cb(FuEngineConfig *config, FuEngine *self) { GPtrArray *remotes = fu_remote_list_get_all(self->remote_list); fu_idle_set_timeout(self->idle, fu_engine_config_get_idle_timeout(config)); /* allow changing the hardcoded ESP location */ if (fu_engine_config_get_esp_location(config) != NULL) fu_context_set_esp_location(self->ctx, fu_engine_config_get_esp_location(config)); /* amend P2P policy */ for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index(remotes, i); fu_engine_remote_list_ensure_p2p_policy_remote(self, remote); } } static void fu_engine_metadata_changed(FuEngine *self) { g_autoptr(GError) error_local = NULL; if (!fu_engine_load_metadata_store(self, FU_ENGINE_LOAD_FLAG_NONE, &error_local)) g_warning("Failed to reload metadata store: %s", error_local->message); /* set device properties from the metadata */ fu_engine_md_refresh_devices(self); /* invalidate host security attributes */ g_clear_pointer(&self->host_security_id, g_free); /* make the UI update */ fu_engine_emit_changed(self); } static void fu_engine_remote_list_changed_cb(FuRemoteList *remote_list, FuEngine *self) { fu_engine_metadata_changed(self); } static void fu_engine_remote_list_added_cb(FuRemoteList *remote_list, FwupdRemote *remote, FuEngine *self) { FuReleasePriority priority = fu_engine_config_get_release_priority(self->config); if (priority == FU_RELEASE_PRIORITY_LOCAL && fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD) { g_debug("priority local and %s is not download remote, so bumping", fwupd_remote_get_id(remote)); fwupd_remote_set_priority(remote, fwupd_remote_get_priority(remote) + 1000); } else if (priority == FU_RELEASE_PRIORITY_REMOTE && fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DOWNLOAD) { g_debug("priority remote and %s is download remote, so bumping", fwupd_remote_get_id(remote)); fwupd_remote_set_priority(remote, fwupd_remote_get_priority(remote) + 1000); } /* set the p2p policy */ fu_engine_remote_list_ensure_p2p_policy_remote(self, remote); } static gint fu_engine_sort_jcat_results_timestamp_cb(gconstpointer a, gconstpointer b) { JcatResult *ra = *((JcatResult **)a); JcatResult *rb = *((JcatResult **)b); if (jcat_result_get_timestamp(ra) < jcat_result_get_timestamp(rb)) return 1; if (jcat_result_get_timestamp(ra) > jcat_result_get_timestamp(rb)) return -1; return 0; } static JcatResult * fu_engine_get_newest_signature_jcat_result(GPtrArray *results, GError **error) { /* sort by timestamp, newest first */ g_ptr_array_sort(results, fu_engine_sort_jcat_results_timestamp_cb); /* get the first signature, ignoring the checksums */ for (guint i = 0; i < results->len; i++) { JcatResult *result = g_ptr_array_index(results, i); if (jcat_result_get_method(result) == JCAT_BLOB_METHOD_SIGNATURE) return g_object_ref(result); } /* should never happen due to %JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no signature method in results"); return NULL; } static JcatResult * fu_engine_get_system_jcat_result(FuEngine *self, FwupdRemote *remote, GError **error) { g_autoptr(GBytes) blob = NULL; g_autoptr(GBytes) blob_sig = NULL; g_autoptr(GInputStream) istream = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(JcatItem) jcat_item = NULL; g_autoptr(JcatFile) jcat_file = jcat_file_new(); blob = fu_bytes_get_contents(fwupd_remote_get_filename_cache(remote), error); if (blob == NULL) return NULL; blob_sig = fu_bytes_get_contents(fwupd_remote_get_filename_cache_sig(remote), error); if (blob_sig == NULL) return NULL; istream = g_memory_input_stream_new_from_bytes(blob_sig); if (!jcat_file_import_stream(jcat_file, istream, JCAT_IMPORT_FLAG_NONE, NULL, error)) return NULL; jcat_item = jcat_file_get_item_default(jcat_file, error); if (jcat_item == NULL) return NULL; results = jcat_context_verify_item(self->jcat_context, blob, jcat_item, JCAT_VERIFY_FLAG_REQUIRE_CHECKSUM | JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE, error); if (results == NULL) return NULL; /* return the newest signature */ return fu_engine_get_newest_signature_jcat_result(results, error); } static gboolean fu_engine_validate_result_timestamp(JcatResult *jcat_result, JcatResult *jcat_result_old, GError **error) { gint64 delta = 0; g_return_val_if_fail(JCAT_IS_RESULT(jcat_result), FALSE); g_return_val_if_fail(JCAT_IS_RESULT(jcat_result_old), FALSE); if (jcat_result_get_timestamp(jcat_result) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no signing timestamp"); return FALSE; } if (jcat_result_get_timestamp(jcat_result_old) > 0) { delta = jcat_result_get_timestamp(jcat_result) - jcat_result_get_timestamp(jcat_result_old); } if (delta < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "new signing timestamp was %" G_GINT64_FORMAT " seconds older", -delta); return FALSE; } if (delta > 0) g_info("timestamp increased, so no rollback"); return TRUE; } /** * fu_engine_update_metadata_bytes: * @self: a #FuEngine * @remote_id: a remote ID, e.g. `lvfs` * @bytes_raw: Blob of metadata * @bytes_sig: Blob of metadata signature, typically Jcat binary format * @error: (nullable): optional return location for an error * * Updates the metadata for a specific remote. * * Returns: %TRUE for success **/ gboolean fu_engine_update_metadata_bytes(FuEngine *self, const gchar *remote_id, GBytes *bytes_raw, GBytes *bytes_sig, GError **error) { FwupdKeyringKind keyring_kind; FwupdRemote *remote; JcatVerifyFlags jcat_flags = JCAT_VERIFY_FLAG_REQUIRE_SIGNATURE; g_autoptr(JcatFile) jcat_file = jcat_file_new(); g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(remote_id != NULL, FALSE); g_return_val_if_fail(bytes_raw != NULL, FALSE); g_return_val_if_fail(bytes_sig != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* check remote is valid */ remote = fu_remote_list_get_by_id(self->remote_list, remote_id); if (remote == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "remote %s not found", remote_id); return FALSE; } if (!fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "remote %s not enabled", remote_id); return FALSE; } /* verify JCatFile, or create a dummy one from legacy data */ keyring_kind = fwupd_remote_get_keyring_kind(remote); if (keyring_kind == FWUPD_KEYRING_KIND_JCAT) { g_autoptr(GInputStream) istream = NULL; istream = g_memory_input_stream_new_from_bytes(bytes_sig); if (!jcat_file_import_stream(jcat_file, istream, JCAT_IMPORT_FLAG_NONE, NULL, error)) return FALSE; jcat_flags |= JCAT_VERIFY_FLAG_REQUIRE_CHECKSUM; } else if (keyring_kind == FWUPD_KEYRING_KIND_GPG) { g_autoptr(JcatBlob) jcab_blob = NULL; g_autoptr(JcatItem) jcat_item = jcat_item_new(""); jcab_blob = jcat_blob_new(JCAT_BLOB_KIND_GPG, bytes_sig); jcat_item_add_blob(jcat_item, jcab_blob); jcat_file_add_item(jcat_file, jcat_item); } else if (keyring_kind == FWUPD_KEYRING_KIND_PKCS7) { g_autoptr(JcatBlob) jcab_blob = NULL; g_autoptr(JcatItem) jcat_item = jcat_item_new(""); jcab_blob = jcat_blob_new(JCAT_BLOB_KIND_PKCS7, bytes_sig); jcat_item_add_blob(jcat_item, jcab_blob); jcat_file_add_item(jcat_file, jcat_item); } /* verify file */ if (keyring_kind != FWUPD_KEYRING_KIND_NONE) { g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(JcatItem) jcat_item = NULL; g_autoptr(JcatResult) jcat_result = NULL; g_autoptr(JcatResult) jcat_result_old = NULL; /* this should only be signing one thing */ jcat_item = jcat_file_get_item_default(jcat_file, error); if (jcat_item == NULL) return FALSE; results = jcat_context_verify_item(self->jcat_context, bytes_raw, jcat_item, jcat_flags, error); if (results == NULL) return FALSE; /* return the newest signature */ jcat_result = fu_engine_get_newest_signature_jcat_result(results, error); if (jcat_result == NULL) return FALSE; /* verify the metadata was signed later than the existing * metadata for this remote to mitigate a rollback attack */ jcat_result_old = fu_engine_get_system_jcat_result(self, remote, &error_local); if (jcat_result_old == NULL) { if (g_error_matches(error_local, G_FILE_ERROR, G_FILE_ERROR_NOENT)) { g_info("no existing valid keyrings: %s", error_local->message); } else { g_warning("could not get existing keyring result: %s", error_local->message); } } else { if (!fu_engine_validate_result_timestamp(jcat_result, jcat_result_old, error)) return FALSE; } } /* save XML and signature to remotes.d */ if (!fu_bytes_set_contents(fwupd_remote_get_filename_cache(remote), bytes_raw, error)) return FALSE; #ifdef HAVE_PASSIM /* send to passimd, if enabled and running */ if (passim_client_get_version(self->passim_client) != NULL && fwupd_remote_get_username(remote) == NULL && fwupd_remote_get_password(remote) == NULL && fu_engine_config_get_p2p_policy(self->config) & FU_P2P_POLICY_METADATA) { g_autofree gchar *basename = g_path_get_basename(fwupd_remote_get_filename_cache(remote)); g_autoptr(GError) error_passim = NULL; g_autoptr(PassimItem) passim_item = passim_item_new(); passim_item_set_basename(passim_item, basename); passim_item_set_bytes(passim_item, bytes_raw); passim_item_set_max_age(passim_item, fwupd_remote_get_refresh_interval(remote)); passim_item_set_share_limit(passim_item, 50); if (!passim_client_publish(self->passim_client, passim_item, &error_passim)) { if (!g_error_matches(error_passim, G_IO_ERROR, G_IO_ERROR_EXISTS)) { g_warning("failed to publish metadata to Passim: %s", error_passim->message); } } else { g_debug("published %s to Passim", passim_item_get_hash(passim_item)); } } #endif /* save signature to remotes.d */ if (keyring_kind != FWUPD_KEYRING_KIND_NONE) { if (!fu_bytes_set_contents(fwupd_remote_get_filename_cache_sig(remote), bytes_sig, error)) return FALSE; } if (!fu_engine_load_metadata_store(self, FU_ENGINE_LOAD_FLAG_NONE, error)) return FALSE; /* refresh SUPPORTED flag on devices */ fu_engine_md_refresh_devices(self); /* invalidate host security attributes */ g_clear_pointer(&self->host_security_id, g_free); /* make the UI update */ fu_engine_emit_changed(self); return TRUE; } /** * fu_engine_update_metadata: * @self: a #FuEngine * @remote_id: a remote ID, e.g. `lvfs` * @fd: file descriptor of the metadata * @fd_sig: file descriptor of the metadata signature * @error: (nullable): optional return location for an error * * Updates the metadata for a specific remote. * * Note: this will close the fds when done * * Returns: %TRUE for success **/ gboolean fu_engine_update_metadata(FuEngine *self, const gchar *remote_id, gint fd, gint fd_sig, GError **error) { #ifdef HAVE_GIO_UNIX g_autoptr(GBytes) bytes_raw = NULL; g_autoptr(GBytes) bytes_sig = NULL; g_autoptr(GInputStream) stream_fd = NULL; g_autoptr(GInputStream) stream_sig = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(remote_id != NULL, FALSE); g_return_val_if_fail(fd > 0, FALSE); g_return_val_if_fail(fd_sig > 0, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* ensures the fd's are closed on error */ stream_fd = g_unix_input_stream_new(fd, TRUE); stream_sig = g_unix_input_stream_new(fd_sig, TRUE); /* read the entire file into memory */ bytes_raw = fu_bytes_get_contents_stream(stream_fd, FU_ENGINE_MAX_METADATA_SIZE, error); if (bytes_raw == NULL) return FALSE; /* read signature */ bytes_sig = fu_bytes_get_contents_stream(stream_sig, FU_ENGINE_MAX_SIGNATURE_SIZE, error); if (bytes_sig == NULL) return FALSE; /* update with blobs */ return fu_engine_update_metadata_bytes(self, remote_id, bytes_raw, bytes_sig, error); #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Not supported as is unavailable"); return FALSE; #endif } /** * fu_engine_get_silo_from_blob: * @self: a #FuEngine * @blob_cab: a data blob * @error: (nullable): optional return location for an error * * Creates a silo from a .cab file blob. * * Returns: (transfer container): a #XbSilo, or %NULL **/ XbSilo * fu_engine_get_silo_from_blob(FuEngine *self, GBytes *blob_cab, GError **error) { g_autoptr(FuCabinet) cabinet = fu_cabinet_new(); g_return_val_if_fail(FU_IS_ENGINE(self), NULL); g_return_val_if_fail(blob_cab != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* load file */ fu_engine_set_status(self, FWUPD_STATUS_DECOMPRESSING); fu_firmware_set_size_max(FU_FIRMWARE(cabinet), fu_engine_config_get_archive_size_max(self->config)); fu_cabinet_set_jcat_context(cabinet, self->jcat_context); if (!fu_firmware_parse(FU_FIRMWARE(cabinet), blob_cab, FWUPD_INSTALL_FLAG_NONE, error)) return NULL; return fu_cabinet_get_silo(cabinet, error); } static FuDevice * fu_engine_get_result_from_component(FuEngine *self, FuEngineRequest *request, XbNode *component, GError **error) { g_autofree gchar *description_xpath = NULL; g_autoptr(FuDevice) dev = NULL; g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error_local = NULL; g_autoptr(GError) error_reqs = NULL; g_autoptr(GPtrArray) provides = NULL; g_autoptr(GPtrArray) tags = NULL; g_autoptr(XbNode) description = NULL; g_autoptr(XbNode) rel = NULL; g_autoptr(XbQuery) query = NULL; dev = fu_device_new(self->ctx); provides = xb_node_query(component, "provides/firmware[@type=$'flashed']", 0, &error_local); if (provides == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get release: %s", error_local->message); return NULL; } for (guint i = 0; i < provides->len; i++) { XbNode *prov = XB_NODE(g_ptr_array_index(provides, i)); const gchar *guid; g_autoptr(FuDevice) device = NULL; /* is a online or offline update appropriate */ guid = xb_node_get_text(prov); if (guid == NULL) continue; device = fu_device_list_get_by_guid(self->device_list, guid, NULL); if (device != NULL) { fu_device_incorporate(dev, device); } else { fu_device_inhibit(dev, "not-found", "Device was not found"); } /* add GUID */ fu_device_add_guid(dev, guid); } if (fu_device_get_guids(dev)->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "component has no GUIDs"); return NULL; } /* add tags */ tags = xb_node_query(component, "tags/tag[@namespace=$'lvfs']", 0, NULL); if (tags != NULL) { for (guint i = 0; i < tags->len; i++) { XbNode *tag = g_ptr_array_index(tags, i); fu_release_add_tag(release, xb_node_get_text(tag)); } } /* add EOL flag */ if (xb_node_get_attr(component, "date_eol") != NULL) fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_END_OF_LIFE); /* check we can install it */ fu_release_set_device(release, dev); fu_release_set_request(release, request); query = xb_query_new_full(xb_node_get_silo(component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, error); if (query == NULL) return NULL; rel = xb_node_query_first_full(component, query, &error_local); if (rel == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get release: %s", error_local->message); return NULL; } if (!fu_engine_load_release(self, release, component, rel, FWUPD_INSTALL_FLAG_IGNORE_VID_PID, &error_reqs)) { if (!fu_device_has_inhibit(dev, "not-found")) fu_device_inhibit(dev, "failed-reqs", error_reqs->message); /* continue */ } /* create a result with all the metadata in */ description_xpath = fu_engine_request_get_localized_xpath(request, "description"); description = xb_node_query_first(component, description_xpath, NULL); if (description != NULL) { g_autofree gchar *xml = NULL; xml = xb_node_export(description, XB_NODE_EXPORT_FLAG_ONLY_CHILDREN, NULL); if (xml != NULL) fu_device_set_description(dev, xml); } /* success */ fu_device_add_release(dev, FWUPD_RELEASE(release)); return g_steal_pointer(&dev); } static gint fu_engine_get_details_sort_cb(gconstpointer a, gconstpointer b) { FuDevice *device1 = *((FuDevice **)a); FuDevice *device2 = *((FuDevice **)b); if (!fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE) && fu_device_has_flag(device2, FWUPD_DEVICE_FLAG_UPDATABLE)) return 1; if (fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE) && !fu_device_has_flag(device2, FWUPD_DEVICE_FLAG_UPDATABLE)) return -1; return 0; } /* for self tests */ GPtrArray * fu_engine_get_details_for_bytes(FuEngine *self, FuEngineRequest *request, GBytes *blob, GError **error) { GChecksumType checksum_types[] = {G_CHECKSUM_SHA256, G_CHECKSUM_SHA1, 0}; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) components = NULL; g_autoptr(GPtrArray) details = NULL; g_autoptr(GPtrArray) checksums = g_ptr_array_new_with_free_func(g_free); g_autoptr(XbNode) component_by_csum = NULL; g_autoptr(XbSilo) silo = NULL; silo = fu_engine_get_silo_from_blob(self, blob, error); if (silo == NULL) return NULL; components = xb_silo_query(silo, "components/component[@type='firmware']", 0, &error_local); if (components == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "no components: %s", error_local->message); return NULL; } /* build the index */ if (!xb_silo_query_build_index(silo, "components/component[@type='firmware']/provides/firmware", "type", error)) return NULL; if (!xb_silo_query_build_index(silo, "components/component[@type='firmware']/provides/firmware", NULL, error)) return NULL; /* calculate the checksums of the blob */ for (guint i = 0; checksum_types[i] != 0; i++) g_ptr_array_add(checksums, g_compute_checksum_for_bytes(checksum_types[i], blob)); /* does this exist in any enabled remote */ for (guint i = 0; i < checksums->len; i++) { const gchar *csum = g_ptr_array_index(checksums, i); component_by_csum = fu_engine_get_component_for_checksum(self, csum); if (component_by_csum != NULL) break; } /* create results with all the metadata in */ details = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < components->len; i++) { XbNode *component = g_ptr_array_index(components, i); FuDevice *dev; g_autoptr(FwupdRelease) rel = fwupd_release_new(); dev = fu_engine_get_result_from_component(self, request, component, error); if (dev == NULL) return NULL; fu_device_add_release(dev, rel); if (component_by_csum != NULL) { const gchar *remote_id = xb_node_query_text(component_by_csum, "../custom/value[@key='fwupd::RemoteId']", NULL); if (remote_id != NULL) fwupd_release_set_remote_id(rel, remote_id); fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED); } /* add the checksum of the container blob */ for (guint j = 0; j < checksums->len; j++) { const gchar *csum = g_ptr_array_index(checksums, j); fwupd_release_add_checksum(rel, csum); } /* if this matched a device on the system, ensure all the * requirements passed before setting UPDATABLE */ if (fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)) { g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error_req = NULL; FwupdInstallFlags install_flags = FWUPD_INSTALL_FLAG_OFFLINE | FWUPD_INSTALL_FLAG_IGNORE_VID_PID | FWUPD_INSTALL_FLAG_ALLOW_REINSTALL | FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH | FWUPD_INSTALL_FLAG_ALLOW_OLDER; fu_release_set_device(release, dev); fu_release_set_request(release, request); if (!fu_engine_load_release(self, release, component, NULL, install_flags, &error_req)) { g_info("%s failed requirement checks: %s", fu_device_get_id(dev), error_req->message); fu_device_inhibit(dev, "failed-reqs", error_req->message); } else { g_info("%s passed requirement checks", fu_device_get_id(dev)); fu_device_uninhibit(dev, "failed-reqs"); } } g_ptr_array_add(details, dev); } /* order multiple devices so that the one that passes the requirement * is listed first */ g_ptr_array_sort(details, fu_engine_get_details_sort_cb); return g_steal_pointer(&details); } /** * fu_engine_get_details: * @self: a #FuEngine * @request: a #FuEngineRequest * @fd: a file descriptor * @error: (nullable): optional return location for an error * * Gets the details about a local file. * * Note: this will close the fd when done * * Returns: (transfer container) (element-type FuDevice): results **/ GPtrArray * fu_engine_get_details(FuEngine *self, FuEngineRequest *request, gint fd, GError **error) { g_autoptr(GBytes) blob = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), NULL); g_return_val_if_fail(fd > 0, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* get all components */ blob = fu_bytes_get_contents_fd(fd, fu_engine_config_get_archive_size_max(self->config), error); if (blob == NULL) return NULL; return fu_engine_get_details_for_bytes(self, request, blob, error); } static gint fu_engine_sort_devices_by_priority_name(gconstpointer a, gconstpointer b) { FuDevice *dev_a = *((FuDevice **)a); FuDevice *dev_b = *((FuDevice **)b); gint prio_a = fu_device_get_priority(dev_a); gint prio_b = fu_device_get_priority(dev_b); const gchar *name_a = fu_device_get_name(dev_a); const gchar *name_b = fu_device_get_name(dev_b); if (prio_a > prio_b) return -1; if (prio_a < prio_b) return 1; if (g_strcmp0(name_a, name_b) > 0) return 1; if (g_strcmp0(name_a, name_b) < 0) return -1; return 0; } /** * fu_engine_get_devices: * @self: a #FuEngine * @error: (nullable): optional return location for an error * * Gets the list of devices. * * Returns: (transfer container) (element-type FwupdDevice): results **/ GPtrArray * fu_engine_get_devices(FuEngine *self, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); devices = fu_device_list_get_active(self->device_list); if (devices->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No detected devices"); return NULL; } g_ptr_array_sort(devices, fu_engine_sort_devices_by_priority_name); return g_steal_pointer(&devices); } /** * fu_engine_get_devices_by_guid: * @self: a #FuEngine * @guid: a GUID * @error: (nullable): optional return location for an error * * Gets a specific device. * * Returns: (transfer full): a device, or %NULL if not found **/ GPtrArray * fu_engine_get_devices_by_guid(FuEngine *self, const gchar *guid, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices_tmp = NULL; /* find the devices by GUID */ devices_tmp = fu_device_list_get_active(self->device_list); devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < devices_tmp->len; i++) { FuDevice *dev_tmp = g_ptr_array_index(devices_tmp, i); if (fu_device_has_guid(dev_tmp, guid)) g_ptr_array_add(devices, g_object_ref(dev_tmp)); } /* nothing */ if (devices->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to find any device providing %s", guid); return NULL; } /* success */ return g_steal_pointer(&devices); } /** * fu_engine_get_devices_by_composite_id: * @self: a #FuEngine * @composite_id: a device ID * @error: (nullable): optional return location for an error * * Gets all active devices that match a specific composite ID. * * Returns: (transfer full) (element-type FuDevice): devices **/ GPtrArray * fu_engine_get_devices_by_composite_id(FuEngine *self, const gchar *composite_id, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices_tmp = NULL; /* find the devices by composite ID */ devices_tmp = fu_device_list_get_active(self->device_list); devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < devices_tmp->len; i++) { FuDevice *dev_tmp = g_ptr_array_index(devices_tmp, i); if (g_strcmp0(fu_device_get_composite_id(dev_tmp), composite_id) == 0) g_ptr_array_add(devices, g_object_ref(dev_tmp)); } /* nothing */ if (devices->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to find any device with composite ID %s", composite_id); return NULL; } /* success */ return g_steal_pointer(&devices); } static void fu_engine_get_history_set_hsi_attrs(FuEngine *self, FuDevice *device) { g_autoptr(GPtrArray) vals = NULL; /* ensure up to date */ fu_engine_ensure_security_attrs(self); /* add attributes */ vals = fu_security_attrs_get_all(self->host_security_attrs); for (guint i = 0; i < vals->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(vals, i); const gchar *tmp; tmp = fwupd_security_attr_result_to_string(fwupd_security_attr_get_result(attr)); fu_device_set_metadata(device, fwupd_security_attr_get_appstream_id(attr), tmp); } /* computed value */ fu_device_set_metadata(device, "HSI", self->host_security_id); } static void fu_engine_fixup_history_device(FuEngine *self, FuDevice *device) { FwupdRelease *rel; GPtrArray *csums; /* get the checksums */ rel = fu_device_get_release_default(device); if (rel == NULL) { g_warning("no checksums from release history"); return; } /* find the checksum that matches */ csums = fwupd_release_get_checksums(rel); for (guint j = 0; j < csums->len; j++) { const gchar *csum = g_ptr_array_index(csums, j); g_autoptr(XbNode) component = fu_engine_get_component_for_checksum(self, csum); if (component != NULL) { const gchar *appstream_id = xb_node_query_text(component, "id", NULL); const gchar *remote_id = xb_node_query_text(component, "../custom/value[@key='fwupd::RemoteId']", NULL); if (remote_id != NULL) fwupd_release_set_remote_id(rel, remote_id); if (appstream_id != NULL) fwupd_release_set_appstream_id(rel, appstream_id); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED); break; } } } /** * fu_engine_get_history: * @self: a #FuEngine * @error: (nullable): optional return location for an error * * Gets the list of history. * * Returns: (transfer container) (element-type FwupdDevice): results **/ GPtrArray * fu_engine_get_history(FuEngine *self, GError **error) { g_autoptr(GPtrArray) devices_all = NULL; g_autoptr(GPtrArray) devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_return_val_if_fail(FU_IS_ENGINE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); devices_all = fu_history_get_devices(self->history, error); if (devices_all == NULL) return NULL; for (guint i = 0; i < devices_all->len; i++) { FuDevice *dev = g_ptr_array_index(devices_all, i); if (fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_EMULATED)) continue; g_ptr_array_add(devices, g_object_ref(dev)); } if (devices->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No history"); return NULL; } /* if this is the system firmware device, add the HSI attrs */ for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index(devices, i); if (fu_device_has_internal_flag(dev, FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE)) fu_engine_get_history_set_hsi_attrs(self, dev); } /* try to set the remote ID for each device */ for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index(devices, i); fu_engine_fixup_history_device(self, dev); } return g_steal_pointer(&devices); } /** * fu_engine_get_remotes: * @self: a #FuEngine * @error: (nullable): optional return location for an error * * Gets the list of remotes in use by the engine. * * Returns: (transfer container) (element-type FwupdRemote): results **/ GPtrArray * fu_engine_get_remotes(FuEngine *self, GError **error) { GPtrArray *remotes; g_return_val_if_fail(FU_IS_ENGINE(self), NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); remotes = fu_remote_list_get_all(self->remote_list); if (remotes->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "No remotes configured"); return NULL; } /* deep copy so the remote list can be kept up to date */ return g_ptr_array_copy(remotes, (GCopyFunc)g_object_ref, NULL); } /** * fu_engine_get_remote_by_id: * @self: a #FuEngine * @remote_id: a string representation of a remote * @error: (nullable): optional return location for an error * * Gets the FwupdRemote object. * * Returns: FwupdRemote **/ FwupdRemote * fu_engine_get_remote_by_id(FuEngine *self, const gchar *remote_id, GError **error) { g_autoptr(GPtrArray) remotes = NULL; remotes = fu_engine_get_remotes(self, error); if (remotes == NULL) return NULL; for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index(remotes, i); if (g_strcmp0(remote_id, fwupd_remote_get_id(remote)) == 0) return remote; } g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Couldn't find remote %s", remote_id); return NULL; } static gint fu_engine_sort_releases_cb(gconstpointer a, gconstpointer b, gpointer user_data) { FuDevice *device = FU_DEVICE(user_data); FuRelease *rel_a = FU_RELEASE(*((FuRelease **)a)); FuRelease *rel_b = FU_RELEASE(*((FuRelease **)b)); gint rc; /* first by branch */ rc = g_strcmp0(fu_release_get_branch(rel_b), fu_release_get_branch(rel_a)); if (rc != 0) return rc; /* then by version */ rc = fu_version_compare(fu_release_get_version(rel_b), fu_release_get_version(rel_a), fu_device_get_version_format(device)); if (rc != 0) return rc; /* then by priority */ return fu_release_compare(rel_a, rel_b); } static gboolean fu_engine_check_release_is_approved(FuEngine *self, FwupdRelease *rel) { GPtrArray *csums = fwupd_release_get_checksums(rel); if (self->approved_firmware == NULL) return FALSE; for (guint i = 0; i < csums->len; i++) { const gchar *csum = g_ptr_array_index(csums, i); g_info("checking %s against approved list", csum); if (g_hash_table_lookup(self->approved_firmware, csum) != NULL) return TRUE; } return FALSE; } static gboolean fu_engine_check_release_is_blocked(FuEngine *self, FuRelease *release) { GPtrArray *csums = fu_release_get_checksums(release); if (self->blocked_firmware == NULL) return FALSE; for (guint i = 0; i < csums->len; i++) { const gchar *csum = g_ptr_array_index(csums, i); if (g_hash_table_lookup(self->blocked_firmware, csum) != NULL) return TRUE; } return FALSE; } static gboolean fu_engine_add_releases_for_device_component(FuEngine *self, FuEngineRequest *request, FuDevice *device, XbNode *component, GPtrArray *releases, GError **error) { FwupdFeatureFlags feature_flags; FwupdVersionFormat fmt = fu_device_get_version_format(device); g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) releases_tmp = NULL; FwupdInstallFlags install_flags = FWUPD_INSTALL_FLAG_OFFLINE | FWUPD_INSTALL_FLAG_IGNORE_VID_PID | FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH | FWUPD_INSTALL_FLAG_ALLOW_REINSTALL | FWUPD_INSTALL_FLAG_ALLOW_OLDER; /* get all releases */ releases_tmp = xb_node_query(component, "releases/release", 0, &error_local); if (releases_tmp == NULL) { if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) return TRUE; if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) return TRUE; g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } feature_flags = fu_engine_request_get_feature_flags(request); for (guint i = 0; i < releases_tmp->len; i++) { XbNode *rel = g_ptr_array_index(releases_tmp, i); const gchar *remote_id; const gchar *update_message; const gchar *update_image; const gchar *update_request_id; gint vercmp; GPtrArray *checksums; GPtrArray *locations; g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error_loop = NULL; /* create new FwupdRelease for the XbNode */ fu_release_set_request(release, request); fu_release_set_device(release, device); if (!fu_engine_load_release(self, release, component, rel, install_flags, &error_loop)) { g_debug("failed to set release for component: %s", error_loop->message); continue; } /* fall back to quirk-provided value */ if (fwupd_release_get_install_duration(FWUPD_RELEASE(release)) == 0) { fwupd_release_set_install_duration(FWUPD_RELEASE(release), fu_device_get_install_duration(device)); } /* invalid */ locations = fwupd_release_get_locations(FWUPD_RELEASE(release)); if (locations->len == 0) { g_autofree gchar *str = fwupd_release_to_string(FWUPD_RELEASE(release)); g_debug("no locations for %s", str); continue; } checksums = fu_release_get_checksums(release); if (checksums->len == 0) { g_autofree gchar *str = fwupd_release_to_string(FWUPD_RELEASE(release)); g_debug("no locations for %s", str); continue; } /* different branch */ if (g_strcmp0(fu_release_get_branch(release), fu_device_get_branch(device)) != 0) { if ((feature_flags & FWUPD_FEATURE_FLAG_SWITCH_BRANCH) == 0) { g_info("client does not understand branches, skipping %s:%s", fu_release_get_branch(release), fu_release_get_version(release)); continue; } fu_release_add_flag(release, FWUPD_RELEASE_FLAG_IS_ALTERNATE_BRANCH); } /* test for upgrade or downgrade */ vercmp = fu_version_compare(fu_release_get_version(release), fu_device_get_version(device), fmt); if (vercmp > 0) fu_release_add_flag(release, FWUPD_RELEASE_FLAG_IS_UPGRADE); else if (vercmp < 0) fu_release_add_flag(release, FWUPD_RELEASE_FLAG_IS_DOWNGRADE); /* lower than allowed to downgrade to */ if (fu_device_get_version_lowest(device) != NULL && fu_version_compare(fu_release_get_version(release), fu_device_get_version_lowest(device), fmt) < 0) { fu_release_add_flag(release, FWUPD_RELEASE_FLAG_BLOCKED_VERSION); } /* manually blocked */ if (fu_engine_check_release_is_blocked(self, release)) fu_release_add_flag(release, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL); /* check if remote is filtering firmware */ remote_id = fwupd_release_get_remote_id(FWUPD_RELEASE(release)); if (remote_id != NULL) { FwupdRemote *remote = fu_engine_get_remote_by_id(self, remote_id, NULL); if (remote != NULL && fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_APPROVAL_REQUIRED) && !fu_engine_check_release_is_approved(self, FWUPD_RELEASE(release))) { fu_release_add_flag(release, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL); } } /* add update message if exists but device doesn't already have one */ update_message = fwupd_release_get_update_message(FWUPD_RELEASE(release)); if (fwupd_device_get_update_message(FWUPD_DEVICE(device)) == NULL && update_message != NULL) { fu_device_set_update_message(device, update_message); } update_image = fwupd_release_get_update_image(FWUPD_RELEASE(release)); if (fwupd_device_get_update_image(FWUPD_DEVICE(device)) == NULL && update_image != NULL) { fwupd_device_set_update_image(FWUPD_DEVICE(device), update_image); } update_request_id = fu_release_get_update_request_id(release); if (fu_device_get_update_request_id(device) == NULL && update_request_id != NULL) { fu_device_add_request_flag(device, FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE); fu_device_set_update_request_id(device, update_request_id); } /* success */ g_ptr_array_add(releases, g_steal_pointer(&release)); /* if we're only checking for SUPPORTED then *any* release is good enough */ if (fu_engine_request_has_flag(request, FU_ENGINE_REQUEST_FLAG_ANY_RELEASE) && releases->len > 0) break; } /* success */ return TRUE; } static const gchar * fu_engine_get_branch_fallback(const gchar *nullable_branch) { if (nullable_branch == NULL) return "default"; return nullable_branch; } GPtrArray * fu_engine_get_releases_for_device(FuEngine *self, FuEngineRequest *request, FuDevice *device, GError **error) { GPtrArray *device_guids; g_autoptr(GPtrArray) branches = NULL; g_autoptr(GPtrArray) releases = NULL; /* no components in silo */ if (self->query_component_by_guid == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no components in silo"); return NULL; } /* get device version */ if (!fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VERSION) && !fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS)) { const gchar *version = fu_device_get_version(device); if (version == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no version set"); return NULL; } } /* only show devices that can be updated */ if (!fu_engine_request_has_feature_flag(request, FWUPD_FEATURE_FLAG_SHOW_PROBLEMS) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not updatable"); return NULL; } /* only show devices that can be updated */ if (!fu_engine_request_has_feature_flag(request, FWUPD_FEATURE_FLAG_REQUESTS_NON_GENERIC) && fu_device_has_request_flag(device, FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "is not updatable as requires a non-generic request"); return NULL; } /* get all the components that provide any of these GUIDs */ device_guids = fu_device_get_guids(device); releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint j = 0; j < device_guids->len; j++) { const gchar *guid = g_ptr_array_index(device_guids, j); g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) components = NULL; g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT(); xb_query_context_set_flags(&context, XB_QUERY_FLAG_USE_INDEXES); xb_value_bindings_bind_str(xb_query_context_get_bindings(&context), 0, guid, NULL); components = xb_silo_query_with_context(self->silo, self->query_component_by_guid, &context, &error_local); if (components == NULL) { g_debug("%s was not found: %s", guid, error_local->message); continue; } /* find all the releases that pass all the requirements */ g_debug("%s matched %u components", guid, components->len); for (guint i = 0; i < components->len; i++) { XbNode *component = XB_NODE(g_ptr_array_index(components, i)); g_autoptr(GError) error_tmp = NULL; if (!fu_engine_add_releases_for_device_component(self, request, device, component, releases, &error_tmp)) { g_debug("%s", error_tmp->message); continue; } } g_debug("%s matched %u releases", guid, releases->len); /* if we're only checking for SUPPORTED then *any* release is good enough */ if (fu_engine_request_has_flag(request, FU_ENGINE_REQUEST_FLAG_ANY_RELEASE) && releases->len > 0) break; } /* are there multiple branches available */ branches = g_ptr_array_new_with_free_func(g_free); g_ptr_array_add(branches, g_strdup(fu_engine_get_branch_fallback(fu_device_get_branch(device)))); for (guint i = 0; i < releases->len; i++) { FwupdRelease *rel_tmp = FWUPD_RELEASE(g_ptr_array_index(releases, i)); const gchar *branch_tmp = fu_engine_get_branch_fallback(fwupd_release_get_branch(rel_tmp)); if (g_ptr_array_find_with_equal_func(branches, branch_tmp, g_str_equal, NULL)) continue; g_ptr_array_add(branches, g_strdup(branch_tmp)); } if (branches->len > 1) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES); /* return the compound error */ if (releases->len == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No releases found"); return NULL; } return g_steal_pointer(&releases); } /** * fu_engine_get_releases: * @self: a #FuEngine * @request: a #FuEngineRequest * @device_id: a device ID * @error: (nullable): optional return location for an error * * Gets the releases available for a specific device. * * Returns: (transfer container) (element-type FwupdDevice): results **/ GPtrArray * fu_engine_get_releases(FuEngine *self, FuEngineRequest *request, const gchar *device_id, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(GPtrArray) releases_deduped = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), NULL); g_return_val_if_fail(device_id != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* find the device */ device = fu_device_list_get_by_id(self->device_list, device_id, error); if (device == NULL) return NULL; /* get all the releases for the device */ releases = fu_engine_get_releases_for_device(self, request, device, error); if (releases == NULL) return NULL; if (releases->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No releases for device"); return NULL; } g_ptr_array_sort_with_data(releases, fu_engine_sort_releases_cb, device); /* dedupe by container checksum */ if (fu_engine_config_get_release_dedupe(self->config)) { g_autoptr(GHashTable) checksums = g_hash_table_new(g_str_hash, g_str_equal); releases_deduped = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < releases->len; i++) { FuRelease *rel = g_ptr_array_index(releases, i); GPtrArray *csums = fu_release_get_checksums(rel); gboolean found = FALSE; /* find existing */ for (guint j = 0; j < csums->len; j++) { const gchar *csum = g_ptr_array_index(csums, j); if (g_hash_table_contains(checksums, csum)) { found = TRUE; break; } g_hash_table_add(checksums, (gpointer)csum); } if (found) { g_debug("found higher priority release for %s, skipping", fu_release_get_version(rel)); continue; } g_ptr_array_add(releases_deduped, g_object_ref(rel)); } } else { releases_deduped = g_ptr_array_ref(releases); } /* success */ return g_steal_pointer(&releases_deduped); } /** * fu_engine_get_downgrades: * @self: a #FuEngine * @request: a #FuEngineRequest * @device_id: a device ID * @error: (nullable): optional return location for an error * * Gets the downgrades available for a specific device. * * Returns: (transfer container) (element-type FwupdDevice): results **/ GPtrArray * fu_engine_get_downgrades(FuEngine *self, FuEngineRequest *request, const gchar *device_id, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(GPtrArray) releases_tmp = NULL; g_autoptr(GString) error_str = g_string_new(NULL); g_return_val_if_fail(FU_IS_ENGINE(self), NULL); g_return_val_if_fail(device_id != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* find the device */ device = fu_device_list_get_by_id(self->device_list, device_id, error); if (device == NULL) return NULL; /* get all the releases for the device */ releases_tmp = fu_engine_get_releases_for_device(self, request, device, error); if (releases_tmp == NULL) return NULL; releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < releases_tmp->len; i++) { FwupdRelease *rel_tmp = g_ptr_array_index(releases_tmp, i); /* same as installed */ if (!fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_UPGRADE) && !fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_DOWNGRADE)) { g_string_append_printf(error_str, "%s=same, ", fwupd_release_get_version(rel_tmp)); g_debug("ignoring %s as the same as %s", fwupd_release_get_version(rel_tmp), fu_device_get_version(device)); continue; } /* newer than current */ if (fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_UPGRADE)) { g_string_append_printf(error_str, "%s=newer, ", fwupd_release_get_version(rel_tmp)); g_debug("ignoring %s as newer than %s", fwupd_release_get_version(rel_tmp), fu_device_get_version(device)); continue; } /* don't show releases we are not allowed to downgrade to */ if (fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_BLOCKED_VERSION)) { g_string_append_printf(error_str, "%s=lowest, ", fwupd_release_get_version(rel_tmp)); g_debug("ignoring %s as older than lowest %s", fwupd_release_get_version(rel_tmp), fu_device_get_version_lowest(device)); continue; } /* different branch */ if (fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_ALTERNATE_BRANCH)) { g_info("ignoring release %s as branch %s, and device is %s", fwupd_release_get_version(rel_tmp), fwupd_release_get_branch(rel_tmp), fu_device_get_branch(device)); continue; } g_ptr_array_add(releases, g_object_ref(rel_tmp)); } if (error_str->len > 2) g_string_truncate(error_str, error_str->len - 2); if (releases->len == 0) { if (error_str->len > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "current version is %s: %s", fu_device_get_version(device), error_str->str); } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "current version is %s", fu_device_get_version(device)); } return NULL; } g_ptr_array_sort_with_data(releases, fu_engine_sort_releases_cb, device); return g_steal_pointer(&releases); } GPtrArray * fu_engine_get_approved_firmware(FuEngine *self) { GPtrArray *checksums = g_ptr_array_new_with_free_func(g_free); if (self->approved_firmware != NULL) { g_autoptr(GList) keys = g_hash_table_get_keys(self->approved_firmware); for (GList *l = keys; l != NULL; l = l->next) { const gchar *csum = l->data; g_ptr_array_add(checksums, g_strdup(csum)); } } return checksums; } void fu_engine_add_approved_firmware(FuEngine *self, const gchar *checksum) { if (self->approved_firmware == NULL) { self->approved_firmware = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); } g_hash_table_add(self->approved_firmware, g_strdup(checksum)); } GPtrArray * fu_engine_get_blocked_firmware(FuEngine *self) { GPtrArray *checksums = g_ptr_array_new_with_free_func(g_free); if (self->blocked_firmware != NULL) { g_autoptr(GList) keys = g_hash_table_get_keys(self->blocked_firmware); for (GList *l = keys; l != NULL; l = l->next) { const gchar *csum = l->data; g_ptr_array_add(checksums, g_strdup(csum)); } } return checksums; } static void fu_engine_add_blocked_firmware(FuEngine *self, const gchar *checksum) { if (self->blocked_firmware == NULL) { self->blocked_firmware = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); } g_hash_table_add(self->blocked_firmware, g_strdup(checksum)); } gboolean fu_engine_set_blocked_firmware(FuEngine *self, GPtrArray *checksums, GError **error) { /* update in-memory hash */ if (self->blocked_firmware != NULL) { g_hash_table_unref(self->blocked_firmware); self->blocked_firmware = NULL; } for (guint i = 0; i < checksums->len; i++) { const gchar *csum = g_ptr_array_index(checksums, i); fu_engine_add_blocked_firmware(self, csum); } /* save database */ if (!fu_history_clear_blocked_firmware(self->history, error)) return FALSE; for (guint i = 0; i < checksums->len; i++) { const gchar *csum = g_ptr_array_index(checksums, i); if (!fu_history_add_blocked_firmware(self->history, csum, error)) return FALSE; } return TRUE; } gchar * fu_engine_self_sign(FuEngine *self, const gchar *value, JcatSignFlags flags, GError **error) { g_autoptr(JcatBlob) jcat_signature = NULL; g_autoptr(JcatEngine) jcat_engine = NULL; g_autoptr(JcatResult) jcat_result = NULL; g_autoptr(GBytes) payload = NULL; /* create detached signature and verify */ jcat_engine = jcat_context_get_engine(self->jcat_context, JCAT_BLOB_KIND_PKCS7, error); if (jcat_engine == NULL) return NULL; payload = g_bytes_new(value, strlen(value)); jcat_signature = jcat_engine_self_sign(jcat_engine, payload, flags, error); if (jcat_signature == NULL) return NULL; jcat_result = jcat_engine_self_verify(jcat_engine, payload, jcat_blob_get_data(jcat_signature), JCAT_VERIFY_FLAG_NONE, error); if (jcat_result == NULL) return NULL; return jcat_blob_get_data_as_string(jcat_signature); } /** * fu_engine_get_upgrades: * @self: a #FuEngine * @request: a #FuEngineRequest * @device_id: a device ID * @error: (nullable): optional return location for an error * * Gets the upgrades available for a specific device. * * Returns: (transfer container) (element-type FwupdDevice): results **/ GPtrArray * fu_engine_get_upgrades(FuEngine *self, FuEngineRequest *request, const gchar *device_id, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(GPtrArray) releases_tmp = NULL; g_autoptr(GString) error_str = g_string_new(NULL); g_return_val_if_fail(FU_IS_ENGINE(self), NULL); g_return_val_if_fail(device_id != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* find the device */ device = fu_device_list_get_by_id(self->device_list, device_id, error); if (device == NULL) return NULL; /* there is no point checking each release */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Device is not updatable"); return NULL; } /* stay on one firmware version unless the new version is explicitly specified */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_ONLY_EXPLICIT_UPDATES)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Installing a specific release is explicitly required"); return NULL; } /* don't show upgrades again until we reboot */ if (fu_device_get_update_state(device) == FWUPD_UPDATE_STATE_NEEDS_REBOOT) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "A reboot is pending"); return NULL; } /* get all the releases for the device */ releases_tmp = fu_engine_get_releases_for_device(self, request, device, error); if (releases_tmp == NULL) return NULL; releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < releases_tmp->len; i++) { FwupdRelease *rel_tmp = g_ptr_array_index(releases_tmp, i); /* same as installed */ if (!fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_UPGRADE) && !fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_DOWNGRADE)) { g_string_append_printf(error_str, "%s=same, ", fwupd_release_get_version(rel_tmp)); g_debug("ignoring %s == %s", fwupd_release_get_version(rel_tmp), fu_device_get_version(device)); continue; } /* older than current */ if (fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_DOWNGRADE)) { g_string_append_printf(error_str, "%s=older, ", fwupd_release_get_version(rel_tmp)); g_debug("ignoring %s < %s", fwupd_release_get_version(rel_tmp), fu_device_get_version(device)); continue; } /* not approved */ if (fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL)) { g_string_append_printf(error_str, "%s=not-approved, ", fwupd_release_get_version(rel_tmp)); g_debug("ignoring %s as not approved as required by %s", fwupd_release_get_version(rel_tmp), fwupd_release_get_remote_id(rel_tmp)); continue; } /* different branch */ if (fwupd_release_has_flag(rel_tmp, FWUPD_RELEASE_FLAG_IS_ALTERNATE_BRANCH)) { g_info("ignoring release %s as branch %s, and device is %s", fwupd_release_get_version(rel_tmp), fwupd_release_get_branch(rel_tmp), fu_device_get_branch(device)); continue; } g_ptr_array_add(releases, g_object_ref(rel_tmp)); } if (error_str->len > 2) g_string_truncate(error_str, error_str->len - 2); if (releases->len == 0) { if (error_str->len > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "current version is %s: %s", fu_device_get_version(device), error_str->str); } else { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "current version is %s", fu_device_get_version(device)); } return NULL; } g_ptr_array_sort_with_data(releases, fu_engine_sort_releases_cb, device); return g_steal_pointer(&releases); } /** * fu_engine_clear_results: * @self: a #FuEngine * @device_id: a device ID * @error: (nullable): optional return location for an error * * Clear the historical state of a specific device operation. * * Returns: %TRUE for success **/ gboolean fu_engine_clear_results(FuEngine *self, const gchar *device_id, GError **error) { g_autoptr(FuDevice) device = NULL; FuPlugin *plugin; g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(device_id != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* find the device */ device = fu_engine_get_item_by_id_fallback_history(self, device_id, error); if (device == NULL) return FALSE; /* already set on the database */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NOTIFIED)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device already has notified flag"); return FALSE; } /* call into the plugin if it still exists */ plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), error); if (plugin != NULL) { if (!fu_plugin_runner_clear_results(plugin, device, error)) return FALSE; } /* if the offline update never got run, unstage it */ if (fu_device_get_update_state(device) == FWUPD_UPDATE_STATE_PENDING) fu_device_set_update_state(device, FWUPD_UPDATE_STATE_UNKNOWN); /* override */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NOTIFIED); return fu_history_modify_device(self->history, device, error); } /** * fu_engine_get_results: * @self: a #FuEngine * @device_id: a device ID * @error: (nullable): optional return location for an error * * Gets the historical state of a specific device operation. * * Returns: (transfer container): a device, or %NULL **/ FwupdDevice * fu_engine_get_results(FuEngine *self, const gchar *device_id, GError **error) { FwupdRelease *rel; g_autoptr(FuDevice) device = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), NULL); g_return_val_if_fail(device_id != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* find the device */ device = fu_engine_get_item_by_id_fallback_history(self, device_id, error); if (device == NULL) return NULL; /* the notification has already been shown to the user */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NOTIFIED)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "User has already been notified about %s [%s]", fu_device_get_name(device), fu_device_get_id(device)); return NULL; } /* try to set some release properties for the UI */ fu_engine_fixup_history_device(self, device); /* we did not either record or find the AppStream ID */ rel = fu_device_get_release_default(device); if (rel == NULL || fwupd_release_get_appstream_id(rel) == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device %s appstream id was not found", fu_device_get_id(device)); return NULL; } /* success */ return g_object_ref(FWUPD_DEVICE(device)); } static void fu_engine_plugins_startup(FuEngine *self, FuProgress *progress) { GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, plugins->len); for (guint i = 0; i < plugins->len; i++) { g_autoptr(GError) error = NULL; FuPlugin *plugin = g_ptr_array_index(plugins, i); if (!fu_plugin_runner_startup(plugin, fu_progress_get_child(progress), &error)) { fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_NO_HARDWARE); } g_info("disabling plugin because: %s", error->message); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_CHILD_FINISHED); } fu_progress_step_done(progress); } } static void fu_engine_plugins_ready(FuEngine *self, FuProgress *progress) { GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, plugins->len); for (guint i = 0; i < plugins->len; i++) { g_autoptr(GError) error = NULL; FuPlugin *plugin = g_ptr_array_index(plugins, i); if (!fu_plugin_runner_ready(plugin, fu_progress_get_child(progress), &error)) { if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_NO_HARDWARE); } g_info("disabling plugin because: %s", error->message); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_CHILD_FINISHED); } fu_progress_step_done(progress); } } static void fu_engine_plugins_coldplug(FuEngine *self, FuProgress *progress) { GPtrArray *plugins; g_autoptr(GString) str = g_string_new(NULL); /* exec */ plugins = fu_plugin_list_get_all(self->plugin_list); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, plugins->len); for (guint i = 0; i < plugins->len; i++) { g_autoptr(GError) error = NULL; FuPlugin *plugin = g_ptr_array_index(plugins, i); if (!fu_plugin_runner_coldplug(plugin, fu_progress_get_child(progress), &error)) { fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED); g_info("disabling plugin because: %s", error->message); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_CHILD_FINISHED); } fu_progress_step_done(progress); } /* print what we do have */ for (guint i = 0; i < plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index(plugins, i); if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) continue; g_string_append_printf(str, "%s, ", fu_plugin_get_name(plugin)); } if (str->len > 2) { g_string_truncate(str, str->len - 2); g_info("using plugins: %s", str->str); } } static void fu_engine_plugin_device_register(FuEngine *self, FuDevice *device) { GPtrArray *plugins; if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REGISTERED)) { g_warning("already registered %s, ignoring", fu_device_get_id(device)); return; } plugins = fu_plugin_list_get_all(self->plugin_list); for (guint i = 0; i < plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index(plugins, i); fu_plugin_runner_device_register(plugin, device); } for (guint i = 0; i < self->backends->len; i++) { FuBackend *backend = g_ptr_array_index(self->backends, i); fu_backend_registered(backend, device); } fu_device_add_flag(device, FWUPD_DEVICE_FLAG_REGISTERED); } static void fu_engine_plugin_device_register_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data) { FuEngine *self = FU_ENGINE(user_data); fu_engine_plugin_device_register(self, device); } static void fu_engine_plugin_device_added_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data) { FuEngine *self = FU_ENGINE(user_data); /* plugin has prio and device not already set from quirk */ if (fu_plugin_get_priority(plugin) > 0 && fu_device_get_priority(device) == 0) { g_info("auto-setting %s priority to %u", fu_device_get_id(device), fu_plugin_get_priority(plugin)); fu_device_set_priority(device, fu_plugin_get_priority(plugin)); } fu_engine_add_device(self, device); } static void fu_engine_adopt_children_device(FuEngine *self, FuDevice *device, FuDevice *device_tmp) { if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE_CHILD) && fu_device_has_internal_flag(device_tmp, FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE)) { fu_device_set_parent(device, device_tmp); fu_engine_ensure_device_supported(self, device_tmp); return; } if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE) && fu_device_has_internal_flag(device_tmp, FU_DEVICE_INTERNAL_FLAG_HOST_FIRMWARE_CHILD)) { fu_device_set_parent(device_tmp, device); fu_engine_ensure_device_supported(self, device_tmp); return; } if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_HOST_CPU_CHILD) && fu_device_has_internal_flag(device_tmp, FU_DEVICE_INTERNAL_FLAG_HOST_CPU)) { fu_device_set_parent(device, device_tmp); fu_engine_ensure_device_supported(self, device_tmp); return; } if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_HOST_CPU) && fu_device_has_internal_flag(device_tmp, FU_DEVICE_INTERNAL_FLAG_HOST_CPU_CHILD)) { fu_device_set_parent(device_tmp, device); fu_engine_ensure_device_supported(self, device_tmp); return; } } static void fu_engine_adopt_children(FuEngine *self, FuDevice *device) { GPtrArray *guids; g_autoptr(GPtrArray) devices = fu_device_list_get_active(self->device_list); /* find the parent in any existing device */ for (guint i = 0; fu_device_get_parent(device) == NULL && i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); fu_engine_adopt_children_device(self, device, device_tmp); } if (fu_device_get_parent(device) == NULL) { for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); if (!fu_device_has_internal_flag( device_tmp, FU_DEVICE_INTERNAL_FLAG_AUTO_PARENT_CHILDREN)) continue; if (fu_device_get_physical_id(device_tmp) == NULL) continue; if (fu_device_has_parent_physical_id( device, fu_device_get_physical_id(device_tmp))) { fu_device_set_parent(device, device_tmp); fu_engine_ensure_device_supported(self, device_tmp); break; } } } if (fu_device_get_parent(device) == NULL) { for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); if (!fu_device_has_internal_flag( device_tmp, FU_DEVICE_INTERNAL_FLAG_AUTO_PARENT_CHILDREN)) continue; if (fu_device_get_backend_id(device_tmp) == NULL) continue; if (fu_device_has_parent_backend_id(device, fu_device_get_backend_id(device_tmp))) { fu_device_set_parent(device, device_tmp); fu_engine_ensure_device_supported(self, device_tmp); break; } } } if (fu_device_get_parent(device) == NULL) { guids = fu_device_get_parent_guids(device); for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index(guids, j); for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); if (fu_device_has_guid(device_tmp, guid)) { fu_device_set_parent(device, device_tmp); fu_engine_ensure_device_supported(self, device_tmp); break; } } } } /* the new device is the parent to an existing child */ for (guint j = 0; j < devices->len; j++) { GPtrArray *parent_physical_ids = NULL; FuDevice *device_tmp = g_ptr_array_index(devices, j); if (fu_device_get_parent(device_tmp) != NULL) continue; parent_physical_ids = fu_device_get_parent_physical_ids(device_tmp); if (parent_physical_ids == NULL) continue; for (guint i = 0; i < parent_physical_ids->len; i++) { const gchar *parent_physical_id = g_ptr_array_index(parent_physical_ids, i); if (g_strcmp0(parent_physical_id, fu_device_get_physical_id(device)) == 0) fu_device_set_parent(device_tmp, device); } } for (guint j = 0; j < devices->len; j++) { GPtrArray *parent_backend_ids = NULL; FuDevice *device_tmp = g_ptr_array_index(devices, j); if (fu_device_get_parent(device_tmp) != NULL) continue; parent_backend_ids = fu_device_get_parent_backend_ids(device_tmp); if (parent_backend_ids == NULL) continue; for (guint i = 0; i < parent_backend_ids->len; i++) { const gchar *parent_backend_id = g_ptr_array_index(parent_backend_ids, i); if (g_strcmp0(parent_backend_id, fu_device_get_backend_id(device)) == 0) fu_device_set_parent(device_tmp, device); } } guids = fu_device_get_guids(device); for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index(guids, j); for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); if (fu_device_get_parent(device_tmp) != NULL) continue; if (fu_device_has_parent_guid(device_tmp, guid)) fu_device_set_parent(device_tmp, device); } } } static void fu_engine_set_proxy_device(FuEngine *self, FuDevice *device) { GPtrArray *guids; g_autoptr(FuDevice) proxy = NULL; g_autoptr(GPtrArray) devices = NULL; if (fu_device_get_proxy(device) != NULL) return; if (fu_device_get_proxy_guid(device) == NULL) return; /* find the proxy GUID in any existing device */ proxy = fu_device_list_get_by_guid(self->device_list, fu_device_get_proxy_guid(device), NULL); if (proxy != NULL) { g_info("setting proxy of %s to %s for %s", fu_device_get_id(proxy), fu_device_get_id(device), fu_device_get_proxy_guid(device)); fu_device_set_proxy(device, proxy); return; } /* are we the parent of an existing device */ guids = fu_device_get_guids(device); for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index(guids, j); devices = fu_device_list_get_active(self->device_list); for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); if (g_strcmp0(fu_device_get_proxy_guid(device_tmp), guid) == 0) { g_info("adding proxy of %s to %s for %s", fu_device_get_id(device), fu_device_get_id(device_tmp), guid); fu_device_set_proxy(device_tmp, device); return; } } } /* nothing found */ g_warning("did not find proxy device %s", fu_device_get_proxy_guid(device)); } static void fu_engine_device_inherit_history(FuEngine *self, FuDevice *device) { g_autoptr(FuDevice) device_history = NULL; /* ignore */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) return; /* any success or failed update? */ device_history = fu_history_get_device_by_id(self->history, fu_device_get_id(device), NULL); if (device_history == NULL) return; /* in an offline environment we may have used the .cab file to find the version-format * to use for the device -- so when we reboot use the database as the archive data is no * longer available */ if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT) && fu_device_get_version_format(device_history) != FWUPD_VERSION_FORMAT_UNKNOWN) { g_debug( "absorbing version format %s into %s from history database", fwupd_version_format_to_string(fu_device_get_version_format(device_history)), fu_device_get_id(device)); fu_device_set_version_format(device, fu_device_get_version_format(device_history)); } /* the device is still running the old firmware version and so if it * required activation before, it still requires it now -- note: * we can't just check for version_new=version to allow for re-installs */ if (fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_INHERIT_ACTIVATION) && fu_device_has_flag(device_history, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { FwupdRelease *release = fu_device_get_release_default(device_history); if (fu_version_compare(fu_device_get_version(device), fwupd_release_get_version(release), fu_device_get_version_format(device)) != 0) { g_info("inheriting needs-activation for %s as version %s != %s", fu_device_get_name(device), fu_device_get_version(device), fwupd_release_get_version(release)); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); } } } static void fu_engine_ensure_device_emulation_tag(FuEngine *self, FuDevice *device) { /* already done */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG)) return; /* we matched this physical ID */ if (fu_device_get_backend_id(device) == NULL) return; if (!g_hash_table_contains(self->emulation_backend_ids, fu_device_get_backend_id(device))) return; /* success */ g_info("adding emulation-tag to %s", fu_device_get_backend_id(device)); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG); fu_engine_check_context_flag_save_events(self); } void fu_engine_add_device(FuEngine *self, FuDevice *device) { GPtrArray *disabled_devices; GPtrArray *device_guids; g_autoptr(XbNode) component = NULL; /* device has no GUIDs set! */ device_guids = fu_device_get_guids(device); if (device_guids->len == 0) { g_warning("no GUIDs for device %s [%s]", fu_device_get_name(device), fu_device_get_id(device)); return; } /* is this GUID disabled */ disabled_devices = fu_engine_config_get_disabled_devices(self->config); for (guint i = 0; i < disabled_devices->len; i++) { const gchar *disabled_guid = g_ptr_array_index(disabled_devices, i); for (guint j = 0; j < device_guids->len; j++) { const gchar *device_guid = g_ptr_array_index(device_guids, j); if (g_strcmp0(disabled_guid, device_guid) == 0) { g_info("%s [%s] is disabled [%s], ignoring from %s", fu_device_get_name(device), fu_device_get_id(device), device_guid, fu_device_get_plugin(device)); return; } } } /* does the device not have an assigned protocol */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE) && fu_device_get_protocols(device)->len == 0) { g_warning("device %s [%s] does not define an update protocol", fu_device_get_id(device), fu_device_get_name(device)); } #ifndef SUPPORTED_BUILD /* we don't know if this device has a signed or unsigned payload */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD) && !fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED)) { g_warning("%s [%s] does not declare signed/unsigned payload -- perhaps add " "fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);", fu_device_get_plugin(device), fu_device_get_id(device)); } #endif /* if this device is locked get some metadata from AppStream */ component = fu_engine_get_component_by_guids(self, device); if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_LOCKED)) { if (component != NULL) { g_autoptr(XbNode) rel = NULL; rel = xb_node_query_first(component, "releases/release", NULL); if (rel != NULL) { g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error_local = NULL; fu_release_set_device(release, device); if (!fu_engine_load_release(self, release, component, rel, FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_warning("failed to set AppStream release: %s", error_local->message); } else { fu_device_add_release(device, FWUPD_RELEASE(release)); } } } } /* check if the device needs emulation-tag */ fu_engine_ensure_device_emulation_tag(self, device); /* set or clear the SUPPORTED flag */ fu_engine_ensure_device_supported(self, device); /* adopt any required children, which may or may not already exist */ fu_engine_adopt_children(self, device); /* set the proxy device if specified by GUID */ fu_engine_set_proxy_device(self, device); /* set any alternate objects on the device from the ID */ if (fu_device_get_alternate_id(device) != NULL) { g_autoptr(FuDevice) device_alt = NULL; device_alt = fu_device_list_get_by_id(self->device_list, fu_device_get_alternate_id(device), NULL); if (device_alt != NULL) fu_device_set_alternate(device, device_alt); } /* sometimes inherit flags from recent history */ fu_engine_device_inherit_history(self, device); /* notify all plugins about this new device */ if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REGISTERED)) fu_engine_plugin_device_register(self, device); if (fu_device_get_version_format(device) == FWUPD_VERSION_FORMAT_UNKNOWN && fu_version_guess_format(fu_device_get_version(device)) == FWUPD_VERSION_FORMAT_NUMBER) { fu_device_inhibit(device, "version-format", "VersionFormat is ambiguous"); } /* no vendor-id, and so no way to lock it down! */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE) && fu_device_get_vendor_ids(device)->len == 0) { fu_device_inhibit(device, "vendor-id", "No vendor ID set"); } /* create new device */ fu_device_list_add(self->device_list, device); /* clean up any state only valid for ->probe */ fu_device_probe_complete(device); /* fix order */ fu_device_list_depsolve_order(self->device_list, device); /* save to emulated phase, but avoid overwriting reload */ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) && self->install_phase == FU_ENGINE_INSTALL_PHASE_SETUP && fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) { g_autoptr(GError) error_local = NULL; if (!fu_engine_backends_save_phase(self, &error_local)) g_warning("failed to save phase: %s", error_local->message); } fu_engine_emit_changed(self); } static void fu_engine_plugin_rules_changed_cb(FuPlugin *plugin, gpointer user_data) { FuEngine *self = FU_ENGINE(user_data); GPtrArray *rules = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_INHIBITS_IDLE); if (rules == NULL) return; for (guint j = 0; j < rules->len; j++) { const gchar *tmp = g_ptr_array_index(rules, j); fu_idle_inhibit(self->idle, FU_IDLE_INHIBIT_TIMEOUT, tmp); } } static void fu_engine_context_security_changed_cb(FuContext *ctx, gpointer user_data) { FuEngine *self = FU_ENGINE(user_data); /* invalidate host security attributes */ g_clear_pointer(&self->host_security_id, g_free); /* make UI refresh */ fu_engine_emit_changed(self); } static void fu_engine_plugin_device_removed_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data) { FuEngine *self = (FuEngine *)user_data; FuPlugin *plugin_old; g_autoptr(FuDevice) device_tmp = NULL; g_autoptr(GError) error = NULL; device_tmp = fu_device_list_get_by_id(self->device_list, fu_device_get_id(device), &error); if (device_tmp == NULL) { g_info("failed to find device %s: %s", fu_device_get_id(device), error->message); return; } /* get the plugin */ plugin_old = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(device), &error); if (plugin_old == NULL) { g_info("failed to find plugin %s: %s", fu_device_get_plugin(device), error->message); return; } /* check this came from the same plugin */ if (g_strcmp0(fu_plugin_get_name(plugin), fu_plugin_get_name(plugin_old)) != 0) { g_info("ignoring duplicate removal from %s", fu_plugin_get_name(plugin)); return; } /* make the UI update */ fu_device_list_remove(self->device_list, device); fu_engine_emit_changed(self); } /* this is called by the self tests as well */ void fu_engine_add_plugin(FuEngine *self, FuPlugin *plugin) { fu_plugin_list_add(self->plugin_list, plugin); } gboolean fu_engine_is_uid_trusted(FuEngine *self, guint64 calling_uid) { GArray *trusted; /* root is always trusted */ if (calling_uid == 0) return TRUE; trusted = fu_engine_config_get_trusted_uids(self->config); for (guint i = 0; i < trusted->len; i++) { if (calling_uid == g_array_index(trusted, guint64, i)) return TRUE; } return FALSE; } static gboolean fu_engine_is_test_plugin_disabled(FuEngine *self, FuPlugin *plugin) { if (!fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_TEST_ONLY)) return FALSE; if (fu_engine_config_get_test_devices(self->config)) return FALSE; return TRUE; } static gboolean fu_engine_is_plugin_name_disabled(FuEngine *self, const gchar *name) { GPtrArray *disabled = fu_engine_config_get_disabled_plugins(self->config); for (guint i = 0; i < disabled->len; i++) { const gchar *name_tmp = g_ptr_array_index(disabled, i); if (g_strcmp0(name_tmp, name) == 0) return TRUE; } return FALSE; } static gboolean fu_engine_is_plugin_name_enabled(FuEngine *self, const gchar *name) { if (self->plugin_filter->len == 0) return TRUE; for (guint i = 0; i < self->plugin_filter->len; i++) { const gchar *name_tmp = g_ptr_array_index(self->plugin_filter, i); if (g_pattern_match_simple(name_tmp, name)) return TRUE; } return FALSE; } void fu_engine_add_plugin_filter(FuEngine *self, const gchar *plugin_glob) { GString *str; g_return_if_fail(FU_IS_ENGINE(self)); g_return_if_fail(plugin_glob != NULL); str = g_string_new(plugin_glob); g_string_replace(str, "-", "_", 0); g_ptr_array_add(self->plugin_filter, g_string_free(str, FALSE)); } static gboolean fu_engine_plugin_check_supported_cb(FuPlugin *plugin, const gchar *guid, FuEngine *self) { g_autoptr(XbNode) n = NULL; g_autofree gchar *xpath = NULL; if (fu_engine_config_get_enumerate_all_devices(self->config)) return TRUE; xpath = g_strdup_printf("components/component[@type='firmware']/" "provides/firmware[@type='flashed'][text()='%s']", guid); n = xb_silo_query_first(self->silo, xpath, NULL); return n != NULL; } FuEngineConfig * fu_engine_get_config(FuEngine *self) { g_return_val_if_fail(FU_IS_ENGINE(self), NULL); return self->config; } const gchar * fu_engine_get_host_vendor(FuEngine *self) { const gchar *result = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), NULL); result = fu_context_get_hwid_value(self->ctx, FU_HWIDS_KEY_MANUFACTURER); return result != NULL ? result : "Unknown Vendor"; } const gchar * fu_engine_get_host_product(FuEngine *self) { const gchar *result = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), NULL); result = fu_context_get_hwid_value(self->ctx, FU_HWIDS_KEY_PRODUCT_NAME); return result != NULL ? result : "Unknown Product"; } const gchar * fu_engine_get_host_machine_id(FuEngine *self) { g_return_val_if_fail(FU_IS_ENGINE(self), NULL); return self->host_machine_id; } const gchar * fu_engine_get_host_bkc(FuEngine *self) { g_return_val_if_fail(FU_IS_ENGINE(self), NULL); if (fu_engine_config_get_host_bkc(self->config) == NULL) return ""; return fu_engine_config_get_host_bkc(self->config); } #ifdef HAVE_HSI static void fu_engine_ensure_security_attrs_supported_cpu(FuEngine *self) { g_autoptr(FwupdSecurityAttr) attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_SUPPORTED_CPU); fwupd_security_attr_set_plugin(attr, "core"); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_VALID); fu_security_attrs_append(self->host_security_attrs, attr); } static void fu_engine_ensure_security_attrs_tainted(FuEngine *self) { gboolean disabled_plugins = FALSE; GPtrArray *disabled = fu_engine_config_get_disabled_plugins(self->config); g_autoptr(FwupdSecurityAttr) attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_FWUPD_PLUGINS); fwupd_security_attr_set_plugin(attr, "core"); fwupd_security_attr_set_result_success(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fu_security_attrs_append(self->host_security_attrs, attr); for (guint i = 0; i < disabled->len; i++) { const gchar *name_tmp = g_ptr_array_index(disabled, i); if (!g_str_has_prefix(name_tmp, "test")) { disabled_plugins = TRUE; break; } } if (self->plugin_filter->len > 0 || disabled_plugins) { fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS); return; } /* success */ fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS); } /* * Get chassis type from SMBIOS data and verify HSI makes sense for it */ static gchar * fu_engine_attrs_calculate_hsi_for_chassis(FuEngine *self) { FuSmbiosChassisKind chassis_kind = fu_context_get_chassis_kind(self->ctx); /* if emulating, force the chassis type to be valid */ if (self->host_emulation && (chassis_kind == FU_SMBIOS_CHASSIS_KIND_OTHER || chassis_kind == FU_SMBIOS_CHASSIS_KIND_UNKNOWN)) { g_info("forcing chassis kind [0x%x] to be valid", chassis_kind); chassis_kind = FU_SMBIOS_CHASSIS_KIND_DESKTOP; } switch (chassis_kind) { case FU_SMBIOS_CHASSIS_KIND_DESKTOP: case FU_SMBIOS_CHASSIS_KIND_LOW_PROFILE_DESKTOP: case FU_SMBIOS_CHASSIS_KIND_MINI_TOWER: case FU_SMBIOS_CHASSIS_KIND_TOWER: case FU_SMBIOS_CHASSIS_KIND_PORTABLE: case FU_SMBIOS_CHASSIS_KIND_LAPTOP: case FU_SMBIOS_CHASSIS_KIND_NOTEBOOK: case FU_SMBIOS_CHASSIS_KIND_ALL_IN_ONE: case FU_SMBIOS_CHASSIS_KIND_SUB_NOTEBOOK: case FU_SMBIOS_CHASSIS_KIND_LUNCH_BOX: case FU_SMBIOS_CHASSIS_KIND_MAIN_SERVER: case FU_SMBIOS_CHASSIS_KIND_TABLET: case FU_SMBIOS_CHASSIS_KIND_CONVERTIBLE: case FU_SMBIOS_CHASSIS_KIND_DETACHABLE: case FU_SMBIOS_CHASSIS_KIND_IOT_GATEWAY: case FU_SMBIOS_CHASSIS_KIND_EMBEDDED_PC: case FU_SMBIOS_CHASSIS_KIND_MINI_PC: case FU_SMBIOS_CHASSIS_KIND_STICK_PC: return fu_security_attrs_calculate_hsi(self->host_security_attrs, FU_SECURITY_ATTRS_FLAG_ADD_VERSION); default: break; } return g_strdup_printf("HSI:INVALID:chassis[0x%02x] (v%d.%d.%d)", chassis_kind, FWUPD_MAJOR_VERSION, FWUPD_MINOR_VERSION, FWUPD_MICRO_VERSION); } static gboolean fu_engine_record_security_attrs(FuEngine *self, GError **error) { g_autoptr(GPtrArray) attrs_array = NULL; g_autofree gchar *json = NULL; /* convert attrs to json string */ json = fu_security_attrs_to_json_string(self->host_security_attrs, error); if (json == NULL) { g_prefix_error(error, "cannot convert current attrs to string: "); return FALSE; } /* check that we did not store this already last boot */ attrs_array = fu_history_get_security_attrs(self->history, 1, error); if (attrs_array == NULL) { g_prefix_error(error, "failed to get historical attr: "); return FALSE; } if (attrs_array->len > 0) { FuSecurityAttrs *attrs_tmp = g_ptr_array_index(attrs_array, 0); if (fu_security_attrs_equal(attrs_tmp, self->host_security_attrs)) { g_info("skipping writing HSI attrs to database as unchanged"); return TRUE; } } /* write new values */ if (!fu_history_add_security_attribute(self->history, json, self->host_security_id, error)) { g_prefix_error(error, "failed to write to DB: "); return FALSE; } /* success */ return TRUE; } static void fu_engine_security_attrs_depsolve(FuEngine *self) { g_autoptr(GPtrArray) items = NULL; /* set the obsoletes flag for each attr */ fu_security_attrs_depsolve(self->host_security_attrs); /* set the fallback names for clients without native translations */ items = fu_security_attrs_get_all(self->host_security_attrs); for (guint i = 0; i < items->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(items, i); if (fwupd_security_attr_get_name(attr) == NULL) { g_autofree gchar *name_tmp = fu_security_attr_get_name(attr); if (name_tmp == NULL) { g_warning("failed to get fallback for %s", fwupd_security_attr_get_appstream_id(attr)); continue; } fwupd_security_attr_set_name(attr, name_tmp); } if (fwupd_security_attr_get_title(attr) == NULL) fwupd_security_attr_set_title(attr, fu_security_attr_get_title(attr)); if (fwupd_security_attr_get_description(attr) == NULL) { fwupd_security_attr_set_description(attr, fu_security_attr_get_description(attr)); } } /* distil into one simple string */ g_free(self->host_security_id); self->host_security_id = fu_engine_attrs_calculate_hsi_for_chassis(self); } #endif /** * fu_history_get_previous_security_attr: * @self: a #FuHistory * @appstream_id: maximum number of attributes to return, or 0 for no limit * @current_setting: (nullable): current value * @error: return location for a #GError, or %NULL * * Gets the security attributes of the previous BIOS setting for the given * appstream ID and current BIOS config. * * Returns: (element-type #FuSecurityAttr) (transfer full): attr, or %NULL **/ static FwupdSecurityAttr * fu_engine_get_previous_bios_security_attr(FuEngine *self, const gchar *appstream_id, const gchar *current_setting, GError **error) { g_autoptr(GPtrArray) attrs_array = NULL; attrs_array = fu_history_get_security_attrs(self->history, 20, error); if (attrs_array == NULL) return NULL; for (guint i = 0; i < attrs_array->len; i++) { FuSecurityAttrs *attrs = g_ptr_array_index(attrs_array, i); g_autoptr(GPtrArray) attr_items = fu_security_attrs_get_all(attrs); for (guint j = 0; j < attr_items->len; j++) { FwupdSecurityAttr *attr = g_ptr_array_index(attr_items, j); if (g_strcmp0(appstream_id, fwupd_security_attr_get_appstream_id(attr)) == 0 && g_strcmp0(current_setting, fwupd_security_attr_get_bios_setting_current_value(attr)) != 0) { g_debug("found previous BIOS setting for %s: %s", appstream_id, fwupd_security_attr_get_bios_setting_id(attr)); return g_object_ref(attr); } } } /* failed */ g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot find previous BIOS value"); return NULL; } /** * fu_engine_fix_host_security_attr: * @self: a #FuEngine * @appstream_id: the Appstream ID * @error: (nullable): optional return location for an error * * Fix one specific security attribute. * * Returns: %TRUE for success **/ gboolean fu_engine_fix_host_security_attr(FuEngine *self, const gchar *appstream_id, GError **error) { FuPlugin *plugin; FwupdBiosSetting *bios_attr; g_autoptr(FwupdSecurityAttr) hsi_attr = NULL; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); fu_engine_ensure_security_attrs(self); hsi_attr = fu_security_attrs_get_by_appstream_id(self->host_security_attrs, appstream_id, error); if (hsi_attr == NULL) return FALSE; if (!fwupd_security_attr_has_flag(hsi_attr, FWUPD_SECURITY_ATTR_FLAG_CAN_FIX)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot auto-fix attribute"); return FALSE; } plugin = fu_plugin_list_find_by_name(self->plugin_list, fwupd_security_attr_get_plugin(hsi_attr), error); if (plugin == NULL) return FALSE; /* first try the per-plugin vfunc */ if (!fu_plugin_runner_fix_host_security_attr(plugin, hsi_attr, &error_local)) { if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } g_debug("ignoring %s", error_local->message); } else { g_info("fixed %s", fwupd_security_attr_get_appstream_id(hsi_attr)); return TRUE; } /* fall back to setting the BIOS attribute */ if (fwupd_security_attr_get_bios_setting_id(hsi_attr) == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no BIOS setting ID set"); return FALSE; } bios_attr = fu_context_get_bios_setting(self->ctx, fwupd_security_attr_get_bios_setting_id(hsi_attr)); if (bios_attr == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot get BIOS setting %s", fwupd_security_attr_get_bios_setting_id(hsi_attr)); return FALSE; } return fwupd_bios_setting_write_value( bios_attr, fwupd_security_attr_get_bios_setting_target_value(hsi_attr), error); } /** * fu_engine_fix_host_security_attr: * @self: a #FuEngine * @appstream_id: the Appstream ID * @error: (nullable): optional return location for an error * * Revert the fix for one specific security attribute. * * Returns: %TRUE for success **/ gboolean fu_engine_undo_host_security_attr(FuEngine *self, const gchar *appstream_id, GError **error) { FuPlugin *plugin; FwupdBiosSetting *bios_attr; g_autoptr(FwupdSecurityAttr) hsi_attr = NULL; g_autoptr(FwupdSecurityAttr) hsi_attr_old = NULL; g_autoptr(GError) error_local = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); fu_engine_ensure_security_attrs(self); hsi_attr = fu_security_attrs_get_by_appstream_id(self->host_security_attrs, appstream_id, error); if (hsi_attr == NULL) return FALSE; if (!fwupd_security_attr_has_flag(hsi_attr, FWUPD_SECURITY_ATTR_FLAG_CAN_UNDO)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot auto-undo attribute"); return FALSE; } plugin = fu_plugin_list_find_by_name(self->plugin_list, fwupd_security_attr_get_plugin(hsi_attr), error); if (plugin == NULL) return FALSE; /* first try the per-plugin vfunc */ if (!fu_plugin_runner_undo_host_security_attr(plugin, hsi_attr, &error_local)) { if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } /* fall back to setting the BIOS attribute */ if (fwupd_security_attr_get_bios_setting_id(hsi_attr) == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no BIOS setting ID"); return FALSE; } bios_attr = fu_context_get_bios_setting(self->ctx, fwupd_security_attr_get_bios_setting_id(hsi_attr)); if (bios_attr == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot get BIOS setting %s", fwupd_security_attr_get_bios_setting_id(hsi_attr)); return FALSE; } if (fwupd_security_attr_get_bios_setting_current_value(hsi_attr) == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no BIOS setting current value"); return FALSE; } hsi_attr_old = fu_engine_get_previous_bios_security_attr( self, appstream_id, fwupd_security_attr_get_bios_setting_current_value(hsi_attr), error); if (hsi_attr_old == NULL) return FALSE; return fwupd_bios_setting_write_value( bios_attr, fwupd_security_attr_get_bios_setting_current_value(hsi_attr_old), error); } static gboolean fu_engine_security_attrs_from_json(FuEngine *self, JsonNode *json_node, GError **error) { JsonObject *obj; /* sanity check */ if (!JSON_NODE_HOLDS_OBJECT(json_node)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not JSON object"); return FALSE; } /* not supplied */ obj = json_node_get_object(json_node); if (!json_object_has_member(obj, "SecurityAttributes")) return TRUE; if (!fu_security_attrs_from_json(self->host_security_attrs, json_node, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_engine_devices_from_json(FuEngine *self, JsonNode *json_node, GError **error) { JsonArray *array; JsonObject *obj; /* sanity check */ if (!JSON_NODE_HOLDS_OBJECT(json_node)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not JSON object"); return FALSE; } /* not supplied */ obj = json_node_get_object(json_node); if (!json_object_has_member(obj, "Devices")) return TRUE; /* this has to exist */ array = json_object_get_array_member(obj, "Devices"); for (guint i = 0; i < json_array_get_length(array); i++) { JsonNode *node_tmp = json_array_get_element(array, i); g_autoptr(FuDevice) device = fu_device_new(self->ctx); if (!fwupd_device_from_json(FWUPD_DEVICE(device), node_tmp, error)) return FALSE; fu_device_set_plugin(device, "dummy"); fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_IS_EMULATED); if (!fu_device_setup(device, error)) return FALSE; fu_engine_add_device(self, device); } /* success */ return TRUE; } static gboolean fu_engine_load_host_emulation(FuEngine *self, const gchar *fn, GError **error) { g_autoptr(JsonParser) parser = json_parser_new(); g_autoptr(GFile) file = g_file_new_for_path(fn); g_autoptr(GInputStream) istream_json = NULL; g_autoptr(GInputStream) istream_raw = NULL; g_autoptr(FwupdSecurityAttr) attr = NULL; g_autoptr(FuBiosSettings) bios_settings = fu_context_get_bios_settings(self->ctx); /* add an attr so we know this is emulated and do not offer to upload results */ attr = fwupd_security_attr_new(FWUPD_SECURITY_ATTR_ID_HOST_EMULATION); fwupd_security_attr_set_plugin(attr, "core"); fwupd_security_attr_add_flag(attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE); fwupd_security_attr_set_result(attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED); fu_security_attrs_append(self->host_security_attrs, attr); /* add from file */ istream_raw = G_INPUT_STREAM(g_file_read(file, NULL, error)); if (istream_raw == NULL) return FALSE; if (g_str_has_suffix(fn, ".gz")) { g_autoptr(GConverter) conv = G_CONVERTER(g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP)); istream_json = g_converter_input_stream_new(istream_raw, conv); } else { istream_json = g_object_ref(istream_raw); } if (!json_parser_load_from_stream(parser, istream_json, NULL, error)) return FALSE; if (!fu_engine_devices_from_json(self, json_parser_get_root(parser), error)) return FALSE; if (!fu_engine_security_attrs_from_json(self, json_parser_get_root(parser), error)) return FALSE; if (!fu_bios_settings_from_json(bios_settings, json_parser_get_root(parser), error)) return FALSE; #ifdef HAVE_HSI /* depsolve */ fu_engine_security_attrs_depsolve(self); #endif /* success */ return TRUE; } static void fu_engine_ensure_security_attrs(FuEngine *self) { #ifdef HAVE_HSI GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); g_autoptr(GPtrArray) devices = fu_device_list_get_active(self->device_list); g_autoptr(GPtrArray) vals = NULL; g_autoptr(GError) error = NULL; /* already valid */ if (self->host_security_id != NULL || self->host_emulation) return; /* clear old values */ fu_security_attrs_remove_all(self->host_security_attrs); /* built in */ fu_engine_ensure_security_attrs_supported_cpu(self); fu_engine_ensure_security_attrs_tainted(self); /* call into devices */ for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); fu_device_add_security_attrs(device, self->host_security_attrs); } /* call into plugins */ for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); fu_plugin_runner_add_security_attrs(plugin_tmp, self->host_security_attrs); } /* sanity check */ vals = fu_security_attrs_get_all(self->host_security_attrs); for (guint i = 0; i < vals->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(vals, i); if (fwupd_security_attr_get_result(attr) == FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) { #ifdef SUPPORTED_BUILD g_debug("HSI attribute %s (from %s) had unknown result", fwupd_security_attr_get_appstream_id(attr), fwupd_security_attr_get_plugin(attr)); #else g_warning("HSI attribute %s (from %s) had unknown result", fwupd_security_attr_get_appstream_id(attr), fwupd_security_attr_get_plugin(attr)); #endif } } /* depsolve */ fu_engine_security_attrs_depsolve(self); /* record into the database (best effort) */ if (!fu_engine_record_security_attrs(self, &error)) g_warning("failed to record HSI attributes: %s", error->message); #endif } const gchar * fu_engine_get_host_security_id(FuEngine *self) { g_return_val_if_fail(FU_IS_ENGINE(self), NULL); fu_engine_ensure_security_attrs(self); return self->host_security_id; } FuSecurityAttrs * fu_engine_get_host_security_attrs(FuEngine *self) { g_return_val_if_fail(FU_IS_ENGINE(self), NULL); fu_engine_ensure_security_attrs(self); return g_object_ref(self->host_security_attrs); } FuSecurityAttrs * fu_engine_get_host_security_events(FuEngine *self, guint limit, GError **error) { g_autoptr(FuSecurityAttrs) events = fu_security_attrs_new(); g_autoptr(GPtrArray) attrs_array = NULL; g_return_val_if_fail(FU_IS_ENGINE(self), NULL); attrs_array = fu_history_get_security_attrs(self->history, limit, error); if (attrs_array == NULL) return NULL; for (guint i = 1; i < attrs_array->len; i++) { FuSecurityAttrs *attrs_new = g_ptr_array_index(attrs_array, i - 1); FuSecurityAttrs *attrs_old = g_ptr_array_index(attrs_array, i - 0); g_autoptr(GPtrArray) diffs = fu_security_attrs_compare(attrs_old, attrs_new); for (guint j = 0; j < diffs->len; j++) { FwupdSecurityAttr *attr = g_ptr_array_index(diffs, j); if (fwupd_security_attr_get_title(attr) == NULL) { fwupd_security_attr_set_title(attr, fu_security_attr_get_title(attr)); } if (fwupd_security_attr_get_description(attr) == NULL) { fwupd_security_attr_set_description( attr, fu_security_attr_get_description(attr)); } fu_security_attrs_append_internal(events, attr); } } /* success */ return g_steal_pointer(&events); } static void fu_engine_load_plugins_filename(FuEngine *self, const gchar *filename, FuProgress *progress) { g_autofree gchar *name = NULL; g_autoptr(FuPlugin) plugin = NULL; g_autoptr(GError) error_local = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_name(progress, filename); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 97, "add"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 3, "open"); /* sanity check */ name = fu_plugin_guess_name_from_fn(filename); if (name == NULL) { fu_progress_finished(progress); return; } /* open module */ plugin = fu_plugin_new(self->ctx); fu_plugin_set_name(plugin, name); fu_engine_add_plugin(self, plugin); fu_progress_step_done(progress); /* open the plugin and call ->load() */ if (!fu_plugin_open(plugin, filename, &error_local)) g_warning("cannot load: %s", error_local->message); fu_progress_step_done(progress); } static void fu_engine_load_plugins_filenames(FuEngine *self, GPtrArray *filenames, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, filenames->len); for (guint i = 0; i < filenames->len; i++) { const gchar *filename = g_ptr_array_index(filenames, i); fu_engine_load_plugins_filename(self, filename, fu_progress_get_child(progress)); fu_progress_step_done(progress); } } static void fu_engine_load_plugins_builtins(FuEngine *self, FuProgress *progress) { guint steps = 0; /* count possible steps */ for (guint i = 0; fu_plugin_externals[i] != NULL; i++) steps++; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, steps); for (guint i = 0; fu_plugin_externals[i] != NULL; i++) { GType plugin_gtype = fu_plugin_externals[i](); g_autoptr(FuPlugin) plugin = fu_plugin_new_from_gtype(plugin_gtype, self->ctx); fu_progress_set_name(fu_progress_get_child(progress), fu_plugin_get_name(plugin)); fu_engine_add_plugin(self, plugin); fu_progress_step_done(progress); } } static void fu_engine_load_plugins(FuEngine *self, FuEngineLoadFlags flags, FuProgress *progress) { g_autofree gchar *plugin_path = NULL; g_autoptr(GPtrArray) filenames = NULL; g_autoptr(GError) error_local = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 13, "search"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 87, "load"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 5, "load-builtins"); /* search */ plugin_path = fu_path_from_kind(FU_PATH_KIND_LIBDIR_PKG); filenames = fu_path_get_files(plugin_path, &error_local); if (filenames == NULL) g_debug("no external plugins found: %s", error_local->message); fu_progress_step_done(progress); /* load */ if (filenames != NULL) fu_engine_load_plugins_filenames(self, filenames, fu_progress_get_child(progress)); fu_progress_step_done(progress); /* load builtins */ if (flags & FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS) fu_engine_load_plugins_builtins(self, fu_progress_get_child(progress)); fu_progress_step_done(progress); } static gboolean fu_engine_plugins_init(FuEngine *self, FuProgress *progress, GError **error) { GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); g_autoptr(GPtrArray) plugins_disabled = g_ptr_array_new_with_free_func(g_free); g_autoptr(GPtrArray) plugins_disabled_rt = g_ptr_array_new_with_free_func(g_free); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, plugins->len); for (guint i = 0; i < plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index(plugins, i); const gchar *name = fu_plugin_get_name(plugin); /* progress */ fu_progress_set_name(fu_progress_get_child(progress), name); /* is disabled */ if (fu_engine_is_plugin_name_disabled(self, name) || fu_engine_is_test_plugin_disabled(self, plugin) || !fu_engine_is_plugin_name_enabled(self, name)) { g_ptr_array_add(plugins_disabled, g_strdup(name)); fu_plugin_add_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED); fu_progress_step_done(progress); continue; } /* init plugin, adding device and firmware GTypes */ fu_plugin_runner_init(plugin); /* runtime disabled */ if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) { g_ptr_array_add(plugins_disabled_rt, g_strdup(name)); fu_progress_step_done(progress); continue; } /* watch for changes */ g_signal_connect(FU_PLUGIN(plugin), "device-added", G_CALLBACK(fu_engine_plugin_device_added_cb), self); g_signal_connect(FU_PLUGIN(plugin), "device-removed", G_CALLBACK(fu_engine_plugin_device_removed_cb), self); g_signal_connect(FU_PLUGIN(plugin), "device-register", G_CALLBACK(fu_engine_plugin_device_register_cb), self); g_signal_connect(FU_PLUGIN(plugin), "check-supported", G_CALLBACK(fu_engine_plugin_check_supported_cb), self); g_signal_connect(FU_PLUGIN(plugin), "rules-changed", G_CALLBACK(fu_engine_plugin_rules_changed_cb), self); fu_progress_step_done(progress); } /* show list */ if (plugins_disabled->len > 0) { g_autofree gchar *str = NULL; g_ptr_array_add(plugins_disabled, NULL); str = g_strjoinv(", ", (gchar **)plugins_disabled->pdata); g_info("plugins disabled: %s", str); } if (plugins_disabled_rt->len > 0) { g_autofree gchar *str = NULL; g_ptr_array_add(plugins_disabled_rt, NULL); str = g_strjoinv(", ", (gchar **)plugins_disabled_rt->pdata); g_info("plugins runtime-disabled: %s", str); } /* depsolve into the correct order */ if (!fu_plugin_list_depsolve(self->plugin_list, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_engine_cleanup_state(GError **error) { const gchar *filenames[] = {"/var/cache/app-info/xmls/fwupd-verify.xml", "/var/cache/app-info/xmls/fwupd.xml", NULL}; for (guint i = 0; filenames[i] != NULL; i++) { g_autoptr(GFile) file = g_file_new_for_path(filenames[i]); if (g_file_query_exists(file, NULL)) { if (!g_file_delete(file, NULL, error)) return FALSE; } } return TRUE; } static gboolean fu_engine_apply_default_bios_settings_policy(FuEngine *self, GError **error) { const gchar *tmp; g_autofree gchar *base = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR_PKG); g_autofree gchar *dirname = g_build_filename(base, "bios-settings.d", NULL); g_autoptr(FuBiosSettings) new_bios_settings = fu_bios_settings_new(); g_autoptr(GHashTable) hashtable = NULL; g_autoptr(GDir) dir = NULL; if (!g_file_test(dirname, G_FILE_TEST_EXISTS)) return TRUE; dir = g_dir_open(dirname, 0, error); if (dir == NULL) return FALSE; while ((tmp = g_dir_read_name(dir)) != NULL) { g_autofree gchar *fn = NULL; if (!g_str_has_suffix(tmp, ".json")) continue; fn = g_build_filename(dirname, tmp, NULL); g_info("loading default BIOS settings policy from %s", fn); if (!fu_bios_settings_from_json_file(new_bios_settings, fn, error)) return FALSE; } hashtable = fu_bios_settings_to_hash_kv(new_bios_settings); return fu_engine_modify_bios_settings(self, hashtable, TRUE, error); } static void fu_engine_check_firmware_attributes(FuEngine *self, FuDevice *device, gboolean added) { const gchar *subsystem; if (!FU_IS_UDEV_DEVICE(device)) return; if (self->host_emulation) return; subsystem = fu_udev_device_get_subsystem(FU_UDEV_DEVICE(device)); if (g_strcmp0(subsystem, "firmware-attributes") == 0) { g_autoptr(GError) error = NULL; if (added) { g_autoptr(FuBiosSettings) settings = fu_context_get_bios_settings(self->ctx); g_autoptr(GPtrArray) items = fu_bios_settings_get_all(settings); if (items->len > 0) { g_debug("ignoring add event for already loaded settings"); return; } } if (!fu_context_reload_bios_settings(self->ctx, &error)) { g_debug("%s", error->message); return; } if (!fu_engine_apply_default_bios_settings_policy(self, &error)) { if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) g_debug("%s", error->message); else g_warning("failed to apply BIOS settings policy: %s", error->message); return; } } } static void fu_engine_backend_device_removed_cb(FuBackend *backend, FuDevice *device, FuEngine *self) { g_autoptr(GPtrArray) devices = NULL; /* if this is for firmware attributes, reload that part of the daemon */ fu_engine_check_firmware_attributes(self, device, FALSE); /* debug */ g_debug("%s removed %s", fu_backend_get_name(backend), fu_device_get_backend_id(device)); /* go through each device and remove any that match */ devices = fu_device_list_get_active(self->device_list); for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); if (g_strcmp0(fu_device_get_backend_id(device_tmp), fu_device_get_backend_id(device)) == 0) { if (fu_device_has_internal_flag(device_tmp, FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE)) { g_info("not auto-removing backend device %s [%s] due to flags", fu_device_get_name(device_tmp), fu_device_get_id(device_tmp)); continue; } g_info("auto-removing backend device %s [%s]", fu_device_get_name(device_tmp), fu_device_get_id(device_tmp)); fu_device_list_remove(self->device_list, device_tmp); fu_engine_emit_changed(self); } } } static gboolean fu_engine_backend_device_added_run_plugin(FuEngine *self, FuDevice *device, const gchar *plugin_name, FuProgress *progress, GError **error) { FuPlugin *plugin; /* find plugin */ fu_progress_set_name(progress, plugin_name); plugin = fu_plugin_list_find_by_name(self->plugin_list, plugin_name, error); if (plugin == NULL) return FALSE; /* run the ->probe() then ->setup() vfuncs */ if (!fu_plugin_runner_backend_device_added(plugin, device, progress, error)) { #ifdef SUPPORTED_BUILD /* sanity check */ if (*error == NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "%s failed but no error set", fu_device_get_backend_id(device)); return FALSE; } #endif return FALSE; } /* success */ return TRUE; } static void fu_engine_backend_device_added_run_plugins(FuEngine *self, FuDevice *device, FuProgress *progress) { g_autoptr(GPtrArray) possible_plugins = fu_device_get_possible_plugins(device); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, possible_plugins->len); for (guint i = 0; i < possible_plugins->len; i++) { const gchar *plugin_name = g_ptr_array_index(possible_plugins, i); g_autoptr(GError) error_local = NULL; if (!fu_engine_backend_device_added_run_plugin(self, device, plugin_name, fu_progress_get_child(progress), &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED) || g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { g_debug("%s ignoring: %s", plugin_name, error_local->message); } else { g_warning("failed to add device %s: %s", fu_device_get_backend_id(device), error_local->message); } fu_progress_add_flag(progress, FU_PROGRESS_FLAG_CHILD_FINISHED); fu_progress_step_done(progress); continue; } fu_progress_step_done(progress); } } static void fu_engine_backend_device_added(FuEngine *self, FuDevice *device, FuProgress *progress) { g_autofree gchar *str1 = NULL; g_autofree gchar *str2 = NULL; g_autoptr(GError) error_local = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_set_name(progress, fu_device_get_backend_id(device)); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 50, "probe-baseclass"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 50, "query-possible-plugins"); /* super useful for plugin development */ str1 = fu_device_to_string(FU_DEVICE(device)); g_debug("%s added %s", fu_device_get_backend_id(device), str1); /* add any extra quirks */ fu_device_set_context(device, self->ctx); if (!fu_device_probe(device, &error_local)) { if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_warning("failed to probe device %s: %s", fu_device_get_backend_id(device), error_local->message); } else { g_debug("failed to probe device %s : %s", fu_device_get_backend_id(device), error_local->message); } fu_progress_finished(progress); return; } fu_progress_step_done(progress); /* check if the device needs emulation-tag */ fu_engine_ensure_device_emulation_tag(self, device); /* super useful for plugin development */ str2 = fu_device_to_string(FU_DEVICE(device)); g_debug("%s added %s", fu_device_get_backend_id(device), str2); /* if this is for firmware attributes, reload that part of the daemon */ fu_engine_check_firmware_attributes(self, device, TRUE); /* can be specified using a quirk */ fu_engine_backend_device_added_run_plugins(self, device, fu_progress_get_child(progress)); fu_progress_step_done(progress); } static void fu_engine_backend_device_added_cb(FuBackend *backend, FuDevice *device, FuEngine *self) { g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); fu_engine_backend_device_added(self, device, progress); } static void fu_engine_backend_device_changed_cb(FuBackend *backend, FuDevice *device, FuEngine *self) { GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); g_autoptr(GPtrArray) devices = NULL; /* debug */ g_debug("%s changed %s", fu_backend_get_name(backend), fu_device_get_physical_id(device)); /* emit changed on any that match */ devices = fu_device_list_get_active(self->device_list); for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); if (!FU_IS_UDEV_DEVICE(device_tmp) || !FU_IS_UDEV_DEVICE(device)) continue; if (g_strcmp0(fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device_tmp)), fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(device))) == 0) { fu_udev_device_emit_changed(FU_UDEV_DEVICE(device)); } } /* get the new GUsbDevice for emulated devices */ for (guint i = 0; i < devices->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices, i); if (!fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_EMULATED)) continue; if (!FU_IS_USB_DEVICE(device_tmp) || !FU_IS_USB_DEVICE(device)) continue; if (g_strcmp0(fu_usb_device_get_platform_id(FU_USB_DEVICE(device_tmp)), fu_usb_device_get_platform_id(FU_USB_DEVICE(device))) == 0) { g_debug("incorporating new GUsbDevice for %s", fu_device_get_id(device_tmp)); fu_device_incorporate(device_tmp, device); } } /* run all plugins */ for (guint j = 0; j < plugins->len; j++) { FuPlugin *plugin_tmp = g_ptr_array_index(plugins, j); g_autoptr(GError) error = NULL; if (!fu_plugin_runner_backend_device_changed(plugin_tmp, device, &error)) { #ifdef SUPPORTED_BUILD /* sanity check */ if (error == NULL) { g_critical( "failed to change device %s: exec failed but no error set!", fu_device_get_backend_id(device)); continue; } #endif if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_debug("%s ignoring: %s", fu_plugin_get_name(plugin_tmp), error->message); continue; } g_warning("%s failed to change device %s: %s", fu_plugin_get_name(plugin_tmp), fu_device_get_id(device), error->message); } } } static void fu_engine_load_quirks_for_hwid(FuEngine *self, const gchar *hwid) { FuPlugin *plugin; const gchar *value; g_auto(GStrv) plugins = NULL; /* does prefixed quirk exist */ value = fu_context_lookup_quirk_by_id(self->ctx, hwid, FU_QUIRKS_PLUGIN); if (value == NULL) return; plugins = g_strsplit(value, ",", -1); for (guint i = 0; plugins[i] != NULL; i++) { g_autoptr(GError) error_local = NULL; plugin = fu_plugin_list_find_by_name(self->plugin_list, plugins[i], &error_local); if (plugin == NULL) { g_info("no %s plugin for HwId %s: %s", plugins[i], hwid, error_local->message); continue; } g_info("enabling %s due to HwId %s", plugins[i], hwid); fu_plugin_remove_flag(plugin, FWUPD_PLUGIN_FLAG_REQUIRE_HWID); } } static gboolean fu_engine_update_history_device(FuEngine *self, FuDevice *dev_history, GError **error) { FuPlugin *plugin; FwupdRelease *rel_history; g_autofree gchar *btime = NULL; g_autoptr(FuDevice) dev = NULL; g_autoptr(GHashTable) metadata_device = NULL; /* is in the device list */ dev = fu_device_list_get_by_id(self->device_list, fu_device_get_id(dev_history), error); if (dev == NULL) return FALSE; /* does the installed version match what we tried to install * before fwupd was restarted */ rel_history = fu_device_get_release_default(dev_history); if (rel_history == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no release for history FuDevice"); return FALSE; } /* is this the same boot time as when we scheduled the update, * i.e. has fwupd been restarted before we rebooted */ btime = fu_engine_get_boot_time(); if (g_strcmp0(fwupd_release_get_metadata_item(rel_history, "BootTime"), btime) == 0) { g_info("service restarted, but no reboot has taken place"); /* if it needed reboot then, it also needs it now... */ if (fu_device_get_update_state(dev_history) == FWUPD_UPDATE_STATE_NEEDS_REBOOT) { g_info("inheriting needs-reboot for %s", fu_device_get_name(dev)); fu_device_set_update_state(dev, FWUPD_UPDATE_STATE_NEEDS_REBOOT); } return TRUE; } /* save any additional report metadata */ metadata_device = fu_device_report_metadata_post(dev); if (metadata_device != NULL && g_hash_table_size(metadata_device) > 0) { fwupd_release_add_metadata(rel_history, metadata_device); if (!fu_history_modify_device_release(self->history, dev_history, rel_history, error)) { g_prefix_error(error, "failed to set metadata: "); return FALSE; } } /* measure the "new" system state */ plugin = fu_plugin_list_find_by_name(self->plugin_list, fu_device_get_plugin(dev), error); if (plugin == NULL) return FALSE; if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY)) fu_engine_update_release_integrity(self, rel_history, "SystemIntegrityNew"); /* do any late-cleanup actions */ if (!fu_plugin_runner_reboot_cleanup(plugin, dev, error)) { g_prefix_error(error, "failed to do post-reboot cleanup: "); return FALSE; } /* the system is running with the new firmware version */ if (fu_version_compare(fu_device_get_version(dev), fwupd_release_get_version(rel_history), fu_device_get_version_format(dev)) == 0) { GPtrArray *checksums; g_info("installed version %s matching history %s", fu_device_get_version(dev), fwupd_release_get_version(rel_history)); /* copy over runtime checksums if set from probe() */ checksums = fu_device_get_checksums(dev); for (guint i = 0; i < checksums->len; i++) { const gchar *csum = g_ptr_array_index(checksums, i); fu_device_add_checksum(dev_history, csum); } fu_device_set_version_format(dev_history, fu_device_get_version_format(dev)); fu_device_set_version(dev_history, fu_device_get_version(dev)); fu_device_remove_flag(dev_history, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION); fu_device_set_update_state(dev_history, FWUPD_UPDATE_STATE_SUCCESS); return fu_history_modify_device_release(self->history, dev_history, rel_history, error); } /* does the plugin know the update failure */ if (!fu_plugin_runner_get_results(plugin, dev, error)) return FALSE; /* the plugin either can't tell us the error, or doesn't know itself */ if (fu_device_get_update_state(dev) != FWUPD_UPDATE_STATE_FAILED && fu_device_get_update_state(dev) != FWUPD_UPDATE_STATE_FAILED_TRANSIENT) { g_autoptr(GString) str = g_string_new("failed to run update on reboot: "); g_info("falling back to generic failure"); fu_device_set_update_state(dev_history, FWUPD_UPDATE_STATE_FAILED); g_string_append_printf(str, "expected %s and got %s", fwupd_release_get_version(rel_history), fu_device_get_version(dev)); fu_device_set_update_error(dev_history, str->str); } else { fu_device_set_update_state(dev_history, fu_device_get_update_state(dev)); fu_device_set_update_error(dev_history, fu_device_get_update_error(dev)); } /* update the state in the database */ return fu_history_modify_device_release(self->history, dev_history, rel_history, error); } static gboolean fu_engine_update_history_database(FuEngine *self, GError **error) { g_autoptr(GPtrArray) devices = NULL; /* get any devices */ devices = fu_history_get_devices(self->history, error); if (devices == NULL) return FALSE; for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index(devices, i); g_autoptr(GError) error_local = NULL; /* not in the required state */ if (fu_device_get_update_state(dev) != FWUPD_UPDATE_STATE_NEEDS_REBOOT && fu_device_get_update_state(dev) != FWUPD_UPDATE_STATE_PENDING) continue; /* try to save the new update-state, but ignoring any error */ if (!fu_engine_update_history_device(self, dev, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_debug("failed to update history database: %s", error_local->message); continue; } g_warning("failed to update history database: %s", error_local->message); } } return TRUE; } static void fu_engine_ensure_client_certificate(FuEngine *self) { g_autoptr(GBytes) blob = g_bytes_new_static(NULL, 0); g_autoptr(GError) error = NULL; g_autoptr(JcatBlob) jcat_sig = NULL; g_autoptr(JcatEngine) jcat_engine = NULL; /* create keyring and sign dummy data to ensure certificate exists */ jcat_engine = jcat_context_get_engine(self->jcat_context, JCAT_BLOB_KIND_PKCS7, &error); if (jcat_engine == NULL) { g_message("failed to create keyring: %s", error->message); return; } jcat_sig = jcat_engine_self_sign(jcat_engine, blob, JCAT_SIGN_FLAG_NONE, &error); if (jcat_sig == NULL) { if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { g_info("client certificate now exists: %s", error->message); return; } g_message("failed to sign using keyring: %s", error->message); return; } g_info("client certificate exists and working"); } static void fu_engine_context_set_battery_threshold(FuContext *ctx) { guint64 minimum_battery; g_autofree gchar *battery_str = NULL; g_autofree gchar *vendor = NULL; vendor = fu_context_get_hwid_replace_value(ctx, FU_HWIDS_KEY_MANUFACTURER, NULL); if (vendor != NULL) { g_autofree gchar *vendor_guid = fwupd_guid_hash_string(vendor); battery_str = g_strdup( fu_context_lookup_quirk_by_id(ctx, vendor_guid, FU_QUIRKS_BATTERY_THRESHOLD)); } if (battery_str == NULL) { minimum_battery = MINIMUM_BATTERY_PERCENTAGE_FALLBACK; } else { g_autoptr(GError) error_local = NULL; if (!fu_strtoull(battery_str, &minimum_battery, 0, 100, &error_local)) { g_warning("invalid minimum battery level specified: %s", error_local->message); minimum_battery = MINIMUM_BATTERY_PERCENTAGE_FALLBACK; } } fu_context_set_battery_threshold(ctx, minimum_battery); } static gboolean fu_engine_ensure_paths_exist(GError **error) { FuPathKind path_kinds[] = {FU_PATH_KIND_LOCALSTATEDIR_QUIRKS, FU_PATH_KIND_LOCALSTATEDIR_METADATA, FU_PATH_KIND_LOCALSTATEDIR_REMOTES, FU_PATH_KIND_CACHEDIR_PKG, FU_PATH_KIND_LAST}; for (guint i = 0; path_kinds[i] != FU_PATH_KIND_LAST; i++) { g_autofree gchar *fn = fu_path_from_kind(path_kinds[i]); if (!fu_path_mkdir(fn, error)) return FALSE; } return TRUE; } static void fu_engine_local_metadata_changed_cb(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { FuEngine *self = FU_ENGINE(user_data); fu_engine_metadata_changed(self); } static gboolean fu_engine_load_local_metadata_watches(FuEngine *self, GError **error) { const FuPathKind path_kinds[] = {FU_PATH_KIND_DATADIR_PKG, FU_PATH_KIND_LOCALSTATEDIR_PKG}; /* add the watches even if the directory does not exist */ for (guint i = 0; i < G_N_ELEMENTS(path_kinds); i++) { GFileMonitor *monitor; g_autoptr(GFile) file = NULL; g_autoptr(GError) error_local = NULL; g_autofree gchar *base = fu_path_from_kind(path_kinds[i]); g_autofree gchar *fn = g_build_filename(base, "local.d", NULL); file = g_file_new_for_path(fn); monitor = g_file_monitor_directory(file, G_FILE_MONITOR_NONE, NULL, &error_local); if (monitor == NULL) { g_warning("failed to watch %s: %s", fn, error_local->message); continue; } g_signal_connect(monitor, "changed", G_CALLBACK(fu_engine_local_metadata_changed_cb), self); g_ptr_array_add(self->local_monitors, g_steal_pointer(&monitor)); } /* success */ return TRUE; } #ifdef _WIN32 static gchar * fu_common_win32_registry_get_string(HKEY hkey, const gchar *subkey, const gchar *value, GError **error) { gchar buf[255] = {'\0'}; DWORD bufsz = sizeof(buf); LSTATUS rc; rc = RegGetValue(hkey, subkey, value, RRF_RT_REG_SZ, NULL, (PVOID)&buf, &bufsz); if (rc != ERROR_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVAL, "Failed to get registry string %s [0x%lX]", subkey, (unsigned long)rc); return NULL; } return g_strndup(buf, bufsz); } #endif static gboolean fu_engine_backends_coldplug_backend_add_devices(FuEngine *self, FuBackend *backend, FuProgress *progress, GError **error) { g_autoptr(GPtrArray) devices = fu_backend_get_devices(backend); /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, devices->len); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); fu_engine_backend_device_added(self, device, fu_progress_get_child(progress)); fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_engine_backends_coldplug_backend(FuEngine *self, FuBackend *backend, FuProgress *progress, GError **error) { /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_set_name(progress, fu_backend_get_name(backend)); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "coldplug"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 99, "add-devices"); /* coldplug */ if (!fu_backend_coldplug(backend, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* add */ fu_engine_backends_coldplug_backend_add_devices(self, backend, fu_progress_get_child(progress), error); fu_progress_step_done(progress); /* success */ g_signal_connect(FU_BACKEND(backend), "device-added", G_CALLBACK(fu_engine_backend_device_added_cb), self); g_signal_connect(FU_BACKEND(backend), "device-removed", G_CALLBACK(fu_engine_backend_device_removed_cb), self); g_signal_connect(FU_BACKEND(backend), "device-changed", G_CALLBACK(fu_engine_backend_device_changed_cb), self); return TRUE; } static void fu_engine_backends_coldplug(FuEngine *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, self->backends->len); for (guint i = 0; i < self->backends->len; i++) { FuBackend *backend = g_ptr_array_index(self->backends, i); g_autoptr(GError) error_backend = NULL; if (!fu_backend_get_enabled(backend)) { fu_progress_step_done(progress); continue; } if (!fu_engine_backends_coldplug_backend(self, backend, fu_progress_get_child(progress), &error_backend)) { if (g_error_matches(error_backend, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_debug("ignoring coldplug failure %s: %s", fu_backend_get_name(backend), error_backend->message); } else { g_warning("failed to coldplug backend %s: %s", fu_backend_get_name(backend), error_backend->message); } fu_progress_finished(fu_progress_get_child(progress)); } fu_progress_step_done(progress); } } /** * fu_engine_load: * @self: a #FuEngine * @flags: engine load flags, e.g. %FU_ENGINE_LOAD_FLAG_READONLY * @progress: a #FuProgress * @error: (nullable): optional return location for an error * * Load the firmware update engine so it is ready for use. * * Returns: %TRUE for success **/ gboolean fu_engine_load(FuEngine *self, FuEngineLoadFlags flags, FuProgress *progress, GError **error) { FuPlugin *plugin_uefi; FuQuirksLoadFlags quirks_flags = FU_QUIRKS_LOAD_FLAG_NONE; GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); const gchar *host_emulate = g_getenv("FWUPD_HOST_EMULATE"); g_autoptr(GPtrArray) checksums_approved = NULL; g_autoptr(GPtrArray) checksums_blocked = NULL; g_autoptr(GError) error_quirks = NULL; g_autoptr(GError) error_json_devices = NULL; g_autoptr(GError) error_local = NULL; #ifdef HAVE_PASSIM g_autoptr(GError) error_passim = NULL; #endif g_autoptr(GString) str = g_string_new(NULL); g_return_val_if_fail(FU_IS_ENGINE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* avoid re-loading a second time if fu-tool or fu-util request to */ if (self->loaded) return TRUE; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_flag(progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "read-config"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "read-remotes"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "ensure-client-cert"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "write-db"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "load-plugins"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "load-quirks"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "load-hwinfo"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "load-appstream"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "backend-setup"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "plugins-init"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "hwid-quirks"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "plugins-setup"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 3, "plugins-coldplug"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 90, "backend-coldplug"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "plugins-ready"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "update-history-db"); /* sanity check libraries are in sync with daemon */ if (g_strcmp0(fwupd_version_string(), VERSION) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVAL, "libfwupd version %s does not match daemon %s", fwupd_version_string(), VERSION); return FALSE; } if (g_strcmp0(fu_version_string(), VERSION) != 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVAL, "libfwupdplugin version %s does not match daemon %s", fu_version_string(), VERSION); return FALSE; } /* cache machine ID so we can use it from a sandboxed app */ #ifdef _WIN32 self->host_machine_id = fu_common_win32_registry_get_string(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography", "MachineGuid", &error_local); #else self->host_machine_id = fwupd_build_machine_id("fwupd", &error_local); #endif if (self->host_machine_id == NULL) g_info("failed to build machine-id: %s", error_local->message); /* ensure these exist before starting */ if (!fu_engine_ensure_paths_exist(error)) return FALSE; /* read config file */ if (!fu_config_load(FU_CONFIG(self->config), error)) { g_prefix_error(error, "Failed to load config: "); return FALSE; } fu_progress_step_done(progress); /* set the hardcoded ESP */ if (fu_engine_config_get_esp_location(self->config) != NULL) { fu_context_set_esp_location(self->ctx, fu_engine_config_get_esp_location(self->config)); } /* read remotes */ if (flags & FU_ENGINE_LOAD_FLAG_REMOTES) { FuRemoteListLoadFlags remote_list_flags = FU_REMOTE_LIST_LOAD_FLAG_FIX_METADATA_URI; if (fu_engine_config_get_test_devices(self->config)) remote_list_flags |= FU_REMOTE_LIST_LOAD_FLAG_TEST_REMOTE; if (flags & FU_ENGINE_LOAD_FLAG_READONLY) remote_list_flags |= FU_REMOTE_LIST_LOAD_FLAG_READONLY_FS; if (flags & FU_ENGINE_LOAD_FLAG_NO_CACHE) remote_list_flags |= FU_REMOTE_LIST_LOAD_FLAG_NO_CACHE; fu_remote_list_set_lvfs_metadata_format(self->remote_list, FU_LVFS_METADATA_FORMAT); if (!fu_remote_list_load(self->remote_list, remote_list_flags, error)) { g_prefix_error(error, "Failed to load remotes: "); return FALSE; } } fu_progress_step_done(progress); /* create client certificate */ fu_engine_ensure_client_certificate(self); fu_progress_step_done(progress); /* get hardcoded approved and blocked firmware */ checksums_approved = fu_engine_config_get_approved_firmware(self->config); for (guint i = 0; i < checksums_approved->len; i++) { const gchar *csum = g_ptr_array_index(checksums_approved, i); fu_engine_add_approved_firmware(self, csum); } checksums_blocked = fu_engine_config_get_blocked_firmware(self->config); for (guint i = 0; i < checksums_blocked->len; i++) { const gchar *csum = g_ptr_array_index(checksums_blocked, i); fu_engine_add_blocked_firmware(self, csum); } /* get extra firmware saved to the database */ checksums_approved = fu_history_get_approved_firmware(self->history, error); if (checksums_approved == NULL) return FALSE; for (guint i = 0; i < checksums_approved->len; i++) { const gchar *csum = g_ptr_array_index(checksums_approved, i); fu_engine_add_approved_firmware(self, csum); } checksums_blocked = fu_history_get_blocked_firmware(self->history, error); if (checksums_blocked == NULL) return FALSE; for (guint i = 0; i < checksums_blocked->len; i++) { const gchar *csum = g_ptr_array_index(checksums_blocked, i); fu_engine_add_blocked_firmware(self, csum); } fu_progress_step_done(progress); /* load plugins early, as we have to call ->load() *before* building quirk silo */ fu_engine_load_plugins(self, flags, fu_progress_get_child(progress)); fu_progress_step_done(progress); /* migrate per-plugin settings into fwupd.conf */ plugin_uefi = fu_plugin_list_find_by_name(self->plugin_list, "uefi_capsule", NULL); if (plugin_uefi != NULL) { const gchar *tmp = fu_plugin_get_config_value(plugin_uefi, "OverrideESPMountPoint"); if (tmp != NULL && g_strcmp0(tmp, fu_engine_config_get_esp_location(self->config)) != 0) { g_info("migrating OverrideESPMountPoint=%s to EspLocation", tmp); if (!fu_config_set_value(FU_CONFIG(self->config), "fwupd", "EspLocation", tmp, error)) return FALSE; } } /* set up idle exit */ if ((flags & FU_ENGINE_LOAD_FLAG_NO_IDLE_SOURCES) == 0) fu_idle_set_timeout(self->idle, fu_engine_config_get_idle_timeout(self->config)); /* on a read-only filesystem don't care about the cache GUID */ if (flags & FU_ENGINE_LOAD_FLAG_READONLY) quirks_flags |= FU_QUIRKS_LOAD_FLAG_READONLY_FS; if (flags & FU_ENGINE_LOAD_FLAG_NO_CACHE) quirks_flags |= FU_QUIRKS_LOAD_FLAG_NO_CACHE; if (!fu_context_load_quirks(self->ctx, quirks_flags, &error_quirks)) g_warning("Failed to load quirks: %s", error_quirks->message); fu_progress_step_done(progress); /* load SMBIOS and the hwids */ if (flags & FU_ENGINE_LOAD_FLAG_HWINFO) { if (!fu_context_load_hwinfo(self->ctx, fu_progress_get_child(progress), FU_CONTEXT_HWID_FLAG_LOAD_ALL, error)) return FALSE; } fu_progress_step_done(progress); /* load AppStream metadata */ if (!fu_engine_load_metadata_store(self, flags, error)) { g_prefix_error(error, "Failed to load AppStream data: "); return FALSE; } fu_progress_step_done(progress); /* watch the local.d directories for changes */ if (!fu_engine_load_local_metadata_watches(self, error)) return FALSE; /* add the "built-in" firmware types */ fu_context_add_firmware_gtype(self->ctx, "raw", FU_TYPE_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "cab", FU_TYPE_CAB_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "dfu", FU_TYPE_DFU_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "fdt", FU_TYPE_FDT_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "csv", FU_TYPE_CSV_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "fit", FU_TYPE_FIT_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "dfuse", FU_TYPE_DFUSE_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "ifwi-cpd", FU_TYPE_IFWI_CPD_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "ifwi-fpt", FU_TYPE_IFWI_FPT_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "oprom", FU_TYPE_OPROM_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "fmap", FU_TYPE_FMAP_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "ihex", FU_TYPE_IHEX_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "srec", FU_TYPE_SREC_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "hid-descriptor", FU_TYPE_HID_DESCRIPTOR); fu_context_add_firmware_gtype(self->ctx, "archive", FU_TYPE_ARCHIVE_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "smbios", FU_TYPE_SMBIOS); fu_context_add_firmware_gtype(self->ctx, "acpi-table", FU_TYPE_ACPI_TABLE); fu_context_add_firmware_gtype(self->ctx, "edid", FU_TYPE_EDID); fu_context_add_firmware_gtype(self->ctx, "efi-firmware-file", FU_TYPE_EFI_FIRMWARE_FILE); fu_context_add_firmware_gtype(self->ctx, "efi-load-option", FU_TYPE_EFI_LOAD_OPTION); fu_context_add_firmware_gtype(self->ctx, "efi-device-path-list", FU_TYPE_EFI_DEVICE_PATH_LIST); fu_context_add_firmware_gtype(self->ctx, "efi-firmware-filesystem", FU_TYPE_EFI_FIRMWARE_FILESYSTEM); fu_context_add_firmware_gtype(self->ctx, "efi-firmware-section", FU_TYPE_EFI_FIRMWARE_SECTION); fu_context_add_firmware_gtype(self->ctx, "efi-firmware-volume", FU_TYPE_EFI_FIRMWARE_VOLUME); fu_context_add_firmware_gtype(self->ctx, "ifd-bios", FU_TYPE_IFD_BIOS); fu_context_add_firmware_gtype(self->ctx, "ifd-firmware", FU_TYPE_IFD_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "cfu-offer", FU_TYPE_CFU_OFFER); fu_context_add_firmware_gtype(self->ctx, "cfu-payload", FU_TYPE_CFU_PAYLOAD); fu_context_add_firmware_gtype(self->ctx, "uswid", FU_TYPE_USWID_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "coswid", FU_TYPE_COSWID_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "pefile", FU_TYPE_PEFILE_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "elf", FU_TYPE_ELF_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "intel-thunderbolt", FU_TYPE_INTEL_THUNDERBOLT_FIRMWARE); fu_context_add_firmware_gtype(self->ctx, "intel-thunderbolt-nvm", FU_TYPE_INTEL_THUNDERBOLT_NVM); fu_context_add_firmware_gtype(self->ctx, "usb-device-fw-ds20", FU_TYPE_USB_DEVICE_FW_DS20); fu_context_add_firmware_gtype(self->ctx, "usb-device-ms-ds20", FU_TYPE_USB_DEVICE_MS_DS20); /* we are emulating a different host */ if (host_emulate != NULL) { g_autofree gchar *fn = NULL; /* did the user specify an absolute path */ if (g_file_test(host_emulate, G_FILE_TEST_EXISTS)) { fn = g_strdup(host_emulate); } else { g_autofree gchar *datadir = fu_path_from_kind(FU_PATH_KIND_DATADIR_PKG); fn = g_build_filename(datadir, "host-emulate.d", host_emulate, NULL); } if (!fu_engine_load_host_emulation(self, fn, error)) { g_prefix_error(error, "failed to load emulated host: "); return FALSE; } /* do not load actual hardware */ flags &= ~FU_ENGINE_LOAD_FLAG_COLDPLUG; self->host_emulation = TRUE; } /* set up backends */ if (flags & FU_ENGINE_LOAD_FLAG_COLDPLUG) { for (guint i = 0; i < self->backends->len; i++) { FuBackend *backend = g_ptr_array_index(self->backends, i); g_autoptr(GError) error_backend = NULL; if (!fu_backend_setup(backend, fu_progress_get_child(progress), &error_backend)) { g_info("failed to setup backend %s: %s", fu_backend_get_name(backend), error_backend->message); continue; } } } fu_progress_step_done(progress); /* delete old data files */ if (!fu_engine_cleanup_state(error)) { g_prefix_error(error, "Failed to clean up: "); return FALSE; } /* init plugins, adding device and firmware GTypes */ if (!fu_engine_plugins_init(self, fu_progress_get_child(progress), error)) { g_prefix_error(error, "failed to init plugins: "); return FALSE; } fu_progress_step_done(progress); /* set quirks for each hwid */ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_LOADED_HWINFO)) { GPtrArray *guids = fu_context_get_hwid_guids(self->ctx); for (guint i = 0; i < guids->len; i++) { const gchar *hwid = g_ptr_array_index(guids, i); fu_engine_load_quirks_for_hwid(self, hwid); } } fu_progress_step_done(progress); /* set up battery threshold */ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_LOADED_HWINFO)) fu_engine_context_set_battery_threshold(self->ctx); /* watch the device list for updates and proxy */ g_signal_connect(FU_DEVICE_LIST(self->device_list), "added", G_CALLBACK(fu_engine_device_added_cb), self); g_signal_connect(FU_DEVICE_LIST(self->device_list), "removed", G_CALLBACK(fu_engine_device_removed_cb), self); g_signal_connect(FU_DEVICE_LIST(self->device_list), "changed", G_CALLBACK(fu_engine_device_changed_cb), self); fu_engine_set_status(self, FWUPD_STATUS_LOADING); /* add devices */ if (flags & FU_ENGINE_LOAD_FLAG_COLDPLUG) { fu_engine_plugins_startup(self, fu_progress_get_child(progress)); fu_progress_step_done(progress); fu_engine_plugins_coldplug(self, fu_progress_get_child(progress)); fu_progress_step_done(progress); } else { fu_progress_step_done(progress); fu_progress_step_done(progress); } /* coldplug backends */ if (flags & FU_ENGINE_LOAD_FLAG_COLDPLUG) fu_engine_backends_coldplug(self, fu_progress_get_child(progress)); fu_progress_step_done(progress); /* coldplug done, so plugin is ready */ if (flags & FU_ENGINE_LOAD_FLAG_COLDPLUG) { fu_engine_plugins_ready(self, fu_progress_get_child(progress)); fu_progress_step_done(progress); } else { fu_progress_step_done(progress); } /* dump plugin information to the console */ for (guint i = 0; i < self->backends->len; i++) { FuBackend *backend = g_ptr_array_index(self->backends, i); fu_backend_add_string(backend, 0, str); } for (guint i = 0; i < plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index(plugins, i); if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) continue; fu_plugin_add_string(plugin, 0, str); } g_info("%s", str->str); /* update the db for devices that were updated during the reboot */ if (!fu_engine_update_history_database(self, error)) return FALSE; fu_progress_step_done(progress); /* update the devices JSON file */ if (!fu_engine_update_devices_file(self, &error_json_devices)) g_info("failed to update list of devices: %s", error_json_devices->message); #ifdef HAVE_PASSIM /* connect to passimd */ if (!passim_client_load(self->passim_client, &error_passim)) g_debug("failed to load Passim: %s", error_passim->message); if (passim_client_get_version(self->passim_client) != NULL) { fu_engine_add_runtime_version(self, "org.freedesktop.Passim", passim_client_get_version(self->passim_client)); } #endif fu_engine_set_status(self, FWUPD_STATUS_IDLE); self->loaded = TRUE; /* let clients know engine finished starting up */ fu_engine_emit_changed(self); /* success */ return TRUE; } static void fu_engine_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { FuEngine *self = FU_ENGINE(object); switch (prop_id) { case PROP_CONTEXT: g_value_set_object(value, self->ctx); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_engine_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { FuEngine *self = FU_ENGINE(object); switch (prop_id) { case PROP_CONTEXT: g_set_object(&self->ctx, g_value_get_object(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void fu_engine_class_init(FuEngineClass *klass) { GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_engine_finalize; object_class->get_property = fu_engine_get_property; object_class->set_property = fu_engine_set_property; object_class->constructed = fu_engine_constructed; pspec = g_param_spec_object("context", NULL, NULL, FU_TYPE_CONTEXT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME); g_object_class_install_property(object_class, PROP_CONTEXT, pspec); /** * FuEngine::changed: * @self: the #FuEngine instance that emitted the signal * * The ::changed signal is emitted when the engine has changed, for instance when a device * state has been modified. **/ signals[SIGNAL_CHANGED] = g_signal_new("changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * FuEngine::device-added: * @self: the #FuEngine instance that emitted the signal * @device: the #FuDevice * * The ::device-added signal is emitted when a device has been added. **/ signals[SIGNAL_DEVICE_ADDED] = g_signal_new("device-added", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuEngine::device-removed: * @self: the #FuEngine instance that emitted the signal * @device: the #FuDevice * * The ::device-removed signal is emitted when a device has been removed. **/ signals[SIGNAL_DEVICE_REMOVED] = g_signal_new("device-removed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuEngine::device-changed: * @self: the #FuEngine instance that emitted the signal * @device: the #FuDevice * * The ::device-changed signal is emitted when a device has been changed. **/ signals[SIGNAL_DEVICE_CHANGED] = g_signal_new("device-changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuEngine::device-request: * @self: the #FuEngine instance that emitted the signal * @request: the #FwupdRequest * * The ::device-request signal is emitted when the engine has asked the front end for an * interactive request. **/ signals[SIGNAL_DEVICE_REQUEST] = g_signal_new("device-request", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FWUPD_TYPE_REQUEST); /** * FuEngine::status-changed: * @self: the #FuEngine instance that emitted the signal * @status: the #FwupdStatus * * The ::status-changed signal is emitted when the daemon global status has changed. **/ signals[SIGNAL_STATUS_CHANGED] = g_signal_new("status-changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); } void fu_engine_add_runtime_version(FuEngine *self, const gchar *component_id, const gchar *version) { fu_context_add_runtime_version(self->ctx, component_id, version); } static void fu_engine_context_power_changed(FuEngine *self) { g_autoptr(GPtrArray) devices = fu_device_list_get_active(self->device_list); /* apply policy on any existing devices */ for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); fu_engine_ensure_device_power_inhibit(self, device); fu_engine_ensure_device_lid_inhibit(self, device); fu_engine_ensure_device_display_required_inhibit(self, device); fu_engine_ensure_device_system_inhibit(self, device); } } static void fu_engine_context_power_changed_cb(FuContext *ctx, GParamSpec *pspec, FuEngine *self) { if (fu_idle_has_inhibit(self->idle, FU_IDLE_INHIBIT_SIGNALS)) { g_debug("suppressing ::power-changed as transaction is in progress"); return; } fu_engine_context_power_changed(self); } static void fu_engine_idle_timeout_cb(FuIdle *idle, FuEngine *self) { fu_engine_set_status(self, FWUPD_STATUS_SHUTDOWN); } static void fu_engine_idle_inhibit_changed_cb(FuIdle *idle, GParamSpec *pspec, FuEngine *self) { if (!fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS) && g_hash_table_size(self->device_changed_allowlist) > 0) { g_debug("clearing device-changed allowlist as transaction done"); g_hash_table_remove_all(self->device_changed_allowlist); /* we might have suppressed this during the transaction, so ensure all the device * inhibits are being set up correctly */ fu_engine_context_power_changed(self); } } static void fu_engine_constructed(GObject *obj) { FuEngine *self = FU_ENGINE(obj); #ifdef HAVE_UTSNAME_H struct utsname uname_tmp; #endif g_autofree gchar *keyring_path = NULL; g_autofree gchar *pkidir_fw = NULL; g_autofree gchar *pkidir_md = NULL; g_autofree gchar *sysconfdir = NULL; /* for debugging */ g_info("starting fwupd %s…", VERSION); g_signal_connect(FU_CONTEXT(self->ctx), "security-changed", G_CALLBACK(fu_engine_context_security_changed_cb), self); g_signal_connect(FU_CONTEXT(self->ctx), "notify::power-state", G_CALLBACK(fu_engine_context_power_changed_cb), self); g_signal_connect(FU_CONTEXT(self->ctx), "notify::lid-state", G_CALLBACK(fu_engine_context_power_changed_cb), self); g_signal_connect(FU_CONTEXT(self->ctx), "notify::display-state", G_CALLBACK(fu_engine_context_power_changed_cb), self); g_signal_connect(FU_CONTEXT(self->ctx), "notify::battery-level", G_CALLBACK(fu_engine_context_power_changed_cb), self); g_signal_connect(FU_CONTEXT(self->ctx), "notify::battery-threshold", G_CALLBACK(fu_engine_context_power_changed_cb), self); g_signal_connect(FU_CONTEXT(self->ctx), "notify::flags", G_CALLBACK(fu_engine_context_power_changed_cb), self); g_signal_connect(FU_CONFIG(self->config), "changed", G_CALLBACK(fu_engine_config_changed_cb), self); g_signal_connect(FU_REMOTE_LIST(self->remote_list), "changed", G_CALLBACK(fu_engine_remote_list_changed_cb), self); g_signal_connect(FU_REMOTE_LIST(self->remote_list), "added", G_CALLBACK(fu_engine_remote_list_added_cb), self); g_signal_connect(FU_IDLE(self->idle), "inhibit-changed", G_CALLBACK(fu_engine_idle_inhibit_changed_cb), self); g_signal_connect(FU_IDLE(self->idle), "timeout", G_CALLBACK(fu_engine_idle_timeout_cb), self); /* backends */ #ifdef HAVE_GUSB g_ptr_array_add(self->backends, fu_usb_backend_new(self->ctx)); #endif #ifdef HAVE_GUDEV g_ptr_array_add(self->backends, fu_udev_backend_new(self->ctx)); #endif #ifdef HAVE_BLUEZ g_ptr_array_add(self->backends, fu_bluez_backend_new(self->ctx)); #endif /* setup Jcat context */ self->jcat_context = jcat_context_new(); #if LIBJCAT_CHECK_VERSION(0, 1, 13) jcat_context_blob_kind_allow(self->jcat_context, JCAT_BLOB_KIND_SHA256); jcat_context_blob_kind_allow(self->jcat_context, JCAT_BLOB_KIND_SHA512); jcat_context_blob_kind_allow(self->jcat_context, JCAT_BLOB_KIND_PKCS7); jcat_context_blob_kind_allow(self->jcat_context, JCAT_BLOB_KIND_GPG); #endif keyring_path = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); jcat_context_set_keyring_path(self->jcat_context, keyring_path); sysconfdir = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR); pkidir_fw = g_build_filename(sysconfdir, "pki", "fwupd", NULL); jcat_context_add_public_keys(self->jcat_context, pkidir_fw); pkidir_md = g_build_filename(sysconfdir, "pki", "fwupd-metadata", NULL); jcat_context_add_public_keys(self->jcat_context, pkidir_md); /* add some runtime versions of things the daemon depends on */ fu_engine_add_runtime_version(self, "org.freedesktop.fwupd", VERSION); #ifdef HAVE_GUSB fu_engine_add_runtime_version(self, "org.freedesktop.gusb", g_usb_version_string()); #endif #if LIBJCAT_CHECK_VERSION(0, 1, 11) fu_engine_add_runtime_version(self, "com.hughsie.libjcat", jcat_version_string()); #endif /* optional kernel version */ #ifdef HAVE_UTSNAME_H memset(&uname_tmp, 0, sizeof(uname_tmp)); if (uname(&uname_tmp) >= 0) fu_engine_add_runtime_version(self, "org.kernel", uname_tmp.release); #endif fu_context_add_compile_version(self->ctx, "org.freedesktop.fwupd", VERSION); #ifdef HAVE_GUSB { g_autofree gchar *version = g_strdup_printf("%i.%i.%i", G_USB_MAJOR_VERSION, G_USB_MINOR_VERSION, G_USB_MICRO_VERSION); fu_context_add_compile_version(self->ctx, "org.freedesktop.gusb", version); } #endif #ifdef HAVE_PASSIM { g_autofree gchar *version = g_strdup_printf("%i.%i.%i", PASSIM_MAJOR_VERSION, PASSIM_MINOR_VERSION, PASSIM_MICRO_VERSION); fu_context_add_compile_version(self->ctx, "org.freedesktop.Passim", version); } #endif { g_autofree gchar *version = g_strdup_printf("%i.%i.%i", JCAT_MAJOR_VERSION, JCAT_MINOR_VERSION, JCAT_MICRO_VERSION); fu_context_add_compile_version(self->ctx, "com.hughsie.libjcat", version); } { g_autofree gchar *version = g_strdup_printf("%i.%i.%i", XMLB_MAJOR_VERSION, XMLB_MINOR_VERSION, XMLB_MICRO_VERSION); fu_context_add_compile_version(self->ctx, "com.hughsie.libxmlb", version); } /* add optional snap version */ if (g_getenv("SNAP_REVISION") != NULL) { fu_context_add_compile_version(self->ctx, "io.snapcraft.fwupd", g_getenv("SNAP_REVISION")); } } static void fu_engine_init(FuEngine *self) { self->percentage = 0; self->config = fu_engine_config_new(); self->remote_list = fu_remote_list_new(); self->device_list = fu_device_list_new(); self->idle = fu_idle_new(); self->history = fu_history_new(); self->plugin_list = fu_plugin_list_new(); self->plugin_filter = g_ptr_array_new_with_free_func(g_free); self->host_security_attrs = fu_security_attrs_new(); self->backends = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); self->local_monitors = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); self->acquiesce_loop = g_main_loop_new(NULL, FALSE); self->emulation_phases = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); self->emulation_backend_ids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); self->device_changed_allowlist = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); #ifdef HAVE_PASSIM self->passim_client = passim_client_new(); #endif /* register /org/freedesktop/fwupd globally */ g_resources_register(fu_get_resource()); } static void fu_engine_finalize(GObject *obj) { FuEngine *self = FU_ENGINE(obj); GPtrArray *plugins = fu_plugin_list_get_all(self->plugin_list); for (guint i = 0; i < plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index(plugins, i); g_signal_handlers_disconnect_by_data(plugin, self); } g_signal_handlers_disconnect_by_data(self->ctx, self); g_signal_handlers_disconnect_by_data(self->config, self); for (guint i = 0; i < self->local_monitors->len; i++) { GFileMonitor *monitor = g_ptr_array_index(self->local_monitors, i); g_file_monitor_cancel(monitor); } if (self->silo != NULL) g_object_unref(self->silo); if (self->query_component_by_guid != NULL) g_object_unref(self->query_component_by_guid); if (self->query_container_checksum1 != NULL) g_object_unref(self->query_container_checksum1); if (self->query_container_checksum2 != NULL) g_object_unref(self->query_container_checksum2); if (self->query_tag_by_guid_version != NULL) g_object_unref(self->query_tag_by_guid_version); if (self->coldplug_id != 0) g_source_remove(self->coldplug_id); if (self->approved_firmware != NULL) g_hash_table_unref(self->approved_firmware); if (self->blocked_firmware != NULL) g_hash_table_unref(self->blocked_firmware); if (self->acquiesce_id != 0) g_source_remove(self->acquiesce_id); if (self->update_motd_id != 0) g_source_remove(self->update_motd_id); #ifdef HAVE_PASSIM if (self->passim_client != NULL) g_object_unref(self->passim_client); #endif g_main_loop_unref(self->acquiesce_loop); g_free(self->host_machine_id); g_free(self->host_security_id); g_object_unref(self->host_security_attrs); g_object_unref(self->idle); g_object_unref(self->config); g_object_unref(self->remote_list); g_object_unref(self->ctx); g_object_unref(self->history); g_object_unref(self->device_list); g_object_unref(self->jcat_context); g_ptr_array_unref(self->plugin_filter); g_ptr_array_unref(self->backends); g_ptr_array_unref(self->local_monitors); g_hash_table_unref(self->emulation_phases); g_hash_table_unref(self->emulation_backend_ids); g_hash_table_unref(self->device_changed_allowlist); g_object_unref(self->plugin_list); G_OBJECT_CLASS(fu_engine_parent_class)->finalize(obj); } FuEngine * fu_engine_new(FuContext *ctx) { return FU_ENGINE(g_object_new(FU_TYPE_ENGINE, "context", ctx, NULL)); } fwupd-1.9.16/src/fu-engine.h000066400000000000000000000201451460375044200155610ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fwupd-device.h" #include "fwupd-enums.h" #include "fu-engine-config.h" #include "fu-release.h" #define FU_TYPE_ENGINE (fu_engine_get_type()) G_DECLARE_FINAL_TYPE(FuEngine, fu_engine, FU, ENGINE, GObject) /** * FuEngineLoadFlags: * @FU_ENGINE_LOAD_FLAG_NONE: No flags set * @FU_ENGINE_LOAD_FLAG_READONLY: Ignore readonly filesystem errors * @FU_ENGINE_LOAD_FLAG_COLDPLUG: Enumerate devices * @FU_ENGINE_LOAD_FLAG_REMOTES: Enumerate remotes * @FU_ENGINE_LOAD_FLAG_HWINFO: Load details about the hardware * @FU_ENGINE_LOAD_FLAG_NO_CACHE: Do not save persistent xmlb silos * @FU_ENGINE_LOAD_FLAG_NO_IDLE_SOURCES:Do not load idle sources * @FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS: Load built-in plugins * * The flags to use when loading the engine. **/ typedef enum { FU_ENGINE_LOAD_FLAG_NONE = 0, FU_ENGINE_LOAD_FLAG_READONLY = 1 << 0, FU_ENGINE_LOAD_FLAG_COLDPLUG = 1 << 1, FU_ENGINE_LOAD_FLAG_REMOTES = 1 << 2, FU_ENGINE_LOAD_FLAG_HWINFO = 1 << 3, FU_ENGINE_LOAD_FLAG_NO_CACHE = 1 << 4, FU_ENGINE_LOAD_FLAG_NO_IDLE_SOURCES = 1 << 5, FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS = 1 << 6, /*< private >*/ FU_ENGINE_LOAD_FLAG_LAST } FuEngineLoadFlags; FuEngine * fu_engine_new(FuContext *ctx); void fu_engine_add_plugin_filter(FuEngine *self, const gchar *plugin_glob); void fu_engine_idle_reset(FuEngine *self); guint32 fu_engine_idle_inhibit(FuEngine *self, FuIdleInhibit inhibit, const gchar *reason); void fu_engine_idle_uninhibit(FuEngine *self, guint32 token); gboolean fu_engine_load(FuEngine *self, FuEngineLoadFlags flags, FuProgress *progress, GError **error); const gchar * fu_engine_get_host_vendor(FuEngine *self); const gchar * fu_engine_get_host_product(FuEngine *self); const gchar * fu_engine_get_host_machine_id(FuEngine *self); const gchar * fu_engine_get_host_bkc(FuEngine *self); gboolean fu_engine_is_uid_trusted(FuEngine *self, guint64 calling_uid); const gchar * fu_engine_get_host_security_id(FuEngine *self); XbSilo * fu_engine_get_silo_from_blob(FuEngine *self, GBytes *blob_cab, GError **error); FuEngineConfig * fu_engine_get_config(FuEngine *self); GPtrArray * fu_engine_get_plugins(FuEngine *self); FuPlugin * fu_engine_get_plugin_by_name(FuEngine *self, const gchar *name, GError **error); GPtrArray * fu_engine_get_devices(FuEngine *self, GError **error); FuDevice * fu_engine_get_device(FuEngine *self, const gchar *device_id, GError **error); GPtrArray * fu_engine_get_devices_by_guid(FuEngine *self, const gchar *guid, GError **error); GPtrArray * fu_engine_get_devices_by_composite_id(FuEngine *self, const gchar *composite_id, GError **error); GPtrArray * fu_engine_get_history(FuEngine *self, GError **error); FwupdRemote * fu_engine_get_remote_by_id(FuEngine *self, const gchar *remote_id, GError **error); GPtrArray * fu_engine_get_remotes(FuEngine *self, GError **error); GPtrArray * fu_engine_get_releases(FuEngine *self, FuEngineRequest *request, const gchar *device_id, GError **error); GPtrArray * fu_engine_get_downgrades(FuEngine *self, FuEngineRequest *request, const gchar *device_id, GError **error); GPtrArray * fu_engine_get_upgrades(FuEngine *self, FuEngineRequest *request, const gchar *device_id, GError **error); FwupdDevice * fu_engine_get_results(FuEngine *self, const gchar *device_id, GError **error); FuSecurityAttrs * fu_engine_get_host_security_attrs(FuEngine *self); FuSecurityAttrs * fu_engine_get_host_security_events(FuEngine *self, guint limit, GError **error); GHashTable * fu_engine_get_report_metadata(FuEngine *self, GError **error); gboolean fu_engine_clear_results(FuEngine *self, const gchar *device_id, GError **error); gboolean fu_engine_update_metadata(FuEngine *self, const gchar *remote_id, gint fd, gint fd_sig, GError **error); gboolean fu_engine_update_metadata_bytes(FuEngine *self, const gchar *remote_id, GBytes *bytes_raw, GBytes *bytes_sig, GError **error); gboolean fu_engine_unlock(FuEngine *self, const gchar *device_id, GError **error); gboolean fu_engine_verify(FuEngine *self, const gchar *device_id, FuProgress *progress, GError **error); gboolean fu_engine_verify_update(FuEngine *self, const gchar *device_id, FuProgress *progress, GError **error); GBytes * fu_engine_firmware_dump(FuEngine *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error); FuFirmware * fu_engine_firmware_read(FuEngine *self, FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error); gboolean fu_engine_modify_remote(FuEngine *self, const gchar *remote_id, const gchar *key, const gchar *value, GError **error); gboolean fu_engine_modify_device(FuEngine *self, const gchar *device_id, const gchar *key, const gchar *value, GError **error); gboolean fu_engine_composite_prepare(FuEngine *self, GPtrArray *devices, GError **error); gboolean fu_engine_composite_cleanup(FuEngine *self, GPtrArray *devices, GError **error); gboolean fu_engine_install_release(FuEngine *self, FuRelease *release, GBytes *blob_cab, FuProgress *progress, FwupdInstallFlags flags, GError **error); gboolean fu_engine_install_blob(FuEngine *self, FuDevice *device, GBytes *blob_fw, FuProgress *progress, FwupdInstallFlags flags, FwupdFeatureFlags feature_flags, GError **error); gboolean fu_engine_install_releases(FuEngine *self, FuEngineRequest *request, GPtrArray *releases, GBytes *blob_cab, FuProgress *progress, FwupdInstallFlags flags, GError **error); GPtrArray * fu_engine_get_details(FuEngine *self, FuEngineRequest *request, gint fd, GError **error); gboolean fu_engine_activate(FuEngine *self, const gchar *device_id, FuProgress *progress, GError **error); GPtrArray * fu_engine_get_approved_firmware(FuEngine *self); void fu_engine_add_approved_firmware(FuEngine *self, const gchar *checksum); void fu_engine_set_approved_firmware(FuEngine *self, GPtrArray *checksums); GPtrArray * fu_engine_get_blocked_firmware(FuEngine *self); gboolean fu_engine_set_blocked_firmware(FuEngine *self, GPtrArray *checksums, GError **error); gchar * fu_engine_self_sign(FuEngine *self, const gchar *value, JcatSignFlags flags, GError **error); gboolean fu_engine_modify_config(FuEngine *self, const gchar *key, const gchar *value, GError **error); gboolean fu_engine_reset_config(FuEngine *self, const gchar *section, GError **error); FuContext * fu_engine_get_context(FuEngine *self); GPtrArray * fu_engine_get_releases_for_device(FuEngine *self, FuEngineRequest *request, FuDevice *device, GError **error); /* for the self tests */ void fu_engine_add_device(FuEngine *self, FuDevice *device); void fu_engine_add_plugin(FuEngine *self, FuPlugin *plugin); void fu_engine_add_remote(FuEngine *self, FwupdRemote *remote); void fu_engine_add_runtime_version(FuEngine *self, const gchar *component_id, const gchar *version); GPtrArray * fu_engine_get_details_for_bytes(FuEngine *self, FuEngineRequest *request, GBytes *blob, GError **error); gboolean fu_engine_check_trust(FuEngine *self, FuRelease *release, GError **error); void fu_engine_set_silo(FuEngine *self, XbSilo *silo); XbNode * fu_engine_get_component_by_guids(FuEngine *self, FuDevice *device); gchar * fu_engine_get_remote_id_for_blob(FuEngine *self, GBytes *blob); gboolean fu_engine_schedule_update(FuEngine *self, FuDevice *device, FwupdRelease *release, GBytes *blob_cab, FwupdInstallFlags flags, GError **error); gboolean fu_engine_modify_bios_settings(FuEngine *self, GHashTable *settings, gboolean force_ro, GError **error); gboolean fu_engine_emulation_load(FuEngine *self, GBytes *data, GError **error); GBytes * fu_engine_emulation_save(FuEngine *self, GError **error); gboolean fu_engine_fix_host_security_attr(FuEngine *self, const gchar *appstream_id, GError **error); gboolean fu_engine_undo_host_security_attr(FuEngine *self, const gchar *appstream_id, GError **error); fwupd-1.9.16/src/fu-engine.rs000066400000000000000000000013551460375044200157600ustar00rootroot00000000000000// Copyright (C) 2023 Richard Hughes // SPDX-License-Identifier: LGPL-2.1+ #[derive(FromString)] enum ReleasePriority { None, Local, Remote, } #[derive(FromString)] enum P2pPolicy { Nothing = 0x00, Metadata = 0x01, Firmware = 0x02, } #[derive(ToString)] enum EngineInstallPhase { Setup, Install, Attach, Detach, Prepare, Cleanup, Reload, CompositePrepare, CompositeCleanup, Last, } #[derive(ToBitString)] enum EngineRequestFlag { None = 0, NoRequirements = 1 << 0, AnyRelease = 1 << 1, } #[derive(ToBitString)] enum IdleInhibit { None = 0, Timeout = 1 << 0, Signals = 1 << 1, } enum ClientFlag { None = 0, Active = 1 << 0, } fwupd-1.9.16/src/fu-history.c000066400000000000000000001267561460375044200160270ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuHistory" #include "config.h" #include #include #ifdef HAVE_SQLITE #include #endif #include #include "fwupd-security-attr-private.h" #include "fu-device-private.h" #include "fu-history.h" #include "fu-security-attr-common.h" /* * v1 legacy schema * v2 initial schema * v3 add checksum_device to history * v4 add protocol to history * v5 create table approved_firmware * v6 create table blocked_firmware * v7 create table hsi_history * v8 add release_id to history * v9 add appstream_id to history * v10 add version_format to history * v11 no changes, bumped due to bungled migration to v10 * v12 add install_duration to history */ #define FU_HISTORY_CURRENT_SCHEMA_VERSION 12 static void fu_history_finalize(GObject *object); struct _FuHistory { GObject parent_instance; #ifdef HAVE_SQLITE sqlite3 *db; GRWLock db_mutex; #endif }; G_DEFINE_TYPE(FuHistory, fu_history, G_TYPE_OBJECT) #ifdef HAVE_SQLITE #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(sqlite3_stmt, sqlite3_finalize); #pragma clang diagnostic pop static FuDevice * fu_history_device_from_stmt(sqlite3_stmt *stmt) { const gchar *tmp; FuDevice *device; g_autoptr(FwupdRelease) release = fwupd_release_new(); /* create new result */ device = fu_device_new(NULL); fu_device_add_release(device, release); /* device_id */ tmp = (const gchar *)sqlite3_column_text(stmt, 0); if (tmp != NULL) fwupd_device_set_id(FWUPD_DEVICE(device), tmp); /* checksum */ tmp = (const gchar *)sqlite3_column_text(stmt, 1); if (tmp != NULL) fwupd_release_add_checksum(release, tmp); /* plugin */ tmp = (const gchar *)sqlite3_column_text(stmt, 2); if (tmp != NULL) fu_device_set_plugin(device, tmp); /* device_created */ fu_device_set_created(device, sqlite3_column_int64(stmt, 3)); /* device_modified */ fu_device_set_modified(device, sqlite3_column_int64(stmt, 4)); /* display_name */ tmp = (const gchar *)sqlite3_column_text(stmt, 5); if (tmp != NULL) fu_device_set_name(device, tmp); /* filename */ tmp = (const gchar *)sqlite3_column_text(stmt, 6); if (tmp != NULL) fwupd_release_set_filename(release, tmp); /* flags */ fu_device_set_flags(device, sqlite3_column_int64(stmt, 7) | FWUPD_DEVICE_FLAG_HISTORICAL); /* metadata */ tmp = (const gchar *)sqlite3_column_text(stmt, 8); if (tmp != NULL) { g_auto(GStrv) split = g_strsplit(tmp, ";", -1); for (guint i = 0; split[i] != NULL; i++) { g_auto(GStrv) kv = g_strsplit(split[i], "=", 2); if (g_strv_length(kv) != 2) continue; fwupd_release_add_metadata_item(release, kv[0], kv[1]); } } /* guid_default */ tmp = (const gchar *)sqlite3_column_text(stmt, 9); if (tmp != NULL) fu_device_add_guid_full(device, tmp, FU_DEVICE_INSTANCE_FLAG_VISIBLE); /* update_state */ fu_device_set_update_state(device, sqlite3_column_int(stmt, 10)); /* update_error */ tmp = (const gchar *)sqlite3_column_text(stmt, 11); fu_device_set_update_error(device, tmp); /* version_new */ tmp = (const gchar *)sqlite3_column_text(stmt, 12); if (tmp != NULL) fwupd_release_set_version(release, tmp); /* version_old */ tmp = (const gchar *)sqlite3_column_text(stmt, 13); if (tmp != NULL) fu_device_set_version(device, tmp); /* checksum_device */ tmp = (const gchar *)sqlite3_column_text(stmt, 14); if (tmp != NULL) fu_device_add_checksum(device, tmp); /* protocol */ tmp = (const gchar *)sqlite3_column_text(stmt, 15); if (tmp != NULL) fwupd_release_set_protocol(release, tmp); /* release_id */ tmp = (const gchar *)sqlite3_column_text(stmt, 16); if (tmp != NULL) fwupd_release_set_id(release, tmp); /* appstream_id */ tmp = (const gchar *)sqlite3_column_text(stmt, 17); if (tmp != NULL) fwupd_release_set_appstream_id(release, tmp); /* version_format */ fu_device_set_version_format(device, sqlite3_column_int(stmt, 18)); /* install_duration */ fu_device_set_install_duration(device, sqlite3_column_int(stmt, 19)); /* success */ return device; } static gboolean fu_history_stmt_exec(FuHistory *self, sqlite3_stmt *stmt, GPtrArray *array, GError **error) { gint rc; if (array == NULL) { rc = sqlite3_step(stmt); } else { while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { FuDevice *device = fu_history_device_from_stmt(stmt); g_ptr_array_add(array, device); } } if (rc != SQLITE_DONE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to execute prepared statement: %s", sqlite3_errmsg(self->db)); return FALSE; } return TRUE; } static gboolean fu_history_create_database(FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec(self->db, "BEGIN TRANSACTION;" "CREATE TABLE IF NOT EXISTS schema (" "created timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP," "version INTEGER DEFAULT 0);" "INSERT INTO schema (version) VALUES (0);" "CREATE TABLE IF NOT EXISTS history (" "device_id TEXT," "update_state INTEGER DEFAULT 0," "update_error TEXT," "filename TEXT," "display_name TEXT," "plugin TEXT," "device_created INTEGER DEFAULT 0," "device_modified INTEGER DEFAULT 0," "checksum TEXT DEFAULT NULL," "flags INTEGER DEFAULT 0," "metadata TEXT DEFAULT NULL," "guid_default TEXT DEFAULT NULL," "version_old TEXT," "version_new TEXT," "checksum_device TEXT DEFAULT NULL," "protocol TEXT DEFAULT NULL," "release_id TEXT DEFAULT NULL," "appstream_id TEXT DEFAULT NULL," "version_format INTEGER DEFAULT 0," "install_duration INTEGER DEFAULT 0);" "CREATE TABLE IF NOT EXISTS approved_firmware (" "checksum TEXT);" "CREATE TABLE IF NOT EXISTS blocked_firmware (" "checksum TEXT);" "CREATE TABLE IF NOT EXISTS hsi_history (" "timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP," "hsi_details TEXT DEFAULT NULL," "hsi_score TEXT DEFAULT NULL);" "COMMIT;", NULL, NULL, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL for creating tables: %s", sqlite3_errmsg(self->db)); return FALSE; } return TRUE; } static gboolean fu_history_migrate_database_v1(FuHistory *self, GError **error) { gint rc; g_info("migrating v1 database by recreating table"); /* rename the table to something out the way */ rc = sqlite3_exec(self->db, "ALTER TABLE history RENAME TO history_old;", NULL, NULL, NULL); if (rc != SQLITE_OK) { g_debug("cannot rename v0 table: %s", sqlite3_errmsg(self->db)); return TRUE; } /* create new table */ if (!fu_history_create_database(self, error)) return FALSE; /* migrate the old entries to the new table */ rc = sqlite3_exec(self->db, "INSERT INTO history SELECT " "device_id, update_state, update_error, filename, " "display_name, plugin, device_created, device_modified, " "checksum, flags, metadata, guid_default, version_old, " "version_new, NULL, NULL, NULL, NULL, NULL, 0 FROM history_old;" "DROP TABLE history_old;", NULL, NULL, NULL); if (rc != SQLITE_OK) { g_debug("no history to migrate: %s", sqlite3_errmsg(self->db)); return TRUE; } return TRUE; } static gboolean fu_history_migrate_database_v2(FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec(self->db, "ALTER TABLE history ADD COLUMN checksum_device TEXT DEFAULT NULL;", NULL, NULL, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to alter database: %s", sqlite3_errmsg(self->db)); return FALSE; } return TRUE; } static gboolean fu_history_migrate_database_v3(FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec(self->db, "ALTER TABLE history ADD COLUMN protocol TEXT DEFAULT NULL;", NULL, NULL, NULL); if (rc != SQLITE_OK) g_debug("ignoring database error: %s", sqlite3_errmsg(self->db)); return TRUE; } static gboolean fu_history_migrate_database_v4(FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec(self->db, "CREATE TABLE IF NOT EXISTS approved_firmware (checksum TEXT);", NULL, NULL, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to create table: %s", sqlite3_errmsg(self->db)); return FALSE; } return TRUE; } static gboolean fu_history_migrate_database_v5(FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec(self->db, "CREATE TABLE IF NOT EXISTS blocked_firmware (checksum TEXT);", NULL, NULL, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to create table: %s", sqlite3_errmsg(self->db)); return FALSE; } return TRUE; } static gboolean fu_history_migrate_database_v6(FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec(self->db, "CREATE TABLE IF NOT EXISTS hsi_history (" "timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP," "hsi_details TEXT DEFAULT NULL," "hsi_score TEXT DEFAULT NULL);", NULL, NULL, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to create table: %s", sqlite3_errmsg(self->db)); return FALSE; } return TRUE; } static gboolean fu_history_migrate_database_v7(FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec(self->db, "ALTER TABLE history ADD COLUMN release_id TEXT DEFAULT NULL;", NULL, NULL, NULL); if (rc != SQLITE_OK) g_debug("ignoring database error: %s", sqlite3_errmsg(self->db)); return TRUE; } static gboolean fu_history_migrate_database_v8(FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec(self->db, "ALTER TABLE history ADD COLUMN appstream_id TEXT DEFAULT NULL;", NULL, NULL, NULL); if (rc != SQLITE_OK) g_debug("ignoring database error: %s", sqlite3_errmsg(self->db)); return TRUE; } static gboolean fu_history_migrate_database_v9(FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec(self->db, "ALTER TABLE history ADD COLUMN version_format INTEGER DEFAULT 0;", NULL, NULL, NULL); if (rc != SQLITE_OK) g_debug("ignoring database error: %s", sqlite3_errmsg(self->db)); return TRUE; } static gboolean fu_history_migrate_database_v10(FuHistory *self, GError **error) { gint rc; rc = sqlite3_exec(self->db, "ALTER TABLE history ADD COLUMN install_duration INTEGER DEFAULT 0;", NULL, NULL, NULL); if (rc != SQLITE_OK) g_debug("ignoring database error: %s", sqlite3_errmsg(self->db)); return TRUE; } /* returns 0 if database is not initialized */ static guint fu_history_get_schema_version(FuHistory *self) { gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; rc = sqlite3_prepare_v2(self->db, "SELECT version FROM schema LIMIT 1;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_debug("no schema version: %s", sqlite3_errmsg(self->db)); return 0; } rc = sqlite3_step(stmt); if (rc != SQLITE_ROW) { g_warning("failed prepare to get schema version: %s", sqlite3_errmsg(self->db)); return 0; } return sqlite3_column_int(stmt, 0); } static gboolean fu_history_create_or_migrate(FuHistory *self, guint schema_ver, GError **error) { gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; if (schema_ver == 0) g_info("building initial database"); else if (schema_ver > 1) g_info("migrating v%u database by altering", schema_ver); switch (schema_ver) { /* create initial up-to-date database or migrate */ case 0: if (!fu_history_create_database(self, error)) return FALSE; break; case 1: if (!fu_history_migrate_database_v1(self, error)) return FALSE; break; case 2: if (!fu_history_migrate_database_v2(self, error)) return FALSE; /* fall through */ case 3: if (!fu_history_migrate_database_v3(self, error)) return FALSE; /* fall through */ case 4: if (!fu_history_migrate_database_v4(self, error)) return FALSE; /* fall through */ case 5: if (!fu_history_migrate_database_v5(self, error)) return FALSE; /* fall through */ case 6: if (!fu_history_migrate_database_v6(self, error)) return FALSE; /* fall through */ case 7: if (!fu_history_migrate_database_v7(self, error)) return FALSE; /* fall through */ case 8: if (!fu_history_migrate_database_v8(self, error)) return FALSE; /* fall through */ case 9: case 10: if (!fu_history_migrate_database_v9(self, error)) return FALSE; /* fall through */ case 11: if (!fu_history_migrate_database_v10(self, error)) return FALSE; /* no longer fall through */ break; default: /* this is probably okay, but return an error if we ever delete * or rename columns */ g_warning("schema version %u is unknown", schema_ver); return TRUE; } /* set new schema version */ rc = sqlite3_prepare_v2(self->db, "UPDATE schema SET version=?1;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL for updating schema: %s", sqlite3_errmsg(self->db)); return FALSE; } sqlite3_bind_int(stmt, 1, FU_HISTORY_CURRENT_SCHEMA_VERSION); return fu_history_stmt_exec(self, stmt, NULL, error); } static gboolean fu_history_open(FuHistory *self, const gchar *filename, GError **error) { gint rc; g_debug("trying to open database '%s'", filename); rc = sqlite3_open(filename, &self->db); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "Can't open %s: %s", filename, sqlite3_errmsg(self->db)); return FALSE; } /* turn off the lookaside cache */ sqlite3_db_config(self->db, SQLITE_DBCONFIG_LOOKASIDE, NULL, 0, 0); return TRUE; } static gboolean fu_history_load(FuHistory *self, GError **error) { gint rc; guint schema_ver; g_autofree gchar *dirname = NULL; g_autofree gchar *filename = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GRWLockWriterLocker) locker = g_rw_lock_writer_locker_new(&self->db_mutex); /* already done */ if (self->db != NULL) return TRUE; g_return_val_if_fail(FU_IS_HISTORY(self), FALSE); g_return_val_if_fail(self->db == NULL, FALSE); g_return_val_if_fail(locker != NULL, FALSE); /* create directory */ dirname = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); file = g_file_new_for_path(dirname); if (!g_file_query_exists(file, NULL)) { if (!g_file_make_directory_with_parents(file, NULL, error)) return FALSE; } /* open */ filename = g_build_filename(dirname, "pending.db", NULL); if (!fu_history_open(self, filename, error)) return FALSE; /* check database */ schema_ver = fu_history_get_schema_version(self); if (schema_ver == 0) { g_autoptr(sqlite3_stmt) stmt_tmp = NULL; rc = sqlite3_prepare_v2(self->db, "SELECT * FROM history LIMIT 0;", -1, &stmt_tmp, NULL); if (rc == SQLITE_OK) schema_ver = 1; } /* create initial up-to-date database, or migrate */ g_debug("got schema version of %u", schema_ver); if (schema_ver != FU_HISTORY_CURRENT_SCHEMA_VERSION) { g_autoptr(GError) error_migrate = NULL; if (!fu_history_create_or_migrate(self, schema_ver, &error_migrate)) { /* this is fatal to the daemon, so delete the database * and try again with something empty */ g_warning("failed to migrate %s database: %s", filename, error_migrate->message); sqlite3_close(self->db); if (g_unlink(filename) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Can't delete %s", filename); return FALSE; } if (!fu_history_open(self, filename, error)) return FALSE; return fu_history_create_database(self, error); } } /* success */ return TRUE; } static gchar * _convert_hash_to_string(GHashTable *hash) { GString *str = g_string_new(NULL); g_autoptr(GList) keys = g_hash_table_get_keys(hash); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup(hash, key); if (str->len > 0) g_string_append(str, ";"); g_string_append_printf(str, "%s=%s", key, value); } return g_string_free(str, FALSE); } /* unset some flags we don't want to store */ static FwupdDeviceFlags fu_history_get_device_flags_filtered(FuDevice *device) { FwupdDeviceFlags flags = fu_device_get_flags(device); flags &= ~FWUPD_DEVICE_FLAG_REGISTERED; flags &= ~FWUPD_DEVICE_FLAG_SUPPORTED; return flags; } #endif /** * fu_history_modify_device: * @self: a #FuHistory * @device: a device * @error: (nullable): optional return location for an error * * Modify a device in the history database * * Returns: @TRUE if successful, @FALSE for failure * * Since: 1.0.4 **/ gboolean fu_history_modify_device(FuHistory *self, FuDevice *device, GError **error) { #ifdef HAVE_SQLITE gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockWriterLocker) locker = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), FALSE); g_return_val_if_fail(FU_IS_DEVICE(device), FALSE); /* lazy load */ if (!fu_history_load(self, error)) return FALSE; /* overwrite entry if it exists */ locker = g_rw_lock_writer_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, FALSE); g_debug("modifying device %s [%s]", fu_device_get_name(device), fu_device_get_id(device)); rc = sqlite3_prepare_v2(self->db, "UPDATE history SET " "update_state = ?1, " "update_error = ?2, " "checksum_device = ?6, " "device_modified = ?7, " "install_duration = ?8, " "flags = ?3 " "WHERE device_id = ?4;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to update history: %s", sqlite3_errmsg(self->db)); return FALSE; } sqlite3_bind_int(stmt, 1, fu_device_get_update_state(device)); sqlite3_bind_text(stmt, 2, fu_device_get_update_error(device), -1, SQLITE_STATIC); sqlite3_bind_int64(stmt, 3, fu_history_get_device_flags_filtered(device)); sqlite3_bind_text(stmt, 4, fu_device_get_id(device), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 5, fu_device_get_version(device), -1, SQLITE_STATIC); sqlite3_bind_text( stmt, 6, fwupd_checksum_get_by_kind(fu_device_get_checksums(device), G_CHECKSUM_SHA1), -1, SQLITE_STATIC); sqlite3_bind_int64(stmt, 7, fu_device_get_modified(device)); sqlite3_bind_int64(stmt, 8, fu_device_get_install_duration(device)); if (!fu_history_stmt_exec(self, stmt, NULL, error)) return FALSE; if (sqlite3_changes(self->db) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no device %s", fu_device_get_id(device)); return FALSE; } #endif return TRUE; } /** * fu_history_modify_device_release: * @self: a #FuHistory * @device: a #FuDevice * @release: a #FwupdRelease * @error: (nullable): optional return location for an error * * Modify a device in the history database, also changing metadata from the new release. * * Returns: @TRUE if successful, @FALSE for failure * * Since: 1.8.8 **/ gboolean fu_history_modify_device_release(FuHistory *self, FuDevice *device, FwupdRelease *release, GError **error) { #ifdef HAVE_SQLITE gint rc; g_autofree gchar *metadata = NULL; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockWriterLocker) locker = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), FALSE); g_return_val_if_fail(FU_IS_DEVICE(device), FALSE); /* lazy load */ if (!fu_history_load(self, error)) return FALSE; /* metadata is stored as a simple string */ metadata = _convert_hash_to_string(fwupd_release_get_metadata(release)); /* overwrite entry if it exists */ locker = g_rw_lock_writer_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, FALSE); g_debug("modifying device %s [%s]", fu_device_get_name(device), fu_device_get_id(device)); rc = sqlite3_prepare_v2(self->db, "UPDATE history SET " "update_state = ?1, " "update_error = ?2, " "checksum_device = ?6, " "device_modified = ?7, " "metadata = ?8, " "flags = ?3 " "WHERE device_id = ?4;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to update history: %s", sqlite3_errmsg(self->db)); return FALSE; } sqlite3_bind_int(stmt, 1, fu_device_get_update_state(device)); sqlite3_bind_text(stmt, 2, fu_device_get_update_error(device), -1, SQLITE_STATIC); sqlite3_bind_int64(stmt, 3, fu_history_get_device_flags_filtered(device)); sqlite3_bind_text(stmt, 4, fu_device_get_id(device), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 5, fu_device_get_version(device), -1, SQLITE_STATIC); sqlite3_bind_text( stmt, 6, fwupd_checksum_get_by_kind(fu_device_get_checksums(device), G_CHECKSUM_SHA1), -1, SQLITE_STATIC); sqlite3_bind_int64(stmt, 7, fu_device_get_modified(device)); sqlite3_bind_text(stmt, 8, metadata, -1, SQLITE_STATIC); return fu_history_stmt_exec(self, stmt, NULL, error); #else return TRUE; #endif } /** * fu_history_add_device: * @self: a #FuHistory * @device: a device * @release: a #FuRelease * @error: (nullable): optional return location for an error * * Adds a device to the history database * * Returns: @TRUE if successful, @FALSE for failure * * Since: 1.0.4 **/ gboolean fu_history_add_device(FuHistory *self, FuDevice *device, FwupdRelease *release, GError **error) { #ifdef HAVE_SQLITE const gchar *checksum_device; const gchar *checksum = NULL; gint rc; g_autofree gchar *metadata = NULL; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockWriterLocker) locker = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), FALSE); g_return_val_if_fail(FU_IS_DEVICE(device), FALSE); g_return_val_if_fail(FWUPD_IS_RELEASE(release), FALSE); /* lazy load */ if (!fu_history_load(self, error)) return FALSE; /* ensure all old device(s) with this ID are removed */ if (!fu_history_remove_device(self, device, error)) return FALSE; g_debug("add device %s [%s]", fu_device_get_name(device), fu_device_get_id(device)); if (release != NULL) { GPtrArray *checksums = fwupd_release_get_checksums(release); checksum = fwupd_checksum_get_by_kind(checksums, G_CHECKSUM_SHA1); } checksum_device = fwupd_checksum_get_by_kind(fu_device_get_checksums(device), G_CHECKSUM_SHA1); /* metadata is stored as a simple string */ metadata = _convert_hash_to_string(fwupd_release_get_metadata(release)); /* add */ locker = g_rw_lock_writer_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, FALSE); rc = sqlite3_prepare_v2(self->db, "INSERT INTO history (device_id," "update_state," "update_error," "flags," "filename," "checksum," "display_name," "plugin," "guid_default," "metadata," "device_created," "device_modified," "version_old," "version_new," "checksum_device," "protocol," "release_id," "appstream_id," "version_format," "install_duration) " "VALUES (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10," "?11,?12,?13,?14,?15,?16,?17,?18,?19,?20)", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to insert history: %s", sqlite3_errmsg(self->db)); return FALSE; } sqlite3_bind_text(stmt, 1, fu_device_get_id(device), -1, SQLITE_STATIC); sqlite3_bind_int(stmt, 2, fu_device_get_update_state(device)); sqlite3_bind_text(stmt, 3, fu_device_get_update_error(device), -1, SQLITE_STATIC); sqlite3_bind_int64(stmt, 4, fu_history_get_device_flags_filtered(device)); sqlite3_bind_text(stmt, 5, fwupd_release_get_filename(release), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 6, checksum, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 7, fu_device_get_name(device), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 8, fu_device_get_plugin(device), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 9, fu_device_get_guid_default(device), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 10, metadata, -1, SQLITE_STATIC); sqlite3_bind_int64(stmt, 11, fu_device_get_created(device)); sqlite3_bind_int64(stmt, 12, fu_device_get_modified(device)); sqlite3_bind_text(stmt, 13, fu_device_get_version(device), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 14, fwupd_release_get_version(release), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 15, checksum_device, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 16, fwupd_release_get_protocol(release), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 17, fwupd_release_get_id(release), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 18, fwupd_release_get_appstream_id(release), -1, SQLITE_STATIC); sqlite3_bind_int(stmt, 19, fu_device_get_version_format(device)); sqlite3_bind_int(stmt, 20, fu_device_get_install_duration(device)); return fu_history_stmt_exec(self, stmt, NULL, error); #else return TRUE; #endif } /** * fu_history_remove_all: * @self: a #FuHistory * @error: (nullable): optional return location for an error * * Remove all devices from the history database * * Returns: @TRUE if successful, @FALSE for failure * * Since: 1.0.4 **/ gboolean fu_history_remove_all(FuHistory *self, GError **error) { #ifdef HAVE_SQLITE gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockWriterLocker) locker = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), FALSE); /* lazy load */ if (!fu_history_load(self, error)) return FALSE; /* remove entries */ locker = g_rw_lock_writer_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, FALSE); g_debug("removing all devices"); rc = sqlite3_prepare_v2(self->db, "DELETE FROM history;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to delete history: %s", sqlite3_errmsg(self->db)); return FALSE; } return fu_history_stmt_exec(self, stmt, NULL, error); #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sqlite support"); return FALSE; #endif } /** * fu_history_remove_device: * @self: a #FuHistory * @device: a device * @error: (nullable): optional return location for an error * * Remove a device from the history database * * Returns: @TRUE if successful, @FALSE for failure * * Since: 1.0.4 **/ gboolean fu_history_remove_device(FuHistory *self, FuDevice *device, GError **error) { #ifdef HAVE_SQLITE gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockWriterLocker) locker = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), FALSE); g_return_val_if_fail(FU_IS_DEVICE(device), FALSE); /* lazy load */ if (!fu_history_load(self, error)) return FALSE; locker = g_rw_lock_writer_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, FALSE); g_debug("remove device %s [%s]", fu_device_get_name(device), fu_device_get_id(device)); rc = sqlite3_prepare_v2(self->db, "DELETE FROM history WHERE device_id = ?1;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to delete history: %s", sqlite3_errmsg(self->db)); return FALSE; } sqlite3_bind_text(stmt, 1, fu_device_get_id(device), -1, SQLITE_STATIC); return fu_history_stmt_exec(self, stmt, NULL, error); #else return TRUE; #endif } /** * fu_history_get_device_by_id: * @self: a #FuHistory * @device_id: a string * @error: (nullable): optional return location for an error * * Returns the device from the history database or NULL if not found * * Returns: (transfer full): a device * * Since: 1.0.4 **/ FuDevice * fu_history_get_device_by_id(FuHistory *self, const gchar *device_id, GError **error) { #ifdef HAVE_SQLITE gint rc; g_autoptr(GPtrArray) array_tmp = NULL; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockReaderLocker) locker = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), NULL); g_return_val_if_fail(device_id != NULL, NULL); /* lazy load */ if (!fu_history_load(self, error)) return NULL; /* get all the devices */ locker = g_rw_lock_reader_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, NULL); rc = sqlite3_prepare_v2(self->db, "SELECT device_id, " "checksum, " "plugin, " "device_created, " "device_modified, " "display_name, " "filename, " "flags, " "metadata, " "guid_default, " "update_state, " "update_error, " "version_new, " "version_old, " "checksum_device, " "protocol, " "release_id, " "appstream_id, " "version_format, " "install_duration FROM history WHERE " "device_id = ?1 ORDER BY device_created DESC " "LIMIT 1", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to get history: %s", sqlite3_errmsg(self->db)); return NULL; } sqlite3_bind_text(stmt, 1, device_id, -1, SQLITE_STATIC); array_tmp = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); if (!fu_history_stmt_exec(self, stmt, array_tmp, error)) return NULL; if (array_tmp->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No devices found"); return NULL; } return g_object_ref(g_ptr_array_index(array_tmp, 0)); #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sqlite support"); return NULL; #endif } /** * fu_history_get_devices: * @self: a #FuHistory * @error: (nullable): optional return location for an error * * Gets the devices in the history database. * * Returns: (element-type #FuDevice) (transfer container): devices * * Since: 1.0.4 **/ GPtrArray * fu_history_get_devices(FuHistory *self, GError **error) { g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); #ifdef HAVE_SQLITE g_autoptr(sqlite3_stmt) stmt = NULL; gint rc; g_autoptr(GRWLockReaderLocker) locker = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), NULL); /* lazy load */ if (self->db == NULL) { if (!fu_history_load(self, error)) return NULL; } /* get all the devices */ locker = g_rw_lock_reader_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, NULL); rc = sqlite3_prepare_v2(self->db, "SELECT device_id, " "checksum, " "plugin, " "device_created, " "device_modified, " "display_name, " "filename, " "flags, " "metadata, " "guid_default, " "update_state, " "update_error, " "version_new, " "version_old, " "checksum_device, " "protocol, " "release_id, " "appstream_id, " "version_format, " "install_duration FROM history " "ORDER BY device_modified ASC;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to get history: %s", sqlite3_errmsg(self->db)); return NULL; } if (!fu_history_stmt_exec(self, stmt, array, error)) return NULL; #endif return g_steal_pointer(&array); } /** * fu_history_get_approved_firmware: * @self: a #FuHistory * @error: (nullable): optional return location for an error * * Returns approved firmware records. * * Returns: (transfer full) (element-type gchar *): records * * Since: 1.2.6 **/ GPtrArray * fu_history_get_approved_firmware(FuHistory *self, GError **error) { g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free); #ifdef HAVE_SQLITE gint rc; g_autoptr(GRWLockReaderLocker) locker = NULL; g_autoptr(sqlite3_stmt) stmt = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), NULL); /* lazy load */ if (self->db == NULL) { if (!fu_history_load(self, error)) return NULL; } /* get all the approved firmware */ locker = g_rw_lock_reader_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, NULL); rc = sqlite3_prepare_v2(self->db, "SELECT checksum FROM approved_firmware;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to get checksum: %s", sqlite3_errmsg(self->db)); return NULL; } while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { const gchar *tmp = (const gchar *)sqlite3_column_text(stmt, 0); g_ptr_array_add(array, g_strdup(tmp)); } if (rc != SQLITE_DONE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to execute prepared statement: %s", sqlite3_errmsg(self->db)); return NULL; } #endif return g_steal_pointer(&array); } /** * fu_history_clear_approved_firmware: * @self: a #FuHistory * @error: (nullable): optional return location for an error * * Clear all approved firmware records * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.2.6 **/ gboolean fu_history_clear_approved_firmware(FuHistory *self, GError **error) { #ifdef HAVE_SQLITE gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockWriterLocker) locker = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), FALSE); /* lazy load */ if (!fu_history_load(self, error)) return FALSE; /* remove entries */ locker = g_rw_lock_writer_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, FALSE); rc = sqlite3_prepare_v2(self->db, "DELETE FROM approved_firmware;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to delete approved firmware: %s", sqlite3_errmsg(self->db)); return FALSE; } return fu_history_stmt_exec(self, stmt, NULL, error); #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sqlite support"); return FALSE; #endif } /** * fu_history_add_approved_firmware: * @self: a #FuHistory * @checksum: a string * @error: (nullable): optional return location for an error * * Add an approved firmware record to the database * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.2.6 **/ gboolean fu_history_add_approved_firmware(FuHistory *self, const gchar *checksum, GError **error) { #ifdef HAVE_SQLITE gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockWriterLocker) locker = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), FALSE); g_return_val_if_fail(checksum != NULL, FALSE); /* lazy load */ if (!fu_history_load(self, error)) return FALSE; /* add */ locker = g_rw_lock_writer_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, FALSE); rc = sqlite3_prepare_v2(self->db, "INSERT INTO approved_firmware (checksum) " "VALUES (?1)", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to insert checksum: %s", sqlite3_errmsg(self->db)); return FALSE; } sqlite3_bind_text(stmt, 1, checksum, -1, SQLITE_STATIC); return fu_history_stmt_exec(self, stmt, NULL, error); #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sqlite support"); return FALSE; #endif } /** * fu_history_get_blocked_firmware: * @self: a #FuHistory * @error: (nullable): optional return location for an error * * Returns blocked firmware records. * * Returns: (transfer full) (element-type gchar *): records * * Since: 1.4.6 **/ GPtrArray * fu_history_get_blocked_firmware(FuHistory *self, GError **error) { g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func(g_free); #ifdef HAVE_SQLITE gint rc; g_autoptr(GRWLockReaderLocker) locker = NULL; g_autoptr(sqlite3_stmt) stmt = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), NULL); /* lazy load */ if (self->db == NULL) { if (!fu_history_load(self, error)) return NULL; } /* get all the blocked firmware */ locker = g_rw_lock_reader_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, NULL); rc = sqlite3_prepare_v2(self->db, "SELECT checksum FROM blocked_firmware;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to get checksum: %s", sqlite3_errmsg(self->db)); return NULL; } while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { const gchar *tmp = (const gchar *)sqlite3_column_text(stmt, 0); g_ptr_array_add(array, g_strdup(tmp)); } if (rc != SQLITE_DONE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to execute prepared statement: %s", sqlite3_errmsg(self->db)); return NULL; } #endif return g_steal_pointer(&array); } /** * fu_history_clear_blocked_firmware: * @self: a #FuHistory * @error: (nullable): optional return location for an error * * Clear all blocked firmware records * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.4.6 **/ gboolean fu_history_clear_blocked_firmware(FuHistory *self, GError **error) { #ifdef HAVE_SQLITE gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockWriterLocker) locker = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), FALSE); /* lazy load */ if (!fu_history_load(self, error)) return FALSE; /* remove entries */ locker = g_rw_lock_writer_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, FALSE); rc = sqlite3_prepare_v2(self->db, "DELETE FROM blocked_firmware;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to delete blocked firmware: %s", sqlite3_errmsg(self->db)); return FALSE; } return fu_history_stmt_exec(self, stmt, NULL, error); #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sqlite support"); return FALSE; #endif } /** * fu_history_add_blocked_firmware: * @self: a #FuHistory * @checksum: a string * @error: (nullable): optional return location for an error * * Add an blocked firmware record to the database * * Returns: #TRUE for success, #FALSE for failure * * Since: 1.4.6 **/ gboolean fu_history_add_blocked_firmware(FuHistory *self, const gchar *checksum, GError **error) { #ifdef HAVE_SQLITE gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockWriterLocker) locker = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), FALSE); g_return_val_if_fail(checksum != NULL, FALSE); /* lazy load */ if (!fu_history_load(self, error)) return FALSE; /* add */ locker = g_rw_lock_writer_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, FALSE); rc = sqlite3_prepare_v2(self->db, "INSERT INTO blocked_firmware (checksum) " "VALUES (?1)", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to insert checksum: %s", sqlite3_errmsg(self->db)); return FALSE; } sqlite3_bind_text(stmt, 1, checksum, -1, SQLITE_STATIC); return fu_history_stmt_exec(self, stmt, NULL, error); #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sqlite support"); return FALSE; #endif } gboolean fu_history_add_security_attribute(FuHistory *self, const gchar *security_attr_json, const gchar *hsi_score, GError **error) { #ifdef HAVE_SQLITE gint rc; g_autoptr(sqlite3_stmt) stmt = NULL; g_autoptr(GRWLockWriterLocker) locker = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), FALSE); /* lazy load */ if (!fu_history_load(self, error)) return FALSE; /* remove entries */ locker = g_rw_lock_writer_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, FALSE); rc = sqlite3_prepare_v2(self->db, "INSERT INTO hsi_history (hsi_details, hsi_score)" "VALUES (?1, ?2)", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to write security attribute: %s", sqlite3_errmsg(self->db)); return FALSE; } sqlite3_bind_text(stmt, 1, security_attr_json, -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 2, hsi_score, -1, SQLITE_STATIC); return fu_history_stmt_exec(self, stmt, NULL, error); #else g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no sqlite support"); return FALSE; #endif } /** * fu_history_get_security_attrs: * @self: a #FuHistory * @limit: maximum number of attributes to return, or 0 for no limit * @error: (nullable): optional return location for an error * * Gets the security attributes in the history database. * Attributes with the same stores JSON data will be deduplicated as required. * * Returns: (element-type #FuSecurityAttrs) (transfer container): attrs * * Since: 1.7.1 **/ GPtrArray * fu_history_get_security_attrs(FuHistory *self, guint limit, GError **error) { g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); #ifdef HAVE_SQLITE g_autoptr(sqlite3_stmt) stmt = NULL; gint rc; guint old_hash = 0; g_autoptr(GRWLockReaderLocker) locker = NULL; g_return_val_if_fail(FU_IS_HISTORY(self), NULL); /* lazy load */ if (self->db == NULL) { if (!fu_history_load(self, error)) return NULL; } /* get all the devices */ locker = g_rw_lock_reader_locker_new(&self->db_mutex); g_return_val_if_fail(locker != NULL, NULL); rc = sqlite3_prepare_v2(self->db, "SELECT timestamp, hsi_details FROM hsi_history " "ORDER BY timestamp DESC;", -1, &stmt, NULL); if (rc != SQLITE_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to prepare SQL to get security attrs: %s", sqlite3_errmsg(self->db)); return NULL; } while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { const gchar *json; guint hash; const gchar *timestamp; g_autoptr(FuSecurityAttrs) attrs = fu_security_attrs_new(); g_autoptr(JsonParser) parser = NULL; g_autoptr(GDateTime) created_dt = NULL; g_autoptr(GTimeZone) tz_utc = g_time_zone_new_utc(); /* old */ timestamp = (const gchar *)sqlite3_column_text(stmt, 0); if (timestamp == NULL) continue; /* device_id */ json = (const gchar *)sqlite3_column_text(stmt, 1); if (json == NULL) continue; /* do not create dups */ hash = g_str_hash(json); if (hash == old_hash) { g_debug("skipping %s as unchanged", timestamp); continue; } old_hash = hash; /* parse JSON */ parser = json_parser_new(); g_debug("parsing %s", timestamp); if (!json_parser_load_from_data(parser, json, -1, error)) return NULL; if (!fu_security_attrs_from_json(attrs, json_parser_get_root(parser), error)) return NULL; /* parse timestamp */ created_dt = g_date_time_new_from_iso8601(timestamp, tz_utc); if (created_dt != NULL) { guint64 created_unix = g_date_time_to_unix(created_dt); g_autoptr(GPtrArray) attr_array = fu_security_attrs_get_all(attrs); for (guint i = 0; i < attr_array->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(attr_array, i); fwupd_security_attr_set_created(attr, created_unix); } } /* success */ g_ptr_array_add(array, g_steal_pointer(&attrs)); if (limit > 0 && array->len >= limit) { rc = SQLITE_DONE; break; } } if (rc != SQLITE_DONE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to execute prepared statement: %s", sqlite3_errmsg(self->db)); return NULL; } #endif return g_steal_pointer(&array); } static void fu_history_class_init(FuHistoryClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_history_finalize; } static void fu_history_init(FuHistory *self) { #ifdef HAVE_SQLITE g_rw_lock_init(&self->db_mutex); #endif } static void fu_history_finalize(GObject *object) { #ifdef HAVE_SQLITE FuHistory *self = FU_HISTORY(object); g_rw_lock_clear(&self->db_mutex); if (self->db != NULL) sqlite3_close(self->db); #endif G_OBJECT_CLASS(fu_history_parent_class)->finalize(object); } /** * fu_history_new: * * Creates a new #FuHistory * * Since: 1.0.4 **/ FuHistory * fu_history_new(void) { FuHistory *self; self = g_object_new(FU_TYPE_PENDING, NULL); return FU_HISTORY(self); } fwupd-1.9.16/src/fu-history.h000066400000000000000000000032321460375044200160130ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_PENDING (fu_history_get_type()) G_DECLARE_FINAL_TYPE(FuHistory, fu_history, FU, HISTORY, GObject) FuHistory * fu_history_new(void); gboolean fu_history_add_device(FuHistory *self, FuDevice *device, FwupdRelease *release, GError **error); gboolean fu_history_modify_device(FuHistory *self, FuDevice *device, GError **error); gboolean fu_history_modify_device_release(FuHistory *self, FuDevice *device, FwupdRelease *release, GError **error); gboolean fu_history_remove_device(FuHistory *self, FuDevice *device, GError **error); gboolean fu_history_remove_all(FuHistory *self, GError **error); FuDevice * fu_history_get_device_by_id(FuHistory *self, const gchar *device_id, GError **error); GPtrArray * fu_history_get_devices(FuHistory *self, GError **error); gboolean fu_history_clear_approved_firmware(FuHistory *self, GError **error); gboolean fu_history_add_approved_firmware(FuHistory *self, const gchar *checksum, GError **error); GPtrArray * fu_history_get_approved_firmware(FuHistory *self, GError **error); gboolean fu_history_clear_blocked_firmware(FuHistory *self, GError **error); gboolean fu_history_add_blocked_firmware(FuHistory *self, const gchar *checksum, GError **error); GPtrArray * fu_history_get_blocked_firmware(FuHistory *self, GError **error); gboolean fu_history_add_security_attribute(FuHistory *self, const gchar *security_attr_json, const gchar *hsi_score, GError **error); GPtrArray * fu_history_get_security_attrs(FuHistory *self, guint limit, GError **error); fwupd-1.9.16/src/fu-idle.c000066400000000000000000000120241460375044200152210ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuIdle" #include "config.h" #include "fu-idle.h" static void fu_idle_finalize(GObject *obj); struct _FuIdle { GObject parent_instance; GPtrArray *items; /* of FuIdleItem */ guint idle_id; guint timeout; FuIdleInhibit inhibit_old; }; enum { SIGNAL_INHIBIT_CHANGED, SIGNAL_TIMEOUT, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; typedef struct { FuIdleInhibit inhibit; gchar *reason; guint32 token; } FuIdleItem; G_DEFINE_TYPE(FuIdle, fu_idle, G_TYPE_OBJECT) static gboolean fu_idle_check_cb(gpointer user_data) { FuIdle *self = FU_IDLE(user_data); g_signal_emit(self, signals[SIGNAL_TIMEOUT], 0); return G_SOURCE_CONTINUE; } static void fu_idle_start(FuIdle *self) { if (self->idle_id != 0) return; if (self->timeout == 0) return; self->idle_id = g_timeout_add_seconds(self->timeout, fu_idle_check_cb, self); } static void fu_idle_stop(FuIdle *self) { if (self->idle_id == 0) return; g_source_remove(self->idle_id); self->idle_id = 0; } static void fu_idle_emit_inhibit_changed(FuIdle *self) { FuIdleInhibit inhibit_global = FU_IDLE_INHIBIT_NONE; fu_idle_reset(self); for (guint i = 0; i < self->items->len; i++) { FuIdleItem *item = g_ptr_array_index(self->items, i); inhibit_global |= item->inhibit; } if (self->inhibit_old != inhibit_global) { g_autofree gchar *inhibit_str = fu_idle_inhibit_to_string(inhibit_global); g_debug("now inhibited: %s", inhibit_str); g_signal_emit(self, signals[SIGNAL_INHIBIT_CHANGED], 0, inhibit_global); self->inhibit_old = inhibit_global; } } void fu_idle_reset(FuIdle *self) { g_return_if_fail(FU_IS_IDLE(self)); fu_idle_stop(self); if (!fu_idle_has_inhibit(self, FU_IDLE_INHIBIT_TIMEOUT)) fu_idle_start(self); } void fu_idle_uninhibit(FuIdle *self, guint32 token) { g_return_if_fail(FU_IS_IDLE(self)); g_return_if_fail(token != 0); for (guint i = 0; i < self->items->len; i++) { FuIdleItem *item = g_ptr_array_index(self->items, i); if (item->token == token) { g_autofree gchar *inhibit_str = fu_idle_inhibit_to_string(item->inhibit); g_debug("uninhibiting: %s by %s", inhibit_str, item->reason); g_ptr_array_remove_index(self->items, i); break; } } fu_idle_emit_inhibit_changed(self); } guint32 fu_idle_inhibit(FuIdle *self, FuIdleInhibit inhibit, const gchar *reason) { FuIdleItem *item; g_autofree gchar *inhibit_str = fu_idle_inhibit_to_string(inhibit); g_return_val_if_fail(FU_IS_IDLE(self), 0); g_return_val_if_fail(inhibit != FU_IDLE_INHIBIT_NONE, 0); g_debug("inhibiting: %s by %s", inhibit_str, reason); item = g_new0(FuIdleItem, 1); item->inhibit = inhibit; item->reason = g_strdup(reason); item->token = g_random_int_range(1, G_MAXINT); g_ptr_array_add(self->items, item); fu_idle_emit_inhibit_changed(self); return item->token; } gboolean fu_idle_has_inhibit(FuIdle *self, FuIdleInhibit inhibit) { g_return_val_if_fail(FU_IS_IDLE(self), FALSE); g_return_val_if_fail(inhibit != FU_IDLE_INHIBIT_NONE, FALSE); for (guint i = 0; i < self->items->len; i++) { FuIdleItem *item = g_ptr_array_index(self->items, i); if (item->inhibit & inhibit) return TRUE; } return FALSE; } void fu_idle_set_timeout(FuIdle *self, guint timeout) { g_return_if_fail(FU_IS_IDLE(self)); g_debug("setting timeout to %us", timeout); self->timeout = timeout; fu_idle_reset(self); } static void fu_idle_item_free(FuIdleItem *item) { g_free(item->reason); g_free(item); } FuIdleLocker * fu_idle_locker_new(FuIdle *self, FuIdleInhibit inhibit, const gchar *reason) { FuIdleLocker *locker = g_new0(FuIdleLocker, 1); locker->idle = g_object_ref(self); locker->token = fu_idle_inhibit(self, inhibit, reason); return locker; } void fu_idle_locker_free(FuIdleLocker *locker) { if (locker == NULL) return; fu_idle_uninhibit(locker->idle, locker->token); g_object_unref(locker->idle); g_free(locker); } static void fu_idle_class_init(FuIdleClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_idle_finalize; signals[SIGNAL_INHIBIT_CHANGED] = g_signal_new("inhibit-changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); signals[SIGNAL_TIMEOUT] = g_signal_new("timeout", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void fu_idle_init(FuIdle *self) { self->items = g_ptr_array_new_with_free_func((GDestroyNotify)fu_idle_item_free); } static void fu_idle_finalize(GObject *obj) { FuIdle *self = FU_IDLE(obj); fu_idle_stop(self); g_ptr_array_unref(self->items); G_OBJECT_CLASS(fu_idle_parent_class)->finalize(obj); } FuIdle * fu_idle_new(void) { FuIdle *self; self = g_object_new(FU_TYPE_IDLE, NULL); return FU_IDLE(self); } fwupd-1.9.16/src/fu-idle.h000066400000000000000000000017621460375044200152350ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-engine-struct.h" #define FU_TYPE_IDLE (fu_idle_get_type()) G_DECLARE_FINAL_TYPE(FuIdle, fu_idle, FU, IDLE, GObject) FuIdle * fu_idle_new(void); guint32 fu_idle_inhibit(FuIdle *self, FuIdleInhibit inhibit, const gchar *reason); void fu_idle_uninhibit(FuIdle *self, guint32 token); gboolean fu_idle_has_inhibit(FuIdle *self, FuIdleInhibit inhibit); void fu_idle_set_timeout(FuIdle *self, guint timeout); void fu_idle_reset(FuIdle *self); /** * FuIdleLocker: * @idle: A #FuIdle * @token: A #guint32 number * * A locker to prevent daemon from shutting down on its own **/ typedef struct { FuIdle *idle; guint32 token; } FuIdleLocker; FuIdleLocker * fu_idle_locker_new(FuIdle *self, FuIdleInhibit inhibit, const gchar *reason); void fu_idle_locker_free(FuIdleLocker *locker); G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuIdleLocker, fu_idle_locker_free) fwupd-1.9.16/src/fu-main-windows.c000066400000000000000000000101071460375044200167200ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ * * - sc create fwupd start="auto" binPath="C:\Program Files (x86)\fwupd\bin\fwupd.exe" * - sc delete fwupd */ #define G_LOG_DOMAIN "FuMain" #include "config.h" #include #include #include "fu-daemon.h" #include "fu-debug.h" static SERVICE_STATUS gSvcStatus = {.dwServiceType = SERVICE_WIN32_OWN_PROCESS, .dwServiceSpecificExitCode = 0}; static SERVICE_STATUS_HANDLE gSvcStatusHandle = 0; static FuDaemon *gDaemon = NULL; static void fu_main_svc_report_status(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) { static DWORD dwCheckPoint = 1; gSvcStatus.dwCurrentState = dwCurrentState; gSvcStatus.dwWin32ExitCode = dwWin32ExitCode; gSvcStatus.dwWaitHint = dwWaitHint; if (dwCurrentState == SERVICE_START_PENDING) gSvcStatus.dwControlsAccepted = 0; else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; if (dwCurrentState == SERVICE_RUNNING || dwCurrentState == SERVICE_STOPPED) gSvcStatus.dwCheckPoint = 0; else gSvcStatus.dwCheckPoint = dwCheckPoint++; SetServiceStatus(gSvcStatusHandle, &gSvcStatus); } static gboolean fu_main_svc_control_stop_cb(gpointer user_data) { FuDaemon *daemon = FU_DAEMON(user_data); fu_daemon_stop(daemon); return G_SOURCE_REMOVE; } static void fu_main_svc_control_cb(DWORD dwCtrl) { switch (dwCtrl) { case SERVICE_CONTROL_STOP: fu_main_svc_report_status(SERVICE_STOP_PENDING, NO_ERROR, 0); /* there is no user_data, because global state with threads is completely fine */ g_idle_add(fu_main_svc_control_stop_cb, gDaemon); fu_main_svc_report_status(gSvcStatus.dwCurrentState, NO_ERROR, 0); break; default: break; } } static void fu_main_svc_main_cb(DWORD dwArgc, LPSTR *lpszArgv) { g_autoptr(FuDaemon) daemon = gDaemon = fu_daemon_new(); g_autoptr(GError) error = NULL; g_autoptr(GOptionContext) context = g_option_context_new(NULL); /* parse debugging args */ g_option_context_add_group(context, fu_debug_get_option_group()); if (!g_option_context_parse(context, (gint *)&dwArgc, &lpszArgv, &error)) { g_printerr("Failed to parse command line: %s\n", error->message); return; } gSvcStatusHandle = RegisterServiceCtrlHandlerA((LPSTR) "fwupd", fu_main_svc_control_cb); if (gSvcStatusHandle == NULL) { g_warning("RegisterServiceCtrlHandlerA failed [%u]", (guint)GetLastError()); return; } /* set up the daemon, which includes coldplugging devices -- then run it */ fu_main_svc_report_status(SERVICE_START_PENDING, NO_ERROR, 1000); if (!fu_daemon_setup(daemon, FWUPD_DBUS_SOCKET_ADDRESS, &error)) { g_warning("Failed to load daemon: %s", error->message); return; } fu_main_svc_report_status(SERVICE_RUNNING, NO_ERROR, 0); fu_daemon_start(daemon); fu_main_svc_report_status(SERVICE_STOPPED, NO_ERROR, 0); } static int fu_main_console(int argc, char *argv[]) { g_autoptr(FuDaemon) daemon = gDaemon = fu_daemon_new(); g_autoptr(GError) error = NULL; g_autoptr(GOptionContext) context = g_option_context_new(NULL); /* parse debugging args */ g_option_context_add_group(context, fu_debug_get_option_group()); if (!g_option_context_parse(context, &argc, &argv, &error)) { g_printerr("Failed to parse command line: %s\n", error->message); return EXIT_FAILURE; } /* set up the daemon, which includes coldplugging devices -- then run it */ if (!fu_daemon_setup(daemon, FWUPD_DBUS_SOCKET_ADDRESS, &error)) { g_printerr("Failed to load daemon: %s\n", error->message); return EXIT_FAILURE; } fu_daemon_start(daemon); /* success */ g_message("Daemon ready for requests"); return EXIT_SUCCESS; } int main(int argc, char *argv[]) { SERVICE_TABLE_ENTRYA svc_table[] = {{(LPSTR) "fwupd", fu_main_svc_main_cb}, {NULL, NULL}}; if (!StartServiceCtrlDispatcherA(svc_table)) { /* program is being run as a console application rather than as a service */ if (GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) return fu_main_console(argc, argv); g_printerr("StartServiceCtrlDispatcherA failed [%u]\n", (guint)GetLastError()); return EXIT_FAILURE; } return EXIT_SUCCESS; } fwupd-1.9.16/src/fu-main.c000066400000000000000000000137621460375044200152420ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include "config.h" #include #include #include #include #include #ifdef HAVE_GIO_UNIX #include #endif #ifdef HAVE_SYSTEMD #include #endif #ifdef HAVE_MALLOC_H #include #endif #include "fu-daemon.h" #include "fu-debug.h" #ifdef HAVE_GIO_UNIX static gboolean fu_main_sigterm_cb(gpointer user_data) { FuDaemon *daemon = FU_DAEMON(user_data); g_warning("Received SIGTERM"); fu_daemon_stop(daemon); return G_SOURCE_CONTINUE; } #endif static gboolean fu_main_timed_exit_cb(gpointer user_data) { FuDaemon *daemon = FU_DAEMON(user_data); fu_daemon_stop(daemon); return G_SOURCE_REMOVE; } static void fu_main_argv_changed_cb(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { FuDaemon *daemon = FU_DAEMON(user_data); g_info("binary changed, shutting down"); fu_daemon_stop(daemon); } static void fu_main_memory_monitor_warning_cb(GMemoryMonitor *memory_monitor, GMemoryMonitorWarningLevel level, FuDaemon *daemon) { g_info("OOM event, shutting down"); fu_daemon_stop(daemon); } static gboolean fu_main_is_hypervisor(void) { g_autofree gchar *buf = NULL; gsize bufsz = 0; if (!g_file_get_contents("/proc/cpuinfo", &buf, &bufsz, NULL)) return FALSE; return g_strstr_len(buf, (gssize)bufsz, "hypervisor") != NULL; } static gboolean fu_main_is_container(void) { g_autofree gchar *buf = NULL; gsize bufsz = 0; if (!g_file_get_contents("/proc/1/cgroup", &buf, &bufsz, NULL)) return FALSE; if (g_strstr_len(buf, (gssize)bufsz, "docker") != NULL) return TRUE; if (g_strstr_len(buf, (gssize)bufsz, "lxc") != NULL) return TRUE; return FALSE; } int main(int argc, char *argv[]) { gboolean immediate_exit = FALSE; gboolean timed_exit = FALSE; const gchar *socket_filename = g_getenv("FWUPD_DBUS_SOCKET"); const GOptionEntry options[] = { {"timed-exit", '\0', 0, G_OPTION_ARG_NONE, &timed_exit, /* TRANSLATORS: exit after we've started up, used for user profiling */ N_("Exit after a small delay"), NULL}, {"immediate-exit", '\0', 0, G_OPTION_ARG_NONE, &immediate_exit, /* TRANSLATORS: exit straight away, used for automatic profiling */ N_("Exit after the engine has loaded"), NULL}, {NULL}}; g_autofree gchar *socket_address = NULL; g_autoptr(GError) error = NULL; g_autoptr(GFile) argv0_file = g_file_new_for_path(argv[0]); g_autoptr(GOptionContext) context = NULL; g_autoptr(FuDaemon) daemon = fu_daemon_new(); g_autoptr(GFileMonitor) argv0_monitor = NULL; g_autoptr(GMemoryMonitor) memory_monitor = NULL; setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); #ifndef SUPPORTED_BUILD /* make critical warnings fatal */ (void)g_setenv("G_DEBUG", "fatal-criticals", FALSE); #endif /* TRANSLATORS: program name */ g_set_application_name(_("Firmware Update Daemon")); context = g_option_context_new(NULL); g_option_context_add_main_entries(context, options, NULL); g_option_context_add_group(context, fu_debug_get_option_group()); /* TRANSLATORS: program summary */ g_option_context_set_summary(context, _("Firmware Update D-Bus Service")); if (!g_option_context_parse(context, &argc, &argv, &error)) { g_printerr("Failed to parse command line: %s\n", error->message); return EXIT_FAILURE; } /* detect the machine kind */ if (fu_main_is_hypervisor()) { fu_daemon_set_machine_kind(daemon, FU_DAEMON_MACHINE_KIND_VIRTUAL); } else if (fu_main_is_container()) { fu_daemon_set_machine_kind(daemon, FU_DAEMON_MACHINE_KIND_CONTAINER); } else { fu_daemon_set_machine_kind(daemon, FU_DAEMON_MACHINE_KIND_PHYSICAL); } #ifdef FWUPD_DBUS_SOCKET_ADDRESS /* this is set for macOS and Windows */ if (socket_filename == NULL) socket_filename = g_strdup(FWUPD_DBUS_SOCKET_ADDRESS); #endif /* convert from filename to address, if required */ if (socket_filename != NULL) { if (g_strrstr(socket_filename, "=") == NULL) { #ifndef HAVE_SYSTEMD /* this must be owned by root */ if (g_file_test(socket_filename, G_FILE_TEST_EXISTS)) g_unlink(socket_filename); #endif socket_address = g_strdup_printf("unix:path=%s", socket_filename); } else { socket_address = g_strdup(socket_filename); } } /* set up the daemon, which includes coldplugging devices */ if (!fu_daemon_setup(daemon, socket_address, &error)) { g_printerr("Failed to load daemon: %s\n", error->message); return EXIT_FAILURE; } #ifdef HAVE_GIO_UNIX g_unix_signal_add_full(G_PRIORITY_DEFAULT, SIGTERM, fu_main_sigterm_cb, daemon, NULL); #endif /* HAVE_GIO_UNIX */ /* restart the daemon if the binary gets replaced */ argv0_monitor = g_file_monitor_file(argv0_file, G_FILE_MONITOR_NONE, NULL, &error); g_signal_connect(G_FILE_MONITOR(argv0_monitor), "changed", G_CALLBACK(fu_main_argv_changed_cb), daemon); /* shut down on low memory event as we can just rescan hardware */ memory_monitor = g_memory_monitor_dup_default(); if (memory_monitor != NULL) { g_signal_connect(G_MEMORY_MONITOR(memory_monitor), "low-memory-warning", G_CALLBACK(fu_main_memory_monitor_warning_cb), daemon); } /* Only timeout and close the mainloop if we have specified it * on the command line */ if (immediate_exit) g_idle_add(fu_main_timed_exit_cb, daemon); else if (timed_exit) g_timeout_add_seconds(5, fu_main_timed_exit_cb, daemon); #ifdef HAVE_MALLOC_TRIM /* drop heap except one page */ malloc_trim(4096); #endif /* wait */ g_message("Daemon ready for requests (locale %s)", g_getenv("LANG")); fu_daemon_start(daemon); #ifdef HAVE_SYSTEMD /* notify the service manager */ sd_notify(0, "STOPPING=1"); #endif /* cancel to avoid a deadlock */ g_file_monitor_cancel(argv0_monitor); /* success */ return EXIT_SUCCESS; } fwupd-1.9.16/src/fu-offline.c000066400000000000000000000206631460375044200157360ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include #include "fu-history.h" #include "fu-plugin-private.h" #include "fu-spawn.h" #include "fu-util-common.h" typedef enum { FU_OFFLINE_FLAG_NONE = 0, FU_OFFLINE_FLAG_ENABLE = 1 << 0, FU_OFFLINE_FLAG_USE_PROGRESS = 1 << 1, } FuOfflineFlag; struct FuUtilPrivate { gchar *splash_cmd; GTimer *splash_timer; FuOfflineFlag splash_flags; }; static gboolean fu_offline_set_splash_progress(FuUtilPrivate *priv, guint percentage, GError **error) { g_autofree gchar *str = g_strdup_printf("%u", percentage); const gchar *argv[] = {priv->splash_cmd, "system-update", "--progress", str, NULL}; /* call into plymouth if installed */ if (priv->splash_flags == FU_OFFLINE_FLAG_NONE) { /* TRANSLATORS: console message when not using plymouth */ g_printerr("%s: %u%%\n", _("Percentage complete"), percentage); return TRUE; } /* fall back to really old mode that should be supported by anything */ if ((priv->splash_flags & FU_OFFLINE_FLAG_USE_PROGRESS) == 0) { argv[1] = "display-message"; argv[2] = "--text"; } return fu_spawn_sync(argv, NULL, NULL, 200, NULL, error); } static gboolean fu_offline_set_splash_mode(FuUtilPrivate *priv, GError **error) { g_autoptr(GError) error_local = NULL; const gchar *argv[] = {priv->splash_cmd, "change-mode", "--system-upgrade", NULL}; /* call into plymouth if installed */ if (priv->splash_cmd == NULL) { /* TRANSLATORS: console message when no Plymouth is installed */ g_printerr("%s\n", _("Installing Firmware…")); return TRUE; } /* try the new fancy mode, then fall back to really old mode */ if (!fu_spawn_sync(argv, NULL, NULL, 1500, NULL, &error_local)) { argv[2] = "--updates"; if (!fu_spawn_sync(argv, NULL, NULL, 1500, NULL, error)) { g_prefix_error(error, "%s: ", error_local->message); return FALSE; } priv->splash_flags = FU_OFFLINE_FLAG_ENABLE; return TRUE; } /* success */ priv->splash_flags = FU_OFFLINE_FLAG_ENABLE | FU_OFFLINE_FLAG_USE_PROGRESS; return TRUE; } static gboolean fu_offline_set_splash_reboot(FuUtilPrivate *priv, GError **error) { g_autoptr(GError) error_local = NULL; const gchar *argv[] = {priv->splash_cmd, "change-mode", "--reboot", NULL}; /* call into plymouth if installed */ if (priv->splash_flags == FU_OFFLINE_FLAG_NONE) { /* TRANSLATORS: console message when not using plymouth */ g_printerr("%s\n", _("Rebooting…")); return TRUE; } /* try the new fancy mode, then fall back to really old mode */ if (!fu_spawn_sync(argv, NULL, NULL, 200, NULL, &error_local)) { /* fall back to really old mode that should be supported */ argv[2] = "--shutdown"; if (!fu_spawn_sync(argv, NULL, NULL, 200, NULL, error)) { g_prefix_error(error, "%s: ", error_local->message); return FALSE; } return TRUE; } /* success */ return TRUE; } static void fu_offline_client_notify_cb(GObject *object, GParamSpec *pspec, gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *)user_data; FwupdClient *client = FWUPD_CLIENT(object); /* rate limit to 1 second */ if (g_timer_elapsed(priv->splash_timer, NULL) < 1.f || fwupd_client_get_percentage(client) < 5) return; fu_offline_set_splash_progress(priv, fwupd_client_get_percentage(client), NULL); g_timer_reset(priv->splash_timer); } static void fu_util_private_free(FuUtilPrivate *priv) { if (priv->splash_timer != NULL) g_timer_destroy(priv->splash_timer); g_free(priv->splash_cmd); g_free(priv); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free) #pragma clang diagnostic pop int main(int argc, char *argv[]) { gint vercmp; guint cnt = 0; g_autofree gchar *link = NULL; g_autofree gchar *target = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); g_autofree gchar *trigger = fu_path_from_kind(FU_PATH_KIND_OFFLINE_TRIGGER); g_autoptr(FuHistory) history = NULL; g_autoptr(FwupdClient) client = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) results = NULL; g_autoptr(FuUtilPrivate) priv = g_new0(FuUtilPrivate, 1); setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); #ifndef SUPPORTED_BUILD /* make critical warnings fatal */ (void)g_setenv("G_DEBUG", "fatal-criticals", FALSE); #endif /* verify this is pointing to our cache */ link = g_file_read_link(trigger, NULL); if (link == NULL) return EXIT_SUCCESS; if (g_strcmp0(link, target) != 0) return EXIT_SUCCESS; /* do this first to avoid a loop if this tool segfaults */ (void)g_unlink(trigger); /* ensure root user */ #ifdef HAVE_GETUID if (getuid() != 0 || geteuid() != 0) { /* TRANSLATORS: the user needs to stop playing with stuff */ g_printerr("%s\n", _("This tool can only be used by the root user")); return EXIT_FAILURE; } #endif /* find plymouth, but not an error if not found */ priv->splash_cmd = g_find_program_in_path("plymouth"); priv->splash_timer = g_timer_new(); /* ensure D-Bus errors are registered */ fwupd_error_quark(); /* get prepared updates */ history = fu_history_new(); results = fu_history_get_devices(history, &error); if (results == NULL) { /* TRANSLATORS: we could not get the devices to update offline */ g_printerr("%s: %s\n", _("Failed to get pending devices"), error->message); return EXIT_FAILURE; } /* connect to the daemon */ client = fwupd_client_new(); g_signal_connect(FWUPD_CLIENT(client), "notify::percentage", G_CALLBACK(fu_offline_client_notify_cb), priv); if (!fwupd_client_connect(client, NULL, &error)) { /* TRANSLATORS: we could not talk to the fwupd daemon */ g_printerr("%s: %s\n", _("Failed to connect to daemon"), error->message); return EXIT_FAILURE; } /* set up splash */ if (!fu_offline_set_splash_mode(priv, &error)) { /* TRANSLATORS: we could not talk to plymouth */ g_printerr("%s: %s\n", _("Failed to set splash mode"), error->message); return EXIT_FAILURE; } /* apply each update */ for (guint i = 0; i < results->len; i++) { FwupdDevice *dev = g_ptr_array_index(results, i); FwupdRelease *rel = fwupd_device_get_release_default(dev); /* check not already done */ if (fwupd_device_get_update_state(dev) != FWUPD_UPDATE_STATE_PENDING) continue; /* tell the user what's going to happen */ vercmp = fu_version_compare(fwupd_device_get_version(dev), fwupd_release_get_version(rel), fwupd_device_get_version_format(dev)); if (vercmp == 0) { /* TRANSLATORS: the first replacement is a display name * e.g. "ColorHugALS" and the second is a version number * e.g. "1.2.3" */ g_print(_("Reinstalling %s with %s... "), fwupd_device_get_name(dev), fwupd_release_get_version(rel)); } else if (vercmp > 0) { /* TRANSLATORS: the first replacement is a display name * e.g. "ColorHugALS" and the second and third are * version numbers e.g. "1.2.3" */ g_print(_("Downgrading %s from %s to %s... "), fwupd_device_get_name(dev), fwupd_device_get_version(dev), fwupd_release_get_version(rel)); } else if (vercmp < 0) { /* TRANSLATORS: the first replacement is a display name * e.g. "ColorHugALS" and the second and third are * version numbers e.g. "1.2.3" */ g_print(_("Updating %s from %s to %s... "), fwupd_device_get_name(dev), fwupd_device_get_version(dev), fwupd_release_get_version(rel)); } if (!fwupd_client_install(client, fwupd_device_get_id(dev), fwupd_release_get_filename(rel), FWUPD_INSTALL_FLAG_ALLOW_REINSTALL | FWUPD_INSTALL_FLAG_ALLOW_OLDER | FWUPD_INSTALL_FLAG_OFFLINE, NULL, &error)) { g_printerr("%s: %s\n", /* TRANSLATORS: we could not install for some reason */ _("Failed to install firmware update"), error->message); return EXIT_FAILURE; } cnt++; } /* nothing to do */ if (cnt == 0) { /* TRANSLATORS: nothing was updated offline */ g_printerr("%s\n", _("No updates were applied")); return EXIT_FAILURE; } /* reboot */ fu_offline_set_splash_reboot(priv, NULL); if (!fu_util_update_reboot(&error)) { /* TRANSLATORS: we could not reboot for some reason */ g_printerr("%s: %s\n", _("Failed to reboot"), error->message); return EXIT_FAILURE; } /* success */ g_print("%s\n", _("Done!")); return EXIT_SUCCESS; } fwupd-1.9.16/src/fu-plugin-list.c000066400000000000000000000226571460375044200165700ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuPluginList" #include "config.h" #include "fu-plugin-list.h" #include "fu-plugin-private.h" /** * FuPluginList: * * This list of plugins provides a way to get the specific plugin quickly using * a hash table and also any plugin-list specific functionality such as * sorting by dependency order. * * See also: [class@FuPlugin] */ static void fu_plugin_list_finalize(GObject *obj); struct _FuPluginList { GObject parent_instance; GPtrArray *plugins; /* of FuPlugin */ GHashTable *plugins_hash; /* of name : FuPlugin */ }; G_DEFINE_TYPE(FuPluginList, fu_plugin_list, G_TYPE_OBJECT) /** * fu_plugin_list_get_all: * @self: a #FuPluginList * * Gets all the plugins that have been added. * * Returns: (transfer none) (element-type FuPlugin): the plugins * * Since: 1.0.2 **/ GPtrArray * fu_plugin_list_get_all(FuPluginList *self) { g_return_val_if_fail(FU_IS_PLUGIN_LIST(self), NULL); return self->plugins; } static void fu_plugin_list_rules_changed_cb(FuPlugin *plugin, gpointer user_data) { FuPluginList *self = FU_PLUGIN_LIST(user_data); GPtrArray *rules = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_CONFLICTS); if (rules == NULL) return; for (guint j = 0; j < rules->len; j++) { const gchar *plugin_name = g_ptr_array_index(rules, j); FuPlugin *dep = fu_plugin_list_find_by_name(self, plugin_name, NULL); if (dep == NULL) continue; if (fu_plugin_has_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED)) continue; g_info("late disabling %s as conflicts with %s", fu_plugin_get_name(dep), fu_plugin_get_name(plugin)); fu_plugin_add_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED); } } /** * fu_plugin_list_add: * @self: a #FuPluginList * @plugin: a plugin * * Adds a plugin to the list. The plugin name is used for a hash key and must * be set before calling this function. * * Since: 1.0.2 **/ void fu_plugin_list_add(FuPluginList *self, FuPlugin *plugin) { g_return_if_fail(FU_IS_PLUGIN_LIST(self)); g_return_if_fail(FU_IS_PLUGIN(plugin)); g_return_if_fail(fu_plugin_get_name(plugin) != NULL); g_ptr_array_add(self->plugins, g_object_ref(plugin)); g_hash_table_insert(self->plugins_hash, g_strdup(fu_plugin_get_name(plugin)), g_object_ref(plugin)); g_signal_connect(FU_PLUGIN(plugin), "rules-changed", G_CALLBACK(fu_plugin_list_rules_changed_cb), self); } /** * fu_plugin_list_find_by_name: * @self: a #FuPluginList * @name: a plugin name, e.g. `dfu` * @error: (nullable): optional return location for an error * * Finds a specific plugin using the plugin name. * * Returns: (transfer none): a plugin, or %NULL * * Since: 1.0.2 **/ FuPlugin * fu_plugin_list_find_by_name(FuPluginList *self, const gchar *name, GError **error) { FuPlugin *plugin; g_return_val_if_fail(FU_IS_PLUGIN_LIST(self), NULL); g_return_val_if_fail(name != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* sanity check */ if (self->plugins->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no plugins loaded"); return NULL; } plugin = g_hash_table_lookup(self->plugins_hash, name); if (plugin == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no plugin %s found", name); return NULL; } return plugin; } static gint fu_plugin_list_sort_cb(gconstpointer a, gconstpointer b) { FuPlugin **pa = (FuPlugin **)a; FuPlugin **pb = (FuPlugin **)b; return fu_plugin_order_compare(*pa, *pb); } /** * fu_plugin_list_depsolve: * @self: a #FuPluginList * @error: (nullable): optional return location for an error * * Depsolves the list of plugins into the correct order. Some plugin methods * are called on all plugins and for some situations the order they are called * may be important. Use fu_plugin_add_rule() to affect the depsolved order * if required. * * Returns: %TRUE for success, or %FALSE if the set could not be depsolved * * Since: 1.0.2 **/ gboolean fu_plugin_list_depsolve(FuPluginList *self, GError **error) { FuPlugin *dep; GPtrArray *deps; gboolean changes; guint dep_loop_check = 0; g_return_val_if_fail(FU_IS_PLUGIN_LIST(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* order by deps */ do { changes = FALSE; for (guint i = 0; i < self->plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index(self->plugins, i); deps = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_RUN_AFTER); if (deps == NULL) continue; for (guint j = 0; j < deps->len && !changes; j++) { const gchar *plugin_name = g_ptr_array_index(deps, j); dep = fu_plugin_list_find_by_name(self, plugin_name, NULL); if (dep == NULL) { g_info("cannot find plugin '%s' " "requested by '%s'", plugin_name, fu_plugin_get_name(plugin)); continue; } if (fu_plugin_has_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED)) continue; if (fu_plugin_get_order(plugin) <= fu_plugin_get_order(dep)) { g_debug("%s [%u] to be ordered after %s [%u] " "so promoting to [%u]", fu_plugin_get_name(plugin), fu_plugin_get_order(plugin), fu_plugin_get_name(dep), fu_plugin_get_order(dep), fu_plugin_get_order(dep) + 1); fu_plugin_set_order(plugin, fu_plugin_get_order(dep) + 1); changes = TRUE; } } } for (guint i = 0; i < self->plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index(self->plugins, i); deps = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_RUN_BEFORE); if (deps == NULL) continue; for (guint j = 0; j < deps->len && !changes; j++) { const gchar *plugin_name = g_ptr_array_index(deps, j); dep = fu_plugin_list_find_by_name(self, plugin_name, NULL); if (dep == NULL) { g_info("cannot find plugin '%s' " "requested by '%s'", plugin_name, fu_plugin_get_name(plugin)); continue; } if (fu_plugin_has_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED)) continue; if (fu_plugin_get_order(plugin) >= fu_plugin_get_order(dep)) { g_debug("%s [%u] to be ordered before %s [%u] " "so promoting to [%u]", fu_plugin_get_name(plugin), fu_plugin_get_order(plugin), fu_plugin_get_name(dep), fu_plugin_get_order(dep), fu_plugin_get_order(dep) + 1); fu_plugin_set_order(dep, fu_plugin_get_order(plugin) + 1); changes = TRUE; } } } /* set priority as well */ for (guint i = 0; i < self->plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index(self->plugins, i); deps = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_BETTER_THAN); if (deps == NULL) continue; for (guint j = 0; j < deps->len && !changes; j++) { const gchar *plugin_name = g_ptr_array_index(deps, j); dep = fu_plugin_list_find_by_name(self, plugin_name, NULL); if (dep == NULL) { g_info("cannot find plugin '%s' " "referenced by '%s'", plugin_name, fu_plugin_get_name(plugin)); continue; } if (fu_plugin_has_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED)) continue; if (fu_plugin_get_priority(plugin) <= fu_plugin_get_priority(dep)) { g_debug("%s [%u] better than %s [%u] " "so bumping to [%u]", fu_plugin_get_name(plugin), fu_plugin_get_priority(plugin), fu_plugin_get_name(dep), fu_plugin_get_priority(dep), fu_plugin_get_priority(dep) + 1); fu_plugin_set_priority(plugin, fu_plugin_get_priority(dep) + 1); changes = TRUE; } } } /* check we're not stuck */ if (dep_loop_check++ > 100) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "got stuck in dep loop"); return FALSE; } } while (changes); /* check for conflicts */ for (guint i = 0; i < self->plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index(self->plugins, i); if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) continue; deps = fu_plugin_get_rules(plugin, FU_PLUGIN_RULE_CONFLICTS); if (deps == NULL) continue; for (guint j = 0; j < deps->len; j++) { const gchar *plugin_name = g_ptr_array_index(deps, j); dep = fu_plugin_list_find_by_name(self, plugin_name, NULL); if (dep == NULL) continue; if (fu_plugin_has_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED)) continue; g_info("disabling %s as conflicts with %s", fu_plugin_get_name(dep), fu_plugin_get_name(plugin)); fu_plugin_add_flag(dep, FWUPD_PLUGIN_FLAG_DISABLED); } } /* sort by order */ g_ptr_array_sort(self->plugins, fu_plugin_list_sort_cb); return TRUE; } static void fu_plugin_list_class_init(FuPluginListClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_plugin_list_finalize; } static void fu_plugin_list_init(FuPluginList *self) { self->plugins = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); self->plugins_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_object_unref); } static void fu_plugin_list_finalize(GObject *obj) { FuPluginList *self = FU_PLUGIN_LIST(obj); g_ptr_array_unref(self->plugins); g_hash_table_unref(self->plugins_hash); G_OBJECT_CLASS(fu_plugin_list_parent_class)->finalize(obj); } /** * fu_plugin_list_new: * * Creates a new plugin list. * * Returns: (transfer full): a #FuPluginList * * Since: 1.0.2 **/ FuPluginList * fu_plugin_list_new(void) { FuPluginList *self; self = g_object_new(FU_TYPE_PLUGIN_LIST, NULL); return FU_PLUGIN_LIST(self); } fwupd-1.9.16/src/fu-plugin-list.h000066400000000000000000000011371460375044200165630ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_PLUGIN_LIST (fu_plugin_list_get_type()) G_DECLARE_FINAL_TYPE(FuPluginList, fu_plugin_list, FU, PLUGIN_LIST, GObject) FuPluginList * fu_plugin_list_new(void); void fu_plugin_list_add(FuPluginList *self, FuPlugin *plugin); GPtrArray * fu_plugin_list_get_all(FuPluginList *self); FuPlugin * fu_plugin_list_find_by_name(FuPluginList *self, const gchar *name, GError **error); gboolean fu_plugin_list_depsolve(FuPluginList *self, GError **error); fwupd-1.9.16/src/fu-polkit-agent.c000066400000000000000000000114521460375044200167060ustar00rootroot00000000000000/* * Copyright (C) 2011 Lennart Poettering * Copyright (C) 2012 Matthias Klumpp * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #ifdef __linux__ #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "fu-polkit-agent.h" static pid_t agent_pid = 0; static int fork_agent(pid_t *pid, const char *path, ...) { char **l; gboolean stderr_is_tty; gboolean stdout_is_tty; int fd; pid_t n_agent_pid; pid_t parent_pid; unsigned n, i; va_list ap; g_return_val_if_fail(pid != 0, 0); g_assert(path); parent_pid = getpid(); /* spawns a temporary TTY agent, making sure it goes away when * we go away */ n_agent_pid = fork(); if (n_agent_pid < 0) return -errno; if (n_agent_pid != 0) { *pid = n_agent_pid; return 0; } #ifdef __linux__ /* make sure the agent goes away when the parent dies */ if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) _exit(EXIT_FAILURE); #endif /* check whether our parent died before we were able * to set the death signal */ if (getppid() != parent_pid) _exit(EXIT_SUCCESS); /* TODO: it might be more clean to close all FDs so we don't leak them to the agent */ stdout_is_tty = isatty(STDOUT_FILENO); stderr_is_tty = isatty(STDERR_FILENO); if (!stdout_is_tty || !stderr_is_tty) { /* Detach from stdout/stderr. and reopen * /dev/tty for them. This is important to * ensure that when systemctl is started via * popen() or a similar call that expects to * read EOF we actually do generate EOF and * not delay this indefinitely by because we * keep an unused copy of stdin around. */ fd = open("/dev/tty", O_WRONLY); if (fd < 0) { g_critical("Failed to open /dev/tty: %m"); _exit(EXIT_FAILURE); } if (!stdout_is_tty) dup2(fd, STDOUT_FILENO); if (!stderr_is_tty) dup2(fd, STDERR_FILENO); if (fd > 2) close(fd); } /* count arguments */ va_start(ap, path); for (n = 0; va_arg(ap, char *); n++) ; va_end(ap); /* allocate strv */ l = alloca(sizeof(char *) * (n + 1)); /* fill in arguments */ va_start(ap, path); for (i = 0; i <= n; i++) l[i] = va_arg(ap, char *); va_end(ap); execv(path, l); _exit(EXIT_FAILURE); } static int close_nointr(int fd) { g_assert(fd >= 0); for (;;) { int r; r = close(fd); if (r >= 0) return r; if (errno != EINTR) return -errno; } } static void close_nointr_nofail(int fd) { int saved_errno = errno; /* cannot fail, and guarantees errno is unchanged */ g_assert(close_nointr(fd) == 0); errno = saved_errno; } static int fd_wait_for_event(int fd, int event, uint64_t t) { struct pollfd pollfd = {0}; int r; pollfd.fd = fd; pollfd.events = event; r = poll(&pollfd, 1, t == (uint64_t)-1 ? -1 : (int)(t / 1000)); if (r < 0) return -errno; if (r == 0) return 0; return pollfd.revents; } static int wait_for_terminate(pid_t pid) { g_return_val_if_fail(pid >= 1, 0); for (;;) { int status; if (waitpid(pid, &status, 0) < 0) { if (errno == EINTR) continue; return -errno; } return 0; } } gboolean fu_polkit_agent_open(GError **error) { int r; int pipe_fd[2]; g_autofree gchar *notify_fd = NULL; g_autofree gchar *pkttyagent_fn = NULL; if (agent_pid > 0) return TRUE; /* find binary */ pkttyagent_fn = fu_path_find_program("pkttyagent", error); if (pkttyagent_fn == NULL) return FALSE; /* check STDIN here, not STDOUT, since this is about input, not output */ if (!isatty(STDIN_FILENO)) return TRUE; if (pipe(pipe_fd) < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to create pipe: %s", g_strerror(errno)); return FALSE; } /* fork pkttyagent */ notify_fd = g_strdup_printf("%i", pipe_fd[1]); r = fork_agent(&agent_pid, pkttyagent_fn, pkttyagent_fn, "--notify-fd", notify_fd, "--fallback", NULL); if (r < 0) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to fork TTY ask password agent: %s", g_strerror(-r)); close_nointr_nofail(pipe_fd[1]); close_nointr_nofail(pipe_fd[0]); return FALSE; } /* close the writing side, because that is the one for the agent */ close_nointr_nofail(pipe_fd[1]); /* wait until the agent closes the fd */ fd_wait_for_event(pipe_fd[0], POLLHUP, (uint64_t)-1); close_nointr_nofail(pipe_fd[0]); return TRUE; } void fu_polkit_agent_close(void) { if (agent_pid <= 0) return; /* inform agent that we are done */ kill(agent_pid, SIGTERM); kill(agent_pid, SIGCONT); wait_for_terminate(agent_pid); agent_pid = 0; } fwupd-1.9.16/src/fu-polkit-agent.h000066400000000000000000000005361460375044200167140ustar00rootroot00000000000000/* * Copyright (C) 2011 Lennart Poettering * Copyright (C) 2012 Matthias Klumpp * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gboolean fu_polkit_agent_open(GError **error); void fu_polkit_agent_close(void); fwupd-1.9.16/src/fu-polkit-authority.c000066400000000000000000000104711460375044200176400ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuPolkitAuthority" #include "config.h" #ifdef HAVE_POLKIT #include #endif #include "fu-polkit-authority.h" #ifdef HAVE_POLKIT #ifndef HAVE_POLKIT_0_114 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(PolkitAuthorizationResult, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(PolkitSubject, g_object_unref) #pragma clang diagnostic pop #endif /* HAVE_POLKIT_0_114 */ #endif /* HAVE_POLKIT */ struct _FuPolkitAuthority { GObject parent_instance; #ifdef HAVE_POLKIT PolkitAuthority *pkauthority; #endif }; G_DEFINE_TYPE(FuPolkitAuthority, fu_polkit_authority, G_TYPE_OBJECT) gboolean fu_polkit_authority_check_finish(FuPolkitAuthority *self, GAsyncResult *res, GError **error) { g_return_val_if_fail(FU_IS_POLKIT_AUTHORITY(self), FALSE); g_return_val_if_fail(g_task_is_valid(res, self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); return g_task_propagate_boolean(G_TASK(res), error); } #ifdef HAVE_POLKIT static void fu_polkit_authority_check_cb(GObject *source_object, GAsyncResult *res, gpointer user_data) { g_autoptr(GError) error_local = NULL; g_autoptr(GTask) task = G_TASK(user_data); g_autoptr(PolkitAuthorizationResult) auth = NULL; auth = polkit_authority_check_authorization_finish(POLKIT_AUTHORITY(source_object), res, &error_local); if (auth == NULL) { g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "Could not check for auth: %s", error_local->message); return; } /* did not auth */ if (!polkit_authorization_result_get_is_authorized(auth)) { g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "Failed to obtain auth"); return; } /* success */ g_task_return_boolean(task, TRUE); } #endif void fu_polkit_authority_check(FuPolkitAuthority *self, const gchar *sender, const gchar *action_id, FuPolkitAuthorityCheckFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_autoptr(GTask) task = g_task_new(self, cancellable, callback, user_data); #ifdef HAVE_POLKIT PolkitCheckAuthorizationFlags pkflags = POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE; g_autofree gchar *owner = polkit_authority_get_owner(self->pkauthority); #endif g_return_if_fail(FU_IS_POLKIT_AUTHORITY(self)); g_return_if_fail(action_id != NULL); g_return_if_fail(callback != NULL); #ifdef HAVE_POLKIT if (owner != NULL && sender != NULL) { g_autoptr(PolkitSubject) pksubject = polkit_system_bus_name_new(sender); if (flags & FU_POLKIT_AUTHORITY_CHECK_FLAG_ALLOW_USER_INTERACTION) pkflags |= POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; polkit_authority_check_authorization(self->pkauthority, pksubject, action_id, NULL, /* details */ pkflags, cancellable, fu_polkit_authority_check_cb, g_steal_pointer(&task)); return; } #endif /* fallback to the caller being euid=0 */ if ((flags & FU_POLKIT_AUTHORITY_CHECK_FLAG_USER_IS_TRUSTED) == 0) { g_task_return_new_error(task, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "Failed to obtain auth as not trusted user"); return; } /* success */ g_task_return_boolean(task, TRUE); } static void fu_polkit_authority_init(FuPolkitAuthority *self) { } gboolean fu_polkit_authority_load(FuPolkitAuthority *self, GError **error) { #ifdef HAVE_POLKIT self->pkauthority = polkit_authority_get_sync(NULL, error); if (self->pkauthority == NULL) { g_prefix_error(error, "failed to load authority: "); return FALSE; } #endif return TRUE; } static void fu_polkit_authority_finalize(GObject *obj) { #ifdef HAVE_POLKIT FuPolkitAuthority *self = FU_POLKIT_AUTHORITY(obj); g_object_unref(self->pkauthority); #endif G_OBJECT_CLASS(fu_polkit_authority_parent_class)->finalize(obj); } static void fu_polkit_authority_class_init(FuPolkitAuthorityClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_polkit_authority_finalize; } FuPolkitAuthority * fu_polkit_authority_new(void) { FuPolkitAuthority *self; self = g_object_new(FU_TYPE_POLKIT_AUTHORITY, NULL); return FU_POLKIT_AUTHORITY(self); } fwupd-1.9.16/src/fu-polkit-authority.h000066400000000000000000000017421460375044200176460ustar00rootroot00000000000000/* * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_POLKIT_AUTHORITY (fu_polkit_authority_get_type()) G_DECLARE_FINAL_TYPE(FuPolkitAuthority, fu_polkit_authority, FU, POLKIT_AUTHORITY, GObject) typedef enum { FU_POLKIT_AUTHORITY_CHECK_FLAG_NONE = 0, FU_POLKIT_AUTHORITY_CHECK_FLAG_ALLOW_USER_INTERACTION = 1 << 0, FU_POLKIT_AUTHORITY_CHECK_FLAG_USER_IS_TRUSTED = 1 << 1, } FuPolkitAuthorityCheckFlags; FuPolkitAuthority * fu_polkit_authority_new(void); gboolean fu_polkit_authority_load(FuPolkitAuthority *self, GError **error); void fu_polkit_authority_check(FuPolkitAuthority *self, const gchar *sender, const gchar *action_id, FuPolkitAuthorityCheckFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean fu_polkit_authority_check_finish(FuPolkitAuthority *self, GAsyncResult *res, GError **error); fwupd-1.9.16/src/fu-release-common.c000066400000000000000000000011651460375044200172160ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuSpawn" #include "config.h" #include "fu-release-common.h" /** * fu_release_uri_get_scheme: * @uri: valid URI, e.g. `https://foo.bar/baz` * * Returns the USI scheme for the given URI. * * Returns: scheme value, or %NULL if invalid, e.g. `https` **/ gchar * fu_release_uri_get_scheme(const gchar *uri) { gchar *tmp; g_return_val_if_fail(uri != NULL, NULL); tmp = g_strstr_len(uri, -1, ":"); if (tmp == NULL || tmp[0] == '\0') return NULL; return g_utf8_strdown(uri, tmp - uri); } fwupd-1.9.16/src/fu-release-common.h000066400000000000000000000003051460375044200172160ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gchar * fu_release_uri_get_scheme(const gchar *uri); fwupd-1.9.16/src/fu-release.c000066400000000000000000001244671460375044200157430ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuRelease" #include "config.h" #include "fu-device-private.h" #include "fu-release-common.h" #include "fu-release.h" /** * FuRelease: * * An installable entity that has been loaded and verified for a specific device. * * See also: [class@FwupdRelease] */ struct _FuRelease { FwupdRelease parent_instance; FuEngineRequest *request; FuDevice *device; FwupdRemote *remote; FuEngineConfig *config; GBytes *blob_fw; gchar *update_request_id; gchar *device_version_old; GPtrArray *soft_reqs; /* nullable, element-type XbNode */ GPtrArray *hard_reqs; /* nullable, element-type XbNode */ guint64 priority; }; G_DEFINE_TYPE(FuRelease, fu_release, FWUPD_TYPE_RELEASE) static gboolean fu_release_ensure_trust_flags(FuRelease *self, XbNode *rel, GError **error); gchar * fu_release_to_string(FuRelease *self) { const guint idt = 1; g_autofree gchar *tmp = NULL; g_autoptr(GString) str = g_string_new(NULL); g_return_val_if_fail(FU_IS_RELEASE(self), NULL); /* parent */ tmp = fwupd_release_to_string(FWUPD_RELEASE(self)); if (tmp != NULL && tmp[0] != '\0') g_string_append(str, tmp); /* instance */ if (self->request != NULL) { fu_string_append(str, idt, "Request", NULL); fu_engine_request_add_string(self->request, idt + 1, str); } if (self->device != NULL) fu_string_append(str, idt, "Device", fu_device_get_id(self->device)); if (self->device_version_old != NULL) fu_string_append(str, idt, "DeviceVersionOld", self->device_version_old); if (self->remote != NULL) fu_string_append(str, idt, "Remote", fwupd_remote_get_id(self->remote)); fu_string_append_kb(str, idt, "HasConfig", self->config != NULL); if (self->blob_fw != NULL) fu_string_append_kx(str, idt, "BlobFwSz", g_bytes_get_size(self->blob_fw)); if (self->update_request_id != NULL) fu_string_append(str, idt, "UpdateRequestId", self->update_request_id); if (self->soft_reqs != NULL) fu_string_append_kx(str, idt, "SoftReqs", self->soft_reqs->len); if (self->hard_reqs != NULL) fu_string_append_kx(str, idt, "HardReqs", self->hard_reqs->len); if (self->priority != 0) fu_string_append_kx(str, idt, "Priority", self->priority); return g_string_free(g_steal_pointer(&str), FALSE); } /** * fu_release_set_request: * @self: a #FuRelease * @request: (nullable): a #FuEngineRequest * * Sets the user request which created this operation. **/ void fu_release_set_request(FuRelease *self, FuEngineRequest *request) { g_return_if_fail(FU_IS_RELEASE(self)); g_set_object(&self->request, request); } /** * fu_release_get_request: * @self: a #FuRelease * * Gets the user request which created this operation. * * Returns: (transfer none) (nullable): request **/ FuEngineRequest * fu_release_get_request(FuRelease *self) { g_return_val_if_fail(FU_IS_RELEASE(self), NULL); return self->request; } /** * fu_release_get_device_version_old: * @self: a #FuRelease * * Gets the original [before update] device version. * * Returns: a string value, or %NULL if never set. **/ const gchar * fu_release_get_device_version_old(FuRelease *self) { g_return_val_if_fail(FU_IS_RELEASE(self), NULL); return self->device_version_old; } static void fu_release_set_device_version_old(FuRelease *self, const gchar *device_version_old) { g_return_if_fail(FU_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(self->device_version_old, device_version_old) == 0) return; g_free(self->device_version_old); self->device_version_old = g_strdup(device_version_old); } /** * fu_release_set_device: * @self: a #FuRelease * @device: (nullable): a #FuDevice * * Sets the device this release should use when checking requirements. **/ void fu_release_set_device(FuRelease *self, FuDevice *device) { g_return_if_fail(FU_IS_RELEASE(self)); g_set_object(&self->device, device); fu_release_set_device_version_old(self, fu_device_get_version(device)); } /** * fu_release_get_device: * @self: a #FuRelease * * Gets the device this release was loaded for. * * Returns: (transfer none) (nullable): device **/ FuDevice * fu_release_get_device(FuRelease *self) { g_return_val_if_fail(FU_IS_RELEASE(self), NULL); return self->device; } /** * fu_release_get_fw_blob: * @self: a #FuRelease * * Gets the firmware payload to use when installing this release. * * Returns: (transfer none) (nullable): data **/ GBytes * fu_release_get_fw_blob(FuRelease *self) { g_return_val_if_fail(FU_IS_RELEASE(self), NULL); return self->blob_fw; } /** * fu_release_get_soft_reqs: * @self: a #FuRelease * * Gets the additional soft requirements that need to be checked in the engine. * * Returns: (transfer none) (nullable) (element-type XbNode): nodes **/ GPtrArray * fu_release_get_soft_reqs(FuRelease *self) { g_return_val_if_fail(FU_IS_RELEASE(self), NULL); return self->soft_reqs; } /** * fu_release_get_soft_reqs: * @self: a #FuRelease * * Gets the additional hard requirements that need to be checked in the engine. * * Returns: (transfer none) (nullable) (element-type XbNode): nodes **/ GPtrArray * fu_release_get_hard_reqs(FuRelease *self) { g_return_val_if_fail(FU_IS_RELEASE(self), NULL); return self->hard_reqs; } /** * fu_release_get_update_request_id: * @self: a #FuRelease * * Gets the update request ID as specified from `LVFS::UpdateRequestId`. * * Returns: a string value, or %NULL if never set. **/ const gchar * fu_release_get_update_request_id(FuRelease *self) { g_return_val_if_fail(FU_IS_RELEASE(self), NULL); return self->update_request_id; } static void fu_release_set_update_request_id(FuRelease *self, const gchar *update_request_id) { g_return_if_fail(FU_IS_RELEASE(self)); /* not changed */ if (g_strcmp0(self->update_request_id, update_request_id) == 0) return; g_free(self->update_request_id); self->update_request_id = g_strdup(update_request_id); } /** * fu_release_set_remote: * @self: a #FuRelease * @remote: (nullable): a #FwupdRemote * * Sets the remote this release should use when loading. This is typically set by the engine by *watching the `remote-id` property to be set and then querying the internal cached list of *`FuRemote`s. **/ void fu_release_set_remote(FuRelease *self, FwupdRemote *remote) { g_return_if_fail(FU_IS_RELEASE(self)); g_set_object(&self->remote, remote); } /** * fu_release_set_config: * @self: a #FuRelease * @config: (nullable): a #FuEngineConfig * * Sets the config to use when loading. The config may be used for things like ordering attributes *like protocol priority. **/ void fu_release_set_config(FuRelease *self, FuEngineConfig *config) { g_return_if_fail(FU_IS_RELEASE(self)); g_set_object(&self->config, config); } static gchar * fu_release_get_localized_xpath(FuRelease *self, const gchar *element) { GString *xpath = g_string_new(element); const gchar *locale = NULL; /* optional; not set in tests */ if (self->request != NULL) locale = fu_engine_request_get_locale(self->request); /* prefer the users locale if set */ if (locale != NULL) { g_autofree gchar *xpath_locale = NULL; xpath_locale = g_strdup_printf("%s[@xml:lang='%s']|", element, locale); g_string_prepend(xpath, xpath_locale); } return g_string_free(xpath, FALSE); } /* convert hex and decimal versions to dotted style */ static gchar * fu_release_get_release_version(FuRelease *self, const gchar *version, GError **error) { FwupdVersionFormat fmt = fu_device_get_version_format(self->device); guint64 ver_uint32; g_autoptr(GError) error_local = NULL; /* already dotted notation */ if (g_strstr_len(version, -1, ".") != NULL) return g_strdup(version); /* don't touch my version! */ if (fmt == FWUPD_VERSION_FORMAT_PLAIN || fmt == FWUPD_VERSION_FORMAT_UNKNOWN) return g_strdup(version); /* parse as integer */ if (!fu_strtoull(version, &ver_uint32, 1, G_MAXUINT32, &error_local)) { g_warning("invalid release version %s: %s", version, error_local->message); return g_strdup(version); } /* convert to dotted decimal */ return fu_version_from_uint32((guint32)ver_uint32, fmt); } static gboolean fu_release_load_test_result(FuRelease *self, XbNode *n, GError **error) { const gchar *tmp; g_autoptr(FwupdReport) report = fwupd_report_new(); g_autoptr(GPtrArray) custom = NULL; g_autoptr(XbNode) os = NULL; g_autoptr(XbNode) vendor_name = NULL; tmp = xb_node_get_attr(n, "date"); if (tmp != NULL) { g_autoptr(GDateTime) dt = NULL; g_autofree gchar *iso8601 = g_strdup_printf("%sT00:00:00Z", tmp); dt = g_date_time_new_from_iso8601(iso8601, NULL); if (dt != NULL) fwupd_report_set_created(report, g_date_time_to_unix(dt)); } tmp = xb_node_query_text(n, "device", NULL); if (tmp != NULL) fwupd_report_set_device_name(report, tmp); tmp = xb_node_query_text(n, "previous_version", NULL); if (tmp != NULL) { fwupd_report_set_version_old(report, tmp); if (fu_version_compare(fu_release_get_version(self), tmp, FWUPD_VERSION_FORMAT_UNKNOWN) > 0) { fwupd_report_add_flag(report, FWUPD_REPORT_FLAG_IS_UPGRADE); } } vendor_name = xb_node_query_first(n, "vendor_name", NULL); if (vendor_name != NULL) { guint64 vendor_id = xb_node_get_attr_as_uint(vendor_name, "id"); fwupd_report_set_vendor(report, xb_node_get_text(vendor_name)); if (vendor_id != G_MAXUINT64) fwupd_report_set_vendor_id(report, vendor_id); } os = xb_node_query_first(n, "os", NULL); if (os != NULL) { tmp = xb_node_get_attr(os, "version"); if (tmp != NULL) fwupd_report_set_distro_version(report, tmp); tmp = xb_node_get_attr(os, "variant"); if (tmp != NULL) fwupd_report_set_distro_variant(report, tmp); fwupd_report_set_distro_id(report, xb_node_get_text(os)); } if (fu_release_get_remote_id(self) != NULL) fwupd_report_set_remote_id(report, fu_release_get_remote_id(self)); custom = xb_node_query(n, "custom/value", 0, NULL); if (custom != NULL) { for (guint i = 0; i < custom->len; i++) { XbNode *c = g_ptr_array_index(custom, i); if (g_strcmp0(xb_node_get_attr(c, "key"), "FromOEM") == 0) { fwupd_report_add_flag(report, FWUPD_REPORT_FLAG_FROM_OEM); continue; } if (xb_node_get_attr(c, "key") == NULL || xb_node_get_text(c) == NULL) { g_debug("ignoring metadata: %s=%s", xb_node_get_attr(c, "key"), xb_node_get_text(c)); continue; } fwupd_report_add_metadata_item(report, xb_node_get_attr(c, "key"), xb_node_get_text(c)); } } /* success */ fwupd_release_add_report(FWUPD_RELEASE(self), report); return TRUE; } static gboolean fu_release_load_artifact(FuRelease *self, XbNode *artifact, GError **error) { const gchar *filename; guint64 size; g_autoptr(GPtrArray) locations = NULL; g_autoptr(GPtrArray) checksums = NULL; g_autoptr(GPtrArray) test_result = NULL; /* filename */ filename = xb_node_query_text(artifact, "filename", NULL); if (filename != NULL && !g_str_has_suffix(filename, ".cab")) { /* some firmware archives was signed with where the * checksums were the *content* checksums, not the *container* checksum */ g_debug("ignoring non-binary artifact entry: %s", filename); return TRUE; } if (filename != NULL) fwupd_release_set_filename(FWUPD_RELEASE(self), filename); /* location */ locations = xb_node_query(artifact, "location", 0, NULL); if (locations != NULL) { for (guint i = 0; i < locations->len; i++) { XbNode *n = g_ptr_array_index(locations, i); g_autofree gchar *scheme = NULL; /* check the scheme is allowed */ scheme = fu_release_uri_get_scheme(xb_node_get_text(n)); if (scheme != NULL) { guint prio = fu_engine_config_get_uri_scheme_prio(self->config, scheme); if (prio == G_MAXUINT) continue; } /* build the complete URI */ if (self->remote != NULL) { g_autofree gchar *uri = NULL; uri = fwupd_remote_build_firmware_uri(self->remote, xb_node_get_text(n), NULL); if (uri != NULL) { fwupd_release_add_location(FWUPD_RELEASE(self), uri); continue; } } fwupd_release_add_location(FWUPD_RELEASE(self), xb_node_get_text(n)); } } /* checksum */ checksums = xb_node_query(artifact, "checksum", 0, NULL); if (checksums != NULL) { for (guint i = 0; i < checksums->len; i++) { XbNode *n = g_ptr_array_index(checksums, i); fwupd_release_add_checksum(FWUPD_RELEASE(self), xb_node_get_text(n)); } } /* test results */ test_result = xb_node_query(artifact, "testing/test_result", 0, NULL); if (test_result != NULL) { for (guint i = 0; i < test_result->len; i++) { XbNode *n = g_ptr_array_index(test_result, i); if (!fu_release_load_test_result(self, n, error)) return FALSE; } } /* size */ size = xb_node_query_text_as_uint(artifact, "size[@type='installed']", NULL); if (size != G_MAXUINT64) fwupd_release_set_size(FWUPD_RELEASE(self), size); /* success */ return TRUE; } static gint fu_release_scheme_compare_cb(gconstpointer a, gconstpointer b, gpointer user_data) { FuRelease *self = FU_RELEASE(user_data); const gchar *location1 = *((const gchar **)a); const gchar *location2 = *((const gchar **)b); g_autofree gchar *scheme1 = fu_release_uri_get_scheme(location1); g_autofree gchar *scheme2 = fu_release_uri_get_scheme(location2); guint prio1 = fu_engine_config_get_uri_scheme_prio(self->config, scheme1); guint prio2 = fu_engine_config_get_uri_scheme_prio(self->config, scheme2); if (prio1 < prio2) return -1; if (prio1 > prio2) return 1; return 0; } static gboolean fu_release_check_requirements_version_check(FuRelease *self, GError **error) { if (self->hard_reqs != NULL) { for (guint i = 0; i < self->hard_reqs->len; i++) { XbNode *req = g_ptr_array_index(self->hard_reqs, i); if (g_strcmp0(xb_node_get_element(req), "firmware") == 0 && xb_node_get_text(req) == NULL) { return TRUE; } } } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no firmware requirement"); return FALSE; } static gchar * fu_release_verfmts_to_string(GPtrArray *verfmts) { GString *str = g_string_new(NULL); for (guint i = 0; i < verfmts->len; i++) { XbNode *verfmt = g_ptr_array_index(verfmts, i); const gchar *tmp = xb_node_get_text(verfmt); g_string_append_printf(str, "%s;", tmp); } if (str->len > 0) g_string_truncate(str, str->len - 1); return g_string_free(str, FALSE); } static gboolean fu_release_check_verfmt(FuRelease *self, GPtrArray *verfmts, FwupdInstallFlags flags, GError **error) { FwupdVersionFormat fmt_dev = fu_device_get_version_format(self->device); g_autofree gchar *verfmts_str = NULL; /* no device format */ if (fmt_dev == FWUPD_VERSION_FORMAT_UNKNOWN && (flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { verfmts_str = fu_release_verfmts_to_string(verfmts); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "release version format '%s' but no device version format", verfmts_str); return FALSE; } /* compare all version formats */ for (guint i = 0; i < verfmts->len; i++) { XbNode *verfmt = g_ptr_array_index(verfmts, i); const gchar *tmp = xb_node_get_text(verfmt); FwupdVersionFormat fmt_rel = fwupd_version_format_from_string(tmp); if (fmt_dev == fmt_rel) return TRUE; } verfmts_str = fu_release_verfmts_to_string(verfmts); if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Firmware version formats were different, " "device was '%s' and release is '%s'", fwupd_version_format_to_string(fmt_dev), verfmts_str); return FALSE; } g_warning("ignoring version format difference %s:%s", fwupd_version_format_to_string(fmt_dev), verfmts_str); return TRUE; } /* these can all be done without the daemon */ static gboolean fu_release_check_requirements(FuRelease *self, XbNode *component, XbNode *rel, FwupdInstallFlags install_flags, GError **error) { const gchar *branch_new; const gchar *branch_old; const gchar *protocol; const gchar *version; const gchar *version_lowest; gboolean matches_guid = FALSE; gint vercmp; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) provides = NULL; g_autoptr(GPtrArray) verfmts = NULL; /* does this component provide a GUID the device has */ provides = xb_node_query(component, "provides/firmware[@type='flashed']", 0, &error_local); if (provides == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No supported devices found: %s", error_local->message); return FALSE; } for (guint i = 0; i < provides->len; i++) { XbNode *provide = g_ptr_array_index(provides, i); if (fu_device_has_guid(self->device, xb_node_get_text(provide))) { matches_guid = TRUE; break; } } if (!matches_guid) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No supported devices found"); return FALSE; } /* device requires a version check */ if (fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED)) { if (!fu_release_check_requirements_version_check(self, error)) { g_prefix_error(error, "device requires firmware with a version check: "); return FALSE; } } /* does the protocol match */ protocol = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateProtocol']", NULL); if (fu_device_get_protocols(self->device)->len != 0 && protocol != NULL && !fu_device_has_protocol(self->device, protocol) && (install_flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_autofree gchar *str = NULL; str = fu_strjoin("|", fu_device_get_protocols(self->device)); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device %s does not support %s, only %s", fu_device_get_name(self->device), protocol, str); return FALSE; } /* check the device is not locked */ if (fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_LOCKED)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device %s [%s] is locked", fu_device_get_name(self->device), fu_device_get_id(self->device)); return FALSE; } /* check the branch is not switching */ branch_new = xb_node_query_text(component, "branch", NULL); branch_old = fu_device_get_branch(self->device); if ((install_flags & FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH) == 0 && g_strcmp0(branch_old, branch_new) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device %s [%s] would switch firmware branch from %s to %s", fu_device_get_name(self->device), fu_device_get_id(self->device), branch_old != NULL ? branch_old : "default", branch_new != NULL ? branch_new : "default"); return FALSE; } /* no update abilities */ if (!fu_engine_request_has_feature_flag(self->request, FWUPD_FEATURE_FLAG_SHOW_PROBLEMS) && !fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_UPDATABLE)) { g_autoptr(GString) str = g_string_new(NULL); g_string_append_printf(str, "Device %s [%s] does not currently allow updates", fu_device_get_name(self->device), fu_device_get_id(self->device)); if (fu_device_get_update_error(self->device) != NULL) { g_string_append_printf(str, ": %s", fu_device_get_update_error(self->device)); } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, str->str); return FALSE; } /* called with online update, test if device is supposed to allow this */ if ((install_flags & FWUPD_INSTALL_FLAG_OFFLINE) == 0 && (install_flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_ONLY_OFFLINE)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device %s [%s] only allows offline updates", fu_device_get_name(self->device), fu_device_get_id(self->device)); return FALSE; } /* get device */ version = fu_device_get_version(self->device); if (version == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Device %s [%s] has no firmware version", fu_device_get_name(self->device), fu_device_get_id(self->device)); return FALSE; } /* check the version formats match if set in the release */ if ((install_flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && (install_flags & FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH) == 0) { verfmts = xb_node_query(component, "custom/value[@key='LVFS::VersionFormat']", 0, NULL); if (verfmts != NULL) { if (!fu_release_check_verfmt(self, verfmts, install_flags, error)) return FALSE; } } /* compare to the lowest supported version, if it exists */ version_lowest = fu_device_get_version_lowest(self->device); if (version_lowest != NULL && fu_version_compare(version_lowest, fu_release_get_version(self), fu_device_get_version_format(self->device)) > 0 && (install_flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Specified firmware is older than the minimum " "required version '%s < %s'", fu_release_get_version(self), version_lowest); return FALSE; } /* is this a downgrade or re-install */ vercmp = fu_version_compare(version, fu_release_get_version(self), fu_device_get_version_format(self->device)); if (fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE) && vercmp > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device only supports version upgrades"); return FALSE; } if (vercmp == 0 && (install_flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_VERSION_SAME, "Specified firmware is already installed '%s'", fu_release_get_version(self)); return FALSE; } if (vercmp > 0) fu_release_add_flag(self, FWUPD_RELEASE_FLAG_IS_DOWNGRADE); if (fu_release_has_flag(self, FWUPD_RELEASE_FLAG_IS_DOWNGRADE) && (install_flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) == 0 && (install_flags & FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_VERSION_NEWER, "Specified firmware is older than installed '%s < %s'", fu_release_get_version(self), version); return FALSE; } /* success */ return TRUE; } void fu_release_set_priority(FuRelease *self, guint64 priority) { g_return_if_fail(FU_IS_RELEASE(self)); self->priority = priority; } guint64 fu_release_get_priority(FuRelease *self) { g_return_val_if_fail(FU_IS_RELEASE(self), 0); return self->priority; } static void fu_release_ensure_device_by_checksum(FuRelease *self, XbNode *component, XbNode *rel) { g_autoptr(GPtrArray) device_checksums = NULL; /* sanity check */ if (fu_device_get_checksums(self->device)->len == 0) return; device_checksums = xb_node_query(rel, "checksum[@target='device']", 0, NULL); if (device_checksums == NULL) return; for (guint i = 0; i < device_checksums->len; i++) { XbNode *device_checksum = g_ptr_array_index(device_checksums, i); if (!fu_device_has_checksum(self->device, xb_node_get_text(device_checksum))) continue; fu_device_ensure_from_component(self->device, component); if (fu_device_has_internal_flag(self->device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VERSION)) { const gchar *rel_version = xb_node_get_attr(rel, "version"); if (rel_version == NULL) continue; fu_device_set_version(self->device, rel_version); fu_device_remove_internal_flag(self->device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VERSION); } break; } } /** * fu_release_load: * @self: a #FuRelease * @component: (not nullable): a #XbNode * @rel_optional: (nullable): a #XbNode * @install_flags: a #FwupdInstallFlags, e.g. %FWUPD_INSTALL_FLAG_FORCE * @error: (nullable): optional return location for an error * * Loads then checks any requirements of this release. This will typically involve checking * that the device can accept the component (the GUIDs match) and that the device can be * upgraded with this firmware version. * * Returns: %TRUE if the release was loaded and the requirements passed **/ gboolean fu_release_load(FuRelease *self, XbNode *component, XbNode *rel_optional, FwupdInstallFlags install_flags, GError **error) { const gchar *tmp; guint64 tmp64; GBytes *blob_fw_tmp; g_autofree gchar *name_xpath = NULL; g_autofree gchar *namevs_xpath = NULL; g_autofree gchar *summary_xpath = NULL; g_autofree gchar *description_xpath = NULL; g_autoptr(GPtrArray) cats = NULL; g_autoptr(GPtrArray) tags = NULL; g_autoptr(GPtrArray) issues = NULL; g_autoptr(XbNode) artifact = NULL; g_autoptr(XbNode) description = NULL; g_autoptr(XbNode) rel = NULL; g_autoptr(GError) error_soft = NULL; g_autoptr(GError) error_hard = NULL; g_return_val_if_fail(FU_IS_RELEASE(self), FALSE); g_return_val_if_fail(XB_IS_NODE(component), FALSE); g_return_val_if_fail(rel_optional == NULL || XB_IS_NODE(rel_optional), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); g_return_val_if_fail(fwupd_release_get_appstream_id(FWUPD_RELEASE(self)) == NULL, FALSE); /* set from the component */ tmp = xb_node_query_text(component, "id", NULL); if (tmp != NULL) fwupd_release_set_appstream_id(FWUPD_RELEASE(self), tmp); tmp = xb_node_query_text(component, "url[@type='homepage']", NULL); if (tmp != NULL) fwupd_release_set_homepage(FWUPD_RELEASE(self), tmp); tmp = xb_node_query_text(component, "project_license", NULL); if (tmp != NULL) fwupd_release_set_license(FWUPD_RELEASE(self), tmp); name_xpath = fu_release_get_localized_xpath(self, "name"); tmp = xb_node_query_text(component, name_xpath, NULL); if (tmp != NULL) fwupd_release_set_name(FWUPD_RELEASE(self), tmp); summary_xpath = fu_release_get_localized_xpath(self, "summary"); tmp = xb_node_query_text(component, summary_xpath, NULL); if (tmp != NULL) fwupd_release_set_summary(FWUPD_RELEASE(self), tmp); namevs_xpath = fu_release_get_localized_xpath(self, "name_variant_suffix"); tmp = xb_node_query_text(component, namevs_xpath, NULL); if (tmp != NULL) fwupd_release_set_name_variant_suffix(FWUPD_RELEASE(self), tmp); tmp = xb_node_query_text(component, "branch", NULL); if (tmp != NULL) fwupd_release_set_branch(FWUPD_RELEASE(self), tmp); tmp = xb_node_query_text(component, "developer_name", NULL); if (tmp != NULL) fwupd_release_set_vendor(FWUPD_RELEASE(self), tmp); /* use default release */ if (rel_optional == NULL) { g_autoptr(GError) error_local = NULL; g_autoptr(XbQuery) query = NULL; query = xb_query_new_full(xb_node_get_silo(component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, error); if (query == NULL) return FALSE; rel = xb_node_query_first_full(component, query, &error_local); if (rel == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "failed to get default release: %s", error_local->message); return FALSE; } } else { rel = g_object_ref(rel_optional); } /* find the remote */ tmp = xb_node_query_text(component, "../custom/value[@key='fwupd::RemoteId']", NULL); if (tmp != NULL) fwupd_release_set_remote_id(FWUPD_RELEASE(self), tmp); tmp = xb_node_query_text(component, "../custom/value[@key='LVFS::Distributor']", NULL); if (tmp != NULL && g_str_has_prefix(tmp, "community")) fwupd_release_add_flag(FWUPD_RELEASE(self), FWUPD_RELEASE_FLAG_IS_COMMUNITY); /* use the metadata to set the device attributes */ if (!fu_release_ensure_trust_flags(self, rel, error)) return FALSE; if (self->device != NULL && fu_release_has_flag(self, FWUPD_RELEASE_FLAG_TRUSTED_METADATA)) { if (fu_device_has_internal_flag(self->device, FU_DEVICE_INTERNAL_FLAG_MD_ONLY_CHECKSUM)) { fu_release_ensure_device_by_checksum(self, component, rel); } else { fu_device_ensure_from_component(self->device, component); } } /* per-release priority wins, but fallback to per-component priority */ tmp64 = xb_node_get_attr_as_uint(rel, "priority"); if (tmp64 == G_MAXUINT64) tmp64 = xb_node_get_attr_as_uint(component, "priority"); if (tmp64 != G_MAXUINT64) fu_release_set_priority(self, tmp64); /* the version is fixed up with the device format */ tmp = xb_node_get_attr(rel, "version"); if (tmp == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "version unset"); return FALSE; } if (self->device != NULL) { g_autofree gchar *version_rel = NULL; version_rel = fu_release_get_release_version(self, tmp, error); if (version_rel == NULL) return FALSE; fwupd_release_set_version(FWUPD_RELEASE(self), version_rel); } else { fwupd_release_set_version(FWUPD_RELEASE(self), tmp); } /* optional release ID -- currently a integer but maybe namespaced in the future */ fwupd_release_set_id(FWUPD_RELEASE(self), xb_node_get_attr(rel, "id")); /* this is the more modern way to do this */ artifact = xb_node_query_first(rel, "artifacts/artifact[@type='binary']", NULL); if (artifact != NULL) { if (!fu_release_load_artifact(self, artifact, error)) return FALSE; } description_xpath = fu_release_get_localized_xpath(self, "description"); description = xb_node_query_first(rel, description_xpath, NULL); if (description != NULL) { g_autofree gchar *xml = NULL; g_autoptr(GString) str = NULL; xml = xb_node_export(description, XB_NODE_EXPORT_FLAG_ONLY_CHILDREN, NULL); str = g_string_new(xml); if (self->device != NULL && self->request != NULL && fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_AFFECTS_FDE) && !fu_engine_request_has_feature_flag(self->request, FWUPD_FEATURE_FLAG_FDE_WARNING)) { g_string_prepend( str, "

    Some of the platform secrets may be invalidated when " "updating this firmware. Please ensure you have the volume " "recovery key before continuing.

    "); } if (fwupd_release_has_flag(FWUPD_RELEASE(self), FWUPD_RELEASE_FLAG_IS_COMMUNITY) && self->request != NULL && !fu_engine_request_has_feature_flag(self->request, FWUPD_FEATURE_FLAG_COMMUNITY_TEXT)) { g_string_prepend( str, "

    This firmware is provided by LVFS community " "members and is not provided (or supported) by the original " "hardware vendor. " "Installing this update may also void any device warranty.

    "); } if (str->len > 0) fwupd_release_set_description(FWUPD_RELEASE(self), str->str); } if (fwupd_release_get_locations(FWUPD_RELEASE(self))->len == 0) { tmp = xb_node_query_text(rel, "location", NULL); if (tmp != NULL) { g_autofree gchar *uri = NULL; if (self->remote != NULL) uri = fwupd_remote_build_firmware_uri(self->remote, tmp, NULL); if (uri == NULL) uri = g_strdup(tmp); fwupd_release_add_location(FWUPD_RELEASE(self), uri); } } if (fwupd_release_get_locations(FWUPD_RELEASE(self))->len == 0 && self->remote != NULL && fwupd_remote_get_kind(self->remote) == FWUPD_REMOTE_KIND_DIRECTORY) { tmp = xb_node_query_text(component, "../custom/value[@key='fwupd::FilenameCache']", NULL); if (tmp != NULL) { g_autofree gchar *uri = g_strdup_printf("file://%s", tmp); fwupd_release_add_location(FWUPD_RELEASE(self), uri); } } if (fwupd_release_get_filename(FWUPD_RELEASE(self)) == NULL) { tmp = xb_node_query_text(rel, "checksum[@target='content']", NULL); if (tmp != NULL) fwupd_release_set_filename(FWUPD_RELEASE(self), tmp); } tmp = xb_node_query_text(rel, "url[@type='details']", NULL); if (tmp != NULL) fwupd_release_set_details_url(FWUPD_RELEASE(self), tmp); tmp = xb_node_query_text(rel, "url[@type='source']", NULL); if (tmp != NULL) fwupd_release_set_source_url(FWUPD_RELEASE(self), tmp); if (fwupd_release_get_checksums(FWUPD_RELEASE(self))->len == 0) { g_autoptr(GPtrArray) checksums = NULL; checksums = xb_node_query(rel, "checksum[@target='container']", 0, NULL); if (checksums != NULL) { for (guint i = 0; i < checksums->len; i++) { XbNode *n = g_ptr_array_index(checksums, i); if (xb_node_get_text(n) == NULL) continue; fwupd_release_add_checksum(FWUPD_RELEASE(self), xb_node_get_text(n)); } } } if (fwupd_release_get_size(FWUPD_RELEASE(self)) == 0) { tmp64 = xb_node_query_text_as_uint(rel, "size[@type='installed']", NULL); if (tmp64 != G_MAXUINT64) fwupd_release_set_size(FWUPD_RELEASE(self), tmp64); } if (fwupd_release_get_size(FWUPD_RELEASE(self)) == 0) { GBytes *sz = xb_node_get_data(rel, "fwupd::ReleaseSize"); if (sz != NULL) { const guint64 *sizeptr = g_bytes_get_data(sz, NULL); fwupd_release_set_size(FWUPD_RELEASE(self), *sizeptr); } } tmp = xb_node_get_attr(rel, "urgency"); if (tmp != NULL) fwupd_release_set_urgency(FWUPD_RELEASE(self), fwupd_release_urgency_from_string(tmp)); tmp64 = xb_node_get_attr_as_uint(rel, "install_duration"); if (tmp64 != G_MAXUINT64) fwupd_release_set_install_duration(FWUPD_RELEASE(self), tmp64); tmp64 = xb_node_get_attr_as_uint(rel, "timestamp"); if (tmp64 != G_MAXUINT64) fwupd_release_set_created(FWUPD_RELEASE(self), tmp64); cats = xb_node_query(component, "categories/category", 0, NULL); if (cats != NULL) { for (guint i = 0; i < cats->len; i++) { XbNode *n = g_ptr_array_index(cats, i); fwupd_release_add_category(FWUPD_RELEASE(self), xb_node_get_text(n)); } } tags = xb_node_query(component, "tags/tag[@namespace=$'lvfs']", 0, NULL); if (tags != NULL) { for (guint i = 0; i < tags->len; i++) { XbNode *tag = g_ptr_array_index(tags, i); fwupd_release_add_tag(FWUPD_RELEASE(self), xb_node_get_text(tag)); } } issues = xb_node_query(rel, "issues/issue", 0, NULL); if (issues != NULL) { for (guint i = 0; i < issues->len; i++) { XbNode *n = g_ptr_array_index(issues, i); fwupd_release_add_issue(FWUPD_RELEASE(self), xb_node_get_text(n)); } } tmp = xb_node_query_text(component, "screenshots/screenshot/caption", NULL); if (tmp != NULL) fwupd_release_set_detach_caption(FWUPD_RELEASE(self), tmp); tmp = xb_node_query_text(component, "screenshots/screenshot/image", NULL); if (tmp != NULL) { if (self->remote != NULL) { g_autofree gchar *img = NULL; img = fwupd_remote_build_firmware_uri(self->remote, tmp, error); if (img == NULL) return FALSE; fwupd_release_set_detach_image(FWUPD_RELEASE(self), img); } else { fwupd_release_set_detach_image(FWUPD_RELEASE(self), tmp); } } tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateProtocol']", NULL); if (tmp != NULL) fwupd_release_set_protocol(FWUPD_RELEASE(self), tmp); tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateMessage']", NULL); if (tmp != NULL) fwupd_release_set_update_message(FWUPD_RELEASE(self), tmp); tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateImage']", NULL); if (tmp != NULL) { if (self->remote != NULL) { g_autofree gchar *img = NULL; img = fwupd_remote_build_firmware_uri(self->remote, tmp, error); if (img == NULL) return FALSE; fwupd_release_set_update_image(FWUPD_RELEASE(self), img); } else { fwupd_release_set_update_image(FWUPD_RELEASE(self), tmp); } } tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateRequestId']", NULL); if (tmp != NULL) fu_release_set_update_request_id(self, tmp); /* hard and soft requirements */ self->hard_reqs = xb_node_query(component, "requires/*", 0, &error_hard); if (self->hard_reqs == NULL) { if (!g_error_matches(error_hard, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && !g_error_matches(error_hard, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { g_propagate_error(error, g_steal_pointer(&error_hard)); return FALSE; } } self->soft_reqs = xb_node_query(component, "suggests/*|recommends/*", 0, &error_soft); if (self->soft_reqs == NULL) { if (!g_error_matches(error_soft, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && !g_error_matches(error_soft, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) { g_propagate_error(error, g_steal_pointer(&error_soft)); return FALSE; } } /* get per-release firmware blob */ blob_fw_tmp = xb_node_get_data(rel, "fwupd::FirmwareBlob"); if (blob_fw_tmp != NULL) self->blob_fw = g_bytes_ref(blob_fw_tmp); /* to build the firmware */ tmp = g_object_get_data(G_OBJECT(component), "fwupd::BuilderScript"); if (tmp != NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "fwupd::BuilderScript is no longer supported"); return FALSE; } /* sort the locations by scheme */ if (self->config != NULL) { g_ptr_array_sort_with_data(fwupd_release_get_locations(FWUPD_RELEASE(self)), fu_release_scheme_compare_cb, self); } /* check requirements for device */ if (self->device != NULL && self->request != NULL && !fu_engine_request_has_flag(self->request, FU_ENGINE_REQUEST_FLAG_NO_REQUIREMENTS)) { if (!fu_release_check_requirements(self, component, rel, install_flags, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_release_ensure_trust_flags(FuRelease *self, XbNode *rel, GError **error) { GBytes *blob; g_return_val_if_fail(FU_IS_RELEASE(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* in the self tests */ if (g_getenv("FWUPD_SELF_TEST") != NULL) { fu_release_add_flag(self, FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD); fu_release_add_flag(self, FWUPD_RELEASE_FLAG_TRUSTED_METADATA); return TRUE; } /* populated from an actual cab archive */ blob = g_object_get_data(G_OBJECT(rel), "fwupd::ReleaseFlags"); if (blob != NULL) { FwupdReleaseFlags flags = FWUPD_RELEASE_FLAG_NONE; if (!fu_memcpy_safe((guint8 *)&flags, sizeof(flags), 0x0, /* dst */ g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), 0x0, /* src */ sizeof(flags), error)) return FALSE; if (flags & FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD) fu_release_add_flag(self, FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD); if (flags & FWUPD_RELEASE_FLAG_TRUSTED_METADATA) fu_release_add_flag(self, FWUPD_RELEASE_FLAG_TRUSTED_METADATA); } /* do not require signatures for anything installed to the immutable datadir */ if (fu_release_get_flags(self) == FWUPD_RELEASE_FLAG_NONE && self->remote != NULL) { if (fwupd_remote_get_keyring_kind(self->remote) == FWUPD_KEYRING_KIND_NONE && (fwupd_remote_get_kind(self->remote) == FWUPD_REMOTE_KIND_LOCAL || fwupd_remote_get_kind(self->remote) == FWUPD_REMOTE_KIND_DIRECTORY)) { g_debug("remote %s has kind=%s and Keyring=none and so marking as trusted", fwupd_remote_get_id(self->remote), fwupd_remote_kind_to_string(fwupd_remote_get_kind(self->remote))); fu_release_add_flag(self, FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD); fu_release_add_flag(self, FWUPD_RELEASE_FLAG_TRUSTED_METADATA); } else if (fwupd_remote_get_keyring_kind(self->remote) != FWUPD_KEYRING_KIND_NONE) { g_debug("remote %s has kind=%s and so marking as trusted", fwupd_remote_get_id(self->remote), fwupd_remote_kind_to_string(fwupd_remote_get_kind(self->remote))); fu_release_add_flag(self, FWUPD_RELEASE_FLAG_TRUSTED_METADATA); } } /* success */ return TRUE; } /** * fu_release_get_action_id: * @self: a #FuEngine * * Gets the PolicyKit action ID to use for the install operation. * * Returns: string, e.g. `org.freedesktop.fwupd.update-internal-trusted` **/ const gchar * fu_release_get_action_id(FuRelease *self) { /* relax authentication checks for removable devices */ if (!fu_device_has_flag(self->device, FWUPD_DEVICE_FLAG_INTERNAL)) { if (fu_release_has_flag(self, FWUPD_RELEASE_FLAG_IS_DOWNGRADE)) { if (fu_release_has_flag(self, FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD)) return "org.freedesktop.fwupd.downgrade-hotplug-trusted"; return "org.freedesktop.fwupd.downgrade-hotplug"; } if (fu_release_has_flag(self, FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD)) return "org.freedesktop.fwupd.update-hotplug-trusted"; return "org.freedesktop.fwupd.update-hotplug"; } /* internal device */ if (fu_release_has_flag(self, FWUPD_RELEASE_FLAG_IS_DOWNGRADE)) { if (fu_release_has_flag(self, FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD)) return "org.freedesktop.fwupd.downgrade-internal-trusted"; return "org.freedesktop.fwupd.downgrade-internal"; } if (fu_release_has_flag(self, FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD)) return "org.freedesktop.fwupd.update-internal-trusted"; return "org.freedesktop.fwupd.update-internal"; } /** * fu_release_compare: * @release1: first task to compare. * @release2: second task to compare. * * Compares two releases. * * Returns: 1, 0 or -1 if @release1 is greater, equal, or less than @release2, respectively. **/ gint fu_release_compare(FuRelease *release1, FuRelease *release2) { FuDevice *device1 = fu_release_get_device(release1); FuDevice *device2 = fu_release_get_device(release2); /* device order, lower is better */ if (device1 != NULL && device2 != NULL && device1 != device2) { if (fu_device_get_order(device1) < fu_device_get_order(device2)) return -1; if (fu_device_get_order(device1) > fu_device_get_order(device2)) return 1; } /* release priority, higher is better */ if (release1->priority > release2->priority) return -1; if (release1->priority < release2->priority) return 1; /* remote priority, higher is better */ if (release1->remote != NULL && release2->remote != NULL) { if (fwupd_remote_get_priority(release1->remote) > fwupd_remote_get_priority(release2->remote)) return -1; if (fwupd_remote_get_priority(release1->remote) < fwupd_remote_get_priority(release2->remote)) return 1; } /* FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES has to be from oldest to newest */ return fu_version_compare(fu_release_get_version(release1), fu_release_get_version(release2), fu_device_get_version_format(device1)); } static void fu_release_init(FuRelease *self) { fu_release_set_flags(self, FWUPD_RELEASE_FLAG_NONE); } static void fu_release_finalize(GObject *obj) { FuRelease *self = FU_RELEASE(obj); g_free(self->update_request_id); g_free(self->device_version_old); if (self->request != NULL) g_object_unref(self->request); if (self->device != NULL) g_object_unref(self->device); if (self->remote != NULL) g_object_unref(self->remote); if (self->config != NULL) g_object_unref(self->config); if (self->blob_fw != NULL) g_bytes_unref(self->blob_fw); if (self->soft_reqs != NULL) g_ptr_array_unref(self->soft_reqs); if (self->hard_reqs != NULL) g_ptr_array_unref(self->hard_reqs); G_OBJECT_CLASS(fu_release_parent_class)->finalize(obj); } static void fu_release_class_init(FuReleaseClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_release_finalize; } FuRelease * fu_release_new(void) { FuRelease *self; self = g_object_new(FU_TYPE_RELEASE, NULL); return FU_RELEASE(self); } fwupd-1.9.16/src/fu-release.h000066400000000000000000000050661460375044200157410ustar00rootroot00000000000000/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fu-engine-config.h" #include "fu-engine-request.h" #define FU_TYPE_RELEASE (fu_release_get_type()) G_DECLARE_FINAL_TYPE(FuRelease, fu_release, FU, RELEASE, FwupdRelease) FuRelease * fu_release_new(void); #define fu_release_get_appstream_id(r) fwupd_release_get_appstream_id(FWUPD_RELEASE(r)) #define fu_release_get_filename(r) fwupd_release_get_filename(FWUPD_RELEASE(r)) #define fu_release_get_version(r) fwupd_release_get_version(FWUPD_RELEASE(r)) #define fu_release_get_branch(r) fwupd_release_get_branch(FWUPD_RELEASE(r)) #define fu_release_get_remote_id(r) fwupd_release_get_remote_id(FWUPD_RELEASE(r)) #define fu_release_get_checksums(r) fwupd_release_get_checksums(FWUPD_RELEASE(r)) #define fu_release_get_reports(r) fwupd_release_get_reports(FWUPD_RELEASE(r)) #define fu_release_get_flags(r) fwupd_release_get_flags(FWUPD_RELEASE(r)) #define fu_release_add_flag(r, v) fwupd_release_add_flag(FWUPD_RELEASE(r), v) #define fu_release_has_flag(r, v) fwupd_release_has_flag(FWUPD_RELEASE(r), v) #define fu_release_add_tag(r, v) fwupd_release_add_tag(FWUPD_RELEASE(r), v) #define fu_release_add_metadata(r, v) fwupd_release_add_metadata(FWUPD_RELEASE(r), v) #define fu_release_set_branch(r, v) fwupd_release_set_branch(FWUPD_RELEASE(r), v) #define fu_release_set_flags(r, v) fwupd_release_set_flags(FWUPD_RELEASE(r), v) gchar * fu_release_to_string(FuRelease *self); FuDevice * fu_release_get_device(FuRelease *self); GBytes * fu_release_get_fw_blob(FuRelease *self); FuEngineRequest * fu_release_get_request(FuRelease *self); GPtrArray * fu_release_get_soft_reqs(FuRelease *self); GPtrArray * fu_release_get_hard_reqs(FuRelease *self); const gchar * fu_release_get_update_request_id(FuRelease *self); const gchar * fu_release_get_device_version_old(FuRelease *self); void fu_release_set_request(FuRelease *self, FuEngineRequest *request); void fu_release_set_device(FuRelease *self, FuDevice *device); void fu_release_set_remote(FuRelease *self, FwupdRemote *remote); void fu_release_set_config(FuRelease *self, FuEngineConfig *config); gboolean fu_release_load(FuRelease *self, XbNode *component, XbNode *rel, FwupdInstallFlags flags, GError **error); const gchar * fu_release_get_action_id(FuRelease *self); gint fu_release_compare(FuRelease *release1, FuRelease *release2); void fu_release_set_priority(FuRelease *self, guint64 priority); guint64 fu_release_get_priority(FuRelease *self); fwupd-1.9.16/src/fu-remote-list.c000066400000000000000000000562551460375044200165660ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuRemoteList" #include "config.h" #include #include #include #ifdef HAVE_INOTIFY_H #include #include #endif #include "fwupd-remote-private.h" #include "fu-remote-list.h" enum { SIGNAL_CHANGED, SIGNAL_ADDED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; static gboolean fu_remote_list_reload(FuRemoteList *self, GError **error); static void fu_remote_list_finalize(GObject *obj); struct _FuRemoteList { GObject parent_instance; GPtrArray *array; /* (element-type FwupdRemote) */ GPtrArray *monitors; /* (element-type GFileMonitor) */ gboolean testing_remote; gboolean fix_metadata_uri; XbSilo *silo; gchar *lvfs_metadata_format; }; G_DEFINE_TYPE(FuRemoteList, fu_remote_list, G_TYPE_OBJECT) static void fu_remote_list_emit_changed(FuRemoteList *self) { g_debug("::remote_list changed"); g_signal_emit(self, signals[SIGNAL_CHANGED], 0); } static void fu_remote_list_emit_added(FuRemoteList *self, FwupdRemote *remote) { g_debug("::remote_list changed"); g_signal_emit(self, signals[SIGNAL_ADDED], 0, remote); } void fu_remote_list_set_lvfs_metadata_format(FuRemoteList *self, const gchar *lvfs_metadata_format) { g_return_if_fail(FU_IS_REMOTE_LIST(self)); g_return_if_fail(lvfs_metadata_format != NULL); if (g_strcmp0(lvfs_metadata_format, self->lvfs_metadata_format) == 0) return; g_free(self->lvfs_metadata_format); self->lvfs_metadata_format = g_strdup(lvfs_metadata_format); } static void fu_remote_list_monitor_changed_cb(GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { FuRemoteList *self = FU_REMOTE_LIST(user_data); g_autoptr(GError) error = NULL; g_autofree gchar *filename = g_file_get_path(file); g_info("%s changed, reloading all remotes", filename); if (!fu_remote_list_reload(self, &error)) g_warning("failed to rescan remotes: %s", error->message); fu_remote_list_emit_changed(self); } static guint64 _fwupd_remote_get_mtime(FwupdRemote *remote) { g_autoptr(GFile) file = NULL; g_autoptr(GFileInfo) info = NULL; file = g_file_new_for_path(fwupd_remote_get_filename_cache(remote)); if (!g_file_query_exists(file, NULL)) return G_MAXUINT64; info = g_file_query_info(file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (info == NULL) return G_MAXUINT64; return g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_TIME_MODIFIED); } /* GLib only returns the very unhelpful "Unable to find default local file monitor type" * when /proc/sys/fs/inotify/max_user_instances is set too low; detect this and set a proper * error prefix to aid debugging when the daemon fails to start */ static void fu_remote_list_fixup_inotify_error(GError **error) { #ifdef HAVE_INOTIFY_H int fd; int wd; const gchar *fn = "/proc/sys/fs/inotify/max_user_instances"; fd = inotify_init(); if (fd == -1) { g_prefix_error(error, "Could not initialize inotify, check %s: ", fn); return; } wd = inotify_add_watch(fd, "/", 0); if (wd < 0) { if (errno == ENOSPC) g_prefix_error(error, "No space for inotify, check %s: ", fn); } else { inotify_rm_watch(fd, wd); } close(fd); #endif } static gboolean fu_remote_list_add_inotify(FuRemoteList *self, const gchar *filename, GError **error) { GFileMonitor *monitor; g_autoptr(GFile) file = g_file_new_for_path(filename); /* set up a notify watch */ monitor = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, error); if (monitor == NULL) { fu_remote_list_fixup_inotify_error(error); return FALSE; } g_signal_connect(G_FILE_MONITOR(monitor), "changed", G_CALLBACK(fu_remote_list_monitor_changed_cb), self); g_ptr_array_add(self->monitors, monitor); return TRUE; } static GString * _fwupd_remote_get_agreement_default(FwupdRemote *self, GError **error) { GString *str = g_string_new(NULL); /* this is designed as a fallback; the actual warning should ideally * come from the LVFS instance that is serving the remote */ g_string_append_printf(str, "

    %s

    ", /* TRANSLATORS: show the user a warning */ _("Your distributor may not have verified any of " "the firmware updates for compatibility with your " "system or connected devices.")); g_string_append_printf(str, "

    %s

    ", /* TRANSLATORS: show the user a warning */ _("Enabling this remote is done at your own risk.")); return str; } static GString * _fwupd_remote_get_agreement_for_app(FwupdRemote *self, XbNode *component, GError **error) { g_autofree gchar *tmp = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(XbNode) n = NULL; /* manually find the first agreement section */ n = xb_node_query_first(component, "agreement/agreement_section/description/*", &error_local); if (n == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "No agreement description found: %s", error_local->message); return NULL; } tmp = xb_node_export(n, XB_NODE_EXPORT_FLAG_INCLUDE_SIBLINGS, error); if (tmp == NULL) return NULL; return g_string_new(tmp); } static gchar * _fwupd_remote_build_component_id(FwupdRemote *remote) { return g_strdup_printf("org.freedesktop.fwupd.remotes.%s", fwupd_remote_get_id(remote)); } static gchar * fu_remote_list_get_last_ext(const gchar *filename) { gchar *tmp; g_return_val_if_fail(filename != NULL, NULL); tmp = g_strrstr(filename, "."); if (tmp == NULL) return NULL; return g_strdup(tmp + 1); } static gboolean fu_remote_list_cleanup_lvfs_remote(FuRemoteList *self, FwupdRemote *remote, GError **error) { const gchar *fn_cache = fwupd_remote_get_filename_cache(remote); g_autofree gchar *dirname = NULL; g_autoptr(GPtrArray) files = NULL; /* sanity check */ if (fn_cache == NULL) return TRUE; if (self->lvfs_metadata_format == NULL) return TRUE; /* get all files */ dirname = g_path_get_dirname(fn_cache); files = fu_path_get_files(dirname, NULL); if (files == NULL) return TRUE; /* delete any obsolete ones */ for (guint i = 0; i < files->len; i++) { const gchar *fn = g_ptr_array_index(files, i); g_autofree gchar *ext = fu_remote_list_get_last_ext(fn); if (g_strcmp0(ext, "jcat") == 0) continue; if (g_strcmp0(ext, self->lvfs_metadata_format) != 0) { g_info("deleting obsolete %s", fn); if (g_unlink(fn) == -1) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed to delete obsolete %s", fn); return FALSE; } } } /* success */ return TRUE; } void fu_remote_list_add_remote(FuRemoteList *self, FwupdRemote *remote) { g_return_if_fail(FU_IS_REMOTE_LIST(self)); g_return_if_fail(FWUPD_IS_REMOTE(remote)); fu_remote_list_emit_added(self, remote); g_ptr_array_add(self->array, g_object_ref(remote)); } static gboolean fu_remote_list_is_remote_origin_lvfs(FwupdRemote *remote) { if (fwupd_remote_get_id(remote) != NULL && g_strstr_len(fwupd_remote_get_id(remote), -1, "lvfs") != NULL) return TRUE; if (fwupd_remote_get_metadata_uri(remote) != NULL && g_strstr_len(fwupd_remote_get_metadata_uri(remote), -1, "fwupd.org") != NULL) return TRUE; return FALSE; } static gboolean fu_remote_list_add_for_file(FuRemoteList *self, GHashTable *os_release, const gchar *filename, GError **error) { FwupdRemote *remote_tmp; g_autofree gchar *remotesdir = NULL; g_autoptr(FwupdRemote) remote = fwupd_remote_new(); /* set directory to store data */ remotesdir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_METADATA); fwupd_remote_set_remotes_dir(remote, remotesdir); /* load from keyfile */ g_info("loading remote from %s", filename); if (!fwupd_remote_load_from_filename(remote, filename, NULL, error)) { g_prefix_error(error, "failed to load %s: ", filename); return FALSE; } /* does it already exist */ remote_tmp = fu_remote_list_get_by_id(self, fwupd_remote_get_id(remote)); if (remote_tmp != NULL) { g_debug("remote %s already added from %s", fwupd_remote_get_id(remote), fwupd_remote_get_filename_source(remote_tmp)); return TRUE; } /* auto-fix before setup */ if (self->fix_metadata_uri && fu_remote_list_is_remote_origin_lvfs(remote)) { const gchar *metadata_url = fwupd_remote_get_metadata_uri(remote); g_autofree gchar *ext = fu_remote_list_get_last_ext(metadata_url); if (g_strcmp0(ext, self->lvfs_metadata_format) != 0) { g_autoptr(GString) str = g_string_new(metadata_url); g_autofree gchar *metadata_ext = g_strdup_printf(".%s", self->lvfs_metadata_format); g_string_replace(str, ".gz", metadata_ext, 0); g_string_replace(str, ".xz", metadata_ext, 0); g_string_replace(str, ".zst", metadata_ext, 0); g_info("auto-fixing remote %s MetadataURI from %s to %s", fwupd_remote_get_id(remote), metadata_url, str->str); fwupd_remote_set_metadata_uri(remote, str->str); } } /* load remote */ if (!fwupd_remote_setup(remote, error)) { g_prefix_error(error, "failed to setup %s: ", filename); return FALSE; } /* delete the obsolete files if the remote is now set up to use a new metadata format */ if (fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED) && fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DOWNLOAD && fu_remote_list_is_remote_origin_lvfs(remote)) { if (!fu_remote_list_cleanup_lvfs_remote(self, remote, error)) return FALSE; } /* watch the remote_list file and the XML file itself */ if (!fu_remote_list_add_inotify(self, filename, error)) return FALSE; if (!fu_remote_list_add_inotify(self, fwupd_remote_get_filename_cache(remote), error)) return FALSE; /* try to find a custom agreement, falling back to a generic warning */ if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DOWNLOAD) { g_autofree gchar *component_id = _fwupd_remote_build_component_id(remote); g_autofree gchar *xpath = NULL; g_autoptr(GString) agreement_markup = NULL; g_autoptr(XbNode) component = NULL; const gchar *tmp; xpath = g_strdup_printf("component/id[text()='%s']/..", component_id); component = xb_silo_query_first(self->silo, xpath, NULL); if (component != NULL) { agreement_markup = _fwupd_remote_get_agreement_for_app(remote, component, error); } else { agreement_markup = _fwupd_remote_get_agreement_default(remote, error); } if (agreement_markup == NULL) return FALSE; /* replace any dynamic values from os-release */ tmp = g_hash_table_lookup(os_release, "NAME"); if (tmp == NULL) tmp = "this distribution"; g_string_replace(agreement_markup, "$OS_RELEASE:NAME$", tmp, 0); tmp = g_hash_table_lookup(os_release, "BUG_REPORT_URL"); if (tmp == NULL) tmp = "https://github.com/fwupd/fwupd/issues"; g_string_replace(agreement_markup, "$OS_RELEASE:BUG_REPORT_URL$", tmp, 0); fwupd_remote_set_agreement(remote, agreement_markup->str); } /* set mtime */ fwupd_remote_set_mtime(remote, _fwupd_remote_get_mtime(remote)); fu_remote_list_add_remote(self, remote); /* success */ return TRUE; } static gboolean fu_remote_list_add_for_path(FuRemoteList *self, const gchar *path, GError **error) { g_autofree gchar *path_remotes = NULL; g_autoptr(GHashTable) os_release = NULL; g_autoptr(GPtrArray) paths = NULL; path_remotes = g_build_filename(path, "remotes.d", NULL); if (!g_file_test(path_remotes, G_FILE_TEST_EXISTS)) { g_debug("path %s does not exist", path_remotes); return TRUE; } if (!fu_remote_list_add_inotify(self, path_remotes, error)) return FALSE; paths = fu_path_glob(path_remotes, "*.conf", NULL); if (paths == NULL) return TRUE; os_release = fwupd_get_os_release(error); if (os_release == NULL) return FALSE; for (guint i = 0; i < paths->len; i++) { const gchar *filename = g_ptr_array_index(paths, i); if (g_str_has_suffix(filename, "fwupd-tests.conf") && !self->testing_remote) continue; if (!fu_remote_list_add_for_file(self, os_release, filename, error)) return FALSE; } return TRUE; } gboolean fu_remote_list_set_key_value(FuRemoteList *self, const gchar *remote_id, const gchar *key, const gchar *value, GError **error) { FwupdRemote *remote; const gchar *filename; g_autofree gchar *filename_new = NULL; g_autofree gchar *value_old = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GKeyFile) keyfile = g_key_file_new(); /* check remote is valid */ remote = fu_remote_list_get_by_id(self, remote_id); if (remote == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "remote %s not found", remote_id); return FALSE; } /* modify the remote */ filename = fwupd_remote_get_filename_source(remote); if (!g_key_file_load_from_file(keyfile, filename, G_KEY_FILE_KEEP_COMMENTS, error)) { g_prefix_error(error, "failed to load %s: ", filename); return FALSE; } value_old = g_key_file_get_string(keyfile, "fwupd Remote", key, NULL); if (g_strcmp0(value_old, value) == 0) return TRUE; g_key_file_set_string(keyfile, "fwupd Remote", key, value); /* try existing file first, then call back to the mutable location */ if (!g_key_file_save_to_file(keyfile, filename, &error_local)) { if (g_error_matches(error_local, G_FILE_ERROR, G_FILE_ERROR_PERM)) { g_autofree gchar *basename = g_path_get_basename(filename); g_autofree gchar *remotesdir_mut = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); filename_new = g_build_filename(remotesdir_mut, "remotes.d", basename, NULL); if (!fu_path_mkdir_parent(filename_new, error)) return FALSE; g_info("falling back from %s to %s", filename, filename_new); if (!g_key_file_save_to_file(keyfile, filename_new, error)) return FALSE; } else { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } else { filename_new = g_strdup(filename); } /* reload values */ if (!fwupd_remote_load_from_filename(remote, filename_new, NULL, error)) { g_prefix_error(error, "failed to load %s: ", filename_new); return FALSE; } fu_remote_list_emit_changed(self); return TRUE; } static gint fu_remote_list_sort_cb(gconstpointer a, gconstpointer b) { FwupdRemote *remote_a = *((FwupdRemote **)a); FwupdRemote *remote_b = *((FwupdRemote **)b); /* use priority first */ if (fwupd_remote_get_priority(remote_a) < fwupd_remote_get_priority(remote_b)) return 1; if (fwupd_remote_get_priority(remote_a) > fwupd_remote_get_priority(remote_b)) return -1; /* fall back to name */ return g_strcmp0(fwupd_remote_get_id(remote_a), fwupd_remote_get_id(remote_b)); } static guint fu_remote_list_depsolve_order_before(FuRemoteList *self) { guint cnt = 0; for (guint i = 0; i < self->array->len; i++) { FwupdRemote *remote = g_ptr_array_index(self->array, i); gchar **order = fwupd_remote_get_order_before(remote); if (order == NULL) continue; for (guint j = 0; order[j] != NULL; j++) { FwupdRemote *remote2; if (g_strcmp0(order[j], fwupd_remote_get_id(remote)) == 0) { g_debug("ignoring self-dep remote %s", order[j]); continue; } remote2 = fu_remote_list_get_by_id(self, order[j]); if (remote2 == NULL) { g_debug("ignoring unfound remote %s", order[j]); continue; } if (fwupd_remote_get_priority(remote) > fwupd_remote_get_priority(remote2)) continue; g_debug("ordering %s=%s+1", fwupd_remote_get_id(remote), fwupd_remote_get_id(remote2)); fwupd_remote_set_priority(remote, fwupd_remote_get_priority(remote2) + 1); cnt++; } } return cnt; } static guint fu_remote_list_depsolve_order_after(FuRemoteList *self) { guint cnt = 0; for (guint i = 0; i < self->array->len; i++) { FwupdRemote *remote = g_ptr_array_index(self->array, i); gchar **order = fwupd_remote_get_order_after(remote); if (order == NULL) continue; for (guint j = 0; order[j] != NULL; j++) { FwupdRemote *remote2; if (g_strcmp0(order[j], fwupd_remote_get_id(remote)) == 0) { g_debug("ignoring self-dep remote %s", order[j]); continue; } remote2 = fu_remote_list_get_by_id(self, order[j]); if (remote2 == NULL) { g_debug("ignoring unfound remote %s", order[j]); continue; } if (fwupd_remote_get_priority(remote) < fwupd_remote_get_priority(remote2)) continue; g_debug("ordering %s=%s+1", fwupd_remote_get_id(remote2), fwupd_remote_get_id(remote)); fwupd_remote_set_priority(remote2, fwupd_remote_get_priority(remote) + 1); cnt++; } } return cnt; } static gboolean fu_remote_list_reload(FuRemoteList *self, GError **error) { guint depsolve_check; g_autofree gchar *remotesdir = NULL; g_autofree gchar *remotesdir_mut = NULL; g_autofree gchar *remotesdir_immut = NULL; g_autoptr(GString) str = g_string_new(NULL); /* clear */ g_ptr_array_set_size(self->array, 0); g_ptr_array_set_size(self->monitors, 0); /* search mutable, and then fall back to /etc and immutable */ remotesdir_mut = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); if (!fu_remote_list_add_for_path(self, remotesdir_mut, error)) return FALSE; remotesdir = fu_path_from_kind(FU_PATH_KIND_SYSCONFDIR_PKG); if (!fu_remote_list_add_for_path(self, remotesdir, error)) return FALSE; remotesdir_immut = fu_path_from_kind(FU_PATH_KIND_DATADIR_PKG); if (!fu_remote_list_add_for_path(self, remotesdir_immut, error)) return FALSE; /* depsolve */ for (depsolve_check = 0; depsolve_check < 100; depsolve_check++) { guint cnt = 0; cnt += fu_remote_list_depsolve_order_before(self); cnt += fu_remote_list_depsolve_order_after(self); if (cnt == 0) break; } if (depsolve_check == 100) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot depsolve remotes ordering"); return FALSE; } /* order these by priority, then name */ g_ptr_array_sort(self->array, fu_remote_list_sort_cb); /* print to the console */ for (guint i = 0; i < self->array->len; i++) { FwupdRemote *remote = g_ptr_array_index(self->array, i); if (!fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)) continue; if (str->len > 0) g_string_append(str, ", "); g_string_append_printf(str, "%s[%i]", fwupd_remote_get_id(remote), fwupd_remote_get_priority(remote)); } g_info("enabled remotes: %s", str->str); /* success */ return TRUE; } static gboolean fu_remote_list_load_metainfos(XbBuilder *builder, GError **error) { const gchar *fn; g_autofree gchar *datadir = NULL; g_autofree gchar *metainfo_path = NULL; g_autoptr(GDir) dir = NULL; /* pkg metainfo dir */ datadir = fu_path_from_kind(FU_PATH_KIND_DATADIR_PKG); metainfo_path = g_build_filename(datadir, "metainfo", NULL); if (!g_file_test(metainfo_path, G_FILE_TEST_EXISTS)) return TRUE; g_debug("loading %s", metainfo_path); dir = g_dir_open(metainfo_path, 0, error); if (dir == NULL) return FALSE; while ((fn = g_dir_read_name(dir)) != NULL) { if (g_str_has_suffix(fn, ".metainfo.xml")) { g_autofree gchar *filename = g_build_filename(metainfo_path, fn, NULL); g_autoptr(GFile) file = g_file_new_for_path(filename); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); if (!xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, error)) return FALSE; xb_builder_import_source(builder, source); } } return TRUE; } gboolean fu_remote_list_load(FuRemoteList *self, FuRemoteListLoadFlags flags, GError **error) { const gchar *const *locales = g_get_language_names(); g_autoptr(GFile) xmlb = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); XbBuilderCompileFlags compile_flags = XB_BUILDER_COMPILE_FLAG_SINGLE_LANG | XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID; g_return_val_if_fail(FU_IS_REMOTE_LIST(self), FALSE); g_return_val_if_fail(self->silo == NULL, FALSE); /* enable testing only remotes */ if (flags & FU_REMOTE_LIST_LOAD_FLAG_TEST_REMOTE) self->testing_remote = TRUE; /* autofix on reload too */ if (flags & FU_REMOTE_LIST_LOAD_FLAG_FIX_METADATA_URI) self->fix_metadata_uri = TRUE; /* load AppStream about the remote_list */ if (!fu_remote_list_load_metainfos(builder, error)) return FALSE; /* add the locales, which is really only going to be 'C' or 'en' */ for (guint i = 0; locales[i] != NULL; i++) xb_builder_add_locale(builder, locales[i]); /* on a read-only filesystem don't care about the cache GUID */ if (flags & FU_REMOTE_LIST_LOAD_FLAG_READONLY_FS) compile_flags |= XB_BUILDER_COMPILE_FLAG_IGNORE_GUID; /* build the metainfo silo */ if (flags & FU_REMOTE_LIST_LOAD_FLAG_NO_CACHE) { g_autoptr(GFileIOStream) iostr = NULL; xmlb = g_file_new_tmp(NULL, &iostr, error); if (xmlb == NULL) return FALSE; } else { g_autofree gchar *cachedirpkg = fu_path_from_kind(FU_PATH_KIND_CACHEDIR_PKG); g_autofree gchar *xmlbfn = g_build_filename(cachedirpkg, "metainfo.xmlb", NULL); xmlb = g_file_new_for_path(xmlbfn); } self->silo = xb_builder_ensure(builder, xmlb, compile_flags, NULL, error); if (self->silo == NULL) return FALSE; /* load remote_list */ return fu_remote_list_reload(self, error); } GPtrArray * fu_remote_list_get_all(FuRemoteList *self) { g_return_val_if_fail(FU_IS_REMOTE_LIST(self), NULL); return self->array; } FwupdRemote * fu_remote_list_get_by_id(FuRemoteList *self, const gchar *remote_id) { g_return_val_if_fail(FU_IS_REMOTE_LIST(self), NULL); for (guint i = 0; i < self->array->len; i++) { FwupdRemote *remote = g_ptr_array_index(self->array, i); if (g_strcmp0(remote_id, fwupd_remote_get_id(remote)) == 0) return remote; } return NULL; } static void fu_remote_list_class_init(FuRemoteListClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_remote_list_finalize; /** * FuRemoteList::changed: * @self: the #FuRemoteList instance that emitted the signal * * The ::changed signal is emitted when the list of remotes has * changed, for instance when a remote has been added or removed. **/ signals[SIGNAL_CHANGED] = g_signal_new("changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * FuRemoteList::added: * @self: the #FuRemoteList instance that emitted the signal * @remote: the #FwupdRemote that was added **/ signals[SIGNAL_ADDED] = g_signal_new("added", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, FWUPD_TYPE_REMOTE); } static void fu_remote_list_monitor_unref(GFileMonitor *monitor) { g_file_monitor_cancel(monitor); g_object_unref(monitor); } static void fu_remote_list_init(FuRemoteList *self) { self->array = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); self->monitors = g_ptr_array_new_with_free_func((GDestroyNotify)fu_remote_list_monitor_unref); } static void fu_remote_list_finalize(GObject *obj) { FuRemoteList *self = FU_REMOTE_LIST(obj); if (self->silo != NULL) g_object_unref(self->silo); g_ptr_array_unref(self->array); g_ptr_array_unref(self->monitors); g_free(self->lvfs_metadata_format); G_OBJECT_CLASS(fu_remote_list_parent_class)->finalize(obj); } FuRemoteList * fu_remote_list_new(void) { FuRemoteList *self; self = g_object_new(FU_TYPE_REMOTE_LIST, NULL); return FU_REMOTE_LIST(self); } fwupd-1.9.16/src/fu-remote-list.h000066400000000000000000000032501460375044200165560ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #define FU_TYPE_REMOTE_LIST (fu_remote_list_get_type()) G_DECLARE_FINAL_TYPE(FuRemoteList, fu_remote_list, FU, REMOTE_LIST, GObject) /** * FuRemoteListLoadFlags: * @FU_REMOTE_LIST_LOAD_FLAG_NONE: No flags set * @FU_REMOTE_LIST_LOAD_FLAG_READONLY_FS: Ignore readonly filesystem errors * @FU_REMOTE_LIST_LOAD_FLAG_NO_CACHE: Do not save persistent xmlb silos * @FU_REMOTE_LIST_LOAD_FLAG_TEST_REMOTE: Enable test mode remotes * @FU_REMOTE_LIST_LOAD_FLAG_FIX_METADATA_URI: Auto-fix to use the newest supported metadata * * The flags to use when loading a remote_listuration file. **/ typedef enum { FU_REMOTE_LIST_LOAD_FLAG_NONE = 0, FU_REMOTE_LIST_LOAD_FLAG_READONLY_FS = 1 << 0, FU_REMOTE_LIST_LOAD_FLAG_NO_CACHE = 1 << 1, FU_REMOTE_LIST_LOAD_FLAG_TEST_REMOTE = 1 << 2, FU_REMOTE_LIST_LOAD_FLAG_FIX_METADATA_URI = 1 << 3, /*< private >*/ FU_REMOTE_LIST_LOAD_FLAG_LAST } FuRemoteListLoadFlags; FuRemoteList * fu_remote_list_new(void); gboolean fu_remote_list_load(FuRemoteList *self, FuRemoteListLoadFlags flags, GError **error); gboolean fu_remote_list_set_key_value(FuRemoteList *self, const gchar *remote_id, const gchar *key, const gchar *value, GError **error); GPtrArray * fu_remote_list_get_all(FuRemoteList *self); FwupdRemote * fu_remote_list_get_by_id(FuRemoteList *self, const gchar *remote_id); void fu_remote_list_set_lvfs_metadata_format(FuRemoteList *self, const gchar *lvfs_metadata_format); /* for the self tests */ void fu_remote_list_add_remote(FuRemoteList *self, FwupdRemote *remote); fwupd-1.9.16/src/fu-security-attr-common.c000066400000000000000000000666711460375044200204320ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include "fwupd-security-attr-private.h" #include "fu-security-attr-common.h" #include "fu-security-attrs-private.h" gchar * fu_security_attr_get_name(FwupdSecurityAttr *attr) { const gchar *appstream_id = fwupd_security_attr_get_appstream_id(attr); if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SPI_BIOSWE) == 0) { /* TRANSLATORS: Title: SPI refers to the flash chip in the computer */ return g_strdup(_("SPI write")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SPI_BLE) == 0) { /* TRANSLATORS: Title: SPI refers to the flash chip in the computer */ return g_strdup(_("SPI lock")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SPI_SMM_BWP) == 0) { /* TRANSLATORS: Title: SPI refers to the flash chip in the computer */ return g_strdup(_("SPI BIOS region")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SPI_DESCRIPTOR) == 0) { /* TRANSLATORS: Title: SPI refers to the flash chip in the computer */ return g_strdup(_("SPI BIOS Descriptor")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_PREBOOT_DMA_PROTECTION) == 0) { /* TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack */ return g_strdup(_("Pre-boot DMA protection")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ENABLED) == 0) { /* TRANSLATORS: Title: BootGuard is a trademark from Intel */ return g_strdup(_("Intel BootGuard")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_VERIFIED) == 0) { /* TRANSLATORS: Title: BootGuard is a trademark from Intel, * verified boot refers to the way the boot process is verified */ return g_strdup(_("Intel BootGuard verified boot")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ACM) == 0) { /* TRANSLATORS: Title: BootGuard is a trademark from Intel, * ACM means to verify the integrity of Initial Boot Block */ return g_strdup(_("Intel BootGuard ACM protected")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_POLICY) == 0) { /* TRANSLATORS: Title: BootGuard is a trademark from Intel, * error policy is what to do on failure */ return g_strdup(_("Intel BootGuard error policy")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_OTP) == 0) { /* TRANSLATORS: Title: BootGuard is a trademark from Intel, * OTP = one time programmable */ return g_strdup(_("Intel BootGuard OTP fuse")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_CET_ENABLED) == 0) { /* TRANSLATORS: Title: CET = Control-flow Enforcement Technology, * enabled means supported by the processor */ return g_strdup(_("CET Platform")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_CET_ACTIVE) == 0) { /* TRANSLATORS: Title: CET = Control-flow Enforcement Technology, * Utilized by OS means the distribution enabled it*/ return g_strdup(_("CET OS Support")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_SMAP) == 0) { /* TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention */ return g_strdup(_("SMAP")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM) == 0) { /* TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME */ return g_strdup(_("Encrypted RAM")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_IOMMU) == 0) { /* TRANSLATORS: Title: * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit */ return g_strdup(_("IOMMU")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_KERNEL_LOCKDOWN) == 0) { /* TRANSLATORS: Title: lockdown is a security mode of the kernel */ return g_strdup(_("Linux kernel lockdown")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_KERNEL_TAINTED) == 0) { /* TRANSLATORS: Title: if it's tainted or not */ return g_strdup(_("Linux kernel")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_KERNEL_SWAP) == 0) { /* TRANSLATORS: Title: swap space or swap partition */ return g_strdup(_("Linux swap")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_RAM) == 0) { /* TRANSLATORS: Title: sleep state */ return g_strdup(_("Suspend-to-ram")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_IDLE) == 0) { /* TRANSLATORS: Title: a better sleep state */ return g_strdup(_("Suspend-to-idle")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_UEFI_PK) == 0) { /* TRANSLATORS: Title: PK is the 'platform key' for the machine */ return g_strdup(_("UEFI platform key")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_UEFI_SECUREBOOT) == 0) { /* TRANSLATORS: Title: SB is a way of locking down UEFI */ return g_strdup(_("UEFI secure boot")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_UEFI_BOOTSERVICE_VARS) == 0) { /* TRANSLATORS: Title: Bootservice is when only readable from early-boot */ return g_strdup(_("UEFI bootservice variables")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_TPM_EMPTY_PCR) == 0) { /* TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be empty */ return g_strdup(_("TPM empty PCRs")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_TPM_RECONSTRUCTION_PCR0) == 0) { /* TRANSLATORS: Title: the PCR is rebuilt from the TPM event log */ return g_strdup(_("TPM PCR0 reconstruction")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_TPM_VERSION_20) == 0) { /* TRANSLATORS: Title: TPM = Trusted Platform Module */ return g_strdup(_("TPM v2.0")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_MANUFACTURING_MODE) == 0) { const gchar *kind = fwupd_security_attr_get_metadata(attr, "kind"); if (kind != NULL) { /* TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT */ return g_strdup_printf(_("%s manufacturing mode"), kind); } /* TRANSLATORS: Title: MEI = Intel Management Engine */ return g_strdup(_("MEI manufacturing mode")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_OVERRIDE_STRAP) == 0) { const gchar *kind = fwupd_security_attr_get_metadata(attr, "kind"); if (kind != NULL) { /* TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT */ return g_strdup_printf(_("%s override"), kind); } /* TRANSLATORS: Title: MEI = Intel Management Engine, and the * "override" is the physical PIN that can be driven to * logic high -- luckily it is probably not accessible to * end users on consumer boards */ return g_strdup(_("MEI override")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST) == 0) { /* TRANSLATORS: Title: MEI = Intel Management Engine, and key refer * to the private/public key used to secure loading of firmware */ return g_strdup(_("MEI key manifest")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_VERSION) == 0) { /* TRANSLATORS: Title: MEI = Intel Management Engine */ const gchar *kind = fwupd_security_attr_get_metadata(attr, "kind"); const gchar *version = fwupd_security_attr_get_metadata(attr, "version"); if (kind != NULL && version != NULL) { /* TRANSLATORS: Title: %1 is ME kind, e.g. CSME/TXT, %2 is a version number */ return g_strdup_printf(_("%s v%s"), kind, version); } if (kind != NULL) { /* TRANSLATORS: Title: %s is ME kind, e.g. CSME/TXT */ return g_strdup_printf(_("%s version"), kind); } return g_strdup(_("MEI version")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_FWUPD_UPDATES) == 0) { /* TRANSLATORS: Title: if firmware updates are available */ return g_strdup(_("Firmware updates")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_FWUPD_ATTESTATION) == 0) { /* TRANSLATORS: Title: if we can verify the firmware checksums */ return g_strdup(_("Firmware attestation")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_FWUPD_PLUGINS) == 0) { /* TRANSLATORS: Title: if the fwupd plugins are all present and correct */ return g_strdup(_("fwupd plugins")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_ENABLED) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_LOCKED) == 0) { /* TRANSLATORS: Title: Allows debugging of parts using proprietary hardware */ return g_strdup(_("Platform debugging")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SUPPORTED_CPU) == 0) { /* TRANSLATORS: Title: if fwupd supports HSI on this chip */ return g_strdup(_("Supported CPU")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_AMD_ROLLBACK_PROTECTION) == 0) { /* TRANSLATORS: Title: if firmware enforces rollback protection */ return g_strdup(_("Processor rollback protection")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_AMD_SPI_REPLAY_PROTECTION) == 0) { /* TRANSLATORS: Title: if hardware enforces control of SPI replays */ return g_strdup(_("SPI replay protection")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_AMD_SPI_WRITE_PROTECTION) == 0) { /* TRANSLATORS: Title: if hardware enforces control of SPI writes */ return g_strdup(_("SPI write protection")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_PLATFORM_FUSED) == 0) { /* TRANSLATORS: Title: if the part has been fused */ return g_strdup(_("Fused platform")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_HOST_EMULATION) == 0) { /* TRANSLATORS: Title: if we are emulating a different host */ return g_strdup(_("Emulated host")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_BIOS_ROLLBACK_PROTECTION) == 0) { /* TRANSLATORS: Title: if firmware enforces rollback protection */ return g_strdup(_("BIOS rollback protection")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_GDS) == 0) { /* TRANSLATORS: Title: GDS is where the CPU leaks information */ return g_strdup(_("Intel GDS mitigation")); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_BIOS_CAPSULE_UPDATES) == 0) { /* TRANSLATORS: Title: Whether BIOS Firmware updates is enabled */ return g_strdup(_("BIOS firmware updates")); } /* we should not get here */ return g_strdup(fwupd_security_attr_get_name(attr)); } const gchar * fu_security_attr_get_title(FwupdSecurityAttr *attr) { const gchar *appstream_id = fwupd_security_attr_get_appstream_id(attr); if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SPI_BIOSWE) == 0) { /* TRANSLATORS: Title: firmware refers to the flash chip in the computer */ return _("Firmware Write Protection"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SPI_BLE) == 0) { /* TRANSLATORS: Title: firmware refers to the flash chip in the computer */ return _("Firmware Write Protection Lock"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SPI_SMM_BWP) == 0) { /* TRANSLATORS: Title: SPI refers to the flash chip in the computer */ return _("Firmware BIOS Region"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SPI_DESCRIPTOR) == 0) { /* TRANSLATORS: Title: firmware refers to the flash chip in the computer */ return _("Firmware BIOS Descriptor"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_PREBOOT_DMA_PROTECTION) == 0) { /* TRANSLATORS: Title: DMA as in https://en.wikipedia.org/wiki/DMA_attack */ return _("Pre-boot DMA Protection"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ENABLED) == 0) { /* TRANSLATORS: Title: BootGuard is a trademark from Intel */ return _("Intel BootGuard"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_VERIFIED) == 0) { /* TRANSLATORS: Title: BootGuard is a trademark from Intel, * verified boot refers to the way the boot process is verified */ return _("Intel BootGuard Verified Boot"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ACM) == 0) { /* TRANSLATORS: Title: BootGuard is a trademark from Intel, * ACM means to verify the integrity of Initial Boot Block */ return _("Intel BootGuard ACM Protected"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_POLICY) == 0) { /* TRANSLATORS: Title: BootGuard is a trademark from Intel, * error policy is what to do on failure */ return _("Intel BootGuard Error Policy"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_OTP) == 0) { /* TRANSLATORS: Title: BootGuard is a trademark from Intel */ return _("Intel BootGuard Fuse"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_CET_ENABLED) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_CET_ACTIVE) == 0) { /* TRANSLATORS: Title: CET = Control-flow Enforcement Technology */ return _("Control-flow Enforcement Technology"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_SMAP) == 0) { /* TRANSLATORS: Title: SMAP = Supervisor Mode Access Prevention */ return _("Supervisor Mode Access Prevention"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM) == 0) { /* TRANSLATORS: Title: Memory contents are encrypted, e.g. Intel TME */ return _("Encrypted RAM"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_IOMMU) == 0) { /* TRANSLATORS: Title: * https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit */ return _("IOMMU Protection"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_KERNEL_LOCKDOWN) == 0) { /* TRANSLATORS: Title: lockdown is a security mode of the kernel */ return _("Linux Kernel Lockdown"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_KERNEL_TAINTED) == 0) { /* TRANSLATORS: Title: if it's tainted or not */ return _("Linux Kernel Verification"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_KERNEL_SWAP) == 0) { /* TRANSLATORS: Title: swap space or swap partition */ return _("Linux Swap"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_RAM) == 0) { /* TRANSLATORS: Title: sleep state */ return _("Suspend To RAM"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_IDLE) == 0) { /* TRANSLATORS: Title: a better sleep state */ return _("Suspend To Idle"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_UEFI_PK) == 0) { /* TRANSLATORS: Title: PK is the 'platform key' for the machine */ return _("UEFI Platform Key"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_UEFI_SECUREBOOT) == 0) { /* TRANSLATORS: Title: SB is a way of locking down UEFI */ return _("UEFI Secure Boot"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_UEFI_BOOTSERVICE_VARS) == 0) { /* TRANSLATORS: Title: Bootservice is when only readable from early-boot */ return _("UEFI Bootservice Variables"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_TPM_EMPTY_PCR) == 0) { /* TRANSLATORS: Title: PCRs (Platform Configuration Registers) shouldn't be empty */ return _("TPM Platform Configuration"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_TPM_RECONSTRUCTION_PCR0) == 0) { /* TRANSLATORS: Title: the PCR is rebuilt from the TPM event log */ return _("TPM Reconstruction"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_TPM_VERSION_20) == 0) { /* TRANSLATORS: Title: TPM = Trusted Platform Module */ return _("TPM v2.0"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_MANUFACTURING_MODE) == 0) { /* TRANSLATORS: Title: MEI = Intel Management Engine */ return _("Intel Management Engine Manufacturing Mode"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_OVERRIDE_STRAP) == 0) { /* TRANSLATORS: Title: MEI = Intel Management Engine, and the "override" is enabled * with a jumper -- luckily it is probably not accessible to end users on consumer * boards */ return _("Intel Management Engine Override"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST) == 0) { /* TRANSLATORS: Title: MEI = Intel Management Engine, and key refers * to the private/public key used to secure loading of firmware */ return _("MEI Key Manifest"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_VERSION) == 0) { /* TRANSLATORS: Title: MEI = Intel Management Engine */ return _("Intel Management Engine Version"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_FWUPD_UPDATES) == 0) { /* TRANSLATORS: Title: if firmware updates are available */ return _("Firmware Updates"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_FWUPD_ATTESTATION) == 0) { /* TRANSLATORS: Title: if we can verify the firmware checksums */ return _("Firmware Attestation"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_FWUPD_PLUGINS) == 0) { /* TRANSLATORS: Title: if the fwupd plugins are all present and correct */ return _("Firmware Updater Verification"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_ENABLED) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_LOCKED) == 0) { /* TRANSLATORS: Title: Allows debugging of parts using proprietary hardware */ return _("Platform Debugging"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SUPPORTED_CPU) == 0) { /* TRANSLATORS: Title: if fwupd supports HSI on this chip */ return _("Processor Security Checks"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_AMD_ROLLBACK_PROTECTION) == 0) { /* TRANSLATORS: Title: if firmware enforces rollback protection */ return _("AMD Secure Processor Rollback Protection"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_AMD_SPI_REPLAY_PROTECTION) == 0) { /* TRANSLATORS: Title: if hardware enforces control of SPI replays */ return _("AMD Firmware Replay Protection"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_AMD_SPI_WRITE_PROTECTION) == 0) { /* TRANSLATORS: Title: if hardware enforces control of SPI writes */ return _("AMD Firmware Write Protection"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_PLATFORM_FUSED) == 0) { /* TRANSLATORS: Title: if the part has been fused */ return _("Fused Platform"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_BIOS_ROLLBACK_PROTECTION) == 0) { /* TRANSLATORS: Title: if firmware enforces rollback protection */ return _("BIOS Rollback Protection"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_GDS) == 0) { /* TRANSLATORS: Title: GDS is where the CPU leaks information */ return _("Intel GDS Mitigation"); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_BIOS_CAPSULE_UPDATES) == 0) { /* TRANSLATORS: Title: Whether BIOS Firmware updates is enabled */ return _("BIOS Firmware Updates"); } return NULL; } /* one line describing the attribute */ const gchar * fu_security_attr_get_description(FwupdSecurityAttr *attr) { const gchar *appstream_id = fwupd_security_attr_get_appstream_id(attr); if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SPI_BIOSWE) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SPI_BLE) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_AMD_SPI_WRITE_PROTECTION) == 0) { /* TRANSLATORS: longer description */ return _("Firmware Write Protection protects device firmware memory from being " "tampered with."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SPI_SMM_BWP) == 0) { /* TRANSLATORS: longer description */ return _("Firmware BIOS Region protects device firmware memory from being " "tampered with."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SPI_DESCRIPTOR) == 0) { /* TRANSLATORS: longer description */ return _("Firmware BIOS Descriptor protects device firmware memory from being " "tampered with."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_PREBOOT_DMA_PROTECTION) == 0) { /* TRANSLATORS: longer description */ return _("Pre-boot DMA protection prevents devices from accessing system memory " "after being connected to the computer."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ENABLED) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_ACM) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_OTP) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_VERIFIED) == 0) { /* TRANSLATORS: longer description */ return _("Intel BootGuard prevents unauthorized device software from operating " "when the device is started."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_BOOTGUARD_POLICY) == 0) { /* TRANSLATORS: longer description */ return _( "Intel BootGuard Error Policy ensures the device does not continue to start if " "its device software has been tampered with."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_CET_ENABLED) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_CET_ACTIVE) == 0) { /* TRANSLATORS: longer description */ return _("Control-Flow Enforcement Technology detects and prevents certain " "methods for running malicious software on the device."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_SMAP) == 0) { /* TRANSLATORS: longer description */ return _("Supervisor Mode Access Prevention ensures critical parts of " "device memory are not accessed by less secure programs."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM) == 0) { /* TRANSLATORS: longer description */ return _( "Encrypted RAM makes it impossible for information that is stored in device " "memory to be read if the memory chip is removed and accessed."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_IOMMU) == 0) { /* TRANSLATORS: longer description */ return _("IOMMU Protection prevents connected devices from accessing unauthorized " "parts of system memory."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_KERNEL_LOCKDOWN) == 0) { /* TRANSLATORS: longer description */ return _("Linux Kernel Lockdown mode prevents administrator (root) accounts from " "accessing and changing critical parts of system software."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_KERNEL_TAINTED) == 0) { /* TRANSLATORS: longer description */ return _( "Linux Kernel Verification makes sure that critical system software has " "not been tampered with. Using device drivers which are not provided with the " "system can prevent this security feature from working correctly."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_KERNEL_SWAP) == 0) { /* TRANSLATORS: longer description */ return _( "Linux Kernel Swap temporarily saves information to disk as you work. If the " "information is not protected, it could be accessed by someone if they " "obtained the disk."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_RAM) == 0) { /* TRANSLATORS: longer description */ return _("Suspend to RAM allows the device to quickly go to sleep in order to save " "power. While the device has been suspended, its memory could be " "physically removed and its information accessed."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SUSPEND_TO_IDLE) == 0) { /* TRANSLATORS: longer description */ return _("Suspend to Idle allows the device to quickly go to sleep in order to " "save power. While the device has been suspended, its memory could be " "physically removed and its information accessed."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_UEFI_PK) == 0) { /* TRANSLATORS: longer description */ return _("The UEFI Platform Key is used to determine if device software comes from " "a trusted source."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_UEFI_SECUREBOOT) == 0) { /* TRANSLATORS: longer description */ return _("UEFI Secure Boot prevents malicious software from being loaded when the " "device starts."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_UEFI_BOOTSERVICE_VARS) == 0) { /* TRANSLATORS: longer description */ return _("UEFI boot service variables should not be readable from runtime mode."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_TPM_EMPTY_PCR) == 0) { /* TRANSLATORS: longer description */ return _("The TPM (Trusted Platform Module) Platform Configuration is used to " "check whether the device start process has been modified."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_TPM_RECONSTRUCTION_PCR0) == 0) { /* TRANSLATORS: longer description */ return _("The TPM (Trusted Platform Module) Reconstruction is used to check " "whether the device start process has been modified."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_TPM_VERSION_20) == 0) { /* TRANSLATORS: longer description */ return _("TPM (Trusted Platform Module) is a computer chip that detects when " "hardware components have been tampered with."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_MANUFACTURING_MODE) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_PLATFORM_FUSED) == 0) { /* TRANSLATORS: longer description */ return _("Manufacturing Mode is used when the device is manufactured and " "security features are not yet enabled."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_OVERRIDE_STRAP) == 0) { /* TRANSLATORS: longer description */ return _("Intel Management Engine Override disables checks for device software " "tampering."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_KEY_MANIFEST) == 0) { /* TRANSLATORS: longer description */ return _("The Intel Management Engine Key Manifest must be valid so " "that the device firmware can be trusted by the CPU."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_MEI_VERSION) == 0) { /* TRANSLATORS: longer description */ return _("The Intel Management Engine controls device components and needs " "to have a recent version to avoid security issues."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_FWUPD_UPDATES) == 0) { /* TRANSLATORS: longer description */ return _("Device software updates are provided for this device."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_FWUPD_ATTESTATION) == 0) { /* TRANSLATORS: longer description */ return _("Firmware Attestation checks device software using a reference copy, to " "make sure that it has not been changed."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_FWUPD_PLUGINS) == 0) { /* TRANSLATORS: longer description */ return _( "Firmware Updater Verification checks that software used for updating has not " "been tampered with."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_ENABLED) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_PLATFORM_DEBUG_LOCKED) == 0) { /* TRANSLATORS: longer description */ return _("Platform Debugging allows device security features to be disabled. " "This should only be used by hardware manufacturers."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_SUPPORTED_CPU) == 0) { /* TRANSLATORS: longer description */ return _("Each system should have tests to ensure firmware security."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_AMD_ROLLBACK_PROTECTION) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_AMD_SPI_REPLAY_PROTECTION) == 0 || g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_BIOS_ROLLBACK_PROTECTION) == 0) { /* TRANSLATORS: longer description */ return _("Rollback Protection prevents device software from being downgraded " "to an older version that has security problems."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_INTEL_GDS) == 0) { /* TRANSLATORS: longer description */ return _("CPU Microcode must be updated to mitigate against various " "information-disclosure security issues."); } if (g_strcmp0(appstream_id, FWUPD_SECURITY_ATTR_ID_BIOS_CAPSULE_UPDATES) == 0) { /* TRANSLATORS: longer description */ return _("Enabling firmware updates for the BIOS allows fixing security issues."); } return NULL; } fwupd-1.9.16/src/fu-security-attr-common.h000066400000000000000000000006461460375044200204250ustar00rootroot00000000000000/* * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fu-security-attrs-private.h" gchar * fu_security_attr_get_name(FwupdSecurityAttr *attr); const gchar * fu_security_attr_get_title(FwupdSecurityAttr *attr); const gchar * fu_security_attr_get_description(FwupdSecurityAttr *attr); fwupd-1.9.16/src/fu-self-test.c000066400000000000000000006656631460375044200162410ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fwupd-bios-setting-private.h" #include "fwupd-remote-private.h" #include "fwupd-security-attr-private.h" #include "../plugins/test/fu-test-plugin.h" #include "fu-backend-private.h" #include "fu-bios-settings-private.h" #include "fu-cabinet-common.h" #include "fu-cabinet.h" #include "fu-client-list.h" #include "fu-config-private.h" #include "fu-console.h" #include "fu-context-private.h" #include "fu-device-list.h" #include "fu-device-private.h" #include "fu-engine-config.h" #include "fu-engine-requirements.h" #include "fu-engine.h" #include "fu-history.h" #include "fu-idle.h" #include "fu-plugin-list.h" #include "fu-plugin-private.h" #include "fu-release-common.h" #include "fu-remote-list.h" #include "fu-security-attr-common.h" #include "fu-smbios-private.h" #include "fu-spawn.h" #include "fu-usb-backend.h" typedef struct { FuPlugin *plugin; FuContext *ctx; } FuTest; static GMainLoop *_test_loop = NULL; static guint _test_loop_timeout_id = 0; static gboolean fu_test_hang_check_cb(gpointer user_data) { g_main_loop_quit(_test_loop); _test_loop_timeout_id = 0; return G_SOURCE_REMOVE; } static void fu_test_loop_run_with_timeout(guint timeout_ms) { g_assert_cmpint(_test_loop_timeout_id, ==, 0); g_assert_null(_test_loop); _test_loop = g_main_loop_new(NULL, FALSE); _test_loop_timeout_id = g_timeout_add(timeout_ms, fu_test_hang_check_cb, NULL); g_main_loop_run(_test_loop); } static void fu_test_loop_quit(void) { if (_test_loop_timeout_id > 0) { g_source_remove(_test_loop_timeout_id); _test_loop_timeout_id = 0; } if (_test_loop != NULL) { g_main_loop_quit(_test_loop); g_main_loop_unref(_test_loop); _test_loop = NULL; } } static void fu_self_test_mkroot(void) { if (g_file_test("/tmp/fwupd-self-test", G_FILE_TEST_EXISTS)) { g_autoptr(GError) error = NULL; if (!fu_path_rmtree("/tmp/fwupd-self-test", &error)) g_warning("failed to mkroot: %s", error->message); } g_assert_cmpint(g_mkdir_with_parents("/tmp/fwupd-self-test/var/lib/fwupd", 0755), ==, 0); } static void fu_test_copy_file(const gchar *source, const gchar *target) { gboolean ret; g_autofree gchar *data = NULL; g_autoptr(GError) error = NULL; g_debug("copying %s to %s", source, target); ret = g_file_get_contents(source, &data, NULL, &error); g_assert_no_error(error); g_assert_true(ret); ret = g_file_set_contents(target, data, -1, &error); g_assert_no_error(error); g_assert_true(ret); } static gboolean fu_test_compare_lines(const gchar *txt1, const gchar *txt2, GError **error) { g_autofree gchar *output = NULL; if (g_strcmp0(txt1, txt2) == 0) return TRUE; if (g_pattern_match_simple(txt2, txt1)) return TRUE; if (!g_file_set_contents("/tmp/a", txt1, -1, error)) return FALSE; if (!g_file_set_contents("/tmp/b", txt2, -1, error)) return FALSE; if (!g_spawn_command_line_sync("diff -urNp /tmp/b /tmp/a", &output, NULL, NULL, error)) return FALSE; g_set_error_literal(error, 1, 0, output); return FALSE; } static void fu_test_free(FuTest *self) { if (self->ctx != NULL) g_object_unref(self->ctx); if (self->plugin != NULL) g_object_unref(self->plugin); g_free(self); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuTest, fu_test_free) #pragma clang diagnostic pop static void fu_client_list_func(void) { g_autoptr(FuClient) client_find = NULL; g_autoptr(FuClient) client = NULL; g_autoptr(FuClient) client_orig = NULL; g_autoptr(FuClientList) client_list = fu_client_list_new(NULL); g_autoptr(GPtrArray) clients_empty = NULL; g_autoptr(GPtrArray) clients_full = NULL; /* ensure empty */ clients_empty = fu_client_list_get_all(client_list); g_assert_cmpint(clients_empty->len, ==, 0); /* register a client, then find it */ client_orig = fu_client_list_register(client_list, ":hello"); g_assert_nonnull(client_orig); client_find = fu_client_list_get_by_sender(client_list, ":hello"); g_assert_nonnull(client_find); g_assert_true(client_orig == client_find); clients_full = fu_client_list_get_all(client_list); g_assert_cmpint(clients_full->len, ==, 1); /* register a duplicate, check properties */ client = fu_client_list_register(client_list, ":hello"); g_assert_nonnull(client); g_assert_true(client_orig == client); g_assert_cmpstr(fu_client_get_sender(client), ==, ":hello"); g_assert_cmpint(fu_client_get_feature_flags(client), ==, FWUPD_FEATURE_FLAG_NONE); g_assert_cmpstr(fu_client_lookup_hint(client, "key"), ==, NULL); g_assert_true(fu_client_has_flag(client, FU_CLIENT_FLAG_ACTIVE)); fu_client_insert_hint(client, "key", "value"); fu_client_set_feature_flags(client, FWUPD_FEATURE_FLAG_UPDATE_ACTION); g_assert_cmpstr(fu_client_lookup_hint(client, "key"), ==, "value"); g_assert_cmpint(fu_client_get_feature_flags(client), ==, FWUPD_FEATURE_FLAG_UPDATE_ACTION); /* emulate disconnect */ fu_client_remove_flag(client, FU_CLIENT_FLAG_ACTIVE); g_assert_false(fu_client_has_flag(client, FU_CLIENT_FLAG_ACTIVE)); } static void fu_idle_func(void) { guint token; g_autoptr(FuIdle) idle = fu_idle_new(); fu_idle_reset(idle); g_assert_false(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_TIMEOUT)); g_assert_false(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS)); token = fu_idle_inhibit(idle, FU_IDLE_INHIBIT_TIMEOUT | FU_IDLE_INHIBIT_SIGNALS, NULL); g_assert_true(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_TIMEOUT)); g_assert_true(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS)); /* wrong token */ fu_idle_uninhibit(idle, token + 1); g_assert_true(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS)); /* correct token */ fu_idle_uninhibit(idle, token); g_assert_false(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_TIMEOUT)); g_assert_false(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS)); /* locker section */ { g_autoptr(FuIdleLocker) idle_locker1 = fu_idle_locker_new(idle, FU_IDLE_INHIBIT_TIMEOUT, NULL); g_autoptr(FuIdleLocker) idle_locker2 = fu_idle_locker_new(idle, FU_IDLE_INHIBIT_SIGNALS, NULL); g_assert_nonnull(idle_locker1); g_assert_nonnull(idle_locker2); g_assert_true(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_TIMEOUT)); g_assert_true(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS)); } g_assert_false(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_TIMEOUT)); g_assert_false(fu_idle_has_inhibit(idle, FU_IDLE_INHIBIT_SIGNALS)); } static void fu_engine_generate_md_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; const gchar *tmp; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GBytes) data = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; /* put cab file somewhere we can parse it */ filename = g_test_build_filename(G_TEST_DIST, "tests", "colorhug", "colorhug-als-3.0.2.cab", NULL); data = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(data); ret = fu_bytes_set_contents("/tmp/fwupd-self-test/var/cache/fwupd/foo.cab", data, &error); g_assert_no_error(error); g_assert_true(ret); /* load engine and check the device was found */ ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.3"); component = fu_engine_get_component_by_guids(engine, device); g_assert_nonnull(component); /* check remote ID set */ tmp = xb_node_query_text(component, "../custom/value[@key='fwupd::RemoteId']", NULL); g_assert_cmpstr(tmp, ==, "directory"); /* verify checksums */ tmp = xb_node_query_text(component, "releases/release/checksum[@target='container']", NULL); g_assert_cmpstr(tmp, ==, "3da49ddd961144a79336b3ac3b0e469cb2531d0e"); tmp = xb_node_query_text(component, "releases/release/checksum[@target='content']", NULL); g_assert_cmpstr(tmp, ==, NULL); } static void fu_engine_requirements_missing_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; const gchar *xml = "" " " " not.going.to.exist" " " " " " " " " ""; /* set up a dummy version */ fu_engine_add_runtime_version(engine, "org.test.dummy", "1.2.3"); /* make the component require one thing */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this fails */ fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_false(ret); } static void fu_engine_requirements_soft_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; const gchar *xml = "" " " " not.going.to.exist" " " " " " " " " ""; /* set up a dummy version */ fu_engine_add_runtime_version(engine, "org.test.dummy", "1.2.3"); /* make the component require one thing */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this passes */ fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_FORCE, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_engine_requirements_client_fail_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; const gchar *xml = "" " " " detach-action" " " " " " " " " ""; /* make the component require one thing */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this fails */ fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_false(ret); } static void fu_engine_requirements_client_invalid_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; const gchar *xml = "" " " " hello-dave" " org.freedesktop.fwupd\n" " " " " " " " " ""; /* make the component require one thing */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this fails */ fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_false(ret); } static void fu_engine_requirements_client_pass_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; const gchar *xml = "" " " " detach-action" " org.freedesktop.fwupd\n" " " " " " " " " ""; /* set up a dummy version */ fu_engine_request_set_feature_flags(request, FWUPD_FEATURE_FLAG_DETACH_ACTION); /* make the component require one thing */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this passes */ fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_engine_requirements_not_hardware_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; const gchar *xml = "" " " " ffffffff-ffff-ffff-ffff-ffffffffffff" " org.freedesktop.fwupd\n" " " " " " " " " ""; /* set up a dummy version */ fu_engine_request_set_feature_flags(request, FWUPD_FEATURE_FLAG_DETACH_ACTION); /* make the component require one thing */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this passes */ fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_engine_requirements_version_require_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " ""; /* set up a dummy device */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.3"); fu_device_set_version_bootloader(device, "4.5.6"); fu_device_add_vendor_id(device, "FFFF"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); /* make the component require one thing */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this fails */ fu_release_set_device(release, device); fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_true( g_str_has_prefix(error->message, "device requires firmware with a version check")); g_assert_false(ret); } static void fu_engine_requirements_version_lowest_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " ""; /* set up a dummy device */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.3"); fu_device_set_version_lowest(device, "1.2.3"); fu_device_add_vendor_id(device, "FFFF"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); /* make the component require one thing */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this fails */ fu_release_set_device(release, device); fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_true( g_str_has_prefix(error->message, "Specified firmware is older than the minimum")); g_assert_false(ret); } static void fu_engine_requirements_unsupported_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; const gchar *xml = "" " " " " " " " " " " " " ""; /* set up a dummy version */ fu_engine_add_runtime_version(engine, "org.test.dummy", "1.2.3"); /* make the component require one thing that we don't support */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this fails */ fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_false(ret); } static void fu_engine_requirements_child_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuDevice) child = fu_device_new(NULL); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " not-child" " org.freedesktop.fwupd\n" " " " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " ""; /* set up a dummy device */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.3"); fu_device_set_version_bootloader(device, "4.5.6"); fu_device_add_vendor_id(device, "FFFF"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(child, "0.0.999"); fu_device_set_physical_id(child, "dummy"); fu_device_add_child(device, child); /* make the component require three things */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this passes */ fu_release_set_device(release, device); fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_engine_requirements_child_fail_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuDevice) child = fu_device_new(NULL); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " not-child" " org.freedesktop.fwupd\n" " " " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " ""; /* set up a dummy device */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.3"); fu_device_set_version_bootloader(device, "4.5.6"); fu_device_add_vendor_id(device, "FFFF"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_set_version_format(child, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(child, "0.0.1"); fu_device_set_physical_id(child, "dummy"); fu_device_add_child(device, child); /* make the component require three things */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this passes */ fu_release_set_device(release, device); fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_nonnull( g_strstr_len(error->message, -1, "Not compatible with child device version")); g_assert_false(ret); } static void fu_engine_requirements_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " org.test.dummy" " 6ff95c9c-ae41-5f59-9d90-3ec1ea66091e" " org.freedesktop.fwupd\n" " org.freedesktop.fwupd\n" " " " " " " " " ""; /* set up some dummy versions */ fu_engine_add_runtime_version(engine, "org.test.dummy", "1.2.3"); fu_engine_add_runtime_version(engine, "com.hughski.colorhug", "7.8.9"); /* make the component require one thing */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this passes */ fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_engine_requirements_device_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " " " bootloader" " vendor-id" #ifdef __linux__ " org.kernel" #endif " org.freedesktop.fwupd\n" " " " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " ""; /* set up a dummy device */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.3"); fu_device_set_version_bootloader(device, "4.5.6"); fu_device_add_vendor_id(device, "USB:0xFFFF"); fu_device_add_vendor_id(device, "PCI:0x0000"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); /* make the component require three things */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this passes */ fu_release_set_device(release, device); fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); /* check this fails, as the wrong requirement is specified */ fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_ENFORCE_REQUIRES); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_nonnull(g_strstr_len(error->message, -1, "child, parent or sibling requirement")); g_assert_false(ret); #ifndef SUPPORTED_BUILD /* we can force this */ g_clear_error(&error); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_FORCE, &error); g_assert_no_error(error); g_assert_true(ret); #endif } static void fu_engine_requirements_device_plain_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " ""; /* set up a dummy device */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_version(device, "5101AALB"); fu_device_add_vendor_id(device, "FFFF"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); /* make the component require three things */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this passes */ fu_release_set_device(release, device); fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_engine_requirements_version_format_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " " " " triplet" " " ""; /* set up a dummy device */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_QUAD); fu_device_set_version(device, "1.2.3.4"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); /* make the component require three things */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this fails */ fu_release_set_device(release, device); fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_nonnull( g_strstr_len(error->message, -1, "Firmware version formats were different")); g_assert_false(ret); } static void fu_engine_requirements_only_upgrade_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; const gchar *xml = "" " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " ""; /* set up a dummy device */ fu_device_set_version(device, "1.2.4"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); /* make the component require three things */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this fails */ fu_release_set_device(release, device); fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_nonnull(g_strstr_len(error->message, -1, "Device only supports version upgrades")); g_assert_false(ret); } static void fu_engine_requirements_sibling_device_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device1 = fu_device_new(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new(self->ctx); g_autoptr(FuDevice) unrelated_device3 = fu_device_new(self->ctx); g_autoptr(FuDevice) parent = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release1 = fu_release_new(); g_autoptr(FuRelease) release2 = fu_release_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); const gchar *xml = "" " " " 1ff60ab2-3905-06a1-b476-0371f00c9e9b" " org.freedesktop.fwupd\n" " " " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " ""; /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* set up a dummy device */ fu_device_set_id(device1, "id1"); fu_device_set_version_format(device1, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device1, "1.2.3"); fu_device_add_vendor_id(device1, "FFFF"); fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_guid(device1, "12345678-1234-1234-1234-123456789012"); fu_device_add_protocol(device1, "com.acme"); fu_engine_add_device(engine, device1); /* setup the parent */ fu_device_set_id(parent, "parent"); fu_device_set_version_format(parent, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(parent, "1.0.0"); fu_device_add_flag(parent, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(parent, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_guid(parent, "42f3d696-0b6f-4d69-908f-357f98ef115e"); fu_device_add_protocol(parent, "com.acme"); fu_device_add_child(parent, device1); fu_engine_add_device(engine, parent); /* set up a different device */ fu_device_set_id(unrelated_device3, "id3"); fu_device_add_vendor_id(unrelated_device3, "USB:FFFF"); fu_device_add_protocol(unrelated_device3, "com.acme"); fu_device_set_name(unrelated_device3, "Foo bar device"); fu_device_set_version_format(unrelated_device3, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(unrelated_device3, "1.5.3"); fu_device_add_vendor_id(unrelated_device3, "FFFF"); fu_device_add_flag(unrelated_device3, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(unrelated_device3, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_guid(unrelated_device3, "3e455c08-352e-4a16-84d3-f04287289fa2"); fu_engine_add_device(engine, unrelated_device3); /* import firmware metainfo */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this fails */ fu_release_set_device(release1, device1); fu_release_set_request(release1, request); ret = fu_release_load(release1, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release1, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_false(ret); g_clear_error(&error); /* set up a sibling device */ fu_device_set_id(device2, "id2"); fu_device_add_vendor_id(device2, "USB:FFFF"); fu_device_add_protocol(device2, "com.acme"); fu_device_set_name(device2, "Secondary firmware"); fu_device_set_version_format(device2, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device2, "4.5.6"); fu_device_add_vendor_id(device2, "FFFF"); fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_guid(device2, "1ff60ab2-3905-06a1-b476-0371f00c9e9b"); fu_device_add_child(parent, device2); fu_engine_add_device(engine, device2); /* check this passes */ fu_release_set_device(release2, device1); fu_release_set_request(release2, request); ret = fu_release_load(release2, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release2, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); /* check this still works, as a child requirement is specified */ fu_device_add_internal_flag(device1, FU_DEVICE_INTERNAL_FLAG_ENFORCE_REQUIRES); ret = fu_engine_requirements_check(engine, release2, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_engine_requirements_other_device_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device1 = fu_device_new(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); const gchar *xml = "" " " " 1ff60ab2-3905-06a1-b476-0371f00c9e9b" " org.freedesktop.fwupd\n" " " " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " ""; /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* set up a dummy device */ fu_device_set_version_format(device1, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device1, "1.2.3"); fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_guid(device1, "12345678-1234-1234-1234-123456789012"); /* set up a different device */ fu_device_set_id(device2, "id2"); fu_device_add_vendor_id(device2, "USB:FFFF"); fu_device_add_protocol(device2, "com.acme"); fu_device_set_name(device2, "Secondary firmware"); fu_device_set_version_format(device2, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device2, "4.5.6"); fu_device_add_vendor_id(device2, "FFFF"); fu_device_add_guid(device2, "1ff60ab2-3905-06a1-b476-0371f00c9e9b"); fu_engine_add_device(engine, device2); /* import firmware metainfo */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this passes */ fu_release_set_device(release, device1); fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_engine_requirements_protocol_check_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; g_autoptr(FuDevice) device1 = fu_device_new(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(GPtrArray) devices = NULL; g_autoptr(FuRelease) release1 = fu_release_new(); g_autoptr(FuRelease) release2 = fu_release_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); gboolean ret; const gchar *xml = "" " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " " " " org.bar" " " ""; /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); fu_device_set_id(device1, "NVME"); fu_device_add_protocol(device1, "com.acme"); fu_device_set_name(device1, "NVME device"); fu_device_add_vendor_id(device1, "ACME"); fu_device_set_version_format(device1, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device1, "1.2.3"); fu_device_add_guid(device1, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_engine_add_device(engine, device1); fu_device_set_id(device2, "UEFI"); fu_device_add_protocol(device2, "org.bar"); fu_device_set_name(device2, "UEFI device"); fu_device_add_vendor_id(device2, "ACME"); fu_device_set_version_format(device2, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device2, "1.2.3"); fu_device_add_guid(device2, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_engine_add_device(engine, device2); /* make sure both devices added */ devices = fu_engine_get_devices(engine, &error); g_assert_no_error(error); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 2); /* import firmware metainfo */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this fails */ fu_release_set_device(release1, device1); fu_release_set_request(release1, request); ret = fu_release_load(release1, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_false(ret); g_clear_error(&error); /* check this passes */ fu_release_set_device(release2, device2); fu_release_set_request(release2, request); ret = fu_release_load(release2, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_engine_requirements_parent_device_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device1 = fu_device_new(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); const gchar *xml = "" " " " " " 12345678-1234-1234-1234-123456789012" " org.freedesktop.fwupd\n" " " " " " 1ff60ab2-3905-06a1-b476-0371f00c9e9b" " " " " " " " " " " " " ""; /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* set up child device */ fu_device_set_id(device2, "child"); fu_device_set_name(device2, "child"); fu_device_set_version_format(device2, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device2, "4.5.6"); fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_guid(device2, "1ff60ab2-3905-06a1-b476-0371f00c9e9b"); /* set up a parent device */ fu_device_set_id(device1, "parent"); fu_device_add_vendor_id(device1, "USB:FFFF"); fu_device_add_protocol(device1, "com.acme"); fu_device_set_name(device1, "parent"); fu_device_set_version_format(device1, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device1, "1.2.3"); fu_device_add_guid(device1, "12345678-1234-1234-1234-123456789012"); fu_device_add_child(device1, device2); fu_engine_add_device(engine, device1); /* import firmware metainfo */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this passes */ fu_release_set_device(release, device2); fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_engine_requirements_child_device_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device1 = fu_device_new(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); const gchar *xml = "" " " " 1ff60ab2-3905-06a1-b476-0371f00c9e9b" " org.freedesktop.fwupd\n" " " " " " 12345678-1234-1234-1234-123456789012" " " " " " " " " " " " " ""; /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* set up a parent device */ fu_device_set_id(device1, "parent"); fu_device_add_vendor_id(device1, "USB:FFFF"); fu_device_add_protocol(device1, "com.acme"); fu_device_set_name(device1, "parent"); fu_device_set_version_format(device1, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device1, "1.2.3"); fu_device_add_guid(device1, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); /* set up child device */ fu_device_set_id(device2, "child"); fu_device_set_name(device2, "child"); fu_device_set_version_format(device2, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device2, "4.5.6"); fu_device_add_guid(device2, "1ff60ab2-3905-06a1-b476-0371f00c9e9b"); fu_device_add_child(device1, device2); fu_engine_add_device(engine, device1); /* import firmware metainfo */ silo = xb_silo_new_from_xml(xml, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "component", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check this passes */ fu_release_set_device(release, device1); fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_engine_device_parent_guid_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; g_autoptr(FuDevice) device1 = fu_device_new(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new(self->ctx); g_autoptr(FuDevice) device3 = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(XbSilo) silo_empty = xb_silo_new(); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* add child */ fu_device_set_id(device1, "child"); fu_device_add_vendor_id(device2, "USB:FFFF"); fu_device_add_protocol(device2, "com.acme"); fu_device_add_instance_id(device1, "child-GUID-1"); fu_device_add_parent_guid(device1, "parent-GUID"); fu_device_convert_instance_ids(device1); fu_engine_add_device(engine, device1); /* parent */ fu_device_set_id(device2, "parent"); fu_device_add_vendor_id(device2, "USB:FFFF"); fu_device_add_protocol(device2, "com.acme"); fu_device_add_instance_id(device2, "parent-GUID"); fu_device_set_vendor(device2, "oem"); fu_device_convert_instance_ids(device2); /* add another child */ fu_device_set_id(device3, "child2"); fu_device_add_instance_id(device3, "child-GUID-2"); fu_device_add_parent_guid(device3, "parent-GUID"); fu_device_convert_instance_ids(device3); fu_device_add_child(device2, device3); /* add two together */ fu_engine_add_device(engine, device2); /* this is normally done by fu_plugin_device_add() */ fu_engine_add_device(engine, device3); /* verify both children were adopted */ g_assert_true(fu_device_get_parent(device3) == device2); g_assert_true(fu_device_get_parent(device1) == device2); g_assert_cmpstr(fu_device_get_vendor(device3), ==, "oem"); /* verify order */ g_assert_cmpint(fu_device_get_order(device1), ==, -1); g_assert_cmpint(fu_device_get_order(device2), ==, 0); g_assert_cmpint(fu_device_get_order(device3), ==, -1); } static void fu_engine_device_parent_id_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; g_autoptr(FuDevice) device1 = fu_device_new(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new(self->ctx); g_autoptr(FuDevice) device3 = fu_device_new(self->ctx); g_autoptr(FuDevice) device4 = fu_device_new(self->ctx); g_autoptr(FuDevice) device5 = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(XbSilo) silo_empty = xb_silo_new(); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* add child */ fu_device_set_id(device1, "child1"); fu_device_set_name(device1, "Child1"); fu_device_set_physical_id(device1, "child-ID1"); fu_device_add_vendor_id(device1, "USB:FFFF"); fu_device_add_protocol(device1, "com.acme"); fu_device_add_instance_id(device1, "child-GUID-1"); fu_device_add_parent_physical_id(device1, "parent-ID-notfound"); fu_device_add_parent_physical_id(device1, "parent-ID"); fu_device_convert_instance_ids(device1); fu_engine_add_device(engine, device1); /* parent */ fu_device_set_id(device2, "parent"); fu_device_set_name(device2, "Parent"); fu_device_set_backend_id(device2, "/sys/devices/foo/bar/baz"); fu_device_set_physical_id(device2, "parent-ID"); fu_device_add_vendor_id(device2, "USB:FFFF"); fu_device_add_protocol(device2, "com.acme"); fu_device_add_instance_id(device2, "parent-GUID"); fu_device_set_vendor(device2, "oem"); fu_device_add_internal_flag(device2, FU_DEVICE_INTERNAL_FLAG_AUTO_PARENT_CHILDREN); fu_device_convert_instance_ids(device2); /* add another child */ fu_device_set_id(device3, "child2"); fu_device_set_name(device3, "Child2"); fu_device_set_physical_id(device3, "child-ID2"); fu_device_add_instance_id(device3, "child-GUID-2"); fu_device_add_parent_physical_id(device3, "parent-ID"); fu_device_convert_instance_ids(device3); fu_device_add_child(device2, device3); /* add two together */ fu_engine_add_device(engine, device2); /* add non-child */ fu_device_set_id(device4, "child4"); fu_device_set_name(device4, "Child4"); fu_device_set_physical_id(device4, "child-ID4"); fu_device_add_vendor_id(device4, "USB:FFFF"); fu_device_add_protocol(device4, "com.acme"); fu_device_add_instance_id(device4, "child-GUID-4"); fu_device_add_parent_physical_id(device4, "parent-ID"); fu_device_convert_instance_ids(device4); fu_engine_add_device(engine, device4); /* this is normally done by fu_plugin_device_add() */ fu_engine_add_device(engine, device4); /* add child with the parent backend ID */ fu_device_set_id(device5, "child5"); fu_device_set_name(device5, "Child5"); fu_device_set_physical_id(device5, "child-ID5"); fu_device_add_vendor_id(device5, "USB:FFFF"); fu_device_add_protocol(device5, "com.acme"); fu_device_add_instance_id(device5, "child-GUID-5"); fu_device_add_parent_backend_id(device5, "/sys/devices/foo/bar/baz"); fu_device_convert_instance_ids(device5); fu_engine_add_device(engine, device5); /* this is normally done by fu_plugin_device_add() */ fu_engine_add_device(engine, device5); /* verify both children were adopted */ g_assert_true(fu_device_get_parent(device3) == device2); g_assert_true(fu_device_get_parent(device4) == device2); g_assert_true(fu_device_get_parent(device5) == device2); g_assert_true(fu_device_get_parent(device1) == device2); g_assert_cmpstr(fu_device_get_vendor(device3), ==, "oem"); } static void fu_engine_partial_hash_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device1 = fu_device_new(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuPlugin) plugin = fu_plugin_new(NULL); g_autoptr(GError) error = NULL; g_autoptr(GError) error_none = NULL; g_autoptr(GError) error_both = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* set up dummy plugin */ fu_plugin_set_name(plugin, "test"); fu_engine_add_plugin(engine, plugin); /* add two dummy devices */ fu_device_set_id(device1, "device1"); fu_device_add_vendor_id(device1, "USB:FFFF"); fu_device_add_protocol(device1, "com.acme"); fu_device_set_plugin(device1, "test"); fu_device_add_guid(device1, "12345678-1234-1234-1234-123456789012"); fu_engine_add_device(engine, device1); fu_device_set_id(device2, "device21"); fu_device_add_vendor_id(device2, "USB:FFFF"); fu_device_add_protocol(device2, "com.acme"); fu_device_set_plugin(device2, "test"); fu_device_set_equivalent_id(device2, "b92f5b7560b84ca005a79f5a15de3c003ce494cf"); fu_device_add_guid(device2, "87654321-1234-1234-1234-123456789012"); fu_engine_add_device(engine, device2); /* match nothing */ ret = fu_engine_unlock(engine, "deadbeef", &error_none); g_assert_error(error_none, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_false(ret); /* match both */ ret = fu_engine_unlock(engine, "9", &error_both); g_assert_error(error_both, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_false(ret); /* match one exactly */ fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_LOCKED); fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_LOCKED); ret = fu_engine_unlock(engine, "934b4162a6daa0b033d649c8d464529cec41d3de", &error); g_assert_no_error(error); g_assert_true(ret); /* match one partially */ fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_LOCKED); fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_LOCKED); ret = fu_engine_unlock(engine, "934b", &error); g_assert_no_error(error); g_assert_true(ret); /* match equivalent ID */ fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_LOCKED); fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_LOCKED); ret = fu_engine_unlock(engine, "b92f", &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_engine_device_unlock_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; FwupdRelease *rel; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; /* load engine to get FuConfig set up */ ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* add the hardcoded 'fwupd' metadata */ filename = g_test_build_filename(G_TEST_DIST, "tests", "metadata.xml", NULL); file = g_file_new_for_path(filename); ret = xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); fu_engine_set_silo(engine, silo); /* add a dummy device */ fu_device_set_id(device, "UEFI-dummy-dev0"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_add_guid(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_LOCKED); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PLAIN); fu_engine_add_device(engine, device); /* ensure the metainfo was matched */ rel = fwupd_device_get_release_default(FWUPD_DEVICE(device)); g_assert_nonnull(rel); g_assert_false(fwupd_release_has_flag(rel, FWUPD_RELEASE_FLAG_TRUSTED_REPORT)); } static void fu_engine_device_md_set_flags_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; const gchar *xml = "\n" "\n" " \n" " org.fwupd.8330a096d9f1af8567c7374cb8403e1ce9cf3163.device\n" " \n" " 2d47f29b-83a2-4f31-a2e8-63474f4d4c2e\n" " \n" " \n" " \n" " \n" " \n" " save-into-backup-remote\n" " \n" " \n" "\n"; /* load engine to get FuConfig set up */ ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* add the XML metadata */ ret = xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); fu_engine_set_silo(engine, silo); /* add a dummy device */ fu_device_set_id(device, "UEFI-dummy-dev0"); fu_device_set_version(device, "0"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_add_guid(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_PLAIN); fu_engine_add_device(engine, device); /* check the flag got set */ g_assert_true( fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_SAVE_INTO_BACKUP_REMOTE)); } static void fu_engine_require_hwid_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); g_autoptr(XbSilo) silo = NULL; /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* load engine to get FuConfig set up */ ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* get generated file as a blob */ filename = g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "hwid-1.2.3.cab", NULL); blob_cab = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(blob_cab); silo = fu_engine_get_silo_from_blob(engine, blob_cab, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* add a dummy device */ fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.2"); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_engine_add_device(engine, device); /* get component */ component = xb_silo_query_first(silo, "components/component/id[text()='com.hughski.test.firmware']/..", &error); g_assert_no_error(error); g_assert_nonnull(component); /* check requirements */ fu_release_set_device(release, device); fu_release_set_request(release, request); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_requirements_check(engine, release, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_nonnull(error); g_assert_cmpstr(error->message, ==, "no HWIDs matched 9342d47a-1bab-5709-9869-c840b2eac501"); g_assert_false(ret); } static void fu_engine_get_details_added_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; FuDevice *device_tmp; FwupdRelease *release; gboolean ret; g_autofree gchar *checksum_sha256 = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* load engine to get FuConfig set up */ ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* add a dummy device */ fu_device_set_id(device, "test_device"); fu_device_set_name(device, "test device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.2"); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_engine_add_device(engine, device); /* get details */ filename = g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "hwid-1.2.3.cab", NULL); blob_cab = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(blob_cab); checksum_sha256 = g_compute_checksum_for_bytes(G_CHECKSUM_SHA256, blob_cab); devices = fu_engine_get_details_for_bytes(engine, request, blob_cab, &error); g_assert_no_error(error); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 1); device_tmp = g_ptr_array_index(devices, 0); g_assert_cmpstr(fu_device_get_name(device_tmp), ==, "test device"); release = fu_device_get_release_default(device_tmp); g_assert_nonnull(release); g_assert_cmpstr(fwupd_release_get_version(release), ==, "1.2.3"); g_assert_true(fwupd_release_has_checksum(release, checksum_sha256)); } static void fu_engine_get_details_missing_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; FuDevice *device_tmp; FwupdRelease *release; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* load engine to get FuConfig set up */ ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* get details */ filename = g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "hwid-1.2.3.cab", NULL); blob_cab = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(blob_cab); devices = fu_engine_get_details_for_bytes(engine, request, blob_cab, &error); g_assert_no_error(error); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 1); device_tmp = g_ptr_array_index(devices, 0); g_assert_cmpstr(fu_device_get_name(device_tmp), ==, NULL); release = fu_device_get_release_default(device_tmp); g_assert_nonnull(release); g_assert_cmpstr(fwupd_release_get_version(release), ==, "1.2.3"); } static void fu_engine_downgrade_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; FwupdRelease *rel; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices_pre = NULL; g_autoptr(GPtrArray) releases_dg = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(GPtrArray) releases_up = NULL; g_autoptr(GPtrArray) releases_up2 = NULL; g_autoptr(GPtrArray) remotes = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); /* ensure empty tree */ fu_self_test_mkroot(); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* write a broken file */ ret = g_file_set_contents("/tmp/fwupd-self-test/broken.xml.gz", "this is not a valid", -1, &error); g_assert_no_error(error); g_assert_true(ret); /* write the main file */ ret = g_file_set_contents( "/tmp/fwupd-self-test/stable.xml", "" " " " test" " Test Device" " " " aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" " " " " " " " 123" " 456" " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdead1111" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " 123" " 456" " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdead2222" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " " "", -1, &error); g_assert_no_error(error); g_assert_true(ret); /* write the extra file */ ret = g_file_set_contents( "/tmp/fwupd-self-test/testing.xml", "" " " " test" " Test Device" " " " aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" " " " " " " " 123" " 456" " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdead3333" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " 123" " 456" " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdead4444" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " " "", -1, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); g_test_assert_expected_messages(); /* return all the remotes, even the broken one */ remotes = fu_engine_get_remotes(engine, &error); g_assert_no_error(error); g_assert_nonnull(remotes); g_assert_cmpint(remotes->len, ==, 7); /* ensure there are no devices already */ devices_pre = fu_engine_get_devices(engine, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO); g_assert_null(devices_pre); g_clear_error(&error); /* add a device so we can get upgrades and downgrades */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.3"); fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_set_name(device, "Test Device"); fu_device_add_guid(device, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_engine_add_device(engine, device); devices = fu_engine_get_devices(engine, &error); g_assert_no_error(error); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 1); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REGISTERED)); /* get the releases for one device */ releases = fu_engine_get_releases(engine, request, fu_device_get_id(device), &error); g_assert_no_error(error); g_assert_nonnull(releases); g_assert_cmpint(releases->len, ==, 4); /* no upgrades, as no firmware is approved */ releases_up = fu_engine_get_upgrades(engine, request, fu_device_get_id(device), &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO); g_assert_null(releases_up); g_clear_error(&error); /* retry with approved firmware set */ fu_engine_add_approved_firmware(engine, "deadbeefdeadbeefdeadbeefdead1111"); fu_engine_add_approved_firmware(engine, "deadbeefdeadbeefdeadbeefdead2222"); fu_engine_add_approved_firmware(engine, "deadbeefdeadbeefdeadbeefdead3333"); fu_engine_add_approved_firmware(engine, "deadbeefdeadbeefdeadbeefdead4444"); fu_engine_add_approved_firmware(engine, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); /* upgrades */ releases_up = fu_engine_get_upgrades(engine, request, fu_device_get_id(device), &error); g_assert_no_error(error); g_assert_nonnull(releases_up); g_assert_cmpint(releases_up->len, ==, 2); /* ensure the list is sorted */ rel = FWUPD_RELEASE(g_ptr_array_index(releases_up, 0)); g_assert_cmpstr(fwupd_release_get_version(rel), ==, "1.2.5"); rel = FWUPD_RELEASE(g_ptr_array_index(releases_up, 1)); g_assert_cmpstr(fwupd_release_get_version(rel), ==, "1.2.4"); /* downgrades */ releases_dg = fu_engine_get_downgrades(engine, request, fu_device_get_id(device), &error); g_assert_no_error(error); g_assert_nonnull(releases_dg); g_assert_cmpint(releases_dg->len, ==, 1); rel = FWUPD_RELEASE(g_ptr_array_index(releases_dg, 0)); g_assert_cmpstr(fwupd_release_get_version(rel), ==, "1.2.2"); /* enforce that updates have to be explicit */ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_ONLY_EXPLICIT_UPDATES); releases_up2 = fu_engine_get_upgrades(engine, request, fu_device_get_id(device), &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO); g_assert_null(releases_up2); } static void fu_engine_md_verfmt_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; FwupdRemote *remote; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); /* ensure empty tree */ fu_self_test_mkroot(); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* write the main file */ ret = g_file_set_contents( "/tmp/fwupd-self-test/stable.xml", "" " " " test" " Test Device" " computer" " ACME" " " " aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" " " " " " X-GraphicsTablet" " " " " " " " 123" " 456" " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdeadbeef" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " " " triplet" " signed" " host-cpu,needs-shutdown" " " " " "", -1, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); g_test_assert_expected_messages(); /* pretend this has a signature */ remote = fu_engine_get_remote_by_id(engine, "stable", &error); g_assert_no_error(error); g_assert_nonnull(remote); fwupd_remote_set_keyring_kind(remote, FWUPD_KEYRING_KIND_JCAT); /* add a device with no defined version format */ fu_device_set_version(device, "16908291"); fu_device_set_version_raw(device, 0x01020003); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_FLAGS); fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_add_guid(device, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_engine_add_device(engine, device); /* ensure the version format was set from the metadata */ g_assert_cmpint(fu_device_get_version_format(device), ==, FWUPD_VERSION_FORMAT_TRIPLET); g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.3"); g_assert_cmpstr(fu_device_get_name(device), ==, "Graphics Tablet"); g_assert_cmpstr(fu_device_get_vendor(device), ==, "ACME"); g_assert_true(fu_device_has_icon(device, "computer")); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD)); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)); g_assert_true(fu_device_has_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_HOST_CPU)); /* ensure the device was added */ devices = fu_engine_get_devices(engine, &error); g_assert_no_error(error); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 1); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REGISTERED)); /* ensure the releases are set */ releases = fu_engine_get_releases(engine, request, fu_device_get_id(device), &error); g_assert_no_error(error); g_assert_nonnull(releases); g_assert_cmpint(releases->len, ==, 1); } static void fu_engine_install_duration_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; FwupdRelease *rel; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); /* ensure empty tree */ fu_self_test_mkroot(); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* write the main file */ ret = g_file_set_contents( "/tmp/fwupd-self-test/stable.xml", "" " " " test" " " " aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" " " " " " " " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdeadbeef" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " " "", -1, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* add a device so we can get the install duration */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.3"); fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_add_guid(device, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); fu_device_set_install_duration(device, 999); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_engine_add_device(engine, device); devices = fu_engine_get_devices(engine, &error); g_assert_no_error(error); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 1); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)); /* check the release install duration */ releases = fu_engine_get_releases(engine, request, fu_device_get_id(device), &error); g_assert_no_error(error); g_assert_nonnull(releases); g_assert_cmpint(releases->len, ==, 1); rel = FWUPD_RELEASE(g_ptr_array_index(releases, 0)); g_assert_cmpint(fwupd_release_get_install_duration(rel), ==, 120); } static void fu_engine_release_dedupe_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); /* ensure empty tree */ fu_self_test_mkroot(); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* write the main file */ ret = g_file_set_contents( "/tmp/fwupd-self-test/stable.xml", "" " " " test" " " " aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" " " " " " " " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdeadbeef" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " https://test.org/foo.cab" " deadbeefdeadbeefdeadbeefdeadbeef" " deadbeefdeadbeefdeadbeefdeadbeef" " " " " " " "", -1, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* add a device so we can get the install duration */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.3"); fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_add_guid(device, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); fu_device_set_install_duration(device, 999); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_engine_add_device(engine, device); devices = fu_engine_get_devices(engine, &error); g_assert_no_error(error); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 1); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_SUPPORTED)); /* check the release install duration */ releases = fu_engine_get_releases(engine, request, fu_device_get_id(device), &error); g_assert_no_error(error); g_assert_nonnull(releases); g_assert_cmpint(releases->len, ==, 1); } static void fu_engine_history_modify_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuHistory) history = fu_history_new(); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error = NULL; #ifndef HAVE_SQLITE g_test_skip("no sqlite support"); return; #endif /* add a new entry */ fu_device_set_id(device, "foobarbaz"); fu_history_remove_device(history, device, NULL); ret = fu_history_add_device(history, device, FWUPD_RELEASE(release), &error); g_assert_no_error(error); g_assert_true(ret); /* try to modify something that does exist */ ret = fu_history_modify_device(history, device, &error); g_assert_no_error(error); g_assert_true(ret); /* does not exist */ fu_device_set_id(device, "DOES-NOT-EXIST"); ret = fu_history_modify_device(history, device, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); } static void fu_engine_history_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autofree gchar *checksum = NULL; g_autofree gchar *device_str_expected = NULL; g_autofree gchar *device_str = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device2 = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuHistory) history = NULL; g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(FwupdDevice) device3 = NULL; g_autoptr(FwupdDevice) device4 = NULL; g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); g_autoptr(XbSilo) silo = NULL; /* ensure empty tree */ fu_self_test_mkroot(); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* set up dummy plugin */ ret = fu_plugin_reset_config_values(self->plugin, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_set_config_value(self->plugin, "AnotherWriteRequired", "true", &error); g_assert_no_error(error); g_assert_true(ret); fu_engine_add_plugin(engine, self->plugin); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* add a device so we can get upgrade it */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.2"); fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_set_name(device, "Test Device"); fu_device_set_plugin(device, "test"); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_add_checksum(device, "0123456789abcdef0123456789abcdef01234567"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_created(device, 1515338000); fu_engine_add_device(engine, device); devices = fu_engine_get_devices(engine, &error); g_assert_no_error(error); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 1); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REGISTERED)); filename = g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "noreqs-1.2.3.cab", NULL); blob_cab = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(blob_cab); silo = fu_engine_get_silo_from_blob(engine, blob_cab, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* get component */ component = xb_silo_query_first(silo, "components/component/id[text()='com.hughski.test.firmware']/..", &error); g_assert_no_error(error); g_assert_nonnull(component); /* set the counter */ fu_device_set_metadata_integer(device, "nr-update", 0); /* install it */ fu_release_set_device(release, device); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_install_release(engine, release, blob_cab, progress, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); /* check the write was done more than once */ g_assert_cmpint(fu_device_get_metadata_integer(device, "nr-update"), ==, 2); /* check the history database */ history = fu_history_new(); device2 = fu_history_get_device_by_id(history, fu_device_get_id(device), &error); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip("no sqlite support"); return; } g_assert_no_error(error); g_assert_nonnull(device2); g_assert_cmpint(fu_device_get_update_state(device2), ==, FWUPD_UPDATE_STATE_SUCCESS); g_assert_cmpstr(fu_device_get_update_error(device2), ==, NULL); fu_device_set_modified(device2, 1514338000); g_hash_table_remove_all(fwupd_release_get_metadata(fu_device_get_release_default(device2))); device_str = fu_device_to_string(device2); checksum = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, blob_cab); device_str_expected = g_strdup_printf("FuDevice:\n" " DeviceId: 894e8c17a29428b09d10cd90d1db74ea76fbcfe8\n" " Name: Test Device\n" " Guid: 12345678-1234-1234-1234-123456789012\n" " Plugin: test\n" " Flags: historical|updatable-hidden|unsigned-payload\n" " Version: 1.2.2\n" " VersionFormat: triplet\n" " Created: 2018-01-07\n" " Modified: 2017-12-27\n" " UpdateState: success\n" " \n" " [Release]\n" " AppstreamId: com.hughski.test.firmware\n" " Version: 1.2.3\n" " Checksum: SHA1(%s)\n" " Flags: none\n" " AcquiesceDelay: 50\n", checksum); ret = fu_test_compare_lines(device_str, device_str_expected, &error); g_assert_no_error(error); g_assert_true(ret); /* GetResults() */ device3 = fu_engine_get_results(engine, FWUPD_DEVICE_ID_ANY, &error); g_assert_no_error(error); g_assert_nonnull(device3); g_assert_cmpstr(fu_device_get_id(device3), ==, "894e8c17a29428b09d10cd90d1db74ea76fbcfe8"); g_assert_cmpint(fu_device_get_update_state(device3), ==, FWUPD_UPDATE_STATE_SUCCESS); g_assert_cmpstr(fu_device_get_update_error(device3), ==, NULL); /* ClearResults() */ ret = fu_engine_clear_results(engine, FWUPD_DEVICE_ID_ANY, &error); g_assert_no_error(error); g_assert_true(ret); /* GetResults() */ device4 = fu_engine_get_results(engine, FWUPD_DEVICE_ID_ANY, &error); g_assert_null(device4); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO); } static void fu_engine_history_verfmt_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device = g_object_new(FU_TYPE_DPAUX_DEVICE, "context", self->ctx, NULL); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* set up dummy plugin */ fu_engine_add_plugin(engine, self->plugin); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* absorb version format from the database */ fu_device_set_version_raw(device, 65563); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_NUMBER); fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_set_plugin(device, "test"); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_add_checksum(device, "0123456789abcdef0123456789abcdef01234567"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT); fu_device_set_created(device, 1515338000); fu_engine_add_device(engine, device); g_assert_cmpint(fu_device_get_version_format(device), ==, FWUPD_VERSION_FORMAT_TRIPLET); g_assert_cmpstr(fu_device_get_version(device), ==, "0.1.27"); } static void fu_engine_multiple_rels_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); g_autoptr(XbSilo) silo = NULL; g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(GPtrArray) releases = NULL; g_autoptr(GPtrArray) rels = NULL; g_autoptr(XbQuery) query = NULL; #ifndef HAVE_LIBARCHIVE g_test_skip("no libarchive support"); return; #endif /* ensure empty tree */ fu_self_test_mkroot(); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* set up dummy plugin */ ret = fu_plugin_reset_config_values(self->plugin, &error); g_assert_no_error(error); g_assert_true(ret); fu_engine_add_plugin(engine, self->plugin); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* add a device so we can get upgrade it */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.2"); fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_set_name(device, "Test Device"); fu_device_set_plugin(device, "test"); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_add_checksum(device, "0123456789abcdef0123456789abcdef01234567"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES); fu_device_set_created(device, 1515338000); fu_engine_add_device(engine, device); filename = g_test_build_filename(G_TEST_BUILT, "tests", "multiple-rels", "multiple-rels-1.2.4.cab", NULL); blob_cab = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(blob_cab); silo = fu_engine_get_silo_from_blob(engine, blob_cab, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* get component */ component = xb_silo_query_first(silo, "components/component/id[text()='com.hughski.test.firmware']/..", &error); g_assert_no_error(error); g_assert_nonnull(component); /* set up counter */ fu_device_set_metadata_integer(device, "nr-update", 0); /* get all */ query = xb_query_new_full(xb_node_get_silo(component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, &error); g_assert_no_error(error); g_assert_nonnull(query); rels = xb_node_query_full(component, query, &error); g_assert_no_error(error); g_assert_nonnull(rels); releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < rels->len; i++) { XbNode *rel = g_ptr_array_index(rels, i); g_autoptr(FuRelease) release = fu_release_new(); fu_release_set_device(release, device); ret = fu_release_load(release, component, rel, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); g_ptr_array_add(releases, g_object_ref(release)); } /* install them */ fu_progress_reset(progress); ret = fu_engine_install_releases(engine, request, releases, blob_cab, progress, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); /* check we did 1.2.2 -> 1.2.3 -> 1.2.4 */ g_assert_cmpint(fu_device_get_metadata_integer(device, "nr-update"), ==, 2); g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.4"); /* reset the config back to defaults */ ret = fu_engine_reset_config(engine, "test", &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_engine_history_inherit(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autofree gchar *filename = NULL; g_autofree gchar *localstatedir = NULL; g_autofree gchar *history_db = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); g_autoptr(XbSilo) silo = NULL; #ifndef HAVE_SQLITE g_test_skip("no sqlite support"); return; #endif /* delete history */ localstatedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); history_db = g_build_filename(localstatedir, "pending.db", NULL); (void)g_unlink(history_db); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* set up dummy plugin */ ret = fu_plugin_reset_config_values(self->plugin, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_set_config_value(self->plugin, "NeedsActivation", "true", &error); g_assert_no_error(error); g_assert_true(ret); fu_engine_add_plugin(engine, self->plugin); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* add a device so we can get upgrade it */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.2"); fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_set_name(device, "Test Device"); fu_device_set_plugin(device, "test"); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_created(device, 1515338000); fu_engine_add_device(engine, device); devices = fu_engine_get_devices(engine, &error); g_assert_no_error(error); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 1); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REGISTERED)); filename = g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "noreqs-1.2.3.cab", NULL); blob_cab = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(blob_cab); silo = fu_engine_get_silo_from_blob(engine, blob_cab, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* get component */ component = xb_silo_query_first(silo, "components/component/id[text()='com.hughski.test.firmware']/..", &error); g_assert_no_error(error); g_assert_nonnull(component); /* install it */ fu_release_set_device(release, device); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_install_release(engine, release, blob_cab, progress, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); /* check the device requires an activation */ g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)); g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.2"); /* activate the device */ fu_progress_reset(progress); ret = fu_engine_activate(engine, fu_device_get_id(device), progress, &error); g_assert_no_error(error); g_assert_true(ret); /* check the device no longer requires an activation */ g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)); g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.3"); /* emulate getting the flag for a fresh boot on old firmware */ fu_progress_reset(progress); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.2"); ret = fu_engine_install_release(engine, release, blob_cab, progress, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); g_object_unref(engine); g_object_unref(device); engine = fu_engine_new(self->ctx); fu_engine_set_silo(engine, silo_empty); fu_engine_add_plugin(engine, self->plugin); device = fu_device_new(self->ctx); fu_device_add_internal_flag(device, FU_DEVICE_INTERNAL_FLAG_INHERIT_ACTIVATION); fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_set_name(device, "Test Device"); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.2"); fu_engine_add_device(engine, device); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)); /* emulate not getting the flag */ g_object_unref(engine); g_object_unref(device); engine = fu_engine_new(self->ctx); fu_engine_set_silo(engine, silo_empty); fu_engine_add_plugin(engine, self->plugin); device = fu_device_new(self->ctx); fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_set_name(device, "Test Device"); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.2"); fu_engine_add_device(engine, device); g_assert_false(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)); } static void fu_engine_install_needs_reboot(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); g_autoptr(XbSilo) silo = NULL; /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* set up dummy plugin */ ret = fu_plugin_reset_config_values(self->plugin, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_set_config_value(self->plugin, "NeedsReboot", "true", &error); g_assert_no_error(error); g_assert_true(ret); fu_engine_add_plugin(engine, self->plugin); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NONE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* add a device so we can get upgrade it */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.2"); fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_set_name(device, "Test Device"); fu_device_set_plugin(device, "test"); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_created(device, 1515338000); fu_engine_add_device(engine, device); devices = fu_engine_get_devices(engine, &error); g_assert_no_error(error); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 1); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REGISTERED)); filename = g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "noreqs-1.2.3.cab", NULL); blob_cab = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(blob_cab); silo = fu_engine_get_silo_from_blob(engine, blob_cab, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* get component */ component = xb_silo_query_first(silo, "components/component/id[text()='com.hughski.test.firmware']/..", &error); g_assert_no_error(error); g_assert_nonnull(component); /* install it */ fu_release_set_device(release, device); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_install_release(engine, release, blob_cab, progress, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); /* check the device requires reboot */ g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)); g_assert_cmpint(fu_device_get_update_state(device), ==, FWUPD_UPDATE_STATE_NEEDS_REBOOT); g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.2"); } typedef struct { guint request_cnt; FwupdStatus last_status; } FuTestRequestHelper; static void _engine_status_changed_cb(FuProgress *progress, FwupdStatus status, gpointer user_data) { FuTestRequestHelper *helper = (FuTestRequestHelper *)user_data; g_debug("status now %s", fwupd_status_to_string(status)); helper->last_status = status; } static void _engine_request_cb(FuEngine *engine, FwupdRequest *request, gpointer user_data) { FuTestRequestHelper *helper = (FuTestRequestHelper *)user_data; g_assert_cmpint(fwupd_request_get_kind(request), ==, FWUPD_REQUEST_KIND_IMMEDIATE); g_assert_cmpstr(fwupd_request_get_id(request), ==, FWUPD_REQUEST_ID_REMOVE_REPLUG); g_assert_true(fwupd_request_has_flag(request, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE)); g_assert_nonnull(fwupd_request_get_message(request)); g_assert_cmpint(helper->last_status, ==, FWUPD_STATUS_WAITING_FOR_USER); helper->request_cnt++; } static void fu_engine_install_request(gconstpointer user_data) { FuTestRequestHelper helper = {.request_cnt = 0, .last_status = FWUPD_STATUS_UNKNOWN}; FuTest *self = (FuTest *)user_data; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); g_autoptr(XbSilo) silo = NULL; /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* set up dummy plugin */ ret = fu_plugin_reset_config_values(self->plugin, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_set_config_value(self->plugin, "RequestSupported", "true", &error); g_assert_no_error(error); g_assert_true(ret); fu_engine_add_plugin(engine, self->plugin); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NONE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* add a device so we can get upgrade it */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.2"); fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_set_name(device, "Test Device"); fu_device_set_plugin(device, "test"); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_request_flag(device, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE); fu_device_set_created(device, 1515338000); fu_engine_add_device(engine, device); devices = fu_engine_get_devices(engine, &error); g_assert_no_error(error); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 1); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REGISTERED)); filename = g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "noreqs-1.2.3.cab", NULL); blob_cab = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(blob_cab); silo = fu_engine_get_silo_from_blob(engine, blob_cab, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* get component */ component = xb_silo_query_first(silo, "components/component/id[text()='com.hughski.test.firmware']/..", &error); g_assert_no_error(error); g_assert_nonnull(component); /* install it */ fu_release_set_device(release, device); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); g_signal_connect(FU_ENGINE(engine), "device-request", G_CALLBACK(_engine_request_cb), &helper); g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(_engine_status_changed_cb), &helper); ret = fu_engine_install_release(engine, release, blob_cab, progress, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(helper.request_cnt, ==, 1); g_assert_cmpint(helper.last_status, ==, FWUPD_STATUS_DEVICE_BUSY); } static void fu_engine_history_error_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autofree gchar *checksum = NULL; g_autofree gchar *device_str_expected = NULL; g_autofree gchar *device_str = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device2 = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuHistory) history = NULL; g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GError) error2 = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); g_autoptr(XbSilo) silo = NULL; /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* set up dummy plugin */ ret = fu_plugin_set_config_value(self->plugin, "WriteSupported", "false", &error); g_assert_no_error(error); g_assert_true(ret); fu_engine_add_plugin(engine, self->plugin); ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* add a device so we can get upgrade it */ fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "1.2.2"); fu_device_set_id(device, "test_device"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_protocol(device, "com.acme"); fu_device_set_name(device, "Test Device"); fu_device_set_plugin(device, "test"); fu_device_add_guid(device, "12345678-1234-1234-1234-123456789012"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_set_created(device, 1515338000); fu_engine_add_device(engine, device); devices = fu_engine_get_devices(engine, &error); g_assert_no_error(error); g_assert_nonnull(devices); g_assert_cmpint(devices->len, ==, 1); g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_REGISTERED)); /* install the wrong thing */ filename = g_test_build_filename(G_TEST_BUILT, "tests", "missing-hwid", "noreqs-1.2.3.cab", NULL); blob_cab = fu_bytes_get_contents(filename, &error); g_assert_no_error(error); g_assert_nonnull(blob_cab); silo = fu_engine_get_silo_from_blob(engine, blob_cab, &error); g_assert_no_error(error); g_assert_nonnull(silo); component = xb_silo_query_first(silo, "components/component/id[text()='com.hughski.test.firmware']/..", &error); g_assert_no_error(error); g_assert_nonnull(component); fu_release_set_device(release, device); ret = fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_engine_install_release(engine, release, blob_cab, progress, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_assert_cmpstr(error->message, ==, "device was not in supported mode"); g_assert_false(ret); /* check the history database */ history = fu_history_new(); device2 = fu_history_get_device_by_id(history, fu_device_get_id(device), &error2); if (g_error_matches(error2, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip("no sqlite support"); return; } g_assert_no_error(error2); g_assert_nonnull(device2); g_assert_cmpint(fu_device_get_update_state(device2), ==, FWUPD_UPDATE_STATE_FAILED); g_assert_cmpstr(fu_device_get_update_error(device2), ==, error->message); g_clear_error(&error); fu_device_set_modified(device2, 1514338000); g_hash_table_remove_all(fwupd_release_get_metadata(fu_device_get_release_default(device2))); device_str = fu_device_to_string(device2); checksum = g_compute_checksum_for_bytes(G_CHECKSUM_SHA1, blob_cab); device_str_expected = g_strdup_printf("FuDevice:\n" " DeviceId: 894e8c17a29428b09d10cd90d1db74ea76fbcfe8\n" " Name: Test Device\n" " Guid: 12345678-1234-1234-1234-123456789012\n" " Plugin: test\n" " Flags: updatable|historical|unsigned-payload\n" " Version: 1.2.2\n" " VersionFormat: triplet\n" " Created: 2018-01-07\n" " Modified: 2017-12-27\n" " UpdateState: failed\n" " UpdateError: device was not in supported mode\n" " \n" " [Release]\n" " AppstreamId: com.hughski.test.firmware\n" " Version: 1.2.3\n" " Checksum: SHA1(%s)\n" " Flags: none\n" " AcquiesceDelay: 50\n", checksum); ret = fu_test_compare_lines(device_str, device_str_expected, &error); g_assert_no_error(error); g_assert_true(ret); } static void _device_list_count_cb(FuDeviceList *device_list, FuDevice *device, gpointer user_data) { guint *cnt = (guint *)user_data; (*cnt)++; } static void fu_device_list_no_auto_remove_children_func(gconstpointer user_data) { g_autoptr(FuDevice) child = fu_device_new(NULL); g_autoptr(FuDevice) parent = fu_device_new(NULL); g_autoptr(FuDeviceList) device_list = fu_device_list_new(); g_autoptr(GPtrArray) active1 = NULL; g_autoptr(GPtrArray) active2 = NULL; g_autoptr(GPtrArray) active3 = NULL; /* normal behavior, remove child with parent */ fu_device_set_id(parent, "parent"); fu_device_set_id(child, "child"); fu_device_add_child(parent, child); fu_device_list_add(device_list, parent); fu_device_list_add(device_list, child); fu_device_list_remove(device_list, parent); active1 = fu_device_list_get_active(device_list); g_assert_cmpint(active1->len, ==, 0); /* new-style behavior, do not remove child */ fu_device_add_internal_flag(parent, FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE_CHILDREN); fu_device_list_add(device_list, parent); fu_device_list_add(device_list, child); fu_device_list_remove(device_list, parent); active2 = fu_device_list_get_active(device_list); g_assert_cmpint(active2->len, ==, 1); fu_device_list_remove(device_list, child); active3 = fu_device_list_get_active(device_list); g_assert_cmpint(active3->len, ==, 0); } static void fu_device_list_delay_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; g_autoptr(FuDevice) device1 = fu_device_new(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new(self->ctx); g_autoptr(FuDeviceList) device_list = fu_device_list_new(); guint added_cnt = 0; guint changed_cnt = 0; guint removed_cnt = 0; g_signal_connect(FU_DEVICE_LIST(device_list), "added", G_CALLBACK(_device_list_count_cb), &added_cnt); g_signal_connect(FU_DEVICE_LIST(device_list), "removed", G_CALLBACK(_device_list_count_cb), &removed_cnt); g_signal_connect(FU_DEVICE_LIST(device_list), "changed", G_CALLBACK(_device_list_count_cb), &changed_cnt); /* add one device */ fu_device_set_id(device1, "device1"); fu_device_add_instance_id(device1, "foobar"); fu_device_set_remove_delay(device1, 100); fu_device_convert_instance_ids(device1); fu_device_list_add(device_list, device1); g_assert_cmpint(added_cnt, ==, 1); g_assert_cmpint(removed_cnt, ==, 0); g_assert_cmpint(changed_cnt, ==, 0); /* add the same device again */ fu_device_list_add(device_list, device1); g_assert_cmpint(added_cnt, ==, 1); g_assert_cmpint(removed_cnt, ==, 0); g_assert_cmpint(changed_cnt, ==, 1); /* add a device with the same ID */ fu_device_set_id(device2, "device1"); fu_device_list_add(device_list, device2); fu_device_set_remove_delay(device2, 100); g_assert_cmpint(added_cnt, ==, 1); g_assert_cmpint(removed_cnt, ==, 0); g_assert_cmpint(changed_cnt, ==, 2); /* spin a bit */ fu_test_loop_run_with_timeout(10); fu_test_loop_quit(); /* verify only a changed event was generated */ added_cnt = removed_cnt = changed_cnt = 0; fu_device_list_remove(device_list, device1); fu_device_list_add(device_list, device1); g_assert_cmpint(added_cnt, ==, 0); g_assert_cmpint(removed_cnt, ==, 0); g_assert_cmpint(changed_cnt, ==, 1); } typedef struct { FuDevice *device_new; FuDevice *device_old; FuDeviceList *device_list; } FuDeviceListReplugHelper; static gboolean fu_device_list_remove_cb(gpointer user_data) { FuDeviceListReplugHelper *helper = (FuDeviceListReplugHelper *)user_data; fu_device_list_remove(helper->device_list, helper->device_old); return FALSE; } static gboolean fu_device_list_add_cb(gpointer user_data) { FuDeviceListReplugHelper *helper = (FuDeviceListReplugHelper *)user_data; fu_device_list_add(helper->device_list, helper->device_new); return FALSE; } static void fu_device_list_replug_auto_func(gconstpointer user_data) { gboolean ret; g_autoptr(FuDevice) device1 = fu_device_new(NULL); g_autoptr(FuDevice) device2 = fu_device_new(NULL); g_autoptr(FuDevice) parent = fu_device_new(NULL); g_autoptr(FuDeviceList) device_list = fu_device_list_new(); g_autoptr(GError) error = NULL; FuDeviceListReplugHelper helper; /* parent */ fu_device_set_id(parent, "parent"); /* fake child devices */ fu_device_set_id(device1, "device1"); fu_device_add_internal_flag(device1, FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_set_physical_id(device1, "ID"); fu_device_set_plugin(device1, "self-test"); fu_device_set_remove_delay(device1, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); fu_device_add_child(parent, device1); fu_device_set_id(device2, "device2"); fu_device_add_internal_flag(device2, FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_set_physical_id(device2, "ID"); /* matches */ fu_device_set_plugin(device2, "self-test"); fu_device_set_remove_delay(device2, FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE); /* not yet added */ ret = fu_device_list_wait_for_replug(device_list, &error); g_assert_no_error(error); g_assert_true(ret); /* add device */ fu_device_list_add(device_list, device1); /* not waiting */ ret = fu_device_list_wait_for_replug(device_list, &error); g_assert_no_error(error); g_assert_true(ret); /* waiting */ helper.device_old = device1; helper.device_new = device2; helper.device_list = device_list; g_timeout_add(100, fu_device_list_remove_cb, &helper); g_timeout_add(200, fu_device_list_add_cb, &helper); fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); ret = fu_device_list_wait_for_replug(device_list, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_false(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)); /* check device2 now has parent too */ g_assert_true(fu_device_get_parent(device2) == parent); /* waiting, failed */ fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); ret = fu_device_list_wait_for_replug(device_list, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_false(ret); g_assert_false(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)); } static void fu_device_list_replug_user_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; g_autoptr(FuDevice) device1 = fu_device_new(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new(self->ctx); g_autoptr(FuDeviceList) device_list = fu_device_list_new(); g_autoptr(GError) error = NULL; FuDeviceListReplugHelper helper; /* fake devices */ fu_device_set_id(device1, "device1"); fu_device_set_name(device1, "device1"); fu_device_add_internal_flag(device1, FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_instance_id(device1, "foo"); fu_device_add_instance_id(device1, "bar"); fu_device_set_plugin(device1, "self-test"); fu_device_set_remove_delay(device1, FU_DEVICE_REMOVE_DELAY_USER_REPLUG); fu_device_convert_instance_ids(device1); fu_device_set_id(device2, "device2"); fu_device_set_name(device2, "device2"); fu_device_add_internal_flag(device2, FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_instance_id(device2, "baz"); fu_device_add_instance_id(device2, "bar"); /* matches */ fu_device_set_plugin(device2, "self-test"); fu_device_set_remove_delay(device2, FU_DEVICE_REMOVE_DELAY_USER_REPLUG); fu_device_convert_instance_ids(device2); /* not yet added */ ret = fu_device_list_wait_for_replug(device_list, &error); g_assert_no_error(error); g_assert_true(ret); /* add device */ fu_device_list_add(device_list, device1); /* add duplicate */ fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); fu_device_list_add(device_list, device1); g_assert_false(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)); /* not waiting */ ret = fu_device_list_wait_for_replug(device_list, &error); g_assert_no_error(error); g_assert_true(ret); /* waiting */ helper.device_old = device1; helper.device_new = device2; helper.device_list = device_list; g_timeout_add(100, fu_device_list_remove_cb, &helper); g_timeout_add(200, fu_device_list_add_cb, &helper); fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); ret = fu_device_list_wait_for_replug(device_list, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_false(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)); /* should not be possible, but here we are */ fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); fu_device_list_add(device_list, device1); g_assert_false(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)); g_assert_false(fu_device_has_flag(device2, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)); /* add back the old device */ fu_device_add_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); fu_device_add_flag(device2, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); fu_device_list_remove(device_list, device2); fu_device_list_add(device_list, device1); g_assert_false(fu_device_has_flag(device1, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)); g_assert_false(fu_device_has_flag(device2, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)); } static void fu_device_list_compatible_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; g_autoptr(FuDevice) device1 = fu_device_new(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new(self->ctx); g_autoptr(FuDevice) device_old = NULL; g_autoptr(FuDeviceList) device_list = fu_device_list_new(); g_autoptr(GPtrArray) devices_all = NULL; g_autoptr(GPtrArray) devices_active = NULL; FuDevice *device; guint added_cnt = 0; guint changed_cnt = 0; guint removed_cnt = 0; g_signal_connect(FU_DEVICE_LIST(device_list), "added", G_CALLBACK(_device_list_count_cb), &added_cnt); g_signal_connect(FU_DEVICE_LIST(device_list), "removed", G_CALLBACK(_device_list_count_cb), &removed_cnt); g_signal_connect(FU_DEVICE_LIST(device_list), "changed", G_CALLBACK(_device_list_count_cb), &changed_cnt); /* add one device in runtime mode */ fu_device_set_id(device1, "device1"); fu_device_set_plugin(device1, "plugin-for-runtime"); fu_device_add_vendor_id(device1, "USB:0x20A0"); fu_device_set_version_format(device1, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device1, "1.2.3"); fu_device_add_internal_flag(device1, FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_add_instance_id(device1, "foobar"); fu_device_add_instance_id(device1, "bootloader"); fu_device_set_remove_delay(device1, 100); fu_device_convert_instance_ids(device1); fu_device_list_add(device_list, device1); g_assert_cmpint(added_cnt, ==, 1); g_assert_cmpint(removed_cnt, ==, 0); g_assert_cmpint(changed_cnt, ==, 0); /* add another device in bootloader mode */ fu_device_set_id(device2, "device2"); fu_device_set_plugin(device2, "plugin-for-bootloader"); fu_device_add_instance_id(device2, "bootloader"); fu_device_add_internal_flag(device2, FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID); fu_device_convert_instance_ids(device2); /* verify only a changed event was generated */ added_cnt = removed_cnt = changed_cnt = 0; fu_device_list_remove(device_list, device1); fu_device_list_add(device_list, device2); g_assert_cmpint(added_cnt, ==, 0); g_assert_cmpint(removed_cnt, ==, 0); g_assert_cmpint(changed_cnt, ==, 1); /* device2 should inherit the vendor ID and version from device1 */ g_assert_true(fu_device_has_vendor_id(device2, "USB:0x20A0")); g_assert_cmpstr(fu_device_get_version(device2), ==, "1.2.3"); /* one device is active */ devices_active = fu_device_list_get_active(device_list); g_assert_cmpint(devices_active->len, ==, 1); device = g_ptr_array_index(devices_active, 0); g_assert_cmpstr(fu_device_get_id(device), ==, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882"); /* the list knows about both devices, list in order of active->old */ devices_all = fu_device_list_get_all(device_list); g_assert_cmpint(devices_all->len, ==, 2); device = g_ptr_array_index(devices_all, 0); g_assert_cmpstr(fu_device_get_id(device), ==, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882"); device = g_ptr_array_index(devices_all, 1); g_assert_cmpstr(fu_device_get_id(device), ==, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a"); /* verify we can get the old device from the new device */ device_old = fu_device_list_get_old(device_list, device2); g_assert_true(device_old == device1); } static void fu_device_list_remove_chain_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; g_autoptr(FuDeviceList) device_list = fu_device_list_new(); g_autoptr(FuDevice) device_child = fu_device_new(self->ctx); g_autoptr(FuDevice) device_parent = fu_device_new(self->ctx); guint added_cnt = 0; guint changed_cnt = 0; guint removed_cnt = 0; g_signal_connect(FU_DEVICE_LIST(device_list), "added", G_CALLBACK(_device_list_count_cb), &added_cnt); g_signal_connect(FU_DEVICE_LIST(device_list), "removed", G_CALLBACK(_device_list_count_cb), &removed_cnt); g_signal_connect(FU_DEVICE_LIST(device_list), "changed", G_CALLBACK(_device_list_count_cb), &changed_cnt); /* add child */ fu_device_set_id(device_child, "child"); fu_device_add_instance_id(device_child, "child-GUID-1"); fu_device_convert_instance_ids(device_child); fu_device_list_add(device_list, device_child); g_assert_cmpint(added_cnt, ==, 1); g_assert_cmpint(removed_cnt, ==, 0); g_assert_cmpint(changed_cnt, ==, 0); /* add parent */ fu_device_set_id(device_parent, "parent"); fu_device_add_instance_id(device_parent, "parent-GUID-1"); fu_device_convert_instance_ids(device_parent); fu_device_add_child(device_parent, device_child); fu_device_list_add(device_list, device_parent); g_assert_cmpint(added_cnt, ==, 2); g_assert_cmpint(removed_cnt, ==, 0); g_assert_cmpint(changed_cnt, ==, 0); /* make sure that removing the parent causes both to go; but the child to go first */ fu_device_list_remove(device_list, device_parent); g_assert_cmpint(added_cnt, ==, 2); g_assert_cmpint(removed_cnt, ==, 2); g_assert_cmpint(changed_cnt, ==, 0); } static void fu_device_list_explicit_order_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; g_autoptr(FuDevice) device_child = fu_device_new(self->ctx); g_autoptr(FuDevice) device_root = fu_device_new(self->ctx); g_autoptr(FuDeviceList) device_list = fu_device_list_new(); /* add both */ fu_device_set_id(device_root, "device"); fu_device_add_instance_id(device_root, "foobar"); fu_device_convert_instance_ids(device_root); fu_device_set_id(device_child, "device-child"); fu_device_add_instance_id(device_child, "baz"); fu_device_convert_instance_ids(device_child); fu_device_add_child(device_root, device_child); fu_device_list_add(device_list, device_root); fu_device_add_internal_flag(device_root, FU_DEVICE_INTERNAL_FLAG_EXPLICIT_ORDER); fu_device_list_depsolve_order(device_list, device_root); g_assert_cmpint(fu_device_get_order(device_root), ==, G_MAXINT); g_assert_cmpint(fu_device_get_order(device_child), ==, G_MAXINT); } static void fu_device_list_explicit_order_post_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; g_autoptr(FuDevice) device_child = fu_device_new(self->ctx); g_autoptr(FuDevice) device_root = fu_device_new(self->ctx); g_autoptr(FuDeviceList) device_list = fu_device_list_new(); /* add both */ fu_device_set_id(device_root, "device"); fu_device_add_instance_id(device_root, "foobar"); fu_device_convert_instance_ids(device_root); fu_device_set_id(device_child, "device-child"); fu_device_add_instance_id(device_child, "baz"); fu_device_convert_instance_ids(device_child); fu_device_add_child(device_root, device_child); fu_device_list_add(device_list, device_root); fu_device_list_add(device_list, device_child); fu_device_list_depsolve_order(device_list, device_root); g_assert_cmpint(fu_device_get_order(device_root), ==, 0); g_assert_cmpint(fu_device_get_order(device_child), ==, -1); fu_device_add_internal_flag(device_root, FU_DEVICE_INTERNAL_FLAG_EXPLICIT_ORDER); g_assert_cmpint(fu_device_get_order(device_root), ==, G_MAXINT); g_assert_cmpint(fu_device_get_order(device_child), ==, G_MAXINT); } static void fu_device_list_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; g_autoptr(FuDeviceList) device_list = fu_device_list_new(); g_autoptr(FuDevice) device1 = fu_device_new(self->ctx); g_autoptr(FuDevice) device2 = fu_device_new(self->ctx); g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices2 = NULL; g_autoptr(GError) error = NULL; FuDevice *device; guint added_cnt = 0; guint changed_cnt = 0; guint removed_cnt = 0; g_signal_connect(FU_DEVICE_LIST(device_list), "added", G_CALLBACK(_device_list_count_cb), &added_cnt); g_signal_connect(FU_DEVICE_LIST(device_list), "removed", G_CALLBACK(_device_list_count_cb), &removed_cnt); g_signal_connect(FU_DEVICE_LIST(device_list), "changed", G_CALLBACK(_device_list_count_cb), &changed_cnt); /* add both */ fu_device_set_id(device1, "device1"); fu_device_add_instance_id(device1, "foobar"); fu_device_convert_instance_ids(device1); fu_device_list_add(device_list, device1); fu_device_set_id(device2, "device2"); fu_device_add_instance_id(device2, "baz"); fu_device_convert_instance_ids(device2); fu_device_list_add(device_list, device2); g_assert_cmpint(added_cnt, ==, 2); g_assert_cmpint(removed_cnt, ==, 0); g_assert_cmpint(changed_cnt, ==, 0); /* get all */ devices = fu_device_list_get_all(device_list); g_assert_cmpint(devices->len, ==, 2); device = g_ptr_array_index(devices, 0); g_assert_cmpstr(fu_device_get_id(device), ==, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a"); /* find by ID */ device = fu_device_list_get_by_id(device_list, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a", &error); g_assert_no_error(error); g_assert_nonnull(device); g_assert_cmpstr(fu_device_get_id(device), ==, "99249eb1bd9ef0b6e192b271a8cb6a3090cfec7a"); g_clear_object(&device); /* find by GUID */ device = fu_device_list_get_by_guid(device_list, "579a3b1c-d1db-5bdc-b6b9-e2c1b28d5b8a", &error); g_assert_no_error(error); g_assert_nonnull(device); g_assert_cmpstr(fu_device_get_id(device), ==, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882"); g_clear_object(&device); /* find by missing GUID */ device = fu_device_list_get_by_guid(device_list, "notfound", &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_null(device); /* remove device */ added_cnt = removed_cnt = changed_cnt = 0; fu_device_list_remove(device_list, device1); g_assert_cmpint(added_cnt, ==, 0); g_assert_cmpint(removed_cnt, ==, 1); g_assert_cmpint(changed_cnt, ==, 0); devices2 = fu_device_list_get_all(device_list); g_assert_cmpint(devices2->len, ==, 1); device = g_ptr_array_index(devices2, 0); g_assert_cmpstr(fu_device_get_id(device), ==, "1a8d0d9a96ad3e67ba76cf3033623625dc6d6882"); } static void fu_plugin_list_func(gconstpointer user_data) { GPtrArray *plugins; FuPlugin *plugin; g_autoptr(FuPluginList) plugin_list = fu_plugin_list_new(); g_autoptr(FuPlugin) plugin1 = fu_plugin_new(NULL); g_autoptr(FuPlugin) plugin2 = fu_plugin_new(NULL); g_autoptr(GError) error = NULL; fu_plugin_set_name(plugin1, "plugin1"); fu_plugin_set_name(plugin2, "plugin2"); /* get all the plugins */ fu_plugin_list_add(plugin_list, plugin1); fu_plugin_list_add(plugin_list, plugin2); plugins = fu_plugin_list_get_all(plugin_list); g_assert_cmpint(plugins->len, ==, 2); /* get a single plugin */ plugin = fu_plugin_list_find_by_name(plugin_list, "plugin1", &error); g_assert_no_error(error); g_assert_nonnull(plugin); g_assert_cmpstr(fu_plugin_get_name(plugin), ==, "plugin1"); /* does not exist */ plugin = fu_plugin_list_find_by_name(plugin_list, "nope", &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_null(plugin); } static void fu_plugin_list_depsolve_func(gconstpointer user_data) { GPtrArray *plugins; FuPlugin *plugin; gboolean ret; g_autoptr(FuPluginList) plugin_list = fu_plugin_list_new(); g_autoptr(FuPlugin) plugin1 = fu_plugin_new(NULL); g_autoptr(FuPlugin) plugin2 = fu_plugin_new(NULL); g_autoptr(GError) error = NULL; fu_plugin_set_name(plugin1, "plugin1"); fu_plugin_set_name(plugin2, "plugin2"); /* add rule then depsolve */ fu_plugin_list_add(plugin_list, plugin1); fu_plugin_list_add(plugin_list, plugin2); fu_plugin_add_rule(plugin1, FU_PLUGIN_RULE_RUN_AFTER, "plugin2"); ret = fu_plugin_list_depsolve(plugin_list, &error); g_assert_no_error(error); g_assert_true(ret); plugins = fu_plugin_list_get_all(plugin_list); g_assert_cmpint(plugins->len, ==, 2); plugin = g_ptr_array_index(plugins, 0); g_assert_cmpstr(fu_plugin_get_name(plugin), ==, "plugin2"); g_assert_cmpint(fu_plugin_get_order(plugin), ==, 0); g_assert_false(fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)); /* add another rule, then re-depsolve */ fu_plugin_add_rule(plugin1, FU_PLUGIN_RULE_CONFLICTS, "plugin2"); ret = fu_plugin_list_depsolve(plugin_list, &error); g_assert_no_error(error); g_assert_true(ret); plugin = fu_plugin_list_find_by_name(plugin_list, "plugin1", &error); g_assert_no_error(error); g_assert_nonnull(plugin); g_assert_false(fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)); plugin = fu_plugin_list_find_by_name(plugin_list, "plugin2", &error); g_assert_no_error(error); g_assert_nonnull(plugin); g_assert_true(fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)); } static void fu_history_migrate_v1_func(gconstpointer user_data) { gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(GFile) file_dst = NULL; g_autoptr(GFile) file_src = NULL; g_autoptr(FuDevice) device = NULL; g_autoptr(FuHistory) history = NULL; g_autofree gchar *filename = NULL; #ifndef HAVE_SQLITE g_test_skip("no sqlite support"); return; #endif /* load old version */ filename = g_test_build_filename(G_TEST_DIST, "tests", "history_v1.db", NULL); file_src = g_file_new_for_path(filename); file_dst = g_file_new_for_path("/tmp/fwupd-self-test/var/lib/fwupd/pending.db"); ret = g_file_copy(file_src, file_dst, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error); g_assert_no_error(error); g_assert_true(ret); /* create, migrating as required */ history = fu_history_new(); g_assert_nonnull(history); /* get device */ device = fu_history_get_device_by_id(history, "2ba16d10df45823dd4494ff10a0bfccfef512c9d", &error); g_assert_no_error(error); g_assert_nonnull(device); g_assert_cmpstr(fu_device_get_id(device), ==, "2ba16d10df45823dd4494ff10a0bfccfef512c9d"); } static void fu_history_migrate_v2_func(gconstpointer user_data) { gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(GFile) file_dst = NULL; g_autoptr(GFile) file_src = NULL; g_autoptr(FuDevice) device = NULL; g_autoptr(FuHistory) history = NULL; g_autofree gchar *filename = NULL; #ifndef HAVE_SQLITE g_test_skip("no sqlite support"); return; #endif /* load old version */ filename = g_test_build_filename(G_TEST_DIST, "tests", "history_v2.db", NULL); file_src = g_file_new_for_path(filename); file_dst = g_file_new_for_path("/tmp/fwupd-self-test/var/lib/fwupd/pending.db"); ret = g_file_copy(file_src, file_dst, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error); g_assert_no_error(error); g_assert_true(ret); /* create, migrating as required */ history = fu_history_new(); g_assert_nonnull(history); /* get device */ device = fu_history_get_device_by_id(history, "2ba16d10df45823dd4494ff10a0bfccfef512c9d", &error); g_assert_no_error(error); g_assert_nonnull(device); g_assert_cmpstr(fu_device_get_id(device), ==, "2ba16d10df45823dd4494ff10a0bfccfef512c9d"); } static void _plugin_status_changed_cb(FuDevice *device, FwupdStatus status, gpointer user_data) { guint *cnt = (guint *)user_data; g_debug("status now %s", fwupd_status_to_string(status)); (*cnt)++; fu_test_loop_quit(); } static void _plugin_device_added_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data) { FuDevice **dev = (FuDevice **)user_data; *dev = g_object_ref(device); fu_test_loop_quit(); } static void _plugin_device_register_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data) { /* fake being a daemon */ fu_plugin_runner_device_register(plugin, device); } #ifdef HAVE_GUSB static void fu_backend_usb_hotplug_cb(FuBackend *backend, FuDevice *device, gpointer user_data) { guint *cnt = (guint *)user_data; (*cnt)++; } static void fu_backend_usb_load_file(FuBackend *backend, const gchar *fn) { gboolean ret; g_autoptr(GError) error = NULL; g_autoptr(JsonParser) parser = json_parser_new(); ret = json_parser_load_from_file(parser, fn, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_backend_load(backend, json_node_get_object(json_parser_get_root(parser)), NULL, FU_BACKEND_LOAD_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); } #endif /* * To generate the fwupd DS20 descriptor in the usb-devices.json file save fw-ds20.builder.xml: * * * 42 * 32 * * * Then run: * * fwupdtool firmware-build fw-ds20.builder.xml fw-ds20.bin * base64 fw-ds20.bin * * To generate the fake control transfer response, save fw-ds20.quirk: * * [USB\VID_273F&PID_1004] * Plugin = dfu * Icon = computer * * Then run: * * contrib/generate-ds20.py fw-ds20.quirk --bufsz 32 */ static void fu_backend_usb_func(gconstpointer user_data) { #ifdef HAVE_GUSB FuTest *self = (FuTest *)user_data; gboolean ret; guint cnt_added = 0; guint cnt_removed = 0; FuDevice *device_tmp; g_autofree gchar *gusb_emulate_fn = NULL; g_autofree gchar *gusb_emulate_fn2 = NULL; g_autofree gchar *gusb_emulate_fn3 = NULL; g_autofree gchar *devicestr = NULL; g_autoptr(FuBackend) backend = fu_usb_backend_new(self->ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) possible_plugins = NULL; #if !G_USB_CHECK_VERSION(0, 4, 5) g_test_skip("GUsb version too old"); return; #endif /* check there were events */ g_signal_connect(backend, "device-added", G_CALLBACK(fu_backend_usb_hotplug_cb), &cnt_added); g_signal_connect(backend, "device-removed", G_CALLBACK(fu_backend_usb_hotplug_cb), &cnt_removed); /* load the JSON into the backend */ g_assert_cmpstr(fu_backend_get_name(backend), ==, "usb"); g_assert_true(fu_backend_get_enabled(backend)); ret = fu_backend_setup(backend, progress, &error); g_assert_no_error(error); g_assert_true(ret); gusb_emulate_fn = g_test_build_filename(G_TEST_DIST, "tests", "usb-devices.json", NULL); g_assert_nonnull(gusb_emulate_fn); fu_backend_usb_load_file(backend, gusb_emulate_fn); g_assert_cmpint(cnt_added, ==, 0); g_assert_cmpint(cnt_removed, ==, 0); ret = fu_backend_coldplug(backend, progress, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(cnt_added, ==, 1); g_assert_cmpint(cnt_removed, ==, 0); devices = fu_backend_get_devices(backend); g_assert_cmpint(devices->len, ==, 1); device_tmp = g_ptr_array_index(devices, 0); fu_device_set_context(device_tmp, self->ctx); ret = fu_device_probe(device_tmp, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_true(fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_EMULATED)); /* for debugging */ devicestr = fu_device_to_string(device_tmp); g_debug("%s", devicestr); /* check the fwupd DS20 descriptor was parsed */ g_assert_true(fu_device_has_icon(device_tmp, "computer")); possible_plugins = fu_device_get_possible_plugins(device_tmp); g_assert_cmpint(possible_plugins->len, ==, 1); g_assert_cmpstr(g_ptr_array_index(possible_plugins, 0), ==, "dfu"); /* load another device with the same VID:PID, and check that we did not get a replug */ gusb_emulate_fn2 = g_test_build_filename(G_TEST_DIST, "tests", "usb-devices-replace.json", NULL); g_assert_nonnull(gusb_emulate_fn2); fu_backend_usb_load_file(backend, gusb_emulate_fn2); g_assert_cmpint(cnt_added, ==, 1); g_assert_cmpint(cnt_removed, ==, 0); /* load another device with a different VID:PID, and check that we *did* get a replug */ gusb_emulate_fn3 = g_test_build_filename(G_TEST_DIST, "tests", "usb-devices-bootloader.json", NULL); g_assert_nonnull(gusb_emulate_fn3); fu_backend_usb_load_file(backend, gusb_emulate_fn3); g_assert_cmpint(cnt_added, ==, 2); g_assert_cmpint(cnt_removed, ==, 1); #else g_test_skip("No GUsb support"); #endif } static void fu_backend_usb_invalid_func(gconstpointer user_data) { #ifdef HAVE_GUSB FuTest *self = (FuTest *)user_data; gboolean ret; FuDevice *device_tmp; g_autofree gchar *gusb_emulate_fn = NULL; g_autoptr(FuBackend) backend = fu_usb_backend_new(self->ctx); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(JsonParser) parser = json_parser_new(); #if !G_USB_CHECK_VERSION(0, 4, 5) g_test_skip("GUsb version too old"); return; #endif /* load the JSON into the backend */ gusb_emulate_fn = g_test_build_filename(G_TEST_DIST, "tests", "usb-devices-invalid.json", NULL); ret = json_parser_load_from_file(parser, gusb_emulate_fn, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_backend_setup(backend, progress, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_backend_load(backend, json_node_get_object(json_parser_get_root(parser)), NULL, FU_BACKEND_LOAD_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_backend_coldplug(backend, progress, &error); g_assert_no_error(error); g_assert_true(ret); devices = fu_backend_get_devices(backend); g_assert_cmpint(devices->len, ==, 1); device_tmp = g_ptr_array_index(devices, 0); fu_device_set_context(device_tmp, self->ctx); g_test_expect_message("FuUsbDevice", G_LOG_LEVEL_WARNING, "*invalid platform version 0x0000000a, expected >= 0x00010805*"); g_test_expect_message("FuUsbDevice", G_LOG_LEVEL_WARNING, "failed to parse * BOS descriptor: *did not find magic*"); locker = fu_device_locker_new(device_tmp, &error); g_assert_no_error(error); g_assert_nonnull(locker); /* check the device was processed correctly by FuUsbDevice */ g_assert_cmpstr(fu_device_get_name(device_tmp), ==, "ColorHug2"); g_assert_true(fu_device_has_instance_id(device_tmp, "USB\\VID_273F&PID_1004")); g_assert_true(fu_device_has_vendor_id(device_tmp, "USB:0x273F")); /* check the fwupd DS20 descriptor was *not* parsed */ g_assert_false(fu_device_has_icon(device_tmp, "computer")); #else g_test_skip("No GUsb support"); #endif } static void fu_plugin_module_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; GError *error = NULL; FuDevice *device_tmp; FwupdRelease *release_tmp; gboolean ret; guint cnt = 0; g_autofree gchar *localstatedir = NULL; g_autofree gchar *mapped_file_fn = NULL; g_autofree gchar *pending_cap = NULL; g_autofree gchar *history_db = NULL; g_autoptr(FuDevice) device = NULL; g_autoptr(FuDevice) device2 = NULL; g_autoptr(FuDevice) device3 = NULL; g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuHistory) history = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(FwupdRelease) release = fwupd_release_new(); g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GMappedFile) mapped_file = NULL; g_autoptr(XbSilo) silo_empty = xb_silo_new(); /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* create a fake device */ ret = fu_plugin_set_config_value(self->plugin, "RegistrationSupported", "true", &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_runner_startup(self->plugin, progress, &error); g_assert_no_error(error); g_assert_true(ret); g_signal_connect(FU_PLUGIN(self->plugin), "device-added", G_CALLBACK(_plugin_device_added_cb), &device); g_signal_connect(FU_PLUGIN(self->plugin), "device-register", G_CALLBACK(_plugin_device_register_cb), &device); ret = fu_plugin_runner_coldplug(self->plugin, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* check we did the right thing */ g_assert_nonnull(device); g_assert_cmpstr(fu_device_get_id(device), ==, "08d460be0f1f9f128413f816022a6439e0078018"); g_assert_cmpstr(fu_device_get_version_lowest(device), ==, "1.2.0"); g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.2"); g_assert_cmpstr(fu_device_get_version_bootloader(device), ==, "0.1.2"); g_assert_cmpstr(fu_device_get_guid_default(device), ==, "b585990a-003e-5270-89d5-3705a17f9a43"); g_assert_cmpstr(fu_device_get_name(device), ==, "Integrated Webcam™"); g_signal_handlers_disconnect_by_data(self->plugin, &device); #ifndef HAVE_FWUPDOFFLINE g_test_skip("No offline update support on Windows"); return; #endif /* schedule an offline update */ g_signal_connect(FU_PROGRESS(progress), "status-changed", G_CALLBACK(_plugin_status_changed_cb), &cnt); mapped_file_fn = g_test_build_filename(G_TEST_DIST, "tests", "fakedevice123.bin", NULL); mapped_file = g_mapped_file_new(mapped_file_fn, FALSE, &error); g_assert_no_error(error); g_assert_nonnull(mapped_file); blob_cab = g_mapped_file_get_bytes(mapped_file); fwupd_release_set_version(release, "1.2.3"); ret = fu_engine_schedule_update(engine, device, release, blob_cab, FWUPD_INSTALL_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); /* set on the current device */ g_assert_true(fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)); /* lets check the history */ history = fu_history_new(); device2 = fu_history_get_device_by_id(history, fu_device_get_id(device), &error); g_assert_no_error(error); g_assert_nonnull(device2); g_assert_cmpint(fu_device_get_update_state(device2), ==, FWUPD_UPDATE_STATE_PENDING); g_assert_cmpstr(fu_device_get_update_error(device2), ==, NULL); g_assert_true(fu_device_has_flag(device2, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)); release_tmp = fu_device_get_release_default(device2); g_assert_nonnull(release_tmp); g_assert_cmpstr(fwupd_release_get_filename(release_tmp), !=, NULL); g_assert_cmpstr(fwupd_release_get_version(release_tmp), ==, "1.2.3"); /* save this; we'll need to delete it later */ pending_cap = g_strdup(fwupd_release_get_filename(release_tmp)); /* lets do this online */ fu_engine_add_device(engine, device); fu_engine_add_plugin(engine, self->plugin); ret = fu_engine_install_blob(engine, device, blob_cab, progress, FWUPD_INSTALL_FLAG_NO_SEARCH, FWUPD_FEATURE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(cnt, >=, 8); /* check the new version */ g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.3"); g_assert_cmpstr(fu_device_get_version_bootloader(device), ==, "0.1.2"); /* lets check the history */ device3 = fu_history_get_device_by_id(history, fu_device_get_id(device), &error); g_assert_no_error(error); g_assert_nonnull(device3); g_assert_cmpint(fu_device_get_update_state(device3), ==, FWUPD_UPDATE_STATE_SUCCESS); g_assert_cmpstr(fu_device_get_update_error(device3), ==, NULL); /* get the status */ device_tmp = fu_device_new(NULL); fu_device_set_id(device_tmp, "FakeDevice"); ret = fu_plugin_runner_get_results(self->plugin, device_tmp, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(fu_device_get_update_state(device_tmp), ==, FWUPD_UPDATE_STATE_SUCCESS); g_assert_cmpstr(fu_device_get_update_error(device_tmp), ==, NULL); /* clear */ ret = fu_plugin_runner_clear_results(self->plugin, device_tmp, &error); g_assert_no_error(error); g_assert_true(ret); g_object_unref(device_tmp); g_clear_error(&error); /* delete files */ localstatedir = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); history_db = g_build_filename(localstatedir, "pending.db", NULL); (void)g_unlink(history_db); (void)g_unlink(pending_cap); } static void fu_history_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; GError *error = NULL; GPtrArray *checksums; gboolean ret; FuDevice *device; FwupdRelease *release; g_autoptr(FuDevice) device_found = NULL; g_autoptr(FuHistory) history = NULL; g_autoptr(GPtrArray) approved_firmware = NULL; g_autofree gchar *dirname = NULL; g_autofree gchar *filename = NULL; #ifndef HAVE_SQLITE g_test_skip("no sqlite support"); return; #endif /* create */ history = fu_history_new(); g_assert_nonnull(history); /* delete the database */ dirname = fu_path_from_kind(FU_PATH_KIND_LOCALSTATEDIR_PKG); if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) return; filename = g_build_filename(dirname, "pending.db", NULL); (void)g_unlink(filename); /* add a device */ device = fu_device_new(self->ctx); fu_device_set_id(device, "self-test"); fu_device_set_name(device, "ColorHug"), fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_device_set_version(device, "3.0.1"), fu_device_set_update_state(device, FWUPD_UPDATE_STATE_FAILED); fu_device_set_update_error(device, "word"); fu_device_add_guid(device, "827edddd-9bb6-5632-889f-2c01255503da"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_INTERNAL); fu_device_set_created(device, 123); fu_device_set_modified(device, 456); release = fwupd_release_new(); fwupd_release_set_filename(release, "/var/lib/dave.cap"), fwupd_release_add_checksum(release, "abcdef"); fwupd_release_set_version(release, "3.0.2"); fwupd_release_add_metadata_item(release, "FwupdVersion", VERSION); ret = fu_history_add_device(history, device, release, &error); g_assert_no_error(error); g_assert_true(ret); g_object_unref(release); /* ensure database was created */ g_assert_true(g_file_test(filename, G_FILE_TEST_EXISTS)); g_object_unref(device); /* get device */ device = fu_history_get_device_by_id(history, "2ba16d10df45823dd4494ff10a0bfccfef512c9d", &error); g_assert_no_error(error); g_assert_nonnull(device); g_assert_cmpstr(fu_device_get_id(device), ==, "2ba16d10df45823dd4494ff10a0bfccfef512c9d"); g_assert_cmpstr(fu_device_get_name(device), ==, "ColorHug"); g_assert_cmpstr(fu_device_get_version(device), ==, "3.0.1"); g_assert_cmpint(fu_device_get_update_state(device), ==, FWUPD_UPDATE_STATE_FAILED); g_assert_cmpstr(fu_device_get_update_error(device), ==, "word"); g_assert_cmpstr(fu_device_get_guid_default(device), ==, "827edddd-9bb6-5632-889f-2c01255503da"); g_assert_cmpint(fu_device_get_flags(device), ==, FWUPD_DEVICE_FLAG_INTERNAL | FWUPD_DEVICE_FLAG_HISTORICAL); g_assert_cmpint(fu_device_get_created(device), ==, 123); g_assert_cmpint(fu_device_get_modified(device), ==, 456); release = fu_device_get_release_default(device); g_assert_nonnull(release); g_assert_cmpstr(fwupd_release_get_version(release), ==, "3.0.2"); g_assert_cmpstr(fwupd_release_get_filename(release), ==, "/var/lib/dave.cap"); g_assert_cmpstr(fwupd_release_get_metadata_item(release, "FwupdVersion"), ==, VERSION); checksums = fwupd_release_get_checksums(release); g_assert_nonnull(checksums); g_assert_cmpint(checksums->len, ==, 1); g_assert_cmpstr(fwupd_checksum_get_by_kind(checksums, G_CHECKSUM_SHA1), ==, "abcdef"); ret = fu_history_add_device(history, device, release, &error); g_assert_no_error(error); g_assert_true(ret); /* get device that does not exist */ device_found = fu_history_get_device_by_id(history, "XXXXXXXXXXXXX", &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_null(device_found); g_clear_error(&error); /* get device that does exist */ device_found = fu_history_get_device_by_id(history, "2ba16d10df45823dd4494ff10a0bfccfef512c9d", &error); g_assert_no_error(error); g_assert_nonnull(device_found); g_object_unref(device_found); /* remove device */ ret = fu_history_remove_device(history, device, &error); g_assert_no_error(error); g_assert_true(ret); g_object_unref(device); /* get device that does not exist */ device_found = fu_history_get_device_by_id(history, "2ba16d10df45823dd4494ff10a0bfccfef512c9d", &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND); g_assert_null(device_found); g_clear_error(&error); /* approved firmware */ ret = fu_history_clear_approved_firmware(history, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_history_add_approved_firmware(history, "foo", &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_history_add_approved_firmware(history, "bar", &error); g_assert_no_error(error); g_assert_true(ret); approved_firmware = fu_history_get_approved_firmware(history, &error); g_assert_no_error(error); g_assert_nonnull(approved_firmware); g_assert_cmpint(approved_firmware->len, ==, 2); g_assert_cmpstr(g_ptr_array_index(approved_firmware, 0), ==, "foo"); g_assert_cmpstr(g_ptr_array_index(approved_firmware, 1), ==, "bar"); } static GBytes * _build_cab(gboolean compressed, ...) { va_list args; g_autoptr(FuCabFirmware) cabinet = fu_cab_firmware_new(); g_autoptr(GError) error = NULL; g_autoptr(GBytes) cabinet_blob = NULL; fu_cab_firmware_set_compressed(cabinet, compressed); /* add each file */ va_start(args, compressed); do { const gchar *fn; const gchar *text; g_autoptr(FuCabImage) img = fu_cab_image_new(); g_autoptr(GBytes) blob = NULL; /* get filename */ fn = va_arg(args, const gchar *); if (fn == NULL) break; /* get contents */ text = va_arg(args, const gchar *); if (text == NULL) break; g_debug("creating %s with %s", fn, text); /* add a GCabFile to the cabinet */ blob = g_bytes_new_static(text, strlen(text)); fu_firmware_set_id(FU_FIRMWARE(img), fn); fu_firmware_set_bytes(FU_FIRMWARE(img), blob); fu_firmware_add_image(FU_FIRMWARE(cabinet), FU_FIRMWARE(img)); } while (TRUE); va_end(args); /* write the archive to a blob */ cabinet_blob = fu_firmware_write(FU_FIRMWARE(cabinet), &error); g_assert_no_error(error); g_assert_nonnull(cabinet_blob); return g_steal_pointer(&cabinet_blob); } static void _plugin_composite_device_added_cb(FuPlugin *plugin, FuDevice *device, gpointer user_data) { GPtrArray *devices = (GPtrArray *)user_data; g_ptr_array_add(devices, g_object_ref(device)); } static gint fu_plugin_composite_release_sort_cb(gconstpointer a, gconstpointer b) { FuRelease *release1 = *((FuRelease **)a); FuRelease *release2 = *((FuRelease **)b); FuDevice *device1 = fu_release_get_device(release1); FuDevice *device2 = fu_release_get_device(release2); if (fu_device_get_order(device1) < fu_device_get_order(device2)) return 1; if (fu_device_get_order(device1) > fu_device_get_order(device2)) return -1; return 0; } static void fu_plugin_composite_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; FuDevice *dev_tmp; GError *error = NULL; gboolean ret; g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuEngineRequest) request = fu_engine_request_new(); g_autoptr(GBytes) blob = NULL; g_autoptr(GPtrArray) components = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(XbSilo) silo_empty = xb_silo_new(); g_autoptr(XbSilo) silo = NULL; /* no metadata in daemon */ fu_engine_set_silo(engine, silo_empty); /* create CAB file */ blob = _build_cab( FALSE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " b585990a-003e-5270-89d5-3705a17f9a43\n" " \n" " \n" " \n" " \n" "", "acme.module1.metainfo.xml", "\n" " com.acme.example.firmware.module1\n" " \n" " 7fddead7-12b5-4fb9-9fa0-6d30305df755\n" " \n" " \n" " \n" " \n" " \n" " plain\n" " \n" "", "acme.module2.metainfo.xml", "\n" " com.acme.example.firmware.module2\n" " \n" " b8fe6b45-8702-4bcd-8120-ef236caac76f\n" " \n" " \n" " \n" " \n" " \n" " plain\n" " \n" "", "firmware.bin", "world", NULL); silo = fu_cabinet_build_silo(blob, 10240, &error); g_assert_no_error(error); g_assert_nonnull(silo); components = xb_silo_query(silo, "components/component", 0, &error); g_assert_no_error(error); g_assert_nonnull(components); g_assert_cmpint(components->len, ==, 3); /* set up dummy plugin */ ret = fu_plugin_reset_config_values(self->plugin, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_plugin_set_config_value(self->plugin, "CompositeChild", "true", &error); g_assert_no_error(error); g_assert_true(ret); fu_engine_add_plugin(engine, self->plugin); ret = fu_plugin_runner_startup(self->plugin, progress, &error); g_assert_no_error(error); g_assert_true(ret); devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_signal_connect(FU_PLUGIN(self->plugin), "device-added", G_CALLBACK(_plugin_composite_device_added_cb), devices); ret = fu_plugin_runner_coldplug(self->plugin, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* check we found all composite devices */ g_assert_cmpint(devices->len, ==, 3); for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); fu_engine_add_device(engine, device); if (g_strcmp0(fu_device_get_id(device), "08d460be0f1f9f128413f816022a6439e0078018") == 0) { g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.2"); } else if (g_strcmp0(fu_device_get_id(device), "c0a0a4aa6480ac28eea1ce164fbb466ca934e1ff") == 0) { g_assert_cmpstr(fu_device_get_version(device), ==, "1"); g_assert_nonnull(fu_device_get_parent(device)); } else if (g_strcmp0(fu_device_get_id(device), "bf455e9f371d2608d1cb67660fd2b335d3f6ef73") == 0) { g_assert_cmpstr(fu_device_get_version(device), ==, "10"); g_assert_nonnull(fu_device_get_parent(device)); } } /* produce install tasks */ for (guint i = 0; i < components->len; i++) { XbNode *component = g_ptr_array_index(components, i); /* do any devices pass the requirements */ for (guint j = 0; j < devices->len; j++) { FuDevice *device = g_ptr_array_index(devices, j); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error_local = NULL; /* is this component valid for the device */ fu_release_set_device(release, device); fu_release_set_request(release, request); if (!fu_release_load(release, component, NULL, FWUPD_INSTALL_FLAG_NONE, &error_local)) { g_debug("requirement on %s:%s failed: %s", fu_device_get_id(device), xb_node_query_text(component, "id", NULL), error_local->message); continue; } g_ptr_array_add(releases, g_steal_pointer(&release)); } } g_assert_cmpint(releases->len, ==, 3); /* sort these by version, forcing fu_engine_install_releases() to sort by device order */ g_ptr_array_sort(releases, fu_plugin_composite_release_sort_cb); dev_tmp = fu_release_get_device(FU_RELEASE(g_ptr_array_index(releases, 0))); g_assert_cmpstr(fu_device_get_logical_id(dev_tmp), ==, "child1"); dev_tmp = fu_release_get_device(FU_RELEASE(g_ptr_array_index(releases, 1))); g_assert_cmpstr(fu_device_get_logical_id(dev_tmp), ==, "child2"); dev_tmp = fu_release_get_device(FU_RELEASE(g_ptr_array_index(releases, 2))); g_assert_cmpstr(fu_device_get_logical_id(dev_tmp), ==, NULL); /* install the cab */ ret = fu_engine_install_releases(engine, request, releases, blob, progress, FWUPD_DEVICE_FLAG_NONE, &error); g_assert_no_error(error); g_assert_true(ret); /* verify we installed the parent first */ dev_tmp = fu_release_get_device(FU_RELEASE(g_ptr_array_index(releases, 0))); g_assert_cmpstr(fu_device_get_logical_id(dev_tmp), ==, NULL); dev_tmp = fu_release_get_device(FU_RELEASE(g_ptr_array_index(releases, 1))); g_assert_cmpstr(fu_device_get_logical_id(dev_tmp), ==, "child2"); dev_tmp = fu_release_get_device(FU_RELEASE(g_ptr_array_index(releases, 2))); g_assert_cmpstr(fu_device_get_logical_id(dev_tmp), ==, "child1"); /* verify everything upgraded */ for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); const gchar *metadata; if (g_strcmp0(fu_device_get_id(device), "08d460be0f1f9f128413f816022a6439e0078018") == 0) { g_assert_cmpstr(fu_device_get_version(device), ==, "1.2.3"); } else if (g_strcmp0(fu_device_get_id(device), "c0a0a4aa6480ac28eea1ce164fbb466ca934e1ff") == 0) { g_assert_cmpstr(fu_device_get_version(device), ==, "2"); } else if (g_strcmp0(fu_device_get_id(device), "bf455e9f371d2608d1cb67660fd2b335d3f6ef73") == 0) { g_assert_cmpstr(fu_device_get_version(device), ==, "11"); } /* verify prepare and cleanup ran on all devices */ metadata = fu_device_get_metadata(device, "frimbulator"); g_assert_cmpstr(metadata, ==, "1"); metadata = fu_device_get_metadata(device, "frombulator"); g_assert_cmpstr(metadata, ==, "1"); } } static void fu_security_attr_func(gconstpointer user_data) { gboolean ret; g_autofree gchar *json1 = NULL; g_autofree gchar *json2 = NULL; g_autoptr(FuSecurityAttrs) attrs1 = fu_security_attrs_new(); g_autoptr(FuSecurityAttrs) attrs2 = fu_security_attrs_new(); g_autoptr(FwupdSecurityAttr) attr1 = fwupd_security_attr_new("org.fwupd.hsi.foo"); g_autoptr(FwupdSecurityAttr) attr2 = fwupd_security_attr_new("org.fwupd.hsi.bar"); g_autoptr(GError) error = NULL; g_autoptr(JsonParser) parser = json_parser_new(); fwupd_security_attr_set_plugin(attr1, "foo"); fwupd_security_attr_set_created(attr1, 0); fwupd_security_attr_set_plugin(attr2, "bar"); fwupd_security_attr_set_created(attr2, 0); fu_security_attrs_append(attrs1, attr1); fu_security_attrs_append(attrs1, attr2); json1 = fu_security_attrs_to_json_string(attrs1, &error); g_assert_no_error(error); g_assert_nonnull(json1); ret = fu_test_compare_lines( json1, "{\n" " \"SecurityAttributes\" : [\n" " {\n" " \"AppstreamId\" : \"org.fwupd.hsi.foo\",\n" " \"HsiLevel\" : 0,\n" " \"Plugin\" : \"foo\",\n" " \"Uri\" : " "\"https://fwupd.github.io/libfwupdplugin/hsi.html#org.fwupd.hsi.foo\"\n" " },\n" " {\n" " \"AppstreamId\" : \"org.fwupd.hsi.bar\",\n" " \"HsiLevel\" : 0,\n" " \"Plugin\" : \"bar\",\n" " \"Uri\" : " "\"https://fwupd.github.io/libfwupdplugin/hsi.html#org.fwupd.hsi.bar\"\n" " }\n" " ]\n" "}", &error); g_assert_no_error(error); g_assert_true(ret); ret = json_parser_load_from_data(parser, json1, -1, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_security_attrs_from_json(attrs2, json_parser_get_root(parser), &error); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_test_skip(error->message); return; } g_assert_no_error(error); g_assert_true(ret); json2 = fu_security_attrs_to_json_string(attrs2, &error); g_assert_no_error(error); g_assert_nonnull(json2); ret = fu_test_compare_lines(json2, json1, &error); g_assert_no_error(error); g_assert_true(ret); } static void fu_common_cabinet_func(void) { g_autoptr(FuCabinet) cabinet = fu_cabinet_new(); g_autoptr(GBytes) blob1 = NULL; g_autoptr(GBytes) blob2 = NULL; g_autoptr(GBytes) jcat_blob1 = g_bytes_new_static("hello", 6); g_autoptr(GBytes) jcat_blob2 = g_bytes_new_static("hellX", 6); g_autoptr(GError) error = NULL; /* add */ fu_cabinet_add_file(cabinet, "firmware.jcat", jcat_blob1); /* replace */ fu_cabinet_add_file(cabinet, "firmware.jcat", jcat_blob2); /* get data */ blob1 = fu_cabinet_get_file(cabinet, "firmware.jcat", &error); g_assert_no_error(error); g_assert_nonnull(blob1); g_assert_cmpstr(g_bytes_get_data(blob1, NULL), ==, "hellX"); /* get data that does not exist */ blob2 = fu_cabinet_get_file(cabinet, "foo.jcat", &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null(blob2); } static void fu_memcpy_func(gconstpointer user_data) { const guint8 src[] = {'a', 'b', 'c', 'd', 'e'}; gboolean ret; guint8 dst[4]; g_autoptr(GError) error = NULL; /* copy entire buffer */ ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 4, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(memcmp(src, dst, 4), ==, 0); /* copy first char */ ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 1, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(dst[0], ==, 'a'); /* copy last char */ ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x4, 1, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(dst[0], ==, 'e'); /* copy nothing */ ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 0, &error); g_assert_no_error(error); g_assert_true(ret); /* write past the end of dst */ ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 5, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE); g_assert_false(ret); g_clear_error(&error); /* write past the end of dst with offset */ ret = fu_memcpy_safe(dst, sizeof(dst), 0x1, src, sizeof(src), 0x0, 4, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_WRITE); g_assert_false(ret); g_clear_error(&error); /* read past the end of dst */ ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x0, 6, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_READ); g_assert_false(ret); g_clear_error(&error); /* read past the end of src with offset */ ret = fu_memcpy_safe(dst, sizeof(dst), 0x0, src, sizeof(src), 0x4, 4, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_READ); g_assert_false(ret); g_clear_error(&error); } static void fu_console_func(gconstpointer user_data) { g_autoptr(FuConsole) console = fu_console_new(); fu_console_set_status_length(console, 20); fu_console_set_percentage_length(console, 50); g_print("\n"); for (guint i = 0; i < 100; i++) { fu_console_set_progress(console, FWUPD_STATUS_DECOMPRESSING, i); g_usleep(10000); } fu_console_set_progress(console, FWUPD_STATUS_IDLE, 0); for (guint i = 0; i < 100; i++) { guint pc = (i > 25 && i < 75) ? 0 : i; fu_console_set_progress(console, FWUPD_STATUS_LOADING, pc); g_usleep(10000); } fu_console_set_progress(console, FWUPD_STATUS_IDLE, 0); for (guint i = 0; i < 5000; i++) { fu_console_set_progress(console, FWUPD_STATUS_LOADING, 0); g_usleep(1000); } fu_console_set_progress(console, FWUPD_STATUS_IDLE, 0); } static gint fu_release_compare_func_cb(gconstpointer a, gconstpointer b) { FuRelease *release1 = *((FuRelease **)a); FuRelease *release2 = *((FuRelease **)b); return fu_release_compare(release1, release2); } static void fu_spawn_stdout_cb(const gchar *line, gpointer user_data) { guint *lines = (guint *)user_data; g_debug("got '%s'", line); (*lines)++; } static void fu_spawn_func(void) { gboolean ret; guint lines = 0; g_autoptr(GError) error = NULL; g_autofree gchar *fn = NULL; const gchar *argv[4] = {"/bin/sh", "replace", "test", NULL}; #ifdef _WIN32 g_test_skip("Known failures on Windows right now, skipping spawn func test"); return; #endif fn = g_test_build_filename(G_TEST_DIST, "tests", "spawn.sh", NULL); argv[1] = fn; ret = fu_spawn_sync(argv, fu_spawn_stdout_cb, &lines, 0, NULL, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpint(lines, ==, 6); } static void fu_spawn_timeout_func(void) { gboolean ret; guint lines = 0; g_autoptr(GError) error = NULL; g_autofree gchar *fn = NULL; const gchar *argv[4] = {"/bin/sh", "replace", "test", NULL}; #ifdef _WIN32 g_test_skip("Known failures on Windows right now, skipping spawn timeout test"); return; #endif fn = g_test_build_filename(G_TEST_DIST, "tests", "spawn.sh", NULL); argv[1] = fn; ret = fu_spawn_sync(argv, fu_spawn_stdout_cb, &lines, 500, NULL, &error); g_assert_error(error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_assert_false(ret); g_assert_cmpint(lines, ==, 1); } static void fu_release_compare_func(gconstpointer user_data) { g_autoptr(GPtrArray) releases = g_ptr_array_new(); g_autoptr(FuDevice) device1 = fu_device_new(NULL); g_autoptr(FuDevice) device2 = fu_device_new(NULL); g_autoptr(FuDevice) device3 = fu_device_new(NULL); g_autoptr(FuRelease) release1 = fu_release_new(); g_autoptr(FuRelease) release2 = fu_release_new(); g_autoptr(FuRelease) release3 = fu_release_new(); fu_device_set_order(device1, 33); fu_release_set_device(release1, device1); fu_release_set_priority(release1, 0); fu_release_set_branch(release1, "1"); fu_device_set_order(device2, 11); fu_release_set_device(release2, device2); fu_release_set_priority(release2, 0); fu_release_set_branch(release2, "2"); fu_device_set_order(device3, 11); fu_release_set_device(release3, device3); fu_release_set_priority(release3, 99); fu_release_set_branch(release3, "3"); g_ptr_array_add(releases, release1); g_ptr_array_add(releases, release2); g_ptr_array_add(releases, release3); /* order the install tasks */ g_ptr_array_sort(releases, fu_release_compare_func_cb); g_assert_cmpint(releases->len, ==, 3); g_assert_cmpstr(fu_release_get_branch(g_ptr_array_index(releases, 0)), ==, "3"); g_assert_cmpstr(fu_release_get_branch(g_ptr_array_index(releases, 1)), ==, "2"); g_assert_cmpstr(fu_release_get_branch(g_ptr_array_index(releases, 2)), ==, "1"); } static void fu_release_uri_scheme_func(void) { struct { const gchar *in; const gchar *op; } strs[] = {{"https://foo.bar/baz", "https"}, {"HTTP://FOO.BAR/BAZ", "http"}, {"ftp://", "ftp"}, {"ftp:", "ftp"}, {"foobarbaz", NULL}, {"", NULL}, {NULL, NULL}}; for (guint i = 0; strs[i].in != NULL; i++) { g_autofree gchar *tmp = fu_release_uri_get_scheme(strs[i].in); g_assert_cmpstr(tmp, ==, strs[i].op); } } static void fu_release_trusted_report_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; FwupdRelease *rel; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FwupdRemote) remote = fwupd_remote_new(); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderNode) custom = xb_builder_node_new("custom"); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(FuEngineRequest) request = fu_engine_request_new(); /* add fake LVFS remote */ fwupd_remote_set_id(remote, "lvfs"); fu_engine_add_remote(engine, remote); /* load engine to get FuConfig set up */ ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* metadata with vendor id=123 */ filename = g_test_build_filename(G_TEST_DIST, "tests", "metadata-report1.xml", NULL); file = g_file_new_for_path(filename); ret = xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_node_insert_text(custom, "value", "lvfs", "key", "fwupd::RemoteId", NULL); xb_builder_source_set_info(source, custom); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); fu_engine_set_silo(engine, silo); /* add a dummy device */ fu_device_set_id(device, "dummy"); fu_device_set_version(device, "1.2.2"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_protocol(device, "com.acme"); fu_device_add_guid(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e"); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_engine_add_device(engine, device); /* ensure we set this as trusted */ releases = fu_engine_get_releases_for_device(engine, request, device, &error); g_assert_no_error(error); g_assert_nonnull(releases); g_assert_cmpint(releases->len, ==, 1); rel = g_ptr_array_index(releases, 0); g_assert_true(fwupd_release_has_flag(rel, FWUPD_RELEASE_FLAG_TRUSTED_REPORT)); } static void fu_release_trusted_report_oem_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; FwupdRelease *rel; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(FuEngineRequest) request = fu_engine_request_new(); /* load engine to get FuConfig set up */ ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* metadata with FromOEM */ filename = g_test_build_filename(G_TEST_DIST, "tests", "metadata-report2.xml", NULL); file = g_file_new_for_path(filename); ret = xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); fu_engine_set_silo(engine, silo); /* add a dummy device */ fu_device_set_id(device, "dummy"); fu_device_set_version(device, "1.2.2"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_protocol(device, "com.acme"); fu_device_add_guid(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e"); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_engine_add_device(engine, device); /* ensure we set this as trusted */ releases = fu_engine_get_releases_for_device(engine, request, device, &error); g_assert_no_error(error); g_assert_nonnull(releases); g_assert_cmpint(releases->len, ==, 1); rel = g_ptr_array_index(releases, 0); g_assert_true(fwupd_release_has_flag(rel, FWUPD_RELEASE_FLAG_TRUSTED_REPORT)); } static void fu_release_no_trusted_report_upgrade_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; FwupdRelease *rel; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(FuEngineRequest) request = fu_engine_request_new(); /* load engine to get FuConfig set up */ ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* metadata with FromOEM, but *NOT* an upgrade */ filename = g_test_build_filename(G_TEST_DIST, "tests", "metadata-report4.xml", NULL); file = g_file_new_for_path(filename); ret = xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); fu_engine_set_silo(engine, silo); /* add a dummy device */ fu_device_set_id(device, "dummy"); fu_device_set_version(device, "1.2.3"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_protocol(device, "com.acme"); fu_device_add_guid(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e"); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_engine_add_device(engine, device); /* ensure we set this as trusted */ releases = fu_engine_get_releases_for_device(engine, request, device, &error); g_assert_no_error(error); g_assert_nonnull(releases); g_assert_cmpint(releases->len, ==, 1); rel = g_ptr_array_index(releases, 0); g_assert_false(fwupd_release_has_flag(rel, FWUPD_RELEASE_FLAG_TRUSTED_REPORT)); } static void fu_release_no_trusted_report_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; FwupdRelease *rel; gboolean ret; g_autofree gchar *filename = NULL; g_autoptr(FuDevice) device = fu_device_new(self->ctx); g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(GError) error = NULL; g_autoptr(GFile) file = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbSilo) silo = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(FuEngineRequest) request = fu_engine_request_new(); /* load engine to get FuConfig set up */ ret = fu_engine_load(engine, FU_ENGINE_LOAD_FLAG_NO_CACHE, progress, &error); g_assert_no_error(error); g_assert_true(ret); /* metadata without OEM or valid VendorId as per tests/fwupd.conf */ filename = g_test_build_filename(G_TEST_DIST, "tests", "metadata-report3.xml", NULL); file = g_file_new_for_path(filename); ret = xb_builder_source_load_file(source, file, XB_BUILDER_SOURCE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_true(ret); xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, &error); g_assert_no_error(error); g_assert_nonnull(silo); fu_engine_set_silo(engine, silo); /* add a dummy device */ fu_device_set_id(device, "dummy"); fu_device_set_version(device, "1.2.2"); fu_device_add_vendor_id(device, "USB:FFFF"); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); fu_device_add_protocol(device, "com.acme"); fu_device_add_guid(device, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e"); fu_device_set_version_format(device, FWUPD_VERSION_FORMAT_TRIPLET); fu_engine_add_device(engine, device); /* ensure trusted reports flag is not set */ releases = fu_engine_get_releases_for_device(engine, request, device, &error); g_assert_no_error(error); g_assert_nonnull(releases); g_assert_cmpint(releases->len, ==, 1); rel = g_ptr_array_index(releases, 0); g_assert_false(fwupd_release_has_flag(rel, FWUPD_RELEASE_FLAG_TRUSTED_REPORT)); } static void fu_common_store_cab_func(void) { GBytes *blob_tmp; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbNode) csum = NULL; g_autoptr(XbNode) rel = NULL; g_autoptr(XbNode) req = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbQuery) query = NULL; /* create silo */ blob = _build_cab( FALSE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " ACME Firmware\n" " \n" " ae56e3fb-6528-5bc4-8b03-012f124075d7\n" " \n" " \n" " \n" " 5\n" " 7c211433f02071597741e6ff5a8ea34789abbf43\n" "

    We fixed things

    \n" "
    \n" "
    \n" " \n" " org.freedesktop.fwupd\n" " \n" "
    ", "firmware.dfu", "world", "firmware.dfu.asc", "signature", NULL); silo = fu_cabinet_build_silo(blob, 10240, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* verify */ component = xb_silo_query_first(silo, "components/component/id[text()='com.acme.example.firmware']/..", &error); g_assert_no_error(error); g_assert_nonnull(component); query = xb_query_new_full(xb_node_get_silo(component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, &error); g_assert_no_error(error); g_assert_nonnull(query); rel = xb_node_query_first_full(component, query, &error); g_assert_no_error(error); g_assert_nonnull(rel); g_assert_cmpstr(xb_node_get_attr(rel, "version"), ==, "1.2.3"); csum = xb_node_query_first(rel, "checksum[@target='content']", &error); g_assert_nonnull(csum); g_assert_cmpstr(xb_node_get_text(csum), ==, "7c211433f02071597741e6ff5a8ea34789abbf43"); blob_tmp = xb_node_get_data(rel, "fwupd::FirmwareBlob"); g_assert_nonnull(blob_tmp); req = xb_node_query_first(component, "requires/id", &error); g_assert_no_error(error); g_assert_nonnull(req); } static void fu_common_store_cab_artifact_func(void) { g_autoptr(GBytes) blob1 = NULL; g_autoptr(GBytes) blob2 = NULL; g_autoptr(GBytes) blob3 = NULL; g_autoptr(GBytes) blob4 = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbSilo) silo = NULL; /* create silo (sha256, using artifacts object) */ blob1 = _build_cab( FALSE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" " \n" " firmware.dfu\n" " 486EA46224D1BB4FB680F34F7C9AD96A8F24EC88BE73EA8E5A6C65260E9CB8A7\n" " \n" " \n" " \n" " \n" "", "firmware.dfu", "world", "firmware.dfu.asc", "signature", NULL); silo = fu_cabinet_build_silo(blob1, 10240, &error); g_assert_no_error(error); g_assert_nonnull(silo); g_clear_object(&silo); /* create silo (sha1, using artifacts object; mixed case) */ blob2 = _build_cab(FALSE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" " \n" " firmware.dfu\n" " 7c211433f02071597741e6ff5a8ea34789abbF43\n" " \n" " \n" " \n" " \n" "", "firmware.dfu", "world", "firmware.dfu.asc", "signature", NULL); silo = fu_cabinet_build_silo(blob2, 10240, &error); g_assert_no_error(error); g_assert_nonnull(silo); g_clear_object(&silo); /* create silo (sha512, using artifacts object; lower case) */ blob3 = _build_cab(FALSE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" " \n" " firmware.dfu\n" " " "11853df40f4b2b919d3815f64792e58d08663767a494bcbb38c0b2389d9140bbb170281b" "4a847be7757bde12c9cd0054ce3652d0ad3a1a0c92babb69798246ee\n" " \n" " \n" " \n" " \n" "", "firmware.dfu", "world", "firmware.dfu.asc", "signature", NULL); silo = fu_cabinet_build_silo(blob3, 10240, &error); g_assert_no_error(error); g_assert_nonnull(silo); g_clear_object(&silo); /* create silo (legacy release object) */ blob4 = _build_cab(FALSE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " " "486EA46224D1BB4FB680F34F7C9AD96A8F24EC88BE73EA8E5A6C65260E9CB8A7\n" " \n" " \n" "", "firmware.dfu", "world", "firmware.dfu.asc", "signature", NULL); silo = fu_cabinet_build_silo(blob4, 10240, &error); g_assert_no_error(error); g_assert_nonnull(silo); } static void fu_common_store_cab_unsigned_func(void) { GBytes *blob_tmp; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbNode) csum = NULL; g_autoptr(XbNode) rel = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbQuery) query = NULL; /* create silo */ blob = _build_cab(FALSE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" "", "firmware.bin", "world", NULL); silo = fu_cabinet_build_silo(blob, 10240, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* verify */ component = xb_silo_query_first(silo, "components/component/id[text()='com.acme.example.firmware']/..", &error); g_assert_no_error(error); g_assert_nonnull(component); query = xb_query_new_full(xb_node_get_silo(component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, &error); g_assert_no_error(error); g_assert_nonnull(query); rel = xb_node_query_first_full(component, query, &error); g_assert_no_error(error); g_assert_nonnull(rel); g_assert_cmpstr(xb_node_get_attr(rel, "version"), ==, "1.2.3"); csum = xb_node_query_first(rel, "checksum[@target='content']", &error); g_assert_null(csum); blob_tmp = xb_node_get_data(rel, "fwupd::FirmwareBlob"); g_assert_nonnull(blob_tmp); } static void fu_common_store_cab_sha256_func(void) { g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbSilo) silo = NULL; /* create silo */ blob = _build_cab( FALSE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " 486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7\n" " \n" " \n" "", "firmware.bin", "world", NULL); silo = fu_cabinet_build_silo(blob, 10240, &error); g_assert_no_error(error); g_assert_nonnull(silo); } static void fu_common_store_cab_folder_func(void) { GBytes *blob_tmp; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; g_autoptr(XbNode) component = NULL; g_autoptr(XbNode) rel = NULL; g_autoptr(XbSilo) silo = NULL; g_autoptr(XbQuery) query = NULL; /* create silo */ blob = _build_cab(FALSE, "lvfs\\acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" "", "lvfs\\firmware.bin", "world", NULL); silo = fu_cabinet_build_silo(blob, 10240, &error); g_assert_no_error(error); g_assert_nonnull(silo); /* verify */ component = xb_silo_query_first(silo, "components/component/id[text()='com.acme.example.firmware']/..", &error); g_assert_no_error(error); g_assert_nonnull(component); query = xb_query_new_full(xb_node_get_silo(component), "releases/release", XB_QUERY_FLAG_FORCE_NODE_CACHE, &error); g_assert_no_error(error); g_assert_nonnull(query); rel = xb_node_query_first_full(component, query, &error); g_assert_no_error(error); g_assert_nonnull(rel); g_assert_cmpstr(xb_node_get_attr(rel, "version"), ==, "1.2.3"); blob_tmp = xb_node_get_data(rel, "fwupd::FirmwareBlob"); g_assert_nonnull(blob_tmp); } static void fu_common_store_cab_error_no_metadata_func(void) { g_autoptr(XbSilo) silo = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; blob = _build_cab(FALSE, "foo.txt", "hello", "bar.txt", "world", NULL); silo = fu_cabinet_build_silo(blob, 10240, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null(silo); } static void fu_common_store_cab_error_wrong_size_func(void) { g_autoptr(XbSilo) silo = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; blob = _build_cab(FALSE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " 7004701\n" " deadbeef\n" " \n" " \n" "", "firmware.bin", "world", NULL); silo = fu_cabinet_build_silo(blob, 10240, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null(silo); } static void fu_common_store_cab_error_missing_file_func(void) { g_autoptr(XbSilo) silo = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; blob = _build_cab(FALSE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" " \n" " \n" "", "firmware.bin", "world", NULL); silo = fu_cabinet_build_silo(blob, 10240, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null(silo); } static void fu_common_store_cab_error_size_func(void) { g_autoptr(XbSilo) silo = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; blob = _build_cab(FALSE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " \n" "", "firmware.bin", "world", NULL); silo = fu_cabinet_build_silo(blob, 123, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null(silo); } static void fu_common_store_cab_error_wrong_checksum_func(void) { g_autoptr(XbSilo) silo = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GError) error = NULL; blob = _build_cab(FALSE, "acme.metainfo.xml", "\n" " com.acme.example.firmware\n" " \n" " \n" " deadbeef\n" " \n" " \n" "", "firmware.bin", "world", NULL); silo = fu_cabinet_build_silo(blob, 10240, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE); g_assert_null(silo); } static void fu_engine_modify_bios_settings_func(gconstpointer user_data) { FuTest *self = (FuTest *)user_data; gboolean ret; const gchar *current; FwupdBiosSetting *attr1; FwupdBiosSetting *attr2; FwupdBiosSetting *attr3; FwupdBiosSetting *attr4; g_autofree gchar *test_dir = NULL; g_autoptr(FuEngine) engine = fu_engine_new(self->ctx); g_autoptr(GError) error = NULL; g_autoptr(FuBiosSettings) attrs = NULL; g_autoptr(GPtrArray) items = NULL; g_autoptr(GHashTable) bios_settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); #ifdef _WIN32 g_test_skip("BIOS settings not supported on Windows"); return; #endif /* Load contrived attributes */ test_dir = g_test_build_filename(G_TEST_DIST, "tests", "bios-attrs", NULL); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", test_dir, TRUE); ret = fu_context_reload_bios_settings(fu_engine_get_context(engine), &error); g_assert_no_error(error); g_assert_true(ret); attrs = fu_context_get_bios_settings(fu_engine_get_context(engine)); items = fu_bios_settings_get_all(attrs); g_assert_cmpint(items->len, ==, 4); /* enumeration */ attr1 = fu_context_get_bios_setting(fu_engine_get_context(engine), "com.fwupd-internal.Absolute"); g_assert_nonnull(attr1); current = fwupd_bios_setting_get_current_value(attr1); g_assert_nonnull(current); g_hash_table_insert(bios_settings, g_strdup("Absolute"), g_strdup("Disabled")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO); g_assert_false(ret); g_clear_error(&error); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("Absolute"), g_strdup("Enabled")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_no_error(error); g_assert_true(ret); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("Absolute"), g_strdup("off")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_no_error(error); g_assert_true(ret); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("Absolute"), g_strdup("FOO")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_clear_error(&error); /* use BiosSettingId instead */ g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("com.fwupd-internal.Absolute"), g_strdup("on")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_no_error(error); g_assert_true(ret); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("com.fwupd-internal.Absolute"), g_strdup("off")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_no_error(error); g_assert_true(ret); /* string */ attr2 = fu_context_get_bios_setting(fu_engine_get_context(engine), "com.fwupd-internal.Asset"); g_assert_nonnull(attr2); current = fwupd_bios_setting_get_current_value(attr2); g_assert_nonnull(current); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("Asset"), g_strdup("0")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_no_error(error); g_assert_true(ret); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("Asset"), g_strdup("1")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_no_error(error); g_assert_true(ret); g_hash_table_remove_all(bios_settings); g_hash_table_insert( bios_settings, g_strdup("Absolute"), g_strdup("1234567891123456789112345678911234567891123456789112345678911111")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_clear_error(&error); /* integer */ attr3 = fu_context_get_bios_setting(fu_engine_get_context(engine), "com.fwupd-internal.CustomChargeStop"); g_assert_nonnull(attr3); current = fwupd_bios_setting_get_current_value(attr3); g_assert_nonnull(current); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("75")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_true(ret); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("110")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_clear_error(&error); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("1")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_clear_error(&error); /* force it to read only */ g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("70")); ret = fu_engine_modify_bios_settings(engine, bios_settings, TRUE, &error); g_assert_no_error(error); g_assert_true(ret); /* Read Only */ attr4 = fu_context_get_bios_setting(fu_engine_get_context(engine), "com.fwupd-internal.pending_reboot"); g_assert_nonnull(attr4); current = fwupd_bios_setting_get_current_value(attr4); g_assert_nonnull(current); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("pending_reboot"), g_strdup("foo")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_clear_error(&error); g_hash_table_remove_all(bios_settings); g_hash_table_insert(bios_settings, g_strdup("CustomChargeStop"), g_strdup("80")); ret = fu_engine_modify_bios_settings(engine, bios_settings, FALSE, &error); g_assert_false(ret); g_assert_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED); g_clear_error(&error); } static void fu_remote_list_repair_func(void) { FwupdRemote *remote; gboolean ret; g_autoptr(FuRemoteList) remote_list = fu_remote_list_new(); g_autoptr(GError) error = NULL; fu_remote_list_set_lvfs_metadata_format(remote_list, "zst"); ret = fu_remote_list_load(remote_list, FU_REMOTE_LIST_LOAD_FLAG_FIX_METADATA_URI, &error); g_assert_no_error(error); g_assert_true(ret); /* check .gz converted to .zst */ remote = fu_remote_list_get_by_id(remote_list, "legacy-lvfs"); g_assert_nonnull(remote); g_assert_cmpstr(fwupd_remote_get_metadata_uri(remote), ==, "http://localhost/stable.xml.zst"); /* check .xz converted to .zst */ remote = fu_remote_list_get_by_id(remote_list, "legacy-lvfs-xz"); g_assert_nonnull(remote); g_assert_cmpstr(fwupd_remote_get_metadata_uri(remote), ==, "http://localhost/stable.xml.zst"); /* check non-LVFS remote NOT .gz converted to .xz */ remote = fu_remote_list_get_by_id(remote_list, "legacy"); g_assert_nonnull(remote); g_assert_cmpstr(fwupd_remote_get_metadata_uri(remote), ==, "http://localhost/stable.xml.gz"); } static void fu_config_migrate_1_9_func(void) { const gchar *fake_localconf_fn = "/tmp/fwupd-self-test/var/etc/fwupd/fwupd.conf"; const gchar *fake_sysconf_fn = "/tmp/fwupd-self-test/fwupd/fwupd.conf"; gboolean ret; g_autofree gchar *localconf_data = NULL; g_autoptr(FuConfig) config = FU_CONFIG(fu_engine_config_new()); g_autoptr(GError) error = NULL; /* ensure empty tree */ fu_self_test_mkroot(); (void)g_unsetenv("CONFIGURATION_DIRECTORY"); (void)g_setenv("FWUPD_SYSCONFDIR", "/tmp/fwupd-self-test", TRUE); ret = fu_path_mkdir_parent(fake_sysconf_fn, &error); g_assert_no_error(error); g_assert_true(ret); ret = g_file_set_contents(fake_sysconf_fn, "# use `man 5 fwupd.conf` for documentation\n" "[fwupd]\n" "DisabledPlugins=test;test_ble\n" "OnlyTrusted=true\n" "AllowEmulation=false\n", -1, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_config_load(config, &error); g_assert_no_error(error); g_assert_true(ret); ret = fu_config_set_value(config, "fwupd", "AllowEmulation", "true", &error); g_assert_no_error(error); g_assert_true(ret); /* ensure that all keys except AllowEmulation migrated */ ret = g_file_get_contents(fake_localconf_fn, &localconf_data, NULL, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpstr(localconf_data, ==, "[fwupd]\n" "AllowEmulation=true\n"); } static void fu_config_set_plugin_defaults(FuConfig *config) { /* these are correct for v2.0.0 */ fu_config_set_default(config, "msr", "MinimumSmeKernelVersion", "5.18.0"); fu_config_set_default(config, "redfish", "CACheck", "false"); fu_config_set_default(config, "redfish", "IpmiDisableCreateUser", "false"); fu_config_set_default(config, "redfish", "ManagerResetTimeout", "1800"); /* seconds */ fu_config_set_default(config, "redfish", "Password", NULL); fu_config_set_default(config, "redfish", "Uri", NULL); fu_config_set_default(config, "redfish", "Username", NULL); fu_config_set_default(config, "redfish", "UserUri", NULL); fu_config_set_default(config, "thunderbolt", "DelayedActivation", "false"); fu_config_set_default(config, "thunderbolt", "MinimumKernelVersion", "4.13.0"); fu_config_set_default(config, "uefi-capsule", "DisableCapsuleUpdateOnDisk", "false"); fu_config_set_default(config, "uefi-capsule", "DisableShimForSecureBoot", "false"); fu_config_set_default(config, "uefi-capsule", "EnableEfiDebugging", "false"); fu_config_set_default(config, "uefi-capsule", "EnableGrubChainLoad", "false"); fu_config_set_default(config, "uefi-capsule", "OverrideESPMountPoint", NULL); fu_config_set_default(config, "uefi-capsule", "RebootCleanup", "true"); fu_config_set_default(config, "uefi-capsule", "RequireESPFreeSpace", "0"); } static void fu_config_migrate_1_7_func(void) { const gchar *sysconfdir = "/tmp/fwupd-self-test/conf-migration-1.7/var/etc"; gboolean ret; const gchar *fn_merge[] = {"daemon.conf", "msr.conf", "redfish.conf", "thunderbolt.conf", "uefi_capsule.conf", NULL}; g_autofree gchar *localconf_data = NULL; g_autofree gchar *fn_mut = NULL; g_autofree gchar *testdatadir = NULL; g_autoptr(FuConfig) config = FU_CONFIG(fu_engine_config_new()); g_autoptr(GError) error = NULL; /* ensure empty tree */ fu_self_test_mkroot(); /* source directory and data */ testdatadir = g_test_build_filename(G_TEST_DIST, "tests", "conf-migration-1.7", NULL); if (!g_file_test(testdatadir, G_FILE_TEST_EXISTS)) { g_test_skip("missing fwupd 1.7.x migration test data"); return; } /* working directory */ (void)g_setenv("FWUPD_SYSCONFDIR", sysconfdir, TRUE); (void)g_unsetenv("CONFIGURATION_DIRECTORY"); fn_mut = g_build_filename(sysconfdir, "fwupd", "fwupd.conf", NULL); g_assert_nonnull(fn_mut); ret = fu_path_mkdir_parent(fn_mut, &error); g_assert_no_error(error); g_assert_true(ret); /* copy all files to working directory */ for (guint i = 0; fn_merge[i] != NULL; i++) { g_autofree gchar *source = g_build_filename(testdatadir, "fwupd", fn_merge[i], NULL); g_autofree gchar *target = g_build_filename(sysconfdir, "fwupd", fn_merge[i], NULL); fu_test_copy_file(source, target); } /* we don't want to run all the plugins just to get the _init() defaults */ fu_config_set_plugin_defaults(config); ret = fu_config_load(config, &error); g_assert_no_error(error); g_assert_true(ret); /* make sure all migrated files were renamed */ for (guint i = 0; fn_merge[i] != NULL; i++) { g_autofree gchar *old = g_build_filename(sysconfdir, "fwupd", fn_merge[i], NULL); g_autofree gchar *new = g_strdup_printf("%s.old", old); ret = g_file_test(old, G_FILE_TEST_EXISTS); g_assert_false(ret); ret = g_file_test(new, G_FILE_TEST_EXISTS); g_assert_true(ret); } /* ensure all default keys migrated */ ret = g_file_get_contents(fn_mut, &localconf_data, NULL, &error); g_assert_no_error(error); g_assert_true(ret); g_assert_cmpstr(localconf_data, ==, ""); } int main(int argc, char **argv) { gboolean ret; g_autofree gchar *testdatadir = NULL; g_autoptr(GError) error = NULL; g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC); g_autoptr(FuTest) self = g_new0(FuTest, 1); (void)g_setenv("G_TEST_SRCDIR", SRCDIR, FALSE); g_test_init(&argc, &argv, NULL); /* only critical and error are fatal */ g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL); (void)g_setenv("G_MESSAGES_DEBUG", "all", TRUE); testdatadir = g_test_build_filename(G_TEST_DIST, "tests", NULL); (void)g_setenv("FWUPD_DATADIR", testdatadir, TRUE); (void)g_setenv("FWUPD_LIBDIR_PKG", testdatadir, TRUE); (void)g_setenv("FWUPD_SYSCONFDIR", testdatadir, TRUE); (void)g_setenv("FWUPD_SYSFSFWDIR", testdatadir, TRUE); (void)g_setenv("CONFIGURATION_DIRECTORY", testdatadir, TRUE); (void)g_setenv("FWUPD_OFFLINE_TRIGGER", "/tmp/fwupd-self-test/system-update", TRUE); (void)g_setenv("FWUPD_LOCALSTATEDIR", "/tmp/fwupd-self-test/var", TRUE); (void)g_setenv("FWUPD_SYSFSFWATTRIBDIR", testdatadir, TRUE); (void)g_setenv("FWUPD_SELF_TEST", "1", TRUE); /* ensure empty tree */ fu_self_test_mkroot(); /* do not save silo */ self->ctx = fu_context_new(); ret = fu_context_load_quirks(self->ctx, FU_QUIRKS_LOAD_FLAG_NO_CACHE, &error); g_assert_no_error(error); g_assert_true(ret); /* load dummy hwids */ ret = fu_context_load_hwinfo(self->ctx, progress, FU_CONTEXT_HWID_FLAG_LOAD_CONFIG, &error); g_assert_no_error(error); g_assert_true(ret); /* load the test plugin */ self->plugin = fu_plugin_new_from_gtype(fu_test_plugin_get_type(), self->ctx); /* tests go here */ if (g_test_slow()) { g_test_add_data_func("/fwupd/console", self, fu_console_func); } g_test_add_func("/fwupd/idle", fu_idle_func); g_test_add_func("/fwupd/client-list", fu_client_list_func); g_test_add_func("/fwupd/remote-list{repair}", fu_remote_list_repair_func); g_test_add_data_func("/fwupd/backend{usb}", self, fu_backend_usb_func); g_test_add_data_func("/fwupd/backend{usb-invalid}", self, fu_backend_usb_invalid_func); g_test_add_data_func("/fwupd/plugin{module}", self, fu_plugin_module_func); g_test_add_data_func("/fwupd/memcpy", self, fu_memcpy_func); g_test_add_func("/fwupd/cabinet", fu_common_cabinet_func); g_test_add_data_func("/fwupd/security-attr", self, fu_security_attr_func); g_test_add_data_func("/fwupd/device-list", self, fu_device_list_func); g_test_add_data_func("/fwupd/device-list{delay}", self, fu_device_list_delay_func); g_test_add_data_func("/fwupd/device-list{explicit-order}", self, fu_device_list_explicit_order_func); g_test_add_data_func("/fwupd/device-list{explicit-order-post}", self, fu_device_list_explicit_order_post_func); g_test_add_data_func("/fwupd/device-list{no-auto-remove-children}", self, fu_device_list_no_auto_remove_children_func); g_test_add_data_func("/fwupd/device-list{compatible}", self, fu_device_list_compatible_func); g_test_add_data_func("/fwupd/device-list{remove-chain}", self, fu_device_list_remove_chain_func); g_test_add_data_func("/fwupd/release{compare}", self, fu_release_compare_func); g_test_add_func("/fwupd/release{uri-scheme}", fu_release_uri_scheme_func); g_test_add_data_func("/fwupd/release{trusted-report}", self, fu_release_trusted_report_func); g_test_add_data_func("/fwupd/release{trusted-report-oem}", self, fu_release_trusted_report_oem_func); g_test_add_data_func("/fwupd/release{no-trusted-report-upgrade}", self, fu_release_no_trusted_report_upgrade_func); g_test_add_data_func("/fwupd/release{no-trusted-report}", self, fu_release_no_trusted_report_func); g_test_add_data_func("/fwupd/engine{get-details-added}", self, fu_engine_get_details_added_func); g_test_add_data_func("/fwupd/engine{get-details-missing}", self, fu_engine_get_details_missing_func); g_test_add_data_func("/fwupd/engine{device-unlock}", self, fu_engine_device_unlock_func); g_test_add_data_func("/fwupd/engine{device-md-set-flags}", self, fu_engine_device_md_set_flags_func); g_test_add_data_func("/fwupd/engine{multiple-releases}", self, fu_engine_multiple_rels_func); g_test_add_data_func("/fwupd/engine{install-request}", self, fu_engine_install_request); g_test_add_data_func("/fwupd/engine{history-success}", self, fu_engine_history_func); g_test_add_data_func("/fwupd/engine{history-verfmt}", self, fu_engine_history_verfmt_func); g_test_add_data_func("/fwupd/engine{history-modify}", self, fu_engine_history_modify_func); g_test_add_data_func("/fwupd/engine{history-error}", self, fu_engine_history_error_func); if (g_test_slow()) { g_test_add_data_func("/fwupd/device-list{replug-auto}", self, fu_device_list_replug_auto_func); } g_test_add_data_func("/fwupd/device-list{replug-user}", self, fu_device_list_replug_user_func); g_test_add_data_func("/fwupd/engine{require-hwid}", self, fu_engine_require_hwid_func); g_test_add_data_func("/fwupd/engine{requires-reboot}", self, fu_engine_install_needs_reboot); g_test_add_data_func("/fwupd/engine{history-inherit}", self, fu_engine_history_inherit); g_test_add_data_func("/fwupd/engine{partial-hash}", self, fu_engine_partial_hash_func); g_test_add_data_func("/fwupd/engine{downgrade}", self, fu_engine_downgrade_func); g_test_add_data_func("/fwupd/engine{md-verfmt}", self, fu_engine_md_verfmt_func); g_test_add_data_func("/fwupd/engine{requirements-success}", self, fu_engine_requirements_func); g_test_add_data_func("/fwupd/engine{requirements-soft}", self, fu_engine_requirements_soft_func); g_test_add_data_func("/fwupd/engine{requirements-missing}", self, fu_engine_requirements_missing_func); g_test_add_data_func("/fwupd/engine{requirements-client-fail}", self, fu_engine_requirements_client_fail_func); g_test_add_data_func("/fwupd/engine{requirements-client-invalid}", self, fu_engine_requirements_client_invalid_func); g_test_add_data_func("/fwupd/engine{requirements-client-pass}", self, fu_engine_requirements_client_pass_func); g_test_add_data_func("/fwupd/engine{requirements-not-hardware}", self, fu_engine_requirements_not_hardware_func); g_test_add_data_func("/fwupd/engine{requirements-version-require}", self, fu_engine_requirements_version_require_func); g_test_add_data_func("/fwupd/engine{requirements-version-lowest}", self, fu_engine_requirements_version_lowest_func); g_test_add_data_func("/fwupd/engine{requirements-parent-device}", self, fu_engine_requirements_parent_device_func); g_test_add_data_func("/fwupd/engine{requirements-child-device}", self, fu_engine_requirements_child_device_func); g_test_add_data_func("/fwupd/engine{requirements_protocol_check_func}", self, fu_engine_requirements_protocol_check_func); g_test_add_data_func("/fwupd/engine{requirements-not-child}", self, fu_engine_requirements_child_func); g_test_add_data_func("/fwupd/engine{requirements-not-child-fail}", self, fu_engine_requirements_child_fail_func); g_test_add_data_func("/fwupd/engine{requirements-unsupported}", self, fu_engine_requirements_unsupported_func); g_test_add_data_func("/fwupd/engine{requirements-device}", self, fu_engine_requirements_device_func); g_test_add_data_func("/fwupd/engine{requirements-device-plain}", self, fu_engine_requirements_device_plain_func); g_test_add_data_func("/fwupd/engine{requirements-version-format}", self, fu_engine_requirements_version_format_func); g_test_add_data_func("/fwupd/engine{requirements-only-upgrade}", self, fu_engine_requirements_only_upgrade_func); g_test_add_data_func("/fwupd/engine{device-auto-parent-id}", self, fu_engine_device_parent_id_func); g_test_add_data_func("/fwupd/engine{device-auto-parent-guid}", self, fu_engine_device_parent_guid_func); g_test_add_data_func("/fwupd/engine{install-duration}", self, fu_engine_install_duration_func); g_test_add_data_func("/fwupd/engine{release-dedupe}", self, fu_engine_release_dedupe_func); g_test_add_data_func("/fwupd/engine{generate-md}", self, fu_engine_generate_md_func); g_test_add_data_func("/fwupd/engine{requirements-other-device}", self, fu_engine_requirements_other_device_func); g_test_add_data_func("/fwupd/engine{fu_engine_requirements_sibling_device_func}", self, fu_engine_requirements_sibling_device_func); g_test_add_data_func("/fwupd/plugin{composite}", self, fu_plugin_composite_func); g_test_add_data_func("/fwupd/history", self, fu_history_func); g_test_add_data_func("/fwupd/history{migrate-v1}", self, fu_history_migrate_v1_func); g_test_add_data_func("/fwupd/history{migrate-v2}", self, fu_history_migrate_v2_func); g_test_add_data_func("/fwupd/plugin-list", self, fu_plugin_list_func); g_test_add_data_func("/fwupd/plugin-list{depsolve}", self, fu_plugin_list_depsolve_func); if (g_test_slow()) { g_test_add_func("/fwupd/spawn", fu_spawn_func); g_test_add_func("/fwupd/spawn-timeout", fu_spawn_timeout_func); } g_test_add_func("/fwupd/common{cab-success}", fu_common_store_cab_func); g_test_add_func("/fwupd/common{cab-success-artifact}", fu_common_store_cab_artifact_func); g_test_add_func("/fwupd/common{cab-success-unsigned}", fu_common_store_cab_unsigned_func); g_test_add_func("/fwupd/common{cab-success-folder}", fu_common_store_cab_folder_func); g_test_add_func("/fwupd/common{cab-success-sha256}", fu_common_store_cab_sha256_func); g_test_add_func("/fwupd/common{cab-error-no-metadata}", fu_common_store_cab_error_no_metadata_func); g_test_add_func("/fwupd/common{cab-error-wrong-size}", fu_common_store_cab_error_wrong_size_func); g_test_add_func("/fwupd/common{cab-error-wrong-checksum}", fu_common_store_cab_error_wrong_checksum_func); g_test_add_func("/fwupd/common{cab-error-missing-file}", fu_common_store_cab_error_missing_file_func); g_test_add_func("/fwupd/common{cab-error-size}", fu_common_store_cab_error_size_func); g_test_add_data_func("/fwupd/write-bios-attrs", self, fu_engine_modify_bios_settings_func); /* these need to be last as they overwrite stuff in the mkroot */ g_test_add_func("/fwupd/config_migrate_1_7", fu_config_migrate_1_7_func); g_test_add_func("/fwupd/config_migrate_1_9", fu_config_migrate_1_9_func); return g_test_run(); } fwupd-1.9.16/src/fu-spawn.c000066400000000000000000000123721460375044200154420ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuSpawn" #include "config.h" #include "fu-spawn.h" typedef struct { FuSpawnOutputHandler handler_cb; gpointer user_data; GMainLoop *loop; GSource *source; GInputStream *stream; GCancellable *cancellable; guint timeout_id; } FuSpawnHelper; static void fu_spawn_create_pollable_source(FuSpawnHelper *helper); static gboolean fu_spawn_source_pollable_cb(GObject *stream, gpointer user_data) { FuSpawnHelper *helper = (FuSpawnHelper *)user_data; gchar buffer[1024]; gssize sz; g_auto(GStrv) split = NULL; g_autoptr(GError) error = NULL; /* read from stream */ sz = g_pollable_input_stream_read_nonblocking(G_POLLABLE_INPUT_STREAM(stream), buffer, sizeof(buffer) - 1, NULL, &error); if (sz < 0) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { g_warning("failed to get read from nonblocking fd: %s", error->message); } return G_SOURCE_REMOVE; } /* no read possible */ if (sz == 0) g_main_loop_quit(helper->loop); /* emit lines */ if (helper->handler_cb != NULL) { buffer[sz] = '\0'; split = g_strsplit(buffer, "\n", -1); for (guint i = 0; split[i] != NULL; i++) { if (split[i][0] == '\0') continue; helper->handler_cb(split[i], helper->user_data); } } /* set up the source for the next read */ fu_spawn_create_pollable_source(helper); return G_SOURCE_REMOVE; } static void fu_spawn_create_pollable_source(FuSpawnHelper *helper) { if (helper->source != NULL) g_source_destroy(helper->source); helper->source = g_pollable_input_stream_create_source(G_POLLABLE_INPUT_STREAM(helper->stream), helper->cancellable); g_source_attach(helper->source, NULL); g_source_set_callback(helper->source, (GSourceFunc)fu_spawn_source_pollable_cb, helper, NULL); } static void fu_spawn_helper_free(FuSpawnHelper *helper) { g_object_unref(helper->cancellable); if (helper->stream != NULL) g_object_unref(helper->stream); if (helper->source != NULL) g_source_destroy(helper->source); if (helper->loop != NULL) g_main_loop_unref(helper->loop); if (helper->timeout_id != 0) g_source_remove(helper->timeout_id); g_free(helper); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuSpawnHelper, fu_spawn_helper_free) #pragma clang diagnostic pop #ifndef _WIN32 static gboolean fu_spawn_timeout_cb(gpointer user_data) { FuSpawnHelper *helper = (FuSpawnHelper *)user_data; g_cancellable_cancel(helper->cancellable); g_main_loop_quit(helper->loop); helper->timeout_id = 0; return G_SOURCE_REMOVE; } static void fu_spawn_cancelled_cb(GCancellable *cancellable, FuSpawnHelper *helper) { /* just propagate */ g_cancellable_cancel(helper->cancellable); } #endif /** * fu_spawn_sync: * @argv: the argument list to run * @handler_cb: (scope call) (closure user_data) (nullable): optional #FuSpawnOutputHandler * @user_data: (nullable): the user data to pass to @handler_cb * @timeout_ms: a timeout in ms, or 0 for no limit * @cancellable: (nullable): optional #GCancellable * @error: (nullable): optional return location for an error * * Runs a subprocess and waits for it to exit. Any output on standard out or * standard error will be forwarded to @handler_cb as whole lines. * * Returns: %TRUE for success * * Since: 0.9.7 **/ gboolean fu_spawn_sync(const gchar *const *argv, FuSpawnOutputHandler handler_cb, gpointer user_data, guint timeout_ms, GCancellable *cancellable, GError **error) { g_autoptr(FuSpawnHelper) helper = NULL; g_autoptr(GSubprocess) subprocess = NULL; g_autofree gchar *argv_str = NULL; #ifndef _WIN32 gulong cancellable_id = 0; #endif g_return_val_if_fail(argv != NULL, FALSE); g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* create subprocess */ argv_str = g_strjoinv(" ", (gchar **)argv); g_debug("running '%s'", argv_str); subprocess = g_subprocess_newv(argv, G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_MERGE, error); if (subprocess == NULL) return FALSE; #ifndef _WIN32 /* watch for process to exit */ helper = g_new0(FuSpawnHelper, 1); helper->handler_cb = handler_cb; helper->user_data = user_data; helper->loop = g_main_loop_new(NULL, FALSE); helper->stream = g_subprocess_get_stdout_pipe(subprocess); /* always create a cancellable, and connect up the parent */ helper->cancellable = g_cancellable_new(); if (cancellable != NULL) { cancellable_id = g_cancellable_connect(cancellable, G_CALLBACK(fu_spawn_cancelled_cb), helper, NULL); } /* allow timeout */ if (timeout_ms > 0) { helper->timeout_id = g_timeout_add(timeout_ms, fu_spawn_timeout_cb, helper); } fu_spawn_create_pollable_source(helper); g_main_loop_run(helper->loop); g_cancellable_disconnect(cancellable, cancellable_id); #endif if (!g_subprocess_wait_check(subprocess, cancellable, error)) return FALSE; #ifndef _WIN32 if (g_cancellable_set_error_if_cancelled(helper->cancellable, error)) return FALSE; #endif /* success */ return TRUE; } fwupd-1.9.16/src/fu-spawn.h000066400000000000000000000011041460375044200154360ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include /** * FuSpawnOutputHandler: * @line: text data * @user_data: (closure): user data * * The process spawn iteration callback. */ typedef void (*FuSpawnOutputHandler)(const gchar *line, gpointer user_data); gboolean fu_spawn_sync(const gchar *const *argv, FuSpawnOutputHandler handler_cb, gpointer user_data, guint timeout_ms, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT; fwupd-1.9.16/src/fu-systemd.c000066400000000000000000000066721460375044200160100ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include "fu-systemd.h" #define SYSTEMD_SERVICE "org.freedesktop.systemd1" #define SYSTEMD_OBJECT_PATH "/org/freedesktop/systemd1" #define SYSTEMD_INTERFACE "org.freedesktop.systemd1" #define SYSTEMD_MANAGER_INTERFACE "org.freedesktop.systemd1.Manager" #define SYSTEMD_UNIT_INTERFACE "org.freedesktop.systemd1.Unit" static GDBusProxy * fu_systemd_get_manager(GError **error) { g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GDBusProxy) proxy = NULL; connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) { g_prefix_error(error, "failed to get bus: "); return NULL; } proxy = g_dbus_proxy_new_sync(connection, G_DBUS_PROXY_FLAGS_NONE, NULL, SYSTEMD_SERVICE, SYSTEMD_OBJECT_PATH, SYSTEMD_MANAGER_INTERFACE, NULL, error); if (proxy == NULL) { g_prefix_error(error, "failed to find %s: ", SYSTEMD_SERVICE); return NULL; } return g_steal_pointer(&proxy); } static gchar * fu_systemd_unit_get_path(GDBusProxy *proxy_manager, const gchar *unit, GError **error) { g_autofree gchar *path = NULL; g_autoptr(GVariant) val = NULL; val = g_dbus_proxy_call_sync(proxy_manager, "GetUnit", g_variant_new("(s)", unit), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (val == NULL) { g_prefix_error(error, "failed to find %s: ", unit); return NULL; } g_variant_get(val, "(o)", &path); return g_steal_pointer(&path); } static GDBusProxy * fu_systemd_unit_get_proxy(GDBusProxy *proxy_manager, const gchar *unit, GError **error) { g_autofree gchar *path = NULL; g_autoptr(GDBusProxy) proxy_unit = NULL; path = fu_systemd_unit_get_path(proxy_manager, unit, error); if (path == NULL) return NULL; proxy_unit = g_dbus_proxy_new_sync(g_dbus_proxy_get_connection(proxy_manager), G_DBUS_PROXY_FLAGS_NONE, NULL, SYSTEMD_SERVICE, path, SYSTEMD_UNIT_INTERFACE, NULL, error); if (proxy_unit == NULL) { g_prefix_error(error, "failed to register proxy for %s: ", path); return NULL; } return g_steal_pointer(&proxy_unit); } gchar * fu_systemd_get_default_target(GError **error) { const gchar *path = NULL; g_autoptr(GDBusProxy) proxy_manager = NULL; g_autoptr(GVariant) val = NULL; proxy_manager = fu_systemd_get_manager(error); if (proxy_manager == NULL) return NULL; val = g_dbus_proxy_call_sync(proxy_manager, "GetDefaultTarget", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); if (val == NULL) return NULL; g_variant_get(val, "(&s)", &path); return g_strdup(path); } gboolean fu_systemd_unit_stop(const gchar *unit, GError **error) { g_autoptr(GDBusProxy) proxy_manager = NULL; g_autoptr(GDBusProxy) proxy_unit = NULL; g_autoptr(GVariant) val = NULL; g_return_val_if_fail(unit != NULL, FALSE); proxy_manager = fu_systemd_get_manager(error); if (proxy_manager == NULL) return FALSE; proxy_unit = fu_systemd_unit_get_proxy(proxy_manager, unit, error); if (proxy_unit == NULL) return FALSE; val = g_dbus_proxy_call_sync(proxy_unit, "Stop", g_variant_new("(s)", "replace"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); return val != NULL; } fwupd-1.9.16/src/fu-systemd.h000066400000000000000000000004061460375044200160020ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include gboolean fu_systemd_unit_stop(const gchar *unit, GError **error); gchar * fu_systemd_get_default_target(GError **error); fwupd-1.9.16/src/fu-tool.c000066400000000000000000004305401460375044200152700ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include "config.h" #include #include #include #ifdef HAVE_GIO_UNIX #include #endif #include #include #include #include #include #include "fwupd-bios-setting-private.h" #include "fwupd-client-private.h" #include "fwupd-common-private.h" #include "fwupd-device-private.h" #include "fwupd-enums-private.h" #include "fwupd-plugin-private.h" #include "fwupd-remote-private.h" #include "fu-bios-settings-private.h" #include "fu-cabinet.h" #include "fu-console.h" #include "fu-context-private.h" #include "fu-debug.h" #include "fu-device-private.h" #include "fu-engine-helper.h" #include "fu-engine-requirements.h" #include "fu-engine.h" #include "fu-history.h" #include "fu-plugin-private.h" #include "fu-security-attr-common.h" #include "fu-security-attrs-private.h" #include "fu-smbios-private.h" #include "fu-util-bios-setting.h" #include "fu-util-common.h" #ifdef HAVE_SYSTEMD #include "fu-systemd.h" #define SYSTEMD_FWUPD_UNIT "fwupd.service" #define SYSTEMD_SNAP_FWUPD_UNIT "snap.fwupd.fwupd.service" #endif /* custom return codes */ #define EXIT_NOTHING_TO_DO 2 #define EXIT_NOT_FOUND 3 typedef enum { FU_UTIL_OPERATION_UNKNOWN, FU_UTIL_OPERATION_UPDATE, FU_UTIL_OPERATION_INSTALL, FU_UTIL_OPERATION_READ, FU_UTIL_OPERATION_LAST } FuUtilOperation; struct FuUtilPrivate { GCancellable *cancellable; GMainContext *main_ctx; GMainLoop *loop; GOptionContext *context; FuEngine *engine; FuEngineRequest *request; FuProgress *progress; FuConsole *console; FwupdClient *client; gboolean as_json; gboolean no_reboot_check; gboolean no_safety_check; gboolean no_device_prompt; gboolean prepare_blob; gboolean cleanup_blob; gboolean enable_json_state; gboolean interactive; FwupdInstallFlags flags; gboolean show_all; gboolean disable_ssl_strict; gint lock_fd; /* only valid in update and downgrade */ FuUtilOperation current_operation; FwupdDevice *current_device; GPtrArray *post_requests; FwupdDeviceFlags completion_flags; FwupdDeviceFlags filter_device_include; FwupdDeviceFlags filter_device_exclude; FwupdReleaseFlags filter_release_include; FwupdReleaseFlags filter_release_exclude; }; static void fu_util_client_notify_cb(GObject *object, GParamSpec *pspec, FuUtilPrivate *priv) { if (priv->as_json) return; fu_console_set_progress(priv->console, fwupd_client_get_status(priv->client), fwupd_client_get_percentage(priv->client)); } static void fu_util_show_plugin_warnings(FuUtilPrivate *priv) { FwupdPluginFlags flags = FWUPD_PLUGIN_FLAG_NONE; GPtrArray *plugins; /* get a superset so we do not show the same message more than once */ plugins = fu_engine_get_plugins(priv->engine); for (guint i = 0; i < plugins->len; i++) { FwupdPlugin *plugin = g_ptr_array_index(plugins, i); if (fwupd_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) continue; if (!fwupd_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_USER_WARNING)) continue; flags |= fwupd_plugin_get_flags(plugin); } /* never show these, they're way too generic */ flags &= ~FWUPD_PLUGIN_FLAG_DISABLED; flags &= ~FWUPD_PLUGIN_FLAG_NO_HARDWARE; flags &= ~FWUPD_PLUGIN_FLAG_REQUIRE_HWID; flags &= ~FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY; /* print */ for (guint i = 0; i < 64; i++) { FwupdPluginFlags flag = (guint64)1 << i; const gchar *tmp; g_autofree gchar *url = NULL; if ((flags & flag) == 0) continue; tmp = fu_util_plugin_flag_to_string((guint64)1 << i); if (tmp == NULL) continue; fu_console_print_full(priv->console, FU_CONSOLE_PRINT_FLAG_WARNING, "%s\n", tmp); url = g_strdup_printf("https://github.com/fwupd/fwupd/wiki/PluginFlag:%s", fwupd_plugin_flag_to_string(flag)); /* TRANSLATORS: %s is a link to a website */ fu_console_print(priv->console, _("See %s for more information."), url); } } static gboolean fu_util_lock(FuUtilPrivate *priv, GError **error) { #ifdef HAVE_WRLCK struct flock lockp = { .l_type = F_WRLCK, .l_whence = SEEK_SET, }; g_autofree gchar *lockfn = NULL; gboolean use_user = FALSE; #ifdef HAVE_GETUID if (getuid() != 0 || geteuid() != 0) use_user = TRUE; #endif /* open file */ if (use_user) { lockfn = fu_util_get_user_cache_path("fwupdtool"); } else { g_autofree gchar *lockdir = fu_path_from_kind(FU_PATH_KIND_LOCKDIR); lockfn = g_build_filename(lockdir, "fwupdtool", NULL); } if (!fu_path_mkdir_parent(lockfn, error)) return FALSE; priv->lock_fd = g_open(lockfn, O_RDWR | O_CREAT, S_IRWXU); if (priv->lock_fd < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to open %s", lockfn); return FALSE; } /* write lock */ #ifdef HAVE_OFD if (fcntl(priv->lock_fd, F_OFD_SETLK, &lockp) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "another instance has locked %s", lockfn); return FALSE; } #else if (fcntl(priv->lock_fd, F_SETLK, &lockp) < 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "another instance has locked %s", lockfn); return FALSE; } #endif /* success */ g_debug("locked %s", lockfn); #endif return TRUE; } #ifdef HAVE_SYSTEMD static const gchar * fu_util_get_systemd_unit(void) { if (g_strcmp0(g_getenv("SNAP_NAME"), "fwupd") == 0) return SYSTEMD_SNAP_FWUPD_UNIT; return SYSTEMD_FWUPD_UNIT; } #endif static gboolean fu_util_start_engine(FuUtilPrivate *priv, FuEngineLoadFlags flags, FuProgress *progress, GError **error) { if (!fu_util_lock(priv, error)) { /* TRANSLATORS: another fwupdtool instance is already running */ g_prefix_error(error, "%s: ", _("Failed to lock")); return FALSE; } #ifdef HAVE_SYSTEMD if (getuid() != 0 || geteuid() != 0) { g_info("not attempting to stop daemon when running as user"); } else { g_autoptr(GError) error_local = NULL; if (!fu_systemd_unit_stop(fu_util_get_systemd_unit(), &error_local)) g_info("failed to stop daemon: %s", error_local->message); } #endif flags |= FU_ENGINE_LOAD_FLAG_NO_IDLE_SOURCES; flags |= FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS; flags |= FU_ENGINE_LOAD_FLAG_HWINFO; if (!fu_engine_load(priv->engine, flags, progress, error)) return FALSE; fu_util_show_plugin_warnings(priv); fu_util_show_unsupported_warning(priv->console); /* copy properties from engine to client */ g_object_set(priv->client, "host-vendor", fu_engine_get_host_vendor(priv->engine), "host-product", fu_engine_get_host_product(priv->engine), "battery-level", fu_context_get_battery_level(fu_engine_get_context(priv->engine)), "battery-threshold", fu_context_get_battery_threshold(fu_engine_get_context(priv->engine)), NULL); /* success */ return TRUE; } static void fu_util_maybe_prefix_sandbox_error(const gchar *value, GError **error) { g_autofree gchar *path = g_path_get_dirname(value); if (!g_file_test(path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { g_prefix_error(error, "Unable to access %s. You may need to copy %s to %s: ", path, value, g_getenv("HOME")); } } static void fu_util_cancelled_cb(GCancellable *cancellable, gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *)user_data; /* TRANSLATORS: this is when a device ctrl+c's a watch */ fu_console_print_literal(priv->console, _("Cancelled")); g_main_loop_quit(priv->loop); } static gboolean fu_util_smbios_dump(FuUtilPrivate *priv, gchar **values, GError **error) { g_autofree gchar *tmp = NULL; g_autoptr(FuSmbios) smbios = NULL; if (g_strv_length(values) < 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } smbios = fu_smbios_new(); if (!fu_smbios_setup_from_file(smbios, values[0], error)) return FALSE; tmp = fu_firmware_to_string(FU_FIRMWARE(smbios)); fu_console_print_literal(priv->console, tmp); return TRUE; } #ifdef HAVE_GIO_UNIX static gboolean fu_util_sigint_cb(gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *)user_data; g_info("handling SIGINT"); g_cancellable_cancel(priv->cancellable); return FALSE; } #endif static void fu_util_setup_signal_handlers(FuUtilPrivate *priv) { #ifdef HAVE_GIO_UNIX g_autoptr(GSource) source = g_unix_signal_source_new(SIGINT); g_source_set_callback(source, fu_util_sigint_cb, priv, NULL); g_source_attach(g_steal_pointer(&source), priv->main_ctx); #endif } static void fu_util_private_free(FuUtilPrivate *priv) { if (priv->current_device != NULL) g_object_unref(priv->current_device); if (priv->engine != NULL) g_object_unref(priv->engine); if (priv->request != NULL) g_object_unref(priv->request); if (priv->client != NULL) g_object_unref(priv->client); if (priv->main_ctx != NULL) g_main_context_unref(priv->main_ctx); if (priv->loop != NULL) g_main_loop_unref(priv->loop); if (priv->cancellable != NULL) g_object_unref(priv->cancellable); if (priv->console != NULL) g_object_unref(priv->console); if (priv->progress != NULL) g_object_unref(priv->progress); if (priv->context != NULL) g_option_context_free(priv->context); if (priv->lock_fd >= 0) g_close(priv->lock_fd, NULL); g_ptr_array_unref(priv->post_requests); g_free(priv); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free) #pragma clang diagnostic pop static void fu_util_update_device_request_cb(FwupdClient *client, FwupdRequest *request, FuUtilPrivate *priv) { /* action has not been assigned yet */ if (priv->current_operation == FU_UTIL_OPERATION_UNKNOWN) return; /* nothing sensible to show */ if (fwupd_request_get_message(request) == NULL) return; /* show this now */ if (fwupd_request_get_kind(request) == FWUPD_REQUEST_KIND_IMMEDIATE) { g_autofree gchar *fmt = NULL; g_autofree gchar *tmp = NULL; /* TRANSLATORS: the user needs to do something, e.g. remove the device */ fmt = fu_console_color_format(_("Action Required:"), FU_CONSOLE_COLOR_RED); tmp = g_strdup_printf("%s %s", fmt, fwupd_request_get_message(request)); fu_console_set_progress_title(priv->console, tmp); fu_console_beep(priv->console, 5); } /* save for later */ if (fwupd_request_get_kind(request) == FWUPD_REQUEST_KIND_POST) g_ptr_array_add(priv->post_requests, g_object_ref(request)); } static void fu_main_engine_device_added_cb(FuEngine *engine, FuDevice *device, FuUtilPrivate *priv) { g_autofree gchar *tmp = fu_device_to_string(device); g_debug("ADDED:\n%s", tmp); } static void fu_main_engine_device_removed_cb(FuEngine *engine, FuDevice *device, FuUtilPrivate *priv) { g_autofree gchar *tmp = fu_device_to_string(device); g_debug("REMOVED:\n%s", tmp); } static void fu_main_engine_status_changed_cb(FuEngine *engine, FwupdStatus status, FuUtilPrivate *priv) { if (priv->as_json) return; fu_console_set_progress(priv->console, status, 0); } static void fu_util_progress_percentage_changed_cb(FuProgress *progress, guint percentage, FuUtilPrivate *priv) { if (priv->as_json) return; fu_console_set_progress(priv->console, fu_progress_get_status(progress), percentage); } static void fu_util_progress_status_changed_cb(FuProgress *progress, FwupdStatus status, FuUtilPrivate *priv) { if (priv->as_json) return; fu_console_set_progress(priv->console, status, fu_progress_get_percentage(progress)); } static gboolean fu_util_watch(FuUtilPrivate *priv, gchar **values, GError **error) { if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG, priv->progress, error)) return FALSE; g_main_loop_run(priv->loop); return TRUE; } static gint fu_util_plugin_name_sort_cb(FuPlugin **item1, FuPlugin **item2) { return fu_plugin_name_compare(*item1, *item2); } static gboolean fu_util_get_plugins_as_json(FuUtilPrivate *priv, GPtrArray *plugins, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Plugins"); json_builder_begin_array(builder); for (guint i = 0; i < plugins->len; i++) { FwupdPlugin *plugin = g_ptr_array_index(plugins, i); json_builder_begin_object(builder); fwupd_plugin_to_json(plugin, builder); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(priv->console, builder, error); } static gboolean fu_util_get_plugins(FuUtilPrivate *priv, gchar **values, GError **error) { GPtrArray *plugins; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_READONLY, priv->progress, error)) return FALSE; /* print */ plugins = fu_engine_get_plugins(priv->engine); g_ptr_array_sort(plugins, (GCompareFunc)fu_util_plugin_name_sort_cb); if (priv->as_json) return fu_util_get_plugins_as_json(priv, plugins, error); /* print */ for (guint i = 0; i < plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index(plugins, i); g_autofree gchar *str = fu_util_plugin_to_string(FWUPD_PLUGIN(plugin), 0); fu_console_print_literal(priv->console, str); } if (plugins->len == 0) { /* TRANSLATORS: nothing found */ fu_console_print_literal(priv->console, _("No plugins found")); } return TRUE; } static FuDevice * fu_util_prompt_for_device(FuUtilPrivate *priv, GPtrArray *devices_opt, GError **error) { FuDevice *dev; guint idx; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices_filtered = NULL; /* get devices from daemon */ if (devices_opt != NULL) { devices = g_ptr_array_ref(devices_opt); } else { devices = fu_engine_get_devices(priv->engine, error); if (devices == NULL) return NULL; } fwupd_device_array_ensure_parents(devices); /* filter results */ devices_filtered = fwupd_device_array_filter_flags(devices, priv->filter_device_include, priv->filter_device_exclude, error); if (devices_filtered == NULL) return NULL; /* exactly one */ if (devices_filtered->len == 1) { dev = g_ptr_array_index(devices_filtered, 0); if (!priv->as_json) { fu_console_print( priv->console, "%s: %s", /* TRANSLATORS: device has been chosen by the daemon for the user */ _("Selected device"), fu_device_get_name(dev)); } return g_object_ref(dev); } /* no questions */ if (priv->no_device_prompt) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "can't prompt for devices"); return NULL; } /* TRANSLATORS: this is to abort the interactive prompt */ fu_console_print(priv->console, "0.\t%s", _("Cancel")); for (guint i = 0; i < devices_filtered->len; i++) { dev = g_ptr_array_index(devices_filtered, i); fu_console_print(priv->console, "%u.\t%s (%s)", i + 1, fu_device_get_id(dev), fu_device_get_name(dev)); } /* TRANSLATORS: get interactive prompt */ idx = fu_console_input_uint(priv->console, devices_filtered->len, "%s", _("Choose device")); if (idx == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return NULL; } dev = g_ptr_array_index(devices_filtered, idx - 1); return g_object_ref(dev); } static FuDevice * fu_util_get_device(FuUtilPrivate *priv, const gchar *id, GError **error) { if (fwupd_guid_is_valid(id)) { g_autoptr(GPtrArray) devices = NULL; devices = fu_engine_get_devices_by_guid(priv->engine, id, error); if (devices == NULL) return NULL; return fu_util_prompt_for_device(priv, devices, error); } /* did this look like a GUID? */ for (guint i = 0; id[i] != '\0'; i++) { if (id[i] == '-') { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return NULL; } } return fu_engine_get_device(priv->engine, id, error); } static gboolean fu_util_get_updates(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_autoptr(GNode) root = g_node_new(NULL); g_autoptr(GPtrArray) devices_no_support = g_ptr_array_new(); g_autoptr(GPtrArray) devices_no_upgrades = g_ptr_array_new(); /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; /* parse arguments */ if (g_strv_length(values) == 0) { devices = fu_engine_get_devices(priv->engine, error); if (devices == NULL) return FALSE; } else if (g_strv_length(values) == 1) { FuDevice *device; device = fu_util_get_device(priv, values[0], error); if (device == NULL) return FALSE; devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_ptr_array_add(devices, device); } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } fwupd_device_array_ensure_parents(devices); g_ptr_array_sort(devices, fu_util_sort_devices_by_flags_cb); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); g_autoptr(GPtrArray) rels = NULL; g_autoptr(GError) error_local = NULL; GNode *child; /* not going to have results, so save a engine round-trip */ if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE) && !fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)) continue; if (!fwupd_device_match_flags(dev, priv->filter_device_include, priv->filter_device_exclude)) continue; if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { g_ptr_array_add(devices_no_support, dev); continue; } /* get the releases for this device and filter for validity */ rels = fu_engine_get_upgrades(priv->engine, priv->request, fwupd_device_get_id(dev), &error_local); if (rels == NULL) { g_ptr_array_add(devices_no_upgrades, dev); /* discard the actual reason from user, but leave for debugging */ g_debug("%s", error_local->message); continue; } child = g_node_append_data(root, dev); for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel = g_ptr_array_index(rels, j); if (!fwupd_release_match_flags(rel, priv->filter_release_include, priv->filter_release_exclude)) continue; g_node_append_data(child, g_object_ref(rel)); } } /* devices that have no updates available for whatever reason */ if (devices_no_support->len > 0) { fu_console_print_literal(priv->console, /* TRANSLATORS: message letting the user know no device * upgrade available due to missing on LVFS */ _("Devices with no available firmware updates: ")); for (guint i = 0; i < devices_no_support->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices_no_support, i); fu_console_print(priv->console, " • %s", fwupd_device_get_name(dev)); } } if (devices_no_upgrades->len > 0) { fu_console_print_literal( priv->console, /* TRANSLATORS: message letting the user know no device upgrade available */ _("Devices with the latest available firmware version:")); for (guint i = 0; i < devices_no_upgrades->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices_no_upgrades, i); fu_console_print(priv->console, " • %s", fwupd_device_get_name(dev)); } } /* updates */ if (g_node_n_nodes(root, G_TRAVERSE_ALL) <= 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, _("No updates available for remaining devices")); return FALSE; } fu_util_print_tree(priv->console, priv->client, root); return TRUE; } static gboolean fu_util_get_details(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) array = NULL; g_autoptr(GNode) root = g_node_new(NULL); g_autoptr(GBytes) blob = NULL; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; /* check args */ if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* implied, important for get-details on a device not in your system */ priv->show_all = TRUE; /* open file */ blob = fu_bytes_get_contents(values[0], error); if (blob == NULL) { fu_util_maybe_prefix_sandbox_error(values[0], error); return FALSE; } array = fu_engine_get_details_for_bytes(priv->engine, priv->request, blob, error); if (array == NULL) return FALSE; for (guint i = 0; i < array->len; i++) { FwupdDevice *dev = g_ptr_array_index(array, i); FwupdRelease *rel; GNode *child; if (!fwupd_device_match_flags(dev, priv->filter_device_include, priv->filter_device_exclude)) continue; child = g_node_append_data(root, dev); rel = fwupd_device_get_release_default(dev); if (rel != NULL) g_node_append_data(child, rel); } fu_util_print_tree(priv->console, priv->client, root); return TRUE; } static gboolean fu_util_get_device_flags(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GString) str = g_string_new(NULL); for (FwupdDeviceFlags i = FWUPD_DEVICE_FLAG_INTERNAL; i < FWUPD_DEVICE_FLAG_UNKNOWN; i <<= 1) { const gchar *tmp = fwupd_device_flag_to_string(i); if (tmp == NULL) break; if (i != FWUPD_DEVICE_FLAG_INTERNAL) g_string_append(str, " "); g_string_append(str, tmp); g_string_append(str, " ~"); g_string_append(str, tmp); } fu_console_print_literal(priv->console, str->str); return TRUE; } static void fu_util_build_device_tree(FuUtilPrivate *priv, GNode *root, GPtrArray *devs, FuDevice *dev) { for (guint i = 0; i < devs->len; i++) { FuDevice *dev_tmp = g_ptr_array_index(devs, i); if (!fwupd_device_match_flags(FWUPD_DEVICE(dev_tmp), priv->filter_device_include, priv->filter_device_exclude)) continue; if (!priv->show_all && !fu_util_is_interesting_device(FWUPD_DEVICE(dev_tmp))) continue; if (fu_device_get_parent(dev_tmp) == dev) { GNode *child = g_node_append_data(root, dev_tmp); fu_util_build_device_tree(priv, child, devs, dev_tmp); } } } static gboolean fu_util_get_devices_as_json(FuUtilPrivate *priv, GPtrArray *devs, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Devices"); json_builder_begin_array(builder); for (guint i = 0; i < devs->len; i++) { FuDevice *dev = g_ptr_array_index(devs, i); g_autoptr(GPtrArray) rels = NULL; g_autoptr(GError) error_local = NULL; /* add all releases that could be applied */ rels = fu_engine_get_releases_for_device(priv->engine, priv->request, dev, &error_local); if (rels == NULL) { g_debug("not adding releases to device: %s", error_local->message); } else { for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel = g_ptr_array_index(rels, j); if (!fwupd_release_match_flags(rel, priv->filter_release_include, priv->filter_release_exclude)) continue; fu_device_add_release(dev, rel); } } /* add to builder */ json_builder_begin_object(builder); fwupd_device_to_json_full(FWUPD_DEVICE(dev), builder, FWUPD_DEVICE_FLAG_TRUSTED); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(priv->console, builder, error); } static gboolean fu_util_get_devices(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GNode) root = g_node_new(NULL); g_autoptr(GPtrArray) devs = NULL; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; /* get devices and build tree */ if (g_strv_length(values) > 0) { devs = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; values[i] != NULL; i++) { FuDevice *device = fu_util_get_device(priv, values[i], error); if (device == NULL) return FALSE; g_ptr_array_add(devs, device); } } else { devs = fu_engine_get_devices(priv->engine, error); if (devs == NULL) return FALSE; } /* not for human consumption */ if (priv->as_json) return fu_util_get_devices_as_json(priv, devs, error); if (devs->len > 0) { fwupd_device_array_ensure_parents(devs); fu_util_build_device_tree(priv, root, devs, NULL); } /* print */ if (g_node_n_children(root) == 0) { fu_console_print_literal(priv->console, /* TRANSLATORS: nothing attached that can be upgraded */ _("No hardware detected with firmware update capability")); return TRUE; } fu_util_print_tree(priv->console, priv->client, root); return TRUE; } static void fu_util_update_device_changed_cb(FwupdClient *client, FwupdDevice *device, FuUtilPrivate *priv) { g_autofree gchar *str = NULL; /* allowed to set whenever the device has changed */ if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN; if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)) priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; /* same as last time, so ignore */ if (priv->current_device == NULL || g_strcmp0(fwupd_device_get_composite_id(priv->current_device), fwupd_device_get_composite_id(device)) == 0) { g_set_object(&priv->current_device, device); return; } /* ignore indirect devices that might have changed */ if (fwupd_device_get_status(device) == FWUPD_STATUS_IDLE || fwupd_device_get_status(device) == FWUPD_STATUS_UNKNOWN) { g_debug("ignoring %s with status %s", fwupd_device_get_name(device), fwupd_status_to_string(fwupd_device_get_status(device))); return; } /* show message in console */ if (priv->current_operation == FU_UTIL_OPERATION_UPDATE) { /* TRANSLATORS: %1 is a device name */ str = g_strdup_printf(_("Updating %s…"), fwupd_device_get_name(device)); fu_console_set_progress_title(priv->console, str); } else if (priv->current_operation == FU_UTIL_OPERATION_INSTALL) { /* TRANSLATORS: %1 is a device name */ str = g_strdup_printf(_("Installing on %s…"), fwupd_device_get_name(device)); fu_console_set_progress_title(priv->console, str); } else if (priv->current_operation == FU_UTIL_OPERATION_READ) { /* TRANSLATORS: %1 is a device name */ str = g_strdup_printf(_("Reading from %s…"), fwupd_device_get_name(device)); fu_console_set_progress_title(priv->console, str); } else { g_warning("no FuUtilOperation set"); } g_set_object(&priv->current_device, device); } static void fu_util_display_current_message(FuUtilPrivate *priv) { /* print all POST requests */ for (guint i = 0; i < priv->post_requests->len; i++) { FwupdRequest *request = g_ptr_array_index(priv->post_requests, i); fu_console_print_literal(priv->console, fu_util_request_get_message(request)); } } static gboolean fu_util_install_blob(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(GBytes) blob_fw = NULL; /* progress */ fu_progress_set_id(priv->progress, G_STRLOC); fu_progress_add_flag(priv->progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 2, "parse"); fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 30, "start-engine"); fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_WRITE, 68, NULL); /* invalid args */ if (g_strv_length(values) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* parse blob */ blob_fw = fu_bytes_get_contents(values[0], error); if (blob_fw == NULL) { fu_util_maybe_prefix_sandbox_error(values[0], error); return FALSE; } fu_progress_step_done(priv->progress); /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, fu_progress_get_child(priv->progress), error)) return FALSE; fu_progress_step_done(priv->progress); /* get device */ priv->filter_device_include |= FWUPD_DEVICE_FLAG_UPDATABLE; if (g_strv_length(values) >= 2) { device = fu_util_get_device(priv, values[1], error); if (device == NULL) return FALSE; } else { device = fu_util_prompt_for_device(priv, NULL, error); if (device == NULL) return FALSE; } priv->current_operation = FU_UTIL_OPERATION_INSTALL; g_signal_connect(FU_ENGINE(priv->engine), "device-changed", G_CALLBACK(fu_util_update_device_changed_cb), priv); /* write bare firmware */ if (priv->prepare_blob) { g_autoptr(GPtrArray) devices = NULL; devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_ptr_array_add(devices, g_object_ref(device)); if (!fu_engine_composite_prepare(priv->engine, devices, error)) { g_prefix_error(error, "failed to prepare composite action: "); return FALSE; } } priv->flags |= FWUPD_INSTALL_FLAG_NO_HISTORY; if (!fu_engine_install_blob(priv->engine, device, blob_fw, fu_progress_get_child(priv->progress), priv->flags, fu_engine_request_get_feature_flags(priv->request), error)) return FALSE; fu_progress_step_done(priv->progress); /* cleanup */ if (priv->cleanup_blob) { g_autoptr(FuDevice) device_new = NULL; g_autoptr(GError) error_local = NULL; /* get the possibly new device from the old ID */ device_new = fu_util_get_device(priv, fu_device_get_id(device), &error_local); if (device_new == NULL) { g_debug("failed to find new device: %s", error_local->message); } else { g_autoptr(GPtrArray) devices_new = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_ptr_array_add(devices_new, g_steal_pointer(&device_new)); if (!fu_engine_composite_cleanup(priv->engine, devices_new, error)) { g_prefix_error(error, "failed to cleanup composite action: "); return FALSE; } } } fu_util_display_current_message(priv); /* success */ return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_firmware_sign(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuCabinet) cabinet = fu_cabinet_new(); g_autoptr(GBytes) archive_blob_new = NULL; g_autoptr(GBytes) archive_blob_old = NULL; g_autoptr(GBytes) cert = NULL; g_autoptr(GBytes) privkey = NULL; /* invalid args */ if (g_strv_length(values) != 3) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments, expected firmware.cab " "certificate.pem privatekey.pfx"); return FALSE; } /* load arguments */ archive_blob_old = fu_bytes_get_contents(values[0], error); if (archive_blob_old == NULL) return FALSE; cert = fu_bytes_get_contents(values[1], error); if (cert == NULL) return FALSE; privkey = fu_bytes_get_contents(values[2], error); if (privkey == NULL) return FALSE; /* load, sign, export */ if (!fu_firmware_parse(FU_FIRMWARE(cabinet), archive_blob_old, FWUPD_INSTALL_FLAG_NONE, error)) return FALSE; if (!fu_cabinet_sign(cabinet, cert, privkey, FU_CABINET_SIGN_FLAG_NONE, error)) return FALSE; archive_blob_new = fu_firmware_write(FU_FIRMWARE(cabinet), error); if (archive_blob_new == NULL) return FALSE; return fu_bytes_set_contents(values[0], archive_blob_new, error); } static gboolean fu_util_firmware_dump(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(GBytes) blob_empty = g_bytes_new(NULL, 0); g_autoptr(GBytes) blob_fw = NULL; /* progress */ fu_progress_set_id(priv->progress, G_STRLOC); fu_progress_add_flag(priv->progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 5, "start-engine"); fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_READ, 95, NULL); /* invalid args */ if (g_strv_length(values) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* file already exists */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && g_file_test(values[0], G_FILE_TEST_EXISTS)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Filename already exists"); return FALSE; } /* write a zero length file to ensure the destination is writable to * avoid failing at the end of a potentially lengthy operation */ if (!fu_bytes_set_contents(values[0], blob_empty, error)) return FALSE; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG, fu_progress_get_child(priv->progress), error)) return FALSE; fu_progress_step_done(priv->progress); /* get device */ priv->filter_device_include |= FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE; if (g_strv_length(values) >= 2) { device = fu_util_get_device(priv, values[1], error); if (device == NULL) return FALSE; } else { device = fu_util_prompt_for_device(priv, NULL, error); if (device == NULL) return FALSE; } priv->current_operation = FU_UTIL_OPERATION_READ; g_signal_connect(FU_ENGINE(priv->engine), "device-changed", G_CALLBACK(fu_util_update_device_changed_cb), priv); /* dump firmware */ blob_fw = fu_engine_firmware_dump(priv->engine, device, fu_progress_get_child(priv->progress), priv->flags, error); if (blob_fw == NULL) return FALSE; fu_progress_step_done(priv->progress); return fu_bytes_set_contents(values[0], blob_fw, error); } static gboolean fu_util_firmware_read(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(FuFirmware) fw = NULL; g_autoptr(GBytes) blob_empty = g_bytes_new(NULL, 0); g_autoptr(GBytes) blob_fw = NULL; /* progress */ fu_progress_set_id(priv->progress, G_STRLOC); fu_progress_add_flag(priv->progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 5, "start-engine"); fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_READ, 95, NULL); /* invalid args */ if (g_strv_length(values) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* file already exists */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && g_file_test(values[0], G_FILE_TEST_EXISTS)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Filename already exists"); return FALSE; } /* write a zero length file to ensure the destination is writable to * avoid failing at the end of a potentially lengthy operation */ if (!fu_bytes_set_contents(values[0], blob_empty, error)) return FALSE; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG, fu_progress_get_child(priv->progress), error)) return FALSE; fu_progress_step_done(priv->progress); /* get device */ priv->filter_device_include |= FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE; if (g_strv_length(values) >= 2) { device = fu_util_get_device(priv, values[1], error); if (device == NULL) return FALSE; } else { device = fu_util_prompt_for_device(priv, NULL, error); if (device == NULL) return FALSE; } priv->current_operation = FU_UTIL_OPERATION_READ; g_signal_connect(FU_ENGINE(priv->engine), "device-changed", G_CALLBACK(fu_util_update_device_changed_cb), priv); /* read firmware into the container format */ fw = fu_engine_firmware_read(priv->engine, device, fu_progress_get_child(priv->progress), priv->flags, error); if (fw == NULL) return FALSE; blob_fw = fu_firmware_write(fw, error); if (blob_fw == NULL) return FALSE; fu_progress_step_done(priv->progress); return fu_bytes_set_contents(values[0], blob_fw, error); } static gint fu_util_release_sort_cb(gconstpointer a, gconstpointer b) { FuRelease *release1 = *((FuRelease **)a); FuRelease *release2 = *((FuRelease **)b); return fu_release_compare(release1, release2); } static gchar * fu_util_download_if_required(FuUtilPrivate *priv, const gchar *perhapsfn, GError **error) { g_autofree gchar *filename = NULL; g_autoptr(GFile) file = NULL; /* a local file */ if (g_file_test(perhapsfn, G_FILE_TEST_EXISTS)) return g_strdup(perhapsfn); if (!fu_util_is_url(perhapsfn)) return g_strdup(perhapsfn); /* download the firmware to a cachedir */ filename = fu_util_get_user_cache_path(perhapsfn); if (!fu_path_mkdir_parent(filename, error)) return NULL; file = g_file_new_for_path(filename); if (!fwupd_client_download_file(priv->client, perhapsfn, file, FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, priv->cancellable, error)) return NULL; return g_steal_pointer(&filename); } static gboolean fu_util_install(FuUtilPrivate *priv, gchar **values, GError **error) { g_autofree gchar *filename = NULL; g_autoptr(GBytes) blob_cab = NULL; g_autoptr(GPtrArray) components = NULL; g_autoptr(GPtrArray) devices_possible = NULL; g_autoptr(GPtrArray) errors = NULL; g_autoptr(GPtrArray) releases = NULL; g_autoptr(XbSilo) silo = NULL; /* progress */ fu_progress_set_id(priv->progress, G_STRLOC); fu_progress_add_flag(priv->progress, FU_PROGRESS_FLAG_NO_PROFILE); fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 50, "start-engine"); fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_WRITE, 50, NULL); /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, fu_progress_get_child(priv->progress), error)) return FALSE; fu_progress_step_done(priv->progress); /* handle both forms */ if (g_strv_length(values) == 1) { devices_possible = fu_engine_get_devices(priv->engine, error); if (devices_possible == NULL) return FALSE; fwupd_device_array_ensure_parents(devices_possible); } else if (g_strv_length(values) == 2) { FuDevice *device = fu_util_get_device(priv, values[1], error); if (device == NULL) return FALSE; if (!priv->no_safety_check) { if (!fu_util_prompt_warning_fde(priv->console, FWUPD_DEVICE(device), error)) return FALSE; } devices_possible = fu_engine_get_devices_by_composite_id(priv->engine, fu_device_get_composite_id(device), error); if (devices_possible == NULL) return FALSE; g_ptr_array_add(devices_possible, device); } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* download if required */ filename = fu_util_download_if_required(priv, values[0], error); if (filename == NULL) return FALSE; /* parse silo */ blob_cab = fu_bytes_get_contents(filename, error); if (blob_cab == NULL) { fu_util_maybe_prefix_sandbox_error(filename, error); return FALSE; } silo = fu_engine_get_silo_from_blob(priv->engine, blob_cab, error); if (silo == NULL) return FALSE; components = xb_silo_query(silo, "components/component", 0, error); if (components == NULL) return FALSE; /* for each component in the silo */ errors = g_ptr_array_new_with_free_func((GDestroyNotify)g_error_free); releases = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < components->len; i++) { XbNode *component = g_ptr_array_index(components, i); /* do any devices pass the requirements */ for (guint j = 0; j < devices_possible->len; j++) { FuDevice *device = g_ptr_array_index(devices_possible, j); g_autoptr(FuRelease) release = fu_release_new(); g_autoptr(GError) error_local = NULL; /* is this component valid for the device */ fu_release_set_device(release, device); fu_release_set_request(release, priv->request); if (!fu_release_load(release, component, NULL, priv->flags, &error_local)) { g_debug("loading release failed on %s:%s failed: %s", fu_device_get_id(device), xb_node_query_text(component, "id", NULL), error_local->message); g_ptr_array_add(errors, g_steal_pointer(&error_local)); continue; } if (!fu_engine_requirements_check(priv->engine, release, priv->flags, &error_local)) { g_debug("requirement on %s:%s failed: %s", fu_device_get_id(device), xb_node_query_text(component, "id", NULL), error_local->message); g_ptr_array_add(errors, g_steal_pointer(&error_local)); continue; } /* if component should have an update message from CAB */ fu_device_incorporate_from_component(device, component); /* success */ g_ptr_array_add(releases, g_steal_pointer(&release)); } } /* order the install tasks by the device priority */ g_ptr_array_sort(releases, fu_util_release_sort_cb); /* nothing suitable */ if (releases->len == 0) { GError *error_tmp = fu_engine_error_array_get_best(errors); g_propagate_error(error, error_tmp); return FALSE; } priv->current_operation = FU_UTIL_OPERATION_INSTALL; g_signal_connect(FU_ENGINE(priv->engine), "device-changed", G_CALLBACK(fu_util_update_device_changed_cb), priv); /* install all the tasks */ if (!fu_engine_install_releases(priv->engine, priv->request, releases, blob_cab, fu_progress_get_child(priv->progress), priv->flags, error)) return FALSE; fu_progress_step_done(priv->progress); fu_util_display_current_message(priv); /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug("skipping reboot check"); return TRUE; } /* success */ return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_install_release(FuUtilPrivate *priv, FwupdRelease *rel, GError **error) { FwupdRemote *remote; GPtrArray *locations; const gchar *remote_id; const gchar *uri_tmp; g_auto(GStrv) argv = NULL; /* get the default release only until other parts of fwupd can cope */ locations = fwupd_release_get_locations(rel); if (locations->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "release missing URI"); return FALSE; } uri_tmp = g_ptr_array_index(locations, 0); remote_id = fwupd_release_get_remote_id(rel); if (remote_id == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "failed to find remote for %s", uri_tmp); return FALSE; } remote = fu_engine_get_remote_by_id(priv->engine, remote_id, error); if (remote == NULL) return FALSE; argv = g_new0(gchar *, 2); /* local remotes may have the firmware already */ if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_LOCAL && !fu_util_is_url(uri_tmp)) { const gchar *fn_cache = fwupd_remote_get_filename_cache(remote); g_autofree gchar *path = g_path_get_dirname(fn_cache); argv[0] = g_build_filename(path, uri_tmp, NULL); } else if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DIRECTORY) { argv[0] = g_strdup(uri_tmp + 7); /* web remote, fu_util_install will download file */ } else { argv[0] = fwupd_remote_build_firmware_uri(remote, uri_tmp, error); } /* reset progress before reusing it. */ fu_progress_reset(priv->progress); return fu_util_install(priv, argv, error); } static gboolean fu_util_update(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; gboolean no_updates_header = FALSE; gboolean latest_header = FALSE; if (priv->flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "--allow-older is not supported for this command"); return FALSE; } if (priv->flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "--allow-reinstall is not supported for this command"); return FALSE; } if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; /* DEVICE-ID and GUID are acceptable args to update */ for (guint idx = 0; idx < g_strv_length(values); idx++) { if (!fwupd_guid_is_valid(values[idx]) && !fwupd_device_id_is_valid(values[idx])) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "'%s' is not a valid GUID nor DEVICE-ID", values[idx]); return FALSE; } } priv->current_operation = FU_UTIL_OPERATION_UPDATE; g_signal_connect(FU_ENGINE(priv->engine), "device-changed", G_CALLBACK(fu_util_update_device_changed_cb), priv); devices = fu_engine_get_devices(priv->engine, error); if (devices == NULL) return FALSE; fwupd_device_array_ensure_parents(devices); g_ptr_array_sort(devices, fu_util_sort_devices_by_flags_cb); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); FwupdRelease *rel; const gchar *device_id = fu_device_get_id(dev); g_autoptr(GPtrArray) rels = NULL; g_autoptr(GError) error_local = NULL; gboolean dev_skip_byid = TRUE; /* only process particular DEVICE-ID or GUID if specified */ for (guint idx = 0; idx < g_strv_length(values); idx++) { const gchar *tmpid = values[idx]; if (fwupd_device_has_guid(dev, tmpid) || g_strcmp0(device_id, tmpid) == 0) { dev_skip_byid = FALSE; break; } } if (g_strv_length(values) > 0 && dev_skip_byid) continue; if (!fu_util_is_interesting_device(dev)) continue; /* only show stuff that has metadata available */ if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)) continue; if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { if (!no_updates_header) { fu_console_print_literal( priv->console, /* TRANSLATORS: message letting the user know no * device upgrade available due to missing on LVFS */ _("Devices with no available firmware updates: ")); no_updates_header = TRUE; } fu_console_print(priv->console, " • %s", fwupd_device_get_name(dev)); continue; } if (!fwupd_device_match_flags(dev, priv->filter_device_include, priv->filter_device_exclude)) continue; rels = fu_engine_get_upgrades(priv->engine, priv->request, device_id, &error_local); if (rels == NULL) { if (!latest_header) { fu_console_print_literal( priv->console, /* TRANSLATORS: message letting the user know no device upgrade * available */ _("Devices with the latest available firmware version:")); latest_header = TRUE; } fu_console_print(priv->console, " • %s", fwupd_device_get_name(dev)); /* discard the actual reason from user, but leave for debugging */ g_debug("%s", error_local->message); continue; } rel = g_ptr_array_index(rels, 0); if (!priv->no_safety_check) { g_autofree gchar *title = g_strdup_printf("%s %s", fu_engine_get_host_vendor(priv->engine), fu_engine_get_host_product(priv->engine)); if (!fu_util_prompt_warning(priv->console, dev, rel, title, error)) return FALSE; if (!fu_util_prompt_warning_fde(priv->console, dev, error)) return FALSE; } if (!fu_util_install_release(priv, rel, &error_local)) { fu_console_print_literal(priv->console, error_local->message); continue; } fu_util_display_current_message(priv); } /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug("skipping reboot check"); return TRUE; } return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_reinstall(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdRelease) rel = NULL; g_autoptr(GPtrArray) rels = NULL; g_autoptr(FuDevice) dev = NULL; if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; dev = fu_util_get_device(priv, values[0], error); if (dev == NULL) return FALSE; /* try to lookup/match release from client */ rels = fu_engine_get_releases_for_device(priv->engine, priv->request, dev, error); if (rels == NULL) return FALSE; for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel_tmp = g_ptr_array_index(rels, j); if (!fwupd_release_match_flags(rel_tmp, priv->filter_release_include, priv->filter_release_exclude)) continue; if (fu_version_compare(fwupd_release_get_version(rel_tmp), fu_device_get_version(dev), fu_device_get_version_format(dev)) == 0) { rel = g_object_ref(rel_tmp); break; } } if (rel == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unable to locate release for %s version %s", fu_device_get_name(dev), fu_device_get_version(dev)); return FALSE; } /* update the console if composite devices are also updated */ priv->current_operation = FU_UTIL_OPERATION_INSTALL; g_signal_connect(FU_ENGINE(priv->engine), "device-changed", G_CALLBACK(fu_util_update_device_changed_cb), priv); priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; if (!fu_util_install_release(priv, rel, error)) return FALSE; fu_util_display_current_message(priv); /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug("skipping reboot check"); return TRUE; } return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_detach(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* progress */ fu_progress_set_id(priv->progress, G_STRLOC); fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 95, "start-engine"); fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_BUSY, 5, NULL); /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, fu_progress_get_child(priv->progress), error)) return FALSE; fu_progress_step_done(priv->progress); /* get device */ priv->filter_device_exclude |= FWUPD_DEVICE_FLAG_IS_BOOTLOADER; if (g_strv_length(values) >= 1) { device = fu_util_get_device(priv, values[0], error); if (device == NULL) return FALSE; } else { device = fu_util_prompt_for_device(priv, NULL, error); if (device == NULL) return FALSE; } /* run vfunc */ locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; if (!fu_device_detach_full(device, fu_progress_get_child(priv->progress), error)) return FALSE; fu_progress_step_done(priv->progress); return TRUE; } static gboolean fu_util_unbind_driver(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; /* get device */ if (g_strv_length(values) == 1) { device = fu_util_get_device(priv, values[0], error); } else { device = fu_util_prompt_for_device(priv, NULL, error); } if (device == NULL) return FALSE; /* run vfunc */ locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; return fu_device_unbind_driver(device, error); } static gboolean fu_util_bind_driver(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; /* get device */ if (g_strv_length(values) == 3) { device = fu_util_get_device(priv, values[2], error); if (device == NULL) return FALSE; } else if (g_strv_length(values) == 2) { device = fu_util_prompt_for_device(priv, NULL, error); if (device == NULL) return FALSE; } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* run vfunc */ locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; return fu_device_bind_driver(device, values[0], values[1], error); } static gboolean fu_util_attach(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuDevice) device = NULL; g_autoptr(FuDeviceLocker) locker = NULL; /* progress */ fu_progress_set_id(priv->progress, G_STRLOC); fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 95, "start-engine"); fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_BUSY, 5, NULL); /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, fu_progress_get_child(priv->progress), error)) return FALSE; fu_progress_step_done(priv->progress); /* get device */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) priv->filter_device_include |= FWUPD_DEVICE_FLAG_IS_BOOTLOADER; if (g_strv_length(values) >= 1) { device = fu_util_get_device(priv, values[0], error); if (device == NULL) return FALSE; } else { device = fu_util_prompt_for_device(priv, NULL, error); if (device == NULL) return FALSE; } /* run vfunc */ locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; if (!fu_device_attach_full(device, fu_progress_get_child(priv->progress), error)) return FALSE; fu_progress_step_done(priv->progress); /* success */ return TRUE; } static void fu_util_report_metadata_to_string(GHashTable *metadata, guint idt, GString *str) { g_autoptr(GList) keys = g_list_sort(g_hash_table_get_keys(metadata), (GCompareFunc)g_strcmp0); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; const gchar *value = g_hash_table_lookup(metadata, key); fu_string_append(str, idt, key, value); } } static gboolean fu_util_get_report_metadata(FuUtilPrivate *priv, gchar **values, GError **error) { GPtrArray *plugins; g_autoptr(GHashTable) metadata = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GString) str = g_string_new(NULL); /* progress */ fu_progress_set_id(priv->progress, G_STRLOC); fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 95, "start-engine"); fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_BUSY, 5, NULL); /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG, fu_progress_get_child(priv->progress), error)) return FALSE; fu_progress_step_done(priv->progress); /* daemon metadata */ metadata = fu_engine_get_report_metadata(priv->engine, error); if (metadata == NULL) return FALSE; fu_util_report_metadata_to_string(metadata, 0, str); /* device metadata */ devices = fu_engine_get_devices(priv->engine, error); if (devices == NULL) return FALSE; for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GHashTable) metadata_post = NULL; g_autoptr(GHashTable) metadata_pre = NULL; locker = fu_device_locker_new(device, error); if (locker == NULL) return FALSE; metadata_pre = fu_device_report_metadata_pre(device); metadata_post = fu_device_report_metadata_post(device); if (metadata_pre != NULL || metadata_post != NULL) { fu_string_append(str, 0, FWUPD_RESULT_KEY_DEVICE_ID, fu_device_get_id(device)); } if (metadata_pre != NULL) { fu_string_append(str, 1, "pre", NULL); fu_util_report_metadata_to_string(metadata_pre, 3, str); } if (metadata_post != NULL) { fu_string_append(str, 1, "post", NULL); fu_util_report_metadata_to_string(metadata_post, 3, str); } } /* plugin metadata */ plugins = fu_engine_get_plugins(priv->engine); for (guint i = 0; i < plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index(plugins, i); if (fu_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) continue; if (fu_plugin_get_report_metadata(plugin) == NULL) continue; fu_util_report_metadata_to_string(fu_plugin_get_report_metadata(plugin), 3, str); } fu_progress_step_done(priv->progress); /* display */ fu_console_print_literal(priv->console, str->str); /* success */ return TRUE; } static gboolean fu_util_modify_config(FuUtilPrivate *priv, gchar **values, GError **error) { /* check args */ if (g_strv_length(values) != 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: KEY VALUE expected"); return FALSE; } /* start engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_NONE, priv->progress, error)) return FALSE; if (!fu_engine_modify_config(priv->engine, values[0], values[1], error)) return FALSE; /* TRANSLATORS: success message -- a per-system setting value */ fu_console_print_literal(priv->console, _("Successfully modified configuration value")); return TRUE; } static gboolean fu_util_remote_modify(FuUtilPrivate *priv, gchar **values, GError **error) { FwupdRemote *remote = NULL; if (g_strv_length(values) < 3) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; remote = fu_engine_get_remote_by_id(priv->engine, values[0], error); if (remote == NULL) return FALSE; if (!fu_engine_modify_remote(priv->engine, fwupd_remote_get_id(remote), values[1], values[2], error)) return FALSE; fu_console_print_literal(priv->console, _("Successfully modified remote")); return TRUE; } static gboolean fu_util_remote_disable(FuUtilPrivate *priv, gchar **values, GError **error) { FwupdRemote *remote = NULL; if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; remote = fu_engine_get_remote_by_id(priv->engine, values[0], error); if (remote == NULL) return FALSE; if (!fu_engine_modify_remote(priv->engine, fwupd_remote_get_id(remote), "Enabled", "false", error)) return FALSE; fu_console_print_literal(priv->console, _("Successfully disabled remote")); return TRUE; } static gboolean fu_util_remote_enable(FuUtilPrivate *priv, gchar **values, GError **error) { FwupdRemote *remote = NULL; if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; remote = fu_engine_get_remote_by_id(priv->engine, values[0], error); if (remote == NULL) return FALSE; if (!fu_util_modify_remote_warning(priv->console, remote, FALSE, error)) return FALSE; if (!fu_engine_modify_remote(priv->engine, fwupd_remote_get_id(remote), "Enabled", "true", error)) return FALSE; fu_console_print_literal(priv->console, _("Successfully enabled remote")); return TRUE; } static gboolean fu_util_toggle_test_devices(FuUtilPrivate *priv, gboolean enable, GError **error) { if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_NONE, priv->progress, error)) return FALSE; if (!fu_engine_modify_config(priv->engine, "TestDevices", enable ? "true" : "false", error)) return FALSE; if (enable) { /* TRANSLATORS: comment explaining result of command */ fu_console_print_literal(priv->console, _("Successfully enabled test devices")); } else { /* TRANSLATORS: comment explaining result of command */ fu_console_print_literal(priv->console, _("Successfully disabled test devices")); } return TRUE; } static gboolean fu_util_disable_test_devices(FuUtilPrivate *priv, gchar **values, GError **error) { return fu_util_toggle_test_devices(priv, FALSE, error); } static gboolean fu_util_enable_test_devices(FuUtilPrivate *priv, gchar **values, GError **error) { return fu_util_toggle_test_devices(priv, TRUE, error); } static gboolean fu_util_check_activation_needed(FuUtilPrivate *priv, GError **error) { gboolean has_pending = FALSE; g_autoptr(FuHistory) history = fu_history_new(); g_autoptr(GPtrArray) devices = fu_history_get_devices(history, error); if (devices == NULL) return FALSE; /* only start up the plugins needed */ for (guint i = 0; i < devices->len; i++) { FuDevice *dev = g_ptr_array_index(devices, i); if (fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { fu_engine_add_plugin_filter(priv->engine, fu_device_get_plugin(dev)); has_pending = TRUE; } } if (!has_pending) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No devices to activate"); return FALSE; } return TRUE; } static gboolean fu_util_activate(FuUtilPrivate *priv, gchar **values, GError **error) { gboolean has_pending = FALSE; g_autoptr(GPtrArray) devices = NULL; /* check the history database before starting the daemon */ if (!fu_util_check_activation_needed(priv, error)) return FALSE; /* progress */ fu_progress_set_id(priv->progress, G_STRLOC); fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 95, "start-engine"); fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_BUSY, 5, NULL); /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_READONLY | FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS, fu_progress_get_child(priv->progress), error)) return FALSE; fu_progress_step_done(priv->progress); /* parse arguments */ if (g_strv_length(values) == 0) { devices = fu_engine_get_devices(priv->engine, error); if (devices == NULL) return FALSE; } else if (g_strv_length(values) == 1) { FuDevice *device; device = fu_util_get_device(priv, values[0], error); if (device == NULL) return FALSE; devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_ptr_array_add(devices, device); } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* activate anything with _NEEDS_ACTIVATION */ for (guint i = 0; i < devices->len; i++) { FuDevice *device = g_ptr_array_index(devices, i); if (!fwupd_device_match_flags(FWUPD_DEVICE(device), priv->filter_device_include, priv->filter_device_exclude)) continue; if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) continue; has_pending = TRUE; fu_console_print( priv->console, "%s %s…", /* TRANSLATORS: shown when shutting down to switch to the new version */ _("Activating firmware update"), fu_device_get_name(device)); if (!fu_engine_activate(priv->engine, fu_device_get_id(device), fu_progress_get_child(priv->progress), error)) return FALSE; } fu_progress_step_done(priv->progress); if (!has_pending) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No devices to activate"); return FALSE; } return TRUE; } static gboolean fu_util_export_hwids(FuUtilPrivate *priv, gchar **values, GError **error) { FuContext *ctx = fu_engine_get_context(priv->engine); FuHwids *hwids = fu_context_get_hwids(ctx); g_autoptr(GKeyFile) kf = g_key_file_new(); g_autoptr(GPtrArray) hwid_keys = NULL; /* check args */ if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments, expected HWIDS-FILE"); return FALSE; } /* setup default hwids */ if (!fu_context_load_hwinfo(ctx, priv->progress, FU_CONTEXT_HWID_FLAG_LOAD_ALL, error)) return FALSE; /* save all keys */ hwid_keys = fu_hwids_get_keys(hwids); for (guint i = 0; i < hwid_keys->len; i++) { const gchar *hwid_key = g_ptr_array_index(hwid_keys, i); const gchar *value = fu_hwids_get_value(hwids, hwid_key); if (value == NULL) continue; g_key_file_set_string(kf, "HwIds", hwid_key, value); } /* success */ return g_key_file_save_to_file(kf, values[0], error); } static gboolean fu_util_hwids(FuUtilPrivate *priv, gchar **values, GError **error) { FuContext *ctx = fu_engine_get_context(priv->engine); FuHwids *hwids = fu_context_get_hwids(ctx); g_autoptr(GPtrArray) hwid_keys = fu_hwids_get_keys(hwids); /* a keyfile with overrides */ if (g_strv_length(values) == 1) { g_autoptr(GKeyFile) kf = g_key_file_new(); if (!g_key_file_load_from_file(kf, values[0], G_KEY_FILE_NONE, error)) return FALSE; for (guint i = 0; i < hwid_keys->len; i++) { const gchar *hwid_key = g_ptr_array_index(hwid_keys, i); g_autofree gchar *tmp = NULL; tmp = g_key_file_get_string(kf, "HwIds", hwid_key, NULL); fu_hwids_add_value(hwids, hwid_key, tmp); } } if (!fu_context_load_hwinfo(ctx, priv->progress, FU_CONTEXT_HWID_FLAG_LOAD_ALL, error)) return FALSE; /* show debug output */ fu_console_print_literal(priv->console, "Computer Information"); fu_console_print_literal(priv->console, "--------------------"); for (guint i = 0; i < hwid_keys->len; i++) { const gchar *hwid_key = g_ptr_array_index(hwid_keys, i); const gchar *value = fu_hwids_get_value(hwids, hwid_key); if (value == NULL) continue; if (g_strcmp0(hwid_key, FU_HWIDS_KEY_BIOS_MAJOR_RELEASE) == 0 || g_strcmp0(hwid_key, FU_HWIDS_KEY_BIOS_MINOR_RELEASE) == 0) { guint64 val = g_ascii_strtoull(value, NULL, 16); fu_console_print(priv->console, "%s: %" G_GUINT64_FORMAT, hwid_key, val); } else { fu_console_print(priv->console, "%s: %s", hwid_key, value); } } /* show GUIDs */ fu_console_print_literal(priv->console, "Hardware IDs"); fu_console_print_literal(priv->console, "------------"); for (guint i = 0; i < 15; i++) { const gchar *keys = NULL; g_autofree gchar *guid = NULL; g_autofree gchar *key = NULL; g_autofree gchar *keys_str = NULL; g_auto(GStrv) keysv = NULL; g_autoptr(GError) error_local = NULL; /* get the GUID */ key = g_strdup_printf("HardwareID-%u", i); keys = fu_hwids_get_replace_keys(hwids, key); guid = fu_hwids_get_guid(hwids, key, &error_local); if (guid == NULL) { fu_console_print_literal(priv->console, error_local->message); continue; } /* show what makes up the GUID */ keysv = g_strsplit(keys, "&", -1); keys_str = g_strjoinv(" + ", keysv); fu_console_print(priv->console, "{%s} <- %s", guid, keys_str); } return TRUE; } static gboolean fu_util_self_sign(FuUtilPrivate *priv, gchar **values, GError **error) { g_autofree gchar *sig = NULL; /* check args */ if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: value expected"); return FALSE; } /* start engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_NONE, priv->progress, error)) return FALSE; sig = fu_engine_self_sign(priv->engine, values[0], JCAT_SIGN_FLAG_ADD_TIMESTAMP | JCAT_SIGN_FLAG_ADD_CERT, error); if (sig == NULL) return FALSE; fu_console_print_literal(priv->console, sig); return TRUE; } static void fu_util_device_added_cb(FwupdClient *client, FwupdDevice *device, gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *)user_data; g_autofree gchar *tmp = fu_util_device_to_string(priv->client, device, 0); /* TRANSLATORS: this is when a device is hotplugged */ fu_console_print(priv->console, "%s\n%s", _("Device added:"), tmp); } static void fu_util_device_removed_cb(FwupdClient *client, FwupdDevice *device, gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *)user_data; g_autofree gchar *tmp = fu_util_device_to_string(priv->client, device, 0); /* TRANSLATORS: this is when a device is hotplugged */ fu_console_print(priv->console, "%s\n%s", _("Device removed:"), tmp); } static void fu_util_device_changed_cb(FwupdClient *client, FwupdDevice *device, gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *)user_data; g_autofree gchar *tmp = fu_util_device_to_string(priv->client, device, 0); /* TRANSLATORS: this is when a device has been updated */ fu_console_print(priv->console, "%s\n%s", _("Device changed:"), tmp); } static void fu_util_changed_cb(FwupdClient *client, gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *)user_data; /* TRANSLATORS: this is when the daemon state changes */ fu_console_print_literal(priv->console, _("Changed")); } static gboolean fu_util_monitor(FuUtilPrivate *priv, gchar **values, GError **error) { /* get all the devices */ if (!fwupd_client_connect(priv->client, priv->cancellable, error)) return FALSE; /* watch for any hotplugged device */ g_signal_connect(FWUPD_CLIENT(priv->client), "changed", G_CALLBACK(fu_util_changed_cb), priv); g_signal_connect(FWUPD_CLIENT(priv->client), "device-added", G_CALLBACK(fu_util_device_added_cb), priv); g_signal_connect(FWUPD_CLIENT(priv->client), "device-removed", G_CALLBACK(fu_util_device_removed_cb), priv); g_signal_connect(FWUPD_CLIENT(priv->client), "device-changed", G_CALLBACK(fu_util_device_changed_cb), priv); g_signal_connect(G_CANCELLABLE(priv->cancellable), "cancelled", G_CALLBACK(fu_util_cancelled_cb), priv); g_main_loop_run(priv->loop); return TRUE; } static gboolean fu_util_get_firmware_types(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) firmware_types = NULL; /* load engine */ if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS, priv->progress, error)) return FALSE; firmware_types = fu_context_get_firmware_gtype_ids(fu_engine_get_context(priv->engine)); for (guint i = 0; i < firmware_types->len; i++) { const gchar *id = g_ptr_array_index(firmware_types, i); fu_console_print_literal(priv->console, id); } if (firmware_types->len == 0) { /* TRANSLATORS: nothing found */ fu_console_print_literal(priv->console, _("No firmware IDs found")); return TRUE; } return TRUE; } static gboolean fu_util_get_firmware_gtypes(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GArray) firmware_types = NULL; /* load engine */ if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS, priv->progress, error)) return FALSE; firmware_types = fu_context_get_firmware_gtypes(fu_engine_get_context(priv->engine)); for (guint i = 0; i < firmware_types->len; i++) { GType gtype = g_array_index(firmware_types, GType, i); fu_console_print_literal(priv->console, g_type_name(gtype)); } if (firmware_types->len == 0) { /* TRANSLATORS: nothing found */ fu_console_print_literal(priv->console, _("No firmware found")); return TRUE; } return TRUE; } static gchar * fu_util_prompt_for_firmware_type(FuUtilPrivate *priv, GPtrArray *firmware_types, GError **error) { guint idx; /* no detected types */ if (firmware_types->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No detected firmware types"); return NULL; } /* there is no point asking */ if (firmware_types->len == 1) { const gchar *id = g_ptr_array_index(firmware_types, 0); return g_strdup(id); } /* TRANSLATORS: this is to abort the interactive prompt */ fu_console_print(priv->console, "0.\t%s", _("Cancel")); for (guint i = 0; i < firmware_types->len; i++) { const gchar *id = g_ptr_array_index(firmware_types, i); fu_console_print(priv->console, "%u.\t%s", i + 1, id); } /* TRANSLATORS: get interactive prompt */ idx = fu_console_input_uint(priv->console, firmware_types->len, "%s", _("Choose firmware")); if (idx == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return NULL; } return g_strdup(g_ptr_array_index(firmware_types, idx - 1)); } static gboolean fu_util_firmware_parse(FuUtilPrivate *priv, gchar **values, GError **error) { FuContext *ctx = fu_engine_get_context(priv->engine); GType gtype; g_autoptr(GBytes) blob = NULL; g_autoptr(FuFirmware) firmware = NULL; g_autofree gchar *firmware_type = NULL; g_autofree gchar *str = NULL; /* check args */ if (g_strv_length(values) == 0 || g_strv_length(values) > 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: filename required"); return FALSE; } /* load file */ blob = fu_bytes_get_contents(values[0], error); if (blob == NULL) return FALSE; /* load engine */ if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS, priv->progress, error)) return FALSE; /* find the GType to use */ if (g_strv_length(values) == 1) { g_autoptr(GPtrArray) firmware_types = fu_context_get_firmware_gtype_ids(ctx); firmware_type = fu_util_prompt_for_firmware_type(priv, firmware_types, error); if (firmware_type == NULL) return FALSE; } else if (g_strcmp0(values[1], "auto") == 0) { g_autoptr(GPtrArray) gtype_ids = fu_context_get_firmware_gtype_ids(ctx); g_autoptr(GPtrArray) firmware_auto_types = g_ptr_array_new_with_free_func(g_free); for (guint i = 0; i < gtype_ids->len; i++) { const gchar *gtype_id = g_ptr_array_index(gtype_ids, i); GType gtype_tmp; g_autofree gchar *firmware_str = NULL; g_autoptr(FuFirmware) firmware_tmp = NULL; g_autoptr(GError) error_local = NULL; if (g_strcmp0(gtype_id, "raw") == 0) continue; g_debug("parsing as %s", gtype_id); gtype_tmp = fu_context_get_firmware_gtype_by_id(ctx, gtype_id); if (gtype_tmp == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GType %s not supported", gtype_id); return FALSE; } firmware_tmp = g_object_new(gtype_tmp, NULL); if (fu_firmware_has_flag(firmware_tmp, FU_FIRMWARE_FLAG_NO_AUTO_DETECTION)) continue; if (!fu_firmware_parse(firmware_tmp, blob, FWUPD_INSTALL_FLAG_NO_SEARCH, &error_local)) { g_debug("failed to parse as %s: %s", gtype_id, error_local->message); continue; } firmware_str = fu_firmware_to_string(firmware_tmp); g_debug("parsed as %s: %s", gtype_id, firmware_str); g_ptr_array_add(firmware_auto_types, g_strdup(gtype_id)); } firmware_type = fu_util_prompt_for_firmware_type(priv, firmware_auto_types, error); if (firmware_type == NULL) return FALSE; } else { firmware_type = g_strdup(values[1]); } gtype = fu_context_get_firmware_gtype_by_id(ctx, firmware_type); if (gtype == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GType %s not supported", firmware_type); return FALSE; } /* does firmware specify an internal size */ firmware = g_object_new(gtype, NULL); if (fu_firmware_has_flag(firmware, FU_FIRMWARE_FLAG_HAS_STORED_SIZE)) { g_autoptr(FuFirmware) firmware_linear = fu_linear_firmware_new(gtype); g_autoptr(GPtrArray) imgs = NULL; if (!fu_firmware_parse(firmware_linear, blob, priv->flags, error)) return FALSE; imgs = fu_firmware_get_images(firmware_linear); if (imgs->len == 1) { g_set_object(&firmware, g_ptr_array_index(imgs, 0)); } else { g_set_object(&firmware, firmware_linear); } } else { if (!fu_firmware_parse(firmware, blob, priv->flags, error)) return FALSE; } str = fu_firmware_to_string(firmware); fu_console_print_literal(priv->console, str); return TRUE; } static gboolean fu_util_firmware_export(FuUtilPrivate *priv, gchar **values, GError **error) { FuContext *ctx = fu_engine_get_context(priv->engine); FuFirmwareExportFlags flags = FU_FIRMWARE_EXPORT_FLAG_NONE; GType gtype; g_autoptr(GBytes) blob = NULL; g_autoptr(FuFirmware) firmware = NULL; g_autofree gchar *firmware_type = NULL; g_autofree gchar *str = NULL; /* check args */ if (g_strv_length(values) == 0 || g_strv_length(values) > 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: filename required"); return FALSE; } if (g_strv_length(values) == 2) firmware_type = g_strdup(values[1]); /* load file */ blob = fu_bytes_get_contents(values[0], error); if (blob == NULL) return FALSE; /* load engine */ if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS, priv->progress, error)) return FALSE; /* find the GType to use */ if (firmware_type == NULL) { g_autoptr(GPtrArray) firmware_types = fu_context_get_firmware_gtype_ids(ctx); firmware_type = fu_util_prompt_for_firmware_type(priv, firmware_types, error); } if (firmware_type == NULL) return FALSE; gtype = fu_context_get_firmware_gtype_by_id(ctx, firmware_type); if (gtype == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GType %s not supported", firmware_type); return FALSE; } firmware = g_object_new(gtype, NULL); if (!fu_firmware_parse(firmware, blob, priv->flags, error)) return FALSE; if (priv->show_all) flags |= FU_FIRMWARE_EXPORT_FLAG_INCLUDE_DEBUG; str = fu_firmware_export_to_xml(firmware, flags, error); if (str == NULL) return FALSE; fu_console_print_literal(priv->console, str); return TRUE; } static gboolean fu_util_firmware_extract(FuUtilPrivate *priv, gchar **values, GError **error) { FuContext *ctx = fu_engine_get_context(priv->engine); GType gtype; g_autofree gchar *firmware_type = NULL; g_autofree gchar *str = NULL; g_autoptr(FuFirmware) firmware = NULL; g_autoptr(GBytes) blob = NULL; g_autoptr(GPtrArray) images = NULL; /* check args */ if (g_strv_length(values) == 0 || g_strv_length(values) > 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: filename required"); return FALSE; } if (g_strv_length(values) == 2) firmware_type = g_strdup(values[1]); /* load file */ blob = fu_bytes_get_contents(values[0], error); if (blob == NULL) return FALSE; /* load engine */ if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS, priv->progress, error)) return FALSE; /* find the GType to use */ if (firmware_type == NULL) { g_autoptr(GPtrArray) firmware_types = fu_context_get_firmware_gtype_ids(ctx); firmware_type = fu_util_prompt_for_firmware_type(priv, firmware_types, error); } if (firmware_type == NULL) return FALSE; gtype = fu_context_get_firmware_gtype_by_id(ctx, firmware_type); if (gtype == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GType %s not supported", firmware_type); return FALSE; } firmware = g_object_new(gtype, NULL); if (!fu_firmware_parse(firmware, blob, priv->flags, error)) return FALSE; str = fu_firmware_to_string(firmware); fu_console_print_literal(priv->console, str); images = fu_firmware_get_images(firmware); for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); g_autofree gchar *fn = NULL; g_autoptr(GBytes) blob_img = NULL; /* get raw image without generated header, footer or crc */ blob_img = fu_firmware_get_bytes(img, error); if (blob_img == NULL) return FALSE; if (g_bytes_get_size(blob_img) == 0) continue; /* use suitable filename */ if (fu_firmware_get_filename(img) != NULL) { fn = g_strdup(fu_firmware_get_filename(img)); } else if (fu_firmware_get_id(img) != NULL) { fn = g_strdup_printf("id-%s.fw", fu_firmware_get_id(img)); } else if (fu_firmware_get_idx(img) != 0x0) { fn = g_strdup_printf("idx-0x%x.fw", (guint)fu_firmware_get_idx(img)); } else { fn = g_strdup_printf("img-0x%x.fw", i); } /* TRANSLATORS: decompressing images from a container firmware */ fu_console_print(priv->console, "%s : %s", _("Writing file:"), fn); if (!fu_bytes_set_contents(fn, blob_img, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_util_firmware_build(FuUtilPrivate *priv, gchar **values, GError **error) { GType gtype = FU_TYPE_FIRMWARE; const gchar *tmp; g_autofree gchar *str = NULL; g_autoptr(FuFirmware) firmware = NULL; g_autoptr(FuFirmware) firmware_dst = NULL; g_autoptr(GBytes) blob_dst = NULL; g_autoptr(GBytes) blob_src = NULL; g_autoptr(XbBuilder) builder = xb_builder_new(); g_autoptr(XbBuilderSource) source = xb_builder_source_new(); g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; /* check args */ if (g_strv_length(values) != 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: filename required"); return FALSE; } /* load file */ blob_src = fu_bytes_get_contents(values[0], error); if (blob_src == NULL) return FALSE; /* load engine */ if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS, priv->progress, error)) return FALSE; /* parse XML */ if (!xb_builder_source_load_bytes(source, blob_src, XB_BUILDER_SOURCE_FLAG_NONE, error)) { g_prefix_error(error, "could not parse XML: "); return FALSE; } xb_builder_import_source(builder, source); silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error); if (silo == NULL) return FALSE; /* create FuFirmware of specific GType */ n = xb_silo_query_first(silo, "firmware", error); if (n == NULL) return FALSE; tmp = xb_node_get_attr(n, "gtype"); if (tmp != NULL) { gtype = g_type_from_name(tmp); if (gtype == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GType %s not registered", tmp); return FALSE; } } tmp = xb_node_get_attr(n, "id"); if (tmp != NULL) { gtype = fu_context_get_firmware_gtype_by_id(fu_engine_get_context(priv->engine), tmp); if (gtype == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GType %s not supported", tmp); return FALSE; } } firmware = g_object_new(gtype, NULL); if (!fu_firmware_build(firmware, n, error)) return FALSE; /* write new file */ blob_dst = fu_firmware_write(firmware, error); if (blob_dst == NULL) return FALSE; if (!fu_bytes_set_contents(values[1], blob_dst, error)) return FALSE; /* show what we wrote */ firmware_dst = g_object_new(gtype, NULL); if (!fu_firmware_parse(firmware_dst, blob_dst, priv->flags, error)) return FALSE; str = fu_firmware_to_string(firmware_dst); fu_console_print_literal(priv->console, str); /* success */ return TRUE; } static gboolean fu_util_firmware_convert(FuUtilPrivate *priv, gchar **values, GError **error) { FuContext *ctx = fu_engine_get_context(priv->engine); GType gtype_dst; GType gtype_src; g_autofree gchar *firmware_type_dst = NULL; g_autofree gchar *firmware_type_src = NULL; g_autofree gchar *str_dst = NULL; g_autofree gchar *str_src = NULL; g_autoptr(FuFirmware) firmware_dst = NULL; g_autoptr(FuFirmware) firmware_src = NULL; g_autoptr(GBytes) blob_dst = NULL; g_autoptr(GBytes) blob_src = NULL; g_autoptr(GPtrArray) images = NULL; /* check args */ if (g_strv_length(values) < 2 || g_strv_length(values) > 4) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: filename required"); return FALSE; } if (g_strv_length(values) > 2) firmware_type_src = g_strdup(values[2]); if (g_strv_length(values) > 3) firmware_type_dst = g_strdup(values[3]); /* load file */ blob_src = fu_bytes_get_contents(values[0], error); if (blob_src == NULL) return FALSE; /* load engine */ if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS, priv->progress, error)) return FALSE; /* find the GType to use */ if (firmware_type_src == NULL) { g_autoptr(GPtrArray) firmware_types = fu_context_get_firmware_gtype_ids(ctx); firmware_type_src = fu_util_prompt_for_firmware_type(priv, firmware_types, error); } if (firmware_type_src == NULL) return FALSE; if (firmware_type_dst == NULL) { g_autoptr(GPtrArray) firmware_types = fu_context_get_firmware_gtype_ids(ctx); firmware_type_dst = fu_util_prompt_for_firmware_type(priv, firmware_types, error); } if (firmware_type_dst == NULL) return FALSE; gtype_src = fu_context_get_firmware_gtype_by_id(ctx, firmware_type_src); if (gtype_src == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GType %s not supported", firmware_type_src); return FALSE; } firmware_src = g_object_new(gtype_src, NULL); if (!fu_firmware_parse(firmware_src, blob_src, priv->flags, error)) return FALSE; gtype_dst = fu_context_get_firmware_gtype_by_id(ctx, firmware_type_dst); if (gtype_dst == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GType %s not supported", firmware_type_dst); return FALSE; } str_src = fu_firmware_to_string(firmware_src); fu_console_print_literal(priv->console, str_src); /* copy images */ firmware_dst = g_object_new(gtype_dst, NULL); images = fu_firmware_get_images(firmware_src); for (guint i = 0; i < images->len; i++) { FuFirmware *img = g_ptr_array_index(images, i); fu_firmware_add_image(firmware_dst, img); } /* copy data as fallback, preferring a binary blob to the export */ if (images->len == 0) { g_autoptr(GBytes) fw = NULL; g_autoptr(FuFirmware) img = NULL; fw = fu_firmware_get_bytes(firmware_src, NULL); if (fw == NULL) { fw = fu_firmware_write(firmware_src, error); if (fw == NULL) return FALSE; } img = fu_firmware_new_from_bytes(fw); fu_firmware_add_image(firmware_dst, img); } /* write new file */ blob_dst = fu_firmware_write(firmware_dst, error); if (blob_dst == NULL) return FALSE; if (!fu_bytes_set_contents(values[1], blob_dst, error)) return FALSE; str_dst = fu_firmware_to_string(firmware_dst); fu_console_print_literal(priv->console, str_dst); /* success */ return TRUE; } static GBytes * fu_util_hex_string_to_bytes(const gchar *val, GError **error) { gsize valsz; g_autoptr(GByteArray) buf = g_byte_array_new(); /* sanity check */ if (val == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "nothing to parse"); return NULL; } /* parse each hex byte */ valsz = strlen(val); for (guint i = 0; i < valsz; i += 2) { guint8 tmp = 0; if (!fu_firmware_strparse_uint8_safe(val, valsz, i, &tmp, error)) return NULL; fu_byte_array_append_uint8(buf, tmp); } return g_bytes_new(buf->data, buf->len); } static gboolean fu_util_firmware_patch(FuUtilPrivate *priv, gchar **values, GError **error) { FuContext *ctx = fu_engine_get_context(priv->engine); GType gtype; g_autofree gchar *firmware_type = NULL; g_autofree gchar *str = NULL; g_autoptr(FuFirmware) firmware = NULL; g_autoptr(GBytes) blob_dst = NULL; g_autoptr(GBytes) blob_src = NULL; g_autoptr(GBytes) patch = NULL; guint64 offset = 0; /* check args */ if (g_strv_length(values) != 3 && g_strv_length(values) != 4) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments, expected %s", "FILENAME OFFSET DATA [FIRMWARE-TYPE]"); return FALSE; } /* hardcoded */ if (g_strv_length(values) == 4) firmware_type = g_strdup(values[3]); /* load file */ blob_src = fu_bytes_get_contents(values[0], error); if (blob_src == NULL) return FALSE; /* parse offset */ if (!fu_strtoull(values[1], &offset, 0x0, G_MAXUINT32, error)) { g_prefix_error(error, "failed to parse offset: "); return FALSE; } /* parse blob */ patch = fu_util_hex_string_to_bytes(values[2], error); if (patch == NULL) return FALSE; if (g_bytes_get_size(patch) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "no data provided"); return FALSE; } /* load engine */ if (!fu_engine_load(priv->engine, FU_ENGINE_LOAD_FLAG_READONLY | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS, priv->progress, error)) return FALSE; /* find the GType to use */ if (firmware_type == NULL) { g_autoptr(GPtrArray) firmware_types = fu_context_get_firmware_gtype_ids(ctx); firmware_type = fu_util_prompt_for_firmware_type(priv, firmware_types, error); } if (firmware_type == NULL) return FALSE; gtype = fu_context_get_firmware_gtype_by_id(ctx, firmware_type); if (gtype == G_TYPE_INVALID) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GType %s not supported", firmware_type); return FALSE; } firmware = g_object_new(gtype, NULL); if (!fu_firmware_parse(firmware, blob_src, priv->flags, error)) return FALSE; /* add patch */ fu_firmware_add_patch(firmware, offset, patch); /* write new file */ blob_dst = fu_firmware_write(firmware, error); if (blob_dst == NULL) return FALSE; if (!fu_bytes_set_contents(values[0], blob_dst, error)) return FALSE; str = fu_firmware_to_string(firmware); fu_console_print_literal(priv->console, str); /* success */ return TRUE; } static gboolean fu_util_verify_update(FuUtilPrivate *priv, gchar **values, GError **error) { g_autofree gchar *str = NULL; g_autoptr(FuDevice) dev = NULL; /* progress */ fu_progress_set_id(priv->progress, G_STRLOC); fu_progress_add_step(priv->progress, FWUPD_STATUS_LOADING, 50, "start-engine"); fu_progress_add_step(priv->progress, FWUPD_STATUS_DEVICE_VERIFY, 50, "verify-update"); /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, fu_progress_get_child(priv->progress), error)) return FALSE; fu_progress_step_done(priv->progress); /* get device */ priv->filter_device_include |= FWUPD_DEVICE_FLAG_UPDATABLE; if (g_strv_length(values) == 1) { dev = fu_util_get_device(priv, values[0], error); if (dev == NULL) return FALSE; } else { dev = fu_util_prompt_for_device(priv, NULL, error); if (dev == NULL) return FALSE; } /* add checksums */ if (!fu_engine_verify_update(priv->engine, fu_device_get_id(dev), fu_progress_get_child(priv->progress), error)) return FALSE; fu_progress_step_done(priv->progress); /* show checksums */ str = fu_device_to_string(dev); fu_console_print_literal(priv->console, str); return TRUE; } static gboolean fu_util_get_history(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_autoptr(GNode) root = g_node_new(NULL); /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; /* get all devices from the history database */ devices = fu_engine_get_history(priv->engine, error); if (devices == NULL) return FALSE; /* show each device */ for (guint i = 0; i < devices->len; i++) { g_autoptr(GPtrArray) rels = NULL; FwupdDevice *dev = g_ptr_array_index(devices, i); FwupdRelease *rel; const gchar *remote; GNode *child; g_autoptr(GError) error_local = NULL; if (!fwupd_device_match_flags(dev, priv->filter_device_include, priv->filter_device_exclude)) continue; child = g_node_append_data(root, dev); rel = fwupd_device_get_release_default(dev); if (rel == NULL) continue; remote = fwupd_release_get_remote_id(rel); /* doesn't actually map to remote */ if (remote == NULL) { g_node_append_data(child, rel); continue; } /* try to lookup releases from client, falling back to the history release */ rels = fu_engine_get_releases(priv->engine, priv->request, fwupd_device_get_id(dev), &error_local); if (rels == NULL) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { rels = g_ptr_array_new(); g_ptr_array_add(rels, fwupd_device_get_release_default(dev)); } else { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } /* map to a release in client */ for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel2 = g_ptr_array_index(rels, j); if (!fwupd_release_match_flags(rel2, priv->filter_release_include, priv->filter_release_exclude)) continue; if (g_strcmp0(remote, fwupd_release_get_remote_id(rel2)) != 0) continue; if (g_strcmp0(fwupd_release_get_version(rel), fwupd_release_get_version(rel2)) != 0) continue; g_node_append_data(child, g_object_ref(rel2)); rel = NULL; break; } /* didn't match anything */ if (rels->len == 0 || rel != NULL) { g_node_append_data(child, rel); continue; } } fu_util_print_tree(priv->console, priv->client, root); return TRUE; } static gboolean fu_util_refresh_remote(FuUtilPrivate *priv, FwupdRemote *remote, GError **error) { g_autofree gchar *uri_raw = NULL; g_autofree gchar *uri_sig = NULL; g_autoptr(GBytes) bytes_raw = NULL; g_autoptr(GBytes) bytes_sig = NULL; /* signature */ if (fwupd_remote_get_metadata_uri_sig(remote) == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "no metadata signature URI available for %s", fwupd_remote_get_id(remote)); return FALSE; } uri_sig = fwupd_remote_build_metadata_sig_uri(remote, error); if (uri_sig == NULL) return FALSE; bytes_sig = fwupd_client_download_bytes(priv->client, uri_sig, FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, priv->cancellable, error); if (bytes_sig == NULL) return FALSE; if (!fwupd_remote_load_signature_bytes(remote, bytes_sig, error)) return FALSE; /* payload */ if (fwupd_remote_get_metadata_uri(remote) == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "no metadata URI available for %s", fwupd_remote_get_id(remote)); return FALSE; } uri_raw = fwupd_remote_build_metadata_uri(remote, error); if (uri_raw == NULL) return FALSE; bytes_raw = fwupd_client_download_bytes(priv->client, uri_raw, FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, priv->cancellable, error); if (bytes_raw == NULL) return FALSE; /* send to daemon */ g_info("updating %s", fwupd_remote_get_id(remote)); return fu_engine_update_metadata_bytes(priv->engine, fwupd_remote_get_id(remote), bytes_raw, bytes_sig, error); } static gboolean fu_util_refresh(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) remotes = NULL; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; /* download new metadata */ remotes = fu_engine_get_remotes(priv->engine, error); if (remotes == NULL) return FALSE; for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index(remotes, i); if (!fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)) continue; if (fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD) continue; if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && !fwupd_remote_needs_refresh(remote)) { g_debug("skipping as remote %s age is %us", fwupd_remote_get_id(remote), (guint)fwupd_remote_get_age(remote)); continue; } if (!fu_util_refresh_remote(priv, remote, error)) return FALSE; } return TRUE; } static gboolean fu_util_get_remotes(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GNode) root = g_node_new(NULL); g_autoptr(GPtrArray) remotes = NULL; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; /* list remotes */ remotes = fu_engine_get_remotes(priv->engine, error); if (remotes == NULL) return FALSE; if (remotes->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "no remotes available"); return FALSE; } for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote_tmp = g_ptr_array_index(remotes, i); g_node_append_data(root, remote_tmp); } fu_util_print_tree(priv->console, priv->client, root); return TRUE; } static gboolean fu_util_security(FuUtilPrivate *priv, gchar **values, GError **error) { FuSecurityAttrToStringFlags flags = FU_SECURITY_ATTR_TO_STRING_FLAG_NONE; g_autoptr(FuSecurityAttrs) attrs = NULL; g_autoptr(FuSecurityAttrs) events = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) items = NULL; g_autoptr(GPtrArray) events_array = NULL; g_autofree gchar *str = NULL; #ifndef HAVE_HSI g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, /* TRANSLATORS: error message for unsupported feature */ _("Host Security ID (HSI) is not supported")); return FALSE; #endif /* HAVE_HSI */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; /* show or hide different elements */ if (priv->show_all) { flags |= FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_OBSOLETES; flags |= FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_URLS; } attrs = fu_engine_get_host_security_attrs(priv->engine); items = fu_security_attrs_get_all(attrs); /* print the "why" */ if (priv->as_json) { str = fu_security_attrs_to_json_string(attrs, error); if (str == NULL) return FALSE; fu_console_print_literal(priv->console, str); return TRUE; } fu_console_print(priv->console, "%s \033[1m%s\033[0m", /* TRANSLATORS: this is a string like 'HSI:2-U' */ _("Host Security ID:"), fu_engine_get_host_security_id(priv->engine)); str = fu_util_security_attrs_to_string(items, flags); fu_console_print_literal(priv->console, str); /* print the "when" */ events = fu_engine_get_host_security_events(priv->engine, 10, error); if (events == NULL) return FALSE; events_array = fu_security_attrs_get_all(attrs); if (events_array->len > 0) { g_autofree gchar *estr = fu_util_security_events_to_string(events_array, flags); if (estr != NULL) fu_console_print_literal(priv->console, estr); } /* print the "also" */ devices = fu_engine_get_devices(priv->engine, error); if (devices == NULL) return FALSE; if (devices->len > 0) { g_autofree gchar *estr = fu_util_security_issues_to_string(devices); if (estr != NULL) fu_console_print_literal(priv->console, estr); } /* success */ return TRUE; } static FuVolume * fu_util_prompt_for_volume(FuUtilPrivate *priv, GError **error) { FuContext *ctx = fu_engine_get_context(priv->engine); FuVolume *volume; guint idx; g_autoptr(GPtrArray) volumes = NULL; /* exactly one */ volumes = fu_context_get_esp_volumes(ctx, error); if (volumes == NULL) return NULL; if (volumes->len == 1) { volume = g_ptr_array_index(volumes, 0); if (fu_volume_get_id(volume) != NULL) { fu_console_print(priv->console, "%s: %s", /* TRANSLATORS: Volume has been chosen by the user */ _("Selected volume"), fu_volume_get_id(volume)); } return g_object_ref(volume); } /* TRANSLATORS: this is to abort the interactive prompt */ fu_console_print(priv->console, "0.\t%s", _("Cancel")); for (guint i = 0; i < volumes->len; i++) { volume = g_ptr_array_index(volumes, i); fu_console_print(priv->console, "%u.\t%s", i + 1, fu_volume_get_id(volume)); } /* TRANSLATORS: get interactive prompt */ idx = fu_console_input_uint(priv->console, volumes->len, "%s", _("Choose volume")); if (idx == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return NULL; } volume = g_ptr_array_index(volumes, idx - 1); return g_object_ref(volume); } static gboolean fu_util_esp_mount(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuVolume) volume = NULL; volume = fu_util_prompt_for_volume(priv, error); if (volume == NULL) return FALSE; return fu_volume_mount(volume, error); } static gboolean fu_util_esp_unmount(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuVolume) volume = NULL; volume = fu_util_prompt_for_volume(priv, error); if (volume == NULL) return FALSE; return fu_volume_unmount(volume, error); } static gboolean fu_util_esp_list(FuUtilPrivate *priv, gchar **values, GError **error) { g_autofree gchar *mount_point = NULL; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(FuVolume) volume = NULL; g_autoptr(GPtrArray) files = NULL; if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_READONLY, priv->progress, error)) return FALSE; volume = fu_util_prompt_for_volume(priv, error); if (volume == NULL) return FALSE; locker = fu_volume_locker(volume, error); if (locker == NULL) return FALSE; mount_point = fu_volume_get_mount_point(volume); if (mount_point == NULL) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "no mountpoint for ESP"); return FALSE; } files = fu_path_get_files(mount_point, error); if (files == NULL) return FALSE; for (guint i = 0; i < files->len; i++) { const gchar *fn = g_ptr_array_index(files, i); fu_console_print_literal(priv->console, fn); } return TRUE; } static gboolean _g_str_equal0(gconstpointer str1, gconstpointer str2) { return g_strcmp0(str1, str2) == 0; } static gboolean fu_util_switch_branch(FuUtilPrivate *priv, gchar **values, GError **error) { const gchar *branch; g_autoptr(FwupdRelease) rel = NULL; g_autoptr(GPtrArray) rels = NULL; g_autoptr(GPtrArray) branches = g_ptr_array_new_with_free_func(g_free); g_autoptr(FuDevice) dev = NULL; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES, priv->progress, error)) return FALSE; /* find the device and check it has multiple branches */ priv->filter_device_include |= FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES; priv->filter_device_include |= FWUPD_DEVICE_FLAG_UPDATABLE; if (g_strv_length(values) == 1) dev = fu_util_get_device(priv, values[1], error); else dev = fu_util_prompt_for_device(priv, NULL, error); if (dev == NULL) return FALSE; if (!fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Multiple branches not available"); return FALSE; } /* get all releases, including the alternate branch versions */ rels = fu_engine_get_releases(priv->engine, priv->request, fu_device_get_id(dev), error); if (rels == NULL) return FALSE; /* get all the unique branches */ for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel_tmp = g_ptr_array_index(rels, i); const gchar *branch_tmp = fwupd_release_get_branch(rel_tmp); if (!fwupd_release_match_flags(rel_tmp, priv->filter_release_include, priv->filter_release_exclude)) continue; if (g_ptr_array_find_with_equal_func(branches, branch_tmp, _g_str_equal0, NULL)) continue; g_ptr_array_add(branches, g_strdup(branch_tmp)); } /* branch name is optional */ if (g_strv_length(values) > 1) { branch = values[1]; } else if (branches->len == 1) { branch = g_ptr_array_index(branches, 0); } else { guint idx; /* TRANSLATORS: this is to abort the interactive prompt */ fu_console_print(priv->console, "0.\t%s", _("Cancel")); for (guint i = 0; i < branches->len; i++) { const gchar *branch_tmp = g_ptr_array_index(branches, i); fu_console_print(priv->console, "%u.\t%s", i + 1, fu_util_branch_for_display(branch_tmp)); } /* TRANSLATORS: get interactive prompt, where branch is the * supplier of the firmware, e.g. "non-free" or "free" */ idx = fu_console_input_uint(priv->console, branches->len, "%s", _("Choose branch")); if (idx == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return FALSE; } branch = g_ptr_array_index(branches, idx - 1); } /* sanity check */ if (g_strcmp0(branch, fu_device_get_branch(dev)) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device %s is already on branch %s", fu_device_get_name(dev), fu_util_branch_for_display(branch)); return FALSE; } /* the releases are ordered by version */ for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel_tmp = g_ptr_array_index(rels, j); if (g_strcmp0(fwupd_release_get_branch(rel_tmp), branch) == 0) { rel = g_object_ref(rel_tmp); break; } } if (rel == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No releases for branch %s", fu_util_branch_for_display(branch)); return FALSE; } /* we're switching branch */ if (!fu_util_switch_branch_warning(priv->console, FWUPD_DEVICE(dev), rel, FALSE, error)) return FALSE; /* update the console if composite devices are also updated */ priv->current_operation = FU_UTIL_OPERATION_INSTALL; g_signal_connect(FU_ENGINE(priv->engine), "device-changed", G_CALLBACK(fu_util_update_device_changed_cb), priv); priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH; if (!fu_util_install_release(priv, rel, error)) return FALSE; fu_util_display_current_message(priv); /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug("skipping reboot check"); return TRUE; } return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_set_bios_setting(FuUtilPrivate *priv, gchar **input, GError **error) { g_autoptr(GHashTable) settings = fu_util_bios_settings_parse_argv(input, error); if (settings == NULL) return FALSE; if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG, priv->progress, error)) return FALSE; if (!fu_engine_modify_bios_settings(priv->engine, settings, FALSE, error)) { if (!g_error_matches(*error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) g_prefix_error(error, "failed to set BIOS setting: "); return FALSE; } if (!priv->as_json) { gpointer key, value; GHashTableIter iter; g_hash_table_iter_init(&iter, settings); while (g_hash_table_iter_next(&iter, &key, &value)) { g_autofree gchar *msg = /* TRANSLATORS: Configured a BIOS setting to a value */ g_strdup_printf(_("Set BIOS setting '%s' using '%s'."), (const gchar *)key, (const gchar *)value); fu_console_print_literal(priv->console, msg); } } priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; if (priv->no_reboot_check) { g_debug("skipping reboot check"); return TRUE; } return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_security_fix(FuUtilPrivate *priv, gchar **values, GError **error) { #ifndef HAVE_HSI g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, /* TRANSLATORS: error message for unsupported feature */ _("Host Security ID (HSI) is not supported")); return FALSE; #endif /* HAVE_HSI */ if (g_strv_length(values) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, /* TRANSLATOR: This is the error message for * incorrect parameter */ _("Invalid arguments, expected an AppStream ID")); return FALSE; } if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS, priv->progress, error)) return FALSE; if (!fu_engine_fix_host_security_attr(priv->engine, values[0], error)) return FALSE; /* TRANSLATORS: we've fixed a security problem on the machine */ fu_console_print_literal(priv->console, _("Fixed successfully")); return TRUE; } static gboolean fu_util_security_undo(FuUtilPrivate *priv, gchar **values, GError **error) { #ifndef HAVE_HSI g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, /* TRANSLATORS: error message for unsupported feature */ _("Host Security ID (HSI) is not supported")); return FALSE; #endif /* HAVE_HSI */ if (g_strv_length(values) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, /* TRANSLATOR: This is the error message for * incorrect parameter */ _("Invalid arguments, expected an AppStream ID")); return FALSE; } if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG | FU_ENGINE_LOAD_FLAG_REMOTES | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS, priv->progress, error)) return FALSE; if (!fu_engine_undo_host_security_attr(priv->engine, values[0], error)) return FALSE; /* TRANSLATORS: we've fixed a security problem on the machine */ fu_console_print_literal(priv->console, _("Fix reverted successfully")); return TRUE; } static gboolean fu_util_get_bios_setting(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuBiosSettings) attrs = NULL; g_autoptr(GPtrArray) items = NULL; FuContext *ctx = fu_engine_get_context(priv->engine); gboolean found = FALSE; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG, priv->progress, error)) return FALSE; attrs = fu_context_get_bios_settings(ctx); items = fu_bios_settings_get_all(attrs); if (priv->as_json) return fu_util_get_bios_setting_as_json(priv->console, values, items, error); for (guint i = 0; i < items->len; i++) { FwupdBiosSetting *attr = g_ptr_array_index(items, i); if (fu_util_bios_setting_matches_args(attr, values)) { g_autofree gchar *tmp = fu_util_bios_setting_to_string(attr, 0); fu_console_print_literal(priv->console, tmp); found = TRUE; } } if (items->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, /* TRANSLATORS: error message */ _("This system doesn't support firmware settings")); return FALSE; } if (!found) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "%s: '%s'", /* TRANSLATORS: error message */ _("Unable to find attribute"), values[0]); return FALSE; } return TRUE; } static gboolean fu_util_reboot_cleanup(FuUtilPrivate *priv, gchar **values, GError **error) { FuPlugin *plugin; g_autoptr(FuDevice) device = NULL; if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_COLDPLUG, priv->progress, error)) return FALSE; /* both arguments are optional */ if (g_strv_length(values) >= 1) { device = fu_engine_get_device(priv->engine, values[1], error); if (device == NULL) return FALSE; } else { device = fu_util_prompt_for_device(priv, NULL, error); if (device == NULL) return FALSE; } plugin = fu_engine_get_plugin_by_name(priv->engine, fu_device_get_plugin(device), error); if (plugin == NULL) return FALSE; return fu_plugin_runner_reboot_cleanup(plugin, device, error); } static gboolean fu_util_efivar_list(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) names = NULL; /* sanity check */ if (g_strv_length(values) < 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, /* TRANSLATORS: error message */ _("Invalid arguments, expected GUID")); return FALSE; } names = fu_efivar_get_names(values[0], error); if (names == NULL) return FALSE; for (guint i = 0; i < names->len; i++) { const gchar *name = g_ptr_array_index(names, i); fu_console_print(priv->console, "name: %s", name); } /* success */ return TRUE; } static gboolean fu_util_build_cabinet(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GBytes) cab_blob = NULL; g_autoptr(FuCabinet) cab_file = fu_cabinet_new(); /* sanity check */ if (g_strv_length(values) < 3) { g_set_error_literal( error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, /* TRANSLATORS: error message */ _("Invalid arguments, expected at least ARCHIVE FIRMWARE METAINFO")); return FALSE; } /* file already exists */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && g_file_test(values[0], G_FILE_TEST_EXISTS)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Filename already exists"); return FALSE; } /* add each file */ for (guint i = 1; values[i] != NULL; i++) { g_autoptr(GBytes) blob = NULL; g_autofree gchar *basename = g_path_get_basename(values[i]); blob = fu_bytes_get_contents(values[i], error); if (blob == NULL) return FALSE; if (g_bytes_get_size(blob) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "%s has zero size", values[i]); return FALSE; } fu_cabinet_add_file(cab_file, basename, blob); } /* export */ cab_blob = fu_firmware_write(FU_FIRMWARE(cab_file), error); if (cab_blob == NULL) return FALSE; /* sanity check JCat and XML MetaInfo files */ if (!fu_firmware_parse(FU_FIRMWARE(cab_file), cab_blob, FWUPD_INSTALL_FLAG_NONE, error)) return FALSE; return fu_bytes_set_contents(values[0], cab_blob, error); } static gboolean fu_util_version(FuUtilPrivate *priv, GError **error) { g_autoptr(GHashTable) metadata = NULL; g_autofree gchar *str = NULL; /* load engine */ if (!fu_util_start_engine(priv, FU_ENGINE_LOAD_FLAG_READONLY | FU_ENGINE_LOAD_FLAG_BUILTIN_PLUGINS, priv->progress, error)) return FALSE; /* get metadata */ metadata = fu_engine_get_report_metadata(priv->engine, error); if (metadata == NULL) return FALSE; /* dump to the screen in the most appropriate format */ if (priv->as_json) return fu_util_project_versions_as_json(priv->console, metadata, error); str = fu_util_project_versions_to_string(metadata); fu_console_print_literal(priv->console, str); return TRUE; } static gboolean fu_util_clear_history(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FuHistory) history = fu_history_new(); return fu_history_remove_all(history, error); } static gboolean fu_util_setup_interactive(FuUtilPrivate *priv, GError **error) { if (priv->as_json) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "using --json"); return FALSE; } return fu_console_setup(priv->console, error); } static void fu_util_print_error(FuUtilPrivate *priv, const GError *error) { if (priv->as_json) { fu_util_print_error_as_json(priv->console, error); return; } fu_console_print_full(priv->console, FU_CONSOLE_PRINT_FLAG_STDERR, "%s\n", error->message); } int main(int argc, char *argv[]) { gboolean allow_branch_switch = FALSE; gboolean allow_older = FALSE; gboolean allow_reinstall = FALSE; gboolean force = FALSE; gboolean no_search = FALSE; gboolean ret; gboolean version = FALSE; gboolean ignore_checksum = FALSE; gboolean ignore_vid_pid = FALSE; g_auto(GStrv) plugin_glob = NULL; g_autoptr(FuContext) ctx = fu_context_new(); g_autoptr(FuUtilPrivate) priv = g_new0(FuUtilPrivate, 1); g_autoptr(GError) error_console = NULL; g_autoptr(GError) error = NULL; g_autoptr(GPtrArray) cmd_array = fu_util_cmd_array_new(); g_autofree gchar *cmd_descriptions = NULL; g_autofree gchar *filter_device = NULL; g_autofree gchar *filter_release = NULL; const GOptionEntry options[] = { {"version", '\0', 0, G_OPTION_ARG_NONE, &version, /* TRANSLATORS: command line option */ N_("Show client and daemon versions"), NULL}, {"allow-reinstall", '\0', 0, G_OPTION_ARG_NONE, &allow_reinstall, /* TRANSLATORS: command line option */ N_("Allow reinstalling existing firmware versions"), NULL}, {"allow-older", '\0', 0, G_OPTION_ARG_NONE, &allow_older, /* TRANSLATORS: command line option */ N_("Allow downgrading firmware versions"), NULL}, {"allow-branch-switch", '\0', 0, G_OPTION_ARG_NONE, &allow_branch_switch, /* TRANSLATORS: command line option */ N_("Allow switching firmware branch"), NULL}, {"force", '\0', 0, G_OPTION_ARG_NONE, &force, /* TRANSLATORS: command line option */ N_("Force the action by relaxing some runtime checks"), NULL}, {"ignore-checksum", '\0', 0, G_OPTION_ARG_NONE, &ignore_checksum, /* TRANSLATORS: command line option */ N_("Ignore firmware checksum failures"), NULL}, {"ignore-vid-pid", '\0', 0, G_OPTION_ARG_NONE, &ignore_vid_pid, /* TRANSLATORS: command line option */ N_("Ignore firmware hardware mismatch failures"), NULL}, {"no-reboot-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_reboot_check, /* TRANSLATORS: command line option */ N_("Do not check or prompt for reboot after update"), NULL}, {"no-search", '\0', 0, G_OPTION_ARG_NONE, &no_search, /* TRANSLATORS: command line option */ N_("Do not search the firmware when parsing"), NULL}, {"no-safety-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_safety_check, /* TRANSLATORS: command line option */ N_("Do not perform device safety checks"), NULL}, {"no-device-prompt", '\0', 0, G_OPTION_ARG_NONE, &priv->no_device_prompt, /* TRANSLATORS: command line option */ N_("Do not prompt for devices"), NULL}, {"show-all", '\0', 0, G_OPTION_ARG_NONE, &priv->show_all, /* TRANSLATORS: command line option */ N_("Show all results"), NULL}, {"show-all-devices", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &priv->show_all, /* TRANSLATORS: command line option */ N_("Show devices that are not updatable"), NULL}, {"plugins", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &plugin_glob, /* TRANSLATORS: command line option */ N_("Manually enable specific plugins"), NULL}, {"plugin-whitelist", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING_ARRAY, &plugin_glob, /* TRANSLATORS: command line option */ N_("Manually enable specific plugins"), NULL}, {"prepare", '\0', 0, G_OPTION_ARG_NONE, &priv->prepare_blob, /* TRANSLATORS: command line option */ N_("Run the plugin composite prepare routine when using install-blob"), NULL}, {"cleanup", '\0', 0, G_OPTION_ARG_NONE, &priv->cleanup_blob, /* TRANSLATORS: command line option */ N_("Run the plugin composite cleanup routine when using install-blob"), NULL}, {"disable-ssl-strict", '\0', 0, G_OPTION_ARG_NONE, &priv->disable_ssl_strict, /* TRANSLATORS: command line option */ N_("Ignore SSL strict checks when downloading files"), NULL}, {"filter", '\0', 0, G_OPTION_ARG_STRING, &filter_device, /* TRANSLATORS: command line option */ N_("Filter with a set of device flags using a ~ prefix to " "exclude, e.g. 'internal,~needs-reboot'"), NULL}, {"filter-release", '\0', 0, G_OPTION_ARG_STRING, &filter_release, /* TRANSLATORS: command line option */ N_("Filter with a set of release flags using a ~ prefix to " "exclude, e.g. 'trusted-release,~trusted-metadata'"), NULL}, {"json", '\0', 0, G_OPTION_ARG_NONE, &priv->as_json, /* TRANSLATORS: command line option */ N_("Output in JSON format"), NULL}, {NULL}}; #ifdef _WIN32 /* workaround Windows setting the codepage to 1252 */ (void)g_setenv("LANG", "C.UTF-8", FALSE); #endif setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); #ifndef SUPPORTED_BUILD /* make critical warnings fatal */ (void)g_setenv("G_DEBUG", "fatal-criticals", FALSE); #endif /* create helper object */ priv->lock_fd = -1; priv->main_ctx = g_main_context_new(); priv->loop = g_main_loop_new(priv->main_ctx, FALSE); priv->console = fu_console_new(); priv->post_requests = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); fu_console_set_main_context(priv->console, priv->main_ctx); priv->request = fu_engine_request_new(); /* used for monitoring and downloading */ priv->client = fwupd_client_new(); fwupd_client_set_main_context(priv->client, priv->main_ctx); fwupd_client_set_daemon_version(priv->client, PACKAGE_VERSION); fwupd_client_set_user_agent_for_package(priv->client, "fwupdtool", PACKAGE_VERSION); g_signal_connect(FWUPD_CLIENT(priv->client), "notify::percentage", G_CALLBACK(fu_util_client_notify_cb), priv); g_signal_connect(FWUPD_CLIENT(priv->client), "notify::status", G_CALLBACK(fu_util_client_notify_cb), priv); /* when not using the engine */ priv->progress = fu_progress_new(G_STRLOC); g_signal_connect(priv->progress, "percentage-changed", G_CALLBACK(fu_util_progress_percentage_changed_cb), priv); g_signal_connect(priv->progress, "status-changed", G_CALLBACK(fu_util_progress_status_changed_cb), priv); /* add commands */ fu_util_cmd_array_add(cmd_array, "smbios-dump", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILE"), /* TRANSLATORS: command description */ _("Dump SMBIOS data from a file"), fu_util_smbios_dump); fu_util_cmd_array_add(cmd_array, "get-plugins", NULL, /* TRANSLATORS: command description */ _("Get all enabled plugins registered with the system"), fu_util_get_plugins); fu_util_cmd_array_add(cmd_array, "get-details", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILE"), /* TRANSLATORS: command description */ _("Gets details about a firmware file"), fu_util_get_details); fu_util_cmd_array_add(cmd_array, "get-history", NULL, /* TRANSLATORS: command description */ _("Show history of firmware updates"), fu_util_get_history); fu_util_cmd_array_add(cmd_array, "get-updates,get-upgrades", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Gets the list of updates for connected hardware"), fu_util_get_updates); fu_util_cmd_array_add(cmd_array, "get-devices,get-topology", NULL, /* TRANSLATORS: command description */ _("Get all devices that support firmware updates"), fu_util_get_devices); fu_util_cmd_array_add(cmd_array, "get-device-flags", NULL, /* TRANSLATORS: command description */ _("Get all device flags supported by fwupd"), fu_util_get_device_flags); fu_util_cmd_array_add(cmd_array, "watch", NULL, /* TRANSLATORS: command description */ _("Watch for hardware changes"), fu_util_watch); fu_util_cmd_array_add(cmd_array, "install-blob", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME DEVICE-ID"), /* TRANSLATORS: command description */ _("Install a raw firmware blob on a device"), fu_util_install_blob); fu_util_cmd_array_add(cmd_array, "install", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILE [DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Install a specific firmware on a device, all possible devices" " will also be installed once the CAB matches"), fu_util_install); fu_util_cmd_array_add(cmd_array, "reinstall", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("DEVICE-ID|GUID"), /* TRANSLATORS: command description */ _("Reinstall firmware on a device"), fu_util_reinstall); fu_util_cmd_array_add(cmd_array, "attach", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("DEVICE-ID|GUID"), /* TRANSLATORS: command description */ _("Attach to firmware mode"), fu_util_attach); fu_util_cmd_array_add(cmd_array, "get-report-metadata", NULL, /* TRANSLATORS: command description */ _("Get device report metadata"), fu_util_get_report_metadata); fu_util_cmd_array_add(cmd_array, "detach", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("DEVICE-ID|GUID"), /* TRANSLATORS: command description */ _("Detach to bootloader mode"), fu_util_detach); fu_util_cmd_array_add(cmd_array, "unbind-driver", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Unbind current driver"), fu_util_unbind_driver); fu_util_cmd_array_add(cmd_array, "bind-driver", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("SUBSYSTEM DRIVER [DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Bind new kernel driver"), fu_util_bind_driver); fu_util_cmd_array_add(cmd_array, "activate", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Activate pending devices"), fu_util_activate); fu_util_cmd_array_add(cmd_array, "hwids", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[SMBIOS-FILE|HWIDS-FILE]"), /* TRANSLATORS: command description */ _("Return all the hardware IDs for the machine"), fu_util_hwids); fu_util_cmd_array_add(cmd_array, "export-hwids", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("HWIDS-FILE"), /* TRANSLATORS: command description */ _("Save a file that allows generation of hardware IDs"), fu_util_export_hwids); fu_util_cmd_array_add(cmd_array, "monitor", NULL, /* TRANSLATORS: command description */ _("Monitor the daemon for events"), fu_util_monitor); fu_util_cmd_array_add(cmd_array, "update,upgrade", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Updates all specified devices to latest firmware version, or all " "devices if unspecified"), fu_util_update); fu_util_cmd_array_add(cmd_array, "self-sign", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("TEXT"), /* TRANSLATORS: command description */ C_("command-description", "Sign data using the client certificate"), fu_util_self_sign); fu_util_cmd_array_add(cmd_array, "verify-update", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Update the stored metadata with current contents"), fu_util_verify_update); fu_util_cmd_array_add(cmd_array, "firmware-sign", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME CERTIFICATE PRIVATE-KEY"), /* TRANSLATORS: command description */ _("Sign a firmware with a new key"), fu_util_firmware_sign); fu_util_cmd_array_add(cmd_array, "firmware-dump", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME [DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Read a firmware blob from a device"), fu_util_firmware_dump); fu_util_cmd_array_add(cmd_array, "firmware-read", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME [DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Read a firmware from a device"), fu_util_firmware_read); fu_util_cmd_array_add(cmd_array, "firmware-patch", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME OFFSET DATA [FIRMWARE-TYPE]"), /* TRANSLATORS: command description */ _("Patch a firmware blob at a known offset"), fu_util_firmware_patch); fu_util_cmd_array_add( cmd_array, "firmware-convert", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME-SRC FILENAME-DST [FIRMWARE-TYPE-SRC] [FIRMWARE-TYPE-DST]"), /* TRANSLATORS: command description */ _("Convert a firmware file"), fu_util_firmware_convert); fu_util_cmd_array_add(cmd_array, "firmware-build", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("BUILDER-XML FILENAME-DST"), /* TRANSLATORS: command description */ _("Build a firmware file"), fu_util_firmware_build); fu_util_cmd_array_add(cmd_array, "firmware-parse", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME [FIRMWARE-TYPE]"), /* TRANSLATORS: command description */ _("Parse and show details about a firmware file"), fu_util_firmware_parse); fu_util_cmd_array_add(cmd_array, "firmware-export", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME [FIRMWARE-TYPE]"), /* TRANSLATORS: command description */ _("Export a firmware file structure to XML"), fu_util_firmware_export); fu_util_cmd_array_add(cmd_array, "firmware-extract", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME [FIRMWARE-TYPE]"), /* TRANSLATORS: command description */ _("Extract a firmware blob to images"), fu_util_firmware_extract); fu_util_cmd_array_add(cmd_array, "get-firmware-types", NULL, /* TRANSLATORS: command description */ _("List the available firmware types"), fu_util_get_firmware_types); fu_util_cmd_array_add(cmd_array, "get-firmware-gtypes", NULL, /* TRANSLATORS: command description */ _("List the available firmware GTypes"), fu_util_get_firmware_gtypes); fu_util_cmd_array_add(cmd_array, "get-remotes", NULL, /* TRANSLATORS: command description */ _("Gets the configured remotes"), fu_util_get_remotes); fu_util_cmd_array_add(cmd_array, "refresh", NULL, /* TRANSLATORS: command description */ _("Refresh metadata from remote server"), fu_util_refresh); fu_util_cmd_array_add(cmd_array, "security", NULL, /* TRANSLATORS: command description */ _("Gets the host security attributes"), fu_util_security); fu_util_cmd_array_add(cmd_array, "esp-mount", NULL, /* TRANSLATORS: command description */ _("Mounts the ESP"), fu_util_esp_mount); fu_util_cmd_array_add(cmd_array, "esp-unmount", NULL, /* TRANSLATORS: command description */ _("Unmounts the ESP"), fu_util_esp_unmount); fu_util_cmd_array_add(cmd_array, "esp-list", NULL, /* TRANSLATORS: command description */ _("Lists files on the ESP"), fu_util_esp_list); fu_util_cmd_array_add(cmd_array, "switch-branch", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID] [BRANCH]"), /* TRANSLATORS: command description */ _("Switch the firmware branch on the device"), fu_util_switch_branch); fu_util_cmd_array_add(cmd_array, "clear-history", NULL, /* TRANSLATORS: command description */ _("Erase all firmware update history"), fu_util_clear_history); fu_util_cmd_array_add( cmd_array, "get-bios-settings,get-bios-setting", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[SETTING1] [ SETTING2]..."), /* TRANSLATORS: command description */ _("Retrieve BIOS settings. If no arguments are passed all settings are returned"), fu_util_get_bios_setting); fu_util_cmd_array_add(cmd_array, "set-bios-setting", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("SETTING VALUE"), /* TRANSLATORS: command description */ _("Set a BIOS setting"), fu_util_set_bios_setting); fu_util_cmd_array_add(cmd_array, "build-cabinet", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("ARCHIVE FIRMWARE METAINFO [FIRMWARE] [METAINFO] [JCATFILE]"), /* TRANSLATORS: command description */ _("Build a cabinet archive from a firmware blob and XML metadata"), fu_util_build_cabinet); fu_util_cmd_array_add(cmd_array, "efivar-list", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ C_("command-argument", "GUID"), /* TRANSLATORS: command description */ _("List EFI variables with a specific GUID"), fu_util_efivar_list); fu_util_cmd_array_add(cmd_array, "security-fix", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[APPSTREAM_ID]"), /* TRANSLATORS: command description */ _("Fix a specific host security attribute"), fu_util_security_fix); fu_util_cmd_array_add(cmd_array, "security-undo", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[APPSTREAM_ID]"), /* TRANSLATORS: command description */ _("Undo the host security attribute fix"), fu_util_security_undo); fu_util_cmd_array_add(cmd_array, "reboot-cleanup", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE]"), /* TRANSLATORS: command description */ _("Run the post-reboot cleanup action"), fu_util_reboot_cleanup); fu_util_cmd_array_add(cmd_array, "modify-config", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("KEY,VALUE"), /* TRANSLATORS: sets something in the daemon configuration file */ _("Modifies a daemon configuration value"), fu_util_modify_config); fu_util_cmd_array_add(cmd_array, "modify-remote", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("REMOTE-ID KEY VALUE"), /* TRANSLATORS: command description */ _("Modifies a given remote"), fu_util_remote_modify); fu_util_cmd_array_add(cmd_array, "enable-remote", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("REMOTE-ID"), /* TRANSLATORS: command description */ _("Enables a given remote"), fu_util_remote_enable); fu_util_cmd_array_add(cmd_array, "disable-remote", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("REMOTE-ID"), /* TRANSLATORS: command description */ _("Disables a given remote"), fu_util_remote_disable); fu_util_cmd_array_add(cmd_array, "enable-test-devices", NULL, /* TRANSLATORS: command description */ _("Enables virtual testing devices"), fu_util_enable_test_devices); fu_util_cmd_array_add(cmd_array, "disable-test-devices", NULL, /* TRANSLATORS: command description */ _("Disables virtual testing devices"), fu_util_disable_test_devices); /* do stuff on ctrl+c */ priv->cancellable = g_cancellable_new(); fu_util_setup_signal_handlers(priv); g_signal_connect(G_CANCELLABLE(priv->cancellable), "cancelled", G_CALLBACK(fu_util_cancelled_cb), priv); /* sort by command name */ fu_util_cmd_array_sort(cmd_array); /* non-TTY consoles cannot answer questions */ if (!fu_util_setup_interactive(priv, &error_console)) { g_info("failed to initialize interactive console: %s", error_console->message); priv->no_reboot_check = TRUE; priv->no_safety_check = TRUE; priv->no_device_prompt = TRUE; } else { priv->interactive = TRUE; /* set our implemented feature set */ fu_engine_request_set_feature_flags( priv->request, FWUPD_FEATURE_FLAG_DETACH_ACTION | FWUPD_FEATURE_FLAG_SWITCH_BRANCH | FWUPD_FEATURE_FLAG_FDE_WARNING | FWUPD_FEATURE_FLAG_UPDATE_ACTION | FWUPD_FEATURE_FLAG_COMMUNITY_TEXT | FWUPD_FEATURE_FLAG_SHOW_PROBLEMS | FWUPD_FEATURE_FLAG_REQUESTS | FWUPD_FEATURE_FLAG_REQUESTS_NON_GENERIC); } fu_console_set_interactive(priv->console, priv->interactive); /* get a list of the commands */ priv->context = g_option_context_new(NULL); cmd_descriptions = fu_util_cmd_array_to_string(cmd_array); g_option_context_set_summary(priv->context, cmd_descriptions); g_option_context_set_description( priv->context, /* TRANSLATORS: CLI description */ _("This tool allows an administrator to use the fwupd plugins " "without being installed on the host system.")); /* TRANSLATORS: program name */ g_set_application_name(_("Firmware Utility")); g_option_context_add_main_entries(priv->context, options, NULL); g_option_context_add_group(priv->context, fu_debug_get_option_group()); ret = g_option_context_parse(priv->context, &argc, &argv, &error); if (!ret) { fu_console_print(priv->console, "%s: %s", /* TRANSLATORS: the user didn't read the man page */ _("Failed to parse arguments"), error->message); return EXIT_FAILURE; } fu_progress_set_profile(priv->progress, g_getenv("FWUPD_VERBOSE") != NULL); /* allow disabling SSL strict mode for broken corporate proxies */ if (priv->disable_ssl_strict) { fu_console_print_full(priv->console, FU_CONSOLE_PRINT_FLAG_WARNING, "%s\n", /* TRANSLATORS: try to help */ _("Ignoring SSL strict checks, " "to do this automatically in the future " "export DISABLE_SSL_STRICT in your environment")); (void)g_setenv("DISABLE_SSL_STRICT", "1", TRUE); } /* parse filter flags */ if (filter_device != NULL) { if (!fu_util_parse_filter_device_flags(filter_device, &priv->filter_device_include, &priv->filter_device_exclude, &error)) { /* TRANSLATORS: the user didn't read the man page */ g_prefix_error(&error, "%s: ", _("Failed to parse flags for --filter")); fu_util_print_error(priv, error); return EXIT_FAILURE; } } if (filter_release != NULL) { if (!fu_util_parse_filter_release_flags(filter_release, &priv->filter_release_include, &priv->filter_release_exclude, &error)) { /* TRANSLATORS: the user didn't read the man page */ g_prefix_error(&error, "%s: ", _("Failed to parse flags for --filter-release")); fu_util_print_error(priv, error); return EXIT_FAILURE; } } /* set flags */ if (allow_reinstall) priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; if (allow_older) priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; if (allow_branch_switch) priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH; if (force) priv->flags |= FWUPD_INSTALL_FLAG_FORCE; if (no_search) priv->flags |= FWUPD_INSTALL_FLAG_NO_SEARCH; if (ignore_checksum) priv->flags |= FWUPD_INSTALL_FLAG_IGNORE_CHECKSUM; if (ignore_vid_pid) priv->flags |= FWUPD_INSTALL_FLAG_IGNORE_VID_PID; /* load engine */ priv->engine = fu_engine_new(ctx); g_signal_connect(FU_ENGINE(priv->engine), "device-request", G_CALLBACK(fu_util_update_device_request_cb), priv); g_signal_connect(FU_ENGINE(priv->engine), "device-added", G_CALLBACK(fu_main_engine_device_added_cb), priv); g_signal_connect(FU_ENGINE(priv->engine), "device-removed", G_CALLBACK(fu_main_engine_device_removed_cb), priv); g_signal_connect(FU_ENGINE(priv->engine), "status-changed", G_CALLBACK(fu_main_engine_status_changed_cb), priv); /* just show versions and exit */ if (version) { if (!fu_util_version(priv, &error)) { fu_util_print_error(priv, error); return EXIT_FAILURE; } return EXIT_SUCCESS; } /* any plugin allowlist specified */ for (guint i = 0; plugin_glob != NULL && plugin_glob[i] != NULL; i++) fu_engine_add_plugin_filter(priv->engine, plugin_glob[i]); /* run the specified command */ ret = fu_util_cmd_array_run(cmd_array, priv, argv[1], (gchar **)&argv[2], &error); if (!ret) { #ifdef SUPPORTED_BUILD /* sanity check */ if (error == NULL) { g_critical("exec failed but no error set!"); return EXIT_FAILURE; } #endif fu_util_print_error(priv, error); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS)) { fu_console_print_literal(priv->console, /* TRANSLATORS: explain how to get help */ _("Use fwupdtool --help for help")); } else if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_info("%s\n", error->message); return EXIT_NOTHING_TO_DO; } else if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { g_info("%s\n", error->message); return EXIT_NOT_FOUND; } #ifdef HAVE_GETUID /* if not root, then notify users on the error path */ if (priv->interactive && (getuid() != 0 || geteuid() != 0)) { fu_console_print_full(priv->console, FU_CONSOLE_PRINT_FLAG_STDERR | FU_CONSOLE_PRINT_FLAG_WARNING, "%s\n", /* TRANSLATORS: we're poking around as a power user */ _("This program may only work correctly as root")); } #endif return EXIT_FAILURE; } /* a good place to do the traceback */ if (fu_progress_get_profile(priv->progress)) { g_autofree gchar *str = fu_progress_traceback(priv->progress); if (str != NULL) fu_console_print_literal(priv->console, str); } /* success */ return EXIT_SUCCESS; } fwupd-1.9.16/src/fu-udev-backend.c000066400000000000000000000257651460375044200166540ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuBackend" #include "config.h" #include #include #include "fu-context-private.h" #include "fu-device-private.h" #include "fu-udev-backend.h" struct _FuUdevBackend { FuBackend parent_instance; GUdevClient *gudev_client; GHashTable *changed_idle_ids; /* sysfs:FuUdevBackendHelper */ GPtrArray *dpaux_devices; /* of FuDpauxDevice */ guint dpaux_devices_rescan_id; gboolean done_coldplug; }; G_DEFINE_TYPE(FuUdevBackend, fu_udev_backend, FU_TYPE_BACKEND) #define FU_UDEV_BACKEND_DPAUX_RESCAN_DELAY 5 /* s */ static void fu_udev_backend_to_string(FuBackend *backend, guint idt, GString *str) { FuUdevBackend *self = FU_UDEV_BACKEND(backend); fu_string_append_kb(str, idt, "DoneColdplug", self->done_coldplug); } static void fu_udev_backend_rescan_dpaux_device(FuUdevBackend *self, FuDevice *dpaux_device) { FuDevice *device_tmp; g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; /* find the device we enumerated */ g_debug("looking for %s", fu_device_get_backend_id(dpaux_device)); device_tmp = fu_backend_lookup_by_id(FU_BACKEND(self), fu_device_get_backend_id(dpaux_device)); /* open */ fu_device_probe_invalidate(dpaux_device); locker = fu_device_locker_new(dpaux_device, &error_local); if (locker == NULL) { g_debug("failed to open device %s: %s", fu_device_get_backend_id(dpaux_device), error_local->message); if (device_tmp != NULL) fu_backend_device_removed(FU_BACKEND(self), FU_DEVICE(device_tmp)); return; } if (device_tmp == NULL) { fu_backend_device_added(FU_BACKEND(self), FU_DEVICE(dpaux_device)); return; } } static gboolean fu_udev_backend_rescan_dpaux_devices_cb(gpointer user_data) { FuUdevBackend *self = FU_UDEV_BACKEND(user_data); for (guint i = 0; i < self->dpaux_devices->len; i++) { FuDevice *dpaux_device = g_ptr_array_index(self->dpaux_devices, i); fu_udev_backend_rescan_dpaux_device(self, dpaux_device); } self->dpaux_devices_rescan_id = 0; return FALSE; } static void fu_udev_backend_rescan_dpaux_devices(FuUdevBackend *self) { if (self->dpaux_devices_rescan_id != 0) g_source_remove(self->dpaux_devices_rescan_id); self->dpaux_devices_rescan_id = g_timeout_add_seconds(FU_UDEV_BACKEND_DPAUX_RESCAN_DELAY, fu_udev_backend_rescan_dpaux_devices_cb, self); } static void fu_udev_backend_device_add(FuUdevBackend *self, GUdevDevice *udev_device) { FuContext *ctx = fu_backend_get_context(FU_BACKEND(self)); GType gtype = FU_TYPE_UDEV_DEVICE; g_autoptr(FuUdevDevice) device = NULL; g_autoptr(GPtrArray) possible_plugins = NULL; struct { const gchar *subsystem; GType gtype; } subsystem_gtype_map[] = {{"mei", FU_TYPE_MEI_DEVICE}, {"drm", FU_TYPE_DRM_DEVICE}, {"i2c", FU_TYPE_I2C_DEVICE}, {"i2c-dev", FU_TYPE_I2C_DEVICE}, {"drm_dp_aux_dev", FU_TYPE_DPAUX_DEVICE}, {NULL, G_TYPE_INVALID}}; /* create the correct object depending on the subsystem */ for (guint i = 0; subsystem_gtype_map[i].gtype != G_TYPE_INVALID; i++) { if (g_strcmp0(g_udev_device_get_subsystem(udev_device), subsystem_gtype_map[i].subsystem) == 0) { gtype = subsystem_gtype_map[i].gtype; break; } } /* success */ device = g_object_new(gtype, "context", fu_backend_get_context(FU_BACKEND(self)), "udev-device", udev_device, NULL); /* notify plugins using fu_plugin_add_udev_subsystem() */ possible_plugins = fu_context_get_plugin_names_for_udev_subsystem(ctx, g_udev_device_get_subsystem(udev_device), NULL); if (possible_plugins != NULL) { for (guint i = 0; i < possible_plugins->len; i++) { const gchar *plugin_name = g_ptr_array_index(possible_plugins, i); fu_device_add_possible_plugin(FU_DEVICE(device), plugin_name); } } /* DP AUX devices are *weird* and can only read the DPCD when a DRM device is attached */ if (g_strcmp0(g_udev_device_get_subsystem(udev_device), "drm_dp_aux_dev") == 0) { /* add and rescan, regardless of if we can open it */ g_ptr_array_add(self->dpaux_devices, g_object_ref(device)); fu_udev_backend_rescan_dpaux_devices(self); /* open -- this might seem redundant, but it means the device is added at daemon * coldplug rather than a few seconds later */ if (!self->done_coldplug) { g_autoptr(FuDeviceLocker) locker = NULL; g_autoptr(GError) error_local = NULL; locker = fu_device_locker_new(device, &error_local); if (locker == NULL) { g_debug("failed to open device %s: %s", fu_device_get_backend_id(FU_DEVICE(device)), error_local->message); return; } fu_backend_device_added(FU_BACKEND(self), FU_DEVICE(device)); } return; } /* success */ fu_backend_device_added(FU_BACKEND(self), FU_DEVICE(device)); } static void fu_udev_backend_device_remove(FuUdevBackend *self, GUdevDevice *udev_device) { FuDevice *device_tmp; /* find the device we enumerated */ device_tmp = fu_backend_lookup_by_id(FU_BACKEND(self), g_udev_device_get_sysfs_path(udev_device)); if (device_tmp != NULL) { g_debug("UDEV %s removed", g_udev_device_get_sysfs_path(udev_device)); fu_backend_device_removed(FU_BACKEND(self), device_tmp); /* rescan all the DP AUX devices if it or any DRM device disappears */ if (g_ptr_array_remove(self->dpaux_devices, device_tmp) || g_strcmp0(g_udev_device_get_subsystem(udev_device), "drm") == 0) { fu_udev_backend_rescan_dpaux_devices(self); } } } typedef struct { FuUdevBackend *self; FuDevice *device; guint idle_id; } FuUdevBackendHelper; static void fu_udev_backend_changed_helper_free(FuUdevBackendHelper *helper) { if (helper->idle_id != 0) g_source_remove(helper->idle_id); g_object_unref(helper->self); g_object_unref(helper->device); g_free(helper); } static FuUdevBackendHelper * fu_udev_backend_changed_helper_new(FuUdevBackend *self, FuDevice *device) { FuUdevBackendHelper *helper = g_new0(FuUdevBackendHelper, 1); helper->self = g_object_ref(self); helper->device = g_object_ref(device); return helper; } static gboolean fu_udev_backend_device_changed_cb(gpointer user_data) { FuUdevBackendHelper *helper = (FuUdevBackendHelper *)user_data; fu_backend_device_changed(FU_BACKEND(helper->self), helper->device); if (g_strcmp0(fu_udev_device_get_subsystem(FU_UDEV_DEVICE(helper->device)), "drm") != 0) fu_udev_backend_rescan_dpaux_devices(helper->self); helper->idle_id = 0; g_hash_table_remove(helper->self->changed_idle_ids, fu_udev_device_get_sysfs_path(FU_UDEV_DEVICE(helper->device))); return FALSE; } static void fu_udev_backend_device_changed(FuUdevBackend *self, GUdevDevice *udev_device) { const gchar *sysfs_path = g_udev_device_get_sysfs_path(udev_device); FuUdevBackendHelper *helper; FuDevice *device_tmp; /* not a device we enumerated */ device_tmp = fu_backend_lookup_by_id(FU_BACKEND(self), sysfs_path); if (device_tmp == NULL) return; /* run all plugins, with per-device rate limiting */ if (g_hash_table_remove(self->changed_idle_ids, sysfs_path)) { g_debug("re-adding rate-limited timeout for %s", sysfs_path); } else { g_debug("adding rate-limited timeout for %s", sysfs_path); } helper = fu_udev_backend_changed_helper_new(self, device_tmp); helper->idle_id = g_timeout_add(500, fu_udev_backend_device_changed_cb, helper); g_hash_table_insert(self->changed_idle_ids, g_strdup(sysfs_path), helper); } static void fu_udev_backend_uevent_cb(GUdevClient *gudev_client, const gchar *action, GUdevDevice *udev_device, FuUdevBackend *self) { if (g_strcmp0(action, "add") == 0) { fu_udev_backend_device_add(self, udev_device); return; } if (g_strcmp0(action, "remove") == 0) { fu_udev_backend_device_remove(self, udev_device); return; } if (g_strcmp0(action, "change") == 0) { fu_udev_backend_device_changed(self, udev_device); return; } } static void fu_udev_backend_coldplug_subsystem(FuUdevBackend *self, const gchar *subsystem, FuProgress *progress) { g_autolist(GObject) devices = NULL; devices = g_udev_client_query_by_subsystem(self->gudev_client, subsystem); g_debug("%u devices with subsystem %s", g_list_length(devices), subsystem); fu_progress_set_id(progress, G_STRLOC); fu_progress_set_name(progress, subsystem); fu_progress_set_steps(progress, g_list_length(devices)); for (GList *l = devices; l != NULL; l = l->next) { GUdevDevice *udev_device = l->data; fu_progress_set_name(fu_progress_get_child(progress), g_udev_device_get_sysfs_path(udev_device)); fu_udev_backend_device_add(self, udev_device); fu_progress_step_done(progress); } } static gboolean fu_udev_backend_coldplug(FuBackend *backend, FuProgress *progress, GError **error) { FuContext *ctx = fu_backend_get_context(backend); FuUdevBackend *self = FU_UDEV_BACKEND(backend); g_autoptr(GPtrArray) udev_subsystems = fu_context_get_udev_subsystems(ctx); /* udev watches can only be set up in _init() so set up client now */ if (udev_subsystems->len > 0) { g_auto(GStrv) subsystems = g_new0(gchar *, udev_subsystems->len + 1); for (guint i = 0; i < udev_subsystems->len; i++) { const gchar *subsystem = g_ptr_array_index(udev_subsystems, i); subsystems[i] = g_strdup(subsystem); } self->gudev_client = g_udev_client_new((const gchar *const *)subsystems); g_signal_connect(G_UDEV_CLIENT(self->gudev_client), "uevent", G_CALLBACK(fu_udev_backend_uevent_cb), self); } /* get all devices of class */ fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, udev_subsystems->len); for (guint i = 0; i < udev_subsystems->len; i++) { const gchar *subsystem = g_ptr_array_index(udev_subsystems, i); fu_udev_backend_coldplug_subsystem(self, subsystem, fu_progress_get_child(progress)); fu_progress_step_done(progress); } /* success */ self->done_coldplug = TRUE; return TRUE; } static void fu_udev_backend_finalize(GObject *object) { FuUdevBackend *self = FU_UDEV_BACKEND(object); if (self->dpaux_devices_rescan_id != 0) g_source_remove(self->dpaux_devices_rescan_id); if (self->gudev_client != NULL) g_object_unref(self->gudev_client); g_hash_table_unref(self->changed_idle_ids); g_ptr_array_unref(self->dpaux_devices); G_OBJECT_CLASS(fu_udev_backend_parent_class)->finalize(object); } static void fu_udev_backend_init(FuUdevBackend *self) { self->dpaux_devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); self->changed_idle_ids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)fu_udev_backend_changed_helper_free); } static void fu_udev_backend_class_init(FuUdevBackendClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuBackendClass *klass_backend = FU_BACKEND_CLASS(klass); object_class->finalize = fu_udev_backend_finalize; klass_backend->coldplug = fu_udev_backend_coldplug; klass_backend->to_string = fu_udev_backend_to_string; } FuBackend * fu_udev_backend_new(FuContext *ctx) { return FU_BACKEND(g_object_new(FU_TYPE_UDEV_BACKEND, "name", "udev", "context", ctx, NULL)); } fwupd-1.9.16/src/fu-udev-backend.h000066400000000000000000000005211460375044200166400ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-backend.h" #define FU_TYPE_UDEV_BACKEND (fu_udev_backend_get_type()) G_DECLARE_FINAL_TYPE(FuUdevBackend, fu_udev_backend, FU, UDEV_BACKEND, FuBackend) FuBackend * fu_udev_backend_new(FuContext *ctx); fwupd-1.9.16/src/fu-usb-backend.c000066400000000000000000000202151460375044200164630ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuBackend" #include "config.h" #include #include "fu-usb-backend.h" #include "fu-usb-device-private.h" struct _FuUsbBackend { FuBackend parent_instance; GUsbContext *usb_ctx; }; G_DEFINE_TYPE(FuUsbBackend, fu_usb_backend, FU_TYPE_BACKEND) #define FU_USB_BACKEND_POLL_INTERVAL_DEFAULT 1000 /* ms */ #define FU_USB_BACKEND_POLL_INTERVAL_WAIT_REPLUG 5 /* ms */ #ifdef _WIN32 static void fu_usb_backend_device_notify_flags_cb(FuDevice *device, GParamSpec *pspec, FuBackend *backend) { #if G_USB_CHECK_VERSION(0, 3, 10) FuUsbBackend *self = FU_USB_BACKEND(backend); /* if waiting for a disconnect, set win32 to poll insanely fast -- and set it * back to the default when the device removal was detected */ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) { g_debug("setting USB poll interval to %ums to detect replug", (guint)FU_USB_BACKEND_POLL_INTERVAL_WAIT_REPLUG); g_usb_context_set_hotplug_poll_interval(self->usb_ctx, FU_USB_BACKEND_POLL_INTERVAL_WAIT_REPLUG); } else { g_usb_context_set_hotplug_poll_interval(self->usb_ctx, FU_USB_BACKEND_POLL_INTERVAL_DEFAULT); } #else g_warning("GUsb >= 0.3.10 may be needed to notice device enumeration"); #endif } #endif static void fu_usb_backend_device_added_cb(GUsbContext *ctx, GUsbDevice *usb_device, FuBackend *backend) { g_autoptr(FuUsbDevice) device = fu_usb_device_new(fu_backend_get_context(backend), usb_device); fu_backend_device_added(backend, FU_DEVICE(device)); } static void fu_usb_backend_device_removed_cb(GUsbContext *ctx, GUsbDevice *usb_device, FuBackend *backend) { FuDevice *device = fu_backend_lookup_by_id(backend, g_usb_device_get_platform_id(usb_device)); if (device != NULL) fu_backend_device_removed(backend, device); } static void fu_usb_backend_context_finalized_cb(gpointer data, GObject *where_the_object_was) { g_critical("GUsbContext %p was finalized from under our feet!", where_the_object_was); } static void fu_usb_backend_context_flags_check(FuUsbBackend *self) { #if G_USB_CHECK_VERSION(0, 4, 5) FuContext *ctx = fu_backend_get_context(FU_BACKEND(self)); GUsbContextFlags usb_flags = G_USB_CONTEXT_FLAGS_DEBUG; if (fu_context_has_flag(ctx, FU_CONTEXT_FLAG_SAVE_EVENTS)) { g_debug("saving FuUsbBackend events"); usb_flags |= G_USB_CONTEXT_FLAGS_SAVE_EVENTS; } g_usb_context_set_flags(self->usb_ctx, usb_flags); #endif } static void fu_usb_backend_context_flags_notify_cb(FuContext *ctx, GParamSpec *pspec, FuUsbBackend *self) { fu_usb_backend_context_flags_check(self); } static gboolean fu_usb_backend_setup(FuBackend *backend, FuProgress *progress, GError **error) { FuUsbBackend *self = FU_USB_BACKEND(backend); self->usb_ctx = g_usb_context_new(error); if (self->usb_ctx == NULL) { g_prefix_error(error, "failed to get USB context: "); return FALSE; } g_object_weak_ref(G_OBJECT(self->usb_ctx), fu_usb_backend_context_finalized_cb, self); g_signal_connect(fu_backend_get_context(backend), "notify::flags", G_CALLBACK(fu_usb_backend_context_flags_notify_cb), self); fu_usb_backend_context_flags_check(self); return TRUE; } static void fu_usb_backend_coldplug_device(FuUsbBackend *self, GUsbDevice *usb_device, FuProgress *progress) { g_autoptr(FuUsbDevice) device = NULL; g_autofree gchar *name = NULL; name = g_strdup_printf("%04X:%04X", g_usb_device_get_vid(usb_device), g_usb_device_get_pid(usb_device)); fu_progress_set_name(progress, name); device = fu_usb_device_new(fu_backend_get_context(FU_BACKEND(self)), usb_device); fu_backend_device_added(FU_BACKEND(self), FU_DEVICE(device)); } static void fu_usb_backend_coldplug_devices(FuUsbBackend *self, GPtrArray *usb_devices, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, usb_devices->len); for (guint i = 0; i < usb_devices->len; i++) { GUsbDevice *usb_device = g_ptr_array_index(usb_devices, i); fu_usb_backend_coldplug_device(self, usb_device, fu_progress_get_child(progress)); fu_progress_step_done(progress); } } static gboolean fu_usb_backend_coldplug(FuBackend *backend, FuProgress *progress, GError **error) { FuUsbBackend *self = FU_USB_BACKEND(backend); g_autoptr(GPtrArray) usb_devices = NULL; /* progress */ fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 1, "enumerate"); fu_progress_add_step(progress, FWUPD_STATUS_LOADING, 99, "add-devices"); /* no insight */ g_usb_context_enumerate(self->usb_ctx); fu_progress_step_done(progress); /* add each device */ usb_devices = g_usb_context_get_devices(self->usb_ctx); fu_usb_backend_coldplug_devices(self, usb_devices, fu_progress_get_child(progress)); fu_progress_step_done(progress); /* watch for future changes */ g_signal_connect(G_USB_CONTEXT(self->usb_ctx), "device-added", G_CALLBACK(fu_usb_backend_device_added_cb), self); g_signal_connect(G_USB_CONTEXT(self->usb_ctx), "device-removed", G_CALLBACK(fu_usb_backend_device_removed_cb), self); return TRUE; } static gboolean fu_usb_backend_load(FuBackend *backend, JsonObject *json_object, const gchar *tag, FuBackendLoadFlags flags, GError **error) { #if G_USB_CHECK_VERSION(0, 4, 5) FuUsbBackend *self = FU_USB_BACKEND(backend); return g_usb_context_load_with_tag(self->usb_ctx, json_object, tag, error); #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "GUsb version too old to load backends"); return FALSE; #endif } static gboolean fu_usb_backend_save(FuBackend *backend, JsonBuilder *json_builder, const gchar *tag, FuBackendSaveFlags flags, GError **error) { #if G_USB_CHECK_VERSION(0, 4, 5) FuUsbBackend *self = FU_USB_BACKEND(backend); guint usb_events_cnt = 0; g_autoptr(GPtrArray) devices = g_usb_context_get_devices(self->usb_ctx); for (guint i = 0; i < devices->len; i++) { GUsbDevice *usb_device = g_ptr_array_index(devices, i); g_autoptr(GPtrArray) usb_events = g_usb_device_get_events(usb_device); if (usb_events->len > 0 || g_usb_device_has_tag(usb_device, tag)) { g_info("%u USB events to save for %s", usb_events->len, g_usb_device_get_platform_id(usb_device)); } usb_events_cnt += usb_events->len; } if (usb_events_cnt == 0) return TRUE; if (!g_usb_context_save_with_tag(self->usb_ctx, json_builder, tag, error)) return FALSE; for (guint i = 0; i < devices->len; i++) { GUsbDevice *usb_device = g_ptr_array_index(devices, i); g_usb_device_clear_events(usb_device); } return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "GUsb version too old to save backends"); return FALSE; #endif } static void fu_usb_backend_registered(FuBackend *backend, FuDevice *device) { #ifdef _WIN32 /* not required */ if (!FU_IS_USB_DEVICE(device)) return; /* on win32 we need to poll the context faster */ g_signal_connect(FU_DEVICE(device), "notify::flags", G_CALLBACK(fu_usb_backend_device_notify_flags_cb), backend); #endif } static void fu_usb_backend_finalize(GObject *object) { FuUsbBackend *self = FU_USB_BACKEND(object); if (self->usb_ctx != NULL) { g_signal_handlers_disconnect_by_data(G_USB_CONTEXT(self->usb_ctx), self); g_object_weak_unref(G_OBJECT(self->usb_ctx), fu_usb_backend_context_finalized_cb, self); g_object_unref(self->usb_ctx); } G_OBJECT_CLASS(fu_usb_backend_parent_class)->finalize(object); } static void fu_usb_backend_init(FuUsbBackend *self) { } static void fu_usb_backend_class_init(FuUsbBackendClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuBackendClass *klass_backend = FU_BACKEND_CLASS(klass); object_class->finalize = fu_usb_backend_finalize; klass_backend->setup = fu_usb_backend_setup; klass_backend->coldplug = fu_usb_backend_coldplug; klass_backend->load = fu_usb_backend_load; klass_backend->save = fu_usb_backend_save; klass_backend->registered = fu_usb_backend_registered; } FuBackend * fu_usb_backend_new(FuContext *ctx) { return FU_BACKEND(g_object_new(FU_TYPE_USB_BACKEND, "name", "usb", "context", ctx, NULL)); } fwupd-1.9.16/src/fu-usb-backend.h000066400000000000000000000005131460375044200164670ustar00rootroot00000000000000/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include "fu-backend.h" #define FU_TYPE_USB_BACKEND (fu_usb_backend_get_type()) G_DECLARE_FINAL_TYPE(FuUsbBackend, fu_usb_backend, FU, USB_BACKEND, FuBackend) FuBackend * fu_usb_backend_new(FuContext *ctx); fwupd-1.9.16/src/fu-util-bios-setting.c000066400000000000000000000146641460375044200177020ustar00rootroot00000000000000/* * Copyright (C) 2022 Mario Limonciello * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include "config.h" #include #include "fu-bios-settings-private.h" #include "fu-util-bios-setting.h" #include "fu-util-common.h" static void fu_util_bios_setting_update_description(FwupdBiosSetting *setting) { const gchar *new = NULL; /* try to look it up from translations */ new = gettext(fwupd_bios_setting_get_description(setting)); if (new != NULL) fwupd_bios_setting_set_description(setting, new); } static const gchar * fu_util_bios_setting_kind_to_string(FwupdBiosSettingKind kind) { if (kind == FWUPD_BIOS_SETTING_KIND_ENUMERATION) { /* TRANSLATORS: The BIOS setting can only be changed to fixed values */ return _("Enumeration"); } if (kind == FWUPD_BIOS_SETTING_KIND_INTEGER) { /* TRANSLATORS: The BIOS setting only accepts integers in a fixed range */ return _("Integer"); } if (kind == FWUPD_BIOS_SETTING_KIND_STRING) { /* TRANSLATORS: The BIOS setting accepts strings */ return _("String"); } return NULL; } gboolean fu_util_bios_setting_matches_args(FwupdBiosSetting *setting, gchar **values) { const gchar *name; /* no arguments set */ if (g_strv_length(values) == 0) return TRUE; name = fwupd_bios_setting_get_name(setting); /* check all arguments */ for (guint j = 0; j < g_strv_length(values); j++) { if (g_strcmp0(name, values[j]) == 0) return TRUE; } return FALSE; } gboolean fu_util_get_bios_setting_as_json(FuConsole *console, gchar **values, GPtrArray *settings, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "BiosSettings"); json_builder_begin_array(builder); for (guint i = 0; i < settings->len; i++) { FwupdBiosSetting *setting = g_ptr_array_index(settings, i); if (fu_util_bios_setting_matches_args(setting, values)) { fu_util_bios_setting_update_description(setting); json_builder_begin_object(builder); fwupd_bios_setting_to_json(setting, builder); json_builder_end_object(builder); } } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(console, builder, error); } gchar * fu_util_bios_setting_to_string(FwupdBiosSetting *setting, guint idt) { const gchar *tmp; FwupdBiosSettingKind type; g_autofree gchar *debug_str = NULL; g_autofree gchar *current_value = NULL; g_autoptr(GString) str = g_string_new(NULL); debug_str = fwupd_bios_setting_to_string(setting); g_debug("%s", debug_str); tmp = fwupd_bios_setting_get_name(setting); fu_string_append(str, idt, tmp, NULL); type = fwupd_bios_setting_get_kind(setting); tmp = fu_util_bios_setting_kind_to_string(type); if (tmp != NULL) { /* TRANSLATORS: type of BIOS setting */ fu_string_append(str, idt + 1, _("Setting type"), tmp); } tmp = fwupd_bios_setting_get_current_value(setting); if (tmp != NULL) { current_value = g_strdup(tmp); } else { /* TRANSLATORS: tell a user how to get information */ current_value = g_strdup_printf(_("Run without '%s' to see"), "--no-authenticate"); } /* TRANSLATORS: current value of a BIOS setting */ fu_string_append(str, idt + 1, _("Current Value"), current_value); fu_util_bios_setting_update_description(setting); tmp = fwupd_bios_setting_get_description(setting); if (tmp != NULL) { /* TRANSLATORS: description of BIOS setting */ fu_string_append(str, idt + 1, _("Description"), tmp); } if (fwupd_bios_setting_get_read_only(setting)) { /* TRANSLATORS: item is TRUE */ tmp = _("True"); } else { /* TRANSLATORS: item is FALSE */ tmp = _("False"); } /* TRANSLATORS: BIOS setting is read only */ fu_string_append(str, idt + 1, _("Read Only"), tmp); if (type == FWUPD_BIOS_SETTING_KIND_INTEGER || type == FWUPD_BIOS_SETTING_KIND_STRING) { g_autofree gchar *lower = g_strdup_printf("%" G_GUINT64_FORMAT, fwupd_bios_setting_get_lower_bound(setting)); g_autofree gchar *upper = g_strdup_printf("%" G_GUINT64_FORMAT, fwupd_bios_setting_get_upper_bound(setting)); if (type == FWUPD_BIOS_SETTING_KIND_INTEGER) { g_autofree gchar *scalar = g_strdup_printf("%" G_GUINT64_FORMAT, fwupd_bios_setting_get_scalar_increment(setting)); if (lower != NULL) { /* TRANSLATORS: Lowest valid integer for BIOS setting */ fu_string_append(str, idt + 1, _("Minimum value"), lower); } if (upper != NULL) { /* TRANSLATORS: Highest valid integer for BIOS setting */ fu_string_append(str, idt + 1, _("Maximum value"), upper); } if (scalar != NULL) { /* TRANSLATORS: Scalar increment for integer BIOS setting */ fu_string_append(str, idt + 1, _("Scalar Increment"), scalar); } } else { if (lower != NULL) { /* TRANSLATORS: Shortest valid string for BIOS setting */ fu_string_append(str, idt + 1, _("Minimum length"), lower); } if (upper != NULL) { /* TRANSLATORS: Longest valid string for BIOS setting */ fu_string_append(str, idt + 1, _("Maximum length"), upper); } } } else if (type == FWUPD_BIOS_SETTING_KIND_ENUMERATION) { GPtrArray *values = fwupd_bios_setting_get_possible_values(setting); if (values != NULL && values->len > 0) { /* TRANSLATORS: Possible values for a bios setting */ fu_string_append(str, idt + 1, _("Possible Values"), NULL); for (guint i = 0; i < values->len; i++) { const gchar *possible = g_ptr_array_index(values, i); g_autofree gchar *index = g_strdup_printf("%u", i); fu_string_append(str, idt + 2, index, possible); } } } return g_string_free(g_steal_pointer(&str), FALSE); } GHashTable * fu_util_bios_settings_parse_argv(gchar **input, GError **error) { GHashTable *bios_settings; /* json input */ if (g_strv_length(input) == 1) { g_autoptr(FuBiosSettings) new_bios_settings = fu_bios_settings_new(); if (!fu_bios_settings_from_json_file(new_bios_settings, input[0], error)) return NULL; return fu_bios_settings_to_hash_kv(new_bios_settings); } if (g_strv_length(input) == 0 || g_strv_length(input) % 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, /* TRANSLATORS: error message */ _("Invalid arguments")); return NULL; } bios_settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); for (guint i = 0; i < g_strv_length(input); i += 2) g_hash_table_insert(bios_settings, g_strdup(input[i]), g_strdup(input[i + 1])); return bios_settings; } fwupd-1.9.16/src/fu-util-bios-setting.h000066400000000000000000000011141460375044200176710ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include "fwupd-bios-setting-private.h" #include "fu-console.h" gchar * fu_util_bios_setting_to_string(FwupdBiosSetting *setting, guint idt); gboolean fu_util_bios_setting_matches_args(FwupdBiosSetting *setting, gchar **values); gboolean fu_util_get_bios_setting_as_json(FuConsole *console, gchar **values, GPtrArray *settings, GError **error); GHashTable * fu_util_bios_settings_parse_argv(gchar **input, GError **error); fwupd-1.9.16/src/fu-util-common.c000066400000000000000000003140521460375044200165550ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include "config.h" #include #include #include #ifdef HAVE_GUSB #include #endif #include #ifdef HAVE_LIBCURL #include #endif #include "fu-console.h" #include "fu-device-private.h" #include "fu-util-common.h" static gchar * fu_util_remote_to_string(FwupdRemote *remote, guint idt); static gchar * fu_util_release_to_string(FwupdRelease *rel, guint idt); static gchar * fu_util_convert_description(const gchar *xml, GError **error); gchar * fu_console_color_format(const gchar *text, FuConsoleColor fg_color) { if (g_getenv("NO_COLOR") != NULL) return g_strdup(text); return g_strdup_printf("\033[%um\033[1m%s\033[0m", fg_color, text); } typedef struct { FwupdClient *client; FuConsole *console; } FuUtilPrintTreeHelper; static gboolean fu_util_traverse_tree(GNode *n, gpointer data) { FuUtilPrintTreeHelper *helper = (FuUtilPrintTreeHelper *)data; guint idx = g_node_depth(n) - 1; g_autofree gchar *tmp = NULL; g_auto(GStrv) split = NULL; /* get split lines */ if (FWUPD_IS_DEVICE(n->data)) { FwupdDevice *dev = FWUPD_DEVICE(n->data); tmp = fu_util_device_to_string(helper->client, dev, idx); } else if (FWUPD_IS_REMOTE(n->data)) { FwupdRemote *remote = FWUPD_REMOTE(n->data); tmp = fu_util_remote_to_string(remote, idx); } else if (FWUPD_IS_RELEASE(n->data)) { FwupdRelease *release = FWUPD_RELEASE(n->data); tmp = fu_util_release_to_string(release, idx); g_info("%s", tmp); } /* root node */ if (n->parent == NULL && g_getenv("FWUPD_VERBOSE") == NULL) { g_autofree gchar *str = g_strdup_printf("%s %s", fwupd_client_get_host_vendor(helper->client), fwupd_client_get_host_product(helper->client)); fu_console_print_literal(helper->console, str); fu_console_print_literal(helper->console, "│"); return FALSE; } if (n->parent == NULL) return FALSE; if (tmp == NULL) return FALSE; split = g_strsplit(tmp, "\n", -1); for (guint i = 0; split[i] != NULL; i++) { g_autoptr(GString) str = g_string_new(NULL); /* header */ if (i == 0) { if (g_node_next_sibling(n) == NULL) g_string_prepend(str, "└─"); else g_string_prepend(str, "├─"); /* properties */ } else { g_string_prepend(str, n->children == NULL ? " " : " │"); g_string_prepend(str, g_node_next_sibling(n) == NULL ? " " : "│"); g_string_append(str, " "); } /* ancestors */ for (GNode *c = n->parent; c != NULL && c->parent != NULL; c = c->parent) { if (g_node_next_sibling(c) != NULL || idx == 0) { g_string_prepend(str, "│ "); continue; } g_string_prepend(str, " "); } /* empty line */ if (split[i][0] == '\0') { fu_console_print_literal(helper->console, str->str); continue; } /* dump to the console */ g_string_append(str, split[i] + (idx * 2)); fu_console_print_literal(helper->console, str->str); } return FALSE; } void fu_util_print_tree(FuConsole *console, FwupdClient *client, GNode *n) { FuUtilPrintTreeHelper helper = {.client = client, .console = console}; g_node_traverse(n, G_PRE_ORDER, G_TRAVERSE_ALL, -1, fu_util_traverse_tree, &helper); } static gboolean fu_util_is_interesting_child(FwupdDevice *dev) { GPtrArray *children = fwupd_device_get_children(dev); for (guint i = 0; i < children->len; i++) { FwupdDevice *child = g_ptr_array_index(children, i); if (fu_util_is_interesting_device(child)) return TRUE; } return FALSE; } gboolean fu_util_is_interesting_device(FwupdDevice *dev) { if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)) return TRUE; if (fwupd_device_get_update_error(dev) != NULL) return TRUE; if (fwupd_device_get_version(dev) != NULL) return TRUE; /* device not plugged in, get-details */ if (fwupd_device_get_flags(dev) == 0) return TRUE; if (fu_util_is_interesting_child(dev)) return TRUE; return FALSE; } gchar * fu_util_get_user_cache_path(const gchar *fn) { const gchar *root = g_get_user_cache_dir(); g_autofree gchar *basename = g_path_get_basename(fn); g_autofree gchar *cachedir_legacy = NULL; /* if run from a systemd unit, use the cache directory set there */ if (g_getenv("CACHE_DIRECTORY") != NULL) root = g_getenv("CACHE_DIRECTORY"); /* return the legacy path if it exists rather than renaming it to * prevent problems when using old and new versions of fwupd */ cachedir_legacy = g_build_filename(root, "fwupdmgr", NULL); if (g_file_test(cachedir_legacy, G_FILE_TEST_IS_DIR)) return g_build_filename(cachedir_legacy, basename, NULL); return g_build_filename(root, "fwupd", basename, NULL); } static gboolean fu_util_update_shutdown(GError **error) { g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GVariant) val = NULL; connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) return FALSE; #ifdef HAVE_LOGIND /* shutdown using logind */ val = g_dbus_connection_call_sync(connection, "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "PowerOff", g_variant_new("(b)", TRUE), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); #elif defined(HAVE_CONSOLEKIT) /* shutdown using ConsoleKit */ val = g_dbus_connection_call_sync(connection, "org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "Stop", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "No supported backend compiled in to perform the operation."); #endif return val != NULL; } gboolean fu_util_update_reboot(GError **error) { g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GVariant) val = NULL; connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, error); if (connection == NULL) return FALSE; #ifdef HAVE_LOGIND /* reboot using logind */ val = g_dbus_connection_call_sync(connection, "org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "Reboot", g_variant_new("(b)", TRUE), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); #elif defined(HAVE_CONSOLEKIT) /* reboot using ConsoleKit */ val = g_dbus_connection_call_sync(connection, "org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "Restart", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, error); #else g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "No supported backend compiled in to perform the operation."); #endif return val != NULL; } static gchar * fu_util_get_release_description_with_fallback(FwupdRelease *rel) { g_autoptr(GString) str = g_string_new(NULL); /* add what we've got from the vendor */ if (fwupd_release_get_description(rel) != NULL) g_string_append(str, fwupd_release_get_description(rel)); /* add this client side to get the translations */ if (fwupd_release_has_flag(rel, FWUPD_RELEASE_FLAG_IS_COMMUNITY)) { g_string_append_printf( str, "

    %s

    ", /* TRANSLATORS: the vendor did not upload this */ _("This firmware is provided by LVFS community members and is not " "provided (or supported) by the original hardware vendor.")); g_string_append_printf( str, "

    %s

    ", /* TRANSLATORS: if it breaks, you get to keep both pieces */ _("Installing this update may also void any device warranty.")); } /* this can't be from the LVFS, but the user could be installing a local file */ if (str->len == 0) { g_string_append_printf(str, "

    %s

    ", /* TRANSLATORS: naughty vendor */ _("The vendor did not supply any release notes.")); } return g_string_free(g_steal_pointer(&str), FALSE); } gboolean fu_util_prompt_warning(FuConsole *console, FwupdDevice *device, FwupdRelease *release, const gchar *machine, GError **error) { FwupdDeviceFlags flags; gint vercmp; g_autofree gchar *desc_fb = NULL; g_autoptr(GString) title = g_string_new(NULL); g_autoptr(GString) str = g_string_new(NULL); /* up, down, or re-install */ vercmp = fu_version_compare(fwupd_release_get_version(release), fu_device_get_version(device), fwupd_device_get_version_format(device)); if (vercmp < 0) { g_string_append_printf( title, /* TRANSLATORS: message letting the user know an downgrade is available * %1 is the device name and %2 and %3 are version strings */ _("Downgrade %s from %s to %s?"), fwupd_device_get_name(device), fwupd_device_get_version(device), fwupd_release_get_version(release)); } else if (vercmp > 0) { g_string_append_printf( title, /* TRANSLATORS: message letting the user know an upgrade is available * %1 is the device name and %2 and %3 are version strings */ _("Upgrade %s from %s to %s?"), fwupd_device_get_name(device), fwupd_device_get_version(device), fwupd_release_get_version(release)); } else { g_string_append_printf( title, /* TRANSLATORS: message letting the user know an upgrade is available * %1 is the device name and %2 is a version string */ _("Reinstall %s to %s?"), fwupd_device_get_name(device), fwupd_release_get_version(release)); } /* description is optional */ desc_fb = fu_util_get_release_description_with_fallback(release); if (desc_fb != NULL) { g_autofree gchar *desc = fu_util_convert_description(desc_fb, NULL); if (desc != NULL) g_string_append_printf(str, "\n%s", desc); } /* device is not already in bootloader mode so show warning */ flags = fwupd_device_get_flags(device); if ((flags & FWUPD_DEVICE_FLAG_IS_BOOTLOADER) == 0) { /* device may reboot */ if ((flags & FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE) == 0) { g_string_append(str, "\n\n"); g_string_append_printf( str, /* TRANSLATORS: warn the user before updating, %1 is a device name */ _("%s and all connected devices may not be usable while updating."), fwupd_device_get_name(device)); /* device can get bricked */ } else if ((flags & FWUPD_DEVICE_FLAG_SELF_RECOVERY) == 0) { g_string_append(str, "\n\n"); /* external device */ if ((flags & FWUPD_DEVICE_FLAG_INTERNAL) == 0) { g_string_append_printf(str, /* TRANSLATORS: warn the user before * updating, %1 is a device name */ _("%s must remain connected for the " "duration of the update to avoid damage."), fwupd_device_get_name(device)); } else if (flags & FWUPD_DEVICE_FLAG_REQUIRE_AC) { g_string_append_printf( str, /* TRANSLATORS: warn the user before updating, %1 is a machine * name */ _("%s must remain plugged into a power source for the duration " "of the update to avoid damage."), machine); } } } fu_console_box(console, title->str, str->str, 80); /* TRANSLATORS: prompt to apply the update */ if (!fu_console_input_bool(console, TRUE, "%s", _("Perform operation?"))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return FALSE; } /* success */ return TRUE; } gboolean fu_util_prompt_complete(FuConsole *console, FwupdDeviceFlags flags, gboolean prompt, GError **error) { if (flags & FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN) { if (prompt) { if (!fu_console_input_bool(console, FALSE, "%s %s", /* TRANSLATORS: explain why */ _("An update requires the system to shutdown " "to complete."), /* TRANSLATORS: shutdown to apply the update */ _("Shutdown now?"))) return TRUE; } return fu_util_update_shutdown(error); } if (flags & FWUPD_DEVICE_FLAG_NEEDS_REBOOT) { if (prompt) { if (!fu_console_input_bool(console, FALSE, "%s %s", /* TRANSLATORS: explain why we want to reboot */ _("An update requires a reboot to complete."), /* TRANSLATORS: reboot to apply the update */ _("Restart now?"))) return TRUE; } return fu_util_update_reboot(error); } return TRUE; } static void fu_util_cmd_free(FuUtilCmd *item) { g_free(item->name); g_free(item->arguments); g_free(item->description); g_free(item); } GPtrArray * fu_util_cmd_array_new(void) { return g_ptr_array_new_with_free_func((GDestroyNotify)fu_util_cmd_free); } static gint fu_util_cmd_sort_cb(FuUtilCmd **item1, FuUtilCmd **item2) { return g_strcmp0((*item1)->name, (*item2)->name); } void fu_util_cmd_array_sort(GPtrArray *array) { g_ptr_array_sort(array, (GCompareFunc)fu_util_cmd_sort_cb); } void fu_util_cmd_array_add(GPtrArray *array, const gchar *name, const gchar *arguments, const gchar *description, FuUtilCmdFunc callback) { g_auto(GStrv) names = NULL; g_return_if_fail(name != NULL); g_return_if_fail(description != NULL); g_return_if_fail(callback != NULL); /* add each one */ names = g_strsplit(name, ",", -1); for (guint i = 0; names[i] != NULL; i++) { FuUtilCmd *item = g_new0(FuUtilCmd, 1); item->name = g_strdup(names[i]); if (i == 0) { item->description = g_strdup(description); } else { /* TRANSLATORS: this is a command alias, e.g. 'get-devices' */ item->description = g_strdup_printf(_("Alias to %s"), names[0]); } item->arguments = g_strdup(arguments); item->callback = callback; g_ptr_array_add(array, item); } } gboolean fu_util_cmd_array_run(GPtrArray *array, FuUtilPrivate *priv, const gchar *command, gchar **values, GError **error) { g_auto(GStrv) values_copy = g_new0(gchar *, g_strv_length(values) + 1); /* clear out bash completion sentinel */ for (guint i = 0; values[i] != NULL; i++) { if (g_strcmp0(values[i], "{") == 0) break; values_copy[i] = g_strdup(values[i]); } /* find command */ for (guint i = 0; i < array->len; i++) { FuUtilCmd *item = g_ptr_array_index(array, i); if (g_strcmp0(item->name, command) == 0) return item->callback(priv, values_copy, error); } /* not found */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, /* TRANSLATORS: error message */ _("Command not found")); return FALSE; } gchar * fu_util_cmd_array_to_string(GPtrArray *array) { gsize len; const gsize max_len = 35; GString *string; /* print each command */ string = g_string_new(""); for (guint i = 0; i < array->len; i++) { FuUtilCmd *item = g_ptr_array_index(array, i); g_string_append(string, " "); g_string_append(string, item->name); len = fu_strwidth(item->name) + 2; if (item->arguments != NULL) { g_string_append(string, " "); g_string_append(string, item->arguments); len += fu_strwidth(item->arguments) + 1; } if (len < max_len) { for (gsize j = len; j < max_len + 1; j++) g_string_append_c(string, ' '); g_string_append(string, item->description); g_string_append_c(string, '\n'); } else { g_string_append_c(string, '\n'); for (gsize j = 0; j < max_len + 1; j++) g_string_append_c(string, ' '); g_string_append(string, item->description); g_string_append_c(string, '\n'); } } /* remove trailing newline */ if (string->len > 0) g_string_set_size(string, string->len - 1); return g_string_free(string, FALSE); } const gchar * fu_util_branch_for_display(const gchar *branch) { if (branch == NULL) { /* TRANSLATORS: this is the default branch name when unset */ return _("default"); } return branch; } static gchar * fu_util_release_get_name(FwupdRelease *release) { const gchar *name = fwupd_release_get_name(release); GPtrArray *cats = fwupd_release_get_categories(release); for (guint i = 0; i < cats->len; i++) { const gchar *cat = g_ptr_array_index(cats, i); if (g_strcmp0(cat, "X-Device") == 0) { /* TRANSLATORS: a specific part of hardware, * the first %s is the device name, e.g. 'Unifying Receiver` */ return g_strdup_printf(_("%s Device Update"), name); } if (g_strcmp0(cat, "X-Configuration") == 0) { /* TRANSLATORS: a specific part of hardware, * the first %s is the device name, e.g. 'Secure Boot` */ return g_strdup_printf(_("%s Configuration Update"), name); } if (g_strcmp0(cat, "X-System") == 0) { /* TRANSLATORS: the entire system, e.g. all internal devices, * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf(_("%s System Update"), name); } if (g_strcmp0(cat, "X-EmbeddedController") == 0) { /* TRANSLATORS: the EC is typically the keyboard controller chip, * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf(_("%s Embedded Controller Update"), name); } if (g_strcmp0(cat, "X-ManagementEngine") == 0) { /* TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf(_("%s ME Update"), name); } if (g_strcmp0(cat, "X-CorporateManagementEngine") == 0) { /* TRANSLATORS: ME stands for Management Engine (with Intel AMT), * where the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf(_("%s Corporate ME Update"), name); } if (g_strcmp0(cat, "X-ConsumerManagementEngine") == 0) { /* TRANSLATORS: ME stands for Management Engine, where * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf(_("%s Consumer ME Update"), name); } if (g_strcmp0(cat, "X-Controller") == 0) { /* TRANSLATORS: the controller is a device that has other devices * plugged into it, for example ThunderBolt, FireWire or USB, * the first %s is the device name, e.g. 'Intel ThunderBolt` */ return g_strdup_printf(_("%s Controller Update"), name); } if (g_strcmp0(cat, "X-ThunderboltController") == 0) { /* TRANSLATORS: the Thunderbolt controller is a device that * has other high speed Thunderbolt devices plugged into it; * the first %s is the system name, e.g. 'ThinkPad P50` */ return g_strdup_printf(_("%s Thunderbolt Controller Update"), name); } if (g_strcmp0(cat, "X-CpuMicrocode") == 0) { /* TRANSLATORS: the CPU microcode is firmware loaded onto the CPU * at system bootup */ return g_strdup_printf(_("%s CPU Microcode Update"), name); } if (g_strcmp0(cat, "X-Battery") == 0) { /* TRANSLATORS: battery refers to the system power source */ return g_strdup_printf(_("%s Battery Update"), name); } if (g_strcmp0(cat, "X-Camera") == 0) { /* TRANSLATORS: camera can refer to the laptop internal * camera in the bezel or external USB webcam */ return g_strdup_printf(_("%s Camera Update"), name); } if (g_strcmp0(cat, "X-TPM") == 0) { /* TRANSLATORS: TPM refers to a Trusted Platform Module */ return g_strdup_printf(_("%s TPM Update"), name); } if (g_strcmp0(cat, "X-Touchpad") == 0) { /* TRANSLATORS: TouchPad refers to a flat input device */ return g_strdup_printf(_("%s Touchpad Update"), name); } if (g_strcmp0(cat, "X-Mouse") == 0) { /* TRANSLATORS: Mouse refers to a handheld input device */ return g_strdup_printf(_("%s Mouse Update"), name); } if (g_strcmp0(cat, "X-Keyboard") == 0) { /* TRANSLATORS: Keyboard refers to an input device for typing */ return g_strdup_printf(_("%s Keyboard Update"), name); } if (g_strcmp0(cat, "X-StorageController") == 0) { /* TRANSLATORS: Storage Controller is typically a RAID or SAS adapter */ return g_strdup_printf(_("%s Storage Controller Update"), name); } if (g_strcmp0(cat, "X-NetworkInterface") == 0) { /* TRANSLATORS: Network Interface refers to the physical * PCI card, not the logical wired connection */ return g_strdup_printf(_("%s Network Interface Update"), name); } if (g_strcmp0(cat, "X-VideoDisplay") == 0) { /* TRANSLATORS: Video Display refers to the laptop internal display or * external monitor */ return g_strdup_printf(_("%s Display Update"), name); } if (g_strcmp0(cat, "X-BaseboardManagementController") == 0) { /* TRANSLATORS: BMC refers to baseboard management controller which * is the device that updates all the other firmware on the system */ return g_strdup_printf(_("%s BMC Update"), name); } if (g_strcmp0(cat, "X-UsbReceiver") == 0) { /* TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth * device that stays in the USB port so the wireless peripheral works */ return g_strdup_printf(_("%s USB Receiver Update"), name); } if (g_strcmp0(cat, "X-Drive") == 0) { /* TRANSLATORS: drive refers to a storage device, e.g. SATA disk */ return g_strdup_printf(_("%s Drive Update"), name); } if (g_strcmp0(cat, "X-FlashDrive") == 0) { /* TRANSLATORS: flash refers to solid state storage, e.g. UFS or eMMC */ return g_strdup_printf(_("%s Flash Drive Update"), name); } if (g_strcmp0(cat, "X-SolidStateDrive") == 0) { /* TRANSLATORS: SSD refers to a Solid State Drive, e.g. non-rotating * SATA or NVMe disk */ return g_strdup_printf(_("%s SSD Update"), name); } if (g_strcmp0(cat, "X-Gpu") == 0) { /* TRANSLATORS: GPU refers to a Graphics Processing Unit, e.g. * the "video card" */ return g_strdup_printf(_("%s GPU Update"), name); } if (g_strcmp0(cat, "X-Dock") == 0) { /* TRANSLATORS: Dock refers to the port replicator hardware laptops are * cradled in, or lowered onto */ return g_strdup_printf(_("%s Dock Update"), name); } if (g_strcmp0(cat, "X-UsbDock") == 0) { /* TRANSLATORS: Dock refers to the port replicator device connected * by plugging in a USB cable -- which may or may not also provide power */ return g_strdup_printf(_("%s USB Dock Update"), name); } if (g_strcmp0(cat, "X-FingerprintReader") == 0) { /* TRANSLATORS: a device that can read your fingerprint pattern */ return g_strdup_printf(_("%s Fingerprint Reader Update"), name); } if (g_strcmp0(cat, "X-GraphicsTablet") == 0) { /* TRANSLATORS: a large pressure-sensitive drawing area typically used * by artists and digital artists */ return g_strdup_printf(_("%s Graphics Tablet Update"), name); } } /* TRANSLATORS: this is the fallback where we don't know if the release * is updating the system, the device, or a device class, or something else -- * the first %s is the device name, e.g. 'ThinkPad P50` */ return g_strdup_printf(_("%s Update"), name); } gboolean fu_util_parse_filter_device_flags(const gchar *filter, FwupdDeviceFlags *include, FwupdDeviceFlags *exclude, GError **error) { FwupdDeviceFlags tmp; g_auto(GStrv) strv = g_strsplit(filter, ",", -1); g_return_val_if_fail(include != NULL, FALSE); g_return_val_if_fail(exclude != NULL, FALSE); for (guint i = 0; strv[i] != NULL; i++) { if (g_str_has_prefix(strv[i], "~")) { tmp = fwupd_device_flag_from_string(strv[i] + 1); if (tmp == FWUPD_DEVICE_FLAG_UNKNOWN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unknown device flag %s", strv[i] + 1); return FALSE; } if ((tmp & *include) > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Filter %s already included", fwupd_device_flag_to_string(tmp)); return FALSE; } if ((tmp & *exclude) > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Filter %s already excluded", fwupd_device_flag_to_string(tmp)); return FALSE; } *exclude |= tmp; } else { tmp = fwupd_device_flag_from_string(strv[i]); if (tmp == FWUPD_DEVICE_FLAG_UNKNOWN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unknown device flag %s", strv[i]); return FALSE; } if ((tmp & *exclude) > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Filter %s already excluded", fwupd_device_flag_to_string(tmp)); return FALSE; } if ((tmp & *include) > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Filter %s already included", fwupd_device_flag_to_string(tmp)); return FALSE; } *include |= tmp; } } return TRUE; } gboolean fu_util_parse_filter_release_flags(const gchar *filter, FwupdReleaseFlags *include, FwupdReleaseFlags *exclude, GError **error) { FwupdDeviceFlags tmp; g_auto(GStrv) strv = g_strsplit(filter, ",", -1); g_return_val_if_fail(include != NULL, FALSE); g_return_val_if_fail(exclude != NULL, FALSE); for (guint i = 0; strv[i] != NULL; i++) { if (g_str_has_prefix(strv[i], "~")) { tmp = fwupd_release_flag_from_string(strv[i] + 1); if (tmp == FWUPD_RELEASE_FLAG_UNKNOWN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unknown release flag %s", strv[i] + 1); return FALSE; } if ((tmp & *include) > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Filter %s already included", fwupd_release_flag_to_string(tmp)); return FALSE; } if ((tmp & *exclude) > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Filter %s already excluded", fwupd_release_flag_to_string(tmp)); return FALSE; } *exclude |= tmp; } else { tmp = fwupd_release_flag_from_string(strv[i]); if (tmp == FWUPD_RELEASE_FLAG_UNKNOWN) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unknown release flag %s", strv[i]); return FALSE; } if ((tmp & *exclude) > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Filter %s already excluded", fwupd_release_flag_to_string(tmp)); return FALSE; } if ((tmp & *include) > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Filter %s already included", fwupd_release_flag_to_string(tmp)); return FALSE; } *include |= tmp; } } return TRUE; } typedef struct { guint cnt; GString *str; } FuUtilConvertHelper; static gboolean fu_util_convert_description_head_cb(XbNode *n, gpointer user_data) { FuUtilConvertHelper *helper = (FuUtilConvertHelper *)user_data; helper->cnt++; /* start */ if (g_strcmp0(xb_node_get_element(n), "em") == 0) { g_string_append(helper->str, "\033[3m"); } else if (g_strcmp0(xb_node_get_element(n), "strong") == 0) { g_string_append(helper->str, "\033[1m"); } else if (g_strcmp0(xb_node_get_element(n), "code") == 0) { g_string_append(helper->str, "`"); } else if (g_strcmp0(xb_node_get_element(n), "li") == 0) { g_string_append(helper->str, "• "); } else if (g_strcmp0(xb_node_get_element(n), "p") == 0 || g_strcmp0(xb_node_get_element(n), "ul") == 0 || g_strcmp0(xb_node_get_element(n), "ol") == 0) { g_string_append(helper->str, "\n"); } /* text */ if (xb_node_get_text(n) != NULL) g_string_append(helper->str, xb_node_get_text(n)); return FALSE; } static gboolean fu_util_convert_description_tail_cb(XbNode *n, gpointer user_data) { FuUtilConvertHelper *helper = (FuUtilConvertHelper *)user_data; helper->cnt++; /* end */ if (g_strcmp0(xb_node_get_element(n), "em") == 0 || g_strcmp0(xb_node_get_element(n), "strong") == 0) { g_string_append(helper->str, "\033[0m"); } else if (g_strcmp0(xb_node_get_element(n), "code") == 0) { g_string_append(helper->str, "`"); } else if (g_strcmp0(xb_node_get_element(n), "li") == 0) { g_string_append(helper->str, "\n"); } else if (g_strcmp0(xb_node_get_element(n), "p") == 0) { g_string_append(helper->str, "\n"); } /* tail */ if (xb_node_get_tail(n) != NULL) g_string_append(helper->str, xb_node_get_tail(n)); return FALSE; } static gchar * fu_util_convert_description(const gchar *xml, GError **error) { g_autoptr(GString) str = g_string_new(NULL); g_autoptr(XbNode) n = NULL; g_autoptr(XbSilo) silo = NULL; FuUtilConvertHelper helper = { .cnt = 0, .str = str, }; /* parse XML */ silo = xb_silo_new_from_xml(xml, error); if (silo == NULL) return NULL; /* convert to something we can show on the console */ n = xb_silo_get_root(silo); xb_node_transmogrify(n, fu_util_convert_description_head_cb, fu_util_convert_description_tail_cb, &helper); /* success */ return fu_strstrip(str->str); } /** * fu_util_time_to_str: * @tmp: the time in seconds * * Converts a timestamp to a 'pretty' translated string * * Returns: (transfer full): A string **/ static gchar * fu_util_time_to_str(guint64 tmp) { g_return_val_if_fail(tmp != 0, NULL); /* seconds */ if (tmp < 60) { /* TRANSLATORS: duration in seconds */ return g_strdup_printf(ngettext("%u second", "%u seconds", (gint)tmp), (guint)tmp); } /* minutes */ tmp /= 60; if (tmp < 60) { /* TRANSLATORS: duration in minutes */ return g_strdup_printf(ngettext("%u minute", "%u minutes", (gint)tmp), (guint)tmp); } /* hours */ tmp /= 60; if (tmp < 60) { /* TRANSLATORS: duration in minutes */ return g_strdup_printf(ngettext("%u hour", "%u hours", (gint)tmp), (guint)tmp); } /* days */ tmp /= 24; /* TRANSLATORS: duration in days! */ return g_strdup_printf(ngettext("%u day", "%u days", (gint)tmp), (guint)tmp); } static gchar * fu_util_device_flag_to_string(guint64 device_flag) { if (device_flag == FWUPD_DEVICE_FLAG_NONE) { return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_INTERNAL) { /* TRANSLATORS: Device cannot be removed easily*/ return _("Internal device"); } if (device_flag == FWUPD_DEVICE_FLAG_UPDATABLE || device_flag == FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN) { /* TRANSLATORS: Device is updatable in this or any other mode */ return _("Updatable"); } if (device_flag == FWUPD_DEVICE_FLAG_ONLY_OFFLINE) { /* TRANSLATORS: Update can only be done from offline mode */ return _("Update requires a reboot"); } if (device_flag == FWUPD_DEVICE_FLAG_REQUIRE_AC) { /* TRANSLATORS: Must be plugged into an outlet */ return _("System requires external power source"); } if (device_flag == FWUPD_DEVICE_FLAG_LOCKED) { /* TRANSLATORS: Is locked and can be unlocked */ return _("Device is locked"); } if (device_flag == FWUPD_DEVICE_FLAG_SUPPORTED) { /* TRANSLATORS: Is found in current metadata */ return _("Supported on remote server"); } if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER) { /* TRANSLATORS: Requires a bootloader mode to be manually enabled by the user */ return _("Requires a bootloader"); } if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_REBOOT) { /* TRANSLATORS: Requires a reboot to apply firmware or to reload hardware */ return _("Needs a reboot after installation"); } if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN) { /* TRANSLATORS: Requires system shutdown to apply firmware */ return _("Needs shutdown after installation"); } if (device_flag == FWUPD_DEVICE_FLAG_REPORTED) { /* TRANSLATORS: Has been reported to a metadata server */ return _("Reported to remote server"); } if (device_flag == FWUPD_DEVICE_FLAG_NOTIFIED) { /* TRANSLATORS: User has been notified */ return _("User has been notified"); } if (device_flag == FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION) { /* skip */ return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_INSTALL_PARENT_FIRST) { /* TRANSLATORS: Install composite firmware on the parent before the child */ return _("Install to parent device first"); } if (device_flag == FWUPD_DEVICE_FLAG_IS_BOOTLOADER) { /* TRANSLATORS: Is currently in bootloader mode */ return _("Is in bootloader mode"); } if (device_flag == FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG) { /* TRANSLATORS: the hardware is waiting to be replugged */ return _("Hardware is waiting to be replugged"); } if (device_flag == FWUPD_DEVICE_FLAG_IGNORE_VALIDATION) { /* TRANSLATORS: Ignore validation safety checks when flashing this device */ return _("Ignore validation safety checks"); } if (device_flag == FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED) { /* skip */ return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION) { /* TRANSLATORS: Device update needs to be separately activated */ return _("Device update needs activation"); } if (device_flag == FWUPD_DEVICE_FLAG_HISTORICAL) { /* skip */ return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_WILL_DISAPPEAR) { /* TRANSLATORS: Device will not return after update completes */ return _("Device will not re-appear after update completes"); } if (device_flag == FWUPD_DEVICE_FLAG_CAN_VERIFY) { /* TRANSLATORS: Device supports some form of checksum verification */ return _("Cryptographic hash verification is available"); } if (device_flag == FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE) { /* skip */ return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_DUAL_IMAGE) { /* TRANSLATORS: Device supports a safety mechanism for flashing */ return _("Device stages updates"); } if (device_flag == FWUPD_DEVICE_FLAG_SELF_RECOVERY) { /* TRANSLATORS: Device supports a safety mechanism for flashing */ return _("Device can recover flash failures"); } if (device_flag == FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE) { /* TRANSLATORS: Device remains usable during update */ return _("Device is usable for the duration of the update"); } if (device_flag == FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED) { /* TRANSLATORS: a version check is required for all firmware */ return _("Device firmware is required to have a version check"); } if (device_flag == FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES) { /* TRANSLATORS: the device cannot update from A->C and has to go A->B->C */ return _("Device is required to install all provided releases"); } if (device_flag == FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES) { /* TRANSLATORS: there is more than one supplier of the firmware */ return _("Device supports switching to a different branch of firmware"); } if (device_flag == FWUPD_DEVICE_FLAG_BACKUP_BEFORE_INSTALL) { /* TRANSLATORS: save the old firmware to disk before installing the new one */ return _("Device will backup firmware before installing"); } if (device_flag == FWUPD_DEVICE_FLAG_WILDCARD_INSTALL) { /* TRANSLATORS: on some systems certain devices have to have matching versions, * e.g. the EFI driver for a given network card cannot be different */ return _("All devices of the same type will be updated at the same time"); } if (device_flag == FWUPD_DEVICE_FLAG_ONLY_VERSION_UPGRADE) { /* TRANSLATORS: some devices can only be updated to a new semver and cannot * be downgraded or reinstalled with the existing version */ return _("Only version upgrades are allowed"); } if (device_flag == FWUPD_DEVICE_FLAG_UNREACHABLE) { /* TRANSLATORS: currently unreachable, perhaps because it is in a lower power state * or is out of wireless range */ return _("Device is unreachable"); } if (device_flag == FWUPD_DEVICE_FLAG_AFFECTS_FDE) { /* TRANSLATORS: we might ask the user the recovery key when next booting Windows */ return _("Full disk encryption secrets may be invalidated when updating"); } if (device_flag == FWUPD_DEVICE_FLAG_END_OF_LIFE) { /* TRANSLATORS: the vendor is no longer supporting the device */ return _("End of life"); } if (device_flag == FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD) { /* TRANSLATORS: firmware is verified on-device the payload using strong crypto */ return _("Signed Payload"); } if (device_flag == FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD) { /* TRANSLATORS: firmware payload is unsigned and it is possible to modify it */ return _("Unsigned Payload"); } if (device_flag == FWUPD_DEVICE_FLAG_EMULATED) { /* TRANSLATORS: this device is not actually real */ return _("Emulated"); } if (device_flag == FWUPD_DEVICE_FLAG_EMULATION_TAG) { /* TRANSLATORS: we're saving all USB events for emulation */ return _("Tagged for emulation"); } if (device_flag == FWUPD_DEVICE_FLAG_ONLY_EXPLICIT_UPDATES) { /* TRANSLATORS: stay on one firmware version unless the new version is explicitly * specified */ return _("Installing a specific release is explicitly required"); } if (device_flag == FWUPD_DEVICE_FLAG_SKIPS_RESTART) { /* skip */ return NULL; } if (device_flag == FWUPD_DEVICE_FLAG_UNKNOWN) { return NULL; } return NULL; } static gchar * fu_util_request_flag_to_string(guint64 request_flag) { if (request_flag == FWUPD_REQUEST_FLAG_NONE) return NULL; if (request_flag == FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE) { /* TRANSLATORS: ask the user to do a simple task which should be translated */ return _("Message"); } if (request_flag == FWUPD_REQUEST_FLAG_ALLOW_GENERIC_IMAGE) { /* TRANSLATORS: show the user a generic image that can be themed */ return _("Image"); } if (request_flag == FWUPD_REQUEST_FLAG_NON_GENERIC_MESSAGE) { /* TRANSLATORS: ask the user a question, and it will not be translated */ return _("Message (custom)"); } if (request_flag == FWUPD_REQUEST_FLAG_NON_GENERIC_IMAGE) { /* TRANSLATORS: show the user a random image from the internet */ return _("Image (custom)"); } return NULL; } static const gchar * fu_util_update_state_to_string(FwupdUpdateState update_state) { if (update_state == FWUPD_UPDATE_STATE_PENDING) { /* TRANSLATORS: the update state of the specific device */ return _("Pending"); } if (update_state == FWUPD_UPDATE_STATE_SUCCESS) { /* TRANSLATORS: the update state of the specific device */ return _("Success"); } if (update_state == FWUPD_UPDATE_STATE_FAILED) { /* TRANSLATORS: the update state of the specific device */ return _("Failed"); } if (update_state == FWUPD_UPDATE_STATE_FAILED_TRANSIENT) { /* TRANSLATORS: the update state of the specific device */ return _("Transient failure"); } if (update_state == FWUPD_UPDATE_STATE_NEEDS_REBOOT) { /* TRANSLATORS: the update state of the specific device */ return _("Needs reboot"); } return NULL; } static gchar * fu_util_device_problem_to_string(FwupdClient *client, FwupdDevice *dev, FwupdDeviceProblem problem) { if (problem == FWUPD_DEVICE_PROBLEM_NONE) return NULL; if (problem == FWUPD_DEVICE_PROBLEM_UNKNOWN) return NULL; if (problem == FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW) { if (fwupd_client_get_battery_level(client) == FWUPD_BATTERY_LEVEL_INVALID || fwupd_client_get_battery_threshold(client) == FWUPD_BATTERY_LEVEL_INVALID) { /* TRANSLATORS: as in laptop battery power */ return g_strdup(_("System power is too low to perform the update")); } return g_strdup_printf( /* TRANSLATORS: as in laptop battery power */ _("System power is too low to perform the update (%u%%, requires %u%%)"), fwupd_client_get_battery_level(client), fwupd_client_get_battery_threshold(client)); } if (problem == FWUPD_DEVICE_PROBLEM_UNREACHABLE) { /* TRANSLATORS: for example, a Bluetooth mouse that is in powersave mode */ return g_strdup(_("Device is unreachable, or out of wireless range")); } if (problem == FWUPD_DEVICE_PROBLEM_POWER_TOO_LOW) { if (fwupd_device_get_battery_level(dev) == FWUPD_BATTERY_LEVEL_INVALID || fwupd_device_get_battery_threshold(dev) == FWUPD_BATTERY_LEVEL_INVALID) { /* TRANSLATORS: for example the batteries *inside* the Bluetooth mouse */ return g_strdup(_("Device battery power is too low")); } /* TRANSLATORS: for example the batteries *inside* the Bluetooth mouse */ return g_strdup_printf(_("Device battery power is too low (%u%%, requires %u%%)"), fwupd_device_get_battery_level(dev), fwupd_device_get_battery_threshold(dev)); } if (problem == FWUPD_DEVICE_PROBLEM_UPDATE_PENDING) { /* TRANSLATORS: usually this is when we're waiting for a reboot */ return g_strdup(_("Device is waiting for the update to be applied")); } if (problem == FWUPD_DEVICE_PROBLEM_REQUIRE_AC_POWER) { /* TRANSLATORS: as in, wired mains power for a laptop */ return g_strdup(_("Device requires AC power to be connected")); } if (problem == FWUPD_DEVICE_PROBLEM_LID_IS_CLOSED) { /* TRANSLATORS: lid means "laptop top cover" */ return g_strdup(_("Device cannot be used while the lid is closed")); } if (problem == FWUPD_DEVICE_PROBLEM_IS_EMULATED) { /* TRANSLATORS: emulated means we are pretending to be a different model */ return g_strdup(_("Device is emulated")); } if (problem == FWUPD_DEVICE_PROBLEM_MISSING_LICENSE) { /* TRANSLATORS: The device cannot be updated due to missing vendor's license." */ return g_strdup(_("Device requires a software license to update")); } if (problem == FWUPD_DEVICE_PROBLEM_SYSTEM_INHIBIT) { /* TRANSLATORS: an application is preventing system updates */ return g_strdup(_("All devices are prevented from update by system inhibit")); } if (problem == FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS) { /* TRANSLATORS: another application is updating the device already */ return g_strdup(_("An update is in progress")); } if (problem == FWUPD_DEVICE_PROBLEM_IN_USE) { /* TRANSLATORS: device cannot be interrupted, for instance taking a phone call */ return g_strdup(_("Device is in use")); } if (problem == FWUPD_DEVICE_PROBLEM_DISPLAY_REQUIRED) { /* TRANSLATORS: device does not have a display connected */ return g_strdup(_("Device requires a display to be plugged in")); } return NULL; } gchar * fu_util_device_to_string(FwupdClient *client, FwupdDevice *dev, guint idt) { FwupdUpdateState state; GPtrArray *guids = fwupd_device_get_guids(dev); GPtrArray *issues = fwupd_device_get_issues(dev); GPtrArray *vendor_ids = fwupd_device_get_vendor_ids(dev); GPtrArray *instance_ids = fwupd_device_get_instance_ids(dev); const gchar *tmp; const gchar *tmp2; guint64 flags = fwupd_device_get_flags(dev); guint64 modified = fwupd_device_get_modified(dev); guint64 request_flags = fwupd_device_get_request_flags(dev); g_autoptr(GHashTable) ids = NULL; g_autoptr(GString) str = g_string_new(NULL); /* some fields are intentionally not included and are only shown in --verbose */ if (g_getenv("FWUPD_VERBOSE") != NULL) { g_autofree gchar *debug_str = NULL; debug_str = fwupd_device_to_string(dev); g_info("%s", debug_str); return NULL; } tmp = fwupd_device_get_name(dev); if (tmp == NULL) { /* TRANSLATORS: Name of hardware */ tmp = _("Unknown Device"); } fu_string_append(str, idt, tmp, NULL); tmp = fwupd_device_get_id(dev); if (tmp != NULL) { /* TRANSLATORS: ID for hardware, typically a SHA1 sum */ fu_string_append(str, idt + 1, _("Device ID"), tmp); } /* summary */ tmp = fwupd_device_get_summary(dev); if (tmp != NULL) { /* TRANSLATORS: one line summary of device */ fu_string_append(str, idt + 1, _("Summary"), tmp); } /* description */ tmp = fwupd_device_get_description(dev); if (tmp != NULL) { g_autofree gchar *desc = NULL; desc = fu_util_convert_description(tmp, NULL); if (desc == NULL) desc = g_strdup(tmp); /* TRANSLATORS: multiline description of device */ fu_string_append(str, idt + 1, _("Description"), desc); } /* versions */ tmp = fwupd_device_get_version(dev); if (tmp != NULL) { g_autoptr(GString) verstr = g_string_new(tmp); if (fwupd_device_get_version_build_date(dev) != 0) { guint64 value = fwupd_device_get_version_build_date(dev); g_autoptr(GDateTime) date = g_date_time_new_from_unix_utc((gint64)value); g_autofree gchar *datestr = g_date_time_format(date, "%F"); g_string_append_printf(verstr, " [%s]", datestr); } if (flags & FWUPD_DEVICE_FLAG_HISTORICAL) { fu_string_append(str, idt + 1, /* TRANSLATORS: version number of previous firmware */ _("Previous version"), verstr->str); } else { /* TRANSLATORS: version number of current firmware */ fu_string_append(str, idt + 1, _("Current version"), verstr->str); } } tmp = fwupd_device_get_version_lowest(dev); if (tmp != NULL) { /* TRANSLATORS: smallest version number installable on device */ fu_string_append(str, idt + 1, _("Minimum Version"), tmp); } tmp = fwupd_device_get_version_bootloader(dev); if (tmp != NULL) { /* TRANSLATORS: firmware version of bootloader */ fu_string_append(str, idt + 1, _("Bootloader Version"), tmp); } /* vendor */ tmp = fwupd_device_get_vendor(dev); if (tmp != NULL && vendor_ids->len > 0) { g_autofree gchar *strv = fu_strjoin(", ", vendor_ids); g_autofree gchar *both = g_strdup_printf("%s (%s)", tmp, strv); /* TRANSLATORS: manufacturer of hardware */ fu_string_append(str, idt + 1, _("Vendor"), both); } else if (tmp != NULL) { /* TRANSLATORS: manufacturer of hardware */ fu_string_append(str, idt + 1, _("Vendor"), tmp); } else if (vendor_ids->len > 0) { g_autofree gchar *strv = fu_strjoin("|", vendor_ids); /* TRANSLATORS: manufacturer of hardware */ fu_string_append(str, idt + 1, _("Vendor"), strv); } /* branch */ if (fwupd_device_get_branch(dev) != NULL) { fu_string_append( str, idt + 1, /* TRANSLATORS: the stream of firmware, e.g. nonfree or open-source */ _("Release Branch"), fwupd_device_get_branch(dev)); } /* install duration */ if (fwupd_device_get_install_duration(dev) > 0) { g_autofree gchar *time = fu_util_time_to_str(fwupd_device_get_install_duration(dev)); /* TRANSLATORS: length of time the update takes to apply */ fu_string_append(str, idt + 1, _("Install Duration"), time); } /* serial # */ tmp = fwupd_device_get_serial(dev); if (tmp != NULL) { /* TRANSLATORS: serial number of hardware */ fu_string_append(str, idt + 1, _("Serial Number"), tmp); } /* update state */ state = fwupd_device_get_update_state(dev); if (state != FWUPD_UPDATE_STATE_UNKNOWN) { fu_string_append(str, idt + 1, /* TRANSLATORS: hardware state, e.g. "pending" */ _("Update State"), fu_util_update_state_to_string(state)); if (state == FWUPD_UPDATE_STATE_SUCCESS) { tmp = fwupd_device_get_update_message(dev); if (tmp != NULL) { g_autofree gchar *color = fu_console_color_format(tmp, FU_CONSOLE_COLOR_BLUE); fu_string_append( str, idt + 1, /* TRANSLATORS: helpful messages from last update */ _("Update Message"), color); } } } /* battery, but only if we're not about to show the same info as an inhibit */ if (!fwupd_device_has_problem(dev, FWUPD_DEVICE_PROBLEM_POWER_TOO_LOW)) { if (fwupd_device_get_battery_level(dev) != FWUPD_BATTERY_LEVEL_INVALID && fwupd_device_get_battery_threshold(dev) != FWUPD_BATTERY_LEVEL_INVALID) { g_autofree gchar *val = NULL; /* TRANSLATORS: first percentage is current value, 2nd percentage is the * lowest limit the firmware update is allowed for the update to happen */ val = g_strdup_printf(_("%u%% (threshold %u%%)"), fwupd_device_get_battery_level(dev), fwupd_device_get_battery_threshold(dev)); /* TRANSLATORS: refers to the battery inside the peripheral device */ fu_string_append(str, idt + 1, _("Battery"), val); } else if (fwupd_device_get_battery_level(dev) != FWUPD_BATTERY_LEVEL_INVALID) { g_autofree gchar *val = NULL; val = g_strdup_printf("%u%%", fwupd_device_get_battery_level(dev)); /* TRANSLATORS: refers to the battery inside the peripheral device */ fu_string_append(str, idt + 1, _("Battery"), val); } } /* either show enumerated [translated] problems or the synthesized update error */ if (fwupd_device_get_problems(dev) == FWUPD_DEVICE_PROBLEM_NONE) { tmp = fwupd_device_get_update_error(dev); if (tmp != NULL) { g_autofree gchar *color = fu_console_color_format(tmp, FU_CONSOLE_COLOR_RED); /* TRANSLATORS: error message from last update attempt */ fu_string_append(str, idt + 1, _("Update Error"), color); } } else { /* TRANSLATORS: reasons the device is not updatable */ tmp = _("Problems"); for (guint i = 0; i < 64; i++) { FwupdDeviceProblem problem = (guint64)1 << i; g_autofree gchar *bullet = NULL; g_autofree gchar *desc = NULL; g_autofree gchar *color = NULL; if (!fwupd_device_has_problem(dev, problem)) continue; desc = fu_util_device_problem_to_string(client, dev, problem); if (desc == NULL) continue; bullet = g_strdup_printf("• %s", desc); color = fu_console_color_format(bullet, FU_CONSOLE_COLOR_RED); fu_string_append(str, idt + 1, tmp, color); tmp = NULL; } } /* modified date: for history devices */ if (modified > 0) { g_autoptr(GDateTime) date = NULL; g_autofree gchar *time_str = NULL; date = g_date_time_new_from_unix_utc(modified); time_str = g_date_time_format(date, "%F %R"); /* TRANSLATORS: the original time/date the device was modified */ fu_string_append(str, idt + 1, _("Last modified"), time_str); } /* all GUIDs for this hardware, with IDs if available */ ids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); for (guint i = 0; i < instance_ids->len; i++) { const gchar *instance_id = g_ptr_array_index(instance_ids, i); g_hash_table_insert(ids, fwupd_guid_hash_string(instance_id), g_strdup(instance_id)); } for (guint i = 0; i < guids->len; i++) { const gchar *guid = g_ptr_array_index(guids, i); const gchar *instance_id = g_hash_table_lookup(ids, guid); g_autofree gchar *guid_src = NULL; /* instance IDs are only available as root */ if (instance_id == NULL) { guid_src = g_strdup(guid); } else { guid_src = g_strdup_printf("%s ← %s", guid, instance_id); } if (i == 0) { fu_string_append(str, idt + 1, /* TRANSLATORS: global ID common to all similar hardware */ ngettext("GUID", "GUIDs", guids->len), guid_src); } else { fu_string_append(str, idt + 1, "", guid_src); } } /* TRANSLATORS: description of device ability */ tmp = _("Device Flags"); for (guint i = 0; i < 64; i++) { if ((flags & ((guint64)1 << i)) == 0) continue; tmp2 = fu_util_device_flag_to_string((guint64)1 << i); if (tmp2 == NULL) continue; /* header */ if (tmp != NULL) { g_autofree gchar *bullet = NULL; bullet = g_strdup_printf("• %s", tmp2); fu_string_append(str, idt + 1, tmp, bullet); tmp = NULL; } else { g_autofree gchar *bullet = NULL; bullet = g_strdup_printf("• %s", tmp2); fu_string_append(str, idt + 1, "", bullet); } } /* TRANSLATORS: description of the device requests */ tmp = _("Device Requests"); for (guint i = 0; i < 64; i++) { if ((request_flags & ((guint64)1 << i)) == 0) continue; tmp2 = fu_util_request_flag_to_string((guint64)1 << i); if (tmp2 == NULL) continue; /* header */ if (tmp != NULL) { g_autofree gchar *bullet = NULL; bullet = g_strdup_printf("• %s", tmp2); fu_string_append(str, idt + 1, tmp, bullet); tmp = NULL; } else { g_autofree gchar *bullet = NULL; bullet = g_strdup_printf("• %s", tmp2); fu_string_append(str, idt + 1, "", bullet); } } for (guint i = 0; i < issues->len; i++) { const gchar *issue = g_ptr_array_index(issues, i); fu_string_append(str, idt + 1, /* TRANSLATORS: issue fixed with the release, e.g. CVE */ i == 0 ? ngettext("Issue", "Issues", issues->len) : "", issue); } return g_string_free(g_steal_pointer(&str), FALSE); } const gchar * fu_util_plugin_flag_to_string(FwupdPluginFlags plugin_flag) { if (plugin_flag == FWUPD_PLUGIN_FLAG_UNKNOWN) return NULL; if (plugin_flag == FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE) return NULL; if (plugin_flag == FWUPD_PLUGIN_FLAG_USER_WARNING) return NULL; if (plugin_flag == FWUPD_PLUGIN_FLAG_READY) return NULL; if (plugin_flag == FWUPD_PLUGIN_FLAG_REQUIRE_HWID) { /* TRANSLATORS: Plugin is active only if hardware is found */ return _("Enabled if hardware matches"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_NONE) { /* TRANSLATORS: Plugin is active and in use */ return _("Enabled"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_DISABLED) { /* TRANSLATORS: Plugin is inactive and not used */ return _("Disabled"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_NO_HARDWARE) { /* TRANSLATORS: not required for this system */ return _("Required hardware was not found"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_LEGACY_BIOS) { /* TRANSLATORS: system is not booted in UEFI mode */ return _("UEFI firmware can not be updated in legacy BIOS mode"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_CAPSULES_UNSUPPORTED) { /* TRANSLATORS: capsule updates are an optional BIOS feature */ return _("UEFI capsule updates not available or enabled in firmware setup"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_UNLOCK_REQUIRED) { /* TRANSLATORS: user needs to run a command */ return _("Firmware updates disabled; run 'fwupdmgr unlock' to enable"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_AUTH_REQUIRED) { /* TRANSLATORS: user needs to run a command */ return _("Authentication details are required"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_SECURE_CONFIG) { /* TRANSLATORS: no peeking */ return _("Configuration is only readable by the system administrator"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_MODULAR) { /* TRANSLATORS: the plugin was created from a .so object, and was not built-in */ return _("Loaded from an external module"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY) { /* TRANSLATORS: check various UEFI and ACPI tables are unchanged after the update */ return _("Will measure elements of system integrity around an update"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_EFIVAR_NOT_MOUNTED) { /* TRANSLATORS: the user is using Gentoo/Arch and has screwed something up */ return _("Required efivarfs filesystem was not found"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_ESP_NOT_FOUND) { /* TRANSLATORS: partition refers to something on disk, again, hey Arch users */ return _("UEFI ESP partition not detected or configured"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_ESP_NOT_VALID) { /* TRANSLATORS: partition refers to something on disk, again, hey Arch users */ return _("UEFI ESP partition may not be set up correctly"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_FAILED_OPEN) { /* TRANSLATORS: Failed to open plugin, hey Arch users */ return _("Plugin dependencies missing"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_KERNEL_TOO_OLD) { /* TRANSLATORS: The kernel does not support this plugin */ return _("Running kernel is too old"); } if (plugin_flag == FWUPD_PLUGIN_FLAG_TEST_ONLY) { /* TRANSLATORS: The plugin is only for testing */ return _("Plugin is only for testing"); } /* fall back for unknown types */ return fwupd_plugin_flag_to_string(plugin_flag); } static gchar * fu_util_plugin_flag_to_cli_text(FwupdPluginFlags plugin_flag) { switch (plugin_flag) { case FWUPD_PLUGIN_FLAG_UNKNOWN: case FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE: case FWUPD_PLUGIN_FLAG_USER_WARNING: case FWUPD_PLUGIN_FLAG_READY: return NULL; case FWUPD_PLUGIN_FLAG_NONE: case FWUPD_PLUGIN_FLAG_REQUIRE_HWID: case FWUPD_PLUGIN_FLAG_MODULAR: case FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY: case FWUPD_PLUGIN_FLAG_SECURE_CONFIG: return fu_console_color_format(fu_util_plugin_flag_to_string(plugin_flag), FU_CONSOLE_COLOR_GREEN); case FWUPD_PLUGIN_FLAG_DISABLED: case FWUPD_PLUGIN_FLAG_NO_HARDWARE: case FWUPD_PLUGIN_FLAG_TEST_ONLY: return fu_console_color_format(fu_util_plugin_flag_to_string(plugin_flag), FU_CONSOLE_COLOR_BLACK); case FWUPD_PLUGIN_FLAG_LEGACY_BIOS: case FWUPD_PLUGIN_FLAG_CAPSULES_UNSUPPORTED: case FWUPD_PLUGIN_FLAG_UNLOCK_REQUIRED: case FWUPD_PLUGIN_FLAG_AUTH_REQUIRED: case FWUPD_PLUGIN_FLAG_EFIVAR_NOT_MOUNTED: case FWUPD_PLUGIN_FLAG_ESP_NOT_FOUND: case FWUPD_PLUGIN_FLAG_ESP_NOT_VALID: case FWUPD_PLUGIN_FLAG_KERNEL_TOO_OLD: return fu_console_color_format(fu_util_plugin_flag_to_string(plugin_flag), FU_CONSOLE_COLOR_RED); default: break; } /* fall back for unknown types */ return g_strdup(fwupd_plugin_flag_to_string(plugin_flag)); } gchar * fu_util_plugin_to_string(FwupdPlugin *plugin, guint idt) { GString *str = g_string_new(NULL); const gchar *hdr; guint64 flags = fwupd_plugin_get_flags(plugin); fu_string_append(str, idt, fwupd_plugin_get_name(plugin), NULL); /* TRANSLATORS: description of plugin state, e.g. disabled */ hdr = _("Flags"); if (flags == 0x0) { g_autofree gchar *tmp = fu_util_plugin_flag_to_cli_text(flags); g_autofree gchar *li = g_strdup_printf("• %s", tmp); fu_string_append(str, idt + 1, hdr, li); } else { for (guint i = 0; i < 64; i++) { g_autofree gchar *li = NULL; g_autofree gchar *tmp = NULL; if ((flags & ((guint64)1 << i)) == 0) continue; tmp = fu_util_plugin_flag_to_cli_text((guint64)1 << i); if (tmp == NULL) continue; li = g_strdup_printf("• %s", tmp); fu_string_append(str, idt + 1, hdr, li); /* clear header */ hdr = ""; } } return g_string_free(str, FALSE); } static const gchar * fu_util_license_to_string(const gchar *license) { if (license == NULL) { /* TRANSLATORS: we don't know the license of the update */ return _("Unknown"); } if (g_strcmp0(license, "LicenseRef-proprietary") == 0 || g_strcmp0(license, "proprietary") == 0) { /* TRANSLATORS: a non-free software license */ return _("Proprietary"); } return license; } static const gchar * fu_util_release_urgency_to_string(FwupdReleaseUrgency release_urgency) { if (release_urgency == FWUPD_RELEASE_URGENCY_LOW) { /* TRANSLATORS: the release urgency */ return _("Low"); } if (release_urgency == FWUPD_RELEASE_URGENCY_MEDIUM) { /* TRANSLATORS: the release urgency */ return _("Medium"); } if (release_urgency == FWUPD_RELEASE_URGENCY_HIGH) { /* TRANSLATORS: the release urgency */ return _("High"); } if (release_urgency == FWUPD_RELEASE_URGENCY_CRITICAL) { /* TRANSLATORS: the release urgency */ return _("Critical"); } /* TRANSLATORS: unknown release urgency */ return _("Unknown"); } static const gchar * fu_util_release_flag_to_string(FwupdReleaseFlags release_flag) { if (release_flag == FWUPD_RELEASE_FLAG_NONE) return NULL; if (release_flag == FWUPD_RELEASE_FLAG_TRUSTED_PAYLOAD) { /* TRANSLATORS: We verified the payload against the server */ return _("Trusted payload"); } if (release_flag == FWUPD_RELEASE_FLAG_TRUSTED_METADATA) { /* TRANSLATORS: We verified the metadata against the server */ return _("Trusted metadata"); } if (release_flag == FWUPD_RELEASE_FLAG_IS_UPGRADE) { /* TRANSLATORS: version is newer */ return _("Is upgrade"); } if (release_flag == FWUPD_RELEASE_FLAG_IS_DOWNGRADE) { /* TRANSLATORS: version is older */ return _("Is downgrade"); } if (release_flag == FWUPD_RELEASE_FLAG_BLOCKED_VERSION) { /* TRANSLATORS: version cannot be installed due to policy */ return _("Blocked version"); } if (release_flag == FWUPD_RELEASE_FLAG_BLOCKED_APPROVAL) { /* TRANSLATORS: version cannot be installed due to policy */ return _("Not approved"); } if (release_flag == FWUPD_RELEASE_FLAG_IS_ALTERNATE_BRANCH) { /* TRANSLATORS: is not the main firmware stream */ return _("Alternate branch"); } if (release_flag == FWUPD_RELEASE_FLAG_IS_COMMUNITY) { /* TRANSLATORS: is not supported by the vendor */ return _("Community supported"); } if (release_flag == FWUPD_RELEASE_FLAG_TRUSTED_REPORT) { /* TRANSLATORS: someone we trust has tested this */ return _("Tested by trusted vendor"); } /* fall back for unknown types */ return fwupd_release_flag_to_string(release_flag); } static void fu_util_report_add_string(FwupdReport *report, guint idt, GString *str) { const gchar *tmp2; g_autofree gchar *title = NULL; /* TRANSLATORS: the %s is a vendor name, e.g. Lenovo */ title = g_strdup_printf(_("Tested by %s"), fwupd_report_get_vendor(report)); fu_string_append(str, idt, title, NULL); if (fwupd_report_get_created(report) != 0) { gint64 value = (gint64)fwupd_report_get_created(report); g_autoptr(GDateTime) date = g_date_time_new_from_unix_utc(value); g_autofree gchar *tmp = g_date_time_format(date, "%F"); /* TRANSLATORS: when the release was tested */ fu_string_append(str, idt + 1, _("Tested"), tmp); } if (fwupd_report_get_distro_id(report) != NULL) { g_autoptr(GString) str2 = g_string_new(fwupd_report_get_distro_id(report)); if (fwupd_report_get_distro_version(report) != NULL) g_string_append_printf(str2, " %s", fwupd_report_get_distro_version(report)); if (fwupd_report_get_distro_variant(report) != NULL) g_string_append_printf(str2, " (%s)", fwupd_report_get_distro_variant(report)); /* TRANSLATORS: the OS the release was tested on */ fu_string_append(str, idt + 1, _("Distribution"), str2->str); } if (fwupd_report_get_version_old(report) != NULL) { fu_string_append(str, idt + 1, /* TRANSLATORS: the firmware old version */ _("Old version"), fwupd_report_get_version_old(report)); } tmp2 = fwupd_report_get_metadata_item(report, "RuntimeVersion(org.freedesktop.fwupd)"); if (tmp2 != NULL) { /* TRANSLATORS: the fwupd version the release was tested on */ fu_string_append(str, idt + 1, _("Version[fwupd]"), tmp2); } } static gchar * fu_util_release_to_string(FwupdRelease *rel, guint idt) { const gchar *title; const gchar *tmp2; GPtrArray *checksums = fwupd_release_get_checksums(rel); GPtrArray *issues = fwupd_release_get_issues(rel); GPtrArray *tags = fwupd_release_get_tags(rel); GPtrArray *reports = fwupd_release_get_reports(rel); guint64 flags = fwupd_release_get_flags(rel); g_autofree gchar *desc_fb = NULL; g_autofree gchar *name = fu_util_release_get_name(rel); g_autoptr(GString) str = g_string_new(NULL); g_return_val_if_fail(FWUPD_IS_RELEASE(rel), NULL); fu_string_append(str, idt, name, NULL); /* TRANSLATORS: version number of new firmware */ fu_string_append(str, idt + 1, _("New version"), fwupd_release_get_version(rel)); if (fwupd_release_get_remote_id(rel) != NULL) { fu_string_append(str, idt + 1, /* TRANSLATORS: the server the file is coming from */ _("Remote ID"), fwupd_release_get_remote_id(rel)); } if (fwupd_release_get_id(rel) != NULL) { fu_string_append(str, idt + 1, /* TRANSLATORS: the exact component on the server */ _("Release ID"), fwupd_release_get_id(rel)); } if (fwupd_release_get_branch(rel) != NULL) { fu_string_append( str, idt + 1, /* TRANSLATORS: the stream of firmware, e.g. nonfree or open-source */ _("Branch"), fwupd_release_get_branch(rel)); } if (fwupd_release_get_summary(rel) != NULL) { fu_string_append(str, idt + 1, /* TRANSLATORS: one line summary of device */ _("Summary"), fwupd_release_get_summary(rel)); } if (fwupd_release_get_name_variant_suffix(rel) != NULL) { fu_string_append( str, idt + 1, /* TRANSLATORS: one line variant of release (e.g. 'Prerelease' or 'China') */ _("Variant"), fwupd_release_get_name_variant_suffix(rel)); } fu_string_append(str, idt + 1, /* TRANSLATORS: e.g. GPLv2+, Proprietary etc */ _("License"), fu_util_license_to_string(fwupd_release_get_license(rel))); if (fwupd_release_get_size(rel) != 0) { g_autofree gchar *tmp = NULL; tmp = g_format_size(fwupd_release_get_size(rel)); /* TRANSLATORS: file size of the download */ fu_string_append(str, idt + 1, _("Size"), tmp); } if (fwupd_release_get_created(rel) != 0) { gint64 value = (gint64)fwupd_release_get_created(rel); g_autoptr(GDateTime) date = g_date_time_new_from_unix_utc(value); g_autofree gchar *tmp = g_date_time_format(date, "%F"); /* TRANSLATORS: when the update was built */ fu_string_append(str, idt + 1, _("Created"), tmp); } if (fwupd_release_get_urgency(rel) != FWUPD_RELEASE_URGENCY_UNKNOWN) { FwupdReleaseUrgency tmp = fwupd_release_get_urgency(rel); fu_string_append(str, idt + 1, /* TRANSLATORS: how important the release is */ _("Urgency"), fu_util_release_urgency_to_string(tmp)); } for (guint i = 0; i < reports->len; i++) { FwupdReport *report = g_ptr_array_index(reports, i); fu_util_report_add_string(report, idt + 1, str); } if (fwupd_release_get_details_url(rel) != NULL) { fu_string_append(str, idt + 1, /* TRANSLATORS: more details about the update link */ _("Details"), fwupd_release_get_details_url(rel)); } if (fwupd_release_get_source_url(rel) != NULL) { fu_string_append(str, idt + 1, /* TRANSLATORS: source (as in code) link */ _("Source"), fwupd_release_get_source_url(rel)); } if (fwupd_release_get_vendor(rel) != NULL) { fu_string_append(str, idt + 1, /* TRANSLATORS: manufacturer of hardware */ _("Vendor"), fwupd_release_get_vendor(rel)); } if (fwupd_release_get_install_duration(rel) != 0) { g_autofree gchar *tmp = fu_util_time_to_str(fwupd_release_get_install_duration(rel)); /* TRANSLATORS: length of time the update takes to apply */ fu_string_append(str, idt + 1, _("Duration"), tmp); } if (fwupd_release_get_update_message(rel) != NULL) { fu_string_append(str, idt + 1, /* TRANSLATORS: helpful messages for the update */ _("Update Message"), fwupd_release_get_update_message(rel)); } if (fwupd_release_get_update_image(rel) != NULL) { fu_string_append(str, idt + 1, /* TRANSLATORS: helpful image for the update */ _("Update Image"), fwupd_release_get_update_image(rel)); } /* TRANSLATORS: release attributes */ title = _("Release Flags"); for (guint i = 0; i < 64; i++) { g_autofree gchar *bullet = NULL; if ((flags & ((guint64)1 << i)) == 0) continue; tmp2 = fu_util_release_flag_to_string((guint64)1 << i); if (tmp2 == NULL) continue; bullet = g_strdup_printf("• %s", tmp2); fu_string_append(str, idt + 1, title, bullet); title = ""; } desc_fb = fu_util_get_release_description_with_fallback(rel); if (desc_fb != NULL) { g_autofree gchar *desc = NULL; desc = fu_util_convert_description(desc_fb, NULL); if (desc == NULL) desc = g_strdup(fwupd_release_get_description(rel)); /* TRANSLATORS: multiline description of device */ fu_string_append(str, idt + 1, _("Description"), desc); } for (guint i = 0; i < issues->len; i++) { const gchar *issue = g_ptr_array_index(issues, i); if (i == 0) { fu_string_append(str, idt + 1, /* TRANSLATORS: issue fixed with the release, e.g. CVE */ ngettext("Issue", "Issues", issues->len), issue); } else { fu_string_append(str, idt + 1, "", issue); } } if (tags->len > 0) { g_autofree gchar *tag_strs = fu_strjoin(", ", tags); fu_string_append(str, idt + 1, /* TRANSLATORS: release tag set for release, e.g. lenovo-2021q3 */ ngettext("Tag", "Tags", tags->len), tag_strs); } for (guint i = 0; i < checksums->len; i++) { const gchar *checksum = g_ptr_array_index(checksums, i); GChecksumType checksum_type = fwupd_checksum_guess_kind(checksum); /* avoid showing brokwn checksums */ if (checksum_type == G_CHECKSUM_SHA1) continue; /* TRANSLATORS: hash to that exact firmware archive */ fu_string_append(str, idt + 1, _("Checksum"), checksum); } return g_string_free(g_steal_pointer(&str), FALSE); } static gchar * fu_util_remote_to_string(FwupdRemote *remote, guint idt) { FwupdRemoteKind kind = fwupd_remote_get_kind(remote); FwupdKeyringKind keyring_kind = fwupd_remote_get_keyring_kind(remote); const gchar *tmp; gint priority; g_autoptr(GString) str = g_string_new(NULL); g_return_val_if_fail(FWUPD_IS_REMOTE(remote), NULL); fu_string_append(str, idt, fwupd_remote_get_title(remote), NULL); /* TRANSLATORS: remote identifier, e.g. lvfs-testing */ fu_string_append(str, idt + 1, _("Remote ID"), fwupd_remote_get_id(remote)); /* TRANSLATORS: remote type, e.g. remote or local */ fu_string_append(str, idt + 1, _("Type"), fwupd_remote_kind_to_string(kind)); if (keyring_kind != FWUPD_KEYRING_KIND_UNKNOWN) { fu_string_append(str, idt + 1, /* TRANSLATORS: keyring type, e.g. GPG or PKCS7 */ _("Keyring"), fwupd_keyring_kind_to_string(keyring_kind)); } fu_string_append(str, idt + 1, /* TRANSLATORS: if the remote is enabled */ _("Enabled"), fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED) ? "true" : "false"); if (fwupd_remote_get_kind(remote) == FWUPD_REMOTE_KIND_DOWNLOAD) { fu_string_append(str, idt + 1, /* TRANSLATORS: if we can get metadata from peer-to-peer clients */ _("P2P Metadata"), fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ALLOW_P2P_METADATA) ? "true" : "false"); fu_string_append(str, idt + 1, /* TRANSLATORS: if we can get metadata from peer-to-peer clients */ _("P2P Firmware"), fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ALLOW_P2P_FIRMWARE) ? "true" : "false"); } tmp = fwupd_remote_get_checksum(remote); if (tmp != NULL) { /* TRANSLATORS: remote checksum */ fu_string_append(str, idt + 1, _("Checksum"), tmp); } /* optional parameters */ if (kind == FWUPD_REMOTE_KIND_DOWNLOAD && fwupd_remote_get_age(remote) > 0 && fwupd_remote_get_age(remote) != G_MAXUINT64) { g_autofree gchar *age_str = fu_util_time_to_str(fwupd_remote_get_age(remote)); /* TRANSLATORS: the age of the metadata */ fu_string_append(str, idt + 1, _("Age"), age_str); } if (kind == FWUPD_REMOTE_KIND_DOWNLOAD && fwupd_remote_get_refresh_interval(remote) > 0) { g_autofree gchar *age_str = fu_util_time_to_str(fwupd_remote_get_refresh_interval(remote)); /* TRANSLATORS: how often we should refresh the metadata */ fu_string_append(str, idt + 1, _("Refresh Interval"), age_str); } priority = fwupd_remote_get_priority(remote); if (priority != 0) { g_autofree gchar *priority_str = NULL; priority_str = g_strdup_printf("%i", priority); /* TRANSLATORS: the numeric priority */ fu_string_append(str, idt + 1, _("Priority"), priority_str); } tmp = fwupd_remote_get_username(remote); if (tmp != NULL) { /* TRANSLATORS: remote filename base */ fu_string_append(str, idt + 1, _("Username"), tmp); } tmp = fwupd_remote_get_password(remote); if (tmp != NULL) { g_autofree gchar *hidden = g_strnfill(fu_strwidth(tmp), '*'); /* TRANSLATORS: remote filename base */ fu_string_append(str, idt + 1, _("Password"), hidden); } tmp = fwupd_remote_get_filename_cache(remote); if (tmp != NULL) { /* TRANSLATORS: filename of the local file */ fu_string_append(str, idt + 1, _("Filename"), tmp); } tmp = fwupd_remote_get_filename_cache_sig(remote); if (tmp != NULL) { /* TRANSLATORS: filename of the local file */ fu_string_append(str, idt + 1, _("Filename Signature"), tmp); } tmp = fwupd_remote_get_filename_source(remote); if (tmp != NULL) { /* TRANSLATORS: full path of the remote.conf file */ fu_string_append(str, idt + 1, _("Filename Source"), tmp); } tmp = fwupd_remote_get_metadata_uri(remote); if (tmp != NULL) { /* TRANSLATORS: remote URI */ fu_string_append(str, idt + 1, _("Metadata URI"), tmp); } tmp = fwupd_remote_get_metadata_uri_sig(remote); if (tmp != NULL) { /* TRANSLATORS: remote URI */ fu_string_append(str, idt + 1, _("Metadata Signature"), tmp); } tmp = fwupd_remote_get_firmware_base_uri(remote); if (tmp != NULL) { /* TRANSLATORS: remote URI */ fu_string_append(str, idt + 1, _("Firmware Base URI"), tmp); } tmp = fwupd_remote_get_report_uri(remote); if (tmp != NULL) { /* TRANSLATORS: URI to send success/failure reports */ fu_string_append(str, idt + 1, _("Report URI"), tmp); fu_string_append(str, idt + 1, /* TRANSLATORS: Boolean value to automatically send reports */ _("Automatic Reporting"), fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS) ? "true" : "false"); } return g_string_free(g_steal_pointer(&str), FALSE); } const gchar * fu_util_request_get_message(FwupdRequest *req) { if (fwupd_request_has_flag(req, FWUPD_REQUEST_FLAG_ALLOW_GENERIC_MESSAGE)) { if (g_strcmp0(fwupd_request_get_id(req), FWUPD_REQUEST_ID_REMOVE_REPLUG) == 0) { /* TRANSLATORS: warning message shown after update has been scheduled */ return _("The update will continue when the device USB cable has been " "unplugged and then re-inserted."); } if (g_strcmp0(fwupd_request_get_id(req), FWUPD_REQUEST_ID_REMOVE_USB_CABLE) == 0) { /* TRANSLATORS: warning message shown after update has been scheduled */ return _("The update will continue when the device USB cable has been " "unplugged."); } if (g_strcmp0(fwupd_request_get_id(req), FWUPD_REQUEST_ID_INSERT_USB_CABLE) == 0) { /* TRANSLATORS: warning message shown after update has been scheduled */ return _("The update will continue when the device USB cable has been " "re-inserted."); } if (g_strcmp0(fwupd_request_get_id(req), FWUPD_REQUEST_ID_PRESS_UNLOCK) == 0) { /* TRANSLATORS: warning message */ return _("Press unlock on the device to continue the update process."); } if (g_strcmp0(fwupd_request_get_id(req), FWUPD_REQUEST_ID_DO_NOT_POWER_OFF) == 0) { /* TRANSLATORS: warning message shown after update has been scheduled */ return _("Do not turn off your computer or remove the AC adaptor " "while the update is in progress."); } if (g_strcmp0(fwupd_request_get_id(req), FWUPD_REQUEST_ID_REPLUG_INSTALL) == 0) { /* TRANSLATORS: message shown after device has been marked for emulation */ return _("Unplug and replug the device to continue the update process."); } if (g_strcmp0(fwupd_request_get_id(req), FWUPD_REQUEST_ID_REPLUG_POWER) == 0) { /* TRANSLATORS: warning message */ return _("The update will continue when the device power cable has been " "removed and re-inserted."); } } return fwupd_request_get_message(req); } static const gchar * fu_security_attr_result_to_string(FwupdSecurityAttrResult result) { if (result == FWUPD_SECURITY_ATTR_RESULT_VALID) { /* TRANSLATORS: Suffix: the HSI result */ return _("Valid"); } if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_VALID) { /* TRANSLATORS: Suffix: the HSI result */ return _("Invalid"); } if (result == FWUPD_SECURITY_ATTR_RESULT_ENABLED) { /* TRANSLATORS: Suffix: the HSI result */ return _("Enabled"); } if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED) { /* TRANSLATORS: Suffix: the HSI result */ return _("Disabled"); } if (result == FWUPD_SECURITY_ATTR_RESULT_LOCKED) { /* TRANSLATORS: Suffix: the HSI result */ return _("Locked"); } if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED) { /* TRANSLATORS: Suffix: the HSI result */ return _("Unlocked"); } if (result == FWUPD_SECURITY_ATTR_RESULT_ENCRYPTED) { /* TRANSLATORS: Suffix: the HSI result */ return _("Encrypted"); } if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED) { /* TRANSLATORS: Suffix: the HSI result */ return _("Unencrypted"); } if (result == FWUPD_SECURITY_ATTR_RESULT_TAINTED) { /* TRANSLATORS: Suffix: the HSI result */ return _("Tainted"); } if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED) { /* TRANSLATORS: Suffix: the HSI result */ return _("Untainted"); } if (result == FWUPD_SECURITY_ATTR_RESULT_FOUND) { /* TRANSLATORS: Suffix: the HSI result */ return _("Found"); } if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND) { /* TRANSLATORS: Suffix: the HSI result */ return _("Not found"); } if (result == FWUPD_SECURITY_ATTR_RESULT_SUPPORTED) { /* TRANSLATORS: Suffix: the HSI result */ return _("Supported"); } if (result == FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED) { /* TRANSLATORS: Suffix: the HSI result */ return _("Not supported"); } return NULL; } static const gchar * fu_security_attr_get_result(FwupdSecurityAttr *attr) { const gchar *tmp; /* common case */ tmp = fu_security_attr_result_to_string(fwupd_security_attr_get_result(attr)); if (tmp != NULL) return tmp; /* fallback */ if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { /* TRANSLATORS: Suffix: the HSI result */ return _("OK"); } /* TRANSLATORS: Suffix: the fallback HSI result */ return _("Unknown"); } static void fu_security_attr_append_str(FwupdSecurityAttr *attr, GString *str, FuSecurityAttrToStringFlags flags) { const gchar *name; /* hide obsoletes by default */ if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED) && (flags & FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_OBSOLETES) == 0) return; name = dgettext(GETTEXT_PACKAGE, fwupd_security_attr_get_name(attr)); if (name == NULL) name = fwupd_security_attr_get_appstream_id(attr); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) { g_string_append(str, "✦ "); } else if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { g_string_append(str, "✔ "); } else { g_string_append(str, "✘ "); } g_string_append_printf(str, "%s:", name); for (guint i = fu_strwidth(name); i < 30; i++) g_string_append(str, " "); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) { g_autofree gchar *fmt = fu_console_color_format(fu_security_attr_get_result(attr), FU_CONSOLE_COLOR_YELLOW); g_string_append(str, fmt); } else if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { g_autofree gchar *fmt = fu_console_color_format(fu_security_attr_get_result(attr), FU_CONSOLE_COLOR_GREEN); g_string_append(str, fmt); } else { g_autofree gchar *fmt = fu_console_color_format(fu_security_attr_get_result(attr), FU_CONSOLE_COLOR_RED); g_string_append(str, fmt); } if ((flags & FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_URLS) > 0 && fwupd_security_attr_get_url(attr) != NULL) { g_string_append_printf(str, ": %s", fwupd_security_attr_get_url(attr)); } if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED)) { /* TRANSLATORS: this is shown as a suffix for obsoleted tests */ g_string_append_printf(str, " %s", _("(obsoleted)")); } g_string_append_printf(str, "\n"); } static gchar * fu_util_security_event_to_string(FwupdSecurityAttr *attr) { const gchar *name; struct { const gchar *appstream_id; FwupdSecurityAttrResult result_old; FwupdSecurityAttrResult result_new; const gchar *text; } items[] = {{FWUPD_SECURITY_ATTR_ID_IOMMU, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND, FWUPD_SECURITY_ATTR_RESULT_ENABLED, /* TRANSLATORS: HSI event title */ _("IOMMU device protection enabled")}, {FWUPD_SECURITY_ATTR_ID_IOMMU, FWUPD_SECURITY_ATTR_RESULT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND, /* TRANSLATORS: HSI event title */ _("IOMMU device protection disabled")}, /* ------------------------------------------*/ {FWUPD_SECURITY_ATTR_ID_FWUPD_PLUGINS, FWUPD_SECURITY_ATTR_RESULT_TAINTED, FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED, NULL}, {FWUPD_SECURITY_ATTR_ID_FWUPD_PLUGINS, FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED, FWUPD_SECURITY_ATTR_RESULT_TAINTED, NULL}, {FWUPD_SECURITY_ATTR_ID_FWUPD_PLUGINS, FWUPD_SECURITY_ATTR_RESULT_UNKNOWN, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, NULL}, /* ------------------------------------------*/ {FWUPD_SECURITY_ATTR_ID_KERNEL_TAINTED, FWUPD_SECURITY_ATTR_RESULT_TAINTED, FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED, /* TRANSLATORS: HSI event title */ _("Kernel is no longer tainted")}, {FWUPD_SECURITY_ATTR_ID_KERNEL_TAINTED, FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED, FWUPD_SECURITY_ATTR_RESULT_TAINTED, /* TRANSLATORS: HSI event title */ _("Kernel is tainted")}, /* ------------------------------------------*/ {FWUPD_SECURITY_ATTR_ID_KERNEL_LOCKDOWN, FWUPD_SECURITY_ATTR_RESULT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, /* TRANSLATORS: HSI event title */ _("Kernel lockdown disabled")}, {FWUPD_SECURITY_ATTR_ID_KERNEL_LOCKDOWN, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_ENABLED, /* TRANSLATORS: HSI event title */ _("Kernel lockdown enabled")}, /* ------------------------------------------*/ {FWUPD_SECURITY_ATTR_ID_PREBOOT_DMA_PROTECTION, FWUPD_SECURITY_ATTR_RESULT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, /* TRANSLATORS: HSI event title */ _("Pre-boot DMA protection is disabled")}, {FWUPD_SECURITY_ATTR_ID_PREBOOT_DMA_PROTECTION, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_ENABLED, /* TRANSLATORS: HSI event title */ _("Pre-boot DMA protection is enabled")}, /* ------------------------------------------*/ {FWUPD_SECURITY_ATTR_ID_UEFI_SECUREBOOT, FWUPD_SECURITY_ATTR_RESULT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, /* TRANSLATORS: HSI event title */ _("Secure Boot disabled")}, {FWUPD_SECURITY_ATTR_ID_UEFI_SECUREBOOT, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED, FWUPD_SECURITY_ATTR_RESULT_ENABLED, /* TRANSLATORS: HSI event title */ _("Secure Boot enabled")}, /* ------------------------------------------*/ {FWUPD_SECURITY_ATTR_ID_TPM_EMPTY_PCR, FWUPD_SECURITY_ATTR_RESULT_UNKNOWN, FWUPD_SECURITY_ATTR_RESULT_VALID, /* TRANSLATORS: HSI event title */ _("All TPM PCRs are valid")}, {FWUPD_SECURITY_ATTR_ID_TPM_EMPTY_PCR, FWUPD_SECURITY_ATTR_RESULT_VALID, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID, /* TRANSLATORS: HSI event title */ _("A TPM PCR is now an invalid value")}, {FWUPD_SECURITY_ATTR_ID_TPM_EMPTY_PCR, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID, FWUPD_SECURITY_ATTR_RESULT_VALID, /* TRANSLATORS: HSI event title */ _("All TPM PCRs are now valid")}, /* ------------------------------------------*/ {FWUPD_SECURITY_ATTR_ID_TPM_RECONSTRUCTION_PCR0, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID, /* TRANSLATORS: HSI event title */ _("TPM PCR0 reconstruction is invalid")}, {FWUPD_SECURITY_ATTR_ID_TPM_RECONSTRUCTION_PCR0, FWUPD_SECURITY_ATTR_RESULT_NOT_VALID, FWUPD_SECURITY_ATTR_RESULT_VALID, /* TRANSLATORS: HSI event title */ _("TPM PCR0 reconstruction is now valid")}, {NULL, 0, 0, NULL}}; /* sanity check */ if (fwupd_security_attr_get_appstream_id(attr) == NULL) return NULL; if (fwupd_security_attr_get_result(attr) == FWUPD_SECURITY_ATTR_RESULT_UNKNOWN && fwupd_security_attr_get_result_fallback(attr) == FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) return NULL; /* look for prepared text */ for (guint i = 0; items[i].appstream_id != NULL; i++) { if (g_strcmp0(fwupd_security_attr_get_appstream_id(attr), items[i].appstream_id) == 0 && fwupd_security_attr_get_result(attr) == items[i].result_new && fwupd_security_attr_get_result_fallback(attr) == items[i].result_old) return g_strdup(items[i].text); } /* disappeared */ if (fwupd_security_attr_get_result(attr) == FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) { name = dgettext(GETTEXT_PACKAGE, fwupd_security_attr_get_name(attr)); return g_strdup_printf( /* TRANSLATORS: %1 refers to some kind of security test, e.g. "SPI BIOS region". %2 refers to a result value, e.g. "Invalid" */ _("%s disappeared: %s"), name, fu_security_attr_result_to_string( fwupd_security_attr_get_result_fallback(attr))); } /* appeared */ if (fwupd_security_attr_get_result_fallback(attr) == FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) { name = dgettext(GETTEXT_PACKAGE, fwupd_security_attr_get_name(attr)); return g_strdup_printf( /* TRANSLATORS: %1 refers to some kind of security test, e.g. "Encrypted RAM". %2 refers to a result value, e.g. "Invalid" */ _("%s appeared: %s"), name, fu_security_attr_result_to_string(fwupd_security_attr_get_result(attr))); } /* fall back to something sensible */ name = dgettext(GETTEXT_PACKAGE, fwupd_security_attr_get_name(attr)); return g_strdup_printf( /* TRANSLATORS: %1 refers to some kind of security test, e.g. "UEFI platform key". * %2 and %3 refer to results value, e.g. "Valid" and "Invalid" */ _("%s changed: %s → %s"), name, fu_security_attr_result_to_string(fwupd_security_attr_get_result_fallback(attr)), fu_security_attr_result_to_string(fwupd_security_attr_get_result(attr))); } gchar * fu_util_security_events_to_string(GPtrArray *events, FuSecurityAttrToStringFlags strflags) { g_autoptr(GString) str = g_string_new(NULL); /* debugging */ if (g_getenv("FWUPD_VERBOSE") != NULL) { for (guint i = 0; i < events->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(events, i); g_autofree gchar *tmp = fwupd_security_attr_to_string(attr); g_info("%s", tmp); } } for (guint i = 0; i < events->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(events, i); g_autoptr(GDateTime) date = NULL; g_autofree gchar *dtstr = NULL; g_autofree gchar *check = NULL; g_autofree gchar *eventstr = NULL; /* skip events that have either been added or removed with no prior value */ if (fwupd_security_attr_get_result(attr) == FWUPD_SECURITY_ATTR_RESULT_UNKNOWN || fwupd_security_attr_get_result_fallback(attr) == FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) continue; date = g_date_time_new_from_unix_utc((gint64)fwupd_security_attr_get_created(attr)); dtstr = g_date_time_format(date, "%F %T"); eventstr = fu_util_security_event_to_string(attr); if (eventstr == NULL) continue; if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { check = fu_console_color_format("✔", FU_CONSOLE_COLOR_GREEN); } else { check = fu_console_color_format("✘", FU_CONSOLE_COLOR_RED); } if (str->len == 0) { /* TRANSLATORS: title for host security events */ g_string_append_printf(str, "%s\n", _("Host Security Events")); } g_string_append_printf(str, " %s: %s %s\n", dtstr, check, eventstr); } /* no output required */ if (str->len == 0) return NULL; /* success */ return g_string_free(g_steal_pointer(&str), FALSE); } gchar * fu_util_security_issues_to_string(GPtrArray *devices) { g_autoptr(GString) str = g_string_new(NULL); for (guint i = 0; i < devices->len; i++) { FwupdDevice *device = g_ptr_array_index(devices, i); GPtrArray *issues = fwupd_device_get_issues(device); if (issues->len == 0) continue; if (str->len == 0) { g_string_append_printf( str, "%s\n", /* TRANSLATORS: now list devices with unfixed high-priority issues */ _("There are devices with issues:")); } g_string_append_printf(str, "\n %s — %s:\n", fwupd_device_get_vendor(device), fwupd_device_get_name(device)); for (guint j = 0; j < issues->len; j++) { const gchar *issue = g_ptr_array_index(issues, j); g_string_append_printf(str, " • %s\n", issue); } } /* no output required */ if (str->len == 0) return NULL; /* success */ return g_string_free(g_steal_pointer(&str), FALSE); } gchar * fu_util_security_attrs_to_string(GPtrArray *attrs, FuSecurityAttrToStringFlags strflags) { FwupdSecurityAttrFlags flags = FWUPD_SECURITY_ATTR_FLAG_NONE; const FwupdSecurityAttrFlags hpi_suffixes[] = { FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE, FWUPD_SECURITY_ATTR_FLAG_NONE, }; GString *str = g_string_new(NULL); gboolean low_help = FALSE; gboolean runtime_help = FALSE; gboolean pcr0_help = FALSE; for (guint j = 1; j <= FWUPD_SECURITY_ATTR_LEVEL_LAST; j++) { gboolean has_header = FALSE; for (guint i = 0; i < attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(attrs, i); if (fwupd_security_attr_get_level(attr) != j) continue; if (!has_header) { g_string_append_printf(str, "\n\033[1mHSI-%u\033[0m\n", j); has_header = TRUE; } fu_security_attr_append_str(attr, str, strflags); /* make sure they have at least HSI-1 */ if (j < FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT && !fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_OBSOLETED) && !fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) low_help = TRUE; /* check for PCR0 not matching */ if (g_strcmp0(fwupd_security_attr_get_appstream_id(attr), FWUPD_SECURITY_ATTR_ID_TPM_RECONSTRUCTION_PCR0) == 0 && fwupd_security_attr_get_result(attr) == FWUPD_SECURITY_ATTR_RESULT_NOT_VALID) pcr0_help = TRUE; } } for (guint i = 0; i < attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(attrs, i); flags |= fwupd_security_attr_get_flags(attr); } for (guint j = 0; hpi_suffixes[j] != FWUPD_SECURITY_ATTR_FLAG_NONE; j++) { if (flags & hpi_suffixes[j]) { g_string_append_printf(str, "\n\033[1m%s -%s\033[0m\n", /* TRANSLATORS: this is the HSI suffix */ _("Runtime Suffix"), fwupd_security_attr_flag_to_suffix(hpi_suffixes[j])); for (guint i = 0; i < attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(attrs, i); if (!fwupd_security_attr_has_flag(attr, hpi_suffixes[j])) continue; if (fwupd_security_attr_has_flag( attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE) && !fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) runtime_help = TRUE; fu_security_attr_append_str(attr, str, strflags); } } } if (low_help) { g_string_append_printf( str, "\n%s\n » %s\n", /* TRANSLATORS: this is instructions on how to improve the HSI security level */ _("This system has a low HSI security level."), "https://fwupd.github.io/hsi.html#low-security-level"); } if (runtime_help) { g_string_append_printf( str, "\n%s\n » %s\n", /* TRANSLATORS: this is instructions on how to improve the HSI suffix */ _("This system has HSI runtime issues."), "https://fwupd.github.io/hsi.html#hsi-runtime-suffix"); } if (pcr0_help) { g_string_append_printf( str, "\n%s\n » %s\n", /* TRANSLATORS: this is more background on a security measurement problem */ _("The TPM PCR0 differs from reconstruction."), "https://fwupd.github.io/hsi.html#pcr0-tpm-event-log-reconstruction"); } return g_string_free(str, FALSE); } gboolean fu_util_send_report(FwupdClient *client, const gchar *report_uri, const gchar *data, const gchar *sig, gchar **uri, /* (nullable) (out) */ GError **error) { const gchar *server_msg = NULL; JsonNode *json_root; JsonObject *json_object; g_autofree gchar *str = NULL; g_autoptr(GBytes) upload_response = NULL; g_autoptr(JsonParser) json_parser = NULL; /* POST request */ upload_response = fwupd_client_upload_bytes(client, report_uri, data, sig, FWUPD_CLIENT_UPLOAD_FLAG_NONE, NULL, error); if (upload_response == NULL) return FALSE; /* server returned nothing, and probably exploded in a ball of flames */ if (g_bytes_get_size(upload_response) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "Failed to upload to %s", report_uri); return FALSE; } /* parse JSON reply */ json_parser = json_parser_new(); str = g_strndup(g_bytes_get_data(upload_response, NULL), g_bytes_get_size(upload_response)); if (!json_parser_load_from_data(json_parser, str, -1, error)) { g_prefix_error(error, "Failed to parse JSON response from '%s': ", str); return FALSE; } json_root = json_parser_get_root(json_parser); if (json_root == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "JSON response was malformed: '%s'", str); return FALSE; } json_object = json_node_get_object(json_root); if (json_object == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "JSON response object was malformed: '%s'", str); return FALSE; } /* get any optional server message */ if (json_object_has_member(json_object, "msg")) server_msg = json_object_get_string_member(json_object, "msg"); /* server reported failed */ if (!json_object_get_boolean_member(json_object, "success")) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "Server rejected report: %s", server_msg != NULL ? server_msg : "unspecified"); return FALSE; } /* server wanted us to see the message */ if (server_msg != NULL) { g_info("server message: %s", server_msg); if (g_strstr_len(server_msg, -1, "known issue") != NULL && json_object_has_member(json_object, "uri")) { if (uri != NULL) *uri = g_strdup(json_object_get_string_member(json_object, "uri")); } } /* success */ return TRUE; } gint fu_util_sort_devices_by_flags_cb(gconstpointer a, gconstpointer b) { FuDevice *dev_a = *((FuDevice **)a); FuDevice *dev_b = *((FuDevice **)b); if ((!fu_device_has_flag(dev_a, FWUPD_DEVICE_FLAG_UPDATABLE) && fu_device_has_flag(dev_b, FWUPD_DEVICE_FLAG_UPDATABLE)) || (!fu_device_has_flag(dev_a, FWUPD_DEVICE_FLAG_SUPPORTED) && fu_device_has_flag(dev_b, FWUPD_DEVICE_FLAG_SUPPORTED))) return -1; if ((fu_device_has_flag(dev_a, FWUPD_DEVICE_FLAG_UPDATABLE) && !fu_device_has_flag(dev_b, FWUPD_DEVICE_FLAG_UPDATABLE)) || (fu_device_has_flag(dev_a, FWUPD_DEVICE_FLAG_SUPPORTED) && !fu_device_has_flag(dev_b, FWUPD_DEVICE_FLAG_SUPPORTED))) return 1; return 0; } gboolean fu_util_switch_branch_warning(FuConsole *console, FwupdDevice *dev, FwupdRelease *rel, gboolean assume_yes, GError **error) { const gchar *desc_markup = NULL; g_autofree gchar *desc_plain = NULL; g_autofree gchar *title = NULL; g_autoptr(GString) desc_full = g_string_new(NULL); /* warn the user if the vendor is different */ if (g_strcmp0(fwupd_device_get_vendor(dev), fwupd_release_get_vendor(rel)) != 0) { g_string_append_printf( desc_full, /* TRANSLATORS: %1 is the firmware vendor, %2 is the device vendor name */ _("The firmware from %s is not " "supplied by %s, the hardware vendor."), fwupd_release_get_vendor(rel), fwupd_device_get_vendor(dev)); g_string_append(desc_full, "\n\n"); g_string_append_printf(desc_full, /* TRANSLATORS: %1 is the device vendor name */ _("Your hardware may be damaged using this firmware, " "and installing this release may void any warranty " "with %s."), fwupd_device_get_vendor(dev)); g_string_append(desc_full, "\n\n"); } /* from the in the AppStream data */ desc_markup = fwupd_release_get_description(rel); if (desc_markup == NULL) return TRUE; desc_plain = fu_util_convert_description(desc_markup, error); if (desc_plain == NULL) return FALSE; g_string_append(desc_full, desc_plain); /* TRANSLATORS: show and ask user to confirm -- * %1 is the old branch name, %2 is the new branch name */ title = g_strdup_printf(_("Switch branch from %s to %s?"), fu_util_branch_for_display(fwupd_device_get_branch(dev)), fu_util_branch_for_display(fwupd_release_get_branch(rel))); fu_console_box(console, title, desc_full->str, 80); if (!assume_yes) { if (!fu_console_input_bool(console, FALSE, "%s", /* TRANSLATORS: should the branch be changed */ _("Do you understand the consequences " "of changing the firmware branch?"))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Declined branch switch"); return FALSE; } } return TRUE; } gboolean fu_util_prompt_warning_fde(FuConsole *console, FwupdDevice *dev, GError **error) { const gchar *url = "https://github.com/fwupd/fwupd/wiki/Full-Disk-Encryption-Detected"; g_autoptr(GString) str = g_string_new(NULL); if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_AFFECTS_FDE)) return TRUE; g_string_append( str, /* TRANSLATORS: the platform secret is stored in the PCRx registers on the TPM */ _("Some of the platform secrets may be invalidated when updating this firmware.")); g_string_append(str, " "); g_string_append(str, /* TRANSLATORS: 'recovery key' here refers to a code, rather than a physical metal thing */ _("Please ensure you have the volume recovery key before continuing.")); g_string_append(str, "\n\n"); g_string_append_printf(str, /* TRANSLATORS: the %1 is a URL to a wiki page */ _("See %s for more details."), url); /* TRANSLATORS: title text, shown as a warning */ fu_console_box(console, _("Full Disk Encryption Detected"), str->str, 80); /* TRANSLATORS: prompt to apply the update */ if (!fu_console_input_bool(console, TRUE, "%s", _("Perform operation?"))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return FALSE; } return TRUE; } void fu_util_show_unsupported_warning(FuConsole *console) { #ifndef SUPPORTED_BUILD if (g_getenv("FWUPD_SUPPORTED") != NULL) return; /* TRANSLATORS: this is a prefix on the console */ fu_console_print_full(console, FU_CONSOLE_PRINT_FLAG_WARNING | FU_CONSOLE_PRINT_FLAG_STDERR, "%s\n", /* TRANSLATORS: unsupported build of the package */ _("This package has not been validated, it may not work properly.")); #endif } gboolean fu_util_modify_remote_warning(FuConsole *console, FwupdRemote *remote, gboolean assume_yes, GError **error) { const gchar *warning_markup = NULL; g_autofree gchar *warning_plain = NULL; /* get formatted text */ warning_markup = fwupd_remote_get_agreement(remote); if (warning_markup == NULL) return TRUE; warning_plain = fu_util_convert_description(warning_markup, error); if (warning_plain == NULL) return FALSE; /* TRANSLATORS: a remote here is like a 'repo' or software source */ fu_console_box(console, _("Enable new remote?"), warning_plain, 80); if (!assume_yes) { if (!fu_console_input_bool(console, TRUE, "%s", /* TRANSLATORS: should the remote still be enabled */ _("Agree and enable the remote?"))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Declined agreement"); return FALSE; } } return TRUE; } #ifdef HAVE_LIBCURL G_DEFINE_AUTOPTR_CLEANUP_FUNC(CURLU, curl_url_cleanup) #endif gboolean fu_util_is_url(const gchar *perhaps_url) { #ifdef HAVE_LIBCURL g_autoptr(CURLU) h = curl_url(); return curl_url_set(h, CURLUPART_URL, perhaps_url, 0) == CURLUE_OK; #else return g_str_has_prefix(perhaps_url, "http://") || g_str_has_prefix(perhaps_url, "https://"); #endif } gboolean fu_util_print_builder(FuConsole *console, JsonBuilder *builder, GError **error) { g_autofree gchar *data = NULL; g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonNode) json_root = NULL; /* export as a string */ json_root = json_builder_get_root(builder); json_generator = json_generator_new(); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); data = json_generator_to_data(json_generator, NULL); if (data == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to convert to JSON string"); return FALSE; } /* just print */ fu_console_print_literal(console, data); return TRUE; } void fu_util_print_error_as_json(FuConsole *console, const GError *error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Error"); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Domain"); json_builder_add_string_value(builder, g_quark_to_string(error->domain)); json_builder_set_member_name(builder, "Code"); json_builder_add_int_value(builder, error->code); json_builder_set_member_name(builder, "Message"); json_builder_add_string_value(builder, error->message); json_builder_end_object(builder); json_builder_end_object(builder); fu_util_print_builder(console, builder, NULL); } typedef enum { FU_UTIL_DEPENDENCY_KIND_UNKNOWN, FU_UTIL_DEPENDENCY_KIND_RUNTIME, FU_UTIL_DEPENDENCY_KIND_COMPILE, } FuUtilDependencyKind; static const gchar * fu_util_dependency_kind_to_string(FuUtilDependencyKind dependency_kind) { if (dependency_kind == FU_UTIL_DEPENDENCY_KIND_RUNTIME) return "runtime"; if (dependency_kind == FU_UTIL_DEPENDENCY_KIND_COMPILE) return "compile"; return NULL; } static gchar * fu_util_parse_project_dependency(const gchar *str, FuUtilDependencyKind *dependency_kind) { g_return_val_if_fail(str != NULL, NULL); if (g_str_has_prefix(str, "RuntimeVersion(")) { gsize strsz = strlen(str); if (dependency_kind != NULL) *dependency_kind = FU_UTIL_DEPENDENCY_KIND_RUNTIME; return g_strndup(str + 15, strsz - 16); } if (g_str_has_prefix(str, "CompileVersion(")) { gsize strsz = strlen(str); if (dependency_kind != NULL) *dependency_kind = FU_UTIL_DEPENDENCY_KIND_COMPILE; return g_strndup(str + 15, strsz - 16); } return g_strdup(str); } static gboolean fu_util_print_version_key_valid(const gchar *key) { g_return_val_if_fail(key != NULL, FALSE); if (g_str_has_prefix(key, "RuntimeVersion")) return TRUE; if (g_str_has_prefix(key, "CompileVersion")) return TRUE; return FALSE; } gboolean fu_util_project_versions_as_json(FuConsole *console, GHashTable *metadata, GError **error) { GHashTableIter iter; const gchar *key; const gchar *value; g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Versions"); json_builder_begin_array(builder); g_hash_table_iter_init(&iter, metadata); while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) { FuUtilDependencyKind dependency_kind = FU_UTIL_DEPENDENCY_KIND_UNKNOWN; g_autofree gchar *project = NULL; /* add version keys */ if (!fu_util_print_version_key_valid(key)) continue; project = fu_util_parse_project_dependency(key, &dependency_kind); json_builder_begin_object(builder); if (dependency_kind != FU_UTIL_DEPENDENCY_KIND_UNKNOWN) { json_builder_set_member_name(builder, "Type"); json_builder_add_string_value( builder, fu_util_dependency_kind_to_string(dependency_kind)); } json_builder_set_member_name(builder, "AppstreamId"); json_builder_add_string_value(builder, project); json_builder_set_member_name(builder, "Version"); json_builder_add_string_value(builder, value); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(console, builder, error); } gchar * fu_util_project_versions_to_string(GHashTable *metadata) { GHashTableIter iter; const gchar *key; const gchar *value; g_autoptr(GString) str = g_string_new(NULL); g_hash_table_iter_init(&iter, metadata); while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) { FuUtilDependencyKind dependency_kind = FU_UTIL_DEPENDENCY_KIND_UNKNOWN; g_autofree gchar *project = NULL; /* print version keys */ if (!fu_util_print_version_key_valid(key)) continue; project = fu_util_parse_project_dependency(key, &dependency_kind); g_string_append_printf(str, "%-10s%-30s%s\n", fu_util_dependency_kind_to_string(dependency_kind), project, value); } return g_string_free(g_steal_pointer(&str), FALSE); } fwupd-1.9.16/src/fu-util-common.h000066400000000000000000000074651460375044200165710ustar00rootroot00000000000000/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include #include #include "fwupd-bios-setting-private.h" #include "fwupd-security-attr-private.h" #include "fu-console.h" /* this is only valid for tools */ #define FWUPD_ERROR_INVALID_ARGS (FWUPD_ERROR_LAST + 1) typedef struct FuUtilPrivate FuUtilPrivate; typedef gboolean (*FuUtilCmdFunc)(FuUtilPrivate *util, gchar **values, GError **error); typedef struct { gchar *name; gchar *arguments; gchar *description; FuUtilCmdFunc callback; } FuUtilCmd; typedef enum { FU_SECURITY_ATTR_TO_STRING_FLAG_NONE = 0, FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_OBSOLETES = 1 << 0, FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_URLS = 1 << 1, /*< private >*/ FU_SECURITY_ATTR_TO_STRING_FLAG_LAST } FuSecurityAttrToStringFlags; void fu_util_print_tree(FuConsole *console, FwupdClient *client, GNode *n); gboolean fu_util_is_interesting_device(FwupdDevice *dev); gchar * fu_util_get_user_cache_path(const gchar *fn); gboolean fu_util_prompt_warning(FuConsole *console, FwupdDevice *device, FwupdRelease *release, const gchar *machine, GError **error); gboolean fu_util_prompt_warning_fde(FuConsole *console, FwupdDevice *dev, GError **error); gboolean fu_util_modify_remote_warning(FuConsole *console, FwupdRemote *remote, gboolean assume_yes, GError **error); gboolean fu_util_prompt_complete(FuConsole *console, FwupdDeviceFlags flags, gboolean prompt, GError **error); gboolean fu_util_update_reboot(GError **error); GPtrArray * fu_util_cmd_array_new(void); void fu_util_cmd_array_add(GPtrArray *array, const gchar *name, const gchar *arguments, const gchar *description, FuUtilCmdFunc callback); gchar * fu_util_cmd_array_to_string(GPtrArray *array); void fu_util_cmd_array_sort(GPtrArray *array); gboolean fu_util_cmd_array_run(GPtrArray *array, FuUtilPrivate *priv, const gchar *command, gchar **values, GError **error); const gchar * fu_util_branch_for_display(const gchar *branch); const gchar * fu_util_request_get_message(FwupdRequest *req); gboolean fu_util_parse_filter_device_flags(const gchar *filter, FwupdDeviceFlags *include, FwupdDeviceFlags *exclude, GError **error); gboolean fu_util_parse_filter_release_flags(const gchar *filter, FwupdReleaseFlags *include, FwupdReleaseFlags *exclude, GError **error); gchar * fu_util_device_to_string(FwupdClient *client, FwupdDevice *dev, guint idt); gchar * fu_util_plugin_to_string(FwupdPlugin *plugin, guint idt); const gchar * fu_util_plugin_flag_to_string(FwupdPluginFlags plugin_flag); gchar * fu_util_security_attrs_to_string(GPtrArray *attrs, FuSecurityAttrToStringFlags flags); gchar * fu_util_security_events_to_string(GPtrArray *events, FuSecurityAttrToStringFlags flags); gchar * fu_util_security_issues_to_string(GPtrArray *devices); gboolean fu_util_send_report(FwupdClient *client, const gchar *report_uri, const gchar *data, const gchar *sig, gchar **uri, GError **error); gint fu_util_sort_devices_by_flags_cb(gconstpointer a, gconstpointer b); gboolean fu_util_switch_branch_warning(FuConsole *console, FwupdDevice *dev, FwupdRelease *rel, gboolean assume_yes, GError **error); void fu_util_show_unsupported_warning(FuConsole *console); gboolean fu_util_is_url(const gchar *perhaps_url); gboolean fu_util_print_builder(FuConsole *console, JsonBuilder *builder, GError **error); void fu_util_print_error_as_json(FuConsole *console, const GError *error); gchar * fu_util_project_versions_to_string(GHashTable *metadata); gboolean fu_util_project_versions_as_json(FuConsole *console, GHashTable *metadata, GError **error); fwupd-1.9.16/src/fu-util.c000066400000000000000000005226321460375044200152740ustar00rootroot00000000000000/* * Copyright (C) 2015 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "FuMain" #include "config.h" #include #include #include #include #ifdef HAVE_GIO_UNIX #include #include #endif #include #include #include #include "fwupd-common-private.h" #include "fwupd-device-private.h" #include "fwupd-plugin-private.h" #include "fwupd-release-private.h" #include "fwupd-remote-private.h" #include "fu-console.h" #include "fu-plugin-private.h" #include "fu-polkit-agent.h" #include "fu-util-bios-setting.h" #include "fu-util-common.h" #ifdef HAVE_SYSTEMD #include "fu-systemd.h" #endif /* custom return codes */ #define EXIT_NOTHING_TO_DO 2 #define EXIT_NOT_FOUND 3 typedef enum { FU_UTIL_OPERATION_UNKNOWN, FU_UTIL_OPERATION_UPDATE, FU_UTIL_OPERATION_DOWNGRADE, FU_UTIL_OPERATION_INSTALL, FU_UTIL_OPERATION_LAST } FuUtilOperation; struct FuUtilPrivate { GCancellable *cancellable; GMainContext *main_ctx; GMainLoop *loop; GOptionContext *context; FwupdInstallFlags flags; FwupdClientDownloadFlags download_flags; FwupdClient *client; FuConsole *console; gboolean no_remote_check; gboolean no_metadata_check; gboolean no_reboot_check; gboolean no_unreported_check; gboolean no_safety_check; gboolean no_device_prompt; gboolean no_emulation_check; gboolean no_security_fix; gboolean assume_yes; gboolean sign; gboolean show_all; gboolean disable_ssl_strict; gboolean as_json; /* only valid in update and downgrade */ FuUtilOperation current_operation; FwupdDevice *current_device; GPtrArray *post_requests; FwupdDeviceFlags completion_flags; FwupdDeviceFlags filter_device_include; FwupdDeviceFlags filter_device_exclude; FwupdReleaseFlags filter_release_include; FwupdReleaseFlags filter_release_exclude; }; static gboolean fu_util_report_history(FuUtilPrivate *priv, gchar **values, GError **error); static FwupdDevice * fu_util_get_device_by_id(FuUtilPrivate *priv, const gchar *id, GError **error); static void fu_util_client_notify_cb(GObject *object, GParamSpec *pspec, FuUtilPrivate *priv) { if (priv->as_json) return; fu_console_set_progress(priv->console, fwupd_client_get_status(priv->client), fwupd_client_get_percentage(priv->client)); } static void fu_util_update_device_request_cb(FwupdClient *client, FwupdRequest *request, FuUtilPrivate *priv) { /* nothing sensible to show */ if (fwupd_request_get_message(request) == NULL) return; /* show this now */ if (fwupd_request_get_kind(request) == FWUPD_REQUEST_KIND_IMMEDIATE) { g_autofree gchar *fmt = NULL; g_autofree gchar *tmp = NULL; /* TRANSLATORS: the user needs to do something, e.g. remove the device */ fmt = fu_console_color_format(_("Action Required:"), FU_CONSOLE_COLOR_RED); tmp = g_strdup_printf("%s %s", fmt, fwupd_request_get_message(request)); fu_console_set_progress_title(priv->console, tmp); fu_console_beep(priv->console, 5); } /* save for later */ if (fwupd_request_get_kind(request) == FWUPD_REQUEST_KIND_POST) g_ptr_array_add(priv->post_requests, g_object_ref(request)); } static void fu_util_update_device_changed_cb(FwupdClient *client, FwupdDevice *device, FuUtilPrivate *priv) { g_autofree gchar *str = NULL; /* action has not been assigned yet */ if (priv->current_operation == FU_UTIL_OPERATION_UNKNOWN) return; /* allowed to set whenever the device has changed */ if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN; if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)) priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; /* same as last time, so ignore */ if (priv->current_device == NULL || g_strcmp0(fwupd_device_get_composite_id(priv->current_device), fwupd_device_get_composite_id(device)) == 0) { g_set_object(&priv->current_device, device); return; } /* ignore indirect devices that might have changed */ if (fwupd_device_get_status(device) == FWUPD_STATUS_IDLE || fwupd_device_get_status(device) == FWUPD_STATUS_UNKNOWN) { g_debug("ignoring %s with status %s", fwupd_device_get_name(device), fwupd_status_to_string(fwupd_device_get_status(device))); return; } /* show message in console */ if (priv->current_operation == FU_UTIL_OPERATION_UPDATE) { /* TRANSLATORS: %1 is a device name */ str = g_strdup_printf(_("Updating %s…"), fwupd_device_get_name(device)); fu_console_set_progress_title(priv->console, str); } else if (priv->current_operation == FU_UTIL_OPERATION_DOWNGRADE) { /* TRANSLATORS: %1 is a device name */ str = g_strdup_printf(_("Downgrading %s…"), fwupd_device_get_name(device)); fu_console_set_progress_title(priv->console, str); } else if (priv->current_operation == FU_UTIL_OPERATION_INSTALL) { /* TRANSLATORS: %1 is a device name */ str = g_strdup_printf(_("Installing on %s…"), fwupd_device_get_name(device)); fu_console_set_progress_title(priv->console, str); } else { g_warning("no FuUtilOperation set"); } g_set_object(&priv->current_device, device); } static FwupdDevice * fu_util_prompt_for_device(FuUtilPrivate *priv, GPtrArray *devices, GError **error) { FwupdDevice *dev; guint idx; g_autoptr(GPtrArray) devices_filtered = NULL; /* filter results */ devices_filtered = fwupd_device_array_filter_flags(devices, priv->filter_device_include, priv->filter_device_exclude, error); if (devices_filtered == NULL) return NULL; /* exactly one */ if (devices_filtered->len == 1) { dev = g_ptr_array_index(devices_filtered, 0); if (!priv->as_json) { fu_console_print( priv->console, "%s: %s", /* TRANSLATORS: device has been chosen by the daemon for the user */ _("Selected device"), fwupd_device_get_name(dev)); } return g_object_ref(dev); } /* no questions */ if (priv->no_device_prompt) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "can't prompt for devices"); return NULL; } /* TRANSLATORS: this is to abort the interactive prompt */ fu_console_print(priv->console, "0.\t%s", _("Cancel")); for (guint i = 0; i < devices_filtered->len; i++) { dev = g_ptr_array_index(devices_filtered, i); fu_console_print(priv->console, "%u.\t%s (%s)", i + 1, fwupd_device_get_id(dev), fwupd_device_get_name(dev)); } /* TRANSLATORS: get interactive prompt */ idx = fu_console_input_uint(priv->console, devices_filtered->len, "%s", _("Choose device")); if (idx == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return NULL; } dev = g_ptr_array_index(devices_filtered, idx - 1); return g_object_ref(dev); } static gboolean fu_util_perhaps_show_unreported(FuUtilPrivate *priv, GError **error) { g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) devices_failed = g_ptr_array_new(); g_autoptr(GPtrArray) devices_success = g_ptr_array_new(); g_autoptr(GPtrArray) remotes = NULL; g_autoptr(GHashTable) remote_id_uri_map = NULL; gboolean all_automatic = FALSE; /* we don't want to ask anything */ if (priv->no_unreported_check) { g_debug("skipping unreported check"); return TRUE; } /* get all devices from the history database */ devices = fwupd_client_get_history(priv->client, priv->cancellable, &error_local); if (devices == NULL) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) return TRUE; g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* create a map of RemoteID to RemoteURI */ remotes = fwupd_client_get_remotes(priv->client, priv->cancellable, error); if (remotes == NULL) return FALSE; remote_id_uri_map = g_hash_table_new(g_str_hash, g_str_equal); for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index(remotes, i); gboolean remote_automatic; if (fwupd_remote_get_id(remote) == NULL) continue; if (fwupd_remote_get_report_uri(remote) == NULL) continue; g_debug("adding %s for %s", fwupd_remote_get_report_uri(remote), fwupd_remote_get_id(remote)); g_hash_table_insert(remote_id_uri_map, (gpointer)fwupd_remote_get_id(remote), (gpointer)fwupd_remote_get_report_uri(remote)); remote_automatic = fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS); g_debug("%s is %d", fwupd_remote_get_title(remote), remote_automatic); if (remote_automatic && !all_automatic) all_automatic = TRUE; if (!remote_automatic && all_automatic) { all_automatic = FALSE; break; } } /* check that they can be reported */ for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); FwupdRelease *rel = fwupd_device_get_release_default(dev); const gchar *remote_id; const gchar *remote_uri; if (!fwupd_device_match_flags(dev, priv->filter_device_include, priv->filter_device_exclude)) continue; if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_REPORTED)) continue; if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) continue; /* find the RemoteURI to use for the device */ remote_id = fwupd_release_get_remote_id(rel); if (remote_id == NULL) { g_debug("%s has no RemoteID", fwupd_device_get_id(dev)); continue; } remote_uri = g_hash_table_lookup(remote_id_uri_map, remote_id); if (remote_uri == NULL) { g_debug("%s has no RemoteURI", remote_id); continue; } /* only send success and failure */ if (fwupd_device_get_update_state(dev) == FWUPD_UPDATE_STATE_FAILED) { g_ptr_array_add(devices_failed, dev); } else if (fwupd_device_get_update_state(dev) == FWUPD_UPDATE_STATE_SUCCESS) { g_ptr_array_add(devices_success, dev); } else { g_debug("ignoring %s with UpdateState %s", fwupd_device_get_id(dev), fwupd_update_state_to_string(fwupd_device_get_update_state(dev))); } } /* nothing to do */ if (devices_failed->len == 0 && devices_success->len == 0) { g_debug("no unreported devices"); return TRUE; } g_debug("All automatic: %d", all_automatic); /* show the success and failures */ if (!priv->assume_yes && !all_automatic) { /* delimit */ fu_console_line(priv->console, 48); /* failures */ if (devices_failed->len > 0) { fu_console_print_literal(priv->console, /* TRANSLATORS: a list of failed updates */ _("Devices that were not updated correctly:")); for (guint i = 0; i < devices_failed->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices_failed, i); FwupdRelease *rel = fwupd_device_get_release_default(dev); fu_console_print(priv->console, " • %s (%s → %s)", fwupd_device_get_name(dev), fwupd_device_get_version(dev), fwupd_release_get_version(rel)); } } /* success */ if (devices_success->len > 0) { fu_console_print_literal(priv->console, /* TRANSLATORS: a list of successful updates */ _("Devices that have been updated successfully:")); for (guint i = 0; i < devices_success->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices_success, i); FwupdRelease *rel = fwupd_device_get_release_default(dev); fu_console_print(priv->console, " • %s (%s → %s)", fwupd_device_get_name(dev), fwupd_device_get_version(dev), fwupd_release_get_version(rel)); } } /* ask for permission */ fu_console_print_literal(priv->console, /* TRANSLATORS: explain why we want to upload */ _("Uploading firmware reports helps hardware vendors " "to quickly identify failing and successful updates " "on real devices.")); if (!fu_console_input_bool(priv->console, TRUE, "%s (%s)", /* TRANSLATORS: ask the user to upload */ _("Review and upload report now?"), /* TRANSLATORS: metadata is downloaded */ _("Requires internet connection"))) { if (fu_console_input_bool(priv->console, FALSE, "%s", /* TRANSLATORS: offer to disable this nag */ _("Do you want to disable this feature " "for future updates?"))) { for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index(remotes, i); const gchar *remote_id = fwupd_remote_get_id(remote); if (fwupd_remote_get_report_uri(remote) == NULL) continue; if (!fwupd_client_modify_remote(priv->client, remote_id, "ReportURI", "", priv->cancellable, error)) return FALSE; } } g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Declined upload"); return FALSE; } } /* upload */ if (!fu_util_report_history(priv, NULL, error)) return FALSE; /* offer to make automatic */ if (!priv->assume_yes && !all_automatic) { if (fu_console_input_bool(priv->console, FALSE, "%s", /* TRANSLATORS: offer to stop asking the question */ _("Do you want to upload reports automatically for " "future updates?"))) { for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index(remotes, i); const gchar *remote_id = fwupd_remote_get_id(remote); if (fwupd_remote_get_report_uri(remote) == NULL) continue; if (fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS)) continue; if (!fwupd_client_modify_remote(priv->client, remote_id, "AutomaticReports", "true", priv->cancellable, error)) return FALSE; } } } /* success */ return TRUE; } static void fu_util_build_device_tree_node(FuUtilPrivate *priv, GNode *root, FwupdDevice *dev) { GNode *root_child = g_node_append_data(root, dev); if (fwupd_device_get_release_default(dev) != NULL) g_node_append_data(root_child, fwupd_device_get_release_default(dev)); } static gboolean fu_util_build_device_tree_cb(GNode *n, gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *)user_data; FwupdDevice *dev = n->data; /* root node */ if (dev == NULL) return FALSE; /* release */ if (FWUPD_IS_RELEASE(n->data)) return FALSE; /* an interesting child, so include the parent */ for (GNode *c = n->children; c != NULL; c = c->next) { if (c->data != NULL) return FALSE; } /* not interesting, clear the node data */ if (!fwupd_device_match_flags(dev, priv->filter_device_include, priv->filter_device_exclude)) n->data = NULL; else if (!priv->show_all && !fu_util_is_interesting_device(dev)) n->data = NULL; /* continue */ return FALSE; } static void fu_util_build_device_tree(FuUtilPrivate *priv, GNode *root, GPtrArray *devs) { /* add the top-level parents */ for (guint i = 0; i < devs->len; i++) { FwupdDevice *dev_tmp = g_ptr_array_index(devs, i); if (fwupd_device_get_parent(dev_tmp) != NULL) continue; fu_util_build_device_tree_node(priv, root, dev_tmp); } /* children */ for (guint i = 0; i < devs->len; i++) { FwupdDevice *dev_tmp = g_ptr_array_index(devs, i); GNode *root_parent; if (fwupd_device_get_parent(dev_tmp) == NULL) continue; root_parent = g_node_find(root, G_PRE_ORDER, G_TRAVERSE_ALL, fwupd_device_get_parent(dev_tmp)); if (root_parent == NULL) continue; fu_util_build_device_tree_node(priv, root_parent, dev_tmp); } /* prune children that are not updatable */ g_node_traverse(root, G_POST_ORDER, G_TRAVERSE_ALL, -1, fu_util_build_device_tree_cb, priv); } static gboolean fu_util_get_releases_as_json(FuUtilPrivate *priv, GPtrArray *rels, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Releases"); json_builder_begin_array(builder); for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel = g_ptr_array_index(rels, i); if (!fwupd_release_match_flags(rel, priv->filter_release_include, priv->filter_release_exclude)) continue; json_builder_begin_object(builder); fwupd_release_to_json(rel, builder); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(priv->console, builder, error); } static gboolean fu_util_get_devices_as_json(FuUtilPrivate *priv, GPtrArray *devs, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Devices"); json_builder_begin_array(builder); for (guint i = 0; i < devs->len; i++) { FwupdDevice *dev = g_ptr_array_index(devs, i); g_autoptr(GPtrArray) rels = NULL; g_autoptr(GError) error_local = NULL; /* add all releases that could be applied */ rels = fwupd_client_get_releases(priv->client, fwupd_device_get_id(dev), priv->cancellable, &error_local); if (rels == NULL) { g_debug("not adding releases to device: %s", error_local->message); } else { for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel = g_ptr_array_index(rels, j); if (!fwupd_release_match_flags(rel, priv->filter_release_include, priv->filter_release_exclude)) continue; fwupd_device_add_release(dev, rel); } } /* add to builder */ json_builder_begin_object(builder); fwupd_device_to_json_full(dev, builder, FWUPD_DEVICE_FLAG_TRUSTED); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(priv->console, builder, error); } static gboolean fu_util_get_devices(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GNode) root = g_node_new(NULL); g_autoptr(GPtrArray) devs = NULL; /* get results from daemon */ if (g_strv_length(values) > 0) { devs = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; values[i] != NULL; i++) { FwupdDevice *device = fu_util_get_device_by_id(priv, values[i], error); if (device == NULL) return FALSE; g_ptr_array_add(devs, device); } } else { devs = fwupd_client_get_devices(priv->client, priv->cancellable, error); if (devs == NULL) return FALSE; } /* not for human consumption */ if (priv->as_json) return fu_util_get_devices_as_json(priv, devs, error); /* print */ if (devs->len > 0) fu_util_build_device_tree(priv, root, devs); if (g_node_n_children(root) == 0) { fu_console_print_literal(priv->console, /* TRANSLATORS: nothing attached that can be upgraded */ _("No hardware detected with firmware update capability")); return TRUE; } fu_util_print_tree(priv->console, priv->client, root); /* nag? */ if (!fu_util_perhaps_show_unreported(priv, error)) return FALSE; return TRUE; } static gboolean fu_util_get_plugins_as_json(FuUtilPrivate *priv, GPtrArray *plugins, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Plugins"); json_builder_begin_array(builder); for (guint i = 0; i < plugins->len; i++) { FwupdPlugin *plugin = g_ptr_array_index(plugins, i); json_builder_begin_object(builder); fwupd_plugin_to_json(plugin, builder); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(priv->console, builder, error); } static gboolean fu_util_get_plugins(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) plugins = NULL; /* get results from daemon */ plugins = fwupd_client_get_plugins(priv->client, priv->cancellable, error); if (plugins == NULL) return FALSE; if (priv->as_json) return fu_util_get_plugins_as_json(priv, plugins, error); /* print */ for (guint i = 0; i < plugins->len; i++) { FuPlugin *plugin = g_ptr_array_index(plugins, i); g_autofree gchar *str = fu_util_plugin_to_string(FWUPD_PLUGIN(plugin), 0); fu_console_print_literal(priv->console, str); } if (plugins->len == 0) { /* TRANSLATORS: nothing found */ fu_console_print_literal(priv->console, _("No plugins found")); } /* success */ return TRUE; } static gchar * fu_util_download_if_required(FuUtilPrivate *priv, const gchar *perhapsfn, GError **error) { g_autofree gchar *filename = NULL; g_autoptr(GBytes) blob = NULL; /* a local file */ if (g_file_test(perhapsfn, G_FILE_TEST_EXISTS)) return g_strdup(perhapsfn); if (!fu_util_is_url(perhapsfn)) return g_strdup(perhapsfn); /* download the firmware to a cachedir */ filename = fu_util_get_user_cache_path(perhapsfn); if (g_file_test(filename, G_FILE_TEST_EXISTS)) return g_steal_pointer(&filename); if (!fu_path_mkdir_parent(filename, error)) return NULL; blob = fwupd_client_download_bytes(priv->client, perhapsfn, priv->download_flags, priv->cancellable, error); if (blob == NULL) return NULL; /* save file to cache */ if (!fu_bytes_set_contents(filename, blob, error)) return NULL; return g_steal_pointer(&filename); } static void fu_util_display_current_message(FuUtilPrivate *priv) { /* TRANSLATORS: success message */ fu_console_print_literal(priv->console, _("Successfully installed firmware")); /* print all POST requests */ for (guint i = 0; i < priv->post_requests->len; i++) { FwupdRequest *request = g_ptr_array_index(priv->post_requests, i); fu_console_print_literal(priv->console, fu_util_request_get_message(request)); } } typedef struct { guint nr_success; guint nr_failed; guint nr_missing; JsonBuilder *builder; const gchar *name; gboolean use_emulation; } FuUtilDeviceTestHelper; static gboolean fu_util_device_test_component(FuUtilPrivate *priv, FuUtilDeviceTestHelper *helper, JsonObject *json_obj, GBytes *fw, GError **error) { JsonArray *json_array; const gchar *name = "component"; const gchar *protocol = NULL; g_autoptr(FwupdDevice) device = NULL; /* some elements are optional */ if (json_object_has_member(json_obj, "name")) { name = json_object_get_string_member(json_obj, "name"); json_builder_set_member_name(helper->builder, "name"); json_builder_add_string_value(helper->builder, name); } if (json_object_has_member(json_obj, "protocol")) { protocol = json_object_get_string_member(json_obj, "protocol"); json_builder_set_member_name(helper->builder, "protocol"); json_builder_add_string_value(helper->builder, protocol); } /* find the device with any of the matching GUIDs */ if (!json_object_has_member(json_obj, "guids")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "JSON invalid as has no 'guids'"); return FALSE; } json_array = json_object_get_array_member(json_obj, "guids"); json_builder_set_member_name(helper->builder, "guids"); json_builder_begin_array(helper->builder); for (guint i = 0; i < json_array_get_length(json_array); i++) { JsonNode *json_node = json_array_get_element(json_array, i); FwupdDevice *device_tmp; const gchar *guid = json_node_get_string(json_node); g_autoptr(GPtrArray) devices = NULL; g_debug("looking for guid %s", guid); devices = fwupd_client_get_devices_by_guid(priv->client, guid, priv->cancellable, NULL); if (devices == NULL) continue; if (devices->len > 1) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "multiple devices with GUID %s", guid); return FALSE; } device_tmp = g_ptr_array_index(devices, 0); if (protocol != NULL && !fwupd_device_has_protocol(device_tmp, protocol)) continue; device = g_object_ref(device_tmp); json_builder_add_string_value(helper->builder, guid); break; } json_builder_end_array(helper->builder); if (device == NULL) { if (!priv->as_json) { g_autofree gchar *msg = NULL; msg = fu_console_color_format( /* TRANSLATORS: this is for the device tests */ _("Did not find any devices with matching GUIDs"), FU_CONSOLE_COLOR_RED); fu_console_print(priv->console, "%s: %s", name, msg); } json_builder_set_member_name(helper->builder, "error"); json_builder_add_string_value(helper->builder, "no devices found"); helper->nr_failed++; return TRUE; } /* verify the version matches what we expected */ if (json_object_has_member(json_obj, "version")) { const gchar *version = json_object_get_string_member(json_obj, "version"); json_builder_set_member_name(helper->builder, "version"); json_builder_add_string_value(helper->builder, version); if (g_strcmp0(version, fwupd_device_get_version(device)) != 0) { g_autofree gchar *str = NULL; str = g_strdup_printf("version did not match: got %s, expected %s", fwupd_device_get_version(device), version); if (!priv->as_json) { g_autofree gchar *msg = NULL; g_autofree gchar *str2 = NULL; str2 = g_strdup_printf( /* TRANSLATORS: this is for the device tests, %1 is the device * version, %2 is what we expected */ _("The device version did not match: got %s, expected %s"), fwupd_device_get_version(device), version); msg = fu_console_color_format(str2, FU_CONSOLE_COLOR_RED); fu_console_print(priv->console, "%s: %s", name, msg); } json_builder_set_member_name(helper->builder, "error"); json_builder_add_string_value(helper->builder, str); helper->nr_failed++; } } /* success */ if (!priv->as_json) { g_autofree gchar *msg = NULL; /* TRANSLATORS: this is for the device tests */ msg = fu_console_color_format(_("OK!"), FU_CONSOLE_COLOR_GREEN); fu_console_print(priv->console, "%s: %s", helper->name, msg); } helper->nr_success++; return TRUE; } static gboolean fu_util_emulation_load_with_fallback(FuUtilPrivate *priv, GBytes *emulation_data, GError **error) { g_autoptr(GError) error_local = NULL; /* load data, but handle the case when emulation is disabled */ if (!fwupd_client_emulation_load(priv->client, emulation_data, priv->cancellable, &error_local)) { if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED) || priv->no_emulation_check) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* TRANSLATORS: */ if (!priv->assume_yes) { if (!fu_console_input_bool(priv->console, TRUE, "%s %s", /* ability to load emulated devices is opt-in */ _("Device emulation is not enabled."), /* TRANSLATORS: we can do this live */ _("Do you want to enable it now?"))) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } if (!fwupd_client_modify_config(priv->client, "AllowEmulation", "true", priv->cancellable, error)) return FALSE; } else { return TRUE; } /* lets try again */ return fwupd_client_emulation_load(priv->client, emulation_data, priv->cancellable, error); } static gboolean fu_util_device_test_step(FuUtilPrivate *priv, FuUtilDeviceTestHelper *helper, JsonObject *json_obj, GError **error) { JsonArray *json_array; const gchar *url; const gchar *emulation_url = NULL; g_autofree gchar *filename = NULL; g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error_local = NULL; /* send this data to the daemon */ if (helper->use_emulation) { g_autofree gchar *emulation_filename = NULL; g_autoptr(GBytes) emulation_data = NULL; /* just ignore anything without emulation data */ if (!json_object_has_member(json_obj, "emulation-url")) return TRUE; emulation_url = json_object_get_string_member(json_obj, "emulation-url"); emulation_filename = fu_util_download_if_required(priv, emulation_url, error); if (emulation_filename == NULL) { g_prefix_error(error, "failed to download %s: ", emulation_url); return FALSE; } emulation_data = fu_bytes_get_contents(emulation_filename, error); if (emulation_data == NULL) return FALSE; if (!fu_util_emulation_load_with_fallback(priv, emulation_data, error)) return FALSE; } /* download file if required */ if (!json_object_has_member(json_obj, "url")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "JSON invalid as has no 'url'"); return FALSE; } /* build URL */ url = json_object_get_string_member(json_obj, "url"); filename = fu_util_download_if_required(priv, url, error); if (filename == NULL) { g_prefix_error(error, "failed to download %s: ", url); return FALSE; } /* log */ json_builder_set_member_name(helper->builder, "url"); json_builder_add_string_value(helper->builder, url); /* install file */ priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; if (!fwupd_client_install(priv->client, FWUPD_DEVICE_ID_ANY, filename, priv->flags, priv->cancellable, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) { json_builder_set_member_name(helper->builder, "info"); json_builder_add_string_value(helper->builder, error_local->message); helper->nr_missing++; return TRUE; } if (!priv->as_json) { g_autofree gchar *msg = NULL; msg = fu_console_color_format(error_local->message, FU_CONSOLE_COLOR_RED); fu_console_print(priv->console, "%s: %s", helper->name, msg); } json_builder_set_member_name(helper->builder, "error"); json_builder_add_string_value(helper->builder, error_local->message); helper->nr_failed++; return TRUE; } /* process each step */ if (!json_object_has_member(json_obj, "components")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "JSON invalid as has no 'components'"); return FALSE; } json_array = json_object_get_array_member(json_obj, "components"); for (guint i = 0; i < json_array_get_length(json_array); i++) { JsonNode *json_node = json_array_get_element(json_array, i); JsonObject *json_obj_tmp = json_node_get_object(json_node); if (!fu_util_device_test_component(priv, helper, json_obj_tmp, fw, error)) return FALSE; } /* success */ json_builder_set_member_name(helper->builder, "success"); json_builder_add_boolean_value(helper->builder, TRUE); return TRUE; } static gboolean fu_util_device_test_filename(FuUtilPrivate *priv, FuUtilDeviceTestHelper *helper, const gchar *filename, GError **error) { JsonNode *json_root; JsonNode *json_steps; JsonObject *json_obj; guint repeat = 1; g_autoptr(JsonParser) parser = json_parser_new(); /* log */ json_builder_set_member_name(helper->builder, "filename"); json_builder_add_string_value(helper->builder, filename); /* parse JSON */ if (!json_parser_load_from_file(parser, filename, error)) { g_prefix_error(error, "test not in JSON format: "); return FALSE; } json_root = json_parser_get_root(parser); if (json_root == NULL || !JSON_NODE_HOLDS_OBJECT(json_root)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "JSON invalid as has no root"); return FALSE; } json_obj = json_node_get_object(json_root); if (!json_object_has_member(json_obj, "steps")) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "JSON invalid as has no 'steps'"); return FALSE; } json_steps = json_object_get_member(json_obj, "steps"); if (!JSON_NODE_HOLDS_ARRAY(json_steps)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "JSON invalid as has 'steps' is not an array"); return FALSE; } /* some elements are optional */ if (json_object_has_member(json_obj, "name")) { helper->name = json_object_get_string_member(json_obj, "name"); json_builder_set_member_name(helper->builder, "name"); json_builder_add_string_value(helper->builder, helper->name); } if (json_object_has_member(json_obj, "interactive")) { gboolean interactive = json_object_get_boolean_member(json_obj, "interactive"); json_builder_set_member_name(helper->builder, "interactive"); json_builder_add_boolean_value(helper->builder, interactive); } /* process each step */ if (json_object_has_member(json_obj, "repeat")) { repeat = json_object_get_int_member(json_obj, "repeat"); json_builder_set_member_name(helper->builder, "repeat"); json_builder_add_int_value(helper->builder, repeat); } json_builder_set_member_name(helper->builder, "steps"); json_builder_begin_array(helper->builder); for (guint j = 0; j < repeat; j++) { JsonArray *json_array = json_node_get_array(json_steps); for (guint i = 0; i < json_array_get_length(json_array); i++) { JsonNode *json_node = json_array_get_element(json_array, i); json_obj = json_node_get_object(json_node); json_builder_begin_object(helper->builder); if (!fu_util_device_test_step(priv, helper, json_obj, error)) return FALSE; json_builder_end_object(helper->builder); } } json_builder_end_array(helper->builder); /* success */ return TRUE; } static gboolean fu_util_inhibit(FuUtilPrivate *priv, gchar **values, GError **error) { const gchar *reason = "not set"; g_autofree gchar *inhibit_id = NULL; g_autoptr(GString) str = g_string_new(NULL); if (g_strv_length(values) > 0) reason = values[1]; /* inhibit then wait */ inhibit_id = fwupd_client_inhibit(priv->client, reason, priv->cancellable, error); if (inhibit_id == NULL) return FALSE; /* TRANSLATORS: the inhibit ID is a short string like dbus-123456 */ g_string_append_printf(str, _("Inhibit ID is %s."), inhibit_id); g_string_append(str, "\n"); /* TRANSLATORS: CTRL^C [holding control, and then pressing C] will exit the program */ g_string_append(str, _("Use CTRL^C to cancel.")); /* TRANSLATORS: this CLI tool is now preventing system updates */ fu_console_box(priv->console, _("System Update Inhibited"), str->str, 80); g_main_loop_run(priv->loop); return TRUE; } static gboolean fu_util_uninhibit(FuUtilPrivate *priv, gchar **values, GError **error) { /* one argument required */ if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments, expected INHIBIT-ID"); return FALSE; } /* just uninhibit with the token */ return fwupd_client_uninhibit(priv->client, values[0], priv->cancellable, error); } typedef struct { FuUtilPrivate *priv; const gchar *value; FwupdDevice *device; /* no-ref */ } FuUtilWaitHelper; static void fu_util_device_wait_added_cb(FwupdClient *client, FwupdDevice *device, FuUtilWaitHelper *helper) { FuUtilPrivate *priv = helper->priv; if (g_strcmp0(fwupd_device_get_id(device), helper->value) == 0 || fwupd_device_has_guid(device, helper->value)) { helper->device = device; g_main_loop_quit(priv->loop); return; } } static gboolean fu_util_device_wait_timeout_cb(gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *)user_data; g_main_loop_quit(priv->loop); return G_SOURCE_REMOVE; } static gboolean fu_util_device_wait(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) device = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GSource) source = g_timeout_source_new_seconds(30); g_autoptr(GTimer) timer = g_timer_new(); FuUtilWaitHelper helper = {.priv = priv, .value = values[0]}; /* one argument required */ if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments, expected GUID|DEVICE-ID"); return FALSE; } /* check if the device already exists */ device = fwupd_client_get_device_by_id(priv->client, helper.value, NULL, NULL); if (device != NULL) { /* TRANSLATORS: the device is already connected */ fu_console_print_literal(priv->console, _("Device already exists")); return TRUE; } devices = fwupd_client_get_devices_by_guid(priv->client, helper.value, NULL, NULL); if (devices != NULL) { /* TRANSLATORS: the device is already connected */ fu_console_print_literal(priv->console, _("Device already exists")); return TRUE; } /* wait for device to show up */ fu_console_set_progress(priv->console, FWUPD_STATUS_IDLE, 0); g_signal_connect(FWUPD_CLIENT(priv->client), "device-added", G_CALLBACK(fu_util_device_wait_added_cb), &helper); g_source_set_callback(source, fu_util_device_wait_timeout_cb, priv, NULL); g_source_attach(source, priv->main_ctx); g_main_loop_run(priv->loop); /* timed out */ if (helper.device == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Stopped waiting for %s after %.0fms", helper.value, g_timer_elapsed(timer, NULL) * 1000.f); return FALSE; } /* success */ fu_console_print(priv->console, /* TRANSLATORS: the device showed up in time */ _("Successfully waited %.0fms for device"), g_timer_elapsed(timer, NULL) * 1000.f); return TRUE; } static gboolean fu_util_quit(FuUtilPrivate *priv, gchar **values, GError **error) { /* success */ return fwupd_client_quit(priv->client, priv->cancellable, error); } static gboolean fu_util_device_test_full(FuUtilPrivate *priv, gchar **values, FuUtilDeviceTestHelper *helper, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); helper->builder = builder; /* required for interactive devices */ priv->current_operation = FU_UTIL_OPERATION_UPDATE; /* at least one argument required */ if (g_strv_length(values) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* prepare to save the data as JSON */ json_builder_begin_object(builder); /* process all the files */ json_builder_set_member_name(builder, "results"); json_builder_begin_array(builder); for (guint i = 0; values[i] != NULL; i++) { json_builder_begin_object(builder); if (!fu_util_device_test_filename(priv, helper, values[i], error)) return FALSE; json_builder_end_object(builder); } json_builder_end_array(builder); /* dump to screen as JSON format */ json_builder_end_object(builder); if (priv->as_json) { if (!fu_util_print_builder(priv->console, builder, error)) return FALSE; } /* we need all to pass for a zero return code */ if (helper->nr_failed > 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Some of the tests failed"); return FALSE; } if (helper->nr_missing > 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "%u devices required for %u tests were not found", helper->nr_missing, g_strv_length(values)); return FALSE; } if (helper->nr_success == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "None of the tests were successful"); return FALSE; } /* nag? */ if (!fu_util_perhaps_show_unreported(priv, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_util_device_emulate(FuUtilPrivate *priv, gchar **values, GError **error) { FuUtilDeviceTestHelper helper = {.use_emulation = TRUE}; return fu_util_device_test_full(priv, values, &helper, error); } static gboolean fu_util_device_test(FuUtilPrivate *priv, gchar **values, GError **error) { FuUtilDeviceTestHelper helper = {.use_emulation = FALSE}; return fu_util_device_test_full(priv, values, &helper, error); } static gboolean fu_util_download(FuUtilPrivate *priv, gchar **values, GError **error) { g_autofree gchar *basename = NULL; g_autoptr(GBytes) blob = NULL; /* one argument required */ if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* file already exists */ basename = g_path_get_basename(values[0]); if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && g_file_test(basename, G_FILE_TEST_EXISTS)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "%s already exists", basename); return FALSE; } blob = fwupd_client_download_bytes(priv->client, values[0], priv->download_flags, priv->cancellable, error); if (blob == NULL) return FALSE; return g_file_set_contents(basename, g_bytes_get_data(blob, NULL), g_bytes_get_size(blob), error); } static gboolean fu_util_local_install(FuUtilPrivate *priv, gchar **values, GError **error) { const gchar *id; g_autofree gchar *filename = NULL; g_autoptr(FwupdDevice) dev = NULL; /* handle both forms */ if (g_strv_length(values) == 1) { id = FWUPD_DEVICE_ID_ANY; } else if (g_strv_length(values) == 2) { dev = fu_util_get_device_by_id(priv, values[1], error); if (dev == NULL) return FALSE; id = fwupd_device_get_id(dev); } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } priv->current_operation = FU_UTIL_OPERATION_INSTALL; /* install with flags chosen by the user */ filename = fu_util_download_if_required(priv, values[0], error); if (filename == NULL) return FALSE; /* detect bitlocker */ if (dev != NULL && !priv->no_safety_check && !priv->assume_yes) { if (!fu_util_prompt_warning_fde(priv->console, dev, error)) return FALSE; } if (!fwupd_client_install(priv->client, id, filename, priv->flags, priv->cancellable, error)) return FALSE; fu_util_display_current_message(priv); /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug("skipping reboot check"); return TRUE; } /* show reboot if needed */ return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_get_details_as_json(FuUtilPrivate *priv, GPtrArray *devs, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Devices"); json_builder_begin_array(builder); for (guint i = 0; i < devs->len; i++) { FwupdDevice *dev = g_ptr_array_index(devs, i); json_builder_begin_object(builder); fwupd_device_to_json_full(dev, builder, FWUPD_DEVICE_FLAG_TRUSTED); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(priv->console, builder, error); } static gboolean fu_util_get_details(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) array = NULL; g_autoptr(GNode) root = g_node_new(NULL); /* check args */ if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* implied, important for get-details on a device not in your system */ priv->show_all = TRUE; array = fwupd_client_get_details(priv->client, values[0], priv->cancellable, error); if (array == NULL) return FALSE; if (priv->as_json) return fu_util_get_details_as_json(priv, array, error); fu_util_build_device_tree(priv, root, array); fu_util_print_tree(priv->console, priv->client, root); return TRUE; } static gboolean fu_util_report_history_for_remote(FuUtilPrivate *priv, const gchar *remote_id, GPtrArray *devices, GError **error) { g_autofree gchar *data = NULL; g_autofree gchar *report_uri = NULL; g_autofree gchar *sig = NULL; g_autofree gchar *uri = NULL; g_autoptr(FwupdRemote) remote = NULL; /* convert to JSON */ data = fwupd_build_history_report_json(devices, error); if (data == NULL) return FALSE; /* self sign data */ if (priv->sign) { sig = fwupd_client_self_sign(priv->client, data, FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP, priv->cancellable, error); if (sig == NULL) return FALSE; } remote = fwupd_client_get_remote_by_id(priv->client, remote_id, priv->cancellable, error); if (remote == NULL) return FALSE; /* ask for permission */ report_uri = fwupd_remote_build_report_uri(remote, error); if (report_uri == NULL) return FALSE; if (!priv->assume_yes && !fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS)) { fu_console_print_kv(priv->console, _("Target"), report_uri); fu_console_print_kv(priv->console, _("Payload"), data); if (sig != NULL) fu_console_print_kv(priv->console, _("Signature"), sig); if (!fu_console_input_bool(priv->console, TRUE, "%s", _("Proceed with upload?"))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "User declined action"); return FALSE; } } /* POST request and parse reply */ if (!fu_util_send_report(priv->client, report_uri, data, sig, &uri, error)) return FALSE; /* server wanted us to see a message */ if (uri != NULL) { fu_console_print( priv->console, "%s %s", /* TRANSLATORS: the server sent the user a small message */ _("Update failure is a known issue, visit this URL for more information:"), uri); } /* success */ return TRUE; } static gboolean fu_util_report_history_force(FuUtilPrivate *priv, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_autoptr(GString) str = g_string_new(NULL); /* get all devices */ devices = fwupd_client_get_history(priv->client, priv->cancellable, error); if (devices == NULL) return FALSE; /* just assume every report goes to this remote */ if (!fu_util_report_history_for_remote(priv, "lvfs", devices, error)) return FALSE; /* mark each device as reported */ for (guint i = 0; i < devices->len; i++) { FwupdDevice *device = g_ptr_array_index(devices, i); g_debug("setting flag on %s", fwupd_device_get_id(device)); if (!fwupd_client_modify_device(priv->client, fwupd_device_get_id(device), "Flags", "reported", priv->cancellable, error)) return FALSE; } /* success */ g_string_append_printf(str, /* TRANSLATORS: success message -- where the user has uploaded * success and/or failure reports to the remote server */ ngettext("Successfully uploaded %u report", "Successfully uploaded %u reports", devices->len), devices->len); fu_console_print_literal(priv->console, str->str); return TRUE; } static gboolean fu_util_report_export(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices_filtered = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_autoptr(GPtrArray) devices = NULL; /* get all devices from the history database, then filter them and export to JSON */ devices = fwupd_client_get_history(priv->client, priv->cancellable, error); if (devices == NULL) return FALSE; g_debug("%u devices with history", devices->len); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); gboolean dev_skip_byid = TRUE; /* only process particular DEVICE-ID or GUID if specified */ for (guint idx = 0; idx < g_strv_length(values); idx++) { const gchar *tmpid = values[idx]; const gchar *device_id = fwupd_device_get_id(dev); if (fwupd_device_has_guid(dev, tmpid) || g_strcmp0(device_id, tmpid) == 0) { dev_skip_byid = FALSE; break; } } if (g_strv_length(values) > 0 && dev_skip_byid) continue; /* filter, if not forcing */ if (!fwupd_device_match_flags(dev, priv->filter_device_include, priv->filter_device_exclude)) continue; if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_REPORTED)) { g_debug("%s has already been reported", fwupd_device_get_id(dev)); continue; } } /* only send success and failure */ if (fwupd_device_get_update_state(dev) != FWUPD_UPDATE_STATE_FAILED && fwupd_device_get_update_state(dev) != FWUPD_UPDATE_STATE_SUCCESS) { g_debug("ignoring %s with UpdateState %s", fwupd_device_get_id(dev), fwupd_update_state_to_string(fwupd_device_get_update_state(dev))); continue; } g_ptr_array_add(devices_filtered, g_object_ref(dev)); } /* nothing to report, but try harder with --force */ if (devices_filtered->len == 0 && (priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No reports require uploading"); return FALSE; } /* write each device report as a new file */ for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); g_autofree gchar *data = NULL; g_autofree gchar *filename = NULL; g_autoptr(FuFirmware) archive = fu_archive_firmware_new(); g_autoptr(FuFirmware) payload_img = NULL; g_autoptr(GBytes) payload_blob = NULL; g_autoptr(GFile) file = NULL; g_autoptr(GPtrArray) devices_tmp = g_ptr_array_new(); /* convert single device to JSON */ g_ptr_array_add(devices_tmp, dev); data = fwupd_build_history_report_json(devices, error); if (data == NULL) return FALSE; payload_blob = g_bytes_new(data, strlen(data)); payload_img = fu_firmware_new_from_bytes(payload_blob); fu_firmware_set_id(payload_img, "report.json"); fu_firmware_add_image(archive, payload_img); /* self sign data */ if (priv->sign) { g_autofree gchar *sig = NULL; g_autoptr(FuFirmware) sig_img = NULL; g_autoptr(GBytes) sig_blob = NULL; sig = fwupd_client_self_sign(priv->client, data, FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP, priv->cancellable, error); if (sig == NULL) return FALSE; sig_blob = g_bytes_new(sig, strlen(sig)); sig_img = fu_firmware_new_from_bytes(sig_blob); fu_firmware_set_id(sig_img, "report.json.p7c"); fu_firmware_add_image(archive, sig_img); } /* save to local file */ fu_archive_firmware_set_format(FU_ARCHIVE_FIRMWARE(archive), FU_ARCHIVE_FORMAT_ZIP); fu_archive_firmware_set_compression(FU_ARCHIVE_FIRMWARE(archive), FU_ARCHIVE_COMPRESSION_GZIP); filename = g_strdup_printf("%s.fwupdreport", fwupd_device_get_id(dev)); file = g_file_new_for_path(filename); if (!fu_firmware_write_file(archive, file, error)) return FALSE; /* TRANSLATORS: key for a offline report filename */ fu_console_print_kv(priv->console, _("Saved report"), filename); } /* success */ return TRUE; } static gboolean fu_util_report_history(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GHashTable) report_map = NULL; g_autoptr(GList) ids = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GString) str = g_string_new(NULL); /* get all devices from the history database, then filter them, * adding to a hash map of report-ids */ devices = fwupd_client_get_history(priv->client, priv->cancellable, error); if (devices == NULL) return FALSE; g_debug("%u devices with history", devices->len); report_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_ptr_array_unref); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); FwupdRelease *rel = fwupd_device_get_release_default(dev); const gchar *remote_id; GPtrArray *devices_tmp; g_autoptr(FwupdRemote) remote = NULL; /* filter, if not forcing */ if (!fwupd_device_match_flags(dev, priv->filter_device_include, priv->filter_device_exclude)) continue; if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_REPORTED)) { g_debug("%s has already been reported", fwupd_device_get_id(dev)); continue; } if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { g_debug("%s is not supported", fwupd_device_get_id(dev)); continue; } } /* only send success and failure */ if (fwupd_device_get_update_state(dev) != FWUPD_UPDATE_STATE_FAILED && fwupd_device_get_update_state(dev) != FWUPD_UPDATE_STATE_SUCCESS) { g_debug("ignoring %s with UpdateState %s", fwupd_device_get_id(dev), fwupd_update_state_to_string(fwupd_device_get_update_state(dev))); continue; } /* find the RemoteURI to use for the device */ remote_id = fwupd_release_get_remote_id(rel); if (remote_id == NULL) { g_debug("%s has no RemoteID", fwupd_device_get_id(dev)); continue; } remote = fwupd_client_get_remote_by_id(priv->client, remote_id, priv->cancellable, error); if (remote == NULL) return FALSE; if (fwupd_remote_get_report_uri(remote) == NULL) { g_debug("%s has no ReportURI", remote_id); continue; } /* add this to the hash map */ devices_tmp = g_hash_table_lookup(report_map, remote_id); if (devices_tmp == NULL) { devices_tmp = g_ptr_array_new(); g_hash_table_insert(report_map, g_strdup(remote_id), devices_tmp); } g_debug("using %s for %s", remote_id, fwupd_device_get_id(dev)); g_ptr_array_add(devices_tmp, dev); } /* nothing to report, but try harder with --force */ if (g_hash_table_size(report_map) == 0) { if (priv->flags & FWUPD_INSTALL_FLAG_FORCE) return fu_util_report_history_force(priv, error); g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No reports require uploading"); return FALSE; } /* process each uri */ ids = g_hash_table_get_keys(report_map); for (GList *l = ids; l != NULL; l = l->next) { const gchar *id = l->data; GPtrArray *devices_tmp = g_hash_table_lookup(report_map, id); if (!fu_util_report_history_for_remote(priv, id, devices_tmp, error)) return FALSE; /* mark each device as reported */ for (guint i = 0; i < devices_tmp->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices_tmp, i); g_debug("setting flag on %s", fwupd_device_get_id(dev)); if (!fwupd_client_modify_device(priv->client, fwupd_device_get_id(dev), "Flags", "reported", priv->cancellable, error)) return FALSE; } } g_string_append_printf(str, /* TRANSLATORS: success message -- where the user has uploaded * success and/or failure reports to the remote server */ ngettext("Successfully uploaded %u report", "Successfully uploaded %u reports", g_hash_table_size(report_map)), g_hash_table_size(report_map)); fu_console_print_literal(priv->console, str->str); return TRUE; } static gboolean fu_util_get_history_as_json(FuUtilPrivate *priv, GPtrArray *devs, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Devices"); json_builder_begin_array(builder); for (guint i = 0; i < devs->len; i++) { FwupdDevice *dev = g_ptr_array_index(devs, i); json_builder_begin_object(builder); fwupd_device_to_json_full(dev, builder, FWUPD_DEVICE_FLAG_TRUSTED); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(priv->console, builder, error); } static gboolean fu_util_get_history(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; g_autoptr(GNode) root = g_node_new(NULL); /* get all devices from the history database */ devices = fwupd_client_get_history(priv->client, priv->cancellable, error); if (devices == NULL) return FALSE; /* not for human consumption */ if (priv->as_json) return fu_util_get_history_as_json(priv, devices, error); /* show each device */ for (guint i = 0; i < devices->len; i++) { g_autoptr(GPtrArray) rels = NULL; FwupdDevice *dev = g_ptr_array_index(devices, i); FwupdRelease *rel; const gchar *remote; GNode *child; g_autoptr(GError) error_local = NULL; if (!fwupd_device_match_flags(dev, priv->filter_device_include, priv->filter_device_exclude)) continue; child = g_node_append_data(root, dev); rel = fwupd_device_get_release_default(dev); if (rel == NULL) continue; remote = fwupd_release_get_remote_id(rel); /* doesn't actually map to remote */ if (remote == NULL) { g_node_append_data(child, rel); continue; } /* try to lookup releases from client */ rels = fwupd_client_get_releases(priv->client, fwupd_device_get_id(dev), priv->cancellable, &error_local); if (rels == NULL) { g_debug("failed to get releases for %s: %s", fwupd_device_get_id(dev), error_local->message); g_node_append_data(child, rel); continue; } /* map to a release in client */ for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel2 = g_ptr_array_index(rels, j); if (!fwupd_release_match_flags(rel2, priv->filter_release_include, priv->filter_release_exclude)) continue; if (g_strcmp0(remote, fwupd_release_get_remote_id(rel2)) != 0) continue; if (g_strcmp0(fwupd_release_get_version(rel), fwupd_release_get_version(rel2)) != 0) continue; g_node_append_data(child, g_object_ref(rel2)); rel = NULL; break; } /* didn't match anything */ if (rels->len == 0 || rel != NULL) { g_node_append_data(child, rel); continue; } } fu_util_print_tree(priv->console, priv->client, root); return TRUE; } static FwupdDevice * fu_util_get_device_by_id(FuUtilPrivate *priv, const gchar *id, GError **error) { if (fwupd_guid_is_valid(id)) { g_autoptr(GPtrArray) devices = NULL; devices = fwupd_client_get_devices_by_guid(priv->client, id, priv->cancellable, error); if (devices == NULL) return NULL; return fu_util_prompt_for_device(priv, devices, error); } /* did this look like a GUID? */ for (guint i = 0; id[i] != '\0'; i++) { if (id[i] == '-') { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return NULL; } } return fwupd_client_get_device_by_id(priv->client, id, priv->cancellable, error); } static FwupdDevice * fu_util_get_device_or_prompt(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; /* get device to use */ if (g_strv_length(values) >= 1) { if (g_strv_length(values) > 1) { for (guint i = 1; i < g_strv_length(values); i++) g_debug("ignoring extra input %s", values[i]); } return fu_util_get_device_by_id(priv, values[0], error); } /* get all devices from daemon */ devices = fwupd_client_get_devices(priv->client, priv->cancellable, error); if (devices == NULL) return NULL; return fu_util_prompt_for_device(priv, devices, error); } static FwupdRelease * fu_util_get_release_for_device_version(FuUtilPrivate *priv, FwupdDevice *device, const gchar *version, GError **error) { g_autoptr(GPtrArray) releases = NULL; /* get all releases */ releases = fwupd_client_get_releases(priv->client, fwupd_device_get_id(device), priv->cancellable, error); if (releases == NULL) return NULL; /* find using vercmp */ for (guint j = 0; j < releases->len; j++) { FwupdRelease *release = g_ptr_array_index(releases, j); if (fu_version_compare(fwupd_release_get_version(release), version, fwupd_device_get_version_format(device)) == 0) { return g_object_ref(release); } } /* did not find */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Unable to locate release %s for %s", version, fwupd_device_get_name(device)); return NULL; } static gboolean fu_util_clear_results(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; dev = fu_util_get_device_or_prompt(priv, values, error); if (dev == NULL) return FALSE; return fwupd_client_clear_results(priv->client, fwupd_device_get_id(dev), priv->cancellable, error); } static gboolean fu_util_verify_update(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; priv->filter_device_include |= FWUPD_DEVICE_FLAG_CAN_VERIFY; dev = fu_util_get_device_or_prompt(priv, values, error); if (dev == NULL) return FALSE; if (!fwupd_client_verify_update(priv->client, fwupd_device_get_id(dev), priv->cancellable, error)) { g_prefix_error(error, "failed to verify update %s: ", fwupd_device_get_name(dev)); return FALSE; } /* TRANSLATORS: success message when user refreshes device checksums */ fu_console_print_literal(priv->console, _("Successfully updated device checksums")); return TRUE; } static gboolean fu_util_download_metadata_enable_lvfs(FuUtilPrivate *priv, GError **error) { g_autoptr(FwupdRemote) remote = NULL; /* is the LVFS available but disabled? */ remote = fwupd_client_get_remote_by_id(priv->client, "lvfs", priv->cancellable, error); if (remote == NULL) return TRUE; fu_console_print_literal( priv->console, /* TRANSLATORS: explain why no metadata available */ _("No remotes are currently enabled so no metadata is available.")); fu_console_print_literal( priv->console, /* TRANSLATORS: explain why no metadata available */ _("Metadata can be obtained from the Linux Vendor Firmware Service.")); /* TRANSLATORS: Turn on the remote */ if (!fu_console_input_bool(priv->console, TRUE, "%s", _("Enable this remote?"))) return TRUE; if (!fwupd_client_modify_remote(priv->client, fwupd_remote_get_id(remote), "Enabled", "true", priv->cancellable, error)) return FALSE; if (!fu_util_modify_remote_warning(priv->console, remote, priv->assume_yes, error)) return FALSE; /* refresh the newly-enabled remote */ return fwupd_client_refresh_remote2(priv->client, remote, priv->download_flags, priv->cancellable, error); } static gboolean fu_util_check_oldest_remote(FuUtilPrivate *priv, guint64 *age_oldest, GError **error) { g_autoptr(GPtrArray) remotes = NULL; gboolean checked = FALSE; /* get the age of the oldest enabled remotes */ remotes = fwupd_client_get_remotes(priv->client, priv->cancellable, error); if (remotes == NULL) return FALSE; for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index(remotes, i); if (!fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)) continue; if (fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD) continue; checked = TRUE; if (!fwupd_remote_needs_refresh(remote)) continue; g_debug("%s is age %u", fwupd_remote_get_id(remote), (guint)fwupd_remote_get_age(remote)); if (fwupd_remote_get_age(remote) > *age_oldest) *age_oldest = fwupd_remote_get_age(remote); } if (!checked) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, /* TRANSLATORS: error message for a user who ran fwupdmgr refresh recently but no remotes */ "No remotes enabled."); return FALSE; } return TRUE; } static gboolean fu_util_download_metadata(FuUtilPrivate *priv, GError **error) { gboolean download_remote_enabled = FALSE; guint devices_supported_cnt = 0; guint refresh_cnt = 0; g_autoptr(GPtrArray) devs = NULL; g_autoptr(GPtrArray) remotes = NULL; g_autoptr(GString) str = g_string_new(NULL); remotes = fwupd_client_get_remotes(priv->client, priv->cancellable, error); if (remotes == NULL) return FALSE; for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index(remotes, i); if (!fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)) continue; if (fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD) continue; download_remote_enabled = TRUE; if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && !fwupd_remote_needs_refresh(remote)) { g_debug("skipping as remote %s age is %us", fwupd_remote_get_id(remote), (guint)fwupd_remote_get_age(remote)); continue; } fu_console_print(priv->console, "%s %s", _("Updating"), fwupd_remote_get_id(remote)); if (!fwupd_client_refresh_remote2(priv->client, remote, priv->download_flags, priv->cancellable, error)) return FALSE; refresh_cnt++; } /* no web remote is declared; try to enable LVFS */ if (!download_remote_enabled) { /* we don't want to ask anything */ if (priv->no_remote_check) { g_debug("skipping remote check"); return TRUE; } if (!fu_util_download_metadata_enable_lvfs(priv, error)) return FALSE; } /* metadata refreshed recently */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && refresh_cnt == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, /* TRANSLATORS: error message for a user who ran fwupdmgr * refresh recently */ _("Metadata is up to date; use --force to refresh again.")); return FALSE; } /* get devices from daemon */ devs = fwupd_client_get_devices(priv->client, priv->cancellable, error); if (devs == NULL) return FALSE; /* get results */ for (guint i = 0; i < devs->len; i++) { FwupdDevice *dev = g_ptr_array_index(devs, i); if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) devices_supported_cnt++; } /* TRANSLATORS: success message -- where 'metadata' is information * about available firmware on the remote server */ g_string_append(str, _("Successfully downloaded new metadata: ")); g_string_append_printf(str, /* TRANSLATORS: how many local devices can expect updates now */ ngettext("%u local device supported", "%u local devices supported", devices_supported_cnt), devices_supported_cnt); fu_console_print_literal(priv->console, str->str); return TRUE; } static gboolean fu_util_refresh(FuUtilPrivate *priv, gchar **values, GError **error) { if (g_strv_length(values) == 0) return fu_util_download_metadata(priv, error); if (g_strv_length(values) != 3) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* open file */ if (!fwupd_client_update_metadata(priv->client, values[2], values[0], values[1], priv->cancellable, error)) return FALSE; /* TRANSLATORS: success message -- the user can do this by-hand too */ fu_console_print_literal(priv->console, _("Successfully refreshed metadata manually")); return TRUE; } static gboolean fu_util_get_results_as_json(FuUtilPrivate *priv, FwupdDevice *res, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); fwupd_device_to_json_full(res, builder, FWUPD_DEVICE_FLAG_TRUSTED); json_builder_end_object(builder); return fu_util_print_builder(priv->console, builder, error); } static gboolean fu_util_get_results(FuUtilPrivate *priv, gchar **values, GError **error) { g_autofree gchar *tmp = NULL; g_autoptr(FwupdDevice) dev = NULL; g_autoptr(FwupdDevice) rel = NULL; dev = fu_util_get_device_or_prompt(priv, values, error); if (dev == NULL) return FALSE; rel = fwupd_client_get_results(priv->client, fwupd_device_get_id(dev), priv->cancellable, error); if (rel == NULL) return FALSE; if (priv->as_json) return fu_util_get_results_as_json(priv, rel, error); tmp = fu_util_device_to_string(priv->client, rel, 0); fu_console_print_literal(priv->console, tmp); return TRUE; } static gboolean fu_util_get_releases(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; g_autoptr(GPtrArray) rels = NULL; priv->filter_device_include |= FWUPD_DEVICE_FLAG_SUPPORTED; dev = fu_util_get_device_or_prompt(priv, values, error); if (dev == NULL) return FALSE; /* get the releases for this device */ rels = fwupd_client_get_releases(priv->client, fwupd_device_get_id(dev), priv->cancellable, error); if (rels == NULL) return FALSE; /* not for human consumption */ if (priv->as_json) return fu_util_get_releases_as_json(priv, rels, error); if (rels->len == 0) { /* TRANSLATORS: no repositories to download from */ fu_console_print_literal(priv->console, _("No releases available")); return TRUE; } if (g_getenv("FWUPD_VERBOSE") != NULL) { for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel = g_ptr_array_index(rels, i); g_autofree gchar *tmp = NULL; if (!fwupd_release_match_flags(rel, priv->filter_release_include, priv->filter_release_exclude)) continue; tmp = fwupd_release_to_string(rel); fu_console_print_literal(priv->console, tmp); } } else { g_autoptr(GNode) root = g_node_new(NULL); for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel = g_ptr_array_index(rels, i); if (!fwupd_release_match_flags(rel, priv->filter_release_include, priv->filter_release_exclude)) continue; g_node_append_data(root, rel); } fu_util_print_tree(priv->console, priv->client, root); } return TRUE; } static FwupdRelease * fu_util_prompt_for_release(FuUtilPrivate *priv, GPtrArray *rels_unfiltered, GError **error) { FwupdRelease *rel; guint idx; g_autoptr(GPtrArray) rels = NULL; /* filter */ rels = fwupd_release_array_filter_flags(rels_unfiltered, priv->filter_release_include, priv->filter_release_exclude, error); if (rels == NULL) return NULL; /* exactly one */ if (rels->len == 1) { rel = g_ptr_array_index(rels, 0); return g_object_ref(rel); } /* TRANSLATORS: this is to abort the interactive prompt */ fu_console_print(priv->console, "0.\t%s", _("Cancel")); for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel_tmp = g_ptr_array_index(rels, i); fu_console_print(priv->console, "%u.\t%s", i + 1, fwupd_release_get_version(rel_tmp)); } /* TRANSLATORS: get interactive prompt */ idx = fu_console_input_uint(priv->console, rels->len, "%s", _("Choose release")); if (idx == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return NULL; } rel = g_ptr_array_index(rels, idx - 1); return g_object_ref(rel); } static gboolean fu_util_verify(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; priv->filter_device_include |= FWUPD_DEVICE_FLAG_CAN_VERIFY; dev = fu_util_get_device_or_prompt(priv, values, error); if (dev == NULL) return FALSE; if (!fwupd_client_verify(priv->client, fwupd_device_get_id(dev), priv->cancellable, error)) { g_prefix_error(error, "failed to verify %s: ", fwupd_device_get_name(dev)); return FALSE; } /* TRANSLATORS: success message when user verified device checksums */ fu_console_print_literal(priv->console, _("Successfully verified device checksums")); return TRUE; } static gboolean fu_util_unlock(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; priv->filter_device_include |= FWUPD_DEVICE_FLAG_LOCKED; dev = fu_util_get_device_or_prompt(priv, values, error); if (dev == NULL) return FALSE; if (!fwupd_client_unlock(priv->client, fwupd_device_get_id(dev), priv->cancellable, error)) return FALSE; /* check flags after unlocking in case the operation changes them */ if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN)) priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_SHUTDOWN; if (fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)) priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_perhaps_refresh_remotes(FuUtilPrivate *priv, GError **error) { guint64 age_oldest = 0; const guint64 age_limit_days = 30; /* we don't want to ask anything */ if (priv->no_metadata_check) { g_debug("skipping metadata check"); return TRUE; } if (!fu_util_check_oldest_remote(priv, &age_oldest, NULL)) return TRUE; /* metadata is new enough */ if (age_oldest < 60 * 60 * 24 * age_limit_days) return TRUE; /* ask for permission */ if (!priv->assume_yes) { fu_console_print( priv->console, /* TRANSLATORS: the metadata is very out of date; %u is a number > 1 */ ngettext("Firmware metadata has not been updated for %u" " day and may not be up to date.", "Firmware metadata has not been updated for %u" " days and may not be up to date.", (gint)age_limit_days), (guint)age_limit_days); if (!fu_console_input_bool(priv->console, FALSE, "%s (%s)", /* TRANSLATORS: ask if we can update metadata */ _("Update now?"), /* TRANSLATORS: metadata is downloaded */ _("Requires internet connection"))) return TRUE; } /* downloads new metadata */ return fu_util_download_metadata(priv, error); } static gboolean fu_util_get_updates_as_json(FuUtilPrivate *priv, GPtrArray *devices, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Devices"); json_builder_begin_array(builder); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); g_autoptr(GPtrArray) rels = NULL; g_autoptr(GError) error_local = NULL; /* not going to have results, so save a D-Bus round-trip */ if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) continue; /* get the releases for this device and filter for validity */ rels = fwupd_client_get_upgrades(priv->client, fwupd_device_get_id(dev), priv->cancellable, &error_local); if (rels == NULL) { g_debug("no upgrades: %s", error_local->message); continue; } for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel = g_ptr_array_index(rels, j); if (!fwupd_release_match_flags(rel, priv->filter_release_include, priv->filter_release_exclude)) continue; fwupd_device_add_release(dev, rel); } /* add to builder */ json_builder_begin_object(builder); fwupd_device_to_json_full(dev, builder, FWUPD_DEVICE_FLAG_TRUSTED); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(priv->console, builder, error); } static gboolean fu_util_get_updates(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; gboolean supported = FALSE; g_autoptr(GNode) root = g_node_new(NULL); g_autoptr(GPtrArray) devices_no_support = g_ptr_array_new(); g_autoptr(GPtrArray) devices_no_upgrades = g_ptr_array_new(); /* are the remotes very old */ if (!fu_util_perhaps_refresh_remotes(priv, error)) return FALSE; /* handle both forms */ if (g_strv_length(values) == 0) { devices = fwupd_client_get_devices(priv->client, priv->cancellable, error); if (devices == NULL) return FALSE; } else if (g_strv_length(values) == 1) { FwupdDevice *device = fu_util_get_device_by_id(priv, values[0], error); if (device == NULL) return FALSE; devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_ptr_array_add(devices, device); } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } g_ptr_array_sort(devices, fu_util_sort_devices_by_flags_cb); /* not for human consumption */ if (priv->as_json) return fu_util_get_updates_as_json(priv, devices, error); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); g_autoptr(GPtrArray) rels = NULL; g_autoptr(GError) error_local = NULL; GNode *child; /* not going to have results, so save a D-Bus round-trip */ if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE) && !fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)) continue; if (!fwupd_device_match_flags(dev, priv->filter_device_include, priv->filter_device_exclude)) continue; if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { g_ptr_array_add(devices_no_support, dev); continue; } supported = TRUE; /* get the releases for this device and filter for validity */ rels = fwupd_client_get_upgrades(priv->client, fwupd_device_get_id(dev), priv->cancellable, &error_local); if (rels == NULL) { g_ptr_array_add(devices_no_upgrades, dev); /* discard the actual reason from user, but leave for debugging */ g_debug("%s", error_local->message); continue; } child = g_node_append_data(root, dev); /* add all releases */ for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel = g_ptr_array_index(rels, j); if (!fwupd_release_match_flags(rel, priv->filter_release_include, priv->filter_release_exclude)) continue; g_node_append_data(child, g_object_ref(rel)); } } /* devices that have no updates available for whatever reason */ if (devices_no_support->len > 0) { fu_console_print_literal(priv->console, /* TRANSLATORS: message letting the user know no device * upgrade available due to missing on LVFS */ _("Devices with no available firmware updates: ")); for (guint i = 0; i < devices_no_support->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices_no_support, i); fu_console_print(priv->console, " • %s", fwupd_device_get_name(dev)); } } if (devices_no_upgrades->len > 0) { fu_console_print_literal( priv->console, /* TRANSLATORS: message letting the user know no device upgrade available */ _("Devices with the latest available firmware version:")); for (guint i = 0; i < devices_no_upgrades->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices_no_upgrades, i); fu_console_print(priv->console, " • %s", fwupd_device_get_name(dev)); } } /* nag? */ if (!fu_util_perhaps_show_unreported(priv, error)) return FALSE; /* no devices supported by LVFS or all are filtered */ if (!supported) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, /* TRANSLATORS: this is an error string */ _("No updatable devices")); return FALSE; } /* no updates available */ if (g_node_n_nodes(root, G_TRAVERSE_ALL) <= 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, /* TRANSLATORS: this is an error string */ _("No updates available")); return FALSE; } fu_util_print_tree(priv->console, priv->client, root); /* success */ return TRUE; } static gboolean fu_util_get_remotes_as_json(FuUtilPrivate *priv, GPtrArray *remotes, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Remotes"); json_builder_begin_array(builder); for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index(remotes, i); json_builder_begin_object(builder); fwupd_remote_to_json(remote, builder); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(priv->console, builder, error); } static gboolean fu_util_get_remotes(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GNode) root = g_node_new(NULL); g_autoptr(GPtrArray) remotes = NULL; remotes = fwupd_client_get_remotes(priv->client, priv->cancellable, error); if (remotes == NULL) return FALSE; if (priv->as_json) return fu_util_get_remotes_as_json(priv, remotes, error); if (remotes->len == 0) { /* TRANSLATORS: no repositories to download from */ fu_console_print_literal(priv->console, _("No remotes available")); return TRUE; } for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote_tmp = g_ptr_array_index(remotes, i); g_node_append_data(root, remote_tmp); } fu_util_print_tree(priv->console, priv->client, root); return TRUE; } static FwupdRelease * fu_util_get_release_with_tag(FuUtilPrivate *priv, FwupdDevice *dev, const gchar *host_bkc, GError **error) { g_autoptr(GPtrArray) rels = NULL; g_auto(GStrv) host_bkcs = g_strsplit(host_bkc, ",", -1); /* find the newest release that matches */ rels = fwupd_client_get_releases(priv->client, fwupd_device_get_id(dev), priv->cancellable, error); if (rels == NULL) return NULL; for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel = g_ptr_array_index(rels, i); if (!fwupd_release_match_flags(rel, priv->filter_release_include, priv->filter_release_exclude)) continue; for (guint j = 0; host_bkcs[j] != NULL; j++) { if (fwupd_release_has_tag(rel, host_bkcs[j])) return g_object_ref(rel); } } /* no match */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no matching releases for device"); return NULL; } static FwupdRelease * fu_util_get_release_with_branch(FuUtilPrivate *priv, FwupdDevice *dev, const gchar *branch, GError **error) { g_autoptr(GPtrArray) rels = NULL; /* find the newest release that matches */ rels = fwupd_client_get_releases(priv->client, fwupd_device_get_id(dev), priv->cancellable, error); if (rels == NULL) return NULL; for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel = g_ptr_array_index(rels, i); if (!fwupd_release_match_flags(rel, priv->filter_release_include, priv->filter_release_exclude)) continue; if (g_strcmp0(branch, fwupd_release_get_branch(rel)) == 0) return g_object_ref(rel); } /* no match */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no matching releases for device"); return NULL; } static gboolean fu_util_prompt_warning_bkc(FuUtilPrivate *priv, FwupdDevice *dev, FwupdRelease *rel, GError **error) { const gchar *host_bkc = fwupd_client_get_host_bkc(priv->client); g_autofree gchar *cmd = g_strdup_printf("%s sync", g_get_prgname()); g_autoptr(FwupdRelease) rel_bkc = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GString) str = g_string_new(NULL); /* nothing to do */ if (host_bkc == NULL) return TRUE; /* get the release that corresponds with the host BKC */ rel_bkc = fu_util_get_release_with_tag(priv, dev, host_bkc, &error_local); if (rel_bkc == NULL) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED) || g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_debug("ignoring %s: %s", fwupd_device_get_id(dev), error_local->message); return TRUE; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* device is already on a different release */ if (g_strcmp0(fwupd_device_get_version(dev), fwupd_release_get_version(rel)) != 0) return TRUE; /* TRANSLATORS: BKC is the industry name for the best known configuration and is a set * of firmware that works together */ g_string_append_printf(str, _("Your system is set up to the BKC of %s."), host_bkc); g_string_append(str, "\n\n"); g_string_append_printf( str, /* TRANSLATORS: %1 is the current device version number, and %2 is the command name, e.g. `fwupdmgr sync` */ _("This device will be reverted back to %s when the %s command is performed."), fwupd_release_get_version(rel), cmd); fu_console_box( priv->console, /* TRANSLATORS: the best known configuration is a set of software that we know works * well together. In the OEM and ODM industries it is often called a BKC */ _("Deviate from the best known configuration?"), str->str, 80); /* TRANSLATORS: prompt to apply the update */ if (!fu_console_input_bool(priv->console, TRUE, "%s", _("Perform operation?"))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return FALSE; } /* success */ return TRUE; } static gboolean fu_util_prompt_warning_composite(FuUtilPrivate *priv, FwupdDevice *dev, FwupdRelease *rel, GError **error) { const gchar *rel_csum; g_autoptr(GPtrArray) devices = NULL; /* get the default checksum */ rel_csum = fwupd_checksum_get_best(fwupd_release_get_checksums(rel)); if (rel_csum == NULL) { g_debug("no checksum for release!"); return TRUE; } /* find other devices matching the composite ID and the release checksum */ devices = fwupd_client_get_devices(priv->client, priv->cancellable, error); if (devices == NULL) return FALSE; for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev_tmp = g_ptr_array_index(devices, i); g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) rels = NULL; /* not the parent device */ if (g_strcmp0(fwupd_device_get_id(dev), fwupd_device_get_id(dev_tmp)) == 0) continue; /* not the same composite device */ if (g_strcmp0(fwupd_device_get_composite_id(dev), fwupd_device_get_composite_id(dev_tmp)) != 0) continue; /* get releases */ if (!fwupd_device_has_flag(dev_tmp, FWUPD_DEVICE_FLAG_UPDATABLE)) continue; rels = fwupd_client_get_releases(priv->client, fwupd_device_get_id(dev_tmp), priv->cancellable, &error_local); if (rels == NULL) { g_debug("ignoring: %s", error_local->message); continue; } /* do any releases match this checksum */ for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel_tmp = g_ptr_array_index(rels, j); if (fwupd_release_has_checksum(rel_tmp, rel_csum)) { g_autofree gchar *title = g_strdup_printf("%s %s", fwupd_client_get_host_product(priv->client), fwupd_client_get_host_product(priv->client)); if (!fu_util_prompt_warning(priv->console, dev_tmp, rel_tmp, title, error)) return FALSE; break; } } } /* success */ return TRUE; } static gboolean fu_util_update_device_with_release(FuUtilPrivate *priv, FwupdDevice *dev, FwupdRelease *rel, GError **error) { if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE)) { const gchar *name = fwupd_device_get_name(dev); g_autofree gchar *str = NULL; /* TRANSLATORS: the device has a reason it can't update, e.g. laptop lid closed */ str = g_strdup_printf(_("%s is not currently updatable"), name); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "%s: %s", str, fwupd_device_get_update_error(dev)); return FALSE; } if (!priv->no_safety_check && !priv->assume_yes) { const gchar *title = fwupd_client_get_host_product(priv->client); if (!fu_util_prompt_warning(priv->console, dev, rel, title, error)) return FALSE; if (!fu_util_prompt_warning_fde(priv->console, dev, error)) return FALSE; if (!fu_util_prompt_warning_composite(priv, dev, rel, error)) return FALSE; if (!fu_util_prompt_warning_bkc(priv, dev, rel, error)) return FALSE; } return fwupd_client_install_release2(priv->client, dev, rel, priv->flags, priv->download_flags, priv->cancellable, error); } static gboolean fu_util_maybe_send_reports(FuUtilPrivate *priv, FwupdRelease *rel, GError **error) { g_autoptr(FwupdRemote) remote = NULL; g_autoptr(GError) error_local = NULL; if (fwupd_release_get_remote_id(rel) == NULL) { g_debug("not sending reports, no remote"); return TRUE; } remote = fwupd_client_get_remote_by_id(priv->client, fwupd_release_get_remote_id(rel), priv->cancellable, error); if (remote == NULL) return FALSE; if (fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_REPORTS)) { if (!fu_util_report_history(priv, NULL, &error_local)) if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) g_warning("%s", error_local->message); } return TRUE; } static gboolean fu_util_update(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; gboolean supported = FALSE; gboolean no_updates_header = FALSE; gboolean latest_header = FALSE; if (priv->flags & FWUPD_INSTALL_FLAG_ALLOW_OLDER) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "--allow-older is not supported for this command"); return FALSE; } if (priv->flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "--allow-reinstall is not supported for this command"); return FALSE; } /* DEVICE-ID and GUID are acceptable args to update */ for (guint idx = 0; idx < g_strv_length(values); idx++) { if (!fwupd_guid_is_valid(values[idx]) && !fwupd_device_id_is_valid(values[idx])) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "'%s' is not a valid GUID nor DEVICE-ID", values[idx]); return FALSE; } } /* get devices from daemon */ devices = fwupd_client_get_devices(priv->client, priv->cancellable, error); if (devices == NULL) return FALSE; priv->current_operation = FU_UTIL_OPERATION_UPDATE; g_ptr_array_sort(devices, fu_util_sort_devices_by_flags_cb); for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); const gchar *device_id = fwupd_device_get_id(dev); g_autoptr(FwupdRelease) rel = NULL; g_autoptr(GPtrArray) rels = NULL; g_autoptr(GError) error_local = NULL; gboolean dev_skip_byid = TRUE; /* not going to have results, so save a D-Bus round-trip */ if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE) && !fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)) continue; if (!fwupd_device_has_flag(dev, FWUPD_DEVICE_FLAG_SUPPORTED)) { if (!no_updates_header) { fu_console_print_literal( priv->console, /* TRANSLATORS: message letting the user know no * device upgrade available due to missing on LVFS */ _("Devices with no available firmware updates: ")); no_updates_header = TRUE; } fu_console_print(priv->console, " • %s", fwupd_device_get_name(dev)); continue; } /* only process particular DEVICE-ID or GUID if specified */ for (guint idx = 0; idx < g_strv_length(values); idx++) { const gchar *tmpid = values[idx]; if (fwupd_device_has_guid(dev, tmpid) || g_strcmp0(device_id, tmpid) == 0) { dev_skip_byid = FALSE; break; } } if (g_strv_length(values) > 0 && dev_skip_byid) continue; if (!fwupd_device_match_flags(dev, priv->filter_device_include, priv->filter_device_exclude)) continue; supported = TRUE; /* get the releases for this device and filter for validity */ rels = fwupd_client_get_upgrades(priv->client, fwupd_device_get_id(dev), priv->cancellable, &error_local); if (rels == NULL) { if (!latest_header) { fu_console_print_literal( priv->console, /* TRANSLATORS: message letting the user know no device upgrade * available */ _("Devices with the latest available firmware version:")); latest_header = TRUE; } fu_console_print(priv->console, " • %s", fwupd_device_get_name(dev)); /* discard the actual reason from user, but leave for debugging */ g_debug("%s", error_local->message); continue; } for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel_tmp = g_ptr_array_index(rels, j); if (!fwupd_release_match_flags(rel_tmp, priv->filter_release_include, priv->filter_release_exclude)) continue; rel = g_object_ref(rel_tmp); break; } if (rel == NULL) continue; if (!fu_util_update_device_with_release(priv, dev, rel, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_debug("ignoring %s: %s", fwupd_device_get_id(dev), error_local->message); continue; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } fu_util_display_current_message(priv); /* send report if we're supposed to */ if (!fu_util_maybe_send_reports(priv, rel, error)) return FALSE; } /* no devices supported by LVFS or all are filtered */ if (!supported) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No updatable devices"); return FALSE; } /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug("skipping reboot check"); return TRUE; } return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_remote_modify(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdRemote) remote = NULL; if (g_strv_length(values) < 3) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* ensure the remote exists */ remote = fwupd_client_get_remote_by_id(priv->client, values[0], priv->cancellable, error); if (remote == NULL) return FALSE; if (!fwupd_client_modify_remote(priv->client, fwupd_remote_get_id(remote), values[1], values[2], priv->cancellable, error)) return FALSE; /* TRANSLATORS: success message for a per-remote setting change */ fu_console_print_literal(priv->console, _("Successfully modified remote")); return TRUE; } static gboolean fu_util_remote_enable(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdRemote) remote = NULL; if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } remote = fwupd_client_get_remote_by_id(priv->client, values[0], priv->cancellable, error); if (remote == NULL) return FALSE; if (!fu_util_modify_remote_warning(priv->console, remote, priv->assume_yes, error)) return FALSE; if (!fwupd_client_modify_remote(priv->client, fwupd_remote_get_id(remote), "Enabled", "true", priv->cancellable, error)) return FALSE; /* ask for permission to refresh */ if (priv->no_remote_check || fwupd_remote_get_kind(remote) != FWUPD_REMOTE_KIND_DOWNLOAD) { /* TRANSLATORS: success message */ fu_console_print_literal(priv->console, _("Successfully enabled remote")); return TRUE; } if (!priv->assume_yes) { if (!fu_console_input_bool(priv->console, TRUE, "%s (%s)", /* TRANSLATORS: ask if we can update the metadata */ _("Do you want to refresh this remote now?"), /* TRANSLATORS: metadata is downloaded */ _("Requires internet connection"))) { /* TRANSLATORS: success message */ fu_console_print_literal(priv->console, _("Successfully enabled remote")); return TRUE; } } if (!fwupd_client_refresh_remote2(priv->client, remote, priv->download_flags, priv->cancellable, error)) return FALSE; /* TRANSLATORS: success message */ fu_console_print_literal(priv->console, _("Successfully enabled and refreshed remote")); return TRUE; } static gboolean fu_util_remote_disable(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdRemote) remote = NULL; if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* ensure the remote exists */ remote = fwupd_client_get_remote_by_id(priv->client, values[0], priv->cancellable, error); if (remote == NULL) return FALSE; if (!fwupd_client_modify_remote(priv->client, values[0], "Enabled", "false", priv->cancellable, error)) return FALSE; /* TRANSLATORS: success message */ fu_console_print_literal(priv->console, _("Successfully disabled remote")); return TRUE; } static gboolean fu_util_downgrade(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; g_autoptr(FwupdRelease) rel = NULL; g_autoptr(GPtrArray) rels = NULL; if (priv->flags & FWUPD_INSTALL_FLAG_ALLOW_REINSTALL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "--allow-reinstall is not supported for this command"); return FALSE; } priv->filter_device_include |= FWUPD_DEVICE_FLAG_SUPPORTED; dev = fu_util_get_device_or_prompt(priv, values, error); if (dev == NULL) return FALSE; /* get the releases for this device and filter for validity */ rels = fwupd_client_get_downgrades(priv->client, fwupd_device_get_id(dev), priv->cancellable, error); if (rels == NULL) { g_autofree gchar *downgrade_str = /* TRANSLATORS: message letting the user know no device downgrade available * %1 is the device name */ g_strdup_printf(_("No downgrades for %s"), fwupd_device_get_name(dev)); g_prefix_error(error, "%s: ", downgrade_str); return FALSE; } /* get the chosen release */ rel = fu_util_prompt_for_release(priv, rels, error); if (rel == NULL) return FALSE; /* update the console if composite devices are also updated */ priv->current_operation = FU_UTIL_OPERATION_DOWNGRADE; priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; if (!fu_util_update_device_with_release(priv, dev, rel, error)) return FALSE; fu_util_display_current_message(priv); /* send report if we're supposed to */ if (!fu_util_maybe_send_reports(priv, rel, error)) return FALSE; /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug("skipping reboot check"); return TRUE; } return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_reinstall(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdRelease) rel = NULL; g_autoptr(FwupdDevice) dev = NULL; priv->filter_device_include |= FWUPD_DEVICE_FLAG_SUPPORTED; dev = fu_util_get_device_or_prompt(priv, values, error); if (dev == NULL) return FALSE; /* try to lookup/match release from client */ rel = fu_util_get_release_for_device_version(priv, dev, fwupd_device_get_version(dev), error); if (rel == NULL) return FALSE; /* update the console if composite devices are also updated */ priv->current_operation = FU_UTIL_OPERATION_INSTALL; priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; if (!fu_util_update_device_with_release(priv, dev, rel, error)) return FALSE; fu_util_display_current_message(priv); /* send report if we're supposed to */ if (!fu_util_maybe_send_reports(priv, rel, error)) return FALSE; /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug("skipping reboot check"); return TRUE; } return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_install(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; g_autoptr(FwupdRelease) rel = NULL; /* fall back for CLI compatibility */ if (g_strv_length(values) >= 1) { if (g_file_test(values[0], G_FILE_TEST_EXISTS) || fu_util_is_url(values[0])) return fu_util_local_install(priv, values, error); } /* find device */ priv->filter_device_include |= FWUPD_DEVICE_FLAG_SUPPORTED; dev = fu_util_get_device_or_prompt(priv, values, error); if (dev == NULL) return FALSE; /* find release */ if (g_strv_length(values) >= 2) { rel = fu_util_get_release_for_device_version(priv, dev, values[1], error); if (rel == NULL) return FALSE; } else { g_autoptr(GPtrArray) rels = NULL; rels = fwupd_client_get_releases(priv->client, fwupd_device_get_id(dev), priv->cancellable, error); if (rels == NULL) return FALSE; rel = fu_util_prompt_for_release(priv, rels, error); if (rel == NULL) return FALSE; } /* allow all actions */ priv->current_operation = FU_UTIL_OPERATION_INSTALL; priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; if (!fu_util_update_device_with_release(priv, dev, rel, error)) return FALSE; fu_util_display_current_message(priv); /* send report if we're supposed to */ if (!fu_util_maybe_send_reports(priv, rel, error)) return FALSE; /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug("skipping reboot check"); return TRUE; } return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean _g_str_equal0(gconstpointer str1, gconstpointer str2) { return g_strcmp0(str1, str2) == 0; } static gboolean fu_util_switch_branch(FuUtilPrivate *priv, gchar **values, GError **error) { const gchar *branch; g_autoptr(FwupdRelease) rel = NULL; g_autoptr(GPtrArray) rels = NULL; g_autoptr(GPtrArray) branches = g_ptr_array_new_with_free_func(g_free); g_autoptr(FwupdDevice) dev = NULL; /* find the device and check it has multiple branches */ priv->filter_device_include |= FWUPD_DEVICE_FLAG_HAS_MULTIPLE_BRANCHES; priv->filter_device_include |= FWUPD_DEVICE_FLAG_UPDATABLE; dev = fu_util_get_device_or_prompt(priv, values, error); if (dev == NULL) return FALSE; /* get all releases, including the alternate branch versions */ rels = fwupd_client_get_releases(priv->client, fwupd_device_get_id(dev), priv->cancellable, error); if (rels == NULL) return FALSE; /* get all the unique branches */ for (guint i = 0; i < rels->len; i++) { FwupdRelease *rel_tmp = g_ptr_array_index(rels, i); const gchar *branch_tmp = fwupd_release_get_branch(rel_tmp); if (!fwupd_release_match_flags(rel_tmp, priv->filter_release_include, priv->filter_release_exclude)) continue; if (g_ptr_array_find_with_equal_func(branches, branch_tmp, _g_str_equal0, NULL)) continue; g_ptr_array_add(branches, g_strdup(branch_tmp)); } /* branch name is optional */ if (g_strv_length(values) > 1) { branch = values[1]; } else if (branches->len == 1) { branch = g_ptr_array_index(branches, 0); } else { guint idx; /* TRANSLATORS: this is to abort the interactive prompt */ fu_console_print(priv->console, "0.\t%s", _("Cancel")); for (guint i = 0; i < branches->len; i++) { const gchar *branch_tmp = g_ptr_array_index(branches, i); fu_console_print(priv->console, "%u.\t%s", i + 1, fu_util_branch_for_display(branch_tmp)); } /* TRANSLATORS: get interactive prompt, where branch is the * supplier of the firmware, e.g. "non-free" or "free" */ idx = fu_console_input_uint(priv->console, branches->len, "%s", _("Choose branch")); if (idx == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "Request canceled"); return FALSE; } branch = g_ptr_array_index(branches, idx - 1); } /* sanity check */ if (g_strcmp0(branch, fwupd_device_get_branch(dev)) == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "Device %s is already on branch %s", fwupd_device_get_name(dev), fu_util_branch_for_display(branch)); return FALSE; } /* the releases are ordered by version */ for (guint j = 0; j < rels->len; j++) { FwupdRelease *rel_tmp = g_ptr_array_index(rels, j); if (g_strcmp0(fwupd_release_get_branch(rel_tmp), branch) == 0) { rel = g_object_ref(rel_tmp); break; } } if (rel == NULL) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No releases for branch %s", fu_util_branch_for_display(branch)); return FALSE; } /* we're switching branch */ if (!fu_util_switch_branch_warning(priv->console, dev, rel, priv->assume_yes, error)) return FALSE; /* update the console if composite devices are also updated */ priv->current_operation = FU_UTIL_OPERATION_INSTALL; priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH; if (!fu_util_update_device_with_release(priv, dev, rel, error)) return FALSE; fu_util_display_current_message(priv); /* send report if we're supposed to */ if (!fu_util_maybe_send_reports(priv, rel, error)) return FALSE; /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug("skipping reboot check"); return TRUE; } return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_activate(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) devices = NULL; gboolean has_pending = FALSE; /* handle both forms */ if (g_strv_length(values) == 0) { /* activate anything with _NEEDS_ACTIVATION */ devices = fwupd_client_get_devices(priv->client, priv->cancellable, error); if (devices == NULL) return FALSE; for (guint i = 0; i < devices->len; i++) { FwupdDevice *device = g_ptr_array_index(devices, i); if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) { has_pending = TRUE; break; } } } else if (g_strv_length(values) == 1) { FwupdDevice *device = fu_util_get_device_by_id(priv, values[0], error); if (device == NULL) return FALSE; devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_ptr_array_add(devices, device); if (fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) has_pending = TRUE; } else { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments"); return FALSE; } /* nothing to do */ if (!has_pending) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No firmware to activate"); return FALSE; } /* activate anything with _NEEDS_ACTIVATION */ for (guint i = 0; i < devices->len; i++) { FwupdDevice *device = g_ptr_array_index(devices, i); if (!fwupd_device_match_flags(device, priv->filter_device_include, priv->filter_device_exclude)) continue; if (!fwupd_device_has_flag(device, FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)) continue; fu_console_print( priv->console, "%s %s…", /* TRANSLATORS: shown when shutting down to switch to the new version */ _("Activating firmware update for"), fwupd_device_get_name(device)); if (!fwupd_client_activate(priv->client, priv->cancellable, fwupd_device_get_id(device), error)) return FALSE; } /* TRANSLATORS: success message -- where activation is making the new * firmware take effect, usually after updating offline */ fu_console_print_literal(priv->console, _("Successfully activated all devices")); return TRUE; } static gboolean fu_util_set_approved_firmware(FuUtilPrivate *priv, gchar **values, GError **error) { g_auto(GStrv) checksums = NULL; /* check args */ if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: filename or list of checksums expected"); return FALSE; } /* filename */ if (g_file_test(values[0], G_FILE_TEST_EXISTS)) { g_autofree gchar *data = NULL; if (!g_file_get_contents(values[0], &data, NULL, error)) return FALSE; checksums = g_strsplit(data, "\n", -1); } else { checksums = g_strsplit(values[0], ",", -1); } /* call into daemon */ return fwupd_client_set_approved_firmware(priv->client, checksums, priv->cancellable, error); } static gboolean fu_util_get_checksums_as_json(FuUtilPrivate *priv, gchar **csums, GError **error) { g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "Checksums"); json_builder_begin_array(builder); for (guint i = 0; csums[i] != NULL; i++) json_builder_add_string_value(builder, csums[i]); json_builder_end_array(builder); json_builder_end_object(builder); return fu_util_print_builder(priv->console, builder, error); } static gboolean fu_util_get_approved_firmware(FuUtilPrivate *priv, gchar **values, GError **error) { g_auto(GStrv) checksums = NULL; /* check args */ if (g_strv_length(values) != 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: none expected"); return FALSE; } /* call into daemon */ checksums = fwupd_client_get_approved_firmware(priv->client, priv->cancellable, error); if (checksums == NULL) return FALSE; if (priv->as_json) return fu_util_get_checksums_as_json(priv, checksums, error); if (g_strv_length(checksums) == 0) { /* TRANSLATORS: approved firmware has been checked by * the domain administrator */ fu_console_print_literal(priv->console, _("There is no approved firmware.")); } else { fu_console_print_literal( priv->console, /* TRANSLATORS: approved firmware has been checked by * the domain administrator */ ngettext("Approved firmware:", "Approved firmware:", g_strv_length(checksums))); for (guint i = 0; checksums[i] != NULL; i++) fu_console_print(priv->console, " * %s", checksums[i]); } return TRUE; } static gboolean fu_util_modify_config(FuUtilPrivate *priv, gchar **values, GError **error) { /* check args */ if (g_strv_length(values) != 2) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments: KEY VALUE expected"); return FALSE; } if (!fwupd_client_modify_config(priv->client, values[0], values[1], priv->cancellable, error)) return FALSE; if (!priv->assume_yes) { if (!fu_console_input_bool(priv->console, FALSE, "%s", /* TRANSLATORS: changes only take effect on restart */ _("Restart the daemon to make the change effective?"))) return TRUE; } if (!fu_util_quit(priv, NULL, error)) return FALSE; if (!fwupd_client_connect(priv->client, priv->cancellable, error)) return FALSE; /* TRANSLATORS: success message -- a per-system setting value */ fu_console_print_literal(priv->console, _("Successfully modified configuration value")); return TRUE; } static FwupdRemote * fu_util_get_remote_with_security_report_uri(FuUtilPrivate *priv, GError **error) { g_autoptr(GPtrArray) remotes = NULL; /* get all remotes */ remotes = fwupd_client_get_remotes(priv->client, priv->cancellable, error); if (remotes == NULL) return NULL; for (guint i = 0; i < remotes->len; i++) { FwupdRemote *remote = g_ptr_array_index(remotes, i); if (!fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_ENABLED)) continue; if (fwupd_remote_get_security_report_uri(remote) != NULL) return g_object_ref(remote); } /* failed */ g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No remotes specified SecurityReportURI"); return NULL; } static gboolean fu_util_upload_security(FuUtilPrivate *priv, GPtrArray *attrs, GError **error) { GHashTableIter iter; const gchar *key; const gchar *value; g_autofree gchar *data = NULL; g_autofree gchar *sig = NULL; g_autoptr(FwupdRemote) remote = NULL; g_autoptr(GBytes) upload_response = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GHashTable) metadata = NULL; g_autoptr(JsonBuilder) builder = NULL; g_autoptr(JsonGenerator) json_generator = NULL; g_autoptr(JsonNode) json_root = NULL; /* can we find a remote with a security attr */ remote = fu_util_get_remote_with_security_report_uri(priv, &error_local); if (remote == NULL) { g_debug("failed to find suitable remote: %s", error_local->message); return TRUE; } if (!priv->assume_yes && !fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS)) { if (!fu_console_input_bool(priv->console, FALSE, /* TRANSLATORS: ask the user to share, %s is something * like: "Linux Vendor Firmware Service" */ _("Upload these anonymous results to the %s to help " "other users?"), fwupd_remote_get_title(remote))) { if (!fu_console_input_bool(priv->console, TRUE, "%s", /* TRANSLATORS: stop nagging the user */ _("Ask again next time?"))) { if (!fwupd_client_modify_remote(priv->client, fwupd_remote_get_id(remote), "SecurityReportURI", "", priv->cancellable, error)) return FALSE; } return TRUE; } } /* get metadata */ metadata = fwupd_client_get_report_metadata(priv->client, priv->cancellable, error); if (metadata == NULL) return FALSE; /* create header */ builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "ReportVersion"); json_builder_add_int_value(builder, 2); json_builder_set_member_name(builder, "MachineId"); json_builder_add_string_value(builder, fwupd_client_get_host_machine_id(priv->client)); /* this is system metadata not stored in the database */ json_builder_set_member_name(builder, "Metadata"); json_builder_begin_object(builder); g_hash_table_iter_init(&iter, metadata); while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) { json_builder_set_member_name(builder, key); json_builder_add_string_value(builder, value); } json_builder_set_member_name(builder, "HostSecurityId"); json_builder_add_string_value(builder, fwupd_client_get_host_security_id(priv->client)); json_builder_end_object(builder); /* attrs */ json_builder_set_member_name(builder, "SecurityAttributes"); json_builder_begin_array(builder); for (guint i = 0; i < attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(attrs, i); json_builder_begin_object(builder); fwupd_security_attr_to_json(attr, builder); json_builder_end_object(builder); } json_builder_end_array(builder); json_builder_end_object(builder); /* export as a string */ json_root = json_builder_get_root(builder); json_generator = json_generator_new(); json_generator_set_pretty(json_generator, TRUE); json_generator_set_root(json_generator, json_root); data = json_generator_to_data(json_generator, NULL); if (data == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Failed to convert to JSON string"); return FALSE; } /* self sign data */ if (priv->sign) { sig = fwupd_client_self_sign(priv->client, data, FWUPD_SELF_SIGN_FLAG_ADD_TIMESTAMP, priv->cancellable, error); if (sig == NULL) return FALSE; } /* ask for permission */ if (!priv->assume_yes && !fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS)) { fu_console_print_kv(priv->console, _("Target"), fwupd_remote_get_security_report_uri(remote)); fu_console_print_kv(priv->console, _("Payload"), data); if (sig != NULL) fu_console_print_kv(priv->console, _("Signature"), sig); if (!fu_console_input_bool(priv->console, TRUE, "%s", _("Proceed with upload?"))) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_PERMISSION_DENIED, "User declined action"); return FALSE; } } /* POST request */ upload_response = fwupd_client_upload_bytes(priv->client, fwupd_remote_get_security_report_uri(remote), data, sig, FWUPD_CLIENT_UPLOAD_FLAG_ALWAYS_MULTIPART, priv->cancellable, error); if (upload_response == NULL) return FALSE; fu_console_print_literal(priv->console, /* TRANSLATORS: success, so say thank you to the user */ _("Host Security ID attributes uploaded successfully, thanks!")); /* as this worked, ask if the user want to do this every time */ if (!fwupd_remote_has_flag(remote, FWUPD_REMOTE_FLAG_AUTOMATIC_SECURITY_REPORTS)) { if (fu_console_input_bool(priv->console, FALSE, "%s", /* TRANSLATORS: can we JFDI? */ _("Automatically upload every time?"))) { if (!fwupd_client_modify_remote(priv->client, fwupd_remote_get_id(remote), "AutomaticSecurityReports", "true", priv->cancellable, error)) return FALSE; } } return TRUE; } static gboolean fu_util_security_as_json(FuUtilPrivate *priv, GPtrArray *attrs, GPtrArray *events, GPtrArray *devices, GError **error) { g_autoptr(GPtrArray) devices_issues = NULL; g_autoptr(JsonBuilder) builder = json_builder_new(); json_builder_begin_object(builder); /* attrs */ json_builder_set_member_name(builder, "SecurityAttributes"); json_builder_begin_array(builder); for (guint i = 0; i < attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(attrs, i); json_builder_begin_object(builder); fwupd_security_attr_to_json(attr, builder); json_builder_end_object(builder); } json_builder_end_array(builder); /* events */ if (events != NULL && events->len > 0) { json_builder_set_member_name(builder, "SecurityEvents"); json_builder_begin_array(builder); for (guint i = 0; i < attrs->len; i++) { FwupdSecurityAttr *attr = g_ptr_array_index(attrs, i); json_builder_begin_object(builder); fwupd_security_attr_to_json(attr, builder); json_builder_end_object(builder); } json_builder_end_array(builder); } /* devices */ devices_issues = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; devices != NULL && i < devices->len; i++) { FwupdDevice *device = g_ptr_array_index(devices, i); GPtrArray *issues = fwupd_device_get_issues(device); if (issues->len == 0) continue; g_ptr_array_add(devices_issues, g_object_ref(device)); } if (devices_issues->len > 0) { json_builder_set_member_name(builder, "Devices"); json_builder_begin_array(builder); for (guint i = 0; i < devices_issues->len; i++) { FwupdDevice *device = g_ptr_array_index(devices_issues, i); json_builder_begin_object(builder); fwupd_device_to_json_full(device, builder, FWUPD_DEVICE_FLAG_TRUSTED); json_builder_end_object(builder); } json_builder_end_array(builder); } json_builder_end_object(builder); return fu_util_print_builder(priv->console, builder, error); } static gboolean fu_util_sync(FuUtilPrivate *priv, gchar **values, GError **error) { const gchar *host_bkc = fwupd_client_get_host_bkc(priv->client); guint cnt = 0; g_autoptr(GPtrArray) devices = NULL; /* update the console if composite devices are also updated */ priv->current_operation = FU_UTIL_OPERATION_INSTALL; priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; devices = fwupd_client_get_devices(priv->client, NULL, error); if (devices == NULL) return FALSE; for (guint i = 0; i < devices->len; i++) { FwupdDevice *dev = g_ptr_array_index(devices, i); g_autoptr(FwupdRelease) rel = NULL; g_autoptr(GError) error_local = NULL; /* find the release that matches the tag */ if (host_bkc != NULL) { rel = fu_util_get_release_with_tag(priv, dev, host_bkc, &error_local); } else if (fu_device_get_branch(dev) != NULL) { rel = fu_util_get_release_with_branch(priv, dev, fu_device_get_branch(dev), &error_local); } else { g_set_error_literal(&error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No device branch or system HostBkc set"); } if (rel == NULL) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED) || g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_debug("ignoring %s: %s", fwupd_device_get_id(dev), error_local->message); continue; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } /* ignore if already on that release */ if (g_strcmp0(fwupd_device_get_version(dev), fwupd_release_get_version(rel)) == 0) continue; /* install this new release */ g_debug("need to move %s from %s to %s", fwupd_device_get_id(dev), fwupd_device_get_version(dev), fwupd_release_get_version(rel)); if (!fu_util_update_device_with_release(priv, dev, rel, &error_local)) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_debug("ignoring %s: %s", fwupd_device_get_id(dev), error_local->message); continue; } g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } fu_util_display_current_message(priv); cnt++; } /* nothing was done */ if (cnt == 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No devices required modification"); return FALSE; } /* we don't want to ask anything */ if (priv->no_reboot_check) { g_debug("skipping reboot check"); return TRUE; } /* show reboot if needed */ return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_security_fix_attr(FuUtilPrivate *priv, FwupdSecurityAttr *attr, GError **error) { g_autoptr(GString) body = g_string_new(NULL); g_autoptr(GString) title = g_string_new(NULL); g_string_append_printf(title, "%s: %s", /* TRANSLATORS: title prefix for the BIOS settings dialog */ _("Configuration Change Suggested"), fwupd_security_attr_get_title(attr)); g_string_append(body, fwupd_security_attr_get_description(attr)); if (fwupd_security_attr_get_bios_setting_id(attr) != NULL && fwupd_security_attr_get_bios_setting_current_value(attr) != NULL && fwupd_security_attr_get_bios_setting_target_value(attr) != NULL) { g_string_append(body, "\n\n"); g_string_append_printf(body, /* TRANSLATORS: the %1 is a BIOS setting name. * %2 and %3 are the values, e.g. "True" or "Windows10" */ _("This tool can change the BIOS setting '%s' from '%s' " "to '%s' automatically, but it will only be active after " "restarting the computer."), fwupd_security_attr_get_bios_setting_id(attr), fwupd_security_attr_get_bios_setting_current_value(attr), fwupd_security_attr_get_bios_setting_target_value(attr)); g_string_append(body, "\n\n"); g_string_append(body, /* TRANSLATORS: the user has to manually recover; we can't do it */ _("You should ensure you are comfortable restoring the setting " "from the system firmware setup, as this change may cause the " "system to not boot into Linux or cause other system " "instability.")); } else if (fwupd_security_attr_get_kernel_target_value(attr) != NULL) { g_string_append(body, "\n\n"); if (fwupd_security_attr_get_kernel_current_value(attr) != NULL) { g_string_append_printf( body, /* TRANSLATORS: the %1 is a kernel command line key=value */ _("This tool can change the kernel argument from '%s' to '%s', but " "it will only be active after restarting the computer."), fwupd_security_attr_get_kernel_current_value(attr), fwupd_security_attr_get_kernel_target_value(attr)); } else { g_string_append_printf( body, /* TRANSLATORS: the %1 is a kernel command line key=value */ _("This tool can add a kernel argument of '%s', but it will " "only be active after restarting the computer."), fwupd_security_attr_get_kernel_target_value(attr)); } g_string_append(body, "\n\n"); g_string_append(body, /* TRANSLATORS: the user has to manually recover; we can't do it */ _("You should ensure you are comfortable restoring the setting " "from a recovery or installation disk, as this change may cause " "the system to not boot into Linux or cause other system " "instability.")); } fu_console_box(priv->console, title->str, body->str, 80); /* TRANSLATORS: prompt to apply the update */ if (!fu_console_input_bool(priv->console, FALSE, "%s", _("Perform operation?"))) return TRUE; if (!fwupd_client_fix_host_security_attr(priv->client, fwupd_security_attr_get_appstream_id(attr), priv->cancellable, error)) return FALSE; /* do not offer to upload the report */ priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; return TRUE; } static gboolean fu_util_security(FuUtilPrivate *priv, gchar **values, GError **error) { FuSecurityAttrToStringFlags flags = FU_SECURITY_ATTR_TO_STRING_FLAG_NONE; g_autoptr(GPtrArray) attrs = NULL; g_autoptr(GPtrArray) devices = NULL; g_autoptr(GPtrArray) events = NULL; g_autoptr(GError) error_local = NULL; g_autofree gchar *str = NULL; #ifndef HAVE_HSI g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, /* TRANSLATORS: error message for unsupported feature */ _("Host Security ID (HSI) is not supported")); return FALSE; #endif /* HAVE_HSI */ /* the "why" */ attrs = fwupd_client_get_host_security_attrs(priv->client, priv->cancellable, error); if (attrs == NULL) return FALSE; /* the "when" */ events = fwupd_client_get_host_security_events(priv->client, 10, priv->cancellable, &error_local); if (events == NULL) { if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) { g_debug("ignoring failed events: %s", error_local->message); } else { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } /* the "also" */ devices = fwupd_client_get_devices(priv->client, priv->cancellable, &error_local); if (devices == NULL) { if (!g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) { g_propagate_error(error, g_steal_pointer(&error_local)); return FALSE; } } /* not for human consumption */ if (priv->as_json) return fu_util_security_as_json(priv, attrs, events, devices, error); fu_console_print(priv->console, "%s \033[1m%s\033[0m", /* TRANSLATORS: this is a string like 'HSI:2-U' */ _("Host Security ID:"), fwupd_client_get_host_security_id(priv->client)); /* show or hide different elements */ if (priv->show_all) { flags |= FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_OBSOLETES; flags |= FU_SECURITY_ATTR_TO_STRING_FLAG_SHOW_URLS; } str = fu_util_security_attrs_to_string(attrs, flags); fu_console_print_literal(priv->console, str); /* events */ if (events != NULL && events->len > 0) { g_autofree gchar *estr = fu_util_security_events_to_string(events, flags); if (estr != NULL) fu_console_print_literal(priv->console, estr); } /* known CVEs */ if (devices != NULL && devices->len > 0) { g_autofree gchar *estr = fu_util_security_issues_to_string(devices); if (estr != NULL) fu_console_print_literal(priv->console, estr); } /* host emulation */ for (guint j = 0; j < attrs->len; j++) { FwupdSecurityAttr *attr = g_ptr_array_index(attrs, j); if (g_strcmp0(fwupd_security_attr_get_appstream_id(attr), FWUPD_SECURITY_ATTR_ID_HOST_EMULATION) == 0) { priv->no_unreported_check = TRUE; break; } } /* any things we can fix? */ if (!priv->no_security_fix) { for (guint j = 0; j < attrs->len; j++) { FwupdSecurityAttr *attr = g_ptr_array_index(attrs, j); if (fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_CAN_FIX) && !fwupd_security_attr_has_flag(attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS)) { if (!fu_util_security_fix_attr(priv, attr, error)) return FALSE; } } } /* upload, with confirmation */ if (!priv->no_unreported_check) { if (!fu_util_upload_security(priv, attrs, error)) return FALSE; } /* reboot is required? */ if (!priv->no_reboot_check && (priv->completion_flags & FWUPD_DEVICE_FLAG_NEEDS_REBOOT) > 0) { if (!fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error)) return FALSE; } /* success */ return TRUE; } static void fu_util_ignore_cb(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { } #ifdef HAVE_GIO_UNIX static gboolean fu_util_sigint_cb(gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *)user_data; g_debug("Handling SIGINT"); g_cancellable_cancel(priv->cancellable); return FALSE; } #endif static void fu_util_setup_signal_handlers(FuUtilPrivate *priv) { #ifdef HAVE_GIO_UNIX g_autoptr(GSource) source = g_unix_signal_source_new(SIGINT); g_source_set_callback(source, fu_util_sigint_cb, priv, NULL); g_source_attach(g_steal_pointer(&source), priv->main_ctx); #endif } static void fu_util_private_free(FuUtilPrivate *priv) { if (priv->client != NULL) g_object_unref(priv->client); if (priv->current_device != NULL) g_object_unref(priv->current_device); g_ptr_array_unref(priv->post_requests); g_main_loop_unref(priv->loop); g_main_context_unref(priv->main_ctx); g_object_unref(priv->cancellable); g_object_unref(priv->console); g_option_context_free(priv->context); g_free(priv); } static gboolean fu_util_check_daemon_version(FuUtilPrivate *priv, GError **error) { const gchar *daemon = fwupd_client_get_daemon_version(priv->client); if (daemon == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, /* TRANSLATORS: error message */ _("Unable to connect to service")); return FALSE; } if (g_strcmp0(daemon, SOURCE_VERSION) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, /* TRANSLATORS: error message */ _("Unsupported daemon version %s, client version is %s"), daemon, SOURCE_VERSION); return FALSE; } return TRUE; } static gboolean fu_util_check_polkit_actions(GError **error) { #ifdef HAVE_POLKIT g_autofree gchar *directory = NULL; g_autofree gchar *filename = NULL; if (g_getenv("FWUPD_POLKIT_NOCHECK") != NULL) return TRUE; directory = fu_path_from_kind(FU_PATH_KIND_POLKIT_ACTIONS); filename = g_build_filename(directory, "org.freedesktop.fwupd.policy", NULL); if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) { g_set_error_literal( error, FWUPD_ERROR, FWUPD_ERROR_AUTH_FAILED, "PolicyKit files are missing, see " "https://github.com/fwupd/fwupd/wiki/PolicyKit-files-are-missing"); return FALSE; } #endif return TRUE; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuUtilPrivate, fu_util_private_free) #pragma clang diagnostic pop static gchar * fu_util_get_history_checksum(FuUtilPrivate *priv, GError **error) { const gchar *csum; g_autoptr(FwupdDevice) device = NULL; g_autoptr(FwupdRelease) release = NULL; g_autoptr(GPtrArray) devices = NULL; devices = fwupd_client_get_history(priv->client, priv->cancellable, error); if (devices == NULL) return NULL; device = fu_util_prompt_for_device(priv, devices, error); if (device == NULL) return NULL; release = fu_util_prompt_for_release(priv, fwupd_device_get_releases(device), error); if (release == NULL) return NULL; csum = fwupd_checksum_get_best(fwupd_release_get_checksums(release)); if (csum == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, "No suitable checksums"); return NULL; } return g_strdup(csum); } static gboolean fu_util_block_firmware(FuUtilPrivate *priv, gchar **values, GError **error) { guint idx = 0; g_autofree gchar *csum = NULL; g_auto(GStrv) csums_new = NULL; g_auto(GStrv) csums = NULL; /* get existing checksums */ csums = fwupd_client_get_blocked_firmware(priv->client, priv->cancellable, error); if (csums == NULL) return FALSE; /* get new value */ if (g_strv_length(values) == 0) { csum = fu_util_get_history_checksum(priv, error); if (csum == NULL) return FALSE; } else { csum = g_strdup(values[0]); } /* ensure it's not already there */ if (g_strv_contains((const gchar *const *)csums, csum)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, /* TRANSLATORS: user selected something not possible */ _("Firmware is already blocked")); return FALSE; } /* TRANSLATORS: we will not offer this firmware to the user */ fu_console_print(priv->console, "%s %s", _("Blocking firmware:"), csum); /* remove it from the new list */ csums_new = g_new0(gchar *, g_strv_length(csums) + 2); for (guint i = 0; csums[i] != NULL; i++) { if (g_strcmp0(csums[i], csum) != 0) csums_new[idx++] = g_strdup(csums[i]); } csums_new[idx] = g_strdup(csum); return fwupd_client_set_blocked_firmware(priv->client, csums_new, priv->cancellable, error); } static gboolean fu_util_unblock_firmware(FuUtilPrivate *priv, gchar **values, GError **error) { guint idx = 0; g_auto(GStrv) csums = NULL; g_auto(GStrv) csums_new = NULL; g_autofree gchar *csum = NULL; /* get existing checksums */ csums = fwupd_client_get_blocked_firmware(priv->client, priv->cancellable, error); if (csums == NULL) return FALSE; /* empty list */ if (g_strv_length(csums) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, /* TRANSLATORS: nothing to show */ _("There are no blocked firmware files")); return FALSE; } /* get new value */ if (g_strv_length(values) == 0) { csum = fu_util_get_history_checksum(priv, error); if (csum == NULL) return FALSE; } else { csum = g_strdup(values[0]); } /* ensure it's there */ if (!g_strv_contains((const gchar *const *)csums, csum)) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, /* TRANSLATORS: user selected something not possible */ _("Firmware is not already blocked")); return FALSE; } /* TRANSLATORS: we will now offer this firmware to the user */ fu_console_print(priv->console, "%s %s", _("Unblocking firmware:"), csum); /* remove it from the new list */ csums_new = g_new0(gchar *, g_strv_length(csums)); for (guint i = 0; csums[i] != NULL; i++) { if (g_strcmp0(csums[i], csum) != 0) csums_new[idx++] = g_strdup(csums[i]); } return fwupd_client_set_blocked_firmware(priv->client, csums_new, priv->cancellable, error); } static gboolean fu_util_get_blocked_firmware(FuUtilPrivate *priv, gchar **values, GError **error) { g_auto(GStrv) csums = NULL; /* get checksums */ csums = fwupd_client_get_blocked_firmware(priv->client, priv->cancellable, error); if (csums == NULL) return FALSE; if (priv->as_json) return fu_util_get_checksums_as_json(priv, csums, error); /* empty list */ if (g_strv_length(csums) == 0) { /* TRANSLATORS: nothing to show */ fu_console_print_literal(priv->console, _("There are no blocked firmware files")); return TRUE; } /* TRANSLATORS: there follows a list of hashes */ fu_console_print_literal(priv->console, _("Blocked firmware files:")); for (guint i = 0; csums[i] != NULL; i++) { fu_console_print(priv->console, "%u.\t%s", i + 1, csums[i]); } /* success */ return TRUE; } static void fu_util_show_plugin_warnings(FuUtilPrivate *priv) { FwupdPluginFlags flags = FWUPD_PLUGIN_FLAG_NONE; g_autoptr(GPtrArray) plugins = NULL; /* get plugins from daemon, ignoring if the daemon is too old */ plugins = fwupd_client_get_plugins(priv->client, priv->cancellable, NULL); if (plugins == NULL) return; /* get a superset so we do not show the same message more than once */ for (guint i = 0; i < plugins->len; i++) { FwupdPlugin *plugin = g_ptr_array_index(plugins, i); if (fwupd_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_DISABLED)) continue; if (!fwupd_plugin_has_flag(plugin, FWUPD_PLUGIN_FLAG_USER_WARNING)) continue; flags |= fwupd_plugin_get_flags(plugin); } /* never show these, they're way too generic */ flags &= ~FWUPD_PLUGIN_FLAG_DISABLED; flags &= ~FWUPD_PLUGIN_FLAG_NO_HARDWARE; flags &= ~FWUPD_PLUGIN_FLAG_REQUIRE_HWID; flags &= ~FWUPD_PLUGIN_FLAG_MEASURE_SYSTEM_INTEGRITY; /* print */ for (guint i = 0; i < 64; i++) { FwupdPluginFlags flag = (guint64)1 << i; const gchar *tmp; g_autofree gchar *url = NULL; g_autoptr(GString) str = g_string_new(NULL); if ((flags & flag) == 0) continue; tmp = fu_util_plugin_flag_to_string(flag); if (tmp == NULL) continue; g_string_append_printf(str, "%s\n", tmp); url = g_strdup_printf("https://github.com/fwupd/fwupd/wiki/PluginFlag:%s", fwupd_plugin_flag_to_string(flag)); /* TRANSLATORS: %s is a link to a website */ g_string_append_printf(str, _("See %s for more information."), url); fu_console_print_full(priv->console, FU_CONSOLE_PRINT_FLAG_WARNING, "%s\n", str->str); } } static gboolean fu_util_set_bios_setting(FuUtilPrivate *priv, gchar **input, GError **error) { g_autoptr(GHashTable) settings = fu_util_bios_settings_parse_argv(input, error); if (settings == NULL) return FALSE; if (!fwupd_client_modify_bios_setting(priv->client, settings, priv->cancellable, error)) { if (!g_error_matches(*error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) g_prefix_error(error, "failed to set BIOS setting: "); return FALSE; } if (!priv->as_json) { gpointer key, value; GHashTableIter iter; g_hash_table_iter_init(&iter, settings); while (g_hash_table_iter_next(&iter, &key, &value)) { g_autofree gchar *msg = /* TRANSLATORS: Configured a BIOS setting to a value */ g_strdup_printf(_("Set BIOS setting '%s' using '%s'."), (const gchar *)key, (const gchar *)value); fu_console_print_literal(priv->console, msg); } } priv->completion_flags |= FWUPD_DEVICE_FLAG_NEEDS_REBOOT; if (priv->no_reboot_check) { g_debug("skipping reboot check"); return TRUE; } return fu_util_prompt_complete(priv->console, priv->completion_flags, TRUE, error); } static gboolean fu_util_get_bios_setting(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GPtrArray) attrs = NULL; gboolean found = FALSE; attrs = fwupd_client_get_bios_settings(priv->client, priv->cancellable, error); if (attrs == NULL) return FALSE; if (priv->as_json) return fu_util_get_bios_setting_as_json(priv->console, values, attrs, error); for (guint i = 0; i < attrs->len; i++) { FwupdBiosSetting *attr = g_ptr_array_index(attrs, i); if (fu_util_bios_setting_matches_args(attr, values)) { g_autofree gchar *tmp = fu_util_bios_setting_to_string(attr, 0); fu_console_print_literal(priv->console, tmp); found = TRUE; } } if (attrs->len == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO, /* TRANSLATORS: error message */ _("This system doesn't support firmware settings")); return FALSE; } if (!found) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, /* TRANSLATORS: error message */ _("Unable to find attribute")); return FALSE; } return TRUE; } static gboolean fu_util_security_fix(FuUtilPrivate *priv, gchar **values, GError **error) { #ifndef HAVE_HSI g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, /* TRANSLATORS: error message for unsupported feature */ _("Host Security ID (HSI) is not supported")); return FALSE; #endif /* HAVE_HSI */ if (g_strv_length(values) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, /* TRANSLATOR: This is the error message for * incorrect parameter */ _("Invalid arguments, expected an AppStream ID")); return FALSE; } if (!fwupd_client_fix_host_security_attr(priv->client, values[0], priv->cancellable, error)) return FALSE; /* TRANSLATORS: we've fixed a security problem on the machine */ fu_console_print_literal(priv->console, _("Fixed successfully")); return TRUE; } static gboolean fu_util_security_undo(FuUtilPrivate *priv, gchar **values, GError **error) { #ifndef HAVE_HSI g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, /* TRANSLATORS: error message for unsupported feature */ _("Host Security ID (HSI) is not supported")); return FALSE; #endif /* HAVE_HSI */ if (g_strv_length(values) == 0) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, /* TRANSLATOR: This is the error message for * incorrect parameter */ _("Invalid arguments, expected an AppStream ID")); return FALSE; } if (!fwupd_client_undo_host_security_attr(priv->client, values[0], priv->cancellable, error)) return FALSE; /* TRANSLATORS: we've fixed a security problem on the machine */ fu_console_print_literal(priv->console, _("Fix reverted successfully")); return TRUE; } static gboolean fu_util_emulation_tag(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; /* set the flag */ dev = fu_util_get_device_or_prompt(priv, values, error); if (dev == NULL) return FALSE; return fwupd_client_modify_device(priv->client, fwupd_device_get_id(dev), "Flags", "emulation-tag", priv->cancellable, error); } static gboolean fu_util_emulation_untag(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(FwupdDevice) dev = NULL; /* set the flag */ priv->filter_device_include |= FWUPD_DEVICE_FLAG_EMULATION_TAG; dev = fu_util_get_device_or_prompt(priv, values, error); if (dev == NULL) return FALSE; return fwupd_client_modify_device(priv->client, fwupd_device_get_id(dev), "Flags", "~emulation-tag", priv->cancellable, error); } static gboolean fu_util_emulation_save(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GBytes) data = NULL; /* check args */ if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments, expected FILENAME"); return FALSE; } /* save */ data = fwupd_client_emulation_save(priv->client, priv->cancellable, error); if (data == NULL) return FALSE; return fu_bytes_set_contents(values[0], data, error); } static gboolean fu_util_emulation_load(FuUtilPrivate *priv, gchar **values, GError **error) { g_autoptr(GBytes) data = NULL; /* check args */ if (g_strv_length(values) != 1) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS, "Invalid arguments, expected FILENAME"); return FALSE; } data = fu_bytes_get_contents(values[0], error); if (data == NULL) return FALSE; return fu_util_emulation_load_with_fallback(priv, data, error); } static gboolean fu_util_version(FuUtilPrivate *priv, GError **error) { g_autoptr(GHashTable) metadata = NULL; g_autofree gchar *str = NULL; /* get metadata */ metadata = fwupd_client_get_report_metadata(priv->client, priv->cancellable, error); if (metadata == NULL) return FALSE; /* dump to the screen in the most appropriate format */ if (priv->as_json) return fu_util_project_versions_as_json(priv->console, metadata, error); str = fu_util_project_versions_to_string(metadata); fu_console_print_literal(priv->console, str); return TRUE; } static gboolean fu_util_setup_interactive(FuUtilPrivate *priv, GError **error) { if (priv->as_json) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "using --json"); return FALSE; } return fu_console_setup(priv->console, error); } static void fu_util_cancelled_cb(GCancellable *cancellable, gpointer user_data) { FuUtilPrivate *priv = (FuUtilPrivate *)user_data; if (!g_main_loop_is_running(priv->loop)) return; /* TRANSLATORS: this is from ctrl+c */ fu_console_print_literal(priv->console, _("Cancelled")); g_main_loop_quit(priv->loop); } static const gchar * fu_util_get_prgname(const gchar *argv0) { const gchar *prgname = (const gchar *)g_strrstr(argv0, " "); if (prgname != NULL) return prgname + 1; return argv0; } static void fu_util_print_error(FuUtilPrivate *priv, const GError *error) { if (priv->as_json) { fu_util_print_error_as_json(priv->console, error); return; } fu_console_print_full(priv->console, FU_CONSOLE_PRINT_FLAG_STDERR, "%s\n", error->message); } int main(int argc, char *argv[]) { gboolean force = FALSE; gboolean allow_branch_switch = FALSE; gboolean allow_older = FALSE; gboolean allow_reinstall = FALSE; gboolean only_p2p = FALSE; gboolean is_interactive = FALSE; gboolean no_history = FALSE; gboolean no_authenticate = FALSE; gboolean offline = FALSE; gboolean ret; gboolean verbose = FALSE; gboolean version = FALSE; g_autoptr(FuUtilPrivate) priv = g_new0(FuUtilPrivate, 1); g_autoptr(GDateTime) dt_now = g_date_time_new_now_utc(); g_autoptr(GError) error = NULL; g_autoptr(GError) error_console = NULL; g_autoptr(GPtrArray) cmd_array = fu_util_cmd_array_new(); g_autofree gchar *cmd_descriptions = NULL; g_autofree gchar *filter_device = NULL; g_autofree gchar *filter_release = NULL; const GOptionEntry options[] = { {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, /* TRANSLATORS: command line option */ N_("Show extra debugging information"), NULL}, {"version", '\0', 0, G_OPTION_ARG_NONE, &version, /* TRANSLATORS: command line option */ N_("Show client and daemon versions"), NULL}, {"offline", '\0', 0, G_OPTION_ARG_NONE, &offline, /* TRANSLATORS: command line option */ N_("Schedule installation for next reboot when possible"), NULL}, {"allow-reinstall", '\0', 0, G_OPTION_ARG_NONE, &allow_reinstall, /* TRANSLATORS: command line option */ N_("Allow reinstalling existing firmware versions"), NULL}, {"allow-older", '\0', 0, G_OPTION_ARG_NONE, &allow_older, /* TRANSLATORS: command line option */ N_("Allow downgrading firmware versions"), NULL}, {"allow-branch-switch", '\0', 0, G_OPTION_ARG_NONE, &allow_branch_switch, /* TRANSLATORS: command line option */ N_("Allow switching firmware branch"), NULL}, {"force", '\0', 0, G_OPTION_ARG_NONE, &force, /* TRANSLATORS: command line option */ N_("Force the action by relaxing some runtime checks"), NULL}, {"assume-yes", 'y', 0, G_OPTION_ARG_NONE, &priv->assume_yes, /* TRANSLATORS: command line option */ N_("Answer yes to all questions"), NULL}, {"sign", '\0', 0, G_OPTION_ARG_NONE, &priv->sign, /* TRANSLATORS: command line option */ N_("Sign the uploaded data with the client certificate"), NULL}, {"no-unreported-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_unreported_check, /* TRANSLATORS: command line option */ N_("Do not check for unreported history"), NULL}, {"no-metadata-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_metadata_check, /* TRANSLATORS: command line option */ N_("Do not check for old metadata"), NULL}, {"no-remote-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_remote_check, /* TRANSLATORS: command line option */ N_("Do not check if download remotes should be enabled"), NULL}, {"no-reboot-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_reboot_check, /* TRANSLATORS: command line option */ N_("Do not check or prompt for reboot after update"), NULL}, {"no-safety-check", '\0', 0, G_OPTION_ARG_NONE, &priv->no_safety_check, /* TRANSLATORS: command line option */ N_("Do not perform device safety checks"), NULL}, {"no-device-prompt", '\0', 0, G_OPTION_ARG_NONE, &priv->no_device_prompt, /* TRANSLATORS: command line option */ N_("Do not prompt for devices"), NULL}, {"no-history", '\0', 0, G_OPTION_ARG_NONE, &no_history, /* TRANSLATORS: command line option */ N_("Do not write to the history database"), NULL}, {"show-all", '\0', 0, G_OPTION_ARG_NONE, &priv->show_all, /* TRANSLATORS: command line option */ N_("Show all results"), NULL}, {"show-all-devices", '\0', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &priv->show_all, /* TRANSLATORS: command line option */ N_("Show devices that are not updatable"), NULL}, {"disable-ssl-strict", '\0', 0, G_OPTION_ARG_NONE, &priv->disable_ssl_strict, /* TRANSLATORS: command line option */ N_("Ignore SSL strict checks when downloading files"), NULL}, {"p2p", '\0', 0, G_OPTION_ARG_NONE, &only_p2p, /* TRANSLATORS: command line option */ N_("Only use peer-to-peer networking when downloading files"), NULL}, {"filter", '\0', 0, G_OPTION_ARG_STRING, &filter_device, /* TRANSLATORS: command line option */ N_("Filter with a set of device flags using a ~ prefix to " "exclude, e.g. 'internal,~needs-reboot'"), NULL}, {"filter-release", '\0', 0, G_OPTION_ARG_STRING, &filter_release, /* TRANSLATORS: command line option */ N_("Filter with a set of release flags using a ~ prefix to " "exclude, e.g. 'trusted-release,~trusted-metadata'"), NULL}, {"json", '\0', 0, G_OPTION_ARG_NONE, &priv->as_json, /* TRANSLATORS: command line option */ N_("Output in JSON format"), NULL}, {"no-security-fix", '\0', 0, G_OPTION_ARG_NONE, &priv->no_security_fix, /* TRANSLATORS: command line option */ N_("Do not prompt to fix security issues"), NULL}, {"no-authenticate", '\0', 0, G_OPTION_ARG_NONE, &no_authenticate, /* TRANSLATORS: command line option */ N_("Don't prompt for authentication (less details may be shown)"), NULL}, {NULL}}; FwupdFeatureFlags feature_flags = FWUPD_FEATURE_FLAG_CAN_REPORT | FWUPD_FEATURE_FLAG_SWITCH_BRANCH | FWUPD_FEATURE_FLAG_FDE_WARNING | FWUPD_FEATURE_FLAG_COMMUNITY_TEXT | FWUPD_FEATURE_FLAG_SHOW_PROBLEMS; #ifdef _WIN32 /* workaround Windows setting the codepage to 1252 */ (void)g_setenv("LANG", "C.UTF-8", FALSE); #endif setlocale(LC_ALL, ""); bindtextdomain(GETTEXT_PACKAGE, FWUPD_LOCALEDIR); bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); textdomain(GETTEXT_PACKAGE); g_set_prgname(fu_util_get_prgname(argv[0])); #ifndef SUPPORTED_BUILD /* make critical warnings fatal */ (void)g_setenv("G_DEBUG", "fatal-criticals", FALSE); #endif /* ensure D-Bus errors are registered */ (void)fwupd_error_quark(); /* this is an old command which is possibly a symlink */ if (g_strcmp0(g_get_prgname(), "fwupdagent") == 0) { g_printerr("INFO: The fwupdagent command is deprecated, " "use `fwupdmgr --json` instead\n"); priv->as_json = TRUE; } /* create helper object */ priv->main_ctx = g_main_context_new(); priv->loop = g_main_loop_new(priv->main_ctx, FALSE); priv->console = fu_console_new(); priv->post_requests = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); fu_console_set_main_context(priv->console, priv->main_ctx); /* add commands */ fu_util_cmd_array_add(cmd_array, "get-devices,get-topology", NULL, /* TRANSLATORS: command description */ _("Get all devices that support firmware updates"), fu_util_get_devices); fu_util_cmd_array_add(cmd_array, "get-history", NULL, /* TRANSLATORS: command description */ _("Show history of firmware updates"), fu_util_get_history); fu_util_cmd_array_add(cmd_array, "report-history", NULL, /* TRANSLATORS: command description */ _("Share firmware history with the developers"), fu_util_report_history); fu_util_cmd_array_add(cmd_array, "report-export", NULL, /* TRANSLATORS: command description */ _("Export firmware history for manual upload"), fu_util_report_export); fu_util_cmd_array_add(cmd_array, "install", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID] [VERSION]"), /* TRANSLATORS: command description */ _("Install a specific firmware file on all devices that match"), fu_util_install); fu_util_cmd_array_add(cmd_array, "local-install", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILE [DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Install a firmware file in cabinet format on this hardware"), fu_util_local_install); fu_util_cmd_array_add(cmd_array, "get-details", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILE"), /* TRANSLATORS: command description */ _("Gets details about a firmware file"), fu_util_get_details); fu_util_cmd_array_add(cmd_array, "get-updates,get-upgrades", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Gets the list of updates for connected hardware"), fu_util_get_updates); fu_util_cmd_array_add(cmd_array, "update,upgrade", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Updates all specified devices to latest firmware version, or all " "devices if unspecified"), fu_util_update); fu_util_cmd_array_add(cmd_array, "verify", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Checks cryptographic hash matches firmware"), fu_util_verify); fu_util_cmd_array_add(cmd_array, "unlock", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("DEVICE-ID|GUID"), /* TRANSLATORS: command description */ _("Unlocks the device for firmware access"), fu_util_unlock); fu_util_cmd_array_add(cmd_array, "clear-results", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("DEVICE-ID|GUID"), /* TRANSLATORS: command description */ _("Clears the results from the last update"), fu_util_clear_results); fu_util_cmd_array_add(cmd_array, "get-results", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("DEVICE-ID|GUID"), /* TRANSLATORS: command description */ _("Gets the results from the last update"), fu_util_get_results); fu_util_cmd_array_add(cmd_array, "get-releases", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Gets the releases for a device"), fu_util_get_releases); fu_util_cmd_array_add(cmd_array, "get-remotes", NULL, /* TRANSLATORS: command description */ _("Gets the configured remotes"), fu_util_get_remotes); fu_util_cmd_array_add(cmd_array, "downgrade", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Downgrades the firmware on a device"), fu_util_downgrade); fu_util_cmd_array_add(cmd_array, "refresh", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[FILE FILE_SIG REMOTE-ID]"), /* TRANSLATORS: command description */ _("Refresh metadata from remote server"), fu_util_refresh); fu_util_cmd_array_add(cmd_array, "verify-update", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Update the stored cryptographic hash with current ROM contents"), fu_util_verify_update); fu_util_cmd_array_add(cmd_array, "modify-remote", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("REMOTE-ID KEY VALUE"), /* TRANSLATORS: command description */ _("Modifies a given remote"), fu_util_remote_modify); fu_util_cmd_array_add(cmd_array, "enable-remote", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("REMOTE-ID"), /* TRANSLATORS: command description */ _("Enables a given remote"), fu_util_remote_enable); fu_util_cmd_array_add(cmd_array, "disable-remote", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("REMOTE-ID"), /* TRANSLATORS: command description */ _("Disables a given remote"), fu_util_remote_disable); fu_util_cmd_array_add(cmd_array, "activate", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Activate devices"), fu_util_activate); fu_util_cmd_array_add(cmd_array, "get-approved-firmware", NULL, /* TRANSLATORS: firmware approved by the admin */ _("Gets the list of approved firmware"), fu_util_get_approved_firmware); fu_util_cmd_array_add(cmd_array, "set-approved-firmware", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME|CHECKSUM1[,CHECKSUM2][,CHECKSUM3]"), /* TRANSLATORS: firmware approved by the admin */ _("Sets the list of approved firmware"), fu_util_set_approved_firmware); fu_util_cmd_array_add(cmd_array, "modify-config", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("KEY,VALUE"), /* TRANSLATORS: sets something in the daemon configuration file */ _("Modifies a daemon configuration value"), fu_util_modify_config); fu_util_cmd_array_add(cmd_array, "reinstall", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Reinstall current firmware on the device"), fu_util_reinstall); fu_util_cmd_array_add(cmd_array, "switch-branch", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID] [BRANCH]"), /* TRANSLATORS: command description */ _("Switch the firmware branch on the device"), fu_util_switch_branch); fu_util_cmd_array_add(cmd_array, "security", NULL, /* TRANSLATORS: command description */ _("Gets the host security attributes"), fu_util_security); fu_util_cmd_array_add(cmd_array, "sync,sync-bkc", NULL, /* TRANSLATORS: command description */ _("Sync firmware versions to the chosen configuration"), fu_util_sync); fu_util_cmd_array_add(cmd_array, "block-firmware", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[CHECKSUM]"), /* TRANSLATORS: command description */ _("Blocks a specific firmware from being installed"), fu_util_block_firmware); fu_util_cmd_array_add(cmd_array, "unblock-firmware", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[CHECKSUM]"), /* TRANSLATORS: command description */ _("Unblocks a specific firmware from being installed"), fu_util_unblock_firmware); fu_util_cmd_array_add(cmd_array, "get-blocked-firmware", NULL, /* TRANSLATORS: command description */ _("Gets the list of blocked firmware"), fu_util_get_blocked_firmware); fu_util_cmd_array_add(cmd_array, "get-plugins", NULL, /* TRANSLATORS: command description */ _("Get all enabled plugins registered with the system"), fu_util_get_plugins); fu_util_cmd_array_add(cmd_array, "download", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("LOCATION"), /* TRANSLATORS: command description */ _("Download a file"), fu_util_download); fu_util_cmd_array_add(cmd_array, "device-test", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[FILENAME1] [FILENAME2]"), /* TRANSLATORS: command description */ _("Test a device using a JSON manifest"), fu_util_device_test); fu_util_cmd_array_add(cmd_array, "device-emulate", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[FILENAME1] [FILENAME2]"), /* TRANSLATORS: command description */ _("Emulate a device using a JSON manifest"), fu_util_device_emulate); fu_util_cmd_array_add(cmd_array, "inhibit", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[REASON]"), /* TRANSLATORS: command description */ _("Inhibit the system to prevent upgrades"), fu_util_inhibit); fu_util_cmd_array_add(cmd_array, "uninhibit", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("INHIBIT-ID"), /* TRANSLATORS: command description */ _("Uninhibit the system to allow upgrades"), fu_util_uninhibit); fu_util_cmd_array_add(cmd_array, "device-wait", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("GUID|DEVICE-ID"), /* TRANSLATORS: command description */ _("Wait for a device to appear"), fu_util_device_wait); fu_util_cmd_array_add(cmd_array, "quit", NULL, /* TRANSLATORS: command description */ _("Asks the daemon to quit"), fu_util_quit); fu_util_cmd_array_add( cmd_array, "get-bios-settings,get-bios-setting", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[SETTING1] [SETTING2] [--no-authenticate]"), /* TRANSLATORS: command description */ _("Retrieve BIOS settings. If no arguments are passed all settings are returned"), fu_util_get_bios_setting); fu_util_cmd_array_add(cmd_array, "set-bios-setting", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("SETTING1 VALUE1 [SETTING2] [VALUE2]"), /* TRANSLATORS: command description */ _("Sets one or more BIOS settings"), fu_util_set_bios_setting); fu_util_cmd_array_add(cmd_array, "emulation-load", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME"), /* TRANSLATORS: command description */ _("Load device emulation data"), fu_util_emulation_load); fu_util_cmd_array_add(cmd_array, "emulation-save", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("FILENAME"), /* TRANSLATORS: command description */ _("Save device emulation data"), fu_util_emulation_save); fu_util_cmd_array_add(cmd_array, "emulation-tag", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Adds devices to watch for future emulation"), fu_util_emulation_tag); fu_util_cmd_array_add(cmd_array, "emulation-untag", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[DEVICE-ID|GUID]"), /* TRANSLATORS: command description */ _("Removes devices to watch for future emulation"), fu_util_emulation_untag); fu_util_cmd_array_add(cmd_array, "security-fix", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[APPSTREAM_ID]"), /* TRANSLATORS: command description */ _("Fix a specific host security attribute"), fu_util_security_fix); fu_util_cmd_array_add(cmd_array, "security-undo", /* TRANSLATORS: command argument: uppercase, spaces->dashes */ _("[APPSTREAM_ID]"), /* TRANSLATORS: command description */ _("Undo the host security attribute fix"), fu_util_security_undo); /* do stuff on ctrl+c */ priv->cancellable = g_cancellable_new(); fu_util_setup_signal_handlers(priv); g_signal_connect(G_CANCELLABLE(priv->cancellable), "cancelled", G_CALLBACK(fu_util_cancelled_cb), priv); /* sort by command name */ fu_util_cmd_array_sort(cmd_array); /* non-TTY consoles cannot answer questions */ if (!fu_util_setup_interactive(priv, &error_console)) { g_info("failed to initialize interactive console: %s", error_console->message); priv->no_unreported_check = TRUE; priv->no_metadata_check = TRUE; priv->no_reboot_check = TRUE; priv->no_safety_check = TRUE; priv->no_remote_check = TRUE; priv->no_device_prompt = TRUE; priv->no_emulation_check = TRUE; priv->no_security_fix = TRUE; } else { is_interactive = TRUE; } fu_console_set_interactive(priv->console, is_interactive); /* get a list of the commands */ priv->context = g_option_context_new(NULL); cmd_descriptions = fu_util_cmd_array_to_string(cmd_array); g_option_context_set_summary(priv->context, cmd_descriptions); g_option_context_set_description( priv->context, /* TRANSLATORS: CLI description */ _("This tool allows an administrator to query and control the " "fwupd daemon, allowing them to perform actions such as " "installing or downgrading firmware.")); /* TRANSLATORS: program name */ g_set_application_name(_("Firmware Utility")); g_option_context_add_main_entries(priv->context, options, NULL); ret = g_option_context_parse(priv->context, &argc, &argv, &error); if (!ret) { fu_console_print(priv->console, "%s: %s", /* TRANSLATORS: the user didn't read the man page */ _("Failed to parse arguments"), error->message); return EXIT_FAILURE; } /* allow disabling SSL strict mode for broken corporate proxies */ if (priv->disable_ssl_strict) { fu_console_print_full(priv->console, FU_CONSOLE_PRINT_FLAG_WARNING, "%s\n", /* TRANSLATORS: try to help */ _("Ignoring SSL strict checks, " "to do this automatically in the future " "export DISABLE_SSL_STRICT in your environment")); (void)g_setenv("DISABLE_SSL_STRICT", "1", TRUE); } /* this doesn't have to be precise (e.g. using the build-year) as we just * want to check the clock is not set to the default of 1970-01-01... */ if (g_date_time_get_year(dt_now) < 2021) { fu_console_print_full( priv->console, FU_CONSOLE_PRINT_FLAG_WARNING, "%s\n", /* TRANSLATORS: try to help */ _("The system clock has not been set correctly and downloading " "files may fail.")); } /* parse filter flags */ if (filter_device != NULL) { if (!fu_util_parse_filter_device_flags(filter_device, &priv->filter_device_include, &priv->filter_device_exclude, &error)) { /* TRANSLATORS: the user didn't read the man page */ g_prefix_error(&error, "%s: ", _("Failed to parse flags for --filter")); fu_util_print_error(priv, error); return EXIT_FAILURE; } } if (filter_release != NULL) { if (!fu_util_parse_filter_release_flags(filter_release, &priv->filter_release_include, &priv->filter_release_exclude, &error)) { /* TRANSLATORS: the user didn't read the man page */ g_prefix_error(&error, "%s: ", _("Failed to parse flags for --filter-release")); fu_util_print_error(priv, error); return EXIT_FAILURE; } } /* set verbose? */ if (verbose) { (void)g_setenv("G_MESSAGES_DEBUG", "all", FALSE); (void)g_setenv("FWUPD_VERBOSE", "1", FALSE); } else { g_log_set_handler(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, fu_util_ignore_cb, NULL); } /* set flags */ if (offline) priv->flags |= FWUPD_INSTALL_FLAG_OFFLINE; if (allow_reinstall) priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_REINSTALL; if (allow_older) priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_OLDER; if (allow_branch_switch) priv->flags |= FWUPD_INSTALL_FLAG_ALLOW_BRANCH_SWITCH; if (force) priv->flags |= FWUPD_INSTALL_FLAG_FORCE; if (no_history) priv->flags |= FWUPD_INSTALL_FLAG_NO_HISTORY; /* use peer-to-peer for metadata and firmware *only* if specified */ if (only_p2p) priv->download_flags |= FWUPD_CLIENT_DOWNLOAD_FLAG_ONLY_P2P; #ifdef HAVE_POLKIT /* start polkit tty agent to listen for password requests */ if (is_interactive) { g_autoptr(GError) error_polkit = NULL; if (!fu_polkit_agent_open(&error_polkit)) { fu_console_print(priv->console, "Failed to open polkit agent: %s", error_polkit->message); } } #endif /* connect to the daemon */ priv->client = fwupd_client_new(); fwupd_client_set_main_context(priv->client, priv->main_ctx); g_signal_connect(FWUPD_CLIENT(priv->client), "notify::percentage", G_CALLBACK(fu_util_client_notify_cb), priv); g_signal_connect(FWUPD_CLIENT(priv->client), "notify::status", G_CALLBACK(fu_util_client_notify_cb), priv); g_signal_connect(FWUPD_CLIENT(priv->client), "device-changed", G_CALLBACK(fu_util_update_device_changed_cb), priv); g_signal_connect(FWUPD_CLIENT(priv->client), "device-request", G_CALLBACK(fu_util_update_device_request_cb), priv); /* show a warning if the daemon is tainted */ if (!fwupd_client_connect(priv->client, priv->cancellable, &error)) { #ifdef _WIN32 fu_console_print_literal( priv->console, /* TRANSLATORS: error message for Windows */ _("Failed to connect to Windows service, please ensure it's running.")); g_debug("%s", error->message); #else /* TRANSLATORS: could not contact the fwupd service over D-Bus */ g_prefix_error(&error, "%s: ", _("Failed to connect to daemon")); fu_util_print_error(priv, error); #endif return EXIT_FAILURE; } if (fwupd_client_get_tainted(priv->client)) { fu_console_print_full(priv->console, FU_CONSOLE_PRINT_FLAG_WARNING, "%s\n", /* TRANSLATORS: the user is SOL for support... */ _("The daemon has loaded 3rd party code and " "is no longer supported by the upstream developers!")); } /* just show versions and exit */ if (version) { if (!fu_util_version(priv, &error)) { fu_util_print_error(priv, error); return EXIT_FAILURE; } return EXIT_SUCCESS; } /* show user-visible warnings from the plugins */ fu_util_show_plugin_warnings(priv); /* show any unsupported warnings */ fu_util_show_unsupported_warning(priv->console); /* we know the runtime daemon version now */ fwupd_client_set_user_agent_for_package(priv->client, g_get_prgname(), PACKAGE_VERSION); /* check that we have at least this version daemon running */ if ((priv->flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && !fu_util_check_daemon_version(priv, &error)) { fu_util_print_error(priv, error); return EXIT_FAILURE; } /* make sure polkit actions were installed */ if (!fu_util_check_polkit_actions(&error)) { fu_util_print_error(priv, error); return EXIT_FAILURE; } /* send our implemented feature set */ if (is_interactive) { feature_flags |= FWUPD_FEATURE_FLAG_REQUESTS | FWUPD_FEATURE_FLAG_REQUESTS_NON_GENERIC | FWUPD_FEATURE_FLAG_UPDATE_ACTION | FWUPD_FEATURE_FLAG_DETACH_ACTION; if (!no_authenticate) feature_flags |= FWUPD_FEATURE_FLAG_ALLOW_AUTHENTICATION; } if (!fwupd_client_set_feature_flags(priv->client, feature_flags, priv->cancellable, &error)) { /* TRANSLATORS: a feature is something like "can show an image" */ g_prefix_error(&error, "%s: ", _("Failed to set front-end features")); fu_util_print_error(priv, error); return EXIT_FAILURE; } /* run the specified command */ ret = fu_util_cmd_array_run(cmd_array, priv, argv[1], (gchar **)&argv[2], &error); if (!ret) { #ifdef SUPPORTED_BUILD /* sanity check */ if (error == NULL) { g_critical("exec failed but no error set!"); return EXIT_FAILURE; } #endif fu_util_print_error(priv, error); if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_ARGS)) { g_autofree gchar *cmd = g_strdup_printf("%s --help", g_get_prgname()); g_autoptr(GString) str = g_string_new("\n"); /* TRANSLATORS: explain how to get help, * where $1 is something like 'fwupdmgr --help' */ g_string_append_printf(str, _("Use %s for help"), cmd); fu_console_print_literal(priv->console, str->str); } else if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) return EXIT_NOTHING_TO_DO; else if (g_error_matches(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) return EXIT_NOT_FOUND; return EXIT_FAILURE; } #ifdef HAVE_POLKIT /* stop listening for polkit questions */ fu_polkit_agent_close(); #endif /* success */ return EXIT_SUCCESS; } fwupd-1.9.16/src/fwupd-windows.mc000066400000000000000000000003271460375044200166710ustar00rootroot00000000000000MessageId=0x0 SymbolicName=FWUPD_CATEGORY_GENERIC Language=English Generic Events . MessageId=0x100 SymbolicName=FWUPD_MESSAGE_GENERIC Language=English No specific message -- see the EventData in the Details tab . fwupd-1.9.16/src/fwupd.gresource.xml000066400000000000000000000003251460375044200173750ustar00rootroot00000000000000 org.freedesktop.fwupd.xml fwupd-1.9.16/src/fwupdagent.md000066400000000000000000000011311460375044200162130ustar00rootroot00000000000000--- title: fwupdagent compatibility layer --- % fwupdagent(1) {{PACKAGE_VERSION}} | fwupdagent man page ## NAME **fwupdagent** — firmware updating agent ## SYNOPSIS | **fwupdagent** [CMD] ## DESCRIPTION fwupdagent used to be a command line fwupd client intended to be used by scripts. You should now use the 100% compatible **fwupdmgr \-\-json** command instead. The output is JSON and guaranteed to be stable. ## EXIT STATUS Commands that successfully execute will return "0". ## BUGS See GitHub Issues: ## SEE ALSO fwupd-1.9.16/src/fwupdmgr.md000066400000000000000000000034171460375044200157130ustar00rootroot00000000000000--- title: fwupdmgr client command line utility --- % fwupdmgr(1) {{PACKAGE_VERSION}} | fwupdmgr man page ## NAME **fwupdmgr** — firmware update manager client utility ## SYNOPSIS | **fwupdmgr** [CMD] ## DESCRIPTION fwupdmgr is a command line fwupd client intended to be used interactively. The terminal output between versions of fwupd is not guaranteed to be stable, but if you plan on parsing the results then adding **\-\-json** might be just what you need. There are also graphical tools to firmware available for GNOME and KDE. These applications may be more useful to many users compared to using the command line. * **GNOME Software**: * **GNOME Firmware**: * **KDE Discover**: On most systems fwupd is configured to download metadata from the Linux Vendor Firmware Service `https://fwupd.org/` and more information about the LVFS is available here: `https://lvfs.readthedocs.io/` Most users who want to just update all devices to the latest versions can do `fwupdmgr refresh` and then `fwupdmgr update`. At this point the system will asking for confirmation, update some devices, and may then reboot to deploy other updates offline. ## OPTIONS The fwupdmgr command takes various options depending on the action. Run **fwupdmgr \-\-help** for the full list. ## EXIT STATUS Commands that successfully execute will return "0", with generic failure as "1". There are also several other exit codes used: A return code of "2" is used for commands that have no actions but were successfully executed, and "3" is used when a resource was not found. ## BUGS See GitHub Issues: ## SEE ALSO fwupd-1.9.16/src/fwupdtool.md000066400000000000000000000025321460375044200161000ustar00rootroot00000000000000--- title: fwupdtool standalone command line utility --- % fwupdtool(1) {{PACKAGE_VERSION}} | standalone firmware update utility man page ## NAME **fwupdtool** — standalone firmware update utility ## SYNOPSIS | **fwupdtool** [CMD] ## DESCRIPTION This tool allows an administrator to use fwupd plugins directly without using the daemon process, which may be faster or easier to use when creating or debugging specific plugins. For most end-users, **fwupdmgr** is a more suitable program to use in almost all cases. Additionally **fwupdtool** can be used to convert firmware from various different formats, or to modify the images contained inside the container firmware file. For example, you can convert DFU or Intel HEX firmware into the vendor-specific format. ## OPTIONS The fwupdtool command takes various options depending on the action. Run **fwupdtool \-\-help** for the full list. Note that some runtimes failures can be ignored using **\-\-force**. ## EXIT STATUS Commands that successfully execute will return "0", with generic failure as "1". There are also several other exit codes used: A return code of "2" is used for commands that have no actions but were successfully executed, and "3" is used when a resource was not found. ## BUGS See GitHub Issues: ## SEE ALSO fwupd-1.9.16/src/meson.build000066400000000000000000000214321460375044200156750ustar00rootroot00000000000000if get_option('tests') subdir('tests') endif client_src = [] systemd_src = [] engine_dep = [ libarchive, libjcat, libxmlb, giounix, gmodule, gudev, gusb, libjsonglib, polkit, sqlite, cbor, fwupdplugin_rs_dep, ] if get_option('passim').allowed() engine_dep += passim endif client_dep = [ gudev, gusb, libcurl, libjcat, libjsonglib, libxmlb, sqlite, fwupdplugin_rs_dep, ] if libsystemd.found() systemd_src += 'fu-systemd.c' endif if polkit.found() client_src += 'fu-polkit-agent.c' endif fwupd_engine_src = [ 'fu-cabinet.c', 'fu-cabinet-common.c', 'fu-debug.c', 'fu-device-list.c', 'fu-engine.c', 'fu-engine-config.c', 'fu-engine-helper.c', 'fu-engine-request.c', 'fu-history.c', 'fu-idle.c', 'fu-polkit-authority.c', 'fu-release.c', 'fu-engine-requirements.c', 'fu-release-common.c', 'fu-plugin-list.c', 'fu-remote-list.c', 'fu-security-attr-common.c', 'fu-client.c', 'fu-client-list.c', ] + systemd_src if gudev.found() fwupd_engine_src += 'fu-udev-backend.c' endif if gusb.found() fwupd_engine_src += 'fu-usb-backend.c' endif if bluez.allowed() fwupd_engine_src += 'fu-bluez-backend.c' endif # include event message file if host_machine.system() == 'windows' windmc = find_program('windmc') fwupd_rc = custom_target('fwupd-rc', input: 'fwupd-windows.mc', output: 'fwupd-windows.rc', command: [ windmc, '@INPUT@', '--rcdir', meson.current_build_dir(), ], ) windows = import('windows') fwupd_engine_src += windows.compile_resources(fwupd_rc) endif fwupdutil = library( 'fwupdutil', sources: [ 'fu-console.c', 'fu-util-bios-setting.c', 'fu-util-common.c', systemd_src, ], install: true, install_rpath: libdir_pkg, install_dir: libdir_pkg, include_directories: [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies: [ client_dep, ], link_with: [ fwupd, fwupdplugin, ], ) dbus_interface = custom_target('fwupd-generate-dbus-interface', input : 'org.freedesktop.fwupd.xml', output : 'org.freedesktop.fwupd.xml', command : [ generate_dbus_interface, '@INPUT@', '@OUTPUT@', ], install: build_daemon, install_dir: join_paths(datadir, 'dbus-1', 'interfaces'), ) if build_daemon fwupdmgr = executable( 'fwupdmgr', sources: [ 'fu-util.c', client_src, ], include_directories: [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies: [ libfwupd_deps, client_dep, ], link_with: [ fwupd, fwupdplugin, fwupdutil, ], install: true, install_rpath: libdir_pkg, install_dir: bindir ) # for compatibility if get_option('compat_cli') install_symlink('fwupdagent', install_dir: join_paths(get_option('prefix'), get_option('bindir')), pointing_to: 'fwupdmgr', ) endif endif if offline.allowed() fwupdoffline = executable( 'fwupdoffline', sources: [ 'fu-history.c', 'fu-offline.c', 'fu-spawn.c', systemd_src ], include_directories: [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies: [ libfwupd_deps, client_dep, ], link_with: [ fwupd, fwupdplugin, fwupdutil, ], install: true, install_rpath: libdir_pkg, install_dir: join_paths(libexecdir, 'fwupd') ) endif resources_src = gnome.compile_resources( 'fwupd-resources', 'fwupd.gresource.xml', c_name: 'fu', dependencies: [dbus_interface], ) # generate a header file that allows us to instantiate the plugins without copy-pasting or # duplicating the meson build logic in the engine plugin_names = [] foreach lib : plugin_builtins plugin_names += lib.full_path() endforeach plugins_hdr = custom_target('fwupd-generate-plugins-header', output : 'fu-plugin-builtin.h', command : [ generate_plugins_header, '@OUTPUT@', '.', ','.join(plugin_names), ], ) # build all the plugins and engine into one installed library fwupdengine_rs = custom_target('fu-engine-rs', input: 'fu-engine.rs', output: ['fu-engine-struct.c', 'fu-engine-struct.h'], command: [ python3, join_paths(meson.project_source_root(), 'libfwupdplugin', 'rustgen.py'), '@INPUT@', '@OUTPUT0@', '@OUTPUT1@', ], ) fwupdengine = library( 'fwupdengine', fwupdengine_rs, resources_src, plugins_hdr, sources: fwupd_engine_src, install: true, install_rpath: libdir_pkg, install_dir: libdir_pkg, include_directories: plugin_incdirs, dependencies: [ engine_dep, ], link_whole: [ plugin_builtins, ], link_with: [ fwupd, fwupdplugin, ], ) fwupdtool = executable( 'fwupdtool', fwupdengine_rs, plugins_hdr, export_dynamic: true, sources: [ 'fu-tool.c', ], include_directories: [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies: [ libfwupd_deps, libarchive, client_dep, valgrind, ], link_with: [ fwupdengine, fwupdutil, plugin_libs, ], install: true, install_rpath: libdir_pkg, install_dir: bindir ) if build_daemon if get_option('man') custom_target('fwupdmgr.1', input: 'fwupdmgr.md', output: 'fwupdmgr.1', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, ], install: true, install_dir: join_paths(mandir, 'man1'), ) endif if build_docs md_targets += custom_target('fwupdmgr.md', input: 'fwupdmgr.md', output: 'fwupdmgr.md', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, '--md', ], ) man_md += ['"fwupdmgr.md"'] endif if get_option('compat_cli') if get_option('man') custom_target('fwupdagent.1', input: 'fwupdagent.md', output: 'fwupdagent.1', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, ], install: true, install_dir: join_paths(mandir, 'man1'), ) endif if build_docs md_targets += custom_target('fwupdagent.md', input: 'fwupdagent.md', output: 'fwupdagent.md', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, '--md', ], ) man_md += ['"fwupdagent.md"'] endif endif endif if build_standalone if get_option('man') custom_target('fwupdtool.1', input: 'fwupdtool.md', output: 'fwupdtool.1', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, ], install: true, install_dir: join_paths(mandir, 'man1'), ) endif if build_docs md_targets += custom_target('fwupdtool.md', input: 'fwupdtool.md', output: 'fwupdtool.md', command: [ generate_man, '@INPUT@', '-o', '@OUTPUT@', '--replace', 'PACKAGE_VERSION', fwupd_version, '--md', ], ) man_md += ['"fwupdtool.md"'] endif endif if build_daemon # the StartServiceCtrlDispatcherA design is so different use a different source file if host_machine.system() == 'windows' daemon_loader_src = 'fu-main-windows.c' else daemon_loader_src = 'fu-main.c' endif fwupddaemon_rs = custom_target('fu-daemon-rs', input: 'fu-daemon.rs', output: ['fu-daemon-struct.c', 'fu-daemon-struct.h'], command: [ python3, join_paths(meson.project_source_root(), 'libfwupdplugin', 'rustgen.py'), '@INPUT@', '@OUTPUT0@', '@OUTPUT1@', ], ) executable( 'fwupd', fwupdengine_rs, fwupddaemon_rs, plugins_hdr, sources: [ daemon_loader_src, 'fu-daemon.c', ], include_directories: [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies: [ valgrind, libsystemd, engine_dep, ], link_with: [ fwupdengine, plugin_libs, ], c_args: [ '-DFU_OFFLINE_DESTDIR=""', ], install: true, install_rpath: libdir_pkg, install_dir: daemon_dir ) endif if get_option('tests') env = environment() env.set('G_TEST_SRCDIR', meson.current_source_dir()) env.set('G_TEST_BUILDDIR', meson.current_build_dir()) env.set('FWUPD_LOCALSTATEDIR', '/tmp/fwupd-self-test/var') e = executable( 'fu-self-test', fwupdengine_rs, colorhug_test_firmware, hwid_test_firmware, multiple_rels_test_firmware, noreqs_test_firmware, plugins_hdr, sources: [ 'fu-spawn.c', 'fu-self-test.c', ], include_directories: [ root_incdir, fwupd_incdir, fwupdplugin_incdir, ], dependencies: [ engine_dep, ], link_with: [ fwupdengine, fwupdutil, plugin_libs, ], c_args: [ '-DSRCDIR="' + meson.current_source_dir() + '"', ], ) test('fu-self-test', e, is_parallel: false, timeout: 180, env: env) endif fwupd-1.9.16/src/org.freedesktop.fwupd.xml000066400000000000000000001017731460375044200205110ustar00rootroot00000000000000 The interface used for querying firmware for the system. The daemon version. The optional best known configuration to use when syncing back to a known state, e.g. vendor-factory-2021q1,mycompany-2023. The vendor name string for the host. The product name string for the host. The machine ID for the host. The Host Security ID, for instance HSI:2 If the daemon has been tainted with a 3rd party plugin. If the daemon is running on an interactive terminal. The daemon status, e.g. decompressing. The job percentage completion, or 0 for unknown. Returns the system battery level, or 101 for unknown. Returns the system battery threshold under which a firmware update cannot be performed. If the daemon requires trusted payloads. Gets a list of all the devices that are supported. An array of devices, with any properties set on each. Gets a list of all the plugins being used by the daemon. An array of plugins, with any properties set on each. Gets a list of all the releases for a specific device. A device ID. An array of releases (with the release number as the key), with any properties set on each. Gets a list of all the downgrades possible for a specific device. A device ID. An array of releases (with the release number as the key), with any properties set on each. Gets a list of all the upgrades possible for a specific device. A device ID. An array of releases (with the release number as the key), with any properties set on each. Gets details about a local firmware file. An index into the array of file descriptors that may have been sent with the DBus message. An array of results, with any properties set on each. Gets a list of all the past firmware updates. An array of devices, with any properties set on each. Gets a list of all the Host Security ID attributes. An array of HSI attributes, with any properties set on each. Gets a list of all the Host Security ID events. The maximum number of events, or 0 for no limit. An array of HSI attributes, with any properties set on each. Gets metadata to include with the firmware and security reports. An array of string key values. Sets optional hints from the client that may affect the list of devices. A typical hint might be locale and unknown hints should be ignored. An array of string key values. Schedules a firmware to be installed. An ID, typically a GUID of the hardware to update, or the string * to match any applicable hardware. An index into the array of file descriptors that may have been sent with the DBus message. Options to be used when constructing the profile, e.g. offline=True. Verifies firmware on a device by reading it back and performing a cryptographic hash, typically SHA1. An ID, typically a GUID of the hardware. Updates the cryptographic hash stored for a device. An ID, typically a GUID of the hardware. Unlock the device to allow firmware access. An ID, typically a GUID of the hardware. Activate a firmware update on the device. An ID, typically the sha hash of the device string. Gets the results of an offline update. An ID, typically a GUID of the hardware that was updated, or the string * to match any hardware. Results about the update, e.g. success=True Gets the list of remotes. The array remotes, with properties Gets the list of approved firmware that can be applied to devices. In an enterprise this will be configured by a domain administrator. The checksums of the archives Sets the list of approved firmware that can be applied to devices. In an enterprise this will be configured by a domain administrator. The checksums of the archives Gets the list of blocked firmware. The checksums of the archives Sets the list of blocked firmware that can be applied to devices. The checksums of the archives Sets the features the client supports. This allows firmware to depend on specific front-end features, for instance showing the user an image on how to detach the hardware. The features the front end supports Clears the results of an offline update. An ID, typically a GUID of the hardware that was updated, or the string * to match any hardware. Modifies a remote in some way. A device ID, or the string * to match any hardware. The key, e.g. 'Flags'. The value of the correct type, e.g. a URL. Modify persistent configuration for daemon The key, e.g. 'DisabledPlugins'. The value of the correct type, e.g. a URL. Adds AppStream resource information from a session client. Remote ID to tag the metadata objects with, e.g. 'lvfs-testing'. File handle to AppStream metadata. File handle to AppStream metadata GPG signature. Modifies a remote in some way. Remote ID, e.g. 'lvfs-testing'. The key, e.g. 'Enabled'. The value of the correct type, e.g. a URL. Fix a specific HSI attribute. The fwupd AppStream ID, e.g. 'org.fwupd.hsi.Kernel.Lockdown'. Revert the fix for a specific HSI attribute. The fwupd AppStream ID, e.g. 'org.fwupd.hsi.Kernel.Lockdown'. Signs some text, typically using a self-signed PKCS-7 certificate. String input data, certainly *NOT* binary data. Options to be used when signing, e.g. add-cert=True or add-timestamp=True. The detached signature string. Modify BIOS setting An array of BIOS settings and their new values. Gets a list of all the BIOS settings. An array of BIOS settings, with any properties set on each. Marks the system as unavailable for update. A reason, e.g. device is being used to capture content. The token that was used for inhibiting. Removes the inhibit token added by the application, but only if there is no other inhibit imposed by other applications or by the system (e.g. low power state). The token to use for uninhibiting. Ask the daemon to quit. This can only be called by the root user. Load emulation data. JSON data of each phase packaged as a ZIP archive (e.g. setup.json, install.json, reload.json). Return emulation data. JSON data of each phase packaged as a compressed ZIP archive (e.g. setup.json, install.json, reload.json). Some value on the interface or the number of devices or profiles has changed. A device structure. A device has been added. A device structure. A device has been removed. A device structure. A device has been changed. A device request. A device request to the client. fwupd-1.9.16/src/tests/000077500000000000000000000000001460375044200146735ustar00rootroot00000000000000fwupd-1.9.16/src/tests/bios-attrs/000077500000000000000000000000001460375044200167625ustar00rootroot00000000000000fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/000077500000000000000000000000001460375044200217215ustar00rootroot00000000000000fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/000077500000000000000000000000001460375044200241075ustar00rootroot00000000000000fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Absolute/000077500000000000000000000000001460375044200256655ustar00rootroot00000000000000fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Absolute/current_value000066400000000000000000000000101460375044200304550ustar00rootroot00000000000000Disabledfwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Absolute/default_value000066400000000000000000000000101460375044200304170ustar00rootroot00000000000000Enabled fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Absolute/dell_modifier000066400000000000000000000000011460375044200303750ustar00rootroot00000000000000 fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Absolute/dell_value_modifier000066400000000000000000000000011460375044200315710ustar00rootroot00000000000000 fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Absolute/display_name000066400000000000000000000000111460375044200302450ustar00rootroot00000000000000Absolute fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Absolute/display_name_language_code000066400000000000000000000000061460375044200331060ustar00rootroot00000000000000en-US fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Absolute/possible_values000066400000000000000000000000461460375044200310070ustar00rootroot00000000000000Enabled;Disabled;PermanentlyDisabled; fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Absolute/type000066400000000000000000000000141460375044200265640ustar00rootroot00000000000000enumeration fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Asset/000077500000000000000000000000001460375044200251665ustar00rootroot00000000000000fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Asset/current_value000066400000000000000000000000011460375044200277560ustar00rootroot000000000000001fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Asset/default_value000066400000000000000000000000121460375044200277220ustar00rootroot00000000000000Asset Tag fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Asset/dell_modifier000066400000000000000000000000011460375044200276760ustar00rootroot00000000000000 fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Asset/display_name000066400000000000000000000000121460375044200275470ustar00rootroot00000000000000Asset Tag fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Asset/display_name_language_code000066400000000000000000000000061460375044200324070ustar00rootroot00000000000000en-US fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Asset/max_length000066400000000000000000000000031460375044200272300ustar00rootroot0000000000000064 fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Asset/min_length000066400000000000000000000000021460375044200272250ustar00rootroot000000000000001 fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/Asset/type000066400000000000000000000000071460375044200260670ustar00rootroot00000000000000string fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/CustomChargeStop/000077500000000000000000000000001460375044200273415ustar00rootroot00000000000000fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/CustomChargeStop/current_value000066400000000000000000000000031460375044200321330ustar00rootroot0000000000000070 fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/CustomChargeStop/default_value000066400000000000000000000000031460375044200320750ustar00rootroot0000000000000090 fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/CustomChargeStop/dell_modifier000066400000000000000000000000011460375044200320510ustar00rootroot00000000000000 fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/CustomChargeStop/display_name000066400000000000000000000000231460375044200317240ustar00rootroot00000000000000Custom Charge Stop display_name_language_code000066400000000000000000000000061460375044200345030ustar00rootroot00000000000000fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/CustomChargeStopen-US fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/CustomChargeStop/max_value000066400000000000000000000000041460375044200312370ustar00rootroot00000000000000100 fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/CustomChargeStop/min_value000066400000000000000000000000031460375044200312340ustar00rootroot0000000000000055 fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/CustomChargeStop/scalar_increment000066400000000000000000000000021460375044200325650ustar00rootroot000000000000001 fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/CustomChargeStop/type000066400000000000000000000000101460375044200302340ustar00rootroot00000000000000integer fwupd-1.9.16/src/tests/bios-attrs/fwupd-internal/attributes/pending_reboot000066400000000000000000000000021460375044200270200ustar00rootroot000000000000000 fwupd-1.9.16/src/tests/colorhug/000077500000000000000000000000001460375044200165155ustar00rootroot00000000000000fwupd-1.9.16/src/tests/colorhug/colorhug-als-3.0.2.cab000066400000000000000000000232171460375044200222260ustar00rootroot00000000000000MSCF&,@TSSG firmware.bin@TSSG firmware.bin.asc!TSSG firmware.metainfo.xml%%1 (~1 p~ 1(00'012!1 00@012!100@012!1@000001,!~ 1*! 02=!4W(4  9GHY(KJj(0G91H!!!3{(3  9GH}(ML(0G")! 0000@001!1# !Gw)0(1T!1(#!1!1)1'!1(#!18!1)1p!1!G")#!1!1)0 >000! ()00>00! ()0 >000! ()0!0000 >000! ))!^ !_ 1#1 W! V! U! T!0 >000! 5))$0!G)#!:N)!:N) 0!Gr)0?0@001x#1!GU)0?0000G1$1!H) )T:(:(:(:(:(:(:( :(:(:(/: )::) :@):(t)!3)3  9GH)ON)UG"!TG" 0!G@0H011#1!I0!G@0H011#!I B:*@98*! A:)! *! B@9:D9:D90[!2 : ,*D9>[D9>8*D9j>[D9>[_E*0[F*`N*`D9>S*`D9j>[A:t*`l*f*D9>i*D9>0`[0[`*0`[`00`[001$1* `0 [*`**D9>*D9>;0`[00`[001$1*;0 `[ D9>0=[\0]^[!*=00{0**! 2:+=+0 g!=+0 g=1!1=!+ 3+1%10 g!0 g=?+1!10 g=V+P+s0 0001$10 g=\+1!1=o+0 0001$1=0!2w+ =+ + go g 9h!0 g= o+!8>+!8>9 0g9g!+1%1+r0 0o0g001$10 g0i+=}+ 0 000 X0000Wc+Vb+Ua+T`,d + , X00=0=0=+Wc,Vb,Ua,T` ,00000000Wc6,Vb6,Ua6,T`b,d Y, U,00=0=0=hgfeU,0000 X00=0=0=(,1T!1 Xd >0>[ZYXhgfe1C"1t srqh_,g^,f],e\,0000hgfe ,! H!.:, ,!,! ,D9>,D9j>qr,r,0q,@9:,:,:,,!H00@0q44W44q4484f404644444444 44)4444444 444444444 4!44444"4444444@4444444@444444 444444)4@444&444u444@44444)4@444444H44u44g44h44s44k44i44 44L44t44d44.4444C44o44l44o44r44H44u44g44A44L44S4444444444?4'44444444444 44!=00u-st00=0qu-0=0{00v-v05>-q5 >0?0???0q0v-==-0q!-0q!0q0v!.vj>v>v>v>0q0v.(0q 0=@00q r00q! nA?vt!$L.s#R.#s0>v  @?sA?t=vu}.uH> s0s0se.0>.0| sH00|>s?t? n.0|s0|s0| s@00|>s?t?0|s! 1p% @9:DA:./!C:/#00 0.:0!0000q/ C!:.:.:// @ 9:@/0!0000q0q! Cq!0!0000q0q! Bq! A:U/: /:&/ :U/:/:4/U/!0 0]q/[\00=0{]a/ 0000 001!1= 0^/^>^>0{0^/0000001!1=(0{ 0{n{B{!/0{0 00{001$ 0{! x/R1u"1/ R1u" R>0=yzPQx1,1,1,1,1,1m,1v,1,1|,1y,1,1,1,  9 -)4d1 3)4q 44444444K)Q)!9$:I)Q)!  905 Z)9q 00 f) f)20b)  905 v) r 9qr9 q9qr69 q9q!t0 0001$=0{=0{! u0 0001$r9 q9qr69 q9qrq00;0v)0uwxs00=)=* n:*j:*0nq=0q m: *1<& !A*0| w@00|>w?x?0|w0 000qW*u v=w=x=05 X*06 _*tsrqL* SRQP 0rq05 }*v05>*s5r05>*t5uw*wv>*wvj>rww>=*01a!1018!101!101!11%1!0W*018!1_01a!1018!1_01a!10!V0W*d1"11= 1*! ! 0jb0b0jb0b0|b!1e$10 0001$11&q8+vj>:+v>tE+0r0w>t?u?sw t@0wt0wtqn+0vj>r+0v>tw@0r0q+0?0q0rvu+0?0s0tvu+006 +tx+sw+#wqxr=uvU00 00=+#0! m:+0| tH00|>t?u?0|t!B G+!A F+G! F!19&H00 j>t?u?0jtJ,1p!1 X!01!1018!!;1!0 X01'10 X@0Y011# Z!1& T:P,d,S:+,:G,:d,w:d,:&,:,d, @ 9:,!0 a!1#'1V'0!0@ 1$1! D>!0@ ! BD>a00001$ A 015>@0r0q,006 ,ty,sx-#rq00=xuv0x>0y=tw,sv-x>uv0U0000=,#0 j000t!2:3- B=.-0/-0t! m:j-H00j>t?u?19&1!=:U-0 jta-0 jt0 jt! !0q0q m:-j 0jqm:!0 n q@00n>q?r?0nq! 0| q@00|>q?r?0|q o9..o6?905>-c5 >dnc0cn 9 :+.0f.n>@?A?cdf@>en>0A1 0c0f-@00n>c?d?1"1"0 oc:1%0q0!B0AW.AqO. W.=:W.0q0>s  @?qA?r=s j qH0q!@..>?<q00=0q0y.>?<q00=0q0.=!..0v !.1#!0| v@00|>v?w?0|v0 jv . !.1#1! 1p%0v 0| v@00|>v?w?0|v! 1p% @:0q!/0!000 B5 0q0r=?!??>0qrr s?>0qrr s0 B/B5 0q0r=?!?>?qr!! C:./:8/:h//-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJVLPMHAAoJEK2KUo/sRIgeAxAH/jPY7c2qrG4UEsZXgUFxMUQe QEufh3cK9cv8kA7SAzpSHy6M0rNanC2vCqcc/fTJI/yBRfBjPPZYEsQgwpB/8m9y wiTPRuQySwCKsH+ZXNh3j6x8Oaf3DTiO7bJI/M3sOb4fdvb0Csp910g67Nt+HtMw I5EUM0uvMquZTUygp9B6BBJv8xRKtCNgqvPhyoDZKxKrPzaFwvb7BY50Q03LymU6 hQUIkjHIvMcTljNocOZNvTBHvEGB2BiBb60QhAXYyNfDrS58pm2JHfw/pgOuQTzT 3Lw9qmedRXbWR95u/piUmyUsY5ey75lD08U/2aE9RLBZ9xR17u1mAgyLGoIMYEk= =ZdoH -----END PGP SIGNATURE----- com.hughski.ColorHugALS.firmware ColorHugALS Firmware Firmware for the ColorHugALS Ambient Light Sensor

    Updating the firmware on your ColorHugALS device improves performance and adds new features.

    84f40464-9272-4ef7-9399-cd95f12da696 12345678-1234-1234-1234-123456789012 http://www.hughski.com/ CC0-1.0 GPL-2.0+ richard_at_hughsie.com Hughski Limited

    This stable release fixes the following bugs:

    • Fix the return code from GetHardwareVersion
    • Scale the output of TakeReadingRaw by the datasheet values
    fwupd-1.9.16/src/tests/colorhug/firmware.bin000077700000000000000000000000001460375044200322042../../../libfwupdplugin/tests/colorhug/firmware.binustar00rootroot00000000000000fwupd-1.9.16/src/tests/conf-migration-1.7/000077500000000000000000000000001460375044200201125ustar00rootroot00000000000000fwupd-1.9.16/src/tests/conf-migration-1.7/fwupd/000077500000000000000000000000001460375044200212375ustar00rootroot00000000000000fwupd-1.9.16/src/tests/conf-migration-1.7/fwupd/daemon.conf000066400000000000000000000036121460375044200233530ustar00rootroot00000000000000[fwupd] # Allow blocking specific devices by their GUID # Uses semicolons as delimiter DisabledDevices= # Allow blocking specific plugins # Uses semicolons as delimiter DisabledPlugins=test;test_ble;invalid # Maximum archive size that can be loaded in Mb, with 0 for the default ArchiveSizeMax=0 # Idle time in seconds to shut down the daemon -- note some plugins might # inhibit the auto-shutdown, for instance thunderbolt. # # A value of 0 specifies 'never' IdleTimeout=7200 # Comma separated list of domains to log in verbose mode # If unset, no domains # If set to FuValue, FuValue domain (same as --domain-verbose=FuValue) # If set to *, all domains (same as --verbose) VerboseDomains= # Update the message of the day (MOTD) on device and metadata changes UpdateMotd=true # For some plugins, enumerate only devices supported by metadata EnumerateAllDevices=false # A list of firmware checksums that has been approved by the site admin # If unset, all firmware is approved ApprovedFirmware= # Allow blocking specific devices by their checksum, either SHA1 or SHA256 # Uses semicolons as delimiter BlockedFirmware= # Allowed URI schemes in the preference order; failed downloads from the first # scheme will be retried with the next in order until no choices remain. # # If unset or no schemes are listed, the default will be: file,https,http,ipfs UriSchemes= # Ignore power levels of devices when running updates IgnorePower=false # Only support installing firmware signed with a trusted key OnlyTrusted=true # A host best known configuration is used when using `fwupdmgr sync` which can # downgrade firmware to factory versions or upgrade firmware to a supported # config level. e.g. `vendor-factory-2021q1` HostBkc= # these are only required when the SMBIOS or Device Tree data is invalid or missing #Manufacturer= #ProductName= #ProductSku= #Family= #EnclosureKind= #BaseboardProduct= #BaseboardManufacturer= fwupd-1.9.16/src/tests/conf-migration-1.7/fwupd/msr.conf000066400000000000000000000001361460375044200227070ustar00rootroot00000000000000[msr] # Minimum kernel version to allow probing for sme flag MinimumSmeKernelVersion=5.18.0 fwupd-1.9.16/src/tests/conf-migration-1.7/fwupd/redfish.conf000066400000000000000000000007461460375044200235410ustar00rootroot00000000000000[redfish] # The URI to the Redfish service in the format ://: # ex: https://192.168.0.133:443 #Uri= # The username and password to the Redfish service #Username= #Password= # Whether to verify the server certificate or not # Expected value: TRUE or FALSE # Default: FALSE #CACheck= # Do not use IPMI KCS to create an initial user account if no SMBIOS data IpmiDisableCreateUser=False # Amount of time in seconds to wait for a BMC restart ManagerResetTimeout=1800 fwupd-1.9.16/src/tests/conf-migration-1.7/fwupd/thunderbolt.conf000066400000000000000000000004221460375044200244360ustar00rootroot00000000000000[thunderbolt] # Minimum kernel version to allow use of this plugin # It's important that all backports from this kernel have been # made if using an older kernel MinimumKernelVersion=4.13.0 # Forces delaying activation until shutdown/logout/reboot DelayedActivation=false fwupd-1.9.16/src/tests/conf-migration-1.7/fwupd/uefi_capsule.conf000066400000000000000000000011571460375044200245560ustar00rootroot00000000000000[uefi_capsule] # use GRUB to launch fwupdx64.efi #EnableGrubChainLoad=false # the shim loader is required to chainload the fwupd EFI binary unless # the fwupd.efi file has been self-signed manually #DisableShimForSecureBoot=true # amount of free space required on the ESP, for example using 0x2000000 for 32Mb #RequireESPFreeSpace= # with the UEFI removable path enabled, the default esp path is set to /EFI/boot # the shim EFI binary and presumably this is $ESP/EFI/boot/bootx64.efi #FallbacktoRemovablePath=false # allow ignoring the CapsuleOnDisk support advertised by the firmware #DisableCapsuleUpdateOnDisk=true fwupd-1.9.16/src/tests/dmi000077700000000000000000000000001460375044200227602../../libfwupdplugin/tests/dmi/ustar00rootroot00000000000000fwupd-1.9.16/src/tests/fakedevice123.bin000077700000000000000000000000001460375044200273602../../data/installed-tests/fakedevice123.binustar00rootroot00000000000000fwupd-1.9.16/src/tests/fwupd.conf000066400000000000000000000002601460375044200166650ustar00rootroot00000000000000[fwupd] TrustedReports=VendorId=123&DistroId=fedora&RemoteId=lvfs&DistroVariant=workstation;VendorId=$OEM&DistroId=chromeos&Flags=is-upgrade Manufacturer=ACME TestDevices=true fwupd-1.9.16/src/tests/history_v1.db000066400000000000000000000300001460375044200173020ustar00rootroot00000000000000SQLite format 3@ . == itablehistoryhistoryCREATE TABLE history ( device_id TEXT PRIMARY KEY, update_state INTEGER DEFAULT 0, update_error TEXT, filename TEXT, display_name TEXT, plugin TEXT, device_created INTEGER DEFAULT 0, device_modified INTEGER DEFAULT 0, checksum TEXT DEFAULT NULL, flags INTEGER DEFAULT 0, metadata TEXT DEFAULT NULL, guid_default TEXT DEFAULT NULL, version_old TEXT, version_new TEXT)-Aindexsqlite_autoindex_history_1history 7]2ba16d10df45823dd4494ff10a0bfccfef512c9d +] 2ba16d10df45823dd4494ff10a0bfccfef512c9dfwupd-1.9.16/src/tests/history_v2.db000066400000000000000000000500001460375044200173050ustar00rootroot00000000000000SQLite format 3@ .j / = = = =o5tablehistoryhistoryCREATE TABLE history (device_id TEXT,update_state INTEGER DEFAULT 0,update_error TEXT,filename TEXT,display_name TEXT,plugin TEXT,device_created INTEGER DEFAULT 0,device_modified INTEGER DEFAULT 0,checksum TEXT DEFAULT NULL,flags INTEGER DEFAULT 0,metadata TEXT DEFAULT NULL,guid_default TEXT DEFAULT NULL,version_old TEXT,version_new TEXT)~WtableschemaschemaCREATE TABLE schema (created timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,version INTEGER DEFAULT 0) 32024-02-19 10:09:46 7]2ba16d10df45823dd4494ff10a0bfccfef512c9dfwupd-1.9.16/src/tests/host-emulate/000077500000000000000000000000001460375044200173025ustar00rootroot00000000000000fwupd-1.9.16/src/tests/host-emulate/meson.build000066400000000000000000000006111460375044200214420ustar00rootroot00000000000000if build_standalone gzip = find_program('gzip') foreach input_file : [ 'thinkpad-p1-iommu.json', ] custom_target(input_file, input: input_file, output: '@0@.gz'.format(input_file), capture: true, command: [gzip, '-k', '--stdout', '@INPUT@'], install: true, install_dir: join_paths(datadir, 'fwupd', 'host-emulate.d'), ) endforeach endif fwupd-1.9.16/src/tests/host-emulate/thinkpad-p1-iommu.json000066400000000000000000000437271460375044200234560ustar00rootroot00000000000000{ "SecurityAttributes": [ { "AppstreamId": "org.fwupd.hsi.Kernel.Tainted", "HsiResult": "not-tainted", "Plugin": "linux_tainted", "Flags": [ "success", "runtime-issue" ] }, { "AppstreamId": "org.fwupd.hsi.Kernel.Lockdown", "HsiResult": "enabled", "Plugin": "linux_lockdown", "Flags": [ "success", "runtime-issue" ] }, { "AppstreamId": "org.fwupd.hsi.Kernel.Swap", "HsiResult": "encrypted", "Plugin": "linux_swap", "Flags": [ "success", "runtime-issue" ] }, { "AppstreamId": "org.fwupd.hsi.Uefi.SecureBoot", "HsiResult": "enabled", "Plugin": "uefi_capsule", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.Fwupd.Plugins", "HsiResult": "not-tainted", "Plugin": "core", "Flags": [ "success", "runtime-issue" ] }, { "AppstreamId": "org.fwupd.hsi.Mei.ManufacturingMode", "HsiResult": "locked", "Plugin": "pci_mei", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.Mei.OverrideStrap", "HsiResult": "locked", "Plugin": "pci_mei", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.Mei.Version", "HsiResult": "valid", "Plugin": "pci_mei", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.PlatformDebugEnabled", "HsiResult": "not-enabled", "Plugin": "msr", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.Spi.SmmBwp", "HsiResult": "locked", "Plugin": "pci_bcr", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.Spi.Ble", "HsiResult": "enabled", "Plugin": "pci_bcr", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.Spi.Bioswe", "HsiResult": "not-enabled", "Plugin": "pci_bcr", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.SupportedCpu", "HsiResult": "valid", "Plugin": "cpu", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.Tpm.EmptyPcr", "HsiResult": "valid", "Plugin": "tpm", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.Tpm.Version20", "HsiResult": "found", "Plugin": "tpm", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.Uefi.Pk", "HsiResult": "valid", "Plugin": "uefi_pk", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.IntelBootguard.Enabled", "HsiResult": "enabled", "Plugin": "pci_mei", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.IntelBootguard.Acm", "HsiResult": "valid", "Plugin": "pci_mei", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.IntelBootguard.Otp", "HsiResult": "valid", "Plugin": "pci_mei", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.IntelBootguard.Verified", "HsiResult": "valid", "Plugin": "pci_mei", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.PlatformDebugLocked", "HsiResult": "locked", "Plugin": "msr", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.Tpm.ReconstructionPcr0", "HsiResult": "valid", "Plugin": "tpm", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.Iommu", "HsiResult": "found", "Plugin": "iommu", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.IntelBootguard.Policy", "HsiResult": "valid", "Plugin": "pci_mei", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.IntelCet.Enabled", "HsiResult": "not-supported", "Plugin": "cpu" }, { "AppstreamId": "org.fwupd.hsi.PrebootDma", "HsiResult": "not-enabled", "Plugin": "acpi_dmar", "Flags": [ "action-contact-oem", "action-config-fw" ] }, { "AppstreamId": "org.fwupd.hsi.SuspendToIdle", "HsiResult": "not-enabled", "Plugin": "acpi_facp", "Flags": [ "action-config-fw", "action-config-os" ] }, { "AppstreamId": "org.fwupd.hsi.SuspendToRam", "HsiResult": "enabled", "Plugin": "linux_sleep", "Flags": [ "action-config-fw", "action-config-os" ] }, { "AppstreamId": "org.fwupd.hsi.IntelSmap", "HsiResult": "enabled", "Plugin": "cpu", "Flags": [ "success" ] }, { "AppstreamId": "org.fwupd.hsi.EncryptedRam", "HsiResult": "not-supported", "Plugin": "cpu" } ], "Devices": [ { "DeviceId": "4a4907dbb1b96c6a1177dfd1b95eb41c781d1265", "InstanceIds": [ "GPIO\\ID_INT3450:00" ], "Guid": [ "1199a818-4c52-5137-b536-d59e2e2cada9" ], "Flags": [ "registered" ] }, { "Name": "AMT [unprovisioned]", "DeviceId": "5fed1486be004d67ea79838d2e83aaa11bb72645", "ParentDeviceId": "a45df35ac0e948ee180fe216a5f703f32dda163f", "CompositeId": "a45df35ac0e948ee180fe216a5f703f32dda163f", "InstanceIds": [ "MEI\\VEN_8086&DEV_06E0", "MEI\\VEN_8086&DEV_06E0&REV_00", "MEI\\VEN_8086&DEV_06E0&SUBSYS_17AA22C2", "MEI\\VEN_8086&DEV_06E0&SUBSYS_17AA22C2&REV_00" ], "Guid": [ "2800f812-b7b4-2d4b-aca8-46e0ff65814c", "15c7ef4d-12fc-5e25-aba6-49b30f5ab130", "a65d125e-0c76-5876-bad3-6956a2f25e5e", "a8f5ca2d-e46c-5c9b-819b-d64c73c9e48d", "45bcbef5-0630-5121-a4d9-881054b2916f" ], "Summary": "Hardware and firmware technology for remote out-of-band management", "Flags": [ "internal", "registered" ], "Vendor": "Intel Corporation", "VendorId": "MEI:0x8086", "Version": "14.1.53.1649", "VersionBootloader": "14.1.53.1649", "VersionFormat": "intel-me", "Icons": [ "computer" ] }, { "Name": "CometLake-H GT2 [UHD Graphics]", "DeviceId": "5792b48846ce271fab11c4a545f7a3df0d36e00a", "InstanceIds": [ "PCI\\VEN_8086&DEV_9BC4", "PCI\\VEN_8086&DEV_9BC4&REV_05", "PCI\\VEN_8086&DEV_9BC4&SUBSYS_17AA22C2", "PCI\\VEN_8086&DEV_9BC4&SUBSYS_17AA22C2&REV_05", "PCI\\VEN_8086&DEV_9BC4&REV_00", "PCI\\VEN_8086&DEV_9BC4&SUBSYS_17AA22C2&REV_00" ], "Guid": [ "3777783a-3f83-56a5-95f4-533eb6a2bd19", "6c3dbf6c-4e6f-5309-9954-c5ab7aca617e", "5fde5d20-db24-5f21-afdd-247c1bf1efa1", "07168636-0f3b-565c-8fe1-0f0a77d82cd8", "7ffe1cb7-395a-52a9-a172-70ec6caaf310", "b813dc18-ddf2-508d-a7eb-0e2fc8752b03" ], "Flags": [ "internal", "registered", "can-verify", "can-verify-image" ], "Vendor": "Intel Corporation", "VendorId": "PCI:0x8086", "Version": "05", "VersionFormat": "plain" }, { "Name": "Core\u2122 i7-10850H CPU @ 2.70GHz", "DeviceId": "4bde70ba4e39b28f9eab1628f9dd6e6244c03027", "InstanceIds": [ "CPUID\\PRO_0&FAM_06", "CPUID\\PRO_0&FAM_06&MOD_A5", "CPUID\\PRO_0&FAM_06&MOD_A5&STP_2" ], "Guid": [ "30249f37-d140-5d3e-9319-186b1bd5cac3", "a45b0522-5722-54bd-b802-86cd044262df", "7b9b6e8c-226c-5db6-86cb-ea3187578013" ], "Flags": [ "internal", "registered" ], "Vendor": "Intel", "Version": "0x000000f0", "VersionFormat": "hex", "VersionRaw": 240, "Icons": [ "computer" ] }, { "Name": "Embedded Controller", "DeviceId": "2292ae5236790b47884e37cf162dcf23bfcd1c60", "Guid": [ "b616d3d6-cca9-40bd-964e-b86ffb62744d" ], "Summary": "UEFI ESRT device", "Protocol": "org.uefi.capsule", "Flags": [ "internal", "updatable", "require-ac", "supported", "registered", "needs-reboot", "usable-during-update" ], "Vendor": "Lenovo", "VendorId": "DMI:LENOVO", "Version": "0.1.11", "VersionLowest": "0.1.11", "VersionFormat": "triplet", "VersionRaw": 65547, "VersionLowestRaw": 65547, "UpdateState": 2 }, { "Name": "Integrated Camera", "DeviceId": "0fef0a0c55f6442bffaebd774ae771341c89571b", "InstanceIds": [ "USB\\VID_13D3&PID_5405", "USB\\VID_13D3&PID_5405&REV_6004" ], "Guid": [ "9284c551-0b4c-51ee-905a-168b8787290c", "6f7c4f56-0085-5aa6-b443-823852a1cdbc" ], "Serial": "0000", "Protocol": "org.usb.dfu", "Flags": [ "updatable", "registered", "add-counterpart-guids" ], "Vendor": "Azurewave", "VendorId": "USB:0x13D3", "Version": "60.4", "VersionFormat": "bcd", "Icons": [ "camera-web" ] }, { "Name": "Intel Management Engine", "DeviceId": "349bb341230b1a86e5effe7dfe4337e1590227bd", "Guid": [ "5695cc48-4f4f-4677-8ffb-9f496d3ad9d3" ], "Summary": "UEFI ESRT device", "Protocol": "org.uefi.capsule", "Flags": [ "internal", "updatable", "require-ac", "supported", "registered", "needs-reboot", "usable-during-update" ], "Vendor": "Lenovo", "VendorId": "DMI:LENOVO", "Version": "225.53.1649", "VersionLowest": "0.0.1", "VersionFormat": "triplet", "VersionRaw": 3778348657, "VersionLowestRaw": 1, "UpdateState": 2 }, { "Name": "System Firmware", "DeviceId": "a45df35ac0e948ee180fe216a5f703f32dda163f", "Guid": [ "6e58e73d-8061-44e4-8949-33b7f0d5c726" ], "Summary": "UEFI ESRT device", "Protocol": "org.uefi.capsule", "Flags": [ "internal", "updatable", "require-ac", "supported", "registered", "needs-reboot", "can-verify", "usable-during-update" ], "Checksums": [ "73319eae91b5838c5a587c54ffb625b58746238b", "6d244e0fadc7cc866e902517b4fe24505e52d1023934d487b039641c726abc46" ], "Vendor": "Lenovo", "VendorId": "DMI:LENOVO", "Version": "0.1.23", "VersionLowest": "0.1.11", "VersionFormat": "triplet", "VersionRaw": 65559, "VersionLowestRaw": 65547, "Icons": [ "computer" ], "UpdateState": 2 }, { "Name": "THNSN5512GPU7 TOSHIBA", "DeviceId": "03281da317dccd2b18de2bd1cc70a782df40ed7e", "InstanceIds": [ "NVME\\VEN_1179&DEV_010F", "NVME\\VEN_1179&DEV_010F&REV_01", "NVME\\VEN_1179&DEV_010F&SUBSYS_11790001", "NVME\\VEN_1179&DEV_010F&SUBSYS_11790001&REV_01", "THNSN5512GPU7 TOSHIBA" ], "Guid": [ "83991323-9951-5adf-b743-d93e882a41e1", "e22c4520-43dc-5bb3-8245-5787fead9b63", "87178ed9-f82b-5895-bcb0-09713abc842c", "2060b01b-f6aa-5fea-9acd-804de1765920", "e1409b09-50cf-5aef-8ad8-760b9022f88d" ], "Serial": "37RS11EATAHT", "Summary": "NVM Express solid state drive", "Protocol": "org.nvmexpress", "Flags": [ "internal", "updatable", "require-ac", "registered", "needs-reboot", "usable-during-update", "signed-payload" ], "Vendor": "Toshiba Corporation", "VendorId": "NVME:0x1179", "Version": "410557LA", "VersionFormat": "plain", "Icons": [ "drive-harddisk" ] }, { "Name": "TPM", "DeviceId": "c6a80ac3a22083423992a3cb15018989f37834d6", "InstanceIds": [ "TPM\\VEN_STM&DEV_0001", "TPM\\VEN_STM&MOD_", "TPM\\VEN_STM&DEV_0001&VER_2.0", "TPM\\VEN_STM&MOD_&VER_2.0" ], "Guid": [ "ff71992e-52f7-5eea-94ef-883e56e034c6", "84df3581-f896-54d2-bd1a-372602f04c32", "bfaed10a-bbc1-525b-a329-35da2f63e918", "70b7b833-7e1a-550a-a291-b94a12d0f319", "06f005e9-cb62-5d1a-82d9-13c534c53c48" ], "Flags": [ "internal", "registered" ], "Vendor": "ST Microelectronics", "VendorId": "TPM:STM", "Version": "1.258.0.0", "VersionFormat": "quad", "VersionRaw": 282583078273024, "Icons": [ "computer" ] }, { "Name": "Thunderbolt host controller", "DeviceId": "2cff15412fb2877637de2c23a10f841bca114e03", "InstanceIds": [ "THUNDERBOLT\\VEN_0109&DEV_1913", "THUNDERBOLT\\VEN_0109&DEV_1913&REV_00", "TBT-01091913-native", "TBT-01091913-native-controller0-0" ], "Guid": [ "b510dc43-dc5d-5449-9ccc-5edd80338954", "b595a681-7c5b-5842-bba7-1d448c261a6e", "10216d57-c796-5f3c-83d3-21baf70bfc54", "f8543f13-e164-5332-8681-4d5ef3ffbff0" ], "Summary": "Unmatched performance for high-speed I/O", "Protocol": "com.intel.thunderbolt", "Flags": [ "internal", "updatable", "require-ac", "registered", "dual-image", "signed-payload" ], "Vendor": "Lenovo", "VendorId": "THUNDERBOLT:0x0109|TBT:0x0109", "VendorIds": [ "THUNDERBOLT:0x0109", "TBT:0x0109" ], "Version": "62.00", "VersionFormat": "pair", "Icons": [ "thunderbolt" ] }, { "Name": "UEFI Device Firmware", "DeviceId": "f95c9218acd12697af946874bfe4239587209232", "Guid": [ "439d54f4-5548-4698-a8b0-46a047c0e66e" ], "Summary": "UEFI ESRT device", "Protocol": "org.uefi.capsule", "Flags": [ "internal", "updatable", "require-ac", "registered", "needs-reboot", "usable-during-update" ], "VendorId": "DMI:LENOVO", "Version": "16842759", "VersionLowest": "1", "VersionFormat": "number", "VersionRaw": 16842759, "VersionLowestRaw": 1, "UpdateState": 2 }, { "Name": "UEFI Device Firmware", "DeviceId": "d96de5c124b60ed6241ebcb6bb2c839cb5580786", "Guid": [ "3fb9a55d-d7f1-4d1b-b216-74e328e28f51" ], "Summary": "UEFI ESRT device", "Protocol": "org.uefi.capsule", "Flags": [ "internal", "updatable", "require-ac", "registered", "needs-reboot", "usable-during-update" ], "VendorId": "DMI:LENOVO", "Version": "65794", "VersionLowest": "65794", "VersionFormat": "number", "VersionRaw": 65794, "VersionLowestRaw": 65794, "UpdateState": 2 }, { "Name": "UEFI Device Firmware", "DeviceId": "f37fb01122dd62c773f4e84ec89737e059712d59", "Guid": [ "33967546-da89-4c51-9c95-5242bcb854e8" ], "Summary": "UEFI ESRT device", "Protocol": "org.uefi.capsule", "Flags": [ "internal", "updatable", "require-ac", "registered", "needs-reboot", "usable-during-update" ], "VendorId": "DMI:LENOVO", "Version": "24580", "VersionLowest": "1", "VersionFormat": "number", "VersionRaw": 24580, "VersionLowestRaw": 1, "UpdateState": 2 }, { "Name": "UEFI dbx", "DeviceId": "362301da643102b9f38477387e2193e57abaa590", "ParentDeviceId": "a45df35ac0e948ee180fe216a5f703f32dda163f", "CompositeId": "a45df35ac0e948ee180fe216a5f703f32dda163f", "InstanceIds": [ "UEFI\\CRT_A9087D1044AD18F7A94916D284CBC01827CF23CD8F60B79072C9CAA1FEF4D649", "UEFI\\CRT_A9087D1044AD18F7A94916D284CBC01827CF23CD8F60B79072C9CAA1FEF4D649&ARCH_X64", "UEFI\\CRT_A1117F516A32CEFCBA3F2D1ACE10A87972FD6BBE8FE0D0B996E09E65D802A503", "UEFI\\CRT_A1117F516A32CEFCBA3F2D1ACE10A87972FD6BBE8FE0D0B996E09E65D802A503&ARCH_X64" ], "Guid": [ "14503b3d-73ce-5d06-8137-77c68972a341", "5971a208-da00-5fce-b5f5-1234342f9cf7", "c6682ade-b5ec-57c4-b687-676351208742", "f8ba2887-9411-5c36-9cee-88995bb39731" ], "Summary": "UEFI revocation database", "Protocol": "org.uefi.dbx", "Flags": [ "internal", "updatable", "registered", "needs-reboot", "only-version-upgrade", "signed-payload" ], "VendorId": "UEFI:Linux Foundation", "Version": "238", "VersionLowest": "238", "VersionFormat": "number", "Icons": [ "computer" ], "InstallDuration": 1 }, { "Name": "WDC PC SN720 SDAQNTW-256G-1001", "DeviceId": "08e1798bf5d9cb56a0290b552cab6c1a371b5089", "InstanceIds": [ "NVME\\VEN_15B7&DEV_5002", "NVME\\VEN_15B7&DEV_5002&REV_00", "NVME\\VEN_15B7&DEV_5002&SUBSYS_15B75002", "NVME\\VEN_15B7&DEV_5002&SUBSYS_15B75002&REV_00", "WDC PC SN720 SDAQNTW-256G-1001" ], "Guid": [ "ff2112dc-038c-596d-90ca-d43c5077c6ec", "137520ce-3603-53e6-9165-56694ed744e7", "c528df4b-7972-5880-8cb1-330415e2dc6a", "06a6f1f7-4ce0-57ef-8154-0705d936e4a6", "237776ee-0bcd-5fe9-8dc8-6984a2d36ba0" ], "Serial": "183985804591", "Summary": "NVM Express solid state drive", "Protocol": "org.nvmexpress", "Flags": [ "internal", "updatable", "require-ac", "supported", "registered", "needs-reboot", "usable-during-update" ], "Vendor": "Sandisk Corp", "VendorId": "NVME:0x15B7", "Version": "10190101", "VersionFormat": "plain", "Icons": [ "drive-harddisk" ] } ] } fwupd-1.9.16/src/tests/meson.build000066400000000000000000000001061460375044200170320ustar00rootroot00000000000000subdir('missing-hwid') subdir('multiple-rels') subdir('host-emulate') fwupd-1.9.16/src/tests/metadata-report1.xml000066400000000000000000000023231460375044200205670ustar00rootroot00000000000000 org.fwupd.something.device 2d47f29b-83a2-4f31-a2e8-63474f4d4c2e

    Applying will enable UEFI firmware reporting

    https://fwupd.org/something.cab 0628376cb0583335a7ff5875dd2d4b09fc5653057f644783a477553ca9981dcd Hughski LENOVO ThinkPad X1 Carbon 7th fedora 1.2.6 1.6.2
    fwupd-1.9.16/src/tests/metadata-report2.xml000066400000000000000000000023271460375044200205740ustar00rootroot00000000000000 org.fwupd.something.device 2d47f29b-83a2-4f31-a2e8-63474f4d4c2e

    Applying will enable UEFI firmware reporting

    https://fwupd.org/something.cab 0628376cb0583335a7ff5875dd2d4b09fc5653057f644783a477553ca9981dcd Google ChromeBook chromeos 1.2.2 1.9.1
    fwupd-1.9.16/src/tests/metadata-report3.xml000066400000000000000000000021621460375044200205720ustar00rootroot00000000000000 org.fwupd.something.device 2d47f29b-83a2-4f31-a2e8-63474f4d4c2e

    Applying will enable UEFI firmware reporting

    https://fwupd.org/something.cab 0628376cb0583335a7ff5875dd2d4b09fc5653057f644783a477553ca9981dcd Google ChromeBook chromeos 1.9.1
    fwupd-1.9.16/src/tests/metadata-report4.xml000066400000000000000000000024201460375044200205700ustar00rootroot00000000000000 org.fwupd.something.device 2d47f29b-83a2-4f31-a2e8-63474f4d4c2e

    Applying will enable UEFI firmware reporting

    https://fwupd.org/something.cab 0628376cb0583335a7ff5875dd2d4b09fc5653057f644783a477553ca9981dcd Google ChromeBook chromeos 1.2.3 1.9.1
    fwupd-1.9.16/src/tests/metadata.xml000066400000000000000000000007731460375044200172040ustar00rootroot00000000000000 org.fwupd.8330a096d9f1af8567c7374cb8403e1ce9cf3163.device 2d47f29b-83a2-4f31-a2e8-63474f4d4c2e

    Applying will enable UEFI firmware reporting

    fwupd-1.9.16/src/tests/missing-hwid/000077500000000000000000000000001460375044200172755ustar00rootroot00000000000000fwupd-1.9.16/src/tests/missing-hwid/firmware.bin000066400000000000000000000175001460375044200216060ustar00rootroot000000000000001 (~1 p~ 1(00'012!1 00@012!100@012!1@000001,!~ 1*! 02=!4W(4  9GHY(KJj(0G91H!!!3{(3  9GH}(ML(0G")! 0000@001!1# !Gw)0(1T!1(#!1!1)1'!1(#!18!1)1p!1!G")#!1!1)0 >000! ()00>00! ()0 >000! ()0!0000 >000! ))!^ !_ 1#1 W! V! U! T!0 >000! 5))$0!G)#!:N)!:N) 0!Gr)0?0@001x#1!GU)0?0000G1$1!H) )T:(:(:(:(:(:(:( :(:(:(/: )::) :@):(t)!3)3  9GH)ON)UG"!TG" 0!G@0H011#1!I0!G@0H011#!I B:*@98*! A:)! *! B@9:D9:D90[!2 : ,*D9>[D9>8*D9j>[D9>[_E*0[F*`N*`D9>S*`D9j>[A:t*`l*f*D9>i*D9>0`[0[`*0`[`00`[001$1* `0 [*`**D9>*D9>;0`[00`[001$1*;0 `[ D9>0=[\0]^[!*=00{0**! 2:+=+0 g!=+0 g=1!1=!+ 3+1%10 g!0 g=?+1!10 g=V+P+s0 0001$10 g=\+1!1=o+0 0001$1=0!2w+ =+ + go g 9h!0 g= o+!8>+!8>9 0g9g!+1%1+r0 0o0g001$10 g0i+=}+ 0 000 X0000Wc+Vb+Ua+T`,d + , X00=0=0=+Wc,Vb,Ua,T` ,00000000Wc6,Vb6,Ua6,T`b,d Y, U,00=0=0=hgfeU,0000 X00=0=0=(,1T!1 Xd >0>[ZYXhgfe1C"1t srqh_,g^,f],e\,0000hgfe ,! H!.:, ,!,! ,D9>,D9j>qr,r,0q,@9:,:,:,,!H00@0q44W44q4484f404644444444 44)4444444 444444444 4!44444"4444444@4444444@444444 444444)4@444&444u444@44444)4@444444H44u44g44h44s44k44i44 44L44t44d44.4444C44o44l44o44r44H44u44g44A44L44S4444444444?4'44444444444 44!=00u-st00=0qu-0=0{00v-v05>-q5 >0?0???0q0v-==-0q!-0q!0q0v!.vj>v>v>v>0q0v.(0q 0=@00q r00q! nA?vt!$L.s#R.#s0>v  @?sA?t=vu}.uH> s0s0se.0>.0| sH00|>s?t? n.0|s0|s0| s@00|>s?t?0|s! 1p% @9:DA:./!C:/#00 0.:0!0000q/ C!:.:.:// @ 9:@/0!0000q0q! Cq!0!0000q0q! Bq! A:U/: /:&/ :U/:/:4/U/!0 0]q/[\00=0{]a/ 0000 001!1= 0^/^>^>0{0^/0000001!1=(0{ 0{n{B{!/0{0 00{001$ 0{! x/R1u"1/ R1u" R>0=yzPQx1,1,1,1,1,1m,1v,1,1|,1y,1,1,1,  9 -)4d1 3)4q 44444444K)Q)!9$:I)Q)!  905 Z)9q 00 f) f)20b)  905 v) r 9qr9 q9qr69 q9q!t0 0001$=0{=0{! u0 0001$r9 q9qr69 q9qrq00;0v)0uwxs00=)=* n:*j:*0nq=0q m: *1<& !A*0| w@00|>w?x?0|w0 000qW*u v=w=x=05 X*06 _*tsrqL* SRQP 0rq05 }*v05>*s5r05>*t5uw*wv>*wvj>rww>=*01a!1018!101!101!11%1!0W*018!1_01a!1018!1_01a!10!V0W*d1"11= 1*! ! 0jb0b0jb0b0|b!1e$10 0001$11&q8+vj>:+v>tE+0r0w>t?u?sw t@0wt0wtqn+0vj>r+0v>tw@0r0q+0?0q0rvu+0?0s0tvu+006 +tx+sw+#wqxr=uvU00 00=+#0! m:+0| tH00|>t?u?0|t!B G+!A F+G! F!19&H00 j>t?u?0jtJ,1p!1 X!01!1018!!;1!0 X01'10 X@0Y011# Z!1& T:P,d,S:+,:G,:d,w:d,:&,:,d, @ 9:,!0 a!1#'1V'0!0@ 1$1! D>!0@ ! BD>a00001$ A 015>@0r0q,006 ,ty,sx-#rq00=xuv0x>0y=tw,sv-x>uv0U0000=,#0 j000t!2:3- B=.-0/-0t! m:j-H00j>t?u?19&1!=:U-0 jta-0 jt0 jt! !0q0q m:-j 0jqm:!0 n q@00n>q?r?0nq! 0| q@00|>q?r?0|q o9..o6?905>-c5 >dnc0cn 9 :+.0f.n>@?A?cdf@>en>0A1 0c0f-@00n>c?d?1"1"0 oc:1%0q0!B0AW.AqO. W.=:W.0q0>s  @?qA?r=s j qH0q!@..>?<q00=0q0y.>?<q00=0q0.=!..0v !.1#!0| v@00|>v?w?0|v0 jv . !.1#1! 1p%0v 0| v@00|>v?w?0|v! 1p% @:0q!/0!000 B5 0q0r=?!??>0qrr s?>0qrr s0 B/B5 0q0r=?!?>?qr!! C:./:8/:h//fwupd-1.9.16/src/tests/missing-hwid/firmware.metainfo.xml000066400000000000000000000010041460375044200234270ustar00rootroot00000000000000 com.hughski.test.firmware 12345678-1234-1234-1234-123456789012 9342d47a-1bab-5709-9869-c840b2eac501 org.freedesktop.fwupd fwupd-1.9.16/src/tests/missing-hwid/firmware2.metainfo.xml000066400000000000000000000005531460375044200235210ustar00rootroot00000000000000 com.hughski.test.firmware 12345678-1234-1234-1234-123456789012 fwupd-1.9.16/src/tests/missing-hwid/meson.build000066400000000000000000000007151460375044200214420ustar00rootroot00000000000000hwid_test_firmware = custom_target('hwid-test-firmware', input: [ 'firmware.bin', 'firmware.metainfo.xml', ], output: 'hwid-1.2.3.cab', command: [ gcab, '--create', '--nopath', '@OUTPUT@', '@INPUT@', ], ) noreqs_test_firmware = custom_target('noreqs-test-firmware', input: [ 'firmware.bin', 'firmware2.metainfo.xml', ], output: 'noreqs-1.2.3.cab', command: [ gcab, '--create', '--nopath', '@OUTPUT@', '@INPUT@', ], ) fwupd-1.9.16/src/tests/multiple-rels/000077500000000000000000000000001460375044200174715ustar00rootroot00000000000000fwupd-1.9.16/src/tests/multiple-rels/firmware-123.bin000066400000000000000000000000111460375044200222720ustar00rootroot000000000000000x1020003fwupd-1.9.16/src/tests/multiple-rels/firmware-124.bin000066400000000000000000000000111460375044200222730ustar00rootroot000000000000000x1020004fwupd-1.9.16/src/tests/multiple-rels/firmware.metainfo.xml000066400000000000000000000010731460375044200236310ustar00rootroot00000000000000 com.hughski.test.firmware 12345678-1234-1234-1234-123456789012 fwupd-1.9.16/src/tests/multiple-rels/meson.build000066400000000000000000000004321460375044200216320ustar00rootroot00000000000000multiple_rels_test_firmware = custom_target('multiple-rels-test-firmware', input: [ 'firmware-123.bin', 'firmware-124.bin', 'firmware.metainfo.xml', ], output: 'multiple-rels-1.2.4.cab', command: [ gcab, '--create', '--nopath', '@OUTPUT@', '@INPUT@', ], ) fwupd-1.9.16/src/tests/os-release000066400000000000000000000002321460375044200166520ustar00rootroot00000000000000ID_LIKE=chromiumos GOOGLE_CRASH_ID=ChromeOS NAME=Chrome OS ID=chromeos HOME_URL=https://www.chromium.org/chromium-os BUG_REPORT_URL=https://crbug.com/new fwupd-1.9.16/src/tests/remotes.d/000077500000000000000000000000001460375044200165735ustar00rootroot00000000000000fwupd-1.9.16/src/tests/remotes.d/broken.conf000066400000000000000000000001221460375044200207150ustar00rootroot00000000000000[fwupd Remote] Enabled=true MetadataURI=file:///tmp/fwupd-self-test/broken.xml.gz fwupd-1.9.16/src/tests/remotes.d/directory.conf000066400000000000000000000001411460375044200214420ustar00rootroot00000000000000[fwupd Remote] Enabled=true Keyring=none MetadataURI=file:///tmp/fwupd-self-test/var/cache/fwupd fwupd-1.9.16/src/tests/remotes.d/legacy-lvfs-xz.conf000066400000000000000000000001101460375044200223050ustar00rootroot00000000000000[fwupd Remote] Enabled=false MetadataURI=http://localhost/stable.xml.xz fwupd-1.9.16/src/tests/remotes.d/legacy-lvfs.conf000066400000000000000000000001101460375044200216460ustar00rootroot00000000000000[fwupd Remote] Enabled=false MetadataURI=http://localhost/stable.xml.gz fwupd-1.9.16/src/tests/remotes.d/legacy.conf000066400000000000000000000001101460375044200206760ustar00rootroot00000000000000[fwupd Remote] Enabled=false MetadataURI=http://localhost/stable.xml.gz fwupd-1.9.16/src/tests/remotes.d/stable.conf000066400000000000000000000001171460375044200207130ustar00rootroot00000000000000[fwupd Remote] Enabled=true MetadataURI=file:///tmp/fwupd-self-test/stable.xml fwupd-1.9.16/src/tests/remotes.d/testing.conf000066400000000000000000000001461460375044200211200ustar00rootroot00000000000000[fwupd Remote] Enabled=true MetadataURI=file:///tmp/fwupd-self-test/testing.xml ApprovalRequired=true fwupd-1.9.16/src/tests/spawn.sh000077500000000000000000000002701460375044200163610ustar00rootroot00000000000000#!/bin/sh echo "this is a test" sleep 1 echo "this is another line1" echo "this is another line2" echo "this is another line3" echo "this is another line4" sleep 1 echo "done!" exit 0 fwupd-1.9.16/src/tests/usb-devices-bootloader.json000066400000000000000000000002501460375044200221240ustar00rootroot00000000000000{ "UsbDevices": [ { "PlatformId": "usb:01:00:06", "Created": "9999-02-03T13:39:21.538713Z", "IdVendor": 999, "IdProduct": 999 } ] } fwupd-1.9.16/src/tests/usb-devices-invalid.json000066400000000000000000000032051460375044200214230ustar00rootroot00000000000000{ "UsbDevices": [ { "PlatformId": "usb:00", "Created": "2023-02-03T13:39:21.538713Z", "IdVendor": 10047, "IdProduct": 4100, "Device": 2, "USB": 513, "Manufacturer": 1, "Product": 2, "UsbBosDescriptors": [ { "Comment": "version invalid", "DevCapabilityType": 5, "ExtraData": "AGPsCgF09c1SndooUlUNlPAKAAAAIAAqAA==" }, { "Comment": "UUID invalid", "DevCapabilityType": 5, "ExtraData": "AAAAAAAAAAAAAAAAAAAAAAAFCAEAIAAqAA==" }, { "Comment": "plugin invalid", "DevCapabilityType": 5, "ExtraData": "AGPsCgF09c1SndooUlUNlPAFCAEAIAArAA==" } ], "UsbEvents": [ { "Id": "GetStringDescriptor:DescIndex=0x02", "Data": "Q29sb3JIdWcyAEcAAAAAAACwA4pgfwAAAN+Vneb9GHkAAAAAAAAAAEA42QAAAAAAwHVdKPx/AAAAAAAAAAAAAJiCXSj8fwAAR9bLiWB/AADAdV0o/H8AAOrE/IlgfwAAgHW/AAAAAAAQw9UAAAAAAJiCXSj8fwAAytnLiQEAAAA=" }, { "Comment": "Plugin=dfu\nIcon=computer\n", "Id": "ControlTransfer:Direction=0x00,RequestType=0x02,Recipient=0x00,Request=0x2a,Value=0x0000,Idx=0x0007,Data=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=,Length=0x20", "Data": "UGx1Z2luPWRmdQpJY29uPWNvbXB1dGVyCgAAAAAAAAA=" }, { "Comment": "Plugin=XXX", "Id": "ControlTransfer:Direction=0x00,RequestType=0x02,Recipient=0x00,Request=0x2b,Value=0x0000,Idx=0x0007,Data=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=,Length=0x20", "Data": "UGx1Z2luPVhYWAoAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" } ] } ] } fwupd-1.9.16/src/tests/usb-devices-replace.json000066400000000000000000000002531460375044200214100ustar00rootroot00000000000000{ "UsbDevices": [ { "PlatformId": "usb:01:00:06", "Created": "2023-02-03T13:39:21.538713Z", "IdVendor": 10047, "IdProduct": 4100 } ] } fwupd-1.9.16/src/tests/usb-devices.json000066400000000000000000000151431460375044200200030ustar00rootroot00000000000000{ "UsbDevices": [ { "PlatformId": "usb:01:00:06", "Created": "2023-02-03T13:39:21.538713Z", "IdVendor": 10047, "IdProduct": 4100, "Device": 2, "USB": 513, "Manufacturer": 1, "Product": 2, "UsbBosDescriptors": [ { "DevCapabilityType": 5, "ExtraData": "AN9g3diJRcdMnNJlnZ5kip8AAAMG4AQVAA==" }, { "DevCapabilityType": 5, "ExtraData": "AGPsCgF09c1SndooUlUNlPAFCAEAIAAqAA==" }, { "DevCapabilityType": 17, "ExtraData": "AQMAAAA=" } ], "UsbInterfaces": [ { "Length": 9, "DescriptorType": 4, "InterfaceNumber": 1, "InterfaceClass": 255, "InterfaceSubClass": 70, "InterfaceProtocol": 87, "Interface": 3 }, { "Length": 9, "DescriptorType": 4, "InterfaceNumber": 2, "InterfaceClass": 255, "InterfaceSubClass": 71, "InterfaceProtocol": 85, "Interface": 4 }, { "Length": 9, "DescriptorType": 4, "InterfaceClass": 3, "UsbEndpoints": [ { "DescriptorType": 5, "EndpointAddress": 129, "Interval": 1, "MaxPacketSize": 64 }, { "DescriptorType": 5, "EndpointAddress": 1, "Interval": 1, "MaxPacketSize": 64 } ], "ExtraData": "CSERAQABIh0A" } ], "UsbEvents": [ { "Id": "GetStringDescriptor:DescIndex=0x01", "Data": "SHVnaHNraSBMdGQuAAAAAAAAAAAAAAAAIFjfAAAAAAAAAAAAAAAAAEA42QAAAAAAwHVdKPx/AAAAAAAAAAAAAJiCXSj8fwAAR9bLiWB/AADAdV0o/H8AAOrE/IlgfwAAgHW/AAAAAAAQw9UAAAAAAJiCXSj8fwAAytnLiQEAAAA=" }, { "Id": "GetStringDescriptor:DescIndex=0x02", "Data": "Q29sb3JIdWcyAEcAAAAAAACwA4pgfwAAAN+Vneb9GHkAAAAAAAAAAEA42QAAAAAAwHVdKPx/AAAAAAAAAAAAAJiCXSj8fwAAR9bLiWB/AADAdV0o/H8AAOrE/IlgfwAAgHW/AAAAAAAQw9UAAAAAAJiCXSj8fwAAytnLiQEAAAA=" }, { "Id": "GetCustomIndex:ClassId=0xff,SubclassId=0x46,ProtocolId=0x57", "Data": "Aw==" }, { "Id": "GetStringDescriptor:DescIndex=0x03", "Data": "Mi4wLjcAAAAD0WmJYH8AAP8AAAAAAAAAA9FpiWB/AACQRNkAAAAAAGCj2wAAAAAAUHZdKPx/AACNC7qJYH8AAAMAAAAAAAAANougiWB/AACYgl0o/H8AAAAAAAAAAAAA/wAAAPx/V0Zgo9sAAAAAAEh5XSj8fwAAEMPVAAAAAAM=" }, { "Id": "GetCustomIndex:ClassId=0xff,SubclassId=0x47,ProtocolId=0x55", "Data": "BA==" }, { "Id": "ControlTransfer:Direction=0x00,RequestType=0x02,Recipient=0x00,Request=0x15,Value=0x0000,Idx=0x0007,Data=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,Length=0x4e0", "Data": "CgAAAAAAAwbgBAgAAQAAANYECAACAAAAkgGAAAQAAQAoAFUAVgBDAC0ARgBTAFMAZQBuAHMAbwByAEcAcgBvAHUAcABJAEQAAABOAHsARgA2ADYARgBBADYANwA0AC0ARgAyAEIARQAtADQARAAxADkALQA5ADgAQwA1AC0ARAAxADMAMgAzADIAOQBDADYANAAyAEYAfQAAAF4ABAABACwAVQBWAEMALQBGAFMAUwBlAG4AcwBvAHIARwByAG8AdQBwAE4AYQBtAGUAAAAoAEwAZQBuAG8AdgBvACAAQwBhAG0AZQByAGEAIABHAHIAbwB1AHAAAAA8AAQABAAuAFUAVgBDAC0ARQBuAGEAYgBsAGUAUABsAGEAdABmAG8AcgBtAEQAbQBmAHQAAAAEAAEAAAA+AAQABAAwAEUAbgBhAGIAbABlAEQAcwBoAG8AdwBSAGUAZABpAHIAZQBjAHQAaQBvAG4AAAAAAAQAAQAAADIABAAEACQAVQBWAEMALQBDAFAAVgAyAEYAYQBjAGUAQQB1AHQAaAAAAAAABAD//wAACAACAAIAwAGAAAQAAQAoAFUAVgBDAC0ARgBTAFMAZQBuAHMAbwByAEcAcgBvAHUAcABJAEQAAABOAHsARgA2ADYARgBBADYANwA0AC0ARgAyAEIARQAtADQARAAxADkALQA5ADgAQwA1AC0ARAAxADMAMgAzADIAOQBDADYANAAyAEYAfQAAAF4ABAABACwAVQBWAEMALQBGAFMAUwBlAG4AcwBvAHIARwByAG8AdQBwAE4AYQBtAGUAAAAoAEwAZQBuAG8AdgBvACAAQwBhAG0AZQByAGEAIABHAHIAbwB1AHAAAAAwAAQABAAiAFMAZQBuAHMAbwByAEMAYQBtAGUAcgBhAE0AbwBkAGUAAAAEAAEAAAA6AAQABAAsAFMAawBpAHAAQwBhAG0AZQByAGEARQBuAHUAbQBlAHIAYQB0AGkAbwBuAAAABAABAAAAPgAEAAQAMABFAG4AYQBiAGwAZQBEAHMAaABvAHcAUgBlAGQAaQByAGUAYwB0AGkAbwBuAAAAAAAEAAEAAAAyAAQABAAkAFUAVgBDAC0AQwBQAFYAMgBGAGEAYwBlAEEAdQB0AGgAAAAAAAQAAAD//wgAAgAEAHwBMgAEAAQAJABEAGUAdgBpAGMAZQBJAGQAbABlAEUAbgBhAGIAbABlAGQAAAAEAAEAAAAyAAQABAAkAEQAZQBmAGEAdQBsAHQASQBkAGwAZQBTAHQAYQB0AGUAAAAAAAQAAQAAADYABAAEACgARABlAGYAYQB1AGwAdABJAGQAbABlAFQAaQBtAGUAbwB1AHQAAAAAAAQAiBMAAEYABAAEADgARABlAHYAaQBjAGUASQBkAGwAZQBJAGcAbgBvAHIAZQBXAGEAawBlAEUAbgBhAGIAbABlAAAAAAAEAAEAAAAUAAMAV0lOVVNCAAAAAAAAAAAAAIAABAABACgARABlAHYAaQBjAGUASQBuAHQAZQByAGYAYQBjAGUARwBVAEkARAAAAE4AewBlAGMAYwBlAGYAZgAzADUALQAxADQANgAzAC0ANABmAGYAMwAtAGEAYwBkADkALQA4AGYAOQA5ADIAZAAwADkAYQBjAGQAZAB9AAAA" }, { "Id": "ControlTransfer:Direction=0x00,RequestType=0x02,Recipient=0x00,Request=0x2a,Value=0x0000,Idx=0x0007,Data=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=,Length=0x20", "Data": "UGx1Z2luPWRmdQpJY29uPWNvbXB1dGVyCgAAAAAAAAA=" }, { "Id": "GetStringDescriptor:DescIndex=0x04", "Data": "MjA4MmI1ZTAtN2E2NC00NzhhLWIxYjItZTM0MDRmYWI2ZGFkAAAAAICg2QAAAAAAUHZdKPx/AACNC7qJYH8AAAQAAAAAAAAANougiWB/AAAAsAOKYH8AAAAAAAAAAAAA/wAAAAAAVUeAoNkAAAAAAFB2XSj8fwAAsKPbAAAAAAQ=" } ] } ] } fwupd-1.9.16/subprojects/000077500000000000000000000000001460375044200153055ustar00rootroot00000000000000fwupd-1.9.16/subprojects/.gitignore000066400000000000000000000000711460375044200172730ustar00rootroot00000000000000gusb gi-docgen flashrom libjcat libxmlb fwupd-efi passim fwupd-1.9.16/subprojects/flashrom.wrap000066400000000000000000000001331460375044200200100ustar00rootroot00000000000000[wrap-git] directory = flashrom url = https://github.com/flashrom/flashrom revision = v1.2 fwupd-1.9.16/subprojects/fwupd-efi.wrap000066400000000000000000000001761460375044200200720ustar00rootroot00000000000000[wrap-git] directory = fwupd-efi url = https://github.com/fwupd/fwupd-efi revision = 5ae565488cf71f05b90fe7e8832ce089bd92b3ac fwupd-1.9.16/subprojects/gi-docgen.wrap000066400000000000000000000001501460375044200200300ustar00rootroot00000000000000[wrap-git] directory=gi-docgen url=https://gitlab.gnome.org/GNOME/gi-docgen.git revision=2021.8 depth=1 fwupd-1.9.16/subprojects/gusb.wrap000066400000000000000000000001321460375044200171340ustar00rootroot00000000000000[wrap-git] directory = gusb url = https://github.com/hughsie/libgusb.git revision = 0.4.6 fwupd-1.9.16/subprojects/libjcat.wrap000066400000000000000000000001361460375044200176100ustar00rootroot00000000000000[wrap-git] directory = libjcat url = https://github.com/hughsie/libjcat.git revision = 0.1.14 fwupd-1.9.16/subprojects/libxmlb.wrap000066400000000000000000000001361460375044200176310ustar00rootroot00000000000000[wrap-git] directory = libxmlb url = https://github.com/hughsie/libxmlb.git revision = 0.3.12 fwupd-1.9.16/subprojects/passim.wrap000066400000000000000000000001331460375044200174710ustar00rootroot00000000000000[wrap-git] directory = passim url = https://github.com/hughsie/passim.git revision = 0.1.2